@squadbase/vantage 0.5.3 → 0.6.0-dev.3cc6138

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 (120) hide show
  1. package/NOTICE +73 -0
  2. package/README.md +6 -5
  3. package/dist/add-S62E5MSP.js +6 -0
  4. package/dist/add-S62E5MSP.js.map +1 -0
  5. package/dist/{build-756WIEVI.js → build-XXKKX6KB.js} +10 -10
  6. package/dist/{build-756WIEVI.js.map → build-XXKKX6KB.js.map} +1 -1
  7. package/dist/{check-KZ5PHCA6.js → check-X2TDOILH.js} +8 -8
  8. package/dist/{check-KZ5PHCA6.js.map → check-X2TDOILH.js.map} +1 -1
  9. package/dist/{chunk-L3TW4FNA.js → chunk-46PPUW6Y.js} +34 -13
  10. package/dist/chunk-46PPUW6Y.js.map +1 -0
  11. package/dist/chunk-5ZOP4SQR.js +47 -0
  12. package/dist/chunk-5ZOP4SQR.js.map +1 -0
  13. package/dist/{chunk-BAM33YZ6.js → chunk-B45WAO5Q.js} +6 -5
  14. package/dist/chunk-B45WAO5Q.js.map +1 -0
  15. package/dist/{chunk-HQDEYCDP.js → chunk-CIJWVFJD.js} +3 -3
  16. package/dist/{chunk-HQDEYCDP.js.map → chunk-CIJWVFJD.js.map} +1 -1
  17. package/dist/chunk-FVWU275X.js +91 -0
  18. package/dist/chunk-FVWU275X.js.map +1 -0
  19. package/dist/{chunk-VXAV2435.js → chunk-KLEIVDUV.js} +30 -7
  20. package/dist/chunk-KLEIVDUV.js.map +1 -0
  21. package/dist/{chunk-RA6Z3OX3.js → chunk-LCN36MYX.js} +61 -9
  22. package/dist/chunk-LCN36MYX.js.map +1 -0
  23. package/dist/{chunk-JC6MT5UU.js → chunk-MXI3PZFT.js} +3 -3
  24. package/dist/{chunk-JC6MT5UU.js.map → chunk-MXI3PZFT.js.map} +1 -1
  25. package/dist/chunk-PLST5Q7T.js +2221 -0
  26. package/dist/chunk-PLST5Q7T.js.map +1 -0
  27. package/dist/chunk-PVTHHV7X.js +105 -0
  28. package/dist/chunk-PVTHHV7X.js.map +1 -0
  29. package/dist/{chunk-EHPJXDJU.js → chunk-SVNOWE5U.js} +4 -2
  30. package/dist/chunk-SVNOWE5U.js.map +1 -0
  31. package/dist/{chunk-C4MQRWXS.js → chunk-TDUIW7LD.js} +17 -3
  32. package/dist/chunk-TDUIW7LD.js.map +1 -0
  33. package/dist/{chunk-X44ZTKID.js → chunk-V2FV5OJ6.js} +61 -9
  34. package/dist/chunk-V2FV5OJ6.js.map +1 -0
  35. package/dist/{chunk-PSEYFYAP.js → chunk-VVOXG6ZU.js} +68 -8
  36. package/dist/chunk-VVOXG6ZU.js.map +1 -0
  37. package/dist/{chunk-D4Y5RWJA.js → chunk-YE4CPCXX.js} +81 -21
  38. package/dist/chunk-YE4CPCXX.js.map +1 -0
  39. package/dist/cli.js +15 -14
  40. package/dist/cli.js.map +1 -1
  41. package/dist/client/index.d.ts +5 -35
  42. package/dist/client/index.js +1 -1
  43. package/dist/components/index.js +3 -2212
  44. package/dist/components/index.js.map +1 -1
  45. package/dist/{dev-EILX2VTF.js → dev-4MQQDHA4.js} +10 -10
  46. package/dist/{dev-EILX2VTF.js.map → dev-4MQQDHA4.js.map} +1 -1
  47. package/dist/{docs-GP2ZHIQF.js → docs-OTFWTCPW.js} +4 -4
  48. package/dist/{docs-GP2ZHIQF.js.map → docs-OTFWTCPW.js.map} +1 -1
  49. package/dist/{doctor-FXZLMGC3.js → doctor-QXA6DURZ.js} +8 -8
  50. package/dist/{doctor-FXZLMGC3.js.map → doctor-QXA6DURZ.js.map} +1 -1
  51. package/dist/manifest-ttGMFi-Z.d.ts +43 -0
  52. package/dist/{preview-WBQPCTTT.js → preview-HVOM27FS.js} +4 -4
  53. package/dist/{preview-WBQPCTTT.js.map → preview-HVOM27FS.js.map} +1 -1
  54. package/dist/query/index.js +1 -102
  55. package/dist/query/index.js.map +1 -1
  56. package/dist/router/index.d.ts +10 -0
  57. package/dist/router/index.js +2 -89
  58. package/dist/router/index.js.map +1 -1
  59. package/dist/{routes-WCN4KSZY.js → routes-2QRAKMGK.js} +17 -8
  60. package/dist/routes-2QRAKMGK.js.map +1 -0
  61. package/dist/{search-GLKCJXSI.js → search-7A43AZ7M.js} +4 -4
  62. package/dist/{search-GLKCJXSI.js.map → search-7A43AZ7M.js.map} +1 -1
  63. package/dist/server/node.d.ts +1 -1
  64. package/dist/server/node.js +6 -1
  65. package/dist/server/node.js.map +1 -1
  66. package/dist/slide/index.d.ts +230 -0
  67. package/dist/slide/index.js +1571 -0
  68. package/dist/slide/index.js.map +1 -0
  69. package/dist/table/index.d.ts +107 -0
  70. package/dist/table/index.js +680 -0
  71. package/dist/table/index.js.map +1 -0
  72. package/dist/types-DQjLpLCs.d.ts +121 -0
  73. package/dist/ui/index.js +3 -41
  74. package/dist/ui/index.js.map +1 -1
  75. package/dist/{upgrade-S64IXL42.js → upgrade-2QCP7UQQ.js} +9 -9
  76. package/dist/upgrade-2QCP7UQQ.js.map +1 -0
  77. package/dist/vite/index.d.ts +1 -1
  78. package/dist/vite/index.js +5 -5
  79. package/docs/en/agent-skills.md +19 -12
  80. package/docs/en/changelog.md +77 -0
  81. package/docs/en/cli-reference.md +8 -4
  82. package/docs/en/pages-and-metadata.md +17 -0
  83. package/docs/en/routing.md +75 -3
  84. package/docs/en/ui-and-theming.md +76 -0
  85. package/docs/index.json +2 -2
  86. package/docs/ja/agent-skills.md +18 -11
  87. package/docs/ja/changelog.md +75 -0
  88. package/docs/ja/cli-reference.md +8 -4
  89. package/docs/ja/pages-and-metadata.md +17 -0
  90. package/docs/ja/routing.md +71 -3
  91. package/docs/ja/ui-and-theming.md +74 -0
  92. package/package.json +29 -9
  93. package/skills/vantage-app/SKILL.md +4 -0
  94. package/skills/vantage-pitfalls/SKILL.md +41 -1
  95. package/skills/vantage-slide/SKILL.md +264 -0
  96. package/skills/vantage-slide/references/morph.md +102 -0
  97. package/skills/vantage-slide/references/steps.md +57 -0
  98. package/skills/vantage-slide/references/tokens.md +87 -0
  99. package/skills/vantage-slide/references/transitions.md +196 -0
  100. package/templates/AGENTS.md +28 -230
  101. package/templates/agents/api.md +49 -0
  102. package/templates/agents/csv.md +32 -0
  103. package/templates/agents/page.md +185 -0
  104. package/templates/agents/slide.md +34 -0
  105. package/templates/agents/xlsx.md +33 -0
  106. package/theme.css +28 -0
  107. package/dist/add-PPCBMWEN.js +0 -6
  108. package/dist/add-PPCBMWEN.js.map +0 -1
  109. package/dist/chunk-BAM33YZ6.js.map +0 -1
  110. package/dist/chunk-C4MQRWXS.js.map +0 -1
  111. package/dist/chunk-D4Y5RWJA.js.map +0 -1
  112. package/dist/chunk-EHPJXDJU.js.map +0 -1
  113. package/dist/chunk-L3TW4FNA.js.map +0 -1
  114. package/dist/chunk-PSEYFYAP.js.map +0 -1
  115. package/dist/chunk-RA6Z3OX3.js.map +0 -1
  116. package/dist/chunk-VXAV2435.js.map +0 -1
  117. package/dist/chunk-X44ZTKID.js.map +0 -1
  118. package/dist/routes-WCN4KSZY.js.map +0 -1
  119. package/dist/types-De3mo_hv.d.ts +0 -59
  120. package/dist/upgrade-S64IXL42.js.map +0 -1
@@ -0,0 +1,680 @@
1
+ import { FILE_PAGE_MAX_ROWS, FILE_PAGE_MAX_BYTES } from '../chunk-TDUIW7LD.js';
2
+ import { useQuery } from '../chunk-PVTHHV7X.js';
3
+ import '../chunk-DTDVSFRY.js';
4
+ import { Loading, ErrorState } from '../chunk-5ZOP4SQR.js';
5
+ import { PageShell, PageShellHeader, PageShellHeading, PageShellTitle, PageShellMeta, PageShellActions, PageShellContent } from '../chunk-PLST5Q7T.js';
6
+ import { Button } from '../chunk-MXI3PZFT.js';
7
+ import { cn } from '../chunk-A2UUGASH.js';
8
+ import { useRef, useState, useMemo, useLayoutEffect, useEffect, useCallback } from 'react';
9
+ import { DownloadIcon } from 'lucide-react';
10
+ import { jsx, jsxs } from 'react/jsx-runtime';
11
+
12
+ // @squadbase/vantage — generated build. Do not edit.
13
+
14
+ // src/table/infer.ts
15
+ var TYPE_SAMPLE_ROWS = 500;
16
+ var TYPE_CONFIDENCE = 0.9;
17
+ function isEmpty(value) {
18
+ return value === null || value === void 0 || value === "";
19
+ }
20
+ var ISO_DATE = /^\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2})?)?$/;
21
+ function classify(value) {
22
+ if (value instanceof Date) return "date";
23
+ if (typeof value === "number" && Number.isFinite(value)) return "number";
24
+ if (typeof value === "string" && ISO_DATE.test(value)) return "date";
25
+ return "text";
26
+ }
27
+ function toColumns(header, width, body) {
28
+ const columns = [];
29
+ for (let index = 0; index < width; index++) {
30
+ const raw = header[index];
31
+ const label = isEmpty(raw) ? "" : String(raw).trim();
32
+ let numbers = 0;
33
+ let dates = 0;
34
+ let filled = 0;
35
+ for (let row = 0; row < Math.min(body.length, TYPE_SAMPLE_ROWS); row++) {
36
+ const cell = body[row]?.[index];
37
+ if (isEmpty(cell)) continue;
38
+ filled++;
39
+ const kind = classify(cell);
40
+ if (kind === "number") numbers++;
41
+ else if (kind === "date") dates++;
42
+ }
43
+ let format = "text";
44
+ if (filled > 0) {
45
+ if (numbers / filled >= TYPE_CONFIDENCE) format = "number";
46
+ else if (dates / filled >= TYPE_CONFIDENCE) format = "date";
47
+ }
48
+ columns.push({ key: `c${index}`, label, format });
49
+ }
50
+ return columns;
51
+ }
52
+ function toSheet(name, rows) {
53
+ if (rows.length === 0) {
54
+ return { name, columns: [], rows: [], totalRows: 0, truncated: false };
55
+ }
56
+ const header = rows[0] ?? [];
57
+ const body = rows.slice(1);
58
+ const width = rows.reduce((max, row) => Math.max(max, row.length), 0);
59
+ const truncated = body.length > FILE_PAGE_MAX_ROWS;
60
+ const kept = truncated ? body.slice(0, FILE_PAGE_MAX_ROWS) : body;
61
+ return {
62
+ name,
63
+ columns: toColumns(header, width, kept),
64
+ // Pad short rows so every row has a value at every column index; ragged
65
+ // rows are common in hand-edited CSV and would otherwise read as holes.
66
+ rows: kept.map((row) => Array.from({ length: width }, (_, i) => row[i] ?? null)),
67
+ totalRows: body.length,
68
+ truncated
69
+ };
70
+ }
71
+ var numberFormat = new Intl.NumberFormat();
72
+ function formatCell(value, format) {
73
+ if (isEmpty(value)) return "";
74
+ if (format === "date" && typeof value === "string") return value;
75
+ if (format === "date" && value instanceof Date) return value.toLocaleDateString();
76
+ if (format === "number" && typeof value === "number") return numberFormat.format(value);
77
+ if (value instanceof Date) return value.toLocaleString();
78
+ if (typeof value === "boolean") return value ? "TRUE" : "FALSE";
79
+ return String(value);
80
+ }
81
+
82
+ // src/table/parse.ts
83
+ var FilePageError = class extends Error {
84
+ constructor(message) {
85
+ super(message);
86
+ this.name = "FilePageError";
87
+ }
88
+ };
89
+ function megabytes(bytes) {
90
+ return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
91
+ }
92
+ function decodeCsv(buffer) {
93
+ const bytes = new Uint8Array(buffer);
94
+ const hasBom = bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191;
95
+ const body = hasBom ? bytes.subarray(3) : bytes;
96
+ const utf8 = new TextDecoder("utf-8").decode(body);
97
+ const replacements = (utf8.match(/�/g) ?? []).length;
98
+ if (replacements > 0 && replacements / Math.max(utf8.length, 1) > 5e-4) {
99
+ try {
100
+ return new TextDecoder("shift-jis").decode(body);
101
+ } catch {
102
+ }
103
+ }
104
+ return utf8;
105
+ }
106
+ async function parseCsv(buffer) {
107
+ const { default: Papa } = await import('papaparse');
108
+ const text = decodeCsv(buffer);
109
+ const result = Papa.parse(text, {
110
+ header: false,
111
+ dynamicTyping: true,
112
+ skipEmptyLines: "greedy"
113
+ });
114
+ return { sheets: [toSheet("Sheet1", result.data)] };
115
+ }
116
+ async function parseXlsx(buffer) {
117
+ const { default: readXlsxFile } = await import('read-excel-file/browser');
118
+ const sheets = await readXlsxFile(buffer);
119
+ return {
120
+ sheets: sheets.map((sheet) => toSheet(sheet.sheet, sheet.data))
121
+ };
122
+ }
123
+ async function loadWorkbook(src, kind) {
124
+ let buffer;
125
+ try {
126
+ const response = await fetch(src);
127
+ if (!response.ok) {
128
+ throw new FilePageError(`Could not load the file (HTTP ${response.status}).`);
129
+ }
130
+ buffer = await response.arrayBuffer();
131
+ } catch (err) {
132
+ if (err instanceof FilePageError) throw err;
133
+ throw new FilePageError(`Could not load the file: ${err.message}`);
134
+ }
135
+ if (buffer.byteLength > FILE_PAGE_MAX_BYTES) {
136
+ throw new FilePageError(
137
+ `This file is ${megabytes(buffer.byteLength)}, over the ${megabytes(
138
+ FILE_PAGE_MAX_BYTES
139
+ )} limit for a data-file page. Download it, or serve the data from server/api/ and render it with DataTable.`
140
+ );
141
+ }
142
+ try {
143
+ switch (kind) {
144
+ case "csv":
145
+ return await parseCsv(buffer);
146
+ case "xlsx":
147
+ return await parseXlsx(buffer);
148
+ default:
149
+ throw new FilePageError(`No viewer for ".${kind}" files.`);
150
+ }
151
+ } catch (err) {
152
+ if (err instanceof FilePageError) throw err;
153
+ throw new FilePageError(
154
+ `This file could not be read. It may be corrupt, password-protected, or not really a .${kind} file. (${err.message})`
155
+ );
156
+ }
157
+ }
158
+ var ROW_HEIGHT = 22;
159
+ var HEAD_HEIGHT = 22;
160
+ var MIN_GUTTER_WIDTH = 44;
161
+ var MIN_COL_WIDTH = 56;
162
+ var MAX_COL_WIDTH = 420;
163
+ var FILLER_COL_WIDTH = 100;
164
+ var COL_PADDING = 18;
165
+ var OVERSCAN_ROWS = 8;
166
+ var OVERSCAN_COLS = 2;
167
+ var WIDTH_SAMPLE_ROWS = 200;
168
+ var CELL_FONT = '13px ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif';
169
+ function columnLabel(index) {
170
+ let label = "";
171
+ for (let n = index; n >= 0; n = Math.floor(n / 26) - 1) {
172
+ label = String.fromCharCode(65 + n % 26) + label;
173
+ }
174
+ return label;
175
+ }
176
+ function measureWidths(sheet) {
177
+ const context = typeof document === "undefined" ? null : document.createElement("canvas").getContext("2d");
178
+ if (context) context.font = CELL_FONT;
179
+ const widthOf = (text) => context ? context.measureText(text).width : text.length * 7.2;
180
+ const stride = Math.max(1, Math.ceil(sheet.rows.length / WIDTH_SAMPLE_ROWS));
181
+ return sheet.columns.map((column, index) => {
182
+ let max = widthOf(column.label);
183
+ for (let row = 0; row < sheet.rows.length; row += stride) {
184
+ const text = formatCell(sheet.rows[row]?.[index], column.format);
185
+ if (text !== "") max = Math.max(max, widthOf(text));
186
+ }
187
+ return Math.min(MAX_COL_WIDTH, Math.max(MIN_COL_WIDTH, Math.ceil(max) + COL_PADDING));
188
+ });
189
+ }
190
+ function runningOffsets(widths) {
191
+ const offsets = [0];
192
+ for (const width of widths) offsets.push(offsets[offsets.length - 1] + width);
193
+ return offsets;
194
+ }
195
+ function columnAt(offsets, x) {
196
+ let low = 0;
197
+ let high = offsets.length - 2;
198
+ while (low < high) {
199
+ const mid = low + high + 1 >> 1;
200
+ if (offsets[mid] <= x) low = mid;
201
+ else high = mid - 1;
202
+ }
203
+ return Math.max(0, low);
204
+ }
205
+ function SheetGrid({ sheet, className }) {
206
+ const scrollRef = useRef(null);
207
+ const [scroll, setScroll] = useState({ top: 0, left: 0 });
208
+ const [viewport, setViewport] = useState({ width: 0, height: 0 });
209
+ const [selected, setSelected] = useState({ row: 0, col: 0 });
210
+ const dataWidths = useMemo(() => measureWidths(sheet), [sheet]);
211
+ const dataCols = sheet.columns.length;
212
+ const dataRows = sheet.rows.length;
213
+ const gutterWidth = Math.max(MIN_GUTTER_WIDTH, String(dataRows + 1).length * 8 + 16);
214
+ const dataWidth = dataWidths.reduce((sum, width) => sum + width, 0);
215
+ const fillerCols = Math.max(
216
+ 0,
217
+ Math.ceil((viewport.width - gutterWidth - dataWidth) / FILLER_COL_WIDTH)
218
+ );
219
+ const fillerRows = Math.max(
220
+ 0,
221
+ Math.ceil((viewport.height - HEAD_HEIGHT - (dataRows + 1) * ROW_HEIGHT) / ROW_HEIGHT)
222
+ );
223
+ const colCount = dataCols + fillerCols;
224
+ const rowCount = dataRows + fillerRows;
225
+ const widths = useMemo(
226
+ () => [...dataWidths, ...Array.from({ length: fillerCols }, () => FILLER_COL_WIDTH)],
227
+ [dataWidths, fillerCols]
228
+ );
229
+ const offsets = useMemo(() => runningOffsets(widths), [widths]);
230
+ const totalWidth = offsets[offsets.length - 1] ?? 0;
231
+ useLayoutEffect(() => {
232
+ const element = scrollRef.current;
233
+ if (!element) return;
234
+ const measure = () => setViewport({ width: element.clientWidth, height: element.clientHeight });
235
+ measure();
236
+ const observer = new ResizeObserver(measure);
237
+ observer.observe(element);
238
+ return () => observer.disconnect();
239
+ }, []);
240
+ useEffect(() => {
241
+ setSelected({ row: 0, col: 0 });
242
+ scrollRef.current?.scrollTo({ top: 0, left: 0 });
243
+ }, [sheet]);
244
+ const firstRow = Math.max(0, Math.floor(scroll.top / ROW_HEIGHT) - OVERSCAN_ROWS);
245
+ const lastRow = Math.min(
246
+ rowCount,
247
+ Math.ceil((scroll.top + viewport.height - HEAD_HEIGHT - ROW_HEIGHT) / ROW_HEIGHT) + OVERSCAN_ROWS
248
+ );
249
+ const firstCol = Math.max(0, columnAt(offsets, scroll.left) - OVERSCAN_COLS);
250
+ const lastCol = Math.min(
251
+ colCount,
252
+ columnAt(offsets, scroll.left + viewport.width) + 1 + OVERSCAN_COLS
253
+ );
254
+ const leadWidth = offsets[firstCol] ?? 0;
255
+ const tailWidth = totalWidth - (offsets[lastCol] ?? totalWidth);
256
+ const visibleCols = Array.from({ length: Math.max(0, lastCol - firstCol) }, (_, i) => firstCol + i);
257
+ const reveal = useCallback(
258
+ (next) => {
259
+ const element = scrollRef.current;
260
+ if (!element) return;
261
+ if (next.row > 0) {
262
+ const top = HEAD_HEIGHT + ROW_HEIGHT + (next.row - 1) * ROW_HEIGHT;
263
+ const frozen = HEAD_HEIGHT + ROW_HEIGHT;
264
+ if (top - element.scrollTop < frozen) element.scrollTop = top - frozen;
265
+ else if (top + ROW_HEIGHT - element.scrollTop > element.clientHeight) {
266
+ element.scrollTop = top + ROW_HEIGHT - element.clientHeight;
267
+ }
268
+ }
269
+ const left = offsets[next.col] ?? 0;
270
+ const width = widths[next.col] ?? 0;
271
+ if (left - element.scrollLeft < 0) element.scrollLeft = left;
272
+ else if (left + width + gutterWidth - element.scrollLeft > element.clientWidth) {
273
+ element.scrollLeft = left + width + gutterWidth - element.clientWidth;
274
+ }
275
+ },
276
+ [gutterWidth, offsets, widths]
277
+ );
278
+ const cellText = useCallback(
279
+ (row, col) => {
280
+ const column = sheet.columns[col];
281
+ if (!column) return "";
282
+ if (row === 0) return column.label;
283
+ return formatCell(sheet.rows[row - 1]?.[col], column.format);
284
+ },
285
+ [sheet]
286
+ );
287
+ const onKeyDown = useCallback(
288
+ (event) => {
289
+ const step = (rows, cols, escapable = false) => {
290
+ const next = {
291
+ row: Math.min(dataRows, Math.max(0, selected.row + rows)),
292
+ col: Math.min(dataCols - 1, Math.max(0, selected.col + cols))
293
+ };
294
+ const moved = next.row !== selected.row || next.col !== selected.col;
295
+ if (moved || !escapable) event.preventDefault();
296
+ if (!moved) return;
297
+ setSelected(next);
298
+ reveal(next);
299
+ };
300
+ const page = Math.max(1, Math.floor(viewport.height / ROW_HEIGHT) - 1);
301
+ switch (event.key) {
302
+ case "ArrowDown":
303
+ return step(1, 0);
304
+ case "ArrowUp":
305
+ return step(-1, 0);
306
+ case "Tab":
307
+ return step(0, event.shiftKey ? -1 : 1, true);
308
+ case "ArrowRight":
309
+ return step(0, 1);
310
+ case "ArrowLeft":
311
+ return step(0, -1);
312
+ case "PageDown":
313
+ return step(page, 0);
314
+ case "PageUp":
315
+ return step(-page, 0);
316
+ case "Home":
317
+ return step(event.ctrlKey || event.metaKey ? -dataRows : 0, -dataCols);
318
+ case "End":
319
+ return step(event.ctrlKey || event.metaKey ? dataRows : 0, dataCols);
320
+ case "c":
321
+ case "C":
322
+ if (event.metaKey || event.ctrlKey) {
323
+ navigator.clipboard?.writeText(cellText(selected.row, selected.col)).catch(() => {
324
+ });
325
+ }
326
+ return;
327
+ default:
328
+ return;
329
+ }
330
+ },
331
+ [cellText, dataCols, dataRows, reveal, selected, viewport.height]
332
+ );
333
+ if (dataCols === 0) {
334
+ return /* @__PURE__ */ jsx("p", { className: cn("p-4 text-sm text-muted-foreground", className), children: "This sheet is empty." });
335
+ }
336
+ const select = (row, col) => {
337
+ setSelected({ row, col });
338
+ scrollRef.current?.focus({ preventScroll: true });
339
+ };
340
+ const rowStyle = { height: ROW_HEIGHT, width: gutterWidth + totalWidth };
341
+ return /* @__PURE__ */ jsx(
342
+ "div",
343
+ {
344
+ ref: scrollRef,
345
+ role: "grid",
346
+ "aria-label": sheet.name,
347
+ "aria-rowcount": dataRows + 1,
348
+ "aria-colcount": dataCols,
349
+ tabIndex: 0,
350
+ onScroll: (event) => setScroll({ top: event.currentTarget.scrollTop, left: event.currentTarget.scrollLeft }),
351
+ onKeyDown,
352
+ className: cn(
353
+ "relative h-full overflow-auto bg-background text-[13px] outline-none select-none [overscroll-behavior:contain]",
354
+ className
355
+ ),
356
+ children: /* @__PURE__ */ jsxs("div", { role: "rowgroup", style: { width: gutterWidth + totalWidth }, children: [
357
+ /* @__PURE__ */ jsxs(
358
+ "div",
359
+ {
360
+ className: "sticky top-0 z-40 flex",
361
+ style: { ...rowStyle, height: HEAD_HEIGHT },
362
+ role: "row",
363
+ children: [
364
+ /* @__PURE__ */ jsx(
365
+ "div",
366
+ {
367
+ className: "sticky left-0 z-10 shrink-0 border-r border-b border-border bg-muted",
368
+ style: { width: gutterWidth, height: HEAD_HEIGHT }
369
+ }
370
+ ),
371
+ /* @__PURE__ */ jsx("div", { className: "shrink-0 border-b border-border bg-muted", style: { width: leadWidth } }),
372
+ visibleCols.map((col) => /* @__PURE__ */ jsx(
373
+ "div",
374
+ {
375
+ role: col < dataCols ? "columnheader" : "presentation",
376
+ "aria-colindex": col < dataCols ? col + 1 : void 0,
377
+ onMouseDown: col < dataCols ? () => select(selected.row, col) : void 0,
378
+ className: cn(
379
+ "flex shrink-0 items-center justify-center border-r border-b border-border bg-muted text-[11px] text-muted-foreground",
380
+ selected.col === col && col < dataCols && "bg-accent font-medium text-foreground"
381
+ ),
382
+ style: { width: widths[col], height: HEAD_HEIGHT },
383
+ children: columnLabel(col)
384
+ },
385
+ col
386
+ )),
387
+ /* @__PURE__ */ jsx("div", { className: "shrink-0 border-b border-border bg-muted", style: { width: tailWidth } })
388
+ ]
389
+ }
390
+ ),
391
+ /* @__PURE__ */ jsxs(
392
+ "div",
393
+ {
394
+ className: "sticky z-30 flex bg-background",
395
+ style: { ...rowStyle, top: HEAD_HEIGHT },
396
+ role: "row",
397
+ children: [
398
+ /* @__PURE__ */ jsx(
399
+ RowHeader,
400
+ {
401
+ label: "1",
402
+ width: gutterWidth,
403
+ active: selected.row === 0,
404
+ onSelect: () => select(0, selected.col)
405
+ }
406
+ ),
407
+ /* @__PURE__ */ jsx("div", { className: "shrink-0 border-b border-foreground/25", style: { width: leadWidth } }),
408
+ visibleCols.map((col) => /* @__PURE__ */ jsx(
409
+ Cell,
410
+ {
411
+ width: widths[col],
412
+ format: "text",
413
+ text: sheet.columns[col]?.label ?? "",
414
+ header: true,
415
+ selected: selected.row === 0 && selected.col === col,
416
+ onSelect: col < dataCols ? () => select(0, col) : void 0,
417
+ colIndex: col < dataCols ? col : void 0
418
+ },
419
+ col
420
+ )),
421
+ /* @__PURE__ */ jsx("div", { className: "shrink-0 border-b border-foreground/25", style: { width: tailWidth } })
422
+ ]
423
+ }
424
+ ),
425
+ /* @__PURE__ */ jsx("div", { role: "presentation", className: "relative", style: { height: rowCount * ROW_HEIGHT }, children: Array.from({ length: Math.max(0, lastRow - firstRow) }, (_, offset) => {
426
+ const row = firstRow + offset;
427
+ const cells = sheet.rows[row];
428
+ return /* @__PURE__ */ jsxs(
429
+ "div",
430
+ {
431
+ role: "row",
432
+ "aria-rowindex": row < dataRows ? row + 2 : void 0,
433
+ className: "absolute flex",
434
+ style: { ...rowStyle, top: row * ROW_HEIGHT },
435
+ children: [
436
+ /* @__PURE__ */ jsx(
437
+ RowHeader,
438
+ {
439
+ label: String(row + 2),
440
+ width: gutterWidth,
441
+ active: selected.row === row + 1,
442
+ onSelect: row < dataRows ? () => select(row + 1, selected.col) : void 0
443
+ }
444
+ ),
445
+ /* @__PURE__ */ jsx("div", { className: "shrink-0", style: { width: leadWidth } }),
446
+ visibleCols.map((col) => {
447
+ const column = sheet.columns[col];
448
+ return /* @__PURE__ */ jsx(
449
+ Cell,
450
+ {
451
+ width: widths[col],
452
+ format: column?.format ?? "text",
453
+ text: column ? formatCell(cells?.[col], column.format) : "",
454
+ selected: selected.row === row + 1 && selected.col === col,
455
+ onSelect: row < dataRows && col < dataCols ? () => select(row + 1, col) : void 0,
456
+ colIndex: col < dataCols ? col : void 0
457
+ },
458
+ col
459
+ );
460
+ }),
461
+ /* @__PURE__ */ jsx("div", { className: "shrink-0", style: { width: tailWidth } })
462
+ ]
463
+ },
464
+ row
465
+ );
466
+ }) })
467
+ ] })
468
+ }
469
+ );
470
+ }
471
+ SheetGrid.displayName = "SheetGrid";
472
+ function RowHeader({
473
+ label,
474
+ width,
475
+ active,
476
+ onSelect
477
+ }) {
478
+ return /* @__PURE__ */ jsx(
479
+ "div",
480
+ {
481
+ role: onSelect ? "rowheader" : "presentation",
482
+ onMouseDown: onSelect,
483
+ className: cn(
484
+ // Above a selected cell (`z-10`): scrolling a selected column under the
485
+ // gutter must not paint its outline over the row number.
486
+ "sticky left-0 z-20 flex h-full shrink-0 items-center justify-center border-r border-b border-border bg-muted text-[11px] text-muted-foreground",
487
+ active && "bg-accent font-medium text-foreground"
488
+ ),
489
+ style: { width },
490
+ children: label
491
+ }
492
+ );
493
+ }
494
+ function Cell({
495
+ width,
496
+ format,
497
+ text,
498
+ header,
499
+ selected,
500
+ onSelect,
501
+ colIndex
502
+ }) {
503
+ return /* @__PURE__ */ jsx(
504
+ "div",
505
+ {
506
+ role: onSelect ? "gridcell" : "presentation",
507
+ "aria-colindex": colIndex === void 0 ? void 0 : colIndex + 1,
508
+ "aria-selected": onSelect ? selected : void 0,
509
+ onMouseDown: onSelect,
510
+ title: text || void 0,
511
+ className: cn(
512
+ "flex h-full shrink-0 items-center border-r border-b border-border px-1.5",
513
+ header && "border-b-foreground/25 font-medium",
514
+ // Numbers and dates read right-aligned, as they do in a spreadsheet.
515
+ format === "text" ? "justify-start" : "justify-end tabular-nums",
516
+ selected && onSelect && "relative z-10 outline-2 -outline-offset-2 outline-primary"
517
+ ),
518
+ style: { width },
519
+ children: /* @__PURE__ */ jsx("span", { className: "truncate", children: text })
520
+ }
521
+ );
522
+ }
523
+ var MIN_HEIGHT = 320;
524
+ var MIN_BOTTOM_GAP = 8;
525
+ function spaceBelow(element) {
526
+ let total = 0;
527
+ for (let node = element.parentElement; node && node !== document.body; node = node.parentElement) {
528
+ const style = getComputedStyle(node);
529
+ total += parseFloat(style.paddingBottom) + parseFloat(style.borderBottomWidth) + parseFloat(style.marginBottom);
530
+ }
531
+ return Number.isFinite(total) ? total : 0;
532
+ }
533
+ function useFillHeight(ref, enabled) {
534
+ const [height, setHeight] = useState(MIN_HEIGHT);
535
+ useLayoutEffect(() => {
536
+ const element = ref.current;
537
+ if (!element || !enabled) return;
538
+ const measure = () => {
539
+ const rect = element.getBoundingClientRect();
540
+ const top = rect.top + window.scrollY;
541
+ const next = Math.max(
542
+ MIN_HEIGHT,
543
+ window.innerHeight - top - Math.max(spaceBelow(element), MIN_BOTTOM_GAP)
544
+ );
545
+ setHeight((current) => Math.abs(current - next) > 1 ? next : current);
546
+ };
547
+ measure();
548
+ const observer = new ResizeObserver(measure);
549
+ observer.observe(document.body);
550
+ window.addEventListener("resize", measure);
551
+ return () => {
552
+ observer.disconnect();
553
+ window.removeEventListener("resize", measure);
554
+ };
555
+ }, [enabled, ref]);
556
+ return height;
557
+ }
558
+ function plural(count, noun) {
559
+ return `${count.toLocaleString()} ${noun}${count === 1 ? "" : "s"}`;
560
+ }
561
+ function TableViewer({
562
+ sheet,
563
+ sheetNames,
564
+ activeSheet = 0,
565
+ onSheetChange,
566
+ height,
567
+ className
568
+ }) {
569
+ const frameRef = useRef(null);
570
+ const filled = useFillHeight(frameRef, height === void 0);
571
+ const tabs = sheetNames && sheetNames.length > 1 ? sheetNames : null;
572
+ return /* @__PURE__ */ jsxs(
573
+ "div",
574
+ {
575
+ ref: frameRef,
576
+ className: cn(
577
+ "flex flex-col overflow-hidden rounded-md border border-border bg-background",
578
+ className
579
+ ),
580
+ style: { height: height ?? filled },
581
+ children: [
582
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsx(SheetGrid, { sheet }) }),
583
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4 border-t border-border bg-muted/60 px-2 py-1", children: [
584
+ tabs ? /* @__PURE__ */ jsx("div", { className: "flex min-w-0 items-end gap-px overflow-x-auto", role: "tablist", children: tabs.map((name, index) => /* @__PURE__ */ jsx(
585
+ "button",
586
+ {
587
+ type: "button",
588
+ role: "tab",
589
+ "aria-selected": index === activeSheet,
590
+ onClick: () => onSheetChange?.(index),
591
+ className: cn(
592
+ "shrink-0 rounded-t-sm border-t-2 px-3 py-1 text-xs whitespace-nowrap",
593
+ index === activeSheet ? "border-primary bg-background font-medium text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"
594
+ ),
595
+ children: name
596
+ },
597
+ name
598
+ )) }) : null,
599
+ /* @__PURE__ */ jsxs("div", { className: "ml-auto flex shrink-0 items-center gap-3 text-[11px] text-muted-foreground", children: [
600
+ sheet.truncated ? /* @__PURE__ */ jsxs("span", { className: "font-medium text-foreground", children: [
601
+ "First ",
602
+ sheet.rows.length.toLocaleString(),
603
+ " of ",
604
+ sheet.totalRows.toLocaleString(),
605
+ " rows \u2014 download the file for the rest"
606
+ ] }) : null,
607
+ /* @__PURE__ */ jsxs("span", { children: [
608
+ plural(sheet.totalRows, "row"),
609
+ " \xD7 ",
610
+ plural(sheet.columns.length, "column")
611
+ ] })
612
+ ] })
613
+ ] })
614
+ ]
615
+ }
616
+ );
617
+ }
618
+ TableViewer.displayName = "TableViewer";
619
+ function FilePageView({ kind, src, fileName, title }) {
620
+ const [active, setActive] = useState(0);
621
+ const workbook = useQuery({
622
+ queryKey: ["vantage:file-page", src],
623
+ queryFn: () => loadWorkbook(src, kind),
624
+ staleTime: Infinity
625
+ });
626
+ const download = (
627
+ // `nativeButton={false}` because this renders an `<a>`: Base UI needs to
628
+ // know it is not driving a real button, or it warns and keeps button
629
+ // semantics that would fight the link's.
630
+ /* @__PURE__ */ jsxs(
631
+ Button,
632
+ {
633
+ variant: "outline",
634
+ size: "sm",
635
+ nativeButton: false,
636
+ render: /* @__PURE__ */ jsx("a", { href: src, download: fileName }),
637
+ children: [
638
+ /* @__PURE__ */ jsx(DownloadIcon, { "aria-hidden": "true" }),
639
+ "Download"
640
+ ]
641
+ }
642
+ )
643
+ );
644
+ const sheets = workbook.data?.sheets ?? [];
645
+ const sheet = sheets[Math.min(active, Math.max(sheets.length - 1, 0))];
646
+ return /* @__PURE__ */ jsxs(PageShell, { children: [
647
+ /* @__PURE__ */ jsxs(PageShellHeader, { className: "py-6", children: [
648
+ /* @__PURE__ */ jsxs(PageShellHeading, { children: [
649
+ /* @__PURE__ */ jsx(PageShellTitle, { children: title ?? fileName }),
650
+ /* @__PURE__ */ jsx(PageShellMeta, { children: fileName })
651
+ ] }),
652
+ /* @__PURE__ */ jsx(PageShellActions, { children: download })
653
+ ] }),
654
+ /* @__PURE__ */ jsxs(PageShellContent, { children: [
655
+ workbook.isPending ? /* @__PURE__ */ jsx(Loading, { label: `Reading ${fileName}\u2026` }) : null,
656
+ workbook.isError ? /* @__PURE__ */ jsx(
657
+ ErrorState,
658
+ {
659
+ title: `Could not display ${fileName}`,
660
+ message: workbook.error.message,
661
+ action: download
662
+ }
663
+ ) : null,
664
+ sheet ? /* @__PURE__ */ jsx(
665
+ TableViewer,
666
+ {
667
+ sheet,
668
+ sheetNames: sheets.map((s) => s.name),
669
+ activeSheet: active,
670
+ onSheetChange: setActive
671
+ }
672
+ ) : null
673
+ ] })
674
+ ] });
675
+ }
676
+ FilePageView.displayName = "FilePageView";
677
+
678
+ export { FilePageError, FilePageView, SheetGrid, TableViewer, columnLabel, formatCell, loadWorkbook, toSheet };
679
+ //# sourceMappingURL=index.js.map
680
+ //# sourceMappingURL=index.js.map