@portabletext/editor 7.8.2 → 7.10.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.
Files changed (42) hide show
  1. package/lib/_chunks-dts/behavior.types.action.d.ts +110 -274
  2. package/lib/_chunks-dts/behavior.types.action.d.ts.map +1 -1
  3. package/lib/_chunks-dts/block-offset.d.ts +10 -0
  4. package/lib/_chunks-dts/block-offset.d.ts.map +1 -0
  5. package/lib/_chunks-dts/editor-selector.d.ts +33 -0
  6. package/lib/_chunks-dts/editor-selector.d.ts.map +1 -0
  7. package/lib/_chunks-dts/editor.d.ts +99 -0
  8. package/lib/_chunks-dts/editor.d.ts.map +1 -0
  9. package/lib/_chunks-es/{get-first-child.js → get-container.js} +10 -1
  10. package/lib/_chunks-es/get-container.js.map +1 -0
  11. package/lib/_chunks-es/get-parent.js +18 -6
  12. package/lib/_chunks-es/get-parent.js.map +1 -1
  13. package/lib/_chunks-es/get-path-sub-schema.js +38 -6
  14. package/lib/_chunks-es/get-path-sub-schema.js.map +1 -1
  15. package/lib/_chunks-es/selector.is-at-the-start-of-block.js +3 -0
  16. package/lib/_chunks-es/selector.is-at-the-start-of-block.js.map +1 -1
  17. package/lib/_chunks-es/{util.is-empty-text-block.js → util.is-equal-selections.js} +7 -2
  18. package/lib/_chunks-es/util.is-equal-selections.js.map +1 -0
  19. package/lib/_chunks-es/util.slice-blocks.js +1 -8
  20. package/lib/_chunks-es/util.slice-blocks.js.map +1 -1
  21. package/lib/behaviors/index.d.ts +1 -1
  22. package/lib/index.d.ts +67 -2
  23. package/lib/index.d.ts.map +1 -0
  24. package/lib/index.js +282 -106
  25. package/lib/index.js.map +1 -1
  26. package/lib/plugins/index.d.ts +2 -1
  27. package/lib/plugins/index.d.ts.map +1 -1
  28. package/lib/selectors/index.d.ts +3 -1
  29. package/lib/selectors/index.d.ts.map +1 -1
  30. package/lib/selectors/index.js +4 -2
  31. package/lib/selectors/index.js.map +1 -1
  32. package/lib/traversal/index.d.ts +41 -7
  33. package/lib/traversal/index.d.ts.map +1 -1
  34. package/lib/traversal/index.js +24 -2
  35. package/lib/traversal/index.js.map +1 -1
  36. package/lib/utils/index.d.ts +3 -3
  37. package/lib/utils/index.d.ts.map +1 -1
  38. package/lib/utils/index.js +3 -6
  39. package/lib/utils/index.js.map +1 -1
  40. package/package.json +20 -20
  41. package/lib/_chunks-es/get-first-child.js.map +0 -1
  42. package/lib/_chunks-es/util.is-empty-text-block.js.map +0 -1
package/lib/index.js CHANGED
@@ -3,22 +3,23 @@ import { c } from "react/compiler-runtime";
3
3
  import { useSelector, useActorRef } from "@xstate/react";
4
4
  import React, { createContext, useContext, useRef, useEffect, useLayoutEffect, useState, useReducer, useCallback, memo, forwardRef, useMemo, Component, useSyncExternalStore, startTransition } from "react";
5
5
  import { serializePath, isKeyedSegment, getNode, getParent, parentPath, getChildren, getNodeChildren } from "./_chunks-es/get-parent.js";
6
- import { isPath, getLeaf, isRange, isPoint, isObject, isLeafObject, getAncestor, pathContains, getSpan, isBeforePoint, getText, rangeIntersects, getTextBlock, getUnionSchema, getFirstChild } from "./_chunks-es/get-first-child.js";
7
- import { rangeEdges, isTextBlockNode, isBackwardRange, hasNode, getNodes, isAncestorPath, isEditableContainer, getSibling, isInline, isObject as isObject$1, isSpanNode, isBlock, getEnclosingBlock, getAncestors, getPathSubSchema, getBlock, resolveContainerAt, comparePaths, getEnclosingContainer, comparePoints$1 as comparePoints, isAfterPoint } from "./_chunks-es/get-path-sub-schema.js";
6
+ import { isPath, getLeaf, isRange, isPoint, isObject, isLeafObject, getAncestor, pathContains, getSpan, isBeforePoint, getText, rangeIntersects, getTextBlock, getUnionSchema, getFirstChild, getContainer } from "./_chunks-es/get-container.js";
7
+ import { rangeEdges, isTextBlockNode, isBackwardRange, hasNode, getNodes, isAncestorPath, isEditableContainer, getSibling, isInline, isObject as isObject$1, isSpanNode, isBlock, getEnclosingBlock, getAncestors, getPathSubSchema, getBlock, resolveContainerAt, resolveChildEntryIndex, getEnclosingContainer, comparePoints$1 as comparePoints, comparePaths, isAfterPoint } from "./_chunks-es/get-path-sub-schema.js";
8
8
  import { isSpan, isTextBlock, getSubSchema, compileSchema } from "@portabletext/schema";
9
9
  import { defineSchema } from "@portabletext/schema";
10
10
  import scrollIntoView from "scroll-into-view-if-needed";
11
11
  import { createKeyboardShortcut, undo, redo, code, underline, italic as italic$1, bold as bold$1 } from "@portabletext/keyboard-shortcuts";
12
- import { rangesOverlap, getFocusInlineObject, getFocusTextBlock, getFocusSpan, getFragment, isSelectionExpanded, getSelectionStartBlock, getSelectionEndBlock, isOverlappingSelection, isSelectionCollapsed as isSelectionCollapsed$1, getFocusBlock, getSelectedBlocks, isSelectingEntireBlocks, getSelectedValue, isActiveAnnotation, getActiveAnnotationsMarks, getActiveDecorators, getCaretWordSelection, getSelectionStartChild, getSelectionEndChild, getPreviousSpan, getNextSpan, getSelectionStartPoint, getSelectionEndPoint, getFocusBlockObject, getLastBlock, getFirstBlock, isAtTheEndOfBlock, isAtTheStartOfBlock, getRootAcceptedTypes, getMarkState, getFocusListBlock, getNextBlock, getPreviousBlock, getSelectedTextBlocks, isActiveDecorator, getFocusChild, getActiveAnnotations, isActiveListItem, isActiveStyle } from "./_chunks-es/selector.is-at-the-start-of-block.js";
12
+ import { rangesOverlap, getFocusInlineObject, isSelectionCollapsed as isSelectionCollapsed$1, getFocusTextBlock, getFocusSpan, getFragment, isSelectionExpanded, getSelectionStartBlock, getSelectionEndBlock, isOverlappingSelection, getFocusBlock, getSelectedBlocks, isSelectingEntireBlocks, getSelectedValue, isActiveAnnotation, getActiveAnnotationsMarks, getActiveDecorators, getCaretWordSelection, getSelectionStartChild, getSelectionStartPoint, getSelectionEndChild, getSelectionEndPoint, getPreviousSpan, getNextSpan, getFocusBlockObject, getLastBlock, getFirstBlock, isAtTheEndOfBlock, isAtTheStartOfBlock, getRootAcceptedTypes, getMarkState, getFocusListBlock, getNextBlock, getPreviousBlock, getSelectedTextBlocks, isActiveDecorator, getFocusChild, getActiveAnnotations, isActiveListItem, isActiveStyle } from "./_chunks-es/selector.is-at-the-start-of-block.js";
13
13
  import { isEqualSelectionPoints, blockOffsetToSpanSelectionPoint, getBlockKeyFromSelectionPoint, getBlockEndPoint, getBlockStartPoint, isSelectionCollapsed, defaultKeyGenerator, parseBlocks, parseBlock, isListBlock, getSelectionStartPoint as getSelectionStartPoint$1, getSelectionEndPoint as getSelectionEndPoint$1, parseAnnotation, parseMarkDefs, parseSpan, parseInlineObject } from "./_chunks-es/util.slice-blocks.js";
14
14
  import rawDebug from "debug";
15
- import { isEmptyTextBlock, getTextBlockText } from "./_chunks-es/util.is-empty-text-block.js";
15
+ import { isEmptyTextBlock, isEqualSelections, getTextBlockText } from "./_chunks-es/util.is-equal-selections.js";
16
16
  import { defineBehavior, forward, raise, effect } from "./behaviors/index.js";
17
17
  import { setup, fromCallback, assign, and, assertEvent, enqueueActions, emit, not, raise as raise$1, createActor } from "xstate";
18
18
  import { htmlToPortableText } from "@portabletext/html";
19
19
  import { toHTML } from "@portabletext/to-html";
20
20
  import { markdownToPortableText, portableTextToMarkdown } from "@portabletext/markdown";
21
21
  import { applyAll, unset, set, insert, setIfMissing, diffMatchPatch as diffMatchPatch$1 } from "@portabletext/patches";
22
+ import "xstate/guards";
22
23
  import { EditorContext as EditorContext$1 } from "./_chunks-es/use-editor.js";
23
24
  import { useEditor } from "./_chunks-es/use-editor.js";
24
25
  function getDomNode(editor, path2) {
@@ -1605,7 +1606,8 @@ function transformTextDiff(textDiff, op) {
1605
1606
  id
1606
1607
  } : null;
1607
1608
  }
1608
- const RESOLVE_DELAY = 25, FLUSH_DELAY = 200, isDataTransfer = (value) => value?.constructor.name === "DataTransfer";
1609
+ const RESOLVE_DELAY = 25, FLUSH_DELAY = 200, debug$1 = (..._) => {
1610
+ }, isDataTransfer = (value) => value?.constructor.name === "DataTransfer";
1609
1611
  function createAndroidInputManager({
1610
1612
  editor,
1611
1613
  editorActor,
@@ -1642,6 +1644,7 @@ function createAndroidInputManager({
1642
1644
  const selectionRef = editor.snapshot.context.selection && rangeRef(editor, editor.snapshot.context.selection, {
1643
1645
  affinity: "forward"
1644
1646
  });
1647
+ debug$1("flush", editor.pendingAction, editor.pendingDiffs);
1645
1648
  let scheduleSelectionChange = hasPendingDiffs(), diff2;
1646
1649
  for (; diff2 = editor.pendingDiffs?.[0]; ) {
1647
1650
  const range2 = targetRange(diff2);
@@ -2874,7 +2877,13 @@ const useChildren = (props) => {
2874
2877
  });
2875
2878
  return childContainer && childContainer !== parentContainer ? /* @__PURE__ */ jsx(ParentContainerContext, { value: childContainer, children: /* @__PURE__ */ jsx(NewPipelineContext.Provider, { value: !0, children: elements }) }) : /* @__PURE__ */ jsx(Fragment, { children: elements });
2876
2879
  }, EngineSelectorContext = createContext({}), refEquality = (a, b) => a === b;
2877
- function useEngineSelector(selector, equalityFn = refEquality) {
2880
+ function useRegistrationsSelector(selector, equalityFn = refEquality) {
2881
+ return useChannelSelector(selector, equalityFn, "registrations");
2882
+ }
2883
+ function useListIndexSelector(selector, equalityFn = refEquality) {
2884
+ return useChannelSelector(selector, equalityFn, "listIndex");
2885
+ }
2886
+ function useChannelSelector(selector, equalityFn, channel) {
2878
2887
  const context = useContext(EngineSelectorContext);
2879
2888
  if (!context)
2880
2889
  throw new Error("The `useEngineSelector` hook must be used inside the <Engine> component's context.");
@@ -2882,36 +2891,32 @@ function useEngineSelector(selector, equalityFn = refEquality) {
2882
2891
  addEventListener
2883
2892
  } = context, editor = useEngineStatic(), genericSelector = useCallback(() => selector(editor), [editor, selector]), [selectedState, update] = useGenericSelector(genericSelector, equalityFn);
2884
2893
  return useIsomorphicLayoutEffect(() => {
2885
- const unsubscribe = addEventListener(update);
2894
+ const unsubscribe = addEventListener(update, channel);
2886
2895
  return update(), unsubscribe;
2887
- }, [addEventListener, update]), selectedState;
2896
+ }, [addEventListener, update, channel]), selectedState;
2888
2897
  }
2889
2898
  function useSelectorContext() {
2890
- const eventListeners = useRef(/* @__PURE__ */ new Set()), deferredEventListeners = useRef(/* @__PURE__ */ new Set()), onChange = useCallback(() => {
2899
+ const eventListeners = useRef(/* @__PURE__ */ new Set()), registrationListeners = useRef(/* @__PURE__ */ new Set()), listIndexListeners = useRef(/* @__PURE__ */ new Set()), onChange = useCallback((changed) => {
2891
2900
  eventListeners.current.forEach((listener) => {
2892
2901
  listener();
2893
- });
2894
- }, []), flushDeferred = useCallback(() => {
2895
- deferredEventListeners.current.forEach((listener) => {
2902
+ }), changed.registrations && registrationListeners.current.forEach((listener) => {
2896
2903
  listener();
2897
- }), deferredEventListeners.current.clear();
2898
- }, []), addEventListener = useCallback((callbackProp) => (eventListeners.current.add(callbackProp), () => {
2899
- eventListeners.current.delete(callbackProp);
2900
- }), []);
2904
+ }), changed.listIndex && listIndexListeners.current.forEach((listener) => {
2905
+ listener();
2906
+ });
2907
+ }, []), addEventListener = useCallback((callbackProp, channel) => {
2908
+ const listeners = channel === "registrations" ? registrationListeners.current : channel === "listIndex" ? listIndexListeners.current : eventListeners.current;
2909
+ return listeners.add(callbackProp), () => {
2910
+ listeners.delete(callbackProp);
2911
+ };
2912
+ }, []);
2901
2913
  return {
2902
2914
  selectorContext: useMemo(() => ({
2903
- addEventListener,
2904
- flushDeferred
2905
- }), [addEventListener, flushDeferred]),
2915
+ addEventListener
2916
+ }), [addEventListener]),
2906
2917
  onChange
2907
2918
  };
2908
2919
  }
2909
- function useFlushDeferredSelectorsOnRender() {
2910
- const {
2911
- flushDeferred
2912
- } = useContext(EngineSelectorContext);
2913
- useIsomorphicLayoutEffect(flushDeferred);
2914
- }
2915
2920
  const useEngine = () => {
2916
2921
  const {
2917
2922
  addEventListener
@@ -3094,10 +3099,8 @@ const RestoreDOM = IS_ANDROID ? RestoreDOMComponent : ({
3094
3099
  const domSelection = getSelection(root2);
3095
3100
  if (activeElement === el2)
3096
3101
  state.latestElement = activeElement, editor.focused = !0;
3097
- else {
3098
- editor.focused = !1;
3102
+ else if (editor.focused = !1, !readOnly)
3099
3103
  return;
3100
- }
3101
3104
  if (!domSelection)
3102
3105
  return deselect(editor);
3103
3106
  const {
@@ -3109,7 +3112,16 @@ const RestoreDOM = IS_ANDROID ? RestoreDOMComponent : ({
3109
3112
  exactMatch: !1,
3110
3113
  suppressThrow: !0
3111
3114
  });
3112
- range2 && (!editor.composing && !androidInputManager?.hasPendingChanges() && !androidInputManager?.isFlushing() ? editor.select(range2) : androidInputManager?.handleUserSelect(range2));
3115
+ if (range2)
3116
+ if (!editor.composing && !androidInputManager?.hasPendingChanges() && !androidInputManager?.isFlushing()) {
3117
+ if (editor.select(range2), domSelection.isCollapsed && anchorNode && anchorNode.nodeType === Node.ELEMENT_NODE)
3118
+ try {
3119
+ const canonicalRange = DOMEditor.toDOMRange(editor, range2);
3120
+ domSelection.setBaseAndExtent(canonicalRange.startContainer, canonicalRange.startOffset, canonicalRange.startContainer, canonicalRange.startOffset);
3121
+ } catch {
3122
+ }
3123
+ } else
3124
+ androidInputManager?.handleUserSelect(range2);
3113
3125
  }
3114
3126
  readOnly && (!anchorNodeSelectable || !focusNodeInEditor) && deselect(editor);
3115
3127
  }
@@ -3470,7 +3482,7 @@ const RestoreDOM = IS_ANDROID ? RestoreDOMComponent : ({
3470
3482
  };
3471
3483
  }, [scheduleOnDOMSelectionChange]);
3472
3484
  const decorations = decorate([editor, []]), decorateContext = useDecorateContext(decorate);
3473
- return useFlushDeferredSelectorsOnRender(), /* @__PURE__ */ jsx(ReadOnlyContext.Provider, { value: readOnly, children: /* @__PURE__ */ jsx(DecorateContext.Provider, { value: decorateContext, children: /* @__PURE__ */ jsx(RestoreDOM, { node: ref, receivedUserInput, children: /* @__PURE__ */ jsx(
3485
+ return /* @__PURE__ */ jsx(ReadOnlyContext.Provider, { value: readOnly, children: /* @__PURE__ */ jsx(DecorateContext.Provider, { value: decorateContext, children: /* @__PURE__ */ jsx(RestoreDOM, { node: ref, receivedUserInput, children: /* @__PURE__ */ jsx(
3474
3486
  "div",
3475
3487
  {
3476
3488
  role: readOnly ? void 0 : "textbox",
@@ -4046,6 +4058,33 @@ const debug = {
4046
4058
  syncValue: createDebugger("sync:value"),
4047
4059
  syncPatch: createDebugger("sync:patch")
4048
4060
  };
4061
+ function getPointAtCoordinates(editorEngine, coordinates) {
4062
+ let domRange;
4063
+ try {
4064
+ const window2 = DOMEditor.getWindow(editorEngine);
4065
+ if (window2.document.caretPositionFromPoint !== void 0) {
4066
+ const position = window2.document.caretPositionFromPoint(coordinates.x, coordinates.y);
4067
+ if (position)
4068
+ try {
4069
+ domRange = window2.document.createRange(), domRange.setStart(position.offsetNode, position.offset), domRange.setEnd(position.offsetNode, position.offset);
4070
+ } catch {
4071
+ }
4072
+ } else window2.document.caretRangeFromPoint !== void 0 && (domRange = window2.document.caretRangeFromPoint(coordinates.x, coordinates.y) ?? void 0);
4073
+ } catch {
4074
+ return null;
4075
+ }
4076
+ if (!domRange)
4077
+ return null;
4078
+ try {
4079
+ return DOMEditor.toEditorSelection(editorEngine, domRange, {
4080
+ exactMatch: !1,
4081
+ // It can still throw even with this option set to true
4082
+ suppressThrow: !1
4083
+ })?.focus ?? null;
4084
+ } catch {
4085
+ return null;
4086
+ }
4087
+ }
4049
4088
  function isDragEvent(event) {
4050
4089
  return "dataTransfer" in event;
4051
4090
  }
@@ -4217,31 +4256,14 @@ function getEventPositionBlock({
4217
4256
  function getSelectionFromEvent(editor, event) {
4218
4257
  if (!event.target || !isDOMNode(event.target))
4219
4258
  return;
4220
- const window2 = DOMEditor.getWindow(editor);
4221
- let domRange;
4222
- if (window2.document.caretPositionFromPoint !== void 0) {
4223
- const position = window2.document.caretPositionFromPoint(event.clientX, event.clientY);
4224
- if (position)
4225
- try {
4226
- domRange = window2.document.createRange(), domRange.setStart(position.offsetNode, position.offset), domRange.setEnd(position.offsetNode, position.offset);
4227
- } catch {
4228
- }
4229
- } else if (window2.document.caretRangeFromPoint !== void 0)
4230
- domRange = window2.document.caretRangeFromPoint(event.clientX, event.clientY) ?? void 0;
4231
- else {
4232
- console.warn("Neither caretPositionFromPoint nor caretRangeFromPoint is supported");
4233
- return;
4234
- }
4235
- if (domRange)
4236
- try {
4237
- return DOMEditor.toEditorSelection(editor, domRange, {
4238
- exactMatch: !1,
4239
- // It can still throw even with this option set to true
4240
- suppressThrow: !1
4241
- });
4242
- } catch {
4243
- return;
4244
- }
4259
+ const point2 = getPointAtCoordinates(editor, {
4260
+ x: event.clientX,
4261
+ y: event.clientY
4262
+ });
4263
+ return point2 ? {
4264
+ anchor: point2,
4265
+ focus: point2
4266
+ } : void 0;
4245
4267
  }
4246
4268
  function isEventContainer(editorEngine, eventNode, eventPath) {
4247
4269
  return isEditor(eventNode) ? !0 : isEditableContainer(editorEngine.snapshot, eventNode, eventPath);
@@ -4260,6 +4282,7 @@ function getDragSelection({
4260
4282
  }))
4261
4283
  return dragSelection;
4262
4284
  const draggingCollapsedSelection = isSelectionCollapsed$1({
4285
+ ...snapshot,
4263
4286
  context: {
4264
4287
  ...snapshot.context,
4265
4288
  selection: eventSelection
@@ -5518,7 +5541,7 @@ function RenderTextBlock(props) {
5518
5541
  const serializedPath = t2, selected = useIsSelectedContainer(serializedPath), focused = useIsFocusedContainer(serializedPath), dropPosition = useElementDropPosition(props.path);
5519
5542
  let t3;
5520
5543
  $[7] !== serializedPath ? (t3 = (editor) => getListIndexMap(editor).get(serializedPath), $[7] = serializedPath, $[8] = t3) : t3 = $[8];
5521
- const listIndex = useEngineSelector(t3), subSchema = useBlockSubSchema(props.path);
5544
+ const listIndex = useListIndexSelector(t3), subSchema = useBlockSubSchema(props.path);
5522
5545
  let children = props.children;
5523
5546
  if (props.legacy.renderStyle && props.textBlock.style) {
5524
5547
  let t42;
@@ -5684,7 +5707,7 @@ function RenderElement(props) {
5684
5707
  const inlinePositionalOverride = t1;
5685
5708
  let t2;
5686
5709
  $[6] !== type ? (t2 = (engine) => [engine.containers.get(type), engine.blockObjects.get(type), engine.blockObjects.get("*"), engine.inlineObjects.get(type), engine.inlineObjects.get("*"), engine.textBlocks.get(type), engine.textBlocks.get("*")], $[6] = type, $[7] = t2) : t2 = $[7];
5687
- const [globalContainerConfig, globalBlockObjectConfig, globalBlockObjectCatchAll, globalInlineObjectConfig, globalInlineObjectCatchAll, textBlockConfig, textBlockCatchAll] = useEngineSelector(t2, tupleRefEqual);
5710
+ const [globalContainerConfig, globalBlockObjectConfig, globalBlockObjectCatchAll, globalInlineObjectConfig, globalInlineObjectCatchAll, textBlockConfig, textBlockCatchAll] = useRegistrationsSelector(t2, tupleRefEqual);
5688
5711
  let t3;
5689
5712
  bb0: {
5690
5713
  if (blockPositionalOverride && "container" in blockPositionalOverride) {
@@ -5876,7 +5899,7 @@ function useSpanConfig(node, _path) {
5876
5899
  const positional = t0;
5877
5900
  let t1;
5878
5901
  $[3] !== node._type ? (t1 = (engine) => [engine.spans.get(node._type), engine.spans.get("*")], $[3] = node._type, $[4] = t1) : t1 = $[4];
5879
- const [globalSpan, globalSpanCatchAll] = useEngineSelector(t1, tupleRefEqual);
5902
+ const [globalSpan, globalSpanCatchAll] = useRegistrationsSelector(t1, tupleRefEqual);
5880
5903
  return positional && "span" in positional ? positional.span.render === void 0 ? globalSpan ?? globalSpanCatchAll : positional : globalSpan ?? globalSpanCatchAll;
5881
5904
  }
5882
5905
  function RenderSpan(props) {
@@ -5893,7 +5916,7 @@ function RenderSpan(props) {
5893
5916
  }, parent) ? parent : void 0;
5894
5917
  let t2;
5895
5918
  $[3] !== block?.children || $[4] !== props.leaf._key ? (t2 = block?.children.find((_child) => _child._key === props.leaf._key), $[3] = block?.children, $[4] = props.leaf._key, $[5] = t2) : t2 = $[5];
5896
- const child = t2, subSchema = useBlockSubSchema(props.path), spanConfig = useSpanConfig(child ?? props.leaf), isInNewPipeline = useContext(NewPipelineContext);
5919
+ const child = t2, subSchema = useBlockSubSchema(props.path), spanConfig = useSpanConfig(child ?? props.leaf, props.path), isInNewPipeline = useContext(NewPipelineContext);
5897
5920
  let t3;
5898
5921
  $[6] !== props.path ? (t3 = serializePath(props.path), $[6] = props.path, $[7] = t3) : t3 = $[7];
5899
5922
  const serializedPath = t3, focused = useIsFocusedLeaf(serializedPath), selected = useIsSelectedLeaf(serializedPath);
@@ -6135,15 +6158,19 @@ function validateSelection(editorEngine, editorElement) {
6135
6158
  if (!root || editorElement !== root.activeElement)
6136
6159
  return;
6137
6160
  const domSelection = DOMEditor.getWindow(editorEngine).getSelection();
6138
- if (!domSelection || domSelection.rangeCount === 0)
6139
- return;
6140
- const existingDOMRange = domSelection.getRangeAt(0);
6141
- try {
6142
- const newDOMRange = DOMEditor.toDOMRange(editorEngine, editorEngine.snapshot.context.selection);
6143
- (newDOMRange.startOffset !== existingDOMRange.startOffset || newDOMRange.endOffset !== existingDOMRange.endOffset) && (debug.selection("DOM range out of sync, validating selection"), domSelection?.removeAllRanges(), domSelection.addRange(newDOMRange));
6144
- } catch {
6145
- debug.selection("Could not resolve selection, skipping DOM sync this tick");
6146
- }
6161
+ if (!(!domSelection || domSelection.rangeCount === 0))
6162
+ try {
6163
+ const domEditorSelection = DOMEditor.toEditorSelection(editorEngine, domSelection, {
6164
+ exactMatch: !1,
6165
+ suppressThrow: !0
6166
+ });
6167
+ if (domEditorSelection && isEqualSelections(domEditorSelection, editorEngine.snapshot.context.selection))
6168
+ return;
6169
+ const newDOMRange = DOMEditor.toDOMRange(editorEngine, editorEngine.snapshot.context.selection);
6170
+ debug.selection("DOM range out of sync, validating selection"), domSelection.removeAllRanges(), domSelection.addRange(newDOMRange);
6171
+ } catch {
6172
+ debug.selection("Could not resolve selection, skipping DOM sync this tick");
6173
+ }
6147
6174
  }
6148
6175
  const PortableTextEditable = forwardRef(function(props, forwardedRef) {
6149
6176
  const $ = c(179);
@@ -6742,7 +6769,11 @@ const Engine = (props) => {
6742
6769
  selectorContext,
6743
6770
  onChange: handleSelectorChange
6744
6771
  } = useSelectorContext(), onContextChange = useCallback(() => {
6745
- handleSelectorChange();
6772
+ const pending = editor.selectorChannelsPending, registrations = pending.registrations, listIndex = pending.listIndex;
6773
+ pending.registrations = !1, pending.listIndex = !1, handleSelectorChange({
6774
+ registrations,
6775
+ listIndex
6776
+ });
6746
6777
  }, [editor, handleSelectorChange]);
6747
6778
  return useEffect(() => (editor.onContextChange = onContextChange, () => {
6748
6779
  editor.onContextChange = () => {
@@ -7686,6 +7717,81 @@ function transformRangeRef(ref, op, root) {
7686
7717
  });
7687
7718
  ref.current = path2, path2 == null && ref.unref();
7688
7719
  }
7720
+ function findNearestSpans(snapshot, path2) {
7721
+ return {
7722
+ previousSpan: findPreviousSpan(snapshot, path2),
7723
+ nextSpan: findNextSpan(snapshot, path2)
7724
+ };
7725
+ }
7726
+ function findNextSpan(snapshot, path2) {
7727
+ const descendantSpan = firstSpanIn(snapshot, path2);
7728
+ if (descendantSpan)
7729
+ return descendantSpan;
7730
+ let currentPath = path2;
7731
+ for (; currentPath.length > 0; ) {
7732
+ const parentPath2 = nodeParentPath(currentPath), siblings = getChildren(snapshot, parentPath2), index = resolveChildEntryIndex(snapshot.blockIndexMap, siblings, currentPath);
7733
+ if (index !== -1)
7734
+ for (let siblingIndex = index + 1; siblingIndex < siblings.length; siblingIndex++) {
7735
+ const sibling = siblings[siblingIndex];
7736
+ if (isSpan({
7737
+ schema: snapshot.context.schema
7738
+ }, sibling.node))
7739
+ return {
7740
+ node: sibling.node,
7741
+ path: sibling.path
7742
+ };
7743
+ const siblingDescendantSpan = firstSpanIn(snapshot, sibling.path);
7744
+ if (siblingDescendantSpan)
7745
+ return siblingDescendantSpan;
7746
+ }
7747
+ currentPath = parentPath2;
7748
+ }
7749
+ }
7750
+ function findPreviousSpan(snapshot, path2) {
7751
+ let currentPath = path2;
7752
+ for (; currentPath.length > 0; ) {
7753
+ const parentPath2 = nodeParentPath(currentPath), siblings = getChildren(snapshot, parentPath2), index = resolveChildEntryIndex(snapshot.blockIndexMap, siblings, currentPath);
7754
+ if (index !== -1)
7755
+ for (let siblingIndex = index - 1; siblingIndex >= 0; siblingIndex--) {
7756
+ const sibling = siblings[siblingIndex], siblingDescendantSpan = lastSpanIn(snapshot, sibling.path);
7757
+ if (siblingDescendantSpan)
7758
+ return siblingDescendantSpan;
7759
+ if (isSpan({
7760
+ schema: snapshot.context.schema
7761
+ }, sibling.node))
7762
+ return {
7763
+ node: sibling.node,
7764
+ path: sibling.path
7765
+ };
7766
+ }
7767
+ currentPath = parentPath2;
7768
+ }
7769
+ }
7770
+ function firstSpanIn(snapshot, path2) {
7771
+ for (const entry of getNodes(snapshot, {
7772
+ at: path2,
7773
+ match: (node) => isSpan({
7774
+ schema: snapshot.context.schema
7775
+ }, node)
7776
+ }))
7777
+ return entry;
7778
+ }
7779
+ function lastSpanIn(snapshot, path2) {
7780
+ for (const entry of getNodes(snapshot, {
7781
+ at: path2,
7782
+ match: (node) => isSpan({
7783
+ schema: snapshot.context.schema
7784
+ }, node),
7785
+ reverse: !0
7786
+ }))
7787
+ return entry;
7788
+ }
7789
+ function nodeParentPath(path2) {
7790
+ let end2 = path2.length - 1;
7791
+ for (; end2 > 0 && typeof path2[end2 - 1] == "string"; )
7792
+ end2--;
7793
+ return path2.slice(0, end2);
7794
+ }
7689
7795
  function getValue(root, path2) {
7690
7796
  let current = root;
7691
7797
  for (const segment of path2) {
@@ -7970,26 +8076,16 @@ function applyOperation(editor, op) {
7970
8076
  if (selection != null && result != null)
7971
8077
  selection[key] = result;
7972
8078
  else {
7973
- let prev, next;
7974
- for (const {
7975
- node: n,
7976
- path: p
7977
- } of getNodes(editor.snapshot))
7978
- if (isSpan({
7979
- schema: editor.snapshot.context.schema
7980
- }, n) && !pathEquals(p, path2))
7981
- if (comparePaths(p, path2, editor.snapshot.context) === -1)
7982
- prev = [n, p];
7983
- else {
7984
- next = [n, p];
7985
- break;
7986
- }
8079
+ const {
8080
+ previousSpan,
8081
+ nextSpan
8082
+ } = findNearestSpans(editor.snapshot, path2);
7987
8083
  let preferNext = !1;
7988
- prev && next && (isSiblingPath(prev[1], path2) ? preferNext = !1 : pathEquals(next[1], path2) ? preferNext = !0 : preferNext = commonPath(prev[1], path2).length < commonPath(next[1], path2).length), prev && !preferNext ? selection[key] = {
7989
- path: prev[1],
7990
- offset: prev[0].text.length
7991
- } : next ? selection[key] = {
7992
- path: next[1],
8084
+ previousSpan && nextSpan && (isSiblingPath(previousSpan.path, path2) ? preferNext = !1 : preferNext = commonPath(previousSpan.path, path2).length < commonPath(nextSpan.path, path2).length), previousSpan && !preferNext ? selection[key] = {
8085
+ path: previousSpan.path,
8086
+ offset: previousSpan.node.text.length
8087
+ } : nextSpan ? selection[key] = {
8088
+ path: nextSpan.path,
7993
8089
  offset: 0
7994
8090
  } : selection = null;
7995
8091
  }
@@ -10733,7 +10829,7 @@ ${safeStringify(event.operation, 2)}`);
10733
10829
  operation.type !== "set.selection" && (operation.type === "insert.text" || operation.type === "remove.text" || (transformBlockIndexMap(editor.blockIndexMap, operation, event.beforeValue, editor.snapshot.context.value, {
10734
10830
  schema: context.schema,
10735
10831
  containers: editor.snapshot.context.containers
10736
- }), editor.listIndexMapDirty = !0, invalidateVerifiedGroups(editor.verifiedUniqueChildGroups, operation)));
10832
+ }), editor.listIndexMapDirty = !0, editor.selectorChannelsPending.listIndex = !0, invalidateVerifiedGroups(editor.verifiedUniqueChildGroups, operation)));
10737
10833
  });
10738
10834
  return () => {
10739
10835
  unsubscribeNormalizationLog?.(), unsubscribeIndexMaps();
@@ -10767,7 +10863,10 @@ function createEditorEngine(config) {
10767
10863
  }, editor.containers = /* @__PURE__ */ new Map(), editor.blockObjects = /* @__PURE__ */ new Map(), editor.inlineObjects = /* @__PURE__ */ new Map(), editor.spans = /* @__PURE__ */ new Map(), editor.textBlocks = /* @__PURE__ */ new Map(), editor.decoratedRanges = [], editor.blockIndexMap = blockIndexMap, editor.history = {
10768
10864
  undos: [],
10769
10865
  redos: []
10770
- }, editor.listIndexMap = /* @__PURE__ */ new Map(), editor.listIndexMapDirty = !1, editor.verifiedUniqueChildGroups = /* @__PURE__ */ new Set(), editor.remotePatches = [], editor.undoStepId = void 0, editor.isDeferringMutations = !1, editor.isNormalizingNode = !1, editor.isPatching = !0, editor.isPerformingBehaviorOperation = !1, editor.isProcessingRemoteChanges = !1, editor.isRedoing = !1, editor.isUndoing = !1, editor.withHistory = !0;
10866
+ }, editor.listIndexMap = /* @__PURE__ */ new Map(), editor.listIndexMapDirty = !1, editor.selectorChannelsPending = {
10867
+ registrations: !1,
10868
+ listIndex: !1
10869
+ }, editor.verifiedUniqueChildGroups = /* @__PURE__ */ new Set(), editor.remotePatches = [], editor.undoStepId = void 0, editor.isDeferringMutations = !1, editor.isNormalizingNode = !1, editor.isPatching = !0, editor.isPerformingBehaviorOperation = !1, editor.isProcessingRemoteChanges = !1, editor.isRedoing = !1, editor.isUndoing = !1, editor.withHistory = !0;
10771
10870
  const editorEngine = plugins(withDOM(editor), {
10772
10871
  editorActor: config.editorActor
10773
10872
  });
@@ -10808,7 +10907,8 @@ function createEditorDom(sendBack, editorEngine) {
10808
10907
  getBlockNodes: (snapshot) => getBlockNodes(editorEngine, snapshot),
10809
10908
  getChildNodes: (snapshot) => getChildNodes(editorEngine, snapshot),
10810
10909
  getEditorElement: () => getEditorElement(editorEngine),
10811
- getSelectionRect: (snapshot) => getSelectionRect(snapshot),
10910
+ getSelectionRect: (snapshot) => getSelectionRect(editorEngine, snapshot),
10911
+ getPointAtCoordinates: (coordinates) => getPointAtCoordinates(editorEngine, coordinates),
10812
10912
  getStartBlockElement: (snapshot) => getStartBlockElement(editorEngine, snapshot),
10813
10913
  getEndBlockElement: (snapshot) => getEndBlockElement(editorEngine, snapshot),
10814
10914
  setDragGhost: ({
@@ -10861,12 +10961,15 @@ function getChildNodes(editorEngine, snapshot) {
10861
10961
  function getEditorElement(editorEngine) {
10862
10962
  return getDomNode(editorEngine, []);
10863
10963
  }
10864
- function getSelectionRect(snapshot) {
10865
- if (!snapshot.context.selection)
10964
+ function getSelectionRect(editorEngine, snapshot) {
10965
+ const selection = snapshot.context.selection;
10966
+ if (!selection)
10866
10967
  return null;
10867
10968
  try {
10868
- const selection = window.getSelection();
10869
- return selection ? selection.getRangeAt(0).getBoundingClientRect() : null;
10969
+ const engineRange = resolveSelection({
10970
+ snapshot
10971
+ }, selection);
10972
+ return engineRange ? DOMEditor.toDOMRange(editorEngine, engineRange).getBoundingClientRect() : null;
10870
10973
  } catch {
10871
10974
  return null;
10872
10975
  }
@@ -10915,6 +11018,7 @@ const addAnnotationOnCollapsedSelection = defineBehavior({
10915
11018
  return !1;
10916
11019
  const caretWordSelection = getCaretWordSelection(adjustedSnapshot);
10917
11020
  return !caretWordSelection || !isSelectionExpanded({
11021
+ ...adjustedSnapshot,
10918
11022
  context: {
10919
11023
  ...adjustedSnapshot.context,
10920
11024
  selection: caretWordSelection
@@ -11102,6 +11206,22 @@ const addAnnotationOnCollapsedSelection = defineBehavior({
11102
11206
  undo,
11103
11207
  redo
11104
11208
  },
11209
+ selectAll: createKeyboardShortcut({
11210
+ default: [{
11211
+ key: "A",
11212
+ alt: !1,
11213
+ ctrl: !0,
11214
+ meta: !1,
11215
+ shift: !1
11216
+ }],
11217
+ apple: [{
11218
+ key: "A",
11219
+ alt: !1,
11220
+ ctrl: !1,
11221
+ meta: !0,
11222
+ shift: !1
11223
+ }]
11224
+ }),
11105
11225
  tab: createKeyboardShortcut({
11106
11226
  default: [{
11107
11227
  key: "Tab",
@@ -12144,11 +12264,13 @@ const MAX_LIST_LEVEL = 10, clearListOnBackspace = defineBehavior({
12144
12264
  if (!isListBlock(snapshot.context, startBlock) || !isListBlock(snapshot.context, endBlock))
12145
12265
  return !1;
12146
12266
  const deleteStartPoint = getSelectionStartPoint({
12267
+ ...snapshot,
12147
12268
  context: {
12148
12269
  ...snapshot.context,
12149
12270
  selection: at
12150
12271
  }
12151
12272
  }), deleteEndPoint = getSelectionEndPoint({
12273
+ ...snapshot,
12152
12274
  context: {
12153
12275
  ...snapshot.context,
12154
12276
  selection: at
@@ -14558,7 +14680,14 @@ const moveBackwardOperationImplementation = ({
14558
14680
  }, selectOperationImplementation = ({
14559
14681
  operation
14560
14682
  }) => {
14561
- const newSelection = resolveSelection(operation.editor, operation.at);
14683
+ let newSelection = resolveSelection(operation.editor, operation.at);
14684
+ if (newSelection) {
14685
+ const anchorContainer = getContainer(operation.editor.snapshot, newSelection.anchor.path), focusContainer = getContainer(operation.editor.snapshot, newSelection.focus.path), containers = anchorContainer ? [anchorContainer] : [];
14686
+ focusContainer && focusContainer.node !== anchorContainer?.node && containers.push(focusContainer), containers.length > 0 && (withoutNormalizing(operation.editor, () => {
14687
+ for (const entry of containers)
14688
+ operation.editor.isNormalizingNode = !0, operation.editor.normalizeNode([entry.node, entry.path]), operation.editor.isNormalizingNode = !1;
14689
+ }), newSelection = resolveSelection(operation.editor, operation.at));
14690
+ }
14562
14691
  newSelection ? applySelect(operation.editor, newSelection) : applyDeselect(operation.editor), operation.editor.focused && operation.editor.snapshot.context.readOnly && (operation.editor.focused = !1);
14563
14692
  }, setOperationImplementation = ({
14564
14693
  operation
@@ -15853,6 +15982,50 @@ const abstractInsertBehaviors = [
15853
15982
  type: "history.redo"
15854
15983
  })]]
15855
15984
  }),
15985
+ /**
15986
+ * Manual handling of select-all. The native gesture cannot be trusted to
15987
+ * build the range: chromium produces an already-collapsed range whenever a
15988
+ * non-editable element sits at either content edge of the editing host,
15989
+ * which any document starting or ending with a void block or a
15990
+ * chrome-bearing container render does. "Everything" is a model-level
15991
+ * statement, so the range is computed from the model instead of
15992
+ * round-tripping through the DOM selection.
15993
+ */
15994
+ defineBehavior({
15995
+ on: "keyboard.keydown",
15996
+ guard: ({
15997
+ snapshot,
15998
+ event
15999
+ }) => {
16000
+ if (!defaultKeyboardShortcuts.selectAll.guard(event.originEvent))
16001
+ return !1;
16002
+ const startLeaf = getLeaf(snapshot, [], {
16003
+ edge: "start"
16004
+ }), endLeaf = getLeaf(snapshot, [], {
16005
+ edge: "end"
16006
+ }), startBlock = startLeaf ? getEnclosingBlock(snapshot, startLeaf.path) : void 0, endBlock = endLeaf ? getEnclosingBlock(snapshot, endLeaf.path) : void 0;
16007
+ return !startBlock || !endBlock ? !1 : {
16008
+ anchor: getBlockStartPoint({
16009
+ context: snapshot.context,
16010
+ block: startBlock
16011
+ }),
16012
+ focus: getBlockEndPoint({
16013
+ context: snapshot.context,
16014
+ block: endBlock
16015
+ })
16016
+ };
16017
+ },
16018
+ actions: [(_, {
16019
+ anchor,
16020
+ focus
16021
+ }) => [raise({
16022
+ type: "select",
16023
+ at: {
16024
+ anchor,
16025
+ focus
16026
+ }
16027
+ })]]
16028
+ }),
15856
16029
  /**
15857
16030
  * Fix edge case where Shift+ArrowLeft didn't reduce a selection hanging
15858
16031
  * onto an empty text block.
@@ -16853,14 +17026,14 @@ function registerNodeOnEngine(engine, node) {
16853
17026
  const containers = new Map(engine.containers);
16854
17027
  containers.set(node.type, containerConfig), engine.containers = containers, engine.snapshot.context.containers = buildPublicContainers(containers), normalize(engine, {
16855
17028
  force: !0
16856
- }), engine.onChange();
17029
+ }), notifyRegistrationsChanged(engine);
16857
17030
  return;
16858
17031
  }
16859
17032
  if (node.kind === "textBlock") {
16860
17033
  if (isTypeAlreadyRegistered(engine, "textBlock", node.type))
16861
17034
  return;
16862
17035
  const textBlocks = new Map(engine.textBlocks);
16863
- textBlocks.set(node.type, resolveTextBlockConfig(node)), engine.textBlocks = textBlocks, engine.onChange();
17036
+ textBlocks.set(node.type, resolveTextBlockConfig(node)), engine.textBlocks = textBlocks, notifyRegistrationsChanged(engine);
16864
17037
  return;
16865
17038
  }
16866
17039
  if (node.kind === "span") {
@@ -16869,7 +17042,7 @@ function registerNodeOnEngine(engine, node) {
16869
17042
  const spans = new Map(engine.spans);
16870
17043
  spans.set(node.type, {
16871
17044
  span: node
16872
- }), engine.spans = spans, engine.onChange();
17045
+ }), engine.spans = spans, notifyRegistrationsChanged(engine);
16873
17046
  return;
16874
17047
  }
16875
17048
  if (node.kind === "blockObject") {
@@ -16878,7 +17051,7 @@ function registerNodeOnEngine(engine, node) {
16878
17051
  const blockObjects = new Map(engine.blockObjects);
16879
17052
  blockObjects.set(node.type, {
16880
17053
  blockObject: node
16881
- }), engine.blockObjects = blockObjects, engine.onChange();
17054
+ }), engine.blockObjects = blockObjects, notifyRegistrationsChanged(engine);
16882
17055
  return;
16883
17056
  }
16884
17057
  if (isTypeAlreadyRegistered(engine, "inlineObject", node.type))
@@ -16886,33 +17059,36 @@ function registerNodeOnEngine(engine, node) {
16886
17059
  const inlineObjects = new Map(engine.inlineObjects);
16887
17060
  inlineObjects.set(node.type, {
16888
17061
  inlineObject: node
16889
- }), engine.inlineObjects = inlineObjects, engine.onChange();
17062
+ }), engine.inlineObjects = inlineObjects, notifyRegistrationsChanged(engine);
16890
17063
  }
16891
17064
  function unregisterNodeOnEngine(engine, node) {
16892
17065
  if (node.kind === "container") {
16893
17066
  const containers = new Map(engine.containers);
16894
17067
  containers.delete(node.type), engine.containers = containers, engine.snapshot.context.containers = buildPublicContainers(containers), normalize(engine, {
16895
17068
  force: !0
16896
- }), engine.onChange();
17069
+ }), notifyRegistrationsChanged(engine);
16897
17070
  return;
16898
17071
  }
16899
17072
  if (node.kind === "textBlock") {
16900
17073
  const textBlocks = new Map(engine.textBlocks);
16901
- textBlocks.delete(node.type), engine.textBlocks = textBlocks, engine.onChange();
17074
+ textBlocks.delete(node.type), engine.textBlocks = textBlocks, notifyRegistrationsChanged(engine);
16902
17075
  return;
16903
17076
  }
16904
17077
  if (node.kind === "span") {
16905
17078
  const spans = new Map(engine.spans);
16906
- spans.delete(node.type), engine.spans = spans, engine.onChange();
17079
+ spans.delete(node.type), engine.spans = spans, notifyRegistrationsChanged(engine);
16907
17080
  return;
16908
17081
  }
16909
17082
  if (node.kind === "blockObject") {
16910
17083
  const blockObjects = new Map(engine.blockObjects);
16911
- blockObjects.delete(node.type), engine.blockObjects = blockObjects, engine.onChange();
17084
+ blockObjects.delete(node.type), engine.blockObjects = blockObjects, notifyRegistrationsChanged(engine);
16912
17085
  return;
16913
17086
  }
16914
17087
  const inlineObjects = new Map(engine.inlineObjects);
16915
- inlineObjects.delete(node.type), engine.inlineObjects = inlineObjects, engine.onChange();
17088
+ inlineObjects.delete(node.type), engine.inlineObjects = inlineObjects, notifyRegistrationsChanged(engine);
17089
+ }
17090
+ function notifyRegistrationsChanged(engine) {
17091
+ engine.selectorChannelsPending.registrations = !0, engine.onChange();
16916
17092
  }
16917
17093
  function rerouteExternalBehaviorEvent({
16918
17094
  event,