@geomak/ui 7.13.0 → 7.14.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,22 @@ 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;
2768
2772
  className?: string;
2769
2773
  style?: react__default.CSSProperties;
2770
2774
  /** Shown when there are no rows. */
@@ -2781,7 +2785,7 @@ interface DataGridProps {
2781
2785
  * `onCellEdit` on commit. Wrap it with {@link Spreadsheet} for multi-sheet
2782
2786
  * switching, file parsing and export.
2783
2787
  */
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;
2788
+ declare function DataGrid({ columns, rows, rowHeight, headerHeight, height, width, editable, sortable, sort: sortProp, onSortChange, virtualize, overscan, rowNumbers, trailingRows, trailingCols, onCellEdit, contextMenu, onInsertRow, onDeleteRow, onInsertColumn, className, style, emptyState, }: DataGridProps): react_jsx_runtime.JSX.Element;
2785
2789
 
2786
2790
  interface Cell {
2787
2791
  value: CellValue;
@@ -2815,6 +2819,8 @@ interface SpreadsheetProps {
2815
2819
  sortable?: boolean;
2816
2820
  /** Blank rows kept below the data (the spreadsheet "slack"). Default 50. */
2817
2821
  emptyRows?: number;
2822
+ /** Blank letter-labelled columns kept to the right of the data. Default 6. */
2823
+ emptyCols?: number;
2818
2824
  /** Show a “+” to add a blank sheet. Defaults to `editable`. */
2819
2825
  allowAddSheet?: boolean;
2820
2826
  /** Overall height. Default 480. */
@@ -2833,7 +2839,7 @@ interface SpreadsheetProps {
2833
2839
  * Exports to multi-sheet `.xlsx`, `.csv` (active sheet, BOM-prefixed) or a
2834
2840
  * paginated table `.pdf` (jsPDF, lazy).
2835
2841
  */
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;
2842
+ 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
2843
 
2838
2844
  interface ThemeSwitchProps {
2839
2845
  checked: boolean;
package/dist/index.d.ts CHANGED
@@ -2753,18 +2753,22 @@ 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;
2768
2772
  className?: string;
2769
2773
  style?: react__default.CSSProperties;
2770
2774
  /** Shown when there are no rows. */
@@ -2781,7 +2785,7 @@ interface DataGridProps {
2781
2785
  * `onCellEdit` on commit. Wrap it with {@link Spreadsheet} for multi-sheet
2782
2786
  * switching, file parsing and export.
2783
2787
  */
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;
2788
+ declare function DataGrid({ columns, rows, rowHeight, headerHeight, height, width, editable, sortable, sort: sortProp, onSortChange, virtualize, overscan, rowNumbers, trailingRows, trailingCols, onCellEdit, contextMenu, onInsertRow, onDeleteRow, onInsertColumn, className, style, emptyState, }: DataGridProps): react_jsx_runtime.JSX.Element;
2785
2789
 
2786
2790
  interface Cell {
2787
2791
  value: CellValue;
@@ -2815,6 +2819,8 @@ interface SpreadsheetProps {
2815
2819
  sortable?: boolean;
2816
2820
  /** Blank rows kept below the data (the spreadsheet "slack"). Default 50. */
2817
2821
  emptyRows?: number;
2822
+ /** Blank letter-labelled columns kept to the right of the data. Default 6. */
2823
+ emptyCols?: number;
2818
2824
  /** Show a “+” to add a blank sheet. Defaults to `editable`. */
2819
2825
  allowAddSheet?: boolean;
2820
2826
  /** Overall height. Default 480. */
@@ -2833,7 +2839,7 @@ interface SpreadsheetProps {
2833
2839
  * Exports to multi-sheet `.xlsx`, `.csv` (active sheet, BOM-prefixed) or a
2834
2840
  * paginated table `.pdf` (jsPDF, lazy).
2835
2841
  */
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;
2842
+ 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
2843
 
2838
2844
  interface ThemeSwitchProps {
2839
2845
  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,12 @@ 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,
6493
6504
  className = "",
6494
6505
  style,
6495
6506
  emptyState = "No data"
@@ -6506,8 +6517,16 @@ function DataGrid({
6506
6517
  const sort = sortProp !== void 0 ? sortProp : internalSort;
6507
6518
  const gutter = rowNumbers ? GUTTER : 0;
6508
6519
  const colSortable = (c) => c.sortable ?? sortable;
6520
+ const cols = useMemo(() => {
6521
+ if (trailingCols <= 0) return columns;
6522
+ const extra = Array.from({ length: trailingCols }, (_, k) => {
6523
+ const idx = columns.length + k;
6524
+ return { key: `__c${idx}`, label: colLetter(idx), editable: false, sortable: false };
6525
+ });
6526
+ return [...columns, ...extra];
6527
+ }, [columns, trailingCols]);
6509
6528
  const { widths, offsets, totalWidth } = useMemo(() => {
6510
- const widths2 = columns.map((c) => resolveWidth(c.width));
6529
+ const widths2 = cols.map((c) => resolveWidth(c.width));
6511
6530
  const offsets2 = [];
6512
6531
  let acc = 0;
6513
6532
  for (const w of widths2) {
@@ -6515,7 +6534,7 @@ function DataGrid({
6515
6534
  acc += w;
6516
6535
  }
6517
6536
  return { widths: widths2, offsets: offsets2, totalWidth: acc };
6518
- }, [columns]);
6537
+ }, [cols]);
6519
6538
  const order = useMemo(() => {
6520
6539
  const idx = rows.map((_, i) => i);
6521
6540
  if (!sort) return idx;
@@ -6544,45 +6563,45 @@ function DataGrid({
6544
6563
  const rowStart = virtualize ? Math.max(0, Math.floor(scroll.top / rowHeight) - overscan) : 0;
6545
6564
  const rowEnd = virtualize ? Math.min(displayRowCount, Math.ceil((scroll.top + bodyH) / rowHeight) + overscan) : displayRowCount;
6546
6565
  let colStart = 0;
6547
- let colEnd = columns.length;
6566
+ let colEnd = cols.length;
6548
6567
  if (virtualize && viewport.w) {
6549
6568
  const viewLeft = scroll.left;
6550
6569
  const viewRight = scroll.left + (viewport.w - gutter);
6551
6570
  colStart = Math.max(0, offsets.findIndex((o, i) => o + widths[i] > viewLeft));
6552
6571
  if (colStart < 0) colStart = 0;
6553
6572
  colEnd = offsets.findIndex((o) => o > viewRight);
6554
- colEnd = colEnd === -1 ? columns.length : Math.min(columns.length, colEnd + 1);
6573
+ colEnd = colEnd === -1 ? cols.length : Math.min(cols.length, colEnd + 1);
6555
6574
  colStart = Math.max(0, colStart - overscan);
6556
- colEnd = Math.min(columns.length, colEnd + overscan);
6575
+ colEnd = Math.min(cols.length, colEnd + overscan);
6557
6576
  }
6558
6577
  const visibleRows = Array.from({ length: Math.max(0, rowEnd - rowStart) }, (_, i) => rowStart + i);
6559
6578
  const visibleCols = Array.from({ length: Math.max(0, colEnd - colStart) }, (_, i) => colStart + i);
6560
6579
  const commit = useCallback(() => {
6561
6580
  if (!editing) return;
6562
- const col = columns[editing.col];
6581
+ const col = cols[editing.col];
6563
6582
  const ri = editing.disp < rows.length ? order[editing.disp] : editing.disp;
6564
6583
  onCellEdit?.({ row: ri, column: col.key, value: draft });
6565
6584
  setEditing(null);
6566
- }, [editing, columns, draft, onCellEdit, order, rows.length]);
6585
+ }, [editing, cols, draft, onCellEdit, order, rows.length]);
6567
6586
  const startEdit = (disp, col) => {
6568
- const c = columns[col];
6587
+ const c = cols[col];
6569
6588
  if (!(c.editable ?? editable)) return;
6570
6589
  setDraft(displayValue(rows[rowIndexForDisp(disp)]?.[c.key] ?? ""));
6571
6590
  setEditing({ disp, col });
6572
6591
  };
6573
- const cellText = (sel) => displayValue(rows[rowIndexForDisp(sel.disp)]?.[columns[sel.col].key] ?? "");
6592
+ const cellText = (sel) => displayValue(rows[rowIndexForDisp(sel.disp)]?.[cols[sel.col].key] ?? "");
6574
6593
  const copyCell = useCallback(async () => {
6575
6594
  if (!selected) return;
6576
6595
  try {
6577
6596
  await navigator.clipboard?.writeText(cellText(selected));
6578
6597
  } catch {
6579
6598
  }
6580
- }, [selected, rows, columns, order]);
6599
+ }, [selected, rows, cols, order]);
6581
6600
  const cutCell = useCallback(async () => {
6582
6601
  if (!selected) return;
6583
6602
  await copyCell();
6584
- onCellEdit?.({ row: rowIndexForDisp(selected.disp), column: columns[selected.col].key, value: "" });
6585
- }, [selected, copyCell, onCellEdit, columns, order, rows.length]);
6603
+ onCellEdit?.({ row: rowIndexForDisp(selected.disp), column: cols[selected.col].key, value: "" });
6604
+ }, [selected, copyCell, onCellEdit, cols, order, rows.length]);
6586
6605
  const pasteCell = useCallback(async () => {
6587
6606
  if (!selected) return;
6588
6607
  let text = "";
@@ -6591,9 +6610,16 @@ function DataGrid({
6591
6610
  } catch {
6592
6611
  return;
6593
6612
  }
6594
- onCellEdit?.({ row: rowIndexForDisp(selected.disp), column: columns[selected.col].key, value: text });
6595
- }, [selected, onCellEdit, columns, order, rows.length]);
6613
+ onCellEdit?.({ row: rowIndexForDisp(selected.disp), column: cols[selected.col].key, value: text });
6614
+ }, [selected, onCellEdit, cols, order, rows.length]);
6596
6615
  const ctxItems = useMemo(() => {
6616
+ if (ctxTarget?.kind === "header") {
6617
+ const ci = ctxTarget.col;
6618
+ return [
6619
+ { key: "left", value: "Add column to the left", disabled: !onInsertColumn, onClick: () => onInsertColumn?.(ci) },
6620
+ { key: "right", value: "Add column to the right", disabled: !onInsertColumn, onClick: () => onInsertColumn?.(ci + 1) }
6621
+ ];
6622
+ }
6597
6623
  if (ctxTarget?.kind === "row") {
6598
6624
  const ri = rowIndexForDisp(ctxTarget.disp);
6599
6625
  const isData = ctxTarget.disp < rows.length;
@@ -6608,7 +6634,7 @@ function DataGrid({
6608
6634
  { key: "cut", value: "Cut", disabled: !editable, onClick: () => void cutCell() },
6609
6635
  { key: "paste", value: "Paste", disabled: !editable, onClick: () => void pasteCell() }
6610
6636
  ];
6611
- }, [ctxTarget, rows.length, editable, onInsertRow, onDeleteRow, copyCell, cutCell, pasteCell, order]);
6637
+ }, [ctxTarget, rows.length, editable, onInsertRow, onDeleteRow, onInsertColumn, copyCell, cutCell, pasteCell, order]);
6612
6638
  const rowHighlighted = (disp) => hoveredRow === disp || selected?.disp === disp;
6613
6639
  const gridInner = /* @__PURE__ */ jsxs("div", { style: { position: "relative", width: gutter + totalWidth + END_PAD, height: headerHeight + totalHeight + END_PAD }, children: [
6614
6640
  rowNumbers && /* @__PURE__ */ jsx(
@@ -6619,7 +6645,7 @@ function DataGrid({
6619
6645
  }
6620
6646
  ),
6621
6647
  visibleCols.map((ci) => {
6622
- const c = columns[ci];
6648
+ const c = cols[ci];
6623
6649
  const sortDir = sort?.key === c.key ? sort.dir : null;
6624
6650
  const canSort = colSortable(c);
6625
6651
  return /* @__PURE__ */ jsxs(
@@ -6628,6 +6654,7 @@ function DataGrid({
6628
6654
  role: "columnheader",
6629
6655
  "aria-sort": sortDir ? sortDir === "asc" ? "ascending" : "descending" : void 0,
6630
6656
  onClick: canSort ? () => toggleSort(c.key) : void 0,
6657
+ onContextMenu: contextMenu ? () => setCtxTarget({ kind: "header", col: ci }) : void 0,
6631
6658
  className: cx(
6632
6659
  "flex items-center gap-1 border-b border-r border-border bg-surface px-3 font-medium text-foreground-secondary",
6633
6660
  canSort && "cursor-pointer select-none hover:text-foreground"
@@ -6670,7 +6697,7 @@ function DataGrid({
6670
6697
  const ri = rowIndexForDisp(disp);
6671
6698
  const hi = rowHighlighted(disp);
6672
6699
  return visibleCols.map((ci) => {
6673
- const c = columns[ci];
6700
+ const c = cols[ci];
6674
6701
  const isEditing = editing?.disp === disp && editing?.col === ci;
6675
6702
  const isSelected = selected?.disp === disp && selected?.col === ci;
6676
6703
  const canEdit = c.editable ?? editable;
@@ -6765,8 +6792,24 @@ function columnLetter(i) {
6765
6792
  } while (n >= 0);
6766
6793
  return s;
6767
6794
  }
6795
+ function makeColumns(existing, count) {
6796
+ const used = new Set(existing.map((c) => c.key));
6797
+ const out = [];
6798
+ let n = existing.length;
6799
+ for (let k = 0; k < count; k++) {
6800
+ let key = columnLetter(n);
6801
+ while (used.has(key)) {
6802
+ n++;
6803
+ key = columnLetter(n);
6804
+ }
6805
+ used.add(key);
6806
+ out.push({ key, label: key });
6807
+ n++;
6808
+ }
6809
+ return out;
6810
+ }
6768
6811
  function blankSheet(name, cols = 8) {
6769
- return { name, columns: Array.from({ length: cols }, (_, i) => ({ key: columnLetter(i), label: columnLetter(i) })), rows: [] };
6812
+ return { name, columns: makeColumns([], cols), rows: [] };
6770
6813
  }
6771
6814
  function parseWorkbook(XLSX, bytes) {
6772
6815
  const wb = XLSX.read(bytes, { type: "array" });
@@ -6821,6 +6864,7 @@ function Spreadsheet({
6821
6864
  virtualize = true,
6822
6865
  sortable = true,
6823
6866
  emptyRows = 50,
6867
+ emptyCols = 6,
6824
6868
  allowAddSheet,
6825
6869
  height = 480,
6826
6870
  width,
@@ -6911,6 +6955,33 @@ function Spreadsheet({
6911
6955
  return next;
6912
6956
  });
6913
6957
  }, [active, onChange]);
6958
+ const add10Columns = useCallback(() => {
6959
+ setSheets((prev) => {
6960
+ if (!prev) return prev;
6961
+ const next = prev.map((s, i) => {
6962
+ if (i !== active) return s;
6963
+ const cols = toColumns(s.columns);
6964
+ return { ...s, columns: [...cols, ...makeColumns(cols, 10)] };
6965
+ });
6966
+ onChange?.(next);
6967
+ return next;
6968
+ });
6969
+ }, [active, onChange]);
6970
+ const insertColumn = useCallback((index) => {
6971
+ setSheets((prev) => {
6972
+ if (!prev) return prev;
6973
+ const next = prev.map((s, i) => {
6974
+ if (i !== active) return s;
6975
+ const cols = toColumns(s.columns);
6976
+ const at = Math.max(0, Math.min(index, cols.length));
6977
+ const out = [...cols];
6978
+ out.splice(at, 0, makeColumns(cols, 1)[0]);
6979
+ return { ...s, columns: out };
6980
+ });
6981
+ onChange?.(next);
6982
+ return next;
6983
+ });
6984
+ }, [active, onChange]);
6914
6985
  const insertRow = useCallback((index) => {
6915
6986
  setSheets((prev) => {
6916
6987
  if (!prev) return prev;
@@ -7050,7 +7121,8 @@ function Spreadsheet({
7050
7121
  const sheetItems = [
7051
7122
  { key: "add", label: "Add Sheet", onSelect: addSheet },
7052
7123
  { 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 }
7124
+ { key: "add100", label: "Add 100 rows", separatorBefore: true, onSelect: add100Rows },
7125
+ { key: "add10cols", label: "Add 10 columns", onSelect: add10Columns }
7054
7126
  ];
7055
7127
  const canDeleteSheet = editable && sheets.length > 1;
7056
7128
  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: [
@@ -7079,9 +7151,11 @@ function Spreadsheet({
7079
7151
  sortable,
7080
7152
  virtualize,
7081
7153
  trailingRows: emptyRows,
7154
+ trailingCols: emptyCols,
7082
7155
  contextMenu: true,
7083
7156
  onInsertRow: editable ? insertRow : void 0,
7084
7157
  onDeleteRow: editable ? deleteRow : void 0,
7158
+ onInsertColumn: editable ? insertColumn : void 0,
7085
7159
  onCellEdit: handleCellEdit,
7086
7160
  height: "100%",
7087
7161
  className: "!rounded-none !border-0"