diff --git a/packages/lexical-playground/src/Editor.tsx b/packages/lexical-playground/src/Editor.tsx index 1b752ced18c..ec1d2c0d1fa 100644 --- a/packages/lexical-playground/src/Editor.tsx +++ b/packages/lexical-playground/src/Editor.tsx @@ -55,7 +55,6 @@ import InlineImagePlugin from './plugins/InlineImagePlugin'; import KeywordsPlugin from './plugins/KeywordsPlugin'; import {LayoutPlugin} from './plugins/LayoutPlugin/LayoutPlugin'; import LinkPlugin from './plugins/LinkPlugin'; -import ListMaxIndentLevelPlugin from './plugins/ListMaxIndentLevelPlugin'; import MarkdownShortcutPlugin from './plugins/MarkdownShortcutPlugin'; import {MaxLengthPlugin} from './plugins/MaxLengthPlugin'; import MentionsPlugin from './plugins/MentionsPlugin'; @@ -200,7 +199,6 @@ export default function Editor(): JSX.Element { - - + diff --git a/packages/lexical-playground/src/plugins/ListMaxIndentLevelPlugin/index.ts b/packages/lexical-playground/src/plugins/ListMaxIndentLevelPlugin/index.ts deleted file mode 100644 index 198543784c1..00000000000 --- a/packages/lexical-playground/src/plugins/ListMaxIndentLevelPlugin/index.ts +++ /dev/null @@ -1,85 +0,0 @@ -/** - * 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. - * - */ - -import type {ElementNode, RangeSelection} from 'lexical'; - -import {$getListDepth, $isListItemNode, $isListNode} from '@lexical/list'; -import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; -import { - $getSelection, - $isElementNode, - $isRangeSelection, - COMMAND_PRIORITY_CRITICAL, - INDENT_CONTENT_COMMAND, -} from 'lexical'; -import {useEffect} from 'react'; - -function getElementNodesInSelection( - selection: RangeSelection, -): Set { - const nodesInSelection = selection.getNodes(); - - if (nodesInSelection.length === 0) { - return new Set([ - selection.anchor.getNode().getParentOrThrow(), - selection.focus.getNode().getParentOrThrow(), - ]); - } - - return new Set( - nodesInSelection.map((n) => ($isElementNode(n) ? n : n.getParentOrThrow())), - ); -} - -function $shouldPreventIndent(maxDepth: number): boolean { - const selection = $getSelection(); - - if (!$isRangeSelection(selection)) { - return false; - } - - const elementNodesInSelection: Set = - getElementNodesInSelection(selection); - - let totalDepth = 0; - - for (const elementNode of elementNodesInSelection) { - if ($isListNode(elementNode)) { - totalDepth = Math.max($getListDepth(elementNode) + 1, totalDepth); - } else if ($isListItemNode(elementNode)) { - const parent = elementNode.getParent(); - - if (!$isListNode(parent)) { - throw new Error( - 'ListMaxIndentLevelPlugin: A ListItemNode must have a ListNode for a parent.', - ); - } - - totalDepth = Math.max($getListDepth(parent) + 1, totalDepth); - } - } - - return totalDepth > maxDepth; -} - -export default function ListMaxIndentLevelPlugin({ - maxDepth = 7, -}: { - maxDepth?: number; -}): null { - const [editor] = useLexicalComposerContext(); - - useEffect(() => { - return editor.registerCommand( - INDENT_CONTENT_COMMAND, - () => $shouldPreventIndent(maxDepth), - COMMAND_PRIORITY_CRITICAL, - ); - }, [editor, maxDepth]); - return null; -} diff --git a/packages/lexical-react/flow/LexicalTabIndentationPlugin.js.flow b/packages/lexical-react/flow/LexicalTabIndentationPlugin.js.flow index 44784b01131..64d9ff87caa 100644 --- a/packages/lexical-react/flow/LexicalTabIndentationPlugin.js.flow +++ b/packages/lexical-react/flow/LexicalTabIndentationPlugin.js.flow @@ -11,6 +11,11 @@ import type {LexicalEditor} from 'lexical'; declare export function registerTabIndentation( editor: LexicalEditor, + maxIndent?: number, ): () => void; -declare export function TabIndentationPlugin(): null; +type Props = $ReadOnly<{ + maxIndent?: number, +}>; + +declare export function TabIndentationPlugin(props: Props): null; diff --git a/packages/lexical-react/src/LexicalTabIndentationPlugin.tsx b/packages/lexical-react/src/LexicalTabIndentationPlugin.tsx index db8c0e8a6e2..7a21a5b3279 100644 --- a/packages/lexical-react/src/LexicalTabIndentationPlugin.tsx +++ b/packages/lexical-react/src/LexicalTabIndentationPlugin.tsx @@ -6,16 +6,22 @@ * */ -import type {LexicalCommand, LexicalEditor, RangeSelection} from 'lexical'; +import type {LexicalEditor, RangeSelection} from 'lexical'; import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; -import {$filter, $getNearestBlockElementAncestorOrThrow} from '@lexical/utils'; +import { + $filter, + $getNearestBlockElementAncestorOrThrow, + mergeRegister, +} from '@lexical/utils'; import { $createRangeSelection, $getSelection, $isBlockElementNode, + $isElementNode, $isRangeSelection, $normalizeSelection__EXPERIMENTAL, + COMMAND_PRIORITY_CRITICAL, COMMAND_PRIORITY_EDITOR, INDENT_CONTENT_COMMAND, INSERT_TAB_COMMAND, @@ -57,24 +63,54 @@ function $indentOverTab(selection: RangeSelection): boolean { return false; } -export function registerTabIndentation(editor: LexicalEditor) { - return editor.registerCommand( - KEY_TAB_COMMAND, - (event) => { - const selection = $getSelection(); - if (!$isRangeSelection(selection)) { - return false; - } +export function registerTabIndentation( + editor: LexicalEditor, + maxIndent?: number, +) { + return mergeRegister( + editor.registerCommand( + KEY_TAB_COMMAND, + (event) => { + const selection = $getSelection(); + if (!$isRangeSelection(selection)) { + return false; + } + + event.preventDefault(); + return $indentOverTab(selection) + ? event.shiftKey + ? editor.dispatchCommand(OUTDENT_CONTENT_COMMAND, undefined) + : editor.dispatchCommand(INDENT_CONTENT_COMMAND, maxIndent) + : editor.dispatchCommand(INSERT_TAB_COMMAND, undefined); + }, + COMMAND_PRIORITY_EDITOR, + ), + + editor.registerCommand( + INDENT_CONTENT_COMMAND, + () => { + if (maxIndent == null) { + return false; + } + + const selection = $getSelection(); + if (!$isRangeSelection(selection)) { + return false; + } + + const indents = selection + .getNodes() + .map((node) => + ($isElementNode(node) && !node.isInline() + ? node + : node.getParentOrThrow() + ).getIndent(), + ); - event.preventDefault(); - const command: LexicalCommand = $indentOverTab(selection) - ? event.shiftKey - ? OUTDENT_CONTENT_COMMAND - : INDENT_CONTENT_COMMAND - : INSERT_TAB_COMMAND; - return editor.dispatchCommand(command, undefined); - }, - COMMAND_PRIORITY_EDITOR, + return Math.max(...indents) + 1 >= maxIndent; + }, + COMMAND_PRIORITY_CRITICAL, + ), ); } @@ -83,11 +119,11 @@ export function registerTabIndentation(editor: LexicalEditor) { * recommend using this plugin as it could negatively affect acessibility for keyboard * users, causing focus to become trapped within the editor. */ -export function TabIndentationPlugin(): null { +export function TabIndentationPlugin({maxIndent}: {maxIndent?: number}): null { const [editor] = useLexicalComposerContext(); useEffect(() => { - return registerTabIndentation(editor); - }, [editor]); + return registerTabIndentation(editor, maxIndent); + }, [editor, maxIndent]); return null; }