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.js CHANGED
@@ -1929,6 +1929,10 @@ function createCommand(type) {
1929
1929
  const SELECTION_CHANGE_COMMAND = createCommand('SELECTION_CHANGE_COMMAND');
1930
1930
  const SELECTION_INSERT_CLIPBOARD_NODES_COMMAND = createCommand('SELECTION_INSERT_CLIPBOARD_NODES_COMMAND');
1931
1931
  const CLICK_COMMAND = createCommand('CLICK_COMMAND');
1932
+ const BEFORE_INPUT_COMMAND = createCommand('BEFORE_INPUT_COMMAND');
1933
+ const INPUT_COMMAND = createCommand('INPUT_COMMAND');
1934
+ const COMPOSITION_START_COMMAND = createCommand('COMPOSITION_START_COMMAND');
1935
+ const COMPOSITION_END_COMMAND = createCommand('COMPOSITION_END_COMMAND');
1932
1936
  /**
1933
1937
  * Dispatched to delete a character, the payload will be `true` if the deletion
1934
1938
  * is backwards (backspace or delete on macOS) and `false` if forwards
@@ -2360,9 +2364,15 @@ function $canRemoveText(anchorNode, focusNode) {
2360
2364
  function isPossiblyAndroidKeyPress(timeStamp) {
2361
2365
  return lastKeyCode === 'MediaLast' && timeStamp < lastKeyDownTimeStamp + ANDROID_COMPOSITION_LATENCY;
2362
2366
  }
2367
+ function registerDefaultCommandHandlers(editor) {
2368
+ editor.registerCommand(BEFORE_INPUT_COMMAND, $handleBeforeInput, COMMAND_PRIORITY_EDITOR);
2369
+ editor.registerCommand(INPUT_COMMAND, $handleInput, COMMAND_PRIORITY_EDITOR);
2370
+ editor.registerCommand(COMPOSITION_START_COMMAND, $handleCompositionStart, COMMAND_PRIORITY_EDITOR);
2371
+ editor.registerCommand(COMPOSITION_END_COMMAND, $handleCompositionEnd, COMMAND_PRIORITY_EDITOR);
2372
+ editor.registerCommand(KEY_DOWN_COMMAND, $handleKeyDown, COMMAND_PRIORITY_EDITOR);
2373
+ }
2363
2374
  function onBeforeInput(event, editor) {
2364
2375
  const inputType = event.inputType;
2365
- const targetRange = getTargetRange(event);
2366
2376
 
2367
2377
  // We let the browser do its own thing for composition.
2368
2378
  if (inputType === 'deleteCompositionText' ||
@@ -2376,237 +2386,242 @@ function onBeforeInput(event, editor) {
2376
2386
  } else if (inputType === 'insertCompositionText') {
2377
2387
  return;
2378
2388
  }
2379
- updateEditorSync(editor, () => {
2380
- const selection = $getSelection();
2381
- if (inputType === 'deleteContentBackward') {
2382
- if (selection === null) {
2383
- // Use previous selection
2384
- const prevSelection = $getPreviousSelection();
2385
- if (!$isRangeSelection(prevSelection)) {
2386
- return;
2387
- }
2388
- $setSelection(prevSelection.clone());
2389
+ dispatchCommand(editor, BEFORE_INPUT_COMMAND, event);
2390
+ }
2391
+ function $handleBeforeInput(event) {
2392
+ const inputType = event.inputType;
2393
+ const targetRange = getTargetRange(event);
2394
+ const editor = getActiveEditor();
2395
+ const selection = $getSelection();
2396
+ if (inputType === 'deleteContentBackward') {
2397
+ if (selection === null) {
2398
+ // Use previous selection
2399
+ const prevSelection = $getPreviousSelection();
2400
+ if (!$isRangeSelection(prevSelection)) {
2401
+ return true;
2389
2402
  }
2390
- if ($isRangeSelection(selection)) {
2391
- const isSelectionAnchorSameAsFocus = selection.anchor.key === selection.focus.key;
2392
- if (isPossiblyAndroidKeyPress(event.timeStamp) && editor.isComposing() && isSelectionAnchorSameAsFocus) {
2393
- $setCompositionKey(null);
2394
- lastKeyDownTimeStamp = 0;
2395
- // Fixes an Android bug where selection flickers when backspacing
2396
- setTimeout(() => {
2397
- updateEditorSync(editor, () => {
2398
- $setCompositionKey(null);
2399
- });
2400
- }, ANDROID_COMPOSITION_LATENCY);
2401
- if ($isRangeSelection(selection)) {
2402
- const anchorNode = selection.anchor.getNode();
2403
- anchorNode.markDirty();
2404
- if (!$isTextNode(anchorNode)) {
2405
- formatDevErrorMessage(`Anchor node must be a TextNode`);
2406
- }
2407
- $updateSelectionFormatStyleFromTextNode(selection, anchorNode);
2408
- }
2409
- } else {
2410
- $setCompositionKey(null);
2411
- event.preventDefault();
2412
- // Chromium Android at the moment seems to ignore the preventDefault
2413
- // on 'deleteContentBackward' and still deletes the content. Which leads
2414
- // to multiple deletions. So we let the browser handle the deletion in this case.
2415
- const selectedNode = selection.anchor.getNode();
2416
- const selectedNodeText = selectedNode.getTextContent();
2417
- // When the target node has `canInsertTextAfter` set to false, the first deletion
2418
- // doesn't have an effect, so we need to handle it with Lexical.
2419
- const selectedNodeCanInsertTextAfter = selectedNode.canInsertTextAfter();
2420
- const hasSelectedAllTextInNode = selection.anchor.offset === 0 && selection.focus.offset === selectedNodeText.length;
2421
- let shouldLetBrowserHandleDelete = IS_ANDROID_CHROME && isSelectionAnchorSameAsFocus && !hasSelectedAllTextInNode && selectedNodeCanInsertTextAfter;
2422
- // Check if selection is collapsed and if the previous node is a decorator node
2423
- // If so, the browser will not be able to handle the deletion
2424
- if (shouldLetBrowserHandleDelete && selection.isCollapsed()) {
2425
- shouldLetBrowserHandleDelete = !$isDecoratorNode($getAdjacentNode(selection.anchor, true));
2403
+ $setSelection(prevSelection.clone());
2404
+ }
2405
+ if ($isRangeSelection(selection)) {
2406
+ const isSelectionAnchorSameAsFocus = selection.anchor.key === selection.focus.key;
2407
+ if (isPossiblyAndroidKeyPress(event.timeStamp) && editor.isComposing() && isSelectionAnchorSameAsFocus) {
2408
+ $setCompositionKey(null);
2409
+ lastKeyDownTimeStamp = 0;
2410
+ // Fixes an Android bug where selection flickers when backspacing
2411
+ setTimeout(() => {
2412
+ updateEditorSync(editor, () => {
2413
+ $setCompositionKey(null);
2414
+ });
2415
+ }, ANDROID_COMPOSITION_LATENCY);
2416
+ if ($isRangeSelection(selection)) {
2417
+ const anchorNode = selection.anchor.getNode();
2418
+ anchorNode.markDirty();
2419
+ if (!$isTextNode(anchorNode)) {
2420
+ formatDevErrorMessage(`Anchor node must be a TextNode`);
2426
2421
  }
2427
- if (!shouldLetBrowserHandleDelete) {
2428
- dispatchCommand(editor, DELETE_CHARACTER_COMMAND, true);
2429
- // When deleting across paragraphs, Chrome on Android incorrectly shifts the selection rightwards
2430
- // We save the correct selection to restore later during handling of selectionchange event
2431
- const selectionAfterDelete = $getSelection();
2432
- if (IS_ANDROID_CHROME && $isRangeSelection(selectionAfterDelete) && selectionAfterDelete.isCollapsed()) {
2433
- postDeleteSelectionToRestore = selectionAfterDelete;
2434
- // Cleanup in case selectionchange does not fire
2435
- setTimeout(() => postDeleteSelectionToRestore = null);
2436
- }
2422
+ $updateSelectionFormatStyleFromTextNode(selection, anchorNode);
2423
+ }
2424
+ } else {
2425
+ $setCompositionKey(null);
2426
+ event.preventDefault();
2427
+ // Chromium Android at the moment seems to ignore the preventDefault
2428
+ // on 'deleteContentBackward' and still deletes the content. Which leads
2429
+ // to multiple deletions. So we let the browser handle the deletion in this case.
2430
+ const selectedNode = selection.anchor.getNode();
2431
+ const selectedNodeText = selectedNode.getTextContent();
2432
+ // When the target node has `canInsertTextAfter` set to false, the first deletion
2433
+ // doesn't have an effect, so we need to handle it with Lexical.
2434
+ const selectedNodeCanInsertTextAfter = selectedNode.canInsertTextAfter();
2435
+ const hasSelectedAllTextInNode = selection.anchor.offset === 0 && selection.focus.offset === selectedNodeText.length;
2436
+ let shouldLetBrowserHandleDelete = IS_ANDROID_CHROME && isSelectionAnchorSameAsFocus && !hasSelectedAllTextInNode && selectedNodeCanInsertTextAfter;
2437
+ // Check if selection is collapsed and if the previous node is a decorator node
2438
+ // If so, the browser will not be able to handle the deletion
2439
+ if (shouldLetBrowserHandleDelete && selection.isCollapsed()) {
2440
+ shouldLetBrowserHandleDelete = !$isDecoratorNode($getAdjacentNode(selection.anchor, true));
2441
+ }
2442
+ if (!shouldLetBrowserHandleDelete) {
2443
+ dispatchCommand(editor, DELETE_CHARACTER_COMMAND, true);
2444
+ // When deleting across paragraphs, Chrome on Android incorrectly shifts the selection rightwards
2445
+ // We save the correct selection to restore later during handling of selectionchange event
2446
+ const selectionAfterDelete = $getSelection();
2447
+ if (IS_ANDROID_CHROME && $isRangeSelection(selectionAfterDelete) && selectionAfterDelete.isCollapsed()) {
2448
+ postDeleteSelectionToRestore = selectionAfterDelete;
2449
+ // Cleanup in case selectionchange does not fire
2450
+ setTimeout(() => postDeleteSelectionToRestore = null);
2437
2451
  }
2438
2452
  }
2439
- return;
2440
2453
  }
2454
+ return true;
2441
2455
  }
2442
- if (!$isRangeSelection(selection)) {
2443
- return;
2444
- }
2445
- const data = event.data;
2456
+ }
2457
+ if (!$isRangeSelection(selection)) {
2458
+ return true;
2459
+ }
2460
+ const data = event.data;
2446
2461
 
2447
- // This represents the case when two beforeinput events are triggered at the same time (without a
2448
- // full event loop ending at input). This happens with MacOS with the default keyboard settings,
2449
- // a combination of autocorrection + autocapitalization.
2450
- // Having Lexical run everything in controlled mode would fix the issue without additional code
2451
- // but this would kill the massive performance win from the most common typing event.
2452
- // Alternatively, when this happens we can prematurely update our EditorState based on the DOM
2453
- // content, a job that would usually be the input event's responsibility.
2454
- if (unprocessedBeforeInputData !== null) {
2455
- $updateSelectedTextFromDOM(false, editor, unprocessedBeforeInputData);
2456
- }
2457
- if ((!selection.dirty || unprocessedBeforeInputData !== null) && selection.isCollapsed() && !$isRootNode(selection.anchor.getNode()) && targetRange !== null) {
2458
- selection.applyDOMRange(targetRange);
2462
+ // This represents the case when two beforeinput events are triggered at the same time (without a
2463
+ // full event loop ending at input). This happens with MacOS with the default keyboard settings,
2464
+ // a combination of autocorrection + autocapitalization.
2465
+ // Having Lexical run everything in controlled mode would fix the issue without additional code
2466
+ // but this would kill the massive performance win from the most common typing event.
2467
+ // Alternatively, when this happens we can prematurely update our EditorState based on the DOM
2468
+ // content, a job that would usually be the input event's responsibility.
2469
+ if (unprocessedBeforeInputData !== null) {
2470
+ $updateSelectedTextFromDOM(false, editor, unprocessedBeforeInputData);
2471
+ }
2472
+ if ((!selection.dirty || unprocessedBeforeInputData !== null) && selection.isCollapsed() && !$isRootNode(selection.anchor.getNode()) && targetRange !== null) {
2473
+ selection.applyDOMRange(targetRange);
2474
+ }
2475
+ unprocessedBeforeInputData = null;
2476
+ const anchor = selection.anchor;
2477
+ const focus = selection.focus;
2478
+ const anchorNode = anchor.getNode();
2479
+ const focusNode = focus.getNode();
2480
+ if (inputType === 'insertText' || inputType === 'insertTranspose') {
2481
+ if (data === '\n') {
2482
+ event.preventDefault();
2483
+ dispatchCommand(editor, INSERT_LINE_BREAK_COMMAND, false);
2484
+ } else if (data === DOUBLE_LINE_BREAK) {
2485
+ event.preventDefault();
2486
+ dispatchCommand(editor, INSERT_PARAGRAPH_COMMAND, undefined);
2487
+ } else if (data == null && event.dataTransfer) {
2488
+ // Gets around a Safari text replacement bug.
2489
+ const text = event.dataTransfer.getData('text/plain');
2490
+ event.preventDefault();
2491
+ selection.insertRawText(text);
2492
+ } else if (data != null && $shouldPreventDefaultAndInsertText(selection, targetRange, data, event.timeStamp, true)) {
2493
+ event.preventDefault();
2494
+ dispatchCommand(editor, CONTROLLED_TEXT_INSERTION_COMMAND, data);
2495
+ } else {
2496
+ unprocessedBeforeInputData = data;
2459
2497
  }
2460
- unprocessedBeforeInputData = null;
2461
- const anchor = selection.anchor;
2462
- const focus = selection.focus;
2463
- const anchorNode = anchor.getNode();
2464
- const focusNode = focus.getNode();
2465
- if (inputType === 'insertText' || inputType === 'insertTranspose') {
2466
- if (data === '\n') {
2467
- event.preventDefault();
2498
+ lastBeforeInputInsertTextTimeStamp = event.timeStamp;
2499
+ return true;
2500
+ }
2501
+
2502
+ // Prevent the browser from carrying out
2503
+ // the input event, so we can control the
2504
+ // output.
2505
+ event.preventDefault();
2506
+ switch (inputType) {
2507
+ case 'insertFromYank':
2508
+ case 'insertFromDrop':
2509
+ case 'insertReplacementText':
2510
+ {
2511
+ dispatchCommand(editor, CONTROLLED_TEXT_INSERTION_COMMAND, event);
2512
+ break;
2513
+ }
2514
+ case 'insertFromComposition':
2515
+ {
2516
+ // This is the end of composition
2517
+ $setCompositionKey(null);
2518
+ dispatchCommand(editor, CONTROLLED_TEXT_INSERTION_COMMAND, event);
2519
+ break;
2520
+ }
2521
+ case 'insertLineBreak':
2522
+ {
2523
+ // Used for Android
2524
+ $setCompositionKey(null);
2468
2525
  dispatchCommand(editor, INSERT_LINE_BREAK_COMMAND, false);
2469
- } else if (data === DOUBLE_LINE_BREAK) {
2470
- event.preventDefault();
2471
- dispatchCommand(editor, INSERT_PARAGRAPH_COMMAND, undefined);
2472
- } else if (data == null && event.dataTransfer) {
2473
- // Gets around a Safari text replacement bug.
2474
- const text = event.dataTransfer.getData('text/plain');
2475
- event.preventDefault();
2476
- selection.insertRawText(text);
2477
- } else if (data != null && $shouldPreventDefaultAndInsertText(selection, targetRange, data, event.timeStamp, true)) {
2478
- event.preventDefault();
2479
- dispatchCommand(editor, CONTROLLED_TEXT_INSERTION_COMMAND, data);
2480
- } else {
2481
- unprocessedBeforeInputData = data;
2526
+ break;
2482
2527
  }
2483
- lastBeforeInputInsertTextTimeStamp = event.timeStamp;
2484
- return;
2485
- }
2528
+ case 'insertParagraph':
2529
+ {
2530
+ // Used for Android
2531
+ $setCompositionKey(null);
2486
2532
 
2487
- // Prevent the browser from carrying out
2488
- // the input event, so we can control the
2489
- // output.
2490
- event.preventDefault();
2491
- switch (inputType) {
2492
- case 'insertFromYank':
2493
- case 'insertFromDrop':
2494
- case 'insertReplacementText':
2495
- {
2496
- dispatchCommand(editor, CONTROLLED_TEXT_INSERTION_COMMAND, event);
2497
- break;
2498
- }
2499
- case 'insertFromComposition':
2500
- {
2501
- // This is the end of composition
2502
- $setCompositionKey(null);
2503
- dispatchCommand(editor, CONTROLLED_TEXT_INSERTION_COMMAND, event);
2504
- break;
2505
- }
2506
- case 'insertLineBreak':
2507
- {
2508
- // Used for Android
2509
- $setCompositionKey(null);
2533
+ // Safari does not provide the type "insertLineBreak".
2534
+ // So instead, we need to infer it from the keyboard event.
2535
+ // We do not apply this logic to iOS to allow newline auto-capitalization
2536
+ // work without creating linebreaks when pressing Enter
2537
+ if (isInsertLineBreak && !IS_IOS) {
2538
+ isInsertLineBreak = false;
2510
2539
  dispatchCommand(editor, INSERT_LINE_BREAK_COMMAND, false);
2511
- break;
2512
- }
2513
- case 'insertParagraph':
2514
- {
2515
- // Used for Android
2516
- $setCompositionKey(null);
2517
-
2518
- // Safari does not provide the type "insertLineBreak".
2519
- // So instead, we need to infer it from the keyboard event.
2520
- // We do not apply this logic to iOS to allow newline auto-capitalization
2521
- // work without creating linebreaks when pressing Enter
2522
- if (isInsertLineBreak && !IS_IOS) {
2523
- isInsertLineBreak = false;
2524
- dispatchCommand(editor, INSERT_LINE_BREAK_COMMAND, false);
2525
- } else {
2526
- dispatchCommand(editor, INSERT_PARAGRAPH_COMMAND, undefined);
2527
- }
2528
- break;
2529
- }
2530
- case 'insertFromPaste':
2531
- case 'insertFromPasteAsQuotation':
2532
- {
2533
- dispatchCommand(editor, PASTE_COMMAND, event);
2534
- break;
2535
- }
2536
- case 'deleteByComposition':
2537
- {
2538
- if ($canRemoveText(anchorNode, focusNode)) {
2539
- dispatchCommand(editor, REMOVE_TEXT_COMMAND, event);
2540
- }
2541
- break;
2540
+ } else {
2541
+ dispatchCommand(editor, INSERT_PARAGRAPH_COMMAND, undefined);
2542
2542
  }
2543
- case 'deleteByDrag':
2544
- case 'deleteByCut':
2545
- {
2543
+ break;
2544
+ }
2545
+ case 'insertFromPaste':
2546
+ case 'insertFromPasteAsQuotation':
2547
+ {
2548
+ dispatchCommand(editor, PASTE_COMMAND, event);
2549
+ break;
2550
+ }
2551
+ case 'deleteByComposition':
2552
+ {
2553
+ if ($canRemoveText(anchorNode, focusNode)) {
2546
2554
  dispatchCommand(editor, REMOVE_TEXT_COMMAND, event);
2547
- break;
2548
- }
2549
- case 'deleteContent':
2550
- {
2551
- dispatchCommand(editor, DELETE_CHARACTER_COMMAND, false);
2552
- break;
2553
- }
2554
- case 'deleteWordBackward':
2555
- {
2556
- dispatchCommand(editor, DELETE_WORD_COMMAND, true);
2557
- break;
2558
- }
2559
- case 'deleteWordForward':
2560
- {
2561
- dispatchCommand(editor, DELETE_WORD_COMMAND, false);
2562
- break;
2563
- }
2564
- case 'deleteHardLineBackward':
2565
- case 'deleteSoftLineBackward':
2566
- {
2567
- dispatchCommand(editor, DELETE_LINE_COMMAND, true);
2568
- break;
2569
- }
2570
- case 'deleteContentForward':
2571
- case 'deleteHardLineForward':
2572
- case 'deleteSoftLineForward':
2573
- {
2574
- dispatchCommand(editor, DELETE_LINE_COMMAND, false);
2575
- break;
2576
- }
2577
- case 'formatStrikeThrough':
2578
- {
2579
- dispatchCommand(editor, FORMAT_TEXT_COMMAND, 'strikethrough');
2580
- break;
2581
2555
  }
2582
- case 'formatBold':
2583
- {
2584
- dispatchCommand(editor, FORMAT_TEXT_COMMAND, 'bold');
2585
- break;
2586
- }
2587
- case 'formatItalic':
2588
- {
2589
- dispatchCommand(editor, FORMAT_TEXT_COMMAND, 'italic');
2590
- break;
2591
- }
2592
- case 'formatUnderline':
2593
- {
2594
- dispatchCommand(editor, FORMAT_TEXT_COMMAND, 'underline');
2595
- break;
2596
- }
2597
- case 'historyUndo':
2598
- {
2599
- dispatchCommand(editor, UNDO_COMMAND, undefined);
2600
- break;
2601
- }
2602
- case 'historyRedo':
2603
- {
2604
- dispatchCommand(editor, REDO_COMMAND, undefined);
2605
- break;
2606
- }
2607
- // NO-OP
2608
- }
2609
- });
2556
+ break;
2557
+ }
2558
+ case 'deleteByDrag':
2559
+ case 'deleteByCut':
2560
+ {
2561
+ dispatchCommand(editor, REMOVE_TEXT_COMMAND, event);
2562
+ break;
2563
+ }
2564
+ case 'deleteContent':
2565
+ {
2566
+ dispatchCommand(editor, DELETE_CHARACTER_COMMAND, false);
2567
+ break;
2568
+ }
2569
+ case 'deleteWordBackward':
2570
+ {
2571
+ dispatchCommand(editor, DELETE_WORD_COMMAND, true);
2572
+ break;
2573
+ }
2574
+ case 'deleteWordForward':
2575
+ {
2576
+ dispatchCommand(editor, DELETE_WORD_COMMAND, false);
2577
+ break;
2578
+ }
2579
+ case 'deleteHardLineBackward':
2580
+ case 'deleteSoftLineBackward':
2581
+ {
2582
+ dispatchCommand(editor, DELETE_LINE_COMMAND, true);
2583
+ break;
2584
+ }
2585
+ case 'deleteContentForward':
2586
+ case 'deleteHardLineForward':
2587
+ case 'deleteSoftLineForward':
2588
+ {
2589
+ dispatchCommand(editor, DELETE_LINE_COMMAND, false);
2590
+ break;
2591
+ }
2592
+ case 'formatStrikeThrough':
2593
+ {
2594
+ dispatchCommand(editor, FORMAT_TEXT_COMMAND, 'strikethrough');
2595
+ break;
2596
+ }
2597
+ case 'formatBold':
2598
+ {
2599
+ dispatchCommand(editor, FORMAT_TEXT_COMMAND, 'bold');
2600
+ break;
2601
+ }
2602
+ case 'formatItalic':
2603
+ {
2604
+ dispatchCommand(editor, FORMAT_TEXT_COMMAND, 'italic');
2605
+ break;
2606
+ }
2607
+ case 'formatUnderline':
2608
+ {
2609
+ dispatchCommand(editor, FORMAT_TEXT_COMMAND, 'underline');
2610
+ break;
2611
+ }
2612
+ case 'historyUndo':
2613
+ {
2614
+ dispatchCommand(editor, UNDO_COMMAND, undefined);
2615
+ break;
2616
+ }
2617
+ case 'historyRedo':
2618
+ {
2619
+ dispatchCommand(editor, REDO_COMMAND, undefined);
2620
+ break;
2621
+ }
2622
+ // NO-OP
2623
+ }
2624
+ return true;
2610
2625
  }
2611
2626
  function onInput(event, editor) {
2612
2627
  // Note that the MutationObserver may or may not have already fired,
@@ -2618,90 +2633,103 @@ function onInput(event, editor) {
2618
2633
  // We don't want the onInput to bubble, in the case of nested editors.
2619
2634
  event.stopPropagation();
2620
2635
  updateEditorSync(editor, () => {
2621
- if (isHTMLElement(event.target) && $isSelectionCapturedInDecorator(event.target)) {
2622
- return;
2636
+ editor.dispatchCommand(INPUT_COMMAND, event);
2637
+ }, {
2638
+ event
2639
+ });
2640
+ unprocessedBeforeInputData = null;
2641
+ }
2642
+ function $handleInput(event) {
2643
+ if (isHTMLElement(event.target) && $isSelectionCapturedInDecorator(event.target)) {
2644
+ return true;
2645
+ }
2646
+ const editor = getActiveEditor();
2647
+ const selection = $getSelection();
2648
+ const data = event.data;
2649
+ const targetRange = getTargetRange(event);
2650
+ if (data != null && $isRangeSelection(selection) && $shouldPreventDefaultAndInsertText(selection, targetRange, data, event.timeStamp, false)) {
2651
+ // Given we're over-riding the default behavior, we will need
2652
+ // to ensure to disable composition before dispatching the
2653
+ // insertText command for when changing the sequence for FF.
2654
+ if (isFirefoxEndingComposition) {
2655
+ $onCompositionEndImpl(editor, data);
2656
+ isFirefoxEndingComposition = false;
2623
2657
  }
2624
- const selection = $getSelection();
2625
- const data = event.data;
2626
- const targetRange = getTargetRange(event);
2627
- if (data != null && $isRangeSelection(selection) && $shouldPreventDefaultAndInsertText(selection, targetRange, data, event.timeStamp, false)) {
2628
- // Given we're over-riding the default behavior, we will need
2629
- // to ensure to disable composition before dispatching the
2630
- // insertText command for when changing the sequence for FF.
2631
- if (isFirefoxEndingComposition) {
2632
- $onCompositionEndImpl(editor, data);
2633
- isFirefoxEndingComposition = false;
2634
- }
2635
- const anchor = selection.anchor;
2636
- const anchorNode = anchor.getNode();
2637
- const domSelection = getDOMSelection(getWindow(editor));
2638
- if (domSelection === null) {
2639
- return;
2640
- }
2641
- const isBackward = selection.isBackward();
2642
- const startOffset = isBackward ? selection.anchor.offset : selection.focus.offset;
2643
- const endOffset = isBackward ? selection.focus.offset : selection.anchor.offset;
2644
- // If the content is the same as inserted, then don't dispatch an insertion.
2645
- // Given onInput doesn't take the current selection (it uses the previous)
2646
- // we can compare that against what the DOM currently says.
2647
- 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)) {
2648
- dispatchCommand(editor, CONTROLLED_TEXT_INSERTION_COMMAND, data);
2649
- }
2650
- const textLength = data.length;
2658
+ const anchor = selection.anchor;
2659
+ const anchorNode = anchor.getNode();
2660
+ const domSelection = getDOMSelection(getWindow(editor));
2661
+ if (domSelection === null) {
2662
+ return true;
2663
+ }
2664
+ const isBackward = selection.isBackward();
2665
+ const startOffset = isBackward ? selection.anchor.offset : selection.focus.offset;
2666
+ const endOffset = isBackward ? selection.focus.offset : selection.anchor.offset;
2667
+ // If the content is the same as inserted, then don't dispatch an insertion.
2668
+ // Given onInput doesn't take the current selection (it uses the previous)
2669
+ // we can compare that against what the DOM currently says.
2670
+ 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)) {
2671
+ dispatchCommand(editor, CONTROLLED_TEXT_INSERTION_COMMAND, data);
2672
+ }
2673
+ const textLength = data.length;
2651
2674
 
2652
- // Another hack for FF, as it's possible that the IME is still
2653
- // open, even though compositionend has already fired (sigh).
2654
- if (IS_FIREFOX && textLength > 1 && event.inputType === 'insertCompositionText' && !editor.isComposing()) {
2655
- selection.anchor.offset -= textLength;
2656
- }
2675
+ // Another hack for FF, as it's possible that the IME is still
2676
+ // open, even though compositionend has already fired (sigh).
2677
+ if (IS_FIREFOX && textLength > 1 && event.inputType === 'insertCompositionText' && !editor.isComposing()) {
2678
+ selection.anchor.offset -= textLength;
2679
+ }
2657
2680
 
2658
- // This ensures consistency on Android.
2659
- if (!IS_SAFARI && !IS_IOS && !IS_APPLE_WEBKIT && editor.isComposing()) {
2660
- lastKeyDownTimeStamp = 0;
2661
- $setCompositionKey(null);
2662
- }
2663
- } else {
2664
- const characterData = data !== null ? data : undefined;
2665
- $updateSelectedTextFromDOM(false, editor, characterData);
2681
+ // This ensures consistency on Android.
2682
+ if (!IS_SAFARI && !IS_IOS && !IS_APPLE_WEBKIT && editor.isComposing()) {
2683
+ lastKeyDownTimeStamp = 0;
2684
+ $setCompositionKey(null);
2685
+ }
2686
+ } else {
2687
+ const characterData = data !== null ? data : undefined;
2688
+ $updateSelectedTextFromDOM(false, editor, characterData);
2666
2689
 
2667
- // onInput always fires after onCompositionEnd for FF.
2668
- if (isFirefoxEndingComposition) {
2669
- $onCompositionEndImpl(editor, data || undefined);
2670
- isFirefoxEndingComposition = false;
2671
- }
2690
+ // onInput always fires after onCompositionEnd for FF.
2691
+ if (isFirefoxEndingComposition) {
2692
+ $onCompositionEndImpl(editor, data || undefined);
2693
+ isFirefoxEndingComposition = false;
2672
2694
  }
2695
+ }
2673
2696
 
2674
- // Also flush any other mutations that might have occurred
2675
- // since the change.
2676
- $flushMutations();
2677
- }, {
2678
- event
2679
- });
2680
- unprocessedBeforeInputData = null;
2697
+ // Also flush any other mutations that might have occurred
2698
+ // since the change.
2699
+ $flushMutations();
2700
+ return true;
2681
2701
  }
2682
2702
  function onCompositionStart(event, editor) {
2683
- updateEditorSync(editor, () => {
2684
- const selection = $getSelection();
2685
- if ($isRangeSelection(selection) && !editor.isComposing()) {
2686
- const anchor = selection.anchor;
2687
- const node = selection.anchor.getNode();
2688
- $setCompositionKey(anchor.key);
2689
- if (
2690
- // If it has been 30ms since the last keydown, then we should
2691
- // apply the empty space heuristic. We can't do this for Safari,
2692
- // as the keydown fires after composition start.
2693
- event.timeStamp < lastKeyDownTimeStamp + ANDROID_COMPOSITION_LATENCY ||
2694
- // FF has issues around composing multibyte characters, so we also
2695
- // need to invoke the empty space heuristic below.
2696
- anchor.type === 'element' || !selection.isCollapsed() || node.getFormat() !== selection.format || $isTextNode(node) && node.getStyle() !== selection.style) {
2697
- // We insert a zero width character, ready for the composition
2698
- // to get inserted into the new node we create. If
2699
- // we don't do this, Safari will fail on us because
2700
- // there is no text node matching the selection.
2701
- dispatchCommand(editor, CONTROLLED_TEXT_INSERTION_COMMAND, COMPOSITION_START_CHAR);
2702
- }
2703
+ dispatchCommand(editor, COMPOSITION_START_COMMAND, event);
2704
+ }
2705
+ function $handleCompositionStart(event) {
2706
+ const editor = getActiveEditor();
2707
+ const selection = $getSelection();
2708
+ if ($isRangeSelection(selection) && !editor.isComposing()) {
2709
+ const anchor = selection.anchor;
2710
+ const node = selection.anchor.getNode();
2711
+ $setCompositionKey(anchor.key);
2712
+ if (
2713
+ // If it has been 30ms since the last keydown, then we should
2714
+ // apply the empty space heuristic. We can't do this for Safari,
2715
+ // as the keydown fires after composition start.
2716
+ event.timeStamp < lastKeyDownTimeStamp + ANDROID_COMPOSITION_LATENCY ||
2717
+ // FF has issues around composing multibyte characters, so we also
2718
+ // need to invoke the empty space heuristic below.
2719
+ anchor.type === 'element' || !selection.isCollapsed() || node.getFormat() !== selection.format || $isTextNode(node) && node.getStyle() !== selection.style) {
2720
+ // We insert a zero width character, ready for the composition
2721
+ // to get inserted into the new node we create. If
2722
+ // we don't do this, Safari will fail on us because
2723
+ // there is no text node matching the selection.
2724
+ dispatchCommand(editor, CONTROLLED_TEXT_INSERTION_COMMAND, COMPOSITION_START_CHAR);
2703
2725
  }
2704
- });
2726
+ }
2727
+ return true;
2728
+ }
2729
+ function $handleCompositionEnd(event) {
2730
+ const editor = getActiveEditor();
2731
+ $onCompositionEndImpl(editor, event.data);
2732
+ return true;
2705
2733
  }
2706
2734
  function $onCompositionEndImpl(editor, data) {
2707
2735
  const compositionKey = editor._compositionKey;
@@ -2753,9 +2781,7 @@ function onCompositionEnd(event, editor) {
2753
2781
  isSafariEndingComposition = true;
2754
2782
  safariEndCompositionEventData = event.data;
2755
2783
  } else {
2756
- updateEditorSync(editor, () => {
2757
- $onCompositionEndImpl(editor, event.data);
2758
- });
2784
+ dispatchCommand(editor, COMPOSITION_END_COMMAND, event);
2759
2785
  }
2760
2786
  }
2761
2787
  function onKeyDown(event, editor) {
@@ -2764,11 +2790,12 @@ function onKeyDown(event, editor) {
2764
2790
  if (editor.isComposing()) {
2765
2791
  return;
2766
2792
  }
2767
- if (dispatchCommand(editor, KEY_DOWN_COMMAND, event)) {
2768
- return;
2769
- }
2793
+ dispatchCommand(editor, KEY_DOWN_COMMAND, event);
2794
+ }
2795
+ function $handleKeyDown(event) {
2796
+ const editor = getActiveEditor();
2770
2797
  if (event.key == null) {
2771
- return;
2798
+ return true;
2772
2799
  }
2773
2800
  if (isSafariEndingComposition && isBackspace(event)) {
2774
2801
  updateEditorSync(editor, () => {
@@ -2776,7 +2803,7 @@ function onKeyDown(event, editor) {
2776
2803
  });
2777
2804
  isSafariEndingComposition = false;
2778
2805
  safariEndCompositionEventData = '';
2779
- return;
2806
+ return true;
2780
2807
  }
2781
2808
  if (isMoveForward(event)) {
2782
2809
  dispatchCommand(editor, KEY_ARROW_RIGHT_COMMAND, event);
@@ -2867,8 +2894,9 @@ function onKeyDown(event, editor) {
2867
2894
  }
2868
2895
  }
2869
2896
  if (isModifier(event)) {
2870
- dispatchCommand(editor, KEY_MODIFIER_COMMAND, event);
2897
+ editor.dispatchCommand(KEY_MODIFIER_COMMAND, event);
2871
2898
  }
2899
+ return true;
2872
2900
  }
2873
2901
  function getRootElementRemoveHandles(rootElement) {
2874
2902
  // @ts-expect-error: internal field
@@ -10186,6 +10214,7 @@ function createEditor(editorConfig) {
10186
10214
  editor._pendingEditorState = initialEditorState;
10187
10215
  editor._dirtyType = FULL_RECONCILE;
10188
10216
  }
10217
+ registerDefaultCommandHandlers(editor);
10189
10218
  return editor;
10190
10219
  }
10191
10220
  class LexicalEditor {
@@ -10865,7 +10894,7 @@ class LexicalEditor {
10865
10894
  };
10866
10895
  }
10867
10896
  }
10868
- LexicalEditor.version = "0.38.3-nightly.20251106.0+dev.cjs";
10897
+ LexicalEditor.version = "0.38.3-nightly.20251110.0+dev.cjs";
10869
10898
 
10870
10899
  let pendingNodeToClone = null;
10871
10900
  function setPendingNodeToClone(pendingNode) {
@@ -14078,6 +14107,7 @@ exports.$splitAtPointCaretNext = $splitAtPointCaretNext;
14078
14107
  exports.$splitNode = $splitNode;
14079
14108
  exports.$updateRangeSelectionFromCaretRange = $updateRangeSelectionFromCaretRange;
14080
14109
  exports.ArtificialNode__DO_NOT_USE = ArtificialNode__DO_NOT_USE;
14110
+ exports.BEFORE_INPUT_COMMAND = BEFORE_INPUT_COMMAND;
14081
14111
  exports.BLUR_COMMAND = BLUR_COMMAND;
14082
14112
  exports.CAN_REDO_COMMAND = CAN_REDO_COMMAND;
14083
14113
  exports.CAN_UNDO_COMMAND = CAN_UNDO_COMMAND;
@@ -14090,6 +14120,8 @@ exports.COMMAND_PRIORITY_EDITOR = COMMAND_PRIORITY_EDITOR;
14090
14120
  exports.COMMAND_PRIORITY_HIGH = COMMAND_PRIORITY_HIGH;
14091
14121
  exports.COMMAND_PRIORITY_LOW = COMMAND_PRIORITY_LOW;
14092
14122
  exports.COMMAND_PRIORITY_NORMAL = COMMAND_PRIORITY_NORMAL;
14123
+ exports.COMPOSITION_END_COMMAND = COMPOSITION_END_COMMAND;
14124
+ exports.COMPOSITION_START_COMMAND = COMPOSITION_START_COMMAND;
14093
14125
  exports.CONTROLLED_TEXT_INSERTION_COMMAND = CONTROLLED_TEXT_INSERTION_COMMAND;
14094
14126
  exports.COPY_COMMAND = COPY_COMMAND;
14095
14127
  exports.CUT_COMMAND = CUT_COMMAND;
@@ -14109,6 +14141,7 @@ exports.HISTORIC_TAG = HISTORIC_TAG;
14109
14141
  exports.HISTORY_MERGE_TAG = HISTORY_MERGE_TAG;
14110
14142
  exports.HISTORY_PUSH_TAG = HISTORY_PUSH_TAG;
14111
14143
  exports.INDENT_CONTENT_COMMAND = INDENT_CONTENT_COMMAND;
14144
+ exports.INPUT_COMMAND = INPUT_COMMAND;
14112
14145
  exports.INSERT_LINE_BREAK_COMMAND = INSERT_LINE_BREAK_COMMAND;
14113
14146
  exports.INSERT_PARAGRAPH_COMMAND = INSERT_PARAGRAPH_COMMAND;
14114
14147
  exports.INSERT_TAB_COMMAND = INSERT_TAB_COMMAND;