superdoc 2.29.0-next.9 → 2.30.0-next.13

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 (177) hide show
  1. package/README.md +127 -25
  2. package/dist/chunks/{FindReplaceSurface-BKVHzhQ4.es.js → FindReplaceSurface-BEnRLpD9.es.js} +1 -1
  3. package/dist/chunks/{FindReplaceSurface-BNDrJ6VS.cjs → FindReplaceSurface-DWXgWgg7.cjs} +1 -1
  4. package/dist/chunks/{PasswordPromptSurface-6mF6Azda.es.js → PasswordPromptSurface-DjV1JgjD.es.js} +1 -1
  5. package/dist/chunks/{PasswordPromptSurface-BNkaztaP.cjs → PasswordPromptSurface-VFPnQOML.cjs} +1 -1
  6. package/dist/chunks/{PdfViewer-aOWhUVI7.es.js → PdfViewer-C2r-K8zC.es.js} +4 -10
  7. package/dist/chunks/{PdfViewer-DmSkZehl.cjs → PdfViewer-DYTbPVUr.cjs} +4 -10
  8. package/dist/chunks/{_plugin-vue_export-helper-6MBoygIY.cjs → _plugin-vue_export-helper-BzM2_tVU.cjs} +40 -7
  9. package/dist/chunks/{_plugin-vue_export-helper-CsJQtMvr.es.js → _plugin-vue_export-helper-CmkaJDnr.es.js} +23 -8
  10. package/dist/chunks/{create-super-doc-ui-Bm5kmx0W.es.js → create-super-doc-ui--o8TcYYv.es.js} +3322 -2729
  11. package/dist/chunks/{create-super-doc-ui-DN70aatf.cjs → create-super-doc-ui-CvK1JdZv.cjs} +3447 -2854
  12. package/dist/collaboration-upgrade-engine.cjs +1 -1
  13. package/dist/collaboration-upgrade-engine.es.js +1 -1
  14. package/dist/document-api/src/changes/changes.d.ts +26 -0
  15. package/dist/document-api/src/changes/changes.types.d.ts +156 -0
  16. package/dist/document-api/src/comments/comment-create-attribution.d.ts +4 -0
  17. package/dist/document-api/src/comments/comments.types.d.ts +4 -0
  18. package/dist/document-api/src/contract/command-catalog.d.ts +1 -1
  19. package/dist/document-api/src/contract/operation-definitions.d.ts +76 -8
  20. package/dist/document-api/src/contract/operation-registry.d.ts +2 -2
  21. package/dist/document-api/src/contract/schemas.d.ts +7 -1
  22. package/dist/document-api/src/create/create.d.ts +0 -4
  23. package/dist/document-api/src/diff/diff.types.d.ts +6 -6
  24. package/dist/document-api/src/header-footers/header-footers.d.ts +1 -3
  25. package/dist/document-api/src/index.d.ts +13 -1
  26. package/dist/document-api/src/inline-semantics/token-sets.d.ts +0 -2
  27. package/dist/document-api/src/insert/insert.d.ts +0 -1
  28. package/dist/document-api/src/paragraphs/paragraphs.d.ts +8 -3
  29. package/dist/document-api/src/paragraphs/paragraphs.types.d.ts +49 -5
  30. package/dist/document-api/src/tables/data/table-preset-appearance.generated.d.ts +2 -0
  31. package/dist/document-api/src/tables/table-style-presets.d.ts +73 -0
  32. package/dist/document-api/src/tables/tables.d.ts +4 -1
  33. package/dist/document-api/src/types/sd-props.d.ts +7 -0
  34. package/dist/document-api/src/types/table-operations.types.d.ts +12 -15
  35. package/dist/layout-engine/contracts/src/index.d.ts +4 -1
  36. package/dist/layout-engine/contracts/src/table-cell-vector-overlay.d.ts +2 -0
  37. package/dist/layout-engine/dom-contract/src/data-attrs.d.ts +4 -0
  38. package/dist/layout-engine/layout-bridge/src/coupled-footnote-pagination.d.ts +1 -0
  39. package/dist/layout-engine/layout-bridge/src/footnote-page-certificate.d.ts +1 -0
  40. package/dist/layout-engine/layout-bridge/src/footnote-page-planner.d.ts +5 -0
  41. package/dist/layout-engine/layout-bridge/src/incrementalLayout.d.ts +1 -0
  42. package/dist/layout-engine/layout-engine/src/footnote-page-flow.d.ts +3 -0
  43. package/dist/layout-engine/layout-engine/src/index.d.ts +7 -0
  44. package/dist/layout-engine/layout-engine/src/layout-paragraph.d.ts +5 -0
  45. package/dist/layout-engine/layout-engine/src/layout-table.d.ts +3 -1
  46. package/dist/layout-engine/layout-engine/src/layout-utils.d.ts +0 -4
  47. package/dist/layout-engine/layout-engine/src/paginator.d.ts +4 -0
  48. package/dist/layout-engine/painters/dom/src/index.d.ts +2 -0
  49. package/dist/layout-engine/painters/dom/src/paragraph/list-marker.d.ts +1 -1
  50. package/dist/layout-engine/painters/dom/src/paragraph/renderParagraphContent.d.ts +1 -1
  51. package/dist/layout-engine/painters/dom/src/styles.d.ts +0 -10
  52. package/dist/layout-engine/painters/dom/src/svg-utils.d.ts +1 -13
  53. package/dist/public/ui-react.cjs +1 -1
  54. package/dist/public/ui-react.es.js +1 -1
  55. package/dist/public/ui-vue.cjs +1 -1
  56. package/dist/public/ui-vue.es.js +1 -1
  57. package/dist/public/ui.cjs +1 -1
  58. package/dist/public/ui.es.js +1 -1
  59. package/dist/shared/common/Telemetry.d.ts +50 -0
  60. package/dist/shared/common/event-types.d.ts +13 -0
  61. package/dist/shared/font-system/src/bundled-face-names.d.ts +1 -0
  62. package/dist/shared/font-system/src/resolver.d.ts +1 -0
  63. package/dist/style.css +1 -1
  64. package/dist/style.layered.css +1 -1
  65. package/dist/superdoc/src/components/CommentsLayer/CommentDialog.vue.d.ts +17 -33
  66. package/dist/superdoc/src/components/CommentsLayer/collect-removed-comment-ids.d.ts +11 -1
  67. package/dist/superdoc/src/components/CommentsLayer/collect-tracked-change-thread.d.ts +25 -6
  68. package/dist/superdoc/src/components/CommentsLayer/comment-params.d.ts +84 -0
  69. package/dist/superdoc/src/components/CommentsLayer/comment-urls.d.ts +1 -1
  70. package/dist/superdoc/src/components/CommentsLayer/commentsList/super-comments-list.d.ts +23 -20
  71. package/dist/superdoc/src/components/CommentsLayer/helpers.d.ts +1 -4
  72. package/dist/superdoc/src/components/CommentsLayer/tracked-change-threading.d.ts +16 -1
  73. package/dist/superdoc/src/components/CommentsLayer/use-comment.d.ts +15 -95
  74. package/dist/superdoc/src/components/Whiteboard/WhiteboardLayer.vue.d.ts +4 -5
  75. package/dist/superdoc/src/components/Whiteboard/WhiteboardPage.vue.d.ts +3 -4
  76. package/dist/superdoc/src/components/Whiteboard/use-whiteboard.d.ts +30 -9
  77. package/dist/superdoc/src/components/Whiteboard/whiteboard-types.d.ts +2 -0
  78. package/dist/superdoc/src/components/surfaces/watermark-surface-runtime.d.ts +7 -7
  79. package/dist/superdoc/src/composables/use-comment-sidebar.d.ts +6 -0
  80. package/dist/superdoc/src/composables/use-comment-small-screen.d.ts +11 -7
  81. package/dist/superdoc/src/composables/use-compact-comment-popover.d.ts +44 -18
  82. package/dist/superdoc/src/composables/use-document.d.ts +51 -99
  83. package/dist/superdoc/src/composables/use-find-replace.d.ts +13 -3
  84. package/dist/superdoc/src/composables/use-password-prompt.d.ts +1 -1
  85. package/dist/superdoc/src/composables/use-viewport-fit.d.ts +62 -26
  86. package/dist/superdoc/src/core/SuperDoc.d.ts +15 -6
  87. package/dist/superdoc/src/core/collaboration/cdn-collaboration-comment-map.d.ts +1 -0
  88. package/dist/superdoc/src/core/collaboration/collaboration-comment-map.d.ts +2 -0
  89. package/dist/superdoc/src/core/collaboration/helpers.d.ts +1 -1
  90. package/dist/superdoc/src/core/config/normalize-interaction-config.d.ts +10 -1
  91. package/dist/superdoc/src/core/document-open-telemetry.d.ts +1 -1
  92. package/dist/superdoc/src/core/editor-runtime/types.d.ts +4 -0
  93. package/dist/superdoc/src/core/editor-runtime/v2/v2-editor-runtime-adapter.d.ts +1 -0
  94. package/dist/superdoc/src/core/pdf/pdf-adapter.d.ts +6 -2
  95. package/dist/superdoc/src/core/types/index.d.ts +147 -43
  96. package/dist/superdoc/src/core/v2-integration/browser-peer-runtime.d.ts +5 -3
  97. package/dist/superdoc/src/core/v2-integration/cdn-engine-loader.d.ts +3 -13
  98. package/dist/superdoc/src/core/v2-integration/internal-vue-runtime.d.ts +64 -59
  99. package/dist/superdoc/src/helpers/deprecation.d.ts +12 -3
  100. package/dist/superdoc/src/helpers/object-guards.d.ts +2 -0
  101. package/dist/superdoc/src/helpers/v2-author-required-rejection.d.ts +8 -11
  102. package/dist/superdoc/src/helpers/v2-bulk-decision-event.d.ts +9 -7
  103. package/dist/superdoc/src/helpers/v2-keyboard-edit-rejection.d.ts +8 -7
  104. package/dist/superdoc/src/helpers/v2-mutation-exception.d.ts +12 -8
  105. package/dist/superdoc/src/helpers/v2-review-geometry-delivery.d.ts +22 -0
  106. package/dist/superdoc/src/helpers/v2-review-target.d.ts +11 -1
  107. package/dist/superdoc/src/helpers/v2-selection-sync.d.ts +40 -14
  108. package/dist/superdoc/src/helpers/v2-typing-mutation-event.d.ts +14 -5
  109. package/dist/superdoc/src/icons.d.ts +11 -19
  110. package/dist/superdoc/src/internal/diagnostics/translate-diagnostic.d.ts +6 -4
  111. package/dist/superdoc/src/internal/toolbar/built-in/PageNumberMenu.vue.d.ts +25 -15
  112. package/dist/superdoc/src/internal/toolbar/built-in/ToolbarButton.vue.d.ts +1 -0
  113. package/dist/superdoc/src/internal/toolbar/built-in/constants.d.ts +3 -1
  114. package/dist/superdoc/src/internal/toolbar/built-in/font-options.d.ts +15 -1
  115. package/dist/superdoc/src/internal/toolbar/built-in/font-typeahead.d.ts +37 -7
  116. package/dist/superdoc/src/internal/toolbar/built-in/general.d.ts +12 -1
  117. package/dist/superdoc/src/internal/toolbar/built-in/helpers.d.ts +9 -4
  118. package/dist/superdoc/src/internal/toolbar/built-in/list-style-buttons.d.ts +2 -2
  119. package/dist/superdoc/src/internal/toolbar/built-in/page-number-feedback.d.ts +4 -5
  120. package/dist/superdoc/src/internal/toolbar/built-in/scroll-helpers.d.ts +3 -4
  121. package/dist/superdoc/src/internal/toolbar/built-in/toolbarIcons.d.ts +78 -145
  122. package/dist/superdoc/src/internal/toolbar/built-in/url-safety.d.ts +11 -3
  123. package/dist/superdoc/src/internal/toolbar/built-in-toolbar.d.ts +4 -3
  124. package/dist/superdoc/src/internal/toolbar/image-upload.d.ts +29 -39
  125. package/dist/superdoc/src/public/content-control-ref.d.ts +12 -0
  126. package/dist/superdoc/src/public/index.d.cts +20 -0
  127. package/dist/superdoc/src/public/index.d.ts +4 -1
  128. package/dist/superdoc/src/public/ui/async-read-cache.d.ts +32 -0
  129. package/dist/superdoc/src/public/ui/block-commands.d.ts +39 -0
  130. package/dist/superdoc/src/public/ui/command-results.d.ts +39 -0
  131. package/dist/superdoc/src/public/ui/commands.d.ts +5 -1
  132. package/dist/superdoc/src/public/ui/create-super-doc-ui.d.ts +1 -17
  133. package/dist/superdoc/src/public/ui/document-change-records.d.ts +7 -0
  134. package/dist/superdoc/src/public/ui/entity-rows.d.ts +14 -0
  135. package/dist/superdoc/src/public/ui/font-options.d.ts +7 -0
  136. package/dist/superdoc/src/public/ui/format-painter-helpers.d.ts +2 -8
  137. package/dist/superdoc/src/public/ui/format-painter-paragraph.d.ts +7 -0
  138. package/dist/superdoc/src/public/ui/format-painter-state.d.ts +29 -0
  139. package/dist/superdoc/src/public/ui/heavy-read-policy.d.ts +11 -0
  140. package/dist/superdoc/src/public/ui/host-events.d.ts +3 -0
  141. package/dist/superdoc/src/public/ui/inline-values.d.ts +80 -0
  142. package/dist/superdoc/src/public/ui/loose-record.d.ts +4 -0
  143. package/dist/superdoc/src/public/ui/reasons.d.ts +1 -0
  144. package/dist/superdoc/src/public/ui/selection-targets.d.ts +53 -0
  145. package/dist/superdoc/src/public/ui/shared-tracked-changes-catalog.d.ts +16 -0
  146. package/dist/superdoc/src/public/ui/tracked-change-ids.d.ts +51 -0
  147. package/dist/superdoc/src/public/ui/types.d.ts +25 -2
  148. package/dist/superdoc/src/public/ui/ui-bench-timing.d.ts +9 -0
  149. package/dist/superdoc/src/public/ui.d.cts +18 -0
  150. package/dist/superdoc/src/public/ui.d.ts +1 -1
  151. package/dist/superdoc/src/stores/comments-store.d.ts +23028 -552
  152. package/dist/superdoc/src/stores/helpers/comment-document-scope.d.ts +22 -0
  153. package/dist/superdoc/src/stores/helpers/comment-grouping.d.ts +1 -1
  154. package/dist/superdoc/src/stores/helpers/comment-identity.d.ts +10 -10
  155. package/dist/superdoc/src/stores/helpers/pending-selection-snapshot.d.ts +17 -0
  156. package/dist/superdoc/src/stores/helpers/review-presentation.d.ts +91 -0
  157. package/dist/superdoc/src/stores/helpers/tracked-change-compaction.d.ts +16 -0
  158. package/dist/superdoc/src/stores/helpers/tracked-change-decision.d.ts +45 -0
  159. package/dist/superdoc/src/stores/helpers/tracked-change-identity.d.ts +4 -2
  160. package/dist/superdoc/src/stores/helpers/tracked-change-row-values.d.ts +1 -8
  161. package/dist/superdoc/src/stores/helpers/tracked-change-thread-index.d.ts +1 -1
  162. package/dist/superdoc/src/stores/helpers/v2-review-adapters.d.ts +147 -0
  163. package/dist/superdoc/src/stores/superdoc-store.d.ts +12 -2895
  164. package/dist/superdoc.cjs +1330 -972
  165. package/dist/superdoc.es.js +1242 -884
  166. package/dist/word-layout/src/marker-utils.d.ts +0 -5
  167. package/dist/word-layout/src/unit-conversions.d.ts +0 -1
  168. package/dist-cdn/style.layered.css +1 -1
  169. package/dist-cdn/superdoc.min.css +1 -1
  170. package/dist-cdn/superdoc.min.js +38 -42
  171. package/package.json +13 -3
  172. package/dist/document-api/src/diff/index.d.ts +0 -3
  173. package/dist/document-api/src/write/locator.d.ts +0 -24
  174. package/dist/layout-engine/painters/dom/src/features/feature-registry.d.ts +0 -41
  175. package/dist/superdoc/src/internal/toolbar/built-in/format-negation.d.ts +0 -5
  176. package/dist/superdoc/src/main.d.ts +0 -1
  177. package/dist/word-layout/src/tab-layout.d.ts +0 -193
@@ -1,20 +1,20 @@
1
1
  import { i as __toESM$2 } from "./chunks/rolldown-runtime-0pSA04fp.es.js";
2
+ import { $ as vModelSelect, A as onActivated, B as renderList, C as isRef$1, D as normalizeClass, E as nextTick, F as openBlock, G as toDisplayString, H as resolveDirective, I as provide$1, J as toRefs, K as toRaw$2, L as reactive, M as onDeactivated, N as onMounted, O as normalizeProps, P as onScopeDispose$1, Q as vModelCheckbox, R as readonly, S as isReactive$1, T as mergeProps, U as resolveDynamicComponent, V as renderSlot, W as shallowRef$1, X as useAttrs, Y as unref, Z as useSlots, _ as getCurrentInstance, a as computed$1, at as withModifiers, b as hasInjectionContext, c as createCommentVNode, ct as isThenable$1, d as createStaticVNode, et as vModelText, f as createTextVNode, g as effectScope, h as defineComponent, i as Transition, it as withKeys, j as onBeforeUnmount, k as normalizeStyle, l as createElementBlock, m as defineAsyncComponent, n as Fragment, nt as withCtx, o as createApp, p as createVNode, q as toRef, r as Teleport, rt as withDirectives, s as createBlock, st as isPlainObject$1, t as _plugin_vue_export_helper_default, tt as watch$1, u as createElementVNode, v as getCurrentScope, w as markRaw, x as inject$1, y as h, z as ref } from "./chunks/_plugin-vue_export-helper-CmkaJDnr.es.js";
2
3
  import { t as blank_default } from "./chunks/blank-docx-DzQccOlW.es.js";
3
- import { C as createInteractionHistory, E as recordInteraction, S as closeInteractionHistory, T as isWorkflowRecordingEnabled, _ as createTrackedChangePermissionResolver, a as registerFirstPartyCommandMutation, b as isPlainObject$1, c as getCommandDescriptor, d as mutationCommittedEventChangedDocument, f as createImageFilePicker, g as PERMISSIONS, h as BUILT_IN_PERMISSION_METHOD, i as readFirstPartyPageNumbers, l as applyV2CommentInvalidationFromMutation, n as executeFirstPartyCommandAsync, o as isRecord, p as resolveImageSrc, r as executeFirstPartyShortcutAsync, t as createSuperDocUI, u as getV2TrackedChangeMutationImpact, v as isAllowed, w as getWorkflowRecording, x as DOM_CLASS_NAMES, y as isSyntheticTrackedChangeCommentLaneItem } from "./chunks/create-super-doc-ui-Bm5kmx0W.es.js";
4
+ import { C as createInteractionHistory, E as recordInteraction, S as closeInteractionHistory, T as isWorkflowRecordingEnabled, _ as isAllowed, a as registerFirstPartyCommandMutation, b as isRecord, c as applyV2CommentInvalidationFromMutation, d as createImageFilePicker, f as resolveImageSrc, g as createTrackedChangePermissionResolver, h as PERMISSIONS, i as readFirstPartyPageNumbers, l as getV2TrackedChangeMutationImpact, m as BUILT_IN_PERMISSION_METHOD, n as executeFirstPartyCommandAsync, r as executeFirstPartyShortcutAsync, s as getCommandDescriptor, t as createSuperDocUI, u as mutationCommittedEventChangedDocument, v as isSyntheticTrackedChangeCommentLaneItem, w as getWorkflowRecording, x as DOM_CLASS_NAMES, y as isStoryLocator } from "./chunks/create-super-doc-ui--o8TcYYv.es.js";
4
5
  import { t as import_eventemitter3 } from "./chunks/eventemitter3-DEIiXiH2.es.js";
5
6
  import { t as v4 } from "./chunks/uuid-H0Xcmmhy.es.js";
6
- import { $ as vModelSelect, A as onActivated, B as renderList, C as isRef$1, D as normalizeClass, E as nextTick, F as openBlock, G as toDisplayString, H as resolveDirective, I as provide$1, J as toRefs, K as toRaw$2, L as reactive, M as onDeactivated, N as onMounted, O as normalizeProps, P as onScopeDispose$1, Q as vModelCheckbox, R as readonly, S as isReactive$1, T as mergeProps, U as resolveDynamicComponent, V as renderSlot, W as shallowRef$1, X as useAttrs, Y as unref, Z as useSlots, _ as getCurrentInstance, a as computed$1, at as withModifiers, b as hasInjectionContext, c as createCommentVNode, d as createStaticVNode, et as vModelText, f as createTextVNode, g as effectScope, h as defineComponent, i as Transition, it as withKeys, j as onBeforeUnmount, k as normalizeStyle, l as createElementBlock, m as defineAsyncComponent, n as Fragment, nt as withCtx, o as createApp, p as createVNode, q as toRef, r as Teleport, rt as withDirectives, s as createBlock, t as _plugin_vue_export_helper_default, tt as watch$1, u as createElementVNode, v as getCurrentScope, w as markRaw, x as inject$1, y as h, z as ref } from "./chunks/_plugin-vue_export-helper-CsJQtMvr.es.js";
7
7
  import { a as init_dist$1, i as global, n as init_dist$2, o as Buffer, r as process$1, s as init_dist, t as require_jszip_min } from "./chunks/jszip-BzJ3CyxR.es.js";
8
8
  import { t as isFiniteNumber } from "./chunks/is-finite-number-BpL4F38c.es.js";
9
9
  import { t as PDF_TO_CSS_UNITS } from "./chunks/constants-BvNs_ybI.es.js";
10
10
  import * as Vue from "vue";
11
11
  import { Map as Map$1 } from "yjs";
12
12
  import Konva from "konva";
13
- //#region src/core/v2-integration/browser-peer-runtime.js
13
+ //#region src/core/v2-integration/browser-peer-runtime.ts
14
14
  var SUPERDOC_V2_BROWSER_RUNTIME_GLOBAL_KEY = "__SUPERDOC_V2_BROWSER_RUNTIME__";
15
15
  function readInstalledRuntime(globalObject = globalThis) {
16
- const installed = globalObject[SUPERDOC_V2_BROWSER_RUNTIME_GLOBAL_KEY];
17
- return installed && typeof installed === "object" ? installed : null;
16
+ const installed = Reflect.get(globalObject, SUPERDOC_V2_BROWSER_RUNTIME_GLOBAL_KEY);
17
+ return isPlainObject$1(installed) ? installed : null;
18
18
  }
19
19
  function installSuperDocV2BrowserRuntime(runtime = {}, globalObject = globalThis) {
20
20
  const installed = readInstalledRuntime(globalObject) ?? {};
@@ -23,7 +23,7 @@ function installSuperDocV2BrowserRuntime(runtime = {}, globalObject = globalThis
23
23
  ...runtime,
24
24
  vue: runtime.vue ?? installed.vue ?? Vue
25
25
  };
26
- globalObject[SUPERDOC_V2_BROWSER_RUNTIME_GLOBAL_KEY] = nextRuntime;
26
+ Reflect.set(globalObject, SUPERDOC_V2_BROWSER_RUNTIME_GLOBAL_KEY, nextRuntime);
27
27
  return nextRuntime;
28
28
  }
29
29
  installSuperDocV2BrowserRuntime();
@@ -258,7 +258,7 @@ var v_click_outside_default = {
258
258
  var DEFAULT_ENDPOINT = "https://ingest.superdoc.dev/v1/collect";
259
259
  function getSuperdocVersion() {
260
260
  try {
261
- return "2.29.0-next.9";
261
+ return "2.30.0-next.13";
262
262
  } catch {
263
263
  return "unknown";
264
264
  }
@@ -5838,6 +5838,11 @@ function useCheckboxField(field) {
5838
5838
  }) : itemoptions) };
5839
5839
  }
5840
5840
  //#endregion
5841
+ //#region src/core/collaboration/collaboration-comment-map.ts
5842
+ function createCollaborationCommentMap(entries) {
5843
+ return new Map$1(entries);
5844
+ }
5845
+ //#endregion
5841
5846
  //#region src/core/collaboration/collaboration-comments.ts
5842
5847
  var readComment = (event) => {
5843
5848
  const { comment } = event;
@@ -5846,14 +5851,14 @@ var readComment = (event) => {
5846
5851
  };
5847
5852
  var addYComment = (yArray, ydoc, event, user) => {
5848
5853
  const comment = readComment(event);
5849
- const yComment = new Map$1(Object.entries(comment));
5854
+ const yComment = createCollaborationCommentMap(Object.entries(comment));
5850
5855
  ydoc.transact(() => {
5851
5856
  yArray.push([yComment]);
5852
5857
  }, { user });
5853
5858
  };
5854
5859
  var updateYComment = (yArray, ydoc, event, user) => {
5855
5860
  const comment = readComment(event);
5856
- const yComment = new Map$1(Object.entries(comment));
5861
+ const yComment = createCollaborationCommentMap(Object.entries(comment));
5857
5862
  const commentIndex = getCommentIndex(yArray, comment);
5858
5863
  if (commentIndex === -1) return;
5859
5864
  ydoc.transact(() => {
@@ -5874,7 +5879,7 @@ var getCommentIndex = (yArray, comment) => {
5874
5879
  //#endregion
5875
5880
  //#region src/core/collaboration/helpers.ts
5876
5881
  var isPropertyBag = (value) => typeof value === "object" && value !== null || typeof value === "function";
5877
- var readCommentsStore = (superdoc) => superdoc["commentsStore"];
5882
+ var readCommentsStore$1 = (superdoc) => superdoc["commentsStore"];
5878
5883
  var isV2ReviewRuntimeActive = (superdoc) => superdoc.activeEditor?.editorVersion === 2 || superdoc.editorVersion === 2;
5879
5884
  var commentKey = (comment) => {
5880
5885
  const key = comment.commentId ?? comment.importedId;
@@ -5893,7 +5898,7 @@ var preserveLocalV2TrackedChangeRows = (superdoc, commentsStore, incomingComment
5893
5898
  };
5894
5899
  var loadCommentsFromYdoc = (superdoc) => {
5895
5900
  if (!superdoc?.ydoc) return false;
5896
- const commentsStore = readCommentsStore(superdoc);
5901
+ const commentsStore = readCommentsStore$1(superdoc);
5897
5902
  if (!commentsStore) return false;
5898
5903
  const comments = superdoc.ydoc.getArray("comments").toJSON();
5899
5904
  const seenCommentIdByKey = /* @__PURE__ */ new Map();
@@ -5939,7 +5944,7 @@ var initCollaborationComments = (superdoc) => {
5939
5944
  const updateCommentsStore = () => loadCommentsFromYdoc(superdoc);
5940
5945
  const onSuperDocYdocSynced = () => {
5941
5946
  if (!updateCommentsStore()) setTimeout(updateCommentsStore, 0);
5942
- const commentsStore = readCommentsStore(superdoc);
5947
+ const commentsStore = readCommentsStore$1(superdoc);
5943
5948
  if (commentsStore) commentsStore.hasSyncedCollaborationComments = true;
5944
5949
  const currentProvider = superdoc.provider;
5945
5950
  if (!currentProvider) throw new TypeError("initCollaborationComments requires superdoc.provider");
@@ -5951,7 +5956,7 @@ var initCollaborationComments = (superdoc) => {
5951
5956
  provider.on("synced", onSuperDocYdocSynced);
5952
5957
  if (!updateCommentsStore()) setTimeout(updateCommentsStore, 0);
5953
5958
  commentsArray.observe((event) => {
5954
- if (!readCommentsStore(superdoc)) return;
5959
+ if (!readCommentsStore$1(superdoc)) return;
5955
5960
  const currentUser = superdoc.config.user;
5956
5961
  const origin = event?.transaction?.origin;
5957
5962
  const originUser = isPropertyBag(origin) ? origin.user : null;
@@ -6750,6 +6755,136 @@ var useSuperdocStore = defineStore("superdoc", () => {
6750
6755
  };
6751
6756
  });
6752
6757
  //#endregion
6758
+ //#region ../document-api/src/errors.ts
6759
+ /**
6760
+ * Structured validation error thrown by document-api execute* functions.
6761
+ *
6762
+ * Consumers should prefer checking `error.code` over `instanceof` for resilience
6763
+ * across package boundaries and bundling scenarios.
6764
+ */
6765
+ var DocumentApiValidationError = class DocumentApiValidationError extends Error {
6766
+ constructor(code, message, details) {
6767
+ super(message);
6768
+ this.name = "DocumentApiValidationError";
6769
+ this.code = code;
6770
+ this.details = details;
6771
+ Object.setPrototypeOf(this, DocumentApiValidationError.prototype);
6772
+ }
6773
+ };
6774
+ //#endregion
6775
+ //#region ../document-api/src/export/export.types.ts
6776
+ /** Canonical tracked-change export modes accepted by every public export surface. */
6777
+ var SD_EXPORT_MODES = [
6778
+ "review-preserving",
6779
+ "final",
6780
+ "original"
6781
+ ];
6782
+ /** Default mode for modeful DOCX export. */
6783
+ var DEFAULT_SD_EXPORT_MODE = "review-preserving";
6784
+ var SD_COMMENT_EXPORT_POLICIES = [
6785
+ "preserve",
6786
+ "resolve",
6787
+ "remove"
6788
+ ];
6789
+ var SD_CONTENT_CONTROL_EXPORT_POLICIES = ["preserve", "flatten"];
6790
+ //#endregion
6791
+ //#region ../document-api/src/export/export.ts
6792
+ /**
6793
+ * Export namespace: engine-agnostic public API and adapter contract.
6794
+ *
6795
+ * Validates the requested mode at the public boundary so an invalid mode fails
6796
+ * closed with `INVALID_INPUT` before any document work happens, then delegates
6797
+ * byte production and degradation reporting to the engine adapter.
6798
+ */
6799
+ function resolveMode$1(input) {
6800
+ if (input != null && !isRecord(input)) throw new DocumentApiValidationError("INVALID_INPUT", "export.toDocx input must be an object.");
6801
+ const raw = input?.mode;
6802
+ if (raw === void 0) return DEFAULT_SD_EXPORT_MODE;
6803
+ if (typeof raw !== "string" || !SD_EXPORT_MODES.includes(raw)) throw new DocumentApiValidationError("INVALID_INPUT", `export.toDocx mode must be one of: ${SD_EXPORT_MODES.join(", ")}.`, {
6804
+ mode: raw,
6805
+ allowedModes: [...SD_EXPORT_MODES]
6806
+ });
6807
+ return raw;
6808
+ }
6809
+ function resolveDocxExportPolicies(input = {}) {
6810
+ if (!isRecord(input)) throw new DocumentApiValidationError("INVALID_INPUT", "DOCX export input must be an object.");
6811
+ const mode = resolveMode$1(input);
6812
+ const policies = input.policies;
6813
+ if (policies !== void 0 && !isRecord(policies)) throw new DocumentApiValidationError("INVALID_INPUT", "DOCX export policies must be an object.");
6814
+ const allowed = {
6815
+ trackedChanges: SD_EXPORT_MODES,
6816
+ comments: SD_COMMENT_EXPORT_POLICIES,
6817
+ contentControls: SD_CONTENT_CONTROL_EXPORT_POLICIES
6818
+ };
6819
+ for (const [key, value] of Object.entries(policies ?? {})) if (!Object.prototype.hasOwnProperty.call(allowed, key) || value !== void 0 && !allowed[key].includes(value)) throw new DocumentApiValidationError("INVALID_INPUT", `Unsupported DOCX export policy: ${key}.`, {
6820
+ policy: key,
6821
+ value
6822
+ });
6823
+ const requested = policies;
6824
+ if (input.mode !== void 0 && requested?.trackedChanges !== void 0 && mode !== requested.trackedChanges) throw new DocumentApiValidationError("INVALID_INPUT", "mode conflicts with policies.trackedChanges.");
6825
+ return {
6826
+ trackedChanges: requested?.trackedChanges ?? mode,
6827
+ comments: requested?.comments ?? "preserve",
6828
+ contentControls: requested?.contentControls ?? "preserve"
6829
+ };
6830
+ }
6831
+ new TextEncoder();
6832
+ function normalizeCommentIdentity(value) {
6833
+ if (value === null) return null;
6834
+ if (typeof value !== "object" || Array.isArray(value) || !("providerId" in value) || !("userId" in value) || !xmlIdentityString(value.providerId) || !xmlIdentityString(value.userId)) throw new TypeError("commentIdentity requires XML-safe non-empty providerId and userId strings.");
6835
+ return {
6836
+ providerId: value.providerId,
6837
+ userId: value.userId
6838
+ };
6839
+ }
6840
+ function xmlIdentityString(value) {
6841
+ return typeof value === "string" && value.trim().length > 0 && !/[^\u0009\u000A\u000D\u0020-\uD7FF\uE000-\uFFFD\u{10000}-\u{10FFFF}]/u.test(value);
6842
+ }
6843
+ //#endregion
6844
+ //#region ../document-api/src/permission-ranges/permission-ranges.ts
6845
+ function editorKey(editor) {
6846
+ return editor.id?.trim() ? editor.id : editor.email?.trim() || void 0;
6847
+ }
6848
+ function editorMatches(editor, id) {
6849
+ return Boolean(id.trim()) && (editor.id === id || Boolean(editor.email?.trim() && editor.email.trim().toLowerCase() === id.toLowerCase()));
6850
+ }
6851
+ /**
6852
+ * Resolve a host ID or imported Word email against an explicitly supplied roster.
6853
+ * Word receives the email when available; opaque IDs remain host-specific.
6854
+ * Duplicate IDs/emails are ambiguous. This does not authenticate a user.
6855
+ */
6856
+ function resolvePermissionRangeEditor(editors, id) {
6857
+ const matches = editors.filter((editor) => editorMatches(editor, id));
6858
+ if (matches.length === 0) return {
6859
+ status: "unknown",
6860
+ id
6861
+ };
6862
+ if (matches.length !== 1) return {
6863
+ status: "ambiguous",
6864
+ id
6865
+ };
6866
+ const editor = matches[0];
6867
+ const wordId = editor.email?.trim() || editor.id;
6868
+ if (editors.filter((candidate) => editorMatches(candidate, wordId)).length !== 1) return {
6869
+ status: "ambiguous",
6870
+ id
6871
+ };
6872
+ return {
6873
+ status: "resolved",
6874
+ id,
6875
+ editor: { ...editor },
6876
+ principal: {
6877
+ kind: "editor",
6878
+ id: wordId
6879
+ }
6880
+ };
6881
+ }
6882
+ /** Enumerate the host roster for an editor picker, including ambiguous entries. */
6883
+ function listPermissionRangeEditors(editors) {
6884
+ const ids = new Set(editors.map(editorKey).filter((id) => id !== void 0));
6885
+ return Array.from(ids, (id) => resolvePermissionRangeEditor(editors, id));
6886
+ }
6887
+ //#endregion
6753
6888
  //#region src/stores/helpers/floating-comment-instances.ts
6754
6889
  var HEADER_FOOTER_TRACKED_CHANGE_STORY_TYPE = "headerFooterPart";
6755
6890
  var toFinitePageIndex = (value) => {
@@ -6890,23 +7025,76 @@ var buildFloatingCommentInstances = ({ comment, positionKey, positionEntry, fall
6890
7025
  })];
6891
7026
  };
6892
7027
  //#endregion
6893
- //#region src/components/CommentsLayer/tracked-change-threading.js
7028
+ //#region src/stores/helpers/comment-document-scope.ts
7029
+ var getCommentDocumentId = (comment) => {
7030
+ if (!comment) return null;
7031
+ if (comment.fileId != null) return String(comment.fileId);
7032
+ if (comment.documentId != null) return String(comment.documentId);
7033
+ if (comment.selection?.documentId != null) return String(comment.selection.documentId);
7034
+ return null;
7035
+ };
7036
+ var createCommentDocumentScope = (readOpenDocuments) => {
7037
+ const getOpenDocuments = () => {
7038
+ const source = readOpenDocuments();
7039
+ const docs = Array.isArray(source) ? source : source?.value;
7040
+ return Array.isArray(docs) ? docs : [];
7041
+ };
7042
+ const getSingleOpenDocumentId = () => {
7043
+ const docs = getOpenDocuments();
7044
+ if (docs.length !== 1) return null;
7045
+ return docs[0]?.id != null ? String(docs[0].id) : null;
7046
+ };
7047
+ const belongsToDocument = (comment, activeDocumentId, options = {}) => {
7048
+ const { allowSingleDocumentMismatch = false } = options;
7049
+ if (!activeDocumentId) return false;
7050
+ const commentDocumentId = getCommentDocumentId(comment);
7051
+ if (commentDocumentId) {
7052
+ if (commentDocumentId === activeDocumentId) return true;
7053
+ const singleOpenDocumentId = getSingleOpenDocumentId();
7054
+ return allowSingleDocumentMismatch && singleOpenDocumentId === activeDocumentId;
7055
+ }
7056
+ return getSingleOpenDocumentId() === activeDocumentId;
7057
+ };
7058
+ const belongsToTrackedChangeSyncDocument = (comment, activeDocumentId) => belongsToDocument(comment, activeDocumentId, { allowSingleDocumentMismatch: true });
7059
+ return {
7060
+ belongsToDocument,
7061
+ belongsToTrackedChangeSyncDocument
7062
+ };
7063
+ };
7064
+ //#endregion
7065
+ //#region src/stores/helpers/pending-selection-snapshot.ts
7066
+ var isThenable = (value) => (typeof value === "object" || typeof value === "function") && value !== null && "then" in value && typeof value.then === "function";
7067
+ var getPendingSelectionSnapshot = (superdoc, activeSelection) => {
7068
+ if (activeSelection?.source !== "document-editor") return null;
7069
+ const selectionDocumentId = activeSelection?.documentId ?? null;
7070
+ const editorDocumentId = superdoc?.activeEditor?.options?.documentId ?? null;
7071
+ if (!selectionDocumentId || !editorDocumentId || selectionDocumentId !== editorDocumentId) return null;
7072
+ let currentSelection;
7073
+ try {
7074
+ currentSelection = superdoc?.activeEditor?.doc?.selection?.current;
7075
+ } catch {
7076
+ return null;
7077
+ }
7078
+ if (typeof currentSelection !== "function") return null;
7079
+ try {
7080
+ const snapshot = currentSelection();
7081
+ return isThenable(snapshot) ? Promise.resolve(snapshot).catch(() => null) : snapshot;
7082
+ } catch {
7083
+ return null;
7084
+ }
7085
+ };
7086
+ //#endregion
7087
+ //#region src/components/CommentsLayer/tracked-change-threading.ts
6894
7088
  /**
6895
7089
  * Legacy comment payloads predate explicit tracked-change thread provenance.
6896
7090
  * These fields distinguish a true legacy review child from a v2 comment whose
6897
7091
  * anchor merely overlaps tracked content.
6898
- *
6899
- * @param {Record<string, any> | null | undefined} comment
6900
- * @returns {boolean}
6901
7092
  */
6902
7093
  var hasLegacyTrackedChangeThreadSignal = (comment) => Boolean(comment?.trackedChangeLink) || Boolean(comment?.trackedChangeType) || Boolean(comment?.trackedChangeDisplayType) || Boolean(comment?.trackedChangeText) || Boolean(comment?.deletedText) || comment?.trackedChange === true;
6903
7094
  /**
6904
7095
  * Resolve explicit thread provenance first, then the guarded legacy fallback.
6905
7096
  * Spatial-only v2 comments carry neither the explicit field nor a legacy
6906
7097
  * review signal and therefore remain standalone.
6907
- *
6908
- * @param {Record<string, any> | null | undefined} comment
6909
- * @returns {string | number | null}
6910
7098
  */
6911
7099
  var trackedChangeThreadParentIdForComment$1 = (comment) => {
6912
7100
  if (comment?.trackedChangeThreadParentId != null) return comment.trackedChangeThreadParentId;
@@ -7058,6 +7246,7 @@ var groupCommittedComments = (comments, presentations, isVisible) => {
7058
7246
  });
7059
7247
  const grouped = groupComments(comments.filter((row) => !presentations[String(row.fileId)]), isVisible);
7060
7248
  for (const [documentId, presentation] of Object.entries(presentations)) {
7249
+ if (!presentation) continue;
7061
7250
  grouped.parentComments.push(...visibleRowsInOrder(documentId, presentation.groupOrder.open));
7062
7251
  grouped.resolvedComments.push(...visibleRowsInOrder(documentId, presentation.groupOrder.resolved));
7063
7252
  }
@@ -7185,7 +7374,6 @@ function createDefaultV2Integration() {
7185
7374
  //#region src/helpers/interaction-trace.ts
7186
7375
  var isStartSpan = (value) => typeof value === "function";
7187
7376
  var isEndSpan = (value) => typeof value === "function";
7188
- var isThenable$3 = (value) => (typeof value === "object" || typeof value === "function") && value !== null && "then" in value && typeof value.then === "function";
7189
7377
  function safeTraceCollector() {
7190
7378
  try {
7191
7379
  const collector = Reflect.get(globalThis, "__superdocInteractionTrace");
@@ -7216,7 +7404,7 @@ function withInteractionSpan(stage, branch, meta, fn) {
7216
7404
  if (!span) return fn();
7217
7405
  try {
7218
7406
  const result = fn();
7219
- if (isThenable$3(result)) return result.then((value) => {
7407
+ if (isThenable$1(result)) return result.then((value) => {
7220
7408
  endInteractionSpan(span);
7221
7409
  return value;
7222
7410
  }, (error) => {
@@ -7231,6 +7419,81 @@ function withInteractionSpan(stage, branch, meta, fn) {
7231
7419
  }
7232
7420
  }
7233
7421
  //#endregion
7422
+ //#region src/stores/helpers/tracked-change-decision.ts
7423
+ var normalizeTrackedChangeDecisionType = (value) => {
7424
+ const lower = typeof value === "string" ? value.toLowerCase() : "";
7425
+ if (!lower) return null;
7426
+ if (lower === "insert" || lower === "insertion" || lower === "trackinsert" || lower === "tableinsert" || lower === "paragraphsplit") return "insertion";
7427
+ if (lower === "delete" || lower === "deletion" || lower === "trackdelete" || lower === "tabledelete") return "deletion";
7428
+ return lower;
7429
+ };
7430
+ var trackedChangeDecisionKeepsCommentAnchor = ({ comment, decision } = {}) => {
7431
+ const normalizedDecision = decision === "accept" || decision === "reject" ? decision : null;
7432
+ if (!normalizedDecision) return false;
7433
+ const type = normalizeTrackedChangeDecisionType(comment?.trackedChangeDisplayType) ?? normalizeTrackedChangeDecisionType(comment?.trackedChangeType);
7434
+ return normalizedDecision === "accept" && type === "insertion" || normalizedDecision === "reject" && type === "deletion";
7435
+ };
7436
+ var collectTrackedChangeDecisionIdsWithAliases = (comment, aliasIds) => {
7437
+ const trackedChangeIds = new Set(aliasIds);
7438
+ if (comment?.trackedChangeCanonicalId != null) trackedChangeIds.add(String(comment.trackedChangeCanonicalId));
7439
+ const anchorKey = comment?.trackedChangeAnchorKey != null ? String(comment.trackedChangeAnchorKey) : null;
7440
+ if (anchorKey) {
7441
+ trackedChangeIds.add(anchorKey);
7442
+ if (anchorKey.startsWith("tc::")) {
7443
+ const rawId = anchorKey.slice(anchorKey.lastIndexOf("::") + 2);
7444
+ if (rawId) trackedChangeIds.add(rawId);
7445
+ }
7446
+ }
7447
+ return trackedChangeIds;
7448
+ };
7449
+ var collectTrackedChangeEntityIdsFromReceipt = (receipt) => {
7450
+ const record = receipt && typeof receipt === "object" ? receipt : null;
7451
+ if (!record?.success) return /* @__PURE__ */ new Set();
7452
+ const ids = /* @__PURE__ */ new Set();
7453
+ const collect = (entry) => {
7454
+ const ref = entry && typeof entry === "object" ? entry : null;
7455
+ if (ref?.kind !== "entity" || ref.entityType !== "trackedChange") return;
7456
+ if (ref.entityId != null && String(ref.entityId).length > 0) ids.add(String(ref.entityId));
7457
+ };
7458
+ if (Array.isArray(record.removed)) record.removed.forEach(collect);
7459
+ if (Array.isArray(record.invalidatedRefs)) record.invalidatedRefs.forEach(collect);
7460
+ return ids;
7461
+ };
7462
+ var collectReliableDecidedTrackedChangeIds = (outcome) => {
7463
+ const ids = /* @__PURE__ */ new Set();
7464
+ if (outcome?.decidedId != null && String(outcome.decidedId).length > 0) ids.add(String(outcome.decidedId));
7465
+ if (Array.isArray(outcome?.decidedIds)) outcome.decidedIds.forEach((id) => {
7466
+ if (id != null && String(id).length > 0) ids.add(String(id));
7467
+ });
7468
+ collectTrackedChangeEntityIdsFromReceipt(outcome?.receipt).forEach((id) => ids.add(id));
7469
+ return ids;
7470
+ };
7471
+ var clearV2ActiveTrackedChangeTarget = (adapter, decidedId) => {
7472
+ if (!decidedId || typeof adapter?.clearActiveTrackedChangeTargetIfMatches !== "function") return;
7473
+ try {
7474
+ adapter.clearActiveTrackedChangeTargetIfMatches(decidedId);
7475
+ } catch {}
7476
+ };
7477
+ var waitForTrackedDecisionCanonicalPaint = async ({ superdoc, receipt } = {}) => {
7478
+ const readiness = superdoc?.activeEditor?.documentMutationReadiness;
7479
+ if (receipt?.success !== true || typeof receipt?.txId !== "string" || !receipt.txId || typeof readiness?.whenPainted !== "function") return;
7480
+ try {
7481
+ await readiness.whenPainted(receipt);
7482
+ const requestFrame = globalThis.requestAnimationFrame;
7483
+ if (typeof requestFrame === "function") await new Promise((resolve) => {
7484
+ let settled = false;
7485
+ const finish = () => {
7486
+ if (settled) return;
7487
+ settled = true;
7488
+ clearTimeout(fallback);
7489
+ resolve();
7490
+ };
7491
+ const fallback = setTimeout(finish, 250);
7492
+ requestFrame(() => requestFrame(() => setTimeout(finish, 0)));
7493
+ });
7494
+ } catch {}
7495
+ };
7496
+ //#endregion
7234
7497
  //#region src/stores/helpers/comment-draft-text.ts
7235
7498
  var COMMENT_DRAFT_BR_TAG = /<br(?:\s[^>]*)?\s*\/?>/gi;
7236
7499
  var COMMENT_DRAFT_BLOCK_BOUNDARY = /(?:<\/(?:p|div|li|blockquote|pre|h[1-6])\s*>\s*)+(?:<(?:p|div|li|blockquote|pre|h[1-6])(?:\s[^>]*)?\s*\/?>)+/gi;
@@ -7603,9 +7866,131 @@ var cloneV2TrackedChangeStory = (story) => {
7603
7866
  };
7604
7867
  };
7605
7868
  //#endregion
7606
- //#region src/stores/comments-store.js
7869
+ //#region src/stores/helpers/tracked-change-compaction.ts
7870
+ var isPresentId = (id) => Boolean(id);
7871
+ var getBodyTrackedChangeIdentityIds = (comment) => {
7872
+ return [normalizeCommentId(comment.commentId), ...!normalizeCommentId(comment.trackedChangeCanonicalId) ? [
7873
+ comment.importedId,
7874
+ comment.trackedChangeAnchorKey,
7875
+ ...normalizeTrackedChangePositionAliases(comment.trackedChangePositionAliases)
7876
+ ] : []].map((id) => normalizeCommentId(id)).filter(isPresentId);
7877
+ };
7878
+ var groupBodyTrackedChangeCommentsByIdentity = (comments) => {
7879
+ const parents = comments.map((_, index) => index);
7880
+ const findRoot = (index) => {
7881
+ let root = index;
7882
+ while (parents[root] !== root) root = parents[root];
7883
+ while (parents[index] !== index) {
7884
+ const next = parents[index];
7885
+ parents[index] = root;
7886
+ index = next;
7887
+ }
7888
+ return root;
7889
+ };
7890
+ const join = (left, right) => {
7891
+ const leftRoot = findRoot(left);
7892
+ const rightRoot = findRoot(right);
7893
+ if (leftRoot === rightRoot) return;
7894
+ parents[Math.max(leftRoot, rightRoot)] = Math.min(leftRoot, rightRoot);
7895
+ };
7896
+ const identityOwner = /* @__PURE__ */ new Map();
7897
+ comments.forEach((comment, index) => {
7898
+ getBodyTrackedChangeIdentityIds(comment).forEach((identityId) => {
7899
+ const owner = identityOwner.get(identityId);
7900
+ if (owner === void 0) identityOwner.set(identityId, index);
7901
+ else join(index, owner);
7902
+ });
7903
+ });
7904
+ const groups = /* @__PURE__ */ new Map();
7905
+ comments.forEach((comment, index) => {
7906
+ const root = findRoot(index);
7907
+ const group = groups.get(root) ?? [];
7908
+ group.push(comment);
7909
+ groups.set(root, group);
7910
+ });
7911
+ return groups;
7912
+ };
7913
+ var mergeDuplicateBodyTrackedChangeComments = (groups) => {
7914
+ const duplicates = /* @__PURE__ */ new Map();
7915
+ groups.forEach(([survivor, ...duplicateRows]) => {
7916
+ if (!survivor || duplicateRows.length === 0) return;
7917
+ const resolvedDuplicate = duplicateRows.find((comment) => comment.resolvedTime != null);
7918
+ if (survivor.resolvedTime == null && resolvedDuplicate) {
7919
+ survivor.resolvedTime = resolvedDuplicate.resolvedTime;
7920
+ survivor.resolvedById = resolvedDuplicate.resolvedById;
7921
+ survivor.resolvedByEmail = resolvedDuplicate.resolvedByEmail;
7922
+ survivor.resolvedByName = resolvedDuplicate.resolvedByName;
7923
+ survivor.trackedChangeDecision = resolvedDuplicate.trackedChangeDecision;
7924
+ }
7925
+ survivor.trackedChangePositionAliases = normalizeTrackedChangePositionAliases([...normalizeTrackedChangePositionAliases(survivor.trackedChangePositionAliases), ...duplicateRows.flatMap((comment) => [
7926
+ comment.commentId,
7927
+ comment.importedId,
7928
+ comment.trackedChangeCanonicalId,
7929
+ ...normalizeTrackedChangePositionAliases(comment.trackedChangePositionAliases)
7930
+ ])]);
7931
+ duplicateRows.forEach((comment) => duplicates.set(comment, survivor));
7932
+ });
7933
+ return duplicates;
7934
+ };
7935
+ var createDroppedCommentIdMap = (duplicates) => {
7936
+ const droppedCommentIds = /* @__PURE__ */ new Map();
7937
+ duplicates.forEach((survivor, duplicate) => {
7938
+ const fromId = normalizeCommentId(duplicate.commentId);
7939
+ const toId = normalizeCommentId(survivor.commentId);
7940
+ if (fromId && toId) droppedCommentIds.set(fromId, toId);
7941
+ });
7942
+ return droppedCommentIds;
7943
+ };
7944
+ //#endregion
7945
+ //#region src/stores/helpers/v2-review-adapters.ts
7946
+ var COMMENTS_ADAPTER_METHODS = [
7947
+ "setActiveComment",
7948
+ "commitPendingComment",
7949
+ "reply",
7950
+ "edit",
7951
+ "resolve",
7952
+ "reopen",
7953
+ "delete",
7954
+ "mapV2CommentToUseCommentInput"
7955
+ ];
7956
+ var COMMENTS_ADAPTER_OPTIONAL_METHODS = [
7957
+ "getCapabilityState",
7958
+ "captureCurrentSelection",
7959
+ "isPendingCommentCreation",
7960
+ "seedReviewCatalog",
7961
+ "selectVisibleReviewComments"
7962
+ ];
7963
+ var TRACKED_CHANGES_ADAPTER_METHODS = [
7964
+ "accept",
7965
+ "reject",
7966
+ "mapV2TrackedChangeToCommentParams"
7967
+ ];
7968
+ var TRACKED_CHANGES_ADAPTER_OPTIONAL_METHODS = ["clearActiveTrackedChangeTargetIfMatches"];
7969
+ var isAdapterDocumentId = (value) => value === void 0 || value === null || typeof value === "string" || typeof value === "number";
7970
+ var hasAdapterShape = (value, required, optional) => isPlainObject$1(value) && isAdapterDocumentId(value.documentId) && required.every((member) => typeof value[member] === "function") && optional.every((member) => value[member] === void 0 || typeof value[member] === "function");
7971
+ function isV2CommentsAdapter(value) {
7972
+ return hasAdapterShape(value, COMMENTS_ADAPTER_METHODS, COMMENTS_ADAPTER_OPTIONAL_METHODS);
7973
+ }
7974
+ function isV2TrackedChangesAdapter(value) {
7975
+ return hasAdapterShape(value, TRACKED_CHANGES_ADAPTER_METHODS, TRACKED_CHANGES_ADAPTER_OPTIONAL_METHODS);
7976
+ }
7977
+ //#endregion
7978
+ //#region src/stores/comments-store.ts
7979
+ var requireHost = (superdoc, action) => {
7980
+ if (!superdoc) throw new TypeError(`${action} requires superdoc`);
7981
+ return superdoc;
7982
+ };
7983
+ var PARENT_LINK_FIELDS = [
7984
+ "trackedChangeThreadParentId",
7985
+ "trackedChangeParentId",
7986
+ "threadingParentCommentId",
7987
+ "parentCommentId"
7988
+ ];
7989
+ var isPresent = (value) => Boolean(value);
7990
+ var errorDetail = (error) => (error == null ? void 0 : Reflect.get(Object(error), "message")) ?? String(error);
7607
7991
  var useCommentsStore = defineStore("comments", () => {
7608
7992
  const superdocStore = useSuperdocStore();
7993
+ const findDocument = (documentId) => typeof documentId === "string" ? superdocStore.getDocument(documentId) : void 0;
7609
7994
  const commentsConfig = reactive({
7610
7995
  name: "comments",
7611
7996
  readOnly: false,
@@ -7635,6 +8020,7 @@ var useCommentsStore = defineStore("comments", () => {
7635
8020
  });
7636
8021
  const debounceTimers = {};
7637
8022
  const trackedChangeResolutionSnapshots = /* @__PURE__ */ new WeakMap();
8023
+ let cachedTrackedChangeBatchIdentityIndex = null;
7638
8024
  const isBenchmarkCommentsDebugEnabled = () => {
7639
8025
  if (typeof window === "undefined") return false;
7640
8026
  return "__labsSuperDocV2BenchmarkDebug" in window;
@@ -7672,7 +8058,7 @@ var useCommentsStore = defineStore("comments", () => {
7672
8058
  });
7673
8059
  const getTrackedChangeThread = (parentComment) => {
7674
8060
  const id = parentComment?.commentId ?? null;
7675
- return (id != null ? trackedChangeThreadIndex.value.get(id) : null) ?? [parentComment].filter(Boolean);
8061
+ return (id != null ? trackedChangeThreadIndex.value.get(id) : null) ?? (parentComment ? [parentComment] : []);
7676
8062
  };
7677
8063
  let priorTrackedChangeDecisionLinkIndex = /* @__PURE__ */ new Map();
7678
8064
  const trackedChangeDecisionLinkIndex = computed$1(() => {
@@ -7704,29 +8090,28 @@ var useCommentsStore = defineStore("comments", () => {
7704
8090
  });
7705
8091
  const v2CommentsAdapter = shallowRef$1(null);
7706
8092
  const setV2CommentsAdapter = (adapter) => {
7707
- if (v2CommentsAdapter.value !== (adapter ?? null)) pendingCommentRequest += 1;
7708
- v2CommentsAdapter.value = adapter ?? null;
8093
+ const next = isV2CommentsAdapter(adapter) ? adapter : null;
8094
+ if (v2CommentsAdapter.value !== next) pendingCommentRequest += 1;
8095
+ v2CommentsAdapter.value = next;
7709
8096
  };
7710
8097
  const getV2CommentsAdapter = (superdoc) => {
7711
- const fromFacade = superdoc?.activeEditor?.v2Comments ?? null;
7712
- if (fromFacade) return fromFacade;
8098
+ const fromFacade = superdoc?.activeEditor?.v2Comments;
8099
+ if (isV2CommentsAdapter(fromFacade)) return fromFacade;
7713
8100
  return v2CommentsAdapter.value;
7714
8101
  };
7715
8102
  const isV2EditorActive = (superdoc) => superdoc?.activeEditor?.editorVersion === 2 || v2CommentsAdapter.value !== null;
7716
8103
  const v2TrackedChangesAdapter = shallowRef$1(null);
7717
8104
  const setV2TrackedChangesAdapter = (adapter) => {
7718
- v2TrackedChangesAdapter.value = adapter ?? null;
8105
+ cachedTrackedChangeBatchIdentityIndex = null;
8106
+ v2TrackedChangesAdapter.value = isV2TrackedChangesAdapter(adapter) ? adapter : null;
7719
8107
  };
7720
8108
  const getV2TrackedChangesAdapter = (superdoc) => {
7721
- const fromFacade = superdoc?.activeEditor?.v2TrackedChanges ?? null;
7722
- if (fromFacade) return fromFacade;
8109
+ const fromFacade = superdoc?.activeEditor?.v2TrackedChanges;
8110
+ if (isV2TrackedChangesAdapter(fromFacade)) return fromFacade;
7723
8111
  return v2TrackedChangesAdapter.value;
7724
8112
  };
7725
8113
  /**
7726
8114
  * Initialize the store
7727
- *
7728
- * @param {Object} config The comments module config from SuperDoc
7729
- * @returns {void}
7730
8115
  */
7731
8116
  const init = (config = {}) => {
7732
8117
  const updatedConfig = {
@@ -7758,12 +8143,17 @@ var useCommentsStore = defineStore("comments", () => {
7758
8143
  comment.trackedChangeDecision = null;
7759
8144
  };
7760
8145
  const getCommentEventPayload = (comment) => typeof comment?.getValues === "function" ? comment.getValues() : { ...comment };
8146
+ const getDeletedCommentChanges = (sources, fileId) => {
8147
+ const commentId = [...sources.map((source) => source?.commentId), ...sources.map((source) => source?.importedId)].map(normalizeCommentId).find(Boolean);
8148
+ return commentId ? [{
8149
+ key: "deleted",
8150
+ commentId,
8151
+ fileId
8152
+ }] : [];
8153
+ };
7761
8154
  /**
7762
8155
  * Check if a comment originated from the document editor (or has no explicit source).
7763
8156
  * Comments without a source are assumed to be editor-backed for backward compatibility.
7764
- *
7765
- * @param {Object} comment - The comment to check
7766
- * @returns {boolean} True if the comment is editor-backed
7767
8157
  */
7768
8158
  const isEditorBackedComment = (comment) => {
7769
8159
  const source = comment?.selection?.source;
@@ -7797,9 +8187,6 @@ var useCommentsStore = defineStore("comments", () => {
7797
8187
  }, { deep: false });
7798
8188
  /**
7799
8189
  * Get the editor position data for a comment.
7800
- *
7801
- * @param {Object | string} commentOrId The comment object or comment ID
7802
- * @returns {Object | null} The position data from editorCommentPositions
7803
8190
  */
7804
8191
  const getCommentPosition = (commentOrId) => {
7805
8192
  return resolveCommentPositionEntry(commentOrId).entry ?? null;
@@ -7812,10 +8199,6 @@ var useCommentsStore = defineStore("comments", () => {
7812
8199
  };
7813
8200
  /**
7814
8201
  * Set the active comment or clear all active comments
7815
- *
7816
- * @param {Object | undefined | null} superdoc The SuperDoc instance holding the active editor
7817
- * @param {string | undefined | null} id The comment ID
7818
- * @returns {void}
7819
8202
  */
7820
8203
  const setActiveComment = (superdoc, id) => {
7821
8204
  const activeEditor = superdoc?.activeEditor;
@@ -7826,7 +8209,7 @@ var useCommentsStore = defineStore("comments", () => {
7826
8209
  v2Adapter.setActiveComment(null);
7827
8210
  return;
7828
8211
  }
7829
- activeEditor?.commands?.setActiveComment({ commentId: null });
8212
+ activeEditor?.commands?.setActiveComment?.({ commentId: null });
7830
8213
  return;
7831
8214
  }
7832
8215
  const comment = getComment(id);
@@ -7837,18 +8220,13 @@ var useCommentsStore = defineStore("comments", () => {
7837
8220
  v2Adapter.setActiveComment(comment.commentId);
7838
8221
  return;
7839
8222
  }
7840
- activeEditor?.commands?.setActiveComment({ commentId: activeComment.value });
8223
+ activeEditor?.commands?.setActiveComment?.({ commentId: activeComment.value });
7841
8224
  };
7842
8225
  /**
7843
8226
  * Called when a tracked change is updated. Creates a new comment if necessary,
7844
8227
  * or updates an existing tracked-change comment.
7845
- *
7846
- * @param {Object} param0
7847
- * @param {Object} param0.superdoc The SuperDoc instance
7848
- * @param {Object} param0.params The tracked change params
7849
- * @returns {void}
7850
8228
  */
7851
- const handleTrackedChangeUpdate = ({ superdoc, params, broadcastChanges = true, trackedChangeIdentityIndex = null }) => {
8229
+ const handleTrackedChangeUpdate = ({ superdoc, params, broadcastChanges = true, trackedChangeIdentityIndex = null, preserveDraft = false }) => {
7852
8230
  const span = startInteractionSpan("store.trackedChanges.handleUpdate", "store-reconciliation", {
7853
8231
  event: params?.event ?? null,
7854
8232
  changeId: params?.changeId ?? null,
@@ -7873,9 +8251,11 @@ var useCommentsStore = defineStore("comments", () => {
7873
8251
  const normalizedTrackedChangeLabel = typeof trackedChangeLabel === "string" && trackedChangeLabel.length > 0 ? trackedChangeLabel : null;
7874
8252
  const normalizedTrackedChangeDetailLines = normalizeTrackedChangeDetailLines(trackedChangeDetailLines);
7875
8253
  const normalizedTrackedChangeImagePreview = normalizeTrackedChangeImagePreview(trackedChangeImagePreview);
8254
+ const hasDraft = preserveDraft && (pendingComment.value !== null || currentCommentText.value !== "" || currentCommentMentions.value.length > 0);
7876
8255
  const comment = getPendingComment({
7877
8256
  documentId,
7878
- commentId: changeId,
8257
+ ...hasDraft ? { commentText: "" } : {},
8258
+ commentId: normalizedChangeId ?? void 0,
7879
8259
  trackedChange: true,
7880
8260
  trackedChangeText,
7881
8261
  trackedChangeType,
@@ -7927,7 +8307,7 @@ var useCommentsStore = defineStore("comments", () => {
7927
8307
  commentCanonicalId,
7928
8308
  commentAnchorKey,
7929
8309
  ...commentAliasIds
7930
- ].filter(Boolean).some((id) => incomingAliasIds.has(id))) return true;
8310
+ ].some((id) => id != null && incomingAliasIds.has(id))) return true;
7931
8311
  const hasSyntheticSideIdentity = normalizedTrackedChangeCanonicalId && normalizedTrackedChangeCanonicalId !== normalizedChangeId || commentCanonicalId && commentCanonicalId !== commentId;
7932
8312
  if (hasCompatibleStory && commentId === normalizedChangeId) return true;
7933
8313
  if (hasCompatibleStory && !hasSyntheticSideIdentity && (importedId === normalizedChangeId || normalizedImportedId !== void 0 && normalizedImportedId != null && importedId === normalizedImportedId)) return true;
@@ -7951,7 +8331,7 @@ var useCommentsStore = defineStore("comments", () => {
7951
8331
  };
7952
8332
  const emitTrackedChangeEvent = (event) => {
7953
8333
  if (!broadcastChanges) return;
7954
- syncCommentsToClients(superdoc, event);
8334
+ syncCommentsToClients(requireHost(superdoc, "handleTrackedChangeUpdate"), event);
7955
8335
  debounceEmit(changeId, event, superdoc);
7956
8336
  };
7957
8337
  const setIfChanged = (target, key, value) => {
@@ -7994,8 +8374,9 @@ var useCommentsStore = defineStore("comments", () => {
7994
8374
  target?.trackedChangeCanonicalId,
7995
8375
  target?.trackedChangeAnchorKey,
7996
8376
  ...getCommentAliasIds(target)
7997
- ].map((id) => normalizeCommentId(id)).filter(Boolean));
7998
- fields.trackedChangePositionAliases = normalizedTrackedChangePositionAliases.some((alias) => targetIdentityIds.has(alias)) ? normalizeTrackedChangePositionAliases([...Array.isArray(target?.trackedChangePositionAliases) ? target.trackedChangePositionAliases : [], ...normalizedTrackedChangePositionAliases]) : normalizedTrackedChangePositionAliases;
8377
+ ].map((id) => normalizeCommentId(id)).filter(isPresent));
8378
+ const incomingAliases = normalizedTrackedChangePositionAliases ?? [];
8379
+ fields.trackedChangePositionAliases = incomingAliases.some((alias) => targetIdentityIds.has(alias)) ? normalizeTrackedChangePositionAliases([...Array.isArray(target?.trackedChangePositionAliases) ? target.trackedChangePositionAliases : [], ...incomingAliases]) : incomingAliases;
7999
8380
  }
8000
8381
  let didChange = false;
8001
8382
  for (const [key, value] of Object.entries(fields)) didChange = setIfChanged(target, key, value) || didChange;
@@ -8004,7 +8385,7 @@ var useCommentsStore = defineStore("comments", () => {
8004
8385
  didChange = true;
8005
8386
  }
8006
8387
  if (hasCustomAttributes && target && !trackedChangeCustomAttributesEqual(target.customAttributes, normalizedCustomAttributes)) {
8007
- target.customAttributes = normalizedCustomAttributes;
8388
+ target.customAttributes = normalizedCustomAttributes ?? null;
8008
8389
  didChange = true;
8009
8390
  }
8010
8391
  return applyStoryMetadata(target) || didChange;
@@ -8026,9 +8407,10 @@ var useCommentsStore = defineStore("comments", () => {
8026
8407
  return;
8027
8408
  }
8028
8409
  addHydratedComment({
8029
- superdoc,
8410
+ superdoc: requireHost(superdoc, "handleTrackedChangeUpdate"),
8030
8411
  comment,
8031
- broadcastChanges
8412
+ broadcastChanges,
8413
+ preserveDraft: hasDraft
8032
8414
  });
8033
8415
  trackedChangeIdentityIndex?.add(comment);
8034
8416
  } else if (event === "update") {
@@ -8046,11 +8428,12 @@ var useCommentsStore = defineStore("comments", () => {
8046
8428
  id: params.resolvedById ?? superdoc?.user?.id ?? null,
8047
8429
  email: params.resolvedByEmail ?? superdoc?.user?.email ?? null,
8048
8430
  name: params.resolvedByName ?? superdoc?.user?.name ?? null,
8049
- decision: params.decision ?? null,
8431
+ decision: params.decision ?? void 0,
8050
8432
  superdoc
8051
8433
  };
8052
8434
  if (existingTrackedChange && !existingTrackedChange.resolvedTime) existingTrackedChange.resolveComment({
8053
8435
  ...resolveArgs,
8436
+ superdoc: requireHost(superdoc, "handleTrackedChangeUpdate"),
8054
8437
  reconciliationToken: COMMENT_RECONCILIATION_TOKEN
8055
8438
  });
8056
8439
  }
@@ -8075,31 +8458,13 @@ var useCommentsStore = defineStore("comments", () => {
8075
8458
  instantSidebarAlignmentInstanceId.value = null;
8076
8459
  };
8077
8460
  const debounceEmit = (commentId, event, superdoc, delay = 1e3) => {
8078
- if (debounceTimers[commentId]) clearTimeout(debounceTimers[commentId]);
8079
- debounceTimers[commentId] = setTimeout(() => {
8461
+ const timerKey = String(commentId);
8462
+ if (debounceTimers[timerKey]) clearTimeout(debounceTimers[timerKey]);
8463
+ debounceTimers[timerKey] = setTimeout(() => {
8080
8464
  if (superdoc) superdoc.emit("comments-update", event);
8081
- delete debounceTimers[commentId];
8465
+ delete debounceTimers[timerKey];
8082
8466
  }, delay);
8083
8467
  };
8084
- const getPendingSelectionSnapshot = (superdoc, activeSelection) => {
8085
- if (activeSelection?.source !== "document-editor") return null;
8086
- const selectionDocumentId = activeSelection?.documentId ?? null;
8087
- const editorDocumentId = superdoc?.activeEditor?.options?.documentId ?? null;
8088
- if (!selectionDocumentId || !editorDocumentId || selectionDocumentId !== editorDocumentId) return null;
8089
- let currentSelection;
8090
- try {
8091
- currentSelection = superdoc?.activeEditor?.doc?.selection?.current;
8092
- } catch {
8093
- return null;
8094
- }
8095
- if (typeof currentSelection !== "function") return null;
8096
- try {
8097
- const snapshot = currentSelection();
8098
- return typeof snapshot?.then === "function" ? Promise.resolve(snapshot).catch(() => null) : snapshot;
8099
- } catch {
8100
- return null;
8101
- }
8102
- };
8103
8468
  const showAddComment = (superdoc, targetClientY = null) => {
8104
8469
  const request = ++pendingCommentRequest;
8105
8470
  const editor = superdoc?.activeEditor;
@@ -8109,7 +8474,7 @@ var useCommentsStore = defineStore("comments", () => {
8109
8474
  if (!ownsRequest()) return;
8110
8475
  superdoc.emit("comments-update", {
8111
8476
  type: COMMENT_EVENTS.PENDING,
8112
- pendingSelection: superdoc?.activeEditor === editor ? snapshot : null
8477
+ pendingSelection: superdoc?.activeEditor === editor && isPlainObject$1(snapshot) ? snapshot : null
8113
8478
  });
8114
8479
  };
8115
8480
  if (v2Adapter) {
@@ -8154,12 +8519,11 @@ var useCommentsStore = defineStore("comments", () => {
8154
8519
  }).catch((err) => ({
8155
8520
  ok: false,
8156
8521
  reason: "selection-capture-failed",
8157
- detail: err?.message ?? String(err)
8522
+ detail: errorDetail(err)
8158
8523
  }));
8159
8524
  }
8160
8525
  const pendingSelection = getPendingSelectionSnapshot(superdoc, superdocStore.activeSelection);
8161
- const asyncSnapshot = typeof pendingSelection?.then === "function";
8162
- if (!asyncSnapshot) emitPending(pendingSelection);
8526
+ if (!isThenable$1(pendingSelection)) emitPending(pendingSelection);
8163
8527
  const showLegacyPending = (selection) => {
8164
8528
  selection.selectionBounds = { ...selection.selectionBounds };
8165
8529
  if (superdocStore.selectionPosition?.source && superdocStore.selectionPosition.source !== "pdf") superdocStore.selectionPosition.source = null;
@@ -8169,7 +8533,7 @@ var useCommentsStore = defineStore("comments", () => {
8169
8533
  parentCommentId: null
8170
8534
  });
8171
8535
  if (!superdoc.config.isInternal) pendingComment.value.isInternal = false;
8172
- if (superdoc.activeEditor?.commands) superdoc.activeEditor.commands.insertComment({
8536
+ if (superdoc.activeEditor?.commands) superdoc.activeEditor.commands.insertComment?.({
8173
8537
  ...pendingComment.value.getValues(),
8174
8538
  commentId: "pending",
8175
8539
  skipEmit: true
@@ -8180,10 +8544,10 @@ var useCommentsStore = defineStore("comments", () => {
8180
8544
  activeComment.value = pendingComment.value.commentId;
8181
8545
  return { ok: true };
8182
8546
  };
8183
- if (asyncSnapshot) {
8547
+ if (isThenable$1(pendingSelection)) {
8184
8548
  const selection = { ...superdocStore.activeSelection };
8185
8549
  selection.selectionBounds = { ...selection.selectionBounds };
8186
- return pendingSelection.then((snapshot) => {
8550
+ return Promise.resolve(pendingSelection).then((snapshot) => {
8187
8551
  if (!ownsRequest()) return {
8188
8552
  ok: false,
8189
8553
  reason: "pending-comment-superseded"
@@ -8215,12 +8579,6 @@ var useCommentsStore = defineStore("comments", () => {
8215
8579
  });
8216
8580
  /**
8217
8581
  * Get a new pending comment
8218
- *
8219
- * @param {Object} param0
8220
- * @param {Object} [param0.selection] The selection object
8221
- * @param {string | null} [param0.documentId] The document ID
8222
- * @param {string | null} [param0.parentCommentId] The parent comment
8223
- * @returns {ReturnType<typeof useComment>} The new comment object
8224
8582
  */
8225
8583
  const getPendingComment = ({ selection, documentId, parentCommentId, ...options }) => {
8226
8584
  return _getNewcomment({
@@ -8232,17 +8590,11 @@ var useCommentsStore = defineStore("comments", () => {
8232
8590
  };
8233
8591
  /**
8234
8592
  * Get the new comment object
8235
- *
8236
- * @param {Object} param0
8237
- * @param {Object} param0.selection The selection object
8238
- * @param {String} param0.documentId The document ID
8239
- * @param {String} param0.parentCommentId The parent comment ID
8240
- * @returns {Object} The new comment object
8241
8593
  */
8242
8594
  const _getNewcomment = ({ selection, documentId, parentCommentId, ...options }) => {
8243
8595
  let activeDocument;
8244
- if (documentId) activeDocument = superdocStore.getDocument(documentId);
8245
- else if (selection) activeDocument = superdocStore.getDocument(selection.documentId);
8596
+ if (documentId) activeDocument = findDocument(documentId);
8597
+ else if (selection) activeDocument = findDocument(selection.documentId);
8246
8598
  if (!activeDocument) activeDocument = superdocStore.documents[0];
8247
8599
  return useComment({
8248
8600
  fileId: activeDocument.id,
@@ -8259,8 +8611,6 @@ var useCommentsStore = defineStore("comments", () => {
8259
8611
  };
8260
8612
  /**
8261
8613
  * Remove the pending comment
8262
- *
8263
- * @returns {void}
8264
8614
  */
8265
8615
  const resetPendingCommentState = (superdoc, clearViewingSelection = true) => {
8266
8616
  const hadPending = !!pendingComment.value;
@@ -8276,7 +8626,7 @@ var useCommentsStore = defineStore("comments", () => {
8276
8626
  } catch {}
8277
8627
  return;
8278
8628
  }
8279
- superdoc?.activeEditor?.commands?.removeComment({ commentId: "pending" });
8629
+ superdoc?.activeEditor?.commands?.removeComment?.({ commentId: "pending" });
8280
8630
  };
8281
8631
  const removePendingComment = (superdoc) => {
8282
8632
  pendingCommentRequest += 1;
@@ -8289,16 +8639,8 @@ var useCommentsStore = defineStore("comments", () => {
8289
8639
  };
8290
8640
  /**
8291
8641
  * Add a new comment to the document
8292
- *
8293
- * @param {Object} param0
8294
- * @param {Object} param0.superdoc The SuperDoc instance
8295
- * @param {ReturnType<typeof useComment>} param0.comment The comment to add
8296
- * @param {boolean} [param0.skipEditorUpdate] Skip writing the comment into the editor
8297
- * @param {boolean} [param0.broadcastChanges] Emit and sync the change
8298
- * @param {boolean} [param0.isHydration] The comment comes from import or collaboration
8299
- * @returns {{ ok: boolean, reason?: string, detail?: string, code?: string } | Promise<{ ok: boolean, reason?: string, detail?: string, code?: string }>} The mutation outcome
8300
8642
  */
8301
- const addCommentInternal = ({ superdoc, comment, skipEditorUpdate = false, broadcastChanges = true, isHydration = false }) => {
8643
+ const addCommentInternal = ({ superdoc, comment, skipEditorUpdate = false, broadcastChanges = true, isHydration = false, preserveDraft = false }) => {
8302
8644
  if (commentsAreReadOnly() && !isHydration) return readOnlyMutationOutcome();
8303
8645
  const v2Adapter = !skipEditorUpdate && !isHydration ? getV2CommentsAdapter(superdoc) : null;
8304
8646
  if (v2Adapter && !comment.trackedChange) {
@@ -8326,7 +8668,7 @@ var useCommentsStore = defineStore("comments", () => {
8326
8668
  return {
8327
8669
  ok: false,
8328
8670
  reason: "adapter-threw",
8329
- detail: err?.message ?? String(err)
8671
+ detail: errorDetail(err)
8330
8672
  };
8331
8673
  }
8332
8674
  })().then((outcome) => {
@@ -8377,28 +8719,30 @@ var useCommentsStore = defineStore("comments", () => {
8377
8719
  };
8378
8720
  });
8379
8721
  }
8380
- let parentComment = commentsList.value.find((c) => c.commentId === activeComment.value);
8722
+ const activeId = activeComment.value;
8723
+ const parent = toRaw$2(commentsList.value).find((c) => unref(c.commentId) === activeId);
8724
+ let parentComment = parent ? reactive(parent) : void 0;
8381
8725
  if (!parentComment) parentComment = comment;
8382
8726
  const newComment = useComment(comment.getValues());
8383
- if (pendingComment.value) newComment.setText({
8384
- text: currentCommentText.value,
8727
+ const text = pendingComment.value && !preserveDraft ? currentCommentText.value : comment.commentText;
8728
+ if (text === "") {
8729
+ newComment.commentText = "";
8730
+ newComment.mentions = [];
8731
+ } else newComment.setText({
8732
+ text,
8385
8733
  suppressUpdate: true
8386
8734
  });
8387
- else newComment.setText({
8388
- text: comment.commentText,
8389
- suppressUpdate: true
8390
- });
8391
- newComment.selection.source = pendingComment.value?.selection?.source ?? newComment.selection.source;
8735
+ if (!preserveDraft) newComment.selection.source = pendingComment.value?.selection?.source ?? newComment.selection.source;
8392
8736
  if (parentComment) newComment.isInternal = parentComment.isInternal;
8393
8737
  if (!superdoc.config.isInternal) newComment.isInternal = false;
8394
8738
  commentsList.value.push(newComment);
8395
- const hadPendingComment = !!pendingComment.value;
8396
- removePendingComment(superdoc);
8739
+ const hadPendingComment = !preserveDraft && !!pendingComment.value;
8740
+ if (!preserveDraft) removePendingComment(superdoc);
8397
8741
  if (hadPendingComment) {
8398
8742
  activeComment.value = newComment.commentId;
8399
8743
  setActiveFloatingCommentInstance(newComment.commentId);
8400
8744
  }
8401
- if (!skipEditorUpdate && !comment.trackedChange && superdoc.activeEditor?.commands && !comment.parentCommentId) superdoc.activeEditor.commands.insertComment({
8745
+ if (!skipEditorUpdate && !comment.trackedChange && superdoc.activeEditor?.commands && !comment.parentCommentId) superdoc.activeEditor.commands.insertComment?.({
8402
8746
  ...newComment.getValues(),
8403
8747
  skipEmit: true
8404
8748
  });
@@ -8421,12 +8765,13 @@ var useCommentsStore = defineStore("comments", () => {
8421
8765
  skipEditorUpdate,
8422
8766
  broadcastChanges
8423
8767
  });
8424
- const addHydratedComment = ({ superdoc, comment, skipEditorUpdate = false, broadcastChanges = true }) => addCommentInternal({
8768
+ const addHydratedComment = ({ superdoc, comment, skipEditorUpdate = false, broadcastChanges = true, preserveDraft = false }) => addCommentInternal({
8425
8769
  superdoc,
8426
8770
  comment,
8427
8771
  skipEditorUpdate,
8428
8772
  broadcastChanges,
8429
- isHydration: true
8773
+ isHydration: true,
8774
+ preserveDraft
8430
8775
  });
8431
8776
  const deleteComment = ({ commentId: commentIdToDelete, superdoc }) => {
8432
8777
  if (commentsAreReadOnly()) return Promise.resolve(readOnlyMutationOutcome());
@@ -8446,7 +8791,7 @@ var useCommentsStore = defineStore("comments", () => {
8446
8791
  return {
8447
8792
  ok: false,
8448
8793
  reason: "adapter-threw",
8449
- detail: err?.message ?? String(err)
8794
+ detail: errorDetail(err)
8450
8795
  };
8451
8796
  }
8452
8797
  })().then((outcome) => {
@@ -8466,16 +8811,16 @@ var useCommentsStore = defineStore("comments", () => {
8466
8811
  });
8467
8812
  return outcome;
8468
8813
  }
8469
- const removedIds = new Set((Array.isArray(outcome.removedCommentIds) ? outcome.removedCommentIds : []).map(normalizeCommentId).filter(Boolean));
8470
- if (removedIds.has(normalizeCommentId(commentId))) {
8471
- if (importedId != null) removedIds.add(normalizeCommentId(importedId));
8814
+ const removedIds = new Set((Array.isArray(outcome.removedCommentIds) ? outcome.removedCommentIds : []).map(normalizeCommentId).filter(isPresent));
8815
+ if (removedIds.has(normalizeCommentId(commentId) ?? "")) {
8816
+ if (importedId != null) removedIds.add(String(importedId));
8472
8817
  let addedChild = true;
8473
8818
  while (addedChild) {
8474
8819
  addedChild = false;
8475
8820
  for (const row of commentsList.value) {
8476
8821
  const parentId = normalizeCommentId(row?.parentCommentId);
8477
8822
  if (!parentId || !removedIds.has(parentId)) continue;
8478
- for (const alias of [row?.commentId, row?.importedId].map(normalizeCommentId).filter(Boolean)) {
8823
+ for (const alias of [row?.commentId, row?.importedId].map(normalizeCommentId).filter(isPresent)) {
8479
8824
  if (removedIds.has(alias)) continue;
8480
8825
  removedIds.add(alias);
8481
8826
  addedChild = true;
@@ -8483,9 +8828,9 @@ var useCommentsStore = defineStore("comments", () => {
8483
8828
  }
8484
8829
  }
8485
8830
  commentsList.value = commentsList.value.filter((row) => {
8486
- return ![row?.commentId, row?.importedId].map(normalizeCommentId).filter(Boolean).some((alias) => removedIds.has(alias));
8831
+ return ![row?.commentId, row?.importedId].map(normalizeCommentId).filter(isPresent).some((alias) => removedIds.has(alias));
8487
8832
  });
8488
- if (removedIds.has(normalizeCommentId(activeComment.value))) clearActiveCommentSelection();
8833
+ if (removedIds.has(normalizeCommentId(activeComment.value) ?? "")) clearActiveCommentSelection();
8489
8834
  } else reconcileCommentsFromV2({
8490
8835
  superdoc,
8491
8836
  adapter: v2Adapter,
@@ -8493,18 +8838,18 @@ var useCommentsStore = defineStore("comments", () => {
8493
8838
  items: outcome.items ?? [],
8494
8839
  pruneStale: outcome.complete !== false
8495
8840
  });
8841
+ const payload = comment.getValues();
8496
8842
  superdoc.emit("comments-update", {
8497
8843
  type: COMMENT_EVENTS.DELETED,
8498
- comment: comment.getValues(),
8499
- changes: [{
8500
- key: "deleted",
8844
+ comment: payload,
8845
+ changes: getDeletedCommentChanges([{
8501
8846
  commentId,
8502
- fileId
8503
- }]
8847
+ importedId
8848
+ }, payload], fileId)
8504
8849
  });
8505
8850
  return { ok: true };
8506
8851
  });
8507
- superdoc.activeEditor?.commands?.removeComment({
8852
+ superdoc.activeEditor?.commands?.removeComment?.({
8508
8853
  commentId,
8509
8854
  importedId
8510
8855
  });
@@ -8512,14 +8857,14 @@ var useCommentsStore = defineStore("comments", () => {
8512
8857
  const childCommentIds = commentsList.value.filter((c) => c.parentCommentId === commentId).map((c) => c.commentId || c.importedId);
8513
8858
  commentsList.value = commentsList.value.filter((c) => !childCommentIds.includes(c.commentId));
8514
8859
  if (activeComment.value === commentId || childCommentIds.includes(activeComment.value)) clearActiveCommentSelection();
8860
+ const payload = comment.getValues();
8515
8861
  const event = {
8516
8862
  type: COMMENT_EVENTS.DELETED,
8517
- comment: comment.getValues(),
8518
- changes: [{
8519
- key: "deleted",
8863
+ comment: payload,
8864
+ changes: getDeletedCommentChanges([{
8520
8865
  commentId,
8521
- fileId
8522
- }]
8866
+ importedId
8867
+ }, payload], fileId)
8523
8868
  };
8524
8869
  superdoc.emit("comments-update", event);
8525
8870
  syncCommentsToClients(superdoc, event);
@@ -8531,7 +8876,7 @@ var useCommentsStore = defineStore("comments", () => {
8531
8876
  * otherwise keep the source bubble after the document comment is gone.
8532
8877
  */
8533
8878
  const dropCommentsFromMutationImpact = ({ superdoc, documentId, removedIds } = {}) => {
8534
- const seedIds = new Set([...removedIds instanceof Set ? removedIds : removedIds ?? []].map((id) => normalizeCommentId(id)).filter(Boolean));
8879
+ const seedIds = new Set([...removedIds instanceof Set ? removedIds : removedIds ?? []].map((id) => normalizeCommentId(id)).filter(isPresent));
8535
8880
  if (!seedIds.size) return {
8536
8881
  ok: true,
8537
8882
  removedIds: []
@@ -8555,7 +8900,7 @@ var useCommentsStore = defineStore("comments", () => {
8555
8900
  if (!isDroppableCommentRow(row)) continue;
8556
8901
  const aliases = getCommentAliasIds(row);
8557
8902
  const alreadyDropped = aliases.some((alias) => droppedIds.has(alias));
8558
- const parentDropped = droppedIds.has(normalizeCommentId(row?.parentCommentId)) || droppedIds.has(normalizeCommentId(row?.threadingParentCommentId));
8903
+ const parentDropped = droppedIds.has(normalizeCommentId(row?.parentCommentId) ?? "") || droppedIds.has(normalizeCommentId(row?.threadingParentCommentId) ?? "");
8559
8904
  if (!alreadyDropped && !parentDropped) continue;
8560
8905
  for (const alias of aliases) {
8561
8906
  if (droppedIds.has(alias)) continue;
@@ -8583,11 +8928,7 @@ var useCommentsStore = defineStore("comments", () => {
8583
8928
  const event = {
8584
8929
  type: COMMENT_EVENTS.DELETED,
8585
8930
  comment: payload,
8586
- changes: [{
8587
- key: "deleted",
8588
- commentId: payload?.commentId,
8589
- fileId: payload?.fileId
8590
- }]
8931
+ changes: getDeletedCommentChanges([payload, comment], payload?.fileId)
8591
8932
  };
8592
8933
  superdoc?.emit?.("comments-update", event);
8593
8934
  });
@@ -8609,7 +8950,7 @@ var useCommentsStore = defineStore("comments", () => {
8609
8950
  const byAlias = /* @__PURE__ */ new Map();
8610
8951
  const childrenByParentAlias = /* @__PURE__ */ new Map();
8611
8952
  for (const row of rows) {
8612
- for (const alias of [row.commentId, row.importedId].map(normalizeCommentId).filter(Boolean)) if (!byAlias.has(alias)) byAlias.set(alias, row);
8953
+ for (const alias of [row.commentId, row.importedId].map(normalizeCommentId).filter(isPresent)) if (!byAlias.has(alias)) byAlias.set(alias, row);
8613
8954
  const parentId = normalizeCommentId(row.parentCommentId);
8614
8955
  if (parentId) {
8615
8956
  const children = childrenByParentAlias.get(parentId) ?? [];
@@ -8636,7 +8977,7 @@ var useCommentsStore = defineStore("comments", () => {
8636
8977
  if (!row || visited.has(row)) continue;
8637
8978
  visited.add(row);
8638
8979
  family.push(row);
8639
- for (const alias of [row.commentId, row.importedId].map(normalizeCommentId).filter(Boolean)) queue.push(...childrenByParentAlias.get(alias) ?? []);
8980
+ for (const alias of [row.commentId, row.importedId].map(normalizeCommentId).filter(isPresent)) queue.push(...childrenByParentAlias.get(alias) ?? []);
8640
8981
  }
8641
8982
  const isResolved = status === "resolved";
8642
8983
  const resolvedAt = Date.now();
@@ -8659,7 +9000,7 @@ var useCommentsStore = defineStore("comments", () => {
8659
9000
  outcome = {
8660
9001
  ok: false,
8661
9002
  reason: "adapter-threw",
8662
- detail: err?.message ?? String(err)
9003
+ detail: errorDetail(err)
8663
9004
  };
8664
9005
  }
8665
9006
  if (!isCurrentV2CommentsAdapter(adapter)) return {
@@ -8731,14 +9072,14 @@ var useCommentsStore = defineStore("comments", () => {
8731
9072
  return (outcome.items ?? []).map((item) => adapter.mapV2CommentToUseCommentInput(item, {
8732
9073
  fileId,
8733
9074
  fileType: null
8734
- })).filter(Boolean);
9075
+ })).filter(isPresent);
8735
9076
  };
8736
9077
  const isBlankDirectoryCommentText = (value) => typeof value !== "string" || value.replace(/<[^>]*>/g, "").replace(/&nbsp;/gi, " ").trim().length === 0;
8737
9078
  const trackedChangeDirectoryComment = (params) => {
8738
9079
  if (!params || params.event === "omit" || params.changeId == null) return null;
8739
9080
  return getPendingComment({
8740
9081
  documentId: params.documentId,
8741
- commentId: params.changeId,
9082
+ commentId: String(params.changeId),
8742
9083
  trackedChange: true,
8743
9084
  trackedChangeText: params.trackedChangeText,
8744
9085
  trackedChangeType: params.trackedChangeType,
@@ -8785,7 +9126,7 @@ var useCommentsStore = defineStore("comments", () => {
8785
9126
  const mappedCommentInputs = commentItems.filter((item) => !isSyntheticTrackedChangeCommentLaneItem(item)).map((item) => commentsAdapter.mapV2CommentToUseCommentInput(item, {
8786
9127
  fileId: documentId,
8787
9128
  fileType: document?.type ?? null
8788
- })).filter(Boolean);
9129
+ })).filter(isPresent);
8789
9130
  const trackedParentBySidecarId = /* @__PURE__ */ new Map();
8790
9131
  for (const input of mappedCommentInputs) if (input.trackedChange !== true && input.trackedChangeThreadParentId && !input.parentCommentId && isBlankDirectoryCommentText(input.commentText)) trackedParentBySidecarId.set(String(input.commentId), String(input.trackedChangeThreadParentId));
8791
9132
  const commentRows = mappedCommentInputs.filter((input) => !trackedParentBySidecarId.has(String(input.commentId))).map((input) => {
@@ -8796,7 +9137,7 @@ var useCommentsStore = defineStore("comments", () => {
8796
9137
  }
8797
9138
  return useComment(input);
8798
9139
  });
8799
- const trackedRows = trackedChangeItems.map((item) => trackedChangesAdapter.mapV2TrackedChangeToCommentParams(item)).map((params) => trackedChangeDirectoryComment(params)).filter(Boolean);
9140
+ const trackedRows = trackedChangeItems.map((item) => trackedChangesAdapter.mapV2TrackedChangeToCommentParams(item)).map((params) => trackedChangeDirectoryComment(params)).filter(isPresent);
8800
9141
  reviewDirectoryList.value = [...commentRows, ...trackedRows];
8801
9142
  return {
8802
9143
  ok: true,
@@ -8877,13 +9218,6 @@ var useCommentsStore = defineStore("comments", () => {
8877
9218
  * - successful mutation refreshes from v2 list and reconciles
8878
9219
  * - rejection preserves rows and emits a rejected `comments-update` event
8879
9220
  * - committed-but-refresh-failed surfaces honestly (no reconcile with `[]`)
8880
- *
8881
- * @param {Object} [param0]
8882
- * @param {Object} [param0.superdoc] The SuperDoc instance.
8883
- * @param {string | null} [param0.parentCommentId] The comment or tracked-change row being replied to.
8884
- * @param {string} [param0.text] The reply text.
8885
- * @param {Array<{ name?: string | null, email?: string | null }>} [param0.mentions] Users mentioned in the reply.
8886
- * @param {{ kind: 'trackedChange', trackedChangeId: string, story?: unknown, side?: import('./helpers/tracked-change-thread-index.js').TrackedChangeSideGroup }} [param0.trackedChangeTarget] The tracked change a first reply anchors to.
8887
9221
  */
8888
9222
  const replyCommentV2 = async ({ superdoc, parentCommentId, text, mentions = [], trackedChangeTarget } = {}) => {
8889
9223
  if (commentsAreReadOnly()) return readOnlyMutationOutcome();
@@ -9158,10 +9492,15 @@ var useCommentsStore = defineStore("comments", () => {
9158
9492
  row?.threadingParentCommentId,
9159
9493
  row?.trackedChangeParentId,
9160
9494
  row?.trackedChangeThreadParentId
9161
- ].map((value) => normalizeCommentId(value)).filter(Boolean));
9495
+ ].map((value) => normalizeCommentId(value)).filter(isPresent));
9162
9496
  /** Keep one mounted review window plus rows whose interaction is still live. */
9163
9497
  const boundReviewPresentationRows = ({ documentId, visibleIds }) => {
9164
9498
  const currentRows = commentsList.value;
9499
+ if (currentRows.every((row) => {
9500
+ if (!belongsToTrackedChangeSyncDocument(row, documentId)) return true;
9501
+ const id = normalizeCommentId(row.commentId);
9502
+ return Boolean(id && visibleIds.has(id));
9503
+ })) return;
9165
9504
  const identityIdsByRow = /* @__PURE__ */ new Map();
9166
9505
  const identityIdsFor = (row) => {
9167
9506
  let ids = identityIdsByRow.get(row);
@@ -9179,7 +9518,7 @@ var useCommentsStore = defineStore("comments", () => {
9179
9518
  pendingComment.value?.commentId,
9180
9519
  instantSidebarAlignmentThreadId.value,
9181
9520
  instantSidebarAlignmentInstanceId.value
9182
- ].map((value) => normalizeCommentId(value)).filter(Boolean));
9521
+ ].map((value) => normalizeCommentId(value)).filter(isPresent));
9183
9522
  let changed = true;
9184
9523
  while (changed) {
9185
9524
  changed = false;
@@ -9247,7 +9586,8 @@ var useCommentsStore = defineStore("comments", () => {
9247
9586
  if (preparedReview?.reuseCatalog === true && committedReviewPresentations.value[normalizedDocumentId]) {
9248
9587
  const previous = committedReviewPresentations.value[normalizedDocumentId];
9249
9588
  const next = preparedReview.presentation;
9250
- if (!next || !Number.isSafeInteger(preparedReview.catalogVersion) || preparedReview.catalogVersion < 1 || previous.catalogVersion > preparedReview.catalogVersion || next.catalogVersion !== preparedReview.catalogVersion || previous.coordinate?.documentEpoch !== next.coordinate?.documentEpoch || previous.coordinate?.mountId !== next.coordinate?.mountId || next.coordinate?.reviewVersion <= previous.coordinate?.reviewVersion || next.coordinate?.commitSequence < previous.coordinate?.commitSequence) return {
9589
+ const catalogVersion = preparedReview.catalogVersion ?? NaN;
9590
+ if (!next || !Number.isSafeInteger(catalogVersion) || catalogVersion < 1 || (previous.catalogVersion ?? 0) > catalogVersion || next.catalogVersion !== preparedReview.catalogVersion || previous.coordinate?.documentEpoch !== next.coordinate?.documentEpoch || previous.coordinate?.mountId !== next.coordinate?.mountId || next.coordinate?.reviewVersion <= previous.coordinate?.reviewVersion || next.coordinate?.commitSequence < previous.coordinate?.commitSequence) return {
9251
9591
  ok: false,
9252
9592
  reason: "review-catalog-stale"
9253
9593
  };
@@ -9293,8 +9633,8 @@ var useCommentsStore = defineStore("comments", () => {
9293
9633
  previousPresentation.updateContext = presentation.updateContext;
9294
9634
  return {
9295
9635
  ok: true,
9296
- commentItems: preparedReview.comments.length,
9297
- trackedItems: preparedReview.trackedChanges.length
9636
+ commentItems: preparedReview?.comments.length,
9637
+ trackedItems: preparedReview?.trackedChanges.length
9298
9638
  };
9299
9639
  }
9300
9640
  let effectiveCommentItems = commentItems;
@@ -9308,7 +9648,7 @@ var useCommentsStore = defineStore("comments", () => {
9308
9648
  reason: seeded?.reason ?? "comment-catalog-seed-failed"
9309
9649
  };
9310
9650
  const selected = commentsAdapter.selectVisibleReviewComments?.();
9311
- if (selected?.ok === true && Array.isArray(selected.items) && (!Array.isArray(selected.unresolvedIds) || selected.unresolvedIds.length === 0)) effectiveCommentItems = selected.items;
9651
+ if (!isThenable$1(selected) && selected?.ok === true && Array.isArray(selected.items) && (!Array.isArray(selected.unresolvedIds) || selected.unresolvedIds.length === 0)) effectiveCommentItems = selected.items;
9312
9652
  }
9313
9653
  const fileType = (normalizedDocumentId ? superdocStore.getDocument(normalizedDocumentId) : null)?.type ?? null;
9314
9654
  let preparedInputs;
@@ -9325,16 +9665,16 @@ var useCommentsStore = defineStore("comments", () => {
9325
9665
  })) : effectiveCommentItems.filter((item) => !isSyntheticTrackedChangeCommentLaneItem(item)).map((item) => commentsAdapter.mapV2CommentToUseCommentInput(item, {
9326
9666
  fileId: normalizedDocumentId,
9327
9667
  fileType
9328
- })).filter(Boolean);
9668
+ })).filter(isPresent);
9329
9669
  preparedParams = preparedReview ? preparedReview.trackedChanges.map((params) => ({
9330
9670
  ...params,
9331
9671
  documentId: normalizedDocumentId
9332
- })) : trackedChangeItems.map((item) => trackedChangesAdapter.mapV2TrackedChangeToCommentParams(item)).filter(Boolean);
9672
+ })) : trackedChangeItems.map((item) => trackedChangesAdapter.mapV2TrackedChangeToCommentParams(item)).filter(isPresent);
9333
9673
  } catch (error) {
9334
9674
  return {
9335
9675
  ok: false,
9336
9676
  reason: "mapper-failed",
9337
- detail: error?.message ?? String(error)
9677
+ detail: errorDetail(error)
9338
9678
  };
9339
9679
  }
9340
9680
  if (typeof patch !== "function") return {
@@ -9362,7 +9702,7 @@ var useCommentsStore = defineStore("comments", () => {
9362
9702
  params?.trackedChangeCanonicalId,
9363
9703
  params?.trackedChangeAnchorKey
9364
9704
  ])
9365
- ].map((value) => normalizeCommentId(value)).filter(Boolean));
9705
+ ].map((value) => normalizeCommentId(value)).filter(isPresent));
9366
9706
  patch(() => {
9367
9707
  const previousPreparedPresentation = committedReviewPresentations.value[normalizedDocumentId] ?? null;
9368
9708
  if (preparedReview?.presentation) committedReviewPresentations.value = {
@@ -9377,7 +9717,8 @@ var useCommentsStore = defineStore("comments", () => {
9377
9717
  preparedParams,
9378
9718
  preparedPresentation: preparedReview?.presentation ?? null,
9379
9719
  previousPreparedPresentation,
9380
- pruneStale: false
9720
+ pruneStale: false,
9721
+ preserveDraft: true
9381
9722
  });
9382
9723
  reconcileCommentsFromV2({
9383
9724
  superdoc,
@@ -9410,7 +9751,7 @@ var useCommentsStore = defineStore("comments", () => {
9410
9751
  };
9411
9752
  const effectiveDocumentId = documentId ?? adapter.documentId ?? null;
9412
9753
  const normalizedEffectiveDocumentId = effectiveDocumentId != null ? String(effectiveDocumentId) : null;
9413
- const fileType = (effectiveDocumentId ? superdocStore.getDocument(effectiveDocumentId) : null)?.type ?? null;
9754
+ const fileType = (effectiveDocumentId ? findDocument(effectiveDocumentId) : null)?.type ?? null;
9414
9755
  const isBlankV2CommentText = (value) => {
9415
9756
  if (typeof value !== "string") return true;
9416
9757
  return value.replace(/<[^>]*>/g, "").replace(/&nbsp;/gi, " ").trim().length === 0;
@@ -9430,7 +9771,7 @@ var useCommentsStore = defineStore("comments", () => {
9430
9771
  const candidateInputs = Array.isArray(preparedInputs) ? preparedInputs : items.filter((item) => !isSyntheticTrackedChangeCommentLaneItem(item)).map((item) => adapter.mapV2CommentToUseCommentInput(item, {
9431
9772
  fileId: effectiveDocumentId,
9432
9773
  fileType
9433
- })).filter(Boolean);
9774
+ })).filter(isPresent);
9434
9775
  for (const input of candidateInputs) {
9435
9776
  if (isV2TrackedChangeSidecarCommentInput(input)) {
9436
9777
  trackedChangeSidecarParentByCommentId.set(String(input.commentId), String(input.trackedChangeThreadParentId));
@@ -9483,7 +9824,7 @@ var useCommentsStore = defineStore("comments", () => {
9483
9824
  existing.mentions = Array.isArray(input.mentions) ? input.mentions : [];
9484
9825
  existing.isInternal = typeof input.isInternal === "boolean" ? input.isInternal : existing.isInternal;
9485
9826
  if (typeof input.resolvedTime === "number") {
9486
- existing.resolvedTime = input.resolvedTimeWasSynthesized === true && typeof existing.resolvedTime === "number" ? existing.resolvedTime : input.resolvedTime;
9827
+ existing.resolvedTime = input.resolvedTimeWasSynthesized === true && typeof existing.resolvedTime === "number" ? existing.resolvedTime : input.resolvedTime ?? null;
9487
9828
  existing.resolvedByEmail = input.resolvedByEmail ?? null;
9488
9829
  existing.resolvedByName = input.resolvedByName ?? null;
9489
9830
  } else {
@@ -9557,7 +9898,7 @@ var useCommentsStore = defineStore("comments", () => {
9557
9898
  return {
9558
9899
  ok: false,
9559
9900
  reason: "comment-read-failed",
9560
- detail: err?.message ?? String(err)
9901
+ detail: errorDetail(err)
9561
9902
  };
9562
9903
  }
9563
9904
  if (!isCurrentV2CommentsAdapter(adapter)) return {
@@ -9583,8 +9924,6 @@ var useCommentsStore = defineStore("comments", () => {
9583
9924
  };
9584
9925
  /**
9585
9926
  * Cancel the pending comment
9586
- *
9587
- * @returns {void}
9588
9927
  */
9589
9928
  const cancelComment = (superdoc) => {
9590
9929
  removePendingComment(superdoc);
@@ -9594,9 +9933,6 @@ var useCommentsStore = defineStore("comments", () => {
9594
9933
  *
9595
9934
  * Replace-file swaps call this before hydrating the replacement so stale
9596
9935
  * comments never survive long enough to render beside the new content.
9597
- *
9598
- * @param {string | null | undefined} documentId
9599
- * @returns {void}
9600
9936
  */
9601
9937
  function removeCommentsForDocument(documentId) {
9602
9938
  const activeDocumentId = documentId != null ? String(documentId) : null;
@@ -9623,139 +9959,20 @@ var useCommentsStore = defineStore("comments", () => {
9623
9959
  const activeCommentId = activeComment.value != null ? String(activeComment.value) : null;
9624
9960
  if (activeCommentId && removedAliasIds.has(activeCommentId)) clearActiveCommentSelection();
9625
9961
  }
9626
- const getCommentDocumentId = (comment) => {
9627
- if (!comment) return null;
9628
- if (comment.fileId != null) return String(comment.fileId);
9629
- if (comment.documentId != null) return String(comment.documentId);
9630
- if (comment.selection?.documentId != null) return String(comment.selection.documentId);
9631
- return null;
9632
- };
9633
- const getOpenDocuments = () => {
9634
- const docs = Array.isArray(superdocStore.documents) ? superdocStore.documents : superdocStore.documents?.value;
9635
- return Array.isArray(docs) ? docs : [];
9636
- };
9637
- const getSingleOpenDocumentId = () => {
9638
- const docs = getOpenDocuments();
9639
- if (docs.length !== 1) return null;
9640
- return docs[0]?.id != null ? String(docs[0].id) : null;
9641
- };
9642
- const belongsToDocument = (comment, activeDocumentId, options = {}) => {
9643
- const { allowSingleDocumentMismatch = false } = options;
9644
- if (!activeDocumentId) return false;
9645
- const commentDocumentId = getCommentDocumentId(comment);
9646
- if (commentDocumentId) {
9647
- if (commentDocumentId === activeDocumentId) return true;
9648
- const singleOpenDocumentId = getSingleOpenDocumentId();
9649
- return allowSingleDocumentMismatch && singleOpenDocumentId === activeDocumentId;
9650
- }
9651
- return getSingleOpenDocumentId() === activeDocumentId;
9652
- };
9653
- const belongsToTrackedChangeSyncDocument = (comment, activeDocumentId) => {
9654
- return belongsToDocument(comment, activeDocumentId, { allowSingleDocumentMismatch: true });
9655
- };
9656
- const normalizeTrackedChangeDecisionType = (value) => {
9657
- const lower = typeof value === "string" ? value.toLowerCase() : "";
9658
- if (!lower) return null;
9659
- if (lower === "insert" || lower === "insertion" || lower === "trackinsert" || lower === "tableinsert" || lower === "paragraphsplit") return "insertion";
9660
- if (lower === "delete" || lower === "deletion" || lower === "trackdelete" || lower === "tabledelete") return "deletion";
9661
- return lower;
9662
- };
9663
- const trackedChangeDecisionKeepsCommentAnchor = ({ comment, decision } = {}) => {
9664
- const normalizedDecision = decision === "accept" || decision === "reject" ? decision : null;
9665
- if (!normalizedDecision) return false;
9666
- const type = normalizeTrackedChangeDecisionType(comment?.trackedChangeDisplayType) ?? normalizeTrackedChangeDecisionType(comment?.trackedChangeType);
9667
- return normalizedDecision === "accept" && type === "insertion" || normalizedDecision === "reject" && type === "deletion";
9668
- };
9669
- const getBodyTrackedChangeIdentityIds = (comment) => {
9670
- return [normalizeCommentId(comment.commentId), ...!normalizeCommentId(comment.trackedChangeCanonicalId) ? [
9671
- comment.importedId,
9672
- comment.trackedChangeAnchorKey,
9673
- ...normalizeTrackedChangePositionAliases(comment.trackedChangePositionAliases)
9674
- ] : []].map((id) => normalizeCommentId(id)).filter(Boolean);
9675
- };
9676
- const groupBodyTrackedChangeCommentsByIdentity = (comments) => {
9677
- const parents = comments.map((_, index) => index);
9678
- const findRoot = (index) => {
9679
- let root = index;
9680
- while (parents[root] !== root) root = parents[root];
9681
- while (parents[index] !== index) {
9682
- const next = parents[index];
9683
- parents[index] = root;
9684
- index = next;
9685
- }
9686
- return root;
9687
- };
9688
- const join = (left, right) => {
9689
- const leftRoot = findRoot(left);
9690
- const rightRoot = findRoot(right);
9691
- if (leftRoot === rightRoot) return;
9692
- parents[Math.max(leftRoot, rightRoot)] = Math.min(leftRoot, rightRoot);
9693
- };
9694
- const identityOwner = /* @__PURE__ */ new Map();
9695
- comments.forEach((comment, index) => {
9696
- getBodyTrackedChangeIdentityIds(comment).forEach((identityId) => {
9697
- const owner = identityOwner.get(identityId);
9698
- if (owner === void 0) identityOwner.set(identityId, index);
9699
- else join(index, owner);
9700
- });
9701
- });
9702
- const groups = /* @__PURE__ */ new Map();
9703
- comments.forEach((comment, index) => {
9704
- const root = findRoot(index);
9705
- const group = groups.get(root) ?? [];
9706
- group.push(comment);
9707
- groups.set(root, group);
9708
- });
9709
- return groups;
9710
- };
9711
- const mergeDuplicateBodyTrackedChangeComments = (groups) => {
9712
- const duplicates = /* @__PURE__ */ new Map();
9713
- groups.forEach(([survivor, ...duplicateRows]) => {
9714
- if (!survivor || duplicateRows.length === 0) return;
9715
- const resolvedDuplicate = duplicateRows.find((comment) => comment.resolvedTime != null);
9716
- if (survivor.resolvedTime == null && resolvedDuplicate) {
9717
- survivor.resolvedTime = resolvedDuplicate.resolvedTime;
9718
- survivor.resolvedById = resolvedDuplicate.resolvedById;
9719
- survivor.resolvedByEmail = resolvedDuplicate.resolvedByEmail;
9720
- survivor.resolvedByName = resolvedDuplicate.resolvedByName;
9721
- survivor.trackedChangeDecision = resolvedDuplicate.trackedChangeDecision;
9722
- }
9723
- survivor.trackedChangePositionAliases = normalizeTrackedChangePositionAliases([...normalizeTrackedChangePositionAliases(survivor.trackedChangePositionAliases), ...duplicateRows.flatMap((comment) => [
9724
- comment.commentId,
9725
- comment.importedId,
9726
- comment.trackedChangeCanonicalId,
9727
- ...normalizeTrackedChangePositionAliases(comment.trackedChangePositionAliases)
9728
- ])]);
9729
- duplicateRows.forEach((comment) => duplicates.set(comment, survivor));
9730
- });
9731
- return duplicates;
9732
- };
9733
- const createDroppedCommentIdMap = (duplicates) => {
9734
- const droppedCommentIds = /* @__PURE__ */ new Map();
9735
- duplicates.forEach((survivor, duplicate) => {
9736
- const fromId = normalizeCommentId(duplicate.commentId);
9737
- const toId = normalizeCommentId(survivor.commentId);
9738
- if (fromId && toId) droppedCommentIds.set(fromId, toId);
9739
- });
9740
- return droppedCommentIds;
9741
- };
9962
+ const { belongsToDocument, belongsToTrackedChangeSyncDocument } = createCommentDocumentScope(() => superdocStore.documents);
9742
9963
  const remapTrackedChangeReferences = (droppedCommentIds, activeDocumentId) => {
9743
9964
  const remapSelection = (selection) => {
9744
9965
  const selectedId = normalizeCommentId(selection.value);
9745
- if (selectedId && droppedCommentIds.has(selectedId)) selection.value = droppedCommentIds.get(selectedId);
9966
+ const replacementId = selectedId ? droppedCommentIds.get(selectedId) : void 0;
9967
+ if (replacementId) selection.value = replacementId;
9746
9968
  };
9747
9969
  remapSelection(activeComment);
9748
9970
  remapSelection(activeFloatingCommentInstanceId);
9749
9971
  commentsList.value.forEach((row) => {
9750
9972
  if (!belongsToTrackedChangeSyncDocument(row, activeDocumentId)) return;
9751
9973
  const rowIdentityUpdates = {};
9752
- for (const field of [
9753
- "trackedChangeThreadParentId",
9754
- "trackedChangeParentId",
9755
- "threadingParentCommentId",
9756
- "parentCommentId"
9757
- ]) {
9758
- const replacementId = droppedCommentIds.get(normalizeCommentId(row?.[field]));
9974
+ for (const field of PARENT_LINK_FIELDS) {
9975
+ const replacementId = droppedCommentIds.get(normalizeCommentId(row?.[field]) ?? "");
9759
9976
  if (!replacementId) continue;
9760
9977
  row[field] = replacementId;
9761
9978
  rowIdentityUpdates[field] = replacementId;
@@ -9764,9 +9981,7 @@ var useCommentsStore = defineStore("comments", () => {
9764
9981
  });
9765
9982
  };
9766
9983
  const compactDuplicateBodyTrackedChangeComments = (activeDocumentId) => {
9767
- const candidates = commentsList.value.filter((comment) => isBodyTrackedChangeComment(comment) && belongsToTrackedChangeSyncDocument(comment, activeDocumentId));
9768
- const groups = groupBodyTrackedChangeCommentsByIdentity(candidates);
9769
- const duplicates = mergeDuplicateBodyTrackedChangeComments(groups);
9984
+ const duplicates = mergeDuplicateBodyTrackedChangeComments(groupBodyTrackedChangeCommentsByIdentity(commentsList.value.filter((comment) => isBodyTrackedChangeComment(comment) && belongsToTrackedChangeSyncDocument(comment, activeDocumentId))));
9770
9985
  if (!duplicates.size) return 0;
9771
9986
  commentsList.value = commentsList.value.filter((comment) => !duplicates.has(comment));
9772
9987
  remapTrackedChangeReferences(createDroppedCommentIdMap(duplicates), activeDocumentId);
@@ -9778,10 +9993,6 @@ var useCommentsStore = defineStore("comments", () => {
9778
9993
  *
9779
9994
  * Pruning is scoped to the active editor document so replay in one document does not
9780
9995
  * delete tracked-change comments from other open documents.
9781
- *
9782
- * @param {Set<string>} liveTrackedChangeIds IDs currently present in editor marks.
9783
- * @param {string | null} activeDocumentId Document currently being synced.
9784
- * @returns {void}
9785
9996
  */
9786
9997
  const pruneStaleTrackedChangeComments = (liveTrackedChangeIds, liveTrackedChangeAnchorKeys, activeDocumentId, superdoc = null, { broadcastChanges = true } = {}) => withInteractionSpan("store.trackedChanges.pruneStaleComments", "store-reconciliation", {
9787
9998
  activeDocumentId: activeDocumentId ?? null,
@@ -9826,7 +10037,7 @@ var useCommentsStore = defineStore("comments", () => {
9826
10037
  comment: payload
9827
10038
  };
9828
10039
  if (broadcastChanges) {
9829
- syncCommentsToClients(superdoc, event);
10040
+ syncCommentsToClients(requireHost(superdoc, "pruneStaleTrackedChangeComments"), event);
9830
10041
  superdoc?.emit?.("comments-update", event);
9831
10042
  }
9832
10043
  });
@@ -9858,14 +10069,10 @@ var useCommentsStore = defineStore("comments", () => {
9858
10069
  const event = {
9859
10070
  type: COMMENT_EVENTS.DELETED,
9860
10071
  comment: payload,
9861
- changes: [{
9862
- key: "deleted",
9863
- commentId: payload.commentId,
9864
- fileId: payload.fileId
9865
- }]
10072
+ changes: getDeletedCommentChanges([payload, comment], payload.fileId)
9866
10073
  };
9867
10074
  if (broadcastChanges) {
9868
- syncCommentsToClients(superdoc, event);
10075
+ syncCommentsToClients(requireHost(superdoc, "pruneStaleTrackedChangeComments"), event);
9869
10076
  superdoc?.emit?.("comments-update", event);
9870
10077
  }
9871
10078
  });
@@ -9888,7 +10095,6 @@ var useCommentsStore = defineStore("comments", () => {
9888
10095
  const resolveArgs = {
9889
10096
  email: superdoc?.user?.email ?? null,
9890
10097
  name: superdoc?.user?.name ?? null,
9891
- superdoc,
9892
10098
  reconciliationToken: COMMENT_RECONCILIATION_TOKEN
9893
10099
  };
9894
10100
  let resolvedCount = 0;
@@ -9899,27 +10105,18 @@ var useCommentsStore = defineStore("comments", () => {
9899
10105
  trackedChangeThreadParentIdForComment(linkedComment),
9900
10106
  linkedComment.threadingParentCommentId,
9901
10107
  linkedComment.parentCommentId
9902
- ].map((id) => normalizeCommentId(id)).filter(Boolean).some((id) => trackedChangeIds.has(id))) return;
10108
+ ].map((id) => normalizeCommentId(id)).filter(isPresent).some((id) => trackedChangeIds.has(id))) return;
9903
10109
  if (typeof linkedComment.resolveComment === "function") {
9904
- linkedComment.resolveComment(resolveArgs);
10110
+ linkedComment.resolveComment({
10111
+ ...resolveArgs,
10112
+ superdoc: requireHost(superdoc, "resolveLinkedCommentsForTrackedChangeDecision")
10113
+ });
9905
10114
  resolvedCount += 1;
9906
10115
  }
9907
10116
  });
9908
10117
  return resolvedCount;
9909
10118
  };
9910
- const collectTrackedChangeDecisionIds = (comment) => {
9911
- const trackedChangeIds = new Set(getCommentAliasIds(comment));
9912
- if (comment?.trackedChangeCanonicalId != null) trackedChangeIds.add(String(comment.trackedChangeCanonicalId));
9913
- const anchorKey = comment?.trackedChangeAnchorKey != null ? String(comment.trackedChangeAnchorKey) : null;
9914
- if (anchorKey) {
9915
- trackedChangeIds.add(anchorKey);
9916
- if (anchorKey.startsWith("tc::")) {
9917
- const rawId = anchorKey.slice(anchorKey.lastIndexOf("::") + 2);
9918
- if (rawId) trackedChangeIds.add(rawId);
9919
- }
9920
- }
9921
- return trackedChangeIds;
9922
- };
10119
+ const collectTrackedChangeDecisionIds = (comment) => collectTrackedChangeDecisionIdsWithAliases(comment, getCommentAliasIds(comment));
9923
10120
  const getTrackedChangeDecisionLinks = (comment) => {
9924
10121
  const linked = [];
9925
10122
  const seen = /* @__PURE__ */ new Set();
@@ -9930,28 +10127,6 @@ var useCommentsStore = defineStore("comments", () => {
9930
10127
  }
9931
10128
  return linked;
9932
10129
  };
9933
- const collectTrackedChangeEntityIdsFromReceipt = (receipt) => {
9934
- const record = receipt && typeof receipt === "object" ? receipt : null;
9935
- if (!record?.success) return /* @__PURE__ */ new Set();
9936
- const ids = /* @__PURE__ */ new Set();
9937
- const collect = (entry) => {
9938
- const ref = entry && typeof entry === "object" ? entry : null;
9939
- if (ref?.kind !== "entity" || ref.entityType !== "trackedChange") return;
9940
- if (ref.entityId != null && String(ref.entityId).length > 0) ids.add(String(ref.entityId));
9941
- };
9942
- if (Array.isArray(record.removed)) record.removed.forEach(collect);
9943
- if (Array.isArray(record.invalidatedRefs)) record.invalidatedRefs.forEach(collect);
9944
- return ids;
9945
- };
9946
- const collectReliableDecidedTrackedChangeIds = (outcome) => {
9947
- const ids = /* @__PURE__ */ new Set();
9948
- if (outcome?.decidedId != null && String(outcome.decidedId).length > 0) ids.add(String(outcome.decidedId));
9949
- if (Array.isArray(outcome?.decidedIds)) outcome.decidedIds.forEach((id) => {
9950
- if (id != null && String(id).length > 0) ids.add(String(id));
9951
- });
9952
- collectTrackedChangeEntityIdsFromReceipt(outcome?.receipt).forEach((id) => ids.add(id));
9953
- return ids;
9954
- };
9955
10130
  const detachLinkedCommentsForTrackedChangeDecision = ({ comment, decision } = {}) => {
9956
10131
  if (!comment?.trackedChange) return 0;
9957
10132
  if (!trackedChangeDecisionKeepsCommentAnchor({
@@ -10032,7 +10207,7 @@ var useCommentsStore = defineStore("comments", () => {
10032
10207
  if (previousCommentId) anchorSourceIds.add(previousCommentId);
10033
10208
  if (previousCanonicalId) anchorSourceIds.add(previousCanonicalId);
10034
10209
  }
10035
- if (comment.trackedChangeAnchorKey == null || anchorSourceIds.has(currentAnchorParts?.rawId)) comment.trackedChangeAnchorKey = toAnchor;
10210
+ if (comment.trackedChangeAnchorKey == null || anchorSourceIds.has(currentAnchorParts?.rawId ?? "")) comment.trackedChangeAnchorKey = toAnchor;
10036
10211
  comment.trackedChangePositionAliases = normalizeTrackedChangePositionAliases([...Array.isArray(comment.trackedChangePositionAliases) ? comment.trackedChangePositionAliases : [], fromId]);
10037
10212
  const identitySourceIds = /* @__PURE__ */ new Set([fromId]);
10038
10213
  if (advancesRemapLineage) {
@@ -10043,33 +10218,28 @@ var useCommentsStore = defineStore("comments", () => {
10043
10218
  if (row === comment) return;
10044
10219
  if (!belongsToTrackedChangeSyncDocument(row, normalizedDocumentId)) return;
10045
10220
  const rowIdentityUpdates = {};
10046
- for (const field of [
10047
- "trackedChangeThreadParentId",
10048
- "trackedChangeParentId",
10049
- "threadingParentCommentId",
10050
- "parentCommentId"
10051
- ]) {
10052
- if (!identitySourceIds.has(normalizeCommentId(row?.[field]))) continue;
10221
+ for (const field of PARENT_LINK_FIELDS) {
10222
+ if (!identitySourceIds.has(normalizeCommentId(row?.[field]) ?? "")) continue;
10053
10223
  row[field] = toId;
10054
10224
  rowIdentityUpdates[field] = toId;
10055
10225
  }
10056
10226
  if (Object.keys(rowIdentityUpdates).length && typeof row.updateIdentityValues === "function") row.updateIdentityValues(rowIdentityUpdates);
10057
10227
  });
10058
10228
  if (Object.keys(commentIdentityUpdates).length && typeof comment.updateIdentityValues === "function") comment.updateIdentityValues(commentIdentityUpdates);
10059
- const previousEntry = previousPositionEntry ?? [positions[fromId], positions[buildBodyTrackedChangeAnchorKey(fromId)]].find((entry) => entry && positionEntryMatchesTrackedChangeStory(entry, comment));
10229
+ const previousEntry = previousPositionEntry ?? [positions[fromId], positions[String(buildBodyTrackedChangeAnchorKey(fromId))]].find((entry) => entry && positionEntryMatchesTrackedChangeStory(entry, comment));
10060
10230
  if (previousEntry) {
10061
- for (const key of [toId, toAnchor]) if (positions[key] === void 0) {
10231
+ for (const key of [toId, String(toAnchor)]) if (positions[key] === void 0) {
10062
10232
  positions[key] = previousEntry;
10063
10233
  positionsChanged = true;
10064
10234
  }
10065
10235
  }
10066
- if (identitySourceIds.has(normalizeCommentId(activeComment.value))) activeComment.value = toId;
10067
- if (identitySourceIds.has(normalizeCommentId(activeFloatingCommentInstanceId.value))) activeFloatingCommentInstanceId.value = toId;
10236
+ if (identitySourceIds.has(normalizeCommentId(activeComment.value) ?? "")) activeComment.value = toId;
10237
+ if (identitySourceIds.has(normalizeCommentId(activeFloatingCommentInstanceId.value) ?? "")) activeFloatingCommentInstanceId.value = toId;
10068
10238
  }
10069
10239
  if (positionsChanged) editorCommentPositions.value = { ...positions };
10070
10240
  };
10071
10241
  const pruneDecidedTrackedChangeRow = ({ superdoc, comment: targetComment, decidedId, decidedIds, documentId } = {}) => {
10072
- const normalizedDecidedIds = new Set((Array.isArray(decidedIds) || decidedIds instanceof Set ? Array.from(decidedIds) : [decidedId]).map((id) => normalizeCommentId(id)).filter(Boolean));
10242
+ const normalizedDecidedIds = new Set((Array.isArray(decidedIds) || decidedIds instanceof Set ? Array.from(decidedIds) : [decidedId]).map((id) => normalizeCommentId(id)).filter(isPresent));
10073
10243
  const normalizedDocumentId = normalizeCommentId(documentId);
10074
10244
  if (!normalizedDecidedIds.size || !normalizedDocumentId) return {
10075
10245
  removed: false,
@@ -10127,39 +10297,8 @@ var useCommentsStore = defineStore("comments", () => {
10127
10297
  removedIds
10128
10298
  };
10129
10299
  };
10130
- const clearV2ActiveTrackedChangeTarget = (adapter, decidedId) => {
10131
- if (!decidedId || typeof adapter?.clearActiveTrackedChangeTargetIfMatches !== "function") return;
10132
- try {
10133
- adapter.clearActiveTrackedChangeTargetIfMatches(decidedId);
10134
- } catch {}
10135
- };
10136
- const waitForTrackedDecisionCanonicalPaint = async ({ superdoc, receipt } = {}) => {
10137
- const readiness = superdoc?.activeEditor?.documentMutationReadiness;
10138
- if (receipt?.success !== true || typeof receipt?.txId !== "string" || !receipt.txId || typeof readiness?.whenPainted !== "function") return;
10139
- try {
10140
- await readiness.whenPainted(receipt);
10141
- const requestFrame = globalThis.requestAnimationFrame;
10142
- if (typeof requestFrame === "function") await new Promise((resolve) => {
10143
- let settled = false;
10144
- const finish = () => {
10145
- if (settled) return;
10146
- settled = true;
10147
- clearTimeout(fallback);
10148
- resolve();
10149
- };
10150
- const fallback = setTimeout(finish, 250);
10151
- requestFrame(() => requestFrame(() => setTimeout(finish, 0)));
10152
- });
10153
- } catch {}
10154
- };
10155
10300
  /**
10156
10301
  * Accept or reject the tracked change behind a sidebar row.
10157
- *
10158
- * @param {Object} param0
10159
- * @param {Object} param0.superdoc The SuperDoc instance.
10160
- * @param {Object} param0.comment The tracked-change row.
10161
- * @param {string} param0.decision `'accept'` or `'reject'`.
10162
- * @returns {{ ok: boolean, success?: boolean, reason?: string } | Promise<{ ok: boolean, success?: boolean, reason?: string }>} The decision outcome.
10163
10302
  */
10164
10303
  const decideTrackedChangeFromSidebar = ({ superdoc, comment, decision }) => {
10165
10304
  const disabledReason = trackedChangeDecisionDisabledReason(superdoc);
@@ -10187,7 +10326,7 @@ var useCommentsStore = defineStore("comments", () => {
10187
10326
  return {
10188
10327
  ok: false,
10189
10328
  reason: "adapter-threw",
10190
- detail: err?.message ?? String(err)
10329
+ detail: errorDetail(err)
10191
10330
  };
10192
10331
  }
10193
10332
  })().then(async (outcome) => {
@@ -10240,9 +10379,11 @@ var useCommentsStore = defineStore("comments", () => {
10240
10379
  if (!activeEditor) return { ok: false };
10241
10380
  const id = comment.commentId ?? comment.importedId;
10242
10381
  if (!id) return { ok: false };
10243
- const story = comment.trackedChangeStory ?? void 0;
10382
+ const rawStory = comment.trackedChangeStory ?? void 0;
10383
+ const story = isStoryLocator(rawStory) ? rawStory : void 0;
10244
10384
  const documentApi = typeof activeEditor.doc === "object" ? activeEditor.doc : null;
10245
10385
  if (documentApi?.trackChanges?.decide) try {
10386
+ if (rawStory && !story) throw new TypeError("trackedChangeStory must be a valid StoryLocator");
10246
10387
  const target = story ? {
10247
10388
  id,
10248
10389
  story
@@ -10255,7 +10396,7 @@ var useCommentsStore = defineStore("comments", () => {
10255
10396
  success: Boolean(receipt?.success)
10256
10397
  }));
10257
10398
  } catch (error) {
10258
- if (story) return {
10399
+ if (rawStory) return {
10259
10400
  ok: false,
10260
10401
  error
10261
10402
  };
@@ -10302,9 +10443,9 @@ var useCommentsStore = defineStore("comments", () => {
10302
10443
  documentId: effectiveDocumentId
10303
10444
  })).removed
10304
10445
  };
10305
- const normalizedRemovedIds = new Set(Array.from(removedIds ?? []).map((id) => normalizeCommentId(id)).filter(Boolean));
10306
- const normalizedUpsertIds = [...new Set(Array.from(upsertIds ?? []).map((id) => normalizeCommentId(id)).filter(Boolean))];
10307
- const remappedSourceIds = new Set((Array.isArray(remappedPairs) ? remappedPairs : []).map((pair) => normalizeCommentId(pair?.from)).filter(Boolean));
10446
+ const normalizedRemovedIds = new Set(Array.from(removedIds ?? []).map((id) => normalizeCommentId(id)).filter(isPresent));
10447
+ const normalizedUpsertIds = [...new Set(Array.from(upsertIds ?? []).map((id) => normalizeCommentId(id)).filter(isPresent))];
10448
+ const remappedSourceIds = new Set((Array.isArray(remappedPairs) ? remappedPairs : []).map((pair) => normalizeCommentId(pair?.from)).filter(isPresent));
10308
10449
  const locallyRemovedIds = new Set([...normalizedRemovedIds].filter((id) => !remappedSourceIds.has(id)));
10309
10450
  if (locallyRemovedIds.size > 0 && effectiveDocumentId != null) pruneDecidedTrackedChangeRow({
10310
10451
  superdoc,
@@ -10323,6 +10464,10 @@ var useCommentsStore = defineStore("comments", () => {
10323
10464
  const byIdentityAlias = /* @__PURE__ */ new Map();
10324
10465
  const aliasesByRow = /* @__PURE__ */ new Map();
10325
10466
  const indexedRows = /* @__PURE__ */ new Set();
10467
+ const indexedIdentities = [];
10468
+ let building = true;
10469
+ let reusable = true;
10470
+ let reused = false;
10326
10471
  let aliasLookups = 0;
10327
10472
  let candidateVisits = 0;
10328
10473
  let rowAliasBuilds = 0;
@@ -10346,6 +10491,7 @@ var useCommentsStore = defineStore("comments", () => {
10346
10491
  index.set(alias, bucket);
10347
10492
  };
10348
10493
  const add = (comment) => {
10494
+ if (!building) reusable = false;
10349
10495
  if (!comment?.trackedChange) return;
10350
10496
  if (documentId && !belongsToTrackedChangeSyncDocument(comment, documentId)) return;
10351
10497
  indexedRows.add(comment);
@@ -10357,19 +10503,49 @@ var useCommentsStore = defineStore("comments", () => {
10357
10503
  rowAliasBuilds += 1;
10358
10504
  aliasesByRow.set(comment, aliases);
10359
10505
  aliases.forEach((alias) => addAlias(byIdentityAlias, alias, comment));
10506
+ if (building) indexedIdentities.push({
10507
+ row: comment,
10508
+ aliases,
10509
+ canonicalId: normalizeCommentId(comment.trackedChangeCanonicalId)
10510
+ });
10360
10511
  };
10361
10512
  commentsList.value.forEach(add);
10513
+ building = false;
10362
10514
  return {
10515
+ inUse: false,
10363
10516
  add,
10364
10517
  aliasesFor,
10365
- work: () => ({
10366
- trackedRowsIndexed: indexedRows.size,
10367
- invalidatedIdMembershipChecks: 0,
10368
- incomingAliasLookups: aliasLookups,
10369
- candidateVisits,
10370
- rowAliasBuilds,
10371
- rowAliasCacheHits
10372
- }),
10518
+ isCurrent: () => {
10519
+ if (!reusable) return false;
10520
+ let index = 0;
10521
+ for (const row of commentsList.value) {
10522
+ if (!row?.trackedChange || documentId && !belongsToTrackedChangeSyncDocument(row, documentId)) continue;
10523
+ const previous = indexedIdentities[index++];
10524
+ if (!previous || previous.row !== row || previous.canonicalId !== normalizeCommentId(row.trackedChangeCanonicalId)) return false;
10525
+ const aliases = getCommentAliasIds(row);
10526
+ rowAliasBuilds += 1;
10527
+ if (aliases.length !== previous.aliases.length || aliases.some((alias, i) => alias !== previous.aliases[i])) return false;
10528
+ }
10529
+ reused = index === indexedIdentities.length;
10530
+ return reused;
10531
+ },
10532
+ work: () => {
10533
+ const result = {
10534
+ trackedRowsIndexed: indexedRows.size,
10535
+ invalidatedIdMembershipChecks: 0,
10536
+ incomingAliasLookups: aliasLookups,
10537
+ candidateVisits,
10538
+ rowAliasBuilds,
10539
+ rowAliasCacheHits,
10540
+ indexReused: Number(reused)
10541
+ };
10542
+ aliasLookups = 0;
10543
+ candidateVisits = 0;
10544
+ rowAliasBuilds = 0;
10545
+ rowAliasCacheHits = 0;
10546
+ reused = false;
10547
+ return result;
10548
+ },
10373
10549
  candidates({ changeId, importedId, anchorKey, canonicalId, positionAliases = [] } = {}) {
10374
10550
  const out = /* @__PURE__ */ new Set();
10375
10551
  const include = (value) => {
@@ -10392,7 +10568,30 @@ var useCommentsStore = defineStore("comments", () => {
10392
10568
  }
10393
10569
  };
10394
10570
  };
10395
- const reconcileTrackedChangesFromV2 = ({ superdoc, adapter, documentId, items, pruneStale = true, preparedParams = null, preparedPresentation = null, previousPreparedPresentation = null, liveIds: suppliedLiveIds = null, liveAnchorKeys: suppliedLiveAnchorKeys = null, hydrationGeneration } = {}) => withInteractionSpan("store.trackedChanges.reconcile", "store-reconciliation", {
10571
+ const getTrackedChangeBatchIdentityIndex = (documentId) => {
10572
+ const cached = cachedTrackedChangeBatchIdentityIndex;
10573
+ if (cached?.documentId === documentId && !cached.index.inUse) {
10574
+ cached.index.inUse = true;
10575
+ let current = false;
10576
+ try {
10577
+ current = cached.index.isCurrent() && cachedTrackedChangeBatchIdentityIndex === cached;
10578
+ } finally {
10579
+ if (!current) {
10580
+ cached.index.inUse = false;
10581
+ if (cachedTrackedChangeBatchIdentityIndex === cached) cachedTrackedChangeBatchIdentityIndex = null;
10582
+ }
10583
+ }
10584
+ if (current) return cached.index;
10585
+ }
10586
+ const index = createTrackedChangeBatchIdentityIndex(documentId);
10587
+ index.inUse = true;
10588
+ cachedTrackedChangeBatchIdentityIndex = {
10589
+ documentId,
10590
+ index
10591
+ };
10592
+ return index;
10593
+ };
10594
+ const reconcileTrackedChangesFromV2 = ({ superdoc, adapter, documentId, items, pruneStale = true, preparedParams = null, preserveDraft = false, preparedPresentation = null, previousPreparedPresentation = null, liveIds: suppliedLiveIds = null, liveAnchorKeys: suppliedLiveAnchorKeys = null, hydrationGeneration } = {}) => withInteractionSpan("store.trackedChanges.reconcile", "store-reconciliation", {
10396
10595
  documentId: documentId ?? null,
10397
10596
  itemCount: Array.isArray(items) ? items.length : null,
10398
10597
  pruneStale,
@@ -10404,33 +10603,36 @@ var useCommentsStore = defineStore("comments", () => {
10404
10603
  const liveAnchorKeys = suppliedLiveAnchorKeys instanceof Set ? suppliedLiveAnchorKeys : /* @__PURE__ */ new Set();
10405
10604
  const liveIds = suppliedLiveIds instanceof Set ? suppliedLiveIds : /* @__PURE__ */ new Set();
10406
10605
  let appliedCount = 0;
10407
- const trackedChangeIdentityIndex = createTrackedChangeBatchIdentityIndex(effectiveDocumentId == null ? null : String(effectiveDocumentId));
10606
+ const trackedChangeIdentityIndex = getTrackedChangeBatchIdentityIndex(effectiveDocumentId == null ? null : String(effectiveDocumentId));
10408
10607
  const identitySpan = startInteractionSpan("store.trackedChanges.batchIdentity", "store-reconciliation", { itemCount: items.length });
10409
- const paramsList = Array.isArray(preparedParams) ? preparedParams : items.map((item) => adapter.mapV2TrackedChangeToCommentParams(item));
10410
- const previousCoordinate = previousPreparedPresentation?.coordinate;
10411
- const coordinate = preparedPresentation?.coordinate;
10412
- const retainUnchangedRows = !pruneStale && previousCoordinate && coordinate && coordinate.documentEpoch === preparedPresentation.epoch && coordinate.documentEpoch === previousPreparedPresentation.epoch && coordinate.documentEpoch === previousCoordinate.documentEpoch && typeof coordinate.mountId === "string" && coordinate.mountId.length > 0 && coordinate.mountId === previousCoordinate.mountId && preparedPresentation.baseVersion === previousPreparedPresentation.version && preparedPresentation.version === preparedPresentation.baseVersion + 1 && coordinate.reviewVersion === preparedPresentation.version && previousCoordinate.reviewVersion === previousPreparedPresentation.version && Number.isSafeInteger(coordinate.commitSequence) && Number.isSafeInteger(previousCoordinate.commitSequence) && coordinate.commitSequence >= previousCoordinate.commitSequence;
10608
+ let enteredBatch = false;
10413
10609
  try {
10610
+ const paramsList = Array.isArray(preparedParams) ? preparedParams : items.map((item) => adapter.mapV2TrackedChangeToCommentParams(item));
10611
+ const previousCoordinate = previousPreparedPresentation?.coordinate;
10612
+ const coordinate = preparedPresentation?.coordinate;
10613
+ const retainUnchangedRows = !pruneStale && previousCoordinate && coordinate && coordinate.documentEpoch === preparedPresentation.epoch && coordinate.documentEpoch === previousPreparedPresentation.epoch && coordinate.documentEpoch === previousCoordinate.documentEpoch && typeof coordinate.mountId === "string" && coordinate.mountId.length > 0 && coordinate.mountId === previousCoordinate.mountId && preparedPresentation.baseVersion === previousPreparedPresentation.version && preparedPresentation.version === preparedPresentation.baseVersion + 1 && coordinate.reviewVersion === preparedPresentation.version && previousCoordinate.reviewVersion === previousPreparedPresentation.version && Number.isSafeInteger(coordinate.commitSequence) && Number.isSafeInteger(previousCoordinate.commitSequence) && coordinate.commitSequence >= previousCoordinate.commitSequence;
10614
+ enteredBatch = true;
10414
10615
  for (const params of paramsList) {
10415
10616
  if (!params) continue;
10416
10617
  let retainUnchangedRow = false;
10618
+ const anchorParts = parseTrackedChangeAnchorKey(params.trackedChangeAnchorKey);
10619
+ const anchorStoryKey = anchorParts ? normalizeTrackedChangeStoryKey(anchorParts.storyKey) : void 0;
10417
10620
  if (preparedPresentation) {
10418
10621
  if (preparedPresentation !== committedReviewPresentations.value[String(effectiveDocumentId)]) continue;
10419
10622
  if (params.documentId != null && String(params.documentId) !== String(effectiveDocumentId)) continue;
10420
10623
  const id = normalizeCommentId(params.changeId);
10421
- const version = preparedPresentation.rowVersions?.[id];
10624
+ const version = preparedPresentation.rowVersions?.[String(id)] ?? NaN;
10422
10625
  if (!id || !Number.isSafeInteger(version) || version < 1) continue;
10423
10626
  const existing = [...trackedChangeIdentityIndex.candidates({ changeId: id })].filter((row) => String(row.fileId) === String(effectiveDocumentId) && String(row.commentId) === id);
10424
10627
  if (existing.length > 1) continue;
10425
10628
  const row = existing[0];
10426
10629
  if (row && row.trackedChangeCanonicalId !== params.trackedChangeCanonicalId) {
10427
- const previousVersion = previousPreparedPresentation?.rowVersions?.[id];
10428
- if (!row.trackedChange || params.event !== "add" || typeof params.trackedChangeCanonicalId !== "string" || !params.trackedChangeCanonicalId || !Number.isSafeInteger(previousVersion) || previousVersion < 1 || version <= previousVersion || normalizeTrackedChangeStoryKey(getTrackedChangeStoryKey(row)) !== normalizeTrackedChangeStoryKey(parseTrackedChangeAnchorKey(params.trackedChangeAnchorKey)?.storyKey) || row.trackedChangeType !== params.trackedChangeType || row.trackedChangeDisplayType !== params.trackedChangeDisplayType) continue;
10630
+ const previousVersion = previousPreparedPresentation?.rowVersions?.[id] ?? NaN;
10631
+ if (!row.trackedChange || params.event !== "add" || typeof params.trackedChangeCanonicalId !== "string" || !params.trackedChangeCanonicalId || !Number.isSafeInteger(previousVersion) || previousVersion < 1 || version <= previousVersion || normalizeTrackedChangeStoryKey(getTrackedChangeStoryKey(row)) !== anchorStoryKey || row.trackedChangeType !== params.trackedChangeType || row.trackedChangeDisplayType !== params.trackedChangeDisplayType) continue;
10429
10632
  row.trackedChangeCanonicalId = params.trackedChangeCanonicalId;
10430
- row.updateIdentityValues?.({ trackedChangeCanonicalId: params.trackedChangeCanonicalId });
10431
10633
  trackedChangeIdentityIndex.add(row);
10432
10634
  }
10433
- retainUnchangedRow = retainUnchangedRows && row?.trackedChange === true && previousPreparedPresentation.rowVersions?.[id] === version && row.trackedChangeCanonicalId === params.trackedChangeCanonicalId && row.trackedChangeType === params.trackedChangeType && row.trackedChangeDisplayType === params.trackedChangeDisplayType && normalizeTrackedChangeStoryKey(getTrackedChangeStoryKey(row)) === normalizeTrackedChangeStoryKey(parseTrackedChangeAnchorKey(params.trackedChangeAnchorKey)?.storyKey) && !row.resolvedTime && !row.resolvedById && !row.resolvedByEmail && !row.resolvedByName;
10635
+ retainUnchangedRow = retainUnchangedRows === true && row?.trackedChange === true && previousPreparedPresentation.rowVersions?.[id] === version && row.trackedChangeCanonicalId === params.trackedChangeCanonicalId && row.trackedChangeType === params.trackedChangeType && row.trackedChangeDisplayType === params.trackedChangeDisplayType && normalizeTrackedChangeStoryKey(getTrackedChangeStoryKey(row)) === anchorStoryKey && !row.resolvedTime && !row.resolvedById && !row.resolvedByEmail && !row.resolvedByName;
10434
10636
  }
10435
10637
  if (params.event === "omit") {
10436
10638
  if (params.changeId != null) liveIds.add(String(params.changeId));
@@ -10444,12 +10646,15 @@ var useCommentsStore = defineStore("comments", () => {
10444
10646
  superdoc,
10445
10647
  params,
10446
10648
  broadcastChanges: false,
10447
- trackedChangeIdentityIndex
10649
+ trackedChangeIdentityIndex,
10650
+ preserveDraft
10448
10651
  });
10449
10652
  appliedCount += 1;
10450
10653
  }
10451
10654
  } finally {
10452
- endInteractionSpan(identitySpan, trackedChangeIdentityIndex.work());
10655
+ const work = trackedChangeIdentityIndex.work();
10656
+ trackedChangeIdentityIndex.inUse = false;
10657
+ if (enteredBatch) endInteractionSpan(identitySpan, work);
10453
10658
  }
10454
10659
  if (!effectiveDocumentId || !pruneStale) {
10455
10660
  if (effectiveDocumentId) compactDuplicateBodyTrackedChangeComments(String(effectiveDocumentId));
@@ -10471,22 +10676,18 @@ var useCommentsStore = defineStore("comments", () => {
10471
10676
  return commentsList.value.filter((comment) => {
10472
10677
  if (comment?.trackedChange !== true) return false;
10473
10678
  if (normalizedDocumentId == null) return true;
10474
- const rowDocumentId = comment?.documentId ?? comment?.fileId ?? null;
10679
+ const rowDocumentId = Reflect.get(comment, "documentId") ?? comment?.fileId ?? null;
10475
10680
  return rowDocumentId != null && String(rowDocumentId) === normalizedDocumentId;
10476
10681
  }).length;
10477
10682
  };
10478
10683
  /**
10479
10684
  * Triggered when the editor locations are updated
10480
10685
  * Updates floating comment locations from the editor
10481
- *
10482
- * @param {Object} allCommentPositions The collected geometry keyed by comment / tracked-change id
10483
- * @param {{ retainMissingTrackedChangeGeometry?: boolean, retainedTrackedChangeIds?: Iterable<string> }} options Geometry reconciliation options
10484
- * @returns {void}
10485
10686
  */
10486
10687
  const handleEditorLocationsUpdate = (allCommentPositions, options = {}) => {
10487
10688
  if (allCommentPositions == null) return;
10488
10689
  const retainMissingTrackedChangeGeometry = options?.retainMissingTrackedChangeGeometry === true;
10489
- const retainedTrackedChangeIds = retainMissingTrackedChangeGeometry && options?.retainedTrackedChangeIds != null ? new Set(Array.from(options.retainedTrackedChangeIds, (id) => normalizeCommentId(id)).filter(Boolean)) : null;
10690
+ const retainedTrackedChangeIds = retainMissingTrackedChangeGeometry && options?.retainedTrackedChangeIds != null ? new Set(Array.from(options.retainedTrackedChangeIds, (id) => normalizeCommentId(id)).filter(isPresent)) : null;
10490
10691
  const previousPositions = editorCommentPositions.value || {};
10491
10692
  const normalizedPositions = {};
10492
10693
  Object.entries(allCommentPositions).forEach(([key, entry]) => {
@@ -10513,7 +10714,7 @@ var useCommentsStore = defineStore("comments", () => {
10513
10714
  return null;
10514
10715
  };
10515
10716
  const writeTrackedChangeAliases = (entry, comment, extraKeys = []) => {
10516
- const keys = [...aliasesFor(comment), ...extraKeys.map((id) => normalizeCommentId(id)).filter(Boolean)];
10717
+ const keys = [...aliasesFor(comment), ...extraKeys.map((id) => normalizeCommentId(id)).filter(isPresent)];
10517
10718
  for (const key of new Set(keys)) if (normalizedPositions[key] === void 0) normalizedPositions[key] = entry;
10518
10719
  };
10519
10720
  commentsList.value.forEach((comment) => {
@@ -10712,7 +10913,7 @@ var ellipsis_vertical_solid_default = "<svg xmlns=\"http://www.w3.org/2000/svg\"
10712
10913
  //#region ../../shared/common/icons/rotate-left-solid.svg?raw
10713
10914
  var rotate_left_solid_default = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d=\"M48.5 224L40 224c-13.3 0-24-10.7-24-24L16 72c0-9.7 5.8-18.5 14.8-22.2s19.3-1.7 26.2 5.2L98.6 96.6c87.6-86.5 228.7-86.2 315.8 1c87.5 87.5 87.5 229.3 0 316.8s-229.3 87.5-316.8 0c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0c62.5 62.5 163.8 62.5 226.3 0s62.5-163.8 0-226.3c-62.2-62.2-162.7-62.5-225.3-1L185 183c6.9 6.9 8.9 17.2 5.2 26.2s-12.5 14.8-22.2 14.8L48.5 224z\"/></svg>";
10714
10915
  //#endregion
10715
- //#region src/icons.js
10916
+ //#region src/icons.ts
10716
10917
  var superdocIcons = {
10717
10918
  comment: comment_solid_default,
10718
10919
  caretDown: caret_down_solid_default,
@@ -11074,12 +11275,9 @@ var InternalDropdown_default = /* @__PURE__ */ _plugin_vue_export_helper_default
11074
11275
  }
11075
11276
  }), [["__scopeId", "data-v-026f5d66"]]);
11076
11277
  //#endregion
11077
- //#region src/components/CommentsLayer/helpers.js
11278
+ //#region src/components/CommentsLayer/helpers.ts
11078
11279
  /**
11079
11280
  * Comments helper to format dates from timestamp
11080
- *
11081
- * @param {Number} timestamp The timestamp to format
11082
- * @returns {String} The formatted date
11083
11281
  */
11084
11282
  function formatDate(timestamp) {
11085
11283
  const date = new Date(timestamp);
@@ -11677,7 +11875,7 @@ var CommentInput_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
11677
11875
  }
11678
11876
  }), [["__scopeId", "data-v-aa97de44"]]);
11679
11877
  //#endregion
11680
- //#region src/internal/toolbar/built-in/url-safety.js
11878
+ //#region src/internal/toolbar/built-in/url-safety.ts
11681
11879
  /**
11682
11880
  * Minimal href sanitizer for the built-in link popover.
11683
11881
  *
@@ -11693,8 +11891,6 @@ var DANGEROUS_SCHEME = /^(javascript|data|vbscript):/i;
11693
11891
  var HAS_SCHEME = /^[a-z][a-z0-9+.-]*:/i;
11694
11892
  /**
11695
11893
  * Validate and normalize an href. Returns `{ href }` for safe values, or `null`.
11696
- * @param {string} value
11697
- * @returns {{ href: string }|null}
11698
11894
  */
11699
11895
  function sanitizeHref(value) {
11700
11896
  if (typeof value !== "string") return null;
@@ -11706,7 +11902,7 @@ function sanitizeHref(value) {
11706
11902
  return { href: trimmed };
11707
11903
  }
11708
11904
  //#endregion
11709
- //#region src/components/CommentsLayer/comment-urls.js
11905
+ //#region src/components/CommentsLayer/comment-urls.ts
11710
11906
  function isAbsoluteWebUrl(value) {
11711
11907
  if (!/^https?:\/\/[^\s/?#\\]+/i.test(value) || /[\s\\]/u.test(value)) return false;
11712
11908
  try {
@@ -11730,7 +11926,7 @@ function trimUrlPunctuation(value) {
11730
11926
  for (const character of value) {
11731
11927
  const close = closingDelimiter[character];
11732
11928
  if (close) balance[close] += 1;
11733
- else if (Object.hasOwn(balance, character)) balance[character] -= 1;
11929
+ else if (Object.prototype.hasOwnProperty.call(balance, character)) balance[character] -= 1;
11734
11930
  }
11735
11931
  let end = value.length;
11736
11932
  while (end > 0) {
@@ -11754,17 +11950,18 @@ function renderCommentHtml(value) {
11754
11950
  if (typeof document === "undefined") return value;
11755
11951
  const template = document.createElement("template");
11756
11952
  template.innerHTML = value;
11757
- for (const link of template.content.querySelectorAll("a")) {
11758
- const safe = sanitizeHref(link.getAttribute("href"));
11953
+ for (const link of Array.from(template.content.querySelectorAll("a"))) {
11954
+ const rawHref = link.getAttribute("href");
11955
+ const safe = rawHref === null ? null : sanitizeHref(rawHref);
11759
11956
  if (safe && (isAbsoluteWebUrl(safe.href) || /^(mailto|tel):\S+$/i.test(safe.href))) configureLink(link, safe.href);
11760
- else link.replaceWith(...link.childNodes);
11957
+ else link.replaceWith(...Array.from(link.childNodes));
11761
11958
  }
11762
11959
  const walker = document.createTreeWalker(template.content, NodeFilter.SHOW_TEXT);
11763
11960
  const textNodes = [];
11764
- while (walker.nextNode()) textNodes.push(walker.currentNode);
11961
+ while (walker.nextNode()) if (walker.currentNode instanceof Text) textNodes.push(walker.currentNode);
11765
11962
  for (const node of textNodes) {
11766
11963
  if (node.parentElement?.closest("a, script, style, textarea, code, pre")) continue;
11767
- const text = node.textContent;
11964
+ const text = node.data;
11768
11965
  const fragment = document.createDocumentFragment();
11769
11966
  let end = 0;
11770
11967
  for (const match of text.matchAll(/(^|[\s([{])(https?:\/\/[^\s<>"']+)/gi)) {
@@ -11823,8 +12020,8 @@ var _hoisted_11$3 = {
11823
12020
  key: 0,
11824
12021
  class: "card-section comment-body"
11825
12022
  };
11826
- var _hoisted_12$2 = ["data-track-change-semantic-color-key"];
11827
- var _hoisted_13$2 = { key: 0 };
12023
+ var _hoisted_12$3 = ["data-track-change-semantic-color-key"];
12024
+ var _hoisted_13$3 = { key: 0 };
11828
12025
  var _hoisted_14$2 = { class: "change-type tracked-change-label" };
11829
12026
  var _hoisted_15$2 = {
11830
12027
  key: 0,
@@ -12609,6 +12806,16 @@ var CommentDialog_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
12609
12806
  }
12610
12807
  handleResolve();
12611
12808
  };
12809
+ const ownsTrackedChangeDecision = (superdoc, editor) => editor !== null && proxy.$superdoc === superdoc && superdoc.activeEditor === editor;
12810
+ const restoreTrackedChangeDecisionFocus = (superdoc, editor) => {
12811
+ nextTick(() => {
12812
+ if (!ownsTrackedChangeDecision(superdoc, editor)) return;
12813
+ commentsStore.lastUpdate = /* @__PURE__ */ new Date();
12814
+ activeComment.value = null;
12815
+ commentsStore.setActiveComment(superdoc, activeComment.value);
12816
+ if (ownsTrackedChangeDecision(superdoc, editor)) superdoc.focus?.({ preventScroll: true });
12817
+ });
12818
+ };
12612
12819
  const handleReject = async () => {
12613
12820
  if (props.comment.trackedChange) {
12614
12821
  if (trackedChangeDecisionsAreDisabled()) return trackedChangeDecisionDisabledOutcome();
@@ -12616,23 +12823,21 @@ var CommentDialog_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
12616
12823
  const customHandler = proxy.$superdoc.config.onTrackedChangeBubbleReject;
12617
12824
  if (props.comment.trackedChange) {
12618
12825
  if (isV2Mode.value) {
12826
+ const superdoc = proxy.$superdoc;
12827
+ const editor = superdoc.activeEditor;
12619
12828
  const outcome = await commentsStore.decideTrackedChangeFromSidebar({
12620
- superdoc: proxy.$superdoc,
12829
+ superdoc,
12621
12830
  comment: props.comment,
12622
12831
  decision: "reject"
12623
12832
  });
12833
+ if (!ownsTrackedChangeDecision(superdoc, editor)) return;
12624
12834
  if (!outcome?.ok || outcome.success === false) {
12625
12835
  reportTrackedChangeDecisionFailure("reject", outcome);
12626
12836
  return;
12627
12837
  }
12628
12838
  clearTrackedChangeDecisionFailure();
12629
- if (typeof customHandler === "function") customHandler(props.comment, proxy.$superdoc.activeEditor);
12630
- nextTick(() => {
12631
- commentsStore.lastUpdate = /* @__PURE__ */ new Date();
12632
- activeComment.value = null;
12633
- commentsStore.setActiveComment(proxy.$superdoc, activeComment.value);
12634
- proxy.$superdoc.focus?.({ preventScroll: true });
12635
- });
12839
+ if (typeof customHandler === "function") customHandler(props.comment, editor);
12840
+ restoreTrackedChangeDecisionFocus(superdoc, editor);
12636
12841
  return;
12637
12842
  }
12638
12843
  let decisionApplied = true;
@@ -12678,23 +12883,21 @@ var CommentDialog_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
12678
12883
  }
12679
12884
  const customHandler = proxy.$superdoc.config.onTrackedChangeBubbleAccept;
12680
12885
  if (props.comment.trackedChange && isV2Mode.value) {
12886
+ const superdoc = proxy.$superdoc;
12887
+ const editor = superdoc.activeEditor;
12681
12888
  const outcome = await commentsStore.decideTrackedChangeFromSidebar({
12682
- superdoc: proxy.$superdoc,
12889
+ superdoc,
12683
12890
  comment: props.comment,
12684
12891
  decision: "accept"
12685
12892
  });
12893
+ if (!ownsTrackedChangeDecision(superdoc, editor)) return;
12686
12894
  if (!outcome?.ok || outcome.success === false) {
12687
12895
  reportTrackedChangeDecisionFailure("accept", outcome);
12688
12896
  return;
12689
12897
  }
12690
12898
  clearTrackedChangeDecisionFailure();
12691
- if (typeof customHandler === "function") customHandler(props.comment, proxy.$superdoc.activeEditor);
12692
- nextTick(() => {
12693
- commentsStore.lastUpdate = /* @__PURE__ */ new Date();
12694
- activeComment.value = null;
12695
- commentsStore.setActiveComment(proxy.$superdoc, activeComment.value);
12696
- proxy.$superdoc.focus?.({ preventScroll: true });
12697
- });
12899
+ if (typeof customHandler === "function") customHandler(props.comment, editor);
12900
+ restoreTrackedChangeDecisionFocus(superdoc, editor);
12698
12901
  return;
12699
12902
  }
12700
12903
  let v1TrackedChangeDecisionApplied = true;
@@ -12998,7 +13201,7 @@ var CommentDialog_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
12998
13201
  "data-track-change-semantic-color-key": comment.semanticColorKey ?? void 0,
12999
13202
  ref_for: true,
13000
13203
  ref: index === 0 ? setParentBodyRef : void 0
13001
- }, [comment.trackedChangeLabel ? (openBlock(), createElementBlock("div", _hoisted_13$2, [createElementVNode("span", _hoisted_14$2, toDisplayString(comment.trackedChangeLabel), 1), comment.trackedChangeDetailLines && comment.trackedChangeDetailLines.length ? (openBlock(), createElementBlock("div", _hoisted_15$2, [(openBlock(true), createElementBlock(Fragment, null, renderList(comment.trackedChangeDetailLines, (line, lineIndex) => {
13204
+ }, [comment.trackedChangeLabel ? (openBlock(), createElementBlock("div", _hoisted_13$3, [createElementVNode("span", _hoisted_14$2, toDisplayString(comment.trackedChangeLabel), 1), comment.trackedChangeDetailLines && comment.trackedChangeDetailLines.length ? (openBlock(), createElementBlock("div", _hoisted_15$2, [(openBlock(true), createElementBlock(Fragment, null, renderList(comment.trackedChangeDetailLines, (line, lineIndex) => {
13002
13205
  return openBlock(), createElementBlock("div", {
13003
13206
  key: lineIndex,
13004
13207
  class: "tracked-change-detail-line"
@@ -13023,7 +13226,7 @@ var CommentDialog_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
13023
13226
  style: normalizeStyle(trackedChangeImagePreviewStyle(comment.trackedChangeImagePreview)),
13024
13227
  loading: "lazy",
13025
13228
  decoding: "async"
13026
- }, null, 12, _hoisted_38)], 8, _hoisted_37)) : createCommentVNode("", true)], 10, _hoisted_12$2),
13229
+ }, null, 12, _hoisted_38)], 8, _hoisted_37)) : createCommentVNode("", true)], 10, _hoisted_12$3),
13027
13230
  shouldTruncate.value && isTextOverflowing.value && index === 0 ? (openBlock(), createElementBlock("div", {
13028
13231
  key: 0,
13029
13232
  class: "show-more-toggle",
@@ -13117,7 +13320,7 @@ var CommentDialog_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
13117
13320
  ], 64))], 14, _hoisted_1$26)), [[_directive_click_outside, handleClickOutside]]);
13118
13321
  };
13119
13322
  }
13120
- }), [["__scopeId", "data-v-4220ce17"]]);
13323
+ }), [["__scopeId", "data-v-04c17a1a"]]);
13121
13324
  //#endregion
13122
13325
  //#region src/components/CommentsLayer/commentsList/ReviewDirectoryListItem.vue?vue&type=script&setup=true&lang.ts
13123
13326
  var _hoisted_1$25 = [
@@ -13445,21 +13648,25 @@ var commentsList_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
13445
13648
  }
13446
13649
  }), [["__scopeId", "data-v-8eca48e1"]]);
13447
13650
  //#endregion
13448
- //#region src/components/CommentsLayer/commentsList/super-comments-list.js
13651
+ //#region src/components/CommentsLayer/commentsList/super-comments-list.ts
13652
+ var isReviewDirectoryStore = (value) => isPlainObject$1(value) && typeof value.setReviewDirectoryFromV2 === "function" && typeof value.clearReviewDirectory === "function";
13653
+ var readCommentsStore = (superdoc) => {
13654
+ const store = superdoc?.["commentsStore"];
13655
+ return isReviewDirectoryStore(store) ? store : void 0;
13656
+ };
13449
13657
  /**
13450
13658
  * Comments list renderer (not floating comments)
13451
13659
  *
13452
13660
  * This renders a list of comments into an element, connected to main SuperDoc instance
13453
13661
  */
13454
13662
  var SuperComments = class extends import_eventemitter3.default {
13455
- element;
13456
- config = {
13457
- comments: [],
13458
- element: null,
13459
- commentsStore: null
13460
- };
13461
13663
  constructor(options, superdoc) {
13462
13664
  super();
13665
+ this.config = {
13666
+ comments: [],
13667
+ element: null,
13668
+ commentsStore: null
13669
+ };
13463
13670
  this.config = {
13464
13671
  ...this.config,
13465
13672
  ...options
@@ -13480,7 +13687,7 @@ var SuperComments = class extends import_eventemitter3.default {
13480
13687
  this.open();
13481
13688
  }
13482
13689
  syncDirectory() {
13483
- const store = this.superdoc?.commentsStore;
13690
+ const store = readCommentsStore(this.superdoc);
13484
13691
  if (!store) return;
13485
13692
  const comments = this.directorySnapshots.comments;
13486
13693
  const trackChanges = this.directorySnapshots.trackChanges;
@@ -13498,12 +13705,14 @@ var SuperComments = class extends import_eventemitter3.default {
13498
13705
  const acceptDirectorySnapshot = (snapshot) => {
13499
13706
  if (generation !== this.directoryGeneration) return;
13500
13707
  if ((snapshot?.listStatus ?? snapshot?.status) !== "ready") {
13501
- if (this.superdoc?.commentsStore) this.superdoc.commentsStore.isReviewDirectoryLoading = true;
13708
+ const store = readCommentsStore(this.superdoc);
13709
+ if (store) store.isReviewDirectoryLoading = true;
13502
13710
  return;
13503
13711
  }
13504
13712
  const items = Array.isArray(snapshot.items) ? snapshot.items : [];
13505
13713
  if (this.directoryItemRefs[family] === items) {
13506
- if (this.superdoc?.commentsStore) this.superdoc.commentsStore.isReviewDirectoryLoading = this.directorySnapshots.comments == null || this.directorySnapshots.trackChanges == null;
13714
+ const store = readCommentsStore(this.superdoc);
13715
+ if (store) store.isReviewDirectoryLoading = this.directorySnapshots.comments == null || this.directorySnapshots.trackChanges == null;
13507
13716
  return;
13508
13717
  }
13509
13718
  this.directoryItemRefs[family] = items;
@@ -13520,15 +13729,16 @@ var SuperComments = class extends import_eventemitter3.default {
13520
13729
  };
13521
13730
  const ui = this.superdoc?.ui;
13522
13731
  if (!ui) return;
13523
- if (this.superdoc?.commentsStore) {
13524
- this.superdoc.commentsStore.isReviewDirectoryActive = true;
13525
- this.superdoc.commentsStore.isReviewDirectoryLoading = true;
13732
+ const store = readCommentsStore(this.superdoc);
13733
+ if (store) {
13734
+ store.isReviewDirectoryActive = true;
13735
+ store.isReviewDirectoryLoading = true;
13526
13736
  }
13527
13737
  this.stopDirectoryObservers = [subscribe(ui.comments, "comments"), subscribe(ui.trackChanges, "trackChanges")];
13528
13738
  }
13529
13739
  createVueApp() {
13530
13740
  this.app = createApp(commentsList_default);
13531
- const parentProvides = this.superdoc?.app?._context?.provides;
13741
+ const parentProvides = this.superdoc?.["app"]?._context?.provides;
13532
13742
  if (parentProvides && this.app?._context?.provides) Object.setPrototypeOf(this.app._context.provides, parentProvides);
13533
13743
  if (this.superdoc?.pinia) {
13534
13744
  setActivePinia(this.superdoc.pinia);
@@ -13552,10 +13762,11 @@ var SuperComments = class extends import_eventemitter3.default {
13552
13762
  comments: null,
13553
13763
  trackChanges: null
13554
13764
  };
13555
- if (this.superdoc?.commentsStore) {
13556
- this.superdoc.commentsStore.clearReviewDirectory();
13557
- this.superdoc.commentsStore.isReviewDirectoryActive = false;
13558
- this.superdoc.commentsStore.isReviewDirectoryLoading = false;
13765
+ const store = readCommentsStore(this.superdoc);
13766
+ if (store) {
13767
+ store.clearReviewDirectory();
13768
+ store.isReviewDirectoryActive = false;
13769
+ store.isReviewDirectoryLoading = false;
13559
13770
  }
13560
13771
  if (this.app) {
13561
13772
  this.app.unmount();
@@ -13569,7 +13780,7 @@ var SuperComments = class extends import_eventemitter3.default {
13569
13780
  }
13570
13781
  };
13571
13782
  //#endregion
13572
- //#region src/composables/use-viewport-fit.js
13783
+ //#region src/composables/use-viewport-fit.ts
13573
13784
  var CSS_PX_PER_INCH = 96;
13574
13785
  var SIDEBAR_SELECTOR = ".superdoc__right-sidebar";
13575
13786
  var PDF_PAGE_SELECTOR = ".sd-pdf-viewer-page";
@@ -13674,7 +13885,7 @@ function useViewportFit({ getSuperdoc, superdocContainerWidth, isReady, activeZo
13674
13885
  const root = superdocRoot.value;
13675
13886
  if (!root?.querySelectorAll) return null;
13676
13887
  let widest = 0;
13677
- for (const page of root.querySelectorAll(PDF_PAGE_SELECTOR)) {
13888
+ for (const page of Array.from(root.querySelectorAll(PDF_PAGE_SELECTOR))) {
13678
13889
  const measured = Number(page.clientWidth) || Number(page.getBoundingClientRect?.().width) || 0;
13679
13890
  let scaleFactor = NaN;
13680
13891
  if (typeof window !== "undefined" && typeof window.getComputedStyle === "function") scaleFactor = Number.parseFloat(window.getComputedStyle(page).getPropertyValue("--scale-factor"));
@@ -13876,124 +14087,6 @@ var composeAuthorColorResolver = (config) => {
13876
14087
  };
13877
14088
  };
13878
14089
  //#endregion
13879
- //#region ../document-api/src/errors.ts
13880
- /**
13881
- * Structured validation error thrown by document-api execute* functions.
13882
- *
13883
- * Consumers should prefer checking `error.code` over `instanceof` for resilience
13884
- * across package boundaries and bundling scenarios.
13885
- */
13886
- var DocumentApiValidationError = class DocumentApiValidationError extends Error {
13887
- constructor(code, message, details) {
13888
- super(message);
13889
- this.name = "DocumentApiValidationError";
13890
- this.code = code;
13891
- this.details = details;
13892
- Object.setPrototypeOf(this, DocumentApiValidationError.prototype);
13893
- }
13894
- };
13895
- //#endregion
13896
- //#region ../document-api/src/export/export.types.ts
13897
- /** Canonical tracked-change export modes accepted by every public export surface. */
13898
- var SD_EXPORT_MODES = [
13899
- "review-preserving",
13900
- "final",
13901
- "original"
13902
- ];
13903
- /** Default mode for modeful DOCX export. */
13904
- var DEFAULT_SD_EXPORT_MODE = "review-preserving";
13905
- var SD_COMMENT_EXPORT_POLICIES = [
13906
- "preserve",
13907
- "resolve",
13908
- "remove"
13909
- ];
13910
- var SD_CONTENT_CONTROL_EXPORT_POLICIES = ["preserve", "flatten"];
13911
- //#endregion
13912
- //#region ../document-api/src/export/export.ts
13913
- /**
13914
- * Export namespace: engine-agnostic public API and adapter contract.
13915
- *
13916
- * Validates the requested mode at the public boundary so an invalid mode fails
13917
- * closed with `INVALID_INPUT` before any document work happens, then delegates
13918
- * byte production and degradation reporting to the engine adapter.
13919
- */
13920
- function resolveMode$1(input) {
13921
- if (input != null && !isRecord(input)) throw new DocumentApiValidationError("INVALID_INPUT", "export.toDocx input must be an object.");
13922
- const raw = input?.mode;
13923
- if (raw === void 0) return DEFAULT_SD_EXPORT_MODE;
13924
- if (typeof raw !== "string" || !SD_EXPORT_MODES.includes(raw)) throw new DocumentApiValidationError("INVALID_INPUT", `export.toDocx mode must be one of: ${SD_EXPORT_MODES.join(", ")}.`, {
13925
- mode: raw,
13926
- allowedModes: [...SD_EXPORT_MODES]
13927
- });
13928
- return raw;
13929
- }
13930
- function resolveDocxExportPolicies(input = {}) {
13931
- if (!isRecord(input)) throw new DocumentApiValidationError("INVALID_INPUT", "DOCX export input must be an object.");
13932
- const mode = resolveMode$1(input);
13933
- const policies = input.policies;
13934
- if (policies !== void 0 && !isRecord(policies)) throw new DocumentApiValidationError("INVALID_INPUT", "DOCX export policies must be an object.");
13935
- const allowed = {
13936
- trackedChanges: SD_EXPORT_MODES,
13937
- comments: SD_COMMENT_EXPORT_POLICIES,
13938
- contentControls: SD_CONTENT_CONTROL_EXPORT_POLICIES
13939
- };
13940
- for (const [key, value] of Object.entries(policies ?? {})) if (!Object.prototype.hasOwnProperty.call(allowed, key) || value !== void 0 && !allowed[key].includes(value)) throw new DocumentApiValidationError("INVALID_INPUT", `Unsupported DOCX export policy: ${key}.`, {
13941
- policy: key,
13942
- value
13943
- });
13944
- const requested = policies;
13945
- if (input.mode !== void 0 && requested?.trackedChanges !== void 0 && mode !== requested.trackedChanges) throw new DocumentApiValidationError("INVALID_INPUT", "mode conflicts with policies.trackedChanges.");
13946
- return {
13947
- trackedChanges: requested?.trackedChanges ?? mode,
13948
- comments: requested?.comments ?? "preserve",
13949
- contentControls: requested?.contentControls ?? "preserve"
13950
- };
13951
- }
13952
- //#endregion
13953
- //#region ../document-api/src/permission-ranges/permission-ranges.ts
13954
- function editorKey(editor) {
13955
- return editor.id?.trim() ? editor.id : editor.email?.trim() || void 0;
13956
- }
13957
- function editorMatches(editor, id) {
13958
- return Boolean(id.trim()) && (editor.id === id || Boolean(editor.email?.trim() && editor.email.trim().toLowerCase() === id.toLowerCase()));
13959
- }
13960
- /**
13961
- * Resolve a host ID or imported Word email against an explicitly supplied roster.
13962
- * Word receives the email when available; opaque IDs remain host-specific.
13963
- * Duplicate IDs/emails are ambiguous. This does not authenticate a user.
13964
- */
13965
- function resolvePermissionRangeEditor(editors, id) {
13966
- const matches = editors.filter((editor) => editorMatches(editor, id));
13967
- if (matches.length === 0) return {
13968
- status: "unknown",
13969
- id
13970
- };
13971
- if (matches.length !== 1) return {
13972
- status: "ambiguous",
13973
- id
13974
- };
13975
- const editor = matches[0];
13976
- const wordId = editor.email?.trim() || editor.id;
13977
- if (editors.filter((candidate) => editorMatches(candidate, wordId)).length !== 1) return {
13978
- status: "ambiguous",
13979
- id
13980
- };
13981
- return {
13982
- status: "resolved",
13983
- id,
13984
- editor: { ...editor },
13985
- principal: {
13986
- kind: "editor",
13987
- id: wordId
13988
- }
13989
- };
13990
- }
13991
- /** Enumerate the host roster for an editor picker, including ambiguous entries. */
13992
- function listPermissionRangeEditors(editors) {
13993
- const ids = new Set(editors.map(editorKey).filter((id) => id !== void 0));
13994
- return Array.from(ids, (id) => resolvePermissionRangeEditor(editors, id));
13995
- }
13996
- //#endregion
13997
14090
  //#region src/core/superdoc-ui-owner.ts
13998
14091
  var ownerIds = /* @__PURE__ */ new WeakMap();
13999
14092
  function getSuperDocUiOwnerId(superdoc) {
@@ -14106,18 +14199,42 @@ var FloatingComments_default = /* @__PURE__ */ _plugin_vue_export_helper_default
14106
14199
  const { activeComment, activeFloatingCommentInstanceId, editorCommentPositions, pendingComment, editingCommentId, instantSidebarAlignmentTargetY, instantSidebarAlignmentThreadId, instantSidebarAlignmentInstanceId } = storeToRefs(piniaCommentsStore);
14107
14200
  const { activeZoom } = storeToRefs(superdocStore);
14108
14201
  const committedPresentation = computed$1(() => commentsStore.committedReviewPresentations?.[String(props.currentDocument?.id)] ?? null);
14202
+ const committedInstanceCache = /* @__PURE__ */ new Map();
14203
+ let committedInstanceCacheOwner = "";
14204
+ const sameReviewInstance = (cached, next) => {
14205
+ const before = Object.entries(cached).filter(([key]) => key !== "comment" && key !== "rowVersion");
14206
+ const after = Object.entries(next);
14207
+ if (before.length !== after.length) return false;
14208
+ const previous = new Map(before);
14209
+ return after.every(([key, value]) => previous.has(key) && (key === "positionEntry" ? JSON.stringify(previous.get(key)) === JSON.stringify(value) : Object.is(previous.get(key), value)));
14210
+ };
14109
14211
  const committedInstances = computed$1(() => {
14110
14212
  const presentation = committedPresentation.value;
14111
14213
  if (!presentation) return null;
14112
14214
  const rows = new Map(commentsStore.commentsList.filter((row) => String(row.fileId) === String(props.currentDocument?.id)).map((row) => [String(row.commentId), row]));
14113
- return presentation.instances.flatMap((instance) => {
14215
+ const owner = `${presentation.epoch}\u0000${presentation.coordinate?.mountId ?? ""}`;
14216
+ if (owner !== committedInstanceCacheOwner) {
14217
+ committedInstanceCache.clear();
14218
+ committedInstanceCacheOwner = owner;
14219
+ }
14220
+ const live = /* @__PURE__ */ new Set();
14221
+ const instances = presentation.instances.flatMap((instance) => {
14114
14222
  const comment = instance.rowId === "pending" ? pendingComment.value : rows.get(instance.rowId);
14115
- return comment ? [{
14223
+ if (!comment) return [];
14224
+ const rowVersion = presentation.rowVersions[instance.rowId] ?? 0;
14225
+ live.add(instance.id);
14226
+ const cached = committedInstanceCache.get(instance.id);
14227
+ if (cached && cached.comment === comment && cached.rowVersion === rowVersion && sameReviewInstance(cached, instance)) return [cached];
14228
+ const merged = {
14116
14229
  ...instance,
14117
14230
  comment,
14118
- rowVersion: presentation.rowVersions[instance.rowId] ?? 0
14119
- }] : [];
14231
+ rowVersion
14232
+ };
14233
+ committedInstanceCache.set(instance.id, merged);
14234
+ return [merged];
14120
14235
  });
14236
+ for (const id of committedInstanceCache.keys()) if (!live.has(id)) committedInstanceCache.delete(id);
14237
+ return instances;
14121
14238
  });
14122
14239
  const floatingCommentInstances = computed$1(() => {
14123
14240
  if (committedInstances.value) return committedInstances.value.filter((instance) => instance.rowId !== "pending");
@@ -14533,6 +14650,18 @@ var FloatingComments_default = /* @__PURE__ */ _plugin_vue_export_helper_default
14533
14650
  scheduleDeferredRemeasure(() => allPositions.value.map((pos) => pos.id));
14534
14651
  });
14535
14652
  };
14653
+ const resizeHandlers = /* @__PURE__ */ new Map();
14654
+ const resizeHandlerFor = (id) => {
14655
+ let handler = resizeHandlers.get(id);
14656
+ if (!handler) {
14657
+ handler = () => {
14658
+ const position = allPositions.value.find((candidate) => candidate.id === id);
14659
+ if (position) handleResize(position);
14660
+ };
14661
+ resizeHandlers.set(id, handler);
14662
+ }
14663
+ return handler;
14664
+ };
14536
14665
  const setInstantLayoutTransitionsDisabled = (disabled) => {
14537
14666
  if (!disabled && directDecisionContinuityTargetId.value != null) return;
14538
14667
  disableInstantLayoutTransitions.value = disabled;
@@ -14937,6 +15066,7 @@ var FloatingComments_default = /* @__PURE__ */ _plugin_vue_export_helper_default
14937
15066
  visibilityChanged = true;
14938
15067
  }
14939
15068
  if (visibilityChanged) visibleIds.value = newVisible;
15069
+ for (const id of resizeHandlers.keys()) if (!currentIds.has(id)) resizeHandlers.delete(id);
14940
15070
  for (const id of prevPositionIds) if (!currentIds.has(id)) delete _heightsCache[id];
14941
15071
  const setChanged = prevPositionIds.size !== currentIds.size || [...prevPositionIds].some((id) => !currentIds.has(id));
14942
15072
  const orderChanged = renderedPositionOrder.length !== positionOrder.length || renderedPositionOrder.some((id, index) => id !== positionOrder[index]);
@@ -15029,12 +15159,13 @@ var FloatingComments_default = /* @__PURE__ */ _plugin_vue_export_helper_default
15029
15159
  style: normalizeStyle({
15030
15160
  top: (pos.isOffscreenReviewCard && !committedPresentation.value ? pos.top - sidebarOffsetY.value : pos.top) + "px",
15031
15161
  height: pos.height + "px",
15032
- transition: committedPresentation.value || disableInstantLayoutTransitions.value ? "none" : void 0
15162
+ transition: committedPresentation.value || disableInstantLayoutTransitions.value ? "none" : void 0,
15163
+ visibility: pos.instanceRef?.provisional ? "hidden" : void 0
15033
15164
  })
15034
15165
  }, [shouldRenderDialog(pos) ? (openBlock(), createBlock(CommentDialog_default, {
15035
15166
  key: pos.id + commentsRenderKey.value,
15036
15167
  onReady: handleDialog,
15037
- onResize: ($event) => handleResize(pos),
15168
+ onResize: resizeHandlerFor(pos.id),
15038
15169
  class: "floating-comment",
15039
15170
  parent: __props.parent,
15040
15171
  comment: pos.commentRef,
@@ -15054,7 +15185,7 @@ var FloatingComments_default = /* @__PURE__ */ _plugin_vue_export_helper_default
15054
15185
  }), 128))], 4)], 36);
15055
15186
  };
15056
15187
  }
15057
- }), [["__scopeId", "data-v-aaf2c537"]]);
15188
+ }), [["__scopeId", "data-v-1070f5a1"]]);
15058
15189
  //#endregion
15059
15190
  //#region src/components/CommentsLayer/PdfCommentsLayer.vue?vue&type=script&setup=true&lang.ts
15060
15191
  var _hoisted_1$22 = {
@@ -15187,7 +15318,7 @@ var PdfCommentsLayer_default = /* @__PURE__ */ _plugin_vue_export_helper_default
15187
15318
  }), 128))]);
15188
15319
  };
15189
15320
  }
15190
- }), [["__scopeId", "data-v-86166837"]]);
15321
+ }), [["__scopeId", "data-v-e3ae65fa"]]);
15191
15322
  //#endregion
15192
15323
  //#region src/components/Whiteboard/use-whiteboard.ts
15193
15324
  var useWhiteboard = ({ proxy, layers, documents, modules }) => {
@@ -15481,7 +15612,7 @@ var WhiteboardPage_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/
15481
15612
  }, null, 44, _hoisted_1$21);
15482
15613
  };
15483
15614
  }
15484
- }), [["__scopeId", "data-v-f730b33d"]]);
15615
+ }), [["__scopeId", "data-v-fdae8749"]]);
15485
15616
  //#endregion
15486
15617
  //#region src/components/Whiteboard/WhiteboardLayer.vue
15487
15618
  var WhiteboardLayer_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
@@ -15552,16 +15683,11 @@ var WhiteboardLayer_default = /* @__PURE__ */ _plugin_vue_export_helper_default(
15552
15683
  }), 128))], 4);
15553
15684
  };
15554
15685
  }
15555
- }), [["__scopeId", "data-v-980e1f94"]]);
15686
+ }), [["__scopeId", "data-v-800e1970"]]);
15556
15687
  //#endregion
15557
- //#region src/components/CommentsLayer/collect-removed-comment-ids.js
15688
+ //#region src/components/CommentsLayer/collect-removed-comment-ids.ts
15558
15689
  /**
15559
15690
  * Expand deleted comment ids through comment and tracked-change descendants.
15560
- *
15561
- * @param {ReadonlyArray<Record<string, any>>} comments
15562
- * @param {ReadonlyArray<string>} targetIds
15563
- * @param {(comment: Record<string, any>) => boolean} [isInScope]
15564
- * @returns {Set<string>}
15565
15691
  */
15566
15692
  var collectRemovedCommentIds = (comments, targetIds, isInScope = () => true) => {
15567
15693
  const targets = new Set(targetIds.map(String));
@@ -15644,7 +15770,7 @@ function normalizeCommentsUiConfig(commentsConfig) {
15644
15770
  return normalized;
15645
15771
  }
15646
15772
  //#endregion
15647
- //#region src/composables/use-comment-small-screen.js
15773
+ //#region src/composables/use-comment-small-screen.ts
15648
15774
  var SUPERDOC_DOCUMENT_SELECTOR = ".superdoc__document";
15649
15775
  var isValidCompactBreakpoint = (value) => {
15650
15776
  return isFiniteNumber(value) && value >= 0;
@@ -15724,10 +15850,11 @@ function useCommentSmallScreen({ commentsModuleConfig, superdocRoot, layers }) {
15724
15850
  }
15725
15851
  documentWidthTargets = nextTargets;
15726
15852
  if (!nextTargets.length) return;
15727
- documentWidthResizeObserver = new ResizeObserverClass(() => {
15853
+ const observer = new ResizeObserverClass(() => {
15728
15854
  recalculateCompactCommentsMode();
15729
15855
  });
15730
- nextTargets.forEach((el) => documentWidthResizeObserver.observe(el));
15856
+ documentWidthResizeObserver = observer;
15857
+ nextTargets.forEach((el) => observer.observe(el));
15731
15858
  };
15732
15859
  const getMeasuredDocumentWidth = () => {
15733
15860
  ensureDocumentWidthObserver();
@@ -15783,7 +15910,22 @@ function useCommentSmallScreen({ commentsModuleConfig, superdocRoot, layers }) {
15783
15910
  };
15784
15911
  }
15785
15912
  //#endregion
15786
- //#region src/composables/use-compact-comment-popover.js
15913
+ //#region src/composables/use-comment-sidebar.ts
15914
+ function useCommentSidebar({ documentKey, enabled, hasContent }) {
15915
+ const reserved = ref(false);
15916
+ watch$1(documentKey, () => {
15917
+ reserved.value = false;
15918
+ }, { flush: "sync" });
15919
+ watch$1(() => enabled.value && hasContent.value, (visible) => {
15920
+ if (visible) reserved.value = true;
15921
+ }, {
15922
+ immediate: true,
15923
+ flush: "sync"
15924
+ });
15925
+ return computed$1(() => enabled.value && (hasContent.value || reserved.value));
15926
+ }
15927
+ //#endregion
15928
+ //#region src/composables/use-compact-comment-popover.ts
15787
15929
  var POPOVER_WIDTH_PX = 320;
15788
15930
  var SAFE_MARGIN_PX = 12;
15789
15931
  var MIN_BOTTOM_SPACE_PX = 220;
@@ -15813,7 +15955,7 @@ var resolveInteractionAnchor = (lastClickAnchor) => {
15813
15955
  var resolveInlineHighlightAnchorRect = ({ rootEl, comment, pendingComment }) => {
15814
15956
  if (Boolean(pendingComment?.value && comment?.commentId === pendingComment.value?.commentId)) {
15815
15957
  const nodes = rootEl.querySelectorAll(COMMENT_HIGHLIGHT_SELECTOR);
15816
- for (const node of nodes) {
15958
+ for (const node of Array.from(nodes)) {
15817
15959
  if (!parseCommentIds(node.getAttribute(COMMENT_HIGHLIGHT_DATA_ATTR)).includes("pending")) continue;
15818
15960
  if (typeof node.getBoundingClientRect !== "function") continue;
15819
15961
  return node.getBoundingClientRect();
@@ -15823,7 +15965,7 @@ var resolveInlineHighlightAnchorRect = ({ rootEl, comment, pendingComment }) =>
15823
15965
  if (!anchorId) return null;
15824
15966
  const safeId = String(anchorId);
15825
15967
  const highlightNodes = rootEl.querySelectorAll(COMMENT_HIGHLIGHT_SELECTOR);
15826
- for (const node of highlightNodes) {
15968
+ for (const node of Array.from(highlightNodes)) {
15827
15969
  if (!parseCommentIds(node.getAttribute(COMMENT_HIGHLIGHT_DATA_ATTR)).includes(safeId)) continue;
15828
15970
  if (typeof node.getBoundingClientRect !== "function") continue;
15829
15971
  return node.getBoundingClientRect();
@@ -15908,9 +16050,10 @@ function useCompactCommentPopover({ activeComment, pendingComment, activeCompact
15908
16050
  };
15909
16051
  const trackCompactPopoverClickAnchor = (e) => {
15910
16052
  const root = superdocRoot.value;
15911
- if (!root || !layers.value?.contains(e.target)) return;
15912
- const anchorElement = (typeof document.elementsFromPoint === "function" ? document.elementsFromPoint(e.clientX, e.clientY) : []).find((node) => node?.nodeType === 1 && root.contains(node) && node.closest("[data-track-change-id], .superdoc-comment-highlight, .sd-comment-anchor"))?.closest("[data-track-change-id], .superdoc-comment-highlight, .sd-comment-anchor") ?? (e.target?.nodeType === 1 ? e.target.closest("[data-track-change-id], .superdoc-comment-highlight, .sd-comment-anchor") : null);
15913
- if (clearCompactPopoverIfPdfClickedOutside(root, e.target, anchorElement)) return;
16053
+ const target = e.target instanceof Node ? e.target : null;
16054
+ if (!root || !layers.value?.contains(target)) return;
16055
+ const anchorElement = (typeof document.elementsFromPoint === "function" ? document.elementsFromPoint(e.clientX, e.clientY) : []).find((node) => node?.nodeType === 1 && root.contains(node) && node.closest("[data-track-change-id], .superdoc-comment-highlight, .sd-comment-anchor"))?.closest("[data-track-change-id], .superdoc-comment-highlight, .sd-comment-anchor") ?? (target instanceof Element ? target.closest("[data-track-change-id], .superdoc-comment-highlight, .sd-comment-anchor") : null);
16056
+ if (clearCompactPopoverIfPdfClickedOutside(root, target, anchorElement)) return;
15914
16057
  if (e.button !== 0 || e.pointerType !== "mouse") return;
15915
16058
  const anchorRect = anchorElement && typeof anchorElement.getBoundingClientRect === "function" ? anchorElement.getBoundingClientRect() : null;
15916
16059
  if (!anchorRect) return;
@@ -16144,7 +16287,7 @@ function normalizeHyperlinksConfig(config = {}) {
16144
16287
  };
16145
16288
  }
16146
16289
  //#endregion
16147
- //#region src/helpers/v2-review-target.js
16290
+ //#region src/helpers/v2-review-target.ts
16148
16291
  /**
16149
16292
  * Resolve the comments-store identity for a document-origin v2 review target.
16150
16293
  * Painted review carriers can become interactive before the asynchronous row
@@ -16160,15 +16303,126 @@ function resolveV2ReviewTargetCommentId(target, getComment) {
16160
16303
  return commentId != null ? String(commentId) : null;
16161
16304
  }
16162
16305
  //#endregion
16163
- //#region src/helpers/v2-mutation-exception.js
16306
+ //#region src/helpers/v2-review-geometry-delivery.ts
16307
+ function createReviewGeometryDelivery(deliver) {
16308
+ const packets = /* @__PURE__ */ new Map();
16309
+ let stageOwners = /* @__PURE__ */ new WeakMap();
16310
+ let active = null;
16311
+ const documentOf = (current) => current.documentId != null ? String(current.documentId) : current.mountContainer?.dataset.superdocV2DocumentId ?? null;
16312
+ const matches = (documentId, current) => current.mountContainer?.dataset.superdocV2DocumentId === documentId && (current.documentId == null || String(current.documentId) === documentId);
16313
+ const matchesPaint = (packet, current) => typeof packet.payload.paintEpoch !== "number" || packet.payload.paintEpoch === current.epoch;
16314
+ const observe = (current) => {
16315
+ const stage = current.mountContainer;
16316
+ const documentId = documentOf(current);
16317
+ if (!stage || !documentId || !matches(documentId, current)) return;
16318
+ const previousOwner = stageOwners.get(stage);
16319
+ if (previousOwner && previousOwner !== documentId) packets.delete(previousOwner);
16320
+ stageOwners.set(stage, documentId);
16321
+ const packet = packets.get(documentId);
16322
+ if (packet?.stage && packet.stage !== stage) packets.delete(documentId);
16323
+ else if (packet && (!active || active.stage !== stage || active.documentId !== documentId || active.epoch !== current.epoch)) packet.delivered = false;
16324
+ active = {
16325
+ documentId,
16326
+ stage,
16327
+ epoch: current.epoch
16328
+ };
16329
+ };
16330
+ const dispatch = (documentId, packet, current, replay = false) => {
16331
+ if (!current.mountContainer || !matches(documentId, current) || !matchesPaint(packet, current)) return false;
16332
+ if (packet.stage && packet.stage !== current.mountContainer || packet.delivering || packet.delivered && !replay) return false;
16333
+ const wasDelivered = packet.delivered;
16334
+ const previousStage = packet.stage;
16335
+ packet.stage = current.mountContainer;
16336
+ packet.delivered = true;
16337
+ packet.delivering = true;
16338
+ try {
16339
+ deliver(current, packet.payload);
16340
+ } catch (error) {
16341
+ packet.delivered = wasDelivered;
16342
+ packet.stage = previousStage;
16343
+ throw error;
16344
+ } finally {
16345
+ packet.delivering = false;
16346
+ }
16347
+ return true;
16348
+ };
16349
+ const notify = (documentId, payload, current, ready = true) => {
16350
+ if (!payload.reviewGeometry || !Array.isArray(payload.pageIndices)) return false;
16351
+ let key;
16352
+ try {
16353
+ key = JSON.stringify([
16354
+ documentId,
16355
+ payload.documentEpoch,
16356
+ payload.paintEpoch,
16357
+ payload.commitSequence,
16358
+ payload.pageIndices,
16359
+ payload.zoomScale,
16360
+ payload.reviewGeometry
16361
+ ]);
16362
+ } catch {
16363
+ return false;
16364
+ }
16365
+ const isCurrent = current != null && matches(documentId, current);
16366
+ if (isCurrent) observe(current);
16367
+ const previous = packets.get(documentId);
16368
+ if (previous && previous.payload.documentEpoch === payload.documentEpoch && typeof previous.payload.paintEpoch === "number" && typeof payload.paintEpoch === "number" && payload.paintEpoch < previous.payload.paintEpoch) return false;
16369
+ if (previous && previous.payload.documentEpoch === payload.documentEpoch && typeof previous.payload.commitSequence === "number" && typeof payload.commitSequence === "number" && payload.commitSequence < previous.payload.commitSequence) return false;
16370
+ if (previous && previous.payload.documentEpoch === payload.documentEpoch && typeof previous.payload.generation === "number" && typeof payload.generation === "number" && payload.generation < previous.payload.generation) return false;
16371
+ if (isCurrent && typeof payload.paintEpoch === "number" && typeof current.epoch === "number" && payload.paintEpoch < current.epoch) return false;
16372
+ if (previous?.key === key) {
16373
+ previous.payload = payload;
16374
+ return isCurrent && ready ? dispatch(documentId, previous, current) : false;
16375
+ }
16376
+ const packet = {
16377
+ payload,
16378
+ key,
16379
+ stage: isCurrent ? current.mountContainer : previous && previous.payload.documentEpoch === payload.documentEpoch ? previous.stage : null,
16380
+ delivered: false,
16381
+ delivering: false
16382
+ };
16383
+ packets.set(documentId, packet);
16384
+ if (!isCurrent || !ready) return false;
16385
+ try {
16386
+ return dispatch(documentId, packet, current);
16387
+ } catch (error) {
16388
+ if (previous) packets.set(documentId, previous);
16389
+ else packets.delete(documentId);
16390
+ throw error;
16391
+ }
16392
+ };
16393
+ return {
16394
+ notify,
16395
+ observe,
16396
+ flush(current) {
16397
+ observe(current);
16398
+ const documentId = documentOf(current);
16399
+ const packet = documentId == null ? null : packets.get(documentId);
16400
+ return documentId && packet ? dispatch(documentId, packet, current) : false;
16401
+ },
16402
+ replay(current) {
16403
+ observe(current);
16404
+ const documentId = documentOf(current);
16405
+ const packet = documentId == null ? null : packets.get(documentId);
16406
+ return documentId && packet ? dispatch(documentId, packet, current, true) : false;
16407
+ },
16408
+ reset() {
16409
+ packets.clear();
16410
+ stageOwners = /* @__PURE__ */ new WeakMap();
16411
+ active = null;
16412
+ }
16413
+ };
16414
+ }
16415
+ //#endregion
16416
+ //#region src/helpers/v2-mutation-exception.ts
16164
16417
  var FAILURE_SOURCES = /* @__PURE__ */ new Set(["shell", "receipt"]);
16165
16418
  var DIAGNOSTIC_CODE = /^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/;
16166
16419
  var COMMAND_KIND = /^[a-z][a-z0-9-]*(?::[a-z][a-z0-9-]*)?$/;
16167
16420
  function createV2MutationRejectionCause(event) {
16168
- if (!event || typeof event !== "object") return {};
16169
- const reason = event.reason ?? (event.failureSource === "receipt" ? event.failure?.code : void 0);
16421
+ if (!isPlainObject$1(event)) return {};
16422
+ const failure = isPlainObject$1(event.failure) ? event.failure : void 0;
16423
+ const reason = event.reason ?? (event.failureSource === "receipt" ? failure?.code : void 0);
16170
16424
  return {
16171
- ...FAILURE_SOURCES.has(event.failureSource) ? { failureSource: event.failureSource } : {},
16425
+ ...typeof event.failureSource === "string" && FAILURE_SOURCES.has(event.failureSource) ? { failureSource: event.failureSource } : {},
16172
16426
  ...typeof reason === "string" && DIAGNOSTIC_CODE.test(reason) ? { reason } : {},
16173
16427
  ...typeof event.inputKind === "string" && DIAGNOSTIC_CODE.test(event.inputKind) ? { inputKind: event.inputKind } : {},
16174
16428
  ...typeof event.editableCommandKind === "string" && event.editableCommandKind.length <= 128 && COMMAND_KIND.test(event.editableCommandKind) ? { editableCommandKind: event.editableCommandKind } : {}
@@ -16196,7 +16450,7 @@ function createV2MutationRejectionNotificationGate(matches) {
16196
16450
  };
16197
16451
  }
16198
16452
  //#endregion
16199
- //#region src/helpers/v2-author-required-rejection.js
16453
+ //#region src/helpers/v2-author-required-rejection.ts
16200
16454
  var NO_AUTHOR_SIGNAL = "no-author-configured";
16201
16455
  /** Stable, non-terminal exception code surfaced to consumers. */
16202
16456
  var V2_AUTHOR_REQUIRED_CODE = "author-required";
@@ -16208,15 +16462,11 @@ var V2_AUTHOR_REQUIRED_MESSAGE = "This edit needs an author identity. Set `user.
16208
16462
  function messageHasNoAuthorSignal(message) {
16209
16463
  return typeof message === "string" && message.includes(NO_AUTHOR_SIGNAL);
16210
16464
  }
16211
- /**
16212
- * @param {any} event - a `v2-host-event` payload.
16213
- * @returns {boolean} true when the rejection means the session needs an author.
16214
- */
16215
16465
  function isV2AuthorRequiredRejection(event) {
16216
- if (!event || event.type !== "mutation:rejected") return false;
16466
+ if (!isPlainObject$1(event) || event.type !== "mutation:rejected") return false;
16217
16467
  if (event.failureSource === "receipt") {
16218
16468
  const failure = event.failure;
16219
- if (!failure) return false;
16469
+ if (!isPlainObject$1(failure)) return false;
16220
16470
  return failure.code === "PRECONDITION_FAILED" && messageHasNoAuthorSignal(failure.message);
16221
16471
  }
16222
16472
  if (event.failureSource === "shell") {
@@ -16229,7 +16479,7 @@ function createV2AuthorRequiredNotificationGate() {
16229
16479
  return createV2MutationRejectionNotificationGate(isV2AuthorRequiredRejection);
16230
16480
  }
16231
16481
  //#endregion
16232
- //#region src/helpers/v2-bulk-decision-event.js
16482
+ //#region src/helpers/v2-bulk-decision-event.ts
16233
16483
  function toV2BulkDecisionEvent(documentId, fact) {
16234
16484
  if (fact?.schemaVersion !== 1 || fact.decision !== "accept" && fact.decision !== "reject") return null;
16235
16485
  return {
@@ -16241,7 +16491,7 @@ function toV2BulkDecisionEvent(documentId, fact) {
16241
16491
  };
16242
16492
  }
16243
16493
  //#endregion
16244
- //#region src/helpers/v2-keyboard-edit-rejection.js
16494
+ //#region src/helpers/v2-keyboard-edit-rejection.ts
16245
16495
  var EDIT_INPUT_KINDS = /* @__PURE__ */ new Set([
16246
16496
  "keydown",
16247
16497
  "beforeinput",
@@ -16291,7 +16541,7 @@ var EDIT_FAILURE_SOURCES = /* @__PURE__ */ new Set(["shell", "receipt"]);
16291
16541
  var V2_EDIT_REJECTED_CODE = "edit-rejected";
16292
16542
  var V2_EDIT_REJECTED_MESSAGE = "This edit couldn’t be completed. Adjust the selection and try again.";
16293
16543
  function isV2KeyboardEditRejection(event) {
16294
- return Boolean(event && event.type === "mutation:rejected" && event.origin === "document-surface" && EDIT_FAILURE_SOURCES.has(event.failureSource) && EDIT_INPUT_KINDS.has(event.inputKind) && (EDIT_COMMAND_KINDS.has(event.editableCommandKind) || event.editableCommandKind == null && EDIT_INPUT_WITHOUT_COMMAND_KINDS.has(event.inputKind)));
16544
+ return isPlainObject$1(event) && event.type === "mutation:rejected" && event.origin === "document-surface" && EDIT_FAILURE_SOURCES.has(event.failureSource) && EDIT_INPUT_KINDS.has(event.inputKind) && (EDIT_COMMAND_KINDS.has(event.editableCommandKind) || event.editableCommandKind == null && EDIT_INPUT_WITHOUT_COMMAND_KINDS.has(event.inputKind));
16295
16545
  }
16296
16546
  function createV2KeyboardEditRejectionException(documentId, event) {
16297
16547
  return {
@@ -16305,24 +16555,17 @@ function createV2KeyboardEditRejectionNotificationGate() {
16305
16555
  return createV2MutationRejectionNotificationGate(isV2KeyboardEditRejection);
16306
16556
  }
16307
16557
  //#endregion
16308
- //#region src/helpers/v2-selection-sync.js
16558
+ //#region src/helpers/v2-selection-sync.ts
16309
16559
  /**
16310
16560
  * A logical v2 selection snapshot represents a non-empty range when its anchor
16311
16561
  * and focus point at different block positions. Mirrors the range check the v2
16312
16562
  * shell uses when it emits `v2-selection-changed`.
16313
- *
16314
- * @param {{ anchor?: { blockId?: unknown, blockOffset?: unknown }, focus?: { blockId?: unknown, blockOffset?: unknown } } | null | undefined} snapshot
16315
- * @returns {boolean}
16316
16563
  */
16317
16564
  var isV2RangeSnapshot = (snapshot) => Boolean(snapshot?.anchor && snapshot?.focus && (snapshot.anchor.blockId !== snapshot.focus.blockId || snapshot.anchor.blockOffset !== snapshot.focus.blockOffset));
16318
16565
  /**
16319
16566
  * Detect a real browser text selection whose endpoints are outside the active
16320
16567
  * v2 mount. Editable host selections can have no native DOM range, so an
16321
16568
  * absent range must not count as an outside selection.
16322
- *
16323
- * @param {Selection | null | undefined} selection
16324
- * @param {Node | null | undefined} root
16325
- * @returns {boolean}
16326
16569
  */
16327
16570
  var hasOutsideV2DomRangeSelection = (selection, root) => {
16328
16571
  if (!selection || selection.rangeCount < 1 || selection.isCollapsed || !root) return false;
@@ -16338,10 +16581,6 @@ var hasOutsideV2DomRangeSelection = (selection, root) => {
16338
16581
  * host snapshot rather than clobber the selection the host just published.
16339
16582
  * Only editable modes use the host snapshot. Viewing mode keeps native DOM
16340
16583
  * selection, and unknown modes should not preserve stale host state.
16341
- *
16342
- * @param {string | null | undefined} documentMode
16343
- * @param {unknown} hostSnapshot
16344
- * @returns {boolean}
16345
16584
  */
16346
16585
  var shouldPreserveHostV2Selection = (documentMode, hostSnapshot) => {
16347
16586
  if (documentMode !== "editing" && documentMode !== "suggesting") return false;
@@ -16352,10 +16591,6 @@ var shouldPreserveHostV2Selection = (documentMode, hostSnapshot) => {
16352
16591
  * rebuilt. Keep an already-positioned comment affordance during that gap when
16353
16592
  * the host still owns a range; otherwise the button unmounts and remounts on
16354
16593
  * consecutive paint frames, making it blink and intercepting its own click.
16355
- *
16356
- * @param {boolean} hasHostRange
16357
- * @param {{ source?: unknown } | null | undefined} selectionPosition
16358
- * @returns {boolean}
16359
16594
  */
16360
16595
  var shouldRetainV2FloatingSelection = (hasHostRange, selectionPosition) => hasHostRange === true && selectionPosition?.source === "document-editor";
16361
16596
  //#endregion
@@ -16547,7 +16782,7 @@ function usePasswordPrompt({ getSurfaceManager, getPasswordPromptConfig, onUnhan
16547
16782
  })
16548
16783
  });
16549
16784
  } else {
16550
- const mod = await import("./chunks/PasswordPromptSurface-6mF6Azda.es.js");
16785
+ const mod = await import("./chunks/PasswordPromptSurface-DjV1JgjD.es.js");
16551
16786
  const surfaceId = `password-prompt-${doc.id}`;
16552
16787
  handle = manager.open({
16553
16788
  id: surfaceId,
@@ -16649,9 +16884,6 @@ function createReplaceContinuation(session, { getCurrentSession, applySlice, onS
16649
16884
  }
16650
16885
  //#endregion
16651
16886
  //#region src/composables/use-find-replace.ts
16652
- function isThenable$2(value) {
16653
- return (typeof value === "object" || typeof value === "function") && value !== null && "then" in value && typeof value.then === "function";
16654
- }
16655
16887
  var DEFAULT_TEXTS = {
16656
16888
  findPlaceholder: "Find",
16657
16889
  findAriaLabel: "Find text",
@@ -16826,7 +17058,7 @@ function useFindReplace({ getSurfaceManager, getActiveEditor, activeEditorRef, g
16826
17058
  try {
16827
17059
  result = search.replace(replaceText.value);
16828
17060
  } catch {}
16829
- if (isThenable$2(result)) Promise.resolve(result).then(finish, finish);
17061
+ if (isThenable$1(result)) Promise.resolve(result).then(finish, finish);
16830
17062
  else finish();
16831
17063
  }
16832
17064
  function replaceAll() {
@@ -16848,7 +17080,7 @@ function useFindReplace({ getSurfaceManager, getActiveEditor, activeEditorRef, g
16848
17080
  try {
16849
17081
  result = search.replaceAll(replaceText.value);
16850
17082
  } catch {}
16851
- if (isThenable$2(result)) Promise.resolve(result).then(finish, finish);
17083
+ if (isThenable$1(result)) Promise.resolve(result).then(finish, finish);
16852
17084
  else finish();
16853
17085
  }
16854
17086
  function syncFromSearchSession() {
@@ -17210,7 +17442,7 @@ function useFindReplace({ getSurfaceManager, getActiveEditor, activeEditorRef, g
17210
17442
  } else {
17211
17443
  let FindReplaceSurface;
17212
17444
  try {
17213
- FindReplaceSurface = (await import("./chunks/FindReplaceSurface-BKVHzhQ4.es.js")).default;
17445
+ FindReplaceSurface = (await import("./chunks/FindReplaceSurface-BEnRLpD9.es.js")).default;
17214
17446
  } catch {
17215
17447
  opening = false;
17216
17448
  return;
@@ -17477,7 +17709,7 @@ var plus_solid_default = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0
17477
17709
  //#region ../../shared/common/icons/trash-can-solid.svg?raw
17478
17710
  var trash_can_solid_default = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d=\"M135.2 17.7C140.6 6.8 151.7 0 163.8 0L284.2 0c12.1 0 23.2 6.8 28.6 17.7L320 32l96 0c17.7 0 32 14.3 32 32s-14.3 32-32 32L32 96C14.3 96 0 81.7 0 64S14.3 32 32 32l96 0 7.2-14.3zM32 128l384 0 0 320c0 35.3-28.7 64-64 64L96 512c-35.3 0-64-28.7-64-64l0-320zm96 64c-8.8 0-16 7.2-16 16l0 224c0 8.8 7.2 16 16 16s16-7.2 16-16l0-224c0-8.8-7.2-16-16-16zm96 0c-8.8 0-16 7.2-16 16l0 224c0 8.8 7.2 16 16 16s16-7.2 16-16l0-224c0-8.8-7.2-16-16-16zm96 0c-8.8 0-16 7.2-16 16l0 224c0 8.8 7.2 16 16 16s16-7.2 16-16l0-224c0-8.8-7.2-16-16-16z\"/></svg>";
17479
17711
  //#endregion
17480
- //#region src/internal/toolbar/built-in/toolbarIcons.js
17712
+ //#region src/internal/toolbar/built-in/toolbarIcons.ts
17481
17713
  var toolbarIcons = {
17482
17714
  undo: rotate_left_solid_default,
17483
17715
  redo: rotate_right_solid_default,
@@ -17584,11 +17816,11 @@ var _hoisted_8$3 = { class: "input-row url-input-row" };
17584
17816
  var _hoisted_9$2 = ["innerHTML"];
17585
17817
  var _hoisted_10$2 = ["readonly", "onKeydown"];
17586
17818
  var _hoisted_11$2 = ["innerHTML"];
17587
- var _hoisted_12$1 = {
17819
+ var _hoisted_12$2 = {
17588
17820
  key: 0,
17589
17821
  class: "input-row link-buttons"
17590
17822
  };
17591
- var _hoisted_13$1 = ["disabled"];
17823
+ var _hoisted_13$2 = ["disabled"];
17592
17824
  var _hoisted_14$1 = ["innerHTML"];
17593
17825
  var _hoisted_15$1 = ["disabled"];
17594
17826
  var _hoisted_16$1 = {
@@ -17847,7 +18079,7 @@ var LinkInput_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__
17847
18079
  "data-item": "btn-link-open"
17848
18080
  }, null, 10, _hoisted_11$2)
17849
18081
  ]),
17850
- !isViewingMode.value ? (openBlock(), createElementBlock("div", _hoisted_12$1, [isEditing.value ? (openBlock(), createElementBlock("button", {
18082
+ !isViewingMode.value ? (openBlock(), createElementBlock("div", _hoisted_12$2, [isEditing.value ? (openBlock(), createElementBlock("button", {
17851
18083
  key: 0,
17852
18084
  class: "remove-btn",
17853
18085
  onClick: handleRemove,
@@ -17856,7 +18088,7 @@ var LinkInput_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__
17856
18088
  }, [createElementVNode("div", {
17857
18089
  class: "remove-btn__icon",
17858
18090
  innerHTML: unref(toolbarIcons).removeLink
17859
- }, null, 8, _hoisted_14$1), _cache[6] || (_cache[6] = createTextVNode(" Remove ", -1))], 8, _hoisted_13$1)) : createCommentVNode("", true), createElementVNode("button", {
18091
+ }, null, 8, _hoisted_14$1), _cache[6] || (_cache[6] = createTextVNode(" Remove ", -1))], 8, _hoisted_13$2)) : createCommentVNode("", true), createElementVNode("button", {
17860
18092
  class: normalizeClass(["sd-submit-btn", { "disable-btn": isDisabled.value }]),
17861
18093
  onClick: handleSubmit,
17862
18094
  disabled: isSubmitting.value,
@@ -17870,7 +18102,7 @@ var LinkInput_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__
17870
18102
  }
17871
18103
  }), [["__scopeId", "data-v-172107e4"]]);
17872
18104
  //#endregion
17873
- //#region src/internal/toolbar/built-in/scroll-helpers.js
18105
+ //#region src/internal/toolbar/built-in/scroll-helpers.ts
17874
18106
  function getScrollableParent(element) {
17875
18107
  let currentElement = element;
17876
18108
  while (currentElement) {
@@ -17903,7 +18135,6 @@ function scrollToElement(targetElement, options = {
17903
18135
  var FLOATING_SURFACE_ID = "link-popover";
17904
18136
  var RECENT_OUTSIDE_CLOSE_MS = 500;
17905
18137
  var MAX_ANCHOR_NAVIGATION_STEPS = 4;
17906
- var isThenable$1 = (value) => Boolean(value && (typeof value === "object" || typeof value === "function") && "then" in value && typeof value.then === "function");
17907
18138
  function isLinkPopoverDocumentApi(value) {
17908
18139
  return isPlainObject$1(value) || typeof value === "function";
17909
18140
  }
@@ -18767,6 +18998,7 @@ function createV2EditorRuntimeAdapter(options) {
18767
18998
  const { id, documentId, root, host, getLegacyEditorProjection, onUnregister } = options;
18768
18999
  let snapshot = host.getSnapshot();
18769
19000
  let didDispose = false;
19001
+ let detached = false;
18770
19002
  let unregistered = false;
18771
19003
  let mountedFocusHandle = null;
18772
19004
  let tokenRevision = 0;
@@ -19063,7 +19295,17 @@ function createV2EditorRuntimeAdapter(options) {
19063
19295
  };
19064
19296
  }
19065
19297
  }
19298
+ function releaseHostSubscriptions() {
19299
+ hostUnsubscribe?.();
19300
+ hostUnsubscribe = null;
19301
+ pageMetricsUnsubscribe?.();
19302
+ pageMetricsUnsubscribe = null;
19303
+ selectionUnsubscribe?.();
19304
+ selectionUnsubscribe = null;
19305
+ activeSelectionController = null;
19306
+ }
19066
19307
  function handleHostSnapshot(next) {
19308
+ if (detached || didDispose) return;
19067
19309
  snapshot = next;
19068
19310
  invalidatePositionTokens();
19069
19311
  syncSelectionSubscription();
@@ -19077,10 +19319,7 @@ function createV2EditorRuntimeAdapter(options) {
19077
19319
  });
19078
19320
  if (next.state === "disposed" && !didDispose) {
19079
19321
  didDispose = true;
19080
- hostUnsubscribe?.();
19081
- pageMetricsUnsubscribe?.();
19082
- selectionUnsubscribe?.();
19083
- selectionUnsubscribe = null;
19322
+ releaseHostSubscriptions();
19084
19323
  emit({ type: "disposed" });
19085
19324
  listeners.clear();
19086
19325
  if (!unregistered) {
@@ -19227,10 +19466,7 @@ function createV2EditorRuntimeAdapter(options) {
19227
19466
  await host.dispose();
19228
19467
  if (!didDispose) {
19229
19468
  didDispose = true;
19230
- hostUnsubscribe?.();
19231
- pageMetricsUnsubscribe?.();
19232
- selectionUnsubscribe?.();
19233
- selectionUnsubscribe = null;
19469
+ releaseHostSubscriptions();
19234
19470
  emit({ type: "disposed" });
19235
19471
  listeners.clear();
19236
19472
  if (!unregistered) {
@@ -19309,8 +19545,17 @@ function createV2EditorRuntimeAdapter(options) {
19309
19545
  }
19310
19546
  },
19311
19547
  attachMountHandle(handle) {
19548
+ if (detached || didDispose) return;
19312
19549
  mountedFocusHandle = handle;
19313
19550
  syncSelectionSubscription();
19551
+ },
19552
+ detach() {
19553
+ if (detached) return;
19554
+ detached = true;
19555
+ unregistered = true;
19556
+ releaseHostSubscriptions();
19557
+ mountedFocusHandle = null;
19558
+ listeners.clear();
19314
19559
  }
19315
19560
  };
19316
19561
  }
@@ -20473,7 +20718,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
20473
20718
  __name: "SuperDoc",
20474
20719
  emits: ["selection-update"],
20475
20720
  setup(__props, { emit: __emit }) {
20476
- const PdfViewer = defineAsyncComponent(() => import("./chunks/PdfViewer-aOWhUVI7.es.js"));
20721
+ const PdfViewer = defineAsyncComponent(() => import("./chunks/PdfViewer-C2r-K8zC.es.js"));
20477
20722
  const getDocumentLoadPassword = (doc) => doc.password ?? proxy.$superdoc.config.password;
20478
20723
  const resolveDocumentV2Collaboration = (doc) => {
20479
20724
  const rawConfig = doc?.v2Collaboration;
@@ -20558,7 +20803,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
20558
20803
  if (commentsConfig.highlightHoverColor) vars["--sd-comments-highlight-hover"] = commentsConfig.highlightHoverColor;
20559
20804
  const activeTrackChangeColors = {
20560
20805
  ...commentsConfig.trackChangeHighlightColors || {},
20561
- ...commentsConfig.trackChangeActiveHighlightColors || {}
20806
+ ...commentsConfig.trackChangeActiveHighlightColors
20562
20807
  };
20563
20808
  if (activeTrackChangeColors.insertBorder) vars["--sd-tracked-changes-insert-border"] = activeTrackChangeColors.insertBorder;
20564
20809
  if (activeTrackChangeColors.insertBackground) vars["--sd-tracked-changes-insert-background"] = activeTrackChangeColors.insertBackground;
@@ -20587,6 +20832,8 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
20587
20832
  const v2ReviewSidebarUnlocked = ref(false);
20588
20833
  const v2GeometryEpoch = ref(null);
20589
20834
  let v2GeometryRafHandle = 0;
20835
+ let v2GeometryReplayRafHandle = 0;
20836
+ let v2GeometryReplayGeneration = 0;
20590
20837
  let v2TrackedChangeRestampRetainPaints = 0;
20591
20838
  let v2TrackedChangeRestampRetentionReason = null;
20592
20839
  const activeEditorRef = computed$1(() => proxy.$superdoc.activeEditor);
@@ -20770,6 +21017,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
20770
21017
  if (document && provider?.sendStateless) document.provider = null;
20771
21018
  const entry = v2Runtimes.get(documentId);
20772
21019
  if (!entry) return;
21020
+ entry.adapter.detach();
20773
21021
  proxy.$superdoc["unregisterEditorRuntime"](entry.runtimeId);
20774
21022
  v2Runtimes.delete(documentId);
20775
21023
  const hostRoot = subDocumentRoots.get(documentId);
@@ -20923,7 +21171,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
20923
21171
  if (loadingDocument?.loadScope && !loadingDocument.loadScope.finish()) return;
20924
21172
  clearV2EditorFailure(payload.documentId ?? null);
20925
21173
  clearV2MutationRejectionNotifications(payload.documentId ?? null);
20926
- const { host, firstPartyCommandMutation, mount, documentId, capabilities, editCommands, bindEditShortcuts, bindSessionShortcuts, commentsAdapter, trackedChangesAdapter, contextMenu, documentApi, documentMutationReadiness, documentApiUnavailableReason, pageMetrics, pageLayout, pageFurniture, presence, lock, connection, collaborationProvider, fonts, replaceFile, upgradeToCollaboration, documentOpenToken } = payload;
21174
+ const { host, firstPartyCommandMutation, mount, documentId, capabilities, editCommands, bindEditShortcuts, bindSessionShortcuts, commentsAdapter, trackedChangesAdapter, contextMenu, viewportQuery, documentApi, documentMutationReadiness, documentApiUnavailableReason, pageMetrics, pageLayout, pageFurniture, presence, lock, connection, collaborationProvider, fonts, replaceFile, upgradeToCollaboration, documentOpenToken } = payload;
20927
21175
  documentOpenTelemetry?.trackDocumentOpen(documentOpenToken ?? null, documentId ?? null);
20928
21176
  const saveV2Bytes = createV2SaveQueue(async (saveOptions = {}) => {
20929
21177
  if (!host || typeof host.save !== "function") throw new Error("v2-editor: save unavailable");
@@ -21019,21 +21267,21 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21019
21267
  };
21020
21268
  };
21021
21269
  const currentV2AuthoringMode = () => proxy.$superdoc.config.documentMode === "suggesting" ? "tracked" : "direct";
21022
- const readV2CurrentSelectionTarget = () => {
21023
- const selection = (host?.getHandles?.()?.editing ?? null)?.selection ?? null;
21024
- if (typeof selection?.toSelectionTarget !== "function") return createV2AuthoringFailure("selection-target-unavailable", "editing.selection.toSelectionTarget is not available");
21025
- const result = selection.toSelectionTarget();
21026
- if (!result || result.kind === "rejected") {
21027
- const rejection = result?.rejection ?? {};
21028
- return createV2AuthoringFailure(rejection.reason ?? rejection.code ?? "selection-target-rejected", rejection.detail);
21270
+ const confirmedV2SelectionTarget = async () => {
21271
+ const selectionTargets = host?.getHandles?.()?.editing?.selectionTargets;
21272
+ if (typeof selectionTargets?.confirmedSelectionTarget !== "function") return createV2AuthoringFailure("selection-confirmation-unavailable");
21273
+ try {
21274
+ const result = await selectionTargets.confirmedSelectionTarget();
21275
+ if (!result?.ok || !result.target) return createV2AuthoringFailure(result?.reason ?? "selection-unconfirmed");
21276
+ return {
21277
+ ok: true,
21278
+ target: result.target,
21279
+ mode: result.mode,
21280
+ story: result.story
21281
+ };
21282
+ } catch (error) {
21283
+ return createV2AuthoringFailure("selection-unconfirmed", error instanceof Error ? error.message : String(error));
21029
21284
  }
21030
- if (!result.target || typeof result.target !== "object") return createV2AuthoringFailure("selection-target-rejected", "selection target was not produced");
21031
- return {
21032
- ok: true,
21033
- target: result.target,
21034
- mode: result.mode ?? "range",
21035
- story: result.story
21036
- };
21037
21285
  };
21038
21286
  const failureFromV2Receipt = (receipt, fallbackReason) => {
21039
21287
  const failure = receipt?.failure ?? receipt;
@@ -21107,7 +21355,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21107
21355
  },
21108
21356
  serializeSelectionToClipboard: async ({ includeHtml = true } = {}) => {
21109
21357
  if (!documentApi?.clipboard || typeof documentApi.clipboard.serializeSelection !== "function") return createV2AuthoringFailure("clipboard-serialize-unavailable", "activeEditor.doc.clipboard.serializeSelection is not available");
21110
- const selected = readV2CurrentSelectionTarget();
21358
+ const selected = await confirmedV2SelectionTarget();
21111
21359
  if (!selected.ok) return selected;
21112
21360
  let serialized;
21113
21361
  try {
@@ -21133,7 +21381,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21133
21381
  let pasteTarget = target;
21134
21382
  let selectionMode = null;
21135
21383
  if (!pasteTarget) {
21136
- const selected = readV2CurrentSelectionTarget();
21384
+ const selected = await confirmedV2SelectionTarget();
21137
21385
  if (!selected.ok) return selected;
21138
21386
  pasteTarget = selected.target;
21139
21387
  selectionMode = selected.mode;
@@ -21194,6 +21442,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21194
21442
  v2Comments: commentsAdapter ?? null,
21195
21443
  v2TrackedChanges: trackedChangesAdapter ?? null,
21196
21444
  contextMenu: contextMenu ?? null,
21445
+ viewportQuery: viewportQuery ?? null,
21197
21446
  pageMetrics: pageMetrics ?? null,
21198
21447
  pageLayout: pageLayout ?? null,
21199
21448
  pageFurniture: pageFurniture ?? null,
@@ -21276,7 +21525,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21276
21525
  let v2SelectionToolbarRafHandle = 0;
21277
21526
  let v2SelectionToolbarTimeoutHandle = 0;
21278
21527
  let v2DomSelectionRafHandle = 0;
21279
- const buildV2EditorUpdatePayload = ({ editor, sourceEditor, surface = "body", headerId = null, sectionType = null } = {}) => {
21528
+ const buildV2EditorUpdatePayload = ({ editor, sourceEditor, surface = "body", headerId = null, sectionType = null, isRemote = false } = {}) => {
21280
21529
  const activeEditor = proxy.$superdoc?.activeEditor ?? null;
21281
21530
  const effectiveEditor = editor ?? sourceEditor ?? activeEditor ?? void 0;
21282
21531
  return {
@@ -21284,7 +21533,8 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21284
21533
  sourceEditor: sourceEditor ?? effectiveEditor,
21285
21534
  surface,
21286
21535
  headerId,
21287
- sectionType
21536
+ sectionType,
21537
+ isRemote
21288
21538
  };
21289
21539
  };
21290
21540
  const emitV2EditorUpdate = (payload = {}) => {
@@ -21534,10 +21784,11 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21534
21784
  const publishV2PendingPositionEntry = (entry) => {
21535
21785
  if (!entry) return;
21536
21786
  handleEditorLocationsUpdate({
21537
- ...editorCommentPositions.value ?? {},
21787
+ ...editorCommentPositions.value,
21538
21788
  pending: entry
21539
21789
  });
21540
21790
  };
21791
+ let scheduleV2GeometryReplay = () => {};
21541
21792
  const v2GeometryPublisher = resolvedEditorIntegration.createGeometryPublisher({
21542
21793
  getLayersContainer: () => layers.value ?? null,
21543
21794
  isCommentsEnabled: () => Boolean(commentsModuleConfig.value) && shouldRenderCommentsInViewing.value,
@@ -21553,7 +21804,8 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21553
21804
  v2ReviewSidebarUnlocked.value = true;
21554
21805
  v2GeometryEpoch.value = v2GeometryPublisher.getLastEpoch();
21555
21806
  }
21556
- }
21807
+ },
21808
+ onCommittedPagePaint: ({ payload }) => scheduleV2GeometryReplay(payload)
21557
21809
  });
21558
21810
  const armV2TrackedChangeRestampGeometryRetention = (reason = "tracked-change-restamp") => {
21559
21811
  v2TrackedChangeRestampRetainPaints = Math.max(v2TrackedChangeRestampRetainPaints, 3);
@@ -21573,20 +21825,53 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21573
21825
  v2TrackedChangeRestampRetentionReason = null;
21574
21826
  };
21575
21827
  const resolveV2GeometryPublishOptions = (options) => typeof options === "function" ? options() : options;
21828
+ const isV2GeometryReady = (current) => {
21829
+ if (!current?.mountContainer || !commentsModuleConfig.value || !shouldRenderCommentsInViewing.value) return false;
21830
+ const mount = current.mount;
21831
+ let liveEpoch;
21832
+ try {
21833
+ liveEpoch = mount?.getRenderEpoch?.();
21834
+ } catch {
21835
+ return false;
21836
+ }
21837
+ return v2GeometryPublisher.getLastPayload() === current && v2GeometryPublisher.getLastEpoch() != null && v2GeometryPublisher.getLastEpoch() === current.epoch && (liveEpoch == null || liveEpoch === current.epoch);
21838
+ };
21576
21839
  const scheduleV2GeometryPublish = (payload, options = void 0) => {
21577
21840
  v2GeometryRender.value = payload;
21578
21841
  if (v2GeometryRafHandle && typeof cancelAnimationFrame === "function") cancelAnimationFrame(v2GeometryRafHandle);
21842
+ const publish = () => {
21843
+ const current = v2GeometryRender.value;
21844
+ Promise.resolve(v2GeometryPublisher.publish(current, resolveV2GeometryPublishOptions(options))).then(() => {
21845
+ if (current === v2GeometryRender.value && isV2GeometryReady(current)) v2ReviewGeometryDelivery.flush(current);
21846
+ });
21847
+ };
21579
21848
  if (typeof requestAnimationFrame !== "function") {
21580
- v2GeometryPublisher.publish(v2GeometryRender.value, resolveV2GeometryPublishOptions(options));
21849
+ publish();
21581
21850
  return;
21582
21851
  }
21583
21852
  v2GeometryRafHandle = requestAnimationFrame(() => {
21584
21853
  v2GeometryRafHandle = 0;
21585
- v2GeometryPublisher.publish(v2GeometryRender.value, resolveV2GeometryPublishOptions(options));
21854
+ publish();
21586
21855
  });
21587
21856
  };
21857
+ const v2ReviewGeometryDelivery = createReviewGeometryDelivery((current, packet) => {
21858
+ v2GeometryPublisher.noteCommittedPageWindow?.(packet);
21859
+ scheduleV2GeometryPublish(current);
21860
+ });
21861
+ scheduleV2GeometryReplay = (payload) => {
21862
+ if (!payload || payload !== v2GeometryRender.value) return;
21863
+ const generation = ++v2GeometryReplayGeneration;
21864
+ if (v2GeometryReplayRafHandle && typeof cancelAnimationFrame === "function") cancelAnimationFrame(v2GeometryReplayRafHandle);
21865
+ const replay = () => {
21866
+ v2GeometryReplayRafHandle = 0;
21867
+ if (generation === v2GeometryReplayGeneration && payload === v2GeometryRender.value && isV2GeometryReady(payload)) v2ReviewGeometryDelivery.replay(payload);
21868
+ };
21869
+ if (typeof requestAnimationFrame === "function") v2GeometryReplayRafHandle = requestAnimationFrame(replay);
21870
+ else queueMicrotask(replay);
21871
+ };
21588
21872
  const onV2Render = (payload) => {
21589
21873
  if (!payload) return;
21874
+ v2ReviewGeometryDelivery.observe(payload);
21590
21875
  scheduleV2GeometryPublish(payload, () => takeV2TrackedChangeRestampGeometryRetention());
21591
21876
  if (v2HasRangeSelection.value) scheduleV2SelectionToolbarStateSync();
21592
21877
  };
@@ -21619,7 +21904,11 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21619
21904
  cancelScheduledV2SelectionToolbarSync();
21620
21905
  if (v2GeometryRafHandle && typeof cancelAnimationFrame === "function") cancelAnimationFrame(v2GeometryRafHandle);
21621
21906
  v2GeometryRafHandle = 0;
21907
+ if (v2GeometryReplayRafHandle && typeof cancelAnimationFrame === "function") cancelAnimationFrame(v2GeometryReplayRafHandle);
21908
+ v2GeometryReplayRafHandle = 0;
21909
+ v2GeometryReplayGeneration++;
21622
21910
  v2GeometryRender.value = null;
21911
+ v2ReviewGeometryDelivery.reset();
21623
21912
  v2GeometryEpoch.value = null;
21624
21913
  v2GeometryAvailable.value = false;
21625
21914
  v2ReviewSidebarUnlocked.value = false;
@@ -21678,6 +21967,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21678
21967
  v2ReviewWindowController.invalidate("collaboration:remote-review-changed");
21679
21968
  v2ReviewWindowController.refreshCommittedWindow("collaboration:remote-review-changed");
21680
21969
  }
21970
+ emitV2EditorUpdate({ isRemote: true });
21681
21971
  return;
21682
21972
  }
21683
21973
  if (event.type === "source:complete") {
@@ -21766,14 +22056,15 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21766
22056
  if (!isV2Mode.value) return;
21767
22057
  if (!v2GeometryPublisher.getLastPayload()) return;
21768
22058
  if (v2GeometryRafHandle && typeof cancelAnimationFrame === "function") cancelAnimationFrame(v2GeometryRafHandle);
21769
- if (typeof requestAnimationFrame !== "function") {
21770
- v2GeometryPublisher.recollect(resolveV2GeometryPublishOptions(options));
21771
- return;
21772
- }
21773
- v2GeometryRafHandle = requestAnimationFrame(() => {
22059
+ const recollect = () => {
21774
22060
  v2GeometryRafHandle = 0;
21775
- v2GeometryPublisher.recollect(resolveV2GeometryPublishOptions(options));
21776
- });
22061
+ const current = v2GeometryRender.value;
22062
+ Promise.resolve(v2GeometryPublisher.recollect(resolveV2GeometryPublishOptions(options))).then(() => {
22063
+ if (current === v2GeometryRender.value && isV2GeometryReady(current)) v2ReviewGeometryDelivery.replay(current);
22064
+ });
22065
+ };
22066
+ if (typeof requestAnimationFrame !== "function") recollect();
22067
+ else v2GeometryRafHandle = requestAnimationFrame(recollect);
21777
22068
  };
21778
22069
  const handleV2DocumentModeChange = () => {
21779
22070
  if (!isV2Mode.value) return;
@@ -22001,13 +22292,17 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
22001
22292
  ...payload,
22002
22293
  documentId: doc.id
22003
22294
  });
22004
- v2GeometryPublisher.noteCommittedPageWindow?.(payload);
22295
+ if (payload.reviewGeometry && Array.isArray(payload.pageIndices)) {
22296
+ const current = v2GeometryRender.value;
22297
+ v2ReviewGeometryDelivery.notify(String(doc.id), payload, current, isV2GeometryReady(current));
22298
+ } else v2GeometryPublisher.noteCommittedPageWindow?.(payload);
22005
22299
  },
22006
22300
  fontAssets: proxy.$superdoc.config.fonts,
22007
22301
  workerUrls: proxy.$superdoc.config.workerUrls,
22008
22302
  fieldContext: doc.fieldContext,
22009
22303
  fieldUpdatePolicy: proxy.$superdoc.config.fieldUpdatePolicy,
22010
22304
  workerStartupTimeoutMs: proxy.$superdoc.config.workerStartupTimeoutMs,
22305
+ ...proxy.$superdoc.config.changes ? { changes: proxy.$superdoc.config.changes } : {},
22011
22306
  proofing: resolvedProofingConfig.value,
22012
22307
  isNewFile,
22013
22308
  password: getDocumentLoadPassword(doc),
@@ -22028,7 +22323,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
22028
22323
  documentLoading: proxy.$superdoc.uiConfig.loading.enabled,
22029
22324
  linkPopoverResolver: getHyperlinkActivationHandler(),
22030
22325
  layoutEngineOptions: useLayoutEngine ? {
22031
- ...proxy.$superdoc.config.layoutEngineOptions || {},
22326
+ ...proxy.$superdoc.config.layoutEngineOptions,
22032
22327
  proofing: resolvedProofingConfig.value,
22033
22328
  debugLabel: proxy.$superdoc.config.layoutEngineOptions?.debugLabel ?? doc.name ?? doc.id,
22034
22329
  zoom: (activeZoom.value ?? 100) / 100,
@@ -22291,11 +22586,16 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
22291
22586
  if (layout !== "auto") return true;
22292
22587
  return !isCompactCommentsMode.value;
22293
22588
  });
22294
- const showCommentsSidebar = computed$1(() => {
22589
+ const sidebarEnabled = computed$1(() => {
22295
22590
  if (isV2Mode.value && !v2GeometryAvailable.value && !v2ReviewSidebarUnlocked.value && !hasPdfFloatingComments.value) return false;
22296
22591
  if (!shouldRenderCommentsInViewing.value) return false;
22297
22592
  if (!shouldUseSidebarComments.value) return false;
22298
- return pendingComment.value || (floatingComments.value.length > 0 || hasOpenTrackedChanges.value) && isReady.value && layers.value && isCommentsEnabled.value && !isCommentsListVisible.value;
22593
+ return Boolean(pendingComment.value || isReady.value && layers.value && isCommentsEnabled.value && !isCommentsListVisible.value);
22594
+ });
22595
+ const showCommentsSidebar = useCommentSidebar({
22596
+ documentKey: computed$1(() => JSON.stringify(documents.value.map((document) => document.id))),
22597
+ enabled: sidebarEnabled,
22598
+ hasContent: computed$1(() => Boolean(pendingComment.value || floatingComments.value.length || hasOpenTrackedChanges.value))
22299
22599
  });
22300
22600
  const activeCompactComment = computed$1(() => {
22301
22601
  if (showCommentsSidebar.value) return null;
@@ -22919,7 +23219,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
22919
23219
  ref_key: "superdocRoot",
22920
23220
  ref: superdocRoot,
22921
23221
  class: normalizeClass(["superdoc", {
22922
- "superdoc--with-sidebar": showCommentsSidebar.value,
23222
+ "superdoc--with-sidebar": unref(showCommentsSidebar),
22923
23223
  "superdoc--web-layout": unref(proxy).$superdoc.config.viewOptions?.layout === "web",
22924
23224
  "superdoc--contained": unref(proxy).$superdoc.config.contained,
22925
23225
  "high-contrast": unref(isHighContrastMode)
@@ -23067,7 +23367,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
23067
23367
  onAnchorActivate: handleHighlightClick
23068
23368
  })) : createCommentVNode("", true)
23069
23369
  ])], 512),
23070
- showCommentsSidebar.value ? (openBlock(), createElementBlock("div", {
23370
+ unref(showCommentsSidebar) ? (openBlock(), createElementBlock("div", {
23071
23371
  key: 0,
23072
23372
  ref_key: "rightSidebarRef",
23073
23373
  ref: rightSidebarRef,
@@ -23090,7 +23390,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
23090
23390
  ], 46, _hoisted_1$17);
23091
23391
  };
23092
23392
  }
23093
- }), [["__scopeId", "data-v-26bcc2bd"]]);
23393
+ }), [["__scopeId", "data-v-b5da778c"]]);
23094
23394
  //#endregion
23095
23395
  //#region src/core/create-app.ts
23096
23396
  var PINIA_DEVTOOLS_SETUP_EVENT = "devtools-plugin:setup";
@@ -24571,6 +24871,16 @@ var SurfaceManager = class {
24571
24871
  };
24572
24872
  //#endregion
24573
24873
  //#region src/helpers/deprecation.ts
24874
+ /**
24875
+ * Deprecation utilities for steering consumers toward the Document API.
24876
+ *
24877
+ * ProseMirror will be removed in a future major version. These helpers emit
24878
+ * one-time console warnings when deprecated properties are accessed on an
24879
+ * editor instance handed to consumer callbacks (onEditorCreate, etc.).
24880
+ *
24881
+ * Internal code continues to use the raw Editor — only the consumer-facing
24882
+ * boundary wraps the editor with a Proxy.
24883
+ */
24574
24884
  var MIGRATION_URL = "https://docs.superdoc.dev/guides/migration/document-api";
24575
24885
  var _warned = /* @__PURE__ */ new Set();
24576
24886
  /**
@@ -24759,7 +25069,8 @@ function normalizeSuperDocUser(user) {
24759
25069
  if (!user || typeof user !== "object") return { ...DEFAULT_SUPERDOC_USER };
24760
25070
  return {
24761
25071
  ...DEFAULT_SUPERDOC_USER,
24762
- ...user
25072
+ ...user,
25073
+ ...user.commentIdentity !== void 0 ? { commentIdentity: normalizeCommentIdentity(user.commentIdentity) } : {}
24763
25074
  };
24764
25075
  }
24765
25076
  //#endregion
@@ -25278,6 +25589,7 @@ function normalizeUiConfig(config = {}) {
25278
25589
  * Keeping policy separate means an application can disable the built-in
25279
25590
  * comments UI without losing the rules enforced by its custom UI.
25280
25591
  */
25592
+ var isAutocapitalize = (value) => value === "off" || value === "sentences" || value === "words" || value === "characters";
25281
25593
  var isCommentInteractionLevel = (value) => value === "read" || value === "write" || value === "resolve";
25282
25594
  /**
25283
25595
  * Resolve the effective interaction policy.
@@ -25297,14 +25609,26 @@ function normalizeInteractionConfig(config = {}) {
25297
25609
  const canonicalLevel = interactionComments.level;
25298
25610
  const hasCanonicalLevel = isCommentInteractionLevel(canonicalLevel);
25299
25611
  const level = hasCanonicalLevel ? canonicalLevel : legacyReadOnly ? "read" : legacyAllowResolve ? "resolve" : "write";
25612
+ const interactionWritingAssistance = isPlainObject$1(interaction.writingAssistance) ? interaction.writingAssistance : {};
25613
+ const autocorrect = interactionWritingAssistance.autocorrect === true;
25614
+ const autocapitalize = isAutocapitalize(interactionWritingAssistance.autocapitalize) ? interactionWritingAssistance.autocapitalize : "off";
25615
+ const spellcheck = interactionWritingAssistance.spellcheck === true;
25300
25616
  const canonicalAllowDecisions = interactionTrackedChanges.allowDecisions;
25617
+ const interactionTouch = isPlainObject$1(interaction.touch) ? interaction.touch : {};
25301
25618
  return {
25302
25619
  comments: {
25303
25620
  level,
25304
25621
  readOnly: hasCanonicalLevel ? level === "read" : legacyReadOnly,
25305
25622
  allowResolve: hasCanonicalLevel ? level === "resolve" : legacyAllowResolve
25306
25623
  },
25307
- trackedChanges: { allowDecisions: typeof canonicalAllowDecisions === "boolean" ? canonicalAllowDecisions : !legacyReadOnly }
25624
+ trackedChanges: { allowDecisions: typeof canonicalAllowDecisions === "boolean" ? canonicalAllowDecisions : !legacyReadOnly },
25625
+ touch: { gestures: interactionTouch.gestures === true },
25626
+ writingAssistance: {
25627
+ enabled: autocorrect || autocapitalize !== "off" || spellcheck,
25628
+ autocorrect,
25629
+ autocapitalize,
25630
+ spellcheck
25631
+ }
25308
25632
  };
25309
25633
  }
25310
25634
  //#endregion
@@ -25873,23 +26197,21 @@ function resolveBuiltInItemCommandId(name) {
25873
26197
  return resolveToolbarCommandId(name) ?? UNCATALOGED_ITEM_COMMAND_IDS[name] ?? null;
25874
26198
  }
25875
26199
  //#endregion
25876
- //#region src/internal/toolbar/built-in/helpers.js
26200
+ //#region src/internal/toolbar/built-in/helpers.ts
25877
26201
  var sanitizeNumber = (value, defaultNumber) => {
25878
- let sanitized = value.replace(/[^0-9.]/g, "");
25879
- sanitized = parseFloat(sanitized);
25880
- if (isNaN(sanitized)) sanitized = defaultNumber;
25881
- sanitized = parseFloat(sanitized);
25882
- return sanitized;
25883
- };
25884
- var throttle = (func, wait, options) => {
25885
- let timeout, args, result;
26202
+ const parsed = parseFloat(String(value ?? "").replace(/[^0-9.]/g, ""));
26203
+ return isNaN(parsed) ? defaultNumber : parsed;
26204
+ };
26205
+ var throttle = (func, wait, options = {}) => {
26206
+ let timeout;
26207
+ let args;
26208
+ let result;
25886
26209
  let previous = 0;
25887
- if (!options) options = {};
25888
26210
  const later = () => {
25889
26211
  previous = options.leading === false ? 0 : Date.now();
25890
- timeout = null;
25891
- result = func(...args);
25892
- if (!timeout) args = null;
26212
+ timeout = void 0;
26213
+ if (args) result = func(...args);
26214
+ if (!timeout) args = void 0;
25893
26215
  };
25894
26216
  const throttled = (...callArgs) => {
25895
26217
  const _now = Date.now();
@@ -25899,18 +26221,18 @@ var throttle = (func, wait, options) => {
25899
26221
  if (remaining <= 0 || remaining > wait) {
25900
26222
  if (timeout) {
25901
26223
  clearTimeout(timeout);
25902
- timeout = null;
26224
+ timeout = void 0;
25903
26225
  }
25904
26226
  previous = _now;
25905
26227
  result = func(...callArgs);
25906
- if (!timeout) args = null;
26228
+ if (!timeout) args = void 0;
25907
26229
  } else if (!timeout && options.trailing !== false) timeout = setTimeout(later, remaining);
25908
26230
  return result;
25909
26231
  };
25910
26232
  throttled.cancel = function() {
25911
26233
  clearTimeout(timeout);
25912
26234
  previous = 0;
25913
- timeout = args = null;
26235
+ timeout = args = void 0;
25914
26236
  };
25915
26237
  return throttled;
25916
26238
  };
@@ -25962,26 +26284,39 @@ var ToolbarButtonIcon_default = /* @__PURE__ */ _plugin_vue_export_helper_defaul
25962
26284
  var _hoisted_1$15 = [
25963
26285
  "role",
25964
26286
  "aria-label",
25965
- "aria-disabled"
26287
+ "aria-disabled",
26288
+ "aria-busy"
25966
26289
  ];
25967
26290
  var _hoisted_2$11 = ["data-item"];
25968
26291
  var _hoisted_3$10 = ["data-item"];
25969
26292
  var _hoisted_4$8 = {
25970
- key: 1,
26293
+ key: 0,
26294
+ role: "status",
26295
+ "aria-label": "Applying list",
26296
+ style: { "font-weight": "700" }
26297
+ };
26298
+ var _hoisted_5$6 = {
26299
+ key: 2,
25971
26300
  class: "sd-button-label"
25972
26301
  };
25973
- var _hoisted_5$6 = ["data-item", "aria-label"];
25974
- var _hoisted_6$4 = ["innerHTML"];
25975
- var _hoisted_7$2 = { key: 2 };
25976
- var _hoisted_8$2 = ["onKeydown", "id"];
25977
- var _hoisted_9$1 = [
26302
+ var _hoisted_6$4 = ["data-item", "aria-label"];
26303
+ var _hoisted_7$2 = ["innerHTML"];
26304
+ var _hoisted_8$2 = {
26305
+ key: 0,
26306
+ role: "status",
26307
+ "aria-label": "Applying list",
26308
+ style: { "font-weight": "700" }
26309
+ };
26310
+ var _hoisted_9$1 = { key: 3 };
26311
+ var _hoisted_10$1 = ["onKeydown", "id"];
26312
+ var _hoisted_11$1 = [
25978
26313
  "placeholder",
25979
26314
  "onKeydown",
25980
26315
  "id",
25981
26316
  "data-item"
25982
26317
  ];
25983
- var _hoisted_10$1 = ["data-item", "innerHTML"];
25984
- var _hoisted_11$1 = {
26318
+ var _hoisted_12$1 = ["data-item", "innerHTML"];
26319
+ var _hoisted_13$1 = {
25985
26320
  "aria-live": "polite",
25986
26321
  class: "sd-visually-hidden"
25987
26322
  };
@@ -26154,6 +26489,7 @@ var ToolbarButton_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
26154
26489
  role: __props.isOverflowItem ? "menuitem" : "button",
26155
26490
  "aria-label": unref(attributes).ariaLabel,
26156
26491
  "aria-disabled": unref(disabled) ? "true" : void 0,
26492
+ "aria-busy": unref(attributes).ariaBusy || void 0,
26157
26493
  "data-sd-part": "toolbar-item",
26158
26494
  onClick: handleOuterClick,
26159
26495
  onKeydown: _cache[9] || (_cache[9] = withKeys(($event) => onEnterKeydown($event), ["enter"])),
@@ -26175,17 +26511,21 @@ var ToolbarButton_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
26175
26511
  class: "sd-toolbar-button__main",
26176
26512
  "data-item": `btn-${unref(name) || ""}-main`,
26177
26513
  onClick: _cache[0] || (_cache[0] = ($event) => handleSplitMainClick($event))
26178
- }, [unref(icon) ? (openBlock(), createBlock(ToolbarButtonIcon_default, {
26179
- key: 0,
26180
- color: unref(iconColor),
26181
- class: "sd-toolbar-icon",
26182
- icon: unref(icon),
26183
- name: unref(name)
26184
- }, null, 8, [
26185
- "color",
26186
- "icon",
26187
- "name"
26188
- ])) : createCommentVNode("", true), unref(label) && !unref(hideLabel) && !unref(inlineTextInputVisible) ? (openBlock(), createElementBlock("div", _hoisted_4$8, toDisplayString(unref(label)), 1)) : createCommentVNode("", true)], 8, _hoisted_3$10)) : createCommentVNode("", true),
26514
+ }, [
26515
+ unref(attributes).ariaBusy && isSplit.value ? (openBlock(), createElementBlock("span", _hoisted_4$8, "…")) : createCommentVNode("", true),
26516
+ unref(icon) ? (openBlock(), createBlock(ToolbarButtonIcon_default, {
26517
+ key: 1,
26518
+ color: unref(iconColor),
26519
+ class: "sd-toolbar-icon",
26520
+ icon: unref(icon),
26521
+ name: unref(name)
26522
+ }, null, 8, [
26523
+ "color",
26524
+ "icon",
26525
+ "name"
26526
+ ])) : createCommentVNode("", true),
26527
+ unref(label) && !unref(hideLabel) && !unref(inlineTextInputVisible) ? (openBlock(), createElementBlock("div", _hoisted_5$6, toDisplayString(unref(label)), 1)) : createCommentVNode("", true)
26528
+ ], 8, _hoisted_3$10)) : createCommentVNode("", true),
26189
26529
  isSplit.value ? (openBlock(), createElementBlock("div", {
26190
26530
  key: 1,
26191
26531
  class: "sd-toolbar-button__caret",
@@ -26196,9 +26536,10 @@ var ToolbarButton_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
26196
26536
  class: "sd-dropdown-caret",
26197
26537
  innerHTML: caretIcon.value,
26198
26538
  style: normalizeStyle({ opacity: unref(disabled) ? .6 : 1 })
26199
- }, null, 12, _hoisted_6$4)], 8, _hoisted_5$6)) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [
26539
+ }, null, 12, _hoisted_7$2)], 8, _hoisted_6$4)) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [
26540
+ unref(attributes).ariaBusy ? (openBlock(), createElementBlock("span", _hoisted_8$2, "…")) : createCommentVNode("", true),
26200
26541
  unref(icon) ? (openBlock(), createBlock(ToolbarButtonIcon_default, {
26201
- key: 0,
26542
+ key: 1,
26202
26543
  color: unref(iconColor),
26203
26544
  class: "sd-toolbar-icon",
26204
26545
  icon: unref(icon),
@@ -26209,10 +26550,10 @@ var ToolbarButton_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
26209
26550
  "name"
26210
26551
  ])) : createCommentVNode("", true),
26211
26552
  unref(label) && !unref(hideLabel) && (!unref(inlineTextInputVisible) || itemName.value === "fontFamily") ? (openBlock(), createElementBlock("div", {
26212
- key: 1,
26553
+ key: 2,
26213
26554
  class: normalizeClass(["sd-button-label", { "sd-visually-hidden": unref(inlineTextInputVisible) && itemName.value === "fontFamily" }])
26214
26555
  }, toDisplayString(unref(label)), 3)) : createCommentVNode("", true),
26215
- unref(inlineTextInputVisible) ? (openBlock(), createElementBlock("span", _hoisted_7$2, [unref(name) === "fontSize" ? withDirectives((openBlock(), createElementBlock("input", {
26556
+ unref(inlineTextInputVisible) ? (openBlock(), createElementBlock("span", _hoisted_9$1, [unref(name) === "fontSize" ? withDirectives((openBlock(), createElementBlock("input", {
26216
26557
  key: 0,
26217
26558
  "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => inlineTextInput.value = $event),
26218
26559
  onKeydown: [withKeys(withModifiers(handleInlineInputEnter, ["stop", "prevent"]), ["enter"]), _cache[2] || (_cache[2] = withKeys(($event) => handleInlineInputTab($event), ["tab"]))],
@@ -26224,7 +26565,7 @@ var ToolbarButton_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
26224
26565
  autocomplete: "off",
26225
26566
  ref_key: "inlineInput",
26226
26567
  ref: inlineInput
26227
- }, null, 42, _hoisted_8$2)), [[vModelText, inlineTextInput.value]]) : withDirectives((openBlock(), createElementBlock("input", {
26568
+ }, null, 42, _hoisted_10$1)), [[vModelText, inlineTextInput.value]]) : withDirectives((openBlock(), createElementBlock("input", {
26228
26569
  key: 1,
26229
26570
  "onUpdate:modelValue": _cache[5] || (_cache[5] = ($event) => inlineTextInput.value = $event),
26230
26571
  placeholder: unref(label),
@@ -26241,20 +26582,20 @@ var ToolbarButton_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
26241
26582
  autocomplete: "off",
26242
26583
  ref_key: "inlineInput",
26243
26584
  ref: inlineInput
26244
- }, null, 42, _hoisted_9$1)), [[vModelText, inlineTextInput.value]])])) : createCommentVNode("", true),
26585
+ }, null, 42, _hoisted_11$1)), [[vModelText, inlineTextInput.value]])])) : createCommentVNode("", true),
26245
26586
  unref(hasCaret) ? (openBlock(), createElementBlock("div", {
26246
- key: 3,
26587
+ key: 4,
26247
26588
  class: "sd-dropdown-caret",
26248
26589
  "data-item": `btn-${unref(name) || ""}-toggle`,
26249
26590
  innerHTML: caretIcon.value,
26250
26591
  style: normalizeStyle({ opacity: unref(disabled) ? .6 : 1 })
26251
- }, null, 12, _hoisted_10$1)) : createCommentVNode("", true)
26592
+ }, null, 12, _hoisted_12$1)) : createCommentVNode("", true)
26252
26593
  ], 64)),
26253
- createElementVNode("div", _hoisted_11$1, toDisplayString(`${unref(attributes).ariaLabel} ${unref(active) ? "selected" : "unset"}`), 1)
26594
+ createElementVNode("div", _hoisted_13$1, toDisplayString(`${unref(attributes).ariaLabel} ${unref(active) ? "selected" : "unset"}`), 1)
26254
26595
  ], 10, _hoisted_2$11)], 46, _hoisted_1$15);
26255
26596
  };
26256
26597
  }
26257
- }), [["__scopeId", "data-v-e599674c"]]);
26598
+ }), [["__scopeId", "data-v-b49ede12"]]);
26258
26599
  //#endregion
26259
26600
  //#region src/internal/toolbar/built-in/ToolbarSeparator.vue?vue&type=script&setup=true&lang.ts
26260
26601
  var _hoisted_1$14 = {
@@ -26809,24 +27150,11 @@ var OverflowMenu_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
26809
27150
  }
26810
27151
  }), [["__scopeId", "data-v-d2e189fd"]]);
26811
27152
  //#endregion
26812
- //#region src/internal/toolbar/built-in/font-typeahead.js
26813
- /**
26814
- * Pure typeahead/autocomplete helpers for the font comboboxes (family + size).
26815
- *
26816
- * Matching is case-insensitive prefix matching on the logical font labels.
26817
- * Space is a normal query character, so the query is never split on spaces;
26818
- * only the outer whitespace is ignored when comparing.
26819
- *
26820
- * These functions hold no Vue or DOM state so CI can unit-test them in isolation.
26821
- */
27153
+ //#region src/internal/toolbar/built-in/font-typeahead.ts
26822
27154
  var normalize = (value) => String(value ?? "").trim().toLowerCase();
26823
27155
  /**
26824
27156
  * Index of the first label that starts with `query` (case-insensitive).
26825
- * An empty/whitespace query never matches.
26826
- *
26827
- * @param {string} query
26828
- * @param {ReadonlyArray<string>} labels
26829
- * @returns {number} matching index or -1
27157
+ * An empty/whitespace query never matches. Returns the matching index or -1.
26830
27158
  */
26831
27159
  var findPrefixMatchIndex = (query, labels) => {
26832
27160
  const q = normalize(query);
@@ -26840,12 +27168,7 @@ var findPrefixMatchIndex = (query, labels) => {
26840
27168
  * When `autocomplete` is true and a prefix match exists that is longer than the
26841
27169
  * typed query, the completion is shown with the suffix selected so the next
26842
27170
  * keystroke replaces it. When false (deletion) or no match exists, the raw
26843
- * query is shown with the caret at its end.
26844
- *
26845
- * @param {string} query - the user-typed portion
26846
- * @param {ReadonlyArray<string>} labels
26847
- * @param {{ autocomplete?: boolean }} [options]
26848
- * @returns {{ matchIndex: number, display: string, selectionStart: number, selectionEnd: number }}
27171
+ * query is shown with the caret at its end. `query` is the user-typed portion.
26849
27172
  */
26850
27173
  var computeTypeahead = (query, labels, { autocomplete = true } = {}) => {
26851
27174
  const typed = String(query ?? "");
@@ -26897,9 +27220,6 @@ var MAX_FONT_SIZE = 1638;
26897
27220
  * the nearest half point (DOCX/Word support half points such as 10.5), and clamps
26898
27221
  * to the supported range. Returns '' when nothing usable was typed, including
26899
27222
  * negative or sub-minimum values.
26900
- *
26901
- * @param {string} value
26902
- * @returns {string} point size (whole or half), or '' when empty/invalid
26903
27223
  */
26904
27224
  var normalizeCustomFontSize = (value) => {
26905
27225
  const cleaned = String(value ?? "").replace(/[^0-9.-]/g, "");
@@ -27567,7 +27887,7 @@ var SdTooltip_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__
27567
27887
  ]);
27568
27888
  const contentStyle = computed$1(() => ({
27569
27889
  ...props.contentStyle,
27570
- ...attrs.style || {},
27890
+ ...attrs.style,
27571
27891
  position: "fixed",
27572
27892
  top: position.value.top,
27573
27893
  left: position.value.left,
@@ -27736,7 +28056,7 @@ var SdTooltip_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__
27736
28056
  }, 8, ["css"])]))], 64);
27737
28057
  };
27738
28058
  }
27739
- }), [["__scopeId", "data-v-dfd6f1d1"]]);
28059
+ }), [["__scopeId", "data-v-87967ab1"]]);
27740
28060
  //#endregion
27741
28061
  //#region src/internal/toolbar/built-in/ButtonGroup.vue?vue&type=script&setup=true&lang.ts
27742
28062
  var _hoisted_1$11 = [
@@ -28590,7 +28910,7 @@ var Toolbar_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PU
28590
28910
  }
28591
28911
  }), [["__scopeId", "data-v-bce7c04a"]]);
28592
28912
  //#endregion
28593
- //#region src/internal/toolbar/built-in/font-options.js
28913
+ //#region src/internal/toolbar/built-in/font-options.ts
28594
28914
  /**
28595
28915
  * Normalize a font dropdown option so each row renders in its own typeface.
28596
28916
  *
@@ -28946,7 +29266,7 @@ var StyleButtonsList_default = /* @__PURE__ */ _plugin_vue_export_helper_default
28946
29266
  }
28947
29267
  }), [["__scopeId", "data-v-539d0995"]]);
28948
29268
  //#endregion
28949
- //#region src/internal/toolbar/built-in/list-style-buttons.js
29269
+ //#region src/internal/toolbar/built-in/list-style-buttons.ts
28950
29270
  var bulletStyleButtons = [
28951
29271
  {
28952
29272
  key: "disc",
@@ -30257,7 +30577,7 @@ var PageNumberMenu_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/
30257
30577
  onClick: ($event) => selectTile(tile),
30258
30578
  onDblclick: applyPageNumbers,
30259
30579
  onKeydown: onTileKeydown
30260
- }, [(openBlock(), createElementBlock("svg", _hoisted_5, [_cache[4] || (_cache[4] = createStaticVNode("<rect class=\"sd-page-number-menu__sheet\" x=\"0.75\" y=\"0.75\" width=\"34.5\" height=\"44.5\" rx=\"2.5\" data-v-a6c124c0></rect><rect class=\"sd-page-number-menu__line\" x=\"7\" y=\"14\" width=\"22\" height=\"2\" rx=\"1\" data-v-a6c124c0></rect><rect class=\"sd-page-number-menu__line\" x=\"7\" y=\"19\" width=\"18\" height=\"2\" rx=\"1\" data-v-a6c124c0></rect><rect class=\"sd-page-number-menu__line\" x=\"7\" y=\"24\" width=\"22\" height=\"2\" rx=\"1\" data-v-a6c124c0></rect><rect class=\"sd-page-number-menu__line\" x=\"7\" y=\"29\" width=\"14\" height=\"2\" rx=\"1\" data-v-a6c124c0></rect>", 5)), createElementVNode("rect", {
30580
+ }, [(openBlock(), createElementBlock("svg", _hoisted_5, [_cache[4] || (_cache[4] = createStaticVNode("<rect class=\"sd-page-number-menu__sheet\" x=\"0.75\" y=\"0.75\" width=\"34.5\" height=\"44.5\" rx=\"2.5\" data-v-e3105afd></rect><rect class=\"sd-page-number-menu__line\" x=\"7\" y=\"14\" width=\"22\" height=\"2\" rx=\"1\" data-v-e3105afd></rect><rect class=\"sd-page-number-menu__line\" x=\"7\" y=\"19\" width=\"18\" height=\"2\" rx=\"1\" data-v-e3105afd></rect><rect class=\"sd-page-number-menu__line\" x=\"7\" y=\"24\" width=\"22\" height=\"2\" rx=\"1\" data-v-e3105afd></rect><rect class=\"sd-page-number-menu__line\" x=\"7\" y=\"29\" width=\"14\" height=\"2\" rx=\"1\" data-v-e3105afd></rect>", 5)), createElementVNode("rect", {
30261
30581
  class: "sd-page-number-menu__chip",
30262
30582
  x: CHIP_X[tile.alignment],
30263
30583
  y: CHIP_Y[tile.position],
@@ -30337,23 +30657,21 @@ var PageNumberMenu_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/
30337
30657
  ], 10, _hoisted_1);
30338
30658
  };
30339
30659
  }
30340
- }), [["__scopeId", "data-v-a6c124c0"]]);
30660
+ }), [["__scopeId", "data-v-e3105afd"]]);
30341
30661
  //#endregion
30342
- //#region src/internal/toolbar/built-in/page-number-feedback.js
30662
+ //#region src/internal/toolbar/built-in/page-number-feedback.ts
30343
30663
  var FLASH_CLASS = "sd-page-number-flash";
30344
30664
  var FLASH_MS = 1400;
30345
30665
  var nextFrame = (view) => new Promise((resolve) => {
30346
- if (typeof view?.requestAnimationFrame === "function") view.requestAnimationFrame(() => resolve());
30666
+ if (typeof view.requestAnimationFrame === "function") view.requestAnimationFrame(() => resolve());
30347
30667
  else setTimeout(resolve, 16);
30348
30668
  });
30349
30669
  /**
30350
30670
  * Briefly highlight the painted header or footer regions that are on screen,
30351
30671
  * so the user sees where page numbers landed without the caret leaving the
30352
30672
  * body. Regions are read after the repaint that follows the mutation; nothing
30353
- * happens when none is visible or the host is unavailable.
30354
- *
30355
- * @param {HTMLElement | null | undefined} host Document viewport host element.
30356
- * @param {'header' | 'footer'} kind
30673
+ * happens when none is visible or the host is unavailable. `host` is the
30674
+ * document viewport host element.
30357
30675
  */
30358
30676
  async function flashPageNumberRegions(host, kind) {
30359
30677
  const view = host?.ownerDocument?.defaultView;
@@ -30361,7 +30679,7 @@ async function flashPageNumberRegions(host, kind) {
30361
30679
  await nextFrame(view);
30362
30680
  await nextFrame(view);
30363
30681
  const viewportHeight = view.innerHeight;
30364
- const regions = [...host.querySelectorAll(`[data-sd-headerfooter-kind="${kind}"]`)].filter((region) => {
30682
+ const regions = Array.from(host.querySelectorAll(`[data-sd-headerfooter-kind="${kind}"]`)).filter((region) => {
30365
30683
  const rect = region.getBoundingClientRect();
30366
30684
  return rect.height > 0 && rect.bottom > 0 && rect.top < viewportHeight;
30367
30685
  });
@@ -30562,6 +30880,8 @@ var makeDefaultItems = ({ superToolbar, toolbarIcons: configuredIcons, toolbarTe
30562
30880
  attributes: { ariaLabel: "Bold" }
30563
30881
  });
30564
30882
  const fontOptions = (toolbarFonts ?? TOOLBAR_FONTS).map(normalizeFontOption);
30883
+ const isFontOption = (option) => typeof option.label === "string" && typeof option.key === "string";
30884
+ const currentFontOptions = () => fontButton.nestedOptions.value.filter(isFontOption);
30565
30885
  const fontButton = useToolbarItem({
30566
30886
  type: "dropdown",
30567
30887
  name: "fontFamily",
@@ -30588,8 +30908,9 @@ var makeDefaultItems = ({ superToolbar, toolbarIcons: configuredIcons, toolbarTe
30588
30908
  if (typeof fontFamily !== "string" || !fontFamily) return;
30589
30909
  const family = fontFamily.split(",")[0];
30590
30910
  const optionValue = (option) => option.value ?? option.label;
30591
- const defaultFont = fontOptions.find((option) => optionValue(option) === fontButton.defaultLabel.value);
30592
- const foundFont = fontOptions.find((option) => optionValue(option) === family);
30911
+ const options = currentFontOptions();
30912
+ const defaultFont = options.find((option) => optionValue(option) === fontButton.defaultLabel.value);
30913
+ const foundFont = options.find((option) => optionValue(option) === family);
30593
30914
  if (foundFont) {
30594
30915
  fontButton.label.value = foundFont.label;
30595
30916
  fontButton.selectedValue.value = foundFont.key;
@@ -30602,7 +30923,7 @@ var makeDefaultItems = ({ superToolbar, toolbarIcons: configuredIcons, toolbarTe
30602
30923
  }
30603
30924
  },
30604
30925
  onDeactivate: () => {
30605
- const defaultFont = fontOptions.find((option) => (option.value ?? option.label) === fontButton.defaultLabel.value);
30926
+ const defaultFont = currentFontOptions().find((option) => (option.value ?? option.label) === fontButton.defaultLabel.value);
30606
30927
  fontButton.label.value = defaultFont?.label ?? fontButton.defaultLabel.value;
30607
30928
  fontButton.selectedValue.value = defaultFont?.key ?? "";
30608
30929
  }
@@ -31709,7 +32030,7 @@ var makeDefaultItems = ({ superToolbar, toolbarIcons: configuredIcons, toolbarTe
31709
32030
  };
31710
32031
  };
31711
32032
  //#endregion
31712
- //#region src/internal/toolbar/built-in/general.js
32033
+ //#region src/internal/toolbar/built-in/general.ts
31713
32034
  /**
31714
32035
  * Minimal DOM helpers for the built-in toolbar shell.
31715
32036
  *
@@ -31720,8 +32041,6 @@ var makeDefaultItems = ({ superToolbar, toolbarIcons: configuredIcons, toolbarTe
31720
32041
  */
31721
32042
  /**
31722
32043
  * Resolve a toolbar mount target from a selector string or element id.
31723
- * @param {string|HTMLElement|null|undefined} selector
31724
- * @returns {HTMLElement|null}
31725
32044
  */
31726
32045
  var findElementBySelector = (selector) => {
31727
32046
  if (!selector) return null;
@@ -31761,9 +32080,6 @@ function toSelectedValue(value) {
31761
32080
  function nonEmptyString(value) {
31762
32081
  return typeof value === "string" && value ? value : void 0;
31763
32082
  }
31764
- function isThenable(value) {
31765
- return (typeof value === "object" || typeof value === "function") && value !== null && "then" in value && typeof value.then === "function";
31766
- }
31767
32083
  function isToolbarCommandCallback(value) {
31768
32084
  return typeof value === "function";
31769
32085
  }
@@ -31786,7 +32102,7 @@ function executeCommand(ui, commandId, argument, options = {}) {
31786
32102
  if (!options.reportUnhandled || settled !== false) return;
31787
32103
  options.onError?.(/* @__PURE__ */ new Error(`[superdoc toolbar] Command "${commandId}" did not run. Check that it is enabled and has the required input.`));
31788
32104
  };
31789
- if (isThenable(result)) Promise.resolve(result).then((settled) => {
32105
+ if (isThenable$1(result)) Promise.resolve(result).then((settled) => {
31790
32106
  reportFailure(settled);
31791
32107
  options.onSettled?.();
31792
32108
  }, (error) => {
@@ -31819,7 +32135,7 @@ var V1_ENABLED_ON_SELECTION_REASON = /* @__PURE__ */ new Set([
31819
32135
  ]);
31820
32136
  var V1_ENABLED_ON_EMPTY_REASON = /* @__PURE__ */ new Set(["list", "numberedlist"]);
31821
32137
  function shouldKeepEnabledForV1ToolbarParity(name, commandState) {
31822
- if (name === "fontFamily" || name === "fontSize") return true;
32138
+ if (name === "fontFamily" || name === "fontSize") return commandState?.reason !== "document-readonly" && commandState?.reason !== "deleted-text-formatting-unsupported";
31823
32139
  if (V1_ENABLED_ON_EMPTY_REASON.has(name) && commandState?.disabled && commandState?.supported !== false && commandState?.reason == null) return true;
31824
32140
  if (!V1_ENABLED_ON_SELECTION_REASON.has(name)) return false;
31825
32141
  const reason = commandState?.reason;
@@ -31853,6 +32169,7 @@ var RESERVED_TOOLBAR_ITEM_NAMES = /* @__PURE__ */ new Set([
31853
32169
  * proof lane, and behavior tests resolve against a real object.
31854
32170
  */
31855
32171
  var BuiltInToolbar = class extends import_eventemitter3.default {
32172
+ #formattingRefusalTooltips;
31856
32173
  #imagePicker;
31857
32174
  #groupItems;
31858
32175
  #highlightOptions;
@@ -31884,6 +32201,7 @@ var BuiltInToolbar = class extends import_eventemitter3.default {
31884
32201
  showPageNumberButton: false
31885
32202
  };
31886
32203
  this.toolbarItems = [];
32204
+ this.#formattingRefusalTooltips = /* @__PURE__ */ new WeakMap();
31887
32205
  this.overflowItems = [];
31888
32206
  this.pendingSelectionCapture = void 0;
31889
32207
  this.#imagePicker = null;
@@ -32010,7 +32328,7 @@ var BuiltInToolbar = class extends import_eventemitter3.default {
32010
32328
  const signature = this.#fontOptionsSignature();
32011
32329
  if (signature !== this._lastFontOptionsSignature) {
32012
32330
  this._lastFontOptionsSignature = signature;
32013
- this.#rebuildToolbarItems();
32331
+ if (!this.#refreshFontFamilyOptions()) this.#rebuildToolbarItems();
32014
32332
  }
32015
32333
  this.updateToolbarState();
32016
32334
  };
@@ -32138,7 +32456,7 @@ var BuiltInToolbar = class extends import_eventemitter3.default {
32138
32456
  argument: payload.argument,
32139
32457
  option: payload.option
32140
32458
  });
32141
- if (isThenable(result)) return Promise.resolve(result).catch((error) => {
32459
+ if (isThenable$1(result)) return Promise.resolve(result).catch((error) => {
32142
32460
  this.#emitCommandException(error, config.name);
32143
32461
  return false;
32144
32462
  });
@@ -32158,6 +32476,13 @@ var BuiltInToolbar = class extends import_eventemitter3.default {
32158
32476
  this.#makeToolbarItems();
32159
32477
  this.emit("toolbar-items-changed");
32160
32478
  }
32479
+ #refreshFontFamilyOptions() {
32480
+ const items = this.#getAllToolbarItems().filter((item) => item.name.value === "fontFamily");
32481
+ if (!items.length) return false;
32482
+ const options = (this.#resolveToolbarFonts(this.config.fonts) ?? TOOLBAR_FONTS).map(normalizeFontOption);
32483
+ for (const item of items) item.nestedOptions.value = options.map((option) => ({ ...option }));
32484
+ return true;
32485
+ }
32161
32486
  #fontOptionsSignature() {
32162
32487
  if (this.config.fonts) return "custom-fonts";
32163
32488
  const options = this.superdoc?.fonts?.getFontFamilyOptions?.() ?? [];
@@ -32337,6 +32662,17 @@ var BuiltInToolbar = class extends import_eventemitter3.default {
32337
32662
  const commandId = resolveBuiltInItemCommandId(name);
32338
32663
  if (!commandId) return false;
32339
32664
  const commandState = this.snapshot?.commands?.[commandId];
32665
+ if (commandState?.reason === "deleted-text-formatting-unsupported") {
32666
+ if (!this.#formattingRefusalTooltips.has(item)) this.#formattingRefusalTooltips.set(item, item.tooltip.value);
32667
+ item.tooltip.value = "Select kept text, or accept or reject the deletion before formatting.";
32668
+ } else if (this.#formattingRefusalTooltips.has(item)) {
32669
+ item.tooltip.value = this.#formattingRefusalTooltips.get(item);
32670
+ this.#formattingRefusalTooltips.delete(item);
32671
+ }
32672
+ if ((name === "list" || name === "numberedlist") && item.attributes.value.ariaBusy !== Boolean(commandState?.pending)) item.attributes.value = {
32673
+ ...item.attributes.value,
32674
+ ariaBusy: Boolean(commandState?.pending)
32675
+ };
32340
32676
  if (shouldKeepEnabledForV1ToolbarParity(name, commandState)) item.setDisabled(false);
32341
32677
  else item.setDisabled(Boolean(commandState?.disabled));
32342
32678
  const handlers = {
@@ -32615,7 +32951,7 @@ var BuiltInToolbar = class extends import_eventemitter3.default {
32615
32951
  ui: this.ui,
32616
32952
  superdoc: this.superdoc
32617
32953
  });
32618
- if (isThenable(result)) Promise.resolve(result).catch((error) => this.#emitCommandException(error, name));
32954
+ if (isThenable$1(result)) Promise.resolve(result).catch((error) => this.#emitCommandException(error, name));
32619
32955
  }
32620
32956
  this.updateToolbarState();
32621
32957
  return;
@@ -32804,6 +33140,9 @@ function deriveCollaborationConnectionKind(snapshot, hasSyncedBefore) {
32804
33140
  default: return hasSyncedBefore ? "reconnecting" : "initial";
32805
33141
  }
32806
33142
  }
33143
+ function destroyedDuringUpgradeError() {
33144
+ return /* @__PURE__ */ new Error("SuperDoc: instance was destroyed during upgrade");
33145
+ }
32807
33146
  function isV2ActiveEditorFacade(editor) {
32808
33147
  return Boolean(editor && typeof editor === "object" && editor.editorVersion === 2);
32809
33148
  }
@@ -32853,6 +33192,7 @@ function runtimeUserOf(user) {
32853
33192
  const text = (value) => typeof value === "string" && value ? value : void 0;
32854
33193
  return {
32855
33194
  name: text(user.name) ?? "",
33195
+ ...user.commentIdentity !== void 0 ? { commentIdentity: user.commentIdentity ? { ...user.commentIdentity } : null } : {},
32856
33196
  ...text(user.id) ? { id: text(user.id) } : {},
32857
33197
  ...text(user.email) ? { email: text(user.email) } : {},
32858
33198
  ...text(user.initials) ? { initials: text(user.initials) } : {}
@@ -32864,10 +33204,7 @@ var SuperDoc = class extends import_eventemitter3.default {
32864
33204
  }
32865
33205
  #destroyed;
32866
33206
  #diagnostics;
32867
- #isUpgrading;
32868
33207
  #pendingUpgrade;
32869
- /** Aborts an in-flight upgrade (sync wait or ready wait). */
32870
- #abortUpgrade;
32871
33208
  /**
32872
33209
  * Unsubscribe handle for the v2 presence → `awareness-update` bridge. Set
32873
33210
  * while a v2 collaborative editor is mounted and torn down on remount,
@@ -32881,6 +33218,7 @@ var SuperDoc = class extends import_eventemitter3.default {
32881
33218
  #v2ConnectionUnsub;
32882
33219
  /** Latest `collaboration-connection-change` payload, for late readers. */
32883
33220
  #v2ConnectionState;
33221
+ #v2ConnectionFailureReported;
32884
33222
  /** Whether the current collaboration room has completed a sync at least once. */
32885
33223
  #v2ConnectionHasSynced;
32886
33224
  /** Lock state captured before a local → v2 collaboration promotion remount. */
@@ -32976,13 +33314,12 @@ var SuperDoc = class extends import_eventemitter3.default {
32976
33314
  constructor(config) {
32977
33315
  super();
32978
33316
  this.#destroyed = false;
32979
- this.#isUpgrading = false;
32980
33317
  this.#pendingUpgrade = null;
32981
- this.#abortUpgrade = null;
32982
33318
  this.#v2AwarenessUnsub = null;
32983
33319
  this.#v2LockUnsub = null;
32984
33320
  this.#v2ConnectionUnsub = null;
32985
33321
  this.#v2ConnectionState = null;
33322
+ this.#v2ConnectionFailureReported = false;
32986
33323
  this.#v2ConnectionHasSynced = false;
32987
33324
  this.#pendingV2LockSeed = null;
32988
33325
  this.#mountWrapper = null;
@@ -33097,6 +33434,7 @@ var SuperDoc = class extends import_eventemitter3.default {
33097
33434
  if (!config.selector) throw new Error("SuperDoc: selector is required");
33098
33435
  const container = typeof config.selector === "string" ? document.querySelector(config.selector) : config.selector;
33099
33436
  if (!(container instanceof HTMLElement)) throw new Error("SuperDoc: selector must be a valid CSS selector string or DOM element");
33437
+ const user = normalizeSuperDocUser(config.user);
33100
33438
  this.#diagnostics = createInteractionHistory(this, config, () => this.version, container);
33101
33439
  this.#surfaceManager = new SurfaceManager({ getModuleConfig: () => this.#surfacesConfig });
33102
33440
  this.#contentControlClickRoot = container;
@@ -33106,7 +33444,10 @@ var SuperDoc = class extends import_eventemitter3.default {
33106
33444
  container.addEventListener("keydown", this.#handleContentControlKeyDown, true);
33107
33445
  container.addEventListener("click", this.#handleContentControlClick, true);
33108
33446
  this.#onConfig("content-control:active-change", config.onContentControlActiveChange);
33109
- this.#init(config, container);
33447
+ this.#init({
33448
+ ...config,
33449
+ user
33450
+ }, container);
33110
33451
  this.#contentControlEventBridgeReady = true;
33111
33452
  if (this.listenerCount("content-control:active-change") > 0) this.ui;
33112
33453
  }
@@ -33159,7 +33500,6 @@ var SuperDoc = class extends import_eventemitter3.default {
33159
33500
  }
33160
33501
  this.editorVersion = 2;
33161
33502
  this.#validateLegacyConfig();
33162
- this.config.user = normalizeSuperDocUser(this.config.user);
33163
33503
  if (!this.config.layoutEngineOptions.virtualization) this.config.layoutEngineOptions.virtualization = {
33164
33504
  enabled: true,
33165
33505
  window: 5,
@@ -33185,7 +33525,7 @@ var SuperDoc = class extends import_eventemitter3.default {
33185
33525
  }
33186
33526
  this.userColorMap = /* @__PURE__ */ new Map();
33187
33527
  this.colorIndex = 0;
33188
- this.version = "2.29.0-next.9";
33528
+ this.version = "2.30.0-next.13";
33189
33529
  this.#log("🦋 [superdoc] Using SuperDoc version:", this.version);
33190
33530
  this.superdocId = config.superdocId || v4();
33191
33531
  this.colors = this.config.colors ?? [];
@@ -33604,6 +33944,7 @@ var SuperDoc = class extends import_eventemitter3.default {
33604
33944
  if (this.#v2ConnectionState?.documentId !== documentId) {
33605
33945
  this.#v2ConnectionState = null;
33606
33946
  this.#v2ConnectionHasSynced = false;
33947
+ this.#v2ConnectionFailureReported = false;
33607
33948
  }
33608
33949
  const publish = (snapshot) => {
33609
33950
  if (this.#destroyed) return;
@@ -33618,8 +33959,21 @@ var SuperDoc = class extends import_eventemitter3.default {
33618
33959
  superdoc: this
33619
33960
  };
33620
33961
  this.#v2ConnectionState = payload;
33621
- if (snapshot.state === "synced") this.#v2ConnectionHasSynced = true;
33622
- this.emit("collaboration-connection-change", payload);
33962
+ if (snapshot.state === "synced") {
33963
+ this.#v2ConnectionHasSynced = true;
33964
+ this.#v2ConnectionFailureReported = false;
33965
+ }
33966
+ const firstFailure = snapshot.state === "failed" && !this.#v2ConnectionFailureReported;
33967
+ if (firstFailure) this.#v2ConnectionFailureReported = true;
33968
+ const reportFailure = firstFailure && snapshot.detail !== "worker-transport-failed";
33969
+ try {
33970
+ this.emit("collaboration-connection-change", payload);
33971
+ } finally {
33972
+ if (reportFailure) {
33973
+ const exception = createCollaborationException("collaboration-connection-failed", documentId);
33974
+ if (exception) this.emit("exception", exception);
33975
+ }
33976
+ }
33623
33977
  };
33624
33978
  try {
33625
33979
  const initialSnapshot = connection.getSnapshot?.();
@@ -33732,41 +34086,43 @@ var SuperDoc = class extends import_eventemitter3.default {
33732
34086
  throw Object.assign(/* @__PURE__ */ new Error("SuperDoc: a different collaboration target is already upgrading"), { code: "collaboration-upgrade-target-conflict" });
33733
34087
  }
33734
34088
  const { target } = this.#validateUpgradePrerequisites(options);
33735
- this.#isUpgrading = true;
33736
- const operation = (async () => {
33737
- try {
33738
- const localSource = await this.#captureCurrentDocxSourceForUpgrade();
33739
- const rollback = this.#snapshotV2UpgradeState();
33740
- const lockSeed = this.#snapshotV2LockSeed();
33741
- this.#assertNotDestroyed();
33742
- try {
33743
- this.#pendingV2LockSeed = lockSeed;
33744
- const promotion = await this.#promoteSingleDocumentToV2Collaboration(target, localSource);
33745
- this.#finalizeSingleDocumentV2Collaboration(promotion, target);
33746
- this.#pendingV2LockSeed = null;
33747
- const attachedDocumentId = this.activeEditor?.options?.documentId;
33748
- const attachedRoomId = promotion.configDoc.v2Collaboration?.documentId;
33749
- if (typeof attachedDocumentId !== "string" || attachedDocumentId !== promotion.configDoc.id || typeof attachedRoomId !== "string" || attachedRoomId !== target.documentId) throw new Error("SuperDoc: collaboration upgrade did not attach the expected room and document");
33750
- return {
33751
- roomId: attachedRoomId,
33752
- documentId: attachedDocumentId
33753
- };
33754
- } catch (err) {
33755
- this.#rollbackV2UpgradeState(rollback);
33756
- throw err;
33757
- }
33758
- } finally {
33759
- this.#abortUpgrade = null;
33760
- this.#isUpgrading = false;
33761
- this.#pendingUpgrade = null;
33762
- }
33763
- })();
34089
+ let rejectOnDestroy = () => {};
34090
+ const destroyed = new Promise((_resolve, reject) => {
34091
+ rejectOnDestroy = reject;
34092
+ });
34093
+ const operation = Promise.race([this.#runUpgrade(target), destroyed]).finally(() => {
34094
+ this.#pendingUpgrade = null;
34095
+ });
33764
34096
  this.#pendingUpgrade = {
33765
34097
  target,
33766
- promise: operation
34098
+ promise: operation,
34099
+ rejectOnDestroy
33767
34100
  };
33768
34101
  return operation;
33769
34102
  }
34103
+ async #runUpgrade(target) {
34104
+ const localSource = await this.#captureCurrentDocxSourceForUpgrade();
34105
+ const rollback = this.#snapshotV2UpgradeState();
34106
+ const lockSeed = this.#snapshotV2LockSeed();
34107
+ this.#assertNotDestroyed();
34108
+ try {
34109
+ this.#pendingV2LockSeed = lockSeed;
34110
+ const promotion = await this.#promoteSingleDocumentToV2Collaboration(target, localSource);
34111
+ this.#assertNotDestroyed();
34112
+ this.#finalizeSingleDocumentV2Collaboration(promotion, target);
34113
+ this.#pendingV2LockSeed = null;
34114
+ const attachedDocumentId = this.activeEditor?.options?.documentId;
34115
+ const attachedRoomId = promotion.configDoc.v2Collaboration?.documentId;
34116
+ if (typeof attachedDocumentId !== "string" || attachedDocumentId !== promotion.configDoc.id || typeof attachedRoomId !== "string" || attachedRoomId !== target.documentId) throw new Error("SuperDoc: collaboration upgrade did not attach the expected room and document");
34117
+ return {
34118
+ roomId: attachedRoomId,
34119
+ documentId: attachedDocumentId
34120
+ };
34121
+ } catch (err) {
34122
+ if (!this.#destroyed) this.#rollbackV2UpgradeState(rollback);
34123
+ throw err;
34124
+ }
34125
+ }
33770
34126
  #sameUpgradeTarget(left, right) {
33771
34127
  if (left.token !== right.token) return false;
33772
34128
  const seen = /* @__PURE__ */ new WeakMap();
@@ -33831,7 +34187,7 @@ var SuperDoc = class extends import_eventemitter3.default {
33831
34187
  * async waits inside upgradeToCollaboration().
33832
34188
  */
33833
34189
  #assertNotDestroyed() {
33834
- if (this.#destroyed) throw new Error("SuperDoc: instance was destroyed during upgrade");
34190
+ if (this.#destroyed) throw destroyedDuringUpgradeError();
33835
34191
  }
33836
34192
  /**
33837
34193
  * Return the superdoc store, throwing a clear lifecycle error if
@@ -33930,6 +34286,7 @@ var SuperDoc = class extends import_eventemitter3.default {
33930
34286
  this.#writeStoreDocV2Collaboration(storeDoc, v2Collaboration);
33931
34287
  this.isCollaborative = true;
33932
34288
  await editor.upgradeToCollaboration(localSource, v2Collaboration);
34289
+ this.#assertNotDestroyed();
33933
34290
  configDoc.data = localSource;
33934
34291
  storeDoc.data = localSource;
33935
34292
  return {
@@ -33989,7 +34346,6 @@ var SuperDoc = class extends import_eventemitter3.default {
33989
34346
  */
33990
34347
  #validateUpgradePrerequisites(options) {
33991
34348
  if (this.#destroyed) throw new Error("SuperDoc: cannot upgrade a destroyed instance");
33992
- if (this.#isUpgrading) throw new Error("SuperDoc: upgrade already in progress");
33993
34349
  if (this.isCollaborative) throw new Error("SuperDoc: instance is already collaborative");
33994
34350
  const cfg = this.config;
33995
34351
  const docxDocs = cfg.documents.filter((d) => d.type === DOCX);
@@ -34044,10 +34400,15 @@ var SuperDoc = class extends import_eventemitter3.default {
34044
34400
  this.#requireReady("setUser");
34045
34401
  const name = typeof user?.name === "string" ? user.name.trim() : "";
34046
34402
  if (!name) return Promise.reject(/* @__PURE__ */ new TypeError("SuperDoc.setUser requires a non-empty user name."));
34047
- const next = normalizeSuperDocUser({
34048
- ...user,
34049
- name
34050
- });
34403
+ let next;
34404
+ try {
34405
+ next = normalizeSuperDocUser({
34406
+ ...user,
34407
+ name
34408
+ });
34409
+ } catch (error) {
34410
+ return Promise.reject(error);
34411
+ }
34051
34412
  const applied = this.#userSwitch.then(() => this.#applyUser(next));
34052
34413
  this.#userSwitch = applied.catch(() => {});
34053
34414
  return applied;
@@ -34061,7 +34422,7 @@ var SuperDoc = class extends import_eventemitter3.default {
34061
34422
  "email",
34062
34423
  "image",
34063
34424
  "initials"
34064
- ].every((key) => (current[key] ?? null) === (next[key] ?? null))) return;
34425
+ ].every((key) => (current[key] ?? null) === (next[key] ?? null)) && current.commentIdentity === void 0 === (next.commentIdentity === void 0) && (current.commentIdentity?.providerId ?? null) === (next.commentIdentity?.providerId ?? null) && (current.commentIdentity?.userId ?? null) === (next.commentIdentity?.userId ?? null)) return;
34065
34426
  const runtimes = this.#editorRuntimeRegistry.getAll();
34066
34427
  const results = await Promise.allSettled(runtimes.map((runtime) => runtime.setUser?.(runtimeUserOf(next))));
34067
34428
  const failure = results.find((result) => result.status === "rejected");
@@ -35396,10 +35757,7 @@ var SuperDoc = class extends import_eventemitter3.default {
35396
35757
  this.#contentControlClickRoot = null;
35397
35758
  this.#contentControlActiveChangeUnsub?.();
35398
35759
  this.#contentControlActiveChangeUnsub = null;
35399
- if (this.#abortUpgrade) {
35400
- this.#abortUpgrade();
35401
- this.#abortUpgrade = null;
35402
- }
35760
+ this.#pendingUpgrade?.rejectOnDestroy(destroyedDuringUpgradeError());
35403
35761
  if (this.#surfaceManager) this.#surfaceManager.destroy();
35404
35762
  this.toolbar?.destroy?.();
35405
35763
  this.commentsList?.close();