@geomak/ui 7.10.0 → 7.12.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 +128 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -3
- package/dist/index.d.ts +13 -3
- package/dist/index.js +128 -55
- package/dist/index.js.map +1 -1
- package/dist/styles.css +12 -0
- package/package.json +1 -1
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
|
|
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(
|
|
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
|
-
|
|
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[
|
|
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 =
|
|
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
|
-
|
|
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,25 @@ 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]);
|
|
6843
|
+
const deleteSheet = React36.useCallback((index) => {
|
|
6844
|
+
if (!sheets || sheets.length <= 1) return;
|
|
6845
|
+
const next = sheets.filter((_, i) => i !== index);
|
|
6846
|
+
setSheets(next);
|
|
6847
|
+
setActive((a) => Math.max(0, Math.min(index < a ? a - 1 : a, next.length - 1)));
|
|
6848
|
+
onChange?.(next);
|
|
6849
|
+
}, [sheets, onChange]);
|
|
6811
6850
|
const baseName = React36.useMemo(
|
|
6812
6851
|
() => (fileName || (typeof source === "object" && "name" in source ? sourceName(source) : null) || "spreadsheet").replace(/\.[^.]+$/, ""),
|
|
6813
6852
|
[fileName, source]
|
|
@@ -6894,39 +6933,21 @@ function Spreadsheet({
|
|
|
6894
6933
|
}
|
|
6895
6934
|
const formats = exportFormats || [];
|
|
6896
6935
|
const exportLabels = {
|
|
6897
|
-
xlsx: "Excel
|
|
6898
|
-
csv: "
|
|
6899
|
-
pdf: "PDF
|
|
6936
|
+
xlsx: "Export to Excel (.xlsx)",
|
|
6937
|
+
csv: "Export to CSV (.csv)",
|
|
6938
|
+
pdf: "Export to PDF (.pdf)"
|
|
6900
6939
|
};
|
|
6940
|
+
const fileItems = formats.map((fmt) => ({ key: fmt, label: exportLabels[fmt], onSelect: () => runExport(fmt) }));
|
|
6941
|
+
const sheetItems = [
|
|
6942
|
+
{ key: "insert", label: "Insert sheet", onSelect: addSheet },
|
|
6943
|
+
{ key: "delete", label: "Delete sheet", danger: true, disabled: sheets.length <= 1, separatorBefore: true, onSelect: () => deleteSheet(active) }
|
|
6944
|
+
];
|
|
6945
|
+
const showMenuBar = fileItems.length > 0 || editable;
|
|
6946
|
+
const canDeleteSheet = editable && sheets.length > 1;
|
|
6901
6947
|
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: [
|
|
6902
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-shrink-0 items-center gap-
|
|
6903
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6904
|
-
/* @__PURE__ */ jsxRuntime.
|
|
6905
|
-
plainRows.length.toLocaleString(),
|
|
6906
|
-
" ",
|
|
6907
|
-
plainRows.length === 1 ? "row" : "rows",
|
|
6908
|
-
" \xB7 ",
|
|
6909
|
-
columns.length,
|
|
6910
|
-
" cols"
|
|
6911
|
-
] }),
|
|
6912
|
-
formats.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
6913
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-5 w-px flex-shrink-0 bg-border", "aria-hidden": "true" }),
|
|
6914
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6915
|
-
MenuButton,
|
|
6916
|
-
{
|
|
6917
|
-
label: "Export",
|
|
6918
|
-
size: "sm",
|
|
6919
|
-
variant: "outline",
|
|
6920
|
-
align: "end",
|
|
6921
|
-
icon: /* @__PURE__ */ jsxRuntime.jsx(DownloadIcon2, {}),
|
|
6922
|
-
items: formats.map((fmt) => ({
|
|
6923
|
-
key: fmt,
|
|
6924
|
-
label: exportLabels[fmt],
|
|
6925
|
-
onSelect: () => runExport(fmt)
|
|
6926
|
-
}))
|
|
6927
|
-
}
|
|
6928
|
-
)
|
|
6929
|
-
] })
|
|
6948
|
+
showMenuBar && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-shrink-0 items-center gap-0.5 border-b border-border bg-surface px-1.5 py-1", children: [
|
|
6949
|
+
fileItems.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(MenuButton, { label: "File", variant: "ghost", size: "sm", hideChevron: true, items: fileItems }),
|
|
6950
|
+
editable && /* @__PURE__ */ jsxRuntime.jsx(MenuButton, { label: "Sheet", variant: "ghost", size: "sm", hideChevron: true, items: sheetItems })
|
|
6930
6951
|
] }),
|
|
6931
6952
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
6932
6953
|
DataGrid,
|
|
@@ -6936,31 +6957,83 @@ function Spreadsheet({
|
|
|
6936
6957
|
editable,
|
|
6937
6958
|
sortable,
|
|
6938
6959
|
virtualize,
|
|
6960
|
+
trailingRows: emptyRows,
|
|
6939
6961
|
onCellEdit: handleCellEdit,
|
|
6940
6962
|
height: "100%",
|
|
6941
6963
|
className: "!rounded-none !border-0"
|
|
6942
6964
|
},
|
|
6943
6965
|
active
|
|
6944
6966
|
) }),
|
|
6945
|
-
|
|
6946
|
-
"
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6967
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-shrink-0 items-center gap-2 border-t border-border bg-surface px-2 py-1", children: [
|
|
6968
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { role: "tablist", "aria-label": "Sheets", className: "flex min-w-0 flex-1 items-center gap-1 overflow-x-auto", children: [
|
|
6969
|
+
sheets.map((s, i) => {
|
|
6970
|
+
const activeTab = i === active;
|
|
6971
|
+
const name = s.name || `Sheet ${i + 1}`;
|
|
6972
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
6973
|
+
"div",
|
|
6974
|
+
{
|
|
6975
|
+
role: "tab",
|
|
6976
|
+
tabIndex: 0,
|
|
6977
|
+
"aria-selected": activeTab,
|
|
6978
|
+
onClick: () => setActive(i),
|
|
6979
|
+
onKeyDown: (e) => {
|
|
6980
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
6981
|
+
e.preventDefault();
|
|
6982
|
+
setActive(i);
|
|
6983
|
+
}
|
|
6984
|
+
},
|
|
6985
|
+
className: cx(
|
|
6986
|
+
"group flex flex-shrink-0 cursor-pointer items-center gap-1 rounded-t-md border-t-2 px-3 py-1 text-xs transition-colors",
|
|
6987
|
+
"focus:outline-none focus-visible:ring-2 focus-visible:ring-accent",
|
|
6988
|
+
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"
|
|
6989
|
+
),
|
|
6990
|
+
children: [
|
|
6991
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "max-w-[160px] truncate", children: name }),
|
|
6992
|
+
canDeleteSheet && /* @__PURE__ */ jsxRuntime.jsx(
|
|
6993
|
+
"button",
|
|
6994
|
+
{
|
|
6995
|
+
type: "button",
|
|
6996
|
+
title: "Delete sheet",
|
|
6997
|
+
"aria-label": `Delete ${name}`,
|
|
6998
|
+
onClick: (e) => {
|
|
6999
|
+
e.stopPropagation();
|
|
7000
|
+
deleteSheet(i);
|
|
7001
|
+
},
|
|
7002
|
+
className: cx(
|
|
7003
|
+
"flex h-4 w-4 items-center justify-center rounded text-foreground-muted transition-opacity hover:text-status-error",
|
|
7004
|
+
activeTab ? "opacity-100" : "opacity-0 group-hover:opacity-100"
|
|
7005
|
+
),
|
|
7006
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.5, className: "h-3 w-3", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", d: "M6 6l12 12M18 6 6 18" }) })
|
|
7007
|
+
}
|
|
7008
|
+
)
|
|
7009
|
+
]
|
|
7010
|
+
},
|
|
7011
|
+
`${s.name}-${i}`
|
|
7012
|
+
);
|
|
7013
|
+
}),
|
|
7014
|
+
canAddSheet && /* @__PURE__ */ jsxRuntime.jsx(
|
|
7015
|
+
"button",
|
|
7016
|
+
{
|
|
7017
|
+
type: "button",
|
|
7018
|
+
onClick: addSheet,
|
|
7019
|
+
title: "Add sheet",
|
|
7020
|
+
"aria-label": "Add sheet",
|
|
7021
|
+
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",
|
|
7022
|
+
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" }) })
|
|
7023
|
+
}
|
|
7024
|
+
)
|
|
7025
|
+
] }),
|
|
7026
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "hidden flex-shrink-0 text-xs tabular-nums text-foreground-muted sm:inline", children: [
|
|
7027
|
+
plainRows.length.toLocaleString(),
|
|
7028
|
+
" ",
|
|
7029
|
+
plainRows.length === 1 ? "row" : "rows",
|
|
7030
|
+
" \xB7 ",
|
|
7031
|
+
columns.length,
|
|
7032
|
+
" cols"
|
|
7033
|
+
] })
|
|
7034
|
+
] })
|
|
6961
7035
|
] });
|
|
6962
7036
|
}
|
|
6963
|
-
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" }) });
|
|
6964
7037
|
function ThemeSwitch({ checked, onChange, label = "Toggle dark mode", className = "" }) {
|
|
6965
7038
|
const id = React36.useId();
|
|
6966
7039
|
return /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: id, className: `flex items-center gap-2 cursor-pointer select-none ${className}`.trim(), children: /* @__PURE__ */ jsxRuntime.jsx(
|