@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.
Files changed (46) hide show
  1. package/README.md +2 -2
  2. package/dist/canvas/index.cjs +406 -142
  3. package/dist/canvas/index.cjs.map +1 -1
  4. package/dist/canvas/index.d.cts +66 -3
  5. package/dist/canvas/index.d.ts +66 -3
  6. package/dist/canvas/index.js +277 -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-XTFR3IMK.js → chunk-ZU54BMDC.js} +473 -160
  13. package/dist/chunk-ZU54BMDC.js.map +1 -0
  14. package/dist/generation/index.cjs +290 -0
  15. package/dist/generation/index.cjs.map +1 -1
  16. package/dist/generation/index.d.cts +66 -2
  17. package/dist/generation/index.d.ts +66 -2
  18. package/dist/generation/index.js +7 -1
  19. package/dist/image-editor/index.cjs +490 -187
  20. package/dist/image-editor/index.cjs.map +1 -1
  21. package/dist/image-editor/index.d.cts +34 -2
  22. package/dist/image-editor/index.d.ts +34 -2
  23. package/dist/image-editor/index.js +5 -1
  24. package/dist/index.cjs +2372 -731
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +88 -6
  27. package/dist/index.d.ts +88 -6
  28. package/dist/index.js +334 -96
  29. package/dist/index.js.map +1 -1
  30. package/dist/spreadsheet/index.cjs +943 -112
  31. package/dist/spreadsheet/index.cjs.map +1 -1
  32. package/dist/spreadsheet/index.d.cts +119 -7
  33. package/dist/spreadsheet/index.d.ts +119 -7
  34. package/dist/spreadsheet/index.js +11 -1
  35. package/dist/tokens/index.cjs +395 -0
  36. package/dist/tokens/index.cjs.map +1 -0
  37. package/dist/tokens/index.d.cts +54 -0
  38. package/dist/tokens/index.d.ts +54 -0
  39. package/dist/tokens/index.js +375 -0
  40. package/dist/tokens/index.js.map +1 -0
  41. package/package.json +10 -2
  42. package/dist/chunk-7SPRQZ6H.js +0 -216
  43. package/dist/chunk-7SPRQZ6H.js.map +0 -1
  44. package/dist/chunk-FYIPXHAE.js +0 -1169
  45. package/dist/chunk-FYIPXHAE.js.map +0 -1
  46. 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 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,
@@ -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
- (0, import_spreadsheet2.runSpreadsheetMenuItem)(item);
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/FormulaHints.tsx
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 FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
414
- const [caret, setCaret] = (0, import_react4.useState)(null);
415
- const [focused, setFocused] = (0, import_react4.useState)(false);
416
- const [selected, setSelected] = (0, import_react4.useState)(0);
417
- const [dismissed, setDismissed] = (0, import_react4.useState)(false);
418
- const [position, setPosition] = (0, import_react4.useState)(null);
419
- const pendingCaret = (0, import_react4.useRef)(null);
420
- const hints = (0, import_react4.useMemo)(
421
- () => (0, import_spreadsheet4.getFormulaHints)(value, caret ?? value.length, { limit }),
422
- [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]
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, import_react4.useRef)({ suggestions, selected: selectedIndex, value, caret, onAccept });
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, import_spreadsheet4.applyFormulaSuggestion)(current, at ?? current.length, suggestion.name);
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, import_react4.useEffect)(() => {
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, import_react4.useLayoutEffect)(() => {
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, import_react4.useLayoutEffect)(() => {
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, import_jsx_runtime3.jsxs)(
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, import_jsx_runtime3.jsxs)("div", { "data-formula-signature": "", className: "px-2 py-1.5 border-b border-border bg-muted/30", children: [
521
- /* @__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)(
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, 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: [
531
- /* @__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 }),
532
911
  ": ",
533
912
  activeCall.argument.description
534
913
  ] }) : activeCall.description })
535
914
  ] }),
536
- 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)(
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, import_jsx_runtime3.jsx)("div", { className: "font-mono font-semibold text-foreground", children: s.signature }),
549
- /* @__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 })
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 import_jsx_runtime4 = require("react/jsx-runtime");
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, import_spreadsheet5.indexToColName)(cell.col)}${cell.row + 1}` : "";
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, import_spreadsheet5.formatCellForDisplay)(cell);
589
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
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 || shown.color ? { ...shown.numeric ? { textAlign: "right" } : {}, ...shown.color ? { color: (0, import_spreadsheet5.numberFormatColorToCss)(shown.color) } : {} } : void 0,
596
- children: shown.text
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, import_react5.useRef)(null);
611
- const formulaBarRef = (0, import_react5.useRef)(null);
612
- const cellInputRef = (0, import_react5.useRef)(null);
613
- const [resizingColId, setResizingColId] = (0, import_react5.useState)(null);
614
- const [hoveredColId, setHoveredColId] = (0, import_react5.useState)(null);
615
- const [resizeStartX, setResizeStartX] = (0, import_react5.useState)(0);
616
- 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);
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, 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;
621
1163
  const filter = state.document.table?.filter;
622
- const filteredColIds = (0, import_react5.useMemo)(
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, import_react5.useCallback)((rIdx, cIdx, e) => {
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, import_spreadsheet5.rangeContains)(state.selectedRange, rIdx, cIdx)) {
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, 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 }) });
634
1176
  }, [readOnly, state]);
635
- 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)(() => {
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, import_react5.useMemo)(
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, import_react5.useMemo)(() => {
1195
+ const coordLabel = (0, import_react8.useMemo)(() => {
649
1196
  if (!state.activeCell) return "--";
650
- const colName = (0, import_spreadsheet5.indexToColName)(state.activeCell.col);
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, import_react5.useState)(null);
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, import_react5.useEffect)(() => {
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.setCellValue(row.id, col.id, formulaDraft);
1215
+ state.commitCellInput(row.id, col.id, formulaDraft);
669
1216
  }
670
1217
  setFormulaDraft(null);
671
1218
  };
672
- const handleCellClick = (0, import_react5.useCallback)((rIdx, cIdx, e) => {
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, import_react5.useCallback)((rIdx, cIdx) => {
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, import_react5.useCallback)((e) => {
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 === "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") {
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 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);
754
1346
  stateRef.current = state;
755
- const onColumnResizeRef = (0, import_react5.useRef)(onColumnResize);
1347
+ const onColumnResizeRef = (0, import_react8.useRef)(onColumnResize);
756
1348
  onColumnResizeRef.current = onColumnResize;
757
- (0, import_react5.useEffect)(() => {
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, import_react5.useCallback)((el, container) => {
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, import_react5.useCallback)((colId, colIndex) => {
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, import_react5.useCallback)((colId, colIndex, e) => {
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, import_react5.useCallback)((colId, colIndex, e) => {
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
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
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, 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: [
930
- /* @__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 }),
931
- /* @__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" }) }),
932
- /* @__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)(
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, import_jsx_runtime4.jsx)(
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, 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: [
978
- /* @__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: [
979
- /* @__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: "#" }),
980
- 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)(
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, import_jsx_runtime4.jsxs)("div", { className: "flex items-center justify-between gap-1 min-w-0 overflow-hidden truncate", children: [
996
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "truncate font-semibold text-foreground text-xs min-w-0", children: col.title }),
997
- 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" }),
998
- /* @__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: [
999
1596
  "(",
1000
1597
  col.key,
1001
1598
  ")"
1002
1599
  ] })
1003
1600
  ] }),
1004
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
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, import_jsx_runtime4.jsx)(
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, import_jsx_runtime4.jsx)(
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, 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: "+" })
1047
1644
  }
1048
1645
  )
1049
1646
  ] }) }),
1050
- /* @__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) => {
1051
1648
  if (viewRow.kind === "group") {
1052
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
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, import_spreadsheet5.normalizeRange)(state.selectedRange) : null;
1066
- 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)(
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, import_jsx_runtime4.jsx)(
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 inFillPreview = !!fillTarget && !selected && (0, import_spreadsheet5.rangeContains)(fillTarget, displayRowIndex, viewCol.index);
1093
- const showFillHandle = !readOnly && !isEditing && !!selection && (span.merge ? (0, import_spreadsheet5.rangeContains)(span.merge, selection.endRow, selection.endCol) : selection.endRow === rIdx && selection.endCol === cIdx);
1094
- 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)(
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, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1114
- /* @__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)(
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, import_jsx_runtime4.jsx)(FormulaHints, { inputRef: cellInputRef, value: state.draftValue, onAccept: state.setDraftValue })
1131
- ] }) : customCellRenderer ? customCellRenderer({ row, col, cell }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CellText, { cell }),
1132
- 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) }),
1133
- showFillHandle && /* @__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)(
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, 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" })
1149
1780
  ]
1150
1781
  },
1151
1782
  viewRow.row.id
1152
1783
  );
1153
1784
  }) })
1154
1785
  ] }) }),
1155
- menu && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
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, 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: `
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