@pixerate/editor-react 0.15.0 → 0.17.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 (54) hide show
  1. package/README.md +6 -2
  2. package/dist/canvas/index.cjs +484 -119
  3. package/dist/canvas/index.cjs.map +1 -1
  4. package/dist/canvas/index.d.cts +122 -3
  5. package/dist/canvas/index.d.ts +122 -3
  6. package/dist/canvas/index.js +380 -0
  7. package/dist/canvas/index.js.map +1 -1
  8. package/dist/chunk-I3E4KSA2.js +515 -0
  9. package/dist/chunk-I3E4KSA2.js.map +1 -0
  10. package/dist/chunk-JR5ZBDR7.js +2012 -0
  11. package/dist/chunk-JR5ZBDR7.js.map +1 -0
  12. package/dist/chunk-XWFU2CSG.js +167 -0
  13. package/dist/chunk-XWFU2CSG.js.map +1 -0
  14. package/dist/chunk-ZU54BMDC.js +2128 -0
  15. package/dist/chunk-ZU54BMDC.js.map +1 -0
  16. package/dist/generation/index.cjs +525 -0
  17. package/dist/generation/index.cjs.map +1 -0
  18. package/dist/generation/index.d.cts +124 -0
  19. package/dist/generation/index.d.ts +124 -0
  20. package/dist/generation/index.js +19 -0
  21. package/dist/generation/index.js.map +1 -0
  22. package/dist/image-editor/index.cjs +988 -337
  23. package/dist/image-editor/index.cjs.map +1 -1
  24. package/dist/image-editor/index.d.cts +92 -2
  25. package/dist/image-editor/index.d.ts +92 -2
  26. package/dist/image-editor/index.js +17 -3
  27. package/dist/index.cjs +3628 -754
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +226 -5
  30. package/dist/index.d.ts +226 -5
  31. package/dist/index.js +868 -77
  32. package/dist/index.js.map +1 -1
  33. package/dist/persistence/index.cjs +191 -0
  34. package/dist/persistence/index.cjs.map +1 -0
  35. package/dist/persistence/index.d.cts +77 -0
  36. package/dist/persistence/index.d.ts +77 -0
  37. package/dist/persistence/index.js +9 -0
  38. package/dist/persistence/index.js.map +1 -0
  39. package/dist/spreadsheet/index.cjs +949 -111
  40. package/dist/spreadsheet/index.cjs.map +1 -1
  41. package/dist/spreadsheet/index.d.cts +121 -7
  42. package/dist/spreadsheet/index.d.ts +121 -7
  43. package/dist/spreadsheet/index.js +11 -1
  44. package/dist/tokens/index.cjs +395 -0
  45. package/dist/tokens/index.cjs.map +1 -0
  46. package/dist/tokens/index.d.cts +54 -0
  47. package/dist/tokens/index.d.ts +54 -0
  48. package/dist/tokens/index.js +375 -0
  49. package/dist/tokens/index.js.map +1 -0
  50. package/package.json +26 -2
  51. package/dist/chunk-LPFZMZ3L.js +0 -1461
  52. package/dist/chunk-LPFZMZ3L.js.map +0 -1
  53. package/dist/chunk-QVQYYHJ5.js +0 -1161
  54. package/dist/chunk-QVQYYHJ5.js.map +0 -1
@@ -1,1161 +0,0 @@
1
- // src/spreadsheet/useSpreadsheetEditor.ts
2
- import { useState, useRef, useCallback, useMemo } from "react";
3
- import {
4
- SpreadsheetController,
5
- bindSpreadsheetTableActions
6
- } from "@pixerate/editor/spreadsheet";
7
- function useSpreadsheetEditor(options = {}) {
8
- const optionsRef = useRef(options);
9
- optionsRef.current = options;
10
- const settersRef = useRef({});
11
- const [controller] = useState(
12
- () => new SpreadsheetController({
13
- ...options,
14
- onDocumentChange: (updated) => {
15
- settersRef.current.setDoc?.({ ...updated });
16
- optionsRef.current.onDocumentChange?.(updated);
17
- },
18
- onSelectionChange: (active, range) => {
19
- settersRef.current.setActiveCell?.(active);
20
- settersRef.current.setSelectedRange?.(range);
21
- optionsRef.current.onSelectionChange?.(active, range);
22
- },
23
- onCellCommit: (rowId, colId, raw, value) => {
24
- optionsRef.current.onCellCommit?.(rowId, colId, raw, value);
25
- }
26
- })
27
- );
28
- const [doc, setDoc] = useState(() => ({ ...controller.document }));
29
- const [activeCell, setActiveCell] = useState(controller.activeCell);
30
- const [selectedRange, setSelectedRange] = useState(controller.selectedRange);
31
- const [editingCell, setEditingCell] = useState(null);
32
- const [draftValue, setDraftState] = useState("");
33
- settersRef.current = { setDoc, setActiveCell, setSelectedRange };
34
- const setDraftValue = useCallback((value) => {
35
- controller.draftValue = value;
36
- setDraftState(value);
37
- }, [controller]);
38
- const setCellValue = useCallback((rowId, colId, raw) => {
39
- controller.setCellValue(rowId, colId, raw);
40
- setDoc({ ...controller.document });
41
- }, [controller]);
42
- const selectCell = useCallback((row, col, extendSelection = false) => {
43
- controller.selectCell(row, col, extendSelection);
44
- setActiveCell(controller.activeCell);
45
- setSelectedRange(controller.selectedRange);
46
- }, [controller]);
47
- const selectRange = useCallback((range) => {
48
- controller.selectRange(range);
49
- setSelectedRange(controller.selectedRange);
50
- }, [controller]);
51
- const startEditing = useCallback((row, col, initialVal) => {
52
- controller.startEditing(row, col, initialVal);
53
- setActiveCell(controller.activeCell);
54
- setEditingCell(controller.editingCell);
55
- setDraftValue(controller.draftValue);
56
- }, [controller]);
57
- const commitEditing = useCallback((val) => {
58
- controller.commitEditing(val);
59
- setEditingCell(null);
60
- setDraftValue("");
61
- setDoc({ ...controller.document });
62
- }, [controller]);
63
- const cancelEditing = useCallback(() => {
64
- controller.cancelEditing();
65
- setEditingCell(null);
66
- setDraftValue("");
67
- }, [controller]);
68
- const moveCursor = useCallback((dir, extend = false) => {
69
- controller.moveCursor(dir, extend);
70
- setActiveCell(controller.activeCell);
71
- setSelectedRange(controller.selectedRange);
72
- }, [controller]);
73
- const insertColumn = useCallback((index, config) => {
74
- controller.insertColumn(index, config);
75
- setDoc({ ...controller.document });
76
- }, [controller]);
77
- const deleteColumn = useCallback((colId) => {
78
- controller.deleteColumn(colId);
79
- setDoc({ ...controller.document });
80
- }, [controller]);
81
- const insertRow = useCallback((index, config) => {
82
- controller.insertRow(index, config);
83
- setDoc({ ...controller.document });
84
- }, [controller]);
85
- const deleteRow = useCallback((rowId) => {
86
- controller.deleteRow(rowId);
87
- setDoc({ ...controller.document });
88
- }, [controller]);
89
- const setColumnWidth = useCallback((colId, width) => {
90
- controller.setColumnWidth(colId, width);
91
- setDoc({ ...controller.document });
92
- }, [controller]);
93
- const autoFitColumnWidth = useCallback((colId, customWidth) => {
94
- controller.autoFitColumnWidth(colId, customWidth);
95
- setDoc({ ...controller.document });
96
- }, [controller]);
97
- const registerDataSource = useCallback((ds) => {
98
- controller.registerDataSource(ds);
99
- setDoc({ ...controller.document });
100
- }, [controller]);
101
- const setPrimaryDataSource = useCallback((id) => {
102
- controller.setPrimaryDataSource(id);
103
- setDoc({ ...controller.document });
104
- }, [controller]);
105
- const recalculateAll = useCallback(() => {
106
- controller.recalculateAll();
107
- setDoc({ ...controller.document });
108
- }, [controller]);
109
- const undo = useCallback(() => {
110
- controller.undo();
111
- setDoc({ ...controller.document });
112
- }, [controller]);
113
- const redo = useCallback(() => {
114
- controller.redo();
115
- setDoc({ ...controller.document });
116
- }, [controller]);
117
- const tableActions = useMemo(() => bindSpreadsheetTableActions(controller), [controller]);
118
- const viewModel = useMemo(() => controller.getViewModel(), [controller, doc]);
119
- return {
120
- document: doc,
121
- activeCell,
122
- selectedRange,
123
- editingCell,
124
- draftValue,
125
- setDraftValue,
126
- controller,
127
- getCell: (rowId, colId) => controller.getCell(rowId, colId),
128
- setCellValue,
129
- selectCell,
130
- selectRange,
131
- startEditing,
132
- commitEditing,
133
- cancelEditing,
134
- moveCursor,
135
- insertColumn,
136
- deleteColumn,
137
- insertRow,
138
- deleteRow,
139
- setColumnWidth,
140
- autoFitColumnWidth,
141
- loadDocument: (document2) => {
142
- controller.loadDocument(document2);
143
- setDoc({ ...controller.document });
144
- setActiveCell(null);
145
- setSelectedRange(null);
146
- setEditingCell(null);
147
- setDraftValue("");
148
- },
149
- syncWithDataSource: () => {
150
- controller.syncWithDataSource();
151
- controller.recalculateAll();
152
- setDoc({ ...controller.document });
153
- },
154
- registerDataSource,
155
- setPrimaryDataSource,
156
- recalculateAll,
157
- exportToTsv: (range) => controller.exportToTsv(range),
158
- importFromTsv: (data) => {
159
- controller.importFromTsv(data);
160
- setDoc({ ...controller.document });
161
- },
162
- ...tableActions,
163
- viewModel,
164
- undo,
165
- redo
166
- };
167
- }
168
-
169
- // src/spreadsheet/SpreadsheetEditor.tsx
170
- import { useRef as useRef5, useState as useState5, useMemo as useMemo3, useEffect as useEffect4, useCallback as useCallback3 } from "react";
171
- import {
172
- indexToColName,
173
- getSpreadsheetMenuItems,
174
- normalizeRange as normalizeRange2,
175
- rangeContains,
176
- formatCellDisplay
177
- } from "@pixerate/editor/spreadsheet";
178
-
179
- // src/spreadsheet/SpreadsheetContextMenu.tsx
180
- import React, { useEffect, useLayoutEffect, useRef as useRef2, useState as useState2 } from "react";
181
- import { clampMenuPosition } from "@pixerate/editor/spreadsheet";
182
- import { jsx, jsxs } from "react/jsx-runtime";
183
- var VIEWPORT_MARGIN = 4;
184
- var SpreadsheetContextMenu = ({ items, x, y, onClose }) => {
185
- const menuRef = useRef2(null);
186
- const onCloseRef = useRef2(onClose);
187
- onCloseRef.current = onClose;
188
- const [position, setPosition] = useState2({ x, y });
189
- useLayoutEffect(() => {
190
- const menu = menuRef.current;
191
- if (!menu || typeof window === "undefined") return;
192
- const { width, height } = menu.getBoundingClientRect();
193
- const next = clampMenuPosition(
194
- { x, y },
195
- { width, height },
196
- { width: window.innerWidth, height: window.innerHeight },
197
- VIEWPORT_MARGIN
198
- );
199
- setPosition((prev) => prev.x === next.x && prev.y === next.y ? prev : next);
200
- }, [x, y, items]);
201
- useEffect(() => {
202
- const handlePointer = (e) => {
203
- if (!menuRef.current?.contains(e.target)) onCloseRef.current();
204
- };
205
- const handleKey = (e) => {
206
- if (e.key === "Escape") onCloseRef.current();
207
- };
208
- window.addEventListener("mousedown", handlePointer);
209
- window.addEventListener("keydown", handleKey);
210
- return () => {
211
- window.removeEventListener("mousedown", handlePointer);
212
- window.removeEventListener("keydown", handleKey);
213
- };
214
- }, []);
215
- return /* @__PURE__ */ jsx(
216
- "div",
217
- {
218
- ref: menuRef,
219
- role: "menu",
220
- "aria-label": "Table actions",
221
- className: "fixed z-50 min-w-[14rem] rounded-md border border-border bg-popover py-1 text-xs text-popover-foreground shadow-lg",
222
- style: {
223
- left: position.x,
224
- top: position.y,
225
- maxHeight: `calc(100vh - ${VIEWPORT_MARGIN * 2}px)`,
226
- overflowY: "auto",
227
- background: "var(--popover, var(--background, #fff))"
228
- },
229
- onContextMenu: (e) => e.preventDefault(),
230
- onKeyDown: (e) => e.stopPropagation(),
231
- children: items.map((item) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
232
- item.separatorBefore && /* @__PURE__ */ jsx("div", { role: "separator", className: "my-1 h-px bg-border" }),
233
- /* @__PURE__ */ jsx(
234
- "button",
235
- {
236
- type: "button",
237
- role: "menuitem",
238
- "data-action": item.id,
239
- disabled: item.disabled,
240
- className: "block w-full px-3 py-1 text-left hover:bg-muted disabled:cursor-default disabled:opacity-40 disabled:hover:bg-transparent",
241
- onClick: () => {
242
- onClose();
243
- item.run();
244
- },
245
- children: item.label
246
- }
247
- )
248
- ] }, item.id))
249
- }
250
- );
251
- };
252
-
253
- // src/spreadsheet/SpreadsheetGroupRow.tsx
254
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
255
- var formatSummary = (value) => value === null ? "\u2013" : String(value);
256
- var SpreadsheetGroupRow = ({
257
- group,
258
- columns,
259
- groupColId,
260
- onToggle,
261
- trailingCell
262
- }) => {
263
- const labelColId = columns.some((c) => c.column.id === groupColId) ? groupColId : columns[0]?.column.id;
264
- return /* @__PURE__ */ jsxs2("tr", { className: "border-b border-border bg-muted/40 font-semibold", "data-group-key": group.key, style: { height: 28 }, children: [
265
- /* @__PURE__ */ jsx2("td", { className: "w-12 min-w-[3rem] sticky left-0 z-10 bg-muted border-r border-border text-center", children: /* @__PURE__ */ jsx2(
266
- "button",
267
- {
268
- type: "button",
269
- "aria-label": `${group.collapsed ? "Expand" : "Collapse"} group ${group.label}`,
270
- "aria-expanded": !group.collapsed,
271
- className: "h-5 w-5 rounded text-[10px] text-muted-foreground hover:bg-muted",
272
- onClick: (e) => {
273
- e.stopPropagation();
274
- onToggle(group.key);
275
- },
276
- children: group.collapsed ? "\u25B8" : "\u25BE"
277
- }
278
- ) }),
279
- columns.map(({ column }) => {
280
- const summary = group.summaries[column.id];
281
- return /* @__PURE__ */ jsx2("td", { className: "border-r border-border px-2 py-1 truncate max-w-0 text-foreground", style: { maxWidth: 0, overflow: "hidden" }, children: column.id === labelColId ? /* @__PURE__ */ jsxs2("span", { className: "truncate", children: [
282
- group.label,
283
- " ",
284
- /* @__PURE__ */ jsxs2("span", { className: "font-normal text-muted-foreground", children: [
285
- "(",
286
- group.rowIndices.length,
287
- ")"
288
- ] })
289
- ] }) : summary !== void 0 ? /* @__PURE__ */ jsx2("span", { className: "truncate", "data-summary": column.id, children: formatSummary(summary) }) : null }, column.id);
290
- }),
291
- trailingCell && /* @__PURE__ */ jsx2("td", { className: "w-10 min-w-[2.5rem] bg-muted/10 border-r border-border" })
292
- ] });
293
- };
294
-
295
- // src/spreadsheet/useSpreadsheetTableInteractions.ts
296
- import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef3, useState as useState3 } from "react";
297
- import {
298
- computeFillTarget,
299
- normalizeRange
300
- } from "@pixerate/editor/spreadsheet";
301
- function gridHasFocus(grid) {
302
- if (!grid || typeof document === "undefined") return false;
303
- const active = document.activeElement;
304
- if (!active || !grid.contains(active)) return false;
305
- return !(active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement);
306
- }
307
- function useSpreadsheetClipboard(gridRef, state, readOnly) {
308
- const stateRef = useRef3(state);
309
- stateRef.current = state;
310
- const readOnlyRef = useRef3(readOnly);
311
- readOnlyRef.current = readOnly;
312
- const copiedRef = useRef3(false);
313
- useEffect2(() => {
314
- if (typeof document === "undefined") return;
315
- const handleCopy = (e) => {
316
- const s = stateRef.current;
317
- if (!gridHasFocus(gridRef.current) || s.editingCell || !e.clipboardData || !s.selectedRange) return;
318
- for (const [type, data] of Object.entries(s.copyToClipboardData(s.selectedRange))) {
319
- e.clipboardData.setData(type, data);
320
- }
321
- copiedRef.current = true;
322
- e.preventDefault();
323
- };
324
- const handlePaste = (e) => {
325
- const s = stateRef.current;
326
- if (readOnlyRef.current || !gridHasFocus(gridRef.current) || s.editingCell || !e.clipboardData) return;
327
- if (s.pasteClipboardData(e.clipboardData)) e.preventDefault();
328
- };
329
- document.addEventListener("copy", handleCopy);
330
- document.addEventListener("paste", handlePaste);
331
- return () => {
332
- document.removeEventListener("copy", handleCopy);
333
- document.removeEventListener("paste", handlePaste);
334
- };
335
- }, [gridRef]);
336
- const copyShortcutFallback = useCallback2(() => {
337
- copiedRef.current = false;
338
- setTimeout(() => {
339
- if (copiedRef.current) return;
340
- const s = stateRef.current;
341
- Promise.resolve(navigator.clipboard?.writeText(s.exportToTsv(s.selectedRange))).catch(() => {
342
- });
343
- }, 0);
344
- }, []);
345
- return { copyShortcutFallback };
346
- }
347
- function useFillHandle(state) {
348
- const [drag, setDrag] = useState3(null);
349
- const dragRef = useRef3(drag);
350
- dragRef.current = drag;
351
- const stateRef = useRef3(state);
352
- stateRef.current = state;
353
- const dragging = drag !== null;
354
- useEffect2(() => {
355
- if (!dragging) return;
356
- const handleMouseUp = () => {
357
- const current = dragRef.current;
358
- setDrag(null);
359
- if (current?.target) stateRef.current.autofill(current.source, current.target);
360
- };
361
- window.addEventListener("mouseup", handleMouseUp);
362
- return () => window.removeEventListener("mouseup", handleMouseUp);
363
- }, [dragging]);
364
- const startFill = useCallback2((e) => {
365
- e.preventDefault();
366
- e.stopPropagation();
367
- const range = stateRef.current.selectedRange;
368
- if (range) setDrag({ source: normalizeRange(range), target: null });
369
- }, []);
370
- const extendFill = useCallback2((row, col) => {
371
- const current = dragRef.current;
372
- if (!current) return;
373
- setDrag({ source: current.source, target: computeFillTarget(current.source, { row, col }) });
374
- }, []);
375
- return { fillTarget: drag?.target ?? null, isFilling: dragging, startFill, extendFill };
376
- }
377
-
378
- // src/spreadsheet/FormulaHints.tsx
379
- import { useEffect as useEffect3, useLayoutEffect as useLayoutEffect2, useMemo as useMemo2, useRef as useRef4, useState as useState4 } from "react";
380
- import { applyFormulaSuggestion, getFormulaHints } from "@pixerate/editor/spreadsheet";
381
- import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
382
- var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
383
- const [caret, setCaret] = useState4(null);
384
- const [focused, setFocused] = useState4(false);
385
- const [selected, setSelected] = useState4(0);
386
- const [dismissed, setDismissed] = useState4(false);
387
- const [position, setPosition] = useState4(null);
388
- const pendingCaret = useRef4(null);
389
- const hints = useMemo2(
390
- () => getFormulaHints(value, caret ?? value.length, { limit }),
391
- [value, caret, limit]
392
- );
393
- const suggestions = dismissed ? [] : hints.suggestions;
394
- const selectedIndex = Math.max(0, Math.min(selected, suggestions.length - 1));
395
- const visible = focused && value.trimStart().startsWith("=") && (suggestions.length > 0 || !!hints.activeCall);
396
- const latest = useRef4({ suggestions, selected: selectedIndex, value, caret, onAccept });
397
- latest.current = { suggestions, selected: selectedIndex, value, caret, onAccept };
398
- const accept = (index) => {
399
- const { suggestions: list, value: current, caret: at, onAccept: commit } = latest.current;
400
- const suggestion = list[index];
401
- if (!suggestion) return;
402
- const next = applyFormulaSuggestion(current, at ?? current.length, suggestion.name);
403
- pendingCaret.current = next.caret;
404
- commit(next.value);
405
- };
406
- useEffect3(() => {
407
- const input = inputRef.current;
408
- if (!input) return;
409
- const syncCaret = () => setCaret(input.selectionStart ?? input.value.length);
410
- const onInput = () => {
411
- setDismissed(false);
412
- setSelected(0);
413
- syncCaret();
414
- };
415
- const onFocus = () => {
416
- setFocused(true);
417
- syncCaret();
418
- };
419
- const onBlur = () => setFocused(false);
420
- const onKeyDown = (e) => {
421
- const { suggestions: list } = latest.current;
422
- if (list.length === 0) return;
423
- if (e.key === "ArrowDown" || e.key === "ArrowUp") {
424
- e.preventDefault();
425
- e.stopPropagation();
426
- const step = e.key === "ArrowDown" ? 1 : -1;
427
- setSelected((i) => (i + step + list.length) % list.length);
428
- } else if (e.key === "Tab" && !e.shiftKey) {
429
- e.preventDefault();
430
- e.stopPropagation();
431
- accept(latest.current.selected);
432
- } else if (e.key === "Escape") {
433
- e.preventDefault();
434
- e.stopPropagation();
435
- setDismissed(true);
436
- }
437
- };
438
- input.addEventListener("input", onInput);
439
- input.addEventListener("keyup", syncCaret);
440
- input.addEventListener("click", syncCaret);
441
- input.addEventListener("select", syncCaret);
442
- input.addEventListener("focus", onFocus);
443
- input.addEventListener("blur", onBlur);
444
- input.addEventListener("keydown", onKeyDown);
445
- if (typeof document !== "undefined" && document.activeElement === input) onFocus();
446
- return () => {
447
- input.removeEventListener("input", onInput);
448
- input.removeEventListener("keyup", syncCaret);
449
- input.removeEventListener("click", syncCaret);
450
- input.removeEventListener("select", syncCaret);
451
- input.removeEventListener("focus", onFocus);
452
- input.removeEventListener("blur", onBlur);
453
- input.removeEventListener("keydown", onKeyDown);
454
- };
455
- }, [inputRef]);
456
- useLayoutEffect2(() => {
457
- const input = inputRef.current;
458
- const at = pendingCaret.current;
459
- if (!input || at === null || input.value !== value) return;
460
- pendingCaret.current = null;
461
- input.setSelectionRange(at, at);
462
- setCaret(at);
463
- setSelected(0);
464
- }, [value, inputRef]);
465
- useLayoutEffect2(() => {
466
- const input = inputRef.current;
467
- if (!visible || !input) return;
468
- const rect = input.getBoundingClientRect();
469
- setPosition({ top: rect.bottom + 2, left: rect.left, width: rect.width });
470
- }, [visible, value, inputRef]);
471
- if (!visible) return null;
472
- const { activeCall } = hints;
473
- return /* @__PURE__ */ jsxs3(
474
- "div",
475
- {
476
- "data-formula-hints": "",
477
- className: "rounded-md border border-border bg-popover text-popover-foreground shadow-md text-xs font-sans overflow-hidden",
478
- style: {
479
- position: "fixed",
480
- top: position?.top ?? 0,
481
- left: position?.left ?? 0,
482
- minWidth: Math.max(260, position?.width ?? 0),
483
- maxWidth: 440,
484
- zIndex: 50,
485
- visibility: position ? "visible" : "hidden"
486
- },
487
- onMouseDown: (e) => e.preventDefault(),
488
- children: [
489
- activeCall && /* @__PURE__ */ jsxs3("div", { "data-formula-signature": "", className: "px-2 py-1.5 border-b border-border bg-muted/30", children: [
490
- /* @__PURE__ */ jsx3("div", { className: "font-mono", children: activeCall.parts.map((part, i) => /* @__PURE__ */ jsx3(
491
- "span",
492
- {
493
- "data-active": part.active ? "" : void 0,
494
- className: part.active ? "font-bold text-primary underline" : "",
495
- children: part.text
496
- },
497
- i
498
- )) }),
499
- /* @__PURE__ */ jsx3("div", { className: "mt-0.5 text-muted-foreground", children: activeCall.argument ? /* @__PURE__ */ jsxs3(Fragment, { children: [
500
- /* @__PURE__ */ jsx3("span", { className: "font-semibold", children: activeCall.argument.name }),
501
- ": ",
502
- activeCall.argument.description
503
- ] }) : activeCall.description })
504
- ] }),
505
- suggestions.length > 0 && /* @__PURE__ */ jsx3("ul", { role: "listbox", "aria-label": "Formula suggestions", className: "max-h-64 overflow-auto py-1 m-0 list-none p-0", children: suggestions.map((s, i) => /* @__PURE__ */ jsxs3(
506
- "li",
507
- {
508
- role: "option",
509
- "aria-selected": i === selectedIndex,
510
- className: `cursor-pointer px-2 py-1 ${i === selectedIndex ? "bg-primary/10" : "hover:bg-muted/50"}`,
511
- onMouseEnter: () => setSelected(i),
512
- onMouseDown: (e) => {
513
- e.preventDefault();
514
- accept(i);
515
- },
516
- children: [
517
- /* @__PURE__ */ jsx3("div", { className: "font-mono font-semibold text-foreground", children: s.signature }),
518
- /* @__PURE__ */ jsx3("div", { className: "text-muted-foreground truncate", children: s.description })
519
- ]
520
- },
521
- s.name
522
- )) })
523
- ]
524
- }
525
- );
526
- };
527
-
528
- // src/spreadsheet/SpreadsheetEditor.tsx
529
- import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
530
- var COLUMN_HEADER_HEIGHT = 28;
531
- var ROW_HEADER_WIDTH = 48;
532
- function frozenCellStyle(viewRow, viewCol) {
533
- if (!viewRow.frozen && !viewCol.frozen) return void 0;
534
- return {
535
- position: "sticky",
536
- ...viewRow.frozen ? { top: COLUMN_HEADER_HEIGHT + viewRow.stickyTop } : {},
537
- ...viewCol.frozen ? { left: ROW_HEADER_WIDTH + viewCol.stickyLeft } : {},
538
- zIndex: viewRow.frozen && viewCol.frozen ? 14 : viewRow.frozen ? 12 : 11,
539
- background: "var(--background, #fff)"
540
- };
541
- }
542
- var coordKey = (cell) => cell ? `${indexToColName(cell.col)}${cell.row + 1}` : "";
543
- function spillBorderStyle(range, row, col) {
544
- const line = "1px solid color-mix(in srgb, var(--primary, #6366f1) 60%, transparent)";
545
- return {
546
- position: "absolute",
547
- inset: 0,
548
- pointerEvents: "none",
549
- zIndex: 5,
550
- borderTop: row === range.startRow ? line : void 0,
551
- borderBottom: row === range.endRow ? line : void 0,
552
- borderLeft: col === range.startCol ? line : void 0,
553
- borderRight: col === range.endCol ? line : void 0
554
- };
555
- }
556
- var SpreadsheetEditor = ({
557
- state: externalState,
558
- readOnly = false,
559
- customCellRenderer,
560
- onAddColumnClick,
561
- onColumnHeaderClick,
562
- onColumnResize
563
- }) => {
564
- const defaultState = useSpreadsheetEditor();
565
- const state = externalState || defaultState;
566
- const gridRef = useRef5(null);
567
- const formulaBarRef = useRef5(null);
568
- const cellInputRef = useRef5(null);
569
- const [resizingColId, setResizingColId] = useState5(null);
570
- const [hoveredColId, setHoveredColId] = useState5(null);
571
- const [resizeStartX, setResizeStartX] = useState5(0);
572
- const [resizeStartWidth, setResizeStartWidth] = useState5(0);
573
- const view = state.viewModel;
574
- const { copyShortcutFallback } = useSpreadsheetClipboard(gridRef, state, readOnly);
575
- const { fillTarget, startFill, extendFill } = useFillHandle(state);
576
- const [menu, setMenu] = useState5(null);
577
- const filter = state.document.table?.filter;
578
- const filteredColIds = useMemo3(
579
- () => new Set(filter?.enabled ? filter.rules.map((r) => r.colId) : []),
580
- [filter]
581
- );
582
- const handleCellContextMenu = useCallback3((rIdx, cIdx, e) => {
583
- if (readOnly) return;
584
- e.preventDefault();
585
- if (state.editingCell) state.commitEditing();
586
- if (!state.selectedRange || !rangeContains(state.selectedRange, rIdx, cIdx)) {
587
- state.selectCell(rIdx, cIdx);
588
- }
589
- setMenu({ x: e.clientX, y: e.clientY, items: getSpreadsheetMenuItems(state.controller, { row: rIdx, col: cIdx }) });
590
- }, [readOnly, state]);
591
- const activeRawValue = useMemo3(() => {
592
- if (!state.activeCell) return "";
593
- const row = state.document.rows[state.activeCell.row];
594
- const col = state.document.columns[state.activeCell.col];
595
- if (!row || !col) return "";
596
- return state.getCell(row.id, col.id).raw;
597
- }, [state.activeCell, state.document]);
598
- const activeSpill = useMemo3(
599
- () => state.activeCell ? state.controller.getSpillAt(state.activeCell.row, state.activeCell.col) : null,
600
- // eslint-disable-next-line react-hooks/exhaustive-deps
601
- [state.controller, state.activeCell, state.document]
602
- );
603
- const spillFormula = activeSpill && !activeRawValue && activeSpill.key !== coordKey(state.activeCell) ? activeSpill.formula : "";
604
- const coordLabel = useMemo3(() => {
605
- if (!state.activeCell) return "--";
606
- const colName = indexToColName(state.activeCell.col);
607
- return `${colName}${state.activeCell.row + 1}`;
608
- }, [state.activeCell]);
609
- const [formulaDraft, setFormulaDraft] = useState5(null);
610
- const activeColumn = state.activeCell ? state.document.columns[state.activeCell.col] : void 0;
611
- const formulaBarReadOnly = readOnly || !state.activeCell || !!activeColumn?.readOnly;
612
- useEffect4(() => {
613
- setFormulaDraft(null);
614
- }, [state.activeCell?.row, state.activeCell?.col]);
615
- const commitFormulaBar = () => {
616
- if (state.editingCell) {
617
- state.commitEditing();
618
- return;
619
- }
620
- if (formulaDraft === null || !state.activeCell) return;
621
- const row = state.document.rows[state.activeCell.row];
622
- const col = state.document.columns[state.activeCell.col];
623
- if (row && col && !formulaBarReadOnly && formulaDraft !== activeRawValue) {
624
- state.setCellValue(row.id, col.id, formulaDraft);
625
- }
626
- setFormulaDraft(null);
627
- };
628
- const handleCellClick = useCallback3((rIdx, cIdx, e) => {
629
- if (state.editingCell && (state.editingCell.row !== rIdx || state.editingCell.col !== cIdx)) {
630
- state.commitEditing();
631
- }
632
- state.selectCell(rIdx, cIdx, e.shiftKey);
633
- }, [state]);
634
- const handleCellDblClick = useCallback3((rIdx, cIdx) => {
635
- if (readOnly) return;
636
- const col = state.document.columns[cIdx];
637
- if (col?.readOnly) return;
638
- state.startEditing(rIdx, cIdx);
639
- }, [readOnly, state]);
640
- const handleKeyDown = useCallback3((e) => {
641
- if (state.editingCell) {
642
- if (e.key === "Enter") {
643
- e.preventDefault();
644
- state.commitEditing();
645
- state.moveCursor("down");
646
- } else if (e.key === "Escape") {
647
- e.preventDefault();
648
- state.cancelEditing();
649
- } else if (e.key === "Tab") {
650
- e.preventDefault();
651
- state.commitEditing();
652
- state.moveCursor(e.shiftKey ? "left" : "right");
653
- } else {
654
- return;
655
- }
656
- gridRef.current?.focus();
657
- return;
658
- }
659
- if (!state.activeCell) return;
660
- if (e.key === "ArrowUp") {
661
- e.preventDefault();
662
- state.moveCursor("up", e.shiftKey);
663
- } else if (e.key === "ArrowDown") {
664
- e.preventDefault();
665
- state.moveCursor("down", e.shiftKey);
666
- } else if (e.key === "ArrowLeft") {
667
- e.preventDefault();
668
- state.moveCursor("left", e.shiftKey);
669
- } else if (e.key === "ArrowRight") {
670
- e.preventDefault();
671
- state.moveCursor("right", e.shiftKey);
672
- } else if (e.key === "Tab") {
673
- e.preventDefault();
674
- state.moveCursor(e.shiftKey ? "left" : "right");
675
- } else if (e.key === "Enter") {
676
- e.preventDefault();
677
- const col = state.document.columns[state.activeCell.col];
678
- if (!readOnly && !col?.readOnly) {
679
- state.startEditing();
680
- }
681
- } else if (e.key === "Backspace" || e.key === "Delete") {
682
- if (readOnly) return;
683
- const row = state.document.rows[state.activeCell.row];
684
- const col = state.document.columns[state.activeCell.col];
685
- if (row && col && !col.readOnly) {
686
- e.preventDefault();
687
- state.setCellValue(row.id, col.id, "");
688
- }
689
- } else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "z") {
690
- e.preventDefault();
691
- if (e.shiftKey) {
692
- state.redo();
693
- } else {
694
- state.undo();
695
- }
696
- } else if (e.ctrlKey && !e.metaKey && e.key.toLowerCase() === "y") {
697
- e.preventDefault();
698
- state.redo();
699
- } else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "c") {
700
- copyShortcutFallback();
701
- } else if (!readOnly && e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
702
- const col = state.document.columns[state.activeCell.col];
703
- if (!col?.readOnly) {
704
- e.preventDefault();
705
- state.startEditing(state.activeCell.row, state.activeCell.col, e.key);
706
- }
707
- }
708
- }, [readOnly, state, copyShortcutFallback]);
709
- const stateRef = useRef5(state);
710
- stateRef.current = state;
711
- const onColumnResizeRef = useRef5(onColumnResize);
712
- onColumnResizeRef.current = onColumnResize;
713
- useEffect4(() => {
714
- if (!resizingColId) return;
715
- if (typeof document !== "undefined") {
716
- document.body.style.cursor = "col-resize";
717
- document.body.style.userSelect = "none";
718
- }
719
- let rafId = null;
720
- let pendingWidth = resizeStartWidth;
721
- let hasDragged = false;
722
- const handleMouseMove = (e) => {
723
- const delta = e.clientX - resizeStartX;
724
- if (Math.abs(delta) > 2) {
725
- hasDragged = true;
726
- }
727
- pendingWidth = Math.max(30, resizeStartWidth + delta);
728
- if (rafId === null) {
729
- rafId = requestAnimationFrame(() => {
730
- rafId = null;
731
- stateRef.current.setColumnWidth(resizingColId, pendingWidth);
732
- });
733
- }
734
- };
735
- const handleMouseUp = () => {
736
- if (rafId !== null) {
737
- cancelAnimationFrame(rafId);
738
- rafId = null;
739
- }
740
- if (hasDragged) {
741
- stateRef.current.setColumnWidth(resizingColId, pendingWidth);
742
- onColumnResizeRef.current?.(resizingColId, pendingWidth);
743
- }
744
- setResizingColId(null);
745
- if (typeof document !== "undefined") {
746
- document.body.style.cursor = "";
747
- document.body.style.userSelect = "";
748
- }
749
- };
750
- window.addEventListener("mousemove", handleMouseMove);
751
- window.addEventListener("mouseup", handleMouseUp);
752
- return () => {
753
- if (rafId !== null) {
754
- cancelAnimationFrame(rafId);
755
- }
756
- if (typeof document !== "undefined") {
757
- document.body.style.cursor = "";
758
- document.body.style.userSelect = "";
759
- }
760
- window.removeEventListener("mousemove", handleMouseMove);
761
- window.removeEventListener("mouseup", handleMouseUp);
762
- };
763
- }, [resizingColId, resizeStartX, resizeStartWidth]);
764
- const measureElementContentWidth = useCallback3((el, container) => {
765
- if (typeof document === "undefined") return 0;
766
- let maxW = el.scrollWidth || 0;
767
- const firstChild = el.firstElementChild;
768
- if (firstChild && firstChild.scrollWidth > maxW) {
769
- maxW = firstChild.scrollWidth;
770
- }
771
- const truncated = el.querySelectorAll(".truncate, [title], span, div");
772
- truncated.forEach((node) => {
773
- if (node.scrollWidth > maxW) {
774
- maxW = node.scrollWidth;
775
- }
776
- });
777
- try {
778
- const clone = firstChild ? firstChild.cloneNode(true) : el.cloneNode(true);
779
- clone.style.cssText = `
780
- position: absolute !important;
781
- visibility: hidden !important;
782
- width: max-content !important;
783
- min-width: max-content !important;
784
- max-width: none !important;
785
- overflow: visible !important;
786
- white-space: nowrap !important;
787
- pointer-events: none !important;
788
- top: -9999px !important;
789
- left: -9999px !important;
790
- `;
791
- const allNodes = clone.querySelectorAll("*");
792
- allNodes.forEach((node) => {
793
- node.style.maxWidth = "none";
794
- node.style.overflow = "visible";
795
- node.style.textOverflow = "clip";
796
- node.style.whiteSpace = "nowrap";
797
- if (node.classList.contains("truncate") || node.classList.contains("min-w-0") || node.classList.contains("w-full") || node.classList.contains("max-w-0") || node.classList.contains("flex-1")) {
798
- node.style.width = "max-content";
799
- node.style.minWidth = "max-content";
800
- }
801
- });
802
- container.appendChild(clone);
803
- const rect = clone.getBoundingClientRect();
804
- container.removeChild(clone);
805
- if (rect.width > maxW) {
806
- maxW = rect.width;
807
- }
808
- } catch {
809
- }
810
- return maxW;
811
- }, []);
812
- const autoFitColumn = useCallback3((colId, colIndex) => {
813
- const col = state.document.columns.find((c) => c.id === colId);
814
- if (!col) return;
815
- let calculatedWidth = 0;
816
- if (gridRef.current && typeof document !== "undefined") {
817
- const table = gridRef.current.querySelector("table");
818
- if (table) {
819
- const th = table.querySelector(`thead th[data-col="${colIndex}"]`);
820
- if (th) {
821
- const headerInner = th.firstElementChild || th;
822
- const headerW = measureElementContentWidth(headerInner, gridRef.current);
823
- calculatedWidth = Math.max(calculatedWidth, headerW + 28);
824
- }
825
- const rows = table.querySelectorAll("tbody tr");
826
- const sampleLimit = Math.min(rows.length, 150);
827
- for (let r = 0; r < sampleLimit; r++) {
828
- const tr = rows[r];
829
- const td = tr.querySelector(`td[data-col="${colIndex}"][colspan="1"]`);
830
- if (td) {
831
- const cellW = measureElementContentWidth(td, gridRef.current);
832
- calculatedWidth = Math.max(calculatedWidth, cellW + 20);
833
- }
834
- }
835
- }
836
- }
837
- if (calculatedWidth > 0) {
838
- const finalWidth = Math.min(600, Math.max(50, Math.ceil(calculatedWidth)));
839
- state.setColumnWidth(colId, finalWidth);
840
- } else {
841
- state.autoFitColumnWidth?.(colId);
842
- }
843
- const updatedCol = state.document.columns.find((c) => c.id === colId);
844
- if (updatedCol && onColumnResize) {
845
- onColumnResize(updatedCol.id, updatedCol.width || 130);
846
- }
847
- }, [state, measureElementContentWidth, onColumnResize]);
848
- const handleResizeDblClick = useCallback3((colId, colIndex, e) => {
849
- e.preventDefault();
850
- e.stopPropagation();
851
- autoFitColumn(colId, colIndex);
852
- }, [autoFitColumn]);
853
- const handleHeaderDblClick = useCallback3((colId, colIndex, e) => {
854
- const target = e.currentTarget;
855
- if (target) {
856
- const rect = target.getBoundingClientRect();
857
- if (rect.right - e.clientX <= 12) {
858
- e.preventDefault();
859
- e.stopPropagation();
860
- autoFitColumn(colId, colIndex);
861
- }
862
- }
863
- }, [autoFitColumn]);
864
- const isCellSelected = (rIdx, cIdx) => {
865
- if (!state.selectedRange) return false;
866
- const minR = Math.min(state.selectedRange.startRow, state.selectedRange.endRow);
867
- const maxR = Math.max(state.selectedRange.startRow, state.selectedRange.endRow);
868
- const minC = Math.min(state.selectedRange.startCol, state.selectedRange.endCol);
869
- const maxC = Math.max(state.selectedRange.startCol, state.selectedRange.endCol);
870
- return rIdx >= minR && rIdx <= maxR && cIdx >= minC && cIdx <= maxC;
871
- };
872
- const isActiveCell = (rIdx, cIdx) => {
873
- return state.activeCell?.row === rIdx && state.activeCell?.col === cIdx;
874
- };
875
- return /* @__PURE__ */ jsxs4(
876
- "div",
877
- {
878
- ref: gridRef,
879
- className: "flex flex-col h-full w-full overflow-hidden border border-border bg-background select-none outline-none text-foreground font-sans text-xs",
880
- tabIndex: 0,
881
- onKeyDown: handleKeyDown,
882
- role: "grid",
883
- "aria-label": "Spreadsheet",
884
- children: [
885
- /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2 border-b border-border bg-muted/20 px-3 py-1.5 text-xs font-mono", children: [
886
- /* @__PURE__ */ jsx4("div", { className: "flex h-6 min-w-[3rem] items-center justify-center rounded border border-border bg-background px-1.5 font-semibold text-foreground", children: coordLabel }),
887
- /* @__PURE__ */ jsx4("div", { className: "flex items-center text-muted-foreground select-none", children: /* @__PURE__ */ jsx4("span", { className: "italic font-serif font-bold text-sm text-primary", children: "fx" }) }),
888
- /* @__PURE__ */ jsx4(
889
- "input",
890
- {
891
- ref: formulaBarRef,
892
- type: "text",
893
- className: "flex-1 bg-transparent px-2 py-0.5 outline-none font-sans text-xs text-foreground placeholder:text-muted-foreground/60 focus:bg-background focus:ring-1 focus:ring-primary rounded",
894
- placeholder: "Enter a value or formula (e.g. =SUM(A1:A5))",
895
- "aria-label": "Formula bar",
896
- readOnly: formulaBarReadOnly,
897
- "data-spill-formula": !state.editingCell && formulaDraft === null && spillFormula ? "true" : void 0,
898
- style: !state.editingCell && formulaDraft === null && spillFormula ? { color: "var(--muted-foreground, #94a3b8)" } : void 0,
899
- value: state.editingCell ? state.draftValue : formulaDraft ?? (activeRawValue || spillFormula),
900
- onFocus: () => {
901
- if (!state.editingCell) setFormulaDraft(activeRawValue);
902
- },
903
- onChange: (e) => {
904
- if (state.editingCell) {
905
- state.setDraftValue(e.target.value);
906
- } else if (state.activeCell) {
907
- setFormulaDraft(e.target.value);
908
- }
909
- },
910
- onBlur: () => commitFormulaBar(),
911
- onKeyDown: (e) => {
912
- e.stopPropagation();
913
- if (e.key === "Enter") {
914
- e.preventDefault();
915
- commitFormulaBar();
916
- } else if (e.key === "Escape") {
917
- e.preventDefault();
918
- setFormulaDraft(null);
919
- if (state.editingCell) state.cancelEditing();
920
- }
921
- }
922
- }
923
- ),
924
- !formulaBarReadOnly && /* @__PURE__ */ jsx4(
925
- FormulaHints,
926
- {
927
- inputRef: formulaBarRef,
928
- value: state.editingCell ? state.draftValue : formulaDraft ?? activeRawValue,
929
- onAccept: (next) => state.editingCell ? state.setDraftValue(next) : setFormulaDraft(next)
930
- }
931
- )
932
- ] }),
933
- /* @__PURE__ */ jsx4("div", { className: "relative flex-1 overflow-auto", children: /* @__PURE__ */ jsxs4("table", { className: "border-collapse table-fixed w-max min-w-full", children: [
934
- /* @__PURE__ */ jsx4("thead", { children: /* @__PURE__ */ jsxs4("tr", { className: "h-7 bg-muted sticky top-0 z-20 border-b border-border shadow-xs", children: [
935
- /* @__PURE__ */ jsx4("th", { className: "w-12 min-w-[3rem] sticky left-0 z-30 bg-muted border-r border-border text-center text-[10px] text-muted-foreground font-medium", children: "#" }),
936
- view.columns.map(({ column: col, index: cIdx, frozen, stickyLeft }) => /* @__PURE__ */ jsxs4(
937
- "th",
938
- {
939
- "data-col": cIdx,
940
- className: `relative border-r border-border px-2 text-left font-medium text-muted-foreground hover:bg-muted/90 transition-colors group overflow-hidden max-w-0 ${onColumnHeaderClick ? "cursor-pointer" : "cursor-default"}`,
941
- style: {
942
- width: col.width || 130,
943
- minWidth: col.width || 130,
944
- maxWidth: col.width || 130,
945
- overflow: "hidden",
946
- ...frozen ? { position: "sticky", left: ROW_HEADER_WIDTH + stickyLeft, zIndex: 25, background: "var(--muted, #f4f4f5)" } : {}
947
- },
948
- onClick: () => onColumnHeaderClick?.(col),
949
- onDoubleClick: (e) => handleHeaderDblClick(col.id, cIdx, e),
950
- children: [
951
- /* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between gap-1 min-w-0 overflow-hidden truncate", children: [
952
- /* @__PURE__ */ jsx4("span", { className: "truncate font-semibold text-foreground text-xs min-w-0", children: col.title }),
953
- filteredColIds.has(col.id) && /* @__PURE__ */ jsx4("span", { className: "text-[10px] text-primary shrink-0", title: "Filtered", "data-filtered": "true", children: "\u23F7" }),
954
- /* @__PURE__ */ jsxs4("span", { className: "text-[10px] text-muted-foreground/70 font-mono font-normal shrink-0", children: [
955
- "(",
956
- col.key,
957
- ")"
958
- ] })
959
- ] }),
960
- /* @__PURE__ */ jsx4(
961
- "div",
962
- {
963
- className: "col-resize-handle",
964
- style: { position: "absolute", top: 0, bottom: 0, width: 12, right: -6, cursor: "col-resize", zIndex: 20, display: "flex", justifyContent: "center", touchAction: "none" },
965
- onMouseEnter: () => setHoveredColId(col.id),
966
- onMouseLeave: () => setHoveredColId((cur) => cur === col.id ? null : cur),
967
- onMouseDown: (e) => {
968
- e.preventDefault();
969
- e.stopPropagation();
970
- setResizingColId(col.id);
971
- setResizeStartX(e.clientX);
972
- setResizeStartWidth(col.width || 130);
973
- },
974
- onDoubleClick: (e) => handleResizeDblClick(col.id, cIdx, e),
975
- role: "separator",
976
- "aria-orientation": "vertical",
977
- children: /* @__PURE__ */ jsx4(
978
- "div",
979
- {
980
- className: "col-resize-handle-line",
981
- style: {
982
- width: 2,
983
- height: "100%",
984
- pointerEvents: "none",
985
- transition: "background-color 150ms ease",
986
- backgroundColor: resizingColId === col.id ? "var(--primary, #6366f1)" : hoveredColId === col.id ? "color-mix(in srgb, var(--primary, #6366f1) 75%, transparent)" : "transparent"
987
- }
988
- }
989
- )
990
- }
991
- )
992
- ]
993
- },
994
- col.id
995
- )),
996
- onAddColumnClick && /* @__PURE__ */ jsx4(
997
- "th",
998
- {
999
- className: "w-10 min-w-[2.5rem] px-1 text-center bg-muted/40 hover:bg-muted cursor-pointer transition-colors",
1000
- onClick: onAddColumnClick,
1001
- title: "Add Column",
1002
- children: /* @__PURE__ */ jsx4("span", { className: "text-xs font-bold text-muted-foreground", children: "+" })
1003
- }
1004
- )
1005
- ] }) }),
1006
- /* @__PURE__ */ jsx4("tbody", { children: view.rows.map((viewRow) => {
1007
- if (viewRow.kind === "group") {
1008
- return /* @__PURE__ */ jsx4(
1009
- SpreadsheetGroupRow,
1010
- {
1011
- group: viewRow.group,
1012
- columns: view.columns,
1013
- groupColId: state.document.table?.grouping?.colId ?? "",
1014
- onToggle: state.toggleGroupCollapsed,
1015
- trailingCell: !!onAddColumnClick
1016
- },
1017
- `group:${viewRow.group.key}`
1018
- );
1019
- }
1020
- const displayRowIndex = viewRow.index;
1021
- const selection = state.selectedRange ? normalizeRange2(state.selectedRange) : null;
1022
- return /* @__PURE__ */ jsxs4(
1023
- "tr",
1024
- {
1025
- "data-row": displayRowIndex,
1026
- className: `border-b border-border hover:bg-muted/10 transition-colors ${viewRow.section !== "body" ? "bg-muted/30 font-semibold" : ""}`,
1027
- style: { height: viewRow.row.height || 32 },
1028
- children: [
1029
- /* @__PURE__ */ jsx4(
1030
- "td",
1031
- {
1032
- className: "w-12 min-w-[3rem] sticky left-0 z-10 bg-muted border-r border-border text-center text-[10px] text-muted-foreground font-mono select-none",
1033
- style: viewRow.frozen ? { top: COLUMN_HEADER_HEIGHT + viewRow.stickyTop, zIndex: 15 } : void 0,
1034
- children: displayRowIndex + 1
1035
- }
1036
- ),
1037
- view.columns.map((viewCol) => {
1038
- const span = view.getCellSpan(displayRowIndex, viewCol.index);
1039
- if (!span) return null;
1040
- const rIdx = span.source.row;
1041
- const cIdx = span.source.col;
1042
- const row = state.document.rows[rIdx];
1043
- const col = state.document.columns[cIdx];
1044
- const cell = state.getCell(row.id, col.id);
1045
- const selected = isCellSelected(rIdx, cIdx);
1046
- const active = isActiveCell(rIdx, cIdx);
1047
- const isEditing = state.editingCell?.row === rIdx && state.editingCell?.col === cIdx;
1048
- const inFillPreview = !!fillTarget && !selected && rangeContains(fillTarget, displayRowIndex, viewCol.index);
1049
- const showFillHandle = !readOnly && !isEditing && !!selection && (span.merge ? rangeContains(span.merge, selection.endRow, selection.endCol) : selection.endRow === rIdx && selection.endCol === cIdx);
1050
- return /* @__PURE__ */ jsxs4(
1051
- "td",
1052
- {
1053
- "data-row": rIdx,
1054
- "data-col": cIdx,
1055
- rowSpan: span.rowSpan,
1056
- colSpan: span.colSpan,
1057
- className: `relative border-r border-border px-2 py-1 truncate max-w-0 text-foreground transition-colors ${selected ? "bg-primary/10" : ""} ${active ? "ring-2 ring-primary ring-inset z-10" : ""} ${viewCol.header ? "font-semibold" : ""}`,
1058
- style: {
1059
- maxWidth: 0,
1060
- overflow: "hidden",
1061
- ...frozenCellStyle(viewRow, viewCol),
1062
- ...inFillPreview ? { outline: "1px dashed var(--primary, #6366f1)", outlineOffset: -1 } : {}
1063
- },
1064
- onClick: (e) => handleCellClick(rIdx, cIdx, e),
1065
- onDoubleClick: () => handleCellDblClick(rIdx, cIdx),
1066
- onContextMenu: (e) => handleCellContextMenu(rIdx, cIdx, e),
1067
- onMouseOver: () => extendFill(displayRowIndex, viewCol.index),
1068
- children: [
1069
- isEditing ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
1070
- /* @__PURE__ */ jsx4(
1071
- "input",
1072
- {
1073
- ref: cellInputRef,
1074
- type: "text",
1075
- className: "absolute inset-0 w-full h-full px-2 py-1 bg-background text-foreground outline-none font-sans text-xs border-2 border-primary z-20",
1076
- value: state.draftValue,
1077
- onChange: (e) => state.setDraftValue(e.target.value),
1078
- onBlur: () => state.commitEditing(),
1079
- onFocus: (e) => {
1080
- const end = e.currentTarget.value.length;
1081
- e.currentTarget.setSelectionRange(end, end);
1082
- },
1083
- autoFocus: true
1084
- }
1085
- ),
1086
- /* @__PURE__ */ jsx4(FormulaHints, { inputRef: cellInputRef, value: state.draftValue, onAccept: state.setDraftValue })
1087
- ] }) : customCellRenderer ? customCellRenderer({ row, col, cell }) : /* @__PURE__ */ jsx4(
1088
- "span",
1089
- {
1090
- className: `truncate block max-w-full ${cell.error ? "text-destructive font-semibold" : ""}`,
1091
- title: cell.error ? cell.errorMessage ?? cell.error : void 0,
1092
- children: formatCellDisplay(cell)
1093
- }
1094
- ),
1095
- activeSpill && rangeContains(activeSpill.range, rIdx, cIdx) && /* @__PURE__ */ jsx4("span", { "aria-hidden": "true", className: "spreadsheet-spill-border", style: spillBorderStyle(activeSpill.range, rIdx, cIdx) }),
1096
- showFillHandle && /* @__PURE__ */ jsx4(
1097
- "div",
1098
- {
1099
- className: "spreadsheet-fill-handle",
1100
- "aria-label": "Fill handle",
1101
- onMouseDown: startFill,
1102
- onClick: (e) => e.stopPropagation(),
1103
- style: { position: "absolute", right: 0, bottom: 0, width: 7, height: 7, background: "var(--primary, #6366f1)", cursor: "crosshair", zIndex: 25 }
1104
- }
1105
- )
1106
- ]
1107
- },
1108
- viewCol.column.id
1109
- );
1110
- }),
1111
- onAddColumnClick && /* @__PURE__ */ jsx4("td", { className: "w-10 min-w-[2.5rem] bg-muted/10 border-r border-border" })
1112
- ]
1113
- },
1114
- viewRow.row.id
1115
- );
1116
- }) })
1117
- ] }) }),
1118
- menu && /* @__PURE__ */ jsx4(
1119
- SpreadsheetContextMenu,
1120
- {
1121
- items: menu.items,
1122
- x: menu.x,
1123
- y: menu.y,
1124
- onClose: () => {
1125
- setMenu(null);
1126
- gridRef.current?.focus();
1127
- }
1128
- }
1129
- ),
1130
- /* @__PURE__ */ jsx4("style", { children: `
1131
- .col-resize-handle:hover .col-resize-handle-line {
1132
- background-color: var(--primary, #6366f1);
1133
- opacity: 0.75;
1134
- }
1135
- .col-resize-handle.active .col-resize-handle-line {
1136
- background-color: var(--primary, #6366f1);
1137
- opacity: 1;
1138
- }
1139
- table.table-fixed th {
1140
- overflow: hidden;
1141
- }
1142
- table.table-fixed td {
1143
- max-width: 0;
1144
- overflow: hidden;
1145
- }
1146
- ` })
1147
- ]
1148
- }
1149
- );
1150
- };
1151
-
1152
- export {
1153
- useSpreadsheetEditor,
1154
- SpreadsheetContextMenu,
1155
- SpreadsheetGroupRow,
1156
- useSpreadsheetClipboard,
1157
- useFillHandle,
1158
- FormulaHints,
1159
- SpreadsheetEditor
1160
- };
1161
- //# sourceMappingURL=chunk-QVQYYHJ5.js.map