@j2-health/design-system 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -9
- package/dist/index.cjs +6 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/src/components/barchart/BarChart.cjs +8 -1
- package/dist/src/components/barchart/BarChart.cjs.map +1 -1
- package/dist/src/components/barchart/BarChart.js +8 -1
- package/dist/src/components/barchart/BarChart.js.map +1 -1
- package/dist/src/components/filterform/FilterInput.cjs +66 -3
- package/dist/src/components/filterform/FilterInput.cjs.map +1 -1
- package/dist/src/components/filterform/FilterInput.d.ts +1 -0
- package/dist/src/components/filterform/FilterInput.js +68 -5
- package/dist/src/components/filterform/FilterInput.js.map +1 -1
- package/dist/src/components/filterform/filterHelpers.cjs +24 -1
- package/dist/src/components/filterform/filterHelpers.cjs.map +1 -1
- package/dist/src/components/filterform/filterHelpers.d.ts +18 -4
- package/dist/src/components/filterform/filterHelpers.js +24 -1
- package/dist/src/components/filterform/filterHelpers.js.map +1 -1
- package/dist/src/components/filterform/index.d.ts +3 -1
- package/dist/src/components/filterform/types.d.ts +4 -2
- package/dist/src/components/filterform/useFilterField.cjs +54 -19
- package/dist/src/components/filterform/useFilterField.cjs.map +1 -1
- package/dist/src/components/filterform/useFilterField.d.ts +17 -1
- package/dist/src/components/filterform/useFilterField.js +55 -20
- package/dist/src/components/filterform/useFilterField.js.map +1 -1
- package/dist/src/components/vennDiagram/VennDiagram.cjs +7 -1
- package/dist/src/components/vennDiagram/VennDiagram.cjs.map +1 -1
- package/dist/src/components/vennDiagram/VennDiagram.js +7 -1
- package/dist/src/components/vennDiagram/VennDiagram.js.map +1 -1
- package/package.json +6 -6
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const isMultiValueTextOperator = (operator) => operator === "isAnyOf" || operator === "isNoneOf";
|
|
4
|
+
const normalizeMultiValues = (raw) => {
|
|
5
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6
|
+
const out = [];
|
|
7
|
+
for (const value of raw ?? []) {
|
|
8
|
+
if (value === null || value === void 0) continue;
|
|
9
|
+
const trimmed = String(value).trim();
|
|
10
|
+
if (trimmed === "") continue;
|
|
11
|
+
const key = trimmed.toLowerCase();
|
|
12
|
+
if (seen.has(key)) continue;
|
|
13
|
+
seen.add(key);
|
|
14
|
+
out.push(trimmed);
|
|
15
|
+
}
|
|
16
|
+
return out;
|
|
17
|
+
};
|
|
18
|
+
const tooManyValuesMessage = (maxValues) => `Up to ${maxValues} values`;
|
|
19
|
+
const exceedsMaxValues = (values, limits) => (limits == null ? void 0 : limits.maxValues) !== void 0 && values !== void 0 && values.length > limits.maxValues;
|
|
3
20
|
const isEmptyFilter = (filter) => {
|
|
4
21
|
if (filter.operator === "blank" || filter.operator === "notBlank") {
|
|
5
22
|
return false;
|
|
@@ -22,7 +39,7 @@ const isEmptyFormFilter = (filter) => {
|
|
|
22
39
|
}
|
|
23
40
|
return true;
|
|
24
41
|
};
|
|
25
|
-
const validateFormFilter = (filter) => {
|
|
42
|
+
const validateFormFilter = (filter, limits) => {
|
|
26
43
|
const errors = [];
|
|
27
44
|
if (!filter.operator) {
|
|
28
45
|
errors.push("Operator is required");
|
|
@@ -31,6 +48,9 @@ const validateFormFilter = (filter) => {
|
|
|
31
48
|
if (isEmptyFormFilter(filter)) {
|
|
32
49
|
errors.push("Value is required");
|
|
33
50
|
}
|
|
51
|
+
if (exceedsMaxValues(filter.values, limits)) {
|
|
52
|
+
errors.push(tooManyValuesMessage(limits.maxValues));
|
|
53
|
+
}
|
|
34
54
|
if (filter.type === "number" && filter.operator === "inRange") {
|
|
35
55
|
if (filter.values && filter.values.length !== 2) {
|
|
36
56
|
errors.push("Value is required");
|
|
@@ -44,5 +64,8 @@ const isFilter = (filter) => {
|
|
|
44
64
|
exports.isEmptyFilter = isEmptyFilter;
|
|
45
65
|
exports.isEmptyFormFilter = isEmptyFormFilter;
|
|
46
66
|
exports.isFilter = isFilter;
|
|
67
|
+
exports.isMultiValueTextOperator = isMultiValueTextOperator;
|
|
68
|
+
exports.normalizeMultiValues = normalizeMultiValues;
|
|
69
|
+
exports.tooManyValuesMessage = tooManyValuesMessage;
|
|
47
70
|
exports.validateFormFilter = validateFormFilter;
|
|
48
71
|
//# sourceMappingURL=filterHelpers.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filterHelpers.cjs","sources":["../../../../src/components/filterform/filterHelpers.ts"],"sourcesContent":["import { Filter } from './types'\n\nexport const isEmptyFilter = (filter: Filter) => {\n if (filter.operator === 'blank' || filter.operator === 'notBlank') {\n return false\n }\n\n if (\n filter.values &&\n filter.values.length > 0 &&\n filter.values.every(\n (value) => value !== '' && value !== undefined && value !== null\n )\n ) {\n return false\n }\n\n return true\n}\n\nexport const isValidFilter = (filter: Filter) => {\n return validateFilterField(filter).length === 0\n}\n\nexport const validateFilterField = (\n filter: Filter\n): { field: string; message: string }[] => {\n const errors = []\n\n if (!filter.operator) {\n errors.push({ field: filter.field, message: 'Operator is required' })\n return errors\n }\n\n if (isEmptyFilter(filter)) {\n errors.push({ field: filter.field, message: 'Value is required' })\n }\n\n if (filter.type === 'number' && filter.operator === 'inRange') {\n if (filter.values && filter.values.length !== 2) {\n errors.push({ field: filter.field, message: 'Value is required' })\n }\n }\n\n return errors\n}\n\nimport { FormFilter } from './types'\n\nexport const isEmptyFormFilter = (filter: FormFilter) => {\n if (filter.operator === 'blank' || filter.operator === 'notBlank') {\n return false\n }\n\n if (\n filter.values &&\n filter.values.length > 0 &&\n filter.values.every(\n (value) => value !== '' && value !== undefined && value !== null\n )\n ) {\n return false\n }\n\n return true\n}\n\nexport const isValidFormFilter = (filter: FormFilter) => {\n return validateFormFilter(filter).length === 0\n}\n\nexport const validateFormFilter = (filter: FormFilter): string[] => {\n const errors = []\n\n if (!filter.operator) {\n errors.push('Operator is required')\n return errors\n }\n\n if (isEmptyFormFilter(filter)) {\n errors.push('Value is required')\n }\n\n if (filter.type === 'number' && filter.operator === 'inRange') {\n if (filter.values && filter.values.length !== 2) {\n errors.push('Value is required')\n }\n }\n\n return errors\n}\n\nexport const isFilter = (filter: FormFilter): filter is Filter => {\n return (\n filter.operator !== undefined &&\n filter.field !== undefined &&\n filter.type !== undefined\n )\n}\n"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"filterHelpers.cjs","sources":["../../../../src/components/filterform/filterHelpers.ts"],"sourcesContent":["import { Filter } from './types'\n\n// Multi-value text operators — the value is a chip list, not a single string.\nexport const isMultiValueTextOperator = (\n operator: string | undefined\n): boolean => operator === 'isAnyOf' || operator === 'isNoneOf'\n\n/**\n * Normalize a chip list: trim, drop empties, dedupe case-insensitively (the\n * match is case-insensitive, so `ABC` and `abc` are the same rule). Keeps\n * first-seen casing and order so the chips read the way they were pasted.\n *\n * Applied when values enter reducer state (`useFilterField`), so the chips\n * on screen, the validation count and the submitted `values` are one array.\n */\nexport const normalizeMultiValues = (\n raw: (string | number | undefined | null)[] | undefined\n): string[] => {\n const seen = new Set<string>()\n const out: string[] = []\n for (const value of raw ?? []) {\n if (value === null || value === undefined) continue\n const trimmed = String(value).trim()\n if (trimmed === '') continue\n const key = trimmed.toLowerCase()\n if (seen.has(key)) continue\n seen.add(key)\n out.push(trimmed)\n }\n return out\n}\n\n// The only `FilterConfig` fields validation consults. Passed separately from\n// the filter because a submitted `Filter` doesn't carry its config.\nexport type FilterValueLimits = { maxValues?: number }\n\nexport const tooManyValuesMessage = (maxValues: number) =>\n `Up to ${maxValues} values`\n\nconst exceedsMaxValues = (\n values: unknown[] | undefined,\n limits: FilterValueLimits | undefined\n): limits is { maxValues: number } =>\n limits?.maxValues !== undefined &&\n values !== undefined &&\n values.length > limits.maxValues\n\nexport const isEmptyFilter = (filter: Filter) => {\n if (filter.operator === 'blank' || filter.operator === 'notBlank') {\n return false\n }\n\n if (\n filter.values &&\n filter.values.length > 0 &&\n filter.values.every(\n (value) => value !== '' && value !== undefined && value !== null\n )\n ) {\n return false\n }\n\n return true\n}\n\nexport const isValidFilter = (filter: Filter, limits?: FilterValueLimits) => {\n return validateFilterField(filter, limits).length === 0\n}\n\nexport const validateFilterField = (\n filter: Filter,\n limits?: FilterValueLimits\n): { field: string; message: string }[] => {\n const errors = []\n\n if (!filter.operator) {\n errors.push({ field: filter.field, message: 'Operator is required' })\n return errors\n }\n\n if (isEmptyFilter(filter)) {\n errors.push({ field: filter.field, message: 'Value is required' })\n }\n\n if (exceedsMaxValues(filter.values, limits)) {\n errors.push({\n field: filter.field,\n message: tooManyValuesMessage(limits.maxValues),\n })\n }\n\n if (filter.type === 'number' && filter.operator === 'inRange') {\n if (filter.values && filter.values.length !== 2) {\n errors.push({ field: filter.field, message: 'Value is required' })\n }\n }\n\n return errors\n}\n\nimport { FormFilter } from './types'\n\nexport const isEmptyFormFilter = (filter: FormFilter) => {\n if (filter.operator === 'blank' || filter.operator === 'notBlank') {\n return false\n }\n\n if (\n filter.values &&\n filter.values.length > 0 &&\n filter.values.every(\n (value) => value !== '' && value !== undefined && value !== null\n )\n ) {\n return false\n }\n\n return true\n}\n\nexport const isValidFormFilter = (\n filter: FormFilter,\n limits?: FilterValueLimits\n) => {\n return validateFormFilter(filter, limits).length === 0\n}\n\nexport const validateFormFilter = (\n filter: FormFilter,\n limits?: FilterValueLimits\n): string[] => {\n const errors = []\n\n if (!filter.operator) {\n errors.push('Operator is required')\n return errors\n }\n\n if (isEmptyFormFilter(filter)) {\n errors.push('Value is required')\n }\n\n if (exceedsMaxValues(filter.values, limits)) {\n errors.push(tooManyValuesMessage(limits.maxValues))\n }\n\n if (filter.type === 'number' && filter.operator === 'inRange') {\n if (filter.values && filter.values.length !== 2) {\n errors.push('Value is required')\n }\n }\n\n return errors\n}\n\nexport const isFilter = (filter: FormFilter): filter is Filter => {\n return (\n filter.operator !== undefined &&\n filter.field !== undefined &&\n filter.type !== undefined\n )\n}\n"],"names":[],"mappings":";;AAGO,MAAM,2BAA2B,CACtC,aACY,aAAa,aAAa,aAAa;AAU9C,MAAM,uBAAuB,CAClC,QACa;AACb,QAAM,2BAAW,IAAA;AACjB,QAAM,MAAgB,CAAA;AACtB,aAAW,SAAS,OAAO,IAAI;AAC7B,QAAI,UAAU,QAAQ,UAAU,OAAW;AAC3C,UAAM,UAAU,OAAO,KAAK,EAAE,KAAA;AAC9B,QAAI,YAAY,GAAI;AACpB,UAAM,MAAM,QAAQ,YAAA;AACpB,QAAI,KAAK,IAAI,GAAG,EAAG;AACnB,SAAK,IAAI,GAAG;AACZ,QAAI,KAAK,OAAO;AAAA,EAClB;AACA,SAAO;AACT;AAMO,MAAM,uBAAuB,CAAC,cACnC,SAAS,SAAS;AAEpB,MAAM,mBAAmB,CACvB,QACA,YAEA,iCAAQ,eAAc,UACtB,WAAW,UACX,OAAO,SAAS,OAAO;AAElB,MAAM,gBAAgB,CAAC,WAAmB;AAC/C,MAAI,OAAO,aAAa,WAAW,OAAO,aAAa,YAAY;AACjE,WAAO;AAAA,EACT;AAEA,MACE,OAAO,UACP,OAAO,OAAO,SAAS,KACvB,OAAO,OAAO;AAAA,IACZ,CAAC,UAAU,UAAU,MAAM,UAAU,UAAa,UAAU;AAAA,EAAA,GAE9D;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAuCO,MAAM,oBAAoB,CAAC,WAAuB;AACvD,MAAI,OAAO,aAAa,WAAW,OAAO,aAAa,YAAY;AACjE,WAAO;AAAA,EACT;AAEA,MACE,OAAO,UACP,OAAO,OAAO,SAAS,KACvB,OAAO,OAAO;AAAA,IACZ,CAAC,UAAU,UAAU,MAAM,UAAU,UAAa,UAAU;AAAA,EAAA,GAE9D;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AASO,MAAM,qBAAqB,CAChC,QACA,WACa;AACb,QAAM,SAAS,CAAA;AAEf,MAAI,CAAC,OAAO,UAAU;AACpB,WAAO,KAAK,sBAAsB;AAClC,WAAO;AAAA,EACT;AAEA,MAAI,kBAAkB,MAAM,GAAG;AAC7B,WAAO,KAAK,mBAAmB;AAAA,EACjC;AAEA,MAAI,iBAAiB,OAAO,QAAQ,MAAM,GAAG;AAC3C,WAAO,KAAK,qBAAqB,OAAO,SAAS,CAAC;AAAA,EACpD;AAEA,MAAI,OAAO,SAAS,YAAY,OAAO,aAAa,WAAW;AAC7D,QAAI,OAAO,UAAU,OAAO,OAAO,WAAW,GAAG;AAC/C,aAAO,KAAK,mBAAmB;AAAA,IACjC;AAAA,EACF;AAEA,SAAO;AACT;AAEO,MAAM,WAAW,CAAC,WAAyC;AAChE,SACE,OAAO,aAAa,UACpB,OAAO,UAAU,UACjB,OAAO,SAAS;AAEpB;;;;;;;;"}
|
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
import { Filter, FormFilter } from './types.js';
|
|
2
|
+
export declare const isMultiValueTextOperator: (operator: string | undefined) => boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Normalize a chip list: trim, drop empties, dedupe case-insensitively (the
|
|
5
|
+
* match is case-insensitive, so `ABC` and `abc` are the same rule). Keeps
|
|
6
|
+
* first-seen casing and order so the chips read the way they were pasted.
|
|
7
|
+
*
|
|
8
|
+
* Applied when values enter reducer state (`useFilterField`), so the chips
|
|
9
|
+
* on screen, the validation count and the submitted `values` are one array.
|
|
10
|
+
*/
|
|
11
|
+
export declare const normalizeMultiValues: (raw: (string | number | undefined | null)[] | undefined) => string[];
|
|
12
|
+
export type FilterValueLimits = {
|
|
13
|
+
maxValues?: number;
|
|
14
|
+
};
|
|
15
|
+
export declare const tooManyValuesMessage: (maxValues: number) => string;
|
|
2
16
|
export declare const isEmptyFilter: (filter: Filter) => boolean;
|
|
3
|
-
export declare const isValidFilter: (filter: Filter) => boolean;
|
|
4
|
-
export declare const validateFilterField: (filter: Filter) => {
|
|
17
|
+
export declare const isValidFilter: (filter: Filter, limits?: FilterValueLimits) => boolean;
|
|
18
|
+
export declare const validateFilterField: (filter: Filter, limits?: FilterValueLimits) => {
|
|
5
19
|
field: string;
|
|
6
20
|
message: string;
|
|
7
21
|
}[];
|
|
8
22
|
export declare const isEmptyFormFilter: (filter: FormFilter) => boolean;
|
|
9
|
-
export declare const isValidFormFilter: (filter: FormFilter) => boolean;
|
|
10
|
-
export declare const validateFormFilter: (filter: FormFilter) => string[];
|
|
23
|
+
export declare const isValidFormFilter: (filter: FormFilter, limits?: FilterValueLimits) => boolean;
|
|
24
|
+
export declare const validateFormFilter: (filter: FormFilter, limits?: FilterValueLimits) => string[];
|
|
11
25
|
export declare const isFilter: (filter: FormFilter) => filter is Filter;
|
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
const isMultiValueTextOperator = (operator) => operator === "isAnyOf" || operator === "isNoneOf";
|
|
2
|
+
const normalizeMultiValues = (raw) => {
|
|
3
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4
|
+
const out = [];
|
|
5
|
+
for (const value of raw ?? []) {
|
|
6
|
+
if (value === null || value === void 0) continue;
|
|
7
|
+
const trimmed = String(value).trim();
|
|
8
|
+
if (trimmed === "") continue;
|
|
9
|
+
const key = trimmed.toLowerCase();
|
|
10
|
+
if (seen.has(key)) continue;
|
|
11
|
+
seen.add(key);
|
|
12
|
+
out.push(trimmed);
|
|
13
|
+
}
|
|
14
|
+
return out;
|
|
15
|
+
};
|
|
16
|
+
const tooManyValuesMessage = (maxValues) => `Up to ${maxValues} values`;
|
|
17
|
+
const exceedsMaxValues = (values, limits) => (limits == null ? void 0 : limits.maxValues) !== void 0 && values !== void 0 && values.length > limits.maxValues;
|
|
1
18
|
const isEmptyFilter = (filter) => {
|
|
2
19
|
if (filter.operator === "blank" || filter.operator === "notBlank") {
|
|
3
20
|
return false;
|
|
@@ -20,7 +37,7 @@ const isEmptyFormFilter = (filter) => {
|
|
|
20
37
|
}
|
|
21
38
|
return true;
|
|
22
39
|
};
|
|
23
|
-
const validateFormFilter = (filter) => {
|
|
40
|
+
const validateFormFilter = (filter, limits) => {
|
|
24
41
|
const errors = [];
|
|
25
42
|
if (!filter.operator) {
|
|
26
43
|
errors.push("Operator is required");
|
|
@@ -29,6 +46,9 @@ const validateFormFilter = (filter) => {
|
|
|
29
46
|
if (isEmptyFormFilter(filter)) {
|
|
30
47
|
errors.push("Value is required");
|
|
31
48
|
}
|
|
49
|
+
if (exceedsMaxValues(filter.values, limits)) {
|
|
50
|
+
errors.push(tooManyValuesMessage(limits.maxValues));
|
|
51
|
+
}
|
|
32
52
|
if (filter.type === "number" && filter.operator === "inRange") {
|
|
33
53
|
if (filter.values && filter.values.length !== 2) {
|
|
34
54
|
errors.push("Value is required");
|
|
@@ -43,6 +63,9 @@ export {
|
|
|
43
63
|
isEmptyFilter,
|
|
44
64
|
isEmptyFormFilter,
|
|
45
65
|
isFilter,
|
|
66
|
+
isMultiValueTextOperator,
|
|
67
|
+
normalizeMultiValues,
|
|
68
|
+
tooManyValuesMessage,
|
|
46
69
|
validateFormFilter
|
|
47
70
|
};
|
|
48
71
|
//# sourceMappingURL=filterHelpers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filterHelpers.js","sources":["../../../../src/components/filterform/filterHelpers.ts"],"sourcesContent":["import { Filter } from './types'\n\nexport const isEmptyFilter = (filter: Filter) => {\n if (filter.operator === 'blank' || filter.operator === 'notBlank') {\n return false\n }\n\n if (\n filter.values &&\n filter.values.length > 0 &&\n filter.values.every(\n (value) => value !== '' && value !== undefined && value !== null\n )\n ) {\n return false\n }\n\n return true\n}\n\nexport const isValidFilter = (filter: Filter) => {\n return validateFilterField(filter).length === 0\n}\n\nexport const validateFilterField = (\n filter: Filter\n): { field: string; message: string }[] => {\n const errors = []\n\n if (!filter.operator) {\n errors.push({ field: filter.field, message: 'Operator is required' })\n return errors\n }\n\n if (isEmptyFilter(filter)) {\n errors.push({ field: filter.field, message: 'Value is required' })\n }\n\n if (filter.type === 'number' && filter.operator === 'inRange') {\n if (filter.values && filter.values.length !== 2) {\n errors.push({ field: filter.field, message: 'Value is required' })\n }\n }\n\n return errors\n}\n\nimport { FormFilter } from './types'\n\nexport const isEmptyFormFilter = (filter: FormFilter) => {\n if (filter.operator === 'blank' || filter.operator === 'notBlank') {\n return false\n }\n\n if (\n filter.values &&\n filter.values.length > 0 &&\n filter.values.every(\n (value) => value !== '' && value !== undefined && value !== null\n )\n ) {\n return false\n }\n\n return true\n}\n\nexport const isValidFormFilter = (filter: FormFilter) => {\n return validateFormFilter(filter).length === 0\n}\n\nexport const validateFormFilter = (filter: FormFilter): string[] => {\n const errors = []\n\n if (!filter.operator) {\n errors.push('Operator is required')\n return errors\n }\n\n if (isEmptyFormFilter(filter)) {\n errors.push('Value is required')\n }\n\n if (filter.type === 'number' && filter.operator === 'inRange') {\n if (filter.values && filter.values.length !== 2) {\n errors.push('Value is required')\n }\n }\n\n return errors\n}\n\nexport const isFilter = (filter: FormFilter): filter is Filter => {\n return (\n filter.operator !== undefined &&\n filter.field !== undefined &&\n filter.type !== undefined\n )\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"filterHelpers.js","sources":["../../../../src/components/filterform/filterHelpers.ts"],"sourcesContent":["import { Filter } from './types'\n\n// Multi-value text operators — the value is a chip list, not a single string.\nexport const isMultiValueTextOperator = (\n operator: string | undefined\n): boolean => operator === 'isAnyOf' || operator === 'isNoneOf'\n\n/**\n * Normalize a chip list: trim, drop empties, dedupe case-insensitively (the\n * match is case-insensitive, so `ABC` and `abc` are the same rule). Keeps\n * first-seen casing and order so the chips read the way they were pasted.\n *\n * Applied when values enter reducer state (`useFilterField`), so the chips\n * on screen, the validation count and the submitted `values` are one array.\n */\nexport const normalizeMultiValues = (\n raw: (string | number | undefined | null)[] | undefined\n): string[] => {\n const seen = new Set<string>()\n const out: string[] = []\n for (const value of raw ?? []) {\n if (value === null || value === undefined) continue\n const trimmed = String(value).trim()\n if (trimmed === '') continue\n const key = trimmed.toLowerCase()\n if (seen.has(key)) continue\n seen.add(key)\n out.push(trimmed)\n }\n return out\n}\n\n// The only `FilterConfig` fields validation consults. Passed separately from\n// the filter because a submitted `Filter` doesn't carry its config.\nexport type FilterValueLimits = { maxValues?: number }\n\nexport const tooManyValuesMessage = (maxValues: number) =>\n `Up to ${maxValues} values`\n\nconst exceedsMaxValues = (\n values: unknown[] | undefined,\n limits: FilterValueLimits | undefined\n): limits is { maxValues: number } =>\n limits?.maxValues !== undefined &&\n values !== undefined &&\n values.length > limits.maxValues\n\nexport const isEmptyFilter = (filter: Filter) => {\n if (filter.operator === 'blank' || filter.operator === 'notBlank') {\n return false\n }\n\n if (\n filter.values &&\n filter.values.length > 0 &&\n filter.values.every(\n (value) => value !== '' && value !== undefined && value !== null\n )\n ) {\n return false\n }\n\n return true\n}\n\nexport const isValidFilter = (filter: Filter, limits?: FilterValueLimits) => {\n return validateFilterField(filter, limits).length === 0\n}\n\nexport const validateFilterField = (\n filter: Filter,\n limits?: FilterValueLimits\n): { field: string; message: string }[] => {\n const errors = []\n\n if (!filter.operator) {\n errors.push({ field: filter.field, message: 'Operator is required' })\n return errors\n }\n\n if (isEmptyFilter(filter)) {\n errors.push({ field: filter.field, message: 'Value is required' })\n }\n\n if (exceedsMaxValues(filter.values, limits)) {\n errors.push({\n field: filter.field,\n message: tooManyValuesMessage(limits.maxValues),\n })\n }\n\n if (filter.type === 'number' && filter.operator === 'inRange') {\n if (filter.values && filter.values.length !== 2) {\n errors.push({ field: filter.field, message: 'Value is required' })\n }\n }\n\n return errors\n}\n\nimport { FormFilter } from './types'\n\nexport const isEmptyFormFilter = (filter: FormFilter) => {\n if (filter.operator === 'blank' || filter.operator === 'notBlank') {\n return false\n }\n\n if (\n filter.values &&\n filter.values.length > 0 &&\n filter.values.every(\n (value) => value !== '' && value !== undefined && value !== null\n )\n ) {\n return false\n }\n\n return true\n}\n\nexport const isValidFormFilter = (\n filter: FormFilter,\n limits?: FilterValueLimits\n) => {\n return validateFormFilter(filter, limits).length === 0\n}\n\nexport const validateFormFilter = (\n filter: FormFilter,\n limits?: FilterValueLimits\n): string[] => {\n const errors = []\n\n if (!filter.operator) {\n errors.push('Operator is required')\n return errors\n }\n\n if (isEmptyFormFilter(filter)) {\n errors.push('Value is required')\n }\n\n if (exceedsMaxValues(filter.values, limits)) {\n errors.push(tooManyValuesMessage(limits.maxValues))\n }\n\n if (filter.type === 'number' && filter.operator === 'inRange') {\n if (filter.values && filter.values.length !== 2) {\n errors.push('Value is required')\n }\n }\n\n return errors\n}\n\nexport const isFilter = (filter: FormFilter): filter is Filter => {\n return (\n filter.operator !== undefined &&\n filter.field !== undefined &&\n filter.type !== undefined\n )\n}\n"],"names":[],"mappings":"AAGO,MAAM,2BAA2B,CACtC,aACY,aAAa,aAAa,aAAa;AAU9C,MAAM,uBAAuB,CAClC,QACa;AACb,QAAM,2BAAW,IAAA;AACjB,QAAM,MAAgB,CAAA;AACtB,aAAW,SAAS,OAAO,IAAI;AAC7B,QAAI,UAAU,QAAQ,UAAU,OAAW;AAC3C,UAAM,UAAU,OAAO,KAAK,EAAE,KAAA;AAC9B,QAAI,YAAY,GAAI;AACpB,UAAM,MAAM,QAAQ,YAAA;AACpB,QAAI,KAAK,IAAI,GAAG,EAAG;AACnB,SAAK,IAAI,GAAG;AACZ,QAAI,KAAK,OAAO;AAAA,EAClB;AACA,SAAO;AACT;AAMO,MAAM,uBAAuB,CAAC,cACnC,SAAS,SAAS;AAEpB,MAAM,mBAAmB,CACvB,QACA,YAEA,iCAAQ,eAAc,UACtB,WAAW,UACX,OAAO,SAAS,OAAO;AAElB,MAAM,gBAAgB,CAAC,WAAmB;AAC/C,MAAI,OAAO,aAAa,WAAW,OAAO,aAAa,YAAY;AACjE,WAAO;AAAA,EACT;AAEA,MACE,OAAO,UACP,OAAO,OAAO,SAAS,KACvB,OAAO,OAAO;AAAA,IACZ,CAAC,UAAU,UAAU,MAAM,UAAU,UAAa,UAAU;AAAA,EAAA,GAE9D;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAuCO,MAAM,oBAAoB,CAAC,WAAuB;AACvD,MAAI,OAAO,aAAa,WAAW,OAAO,aAAa,YAAY;AACjE,WAAO;AAAA,EACT;AAEA,MACE,OAAO,UACP,OAAO,OAAO,SAAS,KACvB,OAAO,OAAO;AAAA,IACZ,CAAC,UAAU,UAAU,MAAM,UAAU,UAAa,UAAU;AAAA,EAAA,GAE9D;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AASO,MAAM,qBAAqB,CAChC,QACA,WACa;AACb,QAAM,SAAS,CAAA;AAEf,MAAI,CAAC,OAAO,UAAU;AACpB,WAAO,KAAK,sBAAsB;AAClC,WAAO;AAAA,EACT;AAEA,MAAI,kBAAkB,MAAM,GAAG;AAC7B,WAAO,KAAK,mBAAmB;AAAA,EACjC;AAEA,MAAI,iBAAiB,OAAO,QAAQ,MAAM,GAAG;AAC3C,WAAO,KAAK,qBAAqB,OAAO,SAAS,CAAC;AAAA,EACpD;AAEA,MAAI,OAAO,SAAS,YAAY,OAAO,aAAa,WAAW;AAC7D,QAAI,OAAO,UAAU,OAAO,OAAO,WAAW,GAAG;AAC/C,aAAO,KAAK,mBAAmB;AAAA,IACjC;AAAA,EACF;AAEA,SAAO;AACT;AAEO,MAAM,WAAW,CAAC,WAAyC;AAChE,SACE,OAAO,aAAa,UACpB,OAAO,UAAU,UACjB,OAAO,SAAS;AAEpB;"}
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export { FiltersForm } from './FiltersForm.js';
|
|
2
|
-
export type { FilterForm, FilterConfig, FilterType, Filter, FilterGroup, } from './types.js';
|
|
2
|
+
export type { FilterForm, FilterConfig, FilterType, Filter, FilterGroup, Operator, } from './types.js';
|
|
3
|
+
export { MultiValueTextOperatorOptions, operatorOptionsFor, } from './useFilterField.js';
|
|
4
|
+
export { isMultiValueTextOperator, normalizeMultiValues } from './filterHelpers.js';
|
|
@@ -28,8 +28,10 @@ export type FilterConfig = {
|
|
|
28
28
|
type: FilterType;
|
|
29
29
|
disabled?: boolean;
|
|
30
30
|
options?: SelectOptionsType | NumberOptionsType;
|
|
31
|
+
operators?: Operator[];
|
|
32
|
+
maxValues?: number;
|
|
31
33
|
};
|
|
32
|
-
export type Operator = 'equals' | 'notEqual' | 'blank' | 'notBlank' | 'greaterThan' | 'greaterThanOrEqual' | 'lessThan' | 'lessThanOrEqual' | 'inRange' | 'contains' | 'notContains' | 'startsWith' | 'endsWith';
|
|
34
|
+
export type Operator = 'equals' | 'notEqual' | 'blank' | 'notBlank' | 'greaterThan' | 'greaterThanOrEqual' | 'lessThan' | 'lessThanOrEqual' | 'inRange' | 'contains' | 'notContains' | 'startsWith' | 'endsWith' | 'isAnyOf' | 'isNoneOf';
|
|
33
35
|
type SelectFilter = {
|
|
34
36
|
field: string;
|
|
35
37
|
type: 'select';
|
|
@@ -45,7 +47,7 @@ type NumberFilter = {
|
|
|
45
47
|
type TextFilter = {
|
|
46
48
|
field: string;
|
|
47
49
|
type: 'text';
|
|
48
|
-
operator: Extract<Operator, 'contains' | 'notContains' | 'startsWith' | 'endsWith' | 'blank' | 'notBlank'> | undefined;
|
|
50
|
+
operator: Extract<Operator, 'contains' | 'notContains' | 'startsWith' | 'endsWith' | 'blank' | 'notBlank' | 'isAnyOf' | 'isNoneOf'> | undefined;
|
|
49
51
|
values: string[];
|
|
50
52
|
};
|
|
51
53
|
export type Filter = SelectFilter | NumberFilter | TextFilter;
|
|
@@ -46,6 +46,19 @@ const TypeToOperatorOptions = {
|
|
|
46
46
|
{ label: "has any value", value: "notBlank" }
|
|
47
47
|
]
|
|
48
48
|
};
|
|
49
|
+
const MultiValueTextOperatorOptions = [
|
|
50
|
+
{ label: "is any of", value: "isAnyOf" },
|
|
51
|
+
{ label: "is not any of", value: "isNoneOf" }
|
|
52
|
+
];
|
|
53
|
+
const operatorOptionsFor = (config) => {
|
|
54
|
+
const defaults = TypeToOperatorOptions[config.type];
|
|
55
|
+
if (!config.operators) return defaults;
|
|
56
|
+
const known = config.type === "text" ? [...defaults, ...MultiValueTextOperatorOptions] : defaults;
|
|
57
|
+
return config.operators.flatMap((operator) => {
|
|
58
|
+
const option = known.find((candidate) => candidate.value === operator);
|
|
59
|
+
return option ? [option] : [];
|
|
60
|
+
});
|
|
61
|
+
};
|
|
49
62
|
const buildValueInputConfig = (config, operator) => {
|
|
50
63
|
if (config.type === "select") {
|
|
51
64
|
if (operator === "blank" || operator === "notBlank") return;
|
|
@@ -65,29 +78,31 @@ const buildValueInputConfig = (config, operator) => {
|
|
|
65
78
|
if (config.type === "text") {
|
|
66
79
|
if (operator === "blank" || operator === "notBlank") return;
|
|
67
80
|
return {
|
|
68
|
-
type: "text"
|
|
81
|
+
type: "text",
|
|
82
|
+
multiValue: filterHelpers.isMultiValueTextOperator(operator),
|
|
83
|
+
maxValues: config.maxValues
|
|
69
84
|
};
|
|
70
85
|
}
|
|
71
86
|
};
|
|
72
87
|
const filterFieldReducer = (state, action) => {
|
|
73
|
-
var _a, _b, _c;
|
|
88
|
+
var _a, _b, _c, _d, _e;
|
|
74
89
|
switch (action.type) {
|
|
75
90
|
case "setField": {
|
|
76
91
|
const config = state.filterConfigs.find(
|
|
77
92
|
(config2) => config2.field === action.payload
|
|
78
93
|
);
|
|
79
94
|
if (!config) return state;
|
|
80
|
-
const operatorOptions = config
|
|
95
|
+
const operatorOptions = operatorOptionsFor(config);
|
|
81
96
|
const newFilter = {
|
|
82
97
|
...state.filter,
|
|
83
98
|
type: config.type,
|
|
84
99
|
field: action.payload,
|
|
85
|
-
operator: operatorOptions[0].value,
|
|
100
|
+
operator: (_a = operatorOptions[0]) == null ? void 0 : _a.value,
|
|
86
101
|
values: []
|
|
87
102
|
};
|
|
88
103
|
const pendingFilter = {
|
|
89
104
|
...newFilter,
|
|
90
|
-
errors: filterHelpers.validateFormFilter(newFilter)
|
|
105
|
+
errors: filterHelpers.validateFormFilter(newFilter, config)
|
|
91
106
|
};
|
|
92
107
|
return {
|
|
93
108
|
...state,
|
|
@@ -109,42 +124,59 @@ const filterFieldReducer = (state, action) => {
|
|
|
109
124
|
...state,
|
|
110
125
|
filter: {
|
|
111
126
|
...pendingFilter,
|
|
112
|
-
errors: filterHelpers.validateFormFilter(pendingFilter)
|
|
127
|
+
errors: filterHelpers.validateFormFilter(pendingFilter, state.config)
|
|
113
128
|
},
|
|
114
129
|
valueInputConfig: buildValueInputConfig(state.config, action.payload)
|
|
115
130
|
};
|
|
116
131
|
}
|
|
117
132
|
case "setValues": {
|
|
118
133
|
if (!state.filter) return state;
|
|
119
|
-
const values = state.filter.type === "number" ? action.payload.map((value) => Number(value)) :
|
|
134
|
+
const values = state.filter.type === "number" ? action.payload.map((value) => Number(value)) : filterHelpers.isMultiValueTextOperator(state.filter.operator) ? (
|
|
135
|
+
// Normalized on the way into state, not just for display —
|
|
136
|
+
// validation counts and `onSubmit` emit exactly these chips.
|
|
137
|
+
filterHelpers.normalizeMultiValues(action.payload)
|
|
138
|
+
) : action.payload.filter(
|
|
120
139
|
(value) => value !== null && value !== void 0
|
|
121
140
|
);
|
|
122
141
|
const pendingFilter = {
|
|
123
142
|
...state.filter,
|
|
124
143
|
values
|
|
125
144
|
};
|
|
126
|
-
pendingFilter.errors = filterHelpers.validateFormFilter(pendingFilter);
|
|
145
|
+
pendingFilter.errors = filterHelpers.validateFormFilter(pendingFilter, state.config);
|
|
127
146
|
return {
|
|
128
147
|
...state,
|
|
129
148
|
filter: pendingFilter
|
|
130
149
|
};
|
|
131
150
|
}
|
|
132
151
|
case "initialize": {
|
|
133
|
-
const field = ((
|
|
152
|
+
const field = ((_b = state.filter) == null ? void 0 : _b.field) || ((_c = state.filterConfigs.find((config2) => !config2.disabled)) == null ? void 0 : _c.field);
|
|
134
153
|
const config = state.filterConfigs.find(
|
|
135
154
|
(config2) => config2.field === field
|
|
136
155
|
);
|
|
137
156
|
if (!config) return state;
|
|
138
|
-
const operatorOptions = config
|
|
139
|
-
const
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
157
|
+
const operatorOptions = operatorOptionsFor(config);
|
|
158
|
+
const persisted = (_d = state.filter) == null ? void 0 : _d.operator;
|
|
159
|
+
const persistedAllowed = persisted !== void 0 && operatorOptions.some((option) => option.value === persisted);
|
|
160
|
+
const operator = persistedAllowed ? persisted : (_e = operatorOptions[0]) == null ? void 0 : _e.value;
|
|
161
|
+
let filter;
|
|
162
|
+
if (!state.filter) {
|
|
163
|
+
filter = {
|
|
164
|
+
field,
|
|
165
|
+
type: config.type,
|
|
166
|
+
operator,
|
|
167
|
+
values: []
|
|
168
|
+
};
|
|
169
|
+
} else if (!persistedAllowed) {
|
|
170
|
+
filter = { ...state.filter, operator, values: [] };
|
|
171
|
+
} else if (filterHelpers.isMultiValueTextOperator(operator)) {
|
|
172
|
+
filter = {
|
|
173
|
+
...state.filter,
|
|
174
|
+
values: filterHelpers.normalizeMultiValues(state.filter.values)
|
|
175
|
+
};
|
|
176
|
+
} else {
|
|
177
|
+
filter = { ...state.filter };
|
|
178
|
+
}
|
|
179
|
+
filter.errors = filterHelpers.validateFormFilter(filter, config);
|
|
148
180
|
return {
|
|
149
181
|
...state,
|
|
150
182
|
config,
|
|
@@ -205,6 +237,9 @@ const useFilterField = ({
|
|
|
205
237
|
handleValuesChange
|
|
206
238
|
};
|
|
207
239
|
};
|
|
240
|
+
exports.isMultiValueTextOperator = filterHelpers.isMultiValueTextOperator;
|
|
241
|
+
exports.MultiValueTextOperatorOptions = MultiValueTextOperatorOptions;
|
|
208
242
|
exports.TypeToOperatorOptions = TypeToOperatorOptions;
|
|
243
|
+
exports.operatorOptionsFor = operatorOptionsFor;
|
|
209
244
|
exports.useFilterField = useFilterField;
|
|
210
245
|
//# sourceMappingURL=useFilterField.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useFilterField.cjs","sources":["../../../../src/components/filterform/useFilterField.tsx"],"sourcesContent":["import * as React from 'react'\nimport {\n FilterConfig,\n FilterType,\n FormFilter,\n NumberOptionsType,\n Operator,\n SelectOptionsType,\n} from './types'\nimport { validateFormFilter } from './filterHelpers'\nexport type UseFilterFieldInputs = {\n filterConfigs: FilterConfig[]\n filter?: FormFilter\n}\n\ntype UseFilterFieldOutputs = {\n filter: FormFilter | undefined\n config: FilterConfig | undefined\n operatorOptions: { label: string; value: string }[]\n valueInputConfig: ValueInputConfig | undefined\n handleFieldChange: (value: string) => void\n handleOperatorChange: (value: string) => void\n handleValuesChange: (value: (string | number | undefined | null)[]) => void\n}\n\nexport const TypeToOperatorOptions: Record<\n FilterType,\n { label: string; value: Operator }[]\n> = {\n select: [\n { label: 'is any of', value: 'equals' },\n { label: 'is not any of', value: 'notEqual' },\n { label: 'has no value', value: 'blank' },\n { label: 'has any value', value: 'notBlank' },\n ],\n number: [\n { label: 'is', value: 'equals' },\n { label: 'is not', value: 'notEqual' },\n { label: 'is greater than', value: 'greaterThan' },\n { label: 'is greater than or equal to', value: 'greaterThanOrEqual' },\n { label: 'is less than', value: 'lessThan' },\n { label: 'is less than or equal to', value: 'lessThanOrEqual' },\n { label: 'has no value', value: 'blank' },\n { label: 'has any value', value: 'notBlank' },\n { label: 'is between', value: 'inRange' },\n ],\n text: [\n { label: 'contains', value: 'contains' },\n { label: 'does not contain', value: 'notContains' },\n { label: 'starts with', value: 'startsWith' },\n { label: 'ends with', value: 'endsWith' },\n { label: 'has no value', value: 'blank' },\n { label: 'has any value', value: 'notBlank' },\n ],\n}\n\ntype SelectValueInputConfig = {\n type: 'select'\n valueOptions: { label: string; value: string }[]\n}\n\ntype NumberValueInputConfig = {\n type: 'number'\n inputCount: number\n numberOptions?: { max: number; min: number; step: number }\n}\n\ntype TextValueInputConfig = {\n type: 'text'\n}\n\nexport type ValueInputConfig =\n SelectValueInputConfig | NumberValueInputConfig | TextValueInputConfig\n\ntype FilterFieldState = {\n filter: FormFilter | undefined\n filterConfigs: FilterConfig[]\n config: FilterConfig | undefined\n operatorOptions: { label: string; value: string }[]\n valueInputConfig: ValueInputConfig | undefined\n}\n\ntype Action = {\n type: 'setField' | 'setOperator' | 'initialize'\n payload: string | undefined\n}\ntype SetValuesAction = {\n type: 'setValues'\n payload: (string | number | undefined | null)[]\n}\n\nconst buildValueInputConfig = (\n config: FilterConfig,\n operator: string\n): ValueInputConfig | undefined => {\n if (config.type === 'select') {\n if (operator === 'blank' || operator === 'notBlank') return\n\n return {\n type: 'select',\n valueOptions: (config.options as SelectOptionsType) || [],\n }\n }\n\n if (config.type === 'number') {\n if (operator === 'blank' || operator === 'notBlank') return\n\n return {\n type: 'number',\n inputCount: operator === 'inRange' ? 2 : 1,\n numberOptions: config.options as NumberOptionsType,\n }\n }\n\n if (config.type === 'text') {\n if (operator === 'blank' || operator === 'notBlank') return\n\n return {\n type: 'text',\n }\n }\n}\n\nconst filterFieldReducer = (\n state: FilterFieldState,\n action: Action | SetValuesAction\n) => {\n switch (action.type) {\n case 'setField': {\n const config = state.filterConfigs.find(\n (config) => config.field === action.payload\n )\n\n if (!config) return state\n\n const operatorOptions = config ? TypeToOperatorOptions[config.type] : []\n\n const newFilter = {\n ...state.filter,\n type: config.type,\n field: action.payload,\n operator: operatorOptions[0].value,\n values: [],\n } as FormFilter\n\n const pendingFilter = {\n ...newFilter,\n errors: validateFormFilter(newFilter),\n }\n\n return {\n ...state,\n filter: pendingFilter,\n config,\n operatorOptions,\n valueInputConfig:\n operatorOptions.length > 0\n ? buildValueInputConfig(config, operatorOptions[0].value)\n : undefined,\n }\n }\n case 'setOperator': {\n if (!state.config) return state\n if (!action.payload) return state\n\n const pendingFilter = {\n ...state.filter,\n operator: action.payload,\n values: [],\n } as FormFilter\n\n return {\n ...state,\n filter: {\n ...pendingFilter,\n errors: validateFormFilter(pendingFilter),\n },\n valueInputConfig: buildValueInputConfig(state.config, action.payload),\n }\n }\n case 'setValues': {\n if (!state.filter) return state\n\n const values =\n state.filter.type === 'number'\n ? action.payload.map((value) => Number(value))\n : (action.payload.filter(\n (value) => value !== null && value !== undefined\n ) as string[])\n\n const pendingFilter = {\n ...state.filter,\n values: values,\n }\n\n pendingFilter.errors = validateFormFilter(pendingFilter)\n\n return {\n ...state,\n filter: pendingFilter,\n }\n }\n case 'initialize': {\n const field =\n state.filter?.field ||\n state.filterConfigs.find((config) => !config.disabled)?.field\n const config = state.filterConfigs.find(\n (config) => config.field === field\n )\n\n if (!config) return state\n\n const operatorOptions = config ? TypeToOperatorOptions[config.type] : []\n\n const operator = state.filter?.operator || operatorOptions[0].value\n\n const filter =\n state.filter ??\n ({\n field,\n type: config.type,\n operator,\n values: [],\n } as FormFilter)\n\n const errors = validateFormFilter(filter)\n filter.errors = errors\n\n return {\n ...state,\n config,\n filter,\n operatorOptions,\n valueInputConfig:\n operatorOptions.length > 0\n ? buildValueInputConfig(config, operator)\n : undefined,\n }\n }\n default:\n return state\n }\n}\n\nexport const useFilterField = ({\n filterConfigs,\n filter: initialFilter,\n}: UseFilterFieldInputs): UseFilterFieldOutputs => {\n const [{ filter, config, operatorOptions, valueInputConfig }, dispatch] =\n React.useReducer(\n filterFieldReducer,\n {\n filter: initialFilter,\n filterConfigs,\n config: undefined,\n operatorOptions: [],\n valueInputConfig: undefined,\n },\n (state) => {\n return filterFieldReducer(state, {\n type: 'initialize',\n payload: undefined,\n })\n }\n )\n\n const handleFieldChange = (value: string) => {\n dispatch({\n type: 'setField',\n payload: value,\n })\n }\n\n const handleOperatorChange = (value: string) => {\n dispatch({\n type: 'setOperator',\n payload: value,\n })\n }\n\n const handleValuesChange = (\n value: (string | number | undefined | null)[]\n ) => {\n dispatch({\n type: 'setValues',\n payload: value,\n })\n }\n\n return {\n filter,\n config,\n operatorOptions,\n valueInputConfig,\n handleFieldChange,\n handleOperatorChange,\n handleValuesChange,\n }\n}\n"],"names":["config","validateFormFilter","React"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useFilterField.cjs","sources":["../../../../src/components/filterform/useFilterField.tsx"],"sourcesContent":["import * as React from 'react'\nimport {\n FilterConfig,\n FilterType,\n FormFilter,\n NumberOptionsType,\n Operator,\n SelectOptionsType,\n} from './types'\nimport {\n isMultiValueTextOperator,\n normalizeMultiValues,\n validateFormFilter,\n} from './filterHelpers'\nexport { isMultiValueTextOperator } from './filterHelpers'\nexport type UseFilterFieldInputs = {\n filterConfigs: FilterConfig[]\n filter?: FormFilter\n}\n\ntype UseFilterFieldOutputs = {\n filter: FormFilter | undefined\n config: FilterConfig | undefined\n operatorOptions: { label: string; value: string }[]\n valueInputConfig: ValueInputConfig | undefined\n handleFieldChange: (value: string) => void\n handleOperatorChange: (value: string) => void\n handleValuesChange: (value: (string | number | undefined | null)[]) => void\n}\n\nexport const TypeToOperatorOptions: Record<\n FilterType,\n { label: string; value: Operator }[]\n> = {\n select: [\n { label: 'is any of', value: 'equals' },\n { label: 'is not any of', value: 'notEqual' },\n { label: 'has no value', value: 'blank' },\n { label: 'has any value', value: 'notBlank' },\n ],\n number: [\n { label: 'is', value: 'equals' },\n { label: 'is not', value: 'notEqual' },\n { label: 'is greater than', value: 'greaterThan' },\n { label: 'is greater than or equal to', value: 'greaterThanOrEqual' },\n { label: 'is less than', value: 'lessThan' },\n { label: 'is less than or equal to', value: 'lessThanOrEqual' },\n { label: 'has no value', value: 'blank' },\n { label: 'has any value', value: 'notBlank' },\n { label: 'is between', value: 'inRange' },\n ],\n text: [\n { label: 'contains', value: 'contains' },\n { label: 'does not contain', value: 'notContains' },\n { label: 'starts with', value: 'startsWith' },\n { label: 'ends with', value: 'endsWith' },\n { label: 'has no value', value: 'blank' },\n { label: 'has any value', value: 'notBlank' },\n ],\n}\n\n// Multi-value exact-match operators for text fields. Deliberately NOT part of\n// `TypeToOperatorOptions.text`: a field only offers them when its\n// `FilterConfig.operators` lists them, so every existing consumer keeps the\n// operator list it has today (see the `operators` doc in types.ts).\nexport const MultiValueTextOperatorOptions: {\n label: string\n value: Operator\n}[] = [\n { label: 'is any of', value: 'isAnyOf' },\n { label: 'is not any of', value: 'isNoneOf' },\n]\n\n/**\n * Operator options a field offers: its type's default list unless the config\n * names its own (`operators`), in which case that list — in the given order,\n * dropping anything the type can't express. `operators` may therefore both\n * narrow the defaults and add the opt-in multi-value text operators.\n */\nexport const operatorOptionsFor = (\n config: FilterConfig\n): { label: string; value: Operator }[] => {\n const defaults = TypeToOperatorOptions[config.type]\n if (!config.operators) return defaults\n const known =\n config.type === 'text'\n ? [...defaults, ...MultiValueTextOperatorOptions]\n : defaults\n return config.operators.flatMap((operator) => {\n const option = known.find((candidate) => candidate.value === operator)\n return option ? [option] : []\n })\n}\n\ntype SelectValueInputConfig = {\n type: 'select'\n valueOptions: { label: string; value: string }[]\n}\n\ntype NumberValueInputConfig = {\n type: 'number'\n inputCount: number\n numberOptions?: { max: number; min: number; step: number }\n}\n\ntype TextValueInputConfig = {\n type: 'text'\n // True for `isAnyOf` / `isNoneOf`: the value input is a chips list rather\n // than a single text box. `maxValues` is the config's cap, if any.\n multiValue?: boolean\n maxValues?: number\n}\n\nexport type ValueInputConfig =\n SelectValueInputConfig | NumberValueInputConfig | TextValueInputConfig\n\ntype FilterFieldState = {\n filter: FormFilter | undefined\n filterConfigs: FilterConfig[]\n config: FilterConfig | undefined\n operatorOptions: { label: string; value: string }[]\n valueInputConfig: ValueInputConfig | undefined\n}\n\ntype Action = {\n type: 'setField' | 'setOperator' | 'initialize'\n payload: string | undefined\n}\ntype SetValuesAction = {\n type: 'setValues'\n payload: (string | number | undefined | null)[]\n}\n\nconst buildValueInputConfig = (\n config: FilterConfig,\n operator: string\n): ValueInputConfig | undefined => {\n if (config.type === 'select') {\n if (operator === 'blank' || operator === 'notBlank') return\n\n return {\n type: 'select',\n valueOptions: (config.options as SelectOptionsType) || [],\n }\n }\n\n if (config.type === 'number') {\n if (operator === 'blank' || operator === 'notBlank') return\n\n return {\n type: 'number',\n inputCount: operator === 'inRange' ? 2 : 1,\n numberOptions: config.options as NumberOptionsType,\n }\n }\n\n if (config.type === 'text') {\n if (operator === 'blank' || operator === 'notBlank') return\n\n return {\n type: 'text',\n multiValue: isMultiValueTextOperator(operator),\n maxValues: config.maxValues,\n }\n }\n}\n\nconst filterFieldReducer = (\n state: FilterFieldState,\n action: Action | SetValuesAction\n) => {\n switch (action.type) {\n case 'setField': {\n const config = state.filterConfigs.find(\n (config) => config.field === action.payload\n )\n\n if (!config) return state\n\n const operatorOptions = operatorOptionsFor(config)\n\n const newFilter = {\n ...state.filter,\n type: config.type,\n field: action.payload,\n operator: operatorOptions[0]?.value,\n values: [],\n } as FormFilter\n\n const pendingFilter = {\n ...newFilter,\n errors: validateFormFilter(newFilter, config),\n }\n\n return {\n ...state,\n filter: pendingFilter,\n config,\n operatorOptions,\n valueInputConfig:\n operatorOptions.length > 0\n ? buildValueInputConfig(config, operatorOptions[0].value)\n : undefined,\n }\n }\n case 'setOperator': {\n if (!state.config) return state\n if (!action.payload) return state\n\n const pendingFilter = {\n ...state.filter,\n operator: action.payload,\n values: [],\n } as FormFilter\n\n return {\n ...state,\n filter: {\n ...pendingFilter,\n errors: validateFormFilter(pendingFilter, state.config),\n },\n valueInputConfig: buildValueInputConfig(state.config, action.payload),\n }\n }\n case 'setValues': {\n if (!state.filter) return state\n\n const values =\n state.filter.type === 'number'\n ? action.payload.map((value) => Number(value))\n : isMultiValueTextOperator(state.filter.operator)\n ? // Normalized on the way into state, not just for display —\n // validation counts and `onSubmit` emit exactly these chips.\n normalizeMultiValues(action.payload)\n : (action.payload.filter(\n (value) => value !== null && value !== undefined\n ) as string[])\n\n const pendingFilter = {\n ...state.filter,\n values: values,\n }\n\n pendingFilter.errors = validateFormFilter(pendingFilter, state.config)\n\n return {\n ...state,\n filter: pendingFilter,\n }\n }\n case 'initialize': {\n const field =\n state.filter?.field ||\n state.filterConfigs.find((config) => !config.disabled)?.field\n const config = state.filterConfigs.find(\n (config) => config.field === field\n )\n\n if (!config) return state\n\n const operatorOptions = operatorOptionsFor(config)\n\n // A persisted filter's operator must still be one this field offers.\n // Configs change between sessions (a field narrowed to `contains`, or\n // one that never opted into the multi-value operators), and the\n // backend may not understand a stale operator — so it falls back to\n // the field's first operator with its values cleared, rather than\n // re-submitting something the config no longer allows.\n const persisted = state.filter?.operator\n const persistedAllowed =\n persisted !== undefined &&\n operatorOptions.some((option) => option.value === persisted)\n const operator = persistedAllowed ? persisted : operatorOptions[0]?.value\n\n let filter: FormFilter\n if (!state.filter) {\n filter = {\n field,\n type: config.type,\n operator,\n values: [],\n } as FormFilter\n } else if (!persistedAllowed) {\n filter = { ...state.filter, operator, values: [] } as FormFilter\n } else if (isMultiValueTextOperator(operator)) {\n // Same normalization as `setValues`, so a persisted chip list that\n // predates it (or was hand-edited) can't show one chip and count two.\n filter = {\n ...state.filter,\n values: normalizeMultiValues(state.filter.values),\n }\n } else {\n // A copy: the incoming object is the caller's, and writing `errors`\n // onto it would hide the correction from `FilterInput`'s comparison\n // of what it was given against what it initialized to.\n filter = { ...state.filter }\n }\n\n filter.errors = validateFormFilter(filter, config)\n\n return {\n ...state,\n config,\n filter,\n operatorOptions,\n valueInputConfig:\n operatorOptions.length > 0\n ? buildValueInputConfig(config, operator)\n : undefined,\n }\n }\n default:\n return state\n }\n}\n\nexport const useFilterField = ({\n filterConfigs,\n filter: initialFilter,\n}: UseFilterFieldInputs): UseFilterFieldOutputs => {\n const [{ filter, config, operatorOptions, valueInputConfig }, dispatch] =\n React.useReducer(\n filterFieldReducer,\n {\n filter: initialFilter,\n filterConfigs,\n config: undefined,\n operatorOptions: [],\n valueInputConfig: undefined,\n },\n (state) => {\n return filterFieldReducer(state, {\n type: 'initialize',\n payload: undefined,\n })\n }\n )\n\n const handleFieldChange = (value: string) => {\n dispatch({\n type: 'setField',\n payload: value,\n })\n }\n\n const handleOperatorChange = (value: string) => {\n dispatch({\n type: 'setOperator',\n payload: value,\n })\n }\n\n const handleValuesChange = (\n value: (string | number | undefined | null)[]\n ) => {\n dispatch({\n type: 'setValues',\n payload: value,\n })\n }\n\n return {\n filter,\n config,\n operatorOptions,\n valueInputConfig,\n handleFieldChange,\n handleOperatorChange,\n handleValuesChange,\n }\n}\n"],"names":["isMultiValueTextOperator","config","validateFormFilter","normalizeMultiValues","React"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FilterConfig, FilterType, FormFilter, Operator } from './types.js';
|
|
2
|
+
export { isMultiValueTextOperator } from './filterHelpers.js';
|
|
2
3
|
export type UseFilterFieldInputs = {
|
|
3
4
|
filterConfigs: FilterConfig[];
|
|
4
5
|
filter?: FormFilter;
|
|
@@ -19,6 +20,20 @@ export declare const TypeToOperatorOptions: Record<FilterType, {
|
|
|
19
20
|
label: string;
|
|
20
21
|
value: Operator;
|
|
21
22
|
}[]>;
|
|
23
|
+
export declare const MultiValueTextOperatorOptions: {
|
|
24
|
+
label: string;
|
|
25
|
+
value: Operator;
|
|
26
|
+
}[];
|
|
27
|
+
/**
|
|
28
|
+
* Operator options a field offers: its type's default list unless the config
|
|
29
|
+
* names its own (`operators`), in which case that list — in the given order,
|
|
30
|
+
* dropping anything the type can't express. `operators` may therefore both
|
|
31
|
+
* narrow the defaults and add the opt-in multi-value text operators.
|
|
32
|
+
*/
|
|
33
|
+
export declare const operatorOptionsFor: (config: FilterConfig) => {
|
|
34
|
+
label: string;
|
|
35
|
+
value: Operator;
|
|
36
|
+
}[];
|
|
22
37
|
type SelectValueInputConfig = {
|
|
23
38
|
type: 'select';
|
|
24
39
|
valueOptions: {
|
|
@@ -37,7 +52,8 @@ type NumberValueInputConfig = {
|
|
|
37
52
|
};
|
|
38
53
|
type TextValueInputConfig = {
|
|
39
54
|
type: 'text';
|
|
55
|
+
multiValue?: boolean;
|
|
56
|
+
maxValues?: number;
|
|
40
57
|
};
|
|
41
58
|
export type ValueInputConfig = SelectValueInputConfig | NumberValueInputConfig | TextValueInputConfig;
|
|
42
59
|
export declare const useFilterField: ({ filterConfigs, filter: initialFilter, }: UseFilterFieldInputs) => UseFilterFieldOutputs;
|
|
43
|
-
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { validateFormFilter } from "./filterHelpers.js";
|
|
2
|
+
import { isMultiValueTextOperator, normalizeMultiValues, validateFormFilter } from "./filterHelpers.js";
|
|
3
3
|
const TypeToOperatorOptions = {
|
|
4
4
|
select: [
|
|
5
5
|
{ label: "is any of", value: "equals" },
|
|
@@ -27,6 +27,19 @@ const TypeToOperatorOptions = {
|
|
|
27
27
|
{ label: "has any value", value: "notBlank" }
|
|
28
28
|
]
|
|
29
29
|
};
|
|
30
|
+
const MultiValueTextOperatorOptions = [
|
|
31
|
+
{ label: "is any of", value: "isAnyOf" },
|
|
32
|
+
{ label: "is not any of", value: "isNoneOf" }
|
|
33
|
+
];
|
|
34
|
+
const operatorOptionsFor = (config) => {
|
|
35
|
+
const defaults = TypeToOperatorOptions[config.type];
|
|
36
|
+
if (!config.operators) return defaults;
|
|
37
|
+
const known = config.type === "text" ? [...defaults, ...MultiValueTextOperatorOptions] : defaults;
|
|
38
|
+
return config.operators.flatMap((operator) => {
|
|
39
|
+
const option = known.find((candidate) => candidate.value === operator);
|
|
40
|
+
return option ? [option] : [];
|
|
41
|
+
});
|
|
42
|
+
};
|
|
30
43
|
const buildValueInputConfig = (config, operator) => {
|
|
31
44
|
if (config.type === "select") {
|
|
32
45
|
if (operator === "blank" || operator === "notBlank") return;
|
|
@@ -46,29 +59,31 @@ const buildValueInputConfig = (config, operator) => {
|
|
|
46
59
|
if (config.type === "text") {
|
|
47
60
|
if (operator === "blank" || operator === "notBlank") return;
|
|
48
61
|
return {
|
|
49
|
-
type: "text"
|
|
62
|
+
type: "text",
|
|
63
|
+
multiValue: isMultiValueTextOperator(operator),
|
|
64
|
+
maxValues: config.maxValues
|
|
50
65
|
};
|
|
51
66
|
}
|
|
52
67
|
};
|
|
53
68
|
const filterFieldReducer = (state, action) => {
|
|
54
|
-
var _a, _b, _c;
|
|
69
|
+
var _a, _b, _c, _d, _e;
|
|
55
70
|
switch (action.type) {
|
|
56
71
|
case "setField": {
|
|
57
72
|
const config = state.filterConfigs.find(
|
|
58
73
|
(config2) => config2.field === action.payload
|
|
59
74
|
);
|
|
60
75
|
if (!config) return state;
|
|
61
|
-
const operatorOptions = config
|
|
76
|
+
const operatorOptions = operatorOptionsFor(config);
|
|
62
77
|
const newFilter = {
|
|
63
78
|
...state.filter,
|
|
64
79
|
type: config.type,
|
|
65
80
|
field: action.payload,
|
|
66
|
-
operator: operatorOptions[0].value,
|
|
81
|
+
operator: (_a = operatorOptions[0]) == null ? void 0 : _a.value,
|
|
67
82
|
values: []
|
|
68
83
|
};
|
|
69
84
|
const pendingFilter = {
|
|
70
85
|
...newFilter,
|
|
71
|
-
errors: validateFormFilter(newFilter)
|
|
86
|
+
errors: validateFormFilter(newFilter, config)
|
|
72
87
|
};
|
|
73
88
|
return {
|
|
74
89
|
...state,
|
|
@@ -90,42 +105,59 @@ const filterFieldReducer = (state, action) => {
|
|
|
90
105
|
...state,
|
|
91
106
|
filter: {
|
|
92
107
|
...pendingFilter,
|
|
93
|
-
errors: validateFormFilter(pendingFilter)
|
|
108
|
+
errors: validateFormFilter(pendingFilter, state.config)
|
|
94
109
|
},
|
|
95
110
|
valueInputConfig: buildValueInputConfig(state.config, action.payload)
|
|
96
111
|
};
|
|
97
112
|
}
|
|
98
113
|
case "setValues": {
|
|
99
114
|
if (!state.filter) return state;
|
|
100
|
-
const values = state.filter.type === "number" ? action.payload.map((value) => Number(value)) :
|
|
115
|
+
const values = state.filter.type === "number" ? action.payload.map((value) => Number(value)) : isMultiValueTextOperator(state.filter.operator) ? (
|
|
116
|
+
// Normalized on the way into state, not just for display —
|
|
117
|
+
// validation counts and `onSubmit` emit exactly these chips.
|
|
118
|
+
normalizeMultiValues(action.payload)
|
|
119
|
+
) : action.payload.filter(
|
|
101
120
|
(value) => value !== null && value !== void 0
|
|
102
121
|
);
|
|
103
122
|
const pendingFilter = {
|
|
104
123
|
...state.filter,
|
|
105
124
|
values
|
|
106
125
|
};
|
|
107
|
-
pendingFilter.errors = validateFormFilter(pendingFilter);
|
|
126
|
+
pendingFilter.errors = validateFormFilter(pendingFilter, state.config);
|
|
108
127
|
return {
|
|
109
128
|
...state,
|
|
110
129
|
filter: pendingFilter
|
|
111
130
|
};
|
|
112
131
|
}
|
|
113
132
|
case "initialize": {
|
|
114
|
-
const field = ((
|
|
133
|
+
const field = ((_b = state.filter) == null ? void 0 : _b.field) || ((_c = state.filterConfigs.find((config2) => !config2.disabled)) == null ? void 0 : _c.field);
|
|
115
134
|
const config = state.filterConfigs.find(
|
|
116
135
|
(config2) => config2.field === field
|
|
117
136
|
);
|
|
118
137
|
if (!config) return state;
|
|
119
|
-
const operatorOptions = config
|
|
120
|
-
const
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
138
|
+
const operatorOptions = operatorOptionsFor(config);
|
|
139
|
+
const persisted = (_d = state.filter) == null ? void 0 : _d.operator;
|
|
140
|
+
const persistedAllowed = persisted !== void 0 && operatorOptions.some((option) => option.value === persisted);
|
|
141
|
+
const operator = persistedAllowed ? persisted : (_e = operatorOptions[0]) == null ? void 0 : _e.value;
|
|
142
|
+
let filter;
|
|
143
|
+
if (!state.filter) {
|
|
144
|
+
filter = {
|
|
145
|
+
field,
|
|
146
|
+
type: config.type,
|
|
147
|
+
operator,
|
|
148
|
+
values: []
|
|
149
|
+
};
|
|
150
|
+
} else if (!persistedAllowed) {
|
|
151
|
+
filter = { ...state.filter, operator, values: [] };
|
|
152
|
+
} else if (isMultiValueTextOperator(operator)) {
|
|
153
|
+
filter = {
|
|
154
|
+
...state.filter,
|
|
155
|
+
values: normalizeMultiValues(state.filter.values)
|
|
156
|
+
};
|
|
157
|
+
} else {
|
|
158
|
+
filter = { ...state.filter };
|
|
159
|
+
}
|
|
160
|
+
filter.errors = validateFormFilter(filter, config);
|
|
129
161
|
return {
|
|
130
162
|
...state,
|
|
131
163
|
config,
|
|
@@ -187,7 +219,10 @@ const useFilterField = ({
|
|
|
187
219
|
};
|
|
188
220
|
};
|
|
189
221
|
export {
|
|
222
|
+
MultiValueTextOperatorOptions,
|
|
190
223
|
TypeToOperatorOptions,
|
|
224
|
+
isMultiValueTextOperator,
|
|
225
|
+
operatorOptionsFor,
|
|
191
226
|
useFilterField
|
|
192
227
|
};
|
|
193
228
|
//# sourceMappingURL=useFilterField.js.map
|