lexical 0.32.2-nightly.20250620.0 → 0.32.2-nightly.20250624.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 +578 -205
- package/Lexical.dev.mjs +574 -206
- package/Lexical.js.flow +224 -3
- package/Lexical.mjs +5 -0
- package/Lexical.node.mjs +5 -0
- package/Lexical.prod.js +1 -1
- package/Lexical.prod.mjs +1 -1
- package/LexicalConstants.d.ts +1 -0
- package/LexicalEditor.d.ts +31 -15
- package/LexicalNode.d.ts +165 -3
- package/LexicalNodeState.d.ts +157 -70
- package/LexicalUtils.d.ts +43 -2
- package/caret/LexicalCaretUtils.d.ts +4 -3
- package/index.d.ts +5 -4
- package/package.json +1 -1
package/Lexical.dev.js
CHANGED
|
@@ -191,6 +191,7 @@ const TEXT_TYPE_TO_MODE = {
|
|
|
191
191
|
[IS_TOKEN]: 'token'
|
|
192
192
|
};
|
|
193
193
|
const NODE_STATE_KEY = '$';
|
|
194
|
+
const PROTOTYPE_CONFIG_METHOD = '$config';
|
|
194
195
|
|
|
195
196
|
/**
|
|
196
197
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
@@ -471,71 +472,42 @@ function initMutationObserver(editor) {
|
|
|
471
472
|
});
|
|
472
473
|
}
|
|
473
474
|
|
|
474
|
-
function coerceToJSON(v) {
|
|
475
|
-
return v;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
475
|
/**
|
|
479
|
-
*
|
|
480
|
-
* {@link $getState} and {@link $setState}.
|
|
476
|
+
* Get the value type (V) from a StateConfig
|
|
481
477
|
*/
|
|
482
|
-
class StateConfig {
|
|
483
|
-
/** The string key used when serializing this state to JSON */
|
|
484
|
-
|
|
485
|
-
/** The parse function from the StateValueConfig passed to createState */
|
|
486
|
-
|
|
487
|
-
/**
|
|
488
|
-
* The unparse function from the StateValueConfig passed to createState,
|
|
489
|
-
* with a default that is simply a pass-through that assumes the value is
|
|
490
|
-
* JSON serializable.
|
|
491
|
-
*/
|
|
492
|
-
|
|
493
|
-
/**
|
|
494
|
-
* An equality function from the StateValueConfig, with a default of
|
|
495
|
-
* Object.is.
|
|
496
|
-
*/
|
|
497
|
-
|
|
498
|
-
/**
|
|
499
|
-
* The result of `stateValueConfig.parse(undefined)`, which is computed only
|
|
500
|
-
* once and used as the default value. When the current value `isEqual` to
|
|
501
|
-
* the `defaultValue`, it will not be serialized to JSON.
|
|
502
|
-
*/
|
|
503
|
-
|
|
504
|
-
constructor(key, stateValueConfig) {
|
|
505
|
-
this.key = key;
|
|
506
|
-
this.parse = stateValueConfig.parse.bind(stateValueConfig);
|
|
507
|
-
this.unparse = (stateValueConfig.unparse || coerceToJSON).bind(stateValueConfig);
|
|
508
|
-
this.isEqual = (stateValueConfig.isEqual || Object.is).bind(stateValueConfig);
|
|
509
|
-
this.defaultValue = this.parse(undefined);
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
478
|
|
|
513
479
|
/**
|
|
514
|
-
*
|
|
515
|
-
* it is required (due to TypeScript's lack of features like
|
|
516
|
-
* higher-kinded types).
|
|
517
|
-
*
|
|
518
|
-
* A {@link StateConfig} type with any key and any value that can be
|
|
519
|
-
* used in situations where the key and value type can not be known,
|
|
520
|
-
* such as in a generic constraint when working with a collection of
|
|
521
|
-
* StateConfig.
|
|
522
|
-
*
|
|
523
|
-
* {@link StateConfigKey} and {@link StateConfigValue} will be
|
|
524
|
-
* useful when this is used as a generic constraint.
|
|
480
|
+
* Get the key type (K) from a StateConfig
|
|
525
481
|
*/
|
|
526
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
527
482
|
|
|
528
483
|
/**
|
|
529
|
-
*
|
|
484
|
+
* A value type, or an updater for that value type. For use with
|
|
485
|
+
* {@link $setState} or any user-defined wrappers around it.
|
|
530
486
|
*/
|
|
531
487
|
|
|
532
488
|
/**
|
|
533
|
-
*
|
|
489
|
+
* A type alias to make it easier to define setter methods on your node class
|
|
490
|
+
*
|
|
491
|
+
* @example
|
|
492
|
+
* ```ts
|
|
493
|
+
* const fooState = createState("foo", { parse: ... });
|
|
494
|
+
* class MyClass extends TextNode {
|
|
495
|
+
* // ...
|
|
496
|
+
* setFoo(valueOrUpdater: StateValueOrUpdater<typeof fooState>): this {
|
|
497
|
+
* return $setState(this, fooState, valueOrUpdater);
|
|
498
|
+
* }
|
|
499
|
+
* }
|
|
500
|
+
* ```
|
|
534
501
|
*/
|
|
535
502
|
|
|
503
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
504
|
+
|
|
505
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
506
|
+
|
|
507
|
+
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
508
|
+
|
|
536
509
|
/**
|
|
537
|
-
*
|
|
538
|
-
* {@link $setState} or any user-defined wrappers around it.
|
|
510
|
+
* The NodeState JSON produced by this LexicalNode
|
|
539
511
|
*/
|
|
540
512
|
|
|
541
513
|
/**
|
|
@@ -579,6 +551,56 @@ class StateConfig {
|
|
|
579
551
|
* zod, valibot, ajv, Effect, TypeBox, etc. perhaps with a wrapper function.
|
|
580
552
|
*/
|
|
581
553
|
|
|
554
|
+
/**
|
|
555
|
+
* The return value of {@link createState}, for use with
|
|
556
|
+
* {@link $getState} and {@link $setState}.
|
|
557
|
+
*/
|
|
558
|
+
class StateConfig {
|
|
559
|
+
/** The string key used when serializing this state to JSON */
|
|
560
|
+
|
|
561
|
+
/** The parse function from the StateValueConfig passed to createState */
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* The unparse function from the StateValueConfig passed to createState,
|
|
565
|
+
* with a default that is simply a pass-through that assumes the value is
|
|
566
|
+
* JSON serializable.
|
|
567
|
+
*/
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* An equality function from the StateValueConfig, with a default of
|
|
571
|
+
* Object.is.
|
|
572
|
+
*/
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* The result of `stateValueConfig.parse(undefined)`, which is computed only
|
|
576
|
+
* once and used as the default value. When the current value `isEqual` to
|
|
577
|
+
* the `defaultValue`, it will not be serialized to JSON.
|
|
578
|
+
*/
|
|
579
|
+
|
|
580
|
+
constructor(key, stateValueConfig) {
|
|
581
|
+
this.key = key;
|
|
582
|
+
this.parse = stateValueConfig.parse.bind(stateValueConfig);
|
|
583
|
+
this.unparse = (stateValueConfig.unparse || coerceToJSON).bind(stateValueConfig);
|
|
584
|
+
this.isEqual = (stateValueConfig.isEqual || Object.is).bind(stateValueConfig);
|
|
585
|
+
this.defaultValue = this.parse(undefined);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* For advanced use cases, using this type is not recommended unless
|
|
591
|
+
* it is required (due to TypeScript's lack of features like
|
|
592
|
+
* higher-kinded types).
|
|
593
|
+
*
|
|
594
|
+
* A {@link StateConfig} type with any key and any value that can be
|
|
595
|
+
* used in situations where the key and value type can not be known,
|
|
596
|
+
* such as in a generic constraint when working with a collection of
|
|
597
|
+
* StateConfig.
|
|
598
|
+
*
|
|
599
|
+
* {@link StateConfigKey} and {@link StateConfigValue} will be
|
|
600
|
+
* useful when this is used as a generic constraint.
|
|
601
|
+
*/
|
|
602
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
603
|
+
|
|
582
604
|
/**
|
|
583
605
|
* Create a StateConfig for the given string key and StateValueConfig.
|
|
584
606
|
*
|
|
@@ -596,26 +618,6 @@ function createState(key, valueConfig) {
|
|
|
596
618
|
return new StateConfig(key, valueConfig);
|
|
597
619
|
}
|
|
598
620
|
|
|
599
|
-
/**
|
|
600
|
-
* Given two versions of a node and a stateConfig, compare their state values
|
|
601
|
-
* using `$getState(nodeVersion, stateConfig, 'direct')`.
|
|
602
|
-
* If the values are equal according to `stateConfig.isEqual`, return `null`,
|
|
603
|
-
* otherwise return `[value, prevValue]`.
|
|
604
|
-
*
|
|
605
|
-
* This is useful for implementing updateDOM. Note that the `'direct'`
|
|
606
|
-
* version argument is used for both nodes.
|
|
607
|
-
*
|
|
608
|
-
* @param node Any LexicalNode
|
|
609
|
-
* @param prevNode A previous version of node
|
|
610
|
-
* @param stateConfig The configuration of the state to read
|
|
611
|
-
* @returns `[value, prevValue]` if changed, otherwise `null`
|
|
612
|
-
*/
|
|
613
|
-
function $getStateChange(node, prevNode, stateConfig) {
|
|
614
|
-
const value = $getState(node, stateConfig, 'direct');
|
|
615
|
-
const prevValue = $getState(prevNode, stateConfig, 'direct');
|
|
616
|
-
return stateConfig.isEqual(value, prevValue) ? null : [value, prevValue];
|
|
617
|
-
}
|
|
618
|
-
|
|
619
621
|
/**
|
|
620
622
|
* The accessor for working with node state. This will read the value for the
|
|
621
623
|
* state on the given node, and will return `stateConfig.defaultValue` if the
|
|
@@ -646,20 +648,23 @@ function $getState(node, stateConfig, version = 'latest') {
|
|
|
646
648
|
}
|
|
647
649
|
|
|
648
650
|
/**
|
|
649
|
-
*
|
|
651
|
+
* Given two versions of a node and a stateConfig, compare their state values
|
|
652
|
+
* using `$getState(nodeVersion, stateConfig, 'direct')`.
|
|
653
|
+
* If the values are equal according to `stateConfig.isEqual`, return `null`,
|
|
654
|
+
* otherwise return `[value, prevValue]`.
|
|
650
655
|
*
|
|
651
|
-
*
|
|
652
|
-
*
|
|
656
|
+
* This is useful for implementing updateDOM. Note that the `'direct'`
|
|
657
|
+
* version argument is used for both nodes.
|
|
658
|
+
*
|
|
659
|
+
* @param node Any LexicalNode
|
|
660
|
+
* @param prevNode A previous version of node
|
|
661
|
+
* @param stateConfig The configuration of the state to read
|
|
662
|
+
* @returns `[value, prevValue]` if changed, otherwise `null`
|
|
653
663
|
*/
|
|
654
|
-
function $
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
{
|
|
659
|
-
formatDevErrorMessage(`$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.`);
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
}
|
|
664
|
+
function $getStateChange(node, prevNode, stateConfig) {
|
|
665
|
+
const value = $getState(node, stateConfig, 'direct');
|
|
666
|
+
const prevValue = $getState(prevNode, stateConfig, 'direct');
|
|
667
|
+
return stateConfig.isEqual(value, prevValue) ? null : [value, prevValue];
|
|
663
668
|
}
|
|
664
669
|
|
|
665
670
|
/**
|
|
@@ -704,6 +709,70 @@ function $setState(node, stateConfig, valueOrUpdater) {
|
|
|
704
709
|
state.updateFromKnown(stateConfig, value);
|
|
705
710
|
return writable;
|
|
706
711
|
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* @internal
|
|
715
|
+
*
|
|
716
|
+
* Register the config to this node's sharedConfigMap and throw an exception in
|
|
717
|
+
* `true` when a collision is detected.
|
|
718
|
+
*/
|
|
719
|
+
function $checkCollision(node, stateConfig, state) {
|
|
720
|
+
{
|
|
721
|
+
const collision = state.sharedNodeState.sharedConfigMap.get(stateConfig.key);
|
|
722
|
+
if (collision !== undefined && collision !== stateConfig) {
|
|
723
|
+
{
|
|
724
|
+
formatDevErrorMessage(`$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.`);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* @internal
|
|
732
|
+
*
|
|
733
|
+
* Opaque state to be stored on the editor's RegisterNode for use by NodeState
|
|
734
|
+
*/
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* @internal
|
|
738
|
+
*
|
|
739
|
+
* Create the state to store on RegisteredNode
|
|
740
|
+
*/
|
|
741
|
+
function createSharedNodeState(nodeConfig) {
|
|
742
|
+
const sharedConfigMap = new Map();
|
|
743
|
+
const flatKeys = new Set();
|
|
744
|
+
for (let klass = typeof nodeConfig === 'function' ? nodeConfig : nodeConfig.replace; klass.prototype && klass.prototype.getType !== undefined; klass = Object.getPrototypeOf(klass)) {
|
|
745
|
+
const {
|
|
746
|
+
ownNodeConfig
|
|
747
|
+
} = getStaticNodeConfig(klass);
|
|
748
|
+
if (ownNodeConfig && ownNodeConfig.stateConfigs) {
|
|
749
|
+
for (const requiredStateConfig of ownNodeConfig.stateConfigs) {
|
|
750
|
+
let stateConfig;
|
|
751
|
+
if ('stateConfig' in requiredStateConfig) {
|
|
752
|
+
stateConfig = requiredStateConfig.stateConfig;
|
|
753
|
+
if (requiredStateConfig.flat) {
|
|
754
|
+
flatKeys.add(stateConfig.key);
|
|
755
|
+
}
|
|
756
|
+
} else {
|
|
757
|
+
stateConfig = requiredStateConfig;
|
|
758
|
+
}
|
|
759
|
+
sharedConfigMap.set(stateConfig.key, stateConfig);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return {
|
|
764
|
+
flatKeys,
|
|
765
|
+
sharedConfigMap
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* @internal
|
|
771
|
+
*
|
|
772
|
+
* A Map of string keys to state configurations to be shared across nodes
|
|
773
|
+
* and/or node versions.
|
|
774
|
+
*/
|
|
775
|
+
|
|
707
776
|
/**
|
|
708
777
|
* @internal
|
|
709
778
|
*/
|
|
@@ -736,8 +805,7 @@ class NodeState {
|
|
|
736
805
|
* imported but has not been parsed yet.
|
|
737
806
|
*
|
|
738
807
|
* It stays here until a get state requires us to parse it, and since we
|
|
739
|
-
* then know the value is safe we move it to knownState
|
|
740
|
-
* it at the next version.
|
|
808
|
+
* then know the value is safe we move it to knownState.
|
|
741
809
|
*
|
|
742
810
|
* Note that since only string keys are used here, we can only allow this
|
|
743
811
|
* state to pass-through on export or on the next version since there is
|
|
@@ -750,8 +818,9 @@ class NodeState {
|
|
|
750
818
|
/**
|
|
751
819
|
* @internal
|
|
752
820
|
*
|
|
753
|
-
* This
|
|
754
|
-
* remains writable. It is how keys are resolved
|
|
821
|
+
* This sharedNodeState is preserved across all instances of a given
|
|
822
|
+
* node type in an editor and remains writable. It is how keys are resolved
|
|
823
|
+
* to configuration.
|
|
755
824
|
*/
|
|
756
825
|
|
|
757
826
|
/**
|
|
@@ -764,11 +833,14 @@ class NodeState {
|
|
|
764
833
|
/**
|
|
765
834
|
* @internal
|
|
766
835
|
*/
|
|
767
|
-
constructor(node,
|
|
836
|
+
constructor(node, sharedNodeState, unknownState = undefined, knownState = new Map(), size = undefined) {
|
|
768
837
|
this.node = node;
|
|
769
|
-
this.
|
|
838
|
+
this.sharedNodeState = sharedNodeState;
|
|
770
839
|
this.unknownState = unknownState;
|
|
771
840
|
this.knownState = knownState;
|
|
841
|
+
const {
|
|
842
|
+
sharedConfigMap
|
|
843
|
+
} = this.sharedNodeState;
|
|
772
844
|
const computedSize = size !== undefined ? size : computeSize(sharedConfigMap, unknownState, knownState);
|
|
773
845
|
{
|
|
774
846
|
if (!(size === undefined || computedSize === size)) {
|
|
@@ -783,13 +855,21 @@ class NodeState {
|
|
|
783
855
|
this.size = computedSize;
|
|
784
856
|
}
|
|
785
857
|
|
|
786
|
-
/**
|
|
858
|
+
/**
|
|
859
|
+
* @internal
|
|
860
|
+
*
|
|
861
|
+
* Get the value from knownState, or parse it from unknownState
|
|
862
|
+
* if it contains the given key.
|
|
863
|
+
*
|
|
864
|
+
* Updates the sharedConfigMap when no known state is found.
|
|
865
|
+
* Updates unknownState and knownState when an unknownState is parsed.
|
|
866
|
+
*/
|
|
787
867
|
getValue(stateConfig) {
|
|
788
868
|
const known = this.knownState.get(stateConfig);
|
|
789
869
|
if (known !== undefined) {
|
|
790
870
|
return known;
|
|
791
871
|
}
|
|
792
|
-
this.sharedConfigMap.set(stateConfig.key, stateConfig);
|
|
872
|
+
this.sharedNodeState.sharedConfigMap.set(stateConfig.key, stateConfig);
|
|
793
873
|
let parsed = stateConfig.defaultValue;
|
|
794
874
|
if (this.unknownState && stateConfig.key in this.unknownState) {
|
|
795
875
|
const jsonValue = this.unknownState[stateConfig.key];
|
|
@@ -824,6 +904,7 @@ class NodeState {
|
|
|
824
904
|
const state = {
|
|
825
905
|
...this.unknownState
|
|
826
906
|
};
|
|
907
|
+
const flatState = {};
|
|
827
908
|
for (const [stateConfig, v] of this.knownState) {
|
|
828
909
|
if (stateConfig.isEqual(v, stateConfig.defaultValue)) {
|
|
829
910
|
delete state[stateConfig.key];
|
|
@@ -831,9 +912,16 @@ class NodeState {
|
|
|
831
912
|
state[stateConfig.key] = stateConfig.unparse(v);
|
|
832
913
|
}
|
|
833
914
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
915
|
+
for (const key of this.sharedNodeState.flatKeys) {
|
|
916
|
+
if (key in state) {
|
|
917
|
+
flatState[key] = state[key];
|
|
918
|
+
delete state[key];
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
if (undefinedIfEmpty(state)) {
|
|
922
|
+
flatState[NODE_STATE_KEY] = state;
|
|
923
|
+
}
|
|
924
|
+
return flatState;
|
|
837
925
|
}
|
|
838
926
|
|
|
839
927
|
/**
|
|
@@ -856,26 +944,27 @@ class NodeState {
|
|
|
856
944
|
if (this.node === node) {
|
|
857
945
|
return this;
|
|
858
946
|
}
|
|
947
|
+
const {
|
|
948
|
+
sharedNodeState,
|
|
949
|
+
unknownState
|
|
950
|
+
} = this;
|
|
859
951
|
const nextKnownState = new Map(this.knownState);
|
|
860
|
-
|
|
861
|
-
if (nextUnknownState) {
|
|
862
|
-
// Garbage collection
|
|
863
|
-
for (const stateConfig of nextKnownState.keys()) {
|
|
864
|
-
delete nextUnknownState[stateConfig.key];
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
return new NodeState(node, this.sharedConfigMap, undefinedIfEmpty(nextUnknownState), nextKnownState, this.size);
|
|
952
|
+
return new NodeState(node, sharedNodeState, parseAndPruneNextUnknownState(sharedNodeState.sharedConfigMap, nextKnownState, unknownState), nextKnownState, this.size);
|
|
868
953
|
}
|
|
869
954
|
|
|
870
955
|
/** @internal */
|
|
871
956
|
updateFromKnown(stateConfig, value) {
|
|
872
957
|
const key = stateConfig.key;
|
|
873
|
-
this.sharedConfigMap.set(key, stateConfig);
|
|
958
|
+
this.sharedNodeState.sharedConfigMap.set(key, stateConfig);
|
|
874
959
|
const {
|
|
875
960
|
knownState,
|
|
876
961
|
unknownState
|
|
877
962
|
} = this;
|
|
878
963
|
if (!(knownState.has(stateConfig) || unknownState && key in unknownState)) {
|
|
964
|
+
if (unknownState) {
|
|
965
|
+
delete unknownState[key];
|
|
966
|
+
this.unknownState = undefinedIfEmpty(unknownState);
|
|
967
|
+
}
|
|
879
968
|
this.size++;
|
|
880
969
|
}
|
|
881
970
|
knownState.set(stateConfig, value);
|
|
@@ -896,7 +985,7 @@ class NodeState {
|
|
|
896
985
|
* @param v The unknown value from an UnknownStateRecord
|
|
897
986
|
*/
|
|
898
987
|
updateFromUnknown(k, v) {
|
|
899
|
-
const stateConfig = this.sharedConfigMap.get(k);
|
|
988
|
+
const stateConfig = this.sharedNodeState.sharedConfigMap.get(k);
|
|
900
989
|
if (stateConfig) {
|
|
901
990
|
this.updateFromKnown(stateConfig, stateConfig.parse(v));
|
|
902
991
|
} else {
|
|
@@ -931,15 +1020,77 @@ class NodeState {
|
|
|
931
1020
|
// the size starts at the number of known keys
|
|
932
1021
|
// and will be updated as we traverse the new state
|
|
933
1022
|
this.size = knownState.size;
|
|
934
|
-
this.unknownState =
|
|
1023
|
+
this.unknownState = undefined;
|
|
935
1024
|
if (unknownState) {
|
|
936
1025
|
for (const [k, v] of Object.entries(unknownState)) {
|
|
937
1026
|
this.updateFromUnknown(k, v);
|
|
938
1027
|
}
|
|
939
1028
|
}
|
|
940
|
-
this.unknownState = undefinedIfEmpty(this.unknownState);
|
|
941
1029
|
}
|
|
942
1030
|
}
|
|
1031
|
+
|
|
1032
|
+
/**
|
|
1033
|
+
* @internal
|
|
1034
|
+
*
|
|
1035
|
+
* Only for direct use in very advanced integrations, such as lexical-yjs.
|
|
1036
|
+
* Typically you would only use {@link createState}, {@link $getState}, and
|
|
1037
|
+
* {@link $setState}. This is effectively the preamble for {@link $setState}.
|
|
1038
|
+
*/
|
|
1039
|
+
function $getWritableNodeState(node) {
|
|
1040
|
+
const writable = node.getWritable();
|
|
1041
|
+
const state = writable.__state ? writable.__state.getWritable(writable) : new NodeState(writable, $getSharedNodeState(writable));
|
|
1042
|
+
writable.__state = state;
|
|
1043
|
+
return state;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
/**
|
|
1047
|
+
* @internal
|
|
1048
|
+
*
|
|
1049
|
+
* Get the SharedNodeState for a node on this editor
|
|
1050
|
+
*/
|
|
1051
|
+
function $getSharedNodeState(node) {
|
|
1052
|
+
return getRegisteredNodeOrThrow($getEditor(), node.getType()).sharedNodeState;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
/**
|
|
1056
|
+
* @internal
|
|
1057
|
+
*
|
|
1058
|
+
* This is used to implement LexicalNode.updateFromJSON and is
|
|
1059
|
+
* not intended to be exported from the package.
|
|
1060
|
+
*
|
|
1061
|
+
* @param node any LexicalNode
|
|
1062
|
+
* @param unknownState undefined or a serialized State
|
|
1063
|
+
* @returns A writable version of node, with the state set.
|
|
1064
|
+
*/
|
|
1065
|
+
function $updateStateFromJSON(node, unknownState) {
|
|
1066
|
+
const writable = node.getWritable();
|
|
1067
|
+
if (unknownState || writable.__state) {
|
|
1068
|
+
$getWritableNodeState(node).updateFromJSON(unknownState);
|
|
1069
|
+
}
|
|
1070
|
+
return writable;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
/**
|
|
1074
|
+
* @internal
|
|
1075
|
+
*
|
|
1076
|
+
* Return true if the two nodes have equivalent NodeState, to be used
|
|
1077
|
+
* to determine when TextNode are being merged, not a lot of use cases
|
|
1078
|
+
* otherwise.
|
|
1079
|
+
*/
|
|
1080
|
+
function nodeStatesAreEquivalent(a, b) {
|
|
1081
|
+
if (a === b) {
|
|
1082
|
+
return true;
|
|
1083
|
+
}
|
|
1084
|
+
if (a && b && a.size !== b.size) {
|
|
1085
|
+
return false;
|
|
1086
|
+
}
|
|
1087
|
+
const keys = new Set();
|
|
1088
|
+
return !(a && hasUnequalMapEntry(keys, a, b) || b && hasUnequalMapEntry(keys, b, a) || a && hasUnequalRecordEntry(keys, a, b) || b && hasUnequalRecordEntry(keys, b, a));
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
/**
|
|
1092
|
+
* Compute the number of distinct keys that will be in a NodeState
|
|
1093
|
+
*/
|
|
943
1094
|
function computeSize(sharedConfigMap, unknownState, knownState) {
|
|
944
1095
|
let size = knownState.size;
|
|
945
1096
|
if (unknownState) {
|
|
@@ -954,6 +1105,8 @@ function computeSize(sharedConfigMap, unknownState, knownState) {
|
|
|
954
1105
|
}
|
|
955
1106
|
|
|
956
1107
|
/**
|
|
1108
|
+
* @internal
|
|
1109
|
+
*
|
|
957
1110
|
* Return obj if it is an object with at least one property, otherwise
|
|
958
1111
|
* return undefined.
|
|
959
1112
|
*/
|
|
@@ -967,95 +1120,93 @@ function undefinedIfEmpty(obj) {
|
|
|
967
1120
|
}
|
|
968
1121
|
|
|
969
1122
|
/**
|
|
970
|
-
*
|
|
971
|
-
*
|
|
1123
|
+
* @internal
|
|
1124
|
+
*
|
|
1125
|
+
* Cast the given v to unknown
|
|
972
1126
|
*/
|
|
973
|
-
function
|
|
974
|
-
return
|
|
975
|
-
...unknownState
|
|
976
|
-
};
|
|
1127
|
+
function coerceToJSON(v) {
|
|
1128
|
+
return v;
|
|
977
1129
|
}
|
|
978
1130
|
|
|
979
1131
|
/**
|
|
980
1132
|
* @internal
|
|
981
1133
|
*
|
|
982
|
-
*
|
|
983
|
-
*
|
|
984
|
-
*
|
|
1134
|
+
* Parse all knowable values in an UnknownStateRecord into nextKnownState
|
|
1135
|
+
* and return the unparsed values in a new UnknownStateRecord. Returns
|
|
1136
|
+
* undefined if no unknown values remain.
|
|
985
1137
|
*/
|
|
986
|
-
function
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1138
|
+
function parseAndPruneNextUnknownState(sharedConfigMap, nextKnownState, unknownState) {
|
|
1139
|
+
let nextUnknownState = undefined;
|
|
1140
|
+
if (unknownState) {
|
|
1141
|
+
for (const [k, v] of Object.entries(unknownState)) {
|
|
1142
|
+
const stateConfig = sharedConfigMap.get(k);
|
|
1143
|
+
if (stateConfig) {
|
|
1144
|
+
if (!nextKnownState.has(stateConfig)) {
|
|
1145
|
+
nextKnownState.set(stateConfig, stateConfig.parse(v));
|
|
1146
|
+
}
|
|
1147
|
+
} else {
|
|
1148
|
+
nextUnknownState = nextUnknownState || {};
|
|
1149
|
+
nextUnknownState[k] = v;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
return nextUnknownState;
|
|
991
1154
|
}
|
|
992
1155
|
|
|
993
1156
|
/**
|
|
994
1157
|
* @internal
|
|
995
1158
|
*
|
|
996
|
-
*
|
|
997
|
-
*
|
|
1159
|
+
* Compare each entry of sourceState.knownState that is not in keys to
|
|
1160
|
+
* otherState (or the default value if otherState is undefined.
|
|
1161
|
+
* Note that otherState will return the defaultValue as well if it
|
|
1162
|
+
* has never been set. Any checked entry's key will be added to keys.
|
|
998
1163
|
*
|
|
999
|
-
* @
|
|
1000
|
-
* @param unknownState undefined or a serialized State
|
|
1001
|
-
* @returns A writable version of node, with the state set.
|
|
1164
|
+
* @returns true if any difference is found, false otherwise
|
|
1002
1165
|
*/
|
|
1003
|
-
function
|
|
1004
|
-
const
|
|
1005
|
-
|
|
1006
|
-
|
|
1166
|
+
function hasUnequalMapEntry(keys, sourceState, otherState) {
|
|
1167
|
+
for (const [stateConfig, value] of sourceState.knownState) {
|
|
1168
|
+
if (keys.has(stateConfig.key)) {
|
|
1169
|
+
continue;
|
|
1170
|
+
}
|
|
1171
|
+
keys.add(stateConfig.key);
|
|
1172
|
+
const otherValue = otherState ? otherState.getValue(stateConfig) : stateConfig.defaultValue;
|
|
1173
|
+
if (otherValue !== value && !stateConfig.isEqual(otherValue, value)) {
|
|
1174
|
+
return true;
|
|
1175
|
+
}
|
|
1007
1176
|
}
|
|
1008
|
-
return
|
|
1177
|
+
return false;
|
|
1009
1178
|
}
|
|
1010
1179
|
|
|
1011
1180
|
/**
|
|
1012
1181
|
* @internal
|
|
1013
1182
|
*
|
|
1014
|
-
*
|
|
1015
|
-
*
|
|
1016
|
-
*
|
|
1183
|
+
* Compare each entry of sourceState.unknownState that is not in keys to
|
|
1184
|
+
* otherState.unknownState (or undefined if otherState is undefined).
|
|
1185
|
+
* Any checked entry's key will be added to keys.
|
|
1186
|
+
*
|
|
1187
|
+
* Notably since we have already checked hasUnequalMapEntry on both sides,
|
|
1188
|
+
* we do not do any parsing or checking of knownState.
|
|
1189
|
+
*
|
|
1190
|
+
* @returns true if any difference is found, false otherwise
|
|
1017
1191
|
*/
|
|
1018
|
-
function
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
const hasUnequalMapEntry = (sourceState, otherState) => {
|
|
1027
|
-
for (const [stateConfig, value] of sourceState.knownState) {
|
|
1028
|
-
if (keys.has(stateConfig.key)) {
|
|
1192
|
+
function hasUnequalRecordEntry(keys, sourceState, otherState) {
|
|
1193
|
+
const {
|
|
1194
|
+
unknownState
|
|
1195
|
+
} = sourceState;
|
|
1196
|
+
const otherUnknownState = otherState ? otherState.unknownState : undefined;
|
|
1197
|
+
if (unknownState) {
|
|
1198
|
+
for (const [key, value] of Object.entries(unknownState)) {
|
|
1199
|
+
if (keys.has(key)) {
|
|
1029
1200
|
continue;
|
|
1030
1201
|
}
|
|
1031
|
-
keys.add(
|
|
1032
|
-
const otherValue =
|
|
1033
|
-
if (
|
|
1202
|
+
keys.add(key);
|
|
1203
|
+
const otherValue = otherUnknownState ? otherUnknownState[key] : undefined;
|
|
1204
|
+
if (value !== otherValue) {
|
|
1034
1205
|
return true;
|
|
1035
1206
|
}
|
|
1036
1207
|
}
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
const hasUnequalRecordEntry = (sourceState, otherState) => {
|
|
1040
|
-
const {
|
|
1041
|
-
unknownState
|
|
1042
|
-
} = sourceState;
|
|
1043
|
-
const otherUnknownState = otherState ? otherState.unknownState : undefined;
|
|
1044
|
-
if (unknownState) {
|
|
1045
|
-
for (const [key, value] of Object.entries(unknownState)) {
|
|
1046
|
-
if (keys.has(key)) {
|
|
1047
|
-
continue;
|
|
1048
|
-
}
|
|
1049
|
-
keys.add(key);
|
|
1050
|
-
const otherValue = otherUnknownState ? otherUnknownState[key] : undefined;
|
|
1051
|
-
if (value !== otherValue) {
|
|
1052
|
-
return true;
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
return false;
|
|
1057
|
-
};
|
|
1058
|
-
return !(a && hasUnequalMapEntry(a, b) || b && hasUnequalMapEntry(b, a) || a && hasUnequalRecordEntry(a, b) || b && hasUnequalRecordEntry(b, a));
|
|
1208
|
+
}
|
|
1209
|
+
return false;
|
|
1059
1210
|
}
|
|
1060
1211
|
|
|
1061
1212
|
/**
|
|
@@ -1075,7 +1226,7 @@ function $canSimpleTextNodesBeMerged(node1, node2) {
|
|
|
1075
1226
|
const node2Style = node2.__style;
|
|
1076
1227
|
const node1State = node1.__state;
|
|
1077
1228
|
const node2State = node2.__state;
|
|
1078
|
-
return (node1Mode === null || node1Mode === node2Mode) && (node1Format === null || node1Format === node2Format) && (node1Style === null || node1Style === node2Style) && (node1.__state === null || node1State === node2State ||
|
|
1229
|
+
return (node1Mode === null || node1Mode === node2Mode) && (node1Format === null || node1Format === node2Format) && (node1Style === null || node1Style === node2Style) && (node1.__state === null || node1State === node2State || nodeStatesAreEquivalent(node1State, node2State));
|
|
1079
1230
|
}
|
|
1080
1231
|
function $mergeTextNodes(node1, node2) {
|
|
1081
1232
|
const writableNode1 = node1.mergeWithSibling(node2);
|
|
@@ -2915,6 +3066,59 @@ function markCollapsedSelectionFormat(format, style, offset, key, timeStamp) {
|
|
|
2915
3066
|
* The base type for all serialized nodes
|
|
2916
3067
|
*/
|
|
2917
3068
|
|
|
3069
|
+
/**
|
|
3070
|
+
* EXPERIMENTAL
|
|
3071
|
+
* The configuration of a node returned by LexicalNode.$config()
|
|
3072
|
+
*
|
|
3073
|
+
* @example
|
|
3074
|
+
* ```ts
|
|
3075
|
+
* class CustomText extends TextNode {
|
|
3076
|
+
* $config() {
|
|
3077
|
+
* return this.config('custom-text', {extends: TextNode}};
|
|
3078
|
+
* }
|
|
3079
|
+
* }
|
|
3080
|
+
* ```
|
|
3081
|
+
*/
|
|
3082
|
+
|
|
3083
|
+
/**
|
|
3084
|
+
* This is the type of LexicalNode.$config() that can be
|
|
3085
|
+
* overridden by subclasses.
|
|
3086
|
+
*/
|
|
3087
|
+
|
|
3088
|
+
/**
|
|
3089
|
+
* Used to extract the node and type from a StaticNodeConfigRecord
|
|
3090
|
+
*/
|
|
3091
|
+
|
|
3092
|
+
/**
|
|
3093
|
+
* Any StaticNodeConfigValue (for generics and collections)
|
|
3094
|
+
*/
|
|
3095
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3096
|
+
|
|
3097
|
+
/**
|
|
3098
|
+
* @internal
|
|
3099
|
+
*
|
|
3100
|
+
* This is the more specific type than BaseStaticNodeConfig that a subclass
|
|
3101
|
+
* should return from $config()
|
|
3102
|
+
*/
|
|
3103
|
+
|
|
3104
|
+
/**
|
|
3105
|
+
* Extract the type from a node based on its $config
|
|
3106
|
+
*
|
|
3107
|
+
* @example
|
|
3108
|
+
* ```ts
|
|
3109
|
+
* type TextNodeType = GetStaticNodeType<TextNode>;
|
|
3110
|
+
* // ? 'text'
|
|
3111
|
+
* ```
|
|
3112
|
+
*/
|
|
3113
|
+
|
|
3114
|
+
/**
|
|
3115
|
+
* The most precise type we can infer for the JSON that will
|
|
3116
|
+
* be produced by T.exportJSON().
|
|
3117
|
+
*
|
|
3118
|
+
* Do not use this for the return type of T.exportJSON()! It must be
|
|
3119
|
+
* a more generic type to be compatible with subclassing.
|
|
3120
|
+
*/
|
|
3121
|
+
|
|
2918
3122
|
/**
|
|
2919
3123
|
* Omit the children, type, and version properties from the given SerializedLexicalNode definition.
|
|
2920
3124
|
*/
|
|
@@ -2959,6 +3163,14 @@ function $removeNode(nodeToRemove, restoreSelection, preserveEmptyParent) {
|
|
|
2959
3163
|
parent.selectEnd();
|
|
2960
3164
|
}
|
|
2961
3165
|
}
|
|
3166
|
+
/**
|
|
3167
|
+
* An identity function that will infer the type of DOM nodes
|
|
3168
|
+
* based on tag names to make it easier to construct a
|
|
3169
|
+
* DOMConversionMap.
|
|
3170
|
+
*/
|
|
3171
|
+
function buildImportMap(importMap) {
|
|
3172
|
+
return importMap;
|
|
3173
|
+
}
|
|
2962
3174
|
class LexicalNode {
|
|
2963
3175
|
// Allow us to look up the type including static props
|
|
2964
3176
|
|
|
@@ -3004,6 +3216,41 @@ class LexicalNode {
|
|
|
3004
3216
|
}
|
|
3005
3217
|
}
|
|
3006
3218
|
|
|
3219
|
+
/**
|
|
3220
|
+
* Override this to implement the new static node configuration protocol,
|
|
3221
|
+
* this method is called directly on the prototype and must not depend
|
|
3222
|
+
* on anything initialized in the constructor. Generally it should be
|
|
3223
|
+
* a trivial implementation.
|
|
3224
|
+
*
|
|
3225
|
+
* @example
|
|
3226
|
+
* ```ts
|
|
3227
|
+
* class MyNode extends TextNode {
|
|
3228
|
+
* $config() {
|
|
3229
|
+
* return this.config('my-node', {extends: TextNode});
|
|
3230
|
+
* }
|
|
3231
|
+
* }
|
|
3232
|
+
* ```
|
|
3233
|
+
*/
|
|
3234
|
+
$config() {
|
|
3235
|
+
return {};
|
|
3236
|
+
}
|
|
3237
|
+
|
|
3238
|
+
/**
|
|
3239
|
+
* This is a convenience method for $config that
|
|
3240
|
+
* aids in type inference. See {@link LexicalNode.$config}
|
|
3241
|
+
* for example usage.
|
|
3242
|
+
*/
|
|
3243
|
+
config(type, config) {
|
|
3244
|
+
const parentKlass = config.extends || Object.getPrototypeOf(this.constructor);
|
|
3245
|
+
Object.assign(config, {
|
|
3246
|
+
extends: parentKlass,
|
|
3247
|
+
type
|
|
3248
|
+
});
|
|
3249
|
+
return {
|
|
3250
|
+
[type]: config
|
|
3251
|
+
};
|
|
3252
|
+
}
|
|
3253
|
+
|
|
3007
3254
|
/**
|
|
3008
3255
|
* Perform any state updates on the clone of prevNode that are not already
|
|
3009
3256
|
* handled by the constructor call in the static clone method. If you have
|
|
@@ -3929,7 +4176,7 @@ class LexicalNode {
|
|
|
3929
4176
|
}
|
|
3930
4177
|
}
|
|
3931
4178
|
function errorOnTypeKlassMismatch(type, klass) {
|
|
3932
|
-
const registeredNode = getActiveEditor()
|
|
4179
|
+
const registeredNode = getRegisteredNode(getActiveEditor(), type);
|
|
3933
4180
|
// Common error - split in its own invariant
|
|
3934
4181
|
if (registeredNode === undefined) {
|
|
3935
4182
|
{
|
|
@@ -7913,6 +8160,7 @@ function parseEditorState(serializedEditorState, editor, updateFn) {
|
|
|
7913
8160
|
activeEditorState = editorState;
|
|
7914
8161
|
isReadOnlyMode = false;
|
|
7915
8162
|
activeEditor = editor;
|
|
8163
|
+
setPendingNodeToClone(null);
|
|
7916
8164
|
try {
|
|
7917
8165
|
const registeredNodes = editor._nodes;
|
|
7918
8166
|
const serializedNode = serializedEditorState.root;
|
|
@@ -8284,6 +8532,7 @@ function $beginUpdate(editor, updateFn, options) {
|
|
|
8284
8532
|
editor._updating = true;
|
|
8285
8533
|
activeEditor = editor;
|
|
8286
8534
|
const headless = editor._headless || editor.getRootElement() === null;
|
|
8535
|
+
setPendingNodeToClone(null);
|
|
8287
8536
|
try {
|
|
8288
8537
|
if (editorStateWasCloned) {
|
|
8289
8538
|
if (headless) {
|
|
@@ -9593,6 +9842,10 @@ function $isParagraphNode(node) {
|
|
|
9593
9842
|
|
|
9594
9843
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9595
9844
|
|
|
9845
|
+
/**
|
|
9846
|
+
* A LexicalNode class or LexicalNodeReplacement configuration
|
|
9847
|
+
*/
|
|
9848
|
+
|
|
9596
9849
|
const DEFAULT_SKIP_INITIALIZATION = false;
|
|
9597
9850
|
|
|
9598
9851
|
/**
|
|
@@ -9728,40 +9981,37 @@ function createEditor(editorConfig) {
|
|
|
9728
9981
|
replace = options.with;
|
|
9729
9982
|
replaceWithKlass = options.withKlass || null;
|
|
9730
9983
|
}
|
|
9984
|
+
const {
|
|
9985
|
+
ownNodeConfig
|
|
9986
|
+
} = getStaticNodeConfig(klass);
|
|
9731
9987
|
// Ensure custom nodes implement required methods and replaceWithKlass is instance of base klass.
|
|
9732
9988
|
{
|
|
9733
9989
|
// ArtificialNode__DO_NOT_USE can get renamed, so we use the type
|
|
9734
|
-
const nodeType = Object.prototype.hasOwnProperty.call(klass, 'getType') && klass.getType();
|
|
9735
9990
|
const name = klass.name;
|
|
9991
|
+
const nodeType = hasOwn(klass, 'getType') && klass.getType();
|
|
9736
9992
|
if (replaceWithKlass) {
|
|
9737
9993
|
if (!(replaceWithKlass.prototype instanceof klass)) {
|
|
9738
9994
|
formatDevErrorMessage(`${replaceWithKlass.name} doesn't extend the ${name}`);
|
|
9739
9995
|
}
|
|
9996
|
+
} else if (replace) {
|
|
9997
|
+
console.warn(`Override for ${name} specifies 'replace' without 'withKlass'. 'withKlass' will be required in a future version.`);
|
|
9740
9998
|
}
|
|
9741
9999
|
if (name !== 'RootNode' && nodeType !== 'root' && nodeType !== 'artificial') {
|
|
9742
10000
|
const proto = klass.prototype;
|
|
9743
10001
|
['getType', 'clone'].forEach(method => {
|
|
9744
|
-
|
|
9745
|
-
if (!klass.hasOwnProperty(method)) {
|
|
10002
|
+
if (!hasOwn(klass, method)) {
|
|
9746
10003
|
console.warn(`${name} must implement static "${method}" method`);
|
|
9747
10004
|
}
|
|
9748
10005
|
});
|
|
9749
|
-
if (
|
|
9750
|
-
// eslint-disable-next-line no-prototype-builtins
|
|
9751
|
-
!klass.hasOwnProperty('importDOM') &&
|
|
9752
|
-
// eslint-disable-next-line no-prototype-builtins
|
|
9753
|
-
klass.hasOwnProperty('exportDOM')) {
|
|
10006
|
+
if (!hasOwn(klass, 'importDOM') && hasOwn(klass, 'exportDOM')) {
|
|
9754
10007
|
console.warn(`${name} should implement "importDOM" if using a custom "exportDOM" method to ensure HTML serialization (important for copy & paste) works as expected`);
|
|
9755
10008
|
}
|
|
9756
10009
|
if ($isDecoratorNode(proto)) {
|
|
9757
|
-
|
|
9758
|
-
if (!proto.hasOwnProperty('decorate')) {
|
|
10010
|
+
if (!hasOwn(proto, 'decorate')) {
|
|
9759
10011
|
console.warn(`${proto.constructor.name} must implement "decorate" method`);
|
|
9760
10012
|
}
|
|
9761
10013
|
}
|
|
9762
|
-
if (
|
|
9763
|
-
// eslint-disable-next-line no-prototype-builtins
|
|
9764
|
-
!klass.hasOwnProperty('importJSON')) {
|
|
10014
|
+
if (!hasOwn(klass, 'importJSON')) {
|
|
9765
10015
|
console.warn(`${name} should implement "importJSON" method to ensure JSON and default HTML serialization works as expected`);
|
|
9766
10016
|
}
|
|
9767
10017
|
}
|
|
@@ -9769,6 +10019,9 @@ function createEditor(editorConfig) {
|
|
|
9769
10019
|
const type = klass.getType();
|
|
9770
10020
|
const transform = klass.transform();
|
|
9771
10021
|
const transforms = new Set();
|
|
10022
|
+
if (ownNodeConfig && ownNodeConfig.$transform) {
|
|
10023
|
+
transforms.add(ownNodeConfig.$transform);
|
|
10024
|
+
}
|
|
9772
10025
|
if (transform !== null) {
|
|
9773
10026
|
transforms.add(transform);
|
|
9774
10027
|
}
|
|
@@ -9777,6 +10030,7 @@ function createEditor(editorConfig) {
|
|
|
9777
10030
|
klass,
|
|
9778
10031
|
replace,
|
|
9779
10032
|
replaceWithKlass,
|
|
10033
|
+
sharedNodeState: createSharedNodeState(nodes[i]),
|
|
9780
10034
|
transforms
|
|
9781
10035
|
});
|
|
9782
10036
|
}
|
|
@@ -10361,14 +10615,6 @@ class LexicalEditor {
|
|
|
10361
10615
|
* where Lexical editor state can be safely mutated.
|
|
10362
10616
|
* @param updateFn - A function that has access to writable editor state.
|
|
10363
10617
|
* @param options - A bag of options to control the behavior of the update.
|
|
10364
|
-
* @param options.onUpdate - A function to run once the update is complete.
|
|
10365
|
-
* Useful for synchronizing updates in some cases.
|
|
10366
|
-
* @param options.skipTransforms - Setting this to true will suppress all node
|
|
10367
|
-
* transforms for this update cycle.
|
|
10368
|
-
* @param options.tag - A tag to identify this update, in an update listener, for instance.
|
|
10369
|
-
* Some tags are reserved by the core and control update behavior in different ways.
|
|
10370
|
-
* @param options.discrete - If true, prevents this update from being batched, forcing it to
|
|
10371
|
-
* run synchronously.
|
|
10372
10618
|
*/
|
|
10373
10619
|
update(updateFn, options) {
|
|
10374
10620
|
updateEditor(this, updateFn, options);
|
|
@@ -10382,8 +10628,6 @@ class LexicalEditor {
|
|
|
10382
10628
|
*
|
|
10383
10629
|
* @param callbackFn - A function to run after the editor is focused.
|
|
10384
10630
|
* @param options - A bag of options
|
|
10385
|
-
* @param options.defaultSelection - Where to move selection when the editor is
|
|
10386
|
-
* focused. Can be rootStart, rootEnd, or undefined. Defaults to rootEnd.
|
|
10387
10631
|
*/
|
|
10388
10632
|
focus(callbackFn, options = {}) {
|
|
10389
10633
|
const rootElement = this._rootElement;
|
|
@@ -10467,8 +10711,17 @@ class LexicalEditor {
|
|
|
10467
10711
|
};
|
|
10468
10712
|
}
|
|
10469
10713
|
}
|
|
10470
|
-
LexicalEditor.version = "0.32.2-nightly.
|
|
10714
|
+
LexicalEditor.version = "0.32.2-nightly.20250624.0+dev.cjs";
|
|
10471
10715
|
|
|
10716
|
+
let pendingNodeToClone = null;
|
|
10717
|
+
function setPendingNodeToClone(pendingNode) {
|
|
10718
|
+
pendingNodeToClone = pendingNode;
|
|
10719
|
+
}
|
|
10720
|
+
function getPendingNodeToClone() {
|
|
10721
|
+
const node = pendingNodeToClone;
|
|
10722
|
+
pendingNodeToClone = null;
|
|
10723
|
+
return node;
|
|
10724
|
+
}
|
|
10472
10725
|
let keyCounter = 1;
|
|
10473
10726
|
function resetRandomKey() {
|
|
10474
10727
|
keyCounter = 1;
|
|
@@ -10476,8 +10729,12 @@ function resetRandomKey() {
|
|
|
10476
10729
|
function generateRandomKey() {
|
|
10477
10730
|
return '' + keyCounter++;
|
|
10478
10731
|
}
|
|
10732
|
+
|
|
10733
|
+
/**
|
|
10734
|
+
* @internal
|
|
10735
|
+
*/
|
|
10479
10736
|
function getRegisteredNodeOrThrow(editor, nodeType) {
|
|
10480
|
-
const registeredNode = editor
|
|
10737
|
+
const registeredNode = getRegisteredNode(editor, nodeType);
|
|
10481
10738
|
if (registeredNode === undefined) {
|
|
10482
10739
|
{
|
|
10483
10740
|
formatDevErrorMessage(`registeredNode: Type ${nodeType} not found`);
|
|
@@ -10485,6 +10742,13 @@ function getRegisteredNodeOrThrow(editor, nodeType) {
|
|
|
10485
10742
|
}
|
|
10486
10743
|
return registeredNode;
|
|
10487
10744
|
}
|
|
10745
|
+
|
|
10746
|
+
/**
|
|
10747
|
+
* @internal
|
|
10748
|
+
*/
|
|
10749
|
+
function getRegisteredNode(editor, nodeType) {
|
|
10750
|
+
return editor._nodes.get(nodeType);
|
|
10751
|
+
}
|
|
10488
10752
|
const scheduleMicroTask = typeof queueMicrotask === 'function' ? queueMicrotask : fn => {
|
|
10489
10753
|
// No window prefix intended (#1400)
|
|
10490
10754
|
Promise.resolve().then(fn);
|
|
@@ -10610,9 +10874,11 @@ function $isLeafNode(node) {
|
|
|
10610
10874
|
return $isTextNode(node) || $isLineBreakNode(node) || $isDecoratorNode(node);
|
|
10611
10875
|
}
|
|
10612
10876
|
function $setNodeKey(node, existingKey) {
|
|
10877
|
+
const pendingNode = getPendingNodeToClone();
|
|
10878
|
+
existingKey = existingKey || pendingNode && pendingNode.__key;
|
|
10613
10879
|
if (existingKey != null) {
|
|
10614
10880
|
{
|
|
10615
|
-
errorOnNodeKeyConstructorMismatch(node, existingKey);
|
|
10881
|
+
errorOnNodeKeyConstructorMismatch(node, existingKey, pendingNode);
|
|
10616
10882
|
}
|
|
10617
10883
|
node.__key = existingKey;
|
|
10618
10884
|
return;
|
|
@@ -10633,13 +10899,18 @@ function $setNodeKey(node, existingKey) {
|
|
|
10633
10899
|
editor._dirtyType = HAS_DIRTY_NODES;
|
|
10634
10900
|
node.__key = key;
|
|
10635
10901
|
}
|
|
10636
|
-
function errorOnNodeKeyConstructorMismatch(node, existingKey) {
|
|
10902
|
+
function errorOnNodeKeyConstructorMismatch(node, existingKey, pendingNode) {
|
|
10637
10903
|
const editorState = internalGetActiveEditorState();
|
|
10638
10904
|
if (!editorState) {
|
|
10639
10905
|
// tests expect to be able to do this kind of clone without an active editor state
|
|
10640
10906
|
return;
|
|
10641
10907
|
}
|
|
10642
10908
|
const existingNode = editorState._nodeMap.get(existingKey);
|
|
10909
|
+
if (pendingNode) {
|
|
10910
|
+
if (!(existingKey === pendingNode.__key)) {
|
|
10911
|
+
formatDevErrorMessage(`Lexical node with constructor ${node.constructor.name} (type ${node.getType()}) has an incorrect clone implementation, got ${String(existingKey)} for nodeKey when expecting ${pendingNode.__key}`);
|
|
10912
|
+
}
|
|
10913
|
+
}
|
|
10643
10914
|
if (existingNode && existingNode.constructor !== node.constructor) {
|
|
10644
10915
|
// Lifted condition to if statement because the inverted logic is a bit confusing
|
|
10645
10916
|
if (node.constructor.name !== existingNode.constructor.name) {
|
|
@@ -11519,8 +11790,8 @@ function $copyNode(node) {
|
|
|
11519
11790
|
}
|
|
11520
11791
|
function $applyNodeReplacement(node) {
|
|
11521
11792
|
const editor = getActiveEditor();
|
|
11522
|
-
const nodeType = node.
|
|
11523
|
-
const registeredNode = editor
|
|
11793
|
+
const nodeType = node.getType();
|
|
11794
|
+
const registeredNode = getRegisteredNode(editor, nodeType);
|
|
11524
11795
|
if (!(registeredNode !== undefined)) {
|
|
11525
11796
|
formatDevErrorMessage(`$applyNodeReplacement node ${node.constructor.name} with type ${nodeType} must be registered to the editor. You can do this by passing the node class via the "nodes" array in the editor config.`);
|
|
11526
11797
|
}
|
|
@@ -11844,7 +12115,7 @@ function computeTypeToNodeMap(editorState) {
|
|
|
11844
12115
|
* do not try and use this function to duplicate or copy an existing node.
|
|
11845
12116
|
*
|
|
11846
12117
|
* Does not mutate the EditorState.
|
|
11847
|
-
* @param
|
|
12118
|
+
* @param latestNode - The node to be cloned.
|
|
11848
12119
|
* @returns The clone of the node.
|
|
11849
12120
|
*/
|
|
11850
12121
|
function $cloneWithProperties(latestNode) {
|
|
@@ -11888,6 +12159,102 @@ function isDOMUnmanaged(elementDom) {
|
|
|
11888
12159
|
return el.__lexicalUnmanaged === true;
|
|
11889
12160
|
}
|
|
11890
12161
|
|
|
12162
|
+
/**
|
|
12163
|
+
* @internal
|
|
12164
|
+
*
|
|
12165
|
+
* Object.hasOwn ponyfill
|
|
12166
|
+
*/
|
|
12167
|
+
function hasOwn(o, k) {
|
|
12168
|
+
return Object.prototype.hasOwnProperty.call(o, k);
|
|
12169
|
+
}
|
|
12170
|
+
|
|
12171
|
+
/** @internal */
|
|
12172
|
+
function isAbstractNodeClass(klass) {
|
|
12173
|
+
return klass === DecoratorNode || klass === ElementNode || klass === Object.getPrototypeOf(ElementNode);
|
|
12174
|
+
}
|
|
12175
|
+
|
|
12176
|
+
/** @internal */
|
|
12177
|
+
function getStaticNodeConfig(klass) {
|
|
12178
|
+
const nodeConfigRecord = PROTOTYPE_CONFIG_METHOD in klass.prototype ? klass.prototype[PROTOTYPE_CONFIG_METHOD]() : undefined;
|
|
12179
|
+
const isAbstract = isAbstractNodeClass(klass);
|
|
12180
|
+
const nodeType = !isAbstract && hasOwn(klass, 'getType') ? klass.getType() : undefined;
|
|
12181
|
+
let ownNodeConfig;
|
|
12182
|
+
let ownNodeType = nodeType;
|
|
12183
|
+
if (nodeConfigRecord) {
|
|
12184
|
+
if (nodeType) {
|
|
12185
|
+
ownNodeConfig = nodeConfigRecord[nodeType];
|
|
12186
|
+
} else {
|
|
12187
|
+
for (const [k, v] of Object.entries(nodeConfigRecord)) {
|
|
12188
|
+
ownNodeType = k;
|
|
12189
|
+
ownNodeConfig = v;
|
|
12190
|
+
}
|
|
12191
|
+
}
|
|
12192
|
+
}
|
|
12193
|
+
if (!isAbstract && ownNodeType) {
|
|
12194
|
+
if (!hasOwn(klass, 'getType')) {
|
|
12195
|
+
klass.getType = () => ownNodeType;
|
|
12196
|
+
}
|
|
12197
|
+
if (!hasOwn(klass, 'clone')) {
|
|
12198
|
+
{
|
|
12199
|
+
if (!(klass.length === 0)) {
|
|
12200
|
+
formatDevErrorMessage(`${klass.name} (type ${ownNodeType}) must implement a static clone method since its constructor has ${String(klass.length)} required arguments (expecting 0). Use an explicit default in the first argument of your constructor(prop: T=X, nodeKey?: NodeKey).`);
|
|
12201
|
+
}
|
|
12202
|
+
}
|
|
12203
|
+
klass.clone = prevNode => {
|
|
12204
|
+
setPendingNodeToClone(prevNode);
|
|
12205
|
+
return new klass();
|
|
12206
|
+
};
|
|
12207
|
+
}
|
|
12208
|
+
if (!hasOwn(klass, 'importJSON')) {
|
|
12209
|
+
{
|
|
12210
|
+
if (!(klass.length === 0)) {
|
|
12211
|
+
formatDevErrorMessage(`${klass.name} (type ${ownNodeType}) must implement a static importJSON method since its constructor has ${String(klass.length)} required arguments (expecting 0). Use an explicit default in the first argument of your constructor(prop: T=X, nodeKey?: NodeKey).`);
|
|
12212
|
+
}
|
|
12213
|
+
}
|
|
12214
|
+
klass.importJSON = ownNodeConfig && ownNodeConfig.$importJSON || (serializedNode => new klass().updateFromJSON(serializedNode));
|
|
12215
|
+
}
|
|
12216
|
+
if (!hasOwn(klass, 'importDOM') && ownNodeConfig) {
|
|
12217
|
+
const {
|
|
12218
|
+
importDOM
|
|
12219
|
+
} = ownNodeConfig;
|
|
12220
|
+
if (importDOM) {
|
|
12221
|
+
klass.importDOM = () => importDOM;
|
|
12222
|
+
}
|
|
12223
|
+
}
|
|
12224
|
+
}
|
|
12225
|
+
return {
|
|
12226
|
+
ownNodeConfig,
|
|
12227
|
+
ownNodeType
|
|
12228
|
+
};
|
|
12229
|
+
}
|
|
12230
|
+
|
|
12231
|
+
/**
|
|
12232
|
+
* Create an node from its class.
|
|
12233
|
+
*
|
|
12234
|
+
* Note that this will directly construct the final `withKlass` node type,
|
|
12235
|
+
* and will ignore the deprecated `with` functions. This allows `$create` to
|
|
12236
|
+
* skip any intermediate steps where the replaced node would be created and
|
|
12237
|
+
* then immediately discarded (once per configured replacement of that node).
|
|
12238
|
+
*
|
|
12239
|
+
* This does not support any arguments to the constructor.
|
|
12240
|
+
* Setters can be used to initialize your node, and they can
|
|
12241
|
+
* be chained. You can of course write your own mutliple-argument functions
|
|
12242
|
+
* to wrap that.
|
|
12243
|
+
*
|
|
12244
|
+
* @example
|
|
12245
|
+
* ```ts
|
|
12246
|
+
* function $createTokenText(text: string): TextNode {
|
|
12247
|
+
* return $create(TextNode).setTextContent(text).setMode('token');
|
|
12248
|
+
* }
|
|
12249
|
+
* ```
|
|
12250
|
+
*/
|
|
12251
|
+
function $create(klass) {
|
|
12252
|
+
const editor = $getEditor();
|
|
12253
|
+
errorOnReadOnly();
|
|
12254
|
+
const registeredNode = editor.resolveRegisteredNodeAfterReplacements(editor.getRegisteredNode(klass));
|
|
12255
|
+
return new registeredNode.klass();
|
|
12256
|
+
}
|
|
12257
|
+
|
|
11891
12258
|
/**
|
|
11892
12259
|
* The direction of a caret, 'next' points towards the end of the document
|
|
11893
12260
|
* and 'previous' points towards the beginning
|
|
@@ -12862,7 +13229,7 @@ function $isCaretAttached(caret) {
|
|
|
12862
13229
|
* blocks then the remaining contents of the later block will be merged with
|
|
12863
13230
|
* the earlier block.
|
|
12864
13231
|
*
|
|
12865
|
-
* @param
|
|
13232
|
+
* @param initialRange The range to remove text and nodes from
|
|
12866
13233
|
* @param sliceMode If 'preserveEmptyTextPointCaret' it will leave an empty TextPointCaret at the anchor for insert if one exists, otherwise empty slices will be removed
|
|
12867
13234
|
* @returns The new collapsed range (biased towards the earlier node)
|
|
12868
13235
|
*/
|
|
@@ -13136,8 +13503,9 @@ function $getChildCaretAtIndex(parent, index, direction) {
|
|
|
13136
13503
|
* R -> P -> T1, T2
|
|
13137
13504
|
* -> P2
|
|
13138
13505
|
* returns T2 for node T1, P2 for node T2, and null for node P2.
|
|
13139
|
-
* @param
|
|
13140
|
-
* @
|
|
13506
|
+
* @param startCaret The initial caret
|
|
13507
|
+
* @param rootMode The root mode, 'root' ('default') or 'shadowRoot'
|
|
13508
|
+
* @returns An array (tuple) containing the found caret and the depth difference, or null, if this node doesn't exist.
|
|
13141
13509
|
*/
|
|
13142
13510
|
function $getAdjacentSiblingOrParentSiblingCaret(startCaret, rootMode = 'root') {
|
|
13143
13511
|
let depthDiff = 0;
|
|
@@ -13237,6 +13605,7 @@ exports.$caretRangeFromSelection = $caretRangeFromSelection;
|
|
|
13237
13605
|
exports.$cloneWithProperties = $cloneWithProperties;
|
|
13238
13606
|
exports.$comparePointCaretNext = $comparePointCaretNext;
|
|
13239
13607
|
exports.$copyNode = $copyNode;
|
|
13608
|
+
exports.$create = $create;
|
|
13240
13609
|
exports.$createLineBreakNode = $createLineBreakNode;
|
|
13241
13610
|
exports.$createNodeSelection = $createNodeSelection;
|
|
13242
13611
|
exports.$createParagraphNode = $createParagraphNode;
|
|
@@ -13393,8 +13762,10 @@ exports.TEXT_TYPE_TO_FORMAT = TEXT_TYPE_TO_FORMAT;
|
|
|
13393
13762
|
exports.TabNode = TabNode;
|
|
13394
13763
|
exports.TextNode = TextNode;
|
|
13395
13764
|
exports.UNDO_COMMAND = UNDO_COMMAND;
|
|
13765
|
+
exports.buildImportMap = buildImportMap;
|
|
13396
13766
|
exports.createCommand = createCommand;
|
|
13397
13767
|
exports.createEditor = createEditor;
|
|
13768
|
+
exports.createSharedNodeState = createSharedNodeState;
|
|
13398
13769
|
exports.createState = createState;
|
|
13399
13770
|
exports.flipDirection = flipDirection;
|
|
13400
13771
|
exports.getDOMOwnerDocument = getDOMOwnerDocument;
|
|
@@ -13403,6 +13774,8 @@ exports.getDOMSelectionFromTarget = getDOMSelectionFromTarget;
|
|
|
13403
13774
|
exports.getDOMTextNode = getDOMTextNode;
|
|
13404
13775
|
exports.getEditorPropertyFromDOMNode = getEditorPropertyFromDOMNode;
|
|
13405
13776
|
exports.getNearestEditorFromDOMNode = getNearestEditorFromDOMNode;
|
|
13777
|
+
exports.getRegisteredNode = getRegisteredNode;
|
|
13778
|
+
exports.getRegisteredNodeOrThrow = getRegisteredNodeOrThrow;
|
|
13406
13779
|
exports.isBlockDomNode = isBlockDomNode;
|
|
13407
13780
|
exports.isCurrentlyReadOnlyMode = isCurrentlyReadOnlyMode;
|
|
13408
13781
|
exports.isDOMDocumentNode = isDOMDocumentNode;
|