@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.cjs CHANGED
@@ -6507,6 +6507,15 @@ function displayValue(v) {
6507
6507
  if (v == null) return "";
6508
6508
  return String(v);
6509
6509
  }
6510
+ function colLetter(i) {
6511
+ let s = "";
6512
+ let n = i;
6513
+ do {
6514
+ s = String.fromCharCode(65 + n % 26) + s;
6515
+ n = Math.floor(n / 26) - 1;
6516
+ } while (n >= 0);
6517
+ return s;
6518
+ }
6510
6519
  function DataGrid({
6511
6520
  columns,
6512
6521
  rows,
@@ -6522,10 +6531,12 @@ function DataGrid({
6522
6531
  overscan = 4,
6523
6532
  rowNumbers = true,
6524
6533
  trailingRows = 0,
6534
+ trailingCols = 0,
6525
6535
  onCellEdit,
6526
6536
  contextMenu = false,
6527
6537
  onInsertRow,
6528
6538
  onDeleteRow,
6539
+ onInsertColumn,
6529
6540
  className = "",
6530
6541
  style,
6531
6542
  emptyState = "No data"
@@ -6542,8 +6553,16 @@ function DataGrid({
6542
6553
  const sort = sortProp !== void 0 ? sortProp : internalSort;
6543
6554
  const gutter = rowNumbers ? GUTTER : 0;
6544
6555
  const colSortable = (c) => c.sortable ?? sortable;
6556
+ const cols = React36.useMemo(() => {
6557
+ if (trailingCols <= 0) return columns;
6558
+ const extra = Array.from({ length: trailingCols }, (_, k) => {
6559
+ const idx = columns.length + k;
6560
+ return { key: `__c${idx}`, label: colLetter(idx), editable: false, sortable: false };
6561
+ });
6562
+ return [...columns, ...extra];
6563
+ }, [columns, trailingCols]);
6545
6564
  const { widths, offsets, totalWidth } = React36.useMemo(() => {
6546
- const widths2 = columns.map((c) => resolveWidth(c.width));
6565
+ const widths2 = cols.map((c) => resolveWidth(c.width));
6547
6566
  const offsets2 = [];
6548
6567
  let acc = 0;
6549
6568
  for (const w of widths2) {
@@ -6551,7 +6570,7 @@ function DataGrid({
6551
6570
  acc += w;
6552
6571
  }
6553
6572
  return { widths: widths2, offsets: offsets2, totalWidth: acc };
6554
- }, [columns]);
6573
+ }, [cols]);
6555
6574
  const order = React36.useMemo(() => {
6556
6575
  const idx = rows.map((_, i) => i);
6557
6576
  if (!sort) return idx;
@@ -6580,45 +6599,45 @@ function DataGrid({
6580
6599
  const rowStart = virtualize ? Math.max(0, Math.floor(scroll.top / rowHeight) - overscan) : 0;
6581
6600
  const rowEnd = virtualize ? Math.min(displayRowCount, Math.ceil((scroll.top + bodyH) / rowHeight) + overscan) : displayRowCount;
6582
6601
  let colStart = 0;
6583
- let colEnd = columns.length;
6602
+ let colEnd = cols.length;
6584
6603
  if (virtualize && viewport.w) {
6585
6604
  const viewLeft = scroll.left;
6586
6605
  const viewRight = scroll.left + (viewport.w - gutter);
6587
6606
  colStart = Math.max(0, offsets.findIndex((o, i) => o + widths[i] > viewLeft));
6588
6607
  if (colStart < 0) colStart = 0;
6589
6608
  colEnd = offsets.findIndex((o) => o > viewRight);
6590
- colEnd = colEnd === -1 ? columns.length : Math.min(columns.length, colEnd + 1);
6609
+ colEnd = colEnd === -1 ? cols.length : Math.min(cols.length, colEnd + 1);
6591
6610
  colStart = Math.max(0, colStart - overscan);
6592
- colEnd = Math.min(columns.length, colEnd + overscan);
6611
+ colEnd = Math.min(cols.length, colEnd + overscan);
6593
6612
  }
6594
6613
  const visibleRows = Array.from({ length: Math.max(0, rowEnd - rowStart) }, (_, i) => rowStart + i);
6595
6614
  const visibleCols = Array.from({ length: Math.max(0, colEnd - colStart) }, (_, i) => colStart + i);
6596
6615
  const commit = React36.useCallback(() => {
6597
6616
  if (!editing) return;
6598
- const col = columns[editing.col];
6617
+ const col = cols[editing.col];
6599
6618
  const ri = editing.disp < rows.length ? order[editing.disp] : editing.disp;
6600
6619
  onCellEdit?.({ row: ri, column: col.key, value: draft });
6601
6620
  setEditing(null);
6602
- }, [editing, columns, draft, onCellEdit, order, rows.length]);
6621
+ }, [editing, cols, draft, onCellEdit, order, rows.length]);
6603
6622
  const startEdit = (disp, col) => {
6604
- const c = columns[col];
6623
+ const c = cols[col];
6605
6624
  if (!(c.editable ?? editable)) return;
6606
6625
  setDraft(displayValue(rows[rowIndexForDisp(disp)]?.[c.key] ?? ""));
6607
6626
  setEditing({ disp, col });
6608
6627
  };
6609
- const cellText = (sel) => displayValue(rows[rowIndexForDisp(sel.disp)]?.[columns[sel.col].key] ?? "");
6628
+ const cellText = (sel) => displayValue(rows[rowIndexForDisp(sel.disp)]?.[cols[sel.col].key] ?? "");
6610
6629
  const copyCell = React36.useCallback(async () => {
6611
6630
  if (!selected) return;
6612
6631
  try {
6613
6632
  await navigator.clipboard?.writeText(cellText(selected));
6614
6633
  } catch {
6615
6634
  }
6616
- }, [selected, rows, columns, order]);
6635
+ }, [selected, rows, cols, order]);
6617
6636
  const cutCell = React36.useCallback(async () => {
6618
6637
  if (!selected) return;
6619
6638
  await copyCell();
6620
- onCellEdit?.({ row: rowIndexForDisp(selected.disp), column: columns[selected.col].key, value: "" });
6621
- }, [selected, copyCell, onCellEdit, columns, order, rows.length]);
6639
+ onCellEdit?.({ row: rowIndexForDisp(selected.disp), column: cols[selected.col].key, value: "" });
6640
+ }, [selected, copyCell, onCellEdit, cols, order, rows.length]);
6622
6641
  const pasteCell = React36.useCallback(async () => {
6623
6642
  if (!selected) return;
6624
6643
  let text = "";
@@ -6627,9 +6646,16 @@ function DataGrid({
6627
6646
  } catch {
6628
6647
  return;
6629
6648
  }
6630
- onCellEdit?.({ row: rowIndexForDisp(selected.disp), column: columns[selected.col].key, value: text });
6631
- }, [selected, onCellEdit, columns, order, rows.length]);
6649
+ onCellEdit?.({ row: rowIndexForDisp(selected.disp), column: cols[selected.col].key, value: text });
6650
+ }, [selected, onCellEdit, cols, order, rows.length]);
6632
6651
  const ctxItems = React36.useMemo(() => {
6652
+ if (ctxTarget?.kind === "header") {
6653
+ const ci = ctxTarget.col;
6654
+ return [
6655
+ { key: "left", value: "Add column to the left", disabled: !onInsertColumn, onClick: () => onInsertColumn?.(ci) },
6656
+ { key: "right", value: "Add column to the right", disabled: !onInsertColumn, onClick: () => onInsertColumn?.(ci + 1) }
6657
+ ];
6658
+ }
6633
6659
  if (ctxTarget?.kind === "row") {
6634
6660
  const ri = rowIndexForDisp(ctxTarget.disp);
6635
6661
  const isData = ctxTarget.disp < rows.length;
@@ -6644,7 +6670,7 @@ function DataGrid({
6644
6670
  { key: "cut", value: "Cut", disabled: !editable, onClick: () => void cutCell() },
6645
6671
  { key: "paste", value: "Paste", disabled: !editable, onClick: () => void pasteCell() }
6646
6672
  ];
6647
- }, [ctxTarget, rows.length, editable, onInsertRow, onDeleteRow, copyCell, cutCell, pasteCell, order]);
6673
+ }, [ctxTarget, rows.length, editable, onInsertRow, onDeleteRow, onInsertColumn, copyCell, cutCell, pasteCell, order]);
6648
6674
  const rowHighlighted = (disp) => hoveredRow === disp || selected?.disp === disp;
6649
6675
  const gridInner = /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative", width: gutter + totalWidth + END_PAD, height: headerHeight + totalHeight + END_PAD }, children: [
6650
6676
  rowNumbers && /* @__PURE__ */ jsxRuntime.jsx(
@@ -6655,7 +6681,7 @@ function DataGrid({
6655
6681
  }
6656
6682
  ),
6657
6683
  visibleCols.map((ci) => {
6658
- const c = columns[ci];
6684
+ const c = cols[ci];
6659
6685
  const sortDir = sort?.key === c.key ? sort.dir : null;
6660
6686
  const canSort = colSortable(c);
6661
6687
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -6664,6 +6690,7 @@ function DataGrid({
6664
6690
  role: "columnheader",
6665
6691
  "aria-sort": sortDir ? sortDir === "asc" ? "ascending" : "descending" : void 0,
6666
6692
  onClick: canSort ? () => toggleSort(c.key) : void 0,
6693
+ onContextMenu: contextMenu ? () => setCtxTarget({ kind: "header", col: ci }) : void 0,
6667
6694
  className: cx(
6668
6695
  "flex items-center gap-1 border-b border-r border-border bg-surface px-3 font-medium text-foreground-secondary",
6669
6696
  canSort && "cursor-pointer select-none hover:text-foreground"
@@ -6706,7 +6733,7 @@ function DataGrid({
6706
6733
  const ri = rowIndexForDisp(disp);
6707
6734
  const hi = rowHighlighted(disp);
6708
6735
  return visibleCols.map((ci) => {
6709
- const c = columns[ci];
6736
+ const c = cols[ci];
6710
6737
  const isEditing = editing?.disp === disp && editing?.col === ci;
6711
6738
  const isSelected = selected?.disp === disp && selected?.col === ci;
6712
6739
  const canEdit = c.editable ?? editable;
@@ -6801,8 +6828,24 @@ function columnLetter(i) {
6801
6828
  } while (n >= 0);
6802
6829
  return s;
6803
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
+ }
6804
6847
  function blankSheet(name, cols = 8) {
6805
- return { name, columns: Array.from({ length: cols }, (_, i) => ({ key: columnLetter(i), label: columnLetter(i) })), rows: [] };
6848
+ return { name, columns: makeColumns([], cols), rows: [] };
6806
6849
  }
6807
6850
  function parseWorkbook(XLSX, bytes) {
6808
6851
  const wb = XLSX.read(bytes, { type: "array" });
@@ -6857,6 +6900,7 @@ function Spreadsheet({
6857
6900
  virtualize = true,
6858
6901
  sortable = true,
6859
6902
  emptyRows = 50,
6903
+ emptyCols = 6,
6860
6904
  allowAddSheet,
6861
6905
  height = 480,
6862
6906
  width,
@@ -6947,6 +6991,33 @@ function Spreadsheet({
6947
6991
  return next;
6948
6992
  });
6949
6993
  }, [active, onChange]);
6994
+ const add10Columns = React36.useCallback(() => {
6995
+ setSheets((prev) => {
6996
+ if (!prev) return prev;
6997
+ const next = prev.map((s, i) => {
6998
+ if (i !== active) return s;
6999
+ const cols = toColumns(s.columns);
7000
+ return { ...s, columns: [...cols, ...makeColumns(cols, 10)] };
7001
+ });
7002
+ onChange?.(next);
7003
+ return next;
7004
+ });
7005
+ }, [active, onChange]);
7006
+ const insertColumn = React36.useCallback((index) => {
7007
+ setSheets((prev) => {
7008
+ if (!prev) return prev;
7009
+ const next = prev.map((s, i) => {
7010
+ if (i !== active) return s;
7011
+ const cols = toColumns(s.columns);
7012
+ const at = Math.max(0, Math.min(index, cols.length));
7013
+ const out = [...cols];
7014
+ out.splice(at, 0, makeColumns(cols, 1)[0]);
7015
+ return { ...s, columns: out };
7016
+ });
7017
+ onChange?.(next);
7018
+ return next;
7019
+ });
7020
+ }, [active, onChange]);
6950
7021
  const insertRow = React36.useCallback((index) => {
6951
7022
  setSheets((prev) => {
6952
7023
  if (!prev) return prev;
@@ -7086,7 +7157,8 @@ function Spreadsheet({
7086
7157
  const sheetItems = [
7087
7158
  { key: "add", label: "Add Sheet", onSelect: addSheet },
7088
7159
  { key: "delete", label: "Delete Sheet", danger: true, disabled: sheets.length <= 1, onSelect: () => deleteSheet(active) },
7089
- { key: "add100", label: "Add 100 rows", separatorBefore: true, onSelect: add100Rows }
7160
+ { key: "add100", label: "Add 100 rows", separatorBefore: true, onSelect: add100Rows },
7161
+ { key: "add10cols", label: "Add 10 columns", onSelect: add10Columns }
7090
7162
  ];
7091
7163
  const canDeleteSheet = editable && sheets.length > 1;
7092
7164
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx("flex flex-col overflow-hidden rounded-lg border border-border bg-surface-raised", className), style: { height, width, ...style }, children: [
@@ -7115,9 +7187,11 @@ function Spreadsheet({
7115
7187
  sortable,
7116
7188
  virtualize,
7117
7189
  trailingRows: emptyRows,
7190
+ trailingCols: emptyCols,
7118
7191
  contextMenu: true,
7119
7192
  onInsertRow: editable ? insertRow : void 0,
7120
7193
  onDeleteRow: editable ? deleteRow : void 0,
7194
+ onInsertColumn: editable ? insertColumn : void 0,
7121
7195
  onCellEdit: handleCellEdit,
7122
7196
  height: "100%",
7123
7197
  className: "!rounded-none !border-0"