@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,2828 @@
1
+ import {
2
+ ArtifactSurface,
3
+ EditableArtifactMessage,
4
+ editableArtifactAccessRevoked,
5
+ editableArtifactStatusLabel,
6
+ useEditableArtifactView
7
+ } from "./chunk-KCGXPP5V.js";
8
+ import {
9
+ cn
10
+ } from "./chunk-22KP6LR5.js";
11
+
12
+ // src/components/artifacts/spreadsheet-grid.tsx
13
+ import { PlusIcon } from "lucide-react";
14
+ import {
15
+ useCallback,
16
+ useEffect,
17
+ useId,
18
+ useLayoutEffect,
19
+ useMemo,
20
+ useRef,
21
+ useState
22
+ } from "react";
23
+
24
+ // src/components/artifacts/spreadsheet-canvas.ts
25
+ var TILE_SIZE = 256;
26
+ var MAX_CACHE_PHYSICAL_PIXELS = 32e6;
27
+ var MAX_CACHE_TILES = 96;
28
+ var MAX_DEVICE_PIXEL_RATIO = 4;
29
+ var MAX_AXIS_MARKS_PER_PAINT = 8192;
30
+ var MAX_CANVAS_EDGE = 8192;
31
+ var MAX_CANVAS_PIXELS = 16e6;
32
+ var MAX_TILES_PER_PAINT = 512;
33
+ var SparseSpreadsheetCellIndex = class {
34
+ size;
35
+ rows;
36
+ constructor(entries) {
37
+ const sorted = [...entries].sort((left, right) => left.row - right.row || left.col - right.col);
38
+ this.size = sorted.length;
39
+ this.rows = [];
40
+ for (const entry of sorted) {
41
+ const previous = this.rows.at(-1);
42
+ if (previous?.row === entry.row) previous.cells.push(entry);
43
+ else this.rows.push({ row: entry.row, cells: [entry] });
44
+ }
45
+ }
46
+ forEachInRange(rowStart, rowEnd, columnStart, columnEnd, visit) {
47
+ let rowIndex = lowerBound(this.rows, rowStart, (row) => row.row);
48
+ while (rowIndex < this.rows.length) {
49
+ const row = this.rows[rowIndex];
50
+ if (row.row >= rowEnd) break;
51
+ let cellIndex = lowerBound(row.cells, columnStart, (cell) => cell.col);
52
+ while (cellIndex < row.cells.length) {
53
+ const cell = row.cells[cellIndex];
54
+ if (cell.col >= columnEnd) break;
55
+ visit(cell);
56
+ cellIndex += 1;
57
+ }
58
+ rowIndex += 1;
59
+ }
60
+ }
61
+ };
62
+ function lowerBound(values, target, valueAt) {
63
+ let low = 0;
64
+ let high = values.length;
65
+ while (low < high) {
66
+ const middle = low + high >>> 1;
67
+ if (valueAt(values[middle]) < target) low = middle + 1;
68
+ else high = middle;
69
+ }
70
+ return low;
71
+ }
72
+ function coverageContains(coverage, query) {
73
+ return coverage === void 0 || coverage.rowStart <= query.rowStart && coverage.rowEnd >= query.rowEnd && coverage.columnStart <= query.columnStart && coverage.columnEnd >= query.columnEnd;
74
+ }
75
+ function intersectCoverage(query, coverage) {
76
+ if (coverage === void 0) return query;
77
+ const intersection = {
78
+ rowStart: Math.max(query.rowStart, coverage.rowStart),
79
+ rowEnd: Math.min(query.rowEnd, coverage.rowEnd),
80
+ columnStart: Math.max(query.columnStart, coverage.columnStart),
81
+ columnEnd: Math.min(query.columnEnd, coverage.columnEnd)
82
+ };
83
+ return intersection.rowStart < intersection.rowEnd && intersection.columnStart < intersection.columnEnd ? intersection : null;
84
+ }
85
+ function spreadsheetCanvasDevicePixelRatio(value) {
86
+ if (!Number.isFinite(value) || value <= 0) return 1;
87
+ return Math.max(1, Math.min(MAX_DEVICE_PIXEL_RATIO, value));
88
+ }
89
+ function spreadsheetCanvasBackingScale(width, height, devicePixelRatio) {
90
+ const safeWidth = finiteExtent(width);
91
+ const safeHeight = finiteExtent(height);
92
+ const requested = spreadsheetCanvasDevicePixelRatio(devicePixelRatio);
93
+ return Math.max(
94
+ Number.EPSILON,
95
+ Math.min(
96
+ requested,
97
+ MAX_CANVAS_EDGE / safeWidth,
98
+ MAX_CANVAS_EDGE / safeHeight,
99
+ Math.sqrt(MAX_CANVAS_PIXELS / (safeWidth * safeHeight))
100
+ )
101
+ );
102
+ }
103
+ var SpreadsheetCanvasRenderer = class {
104
+ tiles = /* @__PURE__ */ new Map();
105
+ generation = "";
106
+ clock = 0;
107
+ dispose() {
108
+ this.tiles.clear();
109
+ this.generation = "";
110
+ }
111
+ paint(input) {
112
+ const context = safeCanvasContext(input.canvas);
113
+ if (!context) return null;
114
+ const width = finiteExtent(input.viewportWidth);
115
+ const height = finiteExtent(input.viewportHeight);
116
+ const dpr = spreadsheetCanvasBackingScale(width, height, input.devicePixelRatio);
117
+ const pixelWidth = Math.max(1, Math.min(MAX_CANVAS_EDGE, Math.round(width * dpr)));
118
+ const pixelHeight = Math.max(1, Math.min(MAX_CANVAS_EDGE, Math.round(height * dpr)));
119
+ if (input.canvas.width !== pixelWidth) input.canvas.width = pixelWidth;
120
+ if (input.canvas.height !== pixelHeight) input.canvas.height = pixelHeight;
121
+ input.canvas.style.width = `${width}px`;
122
+ input.canvas.style.height = `${height}px`;
123
+ context.setTransform(dpr, 0, 0, dpr, 0, 0);
124
+ context.clearRect(0, 0, width, height);
125
+ const generation = [
126
+ input.projection.sheetId,
127
+ input.projection.generationId ?? "<none>",
128
+ input.projection.revision,
129
+ input.dimensionRevision,
130
+ dpr,
131
+ input.theme.signature
132
+ ].join("/");
133
+ if (generation !== this.generation) {
134
+ this.tiles.clear();
135
+ this.generation = generation;
136
+ }
137
+ setFill(context, input.theme.background, "#ffffff");
138
+ context.fillRect(0, 0, width, height);
139
+ const bodyLeft = input.rowHeaderWidth;
140
+ const bodyTop = input.columnHeaderHeight;
141
+ const visibleLeft = clamp(input.logicalScrollLeft, 0, input.columns.total);
142
+ const visibleTop = clamp(input.logicalScrollTop, 0, input.rows.total);
143
+ const visibleRight = clamp(
144
+ input.logicalScrollLeft + width - bodyLeft,
145
+ visibleLeft,
146
+ input.columns.total
147
+ );
148
+ const visibleBottom = clamp(
149
+ input.logicalScrollTop + height - bodyTop,
150
+ visibleTop,
151
+ input.rows.total
152
+ );
153
+ let paintedTiles = 0;
154
+ let reusedTiles = 0;
155
+ let uncachedTiles = 0;
156
+ context.save();
157
+ context.beginPath();
158
+ context.rect(bodyLeft, bodyTop, Math.max(0, width - bodyLeft), Math.max(0, height - bodyTop));
159
+ context.clip();
160
+ const firstTileColumn = Math.floor(visibleLeft / TILE_SIZE);
161
+ const lastTileColumn = Math.floor(
162
+ Math.max(visibleLeft, visibleRight - Number.EPSILON) / TILE_SIZE
163
+ );
164
+ const firstTileRow = Math.floor(visibleTop / TILE_SIZE);
165
+ const lastTileRow = Math.floor(
166
+ Math.max(visibleTop, visibleBottom - Number.EPSILON) / TILE_SIZE
167
+ );
168
+ let visitedTiles = 0;
169
+ tileLoop: for (let tileRow = firstTileRow; tileRow <= lastTileRow; tileRow += 1) {
170
+ for (let tileColumn = firstTileColumn; tileColumn <= lastTileColumn; tileColumn += 1) {
171
+ if (visitedTiles >= MAX_TILES_PER_PAINT) break tileLoop;
172
+ visitedTiles += 1;
173
+ const key = `${tileColumn}:${tileRow}`;
174
+ let tile = this.tiles.get(key);
175
+ if (!tile) {
176
+ const painted = this.paintTile(input, tileColumn, tileRow, dpr);
177
+ tile = {
178
+ canvas: painted.canvas,
179
+ lastUsed: ++this.clock
180
+ };
181
+ if (painted.cacheable) this.tiles.set(key, tile);
182
+ else uncachedTiles += 1;
183
+ paintedTiles += 1;
184
+ } else {
185
+ tile.lastUsed = ++this.clock;
186
+ reusedTiles += 1;
187
+ }
188
+ const logicalX = tileColumn * TILE_SIZE;
189
+ const logicalY = tileRow * TILE_SIZE;
190
+ context.drawImage(
191
+ tile.canvas,
192
+ bodyLeft + logicalX - input.logicalScrollLeft,
193
+ bodyTop + logicalY - input.logicalScrollTop,
194
+ TILE_SIZE,
195
+ TILE_SIZE
196
+ );
197
+ }
198
+ }
199
+ paintSelection(context, input);
200
+ paintVisibleGrid(context, input, visibleLeft, visibleTop, visibleRight, visibleBottom);
201
+ context.restore();
202
+ paintHeaders(
203
+ context,
204
+ input,
205
+ width,
206
+ height,
207
+ visibleLeft,
208
+ visibleTop,
209
+ visibleRight,
210
+ visibleBottom
211
+ );
212
+ this.evictTiles(dpr);
213
+ return {
214
+ cacheSize: this.tiles.size,
215
+ paintedTiles,
216
+ reusedTiles,
217
+ uncachedTiles,
218
+ devicePixelRatio: dpr
219
+ };
220
+ }
221
+ paintTile(input, tileColumn, tileRow, dpr) {
222
+ const tile = input.canvas.ownerDocument.createElement("canvas");
223
+ tile.width = Math.max(1, Math.round(TILE_SIZE * dpr));
224
+ tile.height = Math.max(1, Math.round(TILE_SIZE * dpr));
225
+ const context = safeCanvasContext(tile);
226
+ if (!context) return { canvas: tile, cacheable: false };
227
+ context.setTransform(dpr, 0, 0, dpr, 0, 0);
228
+ setFill(context, input.theme.background, "#ffffff");
229
+ context.fillRect(0, 0, TILE_SIZE, TILE_SIZE);
230
+ const tileLeft = tileColumn * TILE_SIZE;
231
+ const tileTop = tileRow * TILE_SIZE;
232
+ const tileRight = Math.min(input.columns.total, tileLeft + TILE_SIZE);
233
+ const tileBottom = Math.min(input.rows.total, tileTop + TILE_SIZE);
234
+ const firstColumn = input.columns.indexAtOffset(tileLeft);
235
+ const lastColumn = input.columns.indexAtOffset(Math.max(tileLeft, tileRight - Number.EPSILON));
236
+ const firstRow = input.rows.indexAtOffset(tileTop);
237
+ const lastRow = input.rows.indexAtOffset(Math.max(tileTop, tileBottom - Number.EPSILON));
238
+ const query = {
239
+ rowStart: firstRow,
240
+ rowEnd: Math.min(input.rows.count, lastRow + 1),
241
+ columnStart: firstColumn,
242
+ columnEnd: Math.min(input.columns.count, lastColumn + 1)
243
+ };
244
+ const coveredQuery = intersectCoverage(query, input.projection.coverage);
245
+ const cacheable = coverageContains(input.projection.coverage, query);
246
+ const cells = [];
247
+ if (coveredQuery) {
248
+ input.projection.cells.forEachInRange(
249
+ coveredQuery.rowStart,
250
+ coveredQuery.rowEnd,
251
+ coveredQuery.columnStart,
252
+ coveredQuery.columnEnd,
253
+ (cell) => cells.push(cell)
254
+ );
255
+ }
256
+ for (const cell of cells) {
257
+ const fill = cell.format.fill;
258
+ if (!fill) continue;
259
+ const x = input.columns.offsetAt(cell.col) - tileLeft;
260
+ const y = input.rows.offsetAt(cell.row) - tileTop;
261
+ const width = input.columns.sizeAt(cell.col);
262
+ const height = input.rows.sizeAt(cell.row);
263
+ setFill(context, fill, input.theme.background);
264
+ context.fillRect(x, y, width, height);
265
+ }
266
+ paintTileGrid(context, input, tileLeft, tileTop, tileRight, tileBottom);
267
+ for (const cell of cells) {
268
+ const value = input.projection.valueAt(cell);
269
+ const text = displayValue(value);
270
+ if (!text) continue;
271
+ paintCellText(context, input, cell, text, tileLeft, tileTop);
272
+ }
273
+ return { canvas: tile, cacheable };
274
+ }
275
+ evictTiles(dpr) {
276
+ const pixelsPerTile = Math.max(1, Math.round(TILE_SIZE * dpr)) ** 2;
277
+ const maximum = Math.max(
278
+ 4,
279
+ Math.min(MAX_CACHE_TILES, Math.floor(MAX_CACHE_PHYSICAL_PIXELS / pixelsPerTile))
280
+ );
281
+ if (this.tiles.size <= maximum) return;
282
+ const oldest = [...this.tiles.entries()].sort(
283
+ (left, right) => left[1].lastUsed - right[1].lastUsed
284
+ );
285
+ for (let index = 0; index < oldest.length - maximum; index += 1) {
286
+ this.tiles.delete(oldest[index][0]);
287
+ }
288
+ }
289
+ };
290
+ function finiteExtent(value) {
291
+ if (!Number.isFinite(value) || value <= 0) return 1;
292
+ return Math.max(1, Math.floor(value));
293
+ }
294
+ function safeCanvasContext(canvas) {
295
+ try {
296
+ return canvas.getContext("2d", { alpha: false });
297
+ } catch {
298
+ return null;
299
+ }
300
+ }
301
+ function setFill(context, value, fallback) {
302
+ context.fillStyle = fallback;
303
+ try {
304
+ context.fillStyle = value;
305
+ } catch {
306
+ }
307
+ }
308
+ function setStroke(context, value, fallback) {
309
+ context.strokeStyle = fallback;
310
+ try {
311
+ context.strokeStyle = value;
312
+ } catch {
313
+ }
314
+ }
315
+ function paintTileGrid(context, input, tileLeft, tileTop, tileRight, tileBottom) {
316
+ setFill(context, input.theme.border, "#d1d5db");
317
+ visitAxisBoundaries(input.columns, tileLeft, tileRight, (offset) => {
318
+ context.fillRect(offset - tileLeft - 1, 0, 1, TILE_SIZE);
319
+ });
320
+ visitAxisBoundaries(input.rows, tileTop, tileBottom, (offset) => {
321
+ context.fillRect(0, offset - tileTop - 1, TILE_SIZE, 1);
322
+ });
323
+ }
324
+ function paintVisibleGrid(context, input, visibleLeft, visibleTop, visibleRight, visibleBottom) {
325
+ setFill(context, input.theme.border, "#d1d5db");
326
+ visitAxisBoundaries(input.columns, visibleLeft, visibleRight, (offset) => {
327
+ const x = input.rowHeaderWidth + offset - input.logicalScrollLeft;
328
+ context.fillRect(x - 1, input.columnHeaderHeight, 1, input.viewportHeight);
329
+ });
330
+ visitAxisBoundaries(input.rows, visibleTop, visibleBottom, (offset) => {
331
+ const y = input.columnHeaderHeight + offset - input.logicalScrollTop;
332
+ context.fillRect(input.rowHeaderWidth, y - 1, input.viewportWidth, 1);
333
+ });
334
+ }
335
+ function visitAxisBoundaries(axis, start, end, visit) {
336
+ if (axis.count <= 0 || end < start) return;
337
+ let index = axis.indexAtOffset(start);
338
+ let visits = 0;
339
+ while (index < axis.count && visits < MAX_AXIS_MARKS_PER_PAINT) {
340
+ const boundary = axis.offsetAt(index + 1);
341
+ if (boundary > end + 1) break;
342
+ if (boundary >= start) visit(boundary);
343
+ index += 1;
344
+ visits += 1;
345
+ }
346
+ }
347
+ function paintSelection(context, input) {
348
+ const { selection } = input;
349
+ const left = input.rowHeaderWidth + input.columns.offsetAt(selection.left) - input.logicalScrollLeft;
350
+ const right = input.rowHeaderWidth + input.columns.offsetAt(selection.right + 1) - input.logicalScrollLeft;
351
+ const top = input.columnHeaderHeight + input.rows.offsetAt(selection.top) - input.logicalScrollTop;
352
+ const bottom = input.columnHeaderHeight + input.rows.offsetAt(selection.bottom + 1) - input.logicalScrollTop;
353
+ context.save();
354
+ setFill(context, input.theme.accent, "#3b82f6");
355
+ context.globalAlpha = 0.1;
356
+ context.fillRect(left, top, right - left, bottom - top);
357
+ context.restore();
358
+ const activeLeft = input.rowHeaderWidth + input.columns.offsetAt(selection.focusColumn) - input.logicalScrollLeft;
359
+ const activeTop = input.columnHeaderHeight + input.rows.offsetAt(selection.focusRow) - input.logicalScrollTop;
360
+ const activeWidth = input.columns.sizeAt(selection.focusColumn);
361
+ const activeHeight = input.rows.sizeAt(selection.focusRow);
362
+ context.save();
363
+ setStroke(context, input.theme.accent, "#3b82f6");
364
+ context.lineWidth = 2;
365
+ context.strokeRect(
366
+ activeLeft + 1,
367
+ activeTop + 1,
368
+ Math.max(0, activeWidth - 2),
369
+ Math.max(0, activeHeight - 2)
370
+ );
371
+ context.restore();
372
+ }
373
+ function paintHeaders(context, input, width, height, visibleLeft, visibleTop, visibleRight, visibleBottom) {
374
+ setFill(context, input.theme.headerBackground, "#f3f4f6");
375
+ context.fillRect(0, 0, width, input.columnHeaderHeight);
376
+ context.fillRect(0, input.columnHeaderHeight, input.rowHeaderWidth, height);
377
+ setFill(context, input.theme.border, "#d1d5db");
378
+ context.fillRect(0, input.columnHeaderHeight - 1, width, 1);
379
+ context.fillRect(input.rowHeaderWidth - 1, 0, 1, height);
380
+ context.save();
381
+ context.beginPath();
382
+ context.rect(
383
+ input.rowHeaderWidth,
384
+ 0,
385
+ Math.max(0, width - input.rowHeaderWidth),
386
+ input.columnHeaderHeight
387
+ );
388
+ context.clip();
389
+ setFill(context, input.theme.mutedForeground, "#6b7280");
390
+ context.font = `600 ${input.theme.headerFontSize}px ${input.theme.fontFamily}`;
391
+ context.textAlign = "center";
392
+ context.textBaseline = "middle";
393
+ visitAxisItems(input.columns, visibleLeft, visibleRight, (column, offset, size) => {
394
+ const x = input.rowHeaderWidth + offset - input.logicalScrollLeft;
395
+ setFill(context, input.theme.border, "#d1d5db");
396
+ context.fillRect(x + size - 1, 0, 1, input.columnHeaderHeight);
397
+ if (size >= 8) {
398
+ setFill(context, input.theme.mutedForeground, "#6b7280");
399
+ context.fillText(columnName(column), x + size / 2, input.columnHeaderHeight / 2);
400
+ }
401
+ });
402
+ context.restore();
403
+ context.save();
404
+ context.beginPath();
405
+ context.rect(
406
+ 0,
407
+ input.columnHeaderHeight,
408
+ input.rowHeaderWidth,
409
+ Math.max(0, height - input.columnHeaderHeight)
410
+ );
411
+ context.clip();
412
+ setFill(context, input.theme.mutedForeground, "#6b7280");
413
+ context.font = `400 ${input.theme.headerFontSize}px ${input.theme.fontFamily}`;
414
+ context.textAlign = "center";
415
+ context.textBaseline = "middle";
416
+ visitAxisItems(input.rows, visibleTop, visibleBottom, (row, offset, size) => {
417
+ const y = input.columnHeaderHeight + offset - input.logicalScrollTop;
418
+ setFill(context, input.theme.border, "#d1d5db");
419
+ context.fillRect(0, y + size - 1, input.rowHeaderWidth, 1);
420
+ if (size >= 8) {
421
+ setFill(context, input.theme.mutedForeground, "#6b7280");
422
+ context.fillText(String(row + 1), input.rowHeaderWidth / 2, y + size / 2);
423
+ }
424
+ });
425
+ context.restore();
426
+ }
427
+ function visitAxisItems(axis, start, end, visit) {
428
+ if (axis.count <= 0 || end < start) return;
429
+ let index = axis.indexAtOffset(start);
430
+ let visits = 0;
431
+ while (index < axis.count && visits < MAX_AXIS_MARKS_PER_PAINT) {
432
+ const offset = axis.offsetAt(index);
433
+ if (offset > end) break;
434
+ visit(index, offset, axis.sizeAt(index));
435
+ index += 1;
436
+ visits += 1;
437
+ }
438
+ }
439
+ function paintCellText(context, input, cell, text, tileLeft, tileTop) {
440
+ const format = cell.format;
441
+ const x = input.columns.offsetAt(cell.col) - tileLeft;
442
+ const y = input.rows.offsetAt(cell.row) - tileTop;
443
+ const width = input.columns.sizeAt(cell.col);
444
+ const height = input.rows.sizeAt(cell.row);
445
+ if (width <= 2 || height <= 2) return;
446
+ const fontSize = finitePositive(format.font?.size, input.theme.fontSize);
447
+ const fontFamily = format.font?.name || input.theme.fontFamily;
448
+ const weight = format.font?.bold ? 600 : 400;
449
+ const italic = format.font?.italic ? "italic " : "";
450
+ context.save();
451
+ context.beginPath();
452
+ context.rect(x + 1, y + 1, Math.max(0, width - 2), Math.max(0, height - 2));
453
+ context.clip();
454
+ context.font = `${italic}${weight} ${fontSize}px ${fontFamily}`;
455
+ setFill(
456
+ context,
457
+ format.font?.color ?? (text.startsWith("#") ? input.theme.error : input.theme.foreground),
458
+ input.theme.foreground
459
+ );
460
+ const alignment = format.horizontalAlignment;
461
+ context.textAlign = alignment === "center" ? "center" : alignment === "right" ? "right" : "left";
462
+ context.textBaseline = "alphabetic";
463
+ const padding = 6;
464
+ const textX = alignment === "center" ? x + width / 2 : alignment === "right" ? x + width - padding : x + padding;
465
+ const lineHeight = fontSize * 1.5;
466
+ const availableWidth = Math.max(0, width - padding * 2);
467
+ const lines = format.wrapText ? wrapText(context, text, availableWidth, Math.max(1, Math.floor(height / lineHeight))) : [ellipsize(context, text.replaceAll(/\r?\n/g, " "), availableWidth)];
468
+ const blockHeight = lines.length * lineHeight;
469
+ const vertical = format.verticalAlignment;
470
+ const firstBaseline = vertical === "top" ? y + Math.min(height, padding + fontSize) : vertical === "bottom" ? y + height - padding - blockHeight + fontSize : y + (height - blockHeight) / 2 + fontSize;
471
+ for (let index = 0; index < lines.length; index += 1) {
472
+ const line = lines[index];
473
+ const baseline = firstBaseline + index * lineHeight;
474
+ context.fillText(line, textX, baseline);
475
+ if (format.font?.underline && line) {
476
+ const metrics = context.measureText(line);
477
+ const textWidth = metrics.width;
478
+ const startX = alignment === "center" ? textX - textWidth / 2 : alignment === "right" ? textX - textWidth : textX;
479
+ setFill(context, format.font.color ?? input.theme.foreground, input.theme.foreground);
480
+ context.fillRect(startX, baseline + 1, textWidth, Math.max(1, fontSize / 14));
481
+ }
482
+ }
483
+ context.restore();
484
+ }
485
+ function wrapText(context, input, maximumWidth, maximumLines) {
486
+ if (maximumWidth <= 0) return [""];
487
+ const output = [];
488
+ for (const paragraph of input.split(/\r?\n/)) {
489
+ const words = paragraph.split(/\s+/).filter(Boolean);
490
+ if (words.length === 0) output.push("");
491
+ let line = "";
492
+ for (const word of words) {
493
+ const next = line ? `${line} ${word}` : word;
494
+ if (!line || context.measureText(next).width <= maximumWidth) line = next;
495
+ else {
496
+ output.push(ellipsize(context, line, maximumWidth));
497
+ line = word;
498
+ }
499
+ if (output.length >= maximumLines) break;
500
+ }
501
+ if (output.length < maximumLines && line) output.push(ellipsize(context, line, maximumWidth));
502
+ if (output.length >= maximumLines) break;
503
+ }
504
+ return output.slice(0, maximumLines);
505
+ }
506
+ function ellipsize(context, input, maximumWidth) {
507
+ if (!input || maximumWidth <= 0) return "";
508
+ if (context.measureText(input).width <= maximumWidth) return input;
509
+ const suffix = "\u2026";
510
+ const suffixWidth = context.measureText(suffix).width;
511
+ if (suffixWidth > maximumWidth) return "";
512
+ let low = 0;
513
+ let high = input.length;
514
+ while (low < high) {
515
+ const middle = Math.ceil((low + high) / 2);
516
+ if (context.measureText(`${input.slice(0, middle)}${suffix}`).width <= maximumWidth)
517
+ low = middle;
518
+ else high = middle - 1;
519
+ }
520
+ return `${input.slice(0, low)}${suffix}`;
521
+ }
522
+ function finitePositive(value, fallback) {
523
+ return value !== void 0 && Number.isFinite(value) && value > 0 ? value : fallback;
524
+ }
525
+ function displayValue(value) {
526
+ if (value === null || value === void 0) return "";
527
+ if (value instanceof Date) return Number.isNaN(value.getTime()) ? "" : value.toLocaleDateString();
528
+ if (typeof value === "boolean") return value ? "TRUE" : "FALSE";
529
+ return String(value);
530
+ }
531
+ function columnName(index) {
532
+ let value = index + 1;
533
+ let result = "";
534
+ while (value > 0) {
535
+ const remainder = (value - 1) % 26;
536
+ result = String.fromCharCode(65 + remainder) + result;
537
+ value = Math.floor((value - 1) / 26);
538
+ }
539
+ return result;
540
+ }
541
+ function clamp(value, minimum, maximum) {
542
+ return Math.max(minimum, Math.min(maximum, value));
543
+ }
544
+
545
+ // src/components/artifacts/spreadsheet-grid.tsx
546
+ import { jsx, jsxs } from "react/jsx-runtime";
547
+ var MAX_ROWS = 1048576;
548
+ var MAX_COLUMNS = 16384;
549
+ var ROW_HEADER_WIDTH = 48;
550
+ var COLUMN_HEADER_HEIGHT = 28;
551
+ var FALLBACK_VIEWPORT_WIDTH = 880;
552
+ var FALLBACK_VIEWPORT_HEIGHT = 480;
553
+ var MAX_PHYSICAL_SCROLL_SPAN = 8e6;
554
+ var MIN_PHYSICAL_ITEM_SPAN = 4;
555
+ var MAX_MOUNTED_AXIS_ITEMS = 512;
556
+ var MAX_MOUNTED_GRID_CELLS = 2048;
557
+ var MAX_CLIPBOARD_INPUT_BYTES = 4 * 1024 * 1024;
558
+ var MAX_CLIPBOARD_CELLS = 65536;
559
+ var useClientLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
560
+ function boundedInteger(value, fallback, max) {
561
+ if (value === void 0) return fallback;
562
+ if (!Number.isFinite(value)) return fallback;
563
+ return Math.min(max, Math.max(1, Math.floor(value)));
564
+ }
565
+ function buildAxis(count, defaultSize, entries) {
566
+ const normalizedDefault = Number.isFinite(defaultSize) && defaultSize > 0 ? defaultSize : 1;
567
+ const sorted = [...entries].filter(
568
+ ([index, size]) => Number.isInteger(index) && index >= 0 && index < count && Number.isFinite(size) && size > 0
569
+ ).sort(([left], [right]) => left - right);
570
+ const overrides = [];
571
+ let cumulativeDelta = 0;
572
+ for (const [index, size] of sorted) {
573
+ const previous = overrides.at(-1);
574
+ if (previous?.index === index) {
575
+ cumulativeDelta -= previous.size - normalizedDefault;
576
+ overrides.pop();
577
+ }
578
+ if (size === normalizedDefault) continue;
579
+ const start = index * normalizedDefault + cumulativeDelta;
580
+ cumulativeDelta += size - normalizedDefault;
581
+ overrides.push({
582
+ index,
583
+ size,
584
+ start,
585
+ end: start + size,
586
+ cumulativeDelta
587
+ });
588
+ }
589
+ return {
590
+ count,
591
+ defaultSize: normalizedDefault,
592
+ overrides,
593
+ total: count * normalizedDefault + cumulativeDelta
594
+ };
595
+ }
596
+ function lowerBoundOverrideIndex(axis, index) {
597
+ let low = 0;
598
+ let high = axis.overrides.length;
599
+ while (low < high) {
600
+ const middle = low + high >>> 1;
601
+ if (axis.overrides[middle].index < index) low = middle + 1;
602
+ else high = middle;
603
+ }
604
+ return low;
605
+ }
606
+ function axisOffsetAt(axis, index) {
607
+ if (index <= 0) return 0;
608
+ if (index >= axis.count) return axis.total;
609
+ const insertion = lowerBoundOverrideIndex(axis, index);
610
+ const delta = insertion > 0 ? axis.overrides[insertion - 1].cumulativeDelta : 0;
611
+ return index * axis.defaultSize + delta;
612
+ }
613
+ function axisSizeAt(axis, index) {
614
+ const candidate = axis.overrides[lowerBoundOverrideIndex(axis, index)];
615
+ return candidate?.index === index ? candidate.size : axis.defaultSize;
616
+ }
617
+ function upperBoundOverrideStart(axis, offset) {
618
+ let low = 0;
619
+ let high = axis.overrides.length;
620
+ while (low < high) {
621
+ const middle = low + high >>> 1;
622
+ if (axis.overrides[middle].start <= offset) low = middle + 1;
623
+ else high = middle;
624
+ }
625
+ return low;
626
+ }
627
+ function indexAtOffset(axis, offset) {
628
+ if (axis.count <= 1) return 0;
629
+ const target = Math.max(0, Math.min(axis.total - 1, offset));
630
+ const previousIndex = upperBoundOverrideStart(axis, target) - 1;
631
+ if (previousIndex < 0) {
632
+ return Math.min(
633
+ (axis.overrides[0]?.index ?? axis.count) - 1,
634
+ Math.floor(target / axis.defaultSize)
635
+ );
636
+ }
637
+ const previous = axis.overrides[previousIndex];
638
+ if (target < previous.end) return previous.index;
639
+ const nextOverrideIndex = axis.overrides[previousIndex + 1]?.index ?? axis.count;
640
+ return Math.min(
641
+ axis.count - 1,
642
+ nextOverrideIndex - 1,
643
+ previous.index + 1 + Math.floor((target - previous.end) / axis.defaultSize)
644
+ );
645
+ }
646
+ function visibleInterval(axis, startOffset, viewportSize, overscan) {
647
+ const start = Math.max(0, indexAtOffset(axis, startOffset) - overscan);
648
+ const end = Math.min(
649
+ axis.count,
650
+ indexAtOffset(axis, startOffset + Math.max(1, viewportSize)) + overscan + 1
651
+ );
652
+ return { start, end };
653
+ }
654
+ function boundVisibleWindows(rows, columns, focus) {
655
+ let rowCount = Math.min(MAX_MOUNTED_AXIS_ITEMS, rows.end - rows.start);
656
+ let columnCount = Math.min(MAX_MOUNTED_AXIS_ITEMS, columns.end - columns.start);
657
+ if (rowCount * columnCount > MAX_MOUNTED_GRID_CELLS) {
658
+ const balancedLimit = Math.floor(Math.sqrt(MAX_MOUNTED_GRID_CELLS));
659
+ if (rowCount > balancedLimit && columnCount > balancedLimit) {
660
+ rowCount = balancedLimit;
661
+ columnCount = balancedLimit;
662
+ } else if (rowCount >= columnCount) {
663
+ rowCount = Math.max(1, Math.floor(MAX_MOUNTED_GRID_CELLS / columnCount));
664
+ } else {
665
+ columnCount = Math.max(1, Math.floor(MAX_MOUNTED_GRID_CELLS / rowCount));
666
+ }
667
+ }
668
+ return {
669
+ rows: centeredInterval(rows, rowCount, focus.row),
670
+ columns: centeredInterval(columns, columnCount, focus.col)
671
+ };
672
+ }
673
+ function centeredInterval(interval, count, focus) {
674
+ if (count >= interval.end - interval.start) return interval;
675
+ if (focus < interval.start || focus >= interval.end) {
676
+ return { start: interval.start, end: interval.start + count };
677
+ }
678
+ const start = Math.max(
679
+ interval.start,
680
+ Math.min(interval.end - count, focus - Math.floor(count / 2))
681
+ );
682
+ return { start, end: start + count };
683
+ }
684
+ function indexesBetween(start, end) {
685
+ return Array.from({ length: end - start }, (_, index) => start + index);
686
+ }
687
+ function rawBoundaryAt(projection, index) {
688
+ if (index <= 0) return 0;
689
+ if (index >= projection.axis.count) {
690
+ return projection.axis.count * projection.baseItemSpan + projection.proportionalSpan;
691
+ }
692
+ return index * projection.baseItemSpan + axisOffsetAt(projection.axis, index) / projection.axis.total * projection.proportionalSpan;
693
+ }
694
+ function rawOffsetAtLogical(projection, offset) {
695
+ if (offset <= 0) return 0;
696
+ if (offset >= projection.axis.total) {
697
+ return rawBoundaryAt(projection, projection.axis.count);
698
+ }
699
+ const index = indexAtOffset(projection.axis, offset);
700
+ const logicalStart = axisOffsetAt(projection.axis, index);
701
+ const logicalSize = axisSizeAt(projection.axis, index);
702
+ const fraction = Math.max(0, Math.min(1, (offset - logicalStart) / logicalSize));
703
+ const rawStart = rawBoundaryAt(projection, index);
704
+ const rawEnd = rawBoundaryAt(projection, index + 1);
705
+ return rawStart + fraction * (rawEnd - rawStart);
706
+ }
707
+ function logicalOffsetAtRaw(projection, rawOffset) {
708
+ const target = Math.max(0, Math.min(projection.rawMax, rawOffset));
709
+ let low = 0;
710
+ let high = projection.axis.count;
711
+ while (low < high) {
712
+ const middle = Math.ceil((low + high) / 2);
713
+ if (rawBoundaryAt(projection, middle) <= target) low = middle;
714
+ else high = middle - 1;
715
+ }
716
+ const index = Math.min(projection.axis.count - 1, low);
717
+ const rawStart = rawBoundaryAt(projection, index);
718
+ const rawEnd = rawBoundaryAt(projection, index + 1);
719
+ const fraction = rawEnd === rawStart ? 0 : (target - rawStart) / (rawEnd - rawStart);
720
+ return Math.min(
721
+ projection.logicalMax,
722
+ axisOffsetAt(projection.axis, index) + fraction * axisSizeAt(projection.axis, index)
723
+ );
724
+ }
725
+ function buildScrollProjection(axis, headerSize, viewportSize) {
726
+ const logicalTotal = headerSize + axis.total;
727
+ const physicalTotal = Math.min(logicalTotal, MAX_PHYSICAL_SCROLL_SPAN);
728
+ const logicalMax = Math.max(0, logicalTotal - viewportSize);
729
+ const physicalMax = Math.max(0, physicalTotal - viewportSize);
730
+ const mode = physicalTotal === logicalTotal ? "identity" : "indexed";
731
+ const physicalAxisCapacity = Math.max(0, physicalTotal - headerSize);
732
+ const baseItemSpan = mode === "indexed" ? Math.min(MIN_PHYSICAL_ITEM_SPAN, physicalAxisCapacity / axis.count) : 0;
733
+ const projection = {
734
+ axis,
735
+ mode,
736
+ physicalTotal,
737
+ logicalMax,
738
+ physicalMax,
739
+ baseItemSpan,
740
+ proportionalSpan: Math.max(0, physicalAxisCapacity - baseItemSpan * axis.count),
741
+ rawMax: 0
742
+ };
743
+ projection.rawMax = rawOffsetAtLogical(projection, logicalMax);
744
+ return projection;
745
+ }
746
+ function clampPhysicalScroll(offset, projection) {
747
+ return Math.max(0, Math.min(projection.physicalMax, offset));
748
+ }
749
+ function physicalToLogicalScroll(offset, projection) {
750
+ if (projection.logicalMax === 0 || projection.physicalMax === 0) return 0;
751
+ if (projection.mode === "indexed") {
752
+ return logicalOffsetAtRaw(
753
+ projection,
754
+ clampPhysicalScroll(offset, projection) / projection.physicalMax * projection.rawMax
755
+ );
756
+ }
757
+ return clampPhysicalScroll(offset, projection) / projection.physicalMax * projection.logicalMax;
758
+ }
759
+ function logicalToPhysicalScroll(offset, projection) {
760
+ if (projection.logicalMax === 0 || projection.physicalMax === 0) return 0;
761
+ const logical = Math.max(0, Math.min(projection.logicalMax, offset));
762
+ if (projection.mode === "indexed") {
763
+ return rawOffsetAtLogical(projection, logical) / projection.rawMax * projection.physicalMax;
764
+ }
765
+ return logical / projection.logicalMax * projection.physicalMax;
766
+ }
767
+ function columnName2(index) {
768
+ let value = index + 1;
769
+ let result = "";
770
+ while (value > 0) {
771
+ const remainder = (value - 1) % 26;
772
+ result = String.fromCharCode(65 + remainder) + result;
773
+ value = Math.floor((value - 1) / 26);
774
+ }
775
+ return result;
776
+ }
777
+ function cellName(cell) {
778
+ return `${columnName2(cell.col)}${cell.row + 1}`;
779
+ }
780
+ function normalizeSelection(selection) {
781
+ return {
782
+ top: Math.min(selection.anchor.row, selection.focus.row),
783
+ bottom: Math.max(selection.anchor.row, selection.focus.row),
784
+ left: Math.min(selection.anchor.col, selection.focus.col),
785
+ right: Math.max(selection.anchor.col, selection.focus.col)
786
+ };
787
+ }
788
+ function selectionName(selection) {
789
+ const normalized = normalizeSelection(selection);
790
+ const first = cellName({ row: normalized.top, col: normalized.left });
791
+ const last = cellName({ row: normalized.bottom, col: normalized.right });
792
+ return first === last ? first : `${first}:${last}`;
793
+ }
794
+ function isCellSelected(selection, row, col) {
795
+ const normalized = normalizeSelection(selection);
796
+ return row >= normalized.top && row <= normalized.bottom && col >= normalized.left && col <= normalized.right;
797
+ }
798
+ function displayValue2(value) {
799
+ if (value === null || value === void 0) return "";
800
+ if (value instanceof Date) return Number.isNaN(value.getTime()) ? "" : value.toLocaleDateString();
801
+ if (typeof value === "boolean") return value ? "TRUE" : "FALSE";
802
+ return String(value);
803
+ }
804
+ function parseCellInput(input) {
805
+ if (input === "") return null;
806
+ const normalized = input.trim();
807
+ if (/^(?:true|false)$/i.test(normalized)) return normalized.toLowerCase() === "true";
808
+ if (/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i.test(normalized)) {
809
+ const number = Number(normalized);
810
+ if (Number.isFinite(number)) return number;
811
+ }
812
+ return input;
813
+ }
814
+ function parseSpreadsheetClipboard(input) {
815
+ if (new TextEncoder().encode(input).byteLength > MAX_CLIPBOARD_INPUT_BYTES) {
816
+ throw new Error("Clipboard data is too large to paste");
817
+ }
818
+ const records = [[]];
819
+ let field = "";
820
+ let quoted = false;
821
+ let endedWithRecordSeparator = false;
822
+ for (let index = 0; index < input.length; index += 1) {
823
+ const character = input[index];
824
+ if (quoted) {
825
+ if (character === '"') {
826
+ if (input[index + 1] === '"') {
827
+ field += '"';
828
+ index += 1;
829
+ } else {
830
+ quoted = false;
831
+ }
832
+ } else {
833
+ field += character;
834
+ }
835
+ endedWithRecordSeparator = false;
836
+ continue;
837
+ }
838
+ if (character === '"' && field.length === 0) {
839
+ quoted = true;
840
+ endedWithRecordSeparator = false;
841
+ } else if (character === " ") {
842
+ records.at(-1).push(field);
843
+ field = "";
844
+ endedWithRecordSeparator = false;
845
+ } else if (character === "\r" || character === "\n") {
846
+ records.at(-1).push(field);
847
+ field = "";
848
+ records.push([]);
849
+ if (character === "\r" && input[index + 1] === "\n") index += 1;
850
+ endedWithRecordSeparator = true;
851
+ } else {
852
+ field += character;
853
+ endedWithRecordSeparator = false;
854
+ }
855
+ }
856
+ if (quoted) throw new Error("Clipboard data contains an unterminated quoted cell");
857
+ if (endedWithRecordSeparator) records.pop();
858
+ else records.at(-1).push(field);
859
+ if (records.length === 0) records.push([""]);
860
+ const columns = Math.max(1, ...records.map((record) => record.length));
861
+ const cellCount = records.length * columns;
862
+ if (cellCount > MAX_CLIPBOARD_CELLS) {
863
+ throw new Error("Clipboard range is too large to paste");
864
+ }
865
+ const inputs = records.flatMap((record) => [
866
+ ...record,
867
+ ...Array.from({ length: columns - record.length }, () => "")
868
+ ]);
869
+ return Object.freeze({ rows: records.length, columns, inputs: Object.freeze(inputs) });
870
+ }
871
+ function escapeSpreadsheetClipboardField(input) {
872
+ return /["\t\r\n]/u.test(input) ? `"${input.replaceAll('"', '""')}"` : input;
873
+ }
874
+ function cellStyle(format) {
875
+ return {
876
+ background: format.fill,
877
+ color: format.font?.color,
878
+ fontFamily: format.font?.name,
879
+ fontSize: format.font?.size,
880
+ fontWeight: format.font?.bold ? 600 : void 0,
881
+ fontStyle: format.font?.italic ? "italic" : void 0,
882
+ textDecoration: format.font?.underline ? "underline" : void 0,
883
+ textAlign: format.horizontalAlignment,
884
+ justifyContent: format.horizontalAlignment === "center" ? "center" : format.horizontalAlignment === "right" ? "flex-end" : "flex-start",
885
+ alignItems: format.verticalAlignment === "top" ? "flex-start" : format.verticalAlignment === "bottom" ? "flex-end" : "center",
886
+ whiteSpace: format.wrapText ? "normal" : "nowrap"
887
+ };
888
+ }
889
+ function canvasAxis(axis) {
890
+ return {
891
+ count: axis.count,
892
+ total: axis.total,
893
+ offsetAt: (index) => axisOffsetAt(axis, index),
894
+ sizeAt: (index) => axisSizeAt(axis, index),
895
+ indexAtOffset: (offset) => indexAtOffset(axis, offset)
896
+ };
897
+ }
898
+ function computedToken(style, name, fallback) {
899
+ return style.getPropertyValue(name).trim() || fallback;
900
+ }
901
+ function computedPixels(style, name, fallback) {
902
+ const value = Number.parseFloat(style.getPropertyValue(name));
903
+ return Number.isFinite(value) && value > 0 ? value : fallback;
904
+ }
905
+ function spreadsheetCanvasTheme(element) {
906
+ const style = getComputedStyle(element);
907
+ const background = style.backgroundColor || computedToken(style, "--og-color-surface-1", "#ffffff");
908
+ const headerBackground = computedToken(style, "--og-color-surface-2", "#f3f4f6");
909
+ const border = computedToken(style, "--og-color-border", "#d1d5db");
910
+ const foreground = style.color || computedToken(style, "--og-color-fg", "#111827");
911
+ const mutedForeground = computedToken(style, "--og-color-fg-muted", "#6b7280");
912
+ const accent = computedToken(style, "--og-color-accent", "#3b82f6");
913
+ const error = computedToken(style, "--og-color-status-failed", "#dc2626");
914
+ const fontFamily = style.fontFamily || computedToken(
915
+ style,
916
+ "--og-font-sans",
917
+ 'Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
918
+ );
919
+ const fontSize = Number.parseFloat(style.fontSize) || computedPixels(style, "--og-font-size-sm", 12);
920
+ const headerFontSize = computedPixels(style, "--og-font-size-xs", 11);
921
+ return {
922
+ background,
923
+ headerBackground,
924
+ border,
925
+ foreground,
926
+ mutedForeground,
927
+ accent,
928
+ error,
929
+ fontFamily,
930
+ fontSize,
931
+ headerFontSize,
932
+ signature: [
933
+ background,
934
+ headerBackground,
935
+ border,
936
+ foreground,
937
+ mutedForeground,
938
+ accent,
939
+ error,
940
+ fontFamily,
941
+ fontSize,
942
+ headerFontSize
943
+ ].join("")
944
+ };
945
+ }
946
+ function useWorkbookViewState(workbook) {
947
+ const [view, setView] = useState(() => ({
948
+ revision: workbook.revision,
949
+ dimensionRevision: 0
950
+ }));
951
+ useEffect(() => {
952
+ setView(
953
+ (current) => current.revision === workbook.revision ? current : { revision: workbook.revision, dimensionRevision: current.dimensionRevision + 1 }
954
+ );
955
+ return workbook.onChange((change) => {
956
+ setView((current) => ({
957
+ revision: change.revision,
958
+ dimensionRevision: change.reason === "dimension" || change.reason === "structure" ? current.dimensionRevision + 1 : current.dimensionRevision
959
+ }));
960
+ });
961
+ }, [workbook]);
962
+ return view;
963
+ }
964
+ function nextAvailableSheetName(workbook) {
965
+ let index = workbook.worksheets.items.length + 1;
966
+ while (workbook.worksheets.items.some((sheet) => sheet.name === `Sheet${index}`)) index += 1;
967
+ return `Sheet${index}`;
968
+ }
969
+ function SpreadsheetArtifactSurface({
970
+ workbook,
971
+ title = "Workbook",
972
+ subtitle,
973
+ actions,
974
+ initialSheetId,
975
+ allowAddSheet = true,
976
+ readOnly = false,
977
+ ...gridProps
978
+ }) {
979
+ const { revision } = useWorkbookViewState(workbook);
980
+ const availableSheets = workbook.worksheets.items;
981
+ const initial = (initialSheetId ? availableSheets.find((sheet) => sheet.id === initialSheetId) : void 0) ?? (() => {
982
+ try {
983
+ return workbook.worksheets.getActiveWorksheet();
984
+ } catch {
985
+ return availableSheets[0];
986
+ }
987
+ })();
988
+ const [activeSheetId, setActiveSheetId] = useState(initial?.id ?? null);
989
+ const activeSheet = availableSheets.find((sheet) => sheet.id === activeSheetId) ?? availableSheets[0] ?? null;
990
+ useEffect(() => {
991
+ if (!activeSheet && activeSheetId !== null) setActiveSheetId(null);
992
+ else if (activeSheet && activeSheet.id !== activeSheetId) setActiveSheetId(activeSheet.id);
993
+ }, [activeSheet, activeSheetId, revision]);
994
+ const selectSheet = useCallback(
995
+ (sheet) => {
996
+ workbook.worksheets.setActiveWorksheet(sheet);
997
+ setActiveSheetId(sheet.id);
998
+ },
999
+ [workbook]
1000
+ );
1001
+ const addSheet = useCallback(() => {
1002
+ const sheet = workbook.worksheets.add(nextAvailableSheetName(workbook));
1003
+ selectSheet(sheet);
1004
+ }, [selectSheet, workbook]);
1005
+ const footer = /* @__PURE__ */ jsxs(
1006
+ "div",
1007
+ {
1008
+ className: "flex min-h-9 items-center gap-1 overflow-x-auto px-2",
1009
+ role: "tablist",
1010
+ "aria-label": "Worksheets",
1011
+ children: [
1012
+ availableSheets.map((sheet) => /* @__PURE__ */ jsx(
1013
+ "button",
1014
+ {
1015
+ type: "button",
1016
+ role: "tab",
1017
+ "aria-selected": sheet.id === activeSheet?.id,
1018
+ onClick: () => selectSheet(sheet),
1019
+ className: cn(
1020
+ "h-7 shrink-0 rounded-og-sm px-2.5 text-og-sm transition-colors",
1021
+ sheet.id === activeSheet?.id ? "bg-og-surface-3 font-medium text-og-fg" : "text-og-fg-muted hover:bg-og-surface-3 hover:text-og-fg"
1022
+ ),
1023
+ children: sheet.name
1024
+ },
1025
+ sheet.id
1026
+ )),
1027
+ !readOnly && allowAddSheet ? /* @__PURE__ */ jsx(
1028
+ "button",
1029
+ {
1030
+ type: "button",
1031
+ onClick: addSheet,
1032
+ "aria-label": "Add worksheet",
1033
+ className: "grid size-7 shrink-0 place-items-center rounded-og-sm text-og-fg-muted hover:bg-og-surface-3 hover:text-og-fg [&>svg]:size-3.5",
1034
+ children: /* @__PURE__ */ jsx(PlusIcon, {})
1035
+ }
1036
+ ) : null
1037
+ ]
1038
+ }
1039
+ );
1040
+ return /* @__PURE__ */ jsx(
1041
+ ArtifactSurface,
1042
+ {
1043
+ modality: "spreadsheet",
1044
+ title,
1045
+ subtitle: subtitle ?? (activeSheet ? `${availableSheets.length} sheet${availableSheets.length === 1 ? "" : "s"}` : "Empty workbook"),
1046
+ actions,
1047
+ footer,
1048
+ children: activeSheet ? /* @__PURE__ */ jsx(
1049
+ SpreadsheetGrid,
1050
+ {
1051
+ ...gridProps,
1052
+ workbook,
1053
+ worksheet: activeSheet,
1054
+ readOnly
1055
+ },
1056
+ activeSheet.id
1057
+ ) : /* @__PURE__ */ jsx(
1058
+ "div",
1059
+ {
1060
+ className: "grid h-full place-items-center bg-og-bg p-6 text-center",
1061
+ style: { minHeight: 224 },
1062
+ children: /* @__PURE__ */ jsxs("div", { children: [
1063
+ /* @__PURE__ */ jsx("p", { className: "text-og-base font-medium text-og-fg", children: "This workbook has no worksheets." }),
1064
+ !readOnly && allowAddSheet ? /* @__PURE__ */ jsx(
1065
+ "button",
1066
+ {
1067
+ type: "button",
1068
+ onClick: addSheet,
1069
+ className: "mt-3 rounded-og-sm bg-og-accent-deep px-3 py-1.5 text-og-sm font-medium text-og-accent-fg",
1070
+ children: "Add worksheet"
1071
+ }
1072
+ ) : null
1073
+ ] })
1074
+ }
1075
+ )
1076
+ }
1077
+ );
1078
+ }
1079
+ function SpreadsheetGrid({
1080
+ workbook,
1081
+ worksheet,
1082
+ readOnly = false,
1083
+ rowCount: requestedRowCount,
1084
+ columnCount: requestedColumnCount,
1085
+ overscanRows = 3,
1086
+ overscanColumns = 2,
1087
+ onSelectionChange,
1088
+ onCommit,
1089
+ onViewportChange,
1090
+ className
1091
+ }) {
1092
+ const { revision, dimensionRevision } = useWorkbookViewState(workbook);
1093
+ const boundedProjection = requestedRowCount !== void 0 || requestedColumnCount !== void 0;
1094
+ const usedRange = useMemo(() => {
1095
+ void revision;
1096
+ return boundedProjection ? worksheet.usedRangeAddress() : null;
1097
+ }, [boundedProjection, revision, worksheet]);
1098
+ const rowCount = boundedInteger(
1099
+ Math.max(
1100
+ requestedRowCount ?? MAX_ROWS,
1101
+ (usedRange?.row ?? 0) + (usedRange?.rowCount ?? 0) + 32
1102
+ ),
1103
+ MAX_ROWS,
1104
+ MAX_ROWS
1105
+ );
1106
+ const columnCount = boundedInteger(
1107
+ Math.max(
1108
+ requestedColumnCount ?? MAX_COLUMNS,
1109
+ (usedRange?.col ?? 0) + (usedRange?.colCount ?? 0) + 8
1110
+ ),
1111
+ MAX_COLUMNS,
1112
+ MAX_COLUMNS
1113
+ );
1114
+ const rowHeights = useMemo(() => {
1115
+ void dimensionRevision;
1116
+ return [...worksheet.rowHeightEntries()];
1117
+ }, [dimensionRevision, worksheet]);
1118
+ const columnWidths = useMemo(() => {
1119
+ void dimensionRevision;
1120
+ return [...worksheet.columnWidthEntries()];
1121
+ }, [dimensionRevision, worksheet]);
1122
+ const projection = useMemo(() => {
1123
+ void dimensionRevision;
1124
+ const cells = new SparseSpreadsheetCellIndex(
1125
+ function* projectedCells() {
1126
+ for (const { row, col, data } of worksheet.cellEntries()) {
1127
+ yield {
1128
+ row,
1129
+ col,
1130
+ value: data.value,
1131
+ formula: data.formula,
1132
+ format: data.format
1133
+ };
1134
+ }
1135
+ }()
1136
+ );
1137
+ return {
1138
+ sheetId: worksheet.id,
1139
+ sheetName: worksheet.name,
1140
+ generationId: worksheet.id,
1141
+ revision,
1142
+ dimensionRevision,
1143
+ rowCount,
1144
+ columnCount,
1145
+ defaultRowHeight: worksheet.defaultRowHeight,
1146
+ defaultColumnWidth: worksheet.defaultColumnWidth,
1147
+ rowHeights,
1148
+ columnWidths,
1149
+ usedRange,
1150
+ cells,
1151
+ valueAt: (cell) => cell.formula ? workbook.valueAt(worksheet, { row: cell.row, col: cell.col }) : cell.value,
1152
+ readCell: (row, col) => {
1153
+ const data = worksheet.cellData(row, col);
1154
+ return {
1155
+ value: data.formula ? workbook.valueAt(worksheet, { row, col }) : data.value,
1156
+ input: data.formula ?? displayValue2(data.value),
1157
+ format: data.format
1158
+ };
1159
+ }
1160
+ };
1161
+ }, [
1162
+ columnCount,
1163
+ columnWidths,
1164
+ dimensionRevision,
1165
+ revision,
1166
+ rowCount,
1167
+ rowHeights,
1168
+ usedRange,
1169
+ workbook,
1170
+ worksheet
1171
+ ]);
1172
+ const commit = useCallback(
1173
+ (next) => {
1174
+ const target = worksheet.getCell(next.cell.row, next.cell.col);
1175
+ if (next.kind === "formula") target.formulas = [[next.input]];
1176
+ else target.values = [[parseCellInput(next.input)]];
1177
+ onCommit?.(next);
1178
+ },
1179
+ [onCommit, worksheet]
1180
+ );
1181
+ const commitRange = useCallback(
1182
+ (next) => {
1183
+ if (next.rows * next.columns !== next.inputs.length) {
1184
+ throw new Error("Spreadsheet paste shape is invalid");
1185
+ }
1186
+ workbook.transact(() => {
1187
+ for (let index = 0; index < next.inputs.length; index += 1) {
1188
+ const input = next.inputs[index];
1189
+ const row = next.anchor.row + Math.floor(index / next.columns);
1190
+ const col = next.anchor.col + index % next.columns;
1191
+ worksheet.setCell(
1192
+ row,
1193
+ col,
1194
+ input.startsWith("=") ? { formula: input, value: null } : { formula: null, value: parseCellInput(input) },
1195
+ "content"
1196
+ );
1197
+ }
1198
+ });
1199
+ },
1200
+ [workbook, worksheet]
1201
+ );
1202
+ const clear = useCallback(
1203
+ (next) => {
1204
+ const normalized = normalizeSelection(next);
1205
+ worksheet.getRangeByIndexes(
1206
+ normalized.top,
1207
+ normalized.left,
1208
+ normalized.bottom - normalized.top + 1,
1209
+ normalized.right - normalized.left + 1
1210
+ ).clear({ applyTo: "contents" });
1211
+ },
1212
+ [worksheet]
1213
+ );
1214
+ return /* @__PURE__ */ jsx(
1215
+ SpreadsheetProjectionGrid,
1216
+ {
1217
+ projection,
1218
+ readOnly,
1219
+ overscanRows,
1220
+ overscanColumns,
1221
+ onSelectionChange,
1222
+ commit,
1223
+ commitRange,
1224
+ clear,
1225
+ onViewportChange,
1226
+ className
1227
+ }
1228
+ );
1229
+ }
1230
+ function SpreadsheetProjectionGrid({
1231
+ projection,
1232
+ readOnly = false,
1233
+ overscanRows = 3,
1234
+ overscanColumns = 2,
1235
+ onSelectionChange,
1236
+ commit,
1237
+ commitRange,
1238
+ clear,
1239
+ onCommandError,
1240
+ onViewportChange,
1241
+ className
1242
+ }) {
1243
+ const { revision, rowCount, columnCount } = projection;
1244
+ const dimensionRevision = projection.dimensionRevision ?? 0;
1245
+ const editable = !readOnly && commit !== void 0;
1246
+ const rows = useMemo(() => {
1247
+ void dimensionRevision;
1248
+ return buildAxis(rowCount, projection.defaultRowHeight ?? 24, projection.rowHeights ?? []);
1249
+ }, [dimensionRevision, projection.defaultRowHeight, projection.rowHeights, rowCount]);
1250
+ const columns = useMemo(() => {
1251
+ void dimensionRevision;
1252
+ return buildAxis(
1253
+ columnCount,
1254
+ projection.defaultColumnWidth ?? 96,
1255
+ projection.columnWidths ?? []
1256
+ );
1257
+ }, [columnCount, dimensionRevision, projection.columnWidths, projection.defaultColumnWidth]);
1258
+ const canvasRows = useMemo(() => canvasAxis(rows), [rows]);
1259
+ const canvasColumns = useMemo(() => canvasAxis(columns), [columns]);
1260
+ const [selection, setSelection] = useState(() => ({
1261
+ sheetId: projection.sheetId,
1262
+ anchor: { row: 0, col: 0 },
1263
+ focus: { row: 0, col: 0 }
1264
+ }));
1265
+ const [editMode, setEditMode] = useState(null);
1266
+ const [draft, setDraft] = useState(
1267
+ () => projection.readCell(selection.focus.row, selection.focus.col)?.input ?? ""
1268
+ );
1269
+ const [pendingCommands, setPendingCommands] = useState(0);
1270
+ const [commandFailure, setCommandFailure] = useState(null);
1271
+ const [viewport, setViewport] = useState({
1272
+ width: FALLBACK_VIEWPORT_WIDTH,
1273
+ height: FALLBACK_VIEWPORT_HEIGHT,
1274
+ scrollLeft: 0,
1275
+ scrollTop: 0,
1276
+ devicePixelRatio: 1
1277
+ });
1278
+ const [canvasReady, setCanvasReady] = useState(false);
1279
+ const [canvasEpoch, setCanvasEpoch] = useState(0);
1280
+ const viewportRef = useRef(null);
1281
+ const canvasRef = useRef(null);
1282
+ const editorRef = useRef(null);
1283
+ const selectionRef = useRef(selection);
1284
+ const selectingRef = useRef(null);
1285
+ const committingRef = useRef(false);
1286
+ const composingRef = useRef(false);
1287
+ const commitAfterCompositionRef = useRef(false);
1288
+ const selectDraftOnFocusRef = useRef(true);
1289
+ const initialDraftRef = useRef(draft);
1290
+ const optimisticInputRef = useRef(null);
1291
+ const mountedRef = useRef(true);
1292
+ const commandScopeRef = useRef(0);
1293
+ const latestCommandRef = useRef(0);
1294
+ const canvasRendererRef = useRef(null);
1295
+ if (!canvasRendererRef.current) canvasRendererRef.current = new SpreadsheetCanvasRenderer();
1296
+ const reactId = useId().replaceAll(":", "");
1297
+ const updateSelection = useCallback(
1298
+ (next) => {
1299
+ selectionRef.current = next;
1300
+ setSelection(next);
1301
+ onSelectionChange?.(next);
1302
+ },
1303
+ [onSelectionChange]
1304
+ );
1305
+ useEffect(() => {
1306
+ if (selection.sheetId === projection.sheetId) return;
1307
+ commandScopeRef.current += 1;
1308
+ latestCommandRef.current += 1;
1309
+ const next = {
1310
+ sheetId: projection.sheetId,
1311
+ anchor: { row: 0, col: 0 },
1312
+ focus: { row: 0, col: 0 }
1313
+ };
1314
+ optimisticInputRef.current = null;
1315
+ setPendingCommands(0);
1316
+ setCommandFailure(null);
1317
+ setEditMode(null);
1318
+ updateSelection(next);
1319
+ }, [projection.sheetId, selection.sheetId, updateSelection]);
1320
+ useEffect(() => {
1321
+ mountedRef.current = true;
1322
+ return () => {
1323
+ mountedRef.current = false;
1324
+ commandScopeRef.current += 1;
1325
+ latestCommandRef.current += 1;
1326
+ };
1327
+ }, []);
1328
+ useEffect(() => {
1329
+ if (editMode !== null) return;
1330
+ const optimistic = optimisticInputRef.current;
1331
+ if (optimistic?.sheetId === projection.sheetId && optimistic.row === selection.focus.row && optimistic.col === selection.focus.col && optimistic.baseRevision === revision) {
1332
+ setDraft(optimistic.input);
1333
+ return;
1334
+ }
1335
+ if (optimistic && optimistic.baseRevision !== revision) optimisticInputRef.current = null;
1336
+ setDraft(projection.readCell(selection.focus.row, selection.focus.col)?.input ?? "");
1337
+ }, [editMode, projection, revision, selection.focus.col, selection.focus.row]);
1338
+ useClientLayoutEffect(() => {
1339
+ const element = viewportRef.current;
1340
+ if (!element) return;
1341
+ const measure = () => {
1342
+ setViewport((current) => ({
1343
+ ...current,
1344
+ width: element.clientWidth || FALLBACK_VIEWPORT_WIDTH,
1345
+ height: element.clientHeight || FALLBACK_VIEWPORT_HEIGHT,
1346
+ devicePixelRatio: spreadsheetCanvasDevicePixelRatio(globalThis.devicePixelRatio || 1)
1347
+ }));
1348
+ };
1349
+ measure();
1350
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(measure);
1351
+ observer?.observe(element);
1352
+ globalThis.addEventListener?.("resize", measure);
1353
+ return () => {
1354
+ observer?.disconnect();
1355
+ globalThis.removeEventListener?.("resize", measure);
1356
+ };
1357
+ }, []);
1358
+ useEffect(() => {
1359
+ return () => canvasRendererRef.current?.dispose();
1360
+ }, []);
1361
+ useEffect(() => {
1362
+ if (typeof document === "undefined") return;
1363
+ let active = true;
1364
+ void document.fonts?.ready.then(() => {
1365
+ if (active) setCanvasEpoch((value) => value + 1);
1366
+ });
1367
+ if (typeof MutationObserver === "undefined") {
1368
+ return () => {
1369
+ active = false;
1370
+ };
1371
+ }
1372
+ const observer = new MutationObserver(() => setCanvasEpoch((value) => value + 1));
1373
+ let ancestor = viewportRef.current;
1374
+ while (ancestor) {
1375
+ observer.observe(ancestor, {
1376
+ attributes: true,
1377
+ attributeFilter: ["class", "style", "data-og-theme", "data-og-density"]
1378
+ });
1379
+ ancestor = ancestor.parentElement;
1380
+ }
1381
+ return () => {
1382
+ active = false;
1383
+ observer.disconnect();
1384
+ };
1385
+ }, []);
1386
+ useEffect(() => {
1387
+ if (editMode !== "cell") return;
1388
+ const editor = editorRef.current;
1389
+ editor?.focus();
1390
+ if (!editor) return;
1391
+ if (selectDraftOnFocusRef.current) editor.select();
1392
+ else editor.setSelectionRange(editor.value.length, editor.value.length);
1393
+ }, [editMode]);
1394
+ const ensureVisible = useCallback(
1395
+ (cell) => {
1396
+ const element = viewportRef.current;
1397
+ if (!element) return;
1398
+ const viewportWidth = element.clientWidth || FALLBACK_VIEWPORT_WIDTH;
1399
+ const viewportHeight = element.clientHeight || FALLBACK_VIEWPORT_HEIGHT;
1400
+ const horizontalProjection2 = buildScrollProjection(columns, ROW_HEADER_WIDTH, viewportWidth);
1401
+ const verticalProjection2 = buildScrollProjection(rows, COLUMN_HEADER_HEIGHT, viewportHeight);
1402
+ const left = ROW_HEADER_WIDTH + axisOffsetAt(columns, cell.col);
1403
+ const right = left + axisSizeAt(columns, cell.col);
1404
+ const top = COLUMN_HEADER_HEIGHT + axisOffsetAt(rows, cell.row);
1405
+ const bottom = top + axisSizeAt(rows, cell.row);
1406
+ const logicalScrollLeft2 = physicalToLogicalScroll(element.scrollLeft, horizontalProjection2);
1407
+ const logicalScrollTop2 = physicalToLogicalScroll(element.scrollTop, verticalProjection2);
1408
+ const viewportLeft = logicalScrollLeft2 + ROW_HEADER_WIDTH;
1409
+ const viewportRight = logicalScrollLeft2 + viewportWidth;
1410
+ const viewportTop = logicalScrollTop2 + COLUMN_HEADER_HEIGHT;
1411
+ const viewportBottom = logicalScrollTop2 + viewportHeight;
1412
+ if (left < viewportLeft) {
1413
+ element.scrollLeft = logicalToPhysicalScroll(
1414
+ Math.max(0, left - ROW_HEADER_WIDTH),
1415
+ horizontalProjection2
1416
+ );
1417
+ } else if (right > viewportRight) {
1418
+ element.scrollLeft = logicalToPhysicalScroll(right - viewportWidth, horizontalProjection2);
1419
+ }
1420
+ if (top < viewportTop) {
1421
+ element.scrollTop = logicalToPhysicalScroll(
1422
+ Math.max(0, top - COLUMN_HEADER_HEIGHT),
1423
+ verticalProjection2
1424
+ );
1425
+ } else if (bottom > viewportBottom) {
1426
+ element.scrollTop = logicalToPhysicalScroll(bottom - viewportHeight, verticalProjection2);
1427
+ }
1428
+ },
1429
+ [columns, rows]
1430
+ );
1431
+ const moveSelection = useCallback(
1432
+ (rowDelta, colDelta, extend) => {
1433
+ const focus = {
1434
+ row: Math.max(0, Math.min(rowCount - 1, selection.focus.row + rowDelta)),
1435
+ col: Math.max(0, Math.min(columnCount - 1, selection.focus.col + colDelta))
1436
+ };
1437
+ const next = {
1438
+ sheetId: projection.sheetId,
1439
+ anchor: extend ? selection.anchor : focus,
1440
+ focus
1441
+ };
1442
+ updateSelection(next);
1443
+ ensureVisible(focus);
1444
+ },
1445
+ [columnCount, ensureVisible, projection.sheetId, rowCount, selection, updateSelection]
1446
+ );
1447
+ const runCommand = useCallback(
1448
+ (invoke, retry, rollback) => {
1449
+ const commandId = latestCommandRef.current + 1;
1450
+ latestCommandRef.current = commandId;
1451
+ const scope = commandScopeRef.current;
1452
+ setCommandFailure(null);
1453
+ const fail = (cause) => {
1454
+ if (!mountedRef.current || commandScopeRef.current !== scope || latestCommandRef.current !== commandId) {
1455
+ return;
1456
+ }
1457
+ const error = cause instanceof Error ? cause : new Error(String(cause));
1458
+ rollback();
1459
+ setCommandFailure({ message: error.message || "Spreadsheet change failed", retry });
1460
+ onCommandError?.(error);
1461
+ };
1462
+ let result;
1463
+ try {
1464
+ result = invoke();
1465
+ } catch (cause) {
1466
+ fail(cause);
1467
+ return;
1468
+ }
1469
+ if (!result || typeof result.then !== "function") return;
1470
+ setPendingCommands((current) => current + 1);
1471
+ void Promise.resolve(result).then(
1472
+ () => {
1473
+ if (!mountedRef.current || commandScopeRef.current !== scope) return;
1474
+ setPendingCommands((current) => Math.max(0, current - 1));
1475
+ },
1476
+ (cause) => {
1477
+ if (!mountedRef.current || commandScopeRef.current !== scope) return;
1478
+ setPendingCommands((current) => Math.max(0, current - 1));
1479
+ fail(cause);
1480
+ }
1481
+ );
1482
+ },
1483
+ [onCommandError]
1484
+ );
1485
+ const reportCommandFailure = useCallback(
1486
+ (cause) => {
1487
+ const error = cause instanceof Error ? cause : new Error(String(cause));
1488
+ setCommandFailure({ message: error.message || "Spreadsheet action failed" });
1489
+ onCommandError?.(error);
1490
+ },
1491
+ [onCommandError]
1492
+ );
1493
+ const commitDraft = useCallback(
1494
+ (input = draft) => {
1495
+ if (committingRef.current) return;
1496
+ committingRef.current = true;
1497
+ if (!editable || !commit) {
1498
+ setEditMode(null);
1499
+ queueMicrotask(() => {
1500
+ committingRef.current = false;
1501
+ });
1502
+ return;
1503
+ }
1504
+ if (input === initialDraftRef.current) {
1505
+ setEditMode(null);
1506
+ viewportRef.current?.focus();
1507
+ queueMicrotask(() => {
1508
+ committingRef.current = false;
1509
+ });
1510
+ return;
1511
+ }
1512
+ const next = {
1513
+ sheetId: projection.sheetId,
1514
+ cell: { ...selection.focus },
1515
+ input,
1516
+ kind: input.startsWith("=") ? "formula" : "value"
1517
+ };
1518
+ const execute = () => {
1519
+ optimisticInputRef.current = {
1520
+ sheetId: projection.sheetId,
1521
+ row: selection.focus.row,
1522
+ col: selection.focus.col,
1523
+ input,
1524
+ baseRevision: revision
1525
+ };
1526
+ setDraft(input);
1527
+ runCommand(
1528
+ () => commit(next),
1529
+ execute,
1530
+ () => {
1531
+ optimisticInputRef.current = null;
1532
+ const current = selectionRef.current.focus;
1533
+ setDraft(projection.readCell(current.row, current.col)?.input ?? "");
1534
+ }
1535
+ );
1536
+ };
1537
+ execute();
1538
+ initialDraftRef.current = input;
1539
+ setEditMode(null);
1540
+ viewportRef.current?.focus();
1541
+ queueMicrotask(() => {
1542
+ committingRef.current = false;
1543
+ });
1544
+ },
1545
+ [commit, draft, editable, projection, revision, runCommand, selection.focus]
1546
+ );
1547
+ const clearSelection = useCallback(() => {
1548
+ if (!editable || !clear) return;
1549
+ const current = selection;
1550
+ const execute = () => runCommand(
1551
+ () => clear(current),
1552
+ execute,
1553
+ () => {
1554
+ }
1555
+ );
1556
+ execute();
1557
+ }, [clear, editable, runCommand, selection]);
1558
+ const pasteClipboard = useCallback(
1559
+ (event) => {
1560
+ if (!editable || editMode !== null) return;
1561
+ const clipboardText = event.clipboardData.getData("text/plain");
1562
+ if (!clipboardText && !event.clipboardData.types.includes("text/plain")) return;
1563
+ event.preventDefault();
1564
+ const execute = () => {
1565
+ const previousSelection = selectionRef.current;
1566
+ const invoke = () => {
1567
+ const parsed = parseSpreadsheetClipboard(clipboardText);
1568
+ const anchor = previousSelection.focus;
1569
+ if (anchor.row + parsed.rows > rowCount || anchor.col + parsed.columns > columnCount) {
1570
+ throw new Error("Clipboard range does not fit in this worksheet");
1571
+ }
1572
+ if (!commitRange && parsed.inputs.length !== 1) {
1573
+ throw new Error("This spreadsheet adapter does not support rectangular paste");
1574
+ }
1575
+ const bottomRight = {
1576
+ row: anchor.row + parsed.rows - 1,
1577
+ col: anchor.col + parsed.columns - 1
1578
+ };
1579
+ updateSelection({
1580
+ sheetId: projection.sheetId,
1581
+ anchor: { ...anchor },
1582
+ focus: bottomRight
1583
+ });
1584
+ ensureVisible(bottomRight);
1585
+ if (commitRange) {
1586
+ return commitRange({
1587
+ sheetId: projection.sheetId,
1588
+ anchor: { ...anchor },
1589
+ rows: parsed.rows,
1590
+ columns: parsed.columns,
1591
+ inputs: parsed.inputs
1592
+ });
1593
+ }
1594
+ const input = parsed.inputs[0];
1595
+ return commit({
1596
+ sheetId: projection.sheetId,
1597
+ cell: { ...anchor },
1598
+ input,
1599
+ kind: input.startsWith("=") ? "formula" : "value"
1600
+ });
1601
+ };
1602
+ runCommand(invoke, execute, () => updateSelection(previousSelection));
1603
+ };
1604
+ execute();
1605
+ },
1606
+ [
1607
+ columnCount,
1608
+ commit,
1609
+ commitRange,
1610
+ editMode,
1611
+ editable,
1612
+ ensureVisible,
1613
+ projection.sheetId,
1614
+ rowCount,
1615
+ runCommand,
1616
+ updateSelection
1617
+ ]
1618
+ );
1619
+ const copySelection = useCallback(
1620
+ (event) => {
1621
+ if (editMode !== null) return;
1622
+ const selected = normalizeSelection(selectionRef.current);
1623
+ const rowsToCopy = selected.bottom - selected.top + 1;
1624
+ const columnsToCopy = selected.right - selected.left + 1;
1625
+ if (rowsToCopy * columnsToCopy > MAX_CLIPBOARD_CELLS) {
1626
+ event.preventDefault();
1627
+ reportCommandFailure(new Error("Selected range is too large to copy"));
1628
+ return;
1629
+ }
1630
+ const coverage = projection.coverage;
1631
+ if (coverage && (selected.top < coverage.rowStart || selected.bottom >= coverage.rowEnd || selected.left < coverage.columnStart || selected.right >= coverage.columnEnd)) {
1632
+ event.preventDefault();
1633
+ reportCommandFailure(new Error("Selected cells are still loading; copy again when ready"));
1634
+ return;
1635
+ }
1636
+ const output = [];
1637
+ for (let row = selected.top; row <= selected.bottom; row += 1) {
1638
+ const fields = [];
1639
+ for (let col = selected.left; col <= selected.right; col += 1) {
1640
+ fields.push(escapeSpreadsheetClipboardField(projection.readCell(row, col)?.input ?? ""));
1641
+ }
1642
+ output.push(fields.join(" "));
1643
+ }
1644
+ event.clipboardData.setData("text/plain", output.join("\r\n"));
1645
+ event.preventDefault();
1646
+ setCommandFailure(null);
1647
+ },
1648
+ [editMode, projection, reportCommandFailure]
1649
+ );
1650
+ const beginEditing = useCallback(
1651
+ (initial, cell = selectionRef.current.focus) => {
1652
+ if (!editable) return;
1653
+ ensureVisible(cell);
1654
+ const current = projection.readCell(cell.row, cell.col)?.input ?? "";
1655
+ initialDraftRef.current = current;
1656
+ selectDraftOnFocusRef.current = initial === void 0;
1657
+ setDraft(initial ?? current);
1658
+ setEditMode("cell");
1659
+ },
1660
+ [editable, ensureVisible, projection]
1661
+ );
1662
+ const onGridKeyDown = useCallback(
1663
+ (event) => {
1664
+ if (editMode !== null) return;
1665
+ if (event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229) return;
1666
+ switch (event.key) {
1667
+ case "ArrowUp":
1668
+ event.preventDefault();
1669
+ moveSelection(-1, 0, event.shiftKey);
1670
+ return;
1671
+ case "ArrowDown":
1672
+ event.preventDefault();
1673
+ moveSelection(1, 0, event.shiftKey);
1674
+ return;
1675
+ case "ArrowLeft":
1676
+ event.preventDefault();
1677
+ moveSelection(0, -1, event.shiftKey);
1678
+ return;
1679
+ case "ArrowRight":
1680
+ event.preventDefault();
1681
+ moveSelection(0, 1, event.shiftKey);
1682
+ return;
1683
+ case "Tab":
1684
+ event.preventDefault();
1685
+ moveSelection(0, event.shiftKey ? -1 : 1, false);
1686
+ return;
1687
+ case "Enter":
1688
+ case "F2":
1689
+ event.preventDefault();
1690
+ beginEditing();
1691
+ return;
1692
+ case "Backspace":
1693
+ case "Delete":
1694
+ event.preventDefault();
1695
+ clearSelection();
1696
+ return;
1697
+ case "Home": {
1698
+ event.preventDefault();
1699
+ const target = event.metaKey || event.ctrlKey ? { row: 0, col: 0 } : { row: selection.focus.row, col: 0 };
1700
+ updateSelection({ sheetId: projection.sheetId, anchor: target, focus: target });
1701
+ ensureVisible(target);
1702
+ return;
1703
+ }
1704
+ case "End": {
1705
+ event.preventDefault();
1706
+ const used = projection.usedRange;
1707
+ const target = event.metaKey || event.ctrlKey ? {
1708
+ row: Math.min(rowCount - 1, (used?.row ?? 0) + (used?.rowCount ?? 1) - 1),
1709
+ col: Math.min(columnCount - 1, (used?.col ?? 0) + (used?.colCount ?? 1) - 1)
1710
+ } : { row: selection.focus.row, col: columnCount - 1 };
1711
+ updateSelection({ sheetId: projection.sheetId, anchor: target, focus: target });
1712
+ ensureVisible(target);
1713
+ return;
1714
+ }
1715
+ }
1716
+ if (editable && !event.metaKey && !event.ctrlKey && !event.altKey && event.key.length === 1) {
1717
+ event.preventDefault();
1718
+ beginEditing(event.key);
1719
+ }
1720
+ },
1721
+ [
1722
+ beginEditing,
1723
+ clearSelection,
1724
+ columnCount,
1725
+ editMode,
1726
+ ensureVisible,
1727
+ moveSelection,
1728
+ editable,
1729
+ rowCount,
1730
+ selection.focus.row,
1731
+ updateSelection,
1732
+ projection.sheetId,
1733
+ projection.usedRange
1734
+ ]
1735
+ );
1736
+ const onEditorKeyDown = useCallback(
1737
+ (event) => {
1738
+ if (composingRef.current || event.nativeEvent.isComposing) return;
1739
+ if (event.key === "Enter") {
1740
+ event.preventDefault();
1741
+ commitDraft();
1742
+ } else if (event.key === "Escape") {
1743
+ event.preventDefault();
1744
+ committingRef.current = true;
1745
+ setDraft(projection.readCell(selection.focus.row, selection.focus.col)?.input ?? "");
1746
+ setEditMode(null);
1747
+ viewportRef.current?.focus();
1748
+ queueMicrotask(() => {
1749
+ committingRef.current = false;
1750
+ });
1751
+ }
1752
+ },
1753
+ [commitDraft, projection, selection.focus.col, selection.focus.row]
1754
+ );
1755
+ const onEditorCompositionStart = useCallback(() => {
1756
+ composingRef.current = true;
1757
+ }, []);
1758
+ const onEditorCompositionEnd = useCallback(
1759
+ (input) => {
1760
+ composingRef.current = false;
1761
+ if (!commitAfterCompositionRef.current) return;
1762
+ commitAfterCompositionRef.current = false;
1763
+ commitDraft(input);
1764
+ },
1765
+ [commitDraft]
1766
+ );
1767
+ const commitEditorOnBlur = useCallback(
1768
+ (mode, input) => {
1769
+ if (editMode !== mode) return;
1770
+ if (composingRef.current) {
1771
+ commitAfterCompositionRef.current = true;
1772
+ return;
1773
+ }
1774
+ commitDraft(input);
1775
+ },
1776
+ [commitDraft, editMode]
1777
+ );
1778
+ const selectCell = useCallback(
1779
+ (cell, extend) => {
1780
+ const current = selectionRef.current;
1781
+ const next = {
1782
+ sheetId: projection.sheetId,
1783
+ anchor: extend ? current.anchor : cell,
1784
+ focus: cell
1785
+ };
1786
+ updateSelection(next);
1787
+ setEditMode(null);
1788
+ viewportRef.current?.focus();
1789
+ },
1790
+ [projection.sheetId, updateSelection]
1791
+ );
1792
+ const hitTestCell = useCallback(
1793
+ (clientX, clientY) => {
1794
+ const element = viewportRef.current;
1795
+ if (!element) return null;
1796
+ const bounds = element.getBoundingClientRect();
1797
+ const viewportX = clientX - bounds.left;
1798
+ const viewportY = clientY - bounds.top;
1799
+ const viewportWidth = element.clientWidth || FALLBACK_VIEWPORT_WIDTH;
1800
+ const viewportHeight = element.clientHeight || FALLBACK_VIEWPORT_HEIGHT;
1801
+ if (viewportX < ROW_HEADER_WIDTH || viewportY < COLUMN_HEADER_HEIGHT || viewportX >= viewportWidth || viewportY >= viewportHeight) {
1802
+ return null;
1803
+ }
1804
+ const horizontalProjection2 = buildScrollProjection(columns, ROW_HEADER_WIDTH, viewportWidth);
1805
+ const verticalProjection2 = buildScrollProjection(rows, COLUMN_HEADER_HEIGHT, viewportHeight);
1806
+ const logicalX = physicalToLogicalScroll(element.scrollLeft, horizontalProjection2) + viewportX - ROW_HEADER_WIDTH;
1807
+ const logicalY = physicalToLogicalScroll(element.scrollTop, verticalProjection2) + viewportY - COLUMN_HEADER_HEIGHT;
1808
+ if (logicalX < 0 || logicalY < 0 || logicalX >= columns.total || logicalY >= rows.total) {
1809
+ return null;
1810
+ }
1811
+ return {
1812
+ row: Math.min(rowCount - 1, indexAtOffset(rows, logicalY)),
1813
+ col: Math.min(columnCount - 1, indexAtOffset(columns, logicalX))
1814
+ };
1815
+ },
1816
+ [columnCount, columns, rowCount, rows]
1817
+ );
1818
+ const onGridPointerDown = useCallback(
1819
+ (event) => {
1820
+ if (event.button !== 0) return;
1821
+ const cell = hitTestCell(event.clientX, event.clientY);
1822
+ if (!cell) return;
1823
+ event.preventDefault();
1824
+ const current = selectionRef.current;
1825
+ const anchor = event.shiftKey ? current.anchor : cell;
1826
+ selectingRef.current = { pointerId: event.pointerId, anchor };
1827
+ try {
1828
+ event.currentTarget.setPointerCapture(event.pointerId);
1829
+ } catch {
1830
+ }
1831
+ updateSelection({ sheetId: projection.sheetId, anchor, focus: cell });
1832
+ setEditMode(null);
1833
+ event.currentTarget.focus();
1834
+ },
1835
+ [hitTestCell, projection.sheetId, updateSelection]
1836
+ );
1837
+ const onGridPointerMove = useCallback(
1838
+ (event) => {
1839
+ const selecting = selectingRef.current;
1840
+ if (!selecting || selecting.pointerId !== event.pointerId) return;
1841
+ const cell = hitTestCell(event.clientX, event.clientY);
1842
+ if (!cell) return;
1843
+ const current = selectionRef.current;
1844
+ if (current.focus.row === cell.row && current.focus.col === cell.col) return;
1845
+ updateSelection({ sheetId: projection.sheetId, anchor: selecting.anchor, focus: cell });
1846
+ },
1847
+ [hitTestCell, projection.sheetId, updateSelection]
1848
+ );
1849
+ const stopPointerSelection = useCallback((pointerId) => {
1850
+ if (pointerId !== void 0 && selectingRef.current?.pointerId !== pointerId) return;
1851
+ selectingRef.current = null;
1852
+ }, []);
1853
+ useEffect(() => {
1854
+ if (typeof window === "undefined") return;
1855
+ const stopSelecting = () => stopPointerSelection();
1856
+ window.addEventListener("pointerup", stopSelecting);
1857
+ return () => window.removeEventListener("pointerup", stopSelecting);
1858
+ }, [stopPointerSelection]);
1859
+ const horizontalProjection = buildScrollProjection(columns, ROW_HEADER_WIDTH, viewport.width);
1860
+ const verticalProjection = buildScrollProjection(rows, COLUMN_HEADER_HEIGHT, viewport.height);
1861
+ const physicalScrollLeft = clampPhysicalScroll(viewport.scrollLeft, horizontalProjection);
1862
+ const physicalScrollTop = clampPhysicalScroll(viewport.scrollTop, verticalProjection);
1863
+ const logicalScrollLeft = physicalToLogicalScroll(viewport.scrollLeft, horizontalProjection);
1864
+ const logicalScrollTop = physicalToLogicalScroll(viewport.scrollTop, verticalProjection);
1865
+ const visibleRowWindow = visibleInterval(
1866
+ rows,
1867
+ logicalScrollTop,
1868
+ Math.max(1, viewport.height - COLUMN_HEADER_HEIGHT),
1869
+ 0
1870
+ );
1871
+ const visibleColumnWindow = visibleInterval(
1872
+ columns,
1873
+ logicalScrollLeft,
1874
+ Math.max(1, viewport.width - ROW_HEADER_WIDTH),
1875
+ 0
1876
+ );
1877
+ const projectedRowWindow = visibleInterval(
1878
+ rows,
1879
+ logicalScrollTop,
1880
+ Math.max(1, viewport.height - COLUMN_HEADER_HEIGHT),
1881
+ Math.max(0, overscanRows)
1882
+ );
1883
+ const projectedColumnWindow = visibleInterval(
1884
+ columns,
1885
+ logicalScrollLeft,
1886
+ Math.max(1, viewport.width - ROW_HEADER_WIDTH),
1887
+ Math.max(0, overscanColumns)
1888
+ );
1889
+ const { rows: rowWindow, columns: columnWindow } = boundVisibleWindows(
1890
+ projectedRowWindow,
1891
+ projectedColumnWindow,
1892
+ selection.focus
1893
+ );
1894
+ const visibleRows = indexesBetween(rowWindow.start, rowWindow.end);
1895
+ const visibleColumns = indexesBetween(columnWindow.start, columnWindow.end);
1896
+ const rowLayout = visibleRows.map((row) => ({
1897
+ row,
1898
+ top: physicalScrollTop + COLUMN_HEADER_HEIGHT + axisOffsetAt(rows, row) - logicalScrollTop,
1899
+ height: axisSizeAt(rows, row)
1900
+ }));
1901
+ const columnLayout = visibleColumns.map((col) => ({
1902
+ col,
1903
+ left: physicalScrollLeft + ROW_HEADER_WIDTH + axisOffsetAt(columns, col) - logicalScrollLeft,
1904
+ width: axisSizeAt(columns, col)
1905
+ }));
1906
+ const activeCellVisible = selection.focus.row >= rowWindow.start && selection.focus.row < rowWindow.end && selection.focus.col >= columnWindow.start && selection.focus.col < columnWindow.end;
1907
+ const activeCellId = `${reactId}-cell-${selection.focus.row}-${selection.focus.col}`;
1908
+ const hiddenActiveValue = activeCellVisible ? null : projection.readCell(selection.focus.row, selection.focus.col)?.value;
1909
+ useEffect(() => {
1910
+ if (!onViewportChange) return;
1911
+ const next = {
1912
+ sheetId: projection.sheetId,
1913
+ rowStart: visibleRowWindow.start,
1914
+ rowEnd: visibleRowWindow.end,
1915
+ columnStart: visibleColumnWindow.start,
1916
+ columnEnd: visibleColumnWindow.end,
1917
+ overscanRowStart: projectedRowWindow.start,
1918
+ overscanRowEnd: projectedRowWindow.end,
1919
+ overscanColumnStart: projectedColumnWindow.start,
1920
+ overscanColumnEnd: projectedColumnWindow.end,
1921
+ logicalScrollLeft,
1922
+ logicalScrollTop,
1923
+ viewportWidth: viewport.width,
1924
+ viewportHeight: viewport.height
1925
+ };
1926
+ let active = true;
1927
+ const notify = () => {
1928
+ if (active) onViewportChange(next);
1929
+ };
1930
+ if (typeof globalThis.requestAnimationFrame === "function") {
1931
+ const frame = globalThis.requestAnimationFrame(notify);
1932
+ return () => {
1933
+ active = false;
1934
+ globalThis.cancelAnimationFrame?.(frame);
1935
+ };
1936
+ }
1937
+ queueMicrotask(notify);
1938
+ return () => {
1939
+ active = false;
1940
+ };
1941
+ }, [
1942
+ logicalScrollLeft,
1943
+ logicalScrollTop,
1944
+ onViewportChange,
1945
+ projectedColumnWindow.end,
1946
+ projectedColumnWindow.start,
1947
+ projectedRowWindow.end,
1948
+ projectedRowWindow.start,
1949
+ viewport.height,
1950
+ viewport.width,
1951
+ visibleColumnWindow.end,
1952
+ visibleColumnWindow.start,
1953
+ visibleRowWindow.end,
1954
+ visibleRowWindow.start,
1955
+ projection.sheetId
1956
+ ]);
1957
+ useClientLayoutEffect(() => {
1958
+ void canvasEpoch;
1959
+ const canvas = canvasRef.current;
1960
+ const viewportElement = viewportRef.current;
1961
+ const renderer = canvasRendererRef.current;
1962
+ if (!canvas || !viewportElement || !renderer) return;
1963
+ const normalized = normalizeSelection(selection);
1964
+ const theme = spreadsheetCanvasTheme(viewportElement);
1965
+ const paintStartedAt = performance.now();
1966
+ const stats = renderer.paint({
1967
+ canvas,
1968
+ projection,
1969
+ rows: canvasRows,
1970
+ columns: canvasColumns,
1971
+ selection: {
1972
+ ...normalized,
1973
+ focusRow: selection.focus.row,
1974
+ focusColumn: selection.focus.col
1975
+ },
1976
+ logicalScrollLeft,
1977
+ logicalScrollTop,
1978
+ viewportWidth: viewport.width,
1979
+ viewportHeight: viewport.height,
1980
+ rowHeaderWidth: ROW_HEADER_WIDTH,
1981
+ columnHeaderHeight: COLUMN_HEADER_HEIGHT,
1982
+ dimensionRevision,
1983
+ devicePixelRatio: viewport.devicePixelRatio,
1984
+ theme
1985
+ });
1986
+ const paintDurationMs = performance.now() - paintStartedAt;
1987
+ if (!stats) {
1988
+ if (canvasReady) setCanvasReady(false);
1989
+ return;
1990
+ }
1991
+ canvas.dataset.ogTileCacheSize = String(stats.cacheSize);
1992
+ canvas.dataset.ogPaintedTiles = String(stats.paintedTiles);
1993
+ canvas.dataset.ogReusedTiles = String(stats.reusedTiles);
1994
+ canvas.dataset.ogUncachedTiles = String(stats.uncachedTiles);
1995
+ canvas.dataset.ogDevicePixelRatio = String(stats.devicePixelRatio);
1996
+ canvas.dataset.ogLogicalScrollLeft = String(logicalScrollLeft);
1997
+ canvas.dataset.ogLogicalScrollTop = String(logicalScrollTop);
1998
+ canvas.dataset.ogPaintDurationMs = String(paintDurationMs);
1999
+ if (!canvasReady) setCanvasReady(true);
2000
+ }, [
2001
+ canvasColumns,
2002
+ canvasEpoch,
2003
+ canvasReady,
2004
+ canvasRows,
2005
+ dimensionRevision,
2006
+ logicalScrollLeft,
2007
+ logicalScrollTop,
2008
+ projection,
2009
+ revision,
2010
+ selection,
2011
+ viewport.devicePixelRatio,
2012
+ viewport.height,
2013
+ viewport.width
2014
+ ]);
2015
+ return /* @__PURE__ */ jsxs(
2016
+ "div",
2017
+ {
2018
+ className: cn("relative flex h-full min-w-0 flex-col bg-og-bg", className),
2019
+ "data-og-command-state": commandFailure ? "error" : pendingCommands > 0 ? "pending" : "idle",
2020
+ style: { minHeight: 256 },
2021
+ children: [
2022
+ /* @__PURE__ */ jsxs("div", { className: "flex min-h-9 shrink-0 items-center border-b border-og-border bg-og-surface-1", children: [
2023
+ /* @__PURE__ */ jsx(
2024
+ "output",
2025
+ {
2026
+ "aria-label": "Selected range",
2027
+ className: "shrink-0 border-r border-og-border px-2 font-mono text-og-xs text-og-fg-muted",
2028
+ style: { width: 96 },
2029
+ children: selectionName(selection)
2030
+ }
2031
+ ),
2032
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "px-2 font-og-mono text-og-sm italic text-og-fg-subtle", children: "fx" }),
2033
+ /* @__PURE__ */ jsx(
2034
+ "input",
2035
+ {
2036
+ "aria-label": "Formula or value",
2037
+ readOnly: !editable,
2038
+ "aria-invalid": commandFailure ? "true" : void 0,
2039
+ value: draft,
2040
+ onFocus: () => {
2041
+ if (editable) {
2042
+ const current = projection.readCell(selection.focus.row, selection.focus.col)?.input ?? "";
2043
+ initialDraftRef.current = current;
2044
+ setDraft(current);
2045
+ setEditMode("formula");
2046
+ }
2047
+ },
2048
+ onInput: (event) => setDraft(event.currentTarget.value),
2049
+ onKeyDown: onEditorKeyDown,
2050
+ onCompositionStart: onEditorCompositionStart,
2051
+ onCompositionEnd: (event) => onEditorCompositionEnd(event.currentTarget.value),
2052
+ onBlur: (event) => commitEditorOnBlur("formula", event.currentTarget.value),
2053
+ className: "h-8 min-w-0 flex-1 bg-transparent px-1 font-mono text-og-sm text-og-fg outline-hidden placeholder:text-og-fg-subtle"
2054
+ }
2055
+ )
2056
+ ] }),
2057
+ /* @__PURE__ */ jsx("div", { role: "status", "aria-live": "polite", className: "sr-only", children: commandFailure?.message ?? (pendingCommands > 0 ? "Saving spreadsheet changes" : "") }),
2058
+ commandFailure ? /* @__PURE__ */ jsxs(
2059
+ "div",
2060
+ {
2061
+ role: "alert",
2062
+ className: "absolute bottom-2 left-2 z-50 flex 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",
2063
+ style: { maxWidth: "calc(100% - 1rem)" },
2064
+ children: [
2065
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: commandFailure.message }),
2066
+ commandFailure.retry ? /* @__PURE__ */ jsx(
2067
+ "button",
2068
+ {
2069
+ type: "button",
2070
+ onClick: commandFailure.retry,
2071
+ 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",
2072
+ children: "Retry"
2073
+ }
2074
+ ) : null
2075
+ ]
2076
+ }
2077
+ ) : null,
2078
+ /* @__PURE__ */ jsx(
2079
+ "div",
2080
+ {
2081
+ ref: viewportRef,
2082
+ role: "grid",
2083
+ tabIndex: 0,
2084
+ "aria-label": `${projection.sheetName} spreadsheet`,
2085
+ "aria-rowcount": rowCount,
2086
+ "aria-colcount": columnCount,
2087
+ "aria-multiselectable": "true",
2088
+ "aria-readonly": !editable,
2089
+ "aria-busy": pendingCommands > 0 ? "true" : void 0,
2090
+ "aria-activedescendant": activeCellId,
2091
+ onKeyDown: onGridKeyDown,
2092
+ onCopy: copySelection,
2093
+ onPaste: pasteClipboard,
2094
+ onCompositionEnd: (event) => {
2095
+ if (editMode === null && editable && event.data) beginEditing(event.data);
2096
+ },
2097
+ onPointerDown: onGridPointerDown,
2098
+ onPointerMove: onGridPointerMove,
2099
+ onPointerUp: (event) => stopPointerSelection(event.pointerId),
2100
+ onPointerCancel: (event) => stopPointerSelection(event.pointerId),
2101
+ onDoubleClick: (event) => {
2102
+ const cell = hitTestCell(event.clientX, event.clientY);
2103
+ if (!cell) return;
2104
+ selectCell(cell, event.shiftKey);
2105
+ beginEditing(void 0, cell);
2106
+ },
2107
+ onScroll: (event) => {
2108
+ const element = event.currentTarget;
2109
+ setViewport((current) => ({
2110
+ ...current,
2111
+ scrollLeft: element.scrollLeft,
2112
+ scrollTop: element.scrollTop
2113
+ }));
2114
+ },
2115
+ className: "relative min-h-0 min-w-0 flex-1 overflow-auto overscroll-contain bg-og-surface-1 text-og-sm outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent",
2116
+ children: /* @__PURE__ */ jsxs(
2117
+ "div",
2118
+ {
2119
+ role: "rowgroup",
2120
+ className: "relative",
2121
+ style: {
2122
+ width: horizontalProjection.physicalTotal,
2123
+ height: verticalProjection.physicalTotal
2124
+ },
2125
+ children: [
2126
+ /* @__PURE__ */ jsx(
2127
+ "canvas",
2128
+ {
2129
+ ref: canvasRef,
2130
+ "aria-hidden": "true",
2131
+ "data-og-spreadsheet-canvas": "retained-tiles",
2132
+ className: "pointer-events-none absolute z-0 block",
2133
+ style: {
2134
+ left: physicalScrollLeft,
2135
+ top: physicalScrollTop,
2136
+ width: viewport.width,
2137
+ height: viewport.height
2138
+ }
2139
+ }
2140
+ ),
2141
+ /* @__PURE__ */ jsx(
2142
+ "div",
2143
+ {
2144
+ "aria-hidden": true,
2145
+ className: "sticky left-0 top-0 z-40 border-b border-r border-og-border bg-og-surface-2",
2146
+ style: {
2147
+ width: ROW_HEADER_WIDTH,
2148
+ height: COLUMN_HEADER_HEIGHT,
2149
+ ...canvasReady ? { background: "transparent", borderColor: "transparent" } : void 0
2150
+ }
2151
+ }
2152
+ ),
2153
+ /* @__PURE__ */ jsx("div", { role: "row", className: "contents", children: columnLayout.map(({ col, left, width }) => /* @__PURE__ */ jsx(
2154
+ "div",
2155
+ {
2156
+ role: "columnheader",
2157
+ "aria-colindex": col + 1,
2158
+ className: "sticky top-0 z-30 grid select-none place-items-center overflow-hidden border-b border-r border-og-border bg-og-surface-2 text-og-xs font-medium text-og-fg-muted",
2159
+ style: {
2160
+ position: "absolute",
2161
+ left,
2162
+ top: physicalScrollTop,
2163
+ width,
2164
+ height: COLUMN_HEADER_HEIGHT,
2165
+ ...canvasReady ? {
2166
+ background: "transparent",
2167
+ borderColor: "transparent",
2168
+ color: "transparent"
2169
+ } : void 0
2170
+ },
2171
+ children: columnName2(col)
2172
+ },
2173
+ `header-${col}`
2174
+ )) }),
2175
+ rowLayout.map(({ row, top, height }) => {
2176
+ return /* @__PURE__ */ jsxs(
2177
+ "div",
2178
+ {
2179
+ role: "row",
2180
+ "aria-rowindex": row + 1,
2181
+ className: "absolute left-0",
2182
+ style: {
2183
+ top,
2184
+ width: horizontalProjection.physicalTotal,
2185
+ height
2186
+ },
2187
+ children: [
2188
+ /* @__PURE__ */ jsx(
2189
+ "div",
2190
+ {
2191
+ role: "rowheader",
2192
+ className: "sticky left-0 z-20 grid h-full select-none place-items-center border-b border-r border-og-border bg-og-surface-2 text-og-xs text-og-fg-muted",
2193
+ style: {
2194
+ width: ROW_HEADER_WIDTH,
2195
+ ...canvasReady ? {
2196
+ background: "transparent",
2197
+ borderColor: "transparent",
2198
+ color: "transparent"
2199
+ } : void 0
2200
+ },
2201
+ children: row + 1
2202
+ }
2203
+ ),
2204
+ columnLayout.map(({ col, left, width }) => {
2205
+ const active = selection.focus.row === row && selection.focus.col === col;
2206
+ const selected = isCellSelected(selection, row, col);
2207
+ const data = projection.readCell(row, col) ?? {
2208
+ value: null,
2209
+ input: "",
2210
+ format: {}
2211
+ };
2212
+ const value = data.value;
2213
+ const address = cellName({ row, col });
2214
+ const error = typeof value === "string" && value.startsWith("#");
2215
+ return /* @__PURE__ */ jsx(
2216
+ "div",
2217
+ {
2218
+ id: `${reactId}-cell-${row}-${col}`,
2219
+ role: "gridcell",
2220
+ "aria-colindex": col + 1,
2221
+ "aria-selected": selected,
2222
+ "aria-label": `${address}${value === null ? "" : `, ${displayValue2(value)}`}`,
2223
+ "data-og-cell": address,
2224
+ className: cn(
2225
+ "pointer-events-none absolute top-0 flex h-full min-w-0 overflow-hidden border-b border-r border-og-border px-1.5 text-og-fg",
2226
+ !canvasReady && selected && !active && "bg-og-accent/10",
2227
+ !canvasReady && active && "z-10 bg-og-accent/10 ring-2 ring-inset ring-og-accent",
2228
+ !canvasReady && error && "text-og-status-failed"
2229
+ ),
2230
+ style: {
2231
+ left,
2232
+ width,
2233
+ ...cellStyle(data.format),
2234
+ ...canvasReady ? {
2235
+ background: "transparent",
2236
+ borderColor: "transparent",
2237
+ color: "transparent"
2238
+ } : void 0
2239
+ },
2240
+ children: active && editMode === "cell" ? /* @__PURE__ */ jsx(
2241
+ "input",
2242
+ {
2243
+ ref: editorRef,
2244
+ "aria-label": `Edit ${address}`,
2245
+ value: draft,
2246
+ onInput: (event) => setDraft(event.currentTarget.value),
2247
+ onKeyDown: onEditorKeyDown,
2248
+ onCompositionStart: onEditorCompositionStart,
2249
+ onCompositionEnd: (event) => onEditorCompositionEnd(event.currentTarget.value),
2250
+ onBlur: (event) => commitEditorOnBlur("cell", event.currentTarget.value),
2251
+ onPointerDown: (event) => event.stopPropagation(),
2252
+ className: "pointer-events-auto absolute inset-0 size-full bg-og-surface-1 px-1.5 font-mono text-og-sm text-og-fg outline-hidden ring-2 ring-inset ring-og-accent"
2253
+ }
2254
+ ) : /* @__PURE__ */ jsx("span", { className: "block min-w-0 overflow-hidden text-ellipsis", children: displayValue2(value) })
2255
+ },
2256
+ col
2257
+ );
2258
+ })
2259
+ ]
2260
+ },
2261
+ `row-${row}`
2262
+ );
2263
+ }),
2264
+ !activeCellVisible ? /* @__PURE__ */ jsx("div", { role: "row", "aria-rowindex": selection.focus.row + 1, className: "sr-only", children: /* @__PURE__ */ jsx(
2265
+ "div",
2266
+ {
2267
+ id: activeCellId,
2268
+ role: "gridcell",
2269
+ "aria-colindex": selection.focus.col + 1,
2270
+ "aria-selected": "true",
2271
+ "aria-label": `${cellName(selection.focus)}${hiddenActiveValue == null ? "" : `, ${displayValue2(hiddenActiveValue)}`}`
2272
+ }
2273
+ ) }) : null
2274
+ ]
2275
+ }
2276
+ )
2277
+ }
2278
+ )
2279
+ ]
2280
+ }
2281
+ );
2282
+ }
2283
+
2284
+ // src/components/artifacts/editable-spreadsheet.tsx
2285
+ import {
2286
+ SPREADSHEET_ARTIFACT_COMMAND_VERSION,
2287
+ editableArtifactStableId,
2288
+ spreadsheetSheetId
2289
+ } from "@opengeni/sdk/editable-artifacts";
2290
+ import { PlusIcon as PlusIcon2 } from "lucide-react";
2291
+ import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
2292
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
2293
+ var EXCEL_MAX_ROWS = 1048576;
2294
+ var EXCEL_MAX_COLUMNS = 16384;
2295
+ var INITIAL_VIEWPORT_ROWS = 64;
2296
+ var INITIAL_VIEWPORT_COLUMNS = 32;
2297
+ var MAX_INTERACTIVE_QUERY_CELLS = 65536;
2298
+ var MAX_INTERACTIVE_QUERY_BYTES = 8 * 1024 * 1024;
2299
+ var EMPTY_FORMAT = Object.freeze({});
2300
+ var EMPTY_SHEETS = [];
2301
+ function EditableSpreadsheetGrid({
2302
+ session,
2303
+ sheet,
2304
+ metadataRevision,
2305
+ readOnly = false,
2306
+ rowCount: requestedRowCount,
2307
+ columnCount: requestedColumnCount,
2308
+ overscanRows = 3,
2309
+ overscanColumns = 2,
2310
+ onSelectionChange,
2311
+ onCommit,
2312
+ onCommandError,
2313
+ onViewportChange,
2314
+ className
2315
+ }) {
2316
+ const rowCount = boundedSheetCount(requestedRowCount, EXCEL_MAX_ROWS, sheet.usedBounds?.endRow);
2317
+ const columnCount = boundedSheetCount(
2318
+ requestedColumnCount,
2319
+ EXCEL_MAX_COLUMNS,
2320
+ sheet.usedBounds?.endColumn
2321
+ );
2322
+ const [state, setState] = useState2(() => ({
2323
+ query: initialViewportQuery(sheet.sheetId, rowCount, columnCount),
2324
+ projection: null,
2325
+ error: null
2326
+ }));
2327
+ const activeCellRef = useRef2({ row: 0, column: 0 });
2328
+ useEffect2(() => {
2329
+ setState({
2330
+ query: initialViewportQuery(sheet.sheetId, rowCount, columnCount),
2331
+ projection: null,
2332
+ error: null
2333
+ });
2334
+ activeCellRef.current = { row: 0, column: 0 };
2335
+ }, [columnCount, rowCount, session, sheet.sheetId]);
2336
+ useEffect2(() => {
2337
+ const query = state.query;
2338
+ return session.subscribeSpreadsheetViewport(
2339
+ query,
2340
+ (projection2) => {
2341
+ if (!sameViewport(projection2, query)) return;
2342
+ if (sheet.generationId !== null && projection2.generationId !== sheet.generationId) {
2343
+ setState(
2344
+ (current) => sameViewportQuery(current.query, query) ? {
2345
+ ...current,
2346
+ projection: null,
2347
+ error: new Error("Spreadsheet generation changed; refreshing metadata")
2348
+ } : current
2349
+ );
2350
+ return;
2351
+ }
2352
+ setState(
2353
+ (current) => sameViewportQuery(current.query, query) ? { query, projection: projection2, error: null } : current
2354
+ );
2355
+ },
2356
+ {
2357
+ onError(error) {
2358
+ setState(
2359
+ (current) => sameViewportQuery(current.query, query) ? { ...current, error } : current
2360
+ );
2361
+ }
2362
+ }
2363
+ );
2364
+ }, [session, sheet.generationId, state.query]);
2365
+ const projection = useMemo2(
2366
+ () => projectSdkViewport(
2367
+ sheet,
2368
+ metadataRevision,
2369
+ state.query,
2370
+ state.projection,
2371
+ rowCount,
2372
+ columnCount
2373
+ ),
2374
+ [columnCount, metadataRevision, rowCount, sheet, state.projection, state.query]
2375
+ );
2376
+ const generation = useMemo2(() => sheetGeneration(sheet), [sheet]);
2377
+ const editable = !readOnly && generation !== null;
2378
+ const handleCommit = useCallback2(
2379
+ async (commit) => {
2380
+ if (!generation) throw new Error("This sheet generation is not writable yet");
2381
+ const input = spreadsheetCellInput(commit.input, commit.kind);
2382
+ await session.applySpreadsheetCommands({
2383
+ version: SPREADSHEET_ARTIFACT_COMMAND_VERSION,
2384
+ commands: [
2385
+ {
2386
+ kind: "cells.set",
2387
+ sheet: generation,
2388
+ anchor: { row: commit.cell.row, column: commit.cell.col },
2389
+ rows: 1,
2390
+ columns: 1,
2391
+ cells: [input]
2392
+ }
2393
+ ]
2394
+ });
2395
+ onCommit?.(commit);
2396
+ },
2397
+ [generation, onCommit, session]
2398
+ );
2399
+ const handleClear = useCallback2(
2400
+ async (selection) => {
2401
+ if (!generation) throw new Error("This sheet generation is not writable yet");
2402
+ const top = Math.min(selection.anchor.row, selection.focus.row);
2403
+ const bottom = Math.max(selection.anchor.row, selection.focus.row);
2404
+ const left = Math.min(selection.anchor.col, selection.focus.col);
2405
+ const right = Math.max(selection.anchor.col, selection.focus.col);
2406
+ await session.applySpreadsheetCommands({
2407
+ version: SPREADSHEET_ARTIFACT_COMMAND_VERSION,
2408
+ commands: [
2409
+ {
2410
+ kind: "range.clear",
2411
+ sheet: generation,
2412
+ range: {
2413
+ start: { row: top, column: left },
2414
+ end: { row: bottom, column: right }
2415
+ }
2416
+ }
2417
+ ]
2418
+ });
2419
+ },
2420
+ [generation, session]
2421
+ );
2422
+ const handleCommitRange = useCallback2(
2423
+ async (commit) => {
2424
+ if (!generation) throw new Error("This sheet generation is not writable yet");
2425
+ await session.applySpreadsheetCommands({
2426
+ version: SPREADSHEET_ARTIFACT_COMMAND_VERSION,
2427
+ commands: [
2428
+ {
2429
+ kind: "cells.set",
2430
+ sheet: generation,
2431
+ anchor: { row: commit.anchor.row, column: commit.anchor.col },
2432
+ rows: commit.rows,
2433
+ columns: commit.columns,
2434
+ cells: commit.inputs.map(
2435
+ (input) => spreadsheetCellInput(input, input.startsWith("=") ? "formula" : "value")
2436
+ )
2437
+ }
2438
+ ]
2439
+ });
2440
+ },
2441
+ [generation, session]
2442
+ );
2443
+ const handleSelection = useCallback2(
2444
+ (selection) => {
2445
+ activeCellRef.current = {
2446
+ row: selection.focus.row,
2447
+ column: selection.focus.col
2448
+ };
2449
+ setState((current) => {
2450
+ if (queryContains(current.query, selection.focus.row, selection.focus.col)) return current;
2451
+ return {
2452
+ query: boundedViewportQuery(
2453
+ sheet.sheetId,
2454
+ selection.focus.row,
2455
+ selection.focus.row + 1,
2456
+ selection.focus.col,
2457
+ selection.focus.col + 1,
2458
+ rowCount,
2459
+ columnCount,
2460
+ activeCellRef.current
2461
+ ),
2462
+ projection: current.projection,
2463
+ error: null
2464
+ };
2465
+ });
2466
+ onSelectionChange?.(selection);
2467
+ },
2468
+ [columnCount, onSelectionChange, rowCount, sheet.sheetId]
2469
+ );
2470
+ const handleViewport = useCallback2(
2471
+ (viewport) => {
2472
+ const next = boundedViewportQuery(
2473
+ sheet.sheetId,
2474
+ viewport.overscanRowStart,
2475
+ viewport.overscanRowEnd,
2476
+ viewport.overscanColumnStart,
2477
+ viewport.overscanColumnEnd,
2478
+ rowCount,
2479
+ columnCount,
2480
+ activeCellRef.current
2481
+ );
2482
+ setState(
2483
+ (current) => sameViewportQuery(current.query, next) ? current : { query: next, projection: current.projection, error: null }
2484
+ );
2485
+ onViewportChange?.(viewport);
2486
+ },
2487
+ [columnCount, onViewportChange, rowCount, sheet.sheetId]
2488
+ );
2489
+ return /* @__PURE__ */ jsxs2("div", { className: cn("relative h-full min-h-0", className), children: [
2490
+ /* @__PURE__ */ jsx2(
2491
+ SpreadsheetProjectionGrid,
2492
+ {
2493
+ projection,
2494
+ readOnly: !editable,
2495
+ overscanRows,
2496
+ overscanColumns,
2497
+ onSelectionChange: handleSelection,
2498
+ commit: editable ? handleCommit : void 0,
2499
+ commitRange: editable ? handleCommitRange : void 0,
2500
+ clear: editable ? handleClear : void 0,
2501
+ onCommandError,
2502
+ onViewportChange: handleViewport
2503
+ }
2504
+ ),
2505
+ state.error ? /* @__PURE__ */ jsx2(
2506
+ "output",
2507
+ {
2508
+ role: "status",
2509
+ className: "pointer-events-none absolute bottom-2 left-2 z-50 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",
2510
+ style: { maxWidth: "min(28rem, calc(100% - 1rem))" },
2511
+ children: state.error.message
2512
+ }
2513
+ ) : null
2514
+ ] });
2515
+ }
2516
+ function EditableSpreadsheetArtifactSurface({
2517
+ session,
2518
+ title = "Workbook",
2519
+ subtitle,
2520
+ actions,
2521
+ initialSheetId,
2522
+ allowAddSheet = true,
2523
+ readOnly = false,
2524
+ ...gridProps
2525
+ }) {
2526
+ const { metadata, error: metadataError } = useSpreadsheetMetadata(session);
2527
+ const view = useEditableArtifactView(session);
2528
+ const [activeSheetId, setActiveSheetId] = useState2(initialSheetId ?? null);
2529
+ const [creatingSheet, setCreatingSheet] = useState2(false);
2530
+ const [surfaceError, setSurfaceError] = useState2(null);
2531
+ const sheets = metadata?.sheets ?? EMPTY_SHEETS;
2532
+ const activeSheet = sheets.find((sheet) => sheet.sheetId === activeSheetId) ?? (initialSheetId ? sheets.find((sheet) => sheet.sheetId === initialSheetId) : void 0) ?? sheets[0] ?? null;
2533
+ const writable = !readOnly && view.writable;
2534
+ const accessRevoked = editableArtifactAccessRevoked(view);
2535
+ const addSheet = useCallback2(async () => {
2536
+ if (!writable || creatingSheet) return;
2537
+ setCreatingSheet(true);
2538
+ setSurfaceError(null);
2539
+ try {
2540
+ const after = activeSheet ? sheetGeneration(activeSheet) : null;
2541
+ const created = await session.createSpreadsheetSheet({
2542
+ name: nextAvailableSheetName2(sheets),
2543
+ after
2544
+ });
2545
+ setActiveSheetId(created.sheetId);
2546
+ } catch (cause) {
2547
+ setSurfaceError(asError(cause));
2548
+ } finally {
2549
+ setCreatingSheet(false);
2550
+ }
2551
+ }, [activeSheet, creatingSheet, session, sheets, writable]);
2552
+ const footer = /* @__PURE__ */ jsxs2(
2553
+ "div",
2554
+ {
2555
+ className: "flex min-h-9 items-center gap-1 overflow-x-auto px-2",
2556
+ role: "tablist",
2557
+ "aria-label": "Worksheets",
2558
+ children: [
2559
+ sheets.map((sheet) => /* @__PURE__ */ jsx2(
2560
+ "button",
2561
+ {
2562
+ type: "button",
2563
+ role: "tab",
2564
+ "aria-selected": sheet.sheetId === activeSheet?.sheetId,
2565
+ onClick: () => setActiveSheetId(sheet.sheetId),
2566
+ className: cn(
2567
+ "h-7 shrink-0 rounded-og-sm px-2.5 text-og-sm outline-hidden transition-colors focus-visible:ring-2 focus-visible:ring-og-accent",
2568
+ sheet.sheetId === activeSheet?.sheetId ? "bg-og-surface-3 font-medium text-og-fg" : "text-og-fg-muted hover:bg-og-surface-3 hover:text-og-fg"
2569
+ ),
2570
+ children: sheet.name
2571
+ },
2572
+ `${sheet.sheetId}:${sheet.generationId ?? "pending"}`
2573
+ )),
2574
+ writable && allowAddSheet ? /* @__PURE__ */ jsx2(
2575
+ "button",
2576
+ {
2577
+ type: "button",
2578
+ onClick: () => void addSheet(),
2579
+ disabled: creatingSheet,
2580
+ "aria-label": creatingSheet ? "Adding worksheet" : "Add worksheet",
2581
+ className: "grid size-7 shrink-0 place-items-center rounded-og-sm text-og-fg-muted outline-hidden hover:bg-og-surface-3 hover:text-og-fg focus-visible:ring-2 focus-visible:ring-og-accent disabled:opacity-50 [&>svg]:size-3.5",
2582
+ children: /* @__PURE__ */ jsx2(PlusIcon2, {})
2583
+ }
2584
+ ) : null
2585
+ ]
2586
+ }
2587
+ );
2588
+ const error = surfaceError ?? metadataError;
2589
+ return /* @__PURE__ */ jsx2(
2590
+ ArtifactSurface,
2591
+ {
2592
+ modality: "spreadsheet",
2593
+ title,
2594
+ subtitle: subtitle ?? (accessRevoked ? editableArtifactStatusLabel(view) : metadata ? `${sheets.length} sheet${sheets.length === 1 ? "" : "s"}` : editableArtifactStatusLabel(view)),
2595
+ actions,
2596
+ footer: accessRevoked ? void 0 : footer,
2597
+ busy: !accessRevoked && !metadata && !error,
2598
+ children: accessRevoked ? /* @__PURE__ */ jsx2(
2599
+ EditableArtifactMessage,
2600
+ {
2601
+ title: "Access changed",
2602
+ detail: editableArtifactStatusLabel(view)
2603
+ }
2604
+ ) : activeSheet && metadata ? /* @__PURE__ */ jsx2(
2605
+ EditableSpreadsheetGrid,
2606
+ {
2607
+ ...gridProps,
2608
+ session,
2609
+ sheet: activeSheet,
2610
+ metadataRevision: metadata.revision,
2611
+ readOnly: !writable
2612
+ },
2613
+ `${activeSheet.sheetId}:${activeSheet.generationId ?? "pending"}`
2614
+ ) : error ? /* @__PURE__ */ jsx2(EditableArtifactMessage, { title: "Could not open this workbook", detail: error.message }) : metadata ? /* @__PURE__ */ jsx2("div", { className: "grid h-full place-items-center bg-og-bg p-6 text-center", children: /* @__PURE__ */ jsxs2("div", { children: [
2615
+ /* @__PURE__ */ jsx2("p", { className: "text-og-base font-medium text-og-fg", children: "This workbook has no worksheets." }),
2616
+ writable && allowAddSheet ? /* @__PURE__ */ jsx2(
2617
+ "button",
2618
+ {
2619
+ type: "button",
2620
+ onClick: () => void addSheet(),
2621
+ disabled: creatingSheet,
2622
+ className: "mt-3 rounded-og-sm bg-og-accent-deep px-3 py-1.5 text-og-sm font-medium text-og-accent-fg outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent disabled:opacity-50",
2623
+ children: creatingSheet ? "Adding\u2026" : "Add worksheet"
2624
+ }
2625
+ ) : null
2626
+ ] }) }) : /* @__PURE__ */ jsx2(
2627
+ EditableArtifactMessage,
2628
+ {
2629
+ title: "Opening workbook",
2630
+ detail: editableArtifactStatusLabel(view)
2631
+ }
2632
+ )
2633
+ }
2634
+ );
2635
+ }
2636
+ function useSpreadsheetMetadata(session) {
2637
+ const [state, setState] = useState2({ metadata: null, error: null });
2638
+ useEffect2(
2639
+ () => session.subscribeSpreadsheetMetadata({}, (metadata) => setState({ metadata, error: null }), {
2640
+ onError: (error) => setState((current) => ({ ...current, error }))
2641
+ }),
2642
+ [session]
2643
+ );
2644
+ return state;
2645
+ }
2646
+ function projectSdkViewport(sheet, metadataRevision, query, viewport, rowCount, columnCount) {
2647
+ const current = viewport && sameViewport(viewport, query) ? viewport : null;
2648
+ const projectedCells = (current?.cells ?? []).map((cell) => ({
2649
+ row: cell.row,
2650
+ col: cell.column,
2651
+ value: projectedCellValue(cell.value),
2652
+ formula: cell.formula,
2653
+ format: EMPTY_FORMAT
2654
+ }));
2655
+ const cells = new SparseSpreadsheetCellIndex(projectedCells);
2656
+ const byCoordinate = new Map(
2657
+ projectedCells.map((cell) => [`${cell.row}:${cell.col}`, cell])
2658
+ );
2659
+ const used = sheet.usedBounds;
2660
+ return {
2661
+ sheetId: sheet.sheetId,
2662
+ sheetName: sheet.name,
2663
+ generationId: current?.generationId ?? sheet.generationId,
2664
+ revision: `${metadataRevision}:${current?.revision ?? "loading"}`,
2665
+ dimensionRevision: metadataRevision.toString(),
2666
+ rowCount,
2667
+ columnCount,
2668
+ usedRange: used ? {
2669
+ row: used.startRow,
2670
+ col: used.startColumn,
2671
+ rowCount: used.endRow - used.startRow + 1,
2672
+ colCount: used.endColumn - used.startColumn + 1
2673
+ } : null,
2674
+ coverage: current ? {
2675
+ rowStart: current.startRow,
2676
+ rowEnd: current.startRow + current.rowCount,
2677
+ columnStart: current.startColumn,
2678
+ columnEnd: current.startColumn + current.columnCount
2679
+ } : {
2680
+ rowStart: query.startRow,
2681
+ rowEnd: query.startRow,
2682
+ columnStart: query.startColumn,
2683
+ columnEnd: query.startColumn
2684
+ },
2685
+ cells,
2686
+ valueAt: (cell) => cell.value,
2687
+ readCell: (row, column) => {
2688
+ const cell = byCoordinate.get(`${row}:${column}`);
2689
+ return cell ? {
2690
+ value: cell.value,
2691
+ input: cell.formula ?? displayValue3(cell.value),
2692
+ format: cell.format
2693
+ } : null;
2694
+ }
2695
+ };
2696
+ }
2697
+ function initialViewportQuery(sheetId, rowCount, columnCount) {
2698
+ return {
2699
+ sheetId,
2700
+ startRow: 0,
2701
+ startColumn: 0,
2702
+ rowCount: Math.min(rowCount, INITIAL_VIEWPORT_ROWS),
2703
+ columnCount: Math.min(columnCount, INITIAL_VIEWPORT_COLUMNS),
2704
+ maxCells: MAX_INTERACTIVE_QUERY_CELLS,
2705
+ maxBytes: MAX_INTERACTIVE_QUERY_BYTES
2706
+ };
2707
+ }
2708
+ function boundedViewportQuery(sheetId, desiredRowStart, desiredRowEnd, desiredColumnStart, desiredColumnEnd, totalRows, totalColumns, focus) {
2709
+ let rowStart = clampInteger(desiredRowStart, 0, totalRows - 1);
2710
+ let rowEnd = clampInteger(desiredRowEnd, rowStart + 1, totalRows);
2711
+ let columnStart = clampInteger(desiredColumnStart, 0, totalColumns - 1);
2712
+ let columnEnd = clampInteger(desiredColumnEnd, columnStart + 1, totalColumns);
2713
+ const desiredRows = rowEnd - rowStart;
2714
+ const desiredColumns = columnEnd - columnStart;
2715
+ if (desiredRows * desiredColumns > MAX_INTERACTIVE_QUERY_CELLS) {
2716
+ const columns = Math.min(
2717
+ desiredColumns,
2718
+ Math.max(1, Math.floor(Math.sqrt(MAX_INTERACTIVE_QUERY_CELLS * 2)))
2719
+ );
2720
+ const rows = Math.max(1, Math.floor(MAX_INTERACTIVE_QUERY_CELLS / columns));
2721
+ columnStart = clampWindowStart(columnStart, columnEnd, focus.column, columns, totalColumns);
2722
+ columnEnd = Math.min(totalColumns, columnStart + columns);
2723
+ rowStart = clampWindowStart(rowStart, rowEnd, focus.row, rows, totalRows);
2724
+ rowEnd = Math.min(totalRows, rowStart + rows);
2725
+ }
2726
+ return {
2727
+ sheetId,
2728
+ startRow: rowStart,
2729
+ startColumn: columnStart,
2730
+ rowCount: rowEnd - rowStart,
2731
+ columnCount: columnEnd - columnStart,
2732
+ maxCells: MAX_INTERACTIVE_QUERY_CELLS,
2733
+ maxBytes: MAX_INTERACTIVE_QUERY_BYTES
2734
+ };
2735
+ }
2736
+ function clampWindowStart(desiredStart, desiredEnd, focus, size, total) {
2737
+ if (desiredEnd - desiredStart <= size) return Math.min(desiredStart, Math.max(0, total - size));
2738
+ const centered = focus - Math.floor(size / 2);
2739
+ return Math.max(desiredStart, Math.min(desiredEnd - size, centered));
2740
+ }
2741
+ function queryContains(query, row, column) {
2742
+ return row >= query.startRow && row < query.startRow + query.rowCount && column >= query.startColumn && column < query.startColumn + query.columnCount;
2743
+ }
2744
+ function sameViewport(projection, query) {
2745
+ return projection.sheetId === query.sheetId && projection.startRow === query.startRow && projection.startColumn === query.startColumn && projection.rowCount === query.rowCount && projection.columnCount === query.columnCount;
2746
+ }
2747
+ function sameViewportQuery(left, right) {
2748
+ return left.sheetId === right.sheetId && left.startRow === right.startRow && left.startColumn === right.startColumn && left.rowCount === right.rowCount && left.columnCount === right.columnCount && left.maxCells === right.maxCells && left.maxBytes === right.maxBytes;
2749
+ }
2750
+ function sheetGeneration(sheet) {
2751
+ if (sheet.generationId === null) return null;
2752
+ return {
2753
+ kind: "generation",
2754
+ sheetId: spreadsheetSheetId(sheet.sheetId),
2755
+ creationOperationId: editableArtifactStableId(sheet.generationId)
2756
+ };
2757
+ }
2758
+ function spreadsheetCellInput(input, kind) {
2759
+ if (kind === "formula") return { formula: input, cached: null };
2760
+ if (input === "") return null;
2761
+ const normalized = input.trim();
2762
+ if (/^(?:true|false)$/i.test(normalized)) return normalized.toLowerCase() === "true";
2763
+ if (/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i.test(normalized)) {
2764
+ const value = Number(normalized);
2765
+ if (Number.isFinite(value)) return value;
2766
+ }
2767
+ return input;
2768
+ }
2769
+ function projectedCellValue(value) {
2770
+ switch (value.kind) {
2771
+ case "empty":
2772
+ return null;
2773
+ case "boolean":
2774
+ case "number":
2775
+ case "text":
2776
+ return value.value;
2777
+ case "date":
2778
+ return new Date(value.value);
2779
+ case "error":
2780
+ return spreadsheetErrorText(value.value);
2781
+ }
2782
+ }
2783
+ function spreadsheetErrorText(value) {
2784
+ if (typeof value === "object") return value.custom;
2785
+ return {
2786
+ null: "#NULL!",
2787
+ divide_by_zero: "#DIV/0!",
2788
+ value: "#VALUE!",
2789
+ reference: "#REF!",
2790
+ name: "#NAME?",
2791
+ number: "#NUM!",
2792
+ not_available: "#N/A",
2793
+ spill: "#SPILL!",
2794
+ calculation: "#CALC!"
2795
+ }[value];
2796
+ }
2797
+ function boundedSheetCount(requested, maximum, usedEnd) {
2798
+ const requestedCount = requested === void 0 || !Number.isFinite(requested) ? maximum : Math.max(1, Math.min(maximum, Math.floor(requested)));
2799
+ const usedCount = usedEnd === void 0 || !Number.isSafeInteger(usedEnd) ? 1 : Math.max(1, Math.min(maximum, usedEnd + 1));
2800
+ return Math.max(requestedCount, usedCount);
2801
+ }
2802
+ function displayValue3(value) {
2803
+ if (value === null || value === void 0) return "";
2804
+ if (value instanceof Date) return Number.isNaN(value.getTime()) ? "" : value.toISOString();
2805
+ if (typeof value === "boolean") return value ? "TRUE" : "FALSE";
2806
+ return String(value);
2807
+ }
2808
+ function nextAvailableSheetName2(sheets) {
2809
+ let index = sheets.length + 1;
2810
+ while (sheets.some((sheet) => sheet.name === `Sheet${index}`)) index += 1;
2811
+ return `Sheet${index}`;
2812
+ }
2813
+ function clampInteger(value, minimum, maximum) {
2814
+ if (!Number.isFinite(value)) return minimum;
2815
+ return Math.min(maximum, Math.max(minimum, Math.floor(value)));
2816
+ }
2817
+ function asError(cause) {
2818
+ return cause instanceof Error ? cause : new Error("Spreadsheet change failed");
2819
+ }
2820
+
2821
+ export {
2822
+ SpreadsheetArtifactSurface,
2823
+ SpreadsheetGrid,
2824
+ SpreadsheetProjectionGrid,
2825
+ EditableSpreadsheetGrid,
2826
+ EditableSpreadsheetArtifactSurface
2827
+ };
2828
+ //# sourceMappingURL=chunk-7WCLAEFV.js.map