@portabletext/editor 1.44.4 → 1.44.6

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/lib/index.js CHANGED
@@ -1,11 +1,10 @@
1
1
  import { getNodeBlock, getFirstBlock, getLastBlock, slateRangeToSelection, toSlateRange, debugWithName, EditorActorContext, fromSlateValue, KEY_TO_VALUE_ELEMENT, usePortableTextEditor, PortableTextEditor, moveRangeByOperation, isEqualToEmptyEditor, getEditorSnapshot, useEditor } from "./_chunks-es/editor-provider.js";
2
2
  import { EditorProvider, defineSchema, defaultKeyGenerator, useEditorSelector, usePortableTextEditorSelection } from "./_chunks-es/editor-provider.js";
3
3
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
4
- import { useSelector } from "@xstate/react";
5
- import isEqual from "lodash/isEqual.js";
4
+ import { useSelector, useActorRef } from "@xstate/react";
6
5
  import noop from "lodash/noop.js";
7
6
  import { useContext, useRef, useState, useEffect, useMemo, startTransition, useCallback, forwardRef, useImperativeHandle } from "react";
8
- import { Editor, Range, Element as Element$2, Text, Transforms, Path } from "slate";
7
+ import { Editor, Range, Element as Element$2, Text, Path, Transforms } from "slate";
9
8
  import { useSlateStatic, useSelected, ReactEditor, useSlate, Editable } from "slate-react";
10
9
  import { isSelectionCollapsed, getFocusTextBlock, getFocusSpan, getSelectedBlocks, isSelectionExpanded, getSelectionStartBlock, getSelectionEndBlock, isOverlappingSelection, getFocusBlock } from "./_chunks-es/selector.is-overlapping-selection.js";
11
10
  import { getBlockEndPoint, getBlockStartPoint, isKeyedSegment } from "./_chunks-es/util.slice-blocks.js";
@@ -13,10 +12,12 @@ import { getFocusInlineObject } from "./_chunks-es/selector.get-focus-inline-obj
13
12
  import { DOMEditor, isDOMNode } from "slate-dom";
14
13
  import { isSelectionCollapsed as isSelectionCollapsed$1, getSelectionEndPoint } from "./_chunks-es/util.is-selection-collapsed.js";
15
14
  import { parseBlocks } from "./_chunks-es/parse-blocks.js";
15
+ import isEqual from "lodash/isEqual.js";
16
16
  import { isSelectingEntireBlocks } from "./_chunks-es/selector.is-selecting-entire-blocks.js";
17
17
  import { defineBehavior, isHotkey } from "./_chunks-es/behavior.core.js";
18
18
  import { c } from "react-compiler-runtime";
19
19
  import uniq from "lodash/uniq.js";
20
+ import { setup, fromCallback, assign, assertEvent, and } from "xstate";
20
21
  import { useEffectEvent } from "use-effect-event";
21
22
  function getCompoundClientRect(nodes) {
22
23
  if (nodes.length === 0)
@@ -723,18 +724,232 @@ function createWithHotkeys(editorActor, portableTextEditor, hotkeysFromOptions)
723
724
  }, editor;
724
725
  };
725
726
  }
726
- function withSyncRangeDecorations({
727
- editorActor,
728
- slateEditor,
729
- syncRangeDecorations
730
- }) {
731
- const originalApply = slateEditor.apply;
732
- return slateEditor.apply = (op) => {
733
- originalApply(op), !editorActor.getSnapshot().matches({
734
- "edit mode": "read only"
735
- }) && op.type !== "set_selection" && syncRangeDecorations(op);
727
+ const slateOperationCallback = ({
728
+ input,
729
+ sendBack
730
+ }) => {
731
+ const originalApply = input.slateEditor.apply;
732
+ return input.slateEditor.apply = (op) => {
733
+ op.type !== "set_selection" && sendBack({
734
+ type: "slate operation",
735
+ operation: op
736
+ }), originalApply(op);
736
737
  }, () => {
737
- slateEditor.apply = originalApply;
738
+ input.slateEditor.apply = originalApply;
739
+ };
740
+ }, rangeDecorationsMachine = setup({
741
+ types: {
742
+ context: {},
743
+ input: {},
744
+ events: {}
745
+ },
746
+ actions: {
747
+ "update pending range decorations": assign({
748
+ pendingRangeDecorations: ({
749
+ event
750
+ }) => (assertEvent(event, "range decorations updated"), event.rangeDecorations)
751
+ }),
752
+ "set up initial range decorations": assign({
753
+ decoratedRanges: ({
754
+ context,
755
+ event
756
+ }) => {
757
+ assertEvent(event, "ready");
758
+ const rangeDecorationState = [];
759
+ for (const rangeDecoration of context.pendingRangeDecorations) {
760
+ const slateRange = toSlateRange(rangeDecoration.selection, context.slateEditor);
761
+ if (!Range.isRange(slateRange)) {
762
+ rangeDecoration.onMoved?.({
763
+ newSelection: null,
764
+ rangeDecoration,
765
+ origin: "local"
766
+ });
767
+ continue;
768
+ }
769
+ rangeDecorationState.push({
770
+ rangeDecoration,
771
+ ...slateRange
772
+ });
773
+ }
774
+ return rangeDecorationState;
775
+ }
776
+ }),
777
+ "update range decorations": assign({
778
+ decoratedRanges: ({
779
+ context,
780
+ event
781
+ }) => {
782
+ assertEvent(event, "range decorations updated");
783
+ const rangeDecorationState = [];
784
+ for (const rangeDecoration of event.rangeDecorations) {
785
+ const slateRange = toSlateRange(rangeDecoration.selection, context.slateEditor);
786
+ if (!Range.isRange(slateRange)) {
787
+ rangeDecoration.onMoved?.({
788
+ newSelection: null,
789
+ rangeDecoration,
790
+ origin: "local"
791
+ });
792
+ continue;
793
+ }
794
+ rangeDecorationState.push({
795
+ rangeDecoration,
796
+ ...slateRange
797
+ });
798
+ }
799
+ return rangeDecorationState;
800
+ }
801
+ }),
802
+ "move range decorations": assign({
803
+ decoratedRanges: ({
804
+ context,
805
+ event
806
+ }) => {
807
+ assertEvent(event, "slate operation");
808
+ const rangeDecorationState = [];
809
+ for (const decoratedRange of context.decoratedRanges) {
810
+ const slateRange = toSlateRange(decoratedRange.rangeDecoration.selection, context.slateEditor);
811
+ if (!Range.isRange(slateRange)) {
812
+ decoratedRange.rangeDecoration.onMoved?.({
813
+ newSelection: null,
814
+ rangeDecoration: decoratedRange.rangeDecoration,
815
+ origin: "local"
816
+ });
817
+ continue;
818
+ }
819
+ let newRange;
820
+ if (newRange = moveRangeByOperation(slateRange, event.operation), newRange && newRange !== slateRange || newRange === null && slateRange) {
821
+ const newRangeSelection = newRange ? slateRangeToSelection({
822
+ schema: context.schema,
823
+ editor: context.slateEditor,
824
+ range: newRange
825
+ }) : null;
826
+ decoratedRange.rangeDecoration.onMoved?.({
827
+ newSelection: newRangeSelection,
828
+ rangeDecoration: decoratedRange.rangeDecoration,
829
+ origin: "local"
830
+ });
831
+ }
832
+ newRange !== null && rangeDecorationState.push({
833
+ ...newRange || slateRange,
834
+ rangeDecoration: decoratedRange.rangeDecoration
835
+ });
836
+ }
837
+ return rangeDecorationState;
838
+ }
839
+ }),
840
+ "assign readOnly": assign({
841
+ readOnly: ({
842
+ event
843
+ }) => (assertEvent(event, "update read only"), event.readOnly)
844
+ })
845
+ },
846
+ actors: {
847
+ "slate operation listener": fromCallback(slateOperationCallback)
848
+ },
849
+ guards: {
850
+ "has range decorations": ({
851
+ context
852
+ }) => context.decoratedRanges.length > 0,
853
+ "has different decorations": ({
854
+ context,
855
+ event
856
+ }) => (assertEvent(event, "range decorations updated"), !isEqual(context.pendingRangeDecorations, event.rangeDecorations)),
857
+ "not read only": ({
858
+ context
859
+ }) => !context.readOnly
860
+ }
861
+ }).createMachine({
862
+ id: "range decorations",
863
+ context: ({
864
+ input
865
+ }) => ({
866
+ readOnly: input.readOnly,
867
+ pendingRangeDecorations: input.rangeDecorations,
868
+ decoratedRanges: [],
869
+ schema: input.schema,
870
+ slateEditor: input.slateEditor
871
+ }),
872
+ invoke: {
873
+ src: "slate operation listener",
874
+ input: ({
875
+ context
876
+ }) => ({
877
+ slateEditor: context.slateEditor
878
+ })
879
+ },
880
+ on: {
881
+ "update read only": {
882
+ actions: ["assign readOnly"]
883
+ }
884
+ },
885
+ initial: "idle",
886
+ states: {
887
+ idle: {
888
+ on: {
889
+ "range decorations updated": {
890
+ actions: ["update pending range decorations"]
891
+ },
892
+ ready: {
893
+ target: "ready",
894
+ actions: ["set up initial range decorations"]
895
+ }
896
+ }
897
+ },
898
+ ready: {
899
+ initial: "idle",
900
+ on: {
901
+ "range decorations updated": {
902
+ target: ".idle",
903
+ guard: "has different decorations",
904
+ actions: ["update range decorations", "update pending range decorations"]
905
+ }
906
+ },
907
+ states: {
908
+ idle: {
909
+ on: {
910
+ "slate operation": {
911
+ target: "moving range decorations",
912
+ guard: and(["has range decorations", "not read only"])
913
+ }
914
+ }
915
+ },
916
+ "moving range decorations": {
917
+ entry: ["move range decorations"],
918
+ always: {
919
+ target: "idle"
920
+ }
921
+ }
922
+ }
923
+ }
924
+ }
925
+ });
926
+ function createDecorate(rangeDecorationActor) {
927
+ return function([, path]) {
928
+ if (isEqualToEmptyEditor(rangeDecorationActor.getSnapshot().context.slateEditor.children, rangeDecorationActor.getSnapshot().context.schema))
929
+ return [{
930
+ anchor: {
931
+ path: [0, 0],
932
+ offset: 0
933
+ },
934
+ focus: {
935
+ path: [0, 0],
936
+ offset: 0
937
+ },
938
+ placeholder: !0
939
+ }];
940
+ if (path.length === 0)
941
+ return [];
942
+ const result = rangeDecorationActor.getSnapshot().context.decoratedRanges.filter((item) => Range.isCollapsed(item) ? path.length !== 2 ? !1 : Path.equals(item.focus.path, path) && Path.equals(item.anchor.path, path) : Range.intersection(item, {
943
+ anchor: {
944
+ path,
945
+ offset: 0
946
+ },
947
+ focus: {
948
+ path,
949
+ offset: 0
950
+ }
951
+ }) || Range.includes(item, path));
952
+ return result.length > 0 ? result : [];
738
953
  };
739
954
  }
740
955
  const debug = debugWithName("component:Editable"), PLACEHOLDER_STYLE = {
@@ -772,11 +987,30 @@ const debug = debugWithName("component:Editable"), PLACEHOLDER_STYLE = {
772
987
  scrollSelectionIntoView,
773
988
  spellCheck,
774
989
  ...restProps
775
- } = props, portableTextEditor = usePortableTextEditor(), ref = useRef(null), [editableElement, setEditableElement] = useState(null), [hasInvalidValue, setHasInvalidValue] = useState(!1), [rangeDecorationState, setRangeDecorationsState] = useState([]);
990
+ } = props, portableTextEditor = usePortableTextEditor(), ref = useRef(null), [editableElement, setEditableElement] = useState(null), [hasInvalidValue, setHasInvalidValue] = useState(!1);
776
991
  useImperativeHandle(forwardedRef, () => ref.current);
777
- const rangeDecorationsRef = useRef(rangeDecorations), editorActor = useContext(EditorActorContext), readOnly = useSelector(editorActor, (s) => s.matches({
992
+ const editorActor = useContext(EditorActorContext), readOnly = useSelector(editorActor, (s) => s.matches({
778
993
  "edit mode": "read only"
779
- })), schemaTypes = useSelector(editorActor, (s_0) => s_0.context.schema), slateEditor = useSlate(), blockTypeName = schemaTypes.block.name;
994
+ })), schemaTypes = useSelector(editorActor, (s_0) => s_0.context.schema), slateEditor = useSlate(), rangeDecorationsActor = useActorRef(rangeDecorationsMachine, {
995
+ input: {
996
+ readOnly,
997
+ slateEditor,
998
+ schema: schemaTypes,
999
+ rangeDecorations: rangeDecorations ?? []
1000
+ }
1001
+ }), decorate = useMemo(() => createDecorate(rangeDecorationsActor), [rangeDecorationsActor]);
1002
+ useEffect(() => {
1003
+ rangeDecorationsActor.send({
1004
+ type: "update read only",
1005
+ readOnly
1006
+ });
1007
+ }, [rangeDecorationsActor, readOnly]), useEffect(() => {
1008
+ rangeDecorationsActor.send({
1009
+ type: "range decorations updated",
1010
+ rangeDecorations: rangeDecorations ?? []
1011
+ });
1012
+ }, [rangeDecorationsActor, rangeDecorations]);
1013
+ const blockTypeName = schemaTypes.block.name;
780
1014
  useMemo(() => {
781
1015
  if (readOnly)
782
1016
  return debug("Editable is in read only mode"), slateEditor;
@@ -810,46 +1044,12 @@ const debug = debugWithName("component:Editable"), PLACEHOLDER_STYLE = {
810
1044
  }), slateEditor.onChange());
811
1045
  }
812
1046
  }
813
- }, [blockTypeName, editorActor, propsSelection, slateEditor]), syncRangeDecorations = useCallback((operation) => {
814
- if (rangeDecorations && rangeDecorations.length > 0) {
815
- const newSlateRanges = [];
816
- if (rangeDecorations.forEach((rangeDecorationItem) => {
817
- const slateRange_0 = toSlateRange(rangeDecorationItem.selection, slateEditor);
818
- if (!Range.isRange(slateRange_0)) {
819
- rangeDecorationItem.onMoved && rangeDecorationItem.onMoved({
820
- newSelection: null,
821
- rangeDecoration: rangeDecorationItem,
822
- origin: "local"
823
- });
824
- return;
825
- }
826
- let newRange;
827
- if (operation && (newRange = moveRangeByOperation(slateRange_0, operation), newRange && newRange !== slateRange_0 || newRange === null && slateRange_0)) {
828
- const newRangeSelection = newRange ? slateRangeToSelection({
829
- schema: schemaTypes,
830
- editor: slateEditor,
831
- range: newRange
832
- }) : null;
833
- rangeDecorationItem.onMoved && rangeDecorationItem.onMoved({
834
- newSelection: newRangeSelection,
835
- rangeDecoration: rangeDecorationItem,
836
- origin: "local"
837
- });
838
- }
839
- newRange !== null && newSlateRanges.push({
840
- ...newRange || slateRange_0,
841
- rangeDecoration: rangeDecorationItem
842
- });
843
- }), newSlateRanges.length > 0) {
844
- setRangeDecorationsState(newSlateRanges);
845
- return;
846
- }
847
- }
848
- setRangeDecorationsState((rangeDecorationState_0) => rangeDecorationState_0.length > 0 ? [] : rangeDecorationState_0);
849
- }, [rangeDecorations, schemaTypes, slateEditor]);
1047
+ }, [blockTypeName, editorActor, propsSelection, slateEditor]);
850
1048
  useEffect(() => {
851
1049
  const onReady = editorActor.on("ready", () => {
852
- syncRangeDecorations(), restoreSelectionFromProps();
1050
+ rangeDecorationsActor.send({
1051
+ type: "ready"
1052
+ }), restoreSelectionFromProps();
853
1053
  }), onInvalidValue = editorActor.on("invalid value", () => {
854
1054
  setHasInvalidValue(!0);
855
1055
  }), onValueChanged = editorActor.on("value changed", () => {
@@ -858,22 +1058,9 @@ const debug = debugWithName("component:Editable"), PLACEHOLDER_STYLE = {
858
1058
  return () => {
859
1059
  onReady.unsubscribe(), onInvalidValue.unsubscribe(), onValueChanged.unsubscribe();
860
1060
  };
861
- }, [editorActor, restoreSelectionFromProps, syncRangeDecorations]), useEffect(() => {
1061
+ }, [rangeDecorationsActor, editorActor, restoreSelectionFromProps]), useEffect(() => {
862
1062
  propsSelection && !hasInvalidValue && restoreSelectionFromProps();
863
1063
  }, [hasInvalidValue, propsSelection, restoreSelectionFromProps]);
864
- const [syncedRangeDecorations, setSyncedRangeDecorations] = useState(!1);
865
- useEffect(() => {
866
- syncedRangeDecorations || (setSyncedRangeDecorations(!0), syncRangeDecorations());
867
- }, [syncRangeDecorations, syncedRangeDecorations]), useEffect(() => {
868
- isEqual(rangeDecorations, rangeDecorationsRef.current) || syncRangeDecorations(), rangeDecorationsRef.current = rangeDecorations;
869
- }, [rangeDecorations, syncRangeDecorations]), useEffect(() => {
870
- const teardown = withSyncRangeDecorations({
871
- editorActor,
872
- slateEditor,
873
- syncRangeDecorations
874
- });
875
- return () => teardown();
876
- }, [editorActor, slateEditor, syncRangeDecorations]);
877
1064
  const handleCopy = useCallback((event) => {
878
1065
  if (onCopy)
879
1066
  onCopy(event) !== void 0 && event.preventDefault();
@@ -1119,33 +1306,7 @@ const debug = debugWithName("component:Editable"), PLACEHOLDER_STYLE = {
1119
1306
  return scrollSelectionIntoView === null ? noop : (_editor, domRange) => {
1120
1307
  scrollSelectionIntoView(portableTextEditor, domRange);
1121
1308
  };
1122
- }, [portableTextEditor, scrollSelectionIntoView]), decorate = useCallback(([, path_0]) => {
1123
- if (isEqualToEmptyEditor(slateEditor.children, schemaTypes))
1124
- return [{
1125
- anchor: {
1126
- path: [0, 0],
1127
- offset: 0
1128
- },
1129
- focus: {
1130
- path: [0, 0],
1131
- offset: 0
1132
- },
1133
- placeholder: !0
1134
- }];
1135
- if (path_0.length === 0)
1136
- return [];
1137
- const result_2 = rangeDecorationState.filter((item) => Range.isCollapsed(item) ? path_0.length !== 2 ? !1 : Path.equals(item.focus.path, path_0) && Path.equals(item.anchor.path, path_0) : Range.intersection(item, {
1138
- anchor: {
1139
- path: path_0,
1140
- offset: 0
1141
- },
1142
- focus: {
1143
- path: path_0,
1144
- offset: 0
1145
- }
1146
- }) || Range.includes(item, path_0));
1147
- return result_2.length > 0 ? result_2 : [];
1148
- }, [slateEditor, schemaTypes, rangeDecorationState]);
1309
+ }, [portableTextEditor, scrollSelectionIntoView]);
1149
1310
  useEffect(() => {
1150
1311
  ref.current = ReactEditor.toDOMNode(slateEditor, slateEditor), setEditableElement(ref.current);
1151
1312
  }, [slateEditor, ref]), useEffect(() => {