superdoc 2.28.0-next.5 → 2.28.0-next.7

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 (45) hide show
  1. package/dist/chunks/{create-super-doc-ui-D1eOhAjv.cjs → create-super-doc-ui-B4c55pFy.cjs} +420 -84
  2. package/dist/chunks/{create-super-doc-ui-B29zw5qD.es.js → create-super-doc-ui-C4Ka5Ph3.es.js} +415 -85
  3. package/dist/collaboration-upgrade-engine.cjs +1 -1
  4. package/dist/collaboration-upgrade-engine.es.js +1 -1
  5. package/dist/document-api/src/content-controls/content-controls.types.d.ts +11 -0
  6. package/dist/document-api/src/contract/command-catalog.d.ts +1 -1
  7. package/dist/document-api/src/contract/operation-definitions.d.ts +41 -41
  8. package/dist/document-api/src/contract/operation-registry.d.ts +4 -0
  9. package/dist/document-api/src/export/export.d.ts +3 -1
  10. package/dist/document-api/src/export/export.types.d.ts +8 -0
  11. package/dist/document-api/src/get-node/get-node.d.ts +10 -4
  12. package/dist/document-api/src/index.d.ts +2 -1
  13. package/dist/document-api/src/replace/replace.d.ts +4 -1
  14. package/dist/document-api/src/types/mutation-plan.types.d.ts +2 -0
  15. package/dist/document-api/src/types/track-changes.types.d.ts +10 -2
  16. package/dist/layout-engine/contracts/src/index.d.ts +1 -0
  17. package/dist/public/ui-react.cjs +1 -1
  18. package/dist/public/ui-react.es.js +1 -1
  19. package/dist/public/ui-vue.cjs +1 -1
  20. package/dist/public/ui-vue.es.js +1 -1
  21. package/dist/public/ui.cjs +1 -1
  22. package/dist/public/ui.es.js +1 -1
  23. package/dist/style.css +1 -1
  24. package/dist/style.layered.css +1 -1
  25. package/dist/superdoc/src/core/SuperDoc.d.ts +2 -6
  26. package/dist/superdoc/src/core/collaboration/resolve-v2-collaboration-target.d.ts +2 -0
  27. package/dist/superdoc/src/core/editor-runtime/types.d.ts +1 -0
  28. package/dist/superdoc/src/core/editor-runtime/v2/v2-editor-runtime-adapter.d.ts +2 -2
  29. package/dist/superdoc/src/core/helpers/resolve-docx-export.d.ts +6 -0
  30. package/dist/superdoc/src/core/types/index.d.ts +10 -0
  31. package/dist/superdoc/src/public/export-types.d.ts +3 -0
  32. package/dist/superdoc/src/public/ui/entity-at.d.ts +1 -0
  33. package/dist/superdoc/src/public/ui/types.d.ts +16 -2
  34. package/dist/superdoc/src/public/ui.d.cts +14 -0
  35. package/dist/superdoc/src/public/ui.d.ts +1 -1
  36. package/dist/superdoc/src/stores/comments-store.d.ts +3 -57
  37. package/dist/superdoc/src/stores/superdoc-store.d.ts +9 -171
  38. package/dist/superdoc.cjs +352 -135
  39. package/dist/superdoc.es.js +352 -135
  40. package/dist-cdn/style.layered.css +1 -1
  41. package/dist-cdn/superdoc.min.css +1 -1
  42. package/dist-cdn/superdoc.min.js +42 -42
  43. package/package.json +2 -2
  44. package/dist/superdoc/src/index.d.cts +0 -18
  45. package/dist/superdoc/src/index.d.ts +0 -14
@@ -3692,6 +3692,25 @@ function getCommandDescriptor(id) {
3692
3692
  }
3693
3693
  //#endregion
3694
3694
  //#region src/public/ui/entity-at.ts
3695
+ function readDecorationProviders(node) {
3696
+ const encoded = node.getAttribute("data-superdoc-extension-decorations");
3697
+ if (encoded) try {
3698
+ const parsed = JSON.parse(encoded);
3699
+ if (Array.isArray(parsed)) return parsed.filter((id) => typeof id === "string");
3700
+ } catch {
3701
+ return [];
3702
+ }
3703
+ const single = node.getAttribute("data-superdoc-extension-decoration");
3704
+ return single ? [single] : [];
3705
+ }
3706
+ function collectDecorationProvidersFromChain(start, stopAt) {
3707
+ const providers = /* @__PURE__ */ new Set();
3708
+ for (let element = start; element; element = element.parentElement) {
3709
+ for (const provider of readDecorationProviders(element)) providers.add(provider);
3710
+ if (element === stopAt) break;
3711
+ }
3712
+ return [...providers];
3713
+ }
3695
3714
  function parseCommaSeparatedIds(value) {
3696
3715
  return value.split(",").map((id) => id.trim()).filter(Boolean);
3697
3716
  }
@@ -3763,6 +3782,16 @@ function collectEntityHitsFromChain(start, stopAt) {
3763
3782
  for (const hit of pendingStory) hit.story = layoutStory;
3764
3783
  pendingStory = [];
3765
3784
  }
3785
+ for (const decorationProvider of readDecorationProviders(node)) {
3786
+ const key = `decoration:${decorationProvider}`;
3787
+ if (!seen.has(key)) {
3788
+ seen.add(key);
3789
+ hits.push({
3790
+ type: "decoration",
3791
+ id: decorationProvider
3792
+ });
3793
+ }
3794
+ }
3766
3795
  const citationId = node.getAttribute("data-citation-id");
3767
3796
  if (citationId) {
3768
3797
  const key = `citation:${citationId}`;
@@ -6153,6 +6182,7 @@ function createSuperDocUI(options) {
6153
6182
  pointerSelecting: false,
6154
6183
  keyboardSelecting: false
6155
6184
  };
6185
+ const paintedInlineTargets = /* @__PURE__ */ new WeakSet();
6156
6186
  let painterCaptureEpoch = 0;
6157
6187
  let painterCommandDoc = null;
6158
6188
  const painterModeListeners = /* @__PURE__ */ new Set();
@@ -6249,7 +6279,7 @@ function createSuperDocUI(options) {
6249
6279
  const readHostSelectionSnapshot = () => {
6250
6280
  const handles = safeCall(() => hostMethod(getHost(), "getHandles")?.() ?? null, null);
6251
6281
  const editing = isPlainObject(handles) ? handles.editing : void 0;
6252
- const getSnapshot = hostMethod(isPlainObject(editing) ? editing.selection : void 0, "getSnapshot");
6282
+ const getSnapshot = hostMethod(isPlainObject(editing) ? editing.selection : void 0, "getSnapshot") ?? hostMethod(isPlainObject(handles) ? handles.selection : void 0, "getSnapshot");
6253
6283
  if (!getSnapshot) return null;
6254
6284
  const snapshot = safeCall(() => getSnapshot(), null);
6255
6285
  return snapshot && typeof snapshot === "object" ? snapshot : null;
@@ -6463,7 +6493,7 @@ function createSuperDocUI(options) {
6463
6493
  return left.id === right.id && left.paintedEntityId === right.paintedEntityId && storyLocatorSignature(left.story) === storyLocatorSignature(right.story);
6464
6494
  };
6465
6495
  const hasPendingTrackChangeRevealFocus = (focus) => Array.from(pendingTrackChangeRevealFocuses).some((pendingFocus) => explicitActiveChangesEqual(pendingFocus, focus));
6466
- const mirrorTrackedChangeFocusToHost = (next) => {
6496
+ const mirrorReviewFocusToHost = (entityType, next) => {
6467
6497
  const readHandles = hostMethod(getHost(), "getHandles");
6468
6498
  if (!readHandles) return;
6469
6499
  const handles = safeCall(() => readHandles(), null);
@@ -6474,15 +6504,15 @@ function createSuperDocUI(options) {
6474
6504
  if (!setActiveReviewTarget) return;
6475
6505
  const layout = isPlainObject(handles.layout) ? handles.layout : void 0;
6476
6506
  safeCall(() => setActiveReviewTarget({
6477
- entityType: "trackedChange",
6507
+ entityType,
6478
6508
  entityId: next.id,
6479
6509
  ...next.paintedEntityId ? { paintedEntityId: next.paintedEntityId } : {},
6480
6510
  origin: "panel",
6481
6511
  layoutEpoch: typeof layout?.generation === "number" ? layout.generation : 0,
6482
- story: next.story ?? {
6512
+ ...entityType === "trackedChange" ? { story: next.story ?? {
6483
6513
  kind: "story",
6484
6514
  storyType: "body"
6485
- }
6515
+ } } : {}
6486
6516
  }), null);
6487
6517
  return;
6488
6518
  }
@@ -6490,10 +6520,22 @@ function createSuperDocUI(options) {
6490
6520
  const clearActiveReviewTarget = hostMethod(review, "clearActiveReviewTarget");
6491
6521
  if (!getActiveReviewTarget || !clearActiveReviewTarget) return;
6492
6522
  const activeTarget = safeCall(() => getActiveReviewTarget(), null);
6493
- if (isPlainObject(activeTarget) && activeTarget.entityType === "trackedChange") safeCall(() => clearActiveReviewTarget(), void 0);
6523
+ if (isPlainObject(activeTarget) && activeTarget.entityType === entityType) safeCall(() => clearActiveReviewTarget(), void 0);
6524
+ };
6525
+ const mirrorTrackedChangeFocusToHost = (next) => mirrorReviewFocusToHost("trackedChange", next);
6526
+ const readReviewFocus = () => ({
6527
+ change: explicitActiveChange,
6528
+ commentId: explicitActiveCommentId
6529
+ });
6530
+ const restoreReviewFocus = (previous) => {
6531
+ setExplicitActiveChange(previous.change, { invalidateQueuedNavigation: false });
6532
+ explicitActiveCommentId = previous.commentId;
6533
+ if (previous.commentId) mirrorReviewFocusToHost("comment", { id: previous.commentId });
6534
+ else mirrorTrackedChangeFocusToHost(previous.change);
6494
6535
  };
6495
6536
  const setExplicitActiveChange = (next, options) => {
6496
6537
  explicitActiveChange = next;
6538
+ if (next) explicitActiveCommentId = null;
6497
6539
  explicitActiveChangeRevision += 1;
6498
6540
  trackChangeRevealInvalidation += 1;
6499
6541
  if (options?.invalidateQueuedNavigation !== false) queuedTrackChangeNavigationInvalidation += 1;
@@ -7553,15 +7595,53 @@ function createSuperDocUI(options) {
7553
7595
  * Explicit whole-table changes have a complete local source range and also
7554
7596
  * seed clears, so a delayed worker read cannot retain a cleared table target.
7555
7597
  */
7556
- const refreshSelectionFromHost = (hostSelectionSnapshot, afterTypingPaint = false, wholeTableSelectionChanged = false) => {
7598
+ function captureWholeTableParagraphSeed(host, seed, nativeSelection) {
7599
+ const editor = getEditor();
7600
+ const rawLifetime = safeCall(() => hostMethod(host, "getForegroundInteractionLifetime")?.() ?? null, null);
7601
+ const lifetime = hasAbortedFlag(rawLifetime) ? rawLifetime : null;
7602
+ const mount = safeCall(() => hostMethod(host, "getMountedHandle")?.() ?? null, null);
7603
+ const mode = readDocumentMode();
7604
+ if (!host || !editor || !mount || !lifetime || lifetime.aborted || mode === null || seed.empty || seed.tableSelection?.kind !== "tableCells" || seed.tableSelection.type !== "table" || seed.target?.kind !== "text" || !seed.target.segments.length) return false;
7605
+ if (nativeSelection) {
7606
+ const presentation = safeCall(() => hostMethod(host, "readAppliedSelectionPresentation")?.(), null);
7607
+ const applied = normalizeSelectionInfo(isPlainObject(presentation) ? presentation.selection : null);
7608
+ if (readHostSelectionSnapshot() !== nativeSelection || !isNonCollapsedHostSelection(nativeSelection) || !applied || selectionSignature(selectionSliceFromInfo(applied, "ready")) !== selectionSignature(selectionSliceFromInfo(seed, "ready")) || JSON.stringify(applied.tableSelection) !== JSON.stringify(seed.tableSelection)) return false;
7609
+ const snapshot = nativeSelection;
7610
+ const first = seed.target.segments[0]?.blockId, last = seed.target.segments.at(-1)?.blockId;
7611
+ const matches = (point, blockId) => !!blockId && isPlainObject(point) && point.blockId === blockId && storyLocatorSignature(point.story ?? snapshot.story) === storyLocatorSignature(seed.target?.story);
7612
+ if (!(matches(snapshot.anchor, first) && matches(snapshot.focus, last) || matches(snapshot.focus, first) && matches(snapshot.anchor, last))) return false;
7613
+ }
7614
+ wholeTableParagraphSeed = {
7615
+ editor,
7616
+ host,
7617
+ doc: editor.doc,
7618
+ mount,
7619
+ lifetime,
7620
+ sourceToken: contentToken(),
7621
+ mode,
7622
+ info: seed,
7623
+ nativeSelection
7624
+ };
7625
+ return true;
7626
+ }
7627
+ const refreshSelectionFromHost = (hostSelectionSnapshot, afterTypingPaint = false, wholeTableSelectionChanged = false, initialTableSelection = null) => {
7628
+ const host = getHost();
7557
7629
  const foreground = foregroundMutationState();
7630
+ if (isNonCollapsedHostSelection(hostSelectionSnapshot) && readHostSelectionSnapshot() === hostSelectionSnapshot) {
7631
+ const seed = normalizeSelectionInfo(safeCall(() => hostMethod(host, "readLiveSelectionSyncSnapshot")?.(), null));
7632
+ if (seed && !seed.selectionTarget && captureWholeTableParagraphSeed(host, seed, hostSelectionSnapshot)) asyncReads.set("selection", {
7633
+ token: asyncReads.get("selection")?.token ?? null,
7634
+ value: seed,
7635
+ hasSettled: true,
7636
+ inflightToken: null
7637
+ });
7638
+ }
7558
7639
  if (isNonCollapsedHostSelection(hostSelectionSnapshot) && !(foreground && foreground.active > 0)) {
7559
7640
  typedCaretValidationHeld = false;
7560
7641
  pendingSelectionSeedValidationToken = null;
7561
7642
  issueAsyncRead("selection", selectionReadToken(), selectionCurrentRun, normalizeSelectionInfo);
7562
7643
  return;
7563
7644
  }
7564
- const host = getHost();
7565
7645
  const read = host?.readLiveSelectionSyncSnapshot;
7566
7646
  if (typeof read !== "function") return;
7567
7647
  const seed = normalizeSelectionInfo(safeCall(() => read.call(host), null));
@@ -7570,21 +7650,7 @@ function createSuperDocUI(options) {
7570
7650
  typedCaretValidationHeld = false;
7571
7651
  pendingSelectionSeedValidationToken = null;
7572
7652
  wholeTableParagraphSeed = null;
7573
- const editor = getEditor();
7574
- const rawLifetime = safeCall(() => hostMethod(host, "getForegroundInteractionLifetime")?.() ?? null, null);
7575
- const lifetime = hasAbortedFlag(rawLifetime) ? rawLifetime : null;
7576
- const mount = safeCall(() => hostMethod(host, "getMountedHandle")?.() ?? null, null);
7577
- const mode = readDocumentMode();
7578
- if (host && editor && mount && lifetime && !lifetime.aborted && mode !== null && !seed.empty && seed.tableSelection?.kind === "tableCells" && seed.tableSelection.type === "table" && seed.target?.segments.length && seed.selectionTarget) wholeTableParagraphSeed = {
7579
- editor,
7580
- host,
7581
- doc: editor.doc,
7582
- mount,
7583
- lifetime,
7584
- sourceToken: contentToken(),
7585
- mode,
7586
- info: seed
7587
- };
7653
+ captureWholeTableParagraphSeed(host, seed, initialTableSelection);
7588
7654
  const token = selectionReadToken();
7589
7655
  asyncReads.set("selection", {
7590
7656
  token,
@@ -7742,7 +7808,7 @@ function createSuperDocUI(options) {
7742
7808
  const directory = asyncReads.get("comments");
7743
7809
  const directoryItems = directory?.token === contentToken() && directory.hasSettled && Array.isArray(directory.value) ? directory.value : null;
7744
7810
  const activeValidationItems = directoryLeaseCounts.comments > 0 ? directoryItems : items;
7745
- if (explicitActiveCommentId && activeValidationItems && !activeValidationItems.some((item) => readEntityId(item) === explicitActiveCommentId)) explicitActiveCommentId = null;
7811
+ if (explicitActiveCommentId && activeValidationItems && (activeValidationItems !== items || listStatus === "ready")) explicitActiveCommentId = resolveActiveCommentId(activeValidationItems, explicitActiveCommentId);
7746
7812
  const activeId = explicitActiveCommentId ?? activeIds[0] ?? null;
7747
7813
  return {
7748
7814
  status: combineStatus(listStatus, selection.status),
@@ -8387,12 +8453,13 @@ function createSuperDocUI(options) {
8387
8453
  ];
8388
8454
  function effectiveInlineUniformityInput(selection) {
8389
8455
  const cellTarget = currentCellSelectionTarget();
8390
- const target = cellTarget ?? selection.selectionTarget;
8456
+ const paintedTarget = cellTarget ? null : currentUnrepresentableSelectionTarget(selection);
8457
+ const target = cellTarget ?? paintedTarget ?? selection.selectionTarget;
8391
8458
  if (!target) return null;
8392
8459
  const tableCellSelection = cellTarget ? tableCellSelectionForTarget(cellTarget) : null;
8393
8460
  return {
8394
8461
  target,
8395
- offsetSpace: "selection",
8462
+ offsetSpace: cellTarget || paintedTarget ? "selection" : "kernel",
8396
8463
  ...tableCellSelection ? { tableCellSelection } : {}
8397
8464
  };
8398
8465
  }
@@ -8530,7 +8597,11 @@ function createSuperDocUI(options) {
8530
8597
  const getNodeById = hostMethod(doc, "getNodeById");
8531
8598
  const raw = await Promise.resolve(getNode ? getNode(address) : getNodeById?.({
8532
8599
  nodeId: address["nodeId"],
8533
- nodeType: address["nodeType"]
8600
+ nodeType: address["nodeType"],
8601
+ story: address["story"] ?? {
8602
+ kind: "story",
8603
+ storyType: "body"
8604
+ }
8534
8605
  }));
8535
8606
  return isPlainObject(raw) ? raw : void 0;
8536
8607
  };
@@ -9076,7 +9147,8 @@ function createSuperDocUI(options) {
9076
9147
  const token = contentToken();
9077
9148
  const read = readAsync(key, token, () => isBodyStory ? doc.getNodeById({
9078
9149
  nodeId: blockId,
9079
- nodeType: "paragraph"
9150
+ nodeType: "paragraph",
9151
+ story
9080
9152
  }) : doc.getNode(paragraphTarget(blockId, story)), (raw) => raw && typeof raw === "object" ? raw : null);
9081
9153
  const entry = asyncReads.get(key);
9082
9154
  return {
@@ -9285,7 +9357,11 @@ function createSuperDocUI(options) {
9285
9357
  const isBodyStory = !story || story.storyType === "body";
9286
9358
  const raw = story && typeof doc.getNode === "function" ? await doc.getNode(paragraphTarget(blockId, story)) : isBodyStory && typeof doc.getNodeById === "function" ? await doc.getNodeById({
9287
9359
  nodeId: blockId,
9288
- nodeType: "paragraph"
9360
+ nodeType: "paragraph",
9361
+ story: {
9362
+ kind: "story",
9363
+ storyType: "body"
9364
+ }
9289
9365
  }) : null;
9290
9366
  return readParagraphIndentationFromResult(raw && typeof raw === "object" ? raw : null);
9291
9367
  } catch {
@@ -9962,14 +10038,15 @@ function createSuperDocUI(options) {
9962
10038
  const tableSelection = isPlainObject(handles) ? handles.selection : void 0;
9963
10039
  const subscribeWholeTable = hostMethod(tableSelection, "subscribeWholeTable");
9964
10040
  if (subscribeWholeTable) {
9965
- const refreshWholeTable = () => {
10041
+ const refreshWholeTable = (initial = false) => {
10042
+ const native = initial ? readHostSelectionSnapshot() : null;
9966
10043
  selectionEpoch += 1;
9967
- refreshSelectionFromHost(void 0, false, true);
10044
+ refreshSelectionFromHost(void 0, false, true, native);
9968
10045
  recompute("host-selection");
9969
10046
  };
9970
- const detach = subscribeWholeTable(refreshWholeTable);
10047
+ const detach = subscribeWholeTable(() => refreshWholeTable());
9971
10048
  if (typeof detach === "function") unsubscribeCallbacks.push(() => detach());
9972
- if (hostMethod(tableSelection, "getWholeTableNodeId")?.()) refreshWholeTable();
10049
+ if (hostMethod(tableSelection, "getWholeTableNodeId")?.()) refreshWholeTable(true);
9973
10050
  }
9974
10051
  } catch {
9975
10052
  currentHostSelectionSource = null;
@@ -9990,10 +10067,35 @@ function createSuperDocUI(options) {
9990
10067
  };
9991
10068
  let trackedChangeNavigationInFlight = 0;
9992
10069
  let lastPublishedHostReviewTarget;
10070
+ const isRepublishedHostReviewTarget = (left, right) => left === right || isPlainObject(left) && isPlainObject(right) && left.layoutEpoch !== right.layoutEpoch && left.origin === right.origin && left.entityType === right.entityType && left.entityId === right.entityId && left.paintedEntityId === right.paintedEntityId && storyLocatorSignature(left.story) === storyLocatorSignature(right.story);
9993
10071
  const syncTrackedChangeFocusFromHostReviewTarget = (rawTarget, rawSnapshot) => {
9994
10072
  const rejection = (rawSnapshot && typeof rawSnapshot === "object" ? rawSnapshot : null)?.lastInteractionRejection;
9995
- if (rawTarget == null && rejection && typeof rejection === "object" && rejection.code === "review-target-invalidated" && rejection.detail === "not-painted" && explicitActiveChange) return false;
10073
+ if (rawTarget == null && rejection && typeof rejection === "object" && rejection.code === "review-target-invalidated" && rejection.detail === "not-painted" && (explicitActiveChange || explicitActiveCommentId)) return false;
9996
10074
  const target = rawTarget && typeof rawTarget === "object" ? rawTarget : null;
10075
+ if (target?.entityType === "comment" && typeof target.entityId === "string" && target.entityId.length > 0) {
10076
+ const commentId = resolveActiveCommentId(state?.comments.items ?? [], target.entityId) ?? target.entityId;
10077
+ const republished = isRepublishedHostReviewTarget(rawTarget, lastPublishedHostReviewTarget?.raw);
10078
+ lastPublishedHostReviewTarget = {
10079
+ raw: rawTarget,
10080
+ focus: null,
10081
+ origin: target.origin
10082
+ };
10083
+ if (republished || explicitActiveCommentId === commentId && !explicitActiveChange) return false;
10084
+ setExplicitActiveChange(null);
10085
+ explicitActiveCommentId = commentId;
10086
+ return true;
10087
+ }
10088
+ const previousTarget = lastPublishedHostReviewTarget?.raw;
10089
+ if (!target && isPlainObject(previousTarget) && previousTarget.entityType === "comment") {
10090
+ lastPublishedHostReviewTarget = {
10091
+ raw: rawTarget,
10092
+ focus: null,
10093
+ origin: void 0
10094
+ };
10095
+ if (!explicitActiveCommentId) return false;
10096
+ explicitActiveCommentId = null;
10097
+ return true;
10098
+ }
9997
10099
  let next = null;
9998
10100
  if (target?.entityType === "trackedChange" && typeof target.entityId === "string" && target.entityId.length > 0) {
9999
10101
  const targetStory = target.story && typeof target.story === "object" ? target.story : void 0;
@@ -10018,7 +10120,7 @@ function createSuperDocUI(options) {
10018
10120
  }
10019
10121
  if (rawTarget == null && next == null && explicitActiveChange != null && hasPendingTrackChangeRevealFocus(explicitActiveChange)) return false;
10020
10122
  const previousPublication = lastPublishedHostReviewTarget;
10021
- if (previousPublication && (rawTarget === previousPublication.raw || target?.origin === "panel" && previousPublication.origin === "panel" && next && previousPublication.focus && explicitActiveChangesEqual(previousPublication.focus, next))) return false;
10123
+ if (previousPublication && (isRepublishedHostReviewTarget(rawTarget, previousPublication.raw) || target?.origin === "panel" && previousPublication.origin === "panel" && next && previousPublication.focus && explicitActiveChangesEqual(previousPublication.focus, next))) return false;
10022
10124
  lastPublishedHostReviewTarget = {
10023
10125
  raw: rawTarget,
10024
10126
  focus: next,
@@ -10396,11 +10498,16 @@ function createSuperDocUI(options) {
10396
10498
  * ordinary/insertion selections are visible-space, while a selection that
10397
10499
  * reaches painted deleted text is projected in tracked space by the host.
10398
10500
  *
10399
- * Cross-block coverage must come from the model-backed tracked target;
10400
- * intermediate block lengths cannot be inferred from the endpoints.
10501
+ * Cross-block and object-containing coverage use the model-backed tracked
10502
+ * target when the explicit text selection cannot represent the range.
10401
10503
  */
10402
10504
  function trackDecisionRangeFromSelection(selection) {
10403
10505
  if (selection.empty) return null;
10506
+ if (!selection.selectionTarget && selection.trackedTarget) return {
10507
+ kind: "range",
10508
+ coordinateSpace: "tracked",
10509
+ range: selection.trackedTarget
10510
+ };
10404
10511
  const explicit = selection.selectionTarget;
10405
10512
  const start = explicit?.start;
10406
10513
  const end = explicit?.end;
@@ -10554,7 +10661,7 @@ function createSuperDocUI(options) {
10554
10661
  kind: "blocked",
10555
10662
  reason: SUPERDOC_UI_REASONS.operationUnavailable
10556
10663
  };
10557
- if (!selection.selectionTarget) return null;
10664
+ if (!selection.selectionTarget && !selection.trackedTarget && selection.target?.kind !== "text") return null;
10558
10665
  const target = trackDecisionRangeFromSelection(selection);
10559
10666
  if (!target && route() !== "id") return {
10560
10667
  kind: "blocked",
@@ -10894,25 +11001,35 @@ function createSuperDocUI(options) {
10894
11001
  pendingCommandSettlement = settledResults.length ? Promise.all(settledResults).then((results) => combineCommandResults(results)) : null;
10895
11002
  return combineCommandResults(immediateResults);
10896
11003
  };
10897
- /**
10898
- * Call an inline `format.*` operation against the live selection. Mirrors
10899
- * `callEditorMutation` (suggesting mode adds `changeMode: 'tracked'`), and
10900
- * when the target is the host's own editable `selectionTarget` it also
10901
- * carries the PRIVATE V2 option `offsetSpace: 'selection'` (browser
10902
- * selection offsets count an inline object as one caret position). The
10903
- * option is not part of public `MutationOptions`; this is the same cast
10904
- * pattern the delete/replace callers use.
10905
- */
10906
11004
  function currentCellSelectionTarget() {
11005
+ return captureInputSelectionTarget(true);
11006
+ }
11007
+ function captureInputSelectionTarget(requireTableCells = false) {
10907
11008
  const handles = safeCall(() => hostMethod(getHost(), "getHandles")?.(), null);
10908
11009
  const selection = isPlainObject(handles) ? handles.selection : void 0;
10909
11010
  const held = hostMethod(selection, "getSnapshot")?.();
10910
- if (!isPlainObject(held) || !held.tableCells) return null;
11011
+ if (requireTableCells && (!isPlainObject(held) || !held.tableCells)) return null;
10911
11012
  const resolved = hostMethod(selection, "toSelectionTarget")?.();
10912
11013
  return isPlainObject(resolved) && resolved.kind === "ok" && resolved.mode === "range" ? capturedTableCellTarget(resolved.target) : null;
10913
11014
  }
11015
+ function currentUnrepresentableSelectionTarget(selection) {
11016
+ if (selection.empty || selection.selectionTarget || selection.target?.kind !== "text") return null;
11017
+ return heldPaintedTextSelectionTarget();
11018
+ }
11019
+ function heldPaintedTextSelectionTarget() {
11020
+ const target = captureInputSelectionTarget();
11021
+ if (target) paintedInlineTargets.add(target);
11022
+ return target;
11023
+ }
10914
11024
  function resolveInlineCommandTarget(selection) {
10915
- return currentCellSelectionTarget() ?? resolveInlineSelectionTarget(selection);
11025
+ const cells = currentCellSelectionTarget();
11026
+ if (cells) return cells;
11027
+ if (!selection.empty && !selection.selectionTarget && selection.target?.kind === "text") {
11028
+ const snapshot = readHostSelectionSnapshot();
11029
+ const painted = currentUnrepresentableSelectionTarget(selection);
11030
+ if (painted) return snapshot && appliedSelectionPresentation(selection) && readHostSelectionSnapshot() === snapshot ? painted : null;
11031
+ }
11032
+ return resolveInlineSelectionTarget(selection);
10916
11033
  }
10917
11034
  function tableCellSelectionForTarget(target) {
10918
11035
  const cellTarget = currentCellSelectionTarget();
@@ -10932,10 +11049,15 @@ function createSuperDocUI(options) {
10932
11049
  offsetSpace: "selection",
10933
11050
  tableCellSelection
10934
11051
  });
11052
+ const paintedTarget = input?.target;
11053
+ if (paintedTarget !== null && typeof paintedTarget === "object" && (paintedInlineTargets.has(paintedTarget) || paintedTarget.coordinateSpace === "tracked")) return op(input, {
11054
+ ...options,
11055
+ offsetSpace: "selection"
11056
+ });
10935
11057
  if (!selection.selectionTarget) return options ? op(input, options) : op(input);
10936
11058
  return op(input, {
10937
11059
  ...options,
10938
- offsetSpace: "selection"
11060
+ offsetSpace: "kernel"
10939
11061
  });
10940
11062
  };
10941
11063
  /** Build the `format.paragraph.*` / `styles.paragraph.*` input for a block. */
@@ -11164,13 +11286,16 @@ function createSuperDocUI(options) {
11164
11286
  };
11165
11287
  pendingCommandSettlement = (async () => {
11166
11288
  const plans = await Promise.all(blockIds.map((blockId) => planBlock(blockId).catch(() => null)));
11167
- if (plans.length === blockIds.length && plans.every((plan) => plan?.kind === "list") && readDocumentMode() !== "suggesting") {
11289
+ const allListItems = plans.length === blockIds.length && plans.every((plan) => plan?.kind === "list");
11290
+ const preserveFirstItemLevel = blockIds.length === 1 && story.storyType === "body";
11291
+ if (allListItems && (readDocumentMode() !== "suggesting" || preserveFirstItemLevel)) {
11168
11292
  const rangeRoute = mode === "indent" ? "lists.indentRange" : "lists.outdentRange";
11169
11293
  const rangeOp = resolveDocOperation(doc, rangeRoute);
11170
11294
  if (rangeOp) try {
11171
11295
  return await settleOperationResult(callEditorMutation(rangeRoute, rangeOp, {
11172
11296
  paraIds: blockIds,
11173
- story
11297
+ story,
11298
+ ...preserveFirstItemLevel ? { preserveFirstItemLevel: true } : {}
11174
11299
  })).settled;
11175
11300
  } catch {
11176
11301
  return false;
@@ -11710,23 +11835,32 @@ function createSuperDocUI(options) {
11710
11835
  const record = readLinkPayloadRecord(payload);
11711
11836
  return Boolean(readLinkPayloadTarget(payload) || isPlainObject(record.hyperlinkTarget) || isPlainObject(record.textTarget));
11712
11837
  };
11713
- const currentWholeTableParagraphSelection = () => {
11838
+ const currentWholeTableParagraphOwner = () => {
11714
11839
  const owner = wholeTableParagraphSeed, host = getHost();
11715
- if (!owner || disposed || owner.lifetime.aborted || owner.host !== host || owner.editor !== getEditor() || owner.editor.doc !== owner.doc || owner.sourceToken !== contentToken() || owner.mode !== readDocumentMode() || safeCall(() => hostMethod(host, "getMountedHandle")?.(), null) !== owner.mount) return false;
11840
+ if (!owner || disposed || owner.lifetime.aborted || owner.host !== host || owner.editor !== getEditor() || owner.editor.doc !== owner.doc || owner.sourceToken !== contentToken() || owner.mode !== readDocumentMode() || safeCall(() => hostMethod(host, "getMountedHandle")?.(), null) !== owner.mount) return null;
11716
11841
  const tableId = owner.info.tableSelection?.tableId;
11717
11842
  const activeTable = safeCall(() => {
11718
11843
  const handles = hostMethod(host, "getHandles")?.();
11719
11844
  return hostMethod(isPlainObject(handles) ? handles.selection : void 0, "getWholeTableNodeId")?.();
11720
11845
  }, null);
11721
- if (!tableId || activeTable !== tableId || state.selection.tableSelection?.type !== "table" || state.selection.tableSelection.tableId !== tableId || selectionSignature(state.selection) !== selectionSignature(selectionSliceFromInfo(owner.info, "ready"))) return false;
11846
+ if (!tableId || activeTable !== tableId || state.selection.tableSelection?.type !== "table" || state.selection.tableSelection.tableId !== tableId || selectionSignature(state.selection) !== selectionSignature(selectionSliceFromInfo(owner.info, "ready"))) return null;
11722
11847
  const applied = appliedSelectionPresentation(state.selection);
11723
11848
  const info = normalizeSelectionInfo(applied?.selection);
11724
- return !!info && selectionInfoMatchesHost(info) && JSON.stringify(info.tableSelection) === JSON.stringify(owner.info.tableSelection);
11849
+ return info && JSON.stringify(info.tableSelection) === JSON.stringify(owner.info.tableSelection) ? {
11850
+ owner,
11851
+ info
11852
+ } : null;
11853
+ };
11854
+ const currentWholeTableParagraphSelection = () => {
11855
+ const current = currentWholeTableParagraphOwner();
11856
+ if (!current) return false;
11857
+ return current.owner.info.selectionTarget ? selectionInfoMatchesHost(current.info) : current.owner.nativeSelection !== null && readHostSelectionSnapshot() === current.owner.nativeSelection;
11725
11858
  };
11726
11859
  const commandSelectionIsReady = (id, descriptor, payload) => {
11727
11860
  const foreground = foregroundMutationState();
11728
11861
  if (foreground && (foreground.active > 0 || foreground.pending > 0)) return false;
11729
11862
  if (descriptor?.blockParagraph && currentWholeTableParagraphSelection()) return true;
11863
+ if (descriptor?.blockParagraph && state.selection.tableSelection?.type === "table" && !state.selection.selectionTarget) return false;
11730
11864
  if (state.selection.status !== "ready") return false;
11731
11865
  if (trackDecisionCommand(id)?.scope === "id") return true;
11732
11866
  if (!descriptor || descriptor.disposition !== "routed") return true;
@@ -12147,10 +12281,23 @@ function createSuperDocUI(options) {
12147
12281
  const cellTarget = currentCellSelectionTarget();
12148
12282
  const tableCellSelection = tableCellSelectionForTarget(cellTarget);
12149
12283
  const rawSelection = typeof selectionApi?.["current"] === "function" ? await Promise.resolve(selectionApi["current"]({ includeText: true })) : readSelectionInfoLive().value;
12284
+ const selection = normalizeSelectionInfo(rawSelection);
12285
+ let paintedTarget = null;
12286
+ if (selection && !selection.empty && !selection.selectionTarget && selection.target?.kind === "text" && !cellTarget) {
12287
+ const nativeSelection = readHostSelectionSnapshot();
12288
+ paintedTarget = heldPaintedTextSelectionTarget();
12289
+ if (paintedTarget && (!nativeSelection || !appliedSelectionPresentation(selectionSliceFromInfo(selection, "ready")) || readHostSelectionSnapshot() !== nativeSelection)) return {
12290
+ selection: null,
12291
+ cellTarget: null,
12292
+ tableCellSelection: null,
12293
+ paintedTarget: null
12294
+ };
12295
+ }
12150
12296
  return {
12151
- selection: normalizeSelectionInfo(rawSelection),
12297
+ selection,
12152
12298
  cellTarget,
12153
- tableCellSelection
12299
+ tableCellSelection,
12300
+ paintedTarget
12154
12301
  };
12155
12302
  };
12156
12303
  const waitForSelectionSettle = () => new Promise((resolve) => setTimeout(resolve, 16));
@@ -12168,7 +12315,7 @@ function createSuperDocUI(options) {
12168
12315
  break;
12169
12316
  }
12170
12317
  if (selectionRead?.selection && !selectionRead.selection.empty && key !== painter.sourceSelectionKey) {
12171
- await applyFormatPainter(selectionRead.selection, selectionRead.cellTarget, selectionRead.tableCellSelection);
12318
+ await applyFormatPainter(selectionRead.selection, selectionRead.cellTarget, selectionRead.tableCellSelection, selectionRead.paintedTarget);
12172
12319
  return;
12173
12320
  }
12174
12321
  if (painter.snapshot?.paragraph) {
@@ -12176,10 +12323,10 @@ function createSuperDocUI(options) {
12176
12323
  const caretSel = caretRead.selection;
12177
12324
  if (!caretSel) return;
12178
12325
  if (selectionKey(caretSel) === painter.sourceSelectionKey) return;
12179
- await applyFormatPainter(caretSel, caretRead.cellTarget, caretRead.tableCellSelection);
12326
+ await applyFormatPainter(caretSel, caretRead.cellTarget, caretRead.tableCellSelection, caretRead.paintedTarget);
12180
12327
  }
12181
12328
  };
12182
- const applyFormatPainter = async (selection, cellTarget, tableCellSelection) => {
12329
+ const applyFormatPainter = async (selection, cellTarget, tableCellSelection, paintedTarget) => {
12183
12330
  const snap = painter.snapshot;
12184
12331
  if (!snap) {
12185
12332
  if (painter.mode === "armed") exitFormatPainter();
@@ -12197,7 +12344,7 @@ function createSuperDocUI(options) {
12197
12344
  return;
12198
12345
  }
12199
12346
  const selTarget = selection.target;
12200
- const selectionTarget = cellTarget ?? selection.selectionTarget;
12347
+ const selectionTarget = cellTarget ?? paintedTarget ?? selection.selectionTarget;
12201
12348
  const selectionIsEmpty = selection.empty === true;
12202
12349
  const segments = Array.isArray(selTarget?.segments) ? selTarget.segments : [];
12203
12350
  const targetStory = selTarget?.story;
@@ -12435,13 +12582,16 @@ function createSuperDocUI(options) {
12435
12582
  const snapshot = selectionSub.get();
12436
12583
  if (snapshot.empty) return null;
12437
12584
  const cells = currentCellSelectionTarget();
12585
+ const painted = cells ? null : currentUnrepresentableSelectionTarget(snapshot);
12586
+ const hostSelection = selectionHostSnapshots.get(snapshot);
12587
+ if ((cells || painted || hostSelection && !snapshot.selectionTarget) && (!hostSelection || readHostSelectionSnapshot() !== hostSelection)) return null;
12438
12588
  if (!snapshot.target && !snapshot.selectionTarget && !cells) return null;
12439
12589
  const capture = {
12440
12590
  ...snapshot,
12441
12591
  capturedAt: Date.now(),
12442
- ...cells ? { _tableCellSelectionTarget: cells } : {}
12592
+ ...cells ? { _tableCellSelectionTarget: cells } : {},
12593
+ ...painted ? { _paintedSelectionTarget: painted } : {}
12443
12594
  };
12444
- const hostSelection = selectionHostSnapshots.get(snapshot);
12445
12595
  if (hostSelection) selectionHostSnapshots.set(capture, hostSelection);
12446
12596
  return capture;
12447
12597
  },
@@ -12467,7 +12617,13 @@ function createSuperDocUI(options) {
12467
12617
  ok: false,
12468
12618
  reason: SUPERDOC_UI_REASONS.targetUnresolved
12469
12619
  });
12470
- const target = cellTarget ?? selectionTargetForRestore(capture);
12620
+ const hasPaintedTarget = "_paintedSelectionTarget" in capture;
12621
+ const paintedTarget = hasPaintedTarget ? capturedTableCellTarget(capture._paintedSelectionTarget) : null;
12622
+ if (hasPaintedTarget && !paintedTarget) return done({
12623
+ ok: false,
12624
+ reason: SUPERDOC_UI_REASONS.targetUnresolved
12625
+ });
12626
+ const target = cellTarget ?? paintedTarget ?? selectionTargetForRestore(capture);
12471
12627
  if (!target) return done({
12472
12628
  ok: false,
12473
12629
  reason: SUPERDOC_UI_REASONS.targetUnresolved
@@ -12480,7 +12636,7 @@ function createSuperDocUI(options) {
12480
12636
  reason: resolved.reason
12481
12637
  });
12482
12638
  try {
12483
- return done(normalizeHostSelectionApplyResult(hostMethod(resolved.helper, "apply")?.(target, ...cellTarget ? [{ tableCells: true }] : [])));
12639
+ return done(normalizeHostSelectionApplyResult(hostMethod(resolved.helper, "apply")?.(target, ...cellTarget ? [{ tableCells: true }] : paintedTarget ? [{ offsetSpace: "selection" }] : [])));
12484
12640
  } catch {
12485
12641
  return done({
12486
12642
  ok: false,
@@ -12899,6 +13055,10 @@ function createSuperDocUI(options) {
12899
13055
  };
12900
13056
  const commentsSub = sliceHandle((s) => s.comments);
12901
13057
  const commentsSnap = directorySnapshotHandle(commentsSub, select((s) => computeCommentsDirectorySnapshot(s.selection, s.comments)), "comments");
13058
+ documentResetHooks.push(() => {
13059
+ setExplicitActiveChange(null);
13060
+ explicitActiveCommentId = null;
13061
+ });
12902
13062
  const comments = {
12903
13063
  get: commentsSnap.get,
12904
13064
  getSnapshot: commentsSnap.getSnapshot,
@@ -12992,6 +13152,7 @@ function createSuperDocUI(options) {
12992
13152
  setActive: (commentId) => {
12993
13153
  if (commentId == null) {
12994
13154
  explicitActiveCommentId = null;
13155
+ mirrorReviewFocusToHost("comment", null);
12995
13156
  recompute();
12996
13157
  return true;
12997
13158
  }
@@ -13004,7 +13165,9 @@ function createSuperDocUI(options) {
13004
13165
  const resolvedId = resolveActiveCommentId(items, commentId);
13005
13166
  if (!resolvedId) return false;
13006
13167
  if (!items.some((entry) => readEntityId(entry) === resolvedId)) return false;
13168
+ setExplicitActiveChange(null);
13007
13169
  explicitActiveCommentId = resolvedId;
13170
+ mirrorReviewFocusToHost("comment", { id: resolvedId });
13008
13171
  recompute();
13009
13172
  return true;
13010
13173
  },
@@ -13064,7 +13227,7 @@ function createSuperDocUI(options) {
13064
13227
  };
13065
13228
  const navigateAndScrollCurrentRequest = async (step, requestedAtInvalidation) => {
13066
13229
  if (queuedTrackChangeNavigationInvalidation !== requestedAtInvalidation) return { success: false };
13067
- const previousActiveChange = explicitActiveChange;
13230
+ const previousFocus = readReviewFocus();
13068
13231
  const activeId = navigateTrackChange(step, { invalidateQueuedNavigation: false });
13069
13232
  if (activeId == null) return { success: false };
13070
13233
  const optimisticActiveChange = explicitActiveChange;
@@ -13072,7 +13235,7 @@ function createSuperDocUI(options) {
13072
13235
  const isCurrent = () => queuedTrackChangeNavigationInvalidation === requestedAtInvalidation && explicitActiveChange === optimisticActiveChange && explicitActiveChangeRevision === optimisticRevision;
13073
13236
  const rollback = () => {
13074
13237
  if (!isCurrent()) return;
13075
- setExplicitActiveChange(previousActiveChange, { invalidateQueuedNavigation: false });
13238
+ restoreReviewFocus(previousFocus);
13076
13239
  recompute();
13077
13240
  };
13078
13241
  if (optimisticActiveChange) pendingTrackChangeRevealFocuses.add(optimisticActiveChange);
@@ -13092,10 +13255,8 @@ function createSuperDocUI(options) {
13092
13255
  shouldContinue: isCurrent
13093
13256
  });
13094
13257
  if (!isCurrent()) return { success: false };
13095
- if (!result.ok && result.reason !== SUPERDOC_UI_REASONS.targetNotVisible) {
13096
- rollback();
13097
- mirrorTrackedChangeFocusToHost(previousActiveChange);
13098
- } else {
13258
+ if (!result.ok && result.reason !== SUPERDOC_UI_REASONS.targetNotVisible) rollback();
13259
+ else {
13099
13260
  if (result.ok) await restampVisibleTrackedChangeCarrierAliases(activeId);
13100
13261
  if (!isCurrent()) return { success: false };
13101
13262
  mirrorTrackedChangeFocusToHost(optimisticActiveChange);
@@ -13226,7 +13387,7 @@ function createSuperDocUI(options) {
13226
13387
  const matchingRow = matchStory ? loadedItems.find((item) => entityRowMatchesRequest(item, publicId, matchStory)) : loadedItems.find((item) => readEntityId(item) === publicId);
13227
13388
  const story = matchStory ? requestedStory : readEntityRequestStory(matchingRow);
13228
13389
  const importedId = deriveTrackedChangeImportedId(matchingRow);
13229
- const previousActiveChange = explicitActiveChange;
13390
+ const previousFocus = readReviewFocus();
13230
13391
  const requestedActiveChange = {
13231
13392
  id: publicId,
13232
13393
  ...story ? { story } : {},
@@ -13254,7 +13415,7 @@ function createSuperDocUI(options) {
13254
13415
  const isCurrent = () => trackChangeRevealInvalidation === revealInvalidation && explicitActiveChange === optimisticActiveChange && explicitActiveChangeRevision === optimisticRevision;
13255
13416
  const rollback = () => {
13256
13417
  if (!isCurrent()) return;
13257
- setExplicitActiveChange(previousActiveChange, { invalidateQueuedNavigation: false });
13418
+ restoreReviewFocus(previousFocus);
13258
13419
  recompute();
13259
13420
  };
13260
13421
  recompute();
@@ -13268,10 +13429,8 @@ function createSuperDocUI(options) {
13268
13429
  success: false,
13269
13430
  ok: false
13270
13431
  };
13271
- if (!result.ok && result.reason !== SUPERDOC_UI_REASONS.targetNotVisible) {
13272
- rollback();
13273
- mirrorTrackedChangeFocusToHost(previousActiveChange);
13274
- } else {
13432
+ if (!result.ok && result.reason !== SUPERDOC_UI_REASONS.targetNotVisible) rollback();
13433
+ else {
13275
13434
  if (result.ok) await restampVisibleTrackedChangeCarrierAliases(publicId);
13276
13435
  if (!isCurrent()) return {
13277
13436
  success: false,
@@ -13606,14 +13765,14 @@ function createSuperDocUI(options) {
13606
13765
  if (root instanceof HTMLElement) return root;
13607
13766
  return null;
13608
13767
  };
13609
- const pointEntityHits = (x, y) => {
13610
- if (typeof x !== "number" || typeof y !== "number" || !Number.isFinite(x) || !Number.isFinite(y)) return [];
13768
+ const pointHitElements = (x, y) => {
13769
+ if (typeof x !== "number" || typeof y !== "number" || !Number.isFinite(x) || !Number.isFinite(y)) return null;
13611
13770
  const dom = globalThis.document;
13612
- if (!dom || typeof dom.elementFromPoint !== "function") return [];
13771
+ if (!dom || typeof dom.elementFromPoint !== "function") return null;
13613
13772
  const container = resolveVisibleHost();
13614
- if (!container) return [];
13773
+ if (!container) return null;
13615
13774
  const startEl = dom.elementFromPoint(x, y);
13616
- if (!startEl || !container.contains(startEl)) return [];
13775
+ if (!startEl || !container.contains(startEl)) return null;
13617
13776
  const primaryEls = [startEl];
13618
13777
  const addPrimaryEl = (el) => {
13619
13778
  if (el && container.contains(el) && !primaryEls.includes(el)) primaryEls.push(el);
@@ -13628,6 +13787,17 @@ function createSuperDocUI(options) {
13628
13787
  }
13629
13788
  for (const el of stacked) addPrimaryEl(el);
13630
13789
  }
13790
+ return {
13791
+ container,
13792
+ elements: primaryEls,
13793
+ x,
13794
+ y
13795
+ };
13796
+ };
13797
+ const pointEntityHits = (x, y) => {
13798
+ const point = pointHitElements(x, y);
13799
+ if (!point) return [];
13800
+ const { container, elements: primaryEls } = point;
13631
13801
  const markerEls = [];
13632
13802
  const addMarkerEl = (el) => {
13633
13803
  if (el && container.contains(el) && !markerEls.includes(el) && !primaryEls.includes(el)) markerEls.push(el);
@@ -13641,7 +13811,7 @@ function createSuperDocUI(options) {
13641
13811
  } catch {
13642
13812
  return false;
13643
13813
  }
13644
- return rect != null && x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
13814
+ return rect != null && point.x >= rect.left && point.x <= rect.right && point.y >= rect.top && point.y <= rect.bottom;
13645
13815
  };
13646
13816
  for (const el of primaryEls) {
13647
13817
  const queryAll = el.querySelectorAll;
@@ -13665,6 +13835,18 @@ function createSuperDocUI(options) {
13665
13835
  const seen = /* @__PURE__ */ new Set();
13666
13836
  const hits = [];
13667
13837
  for (const hit of rawHits) {
13838
+ if (hit.type === "decoration") {
13839
+ const owner = decorationOwnerForProvider(hit.id);
13840
+ if (!owner) continue;
13841
+ const key = `decoration:${owner.id}`;
13842
+ if (seen.has(key)) continue;
13843
+ seen.add(key);
13844
+ hits.push({
13845
+ type: "decoration",
13846
+ id: owner.id
13847
+ });
13848
+ continue;
13849
+ }
13668
13850
  if (hit.type !== "trackedChange") {
13669
13851
  const layoutStory = hit.type === "contentControl" ? layoutStoryDatasetToStoryLocator(hit.story) : void 0;
13670
13852
  const story = hit.type === "contentControl" ? isTextboxStory(layoutStory) ? layoutStory : storyKeyToStoryLocator(hit.storyKey) ?? layoutStory : void 0;
@@ -13820,6 +14002,149 @@ function createSuperDocUI(options) {
13820
14002
  if (status !== "ready") return { reason: "not-ready" };
13821
14003
  return value ? { target: value } : { reason: "unresolved" };
13822
14004
  };
14005
+ const liveDecorations = /* @__PURE__ */ new Map();
14006
+ const decorationOwnerForProvider = (providerId) => {
14007
+ const owner = currentHostVisuals()?.providerOwner?.(providerId);
14008
+ return isPlainObject(owner) && typeof owner.id === "string" ? owner : null;
14009
+ };
14010
+ const currentHostVisuals = () => getEditor()?.visuals ?? null;
14011
+ const decorationListeners = /* @__PURE__ */ new Set();
14012
+ let detachDecorationPointer = null;
14013
+ let hoveredDecorations = [];
14014
+ let lastPointer = {
14015
+ clientX: 0,
14016
+ clientY: 0
14017
+ };
14018
+ const decorationIdsAt = (x, y) => {
14019
+ const point = pointHitElements(x, y);
14020
+ if (!point) return [];
14021
+ const ids = /* @__PURE__ */ new Set();
14022
+ for (const element of point.elements) for (const provider of collectDecorationProvidersFromChain(element, point.container)) {
14023
+ const owner = decorationOwnerForProvider(provider);
14024
+ if (owner) ids.add(owner.id);
14025
+ }
14026
+ return [...ids];
14027
+ };
14028
+ const decorationData = (id) => {
14029
+ const owner = currentHostVisuals()?.ownerById?.(id);
14030
+ return isPlainObject(owner?.data) ? owner.data : void 0;
14031
+ };
14032
+ const emitDecorationEvent = (type, decoration, point) => {
14033
+ const payload = {
14034
+ type,
14035
+ id: decoration.id,
14036
+ ...decoration.data ? { data: decoration.data } : {},
14037
+ clientX: point.clientX,
14038
+ clientY: point.clientY
14039
+ };
14040
+ for (const listener of Array.from(decorationListeners)) safeCall(() => listener(payload), void 0);
14041
+ };
14042
+ const leaveHoveredDecorations = (onlyId) => {
14043
+ const leaving = hoveredDecorations.filter((decoration) => onlyId === void 0 || decoration.id === onlyId);
14044
+ hoveredDecorations = hoveredDecorations.filter((decoration) => !leaving.includes(decoration));
14045
+ for (const decoration of leaving) emitDecorationEvent("hover-leave", decoration, lastPointer);
14046
+ };
14047
+ const attachDecorationPointer = () => {
14048
+ if (detachDecorationPointer || decorationListeners.size === 0) return;
14049
+ const host = resolveVisibleHost();
14050
+ if (!host) return;
14051
+ const onClick = (event) => {
14052
+ for (const id of decorationIdsAt(event.clientX, event.clientY)) emitDecorationEvent("click", {
14053
+ id,
14054
+ data: decorationData(id)
14055
+ }, event);
14056
+ };
14057
+ const onMove = (event) => {
14058
+ lastPointer = {
14059
+ clientX: event.clientX,
14060
+ clientY: event.clientY
14061
+ };
14062
+ const nextIds = decorationIdsAt(event.clientX, event.clientY);
14063
+ const leaving = hoveredDecorations.filter((decoration) => !nextIds.includes(decoration.id));
14064
+ const kept = hoveredDecorations.filter((decoration) => nextIds.includes(decoration.id));
14065
+ const entering = nextIds.filter((id) => !kept.some((decoration) => decoration.id === id)).map((id) => ({
14066
+ id,
14067
+ data: decorationData(id)
14068
+ }));
14069
+ hoveredDecorations = [...kept, ...entering];
14070
+ for (const decoration of leaving) emitDecorationEvent("hover-leave", decoration, event);
14071
+ for (const decoration of entering) emitDecorationEvent("hover-enter", decoration, event);
14072
+ };
14073
+ const onLeave = (event) => {
14074
+ lastPointer = {
14075
+ clientX: event.clientX,
14076
+ clientY: event.clientY
14077
+ };
14078
+ leaveHoveredDecorations();
14079
+ };
14080
+ host.addEventListener("click", onClick);
14081
+ host.addEventListener("pointermove", onMove);
14082
+ host.addEventListener("pointerleave", onLeave);
14083
+ detachDecorationPointer = () => {
14084
+ host.removeEventListener("click", onClick);
14085
+ host.removeEventListener("pointermove", onMove);
14086
+ host.removeEventListener("pointerleave", onLeave);
14087
+ leaveHoveredDecorations();
14088
+ detachDecorationPointer = null;
14089
+ };
14090
+ };
14091
+ const subscribeDecorationPointer = (listener) => {
14092
+ if (disposed) throw new Error("ui.decorations.subscribe called on a destroyed UI controller.");
14093
+ decorationListeners.add(listener);
14094
+ attachDecorationPointer();
14095
+ return () => {
14096
+ decorationListeners.delete(listener);
14097
+ if (decorationListeners.size === 0) detachDecorationPointer?.();
14098
+ };
14099
+ };
14100
+ documentResetHooks.push(() => {
14101
+ leaveHoveredDecorations();
14102
+ const current = currentHostVisuals();
14103
+ for (const [handle, owner] of Array.from(liveDecorations)) if (owner.visuals !== current || owner.generation !== current.generation()) handle.dispose();
14104
+ });
14105
+ activeEditorResetHooks.push(() => {
14106
+ detachDecorationPointer?.();
14107
+ attachDecorationPointer();
14108
+ });
14109
+ const createDecorationsHandle = (track) => ({
14110
+ subscribe: (listener) => {
14111
+ const unsubscribe = subscribeDecorationPointer(listener);
14112
+ track?.({ dispose: unsubscribe });
14113
+ return unsubscribe;
14114
+ },
14115
+ highlight: (id, options) => {
14116
+ if (disposed) throw new Error("ui.decorations.highlight called on a destroyed UI controller.");
14117
+ const visuals = currentHostVisuals();
14118
+ if (!visuals) throw new Error("ui.decorations.highlight requires a ready document.");
14119
+ const inner = visuals.highlight(id, options);
14120
+ let handleDisposed = false;
14121
+ const handle = {
14122
+ id: inner.id,
14123
+ replace: (targets) => inner.replace(targets),
14124
+ add: (targets) => inner.add(targets),
14125
+ clear: () => {
14126
+ if (handleDisposed) return;
14127
+ inner.clear();
14128
+ leaveHoveredDecorations(inner.id);
14129
+ },
14130
+ invalidate: () => inner.invalidate(),
14131
+ dispose: () => {
14132
+ if (handleDisposed) return;
14133
+ handleDisposed = true;
14134
+ liveDecorations.delete(handle);
14135
+ inner.dispose();
14136
+ leaveHoveredDecorations(inner.id);
14137
+ }
14138
+ };
14139
+ liveDecorations.set(handle, {
14140
+ visuals,
14141
+ generation: visuals.generation()
14142
+ });
14143
+ track?.(handle);
14144
+ return handle;
14145
+ }
14146
+ });
14147
+ const decorations = createDecorationsHandle();
13823
14148
  const viewport = {
13824
14149
  getRects: async (input) => {
13825
14150
  const initialHost = getHost();
@@ -14911,6 +15236,7 @@ function createSuperDocUI(options) {
14911
15236
  const createScope = () => {
14912
15237
  const scopeUnsubs = [];
14913
15238
  return {
15239
+ decorations: createDecorationsHandle((handle) => scopeUnsubs.push(() => handle.dispose())),
14914
15240
  select: (selector, equality) => {
14915
15241
  const sub = select(selector, equality);
14916
15242
  return {
@@ -14931,6 +15257,9 @@ function createSuperDocUI(options) {
14931
15257
  if (disposed) return;
14932
15258
  watermark.destroy();
14933
15259
  clearContentControlHighlight();
15260
+ for (const handle of Array.from(liveDecorations.keys())) handle.dispose();
15261
+ detachDecorationPointer?.();
15262
+ decorationListeners.clear();
14934
15263
  disposed = true;
14935
15264
  completedFallbackSearch = null;
14936
15265
  if (searchRefreshTimer !== null) clearTimeout(searchRefreshTimer);
@@ -15013,6 +15342,7 @@ function createSuperDocUI(options) {
15013
15342
  zoom,
15014
15343
  document: documentHandle,
15015
15344
  viewport,
15345
+ decorations,
15016
15346
  metadata,
15017
15347
  tables,
15018
15348
  search,
@@ -15303,6 +15633,12 @@ Object.defineProperty(exports, "isPlainObject", {
15303
15633
  return isPlainObject;
15304
15634
  }
15305
15635
  });
15636
+ Object.defineProperty(exports, "isRecord", {
15637
+ enumerable: true,
15638
+ get: function() {
15639
+ return isRecord;
15640
+ }
15641
+ });
15306
15642
  Object.defineProperty(exports, "isSyntheticTrackedChangeCommentLaneItem", {
15307
15643
  enumerable: true,
15308
15644
  get: function() {