@lexical/list 0.44.1-nightly.20260519.0 → 0.45.1-dev.0
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.
- package/{LexicalList.dev.js → dist/LexicalList.dev.js} +286 -31
- package/{LexicalList.dev.mjs → dist/LexicalList.dev.mjs} +285 -33
- package/{LexicalList.mjs → dist/LexicalList.mjs} +3 -0
- package/{LexicalList.node.mjs → dist/LexicalList.node.mjs} +3 -0
- package/dist/LexicalList.prod.js +9 -0
- package/dist/LexicalList.prod.mjs +9 -0
- package/dist/LexicalListExtension.d.ts +29 -0
- package/dist/ListImportExtension.d.ts +34 -0
- package/{index.d.ts → dist/index.d.ts} +4 -37
- package/dist/registerList.d.ts +20 -0
- package/package.json +33 -17
- package/src/LexicalListExtension.ts +76 -0
- package/src/LexicalListItemNode.ts +724 -0
- package/src/LexicalListNode.ts +393 -0
- package/src/ListImportExtension.ts +319 -0
- package/src/checkList.ts +469 -0
- package/src/formatList.ts +582 -0
- package/src/index.ts +98 -0
- package/src/registerList.ts +231 -0
- package/src/utils.ts +196 -0
- package/LexicalList.prod.js +0 -9
- package/LexicalList.prod.mjs +0 -9
- /package/{LexicalList.js → dist/LexicalList.js} +0 -0
- /package/{LexicalList.js.flow → dist/LexicalList.js.flow} +0 -0
- /package/{LexicalListItemNode.d.ts → dist/LexicalListItemNode.d.ts} +0 -0
- /package/{LexicalListNode.d.ts → dist/LexicalListNode.d.ts} +0 -0
- /package/{checkList.d.ts → dist/checkList.d.ts} +0 -0
- /package/{formatList.d.ts → dist/formatList.d.ts} +0 -0
- /package/{utils.d.ts → dist/utils.d.ts} +0 -0
package/src/checkList.ts
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {ListItemNode} from './LexicalListItemNode';
|
|
10
|
+
import type {LexicalCommand, LexicalEditor} from 'lexical';
|
|
11
|
+
|
|
12
|
+
import {Signal} from '@lexical/extension';
|
|
13
|
+
import {
|
|
14
|
+
$findMatchingParent,
|
|
15
|
+
calculateZoomLevel,
|
|
16
|
+
isHTMLElement,
|
|
17
|
+
mergeRegister,
|
|
18
|
+
} from '@lexical/utils';
|
|
19
|
+
import {
|
|
20
|
+
$addUpdateTag,
|
|
21
|
+
$getNearestNodeFromDOMNode,
|
|
22
|
+
$getSelection,
|
|
23
|
+
$isElementNode,
|
|
24
|
+
$isRangeSelection,
|
|
25
|
+
COMMAND_PRIORITY_LOW,
|
|
26
|
+
createCommand,
|
|
27
|
+
getNearestEditorFromDOMNode,
|
|
28
|
+
KEY_ARROW_DOWN_COMMAND,
|
|
29
|
+
KEY_ARROW_LEFT_COMMAND,
|
|
30
|
+
KEY_ARROW_UP_COMMAND,
|
|
31
|
+
KEY_ESCAPE_COMMAND,
|
|
32
|
+
KEY_SPACE_COMMAND,
|
|
33
|
+
SKIP_DOM_SELECTION_TAG,
|
|
34
|
+
SKIP_SELECTION_FOCUS_TAG,
|
|
35
|
+
} from 'lexical';
|
|
36
|
+
|
|
37
|
+
import {$insertList} from './formatList';
|
|
38
|
+
import {$isListItemNode} from './LexicalListItemNode';
|
|
39
|
+
import {$isListNode} from './LexicalListNode';
|
|
40
|
+
|
|
41
|
+
export const INSERT_CHECK_LIST_COMMAND: LexicalCommand<void> = createCommand(
|
|
42
|
+
'INSERT_CHECK_LIST_COMMAND',
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Registers the checklist plugin with the editor.
|
|
47
|
+
* @param editor The LexicalEditor instance.
|
|
48
|
+
* @param options Optional configuration.
|
|
49
|
+
* - disableTakeFocusOnClick: If true, clicking a checklist item will not focus the editor (useful for mobile).
|
|
50
|
+
*/
|
|
51
|
+
export function registerCheckList(
|
|
52
|
+
editor: LexicalEditor,
|
|
53
|
+
options?: {disableTakeFocusOnClick?: boolean | Signal<boolean>},
|
|
54
|
+
) {
|
|
55
|
+
const disableTakeFocusOnClick =
|
|
56
|
+
(options && options.disableTakeFocusOnClick) || false;
|
|
57
|
+
const peekDisableTakeFocusOnClick =
|
|
58
|
+
typeof disableTakeFocusOnClick === 'boolean'
|
|
59
|
+
? () => disableTakeFocusOnClick
|
|
60
|
+
: disableTakeFocusOnClick.peek.bind(disableTakeFocusOnClick);
|
|
61
|
+
|
|
62
|
+
// Mobile tap fix: the touchstart listener registered below calls
|
|
63
|
+
// event.preventDefault() to keep the caret away from the marker. On iOS
|
|
64
|
+
// Safari and Android Chrome that suppression also cancels the synthesized
|
|
65
|
+
// click, so handleClick never runs and the checkbox cannot be toggled by
|
|
66
|
+
// tap. We additionally listen for pointerup with pointerType === 'touch'
|
|
67
|
+
// and run the same toggle logic, deduplicating against any click that
|
|
68
|
+
// does fire on browsers where preventDefault doesn't suppress it.
|
|
69
|
+
//
|
|
70
|
+
// Dedup state is per-target: recorded as `__lexicalCheckListLastHandled`
|
|
71
|
+
// on the target element. A global window would
|
|
72
|
+
// block tapping a second checkbox within 500ms of toggling the first.
|
|
73
|
+
const DEDUP_WINDOW_MS = 500;
|
|
74
|
+
const isWithinDedupWindow = (
|
|
75
|
+
event: PointerEvent | MouseEvent | TouchEvent,
|
|
76
|
+
): boolean => {
|
|
77
|
+
const target = event.target;
|
|
78
|
+
if (!isHTMLElement(target)) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
// @ts-ignore internal field
|
|
82
|
+
const last = target.__lexicalCheckListLastHandled as number | undefined;
|
|
83
|
+
return last !== undefined && event.timeStamp - last < DEDUP_WINDOW_MS;
|
|
84
|
+
};
|
|
85
|
+
const recordHandled = (event: PointerEvent | MouseEvent | TouchEvent) => {
|
|
86
|
+
const target = event.target;
|
|
87
|
+
if (isHTMLElement(target)) {
|
|
88
|
+
// @ts-ignore internal field
|
|
89
|
+
target.__lexicalCheckListLastHandled = event.timeStamp;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const configHandleClick = (event: PointerEvent | MouseEvent | TouchEvent) => {
|
|
93
|
+
if (isWithinDedupWindow(event)) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
recordHandled(event);
|
|
97
|
+
handleClick(event, peekDisableTakeFocusOnClick());
|
|
98
|
+
};
|
|
99
|
+
const configHandlePointerUp = (event: PointerEvent) => {
|
|
100
|
+
if (event.pointerType !== 'touch') {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (isWithinDedupWindow(event)) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
recordHandled(event);
|
|
107
|
+
handleClick(event, peekDisableTakeFocusOnClick());
|
|
108
|
+
};
|
|
109
|
+
const configHandleSelectDefaults = (
|
|
110
|
+
event: PointerEvent | MouseEvent | TouchEvent,
|
|
111
|
+
) => {
|
|
112
|
+
handleSelectDefaults(event, peekDisableTakeFocusOnClick());
|
|
113
|
+
};
|
|
114
|
+
return mergeRegister(
|
|
115
|
+
editor.registerCommand(
|
|
116
|
+
INSERT_CHECK_LIST_COMMAND,
|
|
117
|
+
() => {
|
|
118
|
+
$insertList('check');
|
|
119
|
+
return true;
|
|
120
|
+
},
|
|
121
|
+
COMMAND_PRIORITY_LOW,
|
|
122
|
+
),
|
|
123
|
+
editor.registerCommand<KeyboardEvent>(
|
|
124
|
+
KEY_ARROW_DOWN_COMMAND,
|
|
125
|
+
event => {
|
|
126
|
+
return handleArrowUpOrDown(event, editor, false);
|
|
127
|
+
},
|
|
128
|
+
COMMAND_PRIORITY_LOW,
|
|
129
|
+
),
|
|
130
|
+
editor.registerCommand<KeyboardEvent>(
|
|
131
|
+
KEY_ARROW_UP_COMMAND,
|
|
132
|
+
event => {
|
|
133
|
+
return handleArrowUpOrDown(event, editor, true);
|
|
134
|
+
},
|
|
135
|
+
COMMAND_PRIORITY_LOW,
|
|
136
|
+
),
|
|
137
|
+
editor.registerCommand<KeyboardEvent>(
|
|
138
|
+
KEY_ESCAPE_COMMAND,
|
|
139
|
+
() => {
|
|
140
|
+
const activeItem = getActiveCheckListItem();
|
|
141
|
+
|
|
142
|
+
if (activeItem != null) {
|
|
143
|
+
const rootElement = editor.getRootElement();
|
|
144
|
+
|
|
145
|
+
if (rootElement != null) {
|
|
146
|
+
rootElement.focus();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return false;
|
|
153
|
+
},
|
|
154
|
+
COMMAND_PRIORITY_LOW,
|
|
155
|
+
),
|
|
156
|
+
editor.registerCommand<KeyboardEvent>(
|
|
157
|
+
KEY_SPACE_COMMAND,
|
|
158
|
+
event => {
|
|
159
|
+
const activeItem = getActiveCheckListItem();
|
|
160
|
+
|
|
161
|
+
if (activeItem != null && editor.isEditable()) {
|
|
162
|
+
editor.update(() => {
|
|
163
|
+
const listItemNode = $getNearestNodeFromDOMNode(activeItem);
|
|
164
|
+
|
|
165
|
+
if ($isListItemNode(listItemNode)) {
|
|
166
|
+
event.preventDefault();
|
|
167
|
+
listItemNode.toggleChecked();
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return false;
|
|
174
|
+
},
|
|
175
|
+
COMMAND_PRIORITY_LOW,
|
|
176
|
+
),
|
|
177
|
+
editor.registerCommand<KeyboardEvent>(
|
|
178
|
+
KEY_ARROW_LEFT_COMMAND,
|
|
179
|
+
event => {
|
|
180
|
+
return editor.getEditorState().read(() => {
|
|
181
|
+
const selection = $getSelection();
|
|
182
|
+
|
|
183
|
+
if ($isRangeSelection(selection) && selection.isCollapsed()) {
|
|
184
|
+
const {anchor} = selection;
|
|
185
|
+
const isElement = anchor.type === 'element';
|
|
186
|
+
|
|
187
|
+
if (isElement || anchor.offset === 0) {
|
|
188
|
+
const anchorNode = anchor.getNode();
|
|
189
|
+
const elementNode = $findMatchingParent(
|
|
190
|
+
anchorNode,
|
|
191
|
+
node => $isElementNode(node) && !node.isInline(),
|
|
192
|
+
);
|
|
193
|
+
if ($isListItemNode(elementNode)) {
|
|
194
|
+
const parent = elementNode.getParent();
|
|
195
|
+
if (
|
|
196
|
+
$isListNode(parent) &&
|
|
197
|
+
parent.getListType() === 'check' &&
|
|
198
|
+
(isElement || elementNode.getFirstDescendant() === anchorNode)
|
|
199
|
+
) {
|
|
200
|
+
const domNode = editor.getElementByKey(elementNode.__key);
|
|
201
|
+
|
|
202
|
+
if (domNode != null && document.activeElement !== domNode) {
|
|
203
|
+
domNode.focus();
|
|
204
|
+
event.preventDefault();
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return false;
|
|
213
|
+
});
|
|
214
|
+
},
|
|
215
|
+
COMMAND_PRIORITY_LOW,
|
|
216
|
+
),
|
|
217
|
+
|
|
218
|
+
editor.registerRootListener(rootElement => {
|
|
219
|
+
if (rootElement !== null) {
|
|
220
|
+
rootElement.addEventListener('click', configHandleClick);
|
|
221
|
+
rootElement.addEventListener('pointerup', configHandlePointerUp);
|
|
222
|
+
// Use capture so we run before other listeners that might move focus.
|
|
223
|
+
rootElement.addEventListener(
|
|
224
|
+
'pointerdown',
|
|
225
|
+
configHandleSelectDefaults,
|
|
226
|
+
{
|
|
227
|
+
capture: true,
|
|
228
|
+
},
|
|
229
|
+
);
|
|
230
|
+
// Some browsers / integrations still generate mousedown events; handle them too.
|
|
231
|
+
rootElement.addEventListener('mousedown', configHandleSelectDefaults, {
|
|
232
|
+
capture: true,
|
|
233
|
+
});
|
|
234
|
+
// Intercept touchstart to stop the mobile browser from placing the caret
|
|
235
|
+
// and opening the keyboard when tapping the checklist marker.
|
|
236
|
+
rootElement.addEventListener('touchstart', configHandleSelectDefaults, {
|
|
237
|
+
capture: true,
|
|
238
|
+
passive: false,
|
|
239
|
+
});
|
|
240
|
+
return () => {
|
|
241
|
+
rootElement.removeEventListener('click', configHandleClick);
|
|
242
|
+
rootElement.removeEventListener('pointerup', configHandlePointerUp);
|
|
243
|
+
rootElement.removeEventListener(
|
|
244
|
+
'pointerdown',
|
|
245
|
+
configHandleSelectDefaults,
|
|
246
|
+
{
|
|
247
|
+
capture: true,
|
|
248
|
+
},
|
|
249
|
+
);
|
|
250
|
+
rootElement.removeEventListener(
|
|
251
|
+
'mousedown',
|
|
252
|
+
configHandleSelectDefaults,
|
|
253
|
+
{
|
|
254
|
+
capture: true,
|
|
255
|
+
},
|
|
256
|
+
);
|
|
257
|
+
rootElement.removeEventListener(
|
|
258
|
+
'touchstart',
|
|
259
|
+
configHandleSelectDefaults,
|
|
260
|
+
{
|
|
261
|
+
capture: true,
|
|
262
|
+
},
|
|
263
|
+
);
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
}),
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function handleCheckItemEvent(
|
|
271
|
+
event: PointerEvent | MouseEvent | TouchEvent,
|
|
272
|
+
callback: () => void,
|
|
273
|
+
) {
|
|
274
|
+
const target = event.target;
|
|
275
|
+
|
|
276
|
+
if (!isHTMLElement(target)) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Ignore clicks on LI that have nested lists
|
|
281
|
+
const firstChild = target.firstChild;
|
|
282
|
+
|
|
283
|
+
if (
|
|
284
|
+
isHTMLElement(firstChild) &&
|
|
285
|
+
(firstChild.tagName === 'UL' || firstChild.tagName === 'OL')
|
|
286
|
+
) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const parentNode = target.parentNode;
|
|
291
|
+
|
|
292
|
+
// @ts-ignore internal field
|
|
293
|
+
if (!parentNode || parentNode.__lexicalListType !== 'check') {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
let clientX: number | null = null;
|
|
297
|
+
let pointerType: string | null = null;
|
|
298
|
+
|
|
299
|
+
if ('clientX' in event) {
|
|
300
|
+
clientX = event.clientX;
|
|
301
|
+
} else if ('touches' in event) {
|
|
302
|
+
const touches = event.touches;
|
|
303
|
+
if (touches.length > 0) {
|
|
304
|
+
clientX = touches[0].clientX;
|
|
305
|
+
pointerType = 'touch';
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// If we couldn't resolve a clientX (unexpected input), bail out.
|
|
310
|
+
if (clientX == null) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const rect = target.getBoundingClientRect();
|
|
315
|
+
const zoom = calculateZoomLevel(target);
|
|
316
|
+
const clientXInPixels = clientX / zoom;
|
|
317
|
+
|
|
318
|
+
// Use getComputedStyle if available, otherwise fallback to 0px width
|
|
319
|
+
const beforeStyles = window.getComputedStyle
|
|
320
|
+
? window.getComputedStyle(target, '::before')
|
|
321
|
+
: ({width: '0px'} as CSSStyleDeclaration);
|
|
322
|
+
const beforeWidthInPixels = parseFloat(beforeStyles.width);
|
|
323
|
+
|
|
324
|
+
// Make click area slightly larger for touch devices to improve accessibility
|
|
325
|
+
// Determine whether this is a touch event; some environments may supply
|
|
326
|
+
// pointerType on PointerEvent while touch events use the `touches` API above.
|
|
327
|
+
const isTouchEvent =
|
|
328
|
+
pointerType === 'touch' ||
|
|
329
|
+
('pointerType' in event && event.pointerType === 'touch');
|
|
330
|
+
const clickAreaPadding = isTouchEvent ? 32 : 0; // Add 32px padding for touch events
|
|
331
|
+
|
|
332
|
+
if (
|
|
333
|
+
target.dir === 'rtl'
|
|
334
|
+
? clientXInPixels < rect.right + clickAreaPadding &&
|
|
335
|
+
clientXInPixels > rect.right - beforeWidthInPixels - clickAreaPadding
|
|
336
|
+
: clientXInPixels > rect.left - clickAreaPadding &&
|
|
337
|
+
clientXInPixels < rect.left + beforeWidthInPixels + clickAreaPadding
|
|
338
|
+
) {
|
|
339
|
+
callback();
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function handleClick(
|
|
344
|
+
event: PointerEvent | MouseEvent | TouchEvent,
|
|
345
|
+
disableFocusOnClick: boolean,
|
|
346
|
+
) {
|
|
347
|
+
handleCheckItemEvent(event, () => {
|
|
348
|
+
if (isHTMLElement(event.target)) {
|
|
349
|
+
const domNode = event.target;
|
|
350
|
+
const editor = getNearestEditorFromDOMNode(domNode);
|
|
351
|
+
|
|
352
|
+
if (editor != null && editor.isEditable()) {
|
|
353
|
+
editor.update(() => {
|
|
354
|
+
const node = $getNearestNodeFromDOMNode(domNode);
|
|
355
|
+
|
|
356
|
+
if ($isListItemNode(node)) {
|
|
357
|
+
if (disableFocusOnClick) {
|
|
358
|
+
$addUpdateTag(SKIP_SELECTION_FOCUS_TAG);
|
|
359
|
+
$addUpdateTag(SKIP_DOM_SELECTION_TAG);
|
|
360
|
+
} else {
|
|
361
|
+
domNode.focus();
|
|
362
|
+
}
|
|
363
|
+
node.toggleChecked();
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Prevents default focus switch behavior
|
|
373
|
+
*
|
|
374
|
+
* @param event might be of type PointerEvent, MouseEvent, or TouchEvent, hence the generic Event type
|
|
375
|
+
*
|
|
376
|
+
*/
|
|
377
|
+
function handleSelectDefaults(
|
|
378
|
+
event: PointerEvent | MouseEvent | TouchEvent,
|
|
379
|
+
disableTakeFocusOnClick: boolean,
|
|
380
|
+
) {
|
|
381
|
+
handleCheckItemEvent(event, () => {
|
|
382
|
+
// Prevents caret moving when clicking on check mark.
|
|
383
|
+
event.preventDefault();
|
|
384
|
+
if (disableTakeFocusOnClick) {
|
|
385
|
+
event.stopPropagation();
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function getActiveCheckListItem(): HTMLElement | null {
|
|
391
|
+
const activeElement = document.activeElement;
|
|
392
|
+
|
|
393
|
+
return isHTMLElement(activeElement) &&
|
|
394
|
+
activeElement.tagName === 'LI' &&
|
|
395
|
+
activeElement.parentNode != null &&
|
|
396
|
+
// @ts-ignore internal field
|
|
397
|
+
activeElement.parentNode.__lexicalListType === 'check'
|
|
398
|
+
? activeElement
|
|
399
|
+
: null;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function findCheckListItemSibling(
|
|
403
|
+
node: ListItemNode,
|
|
404
|
+
backward: boolean,
|
|
405
|
+
): ListItemNode | null {
|
|
406
|
+
let sibling = backward ? node.getPreviousSibling() : node.getNextSibling();
|
|
407
|
+
let parent: ListItemNode | null = node;
|
|
408
|
+
|
|
409
|
+
// Going up in a tree to get non-null sibling
|
|
410
|
+
while (sibling == null && $isListItemNode(parent)) {
|
|
411
|
+
// Get li -> parent ul/ol -> parent li
|
|
412
|
+
parent = parent.getParentOrThrow().getParent();
|
|
413
|
+
|
|
414
|
+
if (parent != null) {
|
|
415
|
+
sibling = backward
|
|
416
|
+
? parent.getPreviousSibling()
|
|
417
|
+
: parent.getNextSibling();
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Going down in a tree to get first non-nested list item
|
|
422
|
+
while ($isListItemNode(sibling)) {
|
|
423
|
+
const firstChild = backward
|
|
424
|
+
? sibling.getLastChild()
|
|
425
|
+
: sibling.getFirstChild();
|
|
426
|
+
|
|
427
|
+
if (!$isListNode(firstChild)) {
|
|
428
|
+
return sibling;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
sibling = backward ? firstChild.getLastChild() : firstChild.getFirstChild();
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function handleArrowUpOrDown(
|
|
438
|
+
event: KeyboardEvent,
|
|
439
|
+
editor: LexicalEditor,
|
|
440
|
+
backward: boolean,
|
|
441
|
+
) {
|
|
442
|
+
const activeItem = getActiveCheckListItem();
|
|
443
|
+
|
|
444
|
+
if (activeItem != null) {
|
|
445
|
+
editor.update(() => {
|
|
446
|
+
const listItem = $getNearestNodeFromDOMNode(activeItem);
|
|
447
|
+
|
|
448
|
+
if (!$isListItemNode(listItem)) {
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const nextListItem = findCheckListItemSibling(listItem, backward);
|
|
453
|
+
|
|
454
|
+
if (nextListItem != null) {
|
|
455
|
+
nextListItem.selectStart();
|
|
456
|
+
const dom = editor.getElementByKey(nextListItem.__key);
|
|
457
|
+
|
|
458
|
+
if (dom != null) {
|
|
459
|
+
event.preventDefault();
|
|
460
|
+
setTimeout(() => {
|
|
461
|
+
dom.focus();
|
|
462
|
+
}, 0);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return false;
|
|
469
|
+
}
|