@pixerate/editor-react 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/dist/canvas/index.cjs +484 -119
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +122 -3
- package/dist/canvas/index.d.ts +122 -3
- package/dist/canvas/index.js +380 -0
- package/dist/canvas/index.js.map +1 -1
- package/dist/chunk-I3E4KSA2.js +515 -0
- package/dist/chunk-I3E4KSA2.js.map +1 -0
- package/dist/chunk-JR5ZBDR7.js +2012 -0
- package/dist/chunk-JR5ZBDR7.js.map +1 -0
- package/dist/chunk-XWFU2CSG.js +167 -0
- package/dist/chunk-XWFU2CSG.js.map +1 -0
- package/dist/chunk-ZU54BMDC.js +2128 -0
- package/dist/chunk-ZU54BMDC.js.map +1 -0
- package/dist/generation/index.cjs +525 -0
- package/dist/generation/index.cjs.map +1 -0
- package/dist/generation/index.d.cts +124 -0
- package/dist/generation/index.d.ts +124 -0
- package/dist/generation/index.js +19 -0
- package/dist/generation/index.js.map +1 -0
- package/dist/image-editor/index.cjs +988 -337
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +92 -2
- package/dist/image-editor/index.d.ts +92 -2
- package/dist/image-editor/index.js +17 -3
- package/dist/index.cjs +3628 -754
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +226 -5
- package/dist/index.d.ts +226 -5
- package/dist/index.js +868 -77
- package/dist/index.js.map +1 -1
- package/dist/persistence/index.cjs +191 -0
- package/dist/persistence/index.cjs.map +1 -0
- package/dist/persistence/index.d.cts +77 -0
- package/dist/persistence/index.d.ts +77 -0
- package/dist/persistence/index.js +9 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/spreadsheet/index.cjs +949 -111
- package/dist/spreadsheet/index.cjs.map +1 -1
- package/dist/spreadsheet/index.d.cts +121 -7
- package/dist/spreadsheet/index.d.ts +121 -7
- package/dist/spreadsheet/index.js +11 -1
- 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 +26 -2
- package/dist/chunk-LPFZMZ3L.js +0 -1461
- package/dist/chunk-LPFZMZ3L.js.map +0 -1
- package/dist/chunk-QVQYYHJ5.js +0 -1161
- package/dist/chunk-QVQYYHJ5.js.map +0 -1
|
@@ -31,9 +31,14 @@ 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
44
|
useSpreadsheetEditor: () => useSpreadsheetEditor
|
|
@@ -69,6 +74,7 @@ function useSpreadsheetEditor(options = {}) {
|
|
|
69
74
|
const [selectedRange, setSelectedRange] = (0, import_react.useState)(controller.selectedRange);
|
|
70
75
|
const [editingCell, setEditingCell] = (0, import_react.useState)(null);
|
|
71
76
|
const [draftValue, setDraftState] = (0, import_react.useState)("");
|
|
77
|
+
const [validationPrompt, setValidationPrompt] = (0, import_react.useState)(null);
|
|
72
78
|
settersRef.current = { setDoc, setActiveCell, setSelectedRange };
|
|
73
79
|
const setDraftValue = (0, import_react.useCallback)((value) => {
|
|
74
80
|
controller.draftValue = value;
|
|
@@ -93,12 +99,28 @@ function useSpreadsheetEditor(options = {}) {
|
|
|
93
99
|
setEditingCell(controller.editingCell);
|
|
94
100
|
setDraftValue(controller.draftValue);
|
|
95
101
|
}, [controller]);
|
|
96
|
-
const
|
|
97
|
-
controller.
|
|
98
|
-
|
|
99
|
-
|
|
102
|
+
const syncEditing = (0, import_react.useCallback)(() => {
|
|
103
|
+
setEditingCell(controller.editingCell);
|
|
104
|
+
setDraftValue(controller.draftValue);
|
|
105
|
+
setValidationPrompt(controller.validationPrompt);
|
|
106
|
+
setActiveCell(controller.activeCell);
|
|
107
|
+
setSelectedRange(controller.selectedRange);
|
|
100
108
|
setDoc({ ...controller.document });
|
|
101
109
|
}, [controller]);
|
|
110
|
+
const commitEditing = (0, import_react.useCallback)((val) => {
|
|
111
|
+
const result = controller.commitEditing(val);
|
|
112
|
+
syncEditing();
|
|
113
|
+
return result;
|
|
114
|
+
}, [controller, syncEditing]);
|
|
115
|
+
const commitCellInput = (0, import_react.useCallback)((rowId, colId, raw) => {
|
|
116
|
+
const result = controller.commitCellInput(rowId, colId, raw);
|
|
117
|
+
syncEditing();
|
|
118
|
+
return result;
|
|
119
|
+
}, [controller, syncEditing]);
|
|
120
|
+
const resolveValidationPrompt = (0, import_react.useCallback)((action) => {
|
|
121
|
+
controller.resolveValidationPrompt(action);
|
|
122
|
+
syncEditing();
|
|
123
|
+
}, [controller, syncEditing]);
|
|
102
124
|
const cancelEditing = (0, import_react.useCallback)(() => {
|
|
103
125
|
controller.cancelEditing();
|
|
104
126
|
setEditingCell(null);
|
|
@@ -148,13 +170,57 @@ function useSpreadsheetEditor(options = {}) {
|
|
|
148
170
|
const undo = (0, import_react.useCallback)(() => {
|
|
149
171
|
controller.undo();
|
|
150
172
|
setDoc({ ...controller.document });
|
|
173
|
+
setActiveCell(controller.activeCell);
|
|
174
|
+
setSelectedRange(controller.selectedRange);
|
|
151
175
|
}, [controller]);
|
|
152
176
|
const redo = (0, import_react.useCallback)(() => {
|
|
153
177
|
controller.redo();
|
|
154
178
|
setDoc({ ...controller.document });
|
|
179
|
+
setActiveCell(controller.activeCell);
|
|
180
|
+
setSelectedRange(controller.selectedRange);
|
|
155
181
|
}, [controller]);
|
|
156
182
|
const tableActions = (0, import_react.useMemo)(() => (0, import_spreadsheet.bindSpreadsheetTableActions)(controller), [controller]);
|
|
157
183
|
const viewModel = (0, import_react.useMemo)(() => controller.getViewModel(), [controller, doc]);
|
|
184
|
+
const sheets = (0, import_react.useMemo)(() => controller.getSheets(), [controller, doc]);
|
|
185
|
+
const names = (0, import_react.useMemo)(() => controller.getNames(), [controller, doc]);
|
|
186
|
+
const afterSheetSwitch = (0, import_react.useCallback)(() => {
|
|
187
|
+
setEditingCell(controller.editingCell);
|
|
188
|
+
setDraftValue(controller.draftValue);
|
|
189
|
+
setActiveCell(controller.activeCell);
|
|
190
|
+
setSelectedRange(controller.selectedRange);
|
|
191
|
+
setDoc({ ...controller.document });
|
|
192
|
+
}, [controller, setDraftValue]);
|
|
193
|
+
const workbookActions = (0, import_react.useMemo)(
|
|
194
|
+
() => ({
|
|
195
|
+
setActiveSheet: (idOrName) => {
|
|
196
|
+
const changed = controller.setActiveSheet(idOrName);
|
|
197
|
+
afterSheetSwitch();
|
|
198
|
+
return changed;
|
|
199
|
+
},
|
|
200
|
+
addSheet: (options2) => {
|
|
201
|
+
const id = controller.addSheet(options2);
|
|
202
|
+
afterSheetSwitch();
|
|
203
|
+
return id;
|
|
204
|
+
},
|
|
205
|
+
renameSheet: (idOrName, newName) => controller.renameSheet(idOrName, newName),
|
|
206
|
+
moveSheet: (idOrName, toIndex) => controller.moveSheet(idOrName, toIndex),
|
|
207
|
+
deleteSheet: (idOrName) => {
|
|
208
|
+
const deleted = controller.deleteSheet(idOrName);
|
|
209
|
+
afterSheetSwitch();
|
|
210
|
+
return deleted;
|
|
211
|
+
},
|
|
212
|
+
duplicateSheet: (idOrName, options2) => {
|
|
213
|
+
const id = controller.duplicateSheet(idOrName, options2);
|
|
214
|
+
afterSheetSwitch();
|
|
215
|
+
return id;
|
|
216
|
+
},
|
|
217
|
+
defineName: (name, refersTo, options2) => controller.defineName(name, refersTo, options2),
|
|
218
|
+
renameName: (oldName, newName, options2) => controller.renameName(oldName, newName, options2),
|
|
219
|
+
deleteName: (name, options2) => controller.deleteName(name, options2)
|
|
220
|
+
}),
|
|
221
|
+
[controller, afterSheetSwitch]
|
|
222
|
+
);
|
|
223
|
+
const conditionalFormatOverlay = (0, import_react.useMemo)(() => controller.getConditionalFormatOverlay(), [controller, doc]);
|
|
158
224
|
return {
|
|
159
225
|
document: doc,
|
|
160
226
|
activeCell,
|
|
@@ -162,6 +228,9 @@ function useSpreadsheetEditor(options = {}) {
|
|
|
162
228
|
editingCell,
|
|
163
229
|
draftValue,
|
|
164
230
|
setDraftValue,
|
|
231
|
+
validationPrompt,
|
|
232
|
+
resolveValidationPrompt,
|
|
233
|
+
commitCellInput,
|
|
165
234
|
controller,
|
|
166
235
|
getCell: (rowId, colId) => controller.getCell(rowId, colId),
|
|
167
236
|
setCellValue,
|
|
@@ -200,21 +269,28 @@ function useSpreadsheetEditor(options = {}) {
|
|
|
200
269
|
},
|
|
201
270
|
...tableActions,
|
|
202
271
|
viewModel,
|
|
272
|
+
/** The sheets in tab order (see `SpreadsheetController.getSheets`). */
|
|
273
|
+
sheets,
|
|
274
|
+
activeSheetId: controller.activeSheetId,
|
|
275
|
+
/** Defined names (see `SpreadsheetController.getNames`). */
|
|
276
|
+
names,
|
|
277
|
+
...workbookActions,
|
|
278
|
+
conditionalFormatOverlay,
|
|
203
279
|
undo,
|
|
204
280
|
redo
|
|
205
281
|
};
|
|
206
282
|
}
|
|
207
283
|
|
|
208
284
|
// src/spreadsheet/SpreadsheetEditor.tsx
|
|
209
|
-
var
|
|
210
|
-
var
|
|
285
|
+
var import_react8 = require("react");
|
|
286
|
+
var import_spreadsheet7 = require("@pixerate/editor/spreadsheet");
|
|
211
287
|
|
|
212
288
|
// src/spreadsheet/SpreadsheetContextMenu.tsx
|
|
213
289
|
var import_react2 = __toESM(require("react"), 1);
|
|
214
290
|
var import_spreadsheet2 = require("@pixerate/editor/spreadsheet");
|
|
215
291
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
216
292
|
var VIEWPORT_MARGIN = 4;
|
|
217
|
-
var SpreadsheetContextMenu = ({ items, x, y, onClose }) => {
|
|
293
|
+
var SpreadsheetContextMenu = ({ items, x, y, onClose, onOpenPanel, ariaLabel = "Table actions" }) => {
|
|
218
294
|
const menuRef = (0, import_react2.useRef)(null);
|
|
219
295
|
const onCloseRef = (0, import_react2.useRef)(onClose);
|
|
220
296
|
onCloseRef.current = onClose;
|
|
@@ -250,7 +326,7 @@ var SpreadsheetContextMenu = ({ items, x, y, onClose }) => {
|
|
|
250
326
|
{
|
|
251
327
|
ref: menuRef,
|
|
252
328
|
role: "menu",
|
|
253
|
-
"aria-label":
|
|
329
|
+
"aria-label": ariaLabel,
|
|
254
330
|
className: "fixed z-50 min-w-[14rem] rounded-md border border-border bg-popover py-1 text-xs text-popover-foreground shadow-lg",
|
|
255
331
|
style: {
|
|
256
332
|
left: position.x,
|
|
@@ -269,11 +345,13 @@ var SpreadsheetContextMenu = ({ items, x, y, onClose }) => {
|
|
|
269
345
|
type: "button",
|
|
270
346
|
role: "menuitem",
|
|
271
347
|
"data-action": item.id,
|
|
348
|
+
"data-checked": item.checked ? "" : void 0,
|
|
272
349
|
disabled: item.disabled,
|
|
273
|
-
className:
|
|
350
|
+
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" : ""}`,
|
|
274
351
|
onClick: () => {
|
|
275
352
|
onClose();
|
|
276
|
-
item.
|
|
353
|
+
if (item.panel) onOpenPanel?.(item.panel);
|
|
354
|
+
else (0, import_spreadsheet2.runSpreadsheetMenuItem)(item);
|
|
277
355
|
},
|
|
278
356
|
children: item.label
|
|
279
357
|
}
|
|
@@ -405,35 +483,337 @@ function useFillHandle(state) {
|
|
|
405
483
|
return { fillTarget: drag?.target ?? null, isFilling: dragging, startFill, extendFill };
|
|
406
484
|
}
|
|
407
485
|
|
|
408
|
-
// src/spreadsheet/
|
|
486
|
+
// src/spreadsheet/SpreadsheetValidationDialog.tsx
|
|
409
487
|
var import_react4 = require("react");
|
|
410
|
-
var import_spreadsheet4 = require("@pixerate/editor/spreadsheet");
|
|
411
488
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
412
|
-
var
|
|
413
|
-
const
|
|
414
|
-
const
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
const
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
489
|
+
var SpreadsheetValidationDialog = ({ prompt, onResolve }) => {
|
|
490
|
+
const style = prompt.result.errorStyle ?? "stop";
|
|
491
|
+
const firstButton = (0, import_react4.useRef)(null);
|
|
492
|
+
(0, import_react4.useEffect)(() => {
|
|
493
|
+
firstButton.current?.focus();
|
|
494
|
+
}, [prompt]);
|
|
495
|
+
const buttons = style === "stop" ? [
|
|
496
|
+
{ label: "Retry", action: "retry" },
|
|
497
|
+
{ label: "Cancel", action: "cancel" }
|
|
498
|
+
] : style === "warning" ? [
|
|
499
|
+
{ label: "Yes", action: "accept" },
|
|
500
|
+
{ label: "No", action: "retry" },
|
|
501
|
+
{ label: "Cancel", action: "cancel" }
|
|
502
|
+
] : [
|
|
503
|
+
{ label: "OK", action: "accept" },
|
|
504
|
+
{ label: "Cancel", action: "cancel" }
|
|
505
|
+
];
|
|
506
|
+
const icon = style === "stop" ? "\u26D4" : style === "warning" ? "\u26A0" : "\u2139";
|
|
507
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
508
|
+
"div",
|
|
509
|
+
{
|
|
510
|
+
className: "fixed inset-0 z-[60] flex items-center justify-center bg-black/30",
|
|
511
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
512
|
+
onKeyDown: (e) => {
|
|
513
|
+
e.stopPropagation();
|
|
514
|
+
if (e.key === "Escape") {
|
|
515
|
+
e.preventDefault();
|
|
516
|
+
onResolve("cancel");
|
|
517
|
+
}
|
|
518
|
+
},
|
|
519
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
520
|
+
"div",
|
|
521
|
+
{
|
|
522
|
+
role: "alertdialog",
|
|
523
|
+
"aria-modal": "true",
|
|
524
|
+
"aria-labelledby": "spreadsheet-validation-title",
|
|
525
|
+
"aria-describedby": "spreadsheet-validation-message",
|
|
526
|
+
"data-error-style": style,
|
|
527
|
+
className: "w-[22rem] max-w-[calc(100vw-2rem)] rounded-md border border-border bg-popover p-4 text-xs text-popover-foreground shadow-xl",
|
|
528
|
+
style: { background: "var(--popover, var(--background, #fff))" },
|
|
529
|
+
children: [
|
|
530
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-start gap-3", children: [
|
|
531
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { "aria-hidden": "true", className: "text-lg leading-none", children: icon }),
|
|
532
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "min-w-0 flex-1", children: [
|
|
533
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { id: "spreadsheet-validation-title", className: "mb-1 text-sm font-semibold", children: prompt.result.title }),
|
|
534
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { id: "spreadsheet-validation-message", className: "whitespace-pre-wrap", children: prompt.result.message }),
|
|
535
|
+
style === "warning" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mt-2", children: "Continue?" })
|
|
536
|
+
] })
|
|
537
|
+
] }),
|
|
538
|
+
/* @__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)(
|
|
539
|
+
"button",
|
|
540
|
+
{
|
|
541
|
+
ref: i === 0 ? firstButton : void 0,
|
|
542
|
+
type: "button",
|
|
543
|
+
"data-action": button.action,
|
|
544
|
+
className: `rounded border border-border px-3 py-1 ${i === 0 ? "bg-primary text-primary-foreground" : "hover:bg-muted"}`,
|
|
545
|
+
onClick: () => onResolve(button.action),
|
|
546
|
+
children: button.label
|
|
547
|
+
},
|
|
548
|
+
button.action
|
|
549
|
+
)) })
|
|
550
|
+
]
|
|
551
|
+
}
|
|
552
|
+
)
|
|
553
|
+
}
|
|
554
|
+
);
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
// src/spreadsheet/SpreadsheetRulePanels.tsx
|
|
558
|
+
var import_react5 = require("react");
|
|
559
|
+
var import_spreadsheet4 = require("@pixerate/editor/spreadsheet");
|
|
560
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
561
|
+
var inputClass = "w-full rounded border border-border bg-background px-1.5 py-0.5 text-xs text-foreground";
|
|
562
|
+
var buttonClass = "rounded border border-border px-2 py-0.5 hover:bg-muted disabled:opacity-40";
|
|
563
|
+
function selectionText(range) {
|
|
564
|
+
return range ? (0, import_spreadsheet4.formatRuleRanges)([range]) : "A1";
|
|
565
|
+
}
|
|
566
|
+
var PanelFrame = ({
|
|
567
|
+
title,
|
|
568
|
+
label,
|
|
569
|
+
onClose,
|
|
570
|
+
children
|
|
571
|
+
}) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
572
|
+
"div",
|
|
573
|
+
{
|
|
574
|
+
role: "dialog",
|
|
575
|
+
"aria-label": label,
|
|
576
|
+
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",
|
|
577
|
+
style: { background: "var(--popover, var(--background, #fff))" },
|
|
578
|
+
onKeyDown: (e) => {
|
|
579
|
+
e.stopPropagation();
|
|
580
|
+
if (e.key === "Escape") onClose();
|
|
581
|
+
},
|
|
582
|
+
onContextMenu: (e) => e.stopPropagation(),
|
|
583
|
+
children: [
|
|
584
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mb-2 flex items-center justify-between", children: [
|
|
585
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h2", { className: "text-sm font-semibold", children: title }),
|
|
586
|
+
/* @__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" })
|
|
587
|
+
] }),
|
|
588
|
+
children
|
|
589
|
+
]
|
|
590
|
+
}
|
|
591
|
+
);
|
|
592
|
+
function RuleSwatch({ rule }) {
|
|
593
|
+
if (rule.type === "colorScale") {
|
|
594
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
595
|
+
"span",
|
|
596
|
+
{
|
|
597
|
+
"aria-hidden": "true",
|
|
598
|
+
className: "inline-block h-3 w-6 shrink-0 rounded-sm border border-border",
|
|
599
|
+
style: { background: `linear-gradient(90deg, ${rule.stops.map((s) => s.color).join(", ")})` }
|
|
600
|
+
}
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
if (rule.type === "dataBar") {
|
|
604
|
+
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" } });
|
|
605
|
+
}
|
|
606
|
+
if (rule.type === "iconSet") return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { "aria-hidden": "true", className: "shrink-0", children: "\u25CF" });
|
|
607
|
+
const css = (0, import_spreadsheet4.getConditionalFormatCss)(rule.style);
|
|
608
|
+
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" });
|
|
609
|
+
}
|
|
610
|
+
var SpreadsheetConditionalFormatPanel = ({ state, onClose }) => {
|
|
611
|
+
const rules = state.document.table?.conditionalFormats ?? [];
|
|
612
|
+
const [kind, setKind] = (0, import_react5.useState)("greaterThan");
|
|
613
|
+
const [appliesTo, setAppliesTo] = (0, import_react5.useState)(() => selectionText(state.selectedRange));
|
|
614
|
+
const [value, setValue] = (0, import_react5.useState)("");
|
|
615
|
+
const [value2, setValue2] = (0, import_react5.useState)("");
|
|
616
|
+
const [text, setText] = (0, import_react5.useState)("");
|
|
617
|
+
const [formula, setFormula] = (0, import_react5.useState)("=");
|
|
618
|
+
const [rank, setRank] = (0, import_react5.useState)("10");
|
|
619
|
+
const [period, setPeriod] = (0, import_react5.useState)("today");
|
|
620
|
+
const [iconSet, setIconSet] = (0, import_react5.useState)("3TrafficLights1");
|
|
621
|
+
const [color, setColor] = (0, import_react5.useState)("#638ec6");
|
|
622
|
+
const [styleId, setStyleId] = (0, import_react5.useState)(import_spreadsheet4.CONDITIONAL_FORMAT_STYLE_PRESETS[0].id);
|
|
623
|
+
const preset = (0, import_react5.useMemo)(() => import_spreadsheet4.CONDITIONAL_FORMAT_PRESETS.find((p) => p.kind === kind), [kind]);
|
|
624
|
+
const ranges = (0, import_spreadsheet4.parseRuleRanges)(appliesTo);
|
|
625
|
+
const add = () => {
|
|
626
|
+
if (!ranges) return;
|
|
627
|
+
state.addConditionalFormat(
|
|
628
|
+
(0, import_spreadsheet4.createConditionalFormatRule)({
|
|
629
|
+
kind,
|
|
630
|
+
ranges,
|
|
631
|
+
value,
|
|
632
|
+
value2,
|
|
633
|
+
text,
|
|
634
|
+
formula,
|
|
635
|
+
rank: Number(rank) || 10,
|
|
636
|
+
period,
|
|
637
|
+
iconSet,
|
|
638
|
+
color,
|
|
639
|
+
style: import_spreadsheet4.CONDITIONAL_FORMAT_STYLE_PRESETS.find((s) => s.id === styleId)?.style
|
|
640
|
+
})
|
|
641
|
+
);
|
|
642
|
+
};
|
|
643
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(PanelFrame, { title: "Conditional formatting", label: "Conditional formatting rules", onClose, children: [
|
|
644
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("ol", { className: "mb-3 flex flex-col gap-1", "aria-label": "Rules in priority order", children: [
|
|
645
|
+
rules.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { className: "text-muted-foreground", children: "No rules on this sheet." }),
|
|
646
|
+
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: [
|
|
647
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RuleSwatch, { rule }),
|
|
648
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "min-w-0 flex-1", children: [
|
|
649
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "truncate", title: (0, import_spreadsheet4.describeConditionalFormat)(rule), children: (0, import_spreadsheet4.describeConditionalFormat)(rule) }),
|
|
650
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "truncate font-mono text-[10px] text-muted-foreground", children: (0, import_spreadsheet4.formatRuleRanges)(rule.ranges) })
|
|
651
|
+
] }),
|
|
652
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex items-center gap-0.5 text-[10px]", title: "Stop if true", children: [
|
|
653
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
654
|
+
"input",
|
|
655
|
+
{
|
|
656
|
+
type: "checkbox",
|
|
657
|
+
"aria-label": "Stop if true",
|
|
658
|
+
checked: !!rule.stopIfTrue,
|
|
659
|
+
onChange: (e) => state.updateConditionalFormat(rule.id, { stopIfTrue: e.target.checked })
|
|
660
|
+
}
|
|
661
|
+
),
|
|
662
|
+
"Stop"
|
|
663
|
+
] }),
|
|
664
|
+
/* @__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" }),
|
|
665
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
666
|
+
"button",
|
|
667
|
+
{
|
|
668
|
+
type: "button",
|
|
669
|
+
"aria-label": "Move down",
|
|
670
|
+
className: buttonClass,
|
|
671
|
+
disabled: i === rules.length - 1,
|
|
672
|
+
onClick: () => state.moveConditionalFormat(rule.id, { by: 1 }),
|
|
673
|
+
children: "\u25BC"
|
|
674
|
+
}
|
|
675
|
+
),
|
|
676
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", "aria-label": "Delete rule", className: buttonClass, onClick: () => state.deleteConditionalFormat(rule.id), children: "\u2715" })
|
|
677
|
+
] }, rule.id))
|
|
678
|
+
] }),
|
|
679
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("fieldset", { className: "flex flex-col gap-1.5 border-t border-border pt-2", children: [
|
|
680
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("legend", { className: "sr-only", children: "New rule" }),
|
|
681
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex flex-col gap-0.5", children: [
|
|
682
|
+
"Format cells",
|
|
683
|
+
/* @__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)) })
|
|
684
|
+
] }),
|
|
685
|
+
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) }),
|
|
686
|
+
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) }),
|
|
687
|
+
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) }),
|
|
688
|
+
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) }),
|
|
689
|
+
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) }),
|
|
690
|
+
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)) }),
|
|
691
|
+
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)) }),
|
|
692
|
+
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) }),
|
|
693
|
+
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)) }),
|
|
694
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex flex-col gap-0.5", children: [
|
|
695
|
+
"Applies to",
|
|
696
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { "aria-label": "Applies to", className: `${inputClass} font-mono`, value: appliesTo, onChange: (e) => setAppliesTo(e.target.value) })
|
|
697
|
+
] }),
|
|
698
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex justify-between gap-2", children: [
|
|
699
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: buttonClass, onClick: () => setAppliesTo(selectionText(state.selectedRange)), children: "Use selection" }),
|
|
700
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: buttonClass, disabled: !ranges, onClick: add, children: "Add rule" })
|
|
701
|
+
] })
|
|
702
|
+
] })
|
|
703
|
+
] });
|
|
704
|
+
};
|
|
705
|
+
var SpreadsheetDataValidationPanel = ({ state, onClose }) => {
|
|
706
|
+
const rules = state.document.table?.dataValidations ?? [];
|
|
707
|
+
const [type, setType] = (0, import_react5.useState)("list");
|
|
708
|
+
const [operator, setOperator] = (0, import_react5.useState)("between");
|
|
709
|
+
const [formula1, setFormula1] = (0, import_react5.useState)("");
|
|
710
|
+
const [formula2, setFormula2] = (0, import_react5.useState)("");
|
|
711
|
+
const [errorStyle, setErrorStyle] = (0, import_react5.useState)("stop");
|
|
712
|
+
const [errorMessage, setErrorMessage] = (0, import_react5.useState)("");
|
|
713
|
+
const [inputMessage, setInputMessage] = (0, import_react5.useState)("");
|
|
714
|
+
const [ignoreBlank, setIgnoreBlank] = (0, import_react5.useState)(true);
|
|
715
|
+
const [appliesTo, setAppliesTo] = (0, import_react5.useState)(() => selectionText(state.selectedRange));
|
|
716
|
+
const ranges = (0, import_spreadsheet4.parseRuleRanges)(appliesTo);
|
|
717
|
+
const usesOperator = type === "whole" || type === "decimal" || type === "date" || type === "time" || type === "textLength";
|
|
718
|
+
const needsTwo = usesOperator && (operator === "between" || operator === "notBetween");
|
|
719
|
+
const circles = state.controller.getInvalidDataCircles();
|
|
720
|
+
const apply = () => {
|
|
721
|
+
if (!ranges) return;
|
|
722
|
+
state.addDataValidation({
|
|
723
|
+
ranges,
|
|
724
|
+
type,
|
|
725
|
+
...usesOperator ? { operator } : {},
|
|
726
|
+
...formula1 ? { formula1 } : {},
|
|
727
|
+
...needsTwo && formula2 ? { formula2 } : {},
|
|
728
|
+
errorStyle,
|
|
729
|
+
...errorMessage ? { errorMessage } : {},
|
|
730
|
+
...inputMessage ? { inputMessage } : {},
|
|
731
|
+
ignoreBlank
|
|
732
|
+
});
|
|
733
|
+
};
|
|
734
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(PanelFrame, { title: "Data validation", label: "Data validation rules", onClose, children: [
|
|
735
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("ul", { className: "mb-3 flex flex-col gap-1", "aria-label": "Validation rules", children: [
|
|
736
|
+
rules.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { className: "text-muted-foreground", children: "No validation on this sheet." }),
|
|
737
|
+
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: [
|
|
738
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "min-w-0 flex-1", children: [
|
|
739
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "truncate", title: (0, import_spreadsheet4.describeDataValidation)(rule), children: (0, import_spreadsheet4.describeDataValidation)(rule) }),
|
|
740
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "truncate font-mono text-[10px] text-muted-foreground", children: [
|
|
741
|
+
(0, import_spreadsheet4.formatRuleRanges)(rule.ranges),
|
|
742
|
+
" \xB7 ",
|
|
743
|
+
rule.errorStyle ?? "stop"
|
|
744
|
+
] })
|
|
745
|
+
] }),
|
|
746
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", "aria-label": "Delete rule", className: buttonClass, onClick: () => state.deleteDataValidation(rule.id), children: "\u2715" })
|
|
747
|
+
] }, rule.id))
|
|
748
|
+
] }),
|
|
749
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("fieldset", { className: "flex flex-col gap-1.5 border-t border-border pt-2", children: [
|
|
750
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("legend", { className: "sr-only", children: "New validation" }),
|
|
751
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex flex-col gap-0.5", children: [
|
|
752
|
+
"Allow",
|
|
753
|
+
/* @__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)) })
|
|
754
|
+
] }),
|
|
755
|
+
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)) }),
|
|
756
|
+
type !== "any" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
757
|
+
"input",
|
|
758
|
+
{
|
|
759
|
+
"aria-label": type === "list" ? "Source" : type === "custom" ? "Formula" : needsTwo ? "Minimum" : "Value",
|
|
760
|
+
placeholder: type === "list" ? "Low,Medium,High or =$H$1:$H$5" : type === "custom" ? "=A1>0" : "Value or =formula",
|
|
761
|
+
className: inputClass,
|
|
762
|
+
value: formula1,
|
|
763
|
+
onChange: (e) => setFormula1(e.target.value)
|
|
764
|
+
}
|
|
765
|
+
),
|
|
766
|
+
needsTwo && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { "aria-label": "Maximum", placeholder: "Maximum", className: inputClass, value: formula2, onChange: (e) => setFormula2(e.target.value) }),
|
|
767
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex items-center gap-1", children: [
|
|
768
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "checkbox", checked: ignoreBlank, onChange: (e) => setIgnoreBlank(e.target.checked) }),
|
|
769
|
+
" Ignore blank"
|
|
770
|
+
] }),
|
|
771
|
+
/* @__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) }),
|
|
772
|
+
/* @__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)) }) }),
|
|
773
|
+
/* @__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) }),
|
|
774
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex flex-col gap-0.5", children: [
|
|
775
|
+
"Applies to",
|
|
776
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { "aria-label": "Applies to", className: `${inputClass} font-mono`, value: appliesTo, onChange: (e) => setAppliesTo(e.target.value) })
|
|
777
|
+
] }),
|
|
778
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-wrap justify-between gap-2", children: [
|
|
779
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: buttonClass, onClick: () => setAppliesTo(selectionText(state.selectedRange)), children: "Use selection" }),
|
|
780
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: buttonClass, disabled: !ranges, onClick: () => ranges && ranges.forEach((r) => state.clearDataValidation(r)), children: "Clear" }),
|
|
781
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: buttonClass, disabled: !ranges, onClick: apply, children: "Apply" })
|
|
782
|
+
] }),
|
|
783
|
+
/* @__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" })
|
|
784
|
+
] })
|
|
785
|
+
] });
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
// src/spreadsheet/FormulaHints.tsx
|
|
789
|
+
var import_react6 = require("react");
|
|
790
|
+
var import_spreadsheet5 = require("@pixerate/editor/spreadsheet");
|
|
791
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
792
|
+
var FormulaHints = ({ inputRef, value, onAccept, limit = 8, names }) => {
|
|
793
|
+
const [caret, setCaret] = (0, import_react6.useState)(null);
|
|
794
|
+
const [focused, setFocused] = (0, import_react6.useState)(false);
|
|
795
|
+
const [selected, setSelected] = (0, import_react6.useState)(0);
|
|
796
|
+
const [dismissed, setDismissed] = (0, import_react6.useState)(false);
|
|
797
|
+
const [position, setPosition] = (0, import_react6.useState)(null);
|
|
798
|
+
const pendingCaret = (0, import_react6.useRef)(null);
|
|
799
|
+
const hints = (0, import_react6.useMemo)(
|
|
800
|
+
() => (0, import_spreadsheet5.getFormulaHints)(value, caret ?? value.length, { limit, names }),
|
|
801
|
+
[value, caret, limit, names]
|
|
422
802
|
);
|
|
423
803
|
const suggestions = dismissed ? [] : hints.suggestions;
|
|
424
804
|
const selectedIndex = Math.max(0, Math.min(selected, suggestions.length - 1));
|
|
425
805
|
const visible = focused && value.trimStart().startsWith("=") && (suggestions.length > 0 || !!hints.activeCall);
|
|
426
|
-
const latest = (0,
|
|
806
|
+
const latest = (0, import_react6.useRef)({ suggestions, selected: selectedIndex, value, caret, onAccept });
|
|
427
807
|
latest.current = { suggestions, selected: selectedIndex, value, caret, onAccept };
|
|
428
808
|
const accept = (index) => {
|
|
429
809
|
const { suggestions: list, value: current, caret: at, onAccept: commit } = latest.current;
|
|
430
810
|
const suggestion = list[index];
|
|
431
811
|
if (!suggestion) return;
|
|
432
|
-
const next = (0,
|
|
812
|
+
const next = (0, import_spreadsheet5.applyFormulaSuggestion)(current, at ?? current.length, suggestion);
|
|
433
813
|
pendingCaret.current = next.caret;
|
|
434
814
|
commit(next.value);
|
|
435
815
|
};
|
|
436
|
-
(0,
|
|
816
|
+
(0, import_react6.useEffect)(() => {
|
|
437
817
|
const input = inputRef.current;
|
|
438
818
|
if (!input) return;
|
|
439
819
|
const syncCaret = () => setCaret(input.selectionStart ?? input.value.length);
|
|
@@ -483,7 +863,7 @@ var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
|
|
|
483
863
|
input.removeEventListener("keydown", onKeyDown);
|
|
484
864
|
};
|
|
485
865
|
}, [inputRef]);
|
|
486
|
-
(0,
|
|
866
|
+
(0, import_react6.useLayoutEffect)(() => {
|
|
487
867
|
const input = inputRef.current;
|
|
488
868
|
const at = pendingCaret.current;
|
|
489
869
|
if (!input || at === null || input.value !== value) return;
|
|
@@ -492,7 +872,7 @@ var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
|
|
|
492
872
|
setCaret(at);
|
|
493
873
|
setSelected(0);
|
|
494
874
|
}, [value, inputRef]);
|
|
495
|
-
(0,
|
|
875
|
+
(0, import_react6.useLayoutEffect)(() => {
|
|
496
876
|
const input = inputRef.current;
|
|
497
877
|
if (!visible || !input) return;
|
|
498
878
|
const rect = input.getBoundingClientRect();
|
|
@@ -500,7 +880,7 @@ var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
|
|
|
500
880
|
}, [visible, value, inputRef]);
|
|
501
881
|
if (!visible) return null;
|
|
502
882
|
const { activeCall } = hints;
|
|
503
|
-
return /* @__PURE__ */ (0,
|
|
883
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
504
884
|
"div",
|
|
505
885
|
{
|
|
506
886
|
"data-formula-hints": "",
|
|
@@ -516,8 +896,8 @@ var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
|
|
|
516
896
|
},
|
|
517
897
|
onMouseDown: (e) => e.preventDefault(),
|
|
518
898
|
children: [
|
|
519
|
-
activeCall && /* @__PURE__ */ (0,
|
|
520
|
-
/* @__PURE__ */ (0,
|
|
899
|
+
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: [
|
|
900
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "font-mono", children: activeCall.parts.map((part, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
521
901
|
"span",
|
|
522
902
|
{
|
|
523
903
|
"data-active": part.active ? "" : void 0,
|
|
@@ -526,13 +906,13 @@ var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
|
|
|
526
906
|
},
|
|
527
907
|
i
|
|
528
908
|
)) }),
|
|
529
|
-
/* @__PURE__ */ (0,
|
|
530
|
-
/* @__PURE__ */ (0,
|
|
909
|
+
/* @__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: [
|
|
910
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "font-semibold", children: activeCall.argument.name }),
|
|
531
911
|
": ",
|
|
532
912
|
activeCall.argument.description
|
|
533
913
|
] }) : activeCall.description })
|
|
534
914
|
] }),
|
|
535
|
-
suggestions.length > 0 && /* @__PURE__ */ (0,
|
|
915
|
+
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)(
|
|
536
916
|
"li",
|
|
537
917
|
{
|
|
538
918
|
role: "option",
|
|
@@ -544,19 +924,162 @@ var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
|
|
|
544
924
|
accept(i);
|
|
545
925
|
},
|
|
546
926
|
children: [
|
|
547
|
-
/* @__PURE__ */ (0,
|
|
548
|
-
/* @__PURE__ */ (0,
|
|
927
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "font-mono font-semibold text-foreground", children: s.signature }),
|
|
928
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "text-muted-foreground truncate", children: s.description })
|
|
549
929
|
]
|
|
550
930
|
},
|
|
551
|
-
s.name
|
|
931
|
+
`${s.kind ?? "fn"}:${s.name}`
|
|
552
932
|
)) })
|
|
553
933
|
]
|
|
554
934
|
}
|
|
555
935
|
);
|
|
556
936
|
};
|
|
557
937
|
|
|
938
|
+
// src/spreadsheet/SheetTabs.tsx
|
|
939
|
+
var import_react7 = require("react");
|
|
940
|
+
var import_spreadsheet6 = require("@pixerate/editor/spreadsheet");
|
|
941
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
942
|
+
var SheetTabs = ({ state, readOnly = false, className = "" }) => {
|
|
943
|
+
const { controller, sheets } = state;
|
|
944
|
+
const [renaming, setRenaming] = (0, import_react7.useState)(null);
|
|
945
|
+
const [error, setError] = (0, import_react7.useState)(null);
|
|
946
|
+
const [menu, setMenu] = (0, import_react7.useState)(null);
|
|
947
|
+
const dragFrom = (0, import_react7.useRef)(null);
|
|
948
|
+
const [dropTarget, setDropTarget] = (0, import_react7.useState)(null);
|
|
949
|
+
const commitRename = (cancelOnError) => {
|
|
950
|
+
if (!renaming) return;
|
|
951
|
+
const sheet = sheets.find((s) => s.id === renaming.id);
|
|
952
|
+
const name = renaming.draft.trim();
|
|
953
|
+
if (!sheet || !name || name === sheet.name) {
|
|
954
|
+
setRenaming(null);
|
|
955
|
+
setError(null);
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
try {
|
|
959
|
+
state.renameSheet(renaming.id, name);
|
|
960
|
+
setRenaming(null);
|
|
961
|
+
setError(null);
|
|
962
|
+
} catch (err) {
|
|
963
|
+
setError(err?.message ?? String(err));
|
|
964
|
+
if (cancelOnError) setRenaming(null);
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
const dropAfter = (e) => {
|
|
968
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
969
|
+
return e.clientX > rect.left + rect.width / 2;
|
|
970
|
+
};
|
|
971
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: `flex flex-col ${className}`, "data-sheet-tabs": "", children: [
|
|
972
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
973
|
+
"div",
|
|
974
|
+
{
|
|
975
|
+
role: "tablist",
|
|
976
|
+
"aria-label": "Sheets",
|
|
977
|
+
className: "flex items-center gap-0.5 overflow-x-auto border-t border-border bg-muted px-1 py-1 text-xs",
|
|
978
|
+
children: [
|
|
979
|
+
sheets.map(
|
|
980
|
+
(sheet, index) => renaming?.id === sheet.id ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
981
|
+
"input",
|
|
982
|
+
{
|
|
983
|
+
"aria-label": "Sheet name",
|
|
984
|
+
autoFocus: true,
|
|
985
|
+
className: "w-32 rounded border border-primary bg-background px-2 py-0.5 text-xs outline-none",
|
|
986
|
+
value: renaming.draft,
|
|
987
|
+
onChange: (e) => setRenaming({ id: sheet.id, draft: e.target.value }),
|
|
988
|
+
onFocus: (e) => e.currentTarget.select(),
|
|
989
|
+
onKeyDown: (e) => {
|
|
990
|
+
e.stopPropagation();
|
|
991
|
+
if (e.key === "Enter") commitRename(false);
|
|
992
|
+
else if (e.key === "Escape") {
|
|
993
|
+
setRenaming(null);
|
|
994
|
+
setError(null);
|
|
995
|
+
}
|
|
996
|
+
},
|
|
997
|
+
onBlur: () => commitRename(true)
|
|
998
|
+
},
|
|
999
|
+
sheet.id
|
|
1000
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1001
|
+
"button",
|
|
1002
|
+
{
|
|
1003
|
+
type: "button",
|
|
1004
|
+
role: "tab",
|
|
1005
|
+
"aria-selected": sheet.active,
|
|
1006
|
+
"data-sheet-id": sheet.id,
|
|
1007
|
+
draggable: !readOnly,
|
|
1008
|
+
title: readOnly ? sheet.name : `${sheet.name} \u2014 double-click to rename, drag to move`,
|
|
1009
|
+
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" : ""}`,
|
|
1010
|
+
onClick: () => state.setActiveSheet(sheet.id),
|
|
1011
|
+
onDoubleClick: () => {
|
|
1012
|
+
if (readOnly) return;
|
|
1013
|
+
setError(null);
|
|
1014
|
+
setRenaming({ id: sheet.id, draft: sheet.name });
|
|
1015
|
+
},
|
|
1016
|
+
onContextMenu: (e) => {
|
|
1017
|
+
if (readOnly) return;
|
|
1018
|
+
e.preventDefault();
|
|
1019
|
+
setMenu({ x: e.clientX, y: e.clientY, items: (0, import_spreadsheet6.getSheetTabMenuItems)(controller, sheet.id, { onError: setError }) });
|
|
1020
|
+
},
|
|
1021
|
+
onDragStart: (e) => {
|
|
1022
|
+
dragFrom.current = index;
|
|
1023
|
+
e.dataTransfer?.setData("text/plain", sheet.id);
|
|
1024
|
+
if (e.dataTransfer) e.dataTransfer.effectAllowed = "move";
|
|
1025
|
+
},
|
|
1026
|
+
onDragOver: (e) => {
|
|
1027
|
+
if (dragFrom.current === null) return;
|
|
1028
|
+
e.preventDefault();
|
|
1029
|
+
const after = dropAfter(e);
|
|
1030
|
+
setDropTarget((prev) => prev?.index === index && prev.after === after ? prev : { index, after });
|
|
1031
|
+
},
|
|
1032
|
+
onDragLeave: () => setDropTarget(null),
|
|
1033
|
+
onDrop: (e) => {
|
|
1034
|
+
e.preventDefault();
|
|
1035
|
+
const from = dragFrom.current;
|
|
1036
|
+
dragFrom.current = null;
|
|
1037
|
+
setDropTarget(null);
|
|
1038
|
+
if (from === null) return;
|
|
1039
|
+
state.moveSheet(sheets[from].id, (0, import_spreadsheet6.sheetDropIndex)(from, index, dropAfter(e)));
|
|
1040
|
+
},
|
|
1041
|
+
onDragEnd: () => {
|
|
1042
|
+
dragFrom.current = null;
|
|
1043
|
+
setDropTarget(null);
|
|
1044
|
+
},
|
|
1045
|
+
children: sheet.name
|
|
1046
|
+
},
|
|
1047
|
+
sheet.id
|
|
1048
|
+
)
|
|
1049
|
+
),
|
|
1050
|
+
!readOnly && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1051
|
+
"button",
|
|
1052
|
+
{
|
|
1053
|
+
type: "button",
|
|
1054
|
+
"aria-label": "Add sheet",
|
|
1055
|
+
title: "Add sheet",
|
|
1056
|
+
className: "rounded px-2 py-0.5 text-muted-foreground hover:bg-background/60",
|
|
1057
|
+
onClick: () => {
|
|
1058
|
+
setError(null);
|
|
1059
|
+
state.addSheet();
|
|
1060
|
+
},
|
|
1061
|
+
children: "+"
|
|
1062
|
+
}
|
|
1063
|
+
)
|
|
1064
|
+
]
|
|
1065
|
+
}
|
|
1066
|
+
),
|
|
1067
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { role: "alert", className: "px-2 py-1 text-xs text-destructive", children: error }),
|
|
1068
|
+
menu && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1069
|
+
SpreadsheetContextMenu,
|
|
1070
|
+
{
|
|
1071
|
+
items: menu.items,
|
|
1072
|
+
x: menu.x,
|
|
1073
|
+
y: menu.y,
|
|
1074
|
+
ariaLabel: "Sheet actions",
|
|
1075
|
+
onClose: () => setMenu(null)
|
|
1076
|
+
}
|
|
1077
|
+
)
|
|
1078
|
+
] });
|
|
1079
|
+
};
|
|
1080
|
+
|
|
558
1081
|
// src/spreadsheet/SpreadsheetEditor.tsx
|
|
559
|
-
var
|
|
1082
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
560
1083
|
var COLUMN_HEADER_HEIGHT = 28;
|
|
561
1084
|
var ROW_HEADER_WIDTH = 48;
|
|
562
1085
|
function frozenCellStyle(viewRow, viewCol) {
|
|
@@ -569,7 +1092,7 @@ function frozenCellStyle(viewRow, viewCol) {
|
|
|
569
1092
|
background: "var(--background, #fff)"
|
|
570
1093
|
};
|
|
571
1094
|
}
|
|
572
|
-
var coordKey = (cell) => cell ? `${(0,
|
|
1095
|
+
var coordKey = (cell) => cell ? `${(0, import_spreadsheet7.indexToColName)(cell.col)}${cell.row + 1}` : "";
|
|
573
1096
|
function spillBorderStyle(range, row, col) {
|
|
574
1097
|
const line = "1px solid color-mix(in srgb, var(--primary, #6366f1) 60%, transparent)";
|
|
575
1098
|
return {
|
|
@@ -583,63 +1106,101 @@ function spillBorderStyle(range, row, col) {
|
|
|
583
1106
|
borderRight: col === range.endCol ? line : void 0
|
|
584
1107
|
};
|
|
585
1108
|
}
|
|
1109
|
+
function CellText({ cell, conditional }) {
|
|
1110
|
+
const shown = (0, import_spreadsheet7.formatCellForDisplay)(cell);
|
|
1111
|
+
const color = conditional?.color ?? (shown.color ? (0, import_spreadsheet7.numberFormatColorToCss)(shown.color) : void 0);
|
|
1112
|
+
const decorated = !!conditional?.dataBar || !!conditional?.icon;
|
|
1113
|
+
const text = /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1114
|
+
"span",
|
|
1115
|
+
{
|
|
1116
|
+
className: `truncate block max-w-full ${cell.error ? "text-destructive font-semibold" : ""}`,
|
|
1117
|
+
title: cell.error ? cell.errorMessage ?? cell.error : void 0,
|
|
1118
|
+
"data-numeric": shown.numeric ? "" : void 0,
|
|
1119
|
+
style: shown.numeric || color || decorated ? {
|
|
1120
|
+
...decorated ? { position: "relative" } : {},
|
|
1121
|
+
...conditional?.icon ? { flex: 1, minWidth: 0 } : {},
|
|
1122
|
+
...shown.numeric ? { textAlign: "right" } : {},
|
|
1123
|
+
...color ? { color } : {}
|
|
1124
|
+
} : void 0,
|
|
1125
|
+
children: conditional?.hideValue ? "" : shown.text
|
|
1126
|
+
}
|
|
1127
|
+
);
|
|
1128
|
+
if (!conditional || !decorated) return text;
|
|
1129
|
+
const icon = conditional.icon ? (0, import_spreadsheet7.getConditionalFormatIcon)(conditional.icon.set, conditional.icon.index) : null;
|
|
1130
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
1131
|
+
conditional.dataBar && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { "aria-hidden": "true", className: "spreadsheet-data-bar", style: (0, import_spreadsheet7.getDataBarCss)(conditional.dataBar) }),
|
|
1132
|
+
icon ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "flex items-center gap-1 min-w-0", style: { position: "relative" }, children: [
|
|
1133
|
+
/* @__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 }),
|
|
1134
|
+
text
|
|
1135
|
+
] }) : text
|
|
1136
|
+
] });
|
|
1137
|
+
}
|
|
586
1138
|
var SpreadsheetEditor = ({
|
|
587
1139
|
state: externalState,
|
|
588
1140
|
readOnly = false,
|
|
589
1141
|
customCellRenderer,
|
|
590
1142
|
onAddColumnClick,
|
|
591
1143
|
onColumnHeaderClick,
|
|
592
|
-
onColumnResize
|
|
1144
|
+
onColumnResize,
|
|
1145
|
+
showSheetTabs = false
|
|
593
1146
|
}) => {
|
|
594
1147
|
const defaultState = useSpreadsheetEditor();
|
|
595
1148
|
const state = externalState || defaultState;
|
|
596
|
-
const gridRef = (0,
|
|
597
|
-
const formulaBarRef = (0,
|
|
598
|
-
const cellInputRef = (0,
|
|
599
|
-
const [resizingColId, setResizingColId] = (0,
|
|
600
|
-
const [hoveredColId, setHoveredColId] = (0,
|
|
601
|
-
const [resizeStartX, setResizeStartX] = (0,
|
|
602
|
-
const [resizeStartWidth, setResizeStartWidth] = (0,
|
|
1149
|
+
const gridRef = (0, import_react8.useRef)(null);
|
|
1150
|
+
const formulaBarRef = (0, import_react8.useRef)(null);
|
|
1151
|
+
const cellInputRef = (0, import_react8.useRef)(null);
|
|
1152
|
+
const [resizingColId, setResizingColId] = (0, import_react8.useState)(null);
|
|
1153
|
+
const [hoveredColId, setHoveredColId] = (0, import_react8.useState)(null);
|
|
1154
|
+
const [resizeStartX, setResizeStartX] = (0, import_react8.useState)(0);
|
|
1155
|
+
const [resizeStartWidth, setResizeStartWidth] = (0, import_react8.useState)(0);
|
|
603
1156
|
const view = state.viewModel;
|
|
604
1157
|
const { copyShortcutFallback } = useSpreadsheetClipboard(gridRef, state, readOnly);
|
|
605
1158
|
const { fillTarget, startFill, extendFill } = useFillHandle(state);
|
|
606
|
-
const [menu, setMenu] = (0,
|
|
1159
|
+
const [menu, setMenu] = (0, import_react8.useState)(null);
|
|
1160
|
+
const [panel, setPanel] = (0, import_react8.useState)(null);
|
|
1161
|
+
const [listMenu, setListMenu] = (0, import_react8.useState)(null);
|
|
1162
|
+
const overlay = state.conditionalFormatOverlay;
|
|
607
1163
|
const filter = state.document.table?.filter;
|
|
608
|
-
const filteredColIds = (0,
|
|
1164
|
+
const filteredColIds = (0, import_react8.useMemo)(
|
|
609
1165
|
() => new Set(filter?.enabled ? filter.rules.map((r) => r.colId) : []),
|
|
610
1166
|
[filter]
|
|
611
1167
|
);
|
|
612
|
-
const handleCellContextMenu = (0,
|
|
1168
|
+
const handleCellContextMenu = (0, import_react8.useCallback)((rIdx, cIdx, e) => {
|
|
613
1169
|
if (readOnly) return;
|
|
614
1170
|
e.preventDefault();
|
|
615
1171
|
if (state.editingCell) state.commitEditing();
|
|
616
|
-
if (!state.selectedRange || !(0,
|
|
1172
|
+
if (!state.selectedRange || !(0, import_spreadsheet7.rangeContains)(state.selectedRange, rIdx, cIdx)) {
|
|
617
1173
|
state.selectCell(rIdx, cIdx);
|
|
618
1174
|
}
|
|
619
|
-
setMenu({ x: e.clientX, y: e.clientY, items: (0,
|
|
1175
|
+
setMenu({ x: e.clientX, y: e.clientY, items: (0, import_spreadsheet7.getSpreadsheetMenuItems)(state.controller, { row: rIdx, col: cIdx }) });
|
|
620
1176
|
}, [readOnly, state]);
|
|
621
|
-
const
|
|
1177
|
+
const nameSuggestions = (0, import_react8.useMemo)(
|
|
1178
|
+
() => state.controller.getNameSuggestions(),
|
|
1179
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1180
|
+
[state.controller, state.document]
|
|
1181
|
+
);
|
|
1182
|
+
const activeRawValue = (0, import_react8.useMemo)(() => {
|
|
622
1183
|
if (!state.activeCell) return "";
|
|
623
1184
|
const row = state.document.rows[state.activeCell.row];
|
|
624
1185
|
const col = state.document.columns[state.activeCell.col];
|
|
625
1186
|
if (!row || !col) return "";
|
|
626
1187
|
return state.getCell(row.id, col.id).raw;
|
|
627
1188
|
}, [state.activeCell, state.document]);
|
|
628
|
-
const activeSpill = (0,
|
|
1189
|
+
const activeSpill = (0, import_react8.useMemo)(
|
|
629
1190
|
() => state.activeCell ? state.controller.getSpillAt(state.activeCell.row, state.activeCell.col) : null,
|
|
630
1191
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
631
1192
|
[state.controller, state.activeCell, state.document]
|
|
632
1193
|
);
|
|
633
1194
|
const spillFormula = activeSpill && !activeRawValue && activeSpill.key !== coordKey(state.activeCell) ? activeSpill.formula : "";
|
|
634
|
-
const coordLabel = (0,
|
|
1195
|
+
const coordLabel = (0, import_react8.useMemo)(() => {
|
|
635
1196
|
if (!state.activeCell) return "--";
|
|
636
|
-
const colName = (0,
|
|
1197
|
+
const colName = (0, import_spreadsheet7.indexToColName)(state.activeCell.col);
|
|
637
1198
|
return `${colName}${state.activeCell.row + 1}`;
|
|
638
1199
|
}, [state.activeCell]);
|
|
639
|
-
const [formulaDraft, setFormulaDraft] = (0,
|
|
1200
|
+
const [formulaDraft, setFormulaDraft] = (0, import_react8.useState)(null);
|
|
640
1201
|
const activeColumn = state.activeCell ? state.document.columns[state.activeCell.col] : void 0;
|
|
641
1202
|
const formulaBarReadOnly = readOnly || !state.activeCell || !!activeColumn?.readOnly;
|
|
642
|
-
(0,
|
|
1203
|
+
(0, import_react8.useEffect)(() => {
|
|
643
1204
|
setFormulaDraft(null);
|
|
644
1205
|
}, [state.activeCell?.row, state.activeCell?.col]);
|
|
645
1206
|
const commitFormulaBar = () => {
|
|
@@ -651,34 +1212,34 @@ var SpreadsheetEditor = ({
|
|
|
651
1212
|
const row = state.document.rows[state.activeCell.row];
|
|
652
1213
|
const col = state.document.columns[state.activeCell.col];
|
|
653
1214
|
if (row && col && !formulaBarReadOnly && formulaDraft !== activeRawValue) {
|
|
654
|
-
state.
|
|
1215
|
+
state.commitCellInput(row.id, col.id, formulaDraft);
|
|
655
1216
|
}
|
|
656
1217
|
setFormulaDraft(null);
|
|
657
1218
|
};
|
|
658
|
-
const handleCellClick = (0,
|
|
1219
|
+
const handleCellClick = (0, import_react8.useCallback)((rIdx, cIdx, e) => {
|
|
659
1220
|
if (state.editingCell && (state.editingCell.row !== rIdx || state.editingCell.col !== cIdx)) {
|
|
660
1221
|
state.commitEditing();
|
|
661
1222
|
}
|
|
662
1223
|
state.selectCell(rIdx, cIdx, e.shiftKey);
|
|
663
1224
|
}, [state]);
|
|
664
|
-
const handleCellDblClick = (0,
|
|
1225
|
+
const handleCellDblClick = (0, import_react8.useCallback)((rIdx, cIdx) => {
|
|
665
1226
|
if (readOnly) return;
|
|
666
1227
|
const col = state.document.columns[cIdx];
|
|
667
1228
|
if (col?.readOnly) return;
|
|
668
1229
|
state.startEditing(rIdx, cIdx);
|
|
669
1230
|
}, [readOnly, state]);
|
|
670
|
-
const handleKeyDown = (0,
|
|
1231
|
+
const handleKeyDown = (0, import_react8.useCallback)((e) => {
|
|
671
1232
|
if (state.editingCell) {
|
|
672
1233
|
if (e.key === "Enter") {
|
|
673
1234
|
e.preventDefault();
|
|
674
|
-
state.commitEditing();
|
|
1235
|
+
if (state.commitEditing().status !== "committed") return;
|
|
675
1236
|
state.moveCursor("down");
|
|
676
1237
|
} else if (e.key === "Escape") {
|
|
677
1238
|
e.preventDefault();
|
|
678
1239
|
state.cancelEditing();
|
|
679
1240
|
} else if (e.key === "Tab") {
|
|
680
1241
|
e.preventDefault();
|
|
681
|
-
state.commitEditing();
|
|
1242
|
+
if (state.commitEditing().status !== "committed") return;
|
|
682
1243
|
state.moveCursor(e.shiftKey ? "left" : "right");
|
|
683
1244
|
} else {
|
|
684
1245
|
return;
|
|
@@ -687,7 +1248,10 @@ var SpreadsheetEditor = ({
|
|
|
687
1248
|
return;
|
|
688
1249
|
}
|
|
689
1250
|
if (!state.activeCell) return;
|
|
690
|
-
if (e.key === "
|
|
1251
|
+
if (e.altKey && e.key === "ArrowDown") {
|
|
1252
|
+
e.preventDefault();
|
|
1253
|
+
openListMenu(state.activeCell.row, state.activeCell.col);
|
|
1254
|
+
} else if (e.key === "ArrowUp") {
|
|
691
1255
|
e.preventDefault();
|
|
692
1256
|
state.moveCursor("up", e.shiftKey);
|
|
693
1257
|
} else if (e.key === "ArrowDown") {
|
|
@@ -736,11 +1300,53 @@ var SpreadsheetEditor = ({
|
|
|
736
1300
|
}
|
|
737
1301
|
}
|
|
738
1302
|
}, [readOnly, state, copyShortcutFallback]);
|
|
739
|
-
const
|
|
1303
|
+
const activeListValues = (0, import_react8.useMemo)(
|
|
1304
|
+
() => state.activeCell && !readOnly ? state.controller.getValidationListValues(state.activeCell.row, state.activeCell.col) : null,
|
|
1305
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1306
|
+
[state.controller, state.activeCell, state.document, readOnly]
|
|
1307
|
+
);
|
|
1308
|
+
const activeRule = (0, import_react8.useMemo)(
|
|
1309
|
+
() => state.activeCell ? state.controller.getDataValidationAt(state.activeCell.row, state.activeCell.col) : void 0,
|
|
1310
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1311
|
+
[state.controller, state.activeCell, state.document]
|
|
1312
|
+
);
|
|
1313
|
+
const inputMessage = activeRule && activeRule.showInputMessage !== false && (activeRule.inputTitle || activeRule.inputMessage) ? activeRule : null;
|
|
1314
|
+
function openListMenu(row, col, anchor) {
|
|
1315
|
+
if (readOnly || state.document.columns[col]?.readOnly) return;
|
|
1316
|
+
const values = state.controller.getValidationListValues(row, col);
|
|
1317
|
+
if (!values) return;
|
|
1318
|
+
const td = anchor ?? gridRef.current?.querySelector(`td[data-row="${row}"][data-col="${col}"]`);
|
|
1319
|
+
const rect = td?.getBoundingClientRect();
|
|
1320
|
+
setListMenu({ x: rect ? rect.left : 0, y: rect ? rect.bottom : 0, row, col, values });
|
|
1321
|
+
}
|
|
1322
|
+
(0, import_react8.useEffect)(() => {
|
|
1323
|
+
if (!listMenu) return;
|
|
1324
|
+
const close = (e) => {
|
|
1325
|
+
if (!e.target?.closest?.('[aria-label="Allowed values"], .spreadsheet-list-arrow')) setListMenu(null);
|
|
1326
|
+
};
|
|
1327
|
+
window.addEventListener("mousedown", close);
|
|
1328
|
+
return () => window.removeEventListener("mousedown", close);
|
|
1329
|
+
}, [listMenu]);
|
|
1330
|
+
const chooseListValue = (value) => {
|
|
1331
|
+
if (!listMenu) return;
|
|
1332
|
+
const row = state.document.rows[listMenu.row];
|
|
1333
|
+
const col = state.document.columns[listMenu.col];
|
|
1334
|
+
setListMenu(null);
|
|
1335
|
+
if (row && col) state.commitCellInput(row.id, col.id, value);
|
|
1336
|
+
gridRef.current?.focus();
|
|
1337
|
+
};
|
|
1338
|
+
const resolveValidation = (action) => {
|
|
1339
|
+
state.resolveValidationPrompt(action);
|
|
1340
|
+
requestAnimationFrame(() => {
|
|
1341
|
+
if (cellInputRef.current) cellInputRef.current.focus();
|
|
1342
|
+
else gridRef.current?.focus();
|
|
1343
|
+
});
|
|
1344
|
+
};
|
|
1345
|
+
const stateRef = (0, import_react8.useRef)(state);
|
|
740
1346
|
stateRef.current = state;
|
|
741
|
-
const onColumnResizeRef = (0,
|
|
1347
|
+
const onColumnResizeRef = (0, import_react8.useRef)(onColumnResize);
|
|
742
1348
|
onColumnResizeRef.current = onColumnResize;
|
|
743
|
-
(0,
|
|
1349
|
+
(0, import_react8.useEffect)(() => {
|
|
744
1350
|
if (!resizingColId) return;
|
|
745
1351
|
if (typeof document !== "undefined") {
|
|
746
1352
|
document.body.style.cursor = "col-resize";
|
|
@@ -791,7 +1397,7 @@ var SpreadsheetEditor = ({
|
|
|
791
1397
|
window.removeEventListener("mouseup", handleMouseUp);
|
|
792
1398
|
};
|
|
793
1399
|
}, [resizingColId, resizeStartX, resizeStartWidth]);
|
|
794
|
-
const measureElementContentWidth = (0,
|
|
1400
|
+
const measureElementContentWidth = (0, import_react8.useCallback)((el, container) => {
|
|
795
1401
|
if (typeof document === "undefined") return 0;
|
|
796
1402
|
let maxW = el.scrollWidth || 0;
|
|
797
1403
|
const firstChild = el.firstElementChild;
|
|
@@ -839,7 +1445,7 @@ var SpreadsheetEditor = ({
|
|
|
839
1445
|
}
|
|
840
1446
|
return maxW;
|
|
841
1447
|
}, []);
|
|
842
|
-
const autoFitColumn = (0,
|
|
1448
|
+
const autoFitColumn = (0, import_react8.useCallback)((colId, colIndex) => {
|
|
843
1449
|
const col = state.document.columns.find((c) => c.id === colId);
|
|
844
1450
|
if (!col) return;
|
|
845
1451
|
let calculatedWidth = 0;
|
|
@@ -875,12 +1481,12 @@ var SpreadsheetEditor = ({
|
|
|
875
1481
|
onColumnResize(updatedCol.id, updatedCol.width || 130);
|
|
876
1482
|
}
|
|
877
1483
|
}, [state, measureElementContentWidth, onColumnResize]);
|
|
878
|
-
const handleResizeDblClick = (0,
|
|
1484
|
+
const handleResizeDblClick = (0, import_react8.useCallback)((colId, colIndex, e) => {
|
|
879
1485
|
e.preventDefault();
|
|
880
1486
|
e.stopPropagation();
|
|
881
1487
|
autoFitColumn(colId, colIndex);
|
|
882
1488
|
}, [autoFitColumn]);
|
|
883
|
-
const handleHeaderDblClick = (0,
|
|
1489
|
+
const handleHeaderDblClick = (0, import_react8.useCallback)((colId, colIndex, e) => {
|
|
884
1490
|
const target = e.currentTarget;
|
|
885
1491
|
if (target) {
|
|
886
1492
|
const rect = target.getBoundingClientRect();
|
|
@@ -902,7 +1508,7 @@ var SpreadsheetEditor = ({
|
|
|
902
1508
|
const isActiveCell = (rIdx, cIdx) => {
|
|
903
1509
|
return state.activeCell?.row === rIdx && state.activeCell?.col === cIdx;
|
|
904
1510
|
};
|
|
905
|
-
|
|
1511
|
+
const grid = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
906
1512
|
"div",
|
|
907
1513
|
{
|
|
908
1514
|
ref: gridRef,
|
|
@@ -912,10 +1518,10 @@ var SpreadsheetEditor = ({
|
|
|
912
1518
|
role: "grid",
|
|
913
1519
|
"aria-label": "Spreadsheet",
|
|
914
1520
|
children: [
|
|
915
|
-
/* @__PURE__ */ (0,
|
|
916
|
-
/* @__PURE__ */ (0,
|
|
917
|
-
/* @__PURE__ */ (0,
|
|
918
|
-
/* @__PURE__ */ (0,
|
|
1521
|
+
/* @__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: [
|
|
1522
|
+
/* @__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 }),
|
|
1523
|
+
/* @__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" }) }),
|
|
1524
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
919
1525
|
"input",
|
|
920
1526
|
{
|
|
921
1527
|
ref: formulaBarRef,
|
|
@@ -951,19 +1557,24 @@ var SpreadsheetEditor = ({
|
|
|
951
1557
|
}
|
|
952
1558
|
}
|
|
953
1559
|
),
|
|
954
|
-
!formulaBarReadOnly && /* @__PURE__ */ (0,
|
|
1560
|
+
!formulaBarReadOnly && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
955
1561
|
FormulaHints,
|
|
956
1562
|
{
|
|
957
1563
|
inputRef: formulaBarRef,
|
|
958
1564
|
value: state.editingCell ? state.draftValue : formulaDraft ?? activeRawValue,
|
|
959
|
-
onAccept: (next) => state.editingCell ? state.setDraftValue(next) : setFormulaDraft(next)
|
|
1565
|
+
onAccept: (next) => state.editingCell ? state.setDraftValue(next) : setFormulaDraft(next),
|
|
1566
|
+
names: nameSuggestions
|
|
960
1567
|
}
|
|
961
1568
|
)
|
|
962
1569
|
] }),
|
|
963
|
-
/* @__PURE__ */ (0,
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1570
|
+
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: [
|
|
1571
|
+
inputMessage.inputTitle && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "mr-1 font-semibold text-foreground", children: inputMessage.inputTitle }),
|
|
1572
|
+
inputMessage.inputMessage
|
|
1573
|
+
] }),
|
|
1574
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "relative flex-1 overflow-auto", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("table", { className: "border-collapse table-fixed w-max min-w-full", children: [
|
|
1575
|
+
/* @__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: [
|
|
1576
|
+
/* @__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: "#" }),
|
|
1577
|
+
view.columns.map(({ column: col, index: cIdx, frozen, stickyLeft }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
967
1578
|
"th",
|
|
968
1579
|
{
|
|
969
1580
|
"data-col": cIdx,
|
|
@@ -978,16 +1589,16 @@ var SpreadsheetEditor = ({
|
|
|
978
1589
|
onClick: () => onColumnHeaderClick?.(col),
|
|
979
1590
|
onDoubleClick: (e) => handleHeaderDblClick(col.id, cIdx, e),
|
|
980
1591
|
children: [
|
|
981
|
-
/* @__PURE__ */ (0,
|
|
982
|
-
/* @__PURE__ */ (0,
|
|
983
|
-
filteredColIds.has(col.id) && /* @__PURE__ */ (0,
|
|
984
|
-
/* @__PURE__ */ (0,
|
|
1592
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center justify-between gap-1 min-w-0 overflow-hidden truncate", children: [
|
|
1593
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "truncate font-semibold text-foreground text-xs min-w-0", children: col.title }),
|
|
1594
|
+
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" }),
|
|
1595
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "text-[10px] text-muted-foreground/70 font-mono font-normal shrink-0", children: [
|
|
985
1596
|
"(",
|
|
986
1597
|
col.key,
|
|
987
1598
|
")"
|
|
988
1599
|
] })
|
|
989
1600
|
] }),
|
|
990
|
-
/* @__PURE__ */ (0,
|
|
1601
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
991
1602
|
"div",
|
|
992
1603
|
{
|
|
993
1604
|
className: "col-resize-handle",
|
|
@@ -1004,7 +1615,7 @@ var SpreadsheetEditor = ({
|
|
|
1004
1615
|
onDoubleClick: (e) => handleResizeDblClick(col.id, cIdx, e),
|
|
1005
1616
|
role: "separator",
|
|
1006
1617
|
"aria-orientation": "vertical",
|
|
1007
|
-
children: /* @__PURE__ */ (0,
|
|
1618
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1008
1619
|
"div",
|
|
1009
1620
|
{
|
|
1010
1621
|
className: "col-resize-handle-line",
|
|
@@ -1023,19 +1634,19 @@ var SpreadsheetEditor = ({
|
|
|
1023
1634
|
},
|
|
1024
1635
|
col.id
|
|
1025
1636
|
)),
|
|
1026
|
-
onAddColumnClick && /* @__PURE__ */ (0,
|
|
1637
|
+
onAddColumnClick && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1027
1638
|
"th",
|
|
1028
1639
|
{
|
|
1029
1640
|
className: "w-10 min-w-[2.5rem] px-1 text-center bg-muted/40 hover:bg-muted cursor-pointer transition-colors",
|
|
1030
1641
|
onClick: onAddColumnClick,
|
|
1031
1642
|
title: "Add Column",
|
|
1032
|
-
children: /* @__PURE__ */ (0,
|
|
1643
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-xs font-bold text-muted-foreground", children: "+" })
|
|
1033
1644
|
}
|
|
1034
1645
|
)
|
|
1035
1646
|
] }) }),
|
|
1036
|
-
/* @__PURE__ */ (0,
|
|
1647
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("tbody", { children: view.rows.map((viewRow) => {
|
|
1037
1648
|
if (viewRow.kind === "group") {
|
|
1038
|
-
return /* @__PURE__ */ (0,
|
|
1649
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1039
1650
|
SpreadsheetGroupRow,
|
|
1040
1651
|
{
|
|
1041
1652
|
group: viewRow.group,
|
|
@@ -1048,15 +1659,15 @@ var SpreadsheetEditor = ({
|
|
|
1048
1659
|
);
|
|
1049
1660
|
}
|
|
1050
1661
|
const displayRowIndex = viewRow.index;
|
|
1051
|
-
const selection = state.selectedRange ? (0,
|
|
1052
|
-
return /* @__PURE__ */ (0,
|
|
1662
|
+
const selection = state.selectedRange ? (0, import_spreadsheet7.normalizeRange)(state.selectedRange) : null;
|
|
1663
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1053
1664
|
"tr",
|
|
1054
1665
|
{
|
|
1055
1666
|
"data-row": displayRowIndex,
|
|
1056
1667
|
className: `border-b border-border hover:bg-muted/10 transition-colors ${viewRow.section !== "body" ? "bg-muted/30 font-semibold" : ""}`,
|
|
1057
1668
|
style: { height: viewRow.row.height || 32 },
|
|
1058
1669
|
children: [
|
|
1059
|
-
/* @__PURE__ */ (0,
|
|
1670
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1060
1671
|
"td",
|
|
1061
1672
|
{
|
|
1062
1673
|
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",
|
|
@@ -1075,9 +1686,13 @@ var SpreadsheetEditor = ({
|
|
|
1075
1686
|
const selected = isCellSelected(rIdx, cIdx);
|
|
1076
1687
|
const active = isActiveCell(rIdx, cIdx);
|
|
1077
1688
|
const isEditing = state.editingCell?.row === rIdx && state.editingCell?.col === cIdx;
|
|
1078
|
-
const
|
|
1079
|
-
const
|
|
1080
|
-
|
|
1689
|
+
const conditional = overlay.get(rIdx, cIdx);
|
|
1690
|
+
const conditionalCss = (0, import_spreadsheet7.getConditionalFormatCss)(conditional);
|
|
1691
|
+
const circled = state.controller.isInvalidDataCircled(rIdx, cIdx);
|
|
1692
|
+
const showListArrow = active && !isEditing && !!activeListValues;
|
|
1693
|
+
const inFillPreview = !!fillTarget && !selected && (0, import_spreadsheet7.rangeContains)(fillTarget, displayRowIndex, viewCol.index);
|
|
1694
|
+
const showFillHandle = !readOnly && !isEditing && !!selection && (span.merge ? (0, import_spreadsheet7.rangeContains)(span.merge, selection.endRow, selection.endCol) : selection.endRow === rIdx && selection.endCol === cIdx);
|
|
1695
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1081
1696
|
"td",
|
|
1082
1697
|
{
|
|
1083
1698
|
"data-row": rIdx,
|
|
@@ -1089,15 +1704,19 @@ var SpreadsheetEditor = ({
|
|
|
1089
1704
|
maxWidth: 0,
|
|
1090
1705
|
overflow: "hidden",
|
|
1091
1706
|
...frozenCellStyle(viewRow, viewCol),
|
|
1707
|
+
...conditionalCss,
|
|
1708
|
+
// Keep the selection tint visible over a conditional fill.
|
|
1709
|
+
...conditionalCss.background && selected ? { boxShadow: "inset 0 0 0 9999px color-mix(in srgb, var(--primary, #6366f1) 12%, transparent)" } : {},
|
|
1092
1710
|
...inFillPreview ? { outline: "1px dashed var(--primary, #6366f1)", outlineOffset: -1 } : {}
|
|
1093
1711
|
},
|
|
1712
|
+
"data-cf-fill": conditional?.fill,
|
|
1094
1713
|
onClick: (e) => handleCellClick(rIdx, cIdx, e),
|
|
1095
1714
|
onDoubleClick: () => handleCellDblClick(rIdx, cIdx),
|
|
1096
1715
|
onContextMenu: (e) => handleCellContextMenu(rIdx, cIdx, e),
|
|
1097
1716
|
onMouseOver: () => extendFill(displayRowIndex, viewCol.index),
|
|
1098
1717
|
children: [
|
|
1099
|
-
isEditing ? /* @__PURE__ */ (0,
|
|
1100
|
-
/* @__PURE__ */ (0,
|
|
1718
|
+
isEditing ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
1719
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1101
1720
|
"input",
|
|
1102
1721
|
{
|
|
1103
1722
|
ref: cellInputRef,
|
|
@@ -1113,17 +1732,36 @@ var SpreadsheetEditor = ({
|
|
|
1113
1732
|
autoFocus: true
|
|
1114
1733
|
}
|
|
1115
1734
|
),
|
|
1116
|
-
/* @__PURE__ */ (0,
|
|
1117
|
-
] }) : customCellRenderer ? customCellRenderer({ row, col, cell }) : /* @__PURE__ */ (0,
|
|
1735
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(FormulaHints, { inputRef: cellInputRef, value: state.draftValue, onAccept: state.setDraftValue, names: nameSuggestions })
|
|
1736
|
+
] }) : customCellRenderer ? customCellRenderer({ row, col, cell }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CellText, { cell, conditional }),
|
|
1737
|
+
circled && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1118
1738
|
"span",
|
|
1119
1739
|
{
|
|
1120
|
-
className:
|
|
1121
|
-
|
|
1122
|
-
|
|
1740
|
+
className: "spreadsheet-invalid-circle",
|
|
1741
|
+
"aria-label": "Invalid value",
|
|
1742
|
+
style: { position: "absolute", inset: "1px 2px", border: "2px solid #e11d48", borderRadius: "50%", pointerEvents: "none", zIndex: 6 }
|
|
1123
1743
|
}
|
|
1124
1744
|
),
|
|
1125
|
-
|
|
1126
|
-
|
|
1745
|
+
showListArrow && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1746
|
+
"button",
|
|
1747
|
+
{
|
|
1748
|
+
type: "button",
|
|
1749
|
+
className: "spreadsheet-list-arrow",
|
|
1750
|
+
"aria-label": "Show allowed values",
|
|
1751
|
+
"aria-haspopup": "listbox",
|
|
1752
|
+
tabIndex: -1,
|
|
1753
|
+
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 },
|
|
1754
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
1755
|
+
onClick: (e) => {
|
|
1756
|
+
e.stopPropagation();
|
|
1757
|
+
if (listMenu) setListMenu(null);
|
|
1758
|
+
else openListMenu(rIdx, cIdx, e.currentTarget.parentElement);
|
|
1759
|
+
},
|
|
1760
|
+
children: "\u25BE"
|
|
1761
|
+
}
|
|
1762
|
+
),
|
|
1763
|
+
activeSpill && (0, import_spreadsheet7.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) }),
|
|
1764
|
+
showFillHandle && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1127
1765
|
"div",
|
|
1128
1766
|
{
|
|
1129
1767
|
className: "spreadsheet-fill-handle",
|
|
@@ -1138,14 +1776,14 @@ var SpreadsheetEditor = ({
|
|
|
1138
1776
|
viewCol.column.id
|
|
1139
1777
|
);
|
|
1140
1778
|
}),
|
|
1141
|
-
onAddColumnClick && /* @__PURE__ */ (0,
|
|
1779
|
+
onAddColumnClick && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { className: "w-10 min-w-[2.5rem] bg-muted/10 border-r border-border" })
|
|
1142
1780
|
]
|
|
1143
1781
|
},
|
|
1144
1782
|
viewRow.row.id
|
|
1145
1783
|
);
|
|
1146
1784
|
}) })
|
|
1147
1785
|
] }) }),
|
|
1148
|
-
menu && /* @__PURE__ */ (0,
|
|
1786
|
+
menu && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1149
1787
|
SpreadsheetContextMenu,
|
|
1150
1788
|
{
|
|
1151
1789
|
items: menu.items,
|
|
@@ -1154,10 +1792,45 @@ var SpreadsheetEditor = ({
|
|
|
1154
1792
|
onClose: () => {
|
|
1155
1793
|
setMenu(null);
|
|
1156
1794
|
gridRef.current?.focus();
|
|
1157
|
-
}
|
|
1795
|
+
},
|
|
1796
|
+
onOpenPanel: setPanel
|
|
1797
|
+
}
|
|
1798
|
+
),
|
|
1799
|
+
panel === "conditionalFormatting" && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SpreadsheetConditionalFormatPanel, { state, onClose: () => setPanel(null) }),
|
|
1800
|
+
panel === "dataValidation" && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SpreadsheetDataValidationPanel, { state, onClose: () => setPanel(null) }),
|
|
1801
|
+
listMenu && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1802
|
+
"div",
|
|
1803
|
+
{
|
|
1804
|
+
role: "listbox",
|
|
1805
|
+
"aria-label": "Allowed values",
|
|
1806
|
+
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",
|
|
1807
|
+
style: { left: listMenu.x, top: listMenu.y, background: "var(--popover, var(--background, #fff))" },
|
|
1808
|
+
onKeyDown: (e) => {
|
|
1809
|
+
e.stopPropagation();
|
|
1810
|
+
if (e.key === "Escape") {
|
|
1811
|
+
setListMenu(null);
|
|
1812
|
+
gridRef.current?.focus();
|
|
1813
|
+
}
|
|
1814
|
+
},
|
|
1815
|
+
children: [
|
|
1816
|
+
listMenu.values.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "px-3 py-1 text-muted-foreground", children: "No values" }),
|
|
1817
|
+
listMenu.values.map((value, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1818
|
+
"button",
|
|
1819
|
+
{
|
|
1820
|
+
type: "button",
|
|
1821
|
+
role: "option",
|
|
1822
|
+
"aria-selected": false,
|
|
1823
|
+
className: "block w-full px-3 py-1 text-left hover:bg-muted",
|
|
1824
|
+
onClick: () => chooseListValue(value),
|
|
1825
|
+
children: value
|
|
1826
|
+
},
|
|
1827
|
+
`${i}:${value}`
|
|
1828
|
+
))
|
|
1829
|
+
]
|
|
1158
1830
|
}
|
|
1159
1831
|
),
|
|
1160
|
-
/* @__PURE__ */ (0,
|
|
1832
|
+
state.validationPrompt && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SpreadsheetValidationDialog, { prompt: state.validationPrompt, onResolve: resolveValidation }),
|
|
1833
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("style", { children: `
|
|
1161
1834
|
.col-resize-handle:hover .col-resize-handle-line {
|
|
1162
1835
|
background-color: var(--primary, #6366f1);
|
|
1163
1836
|
opacity: 0.75;
|
|
@@ -1177,13 +1850,178 @@ var SpreadsheetEditor = ({
|
|
|
1177
1850
|
]
|
|
1178
1851
|
}
|
|
1179
1852
|
);
|
|
1853
|
+
if (!showSheetTabs) return grid;
|
|
1854
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex h-full w-full flex-col", children: [
|
|
1855
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "min-h-0 flex-1", children: grid }),
|
|
1856
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SheetTabs, { state, readOnly })
|
|
1857
|
+
] });
|
|
1858
|
+
};
|
|
1859
|
+
|
|
1860
|
+
// src/spreadsheet/NameManager.tsx
|
|
1861
|
+
var import_react9 = require("react");
|
|
1862
|
+
var import_spreadsheet8 = require("@pixerate/editor/spreadsheet");
|
|
1863
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1864
|
+
var EMPTY_DRAFT = { original: null, name: "", refersTo: "=", scope: "", comment: "" };
|
|
1865
|
+
var NameManager = ({ state, readOnly = false, onClose, className = "" }) => {
|
|
1866
|
+
const { controller, sheets } = state;
|
|
1867
|
+
const rows = (0, import_react9.useMemo)(
|
|
1868
|
+
() => (0, import_spreadsheet8.getNameManagerRows)(controller),
|
|
1869
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1870
|
+
[controller, state.document, state.names]
|
|
1871
|
+
);
|
|
1872
|
+
const [draft, setDraft] = (0, import_react9.useState)(EMPTY_DRAFT);
|
|
1873
|
+
const [error, setError] = (0, import_react9.useState)(null);
|
|
1874
|
+
const edit = (row) => {
|
|
1875
|
+
setError(null);
|
|
1876
|
+
setDraft({
|
|
1877
|
+
original: { name: row.name, sheetId: row.sheetId },
|
|
1878
|
+
name: row.name,
|
|
1879
|
+
refersTo: row.refersTo,
|
|
1880
|
+
scope: row.sheetId ?? "",
|
|
1881
|
+
comment: row.comment ?? ""
|
|
1882
|
+
});
|
|
1883
|
+
};
|
|
1884
|
+
const save = (e) => {
|
|
1885
|
+
e.preventDefault();
|
|
1886
|
+
const scope = draft.scope ? { sheet: draft.scope } : {};
|
|
1887
|
+
try {
|
|
1888
|
+
const invalid = controller.validateDefinedName(draft.name.trim());
|
|
1889
|
+
if (invalid) throw new Error(invalid);
|
|
1890
|
+
try {
|
|
1891
|
+
(0, import_spreadsheet8.parseFormula)(draft.refersTo.trim().startsWith("=") ? draft.refersTo.trim() : `=${draft.refersTo.trim()}`);
|
|
1892
|
+
} catch (err) {
|
|
1893
|
+
throw new Error(`There's a problem with this formula: ${err?.message ?? draft.refersTo}`);
|
|
1894
|
+
}
|
|
1895
|
+
const original = draft.original;
|
|
1896
|
+
if (original) {
|
|
1897
|
+
const originalScope = original.sheetId ? { sheet: original.sheetId } : {};
|
|
1898
|
+
if ((original.sheetId ?? "") !== draft.scope) {
|
|
1899
|
+
state.deleteName(original.name, originalScope);
|
|
1900
|
+
} else if (original.name !== draft.name) {
|
|
1901
|
+
state.renameName(original.name, draft.name.trim(), originalScope);
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
state.defineName(draft.name.trim(), draft.refersTo, { ...scope, comment: draft.comment || void 0 });
|
|
1905
|
+
setDraft(EMPTY_DRAFT);
|
|
1906
|
+
setError(null);
|
|
1907
|
+
} catch (err) {
|
|
1908
|
+
setError(err?.message ?? String(err));
|
|
1909
|
+
}
|
|
1910
|
+
};
|
|
1911
|
+
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: [
|
|
1912
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center justify-between", children: [
|
|
1913
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { className: "m-0 text-sm font-semibold", children: "Name Manager" }),
|
|
1914
|
+
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" })
|
|
1915
|
+
] }),
|
|
1916
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("table", { className: "w-full border-collapse", children: [
|
|
1917
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("tr", { className: "text-left text-muted-foreground", children: [
|
|
1918
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("th", { className: "px-1 font-medium", children: "Name" }),
|
|
1919
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("th", { className: "px-1 font-medium", children: "Value" }),
|
|
1920
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("th", { className: "px-1 font-medium", children: "Refers to" }),
|
|
1921
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("th", { className: "px-1 font-medium", children: "Scope" }),
|
|
1922
|
+
!readOnly && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("th", { className: "px-1" })
|
|
1923
|
+
] }) }),
|
|
1924
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("tbody", { children: [
|
|
1925
|
+
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." }) }),
|
|
1926
|
+
rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("tr", { "data-name": row.name, className: "border-t border-border", title: row.comment, children: [
|
|
1927
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("td", { className: "px-1 py-0.5 font-mono font-semibold", children: row.name }),
|
|
1928
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("td", { className: "px-1 py-0.5", children: row.value }),
|
|
1929
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("td", { className: "px-1 py-0.5 font-mono", children: row.refersTo }),
|
|
1930
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("td", { className: "px-1 py-0.5", children: row.scopeLabel }),
|
|
1931
|
+
!readOnly && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("td", { className: "whitespace-nowrap px-1 py-0.5 text-right", children: [
|
|
1932
|
+
/* @__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" }),
|
|
1933
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1934
|
+
"button",
|
|
1935
|
+
{
|
|
1936
|
+
type: "button",
|
|
1937
|
+
"aria-label": `Delete ${row.name}`,
|
|
1938
|
+
className: "rounded px-1 text-destructive hover:bg-muted",
|
|
1939
|
+
onClick: () => {
|
|
1940
|
+
state.deleteName(row.name, row.sheetId ? { sheet: row.sheetId } : {});
|
|
1941
|
+
if (draft.original?.name === row.name && (draft.original.sheetId ?? "") === (row.sheetId ?? "")) setDraft(EMPTY_DRAFT);
|
|
1942
|
+
},
|
|
1943
|
+
children: "Delete"
|
|
1944
|
+
}
|
|
1945
|
+
)
|
|
1946
|
+
] })
|
|
1947
|
+
] }, `${row.sheetId ?? ""}!${row.name}`))
|
|
1948
|
+
] })
|
|
1949
|
+
] }),
|
|
1950
|
+
!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: [
|
|
1951
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("label", { htmlFor: "pixerate-name-manager-name", children: "Name" }),
|
|
1952
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1953
|
+
"input",
|
|
1954
|
+
{
|
|
1955
|
+
id: "pixerate-name-manager-name",
|
|
1956
|
+
className: "rounded border border-border bg-background px-1 py-0.5",
|
|
1957
|
+
value: draft.name,
|
|
1958
|
+
onChange: (e) => setDraft({ ...draft, name: e.target.value })
|
|
1959
|
+
}
|
|
1960
|
+
),
|
|
1961
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("label", { htmlFor: "pixerate-name-manager-refers", children: "Refers to" }),
|
|
1962
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1963
|
+
"input",
|
|
1964
|
+
{
|
|
1965
|
+
id: "pixerate-name-manager-refers",
|
|
1966
|
+
className: "rounded border border-border bg-background px-1 py-0.5 font-mono",
|
|
1967
|
+
value: draft.refersTo,
|
|
1968
|
+
onChange: (e) => setDraft({ ...draft, refersTo: e.target.value })
|
|
1969
|
+
}
|
|
1970
|
+
),
|
|
1971
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("label", { htmlFor: "pixerate-name-manager-scope", children: "Scope" }),
|
|
1972
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
1973
|
+
"select",
|
|
1974
|
+
{
|
|
1975
|
+
id: "pixerate-name-manager-scope",
|
|
1976
|
+
className: "rounded border border-border bg-background px-1 py-0.5",
|
|
1977
|
+
value: draft.scope,
|
|
1978
|
+
onChange: (e) => setDraft({ ...draft, scope: e.target.value }),
|
|
1979
|
+
children: [
|
|
1980
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: "", children: "Workbook" }),
|
|
1981
|
+
sheets.map((sheet) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: sheet.id, children: sheet.name }, sheet.id))
|
|
1982
|
+
]
|
|
1983
|
+
}
|
|
1984
|
+
),
|
|
1985
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("label", { htmlFor: "pixerate-name-manager-comment", children: "Comment" }),
|
|
1986
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1987
|
+
"input",
|
|
1988
|
+
{
|
|
1989
|
+
id: "pixerate-name-manager-comment",
|
|
1990
|
+
className: "rounded border border-border bg-background px-1 py-0.5",
|
|
1991
|
+
value: draft.comment,
|
|
1992
|
+
onChange: (e) => setDraft({ ...draft, comment: e.target.value })
|
|
1993
|
+
}
|
|
1994
|
+
),
|
|
1995
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "col-span-2 flex gap-2", children: [
|
|
1996
|
+
/* @__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" }),
|
|
1997
|
+
draft.original && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1998
|
+
"button",
|
|
1999
|
+
{
|
|
2000
|
+
type: "button",
|
|
2001
|
+
className: "rounded px-2 py-0.5 hover:bg-muted",
|
|
2002
|
+
onClick: () => {
|
|
2003
|
+
setDraft(EMPTY_DRAFT);
|
|
2004
|
+
setError(null);
|
|
2005
|
+
},
|
|
2006
|
+
children: "Cancel"
|
|
2007
|
+
}
|
|
2008
|
+
)
|
|
2009
|
+
] })
|
|
2010
|
+
] }),
|
|
2011
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { role: "alert", className: "text-destructive", children: error })
|
|
2012
|
+
] });
|
|
1180
2013
|
};
|
|
1181
2014
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1182
2015
|
0 && (module.exports = {
|
|
1183
2016
|
FormulaHints,
|
|
2017
|
+
NameManager,
|
|
2018
|
+
SheetTabs,
|
|
2019
|
+
SpreadsheetConditionalFormatPanel,
|
|
1184
2020
|
SpreadsheetContextMenu,
|
|
2021
|
+
SpreadsheetDataValidationPanel,
|
|
1185
2022
|
SpreadsheetEditor,
|
|
1186
2023
|
SpreadsheetGroupRow,
|
|
2024
|
+
SpreadsheetValidationDialog,
|
|
1187
2025
|
useFillHandle,
|
|
1188
2026
|
useSpreadsheetClipboard,
|
|
1189
2027
|
useSpreadsheetEditor
|