@undevy-org/tablewright 0.1.0 → 0.2.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/components/data-table/AppliedStateChips.js +1 -10
- package/dist/es/components/data-table/ColumnFilterPopover.js +35 -41
- package/dist/es/components/data-table/CompoundFilterPopover.js +19 -29
- package/dist/es/components/data-table/FilterFormPanel.js +23 -28
- package/dist/es/components/data-table/filter-values.js +36 -0
- package/dist/es/components/data-table/managed-filters.js +1 -9
- package/dist/es/hooks/useTableOrchestration.js +24 -24
- package/dist/es/index.js +6 -0
- package/dist/tablewright.cjs +132 -141
- package/dist/types/components/data-table/filter-types.d.ts +9 -3
- package/dist/types/components/data-table/filter-values.d.ts +16 -0
- package/dist/types/index.d.ts +2 -1
- package/package.json +15 -16
|
@@ -2,6 +2,7 @@ import { jsxs, jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { X } from "lucide-react";
|
|
3
3
|
import { Button } from "../ui/button.js";
|
|
4
4
|
import { FilterChip } from "./FilterChip.js";
|
|
5
|
+
import { isFilterValueEmpty } from "./filter-values.js";
|
|
5
6
|
import { ColumnFilterPopover } from "./ColumnFilterPopover.js";
|
|
6
7
|
import { CompoundFilterPopover } from "./CompoundFilterPopover.js";
|
|
7
8
|
const DEFAULT_LABELS = {
|
|
@@ -9,16 +10,6 @@ const DEFAULT_LABELS = {
|
|
|
9
10
|
columns: "Columns",
|
|
10
11
|
hiddenCount: (n) => `${n} hidden`
|
|
11
12
|
};
|
|
12
|
-
function isFilterValueEmpty(value) {
|
|
13
|
-
if (Array.isArray(value)) return value.length === 0;
|
|
14
|
-
if (typeof value === "string") return value.length === 0;
|
|
15
|
-
if (typeof value === "object" && value !== null) {
|
|
16
|
-
const keys = Object.keys(value);
|
|
17
|
-
if (keys.length === 0) return true;
|
|
18
|
-
return keys.every((k) => isFilterValueEmpty(value[k]));
|
|
19
|
-
}
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
13
|
function AppliedStateChips({
|
|
23
14
|
filters,
|
|
24
15
|
configs,
|
|
@@ -5,6 +5,7 @@ import { Checkbox } from "../ui/checkbox.js";
|
|
|
5
5
|
import { Popover, PopoverTrigger, PopoverContent } from "../ui/popover.js";
|
|
6
6
|
import { FilterChip } from "./FilterChip.js";
|
|
7
7
|
import { FilterPopoverFooter } from "./FilterPopoverFooter.js";
|
|
8
|
+
import { getStringArray, getNumberRange, getDateRange, isFilterValueEmpty } from "./filter-values.js";
|
|
8
9
|
const DEFAULT_LABELS = {
|
|
9
10
|
searchPlaceholder: "Search...",
|
|
10
11
|
addValuePlaceholder: "Add value...",
|
|
@@ -26,14 +27,18 @@ function formatChipDate(iso) {
|
|
|
26
27
|
return d.toLocaleDateString(void 0, { day: "2-digit", month: "short" });
|
|
27
28
|
}
|
|
28
29
|
function isEmptyValue(value, type) {
|
|
29
|
-
if (type === "text") return
|
|
30
|
-
if (type === "
|
|
31
|
-
if (type === "
|
|
32
|
-
return typeof value !== "object" || Array.isArray(value) || !value.from && !value.to;
|
|
33
|
-
if (type === "number-range")
|
|
34
|
-
return typeof value !== "object" || Array.isArray(value) || value.from === void 0 && value.to === void 0;
|
|
30
|
+
if (type === "text" || type === "enum") return isFilterValueEmpty(getStringArray(value));
|
|
31
|
+
if (type === "date") return isFilterValueEmpty(getDateRange(value));
|
|
32
|
+
if (type === "number-range") return isFilterValueEmpty(getNumberRange(value));
|
|
35
33
|
return true;
|
|
36
34
|
}
|
|
35
|
+
function dateDraft(value) {
|
|
36
|
+
const { from = "", to = "" } = getDateRange(value);
|
|
37
|
+
return { from, to };
|
|
38
|
+
}
|
|
39
|
+
function numberDraft(bound) {
|
|
40
|
+
return bound !== void 0 ? String(bound) : "";
|
|
41
|
+
}
|
|
37
42
|
function ColumnFilterPopover({
|
|
38
43
|
config,
|
|
39
44
|
filter,
|
|
@@ -64,47 +69,36 @@ function ColumnFilterPopover({
|
|
|
64
69
|
return () => clearTimeout(id);
|
|
65
70
|
}, [requestOpen]);
|
|
66
71
|
const [draftTags, setDraftTags] = useState(
|
|
67
|
-
|
|
72
|
+
config.type === "text" ? getStringArray(filter.value) : []
|
|
68
73
|
);
|
|
69
74
|
const [draftInput, setDraftInput] = useState("");
|
|
70
|
-
const [draftEnum, setDraftEnum] = useState(
|
|
71
|
-
|
|
72
|
-
);
|
|
73
|
-
const [draftDate, setDraftDate] = useState(
|
|
74
|
-
typeof filter.value === "object" && !Array.isArray(filter.value) ? {
|
|
75
|
-
from: String(filter.value.from ?? ""),
|
|
76
|
-
to: String(filter.value.to ?? "")
|
|
77
|
-
} : { from: "", to: "" }
|
|
78
|
-
);
|
|
75
|
+
const [draftEnum, setDraftEnum] = useState(getStringArray(filter.value));
|
|
76
|
+
const [draftDate, setDraftDate] = useState(dateDraft(filter.value));
|
|
79
77
|
const [draftNumberFrom, setDraftNumberFrom] = useState(
|
|
80
|
-
|
|
81
|
-
);
|
|
82
|
-
const [draftNumberTo, setDraftNumberTo] = useState(
|
|
83
|
-
typeof filter.value === "object" && !Array.isArray(filter.value) && typeof filter.value.to === "number" ? String(filter.value.to) : ""
|
|
78
|
+
numberDraft(getNumberRange(filter.value).from)
|
|
84
79
|
);
|
|
80
|
+
const [draftNumberTo, setDraftNumberTo] = useState(numberDraft(getNumberRange(filter.value).to));
|
|
81
|
+
const resetDrafts = (value) => {
|
|
82
|
+
setDraftTags(config.type === "text" ? getStringArray(value) : []);
|
|
83
|
+
setDraftInput("");
|
|
84
|
+
setDraftEnum(getStringArray(value));
|
|
85
|
+
setDraftDate(dateDraft(value));
|
|
86
|
+
const numVal = getNumberRange(value);
|
|
87
|
+
setDraftNumberFrom(numberDraft(numVal.from));
|
|
88
|
+
setDraftNumberTo(numberDraft(numVal.to));
|
|
89
|
+
};
|
|
90
|
+
const [draftSource, setDraftSource] = useState(filter.value);
|
|
91
|
+
if (!isOpen && draftSource !== filter.value) {
|
|
92
|
+
setDraftSource(filter.value);
|
|
93
|
+
resetDrafts(filter.value);
|
|
94
|
+
}
|
|
85
95
|
const handleOpenChange = (nextOpen) => {
|
|
86
96
|
if (!nextOpen) {
|
|
87
97
|
if (isEmptyValue(filter.value, config.type)) {
|
|
88
98
|
onRemove();
|
|
89
99
|
return;
|
|
90
100
|
}
|
|
91
|
-
|
|
92
|
-
setDraftInput("");
|
|
93
|
-
setDraftEnum(Array.isArray(filter.value) ? filter.value : []);
|
|
94
|
-
setDraftDate(
|
|
95
|
-
typeof filter.value === "object" && !Array.isArray(filter.value) ? {
|
|
96
|
-
from: String(filter.value.from ?? ""),
|
|
97
|
-
to: String(filter.value.to ?? "")
|
|
98
|
-
} : { from: "", to: "" }
|
|
99
|
-
);
|
|
100
|
-
if (typeof filter.value === "object" && !Array.isArray(filter.value)) {
|
|
101
|
-
const numVal = filter.value;
|
|
102
|
-
setDraftNumberFrom(typeof numVal.from === "number" ? String(numVal.from) : "");
|
|
103
|
-
setDraftNumberTo(typeof numVal.to === "number" ? String(numVal.to) : "");
|
|
104
|
-
} else {
|
|
105
|
-
setDraftNumberFrom("");
|
|
106
|
-
setDraftNumberTo("");
|
|
107
|
-
}
|
|
101
|
+
resetDrafts(filter.value);
|
|
108
102
|
}
|
|
109
103
|
setIsOpen(nextOpen);
|
|
110
104
|
};
|
|
@@ -504,14 +498,14 @@ function ColumnFilterPopover({
|
|
|
504
498
|
}
|
|
505
499
|
return DEFAULT_LABELS.selectedCount(value.length);
|
|
506
500
|
}
|
|
507
|
-
if (config.type === "date"
|
|
508
|
-
const { from, to } = value;
|
|
501
|
+
if (config.type === "date") {
|
|
502
|
+
const { from, to } = getDateRange(value);
|
|
509
503
|
if (from && to) return `${from} — ${to}`;
|
|
510
504
|
if (from) return `${DEFAULT_LABELS.chipFrom} ${from}`;
|
|
511
505
|
if (to) return `${DEFAULT_LABELS.chipTo} ${to}`;
|
|
512
506
|
}
|
|
513
|
-
if (config.type === "number-range"
|
|
514
|
-
const { from, to } = value;
|
|
507
|
+
if (config.type === "number-range") {
|
|
508
|
+
const { from, to } = getNumberRange(value);
|
|
515
509
|
if (from !== void 0 && to !== void 0) return `${from}–${to}`;
|
|
516
510
|
if (from !== void 0) return `≥${from}`;
|
|
517
511
|
if (to !== void 0) return `≤${to}`;
|
|
@@ -5,6 +5,7 @@ import { Checkbox } from "../ui/checkbox.js";
|
|
|
5
5
|
import { Popover, PopoverTrigger, PopoverContent } from "../ui/popover.js";
|
|
6
6
|
import { FilterChip } from "./FilterChip.js";
|
|
7
7
|
import { FilterPopoverFooter } from "./FilterPopoverFooter.js";
|
|
8
|
+
import { getCompoundValue, isFilterValueEmpty, getNumberRange, getStringArray } from "./filter-values.js";
|
|
8
9
|
const DEFAULT_LABELS = {
|
|
9
10
|
searchPlaceholder: "Search...",
|
|
10
11
|
addPlaceholder: "Add...",
|
|
@@ -14,25 +15,10 @@ const DEFAULT_LABELS = {
|
|
|
14
15
|
statsAllPrefix: "All:",
|
|
15
16
|
statsFilteredPrefix: "Filt:"
|
|
16
17
|
};
|
|
17
|
-
function isCompoundEmpty(value) {
|
|
18
|
-
if (typeof value !== "object" || Array.isArray(value) || value === null) return true;
|
|
19
|
-
const obj = value;
|
|
20
|
-
return Object.keys(obj).every((k) => isSubValueEmpty(obj[k]));
|
|
21
|
-
}
|
|
22
|
-
function isSubValueEmpty(value) {
|
|
23
|
-
if (value === void 0 || value === null) return true;
|
|
24
|
-
if (Array.isArray(value)) return value.length === 0;
|
|
25
|
-
if (typeof value === "string") return value.length === 0;
|
|
26
|
-
if (typeof value === "object") {
|
|
27
|
-
const obj = value;
|
|
28
|
-
return obj.from === void 0 && obj.to === void 0;
|
|
29
|
-
}
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
18
|
function initDraft(sub, compound) {
|
|
33
19
|
const val = compound[sub.key];
|
|
34
20
|
if (sub.type === "number-range") {
|
|
35
|
-
const range =
|
|
21
|
+
const range = getNumberRange(val);
|
|
36
22
|
return {
|
|
37
23
|
type: "number-range",
|
|
38
24
|
from: range.from !== void 0 ? String(range.from) : "",
|
|
@@ -40,11 +26,11 @@ function initDraft(sub, compound) {
|
|
|
40
26
|
};
|
|
41
27
|
}
|
|
42
28
|
if (sub.type === "enum") {
|
|
43
|
-
return { type: "enum", selected:
|
|
29
|
+
return { type: "enum", selected: getStringArray(val) };
|
|
44
30
|
}
|
|
45
31
|
return {
|
|
46
32
|
type: "text",
|
|
47
|
-
tags:
|
|
33
|
+
tags: getStringArray(val),
|
|
48
34
|
input: ""
|
|
49
35
|
};
|
|
50
36
|
}
|
|
@@ -63,9 +49,9 @@ function draftToValue(draft) {
|
|
|
63
49
|
return all.length > 0 ? all : void 0;
|
|
64
50
|
}
|
|
65
51
|
function formatSubDisplay(sub, val) {
|
|
66
|
-
if (
|
|
67
|
-
if (sub.type === "number-range"
|
|
68
|
-
const { from, to } = val;
|
|
52
|
+
if (isFilterValueEmpty(val)) return null;
|
|
53
|
+
if (sub.type === "number-range") {
|
|
54
|
+
const { from, to } = getNumberRange(val);
|
|
69
55
|
const label = sub.label.replace(/\s+/g, " ").split(" ").pop() ?? sub.label;
|
|
70
56
|
if (from !== void 0 && to !== void 0) return `${label}: ${from}–${to}`;
|
|
71
57
|
if (from !== void 0) return `${label}: ≥${from}`;
|
|
@@ -99,17 +85,23 @@ function CompoundFilterPopover({
|
|
|
99
85
|
onRequestOpenHandled
|
|
100
86
|
}) {
|
|
101
87
|
const subFilters = config.subFilters ?? [];
|
|
102
|
-
const compound =
|
|
88
|
+
const compound = getCompoundValue(filter.value);
|
|
103
89
|
const [isOpen, setIsOpen] = useState(false);
|
|
104
90
|
const onRequestOpenHandledRef = useRef(onRequestOpenHandled);
|
|
105
91
|
useEffect(() => {
|
|
106
92
|
onRequestOpenHandledRef.current = onRequestOpenHandled;
|
|
107
93
|
});
|
|
108
|
-
const
|
|
94
|
+
const initDrafts = () => {
|
|
109
95
|
const d = {};
|
|
110
96
|
for (const sub of subFilters) d[sub.key] = initDraft(sub, compound);
|
|
111
97
|
return d;
|
|
112
|
-
}
|
|
98
|
+
};
|
|
99
|
+
const [drafts, setDrafts] = useState(initDrafts);
|
|
100
|
+
const [draftSource, setDraftSource] = useState(filter.value);
|
|
101
|
+
if (!isOpen && draftSource !== filter.value) {
|
|
102
|
+
setDraftSource(filter.value);
|
|
103
|
+
setDrafts(initDrafts());
|
|
104
|
+
}
|
|
113
105
|
useEffect(() => {
|
|
114
106
|
if (!initialOpen) return;
|
|
115
107
|
const id = setTimeout(() => setIsOpen(true), 0);
|
|
@@ -125,13 +117,11 @@ function CompoundFilterPopover({
|
|
|
125
117
|
}, [requestOpen]);
|
|
126
118
|
const handleOpenChange = (nextOpen) => {
|
|
127
119
|
if (!nextOpen) {
|
|
128
|
-
if (
|
|
120
|
+
if (isFilterValueEmpty(compound)) {
|
|
129
121
|
onRemove();
|
|
130
122
|
return;
|
|
131
123
|
}
|
|
132
|
-
|
|
133
|
-
for (const sub of subFilters) d[sub.key] = initDraft(sub, compound);
|
|
134
|
-
setDrafts(d);
|
|
124
|
+
setDrafts(initDrafts());
|
|
135
125
|
}
|
|
136
126
|
setIsOpen(nextOpen);
|
|
137
127
|
};
|
|
@@ -166,7 +156,7 @@ function CompoundFilterPopover({
|
|
|
166
156
|
});
|
|
167
157
|
};
|
|
168
158
|
const getDisplayValue = () => {
|
|
169
|
-
if (
|
|
159
|
+
if (isFilterValueEmpty(compound)) return void 0;
|
|
170
160
|
const parts = [];
|
|
171
161
|
for (const sub of subFilters) {
|
|
172
162
|
const display = formatSubDisplay(sub, compound[sub.key]);
|
|
@@ -6,16 +6,8 @@ import { Button } from "../ui/button.js";
|
|
|
6
6
|
import { ButtonFooter } from "../ui/button-footer.js";
|
|
7
7
|
import { Input } from "../ui/input.js";
|
|
8
8
|
import { Combobox } from "../ui/combobox.js";
|
|
9
|
+
import { isFilterValueEmpty, getDateRange, getNumberRange, getCompoundValue } from "./filter-values.js";
|
|
9
10
|
import { hasActiveManagedFilters } from "./managed-filters.js";
|
|
10
|
-
function isValueEmpty(v) {
|
|
11
|
-
if (v === void 0) return true;
|
|
12
|
-
if (Array.isArray(v)) return v.length === 0 || v.every((x) => x === "" || x == null);
|
|
13
|
-
if (typeof v === "string") return v.length === 0;
|
|
14
|
-
if (typeof v === "object" && v !== null) {
|
|
15
|
-
return Object.values(v).every((x) => isValueEmpty(x));
|
|
16
|
-
}
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
11
|
function deriveInitialState(fields, filters) {
|
|
20
12
|
const byId = new Map(filters.map((f) => [f.columnId, f.value]));
|
|
21
13
|
const next = {};
|
|
@@ -71,7 +63,7 @@ function FilterFormPanel({
|
|
|
71
63
|
const changes = [];
|
|
72
64
|
for (const id2 of renderableFields) {
|
|
73
65
|
const value = state[id2];
|
|
74
|
-
if (
|
|
66
|
+
if (value === void 0 || isFilterValueEmpty(value)) {
|
|
75
67
|
if (activeIds.has(id2)) changes.push({ columnId: id2, action: "remove" });
|
|
76
68
|
} else {
|
|
77
69
|
changes.push({ columnId: id2, action: "set", value });
|
|
@@ -85,7 +77,7 @@ function FilterFormPanel({
|
|
|
85
77
|
onClear(renderableFields);
|
|
86
78
|
touchedRef.current.clear();
|
|
87
79
|
}, [onClear, renderableFields]);
|
|
88
|
-
const isClearDisabled = !hasActiveManagedFilters(filters, renderableFields) && renderableFields.every((fieldId) =>
|
|
80
|
+
const isClearDisabled = !hasActiveManagedFilters(filters, renderableFields) && renderableFields.every((fieldId) => isFilterValueEmpty(state[fieldId]));
|
|
89
81
|
return /* @__PURE__ */ jsxs(
|
|
90
82
|
"form",
|
|
91
83
|
{
|
|
@@ -186,12 +178,11 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet }) {
|
|
|
186
178
|
] });
|
|
187
179
|
}
|
|
188
180
|
if (config.type === "date") {
|
|
189
|
-
const range =
|
|
181
|
+
const range = getDateRange(value);
|
|
190
182
|
const update = (key) => (e) => {
|
|
191
183
|
const v = e.target.value;
|
|
192
184
|
const next = { ...range, [key]: v || void 0 };
|
|
193
|
-
|
|
194
|
-
onChange(cleaned);
|
|
185
|
+
onChange(next.from || next.to ? next : void 0);
|
|
195
186
|
};
|
|
196
187
|
return /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col gap-1.5 [grid-column:span_2]", children: [
|
|
197
188
|
/* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium text-[var(--text-secondary)]", children: config.label }),
|
|
@@ -220,13 +211,12 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet }) {
|
|
|
220
211
|
] });
|
|
221
212
|
}
|
|
222
213
|
if (config.type === "number-range") {
|
|
223
|
-
const range =
|
|
214
|
+
const range = getNumberRange(value);
|
|
224
215
|
const update = (key) => (e) => {
|
|
225
216
|
const raw = e.target.value;
|
|
226
217
|
const parsed = raw === "" ? void 0 : Number(raw);
|
|
227
218
|
const next = { ...range, [key]: Number.isFinite(parsed) ? parsed : void 0 };
|
|
228
|
-
|
|
229
|
-
onChange(cleaned);
|
|
219
|
+
onChange(next.from !== void 0 || next.to !== void 0 ? next : void 0);
|
|
230
220
|
};
|
|
231
221
|
return /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col gap-1.5 [grid-column:span_2]", children: [
|
|
232
222
|
/* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium text-[var(--text-secondary)]", children: config.label }),
|
|
@@ -257,11 +247,11 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet }) {
|
|
|
257
247
|
] });
|
|
258
248
|
}
|
|
259
249
|
if (config.type === "compound") {
|
|
260
|
-
const compound =
|
|
261
|
-
const updateSub = (
|
|
250
|
+
const compound = getCompoundValue(value);
|
|
251
|
+
const updateSub = (subKey, next) => {
|
|
262
252
|
const rest = { ...compound };
|
|
263
|
-
delete rest[
|
|
264
|
-
const merged = next === void 0 ||
|
|
253
|
+
delete rest[subKey];
|
|
254
|
+
const merged = next === void 0 || isFilterValueEmpty(next) ? rest : { ...rest, [subKey]: next };
|
|
265
255
|
onChange(Object.keys(merged).length === 0 ? void 0 : merged);
|
|
266
256
|
};
|
|
267
257
|
const visibleSubs = (config.subFilters ?? []).filter(
|
|
@@ -277,7 +267,7 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet }) {
|
|
|
277
267
|
children: [
|
|
278
268
|
/* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium text-[var(--text-secondary)]", children: config.label }),
|
|
279
269
|
/* @__PURE__ */ jsx("div", { className: "grid min-w-0 gap-2 grid-cols-[repeat(auto-fit,minmax(180px,1fr))]", children: visibleSubs.map((sub) => {
|
|
280
|
-
const subValue = compound[sub.
|
|
270
|
+
const subValue = compound[sub.key];
|
|
281
271
|
if (sub.type === "text") {
|
|
282
272
|
const text = Array.isArray(subValue) ? subValue[0] ?? "" : "";
|
|
283
273
|
return /* @__PURE__ */ jsx(
|
|
@@ -288,7 +278,7 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet }) {
|
|
|
288
278
|
value: text,
|
|
289
279
|
onChange: (e) => {
|
|
290
280
|
const v = e.target.value;
|
|
291
|
-
updateSub(sub.
|
|
281
|
+
updateSub(sub.key, v === "" ? void 0 : [v]);
|
|
292
282
|
},
|
|
293
283
|
className: "h-9 min-w-0"
|
|
294
284
|
},
|
|
@@ -301,7 +291,7 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet }) {
|
|
|
301
291
|
Combobox,
|
|
302
292
|
{
|
|
303
293
|
value: selected || null,
|
|
304
|
-
onChange: (v) => updateSub(sub.
|
|
294
|
+
onChange: (v) => updateSub(sub.key, v ? [v] : void 0),
|
|
305
295
|
options: sub.options ?? [],
|
|
306
296
|
placeholder: sub.label,
|
|
307
297
|
triggerAriaLabel: sub.label,
|
|
@@ -311,13 +301,18 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet }) {
|
|
|
311
301
|
);
|
|
312
302
|
}
|
|
313
303
|
if (sub.type === "number-range") {
|
|
314
|
-
const range =
|
|
304
|
+
const range = getNumberRange(subValue);
|
|
315
305
|
const updateRange = (key) => (e) => {
|
|
316
306
|
const raw = e.target.value;
|
|
317
307
|
const parsed = raw === "" ? void 0 : Number(raw);
|
|
318
|
-
const next = {
|
|
319
|
-
|
|
320
|
-
|
|
308
|
+
const next = {
|
|
309
|
+
...range,
|
|
310
|
+
[key]: Number.isFinite(parsed) ? parsed : void 0
|
|
311
|
+
};
|
|
312
|
+
updateSub(
|
|
313
|
+
sub.key,
|
|
314
|
+
next.from !== void 0 || next.to !== void 0 ? next : void 0
|
|
315
|
+
);
|
|
321
316
|
};
|
|
322
317
|
return /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 gap-2", children: [
|
|
323
318
|
/* @__PURE__ */ jsx(
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
function isObjectValue(value) {
|
|
2
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
function getStringArray(value) {
|
|
5
|
+
return Array.isArray(value) ? value : [];
|
|
6
|
+
}
|
|
7
|
+
function getDateRange(value) {
|
|
8
|
+
const range = {};
|
|
9
|
+
if (!isObjectValue(value)) return range;
|
|
10
|
+
if (typeof value.from === "string") range.from = value.from;
|
|
11
|
+
if (typeof value.to === "string") range.to = value.to;
|
|
12
|
+
return range;
|
|
13
|
+
}
|
|
14
|
+
function getNumberRange(value) {
|
|
15
|
+
const range = {};
|
|
16
|
+
if (!isObjectValue(value)) return range;
|
|
17
|
+
if (typeof value.from === "number") range.from = value.from;
|
|
18
|
+
if (typeof value.to === "number") range.to = value.to;
|
|
19
|
+
return range;
|
|
20
|
+
}
|
|
21
|
+
function getCompoundValue(value) {
|
|
22
|
+
return isObjectValue(value) ? value : {};
|
|
23
|
+
}
|
|
24
|
+
function isFilterValueEmpty(value) {
|
|
25
|
+
if (value === void 0 || value === null) return true;
|
|
26
|
+
if (typeof value === "string") return value.length === 0;
|
|
27
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
28
|
+
return Object.values(value).every((x) => typeof x !== "number" && isFilterValueEmpty(x));
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
getCompoundValue,
|
|
32
|
+
getDateRange,
|
|
33
|
+
getNumberRange,
|
|
34
|
+
getStringArray,
|
|
35
|
+
isFilterValueEmpty
|
|
36
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isFilterValueEmpty } from "./filter-values.js";
|
|
1
2
|
function applyManagedChanges(orch, changes) {
|
|
2
3
|
const activeIds = new Set(orch.columnFilters.map((f) => f.columnId));
|
|
3
4
|
for (const change of changes) {
|
|
@@ -14,15 +15,6 @@ function clearManagedFilters(orch, managedColumnIds) {
|
|
|
14
15
|
if (activeIds.has(id)) orch.handleRemoveColumnFilter(id);
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
|
-
function isFilterValueEmpty(v) {
|
|
18
|
-
if (v === void 0) return true;
|
|
19
|
-
if (Array.isArray(v)) return v.length === 0 || v.every((x) => x === "" || x == null);
|
|
20
|
-
if (typeof v === "string") return v.length === 0;
|
|
21
|
-
if (typeof v === "object" && v !== null) {
|
|
22
|
-
return Object.values(v).every((x) => isFilterValueEmpty(x));
|
|
23
|
-
}
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
26
18
|
function hasActiveManagedFilters(filters, managedFieldIds) {
|
|
27
19
|
const managed = new Set(managedFieldIds);
|
|
28
20
|
return filters.some((f) => managed.has(f.columnId) && !isFilterValueEmpty(f.value));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useEffect, useMemo, useRef, useCallback } from "react";
|
|
1
|
+
import { useState, useEffect, useMemo, useRef, useCallback, useLayoutEffect } 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";
|
|
@@ -6,6 +6,7 @@ import { computeColumnGaps } from "../lib/column-gaps.js";
|
|
|
6
6
|
import { computeColumnStats } from "../lib/filter-stats.js";
|
|
7
7
|
import { createTableOrchestrationStore, defaultValueForFilter } from "./table-orchestration-store.js";
|
|
8
8
|
const EMPTY_ID_SET = /* @__PURE__ */ new Set();
|
|
9
|
+
const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
|
9
10
|
function useTableOrchestration(config) {
|
|
10
11
|
const {
|
|
11
12
|
rows,
|
|
@@ -37,14 +38,14 @@ function useTableOrchestration(config) {
|
|
|
37
38
|
const requestOpenFilterId = useStore(store, (s) => s.requestOpenFilterId);
|
|
38
39
|
const currentSort = useStore(store, (s) => s.currentSort);
|
|
39
40
|
const columnVisibility = useStore(store, (s) => s.columnVisibility);
|
|
40
|
-
const
|
|
41
|
+
const storedPage = useStore(store, (s) => s.page);
|
|
41
42
|
const rowsPerPage = useStore(store, (s) => s.rowsPerPage);
|
|
42
43
|
const density = useStore(store, (s) => s.density);
|
|
43
44
|
const isRefreshing = useStore(store, (s) => s.isRefreshing);
|
|
44
|
-
const
|
|
45
|
+
const storedSelectedIds = useStore(store, (s) => s.selectedIds);
|
|
45
46
|
const activeView = useStore(store, (s) => s.activeView);
|
|
46
47
|
const columnsMenuOpen = useStore(store, (s) => s.columnsMenuOpen);
|
|
47
|
-
const
|
|
48
|
+
const storedSelectedRowId = useStore(store, (s) => s.selectedRowId);
|
|
48
49
|
const hasPriorities = Boolean(columnPriorities && Object.keys(columnPriorities).length > 0);
|
|
49
50
|
const [isWide, setIsWide] = useState(true);
|
|
50
51
|
const [isCompact, setIsCompact] = useState(false);
|
|
@@ -72,7 +73,7 @@ function useTableOrchestration(config) {
|
|
|
72
73
|
}
|
|
73
74
|
return hidden;
|
|
74
75
|
}, [isWide, columnPriorities]);
|
|
75
|
-
const selectedIdsRef = useRef(
|
|
76
|
+
const selectedIdsRef = useRef(storedSelectedIds);
|
|
76
77
|
const headerCheckStateRef = useRef(false);
|
|
77
78
|
const sortState = useMemo(() => {
|
|
78
79
|
if (!currentSort) return {};
|
|
@@ -150,7 +151,7 @@ function useTableOrchestration(config) {
|
|
|
150
151
|
if (s) result[`${columnId}:${sub.key}`] = s;
|
|
151
152
|
}
|
|
152
153
|
} else {
|
|
153
|
-
const s = computeColumnStats(filteredRows, columnId, cfg);
|
|
154
|
+
const s = computeColumnStats(filteredRows, cfg.fieldName ?? columnId, cfg);
|
|
154
155
|
if (s) result[columnId] = s;
|
|
155
156
|
}
|
|
156
157
|
}
|
|
@@ -175,7 +176,8 @@ function useTableOrchestration(config) {
|
|
|
175
176
|
return result;
|
|
176
177
|
}, [allColumnFilterStats, filteredColumnFilterStats, filterConfigs]);
|
|
177
178
|
const totalPages = Math.max(1, Math.ceil(filteredRows.length / rowsPerPage));
|
|
178
|
-
const safePage = Math.min(
|
|
179
|
+
const safePage = Math.min(storedPage, totalPages);
|
|
180
|
+
const page = safePage;
|
|
179
181
|
const pageNumbers = useMemo(
|
|
180
182
|
() => getVisiblePageNumbers(safePage, totalPages),
|
|
181
183
|
[safePage, totalPages]
|
|
@@ -184,6 +186,21 @@ function useTableOrchestration(config) {
|
|
|
184
186
|
const start = (safePage - 1) * rowsPerPage;
|
|
185
187
|
return filteredRows.slice(start, start + rowsPerPage);
|
|
186
188
|
}, [filteredRows, rowsPerPage, safePage]);
|
|
189
|
+
const filteredIdSet = useMemo(
|
|
190
|
+
() => new Set(filteredRows.map(getRowId)),
|
|
191
|
+
[filteredRows, getRowId]
|
|
192
|
+
);
|
|
193
|
+
const selectedRowId = storedSelectedRowId && filteredIdSet.has(storedSelectedRowId) ? storedSelectedRowId : null;
|
|
194
|
+
const selectedIds = useMemo(() => {
|
|
195
|
+
const pruned = new Set([...storedSelectedIds].filter((id) => filteredIdSet.has(id)));
|
|
196
|
+
return pruned.size === storedSelectedIds.size ? storedSelectedIds : pruned;
|
|
197
|
+
}, [storedSelectedIds, filteredIdSet]);
|
|
198
|
+
useIsomorphicLayoutEffect(() => {
|
|
199
|
+
const state = store.getState();
|
|
200
|
+
if (state.page !== safePage) state.setPage(safePage);
|
|
201
|
+
if (state.selectedRowId !== selectedRowId) state.setSelectedRowId(selectedRowId);
|
|
202
|
+
if (state.selectedIds !== selectedIds) state.setSelectedIds(selectedIds);
|
|
203
|
+
}, [store, storedPage, safePage, storedSelectedRowId, selectedRowId, storedSelectedIds, selectedIds]);
|
|
187
204
|
const pageIds = useMemo(() => new Set(paginatedRows.map(getRowId)), [paginatedRows, getRowId]);
|
|
188
205
|
const selectedOnPage = useMemo(
|
|
189
206
|
() => [...selectedIds].filter((id) => pageIds.has(id)).length,
|
|
@@ -217,23 +234,6 @@ function useTableOrchestration(config) {
|
|
|
217
234
|
const activeCustomFilterChip = activePreset.customFilterChip ?? null;
|
|
218
235
|
selectedIdsRef.current = selectedIds;
|
|
219
236
|
headerCheckStateRef.current = headerCheckState;
|
|
220
|
-
if (page !== safePage) {
|
|
221
|
-
store.getState().setPage(safePage);
|
|
222
|
-
}
|
|
223
|
-
if (selectedRowId && !filteredRows.some((row) => getRowId(row) === selectedRowId)) {
|
|
224
|
-
store.getState().setSelectedRowId(null);
|
|
225
|
-
}
|
|
226
|
-
const filteredIdSet = useMemo(
|
|
227
|
-
() => new Set(filteredRows.map(getRowId)),
|
|
228
|
-
[filteredRows, getRowId]
|
|
229
|
-
);
|
|
230
|
-
const prunedSelectedIds = useMemo(() => {
|
|
231
|
-
const pruned = new Set([...selectedIds].filter((id) => filteredIdSet.has(id)));
|
|
232
|
-
return pruned.size === selectedIds.size ? selectedIds : pruned;
|
|
233
|
-
}, [selectedIds, filteredIdSet]);
|
|
234
|
-
if (prunedSelectedIds !== selectedIds) {
|
|
235
|
-
store.getState().setSelectedIds(prunedSelectedIds);
|
|
236
|
-
}
|
|
237
237
|
const handleSearchSubmit = useCallback(
|
|
238
238
|
(e) => {
|
|
239
239
|
e.preventDefault();
|
package/dist/es/index.js
CHANGED
|
@@ -61,6 +61,7 @@ import { AppliedStateChips } from "./components/data-table/AppliedStateChips.js"
|
|
|
61
61
|
import { FilterFormPanel } from "./components/data-table/FilterFormPanel.js";
|
|
62
62
|
import { FilterFormToggle, useFilterFormOpen } from "./components/data-table/FilterFormToggle.js";
|
|
63
63
|
import { applyManagedChanges, clearManagedFilters, hasActiveManagedFilters } from "./components/data-table/managed-filters.js";
|
|
64
|
+
import { getCompoundValue, getDateRange, getNumberRange, getStringArray, isFilterValueEmpty } from "./components/data-table/filter-values.js";
|
|
64
65
|
import { useTableOrchestration } from "./hooks/useTableOrchestration.js";
|
|
65
66
|
import { createTableOrchestrationStore, defaultValueForFilter } from "./hooks/table-orchestration-store.js";
|
|
66
67
|
import { useRowDragOverride } from "./hooks/useRowDragOverride.js";
|
|
@@ -183,8 +184,13 @@ export {
|
|
|
183
184
|
formatDateParts,
|
|
184
185
|
formatEnumLabel,
|
|
185
186
|
formatLiveTimestamp,
|
|
187
|
+
getCompoundValue,
|
|
188
|
+
getDateRange,
|
|
189
|
+
getNumberRange,
|
|
190
|
+
getStringArray,
|
|
186
191
|
getVisiblePageNumbers,
|
|
187
192
|
hasActiveManagedFilters,
|
|
193
|
+
isFilterValueEmpty,
|
|
188
194
|
parsePossibleTimestamp,
|
|
189
195
|
useAppShell,
|
|
190
196
|
useColumnResize,
|
package/dist/tablewright.cjs
CHANGED
|
@@ -2703,6 +2703,35 @@ function GroupedColumnToggle({
|
|
|
2703
2703
|
}
|
|
2704
2704
|
);
|
|
2705
2705
|
}
|
|
2706
|
+
function isObjectValue(value) {
|
|
2707
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2708
|
+
}
|
|
2709
|
+
function getStringArray(value) {
|
|
2710
|
+
return Array.isArray(value) ? value : [];
|
|
2711
|
+
}
|
|
2712
|
+
function getDateRange(value) {
|
|
2713
|
+
const range = {};
|
|
2714
|
+
if (!isObjectValue(value)) return range;
|
|
2715
|
+
if (typeof value.from === "string") range.from = value.from;
|
|
2716
|
+
if (typeof value.to === "string") range.to = value.to;
|
|
2717
|
+
return range;
|
|
2718
|
+
}
|
|
2719
|
+
function getNumberRange(value) {
|
|
2720
|
+
const range = {};
|
|
2721
|
+
if (!isObjectValue(value)) return range;
|
|
2722
|
+
if (typeof value.from === "number") range.from = value.from;
|
|
2723
|
+
if (typeof value.to === "number") range.to = value.to;
|
|
2724
|
+
return range;
|
|
2725
|
+
}
|
|
2726
|
+
function getCompoundValue(value) {
|
|
2727
|
+
return isObjectValue(value) ? value : {};
|
|
2728
|
+
}
|
|
2729
|
+
function isFilterValueEmpty(value) {
|
|
2730
|
+
if (value === void 0 || value === null) return true;
|
|
2731
|
+
if (typeof value === "string") return value.length === 0;
|
|
2732
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
2733
|
+
return Object.values(value).every((x) => typeof x !== "number" && isFilterValueEmpty(x));
|
|
2734
|
+
}
|
|
2706
2735
|
const DEFAULT_LABELS$2 = {
|
|
2707
2736
|
searchPlaceholder: "Search...",
|
|
2708
2737
|
addValuePlaceholder: "Add value...",
|
|
@@ -2724,14 +2753,18 @@ function formatChipDate(iso) {
|
|
|
2724
2753
|
return d.toLocaleDateString(void 0, { day: "2-digit", month: "short" });
|
|
2725
2754
|
}
|
|
2726
2755
|
function isEmptyValue(value, type) {
|
|
2727
|
-
if (type === "text") return
|
|
2728
|
-
if (type === "
|
|
2729
|
-
if (type === "
|
|
2730
|
-
return typeof value !== "object" || Array.isArray(value) || !value.from && !value.to;
|
|
2731
|
-
if (type === "number-range")
|
|
2732
|
-
return typeof value !== "object" || Array.isArray(value) || value.from === void 0 && value.to === void 0;
|
|
2756
|
+
if (type === "text" || type === "enum") return isFilterValueEmpty(getStringArray(value));
|
|
2757
|
+
if (type === "date") return isFilterValueEmpty(getDateRange(value));
|
|
2758
|
+
if (type === "number-range") return isFilterValueEmpty(getNumberRange(value));
|
|
2733
2759
|
return true;
|
|
2734
2760
|
}
|
|
2761
|
+
function dateDraft(value) {
|
|
2762
|
+
const { from = "", to = "" } = getDateRange(value);
|
|
2763
|
+
return { from, to };
|
|
2764
|
+
}
|
|
2765
|
+
function numberDraft(bound) {
|
|
2766
|
+
return bound !== void 0 ? String(bound) : "";
|
|
2767
|
+
}
|
|
2735
2768
|
function ColumnFilterPopover({
|
|
2736
2769
|
config,
|
|
2737
2770
|
filter,
|
|
@@ -2762,47 +2795,36 @@ function ColumnFilterPopover({
|
|
|
2762
2795
|
return () => clearTimeout(id);
|
|
2763
2796
|
}, [requestOpen]);
|
|
2764
2797
|
const [draftTags, setDraftTags] = React.useState(
|
|
2765
|
-
|
|
2798
|
+
config.type === "text" ? getStringArray(filter.value) : []
|
|
2766
2799
|
);
|
|
2767
2800
|
const [draftInput, setDraftInput] = React.useState("");
|
|
2768
|
-
const [draftEnum, setDraftEnum] = React.useState(
|
|
2769
|
-
|
|
2770
|
-
);
|
|
2771
|
-
const [draftDate, setDraftDate] = React.useState(
|
|
2772
|
-
typeof filter.value === "object" && !Array.isArray(filter.value) ? {
|
|
2773
|
-
from: String(filter.value.from ?? ""),
|
|
2774
|
-
to: String(filter.value.to ?? "")
|
|
2775
|
-
} : { from: "", to: "" }
|
|
2776
|
-
);
|
|
2801
|
+
const [draftEnum, setDraftEnum] = React.useState(getStringArray(filter.value));
|
|
2802
|
+
const [draftDate, setDraftDate] = React.useState(dateDraft(filter.value));
|
|
2777
2803
|
const [draftNumberFrom, setDraftNumberFrom] = React.useState(
|
|
2778
|
-
|
|
2779
|
-
);
|
|
2780
|
-
const [draftNumberTo, setDraftNumberTo] = React.useState(
|
|
2781
|
-
typeof filter.value === "object" && !Array.isArray(filter.value) && typeof filter.value.to === "number" ? String(filter.value.to) : ""
|
|
2804
|
+
numberDraft(getNumberRange(filter.value).from)
|
|
2782
2805
|
);
|
|
2806
|
+
const [draftNumberTo, setDraftNumberTo] = React.useState(numberDraft(getNumberRange(filter.value).to));
|
|
2807
|
+
const resetDrafts = (value) => {
|
|
2808
|
+
setDraftTags(config.type === "text" ? getStringArray(value) : []);
|
|
2809
|
+
setDraftInput("");
|
|
2810
|
+
setDraftEnum(getStringArray(value));
|
|
2811
|
+
setDraftDate(dateDraft(value));
|
|
2812
|
+
const numVal = getNumberRange(value);
|
|
2813
|
+
setDraftNumberFrom(numberDraft(numVal.from));
|
|
2814
|
+
setDraftNumberTo(numberDraft(numVal.to));
|
|
2815
|
+
};
|
|
2816
|
+
const [draftSource, setDraftSource] = React.useState(filter.value);
|
|
2817
|
+
if (!isOpen && draftSource !== filter.value) {
|
|
2818
|
+
setDraftSource(filter.value);
|
|
2819
|
+
resetDrafts(filter.value);
|
|
2820
|
+
}
|
|
2783
2821
|
const handleOpenChange = (nextOpen) => {
|
|
2784
2822
|
if (!nextOpen) {
|
|
2785
2823
|
if (isEmptyValue(filter.value, config.type)) {
|
|
2786
2824
|
onRemove();
|
|
2787
2825
|
return;
|
|
2788
2826
|
}
|
|
2789
|
-
|
|
2790
|
-
setDraftInput("");
|
|
2791
|
-
setDraftEnum(Array.isArray(filter.value) ? filter.value : []);
|
|
2792
|
-
setDraftDate(
|
|
2793
|
-
typeof filter.value === "object" && !Array.isArray(filter.value) ? {
|
|
2794
|
-
from: String(filter.value.from ?? ""),
|
|
2795
|
-
to: String(filter.value.to ?? "")
|
|
2796
|
-
} : { from: "", to: "" }
|
|
2797
|
-
);
|
|
2798
|
-
if (typeof filter.value === "object" && !Array.isArray(filter.value)) {
|
|
2799
|
-
const numVal = filter.value;
|
|
2800
|
-
setDraftNumberFrom(typeof numVal.from === "number" ? String(numVal.from) : "");
|
|
2801
|
-
setDraftNumberTo(typeof numVal.to === "number" ? String(numVal.to) : "");
|
|
2802
|
-
} else {
|
|
2803
|
-
setDraftNumberFrom("");
|
|
2804
|
-
setDraftNumberTo("");
|
|
2805
|
-
}
|
|
2827
|
+
resetDrafts(filter.value);
|
|
2806
2828
|
}
|
|
2807
2829
|
setIsOpen(nextOpen);
|
|
2808
2830
|
};
|
|
@@ -3202,14 +3224,14 @@ function ColumnFilterPopover({
|
|
|
3202
3224
|
}
|
|
3203
3225
|
return DEFAULT_LABELS$2.selectedCount(value.length);
|
|
3204
3226
|
}
|
|
3205
|
-
if (config.type === "date"
|
|
3206
|
-
const { from, to } = value;
|
|
3227
|
+
if (config.type === "date") {
|
|
3228
|
+
const { from, to } = getDateRange(value);
|
|
3207
3229
|
if (from && to) return `${from} — ${to}`;
|
|
3208
3230
|
if (from) return `${DEFAULT_LABELS$2.chipFrom} ${from}`;
|
|
3209
3231
|
if (to) return `${DEFAULT_LABELS$2.chipTo} ${to}`;
|
|
3210
3232
|
}
|
|
3211
|
-
if (config.type === "number-range"
|
|
3212
|
-
const { from, to } = value;
|
|
3233
|
+
if (config.type === "number-range") {
|
|
3234
|
+
const { from, to } = getNumberRange(value);
|
|
3213
3235
|
if (from !== void 0 && to !== void 0) return `${from}–${to}`;
|
|
3214
3236
|
if (from !== void 0) return `≥${from}`;
|
|
3215
3237
|
if (to !== void 0) return `≤${to}`;
|
|
@@ -3250,25 +3272,10 @@ const DEFAULT_LABELS$1 = {
|
|
|
3250
3272
|
statsAllPrefix: "All:",
|
|
3251
3273
|
statsFilteredPrefix: "Filt:"
|
|
3252
3274
|
};
|
|
3253
|
-
function isCompoundEmpty(value) {
|
|
3254
|
-
if (typeof value !== "object" || Array.isArray(value) || value === null) return true;
|
|
3255
|
-
const obj = value;
|
|
3256
|
-
return Object.keys(obj).every((k) => isSubValueEmpty(obj[k]));
|
|
3257
|
-
}
|
|
3258
|
-
function isSubValueEmpty(value) {
|
|
3259
|
-
if (value === void 0 || value === null) return true;
|
|
3260
|
-
if (Array.isArray(value)) return value.length === 0;
|
|
3261
|
-
if (typeof value === "string") return value.length === 0;
|
|
3262
|
-
if (typeof value === "object") {
|
|
3263
|
-
const obj = value;
|
|
3264
|
-
return obj.from === void 0 && obj.to === void 0;
|
|
3265
|
-
}
|
|
3266
|
-
return true;
|
|
3267
|
-
}
|
|
3268
3275
|
function initDraft(sub, compound) {
|
|
3269
3276
|
const val = compound[sub.key];
|
|
3270
3277
|
if (sub.type === "number-range") {
|
|
3271
|
-
const range =
|
|
3278
|
+
const range = getNumberRange(val);
|
|
3272
3279
|
return {
|
|
3273
3280
|
type: "number-range",
|
|
3274
3281
|
from: range.from !== void 0 ? String(range.from) : "",
|
|
@@ -3276,11 +3283,11 @@ function initDraft(sub, compound) {
|
|
|
3276
3283
|
};
|
|
3277
3284
|
}
|
|
3278
3285
|
if (sub.type === "enum") {
|
|
3279
|
-
return { type: "enum", selected:
|
|
3286
|
+
return { type: "enum", selected: getStringArray(val) };
|
|
3280
3287
|
}
|
|
3281
3288
|
return {
|
|
3282
3289
|
type: "text",
|
|
3283
|
-
tags:
|
|
3290
|
+
tags: getStringArray(val),
|
|
3284
3291
|
input: ""
|
|
3285
3292
|
};
|
|
3286
3293
|
}
|
|
@@ -3299,9 +3306,9 @@ function draftToValue(draft) {
|
|
|
3299
3306
|
return all.length > 0 ? all : void 0;
|
|
3300
3307
|
}
|
|
3301
3308
|
function formatSubDisplay(sub, val) {
|
|
3302
|
-
if (
|
|
3303
|
-
if (sub.type === "number-range"
|
|
3304
|
-
const { from, to } = val;
|
|
3309
|
+
if (isFilterValueEmpty(val)) return null;
|
|
3310
|
+
if (sub.type === "number-range") {
|
|
3311
|
+
const { from, to } = getNumberRange(val);
|
|
3305
3312
|
const label = sub.label.replace(/\s+/g, " ").split(" ").pop() ?? sub.label;
|
|
3306
3313
|
if (from !== void 0 && to !== void 0) return `${label}: ${from}–${to}`;
|
|
3307
3314
|
if (from !== void 0) return `${label}: ≥${from}`;
|
|
@@ -3335,17 +3342,23 @@ function CompoundFilterPopover({
|
|
|
3335
3342
|
onRequestOpenHandled
|
|
3336
3343
|
}) {
|
|
3337
3344
|
const subFilters = config.subFilters ?? [];
|
|
3338
|
-
const compound =
|
|
3345
|
+
const compound = getCompoundValue(filter.value);
|
|
3339
3346
|
const [isOpen, setIsOpen] = React.useState(false);
|
|
3340
3347
|
const onRequestOpenHandledRef = React.useRef(onRequestOpenHandled);
|
|
3341
3348
|
React.useEffect(() => {
|
|
3342
3349
|
onRequestOpenHandledRef.current = onRequestOpenHandled;
|
|
3343
3350
|
});
|
|
3344
|
-
const
|
|
3351
|
+
const initDrafts = () => {
|
|
3345
3352
|
const d = {};
|
|
3346
3353
|
for (const sub of subFilters) d[sub.key] = initDraft(sub, compound);
|
|
3347
3354
|
return d;
|
|
3348
|
-
}
|
|
3355
|
+
};
|
|
3356
|
+
const [drafts, setDrafts] = React.useState(initDrafts);
|
|
3357
|
+
const [draftSource, setDraftSource] = React.useState(filter.value);
|
|
3358
|
+
if (!isOpen && draftSource !== filter.value) {
|
|
3359
|
+
setDraftSource(filter.value);
|
|
3360
|
+
setDrafts(initDrafts());
|
|
3361
|
+
}
|
|
3349
3362
|
React.useEffect(() => {
|
|
3350
3363
|
if (!initialOpen) return;
|
|
3351
3364
|
const id = setTimeout(() => setIsOpen(true), 0);
|
|
@@ -3361,13 +3374,11 @@ function CompoundFilterPopover({
|
|
|
3361
3374
|
}, [requestOpen]);
|
|
3362
3375
|
const handleOpenChange = (nextOpen) => {
|
|
3363
3376
|
if (!nextOpen) {
|
|
3364
|
-
if (
|
|
3377
|
+
if (isFilterValueEmpty(compound)) {
|
|
3365
3378
|
onRemove();
|
|
3366
3379
|
return;
|
|
3367
3380
|
}
|
|
3368
|
-
|
|
3369
|
-
for (const sub of subFilters) d[sub.key] = initDraft(sub, compound);
|
|
3370
|
-
setDrafts(d);
|
|
3381
|
+
setDrafts(initDrafts());
|
|
3371
3382
|
}
|
|
3372
3383
|
setIsOpen(nextOpen);
|
|
3373
3384
|
};
|
|
@@ -3402,7 +3413,7 @@ function CompoundFilterPopover({
|
|
|
3402
3413
|
});
|
|
3403
3414
|
};
|
|
3404
3415
|
const getDisplayValue = () => {
|
|
3405
|
-
if (
|
|
3416
|
+
if (isFilterValueEmpty(compound)) return void 0;
|
|
3406
3417
|
const parts = [];
|
|
3407
3418
|
for (const sub of subFilters) {
|
|
3408
3419
|
const display = formatSubDisplay(sub, compound[sub.key]);
|
|
@@ -3744,16 +3755,6 @@ const DEFAULT_LABELS = {
|
|
|
3744
3755
|
columns: "Columns",
|
|
3745
3756
|
hiddenCount: (n) => `${n} hidden`
|
|
3746
3757
|
};
|
|
3747
|
-
function isFilterValueEmpty$1(value) {
|
|
3748
|
-
if (Array.isArray(value)) return value.length === 0;
|
|
3749
|
-
if (typeof value === "string") return value.length === 0;
|
|
3750
|
-
if (typeof value === "object" && value !== null) {
|
|
3751
|
-
const keys = Object.keys(value);
|
|
3752
|
-
if (keys.length === 0) return true;
|
|
3753
|
-
return keys.every((k) => isFilterValueEmpty$1(value[k]));
|
|
3754
|
-
}
|
|
3755
|
-
return false;
|
|
3756
|
-
}
|
|
3757
3758
|
function AppliedStateChips({
|
|
3758
3759
|
filters,
|
|
3759
3760
|
configs,
|
|
@@ -3799,7 +3800,7 @@ function AppliedStateChips({
|
|
|
3799
3800
|
filters.map((filter) => {
|
|
3800
3801
|
const filterConfig = configs[filter.columnId];
|
|
3801
3802
|
if (!filterConfig) return null;
|
|
3802
|
-
const isEmpty = isFilterValueEmpty
|
|
3803
|
+
const isEmpty = isFilterValueEmpty(filter.value);
|
|
3803
3804
|
if (filterConfig.type === "compound") {
|
|
3804
3805
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3805
3806
|
CompoundFilterPopover,
|
|
@@ -3859,28 +3860,10 @@ function clearManagedFilters(orch, managedColumnIds) {
|
|
|
3859
3860
|
if (activeIds.has(id)) orch.handleRemoveColumnFilter(id);
|
|
3860
3861
|
}
|
|
3861
3862
|
}
|
|
3862
|
-
function isFilterValueEmpty(v) {
|
|
3863
|
-
if (v === void 0) return true;
|
|
3864
|
-
if (Array.isArray(v)) return v.length === 0 || v.every((x) => x === "" || x == null);
|
|
3865
|
-
if (typeof v === "string") return v.length === 0;
|
|
3866
|
-
if (typeof v === "object" && v !== null) {
|
|
3867
|
-
return Object.values(v).every((x) => isFilterValueEmpty(x));
|
|
3868
|
-
}
|
|
3869
|
-
return false;
|
|
3870
|
-
}
|
|
3871
3863
|
function hasActiveManagedFilters(filters, managedFieldIds) {
|
|
3872
3864
|
const managed = new Set(managedFieldIds);
|
|
3873
3865
|
return filters.some((f) => managed.has(f.columnId) && !isFilterValueEmpty(f.value));
|
|
3874
3866
|
}
|
|
3875
|
-
function isValueEmpty(v) {
|
|
3876
|
-
if (v === void 0) return true;
|
|
3877
|
-
if (Array.isArray(v)) return v.length === 0 || v.every((x) => x === "" || x == null);
|
|
3878
|
-
if (typeof v === "string") return v.length === 0;
|
|
3879
|
-
if (typeof v === "object" && v !== null) {
|
|
3880
|
-
return Object.values(v).every((x) => isValueEmpty(x));
|
|
3881
|
-
}
|
|
3882
|
-
return false;
|
|
3883
|
-
}
|
|
3884
3867
|
function deriveInitialState(fields, filters) {
|
|
3885
3868
|
const byId = new Map(filters.map((f) => [f.columnId, f.value]));
|
|
3886
3869
|
const next = {};
|
|
@@ -3936,7 +3919,7 @@ function FilterFormPanel({
|
|
|
3936
3919
|
const changes = [];
|
|
3937
3920
|
for (const id2 of renderableFields) {
|
|
3938
3921
|
const value = state[id2];
|
|
3939
|
-
if (
|
|
3922
|
+
if (value === void 0 || isFilterValueEmpty(value)) {
|
|
3940
3923
|
if (activeIds.has(id2)) changes.push({ columnId: id2, action: "remove" });
|
|
3941
3924
|
} else {
|
|
3942
3925
|
changes.push({ columnId: id2, action: "set", value });
|
|
@@ -3950,7 +3933,7 @@ function FilterFormPanel({
|
|
|
3950
3933
|
onClear(renderableFields);
|
|
3951
3934
|
touchedRef.current.clear();
|
|
3952
3935
|
}, [onClear, renderableFields]);
|
|
3953
|
-
const isClearDisabled = !hasActiveManagedFilters(filters, renderableFields) && renderableFields.every((fieldId) =>
|
|
3936
|
+
const isClearDisabled = !hasActiveManagedFilters(filters, renderableFields) && renderableFields.every((fieldId) => isFilterValueEmpty(state[fieldId]));
|
|
3954
3937
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3955
3938
|
"form",
|
|
3956
3939
|
{
|
|
@@ -4051,12 +4034,11 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet }) {
|
|
|
4051
4034
|
] });
|
|
4052
4035
|
}
|
|
4053
4036
|
if (config.type === "date") {
|
|
4054
|
-
const range =
|
|
4037
|
+
const range = getDateRange(value);
|
|
4055
4038
|
const update = (key) => (e) => {
|
|
4056
4039
|
const v = e.target.value;
|
|
4057
4040
|
const next = { ...range, [key]: v || void 0 };
|
|
4058
|
-
|
|
4059
|
-
onChange(cleaned);
|
|
4041
|
+
onChange(next.from || next.to ? next : void 0);
|
|
4060
4042
|
};
|
|
4061
4043
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-col gap-1.5 [grid-column:span_2]", children: [
|
|
4062
4044
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] font-medium text-[var(--text-secondary)]", children: config.label }),
|
|
@@ -4085,13 +4067,12 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet }) {
|
|
|
4085
4067
|
] });
|
|
4086
4068
|
}
|
|
4087
4069
|
if (config.type === "number-range") {
|
|
4088
|
-
const range =
|
|
4070
|
+
const range = getNumberRange(value);
|
|
4089
4071
|
const update = (key) => (e) => {
|
|
4090
4072
|
const raw = e.target.value;
|
|
4091
4073
|
const parsed = raw === "" ? void 0 : Number(raw);
|
|
4092
4074
|
const next = { ...range, [key]: Number.isFinite(parsed) ? parsed : void 0 };
|
|
4093
|
-
|
|
4094
|
-
onChange(cleaned);
|
|
4075
|
+
onChange(next.from !== void 0 || next.to !== void 0 ? next : void 0);
|
|
4095
4076
|
};
|
|
4096
4077
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-col gap-1.5 [grid-column:span_2]", children: [
|
|
4097
4078
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] font-medium text-[var(--text-secondary)]", children: config.label }),
|
|
@@ -4122,11 +4103,11 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet }) {
|
|
|
4122
4103
|
] });
|
|
4123
4104
|
}
|
|
4124
4105
|
if (config.type === "compound") {
|
|
4125
|
-
const compound =
|
|
4126
|
-
const updateSub = (
|
|
4106
|
+
const compound = getCompoundValue(value);
|
|
4107
|
+
const updateSub = (subKey, next) => {
|
|
4127
4108
|
const rest = { ...compound };
|
|
4128
|
-
delete rest[
|
|
4129
|
-
const merged = next === void 0 ||
|
|
4109
|
+
delete rest[subKey];
|
|
4110
|
+
const merged = next === void 0 || isFilterValueEmpty(next) ? rest : { ...rest, [subKey]: next };
|
|
4130
4111
|
onChange(Object.keys(merged).length === 0 ? void 0 : merged);
|
|
4131
4112
|
};
|
|
4132
4113
|
const visibleSubs = (config.subFilters ?? []).filter(
|
|
@@ -4142,7 +4123,7 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet }) {
|
|
|
4142
4123
|
children: [
|
|
4143
4124
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] font-medium text-[var(--text-secondary)]", children: config.label }),
|
|
4144
4125
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid min-w-0 gap-2 grid-cols-[repeat(auto-fit,minmax(180px,1fr))]", children: visibleSubs.map((sub) => {
|
|
4145
|
-
const subValue = compound[sub.
|
|
4126
|
+
const subValue = compound[sub.key];
|
|
4146
4127
|
if (sub.type === "text") {
|
|
4147
4128
|
const text = Array.isArray(subValue) ? subValue[0] ?? "" : "";
|
|
4148
4129
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4153,7 +4134,7 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet }) {
|
|
|
4153
4134
|
value: text,
|
|
4154
4135
|
onChange: (e) => {
|
|
4155
4136
|
const v = e.target.value;
|
|
4156
|
-
updateSub(sub.
|
|
4137
|
+
updateSub(sub.key, v === "" ? void 0 : [v]);
|
|
4157
4138
|
},
|
|
4158
4139
|
className: "h-9 min-w-0"
|
|
4159
4140
|
},
|
|
@@ -4166,7 +4147,7 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet }) {
|
|
|
4166
4147
|
Combobox,
|
|
4167
4148
|
{
|
|
4168
4149
|
value: selected || null,
|
|
4169
|
-
onChange: (v) => updateSub(sub.
|
|
4150
|
+
onChange: (v) => updateSub(sub.key, v ? [v] : void 0),
|
|
4170
4151
|
options: sub.options ?? [],
|
|
4171
4152
|
placeholder: sub.label,
|
|
4172
4153
|
triggerAriaLabel: sub.label,
|
|
@@ -4176,13 +4157,18 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet }) {
|
|
|
4176
4157
|
);
|
|
4177
4158
|
}
|
|
4178
4159
|
if (sub.type === "number-range") {
|
|
4179
|
-
const range =
|
|
4160
|
+
const range = getNumberRange(subValue);
|
|
4180
4161
|
const updateRange = (key) => (e) => {
|
|
4181
4162
|
const raw = e.target.value;
|
|
4182
4163
|
const parsed = raw === "" ? void 0 : Number(raw);
|
|
4183
|
-
const next = {
|
|
4184
|
-
|
|
4185
|
-
|
|
4164
|
+
const next = {
|
|
4165
|
+
...range,
|
|
4166
|
+
[key]: Number.isFinite(parsed) ? parsed : void 0
|
|
4167
|
+
};
|
|
4168
|
+
updateSub(
|
|
4169
|
+
sub.key,
|
|
4170
|
+
next.from !== void 0 || next.to !== void 0 ? next : void 0
|
|
4171
|
+
);
|
|
4186
4172
|
};
|
|
4187
4173
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 gap-2", children: [
|
|
4188
4174
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4530,6 +4516,7 @@ function createTableOrchestrationStore({
|
|
|
4530
4516
|
});
|
|
4531
4517
|
}
|
|
4532
4518
|
const EMPTY_ID_SET = /* @__PURE__ */ new Set();
|
|
4519
|
+
const useIsomorphicLayoutEffect = typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;
|
|
4533
4520
|
function useTableOrchestration(config) {
|
|
4534
4521
|
const {
|
|
4535
4522
|
rows,
|
|
@@ -4561,14 +4548,14 @@ function useTableOrchestration(config) {
|
|
|
4561
4548
|
const requestOpenFilterId = zustand.useStore(store, (s) => s.requestOpenFilterId);
|
|
4562
4549
|
const currentSort = zustand.useStore(store, (s) => s.currentSort);
|
|
4563
4550
|
const columnVisibility = zustand.useStore(store, (s) => s.columnVisibility);
|
|
4564
|
-
const
|
|
4551
|
+
const storedPage = zustand.useStore(store, (s) => s.page);
|
|
4565
4552
|
const rowsPerPage = zustand.useStore(store, (s) => s.rowsPerPage);
|
|
4566
4553
|
const density = zustand.useStore(store, (s) => s.density);
|
|
4567
4554
|
const isRefreshing = zustand.useStore(store, (s) => s.isRefreshing);
|
|
4568
|
-
const
|
|
4555
|
+
const storedSelectedIds = zustand.useStore(store, (s) => s.selectedIds);
|
|
4569
4556
|
const activeView = zustand.useStore(store, (s) => s.activeView);
|
|
4570
4557
|
const columnsMenuOpen = zustand.useStore(store, (s) => s.columnsMenuOpen);
|
|
4571
|
-
const
|
|
4558
|
+
const storedSelectedRowId = zustand.useStore(store, (s) => s.selectedRowId);
|
|
4572
4559
|
const hasPriorities = Boolean(columnPriorities && Object.keys(columnPriorities).length > 0);
|
|
4573
4560
|
const [isWide, setIsWide] = React.useState(true);
|
|
4574
4561
|
const [isCompact, setIsCompact] = React.useState(false);
|
|
@@ -4596,7 +4583,7 @@ function useTableOrchestration(config) {
|
|
|
4596
4583
|
}
|
|
4597
4584
|
return hidden;
|
|
4598
4585
|
}, [isWide, columnPriorities]);
|
|
4599
|
-
const selectedIdsRef = React.useRef(
|
|
4586
|
+
const selectedIdsRef = React.useRef(storedSelectedIds);
|
|
4600
4587
|
const headerCheckStateRef = React.useRef(false);
|
|
4601
4588
|
const sortState = React.useMemo(() => {
|
|
4602
4589
|
if (!currentSort) return {};
|
|
@@ -4674,7 +4661,7 @@ function useTableOrchestration(config) {
|
|
|
4674
4661
|
if (s) result[`${columnId}:${sub.key}`] = s;
|
|
4675
4662
|
}
|
|
4676
4663
|
} else {
|
|
4677
|
-
const s = computeColumnStats(filteredRows, columnId, cfg);
|
|
4664
|
+
const s = computeColumnStats(filteredRows, cfg.fieldName ?? columnId, cfg);
|
|
4678
4665
|
if (s) result[columnId] = s;
|
|
4679
4666
|
}
|
|
4680
4667
|
}
|
|
@@ -4699,7 +4686,8 @@ function useTableOrchestration(config) {
|
|
|
4699
4686
|
return result;
|
|
4700
4687
|
}, [allColumnFilterStats, filteredColumnFilterStats, filterConfigs]);
|
|
4701
4688
|
const totalPages = Math.max(1, Math.ceil(filteredRows.length / rowsPerPage));
|
|
4702
|
-
const safePage = Math.min(
|
|
4689
|
+
const safePage = Math.min(storedPage, totalPages);
|
|
4690
|
+
const page = safePage;
|
|
4703
4691
|
const pageNumbers = React.useMemo(
|
|
4704
4692
|
() => getVisiblePageNumbers(safePage, totalPages),
|
|
4705
4693
|
[safePage, totalPages]
|
|
@@ -4708,6 +4696,21 @@ function useTableOrchestration(config) {
|
|
|
4708
4696
|
const start = (safePage - 1) * rowsPerPage;
|
|
4709
4697
|
return filteredRows.slice(start, start + rowsPerPage);
|
|
4710
4698
|
}, [filteredRows, rowsPerPage, safePage]);
|
|
4699
|
+
const filteredIdSet = React.useMemo(
|
|
4700
|
+
() => new Set(filteredRows.map(getRowId)),
|
|
4701
|
+
[filteredRows, getRowId]
|
|
4702
|
+
);
|
|
4703
|
+
const selectedRowId = storedSelectedRowId && filteredIdSet.has(storedSelectedRowId) ? storedSelectedRowId : null;
|
|
4704
|
+
const selectedIds = React.useMemo(() => {
|
|
4705
|
+
const pruned = new Set([...storedSelectedIds].filter((id) => filteredIdSet.has(id)));
|
|
4706
|
+
return pruned.size === storedSelectedIds.size ? storedSelectedIds : pruned;
|
|
4707
|
+
}, [storedSelectedIds, filteredIdSet]);
|
|
4708
|
+
useIsomorphicLayoutEffect(() => {
|
|
4709
|
+
const state = store.getState();
|
|
4710
|
+
if (state.page !== safePage) state.setPage(safePage);
|
|
4711
|
+
if (state.selectedRowId !== selectedRowId) state.setSelectedRowId(selectedRowId);
|
|
4712
|
+
if (state.selectedIds !== selectedIds) state.setSelectedIds(selectedIds);
|
|
4713
|
+
}, [store, storedPage, safePage, storedSelectedRowId, selectedRowId, storedSelectedIds, selectedIds]);
|
|
4711
4714
|
const pageIds = React.useMemo(() => new Set(paginatedRows.map(getRowId)), [paginatedRows, getRowId]);
|
|
4712
4715
|
const selectedOnPage = React.useMemo(
|
|
4713
4716
|
() => [...selectedIds].filter((id) => pageIds.has(id)).length,
|
|
@@ -4741,23 +4744,6 @@ function useTableOrchestration(config) {
|
|
|
4741
4744
|
const activeCustomFilterChip = activePreset.customFilterChip ?? null;
|
|
4742
4745
|
selectedIdsRef.current = selectedIds;
|
|
4743
4746
|
headerCheckStateRef.current = headerCheckState;
|
|
4744
|
-
if (page !== safePage) {
|
|
4745
|
-
store.getState().setPage(safePage);
|
|
4746
|
-
}
|
|
4747
|
-
if (selectedRowId && !filteredRows.some((row) => getRowId(row) === selectedRowId)) {
|
|
4748
|
-
store.getState().setSelectedRowId(null);
|
|
4749
|
-
}
|
|
4750
|
-
const filteredIdSet = React.useMemo(
|
|
4751
|
-
() => new Set(filteredRows.map(getRowId)),
|
|
4752
|
-
[filteredRows, getRowId]
|
|
4753
|
-
);
|
|
4754
|
-
const prunedSelectedIds = React.useMemo(() => {
|
|
4755
|
-
const pruned = new Set([...selectedIds].filter((id) => filteredIdSet.has(id)));
|
|
4756
|
-
return pruned.size === selectedIds.size ? selectedIds : pruned;
|
|
4757
|
-
}, [selectedIds, filteredIdSet]);
|
|
4758
|
-
if (prunedSelectedIds !== selectedIds) {
|
|
4759
|
-
store.getState().setSelectedIds(prunedSelectedIds);
|
|
4760
|
-
}
|
|
4761
4747
|
const handleSearchSubmit = React.useCallback(
|
|
4762
4748
|
(e) => {
|
|
4763
4749
|
e.preventDefault();
|
|
@@ -5458,8 +5444,13 @@ exports.displayTimeZone = displayTimeZone;
|
|
|
5458
5444
|
exports.formatDateParts = formatDateParts;
|
|
5459
5445
|
exports.formatEnumLabel = formatEnumLabel;
|
|
5460
5446
|
exports.formatLiveTimestamp = formatLiveTimestamp;
|
|
5447
|
+
exports.getCompoundValue = getCompoundValue;
|
|
5448
|
+
exports.getDateRange = getDateRange;
|
|
5449
|
+
exports.getNumberRange = getNumberRange;
|
|
5450
|
+
exports.getStringArray = getStringArray;
|
|
5461
5451
|
exports.getVisiblePageNumbers = getVisiblePageNumbers;
|
|
5462
5452
|
exports.hasActiveManagedFilters = hasActiveManagedFilters;
|
|
5453
|
+
exports.isFilterValueEmpty = isFilterValueEmpty;
|
|
5463
5454
|
exports.parsePossibleTimestamp = parsePossibleTimestamp;
|
|
5464
5455
|
exports.useAppShell = useAppShell;
|
|
5465
5456
|
exports.useColumnResize = useColumnResize;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export type ColumnFilterType = "text" | "enum" | "date" | "number-range" | "compound";
|
|
2
2
|
export interface CompoundSubFilter {
|
|
3
|
+
/** Sub-filter id: the key of its value inside the compound filter value. */
|
|
3
4
|
key: string;
|
|
4
5
|
type: "text" | "number-range" | "enum";
|
|
5
6
|
label: string;
|
|
6
7
|
badge?: string;
|
|
8
|
+
/** Row field the sub-filter reads (used for column stats). */
|
|
7
9
|
field: string;
|
|
8
10
|
options?: {
|
|
9
11
|
label: string;
|
|
@@ -20,15 +22,19 @@ export interface ColumnFilterConfig {
|
|
|
20
22
|
}[];
|
|
21
23
|
subFilters?: CompoundSubFilter[];
|
|
22
24
|
}
|
|
23
|
-
export type
|
|
25
|
+
export type DateRangeValue = {
|
|
24
26
|
from?: string;
|
|
25
27
|
to?: string;
|
|
26
|
-
}
|
|
28
|
+
};
|
|
29
|
+
export type NumberRangeValue = {
|
|
27
30
|
from?: number;
|
|
28
31
|
to?: number;
|
|
29
|
-
}
|
|
32
|
+
};
|
|
33
|
+
/** Compound filter value: sub-filter `key` → that sub-filter's value. */
|
|
34
|
+
export type CompoundFilterValue = {
|
|
30
35
|
[key: string]: ActiveFilterValue;
|
|
31
36
|
};
|
|
37
|
+
export type ActiveFilterValue = string | string[] | DateRangeValue | NumberRangeValue | CompoundFilterValue;
|
|
32
38
|
export interface ActiveFilter {
|
|
33
39
|
columnId: string;
|
|
34
40
|
value: ActiveFilterValue;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ActiveFilterValue, CompoundFilterValue, DateRangeValue, NumberRangeValue } from "./filter-types.js";
|
|
2
|
+
/** Read a text/enum filter value. Non-array values read as `[]`. */
|
|
3
|
+
export declare function getStringArray(value: ActiveFilterValue | undefined): string[];
|
|
4
|
+
/** Read a date filter value. Non-string bounds are dropped. */
|
|
5
|
+
export declare function getDateRange(value: ActiveFilterValue | undefined): DateRangeValue;
|
|
6
|
+
/** Read a number-range filter value. Non-number bounds are dropped. */
|
|
7
|
+
export declare function getNumberRange(value: ActiveFilterValue | undefined): NumberRangeValue;
|
|
8
|
+
/** Read a compound filter value (sub-filter key → value). Non-objects read as `{}`. */
|
|
9
|
+
export declare function getCompoundValue(value: ActiveFilterValue | undefined): CompoundFilterValue;
|
|
10
|
+
/**
|
|
11
|
+
* True when a filter value constrains nothing: `undefined`, `""`, `[]`, or an
|
|
12
|
+
* object whose every entry is itself empty (so `{}`, `{ from: undefined }` and
|
|
13
|
+
* `{ sub: [] }` are empty; `{ from: 0 }` is not). `[""]` is not empty — `""`
|
|
14
|
+
* can be a real enum option value.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isFilterValueEmpty(value: ActiveFilterValue | undefined): boolean;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -70,7 +70,7 @@ export { useScrollActivity } from "./hooks/use-scroll-activity.js";
|
|
|
70
70
|
export { cn } from "./lib/utils.js";
|
|
71
71
|
export { STYLE_SCOPE } from "./lib/style-scope.js";
|
|
72
72
|
export { PortalContainerContext, usePortalContainer, } from "./context/portal-container-context.js";
|
|
73
|
-
export type { ColumnFilterType, CompoundSubFilter, ColumnFilterConfig, ActiveFilterValue, ActiveFilter, FilterColumnStats, FilterStatsSlice, ColumnGroup, ColumnGap, ViewPreset, } from "./components/data-table/filter-types.js";
|
|
73
|
+
export type { ColumnFilterType, CompoundSubFilter, ColumnFilterConfig, ActiveFilterValue, DateRangeValue, NumberRangeValue, CompoundFilterValue, ActiveFilter, FilterColumnStats, FilterStatsSlice, ColumnGroup, ColumnGap, ViewPreset, } from "./components/data-table/filter-types.js";
|
|
74
74
|
export { GapIndicator, GapCell, GAP_HEADER_ATTR, GAP_CELL_ATTR, } from "./components/data-table/GapIndicator.js";
|
|
75
75
|
export { TimestampCell } from "./components/data-table/TimestampCell.js";
|
|
76
76
|
export { ExpiryTimestampCell } from "./components/data-table/ExpiryTimestampCell.js";
|
|
@@ -89,6 +89,7 @@ export { FilterFormToggle, useFilterFormOpen } from "./components/data-table/Fil
|
|
|
89
89
|
export type { FilterFormToggleProps } from "./components/data-table/FilterFormToggle.js";
|
|
90
90
|
export { applyManagedChanges, clearManagedFilters, hasActiveManagedFilters, } from "./components/data-table/managed-filters.js";
|
|
91
91
|
export type { ManagedFilterChange } from "./components/data-table/managed-filters.js";
|
|
92
|
+
export { getCompoundValue, getDateRange, getNumberRange, getStringArray, isFilterValueEmpty, } from "./components/data-table/filter-values.js";
|
|
92
93
|
export { useTableOrchestration } from "./hooks/useTableOrchestration.js";
|
|
93
94
|
export type { TableOrchestrationConfig, TableOrchestrationReturn, } from "./hooks/useTableOrchestration.js";
|
|
94
95
|
export { createTableOrchestrationStore, defaultValueForFilter, } from "./hooks/table-orchestration-store.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@undevy-org/tablewright",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "React data-table and application-shell components: filters, drawers, row controls, sortable and resizable columns, built on Radix UI and TanStack Table.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
"provenance": true
|
|
17
17
|
},
|
|
18
18
|
"type": "module",
|
|
19
|
-
"packageManager": "pnpm@10.28.2",
|
|
20
19
|
"sideEffects": [
|
|
21
20
|
"**/*.css"
|
|
22
21
|
],
|
|
@@ -40,19 +39,6 @@
|
|
|
40
39
|
"files": [
|
|
41
40
|
"dist"
|
|
42
41
|
],
|
|
43
|
-
"scripts": {
|
|
44
|
-
"build": "node scripts/build.mjs",
|
|
45
|
-
"build:js": "vite build --config vite.lib.config.ts",
|
|
46
|
-
"build:css": "node scripts/build-css.mjs",
|
|
47
|
-
"build:types": "tsc -p tsconfig.build.json",
|
|
48
|
-
"check:render": "node scripts/render-check/run.mjs",
|
|
49
|
-
"lint": "eslint src .storybook --max-warnings=0",
|
|
50
|
-
"typecheck": "tsc --project tsconfig.json --noEmit",
|
|
51
|
-
"storybook": "storybook dev -p 6006",
|
|
52
|
-
"build-storybook": "storybook build",
|
|
53
|
-
"changeset": "changeset",
|
|
54
|
-
"release": "changeset publish"
|
|
55
|
-
},
|
|
56
42
|
"peerDependencies": {
|
|
57
43
|
"@tanstack/react-table": "^8.21.3",
|
|
58
44
|
"@types/react": "*",
|
|
@@ -110,5 +96,18 @@
|
|
|
110
96
|
"typescript": "~5.9.3",
|
|
111
97
|
"typescript-eslint": "^8.48.0",
|
|
112
98
|
"vite": "^7.3.1"
|
|
99
|
+
},
|
|
100
|
+
"scripts": {
|
|
101
|
+
"build": "node scripts/build.mjs",
|
|
102
|
+
"build:js": "vite build --config vite.lib.config.ts",
|
|
103
|
+
"build:css": "node scripts/build-css.mjs",
|
|
104
|
+
"build:types": "tsc -p tsconfig.build.json",
|
|
105
|
+
"check:render": "node scripts/render-check/run.mjs",
|
|
106
|
+
"lint": "eslint src .storybook --max-warnings=0",
|
|
107
|
+
"typecheck": "tsc --project tsconfig.json --noEmit",
|
|
108
|
+
"storybook": "storybook dev -p 6006",
|
|
109
|
+
"build-storybook": "storybook build",
|
|
110
|
+
"changeset": "changeset",
|
|
111
|
+
"release": "changeset publish"
|
|
113
112
|
}
|
|
114
|
-
}
|
|
113
|
+
}
|