lexical 0.6.1-next.0 → 0.6.3

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
@@ -1258,38 +1258,64 @@ function getElementByKeyOrThrow(editor, key) {
1258
1258
 
1259
1259
  return element;
1260
1260
  }
1261
- function scrollIntoViewIfNeeded(editor, anchor, rootElement, tags) {
1262
- let anchorNode = anchor.getNode();
1263
-
1264
- if ($isElementNode(anchorNode)) {
1265
- const descendantNode = anchorNode.getDescendantByIndex(anchor.offset);
1261
+ function getParentElement(element) {
1262
+ const parentElement = element.assignedSlot || element.parentElement;
1263
+ return parentElement !== null && parentElement.nodeType === 11 ? parentElement.host : parentElement;
1264
+ }
1265
+ function scrollIntoViewIfNeeded(editor, selectionRect, rootElement) {
1266
+ const doc = rootElement.ownerDocument;
1267
+ const defaultView = doc.defaultView;
1266
1268
 
1267
- if (descendantNode !== null) {
1268
- anchorNode = descendantNode;
1269
- }
1269
+ if (defaultView === null) {
1270
+ return;
1270
1271
  }
1271
1272
 
1272
- const element = editor.getElementByKey(anchorNode.__key);
1273
+ let {
1274
+ top: currentTop,
1275
+ bottom: currentBottom
1276
+ } = selectionRect;
1277
+ let targetTop = 0;
1278
+ let targetBottom = 0;
1279
+ let element = rootElement;
1280
+
1281
+ while (element !== null) {
1282
+ const isBodyElement = element === doc.body;
1283
+
1284
+ if (isBodyElement) {
1285
+ targetTop = 0;
1286
+ targetBottom = getWindow(editor).innerHeight;
1287
+ } else {
1288
+ const targetRect = element.getBoundingClientRect();
1289
+ targetTop = targetRect.top;
1290
+ targetBottom = targetRect.bottom;
1291
+ }
1273
1292
 
1274
- if (element !== null) {
1275
- const rect = element.getBoundingClientRect();
1293
+ let diff = 0;
1276
1294
 
1277
- if (rect.bottom > getWindow(editor).innerHeight) {
1278
- element.scrollIntoView(false);
1279
- } else if (rect.top < 0) {
1280
- element.scrollIntoView();
1281
- } else {
1282
- const rootRect = rootElement.getBoundingClientRect(); // Rects can returning decimal numbers that differ due to rounding
1283
- // differences. So let's normalize the values.
1295
+ if (currentTop < targetTop) {
1296
+ diff = -(targetTop - currentTop);
1297
+ } else if (currentBottom > targetBottom) {
1298
+ diff = currentBottom - targetBottom;
1299
+ }
1284
1300
 
1285
- if (Math.floor(rect.bottom) > Math.floor(rootRect.bottom)) {
1286
- element.scrollIntoView(false);
1287
- } else if (Math.floor(rect.top) < Math.floor(rootRect.top)) {
1288
- element.scrollIntoView();
1301
+ if (diff !== 0) {
1302
+ if (isBodyElement) {
1303
+ // Only handles scrolling of Y axis
1304
+ defaultView.scrollBy(0, diff);
1305
+ } else {
1306
+ const scrollTop = element.scrollTop;
1307
+ element.scrollTop += diff;
1308
+ const yOffset = element.scrollTop - scrollTop;
1309
+ currentTop -= yOffset;
1310
+ currentBottom -= yOffset;
1289
1311
  }
1290
1312
  }
1291
1313
 
1292
- tags.add('scroll-into-view');
1314
+ if (isBodyElement) {
1315
+ break;
1316
+ }
1317
+
1318
+ element = getParentElement(element);
1293
1319
  }
1294
1320
  }
1295
1321
  function $addUpdateTag(tag) {
@@ -1375,6 +1401,49 @@ function $getNearestRootOrShadowRoot(node) {
1375
1401
  function $isRootOrShadowRoot(node) {
1376
1402
  return $isRootNode(node) || $isElementNode(node) && node.isShadowRoot();
1377
1403
  }
1404
+ function $copyNode(node) {
1405
+ // @ts-ignore
1406
+ const copy = node.constructor.clone(node);
1407
+ $setNodeKey(copy, null);
1408
+ return copy;
1409
+ }
1410
+ function $applyNodeReplacement(node) {
1411
+ const editor = getActiveEditor();
1412
+ const nodeType = node.constructor.getType();
1413
+
1414
+ const registeredNode = editor._nodes.get(nodeType);
1415
+
1416
+ if (registeredNode === undefined) {
1417
+ {
1418
+ throw Error(`$initializeNode failed. Ensure node has been registered to the editor. You can do this by passing the node class via the "nodes" array in the editor config.`);
1419
+ }
1420
+ }
1421
+
1422
+ const replaceFunc = registeredNode.replace;
1423
+
1424
+ if (replaceFunc !== null) {
1425
+ const replacementNode = replaceFunc(node);
1426
+
1427
+ if (!(replacementNode instanceof node.constructor)) {
1428
+ {
1429
+ throw Error(`$initializeNode failed. Ensure replacement node is a subclass of the original node.`);
1430
+ }
1431
+ }
1432
+
1433
+ return replacementNode;
1434
+ }
1435
+
1436
+ return node;
1437
+ }
1438
+ function errorOnInsertTextNodeOnRoot(node, insertNode) {
1439
+ const parentNode = node.getParent();
1440
+
1441
+ if ($isRootNode(parentNode) && !$isElementNode(insertNode) && !$isDecoratorNode(insertNode)) {
1442
+ {
1443
+ throw Error(`Only element or decorator nodes can be inserted in to the root node`);
1444
+ }
1445
+ }
1446
+ }
1378
1447
 
1379
1448
  /**
1380
1449
  * Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -1763,7 +1832,7 @@ function createChildren(children, _startIndex, endIndex, dom, insertDOM) {
1763
1832
  function isLastChildLineBreakOrDecorator(children, nodeMap) {
1764
1833
  const childKey = children[children.length - 1];
1765
1834
  const node = nodeMap.get(childKey);
1766
- return $isLineBreakNode(node) || $isDecoratorNode(node);
1835
+ return $isLineBreakNode(node) || $isDecoratorNode(node) && node.isInline();
1767
1836
  } // If we end an element with a LineBreakNode, then we need to add an additional <br>
1768
1837
 
1769
1838
 
@@ -2727,11 +2796,6 @@ function onCompositionEnd(event, editor) {
2727
2796
  }
2728
2797
 
2729
2798
  function onKeyDown(event, editor) {
2730
- if (hasStoppedLexicalPropagation(event)) {
2731
- return;
2732
- }
2733
-
2734
- stopLexicalPropagation(event);
2735
2799
  lastKeyDownTimeStamp = event.timeStamp;
2736
2800
  lastKeyCode = event.keyCode;
2737
2801
 
@@ -2940,10 +3004,22 @@ function addRootElementEvents(rootElement, editor) {
2940
3004
  for (let i = 0; i < rootElementEvents.length; i++) {
2941
3005
  const [eventName, onEvent] = rootElementEvents[i];
2942
3006
  const eventHandler = typeof onEvent === 'function' ? event => {
3007
+ if (hasStoppedLexicalPropagation(event)) {
3008
+ return;
3009
+ }
3010
+
3011
+ stopLexicalPropagation(event);
3012
+
2943
3013
  if (editor.isEditable()) {
2944
3014
  onEvent(event, editor);
2945
3015
  }
2946
3016
  } : event => {
3017
+ if (hasStoppedLexicalPropagation(event)) {
3018
+ return;
3019
+ }
3020
+
3021
+ stopLexicalPropagation(event);
3022
+
2947
3023
  if (editor.isEditable()) {
2948
3024
  switch (eventName) {
2949
3025
  case 'cut':
@@ -4337,7 +4413,8 @@ class RangeSelection {
4337
4413
  const sibling = siblings[i];
4338
4414
  const prevParent = sibling.getParentOrThrow();
4339
4415
 
4340
- if ($isElementNode(target) && !$isBlockElementNode(sibling) && !($isDecoratorNode(sibling) && !sibling.isInline())) {
4416
+ if ($isElementNode(target) && !$isBlockElementNode(sibling) && !($isDecoratorNode(sibling) && ( // Note: We are only looking for decorators that are inline and not isolated.
4417
+ !sibling.isInline() || sibling.isIsolated()))) {
4341
4418
  if (originalTarget === target) {
4342
4419
  target.append(sibling);
4343
4420
  } else {
@@ -4659,7 +4736,8 @@ class RangeSelection {
4659
4736
  if (domSelection.rangeCount > 0) {
4660
4737
  const range = domSelection.getRangeAt(0); // Apply the DOM selection to our Lexical selection.
4661
4738
 
4662
- const root = $getNearestRootOrShadowRoot(this.anchor.getNode());
4739
+ const anchorNode = this.anchor.getNode();
4740
+ const root = $isRootNode(anchorNode) ? anchorNode : $getNearestRootOrShadowRoot(anchorNode);
4663
4741
  this.applyDOMRange(range);
4664
4742
  this.dirty = true;
4665
4743
 
@@ -4724,6 +4802,24 @@ class RangeSelection {
4724
4802
  if ($isElementNode(nextSibling) && !nextSibling.canExtractContents()) {
4725
4803
  return;
4726
4804
  }
4805
+ } // Handle the deletion around decorators.
4806
+
4807
+
4808
+ const possibleNode = $getDecoratorNode(focus, isBackward);
4809
+
4810
+ if ($isDecoratorNode(possibleNode) && !possibleNode.isIsolated()) {
4811
+ // Make it possible to move selection from range selection to
4812
+ // node selection on the node.
4813
+ if (possibleNode.isKeyboardSelectable() && $isElementNode(anchorNode) && anchorNode.getChildrenSize() === 0) {
4814
+ anchorNode.remove();
4815
+ const nodeSelection = $createNodeSelection();
4816
+ nodeSelection.add(possibleNode.__key);
4817
+ $setSelection(nodeSelection);
4818
+ } else {
4819
+ possibleNode.remove();
4820
+ }
4821
+
4822
+ return;
4727
4823
  }
4728
4824
 
4729
4825
  this.modify('extend', isBackward, 'character');
@@ -5453,12 +5549,9 @@ function updateDOMSelection(prevSelection, nextSelection, editor, domSelection,
5453
5549
  rootElement.focus({
5454
5550
  preventScroll: true
5455
5551
  });
5456
- } // In Safari/iOS if we have selection on an element, then we also
5457
- // need to additionally set the DOM selection, otherwise a selectionchange
5458
- // event will not fire.
5459
-
5552
+ }
5460
5553
 
5461
- if (!(IS_IOS || IS_SAFARI) || anchor.type !== 'element') {
5554
+ if (anchor.type !== 'element') {
5462
5555
  return;
5463
5556
  }
5464
5557
  } // Apply the updated selection to the DOM. Note: this will trigger
@@ -5469,7 +5562,13 @@ function updateDOMSelection(prevSelection, nextSelection, editor, domSelection,
5469
5562
  domSelection.setBaseAndExtent(nextAnchorNode, nextAnchorOffset, nextFocusNode, nextFocusOffset);
5470
5563
 
5471
5564
  if (!tags.has('skip-scroll-into-view') && nextSelection.isCollapsed() && rootElement !== null && rootElement === activeElement) {
5472
- scrollIntoViewIfNeeded(editor, anchor, rootElement, tags);
5565
+ const selectionTarget = nextSelection instanceof RangeSelection && nextSelection.anchor.type === 'element' ? nextAnchorNode.childNodes[nextAnchorOffset] || null : domSelection.rangeCount > 0 ? domSelection.getRangeAt(0) : null;
5566
+
5567
+ if (selectionTarget !== null) {
5568
+ // @ts-ignore Text nodes do have getBoundingClientRect
5569
+ const selectionRect = selectionTarget.getBoundingClientRect();
5570
+ scrollIntoViewIfNeeded(editor, selectionRect, rootElement);
5571
+ }
5473
5572
  }
5474
5573
 
5475
5574
  markSelectionChangeFromDOMUpdate();
@@ -6112,7 +6211,7 @@ function beginUpdate(editor, updateFn, options) {
6112
6211
  let editorStateWasCloned = false;
6113
6212
 
6114
6213
  if (pendingEditorState === null || pendingEditorState._readOnly) {
6115
- pendingEditorState = editor._pendingEditorState = cloneEditorState(currentEditorState);
6214
+ pendingEditorState = editor._pendingEditorState = cloneEditorState(pendingEditorState || currentEditorState);
6116
6215
  editorStateWasCloned = true;
6117
6216
  }
6118
6217
 
@@ -6834,6 +6933,7 @@ class LexicalNode {
6834
6933
 
6835
6934
  replace(replaceWith) {
6836
6935
  errorOnReadOnly();
6936
+ errorOnInsertTextNodeOnRoot(this, replaceWith);
6837
6937
  const toReplaceKey = this.__key;
6838
6938
  const writableReplaceWith = replaceWith.getWritable();
6839
6939
  removeFromParent(writableReplaceWith);
@@ -6877,6 +6977,7 @@ class LexicalNode {
6877
6977
 
6878
6978
  insertAfter(nodeToInsert) {
6879
6979
  errorOnReadOnly();
6980
+ errorOnInsertTextNodeOnRoot(this, nodeToInsert);
6880
6981
  const writableSelf = this.getWritable();
6881
6982
  const writableNodeToInsert = nodeToInsert.getWritable();
6882
6983
  const oldParent = writableNodeToInsert.getParent();
@@ -6929,6 +7030,8 @@ class LexicalNode {
6929
7030
  }
6930
7031
 
6931
7032
  insertBefore(nodeToInsert) {
7033
+ errorOnReadOnly();
7034
+ errorOnInsertTextNodeOnRoot(this, nodeToInsert);
6932
7035
  const writableSelf = this.getWritable();
6933
7036
  const writableNodeToInsert = nodeToInsert.getWritable();
6934
7037
  removeFromParent(writableNodeToInsert);
@@ -7690,6 +7793,10 @@ class RootNode extends ElementNode {
7690
7793
  };
7691
7794
  }
7692
7795
 
7796
+ collapseAtStart() {
7797
+ return true;
7798
+ }
7799
+
7693
7800
  }
7694
7801
  function $createRootNode() {
7695
7802
  return new RootNode();
@@ -7861,7 +7968,7 @@ function convertLineBreakElement(node) {
7861
7968
  }
7862
7969
 
7863
7970
  function $createLineBreakNode() {
7864
- return new LineBreakNode();
7971
+ return $applyNodeReplacement(new LineBreakNode());
7865
7972
  }
7866
7973
  function $isLineBreakNode(node) {
7867
7974
  return node instanceof LineBreakNode;
@@ -8659,7 +8766,7 @@ function convertTextFormatElement(domNode) {
8659
8766
  }
8660
8767
 
8661
8768
  function $createTextNode(text = '') {
8662
- return new TextNode(text);
8769
+ return $applyNodeReplacement(new TextNode(text));
8663
8770
  }
8664
8771
  function $isTextNode(node) {
8665
8772
  return node instanceof TextNode;
@@ -8799,7 +8906,7 @@ function convertParagraphElement() {
8799
8906
  }
8800
8907
 
8801
8908
  function $createParagraphNode() {
8802
- return new ParagraphNode();
8909
+ return $applyNodeReplacement(new ParagraphNode());
8803
8910
  }
8804
8911
  function $isParagraphNode(node) {
8805
8912
  return node instanceof ParagraphNode;
@@ -8902,7 +9009,15 @@ function createEditor(editorConfig) {
8902
9009
  registeredNodes = new Map();
8903
9010
 
8904
9011
  for (let i = 0; i < nodes.length; i++) {
8905
- const klass = nodes[i]; // Ensure custom nodes implement required methods.
9012
+ let klass = nodes[i];
9013
+ let replacementClass = null;
9014
+
9015
+ if (typeof klass !== 'function') {
9016
+ const options = klass;
9017
+ klass = options.replace;
9018
+ replacementClass = options.with;
9019
+ } // Ensure custom nodes implement required methods.
9020
+
8906
9021
 
8907
9022
  {
8908
9023
  const name = klass.name;
@@ -8944,6 +9059,7 @@ function createEditor(editorConfig) {
8944
9059
  const type = klass.getType();
8945
9060
  registeredNodes.set(type, {
8946
9061
  klass,
9062
+ replace: replacementClass,
8947
9063
  transforms: new Set()
8948
9064
  });
8949
9065
  }
@@ -9336,7 +9452,7 @@ class LexicalEditor {
9336
9452
  * LICENSE file in the root directory of this source tree.
9337
9453
  *
9338
9454
  */
9339
- const VERSION = '0.4.1';
9455
+ const VERSION = '0.6.3';
9340
9456
 
9341
9457
  /**
9342
9458
  * Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -9390,6 +9506,8 @@ function DEPRECATED_$isGridRowNode(node) {
9390
9506
  }
9391
9507
 
9392
9508
  exports.$addUpdateTag = $addUpdateTag;
9509
+ exports.$applyNodeReplacement = $applyNodeReplacement;
9510
+ exports.$copyNode = $copyNode;
9393
9511
  exports.$createLineBreakNode = $createLineBreakNode;
9394
9512
  exports.$createNodeSelection = $createNodeSelection;
9395
9513
  exports.$createParagraphNode = $createParagraphNode;
package/Lexical.js.flow CHANGED
@@ -266,7 +266,10 @@ declare export function createEditor(editorConfig?: {
266
266
  namespace: string,
267
267
  theme?: EditorThemeClasses,
268
268
  parentEditor?: LexicalEditor,
269
- nodes?: $ReadOnlyArray<Class<LexicalNode>>,
269
+ nodes?: $ReadOnlyArray<
270
+ | Class<LexicalNode>
271
+ | {replace: Class<LexicalNode>, with: (node: LexicalNode) => LexicalNode},
272
+ >,
270
273
  onError: (error: Error) => void,
271
274
  disableEvents?: boolean,
272
275
  editable?: boolean,
@@ -354,9 +357,9 @@ declare export class LexicalNode {
354
357
  isParentOf(targetNode: LexicalNode): boolean;
355
358
  getNodesBetween(targetNode: LexicalNode): Array<LexicalNode>;
356
359
  isDirty(): boolean;
357
- // $FlowFixMe
360
+ // $FlowFixMe[incompatible-type]
358
361
  getLatest<T: LexicalNode>(this: T): T;
359
- // $FlowFixMe
362
+ // $FlowFixMe[incompatible-type]
360
363
  getWritable<T: LexicalNode>(this: T): T;
361
364
  getTextContent(includeDirectionless?: boolean): string;
362
365
  getTextContentSize(includeDirectionless?: boolean): number;
@@ -458,7 +461,7 @@ declare export class RangeSelection implements BaseSelection {
458
461
  focusOffset: number,
459
462
  ): void;
460
463
  getTextContent(): string;
461
- // $FlowFixMe DOM API
464
+ // $FlowFixMe[cannot-resolve-name] DOM API
462
465
  applyDOMRange(range: StaticRange): void;
463
466
  clone(): RangeSelection;
464
467
  toggleFormat(format: TextFormatType): void;
@@ -825,6 +828,10 @@ declare export function $hasAncestor(
825
828
  child: LexicalNode,
826
829
  targetNode: LexicalNode,
827
830
  ): boolean;
831
+ declare export function $copyNode(
832
+ node: ElementNode,
833
+ offset: number,
834
+ ): [ElementNode, ElementNode];
828
835
 
829
836
  /**
830
837
  * LexicalVersion
@@ -845,6 +852,10 @@ declare export function $parseSerializedNode(
845
852
  serializedNode: InternalSerializedNode,
846
853
  ): LexicalNode;
847
854
 
855
+ declare export function $applyNodeReplacement<N: LexicalNode>(
856
+ node: LexicalNode,
857
+ ): N;
858
+
848
859
  export type SerializedLexicalNode = {
849
860
  type: string,
850
861
  version: number,