@xeplr/ui-table 1.0.0 → 1.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.
@@ -14,6 +14,7 @@ import { stringFilterFn } from './filters/StringFilter.jsx';
14
14
  import { numberFilterFn } from './filters/NumberFilter.jsx';
15
15
  import { dateFilterFn } from './filters/DateFilter.jsx';
16
16
  import { booleanFilterFn } from './filters/BooleanFilter.jsx';
17
+ import renderers from './renderers/index.js';
17
18
 
18
19
  var filterFnMap = {
19
20
  [TYPES.STRING]: stringFilterFn,
@@ -24,12 +25,27 @@ var filterFnMap = {
24
25
 
25
26
  var columnHelper = createColumnHelper();
26
27
 
28
+ // A column def may itself be a GROUP — `{ header, columns: [...] }` instead of
29
+ // `{ accessor, ... }` — which becomes a spanning header cell over its
30
+ // children (a pivoted date over the measures under it), same as Excel merges
31
+ // a date across the columns it covers. Optional and recursive: a caller that
32
+ // never nests columns gets exactly the flat header it always got.
33
+ function flattenLeaves(cols) {
34
+ var out = [];
35
+ for (var i = 0; i < cols.length; i++) {
36
+ var col = cols[i];
37
+ if (col.columns && col.columns.length) out = out.concat(flattenLeaves(col.columns));
38
+ else out.push(col);
39
+ }
40
+ return out;
41
+ }
42
+
27
43
  /**
28
44
  * Hook that wires TanStack Table with auto-detected column types and smart filters.
29
45
  *
30
46
  * @param {object} options
31
47
  * @param {Array<object>} options.data - Row array
32
- * @param {Array<object>} options.columns - [{ accessor, header, dataType?, cell? }]
48
+ * @param {Array<object>} options.columns - [{ accessor, header, dataType?, cell? } | { header, columns: [...] }]
33
49
  * @param {number} [options.minDetectionRows] - Min samples for confident detection (default: 10)
34
50
  * @param {number} [options.pageSize] - Rows per page (default: 20)
35
51
  * @param {boolean} [options.enableSorting] - Default: true
@@ -64,14 +80,28 @@ export default function useTableController(options) {
64
80
  }
65
81
 
66
82
  // Run detection
67
- var result = detectTypes(data, columns, minDetectionRows);
83
+ // Leaves only — a group column has no accessor and no data of its own to
84
+ // sample, it is purely a spanning header over the columns that do.
85
+ var result = detectTypes(data, flattenLeaves(columns), minDetectionRows);
68
86
  detectionRef.current = { types: result.types, confident: result.confident, dataRef: data };
69
87
  return result.types;
70
88
  }, [data, columns, minDetectionRows]);
71
89
 
72
90
  // ── Build TanStack column defs ──
73
91
  var tanstackColumns = useMemo(function() {
74
- return columns.map(function(col) {
92
+ function build(col) {
93
+ // A group has no data of its own — column.group() renders it as a
94
+ // spanning header cell (TanStack's own colSpan) over whichever leaf
95
+ // columns are nested under it, and nothing else about those leaves
96
+ // changes: same filters, same sort, same cells they'd have flat.
97
+ if (col.columns && col.columns.length) {
98
+ return columnHelper.group({
99
+ id: col.id || col.header,
100
+ header: col.header,
101
+ columns: col.columns.map(build)
102
+ });
103
+ }
104
+
75
105
  var type = detectedTypes.get(col.accessor) || TYPES.STRING;
76
106
  var filterFn = filterFnMap[type] || stringFilterFn;
77
107
 
@@ -82,13 +112,46 @@ export default function useTableController(options) {
82
112
  enableSorting: col.enableSorting !== false,
83
113
  enableColumnFilter: col.enableColumnFilter !== false
84
114
  };
85
- if (col.cell) colDef.cell = col.cell;
115
+ if (col.cell) {
116
+ colDef.cell = col.cell;
117
+ } else if (col.render) {
118
+ var rendererName = typeof col.render === 'string' ? col.render : col.render.type;
119
+ var rendererFn = renderers[rendererName];
120
+ if (!rendererFn) {
121
+ throw new Error('[xeplr-ui-table] Unknown renderer "' + rendererName + '". Available: ' + Object.keys(renderers).join(', '));
122
+ }
123
+ var rendererConfig = typeof col.render === 'string' ? {} : col.render;
124
+ colDef.cell = rendererFn(rendererConfig);
125
+ }
86
126
  return columnHelper.accessor(col.accessor, colDef);
87
- });
127
+ }
128
+ return columns.map(build);
88
129
  }, [columns, detectedTypes]);
89
130
 
90
131
  // ── Table state ──
91
- var [sorting, setSorting] = useState([]);
132
+ //
133
+ // Sorting can be CONTROLLED by the host. Uncontrolled is the default and is
134
+ // what a plain data grid wants: click a header, the table reorders itself.
135
+ //
136
+ // A host takes it over when the ordering is not the table's to decide.
137
+ // A grouped report is the case that forces it — its rows carry subtotals
138
+ // that must stay with their group, and columns whose values are derived
139
+ // from the row above them. Re-sorting such a result in the view layer
140
+ // scatters the subtotals and leaves every running total describing an order
141
+ // that is no longer on screen. So the host supplies the order and takes the
142
+ // header click as an instruction, which is what `manualSorting` means to
143
+ // TanStack: "already sorted, do not sort it again".
144
+ var controlledSorting = options.sorting !== undefined;
145
+ var [ownSorting, setOwnSorting] = useState([]);
146
+ var sorting = controlledSorting ? options.sorting : ownSorting;
147
+ var setSorting = function (updater) {
148
+ var next = typeof updater === 'function' ? updater(sorting) : updater;
149
+ if (controlledSorting) {
150
+ if (options.onSortingChange) options.onSortingChange(next);
151
+ return;
152
+ }
153
+ setOwnSorting(next);
154
+ };
92
155
  var [columnFilters, setColumnFilters] = useState([]);
93
156
  var [pagination, setPagination] = useState({ pageIndex: 0, pageSize: pageSize });
94
157
 
@@ -106,7 +169,11 @@ export default function useTableController(options) {
106
169
  onPaginationChange: setPagination,
107
170
  getCoreRowModel: getCoreRowModel(),
108
171
  getFilteredRowModel: enableFiltering ? getFilteredRowModel() : undefined,
109
- getSortedRowModel: enableSorting ? getSortedRowModel() : undefined,
172
+ // Not applied when the host controls the order — the rows arrive sorted
173
+ // and sorting them again is both wasted work and, for a grouped result,
174
+ // wrong.
175
+ getSortedRowModel: (enableSorting && !controlledSorting) ? getSortedRowModel() : undefined,
176
+ manualSorting: controlledSorting,
110
177
  getPaginationRowModel: enablePagination ? getPaginationRowModel() : undefined,
111
178
  getFacetedRowModel: getFacetedRowModel(),
112
179
  getFacetedUniqueValues: getFacetedUniqueValues(),