@pixerate/editor-react 0.16.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 +2 -2
- package/dist/canvas/index.cjs +406 -142
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +66 -3
- package/dist/canvas/index.d.ts +66 -3
- package/dist/canvas/index.js +277 -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-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 +2372 -731
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +88 -6
- package/dist/index.d.ts +88 -6
- package/dist/index.js +334 -96
- package/dist/index.js.map +1 -1
- package/dist/spreadsheet/index.cjs +943 -112
- package/dist/spreadsheet/index.cjs.map +1 -1
- package/dist/spreadsheet/index.d.cts +119 -7
- package/dist/spreadsheet/index.d.ts +119 -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 +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,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,
|
|
@@ -274,7 +350,8 @@ var SpreadsheetContextMenu = ({ items, x, y, onClose }) => {
|
|
|
274
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" : ""}`,
|
|
275
351
|
onClick: () => {
|
|
276
352
|
onClose();
|
|
277
|
-
(
|
|
353
|
+
if (item.panel) onOpenPanel?.(item.panel);
|
|
354
|
+
else (0, import_spreadsheet2.runSpreadsheetMenuItem)(item);
|
|
278
355
|
},
|
|
279
356
|
children: item.label
|
|
280
357
|
}
|
|
@@ -406,35 +483,337 @@ function useFillHandle(state) {
|
|
|
406
483
|
return { fillTarget: drag?.target ?? null, isFilling: dragging, startFill, extendFill };
|
|
407
484
|
}
|
|
408
485
|
|
|
409
|
-
// src/spreadsheet/
|
|
486
|
+
// src/spreadsheet/SpreadsheetValidationDialog.tsx
|
|
410
487
|
var import_react4 = require("react");
|
|
411
|
-
var import_spreadsheet4 = require("@pixerate/editor/spreadsheet");
|
|
412
488
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
413
|
-
var
|
|
414
|
-
const
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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]
|
|
423
802
|
);
|
|
424
803
|
const suggestions = dismissed ? [] : hints.suggestions;
|
|
425
804
|
const selectedIndex = Math.max(0, Math.min(selected, suggestions.length - 1));
|
|
426
805
|
const visible = focused && value.trimStart().startsWith("=") && (suggestions.length > 0 || !!hints.activeCall);
|
|
427
|
-
const latest = (0,
|
|
806
|
+
const latest = (0, import_react6.useRef)({ suggestions, selected: selectedIndex, value, caret, onAccept });
|
|
428
807
|
latest.current = { suggestions, selected: selectedIndex, value, caret, onAccept };
|
|
429
808
|
const accept = (index) => {
|
|
430
809
|
const { suggestions: list, value: current, caret: at, onAccept: commit } = latest.current;
|
|
431
810
|
const suggestion = list[index];
|
|
432
811
|
if (!suggestion) return;
|
|
433
|
-
const next = (0,
|
|
812
|
+
const next = (0, import_spreadsheet5.applyFormulaSuggestion)(current, at ?? current.length, suggestion);
|
|
434
813
|
pendingCaret.current = next.caret;
|
|
435
814
|
commit(next.value);
|
|
436
815
|
};
|
|
437
|
-
(0,
|
|
816
|
+
(0, import_react6.useEffect)(() => {
|
|
438
817
|
const input = inputRef.current;
|
|
439
818
|
if (!input) return;
|
|
440
819
|
const syncCaret = () => setCaret(input.selectionStart ?? input.value.length);
|
|
@@ -484,7 +863,7 @@ var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
|
|
|
484
863
|
input.removeEventListener("keydown", onKeyDown);
|
|
485
864
|
};
|
|
486
865
|
}, [inputRef]);
|
|
487
|
-
(0,
|
|
866
|
+
(0, import_react6.useLayoutEffect)(() => {
|
|
488
867
|
const input = inputRef.current;
|
|
489
868
|
const at = pendingCaret.current;
|
|
490
869
|
if (!input || at === null || input.value !== value) return;
|
|
@@ -493,7 +872,7 @@ var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
|
|
|
493
872
|
setCaret(at);
|
|
494
873
|
setSelected(0);
|
|
495
874
|
}, [value, inputRef]);
|
|
496
|
-
(0,
|
|
875
|
+
(0, import_react6.useLayoutEffect)(() => {
|
|
497
876
|
const input = inputRef.current;
|
|
498
877
|
if (!visible || !input) return;
|
|
499
878
|
const rect = input.getBoundingClientRect();
|
|
@@ -501,7 +880,7 @@ var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
|
|
|
501
880
|
}, [visible, value, inputRef]);
|
|
502
881
|
if (!visible) return null;
|
|
503
882
|
const { activeCall } = hints;
|
|
504
|
-
return /* @__PURE__ */ (0,
|
|
883
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
505
884
|
"div",
|
|
506
885
|
{
|
|
507
886
|
"data-formula-hints": "",
|
|
@@ -517,8 +896,8 @@ var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
|
|
|
517
896
|
},
|
|
518
897
|
onMouseDown: (e) => e.preventDefault(),
|
|
519
898
|
children: [
|
|
520
|
-
activeCall && /* @__PURE__ */ (0,
|
|
521
|
-
/* @__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)(
|
|
522
901
|
"span",
|
|
523
902
|
{
|
|
524
903
|
"data-active": part.active ? "" : void 0,
|
|
@@ -527,13 +906,13 @@ var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
|
|
|
527
906
|
},
|
|
528
907
|
i
|
|
529
908
|
)) }),
|
|
530
|
-
/* @__PURE__ */ (0,
|
|
531
|
-
/* @__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 }),
|
|
532
911
|
": ",
|
|
533
912
|
activeCall.argument.description
|
|
534
913
|
] }) : activeCall.description })
|
|
535
914
|
] }),
|
|
536
|
-
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)(
|
|
537
916
|
"li",
|
|
538
917
|
{
|
|
539
918
|
role: "option",
|
|
@@ -545,19 +924,162 @@ var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
|
|
|
545
924
|
accept(i);
|
|
546
925
|
},
|
|
547
926
|
children: [
|
|
548
|
-
/* @__PURE__ */ (0,
|
|
549
|
-
/* @__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 })
|
|
550
929
|
]
|
|
551
930
|
},
|
|
552
|
-
s.name
|
|
931
|
+
`${s.kind ?? "fn"}:${s.name}`
|
|
553
932
|
)) })
|
|
554
933
|
]
|
|
555
934
|
}
|
|
556
935
|
);
|
|
557
936
|
};
|
|
558
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
|
+
|
|
559
1081
|
// src/spreadsheet/SpreadsheetEditor.tsx
|
|
560
|
-
var
|
|
1082
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
561
1083
|
var COLUMN_HEADER_HEIGHT = 28;
|
|
562
1084
|
var ROW_HEADER_WIDTH = 48;
|
|
563
1085
|
function frozenCellStyle(viewRow, viewCol) {
|
|
@@ -570,7 +1092,7 @@ function frozenCellStyle(viewRow, viewCol) {
|
|
|
570
1092
|
background: "var(--background, #fff)"
|
|
571
1093
|
};
|
|
572
1094
|
}
|
|
573
|
-
var coordKey = (cell) => cell ? `${(0,
|
|
1095
|
+
var coordKey = (cell) => cell ? `${(0, import_spreadsheet7.indexToColName)(cell.col)}${cell.row + 1}` : "";
|
|
574
1096
|
function spillBorderStyle(range, row, col) {
|
|
575
1097
|
const line = "1px solid color-mix(in srgb, var(--primary, #6366f1) 60%, transparent)";
|
|
576
1098
|
return {
|
|
@@ -584,18 +1106,34 @@ function spillBorderStyle(range, row, col) {
|
|
|
584
1106
|
borderRight: col === range.endCol ? line : void 0
|
|
585
1107
|
};
|
|
586
1108
|
}
|
|
587
|
-
function CellText({ cell }) {
|
|
588
|
-
const shown = (0,
|
|
589
|
-
|
|
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)(
|
|
590
1114
|
"span",
|
|
591
1115
|
{
|
|
592
1116
|
className: `truncate block max-w-full ${cell.error ? "text-destructive font-semibold" : ""}`,
|
|
593
1117
|
title: cell.error ? cell.errorMessage ?? cell.error : void 0,
|
|
594
1118
|
"data-numeric": shown.numeric ? "" : void 0,
|
|
595
|
-
style: shown.numeric ||
|
|
596
|
-
|
|
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
|
|
597
1126
|
}
|
|
598
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
|
+
] });
|
|
599
1137
|
}
|
|
600
1138
|
var SpreadsheetEditor = ({
|
|
601
1139
|
state: externalState,
|
|
@@ -603,57 +1141,66 @@ var SpreadsheetEditor = ({
|
|
|
603
1141
|
customCellRenderer,
|
|
604
1142
|
onAddColumnClick,
|
|
605
1143
|
onColumnHeaderClick,
|
|
606
|
-
onColumnResize
|
|
1144
|
+
onColumnResize,
|
|
1145
|
+
showSheetTabs = false
|
|
607
1146
|
}) => {
|
|
608
1147
|
const defaultState = useSpreadsheetEditor();
|
|
609
1148
|
const state = externalState || defaultState;
|
|
610
|
-
const gridRef = (0,
|
|
611
|
-
const formulaBarRef = (0,
|
|
612
|
-
const cellInputRef = (0,
|
|
613
|
-
const [resizingColId, setResizingColId] = (0,
|
|
614
|
-
const [hoveredColId, setHoveredColId] = (0,
|
|
615
|
-
const [resizeStartX, setResizeStartX] = (0,
|
|
616
|
-
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);
|
|
617
1156
|
const view = state.viewModel;
|
|
618
1157
|
const { copyShortcutFallback } = useSpreadsheetClipboard(gridRef, state, readOnly);
|
|
619
1158
|
const { fillTarget, startFill, extendFill } = useFillHandle(state);
|
|
620
|
-
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;
|
|
621
1163
|
const filter = state.document.table?.filter;
|
|
622
|
-
const filteredColIds = (0,
|
|
1164
|
+
const filteredColIds = (0, import_react8.useMemo)(
|
|
623
1165
|
() => new Set(filter?.enabled ? filter.rules.map((r) => r.colId) : []),
|
|
624
1166
|
[filter]
|
|
625
1167
|
);
|
|
626
|
-
const handleCellContextMenu = (0,
|
|
1168
|
+
const handleCellContextMenu = (0, import_react8.useCallback)((rIdx, cIdx, e) => {
|
|
627
1169
|
if (readOnly) return;
|
|
628
1170
|
e.preventDefault();
|
|
629
1171
|
if (state.editingCell) state.commitEditing();
|
|
630
|
-
if (!state.selectedRange || !(0,
|
|
1172
|
+
if (!state.selectedRange || !(0, import_spreadsheet7.rangeContains)(state.selectedRange, rIdx, cIdx)) {
|
|
631
1173
|
state.selectCell(rIdx, cIdx);
|
|
632
1174
|
}
|
|
633
|
-
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 }) });
|
|
634
1176
|
}, [readOnly, state]);
|
|
635
|
-
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)(() => {
|
|
636
1183
|
if (!state.activeCell) return "";
|
|
637
1184
|
const row = state.document.rows[state.activeCell.row];
|
|
638
1185
|
const col = state.document.columns[state.activeCell.col];
|
|
639
1186
|
if (!row || !col) return "";
|
|
640
1187
|
return state.getCell(row.id, col.id).raw;
|
|
641
1188
|
}, [state.activeCell, state.document]);
|
|
642
|
-
const activeSpill = (0,
|
|
1189
|
+
const activeSpill = (0, import_react8.useMemo)(
|
|
643
1190
|
() => state.activeCell ? state.controller.getSpillAt(state.activeCell.row, state.activeCell.col) : null,
|
|
644
1191
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
645
1192
|
[state.controller, state.activeCell, state.document]
|
|
646
1193
|
);
|
|
647
1194
|
const spillFormula = activeSpill && !activeRawValue && activeSpill.key !== coordKey(state.activeCell) ? activeSpill.formula : "";
|
|
648
|
-
const coordLabel = (0,
|
|
1195
|
+
const coordLabel = (0, import_react8.useMemo)(() => {
|
|
649
1196
|
if (!state.activeCell) return "--";
|
|
650
|
-
const colName = (0,
|
|
1197
|
+
const colName = (0, import_spreadsheet7.indexToColName)(state.activeCell.col);
|
|
651
1198
|
return `${colName}${state.activeCell.row + 1}`;
|
|
652
1199
|
}, [state.activeCell]);
|
|
653
|
-
const [formulaDraft, setFormulaDraft] = (0,
|
|
1200
|
+
const [formulaDraft, setFormulaDraft] = (0, import_react8.useState)(null);
|
|
654
1201
|
const activeColumn = state.activeCell ? state.document.columns[state.activeCell.col] : void 0;
|
|
655
1202
|
const formulaBarReadOnly = readOnly || !state.activeCell || !!activeColumn?.readOnly;
|
|
656
|
-
(0,
|
|
1203
|
+
(0, import_react8.useEffect)(() => {
|
|
657
1204
|
setFormulaDraft(null);
|
|
658
1205
|
}, [state.activeCell?.row, state.activeCell?.col]);
|
|
659
1206
|
const commitFormulaBar = () => {
|
|
@@ -665,34 +1212,34 @@ var SpreadsheetEditor = ({
|
|
|
665
1212
|
const row = state.document.rows[state.activeCell.row];
|
|
666
1213
|
const col = state.document.columns[state.activeCell.col];
|
|
667
1214
|
if (row && col && !formulaBarReadOnly && formulaDraft !== activeRawValue) {
|
|
668
|
-
state.
|
|
1215
|
+
state.commitCellInput(row.id, col.id, formulaDraft);
|
|
669
1216
|
}
|
|
670
1217
|
setFormulaDraft(null);
|
|
671
1218
|
};
|
|
672
|
-
const handleCellClick = (0,
|
|
1219
|
+
const handleCellClick = (0, import_react8.useCallback)((rIdx, cIdx, e) => {
|
|
673
1220
|
if (state.editingCell && (state.editingCell.row !== rIdx || state.editingCell.col !== cIdx)) {
|
|
674
1221
|
state.commitEditing();
|
|
675
1222
|
}
|
|
676
1223
|
state.selectCell(rIdx, cIdx, e.shiftKey);
|
|
677
1224
|
}, [state]);
|
|
678
|
-
const handleCellDblClick = (0,
|
|
1225
|
+
const handleCellDblClick = (0, import_react8.useCallback)((rIdx, cIdx) => {
|
|
679
1226
|
if (readOnly) return;
|
|
680
1227
|
const col = state.document.columns[cIdx];
|
|
681
1228
|
if (col?.readOnly) return;
|
|
682
1229
|
state.startEditing(rIdx, cIdx);
|
|
683
1230
|
}, [readOnly, state]);
|
|
684
|
-
const handleKeyDown = (0,
|
|
1231
|
+
const handleKeyDown = (0, import_react8.useCallback)((e) => {
|
|
685
1232
|
if (state.editingCell) {
|
|
686
1233
|
if (e.key === "Enter") {
|
|
687
1234
|
e.preventDefault();
|
|
688
|
-
state.commitEditing();
|
|
1235
|
+
if (state.commitEditing().status !== "committed") return;
|
|
689
1236
|
state.moveCursor("down");
|
|
690
1237
|
} else if (e.key === "Escape") {
|
|
691
1238
|
e.preventDefault();
|
|
692
1239
|
state.cancelEditing();
|
|
693
1240
|
} else if (e.key === "Tab") {
|
|
694
1241
|
e.preventDefault();
|
|
695
|
-
state.commitEditing();
|
|
1242
|
+
if (state.commitEditing().status !== "committed") return;
|
|
696
1243
|
state.moveCursor(e.shiftKey ? "left" : "right");
|
|
697
1244
|
} else {
|
|
698
1245
|
return;
|
|
@@ -701,7 +1248,10 @@ var SpreadsheetEditor = ({
|
|
|
701
1248
|
return;
|
|
702
1249
|
}
|
|
703
1250
|
if (!state.activeCell) return;
|
|
704
|
-
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") {
|
|
705
1255
|
e.preventDefault();
|
|
706
1256
|
state.moveCursor("up", e.shiftKey);
|
|
707
1257
|
} else if (e.key === "ArrowDown") {
|
|
@@ -750,11 +1300,53 @@ var SpreadsheetEditor = ({
|
|
|
750
1300
|
}
|
|
751
1301
|
}
|
|
752
1302
|
}, [readOnly, state, copyShortcutFallback]);
|
|
753
|
-
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);
|
|
754
1346
|
stateRef.current = state;
|
|
755
|
-
const onColumnResizeRef = (0,
|
|
1347
|
+
const onColumnResizeRef = (0, import_react8.useRef)(onColumnResize);
|
|
756
1348
|
onColumnResizeRef.current = onColumnResize;
|
|
757
|
-
(0,
|
|
1349
|
+
(0, import_react8.useEffect)(() => {
|
|
758
1350
|
if (!resizingColId) return;
|
|
759
1351
|
if (typeof document !== "undefined") {
|
|
760
1352
|
document.body.style.cursor = "col-resize";
|
|
@@ -805,7 +1397,7 @@ var SpreadsheetEditor = ({
|
|
|
805
1397
|
window.removeEventListener("mouseup", handleMouseUp);
|
|
806
1398
|
};
|
|
807
1399
|
}, [resizingColId, resizeStartX, resizeStartWidth]);
|
|
808
|
-
const measureElementContentWidth = (0,
|
|
1400
|
+
const measureElementContentWidth = (0, import_react8.useCallback)((el, container) => {
|
|
809
1401
|
if (typeof document === "undefined") return 0;
|
|
810
1402
|
let maxW = el.scrollWidth || 0;
|
|
811
1403
|
const firstChild = el.firstElementChild;
|
|
@@ -853,7 +1445,7 @@ var SpreadsheetEditor = ({
|
|
|
853
1445
|
}
|
|
854
1446
|
return maxW;
|
|
855
1447
|
}, []);
|
|
856
|
-
const autoFitColumn = (0,
|
|
1448
|
+
const autoFitColumn = (0, import_react8.useCallback)((colId, colIndex) => {
|
|
857
1449
|
const col = state.document.columns.find((c) => c.id === colId);
|
|
858
1450
|
if (!col) return;
|
|
859
1451
|
let calculatedWidth = 0;
|
|
@@ -889,12 +1481,12 @@ var SpreadsheetEditor = ({
|
|
|
889
1481
|
onColumnResize(updatedCol.id, updatedCol.width || 130);
|
|
890
1482
|
}
|
|
891
1483
|
}, [state, measureElementContentWidth, onColumnResize]);
|
|
892
|
-
const handleResizeDblClick = (0,
|
|
1484
|
+
const handleResizeDblClick = (0, import_react8.useCallback)((colId, colIndex, e) => {
|
|
893
1485
|
e.preventDefault();
|
|
894
1486
|
e.stopPropagation();
|
|
895
1487
|
autoFitColumn(colId, colIndex);
|
|
896
1488
|
}, [autoFitColumn]);
|
|
897
|
-
const handleHeaderDblClick = (0,
|
|
1489
|
+
const handleHeaderDblClick = (0, import_react8.useCallback)((colId, colIndex, e) => {
|
|
898
1490
|
const target = e.currentTarget;
|
|
899
1491
|
if (target) {
|
|
900
1492
|
const rect = target.getBoundingClientRect();
|
|
@@ -916,7 +1508,7 @@ var SpreadsheetEditor = ({
|
|
|
916
1508
|
const isActiveCell = (rIdx, cIdx) => {
|
|
917
1509
|
return state.activeCell?.row === rIdx && state.activeCell?.col === cIdx;
|
|
918
1510
|
};
|
|
919
|
-
|
|
1511
|
+
const grid = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
920
1512
|
"div",
|
|
921
1513
|
{
|
|
922
1514
|
ref: gridRef,
|
|
@@ -926,10 +1518,10 @@ var SpreadsheetEditor = ({
|
|
|
926
1518
|
role: "grid",
|
|
927
1519
|
"aria-label": "Spreadsheet",
|
|
928
1520
|
children: [
|
|
929
|
-
/* @__PURE__ */ (0,
|
|
930
|
-
/* @__PURE__ */ (0,
|
|
931
|
-
/* @__PURE__ */ (0,
|
|
932
|
-
/* @__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)(
|
|
933
1525
|
"input",
|
|
934
1526
|
{
|
|
935
1527
|
ref: formulaBarRef,
|
|
@@ -965,19 +1557,24 @@ var SpreadsheetEditor = ({
|
|
|
965
1557
|
}
|
|
966
1558
|
}
|
|
967
1559
|
),
|
|
968
|
-
!formulaBarReadOnly && /* @__PURE__ */ (0,
|
|
1560
|
+
!formulaBarReadOnly && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
969
1561
|
FormulaHints,
|
|
970
1562
|
{
|
|
971
1563
|
inputRef: formulaBarRef,
|
|
972
1564
|
value: state.editingCell ? state.draftValue : formulaDraft ?? activeRawValue,
|
|
973
|
-
onAccept: (next) => state.editingCell ? state.setDraftValue(next) : setFormulaDraft(next)
|
|
1565
|
+
onAccept: (next) => state.editingCell ? state.setDraftValue(next) : setFormulaDraft(next),
|
|
1566
|
+
names: nameSuggestions
|
|
974
1567
|
}
|
|
975
1568
|
)
|
|
976
1569
|
] }),
|
|
977
|
-
/* @__PURE__ */ (0,
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
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)(
|
|
981
1578
|
"th",
|
|
982
1579
|
{
|
|
983
1580
|
"data-col": cIdx,
|
|
@@ -992,16 +1589,16 @@ var SpreadsheetEditor = ({
|
|
|
992
1589
|
onClick: () => onColumnHeaderClick?.(col),
|
|
993
1590
|
onDoubleClick: (e) => handleHeaderDblClick(col.id, cIdx, e),
|
|
994
1591
|
children: [
|
|
995
|
-
/* @__PURE__ */ (0,
|
|
996
|
-
/* @__PURE__ */ (0,
|
|
997
|
-
filteredColIds.has(col.id) && /* @__PURE__ */ (0,
|
|
998
|
-
/* @__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: [
|
|
999
1596
|
"(",
|
|
1000
1597
|
col.key,
|
|
1001
1598
|
")"
|
|
1002
1599
|
] })
|
|
1003
1600
|
] }),
|
|
1004
|
-
/* @__PURE__ */ (0,
|
|
1601
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1005
1602
|
"div",
|
|
1006
1603
|
{
|
|
1007
1604
|
className: "col-resize-handle",
|
|
@@ -1018,7 +1615,7 @@ var SpreadsheetEditor = ({
|
|
|
1018
1615
|
onDoubleClick: (e) => handleResizeDblClick(col.id, cIdx, e),
|
|
1019
1616
|
role: "separator",
|
|
1020
1617
|
"aria-orientation": "vertical",
|
|
1021
|
-
children: /* @__PURE__ */ (0,
|
|
1618
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1022
1619
|
"div",
|
|
1023
1620
|
{
|
|
1024
1621
|
className: "col-resize-handle-line",
|
|
@@ -1037,19 +1634,19 @@ var SpreadsheetEditor = ({
|
|
|
1037
1634
|
},
|
|
1038
1635
|
col.id
|
|
1039
1636
|
)),
|
|
1040
|
-
onAddColumnClick && /* @__PURE__ */ (0,
|
|
1637
|
+
onAddColumnClick && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1041
1638
|
"th",
|
|
1042
1639
|
{
|
|
1043
1640
|
className: "w-10 min-w-[2.5rem] px-1 text-center bg-muted/40 hover:bg-muted cursor-pointer transition-colors",
|
|
1044
1641
|
onClick: onAddColumnClick,
|
|
1045
1642
|
title: "Add Column",
|
|
1046
|
-
children: /* @__PURE__ */ (0,
|
|
1643
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-xs font-bold text-muted-foreground", children: "+" })
|
|
1047
1644
|
}
|
|
1048
1645
|
)
|
|
1049
1646
|
] }) }),
|
|
1050
|
-
/* @__PURE__ */ (0,
|
|
1647
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("tbody", { children: view.rows.map((viewRow) => {
|
|
1051
1648
|
if (viewRow.kind === "group") {
|
|
1052
|
-
return /* @__PURE__ */ (0,
|
|
1649
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1053
1650
|
SpreadsheetGroupRow,
|
|
1054
1651
|
{
|
|
1055
1652
|
group: viewRow.group,
|
|
@@ -1062,15 +1659,15 @@ var SpreadsheetEditor = ({
|
|
|
1062
1659
|
);
|
|
1063
1660
|
}
|
|
1064
1661
|
const displayRowIndex = viewRow.index;
|
|
1065
|
-
const selection = state.selectedRange ? (0,
|
|
1066
|
-
return /* @__PURE__ */ (0,
|
|
1662
|
+
const selection = state.selectedRange ? (0, import_spreadsheet7.normalizeRange)(state.selectedRange) : null;
|
|
1663
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1067
1664
|
"tr",
|
|
1068
1665
|
{
|
|
1069
1666
|
"data-row": displayRowIndex,
|
|
1070
1667
|
className: `border-b border-border hover:bg-muted/10 transition-colors ${viewRow.section !== "body" ? "bg-muted/30 font-semibold" : ""}`,
|
|
1071
1668
|
style: { height: viewRow.row.height || 32 },
|
|
1072
1669
|
children: [
|
|
1073
|
-
/* @__PURE__ */ (0,
|
|
1670
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1074
1671
|
"td",
|
|
1075
1672
|
{
|
|
1076
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",
|
|
@@ -1089,9 +1686,13 @@ var SpreadsheetEditor = ({
|
|
|
1089
1686
|
const selected = isCellSelected(rIdx, cIdx);
|
|
1090
1687
|
const active = isActiveCell(rIdx, cIdx);
|
|
1091
1688
|
const isEditing = state.editingCell?.row === rIdx && state.editingCell?.col === cIdx;
|
|
1092
|
-
const
|
|
1093
|
-
const
|
|
1094
|
-
|
|
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)(
|
|
1095
1696
|
"td",
|
|
1096
1697
|
{
|
|
1097
1698
|
"data-row": rIdx,
|
|
@@ -1103,15 +1704,19 @@ var SpreadsheetEditor = ({
|
|
|
1103
1704
|
maxWidth: 0,
|
|
1104
1705
|
overflow: "hidden",
|
|
1105
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)" } : {},
|
|
1106
1710
|
...inFillPreview ? { outline: "1px dashed var(--primary, #6366f1)", outlineOffset: -1 } : {}
|
|
1107
1711
|
},
|
|
1712
|
+
"data-cf-fill": conditional?.fill,
|
|
1108
1713
|
onClick: (e) => handleCellClick(rIdx, cIdx, e),
|
|
1109
1714
|
onDoubleClick: () => handleCellDblClick(rIdx, cIdx),
|
|
1110
1715
|
onContextMenu: (e) => handleCellContextMenu(rIdx, cIdx, e),
|
|
1111
1716
|
onMouseOver: () => extendFill(displayRowIndex, viewCol.index),
|
|
1112
1717
|
children: [
|
|
1113
|
-
isEditing ? /* @__PURE__ */ (0,
|
|
1114
|
-
/* @__PURE__ */ (0,
|
|
1718
|
+
isEditing ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
1719
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1115
1720
|
"input",
|
|
1116
1721
|
{
|
|
1117
1722
|
ref: cellInputRef,
|
|
@@ -1127,10 +1732,36 @@ var SpreadsheetEditor = ({
|
|
|
1127
1732
|
autoFocus: true
|
|
1128
1733
|
}
|
|
1129
1734
|
),
|
|
1130
|
-
/* @__PURE__ */ (0,
|
|
1131
|
-
] }) : customCellRenderer ? customCellRenderer({ row, col, cell }) : /* @__PURE__ */ (0,
|
|
1132
|
-
|
|
1133
|
-
|
|
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)(
|
|
1738
|
+
"span",
|
|
1739
|
+
{
|
|
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 }
|
|
1743
|
+
}
|
|
1744
|
+
),
|
|
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)(
|
|
1134
1765
|
"div",
|
|
1135
1766
|
{
|
|
1136
1767
|
className: "spreadsheet-fill-handle",
|
|
@@ -1145,14 +1776,14 @@ var SpreadsheetEditor = ({
|
|
|
1145
1776
|
viewCol.column.id
|
|
1146
1777
|
);
|
|
1147
1778
|
}),
|
|
1148
|
-
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" })
|
|
1149
1780
|
]
|
|
1150
1781
|
},
|
|
1151
1782
|
viewRow.row.id
|
|
1152
1783
|
);
|
|
1153
1784
|
}) })
|
|
1154
1785
|
] }) }),
|
|
1155
|
-
menu && /* @__PURE__ */ (0,
|
|
1786
|
+
menu && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1156
1787
|
SpreadsheetContextMenu,
|
|
1157
1788
|
{
|
|
1158
1789
|
items: menu.items,
|
|
@@ -1161,10 +1792,45 @@ var SpreadsheetEditor = ({
|
|
|
1161
1792
|
onClose: () => {
|
|
1162
1793
|
setMenu(null);
|
|
1163
1794
|
gridRef.current?.focus();
|
|
1164
|
-
}
|
|
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
|
+
]
|
|
1165
1830
|
}
|
|
1166
1831
|
),
|
|
1167
|
-
/* @__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: `
|
|
1168
1834
|
.col-resize-handle:hover .col-resize-handle-line {
|
|
1169
1835
|
background-color: var(--primary, #6366f1);
|
|
1170
1836
|
opacity: 0.75;
|
|
@@ -1184,13 +1850,178 @@ var SpreadsheetEditor = ({
|
|
|
1184
1850
|
]
|
|
1185
1851
|
}
|
|
1186
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
|
+
] });
|
|
1187
2013
|
};
|
|
1188
2014
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1189
2015
|
0 && (module.exports = {
|
|
1190
2016
|
FormulaHints,
|
|
2017
|
+
NameManager,
|
|
2018
|
+
SheetTabs,
|
|
2019
|
+
SpreadsheetConditionalFormatPanel,
|
|
1191
2020
|
SpreadsheetContextMenu,
|
|
2021
|
+
SpreadsheetDataValidationPanel,
|
|
1192
2022
|
SpreadsheetEditor,
|
|
1193
2023
|
SpreadsheetGroupRow,
|
|
2024
|
+
SpreadsheetValidationDialog,
|
|
1194
2025
|
useFillHandle,
|
|
1195
2026
|
useSpreadsheetClipboard,
|
|
1196
2027
|
useSpreadsheetEditor
|