lexical 0.12.4 → 0.12.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Lexical.dev.js CHANGED
@@ -583,16 +583,16 @@ function getDOMTextNode(element) {
583
583
  }
584
584
  function toggleTextFormatType(format, type, alignWithFormat) {
585
585
  const activeFormat = TEXT_TYPE_TO_FORMAT[type];
586
- const isStateFlagPresent = format & activeFormat;
587
- if (isStateFlagPresent && (alignWithFormat === null || (alignWithFormat & activeFormat) === 0)) {
588
- // Remove the state flag.
589
- return format ^ activeFormat;
586
+ if (alignWithFormat !== null && (format & activeFormat) === (alignWithFormat & activeFormat)) {
587
+ return format;
590
588
  }
591
- if (alignWithFormat === null || alignWithFormat & activeFormat) {
592
- // Add the state flag.
593
- return format | activeFormat;
589
+ let newFormat = format ^ activeFormat;
590
+ if (type === 'subscript') {
591
+ newFormat &= ~TEXT_TYPE_TO_FORMAT.superscript;
592
+ } else if (type === 'superscript') {
593
+ newFormat &= ~TEXT_TYPE_TO_FORMAT.subscript;
594
594
  }
595
- return format;
595
+ return newFormat;
596
596
  }
597
597
  function $isLeafNode(node) {
598
598
  return $isTextNode(node) || $isLineBreakNode(node) || $isDecoratorNode(node);
@@ -816,7 +816,7 @@ function $setSelection(selection) {
816
816
  }
817
817
  }
818
818
  selection.dirty = true;
819
- selection._cachedNodes = null;
819
+ selection.setCachedNodes(null);
820
820
  }
821
821
  editorState._selection = selection;
822
822
  }
@@ -2357,6 +2357,8 @@ function onSelectionChange(domSelection, editor, isActive) {
2357
2357
  const windowEvent = getWindow(editor).event;
2358
2358
  const currentTimeStamp = windowEvent ? windowEvent.timeStamp : performance.now();
2359
2359
  const [lastFormat, lastStyle, lastOffset, lastKey, timeStamp] = collapsedSelectionFormat;
2360
+ const root = $getRoot();
2361
+ const isRootTextContentEmpty = editor.isComposing() === false && root.getTextContent() === '';
2360
2362
  if (currentTimeStamp < timeStamp + 200 && anchor.offset === lastOffset && anchor.key === lastKey) {
2361
2363
  selection.format = lastFormat;
2362
2364
  selection.style = lastStyle;
@@ -2364,7 +2366,7 @@ function onSelectionChange(domSelection, editor, isActive) {
2364
2366
  if (anchor.type === 'text') {
2365
2367
  selection.format = anchorNode.getFormat();
2366
2368
  selection.style = anchorNode.getStyle();
2367
- } else if (anchor.type === 'element') {
2369
+ } else if (anchor.type === 'element' && !isRootTextContentEmpty) {
2368
2370
  selection.format = 0;
2369
2371
  selection.style = '';
2370
2372
  }
@@ -2521,6 +2523,11 @@ function onBeforeInput(event, editor) {
2521
2523
  selection.format = anchorNode.getFormat();
2522
2524
  selection.style = anchorNode.getStyle();
2523
2525
  }
2526
+ const selectedText = selection.anchor.getNode().getTextContent();
2527
+ if (selectedText.length <= 1) {
2528
+ event.preventDefault();
2529
+ dispatchCommand(editor, DELETE_CHARACTER_COMMAND, true);
2530
+ }
2524
2531
  } else {
2525
2532
  event.preventDefault();
2526
2533
  dispatchCommand(editor, DELETE_CHARACTER_COMMAND, true);
@@ -3646,8 +3653,8 @@ class LexicalNode {
3646
3653
  const parent = latestNode.__parent;
3647
3654
  const cloneNotNeeded = editor._cloneNotNeeded;
3648
3655
  const selection = $getSelection();
3649
- if (selection !== null) {
3650
- selection._cachedNodes = null;
3656
+ if ($INTERNAL_isPointSelection(selection)) {
3657
+ selection.setCachedNodes(null);
3651
3658
  }
3652
3659
  if (cloneNotNeeded.has(key)) {
3653
3660
  // Transforms clear the dirty node set on each iteration to keep track on newly dirty nodes
@@ -3977,6 +3984,12 @@ class LexicalNode {
3977
3984
  createParentElementNode() {
3978
3985
  return $createParagraphNode();
3979
3986
  }
3987
+ selectStart() {
3988
+ return this.selectPrevious();
3989
+ }
3990
+ selectEnd() {
3991
+ return this.selectNext(0, 0);
3992
+ }
3980
3993
 
3981
3994
  /**
3982
3995
  * Moves selection to the previous sibling of this node, at the specified offsets.
@@ -4047,6 +4060,32 @@ function errorOnTypeKlassMismatch(type, klass) {
4047
4060
  }
4048
4061
  }
4049
4062
 
4063
+ /**
4064
+ * Insert a series of nodes after this LexicalNode (as next siblings)
4065
+ *
4066
+ * @param firstToInsert - The first node to insert after this one.
4067
+ * @param lastToInsert - The last node to insert after this one. Must be a
4068
+ * later sibling of FirstNode. If not provided, it will be its last sibling.
4069
+ */
4070
+ function insertRangeAfter(node, firstToInsert, lastToInsert) {
4071
+ const lastToInsert2 = lastToInsert || firstToInsert.getParentOrThrow().getLastChild();
4072
+ let current = firstToInsert;
4073
+ const nodesToInsert = [firstToInsert];
4074
+ while (current !== lastToInsert2) {
4075
+ if (!current.getNextSibling()) {
4076
+ {
4077
+ throw Error(`insertRangeAfter: lastToInsert must be a later sibling of firstToInsert`);
4078
+ }
4079
+ }
4080
+ current = current.getNextSibling();
4081
+ nodesToInsert.push(current);
4082
+ }
4083
+ let currentNode = node;
4084
+ for (const nodeToInsert of nodesToInsert) {
4085
+ currentNode = currentNode.insertAfter(nodeToInsert);
4086
+ }
4087
+ }
4088
+
4050
4089
  /**
4051
4090
  * Copyright (c) Meta Platforms, Inc. and affiliates.
4052
4091
  *
@@ -4635,7 +4674,8 @@ class TextNode extends LexicalNode {
4635
4674
  }
4636
4675
 
4637
4676
  /**
4638
- * Applies the provided format to this TextNode if it's not present. Removes it if it is present.
4677
+ * Applies the provided format to this TextNode if it's not present. Removes it if it's present.
4678
+ * The subscript and superscript formats are mutually exclusive.
4639
4679
  * Prefer using this method to turn specific formats on and off.
4640
4680
  *
4641
4681
  * @param type - TextFormatType to toggle.
@@ -4643,8 +4683,9 @@ class TextNode extends LexicalNode {
4643
4683
  * @returns this TextNode.
4644
4684
  */
4645
4685
  toggleFormat(type) {
4646
- const formatFlag = TEXT_TYPE_TO_FORMAT[type];
4647
- return this.setFormat(this.getFormat() ^ formatFlag);
4686
+ const format = this.getFormat();
4687
+ const newFormat = toggleTextFormatType(format, type, null);
4688
+ return this.setFormat(newFormat);
4648
4689
  }
4649
4690
 
4650
4691
  /**
@@ -4738,6 +4779,13 @@ class TextNode extends LexicalNode {
4738
4779
  }
4739
4780
  return selection;
4740
4781
  }
4782
+ selectStart() {
4783
+ return this.select(0, 0);
4784
+ }
4785
+ selectEnd() {
4786
+ const size = this.getTextContentSize();
4787
+ return this.select(size, size);
4788
+ }
4741
4789
 
4742
4790
  /**
4743
4791
  * Inserts the provided text into this TextNode at the provided offset, deleting the number of characters
@@ -5293,7 +5341,7 @@ class Point {
5293
5341
  $setCompositionKey(key);
5294
5342
  }
5295
5343
  if (selection !== null) {
5296
- selection._cachedNodes = null;
5344
+ selection.setCachedNodes(null);
5297
5345
  selection.dirty = true;
5298
5346
  }
5299
5347
  }
@@ -5364,12 +5412,68 @@ function $setPointValues(point, key, offset, type) {
5364
5412
  point.offset = offset;
5365
5413
  point.type = type;
5366
5414
  }
5415
+ /**
5416
+ * This class is being used only for internal use case of migration GridSelection outside of core package.
5417
+ * DO NOT USE THIS CLASS DIRECTLY.
5418
+ */
5419
+ class INTERNAL_PointSelection {
5420
+ constructor(anchor, focus) {
5421
+ this.anchor = anchor;
5422
+ this.focus = focus;
5423
+ anchor._selection = this;
5424
+ focus._selection = this;
5425
+ this._cachedNodes = null;
5426
+ this.dirty = false;
5427
+ }
5428
+ getCachedNodes() {
5429
+ return this._cachedNodes;
5430
+ }
5431
+ setCachedNodes(nodes) {
5432
+ this._cachedNodes = nodes;
5433
+ }
5434
+ is(selection) {
5435
+ if (!$INTERNAL_isPointSelection(selection)) {
5436
+ return false;
5437
+ }
5438
+ return this.anchor.is(selection.anchor) && this.focus.is(selection.focus);
5439
+ }
5440
+ isCollapsed() {
5441
+ return false;
5442
+ }
5443
+ extract() {
5444
+ return this.getNodes();
5445
+ }
5446
+ /**
5447
+ * Returns whether the Selection is "backwards", meaning the focus
5448
+ * logically precedes the anchor in the EditorState.
5449
+ * @returns true if the Selection is backwards, false otherwise.
5450
+ */
5451
+ isBackward() {
5452
+ return this.focus.isBefore(this.anchor);
5453
+ }
5454
+
5455
+ /**
5456
+ * Returns the character-based offsets of the Selection, accounting for non-text Points
5457
+ * by using the children size or text content.
5458
+ *
5459
+ * @returns the character offsets for the Selection
5460
+ */
5461
+ getCharacterOffsets() {
5462
+ return getCharacterOffsets(this);
5463
+ }
5464
+ }
5367
5465
  class NodeSelection {
5368
5466
  constructor(objects) {
5369
5467
  this.dirty = false;
5370
5468
  this._nodes = objects;
5371
5469
  this._cachedNodes = null;
5372
5470
  }
5471
+ getCachedNodes() {
5472
+ return this._cachedNodes;
5473
+ }
5474
+ setCachedNodes(nodes) {
5475
+ this._cachedNodes = nodes;
5476
+ }
5373
5477
  is(selection) {
5374
5478
  if (!$isNodeSelection(selection)) {
5375
5479
  return false;
@@ -5456,6 +5560,9 @@ class NodeSelection {
5456
5560
  function $isRangeSelection(x) {
5457
5561
  return x instanceof RangeSelection;
5458
5562
  }
5563
+ function $INTERNAL_isPointSelection(x) {
5564
+ return x instanceof INTERNAL_PointSelection;
5565
+ }
5459
5566
  function DEPRECATED_$getGridCellNodeRect(GridCellNode) {
5460
5567
  const [CellNode,, GridNode] = DEPRECATED_$getNodeTriplet(GridCellNode);
5461
5568
  const rows = GridNode.getChildren();
@@ -5501,15 +5608,16 @@ function DEPRECATED_$getGridCellNodeRect(GridCellNode) {
5501
5608
  }
5502
5609
  return null;
5503
5610
  }
5504
- class GridSelection {
5611
+ class GridSelection extends INTERNAL_PointSelection {
5505
5612
  constructor(gridKey, anchor, focus) {
5613
+ super(anchor, focus);
5506
5614
  this.gridKey = gridKey;
5507
- this.anchor = anchor;
5508
- this.focus = focus;
5509
- this.dirty = false;
5510
- this._cachedNodes = null;
5511
- anchor._selection = this;
5512
- focus._selection = this;
5615
+ }
5616
+ getCachedNodes() {
5617
+ return this._cachedNodes;
5618
+ }
5619
+ setCachedNodes(nodes) {
5620
+ this._cachedNodes = nodes;
5513
5621
  }
5514
5622
  is(selection) {
5515
5623
  if (!DEPRECATED_$isGridSelection(selection)) {
@@ -5530,12 +5638,6 @@ class GridSelection {
5530
5638
  isCollapsed() {
5531
5639
  return false;
5532
5640
  }
5533
- isBackward() {
5534
- return this.focus.isBefore(this.anchor);
5535
- }
5536
- getCharacterOffsets() {
5537
- return getCharacterOffsets(this);
5538
- }
5539
5641
  extract() {
5540
5642
  return this.getNodes();
5541
5643
  }
@@ -5721,16 +5823,17 @@ class GridSelection {
5721
5823
  function DEPRECATED_$isGridSelection(x) {
5722
5824
  return x instanceof GridSelection;
5723
5825
  }
5724
- class RangeSelection {
5826
+ class RangeSelection extends INTERNAL_PointSelection {
5725
5827
  constructor(anchor, focus, format, style) {
5726
- this.anchor = anchor;
5727
- this.focus = focus;
5728
- this.dirty = false;
5828
+ super(anchor, focus);
5729
5829
  this.format = format;
5730
5830
  this.style = style;
5731
- this._cachedNodes = null;
5732
- anchor._selection = this;
5733
- focus._selection = this;
5831
+ }
5832
+ getCachedNodes() {
5833
+ return this._cachedNodes;
5834
+ }
5835
+ setCachedNodes(nodes) {
5836
+ this._cachedNodes = nodes;
5734
5837
  }
5735
5838
 
5736
5839
  /**
@@ -5746,15 +5849,6 @@ class RangeSelection {
5746
5849
  return this.anchor.is(selection.anchor) && this.focus.is(selection.focus) && this.format === selection.format && this.style === selection.style;
5747
5850
  }
5748
5851
 
5749
- /**
5750
- * Returns whether the Selection is "backwards", meaning the focus
5751
- * logically precedes the anchor in the EditorState.
5752
- * @returns true if the Selection is backwards, false otherwise.
5753
- */
5754
- isBackward() {
5755
- return this.focus.isBefore(this.anchor);
5756
- }
5757
-
5758
5852
  /**
5759
5853
  * Returns whether the Selection is "collapsed", meaning the anchor and focus are
5760
5854
  * the same node and have the same offset.
@@ -6372,53 +6466,59 @@ class RangeSelection {
6372
6466
  return selection.insertNodes(nodes);
6373
6467
  }
6374
6468
  const firstBlock = $getAncestor(this.anchor.getNode(), INTERNAL_$isBlock);
6469
+ const last = nodes[nodes.length - 1];
6375
6470
 
6376
- // case where we insert inside a code block
6471
+ // CASE 1: insert inside a code block
6377
6472
  if ('__language' in firstBlock) {
6378
6473
  if ('__language' in nodes[0]) {
6379
6474
  this.insertText(nodes[0].getTextContent());
6380
6475
  } else {
6381
6476
  const index = removeTextAndSplitBlock(this);
6382
6477
  firstBlock.splice(index, 0, nodes);
6383
- const last = nodes[nodes.length - 1];
6384
- if (last.select) {
6385
- last.select();
6386
- } else last.selectNext(0, 0);
6478
+ last.selectEnd();
6387
6479
  }
6388
6480
  return;
6389
6481
  }
6482
+
6483
+ // CASE 2: All elements of the array are inline
6390
6484
  const notInline = node => ($isElementNode(node) || $isDecoratorNode(node)) && !node.isInline();
6391
- const isMergeable = node => $isElementNode(node) && INTERNAL_$isBlock(node) && !node.isEmpty() && $isElementNode(firstBlock) && (!firstBlock.isEmpty() || !$isRootOrShadowRoot(firstBlock.getParentOrThrow()));
6392
- const firstNotInline = nodes.find(notInline);
6485
+ if (!nodes.some(notInline)) {
6486
+ const index = removeTextAndSplitBlock(this);
6487
+ firstBlock.splice(index, 0, nodes);
6488
+ last.selectEnd();
6489
+ return;
6490
+ }
6491
+
6492
+ // CASE 3: At least 1 element of the array is not inline
6493
+ const blocksParent = $wrapInlineNodes(nodes);
6494
+ const nodeToSelect = blocksParent.getLastDescendant();
6495
+ const blocks = blocksParent.getChildren();
6496
+ const isLI = node => '__value' in node && '__checked' in node;
6497
+ const isMergeable = node => $isElementNode(node) && INTERNAL_$isBlock(node) && !node.isEmpty() && $isElementNode(firstBlock) && (!firstBlock.isEmpty() || isLI(firstBlock));
6393
6498
  const shouldInsert = !$isElementNode(firstBlock) || !firstBlock.isEmpty();
6394
6499
  const insertedParagraph = shouldInsert ? this.insertParagraph() : null;
6395
- const last = nodes[nodes.length - 1];
6396
- const nodeToSelect = $isElementNode(last) ? last.getLastDescendant() || last : last;
6397
- const nodeToSelectSize = nodeToSelect.getTextContentSize();
6398
- let currentBlock = firstBlock;
6399
- for (const node of nodes) {
6400
- if (node === firstNotInline && isMergeable(node)) {
6401
- currentBlock.append(...node.getChildren());
6402
- } else if (notInline(node)) {
6403
- currentBlock = currentBlock.insertAfter(node);
6404
- } else {
6405
- currentBlock.append(node);
6406
- }
6407
- }
6408
- if (insertedParagraph && $isElementNode(currentBlock) && INTERNAL_$isBlock(currentBlock)) {
6409
- currentBlock.append(...insertedParagraph.getChildren());
6500
+ const lastToInsert = blocks[blocks.length - 1];
6501
+ let firstToInsert = blocks[0];
6502
+ if (isMergeable(firstToInsert)) {
6503
+ firstBlock.append(...firstToInsert.getChildren());
6504
+ firstToInsert = blocks[1];
6505
+ }
6506
+ if (firstToInsert) {
6507
+ insertRangeAfter(firstBlock, firstToInsert);
6508
+ }
6509
+ const lastInsertedBlock = $getAncestor(nodeToSelect, INTERNAL_$isBlock);
6510
+ if (insertedParagraph && $isElementNode(lastInsertedBlock) && (isLI(insertedParagraph) || INTERNAL_$isBlock(lastToInsert))) {
6511
+ lastInsertedBlock.append(...insertedParagraph.getChildren());
6410
6512
  insertedParagraph.remove();
6411
6513
  }
6412
6514
  if ($isElementNode(firstBlock) && firstBlock.isEmpty()) {
6413
6515
  firstBlock.remove();
6414
6516
  }
6415
- if (!nodeToSelect.select) {
6416
- nodeToSelect.selectNext(0, 0);
6417
- } else {
6418
- nodeToSelect.select(nodeToSelectSize, nodeToSelectSize);
6419
- }
6517
+ nodeToSelect.selectEnd();
6518
+
6519
+ // To understand this take a look at the test "can wrap post-linebreak nodes into new element"
6420
6520
  const lastChild = $isElementNode(firstBlock) ? firstBlock.getLastChild() : null;
6421
- if ($isLineBreakNode(lastChild) && currentBlock !== firstBlock) {
6521
+ if ($isLineBreakNode(lastChild) && lastInsertedBlock !== firstBlock) {
6422
6522
  lastChild.remove();
6423
6523
  }
6424
6524
  }
@@ -6464,16 +6564,6 @@ class RangeSelection {
6464
6564
  }
6465
6565
  }
6466
6566
 
6467
- /**
6468
- * Returns the character-based offsets of the Selection, accounting for non-text Points
6469
- * by using the children size or text content.
6470
- *
6471
- * @returns the character offsets for the Selection
6472
- */
6473
- getCharacterOffsets() {
6474
- return getCharacterOffsets(this);
6475
- }
6476
-
6477
6567
  /**
6478
6568
  * Extracts the nodes in the Selection, splitting nodes where necessary
6479
6569
  * to get offset-level precision.
@@ -7060,10 +7150,10 @@ function internalCreateSelection(editor) {
7060
7150
  const currentEditorState = editor.getEditorState();
7061
7151
  const lastSelection = currentEditorState._selection;
7062
7152
  const domSelection = getDOMSelection(editor._window);
7063
- if ($isNodeSelection(lastSelection) || DEPRECATED_$isGridSelection(lastSelection)) {
7064
- return lastSelection.clone();
7153
+ if ($isRangeSelection(lastSelection) || lastSelection == null) {
7154
+ return internalCreateRangeSelection(lastSelection, domSelection, editor);
7065
7155
  }
7066
- return internalCreateRangeSelection(lastSelection, domSelection, editor);
7156
+ return lastSelection.clone();
7067
7157
  }
7068
7158
  function internalCreateRangeSelection(lastSelection, domSelection, editor) {
7069
7159
  const windowObj = editor._window;
@@ -7504,6 +7594,34 @@ function removeTextAndSplitBlock(selection) {
7504
7594
  }
7505
7595
  return pointParent.getIndexWithinParent() + x;
7506
7596
  }
7597
+ function $wrapInlineNodes(nodes) {
7598
+ // We temporarily insert the topLevelNodes into an arbitrary ElementNode,
7599
+ // since insertAfter does not work on nodes that have no parent (TO-DO: fix that).
7600
+ const virtualRoot = $createParagraphNode();
7601
+ let currentBlock = null;
7602
+ for (let i = 0; i < nodes.length; i++) {
7603
+ const node = nodes[i];
7604
+ const isLineBreakNode = $isLineBreakNode(node);
7605
+ if (isLineBreakNode || $isDecoratorNode(node) && node.isInline() || $isElementNode(node) && node.isInline() || $isTextNode(node) || node.isParentRequired()) {
7606
+ if (currentBlock === null) {
7607
+ currentBlock = node.createParentElementNode();
7608
+ virtualRoot.append(currentBlock);
7609
+ // In the case of LineBreakNode, we just need to
7610
+ // add an empty ParagraphNode to the topLevelBlocks.
7611
+ if (isLineBreakNode) {
7612
+ continue;
7613
+ }
7614
+ }
7615
+ if (currentBlock !== null) {
7616
+ currentBlock.append(node);
7617
+ }
7618
+ } else {
7619
+ virtualRoot.append(node);
7620
+ currentBlock = null;
7621
+ }
7622
+ }
7623
+ return virtualRoot;
7624
+ }
7507
7625
 
7508
7626
  /**
7509
7627
  * Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -8180,6 +8298,10 @@ class DecoratorNode extends LexicalNode {
8180
8298
  constructor(key) {
8181
8299
  super(key);
8182
8300
  }
8301
+
8302
+ /**
8303
+ * The returned value is added to the LexicalEditor._decorators
8304
+ */
8183
8305
  decorate(editor, config) {
8184
8306
  {
8185
8307
  throw Error(`decorate: base method not extended`);
@@ -8463,25 +8585,11 @@ class ElementNode extends LexicalNode {
8463
8585
  }
8464
8586
  selectStart() {
8465
8587
  const firstNode = this.getFirstDescendant();
8466
- if ($isElementNode(firstNode) || $isTextNode(firstNode)) {
8467
- return firstNode.select(0, 0);
8468
- }
8469
- // Decorator or LineBreak
8470
- if (firstNode !== null) {
8471
- return firstNode.selectPrevious();
8472
- }
8473
- return this.select(0, 0);
8588
+ return firstNode ? firstNode.selectStart() : this.select();
8474
8589
  }
8475
8590
  selectEnd() {
8476
8591
  const lastNode = this.getLastDescendant();
8477
- if ($isElementNode(lastNode) || $isTextNode(lastNode)) {
8478
- return lastNode.select();
8479
- }
8480
- // Decorator or LineBreak
8481
- if (lastNode !== null) {
8482
- return lastNode.selectNext();
8483
- }
8484
- return this.select();
8592
+ return lastNode ? lastNode.selectEnd() : this.select();
8485
8593
  }
8486
8594
  clear() {
8487
8595
  const writableSelf = this.getWritable();
@@ -9847,6 +9955,7 @@ function DEPRECATED_$isGridRowNode(node) {
9847
9955
  return node instanceof DEPRECATED_GridRowNode;
9848
9956
  }
9849
9957
 
9958
+ exports.$INTERNAL_isPointSelection = $INTERNAL_isPointSelection;
9850
9959
  exports.$addUpdateTag = $addUpdateTag;
9851
9960
  exports.$applyNodeReplacement = $applyNodeReplacement;
9852
9961
  exports.$copyNode = $copyNode;