@geomak/ui 7.14.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.d.cts CHANGED
@@ -2769,6 +2769,8 @@ interface DataGridProps {
2769
2769
  onDeleteRow?: (index: number) => void;
2770
2770
  /** Insert a column at `index` (from the column-header right-click menu). */
2771
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;
2772
2774
  className?: string;
2773
2775
  style?: react__default.CSSProperties;
2774
2776
  /** Shown when there are no rows. */
@@ -2785,7 +2787,7 @@ interface DataGridProps {
2785
2787
  * `onCellEdit` on commit. Wrap it with {@link Spreadsheet} for multi-sheet
2786
2788
  * switching, file parsing and export.
2787
2789
  */
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;
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;
2789
2791
 
2790
2792
  interface Cell {
2791
2793
  value: CellValue;
package/dist/index.d.ts CHANGED
@@ -2769,6 +2769,8 @@ interface DataGridProps {
2769
2769
  onDeleteRow?: (index: number) => void;
2770
2770
  /** Insert a column at `index` (from the column-header right-click menu). */
2771
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;
2772
2774
  className?: string;
2773
2775
  style?: react__default.CSSProperties;
2774
2776
  /** Shown when there are no rows. */
@@ -2785,7 +2787,7 @@ interface DataGridProps {
2785
2787
  * `onCellEdit` on commit. Wrap it with {@link Spreadsheet} for multi-sheet
2786
2788
  * switching, file parsing and export.
2787
2789
  */
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;
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;
2789
2791
 
2790
2792
  interface Cell {
2791
2793
  value: CellValue;
package/dist/index.js CHANGED
@@ -6501,6 +6501,7 @@ function DataGrid({
6501
6501
  onInsertRow,
6502
6502
  onDeleteRow,
6503
6503
  onInsertColumn,
6504
+ onHeaderEdit,
6504
6505
  className = "",
6505
6506
  style,
6506
6507
  emptyState = "No data"
@@ -6513,6 +6514,8 @@ function DataGrid({
6513
6514
  const [ctxTarget, setCtxTarget] = useState(null);
6514
6515
  const [editing, setEditing] = useState(null);
6515
6516
  const [draft, setDraft] = useState("");
6517
+ const [editingHeader, setEditingHeader] = useState(null);
6518
+ const [headerDraft, setHeaderDraft] = useState("");
6516
6519
  const [internalSort, setInternalSort] = useState(null);
6517
6520
  const sort = sortProp !== void 0 ? sortProp : internalSort;
6518
6521
  const gutter = rowNumbers ? GUTTER : 0;
@@ -6589,6 +6592,17 @@ function DataGrid({
6589
6592
  setDraft(displayValue(rows[rowIndexForDisp(disp)]?.[c.key] ?? ""));
6590
6593
  setEditing({ disp, col });
6591
6594
  };
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
+ };
6592
6606
  const cellText = (sel) => displayValue(rows[rowIndexForDisp(sel.disp)]?.[cols[sel.col].key] ?? "");
6593
6607
  const copyCell = useCallback(async () => {
6594
6608
  if (!selected) return;
@@ -6616,7 +6630,8 @@ function DataGrid({
6616
6630
  if (ctxTarget?.kind === "header") {
6617
6631
  const ci = ctxTarget.col;
6618
6632
  return [
6619
- { key: "left", value: "Add column to the left", disabled: !onInsertColumn, onClick: () => onInsertColumn?.(ci) },
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) },
6620
6635
  { key: "right", value: "Add column to the right", disabled: !onInsertColumn, onClick: () => onInsertColumn?.(ci + 1) }
6621
6636
  ];
6622
6637
  }
@@ -6634,7 +6649,7 @@ function DataGrid({
6634
6649
  { key: "cut", value: "Cut", disabled: !editable, onClick: () => void cutCell() },
6635
6650
  { key: "paste", value: "Paste", disabled: !editable, onClick: () => void pasteCell() }
6636
6651
  ];
6637
- }, [ctxTarget, rows.length, editable, onInsertRow, onDeleteRow, onInsertColumn, copyCell, cutCell, pasteCell, order]);
6652
+ }, [ctxTarget, rows.length, editable, onInsertRow, onDeleteRow, onInsertColumn, onHeaderEdit, copyCell, cutCell, pasteCell, order]);
6638
6653
  const rowHighlighted = (disp) => hoveredRow === disp || selected?.disp === disp;
6639
6654
  const gridInner = /* @__PURE__ */ jsxs("div", { style: { position: "relative", width: gutter + totalWidth + END_PAD, height: headerHeight + totalHeight + END_PAD }, children: [
6640
6655
  rowNumbers && /* @__PURE__ */ jsx(
@@ -6648,16 +6663,20 @@ function DataGrid({
6648
6663
  const c = cols[ci];
6649
6664
  const sortDir = sort?.key === c.key ? sort.dir : null;
6650
6665
  const canSort = colSortable(c);
6651
- return /* @__PURE__ */ jsxs(
6666
+ const renamable = !!onHeaderEdit;
6667
+ const isEditingHeader = editingHeader === ci;
6668
+ return /* @__PURE__ */ jsx(
6652
6669
  "div",
6653
6670
  {
6654
6671
  role: "columnheader",
6655
6672
  "aria-sort": sortDir ? sortDir === "asc" ? "ascending" : "descending" : void 0,
6656
- onClick: canSort ? () => toggleSort(c.key) : void 0,
6673
+ onClick: canSort && !renamable ? () => toggleSort(c.key) : void 0,
6674
+ onDoubleClick: renamable ? () => startHeaderEdit(ci) : void 0,
6657
6675
  onContextMenu: contextMenu ? () => setCtxTarget({ kind: "header", col: ci }) : void 0,
6658
6676
  className: cx(
6659
6677
  "flex items-center gap-1 border-b border-r border-border bg-surface px-3 font-medium text-foreground-secondary",
6660
- canSort && "cursor-pointer select-none hover:text-foreground"
6678
+ canSort && !renamable && "cursor-pointer select-none hover:text-foreground",
6679
+ renamable && "cursor-default select-none"
6661
6680
  ),
6662
6681
  style: {
6663
6682
  position: "absolute",
@@ -6668,10 +6687,27 @@ function DataGrid({
6668
6687
  zIndex: 2,
6669
6688
  justifyContent: c.align === "right" ? "flex-end" : c.align === "center" ? "center" : "flex-start"
6670
6689
  },
6671
- 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: [
6672
6705
  /* @__PURE__ */ jsx("span", { className: "truncate", children: c.label ?? c.key }),
6673
- canSort && /* @__PURE__ */ jsx(SortCaret, { dir: sortDir })
6674
- ]
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
+ ] })
6675
6711
  },
6676
6712
  `h-${c.key}`
6677
6713
  );
@@ -6908,12 +6944,21 @@ function Spreadsheet({
6908
6944
  const sheet = sheets?.[active];
6909
6945
  const columns = useMemo(() => sheet ? toColumns(sheet.columns) : [], [sheet]);
6910
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);
6911
6952
  const handleCellEdit = useCallback(({ row, column, value }) => {
6912
6953
  let coerced = value;
6913
6954
  setSheets((prev) => {
6914
6955
  if (!prev) return prev;
6915
- 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);
6916
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
+ }
6917
6962
  while (target.rows.length <= row) target.rows.push({});
6918
6963
  const existing = target.rows[row]?.[column];
6919
6964
  const prevValue = cellValue(existing);
@@ -6927,7 +6972,22 @@ function Spreadsheet({
6927
6972
  return next;
6928
6973
  });
6929
6974
  onCellEdit?.({ sheet: sheets?.[active]?.name ?? "", row, column, value: coerced });
6930
- }, [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]);
6931
6991
  const addSheet = useCallback(() => {
6932
6992
  setSheets((prev) => {
6933
6993
  const list = prev ?? [];
@@ -7145,17 +7205,17 @@ function Spreadsheet({
7145
7205
  /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsx(
7146
7206
  DataGrid,
7147
7207
  {
7148
- columns,
7208
+ columns: displayColumns,
7149
7209
  rows: plainRows,
7150
7210
  editable,
7151
7211
  sortable,
7152
7212
  virtualize,
7153
7213
  trailingRows: emptyRows,
7154
- trailingCols: emptyCols,
7155
7214
  contextMenu: true,
7156
7215
  onInsertRow: editable ? insertRow : void 0,
7157
7216
  onDeleteRow: editable ? deleteRow : void 0,
7158
7217
  onInsertColumn: editable ? insertColumn : void 0,
7218
+ onHeaderEdit: editable ? renameColumn : void 0,
7159
7219
  onCellEdit: handleCellEdit,
7160
7220
  height: "100%",
7161
7221
  className: "!rounded-none !border-0"