Skip to content

Commit

Permalink
[lexical] Feature: Add version identifier to LexicalEditor constructor (
Browse files Browse the repository at this point in the history
  • Loading branch information
etrepum authored Aug 6, 2024
1 parent e1881a6 commit 20e4ea1
Show file tree
Hide file tree
Showing 18 changed files with 177 additions and 89 deletions.
4 changes: 2 additions & 2 deletions packages/lexical-devtools-core/src/generateContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,8 @@ export function generateContent(
} else {
res += '\n └ None dispatched.';
}

res += '\n\n editor:';
const {version} = editor.constructor;
res += `\n\n editor${version ? ` (v${version})` : ''}:`;
res += `\n └ namespace ${editorConfig.namespace}`;
if (compositionKey !== null) {
res += `\n └ compositionKey ${compositionKey}`;
Expand Down
4 changes: 3 additions & 1 deletion packages/lexical-devtools/src/utils/isLexicalNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
*
*/

import {getEditorPropertyFromDOMNode} from 'lexical';

import {LexicalHTMLElement} from '../types';

export function isLexicalNode(
node: LexicalHTMLElement | Element,
): node is LexicalHTMLElement {
return (node as LexicalHTMLElement).__lexicalEditor !== undefined;
return getEditorPropertyFromDOMNode(node) !== undefined;
}
4 changes: 4 additions & 0 deletions packages/lexical-playground/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ export default defineConfig(({command}) => {
from: /__DEV__/g,
to: 'true',
},
{
from: 'process.env.LEXICAL_VERSION',
to: JSON.stringify(`${process.env.npm_package_version}+git`),
},
],
}),
babel({
Expand Down
4 changes: 4 additions & 0 deletions packages/lexical-playground/vite.prod.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ export default defineConfig({
from: /__DEV__/g,
to: 'false',
},
{
from: 'process.env.LEXICAL_VERSION',
to: JSON.stringify(`${process.env.npm_package_version}+git`),
},
],
}),
babel({
Expand Down
31 changes: 8 additions & 23 deletions packages/lexical-react/src/LexicalCheckListPlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
$isElementNode,
$isRangeSelection,
COMMAND_PRIORITY_LOW,
getNearestEditorFromDOMNode,
KEY_ARROW_DOWN_COMMAND,
KEY_ARROW_LEFT_COMMAND,
KEY_ARROW_UP_COMMAND,
Expand Down Expand Up @@ -199,20 +200,20 @@ function handleCheckItemEvent(event: PointerEvent, callback: () => void) {

function handleClick(event: Event) {
handleCheckItemEvent(event as PointerEvent, () => {
const domNode = event.target as HTMLElement;
const editor = findEditor(domNode);
if (event.target instanceof HTMLElement) {
const domNode = event.target;
const editor = getNearestEditorFromDOMNode(domNode);

if (editor != null && editor.isEditable()) {
editor.update(() => {
if (event.target) {
if (editor != null && editor.isEditable()) {
editor.update(() => {
const node = $getNearestNodeFromDOMNode(domNode);

if ($isListItemNode(node)) {
domNode.focus();
node.toggleChecked();
}
}
});
});
}
}
});
}
Expand All @@ -224,22 +225,6 @@ function handlePointerDown(event: PointerEvent) {
});
}

function findEditor(target: Node) {
let node: ParentNode | Node | null = target;

while (node) {
// @ts-ignore internal field
if (node.__lexicalEditor) {
// @ts-ignore internal field
return node.__lexicalEditor;
}

node = node.parentNode;
}

return null;
}

function getActiveCheckListItem(): HTMLElement | null {
const activeElement = document.activeElement as HTMLElement;

Expand Down
2 changes: 1 addition & 1 deletion packages/lexical-website/docs/concepts/listeners.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ handle external UI state and UI features relating to specific types of node.
If any existing nodes are in the DOM, and skipInitialization is not true, the listener
will be called immediately with an updateTag of 'registerMutationListener' where all
nodes have the 'created' NodeMutation. This can be controlled with the skipInitialization option
(default is currently true for backwards compatibility in 0.16.x but will change to false in 0.17.0).
(default is currently true for backwards compatibility in 0.17.x but will change to false in 0.18.0).

```js
const removeMutationListener = editor.registerMutationListener(
Expand Down
5 changes: 5 additions & 0 deletions packages/lexical/src/LexicalEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,9 @@ export function createEditor(editorConfig?: CreateEditorArgs): LexicalEditor {
export class LexicalEditor {
['constructor']!: KlassConstructor<typeof LexicalEditor>;

/** The version with build identifiers for this editor (since 0.17.1) */
static version: string | undefined;

/** @internal */
_headless: boolean;
/** @internal */
Expand Down Expand Up @@ -1284,3 +1287,5 @@ export class LexicalEditor {
};
}
}

LexicalEditor.version = process.env.LEXICAL_VERSION;
12 changes: 9 additions & 3 deletions packages/lexical/src/LexicalEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ import {
getAnchorTextFromDOM,
getDOMSelection,
getDOMTextNode,
getEditorPropertyFromDOMNode,
getEditorsToPropagate,
getNearestEditorFromDOMNode,
getWindow,
Expand All @@ -111,6 +112,7 @@ import {
isEscape,
isFirefoxClipboardEvents,
isItalic,
isLexicalEditor,
isLineBreak,
isModifier,
isMoveBackward,
Expand Down Expand Up @@ -1329,13 +1331,17 @@ export function removeRootElementEvents(rootElement: HTMLElement): void {
doc.removeEventListener('selectionchange', onDocumentSelectionChange);
}

// @ts-expect-error: internal field
const editor: LexicalEditor | null | undefined = rootElement.__lexicalEditor;
const editor = getEditorPropertyFromDOMNode(rootElement);

if (editor !== null && editor !== undefined) {
if (isLexicalEditor(editor)) {
cleanActiveNestedEditorsMap(editor);
// @ts-expect-error: internal field
rootElement.__lexicalEditor = null;
} else if (editor) {
invariant(
false,
'Attempted to remove event handlers from a node that does not belong to this build of Lexical',
);
}

const removeHandles = getRootElementRemoveHandles(rootElement);
Expand Down
60 changes: 48 additions & 12 deletions packages/lexical/src/LexicalUpdates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,24 @@
*
*/

import type {
import type {SerializedEditorState} from './LexicalEditorState';
import type {LexicalNode, SerializedLexicalNode} from './LexicalNode';

import invariant from 'shared/invariant';

import {$isElementNode, $isTextNode, SELECTION_CHANGE_COMMAND} from '.';
import {FULL_RECONCILE, NO_DIRTY_NODES} from './LexicalConstants';
import {
CommandPayloadType,
EditorUpdateOptions,
LexicalCommand,
LexicalEditor,
Listener,
MutatedNodes,
RegisteredNodes,
resetEditor,
Transform,
} from './LexicalEditor';
import type {SerializedEditorState} from './LexicalEditorState';
import type {LexicalNode, SerializedLexicalNode} from './LexicalNode';

import invariant from 'shared/invariant';

import {$isElementNode, $isTextNode, SELECTION_CHANGE_COMMAND} from '.';
import {FULL_RECONCILE, NO_DIRTY_NODES} from './LexicalConstants';
import {resetEditor} from './LexicalEditor';
import {
cloneEditorState,
createEmptyEditorState,
Expand All @@ -47,9 +47,11 @@ import {
import {
$getCompositionKey,
getDOMSelection,
getEditorPropertyFromDOMNode,
getEditorStateTextContent,
getEditorsToPropagate,
getRegisteredNodeOrThrow,
isLexicalEditor,
removeDOMBlockCursorElement,
scheduleMicroTask,
updateDOMBlockCursorElement,
Expand Down Expand Up @@ -96,7 +98,8 @@ export function getActiveEditorState(): EditorState {
'Unable to find an active editor state. ' +
'State helpers or node methods can only be used ' +
'synchronously during the callback of ' +
'editor.update(), editor.read(), or editorState.read().',
'editor.update(), editor.read(), or editorState.read().%s',
collectBuildInformation(),
);
}

Expand All @@ -110,13 +113,46 @@ export function getActiveEditor(): LexicalEditor {
'Unable to find an active editor. ' +
'This method can only be used ' +
'synchronously during the callback of ' +
'editor.update() or editor.read().',
'editor.update() or editor.read().%s',
collectBuildInformation(),
);
}

return activeEditor;
}

function collectBuildInformation(): string {
let compatibleEditors = 0;
const incompatibleEditors = new Set<string>();
const thisVersion = LexicalEditor.version;
if (typeof window !== 'undefined') {
for (const node of document.querySelectorAll('[contenteditable]')) {
const editor = getEditorPropertyFromDOMNode(node);
if (isLexicalEditor(editor)) {
compatibleEditors++;
} else if (editor) {
let version = String(
(
editor.constructor as typeof editor['constructor'] &
Record<string, unknown>
).version || '<0.17.1',
);
if (version === thisVersion) {
version +=
' (separately built, likely a bundler configuration issue)';
}
incompatibleEditors.add(version);
}
}
}
let output = ` Detected on the page: ${compatibleEditors} compatible editor(s) with version ${thisVersion}`;
if (incompatibleEditors.size) {
output += ` and incompatible editors with versions ${Array.from(
incompatibleEditors,
).join(', ')}`;
}
return output;
}

export function internalGetActiveEditor(): LexicalEditor | null {
return activeEditor;
}
Expand Down
22 changes: 17 additions & 5 deletions packages/lexical/src/LexicalUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,7 @@ export function isSelectionCapturedInDecoratorInput(anchorDOM: Node): boolean {
(nodeName === 'INPUT' ||
nodeName === 'TEXTAREA' ||
(activeElement.contentEditable === 'true' &&
// @ts-ignore internal field
activeElement.__lexicalEditor == null))
getEditorPropertyFromDOMNode(activeElement) == null))
);
}

Expand All @@ -149,21 +148,34 @@ export function isSelectionWithinEditor(
}
}

/**
* @returns true if the given argument is a LexicalEditor instance from this build of Lexical
*/
export function isLexicalEditor(editor: unknown): editor is LexicalEditor {
// Check instanceof to prevent issues with multiple embedded Lexical installations
return editor instanceof LexicalEditor;
}

export function getNearestEditorFromDOMNode(
node: Node | null,
): LexicalEditor | null {
let currentNode = node;
while (currentNode != null) {
// @ts-expect-error: internal field
const editor: LexicalEditor = currentNode.__lexicalEditor;
if (editor != null) {
const editor = getEditorPropertyFromDOMNode(currentNode);
if (isLexicalEditor(editor)) {
return editor;
}
currentNode = getParentElement(currentNode);
}
return null;
}

/** @internal */
export function getEditorPropertyFromDOMNode(node: Node | null): unknown {
// @ts-expect-error: internal field
return node ? node.__lexicalEditor : null;
}

export function getTextDirection(text: string): 'ltr' | 'rtl' | null {
if (RTL_REGEX.test(text)) {
return 'rtl';
Expand Down
2 changes: 2 additions & 0 deletions packages/lexical/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,11 +176,13 @@ export {
$setCompositionKey,
$setSelection,
$splitNode,
getEditorPropertyFromDOMNode,
getNearestEditorFromDOMNode,
isBlockDomNode,
isHTMLAnchorElement,
isHTMLElement,
isInlineDomNode,
isLexicalEditor,
isSelectionCapturedInDecoratorInput,
isSelectionWithinEditor,
resetRandomKey,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
{
"name": "lexical-esm-astro-react",
"type": "module",
"version": "0.0.1",
"version": "0.17.0",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro check && astro build",
"preview": "astro preview",
"astro": "astro",
"astro": "astro",
"test": "playwright test"
},
"dependencies": {
"@astrojs/check": "^0.5.9",
"@astrojs/react": "^3.1.0",
"@lexical/react": "^0.14.3",
"@lexical/utils": "^0.14.3",
"@lexical/react": "0.17.0",
"@lexical/utils": "0.17.0",
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
"astro": "^4.5.4",
"lexical": "^0.14.3",
"lexical": "0.17.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"typescript": "^5.4.2"
},
"devDependencies": {
"@playwright/test": "^1.43.1"
}
},
"sideEffects": false
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "lexical-esm-nextjs",
"version": "0.1.0",
"version": "0.17.0",
"private": true,
"scripts": {
"dev": "next dev",
Expand All @@ -9,9 +9,9 @@
"test": "playwright test"
},
"dependencies": {
"@lexical/plain-text": "^0.14.5",
"@lexical/react": "^0.14.5",
"lexical": "^0.14.5",
"@lexical/plain-text": "0.17.0",
"@lexical/react": "0.17.0",
"lexical": "0.17.0",
"next": "^14.2.1",
"react": "^18",
"react-dom": "^18"
Expand Down
Loading

0 comments on commit 20e4ea1

Please sign in to comment.