@pixerate/editor-react 0.16.0 → 0.18.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.
- package/README.md +4 -2
- package/dist/canvas/index.cjs +1094 -143
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +277 -3
- package/dist/canvas/index.d.ts +277 -3
- package/dist/canvas/index.js +976 -0
- package/dist/canvas/index.js.map +1 -1
- package/dist/chunk-34HEZX7J.js +2144 -0
- package/dist/chunk-34HEZX7J.js.map +1 -0
- package/dist/chunk-I3E4KSA2.js +515 -0
- package/dist/chunk-I3E4KSA2.js.map +1 -0
- package/dist/{chunk-XTFR3IMK.js → chunk-ZU54BMDC.js} +473 -160
- package/dist/chunk-ZU54BMDC.js.map +1 -0
- package/dist/generation/index.cjs +290 -0
- package/dist/generation/index.cjs.map +1 -1
- package/dist/generation/index.d.cts +66 -2
- package/dist/generation/index.d.ts +66 -2
- package/dist/generation/index.js +7 -1
- package/dist/image-editor/index.cjs +490 -187
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +34 -2
- package/dist/image-editor/index.d.ts +34 -2
- package/dist/image-editor/index.js +5 -1
- package/dist/index.cjs +2792 -823
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +188 -7
- package/dist/index.d.ts +188 -7
- package/dist/index.js +600 -158
- package/dist/index.js.map +1 -1
- package/dist/spreadsheet/index.cjs +1099 -142
- package/dist/spreadsheet/index.cjs.map +1 -1
- package/dist/spreadsheet/index.d.cts +156 -8
- package/dist/spreadsheet/index.d.ts +156 -8
- package/dist/spreadsheet/index.js +15 -3
- package/dist/tokens/index.cjs +395 -0
- package/dist/tokens/index.cjs.map +1 -0
- package/dist/tokens/index.d.cts +54 -0
- package/dist/tokens/index.d.ts +54 -0
- package/dist/tokens/index.js +375 -0
- package/dist/tokens/index.js.map +1 -0
- package/package.json +10 -2
- package/dist/chunk-7SPRQZ6H.js +0 -216
- package/dist/chunk-7SPRQZ6H.js.map +0 -1
- package/dist/chunk-FYIPXHAE.js +0 -1169
- package/dist/chunk-FYIPXHAE.js.map +0 -1
- package/dist/chunk-XTFR3IMK.js.map +0 -1
|
@@ -31,12 +31,18 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var spreadsheet_exports = {};
|
|
32
32
|
__export(spreadsheet_exports, {
|
|
33
33
|
FormulaHints: () => FormulaHints,
|
|
34
|
+
NameManager: () => NameManager,
|
|
35
|
+
SheetTabs: () => SheetTabs,
|
|
36
|
+
SpreadsheetConditionalFormatPanel: () => SpreadsheetConditionalFormatPanel,
|
|
34
37
|
SpreadsheetContextMenu: () => SpreadsheetContextMenu,
|
|
38
|
+
SpreadsheetDataValidationPanel: () => SpreadsheetDataValidationPanel,
|
|
35
39
|
SpreadsheetEditor: () => SpreadsheetEditor,
|
|
36
40
|
SpreadsheetGroupRow: () => SpreadsheetGroupRow,
|
|
41
|
+
SpreadsheetValidationDialog: () => SpreadsheetValidationDialog,
|
|
37
42
|
useFillHandle: () => useFillHandle,
|
|
38
43
|
useSpreadsheetClipboard: () => useSpreadsheetClipboard,
|
|
39
|
-
useSpreadsheetEditor: () => useSpreadsheetEditor
|
|
44
|
+
useSpreadsheetEditor: () => useSpreadsheetEditor,
|
|
45
|
+
useSpreadsheetViewport: () => useSpreadsheetViewport
|
|
40
46
|
});
|
|
41
47
|
module.exports = __toCommonJS(spreadsheet_exports);
|
|
42
48
|
|
|
@@ -69,6 +75,7 @@ function useSpreadsheetEditor(options = {}) {
|
|
|
69
75
|
const [selectedRange, setSelectedRange] = (0, import_react.useState)(controller.selectedRange);
|
|
70
76
|
const [editingCell, setEditingCell] = (0, import_react.useState)(null);
|
|
71
77
|
const [draftValue, setDraftState] = (0, import_react.useState)("");
|
|
78
|
+
const [validationPrompt, setValidationPrompt] = (0, import_react.useState)(null);
|
|
72
79
|
settersRef.current = { setDoc, setActiveCell, setSelectedRange };
|
|
73
80
|
const setDraftValue = (0, import_react.useCallback)((value) => {
|
|
74
81
|
controller.draftValue = value;
|
|
@@ -93,12 +100,28 @@ function useSpreadsheetEditor(options = {}) {
|
|
|
93
100
|
setEditingCell(controller.editingCell);
|
|
94
101
|
setDraftValue(controller.draftValue);
|
|
95
102
|
}, [controller]);
|
|
96
|
-
const
|
|
97
|
-
controller.
|
|
98
|
-
|
|
99
|
-
|
|
103
|
+
const syncEditing = (0, import_react.useCallback)(() => {
|
|
104
|
+
setEditingCell(controller.editingCell);
|
|
105
|
+
setDraftValue(controller.draftValue);
|
|
106
|
+
setValidationPrompt(controller.validationPrompt);
|
|
107
|
+
setActiveCell(controller.activeCell);
|
|
108
|
+
setSelectedRange(controller.selectedRange);
|
|
100
109
|
setDoc({ ...controller.document });
|
|
101
110
|
}, [controller]);
|
|
111
|
+
const commitEditing = (0, import_react.useCallback)((val) => {
|
|
112
|
+
const result = controller.commitEditing(val);
|
|
113
|
+
syncEditing();
|
|
114
|
+
return result;
|
|
115
|
+
}, [controller, syncEditing]);
|
|
116
|
+
const commitCellInput = (0, import_react.useCallback)((rowId, colId, raw) => {
|
|
117
|
+
const result = controller.commitCellInput(rowId, colId, raw);
|
|
118
|
+
syncEditing();
|
|
119
|
+
return result;
|
|
120
|
+
}, [controller, syncEditing]);
|
|
121
|
+
const resolveValidationPrompt = (0, import_react.useCallback)((action) => {
|
|
122
|
+
controller.resolveValidationPrompt(action);
|
|
123
|
+
syncEditing();
|
|
124
|
+
}, [controller, syncEditing]);
|
|
102
125
|
const cancelEditing = (0, import_react.useCallback)(() => {
|
|
103
126
|
controller.cancelEditing();
|
|
104
127
|
setEditingCell(null);
|
|
@@ -148,13 +171,57 @@ function useSpreadsheetEditor(options = {}) {
|
|
|
148
171
|
const undo = (0, import_react.useCallback)(() => {
|
|
149
172
|
controller.undo();
|
|
150
173
|
setDoc({ ...controller.document });
|
|
174
|
+
setActiveCell(controller.activeCell);
|
|
175
|
+
setSelectedRange(controller.selectedRange);
|
|
151
176
|
}, [controller]);
|
|
152
177
|
const redo = (0, import_react.useCallback)(() => {
|
|
153
178
|
controller.redo();
|
|
154
179
|
setDoc({ ...controller.document });
|
|
180
|
+
setActiveCell(controller.activeCell);
|
|
181
|
+
setSelectedRange(controller.selectedRange);
|
|
155
182
|
}, [controller]);
|
|
156
183
|
const tableActions = (0, import_react.useMemo)(() => (0, import_spreadsheet.bindSpreadsheetTableActions)(controller), [controller]);
|
|
157
184
|
const viewModel = (0, import_react.useMemo)(() => controller.getViewModel(), [controller, doc]);
|
|
185
|
+
const sheets = (0, import_react.useMemo)(() => controller.getSheets(), [controller, doc]);
|
|
186
|
+
const names = (0, import_react.useMemo)(() => controller.getNames(), [controller, doc]);
|
|
187
|
+
const afterSheetSwitch = (0, import_react.useCallback)(() => {
|
|
188
|
+
setEditingCell(controller.editingCell);
|
|
189
|
+
setDraftValue(controller.draftValue);
|
|
190
|
+
setActiveCell(controller.activeCell);
|
|
191
|
+
setSelectedRange(controller.selectedRange);
|
|
192
|
+
setDoc({ ...controller.document });
|
|
193
|
+
}, [controller, setDraftValue]);
|
|
194
|
+
const workbookActions = (0, import_react.useMemo)(
|
|
195
|
+
() => ({
|
|
196
|
+
setActiveSheet: (idOrName) => {
|
|
197
|
+
const changed = controller.setActiveSheet(idOrName);
|
|
198
|
+
afterSheetSwitch();
|
|
199
|
+
return changed;
|
|
200
|
+
},
|
|
201
|
+
addSheet: (options2) => {
|
|
202
|
+
const id = controller.addSheet(options2);
|
|
203
|
+
afterSheetSwitch();
|
|
204
|
+
return id;
|
|
205
|
+
},
|
|
206
|
+
renameSheet: (idOrName, newName) => controller.renameSheet(idOrName, newName),
|
|
207
|
+
moveSheet: (idOrName, toIndex) => controller.moveSheet(idOrName, toIndex),
|
|
208
|
+
deleteSheet: (idOrName) => {
|
|
209
|
+
const deleted = controller.deleteSheet(idOrName);
|
|
210
|
+
afterSheetSwitch();
|
|
211
|
+
return deleted;
|
|
212
|
+
},
|
|
213
|
+
duplicateSheet: (idOrName, options2) => {
|
|
214
|
+
const id = controller.duplicateSheet(idOrName, options2);
|
|
215
|
+
afterSheetSwitch();
|
|
216
|
+
return id;
|
|
217
|
+
},
|
|
218
|
+
defineName: (name, refersTo, options2) => controller.defineName(name, refersTo, options2),
|
|
219
|
+
renameName: (oldName, newName, options2) => controller.renameName(oldName, newName, options2),
|
|
220
|
+
deleteName: (name, options2) => controller.deleteName(name, options2)
|
|
221
|
+
}),
|
|
222
|
+
[controller, afterSheetSwitch]
|
|
223
|
+
);
|
|
224
|
+
const conditionalFormatOverlay = (0, import_react.useMemo)(() => controller.getConditionalFormatOverlay(), [controller, doc]);
|
|
158
225
|
return {
|
|
159
226
|
document: doc,
|
|
160
227
|
activeCell,
|
|
@@ -162,6 +229,9 @@ function useSpreadsheetEditor(options = {}) {
|
|
|
162
229
|
editingCell,
|
|
163
230
|
draftValue,
|
|
164
231
|
setDraftValue,
|
|
232
|
+
validationPrompt,
|
|
233
|
+
resolveValidationPrompt,
|
|
234
|
+
commitCellInput,
|
|
165
235
|
controller,
|
|
166
236
|
getCell: (rowId, colId) => controller.getCell(rowId, colId),
|
|
167
237
|
setCellValue,
|
|
@@ -200,21 +270,28 @@ function useSpreadsheetEditor(options = {}) {
|
|
|
200
270
|
},
|
|
201
271
|
...tableActions,
|
|
202
272
|
viewModel,
|
|
273
|
+
/** The sheets in tab order (see `SpreadsheetController.getSheets`). */
|
|
274
|
+
sheets,
|
|
275
|
+
activeSheetId: controller.activeSheetId,
|
|
276
|
+
/** Defined names (see `SpreadsheetController.getNames`). */
|
|
277
|
+
names,
|
|
278
|
+
...workbookActions,
|
|
279
|
+
conditionalFormatOverlay,
|
|
203
280
|
undo,
|
|
204
281
|
redo
|
|
205
282
|
};
|
|
206
283
|
}
|
|
207
284
|
|
|
208
285
|
// src/spreadsheet/SpreadsheetEditor.tsx
|
|
209
|
-
var
|
|
210
|
-
var
|
|
286
|
+
var import_react10 = require("react");
|
|
287
|
+
var import_spreadsheet8 = require("@pixerate/editor/spreadsheet");
|
|
211
288
|
|
|
212
289
|
// src/spreadsheet/SpreadsheetContextMenu.tsx
|
|
213
290
|
var import_react2 = __toESM(require("react"), 1);
|
|
214
291
|
var import_spreadsheet2 = require("@pixerate/editor/spreadsheet");
|
|
215
292
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
216
293
|
var VIEWPORT_MARGIN = 4;
|
|
217
|
-
var SpreadsheetContextMenu = ({ items, x, y, onClose }) => {
|
|
294
|
+
var SpreadsheetContextMenu = ({ items, x, y, onClose, onOpenPanel, ariaLabel = "Table actions" }) => {
|
|
218
295
|
const menuRef = (0, import_react2.useRef)(null);
|
|
219
296
|
const onCloseRef = (0, import_react2.useRef)(onClose);
|
|
220
297
|
onCloseRef.current = onClose;
|
|
@@ -250,7 +327,7 @@ var SpreadsheetContextMenu = ({ items, x, y, onClose }) => {
|
|
|
250
327
|
{
|
|
251
328
|
ref: menuRef,
|
|
252
329
|
role: "menu",
|
|
253
|
-
"aria-label":
|
|
330
|
+
"aria-label": ariaLabel,
|
|
254
331
|
className: "fixed z-50 min-w-[14rem] rounded-md border border-border bg-popover py-1 text-xs text-popover-foreground shadow-lg",
|
|
255
332
|
style: {
|
|
256
333
|
left: position.x,
|
|
@@ -274,7 +351,8 @@ var SpreadsheetContextMenu = ({ items, x, y, onClose }) => {
|
|
|
274
351
|
className: `block w-full px-3 py-1 text-left hover:bg-muted disabled:cursor-default disabled:opacity-40 disabled:hover:bg-transparent ${item.checked ? "font-semibold" : ""}`,
|
|
275
352
|
onClick: () => {
|
|
276
353
|
onClose();
|
|
277
|
-
(
|
|
354
|
+
if (item.panel) onOpenPanel?.(item.panel);
|
|
355
|
+
else (0, import_spreadsheet2.runSpreadsheetMenuItem)(item);
|
|
278
356
|
},
|
|
279
357
|
children: item.label
|
|
280
358
|
}
|
|
@@ -285,6 +363,7 @@ var SpreadsheetContextMenu = ({ items, x, y, onClose }) => {
|
|
|
285
363
|
};
|
|
286
364
|
|
|
287
365
|
// src/spreadsheet/SpreadsheetGroupRow.tsx
|
|
366
|
+
var import_react3 = __toESM(require("react"), 1);
|
|
288
367
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
289
368
|
var formatSummary = (value) => value === null ? "\u2013" : String(value);
|
|
290
369
|
var SpreadsheetGroupRow = ({
|
|
@@ -292,9 +371,14 @@ var SpreadsheetGroupRow = ({
|
|
|
292
371
|
columns,
|
|
293
372
|
groupColId,
|
|
294
373
|
onToggle,
|
|
295
|
-
trailingCell
|
|
374
|
+
trailingCell,
|
|
375
|
+
labelColumns = columns,
|
|
376
|
+
leadingColumns,
|
|
377
|
+
leftSpace = 0,
|
|
378
|
+
rightSpace = 0
|
|
296
379
|
}) => {
|
|
297
|
-
const labelColId =
|
|
380
|
+
const labelColId = labelColumns.some((c) => c.column.id === groupColId) ? groupColId : labelColumns[0]?.column.id;
|
|
381
|
+
const spacer = (key) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { "aria-hidden": "true", "data-spacer": "", style: { padding: 0 } }, key);
|
|
298
382
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { className: "border-b border-border bg-muted/40 font-semibold", "data-group-key": group.key, style: { height: 28 }, children: [
|
|
299
383
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { className: "w-12 min-w-[3rem] sticky left-0 z-10 bg-muted border-r border-border text-center", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
300
384
|
"button",
|
|
@@ -310,24 +394,30 @@ var SpreadsheetGroupRow = ({
|
|
|
310
394
|
children: group.collapsed ? "\u25B8" : "\u25BE"
|
|
311
395
|
}
|
|
312
396
|
) }),
|
|
313
|
-
columns.map(({ column }) => {
|
|
397
|
+
columns.map(({ column }, i) => {
|
|
314
398
|
const summary = group.summaries[column.id];
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
399
|
+
const before = leadingColumns !== void 0 && i === leadingColumns && leftSpace > 0 ? spacer("space:left") : null;
|
|
400
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react3.default.Fragment, { children: [
|
|
401
|
+
before,
|
|
402
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "truncate", children: [
|
|
403
|
+
group.label,
|
|
404
|
+
" ",
|
|
405
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-normal text-muted-foreground", children: [
|
|
406
|
+
"(",
|
|
407
|
+
group.rowIndices.length,
|
|
408
|
+
")"
|
|
409
|
+
] })
|
|
410
|
+
] }) : summary !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "truncate", "data-summary": column.id, children: formatSummary(summary) }) : null })
|
|
411
|
+
] }, column.id);
|
|
324
412
|
}),
|
|
413
|
+
leadingColumns !== void 0 && leadingColumns >= columns.length && leftSpace > 0 && spacer("space:left"),
|
|
414
|
+
rightSpace > 0 && spacer("space:right"),
|
|
325
415
|
trailingCell && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { className: "w-10 min-w-[2.5rem] bg-muted/10 border-r border-border" })
|
|
326
416
|
] });
|
|
327
417
|
};
|
|
328
418
|
|
|
329
419
|
// src/spreadsheet/useSpreadsheetTableInteractions.ts
|
|
330
|
-
var
|
|
420
|
+
var import_react4 = require("react");
|
|
331
421
|
var import_spreadsheet3 = require("@pixerate/editor/spreadsheet");
|
|
332
422
|
function gridHasFocus(grid) {
|
|
333
423
|
if (!grid || typeof document === "undefined") return false;
|
|
@@ -336,12 +426,12 @@ function gridHasFocus(grid) {
|
|
|
336
426
|
return !(active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement);
|
|
337
427
|
}
|
|
338
428
|
function useSpreadsheetClipboard(gridRef, state, readOnly) {
|
|
339
|
-
const stateRef = (0,
|
|
429
|
+
const stateRef = (0, import_react4.useRef)(state);
|
|
340
430
|
stateRef.current = state;
|
|
341
|
-
const readOnlyRef = (0,
|
|
431
|
+
const readOnlyRef = (0, import_react4.useRef)(readOnly);
|
|
342
432
|
readOnlyRef.current = readOnly;
|
|
343
|
-
const copiedRef = (0,
|
|
344
|
-
(0,
|
|
433
|
+
const copiedRef = (0, import_react4.useRef)(false);
|
|
434
|
+
(0, import_react4.useEffect)(() => {
|
|
345
435
|
if (typeof document === "undefined") return;
|
|
346
436
|
const handleCopy = (e) => {
|
|
347
437
|
const s = stateRef.current;
|
|
@@ -364,7 +454,7 @@ function useSpreadsheetClipboard(gridRef, state, readOnly) {
|
|
|
364
454
|
document.removeEventListener("paste", handlePaste);
|
|
365
455
|
};
|
|
366
456
|
}, [gridRef]);
|
|
367
|
-
const copyShortcutFallback = (0,
|
|
457
|
+
const copyShortcutFallback = (0, import_react4.useCallback)(() => {
|
|
368
458
|
copiedRef.current = false;
|
|
369
459
|
setTimeout(() => {
|
|
370
460
|
if (copiedRef.current) return;
|
|
@@ -376,13 +466,13 @@ function useSpreadsheetClipboard(gridRef, state, readOnly) {
|
|
|
376
466
|
return { copyShortcutFallback };
|
|
377
467
|
}
|
|
378
468
|
function useFillHandle(state) {
|
|
379
|
-
const [drag, setDrag] = (0,
|
|
380
|
-
const dragRef = (0,
|
|
469
|
+
const [drag, setDrag] = (0, import_react4.useState)(null);
|
|
470
|
+
const dragRef = (0, import_react4.useRef)(drag);
|
|
381
471
|
dragRef.current = drag;
|
|
382
|
-
const stateRef = (0,
|
|
472
|
+
const stateRef = (0, import_react4.useRef)(state);
|
|
383
473
|
stateRef.current = state;
|
|
384
474
|
const dragging = drag !== null;
|
|
385
|
-
(0,
|
|
475
|
+
(0, import_react4.useEffect)(() => {
|
|
386
476
|
if (!dragging) return;
|
|
387
477
|
const handleMouseUp = () => {
|
|
388
478
|
const current = dragRef.current;
|
|
@@ -392,13 +482,13 @@ function useFillHandle(state) {
|
|
|
392
482
|
window.addEventListener("mouseup", handleMouseUp);
|
|
393
483
|
return () => window.removeEventListener("mouseup", handleMouseUp);
|
|
394
484
|
}, [dragging]);
|
|
395
|
-
const startFill = (0,
|
|
485
|
+
const startFill = (0, import_react4.useCallback)((e) => {
|
|
396
486
|
e.preventDefault();
|
|
397
487
|
e.stopPropagation();
|
|
398
488
|
const range = stateRef.current.selectedRange;
|
|
399
489
|
if (range) setDrag({ source: (0, import_spreadsheet3.normalizeRange)(range), target: null });
|
|
400
490
|
}, []);
|
|
401
|
-
const extendFill = (0,
|
|
491
|
+
const extendFill = (0, import_react4.useCallback)((row, col) => {
|
|
402
492
|
const current = dragRef.current;
|
|
403
493
|
if (!current) return;
|
|
404
494
|
setDrag({ source: current.source, target: (0, import_spreadsheet3.computeFillTarget)(current.source, { row, col }) });
|
|
@@ -406,35 +496,401 @@ function useFillHandle(state) {
|
|
|
406
496
|
return { fillTarget: drag?.target ?? null, isFilling: dragging, startFill, extendFill };
|
|
407
497
|
}
|
|
408
498
|
|
|
409
|
-
// src/spreadsheet/
|
|
410
|
-
var
|
|
411
|
-
var import_spreadsheet4 = require("@pixerate/editor/spreadsheet");
|
|
499
|
+
// src/spreadsheet/SpreadsheetValidationDialog.tsx
|
|
500
|
+
var import_react5 = require("react");
|
|
412
501
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
413
|
-
var
|
|
414
|
-
const
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
502
|
+
var SpreadsheetValidationDialog = ({ prompt, onResolve }) => {
|
|
503
|
+
const style = prompt.result.errorStyle ?? "stop";
|
|
504
|
+
const firstButton = (0, import_react5.useRef)(null);
|
|
505
|
+
(0, import_react5.useEffect)(() => {
|
|
506
|
+
firstButton.current?.focus();
|
|
507
|
+
}, [prompt]);
|
|
508
|
+
const buttons = style === "stop" ? [
|
|
509
|
+
{ label: "Retry", action: "retry" },
|
|
510
|
+
{ label: "Cancel", action: "cancel" }
|
|
511
|
+
] : style === "warning" ? [
|
|
512
|
+
{ label: "Yes", action: "accept" },
|
|
513
|
+
{ label: "No", action: "retry" },
|
|
514
|
+
{ label: "Cancel", action: "cancel" }
|
|
515
|
+
] : [
|
|
516
|
+
{ label: "OK", action: "accept" },
|
|
517
|
+
{ label: "Cancel", action: "cancel" }
|
|
518
|
+
];
|
|
519
|
+
const icon = style === "stop" ? "\u26D4" : style === "warning" ? "\u26A0" : "\u2139";
|
|
520
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
521
|
+
"div",
|
|
522
|
+
{
|
|
523
|
+
className: "fixed inset-0 z-[60] flex items-center justify-center bg-black/30",
|
|
524
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
525
|
+
onKeyDown: (e) => {
|
|
526
|
+
e.stopPropagation();
|
|
527
|
+
if (e.key === "Escape") {
|
|
528
|
+
e.preventDefault();
|
|
529
|
+
onResolve("cancel");
|
|
530
|
+
}
|
|
531
|
+
},
|
|
532
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
533
|
+
"div",
|
|
534
|
+
{
|
|
535
|
+
role: "alertdialog",
|
|
536
|
+
"aria-modal": "true",
|
|
537
|
+
"aria-labelledby": "spreadsheet-validation-title",
|
|
538
|
+
"aria-describedby": "spreadsheet-validation-message",
|
|
539
|
+
"data-error-style": style,
|
|
540
|
+
className: "w-[22rem] max-w-[calc(100vw-2rem)] rounded-md border border-border bg-popover p-4 text-xs text-popover-foreground shadow-xl",
|
|
541
|
+
style: { background: "var(--popover, var(--background, #fff))" },
|
|
542
|
+
children: [
|
|
543
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-start gap-3", children: [
|
|
544
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { "aria-hidden": "true", className: "text-lg leading-none", children: icon }),
|
|
545
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "min-w-0 flex-1", children: [
|
|
546
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { id: "spreadsheet-validation-title", className: "mb-1 text-sm font-semibold", children: prompt.result.title }),
|
|
547
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { id: "spreadsheet-validation-message", className: "whitespace-pre-wrap", children: prompt.result.message }),
|
|
548
|
+
style === "warning" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mt-2", children: "Continue?" })
|
|
549
|
+
] })
|
|
550
|
+
] }),
|
|
551
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mt-4 flex justify-end gap-2", children: buttons.map((button, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
552
|
+
"button",
|
|
553
|
+
{
|
|
554
|
+
ref: i === 0 ? firstButton : void 0,
|
|
555
|
+
type: "button",
|
|
556
|
+
"data-action": button.action,
|
|
557
|
+
className: `rounded border border-border px-3 py-1 ${i === 0 ? "bg-primary text-primary-foreground" : "hover:bg-muted"}`,
|
|
558
|
+
onClick: () => onResolve(button.action),
|
|
559
|
+
children: button.label
|
|
560
|
+
},
|
|
561
|
+
button.action
|
|
562
|
+
)) })
|
|
563
|
+
]
|
|
564
|
+
}
|
|
565
|
+
)
|
|
566
|
+
}
|
|
567
|
+
);
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
// src/spreadsheet/SpreadsheetRulePanels.tsx
|
|
571
|
+
var import_react6 = require("react");
|
|
572
|
+
var import_spreadsheet4 = require("@pixerate/editor/spreadsheet");
|
|
573
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
574
|
+
var inputClass = "w-full rounded border border-border bg-background px-1.5 py-0.5 text-xs text-foreground";
|
|
575
|
+
var buttonClass = "rounded border border-border px-2 py-0.5 hover:bg-muted disabled:opacity-40";
|
|
576
|
+
function selectionText(range) {
|
|
577
|
+
return range ? (0, import_spreadsheet4.formatRuleRanges)([range]) : "A1";
|
|
578
|
+
}
|
|
579
|
+
var PanelFrame = ({
|
|
580
|
+
title,
|
|
581
|
+
label,
|
|
582
|
+
onClose,
|
|
583
|
+
children
|
|
584
|
+
}) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
585
|
+
"div",
|
|
586
|
+
{
|
|
587
|
+
role: "dialog",
|
|
588
|
+
"aria-label": label,
|
|
589
|
+
className: "fixed right-4 top-16 z-40 flex max-h-[calc(100vh-5rem)] w-80 flex-col overflow-y-auto rounded-md border border-border bg-popover p-3 text-xs text-popover-foreground shadow-lg",
|
|
590
|
+
style: { background: "var(--popover, var(--background, #fff))" },
|
|
591
|
+
onKeyDown: (e) => {
|
|
592
|
+
e.stopPropagation();
|
|
593
|
+
if (e.key === "Escape") onClose();
|
|
594
|
+
},
|
|
595
|
+
onContextMenu: (e) => e.stopPropagation(),
|
|
596
|
+
children: [
|
|
597
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mb-2 flex items-center justify-between", children: [
|
|
598
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h2", { className: "text-sm font-semibold", children: title }),
|
|
599
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", "aria-label": "Close", className: "px-1 text-muted-foreground hover:text-foreground", onClick: onClose, children: "\u2715" })
|
|
600
|
+
] }),
|
|
601
|
+
children
|
|
602
|
+
]
|
|
603
|
+
}
|
|
604
|
+
);
|
|
605
|
+
function RuleSwatch({ rule }) {
|
|
606
|
+
if (rule.type === "colorScale") {
|
|
607
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
608
|
+
"span",
|
|
609
|
+
{
|
|
610
|
+
"aria-hidden": "true",
|
|
611
|
+
className: "inline-block h-3 w-6 shrink-0 rounded-sm border border-border",
|
|
612
|
+
style: { background: `linear-gradient(90deg, ${rule.stops.map((s) => s.color).join(", ")})` }
|
|
613
|
+
}
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
if (rule.type === "dataBar") {
|
|
617
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { "aria-hidden": "true", className: "inline-block h-3 w-6 shrink-0 rounded-sm", style: { background: rule.color ?? "#638ec6" } });
|
|
618
|
+
}
|
|
619
|
+
if (rule.type === "iconSet") return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { "aria-hidden": "true", className: "shrink-0", children: "\u25CF" });
|
|
620
|
+
const css = (0, import_spreadsheet4.getConditionalFormatCss)(rule.style);
|
|
621
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { "aria-hidden": "true", className: "inline-flex h-4 w-6 shrink-0 items-center justify-center rounded-sm border border-border", style: css, children: "Aa" });
|
|
622
|
+
}
|
|
623
|
+
var SpreadsheetConditionalFormatPanel = ({ state, onClose }) => {
|
|
624
|
+
const rules = state.document.table?.conditionalFormats ?? [];
|
|
625
|
+
const [kind, setKind] = (0, import_react6.useState)("greaterThan");
|
|
626
|
+
const [appliesTo, setAppliesTo] = (0, import_react6.useState)(() => selectionText(state.selectedRange));
|
|
627
|
+
const [value, setValue] = (0, import_react6.useState)("");
|
|
628
|
+
const [value2, setValue2] = (0, import_react6.useState)("");
|
|
629
|
+
const [text, setText] = (0, import_react6.useState)("");
|
|
630
|
+
const [formula, setFormula] = (0, import_react6.useState)("=");
|
|
631
|
+
const [rank, setRank] = (0, import_react6.useState)("10");
|
|
632
|
+
const [period, setPeriod] = (0, import_react6.useState)("today");
|
|
633
|
+
const [iconSet, setIconSet] = (0, import_react6.useState)("3TrafficLights1");
|
|
634
|
+
const [color, setColor] = (0, import_react6.useState)("#638ec6");
|
|
635
|
+
const [styleId, setStyleId] = (0, import_react6.useState)(import_spreadsheet4.CONDITIONAL_FORMAT_STYLE_PRESETS[0].id);
|
|
636
|
+
const preset = (0, import_react6.useMemo)(() => import_spreadsheet4.CONDITIONAL_FORMAT_PRESETS.find((p) => p.kind === kind), [kind]);
|
|
637
|
+
const ranges = (0, import_spreadsheet4.parseRuleRanges)(appliesTo);
|
|
638
|
+
const add = () => {
|
|
639
|
+
if (!ranges) return;
|
|
640
|
+
state.addConditionalFormat(
|
|
641
|
+
(0, import_spreadsheet4.createConditionalFormatRule)({
|
|
642
|
+
kind,
|
|
643
|
+
ranges,
|
|
644
|
+
value,
|
|
645
|
+
value2,
|
|
646
|
+
text,
|
|
647
|
+
formula,
|
|
648
|
+
rank: Number(rank) || 10,
|
|
649
|
+
period,
|
|
650
|
+
iconSet,
|
|
651
|
+
color,
|
|
652
|
+
style: import_spreadsheet4.CONDITIONAL_FORMAT_STYLE_PRESETS.find((s) => s.id === styleId)?.style
|
|
653
|
+
})
|
|
654
|
+
);
|
|
655
|
+
};
|
|
656
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(PanelFrame, { title: "Conditional formatting", label: "Conditional formatting rules", onClose, children: [
|
|
657
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("ol", { className: "mb-3 flex flex-col gap-1", "aria-label": "Rules in priority order", children: [
|
|
658
|
+
rules.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { className: "text-muted-foreground", children: "No rules on this sheet." }),
|
|
659
|
+
rules.map((rule, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("li", { "data-rule-id": rule.id, className: "flex items-center gap-1.5 rounded border border-border px-1.5 py-1", children: [
|
|
660
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RuleSwatch, { rule }),
|
|
661
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "min-w-0 flex-1", children: [
|
|
662
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "truncate", title: (0, import_spreadsheet4.describeConditionalFormat)(rule), children: (0, import_spreadsheet4.describeConditionalFormat)(rule) }),
|
|
663
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "truncate font-mono text-[10px] text-muted-foreground", children: (0, import_spreadsheet4.formatRuleRanges)(rule.ranges) })
|
|
664
|
+
] }),
|
|
665
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex items-center gap-0.5 text-[10px]", title: "Stop if true", children: [
|
|
666
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
667
|
+
"input",
|
|
668
|
+
{
|
|
669
|
+
type: "checkbox",
|
|
670
|
+
"aria-label": "Stop if true",
|
|
671
|
+
checked: !!rule.stopIfTrue,
|
|
672
|
+
onChange: (e) => state.updateConditionalFormat(rule.id, { stopIfTrue: e.target.checked })
|
|
673
|
+
}
|
|
674
|
+
),
|
|
675
|
+
"Stop"
|
|
676
|
+
] }),
|
|
677
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", "aria-label": "Move up", className: buttonClass, disabled: i === 0, onClick: () => state.moveConditionalFormat(rule.id, { by: -1 }), children: "\u25B2" }),
|
|
678
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
679
|
+
"button",
|
|
680
|
+
{
|
|
681
|
+
type: "button",
|
|
682
|
+
"aria-label": "Move down",
|
|
683
|
+
className: buttonClass,
|
|
684
|
+
disabled: i === rules.length - 1,
|
|
685
|
+
onClick: () => state.moveConditionalFormat(rule.id, { by: 1 }),
|
|
686
|
+
children: "\u25BC"
|
|
687
|
+
}
|
|
688
|
+
),
|
|
689
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", "aria-label": "Delete rule", className: buttonClass, onClick: () => state.deleteConditionalFormat(rule.id), children: "\u2715" })
|
|
690
|
+
] }, rule.id))
|
|
691
|
+
] }),
|
|
692
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("fieldset", { className: "flex flex-col gap-1.5 border-t border-border pt-2", children: [
|
|
693
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("legend", { className: "sr-only", children: "New rule" }),
|
|
694
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex flex-col gap-0.5", children: [
|
|
695
|
+
"Format cells",
|
|
696
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("select", { "aria-label": "Rule type", className: inputClass, value: kind, onChange: (e) => setKind(e.target.value), children: import_spreadsheet4.CONDITIONAL_FORMAT_PRESETS.map((p) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: p.kind, children: p.label }, p.kind)) })
|
|
697
|
+
] }),
|
|
698
|
+
preset.fields.includes("value") && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { "aria-label": "Value", placeholder: "Value or =formula", className: inputClass, value, onChange: (e) => setValue(e.target.value) }),
|
|
699
|
+
preset.fields.includes("value2") && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { "aria-label": "Second value", placeholder: "and", className: inputClass, value: value2, onChange: (e) => setValue2(e.target.value) }),
|
|
700
|
+
preset.fields.includes("text") && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { "aria-label": "Text", placeholder: "Text", className: inputClass, value: text, onChange: (e) => setText(e.target.value) }),
|
|
701
|
+
preset.fields.includes("formula") && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { "aria-label": "Formula", placeholder: "=$A1>10", className: inputClass, value: formula, onChange: (e) => setFormula(e.target.value) }),
|
|
702
|
+
preset.fields.includes("rank") && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { "aria-label": "Rank", type: "number", min: 1, className: inputClass, value: rank, onChange: (e) => setRank(e.target.value) }),
|
|
703
|
+
preset.fields.includes("period") && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("select", { "aria-label": "Period", className: inputClass, value: period, onChange: (e) => setPeriod(e.target.value), children: import_spreadsheet4.CONDITIONAL_FORMAT_TIME_PERIODS.map((p) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: p.period, children: p.label }, p.period)) }),
|
|
704
|
+
preset.fields.includes("iconSet") && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("select", { "aria-label": "Icon set", className: inputClass, value: iconSet, onChange: (e) => setIconSet(e.target.value), children: import_spreadsheet4.CONDITIONAL_FORMAT_ICON_SETS.map((set) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: set, children: set }, set)) }),
|
|
705
|
+
preset.fields.includes("color") && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { "aria-label": "Bar colour", type: "color", className: "h-6 w-12", value: color, onChange: (e) => setColor(e.target.value) }),
|
|
706
|
+
preset.usesStyle && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("select", { "aria-label": "Style", className: inputClass, value: styleId, onChange: (e) => setStyleId(e.target.value), children: import_spreadsheet4.CONDITIONAL_FORMAT_STYLE_PRESETS.map((s) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: s.id, children: s.label }, s.id)) }),
|
|
707
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex flex-col gap-0.5", children: [
|
|
708
|
+
"Applies to",
|
|
709
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { "aria-label": "Applies to", className: `${inputClass} font-mono`, value: appliesTo, onChange: (e) => setAppliesTo(e.target.value) })
|
|
710
|
+
] }),
|
|
711
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex justify-between gap-2", children: [
|
|
712
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: buttonClass, onClick: () => setAppliesTo(selectionText(state.selectedRange)), children: "Use selection" }),
|
|
713
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: buttonClass, disabled: !ranges, onClick: add, children: "Add rule" })
|
|
714
|
+
] })
|
|
715
|
+
] })
|
|
716
|
+
] });
|
|
717
|
+
};
|
|
718
|
+
var SpreadsheetDataValidationPanel = ({ state, onClose }) => {
|
|
719
|
+
const rules = state.document.table?.dataValidations ?? [];
|
|
720
|
+
const [type, setType] = (0, import_react6.useState)("list");
|
|
721
|
+
const [operator, setOperator] = (0, import_react6.useState)("between");
|
|
722
|
+
const [formula1, setFormula1] = (0, import_react6.useState)("");
|
|
723
|
+
const [formula2, setFormula2] = (0, import_react6.useState)("");
|
|
724
|
+
const [errorStyle, setErrorStyle] = (0, import_react6.useState)("stop");
|
|
725
|
+
const [errorMessage, setErrorMessage] = (0, import_react6.useState)("");
|
|
726
|
+
const [inputMessage, setInputMessage] = (0, import_react6.useState)("");
|
|
727
|
+
const [ignoreBlank, setIgnoreBlank] = (0, import_react6.useState)(true);
|
|
728
|
+
const [appliesTo, setAppliesTo] = (0, import_react6.useState)(() => selectionText(state.selectedRange));
|
|
729
|
+
const ranges = (0, import_spreadsheet4.parseRuleRanges)(appliesTo);
|
|
730
|
+
const usesOperator = type === "whole" || type === "decimal" || type === "date" || type === "time" || type === "textLength";
|
|
731
|
+
const needsTwo = usesOperator && (operator === "between" || operator === "notBetween");
|
|
732
|
+
const circles = state.controller.getInvalidDataCircles();
|
|
733
|
+
const apply = () => {
|
|
734
|
+
if (!ranges) return;
|
|
735
|
+
state.addDataValidation({
|
|
736
|
+
ranges,
|
|
737
|
+
type,
|
|
738
|
+
...usesOperator ? { operator } : {},
|
|
739
|
+
...formula1 ? { formula1 } : {},
|
|
740
|
+
...needsTwo && formula2 ? { formula2 } : {},
|
|
741
|
+
errorStyle,
|
|
742
|
+
...errorMessage ? { errorMessage } : {},
|
|
743
|
+
...inputMessage ? { inputMessage } : {},
|
|
744
|
+
ignoreBlank
|
|
745
|
+
});
|
|
746
|
+
};
|
|
747
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(PanelFrame, { title: "Data validation", label: "Data validation rules", onClose, children: [
|
|
748
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("ul", { className: "mb-3 flex flex-col gap-1", "aria-label": "Validation rules", children: [
|
|
749
|
+
rules.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { className: "text-muted-foreground", children: "No validation on this sheet." }),
|
|
750
|
+
rules.map((rule) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("li", { "data-rule-id": rule.id, className: "flex items-center gap-1.5 rounded border border-border px-1.5 py-1", children: [
|
|
751
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "min-w-0 flex-1", children: [
|
|
752
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "truncate", title: (0, import_spreadsheet4.describeDataValidation)(rule), children: (0, import_spreadsheet4.describeDataValidation)(rule) }),
|
|
753
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "truncate font-mono text-[10px] text-muted-foreground", children: [
|
|
754
|
+
(0, import_spreadsheet4.formatRuleRanges)(rule.ranges),
|
|
755
|
+
" \xB7 ",
|
|
756
|
+
rule.errorStyle ?? "stop"
|
|
757
|
+
] })
|
|
758
|
+
] }),
|
|
759
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", "aria-label": "Delete rule", className: buttonClass, onClick: () => state.deleteDataValidation(rule.id), children: "\u2715" })
|
|
760
|
+
] }, rule.id))
|
|
761
|
+
] }),
|
|
762
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("fieldset", { className: "flex flex-col gap-1.5 border-t border-border pt-2", children: [
|
|
763
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("legend", { className: "sr-only", children: "New validation" }),
|
|
764
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex flex-col gap-0.5", children: [
|
|
765
|
+
"Allow",
|
|
766
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("select", { "aria-label": "Allow", className: inputClass, value: type, onChange: (e) => setType(e.target.value), children: import_spreadsheet4.DATA_VALIDATION_TYPE_OPTIONS.map((o) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: o.type, children: o.label }, o.type)) })
|
|
767
|
+
] }),
|
|
768
|
+
usesOperator && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("select", { "aria-label": "Operator", className: inputClass, value: operator, onChange: (e) => setOperator(e.target.value), children: import_spreadsheet4.DATA_VALIDATION_OPERATOR_OPTIONS.map((o) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: o.operator, children: o.label }, o.operator)) }),
|
|
769
|
+
type !== "any" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
770
|
+
"input",
|
|
771
|
+
{
|
|
772
|
+
"aria-label": type === "list" ? "Source" : type === "custom" ? "Formula" : needsTwo ? "Minimum" : "Value",
|
|
773
|
+
placeholder: type === "list" ? "Low,Medium,High or =$H$1:$H$5" : type === "custom" ? "=A1>0" : "Value or =formula",
|
|
774
|
+
className: inputClass,
|
|
775
|
+
value: formula1,
|
|
776
|
+
onChange: (e) => setFormula1(e.target.value)
|
|
777
|
+
}
|
|
778
|
+
),
|
|
779
|
+
needsTwo && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { "aria-label": "Maximum", placeholder: "Maximum", className: inputClass, value: formula2, onChange: (e) => setFormula2(e.target.value) }),
|
|
780
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex items-center gap-1", children: [
|
|
781
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "checkbox", checked: ignoreBlank, onChange: (e) => setIgnoreBlank(e.target.checked) }),
|
|
782
|
+
" Ignore blank"
|
|
783
|
+
] }),
|
|
784
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { "aria-label": "Input message", placeholder: "Input message (optional)", className: inputClass, value: inputMessage, onChange: (e) => setInputMessage(e.target.value) }),
|
|
785
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex gap-1", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("select", { "aria-label": "Error style", className: inputClass, value: errorStyle, onChange: (e) => setErrorStyle(e.target.value), children: import_spreadsheet4.DATA_VALIDATION_ERROR_STYLE_OPTIONS.map((o) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: o.style, children: o.label }, o.style)) }) }),
|
|
786
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { "aria-label": "Error message", placeholder: "Error message (optional)", className: inputClass, value: errorMessage, onChange: (e) => setErrorMessage(e.target.value) }),
|
|
787
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex flex-col gap-0.5", children: [
|
|
788
|
+
"Applies to",
|
|
789
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { "aria-label": "Applies to", className: `${inputClass} font-mono`, value: appliesTo, onChange: (e) => setAppliesTo(e.target.value) })
|
|
790
|
+
] }),
|
|
791
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-wrap justify-between gap-2", children: [
|
|
792
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: buttonClass, onClick: () => setAppliesTo(selectionText(state.selectedRange)), children: "Use selection" }),
|
|
793
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: buttonClass, disabled: !ranges, onClick: () => ranges && ranges.forEach((r) => state.clearDataValidation(r)), children: "Clear" }),
|
|
794
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: buttonClass, disabled: !ranges, onClick: apply, children: "Apply" })
|
|
795
|
+
] }),
|
|
796
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: buttonClass, "aria-pressed": circles, onClick: () => state.setInvalidDataCircles(!circles), children: circles ? "Clear validation circles" : "Circle invalid data" })
|
|
797
|
+
] })
|
|
798
|
+
] });
|
|
799
|
+
};
|
|
800
|
+
|
|
801
|
+
// src/spreadsheet/useSpreadsheetViewport.ts
|
|
802
|
+
var import_react7 = require("react");
|
|
803
|
+
var import_spreadsheet5 = require("@pixerate/editor/spreadsheet");
|
|
804
|
+
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? import_react7.useLayoutEffect : import_react7.useEffect;
|
|
805
|
+
function useSpreadsheetViewport(scrollRef, view, options = {}) {
|
|
806
|
+
const metrics = (0, import_react7.useMemo)(() => (0, import_spreadsheet5.measureSpreadsheetView)(view), [view]);
|
|
807
|
+
const virtual = (0, import_spreadsheet5.shouldVirtualizeSpreadsheet)(view, options.virtualize ?? "auto");
|
|
808
|
+
const [viewport, setViewport] = (0, import_react7.useState)({ scrollTop: 0, scrollLeft: 0, width: 0, height: 0 });
|
|
809
|
+
const windowOptions = (0, import_react7.useMemo)(
|
|
810
|
+
() => ({ merges: options.merges, overscanRows: options.overscanRows, overscanColumns: options.overscanColumns }),
|
|
811
|
+
[options.merges, options.overscanRows, options.overscanColumns]
|
|
812
|
+
);
|
|
813
|
+
const win = (0, import_react7.useMemo)(
|
|
814
|
+
() => virtual ? (0, import_spreadsheet5.getSpreadsheetViewWindow)(metrics, viewport, windowOptions) : (0, import_spreadsheet5.fullSpreadsheetViewWindow)(metrics),
|
|
815
|
+
[virtual, metrics, viewport, windowOptions]
|
|
816
|
+
);
|
|
817
|
+
const latest = (0, import_react7.useRef)({ metrics, viewport, virtual, windowOptions });
|
|
818
|
+
latest.current = { metrics, viewport, virtual, windowOptions };
|
|
819
|
+
const readViewport = (0, import_react7.useCallback)(() => {
|
|
820
|
+
const el = scrollRef.current;
|
|
821
|
+
if (!el) return null;
|
|
822
|
+
return { scrollTop: el.scrollTop, scrollLeft: el.scrollLeft, width: el.clientWidth, height: el.clientHeight };
|
|
823
|
+
}, [scrollRef]);
|
|
824
|
+
const updateViewport = (0, import_react7.useCallback)((next) => {
|
|
825
|
+
setViewport((prev) => {
|
|
826
|
+
const { metrics: m, virtual: v, windowOptions: o } = latest.current;
|
|
827
|
+
if (!v) return prev;
|
|
828
|
+
const same = prev.width === next.width && prev.height === next.height && (0, import_spreadsheet5.sameSpreadsheetViewWindow)((0, import_spreadsheet5.getSpreadsheetViewWindow)(m, prev, o), (0, import_spreadsheet5.getSpreadsheetViewWindow)(m, next, o));
|
|
829
|
+
return same ? prev : next;
|
|
830
|
+
});
|
|
831
|
+
}, []);
|
|
832
|
+
const onScroll = (0, import_react7.useCallback)(() => {
|
|
833
|
+
const next = readViewport();
|
|
834
|
+
if (next) updateViewport(next);
|
|
835
|
+
}, [readViewport, updateViewport]);
|
|
836
|
+
useIsomorphicLayoutEffect(() => {
|
|
837
|
+
if (!virtual) return;
|
|
838
|
+
onScroll();
|
|
839
|
+
const el = scrollRef.current;
|
|
840
|
+
if (!el || typeof ResizeObserver === "undefined") return;
|
|
841
|
+
const observer = new ResizeObserver(() => onScroll());
|
|
842
|
+
observer.observe(el);
|
|
843
|
+
return () => observer.disconnect();
|
|
844
|
+
}, [virtual, onScroll, scrollRef]);
|
|
845
|
+
const activeRow = options.activeCell?.row;
|
|
846
|
+
const activeCol = options.activeCell?.col;
|
|
847
|
+
useIsomorphicLayoutEffect(() => {
|
|
848
|
+
if (!virtual || activeRow === void 0 || activeCol === void 0) return;
|
|
849
|
+
const el = scrollRef.current;
|
|
850
|
+
const current = latest.current.viewport;
|
|
851
|
+
const measured = readViewport();
|
|
852
|
+
const base = measured ? { ...measured, scrollTop: Math.max(measured.scrollTop, 0), scrollLeft: Math.max(measured.scrollLeft, 0) } : current;
|
|
853
|
+
const target = (0, import_spreadsheet5.getSpreadsheetScrollToCell)(latest.current.metrics, base, { row: activeRow, col: activeCol });
|
|
854
|
+
if (!target) return;
|
|
855
|
+
const next = { ...base, ...target };
|
|
856
|
+
if (el) {
|
|
857
|
+
if (target.scrollTop !== void 0) el.scrollTop = target.scrollTop;
|
|
858
|
+
if (target.scrollLeft !== void 0) el.scrollLeft = target.scrollLeft;
|
|
859
|
+
}
|
|
860
|
+
updateViewport(next);
|
|
861
|
+
}, [virtual, activeRow, activeCol]);
|
|
862
|
+
return { metrics, window: win, onScroll };
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// src/spreadsheet/FormulaHints.tsx
|
|
866
|
+
var import_react8 = require("react");
|
|
867
|
+
var import_spreadsheet6 = require("@pixerate/editor/spreadsheet");
|
|
868
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
869
|
+
var FormulaHints = ({ inputRef, value, onAccept, limit = 8, names }) => {
|
|
870
|
+
const [caret, setCaret] = (0, import_react8.useState)(null);
|
|
871
|
+
const [focused, setFocused] = (0, import_react8.useState)(false);
|
|
872
|
+
const [selected, setSelected] = (0, import_react8.useState)(0);
|
|
873
|
+
const [dismissed, setDismissed] = (0, import_react8.useState)(false);
|
|
874
|
+
const [position, setPosition] = (0, import_react8.useState)(null);
|
|
875
|
+
const pendingCaret = (0, import_react8.useRef)(null);
|
|
876
|
+
const hints = (0, import_react8.useMemo)(
|
|
877
|
+
() => (0, import_spreadsheet6.getFormulaHints)(value, caret ?? value.length, { limit, names }),
|
|
878
|
+
[value, caret, limit, names]
|
|
423
879
|
);
|
|
424
880
|
const suggestions = dismissed ? [] : hints.suggestions;
|
|
425
881
|
const selectedIndex = Math.max(0, Math.min(selected, suggestions.length - 1));
|
|
426
882
|
const visible = focused && value.trimStart().startsWith("=") && (suggestions.length > 0 || !!hints.activeCall);
|
|
427
|
-
const latest = (0,
|
|
883
|
+
const latest = (0, import_react8.useRef)({ suggestions, selected: selectedIndex, value, caret, onAccept });
|
|
428
884
|
latest.current = { suggestions, selected: selectedIndex, value, caret, onAccept };
|
|
429
885
|
const accept = (index) => {
|
|
430
886
|
const { suggestions: list, value: current, caret: at, onAccept: commit } = latest.current;
|
|
431
887
|
const suggestion = list[index];
|
|
432
888
|
if (!suggestion) return;
|
|
433
|
-
const next = (0,
|
|
889
|
+
const next = (0, import_spreadsheet6.applyFormulaSuggestion)(current, at ?? current.length, suggestion);
|
|
434
890
|
pendingCaret.current = next.caret;
|
|
435
891
|
commit(next.value);
|
|
436
892
|
};
|
|
437
|
-
(0,
|
|
893
|
+
(0, import_react8.useEffect)(() => {
|
|
438
894
|
const input = inputRef.current;
|
|
439
895
|
if (!input) return;
|
|
440
896
|
const syncCaret = () => setCaret(input.selectionStart ?? input.value.length);
|
|
@@ -484,7 +940,7 @@ var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
|
|
|
484
940
|
input.removeEventListener("keydown", onKeyDown);
|
|
485
941
|
};
|
|
486
942
|
}, [inputRef]);
|
|
487
|
-
(0,
|
|
943
|
+
(0, import_react8.useLayoutEffect)(() => {
|
|
488
944
|
const input = inputRef.current;
|
|
489
945
|
const at = pendingCaret.current;
|
|
490
946
|
if (!input || at === null || input.value !== value) return;
|
|
@@ -493,7 +949,7 @@ var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
|
|
|
493
949
|
setCaret(at);
|
|
494
950
|
setSelected(0);
|
|
495
951
|
}, [value, inputRef]);
|
|
496
|
-
(0,
|
|
952
|
+
(0, import_react8.useLayoutEffect)(() => {
|
|
497
953
|
const input = inputRef.current;
|
|
498
954
|
if (!visible || !input) return;
|
|
499
955
|
const rect = input.getBoundingClientRect();
|
|
@@ -501,7 +957,7 @@ var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
|
|
|
501
957
|
}, [visible, value, inputRef]);
|
|
502
958
|
if (!visible) return null;
|
|
503
959
|
const { activeCall } = hints;
|
|
504
|
-
return /* @__PURE__ */ (0,
|
|
960
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
505
961
|
"div",
|
|
506
962
|
{
|
|
507
963
|
"data-formula-hints": "",
|
|
@@ -517,8 +973,8 @@ var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
|
|
|
517
973
|
},
|
|
518
974
|
onMouseDown: (e) => e.preventDefault(),
|
|
519
975
|
children: [
|
|
520
|
-
activeCall && /* @__PURE__ */ (0,
|
|
521
|
-
/* @__PURE__ */ (0,
|
|
976
|
+
activeCall && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { "data-formula-signature": "", className: "px-2 py-1.5 border-b border-border bg-muted/30", children: [
|
|
977
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "font-mono", children: activeCall.parts.map((part, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
522
978
|
"span",
|
|
523
979
|
{
|
|
524
980
|
"data-active": part.active ? "" : void 0,
|
|
@@ -527,13 +983,13 @@ var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
|
|
|
527
983
|
},
|
|
528
984
|
i
|
|
529
985
|
)) }),
|
|
530
|
-
/* @__PURE__ */ (0,
|
|
531
|
-
/* @__PURE__ */ (0,
|
|
986
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mt-0.5 text-muted-foreground", children: activeCall.argument ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
987
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "font-semibold", children: activeCall.argument.name }),
|
|
532
988
|
": ",
|
|
533
989
|
activeCall.argument.description
|
|
534
990
|
] }) : activeCall.description })
|
|
535
991
|
] }),
|
|
536
|
-
suggestions.length > 0 && /* @__PURE__ */ (0,
|
|
992
|
+
suggestions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("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__ */ (0, import_jsx_runtime5.jsxs)(
|
|
537
993
|
"li",
|
|
538
994
|
{
|
|
539
995
|
role: "option",
|
|
@@ -545,19 +1001,162 @@ var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
|
|
|
545
1001
|
accept(i);
|
|
546
1002
|
},
|
|
547
1003
|
children: [
|
|
548
|
-
/* @__PURE__ */ (0,
|
|
549
|
-
/* @__PURE__ */ (0,
|
|
1004
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "font-mono font-semibold text-foreground", children: s.signature }),
|
|
1005
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "text-muted-foreground truncate", children: s.description })
|
|
550
1006
|
]
|
|
551
1007
|
},
|
|
552
|
-
s.name
|
|
1008
|
+
`${s.kind ?? "fn"}:${s.name}`
|
|
553
1009
|
)) })
|
|
554
1010
|
]
|
|
555
1011
|
}
|
|
556
1012
|
);
|
|
557
1013
|
};
|
|
558
1014
|
|
|
1015
|
+
// src/spreadsheet/SheetTabs.tsx
|
|
1016
|
+
var import_react9 = require("react");
|
|
1017
|
+
var import_spreadsheet7 = require("@pixerate/editor/spreadsheet");
|
|
1018
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1019
|
+
var SheetTabs = ({ state, readOnly = false, className = "" }) => {
|
|
1020
|
+
const { controller, sheets } = state;
|
|
1021
|
+
const [renaming, setRenaming] = (0, import_react9.useState)(null);
|
|
1022
|
+
const [error, setError] = (0, import_react9.useState)(null);
|
|
1023
|
+
const [menu, setMenu] = (0, import_react9.useState)(null);
|
|
1024
|
+
const dragFrom = (0, import_react9.useRef)(null);
|
|
1025
|
+
const [dropTarget, setDropTarget] = (0, import_react9.useState)(null);
|
|
1026
|
+
const commitRename = (cancelOnError) => {
|
|
1027
|
+
if (!renaming) return;
|
|
1028
|
+
const sheet = sheets.find((s) => s.id === renaming.id);
|
|
1029
|
+
const name = renaming.draft.trim();
|
|
1030
|
+
if (!sheet || !name || name === sheet.name) {
|
|
1031
|
+
setRenaming(null);
|
|
1032
|
+
setError(null);
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
try {
|
|
1036
|
+
state.renameSheet(renaming.id, name);
|
|
1037
|
+
setRenaming(null);
|
|
1038
|
+
setError(null);
|
|
1039
|
+
} catch (err) {
|
|
1040
|
+
setError(err?.message ?? String(err));
|
|
1041
|
+
if (cancelOnError) setRenaming(null);
|
|
1042
|
+
}
|
|
1043
|
+
};
|
|
1044
|
+
const dropAfter = (e) => {
|
|
1045
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
1046
|
+
return e.clientX > rect.left + rect.width / 2;
|
|
1047
|
+
};
|
|
1048
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: `flex flex-col ${className}`, "data-sheet-tabs": "", children: [
|
|
1049
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1050
|
+
"div",
|
|
1051
|
+
{
|
|
1052
|
+
role: "tablist",
|
|
1053
|
+
"aria-label": "Sheets",
|
|
1054
|
+
className: "flex items-center gap-0.5 overflow-x-auto border-t border-border bg-muted px-1 py-1 text-xs",
|
|
1055
|
+
children: [
|
|
1056
|
+
sheets.map(
|
|
1057
|
+
(sheet, index) => renaming?.id === sheet.id ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1058
|
+
"input",
|
|
1059
|
+
{
|
|
1060
|
+
"aria-label": "Sheet name",
|
|
1061
|
+
autoFocus: true,
|
|
1062
|
+
className: "w-32 rounded border border-primary bg-background px-2 py-0.5 text-xs outline-none",
|
|
1063
|
+
value: renaming.draft,
|
|
1064
|
+
onChange: (e) => setRenaming({ id: sheet.id, draft: e.target.value }),
|
|
1065
|
+
onFocus: (e) => e.currentTarget.select(),
|
|
1066
|
+
onKeyDown: (e) => {
|
|
1067
|
+
e.stopPropagation();
|
|
1068
|
+
if (e.key === "Enter") commitRename(false);
|
|
1069
|
+
else if (e.key === "Escape") {
|
|
1070
|
+
setRenaming(null);
|
|
1071
|
+
setError(null);
|
|
1072
|
+
}
|
|
1073
|
+
},
|
|
1074
|
+
onBlur: () => commitRename(true)
|
|
1075
|
+
},
|
|
1076
|
+
sheet.id
|
|
1077
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1078
|
+
"button",
|
|
1079
|
+
{
|
|
1080
|
+
type: "button",
|
|
1081
|
+
role: "tab",
|
|
1082
|
+
"aria-selected": sheet.active,
|
|
1083
|
+
"data-sheet-id": sheet.id,
|
|
1084
|
+
draggable: !readOnly,
|
|
1085
|
+
title: readOnly ? sheet.name : `${sheet.name} \u2014 double-click to rename, drag to move`,
|
|
1086
|
+
className: `whitespace-nowrap rounded px-3 py-0.5 ${sheet.active ? "bg-background font-semibold text-foreground shadow-sm" : "text-muted-foreground hover:bg-background/60"} ${dropTarget?.index === index ? dropTarget.after ? "border-r-2 border-primary" : "border-l-2 border-primary" : ""}`,
|
|
1087
|
+
onClick: () => state.setActiveSheet(sheet.id),
|
|
1088
|
+
onDoubleClick: () => {
|
|
1089
|
+
if (readOnly) return;
|
|
1090
|
+
setError(null);
|
|
1091
|
+
setRenaming({ id: sheet.id, draft: sheet.name });
|
|
1092
|
+
},
|
|
1093
|
+
onContextMenu: (e) => {
|
|
1094
|
+
if (readOnly) return;
|
|
1095
|
+
e.preventDefault();
|
|
1096
|
+
setMenu({ x: e.clientX, y: e.clientY, items: (0, import_spreadsheet7.getSheetTabMenuItems)(controller, sheet.id, { onError: setError }) });
|
|
1097
|
+
},
|
|
1098
|
+
onDragStart: (e) => {
|
|
1099
|
+
dragFrom.current = index;
|
|
1100
|
+
e.dataTransfer?.setData("text/plain", sheet.id);
|
|
1101
|
+
if (e.dataTransfer) e.dataTransfer.effectAllowed = "move";
|
|
1102
|
+
},
|
|
1103
|
+
onDragOver: (e) => {
|
|
1104
|
+
if (dragFrom.current === null) return;
|
|
1105
|
+
e.preventDefault();
|
|
1106
|
+
const after = dropAfter(e);
|
|
1107
|
+
setDropTarget((prev) => prev?.index === index && prev.after === after ? prev : { index, after });
|
|
1108
|
+
},
|
|
1109
|
+
onDragLeave: () => setDropTarget(null),
|
|
1110
|
+
onDrop: (e) => {
|
|
1111
|
+
e.preventDefault();
|
|
1112
|
+
const from = dragFrom.current;
|
|
1113
|
+
dragFrom.current = null;
|
|
1114
|
+
setDropTarget(null);
|
|
1115
|
+
if (from === null) return;
|
|
1116
|
+
state.moveSheet(sheets[from].id, (0, import_spreadsheet7.sheetDropIndex)(from, index, dropAfter(e)));
|
|
1117
|
+
},
|
|
1118
|
+
onDragEnd: () => {
|
|
1119
|
+
dragFrom.current = null;
|
|
1120
|
+
setDropTarget(null);
|
|
1121
|
+
},
|
|
1122
|
+
children: sheet.name
|
|
1123
|
+
},
|
|
1124
|
+
sheet.id
|
|
1125
|
+
)
|
|
1126
|
+
),
|
|
1127
|
+
!readOnly && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1128
|
+
"button",
|
|
1129
|
+
{
|
|
1130
|
+
type: "button",
|
|
1131
|
+
"aria-label": "Add sheet",
|
|
1132
|
+
title: "Add sheet",
|
|
1133
|
+
className: "rounded px-2 py-0.5 text-muted-foreground hover:bg-background/60",
|
|
1134
|
+
onClick: () => {
|
|
1135
|
+
setError(null);
|
|
1136
|
+
state.addSheet();
|
|
1137
|
+
},
|
|
1138
|
+
children: "+"
|
|
1139
|
+
}
|
|
1140
|
+
)
|
|
1141
|
+
]
|
|
1142
|
+
}
|
|
1143
|
+
),
|
|
1144
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { role: "alert", className: "px-2 py-1 text-xs text-destructive", children: error }),
|
|
1145
|
+
menu && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1146
|
+
SpreadsheetContextMenu,
|
|
1147
|
+
{
|
|
1148
|
+
items: menu.items,
|
|
1149
|
+
x: menu.x,
|
|
1150
|
+
y: menu.y,
|
|
1151
|
+
ariaLabel: "Sheet actions",
|
|
1152
|
+
onClose: () => setMenu(null)
|
|
1153
|
+
}
|
|
1154
|
+
)
|
|
1155
|
+
] });
|
|
1156
|
+
};
|
|
1157
|
+
|
|
559
1158
|
// src/spreadsheet/SpreadsheetEditor.tsx
|
|
560
|
-
var
|
|
1159
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
561
1160
|
var COLUMN_HEADER_HEIGHT = 28;
|
|
562
1161
|
var ROW_HEADER_WIDTH = 48;
|
|
563
1162
|
function frozenCellStyle(viewRow, viewCol) {
|
|
@@ -570,7 +1169,7 @@ function frozenCellStyle(viewRow, viewCol) {
|
|
|
570
1169
|
background: "var(--background, #fff)"
|
|
571
1170
|
};
|
|
572
1171
|
}
|
|
573
|
-
var coordKey = (cell) => cell ? `${(0,
|
|
1172
|
+
var coordKey = (cell) => cell ? `${(0, import_spreadsheet8.indexToColName)(cell.col)}${cell.row + 1}` : "";
|
|
574
1173
|
function spillBorderStyle(range, row, col) {
|
|
575
1174
|
const line = "1px solid color-mix(in srgb, var(--primary, #6366f1) 60%, transparent)";
|
|
576
1175
|
return {
|
|
@@ -584,18 +1183,34 @@ function spillBorderStyle(range, row, col) {
|
|
|
584
1183
|
borderRight: col === range.endCol ? line : void 0
|
|
585
1184
|
};
|
|
586
1185
|
}
|
|
587
|
-
function CellText({ cell }) {
|
|
588
|
-
const shown = (0,
|
|
589
|
-
|
|
1186
|
+
function CellText({ cell, conditional }) {
|
|
1187
|
+
const shown = (0, import_spreadsheet8.formatCellForDisplay)(cell);
|
|
1188
|
+
const color = conditional?.color ?? (shown.color ? (0, import_spreadsheet8.numberFormatColorToCss)(shown.color) : void 0);
|
|
1189
|
+
const decorated = !!conditional?.dataBar || !!conditional?.icon;
|
|
1190
|
+
const text = /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
590
1191
|
"span",
|
|
591
1192
|
{
|
|
592
1193
|
className: `truncate block max-w-full ${cell.error ? "text-destructive font-semibold" : ""}`,
|
|
593
1194
|
title: cell.error ? cell.errorMessage ?? cell.error : void 0,
|
|
594
1195
|
"data-numeric": shown.numeric ? "" : void 0,
|
|
595
|
-
style: shown.numeric ||
|
|
596
|
-
|
|
1196
|
+
style: shown.numeric || color || decorated ? {
|
|
1197
|
+
...decorated ? { position: "relative" } : {},
|
|
1198
|
+
...conditional?.icon ? { flex: 1, minWidth: 0 } : {},
|
|
1199
|
+
...shown.numeric ? { textAlign: "right" } : {},
|
|
1200
|
+
...color ? { color } : {}
|
|
1201
|
+
} : void 0,
|
|
1202
|
+
children: conditional?.hideValue ? "" : shown.text
|
|
597
1203
|
}
|
|
598
1204
|
);
|
|
1205
|
+
if (!conditional || !decorated) return text;
|
|
1206
|
+
const icon = conditional.icon ? (0, import_spreadsheet8.getConditionalFormatIcon)(conditional.icon.set, conditional.icon.index) : null;
|
|
1207
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
1208
|
+
conditional.dataBar && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { "aria-hidden": "true", className: "spreadsheet-data-bar", style: (0, import_spreadsheet8.getDataBarCss)(conditional.dataBar) }),
|
|
1209
|
+
icon ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "flex items-center gap-1 min-w-0", style: { position: "relative" }, children: [
|
|
1210
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "spreadsheet-cf-icon shrink-0", role: "img", "aria-label": icon.label, "data-icon": conditional.icon.id, style: { color: icon.color }, children: icon.glyph }),
|
|
1211
|
+
text
|
|
1212
|
+
] }) : text
|
|
1213
|
+
] });
|
|
599
1214
|
}
|
|
600
1215
|
var SpreadsheetEditor = ({
|
|
601
1216
|
state: externalState,
|
|
@@ -603,57 +1218,95 @@ var SpreadsheetEditor = ({
|
|
|
603
1218
|
customCellRenderer,
|
|
604
1219
|
onAddColumnClick,
|
|
605
1220
|
onColumnHeaderClick,
|
|
606
|
-
onColumnResize
|
|
1221
|
+
onColumnResize,
|
|
1222
|
+
showSheetTabs = false,
|
|
1223
|
+
virtualize = "auto"
|
|
607
1224
|
}) => {
|
|
608
1225
|
const defaultState = useSpreadsheetEditor();
|
|
609
1226
|
const state = externalState || defaultState;
|
|
610
|
-
const gridRef = (0,
|
|
611
|
-
const
|
|
612
|
-
const
|
|
613
|
-
const
|
|
614
|
-
const [
|
|
615
|
-
const [
|
|
616
|
-
const [
|
|
1227
|
+
const gridRef = (0, import_react10.useRef)(null);
|
|
1228
|
+
const scrollRef = (0, import_react10.useRef)(null);
|
|
1229
|
+
const formulaBarRef = (0, import_react10.useRef)(null);
|
|
1230
|
+
const cellInputRef = (0, import_react10.useRef)(null);
|
|
1231
|
+
const [resizingColId, setResizingColId] = (0, import_react10.useState)(null);
|
|
1232
|
+
const [hoveredColId, setHoveredColId] = (0, import_react10.useState)(null);
|
|
1233
|
+
const [resizeStartX, setResizeStartX] = (0, import_react10.useState)(0);
|
|
1234
|
+
const [resizeStartWidth, setResizeStartWidth] = (0, import_react10.useState)(0);
|
|
617
1235
|
const view = state.viewModel;
|
|
1236
|
+
const { window: viewWindow, onScroll: handleGridScroll } = useSpreadsheetViewport(scrollRef, view, {
|
|
1237
|
+
virtualize,
|
|
1238
|
+
merges: state.document.table?.merges,
|
|
1239
|
+
activeCell: state.activeCell
|
|
1240
|
+
});
|
|
1241
|
+
const columnSlots = (0, import_react10.useMemo)(() => {
|
|
1242
|
+
const col = (viewCol) => ({ kind: "col", viewCol });
|
|
1243
|
+
if (!viewWindow.virtual) return view.columns.map(col);
|
|
1244
|
+
const slots = view.columns.slice(0, viewWindow.frozenColumns).map(col);
|
|
1245
|
+
if (viewWindow.leftSpace > 0) slots.push({ kind: "space", key: "space:left", width: viewWindow.leftSpace });
|
|
1246
|
+
for (let i = viewWindow.colStart; i < viewWindow.colEnd; i++) slots.push(col(view.columns[i]));
|
|
1247
|
+
if (viewWindow.rightSpace > 0) slots.push({ kind: "space", key: "space:right", width: viewWindow.rightSpace });
|
|
1248
|
+
return slots;
|
|
1249
|
+
}, [view, viewWindow]);
|
|
1250
|
+
const renderedColumns = (0, import_react10.useMemo)(
|
|
1251
|
+
() => columnSlots.flatMap((slot) => slot.kind === "col" ? [slot.viewCol] : []),
|
|
1252
|
+
[columnSlots]
|
|
1253
|
+
);
|
|
1254
|
+
const rowSlots = (0, import_react10.useMemo)(() => {
|
|
1255
|
+
const row = (viewRow) => ({ kind: "row", viewRow });
|
|
1256
|
+
if (!viewWindow.virtual) return view.rows.map(row);
|
|
1257
|
+
const slots = view.rows.slice(0, viewWindow.frozenRows).map(row);
|
|
1258
|
+
if (viewWindow.topSpace > 0) slots.push({ kind: "space", key: "space:top", height: viewWindow.topSpace });
|
|
1259
|
+
for (let i = viewWindow.rowStart; i < viewWindow.rowEnd; i++) slots.push(row(view.rows[i]));
|
|
1260
|
+
if (viewWindow.bottomSpace > 0) slots.push({ kind: "space", key: "space:bottom", height: viewWindow.bottomSpace });
|
|
1261
|
+
return slots;
|
|
1262
|
+
}, [view, viewWindow]);
|
|
618
1263
|
const { copyShortcutFallback } = useSpreadsheetClipboard(gridRef, state, readOnly);
|
|
619
1264
|
const { fillTarget, startFill, extendFill } = useFillHandle(state);
|
|
620
|
-
const [menu, setMenu] = (0,
|
|
1265
|
+
const [menu, setMenu] = (0, import_react10.useState)(null);
|
|
1266
|
+
const [panel, setPanel] = (0, import_react10.useState)(null);
|
|
1267
|
+
const [listMenu, setListMenu] = (0, import_react10.useState)(null);
|
|
1268
|
+
const overlay = state.conditionalFormatOverlay;
|
|
621
1269
|
const filter = state.document.table?.filter;
|
|
622
|
-
const filteredColIds = (0,
|
|
1270
|
+
const filteredColIds = (0, import_react10.useMemo)(
|
|
623
1271
|
() => new Set(filter?.enabled ? filter.rules.map((r) => r.colId) : []),
|
|
624
1272
|
[filter]
|
|
625
1273
|
);
|
|
626
|
-
const handleCellContextMenu = (0,
|
|
1274
|
+
const handleCellContextMenu = (0, import_react10.useCallback)((rIdx, cIdx, e) => {
|
|
627
1275
|
if (readOnly) return;
|
|
628
1276
|
e.preventDefault();
|
|
629
1277
|
if (state.editingCell) state.commitEditing();
|
|
630
|
-
if (!state.selectedRange || !(0,
|
|
1278
|
+
if (!state.selectedRange || !(0, import_spreadsheet8.rangeContains)(state.selectedRange, rIdx, cIdx)) {
|
|
631
1279
|
state.selectCell(rIdx, cIdx);
|
|
632
1280
|
}
|
|
633
|
-
setMenu({ x: e.clientX, y: e.clientY, items: (0,
|
|
1281
|
+
setMenu({ x: e.clientX, y: e.clientY, items: (0, import_spreadsheet8.getSpreadsheetMenuItems)(state.controller, { row: rIdx, col: cIdx }) });
|
|
634
1282
|
}, [readOnly, state]);
|
|
635
|
-
const
|
|
1283
|
+
const nameSuggestions = (0, import_react10.useMemo)(
|
|
1284
|
+
() => state.controller.getNameSuggestions(),
|
|
1285
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1286
|
+
[state.controller, state.document]
|
|
1287
|
+
);
|
|
1288
|
+
const activeRawValue = (0, import_react10.useMemo)(() => {
|
|
636
1289
|
if (!state.activeCell) return "";
|
|
637
1290
|
const row = state.document.rows[state.activeCell.row];
|
|
638
1291
|
const col = state.document.columns[state.activeCell.col];
|
|
639
1292
|
if (!row || !col) return "";
|
|
640
1293
|
return state.getCell(row.id, col.id).raw;
|
|
641
1294
|
}, [state.activeCell, state.document]);
|
|
642
|
-
const activeSpill = (0,
|
|
1295
|
+
const activeSpill = (0, import_react10.useMemo)(
|
|
643
1296
|
() => state.activeCell ? state.controller.getSpillAt(state.activeCell.row, state.activeCell.col) : null,
|
|
644
1297
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
645
1298
|
[state.controller, state.activeCell, state.document]
|
|
646
1299
|
);
|
|
647
1300
|
const spillFormula = activeSpill && !activeRawValue && activeSpill.key !== coordKey(state.activeCell) ? activeSpill.formula : "";
|
|
648
|
-
const coordLabel = (0,
|
|
1301
|
+
const coordLabel = (0, import_react10.useMemo)(() => {
|
|
649
1302
|
if (!state.activeCell) return "--";
|
|
650
|
-
const colName = (0,
|
|
1303
|
+
const colName = (0, import_spreadsheet8.indexToColName)(state.activeCell.col);
|
|
651
1304
|
return `${colName}${state.activeCell.row + 1}`;
|
|
652
1305
|
}, [state.activeCell]);
|
|
653
|
-
const [formulaDraft, setFormulaDraft] = (0,
|
|
1306
|
+
const [formulaDraft, setFormulaDraft] = (0, import_react10.useState)(null);
|
|
654
1307
|
const activeColumn = state.activeCell ? state.document.columns[state.activeCell.col] : void 0;
|
|
655
1308
|
const formulaBarReadOnly = readOnly || !state.activeCell || !!activeColumn?.readOnly;
|
|
656
|
-
(0,
|
|
1309
|
+
(0, import_react10.useEffect)(() => {
|
|
657
1310
|
setFormulaDraft(null);
|
|
658
1311
|
}, [state.activeCell?.row, state.activeCell?.col]);
|
|
659
1312
|
const commitFormulaBar = () => {
|
|
@@ -665,34 +1318,34 @@ var SpreadsheetEditor = ({
|
|
|
665
1318
|
const row = state.document.rows[state.activeCell.row];
|
|
666
1319
|
const col = state.document.columns[state.activeCell.col];
|
|
667
1320
|
if (row && col && !formulaBarReadOnly && formulaDraft !== activeRawValue) {
|
|
668
|
-
state.
|
|
1321
|
+
state.commitCellInput(row.id, col.id, formulaDraft);
|
|
669
1322
|
}
|
|
670
1323
|
setFormulaDraft(null);
|
|
671
1324
|
};
|
|
672
|
-
const handleCellClick = (0,
|
|
1325
|
+
const handleCellClick = (0, import_react10.useCallback)((rIdx, cIdx, e) => {
|
|
673
1326
|
if (state.editingCell && (state.editingCell.row !== rIdx || state.editingCell.col !== cIdx)) {
|
|
674
1327
|
state.commitEditing();
|
|
675
1328
|
}
|
|
676
1329
|
state.selectCell(rIdx, cIdx, e.shiftKey);
|
|
677
1330
|
}, [state]);
|
|
678
|
-
const handleCellDblClick = (0,
|
|
1331
|
+
const handleCellDblClick = (0, import_react10.useCallback)((rIdx, cIdx) => {
|
|
679
1332
|
if (readOnly) return;
|
|
680
1333
|
const col = state.document.columns[cIdx];
|
|
681
1334
|
if (col?.readOnly) return;
|
|
682
1335
|
state.startEditing(rIdx, cIdx);
|
|
683
1336
|
}, [readOnly, state]);
|
|
684
|
-
const handleKeyDown = (0,
|
|
1337
|
+
const handleKeyDown = (0, import_react10.useCallback)((e) => {
|
|
685
1338
|
if (state.editingCell) {
|
|
686
1339
|
if (e.key === "Enter") {
|
|
687
1340
|
e.preventDefault();
|
|
688
|
-
state.commitEditing();
|
|
1341
|
+
if (state.commitEditing().status !== "committed") return;
|
|
689
1342
|
state.moveCursor("down");
|
|
690
1343
|
} else if (e.key === "Escape") {
|
|
691
1344
|
e.preventDefault();
|
|
692
1345
|
state.cancelEditing();
|
|
693
1346
|
} else if (e.key === "Tab") {
|
|
694
1347
|
e.preventDefault();
|
|
695
|
-
state.commitEditing();
|
|
1348
|
+
if (state.commitEditing().status !== "committed") return;
|
|
696
1349
|
state.moveCursor(e.shiftKey ? "left" : "right");
|
|
697
1350
|
} else {
|
|
698
1351
|
return;
|
|
@@ -701,7 +1354,10 @@ var SpreadsheetEditor = ({
|
|
|
701
1354
|
return;
|
|
702
1355
|
}
|
|
703
1356
|
if (!state.activeCell) return;
|
|
704
|
-
if (e.key === "
|
|
1357
|
+
if (e.altKey && e.key === "ArrowDown") {
|
|
1358
|
+
e.preventDefault();
|
|
1359
|
+
openListMenu(state.activeCell.row, state.activeCell.col);
|
|
1360
|
+
} else if (e.key === "ArrowUp") {
|
|
705
1361
|
e.preventDefault();
|
|
706
1362
|
state.moveCursor("up", e.shiftKey);
|
|
707
1363
|
} else if (e.key === "ArrowDown") {
|
|
@@ -750,11 +1406,53 @@ var SpreadsheetEditor = ({
|
|
|
750
1406
|
}
|
|
751
1407
|
}
|
|
752
1408
|
}, [readOnly, state, copyShortcutFallback]);
|
|
753
|
-
const
|
|
1409
|
+
const activeListValues = (0, import_react10.useMemo)(
|
|
1410
|
+
() => state.activeCell && !readOnly ? state.controller.getValidationListValues(state.activeCell.row, state.activeCell.col) : null,
|
|
1411
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1412
|
+
[state.controller, state.activeCell, state.document, readOnly]
|
|
1413
|
+
);
|
|
1414
|
+
const activeRule = (0, import_react10.useMemo)(
|
|
1415
|
+
() => state.activeCell ? state.controller.getDataValidationAt(state.activeCell.row, state.activeCell.col) : void 0,
|
|
1416
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1417
|
+
[state.controller, state.activeCell, state.document]
|
|
1418
|
+
);
|
|
1419
|
+
const inputMessage = activeRule && activeRule.showInputMessage !== false && (activeRule.inputTitle || activeRule.inputMessage) ? activeRule : null;
|
|
1420
|
+
function openListMenu(row, col, anchor) {
|
|
1421
|
+
if (readOnly || state.document.columns[col]?.readOnly) return;
|
|
1422
|
+
const values = state.controller.getValidationListValues(row, col);
|
|
1423
|
+
if (!values) return;
|
|
1424
|
+
const td = anchor ?? gridRef.current?.querySelector(`td[data-row="${row}"][data-col="${col}"]`);
|
|
1425
|
+
const rect = td?.getBoundingClientRect();
|
|
1426
|
+
setListMenu({ x: rect ? rect.left : 0, y: rect ? rect.bottom : 0, row, col, values });
|
|
1427
|
+
}
|
|
1428
|
+
(0, import_react10.useEffect)(() => {
|
|
1429
|
+
if (!listMenu) return;
|
|
1430
|
+
const close = (e) => {
|
|
1431
|
+
if (!e.target?.closest?.('[aria-label="Allowed values"], .spreadsheet-list-arrow')) setListMenu(null);
|
|
1432
|
+
};
|
|
1433
|
+
window.addEventListener("mousedown", close);
|
|
1434
|
+
return () => window.removeEventListener("mousedown", close);
|
|
1435
|
+
}, [listMenu]);
|
|
1436
|
+
const chooseListValue = (value) => {
|
|
1437
|
+
if (!listMenu) return;
|
|
1438
|
+
const row = state.document.rows[listMenu.row];
|
|
1439
|
+
const col = state.document.columns[listMenu.col];
|
|
1440
|
+
setListMenu(null);
|
|
1441
|
+
if (row && col) state.commitCellInput(row.id, col.id, value);
|
|
1442
|
+
gridRef.current?.focus();
|
|
1443
|
+
};
|
|
1444
|
+
const resolveValidation = (action) => {
|
|
1445
|
+
state.resolveValidationPrompt(action);
|
|
1446
|
+
requestAnimationFrame(() => {
|
|
1447
|
+
if (cellInputRef.current) cellInputRef.current.focus();
|
|
1448
|
+
else gridRef.current?.focus();
|
|
1449
|
+
});
|
|
1450
|
+
};
|
|
1451
|
+
const stateRef = (0, import_react10.useRef)(state);
|
|
754
1452
|
stateRef.current = state;
|
|
755
|
-
const onColumnResizeRef = (0,
|
|
1453
|
+
const onColumnResizeRef = (0, import_react10.useRef)(onColumnResize);
|
|
756
1454
|
onColumnResizeRef.current = onColumnResize;
|
|
757
|
-
(0,
|
|
1455
|
+
(0, import_react10.useEffect)(() => {
|
|
758
1456
|
if (!resizingColId) return;
|
|
759
1457
|
if (typeof document !== "undefined") {
|
|
760
1458
|
document.body.style.cursor = "col-resize";
|
|
@@ -805,7 +1503,7 @@ var SpreadsheetEditor = ({
|
|
|
805
1503
|
window.removeEventListener("mouseup", handleMouseUp);
|
|
806
1504
|
};
|
|
807
1505
|
}, [resizingColId, resizeStartX, resizeStartWidth]);
|
|
808
|
-
const measureElementContentWidth = (0,
|
|
1506
|
+
const measureElementContentWidth = (0, import_react10.useCallback)((el, container) => {
|
|
809
1507
|
if (typeof document === "undefined") return 0;
|
|
810
1508
|
let maxW = el.scrollWidth || 0;
|
|
811
1509
|
const firstChild = el.firstElementChild;
|
|
@@ -853,7 +1551,7 @@ var SpreadsheetEditor = ({
|
|
|
853
1551
|
}
|
|
854
1552
|
return maxW;
|
|
855
1553
|
}, []);
|
|
856
|
-
const autoFitColumn = (0,
|
|
1554
|
+
const autoFitColumn = (0, import_react10.useCallback)((colId, colIndex) => {
|
|
857
1555
|
const col = state.document.columns.find((c) => c.id === colId);
|
|
858
1556
|
if (!col) return;
|
|
859
1557
|
let calculatedWidth = 0;
|
|
@@ -889,12 +1587,12 @@ var SpreadsheetEditor = ({
|
|
|
889
1587
|
onColumnResize(updatedCol.id, updatedCol.width || 130);
|
|
890
1588
|
}
|
|
891
1589
|
}, [state, measureElementContentWidth, onColumnResize]);
|
|
892
|
-
const handleResizeDblClick = (0,
|
|
1590
|
+
const handleResizeDblClick = (0, import_react10.useCallback)((colId, colIndex, e) => {
|
|
893
1591
|
e.preventDefault();
|
|
894
1592
|
e.stopPropagation();
|
|
895
1593
|
autoFitColumn(colId, colIndex);
|
|
896
1594
|
}, [autoFitColumn]);
|
|
897
|
-
const handleHeaderDblClick = (0,
|
|
1595
|
+
const handleHeaderDblClick = (0, import_react10.useCallback)((colId, colIndex, e) => {
|
|
898
1596
|
const target = e.currentTarget;
|
|
899
1597
|
if (target) {
|
|
900
1598
|
const rect = target.getBoundingClientRect();
|
|
@@ -916,7 +1614,7 @@ var SpreadsheetEditor = ({
|
|
|
916
1614
|
const isActiveCell = (rIdx, cIdx) => {
|
|
917
1615
|
return state.activeCell?.row === rIdx && state.activeCell?.col === cIdx;
|
|
918
1616
|
};
|
|
919
|
-
|
|
1617
|
+
const grid = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
920
1618
|
"div",
|
|
921
1619
|
{
|
|
922
1620
|
ref: gridRef,
|
|
@@ -926,10 +1624,10 @@ var SpreadsheetEditor = ({
|
|
|
926
1624
|
role: "grid",
|
|
927
1625
|
"aria-label": "Spreadsheet",
|
|
928
1626
|
children: [
|
|
929
|
-
/* @__PURE__ */ (0,
|
|
930
|
-
/* @__PURE__ */ (0,
|
|
931
|
-
/* @__PURE__ */ (0,
|
|
932
|
-
/* @__PURE__ */ (0,
|
|
1627
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-2 border-b border-border bg-muted/20 px-3 py-1.5 text-xs font-mono", children: [
|
|
1628
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("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 }),
|
|
1629
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "flex items-center text-muted-foreground select-none", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "italic font-serif font-bold text-sm text-primary", children: "fx" }) }),
|
|
1630
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
933
1631
|
"input",
|
|
934
1632
|
{
|
|
935
1633
|
ref: formulaBarRef,
|
|
@@ -965,19 +1663,33 @@ var SpreadsheetEditor = ({
|
|
|
965
1663
|
}
|
|
966
1664
|
}
|
|
967
1665
|
),
|
|
968
|
-
!formulaBarReadOnly && /* @__PURE__ */ (0,
|
|
1666
|
+
!formulaBarReadOnly && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
969
1667
|
FormulaHints,
|
|
970
1668
|
{
|
|
971
1669
|
inputRef: formulaBarRef,
|
|
972
1670
|
value: state.editingCell ? state.draftValue : formulaDraft ?? activeRawValue,
|
|
973
|
-
onAccept: (next) => state.editingCell ? state.setDraftValue(next) : setFormulaDraft(next)
|
|
1671
|
+
onAccept: (next) => state.editingCell ? state.setDraftValue(next) : setFormulaDraft(next),
|
|
1672
|
+
names: nameSuggestions
|
|
974
1673
|
}
|
|
975
1674
|
)
|
|
976
1675
|
] }),
|
|
977
|
-
/* @__PURE__ */ (0,
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1676
|
+
inputMessage && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { role: "note", "data-validation-input-message": "", className: "border-b border-border bg-muted/30 px-3 py-1 text-[11px] text-muted-foreground", children: [
|
|
1677
|
+
inputMessage.inputTitle && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "mr-1 font-semibold text-foreground", children: inputMessage.inputTitle }),
|
|
1678
|
+
inputMessage.inputMessage
|
|
1679
|
+
] }),
|
|
1680
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ref: scrollRef, className: "relative flex-1 overflow-auto", onScroll: handleGridScroll, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("table", { className: "border-separate border-spacing-0 table-fixed w-max min-w-full [&>*>tr:not([data-spacer])>*]:border-b [&>*>tr>*]:border-border", children: [
|
|
1681
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("tr", { className: "h-7 bg-muted sticky top-0 z-20 border-b border-border shadow-xs", children: [
|
|
1682
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("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: "#" }),
|
|
1683
|
+
columnSlots.map((slot) => slot.kind === "space" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1684
|
+
"th",
|
|
1685
|
+
{
|
|
1686
|
+
"aria-hidden": "true",
|
|
1687
|
+
"data-spacer": "",
|
|
1688
|
+
className: "p-0",
|
|
1689
|
+
style: { width: slot.width, minWidth: slot.width, maxWidth: slot.width, padding: 0 }
|
|
1690
|
+
},
|
|
1691
|
+
slot.key
|
|
1692
|
+
) : (({ column: col, index: cIdx, frozen, stickyLeft }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
981
1693
|
"th",
|
|
982
1694
|
{
|
|
983
1695
|
"data-col": cIdx,
|
|
@@ -992,16 +1704,16 @@ var SpreadsheetEditor = ({
|
|
|
992
1704
|
onClick: () => onColumnHeaderClick?.(col),
|
|
993
1705
|
onDoubleClick: (e) => handleHeaderDblClick(col.id, cIdx, e),
|
|
994
1706
|
children: [
|
|
995
|
-
/* @__PURE__ */ (0,
|
|
996
|
-
/* @__PURE__ */ (0,
|
|
997
|
-
filteredColIds.has(col.id) && /* @__PURE__ */ (0,
|
|
998
|
-
/* @__PURE__ */ (0,
|
|
1707
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center justify-between gap-1 min-w-0 overflow-hidden truncate", children: [
|
|
1708
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "truncate font-semibold text-foreground text-xs min-w-0", children: col.title }),
|
|
1709
|
+
filteredColIds.has(col.id) && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-[10px] text-primary shrink-0", title: "Filtered", "data-filtered": "true", children: "\u23F7" }),
|
|
1710
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "text-[10px] text-muted-foreground/70 font-mono font-normal shrink-0", children: [
|
|
999
1711
|
"(",
|
|
1000
1712
|
col.key,
|
|
1001
1713
|
")"
|
|
1002
1714
|
] })
|
|
1003
1715
|
] }),
|
|
1004
|
-
/* @__PURE__ */ (0,
|
|
1716
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1005
1717
|
"div",
|
|
1006
1718
|
{
|
|
1007
1719
|
className: "col-resize-handle",
|
|
@@ -1018,7 +1730,7 @@ var SpreadsheetEditor = ({
|
|
|
1018
1730
|
onDoubleClick: (e) => handleResizeDblClick(col.id, cIdx, e),
|
|
1019
1731
|
role: "separator",
|
|
1020
1732
|
"aria-orientation": "vertical",
|
|
1021
|
-
children: /* @__PURE__ */ (0,
|
|
1733
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1022
1734
|
"div",
|
|
1023
1735
|
{
|
|
1024
1736
|
className: "col-resize-handle-line",
|
|
@@ -1036,24 +1748,32 @@ var SpreadsheetEditor = ({
|
|
|
1036
1748
|
]
|
|
1037
1749
|
},
|
|
1038
1750
|
col.id
|
|
1039
|
-
)),
|
|
1040
|
-
onAddColumnClick && /* @__PURE__ */ (0,
|
|
1751
|
+
))(slot.viewCol)),
|
|
1752
|
+
onAddColumnClick && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1041
1753
|
"th",
|
|
1042
1754
|
{
|
|
1043
1755
|
className: "w-10 min-w-[2.5rem] px-1 text-center bg-muted/40 hover:bg-muted cursor-pointer transition-colors",
|
|
1044
1756
|
onClick: onAddColumnClick,
|
|
1045
1757
|
title: "Add Column",
|
|
1046
|
-
children: /* @__PURE__ */ (0,
|
|
1758
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-xs font-bold text-muted-foreground", children: "+" })
|
|
1047
1759
|
}
|
|
1048
1760
|
)
|
|
1049
1761
|
] }) }),
|
|
1050
|
-
/* @__PURE__ */ (0,
|
|
1762
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("tbody", { children: rowSlots.map((rowSlot) => {
|
|
1763
|
+
if (rowSlot.kind === "space") {
|
|
1764
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("tr", { "aria-hidden": "true", "data-spacer": "", style: { height: rowSlot.height }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { colSpan: columnSlots.length + 2, style: { padding: 0, border: 0 } }) }, rowSlot.key);
|
|
1765
|
+
}
|
|
1766
|
+
const viewRow = rowSlot.viewRow;
|
|
1051
1767
|
if (viewRow.kind === "group") {
|
|
1052
|
-
return /* @__PURE__ */ (0,
|
|
1768
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1053
1769
|
SpreadsheetGroupRow,
|
|
1054
1770
|
{
|
|
1055
1771
|
group: viewRow.group,
|
|
1056
|
-
columns:
|
|
1772
|
+
columns: renderedColumns,
|
|
1773
|
+
labelColumns: view.columns,
|
|
1774
|
+
leadingColumns: viewWindow.virtual ? viewWindow.frozenColumns : void 0,
|
|
1775
|
+
leftSpace: viewWindow.leftSpace,
|
|
1776
|
+
rightSpace: viewWindow.rightSpace,
|
|
1057
1777
|
groupColId: state.document.table?.grouping?.colId ?? "",
|
|
1058
1778
|
onToggle: state.toggleGroupCollapsed,
|
|
1059
1779
|
trailingCell: !!onAddColumnClick
|
|
@@ -1062,15 +1782,15 @@ var SpreadsheetEditor = ({
|
|
|
1062
1782
|
);
|
|
1063
1783
|
}
|
|
1064
1784
|
const displayRowIndex = viewRow.index;
|
|
1065
|
-
const selection = state.selectedRange ? (0,
|
|
1066
|
-
return /* @__PURE__ */ (0,
|
|
1785
|
+
const selection = state.selectedRange ? (0, import_spreadsheet8.normalizeRange)(state.selectedRange) : null;
|
|
1786
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1067
1787
|
"tr",
|
|
1068
1788
|
{
|
|
1069
1789
|
"data-row": displayRowIndex,
|
|
1070
1790
|
className: `border-b border-border hover:bg-muted/10 transition-colors ${viewRow.section !== "body" ? "bg-muted/30 font-semibold" : ""}`,
|
|
1071
1791
|
style: { height: viewRow.row.height || 32 },
|
|
1072
1792
|
children: [
|
|
1073
|
-
/* @__PURE__ */ (0,
|
|
1793
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1074
1794
|
"td",
|
|
1075
1795
|
{
|
|
1076
1796
|
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",
|
|
@@ -1078,7 +1798,9 @@ var SpreadsheetEditor = ({
|
|
|
1078
1798
|
children: displayRowIndex + 1
|
|
1079
1799
|
}
|
|
1080
1800
|
),
|
|
1081
|
-
|
|
1801
|
+
columnSlots.map((slot) => {
|
|
1802
|
+
if (slot.kind === "space") return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { "aria-hidden": "true", "data-spacer": "", style: { padding: 0 } }, slot.key);
|
|
1803
|
+
const viewCol = slot.viewCol;
|
|
1082
1804
|
const span = view.getCellSpan(displayRowIndex, viewCol.index);
|
|
1083
1805
|
if (!span) return null;
|
|
1084
1806
|
const rIdx = span.source.row;
|
|
@@ -1089,9 +1811,13 @@ var SpreadsheetEditor = ({
|
|
|
1089
1811
|
const selected = isCellSelected(rIdx, cIdx);
|
|
1090
1812
|
const active = isActiveCell(rIdx, cIdx);
|
|
1091
1813
|
const isEditing = state.editingCell?.row === rIdx && state.editingCell?.col === cIdx;
|
|
1092
|
-
const
|
|
1093
|
-
const
|
|
1094
|
-
|
|
1814
|
+
const conditional = overlay.get(rIdx, cIdx);
|
|
1815
|
+
const conditionalCss = (0, import_spreadsheet8.getConditionalFormatCss)(conditional);
|
|
1816
|
+
const circled = state.controller.isInvalidDataCircled(rIdx, cIdx);
|
|
1817
|
+
const showListArrow = active && !isEditing && !!activeListValues;
|
|
1818
|
+
const inFillPreview = !!fillTarget && !selected && (0, import_spreadsheet8.rangeContains)(fillTarget, displayRowIndex, viewCol.index);
|
|
1819
|
+
const showFillHandle = !readOnly && !isEditing && !!selection && (span.merge ? (0, import_spreadsheet8.rangeContains)(span.merge, selection.endRow, selection.endCol) : selection.endRow === rIdx && selection.endCol === cIdx);
|
|
1820
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1095
1821
|
"td",
|
|
1096
1822
|
{
|
|
1097
1823
|
"data-row": rIdx,
|
|
@@ -1103,15 +1829,19 @@ var SpreadsheetEditor = ({
|
|
|
1103
1829
|
maxWidth: 0,
|
|
1104
1830
|
overflow: "hidden",
|
|
1105
1831
|
...frozenCellStyle(viewRow, viewCol),
|
|
1832
|
+
...conditionalCss,
|
|
1833
|
+
// Keep the selection tint visible over a conditional fill.
|
|
1834
|
+
...conditionalCss.background && selected ? { boxShadow: "inset 0 0 0 9999px color-mix(in srgb, var(--primary, #6366f1) 12%, transparent)" } : {},
|
|
1106
1835
|
...inFillPreview ? { outline: "1px dashed var(--primary, #6366f1)", outlineOffset: -1 } : {}
|
|
1107
1836
|
},
|
|
1837
|
+
"data-cf-fill": conditional?.fill,
|
|
1108
1838
|
onClick: (e) => handleCellClick(rIdx, cIdx, e),
|
|
1109
1839
|
onDoubleClick: () => handleCellDblClick(rIdx, cIdx),
|
|
1110
1840
|
onContextMenu: (e) => handleCellContextMenu(rIdx, cIdx, e),
|
|
1111
1841
|
onMouseOver: () => extendFill(displayRowIndex, viewCol.index),
|
|
1112
1842
|
children: [
|
|
1113
|
-
isEditing ? /* @__PURE__ */ (0,
|
|
1114
|
-
/* @__PURE__ */ (0,
|
|
1843
|
+
isEditing ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
1844
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1115
1845
|
"input",
|
|
1116
1846
|
{
|
|
1117
1847
|
ref: cellInputRef,
|
|
@@ -1127,10 +1857,36 @@ var SpreadsheetEditor = ({
|
|
|
1127
1857
|
autoFocus: true
|
|
1128
1858
|
}
|
|
1129
1859
|
),
|
|
1130
|
-
/* @__PURE__ */ (0,
|
|
1131
|
-
] }) : customCellRenderer ? customCellRenderer({ row, col, cell }) : /* @__PURE__ */ (0,
|
|
1132
|
-
|
|
1133
|
-
|
|
1860
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(FormulaHints, { inputRef: cellInputRef, value: state.draftValue, onAccept: state.setDraftValue, names: nameSuggestions })
|
|
1861
|
+
] }) : customCellRenderer ? customCellRenderer({ row, col, cell }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CellText, { cell, conditional }),
|
|
1862
|
+
circled && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1863
|
+
"span",
|
|
1864
|
+
{
|
|
1865
|
+
className: "spreadsheet-invalid-circle",
|
|
1866
|
+
"aria-label": "Invalid value",
|
|
1867
|
+
style: { position: "absolute", inset: "1px 2px", border: "2px solid #e11d48", borderRadius: "50%", pointerEvents: "none", zIndex: 6 }
|
|
1868
|
+
}
|
|
1869
|
+
),
|
|
1870
|
+
showListArrow && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1871
|
+
"button",
|
|
1872
|
+
{
|
|
1873
|
+
type: "button",
|
|
1874
|
+
className: "spreadsheet-list-arrow",
|
|
1875
|
+
"aria-label": "Show allowed values",
|
|
1876
|
+
"aria-haspopup": "listbox",
|
|
1877
|
+
tabIndex: -1,
|
|
1878
|
+
style: { position: "absolute", right: 1, top: 1, bottom: 1, width: 16, zIndex: 24, fontSize: 10, lineHeight: 1, background: "var(--muted, #f4f4f5)", border: "1px solid var(--border, #e4e4e7)", borderRadius: 2, cursor: "pointer", padding: 0 },
|
|
1879
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
1880
|
+
onClick: (e) => {
|
|
1881
|
+
e.stopPropagation();
|
|
1882
|
+
if (listMenu) setListMenu(null);
|
|
1883
|
+
else openListMenu(rIdx, cIdx, e.currentTarget.parentElement);
|
|
1884
|
+
},
|
|
1885
|
+
children: "\u25BE"
|
|
1886
|
+
}
|
|
1887
|
+
),
|
|
1888
|
+
activeSpill && (0, import_spreadsheet8.rangeContains)(activeSpill.range, rIdx, cIdx) && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { "aria-hidden": "true", className: "spreadsheet-spill-border", style: spillBorderStyle(activeSpill.range, rIdx, cIdx) }),
|
|
1889
|
+
showFillHandle && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1134
1890
|
"div",
|
|
1135
1891
|
{
|
|
1136
1892
|
className: "spreadsheet-fill-handle",
|
|
@@ -1145,14 +1901,14 @@ var SpreadsheetEditor = ({
|
|
|
1145
1901
|
viewCol.column.id
|
|
1146
1902
|
);
|
|
1147
1903
|
}),
|
|
1148
|
-
onAddColumnClick && /* @__PURE__ */ (0,
|
|
1904
|
+
onAddColumnClick && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { className: "w-10 min-w-[2.5rem] bg-muted/10 border-r border-border" })
|
|
1149
1905
|
]
|
|
1150
1906
|
},
|
|
1151
1907
|
viewRow.row.id
|
|
1152
1908
|
);
|
|
1153
1909
|
}) })
|
|
1154
1910
|
] }) }),
|
|
1155
|
-
menu && /* @__PURE__ */ (0,
|
|
1911
|
+
menu && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1156
1912
|
SpreadsheetContextMenu,
|
|
1157
1913
|
{
|
|
1158
1914
|
items: menu.items,
|
|
@@ -1161,10 +1917,45 @@ var SpreadsheetEditor = ({
|
|
|
1161
1917
|
onClose: () => {
|
|
1162
1918
|
setMenu(null);
|
|
1163
1919
|
gridRef.current?.focus();
|
|
1164
|
-
}
|
|
1920
|
+
},
|
|
1921
|
+
onOpenPanel: setPanel
|
|
1922
|
+
}
|
|
1923
|
+
),
|
|
1924
|
+
panel === "conditionalFormatting" && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SpreadsheetConditionalFormatPanel, { state, onClose: () => setPanel(null) }),
|
|
1925
|
+
panel === "dataValidation" && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SpreadsheetDataValidationPanel, { state, onClose: () => setPanel(null) }),
|
|
1926
|
+
listMenu && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1927
|
+
"div",
|
|
1928
|
+
{
|
|
1929
|
+
role: "listbox",
|
|
1930
|
+
"aria-label": "Allowed values",
|
|
1931
|
+
className: "fixed z-50 max-h-60 min-w-[8rem] overflow-y-auto rounded-md border border-border bg-popover py-1 text-xs text-popover-foreground shadow-lg",
|
|
1932
|
+
style: { left: listMenu.x, top: listMenu.y, background: "var(--popover, var(--background, #fff))" },
|
|
1933
|
+
onKeyDown: (e) => {
|
|
1934
|
+
e.stopPropagation();
|
|
1935
|
+
if (e.key === "Escape") {
|
|
1936
|
+
setListMenu(null);
|
|
1937
|
+
gridRef.current?.focus();
|
|
1938
|
+
}
|
|
1939
|
+
},
|
|
1940
|
+
children: [
|
|
1941
|
+
listMenu.values.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "px-3 py-1 text-muted-foreground", children: "No values" }),
|
|
1942
|
+
listMenu.values.map((value, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1943
|
+
"button",
|
|
1944
|
+
{
|
|
1945
|
+
type: "button",
|
|
1946
|
+
role: "option",
|
|
1947
|
+
"aria-selected": false,
|
|
1948
|
+
className: "block w-full px-3 py-1 text-left hover:bg-muted",
|
|
1949
|
+
onClick: () => chooseListValue(value),
|
|
1950
|
+
children: value
|
|
1951
|
+
},
|
|
1952
|
+
`${i}:${value}`
|
|
1953
|
+
))
|
|
1954
|
+
]
|
|
1165
1955
|
}
|
|
1166
1956
|
),
|
|
1167
|
-
/* @__PURE__ */ (0,
|
|
1957
|
+
state.validationPrompt && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SpreadsheetValidationDialog, { prompt: state.validationPrompt, onResolve: resolveValidation }),
|
|
1958
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("style", { children: `
|
|
1168
1959
|
.col-resize-handle:hover .col-resize-handle-line {
|
|
1169
1960
|
background-color: var(--primary, #6366f1);
|
|
1170
1961
|
opacity: 0.75;
|
|
@@ -1184,15 +1975,181 @@ var SpreadsheetEditor = ({
|
|
|
1184
1975
|
]
|
|
1185
1976
|
}
|
|
1186
1977
|
);
|
|
1978
|
+
if (!showSheetTabs) return grid;
|
|
1979
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex h-full w-full flex-col", children: [
|
|
1980
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "min-h-0 flex-1", children: grid }),
|
|
1981
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SheetTabs, { state, readOnly })
|
|
1982
|
+
] });
|
|
1983
|
+
};
|
|
1984
|
+
|
|
1985
|
+
// src/spreadsheet/NameManager.tsx
|
|
1986
|
+
var import_react11 = require("react");
|
|
1987
|
+
var import_spreadsheet9 = require("@pixerate/editor/spreadsheet");
|
|
1988
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1989
|
+
var EMPTY_DRAFT = { original: null, name: "", refersTo: "=", scope: "", comment: "" };
|
|
1990
|
+
var NameManager = ({ state, readOnly = false, onClose, className = "" }) => {
|
|
1991
|
+
const { controller, sheets } = state;
|
|
1992
|
+
const rows = (0, import_react11.useMemo)(
|
|
1993
|
+
() => (0, import_spreadsheet9.getNameManagerRows)(controller),
|
|
1994
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1995
|
+
[controller, state.document, state.names]
|
|
1996
|
+
);
|
|
1997
|
+
const [draft, setDraft] = (0, import_react11.useState)(EMPTY_DRAFT);
|
|
1998
|
+
const [error, setError] = (0, import_react11.useState)(null);
|
|
1999
|
+
const edit = (row) => {
|
|
2000
|
+
setError(null);
|
|
2001
|
+
setDraft({
|
|
2002
|
+
original: { name: row.name, sheetId: row.sheetId },
|
|
2003
|
+
name: row.name,
|
|
2004
|
+
refersTo: row.refersTo,
|
|
2005
|
+
scope: row.sheetId ?? "",
|
|
2006
|
+
comment: row.comment ?? ""
|
|
2007
|
+
});
|
|
2008
|
+
};
|
|
2009
|
+
const save = (e) => {
|
|
2010
|
+
e.preventDefault();
|
|
2011
|
+
const scope = draft.scope ? { sheet: draft.scope } : {};
|
|
2012
|
+
try {
|
|
2013
|
+
const invalid = controller.validateDefinedName(draft.name.trim());
|
|
2014
|
+
if (invalid) throw new Error(invalid);
|
|
2015
|
+
try {
|
|
2016
|
+
(0, import_spreadsheet9.parseFormula)(draft.refersTo.trim().startsWith("=") ? draft.refersTo.trim() : `=${draft.refersTo.trim()}`);
|
|
2017
|
+
} catch (err) {
|
|
2018
|
+
throw new Error(`There's a problem with this formula: ${err?.message ?? draft.refersTo}`);
|
|
2019
|
+
}
|
|
2020
|
+
const original = draft.original;
|
|
2021
|
+
if (original) {
|
|
2022
|
+
const originalScope = original.sheetId ? { sheet: original.sheetId } : {};
|
|
2023
|
+
if ((original.sheetId ?? "") !== draft.scope) {
|
|
2024
|
+
state.deleteName(original.name, originalScope);
|
|
2025
|
+
} else if (original.name !== draft.name) {
|
|
2026
|
+
state.renameName(original.name, draft.name.trim(), originalScope);
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
state.defineName(draft.name.trim(), draft.refersTo, { ...scope, comment: draft.comment || void 0 });
|
|
2030
|
+
setDraft(EMPTY_DRAFT);
|
|
2031
|
+
setError(null);
|
|
2032
|
+
} catch (err) {
|
|
2033
|
+
setError(err?.message ?? String(err));
|
|
2034
|
+
}
|
|
2035
|
+
};
|
|
2036
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("section", { "aria-label": "Name Manager", className: `flex flex-col gap-2 text-xs ${className}`, "data-name-manager": "", children: [
|
|
2037
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center justify-between", children: [
|
|
2038
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { className: "m-0 text-sm font-semibold", children: "Name Manager" }),
|
|
2039
|
+
onClose && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", "aria-label": "Close Name Manager", className: "rounded px-2 hover:bg-muted", onClick: onClose, children: "\xD7" })
|
|
2040
|
+
] }),
|
|
2041
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("table", { className: "w-full border-collapse", children: [
|
|
2042
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("tr", { className: "text-left text-muted-foreground", children: [
|
|
2043
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("th", { className: "px-1 font-medium", children: "Name" }),
|
|
2044
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("th", { className: "px-1 font-medium", children: "Value" }),
|
|
2045
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("th", { className: "px-1 font-medium", children: "Refers to" }),
|
|
2046
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("th", { className: "px-1 font-medium", children: "Scope" }),
|
|
2047
|
+
!readOnly && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("th", { className: "px-1" })
|
|
2048
|
+
] }) }),
|
|
2049
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("tbody", { children: [
|
|
2050
|
+
rows.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("td", { colSpan: 5, className: "px-1 py-2 text-muted-foreground", children: "No names yet." }) }),
|
|
2051
|
+
rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("tr", { "data-name": row.name, className: "border-t border-border", title: row.comment, children: [
|
|
2052
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("td", { className: "px-1 py-0.5 font-mono font-semibold", children: row.name }),
|
|
2053
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("td", { className: "px-1 py-0.5", children: row.value }),
|
|
2054
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("td", { className: "px-1 py-0.5 font-mono", children: row.refersTo }),
|
|
2055
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("td", { className: "px-1 py-0.5", children: row.scopeLabel }),
|
|
2056
|
+
!readOnly && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("td", { className: "whitespace-nowrap px-1 py-0.5 text-right", children: [
|
|
2057
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", "aria-label": `Edit ${row.name}`, className: "rounded px-1 hover:bg-muted", onClick: () => edit(row), children: "Edit" }),
|
|
2058
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2059
|
+
"button",
|
|
2060
|
+
{
|
|
2061
|
+
type: "button",
|
|
2062
|
+
"aria-label": `Delete ${row.name}`,
|
|
2063
|
+
className: "rounded px-1 text-destructive hover:bg-muted",
|
|
2064
|
+
onClick: () => {
|
|
2065
|
+
state.deleteName(row.name, row.sheetId ? { sheet: row.sheetId } : {});
|
|
2066
|
+
if (draft.original?.name === row.name && (draft.original.sheetId ?? "") === (row.sheetId ?? "")) setDraft(EMPTY_DRAFT);
|
|
2067
|
+
},
|
|
2068
|
+
children: "Delete"
|
|
2069
|
+
}
|
|
2070
|
+
)
|
|
2071
|
+
] })
|
|
2072
|
+
] }, `${row.sheetId ?? ""}!${row.name}`))
|
|
2073
|
+
] })
|
|
2074
|
+
] }),
|
|
2075
|
+
!readOnly && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("form", { className: "grid grid-cols-[auto_1fr] items-center gap-x-2 gap-y-1", onSubmit: save, "aria-label": draft.original ? `Edit name ${draft.original.name}` : "New name", children: [
|
|
2076
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("label", { htmlFor: "pixerate-name-manager-name", children: "Name" }),
|
|
2077
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2078
|
+
"input",
|
|
2079
|
+
{
|
|
2080
|
+
id: "pixerate-name-manager-name",
|
|
2081
|
+
className: "rounded border border-border bg-background px-1 py-0.5",
|
|
2082
|
+
value: draft.name,
|
|
2083
|
+
onChange: (e) => setDraft({ ...draft, name: e.target.value })
|
|
2084
|
+
}
|
|
2085
|
+
),
|
|
2086
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("label", { htmlFor: "pixerate-name-manager-refers", children: "Refers to" }),
|
|
2087
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2088
|
+
"input",
|
|
2089
|
+
{
|
|
2090
|
+
id: "pixerate-name-manager-refers",
|
|
2091
|
+
className: "rounded border border-border bg-background px-1 py-0.5 font-mono",
|
|
2092
|
+
value: draft.refersTo,
|
|
2093
|
+
onChange: (e) => setDraft({ ...draft, refersTo: e.target.value })
|
|
2094
|
+
}
|
|
2095
|
+
),
|
|
2096
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("label", { htmlFor: "pixerate-name-manager-scope", children: "Scope" }),
|
|
2097
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
2098
|
+
"select",
|
|
2099
|
+
{
|
|
2100
|
+
id: "pixerate-name-manager-scope",
|
|
2101
|
+
className: "rounded border border-border bg-background px-1 py-0.5",
|
|
2102
|
+
value: draft.scope,
|
|
2103
|
+
onChange: (e) => setDraft({ ...draft, scope: e.target.value }),
|
|
2104
|
+
children: [
|
|
2105
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: "", children: "Workbook" }),
|
|
2106
|
+
sheets.map((sheet) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: sheet.id, children: sheet.name }, sheet.id))
|
|
2107
|
+
]
|
|
2108
|
+
}
|
|
2109
|
+
),
|
|
2110
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("label", { htmlFor: "pixerate-name-manager-comment", children: "Comment" }),
|
|
2111
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2112
|
+
"input",
|
|
2113
|
+
{
|
|
2114
|
+
id: "pixerate-name-manager-comment",
|
|
2115
|
+
className: "rounded border border-border bg-background px-1 py-0.5",
|
|
2116
|
+
value: draft.comment,
|
|
2117
|
+
onChange: (e) => setDraft({ ...draft, comment: e.target.value })
|
|
2118
|
+
}
|
|
2119
|
+
),
|
|
2120
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "col-span-2 flex gap-2", children: [
|
|
2121
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "submit", className: "rounded bg-primary px-2 py-0.5 text-primary-foreground", children: draft.original ? "Save" : "Add name" }),
|
|
2122
|
+
draft.original && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2123
|
+
"button",
|
|
2124
|
+
{
|
|
2125
|
+
type: "button",
|
|
2126
|
+
className: "rounded px-2 py-0.5 hover:bg-muted",
|
|
2127
|
+
onClick: () => {
|
|
2128
|
+
setDraft(EMPTY_DRAFT);
|
|
2129
|
+
setError(null);
|
|
2130
|
+
},
|
|
2131
|
+
children: "Cancel"
|
|
2132
|
+
}
|
|
2133
|
+
)
|
|
2134
|
+
] })
|
|
2135
|
+
] }),
|
|
2136
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { role: "alert", className: "text-destructive", children: error })
|
|
2137
|
+
] });
|
|
1187
2138
|
};
|
|
1188
2139
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1189
2140
|
0 && (module.exports = {
|
|
1190
2141
|
FormulaHints,
|
|
2142
|
+
NameManager,
|
|
2143
|
+
SheetTabs,
|
|
2144
|
+
SpreadsheetConditionalFormatPanel,
|
|
1191
2145
|
SpreadsheetContextMenu,
|
|
2146
|
+
SpreadsheetDataValidationPanel,
|
|
1192
2147
|
SpreadsheetEditor,
|
|
1193
2148
|
SpreadsheetGroupRow,
|
|
2149
|
+
SpreadsheetValidationDialog,
|
|
1194
2150
|
useFillHandle,
|
|
1195
2151
|
useSpreadsheetClipboard,
|
|
1196
|
-
useSpreadsheetEditor
|
|
2152
|
+
useSpreadsheetEditor,
|
|
2153
|
+
useSpreadsheetViewport
|
|
1197
2154
|
});
|
|
1198
2155
|
//# sourceMappingURL=index.cjs.map
|