@pixerate/editor-react 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +6 -2
  2. package/dist/canvas/index.cjs +484 -119
  3. package/dist/canvas/index.cjs.map +1 -1
  4. package/dist/canvas/index.d.cts +122 -3
  5. package/dist/canvas/index.d.ts +122 -3
  6. package/dist/canvas/index.js +380 -0
  7. package/dist/canvas/index.js.map +1 -1
  8. package/dist/chunk-I3E4KSA2.js +515 -0
  9. package/dist/chunk-I3E4KSA2.js.map +1 -0
  10. package/dist/chunk-JR5ZBDR7.js +2012 -0
  11. package/dist/chunk-JR5ZBDR7.js.map +1 -0
  12. package/dist/chunk-XWFU2CSG.js +167 -0
  13. package/dist/chunk-XWFU2CSG.js.map +1 -0
  14. package/dist/chunk-ZU54BMDC.js +2128 -0
  15. package/dist/chunk-ZU54BMDC.js.map +1 -0
  16. package/dist/generation/index.cjs +525 -0
  17. package/dist/generation/index.cjs.map +1 -0
  18. package/dist/generation/index.d.cts +124 -0
  19. package/dist/generation/index.d.ts +124 -0
  20. package/dist/generation/index.js +19 -0
  21. package/dist/generation/index.js.map +1 -0
  22. package/dist/image-editor/index.cjs +988 -337
  23. package/dist/image-editor/index.cjs.map +1 -1
  24. package/dist/image-editor/index.d.cts +92 -2
  25. package/dist/image-editor/index.d.ts +92 -2
  26. package/dist/image-editor/index.js +17 -3
  27. package/dist/index.cjs +3628 -754
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +226 -5
  30. package/dist/index.d.ts +226 -5
  31. package/dist/index.js +868 -77
  32. package/dist/index.js.map +1 -1
  33. package/dist/persistence/index.cjs +191 -0
  34. package/dist/persistence/index.cjs.map +1 -0
  35. package/dist/persistence/index.d.cts +77 -0
  36. package/dist/persistence/index.d.ts +77 -0
  37. package/dist/persistence/index.js +9 -0
  38. package/dist/persistence/index.js.map +1 -0
  39. package/dist/spreadsheet/index.cjs +949 -111
  40. package/dist/spreadsheet/index.cjs.map +1 -1
  41. package/dist/spreadsheet/index.d.cts +121 -7
  42. package/dist/spreadsheet/index.d.ts +121 -7
  43. package/dist/spreadsheet/index.js +11 -1
  44. package/dist/tokens/index.cjs +395 -0
  45. package/dist/tokens/index.cjs.map +1 -0
  46. package/dist/tokens/index.d.cts +54 -0
  47. package/dist/tokens/index.d.ts +54 -0
  48. package/dist/tokens/index.js +375 -0
  49. package/dist/tokens/index.js.map +1 -0
  50. package/package.json +26 -2
  51. package/dist/chunk-LPFZMZ3L.js +0 -1461
  52. package/dist/chunk-LPFZMZ3L.js.map +0 -1
  53. package/dist/chunk-QVQYYHJ5.js +0 -1161
  54. package/dist/chunk-QVQYYHJ5.js.map +0 -1
@@ -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 commitEditing = (0, import_react.useCallback)((val) => {
97
- controller.commitEditing(val);
98
- setEditingCell(null);
99
- setDraftValue("");
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 import_react5 = require("react");
210
- var import_spreadsheet5 = require("@pixerate/editor/spreadsheet");
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": "Table actions",
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: "block w-full px-3 py-1 text-left hover:bg-muted disabled:cursor-default disabled:opacity-40 disabled:hover:bg-transparent",
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.run();
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/FormulaHints.tsx
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 FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
413
- const [caret, setCaret] = (0, import_react4.useState)(null);
414
- const [focused, setFocused] = (0, import_react4.useState)(false);
415
- const [selected, setSelected] = (0, import_react4.useState)(0);
416
- const [dismissed, setDismissed] = (0, import_react4.useState)(false);
417
- const [position, setPosition] = (0, import_react4.useState)(null);
418
- const pendingCaret = (0, import_react4.useRef)(null);
419
- const hints = (0, import_react4.useMemo)(
420
- () => (0, import_spreadsheet4.getFormulaHints)(value, caret ?? value.length, { limit }),
421
- [value, caret, limit]
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, import_react4.useRef)({ suggestions, selected: selectedIndex, value, caret, onAccept });
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, import_spreadsheet4.applyFormulaSuggestion)(current, at ?? current.length, suggestion.name);
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, import_react4.useEffect)(() => {
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, import_react4.useLayoutEffect)(() => {
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, import_react4.useLayoutEffect)(() => {
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, import_jsx_runtime3.jsxs)(
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, import_jsx_runtime3.jsxs)("div", { "data-formula-signature": "", className: "px-2 py-1.5 border-b border-border bg-muted/30", children: [
520
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "font-mono", children: activeCall.parts.map((part, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
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, import_jsx_runtime3.jsx)("div", { className: "mt-0.5 text-muted-foreground", children: activeCall.argument ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
530
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "font-semibold", children: activeCall.argument.name }),
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, import_jsx_runtime3.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_runtime3.jsxs)(
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, import_jsx_runtime3.jsx)("div", { className: "font-mono font-semibold text-foreground", children: s.signature }),
548
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "text-muted-foreground truncate", children: s.description })
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 import_jsx_runtime4 = require("react/jsx-runtime");
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, import_spreadsheet5.indexToColName)(cell.col)}${cell.row + 1}` : "";
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, import_react5.useRef)(null);
597
- const formulaBarRef = (0, import_react5.useRef)(null);
598
- const cellInputRef = (0, import_react5.useRef)(null);
599
- const [resizingColId, setResizingColId] = (0, import_react5.useState)(null);
600
- const [hoveredColId, setHoveredColId] = (0, import_react5.useState)(null);
601
- const [resizeStartX, setResizeStartX] = (0, import_react5.useState)(0);
602
- const [resizeStartWidth, setResizeStartWidth] = (0, import_react5.useState)(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, import_react5.useState)(null);
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, import_react5.useMemo)(
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, import_react5.useCallback)((rIdx, cIdx, e) => {
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, import_spreadsheet5.rangeContains)(state.selectedRange, rIdx, cIdx)) {
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, import_spreadsheet5.getSpreadsheetMenuItems)(state.controller, { row: rIdx, col: cIdx }) });
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 activeRawValue = (0, import_react5.useMemo)(() => {
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, import_react5.useMemo)(
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, import_react5.useMemo)(() => {
1195
+ const coordLabel = (0, import_react8.useMemo)(() => {
635
1196
  if (!state.activeCell) return "--";
636
- const colName = (0, import_spreadsheet5.indexToColName)(state.activeCell.col);
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, import_react5.useState)(null);
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, import_react5.useEffect)(() => {
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.setCellValue(row.id, col.id, formulaDraft);
1215
+ state.commitCellInput(row.id, col.id, formulaDraft);
655
1216
  }
656
1217
  setFormulaDraft(null);
657
1218
  };
658
- const handleCellClick = (0, import_react5.useCallback)((rIdx, cIdx, e) => {
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, import_react5.useCallback)((rIdx, cIdx) => {
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, import_react5.useCallback)((e) => {
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 === "ArrowUp") {
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 stateRef = (0, import_react5.useRef)(state);
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, import_react5.useRef)(onColumnResize);
1347
+ const onColumnResizeRef = (0, import_react8.useRef)(onColumnResize);
742
1348
  onColumnResizeRef.current = onColumnResize;
743
- (0, import_react5.useEffect)(() => {
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, import_react5.useCallback)((el, container) => {
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, import_react5.useCallback)((colId, colIndex) => {
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, import_react5.useCallback)((colId, colIndex, e) => {
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, import_react5.useCallback)((colId, colIndex, e) => {
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
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
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, import_jsx_runtime4.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: [
916
- /* @__PURE__ */ (0, import_jsx_runtime4.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 }),
917
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex items-center text-muted-foreground select-none", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "italic font-serif font-bold text-sm text-primary", children: "fx" }) }),
918
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
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, import_jsx_runtime4.jsx)(
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, import_jsx_runtime4.jsx)("div", { className: "relative flex-1 overflow-auto", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("table", { className: "border-collapse table-fixed w-max min-w-full", children: [
964
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { className: "h-7 bg-muted sticky top-0 z-20 border-b border-border shadow-xs", children: [
965
- /* @__PURE__ */ (0, import_jsx_runtime4.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: "#" }),
966
- view.columns.map(({ column: col, index: cIdx, frozen, stickyLeft }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
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, import_jsx_runtime4.jsxs)("div", { className: "flex items-center justify-between gap-1 min-w-0 overflow-hidden truncate", children: [
982
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "truncate font-semibold text-foreground text-xs min-w-0", children: col.title }),
983
- filteredColIds.has(col.id) && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-[10px] text-primary shrink-0", title: "Filtered", "data-filtered": "true", children: "\u23F7" }),
984
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "text-[10px] text-muted-foreground/70 font-mono font-normal shrink-0", children: [
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, import_jsx_runtime4.jsx)(
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, import_jsx_runtime4.jsx)(
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, import_jsx_runtime4.jsx)(
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, import_jsx_runtime4.jsx)("span", { className: "text-xs font-bold text-muted-foreground", children: "+" })
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, import_jsx_runtime4.jsx)("tbody", { children: view.rows.map((viewRow) => {
1647
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("tbody", { children: view.rows.map((viewRow) => {
1037
1648
  if (viewRow.kind === "group") {
1038
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
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, import_spreadsheet5.normalizeRange)(state.selectedRange) : null;
1052
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
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, import_jsx_runtime4.jsx)(
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 inFillPreview = !!fillTarget && !selected && (0, import_spreadsheet5.rangeContains)(fillTarget, displayRowIndex, viewCol.index);
1079
- const showFillHandle = !readOnly && !isEditing && !!selection && (span.merge ? (0, import_spreadsheet5.rangeContains)(span.merge, selection.endRow, selection.endCol) : selection.endRow === rIdx && selection.endCol === cIdx);
1080
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
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, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1100
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
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, import_jsx_runtime4.jsx)(FormulaHints, { inputRef: cellInputRef, value: state.draftValue, onAccept: state.setDraftValue })
1117
- ] }) : customCellRenderer ? customCellRenderer({ row, col, cell }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
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: `truncate block max-w-full ${cell.error ? "text-destructive font-semibold" : ""}`,
1121
- title: cell.error ? cell.errorMessage ?? cell.error : void 0,
1122
- children: (0, import_spreadsheet5.formatCellDisplay)(cell)
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
- activeSpill && (0, import_spreadsheet5.rangeContains)(activeSpill.range, rIdx, cIdx) && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { "aria-hidden": "true", className: "spreadsheet-spill-border", style: spillBorderStyle(activeSpill.range, rIdx, cIdx) }),
1126
- showFillHandle && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
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, import_jsx_runtime4.jsx)("td", { className: "w-10 min-w-[2.5rem] bg-muted/10 border-r border-border" })
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, import_jsx_runtime4.jsx)(
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, import_jsx_runtime4.jsx)("style", { children: `
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