lexical 0.33.2-nightly.20250724.0 → 0.33.2-nightly.20250725.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.
package/Lexical.dev.js CHANGED
@@ -369,7 +369,11 @@ function flushMutations(editor, mutations, observer) {
369
369
  if (type === 'characterData') {
370
370
  // Text mutations are deferred and passed to mutation listeners to be
371
371
  // processed outside of the Lexical engine.
372
- if (shouldFlushTextMutations && $isTextNode(targetNode) && isDOMTextNode(targetDOM) && shouldUpdateTextNodeFromMutation(selection, targetDOM, targetNode)) {
372
+ if (
373
+ // TODO there is an edge case here if a mutation happens too quickly
374
+ // after text input, it may never be handled since we do not
375
+ // track the ignored mutations in any way
376
+ shouldFlushTextMutations && $isTextNode(targetNode) && isDOMTextNode(targetDOM) && shouldUpdateTextNodeFromMutation(selection, targetDOM, targetNode)) {
373
377
  $handleTextMutation(targetDOM, targetNode, editor);
374
378
  }
375
379
  } else if (type === 'childList') {
@@ -7196,7 +7200,7 @@ function $internalResolveSelectionPoint(dom, offset, lastPoint, editor) {
7196
7200
  }
7197
7201
  resolvedNode = $getNodeFromDOM(childDOM);
7198
7202
  if ($isTextNode(resolvedNode)) {
7199
- resolvedOffset = getTextNodeOffset(resolvedNode, moveSelectionToEnd);
7203
+ resolvedOffset = $getTextNodeOffset(resolvedNode, moveSelectionToEnd ? 'next' : 'previous');
7200
7204
  } else {
7201
7205
  let resolvedElement = $getNodeFromDOM(dom);
7202
7206
  // Ensure resolvedElement is actually a element.
@@ -7231,7 +7235,7 @@ function $internalResolveSelectionPoint(dom, offset, lastPoint, editor) {
7231
7235
  if ($isTextNode(child)) {
7232
7236
  resolvedNode = child;
7233
7237
  resolvedElement = null;
7234
- resolvedOffset = getTextNodeOffset(child, moveSelectionToEnd);
7238
+ resolvedOffset = $getTextNodeOffset(child, moveSelectionToEnd ? 'next' : 'previous');
7235
7239
  } else if (child !== resolvedElement && moveSelectionToEnd && !hasBlockCursor) {
7236
7240
  if (!$isElementNode(resolvedElement)) {
7237
7241
  formatDevErrorMessage(`invariant`);
@@ -7260,7 +7264,7 @@ function $internalResolveSelectionPoint(dom, offset, lastPoint, editor) {
7260
7264
  if (!$isTextNode(resolvedNode)) {
7261
7265
  return null;
7262
7266
  }
7263
- return $createPoint(resolvedNode.__key, resolvedOffset, 'text');
7267
+ return $createPoint(resolvedNode.__key, $getTextNodeOffset(resolvedNode, resolvedOffset, 'clamp'), 'text');
7264
7268
  }
7265
7269
  function resolveSelectionPointOnBoundary(point, isBackward, isCollapsed) {
7266
7270
  const offset = point.offset;
@@ -7327,8 +7331,8 @@ function $internalResolveSelectionPoints(anchorDOM, anchorOffset, focusDOM, focu
7327
7331
  return null;
7328
7332
  }
7329
7333
  {
7330
- $validatePoint(editor, 'anchor', resolvedAnchorPoint);
7331
- $validatePoint(editor, 'focus', resolvedAnchorPoint);
7334
+ $validatePoint('anchor', resolvedAnchorPoint);
7335
+ $validatePoint('focus', resolvedFocusPoint);
7332
7336
  }
7333
7337
  if (resolvedAnchorPoint.type === 'element' && resolvedFocusPoint.type === 'element') {
7334
7338
  const anchorNode = $getNodeFromDOM(anchorDOM);
@@ -7427,7 +7431,7 @@ function $internalCreateRangeSelection(lastSelection, domSelection, editor, even
7427
7431
  const [resolvedAnchorPoint, resolvedFocusPoint] = resolvedSelectionPoints;
7428
7432
  return new RangeSelection(resolvedAnchorPoint, resolvedFocusPoint, !$isRangeSelection(lastSelection) ? 0 : lastSelection.format, !$isRangeSelection(lastSelection) ? '' : lastSelection.style);
7429
7433
  }
7430
- function $validatePoint(editor, name, point) {
7434
+ function $validatePoint(name, point) {
7431
7435
  const node = $getNodeByKey(point.key);
7432
7436
  if (!(node !== undefined)) {
7433
7437
  formatDevErrorMessage(`$validatePoint: ${name} key ${point.key} not found in current editorState`);
@@ -8507,7 +8511,7 @@ function triggerDeferredUpdateCallbacks(editor, deferred) {
8507
8511
  }
8508
8512
  }
8509
8513
  }
8510
- function processNestedUpdates(editor, initialSkipTransforms) {
8514
+ function $processNestedUpdates(editor, initialSkipTransforms) {
8511
8515
  const queuedUpdates = editor._updates;
8512
8516
  let skipTransforms = initialSkipTransforms || false;
8513
8517
 
@@ -8518,6 +8522,7 @@ function processNestedUpdates(editor, initialSkipTransforms) {
8518
8522
  const queuedUpdate = queuedUpdates.shift();
8519
8523
  if (queuedUpdate) {
8520
8524
  const [nextUpdateFn, options] = queuedUpdate;
8525
+ const pendingEditorState = editor._pendingEditorState;
8521
8526
  let onUpdate;
8522
8527
  if (options !== undefined) {
8523
8528
  onUpdate = options.onUpdate;
@@ -8525,7 +8530,6 @@ function processNestedUpdates(editor, initialSkipTransforms) {
8525
8530
  skipTransforms = true;
8526
8531
  }
8527
8532
  if (options.discrete) {
8528
- const pendingEditorState = editor._pendingEditorState;
8529
8533
  if (!(pendingEditorState !== null)) {
8530
8534
  formatDevErrorMessage(`Unexpected empty pending editor state on discrete nested update`);
8531
8535
  }
@@ -8536,7 +8540,11 @@ function processNestedUpdates(editor, initialSkipTransforms) {
8536
8540
  }
8537
8541
  addTags(editor, options.tag);
8538
8542
  }
8539
- nextUpdateFn();
8543
+ if (pendingEditorState == null) {
8544
+ $beginUpdate(editor, nextUpdateFn, options);
8545
+ } else {
8546
+ nextUpdateFn();
8547
+ }
8540
8548
  }
8541
8549
  }
8542
8550
  return skipTransforms;
@@ -8585,7 +8593,7 @@ function $beginUpdate(editor, updateFn, options) {
8585
8593
  }
8586
8594
  const startingCompositionKey = editor._compositionKey;
8587
8595
  updateFn();
8588
- skipTransforms = processNestedUpdates(editor, skipTransforms);
8596
+ skipTransforms = $processNestedUpdates(editor, skipTransforms);
8589
8597
  applySelectionTransforms(pendingEditorState, editor);
8590
8598
  if (editor._dirtyType !== NO_DIRTY_NODES) {
8591
8599
  if (skipTransforms) {
@@ -8593,7 +8601,7 @@ function $beginUpdate(editor, updateFn, options) {
8593
8601
  } else {
8594
8602
  $applyAllTransforms(pendingEditorState, editor);
8595
8603
  }
8596
- processNestedUpdates(editor);
8604
+ $processNestedUpdates(editor);
8597
8605
  $garbageCollectDetachedNodes(currentEditorState, pendingEditorState, editor._dirtyLeaves, editor._dirtyElements);
8598
8606
  }
8599
8607
  const endingCompositionKey = editor._compositionKey;
@@ -10763,7 +10771,7 @@ class LexicalEditor {
10763
10771
  };
10764
10772
  }
10765
10773
  }
10766
- LexicalEditor.version = "0.33.2-nightly.20250724.0+dev.cjs";
10774
+ LexicalEditor.version = "0.33.2-nightly.20250725.0+dev.cjs";
10767
10775
 
10768
10776
  let pendingNodeToClone = null;
10769
10777
  function setPendingNodeToClone(pendingNode) {
@@ -11213,9 +11221,6 @@ function $getNodeFromDOM(dom) {
11213
11221
  }
11214
11222
  return $getNodeByKey(nodeKey);
11215
11223
  }
11216
- function getTextNodeOffset(node, moveSelectionToEnd) {
11217
- return moveSelectionToEnd ? node.getTextContentSize() : 0;
11218
- }
11219
11224
  function getNodeKeyFromDOMTree(
11220
11225
  // Note that node here refers to a DOM Node, not an Lexical Node
11221
11226
  dom, editor) {
@@ -11323,7 +11328,7 @@ function $updateTextNodeFromDOMContent(textNode, textContent, anchorOffset, focu
11323
11328
  }
11324
11329
  const selection = $getSelection();
11325
11330
  if (!$isRangeSelection(selection) || anchorOffset === null || focusOffset === null) {
11326
- node.setTextContent(normalizedTextContent);
11331
+ $setTextContentWithSelection(node, normalizedTextContent, selection);
11327
11332
  return;
11328
11333
  }
11329
11334
  selection.setTextNodeRange(node, anchorOffset, node, focusOffset);
@@ -11333,7 +11338,19 @@ function $updateTextNodeFromDOMContent(textNode, textContent, anchorOffset, focu
11333
11338
  node.replace(replacement);
11334
11339
  node = replacement;
11335
11340
  }
11336
- node.setTextContent(normalizedTextContent);
11341
+ $setTextContentWithSelection(node, normalizedTextContent, selection);
11342
+ }
11343
+ }
11344
+ }
11345
+ function $setTextContentWithSelection(node, textContent, selection) {
11346
+ node.setTextContent(textContent);
11347
+ if ($isRangeSelection(selection)) {
11348
+ const key = node.getKey();
11349
+ for (const k of ['anchor', 'focus']) {
11350
+ const pt = selection[k];
11351
+ if (pt.type === 'text' && pt.key === key) {
11352
+ pt.offset = $getTextNodeOffset(node, pt.offset, 'clamp');
11353
+ }
11337
11354
  }
11338
11355
  }
11339
11356
  }
@@ -12762,13 +12779,14 @@ function $getTextPointCaret(origin, direction, offset) {
12762
12779
  *
12763
12780
  * @param origin a TextNode
12764
12781
  * @param offset An absolute offset into the TextNode string, or a direction for which end to use as the offset
12782
+ * @param mode If 'error' (the default) out of bounds offsets will be an error in dev. Otherwise it will clamp to a valid offset.
12765
12783
  * @returns An absolute offset into the TextNode string
12766
12784
  */
12767
- function $getTextNodeOffset(origin, offset) {
12785
+ function $getTextNodeOffset(origin, offset, mode = 'error') {
12768
12786
  const size = origin.getTextContentSize();
12769
12787
  let numericOffset = offset === 'next' ? size : offset === 'previous' ? 0 : offset;
12770
12788
  if (numericOffset < 0 || numericOffset > size) {
12771
- {
12789
+ if (!(mode === 'clamp')) {
12772
12790
  formatDevErrorMessage(`$getTextNodeOffset: invalid offset ${String(offset)} for size ${String(size)} at key ${origin.getKey()}`);
12773
12791
  } // Clamp invalid offsets in prod
12774
12792
  numericOffset = numericOffset < 0 ? 0 : size;
package/Lexical.dev.mjs CHANGED
@@ -367,7 +367,11 @@ function flushMutations(editor, mutations, observer) {
367
367
  if (type === 'characterData') {
368
368
  // Text mutations are deferred and passed to mutation listeners to be
369
369
  // processed outside of the Lexical engine.
370
- if (shouldFlushTextMutations && $isTextNode(targetNode) && isDOMTextNode(targetDOM) && shouldUpdateTextNodeFromMutation(selection, targetDOM, targetNode)) {
370
+ if (
371
+ // TODO there is an edge case here if a mutation happens too quickly
372
+ // after text input, it may never be handled since we do not
373
+ // track the ignored mutations in any way
374
+ shouldFlushTextMutations && $isTextNode(targetNode) && isDOMTextNode(targetDOM) && shouldUpdateTextNodeFromMutation(selection, targetDOM, targetNode)) {
371
375
  $handleTextMutation(targetDOM, targetNode, editor);
372
376
  }
373
377
  } else if (type === 'childList') {
@@ -7194,7 +7198,7 @@ function $internalResolveSelectionPoint(dom, offset, lastPoint, editor) {
7194
7198
  }
7195
7199
  resolvedNode = $getNodeFromDOM(childDOM);
7196
7200
  if ($isTextNode(resolvedNode)) {
7197
- resolvedOffset = getTextNodeOffset(resolvedNode, moveSelectionToEnd);
7201
+ resolvedOffset = $getTextNodeOffset(resolvedNode, moveSelectionToEnd ? 'next' : 'previous');
7198
7202
  } else {
7199
7203
  let resolvedElement = $getNodeFromDOM(dom);
7200
7204
  // Ensure resolvedElement is actually a element.
@@ -7229,7 +7233,7 @@ function $internalResolveSelectionPoint(dom, offset, lastPoint, editor) {
7229
7233
  if ($isTextNode(child)) {
7230
7234
  resolvedNode = child;
7231
7235
  resolvedElement = null;
7232
- resolvedOffset = getTextNodeOffset(child, moveSelectionToEnd);
7236
+ resolvedOffset = $getTextNodeOffset(child, moveSelectionToEnd ? 'next' : 'previous');
7233
7237
  } else if (child !== resolvedElement && moveSelectionToEnd && !hasBlockCursor) {
7234
7238
  if (!$isElementNode(resolvedElement)) {
7235
7239
  formatDevErrorMessage(`invariant`);
@@ -7258,7 +7262,7 @@ function $internalResolveSelectionPoint(dom, offset, lastPoint, editor) {
7258
7262
  if (!$isTextNode(resolvedNode)) {
7259
7263
  return null;
7260
7264
  }
7261
- return $createPoint(resolvedNode.__key, resolvedOffset, 'text');
7265
+ return $createPoint(resolvedNode.__key, $getTextNodeOffset(resolvedNode, resolvedOffset, 'clamp'), 'text');
7262
7266
  }
7263
7267
  function resolveSelectionPointOnBoundary(point, isBackward, isCollapsed) {
7264
7268
  const offset = point.offset;
@@ -7325,8 +7329,8 @@ function $internalResolveSelectionPoints(anchorDOM, anchorOffset, focusDOM, focu
7325
7329
  return null;
7326
7330
  }
7327
7331
  {
7328
- $validatePoint(editor, 'anchor', resolvedAnchorPoint);
7329
- $validatePoint(editor, 'focus', resolvedAnchorPoint);
7332
+ $validatePoint('anchor', resolvedAnchorPoint);
7333
+ $validatePoint('focus', resolvedFocusPoint);
7330
7334
  }
7331
7335
  if (resolvedAnchorPoint.type === 'element' && resolvedFocusPoint.type === 'element') {
7332
7336
  const anchorNode = $getNodeFromDOM(anchorDOM);
@@ -7425,7 +7429,7 @@ function $internalCreateRangeSelection(lastSelection, domSelection, editor, even
7425
7429
  const [resolvedAnchorPoint, resolvedFocusPoint] = resolvedSelectionPoints;
7426
7430
  return new RangeSelection(resolvedAnchorPoint, resolvedFocusPoint, !$isRangeSelection(lastSelection) ? 0 : lastSelection.format, !$isRangeSelection(lastSelection) ? '' : lastSelection.style);
7427
7431
  }
7428
- function $validatePoint(editor, name, point) {
7432
+ function $validatePoint(name, point) {
7429
7433
  const node = $getNodeByKey(point.key);
7430
7434
  if (!(node !== undefined)) {
7431
7435
  formatDevErrorMessage(`$validatePoint: ${name} key ${point.key} not found in current editorState`);
@@ -8505,7 +8509,7 @@ function triggerDeferredUpdateCallbacks(editor, deferred) {
8505
8509
  }
8506
8510
  }
8507
8511
  }
8508
- function processNestedUpdates(editor, initialSkipTransforms) {
8512
+ function $processNestedUpdates(editor, initialSkipTransforms) {
8509
8513
  const queuedUpdates = editor._updates;
8510
8514
  let skipTransforms = initialSkipTransforms || false;
8511
8515
 
@@ -8516,6 +8520,7 @@ function processNestedUpdates(editor, initialSkipTransforms) {
8516
8520
  const queuedUpdate = queuedUpdates.shift();
8517
8521
  if (queuedUpdate) {
8518
8522
  const [nextUpdateFn, options] = queuedUpdate;
8523
+ const pendingEditorState = editor._pendingEditorState;
8519
8524
  let onUpdate;
8520
8525
  if (options !== undefined) {
8521
8526
  onUpdate = options.onUpdate;
@@ -8523,7 +8528,6 @@ function processNestedUpdates(editor, initialSkipTransforms) {
8523
8528
  skipTransforms = true;
8524
8529
  }
8525
8530
  if (options.discrete) {
8526
- const pendingEditorState = editor._pendingEditorState;
8527
8531
  if (!(pendingEditorState !== null)) {
8528
8532
  formatDevErrorMessage(`Unexpected empty pending editor state on discrete nested update`);
8529
8533
  }
@@ -8534,7 +8538,11 @@ function processNestedUpdates(editor, initialSkipTransforms) {
8534
8538
  }
8535
8539
  addTags(editor, options.tag);
8536
8540
  }
8537
- nextUpdateFn();
8541
+ if (pendingEditorState == null) {
8542
+ $beginUpdate(editor, nextUpdateFn, options);
8543
+ } else {
8544
+ nextUpdateFn();
8545
+ }
8538
8546
  }
8539
8547
  }
8540
8548
  return skipTransforms;
@@ -8583,7 +8591,7 @@ function $beginUpdate(editor, updateFn, options) {
8583
8591
  }
8584
8592
  const startingCompositionKey = editor._compositionKey;
8585
8593
  updateFn();
8586
- skipTransforms = processNestedUpdates(editor, skipTransforms);
8594
+ skipTransforms = $processNestedUpdates(editor, skipTransforms);
8587
8595
  applySelectionTransforms(pendingEditorState, editor);
8588
8596
  if (editor._dirtyType !== NO_DIRTY_NODES) {
8589
8597
  if (skipTransforms) {
@@ -8591,7 +8599,7 @@ function $beginUpdate(editor, updateFn, options) {
8591
8599
  } else {
8592
8600
  $applyAllTransforms(pendingEditorState, editor);
8593
8601
  }
8594
- processNestedUpdates(editor);
8602
+ $processNestedUpdates(editor);
8595
8603
  $garbageCollectDetachedNodes(currentEditorState, pendingEditorState, editor._dirtyLeaves, editor._dirtyElements);
8596
8604
  }
8597
8605
  const endingCompositionKey = editor._compositionKey;
@@ -10761,7 +10769,7 @@ class LexicalEditor {
10761
10769
  };
10762
10770
  }
10763
10771
  }
10764
- LexicalEditor.version = "0.33.2-nightly.20250724.0+dev.esm";
10772
+ LexicalEditor.version = "0.33.2-nightly.20250725.0+dev.esm";
10765
10773
 
10766
10774
  let pendingNodeToClone = null;
10767
10775
  function setPendingNodeToClone(pendingNode) {
@@ -11211,9 +11219,6 @@ function $getNodeFromDOM(dom) {
11211
11219
  }
11212
11220
  return $getNodeByKey(nodeKey);
11213
11221
  }
11214
- function getTextNodeOffset(node, moveSelectionToEnd) {
11215
- return moveSelectionToEnd ? node.getTextContentSize() : 0;
11216
- }
11217
11222
  function getNodeKeyFromDOMTree(
11218
11223
  // Note that node here refers to a DOM Node, not an Lexical Node
11219
11224
  dom, editor) {
@@ -11321,7 +11326,7 @@ function $updateTextNodeFromDOMContent(textNode, textContent, anchorOffset, focu
11321
11326
  }
11322
11327
  const selection = $getSelection();
11323
11328
  if (!$isRangeSelection(selection) || anchorOffset === null || focusOffset === null) {
11324
- node.setTextContent(normalizedTextContent);
11329
+ $setTextContentWithSelection(node, normalizedTextContent, selection);
11325
11330
  return;
11326
11331
  }
11327
11332
  selection.setTextNodeRange(node, anchorOffset, node, focusOffset);
@@ -11331,7 +11336,19 @@ function $updateTextNodeFromDOMContent(textNode, textContent, anchorOffset, focu
11331
11336
  node.replace(replacement);
11332
11337
  node = replacement;
11333
11338
  }
11334
- node.setTextContent(normalizedTextContent);
11339
+ $setTextContentWithSelection(node, normalizedTextContent, selection);
11340
+ }
11341
+ }
11342
+ }
11343
+ function $setTextContentWithSelection(node, textContent, selection) {
11344
+ node.setTextContent(textContent);
11345
+ if ($isRangeSelection(selection)) {
11346
+ const key = node.getKey();
11347
+ for (const k of ['anchor', 'focus']) {
11348
+ const pt = selection[k];
11349
+ if (pt.type === 'text' && pt.key === key) {
11350
+ pt.offset = $getTextNodeOffset(node, pt.offset, 'clamp');
11351
+ }
11335
11352
  }
11336
11353
  }
11337
11354
  }
@@ -12760,13 +12777,14 @@ function $getTextPointCaret(origin, direction, offset) {
12760
12777
  *
12761
12778
  * @param origin a TextNode
12762
12779
  * @param offset An absolute offset into the TextNode string, or a direction for which end to use as the offset
12780
+ * @param mode If 'error' (the default) out of bounds offsets will be an error in dev. Otherwise it will clamp to a valid offset.
12763
12781
  * @returns An absolute offset into the TextNode string
12764
12782
  */
12765
- function $getTextNodeOffset(origin, offset) {
12783
+ function $getTextNodeOffset(origin, offset, mode = 'error') {
12766
12784
  const size = origin.getTextContentSize();
12767
12785
  let numericOffset = offset === 'next' ? size : offset === 'previous' ? 0 : offset;
12768
12786
  if (numericOffset < 0 || numericOffset > size) {
12769
- {
12787
+ if (!(mode === 'clamp')) {
12770
12788
  formatDevErrorMessage(`$getTextNodeOffset: invalid offset ${String(offset)} for size ${String(size)} at key ${origin.getKey()}`);
12771
12789
  } // Clamp invalid offsets in prod
12772
12790
  numericOffset = numericOffset < 0 ? 0 : size;