From b70d217b28aa5997d6e1fdbde445aed5c83ab1e8 Mon Sep 17 00:00:00 2001 From: John Flockton Date: Thu, 23 Jun 2022 08:34:35 +0100 Subject: [PATCH] Migrate to TS strict mode 6/n (#2496) * Make suggested amends from @trueadm * Migrate files from playground --- packages/lexical-html/src/index.ts | 4 +- packages/lexical-playground/src/App.tsx | 6 +- .../src/commenting/index.ts | 58 ++++++++---- .../src/context/SettingsContext.tsx | 16 +++- .../src/context/SharedAutocompleteContext.tsx | 24 +++-- .../src/context/SharedHistoryContext.tsx | 8 +- .../lexical-playground/src/hooks/useModal.tsx | 4 +- .../lexical-playground/src/hooks/useReport.ts | 6 +- packages/lexical-playground/src/index.tsx | 4 +- .../src/nodes/AutocompleteNode.tsx | 4 +- .../nodes/ExcalidrawNode/ExcalidrawImage.tsx | 10 ++- .../nodes/ExcalidrawNode/ExcalidrawModal.tsx | 13 +-- .../src/nodes/ExcalidrawNode/index.tsx | 18 ++-- .../src/nodes/ImageNode.tsx | 14 ++- .../src/nodes/MentionNode.ts | 18 ++-- .../lexical-playground/src/nodes/PollNode.tsx | 18 ++-- .../src/nodes/StickyNode.tsx | 23 +++-- .../src/nodes/TweetNode.tsx | 21 +++-- .../src/nodes/YouTubeNode.tsx | 2 +- .../src/plugins/AutoLinkPlugin.tsx | 4 +- .../src/plugins/AutocompletePlugin.tsx | 24 +++-- .../src/plugins/ClickableLinkPlugin.ts | 12 +-- .../src/plugins/CommentPlugin.tsx | 41 +++++---- .../src/plugins/EmojisPlugin.ts | 4 +- .../src/plugins/ExcalidrawPlugin.ts | 2 +- .../src/plugins/ImagesPlugin.ts | 10 ++- .../src/plugins/KeywordsPlugin.ts | 2 +- .../src/plugins/MaxLengthPlugin.tsx | 4 +- .../src/plugins/MentionsPlugin.tsx | 40 ++++++--- .../src/plugins/PollPlugin.ts | 2 +- .../src/plugins/StickyPlugin.ts | 2 +- .../src/plugins/TableActionMenuPlugin.tsx | 21 +++-- .../src/plugins/TableCellResizer.tsx | 90 ++++++++++--------- .../src/plugins/TestRecorderPlugin.tsx | 81 +++++++++++------ .../src/plugins/ToolbarPlugin.tsx | 79 ++++++++-------- .../src/plugins/TwitterPlugin.ts | 2 +- .../src/plugins/TypingPerfPlugin.ts | 12 +-- .../src/plugins/YouTubePlugin.ts | 2 +- packages/lexical-playground/src/setupEnv.ts | 5 +- packages/lexical-playground/src/ui/Button.tsx | 3 +- .../lexical-playground/src/ui/ColorPicker.tsx | 25 +++--- .../lexical-playground/src/ui/DropDown.tsx | 11 ++- .../src/ui/EquationEditor.tsx | 6 +- .../lexical-playground/src/ui/FileInput.tsx | 2 +- .../src/ui/ImageResizer.tsx | 2 +- .../src/ui/KatexEquationAlterer.tsx | 2 +- .../lexical-playground/src/ui/LinkPreview.tsx | 20 +++-- packages/lexical-playground/src/ui/Modal.tsx | 12 +-- .../lexical-playground/src/ui/Placeholder.tsx | 3 +- .../src/utils/join-classes.ts | 4 +- .../lexical-playground/src/utils/swipe.ts | 36 ++++++-- .../src/LexicalBlockWithAlignableContents.tsx | 4 +- .../src/LexicalNestedComposer.tsx | 4 +- .../lexical-react/src/LexicalTablePlugin.ts | 2 +- packages/lexical/src/LexicalNode.ts | 14 +-- tsconfig.json | 3 +- tsconfig.test.json | 10 ++- 57 files changed, 541 insertions(+), 332 deletions(-) diff --git a/packages/lexical-html/src/index.ts b/packages/lexical-html/src/index.ts index b128f3639..eb59371d3 100644 --- a/packages/lexical-html/src/index.ts +++ b/packages/lexical-html/src/index.ts @@ -175,7 +175,9 @@ function $createNodesFromDOM( let currentLexicalNode = null; const transformFunction = getConversionFunction(node, editor); - const transformOutput = transformFunction ? transformFunction(node) : null; + const transformOutput = transformFunction + ? transformFunction(node as HTMLElement) + : null; let postTransform = null; if (transformOutput !== null) { diff --git a/packages/lexical-playground/src/App.tsx b/packages/lexical-playground/src/App.tsx index b88c9fd0d..d1c8299a9 100644 --- a/packages/lexical-playground/src/App.tsx +++ b/packages/lexical-playground/src/App.tsx @@ -122,7 +122,7 @@ function App(): JSX.Element { : prepopulatedRichText, namespace: 'Playground', nodes: [...PlaygroundNodes], - onError: (error) => { + onError: (error: Error) => { throw error; }, theme: PlaygroundEditorTheme, @@ -141,8 +141,8 @@ function App(): JSX.Element { - {isDevPlayground && } - {measureTypingPerf && } + {isDevPlayground ? : null} + {measureTypingPerf ? : null} diff --git a/packages/lexical-playground/src/commenting/index.ts b/packages/lexical-playground/src/commenting/index.ts index 263378c77..592350f0b 100644 --- a/packages/lexical-playground/src/commenting/index.ts +++ b/packages/lexical-playground/src/commenting/index.ts @@ -12,7 +12,13 @@ import {TOGGLE_CONNECT_COMMAND} from '@lexical/yjs'; import {COMMAND_PRIORITY_EDITOR} from 'lexical'; import {useEffect, useState} from 'react'; import {WebsocketProvider} from 'y-websocket'; -import {Array as YArray, Map as YMap, YArrayEvent} from 'yjs'; +import { + Array as YArray, + Map as YMap, + Transaction, + YArrayEvent, + YEvent, +} from 'yjs'; export type Comment = { author: string; @@ -109,7 +115,9 @@ export class CommentStore { offset?: number, ): void { const nextComments = Array.from(this._comments); - const sharedCommentsArray = this._getCollabComments(); + // The YJS types explicitly use `any` as well. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const sharedCommentsArray: YArray | null = this._getCollabComments(); if (thread !== undefined && commentOrThread.type === 'comment') { for (let i = 0; i < nextComments.length; i++) { @@ -118,7 +126,7 @@ export class CommentStore { const newThread = cloneThread(comment); nextComments.splice(i, 1, newThread); const insertOffset = offset || newThread.comments.length; - if (this.isCollaborative()) { + if (this.isCollaborative() && sharedCommentsArray !== null) { const parentSharedArray = sharedCommentsArray .get(i) .get('comments'); @@ -133,7 +141,7 @@ export class CommentStore { } } else { const insertOffset = offset || nextComments.length; - if (this.isCollaborative()) { + if (this.isCollaborative() && sharedCommentsArray !== null) { this._withRemoteTransaction(() => { const sharedMap = this._createCollabSharedMap(commentOrThread); sharedCommentsArray.insert(nextComments.length, [sharedMap]); @@ -147,7 +155,9 @@ export class CommentStore { deleteComment(commentOrThread: Comment | Thread, thread?: Thread): void { const nextComments = Array.from(this._comments); - const sharedCommentsArray = this._getCollabComments(); + // The YJS types explicitly use `any` as well. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const sharedCommentsArray: YArray | null = this._getCollabComments(); if (thread !== undefined) { for (let i = 0; i < nextComments.length; i++) { @@ -158,7 +168,7 @@ export class CommentStore { const threadComments = newThread.comments; if (threadComments.length === 1) { const threadIndex = nextComments.indexOf(newThread); - if (this.isCollaborative()) { + if (this.isCollaborative() && sharedCommentsArray !== null) { this._withRemoteTransaction(() => { sharedCommentsArray.delete(threadIndex); }); @@ -166,7 +176,7 @@ export class CommentStore { nextComments.splice(threadIndex, 1); } else { const index = threadComments.indexOf(commentOrThread as Comment); - if (this.isCollaborative()) { + if (this.isCollaborative() && sharedCommentsArray !== null) { const parentSharedArray = sharedCommentsArray .get(i) .get('comments'); @@ -181,7 +191,7 @@ export class CommentStore { } } else { const index = nextComments.indexOf(commentOrThread); - if (this.isCollaborative()) { + if (this.isCollaborative() && sharedCommentsArray !== null) { this._withRemoteTransaction(() => { sharedCommentsArray.delete(index); }); @@ -292,7 +302,12 @@ export class CommentStore { COMMAND_PRIORITY_EDITOR, ); - const onSharedCommentChanges = (events, transaction) => { + const onSharedCommentChanges = ( + // The YJS types explicitly use `any` as well. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + events: Array>, + transaction: Transaction, + ) => { if (transaction.origin !== this) { for (let i = 0; i < events.length; i++) { const event = events[i]; @@ -329,12 +344,13 @@ export class CommentStore { map .get('comments') .toArray() - .map((innerComment) => + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .map((innerComment: Map) => createComment( - innerComment.get('content'), - innerComment.get('author'), - innerComment.get('id'), - innerComment.get('timeStamp'), + innerComment.get('content') as string, + innerComment.get('author') as string, + innerComment.get('id') as string, + innerComment.get('timeStamp') as number, ), ), id, @@ -346,7 +362,9 @@ export class CommentStore { map.get('timeStamp'), ); this._withLocalTransaction(() => { - this.addComment(commentOrThread, parentThread, offset); + if (parentThread !== undefined && parentThread !== false) { + this.addComment(commentOrThread, parentThread, offset); + } }); }); } else if (typeof retain === 'number') { @@ -354,11 +372,13 @@ export class CommentStore { } else if (typeof del === 'number') { for (let d = 0; d < del; d++) { const commentOrThread = - parentThread === undefined + parentThread === undefined || parentThread === false ? this._comments[offset] : parentThread.comments[offset]; this._withLocalTransaction(() => { - this.deleteComment(commentOrThread, parentThread); + if (parentThread !== undefined && parentThread !== false) { + this.deleteComment(commentOrThread, parentThread); + } }); offset++; } @@ -369,6 +389,10 @@ export class CommentStore { } }; + if (sharedCommentsArray === null) { + return () => null; + } + sharedCommentsArray.observeDeep(onSharedCommentChanges); connect(); diff --git a/packages/lexical-playground/src/context/SettingsContext.tsx b/packages/lexical-playground/src/context/SettingsContext.tsx index 0a503d322..27f5a6fce 100644 --- a/packages/lexical-playground/src/context/SettingsContext.tsx +++ b/packages/lexical-playground/src/context/SettingsContext.tsx @@ -9,7 +9,14 @@ import type {SettingName} from '../appSettings'; import * as React from 'react'; -import {createContext, useCallback, useContext, useMemo, useState} from 'react'; +import { + createContext, + ReactNode, + useCallback, + useContext, + useMemo, + useState, +} from 'react'; import {DEFAULT_SETTINGS} from '../appSettings'; @@ -19,7 +26,7 @@ type SettingsContextShape = { }; const Context: React.Context = createContext({ - setOption: () => { + setOption: (name: SettingName, value: boolean) => { return; }, settings: DEFAULT_SETTINGS, @@ -28,9 +35,10 @@ const Context: React.Context = createContext({ export const SettingsContext = ({ children, }: { - children: JSX.Element | string | (JSX.Element | string)[]; + children: ReactNode; }): JSX.Element => { const [settings, setSettings] = useState(DEFAULT_SETTINGS); + const setOption = useCallback((setting: SettingName, value: boolean) => { setSettings((options) => ({ ...options, @@ -42,9 +50,11 @@ export const SettingsContext = ({ setURLParam(setting, value); } }, []); + const contextValue = useMemo(() => { return {setOption, settings}; }, [setOption, settings]); + return {children}; }; diff --git a/packages/lexical-playground/src/context/SharedAutocompleteContext.tsx b/packages/lexical-playground/src/context/SharedAutocompleteContext.tsx index a0d56c39f..4f282709e 100644 --- a/packages/lexical-playground/src/context/SharedAutocompleteContext.tsx +++ b/packages/lexical-playground/src/context/SharedAutocompleteContext.tsx @@ -7,7 +7,14 @@ */ import * as React from 'react'; -import {createContext, useContext, useEffect, useMemo, useState} from 'react'; +import { + createContext, + ReactNode, + useContext, + useEffect, + useMemo, + useState, +} from 'react'; type Suggestion = null | string; type CallbackFn = (newSuggestion: Suggestion) => void; @@ -16,15 +23,22 @@ type PublishFn = (newSuggestion: Suggestion) => void; type ContextShape = [SubscribeFn, PublishFn]; type HookShape = [suggestion: Suggestion, setSuggestion: PublishFn]; -const Context: React.Context = createContext(null); +const Context: React.Context = createContext([ + (_cb) => () => { + return; + }, + (_newSuggestion: Suggestion) => { + return; + }, +]); export const SharedAutocompleteContext = ({ children, }: { - children: JSX.Element | string | (JSX.Element | string)[]; + children: ReactNode; }): JSX.Element => { const context: ContextShape = useMemo(() => { - let suggestion = null; + let suggestion: Suggestion | null = null; const listeners: Set = new Set(); return [ (cb: (newSuggestion: Suggestion) => void) => { @@ -47,7 +61,7 @@ export const SharedAutocompleteContext = ({ export const useSharedAutocompleteContext = (): HookShape => { const [subscribe, publish]: ContextShape = useContext(Context); - const [suggestion, setSuggestion] = useState(null); + const [suggestion, setSuggestion] = useState(null); useEffect(() => { return subscribe((newSuggestion: Suggestion) => { setSuggestion(newSuggestion); diff --git a/packages/lexical-playground/src/context/SharedHistoryContext.tsx b/packages/lexical-playground/src/context/SharedHistoryContext.tsx index 27fffa6b9..316c337f4 100644 --- a/packages/lexical-playground/src/context/SharedHistoryContext.tsx +++ b/packages/lexical-playground/src/context/SharedHistoryContext.tsx @@ -10,20 +10,18 @@ import type {HistoryState} from '@lexical/react/LexicalHistoryPlugin'; import {createEmptyHistoryState} from '@lexical/react/LexicalHistoryPlugin'; import * as React from 'react'; -import {createContext, useContext, useMemo} from 'react'; +import {createContext, ReactNode, useContext, useMemo} from 'react'; type ContextShape = { historyState?: HistoryState; }; -const Context: React.Context = createContext({ - historyState: {current: null, redoStack: [], undoStack: []}, -}); +const Context: React.Context = createContext({}); export const SharedHistoryContext = ({ children, }: { - children: JSX.Element | string | (JSX.Element | string)[]; + children: ReactNode; }): JSX.Element => { const historyContext = useMemo( () => ({historyState: createEmptyHistoryState()}), diff --git a/packages/lexical-playground/src/hooks/useModal.tsx b/packages/lexical-playground/src/hooks/useModal.tsx index 60c5c6250..ef68e7c79 100644 --- a/packages/lexical-playground/src/hooks/useModal.tsx +++ b/packages/lexical-playground/src/hooks/useModal.tsx @@ -12,7 +12,7 @@ import * as React from 'react'; import Modal from '../ui/Modal'; export default function useModal(): [ - JSX.Element, + JSX.Element | null, (title: string, showModal: (onClose: () => void) => JSX.Element) => void, ] { const [modalContent, setModalContent] = useState void) => JSX.Element, closeOnClickOutside = false, diff --git a/packages/lexical-playground/src/hooks/useReport.ts b/packages/lexical-playground/src/hooks/useReport.ts index 679826824..5cbabe9c3 100644 --- a/packages/lexical-playground/src/hooks/useReport.ts +++ b/packages/lexical-playground/src/hooks/useReport.ts @@ -34,7 +34,9 @@ const getElement = (): HTMLElement => { export default function useReport(): (arg0: string) => NodeJS.Timeout { const timer = useRef(null); const cleanup = useCallback(() => { - clearTimeout(timer.current); + if (timer !== null) { + clearTimeout(timer.current as NodeJS.Timeout); + } if (document.body) { document.body.removeChild(getElement()); @@ -50,7 +52,7 @@ export default function useReport(): (arg0: string) => NodeJS.Timeout { // eslint-disable-next-line no-console console.log(content); const element = getElement(); - clearTimeout(timer.current); + clearTimeout(timer.current as NodeJS.Timeout); element.innerHTML = content; timer.current = setTimeout(cleanup, 1000); return timer.current; diff --git a/packages/lexical-playground/src/index.tsx b/packages/lexical-playground/src/index.tsx index ef294964d..7bc2fe88f 100644 --- a/packages/lexical-playground/src/index.tsx +++ b/packages/lexical-playground/src/index.tsx @@ -15,7 +15,7 @@ import {createRoot} from 'react-dom/client'; import App from './App'; // Handle runtime errors -const showErrorOverlay = (err) => { +const showErrorOverlay = (err: Event) => { const ErrorOverlay = customElements.get('vite-error-overlay'); if (!ErrorOverlay) { return; @@ -32,7 +32,7 @@ window.addEventListener('unhandledrejection', ({reason}) => showErrorOverlay(reason), ); -createRoot(document.getElementById('root')).render( +createRoot(document.getElementById('root') as HTMLElement).render( , diff --git a/packages/lexical-playground/src/nodes/AutocompleteNode.tsx b/packages/lexical-playground/src/nodes/AutocompleteNode.tsx index 84bedc115..1c83ec113 100644 --- a/packages/lexical-playground/src/nodes/AutocompleteNode.tsx +++ b/packages/lexical-playground/src/nodes/AutocompleteNode.tsx @@ -28,7 +28,7 @@ export type SerializedAutocompleteNode = Spread< SerializedLexicalNode >; -export class AutocompleteNode extends DecoratorNode { +export class AutocompleteNode extends DecoratorNode { // TODO add comment __uuid: string; @@ -73,7 +73,7 @@ export class AutocompleteNode extends DecoratorNode { return document.createElement('span'); } - decorate(): JSX.Element { + decorate(): JSX.Element | null { if (this.__uuid !== UUID) { return null; } diff --git a/packages/lexical-playground/src/nodes/ExcalidrawNode/ExcalidrawImage.tsx b/packages/lexical-playground/src/nodes/ExcalidrawNode/ExcalidrawImage.tsx index 45fbc5778..23c839b4a 100644 --- a/packages/lexical-playground/src/nodes/ExcalidrawNode/ExcalidrawImage.tsx +++ b/packages/lexical-playground/src/nodes/ExcalidrawNode/ExcalidrawImage.tsx @@ -54,7 +54,7 @@ type Props = { // exportToSvg has fonts from excalidraw.com // We don't want them to be used in open source -const removeStyleFromSvg_HACK = (svg) => { +const removeStyleFromSvg_HACK = (svg: SVGElement) => { const styleTag = svg?.firstElementChild?.firstElementChild; // Generated SVG is getting double-sized by height and width attributes @@ -81,11 +81,15 @@ export default function ExcalidrawImage({ appState = null, rootClassName = null, }: Props): JSX.Element { - const [Svg, setSvg] = useState(null); + const [Svg, setSvg] = useState(null); useEffect(() => { const setContent = async () => { - const svg: Element = await exportToSvg({ + if (appState === null) { + return; + } + + const svg: SVGElement = await exportToSvg({ appState, elements, files: null, diff --git a/packages/lexical-playground/src/nodes/ExcalidrawNode/ExcalidrawModal.tsx b/packages/lexical-playground/src/nodes/ExcalidrawNode/ExcalidrawModal.tsx index c0ff21ed9..3777c4d1e 100644 --- a/packages/lexical-playground/src/nodes/ExcalidrawNode/ExcalidrawModal.tsx +++ b/packages/lexical-playground/src/nodes/ExcalidrawNode/ExcalidrawModal.tsx @@ -57,8 +57,7 @@ export default function ExcalidrawModal({ onHide, onDelete, }: Props): ReactPortal | null { - const excalidrawRef = useRef(null); - const excaliDrawModelRef = useRef(null); + const excaliDrawModelRef = useRef(null); const [discardModalOpen, setDiscardModalOpen] = useState(false); const [elements, setElements] = useState>(initialElements); @@ -70,12 +69,12 @@ export default function ExcalidrawModal({ }, []); useEffect(() => { - let modalOverlayElement = null; + let modalOverlayElement: HTMLElement | null = null; const clickOutsideHandler = (event: MouseEvent) => { const target = event.target; if ( excaliDrawModelRef.current !== null && - !excaliDrawModelRef.current.contains(target) && + !excaliDrawModelRef.current.contains(target as Node) && closeOnClickOutside ) { onDelete(); @@ -143,15 +142,11 @@ export default function ExcalidrawModal({ ); } - useEffect(() => { - excalidrawRef?.current?.updateScene({elements: initialElements}); - }, [initialElements]); - if (isShown === false) { return null; } - const onChange = (els) => { + const onChange = (els: ReadonlyArray) => { setElements(els); }; diff --git a/packages/lexical-playground/src/nodes/ExcalidrawNode/index.tsx b/packages/lexical-playground/src/nodes/ExcalidrawNode/index.tsx index e1f03724e..ca9e17ca7 100644 --- a/packages/lexical-playground/src/nodes/ExcalidrawNode/index.tsx +++ b/packages/lexical-playground/src/nodes/ExcalidrawNode/index.tsx @@ -57,9 +57,8 @@ function ExcalidrawComponent({ const [isResizing, setIsResizing] = useState(false); const onDelete = useCallback( - (payload) => { + (event: KeyboardEvent) => { if (isSelected && $isNodeSelection($getSelection())) { - const event: KeyboardEvent = payload; event.preventDefault(); editor.update(() => { const node = $getNodeByKey(nodeKey); @@ -153,7 +152,7 @@ function ExcalidrawComponent({ setIsResizing(true); }; - const onResizeEnd = (nextWidth, nextHeight) => { + const onResizeEnd = () => { // Delay hiding the resize bars for click case setTimeout(() => { setIsResizing(false); @@ -212,7 +211,9 @@ export type SerializedExcalidrawNode = Spread< SerializedLexicalNode >; -function convertExcalidrawElement(domNode: HTMLElement): DOMConversionOutput { +function convertExcalidrawElement( + domNode: HTMLElement, +): DOMConversionOutput | null { const excalidrawData = domNode.getAttribute('data-lexical-excalidraw-json'); if (excalidrawData) { const node = $createExcalidrawNode(); @@ -267,9 +268,9 @@ export class ExcalidrawNode extends DecoratorNode { return false; } - static importDOM(): DOMConversionMap | null { + static importDOM(): DOMConversionMap | null { return { - span: (domNode: HTMLElement) => { + span: (domNode: HTMLSpanElement) => { if (!domNode.hasAttribute('data-lexical-excalidraw-json')) { return null; } @@ -285,7 +286,10 @@ export class ExcalidrawNode extends DecoratorNode { const element = document.createElement('span'); const content = editor.getElementByKey(this.getKey()); if (content !== null) { - element.innerHTML = content.querySelector('svg').outerHTML; + const svg = content.querySelector('svg'); + if (svg !== null) { + element.innerHTML = svg.outerHTML; + } } element.setAttribute('data-lexical-excalidraw-json', this.__data); return {element}; diff --git a/packages/lexical-playground/src/nodes/ImageNode.tsx b/packages/lexical-playground/src/nodes/ImageNode.tsx index 539d9e8ff..9aba36e8d 100644 --- a/packages/lexical-playground/src/nodes/ImageNode.tsx +++ b/packages/lexical-playground/src/nodes/ImageNode.tsx @@ -8,9 +8,12 @@ import type { EditorConfig, + GridSelection, LexicalEditor, LexicalNode, NodeKey, + NodeSelection, + RangeSelection, SerializedEditor, SerializedLexicalNode, Spread, @@ -104,7 +107,7 @@ function LazyImage({ useSuspenseImage(src); return ( {altText}(null); const onDelete = useCallback( (payload: KeyboardEvent) => { @@ -219,7 +224,10 @@ function ImageComponent({ }); }; - const onResizeEnd = (nextWidth, nextHeight) => { + const onResizeEnd = ( + nextWidth: 'inherit' | number, + nextHeight: 'inherit' | number, + ) => { // Delay hiding the resize bars for click case setTimeout(() => { setIsResizing(false); diff --git a/packages/lexical-playground/src/nodes/MentionNode.ts b/packages/lexical-playground/src/nodes/MentionNode.ts index 3bf5cb466..c16e4b226 100644 --- a/packages/lexical-playground/src/nodes/MentionNode.ts +++ b/packages/lexical-playground/src/nodes/MentionNode.ts @@ -28,11 +28,19 @@ export type SerializedMentionNode = Spread< SerializedTextNode >; -function convertMentionElement(domNode: HTMLElement): DOMConversionOutput { - const node = $createMentionNode(domNode.textContent); - return { - node, - }; +function convertMentionElement( + domNode: HTMLElement, +): DOMConversionOutput | null { + const textContent = domNode.textContent; + + if (textContent !== null) { + const node = $createMentionNode(textContent); + return { + node, + }; + } + + return null; } const mentionStyle = 'background-color: rgba(24, 119, 232, 0.2)'; diff --git a/packages/lexical-playground/src/nodes/PollNode.tsx b/packages/lexical-playground/src/nodes/PollNode.tsx index d9690d01a..43f517062 100644 --- a/packages/lexical-playground/src/nodes/PollNode.tsx +++ b/packages/lexical-playground/src/nodes/PollNode.tsx @@ -22,10 +22,13 @@ import { DOMConversionMap, DOMConversionOutput, DOMExportOutput, + GridSelection, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, LexicalNode, NodeKey, + NodeSelection, + RangeSelection, SerializedLexicalNode, Spread, } from 'lexical'; @@ -87,7 +90,7 @@ function PollOptionComponent({ option: Option; options: Options; totalVotes: number; - withPollNode: (cb: (PollNode) => void) => void; + withPollNode: (cb: (pollNode: PollNode) => void) => void; }): JSX.Element { const {clientID} = useCollaborationContext(); const checkboxRef = useRef(null); @@ -166,7 +169,9 @@ function PollComponent({ const totalVotes = useMemo(() => getTotalVotes(options), [options]); const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection(nodeKey); - const [selection, setSelection] = useState(null); + const [selection, setSelection] = useState< + RangeSelection | NodeSelection | GridSelection | null + >(null); const ref = useRef(null); const onDelete = useCallback( @@ -276,10 +281,13 @@ export type SerializedPollNode = Spread< SerializedLexicalNode >; -function convertPollElement(domNode: HTMLElement): DOMConversionOutput { +function convertPollElement(domNode: HTMLElement): DOMConversionOutput | null { const question = domNode.getAttribute('data-lexical-poll-question'); - const node = $createPollNode(question); - return {node}; + if (question !== null) { + const node = $createPollNode(question); + return {node}; + } + return null; } export class PollNode extends DecoratorNode { diff --git a/packages/lexical-playground/src/nodes/StickyNode.tsx b/packages/lexical-playground/src/nodes/StickyNode.tsx index 0d4247c9d..6a2cdd23b 100644 --- a/packages/lexical-playground/src/nodes/StickyNode.tsx +++ b/packages/lexical-playground/src/nodes/StickyNode.tsx @@ -42,7 +42,19 @@ import StickyEditorTheme from '../themes/StickyEditorTheme'; import ContentEditable from '../ui/ContentEditable'; import Placeholder from '../ui/Placeholder'; -function positionSticky(stickyElem: HTMLElement, positioning): void { +type Positioning = { + isDragging: boolean; + offsetX: number; + offsetY: number; + rootElementRect: null | ClientRect; + x: number; + y: number; +}; + +function positionSticky( + stickyElem: HTMLElement, + positioning: Positioning, +): void { const style = stickyElem.style; const rootElementRect = positioning.rootElementRect; const rectLeft = rootElementRect !== null ? rootElementRect.left : 0; @@ -66,14 +78,7 @@ function StickyComponent({ }): JSX.Element { const [editor] = useLexicalComposerContext(); const stickyContainerRef = useRef(null); - const positioningRef = useRef<{ - isDragging: boolean; - offsetX: number; - offsetY: number; - rootElementRect: null | ClientRect; - x: number; - y: number; - }>({ + const positioningRef = useRef({ isDragging: false, offsetX: 0, offsetY: 0, diff --git a/packages/lexical-playground/src/nodes/TweetNode.tsx b/packages/lexical-playground/src/nodes/TweetNode.tsx index bd6aaef28..a00907d51 100644 --- a/packages/lexical-playground/src/nodes/TweetNode.tsx +++ b/packages/lexical-playground/src/nodes/TweetNode.tsx @@ -44,10 +44,15 @@ type TweetComponentProps = Readonly<{ tweetID: string; }>; -function convertTweetElement(domNode: HTMLElement): null | DOMConversionOutput { +function convertTweetElement( + domNode: HTMLDivElement, +): DOMConversionOutput | null { const id = domNode.getAttribute('data-lexical-tweet-id'); - const node = $createTweetNode(id); - return {node}; + if (id) { + const node = $createTweetNode(id); + return {node}; + } + return null; } function TweetComponent({ @@ -91,7 +96,9 @@ function TweetComponent({ script.async = true; document.body?.appendChild(script); script.onload = createTweet; - script.onerror = onError; + if (onError) { + script.onerror = onError as OnErrorEventHandler; + } } else { createTweet(); } @@ -151,9 +158,9 @@ export class TweetNode extends DecoratorBlockNode { }; } - static importDOM(): DOMConversionMap | null { + static importDOM(): DOMConversionMap | null { return { - div: (domNode: HTMLElement) => { + div: (domNode: HTMLDivElement) => { if (!domNode.hasAttribute('data-lexical-tweet-id')) { return null; } @@ -171,7 +178,7 @@ export class TweetNode extends DecoratorBlockNode { return {element}; } - constructor(id: string, format?: ElementFormatType | null, key?: NodeKey) { + constructor(id: string, format?: ElementFormatType, key?: NodeKey) { super(format, key); this.__id = id; } diff --git a/packages/lexical-playground/src/nodes/YouTubeNode.tsx b/packages/lexical-playground/src/nodes/YouTubeNode.tsx index 0da7f3f8f..85fc6eb80 100644 --- a/packages/lexical-playground/src/nodes/YouTubeNode.tsx +++ b/packages/lexical-playground/src/nodes/YouTubeNode.tsx @@ -91,7 +91,7 @@ export class YouTubeNode extends DecoratorBlockNode { }; } - constructor(id: string, format?: ElementFormatType | null, key?: NodeKey) { + constructor(id: string, format?: ElementFormatType, key?: NodeKey) { super(format, key); this.__id = id; } diff --git a/packages/lexical-playground/src/plugins/AutoLinkPlugin.tsx b/packages/lexical-playground/src/plugins/AutoLinkPlugin.tsx index 6a2e02edf..e98c84647 100644 --- a/packages/lexical-playground/src/plugins/AutoLinkPlugin.tsx +++ b/packages/lexical-playground/src/plugins/AutoLinkPlugin.tsx @@ -16,7 +16,7 @@ const EMAIL_MATCHER = /(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/; const MATCHERS = [ - (text) => { + (text: string) => { const match = URL_MATCHER.exec(text); return ( match && { @@ -27,7 +27,7 @@ const MATCHERS = [ } ); }, - (text) => { + (text: string) => { const match = EMAIL_MATCHER.exec(text); return ( match && { diff --git a/packages/lexical-playground/src/plugins/AutocompletePlugin.tsx b/packages/lexical-playground/src/plugins/AutocompletePlugin.tsx index ff73d898c..3b830013b 100644 --- a/packages/lexical-playground/src/plugins/AutocompletePlugin.tsx +++ b/packages/lexical-playground/src/plugins/AutocompletePlugin.tsx @@ -83,7 +83,7 @@ function useQuery(): (searchText: string) => SearchPromise { }, []); } -export default function AutocompletePlugin(): JSX.Element { +export default function AutocompletePlugin(): JSX.Element | null { const [editor] = useLexicalComposerContext(); const [, setSuggestion] = useSharedAutocompleteContext(); const query = useQuery(); @@ -94,7 +94,10 @@ export default function AutocompletePlugin(): JSX.Element { let lastSuggestion: null | string = null; let searchPromise: null | SearchPromise = null; function $clearSuggestion() { - const autocompleteNode = $getNodeByKey(autocompleteNodeKey); + const autocompleteNode = + autocompleteNodeKey !== null + ? $getNodeByKey(autocompleteNodeKey) + : null; if (autocompleteNode !== null && autocompleteNode.isAttached()) { autocompleteNode.remove(); autocompleteNodeKey = null; @@ -160,9 +163,11 @@ export default function AutocompletePlugin(): JSX.Element { $clearSuggestion(); searchPromise = query(match); searchPromise.promise - .then((newSuggestion) => - updateAsyncSuggestion(searchPromise, newSuggestion), - ) + .then((newSuggestion) => { + if (searchPromise !== null) { + updateAsyncSuggestion(searchPromise, newSuggestion); + } + }) .catch((e) => { console.error(e); }); @@ -170,7 +175,7 @@ export default function AutocompletePlugin(): JSX.Element { }); } function $handleAutocompleteIntent(): boolean { - if (lastSuggestion === null) { + if (lastSuggestion === null || autocompleteNodeKey === null) { return false; } const autocompleteNode = $getNodeByKey(autocompleteNodeKey); @@ -202,6 +207,9 @@ export default function AutocompletePlugin(): JSX.Element { $clearSuggestion(); }); } + + const rootElem = editor.getRootElement(); + return mergeRegister( editor.registerNodeTransform( AutocompleteNode, @@ -218,7 +226,9 @@ export default function AutocompletePlugin(): JSX.Element { $handleKeypressCommand, COMMAND_PRIORITY_LOW, ), - addSwipeRightListener(editor.getRootElement(), handleSwipeRight), + ...(rootElem !== null + ? [addSwipeRightListener(rootElem, handleSwipeRight)] + : []), unmountSuggestion, ); }, [editor, query, setSuggestion]); diff --git a/packages/lexical-playground/src/plugins/ClickableLinkPlugin.ts b/packages/lexical-playground/src/plugins/ClickableLinkPlugin.ts index f8c817dac..266a00e6b 100644 --- a/packages/lexical-playground/src/plugins/ClickableLinkPlugin.ts +++ b/packages/lexical-playground/src/plugins/ClickableLinkPlugin.ts @@ -26,7 +26,7 @@ export default function ClickableLinkPlugin({ }: { filter?: LinkFilter; newTab?: boolean; -}): JSX.Element { +}): JSX.Element | null { const [editor] = useLexicalComposerContext(); useEffect(() => { function onClick(e: Event) { @@ -69,10 +69,12 @@ export default function ClickableLinkPlugin({ } try { - window.open( - href, - newTab || event.metaKey || event.ctrlKey ? '_blank' : '_self', - ); + if (href !== null) { + window.open( + href, + newTab || event.metaKey || event.ctrlKey ? '_blank' : '_self', + ); + } } catch { // It didn't work, which is better than throwing an exception! } diff --git a/packages/lexical-playground/src/plugins/CommentPlugin.tsx b/packages/lexical-playground/src/plugins/CommentPlugin.tsx index 0f56f2fe4..3bee6d405 100644 --- a/packages/lexical-playground/src/plugins/CommentPlugin.tsx +++ b/packages/lexical-playground/src/plugins/CommentPlugin.tsx @@ -77,7 +77,7 @@ function AddCommentBox({ editor: LexicalEditor; onAddComment: () => void; }): JSX.Element { - const boxRef = useRef(null); + const boxRef = useRef(null); const updatePosition = useCallback(() => { const boxElem = boxRef.current; @@ -135,7 +135,7 @@ function EditorRefPlugin({ function EscapeHandlerPlugin({ onEscape, }: { - onEscape: (KeyboardEvent) => boolean; + onEscape: (e: KeyboardEvent) => boolean; }): null { const [editor] = useLexicalComposerContext(); @@ -164,13 +164,13 @@ function PlainTextEditor({ className?: string; editorRef?: {current: null | LexicalEditor}; onChange: (editorState: EditorState, editor: LexicalEditor) => void; - onEscape: (KeyboardEvent) => boolean; + onEscape: (e: KeyboardEvent) => boolean; placeholder?: string; }) { const initialConfig = { namespace: 'Commenting', nodes: [], - onError: (error) => { + onError: (error: Error) => { throw error; }, theme: CommentEditorTheme, @@ -194,7 +194,10 @@ function PlainTextEditor({ ); } -function useOnChange(setContent, setCanSubmit) { +function useOnChange( + setContent: (text: string) => void, + setCanSubmit: (canSubmit: boolean) => void, +) { return useCallback( (editorState: EditorState, _editor: LexicalEditor) => { editorState.read(() => { @@ -220,7 +223,7 @@ function CommentInputBox({ }) { const [content, setContent] = useState(''); const [canSubmit, setCanSubmit] = useState(false); - const boxRef = useRef(null); + const boxRef = useRef(null); const selectionState = useMemo( () => ({ container: document.createElement('div'), @@ -256,12 +259,13 @@ function CommentInputBox({ boxElem.style.left = `${correctedLeft}px`; boxElem.style.top = `${bottom + 20}px`; const selectionRectsLength = selectionRects.length; - const {elements, container} = selectionState; + const {container} = selectionState; + const elements: Array = selectionState.elements; const elementsLength = elements.length; for (let i = 0; i < selectionRectsLength; i++) { const selectionRect = selectionRects[i]; - let elem = elements[i]; + let elem: HTMLSpanElement = elements[i]; if (elem === undefined) { elem = document.createElement('span'); elements[i] = elem; @@ -365,7 +369,7 @@ function CommentsComposer({ }) { const [content, setContent] = useState(''); const [canSubmit, setCanSubmit] = useState(false); - const editorRef = useRef(null); + const editorRef = useRef(null); const author = useCollabAuthorName(); const onChange = useOnChange(setContent, setCanSubmit); @@ -375,7 +379,7 @@ function CommentsComposer({ submitAddComment(createComment(content, author), false, thread); const editor = editorRef.current; if (editor !== null) { - editor.dispatchCommand(CLEAR_EDITOR_COMMAND); + editor.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined); } } }; @@ -553,7 +557,7 @@ function CommentsPanelList({ {comments.map((commentOrThread) => { const id = commentOrThread.id; if (commentOrThread.type === 'thread') { - const handleClickThread = (event) => { + const handleClickThread = () => { const markNodeKeys = markNodeMap.get(id); if ( markNodeKeys !== undefined && @@ -644,8 +648,8 @@ function CommentsPanel({ thread?: Thread, ) => void; }): JSX.Element { - const footerRef = useRef(null); - const listRef = useRef(null); + const footerRef = useRef(null); + const listRef = useRef(null); const isEmpty = comments.length === 0; useLayoutEffect(() => { @@ -713,7 +717,7 @@ export default function CommentPlugin({ const markNodeMap = useMemo>>(() => { return new Map(); }, []); - const [activeAnchorKey, setActiveAnchorKey] = useState(null); + const [activeAnchorKey, setActiveAnchorKey] = useState(); const [activeIDs, setActiveIDs] = useState>([]); const [showCommentInput, setShowCommentInput] = useState(false); const [showComments, setShowComments] = useState(false); @@ -797,7 +801,7 @@ export default function CommentPlugin({ ); useEffect(() => { - const changedElems = []; + const changedElems: Array = []; for (let i = 0; i < activeIDs.length; i++) { const id = activeIDs[i]; const keys = markNodeMap.get(id); @@ -842,7 +846,7 @@ export default function CommentPlugin({ editor.getEditorState().read(() => { for (const [key, mutation] of mutations) { const node: null | MarkNode = $getNodeByKey(key); - let ids = []; + let ids: NodeKey[] = []; if (mutation === 'destroyed') { ids = markNodeKeysToIDs.get(key) || []; @@ -916,7 +920,9 @@ export default function CommentPlugin({ INSERT_INLINE_COMMAND, () => { const domSelection = window.getSelection(); - domSelection.removeAllRanges(); + if (domSelection !== null) { + domSelection.removeAllRanges(); + } setShowCommentInput(true); return true; }, @@ -941,6 +947,7 @@ export default function CommentPlugin({ document.body, )} {activeAnchorKey !== null && + activeAnchorKey !== undefined && !showCommentInput && createPortal( = createCommand(); -export default function ExcalidrawPlugin(): JSX.Element { +export default function ExcalidrawPlugin(): JSX.Element | null { const [editor] = useLexicalComposerContext(); useEffect(() => { if (!editor.hasNodes([ExcalidrawNode])) { diff --git a/packages/lexical-playground/src/plugins/ImagesPlugin.ts b/packages/lexical-playground/src/plugins/ImagesPlugin.ts index 1e2f139ee..670b530f9 100644 --- a/packages/lexical-playground/src/plugins/ImagesPlugin.ts +++ b/packages/lexical-playground/src/plugins/ImagesPlugin.ts @@ -38,7 +38,7 @@ export type InsertImagePayload = Readonly; export const INSERT_IMAGE_COMMAND: LexicalCommand = createCommand(); -export default function ImagesPlugin(): JSX.Element { +export default function ImagesPlugin(): JSX.Element | null { const [editor] = useLexicalComposerContext(); useEffect(() => { @@ -150,7 +150,9 @@ function onDrop(event: DragEvent, editor: LexicalEditor): boolean { const range = getDragSelection(event); node.remove(); const rangeSelection = $createRangeSelection(); - rangeSelection.applyDOMRange(range); + if (range !== null && range !== undefined) { + rangeSelection.applyDOMRange(range); + } $setSelection(rangeSelection); editor.dispatchCommand(INSERT_IMAGE_COMMAND, data); } @@ -198,12 +200,12 @@ function canDropImage(event: DragEvent): boolean { ); } -function getDragSelection(event: DragEvent): Range { +function getDragSelection(event: DragEvent): Range | null | undefined { let range; const domSelection = getSelection(); if (document.caretRangeFromPoint) { range = document.caretRangeFromPoint(event.clientX, event.clientY); - } else if (event.rangeParent) { + } else if (event.rangeParent && domSelection !== null) { domSelection.collapse(event.rangeParent, event.rangeOffset || 0); range = domSelection.getRangeAt(0); } else { diff --git a/packages/lexical-playground/src/plugins/KeywordsPlugin.ts b/packages/lexical-playground/src/plugins/KeywordsPlugin.ts index f17d5419c..d998acb58 100644 --- a/packages/lexical-playground/src/plugins/KeywordsPlugin.ts +++ b/packages/lexical-playground/src/plugins/KeywordsPlugin.ts @@ -17,7 +17,7 @@ import {$createKeywordNode, KeywordNode} from '../nodes/KeywordNode'; const KEYWORDS_REGEX = /(^|$|[^A-Za-zªµºÀ-ÖØ-öø-ˁˆ-ˑˠ-ˤˬˮͰ-ʹͶͷͺ-ͽΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԧԱ-Ֆՙա-ևא-תװ-ײؠ-يٮٯٱ-ۓەۥۦۮۯۺ-ۼۿܐܒ-ܯݍ-ޥޱߊ-ߪߴߵߺࠀ-ࠕࠚࠤࠨࡀ-ࡘࢠࢢ-ࢬऄ-हऽॐक़-ॡॱ-ॷॹ-ॿঅ-ঌএঐও-নপ-রলশ-হঽৎড়ঢ়য়-ৡৰৱਅ-ਊਏਐਓ-ਨਪ-ਰਲਲ਼ਵਸ਼ਸਹਖ਼-ੜਫ਼ੲ-ੴઅ-ઍએ-ઑઓ-નપ-રલળવ-હઽૐૠૡଅ-ଌଏଐଓ-ନପ-ରଲଳଵ-ହଽଡ଼ଢ଼ୟ-ୡୱஃஅ-ஊஎ-ஐஒ-கஙசஜஞடணதந-பம-ஹௐఅ-ఌఎ-ఐఒ-నప-ళవ-హఽౘౙౠౡಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹಽೞೠೡೱೲഅ-ഌഎ-ഐഒ-ഺഽൎൠൡൺ-ൿඅ-ඖක-නඳ-රලව-ෆก-ะาำเ-ๆກຂຄງຈຊຍດ-ທນ-ຟມ-ຣລວສຫອ-ະາຳຽເ-ໄໆໜ-ໟༀཀ-ཇཉ-ཬྈ-ྌက-ဪဿၐ-ၕၚ-ၝၡၥၦၮ-ၰၵ-ႁႎႠ-ჅჇჍა-ჺჼ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚᎀ-ᎏᎠ-Ᏼᐁ-ᙬᙯ-ᙿᚁ-ᚚᚠ-ᛪᜀ-ᜌᜎ-ᜑᜠ-ᜱᝀ-ᝑᝠ-ᝬᝮ-ᝰក-ឳៗៜᠠ-ᡷᢀ-ᢨᢪᢰ-ᣵᤀ-ᤜᥐ-ᥭᥰ-ᥴᦀ-ᦫᧁ-ᧇᨀ-ᨖᨠ-ᩔᪧᬅ-ᬳᭅ-ᭋᮃ-ᮠᮮᮯᮺ-ᯥᰀ-ᰣᱍ-ᱏᱚ-ᱽᳩ-ᳬᳮ-ᳱᳵᳶᴀ-ᶿḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼⁱⁿₐ-ₜℂℇℊ-ℓℕℙ-ℝℤΩℨK-ℭℯ-ℹℼ-ℿⅅ-ⅉⅎↃↄⰀ-Ⱞⰰ-ⱞⱠ-ⳤⳫ-ⳮⳲⳳⴀ-ⴥⴧⴭⴰ-ⵧⵯⶀ-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞⸯ々〆〱-〵〻〼ぁ-ゖゝ-ゟァ-ヺー-ヿㄅ-ㄭㄱ-ㆎㆠ-ㆺㇰ-ㇿ㐀-䶵一-鿌ꀀ-ꒌꓐ-ꓽꔀ-ꘌꘐ-ꘟꘪꘫꙀ-ꙮꙿ-ꚗꚠ-ꛥꜗ-ꜟꜢ-ꞈꞋ-ꞎꞐ-ꞓꞠ-Ɦꟸ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠢꡀ-ꡳꢂ-ꢳꣲ-ꣷꣻꤊ-ꤥꤰ-ꥆꥠ-ꥼꦄ-ꦲꧏꨀ-ꨨꩀ-ꩂꩄ-ꩋꩠ-ꩶꩺꪀ-ꪯꪱꪵꪶꪹ-ꪽꫀꫂꫛ-ꫝꫠ-ꫪꫲ-ꫴꬁ-ꬆꬉ-ꬎꬑ-ꬖꬠ-ꬦꬨ-ꬮꯀ-ꯢ가-힣ힰ-ퟆퟋ-ퟻ豈-舘並-龎ff-stﬓ-ﬗיִײַ-ﬨשׁ-זּטּ-לּמּנּסּףּפּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻﹰ-ﹴﹶ-ﻼA-Za-zヲ-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ])(congrats|congratulations|gratuluju|gratuluji|gratulujeme|blahopřeju|blahopřeji|blahopřejeme|Til lykke|Tillykke|Glückwunsch|Gratuliere|felicitaciones|enhorabuena|paljon onnea|onnittelut|Félicitations|gratula|gratulálok|gratulálunk|congratulazioni|complimenti|おめでとう|おめでとうございます|축하해|축하해요|gratulerer|Gefeliciteerd|gratulacje|Parabéns|parabéns|felicitações|felicitări|мои поздравления|поздравляем|поздравляю|gratulujem|blahoželám|ยินดีด้วย|ขอแสดงความยินดี|tebrikler|tebrik ederim|恭喜|祝贺你|恭喜你|恭喜|恭喜|baie geluk|veels geluk|অভিনন্দন|Čestitam|Čestitke|Čestitamo|Συγχαρητήρια|Μπράβο|અભિનંદન|badhai|बधाई|अभिनंदन|Честитам|Свака част|hongera|வாழ்த்துகள்|வாழ்த்துக்கள்|అభినందనలు|അഭിനന്ദനങ്ങൾ|Chúc mừng|מזל טוב|mazel tov|mazal tov)(^|$|[^A-Za-zªµºÀ-ÖØ-öø-ˁˆ-ˑˠ-ˤˬˮͰ-ʹͶͷͺ-ͽΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԧԱ-Ֆՙա-ևא-תװ-ײؠ-يٮٯٱ-ۓەۥۦۮۯۺ-ۼۿܐܒ-ܯݍ-ޥޱߊ-ߪߴߵߺࠀ-ࠕࠚࠤࠨࡀ-ࡘࢠࢢ-ࢬऄ-हऽॐक़-ॡॱ-ॷॹ-ॿঅ-ঌএঐও-নপ-রলশ-হঽৎড়ঢ়য়-ৡৰৱਅ-ਊਏਐਓ-ਨਪ-ਰਲਲ਼ਵਸ਼ਸਹਖ਼-ੜਫ਼ੲ-ੴઅ-ઍએ-ઑઓ-નપ-રલળવ-હઽૐૠૡଅ-ଌଏଐଓ-ନପ-ରଲଳଵ-ହଽଡ଼ଢ଼ୟ-ୡୱஃஅ-ஊஎ-ஐஒ-கஙசஜஞடணதந-பம-ஹௐఅ-ఌఎ-ఐఒ-నప-ళవ-హఽౘౙౠౡಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹಽೞೠೡೱೲഅ-ഌഎ-ഐഒ-ഺഽൎൠൡൺ-ൿඅ-ඖක-නඳ-රලව-ෆก-ะาำเ-ๆກຂຄງຈຊຍດ-ທນ-ຟມ-ຣລວສຫອ-ະາຳຽເ-ໄໆໜ-ໟༀཀ-ཇཉ-ཬྈ-ྌက-ဪဿၐ-ၕၚ-ၝၡၥၦၮ-ၰၵ-ႁႎႠ-ჅჇჍა-ჺჼ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚᎀ-ᎏᎠ-Ᏼᐁ-ᙬᙯ-ᙿᚁ-ᚚᚠ-ᛪᜀ-ᜌᜎ-ᜑᜠ-ᜱᝀ-ᝑᝠ-ᝬᝮ-ᝰក-ឳៗៜᠠ-ᡷᢀ-ᢨᢪᢰ-ᣵᤀ-ᤜᥐ-ᥭᥰ-ᥴᦀ-ᦫᧁ-ᧇᨀ-ᨖᨠ-ᩔᪧᬅ-ᬳᭅ-ᭋᮃ-ᮠᮮᮯᮺ-ᯥᰀ-ᰣᱍ-ᱏᱚ-ᱽᳩ-ᳬᳮ-ᳱᳵᳶᴀ-ᶿḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼⁱⁿₐ-ₜℂℇℊ-ℓℕℙ-ℝℤΩℨK-ℭℯ-ℹℼ-ℿⅅ-ⅉⅎↃↄⰀ-Ⱞⰰ-ⱞⱠ-ⳤⳫ-ⳮⳲⳳⴀ-ⴥⴧⴭⴰ-ⵧⵯⶀ-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞⸯ々〆〱-〵〻〼ぁ-ゖゝ-ゟァ-ヺー-ヿㄅ-ㄭㄱ-ㆎㆠ-ㆺㇰ-ㇿ㐀-䶵一-鿌ꀀ-ꒌꓐ-ꓽꔀ-ꘌꘐ-ꘟꘪꘫꙀ-ꙮꙿ-ꚗꚠ-ꛥꜗ-ꜟꜢ-ꞈꞋ-ꞎꞐ-ꞓꞠ-Ɦꟸ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠢꡀ-ꡳꢂ-ꢳꣲ-ꣷꣻꤊ-ꤥꤰ-ꥆꥠ-ꥼꦄ-ꦲꧏꨀ-ꨨꩀ-ꩂꩄ-ꩋꩠ-ꩶꩺꪀ-ꪯꪱꪵꪶꪹ-ꪽꫀꫂꫛ-ꫝꫠ-ꫪꫲ-ꫴꬁ-ꬆꬉ-ꬎꬑ-ꬖꬠ-ꬦꬨ-ꬮꯀ-ꯢ가-힣ힰ-ퟆퟋ-ퟻ豈-舘並-龎ff-stﬓ-ﬗיִײַ-ﬨשׁ-זּטּ-לּמּנּסּףּפּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻﹰ-ﹴﹶ-ﻼA-Za-zヲ-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ])/i; -export default function KeywordsPlugin(): JSX.Element { +export default function KeywordsPlugin(): JSX.Element | null { const [editor] = useLexicalComposerContext(); useEffect(() => { diff --git a/packages/lexical-playground/src/plugins/MaxLengthPlugin.tsx b/packages/lexical-playground/src/plugins/MaxLengthPlugin.tsx index cd6689ab3..d4d97032b 100644 --- a/packages/lexical-playground/src/plugins/MaxLengthPlugin.tsx +++ b/packages/lexical-playground/src/plugins/MaxLengthPlugin.tsx @@ -9,14 +9,14 @@ import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; import {trimTextContentFromAnchor} from '@lexical/selection'; import {$restoreEditorState} from '@lexical/utils'; -import {$getSelection, $isRangeSelection, RootNode} from 'lexical'; +import {$getSelection, $isRangeSelection, EditorState, RootNode} from 'lexical'; import {useEffect} from 'react'; export function MaxLengthPlugin({maxLength}: {maxLength: number}): null { const [editor] = useLexicalComposerContext(); useEffect(() => { - let lastRestoredEditorState = null; + let lastRestoredEditorState: EditorState | null = null; return editor.registerNodeTransform(RootNode, (rootNode: RootNode) => { const selection = $getSelection(); diff --git a/packages/lexical-playground/src/plugins/MentionsPlugin.tsx b/packages/lexical-playground/src/plugins/MentionsPlugin.tsx index a090b76f1..85858e0bc 100644 --- a/packages/lexical-playground/src/plugins/MentionsPlugin.tsx +++ b/packages/lexical-playground/src/plugins/MentionsPlugin.tsx @@ -21,7 +21,14 @@ import { KEY_ESCAPE_COMMAND, KEY_TAB_COMMAND, } from 'lexical'; -import {startTransition, useCallback, useEffect, useRef, useState} from 'react'; +import { + ReactPortal, + startTransition, + useCallback, + useEffect, + useRef, + useState, +} from 'react'; import * as React from 'react'; import {createPortal} from 'react-dom'; import useLayoutEffect from 'shared/useLayoutEffect'; @@ -532,7 +539,7 @@ const dummyLookupService = { }, }; -function useMentionLookupService(mentionString) { +function useMentionLookupService(mentionString: string) { const [results, setResults] = useState | null>(null); useEffect(() => { @@ -599,8 +606,8 @@ function MentionsTypeahead({ close: () => void; editor: LexicalEditor; resolution: Resolution; -}): JSX.Element { - const divRef = useRef(null); +}): JSX.Element | null { + const divRef = useRef(null); const match = resolution.match; const results = useMentionLookupService(match.matchingString); const [selectedIndex, setSelectedIndex] = useState(null); @@ -635,7 +642,7 @@ function MentionsTypeahead({ }, [close, match, editor, results, selectedIndex]); const updateSelectedIndex = useCallback( - (index) => { + (index: number) => { const rootElem = editor.getRootElement(); if (rootElem !== null) { rootElem.setAttribute( @@ -785,8 +792,8 @@ function MentionsTypeahead({ } function checkForCapitalizedNameMentions( - text, - minMatchLength, + text: string, + minMatchLength: number, ): MentionMatch | null { const match = CapitalizedNameMentionsRegex.exec(text); if (match !== null) { @@ -806,7 +813,10 @@ function checkForCapitalizedNameMentions( return null; } -function checkForAtSignMentions(text, minMatchLength): MentionMatch | null { +function checkForAtSignMentions( + text: string, + minMatchLength: number, +): MentionMatch | null { let match = AtSignMentionsRegex.exec(text); if (match === null) { @@ -829,7 +839,7 @@ function checkForAtSignMentions(text, minMatchLength): MentionMatch | null { return null; } -function getPossibleMentionMatch(text): MentionMatch | null { +function getPossibleMentionMatch(text: string): MentionMatch | null { const match = checkForAtSignMentions(text, 1); return match === null ? checkForCapitalizedNameMentions(text, 3) : match; } @@ -859,8 +869,10 @@ function tryToPositionRange(match: MentionMatch, range: Range): boolean { const startOffset = match.leadOffset; const endOffset = domSelection.anchorOffset; try { - range.setStart(anchorNode, startOffset); - range.setEnd(anchorNode, endOffset); + if (anchorNode) { + range.setStart(anchorNode, startOffset); + range.setEnd(anchorNode, endOffset); + } } catch (error) { return false; } @@ -976,7 +988,7 @@ function isSelectionOnEntityBoundary( }); } -function useMentions(editor: LexicalEditor): JSX.Element { +function useMentions(editor: LexicalEditor): ReactPortal | null { const [resolution, setResolution] = useState(null); useEffect(() => { @@ -987,7 +999,7 @@ function useMentions(editor: LexicalEditor): JSX.Element { useEffect(() => { let activeRange: Range | null = document.createRange(); - let previousText = null; + let previousText: string | null = null; const updateListener = () => { const range = activeRange; @@ -1044,7 +1056,7 @@ function useMentions(editor: LexicalEditor): JSX.Element { ); } -export default function MentionsPlugin(): JSX.Element { +export default function MentionsPlugin(): ReactPortal | null { const [editor] = useLexicalComposerContext(); return useMentions(editor); } diff --git a/packages/lexical-playground/src/plugins/PollPlugin.ts b/packages/lexical-playground/src/plugins/PollPlugin.ts index 741c6a229..27d736686 100644 --- a/packages/lexical-playground/src/plugins/PollPlugin.ts +++ b/packages/lexical-playground/src/plugins/PollPlugin.ts @@ -22,7 +22,7 @@ import {$createPollNode, PollNode} from '../nodes/PollNode'; export const INSERT_POLL_COMMAND: LexicalCommand = createCommand(); -export default function PollPlugin(): JSX.Element { +export default function PollPlugin(): JSX.Element | null { const [editor] = useLexicalComposerContext(); useEffect(() => { if (!editor.hasNodes([PollNode])) { diff --git a/packages/lexical-playground/src/plugins/StickyPlugin.ts b/packages/lexical-playground/src/plugins/StickyPlugin.ts index ad5651d15..4c065b23c 100644 --- a/packages/lexical-playground/src/plugins/StickyPlugin.ts +++ b/packages/lexical-playground/src/plugins/StickyPlugin.ts @@ -11,7 +11,7 @@ import {useEffect} from 'react'; import {StickyNode} from '../nodes/StickyNode'; -export default function StickyPlugin(): JSX.Element { +export default function StickyPlugin(): JSX.Element | null { const [editor] = useLexicalComposerContext(); useEffect(() => { if (!editor.hasNodes([StickyNode])) { diff --git a/packages/lexical-playground/src/plugins/TableActionMenuPlugin.tsx b/packages/lexical-playground/src/plugins/TableActionMenuPlugin.tsx index 1f493b66c..aaf074bb5 100644 --- a/packages/lexical-playground/src/plugins/TableActionMenuPlugin.tsx +++ b/packages/lexical-playground/src/plugins/TableActionMenuPlugin.tsx @@ -37,7 +37,7 @@ import {createPortal} from 'react-dom'; type TableCellActionMenuProps = Readonly<{ contextRef: {current: null | HTMLElement}; onClose: () => void; - setIsMenuOpen: (boolean) => void; + setIsMenuOpen: (isOpen: boolean) => void; tableCellNode: TableCellNode; }>; @@ -48,7 +48,7 @@ function TableActionMenu({ contextRef, }: TableCellActionMenuProps) { const [editor] = useLexicalComposerContext(); - const dropDownRef = useRef(); + const dropDownRef = useRef(null); const [tableCellNode, updateTableCellNode] = useState(_tableCellNode); const [selectionCounts, updateSelectionCounts] = useState({ columns: 1, @@ -103,12 +103,12 @@ function TableActionMenu({ }, [contextRef, dropDownRef]); useEffect(() => { - function handleClickOutside(event) { + function handleClickOutside(event: MouseEvent) { if ( dropDownRef.current != null && contextRef.current != null && - !dropDownRef.current.contains(event.target) && - !contextRef.current.contains(event.target) + !dropDownRef.current.contains(event.target as Node) && + !contextRef.current.contains(event.target as Node) ) { setIsMenuOpen(false); } @@ -132,7 +132,9 @@ function TableActionMenu({ } const tableSelection = getTableSelectionFromTableElement(tableElement); - tableSelection.clearHighlight(); + if (tableSelection !== null) { + tableSelection.clearHighlight(); + } tableNode.markDirty(); updateTableCellNode(tableCellNode.getLatest()); @@ -143,7 +145,7 @@ function TableActionMenu({ }, [editor, tableCellNode]); const insertTableRowAtSelection = useCallback( - (shouldInsertAfter) => { + (shouldInsertAfter: boolean) => { editor.update(() => { const selection = $getSelection(); @@ -179,7 +181,7 @@ function TableActionMenu({ ); const insertTableColumnAtSelection = useCallback( - (shouldInsertAfter) => { + (shouldInsertAfter: boolean) => { editor.update(() => { const selection = $getSelection(); @@ -427,6 +429,7 @@ function TableCellActionMenuContainer(): JSX.Element { if ( $isRangeSelection(selection) && rootElement !== null && + nativeSelection !== null && rootElement.contains(nativeSelection.anchorNode) ) { const tableCellNodeFromSelection = $getTableCellNodeFromLexicalNode( @@ -462,7 +465,7 @@ function TableCellActionMenuContainer(): JSX.Element { }); useEffect(() => { - const menuButtonDOM = menuButtonRef.current; + const menuButtonDOM = menuButtonRef.current as HTMLButtonElement | null; if (menuButtonDOM != null && tableCellNode != null) { const tableCellNodeDOM = editor.getElementByKey(tableCellNode.getKey()); diff --git a/packages/lexical-playground/src/plugins/TableCellResizer.tsx b/packages/lexical-playground/src/plugins/TableCellResizer.tsx index 2905cc5f7..6b2c56500 100644 --- a/packages/lexical-playground/src/plugins/TableCellResizer.tsx +++ b/packages/lexical-playground/src/plugins/TableCellResizer.tsx @@ -28,6 +28,7 @@ import { } from 'lexical'; import * as React from 'react'; import { + MouseEventHandler, ReactPortal, useCallback, useEffect, @@ -224,57 +225,58 @@ function TableCellResizer({editor}: {editor: LexicalEditor}): JSX.Element { ); const toggleResize = useCallback( - (direction) => (event) => { - event.preventDefault(); - event.stopPropagation(); - - if (!activeCell) { - throw new Error('TableCellResizer: Expected active cell.'); - } - - if (draggingDirection === direction && mouseStartPosRef.current) { - const {x, y} = mouseStartPosRef.current; + (direction: MouseDraggingDirection): MouseEventHandler => + (event) => { + event.preventDefault(); + event.stopPropagation(); if (!activeCell) { - return; + throw new Error('TableCellResizer: Expected active cell.'); } - const {height, width} = activeCell.elem.getBoundingClientRect(); + if (draggingDirection === direction && mouseStartPosRef.current) { + const {x, y} = mouseStartPosRef.current; - if (isHeightChanging(direction)) { - const heightChange = Math.abs(event.clientY - y); + if (activeCell === null) { + return; + } - const isShrinking = direction === 'bottom' && y > event.clientY; + const {height, width} = activeCell.elem.getBoundingClientRect(); - updateRowHeight( - Math.max( - isShrinking ? height - heightChange : heightChange + height, - MIN_ROW_HEIGHT, - ), - ); + if (isHeightChanging(direction)) { + const heightChange = Math.abs(event.clientY - y); + + const isShrinking = direction === 'bottom' && y > event.clientY; + + updateRowHeight( + Math.max( + isShrinking ? height - heightChange : heightChange + height, + MIN_ROW_HEIGHT, + ), + ); + } else { + const widthChange = Math.abs(event.clientX - x); + + const isShrinking = direction === 'right' && x > event.clientX; + + updateColumnWidth( + Math.max( + isShrinking ? width - widthChange : widthChange + width, + MIN_COLUMN_WIDTH, + ), + ); + } + + resetState(); } else { - const widthChange = Math.abs(event.clientX - x); - - const isShrinking = direction === 'right' && x > event.clientX; - - updateColumnWidth( - Math.max( - isShrinking ? width - widthChange : widthChange + width, - MIN_COLUMN_WIDTH, - ), - ); + mouseStartPosRef.current = { + x: event.clientX, + y: event.clientY, + }; + updateMouseCurrentPos(mouseStartPosRef.current); + updateDraggingDirection(direction); } - - resetState(); - } else { - mouseStartPosRef.current = { - x: event.clientX, - y: event.clientY, - }; - updateMouseCurrentPos(mouseStartPosRef.current); - updateDraggingDirection(direction); - } - }, + }, [ activeCell, draggingDirection, @@ -353,13 +355,13 @@ function TableCellResizer({editor}: {editor: LexicalEditor}): JSX.Element { <>
diff --git a/packages/lexical-playground/src/plugins/TestRecorderPlugin.tsx b/packages/lexical-playground/src/plugins/TestRecorderPlugin.tsx index 6fdeaad85..7c6785c6a 100644 --- a/packages/lexical-playground/src/plugins/TestRecorderPlugin.tsx +++ b/packages/lexical-playground/src/plugins/TestRecorderPlugin.tsx @@ -6,7 +6,12 @@ * */ -import type {LexicalEditor} from 'lexical'; +import type { + GridSelection, + LexicalEditor, + NodeSelection, + RangeSelection, +} from 'lexical'; import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; import {$createParagraphNode, $createTextNode, $getRoot} from 'lexical'; @@ -46,8 +51,8 @@ const download = (filename: string, text: string | null) => { document.body?.removeChild(a); }; -const formatStep = (step) => { - const formatOneStep = (name, value) => { +const formatStep = (step: Step) => { + const formatOneStep = (name: string, value: Step['value']) => { switch (name) { case 'click': { return ` await page.mouse.click(${value.x}, ${value.y});`; @@ -99,7 +104,7 @@ export function isSelectAll(event: KeyboardEvent): boolean { } // stolen from LexicalSelection-test -function sanitizeSelection(selection) { +function sanitizeSelection(selection: Selection) { const {anchorNode, focusNode} = selection; let {anchorOffset, focusOffset} = selection; if (anchorOffset !== 0) { @@ -111,15 +116,17 @@ function sanitizeSelection(selection) { return {anchorNode, anchorOffset, focusNode, focusOffset}; } -function getPathFromNodeToEditor(node: Node, rootElement) { - let currentNode = node; +function getPathFromNodeToEditor(node: Node, rootElement: HTMLElement | null) { + let currentNode: Node | null | undefined = node; const path = []; while (currentNode !== rootElement) { - path.unshift( - Array.from(currentNode?.parentNode?.childNodes ?? []).indexOf( - currentNode as ChildNode, - ), - ); + if (currentNode !== null && currentNode !== undefined) { + path.unshift( + Array.from(currentNode?.parentNode?.childNodes ?? []).indexOf( + currentNode as ChildNode, + ), + ); + } currentNode = currentNode?.parentNode; } return path; @@ -137,21 +144,26 @@ const keyPresses = new Set([ ]); type Step = { - value: number; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + value: any; count: number; name: string; }; type Steps = Step[]; -function useTestRecorder(editor: LexicalEditor): [JSX.Element, JSX.Element] { +function useTestRecorder( + editor: LexicalEditor, +): [JSX.Element, JSX.Element | null] { const [steps, setSteps] = useState([]); const [isRecording, setIsRecording] = useState(false); const [, setCurrentInnerHTML] = useState(''); const [templatedTest, setTemplatedTest] = useState(''); - const previousSelectionRef = useRef(null); + const previousSelectionRef = useRef< + RangeSelection | GridSelection | NodeSelection | null + >(null); const skipNextSelectionChangeRef = useRef(false); - const preRef = useRef(null); + const preRef = useRef(null); const getCurrentEditor = useCallback(() => { return editor; @@ -188,6 +200,8 @@ import { repeat, } from '../utils'; import {selectAll} from '../keyboardShortcuts'; +import { RangeSelection } from 'lexical'; +import { NodeSelection } from 'lexical'; describe('Test case', () => { initializeE2E((e2e) => { @@ -204,7 +218,7 @@ ${steps.map(formatStep).join(`\n`)} // just a wrapper around inserting new actions so that we can // coalesce some actions like insertText/moveNativeSelection const pushStep = useCallback( - (name, value) => { + (name: string, value: Step['value']) => { setSteps((currentSteps) => { // trying to group steps const currentIndex = steps.length - 1; @@ -287,7 +301,10 @@ ${steps.map(formatStep).join(`\n`)} useEffect(() => { if (steps) { - setTemplatedTest(generateTestContent()); + const testContent = generateTestContent(); + if (testContent !== null) { + setTemplatedTest(testContent); + } if (preRef.current) { preRef.current.scrollTo(0, preRef.current.scrollHeight); } @@ -311,8 +328,9 @@ ${steps.map(formatStep).join(`\n`)} ) { const browserSelection = window.getSelection(); if ( - browserSelection.anchorNode == null || - browserSelection.focusNode == null + browserSelection && + (browserSelection.anchorNode == null || + browserSelection.focusNode == null) ) { return; } @@ -320,7 +338,10 @@ ${steps.map(formatStep).join(`\n`)} previousSelectionRef.current = currentSelection; } skipNextSelectionChangeRef.current = false; - setTemplatedTest(generateTestContent()); + const testContent = generateTestContent(); + if (testContent !== null) { + setTemplatedTest(testContent); + } }, ); return removeUpdateListener; @@ -342,7 +363,7 @@ ${steps.map(formatStep).join(`\n`)} // clear editor and start recording const toggleEditorSelection = useCallback( - (currentEditor) => { + (currentEditor: LexicalEditor) => { if (!isRecording) { currentEditor.update(() => { const root = $getRoot(); @@ -364,6 +385,7 @@ ${steps.map(formatStep).join(`\n`)} } const browserSelection = window.getSelection(); if ( + browserSelection === null || browserSelection.anchorNode == null || browserSelection.focusNode == null ) { @@ -371,14 +393,15 @@ ${steps.map(formatStep).join(`\n`)} } const {anchorNode, anchorOffset, focusNode, focusOffset} = sanitizeSelection(browserSelection); - const anchorPath = getPathFromNodeToEditor( - anchorNode, - getCurrentEditor().getRootElement(), - ); - const focusPath = getPathFromNodeToEditor( - focusNode, - getCurrentEditor().getRootElement(), - ); + const rootElement = getCurrentEditor().getRootElement(); + let anchorPath; + if (anchorNode !== null) { + anchorPath = getPathFromNodeToEditor(anchorNode, rootElement); + } + let focusPath; + if (focusNode !== null) { + focusPath = getPathFromNodeToEditor(focusNode, rootElement); + } pushStep('snapshot', { anchorNode, anchorOffset, diff --git a/packages/lexical-playground/src/plugins/ToolbarPlugin.tsx b/packages/lexical-playground/src/plugins/ToolbarPlugin.tsx index 80889cefb..21c4865a2 100644 --- a/packages/lexical-playground/src/plugins/ToolbarPlugin.tsx +++ b/packages/lexical-playground/src/plugins/ToolbarPlugin.tsx @@ -7,7 +7,12 @@ */ import type {InsertImagePayload} from './ImagesPlugin'; -import type {LexicalEditor, RangeSelection} from 'lexical'; +import type { + GridSelection, + LexicalEditor, + NodeSelection, + RangeSelection, +} from 'lexical'; import './ToolbarPlugin.css'; @@ -28,6 +33,7 @@ import { $createHeadingNode, $createQuoteNode, $isHeadingNode, + HeadingTagType, } from '@lexical/rich-text'; import { $getSelectionStyleValueForProperty, @@ -58,6 +64,7 @@ import { FORMAT_ELEMENT_COMMAND, FORMAT_TEXT_COMMAND, INDENT_CONTENT_COMMAND, + NodeKey, OUTDENT_CONTENT_COMMAND, REDO_COMMAND, SELECTION_CHANGE_COMMAND, @@ -65,7 +72,7 @@ import { UNDO_COMMAND, } from 'lexical'; import * as React from 'react'; -import {useCallback, useEffect, useRef, useState} from 'react'; +import {ChangeEvent, useCallback, useEffect, useRef, useState} from 'react'; import {createPortal} from 'react-dom'; import {IS_APPLE} from 'shared/environment'; @@ -87,21 +94,6 @@ import {INSERT_POLL_COMMAND} from './PollPlugin'; import {INSERT_TWEET_COMMAND} from './TwitterPlugin'; import {INSERT_YOUTUBE_COMMAND} from './YouTubePlugin'; -const supportedBlockTypes = new Set([ - 'paragraph', - 'quote', - 'code', - 'h1', - 'h2', - 'h3', - 'h4', - 'h5', - 'h6', - 'bullet', - 'number', - 'check', -]); - const blockTypeToBlockName = { bullet: 'Bulleted List', check: 'Check List', @@ -160,7 +152,7 @@ function getSelectedNode(selection: RangeSelection): TextNode | ElementNode { function positionEditorElement( editor: HTMLElement, - rect: ClientRect, + rect: ClientRect | null, rootElement: HTMLElement, ): void { if (rect === null) { @@ -184,10 +176,12 @@ function positionEditorElement( function FloatingLinkEditor({editor}: {editor: LexicalEditor}): JSX.Element { const editorRef = useRef(null); - const inputRef = useRef(null); + const inputRef = useRef(null); const [linkUrl, setLinkUrl] = useState(''); const [isEditMode, setEditMode] = useState(false); - const [lastSelection, setLastSelection] = useState(null); + const [lastSelection, setLastSelection] = useState< + RangeSelection | GridSelection | NodeSelection | null + >(null); const updateLinkEditor = useCallback(() => { const selection = $getSelection(); @@ -213,6 +207,7 @@ function FloatingLinkEditor({editor}: {editor: LexicalEditor}): JSX.Element { const rootElement = editor.getRootElement(); if ( selection !== null && + nativeSelection !== null && !nativeSelection.isCollapsed && rootElement !== null && rootElement.contains(nativeSelection.anchorNode) @@ -232,7 +227,9 @@ function FloatingLinkEditor({editor}: {editor: LexicalEditor}): JSX.Element { positionEditorElement(editorElem, rect, rootElement); setLastSelection(selection); } else if (!activeElement || activeElement.className !== 'link-input') { - positionEditorElement(editorElem, null, rootElement); + if (rootElement !== null) { + positionEditorElement(editorElem, null, rootElement); + } setLastSelection(null); setEditMode(false); setLinkUrl(''); @@ -381,7 +378,7 @@ function InsertImageUploadedDialogBody({ const isDisabled = src === ''; - const loadImage = (files: FileList) => { + const loadImage = (files: FileList | null) => { const reader = new FileReader(); reader.onload = function () { if (typeof reader.result === 'string') { @@ -389,7 +386,9 @@ function InsertImageUploadedDialogBody({ } return ''; }; - reader.readAsDataURL(files[0]); + if (files !== null) { + reader.readAsDataURL(files[0]); + } }; return ( @@ -632,7 +631,7 @@ function BlockFormatDropDown({ editor, blockType, }: { - blockType: string; + blockType: keyof typeof blockTypeToBlockName; editor: LexicalEditor; }): JSX.Element { const formatParagraph = () => { @@ -647,7 +646,7 @@ function BlockFormatDropDown({ } }; - const formatHeading = (headingSize) => { + const formatHeading = (headingSize: HeadingTagType) => { if (blockType !== headingSize) { editor.update(() => { const selection = $getSelection(); @@ -791,7 +790,7 @@ function Select({ value, }: { className: string; - onChange: (event: {target: {value: string}}) => void; + onChange: (e: ChangeEvent) => void; options: [string, string][]; value: string; }): JSX.Element { @@ -809,8 +808,11 @@ function Select({ export default function ToolbarPlugin(): JSX.Element { const [editor] = useLexicalComposerContext(); const [activeEditor, setActiveEditor] = useState(editor); - const [blockType, setBlockType] = useState('paragraph'); - const [selectedElementKey, setSelectedElementKey] = useState(null); + const [blockType, setBlockType] = + useState('paragraph'); + const [selectedElementKey, setSelectedElementKey] = useState( + null, + ); const [fontSize, setFontSize] = useState('15px'); const [fontColor, setFontColor] = useState('#000'); const [bgColor, setBgColor] = useState('#fff'); @@ -874,9 +876,12 @@ export default function ToolbarPlugin(): JSX.Element { const type = $isHeadingNode(element) ? element.getTag() : element.getType(); - setBlockType(type); + if (type in blockTypeToBlockName) { + setBlockType(type as keyof typeof blockTypeToBlockName); + } if ($isCodeNode(element)) { - const language = element.getLanguage(); + const language = + element.getLanguage() as keyof typeof CODE_LANGUAGE_MAP; setCodeLanguage( language ? CODE_LANGUAGE_MAP[language] || language : '', ); @@ -974,8 +979,8 @@ export default function ToolbarPlugin(): JSX.Element { }, [activeEditor]); const onFontSizeSelect = useCallback( - (e) => { - applyStyleText({'font-size': e.target.value}); + (e: ChangeEvent) => { + applyStyleText({'font-size': (e.target as HTMLSelectElement).value}); }, [applyStyleText], ); @@ -995,8 +1000,8 @@ export default function ToolbarPlugin(): JSX.Element { ); const onFontFamilySelect = useCallback( - (e) => { - applyStyleText({'font-family': e.target.value}); + (e: ChangeEvent) => { + applyStyleText({'font-family': (e.target as HTMLSelectElement).value}); }, [applyStyleText], ); @@ -1010,12 +1015,12 @@ export default function ToolbarPlugin(): JSX.Element { }, [editor, isLink]); const onCodeLanguageSelect = useCallback( - (e) => { + (e: ChangeEvent) => { activeEditor.update(() => { if (selectedElementKey !== null) { const node = $getNodeByKey(selectedElementKey); if ($isCodeNode(node)) { - node.setLanguage(e.target.value); + node.setLanguage((e.target as HTMLSelectElement).value); } } }); @@ -1049,7 +1054,7 @@ export default function ToolbarPlugin(): JSX.Element { - {supportedBlockTypes.has(blockType) && activeEditor === editor && ( + {blockType in blockTypeToBlockName && activeEditor === editor && ( <> diff --git a/packages/lexical-playground/src/plugins/TwitterPlugin.ts b/packages/lexical-playground/src/plugins/TwitterPlugin.ts index 5a433e6b4..181c2633c 100644 --- a/packages/lexical-playground/src/plugins/TwitterPlugin.ts +++ b/packages/lexical-playground/src/plugins/TwitterPlugin.ts @@ -24,7 +24,7 @@ import {$createTweetNode, TweetNode} from '../nodes/TweetNode'; export const INSERT_TWEET_COMMAND: LexicalCommand = createCommand(); -export default function TwitterPlugin(): JSX.Element { +export default function TwitterPlugin(): JSX.Element | null { const [editor] = useLexicalComposerContext(); useEffect(() => { diff --git a/packages/lexical-playground/src/plugins/TypingPerfPlugin.ts b/packages/lexical-playground/src/plugins/TypingPerfPlugin.ts index a0a1d4a6a..037b95091 100644 --- a/packages/lexical-playground/src/plugins/TypingPerfPlugin.ts +++ b/packages/lexical-playground/src/plugins/TypingPerfPlugin.ts @@ -29,13 +29,13 @@ const validInputTypes = new Set([ 'deleteSoftLineForward', ]); -export default function TypingPerfPlugin(): JSX.Element { +export default function TypingPerfPlugin(): JSX.Element | null { const report = useReport(); useEffect(() => { let start = 0; - let timerId = null; - let keyPressTimerId = null; - let log = []; + let timerId: NodeJS.Timeout | null; + let keyPressTimerId: number | null; + let log: Array = []; let invalidatingEvent = false; const measureEventEnd = function logKeyPress() { @@ -73,7 +73,7 @@ export default function TypingPerfPlugin(): JSX.Element { start = performance.now(); }; - const beforeInputHandler = function beforeInputHandler(event) { + const beforeInputHandler = function beforeInputHandler(event: InputEvent) { if (!validInputTypes.has(event.inputType) || invalidatingEvent) { invalidatingEvent = false; return; @@ -82,7 +82,7 @@ export default function TypingPerfPlugin(): JSX.Element { measureEventStart(); }; - const keyDownHandler = function keyDownHandler(event) { + const keyDownHandler = function keyDownHandler(event: KeyboardEvent) { const keyCode = event.keyCode; if (keyCode === 8 || keyCode === 13) { diff --git a/packages/lexical-playground/src/plugins/YouTubePlugin.ts b/packages/lexical-playground/src/plugins/YouTubePlugin.ts index 91a857582..b52238044 100644 --- a/packages/lexical-playground/src/plugins/YouTubePlugin.ts +++ b/packages/lexical-playground/src/plugins/YouTubePlugin.ts @@ -24,7 +24,7 @@ import {$createYouTubeNode, YouTubeNode} from '../nodes/YouTubeNode'; export const INSERT_YOUTUBE_COMMAND: LexicalCommand = createCommand(); -export default function YouTubePlugin(): JSX.Element { +export default function YouTubePlugin(): JSX.Element | null { const [editor] = useLexicalComposerContext(); useEffect(() => { diff --git a/packages/lexical-playground/src/setupEnv.ts b/packages/lexical-playground/src/setupEnv.ts index e34b22c35..83e4f93bd 100644 --- a/packages/lexical-playground/src/setupEnv.ts +++ b/packages/lexical-playground/src/setupEnv.ts @@ -6,7 +6,7 @@ * */ -import {DEFAULT_SETTINGS} from './appSettings'; +import {DEFAULT_SETTINGS, Settings} from './appSettings'; // override default options with query parameters if any const urlSearchParams = new URLSearchParams(window.location.search); @@ -15,7 +15,7 @@ for (const param of Object.keys(DEFAULT_SETTINGS)) { if (urlSearchParams.has(param)) { try { const value = JSON.parse(urlSearchParams.get(param) ?? 'true'); - DEFAULT_SETTINGS[param] = Boolean(value); + DEFAULT_SETTINGS[param as keyof Settings] = Boolean(value); } catch (error) { console.warn(`Unable to parse query parameter "${param}"`); } @@ -23,5 +23,6 @@ for (const param of Object.keys(DEFAULT_SETTINGS)) { } if (DEFAULT_SETTINGS.disableBeforeInput) { + // @ts-expect-error delete window.InputEvent.prototype.getTargetRanges; } diff --git a/packages/lexical-playground/src/ui/Button.tsx b/packages/lexical-playground/src/ui/Button.tsx index dfba30f20..d6a295ce6 100644 --- a/packages/lexical-playground/src/ui/Button.tsx +++ b/packages/lexical-playground/src/ui/Button.tsx @@ -9,6 +9,7 @@ import './Button.css'; import * as React from 'react'; +import {ReactNode} from 'react'; import joinClasses from '../utils/join-classes'; @@ -22,7 +23,7 @@ export default function Button({ title, }: { 'data-test-id'?: string; - children: JSX.Element | string | (JSX.Element | string)[]; + children: ReactNode; className?: string; disabled?: boolean; onClick: () => void; diff --git a/packages/lexical-playground/src/ui/ColorPicker.tsx b/packages/lexical-playground/src/ui/ColorPicker.tsx index 5f9095bea..79a65c01a 100644 --- a/packages/lexical-playground/src/ui/ColorPicker.tsx +++ b/packages/lexical-playground/src/ui/ColorPicker.tsx @@ -8,7 +8,7 @@ import './ColorPicker.css'; -import {useEffect, useMemo, useRef, useState} from 'react'; +import {ReactNode, useEffect, useMemo, useRef, useState} from 'react'; import * as React from 'react'; import DropDown from './DropDown'; @@ -19,8 +19,8 @@ interface ColorPickerProps { buttonClassName: string; buttonIconClassName?: string; buttonLabel?: string; - color?: string; - children?: JSX.Element; + color: string; + children?: ReactNode; onChange?: (color: string) => void; title?: string; } @@ -100,7 +100,7 @@ export default function ColorPicker({ useEffect(() => { // Check if the dropdown is actually active - if (innerDivRef.current !== null) { + if (innerDivRef.current !== null && onChange) { onChange(selfColor.hex); setInputColor(selfColor.hex); } @@ -269,14 +269,15 @@ export function toHex(value: string): string { } function hex2rgb(hex: string): RGB { - const rbgArr = hex - .replace( - /^#?([a-f\d])([a-f\d])([a-f\d])$/i, - (m, r, g, b) => '#' + r + r + g + g + b + b, - ) - .substring(1) - .match(/.{2}/g) - .map((x) => parseInt(x, 16)); + const rbgArr = ( + hex + .replace( + /^#?([a-f\d])([a-f\d])([a-f\d])$/i, + (m, r, g, b) => '#' + r + r + g + g + b + b, + ) + .substring(1) + .match(/.{2}/g) || [] + ).map((x) => parseInt(x, 16)); return { b: rbgArr[2], diff --git a/packages/lexical-playground/src/ui/DropDown.tsx b/packages/lexical-playground/src/ui/DropDown.tsx index daf1385a3..83f328c80 100644 --- a/packages/lexical-playground/src/ui/DropDown.tsx +++ b/packages/lexical-playground/src/ui/DropDown.tsx @@ -6,7 +6,14 @@ * */ -import {useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import { + ReactNode, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; import * as React from 'react'; import {createPortal} from 'react-dom'; @@ -133,7 +140,7 @@ export default function DropDown({ buttonClassName: string; buttonIconClassName?: string; buttonLabel?: string; - children: JSX.Element | string | (JSX.Element | string)[]; + children: ReactNode; stopCloseOnClickSelf?: boolean; }): JSX.Element { const dropDownRef = useRef(null); diff --git a/packages/lexical-playground/src/ui/EquationEditor.tsx b/packages/lexical-playground/src/ui/EquationEditor.tsx index 265c67427..31ca9c5c6 100644 --- a/packages/lexical-playground/src/ui/EquationEditor.tsx +++ b/packages/lexical-playground/src/ui/EquationEditor.tsx @@ -15,7 +15,7 @@ type BaseEquationEditorProps = { equation: string; inline: boolean; inputRef: {current: null | HTMLInputElement | HTMLTextAreaElement}; - setEquation: (string) => void; + setEquation: (equation: string) => void; }; export default function EquationEditor({ @@ -24,8 +24,8 @@ export default function EquationEditor({ inline, inputRef, }: BaseEquationEditorProps): JSX.Element { - const onChange = (event) => { - setEquation(event.target.value); + const onChange = (event: ChangeEvent) => { + setEquation((event.target as HTMLInputElement).value); }; const props = { diff --git a/packages/lexical-playground/src/ui/FileInput.tsx b/packages/lexical-playground/src/ui/FileInput.tsx index d8f2398c6..465330bb4 100644 --- a/packages/lexical-playground/src/ui/FileInput.tsx +++ b/packages/lexical-playground/src/ui/FileInput.tsx @@ -14,7 +14,7 @@ type Props = Readonly<{ 'data-test-id'?: string; accept?: string; label: string; - onChange: (files: FileList) => void; + onChange: (files: FileList | null) => void; }>; export default function FileInput({ diff --git a/packages/lexical-playground/src/ui/ImageResizer.tsx b/packages/lexical-playground/src/ui/ImageResizer.tsx index 59461b169..155d6bdc9 100644 --- a/packages/lexical-playground/src/ui/ImageResizer.tsx +++ b/packages/lexical-playground/src/ui/ImageResizer.tsx @@ -36,7 +36,7 @@ export default function ImageResizer({ maxWidth?: number; onResizeEnd: (width: 'inherit' | number, height: 'inherit' | number) => void; onResizeStart: () => void; - setShowCaption: (boolean) => void; + setShowCaption: (show: boolean) => void; showCaption: boolean; }): JSX.Element { const buttonRef = useRef(null); diff --git a/packages/lexical-playground/src/ui/KatexEquationAlterer.tsx b/packages/lexical-playground/src/ui/KatexEquationAlterer.tsx index 206b83151..9ed19c754 100644 --- a/packages/lexical-playground/src/ui/KatexEquationAlterer.tsx +++ b/packages/lexical-playground/src/ui/KatexEquationAlterer.tsx @@ -16,7 +16,7 @@ import KatexRenderer from './KatexRenderer'; type Props = { initialEquation?: string; - onConfirm: (string, boolean) => void; + onConfirm: (equation: string, inline: boolean) => void; }; export default function KatexEquationAlterer({ diff --git a/packages/lexical-playground/src/ui/LinkPreview.tsx b/packages/lexical-playground/src/ui/LinkPreview.tsx index 04b3a06aa..a6de4a4c8 100644 --- a/packages/lexical-playground/src/ui/LinkPreview.tsx +++ b/packages/lexical-playground/src/ui/LinkPreview.tsx @@ -9,10 +9,17 @@ import './LinkPreview.css'; import * as React from 'react'; -import {Suspense} from 'react'; +import {CSSProperties, Suspense} from 'react'; + +type Preview = { + title: string; + description: string; + img: string; + domain: string; +} | null; // Cached responses or running request promises -const PREVIEW_CACHE = {}; +const PREVIEW_CACHE: Record | {preview: Preview}> = {}; const URL_MATCHER = /((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/; @@ -49,7 +56,7 @@ function LinkPreviewContent({ url, }: Readonly<{ url: string; -}>): JSX.Element { +}>): JSX.Element | null { const {preview} = useSuspenseRequest(url); if (preview === null) { return null; @@ -78,12 +85,15 @@ function LinkPreviewContent({ ); } -function Glimmer(props): JSX.Element { +function Glimmer(props: {style: CSSProperties; index: number}): JSX.Element { return (
); } diff --git a/packages/lexical-playground/src/ui/Modal.tsx b/packages/lexical-playground/src/ui/Modal.tsx index 3b16e63b1..86887e21c 100644 --- a/packages/lexical-playground/src/ui/Modal.tsx +++ b/packages/lexical-playground/src/ui/Modal.tsx @@ -9,7 +9,7 @@ import './Modal.css'; import * as React from 'react'; -import {useEffect, useRef} from 'react'; +import {ReactNode, useEffect, useRef} from 'react'; import {createPortal} from 'react-dom'; function PortalImpl({ @@ -18,12 +18,12 @@ function PortalImpl({ title, closeOnClickOutside, }: { - children: JSX.Element | string | (JSX.Element | string)[]; + children: ReactNode; closeOnClickOutside: boolean; onClose: () => void; title: string; }) { - const modalRef = useRef(); + const modalRef = useRef(null); useEffect(() => { if (modalRef.current !== null) { @@ -32,8 +32,8 @@ function PortalImpl({ }, []); useEffect(() => { - let modalOverlayElement = null; - const handler = (event) => { + let modalOverlayElement: HTMLElement | null = null; + const handler = (event: KeyboardEvent) => { if (event.keyCode === 27) { onClose(); } @@ -88,7 +88,7 @@ export default function Modal({ title, closeOnClickOutside = false, }: { - children: JSX.Element | string | (JSX.Element | string)[]; + children: ReactNode; closeOnClickOutside?: boolean; onClose: () => void; title: string; diff --git a/packages/lexical-playground/src/ui/Placeholder.tsx b/packages/lexical-playground/src/ui/Placeholder.tsx index 858167a0a..676776886 100644 --- a/packages/lexical-playground/src/ui/Placeholder.tsx +++ b/packages/lexical-playground/src/ui/Placeholder.tsx @@ -9,12 +9,13 @@ import './Placeholder.css'; import * as React from 'react'; +import {ReactNode} from 'react'; export default function Placeholder({ children, className, }: { - children: JSX.Element | string | (JSX.Element | string)[]; + children: ReactNode; className?: string; }): JSX.Element { return
{children}
; diff --git a/packages/lexical-playground/src/utils/join-classes.ts b/packages/lexical-playground/src/utils/join-classes.ts index f0cd4e8c2..40a2dd5fb 100644 --- a/packages/lexical-playground/src/utils/join-classes.ts +++ b/packages/lexical-playground/src/utils/join-classes.ts @@ -6,6 +6,8 @@ * */ -export default function joinClasses(...args) { +export default function joinClasses( + ...args: Array +) { return args.filter(Boolean).join(' '); } diff --git a/packages/lexical-playground/src/utils/swipe.ts b/packages/lexical-playground/src/utils/swipe.ts index af7dc72db..9edc93acb 100644 --- a/packages/lexical-playground/src/utils/swipe.ts +++ b/packages/lexical-playground/src/utils/swipe.ts @@ -17,7 +17,7 @@ type ElementValues = { const elements = new WeakMap(); -function readTouch(e: TouchEvent): [number, number] { +function readTouch(e: TouchEvent): [number, number] | null { const touch = e.changedTouches[0]; if (touch === undefined) { return null; @@ -30,16 +30,23 @@ function addListener(element: HTMLElement, cb: Listener): () => void { if (elementValues === undefined) { const listeners = new Set(); const handleTouchstart = (e: TouchEvent) => { - elementValues.start = readTouch(e); + if (elementValues !== undefined) { + elementValues.start = readTouch(e); + } }; const handleTouchend = (e: TouchEvent) => { + if (elementValues === undefined) { + return; + } const start = elementValues.start; if (start === null) { return; } const end = readTouch(e); for (const listener of listeners) { - listener([end[0] - start[0], end[1] - start[1]], e); + if (end !== null) { + listener([end[0] - start[0], end[1] - start[1]], e); + } } }; element.addEventListener('touchstart', handleTouchstart); @@ -59,6 +66,9 @@ function addListener(element: HTMLElement, cb: Listener): () => void { function deleteListener(element: HTMLElement, cb: Listener): void { const elementValues = elements.get(element); + if (elementValues === undefined) { + return; + } const listeners = elementValues.listeners; listeners.delete(cb); if (listeners.size === 0) { @@ -68,7 +78,10 @@ function deleteListener(element: HTMLElement, cb: Listener): void { } } -export function addSwipeLeftListener(element, cb) { +export function addSwipeLeftListener( + element: HTMLElement, + cb: (_force: number, e: TouchEvent) => void, +) { return addListener(element, (force, e) => { const [x, y] = force; if (x < 0 && -x > Math.abs(y)) { @@ -77,7 +90,10 @@ export function addSwipeLeftListener(element, cb) { }); } -export function addSwipeRightListener(element, cb) { +export function addSwipeRightListener( + element: HTMLElement, + cb: (_force: number, e: TouchEvent) => void, +) { return addListener(element, (force, e) => { const [x, y] = force; if (x > 0 && x > Math.abs(y)) { @@ -86,7 +102,10 @@ export function addSwipeRightListener(element, cb) { }); } -export function addSwipeUpListener(element, cb) { +export function addSwipeUpListener( + element: HTMLElement, + cb: (_force: number, e: TouchEvent) => void, +) { return addListener(element, (force, e) => { const [x, y] = force; if (y < 0 && -y > Math.abs(x)) { @@ -95,7 +114,10 @@ export function addSwipeUpListener(element, cb) { }); } -export function addSwipeDownListener(element, cb) { +export function addSwipeDownListener( + element: HTMLElement, + cb: (_force: number, e: TouchEvent) => void, +) { return addListener(element, (force, e) => { const [x, y] = force; if (y > 0 && y > Math.abs(x)) { diff --git a/packages/lexical-react/src/LexicalBlockWithAlignableContents.tsx b/packages/lexical-react/src/LexicalBlockWithAlignableContents.tsx index f305478ed..32781d994 100644 --- a/packages/lexical-react/src/LexicalBlockWithAlignableContents.tsx +++ b/packages/lexical-react/src/LexicalBlockWithAlignableContents.tsx @@ -28,10 +28,10 @@ import { KEY_DELETE_COMMAND, } from 'lexical'; import * as React from 'react'; -import {useCallback, useEffect, useRef} from 'react'; +import {ReactNode, useCallback, useEffect, useRef} from 'react'; type Props = Readonly<{ - children: JSX.Element | string | (JSX.Element | string)[]; + children: ReactNode; format: ElementFormatType | null | undefined; nodeKey: NodeKey; className: Readonly<{ diff --git a/packages/lexical-react/src/LexicalNestedComposer.tsx b/packages/lexical-react/src/LexicalNestedComposer.tsx index 2e8a9eb4e..5d44773c8 100644 --- a/packages/lexical-react/src/LexicalNestedComposer.tsx +++ b/packages/lexical-react/src/LexicalNestedComposer.tsx @@ -15,7 +15,7 @@ import { LexicalComposerContext, } from '@lexical/react/LexicalComposerContext'; import * as React from 'react'; -import {useContext, useMemo} from 'react'; +import {ReactNode, useContext, useMemo} from 'react'; import invariant from 'shared/invariant'; export function LexicalNestedComposer({ @@ -23,7 +23,7 @@ export function LexicalNestedComposer({ children, initialTheme, }: { - children: JSX.Element | string | (JSX.Element | string)[]; + children: ReactNode; initialEditor: LexicalEditor; initialTheme?: EditorThemeClasses; }): JSX.Element { diff --git a/packages/lexical-react/src/LexicalTablePlugin.ts b/packages/lexical-react/src/LexicalTablePlugin.ts index 642d90fc9..117b51f02 100644 --- a/packages/lexical-react/src/LexicalTablePlugin.ts +++ b/packages/lexical-react/src/LexicalTablePlugin.ts @@ -115,7 +115,7 @@ export function TablePlugin(): JSX.Element | null { } else if (mutation === 'destroyed') { const tableSelection = tableSelections.get(nodeKey); - if (tableSelection) { + if (tableSelection !== undefined) { tableSelection.removeListeners(); tableSelections.delete(nodeKey); } diff --git a/packages/lexical/src/LexicalNode.ts b/packages/lexical/src/LexicalNode.ts index dd47bb09e..039cdae85 100644 --- a/packages/lexical/src/LexicalNode.ts +++ b/packages/lexical/src/LexicalNode.ts @@ -121,24 +121,24 @@ export function $getNodeByKeyOrThrow(key: NodeKey): N { return node; } -export type DOMConversion = { - conversion: DOMConversionFn; +export type DOMConversion = { + conversion: DOMConversionFn; priority: 0 | 1 | 2 | 3 | 4; }; -export type DOMConversionFn = ( - element: Node, +export type DOMConversionFn = ( + element: T, parent?: Node, -) => DOMConversionOutput; +) => DOMConversionOutput | null; export type DOMChildConversion = ( lexicalNode: LexicalNode, parentLexicalNode: LexicalNode | null | undefined, ) => LexicalNode | null | undefined; -export type DOMConversionMap = Record< +export type DOMConversionMap = Record< NodeName, - (node: T) => DOMConversion | null + (node: T) => DOMConversion | null >; type NodeName = string; diff --git a/tsconfig.json b/tsconfig.json index c87d9ba1c..79f1463d9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,7 @@ "moduleResolution": "node", "downlevelIteration": true, "noEmit": true, - "strict": false, + "strict": true, "baseUrl": ".", "typeRoots": ["node_modules/@types", "libdefs/globals"], "skipLibCheck": true, @@ -185,6 +185,7 @@ }, "include": ["./libdefs", "./packages"], "exclude": [ + "**/__tests__/**", "**/dist/**", "**/npm/**", "**/node_modules/**", diff --git a/tsconfig.test.json b/tsconfig.test.json index 2e4762b3f..c70ee4543 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -1,7 +1,15 @@ { "compilerOptions": { "allowJs": true, - "esModuleInterop": true + "esModuleInterop": true, + "strict": false }, + "include": ["./libdefs", "./packages"], + "exclude": [ + "**/dist/**", + "**/npm/**", + "**/node_modules/**", + "./packages/playwright-core/**" + ], "extends": "./tsconfig.json" }