@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.cjs +162 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -3
- package/dist/index.d.ts +11 -3
- package/dist/index.js +162 -28
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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 =
|
|
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
|
-
}, [
|
|
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 =
|
|
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 ?
|
|
6576
|
+
colEnd = colEnd === -1 ? cols.length : Math.min(cols.length, colEnd + 1);
|
|
6555
6577
|
colStart = Math.max(0, colStart - overscan);
|
|
6556
|
-
colEnd = Math.min(
|
|
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 =
|
|
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,
|
|
6588
|
+
}, [editing, cols, draft, onCellEdit, order, rows.length]);
|
|
6567
6589
|
const startEdit = (disp, col) => {
|
|
6568
|
-
const c =
|
|
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
|
|
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,
|
|
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:
|
|
6585
|
-
}, [selected, copyCell, onCellEdit,
|
|
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:
|
|
6595
|
-
}, [selected, onCellEdit,
|
|
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 =
|
|
6663
|
+
const c = cols[ci];
|
|
6623
6664
|
const sortDir = sort?.key === c.key ? sort.dir : null;
|
|
6624
6665
|
const canSort = colSortable(c);
|
|
6625
|
-
|
|
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(
|
|
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 =
|
|
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:
|
|
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"
|