@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.
@@ -0,0 +1,582 @@
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 invariant from '@lexical/internal/invariant';
10
+ import {$getNearestNodeOfType} from '@lexical/utils';
11
+ import {
12
+ $copyNode,
13
+ $createParagraphNode,
14
+ $getChildCaret,
15
+ $getSelection,
16
+ $isElementNode,
17
+ $isLeafNode,
18
+ $isRangeSelection,
19
+ $isRootOrShadowRoot,
20
+ $isTextNode,
21
+ $normalizeCaret,
22
+ $setPointFromCaret,
23
+ ElementNode,
24
+ LexicalNode,
25
+ NodeKey,
26
+ ParagraphNode,
27
+ } from 'lexical';
28
+
29
+ import {
30
+ $createListItemNode,
31
+ $createListNode,
32
+ $isListItemNode,
33
+ $isListNode,
34
+ ListItemNode,
35
+ ListNode,
36
+ } from './';
37
+ import {ListType} from './LexicalListNode';
38
+ import {
39
+ $getAllListItems,
40
+ $getNewListStart,
41
+ $getTopListNode,
42
+ $removeHighestEmptyListParent,
43
+ isNestedListNode,
44
+ } from './utils';
45
+
46
+ function $isSelectingEmptyListItem(
47
+ anchorNode: ListItemNode | LexicalNode,
48
+ nodes: Array<LexicalNode>,
49
+ ): boolean {
50
+ return (
51
+ $isListItemNode(anchorNode) &&
52
+ (nodes.length === 0 ||
53
+ (nodes.length === 1 &&
54
+ anchorNode.is(nodes[0]) &&
55
+ anchorNode.getChildrenSize() === 0))
56
+ );
57
+ }
58
+
59
+ /**
60
+ * Inserts a new ListNode. If the selection's anchor node is an empty ListItemNode and is a child of
61
+ * the root/shadow root, it will replace the ListItemNode with a ListNode and the old ListItemNode.
62
+ * Otherwise it will replace its parent with a new ListNode and re-insert the ListItemNode and any previous children.
63
+ * If the selection's anchor node is not an empty ListItemNode, it will add a new ListNode or merge an existing ListNode,
64
+ * unless the the node is a leaf node, in which case it will attempt to find a ListNode up the branch and replace it with
65
+ * a new ListNode, or create a new ListNode at the nearest root/shadow root.
66
+ * @param listType - The type of list, "number" | "bullet" | "check".
67
+ */
68
+ export function $insertList(listType: ListType): void {
69
+ const selection = $getSelection();
70
+
71
+ if (selection !== null) {
72
+ let nodes = selection.getNodes();
73
+ if ($isRangeSelection(selection)) {
74
+ const [anchor] = selection.getStartEndPoints();
75
+ const anchorNode = anchor.getNode();
76
+ const anchorNodeParent = anchorNode.getParent();
77
+
78
+ if ($isRootOrShadowRoot(anchorNode)) {
79
+ const firstChild = anchorNode.getFirstChild();
80
+ if (firstChild) {
81
+ nodes = firstChild.selectStart().getNodes();
82
+ } else {
83
+ const paragraph = $createParagraphNode();
84
+ anchorNode.append(paragraph);
85
+ nodes = paragraph.select().getNodes();
86
+ }
87
+ } else if ($isSelectingEmptyListItem(anchorNode, nodes)) {
88
+ const list = $createListNode(listType);
89
+
90
+ if ($isRootOrShadowRoot(anchorNodeParent)) {
91
+ anchorNode.replace(list);
92
+ const listItem = $createListItemNode();
93
+ if ($isElementNode(anchorNode)) {
94
+ listItem.setFormat(anchorNode.getFormatType());
95
+ listItem.setIndent(anchorNode.getIndent());
96
+ }
97
+ list.append(listItem);
98
+ } else if ($isListItemNode(anchorNode)) {
99
+ const parent = anchorNode.getParentOrThrow();
100
+ append(list, parent.getChildren());
101
+ parent.replace(list);
102
+ }
103
+
104
+ return;
105
+ }
106
+ }
107
+
108
+ const handled = new Set();
109
+ for (let i = 0; i < nodes.length; i++) {
110
+ const node = nodes[i];
111
+
112
+ if (
113
+ $isElementNode(node) &&
114
+ node.isEmpty() &&
115
+ !$isListItemNode(node) &&
116
+ !handled.has(node.getKey())
117
+ ) {
118
+ $createListOrMerge(node, listType);
119
+ continue;
120
+ }
121
+
122
+ let parent = $isLeafNode(node)
123
+ ? node.getParent()
124
+ : $isListItemNode(node) && node.isEmpty()
125
+ ? node
126
+ : null;
127
+
128
+ while (parent != null) {
129
+ const parentKey = parent.getKey();
130
+
131
+ if ($isListNode(parent)) {
132
+ if (!handled.has(parentKey)) {
133
+ const newListNode = $createListNode(listType);
134
+ append(newListNode, parent.getChildren());
135
+ parent.replace(newListNode);
136
+ handled.add(parentKey);
137
+ }
138
+
139
+ break;
140
+ } else {
141
+ const nextParent = parent.getParent();
142
+
143
+ if ($isRootOrShadowRoot(nextParent) && !handled.has(parentKey)) {
144
+ handled.add(parentKey);
145
+ $createListOrMerge(parent, listType);
146
+ break;
147
+ }
148
+
149
+ parent = nextParent;
150
+ }
151
+ }
152
+ }
153
+ }
154
+ }
155
+
156
+ function append(node: ElementNode, nodesToAppend: Array<LexicalNode>) {
157
+ node.splice(node.getChildrenSize(), 0, nodesToAppend);
158
+ }
159
+
160
+ function $createListOrMerge(node: ElementNode, listType: ListType): ListNode {
161
+ if ($isListNode(node)) {
162
+ return node;
163
+ }
164
+
165
+ const previousSibling = node.getPreviousSibling();
166
+ const nextSibling = node.getNextSibling();
167
+ const listItem = $createListItemNode();
168
+ append(listItem, node.getChildren());
169
+
170
+ let targetList;
171
+ if (
172
+ $isListNode(previousSibling) &&
173
+ listType === previousSibling.getListType()
174
+ ) {
175
+ previousSibling.append(listItem);
176
+ // if the same type of list is on both sides, merge them.
177
+ if ($isListNode(nextSibling) && listType === nextSibling.getListType()) {
178
+ append(previousSibling, nextSibling.getChildren());
179
+ nextSibling.remove();
180
+ }
181
+ targetList = previousSibling;
182
+ } else if (
183
+ $isListNode(nextSibling) &&
184
+ listType === nextSibling.getListType()
185
+ ) {
186
+ nextSibling.getFirstChildOrThrow().insertBefore(listItem);
187
+ targetList = nextSibling;
188
+ } else {
189
+ const list = $createListNode(listType);
190
+ list.append(listItem);
191
+ node.replace(list);
192
+ targetList = list;
193
+ }
194
+ // listItem needs to be attached to root prior to setting indent
195
+ listItem.setFormat(node.getFormatType());
196
+ listItem.setIndent(node.getIndent());
197
+
198
+ // Preserve element-anchored selections by updating them to anchor to the listItem instead of the listNode.
199
+ const selection = $getSelection();
200
+ if ($isRangeSelection(selection)) {
201
+ if (targetList.getKey() === selection.anchor.key) {
202
+ selection.anchor.set(
203
+ listItem.getKey(),
204
+ selection.anchor.offset,
205
+ 'element',
206
+ );
207
+ }
208
+ if (targetList.getKey() === selection.focus.key) {
209
+ selection.focus.set(listItem.getKey(), selection.focus.offset, 'element');
210
+ }
211
+ }
212
+
213
+ node.remove();
214
+
215
+ return targetList;
216
+ }
217
+
218
+ /**
219
+ * A recursive function that goes through each list and their children, including nested lists,
220
+ * appending list2 children after list1 children and updating ListItemNode values.
221
+ * @param list1 - The first list to be merged.
222
+ * @param list2 - The second list to be merged.
223
+ */
224
+ export function mergeLists(list1: ListNode, list2: ListNode): void {
225
+ const listItem1 = list1.getLastChild();
226
+ const listItem2 = list2.getFirstChild();
227
+
228
+ if (
229
+ listItem1 &&
230
+ listItem2 &&
231
+ isNestedListNode(listItem1) &&
232
+ isNestedListNode(listItem2)
233
+ ) {
234
+ mergeLists(listItem1.getFirstChild(), listItem2.getFirstChild());
235
+ listItem2.remove();
236
+ }
237
+
238
+ const toMerge = list2.getChildren();
239
+ if (toMerge.length > 0) {
240
+ list1.append(...toMerge);
241
+ }
242
+
243
+ list2.remove();
244
+ }
245
+
246
+ /**
247
+ * Searches for the nearest ancestral ListNode and removes it. If selection is an empty ListItemNode
248
+ * it will remove the whole list, including the ListItemNode. For each ListItemNode in the ListNode,
249
+ * removeList will also generate new ParagraphNodes in the removed ListNode's place. Any child node
250
+ * inside a ListItemNode will be appended to the new ParagraphNodes.
251
+ */
252
+ export function $removeList(): void {
253
+ const selection = $getSelection();
254
+
255
+ if ($isRangeSelection(selection)) {
256
+ const listNodes = new Set<ListNode>();
257
+ const nodes = selection.getNodes();
258
+ const anchorNode = selection.anchor.getNode();
259
+
260
+ if ($isSelectingEmptyListItem(anchorNode, nodes)) {
261
+ listNodes.add($getTopListNode(anchorNode));
262
+ } else {
263
+ for (let i = 0; i < nodes.length; i++) {
264
+ const node = nodes[i];
265
+
266
+ if ($isLeafNode(node)) {
267
+ const listItemNode = $getNearestNodeOfType(node, ListItemNode);
268
+
269
+ if (listItemNode != null) {
270
+ listNodes.add($getTopListNode(listItemNode));
271
+ }
272
+ }
273
+ }
274
+ }
275
+
276
+ for (const listNode of listNodes) {
277
+ let insertionPoint: ListNode | ParagraphNode = listNode;
278
+
279
+ const listItems = $getAllListItems(listNode);
280
+
281
+ for (const listItemNode of listItems) {
282
+ const paragraph = $createParagraphNode()
283
+ .setTextStyle(selection.style)
284
+ .setTextFormat(selection.format);
285
+
286
+ append(paragraph, listItemNode.getChildren());
287
+
288
+ insertionPoint.insertAfter(paragraph);
289
+ insertionPoint = paragraph;
290
+
291
+ // When the anchor and focus fall on the textNode
292
+ // we don't have to change the selection because the textNode will be appended to
293
+ // the newly generated paragraph.
294
+ // When selection is in empty nested list item, selection is actually on the listItemNode.
295
+ // When the corresponding listItemNode is deleted and replaced by the newly generated paragraph
296
+ // we should manually set the selection's focus and anchor to the newly generated paragraph.
297
+ if (listItemNode.__key === selection.anchor.key) {
298
+ $setPointFromCaret(
299
+ selection.anchor,
300
+ $normalizeCaret($getChildCaret(paragraph, 'next')),
301
+ );
302
+ }
303
+ if (listItemNode.__key === selection.focus.key) {
304
+ $setPointFromCaret(
305
+ selection.focus,
306
+ $normalizeCaret($getChildCaret(paragraph, 'next')),
307
+ );
308
+ }
309
+
310
+ listItemNode.remove();
311
+ }
312
+ listNode.remove();
313
+ }
314
+ }
315
+ }
316
+
317
+ /**
318
+ * Takes the value of a child ListItemNode and makes it the value the ListItemNode
319
+ * should be if it isn't already. Also ensures that checked is undefined if the
320
+ * parent does not have a list type of 'check'.
321
+ * @param list - The list whose children are updated.
322
+ */
323
+ export function updateChildrenListItemValue(list: ListNode): void {
324
+ const isNotChecklist = list.getListType() !== 'check';
325
+ let value = list.getStart();
326
+ for (const child of list.getChildren()) {
327
+ if ($isListItemNode(child)) {
328
+ if (child.getValue() !== value) {
329
+ child.setValue(value);
330
+ }
331
+ if (isNotChecklist && child.getLatest().__checked != null) {
332
+ child.setChecked(undefined);
333
+ }
334
+ if (!$isListNode(child.getFirstChild())) {
335
+ value++;
336
+ }
337
+ }
338
+ }
339
+ }
340
+
341
+ /**
342
+ * Merge the next sibling list if same type.
343
+ * <ul> will merge with <ul>, but NOT <ul> with <ol>.
344
+ * @param list - The list whose next sibling should be potentially merged
345
+ */
346
+ export function mergeNextSiblingListIfSameType(list: ListNode): void {
347
+ const nextSibling = list.getNextSibling();
348
+ if (
349
+ $isListNode(nextSibling) &&
350
+ list.getListType() === nextSibling.getListType()
351
+ ) {
352
+ mergeLists(list, nextSibling);
353
+ }
354
+ }
355
+
356
+ /**
357
+ * Adds an empty ListNode/ListItemNode chain at listItemNode, so as to
358
+ * create an indent effect. Won't indent ListItemNodes that have a ListNode as
359
+ * a child, but does merge sibling ListItemNodes if one has a nested ListNode.
360
+ * @param listItemNode - The ListItemNode to be indented.
361
+ */
362
+ export function $handleIndent(listItemNode: ListItemNode): void {
363
+ // go through each node and decide where to move it.
364
+ const removed = new Set<NodeKey>();
365
+
366
+ if (isNestedListNode(listItemNode) || removed.has(listItemNode.getKey())) {
367
+ return;
368
+ }
369
+
370
+ const parent = listItemNode.getParent();
371
+
372
+ // We can cast both of the below `isNestedListNode` only returns a boolean type instead of a user-defined type guards
373
+ const nextSibling =
374
+ listItemNode.getNextSibling<ListItemNode>() as ListItemNode;
375
+ const previousSibling =
376
+ listItemNode.getPreviousSibling<ListItemNode>() as ListItemNode;
377
+ // if there are nested lists on either side, merge them all together.
378
+
379
+ if (isNestedListNode(nextSibling) && isNestedListNode(previousSibling)) {
380
+ const innerList = previousSibling.getFirstChild();
381
+
382
+ if ($isListNode(innerList)) {
383
+ innerList.append(listItemNode);
384
+ const nextInnerList = nextSibling.getFirstChild();
385
+
386
+ if ($isListNode(nextInnerList)) {
387
+ const children = nextInnerList.getChildren();
388
+ append(innerList, children);
389
+ nextSibling.remove();
390
+ removed.add(nextSibling.getKey());
391
+ }
392
+ }
393
+ } else if (isNestedListNode(nextSibling)) {
394
+ // if the ListItemNode is next to a nested ListNode, merge them
395
+ const innerList = nextSibling.getFirstChild();
396
+
397
+ if ($isListNode(innerList)) {
398
+ const firstChild = innerList.getFirstChild();
399
+
400
+ if (firstChild !== null) {
401
+ firstChild.insertBefore(listItemNode);
402
+ }
403
+ }
404
+ } else if (isNestedListNode(previousSibling)) {
405
+ const innerList = previousSibling.getFirstChild();
406
+
407
+ if ($isListNode(innerList)) {
408
+ innerList.append(listItemNode);
409
+ }
410
+ } else {
411
+ // otherwise, we need to create a new nested ListNode
412
+
413
+ if ($isListNode(parent)) {
414
+ const newListItem = $copyNode(listItemNode);
415
+ const newList = $copyNode(parent);
416
+ newListItem.append(newList);
417
+ newList.append(listItemNode);
418
+
419
+ if (previousSibling) {
420
+ previousSibling.insertAfter(newListItem);
421
+ } else if (nextSibling) {
422
+ nextSibling.insertBefore(newListItem);
423
+ } else {
424
+ parent.append(newListItem);
425
+ }
426
+ }
427
+ }
428
+ }
429
+
430
+ /**
431
+ * Removes an indent by removing an empty ListNode/ListItemNode chain. An indented ListItemNode
432
+ * has a great grandparent node of type ListNode, which is where the ListItemNode will reside
433
+ * within as a child.
434
+ * @param listItemNode - The ListItemNode to remove the indent (outdent).
435
+ */
436
+ export function $handleOutdent(listItemNode: ListItemNode): void {
437
+ // go through each node and decide where to move it.
438
+
439
+ if (isNestedListNode(listItemNode)) {
440
+ return;
441
+ }
442
+ const parentList = listItemNode.getParent();
443
+ const grandparentListItem = parentList ? parentList.getParent() : undefined;
444
+ const greatGrandparentList = grandparentListItem
445
+ ? grandparentListItem.getParent()
446
+ : undefined;
447
+ // If it doesn't have these ancestors, it's not indented.
448
+
449
+ if (
450
+ $isListNode(greatGrandparentList) &&
451
+ $isListItemNode(grandparentListItem) &&
452
+ $isListNode(parentList)
453
+ ) {
454
+ // if it's the first child in it's parent list, insert it into the
455
+ // great grandparent list before the grandparent
456
+ const firstChild = parentList ? parentList.getFirstChild() : undefined;
457
+ const lastChild = parentList ? parentList.getLastChild() : undefined;
458
+
459
+ if (listItemNode.is(firstChild)) {
460
+ grandparentListItem.insertBefore(listItemNode);
461
+
462
+ if (parentList.isEmpty()) {
463
+ grandparentListItem.remove();
464
+ }
465
+ // if it's the last child in it's parent list, insert it into the
466
+ // great grandparent list after the grandparent.
467
+ } else if (listItemNode.is(lastChild)) {
468
+ grandparentListItem.insertAfter(listItemNode);
469
+
470
+ if (parentList.isEmpty()) {
471
+ grandparentListItem.remove();
472
+ }
473
+ } else {
474
+ // otherwise, we need to split the siblings into two new nested lists
475
+ const previousSiblingsListItem = $copyNode(listItemNode);
476
+ const previousSiblingsList = $copyNode(parentList);
477
+ previousSiblingsListItem.append(previousSiblingsList);
478
+ listItemNode
479
+ .getPreviousSiblings()
480
+ .forEach(sibling => previousSiblingsList.append(sibling));
481
+ const nextSiblingsListItem = $copyNode(listItemNode);
482
+ const nextSiblingsList = $copyNode(parentList);
483
+ nextSiblingsListItem.append(nextSiblingsList);
484
+ append(nextSiblingsList, listItemNode.getNextSiblings());
485
+ // put the sibling nested lists on either side of the grandparent list item in the great grandparent.
486
+ grandparentListItem.insertBefore(previousSiblingsListItem);
487
+ grandparentListItem.insertAfter(nextSiblingsListItem);
488
+ // replace the grandparent list item (now between the siblings) with the outdented list item.
489
+ grandparentListItem.replace(listItemNode);
490
+ }
491
+ }
492
+ }
493
+
494
+ /**
495
+ * Attempts to insert a ParagraphNode at selection and selects the new node. The selection must contain a ListItemNode
496
+ * or a node that does not already contain text. If its grandparent is the root/shadow root, it will get the ListNode
497
+ * (which should be the parent node) and insert the ParagraphNode as a sibling to the ListNode. If the ListNode is
498
+ * nested in a ListItemNode instead, it will add the ParagraphNode after the grandparent ListItemNode.
499
+ * Throws an invariant if the selection is not a child of a ListNode.
500
+ * @returns true if a ParagraphNode was inserted successfully, false if there is no selection
501
+ * or the selection does not contain a ListItemNode or the node already holds text.
502
+ */
503
+ export function $handleListInsertParagraph(
504
+ restoreNumbering: boolean = false,
505
+ ): boolean {
506
+ const selection = $getSelection();
507
+
508
+ if (!$isRangeSelection(selection) || !selection.isCollapsed()) {
509
+ return false;
510
+ }
511
+ // Only run this code on empty list items (including whitespace-only)
512
+ const anchor = selection.anchor.getNode();
513
+
514
+ let listItem: ListItemNode | null = null;
515
+
516
+ if ($isListItemNode(anchor) && anchor.getChildrenSize() === 0) {
517
+ // Truly empty list item (element selection)
518
+ listItem = anchor;
519
+ } else if ($isTextNode(anchor)) {
520
+ // Check if the entire list item contains only whitespace text nodes
521
+ const parentListItem = anchor.getParent();
522
+ if (
523
+ $isListItemNode(parentListItem) &&
524
+ parentListItem
525
+ .getChildren()
526
+ .every(node => $isTextNode(node) && node.getTextContent().trim() === '')
527
+ ) {
528
+ listItem = parentListItem;
529
+ }
530
+ }
531
+
532
+ if (listItem === null) {
533
+ return false;
534
+ }
535
+
536
+ const topListNode = $getTopListNode(listItem);
537
+ const parent = listItem.getParent();
538
+
539
+ invariant(
540
+ $isListNode(parent),
541
+ 'A ListItemNode must have a ListNode for a parent.',
542
+ );
543
+
544
+ const grandparent = parent.getParent();
545
+
546
+ let replacementNode: ParagraphNode | ListItemNode;
547
+
548
+ if ($isRootOrShadowRoot(grandparent)) {
549
+ replacementNode = $createParagraphNode();
550
+ topListNode.insertAfter(replacementNode);
551
+ } else if ($isListItemNode(grandparent)) {
552
+ replacementNode = $copyNode(grandparent);
553
+ grandparent.insertAfter(replacementNode);
554
+ } else {
555
+ return false;
556
+ }
557
+ replacementNode
558
+ .setTextStyle(selection.style)
559
+ .setTextFormat(selection.format)
560
+ .select();
561
+
562
+ const nextSiblings = listItem.getNextSiblings();
563
+
564
+ if (nextSiblings.length > 0) {
565
+ const newStart = restoreNumbering ? $getNewListStart(parent, listItem) : 1;
566
+ const newList = $copyNode(parent).setStart(newStart);
567
+
568
+ if ($isListItemNode(replacementNode)) {
569
+ const newListItem = $copyNode(replacementNode);
570
+ newListItem.append(newList);
571
+ replacementNode.insertAfter(newListItem);
572
+ } else {
573
+ replacementNode.insertAfter(newList);
574
+ }
575
+ newList.append(...nextSiblings);
576
+ }
577
+
578
+ // Don't leave hanging nested empty lists
579
+ $removeHighestEmptyListParent(listItem);
580
+
581
+ return true;
582
+ }
package/src/index.ts ADDED
@@ -0,0 +1,98 @@
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 {SerializedListItemNode} from './LexicalListItemNode';
10
+ import type {
11
+ ListNodeTagType,
12
+ ListType,
13
+ SerializedListNode,
14
+ } from './LexicalListNode';
15
+ import type {LexicalEditor} from 'lexical';
16
+
17
+ import {INSERT_CHECK_LIST_COMMAND, registerCheckList} from './checkList';
18
+ import {
19
+ $handleListInsertParagraph,
20
+ $insertList,
21
+ $removeList,
22
+ } from './formatList';
23
+ import {
24
+ $createListItemNode,
25
+ $isListItemNode,
26
+ ListItemNode,
27
+ } from './LexicalListItemNode';
28
+ import {$createListNode, $isListNode, ListNode} from './LexicalListNode';
29
+ import {$getListDepth} from './utils';
30
+
31
+ export {
32
+ type CheckListConfig,
33
+ CheckListExtension,
34
+ type ListConfig,
35
+ ListExtension,
36
+ } from './LexicalListExtension';
37
+ export {
38
+ ListImportExtension,
39
+ ListImportRules,
40
+ ListSchema,
41
+ } from './ListImportExtension';
42
+ export {
43
+ INSERT_ORDERED_LIST_COMMAND,
44
+ INSERT_UNORDERED_LIST_COMMAND,
45
+ registerList,
46
+ type RegisterListOptions,
47
+ registerListStrictIndentTransform,
48
+ REMOVE_LIST_COMMAND,
49
+ UPDATE_LIST_START_COMMAND,
50
+ } from './registerList';
51
+
52
+ export {
53
+ $createListItemNode,
54
+ $createListNode,
55
+ $getListDepth,
56
+ $handleListInsertParagraph,
57
+ $insertList,
58
+ $isListItemNode,
59
+ $isListNode,
60
+ $removeList,
61
+ INSERT_CHECK_LIST_COMMAND,
62
+ ListItemNode,
63
+ ListNode,
64
+ ListNodeTagType,
65
+ ListType,
66
+ registerCheckList,
67
+ SerializedListItemNode,
68
+ SerializedListNode,
69
+ };
70
+
71
+ /**
72
+ * @deprecated use {@link $insertList} from an update or command listener.
73
+ *
74
+ * Inserts a new ListNode. If the selection's anchor node is an empty ListItemNode and is a child of
75
+ * the root/shadow root, it will replace the ListItemNode with a ListNode and the old ListItemNode.
76
+ * Otherwise it will replace its parent with a new ListNode and re-insert the ListItemNode and any previous children.
77
+ * If the selection's anchor node is not an empty ListItemNode, it will add a new ListNode or merge an existing ListNode,
78
+ * unless the the node is a leaf node, in which case it will attempt to find a ListNode up the branch and replace it with
79
+ * a new ListNode, or create a new ListNode at the nearest root/shadow root.
80
+ * @param editor - The lexical editor.
81
+ * @param listType - The type of list, "number" | "bullet" | "check".
82
+ */
83
+ export function insertList(editor: LexicalEditor, listType: ListType): void {
84
+ editor.update(() => $insertList(listType));
85
+ }
86
+
87
+ /**
88
+ * @deprecated use {@link $removeList} from an update or command listener.
89
+ *
90
+ * Searches for the nearest ancestral ListNode and removes it. If selection is an empty ListItemNode
91
+ * it will remove the whole list, including the ListItemNode. For each ListItemNode in the ListNode,
92
+ * removeList will also generate new ParagraphNodes in the removed ListNode's place. Any child node
93
+ * inside a ListItemNode will be appended to the new ParagraphNodes.
94
+ * @param editor - The lexical editor.
95
+ */
96
+ export function removeList(editor: LexicalEditor): void {
97
+ editor.update(() => $removeList());
98
+ }