react-glide-table 1.7.0 → 2.0.2

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/core.js CHANGED
@@ -97,6 +97,37 @@ function applyCellEdit(data, rows, rowIndex, colIndex, raw) {
97
97
  return newData;
98
98
  }
99
99
 
100
+ // src/components/ui/table/features/cell-render/commitCellValue.ts
101
+ function commitCellValue({
102
+ data,
103
+ rows,
104
+ rowId,
105
+ columnId,
106
+ value,
107
+ onCellChange,
108
+ onDataChange
109
+ }) {
110
+ if (!onCellChange && !onDataChange) return true;
111
+ if (onCellChange) {
112
+ onCellChange(rowId, columnId, value);
113
+ return true;
114
+ }
115
+ const row = rows.find((item) => item.id === rowId);
116
+ if (!row) return false;
117
+ const cell = row.getAllCells().find((item) => item.column.id === columnId) ?? row.getVisibleCells().find((item) => item.column.id === columnId);
118
+ if (!cell) return false;
119
+ const accessorKey = getColumnAccessorKey(cell.column.columnDef);
120
+ if (!accessorKey) return false;
121
+ const dataIndex = row.index;
122
+ if (dataIndex < 0 || dataIndex >= data.length) return false;
123
+ const next = data.map((item) => ({ ...item }));
124
+ const target = next[dataIndex];
125
+ if (!target) return false;
126
+ target[accessorKey] = value;
127
+ onDataChange?.(next);
128
+ return true;
129
+ }
130
+
100
131
  // src/components/ui/table/features/cell-edit/useCellEdit.ts
101
132
  function useCellEdit({
102
133
  data,
@@ -132,21 +163,23 @@ function useCellEdit({
132
163
  cancelEdit();
133
164
  return true;
134
165
  }
135
- const value = raw ?? draftValueRef.current;
136
166
  if (!isColumnEditable(cell.column.columnDef)) {
137
167
  cancelEdit();
138
168
  return true;
139
169
  }
170
+ const value = raw ?? draftValueRef.current;
140
171
  const parsed = parseCellEditValue(value, getColumnEditType(cell.column.columnDef));
141
172
  if (!parsed.ok) return false;
142
- if (onCellChange) {
143
- onCellChange(row.id, cell.column.id, parsed.value);
144
- cancelEdit();
145
- return true;
146
- }
147
- const next = applyCellEdit(data, rows, current.rowIndex, current.colIndex, value);
148
- if (!next) return false;
149
- onDataChange?.(next);
173
+ const committed = commitCellValue({
174
+ data,
175
+ rows,
176
+ rowId: row.id,
177
+ columnId: cell.column.id,
178
+ value: parsed.value,
179
+ onCellChange,
180
+ onDataChange
181
+ });
182
+ if (!committed) return false;
150
183
  cancelEdit();
151
184
  return true;
152
185
  },
@@ -175,6 +208,228 @@ function useCellEdit({
175
208
  };
176
209
  }
177
210
 
211
+ // src/components/ui/table/features/cell-render/builtins.tsx
212
+ import { jsx, jsxs } from "react/jsx-runtime";
213
+ function asString(value) {
214
+ if (value == null) return "";
215
+ return String(value);
216
+ }
217
+ function asStringList(value) {
218
+ if (Array.isArray(value)) {
219
+ return value.map((item) => asString(item)).filter(Boolean);
220
+ }
221
+ if (value == null || value === "") return [];
222
+ return [asString(value)];
223
+ }
224
+ function asDrilldownItems(value) {
225
+ if (!Array.isArray(value)) return [];
226
+ return value.flatMap((item) => {
227
+ if (item == null) return [];
228
+ if (typeof item === "string") return [{ text: item }];
229
+ if (typeof item === "object") {
230
+ const record = item;
231
+ const text = asString(record.text ?? record.label ?? "");
232
+ if (!text) return [];
233
+ const img = record.img ?? record.image;
234
+ return [{ text, ...typeof img === "string" ? { img } : {} }];
235
+ }
236
+ return [{ text: asString(item) }];
237
+ });
238
+ }
239
+ function escapeHtml(text) {
240
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
241
+ }
242
+ function simpleMarkdownToHtml(source) {
243
+ const escaped = escapeHtml(source);
244
+ return escaped.replace(/`([^`]+)`/g, "<code>$1</code>").replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>").replace(/\*([^*]+)\*/g, "<em>$1</em>").replace(/\n/g, "<br />");
245
+ }
246
+ function TextCell({ value }) {
247
+ return asString(value);
248
+ }
249
+ function NumberCell({ value }) {
250
+ if (value == null || value === "") return null;
251
+ return asString(value);
252
+ }
253
+ function BooleanCell({ value, update, cellProps }) {
254
+ const checked = Boolean(value);
255
+ const readonly = Boolean(cellProps?.readonly);
256
+ return /* @__PURE__ */ jsx(
257
+ "input",
258
+ {
259
+ type: "checkbox",
260
+ className: "data-table-cell-boolean",
261
+ checked,
262
+ disabled: readonly,
263
+ "aria-checked": checked,
264
+ onChange: (event) => {
265
+ if (readonly) return;
266
+ update(event.target.checked);
267
+ },
268
+ onClick: (event) => {
269
+ event.stopPropagation();
270
+ },
271
+ onMouseDown: (event) => {
272
+ event.stopPropagation();
273
+ }
274
+ }
275
+ );
276
+ }
277
+ function sanitizeUriHref(raw) {
278
+ const href = raw.trim();
279
+ if (!href) return null;
280
+ if (href.startsWith("/") || href.startsWith("#") || href.startsWith("?") || href.startsWith("./") || href.startsWith("../")) {
281
+ return href;
282
+ }
283
+ try {
284
+ const parsed = new URL(href);
285
+ const protocol = parsed.protocol.toLowerCase();
286
+ if (protocol === "http:" || protocol === "https:" || protocol === "mailto:") {
287
+ return href;
288
+ }
289
+ return null;
290
+ } catch {
291
+ if (/^[a-z][a-z0-9+.-]*:/i.test(href)) return null;
292
+ return href;
293
+ }
294
+ }
295
+ function UriCell({ value }) {
296
+ const raw = asString(value);
297
+ if (!raw) return null;
298
+ const href = sanitizeUriHref(raw);
299
+ if (!href) {
300
+ return /* @__PURE__ */ jsx("span", { className: "data-table-cell-uri", children: raw });
301
+ }
302
+ return /* @__PURE__ */ jsx(
303
+ "a",
304
+ {
305
+ className: "data-table-cell-uri",
306
+ href,
307
+ target: "_blank",
308
+ rel: "noopener noreferrer",
309
+ onClick: (event) => event.stopPropagation(),
310
+ onMouseDown: (event) => event.stopPropagation(),
311
+ children: raw
312
+ }
313
+ );
314
+ }
315
+ function ImageCell({ value }) {
316
+ const urls = asStringList(value);
317
+ if (urls.length === 0) return null;
318
+ return /* @__PURE__ */ jsx("span", { className: "data-table-cell-image", children: urls.map((url, index) => /* @__PURE__ */ jsx(
319
+ "img",
320
+ {
321
+ src: url,
322
+ alt: "",
323
+ className: "data-table-cell-image-item"
324
+ },
325
+ `${index}:${url}`
326
+ )) });
327
+ }
328
+ function BubbleCell({ value }) {
329
+ const items = asStringList(value);
330
+ if (items.length === 0) return null;
331
+ return /* @__PURE__ */ jsx("span", { className: "data-table-cell-bubble", children: items.map((item, index) => /* @__PURE__ */ jsx("span", { className: "data-table-cell-bubble-item", children: item }, `${index}:${item}`)) });
332
+ }
333
+ function MarkdownCell({ value }) {
334
+ const source = asString(value);
335
+ if (!source) return null;
336
+ return /* @__PURE__ */ jsx(
337
+ "span",
338
+ {
339
+ className: "data-table-cell-markdown",
340
+ dangerouslySetInnerHTML: { __html: simpleMarkdownToHtml(source) }
341
+ }
342
+ );
343
+ }
344
+ function DrilldownCell({ value }) {
345
+ const items = asDrilldownItems(value);
346
+ if (items.length === 0) return null;
347
+ return /* @__PURE__ */ jsx("span", { className: "data-table-cell-drilldown", children: items.map((item, index) => /* @__PURE__ */ jsxs(
348
+ "span",
349
+ {
350
+ className: "data-table-cell-drilldown-item",
351
+ children: [
352
+ item.img ? /* @__PURE__ */ jsx(
353
+ "img",
354
+ {
355
+ src: item.img,
356
+ alt: "",
357
+ className: "data-table-cell-drilldown-image"
358
+ }
359
+ ) : null,
360
+ /* @__PURE__ */ jsx("span", { className: "data-table-cell-drilldown-text", children: item.text })
361
+ ]
362
+ },
363
+ `${index}:${item.text}:${item.img ?? ""}`
364
+ )) });
365
+ }
366
+ function LoadingCell() {
367
+ return /* @__PURE__ */ jsx("span", { className: "data-table-cell-loading", "aria-busy": "true" });
368
+ }
369
+ function ProtectedCell() {
370
+ return /* @__PURE__ */ jsx("span", { className: "data-table-cell-protected", "aria-label": "protected", children: "****" });
371
+ }
372
+ function RowIdCell({ value }) {
373
+ return /* @__PURE__ */ jsx("span", { className: "data-table-cell-row-id", children: asString(value) });
374
+ }
375
+ var BUILTIN_RENDER_MAP = {
376
+ text: TextCell,
377
+ number: NumberCell,
378
+ boolean: BooleanCell,
379
+ uri: UriCell,
380
+ image: ImageCell,
381
+ bubble: BubbleCell,
382
+ markdown: MarkdownCell,
383
+ drilldown: DrilldownCell,
384
+ loading: LoadingCell,
385
+ protected: ProtectedCell,
386
+ "row-id": RowIdCell
387
+ };
388
+ var BUILTIN_CELL_RENDERERS = Object.keys(BUILTIN_RENDER_MAP).map((kind) => ({
389
+ kind,
390
+ render: BUILTIN_RENDER_MAP[kind]
391
+ }));
392
+
393
+ // src/components/ui/table/features/cell-render/registry.ts
394
+ function createCellRendererRegistry(customRenderers = []) {
395
+ const registry = /* @__PURE__ */ new Map();
396
+ for (const renderer of BUILTIN_CELL_RENDERERS) {
397
+ registry.set(renderer.kind, renderer);
398
+ }
399
+ for (const renderer of customRenderers) {
400
+ registry.set(renderer.kind, renderer);
401
+ }
402
+ return registry;
403
+ }
404
+ function resolveCellRenderer(registry, kind, ctx) {
405
+ if (!kind) return void 0;
406
+ const renderer = registry.get(kind);
407
+ if (!renderer) return void 0;
408
+ if (renderer.isMatch && !renderer.isMatch(ctx)) {
409
+ return void 0;
410
+ }
411
+ return renderer;
412
+ }
413
+ function formatDefaultCellValue(value) {
414
+ if (value == null) return null;
415
+ if (typeof value === "string") return value;
416
+ if (typeof value === "number" || typeof value === "boolean") {
417
+ return String(value);
418
+ }
419
+ if (typeof value === "bigint") return value.toString();
420
+ return String(value);
421
+ }
422
+
423
+ // src/components/ui/table/features/cell-render/withCellUpdate.ts
424
+ function withCellUpdate(context, commitValue) {
425
+ return {
426
+ ...context,
427
+ update: (next) => {
428
+ commitValue(context.row.id, context.column.id, next);
429
+ }
430
+ };
431
+ }
432
+
178
433
  // src/components/ui/table/features/cell-selection/useCellSelection.ts
179
434
  import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
180
435
 
@@ -484,9 +739,34 @@ function hasCellSelectionEdges(style) {
484
739
  }
485
740
 
486
741
  // src/components/ui/table/features/cell-selection/copyData.ts
742
+ function formatPrimitive(value) {
743
+ if (value === null || value === void 0) return "";
744
+ if (typeof value === "string") return value;
745
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
746
+ return String(value);
747
+ }
748
+ return "";
749
+ }
750
+ function formatObjectValue(value) {
751
+ const text = value.text ?? value.label ?? value.name ?? value.title;
752
+ if (text != null && text !== "") {
753
+ return formatCellValue(text);
754
+ }
755
+ try {
756
+ return JSON.stringify(value);
757
+ } catch {
758
+ return "";
759
+ }
760
+ }
487
761
  function formatCellValue(value) {
488
762
  if (value === null || value === void 0) return "";
489
- return String(value);
763
+ if (Array.isArray(value)) {
764
+ return value.map((item) => formatCellValue(item)).filter((item) => item.length > 0).join(", ");
765
+ }
766
+ if (typeof value === "object") {
767
+ return formatObjectValue(value);
768
+ }
769
+ return formatPrimitive(value);
490
770
  }
491
771
  function getNestedValue(row, path) {
492
772
  if (!path.includes(".")) return row[path];
@@ -1110,10 +1390,40 @@ function getColumnFreezeStyle(offset, options) {
1110
1390
  return {
1111
1391
  position: "sticky",
1112
1392
  ...offset.side === "left" ? { left: offset.offset } : { right: offset.offset },
1113
- zIndex: zBase + offset.stack,
1114
- ...options?.isHeader ? { top: options.headerTop ?? 0 } : {}
1393
+ zIndex: zBase + offset.stack
1394
+ };
1395
+ }
1396
+ function resolveHeaderFreezeOffset(column, freezeOffsets) {
1397
+ const direct = freezeOffsets.get(column.id);
1398
+ if (direct) return direct;
1399
+ const leaves = typeof column.getLeafColumns === "function" ? column.getLeafColumns() : column.columns && column.columns.length > 0 ? flattenHeaderLeaves(column) : [];
1400
+ if (leaves.length === 0) return void 0;
1401
+ const leafOffsets = [];
1402
+ for (const leaf of leaves) {
1403
+ const offset2 = freezeOffsets.get(leaf.id);
1404
+ if (!offset2) return void 0;
1405
+ leafOffsets.push(offset2);
1406
+ }
1407
+ const side = leafOffsets[0]?.side;
1408
+ if (!side || leafOffsets.some((offset2) => offset2.side !== side)) {
1409
+ return void 0;
1410
+ }
1411
+ const offset = Math.min(...leafOffsets.map((item) => item.offset));
1412
+ const leftmost = leafOffsets[0];
1413
+ const rightmost = leafOffsets[leafOffsets.length - 1];
1414
+ return {
1415
+ side,
1416
+ offset,
1417
+ edgeLeft: leftmost.edgeLeft,
1418
+ edgeRight: rightmost.edgeRight,
1419
+ isEdge: leftmost.edgeLeft || rightmost.edgeRight,
1420
+ stack: Math.max(...leafOffsets.map((item) => item.stack))
1115
1421
  };
1116
1422
  }
1423
+ function flattenHeaderLeaves(column) {
1424
+ if (!column.columns || column.columns.length === 0) return [column];
1425
+ return column.columns.flatMap((child) => flattenHeaderLeaves(child));
1426
+ }
1117
1427
 
1118
1428
  // src/components/ui/table/features/inline-search/inlineSearch.ts
1119
1429
  var INLINE_SEARCH_MAX_RESULTS = 1e3;
@@ -1864,6 +2174,7 @@ function useGlideTable(options) {
1864
2174
  onDataChange,
1865
2175
  onCellChange,
1866
2176
  onBatchChange,
2177
+ cellRenderers,
1867
2178
  preserveRowSelection = false,
1868
2179
  toggleField,
1869
2180
  childField,
@@ -2089,6 +2400,26 @@ function useGlideTable(options) {
2089
2400
  commitEdit,
2090
2401
  cancelEdit
2091
2402
  } = useCellEdit({ data: tableData, rows, onDataChange, onCellChange });
2403
+ const cellRendererRegistry = useMemo3(
2404
+ () => createCellRendererRegistry(cellRenderers),
2405
+ [cellRenderers]
2406
+ );
2407
+ const commitRenderedCellValue = useCallback4(
2408
+ (rowId, columnId, value) => commitCellValue({
2409
+ data: tableData,
2410
+ rows,
2411
+ rowId,
2412
+ columnId,
2413
+ value,
2414
+ onCellChange,
2415
+ onDataChange
2416
+ }),
2417
+ [onCellChange, onDataChange, rows, tableData]
2418
+ );
2419
+ const getCellContext = useCallback4(
2420
+ (cell) => withCellUpdate(cell.getContext(), commitRenderedCellValue),
2421
+ [commitRenderedCellValue]
2422
+ );
2092
2423
  const handleCellMouseDownWithCommit = useCallback4(
2093
2424
  (rowIndex, colIndex, options2) => {
2094
2425
  const isSameEditingCell = editingCell?.rowIndex === rowIndex && editingCell?.colIndex === colIndex;
@@ -2325,6 +2656,10 @@ function useGlideTable(options) {
2325
2656
  onCommitEdit: commitEdit,
2326
2657
  onCancelEdit: cancelEdit
2327
2658
  },
2659
+ cellRender: {
2660
+ registry: cellRendererRegistry,
2661
+ commitValue: commitRenderedCellValue
2662
+ },
2328
2663
  expand: {
2329
2664
  enableExpand,
2330
2665
  toggleField,
@@ -2371,6 +2706,8 @@ function useGlideTable(options) {
2371
2706
  startEdit,
2372
2707
  commitEdit,
2373
2708
  cancelEdit,
2709
+ cellRendererRegistry,
2710
+ commitRenderedCellValue,
2374
2711
  enableExpand,
2375
2712
  toggleField,
2376
2713
  expandedRows,
@@ -2415,6 +2752,7 @@ function useGlideTable(options) {
2415
2752
  paddingTop,
2416
2753
  paddingBottom,
2417
2754
  rowContextValue,
2755
+ getCellContext,
2418
2756
  handleToggleSelect,
2419
2757
  clearHover,
2420
2758
  copySelection: stableCopySelection,
@@ -2435,6 +2773,54 @@ function useGlideTable(options) {
2435
2773
  };
2436
2774
  }
2437
2775
 
2776
+ // src/components/ui/table/features/cell-render/ResolvedTableCell.tsx
2777
+ import { useCallback as useCallback5 } from "react";
2778
+
2779
+ // src/components/ui/table/DataTableContext.tsx
2780
+ import { createContext, use } from "react";
2781
+ import { jsx as jsx2 } from "react/jsx-runtime";
2782
+ var DataTableContext = createContext(null);
2783
+ function useDataTableRowContext() {
2784
+ const context = use(DataTableContext);
2785
+ if (!context) {
2786
+ throw new Error("useDataTableRowContext must be used within a DataTableContextProvider");
2787
+ }
2788
+ return context;
2789
+ }
2790
+
2791
+ // src/components/ui/table/features/cell-render/ResolvedTableCell.tsx
2792
+ function ResolvedTableCell({
2793
+ info
2794
+ }) {
2795
+ const { cellRender } = useDataTableRowContext();
2796
+ const { row, column, getValue } = info;
2797
+ const meta = column.columnDef.meta;
2798
+ const value = getValue();
2799
+ const columnId = column.id;
2800
+ const update = useCallback5(
2801
+ (next) => {
2802
+ cellRender.commitValue(row.id, columnId, next);
2803
+ },
2804
+ [cellRender, columnId, row.id]
2805
+ );
2806
+ const ctx = {
2807
+ value,
2808
+ row,
2809
+ index: row.index,
2810
+ columnId,
2811
+ cellProps: meta?.cellProps,
2812
+ update
2813
+ };
2814
+ if (meta?.cellRender) {
2815
+ return meta.cellRender(ctx);
2816
+ }
2817
+ const renderer = resolveCellRenderer(cellRender.registry, meta?.kind, ctx);
2818
+ if (renderer) {
2819
+ return renderer.render(ctx);
2820
+ }
2821
+ return formatDefaultCellValue(value);
2822
+ }
2823
+
2438
2824
  // src/components/ui/table/features/column-resize/columnResize.ts
2439
2825
  function getColumnSizeStyle(size, options) {
2440
2826
  const { force = false, lockMax = false } = options ?? {};
@@ -2448,6 +2834,7 @@ function getColumnSizeStyle(size, options) {
2448
2834
  };
2449
2835
  }
2450
2836
  export {
2837
+ BUILTIN_CELL_RENDERERS,
2451
2838
  CELL_SELECTION_EDGES_CLASS,
2452
2839
  DEFAULT_DATA_TABLE_LABELS,
2453
2840
  DEFAULT_TREE_CHILDREN_FIELD,
@@ -2455,6 +2842,7 @@ export {
2455
2842
  DEFAULT_TREE_PARENT_ID_FIELD,
2456
2843
  DEFAULT_TREE_QTY_FIELD,
2457
2844
  INLINE_SEARCH_MAX_RESULTS,
2845
+ ResolvedTableCell,
2458
2846
  applyCellEdit,
2459
2847
  applyFillData,
2460
2848
  applySelectionUpdater,
@@ -2473,9 +2861,13 @@ export {
2473
2861
  collectFillChanges,
2474
2862
  collectRowSpanColumns,
2475
2863
  collectSearchMatchesInRange,
2864
+ commitCellValue,
2865
+ createCellRendererRegistry,
2476
2866
  createSearchRegex,
2477
2867
  escapeSearchRegex,
2478
2868
  flattenSubtreeRows,
2869
+ formatCellValue,
2870
+ formatDefaultCellValue,
2479
2871
  formatSearchResultLabel,
2480
2872
  getCellEditDraftValue,
2481
2873
  getCellSelectionEdgeStyle,
@@ -2497,8 +2889,10 @@ export {
2497
2889
  parseClipboardTSV,
2498
2890
  parseClipboardTSVWithDepths,
2499
2891
  previousSearchIndex,
2892
+ resolveCellRenderer,
2500
2893
  resolveColumnFreezeSide,
2501
2894
  resolveDataTableLabels,
2895
+ resolveHeaderFreezeOffset,
2502
2896
  resolvePasteColumnIds,
2503
2897
  resolveRowSelection,
2504
2898
  resolveRowSpanAt,
@@ -2511,5 +2905,6 @@ export {
2511
2905
  useConvertTreeData,
2512
2906
  useGlideTable,
2513
2907
  useInlineSearch,
2908
+ withCellUpdate,
2514
2909
  writeSelectionToClipboard
2515
2910
  };