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