superdoc 2.28.0-next.1 → 2.28.0-next.10

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