@geomak/ui 7.13.0 → 7.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -2753,18 +2753,24 @@ interface DataGridProps {
2753
2753
  * `row` index at/after `rows.length` so the consumer can append the row.
2754
2754
  */
2755
2755
  trailingRows?: number;
2756
+ /** Blank, letter-labelled columns rendered after the data columns (spreadsheet slack). Default 0. */
2757
+ trailingCols?: number;
2756
2758
  /** `row` is the index into `rows` (stable across sorting; may equal `rows.length`+ for a blank trailing row). */
2757
2759
  onCellEdit?: (e: {
2758
2760
  row: number;
2759
2761
  column: string;
2760
2762
  value: string;
2761
2763
  }) => void;
2762
- /** Enable right-click menus: Copy/Cut/Paste on cells, Add/Delete on rows. Default false. */
2764
+ /** Enable right-click menus: Copy/Cut/Paste on cells, Add/Delete on rows, Add column on headers. Default false. */
2763
2765
  contextMenu?: boolean;
2764
2766
  /** Insert a blank row at `index` (from the row right-click menu). */
2765
2767
  onInsertRow?: (index: number) => void;
2766
2768
  /** Delete the row at `index` (from the row right-click menu). */
2767
2769
  onDeleteRow?: (index: number) => void;
2770
+ /** Insert a column at `index` (from the column-header right-click menu). */
2771
+ onInsertColumn?: (index: number) => void;
2772
+ /** Rename a column. When set, headers become editable (double-click / Rename menu). */
2773
+ onHeaderEdit?: (index: number, label: string) => void;
2768
2774
  className?: string;
2769
2775
  style?: react__default.CSSProperties;
2770
2776
  /** Shown when there are no rows. */
@@ -2781,7 +2787,7 @@ interface DataGridProps {
2781
2787
  * `onCellEdit` on commit. Wrap it with {@link Spreadsheet} for multi-sheet
2782
2788
  * switching, file parsing and export.
2783
2789
  */
2784
- declare function DataGrid({ columns, rows, rowHeight, headerHeight, height, width, editable, sortable, sort: sortProp, onSortChange, virtualize, overscan, rowNumbers, trailingRows, onCellEdit, contextMenu, onInsertRow, onDeleteRow, className, style, emptyState, }: DataGridProps): react_jsx_runtime.JSX.Element;
2790
+ declare function DataGrid({ columns, rows, rowHeight, headerHeight, height, width, editable, sortable, sort: sortProp, onSortChange, virtualize, overscan, rowNumbers, trailingRows, trailingCols, onCellEdit, contextMenu, onInsertRow, onDeleteRow, onInsertColumn, onHeaderEdit, className, style, emptyState, }: DataGridProps): react_jsx_runtime.JSX.Element;
2785
2791
 
2786
2792
  interface Cell {
2787
2793
  value: CellValue;
@@ -2815,6 +2821,8 @@ interface SpreadsheetProps {
2815
2821
  sortable?: boolean;
2816
2822
  /** Blank rows kept below the data (the spreadsheet "slack"). Default 50. */
2817
2823
  emptyRows?: number;
2824
+ /** Blank letter-labelled columns kept to the right of the data. Default 6. */
2825
+ emptyCols?: number;
2818
2826
  /** Show a “+” to add a blank sheet. Defaults to `editable`. */
2819
2827
  allowAddSheet?: boolean;
2820
2828
  /** Overall height. Default 480. */
@@ -2833,7 +2841,7 @@ interface SpreadsheetProps {
2833
2841
  * Exports to multi-sheet `.xlsx`, `.csv` (active sheet, BOM-prefixed) or a
2834
2842
  * paginated table `.pdf` (jsPDF, lazy).
2835
2843
  */
2836
- declare function Spreadsheet({ source, remote, editable, onCellEdit, onChange, export: exportFormats, fileName, virtualize, sortable, emptyRows, allowAddSheet, height, width, className, style, }: SpreadsheetProps): react_jsx_runtime.JSX.Element;
2844
+ declare function Spreadsheet({ source, remote, editable, onCellEdit, onChange, export: exportFormats, fileName, virtualize, sortable, emptyRows, emptyCols, allowAddSheet, height, width, className, style, }: SpreadsheetProps): react_jsx_runtime.JSX.Element;
2837
2845
 
2838
2846
  interface ThemeSwitchProps {
2839
2847
  checked: boolean;
package/dist/index.d.ts CHANGED
@@ -2753,18 +2753,24 @@ interface DataGridProps {
2753
2753
  * `row` index at/after `rows.length` so the consumer can append the row.
2754
2754
  */
2755
2755
  trailingRows?: number;
2756
+ /** Blank, letter-labelled columns rendered after the data columns (spreadsheet slack). Default 0. */
2757
+ trailingCols?: number;
2756
2758
  /** `row` is the index into `rows` (stable across sorting; may equal `rows.length`+ for a blank trailing row). */
2757
2759
  onCellEdit?: (e: {
2758
2760
  row: number;
2759
2761
  column: string;
2760
2762
  value: string;
2761
2763
  }) => void;
2762
- /** Enable right-click menus: Copy/Cut/Paste on cells, Add/Delete on rows. Default false. */
2764
+ /** Enable right-click menus: Copy/Cut/Paste on cells, Add/Delete on rows, Add column on headers. Default false. */
2763
2765
  contextMenu?: boolean;
2764
2766
  /** Insert a blank row at `index` (from the row right-click menu). */
2765
2767
  onInsertRow?: (index: number) => void;
2766
2768
  /** Delete the row at `index` (from the row right-click menu). */
2767
2769
  onDeleteRow?: (index: number) => void;
2770
+ /** Insert a column at `index` (from the column-header right-click menu). */
2771
+ onInsertColumn?: (index: number) => void;
2772
+ /** Rename a column. When set, headers become editable (double-click / Rename menu). */
2773
+ onHeaderEdit?: (index: number, label: string) => void;
2768
2774
  className?: string;
2769
2775
  style?: react__default.CSSProperties;
2770
2776
  /** Shown when there are no rows. */
@@ -2781,7 +2787,7 @@ interface DataGridProps {
2781
2787
  * `onCellEdit` on commit. Wrap it with {@link Spreadsheet} for multi-sheet
2782
2788
  * switching, file parsing and export.
2783
2789
  */
2784
- declare function DataGrid({ columns, rows, rowHeight, headerHeight, height, width, editable, sortable, sort: sortProp, onSortChange, virtualize, overscan, rowNumbers, trailingRows, onCellEdit, contextMenu, onInsertRow, onDeleteRow, className, style, emptyState, }: DataGridProps): react_jsx_runtime.JSX.Element;
2790
+ declare function DataGrid({ columns, rows, rowHeight, headerHeight, height, width, editable, sortable, sort: sortProp, onSortChange, virtualize, overscan, rowNumbers, trailingRows, trailingCols, onCellEdit, contextMenu, onInsertRow, onDeleteRow, onInsertColumn, onHeaderEdit, className, style, emptyState, }: DataGridProps): react_jsx_runtime.JSX.Element;
2785
2791
 
2786
2792
  interface Cell {
2787
2793
  value: CellValue;
@@ -2815,6 +2821,8 @@ interface SpreadsheetProps {
2815
2821
  sortable?: boolean;
2816
2822
  /** Blank rows kept below the data (the spreadsheet "slack"). Default 50. */
2817
2823
  emptyRows?: number;
2824
+ /** Blank letter-labelled columns kept to the right of the data. Default 6. */
2825
+ emptyCols?: number;
2818
2826
  /** Show a “+” to add a blank sheet. Defaults to `editable`. */
2819
2827
  allowAddSheet?: boolean;
2820
2828
  /** Overall height. Default 480. */
@@ -2833,7 +2841,7 @@ interface SpreadsheetProps {
2833
2841
  * Exports to multi-sheet `.xlsx`, `.csv` (active sheet, BOM-prefixed) or a
2834
2842
  * paginated table `.pdf` (jsPDF, lazy).
2835
2843
  */
2836
- declare function Spreadsheet({ source, remote, editable, onCellEdit, onChange, export: exportFormats, fileName, virtualize, sortable, emptyRows, allowAddSheet, height, width, className, style, }: SpreadsheetProps): react_jsx_runtime.JSX.Element;
2844
+ declare function Spreadsheet({ source, remote, editable, onCellEdit, onChange, export: exportFormats, fileName, virtualize, sortable, emptyRows, emptyCols, allowAddSheet, height, width, className, style, }: SpreadsheetProps): react_jsx_runtime.JSX.Element;
2837
2845
 
2838
2846
  interface ThemeSwitchProps {
2839
2847
  checked: boolean;
package/dist/index.js CHANGED
@@ -6471,6 +6471,15 @@ function displayValue(v) {
6471
6471
  if (v == null) return "";
6472
6472
  return String(v);
6473
6473
  }
6474
+ function colLetter(i) {
6475
+ let s = "";
6476
+ let n = i;
6477
+ do {
6478
+ s = String.fromCharCode(65 + n % 26) + s;
6479
+ n = Math.floor(n / 26) - 1;
6480
+ } while (n >= 0);
6481
+ return s;
6482
+ }
6474
6483
  function DataGrid({
6475
6484
  columns,
6476
6485
  rows,
@@ -6486,10 +6495,13 @@ function DataGrid({
6486
6495
  overscan = 4,
6487
6496
  rowNumbers = true,
6488
6497
  trailingRows = 0,
6498
+ trailingCols = 0,
6489
6499
  onCellEdit,
6490
6500
  contextMenu = false,
6491
6501
  onInsertRow,
6492
6502
  onDeleteRow,
6503
+ onInsertColumn,
6504
+ onHeaderEdit,
6493
6505
  className = "",
6494
6506
  style,
6495
6507
  emptyState = "No data"
@@ -6502,12 +6514,22 @@ function DataGrid({
6502
6514
  const [ctxTarget, setCtxTarget] = useState(null);
6503
6515
  const [editing, setEditing] = useState(null);
6504
6516
  const [draft, setDraft] = useState("");
6517
+ const [editingHeader, setEditingHeader] = useState(null);
6518
+ const [headerDraft, setHeaderDraft] = useState("");
6505
6519
  const [internalSort, setInternalSort] = useState(null);
6506
6520
  const sort = sortProp !== void 0 ? sortProp : internalSort;
6507
6521
  const gutter = rowNumbers ? GUTTER : 0;
6508
6522
  const colSortable = (c) => c.sortable ?? sortable;
6523
+ const cols = useMemo(() => {
6524
+ if (trailingCols <= 0) return columns;
6525
+ const extra = Array.from({ length: trailingCols }, (_, k) => {
6526
+ const idx = columns.length + k;
6527
+ return { key: `__c${idx}`, label: colLetter(idx), editable: false, sortable: false };
6528
+ });
6529
+ return [...columns, ...extra];
6530
+ }, [columns, trailingCols]);
6509
6531
  const { widths, offsets, totalWidth } = useMemo(() => {
6510
- const widths2 = columns.map((c) => resolveWidth(c.width));
6532
+ const widths2 = cols.map((c) => resolveWidth(c.width));
6511
6533
  const offsets2 = [];
6512
6534
  let acc = 0;
6513
6535
  for (const w of widths2) {
@@ -6515,7 +6537,7 @@ function DataGrid({
6515
6537
  acc += w;
6516
6538
  }
6517
6539
  return { widths: widths2, offsets: offsets2, totalWidth: acc };
6518
- }, [columns]);
6540
+ }, [cols]);
6519
6541
  const order = useMemo(() => {
6520
6542
  const idx = rows.map((_, i) => i);
6521
6543
  if (!sort) return idx;
@@ -6544,45 +6566,56 @@ function DataGrid({
6544
6566
  const rowStart = virtualize ? Math.max(0, Math.floor(scroll.top / rowHeight) - overscan) : 0;
6545
6567
  const rowEnd = virtualize ? Math.min(displayRowCount, Math.ceil((scroll.top + bodyH) / rowHeight) + overscan) : displayRowCount;
6546
6568
  let colStart = 0;
6547
- let colEnd = columns.length;
6569
+ let colEnd = cols.length;
6548
6570
  if (virtualize && viewport.w) {
6549
6571
  const viewLeft = scroll.left;
6550
6572
  const viewRight = scroll.left + (viewport.w - gutter);
6551
6573
  colStart = Math.max(0, offsets.findIndex((o, i) => o + widths[i] > viewLeft));
6552
6574
  if (colStart < 0) colStart = 0;
6553
6575
  colEnd = offsets.findIndex((o) => o > viewRight);
6554
- colEnd = colEnd === -1 ? columns.length : Math.min(columns.length, colEnd + 1);
6576
+ colEnd = colEnd === -1 ? cols.length : Math.min(cols.length, colEnd + 1);
6555
6577
  colStart = Math.max(0, colStart - overscan);
6556
- colEnd = Math.min(columns.length, colEnd + overscan);
6578
+ colEnd = Math.min(cols.length, colEnd + overscan);
6557
6579
  }
6558
6580
  const visibleRows = Array.from({ length: Math.max(0, rowEnd - rowStart) }, (_, i) => rowStart + i);
6559
6581
  const visibleCols = Array.from({ length: Math.max(0, colEnd - colStart) }, (_, i) => colStart + i);
6560
6582
  const commit = useCallback(() => {
6561
6583
  if (!editing) return;
6562
- const col = columns[editing.col];
6584
+ const col = cols[editing.col];
6563
6585
  const ri = editing.disp < rows.length ? order[editing.disp] : editing.disp;
6564
6586
  onCellEdit?.({ row: ri, column: col.key, value: draft });
6565
6587
  setEditing(null);
6566
- }, [editing, columns, draft, onCellEdit, order, rows.length]);
6588
+ }, [editing, cols, draft, onCellEdit, order, rows.length]);
6567
6589
  const startEdit = (disp, col) => {
6568
- const c = columns[col];
6590
+ const c = cols[col];
6569
6591
  if (!(c.editable ?? editable)) return;
6570
6592
  setDraft(displayValue(rows[rowIndexForDisp(disp)]?.[c.key] ?? ""));
6571
6593
  setEditing({ disp, col });
6572
6594
  };
6573
- const cellText = (sel) => displayValue(rows[rowIndexForDisp(sel.disp)]?.[columns[sel.col].key] ?? "");
6595
+ const startHeaderEdit = (col) => {
6596
+ if (!onHeaderEdit) return;
6597
+ const c = cols[col];
6598
+ setHeaderDraft(typeof c.label === "string" ? c.label : String(c.key));
6599
+ setEditingHeader(col);
6600
+ };
6601
+ const commitHeader = () => {
6602
+ if (editingHeader == null) return;
6603
+ onHeaderEdit?.(editingHeader, headerDraft);
6604
+ setEditingHeader(null);
6605
+ };
6606
+ const cellText = (sel) => displayValue(rows[rowIndexForDisp(sel.disp)]?.[cols[sel.col].key] ?? "");
6574
6607
  const copyCell = useCallback(async () => {
6575
6608
  if (!selected) return;
6576
6609
  try {
6577
6610
  await navigator.clipboard?.writeText(cellText(selected));
6578
6611
  } catch {
6579
6612
  }
6580
- }, [selected, rows, columns, order]);
6613
+ }, [selected, rows, cols, order]);
6581
6614
  const cutCell = useCallback(async () => {
6582
6615
  if (!selected) return;
6583
6616
  await copyCell();
6584
- onCellEdit?.({ row: rowIndexForDisp(selected.disp), column: columns[selected.col].key, value: "" });
6585
- }, [selected, copyCell, onCellEdit, columns, order, rows.length]);
6617
+ onCellEdit?.({ row: rowIndexForDisp(selected.disp), column: cols[selected.col].key, value: "" });
6618
+ }, [selected, copyCell, onCellEdit, cols, order, rows.length]);
6586
6619
  const pasteCell = useCallback(async () => {
6587
6620
  if (!selected) return;
6588
6621
  let text = "";
@@ -6591,9 +6624,17 @@ function DataGrid({
6591
6624
  } catch {
6592
6625
  return;
6593
6626
  }
6594
- onCellEdit?.({ row: rowIndexForDisp(selected.disp), column: columns[selected.col].key, value: text });
6595
- }, [selected, onCellEdit, columns, order, rows.length]);
6627
+ onCellEdit?.({ row: rowIndexForDisp(selected.disp), column: cols[selected.col].key, value: text });
6628
+ }, [selected, onCellEdit, cols, order, rows.length]);
6596
6629
  const ctxItems = useMemo(() => {
6630
+ if (ctxTarget?.kind === "header") {
6631
+ const ci = ctxTarget.col;
6632
+ return [
6633
+ { key: "rename", value: "Rename column", disabled: !onHeaderEdit, onClick: () => startHeaderEdit(ci) },
6634
+ { key: "left", value: "Add column to the left", disabled: !onInsertColumn, separatorBefore: true, onClick: () => onInsertColumn?.(ci) },
6635
+ { key: "right", value: "Add column to the right", disabled: !onInsertColumn, onClick: () => onInsertColumn?.(ci + 1) }
6636
+ ];
6637
+ }
6597
6638
  if (ctxTarget?.kind === "row") {
6598
6639
  const ri = rowIndexForDisp(ctxTarget.disp);
6599
6640
  const isData = ctxTarget.disp < rows.length;
@@ -6608,7 +6649,7 @@ function DataGrid({
6608
6649
  { key: "cut", value: "Cut", disabled: !editable, onClick: () => void cutCell() },
6609
6650
  { key: "paste", value: "Paste", disabled: !editable, onClick: () => void pasteCell() }
6610
6651
  ];
6611
- }, [ctxTarget, rows.length, editable, onInsertRow, onDeleteRow, copyCell, cutCell, pasteCell, order]);
6652
+ }, [ctxTarget, rows.length, editable, onInsertRow, onDeleteRow, onInsertColumn, onHeaderEdit, copyCell, cutCell, pasteCell, order]);
6612
6653
  const rowHighlighted = (disp) => hoveredRow === disp || selected?.disp === disp;
6613
6654
  const gridInner = /* @__PURE__ */ jsxs("div", { style: { position: "relative", width: gutter + totalWidth + END_PAD, height: headerHeight + totalHeight + END_PAD }, children: [
6614
6655
  rowNumbers && /* @__PURE__ */ jsx(
@@ -6619,18 +6660,23 @@ function DataGrid({
6619
6660
  }
6620
6661
  ),
6621
6662
  visibleCols.map((ci) => {
6622
- const c = columns[ci];
6663
+ const c = cols[ci];
6623
6664
  const sortDir = sort?.key === c.key ? sort.dir : null;
6624
6665
  const canSort = colSortable(c);
6625
- return /* @__PURE__ */ jsxs(
6666
+ const renamable = !!onHeaderEdit;
6667
+ const isEditingHeader = editingHeader === ci;
6668
+ return /* @__PURE__ */ jsx(
6626
6669
  "div",
6627
6670
  {
6628
6671
  role: "columnheader",
6629
6672
  "aria-sort": sortDir ? sortDir === "asc" ? "ascending" : "descending" : void 0,
6630
- onClick: canSort ? () => toggleSort(c.key) : void 0,
6673
+ onClick: canSort && !renamable ? () => toggleSort(c.key) : void 0,
6674
+ onDoubleClick: renamable ? () => startHeaderEdit(ci) : void 0,
6675
+ onContextMenu: contextMenu ? () => setCtxTarget({ kind: "header", col: ci }) : void 0,
6631
6676
  className: cx(
6632
6677
  "flex items-center gap-1 border-b border-r border-border bg-surface px-3 font-medium text-foreground-secondary",
6633
- canSort && "cursor-pointer select-none hover:text-foreground"
6678
+ canSort && !renamable && "cursor-pointer select-none hover:text-foreground",
6679
+ renamable && "cursor-default select-none"
6634
6680
  ),
6635
6681
  style: {
6636
6682
  position: "absolute",
@@ -6641,10 +6687,27 @@ function DataGrid({
6641
6687
  zIndex: 2,
6642
6688
  justifyContent: c.align === "right" ? "flex-end" : c.align === "center" ? "center" : "flex-start"
6643
6689
  },
6644
- children: [
6690
+ children: isEditingHeader ? /* @__PURE__ */ jsx(
6691
+ "input",
6692
+ {
6693
+ autoFocus: true,
6694
+ value: headerDraft,
6695
+ onChange: (e) => setHeaderDraft(e.target.value),
6696
+ onBlur: commitHeader,
6697
+ onClick: (e) => e.stopPropagation(),
6698
+ onKeyDown: (e) => {
6699
+ if (e.key === "Enter") commitHeader();
6700
+ else if (e.key === "Escape") setEditingHeader(null);
6701
+ },
6702
+ className: "h-full w-full bg-transparent font-medium text-foreground outline-none"
6703
+ }
6704
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
6645
6705
  /* @__PURE__ */ jsx("span", { className: "truncate", children: c.label ?? c.key }),
6646
- canSort && /* @__PURE__ */ jsx(SortCaret, { dir: sortDir })
6647
- ]
6706
+ canSort && (renamable ? /* @__PURE__ */ jsx("button", { type: "button", title: "Sort", onClick: (e) => {
6707
+ e.stopPropagation();
6708
+ toggleSort(c.key);
6709
+ }, className: "flex-shrink-0 rounded hover:text-foreground", children: /* @__PURE__ */ jsx(SortCaret, { dir: sortDir }) }) : /* @__PURE__ */ jsx(SortCaret, { dir: sortDir }))
6710
+ ] })
6648
6711
  },
6649
6712
  `h-${c.key}`
6650
6713
  );
@@ -6670,7 +6733,7 @@ function DataGrid({
6670
6733
  const ri = rowIndexForDisp(disp);
6671
6734
  const hi = rowHighlighted(disp);
6672
6735
  return visibleCols.map((ci) => {
6673
- const c = columns[ci];
6736
+ const c = cols[ci];
6674
6737
  const isEditing = editing?.disp === disp && editing?.col === ci;
6675
6738
  const isSelected = selected?.disp === disp && selected?.col === ci;
6676
6739
  const canEdit = c.editable ?? editable;
@@ -6765,8 +6828,24 @@ function columnLetter(i) {
6765
6828
  } while (n >= 0);
6766
6829
  return s;
6767
6830
  }
6831
+ function makeColumns(existing, count) {
6832
+ const used = new Set(existing.map((c) => c.key));
6833
+ const out = [];
6834
+ let n = existing.length;
6835
+ for (let k = 0; k < count; k++) {
6836
+ let key = columnLetter(n);
6837
+ while (used.has(key)) {
6838
+ n++;
6839
+ key = columnLetter(n);
6840
+ }
6841
+ used.add(key);
6842
+ out.push({ key, label: key });
6843
+ n++;
6844
+ }
6845
+ return out;
6846
+ }
6768
6847
  function blankSheet(name, cols = 8) {
6769
- return { name, columns: Array.from({ length: cols }, (_, i) => ({ key: columnLetter(i), label: columnLetter(i) })), rows: [] };
6848
+ return { name, columns: makeColumns([], cols), rows: [] };
6770
6849
  }
6771
6850
  function parseWorkbook(XLSX, bytes) {
6772
6851
  const wb = XLSX.read(bytes, { type: "array" });
@@ -6821,6 +6900,7 @@ function Spreadsheet({
6821
6900
  virtualize = true,
6822
6901
  sortable = true,
6823
6902
  emptyRows = 50,
6903
+ emptyCols = 6,
6824
6904
  allowAddSheet,
6825
6905
  height = 480,
6826
6906
  width,
@@ -6864,12 +6944,21 @@ function Spreadsheet({
6864
6944
  const sheet = sheets?.[active];
6865
6945
  const columns = useMemo(() => sheet ? toColumns(sheet.columns) : [], [sheet]);
6866
6946
  const plainRows = useMemo(() => sheet ? toPlainRows(sheet, columns) : [], [sheet, columns]);
6947
+ const displayColumns = useMemo(
6948
+ () => [...columns, ...makeColumns(columns, Math.max(0, emptyCols)).map((c) => ({ ...c, sortable: false }))],
6949
+ [columns, emptyCols]
6950
+ );
6951
+ const isSlackKey = (key) => !columns.some((c) => c.key === key);
6867
6952
  const handleCellEdit = useCallback(({ row, column, value }) => {
6868
6953
  let coerced = value;
6869
6954
  setSheets((prev) => {
6870
6955
  if (!prev) return prev;
6871
- const next = prev.map((s, i) => i === active ? { ...s, rows: s.rows.map((r) => ({ ...r })) } : s);
6956
+ const next = prev.map((s, i) => i === active ? { ...s, columns: toColumns(s.columns), rows: s.rows.map((r) => ({ ...r })) } : s);
6872
6957
  const target = next[active];
6958
+ if (isSlackKey(column)) {
6959
+ const j = displayColumns.findIndex((c) => c.key === column);
6960
+ if (j >= 0) target.columns = displayColumns.slice(0, j + 1).map((c) => ({ key: c.key, label: c.label }));
6961
+ }
6873
6962
  while (target.rows.length <= row) target.rows.push({});
6874
6963
  const existing = target.rows[row]?.[column];
6875
6964
  const prevValue = cellValue(existing);
@@ -6883,7 +6972,22 @@ function Spreadsheet({
6883
6972
  return next;
6884
6973
  });
6885
6974
  onCellEdit?.({ sheet: sheets?.[active]?.name ?? "", row, column, value: coerced });
6886
- }, [active, onCellEdit, onChange, sheets]);
6975
+ }, [active, onCellEdit, onChange, sheets, displayColumns, columns]);
6976
+ const renameColumn = useCallback((index, label) => {
6977
+ setSheets((prev) => {
6978
+ if (!prev) return prev;
6979
+ const next = prev.map((s, i) => i === active ? { ...s, columns: toColumns(s.columns) } : s);
6980
+ const target = next[active];
6981
+ let tcols = target.columns;
6982
+ if (index >= tcols.length) {
6983
+ tcols = displayColumns.slice(0, index + 1).map((c) => ({ key: c.key, label: c.label }));
6984
+ target.columns = tcols;
6985
+ }
6986
+ if (tcols[index]) tcols[index] = { ...tcols[index], label };
6987
+ onChange?.(next);
6988
+ return next;
6989
+ });
6990
+ }, [active, onChange, displayColumns]);
6887
6991
  const addSheet = useCallback(() => {
6888
6992
  setSheets((prev) => {
6889
6993
  const list = prev ?? [];
@@ -6911,6 +7015,33 @@ function Spreadsheet({
6911
7015
  return next;
6912
7016
  });
6913
7017
  }, [active, onChange]);
7018
+ const add10Columns = useCallback(() => {
7019
+ setSheets((prev) => {
7020
+ if (!prev) return prev;
7021
+ const next = prev.map((s, i) => {
7022
+ if (i !== active) return s;
7023
+ const cols = toColumns(s.columns);
7024
+ return { ...s, columns: [...cols, ...makeColumns(cols, 10)] };
7025
+ });
7026
+ onChange?.(next);
7027
+ return next;
7028
+ });
7029
+ }, [active, onChange]);
7030
+ const insertColumn = useCallback((index) => {
7031
+ setSheets((prev) => {
7032
+ if (!prev) return prev;
7033
+ const next = prev.map((s, i) => {
7034
+ if (i !== active) return s;
7035
+ const cols = toColumns(s.columns);
7036
+ const at = Math.max(0, Math.min(index, cols.length));
7037
+ const out = [...cols];
7038
+ out.splice(at, 0, makeColumns(cols, 1)[0]);
7039
+ return { ...s, columns: out };
7040
+ });
7041
+ onChange?.(next);
7042
+ return next;
7043
+ });
7044
+ }, [active, onChange]);
6914
7045
  const insertRow = useCallback((index) => {
6915
7046
  setSheets((prev) => {
6916
7047
  if (!prev) return prev;
@@ -7050,7 +7181,8 @@ function Spreadsheet({
7050
7181
  const sheetItems = [
7051
7182
  { key: "add", label: "Add Sheet", onSelect: addSheet },
7052
7183
  { key: "delete", label: "Delete Sheet", danger: true, disabled: sheets.length <= 1, onSelect: () => deleteSheet(active) },
7053
- { key: "add100", label: "Add 100 rows", separatorBefore: true, onSelect: add100Rows }
7184
+ { key: "add100", label: "Add 100 rows", separatorBefore: true, onSelect: add100Rows },
7185
+ { key: "add10cols", label: "Add 10 columns", onSelect: add10Columns }
7054
7186
  ];
7055
7187
  const canDeleteSheet = editable && sheets.length > 1;
7056
7188
  return /* @__PURE__ */ jsxs("div", { className: cx("flex flex-col overflow-hidden rounded-lg border border-border bg-surface-raised", className), style: { height, width, ...style }, children: [
@@ -7073,7 +7205,7 @@ function Spreadsheet({
7073
7205
  /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsx(
7074
7206
  DataGrid,
7075
7207
  {
7076
- columns,
7208
+ columns: displayColumns,
7077
7209
  rows: plainRows,
7078
7210
  editable,
7079
7211
  sortable,
@@ -7082,6 +7214,8 @@ function Spreadsheet({
7082
7214
  contextMenu: true,
7083
7215
  onInsertRow: editable ? insertRow : void 0,
7084
7216
  onDeleteRow: editable ? deleteRow : void 0,
7217
+ onInsertColumn: editable ? insertColumn : void 0,
7218
+ onHeaderEdit: editable ? renameColumn : void 0,
7085
7219
  onCellEdit: handleCellEdit,
7086
7220
  height: "100%",
7087
7221
  className: "!rounded-none !border-0"