@undevy-org/tablewright 0.2.0 → 0.2.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.
@@ -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,35 @@
1
+ import { useMemo } from "react";
2
+ import { computeColumnGaps } from "../../lib/column-gaps.js";
3
+ const GAP_WIDTH = 32;
4
+ function gapColumnId(afterColumnId) {
5
+ return `__gap_after_${afterColumnId ?? "start"}`;
6
+ }
7
+ function useColumnGaps({
8
+ columnGroups,
9
+ toggleableColumnIds,
10
+ columnVisibility,
11
+ columnMeta,
12
+ widths
13
+ }) {
14
+ const columnGaps = useMemo(
15
+ () => columnGroups ? computeColumnGaps(toggleableColumnIds, columnVisibility) : [],
16
+ [columnGroups, toggleableColumnIds, columnVisibility]
17
+ );
18
+ const extendedColumnMeta = useMemo(() => {
19
+ if (columnGaps.length === 0) return columnMeta;
20
+ const meta = { ...columnMeta };
21
+ for (const gap of columnGaps) meta[gapColumnId(gap.afterColumnId)] = { minW: GAP_WIDTH };
22
+ return meta;
23
+ }, [columnGaps, columnMeta]);
24
+ const extendedWidths = useMemo(() => {
25
+ if (columnGaps.length === 0) return widths;
26
+ const merged = { ...widths };
27
+ for (const gap of columnGaps) merged[gapColumnId(gap.afterColumnId)] = GAP_WIDTH;
28
+ return merged;
29
+ }, [widths, columnGaps]);
30
+ return { columnGaps, extendedColumnMeta, extendedWidths };
31
+ }
32
+ export {
33
+ gapColumnId,
34
+ useColumnGaps
35
+ };
@@ -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
+ };