@undevy-org/tablewright 0.2.1 → 0.3.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/es/hooks/table-orchestration/use-column-filter-stats.js +38 -0
- package/dist/es/hooks/table-orchestration/use-column-gaps.js +31 -0
- package/dist/es/hooks/table-orchestration/use-filtered-rows.js +32 -0
- package/dist/es/hooks/table-orchestration/use-orchestration-actions.js +182 -0
- package/dist/es/hooks/table-orchestration/use-responsive-columns.js +35 -0
- package/dist/es/hooks/useTableOrchestration.js +69 -264
- package/dist/es/index.js +2 -1
- package/dist/es/lib/column-gaps.js +5 -1
- package/dist/tablewright.cjs +390 -289
- package/dist/types/hooks/table-orchestration/use-column-filter-stats.d.ts +6 -0
- package/dist/types/hooks/table-orchestration/use-column-gaps.d.ts +15 -0
- package/dist/types/hooks/table-orchestration/use-filtered-rows.d.ts +15 -0
- package/dist/types/hooks/table-orchestration/use-orchestration-actions.d.ts +49 -0
- package/dist/types/hooks/table-orchestration/use-responsive-columns.d.ts +10 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/lib/column-gaps.d.ts +6 -0
- package/package.json +1 -1
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { useState,
|
|
1
|
+
import { useState, useRef, useMemo, useCallback, useLayoutEffect, useEffect } from "react";
|
|
2
2
|
import { useStore } from "zustand";
|
|
3
3
|
import { useColumnResize } from "../components/data-table/hooks/use-column-resize.js";
|
|
4
4
|
import { getVisiblePageNumbers } from "../components/data-table/hooks/use-table-pagination.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
5
|
+
import { createTableOrchestrationStore } from "./table-orchestration-store.js";
|
|
6
|
+
import { useColumnFilterStats } from "./table-orchestration/use-column-filter-stats.js";
|
|
7
|
+
import { useColumnGaps } from "./table-orchestration/use-column-gaps.js";
|
|
8
|
+
import { useFilteredRows } from "./table-orchestration/use-filtered-rows.js";
|
|
9
|
+
import { useOrchestrationActions } from "./table-orchestration/use-orchestration-actions.js";
|
|
10
|
+
import { useResponsiveColumns } from "./table-orchestration/use-responsive-columns.js";
|
|
9
11
|
const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
|
10
12
|
function useTableOrchestration(config) {
|
|
11
13
|
const {
|
|
@@ -46,33 +48,11 @@ function useTableOrchestration(config) {
|
|
|
46
48
|
const activeView = useStore(store, (s) => s.activeView);
|
|
47
49
|
const columnsMenuOpen = useStore(store, (s) => s.columnsMenuOpen);
|
|
48
50
|
const storedSelectedRowId = useStore(store, (s) => s.selectedRowId);
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const mql = window.matchMedia(`(min-width: ${wideBreakpoint}px)`);
|
|
55
|
-
const onChange = (e) => setIsWide(e.matches);
|
|
56
|
-
setIsWide(mql.matches);
|
|
57
|
-
mql.addEventListener("change", onChange);
|
|
58
|
-
return () => mql.removeEventListener("change", onChange);
|
|
59
|
-
}, [hasPriorities, wideBreakpoint]);
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
if (typeof window === "undefined") return;
|
|
62
|
-
const mql = window.matchMedia(`(max-width: ${compactBreakpoint - 1}px)`);
|
|
63
|
-
const onChange = (e) => setIsCompact(e.matches);
|
|
64
|
-
setIsCompact(mql.matches);
|
|
65
|
-
mql.addEventListener("change", onChange);
|
|
66
|
-
return () => mql.removeEventListener("change", onChange);
|
|
67
|
-
}, [compactBreakpoint]);
|
|
68
|
-
const responsiveHiddenColumnIds = useMemo(() => {
|
|
69
|
-
if (isWide || !columnPriorities) return EMPTY_ID_SET;
|
|
70
|
-
const hidden = /* @__PURE__ */ new Set();
|
|
71
|
-
for (const [id, priority] of Object.entries(columnPriorities)) {
|
|
72
|
-
if (priority === 3) hidden.add(id);
|
|
73
|
-
}
|
|
74
|
-
return hidden;
|
|
75
|
-
}, [isWide, columnPriorities]);
|
|
51
|
+
const { responsiveHiddenColumnIds, isCompact } = useResponsiveColumns(
|
|
52
|
+
columnPriorities,
|
|
53
|
+
wideBreakpoint,
|
|
54
|
+
compactBreakpoint
|
|
55
|
+
);
|
|
76
56
|
const selectedIdsRef = useRef(storedSelectedIds);
|
|
77
57
|
const headerCheckStateRef = useRef(false);
|
|
78
58
|
const sortState = useMemo(() => {
|
|
@@ -92,89 +72,22 @@ function useTableOrchestration(config) {
|
|
|
92
72
|
}
|
|
93
73
|
return viewPresets.find((v) => v.key === activeView) ?? viewPresets[0];
|
|
94
74
|
}, [activeView, viewPresets]);
|
|
95
|
-
const
|
|
96
|
-
const result = {};
|
|
97
|
-
for (const [columnId, cfg] of Object.entries(filterConfigs)) {
|
|
98
|
-
if (cfg.type === "compound" && cfg.subFilters) {
|
|
99
|
-
for (const sub of cfg.subFilters) {
|
|
100
|
-
const s = computeColumnStats(rows, sub.field, {
|
|
101
|
-
type: sub.type,
|
|
102
|
-
label: sub.label,
|
|
103
|
-
options: sub.options
|
|
104
|
-
});
|
|
105
|
-
if (s) result[`${columnId}:${sub.key}`] = s;
|
|
106
|
-
}
|
|
107
|
-
} else {
|
|
108
|
-
const s = computeColumnStats(rows, cfg.fieldName ?? columnId, cfg);
|
|
109
|
-
if (s) result[columnId] = s;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
return result;
|
|
113
|
-
}, [rows, filterConfigs]);
|
|
114
|
-
const filteredRows = useMemo(() => {
|
|
115
|
-
const search = searchQuery.trim().toLowerCase();
|
|
116
|
-
const filtered = rows.filter((row) => {
|
|
117
|
-
if (activePreset.customFilter && !activePreset.customFilter(row)) return false;
|
|
118
|
-
if (search.length > 0 && !getSearchText(row).includes(search)) return false;
|
|
119
|
-
return columnFilters.every((filter) => filterRow(row, filter));
|
|
120
|
-
});
|
|
121
|
-
if (!currentSort) return filtered;
|
|
122
|
-
const { columnId, direction } = currentSort;
|
|
123
|
-
return [...filtered].sort((a, b) => {
|
|
124
|
-
const aVal = getSortValue(a, columnId);
|
|
125
|
-
const bVal = getSortValue(b, columnId);
|
|
126
|
-
if (aVal < bVal) return direction === "asc" ? -1 : 1;
|
|
127
|
-
if (aVal > bVal) return direction === "asc" ? 1 : -1;
|
|
128
|
-
return 0;
|
|
129
|
-
});
|
|
130
|
-
}, [
|
|
75
|
+
const filteredRows = useFilteredRows({
|
|
131
76
|
rows,
|
|
77
|
+
activePreset,
|
|
132
78
|
searchQuery,
|
|
133
79
|
columnFilters,
|
|
134
80
|
currentSort,
|
|
135
|
-
activePreset,
|
|
136
|
-
getSearchText,
|
|
137
81
|
filterRow,
|
|
82
|
+
getSearchText,
|
|
138
83
|
getSortValue
|
|
139
|
-
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const s = computeColumnStats(filteredRows, sub.field, {
|
|
147
|
-
type: sub.type,
|
|
148
|
-
label: sub.label,
|
|
149
|
-
options: sub.options
|
|
150
|
-
});
|
|
151
|
-
if (s) result[`${columnId}:${sub.key}`] = s;
|
|
152
|
-
}
|
|
153
|
-
} else {
|
|
154
|
-
const s = computeColumnStats(filteredRows, cfg.fieldName ?? columnId, cfg);
|
|
155
|
-
if (s) result[columnId] = s;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return result;
|
|
159
|
-
}, [filteredRows, filterConfigs, columnFilters, searchQuery]);
|
|
160
|
-
const columnFilterStats = useMemo(() => {
|
|
161
|
-
const result = {};
|
|
162
|
-
for (const [columnId, cfg] of Object.entries(filterConfigs)) {
|
|
163
|
-
if (cfg.type === "compound" && cfg.subFilters) {
|
|
164
|
-
for (const sub of cfg.subFilters) {
|
|
165
|
-
const key = `${columnId}:${sub.key}`;
|
|
166
|
-
const all = allColumnFilterStats[key];
|
|
167
|
-
if (!all) continue;
|
|
168
|
-
result[key] = { all, filtered: filteredColumnFilterStats?.[key] ?? null };
|
|
169
|
-
}
|
|
170
|
-
} else {
|
|
171
|
-
const all = allColumnFilterStats[columnId];
|
|
172
|
-
if (!all) continue;
|
|
173
|
-
result[columnId] = { all, filtered: filteredColumnFilterStats?.[columnId] ?? null };
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
return result;
|
|
177
|
-
}, [allColumnFilterStats, filteredColumnFilterStats, filterConfigs]);
|
|
84
|
+
});
|
|
85
|
+
const columnFilterStats = useColumnFilterStats(
|
|
86
|
+
rows,
|
|
87
|
+
filteredRows,
|
|
88
|
+
filterConfigs,
|
|
89
|
+
columnFilters.length > 0 || searchQuery.trim().length > 0
|
|
90
|
+
);
|
|
178
91
|
const totalPages = Math.max(1, Math.ceil(filteredRows.length / rowsPerPage));
|
|
179
92
|
const safePage = Math.min(storedPage, totalPages);
|
|
180
93
|
const page = safePage;
|
|
@@ -208,167 +121,59 @@ function useTableOrchestration(config) {
|
|
|
208
121
|
);
|
|
209
122
|
const allOnPageSelected = pageIds.size > 0 && selectedOnPage === pageIds.size;
|
|
210
123
|
const headerCheckState = allOnPageSelected ? true : selectedOnPage > 0 ? "indeterminate" : false;
|
|
211
|
-
const columnGaps =
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
for (const gap of columnGaps) {
|
|
219
|
-
meta[`__gap_after_${gap.afterColumnId ?? "start"}`] = { minW: 32 };
|
|
220
|
-
}
|
|
221
|
-
return meta;
|
|
222
|
-
}, [columnGaps, columnMeta]);
|
|
223
|
-
const extendedWidths = useMemo(() => {
|
|
224
|
-
if (columnGaps.length === 0) return widths;
|
|
225
|
-
const merged = { ...widths };
|
|
226
|
-
for (const gap of columnGaps) {
|
|
227
|
-
merged[`__gap_after_${gap.afterColumnId ?? "start"}`] = 32;
|
|
228
|
-
}
|
|
229
|
-
return merged;
|
|
230
|
-
}, [widths, columnGaps]);
|
|
124
|
+
const { columnGaps, extendedColumnMeta, extendedWidths } = useColumnGaps({
|
|
125
|
+
columnGroups,
|
|
126
|
+
toggleableColumnIds,
|
|
127
|
+
columnVisibility,
|
|
128
|
+
columnMeta,
|
|
129
|
+
widths
|
|
130
|
+
});
|
|
231
131
|
const hiddenColumnsCount = useMemo(() => {
|
|
232
132
|
return toggleableColumnIds.filter((id) => columnVisibility[id] === false).length;
|
|
233
133
|
}, [toggleableColumnIds, columnVisibility]);
|
|
234
134
|
const activeCustomFilterChip = activePreset.customFilterChip ?? null;
|
|
235
135
|
selectedIdsRef.current = selectedIds;
|
|
236
136
|
headerCheckStateRef.current = headerCheckState;
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
() => store.getState().setRequestOpenFilterId(null),
|
|
278
|
-
[store]
|
|
279
|
-
);
|
|
280
|
-
const handleRemoveColumnFilter = useCallback(
|
|
281
|
-
(columnId) => store.getState().removeColumnFilter(columnId),
|
|
282
|
-
[store]
|
|
283
|
-
);
|
|
284
|
-
const handleChangeColumnFilter = useCallback(
|
|
285
|
-
(columnId, value) => store.getState().changeColumnFilter(columnId, value),
|
|
286
|
-
[store]
|
|
287
|
-
);
|
|
288
|
-
const handleSetColumnFilter = useCallback(
|
|
289
|
-
(columnId, value) => store.getState().setColumnFilter(columnId, value),
|
|
290
|
-
[store]
|
|
291
|
-
);
|
|
292
|
-
const handleHeaderFilterClick = useCallback(
|
|
293
|
-
(columnId) => {
|
|
294
|
-
const state = store.getState();
|
|
295
|
-
const isActive = state.columnFilters.some((f) => f.columnId === columnId);
|
|
296
|
-
if (!isActive) {
|
|
297
|
-
const cfg = filterConfigs[columnId];
|
|
298
|
-
if (cfg) state.addColumnFilter(columnId, defaultValueForFilter(cfg));
|
|
299
|
-
}
|
|
300
|
-
state.setRequestOpenFilterId(columnId);
|
|
301
|
-
},
|
|
302
|
-
[filterConfigs, store]
|
|
303
|
-
);
|
|
304
|
-
const handleHideColumn = useCallback(
|
|
305
|
-
(columnId) => store.getState().hideColumn(columnId),
|
|
306
|
-
[store]
|
|
307
|
-
);
|
|
308
|
-
const handleResetColumns = useCallback(() => {
|
|
309
|
-
store.getState().resetColumns();
|
|
310
|
-
setWidths(() => {
|
|
311
|
-
const w = {};
|
|
312
|
-
for (const [id, m] of Object.entries(columnMeta)) w[id] = m.minW;
|
|
313
|
-
return w;
|
|
314
|
-
});
|
|
315
|
-
}, [store, setWidths, columnMeta]);
|
|
316
|
-
const handleRemoveSort = useCallback(() => store.getState().removeSort(), [store]);
|
|
317
|
-
const handleRemoveCustomFilter = useCallback(() => {
|
|
318
|
-
store.getState().goCustom();
|
|
319
|
-
store.getState().setPage(1);
|
|
320
|
-
}, [store]);
|
|
321
|
-
const handleClearAll = useCallback(
|
|
322
|
-
() => store.getState().clearAll(defaultViewKey),
|
|
323
|
-
[defaultViewKey, store]
|
|
324
|
-
);
|
|
325
|
-
const handleRefresh = useCallback(() => store.getState().startRefresh(), [store]);
|
|
326
|
-
const handleToggleRowSelection = useCallback(
|
|
327
|
-
(id) => store.getState().toggleRowSelection(id),
|
|
328
|
-
[store]
|
|
329
|
-
);
|
|
330
|
-
const handleToggleHeaderCheck = useCallback(() => {
|
|
331
|
-
const ids = paginatedRows.map(getRowId);
|
|
332
|
-
store.getState().toggleHeaderCheck(ids, allOnPageSelected);
|
|
333
|
-
}, [paginatedRows, allOnPageSelected, getRowId, store]);
|
|
334
|
-
const handleExpandRow = useCallback(
|
|
335
|
-
(row, e) => {
|
|
336
|
-
e.stopPropagation();
|
|
337
|
-
const id = getRowId(row);
|
|
338
|
-
store.getState().toggleSelectedRowId(id);
|
|
339
|
-
},
|
|
340
|
-
[getRowId, store]
|
|
341
|
-
);
|
|
342
|
-
const handleExpandGap = useCallback(
|
|
343
|
-
(hiddenIds) => store.getState().showColumns(hiddenIds),
|
|
344
|
-
[store]
|
|
345
|
-
);
|
|
346
|
-
const handlePageChange = useCallback((p) => store.getState().setPage(p), [store]);
|
|
347
|
-
const handleRowsPerPageChange = useCallback(
|
|
348
|
-
(value) => store.getState().setRowsPerPage(value),
|
|
349
|
-
[store]
|
|
350
|
-
);
|
|
351
|
-
const setSearchInput = useCallback((v) => store.getState().setSearchInput(v), [store]);
|
|
352
|
-
const setColumnVisibility = useCallback(
|
|
353
|
-
(next) => store.getState().setColumnVisibility(next),
|
|
354
|
-
[store]
|
|
355
|
-
);
|
|
356
|
-
const setDensity = useCallback(
|
|
357
|
-
(v) => store.getState().setDensity(v),
|
|
358
|
-
[store]
|
|
359
|
-
);
|
|
360
|
-
const setSelectedIds = useCallback(
|
|
361
|
-
(next) => store.getState().setSelectedIds(next),
|
|
362
|
-
[store]
|
|
363
|
-
);
|
|
364
|
-
const setColumnsMenuOpen = useCallback(
|
|
365
|
-
(v) => store.getState().setColumnsMenuOpen(v),
|
|
366
|
-
[store]
|
|
367
|
-
);
|
|
368
|
-
const setSelectedRowId = useCallback(
|
|
369
|
-
(v) => store.getState().setSelectedRowId(v),
|
|
370
|
-
[store]
|
|
371
|
-
);
|
|
137
|
+
const {
|
|
138
|
+
handleSearchSubmit,
|
|
139
|
+
handleHeaderSort,
|
|
140
|
+
handleViewChange,
|
|
141
|
+
handleAddColumnFilter,
|
|
142
|
+
handleOpenExistingFilter,
|
|
143
|
+
handleRequestOpenHandled,
|
|
144
|
+
handleRemoveColumnFilter,
|
|
145
|
+
handleChangeColumnFilter,
|
|
146
|
+
handleSetColumnFilter,
|
|
147
|
+
handleHeaderFilterClick,
|
|
148
|
+
handleHideColumn,
|
|
149
|
+
handleResetColumns,
|
|
150
|
+
handleRemoveSort,
|
|
151
|
+
handleRemoveCustomFilter,
|
|
152
|
+
handleClearAll,
|
|
153
|
+
handleRefresh,
|
|
154
|
+
handleToggleRowSelection,
|
|
155
|
+
handleToggleHeaderCheck,
|
|
156
|
+
handleExpandRow,
|
|
157
|
+
handleExpandGap,
|
|
158
|
+
handlePageChange,
|
|
159
|
+
handleRowsPerPageChange,
|
|
160
|
+
setSearchInput,
|
|
161
|
+
setColumnVisibility,
|
|
162
|
+
setDensity,
|
|
163
|
+
setSelectedIds,
|
|
164
|
+
setColumnsMenuOpen,
|
|
165
|
+
setSelectedRowId
|
|
166
|
+
} = useOrchestrationActions({
|
|
167
|
+
store,
|
|
168
|
+
viewPresets,
|
|
169
|
+
filterConfigs,
|
|
170
|
+
columnMeta,
|
|
171
|
+
setWidths,
|
|
172
|
+
defaultViewKey,
|
|
173
|
+
paginatedRows,
|
|
174
|
+
allOnPageSelected,
|
|
175
|
+
getRowId
|
|
176
|
+
});
|
|
372
177
|
const getSelectedIds = useCallback(() => selectedIdsRef.current, []);
|
|
373
178
|
const getHeaderCheckState = useCallback(() => headerCheckStateRef.current, []);
|
|
374
179
|
return {
|
package/dist/es/index.js
CHANGED
|
@@ -65,7 +65,7 @@ import { getCompoundValue, getDateRange, getNumberRange, getStringArray, isFilte
|
|
|
65
65
|
import { useTableOrchestration } from "./hooks/useTableOrchestration.js";
|
|
66
66
|
import { createTableOrchestrationStore, defaultValueForFilter } from "./hooks/table-orchestration-store.js";
|
|
67
67
|
import { useRowDragOverride } from "./hooks/useRowDragOverride.js";
|
|
68
|
-
import { computeColumnGaps } from "./lib/column-gaps.js";
|
|
68
|
+
import { computeColumnGaps, gapColumnId } from "./lib/column-gaps.js";
|
|
69
69
|
import { computeColumnStats } from "./lib/filter-stats.js";
|
|
70
70
|
import { formatEnumLabel } from "./lib/format-enum.js";
|
|
71
71
|
import { displayTimeZone, formatDateParts, formatLiveTimestamp, parsePossibleTimestamp } from "./lib/format-timestamp.js";
|
|
@@ -184,6 +184,7 @@ export {
|
|
|
184
184
|
formatDateParts,
|
|
185
185
|
formatEnumLabel,
|
|
186
186
|
formatLiveTimestamp,
|
|
187
|
+
gapColumnId,
|
|
187
188
|
getCompoundValue,
|
|
188
189
|
getDateRange,
|
|
189
190
|
getNumberRange,
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
function gapColumnId(afterColumnId) {
|
|
2
|
+
return `__gap_after_${afterColumnId ?? "start"}`;
|
|
3
|
+
}
|
|
1
4
|
function computeColumnGaps(orderedColumnIds, columnVisibility) {
|
|
2
5
|
const gaps = [];
|
|
3
6
|
let lastVisibleId = null;
|
|
@@ -19,5 +22,6 @@ function computeColumnGaps(orderedColumnIds, columnVisibility) {
|
|
|
19
22
|
return gaps;
|
|
20
23
|
}
|
|
21
24
|
export {
|
|
22
|
-
computeColumnGaps
|
|
25
|
+
computeColumnGaps,
|
|
26
|
+
gapColumnId
|
|
23
27
|
};
|