From dec2f63663ecc11da9f6723dabe4b6a643eb2a51 Mon Sep 17 00:00:00 2001 From: John Flockton Date: Fri, 17 Jun 2022 11:14:59 +0100 Subject: [PATCH 1/5] Migrate first few packages --- packages/lexical-code/src/index.ts | 2 +- packages/lexical/src/LexicalSelection.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/lexical-code/src/index.ts b/packages/lexical-code/src/index.ts index 23b7aa47700..be6d18e7021 100644 --- a/packages/lexical-code/src/index.ts +++ b/packages/lexical-code/src/index.ts @@ -1075,7 +1075,7 @@ function handleShiftLines( function handleMoveTo( type: LexicalCommand, event: KeyboardEvent, -): boolean { +) { const selection = $getSelection(); if (!$isRangeSelection(selection)) { return false; diff --git a/packages/lexical/src/LexicalSelection.ts b/packages/lexical/src/LexicalSelection.ts index a253f14e3fe..070b73c6115 100644 --- a/packages/lexical/src/LexicalSelection.ts +++ b/packages/lexical/src/LexicalSelection.ts @@ -995,7 +995,10 @@ export class RangeSelection implements BaseSelection { markedNodeKeysForKeep.delete(parent.__key); lastRemovedParent = parent; } - parent = parent.getParent(); + const grandParent = parent.getParent(); + if (grandParent != null) { + parent = grandParent; + } } } From efcbd982119c1efd5ca42d1fe491889ba4d3a936 Mon Sep 17 00:00:00 2001 From: John Flockton Date: Fri, 17 Jun 2022 13:57:54 +0100 Subject: [PATCH 2/5] Fix failing tests --- packages/lexical-code/src/index.ts | 2 +- packages/lexical/src/LexicalSelection.ts | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/lexical-code/src/index.ts b/packages/lexical-code/src/index.ts index be6d18e7021..23b7aa47700 100644 --- a/packages/lexical-code/src/index.ts +++ b/packages/lexical-code/src/index.ts @@ -1075,7 +1075,7 @@ function handleShiftLines( function handleMoveTo( type: LexicalCommand, event: KeyboardEvent, -) { +): boolean { const selection = $getSelection(); if (!$isRangeSelection(selection)) { return false; diff --git a/packages/lexical/src/LexicalSelection.ts b/packages/lexical/src/LexicalSelection.ts index 070b73c6115..a253f14e3fe 100644 --- a/packages/lexical/src/LexicalSelection.ts +++ b/packages/lexical/src/LexicalSelection.ts @@ -995,10 +995,7 @@ export class RangeSelection implements BaseSelection { markedNodeKeysForKeep.delete(parent.__key); lastRemovedParent = parent; } - const grandParent = parent.getParent(); - if (grandParent != null) { - parent = grandParent; - } + parent = parent.getParent(); } } From eddf3297c3fe9743a8f3c183c6bea5ed0bb7d6da Mon Sep 17 00:00:00 2001 From: John Flockton Date: Fri, 17 Jun 2022 11:59:26 +0100 Subject: [PATCH 3/5] Migrate more types --- packages/lexical-devtools/src/main.tsx | 2 +- packages/lexical-file/src/fileImportExport.ts | 34 ++++++--- packages/lexical-headless/src/index.ts | 8 ++- packages/lexical-html/src/index.ts | 25 +++---- packages/lexical-link/src/index.ts | 6 +- .../lexical-list/src/LexicalListItemNode.ts | 10 +-- packages/lexical-list/src/formatList.ts | 13 ++-- packages/lexical-list/src/utils.ts | 6 +- packages/lexical-mark/src/index.ts | 6 +- packages/lexical-markdown/src/utils.ts | 31 +++++---- .../lexical-markdown/src/v2/MarkdownImport.ts | 10 +-- .../src/v2/MarkdownShortcuts.ts | 4 +- packages/lexical-markdown/src/v2/utils.ts | 2 +- packages/lexical-offset/src/index.ts | 2 +- .../src/plugins/TableActionMenuPlugin.tsx | 5 +- .../src/LexicalAutoFocusPlugin.ts | 4 +- .../src/LexicalAutoLinkPlugin.ts | 2 +- .../src/LexicalAutoScrollPlugin.ts | 2 +- .../src/LexicalBlockWithAlignableContents.tsx | 7 +- .../src/LexicalCharacterLimitPlugin.tsx | 4 +- .../src/LexicalCheckListPlugin.tsx | 34 +++++---- .../src/LexicalClearEditorPlugin.ts | 2 +- .../src/LexicalComposerContext.ts | 2 +- .../src/LexicalContentEditable.tsx | 6 +- .../lexical-react/src/LexicalHashtagPlugin.ts | 2 +- .../src/LexicalNestedComposer.tsx | 2 +- .../src/LexicalPlainTextPlugin.tsx | 6 +- .../src/LexicalRichTextPlugin.tsx | 6 +- .../lexical-react/src/LexicalTablePlugin.ts | 2 +- .../lexical-react/src/LexicalTreeView.tsx | 69 ++++++++++++------- .../src/shared/useEditorEvents.ts | 4 +- .../src/shared/useYjsCollaboration.tsx | 13 ++-- .../lexical-table/flow/LexicalTable.js.flow | 1 - .../lexical-table/src/LexicalTableCellNode.ts | 8 +-- .../lexical-table/src/LexicalTableNode.ts | 2 +- .../lexical-table/src/LexicalTableRowNode.ts | 8 +-- .../src/LexicalTableSelection.ts | 13 ++-- .../src/LexicalTableSelectionHelpers.ts | 18 +++-- .../lexical-table/src/LexicalTableUtils.ts | 4 +- packages/lexical-table/src/index.ts | 2 + packages/lexical-utils/src/index.ts | 20 +++--- packages/lexical-yjs/src/CollabElementNode.ts | 9 +-- packages/lexical-yjs/src/SyncCursors.ts | 2 +- packages/lexical-yjs/src/SyncEditorStates.ts | 6 +- packages/lexical/src/LexicalNode.ts | 4 +- packages/shared/src/getDOMSelection.ts | 2 +- 46 files changed, 259 insertions(+), 171 deletions(-) diff --git a/packages/lexical-devtools/src/main.tsx b/packages/lexical-devtools/src/main.tsx index 9feb1257db8..4941c0272d7 100644 --- a/packages/lexical-devtools/src/main.tsx +++ b/packages/lexical-devtools/src/main.tsx @@ -13,7 +13,7 @@ import * as ReactDOM from 'react-dom/client'; import App from './App'; -ReactDOM.createRoot(document.getElementById('root')).render( +ReactDOM.createRoot(document.getElementById('root') as Element).render( , diff --git a/packages/lexical-file/src/fileImportExport.ts b/packages/lexical-file/src/fileImportExport.ts index 375265c253a..8b11f970cd1 100644 --- a/packages/lexical-file/src/fileImportExport.ts +++ b/packages/lexical-file/src/fileImportExport.ts @@ -6,7 +6,7 @@ * */ -import type {LexicalEditor} from 'lexical'; +import type {EditorState, LexicalEditor} from 'lexical'; import {CLEAR_HISTORY_COMMAND, VERSION} from 'lexical'; @@ -25,20 +25,32 @@ function readTextFileFromSystem(callback: (text: string) => void) { const input = document.createElement('input'); input.type = 'file'; input.accept = '.lexical'; - input.addEventListener('change', (event: InputEvent) => { + input.addEventListener('change', (event: Event) => { const target = event.target as HTMLInputElement; - const file = target.files[0]; - const reader = new FileReader(); - reader.readAsText(file, 'UTF-8'); - reader.onload = (readerEvent) => { - const content = readerEvent.target.result; - callback(content as string); - }; + if (target.files) { + const file = target.files[0]; + const reader = new FileReader(); + reader.readAsText(file, 'UTF-8'); + + reader.onload = (readerEvent) => { + if (readerEvent.target) { + const content = readerEvent.target.result; + callback(content as string); + } + }; + } }); input.click(); } +type DocumentJSON = { + editorState: EditorState; + lastSaved: number; + source: string | 'Lexical'; + version: typeof VERSION; +}; + export function exportFile( editor: LexicalEditor, config: Readonly<{ @@ -48,7 +60,7 @@ export function exportFile( ) { const now = new Date(); const editorState = editor.getEditorState(); - const documentJSON = { + const documentJSON: DocumentJSON = { editorState: editorState, lastSaved: now.getTime(), source: config.source || 'Lexical', @@ -59,7 +71,7 @@ export function exportFile( } // Adapted from https://stackoverflow.com/a/19328891/2013580 -function exportBlob(data, fileName: string) { +function exportBlob(data: DocumentJSON, fileName: string) { const a = document.createElement('a'); const body = document.body; diff --git a/packages/lexical-headless/src/index.ts b/packages/lexical-headless/src/index.ts index d50ce57af9a..e84b0fec27f 100644 --- a/packages/lexical-headless/src/index.ts +++ b/packages/lexical-headless/src/index.ts @@ -29,16 +29,18 @@ export function createHeadlessEditor(editorConfig?: { const editor = createEditor(editorConfig); editor._headless = true; - [ + const unsupportedMethods = [ 'registerDecoratorListener', 'registerRootListener', - 'registerMutationListeners', + 'registerMutationListener', 'getRootElement', 'setRootElement', 'getElementByKey', 'focus', 'blur', - ].forEach((method) => { + ] as const; + + unsupportedMethods.forEach((method: typeof unsupportedMethods[number]) => { editor[method] = () => { throw new Error(`${method} is not supported in headless mode`); }; diff --git a/packages/lexical-html/src/index.ts b/packages/lexical-html/src/index.ts index 5a6005cdfdd..b128f3639ba 100644 --- a/packages/lexical-html/src/index.ts +++ b/packages/lexical-html/src/index.ts @@ -32,7 +32,7 @@ export function $generateNodesFromDOM( editor: LexicalEditor, dom: Document, ): Array { - let lexicalNodes = []; + let lexicalNodes: Array = []; const elements: Array = dom.body ? Array.from(dom.body.childNodes) : []; const elementsLength = elements.length; @@ -67,7 +67,9 @@ export function $generateHtmlFromNodes( for (let i = 0; i < topLevelChildren.length; i++) { const topLevelNode = topLevelChildren[i]; - $appendNodesToHTML(editor, selection, topLevelNode, container); + if (selection) { + $appendNodesToHTML(editor, selection, topLevelNode, container); + } } return container.innerHTML; @@ -75,7 +77,7 @@ export function $generateHtmlFromNodes( function $appendNodesToHTML( editor: LexicalEditor, - selection: RangeSelection | NodeSelection | GridSelection | null, + selection: RangeSelection | NodeSelection | GridSelection, currentNode: LexicalNode, parentElement: HTMLElement | DocumentFragment, ): boolean { @@ -141,18 +143,17 @@ function getConversionFunction( let currentConversion: DOMConversion | null = null; if (cachedConversions !== undefined) { - cachedConversions.forEach((cachedConversion) => { + for (const cachedConversion of cachedConversions) { const domConversion = cachedConversion(domNode); - if (domConversion !== null) { - if ( - currentConversion === null || - currentConversion.priority < domConversion.priority - ) { - currentConversion = domConversion; - } + if ( + domConversion !== null && + (currentConversion === null || + currentConversion.priority < domConversion.priority) + ) { + currentConversion = domConversion; } - }); + } } return currentConversion !== null ? currentConversion.conversion : null; diff --git a/packages/lexical-link/src/index.ts b/packages/lexical-link/src/index.ts index cc9591fc1ac..056821b7f86 100644 --- a/packages/lexical-link/src/index.ts +++ b/packages/lexical-link/src/index.ts @@ -138,7 +138,7 @@ export class LinkNode extends ElementNode { function convertAnchorElement(domNode: Node): DOMConversionOutput { let node = null; if (domNode instanceof HTMLAnchorElement) { - node = $createLinkNode(domNode.getAttribute('href')); + node = $createLinkNode(domNode.getAttribute('href') || ''); } return {node}; } @@ -267,8 +267,8 @@ export function toggleLink(url: null | string): void { } } - let prevParent = null; - let linkNode = null; + let prevParent: ElementNode | LinkNode | null = null; + let linkNode: LinkNode | null = null; nodes.forEach((node) => { const parent = node.getParent(); diff --git a/packages/lexical-list/src/LexicalListItemNode.ts b/packages/lexical-list/src/LexicalListItemNode.ts index 633d640a4dd..307fcdaa247 100644 --- a/packages/lexical-list/src/LexicalListItemNode.ts +++ b/packages/lexical-list/src/LexicalListItemNode.ts @@ -44,7 +44,7 @@ import { export type SerializedListItemNode = Spread< { - checked: boolean; + checked: boolean | undefined; type: 'listitem'; value: number; version: 1; @@ -53,7 +53,7 @@ export type SerializedListItemNode = Spread< >; export class ListItemNode extends ElementNode { __value: number; - __checked: boolean; + __checked?: boolean; static getType(): string { return 'listitem'; @@ -307,13 +307,13 @@ export class ListItemNode extends ElementNode { self.__value = value; } - getChecked(): boolean { + getChecked(): boolean | undefined { const self = this.getLatest(); return self.__checked; } - setChecked(checked: boolean): void { + setChecked(checked?: boolean): void { const self = this.getWritable(); self.__checked = checked; } @@ -365,7 +365,7 @@ export class ListItemNode extends ElementNode { const parent = this.getParentOrThrow(); if ($isListNode(parent)) { - const siblings = this.getNextSiblings(); + const siblings = this.getNextSiblings(); updateChildrenListItemValue(parent, siblings); } } diff --git a/packages/lexical-list/src/formatList.ts b/packages/lexical-list/src/formatList.ts index 58791ed9c63..c884a2f2969 100644 --- a/packages/lexical-list/src/formatList.ts +++ b/packages/lexical-list/src/formatList.ts @@ -250,7 +250,7 @@ export function removeList(editor: LexicalEditor): void { export function updateChildrenListItemValue( list: ListNode, - children?: Array, + children?: Array, ): void { (children || list.getChildren()).forEach((child: ListItemNode) => { const prevValue = child.getValue(); @@ -272,8 +272,12 @@ export function $handleIndent(listItemNodes: Array): void { } const parent = listItemNode.getParent(); - const nextSibling = listItemNode.getNextSibling(); - const previousSibling = listItemNode.getPreviousSibling(); + + // We can cast both of the below `isNestedListNode` only returns a boolean type instead of a user-defined type guards + const nextSibling = + listItemNode.getNextSibling() as ListItemNode; + const previousSibling = + listItemNode.getPreviousSibling() as ListItemNode; // if there are nested lists on either side, merge them all together. if (isNestedListNode(nextSibling) && isNestedListNode(previousSibling)) { @@ -337,6 +341,7 @@ export function $handleIndent(listItemNodes: Array): void { export function $handleOutdent(listItemNodes: Array): void { // go through each node and decide where to move it. + listItemNodes.forEach((listItemNode) => { if (isNestedListNode(listItemNode)) { return; @@ -404,7 +409,7 @@ function maybeIndentOrOutdent(direction: 'indent' | 'outdent'): void { return; } const selectedNodes = selection.getNodes(); - let listItemNodes = []; + let listItemNodes: Array = []; if (selectedNodes.length === 0) { selectedNodes.push(selection.anchor.getNode()); diff --git a/packages/lexical-list/src/utils.ts b/packages/lexical-list/src/utils.ts index 76343ee1ed3..2e1491c5c3d 100644 --- a/packages/lexical-list/src/utils.ts +++ b/packages/lexical-list/src/utils.ts @@ -41,7 +41,7 @@ export function $getTopListNode(listItem: LexicalNode): ListNode { invariant(false, 'A ListItemNode must have a ListNode for a parent.'); } - let parent = list; + let parent: ListNode | null = list; while (parent !== null) { parent = parent.getParent(); @@ -61,7 +61,7 @@ export function $isLastItemInList(listItem: ListItemNode): boolean { if ($isListNode(firstChild)) { return false; } - let parent = listItem; + let parent: ListItemNode | null = listItem; while (parent !== null) { if ($isListItemNode(parent)) { @@ -107,7 +107,7 @@ export function isNestedListNode( export function findNearestListItemNode( node: LexicalNode, ): ListItemNode | null { - let currentNode = node; + let currentNode: LexicalNode | null = node; while (currentNode !== null) { if ($isListItemNode(currentNode)) { diff --git a/packages/lexical-mark/src/index.ts b/packages/lexical-mark/src/index.ts index b4894d8cdea..94e4d95b5e8 100644 --- a/packages/lexical-mark/src/index.ts +++ b/packages/lexical-mark/src/index.ts @@ -52,7 +52,7 @@ export function $wrapSelectionInMarkNode( } const isFirstNode = i === 0; const isLastNode = i === nodesLength - 1; - let targetNode: LexicalNode; + let targetNode: LexicalNode | null = null; if ($isTextNode(node)) { const textContentSize = node.getTextContentSize(); @@ -72,7 +72,7 @@ export function $wrapSelectionInMarkNode( } else if ($isElementNode(node) && node.isInline()) { targetNode = node; } - if (targetNode !== undefined) { + if (targetNode !== null) { if (targetNode && targetNode.is(currentNodeParent)) { continue; } @@ -97,7 +97,7 @@ export function $getMarkIDs( node: TextNode, offset: number, ): null | Array { - let currentNode: LexicalNode = node; + let currentNode: LexicalNode | null = node; while (currentNode !== null) { if ($isMarkNode(currentNode)) { return currentNode.getIDs(); diff --git a/packages/lexical-markdown/src/utils.ts b/packages/lexical-markdown/src/utils.ts index 5f018a8a917..1291def4f61 100644 --- a/packages/lexical-markdown/src/utils.ts +++ b/packages/lexical-markdown/src/utils.ts @@ -555,17 +555,18 @@ function getPatternMatchResultsWithRegEx( const patternMatchResults: PatternMatchResults = { regExCaptureGroups: [], }; - const regExMatches = textToSearch.match(regEx); + const regExMatches: RegExpMatchArray | null = textToSearch.match(regEx); if ( - regExMatches !== null && + regExMatches && regExMatches.length > 0 && (matchMustAppearAtStartOfString === false || regExMatches.index === 0) && - (matchMustAppearAtEndOfString === false || - regExMatches.index + regExMatches[0].length === textToSearch.length) + (matchMustAppearAtEndOfString === false || regExMatches.index + ? regExMatches.index + : 0 + regExMatches[0].length === textToSearch.length) ) { const captureGroupsCount = regExMatches.length; - let runningLength = regExMatches.index; + let runningLength = regExMatches.index || 0; for ( let captureGroupIndex = 0; @@ -1211,8 +1212,8 @@ function createSelectionWithCaptureGroups( } function removeTextByCaptureGroups( - anchorCaptureGroupIndex, - focusCaptureGroupIndex, + anchorCaptureGroupIndex: number, + focusCaptureGroupIndex: number, scanningContext: ScanningContext, parentElementNode: ElementNode, ) { @@ -1382,7 +1383,7 @@ type BlockExport = ( exportChildren: (node: ElementNode) => string, ) => string | null; -function createHeadingExport(level): BlockExport { +function createHeadingExport(level: number): BlockExport { return (node, exportChildren) => { return $isHeadingNode(node) && node.getTag() === 'h' + level ? '#'.repeat(level) + ' ' + exportChildren(node) @@ -1390,7 +1391,10 @@ function createHeadingExport(level): BlockExport { }; } -function listExport(node, exportChildren) { +function listExport( + node: LexicalNode, + exportChildren: (_node: ElementNode) => string, +) { return $isListNode(node) ? processNestedLists(node, exportChildren, 0) : null; } @@ -1401,7 +1405,7 @@ function processNestedLists( listNode: ListNode, exportChildren: (node: ElementNode) => string, depth: number, -) { +): string { const output = []; const children = listNode.getChildren(); let index = 0; @@ -1432,11 +1436,14 @@ function processNestedLists( return output.join('\n'); } -function blockQuoteExport(node, exportChildren) { +function blockQuoteExport( + node: LexicalNode, + exportChildren: (_node: ElementNode) => string, +) { return $isQuoteNode(node) ? '> ' + exportChildren(node) : null; } -function codeBlockExport(node, exportChildren) { +function codeBlockExport(node: LexicalNode) { if (!$isCodeNode(node)) { return null; } diff --git a/packages/lexical-markdown/src/v2/MarkdownImport.ts b/packages/lexical-markdown/src/v2/MarkdownImport.ts index 1207af92a77..23e6ccecb19 100644 --- a/packages/lexical-markdown/src/v2/MarkdownImport.ts +++ b/packages/lexical-markdown/src/v2/MarkdownImport.ts @@ -155,7 +155,7 @@ function importTextFormatTransformers( if (match[0] === textContent) { currentNode = textNode; } else { - const startIndex = match.index; + const startIndex = match.index || 0; const endIndex = startIndex + match[0].length; if (startIndex === 0) { @@ -209,7 +209,7 @@ function importTextMatchTransformers( continue; } - const startIndex = match.index; + const startIndex = match.index || 0; const endIndex = startIndex + match[0].length; let replaceNode; @@ -256,7 +256,7 @@ function findOutermostMatch( // For non-intraword transformers checking if it's within a word // or surrounded with space/punctuation/newline - const {index} = fullMatch; + const {index = 0} = fullMatch; const beforeChar = textContent[index - 1]; const afterChar = textContent[index + fullMatch[0].length]; @@ -275,8 +275,8 @@ function findOutermostMatch( function createTextFormatTransformersIndex( textTransformers: Array, ): TextFormatTransformersIndex { - const transformersByTag = {}; - const fullMatchRegExpByTag = {}; + const transformersByTag: Record = {}; + const fullMatchRegExpByTag: Record = {}; const openTagsRegExp = []; for (const transformer of textTransformers) { diff --git a/packages/lexical-markdown/src/v2/MarkdownShortcuts.ts b/packages/lexical-markdown/src/v2/MarkdownShortcuts.ts index a9492df4317..b69a5069e90 100644 --- a/packages/lexical-markdown/src/v2/MarkdownShortcuts.ts +++ b/packages/lexical-markdown/src/v2/MarkdownShortcuts.ts @@ -99,7 +99,7 @@ function runTextMatchTransformers( continue; } - const startIndex = match.index; + const startIndex = match.index || 0; const endIndex = startIndex + match[0].length; let replaceNode; @@ -174,7 +174,7 @@ function runTextFormatTransformers( // Go through text node siblings and search for opening tag // if haven't found it within the same text node as closing tag - let sibling = openNode; + let sibling: TextNode | null = openNode; while ( openTagStartIndex < 0 && diff --git a/packages/lexical-markdown/src/v2/utils.ts b/packages/lexical-markdown/src/v2/utils.ts index d30bad54fc3..c7301709f18 100644 --- a/packages/lexical-markdown/src/v2/utils.ts +++ b/packages/lexical-markdown/src/v2/utils.ts @@ -17,7 +17,7 @@ export function indexBy( list: Array, callback: (arg0: T) => string, ): Readonly>> { - const index = {}; + const index: Record> = {}; for (const item of list) { const key = callback(item); diff --git a/packages/lexical-offset/src/index.ts b/packages/lexical-offset/src/index.ts index 40e63592915..5594e6895de 100644 --- a/packages/lexical-offset/src/index.ts +++ b/packages/lexical-offset/src/index.ts @@ -539,7 +539,7 @@ function $createOffsetChild( export function $createOffsetView( editor: LexicalEditor, blockOffsetSize = 1, - editorState?: EditorState, + editorState?: EditorState | null, ): OffsetView { const targetEditorState = editorState || editor._pendingEditorState || editor._editorState; diff --git a/packages/lexical-playground/src/plugins/TableActionMenuPlugin.tsx b/packages/lexical-playground/src/plugins/TableActionMenuPlugin.tsx index b5cc311cb3e..1f493b66c4f 100644 --- a/packages/lexical-playground/src/plugins/TableActionMenuPlugin.tsx +++ b/packages/lexical-playground/src/plugins/TableActionMenuPlugin.tsx @@ -20,6 +20,7 @@ import { $isTableRowNode, $removeTableRowAtIndex, getTableSelectionFromTableElement, + HTMLTableElementWithWithTableSelectionState, TableCellHeaderStates, TableCellNode, } from '@lexical/table'; @@ -122,7 +123,9 @@ function TableActionMenu({ editor.update(() => { if (tableCellNode.isAttached()) { const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode); - const tableElement = editor.getElementByKey(tableNode.getKey()); + const tableElement = editor.getElementByKey( + tableNode.getKey(), + ) as HTMLTableElementWithWithTableSelectionState; if (!tableElement) { throw new Error('Expected to find tableElement in DOM'); diff --git a/packages/lexical-react/src/LexicalAutoFocusPlugin.ts b/packages/lexical-react/src/LexicalAutoFocusPlugin.ts index a1e7b58f1b4..f9e18f8bce4 100644 --- a/packages/lexical-react/src/LexicalAutoFocusPlugin.ts +++ b/packages/lexical-react/src/LexicalAutoFocusPlugin.ts @@ -21,9 +21,9 @@ export function AutoFocusPlugin(): null { const activeElement = document.activeElement; const rootElement = editor.getRootElement() as HTMLDivElement; if ( + !rootElement.contains(activeElement) || rootElement !== null || - activeElement === null || - !rootElement.contains(activeElement) + activeElement === null ) { // Note: preventScroll won't work in Webkit. rootElement.focus({preventScroll: true}); diff --git a/packages/lexical-react/src/LexicalAutoLinkPlugin.ts b/packages/lexical-react/src/LexicalAutoLinkPlugin.ts index 860c1e8c2dc..865307adaf1 100644 --- a/packages/lexical-react/src/LexicalAutoLinkPlugin.ts +++ b/packages/lexical-react/src/LexicalAutoLinkPlugin.ts @@ -265,7 +265,7 @@ export function AutoLinkPlugin({ }: { matchers: Array; onChange?: ChangeHandler; -}): JSX.Element { +}): JSX.Element | null { const [editor] = useLexicalComposerContext(); useAutoLink(editor, matchers, onChange); diff --git a/packages/lexical-react/src/LexicalAutoScrollPlugin.ts b/packages/lexical-react/src/LexicalAutoScrollPlugin.ts index 5012e96f619..ec5a5567993 100644 --- a/packages/lexical-react/src/LexicalAutoScrollPlugin.ts +++ b/packages/lexical-react/src/LexicalAutoScrollPlugin.ts @@ -16,7 +16,7 @@ type Props = Readonly<{ }; }>; -export function AutoScrollPlugin({scrollRef}: Props): JSX.Element { +export function AutoScrollPlugin({scrollRef}: Props): JSX.Element | null { const [editor] = useLexicalComposerContext(); useLayoutEffect(() => { diff --git a/packages/lexical-react/src/LexicalBlockWithAlignableContents.tsx b/packages/lexical-react/src/LexicalBlockWithAlignableContents.tsx index b157717ff85..f305478ed68 100644 --- a/packages/lexical-react/src/LexicalBlockWithAlignableContents.tsx +++ b/packages/lexical-react/src/LexicalBlockWithAlignableContents.tsx @@ -50,12 +50,11 @@ export function BlockWithAlignableContents({ const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection(nodeKey); - const ref = useRef(); + const ref = useRef(null); const onDelete = useCallback( - (payload) => { + (event: KeyboardEvent) => { if (isSelected && $isNodeSelection($getSelection())) { - const event: KeyboardEvent = payload; event.preventDefault(); editor.update(() => { const node = $getNodeByKey(nodeKey); @@ -145,7 +144,7 @@ export function BlockWithAlignableContents({ .join(' ')} ref={ref} style={{ - textAlign: format ? format : null, + textAlign: format ? format : undefined, }}> {children} diff --git a/packages/lexical-react/src/LexicalCharacterLimitPlugin.tsx b/packages/lexical-react/src/LexicalCharacterLimitPlugin.tsx index cf3d1a57d8f..2c31fd08755 100644 --- a/packages/lexical-react/src/LexicalCharacterLimitPlugin.tsx +++ b/packages/lexical-react/src/LexicalCharacterLimitPlugin.tsx @@ -13,7 +13,7 @@ import {useMemo, useState} from 'react'; import {useCharacterLimit} from './shared/useCharacterLimit'; const CHARACTER_LIMIT = 5; -let textEncoderInstance = null; +let textEncoderInstance: null | TextEncoder = null; function textEncoder(): null | TextEncoder { if (window.TextEncoder === undefined) { @@ -51,7 +51,7 @@ export function CharacterLimitPlugin({ const characterLimitProps = useMemo( () => ({ remainingCharacters: setRemainingCharacters, - strlen: (text) => { + strlen: (text: string) => { if (charset === 'UTF-8') { return utf8Length(text); } else if (charset === 'UTF-16') { diff --git a/packages/lexical-react/src/LexicalCheckListPlugin.tsx b/packages/lexical-react/src/LexicalCheckListPlugin.tsx index ad024bd4c97..e3c50d77aa0 100644 --- a/packages/lexical-react/src/LexicalCheckListPlugin.tsx +++ b/packages/lexical-react/src/LexicalCheckListPlugin.tsx @@ -158,7 +158,7 @@ function listenPointerDown() { }; } -function handleCheckItemEvent(event: PointerEvent, callback) { +function handleCheckItemEvent(event: PointerEvent, callback: () => void) { const target = event.target; if (!(target instanceof HTMLElement)) { @@ -166,10 +166,11 @@ function handleCheckItemEvent(event: PointerEvent, callback) { } // Ignore clicks on LI that have nested lists - const firstChild = target.firstChild as HTMLElement; + const firstChild = target.firstChild; if ( firstChild != null && + firstChild instanceof HTMLElement && (firstChild.tagName === 'UL' || firstChild.tagName === 'OL') ) { return; @@ -194,36 +195,41 @@ function handleCheckItemEvent(event: PointerEvent, callback) { } } -function handleClick(event) { - handleCheckItemEvent(event, () => { - const editor = findEditor(event.target); +function handleClick(event: Event) { + handleCheckItemEvent(event as PointerEvent, () => { + const domNode = event.target as HTMLElement; + const editor = findEditor(domNode); if (editor != null && !editor.isReadOnly()) { editor.update(() => { - const node = $getNearestNodeFromDOMNode(event.target); + if (event.target) { + const node = $getNearestNodeFromDOMNode(domNode); - if ($isListItemNode(node)) { - event.target.focus(); - node.toggleChecked(); + if ($isListItemNode(node)) { + domNode.focus(); + node.toggleChecked(); + } } }); } }); } -function handlePointerDown(event) { +function handlePointerDown(event: PointerEvent) { handleCheckItemEvent(event, () => { // Prevents caret moving when clicking on check mark event.preventDefault(); }); } -function findEditor(target) { - let node = target; +function findEditor(target: Node) { + let node: ParentNode | Node | null = target; while (node) { + // @ts-ignore internal field if (node.__lexicalEditor) { - return node.__lexicalEditor as LexicalEditor; + // @ts-ignore internal field + return node.__lexicalEditor; } node = node.parentNode; @@ -249,7 +255,7 @@ function findCheckListItemSibling( backward: boolean, ): ListItemNode | null { let sibling = backward ? node.getPreviousSibling() : node.getNextSibling(); - let parent = node; + let parent: ListItemNode | null = node; // Going up in a tree to get non-null sibling while (sibling == null && $isListItemNode(parent)) { diff --git a/packages/lexical-react/src/LexicalClearEditorPlugin.ts b/packages/lexical-react/src/LexicalClearEditorPlugin.ts index 5461afc7315..dd6a8f038ca 100644 --- a/packages/lexical-react/src/LexicalClearEditorPlugin.ts +++ b/packages/lexical-react/src/LexicalClearEditorPlugin.ts @@ -20,7 +20,7 @@ type Props = Readonly<{ onClear?: () => void; }>; -export function ClearEditorPlugin({onClear}: Props): JSX.Element { +export function ClearEditorPlugin({onClear}: Props): JSX.Element | null { const [editor] = useLexicalComposerContext(); useLayoutEffect(() => { diff --git a/packages/lexical-react/src/LexicalComposerContext.ts b/packages/lexical-react/src/LexicalComposerContext.ts index 2d96541a97b..9f397da095e 100644 --- a/packages/lexical-react/src/LexicalComposerContext.ts +++ b/packages/lexical-react/src/LexicalComposerContext.ts @@ -30,7 +30,7 @@ export function createLexicalComposerContext( parent: LexicalComposerContextWithEditor | null | undefined, theme: EditorThemeClasses | null | undefined, ): LexicalComposerContextType { - let parentContext = null; + let parentContext: LexicalComposerContextType | null = null; if (parent != null) { parentContext = parent[1]; diff --git a/packages/lexical-react/src/LexicalContentEditable.tsx b/packages/lexical-react/src/LexicalContentEditable.tsx index 0ba3383cae5..96a1f68a8a1 100644 --- a/packages/lexical-react/src/LexicalContentEditable.tsx +++ b/packages/lexical-react/src/LexicalContentEditable.tsx @@ -90,17 +90,17 @@ export function ContentEditable({ aria-owns={isReadOnly ? null : ariaOwneeID} aria-required={ariaRequired} autoCapitalize={ - autoCapitalize !== undefined ? String(autoCapitalize) : null + autoCapitalize !== undefined ? String(autoCapitalize) : undefined } // @ts-ignore This is a valid attribute autoComplete={autoComplete} - autoCorrect={autoCorrect !== undefined ? String(autoCorrect) : null} + autoCorrect={autoCorrect !== undefined ? String(autoCorrect) : undefined} className={className} contentEditable={!isReadOnly} data-testid={testid} id={id} ref={ref} - role={isReadOnly ? null : role} + role={isReadOnly ? undefined : role} spellCheck={spellCheck} style={style} tabIndex={tabIndex} diff --git a/packages/lexical-react/src/LexicalHashtagPlugin.ts b/packages/lexical-react/src/LexicalHashtagPlugin.ts index bbda84d5a0b..0b1e01c4732 100644 --- a/packages/lexical-react/src/LexicalHashtagPlugin.ts +++ b/packages/lexical-react/src/LexicalHashtagPlugin.ts @@ -248,7 +248,7 @@ function getHashtagRegexString(): string { const REGEX = new RegExp(getHashtagRegexString(), 'i'); -export function HashtagPlugin(): JSX.Element { +export function HashtagPlugin(): JSX.Element | null { const [editor] = useLexicalComposerContext(); useEffect(() => { diff --git a/packages/lexical-react/src/LexicalNestedComposer.tsx b/packages/lexical-react/src/LexicalNestedComposer.tsx index 733ebbfa402..2e8a9eb4e2c 100644 --- a/packages/lexical-react/src/LexicalNestedComposer.tsx +++ b/packages/lexical-react/src/LexicalNestedComposer.tsx @@ -36,7 +36,7 @@ export function LexicalNestedComposer({ const composerContext: [LexicalEditor, LexicalComposerContextType] = useMemo( () => { const [parentEditor, parentContextContext] = parentContext; - const composerTheme: void | EditorThemeClasses = + const composerTheme: EditorThemeClasses | undefined = initialTheme || parentContextContext.getTheme() || undefined; const context: LexicalComposerContextType = createLexicalComposerContext( diff --git a/packages/lexical-react/src/LexicalPlainTextPlugin.tsx b/packages/lexical-react/src/LexicalPlainTextPlugin.tsx index 68b5ded493f..74d375810d9 100644 --- a/packages/lexical-react/src/LexicalPlainTextPlugin.tsx +++ b/packages/lexical-react/src/LexicalPlainTextPlugin.tsx @@ -29,7 +29,11 @@ export function PlainTextPlugin({ initialEditorState?: InitialEditorStateType; placeholder: JSX.Element | string; }): JSX.Element { - if (__DEV__ && initialEditorState !== undefined) { + if ( + __DEV__ && + deprecatedInitialEditorStateWarning && + initialEditorState !== undefined + ) { deprecatedInitialEditorStateWarning(); } const [editor] = useLexicalComposerContext(); diff --git a/packages/lexical-react/src/LexicalRichTextPlugin.tsx b/packages/lexical-react/src/LexicalRichTextPlugin.tsx index 413c4bfa487..0e3e8d7c1d7 100644 --- a/packages/lexical-react/src/LexicalRichTextPlugin.tsx +++ b/packages/lexical-react/src/LexicalRichTextPlugin.tsx @@ -29,7 +29,11 @@ export function RichTextPlugin({ initialEditorState?: InitialEditorStateType; placeholder: JSX.Element | string; }>): JSX.Element { - if (__DEV__ && initialEditorState !== undefined) { + if ( + __DEV__ && + deprecatedInitialEditorStateWarning && + initialEditorState !== undefined + ) { deprecatedInitialEditorStateWarning(); } const [editor] = useLexicalComposerContext(); diff --git a/packages/lexical-react/src/LexicalTablePlugin.ts b/packages/lexical-react/src/LexicalTablePlugin.ts index 5f4286feafb..0f367dfe740 100644 --- a/packages/lexical-react/src/LexicalTablePlugin.ts +++ b/packages/lexical-react/src/LexicalTablePlugin.ts @@ -29,7 +29,7 @@ import { import {useEffect} from 'react'; import invariant from 'shared/invariant'; -export function TablePlugin(): JSX.Element { +export function TablePlugin(): JSX.Element | null { const [editor] = useLexicalComposerContext(); useEffect(() => { diff --git a/packages/lexical-react/src/LexicalTreeView.tsx b/packages/lexical-react/src/LexicalTreeView.tsx index ba1463b3ee6..145672f7c11 100644 --- a/packages/lexical-react/src/LexicalTreeView.tsx +++ b/packages/lexical-react/src/LexicalTreeView.tsx @@ -11,6 +11,7 @@ import type { ElementNode, GridSelection, LexicalEditor, + LexicalNode, NodeSelection, RangeSelection, } from 'lexical'; @@ -36,7 +37,7 @@ const NON_SINGLE_WIDTH_CHARS_REGEX = new RegExp( Object.keys(NON_SINGLE_WIDTH_CHARS_REPLACEMENT).join('|'), 'g', ); -const SYMBOLS = Object.freeze({ +const SYMBOLS: Record = Object.freeze({ ancestorHasNextSibling: '|', ancestorIsLastChild: ' ', hasNextSibling: '├', @@ -60,7 +61,9 @@ export function TreeView({ timeTravelPanelSliderClassName: string; viewClassName: string; }): JSX.Element { - const [timeStampedEditorStates, setTimeStampedEditorStates] = useState([]); + const [timeStampedEditorStates, setTimeStampedEditorStates] = useState< + Array<[number, EditorState]> + >([]); const [content, setContent] = useState(''); const [timeTravelEnabled, setTimeTravelEnabled] = useState(false); const playingIndexRef = useRef(0); @@ -89,7 +92,7 @@ export function TreeView({ useEffect(() => { if (isPlaying) { - let timeoutId; + let timeoutId: NodeJS.Timeout; const play = () => { const currentIndex = playingIndexRef.current; @@ -246,7 +249,7 @@ function generateContent(editorState: EditorState): string { const selectionString = editorState.read(() => { const selection = $getSelection(); - visitTree($getRoot(), (node, indent) => { + visitTree($getRoot(), (node: LexicalNode, indent: Array) => { const nodeKey = node.getKey(); const nodeKeyDisplay = `(${nodeKey})`; const typeDisplay = node.getType() || ''; @@ -281,7 +284,11 @@ function generateContent(editorState: EditorState): string { return res + '\n selection' + selectionString; } -function visitTree(currentNode: ElementNode, visitor, indent = []) { +function visitTree( + currentNode: ElementNode, + visitor: (node: LexicalNode, indentArr: Array) => void, + indent: Array = [], +) { const childNodes = currentNode.getChildren(); const childNodesLength = childNodes.length; @@ -309,14 +316,14 @@ function visitTree(currentNode: ElementNode, visitor, indent = []) { }); } -function normalize(text) { +function normalize(text: string) { return Object.entries(NON_SINGLE_WIDTH_CHARS_REPLACEMENT).reduce( (acc, [key, value]) => acc.replace(new RegExp(key, 'g'), String(value)), text, ); } -function printNode(node) { +function printNode(node: LexicalNode) { if ($isTextNode(node)) { const text = node.getTextContent(true); const title = text.length === 0 ? '(empty)' : `"${normalize(text)}"`; @@ -331,27 +338,31 @@ function printNode(node) { } const FORMAT_PREDICATES = [ - (node) => node.hasFormat('bold') && 'Bold', - (node) => node.hasFormat('code') && 'Code', - (node) => node.hasFormat('italic') && 'Italic', - (node) => node.hasFormat('strikethrough') && 'Strikethrough', - (node) => node.hasFormat('subscript') && 'Subscript', - (node) => node.hasFormat('superscript') && 'Superscript', - (node) => node.hasFormat('underline') && 'Underline', + (node: LexicalNode | RangeSelection) => node.hasFormat('bold') && 'Bold', + (node: LexicalNode | RangeSelection) => node.hasFormat('code') && 'Code', + (node: LexicalNode | RangeSelection) => node.hasFormat('italic') && 'Italic', + (node: LexicalNode | RangeSelection) => + node.hasFormat('strikethrough') && 'Strikethrough', + (node: LexicalNode | RangeSelection) => + node.hasFormat('subscript') && 'Subscript', + (node: LexicalNode | RangeSelection) => + node.hasFormat('superscript') && 'Superscript', + (node: LexicalNode | RangeSelection) => + node.hasFormat('underline') && 'Underline', ]; const DETAIL_PREDICATES = [ - (node) => node.isDirectionless() && 'Directionless', - (node) => node.isUnmergeable() && 'Unmergeable', + (node: LexicalNode) => node.isDirectionless() && 'Directionless', + (node: LexicalNode) => node.isUnmergeable() && 'Unmergeable', ]; const MODE_PREDICATES = [ - (node) => node.isToken() && 'Token', - (node) => node.isSegmented() && 'Segmented', - (node) => node.isInert() && 'Inert', + (node: LexicalNode) => node.isToken() && 'Token', + (node: LexicalNode) => node.isSegmented() && 'Segmented', + (node: LexicalNode) => node.isInert() && 'Inert', ]; -function printAllProperties(node) { +function printAllProperties(node: LexicalNode) { return [ printFormatProperties(node), printDetailProperties(node), @@ -361,7 +372,7 @@ function printAllProperties(node) { .join(', '); } -function printDetailProperties(nodeOrSelection) { +function printDetailProperties(nodeOrSelection: LexicalNode) { let str = DETAIL_PREDICATES.map((predicate) => predicate(nodeOrSelection)) .filter(Boolean) .join(', ') @@ -374,7 +385,7 @@ function printDetailProperties(nodeOrSelection) { return str; } -function printModeProperties(nodeOrSelection) { +function printModeProperties(nodeOrSelection: LexicalNode) { let str = MODE_PREDICATES.map((predicate) => predicate(nodeOrSelection)) .filter(Boolean) .join(', ') @@ -387,7 +398,7 @@ function printModeProperties(nodeOrSelection) { return str; } -function printFormatProperties(nodeOrSelection) { +function printFormatProperties(nodeOrSelection: LexicalNode | RangeSelection) { let str = FORMAT_PREDICATES.map((predicate) => predicate(nodeOrSelection)) .filter(Boolean) .join(', ') @@ -407,6 +418,13 @@ function printSelectedCharsLine({ nodeKeyDisplay, selection, typeDisplay, +}: { + indent: Array; + isSelected: boolean; + node: LexicalNode; + nodeKeyDisplay: string; + selection: GridSelection | NodeSelection | RangeSelection | null; + typeDisplay: string; }) { // No selection or node is not selected. if ( @@ -462,7 +480,10 @@ function printSelectedCharsLine({ ); } -function $getSelectionStartEnd(node, selection): [number, number] { +function $getSelectionStartEnd( + node: LexicalNode, + selection: RangeSelection | GridSelection, +): [number, number] { const anchor = selection.anchor; const focus = selection.focus; const textContent = node.getTextContent(true); diff --git a/packages/lexical-react/src/shared/useEditorEvents.ts b/packages/lexical-react/src/shared/useEditorEvents.ts index 9367b0e0de7..64bbb631c69 100644 --- a/packages/lexical-react/src/shared/useEditorEvents.ts +++ b/packages/lexical-react/src/shared/useEditorEvents.ts @@ -31,8 +31,8 @@ export function useEditorEvents( editor: LexicalEditor, ): void { useLayoutEffect(() => { - const create = []; - const destroy = []; + const create: Array<(rootElement: HTMLElement) => void> = []; + const destroy: Array<(rootElement: HTMLElement) => void> = []; for (let i = 0; i < events.length; i++) { const [eventName, handler] = events[i]; diff --git a/packages/lexical-react/src/shared/useYjsCollaboration.tsx b/packages/lexical-react/src/shared/useYjsCollaboration.tsx index 9f2bc52636e..93b4284c12b 100644 --- a/packages/lexical-react/src/shared/useYjsCollaboration.tsx +++ b/packages/lexical-react/src/shared/useYjsCollaboration.tsx @@ -8,7 +8,7 @@ import type {Binding} from '@lexical/yjs'; import type {LexicalEditor} from 'lexical'; -import type {Doc} from 'yjs'; +import type {Doc, Transaction, YEvent} from 'yjs'; import {mergeRegister} from '@lexical/utils'; import { @@ -92,7 +92,12 @@ export function useYjsCollaboration( syncCursorPositions(binding, provider); }; - const onYjsTreeChanges = (events, transaction) => { + const onYjsTreeChanges = ( + // The below `any` type is taken directly from the vendor types for YJS. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + events: Array>, + transaction: Transaction, + ) => { if (transaction.origin !== binding) { syncYjsChangesToLexical(binding, provider, events); } @@ -105,7 +110,7 @@ export function useYjsCollaboration( document.activeElement === editor.getRootElement(), ); - const onProviderDocReload = (ydoc) => { + const onProviderDocReload = (ydoc: Doc) => { clearEditorSkipCollab(editor, binding); setDoc(ydoc); docMap.set(id, ydoc); @@ -167,7 +172,7 @@ export function useYjsCollaboration( shouldBootstrap, ]); const cursorsContainer = useMemo(() => { - const ref = (element) => { + const ref = (element: null | HTMLElement) => { binding.cursorsContainer = element; }; diff --git a/packages/lexical-table/flow/LexicalTable.js.flow b/packages/lexical-table/flow/LexicalTable.js.flow index c754889b7b1..694b9d5f779 100644 --- a/packages/lexical-table/flow/LexicalTable.js.flow +++ b/packages/lexical-table/flow/LexicalTable.js.flow @@ -218,7 +218,6 @@ declare export class TableSelection { currentX: number; currentY: number; listenersToRemove: Set<() => void>; - domListeners: Set<() => void>; grid: Grid; isHighlightingCells: boolean; isMouseDown: boolean; diff --git a/packages/lexical-table/src/LexicalTableCellNode.ts b/packages/lexical-table/src/LexicalTableCellNode.ts index 9ed0487aed8..4cb63704d85 100644 --- a/packages/lexical-table/src/LexicalTableCellNode.ts +++ b/packages/lexical-table/src/LexicalTableCellNode.ts @@ -40,14 +40,14 @@ export type SerializedTableCellNode = Spread< { headerState: TableCellHeaderState; type: 'tablecell'; - width: number; + width: number | undefined; }, SerializedGridCellNode >; export class TableCellNode extends GridCellNode { __headerState: TableCellHeaderState; - __width: number; + __width?: number; static getType(): 'tablecell' { return 'tablecell'; @@ -158,13 +158,13 @@ export class TableCellNode extends GridCellNode { return this.getLatest().__headerState; } - setWidth(width: number): number { + setWidth(width: number): number | null | undefined { const self = this.getWritable(); self.__width = width; return this.__width; } - getWidth(): number { + getWidth(): number | undefined { return this.getLatest().__width; } diff --git a/packages/lexical-table/src/LexicalTableNode.ts b/packages/lexical-table/src/LexicalTableNode.ts index 98cec648010..94f47260a31 100644 --- a/packages/lexical-table/src/LexicalTableNode.ts +++ b/packages/lexical-table/src/LexicalTableNode.ts @@ -36,7 +36,7 @@ export type SerializedTableNode = Spread< >; export class TableNode extends GridNode { - __grid: Grid; + __grid?: Grid; static getType(): 'table' { return 'table'; diff --git a/packages/lexical-table/src/LexicalTableRowNode.ts b/packages/lexical-table/src/LexicalTableRowNode.ts index 12bded5a90b..9d613fd62fb 100644 --- a/packages/lexical-table/src/LexicalTableRowNode.ts +++ b/packages/lexical-table/src/LexicalTableRowNode.ts @@ -29,7 +29,7 @@ export type SerializedTableRowNode = Spread< >; export class TableRowNode extends GridRowNode { - __height: number; + __height?: number; static getType(): 'tablerow' { return 'tablerow'; @@ -52,7 +52,7 @@ export class TableRowNode extends GridRowNode { return $createTableRowNode(serializedNode.height); } - constructor(height?: number, key?: NodeKey) { + constructor(height?: number | undefined, key?: NodeKey) { super(key); this.__height = height; } @@ -77,13 +77,13 @@ export class TableRowNode extends GridRowNode { return element; } - setHeight(height: number): number { + setHeight(height: number): number | null | undefined { const self = this.getWritable(); self.__height = height; return this.__height; } - getHeight(): number { + getHeight(): number | null | undefined { return this.getLatest().__height; } diff --git a/packages/lexical-table/src/LexicalTableSelection.ts b/packages/lexical-table/src/LexicalTableSelection.ts index 12fbc3db86a..00157108011 100644 --- a/packages/lexical-table/src/LexicalTableSelection.ts +++ b/packages/lexical-table/src/LexicalTableSelection.ts @@ -81,7 +81,6 @@ export class TableSelection { currentX: number; currentY: number; listenersToRemove: Set<() => void>; - domListeners: Set<() => void>; grid: Grid; isHighlightingCells: boolean; startX: number; @@ -244,7 +243,9 @@ export class TableSelection { if (anchorElement && focusElement) { const domSelection = getDOMSelection(); - domSelection.setBaseAndExtent(anchorElement, 0, focusElement, 0); + if (domSelection) { + domSelection.setBaseAndExtent(anchorElement, 0, focusElement, 0); + } } $updateDOMForSelection(this.grid, this.gridSelection); @@ -274,7 +275,9 @@ export class TableSelection { if (this.anchorCell !== null) { // Collapse the selection - domSelection.setBaseAndExtent(this.anchorCell.elem, 0, cell.elem, 0); + if (domSelection) { + domSelection.setBaseAndExtent(this.anchorCell.elem, 0, cell.elem, 0); + } } if ( @@ -325,7 +328,9 @@ export class TableSelection { this.startX = cell.x; this.startY = cell.y; const domSelection = getDOMSelection(); - domSelection.setBaseAndExtent(cell.elem, 0, cell.elem, 0); + if (domSelection) { + domSelection.setBaseAndExtent(cell.elem, 0, cell.elem, 0); + } const anchorTableCellNode = $getNearestNodeFromDOMNode(cell.elem); if ($isTableCellNode(anchorTableCellNode)) { diff --git a/packages/lexical-table/src/LexicalTableSelectionHelpers.ts b/packages/lexical-table/src/LexicalTableSelectionHelpers.ts index fd92f510606..fdaa49ebd4a 100644 --- a/packages/lexical-table/src/LexicalTableSelectionHelpers.ts +++ b/packages/lexical-table/src/LexicalTableSelectionHelpers.ts @@ -17,6 +17,7 @@ import type { TextFormatType, } from 'lexical'; +import {TableCellNode} from '@lexical/table'; import {$findMatchingParent} from '@lexical/utils'; import { $createRangeSelection, @@ -49,7 +50,7 @@ const LEXICAL_ELEMENT_KEY = '__lexicalTableSelection'; export function applyTableHandlers( tableNode: TableNode, - tableElement: HTMLElement, + tableElement: HTMLTableElementWithWithTableSelectionState, editor: LexicalEditor, ): TableSelection { const rootElement = editor.getRootElement(); @@ -874,21 +875,24 @@ export function applyTableHandlers( return tableSelection; } +export type HTMLTableElementWithWithTableSelectionState = HTMLTableElement & + Record; + export function attachTableSelectionToTableElement( - tableElement: HTMLElement, + tableElement: HTMLTableElementWithWithTableSelectionState, tableSelection: TableSelection, ) { tableElement[LEXICAL_ELEMENT_KEY] = tableSelection; } export function getTableSelectionFromTableElement( - tableElement: HTMLElement, -): TableSelection { + tableElement: HTMLTableElementWithWithTableSelectionState, +): TableSelection | null { return tableElement[LEXICAL_ELEMENT_KEY]; } export function getCellFromTarget(node: Node): Cell | null { - let currentNode = node; + let currentNode: ParentNode | Node | null = node; while (currentNode != null) { const nodeName = currentNode.nodeName; @@ -984,7 +988,7 @@ export function $updateDOMForSelection( grid: Grid, selection: GridSelection | RangeSelection | null, ): Array { - const highlightedCells = []; + const highlightedCells: Array = []; const selectedCellNodes = new Set(selection ? selection.getNodes() : []); $forEachGridCell(grid, (cell, lexicalNode) => { const elem = cell.elem; @@ -1188,7 +1192,7 @@ function $isSelectionInTable( return false; } -function selectTableCellNode(tableCell) { +function selectTableCellNode(tableCell: TableCellNode) { const possibleParagraph = tableCell .getChildren() .find((n) => $isParagraphNode(n)); diff --git a/packages/lexical-table/src/LexicalTableUtils.ts b/packages/lexical-table/src/LexicalTableUtils.ts index 4d03305a7cb..dc63ee6bdff 100644 --- a/packages/lexical-table/src/LexicalTableUtils.ts +++ b/packages/lexical-table/src/LexicalTableUtils.ts @@ -180,7 +180,9 @@ export function $insertTableRow( let headerState = TableCellHeaderStates.NO_STATUS; const width = - (above && above.getWidth()) || (below && below.getWidth()) || null; + (above && above.getWidth()) || + (below && below.getWidth()) || + undefined; if ( (above && above.hasHeaderState(TableCellHeaderStates.COLUMN)) || diff --git a/packages/lexical-table/src/index.ts b/packages/lexical-table/src/index.ts index 84374349724..3b96d9a24bb 100644 --- a/packages/lexical-table/src/index.ts +++ b/packages/lexical-table/src/index.ts @@ -33,6 +33,7 @@ import { applyTableHandlers, getCellFromTarget, getTableSelectionFromTableElement, + HTMLTableElementWithWithTableSelectionState, } from './LexicalTableSelectionHelpers'; import { $createTableNodeWithDimensions, @@ -69,6 +70,7 @@ export { Cell, getCellFromTarget, getTableSelectionFromTableElement, + HTMLTableElementWithWithTableSelectionState, TableCellHeaderStates, TableCellNode, TableNode, diff --git a/packages/lexical-utils/src/index.ts b/packages/lexical-utils/src/index.ts index cd9aa9b55e5..13c0296aaf6 100644 --- a/packages/lexical-utils/src/index.ts +++ b/packages/lexical-utils/src/index.ts @@ -31,7 +31,7 @@ export type DFSNode = Readonly<{ export function addClassNamesToElement( element: HTMLElement, - ...classNames: Array + ...classNames: Array ): void { classNames.forEach((className) => { if (typeof className === 'string') { @@ -42,7 +42,7 @@ export function addClassNamesToElement( export function removeClassNamesFromElement( element: HTMLElement, - ...classNames: Array + ...classNames: Array ): void { classNames.forEach((className) => { if (typeof className === 'string') { @@ -59,7 +59,7 @@ export function $dfs( const start = (startingNode || $getRoot()).getLatest(); const end = endingNode || ($isElementNode(start) ? start.getLastDescendant() : start); - let node = start; + let node: LexicalNode | null = start; let depth = $getDepth(node); while (node !== null && !node.is(end)) { @@ -93,10 +93,10 @@ export function $dfs( } function $getDepth(node: LexicalNode): number { - let node_ = node; + let innerNode: LexicalNode | null = node; let depth = 0; - while ((node_ = node_.getParent()) !== null) { + while ((innerNode = innerNode.getParent()) !== null) { depth++; } @@ -150,7 +150,7 @@ export function $findMatchingParent( startingNode: LexicalNode, findFn: (node: LexicalNode) => boolean, ): LexicalNode | null { - let curr = startingNode; + let curr: ElementNode | LexicalNode | null = startingNode; while (curr !== $getRoot() && curr != null) { if (findFn(curr)) { @@ -194,7 +194,7 @@ export function registerNestedElementResolver( } } - let parentNode = node; + let parentNode: N | null = node; let childNode = node; while (parentNode !== null) { @@ -398,7 +398,11 @@ export function $restoreEditorState( const FULL_RECONCILE = 2; const nodeMap = new Map(editorState._nodeMap); const activeEditorState = editor._pendingEditorState; - activeEditorState._nodeMap = nodeMap; + + if (activeEditorState) { + activeEditorState._nodeMap = nodeMap; + } + editor._dirtyType = FULL_RECONCILE; const selection = editorState._selection; $setSelection(selection === null ? null : selection.clone()); diff --git a/packages/lexical-yjs/src/CollabElementNode.ts b/packages/lexical-yjs/src/CollabElementNode.ts index f4b6400d8d8..6a699639602 100644 --- a/packages/lexical-yjs/src/CollabElementNode.ts +++ b/packages/lexical-yjs/src/CollabElementNode.ts @@ -240,13 +240,14 @@ export class CollabElementNode { const key = lexicalNode.__key; const prevLexicalChildrenKeys = lexicalNode.__children; - const nextLexicalChildrenKeys = []; + const nextLexicalChildrenKeys: Array = []; const lexicalChildrenKeysLength = prevLexicalChildrenKeys.length; const collabChildren = this._children; const collabChildrenLength = collabChildren.length; const collabNodeMap = binding.collabNodeMap; const visitedKeys = new Set(); let collabKeys; + // Assign the new children key array that we're about to mutate let writableLexicalNode; @@ -425,8 +426,8 @@ export class CollabElementNode { const prevEndIndex = prevChildren.length - 1; const nextEndIndex = nextChildren.length - 1; const collabNodeMap = binding.collabNodeMap; - let prevChildrenSet: Set; - let nextChildrenSet: Set; + let prevChildrenSet: Set | undefined; + let nextChildrenSet: Set | undefined; let prevIndex = 0; let nextIndex = 0; @@ -644,7 +645,7 @@ export class CollabElementNode { function lazilyCloneElementNode( lexicalNode: ElementNode, - writableLexicalNode: ElementNode, + writableLexicalNode: ElementNode | undefined, nextLexicalChildrenKeys: Array, ): ElementNode { if (writableLexicalNode === undefined) { diff --git a/packages/lexical-yjs/src/SyncCursors.ts b/packages/lexical-yjs/src/SyncCursors.ts index 3e11583bb42..87003ddc75f 100644 --- a/packages/lexical-yjs/src/SyncCursors.ts +++ b/packages/lexical-yjs/src/SyncCursors.ts @@ -88,7 +88,7 @@ function createRelativePosition( function createAbsolutePosition( relativePosition: RelativePosition, binding: Binding, -): AbsolutePosition { +): AbsolutePosition | null { return createAbsolutePositionFromRelativePosition( relativePosition, binding.doc, diff --git a/packages/lexical-yjs/src/SyncEditorStates.ts b/packages/lexical-yjs/src/SyncEditorStates.ts index d5323ef16a0..8a3b1987648 100644 --- a/packages/lexical-yjs/src/SyncEditorStates.ts +++ b/packages/lexical-yjs/src/SyncEditorStates.ts @@ -92,7 +92,7 @@ export function syncYjsChangesToLexical( const currentEditorState = editor._editorState; editor.update( () => { - const pendingEditorState: EditorState = editor._pendingEditorState; + const pendingEditorState: EditorState | null = editor._pendingEditorState; for (let i = 0; i < events.length; i++) { const event = events[i]; @@ -210,7 +210,9 @@ function handleNormalizationMergeConflicts( for (let i = 0; i < mergedNodes.length; i++) { const [collabNode, text] = mergedNodes[i]; - collabNode._text = text; + if (collabNode instanceof CollabTextNode && typeof text === 'string') { + collabNode._text = text; + } } } diff --git a/packages/lexical/src/LexicalNode.ts b/packages/lexical/src/LexicalNode.ts index eb88f38d348..ef8baf095f9 100644 --- a/packages/lexical/src/LexicalNode.ts +++ b/packages/lexical/src/LexicalNode.ts @@ -133,8 +133,8 @@ export type DOMConversionFn = ( export type DOMChildConversion = ( lexicalNode: LexicalNode, - parentLexicalNode: LexicalNode | null, -) => LexicalNode | null | void; + parentLexicalNode: LexicalNode | null | undefined, +) => LexicalNode | null | undefined; export type DOMConversionMap = Record< NodeName, diff --git a/packages/shared/src/getDOMSelection.ts b/packages/shared/src/getDOMSelection.ts index 6745d6d337b..c0bf1cf7875 100644 --- a/packages/shared/src/getDOMSelection.ts +++ b/packages/shared/src/getDOMSelection.ts @@ -6,6 +6,6 @@ * */ -const getSelection = (): Selection => window.getSelection(); +const getSelection = (): Selection | null => window.getSelection(); export default getSelection; From dd65607237f4c7e676668c36710d45cb9aaa881c Mon Sep 17 00:00:00 2001 From: John Flockton Date: Sat, 18 Jun 2022 00:27:40 +0100 Subject: [PATCH 4/5] Fix failing test --- packages/lexical-react/src/LexicalTablePlugin.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/lexical-react/src/LexicalTablePlugin.ts b/packages/lexical-react/src/LexicalTablePlugin.ts index 0f367dfe740..642d90fc9e0 100644 --- a/packages/lexical-react/src/LexicalTablePlugin.ts +++ b/packages/lexical-react/src/LexicalTablePlugin.ts @@ -6,7 +6,11 @@ * */ -import type {InsertTableCommandPayload, TableSelection} from '@lexical/table'; +import type { + HTMLTableElementWithWithTableSelectionState, + InsertTableCommandPayload, + TableSelection, +} from '@lexical/table'; import type {ElementNode, NodeKey} from 'lexical'; import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; @@ -94,7 +98,9 @@ export function TablePlugin(): JSX.Element | null { for (const [nodeKey, mutation] of nodeMutations) { if (mutation === 'created') { editor.update(() => { - const tableElement = editor.getElementByKey(nodeKey); + const tableElement = editor.getElementByKey( + nodeKey, + ) as HTMLTableElementWithWithTableSelectionState; const tableNode = $getNodeByKey(nodeKey); if (tableElement && tableNode) { From efb20d874f28fa7708d7400674e2656577012b2f Mon Sep 17 00:00:00 2001 From: John Flockton Date: Sat, 18 Jun 2022 00:30:59 +0100 Subject: [PATCH 5/5] Fix build script --- packages/lexical-table/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lexical-table/src/index.ts b/packages/lexical-table/src/index.ts index 3b96d9a24bb..179ceb7265b 100644 --- a/packages/lexical-table/src/index.ts +++ b/packages/lexical-table/src/index.ts @@ -7,6 +7,7 @@ */ import type {Cell} from './LexicalTableSelection'; +import type {HTMLTableElementWithWithTableSelectionState} from './LexicalTableSelectionHelpers'; import type {LexicalCommand} from 'lexical'; import {createCommand} from 'lexical'; @@ -33,7 +34,6 @@ import { applyTableHandlers, getCellFromTarget, getTableSelectionFromTableElement, - HTMLTableElementWithWithTableSelectionState, } from './LexicalTableSelectionHelpers'; import { $createTableNodeWithDimensions,