Fix all of the playground type errors (#2116)

* Fix several playground errors

* Fix more types

* Remove ts-ignores

* Small fix

* Small fix

* Small changes

* Remove unnecessary dep

* Add katex types

* Small fix
This commit is contained in:
John Flockton
2022-05-10 13:52:35 +01:00
committed by GitHub
parent 7b886c8dc0
commit f6986cff9f
62 changed files with 467 additions and 3060 deletions

12
libdefs/globals.d.ts vendored Normal file
View File

@ -0,0 +1,12 @@
declare module '*.svg' {
const content: any;
export default content;
}
declare module '*.gif' {
const content: any;
export default content;
}
declare module '*.jpg' {
const content: any;
export default content;
}

2760
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -149,5 +149,8 @@
"@playwright/test": {
"playwright-core": "1.16.0-next-alpha-trueadm-fork"
}
},
"dependencies": {
"@types/katex": "^0.14.0"
}
}

View File

@ -13,12 +13,14 @@ import type {
EditorThemeClasses,
} from 'lexical';
import {Class} from 'utility-types';
export function createHeadlessEditor(editorConfig?: {
namespace?: string;
editorState?: EditorState;
theme?: EditorThemeClasses;
parentEditor?: LexicalEditor;
nodes?: $ReadOnlyArray<Class<LexicalNode>>;
nodes?: ReadonlyArray<Class<LexicalNode>>;
onError: (error: Error) => void;
disableEvents?: boolean;
readOnly?: boolean;

View File

@ -39,13 +39,17 @@ export declare class LinkNode extends ElementNode {
}
export function convertAnchorElement(domNode: Node): DOMConversionOutput;
export function $createLinkNode(url: string): LinkNode;
export function $isLinkNode(node: ?LexicalNode): node is LinkNode;
export function $isLinkNode(
node: LinkNode | LexicalNode | null | undefined,
): node is LinkNode;
export declare class AutoLinkNode extends LinkNode {
static getType(): string;
static clone(node: AutoLinkNode): AutoLinkNode;
insertNewAfter(selection: RangeSelection): null | ElementNode;
}
export function $createAutoLinkNode(url: string): AutoLinkNode;
export function $isAutoLinkNode(node: ?LexicalNode): node is AutoLinkNode;
export function $isAutoLinkNode(
node: LinkNode | LexicalNode | null | undefined,
): node is AutoLinkNode;
export var TOGGLE_LINK_COMMAND: LexicalCommand<string | null>;

View File

@ -10,7 +10,7 @@ import {ElementNode, LexicalNode} from 'lexical';
export declare class MarkNode extends ElementNode {
__ids: Array<string>;
clone(node: MarkNode): MarkNode;
constructor(ids: Array<string>, key?: NodeKey): void;
constructor(ids: Array<string>, key?: NodeKey);
hasID(id: string): boolean;
getIDs(): Array<string>;
addID(id: string): void;

View File

@ -57,6 +57,13 @@ import YouTubePlugin from './plugins/YouTubePlugin';
import ContentEditable from './ui/ContentEditable';
import Placeholder from './ui/Placeholder';
interface customWindow extends Window {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
parent: any;
}
declare const window: customWindow;
const skipCollaborationInit =
window.parent != null && window.parent.frames.right === window;

View File

@ -6,8 +6,6 @@
*
*/
import type {Provider} from '@lexical/yjs';
import {WebsocketProvider} from 'y-websocket';
import {Doc} from 'yjs';
@ -21,7 +19,7 @@ const WEBSOCKET_ID = params.get('collabId') || '0';
export function createWebsocketProvider(
id: string,
yjsDocMap: Map<string, Doc>,
): Provider {
): WebsocketProvider {
let doc = yjsDocMap.get(id);
if (doc === undefined) {

View File

@ -22,13 +22,13 @@ const Context: React.Context<SettingsContextShape> = createContext({
setOption: () => {
return;
},
settings: {},
settings: DEFAULT_SETTINGS,
});
export const SettingsContext = ({
children,
}: {
children: JSX.Element;
children: JSX.Element | string | (JSX.Element | string)[];
}): JSX.Element => {
const [settings, setSettings] = useState(DEFAULT_SETTINGS);
const setOption = useCallback((setting: SettingName, value: boolean) => {

View File

@ -16,14 +16,14 @@ type ContextShape = {
historyState?: HistoryState;
};
const Context: React$Context<ContextShape> = createContext({
const Context: React.Context<ContextShape> = createContext({
historyState: {current: null, redoStack: [], undoStack: []},
});
export const SharedHistoryContext = ({
children,
}: {
children: JSX.Element;
children: JSX.Element | string | (JSX.Element | string)[];
}): JSX.Element => {
const historyContext = useMemo(
() => ({historyState: createEmptyHistoryState()}),

View File

@ -10,7 +10,6 @@ import './setupEnv';
import './index.css';
import * as React from 'react';
// $FlowFixMe: Flow doesn't understand react-dom
import {createRoot} from 'react-dom/client';
import App from './App';

View File

@ -11,7 +11,7 @@ import type {EditorConfig, LexicalNode, NodeKey} from 'lexical';
import {TextNode} from 'lexical';
export class EmojiNode extends TextNode {
__className: string;
__className?: string;
static getType(): string {
return 'emoji';
@ -21,7 +21,7 @@ export class EmojiNode extends TextNode {
return new EmojiNode(node.__className, node.__text, node.__key);
}
constructor(className: string, text: string, key: void | NodeKey) {
constructor(className: string, text: string, key?: NodeKey) {
super(text, key);
this.__className = className;
}
@ -40,17 +40,18 @@ export class EmojiNode extends TextNode {
dom: HTMLElement,
config: EditorConfig,
): boolean {
// $FlowFixMe: this will always be an element or null
const inner: null | HTMLElement = dom.firstChild;
const inner = dom.firstChild;
if (inner === null) {
return true;
}
super.updateDOM(prevNode, inner, config);
super.updateDOM(prevNode, inner as HTMLElement, config);
return false;
}
}
export function $isEmojiNode(node: LexicalNode | null): node is EmojiNode {
export function $isEmojiNode(
node: LexicalNode | null | undefined,
): node is EmojiNode {
return node instanceof EmojiNode;
}

View File

@ -152,7 +152,7 @@ export class EquationNode extends DecoratorNode<JSX.Element> {
}
setEquation(equation: string): void {
const writable = this.getWritable();
const writable = this.getWritable<EquationNode>();
writable.__equation = equation;
}
@ -176,7 +176,7 @@ export function $createEquationNode(
}
export function $isEquationNode(
node: LexicalNode | null,
node: LexicalNode | null | undefined,
): node is EquationNode {
return node instanceof EquationNode;
}

View File

@ -6,12 +6,12 @@
*
*/
// $FlowFixMe: node modules are ignored by flow
import {exportToSvg} from '@excalidraw/excalidraw';
import {
ExcalidrawElement,
NonDeleted,
} from '@excalidraw/excalidraw/types/element/types';
import {AppState} from '@excalidraw/excalidraw/types/types';
import * as React from 'react';
import {useEffect, useState} from 'react';
@ -21,7 +21,7 @@ type Props = {
/**
* Configures the export setting for SVG/Canvas
*/
appState?: mixed;
appState?: Partial<Omit<AppState, 'offsetTop' | 'offsetLeft'>> | null;
/**
* The css class applied to image to be rendered
*/
@ -37,7 +37,7 @@ type Props = {
/**
* The ref object to be used to render the image
*/
imageContainerRef: {current: null | HTMLElement};
imageContainerRef: {current: null | HTMLDivElement};
/**
* The type of image to be rendered
*/
@ -78,9 +78,6 @@ const removeStyleFromSvg_HACK = (svg) => {
export default function ExcalidrawImage({
elements,
imageContainerRef,
className = '',
height = null,
width = null,
appState = null,
rootClassName = null,
}: Props): JSX.Element {
@ -88,6 +85,10 @@ export default function ExcalidrawImage({
useEffect(() => {
const setContent = async () => {
if (!appState) {
return;
}
const svg: Element = await exportToSvg({
appState,
elements,
@ -107,8 +108,8 @@ export default function ExcalidrawImage({
return (
<div
ref={imageContainerRef}
className={rootClassName}
dangerouslySetInnerHTML={{__html: Svg?.outerHTML}}
className={rootClassName ?? ''}
dangerouslySetInnerHTML={{__html: Svg?.outerHTML ?? ''}}
/>
);
}

View File

@ -6,14 +6,11 @@
*
*/
// $FlowFixMe: node modules are ignored by flow
import './ExcalidrawModal.css';
// $FlowFixMe: Flow doesn't have types for Excalidraw
import Excalidraw from '@excalidraw/excalidraw';
import * as React from 'react';
import {useEffect, useRef, useState} from 'react';
// $FlowFixMe: Flow doesn't see react-dom module
import {ReactPortal, useEffect, useRef, useState} from 'react';
import {createPortal} from 'react-dom';
import Button from '../../ui/Button';
@ -40,11 +37,11 @@ type Props = {
/**
* Handle modal closing
*/
onHide: () => mixed;
onHide: () => void;
/**
* Callback when the save button is clicked
*/
onSave: (elements: ReadonlyArray<ExcalidrawElementFragment>) => mixed;
onSave: (elements: ReadonlyArray<ExcalidrawElementFragment>) => void;
};
/**
@ -59,7 +56,7 @@ export default function ExcalidrawModal({
isShown = false,
onHide,
onDelete,
}: Props): React.Portal | null {
}: Props): ReactPortal | null {
const excalidrawRef = useRef(null);
const excaliDrawModelRef = useRef(null);
const [discardModalOpen, setDiscardModalOpen] = useState(false);
@ -75,8 +72,7 @@ export default function ExcalidrawModal({
useEffect(() => {
let modalOverlayElement = null;
const clickOutsideHandler = (event: MouseEvent) => {
// $FlowFixMe: event.target is always a Node on the DOM
const target: HTMLElement = event.target;
const target = event.target;
if (
excaliDrawModelRef.current !== null &&
!excaliDrawModelRef.current.contains(target) &&

View File

@ -35,13 +35,13 @@ function ExcalidrawComponent({
}: {
data: string;
nodeKey: NodeKey;
}): React.Node {
}): JSX.Element {
const [editor] = useLexicalComposerContext();
const [isModalOpen, setModalOpen] = useState<boolean>(
data === '[]' && !editor.isReadOnly(),
);
const imageContainerRef = useRef<HTMLElement | null>(null);
const buttonRef = useRef<HTMLElement | null>(null);
const imageContainerRef = useRef<HTMLImageElement | null>(null);
const buttonRef = useRef<HTMLButtonElement | null>(null);
const [isSelected, setSelected, clearSelection] =
useLexicalNodeSelection(nodeKey);
const [isResizing, setIsResizing] = useState<boolean>(false);
@ -79,14 +79,13 @@ function ExcalidrawComponent({
CLICK_COMMAND,
(event: MouseEvent) => {
const buttonElem = buttonRef.current;
// $FlowFixMe: this will work
const eventTarget: Element = event.target;
const eventTarget = event.target;
if (isResizing) {
return true;
}
if (buttonElem !== null && buttonElem.contains(eventTarget)) {
if (buttonElem !== null && buttonElem.contains(eventTarget as Node)) {
if (!event.shiftKey) {
clearSelection();
}
@ -226,7 +225,7 @@ export class ExcalidrawNode extends DecoratorNode<JSX.Element> {
}
setData(data: string): void {
const self = this.getWritable();
const self = this.getWritable<ExcalidrawNode>();
self.__data = data;
}

View File

@ -65,7 +65,7 @@ function useSuspenseImage(src: string) {
img.src = src;
img.onload = () => {
imageCache.add(src);
resolve();
resolve(null);
};
});
}
@ -83,11 +83,11 @@ function LazyImage({
altText: string;
className: string | null;
height: 'inherit' | number;
imageRef: {current: null | HTMLElement};
imageRef: {current: null | HTMLImageElement};
maxWidth: number;
src: string;
width: 'inherit' | number;
}): React.Node {
}): JSX.Element {
useSuspenseImage(src);
return (
<img
@ -124,7 +124,7 @@ function ImageComponent({
showCaption: boolean;
src: string;
width: 'inherit' | number;
}): React.Node {
}): JSX.Element {
const ref = useRef(null);
const [isSelected, setSelected, clearSelection] =
useLexicalNodeSelection(nodeKey);
@ -135,7 +135,7 @@ function ImageComponent({
const [selection, setSelection] = useState(null);
const onDelete = useCallback(
(payload) => {
(payload: KeyboardEvent) => {
if (isSelected && $isNodeSelection($getSelection())) {
const event: KeyboardEvent = payload;
event.preventDefault();
@ -157,10 +157,10 @@ function ImageComponent({
editor.registerUpdateListener(({editorState}) => {
setSelection(editorState.read(() => $getSelection()));
}),
editor.registerCommand(
editor.registerCommand<MouseEvent>(
CLICK_COMMAND,
(payload) => {
const event: MouseEvent = payload;
const event = payload;
if (isResizing) {
return true;
@ -273,7 +273,7 @@ function ImageComponent({
}
initialEditorState={null}
/>
{showNestedEditorTreeView && <TreeViewPlugin />}
{showNestedEditorTreeView === true ? <TreeViewPlugin /> : null}
</LexicalNestedComposer>
</div>
)}
@ -352,13 +352,13 @@ export class ImageNode extends DecoratorNode<JSX.Element> {
width: 'inherit' | number,
height: 'inherit' | number,
): void {
const writable = this.getWritable();
const writable = this.getWritable<ImageNode>();
writable.__width = width;
writable.__height = height;
}
setShowCaption(showCaption: boolean): void {
const writable = this.getWritable();
const writable = this.getWritable<ImageNode>();
writable.__showCaption = showCaption;
}
@ -411,6 +411,8 @@ export function $createImageNode(
return new ImageNode(src, altText, maxWidth);
}
export function $isImageNode(node: LexicalNode | null): node is ImageNode {
export function $isImageNode(
node: LexicalNode | null | undefined,
): node is ImageNode {
return node instanceof ImageNode;
}

View File

@ -43,6 +43,8 @@ export function $createKeywordNode(keyword: string): KeywordNode {
return new KeywordNode(keyword);
}
export function $isKeywordNode(node: LexicalNode | null | undefined): boolean {
export function $isKeywordNode(
node: LexicalNode | null | undefined | undefined,
): boolean {
return node instanceof KeywordNode;
}

View File

@ -45,6 +45,8 @@ export function $createMentionNode(mentionName: string): MentionNode {
return mentionNode;
}
export function $isMentionNode(node: LexicalNode | null): node is MentionNode {
export function $isMentionNode(
node: LexicalNode | null | undefined,
): node is MentionNode {
return node instanceof MentionNode;
}

View File

@ -208,14 +208,14 @@ export class PollNode extends DecoratorNode<JSX.Element> {
}
addOption(option: Option): void {
const self = this.getWritable();
const self = this.getWritable<PollNode>();
const options = Array.from(self.__options);
options.push(option);
self.__options = options;
}
deleteOption(option: Option): void {
const self = this.getWritable();
const self = this.getWritable<PollNode>();
const options = Array.from(self.__options);
const index = options.indexOf(option);
options.splice(index, 1);
@ -223,7 +223,7 @@ export class PollNode extends DecoratorNode<JSX.Element> {
}
setOptionText(option: Option, text: string): void {
const self = this.getWritable();
const self = this.getWritable<PollNode>();
const clonedOption = cloneOption(option, text);
const options = Array.from(self.__options);
const index = options.indexOf(option);
@ -232,7 +232,7 @@ export class PollNode extends DecoratorNode<JSX.Element> {
}
toggleVote(option: Option, clientID: number): void {
const self = this.getWritable();
const self = this.getWritable<PollNode>();
const votes = option.votes;
const votesClone = Array.from(votes);
const voteIndex = votes.indexOf(clientID);
@ -273,6 +273,8 @@ export function $createPollNode(question: string): PollNode {
return new PollNode(question);
}
export function $isPollNode(node: LexicalNode | null): node is PollNode {
export function $isPollNode(
node: LexicalNode | null | undefined,
): node is PollNode {
return node instanceof PollNode;
}

View File

@ -26,7 +26,6 @@ import {
} from 'lexical';
import * as React from 'react';
import {useEffect, useRef} from 'react';
// $FlowFixMe
import {createPortal} from 'react-dom';
import useLayoutEffect from 'shared/useLayoutEffect';
@ -59,7 +58,7 @@ function StickyComponent({
y: number;
}): JSX.Element {
const [editor] = useLexicalComposerContext();
const stickyContainerRef = useRef<null | HTMLElement>(null);
const stickyContainerRef = useRef<null | HTMLDivElement>(null);
const positioningRef = useRef<{
isDragging: boolean;
offsetX: number;
@ -228,14 +227,14 @@ function StickyComponent({
onClick={handleDelete}
className="delete"
aria-label="Delete sticky note"
type="Delete">
title="Delete">
X
</button>
<button
onClick={handleColorChange}
className="color"
aria-label="Change sticky note color"
type="Color">
title="Color">
<i className="bucket" />
</button>
<LexicalNestedComposer
@ -312,14 +311,14 @@ export class StickyNode extends DecoratorNode<JSX.Element> {
}
setPosition(x: number, y: number): void {
const writable = this.getWritable();
const writable = this.getWritable<StickyNode>();
writable.__x = x;
writable.__y = y;
$setSelection(null);
}
toggleColor(): void {
const writable = this.getWritable();
const writable = this.getWritable<StickyNode>();
writable.__color = writable.__color === 'pink' ? 'yellow' : 'pink';
}
@ -341,7 +340,9 @@ export class StickyNode extends DecoratorNode<JSX.Element> {
}
}
export function $isStickyNode(node: LexicalNode | null): node is StickyNode {
export function $isStickyNode(
node: LexicalNode | null | undefined,
): node is StickyNode {
return node instanceof StickyNode;
}

View File

@ -13,6 +13,13 @@ import {DecoratorBlockNode} from '@lexical/react/LexicalDecoratorBlockNode';
import * as React from 'react';
import {useCallback, useEffect, useRef, useState} from 'react';
interface customWindow extends Window {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
twttr?: any;
}
declare const window: customWindow;
const WIDGET_SCRIPT_URL = 'https://platform.twitter.com/widgets.js';
const getHasScriptCached = () =>
@ -20,9 +27,9 @@ const getHasScriptCached = () =>
type TweetComponentProps = Readonly<{
format: ElementFormatType | null;
loadingComponent?: JSX.Element;
loadingComponent?: JSX.Element | string;
nodeKey: NodeKey;
onError?: (error?: Error) => void;
onError?: (error: string) => void;
onLoad?: () => void;
tweetID: string;
}>;
@ -37,7 +44,7 @@ function TweetComponent({
}: TweetComponentProps) {
const containerRef = useRef<HTMLDivElement | null>(null);
const previousTweetIDRef = useRef(null);
const previousTweetIDRef = useRef<string>('');
const [isLoading, setIsLoading] = useState(false);
const createTweet = useCallback(async () => {
@ -49,9 +56,9 @@ function TweetComponent({
if (onLoad) {
onLoad();
}
} catch (e) {
} catch (error) {
if (onError) {
onError(e);
onError(String(error));
}
}
}, [onError, onLoad, tweetID]);
@ -71,7 +78,9 @@ function TweetComponent({
createTweet();
}
previousTweetIDRef.current = tweetID;
if (previousTweetIDRef) {
previousTweetIDRef.current = tweetID;
}
}
}, [createTweet, onError, tweetID]);
@ -97,7 +106,7 @@ export class TweetNode extends DecoratorBlockNode<JSX.Element> {
return new TweetNode(node.__id, node.__format, node.__key);
}
constructor(id: string, format: ElementFormatType | null, key?: NodeKey) {
constructor(id: string, format?: ElementFormatType | null, key?: NodeKey) {
super(format, key);
this.__id = id;
}
@ -126,6 +135,8 @@ export function $createTweetNode(tweetID: string): TweetNode {
return new TweetNode(tweetID);
}
export function $isTweetNode(node: LexicalNode | null): node is TweetNode {
export function $isTweetNode(
node: TweetNode | LexicalNode | null | undefined,
): node is TweetNode {
return node instanceof TweetNode;
}

View File

@ -45,7 +45,7 @@ export class YouTubeNode extends DecoratorBlockNode<JSX.Element> {
return new YouTubeNode(node.__id, node.__format, node.__key);
}
constructor(id: string, format: ElementFormatType | null, key?: NodeKey) {
constructor(id: string, format?: ElementFormatType | null, key?: NodeKey) {
super(format, key);
this.__id = id;
}
@ -73,6 +73,8 @@ export function $createYouTubeNode(videoID: string): YouTubeNode {
return new YouTubeNode(videoID);
}
export function $isYouTubeNode(node: LexicalNode | null): node is YouTubeNode {
export function $isYouTubeNode(
node: YouTubeNode | LexicalNode | null | undefined,
): node is YouTubeNode {
return node instanceof YouTubeNode;
}

View File

@ -55,7 +55,7 @@ export default function ActionsPlugin({
editor.registerReadOnlyListener((readOnly) => {
setIsReadyOnly(readOnly);
}),
editor.registerCommand(
editor.registerCommand<boolean>(
CONNECTED_COMMAND,
(payload) => {
const isConnected = payload;
@ -113,7 +113,10 @@ export default function ActionsPlugin({
{SUPPORT_SPEECH_RECOGNITION && (
<button
onClick={() => {
editor.dispatchCommand(SPEECH_TO_TEXT_COMMAND, !isSpeechToText);
editor.dispatchCommand<boolean>(
SPEECH_TO_TEXT_COMMAND,
!isSpeechToText,
);
setIsSpeechToText(!isSpeechToText);
}}
className={
@ -207,7 +210,7 @@ function ShowClearDialog({
<div className="Modal__content">
<Button
onClick={() => {
editor.dispatchCommand(CLEAR_EDITOR_COMMAND);
editor.dispatchCommand<void>(CLEAR_EDITOR_COMMAND, undefined);
editor.focus();
onClose();
}}>

View File

@ -70,7 +70,7 @@ function useTypeahead(editor: LexicalEditor): void {
let anchorNode = anchor.getNode();
let anchorNodeOffset = anchor.offset;
if (anchorNode.getKey() === currentTypeaheadNode.getKey()) {
anchorNode = anchorNode.getPreviousSibling();
anchorNode = anchorNode.getPreviousSibling() as TextNode;
if ($isTextNode(anchorNode)) {
anchorNodeOffset = anchorNode.getTextContent().length;
}
@ -78,7 +78,7 @@ function useTypeahead(editor: LexicalEditor): void {
let focusNode = focus.getNode();
let focusNodeOffset = focus.offset;
if (focusNode.getKey() === currentTypeaheadNode.getKey()) {
focusNode = focusNode.getPreviousSibling();
focusNode = focusNode.getPreviousSibling() as TextNode;
if ($isTextNode(focusNode)) {
focusNodeOffset = focusNode.getTextContent().length;
}
@ -269,7 +269,7 @@ class TypeaheadServer {
): {cancel: () => void; promise: () => Promise<string | null>} => {
let isCancelled = false;
const promise = () =>
const promise = (): Promise<string | null> =>
new Promise((resolve, reject) => {
setTimeout(() => {
const response = this.DATABASE[text] ?? null;

View File

@ -6,8 +6,6 @@
*
*/
import type {ElementNode, LexicalEditor, RangeSelection} from 'lexical';
import {$isCodeHighlightNode} from '@lexical/code';
import {$isLinkNode, TOGGLE_LINK_COMMAND} from '@lexical/link';
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
@ -18,13 +16,15 @@ import {
$isRangeSelection,
$isTextNode,
COMMAND_PRIORITY_LOW,
ElementNode,
FORMAT_TEXT_COMMAND,
LexicalEditor,
RangeSelection,
SELECTION_CHANGE_COMMAND,
TextNode,
} from 'lexical';
import {useCallback, useEffect, useRef, useState} from 'react';
import * as React from 'react';
// $FlowFixMe
import {createPortal} from 'react-dom';
function setPopupPosition(
@ -71,7 +71,7 @@ function FloatingCharacterStylesEditor({
isSuperscript: boolean;
isUnderline: boolean;
}): JSX.Element {
const popupCharStylesEditorRef = useRef<HTMLElement | null>(null);
const popupCharStylesEditorRef = useRef<HTMLDivElement | null>(null);
const mouseDownRef = useRef(false);
const insertLink = useCallback(() => {
@ -95,6 +95,7 @@ function FloatingCharacterStylesEditor({
const rootElement = editor.getRootElement();
if (
selection !== null &&
nativeSelection !== null &&
!nativeSelection.isCollapsed &&
rootElement !== null &&
rootElement.contains(nativeSelection.anchorNode)
@ -106,7 +107,7 @@ function FloatingCharacterStylesEditor({
if (nativeSelection.anchorNode === rootElement) {
let inner = rootElement;
while (inner.firstElementChild != null) {
inner = inner.firstElementChild;
inner = inner.firstElementChild as HTMLElement;
}
rect = inner.getBoundingClientRect();
} else {
@ -240,7 +241,7 @@ function getSelectedNode(selection: RangeSelection): TextNode | ElementNode {
}
}
function useCharacterStylesPopup(editor: LexicalEditor): JSX.Element {
function useCharacterStylesPopup(editor: LexicalEditor): JSX.Element | null {
const [isText, setIsText] = useState(false);
const [isLink, setIsLink] = useState(false);
const [isBold, setIsBold] = useState(false);
@ -258,14 +259,19 @@ function useCharacterStylesPopup(editor: LexicalEditor): JSX.Element {
const rootElement = editor.getRootElement();
if (
!$isRangeSelection(selection) ||
rootElement === null ||
!rootElement.contains(nativeSelection.anchorNode)
nativeSelection !== null &&
(!$isRangeSelection(selection) ||
rootElement === null ||
!rootElement.contains(nativeSelection.anchorNode))
) {
setIsText(false);
return;
}
if (!$isRangeSelection(selection)) {
return;
}
const node = getSelectedNode(selection);
// Update text format
@ -329,7 +335,7 @@ function useCharacterStylesPopup(editor: LexicalEditor): JSX.Element {
);
}
export default function CharacterStylesPopupPlugin(): JSX.Element {
export default function CharacterStylesPopupPlugin(): JSX.Element | null {
const [editor] = useLexicalComposerContext();
return useCharacterStylesPopup(editor);
}

View File

@ -39,7 +39,6 @@ import {
} from 'lexical';
import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import * as React from 'react';
// $FlowFixMe: Flow doesn't see react-dom module
import {createPortal} from 'react-dom';
import useLayoutEffect from 'shared/useLayoutEffect';
@ -50,8 +49,6 @@ import Button from '../ui/Button';
import ContentEditable from '../ui/ContentEditable.jsx';
import Placeholder from '../ui/Placeholder.jsx';
type RtfObject = Record<string, unknown>;
function AddCommentBox({
anchorKey,
editor,
@ -385,7 +382,7 @@ function CommentsPanelFooter({
footerRef,
submitAddComment,
}: {
footerRef: {current: null | HTMLElement};
footerRef: {current: null | HTMLDivElement};
submitAddComment: (
commentOrThread: Comment | Thread,
isInlineComment: boolean,
@ -448,7 +445,7 @@ function CommentsPanelListComment({
// eslint-disable-next-line no-shadow
thread?: Thread,
) => void;
rtf: RtfObject;
rtf: Intl.RelativeTimeFormat;
thread?: Thread;
}): JSX.Element {
const seconds = Math.round((comment.timeStamp - performance.now()) / 1000);
@ -496,7 +493,7 @@ function CommentsPanelList({
activeIDs: Array<string>;
comments: Comments;
deleteComment: (commentOrThread: Comment, thread?: Thread) => void;
listRef: {current: null | HTMLElement};
listRef: {current: null | HTMLUListElement};
markNodeMap: Map<string, Set<NodeKey>>;
submitAddComment: (
commentOrThread: Comment | Thread,
@ -506,9 +503,8 @@ function CommentsPanelList({
}): JSX.Element {
const [editor] = useLexicalComposerContext();
const [counter, setCounter] = useState(0);
const rtf: RtfObject = useMemo(
const rtf = useMemo(
() =>
// $FlowFixMe: Flow hasn't got types yet
new Intl.RelativeTimeFormat('en', {
localeMatcher: 'best fit',
numeric: 'auto',
@ -545,7 +541,7 @@ function CommentsPanelList({
editor.update(
() => {
const markNodeKey = Array.from(markNodeKeys)[0];
const markNode = $getNodeByKey(markNodeKey);
const markNode = $getNodeByKey<MarkNode>(markNodeKey);
if ($isMarkNode(markNode)) {
markNode.selectStart();
}
@ -554,7 +550,7 @@ function CommentsPanelList({
onUpdate() {
// Restore selection to the previous element
if (activeElement !== null) {
activeElement.focus();
(activeElement as HTMLElement).focus();
}
},
},

View File

@ -21,10 +21,13 @@ import {useEffect} from 'react';
import {$createEquationNode, EquationNode} from '../nodes/EquationNode';
export const INSERT_EQUATION_COMMAND: LexicalCommand<{
type CommandPayload = {
equation: string;
inline: boolean;
}> = createCommand();
};
export const INSERT_EQUATION_COMMAND: LexicalCommand<CommandPayload> =
createCommand();
export default function EquationsPlugin(): JSX.Element | null {
const [editor] = useLexicalComposerContext();
@ -36,7 +39,7 @@ export default function EquationsPlugin(): JSX.Element | null {
);
}
return editor.registerCommand(
return editor.registerCommand<CommandPayload>(
INSERT_EQUATION_COMMAND,
(payload) => {
const {equation, inline} = payload;

View File

@ -34,7 +34,7 @@ export default function ImagesPlugin(): JSX.Element {
throw new Error('ImagesPlugin: ImageNode not registered on editor');
}
return editor.registerCommand(
return editor.registerCommand<InsertImagePayload>(
INSERT_IMAGE_COMMAND,
(payload) => {
const selection = $getSelection();

View File

@ -199,7 +199,7 @@ export const TABLE: ElementTransformer = {
table.append(tableRow);
for (let i = 0; i < maxCells; i++) {
tableRow.append(i < cells.length ? cells[i] : createTableCell());
tableRow.append(i < cells.length ? cells[i] : createTableCell(null));
}
}

View File

@ -23,7 +23,6 @@ import {
} from 'lexical';
import {startTransition, useCallback, useEffect, useRef, useState} from 'react';
import * as React from 'react';
// $FlowFixMe
import {createPortal} from 'react-dom';
import useLayoutEffect from 'shared/useLayoutEffect';
@ -677,10 +676,10 @@ function MentionsTypeahead({
useEffect(() => {
return mergeRegister(
editor.registerCommand(
editor.registerCommand<KeyboardEvent>(
KEY_ARROW_DOWN_COMMAND,
(payload) => {
const event: KeyboardEvent = payload;
const event = payload;
if (results !== null && selectedIndex !== null) {
if (
selectedIndex < SUGGESTION_LIST_LENGTH_LIMIT - 1 &&
@ -695,10 +694,10 @@ function MentionsTypeahead({
},
COMMAND_PRIORITY_LOW,
),
editor.registerCommand(
editor.registerCommand<KeyboardEvent>(
KEY_ARROW_UP_COMMAND,
(payload) => {
const event: KeyboardEvent = payload;
const event = payload;
if (results !== null && selectedIndex !== null) {
if (selectedIndex !== 0) {
updateSelectedIndex(selectedIndex - 1);
@ -710,10 +709,10 @@ function MentionsTypeahead({
},
COMMAND_PRIORITY_LOW,
),
editor.registerCommand(
editor.registerCommand<KeyboardEvent>(
KEY_ESCAPE_COMMAND,
(payload) => {
const event: KeyboardEvent = payload;
const event = payload;
if (results === null || selectedIndex === null) {
return false;
}
@ -724,10 +723,10 @@ function MentionsTypeahead({
},
COMMAND_PRIORITY_LOW,
),
editor.registerCommand(
editor.registerCommand<KeyboardEvent>(
KEY_TAB_COMMAND,
(payload) => {
const event: KeyboardEvent = payload;
const event = payload;
if (results === null || selectedIndex === null) {
return false;
}

View File

@ -29,13 +29,13 @@ export default function PollPlugin(): JSX.Element {
throw new Error('PollPlugin: PollNode not registered on editor');
}
return editor.registerCommand(
return editor.registerCommand<string>(
INSERT_POLL_COMMAND,
(payload) => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
const question: string = payload;
const question = payload;
const pollNode = $createPollNode(question);
if ($isRootNode(selection.anchor.getNode())) {

View File

@ -21,6 +21,15 @@ import {useEffect, useRef, useState} from 'react';
import useReport from '../hooks/useReport';
interface customWindow extends Window {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
webkitSpeechRecognition?: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
SpeechRecognition?: any;
}
declare const window: customWindow;
export const SPEECH_TO_TEXT_COMMAND: LexicalCommand<boolean> = createCommand();
const VOICE_COMMANDS: Readonly<
@ -33,10 +42,10 @@ const VOICE_COMMANDS: Readonly<
selection.insertParagraph();
},
redo: ({editor}) => {
editor.dispatchCommand(REDO_COMMAND);
editor.dispatchCommand(REDO_COMMAND, undefined);
},
undo: ({editor}) => {
editor.dispatchCommand(UNDO_COMMAND);
editor.dispatchCommand(UNDO_COMMAND, undefined);
},
};

View File

@ -30,8 +30,7 @@ import {
$setSelection,
} from 'lexical';
import * as React from 'react';
import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
// $FlowFixMe
import {ReactPortal, useCallback, useEffect, useRef, useState} from 'react';
import {createPortal} from 'react-dom';
type TableCellActionMenuProps = Readonly<{
@ -48,7 +47,7 @@ function TableActionMenu({
contextRef,
}: TableCellActionMenuProps) {
const [editor] = useLexicalComposerContext();
const dropDownRef = useRef();
const dropDownRef = useRef<HTMLDivElement>();
const [tableCellNode, updateTableCellNode] = useState(_tableCellNode);
const [selectionCounts, updateSelectionCounts] = useState({
columns: 1,
@ -62,7 +61,7 @@ function TableActionMenu({
if (nodeUpdated) {
editor.getEditorState().read(() => {
updateTableCellNode(tableCellNode.getLatest());
updateTableCellNode(tableCellNode.getLatest<TableCellNode>());
});
}
});
@ -133,7 +132,7 @@ function TableActionMenu({
tableSelection.clearHighlight();
tableNode.markDirty();
updateTableCellNode(tableCellNode.getLatest());
updateTableCellNode(tableCellNode.getLatest<TableCellNode>());
}
$setSelection(null);
@ -252,78 +251,72 @@ function TableActionMenu({
});
}, [editor, tableCellNode, clearTableSelection, onClose]);
const toggleTableRowIsHeader = useCallback(
(isHeader) => {
editor.update(() => {
const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
const toggleTableRowIsHeader = useCallback(() => {
editor.update(() => {
const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
const tableRowIndex = $getTableRowIndexFromTableCellNode(tableCellNode);
const tableRowIndex = $getTableRowIndexFromTableCellNode(tableCellNode);
const tableRows = tableNode.getChildren();
const tableRows = tableNode.getChildren();
if (tableRowIndex >= tableRows.length || tableRowIndex < 0) {
throw new Error('Expected table cell to be inside of table row.');
if (tableRowIndex >= tableRows.length || tableRowIndex < 0) {
throw new Error('Expected table cell to be inside of table row.');
}
const tableRow = tableRows[tableRowIndex];
if (!$isTableRowNode(tableRow)) {
throw new Error('Expected table row');
}
tableRow.getChildren().forEach((tableCell) => {
if (!$isTableCellNode(tableCell)) {
throw new Error('Expected table cell');
}
const tableRow = tableRows[tableRowIndex];
tableCell.toggleHeaderStyle(TableCellHeaderStates.ROW);
});
clearTableSelection();
onClose();
});
}, [editor, tableCellNode, clearTableSelection, onClose]);
const toggleTableColumnIsHeader = useCallback(() => {
editor.update(() => {
const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
const tableColumnIndex =
$getTableColumnIndexFromTableCellNode(tableCellNode);
const tableRows = tableNode.getChildren();
for (let r = 0; r < tableRows.length; r++) {
const tableRow = tableRows[r];
if (!$isTableRowNode(tableRow)) {
throw new Error('Expected table row');
}
tableRow.getChildren().forEach((tableCell) => {
if (!$isTableCellNode(tableCell)) {
throw new Error('Expected table cell');
}
const tableCells = tableRow.getChildren();
tableCell.toggleHeaderStyle(TableCellHeaderStates.ROW);
});
clearTableSelection();
onClose();
});
},
[editor, tableCellNode, clearTableSelection, onClose],
);
const toggleTableColumnIsHeader = useCallback(
(isHeader) => {
editor.update(() => {
const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
const tableColumnIndex =
$getTableColumnIndexFromTableCellNode(tableCellNode);
const tableRows = tableNode.getChildren();
for (let r = 0; r < tableRows.length; r++) {
const tableRow = tableRows[r];
if (!$isTableRowNode(tableRow)) {
throw new Error('Expected table row');
}
const tableCells = tableRow.getChildren();
if (tableColumnIndex >= tableCells.length || tableColumnIndex < 0) {
throw new Error('Expected table cell to be inside of table row.');
}
const tableCell = tableCells[tableColumnIndex];
if (!$isTableCellNode(tableCell)) {
throw new Error('Expected table cell');
}
tableCell.toggleHeaderStyle(TableCellHeaderStates.COLUMN);
if (tableColumnIndex >= tableCells.length || tableColumnIndex < 0) {
throw new Error('Expected table cell to be inside of table row.');
}
clearTableSelection();
onClose();
});
},
[editor, tableCellNode, clearTableSelection, onClose],
);
const tableCell = tableCells[tableColumnIndex];
if (!$isTableCellNode(tableCell)) {
throw new Error('Expected table cell');
}
tableCell.toggleHeaderStyle(TableCellHeaderStates.COLUMN);
}
clearTableSelection();
onClose();
});
}, [editor, tableCellNode, clearTableSelection, onClose]);
return createPortal(
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
@ -404,7 +397,7 @@ function TableActionMenu({
);
}
function TableCellActionMenuContainer(): React.MixedElement {
function TableCellActionMenuContainer(): JSX.Element {
const [editor] = useLexicalComposerContext();
const menuButtonRef = useRef(null);
@ -531,15 +524,6 @@ function TableCellActionMenuContainer(): React.MixedElement {
);
}
export default function TableActionMenuPlugin(): React.Portal {
const [editor] = useLexicalComposerContext();
return useMemo(
() =>
createPortal(
<TableCellActionMenuContainer editor={editor} />,
document.body,
),
[editor],
);
export default function TableActionMenuPlugin(): ReactPortal {
return createPortal(<TableCellActionMenuContainer />, document.body);
}

View File

@ -27,8 +27,14 @@ import {
SELECTION_CHANGE_COMMAND,
} from 'lexical';
import * as React from 'react';
import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
// $FlowFixMe
import {
ReactPortal,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import {createPortal} from 'react-dom';
type MousePosition = {
@ -43,7 +49,7 @@ const MIN_COLUMN_WIDTH = 50;
function TableCellResizer({editor}: {editor: LexicalEditor}): JSX.Element {
const targetRef = useRef<HTMLElement | null>(null);
const resizerRef = useRef<HTMLElement | null>(null);
const resizerRef = useRef<HTMLDivElement | null>(null);
const tableRectRef = useRef<ClientRect | null>(null);
const mouseStartPosRef = useRef<MousePosition | null>(null);
@ -83,8 +89,7 @@ function TableCellResizer({editor}: {editor: LexicalEditor}): JSX.Element {
useEffect(() => {
const onMouseMove = (event: MouseEvent) => {
setTimeout(() => {
// $FlowFixMe: event.target is always a Node on the DOM
const target: HTMLElement = event.target;
const target = event.target;
if (draggingDirection) {
updateMouseCurrentPos({
@ -94,13 +99,13 @@ function TableCellResizer({editor}: {editor: LexicalEditor}): JSX.Element {
return;
}
if (resizerRef.current && resizerRef.current.contains(target)) {
if (resizerRef.current && resizerRef.current.contains(target as Node)) {
return;
}
if (targetRef.current !== target) {
targetRef.current = target;
const cell = getCellFromTarget(target);
targetRef.current = target as HTMLElement;
const cell = getCellFromTarget(target as HTMLElement);
if (cell && activeCell !== cell) {
editor.update(() => {
@ -117,7 +122,7 @@ function TableCellResizer({editor}: {editor: LexicalEditor}): JSX.Element {
throw new Error('TableCellResizer: Table element not found.');
}
targetRef.current = target;
targetRef.current = target as HTMLElement;
tableRectRef.current = tableElement.getBoundingClientRect();
updateActiveCell(cell);
});
@ -364,7 +369,7 @@ function TableCellResizer({editor}: {editor: LexicalEditor}): JSX.Element {
);
}
export default function TableCellResizerPlugin(): React.Portal {
export default function TableCellResizerPlugin(): ReactPortal {
const [editor] = useLexicalComposerContext();
return useMemo(

View File

@ -117,7 +117,7 @@ function getPathFromNodeToEditor(node: Node, rootElement) {
while (currentNode !== rootElement) {
path.unshift(
Array.from(currentNode?.parentNode?.childNodes ?? []).indexOf(
currentNode,
currentNode as ChildNode,
),
);
currentNode = currentNode?.parentNode;
@ -136,7 +136,13 @@ const keyPresses = new Set([
'ArrowDown',
]);
type Steps = Array<unknown>;
type Step = {
value: number;
count: number;
name: string;
};
type Steps = Step[];
function useTestRecorder(editor: LexicalEditor): [JSX.Element, JSX.Element] {
const [steps, setSteps] = useState<Steps>([]);

View File

@ -59,7 +59,6 @@ import {
} from 'lexical';
import * as React from 'react';
import {useCallback, useEffect, useRef, useState} from 'react';
// $FlowFixMe
import {createPortal} from 'react-dom';
import {IS_APPLE} from 'shared/environment';
@ -106,7 +105,7 @@ const blockTypeToBlockName = {
quote: 'Quote',
};
const CODE_LANGUAGE_OPTIONS = [
const CODE_LANGUAGE_OPTIONS: [string, string][] = [
['', '- Select language -'],
['c', 'C'],
['clike', 'C-like'],
@ -162,7 +161,7 @@ function positionEditorElement(editor, rect) {
}
function FloatingLinkEditor({editor}: {editor: LexicalEditor}): JSX.Element {
const editorRef = useRef<HTMLElement | null>(null);
const editorRef = useRef<HTMLDivElement | null>(null);
const inputRef = useRef(null);
const mouseDownRef = useRef(false);
const [linkUrl, setLinkUrl] = useState('');
@ -202,7 +201,7 @@ function FloatingLinkEditor({editor}: {editor: LexicalEditor}): JSX.Element {
if (nativeSelection.anchorNode === rootElement) {
let inner = rootElement;
while (inner.firstElementChild != null) {
inner = inner.firstElementChild;
inner = inner.firstElementChild as HTMLElement;
}
rect = inner.getBoundingClientRect();
} else {
@ -350,7 +349,7 @@ function InsertImageUploadedDialogBody({
const isDisabled = src === '';
const loadImage = (files: File[]) => {
const loadImage = (files: FileList) => {
const reader = new FileReader();
reader.onload = function () {
if (typeof reader.result === 'string') {
@ -627,25 +626,25 @@ function BlockFormatDropDown({
const formatBulletList = () => {
if (blockType !== 'bullet') {
editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND);
editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined);
} else {
editor.dispatchCommand(REMOVE_LIST_COMMAND);
editor.dispatchCommand(REMOVE_LIST_COMMAND, undefined);
}
};
const formatCheckList = () => {
if (blockType !== 'check') {
editor.dispatchCommand(INSERT_CHECK_LIST_COMMAND);
editor.dispatchCommand(INSERT_CHECK_LIST_COMMAND, undefined);
} else {
editor.dispatchCommand(REMOVE_LIST_COMMAND);
editor.dispatchCommand(REMOVE_LIST_COMMAND, undefined);
}
};
const formatNumberedList = () => {
if (blockType !== 'number') {
editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND);
editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined);
} else {
editor.dispatchCommand(REMOVE_LIST_COMMAND);
editor.dispatchCommand(REMOVE_LIST_COMMAND, undefined);
}
};
@ -748,7 +747,7 @@ function Select({
}: {
className: string;
onChange: (event: {target: {value: string}}) => void;
options: Array<[string, string]>;
options: [string, string][];
value: string;
}): JSX.Element {
return (
@ -816,7 +815,10 @@ export default function ToolbarPlugin(): JSX.Element {
if (elementDOM !== null) {
setSelectedElementKey(elementKey);
if ($isListNode(element)) {
const parentList = $getNearestNodeOfType(anchorNode, ListNode);
const parentList = $getNearestNodeOfType<ListNode>(
anchorNode,
ListNode,
);
const type = parentList
? parentList.getListType()
: element.getListType();
@ -864,7 +866,7 @@ export default function ToolbarPlugin(): JSX.Element {
updateToolbar();
});
}),
activeEditor.registerCommand(
activeEditor.registerCommand<boolean>(
CAN_UNDO_COMMAND,
(payload) => {
setCanUndo(payload);
@ -872,7 +874,7 @@ export default function ToolbarPlugin(): JSX.Element {
},
COMMAND_PRIORITY_CRITICAL,
),
activeEditor.registerCommand(
activeEditor.registerCommand<boolean>(
CAN_REDO_COMMAND,
(payload) => {
setCanRedo(payload);
@ -939,7 +941,7 @@ export default function ToolbarPlugin(): JSX.Element {
<button
disabled={!canUndo}
onClick={() => {
activeEditor.dispatchCommand(UNDO_COMMAND);
activeEditor.dispatchCommand(UNDO_COMMAND, undefined);
}}
title={IS_APPLE ? 'Undo (⌘Z)' : 'Undo (Ctrl+Z)'}
className="toolbar-item spaced"
@ -949,7 +951,7 @@ export default function ToolbarPlugin(): JSX.Element {
<button
disabled={!canRedo}
onClick={() => {
activeEditor.dispatchCommand(REDO_COMMAND);
activeEditor.dispatchCommand(REDO_COMMAND, undefined);
}}
title={IS_APPLE ? 'Redo (⌘Y)' : 'Undo (Ctrl+Y)'}
className="toolbar-item"
@ -1059,7 +1061,7 @@ export default function ToolbarPlugin(): JSX.Element {
onClick={insertLink}
className={'toolbar-item spaced ' + (isLink ? 'active' : '')}
aria-label="Insert link"
type="Insert link">
title="Insert link">
<i className="format link" />
</button>
{isLink &&
@ -1123,7 +1125,10 @@ export default function ToolbarPlugin(): JSX.Element {
buttonIconClassName="icon plus">
<button
onClick={() => {
activeEditor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND);
activeEditor.dispatchCommand(
INSERT_HORIZONTAL_RULE_COMMAND,
undefined,
);
}}
className="item">
<i className="icon horizontal-rule" />
@ -1155,7 +1160,10 @@ export default function ToolbarPlugin(): JSX.Element {
</button>
<button
onClick={() => {
activeEditor.dispatchCommand(INSERT_EXCALIDRAW_COMMAND);
activeEditor.dispatchCommand(
INSERT_EXCALIDRAW_COMMAND,
undefined,
);
}}
className="item">
<i className="icon diagram-2" />
@ -1282,7 +1290,7 @@ export default function ToolbarPlugin(): JSX.Element {
<Divider />
<button
onClick={() => {
activeEditor.dispatchCommand(OUTDENT_CONTENT_COMMAND);
activeEditor.dispatchCommand(OUTDENT_CONTENT_COMMAND, undefined);
}}
className="item">
<i className={'icon ' + (isRTL ? 'indent' : 'outdent')} />
@ -1290,7 +1298,7 @@ export default function ToolbarPlugin(): JSX.Element {
</button>
<button
onClick={() => {
activeEditor.dispatchCommand(INDENT_CONTENT_COMMAND);
activeEditor.dispatchCommand(INDENT_CONTENT_COMMAND, undefined);
}}
className="item">
<i className={'icon ' + (isRTL ? 'outdent' : 'indent')} />

View File

@ -30,7 +30,7 @@ export default function TwitterPlugin(): JSX.Element {
throw new Error('TwitterPlugin: TweetNode not registered on editor');
}
return editor.registerCommand(
return editor.registerCommand<string>(
INSERT_TWEET_COMMAND,
(payload) => {
const selection = $getSelection();

View File

@ -30,7 +30,7 @@ export default function YouTubePlugin(): JSX.Element {
throw new Error('YouTubePlugin: YouTubeNode not registered on editor');
}
return editor.registerCommand(
return editor.registerCommand<string>(
INSERT_YOUTUBE_COMMAND,
(payload) => {
const selection = $getSelection();

View File

@ -22,7 +22,7 @@ export default function Button({
title,
}: {
'data-test-id'?: string;
children: JSX.Element | string;
children: JSX.Element | string | (JSX.Element | string)[];
className?: string;
disabled?: boolean;
onClick: () => void;

View File

@ -8,7 +8,6 @@
import {useEffect, useRef, useState} from 'react';
import * as React from 'react';
// $FlowFixMe
import {createPortal} from 'react-dom';
export default function DropDown({
@ -22,10 +21,10 @@ export default function DropDown({
buttonClassName: string;
buttonIconClassName?: string;
buttonLabel?: string;
children: React.Node;
children: JSX.Element | string | (JSX.Element | string)[];
}): JSX.Element {
const dropDownRef = useRef<HTMLElement | null>(null);
const buttonRef = useRef<HTMLElement | null>(null);
const dropDownRef = useRef<HTMLDivElement | null>(null);
const buttonRef = useRef<HTMLButtonElement | null>(null);
const [showDropDown, setShowDropDown] = useState(false);
useEffect(() => {
@ -47,8 +46,8 @@ export default function DropDown({
if (button !== null && showDropDown) {
const handle = (event: MouseEvent) => {
const target: HTMLElement = event.target;
if (!button.contains(target)) {
const target = event.target;
if (!button.contains(target as Node)) {
setShowDropDown(false);
}
};

View File

@ -8,12 +8,13 @@
import './EquationEditor.css';
import React from 'react';
import * as React from 'react';
import {ChangeEvent, RefObject} from 'react';
type BaseEquationEditorProps = {
equation: string;
inline: boolean;
inputRef: {current: null | HTMLElement};
inputRef: {current: null | HTMLInputElement | HTMLTextAreaElement};
setEquation: (string) => void;
};
@ -34,16 +35,22 @@ export default function EquationEditor({
};
return inline ? (
<InlineEquationEditor {...props} />
<InlineEquationEditor
{...props}
inputRef={inputRef as RefObject<HTMLInputElement>}
/>
) : (
<BlockEquationEditor {...props} />
<BlockEquationEditor
{...props}
inputRef={inputRef as RefObject<HTMLTextAreaElement>}
/>
);
}
type EquationEditorImplProps = {
equation: string;
inputRef: {current: null | HTMLElement};
onChange: (event: SyntheticInputEvent<HTMLInputElement>) => void;
inputRef: {current: null | HTMLInputElement};
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
};
function InlineEquationEditor({
@ -66,11 +73,17 @@ function InlineEquationEditor({
);
}
type BlockEquationEditorImplProps = {
equation: string;
inputRef: {current: null | HTMLTextAreaElement};
onChange: (event: ChangeEvent<HTMLTextAreaElement>) => void;
};
function BlockEquationEditor({
equation,
onChange,
inputRef,
}: EquationEditorImplProps): JSX.Element {
}: BlockEquationEditorImplProps): JSX.Element {
return (
<div className="EquationEditor_inputBackground">
<span className="EquationEditor_dollarSign">{'$$\n'}</span>

View File

@ -14,7 +14,7 @@ type Props = Readonly<{
'data-test-id'?: string;
accept?: string;
label: string;
onChange: (files: File[]) => void;
onChange: (files: FileList) => void;
}>;
export default function FileInput({

View File

@ -38,7 +38,7 @@ export default function ImageResizer({
onResizeStart: () => void;
setShowCaption: (boolean) => void;
showCaption: boolean;
}): React.Node {
}): JSX.Element {
const buttonRef = useRef(null);
const positioningRef = useRef<{
currentHeight: 'inherit' | number;
@ -110,7 +110,10 @@ export default function ImageResizer({
}
};
const handlePointerDown = (event: PointerEvent, direction: number) => {
const handlePointerDown = (
event: React.PointerEvent<HTMLDivElement>,
direction: number,
) => {
const image = imageRef.current;
if (image !== null) {
const {width, height} = image.getBoundingClientRect();
@ -188,7 +191,7 @@ export default function ImageResizer({
}
}
};
const handlePointerUp = (_event: PointerEvent) => {
const handlePointerUp = () => {
const image = imageRef.current;
const positioning = positioningRef.current;
if (image !== null && positioning.isResizing) {

View File

@ -6,7 +6,6 @@
*
*/
// $FlowFixMe
import katex from 'katex';
import * as React from 'react';
import {useEffect, useRef} from 'react';

View File

@ -10,7 +10,6 @@ import './Modal.css';
import * as React from 'react';
import {useEffect, useRef} from 'react';
// $FlowFixMe
import {createPortal} from 'react-dom';
function PortalImpl({
@ -19,12 +18,12 @@ function PortalImpl({
title,
closeOnClickOutside,
}: {
children: JSX.Element;
children: JSX.Element | string | (JSX.Element | string)[];
closeOnClickOutside: boolean;
onClose: () => void;
title: string;
}) {
const modalRef = useRef<HTMLElement | null>(null);
const modalRef = useRef<HTMLDivElement>();
useEffect(() => {
if (modalRef.current !== null) {
@ -40,11 +39,10 @@ function PortalImpl({
}
};
const clickOutsideHandler = (event: MouseEvent) => {
// $FlowFixMe: event.target is always a Node on the DOM
const target: HTMLElement = event.target;
const target = event.target;
if (
modalRef.current !== null &&
!modalRef.current.contains(target) &&
!modalRef.current.contains(target as Node) &&
closeOnClickOutside
) {
onClose();
@ -74,7 +72,7 @@ function PortalImpl({
<button
className="Modal__closeButton"
aria-label="Close modal"
type="Close"
type="button"
onClick={onClose}>
X
</button>
@ -90,7 +88,7 @@ export default function Modal({
title,
closeOnClickOutside = false,
}: {
children: JSX.Element;
children: JSX.Element | string | (JSX.Element | string)[];
closeOnClickOutside?: boolean;
onClose: () => void;
title: string;

View File

@ -14,7 +14,7 @@ export default function Placeholder({
children,
className,
}: {
children: string;
children: JSX.Element | string | (JSX.Element | string)[];
className?: string;
}): JSX.Element {
return <div className={className || 'Placeholder__root'}>{children}</div>;

View File

@ -6,8 +6,7 @@
*
*/
import {$ReadOnly} from 'utility-types';
type Props = $ReadOnly<{
type Props = Readonly<{
scrollRef: {current: HTMLElement | null};
}>;
export default function LexicalAutoScrollPlugin(props: Props): null;

View File

@ -0,0 +1,18 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
*/
import type {ElementFormatType, NodeKey} from 'lexical';
type Props = Readonly<{
children: JSX.Element | string | (JSX.Element | string)[];
format: ElementFormatType | null;
nodeKey: NodeKey;
}>;
declare function BlockWithAlignableContents(Props): JSX.Element;

View File

@ -0,0 +1,9 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
export default function LexicalCheckListPlugin(): null;

View File

@ -6,6 +6,7 @@
*
*/
import {WebsocketProvider} from 'y-websocket';
import type {Doc, RelativePosition} from 'yjs';
export type UserState = {
anchorPos: null | RelativePosition;
@ -21,34 +22,15 @@ export type ProviderAwareness = {
on: (type: 'update', cb: () => void) => void;
off: (type: 'update', cb: () => void) => void;
};
export interface Provider {
connect(): void | Promise<void>;
disconnect(): void;
awareness: ProviderAwareness;
on(type: 'sync', cb: (isSynced: boolean) => void): void;
on(type: 'status', cb: (arg0: {status: string}) => void): void;
// $FlowFixMe: temp
on(type: 'update', cb: (arg0: any) => void): void;
on(type: 'reload', cb: (doc: Doc) => boolean): void;
off(type: 'sync', cb: (isSynced: boolean) => void): void;
// $FlowFixMe: temp
off(type: 'update', cb: (arg0: any) => void): void;
off(type: 'status', cb: (arg0: {status: string}) => void): void;
off(type: 'reload', cb: (doc: Doc) => boolean): void;
}
type CollaborationContextType = {
clientID: number;
color: string;
name: string;
yjsDocMap: Map<string, Doc>;
};
export type ProviderFactory = (
id: string,
yjsDocMap: Map<string, Doc>,
) => Provider;
export function CollaborationPlugin(arg0: {
id: string;
providerFactory: ProviderFactory;
providerFactory(id: string, yjsDocMap: Map<string, Doc>): WebsocketProvider;
shouldBootstrap: boolean;
username?: string;
}): JSX.Element | null;

View File

@ -0,0 +1,23 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
*/
import type {ElementFormatType, LexicalNode, NodeKey} from 'lexical';
import {DecoratorNode} from 'lexical';
declare class DecoratorBlockNode<T> extends DecoratorNode<T> {
__format: ElementFormatType;
constructor(format?: ElementFormatType | null, key?: NodeKey);
createDOM(): HTMLElement;
setFormat(format: ElementFormatType): void;
}
declare function $isDecoratorBlockNode<T>(
node: LexicalNode,
): node is DecoratorBlockNode<T>;

View File

@ -6,6 +6,8 @@
*
*/
import type {Transformer} from '@lexical/markdown';
export default function LexicalMarkdownShortcutPlugin(arg0: {
transformers: Array<Transformer>;
}): JSX.Element | null;

View File

@ -11,5 +11,5 @@ import type {LexicalEditor, EditorThemeClasses} from 'lexical';
export default function LexicalNestedComposer(arg0: {
initialEditor: LexicalEditor;
initialTheme?: EditorThemeClasses;
children: JSX.Element | JSX.Element[] | null;
children: JSX.Element | (JSX.Element | string | null)[] | null;
}): JSX.Element | null;

View File

@ -27,7 +27,9 @@ export declare class QuoteNode extends ElementNode {
collapseAtStart(): true;
}
export function $createQuoteNode(): QuoteNode;
export function $isQuoteNode(node: ?LexicalNode): node is QuoteNode;
export function $isQuoteNode(
node: LexicalNode | null | undefined,
): node is QuoteNode;
export type HeadingTagType = 'h1' | 'h2' | 'h3' | 'h4' | 'h5';
export declare class HeadingNode extends ElementNode {
__tag: HeadingTagType;
@ -42,7 +44,9 @@ export declare class HeadingNode extends ElementNode {
collapseAtStart(): true;
}
export function $createHeadingNode(headingTag: HeadingTagType): HeadingNode;
export function $isHeadingNode(node: ?LexicalNode): node is HeadingNode;
export function $isHeadingNode(
node: LexicalNode | null | undefined,
): node is HeadingNode;
export function registerRichText(
editor: LexicalEditor,
initialEditorState?: InitialEditorStateType,

View File

@ -18,9 +18,9 @@ import type {
TextFormatType,
LexicalCommand,
} from 'lexical';
import {TableSelection} from './src/TableSelection';
import {$Values} from 'utility-types';
export enum TableCellHeaderState {
export enum TableCellHeaderStates {
NO_STATUS = 0,
ROW = 1,
COLUMN = 2,
@ -31,24 +31,16 @@ export enum TableCellHeaderState {
* LexicalTableCellNode
*/
export const TableCellHeaderStates = {
NO_STATUS: 0,
ROW: 1,
COLUMN: 2,
BOTH: 3,
};
export type TableCellHeaderState = $Values<typeof TableCellHeaderStates>;
export declare class TableCellNode extends ElementNode {
static getType(): string;
static clone(node: TableCellNode): TableCellNode;
constructor(
headerState?: TableCellHeaderState,
headerState?: TableCellHeaderStates,
colSpan?: number,
width?: ?number,
width?: number | null | undefined,
key?: NodeKey,
);
__headerState: TableCellHeaderStates;
createDOM(config: EditorConfig): HTMLElement;
updateDOM(prevNode: TableCellNode, dom: HTMLElement): boolean;
insertNewAfter(
@ -56,17 +48,22 @@ export declare class TableCellNode extends ElementNode {
): null | ParagraphNode | TableCellNode;
collapseAtStart(): true;
getTag(): string;
setHeaderState(headerState: TableCellHeaderState): TableCellHeaderState;
getHeaderState(): TableCellHeaderState;
toggleHeaderState(headerState: TableCellHeaderState): TableCellNode;
setHeaderState(headerState: TableCellHeaderStates): TableCellHeaderStates;
getHeaderState(): TableCellHeaderStates;
toggleHeaderState(headerState: TableCellHeaderStates): TableCellNode;
hasHeader(): boolean;
setWidth(width: number): ?number;
getWidth(): ?number;
setWidth(width: number): number | null | undefined;
getWidth(): number | null | undefined;
toggleHeaderStyle(headerState: TableCellHeaderStates): TableCellNode;
updateDOM(prevNode: TableCellNode): boolean;
collapseAtStart(): true;
canBeEmpty(): false;
}
export declare function $createTableCellNode(): TableCellNode;
declare function $createTableCellNode(
headerState: TableCellHeaderStates,
colSpan?: number,
width?: number | null | undefined,
): TableCellNode;
export declare function $isTableCellNode(
node?: LexicalNode,
): node is TableCellNode;
@ -84,7 +81,7 @@ export declare class TableNode extends ElementNode {
insertNewAfter(selection: RangeSelection): null | ParagraphNode | TableNode;
collapseAtStart(): true;
getCordsFromCellNode(tableCellNode: TableCellNode): {x: number; y: number};
getCellFromCords(x: number, y: number, grid: Grid): ?Cell;
getCellFromCords(x: number, y: number, grid: Grid): Cell | null | undefined;
getCellFromCordsOrThrow(x: number, y: number, grid: Grid): Cell;
getCellNodeFromCords(x: number, y: number): TableCellNode | null;
getCellNodeFromCordsOrThrow(x: number, y: number): TableCellNode;
@ -102,14 +99,14 @@ declare function $isTableNode(node?: LexicalNode): node is TableNode;
declare class TableRowNode extends ElementNode {
static getType(): string;
static clone(node: TableRowNode): TableRowNode;
constructor(key?: NodeKey, height?: ?number);
constructor(key?: NodeKey, height?: number | null | undefined);
createDOM(config: EditorConfig): HTMLElement;
updateDOM(prevNode: TableRowNode, dom: HTMLElement): boolean;
insertNewAfter(
selection: RangeSelection,
): null | ParagraphNode | TableRowNode;
setHeight(height: number): ?number;
getHeight(): ?number;
setHeight(height: number): number | null | undefined;
getHeight(): number | null | undefined;
collapseAtStart(): true;
}
declare function $createTableRowNode(): TableRowNode;

View File

@ -6,6 +6,7 @@
*
*/
import type {ElementNode, LexicalEditor, RootNode, TextNode} from 'lexical';
import {Class} from 'utility-types';
export type TextNodeWithOffset = {
node: TextNode;
offset: number;
@ -27,7 +28,7 @@ export function $findNodeWithOffsetFromJoinedText(
joinedTextLength: number,
separatorLength: number,
elementNode: ElementNode,
): ?TextNodeWithOffset;
): TextNodeWithOffset | null | undefined;
export function $isRootTextContentEmpty(
isEditorComposing: boolean,
trim?: boolean,

View File

@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*
*/
import {Class} from 'utility-types';
import type {LexicalNode, ElementNode, LexicalEditor} from 'lexical';
export type DFSNode = Readonly<{
depth: number;
@ -25,7 +27,7 @@ declare function $dfs(
declare function $getDepth(node: LexicalNode): number;
declare function $getNearestNodeOfType<T extends LexicalNode>(
node: LexicalNode,
klass: T,
klass: Class<T>,
): T | null;
export type DOMNodeToLexicalConversion = (element: Node) => LexicalNode;
export type DOMNodeToLexicalConversionMap = Record<
@ -44,7 +46,7 @@ declare function $getNearestBlockElementAncestorOrThrow(
declare function registerNestedElementResolver<N extends ElementNode>(
editor: LexicalEditor,
targetNode: N,
targetNode: Class<N>,
cloneNode: (from: N) => N,
handleOverlap: (from: N, to: N) => void,
): () => void;

View File

@ -8,7 +8,6 @@
import type {
Doc,
RelativePosition,
TextOperation,
UndoManager,
XmlElement,
XmlText,
@ -26,7 +25,6 @@ import type {
TextNode,
IntentionallyMarkedAsDirtyElement,
} from 'lexical';
// @ts-expect-error: todo
export type YjsEvent = Record<string, any>;
export type UserState = {
anchorPos: null | RelativePosition;
@ -121,30 +119,35 @@ export declare class CollabDecoratorNode {
destroy(binding: Binding): void;
}
export declare class CollabLineBreakNode {
_map: YMap;
_map: YMap<unknown>;
_key: NodeKey;
_parent: CollabElementNode;
_type: 'linebreak';
constructor(map: YMap, parent: CollabElementNode);
constructor(map: YMap<unknown>, parent: CollabElementNode);
getNode(): null | LineBreakNode;
getKey(): NodeKey;
getSharedType(): YMap;
getSharedType(): YMap<unknown>;
getType(): string;
getSize(): number;
getOffset(): number;
destroy(binding: Binding): void;
}
export declare class CollabTextNode {
_map: YMap;
_map: YMap<unknown>;
_key: NodeKey;
_parent: CollabElementNode;
_text: string;
_type: string;
_normalized: boolean;
constructor(map: YMap, text: string, parent: CollabElementNode, type: string);
constructor(
map: YMap<unknown>,
text: string,
parent: CollabElementNode,
type: string,
);
getPrevNode(nodeMap: null | NodeMap): null | TextNode;
getNode(): null | TextNode;
getSharedType(): YMap;
getSharedType(): YMap<unknown>;
getType(): string;
getKey(): NodeKey;
getSize(): number;
@ -185,7 +188,7 @@ export declare class CollabElementNode {
binding: Binding,
keysChanged: null | Set<string>,
): void;
applyChildrenYjsDelta(binding: Binding, deltas: Array<TextOperation>): void;
applyChildrenYjsDelta(binding: Binding, deltas: Array<unknown>): void;
syncChildrenFromYjs(binding: Binding): void;
syncPropertiesFromLexical(
binding: Binding,

View File

@ -149,6 +149,7 @@ export declare class LexicalEditor {
): () => void;
dispatchCommand<P>(type: LexicalCommand<P>, payload: P): boolean;
hasNodes(nodes: Array<Class<LexicalNode>>): boolean;
getKey(): string;
getDecorators<X>(): Record<NodeKey, X>;
getRootElement(): null | HTMLElement;
setRootElement(rootElement: null | HTMLElement): void;
@ -617,6 +618,7 @@ export declare class TextNode extends LexicalNode {
canInsertTextAfter(): boolean;
splitText(...splitOffsets: Array<number>): Array<TextNode>;
mergeWithSibling(target: TextNode): TextNode;
isTextEntity(): boolean;
}
export function $createTextNode(text?: string): TextNode;
export function $isTextNode(

View File

@ -5,6 +5,6 @@
"packages/lexical-dragon/**/*",
"packages/lexical-history/**/*"
],
"exclude": ["packages/lexical-playground/**/*"],
"exclude": ["node_modules/**/*", "packages/lexical-playground/**/*"],
"extends": "./tsconfig.json"
}

View File

@ -199,5 +199,5 @@
"shared/useLayoutEffect": ["packages/shared/src/useLayoutEffect.js"]
}
},
"exclude": ["./node_modules/**/*"]
"exclude": ["node_modules/**/*"]
}