@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.
- package/NOTICE +73 -0
- package/README.md +6 -5
- package/dist/add-S62E5MSP.js +6 -0
- package/dist/add-S62E5MSP.js.map +1 -0
- package/dist/{build-756WIEVI.js → build-XXKKX6KB.js} +10 -10
- package/dist/{build-756WIEVI.js.map → build-XXKKX6KB.js.map} +1 -1
- package/dist/{check-KZ5PHCA6.js → check-X2TDOILH.js} +8 -8
- package/dist/{check-KZ5PHCA6.js.map → check-X2TDOILH.js.map} +1 -1
- package/dist/{chunk-L3TW4FNA.js → chunk-46PPUW6Y.js} +34 -13
- package/dist/chunk-46PPUW6Y.js.map +1 -0
- package/dist/chunk-5ZOP4SQR.js +47 -0
- package/dist/chunk-5ZOP4SQR.js.map +1 -0
- package/dist/{chunk-BAM33YZ6.js → chunk-B45WAO5Q.js} +6 -5
- package/dist/chunk-B45WAO5Q.js.map +1 -0
- package/dist/{chunk-HQDEYCDP.js → chunk-CIJWVFJD.js} +3 -3
- package/dist/{chunk-HQDEYCDP.js.map → chunk-CIJWVFJD.js.map} +1 -1
- package/dist/chunk-FVWU275X.js +91 -0
- package/dist/chunk-FVWU275X.js.map +1 -0
- package/dist/{chunk-VXAV2435.js → chunk-KLEIVDUV.js} +30 -7
- package/dist/chunk-KLEIVDUV.js.map +1 -0
- package/dist/{chunk-RA6Z3OX3.js → chunk-LCN36MYX.js} +61 -9
- package/dist/chunk-LCN36MYX.js.map +1 -0
- package/dist/{chunk-JC6MT5UU.js → chunk-MXI3PZFT.js} +3 -3
- package/dist/{chunk-JC6MT5UU.js.map → chunk-MXI3PZFT.js.map} +1 -1
- package/dist/chunk-PLST5Q7T.js +2221 -0
- package/dist/chunk-PLST5Q7T.js.map +1 -0
- package/dist/chunk-PVTHHV7X.js +105 -0
- package/dist/chunk-PVTHHV7X.js.map +1 -0
- package/dist/{chunk-EHPJXDJU.js → chunk-SVNOWE5U.js} +4 -2
- package/dist/chunk-SVNOWE5U.js.map +1 -0
- package/dist/{chunk-C4MQRWXS.js → chunk-TDUIW7LD.js} +17 -3
- package/dist/chunk-TDUIW7LD.js.map +1 -0
- package/dist/{chunk-X44ZTKID.js → chunk-V2FV5OJ6.js} +61 -9
- package/dist/chunk-V2FV5OJ6.js.map +1 -0
- package/dist/{chunk-PSEYFYAP.js → chunk-VVOXG6ZU.js} +68 -8
- package/dist/chunk-VVOXG6ZU.js.map +1 -0
- package/dist/{chunk-D4Y5RWJA.js → chunk-YE4CPCXX.js} +81 -21
- package/dist/chunk-YE4CPCXX.js.map +1 -0
- package/dist/cli.js +15 -14
- package/dist/cli.js.map +1 -1
- package/dist/client/index.d.ts +5 -35
- package/dist/client/index.js +1 -1
- package/dist/components/index.js +3 -2212
- package/dist/components/index.js.map +1 -1
- package/dist/{dev-EILX2VTF.js → dev-4MQQDHA4.js} +10 -10
- package/dist/{dev-EILX2VTF.js.map → dev-4MQQDHA4.js.map} +1 -1
- package/dist/{docs-GP2ZHIQF.js → docs-OTFWTCPW.js} +4 -4
- package/dist/{docs-GP2ZHIQF.js.map → docs-OTFWTCPW.js.map} +1 -1
- package/dist/{doctor-FXZLMGC3.js → doctor-QXA6DURZ.js} +8 -8
- package/dist/{doctor-FXZLMGC3.js.map → doctor-QXA6DURZ.js.map} +1 -1
- package/dist/manifest-ttGMFi-Z.d.ts +43 -0
- package/dist/{preview-WBQPCTTT.js → preview-HVOM27FS.js} +4 -4
- package/dist/{preview-WBQPCTTT.js.map → preview-HVOM27FS.js.map} +1 -1
- package/dist/query/index.js +1 -102
- package/dist/query/index.js.map +1 -1
- package/dist/router/index.d.ts +10 -0
- package/dist/router/index.js +2 -89
- package/dist/router/index.js.map +1 -1
- package/dist/{routes-WCN4KSZY.js → routes-2QRAKMGK.js} +17 -8
- package/dist/routes-2QRAKMGK.js.map +1 -0
- package/dist/{search-GLKCJXSI.js → search-7A43AZ7M.js} +4 -4
- package/dist/{search-GLKCJXSI.js.map → search-7A43AZ7M.js.map} +1 -1
- package/dist/server/node.d.ts +1 -1
- package/dist/server/node.js +6 -1
- package/dist/server/node.js.map +1 -1
- package/dist/slide/index.d.ts +230 -0
- package/dist/slide/index.js +1571 -0
- package/dist/slide/index.js.map +1 -0
- package/dist/table/index.d.ts +107 -0
- package/dist/table/index.js +680 -0
- package/dist/table/index.js.map +1 -0
- package/dist/types-DQjLpLCs.d.ts +121 -0
- package/dist/ui/index.js +3 -41
- package/dist/ui/index.js.map +1 -1
- package/dist/{upgrade-S64IXL42.js → upgrade-2QCP7UQQ.js} +9 -9
- package/dist/upgrade-2QCP7UQQ.js.map +1 -0
- package/dist/vite/index.d.ts +1 -1
- package/dist/vite/index.js +5 -5
- package/docs/en/agent-skills.md +19 -12
- package/docs/en/changelog.md +77 -0
- package/docs/en/cli-reference.md +8 -4
- package/docs/en/pages-and-metadata.md +17 -0
- package/docs/en/routing.md +75 -3
- package/docs/en/ui-and-theming.md +76 -0
- package/docs/index.json +2 -2
- package/docs/ja/agent-skills.md +18 -11
- package/docs/ja/changelog.md +75 -0
- package/docs/ja/cli-reference.md +8 -4
- package/docs/ja/pages-and-metadata.md +17 -0
- package/docs/ja/routing.md +71 -3
- package/docs/ja/ui-and-theming.md +74 -0
- package/package.json +29 -9
- package/skills/vantage-app/SKILL.md +4 -0
- package/skills/vantage-pitfalls/SKILL.md +41 -1
- package/skills/vantage-slide/SKILL.md +264 -0
- package/skills/vantage-slide/references/morph.md +102 -0
- package/skills/vantage-slide/references/steps.md +57 -0
- package/skills/vantage-slide/references/tokens.md +87 -0
- package/skills/vantage-slide/references/transitions.md +196 -0
- package/templates/AGENTS.md +28 -230
- package/templates/agents/api.md +49 -0
- package/templates/agents/csv.md +32 -0
- package/templates/agents/page.md +185 -0
- package/templates/agents/slide.md +34 -0
- package/templates/agents/xlsx.md +33 -0
- package/theme.css +28 -0
- package/dist/add-PPCBMWEN.js +0 -6
- package/dist/add-PPCBMWEN.js.map +0 -1
- package/dist/chunk-BAM33YZ6.js.map +0 -1
- package/dist/chunk-C4MQRWXS.js.map +0 -1
- package/dist/chunk-D4Y5RWJA.js.map +0 -1
- package/dist/chunk-EHPJXDJU.js.map +0 -1
- package/dist/chunk-L3TW4FNA.js.map +0 -1
- package/dist/chunk-PSEYFYAP.js.map +0 -1
- package/dist/chunk-RA6Z3OX3.js.map +0 -1
- package/dist/chunk-VXAV2435.js.map +0 -1
- package/dist/chunk-X44ZTKID.js.map +0 -1
- package/dist/routes-WCN4KSZY.js.map +0 -1
- package/dist/types-De3mo_hv.d.ts +0 -59
- 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
|