@geomak/ui 7.10.0 → 7.11.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
@@ -6503,6 +6503,7 @@ function DataGrid({
6503
6503
  virtualize = true,
6504
6504
  overscan = 4,
6505
6505
  rowNumbers = true,
6506
+ trailingRows = 0,
6506
6507
  onCellEdit,
6507
6508
  className = "",
6508
6509
  style,
@@ -6539,7 +6540,9 @@ function DataGrid({
6539
6540
  if (sortProp === void 0) setInternalSort(next);
6540
6541
  onSortChange?.(next);
6541
6542
  };
6542
- const totalHeight = rows.length * rowHeight;
6543
+ const displayRowCount = rows.length + Math.max(0, trailingRows);
6544
+ const totalHeight = displayRowCount * rowHeight;
6545
+ const rowIndexForDisp = (disp) => disp < rows.length ? order[disp] : disp;
6543
6546
  React36.useEffect(() => {
6544
6547
  const el = scrollRef.current;
6545
6548
  if (!el || typeof ResizeObserver === "undefined") return;
@@ -6551,7 +6554,7 @@ function DataGrid({
6551
6554
  }, []);
6552
6555
  const bodyH = (viewport.h || (typeof height === "number" ? height : 480)) - headerHeight;
6553
6556
  const rowStart = virtualize ? Math.max(0, Math.floor(scroll.top / rowHeight) - overscan) : 0;
6554
- const rowEnd = virtualize ? Math.min(rows.length, Math.ceil((scroll.top + bodyH) / rowHeight) + overscan) : rows.length;
6557
+ const rowEnd = virtualize ? Math.min(displayRowCount, Math.ceil((scroll.top + bodyH) / rowHeight) + overscan) : displayRowCount;
6555
6558
  let colStart = 0;
6556
6559
  let colEnd = columns.length;
6557
6560
  if (virtualize && viewport.w) {
@@ -6569,13 +6572,14 @@ function DataGrid({
6569
6572
  const commit = React36.useCallback(() => {
6570
6573
  if (!editing) return;
6571
6574
  const col = columns[editing.col];
6572
- onCellEdit?.({ row: order[editing.disp], column: col.key, value: draft });
6575
+ const ri = editing.disp < rows.length ? order[editing.disp] : editing.disp;
6576
+ onCellEdit?.({ row: ri, column: col.key, value: draft });
6573
6577
  setEditing(null);
6574
- }, [editing, columns, draft, onCellEdit, order]);
6578
+ }, [editing, columns, draft, onCellEdit, order, rows.length]);
6575
6579
  const startEdit = (disp, col) => {
6576
6580
  const c = columns[col];
6577
6581
  if (!(c.editable ?? editable)) return;
6578
- setDraft(displayValue(rows[order[disp]]?.[c.key] ?? ""));
6582
+ setDraft(displayValue(rows[rowIndexForDisp(disp)]?.[c.key] ?? ""));
6579
6583
  setEditing({ disp, col });
6580
6584
  };
6581
6585
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -6638,7 +6642,7 @@ function DataGrid({
6638
6642
  `g-${disp}`
6639
6643
  )),
6640
6644
  visibleRows.map((disp) => {
6641
- const ri = order[disp];
6645
+ const ri = rowIndexForDisp(disp);
6642
6646
  return visibleCols.map((ci) => {
6643
6647
  const c = columns[ci];
6644
6648
  const isEditing = editing?.disp === disp && editing?.col === ci;
@@ -6681,7 +6685,7 @@ function DataGrid({
6681
6685
  });
6682
6686
  })
6683
6687
  ] }),
6684
- rows.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center text-foreground-muted", style: { top: headerHeight }, children: emptyState })
6688
+ displayRowCount === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center text-foreground-muted", style: { top: headerHeight }, children: emptyState })
6685
6689
  ]
6686
6690
  }
6687
6691
  );
@@ -6699,6 +6703,18 @@ function cellValue(v) {
6699
6703
  if (v != null && typeof v === "object" && "value" in v) return v.value;
6700
6704
  return v;
6701
6705
  }
6706
+ function columnLetter(i) {
6707
+ let s = "";
6708
+ let n = i;
6709
+ do {
6710
+ s = String.fromCharCode(65 + n % 26) + s;
6711
+ n = Math.floor(n / 26) - 1;
6712
+ } while (n >= 0);
6713
+ return s;
6714
+ }
6715
+ function blankSheet(name, cols = 8) {
6716
+ return { name, columns: Array.from({ length: cols }, (_, i) => ({ key: columnLetter(i), label: columnLetter(i) })), rows: [] };
6717
+ }
6702
6718
  function toPlainRows(sheet, columns) {
6703
6719
  return sheet.rows.map((row) => {
6704
6720
  const out = {};
@@ -6731,11 +6747,14 @@ function Spreadsheet({
6731
6747
  fileName,
6732
6748
  virtualize = true,
6733
6749
  sortable = true,
6750
+ emptyRows = 50,
6751
+ allowAddSheet,
6734
6752
  height = 480,
6735
6753
  width,
6736
6754
  className = "",
6737
6755
  style
6738
6756
  }) {
6757
+ const canAddSheet = allowAddSheet ?? editable;
6739
6758
  const [sheets, setSheets] = React36.useState(Array.isArray(source) ? source : null);
6740
6759
  const [active, setActive] = React36.useState(0);
6741
6760
  const [status, setStatus] = React36.useState(Array.isArray(source) ? "ready" : "loading");
@@ -6795,6 +6814,7 @@ function Spreadsheet({
6795
6814
  if (!prev) return prev;
6796
6815
  const next = prev.map((s, i) => i === active ? { ...s, rows: s.rows.map((r) => ({ ...r })) } : s);
6797
6816
  const target = next[active];
6817
+ while (target.rows.length <= row) target.rows.push({});
6798
6818
  const existing = target.rows[row]?.[column];
6799
6819
  const prevValue = cellValue(existing);
6800
6820
  coerced = coerceToCellType(prevValue, value);
@@ -6808,6 +6828,18 @@ function Spreadsheet({
6808
6828
  });
6809
6829
  onCellEdit?.({ sheet: sheets?.[active]?.name ?? "", row, column, value: coerced });
6810
6830
  }, [active, onCellEdit, onChange, sheets]);
6831
+ const addSheet = React36.useCallback(() => {
6832
+ setSheets((prev) => {
6833
+ const list = prev ?? [];
6834
+ const used = new Set(list.map((s) => s.name));
6835
+ let n = list.length + 1;
6836
+ while (used.has(`Sheet ${n}`)) n++;
6837
+ const next = [...list, blankSheet(`Sheet ${n}`)];
6838
+ setActive(next.length - 1);
6839
+ onChange?.(next);
6840
+ return next;
6841
+ });
6842
+ }, [onChange]);
6811
6843
  const baseName = React36.useMemo(
6812
6844
  () => (fileName || (typeof source === "object" && "name" in source ? sourceName(source) : null) || "spreadsheet").replace(/\.[^.]+$/, ""),
6813
6845
  [fileName, source]
@@ -6936,28 +6968,45 @@ function Spreadsheet({
6936
6968
  editable,
6937
6969
  sortable,
6938
6970
  virtualize,
6971
+ trailingRows: emptyRows,
6939
6972
  onCellEdit: handleCellEdit,
6940
6973
  height: "100%",
6941
6974
  className: "!rounded-none !border-0"
6942
6975
  },
6943
6976
  active
6944
6977
  ) }),
6945
- sheets.length > 1 && /* @__PURE__ */ jsxRuntime.jsx("div", { role: "tablist", "aria-label": "Sheets", className: "flex flex-shrink-0 items-center gap-1 overflow-x-auto border-t border-border bg-surface px-2 py-1", children: sheets.map((s, i) => /* @__PURE__ */ jsxRuntime.jsx(
6946
- "button",
6947
- {
6948
- role: "tab",
6949
- type: "button",
6950
- "aria-selected": i === active,
6951
- onClick: () => setActive(i),
6952
- className: cx(
6953
- "flex-shrink-0 rounded-md px-3 py-1 text-xs font-medium transition-colors",
6954
- "focus:outline-none focus-visible:ring-2 focus-visible:ring-accent",
6955
- i === active ? "bg-surface-raised text-foreground shadow-sm" : "text-foreground-secondary hover:bg-surface-raised hover:text-foreground"
6956
- ),
6957
- children: s.name || `Sheet ${i + 1}`
6958
- },
6959
- `${s.name}-${i}`
6960
- )) })
6978
+ (sheets.length > 1 || canAddSheet) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-shrink-0 items-center gap-1 border-t border-border bg-surface px-2 py-1", children: [
6979
+ /* @__PURE__ */ jsxRuntime.jsx("div", { role: "tablist", "aria-label": "Sheets", className: "flex min-w-0 flex-1 items-center gap-1 overflow-x-auto", children: sheets.map((s, i) => {
6980
+ const activeTab = i === active;
6981
+ return /* @__PURE__ */ jsxRuntime.jsx(
6982
+ "button",
6983
+ {
6984
+ role: "tab",
6985
+ type: "button",
6986
+ "aria-selected": activeTab,
6987
+ onClick: () => setActive(i),
6988
+ className: cx(
6989
+ "flex-shrink-0 rounded-t-md border-t-2 px-3 py-1 text-xs transition-colors",
6990
+ "focus:outline-none focus-visible:ring-2 focus-visible:ring-accent",
6991
+ activeTab ? "border-t-accent bg-surface-raised font-semibold text-foreground shadow-sm" : "border-t-transparent font-medium text-foreground-secondary hover:bg-surface-raised hover:text-foreground"
6992
+ ),
6993
+ children: s.name || `Sheet ${i + 1}`
6994
+ },
6995
+ `${s.name}-${i}`
6996
+ );
6997
+ }) }),
6998
+ canAddSheet && /* @__PURE__ */ jsxRuntime.jsx(
6999
+ "button",
7000
+ {
7001
+ type: "button",
7002
+ onClick: addSheet,
7003
+ title: "Add sheet",
7004
+ "aria-label": "Add sheet",
7005
+ className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-md text-foreground-muted transition-colors hover:bg-surface-raised hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-accent",
7006
+ children: /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: "h-4 w-4", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", d: "M12 5v14M5 12h14" }) })
7007
+ }
7008
+ )
7009
+ ] })
6961
7010
  ] });
6962
7011
  }
6963
7012
  var DownloadIcon2 = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: "h-4 w-4", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 3v12m0 0 4-4m-4 4-4-4M4 21h16" }) });