// eslint-disable-next-line import/no-extraneous-dependencies import { $getListDepth, $isListItemNode, $isListNode } from '@lexical/list'; import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; import { $getSelection, $isElementNode, $isRangeSelection, INDENT_CONTENT_COMMAND, COMMAND_PRIORITY_HIGH, RangeSelection, } from 'lexical'; import { FC, useEffect } from 'react'; const getElementNodesInSelection = (selection: RangeSelection) => { 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()))); }; const isIndentPermitted = (maxDepth: number) => { const selection = $getSelection(); if (!$isRangeSelection(selection)) { return false; } const elementNodesInSelection = 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 const ListMaxIndentLevel: FC = ({ maxDepth }) => { const [editor] = useLexicalComposerContext(); useEffect(() => { return editor.registerCommand( INDENT_CONTENT_COMMAND, () => !isIndentPermitted(maxDepth ?? 7), COMMAND_PRIORITY_HIGH ); }, [editor, maxDepth]); return null; }; interface ListMaxIndentLevelProps { maxDepth: number; }