@undevy-org/tablewright 0.7.1 → 0.7.3
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/FilterFormPanel.js +123 -26
- package/dist/es/components/data-table/filter-form-panel-apply.js +114 -0
- package/dist/es/components/data-table/filter-values.js +5 -1
- package/dist/styles.css +9 -0
- package/dist/tablewright.cjs +224 -25
- package/dist/tablewright.css +9 -0
- package/dist/types/components/data-table/FilterFormPanel.d.ts +6 -0
- package/dist/types/components/data-table/filter-form-panel-apply.d.ts +27 -0
- package/dist/types/components/data-table/filter-types.d.ts +8 -0
- package/dist/types/components/data-table/filter-values.d.ts +2 -0
- package/package.json +1 -1
|
@@ -6,7 +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 {
|
|
9
|
+
import { recordFilterFieldTouch, patchFieldState, resolveFieldApplyChange, formatTextMultiSummary, singleStringDraftValue, formatEnumMultiSummary, enumComboboxSelection, mergeCompoundSubDraft } from "./filter-form-panel-apply.js";
|
|
10
|
+
import { isFilterValueEmpty, getDateRange, getNumberRange, getStringArray, getCompoundValue } from "./filter-values.js";
|
|
10
11
|
import { hasActiveManagedFilters } from "./managed-filters.js";
|
|
11
12
|
const DEFAULT_LABELS = {
|
|
12
13
|
closeFiltersAriaLabel: "Close filters",
|
|
@@ -21,7 +22,11 @@ const DEFAULT_LABELS = {
|
|
|
21
22
|
compoundSubRangeFromAriaLabel: (subLabel) => `${subLabel} — from`,
|
|
22
23
|
compoundSubRangeToAriaLabel: (subLabel) => `${subLabel} — to`,
|
|
23
24
|
compoundSubRangeFromPlaceholder: (subLabel) => `${subLabel} from`,
|
|
24
|
-
compoundSubRangeToPlaceholder: (subLabel) => `${subLabel} to
|
|
25
|
+
compoundSubRangeToPlaceholder: (subLabel) => `${subLabel} to`,
|
|
26
|
+
multiValueFormHint: "Multiple values — edit using the column filter chips or popover above the table.",
|
|
27
|
+
valuesCount: (count) => `${count} values`,
|
|
28
|
+
selectedCount: (count) => `${count} selected`,
|
|
29
|
+
clearFieldForSingleEdit: "Clear to edit one value"
|
|
25
30
|
};
|
|
26
31
|
function deriveInitialState(fields, filters) {
|
|
27
32
|
const byId = new Map(filters.map((f) => [f.columnId, f.value]));
|
|
@@ -71,24 +76,38 @@ function FilterFormPanel({
|
|
|
71
76
|
return changed ? next : prev;
|
|
72
77
|
});
|
|
73
78
|
}, [filters, renderableFields]);
|
|
74
|
-
const setField = useCallback(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
79
|
+
const setField = useCallback(
|
|
80
|
+
(id2, value, compoundSubKey) => {
|
|
81
|
+
recordFilterFieldTouch(touchedRef.current, id2, compoundSubKey);
|
|
82
|
+
setState((prev) => patchFieldState(prev, id2, value));
|
|
83
|
+
},
|
|
84
|
+
[]
|
|
85
|
+
);
|
|
78
86
|
const handleApply = useCallback(() => {
|
|
79
87
|
const activeIds = new Set(filters.map((f) => f.columnId));
|
|
88
|
+
const committedById = new Map(filters.map((f) => [f.columnId, f.value]));
|
|
89
|
+
const touched = touchedRef.current;
|
|
80
90
|
const changes = [];
|
|
81
91
|
for (const id2 of renderableFields) {
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
92
|
+
const config = filterConfigs[id2];
|
|
93
|
+
const committed = committedById.get(id2);
|
|
94
|
+
const draft = state[id2];
|
|
95
|
+
const resolution = resolveFieldApplyChange(
|
|
96
|
+
config,
|
|
97
|
+
id2,
|
|
98
|
+
committed,
|
|
99
|
+
draft,
|
|
100
|
+
touched,
|
|
101
|
+
hiddenSubSet,
|
|
102
|
+
activeIds.has(id2)
|
|
103
|
+
);
|
|
104
|
+
if (resolution.kind === "skip") continue;
|
|
105
|
+
if (resolution.kind === "remove") changes.push({ columnId: id2, action: "remove" });
|
|
106
|
+
else changes.push({ columnId: id2, action: "set", value: resolution.value });
|
|
88
107
|
}
|
|
89
108
|
onApply(changes);
|
|
90
109
|
touchedRef.current.clear();
|
|
91
|
-
}, [filters, onApply, renderableFields, state]);
|
|
110
|
+
}, [filterConfigs, filters, hiddenSubSet, onApply, renderableFields, state]);
|
|
92
111
|
const handleClear = useCallback(() => {
|
|
93
112
|
setState(deriveInitialState(renderableFields, []));
|
|
94
113
|
onClear(renderableFields);
|
|
@@ -133,7 +152,7 @@ function FilterFormPanel({
|
|
|
133
152
|
{
|
|
134
153
|
config,
|
|
135
154
|
value: state[fieldId],
|
|
136
|
-
onChange: (v) => setField(fieldId, v),
|
|
155
|
+
onChange: (v, compoundSubKey) => setField(fieldId, v, compoundSubKey),
|
|
137
156
|
fieldId,
|
|
138
157
|
hiddenSubSet,
|
|
139
158
|
labels
|
|
@@ -161,7 +180,20 @@ function FilterFormPanel({
|
|
|
161
180
|
}
|
|
162
181
|
function Field({ config, value, onChange, fieldId, hiddenSubSet, labels }) {
|
|
163
182
|
if (config.type === "text") {
|
|
164
|
-
const
|
|
183
|
+
const strings = getStringArray(value);
|
|
184
|
+
if (strings.length > 1) {
|
|
185
|
+
return /* @__PURE__ */ jsx(
|
|
186
|
+
MultiValueStringSummary,
|
|
187
|
+
{
|
|
188
|
+
label: config.label,
|
|
189
|
+
summary: formatTextMultiSummary(strings, labels),
|
|
190
|
+
hint: labels.multiValueFormHint,
|
|
191
|
+
clearLabel: labels.clearFieldForSingleEdit,
|
|
192
|
+
onClearForEdit: () => onChange(void 0)
|
|
193
|
+
}
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
const text = singleStringDraftValue(strings);
|
|
165
197
|
return /* @__PURE__ */ jsxs("label", { className: "flex min-w-0 flex-col gap-1.5", children: [
|
|
166
198
|
/* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium text-[var(--text-secondary)]", children: config.label }),
|
|
167
199
|
/* @__PURE__ */ jsx(
|
|
@@ -179,14 +211,27 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet, labels }) {
|
|
|
179
211
|
] });
|
|
180
212
|
}
|
|
181
213
|
if (config.type === "enum") {
|
|
182
|
-
const
|
|
214
|
+
const strings = getStringArray(value);
|
|
215
|
+
if (strings.length > 1) {
|
|
216
|
+
return /* @__PURE__ */ jsx(
|
|
217
|
+
MultiValueStringSummary,
|
|
218
|
+
{
|
|
219
|
+
label: config.label,
|
|
220
|
+
summary: formatEnumMultiSummary(strings, config.options, labels),
|
|
221
|
+
hint: labels.multiValueFormHint,
|
|
222
|
+
clearLabel: labels.clearFieldForSingleEdit,
|
|
223
|
+
onClearForEdit: () => onChange(void 0)
|
|
224
|
+
}
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
const comboboxValue = enumComboboxSelection(strings);
|
|
183
228
|
return /* @__PURE__ */ jsxs("label", { className: "flex min-w-0 flex-col gap-1.5", children: [
|
|
184
229
|
/* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium text-[var(--text-secondary)]", children: config.label }),
|
|
185
230
|
/* @__PURE__ */ jsx(
|
|
186
231
|
Combobox,
|
|
187
232
|
{
|
|
188
|
-
value:
|
|
189
|
-
onChange: (v) => onChange(v
|
|
233
|
+
value: comboboxValue,
|
|
234
|
+
onChange: (v) => onChange(v === null ? void 0 : [v]),
|
|
190
235
|
options: config.options ?? [],
|
|
191
236
|
placeholder: labels.anyPlaceholder,
|
|
192
237
|
triggerAriaLabel: config.label,
|
|
@@ -267,10 +312,7 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet, labels }) {
|
|
|
267
312
|
if (config.type === "compound") {
|
|
268
313
|
const compound = getCompoundValue(value);
|
|
269
314
|
const updateSub = (subKey, next) => {
|
|
270
|
-
|
|
271
|
-
delete rest[subKey];
|
|
272
|
-
const merged = next === void 0 || isFilterValueEmpty(next) ? rest : { ...rest, [subKey]: next };
|
|
273
|
-
onChange(Object.keys(merged).length === 0 ? void 0 : merged);
|
|
315
|
+
onChange(mergeCompoundSubDraft(compound, subKey, next), subKey);
|
|
274
316
|
};
|
|
275
317
|
const visibleSubs = (config.subFilters ?? []).filter(
|
|
276
318
|
(sub) => !hiddenSubSet.has(`${fieldId}:${sub.key}`)
|
|
@@ -287,7 +329,22 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet, labels }) {
|
|
|
287
329
|
/* @__PURE__ */ jsx("div", { className: "grid min-w-0 gap-2 grid-cols-[repeat(auto-fit,minmax(180px,1fr))]", children: visibleSubs.map((sub) => {
|
|
288
330
|
const subValue = compound[sub.key];
|
|
289
331
|
if (sub.type === "text") {
|
|
290
|
-
const
|
|
332
|
+
const strings = getStringArray(subValue);
|
|
333
|
+
if (strings.length > 1) {
|
|
334
|
+
return /* @__PURE__ */ jsx(
|
|
335
|
+
MultiValueStringSummary,
|
|
336
|
+
{
|
|
337
|
+
label: sub.label,
|
|
338
|
+
summary: formatTextMultiSummary(strings, labels),
|
|
339
|
+
hint: labels.multiValueFormHint,
|
|
340
|
+
clearLabel: labels.clearFieldForSingleEdit,
|
|
341
|
+
onClearForEdit: () => updateSub(sub.key, void 0),
|
|
342
|
+
compact: true
|
|
343
|
+
},
|
|
344
|
+
sub.key
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
const text = singleStringDraftValue(strings);
|
|
291
348
|
return /* @__PURE__ */ jsx(
|
|
292
349
|
Input,
|
|
293
350
|
{
|
|
@@ -304,12 +361,27 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet, labels }) {
|
|
|
304
361
|
);
|
|
305
362
|
}
|
|
306
363
|
if (sub.type === "enum") {
|
|
307
|
-
const
|
|
364
|
+
const strings = getStringArray(subValue);
|
|
365
|
+
if (strings.length > 1) {
|
|
366
|
+
return /* @__PURE__ */ jsx(
|
|
367
|
+
MultiValueStringSummary,
|
|
368
|
+
{
|
|
369
|
+
label: sub.label,
|
|
370
|
+
summary: formatEnumMultiSummary(strings, sub.options, labels),
|
|
371
|
+
hint: labels.multiValueFormHint,
|
|
372
|
+
clearLabel: labels.clearFieldForSingleEdit,
|
|
373
|
+
onClearForEdit: () => updateSub(sub.key, void 0),
|
|
374
|
+
compact: true
|
|
375
|
+
},
|
|
376
|
+
sub.key
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
const comboboxValue = enumComboboxSelection(strings);
|
|
308
380
|
return /* @__PURE__ */ jsx(
|
|
309
381
|
Combobox,
|
|
310
382
|
{
|
|
311
|
-
value:
|
|
312
|
-
onChange: (v) => updateSub(sub.key, v
|
|
383
|
+
value: comboboxValue,
|
|
384
|
+
onChange: (v) => updateSub(sub.key, v === null ? void 0 : [v]),
|
|
313
385
|
options: sub.options ?? [],
|
|
314
386
|
placeholder: sub.label,
|
|
315
387
|
triggerAriaLabel: sub.label,
|
|
@@ -365,6 +437,31 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet, labels }) {
|
|
|
365
437
|
}
|
|
366
438
|
return null;
|
|
367
439
|
}
|
|
440
|
+
function MultiValueStringSummary({
|
|
441
|
+
label,
|
|
442
|
+
summary,
|
|
443
|
+
hint,
|
|
444
|
+
clearLabel,
|
|
445
|
+
onClearForEdit,
|
|
446
|
+
compact
|
|
447
|
+
}) {
|
|
448
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex min-w-0 flex-col gap-1.5", compact && "col-span-full"), children: [
|
|
449
|
+
/* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium text-[var(--text-secondary)]", children: label }),
|
|
450
|
+
/* @__PURE__ */ jsx("p", { className: "text-[12px] text-[var(--text-primary)]", children: summary }),
|
|
451
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] leading-snug text-[var(--text-secondary)]", children: hint }),
|
|
452
|
+
/* @__PURE__ */ jsx(
|
|
453
|
+
Button,
|
|
454
|
+
{
|
|
455
|
+
type: "button",
|
|
456
|
+
variant: "secondary",
|
|
457
|
+
size: "sm",
|
|
458
|
+
className: "h-9 self-start",
|
|
459
|
+
onClick: onClearForEdit,
|
|
460
|
+
children: clearLabel
|
|
461
|
+
}
|
|
462
|
+
)
|
|
463
|
+
] });
|
|
464
|
+
}
|
|
368
465
|
export {
|
|
369
466
|
FilterFormPanel
|
|
370
467
|
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { isFilterValueEmpty, isMultiStringFilterValue, getCompoundValue } from "./filter-values.js";
|
|
2
|
+
function compoundSubTouchKey(fieldId, subKey) {
|
|
3
|
+
return `${fieldId}:${subKey}`;
|
|
4
|
+
}
|
|
5
|
+
function recordFilterFieldTouch(touched, fieldId, compoundSubKey) {
|
|
6
|
+
touched.add(fieldId);
|
|
7
|
+
if (compoundSubKey) {
|
|
8
|
+
touched.add(compoundSubTouchKey(fieldId, compoundSubKey));
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function singleStringDraftValue(strings) {
|
|
12
|
+
return strings[0] ?? "";
|
|
13
|
+
}
|
|
14
|
+
function enumComboboxSelection(strings) {
|
|
15
|
+
return strings.length > 0 ? strings[0] : null;
|
|
16
|
+
}
|
|
17
|
+
function patchFieldState(prev, fieldId, value) {
|
|
18
|
+
return { ...prev, [fieldId]: value };
|
|
19
|
+
}
|
|
20
|
+
function mergeCompoundSubDraft(compound, subKey, next) {
|
|
21
|
+
const rest = { ...compound };
|
|
22
|
+
delete rest[subKey];
|
|
23
|
+
const merged = next === void 0 || isFilterValueEmpty(next) ? rest : { ...rest, [subKey]: next };
|
|
24
|
+
return Object.keys(merged).length === 0 ? void 0 : merged;
|
|
25
|
+
}
|
|
26
|
+
function formatTextMultiSummary(values, labels) {
|
|
27
|
+
if (values.length === 2) {
|
|
28
|
+
const joined = values.join(", ");
|
|
29
|
+
return joined.length > 24 ? labels.valuesCount(values.length) : joined;
|
|
30
|
+
}
|
|
31
|
+
return labels.valuesCount(values.length);
|
|
32
|
+
}
|
|
33
|
+
function formatEnumMultiSummary(values, options, labels) {
|
|
34
|
+
if (values.length === 2) {
|
|
35
|
+
const parts = values.map((v) => options?.find((o) => o.value === v)?.label ?? v);
|
|
36
|
+
const joined = parts.join(", ");
|
|
37
|
+
return joined.length > 24 ? labels.selectedCount(values.length) : joined;
|
|
38
|
+
}
|
|
39
|
+
return labels.selectedCount(values.length);
|
|
40
|
+
}
|
|
41
|
+
function shouldPreserveMultiSubOnApply(sub, subCommitted, touchKey, touched) {
|
|
42
|
+
if (sub.type !== "text" && sub.type !== "enum") return false;
|
|
43
|
+
return isMultiStringFilterValue(subCommitted) && !touched.has(touchKey);
|
|
44
|
+
}
|
|
45
|
+
function mergeCompoundValueForApply(config, fieldId, committed, draft, touched, hiddenSubSet) {
|
|
46
|
+
const committedCompound = getCompoundValue(committed);
|
|
47
|
+
const draftCompound = getCompoundValue(draft);
|
|
48
|
+
const merged = {};
|
|
49
|
+
for (const sub of config.subFilters ?? []) {
|
|
50
|
+
if (hiddenSubSet.has(compoundSubTouchKey(fieldId, sub.key))) continue;
|
|
51
|
+
const touchKey = compoundSubTouchKey(fieldId, sub.key);
|
|
52
|
+
const subCommitted = committedCompound[sub.key];
|
|
53
|
+
const subDraft = draftCompound[sub.key];
|
|
54
|
+
if (shouldPreserveMultiSubOnApply(sub, subCommitted, touchKey, touched)) {
|
|
55
|
+
if (subCommitted !== void 0 && !isFilterValueEmpty(subCommitted)) {
|
|
56
|
+
merged[sub.key] = subCommitted;
|
|
57
|
+
}
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (subDraft === void 0 || isFilterValueEmpty(subDraft)) continue;
|
|
61
|
+
merged[sub.key] = subDraft;
|
|
62
|
+
}
|
|
63
|
+
return Object.keys(merged).length === 0 ? void 0 : merged;
|
|
64
|
+
}
|
|
65
|
+
function resolveFieldApplyChange(config, fieldId, committed, draft, touched, hiddenSubSet, isActive) {
|
|
66
|
+
switch (config.type) {
|
|
67
|
+
case "text":
|
|
68
|
+
case "enum": {
|
|
69
|
+
if (isMultiStringFilterValue(committed) && !touched.has(fieldId)) {
|
|
70
|
+
return { kind: "skip" };
|
|
71
|
+
}
|
|
72
|
+
if (draft === void 0 || isFilterValueEmpty(draft)) {
|
|
73
|
+
return isActive ? { kind: "remove" } : { kind: "skip" };
|
|
74
|
+
}
|
|
75
|
+
return { kind: "set", value: draft };
|
|
76
|
+
}
|
|
77
|
+
case "date":
|
|
78
|
+
case "number-range": {
|
|
79
|
+
if (draft === void 0 || isFilterValueEmpty(draft)) {
|
|
80
|
+
return isActive ? { kind: "remove" } : { kind: "skip" };
|
|
81
|
+
}
|
|
82
|
+
return { kind: "set", value: draft };
|
|
83
|
+
}
|
|
84
|
+
case "compound": {
|
|
85
|
+
const merged = mergeCompoundValueForApply(
|
|
86
|
+
config,
|
|
87
|
+
fieldId,
|
|
88
|
+
committed,
|
|
89
|
+
draft,
|
|
90
|
+
touched,
|
|
91
|
+
hiddenSubSet
|
|
92
|
+
);
|
|
93
|
+
if (merged === void 0 || isFilterValueEmpty(merged)) {
|
|
94
|
+
return isActive ? { kind: "remove" } : { kind: "skip" };
|
|
95
|
+
}
|
|
96
|
+
return { kind: "set", value: merged };
|
|
97
|
+
}
|
|
98
|
+
default:
|
|
99
|
+
return { kind: "skip" };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
export {
|
|
103
|
+
compoundSubTouchKey,
|
|
104
|
+
enumComboboxSelection,
|
|
105
|
+
formatEnumMultiSummary,
|
|
106
|
+
formatTextMultiSummary,
|
|
107
|
+
mergeCompoundSubDraft,
|
|
108
|
+
mergeCompoundValueForApply,
|
|
109
|
+
patchFieldState,
|
|
110
|
+
recordFilterFieldTouch,
|
|
111
|
+
resolveFieldApplyChange,
|
|
112
|
+
shouldPreserveMultiSubOnApply,
|
|
113
|
+
singleStringDraftValue
|
|
114
|
+
};
|
|
@@ -4,6 +4,9 @@ function isObjectValue(value) {
|
|
|
4
4
|
function getStringArray(value) {
|
|
5
5
|
return Array.isArray(value) ? value : [];
|
|
6
6
|
}
|
|
7
|
+
function isMultiStringFilterValue(value) {
|
|
8
|
+
return getStringArray(value).length > 1;
|
|
9
|
+
}
|
|
7
10
|
function getDateRange(value) {
|
|
8
11
|
const range = {};
|
|
9
12
|
if (!isObjectValue(value)) return range;
|
|
@@ -32,5 +35,6 @@ export {
|
|
|
32
35
|
getDateRange,
|
|
33
36
|
getNumberRange,
|
|
34
37
|
getStringArray,
|
|
35
|
-
isFilterValueEmpty
|
|
38
|
+
isFilterValueEmpty,
|
|
39
|
+
isMultiStringFilterValue
|
|
36
40
|
};
|
package/dist/styles.css
CHANGED
|
@@ -469,6 +469,9 @@
|
|
|
469
469
|
.z-50 {
|
|
470
470
|
z-index: 50
|
|
471
471
|
}
|
|
472
|
+
.col-span-full {
|
|
473
|
+
grid-column: 1 / -1
|
|
474
|
+
}
|
|
472
475
|
.mx-3 {
|
|
473
476
|
margin-left: 0.75rem;
|
|
474
477
|
margin-right: 0.75rem
|
|
@@ -889,6 +892,9 @@
|
|
|
889
892
|
margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
|
|
890
893
|
margin-bottom: calc(1rem * var(--tw-space-y-reverse))
|
|
891
894
|
}
|
|
895
|
+
.self-start {
|
|
896
|
+
align-self: flex-start
|
|
897
|
+
}
|
|
892
898
|
.self-stretch {
|
|
893
899
|
align-self: stretch
|
|
894
900
|
}
|
|
@@ -1280,6 +1286,9 @@
|
|
|
1280
1286
|
.leading-none {
|
|
1281
1287
|
line-height: 1
|
|
1282
1288
|
}
|
|
1289
|
+
.leading-snug {
|
|
1290
|
+
line-height: 1.375
|
|
1291
|
+
}
|
|
1283
1292
|
.leading-tight {
|
|
1284
1293
|
line-height: 1.25
|
|
1285
1294
|
}
|
package/dist/tablewright.cjs
CHANGED
|
@@ -2825,6 +2825,9 @@ function isObjectValue(value) {
|
|
|
2825
2825
|
function getStringArray(value) {
|
|
2826
2826
|
return Array.isArray(value) ? value : [];
|
|
2827
2827
|
}
|
|
2828
|
+
function isMultiStringFilterValue(value) {
|
|
2829
|
+
return getStringArray(value).length > 1;
|
|
2830
|
+
}
|
|
2828
2831
|
function getDateRange(value) {
|
|
2829
2832
|
const range = {};
|
|
2830
2833
|
if (!isObjectValue(value)) return range;
|
|
@@ -3998,6 +4001,106 @@ function AppliedStateChips({
|
|
|
3998
4001
|
)
|
|
3999
4002
|
] });
|
|
4000
4003
|
}
|
|
4004
|
+
function compoundSubTouchKey(fieldId, subKey) {
|
|
4005
|
+
return `${fieldId}:${subKey}`;
|
|
4006
|
+
}
|
|
4007
|
+
function recordFilterFieldTouch(touched, fieldId, compoundSubKey) {
|
|
4008
|
+
touched.add(fieldId);
|
|
4009
|
+
if (compoundSubKey) {
|
|
4010
|
+
touched.add(compoundSubTouchKey(fieldId, compoundSubKey));
|
|
4011
|
+
}
|
|
4012
|
+
}
|
|
4013
|
+
function singleStringDraftValue(strings) {
|
|
4014
|
+
return strings[0] ?? "";
|
|
4015
|
+
}
|
|
4016
|
+
function enumComboboxSelection(strings) {
|
|
4017
|
+
return strings.length > 0 ? strings[0] : null;
|
|
4018
|
+
}
|
|
4019
|
+
function patchFieldState(prev, fieldId, value) {
|
|
4020
|
+
return { ...prev, [fieldId]: value };
|
|
4021
|
+
}
|
|
4022
|
+
function mergeCompoundSubDraft(compound, subKey, next) {
|
|
4023
|
+
const rest = { ...compound };
|
|
4024
|
+
delete rest[subKey];
|
|
4025
|
+
const merged = next === void 0 || isFilterValueEmpty(next) ? rest : { ...rest, [subKey]: next };
|
|
4026
|
+
return Object.keys(merged).length === 0 ? void 0 : merged;
|
|
4027
|
+
}
|
|
4028
|
+
function formatTextMultiSummary(values, labels) {
|
|
4029
|
+
if (values.length === 2) {
|
|
4030
|
+
const joined = values.join(", ");
|
|
4031
|
+
return joined.length > 24 ? labels.valuesCount(values.length) : joined;
|
|
4032
|
+
}
|
|
4033
|
+
return labels.valuesCount(values.length);
|
|
4034
|
+
}
|
|
4035
|
+
function formatEnumMultiSummary(values, options, labels) {
|
|
4036
|
+
if (values.length === 2) {
|
|
4037
|
+
const parts = values.map((v) => options?.find((o) => o.value === v)?.label ?? v);
|
|
4038
|
+
const joined = parts.join(", ");
|
|
4039
|
+
return joined.length > 24 ? labels.selectedCount(values.length) : joined;
|
|
4040
|
+
}
|
|
4041
|
+
return labels.selectedCount(values.length);
|
|
4042
|
+
}
|
|
4043
|
+
function shouldPreserveMultiSubOnApply(sub, subCommitted, touchKey, touched) {
|
|
4044
|
+
if (sub.type !== "text" && sub.type !== "enum") return false;
|
|
4045
|
+
return isMultiStringFilterValue(subCommitted) && !touched.has(touchKey);
|
|
4046
|
+
}
|
|
4047
|
+
function mergeCompoundValueForApply(config, fieldId, committed, draft, touched, hiddenSubSet) {
|
|
4048
|
+
const committedCompound = getCompoundValue(committed);
|
|
4049
|
+
const draftCompound = getCompoundValue(draft);
|
|
4050
|
+
const merged = {};
|
|
4051
|
+
for (const sub of config.subFilters ?? []) {
|
|
4052
|
+
if (hiddenSubSet.has(compoundSubTouchKey(fieldId, sub.key))) continue;
|
|
4053
|
+
const touchKey = compoundSubTouchKey(fieldId, sub.key);
|
|
4054
|
+
const subCommitted = committedCompound[sub.key];
|
|
4055
|
+
const subDraft = draftCompound[sub.key];
|
|
4056
|
+
if (shouldPreserveMultiSubOnApply(sub, subCommitted, touchKey, touched)) {
|
|
4057
|
+
if (subCommitted !== void 0 && !isFilterValueEmpty(subCommitted)) {
|
|
4058
|
+
merged[sub.key] = subCommitted;
|
|
4059
|
+
}
|
|
4060
|
+
continue;
|
|
4061
|
+
}
|
|
4062
|
+
if (subDraft === void 0 || isFilterValueEmpty(subDraft)) continue;
|
|
4063
|
+
merged[sub.key] = subDraft;
|
|
4064
|
+
}
|
|
4065
|
+
return Object.keys(merged).length === 0 ? void 0 : merged;
|
|
4066
|
+
}
|
|
4067
|
+
function resolveFieldApplyChange(config, fieldId, committed, draft, touched, hiddenSubSet, isActive) {
|
|
4068
|
+
switch (config.type) {
|
|
4069
|
+
case "text":
|
|
4070
|
+
case "enum": {
|
|
4071
|
+
if (isMultiStringFilterValue(committed) && !touched.has(fieldId)) {
|
|
4072
|
+
return { kind: "skip" };
|
|
4073
|
+
}
|
|
4074
|
+
if (draft === void 0 || isFilterValueEmpty(draft)) {
|
|
4075
|
+
return isActive ? { kind: "remove" } : { kind: "skip" };
|
|
4076
|
+
}
|
|
4077
|
+
return { kind: "set", value: draft };
|
|
4078
|
+
}
|
|
4079
|
+
case "date":
|
|
4080
|
+
case "number-range": {
|
|
4081
|
+
if (draft === void 0 || isFilterValueEmpty(draft)) {
|
|
4082
|
+
return isActive ? { kind: "remove" } : { kind: "skip" };
|
|
4083
|
+
}
|
|
4084
|
+
return { kind: "set", value: draft };
|
|
4085
|
+
}
|
|
4086
|
+
case "compound": {
|
|
4087
|
+
const merged = mergeCompoundValueForApply(
|
|
4088
|
+
config,
|
|
4089
|
+
fieldId,
|
|
4090
|
+
committed,
|
|
4091
|
+
draft,
|
|
4092
|
+
touched,
|
|
4093
|
+
hiddenSubSet
|
|
4094
|
+
);
|
|
4095
|
+
if (merged === void 0 || isFilterValueEmpty(merged)) {
|
|
4096
|
+
return isActive ? { kind: "remove" } : { kind: "skip" };
|
|
4097
|
+
}
|
|
4098
|
+
return { kind: "set", value: merged };
|
|
4099
|
+
}
|
|
4100
|
+
default:
|
|
4101
|
+
return { kind: "skip" };
|
|
4102
|
+
}
|
|
4103
|
+
}
|
|
4001
4104
|
function applyManagedChanges(orch, changes) {
|
|
4002
4105
|
const activeIds = new Set(orch.columnFilters.map((f) => f.columnId));
|
|
4003
4106
|
for (const change of changes) {
|
|
@@ -4031,7 +4134,11 @@ const DEFAULT_LABELS = {
|
|
|
4031
4134
|
compoundSubRangeFromAriaLabel: (subLabel) => `${subLabel} — from`,
|
|
4032
4135
|
compoundSubRangeToAriaLabel: (subLabel) => `${subLabel} — to`,
|
|
4033
4136
|
compoundSubRangeFromPlaceholder: (subLabel) => `${subLabel} from`,
|
|
4034
|
-
compoundSubRangeToPlaceholder: (subLabel) => `${subLabel} to
|
|
4137
|
+
compoundSubRangeToPlaceholder: (subLabel) => `${subLabel} to`,
|
|
4138
|
+
multiValueFormHint: "Multiple values — edit using the column filter chips or popover above the table.",
|
|
4139
|
+
valuesCount: (count) => `${count} values`,
|
|
4140
|
+
selectedCount: (count) => `${count} selected`,
|
|
4141
|
+
clearFieldForSingleEdit: "Clear to edit one value"
|
|
4035
4142
|
};
|
|
4036
4143
|
function deriveInitialState(fields, filters) {
|
|
4037
4144
|
const byId = new Map(filters.map((f) => [f.columnId, f.value]));
|
|
@@ -4081,24 +4188,38 @@ function FilterFormPanel({
|
|
|
4081
4188
|
return changed ? next : prev;
|
|
4082
4189
|
});
|
|
4083
4190
|
}, [filters, renderableFields]);
|
|
4084
|
-
const setField = React.useCallback(
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4191
|
+
const setField = React.useCallback(
|
|
4192
|
+
(id2, value, compoundSubKey) => {
|
|
4193
|
+
recordFilterFieldTouch(touchedRef.current, id2, compoundSubKey);
|
|
4194
|
+
setState((prev) => patchFieldState(prev, id2, value));
|
|
4195
|
+
},
|
|
4196
|
+
[]
|
|
4197
|
+
);
|
|
4088
4198
|
const handleApply = React.useCallback(() => {
|
|
4089
4199
|
const activeIds = new Set(filters.map((f) => f.columnId));
|
|
4200
|
+
const committedById = new Map(filters.map((f) => [f.columnId, f.value]));
|
|
4201
|
+
const touched = touchedRef.current;
|
|
4090
4202
|
const changes = [];
|
|
4091
4203
|
for (const id2 of renderableFields) {
|
|
4092
|
-
const
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4204
|
+
const config = filterConfigs[id2];
|
|
4205
|
+
const committed = committedById.get(id2);
|
|
4206
|
+
const draft = state[id2];
|
|
4207
|
+
const resolution = resolveFieldApplyChange(
|
|
4208
|
+
config,
|
|
4209
|
+
id2,
|
|
4210
|
+
committed,
|
|
4211
|
+
draft,
|
|
4212
|
+
touched,
|
|
4213
|
+
hiddenSubSet,
|
|
4214
|
+
activeIds.has(id2)
|
|
4215
|
+
);
|
|
4216
|
+
if (resolution.kind === "skip") continue;
|
|
4217
|
+
if (resolution.kind === "remove") changes.push({ columnId: id2, action: "remove" });
|
|
4218
|
+
else changes.push({ columnId: id2, action: "set", value: resolution.value });
|
|
4098
4219
|
}
|
|
4099
4220
|
onApply(changes);
|
|
4100
4221
|
touchedRef.current.clear();
|
|
4101
|
-
}, [filters, onApply, renderableFields, state]);
|
|
4222
|
+
}, [filterConfigs, filters, hiddenSubSet, onApply, renderableFields, state]);
|
|
4102
4223
|
const handleClear = React.useCallback(() => {
|
|
4103
4224
|
setState(deriveInitialState(renderableFields, []));
|
|
4104
4225
|
onClear(renderableFields);
|
|
@@ -4143,7 +4264,7 @@ function FilterFormPanel({
|
|
|
4143
4264
|
{
|
|
4144
4265
|
config,
|
|
4145
4266
|
value: state[fieldId],
|
|
4146
|
-
onChange: (v) => setField(fieldId, v),
|
|
4267
|
+
onChange: (v, compoundSubKey) => setField(fieldId, v, compoundSubKey),
|
|
4147
4268
|
fieldId,
|
|
4148
4269
|
hiddenSubSet,
|
|
4149
4270
|
labels
|
|
@@ -4171,7 +4292,20 @@ function FilterFormPanel({
|
|
|
4171
4292
|
}
|
|
4172
4293
|
function Field({ config, value, onChange, fieldId, hiddenSubSet, labels }) {
|
|
4173
4294
|
if (config.type === "text") {
|
|
4174
|
-
const
|
|
4295
|
+
const strings = getStringArray(value);
|
|
4296
|
+
if (strings.length > 1) {
|
|
4297
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4298
|
+
MultiValueStringSummary,
|
|
4299
|
+
{
|
|
4300
|
+
label: config.label,
|
|
4301
|
+
summary: formatTextMultiSummary(strings, labels),
|
|
4302
|
+
hint: labels.multiValueFormHint,
|
|
4303
|
+
clearLabel: labels.clearFieldForSingleEdit,
|
|
4304
|
+
onClearForEdit: () => onChange(void 0)
|
|
4305
|
+
}
|
|
4306
|
+
);
|
|
4307
|
+
}
|
|
4308
|
+
const text = singleStringDraftValue(strings);
|
|
4175
4309
|
return /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex min-w-0 flex-col gap-1.5", children: [
|
|
4176
4310
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] font-medium text-[var(--text-secondary)]", children: config.label }),
|
|
4177
4311
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4189,14 +4323,27 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet, labels }) {
|
|
|
4189
4323
|
] });
|
|
4190
4324
|
}
|
|
4191
4325
|
if (config.type === "enum") {
|
|
4192
|
-
const
|
|
4326
|
+
const strings = getStringArray(value);
|
|
4327
|
+
if (strings.length > 1) {
|
|
4328
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4329
|
+
MultiValueStringSummary,
|
|
4330
|
+
{
|
|
4331
|
+
label: config.label,
|
|
4332
|
+
summary: formatEnumMultiSummary(strings, config.options, labels),
|
|
4333
|
+
hint: labels.multiValueFormHint,
|
|
4334
|
+
clearLabel: labels.clearFieldForSingleEdit,
|
|
4335
|
+
onClearForEdit: () => onChange(void 0)
|
|
4336
|
+
}
|
|
4337
|
+
);
|
|
4338
|
+
}
|
|
4339
|
+
const comboboxValue = enumComboboxSelection(strings);
|
|
4193
4340
|
return /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex min-w-0 flex-col gap-1.5", children: [
|
|
4194
4341
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] font-medium text-[var(--text-secondary)]", children: config.label }),
|
|
4195
4342
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4196
4343
|
Combobox,
|
|
4197
4344
|
{
|
|
4198
|
-
value:
|
|
4199
|
-
onChange: (v) => onChange(v
|
|
4345
|
+
value: comboboxValue,
|
|
4346
|
+
onChange: (v) => onChange(v === null ? void 0 : [v]),
|
|
4200
4347
|
options: config.options ?? [],
|
|
4201
4348
|
placeholder: labels.anyPlaceholder,
|
|
4202
4349
|
triggerAriaLabel: config.label,
|
|
@@ -4277,10 +4424,7 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet, labels }) {
|
|
|
4277
4424
|
if (config.type === "compound") {
|
|
4278
4425
|
const compound = getCompoundValue(value);
|
|
4279
4426
|
const updateSub = (subKey, next) => {
|
|
4280
|
-
|
|
4281
|
-
delete rest[subKey];
|
|
4282
|
-
const merged = next === void 0 || isFilterValueEmpty(next) ? rest : { ...rest, [subKey]: next };
|
|
4283
|
-
onChange(Object.keys(merged).length === 0 ? void 0 : merged);
|
|
4427
|
+
onChange(mergeCompoundSubDraft(compound, subKey, next), subKey);
|
|
4284
4428
|
};
|
|
4285
4429
|
const visibleSubs = (config.subFilters ?? []).filter(
|
|
4286
4430
|
(sub) => !hiddenSubSet.has(`${fieldId}:${sub.key}`)
|
|
@@ -4297,7 +4441,22 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet, labels }) {
|
|
|
4297
4441
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid min-w-0 gap-2 grid-cols-[repeat(auto-fit,minmax(180px,1fr))]", children: visibleSubs.map((sub) => {
|
|
4298
4442
|
const subValue = compound[sub.key];
|
|
4299
4443
|
if (sub.type === "text") {
|
|
4300
|
-
const
|
|
4444
|
+
const strings = getStringArray(subValue);
|
|
4445
|
+
if (strings.length > 1) {
|
|
4446
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4447
|
+
MultiValueStringSummary,
|
|
4448
|
+
{
|
|
4449
|
+
label: sub.label,
|
|
4450
|
+
summary: formatTextMultiSummary(strings, labels),
|
|
4451
|
+
hint: labels.multiValueFormHint,
|
|
4452
|
+
clearLabel: labels.clearFieldForSingleEdit,
|
|
4453
|
+
onClearForEdit: () => updateSub(sub.key, void 0),
|
|
4454
|
+
compact: true
|
|
4455
|
+
},
|
|
4456
|
+
sub.key
|
|
4457
|
+
);
|
|
4458
|
+
}
|
|
4459
|
+
const text = singleStringDraftValue(strings);
|
|
4301
4460
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4302
4461
|
Input,
|
|
4303
4462
|
{
|
|
@@ -4314,12 +4473,27 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet, labels }) {
|
|
|
4314
4473
|
);
|
|
4315
4474
|
}
|
|
4316
4475
|
if (sub.type === "enum") {
|
|
4317
|
-
const
|
|
4476
|
+
const strings = getStringArray(subValue);
|
|
4477
|
+
if (strings.length > 1) {
|
|
4478
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4479
|
+
MultiValueStringSummary,
|
|
4480
|
+
{
|
|
4481
|
+
label: sub.label,
|
|
4482
|
+
summary: formatEnumMultiSummary(strings, sub.options, labels),
|
|
4483
|
+
hint: labels.multiValueFormHint,
|
|
4484
|
+
clearLabel: labels.clearFieldForSingleEdit,
|
|
4485
|
+
onClearForEdit: () => updateSub(sub.key, void 0),
|
|
4486
|
+
compact: true
|
|
4487
|
+
},
|
|
4488
|
+
sub.key
|
|
4489
|
+
);
|
|
4490
|
+
}
|
|
4491
|
+
const comboboxValue = enumComboboxSelection(strings);
|
|
4318
4492
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4319
4493
|
Combobox,
|
|
4320
4494
|
{
|
|
4321
|
-
value:
|
|
4322
|
-
onChange: (v) => updateSub(sub.key, v
|
|
4495
|
+
value: comboboxValue,
|
|
4496
|
+
onChange: (v) => updateSub(sub.key, v === null ? void 0 : [v]),
|
|
4323
4497
|
options: sub.options ?? [],
|
|
4324
4498
|
placeholder: sub.label,
|
|
4325
4499
|
triggerAriaLabel: sub.label,
|
|
@@ -4375,6 +4549,31 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet, labels }) {
|
|
|
4375
4549
|
}
|
|
4376
4550
|
return null;
|
|
4377
4551
|
}
|
|
4552
|
+
function MultiValueStringSummary({
|
|
4553
|
+
label,
|
|
4554
|
+
summary,
|
|
4555
|
+
hint,
|
|
4556
|
+
clearLabel,
|
|
4557
|
+
onClearForEdit,
|
|
4558
|
+
compact
|
|
4559
|
+
}) {
|
|
4560
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex min-w-0 flex-col gap-1.5", compact && "col-span-full"), children: [
|
|
4561
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] font-medium text-[var(--text-secondary)]", children: label }),
|
|
4562
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[12px] text-[var(--text-primary)]", children: summary }),
|
|
4563
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] leading-snug text-[var(--text-secondary)]", children: hint }),
|
|
4564
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4565
|
+
Button,
|
|
4566
|
+
{
|
|
4567
|
+
type: "button",
|
|
4568
|
+
variant: "secondary",
|
|
4569
|
+
size: "sm",
|
|
4570
|
+
className: "h-9 self-start",
|
|
4571
|
+
onClick: onClearForEdit,
|
|
4572
|
+
children: clearLabel
|
|
4573
|
+
}
|
|
4574
|
+
)
|
|
4575
|
+
] });
|
|
4576
|
+
}
|
|
4378
4577
|
const STORAGE_PREFIX = "tablewright.filters.open.";
|
|
4379
4578
|
function readPersisted(persistKey, fallback) {
|
|
4380
4579
|
if (typeof window === "undefined") return fallback;
|
package/dist/tablewright.css
CHANGED
|
@@ -317,6 +317,9 @@
|
|
|
317
317
|
.z-50 {
|
|
318
318
|
z-index: 50
|
|
319
319
|
}
|
|
320
|
+
.col-span-full {
|
|
321
|
+
grid-column: 1 / -1
|
|
322
|
+
}
|
|
320
323
|
.mx-3 {
|
|
321
324
|
margin-left: 0.75rem;
|
|
322
325
|
margin-right: 0.75rem
|
|
@@ -737,6 +740,9 @@
|
|
|
737
740
|
margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
|
|
738
741
|
margin-bottom: calc(1rem * var(--tw-space-y-reverse))
|
|
739
742
|
}
|
|
743
|
+
.self-start {
|
|
744
|
+
align-self: flex-start
|
|
745
|
+
}
|
|
740
746
|
.self-stretch {
|
|
741
747
|
align-self: stretch
|
|
742
748
|
}
|
|
@@ -1128,6 +1134,9 @@
|
|
|
1128
1134
|
.leading-none {
|
|
1129
1135
|
line-height: 1
|
|
1130
1136
|
}
|
|
1137
|
+
.leading-snug {
|
|
1138
|
+
line-height: 1.375
|
|
1139
|
+
}
|
|
1131
1140
|
.leading-tight {
|
|
1132
1141
|
line-height: 1.25
|
|
1133
1142
|
}
|
|
@@ -14,6 +14,12 @@ export interface FilterFormPanelLabels {
|
|
|
14
14
|
compoundSubRangeToAriaLabel: (subLabel: string) => string;
|
|
15
15
|
compoundSubRangeFromPlaceholder: (subLabel: string) => string;
|
|
16
16
|
compoundSubRangeToPlaceholder: (subLabel: string) => string;
|
|
17
|
+
/** Shown under read-only multi-value text fields in the form panel. */
|
|
18
|
+
multiValueFormHint: string;
|
|
19
|
+
valuesCount: (count: number) => string;
|
|
20
|
+
selectedCount: (count: number) => string;
|
|
21
|
+
/** Clears a multi-value field so the user can enter a single value. */
|
|
22
|
+
clearFieldForSingleEdit: string;
|
|
17
23
|
}
|
|
18
24
|
export interface FilterFormPanelProps {
|
|
19
25
|
/** All known filter configs (same shape used by GroupedFilterAdd). */
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ActiveFilterValue, ColumnFilterConfig, CompoundFilterValue, CompoundSubFilter } from "./filter-types.js";
|
|
2
|
+
export interface MultiValueSummaryLabels {
|
|
3
|
+
valuesCount: (count: number) => string;
|
|
4
|
+
selectedCount: (count: number) => string;
|
|
5
|
+
}
|
|
6
|
+
export declare function compoundSubTouchKey(fieldId: string, subKey: string): string;
|
|
7
|
+
export declare function recordFilterFieldTouch(touched: Set<string>, fieldId: string, compoundSubKey?: string): void;
|
|
8
|
+
export declare function singleStringDraftValue(strings: string[]): string;
|
|
9
|
+
export declare function enumComboboxSelection(strings: string[]): string | null;
|
|
10
|
+
export declare function patchFieldState<T extends Record<string, ActiveFilterValue | undefined>>(prev: T, fieldId: string, value: ActiveFilterValue | undefined): T;
|
|
11
|
+
export declare function mergeCompoundSubDraft(compound: CompoundFilterValue, subKey: string, next: ActiveFilterValue | undefined): ActiveFilterValue | undefined;
|
|
12
|
+
export declare function formatTextMultiSummary(values: string[], labels: MultiValueSummaryLabels): string;
|
|
13
|
+
export declare function formatEnumMultiSummary(values: string[], options: {
|
|
14
|
+
label: string;
|
|
15
|
+
value: string;
|
|
16
|
+
}[] | undefined, labels: MultiValueSummaryLabels): string;
|
|
17
|
+
export declare function shouldPreserveMultiSubOnApply(sub: CompoundSubFilter, subCommitted: ActiveFilterValue | undefined, touchKey: string, touched: Set<string>): boolean;
|
|
18
|
+
export declare function mergeCompoundValueForApply(config: ColumnFilterConfig, fieldId: string, committed: ActiveFilterValue | undefined, draft: ActiveFilterValue | undefined, touched: Set<string>, hiddenSubSet: Set<string>): ActiveFilterValue | undefined;
|
|
19
|
+
export type ApplyResolution = {
|
|
20
|
+
kind: "skip";
|
|
21
|
+
} | {
|
|
22
|
+
kind: "remove";
|
|
23
|
+
} | {
|
|
24
|
+
kind: "set";
|
|
25
|
+
value: ActiveFilterValue;
|
|
26
|
+
};
|
|
27
|
+
export declare function resolveFieldApplyChange(config: ColumnFilterConfig, fieldId: string, committed: ActiveFilterValue | undefined, draft: ActiveFilterValue | undefined, touched: Set<string>, hiddenSubSet: Set<string>, isActive: boolean): ApplyResolution;
|
|
@@ -34,6 +34,14 @@ export type NumberRangeValue = {
|
|
|
34
34
|
export type CompoundFilterValue = {
|
|
35
35
|
[key: string]: ActiveFilterValue;
|
|
36
36
|
};
|
|
37
|
+
/**
|
|
38
|
+
* Stored filter payload for one column. Text and enum filters are usually `string[]`
|
|
39
|
+
* (one element for a single selection; multiple for popover/chip multi-select).
|
|
40
|
+
*
|
|
41
|
+
* `FilterFormPanel` edits at most one string per text/enum field (and per compound
|
|
42
|
+
* sub-filter of those types). Multi-value text/enum filters are edited via column
|
|
43
|
+
* filter chips and `ColumnFilterPopover`, not the form panel.
|
|
44
|
+
*/
|
|
37
45
|
export type ActiveFilterValue = string | string[] | DateRangeValue | NumberRangeValue | CompoundFilterValue;
|
|
38
46
|
export interface ActiveFilter {
|
|
39
47
|
columnId: string;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { ActiveFilterValue, CompoundFilterValue, DateRangeValue, NumberRangeValue } from "./filter-types.js";
|
|
2
2
|
/** Read a text/enum filter value. Non-array values read as `[]`. */
|
|
3
3
|
export declare function getStringArray(value: ActiveFilterValue | undefined): string[];
|
|
4
|
+
/** True when a text/enum value selects more than one string (popover/chip multi-select). */
|
|
5
|
+
export declare function isMultiStringFilterValue(value: ActiveFilterValue | undefined): boolean;
|
|
4
6
|
/** Read a date filter value. Non-string bounds are dropped. */
|
|
5
7
|
export declare function getDateRange(value: ActiveFilterValue | undefined): DateRangeValue;
|
|
6
8
|
/** Read a number-range filter value. Non-number bounds are dropped. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@undevy-org/tablewright",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.3",
|
|
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": {
|