Skip to content

Commit eb09ce9

Browse files
authored
feat(richtext-lexical): allow disabling indentation for specific nodes (#11631)
allow disabling indentation for specific nodes via IndentFeature Usage: ```ts editor: lexicalEditor({ features: ({ defaultFeatures }) => [ ...defaultFeatures, IndentFeature({ // the array must contain the "type" property of registered indentable nodes disabledNodes: ['paragraph', 'listitem'], }), ], }), ``` The nodes "paragraph", "heading", "listitem", "quote" remain indentable by default, even without `IndentFeature` registered. In a future PR we will probably add the option to disable TabNode.
1 parent f2da72b commit eb09ce9

File tree

3 files changed

+94
-7
lines changed

3 files changed

+94
-7
lines changed
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import type { ElementNode } from 'lexical'
2+
3+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
4+
import { TabIndentationPlugin } from '@lexical/react/LexicalTabIndentationPlugin'
5+
import { $findMatchingParent, mergeRegister } from '@lexical/utils'
6+
import {
7+
$getSelection,
8+
$isElementNode,
9+
$isRangeSelection,
10+
COMMAND_PRIORITY_LOW,
11+
INDENT_CONTENT_COMMAND,
12+
} from 'lexical'
13+
import { useEffect } from 'react'
14+
15+
import type { PluginComponent } from '../../typesClient.js'
16+
import type { IndentFeatureProps } from '../server/index.js'
17+
18+
export const IndentPlugin: PluginComponent<IndentFeatureProps> = (props) => {
19+
const [editor] = useLexicalComposerContext()
20+
const { disabledNodes } = props.clientProps
21+
22+
useEffect(() => {
23+
if (!editor || !disabledNodes?.length) {
24+
return
25+
}
26+
return mergeRegister(
27+
editor.registerCommand(
28+
INDENT_CONTENT_COMMAND,
29+
() => {
30+
return $handleIndentAndOutdent((block) => {
31+
if (!disabledNodes.includes(block.getType())) {
32+
const indent = block.getIndent()
33+
block.setIndent(indent + 1)
34+
}
35+
})
36+
},
37+
COMMAND_PRIORITY_LOW,
38+
),
39+
)
40+
}, [editor, disabledNodes])
41+
42+
return <TabIndentationPlugin />
43+
}
44+
45+
function $handleIndentAndOutdent(indentOrOutdent: (block: ElementNode) => void): boolean {
46+
const selection = $getSelection()
47+
if (!$isRangeSelection(selection)) {
48+
return false
49+
}
50+
const alreadyHandled = new Set()
51+
const nodes = selection.getNodes()
52+
for (let i = 0; i < nodes.length; i++) {
53+
const node = nodes[i]!
54+
const key = node.getKey()
55+
if (alreadyHandled.has(key)) {
56+
continue
57+
}
58+
const parentBlock = $findMatchingParent(
59+
node,
60+
(parentNode): parentNode is ElementNode =>
61+
$isElementNode(parentNode) && !parentNode.isInline(),
62+
)
63+
if (parentBlock === null) {
64+
continue
65+
}
66+
const parentKey = parentBlock.getKey()
67+
if (parentBlock.canIndent() && !alreadyHandled.has(parentKey)) {
68+
alreadyHandled.add(parentKey)
69+
indentOrOutdent(parentBlock)
70+
}
71+
}
72+
return alreadyHandled.size > 0
73+
}

packages/richtext-lexical/src/features/indent/client/index.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import type { ElementNode, LexicalNode } from 'lexical'
44

5-
import { TabIndentationPlugin } from '@lexical/react/LexicalTabIndentationPlugin'
65
import { $findMatchingParent } from '@lexical/utils'
76
import {
87
$isElementNode,
@@ -12,11 +11,11 @@ import {
1211
} from 'lexical'
1312

1413
import type { ToolbarGroup } from '../../toolbars/types.js'
15-
import type { PluginComponent } from '../../typesClient.js'
1614

1715
import { IndentDecreaseIcon } from '../../../lexical/ui/icons/IndentDecrease/index.js'
1816
import { IndentIncreaseIcon } from '../../../lexical/ui/icons/IndentIncrease/index.js'
1917
import { createClientFeature } from '../../../utilities/createClientFeature.js'
18+
import { IndentPlugin } from './IndentPlugin.js'
2019
import { toolbarIndentGroupWithItems } from './toolbarIndentGroup.js'
2120

2221
const toolbarGroups: ToolbarGroup[] = [
@@ -84,7 +83,7 @@ const toolbarGroups: ToolbarGroup[] = [
8483
export const IndentFeatureClient = createClientFeature({
8584
plugins: [
8685
{
87-
Component: TabIndentationPlugin as PluginComponent,
86+
Component: IndentPlugin,
8887
position: 'normal',
8988
},
9089
],
Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,25 @@
11
import { createServerFeature } from '../../../utilities/createServerFeature.js'
22
import { i18n } from './i18n.js'
33

4-
export const IndentFeature = createServerFeature({
5-
feature: {
6-
ClientFeature: '@payloadcms/richtext-lexical/client#IndentFeatureClient',
7-
i18n,
4+
export type IndentFeatureProps = {
5+
/**
6+
* The nodes that should not be indented. "type" property of the nodes you don't want to be indented.
7+
* These can be: "paragraph", "heading", "listitem", "quote" or other indentable nodes if they exist.
8+
*/
9+
disabledNodes?: string[]
10+
}
11+
12+
export const IndentFeature = createServerFeature<
13+
IndentFeatureProps,
14+
IndentFeatureProps,
15+
IndentFeatureProps
16+
>({
17+
feature: ({ props }) => {
18+
return {
19+
ClientFeature: '@payloadcms/richtext-lexical/client#IndentFeatureClient',
20+
clientFeatureProps: props,
21+
i18n,
22+
}
823
},
924
key: 'indent',
1025
})

0 commit comments

Comments
 (0)