lexical 0.38.3-nightly.20251106.0 → 0.38.3-nightly.20251110.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.mjs CHANGED
@@ -1927,6 +1927,10 @@ function createCommand(type) {
1927
1927
  const SELECTION_CHANGE_COMMAND = createCommand('SELECTION_CHANGE_COMMAND');
1928
1928
  const SELECTION_INSERT_CLIPBOARD_NODES_COMMAND = createCommand('SELECTION_INSERT_CLIPBOARD_NODES_COMMAND');
1929
1929
  const CLICK_COMMAND = createCommand('CLICK_COMMAND');
1930
+ const BEFORE_INPUT_COMMAND = createCommand('BEFORE_INPUT_COMMAND');
1931
+ const INPUT_COMMAND = createCommand('INPUT_COMMAND');
1932
+ const COMPOSITION_START_COMMAND = createCommand('COMPOSITION_START_COMMAND');
1933
+ const COMPOSITION_END_COMMAND = createCommand('COMPOSITION_END_COMMAND');
1930
1934
  /**
1931
1935
  * Dispatched to delete a character, the payload will be `true` if the deletion
1932
1936
  * is backwards (backspace or delete on macOS) and `false` if forwards
@@ -2358,9 +2362,15 @@ function $canRemoveText(anchorNode, focusNode) {
2358
2362
  function isPossiblyAndroidKeyPress(timeStamp) {
2359
2363
  return lastKeyCode === 'MediaLast' && timeStamp < lastKeyDownTimeStamp + ANDROID_COMPOSITION_LATENCY;
2360
2364
  }
2365
+ function registerDefaultCommandHandlers(editor) {
2366
+ editor.registerCommand(BEFORE_INPUT_COMMAND, $handleBeforeInput, COMMAND_PRIORITY_EDITOR);
2367
+ editor.registerCommand(INPUT_COMMAND, $handleInput, COMMAND_PRIORITY_EDITOR);
2368
+ editor.registerCommand(COMPOSITION_START_COMMAND, $handleCompositionStart, COMMAND_PRIORITY_EDITOR);
2369
+ editor.registerCommand(COMPOSITION_END_COMMAND, $handleCompositionEnd, COMMAND_PRIORITY_EDITOR);
2370
+ editor.registerCommand(KEY_DOWN_COMMAND, $handleKeyDown, COMMAND_PRIORITY_EDITOR);
2371
+ }
2361
2372
  function onBeforeInput(event, editor) {
2362
2373
  const inputType = event.inputType;
2363
- const targetRange = getTargetRange(event);
2364
2374
 
2365
2375
  // We let the browser do its own thing for composition.
2366
2376
  if (inputType === 'deleteCompositionText' ||
@@ -2374,237 +2384,242 @@ function onBeforeInput(event, editor) {
2374
2384
  } else if (inputType === 'insertCompositionText') {
2375
2385
  return;
2376
2386
  }
2377
- updateEditorSync(editor, () => {
2378
- const selection = $getSelection();
2379
- if (inputType === 'deleteContentBackward') {
2380
- if (selection === null) {
2381
- // Use previous selection
2382
- const prevSelection = $getPreviousSelection();
2383
- if (!$isRangeSelection(prevSelection)) {
2384
- return;
2385
- }
2386
- $setSelection(prevSelection.clone());
2387
+ dispatchCommand(editor, BEFORE_INPUT_COMMAND, event);
2388
+ }
2389
+ function $handleBeforeInput(event) {
2390
+ const inputType = event.inputType;
2391
+ const targetRange = getTargetRange(event);
2392
+ const editor = getActiveEditor();
2393
+ const selection = $getSelection();
2394
+ if (inputType === 'deleteContentBackward') {
2395
+ if (selection === null) {
2396
+ // Use previous selection
2397
+ const prevSelection = $getPreviousSelection();
2398
+ if (!$isRangeSelection(prevSelection)) {
2399
+ return true;
2387
2400
  }
2388
- if ($isRangeSelection(selection)) {
2389
- const isSelectionAnchorSameAsFocus = selection.anchor.key === selection.focus.key;
2390
- if (isPossiblyAndroidKeyPress(event.timeStamp) && editor.isComposing() && isSelectionAnchorSameAsFocus) {
2391
- $setCompositionKey(null);
2392
- lastKeyDownTimeStamp = 0;
2393
- // Fixes an Android bug where selection flickers when backspacing
2394
- setTimeout(() => {
2395
- updateEditorSync(editor, () => {
2396
- $setCompositionKey(null);
2397
- });
2398
- }, ANDROID_COMPOSITION_LATENCY);
2399
- if ($isRangeSelection(selection)) {
2400
- const anchorNode = selection.anchor.getNode();
2401
- anchorNode.markDirty();
2402
- if (!$isTextNode(anchorNode)) {
2403
- formatDevErrorMessage(`Anchor node must be a TextNode`);
2404
- }
2405
- $updateSelectionFormatStyleFromTextNode(selection, anchorNode);
2406
- }
2407
- } else {
2408
- $setCompositionKey(null);
2409
- event.preventDefault();
2410
- // Chromium Android at the moment seems to ignore the preventDefault
2411
- // on 'deleteContentBackward' and still deletes the content. Which leads
2412
- // to multiple deletions. So we let the browser handle the deletion in this case.
2413
- const selectedNode = selection.anchor.getNode();
2414
- const selectedNodeText = selectedNode.getTextContent();
2415
- // When the target node has `canInsertTextAfter` set to false, the first deletion
2416
- // doesn't have an effect, so we need to handle it with Lexical.
2417
- const selectedNodeCanInsertTextAfter = selectedNode.canInsertTextAfter();
2418
- const hasSelectedAllTextInNode = selection.anchor.offset === 0 && selection.focus.offset === selectedNodeText.length;
2419
- let shouldLetBrowserHandleDelete = IS_ANDROID_CHROME && isSelectionAnchorSameAsFocus && !hasSelectedAllTextInNode && selectedNodeCanInsertTextAfter;
2420
- // Check if selection is collapsed and if the previous node is a decorator node
2421
- // If so, the browser will not be able to handle the deletion
2422
- if (shouldLetBrowserHandleDelete && selection.isCollapsed()) {
2423
- shouldLetBrowserHandleDelete = !$isDecoratorNode($getAdjacentNode(selection.anchor, true));
2401
+ $setSelection(prevSelection.clone());
2402
+ }
2403
+ if ($isRangeSelection(selection)) {
2404
+ const isSelectionAnchorSameAsFocus = selection.anchor.key === selection.focus.key;
2405
+ if (isPossiblyAndroidKeyPress(event.timeStamp) && editor.isComposing() && isSelectionAnchorSameAsFocus) {
2406
+ $setCompositionKey(null);
2407
+ lastKeyDownTimeStamp = 0;
2408
+ // Fixes an Android bug where selection flickers when backspacing
2409
+ setTimeout(() => {
2410
+ updateEditorSync(editor, () => {
2411
+ $setCompositionKey(null);
2412
+ });
2413
+ }, ANDROID_COMPOSITION_LATENCY);
2414
+ if ($isRangeSelection(selection)) {
2415
+ const anchorNode = selection.anchor.getNode();
2416
+ anchorNode.markDirty();
2417
+ if (!$isTextNode(anchorNode)) {
2418
+ formatDevErrorMessage(`Anchor node must be a TextNode`);
2424
2419
  }
2425
- if (!shouldLetBrowserHandleDelete) {
2426
- dispatchCommand(editor, DELETE_CHARACTER_COMMAND, true);
2427
- // When deleting across paragraphs, Chrome on Android incorrectly shifts the selection rightwards
2428
- // We save the correct selection to restore later during handling of selectionchange event
2429
- const selectionAfterDelete = $getSelection();
2430
- if (IS_ANDROID_CHROME && $isRangeSelection(selectionAfterDelete) && selectionAfterDelete.isCollapsed()) {
2431
- postDeleteSelectionToRestore = selectionAfterDelete;
2432
- // Cleanup in case selectionchange does not fire
2433
- setTimeout(() => postDeleteSelectionToRestore = null);
2434
- }
2420
+ $updateSelectionFormatStyleFromTextNode(selection, anchorNode);
2421
+ }
2422
+ } else {
2423
+ $setCompositionKey(null);
2424
+ event.preventDefault();
2425
+ // Chromium Android at the moment seems to ignore the preventDefault
2426
+ // on 'deleteContentBackward' and still deletes the content. Which leads
2427
+ // to multiple deletions. So we let the browser handle the deletion in this case.
2428
+ const selectedNode = selection.anchor.getNode();
2429
+ const selectedNodeText = selectedNode.getTextContent();
2430
+ // When the target node has `canInsertTextAfter` set to false, the first deletion
2431
+ // doesn't have an effect, so we need to handle it with Lexical.
2432
+ const selectedNodeCanInsertTextAfter = selectedNode.canInsertTextAfter();
2433
+ const hasSelectedAllTextInNode = selection.anchor.offset === 0 && selection.focus.offset === selectedNodeText.length;
2434
+ let shouldLetBrowserHandleDelete = IS_ANDROID_CHROME && isSelectionAnchorSameAsFocus && !hasSelectedAllTextInNode && selectedNodeCanInsertTextAfter;
2435
+ // Check if selection is collapsed and if the previous node is a decorator node
2436
+ // If so, the browser will not be able to handle the deletion
2437
+ if (shouldLetBrowserHandleDelete && selection.isCollapsed()) {
2438
+ shouldLetBrowserHandleDelete = !$isDecoratorNode($getAdjacentNode(selection.anchor, true));
2439
+ }
2440
+ if (!shouldLetBrowserHandleDelete) {
2441
+ dispatchCommand(editor, DELETE_CHARACTER_COMMAND, true);
2442
+ // When deleting across paragraphs, Chrome on Android incorrectly shifts the selection rightwards
2443
+ // We save the correct selection to restore later during handling of selectionchange event
2444
+ const selectionAfterDelete = $getSelection();
2445
+ if (IS_ANDROID_CHROME && $isRangeSelection(selectionAfterDelete) && selectionAfterDelete.isCollapsed()) {
2446
+ postDeleteSelectionToRestore = selectionAfterDelete;
2447
+ // Cleanup in case selectionchange does not fire
2448
+ setTimeout(() => postDeleteSelectionToRestore = null);
2435
2449
  }
2436
2450
  }
2437
- return;
2438
2451
  }
2452
+ return true;
2439
2453
  }
2440
- if (!$isRangeSelection(selection)) {
2441
- return;
2442
- }
2443
- const data = event.data;
2454
+ }
2455
+ if (!$isRangeSelection(selection)) {
2456
+ return true;
2457
+ }
2458
+ const data = event.data;
2444
2459
 
2445
- // This represents the case when two beforeinput events are triggered at the same time (without a
2446
- // full event loop ending at input). This happens with MacOS with the default keyboard settings,
2447
- // a combination of autocorrection + autocapitalization.
2448
- // Having Lexical run everything in controlled mode would fix the issue without additional code
2449
- // but this would kill the massive performance win from the most common typing event.
2450
- // Alternatively, when this happens we can prematurely update our EditorState based on the DOM
2451
- // content, a job that would usually be the input event's responsibility.
2452
- if (unprocessedBeforeInputData !== null) {
2453
- $updateSelectedTextFromDOM(false, editor, unprocessedBeforeInputData);
2454
- }
2455
- if ((!selection.dirty || unprocessedBeforeInputData !== null) && selection.isCollapsed() && !$isRootNode(selection.anchor.getNode()) && targetRange !== null) {
2456
- selection.applyDOMRange(targetRange);
2460
+ // This represents the case when two beforeinput events are triggered at the same time (without a
2461
+ // full event loop ending at input). This happens with MacOS with the default keyboard settings,
2462
+ // a combination of autocorrection + autocapitalization.
2463
+ // Having Lexical run everything in controlled mode would fix the issue without additional code
2464
+ // but this would kill the massive performance win from the most common typing event.
2465
+ // Alternatively, when this happens we can prematurely update our EditorState based on the DOM
2466
+ // content, a job that would usually be the input event's responsibility.
2467
+ if (unprocessedBeforeInputData !== null) {
2468
+ $updateSelectedTextFromDOM(false, editor, unprocessedBeforeInputData);
2469
+ }
2470
+ if ((!selection.dirty || unprocessedBeforeInputData !== null) && selection.isCollapsed() && !$isRootNode(selection.anchor.getNode()) && targetRange !== null) {
2471
+ selection.applyDOMRange(targetRange);
2472
+ }
2473
+ unprocessedBeforeInputData = null;
2474
+ const anchor = selection.anchor;
2475
+ const focus = selection.focus;
2476
+ const anchorNode = anchor.getNode();
2477
+ const focusNode = focus.getNode();
2478
+ if (inputType === 'insertText' || inputType === 'insertTranspose') {
2479
+ if (data === '\n') {
2480
+ event.preventDefault();
2481
+ dispatchCommand(editor, INSERT_LINE_BREAK_COMMAND, false);
2482
+ } else if (data === DOUBLE_LINE_BREAK) {
2483
+ event.preventDefault();
2484
+ dispatchCommand(editor, INSERT_PARAGRAPH_COMMAND, undefined);
2485
+ } else if (data == null && event.dataTransfer) {
2486
+ // Gets around a Safari text replacement bug.
2487
+ const text = event.dataTransfer.getData('text/plain');
2488
+ event.preventDefault();
2489
+ selection.insertRawText(text);
2490
+ } else if (data != null && $shouldPreventDefaultAndInsertText(selection, targetRange, data, event.timeStamp, true)) {
2491
+ event.preventDefault();
2492
+ dispatchCommand(editor, CONTROLLED_TEXT_INSERTION_COMMAND, data);
2493
+ } else {
2494
+ unprocessedBeforeInputData = data;
2457
2495
  }
2458
- unprocessedBeforeInputData = null;
2459
- const anchor = selection.anchor;
2460
- const focus = selection.focus;
2461
- const anchorNode = anchor.getNode();
2462
- const focusNode = focus.getNode();
2463
- if (inputType === 'insertText' || inputType === 'insertTranspose') {
2464
- if (data === '\n') {
2465
- event.preventDefault();
2496
+ lastBeforeInputInsertTextTimeStamp = event.timeStamp;
2497
+ return true;
2498
+ }
2499
+
2500
+ // Prevent the browser from carrying out
2501
+ // the input event, so we can control the
2502
+ // output.
2503
+ event.preventDefault();
2504
+ switch (inputType) {
2505
+ case 'insertFromYank':
2506
+ case 'insertFromDrop':
2507
+ case 'insertReplacementText':
2508
+ {
2509
+ dispatchCommand(editor, CONTROLLED_TEXT_INSERTION_COMMAND, event);
2510
+ break;
2511
+ }
2512
+ case 'insertFromComposition':
2513
+ {
2514
+ // This is the end of composition
2515
+ $setCompositionKey(null);
2516
+ dispatchCommand(editor, CONTROLLED_TEXT_INSERTION_COMMAND, event);
2517
+ break;
2518
+ }
2519
+ case 'insertLineBreak':
2520
+ {
2521
+ // Used for Android
2522
+ $setCompositionKey(null);
2466
2523
  dispatchCommand(editor, INSERT_LINE_BREAK_COMMAND, false);
2467
- } else if (data === DOUBLE_LINE_BREAK) {
2468
- event.preventDefault();
2469
- dispatchCommand(editor, INSERT_PARAGRAPH_COMMAND, undefined);
2470
- } else if (data == null && event.dataTransfer) {
2471
- // Gets around a Safari text replacement bug.
2472
- const text = event.dataTransfer.getData('text/plain');
2473
- event.preventDefault();
2474
- selection.insertRawText(text);
2475
- } else if (data != null && $shouldPreventDefaultAndInsertText(selection, targetRange, data, event.timeStamp, true)) {
2476
- event.preventDefault();
2477
- dispatchCommand(editor, CONTROLLED_TEXT_INSERTION_COMMAND, data);
2478
- } else {
2479
- unprocessedBeforeInputData = data;
2524
+ break;
2480
2525
  }
2481
- lastBeforeInputInsertTextTimeStamp = event.timeStamp;
2482
- return;
2483
- }
2526
+ case 'insertParagraph':
2527
+ {
2528
+ // Used for Android
2529
+ $setCompositionKey(null);
2484
2530
 
2485
- // Prevent the browser from carrying out
2486
- // the input event, so we can control the
2487
- // output.
2488
- event.preventDefault();
2489
- switch (inputType) {
2490
- case 'insertFromYank':
2491
- case 'insertFromDrop':
2492
- case 'insertReplacementText':
2493
- {
2494
- dispatchCommand(editor, CONTROLLED_TEXT_INSERTION_COMMAND, event);
2495
- break;
2496
- }
2497
- case 'insertFromComposition':
2498
- {
2499
- // This is the end of composition
2500
- $setCompositionKey(null);
2501
- dispatchCommand(editor, CONTROLLED_TEXT_INSERTION_COMMAND, event);
2502
- break;
2503
- }
2504
- case 'insertLineBreak':
2505
- {
2506
- // Used for Android
2507
- $setCompositionKey(null);
2531
+ // Safari does not provide the type "insertLineBreak".
2532
+ // So instead, we need to infer it from the keyboard event.
2533
+ // We do not apply this logic to iOS to allow newline auto-capitalization
2534
+ // work without creating linebreaks when pressing Enter
2535
+ if (isInsertLineBreak && !IS_IOS) {
2536
+ isInsertLineBreak = false;
2508
2537
  dispatchCommand(editor, INSERT_LINE_BREAK_COMMAND, false);
2509
- break;
2510
- }
2511
- case 'insertParagraph':
2512
- {
2513
- // Used for Android
2514
- $setCompositionKey(null);
2515
-
2516
- // Safari does not provide the type "insertLineBreak".
2517
- // So instead, we need to infer it from the keyboard event.
2518
- // We do not apply this logic to iOS to allow newline auto-capitalization
2519
- // work without creating linebreaks when pressing Enter
2520
- if (isInsertLineBreak && !IS_IOS) {
2521
- isInsertLineBreak = false;
2522
- dispatchCommand(editor, INSERT_LINE_BREAK_COMMAND, false);
2523
- } else {
2524
- dispatchCommand(editor, INSERT_PARAGRAPH_COMMAND, undefined);
2525
- }
2526
- break;
2527
- }
2528
- case 'insertFromPaste':
2529
- case 'insertFromPasteAsQuotation':
2530
- {
2531
- dispatchCommand(editor, PASTE_COMMAND, event);
2532
- break;
2533
- }
2534
- case 'deleteByComposition':
2535
- {
2536
- if ($canRemoveText(anchorNode, focusNode)) {
2537
- dispatchCommand(editor, REMOVE_TEXT_COMMAND, event);
2538
- }
2539
- break;
2538
+ } else {
2539
+ dispatchCommand(editor, INSERT_PARAGRAPH_COMMAND, undefined);
2540
2540
  }
2541
- case 'deleteByDrag':
2542
- case 'deleteByCut':
2543
- {
2541
+ break;
2542
+ }
2543
+ case 'insertFromPaste':
2544
+ case 'insertFromPasteAsQuotation':
2545
+ {
2546
+ dispatchCommand(editor, PASTE_COMMAND, event);
2547
+ break;
2548
+ }
2549
+ case 'deleteByComposition':
2550
+ {
2551
+ if ($canRemoveText(anchorNode, focusNode)) {
2544
2552
  dispatchCommand(editor, REMOVE_TEXT_COMMAND, event);
2545
- break;
2546
- }
2547
- case 'deleteContent':
2548
- {
2549
- dispatchCommand(editor, DELETE_CHARACTER_COMMAND, false);
2550
- break;
2551
- }
2552
- case 'deleteWordBackward':
2553
- {
2554
- dispatchCommand(editor, DELETE_WORD_COMMAND, true);
2555
- break;
2556
- }
2557
- case 'deleteWordForward':
2558
- {
2559
- dispatchCommand(editor, DELETE_WORD_COMMAND, false);
2560
- break;
2561
- }
2562
- case 'deleteHardLineBackward':
2563
- case 'deleteSoftLineBackward':
2564
- {
2565
- dispatchCommand(editor, DELETE_LINE_COMMAND, true);
2566
- break;
2567
- }
2568
- case 'deleteContentForward':
2569
- case 'deleteHardLineForward':
2570
- case 'deleteSoftLineForward':
2571
- {
2572
- dispatchCommand(editor, DELETE_LINE_COMMAND, false);
2573
- break;
2574
- }
2575
- case 'formatStrikeThrough':
2576
- {
2577
- dispatchCommand(editor, FORMAT_TEXT_COMMAND, 'strikethrough');
2578
- break;
2579
2553
  }
2580
- case 'formatBold':
2581
- {
2582
- dispatchCommand(editor, FORMAT_TEXT_COMMAND, 'bold');
2583
- break;
2584
- }
2585
- case 'formatItalic':
2586
- {
2587
- dispatchCommand(editor, FORMAT_TEXT_COMMAND, 'italic');
2588
- break;
2589
- }
2590
- case 'formatUnderline':
2591
- {
2592
- dispatchCommand(editor, FORMAT_TEXT_COMMAND, 'underline');
2593
- break;
2594
- }
2595
- case 'historyUndo':
2596
- {
2597
- dispatchCommand(editor, UNDO_COMMAND, undefined);
2598
- break;
2599
- }
2600
- case 'historyRedo':
2601
- {
2602
- dispatchCommand(editor, REDO_COMMAND, undefined);
2603
- break;
2604
- }
2605
- // NO-OP
2606
- }
2607
- });
2554
+ break;
2555
+ }
2556
+ case 'deleteByDrag':
2557
+ case 'deleteByCut':
2558
+ {
2559
+ dispatchCommand(editor, REMOVE_TEXT_COMMAND, event);
2560
+ break;
2561
+ }
2562
+ case 'deleteContent':
2563
+ {
2564
+ dispatchCommand(editor, DELETE_CHARACTER_COMMAND, false);
2565
+ break;
2566
+ }
2567
+ case 'deleteWordBackward':
2568
+ {
2569
+ dispatchCommand(editor, DELETE_WORD_COMMAND, true);
2570
+ break;
2571
+ }
2572
+ case 'deleteWordForward':
2573
+ {
2574
+ dispatchCommand(editor, DELETE_WORD_COMMAND, false);
2575
+ break;
2576
+ }
2577
+ case 'deleteHardLineBackward':
2578
+ case 'deleteSoftLineBackward':
2579
+ {
2580
+ dispatchCommand(editor, DELETE_LINE_COMMAND, true);
2581
+ break;
2582
+ }
2583
+ case 'deleteContentForward':
2584
+ case 'deleteHardLineForward':
2585
+ case 'deleteSoftLineForward':
2586
+ {
2587
+ dispatchCommand(editor, DELETE_LINE_COMMAND, false);
2588
+ break;
2589
+ }
2590
+ case 'formatStrikeThrough':
2591
+ {
2592
+ dispatchCommand(editor, FORMAT_TEXT_COMMAND, 'strikethrough');
2593
+ break;
2594
+ }
2595
+ case 'formatBold':
2596
+ {
2597
+ dispatchCommand(editor, FORMAT_TEXT_COMMAND, 'bold');
2598
+ break;
2599
+ }
2600
+ case 'formatItalic':
2601
+ {
2602
+ dispatchCommand(editor, FORMAT_TEXT_COMMAND, 'italic');
2603
+ break;
2604
+ }
2605
+ case 'formatUnderline':
2606
+ {
2607
+ dispatchCommand(editor, FORMAT_TEXT_COMMAND, 'underline');
2608
+ break;
2609
+ }
2610
+ case 'historyUndo':
2611
+ {
2612
+ dispatchCommand(editor, UNDO_COMMAND, undefined);
2613
+ break;
2614
+ }
2615
+ case 'historyRedo':
2616
+ {
2617
+ dispatchCommand(editor, REDO_COMMAND, undefined);
2618
+ break;
2619
+ }
2620
+ // NO-OP
2621
+ }
2622
+ return true;
2608
2623
  }
2609
2624
  function onInput(event, editor) {
2610
2625
  // Note that the MutationObserver may or may not have already fired,
@@ -2616,90 +2631,103 @@ function onInput(event, editor) {
2616
2631
  // We don't want the onInput to bubble, in the case of nested editors.
2617
2632
  event.stopPropagation();
2618
2633
  updateEditorSync(editor, () => {
2619
- if (isHTMLElement(event.target) && $isSelectionCapturedInDecorator(event.target)) {
2620
- return;
2634
+ editor.dispatchCommand(INPUT_COMMAND, event);
2635
+ }, {
2636
+ event
2637
+ });
2638
+ unprocessedBeforeInputData = null;
2639
+ }
2640
+ function $handleInput(event) {
2641
+ if (isHTMLElement(event.target) && $isSelectionCapturedInDecorator(event.target)) {
2642
+ return true;
2643
+ }
2644
+ const editor = getActiveEditor();
2645
+ const selection = $getSelection();
2646
+ const data = event.data;
2647
+ const targetRange = getTargetRange(event);
2648
+ if (data != null && $isRangeSelection(selection) && $shouldPreventDefaultAndInsertText(selection, targetRange, data, event.timeStamp, false)) {
2649
+ // Given we're over-riding the default behavior, we will need
2650
+ // to ensure to disable composition before dispatching the
2651
+ // insertText command for when changing the sequence for FF.
2652
+ if (isFirefoxEndingComposition) {
2653
+ $onCompositionEndImpl(editor, data);
2654
+ isFirefoxEndingComposition = false;
2621
2655
  }
2622
- const selection = $getSelection();
2623
- const data = event.data;
2624
- const targetRange = getTargetRange(event);
2625
- if (data != null && $isRangeSelection(selection) && $shouldPreventDefaultAndInsertText(selection, targetRange, data, event.timeStamp, false)) {
2626
- // Given we're over-riding the default behavior, we will need
2627
- // to ensure to disable composition before dispatching the
2628
- // insertText command for when changing the sequence for FF.
2629
- if (isFirefoxEndingComposition) {
2630
- $onCompositionEndImpl(editor, data);
2631
- isFirefoxEndingComposition = false;
2632
- }
2633
- const anchor = selection.anchor;
2634
- const anchorNode = anchor.getNode();
2635
- const domSelection = getDOMSelection(getWindow(editor));
2636
- if (domSelection === null) {
2637
- return;
2638
- }
2639
- const isBackward = selection.isBackward();
2640
- const startOffset = isBackward ? selection.anchor.offset : selection.focus.offset;
2641
- const endOffset = isBackward ? selection.focus.offset : selection.anchor.offset;
2642
- // If the content is the same as inserted, then don't dispatch an insertion.
2643
- // Given onInput doesn't take the current selection (it uses the previous)
2644
- // we can compare that against what the DOM currently says.
2645
- if (!CAN_USE_BEFORE_INPUT || selection.isCollapsed() || !$isTextNode(anchorNode) || domSelection.anchorNode === null || anchorNode.getTextContent().slice(0, startOffset) + data + anchorNode.getTextContent().slice(startOffset + endOffset) !== getAnchorTextFromDOM(domSelection.anchorNode)) {
2646
- dispatchCommand(editor, CONTROLLED_TEXT_INSERTION_COMMAND, data);
2647
- }
2648
- const textLength = data.length;
2656
+ const anchor = selection.anchor;
2657
+ const anchorNode = anchor.getNode();
2658
+ const domSelection = getDOMSelection(getWindow(editor));
2659
+ if (domSelection === null) {
2660
+ return true;
2661
+ }
2662
+ const isBackward = selection.isBackward();
2663
+ const startOffset = isBackward ? selection.anchor.offset : selection.focus.offset;
2664
+ const endOffset = isBackward ? selection.focus.offset : selection.anchor.offset;
2665
+ // If the content is the same as inserted, then don't dispatch an insertion.
2666
+ // Given onInput doesn't take the current selection (it uses the previous)
2667
+ // we can compare that against what the DOM currently says.
2668
+ if (!CAN_USE_BEFORE_INPUT || selection.isCollapsed() || !$isTextNode(anchorNode) || domSelection.anchorNode === null || anchorNode.getTextContent().slice(0, startOffset) + data + anchorNode.getTextContent().slice(startOffset + endOffset) !== getAnchorTextFromDOM(domSelection.anchorNode)) {
2669
+ dispatchCommand(editor, CONTROLLED_TEXT_INSERTION_COMMAND, data);
2670
+ }
2671
+ const textLength = data.length;
2649
2672
 
2650
- // Another hack for FF, as it's possible that the IME is still
2651
- // open, even though compositionend has already fired (sigh).
2652
- if (IS_FIREFOX && textLength > 1 && event.inputType === 'insertCompositionText' && !editor.isComposing()) {
2653
- selection.anchor.offset -= textLength;
2654
- }
2673
+ // Another hack for FF, as it's possible that the IME is still
2674
+ // open, even though compositionend has already fired (sigh).
2675
+ if (IS_FIREFOX && textLength > 1 && event.inputType === 'insertCompositionText' && !editor.isComposing()) {
2676
+ selection.anchor.offset -= textLength;
2677
+ }
2655
2678
 
2656
- // This ensures consistency on Android.
2657
- if (!IS_SAFARI && !IS_IOS && !IS_APPLE_WEBKIT && editor.isComposing()) {
2658
- lastKeyDownTimeStamp = 0;
2659
- $setCompositionKey(null);
2660
- }
2661
- } else {
2662
- const characterData = data !== null ? data : undefined;
2663
- $updateSelectedTextFromDOM(false, editor, characterData);
2679
+ // This ensures consistency on Android.
2680
+ if (!IS_SAFARI && !IS_IOS && !IS_APPLE_WEBKIT && editor.isComposing()) {
2681
+ lastKeyDownTimeStamp = 0;
2682
+ $setCompositionKey(null);
2683
+ }
2684
+ } else {
2685
+ const characterData = data !== null ? data : undefined;
2686
+ $updateSelectedTextFromDOM(false, editor, characterData);
2664
2687
 
2665
- // onInput always fires after onCompositionEnd for FF.
2666
- if (isFirefoxEndingComposition) {
2667
- $onCompositionEndImpl(editor, data || undefined);
2668
- isFirefoxEndingComposition = false;
2669
- }
2688
+ // onInput always fires after onCompositionEnd for FF.
2689
+ if (isFirefoxEndingComposition) {
2690
+ $onCompositionEndImpl(editor, data || undefined);
2691
+ isFirefoxEndingComposition = false;
2670
2692
  }
2693
+ }
2671
2694
 
2672
- // Also flush any other mutations that might have occurred
2673
- // since the change.
2674
- $flushMutations();
2675
- }, {
2676
- event
2677
- });
2678
- unprocessedBeforeInputData = null;
2695
+ // Also flush any other mutations that might have occurred
2696
+ // since the change.
2697
+ $flushMutations();
2698
+ return true;
2679
2699
  }
2680
2700
  function onCompositionStart(event, editor) {
2681
- updateEditorSync(editor, () => {
2682
- const selection = $getSelection();
2683
- if ($isRangeSelection(selection) && !editor.isComposing()) {
2684
- const anchor = selection.anchor;
2685
- const node = selection.anchor.getNode();
2686
- $setCompositionKey(anchor.key);
2687
- if (
2688
- // If it has been 30ms since the last keydown, then we should
2689
- // apply the empty space heuristic. We can't do this for Safari,
2690
- // as the keydown fires after composition start.
2691
- event.timeStamp < lastKeyDownTimeStamp + ANDROID_COMPOSITION_LATENCY ||
2692
- // FF has issues around composing multibyte characters, so we also
2693
- // need to invoke the empty space heuristic below.
2694
- anchor.type === 'element' || !selection.isCollapsed() || node.getFormat() !== selection.format || $isTextNode(node) && node.getStyle() !== selection.style) {
2695
- // We insert a zero width character, ready for the composition
2696
- // to get inserted into the new node we create. If
2697
- // we don't do this, Safari will fail on us because
2698
- // there is no text node matching the selection.
2699
- dispatchCommand(editor, CONTROLLED_TEXT_INSERTION_COMMAND, COMPOSITION_START_CHAR);
2700
- }
2701
+ dispatchCommand(editor, COMPOSITION_START_COMMAND, event);
2702
+ }
2703
+ function $handleCompositionStart(event) {
2704
+ const editor = getActiveEditor();
2705
+ const selection = $getSelection();
2706
+ if ($isRangeSelection(selection) && !editor.isComposing()) {
2707
+ const anchor = selection.anchor;
2708
+ const node = selection.anchor.getNode();
2709
+ $setCompositionKey(anchor.key);
2710
+ if (
2711
+ // If it has been 30ms since the last keydown, then we should
2712
+ // apply the empty space heuristic. We can't do this for Safari,
2713
+ // as the keydown fires after composition start.
2714
+ event.timeStamp < lastKeyDownTimeStamp + ANDROID_COMPOSITION_LATENCY ||
2715
+ // FF has issues around composing multibyte characters, so we also
2716
+ // need to invoke the empty space heuristic below.
2717
+ anchor.type === 'element' || !selection.isCollapsed() || node.getFormat() !== selection.format || $isTextNode(node) && node.getStyle() !== selection.style) {
2718
+ // We insert a zero width character, ready for the composition
2719
+ // to get inserted into the new node we create. If
2720
+ // we don't do this, Safari will fail on us because
2721
+ // there is no text node matching the selection.
2722
+ dispatchCommand(editor, CONTROLLED_TEXT_INSERTION_COMMAND, COMPOSITION_START_CHAR);
2701
2723
  }
2702
- });
2724
+ }
2725
+ return true;
2726
+ }
2727
+ function $handleCompositionEnd(event) {
2728
+ const editor = getActiveEditor();
2729
+ $onCompositionEndImpl(editor, event.data);
2730
+ return true;
2703
2731
  }
2704
2732
  function $onCompositionEndImpl(editor, data) {
2705
2733
  const compositionKey = editor._compositionKey;
@@ -2751,9 +2779,7 @@ function onCompositionEnd(event, editor) {
2751
2779
  isSafariEndingComposition = true;
2752
2780
  safariEndCompositionEventData = event.data;
2753
2781
  } else {
2754
- updateEditorSync(editor, () => {
2755
- $onCompositionEndImpl(editor, event.data);
2756
- });
2782
+ dispatchCommand(editor, COMPOSITION_END_COMMAND, event);
2757
2783
  }
2758
2784
  }
2759
2785
  function onKeyDown(event, editor) {
@@ -2762,11 +2788,12 @@ function onKeyDown(event, editor) {
2762
2788
  if (editor.isComposing()) {
2763
2789
  return;
2764
2790
  }
2765
- if (dispatchCommand(editor, KEY_DOWN_COMMAND, event)) {
2766
- return;
2767
- }
2791
+ dispatchCommand(editor, KEY_DOWN_COMMAND, event);
2792
+ }
2793
+ function $handleKeyDown(event) {
2794
+ const editor = getActiveEditor();
2768
2795
  if (event.key == null) {
2769
- return;
2796
+ return true;
2770
2797
  }
2771
2798
  if (isSafariEndingComposition && isBackspace(event)) {
2772
2799
  updateEditorSync(editor, () => {
@@ -2774,7 +2801,7 @@ function onKeyDown(event, editor) {
2774
2801
  });
2775
2802
  isSafariEndingComposition = false;
2776
2803
  safariEndCompositionEventData = '';
2777
- return;
2804
+ return true;
2778
2805
  }
2779
2806
  if (isMoveForward(event)) {
2780
2807
  dispatchCommand(editor, KEY_ARROW_RIGHT_COMMAND, event);
@@ -2865,8 +2892,9 @@ function onKeyDown(event, editor) {
2865
2892
  }
2866
2893
  }
2867
2894
  if (isModifier(event)) {
2868
- dispatchCommand(editor, KEY_MODIFIER_COMMAND, event);
2895
+ editor.dispatchCommand(KEY_MODIFIER_COMMAND, event);
2869
2896
  }
2897
+ return true;
2870
2898
  }
2871
2899
  function getRootElementRemoveHandles(rootElement) {
2872
2900
  // @ts-expect-error: internal field
@@ -10184,6 +10212,7 @@ function createEditor(editorConfig) {
10184
10212
  editor._pendingEditorState = initialEditorState;
10185
10213
  editor._dirtyType = FULL_RECONCILE;
10186
10214
  }
10215
+ registerDefaultCommandHandlers(editor);
10187
10216
  return editor;
10188
10217
  }
10189
10218
  class LexicalEditor {
@@ -10863,7 +10892,7 @@ class LexicalEditor {
10863
10892
  };
10864
10893
  }
10865
10894
  }
10866
- LexicalEditor.version = "0.38.3-nightly.20251106.0+dev.esm";
10895
+ LexicalEditor.version = "0.38.3-nightly.20251110.0+dev.esm";
10867
10896
 
10868
10897
  let pendingNodeToClone = null;
10869
10898
  function setPendingNodeToClone(pendingNode) {
@@ -13985,4 +14014,4 @@ function shallowMergeConfig(config, overrides) {
13985
14014
  return config;
13986
14015
  }
13987
14016
 
13988
- export { $addUpdateTag, $applyNodeReplacement, $caretFromPoint, $caretRangeFromSelection, $cloneWithProperties, $cloneWithPropertiesEphemeral, $comparePointCaretNext, $copyNode, $create, $createLineBreakNode, $createNodeSelection, $createParagraphNode, $createPoint, $createRangeSelection, $createRangeSelectionFromDom, $createTabNode, $createTextNode, $extendCaretToRange, $findMatchingParent, $getAdjacentChildCaret, $getAdjacentNode, $getAdjacentSiblingOrParentSiblingCaret, $getCaretInDirection, $getCaretRange, $getCaretRangeInDirection, $getCharacterOffsets, $getChildCaret, $getChildCaretAtIndex, $getChildCaretOrSelf, $getCollapsedCaretRange, $getCommonAncestor, $getCommonAncestorResultBranchOrder, $getEditor, $getNearestNodeFromDOMNode, $getNearestRootOrShadowRoot, $getNodeByKey, $getNodeByKeyOrThrow, $getNodeFromDOMNode, $getPreviousSelection, $getRoot, $getSelection, $getSiblingCaret, $getState, $getStateChange, $getTextContent, $getTextNodeOffset, $getTextPointCaret, $getTextPointCaretSlice, $getWritableNodeState, $hasAncestor, $hasUpdateTag, $insertNodes, $isBlockElementNode, $isChildCaret, $isDecoratorNode, $isEditorState, $isElementNode, $isExtendableTextPointCaret, $isInlineElementOrDecoratorNode, $isLeafNode, $isLineBreakNode, $isNodeCaret, $isNodeSelection, $isParagraphNode, $isRangeSelection, $isRootNode, $isRootOrShadowRoot, $isSiblingCaret, $isTabNode, $isTextNode, $isTextPointCaret, $isTextPointCaretSlice, $isTokenOrSegmented, $isTokenOrTab, $nodesOfType, $normalizeCaret, $normalizeSelection as $normalizeSelection__EXPERIMENTAL, $onUpdate, $parseSerializedNode, $removeTextFromCaretRange, $rewindSiblingCaret, $selectAll, $setCompositionKey, $setPointFromCaret, $setSelection, $setSelectionFromCaretRange, $setState, $splitAtPointCaretNext, $splitNode, $updateRangeSelectionFromCaretRange, ArtificialNode__DO_NOT_USE, BLUR_COMMAND, CAN_REDO_COMMAND, CAN_UNDO_COMMAND, CLEAR_EDITOR_COMMAND, CLEAR_HISTORY_COMMAND, CLICK_COMMAND, COLLABORATION_TAG, COMMAND_PRIORITY_CRITICAL, COMMAND_PRIORITY_EDITOR, COMMAND_PRIORITY_HIGH, COMMAND_PRIORITY_LOW, COMMAND_PRIORITY_NORMAL, CONTROLLED_TEXT_INSERTION_COMMAND, COPY_COMMAND, CUT_COMMAND, DELETE_CHARACTER_COMMAND, DELETE_LINE_COMMAND, DELETE_WORD_COMMAND, DRAGEND_COMMAND, DRAGOVER_COMMAND, DRAGSTART_COMMAND, DROP_COMMAND, DecoratorNode, ElementNode, FOCUS_COMMAND, FORMAT_ELEMENT_COMMAND, FORMAT_TEXT_COMMAND, HISTORIC_TAG, HISTORY_MERGE_TAG, HISTORY_PUSH_TAG, INDENT_CONTENT_COMMAND, INSERT_LINE_BREAK_COMMAND, INSERT_PARAGRAPH_COMMAND, INSERT_TAB_COMMAND, INTERNAL_$isBlock, IS_ALL_FORMATTING, IS_BOLD, IS_CODE, IS_HIGHLIGHT, IS_ITALIC, IS_STRIKETHROUGH, IS_SUBSCRIPT, IS_SUPERSCRIPT, IS_UNDERLINE, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, KEY_DOWN_COMMAND, KEY_ENTER_COMMAND, KEY_ESCAPE_COMMAND, KEY_MODIFIER_COMMAND, KEY_SPACE_COMMAND, KEY_TAB_COMMAND, LineBreakNode, MOVE_TO_END, MOVE_TO_START, NODE_STATE_KEY, OUTDENT_CONTENT_COMMAND, PASTE_COMMAND, PASTE_TAG, ParagraphNode, REDO_COMMAND, REMOVE_TEXT_COMMAND, RootNode, SELECTION_CHANGE_COMMAND, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, SELECT_ALL_COMMAND, SKIP_COLLAB_TAG, SKIP_DOM_SELECTION_TAG, SKIP_SCROLL_INTO_VIEW_TAG, SKIP_SELECTION_FOCUS_TAG, TEXT_TYPE_TO_FORMAT, TabNode, TextNode, UNDO_COMMAND, buildImportMap, configExtension, createCommand, createEditor, createSharedNodeState, createState, declarePeerDependency, defineExtension, flipDirection, getDOMOwnerDocument, getDOMSelection, getDOMSelectionFromTarget, getDOMTextNode, getEditorPropertyFromDOMNode, getNearestEditorFromDOMNode, getRegisteredNode, getRegisteredNodeOrThrow, getStaticNodeConfig, getTextDirection, getTransformSetFromKlass, isBlockDomNode, isCurrentlyReadOnlyMode, isDOMDocumentNode, isDOMNode, isDOMTextNode, isDOMUnmanaged, isDocumentFragment, isExactShortcutMatch, isHTMLAnchorElement, isHTMLElement, isInlineDomNode, isLexicalEditor, isModifierMatch, isSelectionCapturedInDecoratorInput, isSelectionWithinEditor, makeStepwiseIterator, removeFromParent, resetRandomKey, safeCast, setDOMUnmanaged, setNodeIndentFromDOM, shallowMergeConfig };
14017
+ export { $addUpdateTag, $applyNodeReplacement, $caretFromPoint, $caretRangeFromSelection, $cloneWithProperties, $cloneWithPropertiesEphemeral, $comparePointCaretNext, $copyNode, $create, $createLineBreakNode, $createNodeSelection, $createParagraphNode, $createPoint, $createRangeSelection, $createRangeSelectionFromDom, $createTabNode, $createTextNode, $extendCaretToRange, $findMatchingParent, $getAdjacentChildCaret, $getAdjacentNode, $getAdjacentSiblingOrParentSiblingCaret, $getCaretInDirection, $getCaretRange, $getCaretRangeInDirection, $getCharacterOffsets, $getChildCaret, $getChildCaretAtIndex, $getChildCaretOrSelf, $getCollapsedCaretRange, $getCommonAncestor, $getCommonAncestorResultBranchOrder, $getEditor, $getNearestNodeFromDOMNode, $getNearestRootOrShadowRoot, $getNodeByKey, $getNodeByKeyOrThrow, $getNodeFromDOMNode, $getPreviousSelection, $getRoot, $getSelection, $getSiblingCaret, $getState, $getStateChange, $getTextContent, $getTextNodeOffset, $getTextPointCaret, $getTextPointCaretSlice, $getWritableNodeState, $hasAncestor, $hasUpdateTag, $insertNodes, $isBlockElementNode, $isChildCaret, $isDecoratorNode, $isEditorState, $isElementNode, $isExtendableTextPointCaret, $isInlineElementOrDecoratorNode, $isLeafNode, $isLineBreakNode, $isNodeCaret, $isNodeSelection, $isParagraphNode, $isRangeSelection, $isRootNode, $isRootOrShadowRoot, $isSiblingCaret, $isTabNode, $isTextNode, $isTextPointCaret, $isTextPointCaretSlice, $isTokenOrSegmented, $isTokenOrTab, $nodesOfType, $normalizeCaret, $normalizeSelection as $normalizeSelection__EXPERIMENTAL, $onUpdate, $parseSerializedNode, $removeTextFromCaretRange, $rewindSiblingCaret, $selectAll, $setCompositionKey, $setPointFromCaret, $setSelection, $setSelectionFromCaretRange, $setState, $splitAtPointCaretNext, $splitNode, $updateRangeSelectionFromCaretRange, ArtificialNode__DO_NOT_USE, BEFORE_INPUT_COMMAND, BLUR_COMMAND, CAN_REDO_COMMAND, CAN_UNDO_COMMAND, CLEAR_EDITOR_COMMAND, CLEAR_HISTORY_COMMAND, CLICK_COMMAND, COLLABORATION_TAG, COMMAND_PRIORITY_CRITICAL, COMMAND_PRIORITY_EDITOR, COMMAND_PRIORITY_HIGH, COMMAND_PRIORITY_LOW, COMMAND_PRIORITY_NORMAL, COMPOSITION_END_COMMAND, COMPOSITION_START_COMMAND, CONTROLLED_TEXT_INSERTION_COMMAND, COPY_COMMAND, CUT_COMMAND, DELETE_CHARACTER_COMMAND, DELETE_LINE_COMMAND, DELETE_WORD_COMMAND, DRAGEND_COMMAND, DRAGOVER_COMMAND, DRAGSTART_COMMAND, DROP_COMMAND, DecoratorNode, ElementNode, FOCUS_COMMAND, FORMAT_ELEMENT_COMMAND, FORMAT_TEXT_COMMAND, HISTORIC_TAG, HISTORY_MERGE_TAG, HISTORY_PUSH_TAG, INDENT_CONTENT_COMMAND, INPUT_COMMAND, INSERT_LINE_BREAK_COMMAND, INSERT_PARAGRAPH_COMMAND, INSERT_TAB_COMMAND, INTERNAL_$isBlock, IS_ALL_FORMATTING, IS_BOLD, IS_CODE, IS_HIGHLIGHT, IS_ITALIC, IS_STRIKETHROUGH, IS_SUBSCRIPT, IS_SUPERSCRIPT, IS_UNDERLINE, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, KEY_DOWN_COMMAND, KEY_ENTER_COMMAND, KEY_ESCAPE_COMMAND, KEY_MODIFIER_COMMAND, KEY_SPACE_COMMAND, KEY_TAB_COMMAND, LineBreakNode, MOVE_TO_END, MOVE_TO_START, NODE_STATE_KEY, OUTDENT_CONTENT_COMMAND, PASTE_COMMAND, PASTE_TAG, ParagraphNode, REDO_COMMAND, REMOVE_TEXT_COMMAND, RootNode, SELECTION_CHANGE_COMMAND, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, SELECT_ALL_COMMAND, SKIP_COLLAB_TAG, SKIP_DOM_SELECTION_TAG, SKIP_SCROLL_INTO_VIEW_TAG, SKIP_SELECTION_FOCUS_TAG, TEXT_TYPE_TO_FORMAT, TabNode, TextNode, UNDO_COMMAND, buildImportMap, configExtension, createCommand, createEditor, createSharedNodeState, createState, declarePeerDependency, defineExtension, flipDirection, getDOMOwnerDocument, getDOMSelection, getDOMSelectionFromTarget, getDOMTextNode, getEditorPropertyFromDOMNode, getNearestEditorFromDOMNode, getRegisteredNode, getRegisteredNodeOrThrow, getStaticNodeConfig, getTextDirection, getTransformSetFromKlass, isBlockDomNode, isCurrentlyReadOnlyMode, isDOMDocumentNode, isDOMNode, isDOMTextNode, isDOMUnmanaged, isDocumentFragment, isExactShortcutMatch, isHTMLAnchorElement, isHTMLElement, isInlineDomNode, isLexicalEditor, isModifierMatch, isSelectionCapturedInDecoratorInput, isSelectionWithinEditor, makeStepwiseIterator, removeFromParent, resetRandomKey, safeCast, setDOMUnmanaged, setNodeIndentFromDOM, shallowMergeConfig };