@geomak/ui 7.14.0 → 7.16.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
@@ -6537,6 +6537,7 @@ function DataGrid({
6537
6537
  onInsertRow,
6538
6538
  onDeleteRow,
6539
6539
  onInsertColumn,
6540
+ onHeaderEdit,
6540
6541
  className = "",
6541
6542
  style,
6542
6543
  emptyState = "No data"
@@ -6549,6 +6550,8 @@ function DataGrid({
6549
6550
  const [ctxTarget, setCtxTarget] = React36.useState(null);
6550
6551
  const [editing, setEditing] = React36.useState(null);
6551
6552
  const [draft, setDraft] = React36.useState("");
6553
+ const [editingHeader, setEditingHeader] = React36.useState(null);
6554
+ const [headerDraft, setHeaderDraft] = React36.useState("");
6552
6555
  const [internalSort, setInternalSort] = React36.useState(null);
6553
6556
  const sort = sortProp !== void 0 ? sortProp : internalSort;
6554
6557
  const gutter = rowNumbers ? GUTTER : 0;
@@ -6625,6 +6628,17 @@ function DataGrid({
6625
6628
  setDraft(displayValue(rows[rowIndexForDisp(disp)]?.[c.key] ?? ""));
6626
6629
  setEditing({ disp, col });
6627
6630
  };
6631
+ const startHeaderEdit = (col) => {
6632
+ if (!onHeaderEdit) return;
6633
+ const c = cols[col];
6634
+ setHeaderDraft(typeof c.label === "string" ? c.label : String(c.key));
6635
+ setEditingHeader(col);
6636
+ };
6637
+ const commitHeader = () => {
6638
+ if (editingHeader == null) return;
6639
+ onHeaderEdit?.(editingHeader, headerDraft);
6640
+ setEditingHeader(null);
6641
+ };
6628
6642
  const cellText = (sel) => displayValue(rows[rowIndexForDisp(sel.disp)]?.[cols[sel.col].key] ?? "");
6629
6643
  const copyCell = React36.useCallback(async () => {
6630
6644
  if (!selected) return;
@@ -6652,7 +6666,8 @@ function DataGrid({
6652
6666
  if (ctxTarget?.kind === "header") {
6653
6667
  const ci = ctxTarget.col;
6654
6668
  return [
6655
- { key: "left", value: "Add column to the left", disabled: !onInsertColumn, onClick: () => onInsertColumn?.(ci) },
6669
+ { key: "rename", value: "Rename column", disabled: !onHeaderEdit, onClick: () => startHeaderEdit(ci) },
6670
+ { key: "left", value: "Add column to the left", disabled: !onInsertColumn, separatorBefore: true, onClick: () => onInsertColumn?.(ci) },
6656
6671
  { key: "right", value: "Add column to the right", disabled: !onInsertColumn, onClick: () => onInsertColumn?.(ci + 1) }
6657
6672
  ];
6658
6673
  }
@@ -6670,7 +6685,7 @@ function DataGrid({
6670
6685
  { key: "cut", value: "Cut", disabled: !editable, onClick: () => void cutCell() },
6671
6686
  { key: "paste", value: "Paste", disabled: !editable, onClick: () => void pasteCell() }
6672
6687
  ];
6673
- }, [ctxTarget, rows.length, editable, onInsertRow, onDeleteRow, onInsertColumn, copyCell, cutCell, pasteCell, order]);
6688
+ }, [ctxTarget, rows.length, editable, onInsertRow, onDeleteRow, onInsertColumn, onHeaderEdit, copyCell, cutCell, pasteCell, order]);
6674
6689
  const rowHighlighted = (disp) => hoveredRow === disp || selected?.disp === disp;
6675
6690
  const gridInner = /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative", width: gutter + totalWidth + END_PAD, height: headerHeight + totalHeight + END_PAD }, children: [
6676
6691
  rowNumbers && /* @__PURE__ */ jsxRuntime.jsx(
@@ -6684,16 +6699,20 @@ function DataGrid({
6684
6699
  const c = cols[ci];
6685
6700
  const sortDir = sort?.key === c.key ? sort.dir : null;
6686
6701
  const canSort = colSortable(c);
6687
- return /* @__PURE__ */ jsxRuntime.jsxs(
6702
+ const renamable = !!onHeaderEdit;
6703
+ const isEditingHeader = editingHeader === ci;
6704
+ return /* @__PURE__ */ jsxRuntime.jsx(
6688
6705
  "div",
6689
6706
  {
6690
6707
  role: "columnheader",
6691
6708
  "aria-sort": sortDir ? sortDir === "asc" ? "ascending" : "descending" : void 0,
6692
- onClick: canSort ? () => toggleSort(c.key) : void 0,
6709
+ onClick: canSort && !renamable ? () => toggleSort(c.key) : void 0,
6710
+ onDoubleClick: renamable ? () => startHeaderEdit(ci) : void 0,
6693
6711
  onContextMenu: contextMenu ? () => setCtxTarget({ kind: "header", col: ci }) : void 0,
6694
6712
  className: cx(
6695
6713
  "flex items-center gap-1 border-b border-r border-border bg-surface px-3 font-medium text-foreground-secondary",
6696
- canSort && "cursor-pointer select-none hover:text-foreground"
6714
+ canSort && !renamable && "cursor-pointer select-none hover:text-foreground",
6715
+ renamable && "cursor-default select-none"
6697
6716
  ),
6698
6717
  style: {
6699
6718
  position: "absolute",
@@ -6704,10 +6723,27 @@ function DataGrid({
6704
6723
  zIndex: 2,
6705
6724
  justifyContent: c.align === "right" ? "flex-end" : c.align === "center" ? "center" : "flex-start"
6706
6725
  },
6707
- children: [
6726
+ children: isEditingHeader ? /* @__PURE__ */ jsxRuntime.jsx(
6727
+ "input",
6728
+ {
6729
+ autoFocus: true,
6730
+ value: headerDraft,
6731
+ onChange: (e) => setHeaderDraft(e.target.value),
6732
+ onBlur: commitHeader,
6733
+ onClick: (e) => e.stopPropagation(),
6734
+ onKeyDown: (e) => {
6735
+ if (e.key === "Enter") commitHeader();
6736
+ else if (e.key === "Escape") setEditingHeader(null);
6737
+ },
6738
+ className: "h-full w-full bg-transparent font-medium text-foreground outline-none"
6739
+ }
6740
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6708
6741
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: c.label ?? c.key }),
6709
- canSort && /* @__PURE__ */ jsxRuntime.jsx(SortCaret, { dir: sortDir })
6710
- ]
6742
+ canSort && (renamable ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", title: "Sort", onClick: (e) => {
6743
+ e.stopPropagation();
6744
+ toggleSort(c.key);
6745
+ }, className: "flex-shrink-0 rounded hover:text-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(SortCaret, { dir: sortDir }) }) : /* @__PURE__ */ jsxRuntime.jsx(SortCaret, { dir: sortDir }))
6746
+ ] })
6711
6747
  },
6712
6748
  `h-${c.key}`
6713
6749
  );
@@ -6944,12 +6980,21 @@ function Spreadsheet({
6944
6980
  const sheet = sheets?.[active];
6945
6981
  const columns = React36.useMemo(() => sheet ? toColumns(sheet.columns) : [], [sheet]);
6946
6982
  const plainRows = React36.useMemo(() => sheet ? toPlainRows(sheet, columns) : [], [sheet, columns]);
6983
+ const displayColumns = React36.useMemo(
6984
+ () => [...columns, ...makeColumns(columns, Math.max(0, emptyCols)).map((c) => ({ ...c, sortable: false }))],
6985
+ [columns, emptyCols]
6986
+ );
6987
+ const isSlackKey = (key) => !columns.some((c) => c.key === key);
6947
6988
  const handleCellEdit = React36.useCallback(({ row, column, value }) => {
6948
6989
  let coerced = value;
6949
6990
  setSheets((prev) => {
6950
6991
  if (!prev) return prev;
6951
- const next = prev.map((s, i) => i === active ? { ...s, rows: s.rows.map((r) => ({ ...r })) } : s);
6992
+ const next = prev.map((s, i) => i === active ? { ...s, columns: toColumns(s.columns), rows: s.rows.map((r) => ({ ...r })) } : s);
6952
6993
  const target = next[active];
6994
+ if (isSlackKey(column)) {
6995
+ const j = displayColumns.findIndex((c) => c.key === column);
6996
+ if (j >= 0) target.columns = displayColumns.slice(0, j + 1).map((c) => ({ key: c.key, label: c.label }));
6997
+ }
6953
6998
  while (target.rows.length <= row) target.rows.push({});
6954
6999
  const existing = target.rows[row]?.[column];
6955
7000
  const prevValue = cellValue(existing);
@@ -6963,7 +7008,22 @@ function Spreadsheet({
6963
7008
  return next;
6964
7009
  });
6965
7010
  onCellEdit?.({ sheet: sheets?.[active]?.name ?? "", row, column, value: coerced });
6966
- }, [active, onCellEdit, onChange, sheets]);
7011
+ }, [active, onCellEdit, onChange, sheets, displayColumns, columns]);
7012
+ const renameColumn = React36.useCallback((index, label) => {
7013
+ setSheets((prev) => {
7014
+ if (!prev) return prev;
7015
+ const next = prev.map((s, i) => i === active ? { ...s, columns: toColumns(s.columns) } : s);
7016
+ const target = next[active];
7017
+ let tcols = target.columns;
7018
+ if (index >= tcols.length) {
7019
+ tcols = displayColumns.slice(0, index + 1).map((c) => ({ key: c.key, label: c.label }));
7020
+ target.columns = tcols;
7021
+ }
7022
+ if (tcols[index]) tcols[index] = { ...tcols[index], label };
7023
+ onChange?.(next);
7024
+ return next;
7025
+ });
7026
+ }, [active, onChange, displayColumns]);
6967
7027
  const addSheet = React36.useCallback(() => {
6968
7028
  setSheets((prev) => {
6969
7029
  const list = prev ?? [];
@@ -7181,17 +7241,17 @@ function Spreadsheet({
7181
7241
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsxRuntime.jsx(
7182
7242
  DataGrid,
7183
7243
  {
7184
- columns,
7244
+ columns: displayColumns,
7185
7245
  rows: plainRows,
7186
7246
  editable,
7187
7247
  sortable,
7188
7248
  virtualize,
7189
7249
  trailingRows: emptyRows,
7190
- trailingCols: emptyCols,
7191
7250
  contextMenu: true,
7192
7251
  onInsertRow: editable ? insertRow : void 0,
7193
7252
  onDeleteRow: editable ? deleteRow : void 0,
7194
7253
  onInsertColumn: editable ? insertColumn : void 0,
7254
+ onHeaderEdit: editable ? renameColumn : void 0,
7195
7255
  onCellEdit: handleCellEdit,
7196
7256
  height: "100%",
7197
7257
  className: "!rounded-none !border-0"
@@ -11121,6 +11181,153 @@ function useJwt(token) {
11121
11181
  const isValid = decoded.payload != null && !isExpired;
11122
11182
  return { payload: decoded.payload, header: decoded.header, expiresAt, isExpired, isValid, raw: token ?? null };
11123
11183
  }
11184
+ var INVALID_WS_CHARS = /[:\\/?\*[\]]/g;
11185
+ function sanitizeName(name) {
11186
+ return name.replace(INVALID_WS_CHARS, "_").slice(0, 31) || "Sheet";
11187
+ }
11188
+ var xlsxPromise2 = null;
11189
+ var loadXlsx2 = () => xlsxPromise2 ??= import('xlsx');
11190
+ function useExcel() {
11191
+ const [isExporting, setIsExporting] = React36.useState(false);
11192
+ const exportSheets = React36.useCallback(async (sheets, options = {}) => {
11193
+ const { fileName = "export", onSave } = options;
11194
+ setIsExporting(true);
11195
+ try {
11196
+ const XLSX = await loadXlsx2();
11197
+ const wb = XLSX.utils.book_new();
11198
+ for (const sheet of sheets) {
11199
+ let headers;
11200
+ if (sheet.columns) {
11201
+ headers = sheet.columns;
11202
+ } else {
11203
+ const seen = /* @__PURE__ */ new Set();
11204
+ for (const row of sheet.rows) {
11205
+ if (!Array.isArray(row)) {
11206
+ for (const k of Object.keys(row)) seen.add(k);
11207
+ }
11208
+ }
11209
+ headers = Array.from(seen);
11210
+ }
11211
+ const aoa = [];
11212
+ if (headers.length > 0) aoa.push(headers);
11213
+ for (const row of sheet.rows) {
11214
+ if (Array.isArray(row)) {
11215
+ aoa.push(row);
11216
+ } else {
11217
+ aoa.push(headers.map((h) => row[h] ?? null));
11218
+ }
11219
+ }
11220
+ const ws = XLSX.utils.aoa_to_sheet(aoa);
11221
+ if (headers.length > 0) {
11222
+ for (let ci = 0; ci < headers.length; ci++) {
11223
+ const addr = XLSX.utils.encode_cell({ r: 0, c: ci });
11224
+ if (ws[addr]) ws[addr].s = { font: { bold: true } };
11225
+ }
11226
+ }
11227
+ const colWidths = headers.map((h) => Math.min(h.length, 40));
11228
+ for (const row of aoa.slice(1)) {
11229
+ row.forEach((cell, ci) => {
11230
+ const len = cell == null ? 0 : String(cell).length;
11231
+ if (len > (colWidths[ci] ?? 0)) colWidths[ci] = Math.min(len, 40);
11232
+ });
11233
+ }
11234
+ ws["!cols"] = colWidths.map((w) => ({ wch: w + 2 }));
11235
+ XLSX.utils.book_append_sheet(wb, ws, sanitizeName(sheet.name));
11236
+ }
11237
+ const out = XLSX.write(wb, { bookType: "xlsx", type: "array" });
11238
+ const blob = new Blob([out], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
11239
+ const name = `${fileName}.xlsx`;
11240
+ if (onSave) {
11241
+ await onSave(blob, name);
11242
+ } else {
11243
+ downloadBlob(blob, name);
11244
+ }
11245
+ } finally {
11246
+ setIsExporting(false);
11247
+ }
11248
+ }, []);
11249
+ const readWorkbook = React36.useCallback(async (source) => {
11250
+ const XLSX = await loadXlsx2();
11251
+ let bytes;
11252
+ if (source instanceof Uint8Array) {
11253
+ bytes = source;
11254
+ } else if (source instanceof ArrayBuffer) {
11255
+ bytes = new Uint8Array(source);
11256
+ } else {
11257
+ bytes = new Uint8Array(await source.arrayBuffer());
11258
+ }
11259
+ const wb = XLSX.read(bytes, { type: "array" });
11260
+ return wb.SheetNames.map((name) => {
11261
+ const ws = wb.Sheets[name];
11262
+ const aoa = XLSX.utils.sheet_to_json(ws, { header: 1, blankrows: false, defval: null });
11263
+ if (aoa.length === 0) return { name, rows: [] };
11264
+ const headerRow = aoa[0] ?? [];
11265
+ const headers = headerRow.map(
11266
+ (h, i) => h != null && String(h).length ? String(h) : `col_${i}`
11267
+ );
11268
+ const rows = aoa.slice(1).map((row) => {
11269
+ const rec = {};
11270
+ headers.forEach((h, i) => {
11271
+ rec[h] = row[i] ?? null;
11272
+ });
11273
+ return rec;
11274
+ });
11275
+ return { name, rows };
11276
+ });
11277
+ }, []);
11278
+ return { exportSheets, readWorkbook, isExporting };
11279
+ }
11280
+ var jspdfPromise2 = null;
11281
+ var loadJspdf2 = () => jspdfPromise2 ??= import('jspdf');
11282
+ function usePdf() {
11283
+ const [isExporting, setIsExporting] = React36.useState(false);
11284
+ const exportCanvases = React36.useCallback(async (pages, options = {}) => {
11285
+ const { fileName = "report", onSave, orientation = "landscape" } = options;
11286
+ const validPages = pages.filter((p) => p.canvas.width !== 0 && p.canvas.height !== 0);
11287
+ if (validPages.length === 0) return;
11288
+ setIsExporting(true);
11289
+ try {
11290
+ const { jsPDF } = await loadJspdf2();
11291
+ const pdf = new jsPDF({ orientation, unit: "px", compress: true });
11292
+ const pageW = pdf.internal.pageSize.getWidth();
11293
+ const pageH = pdf.internal.pageSize.getHeight();
11294
+ const TITLE_H = 28;
11295
+ for (let i = 0; i < validPages.length; i++) {
11296
+ const { canvas, title } = validPages[i];
11297
+ if (i > 0) pdf.addPage();
11298
+ let contentY = 0;
11299
+ const contentH = title ? pageH - TITLE_H : pageH;
11300
+ if (title) {
11301
+ pdf.setFontSize(12);
11302
+ pdf.setFont("helvetica", "bold");
11303
+ pdf.text(title, pageW / 2, TITLE_H / 2 + 6, { align: "center" });
11304
+ contentY = TITLE_H;
11305
+ }
11306
+ const scale = Math.min(pageW / canvas.width, contentH / canvas.height);
11307
+ const w = canvas.width * scale;
11308
+ const h = canvas.height * scale;
11309
+ const x = (pageW - w) / 2;
11310
+ const y = contentY + (contentH - h) / 2;
11311
+ pdf.addImage(canvas.toDataURL("image/png", 1), "PNG", x, y, w, h);
11312
+ }
11313
+ const blob = pdf.output("blob");
11314
+ const name = `${fileName}.pdf`;
11315
+ if (onSave) {
11316
+ await onSave(blob, name);
11317
+ } else {
11318
+ const url = URL.createObjectURL(blob);
11319
+ const a = document.createElement("a");
11320
+ a.href = url;
11321
+ a.download = name;
11322
+ a.click();
11323
+ URL.revokeObjectURL(url);
11324
+ }
11325
+ } finally {
11326
+ setIsExporting(false);
11327
+ }
11328
+ }, []);
11329
+ return { exportCanvases, isExporting };
11330
+ }
11124
11331
  var GRADIENT = "radial-gradient(ellipse 80% 60% at 50% 0%, color-mix(in oklab, var(--color-accent) 12%, transparent), transparent 70%)";
11125
11332
  function Jumbotron({
11126
11333
  eyebrow,
@@ -11808,6 +12015,7 @@ exports.runFieldRules = runFieldRules;
11808
12015
  exports.scorePassword = scorePassword;
11809
12016
  exports.useBreakpoint = useBreakpoint;
11810
12017
  exports.useCart = useCart;
12018
+ exports.useExcel = useExcel;
11811
12019
  exports.useFieldArray = useFieldArray;
11812
12020
  exports.useForm = useForm;
11813
12021
  exports.useFormField = useFormField;
@@ -11816,5 +12024,6 @@ exports.useJwt = useJwt;
11816
12024
  exports.useLocalStorage = useLocalStorage;
11817
12025
  exports.useMediaQuery = useMediaQuery;
11818
12026
  exports.useNotification = useNotification;
12027
+ exports.usePdf = usePdf;
11819
12028
  //# sourceMappingURL=index.cjs.map
11820
12029
  //# sourceMappingURL=index.cjs.map