lexical 0.25.1-nightly.20250227.0 → 0.26.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
@@ -8,6 +8,8 @@
8
8
 
9
9
  'use strict';
10
10
 
11
+ var lexical = require('lexical');
12
+
11
13
  /**
12
14
  * Copyright (c) Meta Platforms, Inc. and affiliates.
13
15
  *
@@ -176,6 +178,7 @@ const TEXT_TYPE_TO_MODE = {
176
178
  [IS_SEGMENTED]: 'segmented',
177
179
  [IS_TOKEN]: 'token'
178
180
  };
181
+ const NODE_STATE_KEY = '$';
179
182
 
180
183
  /**
181
184
  * Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -456,6 +459,601 @@ function initMutationObserver(editor) {
456
459
  });
457
460
  }
458
461
 
462
+ /**
463
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
464
+ *
465
+ * This source code is licensed under the MIT license found in the
466
+ * LICENSE file in the root directory of this source tree.
467
+ *
468
+ */
469
+
470
+ function coerceToJSON(v) {
471
+ return v;
472
+ }
473
+
474
+ /**
475
+ * The return value of {@link createState}, for use with
476
+ * {@link $getState} and {@link $setState}.
477
+ */
478
+ class StateConfig {
479
+ /** The string key used when serializing this state to JSON */
480
+
481
+ /** The parse function from the StateValueConfig passed to createState */
482
+
483
+ /**
484
+ * The unparse function from the StateValueConfig passed to createState,
485
+ * with a default that is simply a pass-through that assumes the value is
486
+ * JSON serializable.
487
+ */
488
+
489
+ /**
490
+ * An equality function from the StateValueConfig, with a default of
491
+ * Object.is.
492
+ */
493
+
494
+ /**
495
+ * The result of `stateValueConfig.parse(undefined)`, which is computed only
496
+ * once and used as the default value. When the current value `isEqual` to
497
+ * the `defaultValue`, it will not be serialized to JSON.
498
+ */
499
+
500
+ constructor(key, stateValueConfig) {
501
+ this.key = key;
502
+ this.parse = stateValueConfig.parse.bind(stateValueConfig);
503
+ this.unparse = (stateValueConfig.unparse || coerceToJSON).bind(stateValueConfig);
504
+ this.isEqual = (stateValueConfig.isEqual || Object.is).bind(stateValueConfig);
505
+ this.defaultValue = this.parse(undefined);
506
+ }
507
+ }
508
+
509
+ /**
510
+ * For advanced use cases, using this type is not recommended unless
511
+ * it is required (due to TypeScript's lack of features like
512
+ * higher-kinded types).
513
+ *
514
+ * A {@link StateConfig} type with any key and any value that can be
515
+ * used in situations where the key and value type can not be known,
516
+ * such as in a generic constraint when working with a collection of
517
+ * StateConfig.
518
+ *
519
+ * {@link StateConfigKey} and {@link StateConfigValue} will be
520
+ * useful when this is used as a generic constraint.
521
+ */
522
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
523
+
524
+ /**
525
+ * Get the value type (V) from a StateConfig
526
+ */
527
+
528
+ /**
529
+ * Get the key type (K) from a StateConfig
530
+ */
531
+
532
+ /**
533
+ * A value type, or an updater for that value type. For use with
534
+ * {@link $setState} or any user-defined wrappers around it.
535
+ */
536
+
537
+ /**
538
+ * Configure a value to be used with StateConfig.
539
+ *
540
+ * The value type should be inferred from the definition of parse.
541
+ *
542
+ * If the value type is not JSON serializable, then unparse must also be provided.
543
+ *
544
+ * Values should be treated as immutable, much like React.useState. Mutating
545
+ * stored values directly will cause unpredictable behavior, is not supported,
546
+ * and may trigger errors in the future.
547
+ *
548
+ * @example
549
+ * ```ts
550
+ * const numberOrNullState = createState('numberOrNull', {parse: (v) => typeof v === 'number' ? v : null});
551
+ * // ^? State<'numberOrNull', StateValueConfig<number | null>>
552
+ * const numberState = createState('number', {parse: (v) => typeof v === 'number' ? v : 0});
553
+ * // ^? State<'number', StateValueConfig<number>>
554
+ * ```
555
+ *
556
+ * Only the parse option is required, it is generally not useful to
557
+ * override `unparse` or `isEqual`. However, if you are using
558
+ * non-primitive types such as Array, Object, Date, or something
559
+ * more exotic then you would want to override this. In these
560
+ * cases you might want to reach for third party libraries.
561
+ *
562
+ * @example
563
+ * ```ts
564
+ * const isoDateState = createState('isoDate', {
565
+ * parse: (v): null | Date => {
566
+ * const date = typeof v === 'string' ? new Date(v) : null;
567
+ * return date && !isNaN(date.valueOf()) ? date : null;
568
+ * }
569
+ * isEqual: (a, b) => a === b || (a && b && a.valueOf() === b.valueOf()),
570
+ * unparse: (v) => v && v.toString()
571
+ * });
572
+ * ```
573
+ *
574
+ * You may find it easier to write a parse function using libraries like
575
+ * zod, valibot, ajv, Effect, TypeBox, etc. perhaps with a wrapper function.
576
+ */
577
+
578
+ /**
579
+ * Create a StateConfig for the given string key and StateValueConfig.
580
+ *
581
+ * The key must be locally unique. In dev you wil get a key collision error
582
+ * when you use two separate StateConfig on the same node with the same key.
583
+ *
584
+ * The returned StateConfig value should be used with {@link $getState} and
585
+ * {@link $setState}.
586
+ *
587
+ * @param key The key to use
588
+ * @param valueConfig Configuration for the value type
589
+ * @returns a StateConfig
590
+ */
591
+ function createState(key, valueConfig) {
592
+ return new StateConfig(key, valueConfig);
593
+ }
594
+
595
+ /**
596
+ * Given two versions of a node and a stateConfig, compare their state values
597
+ * using `$getState(nodeVersion, stateConfig, 'direct')`.
598
+ * If the values are equal according to `stateConfig.isEqual`, return `null`,
599
+ * otherwise return `[value, prevValue]`.
600
+ *
601
+ * This is useful for implementing updateDOM. Note that the `'direct'`
602
+ * version argument is used for both nodes.
603
+ *
604
+ * @param node Any LexicalNode
605
+ * @param prevNode A previous version of node
606
+ * @param stateConfig The configuration of the state to read
607
+ * @returns `[value, prevValue]` if changed, otherwise `null`
608
+ */
609
+ function $getStateChange(node, prevNode, stateConfig) {
610
+ const value = $getState(node, stateConfig, 'direct');
611
+ const prevValue = $getState(prevNode, stateConfig, 'direct');
612
+ return stateConfig.isEqual(value, prevValue) ? null : [value, prevValue];
613
+ }
614
+
615
+ /**
616
+ * The accessor for working with node state. This will read the value for the
617
+ * state on the given node, and will return `stateConfig.defaultValue` if the
618
+ * state has never been set on this node.
619
+ *
620
+ * The `version` parameter is optional and should generally be `'latest'`,
621
+ * consistent with the behavior of other node methods and functions,
622
+ * but for certain use cases such as `updateDOM` you may have a need to
623
+ * use `'direct'` to read the state from a previous version of the node.
624
+ *
625
+ * For very advanced use cases, you can expect that 'direct' does not
626
+ * require an editor state, just like directly accessing other properties
627
+ * of a node without an accessor (e.g. `textNode.__text`).
628
+ *
629
+ * @param node Any LexicalNode
630
+ * @param stateConfig The configuration of the state to read
631
+ * @param version The default value 'latest' will read the latest version of the node state, 'direct' will read the version that is stored on this LexicalNode which not reflect the version used in the current editor state
632
+ * @returns The current value from the state, or the default value provided by the configuration.
633
+ */
634
+ function $getState(node, stateConfig, version = 'latest') {
635
+ const latestOrDirectNode = version === 'latest' ? node.getLatest() : node;
636
+ const state = latestOrDirectNode.__state;
637
+ if (state) {
638
+ $checkCollision(node, stateConfig, state);
639
+ return state.getValue(stateConfig);
640
+ }
641
+ return stateConfig.defaultValue;
642
+ }
643
+
644
+ /**
645
+ * @internal
646
+ *
647
+ * Register the config to this node's sharedConfigMap and throw an exception in
648
+ * `true` when a collision is detected.
649
+ */
650
+ function $checkCollision(node, stateConfig, state) {
651
+ {
652
+ const collision = state.sharedConfigMap.get(stateConfig.key);
653
+ if (collision !== undefined && collision !== stateConfig) {
654
+ {
655
+ throw Error(`$setState: State key collision ${JSON.stringify(stateConfig.key)} detected in ${node.constructor.name} node with type ${node.getType()} and key ${node.getKey()}. Only one StateConfig with a given key should be used on a node.`);
656
+ }
657
+ }
658
+ }
659
+ }
660
+
661
+ /**
662
+ * Set the state defined by stateConfig on node. Like with `React.useState`
663
+ * you may directly specify the value or use an updater function that will
664
+ * be called with the previous value of the state on that node (which will
665
+ * be the `stateConfig.defaultValue` if not set).
666
+ *
667
+ * When an updater function is used, the node will only be marked dirty if
668
+ * `stateConfig.isEqual(prevValue, value)` is false.
669
+ *
670
+ * @example
671
+ * ```ts
672
+ * const toggle = createState('toggle', {parse: Boolean});
673
+ * // set it direction
674
+ * $setState(node, counterState, true);
675
+ * // use an updater
676
+ * $setState(node, counterState, (prev) => !prev);
677
+ * ```
678
+ *
679
+ * @param node The LexicalNode to set the state on
680
+ * @param stateConfig The configuration for this state
681
+ * @param valueOrUpdater The value or updater function
682
+ * @returns node
683
+ */
684
+ function $setState(node, stateConfig, valueOrUpdater) {
685
+ errorOnReadOnly();
686
+ let value;
687
+ if (typeof valueOrUpdater === 'function') {
688
+ const latest = node.getLatest();
689
+ const prevValue = $getState(latest, stateConfig);
690
+ value = valueOrUpdater(prevValue);
691
+ if (stateConfig.isEqual(prevValue, value)) {
692
+ return latest;
693
+ }
694
+ } else {
695
+ value = valueOrUpdater;
696
+ }
697
+ const writable = node.getWritable();
698
+ const state = $getWritableNodeState(writable);
699
+ $checkCollision(node, stateConfig, state);
700
+ state.updateFromKnown(stateConfig, value);
701
+ return writable;
702
+ }
703
+ /**
704
+ * @internal
705
+ */
706
+ class NodeState {
707
+ /**
708
+ * @internal
709
+ *
710
+ * Track the (versioned) node that this NodeState was created for, to
711
+ * facilitate copy-on-write for NodeState. When a LexicalNode is cloned,
712
+ * it will *reference* the NodeState from its prevNode. From the nextNode
713
+ * you can continue to read state without copying, but the first $setState
714
+ * will trigger a copy of the prevNode's NodeState with the node property
715
+ * updated.
716
+ */
717
+
718
+ /**
719
+ * @internal
720
+ *
721
+ * State that has already been parsed in a get state, so it is safe. (can be returned with
722
+ * just a cast since the proof was given before).
723
+ *
724
+ * Note that it uses StateConfig, so in addition to (1) the CURRENT VALUE, it has access to
725
+ * (2) the State key (3) the DEFAULT VALUE and (4) the PARSE FUNCTION
726
+ */
727
+
728
+ /**
729
+ * @internal
730
+ *
731
+ * A copy of serializedNode[NODE_STATE_KEY] that is made when JSON is
732
+ * imported but has not been parsed yet.
733
+ *
734
+ * It stays here until a get state requires us to parse it, and since we
735
+ * then know the value is safe we move it to knownState and garbage collect
736
+ * it at the next version.
737
+ *
738
+ * Note that since only string keys are used here, we can only allow this
739
+ * state to pass-through on export or on the next version since there is
740
+ * no known value configuration. This pass-through is to support scenarios
741
+ * where multiple versions of the editor code are working in parallel so
742
+ * an old version of your code doesnt erase metadata that was
743
+ * set by a newer version of your code.
744
+ */
745
+
746
+ /**
747
+ * @internal
748
+ *
749
+ * This sharedConfigMap is preserved across all versions of a given node and
750
+ * remains writable. It is how keys are resolved to configuration.
751
+ */
752
+
753
+ /**
754
+ * @internal
755
+ *
756
+ * The count of known or unknown keys in this state, ignoring the
757
+ * intersection between the two sets.
758
+ */
759
+
760
+ /**
761
+ * @internal
762
+ */
763
+ constructor(node, sharedConfigMap = new Map(), unknownState = undefined, knownState = new Map(), size = undefined) {
764
+ this.node = node;
765
+ this.sharedConfigMap = sharedConfigMap;
766
+ this.unknownState = unknownState;
767
+ this.knownState = knownState;
768
+ const computedSize = size !== undefined ? size : computeSize(sharedConfigMap, unknownState, knownState);
769
+ {
770
+ if (!(size === undefined || computedSize === size)) {
771
+ throw Error(`NodeState: size != computedSize (${String(size)} != ${String(computedSize)})`);
772
+ }
773
+ for (const stateConfig of knownState.keys()) {
774
+ if (!sharedConfigMap.has(stateConfig.key)) {
775
+ throw Error(`NodeState: sharedConfigMap missing knownState key ${stateConfig.key}`);
776
+ }
777
+ }
778
+ }
779
+ this.size = computedSize;
780
+ }
781
+
782
+ /** @internal */
783
+ getValue(stateConfig) {
784
+ const known = this.knownState.get(stateConfig);
785
+ if (known !== undefined) {
786
+ return known;
787
+ }
788
+ this.sharedConfigMap.set(stateConfig.key, stateConfig);
789
+ let parsed = stateConfig.defaultValue;
790
+ if (this.unknownState && stateConfig.key in this.unknownState) {
791
+ const jsonValue = this.unknownState[stateConfig.key];
792
+ if (jsonValue !== undefined) {
793
+ parsed = stateConfig.parse(jsonValue);
794
+ }
795
+ // Only update if the key was unknown
796
+ this.updateFromKnown(stateConfig, parsed);
797
+ }
798
+ return parsed;
799
+ }
800
+
801
+ /**
802
+ * @internal
803
+ *
804
+ * Used only for advanced use cases, such as collab. The intent here is to
805
+ * allow you to diff states with a more stable interface than the properties
806
+ * of this class.
807
+ */
808
+ getInternalState() {
809
+ return [this.unknownState, this.knownState];
810
+ }
811
+
812
+ /**
813
+ * Encode this NodeState to JSON in the format that its node expects.
814
+ * This returns `{[NODE_STATE_KEY]?: UnknownStateRecord}` rather than
815
+ * `UnknownStateRecord | undefined` so that we can support flattening
816
+ * specific entries in the future when nodes can declare what
817
+ * their required StateConfigs are.
818
+ */
819
+ toJSON() {
820
+ const state = {
821
+ ...this.unknownState
822
+ };
823
+ for (const [stateConfig, v] of this.knownState) {
824
+ if (stateConfig.isEqual(v, stateConfig.defaultValue)) {
825
+ delete state[stateConfig.key];
826
+ } else {
827
+ state[stateConfig.key] = stateConfig.unparse(v);
828
+ }
829
+ }
830
+ return undefinedIfEmpty(state) ? {
831
+ [NODE_STATE_KEY]: state
832
+ } : {};
833
+ }
834
+
835
+ /**
836
+ * @internal
837
+ *
838
+ * A NodeState is writable when the node to update matches
839
+ * the node associated with the NodeState. This basically
840
+ * mirrors how the EditorState NodeMap works, but in a
841
+ * bottom-up organization rather than a top-down organization.
842
+ *
843
+ * This allows us to implement the same "copy on write"
844
+ * pattern for state, without having the state version
845
+ * update every time the node version changes (e.g. when
846
+ * its parent or siblings change).
847
+ *
848
+ * @param node The node to associate with the state
849
+ * @returns The next writaable state
850
+ */
851
+ getWritable(node) {
852
+ if (this.node === node) {
853
+ return this;
854
+ }
855
+ const nextKnownState = new Map(this.knownState);
856
+ const nextUnknownState = cloneUnknownState(this.unknownState);
857
+ if (nextUnknownState) {
858
+ // Garbage collection
859
+ for (const stateConfig of nextKnownState.keys()) {
860
+ delete nextUnknownState[stateConfig.key];
861
+ }
862
+ }
863
+ return new NodeState(node, this.sharedConfigMap, undefinedIfEmpty(nextUnknownState), nextKnownState, this.size);
864
+ }
865
+
866
+ /** @internal */
867
+ updateFromKnown(stateConfig, value) {
868
+ const key = stateConfig.key;
869
+ this.sharedConfigMap.set(key, stateConfig);
870
+ const {
871
+ knownState,
872
+ unknownState
873
+ } = this;
874
+ if (!(knownState.has(stateConfig) || unknownState && key in unknownState)) {
875
+ this.size++;
876
+ }
877
+ knownState.set(stateConfig, value);
878
+ }
879
+
880
+ /**
881
+ * @internal
882
+ *
883
+ * This is intended for advanced use cases only, such
884
+ * as collab or dev tools.
885
+ *
886
+ * Update a single key value pair from unknown state,
887
+ * parsing it if the key is known to this node. This is
888
+ * basically like updateFromJSON, but the effect is
889
+ * isolated to a single entry.
890
+ *
891
+ * @param k The string key from an UnknownStateRecord
892
+ * @param v The unknown value from an UnknownStateRecord
893
+ */
894
+ updateFromUnknown(k, v) {
895
+ const stateConfig = this.sharedConfigMap.get(k);
896
+ if (stateConfig) {
897
+ this.updateFromKnown(stateConfig, stateConfig.parse(v));
898
+ } else {
899
+ this.unknownState = this.unknownState || {};
900
+ if (!(k in this.unknownState)) {
901
+ this.size++;
902
+ }
903
+ this.unknownState[k] = v;
904
+ }
905
+ }
906
+
907
+ /**
908
+ * @internal
909
+ *
910
+ * Reset all existing state to default or empty values,
911
+ * and perform any updates from the given unknownState.
912
+ *
913
+ * This is used when initializing a node's state from JSON,
914
+ * or when resetting a node's state from JSON.
915
+ *
916
+ * @param unknownState The new state in serialized form
917
+ */
918
+ updateFromJSON(unknownState) {
919
+ const {
920
+ knownState
921
+ } = this;
922
+ // Reset all known state to defaults
923
+ for (const stateConfig of knownState.keys()) {
924
+ knownState.set(stateConfig, stateConfig.defaultValue);
925
+ }
926
+ // Since we are resetting all state to this new record,
927
+ // the size starts at the number of known keys
928
+ // and will be updated as we traverse the new state
929
+ this.size = knownState.size;
930
+ this.unknownState = {};
931
+ if (unknownState) {
932
+ for (const [k, v] of Object.entries(unknownState)) {
933
+ this.updateFromUnknown(k, v);
934
+ }
935
+ }
936
+ this.unknownState = undefinedIfEmpty(this.unknownState);
937
+ }
938
+ }
939
+ function computeSize(sharedConfigMap, unknownState, knownState) {
940
+ let size = knownState.size;
941
+ if (unknownState) {
942
+ for (const k in unknownState) {
943
+ const sharedConfig = sharedConfigMap.get(k);
944
+ if (!sharedConfig || !knownState.has(sharedConfig)) {
945
+ size++;
946
+ }
947
+ }
948
+ }
949
+ return size;
950
+ }
951
+
952
+ /**
953
+ * Return obj if it is an object with at least one property, otherwise
954
+ * return undefined.
955
+ */
956
+ function undefinedIfEmpty(obj) {
957
+ if (obj) {
958
+ for (const key in obj) {
959
+ return obj;
960
+ }
961
+ }
962
+ return undefined;
963
+ }
964
+
965
+ /**
966
+ * Return undefined if unknownState is undefined or an empty object,
967
+ * otherwise return a shallow clone of it.
968
+ */
969
+ function cloneUnknownState(unknownState) {
970
+ return undefinedIfEmpty(unknownState) && {
971
+ ...unknownState
972
+ };
973
+ }
974
+
975
+ /**
976
+ * @internal
977
+ *
978
+ * Only for direct use in very advanced integrations, such as lexical-yjs.
979
+ * Typically you would only use {@link createState}, {@link $getState}, and
980
+ * {@link $setState}. This is effectively the preamble for {@link $setState}.
981
+ */
982
+ function $getWritableNodeState(node) {
983
+ const writable = node.getWritable();
984
+ const state = writable.__state ? writable.__state.getWritable(writable) : new NodeState(writable);
985
+ writable.__state = state;
986
+ return state;
987
+ }
988
+
989
+ /**
990
+ * @internal
991
+ *
992
+ * This is used to implement LexicalNode.updateFromJSON and is
993
+ * not intended to be exported from the package.
994
+ *
995
+ * @param node any LexicalNode
996
+ * @param unknownState undefined or a serialized State
997
+ * @returns A writable version of node, with the state set.
998
+ */
999
+ function $updateStateFromJSON(node, unknownState) {
1000
+ const writable = node.getWritable();
1001
+ if (unknownState || writable.__state) {
1002
+ $getWritableNodeState(node).updateFromJSON(unknownState);
1003
+ }
1004
+ return writable;
1005
+ }
1006
+
1007
+ /**
1008
+ * @internal
1009
+ *
1010
+ * Return true if the two nodes have equivalent NodeState, to be used
1011
+ * to determine when TextNode are being merged, not a lot of use cases
1012
+ * otherwise.
1013
+ */
1014
+ function $nodeStatesAreEquivalent(a, b) {
1015
+ if (a === b) {
1016
+ return true;
1017
+ }
1018
+ if (a && b && a.size !== b.size) {
1019
+ return false;
1020
+ }
1021
+ const keys = new Set();
1022
+ const hasUnequalMapEntry = (sourceState, otherState) => {
1023
+ for (const [stateConfig, value] of sourceState.knownState) {
1024
+ if (keys.has(stateConfig.key)) {
1025
+ continue;
1026
+ }
1027
+ keys.add(stateConfig.key);
1028
+ const otherValue = otherState ? otherState.getValue(stateConfig) : stateConfig.defaultValue;
1029
+ if (otherValue !== value && !stateConfig.isEqual(otherValue, value)) {
1030
+ return true;
1031
+ }
1032
+ }
1033
+ return false;
1034
+ };
1035
+ const hasUnequalRecordEntry = (sourceState, otherState) => {
1036
+ const {
1037
+ unknownState
1038
+ } = sourceState;
1039
+ const otherUnknownState = otherState ? otherState.unknownState : undefined;
1040
+ if (unknownState) {
1041
+ for (const [key, value] of Object.entries(unknownState)) {
1042
+ if (keys.has(key)) {
1043
+ continue;
1044
+ }
1045
+ keys.add(key);
1046
+ const otherValue = otherUnknownState ? otherUnknownState[key] : undefined;
1047
+ if (value !== otherValue) {
1048
+ return true;
1049
+ }
1050
+ }
1051
+ }
1052
+ return false;
1053
+ };
1054
+ return !(a && hasUnequalMapEntry(a, b) || b && hasUnequalMapEntry(b, a) || a && hasUnequalRecordEntry(a, b) || b && hasUnequalRecordEntry(b, a));
1055
+ }
1056
+
459
1057
  /**
460
1058
  * Copyright (c) Meta Platforms, Inc. and affiliates.
461
1059
  *
@@ -471,7 +1069,9 @@ function $canSimpleTextNodesBeMerged(node1, node2) {
471
1069
  const node2Mode = node2.__mode;
472
1070
  const node2Format = node2.__format;
473
1071
  const node2Style = node2.__style;
474
- return (node1Mode === null || node1Mode === node2Mode) && (node1Format === null || node1Format === node2Format) && (node1Style === null || node1Style === node2Style);
1072
+ const node1State = node1.__state;
1073
+ const node2State = node2.__state;
1074
+ return (node1Mode === null || node1Mode === node2Mode) && (node1Format === null || node1Format === node2Format) && (node1Style === null || node1Style === node2Style) && (node1.__state === null || node1State === node2State || $nodeStatesAreEquivalent(node1State, node2State));
475
1075
  }
476
1076
  function $mergeTextNodes(node1, node2) {
477
1077
  const writableNode1 = node1.mergeWithSibling(node2);
@@ -1237,6 +1837,7 @@ let isInsertLineBreak = false;
1237
1837
  let isFirefoxEndingComposition = false;
1238
1838
  let isSafariEndingComposition = false;
1239
1839
  let safariEndCompositionEventData = '';
1840
+ let postDeleteSelectionToRestore = null;
1240
1841
  let collapsedSelectionFormat = [0, '', 0, 'root', 0];
1241
1842
 
1242
1843
  // This function is used to determine if Lexical should attempt to override
@@ -1305,7 +1906,7 @@ function onSelectionChange(domSelection, editor, isActive) {
1305
1906
  // We also need to check if the offset is at the boundary,
1306
1907
  // because in this case, we might need to normalize to a
1307
1908
  // sibling instead.
1308
- if (shouldSkipSelectionChange(anchorDOM, anchorOffset) && shouldSkipSelectionChange(focusDOM, focusOffset)) {
1909
+ if (shouldSkipSelectionChange(anchorDOM, anchorOffset) && shouldSkipSelectionChange(focusDOM, focusOffset) && !postDeleteSelectionToRestore) {
1309
1910
  return;
1310
1911
  }
1311
1912
  }
@@ -1319,7 +1920,23 @@ function onSelectionChange(domSelection, editor, isActive) {
1319
1920
  if (!isSelectionWithinEditor(editor, anchorDOM, focusDOM)) {
1320
1921
  return;
1321
1922
  }
1322
- const selection = $getSelection();
1923
+ let selection = $getSelection();
1924
+
1925
+ // Restore selection in the event of incorrect rightward shift after deletion
1926
+ if (postDeleteSelectionToRestore && $isRangeSelection(selection) && selection.isCollapsed()) {
1927
+ const curAnchor = selection.anchor;
1928
+ const prevAnchor = postDeleteSelectionToRestore.anchor;
1929
+ if (
1930
+ // Rightward shift in same node
1931
+ curAnchor.key === prevAnchor.key && curAnchor.offset === prevAnchor.offset + 1 ||
1932
+ // Or rightward shift into sibling node
1933
+ curAnchor.offset === 1 && prevAnchor.getNode().is(curAnchor.getNode().getPreviousSibling())) {
1934
+ // Restore selection
1935
+ selection = postDeleteSelectionToRestore.clone();
1936
+ $setSelection(selection);
1937
+ }
1938
+ }
1939
+ postDeleteSelectionToRestore = null;
1323
1940
 
1324
1941
  // Update the selection format
1325
1942
  if ($isRangeSelection(selection)) {
@@ -1556,6 +2173,14 @@ function onBeforeInput(event, editor) {
1556
2173
  }
1557
2174
  if (!shouldLetBrowserHandleDelete) {
1558
2175
  dispatchCommand(editor, DELETE_CHARACTER_COMMAND, true);
2176
+ // When deleting across paragraphs, Chrome on Android incorrectly shifts the selection rightwards
2177
+ // We save the correct selection to restore later during handling of selectionchange event
2178
+ const selectionAfterDelete = $getSelection();
2179
+ if (IS_ANDROID_CHROME && $isRangeSelection(selectionAfterDelete) && selectionAfterDelete.isCollapsed()) {
2180
+ postDeleteSelectionToRestore = selectionAfterDelete;
2181
+ // Cleanup in case selectionchange does not fire
2182
+ setTimeout(() => postDeleteSelectionToRestore = null);
2183
+ }
1559
2184
  }
1560
2185
  }
1561
2186
  return;
@@ -2250,6 +2875,8 @@ class LexicalNode {
2250
2875
 
2251
2876
  /** @internal */
2252
2877
 
2878
+ /** @internal */
2879
+
2253
2880
  // Flow doesn't support abstract classes unfortunately, so we can't _force_
2254
2881
  // subclasses of Node to implement statics. All subclasses of Node should have
2255
2882
  // a static getType and clone method though. We define getType and clone here so we can call it
@@ -2333,6 +2960,7 @@ class LexicalNode {
2333
2960
  this.__parent = prevNode.__parent;
2334
2961
  this.__next = prevNode.__next;
2335
2962
  this.__prev = prevNode.__prev;
2963
+ this.__state = prevNode.__state;
2336
2964
  }
2337
2965
 
2338
2966
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -2342,6 +2970,12 @@ class LexicalNode {
2342
2970
  this.__parent = null;
2343
2971
  this.__prev = null;
2344
2972
  this.__next = null;
2973
+ Object.defineProperty(this, '__state', {
2974
+ configurable: true,
2975
+ enumerable: false,
2976
+ value: undefined,
2977
+ writable: true
2978
+ });
2345
2979
  $setNodeKey(this, key);
2346
2980
  {
2347
2981
  if (this.__type !== 'root') {
@@ -2859,9 +3493,12 @@ class LexicalNode {
2859
3493
  *
2860
3494
  * */
2861
3495
  exportJSON() {
3496
+ // eslint-disable-next-line dot-notation
3497
+ const state = this.__state ? this.__state.toJSON() : undefined;
2862
3498
  return {
2863
3499
  type: this.__type,
2864
- version: 1
3500
+ version: 1,
3501
+ ...state
2865
3502
  };
2866
3503
  }
2867
3504
 
@@ -2907,7 +3544,7 @@ class LexicalNode {
2907
3544
  * ```
2908
3545
  **/
2909
3546
  updateFromJSON(serializedNode) {
2910
- return this;
3547
+ return $updateStateFromJSON(this, serializedNode[lexical.NODE_STATE_KEY]);
2911
3548
  }
2912
3549
 
2913
3550
  /**
@@ -3257,6 +3894,9 @@ class LineBreakNode extends LexicalNode {
3257
3894
  updateDOM() {
3258
3895
  return false;
3259
3896
  }
3897
+ isInline() {
3898
+ return true;
3899
+ }
3260
3900
  static importDOM() {
3261
3901
  return {
3262
3902
  br: node => {
@@ -3634,6 +4274,13 @@ class TextNode extends LexicalNode {
3634
4274
  return true;
3635
4275
  }
3636
4276
 
4277
+ /**
4278
+ * @returns true if the text node is inline, false otherwise.
4279
+ */
4280
+ isInline() {
4281
+ return true;
4282
+ }
4283
+
3637
4284
  // View
3638
4285
 
3639
4286
  createDOM(config, editor) {
@@ -4526,22 +5173,12 @@ class Point {
4526
5173
  return this.key === point.key && this.offset === point.offset && this.type === point.type;
4527
5174
  }
4528
5175
  isBefore(b) {
4529
- let aNode = this.getNode();
4530
- let bNode = b.getNode();
4531
- const aOffset = this.offset;
4532
- const bOffset = b.offset;
4533
- if ($isElementNode(aNode)) {
4534
- const aNodeDescendant = aNode.getDescendantByIndex(aOffset);
4535
- aNode = aNodeDescendant != null ? aNodeDescendant : aNode;
4536
- }
4537
- if ($isElementNode(bNode)) {
4538
- const bNodeDescendant = bNode.getDescendantByIndex(bOffset);
4539
- bNode = bNodeDescendant != null ? bNodeDescendant : bNode;
4540
- }
4541
- if (aNode === bNode) {
4542
- return aOffset < bOffset;
5176
+ if (this.key === b.key) {
5177
+ return this.offset < b.offset;
4543
5178
  }
4544
- return aNode.isBefore(bNode);
5179
+ const aCaret = $normalizeCaret($caretFromPoint(this, 'next'));
5180
+ const bCaret = $normalizeCaret($caretFromPoint(b, 'next'));
5181
+ return $comparePointCaretNext(aCaret, bCaret) < 0;
4545
5182
  }
4546
5183
  getNode() {
4547
5184
  const key = this.key;
@@ -9644,7 +10281,7 @@ class LexicalEditor {
9644
10281
  };
9645
10282
  }
9646
10283
  }
9647
- LexicalEditor.version = "0.25.1-nightly.20250227.0+dev.cjs";
10284
+ LexicalEditor.version = "0.26.0+dev.cjs";
9648
10285
 
9649
10286
  /**
9650
10287
  * Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -11956,8 +12593,10 @@ function $caretRangeFromSelection(selection) {
11956
12593
  anchor,
11957
12594
  focus
11958
12595
  } = selection;
11959
- const direction = focus.isBefore(anchor) ? 'previous' : 'next';
11960
- return $getCaretRange($caretFromPoint(anchor, direction), $caretFromPoint(focus, direction));
12596
+ const anchorCaret = $caretFromPoint(anchor, 'next');
12597
+ const focusCaret = $caretFromPoint(focus, 'next');
12598
+ const direction = $comparePointCaretNext(anchorCaret, focusCaret) <= 0 ? 'next' : 'previous';
12599
+ return $getCaretRange($getCaretInDirection(anchorCaret, direction), $getCaretInDirection(focusCaret, direction));
11961
12600
  }
11962
12601
 
11963
12602
  /**
@@ -12318,10 +12957,13 @@ exports.$getPreviousSelection = $getPreviousSelection;
12318
12957
  exports.$getRoot = $getRoot;
12319
12958
  exports.$getSelection = $getSelection;
12320
12959
  exports.$getSiblingCaret = $getSiblingCaret;
12960
+ exports.$getState = $getState;
12961
+ exports.$getStateChange = $getStateChange;
12321
12962
  exports.$getTextContent = $getTextContent;
12322
12963
  exports.$getTextNodeOffset = $getTextNodeOffset;
12323
12964
  exports.$getTextPointCaret = $getTextPointCaret;
12324
12965
  exports.$getTextPointCaretSlice = $getTextPointCaretSlice;
12966
+ exports.$getWritableNodeState = $getWritableNodeState;
12325
12967
  exports.$hasAncestor = $hasAncestor;
12326
12968
  exports.$hasUpdateTag = $hasUpdateTag;
12327
12969
  exports.$insertNodes = $insertNodes;
@@ -12356,6 +12998,7 @@ exports.$setCompositionKey = $setCompositionKey;
12356
12998
  exports.$setPointFromCaret = $setPointFromCaret;
12357
12999
  exports.$setSelection = $setSelection;
12358
13000
  exports.$setSelectionFromCaretRange = $setSelectionFromCaretRange;
13001
+ exports.$setState = $setState;
12359
13002
  exports.$splitNode = $splitNode;
12360
13003
  exports.$updateRangeSelectionFromCaretRange = $updateRangeSelectionFromCaretRange;
12361
13004
  exports.ArtificialNode__DO_NOT_USE = ArtificialNode__DO_NOT_USE;
@@ -12414,6 +13057,7 @@ exports.KEY_TAB_COMMAND = KEY_TAB_COMMAND;
12414
13057
  exports.LineBreakNode = LineBreakNode;
12415
13058
  exports.MOVE_TO_END = MOVE_TO_END;
12416
13059
  exports.MOVE_TO_START = MOVE_TO_START;
13060
+ exports.NODE_STATE_KEY = NODE_STATE_KEY;
12417
13061
  exports.OUTDENT_CONTENT_COMMAND = OUTDENT_CONTENT_COMMAND;
12418
13062
  exports.PASTE_COMMAND = PASTE_COMMAND;
12419
13063
  exports.ParagraphNode = ParagraphNode;
@@ -12429,6 +13073,7 @@ exports.TextNode = TextNode;
12429
13073
  exports.UNDO_COMMAND = UNDO_COMMAND;
12430
13074
  exports.createCommand = createCommand;
12431
13075
  exports.createEditor = createEditor;
13076
+ exports.createState = createState;
12432
13077
  exports.flipDirection = flipDirection;
12433
13078
  exports.getDOMOwnerDocument = getDOMOwnerDocument;
12434
13079
  exports.getDOMSelection = getDOMSelection;