@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
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { computeColumnStats } from "../../lib/filter-stats.js";
|
|
3
|
+
function computeFilterStats(rows, filterConfigs) {
|
|
4
|
+
const result = {};
|
|
5
|
+
for (const [columnId, cfg] of Object.entries(filterConfigs)) {
|
|
6
|
+
if (cfg.type === "compound" && cfg.subFilters) {
|
|
7
|
+
for (const sub of cfg.subFilters) {
|
|
8
|
+
const s = computeColumnStats(rows, sub.field, {
|
|
9
|
+
type: sub.type,
|
|
10
|
+
label: sub.label,
|
|
11
|
+
options: sub.options
|
|
12
|
+
});
|
|
13
|
+
if (s) result[`${columnId}:${sub.key}`] = s;
|
|
14
|
+
}
|
|
15
|
+
} else {
|
|
16
|
+
const s = computeColumnStats(rows, cfg.fieldName ?? columnId, cfg);
|
|
17
|
+
if (s) result[columnId] = s;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
function useColumnFilterStats(rows, filteredRows, filterConfigs, isFiltered) {
|
|
23
|
+
const allStats = useMemo(() => computeFilterStats(rows, filterConfigs), [rows, filterConfigs]);
|
|
24
|
+
const filteredStats = useMemo(
|
|
25
|
+
() => isFiltered ? computeFilterStats(filteredRows, filterConfigs) : null,
|
|
26
|
+
[filteredRows, filterConfigs, isFiltered]
|
|
27
|
+
);
|
|
28
|
+
return useMemo(() => {
|
|
29
|
+
const result = {};
|
|
30
|
+
for (const [key, all] of Object.entries(allStats)) {
|
|
31
|
+
result[key] = { all, filtered: filteredStats?.[key] ?? null };
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}, [allStats, filteredStats]);
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
useColumnFilterStats
|
|
38
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { computeColumnGaps, gapColumnId } from "../../lib/column-gaps.js";
|
|
3
|
+
const GAP_WIDTH = 32;
|
|
4
|
+
function useColumnGaps({
|
|
5
|
+
columnGroups,
|
|
6
|
+
toggleableColumnIds,
|
|
7
|
+
columnVisibility,
|
|
8
|
+
columnMeta,
|
|
9
|
+
widths
|
|
10
|
+
}) {
|
|
11
|
+
const columnGaps = useMemo(
|
|
12
|
+
() => columnGroups ? computeColumnGaps(toggleableColumnIds, columnVisibility) : [],
|
|
13
|
+
[columnGroups, toggleableColumnIds, columnVisibility]
|
|
14
|
+
);
|
|
15
|
+
const extendedColumnMeta = useMemo(() => {
|
|
16
|
+
if (columnGaps.length === 0) return columnMeta;
|
|
17
|
+
const meta = { ...columnMeta };
|
|
18
|
+
for (const gap of columnGaps) meta[gapColumnId(gap.afterColumnId)] = { minW: GAP_WIDTH };
|
|
19
|
+
return meta;
|
|
20
|
+
}, [columnGaps, columnMeta]);
|
|
21
|
+
const extendedWidths = useMemo(() => {
|
|
22
|
+
if (columnGaps.length === 0) return widths;
|
|
23
|
+
const merged = { ...widths };
|
|
24
|
+
for (const gap of columnGaps) merged[gapColumnId(gap.afterColumnId)] = GAP_WIDTH;
|
|
25
|
+
return merged;
|
|
26
|
+
}, [widths, columnGaps]);
|
|
27
|
+
return { columnGaps, extendedColumnMeta, extendedWidths };
|
|
28
|
+
}
|
|
29
|
+
export {
|
|
30
|
+
useColumnGaps
|
|
31
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
function useFilteredRows({
|
|
3
|
+
rows,
|
|
4
|
+
activePreset,
|
|
5
|
+
searchQuery,
|
|
6
|
+
columnFilters,
|
|
7
|
+
currentSort,
|
|
8
|
+
filterRow,
|
|
9
|
+
getSearchText,
|
|
10
|
+
getSortValue
|
|
11
|
+
}) {
|
|
12
|
+
return useMemo(() => {
|
|
13
|
+
const search = searchQuery.trim().toLowerCase();
|
|
14
|
+
const filtered = rows.filter((row) => {
|
|
15
|
+
if (activePreset.customFilter && !activePreset.customFilter(row)) return false;
|
|
16
|
+
if (search.length > 0 && !getSearchText(row).includes(search)) return false;
|
|
17
|
+
return columnFilters.every((filter) => filterRow(row, filter));
|
|
18
|
+
});
|
|
19
|
+
if (!currentSort) return filtered;
|
|
20
|
+
const { columnId, direction } = currentSort;
|
|
21
|
+
return [...filtered].sort((a, b) => {
|
|
22
|
+
const aVal = getSortValue(a, columnId);
|
|
23
|
+
const bVal = getSortValue(b, columnId);
|
|
24
|
+
if (aVal < bVal) return direction === "asc" ? -1 : 1;
|
|
25
|
+
if (aVal > bVal) return direction === "asc" ? 1 : -1;
|
|
26
|
+
return 0;
|
|
27
|
+
});
|
|
28
|
+
}, [rows, searchQuery, columnFilters, currentSort, activePreset, getSearchText, filterRow, getSortValue]);
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
useFilteredRows
|
|
32
|
+
};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import { defaultValueForFilter } from "../table-orchestration-store.js";
|
|
3
|
+
function useOrchestrationActions({
|
|
4
|
+
store,
|
|
5
|
+
viewPresets,
|
|
6
|
+
filterConfigs,
|
|
7
|
+
columnMeta,
|
|
8
|
+
setWidths,
|
|
9
|
+
defaultViewKey,
|
|
10
|
+
paginatedRows,
|
|
11
|
+
allOnPageSelected,
|
|
12
|
+
getRowId
|
|
13
|
+
}) {
|
|
14
|
+
const handleSearchSubmit = useCallback(
|
|
15
|
+
(e) => {
|
|
16
|
+
e.preventDefault();
|
|
17
|
+
store.getState().submitSearch();
|
|
18
|
+
},
|
|
19
|
+
[store]
|
|
20
|
+
);
|
|
21
|
+
const handleHeaderSort = useCallback(
|
|
22
|
+
(columnId, direction) => {
|
|
23
|
+
store.getState().toggleSort(columnId, direction);
|
|
24
|
+
},
|
|
25
|
+
[store]
|
|
26
|
+
);
|
|
27
|
+
const handleViewChange = useCallback(
|
|
28
|
+
(key) => {
|
|
29
|
+
if (key === "custom") return;
|
|
30
|
+
const preset = viewPresets.find((v) => v.key === key);
|
|
31
|
+
if (!preset) return;
|
|
32
|
+
store.getState().applyPreset({
|
|
33
|
+
key,
|
|
34
|
+
filters: preset.filters,
|
|
35
|
+
sort: preset.sort,
|
|
36
|
+
columnVisibility: preset.columnVisibility
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
[viewPresets, store]
|
|
40
|
+
);
|
|
41
|
+
const handleAddColumnFilter = useCallback(
|
|
42
|
+
(columnId) => {
|
|
43
|
+
const cfg = filterConfigs[columnId];
|
|
44
|
+
if (!cfg) return;
|
|
45
|
+
store.getState().addColumnFilter(columnId, defaultValueForFilter(cfg));
|
|
46
|
+
},
|
|
47
|
+
[filterConfigs, store]
|
|
48
|
+
);
|
|
49
|
+
const handleOpenExistingFilter = useCallback(
|
|
50
|
+
(columnId) => store.getState().setRequestOpenFilterId(columnId),
|
|
51
|
+
[store]
|
|
52
|
+
);
|
|
53
|
+
const handleRequestOpenHandled = useCallback(
|
|
54
|
+
() => store.getState().setRequestOpenFilterId(null),
|
|
55
|
+
[store]
|
|
56
|
+
);
|
|
57
|
+
const handleRemoveColumnFilter = useCallback(
|
|
58
|
+
(columnId) => store.getState().removeColumnFilter(columnId),
|
|
59
|
+
[store]
|
|
60
|
+
);
|
|
61
|
+
const handleChangeColumnFilter = useCallback(
|
|
62
|
+
(columnId, value) => store.getState().changeColumnFilter(columnId, value),
|
|
63
|
+
[store]
|
|
64
|
+
);
|
|
65
|
+
const handleSetColumnFilter = useCallback(
|
|
66
|
+
(columnId, value) => store.getState().setColumnFilter(columnId, value),
|
|
67
|
+
[store]
|
|
68
|
+
);
|
|
69
|
+
const handleHeaderFilterClick = useCallback(
|
|
70
|
+
(columnId) => {
|
|
71
|
+
const state = store.getState();
|
|
72
|
+
const isActive = state.columnFilters.some((f) => f.columnId === columnId);
|
|
73
|
+
if (!isActive) {
|
|
74
|
+
const cfg = filterConfigs[columnId];
|
|
75
|
+
if (cfg) state.addColumnFilter(columnId, defaultValueForFilter(cfg));
|
|
76
|
+
}
|
|
77
|
+
state.setRequestOpenFilterId(columnId);
|
|
78
|
+
},
|
|
79
|
+
[filterConfigs, store]
|
|
80
|
+
);
|
|
81
|
+
const handleHideColumn = useCallback(
|
|
82
|
+
(columnId) => store.getState().hideColumn(columnId),
|
|
83
|
+
[store]
|
|
84
|
+
);
|
|
85
|
+
const handleResetColumns = useCallback(() => {
|
|
86
|
+
store.getState().resetColumns();
|
|
87
|
+
setWidths(() => {
|
|
88
|
+
const w = {};
|
|
89
|
+
for (const [id, m] of Object.entries(columnMeta)) w[id] = m.minW;
|
|
90
|
+
return w;
|
|
91
|
+
});
|
|
92
|
+
}, [store, setWidths, columnMeta]);
|
|
93
|
+
const handleRemoveSort = useCallback(() => store.getState().removeSort(), [store]);
|
|
94
|
+
const handleRemoveCustomFilter = useCallback(() => {
|
|
95
|
+
store.getState().goCustom();
|
|
96
|
+
store.getState().setPage(1);
|
|
97
|
+
}, [store]);
|
|
98
|
+
const handleClearAll = useCallback(
|
|
99
|
+
() => store.getState().clearAll(defaultViewKey),
|
|
100
|
+
[defaultViewKey, store]
|
|
101
|
+
);
|
|
102
|
+
const handleRefresh = useCallback(() => store.getState().startRefresh(), [store]);
|
|
103
|
+
const handleToggleRowSelection = useCallback(
|
|
104
|
+
(id) => store.getState().toggleRowSelection(id),
|
|
105
|
+
[store]
|
|
106
|
+
);
|
|
107
|
+
const handleToggleHeaderCheck = useCallback(() => {
|
|
108
|
+
const ids = paginatedRows.map(getRowId);
|
|
109
|
+
store.getState().toggleHeaderCheck(ids, allOnPageSelected);
|
|
110
|
+
}, [paginatedRows, allOnPageSelected, getRowId, store]);
|
|
111
|
+
const handleExpandRow = useCallback(
|
|
112
|
+
(row, e) => {
|
|
113
|
+
e.stopPropagation();
|
|
114
|
+
const id = getRowId(row);
|
|
115
|
+
store.getState().toggleSelectedRowId(id);
|
|
116
|
+
},
|
|
117
|
+
[getRowId, store]
|
|
118
|
+
);
|
|
119
|
+
const handleExpandGap = useCallback(
|
|
120
|
+
(hiddenIds) => store.getState().showColumns(hiddenIds),
|
|
121
|
+
[store]
|
|
122
|
+
);
|
|
123
|
+
const handlePageChange = useCallback((p) => store.getState().setPage(p), [store]);
|
|
124
|
+
const handleRowsPerPageChange = useCallback(
|
|
125
|
+
(value) => store.getState().setRowsPerPage(value),
|
|
126
|
+
[store]
|
|
127
|
+
);
|
|
128
|
+
const setSearchInput = useCallback((v) => store.getState().setSearchInput(v), [store]);
|
|
129
|
+
const setColumnVisibility = useCallback(
|
|
130
|
+
(next) => store.getState().setColumnVisibility(next),
|
|
131
|
+
[store]
|
|
132
|
+
);
|
|
133
|
+
const setDensity = useCallback(
|
|
134
|
+
(v) => store.getState().setDensity(v),
|
|
135
|
+
[store]
|
|
136
|
+
);
|
|
137
|
+
const setSelectedIds = useCallback(
|
|
138
|
+
(next) => store.getState().setSelectedIds(next),
|
|
139
|
+
[store]
|
|
140
|
+
);
|
|
141
|
+
const setColumnsMenuOpen = useCallback(
|
|
142
|
+
(v) => store.getState().setColumnsMenuOpen(v),
|
|
143
|
+
[store]
|
|
144
|
+
);
|
|
145
|
+
const setSelectedRowId = useCallback(
|
|
146
|
+
(v) => store.getState().setSelectedRowId(v),
|
|
147
|
+
[store]
|
|
148
|
+
);
|
|
149
|
+
return {
|
|
150
|
+
handleSearchSubmit,
|
|
151
|
+
handleHeaderSort,
|
|
152
|
+
handleViewChange,
|
|
153
|
+
handleAddColumnFilter,
|
|
154
|
+
handleOpenExistingFilter,
|
|
155
|
+
handleRequestOpenHandled,
|
|
156
|
+
handleRemoveColumnFilter,
|
|
157
|
+
handleChangeColumnFilter,
|
|
158
|
+
handleSetColumnFilter,
|
|
159
|
+
handleHeaderFilterClick,
|
|
160
|
+
handleHideColumn,
|
|
161
|
+
handleResetColumns,
|
|
162
|
+
handleRemoveSort,
|
|
163
|
+
handleRemoveCustomFilter,
|
|
164
|
+
handleClearAll,
|
|
165
|
+
handleRefresh,
|
|
166
|
+
handleToggleRowSelection,
|
|
167
|
+
handleToggleHeaderCheck,
|
|
168
|
+
handleExpandRow,
|
|
169
|
+
handleExpandGap,
|
|
170
|
+
handlePageChange,
|
|
171
|
+
handleRowsPerPageChange,
|
|
172
|
+
setSearchInput,
|
|
173
|
+
setColumnVisibility,
|
|
174
|
+
setDensity,
|
|
175
|
+
setSelectedIds,
|
|
176
|
+
setColumnsMenuOpen,
|
|
177
|
+
setSelectedRowId
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
export {
|
|
181
|
+
useOrchestrationActions
|
|
182
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useState, useEffect, useMemo } from "react";
|
|
2
|
+
const EMPTY_ID_SET = /* @__PURE__ */ new Set();
|
|
3
|
+
function useResponsiveColumns(columnPriorities, wideBreakpoint, compactBreakpoint) {
|
|
4
|
+
const hasPriorities = Boolean(columnPriorities && Object.keys(columnPriorities).length > 0);
|
|
5
|
+
const [isWide, setIsWide] = useState(true);
|
|
6
|
+
const [isCompact, setIsCompact] = useState(false);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (!hasPriorities || typeof window === "undefined") return;
|
|
9
|
+
const mql = window.matchMedia(`(min-width: ${wideBreakpoint}px)`);
|
|
10
|
+
const onChange = (e) => setIsWide(e.matches);
|
|
11
|
+
setIsWide(mql.matches);
|
|
12
|
+
mql.addEventListener("change", onChange);
|
|
13
|
+
return () => mql.removeEventListener("change", onChange);
|
|
14
|
+
}, [hasPriorities, wideBreakpoint]);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (typeof window === "undefined") return;
|
|
17
|
+
const mql = window.matchMedia(`(max-width: ${compactBreakpoint - 1}px)`);
|
|
18
|
+
const onChange = (e) => setIsCompact(e.matches);
|
|
19
|
+
setIsCompact(mql.matches);
|
|
20
|
+
mql.addEventListener("change", onChange);
|
|
21
|
+
return () => mql.removeEventListener("change", onChange);
|
|
22
|
+
}, [compactBreakpoint]);
|
|
23
|
+
const responsiveHiddenColumnIds = useMemo(() => {
|
|
24
|
+
if (isWide || !columnPriorities) return EMPTY_ID_SET;
|
|
25
|
+
const hidden = /* @__PURE__ */ new Set();
|
|
26
|
+
for (const [id, priority] of Object.entries(columnPriorities)) {
|
|
27
|
+
if (priority === 3) hidden.add(id);
|
|
28
|
+
}
|
|
29
|
+
return hidden;
|
|
30
|
+
}, [isWide, columnPriorities]);
|
|
31
|
+
return { responsiveHiddenColumnIds, isCompact };
|
|
32
|
+
}
|
|
33
|
+
export {
|
|
34
|
+
useResponsiveColumns
|
|
35
|
+
};
|