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