@opengeni/react 0.48.0 → 0.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (325) hide show
  1. package/README.md +138 -2
  2. package/dist/artifacts-document.d.ts +3 -0
  3. package/dist/artifacts-document.js +17 -0
  4. package/dist/artifacts-document.js.map +1 -0
  5. package/dist/artifacts-presentation.d.ts +3 -0
  6. package/dist/artifacts-presentation.js +17 -0
  7. package/dist/artifacts-presentation.js.map +1 -0
  8. package/dist/artifacts-spreadsheet.d.ts +3 -0
  9. package/dist/artifacts-spreadsheet.js +17 -0
  10. package/dist/artifacts-spreadsheet.js.map +1 -0
  11. package/dist/artifacts.d.ts +2 -0
  12. package/dist/artifacts.js +199 -0
  13. package/dist/artifacts.js.map +1 -0
  14. package/dist/{chunk-NJ7M2B6Z.js → chunk-5UQPNLIO.js} +16 -8
  15. package/dist/{chunk-NJ7M2B6Z.js.map → chunk-5UQPNLIO.js.map} +1 -1
  16. package/dist/{chunk-LHLYWG5R.js → chunk-6KLX2RH5.js} +461 -38
  17. package/dist/chunk-6KLX2RH5.js.map +1 -0
  18. package/dist/chunk-7WCLAEFV.js +2828 -0
  19. package/dist/chunk-7WCLAEFV.js.map +1 -0
  20. package/dist/{chunk-I4MPFDRP.js → chunk-B6WPZSTT.js} +104 -104
  21. package/dist/{chunk-I4MPFDRP.js.map → chunk-B6WPZSTT.js.map} +1 -1
  22. package/dist/{chunk-EKZH6IDG.js → chunk-C5ZPSSS5.js} +111 -1
  23. package/dist/chunk-C5ZPSSS5.js.map +1 -0
  24. package/dist/{chunk-OL6QVXGF.js → chunk-HA46CYQK.js} +152 -8
  25. package/dist/chunk-HA46CYQK.js.map +1 -0
  26. package/dist/chunk-HUIKIDM5.js +2148 -0
  27. package/dist/chunk-HUIKIDM5.js.map +1 -0
  28. package/dist/{chunk-FWXGSNXT.js → chunk-IGC6A2ZI.js} +585 -34
  29. package/dist/chunk-IGC6A2ZI.js.map +1 -0
  30. package/dist/chunk-JVYQRRC4.js +53 -0
  31. package/dist/chunk-JVYQRRC4.js.map +1 -0
  32. package/dist/chunk-KCGXPP5V.js +157 -0
  33. package/dist/chunk-KCGXPP5V.js.map +1 -0
  34. package/dist/chunk-LLOLUHVG.js +5937 -0
  35. package/dist/chunk-LLOLUHVG.js.map +1 -0
  36. package/dist/{chunk-WYA65Y3F.js → chunk-MKPIAGVL.js} +13 -3
  37. package/dist/chunk-MKPIAGVL.js.map +1 -0
  38. package/dist/chunk-PEI6RANI.js +3234 -0
  39. package/dist/chunk-PEI6RANI.js.map +1 -0
  40. package/dist/chunk-R3AZOAB5.js +933 -0
  41. package/dist/chunk-R3AZOAB5.js.map +1 -0
  42. package/dist/{chunk-O4VXOBGF.js → chunk-WSK7G5LE.js} +70 -2
  43. package/dist/chunk-WSK7G5LE.js.map +1 -0
  44. package/dist/{chunk-23EJ676W.js → chunk-XBZWOM2K.js} +9 -4
  45. package/dist/chunk-XBZWOM2K.js.map +1 -0
  46. package/dist/{chunk-XCNTXWBB.js → chunk-Y7OEDUJ5.js} +856 -338
  47. package/dist/chunk-Y7OEDUJ5.js.map +1 -0
  48. package/dist/client.d.ts +10 -2
  49. package/dist/clipboard.d.ts +1 -0
  50. package/dist/clipboard.js +7 -0
  51. package/dist/clipboard.js.map +1 -0
  52. package/dist/commands/index.d.ts +3 -3
  53. package/dist/commands/registry.d.ts +1 -1
  54. package/dist/commands/types.d.ts +1 -1
  55. package/dist/components/approval-surface.d.ts +2 -2
  56. package/dist/components/artifacts/artifact-surface.d.ts +18 -0
  57. package/dist/components/artifacts/document-editor.d.ts +172 -0
  58. package/dist/components/artifacts/editable-artifact-ui.d.ts +13 -0
  59. package/dist/components/artifacts/editable-artifact-workbench.d.ts +49 -0
  60. package/dist/components/artifacts/editable-document.d.ts +24 -0
  61. package/dist/components/artifacts/editable-presentation.d.ts +24 -0
  62. package/dist/components/artifacts/editable-spreadsheet.d.ts +32 -0
  63. package/dist/components/artifacts/index.d.ts +9 -0
  64. package/dist/components/artifacts/presentation-editor.d.ts +297 -0
  65. package/dist/components/artifacts/published-html-artifact-frame.d.ts +14 -0
  66. package/dist/components/artifacts/spreadsheet-canvas.d.ts +120 -0
  67. package/dist/components/artifacts/spreadsheet-grid.d.ts +113 -0
  68. package/dist/components/browser-input.d.ts +7 -0
  69. package/dist/components/browser-viewer.d.ts +30 -0
  70. package/dist/components/chat-composer.d.ts +8 -8
  71. package/dist/components/code-editor.d.ts +25 -3
  72. package/dist/components/command-palette.d.ts +2 -2
  73. package/dist/components/composer-transcription-control.d.ts +2 -2
  74. package/dist/components/composer.d.ts +17 -17
  75. package/dist/components/computer-viewer.d.ts +24 -0
  76. package/dist/components/copy-button.d.ts +2 -2
  77. package/dist/components/desktop-viewer.d.ts +50 -22
  78. package/dist/components/diff-view.d.ts +1 -1
  79. package/dist/components/enrollment-consent.d.ts +1 -1
  80. package/dist/components/enrollment-device-flow.d.ts +1 -1
  81. package/dist/components/file-browser.d.ts +2 -2
  82. package/dist/components/fleet-tile.d.ts +1 -1
  83. package/dist/components/generated-video-player.d.ts +10 -0
  84. package/dist/components/human-input-form.d.ts +1 -1
  85. package/dist/components/human-input-surface.d.ts +2 -2
  86. package/dist/components/interaction-intervention-banner.d.ts +11 -0
  87. package/dist/components/interactive-workbench-viewers.d.ts +2 -0
  88. package/dist/components/machine-card.d.ts +2 -2
  89. package/dist/components/machine-dock-bar.d.ts +3 -3
  90. package/dist/components/machine-health-pill.d.ts +2 -2
  91. package/dist/components/machine-input-display.d.ts +1 -1
  92. package/dist/components/machine-metrics.d.ts +2 -2
  93. package/dist/components/machine-status-pill.d.ts +4 -4
  94. package/dist/components/machines/health.d.ts +1 -1
  95. package/dist/components/machines/machine-detail.d.ts +3 -3
  96. package/dist/components/machines/metric-history-chart.d.ts +1 -1
  97. package/dist/components/machines/metric-sparkline.d.ts +2 -2
  98. package/dist/components/machines/series.d.ts +2 -2
  99. package/dist/components/machines-dashboard.d.ts +2 -2
  100. package/dist/components/markdown.d.ts +1 -1
  101. package/dist/components/message-timeline.d.ts +12 -5
  102. package/dist/components/model-picker.d.ts +2 -2
  103. package/dist/components/model-policy-picker.d.ts +7 -7
  104. package/dist/components/pierre-diff.d.ts +1 -1
  105. package/dist/components/pierre-file.d.ts +1 -1
  106. package/dist/components/queue-draft-policy.d.ts +1 -1
  107. package/dist/components/queue-surface-implementation.d.ts +3 -3
  108. package/dist/components/queue-surface-state.d.ts +4 -4
  109. package/dist/components/queue-surface.d.ts +5 -5
  110. package/dist/components/sandbox-files.d.ts +3 -3
  111. package/dist/components/sandbox-terminal.d.ts +24 -4
  112. package/dist/components/sandbox-workspace.d.ts +28 -9
  113. package/dist/components/session-chrome.d.ts +4 -4
  114. package/dist/components/session-status.d.ts +2 -2
  115. package/dist/components/timeline-annotation-selection.d.ts +9 -0
  116. package/dist/components/timeline-annotations-dialog.d.ts +16 -0
  117. package/dist/components/timeline-annotations.d.ts +13 -0
  118. package/dist/components/tooltip.d.ts +4 -4
  119. package/dist/components/user-message-body.d.ts +2 -2
  120. package/dist/components/workbench-changes.d.ts +2 -2
  121. package/dist/components/workspace-dock.d.ts +1 -1
  122. package/dist/composer.d.ts +11 -11
  123. package/dist/composer.js +7 -7
  124. package/dist/hooks/internal.d.ts +1 -1
  125. package/dist/hooks/use-attached-browsers.d.ts +21 -0
  126. package/dist/hooks/use-auth-runs.d.ts +32 -0
  127. package/dist/hooks/use-available-models.d.ts +2 -2
  128. package/dist/hooks/use-billing-usage.d.ts +1 -1
  129. package/dist/hooks/use-browser-downloads.d.ts +25 -0
  130. package/dist/hooks/use-browser-frame-stream.d.ts +28 -0
  131. package/dist/hooks/use-browser-identities.d.ts +30 -0
  132. package/dist/hooks/use-browser-session.d.ts +29 -0
  133. package/dist/hooks/use-browser-sessions.d.ts +32 -0
  134. package/dist/hooks/use-channels.d.ts +26 -0
  135. package/dist/hooks/use-codex-accounts.d.ts +2 -2
  136. package/dist/hooks/use-composer.d.ts +36 -9
  137. package/dist/hooks/use-computer-frame-stream.d.ts +24 -0
  138. package/dist/hooks/use-computer-session.d.ts +29 -0
  139. package/dist/hooks/use-computer-sessions.d.ts +28 -0
  140. package/dist/hooks/use-desktop-stream.d.ts +5 -1
  141. package/dist/hooks/use-environments.d.ts +1 -1
  142. package/dist/hooks/use-file-attachments.d.ts +1 -1
  143. package/dist/hooks/use-goal.d.ts +2 -2
  144. package/dist/hooks/use-human-input.d.ts +2 -2
  145. package/dist/hooks/use-interaction-interventions.d.ts +27 -0
  146. package/dist/hooks/use-interaction-invalidation.d.ts +19 -0
  147. package/dist/hooks/use-last-started-turn-policy.d.ts +2 -2
  148. package/dist/hooks/use-machine-chip.d.ts +2 -2
  149. package/dist/hooks/use-machines.d.ts +4 -4
  150. package/dist/hooks/use-network-routes.d.ts +25 -0
  151. package/dist/hooks/use-packs.d.ts +7 -2
  152. package/dist/hooks/use-rigs.d.ts +1 -1
  153. package/dist/hooks/use-sandbox-files.d.ts +6 -4
  154. package/dist/hooks/use-sandbox-git.d.ts +6 -5
  155. package/dist/hooks/use-sandbox-terminal.d.ts +1 -1
  156. package/dist/hooks/use-scheduled-tasks.d.ts +1 -1
  157. package/dist/hooks/use-session-capabilities.d.ts +5 -4
  158. package/dist/hooks/use-session-control.d.ts +1 -1
  159. package/dist/hooks/use-session-events.d.ts +2 -2
  160. package/dist/hooks/use-session-lineage.d.ts +1 -1
  161. package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
  162. package/dist/hooks/use-session.d.ts +2 -2
  163. package/dist/hooks/use-site-auth-connections.d.ts +25 -0
  164. package/dist/hooks/use-slash-commands.d.ts +1 -1
  165. package/dist/hooks/use-supergrok-accounts.d.ts +26 -0
  166. package/dist/hooks/use-terminal-stream.d.ts +11 -2
  167. package/dist/hooks/use-turn-queue.d.ts +2 -2
  168. package/dist/hooks/use-video-artifact-playback.d.ts +9 -0
  169. package/dist/hooks/use-voice-input.d.ts +1 -1
  170. package/dist/hooks/use-workspace-capture.d.ts +1 -1
  171. package/dist/hooks/use-workspace-edit.d.ts +1 -1
  172. package/dist/hooks/use-workspace-sessions.d.ts +1 -1
  173. package/dist/hooks/use-workspaces.d.ts +1 -1
  174. package/dist/index.d.ts +162 -155
  175. package/dist/index.js +1822 -1703
  176. package/dist/index.js.map +1 -1
  177. package/dist/interaction.d.ts +33 -0
  178. package/dist/interaction.js +550 -0
  179. package/dist/interaction.js.map +1 -0
  180. package/dist/interactive-workbench-viewers-TOHBXJEE.js +15 -0
  181. package/dist/interactive-workbench-viewers-TOHBXJEE.js.map +1 -0
  182. package/dist/lib/host-platform.d.ts +5 -0
  183. package/dist/lib/relay-wire.d.ts +24 -1
  184. package/dist/lib/sandbox-liveness.d.ts +9 -0
  185. package/dist/lib/terminal-capability.d.ts +5 -0
  186. package/dist/lib/xterm-theme.d.ts +1 -1
  187. package/dist/machines.d.ts +30 -30
  188. package/dist/machines.js +2 -2
  189. package/dist/model-policy.d.ts +1 -1
  190. package/dist/model-policy.js +1 -1
  191. package/dist/provider.d.ts +6 -5
  192. package/dist/{queue-surface-implementation-U5JFG7HT.js → queue-surface-implementation-2KVJTYXH.js} +100 -48
  193. package/dist/queue-surface-implementation-2KVJTYXH.js.map +1 -0
  194. package/dist/realtime/dropdown-menu.d.ts +8 -8
  195. package/dist/realtime/realtime-control.d.ts +8 -8
  196. package/dist/realtime.d.ts +4 -4
  197. package/dist/realtime.js +38 -11
  198. package/dist/realtime.js.map +1 -1
  199. package/dist/session-context.d.ts +28 -2
  200. package/dist/session-ui.d.ts +14 -14
  201. package/dist/session-ui.js +5 -4
  202. package/dist/session.d.ts +28 -28
  203. package/dist/session.js +10 -10
  204. package/dist/timeline/activity-rail.d.ts +5 -3
  205. package/dist/timeline/disclosure-context.d.ts +1 -1
  206. package/dist/timeline/fleet-decision-projection.d.ts +1 -1
  207. package/dist/timeline/fleet-decision-row.d.ts +3 -3
  208. package/dist/timeline/index.d.ts +19 -19
  209. package/dist/timeline/parsers.d.ts +4 -1
  210. package/dist/timeline/projection.d.ts +3 -3
  211. package/dist/timeline/registry.d.ts +8 -2
  212. package/dist/timeline/screenshot-lightbox.d.ts +2 -2
  213. package/dist/timeline/shared.d.ts +21 -10
  214. package/dist/timeline/tool-diff.d.ts +2 -2
  215. package/dist/timeline/tool-renderers.d.ts +1 -1
  216. package/dist/timeline/turn-summary.d.ts +3 -3
  217. package/dist/timeline/types.d.ts +44 -3
  218. package/dist/timeline-annotation-selection-3L7LHGG2.js +187 -0
  219. package/dist/timeline-annotation-selection-3L7LHGG2.js.map +1 -0
  220. package/dist/timeline-annotations-dialog-U7EVLR75.js +196 -0
  221. package/dist/timeline-annotations-dialog-U7EVLR75.js.map +1 -0
  222. package/package.json +36 -7
  223. package/src/artifacts-document.ts +31 -0
  224. package/src/artifacts-presentation.ts +36 -0
  225. package/src/artifacts-spreadsheet.ts +21 -0
  226. package/src/artifacts.ts +89 -0
  227. package/src/client.ts +157 -1
  228. package/src/clipboard.ts +2 -0
  229. package/src/components/artifacts/artifact-surface.tsx +93 -0
  230. package/src/components/artifacts/document-editor.tsx +2255 -0
  231. package/src/components/artifacts/editable-artifact-ui.tsx +89 -0
  232. package/src/components/artifacts/editable-artifact-workbench.tsx +203 -0
  233. package/src/components/artifacts/editable-document.tsx +657 -0
  234. package/src/components/artifacts/editable-presentation.tsx +1020 -0
  235. package/src/components/artifacts/editable-spreadsheet.tsx +733 -0
  236. package/src/components/artifacts/index.ts +104 -0
  237. package/src/components/artifacts/presentation-editor.tsx +3148 -0
  238. package/src/components/artifacts/published-html-artifact-frame.tsx +38 -0
  239. package/src/components/artifacts/spreadsheet-canvas.ts +799 -0
  240. package/src/components/artifacts/spreadsheet-grid.tsx +2129 -0
  241. package/src/components/browser-input.ts +65 -0
  242. package/src/components/browser-viewer.tsx +2702 -0
  243. package/src/components/chat-composer.tsx +13 -0
  244. package/src/components/code-editor.tsx +89 -30
  245. package/src/components/composer-transcription-control.tsx +10 -6
  246. package/src/components/computer-viewer.tsx +1636 -0
  247. package/src/components/desktop-viewer.tsx +215 -74
  248. package/src/components/generated-video-player.tsx +89 -0
  249. package/src/components/interaction-intervention-banner.tsx +99 -0
  250. package/src/components/interactive-workbench-viewers.ts +2 -0
  251. package/src/components/machine-input-display.ts +3 -0
  252. package/src/components/machines/machine-detail.tsx +1 -1
  253. package/src/components/message-timeline.tsx +289 -32
  254. package/src/components/model-policy-picker.tsx +15 -0
  255. package/src/components/queue-surface-implementation.tsx +116 -60
  256. package/src/components/sandbox-files.tsx +2 -2
  257. package/src/components/sandbox-terminal.tsx +128 -46
  258. package/src/components/sandbox-workspace.tsx +256 -90
  259. package/src/components/session-chrome.tsx +53 -13
  260. package/src/components/timeline-annotation-selection.tsx +221 -0
  261. package/src/components/timeline-annotations-dialog.tsx +213 -0
  262. package/src/components/timeline-annotations.tsx +84 -0
  263. package/src/components/workbench-changes.tsx +188 -31
  264. package/src/components/workspace-dock.tsx +59 -5
  265. package/src/hooks/use-attached-browsers.ts +154 -0
  266. package/src/hooks/use-auth-runs.ts +278 -0
  267. package/src/hooks/use-browser-downloads.ts +218 -0
  268. package/src/hooks/use-browser-frame-stream.ts +425 -0
  269. package/src/hooks/use-browser-identities.ts +266 -0
  270. package/src/hooks/use-browser-session.ts +543 -0
  271. package/src/hooks/use-browser-sessions.ts +286 -0
  272. package/src/hooks/use-channels.ts +99 -0
  273. package/src/hooks/use-composer.ts +769 -24
  274. package/src/hooks/use-computer-frame-stream.ts +434 -0
  275. package/src/hooks/use-computer-session.ts +462 -0
  276. package/src/hooks/use-computer-sessions.ts +251 -0
  277. package/src/hooks/use-desktop-stream.ts +39 -7
  278. package/src/hooks/use-interaction-interventions.ts +242 -0
  279. package/src/hooks/use-interaction-invalidation.ts +66 -0
  280. package/src/hooks/use-machine-chip.ts +15 -3
  281. package/src/hooks/use-machines.ts +33 -3
  282. package/src/hooks/use-network-routes.ts +214 -0
  283. package/src/hooks/use-packs.ts +51 -0
  284. package/src/hooks/use-sandbox-files.ts +98 -55
  285. package/src/hooks/use-sandbox-git.ts +93 -38
  286. package/src/hooks/use-sandbox-terminal.ts +3 -2
  287. package/src/hooks/use-session-capabilities.ts +59 -46
  288. package/src/hooks/use-site-auth-connections.ts +226 -0
  289. package/src/hooks/use-supergrok-accounts.ts +81 -0
  290. package/src/hooks/use-terminal-stream.ts +359 -47
  291. package/src/hooks/use-video-artifact-playback.ts +28 -0
  292. package/src/hooks/use-workspace-edit.ts +2 -1
  293. package/src/index.ts +18 -0
  294. package/src/interaction.ts +101 -0
  295. package/src/lib/host-platform.ts +10 -0
  296. package/src/lib/relay-wire.ts +47 -4
  297. package/src/lib/sandbox-liveness.ts +11 -0
  298. package/src/lib/terminal-capability.ts +10 -0
  299. package/src/model-policy.ts +19 -4
  300. package/src/provider.tsx +133 -1
  301. package/src/realtime/realtime-control.tsx +34 -6
  302. package/src/session-context.ts +156 -1
  303. package/src/timeline/activity-rail.tsx +25 -3
  304. package/src/timeline/index.ts +3 -0
  305. package/src/timeline/parsers.ts +14 -1
  306. package/src/timeline/projection.ts +269 -14
  307. package/src/timeline/registry.ts +13 -1
  308. package/src/timeline/screenshot-lightbox.tsx +19 -9
  309. package/src/timeline/shared.tsx +56 -8
  310. package/src/timeline/tool-renderers.tsx +303 -50
  311. package/src/timeline/types.ts +57 -3
  312. package/styles/compiled.css +312 -21
  313. package/styles/index.css +15 -0
  314. package/styles/responsive.css +13 -1
  315. package/dist/chunk-23EJ676W.js.map +0 -1
  316. package/dist/chunk-EKZH6IDG.js.map +0 -1
  317. package/dist/chunk-FWXGSNXT.js.map +0 -1
  318. package/dist/chunk-LHLYWG5R.js.map +0 -1
  319. package/dist/chunk-O4VXOBGF.js.map +0 -1
  320. package/dist/chunk-OL6QVXGF.js.map +0 -1
  321. package/dist/chunk-WYA65Y3F.js.map +0 -1
  322. package/dist/chunk-XCNTXWBB.js.map +0 -1
  323. package/dist/fleet-decision-projection-SLNZMS42.js +0 -241
  324. package/dist/fleet-decision-projection-SLNZMS42.js.map +0 -1
  325. package/dist/queue-surface-implementation-U5JFG7HT.js.map +0 -1
@@ -0,0 +1,2148 @@
1
+ import {
2
+ ArtifactSurface,
3
+ EditableArtifactMessage,
4
+ asEditableArtifactError,
5
+ editableArtifactAccessRevoked,
6
+ editableArtifactProjectionKey,
7
+ editableArtifactStatusLabel,
8
+ useEditableArtifactView
9
+ } from "./chunk-KCGXPP5V.js";
10
+ import {
11
+ cn
12
+ } from "./chunk-22KP6LR5.js";
13
+
14
+ // src/components/artifacts/document-editor.tsx
15
+ import {
16
+ BoldIcon,
17
+ Columns2Icon,
18
+ ItalicIcon,
19
+ MessageSquareTextIcon,
20
+ PilcrowIcon,
21
+ Rows3Icon,
22
+ UnderlineIcon
23
+ } from "lucide-react";
24
+ import {
25
+ memo,
26
+ useCallback,
27
+ useEffect,
28
+ useLayoutEffect,
29
+ useMemo,
30
+ useRef,
31
+ useState
32
+ } from "react";
33
+ import { jsx, jsxs } from "react/jsx-runtime";
34
+ var POINT_TO_CSS_PIXEL = 4 / 3;
35
+ var DEFAULT_VIEWPORT_HEIGHT = 640;
36
+ var RESPONSIVE_CONTINUOUS_LAYOUT_WIDTH = 640;
37
+ var PAGINATED_GAP = 24;
38
+ var CONTINUOUS_GAP = 1;
39
+ var MAX_PLAIN_TEXT_TRANSFER = 1e6;
40
+ var MAX_PROJECTION_BLOCKS = 1e5;
41
+ var MAX_PROJECTION_TABLE_CELLS = 1e6;
42
+ var MAX_PROJECTION_ANNOTATIONS = 1e5;
43
+ var MAX_PROJECTION_TEXT = 1e6;
44
+ var EMPTY_COMMENTS = [];
45
+ var EMPTY_CHANGES = [];
46
+ function DocumentEditor(props) {
47
+ return /* @__PURE__ */ jsx(DocumentEditorCore, { ...props, document: asDocumentView(props.document) });
48
+ }
49
+ function DocumentProjectionEditor({
50
+ projection,
51
+ commit,
52
+ onCommandError,
53
+ onSelectionChange,
54
+ readOnly = false,
55
+ ...props
56
+ }) {
57
+ const view = useMemo(() => documentViewFromProjection(projection), [projection]);
58
+ return /* @__PURE__ */ jsx(
59
+ DocumentEditorCore,
60
+ {
61
+ ...props,
62
+ document: view.document,
63
+ revisionToken: projection.revision,
64
+ readOnly: readOnly || !commit,
65
+ onCommandError,
66
+ blockIdForView: (blockId) => view.externalIdByInternal.get(blockId) ?? blockId,
67
+ onSelectionChange: (selection) => onSelectionChange?.({
68
+ ...selection,
69
+ blockId: view.externalIdByInternal.get(selection.blockId) ?? selection.blockId
70
+ }),
71
+ onCommit: (command) => commit?.(mapDocumentCommit(command, view.externalIdByInternal))
72
+ }
73
+ );
74
+ }
75
+ function DocumentEditorCore({
76
+ document: document2,
77
+ revisionToken,
78
+ layout: controlledLayout,
79
+ defaultLayout,
80
+ onLayoutChange,
81
+ onSelectionChange,
82
+ onCommit,
83
+ onCommandError,
84
+ blockIdForView = identity,
85
+ readOnly = false,
86
+ ariaLabel = "Document editor",
87
+ viewportHeight = DEFAULT_VIEWPORT_HEIGHT,
88
+ overscanPages = 1,
89
+ className
90
+ }) {
91
+ const [uncontrolledLayout, setUncontrolledLayout] = useState(
92
+ defaultLayout ?? "paginated"
93
+ );
94
+ const [scrollTop, setScrollTop] = useState(0);
95
+ const [measuredViewport, setMeasuredViewport] = useState({ width: 0, height: 0 });
96
+ const [localRevision, setLocalRevision] = useState(document2.revision);
97
+ const [measuredPageHeights, setMeasuredPageHeights] = useState(
98
+ () => /* @__PURE__ */ new Map()
99
+ );
100
+ const [selectedBlockId, setSelectedBlockId] = useState(null);
101
+ const [hasTextSelection, setHasTextSelection] = useState(false);
102
+ const selectionRef = useRef(null);
103
+ const [openThreadId, setOpenThreadId] = useState(null);
104
+ const [pendingCommands, setPendingCommands] = useState(0);
105
+ const [commandFailure, setCommandFailure] = useState(null);
106
+ const editorNodes = useRef(/* @__PURE__ */ new Map());
107
+ const viewportRef = useRef(null);
108
+ const pendingScrollTop = useRef(0);
109
+ const scrollFrame = useRef(null);
110
+ const pendingMeasurementAnchor = useRef(null);
111
+ const responsiveLayoutInitialized = useRef(false);
112
+ const measuredPageHeightsRef = useRef(/* @__PURE__ */ new Map());
113
+ const mountedRef = useRef(true);
114
+ const commandScopeRef = useRef(0);
115
+ const commandVersionsRef = useRef(/* @__PURE__ */ new Map());
116
+ const layout = controlledLayout ?? uncontrolledLayout;
117
+ const documentRevision = revisionToken ?? document2.revision;
118
+ const pageGap = layout === "paginated" ? PAGINATED_GAP : CONTINUOUS_GAP;
119
+ const basePages = useMemo(
120
+ () => projectPages(document2, layout),
121
+ // The revision values intentionally invalidate projections over the mutable public model.
122
+ // oxlint-disable-next-line react-hooks/exhaustive-deps
123
+ [document2, documentRevision, layout, localRevision]
124
+ );
125
+ const naturalPages = useMemo(
126
+ () => basePages.map((page) => ({
127
+ ...page,
128
+ // Estimates seed unmounted pages only. Once mounted, intrinsic content
129
+ // may correct the page in either direction while preserving paper size.
130
+ height: measuredPageHeights.has(page.key) ? Math.max(page.minimumHeight, measuredPageHeights.get(page.key) ?? page.height) : page.height
131
+ })),
132
+ [basePages, measuredPageHeights]
133
+ );
134
+ const pages = useMemo(
135
+ () => naturalPages.map((page) => {
136
+ const renderScale = layout === "paginated" ? responsiveDocumentPageScale(page.page, measuredViewport.width) : 1;
137
+ return {
138
+ ...page,
139
+ naturalHeight: page.height,
140
+ renderScale,
141
+ height: page.height * renderScale,
142
+ minimumHeight: page.minimumHeight * renderScale
143
+ };
144
+ }),
145
+ [layout, measuredViewport.width, naturalPages]
146
+ );
147
+ const annotations = useMemo(
148
+ () => indexAnnotations(document2),
149
+ // The revision values intentionally invalidate indexes over the mutable public model.
150
+ // oxlint-disable-next-line react-hooks/exhaustive-deps
151
+ [document2, documentRevision, localRevision]
152
+ );
153
+ const axis = useMemo(() => buildPageAxis(pages, pageGap), [pages, pageGap]);
154
+ const pagesRef = useRef(pages);
155
+ const axisRef = useRef(axis);
156
+ pagesRef.current = pages;
157
+ axisRef.current = axis;
158
+ const blockPageIndex = useMemo(() => {
159
+ const index = /* @__PURE__ */ new Map();
160
+ pages.forEach((page, pageIndex) => {
161
+ for (const { block } of page.blocks) index.set(block.id, pageIndex);
162
+ });
163
+ return index;
164
+ }, [pages]);
165
+ const activePage = selectedBlockId ? blockPageIndex.get(selectedBlockId) ?? -1 : -1;
166
+ const mountedIndexes = useMemo(
167
+ () => visiblePageIndexes(
168
+ axis,
169
+ pages.length,
170
+ scrollTop,
171
+ Math.max(1, measuredViewport.height || viewportHeight),
172
+ Math.max(0, Math.floor(overscanPages)),
173
+ activePage
174
+ ),
175
+ [
176
+ activePage,
177
+ axis,
178
+ measuredViewport.height,
179
+ overscanPages,
180
+ pages.length,
181
+ scrollTop,
182
+ viewportHeight
183
+ ]
184
+ );
185
+ useLayoutEffect(() => {
186
+ const element = viewportRef.current;
187
+ if (!element) return;
188
+ const measure = () => {
189
+ const width = element.clientWidth;
190
+ const height = element.clientHeight;
191
+ if (width <= 0 && height <= 0) return;
192
+ setMeasuredViewport(
193
+ (current) => current.width === width && current.height === height ? current : { width, height }
194
+ );
195
+ };
196
+ measure();
197
+ const observer = globalThis.ResizeObserver ? new globalThis.ResizeObserver(measure) : null;
198
+ observer?.observe(element);
199
+ globalThis.addEventListener?.("resize", measure);
200
+ return () => {
201
+ observer?.disconnect();
202
+ globalThis.removeEventListener?.("resize", measure);
203
+ };
204
+ }, []);
205
+ useLayoutEffect(() => {
206
+ if (responsiveLayoutInitialized.current || controlledLayout !== void 0 || defaultLayout !== void 0 || measuredViewport.width <= 0) {
207
+ return;
208
+ }
209
+ responsiveLayoutInitialized.current = true;
210
+ if (measuredViewport.width < RESPONSIVE_CONTINUOUS_LAYOUT_WIDTH) {
211
+ setUncontrolledLayout("continuous");
212
+ onLayoutChange?.("continuous");
213
+ }
214
+ }, [controlledLayout, defaultLayout, measuredViewport.width, onLayoutChange]);
215
+ useEffect(() => {
216
+ commandScopeRef.current += 1;
217
+ commandVersionsRef.current.clear();
218
+ selectionRef.current = null;
219
+ setSelectedBlockId(null);
220
+ setHasTextSelection(false);
221
+ setOpenThreadId(null);
222
+ setLocalRevision(document2.revision);
223
+ setPendingCommands(0);
224
+ setCommandFailure(null);
225
+ }, [document2, revisionToken]);
226
+ useEffect(() => {
227
+ mountedRef.current = true;
228
+ return () => {
229
+ mountedRef.current = false;
230
+ if (scrollFrame.current !== null && globalThis.cancelAnimationFrame) {
231
+ globalThis.cancelAnimationFrame(scrollFrame.current);
232
+ }
233
+ };
234
+ }, []);
235
+ const handleScroll = useCallback((event) => {
236
+ pendingScrollTop.current = event.currentTarget.scrollTop;
237
+ if (scrollFrame.current !== null) return;
238
+ if (!globalThis.requestAnimationFrame) {
239
+ scrollFrame.current = -1;
240
+ queueMicrotask(() => {
241
+ scrollFrame.current = null;
242
+ setScrollTop(pendingScrollTop.current);
243
+ });
244
+ return;
245
+ }
246
+ scrollFrame.current = globalThis.requestAnimationFrame(() => {
247
+ scrollFrame.current = null;
248
+ setScrollTop(pendingScrollTop.current);
249
+ });
250
+ }, []);
251
+ const clearSelection = useCallback(() => {
252
+ selectionRef.current = null;
253
+ setSelectedBlockId(null);
254
+ setHasTextSelection(false);
255
+ }, []);
256
+ const recordPageHeight = useCallback((key, height) => {
257
+ if (!Number.isFinite(height) || height <= 0) return;
258
+ const rounded = Math.ceil(height);
259
+ const current = measuredPageHeightsRef.current;
260
+ if (Math.abs((current.get(key) ?? 0) - rounded) <= 1) return;
261
+ const currentPages = pagesRef.current;
262
+ if (!currentPages.some((page) => page.key === key)) return;
263
+ const viewport = viewportRef.current;
264
+ if (viewport && pendingMeasurementAnchor.current === null) {
265
+ const currentAxis = axisRef.current;
266
+ const anchorIndex = pageAtOffset(currentAxis, currentPages.length, viewport.scrollTop);
267
+ const anchorPage = currentPages[anchorIndex];
268
+ if (anchorPage) {
269
+ pendingMeasurementAnchor.current = {
270
+ axis: currentAxis,
271
+ key: anchorPage.key,
272
+ offset: viewport.scrollTop - currentAxis.offsets[anchorIndex],
273
+ scrollTop: viewport.scrollTop
274
+ };
275
+ }
276
+ }
277
+ const next = /* @__PURE__ */ new Map();
278
+ for (const page of currentPages) {
279
+ const measured = current.get(page.key);
280
+ if (measured !== void 0) next.set(page.key, measured);
281
+ }
282
+ next.set(key, rounded);
283
+ measuredPageHeightsRef.current = next;
284
+ setMeasuredPageHeights(next);
285
+ }, []);
286
+ useLayoutEffect(() => {
287
+ const anchor = pendingMeasurementAnchor.current;
288
+ if (!anchor || anchor.axis === axis) return;
289
+ pendingMeasurementAnchor.current = null;
290
+ const viewport = viewportRef.current;
291
+ if (!viewport || Math.abs(viewport.scrollTop - anchor.scrollTop) > 1) return;
292
+ const anchorIndex = pages.findIndex((page) => page.key === anchor.key);
293
+ if (anchorIndex < 0) return;
294
+ const nextScrollTop = Math.max(0, axis.offsets[anchorIndex] + anchor.offset);
295
+ if (Math.abs(viewport.scrollTop - nextScrollTop) > 0.5) {
296
+ viewport.scrollTop = nextScrollTop;
297
+ }
298
+ const correctedScrollTop = viewport.scrollTop;
299
+ pendingScrollTop.current = correctedScrollTop;
300
+ setScrollTop(
301
+ (current) => Math.abs(current - correctedScrollTop) > 0.5 ? correctedScrollTop : current
302
+ );
303
+ }, [axis, pages]);
304
+ const publishSelection = useCallback(
305
+ (next) => {
306
+ selectionRef.current = next;
307
+ setSelectedBlockId((current) => current === next.blockId ? current : next.blockId);
308
+ const nextHasTextSelection = next.end > next.start;
309
+ setHasTextSelection(
310
+ (current) => current === nextHasTextSelection ? current : nextHasTextSelection
311
+ );
312
+ onSelectionChange?.(next);
313
+ },
314
+ [onSelectionChange]
315
+ );
316
+ const readDomSelection = useCallback(
317
+ (blockId) => {
318
+ const node = editorNodes.current.get(blockId);
319
+ const offsets = node ? selectionOffsets(node) : null;
320
+ const next = {
321
+ blockId,
322
+ start: offsets?.start ?? 0,
323
+ end: offsets?.end ?? 0
324
+ };
325
+ publishSelection(next);
326
+ return next;
327
+ },
328
+ [publishSelection]
329
+ );
330
+ const runCommit = useCallback(
331
+ (command) => {
332
+ if (!onCommit) return;
333
+ const scope = commandScopeRef.current;
334
+ const submit = () => {
335
+ const key = command.blockId;
336
+ const version = (commandVersionsRef.current.get(key) ?? 0) + 1;
337
+ commandVersionsRef.current.set(key, version);
338
+ setCommandFailure(null);
339
+ const fail = (cause) => {
340
+ if (!mountedRef.current || commandScopeRef.current !== scope || commandVersionsRef.current.get(key) !== version) {
341
+ return;
342
+ }
343
+ const error = asDocumentError(cause);
344
+ setCommandFailure({ message: error.message, retry: submit });
345
+ onCommandError?.(error);
346
+ };
347
+ let result;
348
+ try {
349
+ result = onCommit(command);
350
+ } catch (cause) {
351
+ fail(cause);
352
+ return;
353
+ }
354
+ if (!result || typeof result.then !== "function") return;
355
+ setPendingCommands((current) => current + 1);
356
+ void Promise.resolve(result).then(
357
+ () => {
358
+ if (!mountedRef.current || commandScopeRef.current !== scope) return;
359
+ setPendingCommands((current) => Math.max(0, current - 1));
360
+ },
361
+ (cause) => {
362
+ if (!mountedRef.current || commandScopeRef.current !== scope) return;
363
+ setPendingCommands((current) => Math.max(0, current - 1));
364
+ fail(cause);
365
+ }
366
+ );
367
+ };
368
+ submit();
369
+ },
370
+ [onCommandError, onCommit]
371
+ );
372
+ const commitText = useCallback(
373
+ (paragraph, text) => {
374
+ if (readOnly || paragraph.text === text) return;
375
+ paragraph.text = text;
376
+ runCommit({
377
+ kind: "text",
378
+ blockId: paragraph.id,
379
+ text,
380
+ revision: revisionToken ?? document2.revision
381
+ });
382
+ },
383
+ [document2, readOnly, revisionToken, runCommit]
384
+ );
385
+ const applyFormat = useCallback(
386
+ (property) => {
387
+ const selection = selectionRef.current;
388
+ if (readOnly || !selection) return;
389
+ let block;
390
+ try {
391
+ block = document2.resolve(selection.blockId);
392
+ } catch {
393
+ clearSelection();
394
+ return;
395
+ }
396
+ if (!isDocumentParagraph(block)) return;
397
+ const node = editorNodes.current.get(block.id);
398
+ if (node) commitText(block, node.textContent ?? "");
399
+ const current = selectionOffsets(node) ?? selection;
400
+ const start = Math.max(0, Math.min(current.start, block.text.length));
401
+ const end = Math.max(start, Math.min(current.end, block.text.length));
402
+ if (end <= start) return;
403
+ const effectiveEnd = end;
404
+ const effectiveStart = start;
405
+ const style = toggledTextStyle(block, effectiveStart, effectiveEnd, property);
406
+ block.format({ start: effectiveStart, end: effectiveEnd, style });
407
+ runCommit({
408
+ kind: "format",
409
+ blockId: block.id,
410
+ range: { start: effectiveStart, end: effectiveEnd },
411
+ style,
412
+ revision: revisionToken ?? document2.revision
413
+ });
414
+ queueMicrotask(() => restoreSelection(editorNodes.current.get(block.id), start, end));
415
+ },
416
+ [clearSelection, commitText, document2, readOnly, revisionToken, runCommit]
417
+ );
418
+ const handleEditorKeyDown = useCallback(
419
+ (event, paragraph) => {
420
+ const modifier = event.metaKey || event.ctrlKey;
421
+ if (!modifier) return;
422
+ const key = event.key.toLowerCase();
423
+ if (key !== "b" && key !== "i" && key !== "u") return;
424
+ event.preventDefault();
425
+ const current = readDomSelection(paragraph.id);
426
+ selectionRef.current = current;
427
+ const property = key === "b" ? "bold" : key === "i" ? "italic" : "underline";
428
+ const start = current.start;
429
+ const end = current.end;
430
+ if (end <= start || readOnly) return;
431
+ const effectiveStart = start;
432
+ const effectiveEnd = end;
433
+ const style = toggledTextStyle(paragraph, effectiveStart, effectiveEnd, property);
434
+ paragraph.format({ start: effectiveStart, end: effectiveEnd, style });
435
+ runCommit({
436
+ kind: "format",
437
+ blockId: paragraph.id,
438
+ range: { start: effectiveStart, end: effectiveEnd },
439
+ style,
440
+ revision: revisionToken ?? document2.revision
441
+ });
442
+ queueMicrotask(() => restoreSelection(editorNodes.current.get(paragraph.id), start, end));
443
+ },
444
+ [document2, readDomSelection, readOnly, revisionToken, runCommit]
445
+ );
446
+ const changeLayout = (next) => {
447
+ if (controlledLayout === void 0) setUncontrolledLayout(next);
448
+ onLayoutChange?.(next);
449
+ };
450
+ const addFirstParagraph = () => {
451
+ if (readOnly) return;
452
+ const paragraph = document2.blocks.addParagraph("");
453
+ setLocalRevision(document2.revision);
454
+ publishSelection({ blockId: paragraph.id, start: 0, end: 0 });
455
+ runCommit({
456
+ kind: "insert-paragraph",
457
+ blockId: paragraph.id,
458
+ revision: revisionToken ?? document2.revision
459
+ });
460
+ };
461
+ return /* @__PURE__ */ jsxs(
462
+ "div",
463
+ {
464
+ "data-og-document-editor": "",
465
+ "data-og-document-layout": layout,
466
+ "data-og-command-state": commandFailure ? "error" : pendingCommands > 0 ? "pending" : "idle",
467
+ "aria-busy": pendingCommands > 0 ? "true" : void 0,
468
+ className: cn("flex h-full min-h-0 min-w-0 flex-col bg-og-surface-2", className),
469
+ children: [
470
+ /* @__PURE__ */ jsxs(
471
+ "div",
472
+ {
473
+ role: "toolbar",
474
+ "aria-label": "Document formatting",
475
+ className: "flex min-h-10 shrink-0 items-center gap-1 border-b border-og-border bg-og-surface-1 px-2",
476
+ children: [
477
+ /* @__PURE__ */ jsx(
478
+ ToolbarButton,
479
+ {
480
+ label: "Bold",
481
+ shortcut: "\u2318B",
482
+ disabled: readOnly || !selectedBlockId || !hasTextSelection,
483
+ onPress: () => applyFormat("bold"),
484
+ children: /* @__PURE__ */ jsx(BoldIcon, { className: "size-4" })
485
+ }
486
+ ),
487
+ /* @__PURE__ */ jsx(
488
+ ToolbarButton,
489
+ {
490
+ label: "Italic",
491
+ shortcut: "\u2318I",
492
+ disabled: readOnly || !selectedBlockId || !hasTextSelection,
493
+ onPress: () => applyFormat("italic"),
494
+ children: /* @__PURE__ */ jsx(ItalicIcon, { className: "size-4" })
495
+ }
496
+ ),
497
+ /* @__PURE__ */ jsx(
498
+ ToolbarButton,
499
+ {
500
+ label: "Underline",
501
+ shortcut: "\u2318U",
502
+ disabled: readOnly || !selectedBlockId || !hasTextSelection,
503
+ onPress: () => applyFormat("underline"),
504
+ children: /* @__PURE__ */ jsx(UnderlineIcon, { className: "size-4" })
505
+ }
506
+ ),
507
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "mx-1 h-5 w-px bg-og-border" }),
508
+ /* @__PURE__ */ jsx(
509
+ ToolbarButton,
510
+ {
511
+ label: "Continuous layout",
512
+ pressed: layout === "continuous",
513
+ onPress: () => changeLayout("continuous"),
514
+ children: /* @__PURE__ */ jsx(Rows3Icon, { className: "size-4" })
515
+ }
516
+ ),
517
+ /* @__PURE__ */ jsx(
518
+ ToolbarButton,
519
+ {
520
+ label: "Paginated layout",
521
+ pressed: layout === "paginated",
522
+ onPress: () => changeLayout("paginated"),
523
+ children: /* @__PURE__ */ jsx(Columns2Icon, { className: "size-4" })
524
+ }
525
+ ),
526
+ /* @__PURE__ */ jsxs("span", { className: "ml-auto truncate text-og-xs text-og-fg-muted", children: [
527
+ commandFailure ? "Change not saved" : pendingCommands > 0 ? "Saving\u2026" : readOnly ? "Read only" : "Editing",
528
+ " ",
529
+ "\xB7 ",
530
+ document2.blocks.items.length.toLocaleString(),
531
+ " blocks"
532
+ ] })
533
+ ]
534
+ }
535
+ ),
536
+ /* @__PURE__ */ jsx("div", { role: "status", "aria-live": "polite", className: "sr-only", children: commandFailure?.message ?? (pendingCommands > 0 ? "Saving document changes" : "") }),
537
+ /* @__PURE__ */ jsxs(
538
+ "div",
539
+ {
540
+ ref: viewportRef,
541
+ role: "document",
542
+ "aria-label": ariaLabel,
543
+ "aria-readonly": readOnly,
544
+ tabIndex: -1,
545
+ onPointerDown: (event) => {
546
+ const target = event.target;
547
+ if (target instanceof Element && !target.closest("[data-og-paragraph]")) {
548
+ clearSelection();
549
+ }
550
+ },
551
+ onScroll: handleScroll,
552
+ className: cn(
553
+ "relative min-h-0 min-w-0 flex-1 overflow-auto overscroll-contain outline-hidden [overflow-anchor:none]",
554
+ layout === "paginated" ? "bg-og-surface-3" : "bg-og-surface-1"
555
+ ),
556
+ style: { height: Math.max(1, viewportHeight) },
557
+ children: [
558
+ commandFailure ? /* @__PURE__ */ jsxs(
559
+ "div",
560
+ {
561
+ role: "alert",
562
+ className: "absolute left-2 top-2 z-30 flex w-fit items-center gap-2 rounded-og-sm border border-og-status-failed/30 bg-og-surface-1/95 px-2 py-1 text-og-xs text-og-status-failed shadow-og-sm",
563
+ style: { maxWidth: "calc(100% - 1rem)" },
564
+ children: [
565
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: commandFailure.message }),
566
+ /* @__PURE__ */ jsx(
567
+ "button",
568
+ {
569
+ type: "button",
570
+ onClick: commandFailure.retry,
571
+ className: "shrink-0 rounded-og-xs px-1.5 py-0.5 font-medium outline-hidden hover:bg-og-surface-3 focus-visible:ring-2 focus-visible:ring-og-accent",
572
+ children: "Retry"
573
+ }
574
+ )
575
+ ]
576
+ }
577
+ ) : null,
578
+ pages.length === 0 ? /* @__PURE__ */ jsx("div", { className: "grid h-full place-items-center p-8 text-center", children: /* @__PURE__ */ jsxs("div", { children: [
579
+ /* @__PURE__ */ jsx(PilcrowIcon, { "aria-hidden": true, className: "mx-auto mb-3 size-7 text-og-fg-subtle" }),
580
+ /* @__PURE__ */ jsx("p", { className: "text-og-sm text-og-fg-muted", children: "This document is empty." }),
581
+ !readOnly ? /* @__PURE__ */ jsx(
582
+ "button",
583
+ {
584
+ type: "button",
585
+ onClick: addFirstParagraph,
586
+ className: "mt-3 rounded-og-md border border-og-border bg-og-surface-1 px-3 py-1.5 text-og-sm text-og-fg hover:bg-og-surface-2 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent",
587
+ children: "Start writing"
588
+ }
589
+ ) : null
590
+ ] }) }) : /* @__PURE__ */ jsx("div", { className: "relative min-w-full", style: { height: axis.total }, children: mountedIndexes.map((pageIndex) => {
591
+ const page = pages[pageIndex];
592
+ const top = axis.offsets[pageIndex];
593
+ return /* @__PURE__ */ jsx(
594
+ "article",
595
+ {
596
+ "aria-label": `${layout === "paginated" ? "Page" : "Section"} ${pageIndex + 1} of ${pages.length}`,
597
+ "aria-posinset": pageIndex + 1,
598
+ "aria-setsize": pages.length,
599
+ "data-og-document-page": pageIndex + 1,
600
+ "data-og-page-width-pt": page.page.widthPt,
601
+ "data-og-page-height-pt": page.page.heightPt,
602
+ className: cn(
603
+ "absolute overflow-hidden",
604
+ layout === "paginated" ? "left-1/2 -translate-x-1/2 border shadow-og-sm" : "left-0 right-0 border-b border-og-border bg-og-surface-1 text-og-fg"
605
+ ),
606
+ style: {
607
+ top,
608
+ color: layout === "paginated" ? "#171717" : void 0,
609
+ backgroundColor: layout === "paginated" ? "#fff" : void 0,
610
+ borderColor: layout === "paginated" ? "#0000001a" : void 0,
611
+ width: layout === "paginated" ? page.page.widthPt * POINT_TO_CSS_PIXEL * page.renderScale : void 0,
612
+ height: page.height
613
+ },
614
+ children: /* @__PURE__ */ jsx(
615
+ MeasuredPageBody,
616
+ {
617
+ pageKey: page.key,
618
+ measureToken: documentRevision,
619
+ onHeight: recordPageHeight,
620
+ className: cn(
621
+ layout === "paginated" ? "absolute left-0 top-0 overflow-hidden" : "mx-auto"
622
+ ),
623
+ style: {
624
+ ...pageContentStyle(page.page, layout),
625
+ maxWidth: layout === "continuous" ? "68rem" : void 0,
626
+ ...layout === "paginated" ? {
627
+ width: page.page.widthPt * POINT_TO_CSS_PIXEL,
628
+ minHeight: page.naturalHeight,
629
+ transform: `scale(${page.renderScale})`,
630
+ transformOrigin: "top left"
631
+ } : {}
632
+ },
633
+ children: page.blocks.map(({ block, listOrdinal }) => /* @__PURE__ */ jsx(
634
+ DocumentBlockView,
635
+ {
636
+ block,
637
+ viewBlockId: blockIdForView(block.id),
638
+ comments: annotations.comments.get(block.id) ?? EMPTY_COMMENTS,
639
+ changes: annotations.changes.get(block.id) ?? EMPTY_CHANGES,
640
+ projectionRevision: localRevision,
641
+ listOrdinal,
642
+ readOnly,
643
+ selected: selectedBlockId === block.id,
644
+ openThreadId,
645
+ registerEditor: (node) => {
646
+ if (node) editorNodes.current.set(block.id, node);
647
+ else editorNodes.current.delete(block.id);
648
+ },
649
+ onThreadToggle: (threadId) => setOpenThreadId((current) => current === threadId ? null : threadId),
650
+ onInput: (paragraph, text) => commitText(paragraph, text),
651
+ onBlur: () => setLocalRevision(document2.revision),
652
+ onSelection: (paragraph) => readDomSelection(paragraph.id),
653
+ onKeyDown: handleEditorKeyDown
654
+ },
655
+ block.id
656
+ ))
657
+ }
658
+ )
659
+ },
660
+ page.key
661
+ );
662
+ }) })
663
+ ]
664
+ }
665
+ )
666
+ ]
667
+ }
668
+ );
669
+ }
670
+ function DocumentArtifactSurface({
671
+ document: document2,
672
+ title,
673
+ subtitle,
674
+ busy,
675
+ className,
676
+ editorClassName,
677
+ ...editorProps
678
+ }) {
679
+ return /* @__PURE__ */ jsx(
680
+ ArtifactSurface,
681
+ {
682
+ modality: "document",
683
+ title,
684
+ subtitle,
685
+ busy,
686
+ className,
687
+ children: /* @__PURE__ */ jsx(
688
+ DocumentEditor,
689
+ {
690
+ ...editorProps,
691
+ document: document2,
692
+ ariaLabel: `${title} document`,
693
+ className: editorClassName
694
+ }
695
+ )
696
+ }
697
+ );
698
+ }
699
+ function DocumentProjectionArtifactSurface({
700
+ projection,
701
+ title,
702
+ subtitle,
703
+ busy,
704
+ className,
705
+ editorClassName,
706
+ ...editorProps
707
+ }) {
708
+ return /* @__PURE__ */ jsx(
709
+ ArtifactSurface,
710
+ {
711
+ modality: "document",
712
+ title,
713
+ subtitle,
714
+ busy,
715
+ className,
716
+ children: /* @__PURE__ */ jsx(
717
+ DocumentProjectionEditor,
718
+ {
719
+ ...editorProps,
720
+ projection,
721
+ ariaLabel: `${title} document`,
722
+ className: editorClassName
723
+ }
724
+ )
725
+ }
726
+ );
727
+ }
728
+ function MeasuredPageBody({
729
+ pageKey,
730
+ measureToken,
731
+ onHeight,
732
+ className,
733
+ style,
734
+ children
735
+ }) {
736
+ const nodeRef = useRef(null);
737
+ useLayoutEffect(() => {
738
+ const node = nodeRef.current;
739
+ if (!node) return;
740
+ const report = () => onHeight(pageKey, Math.max(node.clientHeight, node.scrollHeight));
741
+ report();
742
+ if (!globalThis.ResizeObserver) return;
743
+ const observer = new globalThis.ResizeObserver(report);
744
+ observer.observe(node);
745
+ return () => observer.disconnect();
746
+ }, [measureToken, onHeight, pageKey]);
747
+ return /* @__PURE__ */ jsx("div", { ref: nodeRef, className, style, children });
748
+ }
749
+ function DocumentBlockView({
750
+ block,
751
+ viewBlockId,
752
+ comments,
753
+ changes,
754
+ projectionRevision,
755
+ listOrdinal,
756
+ readOnly,
757
+ selected,
758
+ openThreadId,
759
+ registerEditor,
760
+ onThreadToggle,
761
+ onInput,
762
+ onBlur,
763
+ onSelection,
764
+ onKeyDown
765
+ }) {
766
+ if (isDocumentTable(block)) {
767
+ return /* @__PURE__ */ jsx(DocumentTableView, { table: block, viewBlockId });
768
+ }
769
+ const unresolved = comments.filter((thread) => !thread.resolved);
770
+ const commentPanelOpen = comments.some((thread) => thread.id === openThreadId);
771
+ const paragraphStyle = toParagraphStyle(block);
772
+ const segments = annotateRuns(block, unresolved, changes);
773
+ const marker = block.style.list ? block.style.list.kind === "number" ? `${listOrdinal ?? 1}.` : "\u2022" : null;
774
+ return /* @__PURE__ */ jsxs(
775
+ "div",
776
+ {
777
+ "data-og-document-block": viewBlockId,
778
+ "data-og-block-kind": "paragraph",
779
+ className: cn(
780
+ "group relative flex min-h-7 min-w-0 gap-2 rounded-sm px-1 transition-colors",
781
+ selected && "bg-og-accent-soft ring-1 ring-inset ring-og-accent"
782
+ ),
783
+ children: [
784
+ marker ? /* @__PURE__ */ jsx(
785
+ "span",
786
+ {
787
+ "aria-hidden": true,
788
+ className: "w-7 shrink-0 select-none text-right opacity-70",
789
+ style: {
790
+ paddingLeft: `${(block.style.list?.level ?? 0) * 12}px`,
791
+ paddingTop: "0.1em"
792
+ },
793
+ children: marker
794
+ }
795
+ ) : null,
796
+ /* @__PURE__ */ jsx(
797
+ ParagraphLeaf,
798
+ {
799
+ paragraph: block,
800
+ viewBlockId,
801
+ segments,
802
+ paragraphStyle,
803
+ listOrdinal,
804
+ readOnly,
805
+ registerEditor,
806
+ onInput,
807
+ onBlur,
808
+ onSelection,
809
+ onKeyDown
810
+ },
811
+ `${block.id}:${projectionRevision}`
812
+ ),
813
+ comments.length > 0 || changes.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "relative flex w-7 shrink-0 flex-col items-center gap-1 pt-0.5", children: [
814
+ comments.length > 0 ? /* @__PURE__ */ jsx(
815
+ "button",
816
+ {
817
+ type: "button",
818
+ "aria-label": unresolved.length > 0 ? `${unresolved.length} unresolved ${unresolved.length === 1 ? "comment" : "comments"}` : `${comments.length} resolved ${comments.length === 1 ? "comment" : "comments"}`,
819
+ "aria-expanded": commentPanelOpen,
820
+ onClick: () => onThreadToggle((unresolved[0] ?? comments[0]).id),
821
+ className: cn(
822
+ "grid size-6 place-items-center rounded-full focus-visible:outline-hidden focus-visible:ring-2",
823
+ unresolved.length > 0 ? "bg-og-accent-soft text-og-status-waiting hover:bg-og-surface-3 focus-visible:ring-og-status-waiting" : "bg-og-surface-3 text-og-fg-muted hover:text-og-fg focus-visible:ring-og-accent"
824
+ ),
825
+ children: /* @__PURE__ */ jsx(MessageSquareTextIcon, { className: "size-3.5" })
826
+ }
827
+ ) : null,
828
+ changes.length > 0 ? /* @__PURE__ */ jsx(
829
+ "span",
830
+ {
831
+ role: "img",
832
+ "aria-label": `${changes.length} tracked ${changes.length === 1 ? "change" : "changes"}`,
833
+ className: "mt-1 h-4 w-1 rounded-full bg-og-status-idle"
834
+ }
835
+ ) : null,
836
+ commentPanelOpen ? /* @__PURE__ */ jsxs(
837
+ "aside",
838
+ {
839
+ "aria-label": "Comment thread",
840
+ className: "absolute right-0 top-8 z-20 w-64 rounded-og-md border border-og-border bg-og-surface-1 p-3 text-og-xs text-og-fg shadow-og-lg",
841
+ children: [
842
+ /* @__PURE__ */ jsx("div", { className: "mb-2 font-medium", children: comments.length === 1 ? "Comment" : `${comments.length} comments` }),
843
+ /* @__PURE__ */ jsx("ol", { className: "max-h-72 space-y-3 overflow-y-auto", children: comments.map((thread) => /* @__PURE__ */ jsxs("li", { className: "space-y-2", children: [
844
+ /* @__PURE__ */ jsx("div", { className: "text-og-fg-muted", children: thread.resolved ? "Resolved" : "Open" }),
845
+ thread.replies.map((reply, index) => (
846
+ // Reply position is stable within the append-only comment thread.
847
+ // oxlint-disable-next-line react/no-array-index-key
848
+ /* @__PURE__ */ jsxs("div", { children: [
849
+ /* @__PURE__ */ jsx("div", { className: "text-og-fg-muted", children: reply.author }),
850
+ /* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap", children: reply.text })
851
+ ] }, `${reply.createdAt}:${index}`)
852
+ ))
853
+ ] }, thread.id)) })
854
+ ]
855
+ }
856
+ ) : null
857
+ ] }) : null
858
+ ]
859
+ }
860
+ );
861
+ }
862
+ var ParagraphLeaf = memo(
863
+ function ParagraphLeaf2({
864
+ paragraph,
865
+ viewBlockId,
866
+ segments,
867
+ paragraphStyle,
868
+ listOrdinal,
869
+ readOnly,
870
+ registerEditor,
871
+ onInput,
872
+ onBlur,
873
+ onSelection,
874
+ onKeyDown
875
+ }) {
876
+ const composing = useRef(false);
877
+ const Tag = headingTag(paragraph.style.headingLevel);
878
+ const label = paragraph.style.list?.kind === "number" ? `Numbered list item ${listOrdinal ?? 1}` : paragraph.style.list?.kind === "bullet" ? "Bullet list item" : paragraph.style.headingLevel ? `Heading ${paragraph.style.headingLevel}` : "Paragraph";
879
+ return /* @__PURE__ */ jsx(
880
+ Tag,
881
+ {
882
+ ref: registerEditor,
883
+ contentEditable: !readOnly,
884
+ suppressContentEditableWarning: true,
885
+ tabIndex: readOnly ? 0 : void 0,
886
+ role: !readOnly && !paragraph.style.headingLevel ? "textbox" : void 0,
887
+ "aria-label": label,
888
+ "aria-multiline": !readOnly || void 0,
889
+ "aria-readonly": readOnly,
890
+ spellCheck: !readOnly,
891
+ "data-og-paragraph": viewBlockId,
892
+ onFocus: () => onSelection(paragraph),
893
+ onSelect: () => onSelection(paragraph),
894
+ onKeyUp: () => onSelection(paragraph),
895
+ onMouseUp: () => onSelection(paragraph),
896
+ onCompositionStart: () => {
897
+ composing.current = true;
898
+ },
899
+ onCompositionEnd: (event) => {
900
+ composing.current = false;
901
+ onInput(paragraph, event.currentTarget.textContent ?? "");
902
+ onSelection(paragraph);
903
+ },
904
+ onPaste: (event) => {
905
+ if (readOnly) return;
906
+ event.preventDefault();
907
+ const text = event.clipboardData.getData("text/plain").slice(0, MAX_PLAIN_TEXT_TRANSFER);
908
+ if (!text) return;
909
+ insertPlainText(event.currentTarget, text);
910
+ onInput(paragraph, event.currentTarget.textContent ?? "");
911
+ onSelection(paragraph);
912
+ },
913
+ onDragOver: (event) => {
914
+ if (!readOnly) event.preventDefault();
915
+ },
916
+ onDrop: (event) => {
917
+ if (!readOnly) event.preventDefault();
918
+ },
919
+ onKeyDown: (event) => {
920
+ onKeyDown(event, paragraph);
921
+ if (event.defaultPrevented || readOnly) return;
922
+ if (event.key === "Enter" && !event.metaKey && !event.ctrlKey && !event.altKey) {
923
+ event.preventDefault();
924
+ insertPlainText(event.currentTarget, "\n");
925
+ onInput(paragraph, event.currentTarget.textContent ?? "");
926
+ onSelection(paragraph);
927
+ }
928
+ },
929
+ onInput: (event) => {
930
+ if (!composing.current && !event.nativeEvent.isComposing) {
931
+ onInput(paragraph, event.currentTarget.textContent ?? "");
932
+ }
933
+ },
934
+ onBlur,
935
+ className: cn(
936
+ "min-w-0 flex-1 whitespace-pre-wrap break-words rounded-sm outline-hidden",
937
+ !readOnly && "cursor-text focus-visible:ring-2 focus-visible:ring-og-accent"
938
+ ),
939
+ style: paragraphStyle,
940
+ children: segments.map((segment, index) => /* @__PURE__ */ jsx(
941
+ "span",
942
+ {
943
+ "data-og-commented": segment.commented || void 0,
944
+ "data-og-change": segment.changeKind,
945
+ style: segment.style,
946
+ children: segment.text
947
+ },
948
+ `${segment.start}:${index}`
949
+ ))
950
+ }
951
+ );
952
+ },
953
+ (previous, next) => {
954
+ if (next.readOnly || previous.viewBlockId !== next.viewBlockId) return false;
955
+ const active = globalThis.document?.activeElement;
956
+ return active instanceof HTMLElement && active.dataset.ogParagraph === next.viewBlockId;
957
+ }
958
+ );
959
+ function DocumentTableView({
960
+ table,
961
+ viewBlockId
962
+ }) {
963
+ const columnWidths = table.style.columnWidthsPt;
964
+ return /* @__PURE__ */ jsx(
965
+ "div",
966
+ {
967
+ "data-og-document-block": viewBlockId,
968
+ "data-og-block-kind": "table",
969
+ className: "overflow-x-auto py-2",
970
+ children: /* @__PURE__ */ jsxs(
971
+ "table",
972
+ {
973
+ className: "w-full border-collapse text-left",
974
+ style: {
975
+ fontSize: "0.92em",
976
+ width: table.style.widthPt ? `${table.style.widthPt}pt` : void 0
977
+ },
978
+ children: [
979
+ columnWidths ? /* @__PURE__ */ jsx("colgroup", { children: columnWidths.map((width, index) => (
980
+ // Column position is its identity in the document table model.
981
+ // oxlint-disable-next-line react/no-array-index-key
982
+ /* @__PURE__ */ jsx("col", { style: { width: `${width}pt` } }, index)
983
+ )) }) : null,
984
+ /* @__PURE__ */ jsx("tbody", { children: table.rows.map((row, rowIndex) => (
985
+ // Row position is its identity in the document table model.
986
+ // oxlint-disable-next-line react/no-array-index-key
987
+ /* @__PURE__ */ jsx("tr", { children: row.map((cell, columnIndex) => {
988
+ const header = rowIndex < (table.style.headerRows ?? 0);
989
+ const Cell = header ? "th" : "td";
990
+ return /* @__PURE__ */ jsx(
991
+ Cell,
992
+ {
993
+ scope: header ? "col" : void 0,
994
+ className: cn("border align-middle", header && "font-semibold"),
995
+ style: {
996
+ borderColor: table.style.borderColor ?? "#d1d5db",
997
+ background: header ? table.style.headerFill ?? "#f3f4f6" : void 0,
998
+ padding: `${table.style.cellPaddingPt ?? 6}pt`
999
+ },
1000
+ children: cell.map((run, index) => (
1001
+ // Run position is stable within the immutable cell projection.
1002
+ // oxlint-disable-next-line react/no-array-index-key
1003
+ /* @__PURE__ */ jsx("span", { style: toRunStyle(run.style), children: run.text }, index)
1004
+ ))
1005
+ },
1006
+ columnIndex
1007
+ );
1008
+ }) }, rowIndex)
1009
+ )) })
1010
+ ]
1011
+ }
1012
+ )
1013
+ }
1014
+ );
1015
+ }
1016
+ function ToolbarButton({
1017
+ label,
1018
+ shortcut,
1019
+ pressed,
1020
+ disabled,
1021
+ onPress,
1022
+ children
1023
+ }) {
1024
+ return /* @__PURE__ */ jsx(
1025
+ "button",
1026
+ {
1027
+ type: "button",
1028
+ "aria-label": label,
1029
+ "aria-pressed": pressed,
1030
+ title: shortcut ? `${label} (${shortcut})` : label,
1031
+ disabled,
1032
+ onMouseDown: (event) => event.preventDefault(),
1033
+ onClick: onPress,
1034
+ className: cn(
1035
+ "grid size-7 place-items-center rounded-og-sm text-og-fg-muted outline-hidden transition-colors hover:bg-og-surface-3 hover:text-og-fg focus-visible:ring-2 focus-visible:ring-og-accent disabled:pointer-events-none disabled:opacity-40",
1036
+ pressed && "bg-og-surface-3 text-og-fg"
1037
+ ),
1038
+ children
1039
+ }
1040
+ );
1041
+ }
1042
+ function projectPages(document2, layout) {
1043
+ const pages = [];
1044
+ const sections = document2.sections.items;
1045
+ let sectionIndex = 0;
1046
+ let pageGeometry = sections[0]?.page ?? document2.page;
1047
+ let blocks = [];
1048
+ let used = 0;
1049
+ let pageIndex = 0;
1050
+ let numberOrdinal = 0;
1051
+ const flush = (force = false) => {
1052
+ if (blocks.length === 0 && !force) return;
1053
+ const physicalPageHeight = pageGeometry.heightPt * POINT_TO_CSS_PIXEL;
1054
+ const pageVerticalChrome = (pageGeometry.marginTopPt + pageGeometry.marginBottomPt) * POINT_TO_CSS_PIXEL;
1055
+ pages.push({
1056
+ key: `${layout}:${sections[sectionIndex]?.id ?? "default"}:${pageIndex++}:${blocks[0]?.block.id ?? "blank"}`,
1057
+ blocks,
1058
+ height: layout === "paginated" ? Math.max(physicalPageHeight, used + pageVerticalChrome) : Math.max(1, used + 48),
1059
+ minimumHeight: layout === "paginated" ? physicalPageHeight : 1,
1060
+ page: { ...pageGeometry }
1061
+ });
1062
+ blocks = [];
1063
+ used = 0;
1064
+ };
1065
+ for (let blockIndex = 0; blockIndex < document2.blocks.items.length; blockIndex += 1) {
1066
+ while (sectionIndex + 1 < sections.length && sections[sectionIndex + 1].startBlockIndex <= blockIndex) {
1067
+ flush();
1068
+ sectionIndex += 1;
1069
+ pageGeometry = sections[sectionIndex].page;
1070
+ }
1071
+ const block = document2.blocks.items[blockIndex];
1072
+ if (isDocumentPageBreak(block)) {
1073
+ flush(true);
1074
+ numberOrdinal = 0;
1075
+ continue;
1076
+ }
1077
+ if (isDocumentParagraph(block) && block.style.pageBreakBefore) flush();
1078
+ const height = estimateBlockHeight(block);
1079
+ const physicalPageHeight = pageGeometry.heightPt * POINT_TO_CSS_PIXEL;
1080
+ const usableHeight = Math.max(
1081
+ 120,
1082
+ physicalPageHeight - (pageGeometry.marginTopPt + pageGeometry.marginBottomPt) * POINT_TO_CSS_PIXEL
1083
+ );
1084
+ const targetHeight = layout === "paginated" ? usableHeight : Math.max(560, usableHeight);
1085
+ if (blocks.length > 0 && used + height > targetHeight) flush();
1086
+ if (isDocumentParagraph(block) && block.style.list?.kind === "number") {
1087
+ numberOrdinal += 1;
1088
+ } else {
1089
+ numberOrdinal = 0;
1090
+ }
1091
+ blocks.push({
1092
+ block,
1093
+ height,
1094
+ listOrdinal: isDocumentParagraph(block) && block.style.list?.kind === "number" ? numberOrdinal : void 0
1095
+ });
1096
+ used += height;
1097
+ }
1098
+ flush();
1099
+ return pages;
1100
+ }
1101
+ function indexAnnotations(document2) {
1102
+ const comments = /* @__PURE__ */ new Map();
1103
+ const changes = /* @__PURE__ */ new Map();
1104
+ for (const thread of document2.comments.items) {
1105
+ const items = comments.get(thread.blockId);
1106
+ if (items) items.push(thread);
1107
+ else comments.set(thread.blockId, [thread]);
1108
+ }
1109
+ for (const change of document2.changes.items) {
1110
+ const items = changes.get(change.blockId);
1111
+ if (items) items.push(change);
1112
+ else changes.set(change.blockId, [change]);
1113
+ }
1114
+ return { comments, changes };
1115
+ }
1116
+ function estimateBlockHeight(block) {
1117
+ if (isDocumentTable(block)) {
1118
+ return Math.max(48, block.rows.length * 38 + 16);
1119
+ }
1120
+ const fontSize = block.runs.reduce(
1121
+ (largest, run) => Math.max(largest, run.style.fontSizePt ?? 11),
1122
+ block.style.headingLevel ? Math.max(14, 26 - block.style.headingLevel * 2) : 11
1123
+ );
1124
+ const lineHeight = fontSize * POINT_TO_CSS_PIXEL * (block.style.lineHeight ?? 1.35);
1125
+ const lines = Math.max(
1126
+ 1,
1127
+ block.text.split("\n").reduce((total, line) => total + Math.max(1, Math.ceil(line.length / 72)), 0)
1128
+ );
1129
+ return Math.max(
1130
+ 28,
1131
+ lines * lineHeight + ((block.style.spaceBeforePt ?? 2) + (block.style.spaceAfterPt ?? 6)) * POINT_TO_CSS_PIXEL
1132
+ );
1133
+ }
1134
+ function buildPageAxis(pages, gap) {
1135
+ const offsets = new Float64Array(pages.length + 1);
1136
+ for (let index = 0; index < pages.length; index += 1) {
1137
+ offsets[index + 1] = offsets[index] + pages[index].height + gap;
1138
+ }
1139
+ return { offsets, total: Math.max(1, (offsets[pages.length] ?? 0) - gap) };
1140
+ }
1141
+ function pageAtOffset(axis, count, offset) {
1142
+ if (count <= 1) return 0;
1143
+ let low = 0;
1144
+ let high = count;
1145
+ while (low < high) {
1146
+ const middle = low + high >>> 1;
1147
+ if (axis.offsets[middle + 1] <= offset) low = middle + 1;
1148
+ else high = middle;
1149
+ }
1150
+ return Math.max(0, Math.min(count - 1, low));
1151
+ }
1152
+ function visiblePageIndexes(axis, count, scrollTop, viewportHeight, overscan, required) {
1153
+ if (count === 0) return [];
1154
+ const start = Math.max(0, pageAtOffset(axis, count, scrollTop) - overscan);
1155
+ const end = Math.min(count, pageAtOffset(axis, count, scrollTop + viewportHeight) + overscan + 1);
1156
+ const indexes = Array.from({ length: end - start }, (_, index) => start + index);
1157
+ if (required >= 0 && (required < start || required >= end)) indexes.push(required);
1158
+ return indexes.sort((left, right) => left - right);
1159
+ }
1160
+ function responsiveDocumentPageScale(page, viewportWidth) {
1161
+ if (viewportWidth <= 0) return 1;
1162
+ const naturalWidth = page.widthPt * POINT_TO_CSS_PIXEL;
1163
+ return Math.min(1, Math.max(0.1, (viewportWidth - 32) / naturalWidth));
1164
+ }
1165
+ function pageContentStyle(page, layout) {
1166
+ if (layout === "continuous") {
1167
+ return {
1168
+ padding: "24px clamp(24px, 8vw, 96px)"
1169
+ };
1170
+ }
1171
+ return {
1172
+ paddingTop: `${page.marginTopPt * POINT_TO_CSS_PIXEL}px`,
1173
+ paddingRight: `${page.marginRightPt * POINT_TO_CSS_PIXEL}px`,
1174
+ paddingBottom: `${page.marginBottomPt * POINT_TO_CSS_PIXEL}px`,
1175
+ paddingLeft: `${page.marginLeftPt * POINT_TO_CSS_PIXEL}px`
1176
+ };
1177
+ }
1178
+ function headingTag(level) {
1179
+ if (!level) return "p";
1180
+ return `h${Math.max(1, Math.min(6, level))}`;
1181
+ }
1182
+ function toParagraphStyle(paragraph) {
1183
+ const firstRun = paragraph.runs[0];
1184
+ return {
1185
+ textAlign: paragraph.style.alignment,
1186
+ marginTop: `${paragraph.style.spaceBeforePt ?? 0}pt`,
1187
+ marginBottom: `${paragraph.style.spaceAfterPt ?? 0}pt`,
1188
+ lineHeight: paragraph.style.lineHeight ?? 1.35,
1189
+ breakBefore: paragraph.style.pageBreakBefore ? "page" : void 0,
1190
+ fontFamily: firstRun?.style.fontFamily,
1191
+ fontSize: firstRun?.style.fontSizePt ? `${firstRun.style.fontSizePt}pt` : void 0,
1192
+ fontWeight: paragraph.style.headingLevel ? 650 : void 0
1193
+ };
1194
+ }
1195
+ function toRunStyle(style) {
1196
+ return {
1197
+ fontFamily: style.fontFamily,
1198
+ fontSize: style.fontSizePt ? `${style.fontSizePt}pt` : void 0,
1199
+ color: style.color,
1200
+ fontWeight: style.bold ? 700 : void 0,
1201
+ fontStyle: style.italic ? "italic" : void 0,
1202
+ textDecoration: [style.underline ? "underline" : "", style.strike ? "line-through" : ""].filter(Boolean).join(" ") || void 0
1203
+ };
1204
+ }
1205
+ function annotateRuns(paragraph, comments, changes) {
1206
+ const segments = [];
1207
+ let runStart = 0;
1208
+ for (const run of paragraph.runs) {
1209
+ const runEnd = runStart + run.text.length;
1210
+ const boundaries = /* @__PURE__ */ new Set([runStart, runEnd]);
1211
+ for (const annotation of [...comments, ...changes]) {
1212
+ if (annotation.start > runStart && annotation.start < runEnd)
1213
+ boundaries.add(annotation.start);
1214
+ if (annotation.end > runStart && annotation.end < runEnd) boundaries.add(annotation.end);
1215
+ }
1216
+ const ordered = [...boundaries].sort((left, right) => left - right);
1217
+ for (let index = 0; index < ordered.length - 1; index += 1) {
1218
+ const start = ordered[index];
1219
+ const end = ordered[index + 1];
1220
+ const commented = comments.some((comment) => comment.start < end && comment.end > start);
1221
+ const change = changes.find((item) => item.start < end && item.end > start);
1222
+ const baseStyle = toRunStyle(run.style);
1223
+ segments.push({
1224
+ text: run.text.slice(start - runStart, end - runStart),
1225
+ start,
1226
+ commented,
1227
+ changeKind: change?.kind,
1228
+ style: {
1229
+ ...baseStyle,
1230
+ backgroundColor: commented ? "color-mix(in oklch, var(--og-color-status-waiting) 18%, transparent)" : void 0,
1231
+ textDecoration: change?.kind === "delete" ? "line-through" : change?.kind === "insert" ? "underline" : baseStyle.textDecoration,
1232
+ textDecorationColor: change?.kind === "delete" ? "var(--og-color-status-failed)" : change?.kind === "insert" ? "var(--og-color-status-idle)" : void 0
1233
+ }
1234
+ });
1235
+ }
1236
+ runStart = runEnd;
1237
+ }
1238
+ if (segments.length === 0) {
1239
+ segments.push({ text: "", start: 0, style: {}, commented: false });
1240
+ }
1241
+ return segments;
1242
+ }
1243
+ function selectionOffsets(root) {
1244
+ if (!root) return null;
1245
+ const selection = globalThis.getSelection?.();
1246
+ if (!selection || selection.rangeCount === 0) return null;
1247
+ const range = selection.getRangeAt(0);
1248
+ if (!root.contains(range.startContainer) || !root.contains(range.endContainer)) return null;
1249
+ const beforeStart = range.cloneRange();
1250
+ beforeStart.selectNodeContents(root);
1251
+ beforeStart.setEnd(range.startContainer, range.startOffset);
1252
+ const beforeEnd = range.cloneRange();
1253
+ beforeEnd.selectNodeContents(root);
1254
+ beforeEnd.setEnd(range.endContainer, range.endOffset);
1255
+ return {
1256
+ start: beforeStart.toString().length,
1257
+ end: beforeEnd.toString().length
1258
+ };
1259
+ }
1260
+ function insertPlainText(root, text) {
1261
+ const selection = globalThis.getSelection?.();
1262
+ if (!selection || selection.rangeCount === 0) return;
1263
+ const range = selection.getRangeAt(0);
1264
+ if (!root.contains(range.startContainer) || !root.contains(range.endContainer)) return;
1265
+ range.deleteContents();
1266
+ const node = globalThis.document.createTextNode(text);
1267
+ range.insertNode(node);
1268
+ range.setStartAfter(node);
1269
+ range.collapse(true);
1270
+ selection.removeAllRanges();
1271
+ selection.addRange(range);
1272
+ }
1273
+ function restoreSelection(root, requestedStart, requestedEnd) {
1274
+ if (!root || !globalThis.getSelection) return;
1275
+ const textLength = root.textContent?.length ?? 0;
1276
+ const start = Math.max(0, Math.min(requestedStart, textLength));
1277
+ const end = Math.max(start, Math.min(requestedEnd, textLength));
1278
+ const startPoint = textPointAt(root, start);
1279
+ const endPoint = textPointAt(root, end);
1280
+ if (!startPoint || !endPoint) return;
1281
+ const range = document.createRange();
1282
+ range.setStart(startPoint.node, startPoint.offset);
1283
+ range.setEnd(endPoint.node, endPoint.offset);
1284
+ const domSelection = globalThis.getSelection();
1285
+ domSelection?.removeAllRanges();
1286
+ domSelection?.addRange(range);
1287
+ }
1288
+ function textPointAt(root, target) {
1289
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
1290
+ let remaining = target;
1291
+ let node = walker.nextNode();
1292
+ while (node) {
1293
+ const length = node.textContent?.length ?? 0;
1294
+ if (remaining <= length) return { node, offset: remaining };
1295
+ remaining -= length;
1296
+ node = walker.nextNode();
1297
+ }
1298
+ return { node: root, offset: root.childNodes.length };
1299
+ }
1300
+ function toggledTextStyle(paragraph, start, end, property) {
1301
+ let allEnabled = end > start;
1302
+ let offset = 0;
1303
+ for (const run of paragraph.runs) {
1304
+ const runEnd = offset + run.text.length;
1305
+ if (runEnd > start && offset < end && run.style[property] !== true) {
1306
+ allEnabled = false;
1307
+ break;
1308
+ }
1309
+ offset = runEnd;
1310
+ }
1311
+ return { [property]: !allEnabled };
1312
+ }
1313
+ function documentViewFromProjection(projection) {
1314
+ if (projection.blocks.length > MAX_PROJECTION_BLOCKS) {
1315
+ throw new Error(`Document projection exceeds ${MAX_PROJECTION_BLOCKS} blocks`);
1316
+ }
1317
+ if ((projection.comments?.length ?? 0) > MAX_PROJECTION_ANNOTATIONS || (projection.changes?.length ?? 0) > MAX_PROJECTION_ANNOTATIONS) {
1318
+ throw new Error(`Document projection exceeds ${MAX_PROJECTION_ANNOTATIONS} annotations`);
1319
+ }
1320
+ const sections = [...projection.sections ?? []].sort(
1321
+ (left, right) => left.startBlockIndex - right.startBlockIndex
1322
+ );
1323
+ if (sections.some(
1324
+ (section, index) => section.startBlockIndex < 0 || index > 0 && section.startBlockIndex <= sections[index - 1].startBlockIndex
1325
+ )) {
1326
+ throw new Error("Document projection sections must have unique increasing block indexes");
1327
+ }
1328
+ const firstPage = sections[0]?.startBlockIndex === 0 ? sections[0].page : projection.page;
1329
+ validateProjectionPage(firstPage);
1330
+ const externalIdByInternal = /* @__PURE__ */ new Map();
1331
+ const paragraphByExternal = /* @__PURE__ */ new Map();
1332
+ const objects = /* @__PURE__ */ new Map();
1333
+ const blocks = [];
1334
+ const documentSections = [];
1335
+ const comments = [];
1336
+ const changes = [];
1337
+ const seenIds = /* @__PURE__ */ new Set();
1338
+ const remember = (internalId, externalId, value) => {
1339
+ if (!externalId || seenIds.has(externalId)) {
1340
+ throw new Error(`Document projection contains an invalid or duplicate id: ${externalId}`);
1341
+ }
1342
+ seenIds.add(externalId);
1343
+ externalIdByInternal.set(internalId, externalId);
1344
+ objects.set(internalId, value);
1345
+ };
1346
+ let revision = 0;
1347
+ let localParagraphId = 0;
1348
+ const changed = () => {
1349
+ revision += 1;
1350
+ };
1351
+ const document2 = {
1352
+ get revision() {
1353
+ return revision;
1354
+ },
1355
+ page: copyDocumentPage(firstPage),
1356
+ blocks: {
1357
+ items: blocks,
1358
+ addParagraph(text = "") {
1359
+ let id;
1360
+ do {
1361
+ localParagraphId += 1;
1362
+ id = `projection-paragraph-${localParagraphId}`;
1363
+ } while (objects.has(id));
1364
+ const paragraph = new ProjectionParagraphView(id, [{ text, style: {} }], {}, changed);
1365
+ blocks.push(paragraph);
1366
+ objects.set(id, paragraph);
1367
+ changed();
1368
+ return paragraph;
1369
+ }
1370
+ },
1371
+ sections: { items: documentSections },
1372
+ comments: { items: comments },
1373
+ changes: { items: changes },
1374
+ resolve(id) {
1375
+ const value = objects.get(id);
1376
+ if (!value) throw new Error(`Unknown document object id: ${id}`);
1377
+ return value;
1378
+ }
1379
+ };
1380
+ let sectionIndex = sections[0]?.startBlockIndex === 0 ? 1 : 0;
1381
+ if (sections[0]?.startBlockIndex === 0) {
1382
+ const projectedSection = sections[0];
1383
+ validateProjectionPage(projectedSection.page);
1384
+ const section = {
1385
+ ...projectedSection,
1386
+ page: copyDocumentPage(projectedSection.page)
1387
+ };
1388
+ documentSections.push(section);
1389
+ remember(section.id, projectedSection.id, section);
1390
+ } else {
1391
+ documentSections.push({
1392
+ id: "projection-default-section",
1393
+ startBlockIndex: 0,
1394
+ page: copyDocumentPage(firstPage)
1395
+ });
1396
+ }
1397
+ for (let blockIndex = 0; blockIndex < projection.blocks.length; blockIndex += 1) {
1398
+ while (sections[sectionIndex]?.startBlockIndex === blockIndex) {
1399
+ const projectedSection = sections[sectionIndex];
1400
+ validateProjectionPage(projectedSection.page);
1401
+ const section = {
1402
+ ...projectedSection,
1403
+ page: copyDocumentPage(projectedSection.page)
1404
+ };
1405
+ documentSections.push(section);
1406
+ remember(section.id, projectedSection.id, section);
1407
+ sectionIndex += 1;
1408
+ }
1409
+ const projectedBlock = projection.blocks[blockIndex];
1410
+ if (projectedBlock.kind === "paragraph") {
1411
+ const paragraph = new ProjectionParagraphView(
1412
+ projectedBlock.id,
1413
+ projectedBlock.runs.length > 0 ? projectedBlock.runs.map((run) => ({
1414
+ text: run.text.slice(0, MAX_PROJECTION_TEXT),
1415
+ style: { ...run.style ?? {} }
1416
+ })) : [{ text: "", style: {} }],
1417
+ { ...projectedBlock.style ?? {} },
1418
+ changed
1419
+ );
1420
+ blocks.push(paragraph);
1421
+ remember(paragraph.id, projectedBlock.id, paragraph);
1422
+ paragraphByExternal.set(projectedBlock.id, paragraph);
1423
+ } else if (projectedBlock.kind === "table") {
1424
+ const columns = projectedBlock.rows.reduce(
1425
+ (maximum, row) => Math.max(maximum, row.length),
1426
+ 0
1427
+ );
1428
+ if (projectedBlock.rows.length === 0 || columns === 0 || projectedBlock.rows.some((row) => row.length !== columns)) {
1429
+ throw new Error(`Document table ${projectedBlock.id} must be a non-empty rectangle`);
1430
+ }
1431
+ if (projectedBlock.rows.length * columns > MAX_PROJECTION_TABLE_CELLS) {
1432
+ throw new Error(`Document table ${projectedBlock.id} exceeds the projected cell limit`);
1433
+ }
1434
+ const table = {
1435
+ kind: "table",
1436
+ id: projectedBlock.id,
1437
+ rows: projectedBlock.rows.map(
1438
+ (row) => row.map(
1439
+ (cell) => cell.length > 0 ? cell.map((run) => ({
1440
+ text: run.text.slice(0, MAX_PROJECTION_TEXT),
1441
+ style: { ...run.style ?? {} }
1442
+ })) : [{ text: "", style: {} }]
1443
+ )
1444
+ ),
1445
+ style: { ...projectedBlock.style ?? {} }
1446
+ };
1447
+ blocks.push(table);
1448
+ remember(table.id, projectedBlock.id, table);
1449
+ } else {
1450
+ const pageBreak = {
1451
+ kind: "pageBreak",
1452
+ id: projectedBlock.id
1453
+ };
1454
+ blocks.push(pageBreak);
1455
+ remember(pageBreak.id, projectedBlock.id, pageBreak);
1456
+ }
1457
+ }
1458
+ while (sections[sectionIndex]?.startBlockIndex === projection.blocks.length) {
1459
+ const projectedSection = sections[sectionIndex];
1460
+ validateProjectionPage(projectedSection.page);
1461
+ const section = {
1462
+ ...projectedSection,
1463
+ page: copyDocumentPage(projectedSection.page)
1464
+ };
1465
+ documentSections.push(section);
1466
+ remember(section.id, projectedSection.id, section);
1467
+ sectionIndex += 1;
1468
+ }
1469
+ if (sectionIndex < sections.length) {
1470
+ throw new Error("Document projection section starts beyond the projected blocks");
1471
+ }
1472
+ for (const projected of projection.comments ?? []) {
1473
+ const paragraph = paragraphByExternal.get(projected.blockId);
1474
+ const first = projected.replies[0];
1475
+ if (!paragraph || !first) continue;
1476
+ if (!Number.isSafeInteger(projected.start) || !Number.isSafeInteger(projected.end) || projected.start < 0 || projected.end < projected.start || projected.end > paragraph.text.length) {
1477
+ throw new Error(`Document comment ${projected.id} has an invalid range`);
1478
+ }
1479
+ const thread = {
1480
+ ...projected,
1481
+ replies: projected.replies.map((reply) => ({ ...reply }))
1482
+ };
1483
+ comments.push(thread);
1484
+ remember(thread.id, projected.id, thread);
1485
+ }
1486
+ for (const projected of projection.changes ?? []) {
1487
+ const paragraph = paragraphByExternal.get(projected.blockId);
1488
+ if (!paragraph) continue;
1489
+ if (!Number.isSafeInteger(projected.start) || !Number.isSafeInteger(projected.end) || projected.start < 0 || projected.end <= projected.start || projected.end > paragraph.text.length) {
1490
+ throw new Error(`Document tracked change ${projected.id} has an invalid range`);
1491
+ }
1492
+ const change = { ...projected };
1493
+ changes.push(change);
1494
+ remember(change.id, projected.id, change);
1495
+ }
1496
+ return { document: document2, externalIdByInternal };
1497
+ }
1498
+ function copyDocumentPage(page) {
1499
+ return {
1500
+ widthPt: page.widthPt,
1501
+ heightPt: page.heightPt,
1502
+ marginTopPt: page.marginTopPt,
1503
+ marginRightPt: page.marginRightPt,
1504
+ marginBottomPt: page.marginBottomPt,
1505
+ marginLeftPt: page.marginLeftPt,
1506
+ ...page.headerPt === void 0 ? {} : { headerPt: page.headerPt },
1507
+ ...page.footerPt === void 0 ? {} : { footerPt: page.footerPt },
1508
+ ...page.gutterPt === void 0 ? {} : { gutterPt: page.gutterPt }
1509
+ };
1510
+ }
1511
+ var ProjectionParagraphView = class {
1512
+ constructor(id, runs, style, changed) {
1513
+ this.id = id;
1514
+ this.changed = changed;
1515
+ this.runStorage = normalizeProjectionRuns(runs);
1516
+ this.style = { ...style, ...style.list ? { list: { ...style.list } } : {} };
1517
+ }
1518
+ kind = "paragraph";
1519
+ runStorage;
1520
+ style;
1521
+ get runs() {
1522
+ return this.runStorage;
1523
+ }
1524
+ get text() {
1525
+ return this.runStorage.map((run) => run.text).join("");
1526
+ }
1527
+ set text(value) {
1528
+ const current = this.text;
1529
+ if (current === value) return;
1530
+ let start = 0;
1531
+ while (start < current.length && start < value.length && current[start] === value[start]) {
1532
+ start += 1;
1533
+ }
1534
+ let currentEnd = current.length;
1535
+ let valueEnd = value.length;
1536
+ while (currentEnd > start && valueEnd > start && current[currentEnd - 1] === value[valueEnd - 1]) {
1537
+ currentEnd -= 1;
1538
+ valueEnd -= 1;
1539
+ }
1540
+ const inheritedStyle = projectionStyleAt(this.runStorage, start);
1541
+ this.runStorage = normalizeProjectionRuns([
1542
+ ...sliceProjectionRuns(this.runStorage, 0, start),
1543
+ ...valueEnd > start ? [{ text: value.slice(start, valueEnd), style: inheritedStyle }] : [],
1544
+ ...sliceProjectionRuns(this.runStorage, currentEnd, current.length)
1545
+ ]);
1546
+ this.changed();
1547
+ }
1548
+ format({
1549
+ start,
1550
+ end,
1551
+ style
1552
+ }) {
1553
+ const textLength = this.text.length;
1554
+ if (start < 0 || end <= start || end > textLength || Object.keys(style).length === 0) return;
1555
+ this.runStorage = normalizeProjectionRuns([
1556
+ ...sliceProjectionRuns(this.runStorage, 0, start),
1557
+ ...sliceProjectionRuns(this.runStorage, start, end).map((run) => ({
1558
+ text: run.text,
1559
+ style: { ...run.style, ...style }
1560
+ })),
1561
+ ...sliceProjectionRuns(this.runStorage, end, textLength)
1562
+ ]);
1563
+ this.changed();
1564
+ }
1565
+ };
1566
+ function sliceProjectionRuns(runs, start, end) {
1567
+ if (end <= start) return [];
1568
+ const result = [];
1569
+ let offset = 0;
1570
+ for (const run of runs) {
1571
+ const runEnd = offset + run.text.length;
1572
+ const overlapStart = Math.max(start, offset);
1573
+ const overlapEnd = Math.min(end, runEnd);
1574
+ if (overlapEnd > overlapStart) {
1575
+ result.push({
1576
+ text: run.text.slice(overlapStart - offset, overlapEnd - offset),
1577
+ style: { ...run.style }
1578
+ });
1579
+ }
1580
+ offset = runEnd;
1581
+ if (offset >= end) break;
1582
+ }
1583
+ return result;
1584
+ }
1585
+ function normalizeProjectionRuns(runs) {
1586
+ const normalized = [];
1587
+ for (const run of runs) {
1588
+ if (!run.text && runs.length > 1) continue;
1589
+ const style = { ...run.style };
1590
+ const previous = normalized.at(-1);
1591
+ if (previous && sameProjectionStyle(previous.style, style)) {
1592
+ normalized[normalized.length - 1] = { text: previous.text + run.text, style };
1593
+ } else {
1594
+ normalized.push({ text: run.text, style });
1595
+ }
1596
+ }
1597
+ return normalized.length > 0 ? normalized : [{ text: "", style: {} }];
1598
+ }
1599
+ function projectionStyleAt(runs, offset) {
1600
+ let cursor = 0;
1601
+ for (const run of runs) {
1602
+ const end = cursor + run.text.length;
1603
+ if (offset <= end) return { ...run.style };
1604
+ cursor = end;
1605
+ }
1606
+ return { ...runs.at(-1)?.style ?? {} };
1607
+ }
1608
+ function sameProjectionStyle(left, right) {
1609
+ return left.fontFamily === right.fontFamily && left.fontSizePt === right.fontSizePt && left.color === right.color && left.bold === right.bold && left.italic === right.italic && left.underline === right.underline && left.strike === right.strike;
1610
+ }
1611
+ function validateProjectionPage(page) {
1612
+ const values = [
1613
+ page.widthPt,
1614
+ page.heightPt,
1615
+ page.marginTopPt,
1616
+ page.marginRightPt,
1617
+ page.marginBottomPt,
1618
+ page.marginLeftPt
1619
+ ];
1620
+ if (values.some((value) => !Number.isFinite(value) || value < 0) || page.widthPt <= 0 || page.heightPt <= 0 || page.marginLeftPt + page.marginRightPt >= page.widthPt || page.marginTopPt + page.marginBottomPt >= page.heightPt) {
1621
+ throw new Error("Document projection contains invalid page geometry");
1622
+ }
1623
+ }
1624
+ function isDocumentParagraph(value) {
1625
+ return isDocumentBlockKind(value, "paragraph");
1626
+ }
1627
+ function isDocumentTable(value) {
1628
+ return isDocumentBlockKind(value, "table");
1629
+ }
1630
+ function isDocumentPageBreak(value) {
1631
+ return isDocumentBlockKind(value, "pageBreak");
1632
+ }
1633
+ function isDocumentBlockKind(value, kind) {
1634
+ return typeof value === "object" && value !== null && "kind" in value && value.kind === kind;
1635
+ }
1636
+ function asDocumentView(document2) {
1637
+ return document2;
1638
+ }
1639
+ function mapDocumentCommit(command, externalIdByInternal) {
1640
+ return {
1641
+ ...command,
1642
+ blockId: externalIdByInternal.get(command.blockId) ?? command.blockId
1643
+ };
1644
+ }
1645
+ function identity(value) {
1646
+ return value;
1647
+ }
1648
+ function asDocumentError(cause) {
1649
+ return cause instanceof Error ? cause : new Error("Document change failed");
1650
+ }
1651
+
1652
+ // src/components/artifacts/editable-document.tsx
1653
+ import {
1654
+ DOCUMENT_ARTIFACT_COMMAND_VERSION
1655
+ } from "@opengeni/sdk/editable-artifacts";
1656
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
1657
+ import { jsx as jsx2 } from "react/jsx-runtime";
1658
+ var DOCUMENT_PAGE_ITEMS = 512;
1659
+ var DOCUMENT_MAX_BLOCKS = 4096;
1660
+ var DOCUMENT_MAX_SECTIONS = 1024;
1661
+ var DOCUMENT_MAX_REVIEW_ITEMS = 4096;
1662
+ var DOCUMENT_MAX_TEXT_UTF16 = 1e6;
1663
+ var DOCUMENT_MAX_TABLE_CELLS = 1e5;
1664
+ var DOCUMENT_MAX_QUERY_PAGES = 64;
1665
+ var DOCUMENT_COMPOSITION_ATTEMPTS = 3;
1666
+ function EditableDocumentArtifactSurface({
1667
+ session,
1668
+ title,
1669
+ subtitle,
1670
+ readOnly = false,
1671
+ onCommit,
1672
+ onCommandError,
1673
+ ...surfaceProps
1674
+ }) {
1675
+ const view = useEditableArtifactView(session);
1676
+ const invalidator = editableArtifactProjectionKey(view);
1677
+ const [retryEpoch, setRetryEpoch] = useState2(0);
1678
+ const [state, setState] = useState2({
1679
+ session,
1680
+ projection: null,
1681
+ loading: true,
1682
+ error: null
1683
+ });
1684
+ const loadGeneration = useRef2(0);
1685
+ const commandStateRef = useRef2(null);
1686
+ if (commandStateRef.current?.session !== session) {
1687
+ commandStateRef.current = {
1688
+ session,
1689
+ tail: Promise.resolve(),
1690
+ paragraphText: /* @__PURE__ */ new Map(),
1691
+ localParagraphIds: /* @__PURE__ */ new Set(),
1692
+ optimisticParagraphIds: /* @__PURE__ */ new Map()
1693
+ };
1694
+ }
1695
+ const commandState = commandStateRef.current;
1696
+ useEffect2(() => {
1697
+ const generation = ++loadGeneration.current;
1698
+ let cancelled = false;
1699
+ setState(
1700
+ (current) => current.session === session ? { ...current, loading: true, error: null } : { session, projection: null, loading: true, error: null }
1701
+ );
1702
+ void composeDocumentEditorProjection(session).then(
1703
+ (projection2) => {
1704
+ if (cancelled || generation !== loadGeneration.current || commandStateRef.current !== commandState) {
1705
+ return;
1706
+ }
1707
+ const nextParagraphText = paragraphTextById(projection2);
1708
+ for (const [localId, canonicalId] of commandState.optimisticParagraphIds) {
1709
+ if (nextParagraphText.has(canonicalId)) continue;
1710
+ const optimisticText = commandState.paragraphText.get(canonicalId) ?? commandState.paragraphText.get(localId);
1711
+ if (optimisticText !== void 0) nextParagraphText.set(canonicalId, optimisticText);
1712
+ }
1713
+ commandState.paragraphText = nextParagraphText;
1714
+ setState({ session, projection: projection2, loading: false, error: null });
1715
+ },
1716
+ (cause) => {
1717
+ if (cancelled || generation !== loadGeneration.current || commandStateRef.current !== commandState) {
1718
+ return;
1719
+ }
1720
+ setState({
1721
+ session,
1722
+ projection: null,
1723
+ loading: false,
1724
+ error: asEditableArtifactError(cause, "Could not open this document")
1725
+ });
1726
+ }
1727
+ );
1728
+ return () => {
1729
+ cancelled = true;
1730
+ };
1731
+ }, [commandState, invalidator, retryEpoch, session]);
1732
+ const refresh = useCallback2(() => setRetryEpoch((value) => value + 1), []);
1733
+ const writable = !readOnly && view.writable;
1734
+ const applyCommit = useCallback2(
1735
+ (commit) => {
1736
+ const projection2 = state.session === session ? state.projection : null;
1737
+ if (!projection2) return Promise.reject(new Error("Document projection is not ready"));
1738
+ if (!writable) return Promise.reject(new Error("This document is read only"));
1739
+ if (String(commit.revision) !== String(projection2.revision)) {
1740
+ return Promise.reject(
1741
+ new Error("The document changed; retry this edit on the latest version")
1742
+ );
1743
+ }
1744
+ const execute = async () => {
1745
+ if (commandStateRef.current !== commandState) {
1746
+ throw new Error("The document session changed before this edit could be saved");
1747
+ }
1748
+ const allocateLocalParagraph = async (localId) => {
1749
+ const allocated = commandState.optimisticParagraphIds.get(localId);
1750
+ if (allocated !== void 0) return allocated;
1751
+ if (!commandState.localParagraphIds.has(localId)) {
1752
+ throw new Error("The edited paragraph is no longer available");
1753
+ }
1754
+ const created = await session.createDocumentParagraph();
1755
+ if (commandStateRef.current !== commandState) {
1756
+ throw new Error("The document session changed before this edit could be saved");
1757
+ }
1758
+ commandState.optimisticParagraphIds.set(localId, created.paragraphId);
1759
+ commandState.paragraphText.set(created.paragraphId, "");
1760
+ return created.paragraphId;
1761
+ };
1762
+ if (commit.kind === "insert-paragraph") {
1763
+ commandState.localParagraphIds.add(commit.blockId);
1764
+ await allocateLocalParagraph(commit.blockId);
1765
+ notifyDocumentCommit(onCommit, onCommandError, commit);
1766
+ refresh();
1767
+ return;
1768
+ }
1769
+ const targetId = commandState.localParagraphIds.has(commit.blockId) ? await allocateLocalParagraph(commit.blockId) : commit.blockId;
1770
+ let command = null;
1771
+ let rollbackText = null;
1772
+ if (commit.kind === "text") {
1773
+ const before = commandState.paragraphText.get(targetId);
1774
+ if (before === void 0) {
1775
+ throw new Error("The edited paragraph is no longer available");
1776
+ }
1777
+ const edit = minimalUtf16TextEdit(before, commit.text);
1778
+ if (!edit) return;
1779
+ commandState.paragraphText.set(targetId, commit.text);
1780
+ rollbackText = () => {
1781
+ if (commandState.paragraphText.get(targetId) === commit.text) {
1782
+ commandState.paragraphText.set(targetId, before);
1783
+ }
1784
+ };
1785
+ command = {
1786
+ kind: "paragraph.edit",
1787
+ id: targetId,
1788
+ range: edit.range,
1789
+ replacement: edit.replacement,
1790
+ style: null
1791
+ };
1792
+ } else if (commit.kind === "format") {
1793
+ command = {
1794
+ kind: "paragraph.format",
1795
+ id: targetId,
1796
+ range: commit.range,
1797
+ style: documentTextStylePatch(commit.style)
1798
+ };
1799
+ }
1800
+ if (!command) throw new Error("Unsupported document edit");
1801
+ try {
1802
+ await session.applyDocumentCommands({
1803
+ version: DOCUMENT_ARTIFACT_COMMAND_VERSION,
1804
+ commands: [command]
1805
+ });
1806
+ if (commandStateRef.current !== commandState) return;
1807
+ notifyDocumentCommit(onCommit, onCommandError, commit);
1808
+ refresh();
1809
+ } catch (cause) {
1810
+ rollbackText?.();
1811
+ throw cause;
1812
+ }
1813
+ };
1814
+ const operation = commandState.tail.then(execute, execute);
1815
+ commandState.tail = operation;
1816
+ void operation.then(
1817
+ () => {
1818
+ if (commandState.tail === operation) commandState.tail = Promise.resolve();
1819
+ },
1820
+ () => {
1821
+ if (commandState.tail === operation) commandState.tail = Promise.resolve();
1822
+ }
1823
+ );
1824
+ return operation;
1825
+ },
1826
+ [commandState, onCommandError, onCommit, refresh, session, state, writable]
1827
+ );
1828
+ const projection = state.session === session ? state.projection : null;
1829
+ const status = editableArtifactStatusLabel(view);
1830
+ const accessRevoked = editableArtifactAccessRevoked(view);
1831
+ return projection && !accessRevoked ? /* @__PURE__ */ jsx2(
1832
+ DocumentProjectionArtifactSurface,
1833
+ {
1834
+ ...surfaceProps,
1835
+ title,
1836
+ subtitle: subtitle ?? `${projection.blocks.length} block${projection.blocks.length === 1 ? "" : "s"}${writable ? "" : " \xB7 Read only"}`,
1837
+ projection,
1838
+ commit: writable ? applyCommit : void 0,
1839
+ readOnly: !writable,
1840
+ busy: state.loading,
1841
+ onCommandError
1842
+ }
1843
+ ) : /* @__PURE__ */ jsx2(
1844
+ ArtifactSurface,
1845
+ {
1846
+ modality: "document",
1847
+ title,
1848
+ subtitle: subtitle ?? status,
1849
+ busy: !accessRevoked && !state.error,
1850
+ className: surfaceProps.className,
1851
+ children: /* @__PURE__ */ jsx2(
1852
+ EditableArtifactMessage,
1853
+ {
1854
+ title: accessRevoked ? "Access changed" : state.error ? "Could not open this document" : "Opening document",
1855
+ detail: accessRevoked ? status : state.error?.message ?? status,
1856
+ retry: !accessRevoked && state.error ? refresh : void 0
1857
+ }
1858
+ )
1859
+ }
1860
+ );
1861
+ }
1862
+ async function composeDocumentEditorProjection(session) {
1863
+ if (session.modality !== "document") throw new Error("Expected a document artifact session");
1864
+ let lastRevisionError = null;
1865
+ for (let attempt = 0; attempt < DOCUMENT_COMPOSITION_ATTEMPTS; attempt += 1) {
1866
+ try {
1867
+ return await composeDocumentAttempt(session);
1868
+ } catch (cause) {
1869
+ const error = asEditableArtifactError(cause, "Could not project this document");
1870
+ if (!isRevisionDrift(error) || attempt === DOCUMENT_COMPOSITION_ATTEMPTS - 1) throw error;
1871
+ lastRevisionError = error;
1872
+ }
1873
+ }
1874
+ throw lastRevisionError ?? new Error("Document changed while it was opening");
1875
+ }
1876
+ async function composeDocumentAttempt(session) {
1877
+ const summaryPage = await session.queryDocument({ kind: "summary" });
1878
+ const summary = summaryItem(summaryPage);
1879
+ const revision = summaryPage.revision;
1880
+ if (summary.revision !== revision) throw new Error("Document projection revision drifted");
1881
+ if (summary.blockCount > DOCUMENT_MAX_BLOCKS) {
1882
+ throw new Error(`Document exceeds the interactive limit of ${DOCUMENT_MAX_BLOCKS} blocks`);
1883
+ }
1884
+ if (summary.sectionCount > DOCUMENT_MAX_SECTIONS) {
1885
+ throw new Error(`Document exceeds the interactive limit of ${DOCUMENT_MAX_SECTIONS} sections`);
1886
+ }
1887
+ if (summary.commentCount + summary.trackedChangeCount > DOCUMENT_MAX_REVIEW_ITEMS) {
1888
+ throw new Error(
1889
+ `Document exceeds the interactive limit of ${DOCUMENT_MAX_REVIEW_ITEMS} review items`
1890
+ );
1891
+ }
1892
+ const body = await collectDocumentPages(
1893
+ session,
1894
+ "body",
1895
+ revision,
1896
+ DOCUMENT_MAX_BLOCKS,
1897
+ DOCUMENT_MAX_TEXT_UTF16,
1898
+ DOCUMENT_MAX_TABLE_CELLS,
1899
+ (cursor, limits) => ({ kind: "body", startBlock: cursor, limits })
1900
+ );
1901
+ const sections = await collectDocumentPages(
1902
+ session,
1903
+ "sections",
1904
+ revision,
1905
+ DOCUMENT_MAX_SECTIONS,
1906
+ DOCUMENT_MAX_TEXT_UTF16 - body.text,
1907
+ DOCUMENT_MAX_TABLE_CELLS - body.cells,
1908
+ (cursor, limits) => ({ kind: "sections", startSection: cursor, limits })
1909
+ );
1910
+ const review = await collectDocumentPages(
1911
+ session,
1912
+ "review",
1913
+ revision,
1914
+ DOCUMENT_MAX_REVIEW_ITEMS,
1915
+ DOCUMENT_MAX_TEXT_UTF16 - body.text - sections.text,
1916
+ DOCUMENT_MAX_TABLE_CELLS - body.cells - sections.cells,
1917
+ (cursor, limits) => ({ kind: "review", startItem: cursor, limits })
1918
+ );
1919
+ if (body.items.length !== summary.blockCount) {
1920
+ throw new Error("Document body changed while it was opening");
1921
+ }
1922
+ if (sections.items.length !== summary.sectionCount) {
1923
+ throw new Error("Document sections changed while they were opening");
1924
+ }
1925
+ if (review.items.length !== summary.commentCount + summary.trackedChangeCount) {
1926
+ throw new Error("Document review state changed while it was opening");
1927
+ }
1928
+ const totalText = body.text + sections.text + review.text;
1929
+ const totalCells = body.cells + sections.cells + review.cells;
1930
+ if (totalText > DOCUMENT_MAX_TEXT_UTF16) {
1931
+ throw new Error(
1932
+ `Document exceeds the interactive limit of ${DOCUMENT_MAX_TEXT_UTF16} UTF-16 text units`
1933
+ );
1934
+ }
1935
+ if (totalCells > DOCUMENT_MAX_TABLE_CELLS) {
1936
+ throw new Error(
1937
+ `Document exceeds the interactive limit of ${DOCUMENT_MAX_TABLE_CELLS} table cells`
1938
+ );
1939
+ }
1940
+ return Object.freeze({
1941
+ revision: revision.toString(),
1942
+ page: projectDocumentPage(summary.page),
1943
+ blocks: Object.freeze(body.items.map(projectDocumentBlock)),
1944
+ sections: Object.freeze(sections.items.map(projectDocumentSection)),
1945
+ comments: Object.freeze(
1946
+ review.items.filter(isProjectionKind("comment")).map((item) => ({
1947
+ id: item.id,
1948
+ blockId: item.paragraphId,
1949
+ start: item.range.start,
1950
+ end: item.range.end,
1951
+ resolved: item.resolved,
1952
+ replies: item.replies.map((reply) => ({ ...reply }))
1953
+ }))
1954
+ ),
1955
+ changes: Object.freeze(
1956
+ review.items.filter(isProjectionKind("tracked-change")).map((item) => ({
1957
+ id: item.id,
1958
+ blockId: item.paragraphId,
1959
+ kind: item.changeKind,
1960
+ start: item.range.start,
1961
+ end: item.range.end,
1962
+ author: item.author,
1963
+ createdAt: item.createdAt
1964
+ }))
1965
+ )
1966
+ });
1967
+ }
1968
+ async function collectDocumentPages(session, label, revision, maxItems, maxText, maxCells, query) {
1969
+ const output = { items: [], text: 0, cells: 0 };
1970
+ let cursor = 0;
1971
+ for (let pageIndex = 0; pageIndex < DOCUMENT_MAX_QUERY_PAGES; pageIndex += 1) {
1972
+ if (output.items.length >= maxItems && cursor > 0) {
1973
+ throw new Error(`Document ${label} exceeds its interactive item limit`);
1974
+ }
1975
+ const page = await session.queryDocument(
1976
+ query(cursor, {
1977
+ maxItems: Math.min(DOCUMENT_PAGE_ITEMS, Math.max(1, maxItems - output.items.length)),
1978
+ maxTextUtf16: Math.max(1, maxText - output.text),
1979
+ maxTableCells: Math.max(1, maxCells - output.cells)
1980
+ })
1981
+ );
1982
+ assertDocumentRevision(page, revision);
1983
+ assertDocumentPageKinds(label, page.items);
1984
+ if (output.items.length + page.items.length > maxItems) {
1985
+ throw new Error(`Document ${label} exceeds its interactive item limit`);
1986
+ }
1987
+ output.items.push(...page.items);
1988
+ output.text += page.projectedTextUtf16;
1989
+ output.cells += page.projectedTableCells;
1990
+ if (output.text > maxText || output.cells > maxCells) {
1991
+ throw new Error(`Document ${label} exceeds its interactive content limit`);
1992
+ }
1993
+ if (page.nextCursor === null) return output;
1994
+ if (page.nextCursor <= cursor || page.items.length === 0) {
1995
+ throw new Error(`Document ${label} pagination did not advance`);
1996
+ }
1997
+ cursor = page.nextCursor;
1998
+ }
1999
+ throw new Error(`Document ${label} exceeds ${DOCUMENT_MAX_QUERY_PAGES} query pages`);
2000
+ }
2001
+ function summaryItem(projection) {
2002
+ if (projection.items.length !== 1 || projection.items[0]?.kind !== "summary" || projection.nextCursor !== null || projection.truncated) {
2003
+ throw new Error("Document summary projection is malformed");
2004
+ }
2005
+ return projection.items[0];
2006
+ }
2007
+ function assertDocumentRevision(page, revision) {
2008
+ if (page.revision !== revision) throw new Error("Document projection revision drifted");
2009
+ }
2010
+ function assertDocumentPageKinds(label, items) {
2011
+ const valid = label === "body" ? /* @__PURE__ */ new Set(["paragraph", "table", "page-break"]) : label === "sections" ? /* @__PURE__ */ new Set(["section"]) : /* @__PURE__ */ new Set(["comment", "tracked-change"]);
2012
+ if (items.some((item) => !valid.has(item.kind))) {
2013
+ throw new Error(`Document ${label} projection contains an unexpected item`);
2014
+ }
2015
+ }
2016
+ function projectDocumentBlock(item) {
2017
+ if (item.kind === "paragraph") {
2018
+ const { headingLevel, list, ...paragraphStyle } = item.style;
2019
+ return {
2020
+ kind: "paragraph",
2021
+ id: item.id,
2022
+ runs: item.runs.map((run) => ({ text: run.text, style: { ...run.style } })),
2023
+ style: {
2024
+ ...paragraphStyle,
2025
+ ...headingLevel === void 0 ? {} : { headingLevel: documentHeadingLevel(headingLevel) },
2026
+ ...list ? {
2027
+ list: {
2028
+ kind: list.kind,
2029
+ ...list.level === null ? {} : { level: list.level },
2030
+ ...list.instanceId === null ? {} : { instanceId: list.instanceId }
2031
+ }
2032
+ } : {}
2033
+ }
2034
+ };
2035
+ }
2036
+ if (item.kind === "table") {
2037
+ return {
2038
+ kind: "table",
2039
+ id: item.id,
2040
+ rows: item.rows.map(
2041
+ (row) => row.map((cell) => cell.map((run) => ({ text: run.text, style: { ...run.style } })))
2042
+ ),
2043
+ style: { ...item.style }
2044
+ };
2045
+ }
2046
+ if (item.kind === "page-break") return { kind: "pageBreak", id: item.id };
2047
+ throw new Error("Document body projection contains an unexpected item");
2048
+ }
2049
+ function documentHeadingLevel(value) {
2050
+ if (value < 1 || value > 6 || !Number.isInteger(value)) {
2051
+ throw new Error("Document projection contains an invalid heading level");
2052
+ }
2053
+ return value;
2054
+ }
2055
+ function projectDocumentSection(item) {
2056
+ if (item.kind !== "section") {
2057
+ throw new Error("Document section projection contains an unexpected item");
2058
+ }
2059
+ return {
2060
+ id: item.id,
2061
+ startBlockIndex: item.startBlockIndex,
2062
+ page: projectDocumentPage(item.page),
2063
+ titlePage: item.titlePage,
2064
+ headerBlockCounts: item.headerBlockCounts,
2065
+ footerBlockCounts: item.footerBlockCounts
2066
+ };
2067
+ }
2068
+ function projectDocumentPage(page) {
2069
+ const points = (value) => Number(value) / 1e3;
2070
+ return {
2071
+ widthPt: points(page.widthMillipoints),
2072
+ heightPt: points(page.heightMillipoints),
2073
+ marginTopPt: points(page.marginTopMillipoints),
2074
+ marginRightPt: points(page.marginRightMillipoints),
2075
+ marginBottomPt: points(page.marginBottomMillipoints),
2076
+ marginLeftPt: points(page.marginLeftMillipoints),
2077
+ headerPt: points(page.headerMillipoints),
2078
+ footerPt: points(page.footerMillipoints),
2079
+ gutterPt: points(page.gutterMillipoints)
2080
+ };
2081
+ }
2082
+ function isProjectionKind(kind) {
2083
+ return (item) => item.kind === kind;
2084
+ }
2085
+ function paragraphTextById(projection) {
2086
+ return new Map(
2087
+ projection.blocks.filter((block) => block.kind === "paragraph").map((paragraph) => [paragraph.id, paragraph.runs.map((run) => run.text).join("")])
2088
+ );
2089
+ }
2090
+ function minimalUtf16TextEdit(before, after) {
2091
+ if (before === after) return null;
2092
+ let start = 0;
2093
+ const maximumStart = Math.min(before.length, after.length);
2094
+ while (start < maximumStart && before.charCodeAt(start) === after.charCodeAt(start)) start += 1;
2095
+ while (start > 0 && (splitsSurrogate(before, start) || splitsSurrogate(after, start))) start -= 1;
2096
+ let suffix = 0;
2097
+ while (suffix < before.length - start && suffix < after.length - start && before.charCodeAt(before.length - suffix - 1) === after.charCodeAt(after.length - suffix - 1)) {
2098
+ suffix += 1;
2099
+ }
2100
+ while (suffix > 0 && (splitsSurrogate(before, before.length - suffix) || splitsSurrogate(after, after.length - suffix))) {
2101
+ suffix -= 1;
2102
+ }
2103
+ return {
2104
+ range: { start, end: before.length - suffix },
2105
+ replacement: after.slice(start, after.length - suffix)
2106
+ };
2107
+ }
2108
+ function splitsSurrogate(value, index) {
2109
+ if (index <= 0 || index >= value.length) return false;
2110
+ const left = value.charCodeAt(index - 1);
2111
+ const right = value.charCodeAt(index);
2112
+ return left >= 55296 && left <= 56319 && right >= 56320 && right <= 57343;
2113
+ }
2114
+ function documentTextStylePatch(style) {
2115
+ return {
2116
+ ...style.fontFamily === void 0 ? {} : { fontFamily: style.fontFamily },
2117
+ ...style.fontSizePt === void 0 ? {} : { fontSizePt: style.fontSizePt },
2118
+ ...style.color === void 0 ? {} : { color: style.color },
2119
+ ...style.bold === void 0 ? {} : { bold: style.bold },
2120
+ ...style.italic === void 0 ? {} : { italic: style.italic },
2121
+ ...style.underline === void 0 ? {} : { underline: style.underline },
2122
+ ...style.strike === void 0 ? {} : { strike: style.strike }
2123
+ };
2124
+ }
2125
+ function isRevisionDrift(error) {
2126
+ return error.message.includes("changed while") || error.message.includes("revision drifted");
2127
+ }
2128
+ function notifyDocumentCommit(observer, onError, commit) {
2129
+ try {
2130
+ const result = observer?.(commit);
2131
+ if (result && typeof result.then === "function") {
2132
+ void Promise.resolve(result).catch(
2133
+ (cause) => onError?.(asEditableArtifactError(cause, "Document commit observer failed"))
2134
+ );
2135
+ }
2136
+ } catch (cause) {
2137
+ onError?.(asEditableArtifactError(cause, "Document commit observer failed"));
2138
+ }
2139
+ }
2140
+
2141
+ export {
2142
+ DocumentEditor,
2143
+ DocumentProjectionEditor,
2144
+ DocumentArtifactSurface,
2145
+ DocumentProjectionArtifactSurface,
2146
+ EditableDocumentArtifactSurface
2147
+ };
2148
+ //# sourceMappingURL=chunk-HUIKIDM5.js.map