@portabletext/editor 8.0.3 → 8.1.1

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.
Files changed (28) hide show
  1. package/lib/{behavior.types.action-C6D8CCF9.d.ts → behavior.types.action-C0XDEQnB.d.ts} +18 -1
  2. package/lib/behavior.types.action-C0XDEQnB.d.ts.map +1 -0
  3. package/lib/behaviors/index.d.ts +1 -1
  4. package/lib/{get-container-D1nM_ww9.js → get-container-D2pN5Npr.js} +3 -3
  5. package/lib/{get-container-D1nM_ww9.js.map → get-container-D2pN5Npr.js.map} +1 -1
  6. package/lib/{get-parent-Ci5241qs.js → get-parent-DgaKcy8-.js} +18 -12
  7. package/lib/get-parent-DgaKcy8-.js.map +1 -0
  8. package/lib/{get-path-sub-schema-CZd9VcVM.js → get-path-sub-schema-Dd0QYZ0m.js} +2 -2
  9. package/lib/{get-path-sub-schema-CZd9VcVM.js.map → get-path-sub-schema-Dd0QYZ0m.js.map} +1 -1
  10. package/lib/index.d.ts +1 -1
  11. package/lib/index.js +290 -205
  12. package/lib/index.js.map +1 -1
  13. package/lib/plugins/index.d.ts +1 -1
  14. package/lib/{selector.is-selecting-entire-blocks-DPnzucrd.js → selector.is-selecting-entire-blocks-BFhnnkHf.js} +4 -4
  15. package/lib/{selector.is-selecting-entire-blocks-DPnzucrd.js.map → selector.is-selecting-entire-blocks-BFhnnkHf.js.map} +1 -1
  16. package/lib/selectors/index.d.ts +1 -1
  17. package/lib/selectors/index.js +4 -4
  18. package/lib/traversal/index.d.ts +1 -1
  19. package/lib/traversal/index.js +3 -3
  20. package/lib/{util.is-equal-selections-N1hxVUbE.js → util.is-equal-selections-BdK98okY.js} +2 -2
  21. package/lib/{util.is-equal-selections-N1hxVUbE.js.map → util.is-equal-selections-BdK98okY.js.map} +1 -1
  22. package/lib/{util.slice-blocks-B2fZcNok.js → util.slice-blocks-_ElaB53f.js} +2 -2
  23. package/lib/{util.slice-blocks-B2fZcNok.js.map → util.slice-blocks-_ElaB53f.js.map} +1 -1
  24. package/lib/utils/index.d.ts +1 -1
  25. package/lib/utils/index.js +3 -3
  26. package/package.json +1 -1
  27. package/lib/behavior.types.action-C6D8CCF9.d.ts.map +0 -1
  28. package/lib/get-parent-Ci5241qs.js.map +0 -1
package/lib/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import { a as getNodeChildren, i as getChildren, l as serializePath, n as parentPath, r as getNode, t as getParent, u as isKeyedSegment } from "./get-parent-Ci5241qs.js";
2
- import { C as getAncestors, S as hasNode, _ as comparePoints, a as isInline, b as isEditableContainer, c as isSpanNode, d as resolveChildEntryIndex, f as isAncestorPath, g as isAfterPoint, h as isBackwardRange, i as getEnclosingBlock, l as getSibling, m as rangeEdges, n as getEnclosingContainer, o as getBlock, p as isTextBlockNode, s as isBlock, t as getPathSubSchema, u as getNodes, v as comparePaths, x as resolveContainerAt, y as isObject } from "./get-path-sub-schema-CZd9VcVM.js";
3
- import { _ as getAncestor, a as getTextBlock, c as pathContains, d as isRange, f as isPoint, g as isLeafObject, h as getLeaf, i as getFirstChild, l as getSpan, m as isPath, n as getUnionSchema, o as rangeIntersects, p as isObject$1, r as getLastChild, s as isBeforePoint, t as getContainer, u as getText } from "./get-container-D1nM_ww9.js";
4
- import { a as parseBlocks, b as childAtTextOffset, c as parseSpan, d as getSelectionEndPoint, f as isSelectionCollapsed, g as isEqualSelectionPoints, h as getBlockKeyFromSelectionPoint, i as parseBlock, l as defaultKeyGenerator, m as getBlockEndPoint, n as isListBlock, o as parseInlineObject, p as getBlockStartPoint, r as parseAnnotation, s as parseMarkDefs, u as getSelectionStartPoint, v as blockOffsetToSpanSelectionPoint, x as textOffsetOfChild } from "./util.slice-blocks-B2fZcNok.js";
5
- import { A as getSelectionEndPoint$1, B as getFocusBlock, C as getSelectedTextBlocks, D as getPreviousSpan, F as getSelectionEndBlock, G as rangesOverlap, H as getFocusInlineObject, I as getFragment, L as getSelectedValue, M as isSelectionCollapsed$1, N as isOverlappingSelection, O as getSelectionStartPoint$1, P as getSelectionStartBlock, R as getRootAcceptedTypes, S as getActiveDecorators, T as getMarkState, U as getFocusChild, V as getFocusSpan, a as isActiveListItem, b as getActiveAnnotations, c as getSelectionEndChild, d as getLastBlock, f as getFocusListBlock, i as isActiveStyle, j as isSelectionExpanded, k as getNextSpan, l as getPreviousBlock, m as getCaretWordSelection, n as isAtTheStartOfBlock, o as isActiveDecorator, p as getFocusBlockObject, r as isAtTheEndOfBlock, s as getSelectionStartChild, t as isSelectingEntireBlocks, u as getNextBlock, w as getActiveAnnotationsMarks, x as isActiveAnnotation, z as getFocusTextBlock } from "./selector.is-selecting-entire-blocks-DPnzucrd.js";
6
- import { n as isEmptyTextBlock, r as getTextBlockText, t as isEqualSelections } from "./util.is-equal-selections-N1hxVUbE.js";
1
+ import { a as getChildren, d as isKeyedSegment, i as resolveNode, n as parentPath, o as getNodeChildren, r as getNode, t as getParent, u as serializePath } from "./get-parent-DgaKcy8-.js";
2
+ import { C as getAncestors, S as hasNode, _ as comparePoints, a as isInline, b as isEditableContainer, c as isSpanNode, d as resolveChildEntryIndex, f as isAncestorPath, g as isAfterPoint, h as isBackwardRange, i as getEnclosingBlock, l as getSibling, m as rangeEdges, n as getEnclosingContainer, o as getBlock, p as isTextBlockNode, s as isBlock, t as getPathSubSchema, u as getNodes, v as comparePaths, x as resolveContainerAt, y as isObject } from "./get-path-sub-schema-Dd0QYZ0m.js";
3
+ import { _ as getAncestor, a as getTextBlock, c as pathContains, d as isRange, f as isPoint, g as isLeafObject, h as getLeaf, i as getFirstChild, l as getSpan, m as isPath, n as getUnionSchema, o as rangeIntersects, p as isObject$1, r as getLastChild, s as isBeforePoint, t as getContainer, u as getText } from "./get-container-D2pN5Npr.js";
4
+ import { a as parseBlocks, b as childAtTextOffset, c as parseSpan, d as getSelectionEndPoint, f as isSelectionCollapsed, g as isEqualSelectionPoints, h as getBlockKeyFromSelectionPoint, i as parseBlock, l as defaultKeyGenerator, m as getBlockEndPoint, n as isListBlock, o as parseInlineObject, p as getBlockStartPoint, r as parseAnnotation, s as parseMarkDefs, u as getSelectionStartPoint, v as blockOffsetToSpanSelectionPoint, x as textOffsetOfChild } from "./util.slice-blocks-_ElaB53f.js";
5
+ import { A as getSelectionEndPoint$1, B as getFocusBlock, C as getSelectedTextBlocks, D as getPreviousSpan, F as getSelectionEndBlock, G as rangesOverlap, H as getFocusInlineObject, I as getFragment, L as getSelectedValue, M as isSelectionCollapsed$1, N as isOverlappingSelection, O as getSelectionStartPoint$1, P as getSelectionStartBlock, R as getRootAcceptedTypes, S as getActiveDecorators, T as getMarkState, U as getFocusChild, V as getFocusSpan, a as isActiveListItem, b as getActiveAnnotations, c as getSelectionEndChild, d as getLastBlock, f as getFocusListBlock, i as isActiveStyle, j as isSelectionExpanded, k as getNextSpan, l as getPreviousBlock, m as getCaretWordSelection, n as isAtTheStartOfBlock, o as isActiveDecorator, p as getFocusBlockObject, r as isAtTheEndOfBlock, s as getSelectionStartChild, t as isSelectingEntireBlocks, u as getNextBlock, w as getActiveAnnotationsMarks, x as isActiveAnnotation, z as getFocusTextBlock } from "./selector.is-selecting-entire-blocks-BFhnnkHf.js";
6
+ import { n as isEmptyTextBlock, r as getTextBlockText, t as isEqualSelections } from "./util.is-equal-selections-BdK98okY.js";
7
7
  import { a as raise$1, i as forward, n as effect, t as defineBehavior } from "./behavior.types.behavior-BxSLb73I.js";
8
8
  import { n as EditorContext$1, t as useEditor } from "./use-editor-CwNeVwVC.js";
9
9
  import { c } from "react/compiler-runtime";
@@ -746,6 +746,40 @@ function move(editor, options = {}) {
746
746
  }
747
747
  editor.setSelection(props);
748
748
  }
749
+ function subscribeToOperations(editor, listener, options) {
750
+ let phase = options?.phase ?? "after";
751
+ return editor.operationListeners[phase] = [...editor.operationListeners[phase], listener], () => {
752
+ let listeners = editor.operationListeners[phase], index = listeners.indexOf(listener);
753
+ index !== -1 && (editor.operationListeners[phase] = [...listeners.slice(0, index), ...listeners.slice(index + 1)]);
754
+ };
755
+ }
756
+ /**
757
+ * Captures the operation together with the engine state that `after`
758
+ * listeners can no longer read once the operation has been applied. All
759
+ * engine flags are stable for the duration of a single apply call, so
760
+ * capturing them at entry is equivalent to reading them at any point during
761
+ * the apply.
762
+ */
763
+ function createOperationEvent(editor, operation) {
764
+ let isNormalizingNode = !!editor.isNormalizingNode, isProcessingRemoteChanges = !!editor.isProcessingRemoteChanges, isUndoing = !!editor.isUndoing, isRedoing = !!editor.isRedoing;
765
+ return {
766
+ operation,
767
+ beforeValue: editor.snapshot.context.value,
768
+ beforeSelection: editor.snapshot.context.selection,
769
+ operationsInProgress: editor.operations.length > 0,
770
+ isNormalizingNode,
771
+ isPatching: !!editor.isPatching,
772
+ isProcessingRemoteChanges,
773
+ isUndoing,
774
+ isRedoing,
775
+ withHistory: !!editor.withHistory,
776
+ undoStepId: editor.undoStepId,
777
+ origin: isProcessingRemoteChanges ? "remote" : isUndoing ? "undo" : isRedoing ? "redo" : isNormalizingNode ? "normalization" : "local"
778
+ };
779
+ }
780
+ function emitOperationEvent(listeners, event) {
781
+ for (let index = 0; index < listeners.length; index++) listeners[index]?.(event);
782
+ }
749
783
  const bold$1 = createKeyboardShortcut({
750
784
  default: [{
751
785
  key: "B",
@@ -2789,9 +2823,12 @@ const RestoreDOM = IS_ANDROID ? RestoreDOMComponent : ({ children }) => /* @__PU
2789
2823
  editor.forceRender = forceRender, editor.readOnly = readOnly;
2790
2824
  let state = useMemo(() => ({
2791
2825
  isUpdatingSelection: !1,
2792
- latestElement: null
2826
+ latestElement: null,
2827
+ localContentChangePending: !1
2793
2828
  }), []);
2794
- useEffect(() => {
2829
+ useEffect(() => subscribeToOperations(editor, (event) => {
2830
+ event.operation.type !== "set.selection" && (event.origin === "local" || event.origin === "undo" || event.origin === "redo") && (state.localContentChangePending = !0);
2831
+ }), [editor, state]), useEffect(() => {
2795
2832
  ref.current && autoFocus && ref.current.focus();
2796
2833
  }, [autoFocus]);
2797
2834
  /**
@@ -2846,6 +2883,8 @@ const RestoreDOM = IS_ANDROID ? RestoreDOMComponent : ({ children }) => /* @__PU
2846
2883
  onDOMSelectionChange,
2847
2884
  scheduleOnDOMSelectionChange
2848
2885
  }), useIsomorphicLayoutEffect(() => {
2886
+ let hadPendingLocalContentChange = state.localContentChangePending;
2887
+ state.localContentChangePending = !1;
2849
2888
  let window = null;
2850
2889
  ref.current && (window = getDefaultView(ref.current)) && (editor.domWindow = window, editor.domElement = ref.current);
2851
2890
  let { selection } = editor.snapshot.context, root = DOMEditor.findDocumentOrShadowRoot(editor), domSelection = getSelection(root);
@@ -2864,7 +2903,13 @@ const RestoreDOM = IS_ANDROID ? RestoreDOMComponent : ({ children }) => /* @__PU
2864
2903
  exactMatch: !0,
2865
2904
  suppressThrow: !0
2866
2905
  });
2867
- if (editorSelection && rangeEquals(editorSelection, selection)) return;
2906
+ if (editorSelection && rangeEquals(editorSelection, selection)) {
2907
+ if (hadPendingLocalContentChange) try {
2908
+ let domRange = DOMEditor.toDOMRange(editor, selection);
2909
+ scrollSelectionIntoView(editor, domRange);
2910
+ } catch {}
2911
+ return;
2912
+ }
2868
2913
  }
2869
2914
  if (selection && !DOMEditor.hasRange(editor, selection)) {
2870
2915
  let fallbackRange = DOMEditor.toEditorSelection(editor, domSelection, {
@@ -4116,40 +4161,6 @@ function performHotkey({ editorActor, editor, portableTextEditor, hotkeys, event
4116
4161
  }
4117
4162
  });
4118
4163
  }
4119
- function subscribeToOperations(editor, listener, options) {
4120
- let phase = options?.phase ?? "after";
4121
- return editor.operationListeners[phase] = [...editor.operationListeners[phase], listener], () => {
4122
- let listeners = editor.operationListeners[phase], index = listeners.indexOf(listener);
4123
- index !== -1 && (editor.operationListeners[phase] = [...listeners.slice(0, index), ...listeners.slice(index + 1)]);
4124
- };
4125
- }
4126
- /**
4127
- * Captures the operation together with the engine state that `after`
4128
- * listeners can no longer read once the operation has been applied. All
4129
- * engine flags are stable for the duration of a single apply call, so
4130
- * capturing them at entry is equivalent to reading them at any point during
4131
- * the apply.
4132
- */
4133
- function createOperationEvent(editor, operation) {
4134
- let isNormalizingNode = !!editor.isNormalizingNode, isProcessingRemoteChanges = !!editor.isProcessingRemoteChanges, isUndoing = !!editor.isUndoing, isRedoing = !!editor.isRedoing;
4135
- return {
4136
- operation,
4137
- beforeValue: editor.snapshot.context.value,
4138
- beforeSelection: editor.snapshot.context.selection,
4139
- operationsInProgress: editor.operations.length > 0,
4140
- isNormalizingNode,
4141
- isPatching: !!editor.isPatching,
4142
- isProcessingRemoteChanges,
4143
- isUndoing,
4144
- isRedoing,
4145
- withHistory: !!editor.withHistory,
4146
- undoStepId: editor.undoStepId,
4147
- origin: isProcessingRemoteChanges ? "remote" : isUndoing ? "undo" : isRedoing ? "redo" : isNormalizingNode ? "normalization" : "local"
4148
- };
4149
- }
4150
- function emitOperationEvent(listeners, event) {
4151
- for (let index = 0; index < listeners.length; index++) listeners[index]?.(event);
4152
- }
4153
4164
  function isForwardRange(range, root) {
4154
4165
  return !isBackwardRange(range, root);
4155
4166
  }
@@ -4192,7 +4203,12 @@ const engineOperationCallback = ({ input, sendBack }) => subscribeToOperations(i
4192
4203
  operation: event.operation,
4193
4204
  origin: event.origin
4194
4205
  });
4195
- }, { phase: "before" }), rangeDecorationsMachine = setup({
4206
+ }, { phase: "before" });
4207
+ function mergeRangeDecoration(leaf, decoration) {
4208
+ let { rangeDecoration } = decoration;
4209
+ leaf.rangeDecorations = [...leaf.rangeDecorations ?? [], rangeDecoration];
4210
+ }
4211
+ const rangeDecorationsMachine = setup({
4196
4212
  types: {
4197
4213
  context: {},
4198
4214
  input: {},
@@ -4213,6 +4229,7 @@ const engineOperationCallback = ({ input, sendBack }) => subscribeToOperations(i
4213
4229
  }
4214
4230
  rangeDecorationState.push({
4215
4231
  rangeDecoration,
4232
+ merge: mergeRangeDecoration,
4216
4233
  ...rangeDecoration.selection
4217
4234
  });
4218
4235
  }
@@ -4232,6 +4249,7 @@ const engineOperationCallback = ({ input, sendBack }) => subscribeToOperations(i
4232
4249
  }
4233
4250
  rangeDecorationState.push({
4234
4251
  rangeDecoration,
4252
+ merge: mergeRangeDecoration,
4235
4253
  ...rangeDecoration.selection
4236
4254
  });
4237
4255
  }
@@ -4260,7 +4278,8 @@ const engineOperationCallback = ({ input, sendBack }) => subscribeToOperations(i
4260
4278
  rangeDecoration: {
4261
4279
  ...decoratedRange.rangeDecoration,
4262
4280
  selection: newRange || currentSelection
4263
- }
4281
+ },
4282
+ merge: mergeRangeDecoration
4264
4283
  });
4265
4284
  }
4266
4285
  context.editorEngine.decoratedRanges = rangeDecorationState;
@@ -5168,29 +5187,33 @@ const PLACEHOLDER_STYLE = {
5168
5187
  right: 0
5169
5188
  };
5170
5189
  function RenderLeaf(props) {
5171
- let $ = c(12), schema = props.schema;
5190
+ let $ = c(16), schema = props.schema;
5172
5191
  if (props.leaf._type !== schema.span.name) return props.children;
5173
5192
  let t0;
5174
5193
  $[0] === props ? t0 = $[1] : (t0 = /* @__PURE__ */ jsx(RenderSpan, { ...props }), $[0] = props, $[1] = t0);
5175
- let renderedSpan = t0;
5176
- if (props.renderPlaceholder && props.leaf.placeholder && props.text.text === "") {
5177
- let t1;
5178
- $[2] === props.renderPlaceholder ? t1 = $[3] : (t1 = props.renderPlaceholder(), $[2] = props.renderPlaceholder, $[3] = t1);
5179
- let t2;
5180
- $[4] === t1 ? t2 = $[5] : (t2 = /* @__PURE__ */ jsx("span", {
5181
- style: PLACEHOLDER_STYLE,
5182
- contentEditable: !1,
5183
- children: t1
5184
- }), $[4] = t1, $[5] = t2);
5185
- let t3;
5186
- return $[6] !== renderedSpan || $[7] !== t2 ? (t3 = /* @__PURE__ */ jsxs(Fragment, { children: [t2, renderedSpan] }), $[6] = renderedSpan, $[7] = t2, $[8] = t3) : t3 = $[8], t3;
5187
- }
5188
- let rangeDecoration = props.leaf.rangeDecoration;
5189
- if (rangeDecoration) {
5190
- let t1;
5191
- $[9] !== rangeDecoration || $[10] !== renderedSpan ? (t1 = rangeDecoration.component({ children: renderedSpan }), $[9] = rangeDecoration, $[10] = renderedSpan, $[11] = t1) : t1 = $[11], renderedSpan = t1;
5192
- }
5193
- return renderedSpan;
5194
+ let renderedSpan, t1;
5195
+ if ($[2] !== props.leaf.placeholder || $[3] !== props.leaf.rangeDecorations || $[4] !== props.renderPlaceholder || $[5] !== props.text || $[6] !== t0) {
5196
+ t1 = Symbol.for("react.early_return_sentinel");
5197
+ bb0: {
5198
+ if (renderedSpan = t0, props.renderPlaceholder && props.leaf.placeholder && props.text.text === "") {
5199
+ let t2;
5200
+ $[9] === props.renderPlaceholder ? t2 = $[10] : (t2 = props.renderPlaceholder(), $[9] = props.renderPlaceholder, $[10] = t2);
5201
+ let t3;
5202
+ $[11] === t2 ? t3 = $[12] : (t3 = /* @__PURE__ */ jsx("span", {
5203
+ style: PLACEHOLDER_STYLE,
5204
+ contentEditable: !1,
5205
+ children: t2
5206
+ }), $[11] = t2, $[12] = t3);
5207
+ let t4;
5208
+ $[13] !== renderedSpan || $[14] !== t3 ? (t4 = /* @__PURE__ */ jsxs(Fragment, { children: [t3, renderedSpan] }), $[13] = renderedSpan, $[14] = t3, $[15] = t4) : t4 = $[15], t1 = t4;
5209
+ break bb0;
5210
+ }
5211
+ let rangeDecorations = props.leaf.rangeDecorations;
5212
+ if (rangeDecorations) for (let rangeDecoration of [...rangeDecorations].reverse()) renderedSpan = rangeDecoration.component({ children: renderedSpan });
5213
+ }
5214
+ $[2] = props.leaf.placeholder, $[3] = props.leaf.rangeDecorations, $[4] = props.renderPlaceholder, $[5] = props.text, $[6] = t0, $[7] = renderedSpan, $[8] = t1;
5215
+ } else renderedSpan = $[7], t1 = $[8];
5216
+ return t1 === Symbol.for("react.early_return_sentinel") ? renderedSpan : t1;
5194
5217
  }
5195
5218
  function RenderText(props) {
5196
5219
  let $ = c(3), t0;
@@ -7543,8 +7566,7 @@ function applyMergeNode(editor, path, position) {
7543
7566
  if (!prevEntry || !isTextBlock({ schema: editor.snapshot.context.schema }, prevEntry.node)) return;
7544
7567
  let prevChildren = prevEntry.node.children, lastInsertedKey = prevChildren.length > 0 ? prevChildren[prevChildren.length - 1]._key : void 0;
7545
7568
  for (let i = 0; i < node.children.length; i++) {
7546
- let child = node.children[i];
7547
- lastInsertedKey === void 0 ? editor.apply({
7569
+ let child = node.children[i], operation = lastInsertedKey === void 0 ? {
7548
7570
  type: "insert",
7549
7571
  path: [
7550
7572
  ...prevPath,
@@ -7553,7 +7575,7 @@ function applyMergeNode(editor, path, position) {
7553
7575
  ],
7554
7576
  node: child,
7555
7577
  position: "before"
7556
- }) : editor.apply({
7578
+ } : {
7557
7579
  type: "insert",
7558
7580
  path: [
7559
7581
  ...prevPath,
@@ -7562,7 +7584,8 @@ function applyMergeNode(editor, path, position) {
7562
7584
  ],
7563
7585
  node: child,
7564
7586
  position: "after"
7565
- }), lastInsertedKey = child._key;
7587
+ };
7588
+ editor.apply(operation), lastInsertedKey = operation.node._key;
7566
7589
  }
7567
7590
  editor.apply({
7568
7591
  type: "unset",
@@ -7639,33 +7662,6 @@ function transformPointForMerge(point, mergePath, prevKey, position) {
7639
7662
  };
7640
7663
  }
7641
7664
  /**
7642
- * Create a placeholder text block -- the empty initial block used when the
7643
- * editor value is empty or when a container needs a default child.
7644
- *
7645
- * When `path` is provided and points into a container, the placeholder
7646
- * uses the container's default style. Without `path`, or at root depth,
7647
- * falls back to the root schema's first style.
7648
- */
7649
- function createPlaceholderBlock(snapshot, path) {
7650
- let style = resolveDefaultStyle(snapshot, path);
7651
- return {
7652
- _type: snapshot.context.schema.block.name,
7653
- _key: snapshot.context.keyGenerator(),
7654
- style,
7655
- markDefs: [],
7656
- children: [{
7657
- _type: snapshot.context.schema.span.name,
7658
- _key: snapshot.context.keyGenerator(),
7659
- text: "",
7660
- marks: []
7661
- }]
7662
- };
7663
- }
7664
- function resolveDefaultStyle(snapshot, path) {
7665
- let rootFallback = snapshot.context.schema.styles[0]?.name ?? "normal";
7666
- return path ? getPathSubSchema(snapshot, path).styles[0]?.name ?? rootFallback : rootFallback;
7667
- }
7668
- /**
7669
7665
  * Apply property changes at a known path using primitive `set` and `unset`
7670
7666
  * operations.
7671
7667
  *
@@ -7674,10 +7670,11 @@ function resolveDefaultStyle(snapshot, path) {
7674
7670
  * downstream patch consumers see the key change before any property
7675
7671
  * mutations that reference the new key.
7676
7672
  */
7677
- function setNodeProperties(editor, props, path) {
7678
- let nodeEntry = getNode(editor.snapshot, path);
7679
- if (!nodeEntry) return;
7680
- let nodeRecord = nodeEntry.node, propsRecord = props, keys = Object.keys(propsRecord), keyIndex = keys.indexOf("_key");
7673
+ function applyNodeProperties(editor, props, path) {
7674
+ let result = resolveNode(editor.snapshot, path);
7675
+ if (result.status === "unreachable") throw Error(`Unable to apply properties at structurally unreachable path ${safeStringify(path)}`);
7676
+ if (result.status === "missing") return;
7677
+ let nodeRecord = result.entry.node, propsRecord = props, keys = Object.keys(propsRecord), keyIndex = keys.indexOf("_key");
7681
7678
  keyIndex !== -1 && (keys.splice(keyIndex, 1), keys.unshift("_key"));
7682
7679
  let currentPath = path;
7683
7680
  for (let key of keys) if (propsRecord[key] !== nodeRecord[key]) if (propsRecord[key] != null) {
@@ -7706,6 +7703,33 @@ function setNodeProperties(editor, props, path) {
7706
7703
  });
7707
7704
  }
7708
7705
  /**
7706
+ * Create a placeholder text block -- the empty initial block used when the
7707
+ * editor value is empty or when a container needs a default child.
7708
+ *
7709
+ * When `path` is provided and points into a container, the placeholder
7710
+ * uses the container's default style. Without `path`, or at root depth,
7711
+ * falls back to the root schema's first style.
7712
+ */
7713
+ function createPlaceholderBlock(snapshot, path) {
7714
+ let style = resolveDefaultStyle(snapshot, path);
7715
+ return {
7716
+ _type: snapshot.context.schema.block.name,
7717
+ _key: snapshot.context.keyGenerator(),
7718
+ style,
7719
+ markDefs: [],
7720
+ children: [{
7721
+ _type: snapshot.context.schema.span.name,
7722
+ _key: snapshot.context.keyGenerator(),
7723
+ text: "",
7724
+ marks: []
7725
+ }]
7726
+ };
7727
+ }
7728
+ function resolveDefaultStyle(snapshot, path) {
7729
+ let rootFallback = snapshot.context.schema.styles[0]?.name ?? "normal";
7730
+ return path ? getPathSubSchema(snapshot, path).styles[0]?.name ?? rootFallback : rootFallback;
7731
+ }
7732
+ /**
7709
7733
  * Read the `_type` from a pre-resolved `of` entry.
7710
7734
  */
7711
7735
  function entryType(entry) {
@@ -7936,7 +7960,7 @@ const normalizeNode = (editor, entry) => {
7936
7960
  * Add missing .markDefs to text block nodes
7937
7961
  */
7938
7962
  if (!editor.isProcessingRemoteChanges && isTextBlockNode({ schema: editor.snapshot.context.schema }, node) && !Array.isArray(node.markDefs)) {
7939
- debug.normalization("adding .markDefs to block node"), setNodeProperties(editor, { markDefs: [] }, path);
7963
+ debug.normalization("adding .markDefs to block node"), applyNodeProperties(editor, { markDefs: [] }, path);
7940
7964
  return;
7941
7965
  }
7942
7966
  /**
@@ -7945,7 +7969,7 @@ const normalizeNode = (editor, entry) => {
7945
7969
  if (!editor.isProcessingRemoteChanges && isTextBlockNode({ schema: editor.snapshot.context.schema }, node) && node.style === void 0) {
7946
7970
  let defaultStyle = getPathSubSchema(editor.snapshot, path).styles.at(0)?.name;
7947
7971
  if (defaultStyle) {
7948
- debug.normalization("adding .style to block node"), setNodeProperties(editor, { style: defaultStyle }, path);
7972
+ debug.normalization("adding .style to block node"), applyNodeProperties(editor, { style: defaultStyle }, path);
7949
7973
  return;
7950
7974
  }
7951
7975
  }
@@ -7968,7 +7992,7 @@ const normalizeNode = (editor, entry) => {
7968
7992
  * Add missing .marks to span nodes
7969
7993
  */
7970
7994
  if (!editor.isProcessingRemoteChanges && isSpan({ schema: editor.snapshot.context.schema }, node) && !Array.isArray(node.marks)) {
7971
- debug.normalization("Adding .marks to span node"), setNodeProperties(editor, { marks: [] }, path);
7995
+ debug.normalization("Adding .marks to span node"), applyNodeProperties(editor, { marks: [] }, path);
7972
7996
  return;
7973
7997
  }
7974
7998
  /**
@@ -7979,7 +8003,7 @@ const normalizeNode = (editor, entry) => {
7979
8003
  if (!blockEntry) return;
7980
8004
  let annotations = node.marks?.filter((mark) => blockEntry.node.markDefs?.some((markDef) => markDef._key === mark));
7981
8005
  if (node.text === "" && annotations && annotations.length > 0) {
7982
- debug.normalization("removing annotations from empty span node"), setNodeProperties(editor, { marks: node.marks?.filter((mark) => !annotations.includes(mark)) }, path);
8006
+ debug.normalization("removing annotations from empty span node"), applyNodeProperties(editor, { marks: node.marks?.filter((mark) => !annotations.includes(mark)) }, path);
7983
8007
  return;
7984
8008
  }
7985
8009
  }
@@ -7990,7 +8014,7 @@ const normalizeNode = (editor, entry) => {
7990
8014
  let markDefs = node.markDefs ?? [], markDefKeys = /* @__PURE__ */ new Set(), newMarkDefs = [];
7991
8015
  for (let markDef of markDefs) markDefKeys.has(markDef._key) || (markDefKeys.add(markDef._key), newMarkDefs.push(markDef));
7992
8016
  if (markDefs.length !== newMarkDefs.length) {
7993
- debug.normalization("removing duplicate markDefs"), setNodeProperties(editor, { markDefs: newMarkDefs }, path);
8017
+ debug.normalization("removing duplicate markDefs"), applyNodeProperties(editor, { markDefs: newMarkDefs }, path);
7994
8018
  return;
7995
8019
  }
7996
8020
  }
@@ -8000,7 +8024,7 @@ const normalizeNode = (editor, entry) => {
8000
8024
  if (!editor.isProcessingRemoteChanges && isTextBlock({ schema: editor.snapshot.context.schema }, node)) {
8001
8025
  let newMarkDefs = (node.markDefs || []).filter((def) => node.children.find((child) => isSpan({ schema: editor.snapshot.context.schema }, child) && Array.isArray(child.marks) && child.marks.includes(def._key)));
8002
8026
  if (node.markDefs && !isEqualMarkDefs(newMarkDefs, node.markDefs)) {
8003
- debug.normalization("removing markDef not in use"), setNodeProperties(editor, { markDefs: newMarkDefs }, path);
8027
+ debug.normalization("removing markDef not in use"), applyNodeProperties(editor, { markDefs: newMarkDefs }, path);
8004
8028
  return;
8005
8029
  }
8006
8030
  }
@@ -8040,11 +8064,11 @@ const normalizeNode = (editor, entry) => {
8040
8064
  _type: firstChildType.type === "object" && "name" in firstChildType ? firstChildType.name : firstChildType.type,
8041
8065
  _key: editor.snapshot.context.keyGenerator()
8042
8066
  }), needsField && childNode) {
8043
- setNodeProperties(editor, { [arrayField.name]: [childNode] }, path);
8067
+ applyNodeProperties(editor, { [arrayField.name]: [childNode] }, path);
8044
8068
  return;
8045
8069
  }
8046
8070
  if (needsField) {
8047
- setNodeProperties(editor, { [arrayField.name]: [] }, path);
8071
+ applyNodeProperties(editor, { [arrayField.name]: [] }, path);
8048
8072
  return;
8049
8073
  }
8050
8074
  if (childNode) {
@@ -11166,7 +11190,7 @@ const addAnnotationOperationImplementation = ({ snapshot, operation }) => {
11166
11190
  for (let { node: block, path: blockPath } of selectedBlocks) {
11167
11191
  if (!isTextBlock({ schema: editor.snapshot.context.schema }, block) || block.children.length === 0 || block.children.length === 1 && block.children[0]?.text === "" || !getPathSubSchema(snapshot, blockPath).annotations.some((annotation) => annotation.name === parsedAnnotation._type)) continue;
11168
11192
  let annotationKey = blockIndex === 0 ? parsedAnnotation._key : context.keyGenerator(), markDefs = block.markDefs ?? [];
11169
- markDefs.find((markDef) => markDef._type === parsedAnnotation._type && markDef._key === annotationKey) === void 0 && setNodeProperties(editor, { markDefs: [...markDefs, {
11193
+ markDefs.find((markDef) => markDef._type === parsedAnnotation._type && markDef._key === annotationKey) === void 0 && applyNodeProperties(editor, { markDefs: [...markDefs, {
11170
11194
  ...parsedAnnotation,
11171
11195
  _key: annotationKey
11172
11196
  }] }, blockPath);
@@ -11184,7 +11208,7 @@ const addAnnotationOperationImplementation = ({ snapshot, operation }) => {
11184
11208
  for (let { node: span, path: spanPath } of children) {
11185
11209
  if (!isSpan({ schema: editor.snapshot.context.schema }, span) || !selectionRange || !rangeIntersects(editor.snapshot, selectionRange, spanPath)) continue;
11186
11210
  let marks = span.marks ?? [];
11187
- setNodeProperties(editor, { marks: [...marks, annotationKey] }, spanPath);
11211
+ applyNodeProperties(editor, { marks: [...marks, annotationKey] }, spanPath);
11188
11212
  }
11189
11213
  blockIndex++;
11190
11214
  }
@@ -11222,7 +11246,7 @@ const removeAnnotationOperationImplementation = ({ snapshot, operation }) => {
11222
11246
  ...previousSpansWithSameAnnotation,
11223
11247
  [selectedChild, selectedChildPath],
11224
11248
  ...nextSpansWithSameAnnotation
11225
- ]) setNodeProperties(editor, { marks: child.marks?.filter((mark) => mark !== annotationToRemove) }, childPath);
11249
+ ]) applyNodeProperties(editor, { marks: child.marks?.filter((mark) => mark !== annotationToRemove) }, childPath);
11226
11250
  } else {
11227
11251
  let ref = at ? rangeRef(editor, at, { affinity: "inward" }) : null;
11228
11252
  withoutNormalizing(editor, () => {
@@ -11247,7 +11271,7 @@ const removeAnnotationOperationImplementation = ({ snapshot, operation }) => {
11247
11271
  for (let { node: child, path: childPath } of children) {
11248
11272
  if (!isSpan({ schema: editor.snapshot.context.schema }, child) || !selectionRange || !rangeIntersects(editor.snapshot, selectionRange, childPath)) continue;
11249
11273
  let markDefs = block.markDefs ?? [], marks = child.marks ?? [], marksWithoutAnnotation = marks.filter((mark) => markDefs.find((markDef) => markDef._key === mark)?._type !== operation.annotation.name);
11250
- marksWithoutAnnotation.length !== marks.length && setNodeProperties(editor, { marks: marksWithoutAnnotation }, childPath);
11274
+ marksWithoutAnnotation.length !== marks.length && applyNodeProperties(editor, { marks: marksWithoutAnnotation }, childPath);
11251
11275
  }
11252
11276
  }
11253
11277
  }), ref?.unref();
@@ -11310,7 +11334,7 @@ const blockSetOperationImplementation = ({ snapshot, operation }) => {
11310
11334
  }
11311
11335
  context.schema.block.fields?.some((field) => field.name === key) && (filteredProps[key] = operation.props[key]);
11312
11336
  }
11313
- setNodeProperties(operation.editor, filteredProps, blockEntry.path);
11337
+ applyNodeProperties(operation.editor, filteredProps, blockEntry.path);
11314
11338
  } else {
11315
11339
  let schemaDefinition = getBlockObjectSchema(snapshot, engineBlock, blockEntry.path), filteredProps = {};
11316
11340
  for (let key of Object.keys(operation.props)) if (key !== "_type") {
@@ -11321,7 +11345,7 @@ const blockSetOperationImplementation = ({ snapshot, operation }) => {
11321
11345
  schemaDefinition?.fields?.some((field) => field.name === key) && (filteredProps[key] = operation.props[key]);
11322
11346
  }
11323
11347
  let patches = Object.entries(filteredProps).map(([key, value]) => set(value, [key])), updatedEngineBlock = applyAll(engineBlock, patches);
11324
- setNodeProperties(operation.editor, updatedEngineBlock, blockEntry.path);
11348
+ applyNodeProperties(operation.editor, updatedEngineBlock, blockEntry.path);
11325
11349
  }
11326
11350
  }, blockUnsetOperationImplementation = ({ snapshot, operation }) => {
11327
11351
  let { context } = snapshot, blockEntry = getNode(operation.editor.snapshot, operation.at);
@@ -11330,12 +11354,12 @@ const blockSetOperationImplementation = ({ snapshot, operation }) => {
11330
11354
  if (isTextBlockNode(context, engineBlock)) {
11331
11355
  let propsToRemove = operation.props.filter((prop) => prop !== "_type" && prop !== "_key"), unsetProps = {};
11332
11356
  for (let prop of propsToRemove) unsetProps[prop] = null;
11333
- setNodeProperties(operation.editor, unsetProps, blockEntry.path), operation.props.includes("_key") && setNodeProperties(operation.editor, { _key: context.keyGenerator() }, blockEntry.path);
11357
+ applyNodeProperties(operation.editor, unsetProps, blockEntry.path), operation.props.includes("_key") && applyNodeProperties(operation.editor, { _key: context.keyGenerator() }, blockEntry.path);
11334
11358
  return;
11335
11359
  }
11336
11360
  let unsetProps = {};
11337
11361
  for (let key of operation.props) key !== "_type" && (key === "_key" ? unsetProps._key = context.keyGenerator() : unsetProps[key] = null);
11338
- setNodeProperties(operation.editor, unsetProps, blockEntry.path);
11362
+ applyNodeProperties(operation.editor, unsetProps, blockEntry.path);
11339
11363
  };
11340
11364
  /**
11341
11365
  * Compute the canonical path that a new node will have when inserted as a
@@ -11362,7 +11386,7 @@ const childSetOperationImplementation = ({ snapshot, operation }) => {
11362
11386
  let { node: child, path: childPath } = childEntry;
11363
11387
  if (isSpan({ schema: operation.editor.snapshot.context.schema }, child)) {
11364
11388
  let { _type, text, ...rest } = operation.props;
11365
- setNodeProperties(operation.editor, {
11389
+ applyNodeProperties(operation.editor, {
11366
11390
  ...child,
11367
11391
  ...rest
11368
11392
  }, childPath);
@@ -11385,7 +11409,7 @@ const childSetOperationImplementation = ({ snapshot, operation }) => {
11385
11409
  if (!definition) return;
11386
11410
  let { _type, _key, ...rest } = operation.props;
11387
11411
  for (let prop in rest) definition.fields.some((field) => field.name === prop) || delete rest[prop];
11388
- setNodeProperties(operation.editor, {
11412
+ applyNodeProperties(operation.editor, {
11389
11413
  ...child,
11390
11414
  _key: typeof _key == "string" ? _key : child._key,
11391
11415
  ...rest
@@ -11406,13 +11430,13 @@ const childSetOperationImplementation = ({ snapshot, operation }) => {
11406
11430
  }
11407
11431
  newNode[prop] = null;
11408
11432
  }
11409
- setNodeProperties(operation.editor, newNode, childPath);
11433
+ applyNodeProperties(operation.editor, newNode, childPath);
11410
11434
  return;
11411
11435
  }
11412
11436
  if (isObject(operation.editor.snapshot, child)) {
11413
11437
  let unsetProps = {};
11414
11438
  for (let prop of operation.props) prop !== "_type" && (prop === "_key" ? unsetProps._key = context.keyGenerator() : unsetProps[prop] = null);
11415
- setNodeProperties(operation.editor, unsetProps, childPath);
11439
+ applyNodeProperties(operation.editor, unsetProps, childPath);
11416
11440
  return;
11417
11441
  }
11418
11442
  throw Error(`Unable to determine the type of child at ${safeStringify(operation.at)}`);
@@ -11435,7 +11459,7 @@ const childSetOperationImplementation = ({ snapshot, operation }) => {
11435
11459
  let blockPath = parentPath(spanPath);
11436
11460
  if (!getPathSubSchema(snapshot, blockPath).decorators.some((decorator) => decorator.name === mark)) continue;
11437
11461
  let marks = [...(Array.isArray(node.marks) ? node.marks : []).filter((eMark) => eMark !== mark), mark];
11438
- setNodeProperties(editor, { marks }, spanPath);
11462
+ applyNodeProperties(editor, { marks }, spanPath);
11439
11463
  }
11440
11464
  });
11441
11465
  } else {
@@ -11454,7 +11478,7 @@ const childSetOperationImplementation = ({ snapshot, operation }) => {
11454
11478
  for (let { path: spanPath } of Array.from(getNodes(editor.snapshot, {
11455
11479
  at: blockPath,
11456
11480
  match: (node) => isSpan({ schema: editor.snapshot.context.schema }, node)
11457
- }))) setNodeProperties(editor, { marks: newMarks }, spanPath);
11481
+ }))) applyNodeProperties(editor, { marks: newMarks }, spanPath);
11458
11482
  } else editor.snapshot.decoratorState[mark] = !0;
11459
11483
  }
11460
11484
  if (editor.snapshot.context.selection) {
@@ -11483,7 +11507,7 @@ const childSetOperationImplementation = ({ snapshot, operation }) => {
11483
11507
  for (let { node, path: nodePath } of splitTextNodes) {
11484
11508
  if (!isSpan({ schema: editor.snapshot.context.schema }, node)) continue;
11485
11509
  let blockPath = parentPath(nodePath), block = getNode(editor.snapshot, blockPath)?.node;
11486
- isTextBlock({ schema: editor.snapshot.context.schema }, block) && block.children.includes(node) && setNodeProperties(editor, {
11510
+ isTextBlock({ schema: editor.snapshot.context.schema }, block) && block.children.includes(node) && applyNodeProperties(editor, {
11487
11511
  marks: (Array.isArray(node.marks) ? node.marks : []).filter((eMark) => eMark !== mark),
11488
11512
  _type: context.schema.span.name
11489
11513
  }, nodePath);
@@ -11499,7 +11523,7 @@ const childSetOperationImplementation = ({ snapshot, operation }) => {
11499
11523
  for (let { path: spanPath } of Array.from(getNodes(editor.snapshot, {
11500
11524
  at: blockPath,
11501
11525
  match: (node) => isSpan({ schema: editor.snapshot.context.schema }, node)
11502
- }))) setNodeProperties(editor, { marks: existingMarksWithoutDecorator }, spanPath);
11526
+ }))) applyNodeProperties(editor, { marks: existingMarksWithoutDecorator }, spanPath);
11503
11527
  } else editor.snapshot.decoratorState[mark] = !1;
11504
11528
  }
11505
11529
  if (editor.snapshot.context.selection) {
@@ -11610,6 +11634,74 @@ function fieldAcceptsTextBlock(editor, node, _path) {
11610
11634
  return container ? container.field.of.some((member) => member.type === editor.snapshot.context.schema.block.name) : !1;
11611
11635
  }
11612
11636
  /**
11637
+ * A block merge folds `mergingBlock`'s children (and markDefs) into
11638
+ * `destinationBlock` by key. Any child key the merging block shares with
11639
+ * the destination has to be renamed before the merge, or the engine's own
11640
+ * collision handling mints a fresh key for it, which reads on the wire as
11641
+ * that node being destroyed and a new one created instead of moved.
11642
+ *
11643
+ * A colliding markDef that is deeply equal to the destination's is left
11644
+ * unrenamed and reported in `dedupedMarkDefKeys` instead: the merge keeps
11645
+ * a single copy under the original `_key` rather than duplicating
11646
+ * identical content under two keys. A colliding markDef that differs gets
11647
+ * renamed like a child would.
11648
+ *
11649
+ * `renamedBlock` keeps every deduped def in its own `markDefs`, even
11650
+ * though the destination already carries it: `renamedBlock` feeds
11651
+ * `insert.block` on some callers' paths, and that path parses the block
11652
+ * standalone, stripping any mark that doesn't resolve in the block's own
11653
+ * `markDefs`. A caller that appends `renamedBlock.markDefs` onto the
11654
+ * destination's must skip the keys listed in `dedupedMarkDefKeys`, or the
11655
+ * def duplicates.
11656
+ *
11657
+ * Pure: computes the renames and the block they produce without touching
11658
+ * an editor. Callers raise or apply the renames themselves, ahead of the
11659
+ * merge, against the still-living `mergingBlock`.
11660
+ */
11661
+ function planMergeKeyRenames(args) {
11662
+ let { context, mergingBlock, destinationBlock } = args, destinationChildKeys = new Set(destinationBlock.children.map((child) => child._key)), destinationMarkDefsByKey = new Map((destinationBlock.markDefs ?? []).map((markDef) => [markDef._key, markDef])), markDefKeyMap = /* @__PURE__ */ new Map(), dedupedMarkDefKeys = [], renamedMarkDefs = mergingBlock.markDefs?.flatMap((markDef) => {
11663
+ let destinationMarkDef = destinationMarkDefsByKey.get(markDef._key);
11664
+ if (!destinationMarkDef) return [markDef];
11665
+ if (isDeepEqual(markDef, destinationMarkDef)) return dedupedMarkDefKeys.push(markDef._key), [markDef];
11666
+ let newKey = context.keyGenerator();
11667
+ return markDefKeyMap.set(markDef._key, newKey), [{
11668
+ ...markDef,
11669
+ _key: newKey
11670
+ }];
11671
+ }), markDefRenames = [];
11672
+ for (let markDef of mergingBlock.markDefs ?? []) {
11673
+ let newKey = markDefKeyMap.get(markDef._key);
11674
+ newKey && markDefRenames.push({
11675
+ markDefKey: markDef._key,
11676
+ newKey
11677
+ });
11678
+ }
11679
+ let childRenames = [], renamedChildren = mergingBlock.children.map((child) => {
11680
+ let currentMarks = isSpan(context, child) ? child.marks : void 0, remappedMarks = currentMarks?.map((mark) => markDefKeyMap.get(mark) ?? mark), marksChanged = !!(remappedMarks && !isEqualMarks(remappedMarks, currentMarks)), newKey = destinationChildKeys.has(child._key) ? context.keyGenerator() : child._key, props = {};
11681
+ return newKey !== child._key && (props._key = newKey), marksChanged && (props.marks = remappedMarks), Object.keys(props).length > 0 && childRenames.push({
11682
+ childKey: child._key,
11683
+ props
11684
+ }), marksChanged ? {
11685
+ ...child,
11686
+ _key: newKey,
11687
+ marks: remappedMarks
11688
+ } : {
11689
+ ...child,
11690
+ _key: newKey
11691
+ };
11692
+ });
11693
+ return {
11694
+ renamedBlock: {
11695
+ ...mergingBlock,
11696
+ children: renamedChildren,
11697
+ ...mergingBlock.markDefs ? { markDefs: renamedMarkDefs } : {}
11698
+ },
11699
+ childRenames,
11700
+ markDefRenames,
11701
+ dedupedMarkDefKeys
11702
+ };
11703
+ }
11704
+ /**
11613
11705
  * Indic scripts whose grapheme clusters span multiple code points. Backward
11614
11706
  * character delete on these scripts removes one code point at a time instead
11615
11707
  * of the entire cluster. Besides the script ranges, the class lists the BMP
@@ -11942,12 +12034,28 @@ function mergeBlock(editor, startBlockPath, endBlockPath, removeEmptyStartBlock)
11942
12034
  removeNodeAt(editor, startBlockPath);
11943
12035
  return;
11944
12036
  }
11945
- let endMarkDefs = endBlock.node.markDefs;
11946
- if (Array.isArray(endMarkDefs) && endMarkDefs.length > 0) {
11947
- let oldDefs = startBlock.node.markDefs ?? [];
11948
- setNodeProperties(editor, { markDefs: [...new Map([...oldDefs, ...endMarkDefs].map((def) => [def._key, def])).values()] }, startBlockPath);
11949
- }
11950
- applyMergeNode(editor, endBlockPath, startBlock.node.children.length);
12037
+ let { renamedBlock, childRenames, markDefRenames, dedupedMarkDefKeys } = planMergeKeyRenames({
12038
+ context: editor.snapshot.context,
12039
+ mergingBlock: endBlock.node,
12040
+ destinationBlock: startBlock.node
12041
+ });
12042
+ for (let { markDefKey, newKey } of markDefRenames) editor.apply({
12043
+ type: "set",
12044
+ path: [
12045
+ ...endBlockPath,
12046
+ "markDefs",
12047
+ { _key: markDefKey },
12048
+ "_key"
12049
+ ],
12050
+ value: newKey
12051
+ });
12052
+ for (let { childKey, props } of childRenames) applyNodeProperties(editor, props, [
12053
+ ...endBlockPath,
12054
+ "children",
12055
+ { _key: childKey }
12056
+ ]);
12057
+ let endMarkDefs = renamedBlock.markDefs?.filter((markDef) => !dedupedMarkDefKeys.includes(markDef._key));
12058
+ Array.isArray(endMarkDefs) && endMarkDefs.length > 0 && applyNodeProperties(editor, { markDefs: [...startBlock.node.markDefs ?? [], ...endMarkDefs] }, startBlockPath), applyMergeNode(editor, endBlockPath, startBlock.node.children.length);
11951
12059
  }
11952
12060
  function removeNodeAt(editor, path) {
11953
12061
  getNode(editor.snapshot, path) && editor.apply({
@@ -12636,7 +12744,7 @@ function mergeTextBlockFragment(args) {
12636
12744
  block,
12637
12745
  endBlock
12638
12746
  });
12639
- setNodeProperties(editor, { markDefs: [...endBlock.markDefs ?? [], ...adjustedMarkDefs ?? []] }, endBlockPath);
12747
+ applyNodeProperties(editor, { markDefs: [...endBlock.markDefs ?? [], ...adjustedMarkDefs ?? []] }, endBlockPath);
12640
12748
  let atPathRef = pathRef(editor, at.path), atBlockStart = pointEquals(at, start(editor, endBlockPath)), firstInsertedKey = insertFragmentChildren(editor, adjustedBlock, at), resolvedAtPath = atPathRef.unref();
12641
12749
  if (select === "none") {
12642
12750
  (wasCrossBlock || !atBlockStart) && applySelect(editor, at);
@@ -12744,8 +12852,12 @@ function resolveChildIndex(children, segment) {
12744
12852
  }
12745
12853
  /**
12746
12854
  * Reassign keys on spans, inline objects and markDefs that collide with the
12747
- * end block's existing keys. Returns the adjusted block and the adjusted
12748
- * markDefs (to be merged into the end block).
12855
+ * end block's existing keys. A colliding markDef that is deeply equal to
12856
+ * the end block's is left out of the returned `adjustedMarkDefs` instead
12857
+ * of renamed, since the end block already carries it; its key isn't
12858
+ * remapped, so the fragment's marks keep pointing at it unchanged. The
12859
+ * caller must merge the returned `adjustedMarkDefs` into the end block's
12860
+ * own `markDefs`, or the deduped defs are lost.
12749
12861
  */
12750
12862
  function adjustFragmentKeys(args) {
12751
12863
  let { context, block, endBlock } = args;
@@ -12757,15 +12869,15 @@ function adjustFragmentKeys(args) {
12757
12869
  adjustedBlock: block,
12758
12870
  adjustedMarkDefs: block.markDefs
12759
12871
  };
12760
- let endBlockChildKeys = endBlock.children.map((child) => child._key), endBlockMarkDefsKeys = endBlock.markDefs?.map((markDef) => markDef._key) ?? [], markDefKeyMap = /* @__PURE__ */ new Map(), adjustedMarkDefs = block.markDefs?.map((markDef) => {
12761
- if (endBlockMarkDefsKeys.includes(markDef._key)) {
12762
- let newKey = context.keyGenerator();
12763
- return markDefKeyMap.set(markDef._key, newKey), {
12764
- ...markDef,
12765
- _key: newKey
12766
- };
12767
- }
12768
- return markDef;
12872
+ let endBlockChildKeys = endBlock.children.map((child) => child._key), endBlockMarkDefsByKey = new Map((endBlock.markDefs ?? []).map((markDef) => [markDef._key, markDef])), markDefKeyMap = /* @__PURE__ */ new Map(), adjustedMarkDefs = block.markDefs?.flatMap((markDef) => {
12873
+ let endBlockMarkDef = endBlockMarkDefsByKey.get(markDef._key);
12874
+ if (!endBlockMarkDef) return [markDef];
12875
+ if (isDeepEqual(markDef, endBlockMarkDef)) return [];
12876
+ let newKey = context.keyGenerator();
12877
+ return markDefKeyMap.set(markDef._key, newKey), [{
12878
+ ...markDef,
12879
+ _key: newKey
12880
+ }];
12769
12881
  }), adjustedChildren = block.children.map((child) => {
12770
12882
  if (isSpan(context, child)) {
12771
12883
  let marks = child.marks?.map((mark) => markDefKeyMap.get(mark) ?? mark) ?? [];
@@ -13138,7 +13250,7 @@ const abstractAnnotationBehaviors = [
13138
13250
  }, previousBlockEndPoint = getBlockEndPoint({
13139
13251
  context: snapshot.context,
13140
13252
  block: previousBlock
13141
- }), { renamedBlock, renameActions } = planMergeKeyRenames({
13253
+ }), { renamedBlock, renameActions } = planMergeKeyRenameActions({
13142
13254
  context: snapshot.context,
13143
13255
  mergingBlockPath: focusTextBlock.path,
13144
13256
  mergingBlock: focusTextBlock.node,
@@ -13236,7 +13348,7 @@ const abstractAnnotationBehaviors = [
13236
13348
  if (!focusTextBlock) return !1;
13237
13349
  let nextSibling = getSibling(adjustedSnapshot, focusTextBlock.path, { direction: "next" });
13238
13350
  if (!nextSibling || !isAtTheEndOfBlock(focusTextBlock)(adjustedSnapshot) || !isTextBlock(snapshot.context, nextSibling.node)) return !1;
13239
- let { renamedBlock, renameActions } = planMergeKeyRenames({
13351
+ let { renamedBlock, renameActions } = planMergeKeyRenameActions({
13240
13352
  context: snapshot.context,
13241
13353
  mergingBlockPath: nextSibling.path,
13242
13354
  mergingBlock: nextSibling.node,
@@ -13332,65 +13444,37 @@ const abstractAnnotationBehaviors = [
13332
13444
  })
13333
13445
  ];
13334
13446
  /**
13335
- * A block merge folds `mergingBlock`'s children (and markDefs) into
13336
- * `destinationBlock` by key. Any key the merging block shares with the
13337
- * destination has to be renamed before the merge, or `insert.block`'s own
13338
- * collision handling mints a fresh key for it, which reads on the wire as
13339
- * that node being destroyed and a new one created instead of moved.
13340
- *
13341
- * Renames are raised as `set`/`child.set` events against the still-living
13342
- * `mergingBlock`, so they land on the wire before the merge's `unset`.
13343
- * `renamedBlock` mirrors the same renames applied to the value the caller
13344
- * already captured, since raising the rename events now doesn't change
13345
- * that captured value.
13447
+ * Raise the child/markDef renames `planMergeKeyRenames` computed as
13448
+ * `set`/`child.set` events against the still-living `mergingBlock`, so
13449
+ * they land on the wire before the merge's `unset`.
13346
13450
  */
13347
- function planMergeKeyRenames(args) {
13348
- let { context, mergingBlockPath, mergingBlock, destinationBlock } = args, destinationChildKeys = new Set(destinationBlock.children.map((child) => child._key)), destinationMarkDefKeys = new Set((destinationBlock.markDefs ?? []).map((markDef) => markDef._key)), markDefKeyMap = /* @__PURE__ */ new Map(), renamedMarkDefs = mergingBlock.markDefs?.map((markDef) => {
13349
- if (!destinationMarkDefKeys.has(markDef._key)) return markDef;
13350
- let newKey = context.keyGenerator();
13351
- return markDefKeyMap.set(markDef._key, newKey), {
13352
- ...markDef,
13353
- _key: newKey
13354
- };
13451
+ function planMergeKeyRenameActions(args) {
13452
+ let { context, mergingBlockPath, mergingBlock, destinationBlock } = args, { renamedBlock, childRenames, markDefRenames } = planMergeKeyRenames({
13453
+ context,
13454
+ mergingBlock,
13455
+ destinationBlock
13355
13456
  }), renameActions = [];
13356
- for (let markDef of mergingBlock.markDefs ?? []) {
13357
- let newKey = markDefKeyMap.get(markDef._key);
13358
- newKey && renameActions.push(raise$1({
13359
- type: "set",
13360
- at: [
13361
- ...mergingBlockPath,
13362
- "markDefs",
13363
- { _key: markDef._key },
13364
- "_key"
13365
- ],
13366
- value: newKey
13367
- }));
13368
- }
13369
- let renamedChildren = mergingBlock.children.map((child) => {
13370
- let currentMarks = isSpan(context, child) ? child.marks : void 0, remappedMarks = currentMarks?.map((mark) => markDefKeyMap.get(mark) ?? mark), marksChanged = !!(remappedMarks && !isEqualMarks(remappedMarks, currentMarks)), newKey = destinationChildKeys.has(child._key) ? context.keyGenerator() : child._key, props = {};
13371
- return newKey !== child._key && (props._key = newKey), marksChanged && (props.marks = remappedMarks), Object.keys(props).length > 0 && renameActions.push(raise$1({
13372
- type: "child.set",
13373
- at: [
13374
- ...mergingBlockPath,
13375
- "children",
13376
- { _key: child._key }
13377
- ],
13378
- props
13379
- })), marksChanged ? {
13380
- ...child,
13381
- _key: newKey,
13382
- marks: remappedMarks
13383
- } : {
13384
- ...child,
13385
- _key: newKey
13386
- };
13387
- });
13457
+ for (let { markDefKey, newKey } of markDefRenames) renameActions.push(raise$1({
13458
+ type: "set",
13459
+ at: [
13460
+ ...mergingBlockPath,
13461
+ "markDefs",
13462
+ { _key: markDefKey },
13463
+ "_key"
13464
+ ],
13465
+ value: newKey
13466
+ }));
13467
+ for (let { childKey, props } of childRenames) renameActions.push(raise$1({
13468
+ type: "child.set",
13469
+ at: [
13470
+ ...mergingBlockPath,
13471
+ "children",
13472
+ { _key: childKey }
13473
+ ],
13474
+ props
13475
+ }));
13388
13476
  return {
13389
- renamedBlock: {
13390
- ...mergingBlock,
13391
- children: renamedChildren,
13392
- ...mergingBlock.markDefs ? { markDefs: renamedMarkDefs } : {}
13393
- },
13477
+ renamedBlock,
13394
13478
  renameActions
13395
13479
  };
13396
13480
  }
@@ -16299,13 +16383,13 @@ function replaceBlock({ context, editorEngine, block, index }) {
16299
16383
  }
16300
16384
  function updateBlock({ context, editorEngine, oldEngineBlock, block, index: _index }) {
16301
16385
  let engineBlock = toEngineBlock(block, { schemaTypes: context.schema }), { children: _children, ...blockProps } = engineBlock;
16302
- setNodeProperties(editorEngine, blockProps, [{ _key: oldEngineBlock._key }]);
16386
+ applyNodeProperties(editorEngine, blockProps, [{ _key: oldEngineBlock._key }]);
16303
16387
  let oldRecord = oldEngineBlock, newRecord = engineBlock, removedProperties = {};
16304
16388
  for (let key of Object.keys(oldRecord)) key !== "children" && key !== "text" && (newRecord.hasOwnProperty(key) || (removedProperties[key] = null));
16305
- if (Object.keys(removedProperties).length > 0 && setNodeProperties(editorEngine, removedProperties, [{ _key: oldEngineBlock._key }]), isTextBlock({ schema: editorEngine.snapshot.context.schema }, engineBlock) && isTextBlock({ schema: editorEngine.snapshot.context.schema }, oldEngineBlock)) {
16389
+ if (Object.keys(removedProperties).length > 0 && applyNodeProperties(editorEngine, removedProperties, [{ _key: oldEngineBlock._key }]), isTextBlock({ schema: editorEngine.snapshot.context.schema }, engineBlock) && isTextBlock({ schema: editorEngine.snapshot.context.schema }, oldEngineBlock)) {
16306
16390
  let oldKeys = oldEngineBlock.children.map((c) => c._key), newKeys = engineBlock.children.map((c) => c._key), oldKeySet = new Set(oldKeys), hasSharedKeys = newKeys.some((key) => oldKeySet.has(key));
16307
16391
  if (oldKeys.length === newKeys.length && oldKeys.length > 0 && oldKeySet.size === oldKeys.length && oldKeys.some((key, i) => key !== newKeys[i]) && newKeys.every((key) => oldKeySet.has(key)) || newKeys.length > 0 && !hasSharedKeys) {
16308
- debug.syncValue("Replacing children via set"), setNodeProperties(editorEngine, { children: engineBlock.children }, [{ _key: oldEngineBlock._key }]), editorEngine.onChange();
16392
+ debug.syncValue("Replacing children via set"), applyNodeProperties(editorEngine, { children: engineBlock.children }, [{ _key: oldEngineBlock._key }]), editorEngine.onChange();
16309
16393
  return;
16310
16394
  }
16311
16395
  let oldBlockChildrenLength = oldEngineBlock.children.length;
@@ -16334,7 +16418,7 @@ function updateBlock({ context, editorEngine, oldEngineBlock, block, index: _ind
16334
16418
  if (currentBlockChild._key === oldBlockChild?._key && currentBlockChild._type === oldBlockChild?._type) {
16335
16419
  debug.syncValue("Updating changed child", currentBlockChild, oldBlockChild);
16336
16420
  let isSpanNode = isSpan({ schema: context.schema }, currentBlockChild) && isSpan({ schema: context.schema }, oldBlockChild), childProps = { ...currentBlockChild };
16337
- isSpanNode && delete childProps.text, setNodeProperties(editorEngine, childProps, path), isSpanNode && isTextChanged ? (oldBlockChild.text.length > 0 && deleteRange(editorEngine, {
16421
+ isSpanNode && delete childProps.text, applyNodeProperties(editorEngine, childProps, path), isSpanNode && isTextChanged ? (oldBlockChild.text.length > 0 && deleteRange(editorEngine, {
16338
16422
  focus: {
16339
16423
  path,
16340
16424
  offset: 0
@@ -16542,7 +16626,8 @@ function createActors(config) {
16542
16626
  return config.subscriptions.push(mutationBatcher.subscribe), config.subscriptions.push(() => subscribeToOperations(config.editorEngine, (event) => {
16543
16627
  isPublicOperation(event.operation) && config.relay.send({
16544
16628
  type: "operation",
16545
- operation: event.operation
16629
+ operation: event.operation,
16630
+ origin: event.origin === "remote" ? "remote" : "local"
16546
16631
  });
16547
16632
  })), config.subscriptions.push(() => {
16548
16633
  let subscription = syncActor.on("*", (event) => {