@thangph2146/lexical-editor 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/README.md +47 -0
  2. package/dist/editor-x/editor.cjs +732 -443
  3. package/dist/editor-x/editor.cjs.map +1 -1
  4. package/dist/editor-x/editor.css +1418 -1120
  5. package/dist/editor-x/editor.css.map +1 -1
  6. package/dist/editor-x/editor.d.cts +2 -1
  7. package/dist/editor-x/editor.d.ts +2 -1
  8. package/dist/editor-x/editor.js +736 -447
  9. package/dist/editor-x/editor.js.map +1 -1
  10. package/dist/index.cjs +772 -482
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.css +1418 -1120
  13. package/dist/index.css.map +1 -1
  14. package/dist/index.d.cts +1 -1
  15. package/dist/index.d.ts +1 -1
  16. package/dist/index.js +775 -485
  17. package/dist/index.js.map +1 -1
  18. package/package.json +86 -84
  19. package/src/components/lexical-editor.tsx +140 -123
  20. package/src/editor-x/editor.tsx +20 -5
  21. package/src/editor-x/plugins.tsx +385 -380
  22. package/src/nodes/list-with-color-node.tsx +160 -160
  23. package/src/plugins/autocomplete-plugin.tsx +2574 -2574
  24. package/src/plugins/context-menu-plugin.tsx +239 -9
  25. package/src/plugins/floating-text-format-plugin.tsx +84 -92
  26. package/src/plugins/images-plugin.tsx +4 -4
  27. package/src/plugins/list-color-plugin.tsx +178 -178
  28. package/src/plugins/tab-focus-plugin.tsx +66 -66
  29. package/src/plugins/table-column-resizer-plugin.tsx +329 -190
  30. package/src/plugins/toolbar/block-format/block-format-data.tsx +4 -0
  31. package/src/plugins/toolbar/block-format/format-bulleted-list.tsx +40 -40
  32. package/src/plugins/toolbar/block-format/format-list-with-marker.tsx +74 -74
  33. package/src/plugins/toolbar/block-format/format-numbered-list.tsx +40 -40
  34. package/src/plugins/toolbar/block-format-toolbar-plugin.tsx +118 -117
  35. package/src/plugins/toolbar/element-format-toolbar-plugin.tsx +37 -53
  36. package/src/plugins/toolbar/font-format-toolbar-plugin.tsx +8 -15
  37. package/src/plugins/toolbar/font-size-toolbar-plugin.tsx +2 -3
  38. package/src/plugins/toolbar/history-toolbar-plugin.tsx +2 -5
  39. package/src/plugins/toolbar/subsuper-toolbar-plugin.tsx +15 -23
  40. package/src/themes/_mixins.scss +158 -10
  41. package/src/themes/_variables.scss +168 -0
  42. package/src/themes/core/_code.scss +59 -0
  43. package/src/themes/core/_images.scss +80 -0
  44. package/src/themes/core/_lists.scss +214 -0
  45. package/src/themes/core/_misc.scss +46 -0
  46. package/src/themes/core/_reset.scss +119 -0
  47. package/src/themes/core/_tables.scss +145 -0
  48. package/src/themes/core/_text.scss +35 -0
  49. package/src/themes/core/_typography.scss +73 -0
  50. package/src/themes/editor-theme.scss +9 -623
  51. package/src/themes/editor-theme.ts +118 -118
  52. package/src/themes/plugins/_auto-embed.scss +11 -0
  53. package/src/themes/plugins/_color-picker.scss +103 -0
  54. package/src/themes/plugins/_draggable-block.scss +32 -0
  55. package/src/themes/plugins/_floating-link-editor.scss +47 -0
  56. package/src/themes/plugins/_floating-toolbars.scss +61 -0
  57. package/src/themes/plugins/_image-resizer.scss +38 -0
  58. package/src/themes/plugins/_image.scss +57 -0
  59. package/src/themes/plugins/_layout.scss +39 -0
  60. package/src/themes/plugins/_list-color.scss +23 -0
  61. package/src/themes/plugins/_mentions.scss +21 -0
  62. package/src/themes/plugins/_menus-and-pickers.scss +153 -0
  63. package/src/themes/plugins/_table.scss +20 -0
  64. package/src/themes/plugins/_toolbar.scss +36 -0
  65. package/src/themes/plugins/_tree-view.scss +11 -0
  66. package/src/themes/plugins.scss +20 -1165
  67. package/src/themes/ui-components/_animations.scss +31 -0
  68. package/src/themes/ui-components/_backgrounds.scss +27 -0
  69. package/src/themes/ui-components/_borders.scss +20 -0
  70. package/src/themes/ui-components/_button.scss +176 -0
  71. package/src/themes/ui-components/_checkbox.scss +14 -0
  72. package/src/themes/ui-components/_cursors.scss +31 -0
  73. package/src/themes/ui-components/_dialog.scss +86 -0
  74. package/src/themes/ui-components/_display-sizing.scss +100 -0
  75. package/src/themes/ui-components/_flex.scss +124 -0
  76. package/src/themes/ui-components/_form-layout.scss +15 -0
  77. package/src/themes/ui-components/_icons.scss +23 -0
  78. package/src/themes/ui-components/_input.scss +86 -0
  79. package/src/themes/ui-components/_label.scss +19 -0
  80. package/src/themes/ui-components/_loader.scss +9 -0
  81. package/src/themes/ui-components/_margins-paddings.scss +45 -0
  82. package/src/themes/ui-components/_popover.scss +16 -0
  83. package/src/themes/ui-components/_positioning.scss +73 -0
  84. package/src/themes/ui-components/_rounded.scss +19 -0
  85. package/src/themes/ui-components/_scroll-area.scss +11 -0
  86. package/src/themes/ui-components/_select.scss +110 -0
  87. package/src/themes/ui-components/_separator.scss +19 -0
  88. package/src/themes/ui-components/_shadow.scss +15 -0
  89. package/src/themes/ui-components/_tabs.scss +46 -0
  90. package/src/themes/ui-components/_text-utilities.scss +48 -0
  91. package/src/themes/ui-components/_toggle-toolbar.scss +128 -0
  92. package/src/themes/ui-components/_toggle.scss +80 -0
  93. package/src/themes/ui-components/_typography.scss +22 -0
  94. package/src/themes/ui-components.scss +27 -937
  95. package/src/transformers/markdown-list-transformer.ts +51 -51
  96. package/src/ui/button.tsx +11 -2
  97. package/src/ui/collapsible.tsx +1 -1
  98. package/src/ui/dialog.tsx +2 -2
  99. package/src/ui/flex.tsx +4 -4
  100. package/src/ui/popover.tsx +1 -1
  101. package/src/ui/tooltip.tsx +2 -2
@@ -1,178 +1,178 @@
1
- "use client"
2
-
3
- import type { JSX } from "react"
4
- import * as React from "react"
5
- import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"
6
- import { mergeRegister } from "@lexical/utils"
7
- import { COMMAND_PRIORITY_EDITOR, createCommand, $getNodeByKey } from "lexical"
8
- import type { LexicalCommand, NodeKey } from "lexical"
9
- import {
10
- ColorPicker,
11
- ColorPickerArea,
12
- ColorPickerContent,
13
- ColorPickerHueSlider,
14
- ColorPickerInput,
15
- ColorPickerPresets,
16
- } from "../editor-ui/color-picker"
17
- import { useEditorModal } from "../editor-hooks/use-modal"
18
- import {
19
- $isListWithColorNode,
20
- } from "../nodes/list-with-color-node"
21
- import { createListWithColorNodeFromRegistry } from "../editor-x/nodes"
22
- import { $isListNode, ListNode } from "@lexical/list"
23
- import { Button } from "../ui/button"
24
- import { DialogFooter } from "../ui/dialog"
25
- import { Flex } from "../ui/flex"
26
-
27
- export const OPEN_LIST_COLOR_PICKER_COMMAND: LexicalCommand<{
28
- listKey: NodeKey
29
- }> = createCommand<{ listKey: NodeKey }>("OPEN_LIST_COLOR_PICKER_COMMAND")
30
-
31
- const listColorStore = new Map<NodeKey, string>()
32
-
33
- /** Patch serialized state: thêm listColor vào list nodes theo listColorStore (fallback khi load content cũ). */
34
- export function patchListColorsInSerializedState(
35
- json: Record<string, unknown>
36
- ): void {
37
- const root = json.root as Record<string, unknown> | undefined
38
- if (!root || typeof root !== "object") return
39
- const children = root.children as Array<Record<string, unknown>> | undefined
40
- if (!Array.isArray(children)) return
41
-
42
- function walk(nodes: Array<Record<string, unknown>>): void {
43
- for (const node of nodes) {
44
- const key = node.key as string | undefined
45
- const type = node.type as string | undefined
46
- if (type === "list" && key && listColorStore.has(key)) {
47
- ;(node as Record<string, unknown>).listColor = listColorStore.get(key)
48
- }
49
- const childList = node.children as Array<Record<string, unknown>> | undefined
50
- if (Array.isArray(childList)) walk(childList)
51
- }
52
- }
53
- walk(children)
54
- }
55
-
56
- function getListElement(element: HTMLElement): HTMLElement | null {
57
- const tag = element.tagName.toUpperCase()
58
- if (tag === "UL" || tag === "OL") return element
59
- const list = element.querySelector("ul, ol")
60
- return list instanceof HTMLElement ? list : null
61
- }
62
-
63
- function applyListColor(editor: ReturnType<typeof useLexicalComposerContext>[0]) {
64
- listColorStore.forEach((color, listKey) => {
65
- const element = editor.getElementByKey(listKey)
66
- if (!(element instanceof HTMLElement)) return
67
- const listEl = getListElement(element)
68
- if (!listEl) return
69
- listEl.style.setProperty("--list-marker-color", color, "important")
70
- listEl.setAttribute("data-list-color", color)
71
- })
72
- }
73
-
74
- export function ListColorPlugin(): JSX.Element | null {
75
- const [editor] = useLexicalComposerContext()
76
- const [modal, showModal] = useEditorModal()
77
-
78
- React.useEffect(() => {
79
- const applyColor = (color: string, listKey: NodeKey) => {
80
- listColorStore.set(listKey, color)
81
- editor.update(() => {
82
- const node = $getNodeByKey(listKey)
83
- if ($isListWithColorNode(node)) {
84
- node.setListColor(color)
85
- return
86
- }
87
- if ($isListNode(node)) {
88
- const listType = node.getListType()
89
- const start = node.getStart()
90
- const newList = createListWithColorNodeFromRegistry(editor, listType, start)
91
- newList.setListColor(color)
92
- const children = node.getChildren()
93
- for (const c of children) newList.append(c)
94
- node.replace(newList)
95
- }
96
- })
97
- applyListColor(editor)
98
- }
99
-
100
- return mergeRegister(
101
- editor.registerNodeTransform(ListNode, (node) => {
102
- if ($isListWithColorNode(node)) return
103
- const listType = node.getListType()
104
- const start = node.getStart()
105
- const newList = createListWithColorNodeFromRegistry(editor, listType, start)
106
- const children = node.getChildren()
107
- for (const c of children) newList.append(c)
108
- node.replace(newList)
109
- }),
110
- editor.registerUpdateListener(() => {
111
- applyListColor(editor)
112
- }),
113
- editor.registerCommand(
114
- OPEN_LIST_COLOR_PICKER_COMMAND,
115
- ({ listKey }) => {
116
- const element = editor.getElementByKey(listKey)
117
- if (!(element instanceof HTMLElement)) return true
118
- const listEl = getListElement(element)
119
- if (!listEl) return true
120
-
121
- const fromStore = listColorStore.get(listKey)
122
- const fromVar = listEl.style.getPropertyValue("--list-marker-color")
123
- const fromAttr = listEl.getAttribute("data-list-color")
124
- const initialColor =
125
- fromStore || fromVar || fromAttr || "#000000"
126
-
127
- function ListColorModalContent({ onClose }: { onClose: () => void }) {
128
- const [color, setColor] = React.useState(initialColor)
129
- return (
130
- <div className="editor-list-color-dialog">
131
- <Flex direction="column" gap={4}>
132
- <div className="editor-text-xs-muted">
133
- Chọn màu cho bullet hoặc số thứ tự của list.
134
- </div>
135
-
136
- <ColorPicker
137
- inline
138
- value={color}
139
- onValueChange={(next) => {
140
- setColor(next)
141
- applyColor(next, listKey)
142
- applyListColor(editor)
143
- }}
144
- >
145
- <ColorPickerContent className="editor-w-full editor-border-0 editor-shadow-none editor-p-0">
146
- <ColorPickerArea className="editor-h-40 editor-w-full editor-rounded-md" />
147
- <Flex direction="column" gap={3} className="editor-mt-3">
148
- <Flex direction="column" gap={2}>
149
- <ColorPickerHueSlider className="editor-w-full" />
150
- <ColorPickerInput className="editor-w-full" />
151
- </Flex>
152
- <ColorPickerPresets />
153
- </Flex>
154
- </ColorPickerContent>
155
- </ColorPicker>
156
-
157
- <DialogFooter className="editor-px-0">
158
- <Button variant="outline" size="sm" onClick={onClose} className="editor-w-full">
159
- Hoàn tất
160
- </Button>
161
- </DialogFooter>
162
- </Flex>
163
- </div>
164
- )
165
- }
166
-
167
- showModal("Đổi màu list", (onClose) => (
168
- <ListColorModalContent onClose={onClose} />
169
- ))
170
- return true
171
- },
172
- COMMAND_PRIORITY_EDITOR
173
- )
174
- )
175
- }, [editor, showModal])
176
-
177
- return <>{modal}</>
178
- }
1
+ "use client"
2
+
3
+ import type { JSX } from "react"
4
+ import * as React from "react"
5
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"
6
+ import { mergeRegister } from "@lexical/utils"
7
+ import { COMMAND_PRIORITY_EDITOR, createCommand, $getNodeByKey } from "lexical"
8
+ import type { LexicalCommand, NodeKey } from "lexical"
9
+ import {
10
+ ColorPicker,
11
+ ColorPickerArea,
12
+ ColorPickerContent,
13
+ ColorPickerHueSlider,
14
+ ColorPickerInput,
15
+ ColorPickerPresets,
16
+ } from "../editor-ui/color-picker"
17
+ import { useEditorModal } from "../editor-hooks/use-modal"
18
+ import {
19
+ $isListWithColorNode,
20
+ } from "../nodes/list-with-color-node"
21
+ import { createListWithColorNodeFromRegistry } from "../editor-x/nodes"
22
+ import { $isListNode, ListNode } from "@lexical/list"
23
+ import { Button } from "../ui/button"
24
+ import { DialogFooter } from "../ui/dialog"
25
+ import { Flex } from "../ui/flex"
26
+
27
+ export const OPEN_LIST_COLOR_PICKER_COMMAND: LexicalCommand<{
28
+ listKey: NodeKey
29
+ }> = createCommand<{ listKey: NodeKey }>("OPEN_LIST_COLOR_PICKER_COMMAND")
30
+
31
+ const listColorStore = new Map<NodeKey, string>()
32
+
33
+ /** Patch serialized state: thêm listColor vào list nodes theo listColorStore (fallback khi load content cũ). */
34
+ export function patchListColorsInSerializedState(
35
+ json: Record<string, unknown>
36
+ ): void {
37
+ const root = json.root as Record<string, unknown> | undefined
38
+ if (!root || typeof root !== "object") return
39
+ const children = root.children as Array<Record<string, unknown>> | undefined
40
+ if (!Array.isArray(children)) return
41
+
42
+ function walk(nodes: Array<Record<string, unknown>>): void {
43
+ for (const node of nodes) {
44
+ const key = node.key as string | undefined
45
+ const type = node.type as string | undefined
46
+ if (type === "list" && key && listColorStore.has(key)) {
47
+ ;(node as Record<string, unknown>).listColor = listColorStore.get(key)
48
+ }
49
+ const childList = node.children as Array<Record<string, unknown>> | undefined
50
+ if (Array.isArray(childList)) walk(childList)
51
+ }
52
+ }
53
+ walk(children)
54
+ }
55
+
56
+ function getListElement(element: HTMLElement): HTMLElement | null {
57
+ const tag = element.tagName.toUpperCase()
58
+ if (tag === "UL" || tag === "OL") return element
59
+ const list = element.querySelector("ul, ol")
60
+ return list instanceof HTMLElement ? list : null
61
+ }
62
+
63
+ function applyListColor(editor: ReturnType<typeof useLexicalComposerContext>[0]) {
64
+ listColorStore.forEach((color, listKey) => {
65
+ const element = editor.getElementByKey(listKey)
66
+ if (!(element instanceof HTMLElement)) return
67
+ const listEl = getListElement(element)
68
+ if (!listEl) return
69
+ listEl.style.setProperty("--list-marker-color", color, "important")
70
+ listEl.setAttribute("data-list-color", color)
71
+ })
72
+ }
73
+
74
+ export function ListColorPlugin(): JSX.Element | null {
75
+ const [editor] = useLexicalComposerContext()
76
+ const [modal, showModal] = useEditorModal()
77
+
78
+ React.useEffect(() => {
79
+ const applyColor = (color: string, listKey: NodeKey) => {
80
+ listColorStore.set(listKey, color)
81
+ editor.update(() => {
82
+ const node = $getNodeByKey(listKey)
83
+ if ($isListWithColorNode(node)) {
84
+ node.setListColor(color)
85
+ return
86
+ }
87
+ if ($isListNode(node)) {
88
+ const listType = node.getListType()
89
+ const start = node.getStart()
90
+ const newList = createListWithColorNodeFromRegistry(editor, listType, start)
91
+ newList.setListColor(color)
92
+ const children = node.getChildren()
93
+ for (const c of children) newList.append(c)
94
+ node.replace(newList)
95
+ }
96
+ })
97
+ applyListColor(editor)
98
+ }
99
+
100
+ return mergeRegister(
101
+ editor.registerNodeTransform(ListNode, (node) => {
102
+ if ($isListWithColorNode(node)) return
103
+ const listType = node.getListType()
104
+ const start = node.getStart()
105
+ const newList = createListWithColorNodeFromRegistry(editor, listType, start)
106
+ const children = node.getChildren()
107
+ for (const c of children) newList.append(c)
108
+ node.replace(newList)
109
+ }),
110
+ editor.registerUpdateListener(() => {
111
+ applyListColor(editor)
112
+ }),
113
+ editor.registerCommand(
114
+ OPEN_LIST_COLOR_PICKER_COMMAND,
115
+ ({ listKey }) => {
116
+ const element = editor.getElementByKey(listKey)
117
+ if (!(element instanceof HTMLElement)) return true
118
+ const listEl = getListElement(element)
119
+ if (!listEl) return true
120
+
121
+ const fromStore = listColorStore.get(listKey)
122
+ const fromVar = listEl.style.getPropertyValue("--list-marker-color")
123
+ const fromAttr = listEl.getAttribute("data-list-color")
124
+ const initialColor =
125
+ fromStore || fromVar || fromAttr || "#000000"
126
+
127
+ function ListColorModalContent({ onClose }: { onClose: () => void }) {
128
+ const [color, setColor] = React.useState(initialColor)
129
+ return (
130
+ <div className="editor-list-color-dialog">
131
+ <Flex direction="column" gap={4}>
132
+ <div className="editor-text-xs-muted">
133
+ Chọn màu cho bullet hoặc số thứ tự của list.
134
+ </div>
135
+
136
+ <ColorPicker
137
+ inline
138
+ value={color}
139
+ onValueChange={(next) => {
140
+ setColor(next)
141
+ applyColor(next, listKey)
142
+ applyListColor(editor)
143
+ }}
144
+ >
145
+ <ColorPickerContent className="editor-w-full editor-border-0 editor-shadow-none editor-p-0">
146
+ <ColorPickerArea className="editor-h-40 editor-w-full editor-rounded-md" />
147
+ <Flex direction="column" gap={3} className="editor-mt-3">
148
+ <Flex direction="column" gap={2}>
149
+ <ColorPickerHueSlider className="editor-w-full" />
150
+ <ColorPickerInput className="editor-w-full" />
151
+ </Flex>
152
+ <ColorPickerPresets />
153
+ </Flex>
154
+ </ColorPickerContent>
155
+ </ColorPicker>
156
+
157
+ <DialogFooter className="editor-px-0">
158
+ <Button variant="outline" size="sm" onClick={onClose} className="editor-w-full">
159
+ Hoàn tất
160
+ </Button>
161
+ </DialogFooter>
162
+ </Flex>
163
+ </div>
164
+ )
165
+ }
166
+
167
+ showModal("Đổi màu list", (onClose) => (
168
+ <ListColorModalContent onClose={onClose} />
169
+ ))
170
+ return true
171
+ },
172
+ COMMAND_PRIORITY_EDITOR
173
+ )
174
+ )
175
+ }, [editor, showModal])
176
+
177
+ return <>{modal}</>
178
+ }
@@ -1,66 +1,66 @@
1
- "use client"
2
-
3
- /**
4
- * Copyright (c) Meta Platforms, Inc. and affiliates.
5
- *
6
- * This source code is licensed under the MIT license found in the
7
- * LICENSE file in the root directory of this source tree.
8
- *
9
- */
10
- import { useEffect } from "react"
11
- import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"
12
- import {
13
- $getSelection,
14
- $isRangeSelection,
15
- $setSelection,
16
- FOCUS_COMMAND,
17
- } from "lexical"
18
-
19
- const COMMAND_PRIORITY_LOW = 1
20
- const TAB_TO_FOCUS_INTERVAL = 100
21
-
22
- let lastTabKeyDownTimestamp = 0
23
- let hasRegisteredKeyDownListener = false
24
-
25
- function registerKeyTimeStampTracker() {
26
- window.addEventListener(
27
- "keydown",
28
- (event: KeyboardEvent) => {
29
- // Tab
30
- if (event.key === "Tab") {
31
- lastTabKeyDownTimestamp = event.timeStamp
32
- }
33
- },
34
- true
35
- )
36
- }
37
-
38
- export function TabFocusPlugin(): null {
39
- const [editor] = useLexicalComposerContext()
40
-
41
- useEffect(() => {
42
- if (!hasRegisteredKeyDownListener) {
43
- registerKeyTimeStampTracker()
44
- hasRegisteredKeyDownListener = true
45
- }
46
-
47
- return editor.registerCommand(
48
- FOCUS_COMMAND,
49
- (event: FocusEvent) => {
50
- const selection = $getSelection()
51
- if ($isRangeSelection(selection)) {
52
- if (
53
- lastTabKeyDownTimestamp + TAB_TO_FOCUS_INTERVAL >
54
- event.timeStamp
55
- ) {
56
- $setSelection(selection.clone())
57
- }
58
- }
59
- return false
60
- },
61
- COMMAND_PRIORITY_LOW
62
- )
63
- }, [editor])
64
-
65
- return null
66
- }
1
+ "use client"
2
+
3
+ /**
4
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ *
9
+ */
10
+ import { useEffect } from "react"
11
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"
12
+ import {
13
+ $getSelection,
14
+ $isRangeSelection,
15
+ $setSelection,
16
+ FOCUS_COMMAND,
17
+ } from "lexical"
18
+
19
+ const COMMAND_PRIORITY_LOW = 1
20
+ const TAB_TO_FOCUS_INTERVAL = 100
21
+
22
+ let lastTabKeyDownTimestamp = 0
23
+ let hasRegisteredKeyDownListener = false
24
+
25
+ function registerKeyTimeStampTracker() {
26
+ window.addEventListener(
27
+ "keydown",
28
+ (event: KeyboardEvent) => {
29
+ // Tab
30
+ if (event.key === "Tab") {
31
+ lastTabKeyDownTimestamp = event.timeStamp
32
+ }
33
+ },
34
+ true
35
+ )
36
+ }
37
+
38
+ export function TabFocusPlugin(): null {
39
+ const [editor] = useLexicalComposerContext()
40
+
41
+ useEffect(() => {
42
+ if (!hasRegisteredKeyDownListener) {
43
+ registerKeyTimeStampTracker()
44
+ hasRegisteredKeyDownListener = true
45
+ }
46
+
47
+ return editor.registerCommand(
48
+ FOCUS_COMMAND,
49
+ (event: FocusEvent) => {
50
+ const selection = $getSelection()
51
+ if ($isRangeSelection(selection)) {
52
+ if (
53
+ lastTabKeyDownTimestamp + TAB_TO_FOCUS_INTERVAL >
54
+ event.timeStamp
55
+ ) {
56
+ $setSelection(selection.clone())
57
+ }
58
+ }
59
+ return false
60
+ },
61
+ COMMAND_PRIORITY_LOW
62
+ )
63
+ }, [editor])
64
+
65
+ return null
66
+ }