@lexical/list 0.8.1 → 0.9.1

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.
@@ -16,60 +16,105 @@ var utils = require('@lexical/utils');
16
16
  * LICENSE file in the root directory of this source tree.
17
17
  *
18
18
  */
19
+ /**
20
+ * Checks the depth of listNode from the root node.
21
+ * @param listNode - The ListNode to be checked.
22
+ * @returns The depth of the ListNode.
23
+ */
24
+
19
25
  function $getListDepth(listNode) {
20
26
  let depth = 1;
21
27
  let parent = listNode.getParent();
28
+
22
29
  while (parent != null) {
23
30
  if ($isListItemNode(parent)) {
24
31
  const parentList = parent.getParent();
32
+
25
33
  if ($isListNode(parentList)) {
26
34
  depth++;
27
35
  parent = parentList.getParent();
28
36
  continue;
29
37
  }
38
+
30
39
  {
31
40
  throw Error(`A ListItemNode must have a ListNode for a parent.`);
32
41
  }
33
42
  }
43
+
34
44
  return depth;
35
45
  }
46
+
36
47
  return depth;
37
48
  }
49
+ /**
50
+ * Finds the nearest ancestral ListNode and returns it, throws an invariant if listItem is not a ListItemNode.
51
+ * @param listItem - The node to be checked.
52
+ * @returns The ListNode found.
53
+ */
54
+
38
55
  function $getTopListNode(listItem) {
39
56
  let list = listItem.getParent();
57
+
40
58
  if (!$isListNode(list)) {
41
59
  {
42
60
  throw Error(`A ListItemNode must have a ListNode for a parent.`);
43
61
  }
44
62
  }
63
+
45
64
  let parent = list;
65
+
46
66
  while (parent !== null) {
47
67
  parent = parent.getParent();
68
+
48
69
  if ($isListNode(parent)) {
49
70
  list = parent;
50
71
  }
51
72
  }
73
+
52
74
  return list;
53
75
  }
54
-
76
+ /**
77
+ * A recursive Depth-First Search (Postorder Traversal) that finds all of a node's children
78
+ * that are of type ListItemNode and returns them in an array.
79
+ * @param node - The ListNode to start the search.
80
+ * @returns An array containing all nodes of type ListItemNode found.
81
+ */
55
82
  // This should probably be $getAllChildrenOfType
83
+
56
84
  function $getAllListItems(node) {
57
85
  let listItemNodes = [];
58
86
  const listChildren = node.getChildren().filter($isListItemNode);
87
+
59
88
  for (let i = 0; i < listChildren.length; i++) {
60
89
  const listItemNode = listChildren[i];
61
90
  const firstChild = listItemNode.getFirstChild();
91
+
62
92
  if ($isListNode(firstChild)) {
63
93
  listItemNodes = listItemNodes.concat($getAllListItems(firstChild));
64
94
  } else {
65
95
  listItemNodes.push(listItemNode);
66
96
  }
67
97
  }
98
+
68
99
  return listItemNodes;
69
100
  }
101
+ /**
102
+ * Checks to see if the passed node is a ListItemNode and has a ListNode as a child.
103
+ * @param node - The node to be checked.
104
+ * @returns true if the node is a ListItemNode and has a ListNode child, false otherwise.
105
+ */
106
+
70
107
  function isNestedListNode(node) {
71
108
  return $isListItemNode(node) && $isListNode(node.getFirstChild());
72
109
  }
110
+ /**
111
+ * Takes a deeply nested ListNode or ListItemNode and traverses up the branch to delete the first
112
+ * ancestral ListNode (which could be the root ListNode) or ListItemNode with siblings, essentially
113
+ * bringing the deeply nested node up the branch once. Would remove sublist if it has siblings.
114
+ * Should not break ListItem -> List -> ListItem chain as empty List/ItemNodes should be removed on .remove().
115
+ * @param sublist - The nested ListNode or ListItemNode to be brought up the branch.
116
+ */
117
+
73
118
  function $removeHighestEmptyListParent(sublist) {
74
119
  // Nodes may be repeatedly indented, to create deeply nested lists that each
75
120
  // contain just one bullet.
@@ -78,15 +123,25 @@ function $removeHighestEmptyListParent(sublist) {
78
123
  // (e.g. is actually part of the list contents) and delete that, or delete
79
124
  // the root of the list (if no list nodes have siblings.)
80
125
  let emptyListPtr = sublist;
126
+
81
127
  while (emptyListPtr.getNextSibling() == null && emptyListPtr.getPreviousSibling() == null) {
82
128
  const parent = emptyListPtr.getParent();
129
+
83
130
  if (parent == null || !($isListItemNode(emptyListPtr) || $isListNode(emptyListPtr))) {
84
131
  break;
85
132
  }
133
+
86
134
  emptyListPtr = parent;
87
135
  }
136
+
88
137
  emptyListPtr.remove();
89
138
  }
139
+ /**
140
+ * Wraps a node into a ListItemNode.
141
+ * @param node - The node to be wrapped into a ListItemNode
142
+ * @returns The ListItemNode which the passed node is wrapped in.
143
+ */
144
+
90
145
  function wrapInListItem(node) {
91
146
  const listItemWrapper = $createListItemNode();
92
147
  return listItemWrapper.append(node);
@@ -99,12 +154,15 @@ function wrapInListItem(node) {
99
154
  * LICENSE file in the root directory of this source tree.
100
155
  *
101
156
  */
157
+
102
158
  function $isSelectingEmptyListItem(anchorNode, nodes) {
103
159
  return $isListItemNode(anchorNode) && (nodes.length === 0 || nodes.length === 1 && anchorNode.is(nodes[0]) && anchorNode.getChildrenSize() === 0);
104
160
  }
161
+
105
162
  function $getListItemValue(listItem) {
106
163
  const list = listItem.getParent();
107
164
  let value = 1;
165
+
108
166
  if (list != null) {
109
167
  if (!$isListNode(list)) {
110
168
  {
@@ -114,51 +172,78 @@ function $getListItemValue(listItem) {
114
172
  value = list.getStart();
115
173
  }
116
174
  }
175
+
117
176
  const siblings = listItem.getPreviousSiblings();
177
+
118
178
  for (let i = 0; i < siblings.length; i++) {
119
179
  const sibling = siblings[i];
180
+
120
181
  if ($isListItemNode(sibling) && !$isListNode(sibling.getFirstChild())) {
121
182
  value++;
122
183
  }
123
184
  }
185
+
124
186
  return value;
125
187
  }
188
+ /**
189
+ * Inserts a new ListNode. If the selection's anchor node is an empty ListItemNode and is a child of
190
+ * the root/shadow root, it will replace the ListItemNode with a ListNode and the old ListItemNode.
191
+ * Otherwise it will replace its parent with a new ListNode and re-insert the ListItemNode and any previous children.
192
+ * If the selection's anchor node is not an empty ListItemNode, it will add a new ListNode or merge an existing ListNode,
193
+ * 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
194
+ * a new ListNode, or create a new ListNode at the nearest root/shadow root.
195
+ * @param editor - The lexical editor.
196
+ * @param listType - The type of list, "number" | "bullet" | "check".
197
+ */
198
+
199
+
126
200
  function insertList(editor, listType) {
127
201
  editor.update(() => {
128
202
  const selection = lexical.$getSelection();
203
+
129
204
  if (lexical.$isRangeSelection(selection) || lexical.DEPRECATED_$isGridSelection(selection)) {
130
205
  const nodes = selection.getNodes();
131
206
  const anchor = selection.anchor;
132
207
  const anchorNode = anchor.getNode();
133
208
  const anchorNodeParent = anchorNode.getParent();
209
+
134
210
  if ($isSelectingEmptyListItem(anchorNode, nodes)) {
135
211
  const list = $createListNode(listType);
212
+
136
213
  if (lexical.$isRootOrShadowRoot(anchorNodeParent)) {
137
214
  anchorNode.replace(list);
138
215
  const listItem = $createListItemNode();
216
+
139
217
  if (lexical.$isElementNode(anchorNode)) {
140
218
  listItem.setFormat(anchorNode.getFormatType());
141
219
  listItem.setIndent(anchorNode.getIndent());
142
220
  }
221
+
143
222
  list.append(listItem);
144
223
  } else if ($isListItemNode(anchorNode)) {
145
224
  const parent = anchorNode.getParentOrThrow();
146
225
  append(list, parent.getChildren());
147
226
  parent.replace(list);
148
227
  }
228
+
149
229
  return;
150
230
  } else {
151
231
  const handled = new Set();
232
+
152
233
  for (let i = 0; i < nodes.length; i++) {
153
234
  const node = nodes[i];
235
+
154
236
  if (lexical.$isElementNode(node) && node.isEmpty() && !handled.has(node.getKey())) {
155
237
  createListOrMerge(node, listType);
156
238
  continue;
157
239
  }
240
+
158
241
  if (lexical.$isLeafNode(node)) {
159
242
  let parent = node.getParent();
243
+
160
244
  while (parent != null) {
161
245
  const parentKey = parent.getKey();
246
+
162
247
  if ($isListNode(parent)) {
163
248
  if (!handled.has(parentKey)) {
164
249
  const newListNode = $createListNode(listType);
@@ -167,14 +252,17 @@ function insertList(editor, listType) {
167
252
  updateChildrenListItemValue(newListNode);
168
253
  handled.add(parentKey);
169
254
  }
255
+
170
256
  break;
171
257
  } else {
172
258
  const nextParent = parent.getParent();
259
+
173
260
  if (lexical.$isRootOrShadowRoot(nextParent) && !handled.has(parentKey)) {
174
261
  handled.add(parentKey);
175
262
  createListOrMerge(parent, listType);
176
263
  break;
177
264
  }
265
+
178
266
  parent = nextParent;
179
267
  }
180
268
  }
@@ -184,28 +272,32 @@ function insertList(editor, listType) {
184
272
  }
185
273
  });
186
274
  }
275
+
187
276
  function append(node, nodesToAppend) {
188
277
  node.splice(node.getChildrenSize(), 0, nodesToAppend);
189
278
  }
279
+
190
280
  function createListOrMerge(node, listType) {
191
281
  if ($isListNode(node)) {
192
282
  return node;
193
283
  }
284
+
194
285
  const previousSibling = node.getPreviousSibling();
195
286
  const nextSibling = node.getNextSibling();
196
287
  const listItem = $createListItemNode();
197
288
  listItem.setFormat(node.getFormatType());
198
289
  listItem.setIndent(node.getIndent());
199
290
  append(listItem, node.getChildren());
291
+
200
292
  if ($isListNode(previousSibling) && listType === previousSibling.getListType()) {
201
293
  previousSibling.append(listItem);
202
- node.remove();
203
- // if the same type of list is on both sides, merge them.
294
+ node.remove(); // if the same type of list is on both sides, merge them.
204
295
 
205
296
  if ($isListNode(nextSibling) && listType === nextSibling.getListType()) {
206
297
  append(previousSibling, nextSibling.getChildren());
207
298
  nextSibling.remove();
208
299
  }
300
+
209
301
  return previousSibling;
210
302
  } else if ($isListNode(nextSibling) && listType === nextSibling.getListType()) {
211
303
  nextSibling.getFirstChildOrThrow().insertBefore(listItem);
@@ -219,62 +311,115 @@ function createListOrMerge(node, listType) {
219
311
  return list;
220
312
  }
221
313
  }
314
+ /**
315
+ * A recursive function that goes through each list and their children, including nested lists,
316
+ * appending list2 children after list1 children and updating ListItemNode values.
317
+ * @param list1 - The first list to be merged.
318
+ * @param list2 - The second list to be merged.
319
+ */
320
+
321
+
322
+ function mergeLists(list1, list2) {
323
+ const listItem1 = list1.getLastChild();
324
+ const listItem2 = list2.getFirstChild();
325
+
326
+ if (listItem1 && listItem2 && isNestedListNode(listItem1) && isNestedListNode(listItem2)) {
327
+ mergeLists(listItem1.getFirstChild(), listItem2.getFirstChild());
328
+ listItem2.remove();
329
+ }
330
+
331
+ const toMerge = list2.getChildren();
332
+
333
+ if (toMerge.length > 0) {
334
+ list1.append(...toMerge);
335
+ updateChildrenListItemValue(list1);
336
+ }
337
+
338
+ list2.remove();
339
+ }
340
+ /**
341
+ * Searches for the nearest ancestral ListNode and removes it. If selection is an empty ListItemNode
342
+ * it will remove the whole list, including the ListItemNode. For each ListItemNode in the ListNode,
343
+ * removeList will also generate new ParagraphNodes in the removed ListNode's place. Any child node
344
+ * inside a ListItemNode will be appended to the new ParagraphNodes.
345
+ * @param editor - The lexical editor.
346
+ */
347
+
222
348
  function removeList(editor) {
223
349
  editor.update(() => {
224
350
  const selection = lexical.$getSelection();
351
+
225
352
  if (lexical.$isRangeSelection(selection)) {
226
353
  const listNodes = new Set();
227
354
  const nodes = selection.getNodes();
228
355
  const anchorNode = selection.anchor.getNode();
356
+
229
357
  if ($isSelectingEmptyListItem(anchorNode, nodes)) {
230
358
  listNodes.add($getTopListNode(anchorNode));
231
359
  } else {
232
360
  for (let i = 0; i < nodes.length; i++) {
233
361
  const node = nodes[i];
362
+
234
363
  if (lexical.$isLeafNode(node)) {
235
364
  const listItemNode = utils.$getNearestNodeOfType(node, ListItemNode);
365
+
236
366
  if (listItemNode != null) {
237
367
  listNodes.add($getTopListNode(listItemNode));
238
368
  }
239
369
  }
240
370
  }
241
371
  }
372
+
242
373
  for (const listNode of listNodes) {
243
374
  let insertionPoint = listNode;
244
375
  const listItems = $getAllListItems(listNode);
376
+
245
377
  for (const listItemNode of listItems) {
246
378
  const paragraph = lexical.$createParagraphNode();
247
379
  append(paragraph, listItemNode.getChildren());
248
380
  insertionPoint.insertAfter(paragraph);
249
- insertionPoint = paragraph;
250
-
251
- // When the anchor and focus fall on the textNode
381
+ insertionPoint = paragraph; // When the anchor and focus fall on the textNode
252
382
  // we don't have to change the selection because the textNode will be appended to
253
383
  // the newly generated paragraph.
254
384
  // When selection is in empty nested list item, selection is actually on the listItemNode.
255
385
  // When the corresponding listItemNode is deleted and replaced by the newly generated paragraph
256
386
  // we should manually set the selection's focus and anchor to the newly generated paragraph.
387
+
257
388
  if (listItemNode.__key === selection.anchor.key) {
258
389
  selection.anchor.set(paragraph.getKey(), 0, 'element');
259
390
  }
391
+
260
392
  if (listItemNode.__key === selection.focus.key) {
261
393
  selection.focus.set(paragraph.getKey(), 0, 'element');
262
394
  }
395
+
263
396
  listItemNode.remove();
264
397
  }
398
+
265
399
  listNode.remove();
266
400
  }
267
401
  }
268
402
  });
269
403
  }
404
+ /**
405
+ * Takes the value of a child ListItemNode and makes it the value the ListItemNode
406
+ * should be if it isn't already. If only certain children should be updated, they
407
+ * can be passed optionally in an array.
408
+ * @param list - The list whose children are updated.
409
+ * @param children - An array of the children to be updated.
410
+ */
411
+
270
412
  function updateChildrenListItemValue(list, children) {
271
413
  const childrenOrExisting = children || list.getChildren();
414
+
272
415
  if (childrenOrExisting !== undefined) {
273
416
  for (let i = 0; i < childrenOrExisting.length; i++) {
274
417
  const child = childrenOrExisting[i];
418
+
275
419
  if ($isListItemNode(child)) {
276
420
  const prevValue = child.getValue();
277
421
  const nextValue = $getListItemValue(child);
422
+
278
423
  if (prevValue !== nextValue) {
279
424
  child.setValue(nextValue);
280
425
  }
@@ -282,56 +427,70 @@ function updateChildrenListItemValue(list, children) {
282
427
  }
283
428
  }
284
429
  }
430
+ /**
431
+ * Adds an empty ListNode/ListItemNode chain at listItemNode, so as to
432
+ * create an indent effect. Won't indent ListItemNodes that have a ListNode as
433
+ * a child, but does merge sibling ListItemNodes if one has a nested ListNode.
434
+ * @param listItemNode - The ListItemNode to be indented.
435
+ */
436
+
285
437
  function $handleIndent(listItemNode) {
286
438
  // go through each node and decide where to move it.
287
439
  const removed = new Set();
440
+
288
441
  if (isNestedListNode(listItemNode) || removed.has(listItemNode.getKey())) {
289
442
  return;
290
443
  }
291
- const parent = listItemNode.getParent();
292
444
 
293
- // We can cast both of the below `isNestedListNode` only returns a boolean type instead of a user-defined type guards
445
+ const parent = listItemNode.getParent(); // We can cast both of the below `isNestedListNode` only returns a boolean type instead of a user-defined type guards
446
+
294
447
  const nextSibling = listItemNode.getNextSibling();
295
- const previousSibling = listItemNode.getPreviousSibling();
296
- // if there are nested lists on either side, merge them all together.
448
+ const previousSibling = listItemNode.getPreviousSibling(); // if there are nested lists on either side, merge them all together.
297
449
 
298
450
  if (isNestedListNode(nextSibling) && isNestedListNode(previousSibling)) {
299
451
  const innerList = previousSibling.getFirstChild();
452
+
300
453
  if ($isListNode(innerList)) {
301
454
  innerList.append(listItemNode);
302
455
  const nextInnerList = nextSibling.getFirstChild();
456
+
303
457
  if ($isListNode(nextInnerList)) {
304
458
  const children = nextInnerList.getChildren();
305
459
  append(innerList, children);
306
460
  nextSibling.remove();
307
461
  removed.add(nextSibling.getKey());
308
462
  }
463
+
309
464
  updateChildrenListItemValue(innerList);
310
465
  }
311
466
  } else if (isNestedListNode(nextSibling)) {
312
467
  // if the ListItemNode is next to a nested ListNode, merge them
313
468
  const innerList = nextSibling.getFirstChild();
469
+
314
470
  if ($isListNode(innerList)) {
315
471
  const firstChild = innerList.getFirstChild();
472
+
316
473
  if (firstChild !== null) {
317
474
  firstChild.insertBefore(listItemNode);
318
475
  }
476
+
319
477
  updateChildrenListItemValue(innerList);
320
478
  }
321
479
  } else if (isNestedListNode(previousSibling)) {
322
480
  const innerList = previousSibling.getFirstChild();
481
+
323
482
  if ($isListNode(innerList)) {
324
483
  innerList.append(listItemNode);
325
484
  updateChildrenListItemValue(innerList);
326
485
  }
327
486
  } else {
328
487
  // otherwise, we need to create a new nested ListNode
329
-
330
488
  if ($isListNode(parent)) {
331
489
  const newListItem = $createListItemNode();
332
490
  const newList = $createListNode(parent.getListType());
333
491
  newListItem.append(newList);
334
492
  newList.append(listItemNode);
493
+
335
494
  if (previousSibling) {
336
495
  previousSibling.insertAfter(newListItem);
337
496
  } else if (nextSibling) {
@@ -339,37 +498,49 @@ function $handleIndent(listItemNode) {
339
498
  } else {
340
499
  parent.append(newListItem);
341
500
  }
501
+
502
+ updateChildrenListItemValue(newList);
342
503
  }
343
504
  }
505
+
344
506
  if ($isListNode(parent)) {
345
507
  updateChildrenListItemValue(parent);
346
508
  }
347
509
  }
510
+ /**
511
+ * Removes an indent by removing an empty ListNode/ListItemNode chain. An indented ListItemNode
512
+ * has a great grandparent node of type ListNode, which is where the ListItemNode will reside
513
+ * within as a child.
514
+ * @param listItemNode - The ListItemNode to remove the indent (outdent).
515
+ */
516
+
348
517
  function $handleOutdent(listItemNode) {
349
518
  // go through each node and decide where to move it.
350
-
351
519
  if (isNestedListNode(listItemNode)) {
352
520
  return;
353
521
  }
522
+
354
523
  const parentList = listItemNode.getParent();
355
524
  const grandparentListItem = parentList ? parentList.getParent() : undefined;
356
- const greatGrandparentList = grandparentListItem ? grandparentListItem.getParent() : undefined;
357
- // If it doesn't have these ancestors, it's not indented.
525
+ const greatGrandparentList = grandparentListItem ? grandparentListItem.getParent() : undefined; // If it doesn't have these ancestors, it's not indented.
358
526
 
359
527
  if ($isListNode(greatGrandparentList) && $isListItemNode(grandparentListItem) && $isListNode(parentList)) {
360
528
  // if it's the first child in it's parent list, insert it into the
361
529
  // great grandparent list before the grandparent
362
530
  const firstChild = parentList ? parentList.getFirstChild() : undefined;
363
531
  const lastChild = parentList ? parentList.getLastChild() : undefined;
532
+
364
533
  if (listItemNode.is(firstChild)) {
365
534
  grandparentListItem.insertBefore(listItemNode);
535
+
366
536
  if (parentList.isEmpty()) {
367
537
  grandparentListItem.remove();
368
- }
369
- // if it's the last child in it's parent list, insert it into the
538
+ } // if it's the last child in it's parent list, insert it into the
370
539
  // great grandparent list after the grandparent.
540
+
371
541
  } else if (listItemNode.is(lastChild)) {
372
542
  grandparentListItem.insertAfter(listItemNode);
543
+
373
544
  if (parentList.isEmpty()) {
374
545
  grandparentListItem.remove();
375
546
  }
@@ -383,34 +554,52 @@ function $handleOutdent(listItemNode) {
383
554
  const nextSiblingsListItem = $createListItemNode();
384
555
  const nextSiblingsList = $createListNode(listType);
385
556
  nextSiblingsListItem.append(nextSiblingsList);
386
- append(nextSiblingsList, listItemNode.getNextSiblings());
387
- // put the sibling nested lists on either side of the grandparent list item in the great grandparent.
557
+ append(nextSiblingsList, listItemNode.getNextSiblings()); // put the sibling nested lists on either side of the grandparent list item in the great grandparent.
558
+
388
559
  grandparentListItem.insertBefore(previousSiblingsListItem);
389
- grandparentListItem.insertAfter(nextSiblingsListItem);
390
- // replace the grandparent list item (now between the siblings) with the outdented list item.
560
+ grandparentListItem.insertAfter(nextSiblingsListItem); // replace the grandparent list item (now between the siblings) with the outdented list item.
561
+
391
562
  grandparentListItem.replace(listItemNode);
392
563
  }
564
+
393
565
  updateChildrenListItemValue(parentList);
394
566
  updateChildrenListItemValue(greatGrandparentList);
395
567
  }
396
568
  }
569
+ /**
570
+ * Attempts to insert a ParagraphNode at selection and selects the new node. The selection must contain a ListItemNode
571
+ * or a node that does not already contain text. If its grandparent is the root/shadow root, it will get the ListNode
572
+ * (which should be the parent node) and insert the ParagraphNode as a sibling to the ListNode. If the ListNode is
573
+ * nested in a ListItemNode instead, it will add the ParagraphNode after the grandparent ListItemNode.
574
+ * Throws an invariant if the selection is not a child of a ListNode.
575
+ * @returns true if a ParagraphNode was inserted succesfully, false if there is no selection
576
+ * or the selection does not contain a ListItemNode or the node already holds text.
577
+ */
578
+
397
579
  function $handleListInsertParagraph() {
398
580
  const selection = lexical.$getSelection();
581
+
399
582
  if (!lexical.$isRangeSelection(selection) || !selection.isCollapsed()) {
400
583
  return false;
401
- }
402
- // Only run this code on empty list items
584
+ } // Only run this code on empty list items
585
+
586
+
403
587
  const anchor = selection.anchor.getNode();
588
+
404
589
  if (!$isListItemNode(anchor) || anchor.getTextContent() !== '') {
405
590
  return false;
406
591
  }
592
+
407
593
  const topListNode = $getTopListNode(anchor);
408
594
  const parent = anchor.getParent();
595
+
409
596
  if (!$isListNode(parent)) {
410
597
  throw Error(`A ListItemNode must have a ListNode for a parent.`);
411
598
  }
599
+
412
600
  const grandparent = parent.getParent();
413
601
  let replacementNode;
602
+
414
603
  if (lexical.$isRootOrShadowRoot(grandparent)) {
415
604
  replacementNode = lexical.$createParagraphNode();
416
605
  topListNode.insertAfter(replacementNode);
@@ -420,10 +609,13 @@ function $handleListInsertParagraph() {
420
609
  } else {
421
610
  return false;
422
611
  }
612
+
423
613
  replacementNode.select();
424
614
  const nextSiblings = anchor.getNextSiblings();
615
+
425
616
  if (nextSiblings.length > 0) {
426
617
  const newList = $createListNode(parent.getListType());
618
+
427
619
  if (lexical.$isParagraphNode(replacementNode)) {
428
620
  replacementNode.insertAfter(newList);
429
621
  } else {
@@ -431,13 +623,14 @@ function $handleListInsertParagraph() {
431
623
  newListItem.append(newList);
432
624
  replacementNode.insertAfter(newListItem);
433
625
  }
626
+
434
627
  nextSiblings.forEach(sibling => {
435
628
  sibling.remove();
436
629
  newList.append(sibling);
437
630
  });
438
- }
631
+ } // Don't leave hanging nested empty lists
632
+
439
633
 
440
- // Don't leave hanging nested empty lists
441
634
  $removeHighestEmptyListParent(anchor);
442
635
  return true;
443
636
  }
@@ -449,45 +642,66 @@ function $handleListInsertParagraph() {
449
642
  * LICENSE file in the root directory of this source tree.
450
643
  *
451
644
  */
645
+
452
646
  /** @noInheritDoc */
453
647
  class ListItemNode extends lexical.ElementNode {
454
648
  /** @internal */
455
649
 
456
650
  /** @internal */
457
-
458
651
  static getType() {
459
652
  return 'listitem';
460
653
  }
654
+
461
655
  static clone(node) {
462
656
  return new ListItemNode(node.__value, node.__checked, node.__key);
463
657
  }
658
+
464
659
  constructor(value, checked, key) {
465
660
  super(key);
466
661
  this.__value = value === undefined ? 1 : value;
467
662
  this.__checked = checked;
468
663
  }
664
+
469
665
  createDOM(config) {
470
666
  const element = document.createElement('li');
471
667
  const parent = this.getParent();
472
- if ($isListNode(parent)) {
473
- updateChildrenListItemValue(parent);
474
- updateListItemChecked(element, this, null, parent);
668
+
669
+ if ($isListNode(parent) && parent.getListType() === 'check') {
670
+ updateListItemChecked(element, this, null);
475
671
  }
672
+
476
673
  element.value = this.__value;
477
674
  $setListItemThemeClassNames(element, config.theme, this);
478
675
  return element;
479
676
  }
677
+
480
678
  updateDOM(prevNode, dom, config) {
481
679
  const parent = this.getParent();
482
- if ($isListNode(parent)) {
483
- updateChildrenListItemValue(parent);
484
- updateListItemChecked(dom, this, prevNode, parent);
485
- }
486
- // @ts-expect-error - this is always HTMLListItemElement
680
+
681
+ if ($isListNode(parent) && parent.getListType() === 'check') {
682
+ updateListItemChecked(dom, this, prevNode);
683
+ } // @ts-expect-error - this is always HTMLListItemElement
684
+
685
+
487
686
  dom.value = this.__value;
488
687
  $setListItemThemeClassNames(dom, config.theme, this);
489
688
  return false;
490
689
  }
690
+
691
+ static transform() {
692
+ return node => {
693
+ const parent = node.getParent();
694
+
695
+ if ($isListNode(parent)) {
696
+ updateChildrenListItemValue(parent);
697
+
698
+ if (parent.getListType() !== 'check' && node.getChecked() != null) {
699
+ node.setChecked(undefined);
700
+ }
701
+ }
702
+ };
703
+ }
704
+
491
705
  static importDOM() {
492
706
  return {
493
707
  li: node => ({
@@ -496,25 +710,30 @@ class ListItemNode extends lexical.ElementNode {
496
710
  })
497
711
  };
498
712
  }
713
+
499
714
  static importJSON(serializedNode) {
500
715
  const node = new ListItemNode(serializedNode.value, serializedNode.checked);
501
- node.setFormat(serializedNode.format);
502
- node.setIndent(serializedNode.indent);
716
+ node.setFormat(serializedNode.format); //setIndent is overridden here with logic that doesn't need
717
+ //to run during deserialization
718
+
719
+ node.getWritable().__indent = serializedNode.indent;
503
720
  node.setDirection(serializedNode.direction);
504
721
  return node;
505
722
  }
723
+
506
724
  exportJSON() {
507
- return {
508
- ...super.exportJSON(),
725
+ return { ...super.exportJSON(),
509
726
  checked: this.getChecked(),
510
727
  type: 'listitem',
511
728
  value: this.getValue(),
512
729
  version: 1
513
730
  };
514
731
  }
732
+
515
733
  append(...nodes) {
516
734
  for (let i = 0; i < nodes.length; i++) {
517
735
  const node = nodes[i];
736
+
518
737
  if (lexical.$isElementNode(node) && this.canMergeWith(node)) {
519
738
  const children = node.getChildren();
520
739
  this.append(...children);
@@ -523,98 +742,124 @@ class ListItemNode extends lexical.ElementNode {
523
742
  super.append(node);
524
743
  }
525
744
  }
745
+
526
746
  return this;
527
747
  }
748
+
528
749
  replace(replaceWithNode, includeChildren) {
529
750
  if ($isListItemNode(replaceWithNode)) {
530
751
  return super.replace(replaceWithNode);
531
752
  }
753
+
754
+ this.setIndent(0);
532
755
  const list = this.getParentOrThrow();
533
- if ($isListNode(list)) {
534
- const childrenKeys = list.getChildrenKeys();
535
- const childrenLength = childrenKeys.length;
536
- const index = childrenKeys.indexOf(this.__key);
537
- if (index === 0) {
538
- list.insertBefore(replaceWithNode);
539
- } else if (index === childrenLength - 1) {
540
- list.insertAfter(replaceWithNode);
541
- } else {
542
- // Split the list
543
- const newList = $createListNode(list.getListType());
544
- const children = list.getChildren();
545
- for (let i = index + 1; i < childrenLength; i++) {
546
- const child = children[i];
547
- newList.append(child);
548
- }
549
- list.insertAfter(replaceWithNode);
550
- replaceWithNode.insertAfter(newList);
551
- }
552
- if (includeChildren) {
553
- this.getChildren().forEach(child => {
554
- replaceWithNode.append(child);
555
- });
556
- }
557
- this.remove();
558
- if (childrenLength === 1) {
559
- list.remove();
756
+ if (!$isListNode(list)) return replaceWithNode;
757
+
758
+ if (list.__first === this.getKey()) {
759
+ list.insertBefore(replaceWithNode);
760
+ } else if (list.__last === this.getKey()) {
761
+ list.insertAfter(replaceWithNode);
762
+ } else {
763
+ // Split the list
764
+ const newList = $createListNode(list.getListType());
765
+ let nextSibling = this.getNextSibling();
766
+
767
+ while (nextSibling) {
768
+ const nodeToAppend = nextSibling;
769
+ nextSibling = nextSibling.getNextSibling();
770
+ newList.append(nodeToAppend);
560
771
  }
772
+
773
+ list.insertAfter(replaceWithNode);
774
+ replaceWithNode.insertAfter(newList);
775
+ }
776
+
777
+ if (includeChildren) {
778
+ this.getChildren().forEach(child => {
779
+ replaceWithNode.append(child);
780
+ });
781
+ }
782
+
783
+ this.remove();
784
+
785
+ if (list.getChildrenSize() === 0) {
786
+ list.remove();
561
787
  }
788
+
562
789
  return replaceWithNode;
563
790
  }
791
+
564
792
  insertAfter(node, restoreSelection = true) {
565
793
  const listNode = this.getParentOrThrow();
794
+
566
795
  if (!$isListNode(listNode)) {
567
796
  {
568
797
  throw Error(`insertAfter: list node is not parent of list item node`);
569
798
  }
570
799
  }
800
+
571
801
  const siblings = this.getNextSiblings();
802
+
572
803
  if ($isListItemNode(node)) {
573
804
  const after = super.insertAfter(node, restoreSelection);
574
805
  const afterListNode = node.getParentOrThrow();
806
+
575
807
  if ($isListNode(afterListNode)) {
576
808
  updateChildrenListItemValue(afterListNode);
577
809
  }
810
+
578
811
  return after;
579
- }
812
+ } // Attempt to merge if the list is of the same type.
580
813
 
581
- // Attempt to merge if the list is of the same type.
582
814
 
583
815
  if ($isListNode(node) && node.getListType() === listNode.getListType()) {
584
816
  let child = node;
585
817
  const children = node.getChildren();
818
+
586
819
  for (let i = children.length - 1; i >= 0; i--) {
587
820
  child = children[i];
588
821
  this.insertAfter(child, restoreSelection);
589
822
  }
590
- return child;
591
- }
592
823
 
593
- // Otherwise, split the list
824
+ return child;
825
+ } // Otherwise, split the list
594
826
  // Split the lists and insert the node in between them
827
+
828
+
595
829
  listNode.insertAfter(node, restoreSelection);
830
+
596
831
  if (siblings.length !== 0) {
597
832
  const newListNode = $createListNode(listNode.getListType());
598
833
  siblings.forEach(sibling => newListNode.append(sibling));
599
834
  node.insertAfter(newListNode, restoreSelection);
600
835
  }
836
+
601
837
  return node;
602
838
  }
839
+
603
840
  remove(preserveEmptyParent) {
841
+ const prevSibling = this.getPreviousSibling();
604
842
  const nextSibling = this.getNextSibling();
605
843
  super.remove(preserveEmptyParent);
606
- if (nextSibling !== null) {
844
+
845
+ if (prevSibling && nextSibling && isNestedListNode(prevSibling) && isNestedListNode(nextSibling)) {
846
+ mergeLists(prevSibling.getFirstChild(), nextSibling.getFirstChild());
847
+ nextSibling.remove();
848
+ } else if (nextSibling) {
607
849
  const parent = nextSibling.getParent();
850
+
608
851
  if ($isListNode(parent)) {
609
852
  updateChildrenListItemValue(parent);
610
853
  }
611
854
  }
612
855
  }
856
+
613
857
  insertNewAfter(_, restoreSelection = true) {
614
858
  const newElement = $createListItemNode(this.__checked == null ? undefined : false);
615
859
  this.insertAfter(newElement, restoreSelection);
616
860
  return newElement;
617
861
  }
862
+
618
863
  collapseAtStart(selection) {
619
864
  const paragraph = lexical.$createParagraphNode();
620
865
  const children = this.getChildren();
@@ -622,6 +867,7 @@ class ListItemNode extends lexical.ElementNode {
622
867
  const listNode = this.getParentOrThrow();
623
868
  const listNodeParent = listNode.getParentOrThrow();
624
869
  const isIndented = $isListItemNode(listNodeParent);
870
+
625
871
  if (listNode.getChildrenSize() === 1) {
626
872
  if (isIndented) {
627
873
  // if the list node is nested, we just want to remove it,
@@ -630,15 +876,17 @@ class ListItemNode extends lexical.ElementNode {
630
876
  listNodeParent.select();
631
877
  } else {
632
878
  listNode.insertBefore(paragraph);
633
- listNode.remove();
634
- // If we have selection on the list item, we'll need to move it
879
+ listNode.remove(); // If we have selection on the list item, we'll need to move it
635
880
  // to the paragraph
881
+
636
882
  const anchor = selection.anchor;
637
883
  const focus = selection.focus;
638
884
  const key = paragraph.getKey();
885
+
639
886
  if (anchor.type === 'element' && anchor.getNode().is(this)) {
640
887
  anchor.set(key, anchor.offset, 'element');
641
888
  }
889
+
642
890
  if (focus.type === 'element' && focus.getNode().is(this)) {
643
891
  focus.set(key, focus.offset, 'element');
644
892
  }
@@ -647,44 +895,61 @@ class ListItemNode extends lexical.ElementNode {
647
895
  listNode.insertBefore(paragraph);
648
896
  this.remove();
649
897
  }
898
+
650
899
  return true;
651
900
  }
901
+
652
902
  getValue() {
653
903
  const self = this.getLatest();
654
904
  return self.__value;
655
905
  }
906
+
656
907
  setValue(value) {
657
908
  const self = this.getWritable();
658
909
  self.__value = value;
659
910
  }
911
+
660
912
  getChecked() {
661
913
  const self = this.getLatest();
662
914
  return self.__checked;
663
915
  }
916
+
664
917
  setChecked(checked) {
665
918
  const self = this.getWritable();
666
919
  self.__checked = checked;
667
920
  }
921
+
668
922
  toggleChecked() {
669
923
  this.setChecked(!this.__checked);
670
924
  }
925
+
671
926
  getIndent() {
672
927
  // If we don't have a parent, we are likely serializing
673
928
  const parent = this.getParent();
929
+
674
930
  if (parent === null) {
675
931
  return this.getLatest().__indent;
676
- }
677
- // ListItemNode should always have a ListNode for a parent.
932
+ } // ListItemNode should always have a ListNode for a parent.
933
+
934
+
678
935
  let listNodeParent = parent.getParentOrThrow();
679
936
  let indentLevel = 0;
937
+
680
938
  while ($isListItemNode(listNodeParent)) {
681
939
  listNodeParent = listNodeParent.getParentOrThrow().getParentOrThrow();
682
940
  indentLevel++;
683
941
  }
942
+
684
943
  return indentLevel;
685
944
  }
945
+
686
946
  setIndent(indent) {
947
+ if (!(typeof indent === 'number' && indent > -1)) {
948
+ throw Error(`Invalid indent value.`);
949
+ }
950
+
687
951
  let currentIndent = this.getIndent();
952
+
688
953
  while (currentIndent !== indent) {
689
954
  if (currentIndent < indent) {
690
955
  $handleIndent(this);
@@ -694,115 +959,146 @@ class ListItemNode extends lexical.ElementNode {
694
959
  currentIndent--;
695
960
  }
696
961
  }
962
+
697
963
  return this;
698
964
  }
965
+
699
966
  insertBefore(nodeToInsert) {
700
967
  if ($isListItemNode(nodeToInsert)) {
701
968
  const parent = this.getParentOrThrow();
969
+
702
970
  if ($isListNode(parent)) {
703
971
  const siblings = this.getNextSiblings();
704
972
  updateChildrenListItemValue(parent, siblings);
705
973
  }
706
974
  }
975
+
707
976
  return super.insertBefore(nodeToInsert);
708
977
  }
978
+
709
979
  canInsertAfter(node) {
710
980
  return $isListItemNode(node);
711
981
  }
982
+
712
983
  canReplaceWith(replacement) {
713
984
  return $isListItemNode(replacement);
714
985
  }
986
+
715
987
  canMergeWith(node) {
716
988
  return lexical.$isParagraphNode(node) || $isListItemNode(node);
717
989
  }
990
+
718
991
  extractWithChild(child, selection) {
719
992
  if (!lexical.$isRangeSelection(selection)) {
720
993
  return false;
721
994
  }
995
+
722
996
  const anchorNode = selection.anchor.getNode();
723
997
  const focusNode = selection.focus.getNode();
724
998
  return this.isParentOf(anchorNode) && this.isParentOf(focusNode) && this.getTextContent().length === selection.getTextContent().length;
725
999
  }
1000
+
726
1001
  isParentRequired() {
727
1002
  return true;
728
1003
  }
1004
+
729
1005
  createParentElementNode() {
730
1006
  return $createListNode('bullet');
731
1007
  }
1008
+
732
1009
  }
1010
+
733
1011
  function $setListItemThemeClassNames(dom, editorThemeClasses, node) {
734
1012
  const classesToAdd = [];
735
1013
  const classesToRemove = [];
736
1014
  const listTheme = editorThemeClasses.list;
737
1015
  const listItemClassName = listTheme ? listTheme.listitem : undefined;
738
1016
  let nestedListItemClassName;
1017
+
739
1018
  if (listTheme && listTheme.nested) {
740
1019
  nestedListItemClassName = listTheme.nested.listitem;
741
1020
  }
1021
+
742
1022
  if (listItemClassName !== undefined) {
743
1023
  const listItemClasses = listItemClassName.split(' ');
744
1024
  classesToAdd.push(...listItemClasses);
745
1025
  }
1026
+
746
1027
  if (listTheme) {
747
1028
  const parentNode = node.getParent();
748
1029
  const isCheckList = $isListNode(parentNode) && parentNode.getListType() === 'check';
749
1030
  const checked = node.getChecked();
1031
+
750
1032
  if (!isCheckList || checked) {
751
1033
  classesToRemove.push(listTheme.listitemUnchecked);
752
1034
  }
1035
+
753
1036
  if (!isCheckList || !checked) {
754
1037
  classesToRemove.push(listTheme.listitemChecked);
755
1038
  }
1039
+
756
1040
  if (isCheckList) {
757
1041
  classesToAdd.push(checked ? listTheme.listitemChecked : listTheme.listitemUnchecked);
758
1042
  }
759
1043
  }
1044
+
760
1045
  if (nestedListItemClassName !== undefined) {
761
1046
  const nestedListItemClasses = nestedListItemClassName.split(' ');
1047
+
762
1048
  if (node.getChildren().some(child => $isListNode(child))) {
763
1049
  classesToAdd.push(...nestedListItemClasses);
764
1050
  } else {
765
1051
  classesToRemove.push(...nestedListItemClasses);
766
1052
  }
767
1053
  }
1054
+
768
1055
  if (classesToRemove.length > 0) {
769
1056
  utils.removeClassNamesFromElement(dom, ...classesToRemove);
770
1057
  }
1058
+
771
1059
  if (classesToAdd.length > 0) {
772
1060
  utils.addClassNamesToElement(dom, ...classesToAdd);
773
1061
  }
774
1062
  }
1063
+
775
1064
  function updateListItemChecked(dom, listItemNode, prevListItemNode, listNode) {
776
- const isCheckList = listNode.getListType() === 'check';
777
- if (isCheckList) {
778
- // Only add attributes for leaf list items
779
- if ($isListNode(listItemNode.getFirstChild())) {
780
- dom.removeAttribute('role');
781
- dom.removeAttribute('tabIndex');
782
- dom.removeAttribute('aria-checked');
783
- } else {
784
- dom.setAttribute('role', 'checkbox');
785
- dom.setAttribute('tabIndex', '-1');
786
- if (!prevListItemNode || listItemNode.__checked !== prevListItemNode.__checked) {
787
- dom.setAttribute('aria-checked', listItemNode.getChecked() ? 'true' : 'false');
788
- }
789
- }
1065
+ // Only add attributes for leaf list items
1066
+ if ($isListNode(listItemNode.getFirstChild())) {
1067
+ dom.removeAttribute('role');
1068
+ dom.removeAttribute('tabIndex');
1069
+ dom.removeAttribute('aria-checked');
790
1070
  } else {
791
- // Clean up checked state
792
- if (listItemNode.getChecked() != null) {
793
- listItemNode.setChecked(undefined);
1071
+ dom.setAttribute('role', 'checkbox');
1072
+ dom.setAttribute('tabIndex', '-1');
1073
+
1074
+ if (!prevListItemNode || listItemNode.__checked !== prevListItemNode.__checked) {
1075
+ dom.setAttribute('aria-checked', listItemNode.getChecked() ? 'true' : 'false');
794
1076
  }
795
1077
  }
796
1078
  }
1079
+
797
1080
  function convertListItemElement(domNode) {
798
1081
  const checked = utils.isHTMLElement(domNode) && domNode.getAttribute('aria-checked') === 'true';
799
1082
  return {
800
1083
  node: $createListItemNode(checked)
801
1084
  };
802
1085
  }
1086
+ /**
1087
+ * Creates a new List Item node, passing true/false will convert it to a checkbox input.
1088
+ * @param checked - Is the List Item a checkbox and, if so, is it checked? undefined/null: not a checkbox, true/false is a checkbox and checked/unchecked, respectively.
1089
+ * @returns The new List Item.
1090
+ */
1091
+
1092
+
803
1093
  function $createListItemNode(checked) {
804
1094
  return lexical.$applyNodeReplacement(new ListItemNode(undefined, checked));
805
1095
  }
1096
+ /**
1097
+ * Checks to see if the node is a ListItemNode.
1098
+ * @param node - The node to be checked.
1099
+ * @returns true if the node is a ListItemNode, false otherwise.
1100
+ */
1101
+
806
1102
  function $isListItemNode(node) {
807
1103
  return node instanceof ListItemNode;
808
1104
  }
@@ -814,6 +1110,7 @@ function $isListItemNode(node) {
814
1110
  * LICENSE file in the root directory of this source tree.
815
1111
  *
816
1112
  */
1113
+
817
1114
  /** @noInheritDoc */
818
1115
  class ListNode extends lexical.ElementNode {
819
1116
  /** @internal */
@@ -821,51 +1118,61 @@ class ListNode extends lexical.ElementNode {
821
1118
  /** @internal */
822
1119
 
823
1120
  /** @internal */
824
-
825
1121
  static getType() {
826
1122
  return 'list';
827
1123
  }
1124
+
828
1125
  static clone(node) {
829
1126
  const listType = node.__listType || TAG_TO_LIST_TYPE[node.__tag];
830
1127
  return new ListNode(listType, node.__start, node.__key);
831
1128
  }
1129
+
832
1130
  constructor(listType, start, key) {
833
1131
  super(key);
1132
+
834
1133
  const _listType = TAG_TO_LIST_TYPE[listType] || listType;
1134
+
835
1135
  this.__listType = _listType;
836
1136
  this.__tag = _listType === 'number' ? 'ol' : 'ul';
837
1137
  this.__start = start;
838
1138
  }
1139
+
839
1140
  getTag() {
840
1141
  return this.__tag;
841
1142
  }
1143
+
842
1144
  getListType() {
843
1145
  return this.__listType;
844
1146
  }
1147
+
845
1148
  getStart() {
846
1149
  return this.__start;
847
- }
1150
+ } // View
848
1151
 
849
- // View
850
1152
 
851
1153
  createDOM(config, _editor) {
852
1154
  const tag = this.__tag;
853
1155
  const dom = document.createElement(tag);
1156
+
854
1157
  if (this.__start !== 1) {
855
1158
  dom.setAttribute('start', String(this.__start));
856
- }
857
- // @ts-expect-error Internal field.
1159
+ } // @ts-expect-error Internal field.
1160
+
1161
+
858
1162
  dom.__lexicalListType = this.__listType;
859
1163
  setListThemeClassNames(dom, config.theme, this);
860
1164
  return dom;
861
1165
  }
1166
+
862
1167
  updateDOM(prevNode, dom, config) {
863
1168
  if (prevNode.__tag !== this.__tag) {
864
1169
  return true;
865
1170
  }
1171
+
866
1172
  setListThemeClassNames(dom, config.theme, this);
867
1173
  return false;
868
1174
  }
1175
+
869
1176
  static importDOM() {
870
1177
  return {
871
1178
  ol: node => ({
@@ -878,6 +1185,7 @@ class ListNode extends lexical.ElementNode {
878
1185
  })
879
1186
  };
880
1187
  }
1188
+
881
1189
  static importJSON(serializedNode) {
882
1190
  const node = $createListNode(serializedNode.listType, serializedNode.start);
883
1191
  node.setFormat(serializedNode.format);
@@ -885,25 +1193,29 @@ class ListNode extends lexical.ElementNode {
885
1193
  node.setDirection(serializedNode.direction);
886
1194
  return node;
887
1195
  }
1196
+
888
1197
  exportDOM(editor) {
889
1198
  const {
890
1199
  element
891
1200
  } = super.exportDOM(editor);
1201
+
892
1202
  if (element) {
893
1203
  if (this.__start !== 1) {
894
1204
  element.setAttribute('start', String(this.__start));
895
1205
  }
1206
+
896
1207
  if (this.__listType === 'check') {
897
1208
  element.setAttribute('__lexicalListType', 'check');
898
1209
  }
899
1210
  }
1211
+
900
1212
  return {
901
1213
  element
902
1214
  };
903
1215
  }
1216
+
904
1217
  exportJSON() {
905
- return {
906
- ...super.exportJSON(),
1218
+ return { ...super.exportJSON(),
907
1219
  listType: this.getListType(),
908
1220
  start: this.getStart(),
909
1221
  tag: this.getTag(),
@@ -911,19 +1223,24 @@ class ListNode extends lexical.ElementNode {
911
1223
  version: 1
912
1224
  };
913
1225
  }
1226
+
914
1227
  canBeEmpty() {
915
1228
  return false;
916
1229
  }
1230
+
917
1231
  canIndent() {
918
1232
  return false;
919
1233
  }
1234
+
920
1235
  append(...nodesToAppend) {
921
1236
  for (let i = 0; i < nodesToAppend.length; i++) {
922
1237
  const currentNode = nodesToAppend[i];
1238
+
923
1239
  if ($isListItemNode(currentNode)) {
924
1240
  super.append(currentNode);
925
1241
  } else {
926
1242
  const listItemNode = $createListItemNode();
1243
+
927
1244
  if ($isListNode(currentNode)) {
928
1245
  listItemNode.append(currentNode);
929
1246
  } else if (lexical.$isElementNode(currentNode)) {
@@ -932,19 +1249,26 @@ class ListNode extends lexical.ElementNode {
932
1249
  } else {
933
1250
  listItemNode.append(currentNode);
934
1251
  }
1252
+
935
1253
  super.append(listItemNode);
936
1254
  }
937
1255
  }
1256
+
1257
+ updateChildrenListItemValue(this);
938
1258
  return this;
939
1259
  }
1260
+
940
1261
  extractWithChild(child) {
941
1262
  return $isListItemNode(child);
942
1263
  }
1264
+
943
1265
  }
1266
+
944
1267
  function setListThemeClassNames(dom, editorThemeClasses, node) {
945
1268
  const classesToAdd = [];
946
1269
  const classesToRemove = [];
947
1270
  const listTheme = editorThemeClasses.list;
1271
+
948
1272
  if (listTheme !== undefined) {
949
1273
  const listLevelsClassNames = listTheme[`${node.__tag}Depth`] || [];
950
1274
  const listDepth = $getListDepth(node) - 1;
@@ -953,23 +1277,29 @@ function setListThemeClassNames(dom, editorThemeClasses, node) {
953
1277
  const listClassName = listTheme[node.__tag];
954
1278
  let nestedListClassName;
955
1279
  const nestedListTheme = listTheme.nested;
1280
+
956
1281
  if (nestedListTheme !== undefined && nestedListTheme.list) {
957
1282
  nestedListClassName = nestedListTheme.list;
958
1283
  }
1284
+
959
1285
  if (listClassName !== undefined) {
960
1286
  classesToAdd.push(listClassName);
961
1287
  }
1288
+
962
1289
  if (listLevelClassName !== undefined) {
963
1290
  const listItemClasses = listLevelClassName.split(' ');
964
1291
  classesToAdd.push(...listItemClasses);
1292
+
965
1293
  for (let i = 0; i < listLevelsClassNames.length; i++) {
966
1294
  if (i !== normalizedListDepth) {
967
1295
  classesToRemove.push(node.__tag + i);
968
1296
  }
969
1297
  }
970
1298
  }
1299
+
971
1300
  if (nestedListClassName !== undefined) {
972
1301
  const nestedListItemClasses = nestedListClassName.split(' ');
1302
+
973
1303
  if (listDepth > 1) {
974
1304
  classesToAdd.push(...nestedListItemClasses);
975
1305
  } else {
@@ -977,26 +1307,32 @@ function setListThemeClassNames(dom, editorThemeClasses, node) {
977
1307
  }
978
1308
  }
979
1309
  }
1310
+
980
1311
  if (classesToRemove.length > 0) {
981
1312
  utils.removeClassNamesFromElement(dom, ...classesToRemove);
982
1313
  }
1314
+
983
1315
  if (classesToAdd.length > 0) {
984
1316
  utils.addClassNamesToElement(dom, ...classesToAdd);
985
1317
  }
986
1318
  }
987
-
988
1319
  /*
989
1320
  * This function normalizes the children of a ListNode after the conversion from HTML,
990
1321
  * ensuring that they are all ListItemNodes and contain either a single nested ListNode
991
1322
  * or some other inline content.
992
1323
  */
1324
+
1325
+
993
1326
  function normalizeChildren(nodes) {
994
1327
  const normalizedListItems = [];
1328
+
995
1329
  for (let i = 0; i < nodes.length; i++) {
996
1330
  const node = nodes[i];
1331
+
997
1332
  if ($isListItemNode(node)) {
998
1333
  normalizedListItems.push(node);
999
1334
  const children = node.getChildren();
1335
+
1000
1336
  if (children.length > 1) {
1001
1337
  children.forEach(child => {
1002
1338
  if ($isListNode(child)) {
@@ -1008,13 +1344,18 @@ function normalizeChildren(nodes) {
1008
1344
  normalizedListItems.push(wrapInListItem(node));
1009
1345
  }
1010
1346
  }
1347
+
1011
1348
  return normalizedListItems;
1012
1349
  }
1350
+
1013
1351
  function convertListNode(domNode) {
1014
1352
  const nodeName = domNode.nodeName.toLowerCase();
1015
1353
  let node = null;
1354
+
1016
1355
  if (nodeName === 'ol') {
1017
- node = $createListNode('number');
1356
+ // @ts-ignore
1357
+ const start = domNode.start;
1358
+ node = $createListNode('number', start);
1018
1359
  } else if (nodeName === 'ul') {
1019
1360
  if (utils.isHTMLElement(domNode) && domNode.getAttribute('__lexicallisttype') === 'check') {
1020
1361
  node = $createListNode('check');
@@ -1022,18 +1363,33 @@ function convertListNode(domNode) {
1022
1363
  node = $createListNode('bullet');
1023
1364
  }
1024
1365
  }
1366
+
1025
1367
  return {
1026
1368
  after: normalizeChildren,
1027
1369
  node
1028
1370
  };
1029
1371
  }
1372
+
1030
1373
  const TAG_TO_LIST_TYPE = {
1031
1374
  ol: 'number',
1032
1375
  ul: 'bullet'
1033
1376
  };
1377
+ /**
1378
+ * Creates a ListNode of listType.
1379
+ * @param listType - The type of list to be created. Can be 'number', 'bullet', or 'check'.
1380
+ * @param start - Where an ordered list starts its count, start = 1 if left undefined.
1381
+ * @returns The new ListNode
1382
+ */
1383
+
1034
1384
  function $createListNode(listType, start = 1) {
1035
1385
  return lexical.$applyNodeReplacement(new ListNode(listType, start));
1036
1386
  }
1387
+ /**
1388
+ * Checks to see if the node is a ListNode.
1389
+ * @param node - The node to be checked.
1390
+ * @returns true if the node is a ListNode, false otherwise.
1391
+ */
1392
+
1037
1393
  function $isListNode(node) {
1038
1394
  return node instanceof ListNode;
1039
1395
  }