@wallarm-org/design-system 0.66.3 → 0.67.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/FilterInput/FilterInputContext/types.d.ts +6 -0
- package/dist/components/FilterInput/FilterInputContext/useFilterInputContextValue.d.ts +2 -0
- package/dist/components/FilterInput/FilterInputContext/useFilterInputContextValue.js +4 -0
- package/dist/components/FilterInput/FilterInputErrors/parseFilterInputErrors.js +11 -2
- package/dist/components/FilterInput/FilterInputField/ChipsWithGaps.js +4 -2
- package/dist/components/FilterInput/FilterInputField/FilterInputChip/FilterInputChip.d.ts +7 -1
- package/dist/components/FilterInput/FilterInputField/FilterInputChip/FilterInputChip.js +44 -6
- package/dist/components/FilterInput/FilterInputField/FilterInputChip/PairSeparator.d.ts +3 -0
- package/dist/components/FilterInput/FilterInputField/FilterInputChip/PairSeparator.js +16 -0
- package/dist/components/FilterInput/FilterInputField/FilterInputChip/context/EditingContext.d.ts +2 -0
- package/dist/components/FilterInput/FilterInputField/FilterInputChip/index.d.ts +3 -2
- package/dist/components/FilterInput/FilterInputField/FilterInputChip/index.js +3 -2
- package/dist/components/FilterInput/FilterInputField/FilterInputChip/segmentVariant.d.ts +2 -0
- package/dist/components/FilterInput/FilterInputField/FilterInputChip/segmentVariant.js +2 -1
- package/dist/components/FilterInput/FilterInputField/FilterInputField.js +3 -1
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/lib/deriveAutocompleteValues.d.ts +8 -1
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/lib/deriveAutocompleteValues.js +22 -3
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useAutocompleteState.d.ts +10 -0
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useAutocompleteState.js +6 -0
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useChipEditing.d.ts +2 -0
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useChipEditing.js +31 -0
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useFilterInputAutocomplete.d.ts +2 -0
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useFilterInputAutocomplete.js +14 -3
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useMenuFlow/types.d.ts +9 -0
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useMenuFlow/useOperatorFlow.js +8 -5
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useMenuFlow/useValueFlow.d.ts +1 -1
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useMenuFlow/useValueFlow.js +77 -13
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useResetState.d.ts +3 -1
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useResetState.js +5 -1
- package/dist/components/FilterInput/hooks/useFilterInputExpression/buildChips.js +25 -1
- package/dist/components/FilterInput/hooks/useFilterInputExpression/expression.d.ts +9 -3
- package/dist/components/FilterInput/hooks/useFilterInputExpression/expression.js +75 -9
- package/dist/components/FilterInput/hooks/useFilterInputExpression/useFilterInputExpression.d.ts +1 -1
- package/dist/components/FilterInput/hooks/useFilterInputExpression/useFilterInputExpression.js +49 -14
- package/dist/components/FilterInput/types.d.ts +25 -1
- package/dist/metadata/components.json +8 -2
- package/package.json +1 -1
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { RefObject } from 'react';
|
|
2
2
|
import type { ChipSegment } from '../../../FilterInputField/FilterInputChip';
|
|
3
3
|
import type { Condition, FieldMetadata, FilterOperator, MenuState, UpsertCondition } from '../../../types';
|
|
4
|
+
import type { BuildingBase } from '../useAutocompleteState';
|
|
4
5
|
export interface MenuFlowDeps {
|
|
5
6
|
editing: {
|
|
6
7
|
editingChipId: string | null;
|
|
7
8
|
editingSegment: ChipSegment | null;
|
|
9
|
+
/** Which triplet is being edited: 0 = base, 1 = paired second. */
|
|
10
|
+
editingSide: 0 | 1;
|
|
8
11
|
/** Pre-derived chipId === null && segment !== null marker — single source
|
|
9
12
|
* of truth, avoids re-deriving in every consumer. */
|
|
10
13
|
isBuildingEdit: boolean;
|
|
@@ -36,6 +39,12 @@ export interface MenuFlowDeps {
|
|
|
36
39
|
setInputText: (text: string) => void;
|
|
37
40
|
setMenuState: (state: MenuState) => void;
|
|
38
41
|
setBuildingMultiValue: (val: string | undefined) => void;
|
|
42
|
+
/** Which triplet is being built: 0 = base, 1 = paired second. */
|
|
43
|
+
buildingSide: 0 | 1;
|
|
44
|
+
setBuildingSide: (side: 0 | 1) => void;
|
|
45
|
+
/** Base triplet stashed while building a paired chip's second value. */
|
|
46
|
+
buildingBase: BuildingBase | null;
|
|
47
|
+
setBuildingBase: (base: BuildingBase | null) => void;
|
|
39
48
|
}
|
|
40
49
|
/** Shared shape: each sub-hook receives the same deps plus a ref to `conditions`
|
|
41
50
|
* that stays fresh without forcing callbacks to recreate on every keystroke. */
|
package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useMenuFlow/useOperatorFlow.js
CHANGED
|
@@ -2,7 +2,7 @@ import { useCallback } from "react";
|
|
|
2
2
|
import { SEGMENT_VARIANT } from "../../../FilterInputField/FilterInputChip/index.js";
|
|
3
3
|
import { OPERATOR_SYMBOLS, chipIdToConditionIndex, getFieldOperators, getOperatorFromLabel, isNoValueOperator, isOperatorAllowedForField, isValueShapeCompatible } from "../../../lib/index.js";
|
|
4
4
|
const useOperatorFlow = ({ editing, selectedField, selectedOperator, insertIndex, upsertCondition, conditionsRef, resetState, setSelectedOperator, setMenuState, setBuildingMultiValue, setInputText })=>{
|
|
5
|
-
const { editingChipId, editingSegment, isBuildingEdit, setEditingSegment, setSegmentFilterText, clearEditing } = editing;
|
|
5
|
+
const { editingChipId, editingSegment, editingSide, isBuildingEdit, setEditingSegment, setSegmentFilterText, clearEditing } = editing;
|
|
6
6
|
const handleOperatorSelect = useCallback((operator)=>{
|
|
7
7
|
if (!selectedField) return;
|
|
8
8
|
if (isBuildingEdit && editingSegment === SEGMENT_VARIANT.operator) {
|
|
@@ -20,7 +20,7 @@ const useOperatorFlow = ({ editing, selectedField, selectedOperator, insertIndex
|
|
|
20
20
|
}
|
|
21
21
|
if (isNoValueOperator(operator)) {
|
|
22
22
|
const isEditing = !!editingChipId;
|
|
23
|
-
upsertCondition(selectedField, operator, null, editingChipId, isEditing ? void 0 : insertIndex);
|
|
23
|
+
upsertCondition(selectedField, operator, null, editingChipId, isEditing ? void 0 : insertIndex, void 0, void 0, editingSide);
|
|
24
24
|
resetState(!isEditing);
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
@@ -28,13 +28,15 @@ const useOperatorFlow = ({ editing, selectedField, selectedOperator, insertIndex
|
|
|
28
28
|
const idx = chipIdToConditionIndex(editingChipId);
|
|
29
29
|
const condition = null !== idx ? conditionsRef.current[idx] : null;
|
|
30
30
|
if (condition) {
|
|
31
|
-
const
|
|
31
|
+
const currentValue = 1 === editingSide ? condition.pair?.value : condition.value;
|
|
32
|
+
const currentDateOrigin = 1 === editingSide ? condition.pair?.dateOrigin : condition.dateOrigin;
|
|
33
|
+
const hasValue = null !== currentValue && '' !== currentValue && void 0 !== currentValue;
|
|
32
34
|
if (hasValue) {
|
|
33
|
-
upsertCondition(selectedField, operator,
|
|
35
|
+
upsertCondition(selectedField, operator, currentValue, editingChipId, void 0, void 0, currentDateOrigin, editingSide);
|
|
34
36
|
resetState();
|
|
35
37
|
return;
|
|
36
38
|
}
|
|
37
|
-
upsertCondition(selectedField, operator, null, editingChipId);
|
|
39
|
+
upsertCondition(selectedField, operator, 1 === editingSide ? '' : null, editingChipId, void 0, void 0, void 0, editingSide);
|
|
38
40
|
setEditingSegment(SEGMENT_VARIANT.value);
|
|
39
41
|
setSegmentFilterText('');
|
|
40
42
|
}
|
|
@@ -45,6 +47,7 @@ const useOperatorFlow = ({ editing, selectedField, selectedOperator, insertIndex
|
|
|
45
47
|
}, [
|
|
46
48
|
editingChipId,
|
|
47
49
|
editingSegment,
|
|
50
|
+
editingSide,
|
|
48
51
|
isBuildingEdit,
|
|
49
52
|
setEditingSegment,
|
|
50
53
|
setSegmentFilterText,
|
package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useMenuFlow/useValueFlow.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { MenuFlowInternalDeps } from './types';
|
|
|
4
4
|
* keyboard-typed custom-value commit (which routes through the field-type
|
|
5
5
|
* specific resolver). Also handles multi-select preview/toggle plumbing.
|
|
6
6
|
*/
|
|
7
|
-
export declare const useValueFlow: ({ editing, selectedField, selectedOperator, insertIndex, upsertCondition, conditionsRef, resetState, dateRange, setBuildingMultiValue, setInputText, }: MenuFlowInternalDeps) => {
|
|
7
|
+
export declare const useValueFlow: ({ editing, selectedField, selectedOperator, insertIndex, upsertCondition, conditionsRef, resetState, dateRange, buildingSide, setBuildingSide, buildingBase, setBuildingBase, setSelectedField, setSelectedOperator, setMenuState, setBuildingMultiValue, setInputText, }: MenuFlowInternalDeps) => {
|
|
8
8
|
handleValueSelect: (val: string | number | boolean) => void;
|
|
9
9
|
handleMultiCommit: (values: Array<string | number | boolean>) => void;
|
|
10
10
|
handleBuildingValueChange: (preview: string | undefined) => void;
|
package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useMenuFlow/useValueFlow.js
CHANGED
|
@@ -2,28 +2,77 @@ import { useCallback } from "react";
|
|
|
2
2
|
import { SEGMENT_VARIANT } from "../../../FilterInputField/FilterInputChip/index.js";
|
|
3
3
|
import { isBetweenOperator, isMultiSelectOperator } from "../../../lib/index.js";
|
|
4
4
|
import { resolveDateRangeValue, resolveDateValue, resolveMultiValues, resolveSingleValue } from "../lib/index.js";
|
|
5
|
-
const useValueFlow = ({ editing, selectedField, selectedOperator, insertIndex, upsertCondition, conditionsRef, resetState, dateRange, setBuildingMultiValue, setInputText })=>{
|
|
6
|
-
const { editingChipId, editingSegment, resetSegmentTyping } = editing;
|
|
5
|
+
const useValueFlow = ({ editing, selectedField, selectedOperator, insertIndex, upsertCondition, conditionsRef, resetState, dateRange, buildingSide, setBuildingSide, buildingBase, setBuildingBase, setSelectedField, setSelectedOperator, setMenuState, setBuildingMultiValue, setInputText })=>{
|
|
6
|
+
const { editingChipId, editingSegment, editingSide, resetSegmentTyping } = editing;
|
|
7
|
+
const stashAndAdvance = useCallback((value)=>{
|
|
8
|
+
const pairedField = selectedField?.pairedField;
|
|
9
|
+
if (0 !== buildingSide || !pairedField || editingChipId || !selectedField || !selectedOperator) return false;
|
|
10
|
+
setBuildingBase({
|
|
11
|
+
field: selectedField,
|
|
12
|
+
operator: selectedOperator,
|
|
13
|
+
value
|
|
14
|
+
});
|
|
15
|
+
setSelectedField(pairedField);
|
|
16
|
+
setSelectedOperator(null);
|
|
17
|
+
setBuildingSide(1);
|
|
18
|
+
setInputText('');
|
|
19
|
+
setMenuState('operator');
|
|
20
|
+
return true;
|
|
21
|
+
}, [
|
|
22
|
+
selectedField,
|
|
23
|
+
selectedOperator,
|
|
24
|
+
buildingSide,
|
|
25
|
+
editingChipId,
|
|
26
|
+
setBuildingBase,
|
|
27
|
+
setSelectedField,
|
|
28
|
+
setSelectedOperator,
|
|
29
|
+
setBuildingSide,
|
|
30
|
+
setInputText,
|
|
31
|
+
setMenuState
|
|
32
|
+
]);
|
|
33
|
+
const commitPairedSecond = useCallback((value, error, dateOrigin)=>{
|
|
34
|
+
if (1 !== buildingSide || !buildingBase || !selectedField || !selectedOperator) return false;
|
|
35
|
+
upsertCondition(buildingBase.field, buildingBase.operator, buildingBase.value, null, insertIndex);
|
|
36
|
+
upsertCondition(selectedField, selectedOperator, value, null, void 0, error, dateOrigin, 1);
|
|
37
|
+
setBuildingBase(null);
|
|
38
|
+
setBuildingSide(0);
|
|
39
|
+
resetState(true);
|
|
40
|
+
return true;
|
|
41
|
+
}, [
|
|
42
|
+
buildingSide,
|
|
43
|
+
buildingBase,
|
|
44
|
+
selectedField,
|
|
45
|
+
selectedOperator,
|
|
46
|
+
insertIndex,
|
|
47
|
+
upsertCondition,
|
|
48
|
+
setBuildingBase,
|
|
49
|
+
setBuildingSide,
|
|
50
|
+
resetState
|
|
51
|
+
]);
|
|
7
52
|
const handleValueSelect = useCallback((val)=>{
|
|
8
53
|
if (!selectedField || !selectedOperator) return;
|
|
54
|
+
let committedValue = val;
|
|
9
55
|
if (isBetweenOperator(selectedOperator) && 'date' === selectedField.type) {
|
|
10
56
|
const result = dateRange.selectValue(String(val));
|
|
11
57
|
if (!result) return;
|
|
12
|
-
|
|
13
|
-
resetState(!editingChipId);
|
|
14
|
-
return;
|
|
58
|
+
committedValue = result;
|
|
15
59
|
}
|
|
60
|
+
if (stashAndAdvance(committedValue)) return;
|
|
61
|
+
if (commitPairedSecond(committedValue)) return;
|
|
16
62
|
const isEditing = !!editingChipId;
|
|
17
|
-
upsertCondition(selectedField, selectedOperator,
|
|
63
|
+
upsertCondition(selectedField, selectedOperator, committedValue, editingChipId, isEditing ? void 0 : insertIndex, void 0, void 0, editingSide);
|
|
18
64
|
resetState(!isEditing);
|
|
19
65
|
}, [
|
|
20
66
|
selectedField,
|
|
21
67
|
selectedOperator,
|
|
22
68
|
editingChipId,
|
|
69
|
+
editingSide,
|
|
23
70
|
dateRange,
|
|
24
71
|
insertIndex,
|
|
25
72
|
upsertCondition,
|
|
26
|
-
resetState
|
|
73
|
+
resetState,
|
|
74
|
+
stashAndAdvance,
|
|
75
|
+
commitPairedSecond
|
|
27
76
|
]);
|
|
28
77
|
const handleMultiCommit = useCallback((values)=>{
|
|
29
78
|
if (!selectedField || !selectedOperator || 0 === values.length) return;
|
|
@@ -71,29 +120,44 @@ const useValueFlow = ({ editing, selectedField, selectedOperator, insertIndex, u
|
|
|
71
120
|
if (!selectedField || !selectedOperator || !customText.trim()) return;
|
|
72
121
|
const trimmed = customText.trim();
|
|
73
122
|
const isEditing = !!editingChipId;
|
|
123
|
+
let resolvedValue = trimmed;
|
|
124
|
+
let valueError;
|
|
125
|
+
let dateOrigin;
|
|
74
126
|
if (isMultiSelectOperator(selectedOperator)) {
|
|
75
127
|
const { resolved, error } = resolveMultiValues(selectedField, trimmed);
|
|
76
|
-
|
|
128
|
+
resolvedValue = resolved;
|
|
129
|
+
valueError = error ? SEGMENT_VARIANT.value : void 0;
|
|
77
130
|
} else if ('date' === selectedField.type) if (isBetweenOperator(selectedOperator)) {
|
|
78
131
|
const rangeValue = resolveDateRangeValue(trimmed);
|
|
79
|
-
|
|
132
|
+
resolvedValue = rangeValue ?? trimmed;
|
|
133
|
+
valueError = rangeValue ? void 0 : SEGMENT_VARIANT.value;
|
|
134
|
+
dateOrigin = 'absolute';
|
|
80
135
|
} else {
|
|
81
|
-
const
|
|
82
|
-
|
|
136
|
+
const resolved = resolveDateValue(trimmed, editingChipId, conditionsRef.current);
|
|
137
|
+
resolvedValue = trimmed;
|
|
138
|
+
valueError = resolved.error ? SEGMENT_VARIANT.value : void 0;
|
|
139
|
+
dateOrigin = resolved.dateOrigin;
|
|
83
140
|
}
|
|
84
141
|
else {
|
|
85
142
|
const { resolved, error } = resolveSingleValue(selectedField, trimmed);
|
|
86
|
-
|
|
143
|
+
resolvedValue = resolved;
|
|
144
|
+
valueError = error ? SEGMENT_VARIANT.value : void 0;
|
|
87
145
|
}
|
|
146
|
+
if (stashAndAdvance(resolvedValue)) return;
|
|
147
|
+
if (commitPairedSecond(resolvedValue, valueError, dateOrigin)) return;
|
|
148
|
+
upsertCondition(selectedField, selectedOperator, resolvedValue, editingChipId, isEditing ? void 0 : insertIndex, valueError, dateOrigin, editingSide);
|
|
88
149
|
resetState(!isEditing);
|
|
89
150
|
}, [
|
|
90
151
|
selectedField,
|
|
91
152
|
selectedOperator,
|
|
92
153
|
editingChipId,
|
|
154
|
+
editingSide,
|
|
93
155
|
insertIndex,
|
|
94
156
|
conditionsRef,
|
|
95
157
|
upsertCondition,
|
|
96
|
-
resetState
|
|
158
|
+
resetState,
|
|
159
|
+
stashAndAdvance,
|
|
160
|
+
commitPairedSecond
|
|
97
161
|
]);
|
|
98
162
|
return {
|
|
99
163
|
handleValueSelect,
|
|
@@ -14,6 +14,8 @@ interface UseResetStateDeps {
|
|
|
14
14
|
setSelectedField: Dispatch<SetStateAction<FieldMetadata | null>>;
|
|
15
15
|
setSelectedOperator: Dispatch<SetStateAction<FilterOperator | null>>;
|
|
16
16
|
setBuildingMultiValue: Dispatch<SetStateAction<string | undefined>>;
|
|
17
|
+
setBuildingSide: (side: 0 | 1) => void;
|
|
18
|
+
setBuildingBase: (base: null) => void;
|
|
17
19
|
setInsertIndex: Dispatch<SetStateAction<number | null>>;
|
|
18
20
|
setInsertAfterConnector: Dispatch<SetStateAction<boolean>>;
|
|
19
21
|
setMenuState: Dispatch<SetStateAction<MenuState>>;
|
|
@@ -29,5 +31,5 @@ interface UseResetStateDeps {
|
|
|
29
31
|
* vs. genuine outside click). If unifying these, preserve the re-render
|
|
30
32
|
* refocus case or commit chains break. AS-882.
|
|
31
33
|
*/
|
|
32
|
-
export declare const useResetState: ({ editing, dateRange, containerRef, inputRef, resetMenuAnchor, setInputText, setSelectedField, setSelectedOperator, setBuildingMultiValue, setInsertIndex, setInsertAfterConnector, setMenuState, }: UseResetStateDeps) => (continueBuilding?: boolean) => void;
|
|
34
|
+
export declare const useResetState: ({ editing, dateRange, containerRef, inputRef, resetMenuAnchor, setInputText, setSelectedField, setSelectedOperator, setBuildingMultiValue, setBuildingSide, setBuildingBase, setInsertIndex, setInsertAfterConnector, setMenuState, }: UseResetStateDeps) => (continueBuilding?: boolean) => void;
|
|
33
35
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback } from "react";
|
|
2
2
|
import { flushSync } from "react-dom";
|
|
3
3
|
import { isMenuRelated } from "../../lib/index.js";
|
|
4
|
-
const useResetState = ({ editing, dateRange, containerRef, inputRef, resetMenuAnchor, setInputText, setSelectedField, setSelectedOperator, setBuildingMultiValue, setInsertIndex, setInsertAfterConnector, setMenuState })=>{
|
|
4
|
+
const useResetState = ({ editing, dateRange, containerRef, inputRef, resetMenuAnchor, setInputText, setSelectedField, setSelectedOperator, setBuildingMultiValue, setBuildingSide, setBuildingBase, setInsertIndex, setInsertAfterConnector, setMenuState })=>{
|
|
5
5
|
const resetState = useCallback((continueBuilding = false)=>{
|
|
6
6
|
const doReset = ()=>{
|
|
7
7
|
setInputText('');
|
|
@@ -10,6 +10,8 @@ const useResetState = ({ editing, dateRange, containerRef, inputRef, resetMenuAn
|
|
|
10
10
|
editing.clearEditing();
|
|
11
11
|
dateRange.reset();
|
|
12
12
|
setBuildingMultiValue(void 0);
|
|
13
|
+
setBuildingSide(0);
|
|
14
|
+
setBuildingBase(null);
|
|
13
15
|
setInsertIndex(null);
|
|
14
16
|
setInsertAfterConnector(false);
|
|
15
17
|
resetMenuAnchor();
|
|
@@ -32,6 +34,8 @@ const useResetState = ({ editing, dateRange, containerRef, inputRef, resetMenuAn
|
|
|
32
34
|
setSelectedField,
|
|
33
35
|
setSelectedOperator,
|
|
34
36
|
setBuildingMultiValue,
|
|
37
|
+
setBuildingSide,
|
|
38
|
+
setBuildingBase,
|
|
35
39
|
setInsertIndex,
|
|
36
40
|
setInsertAfterConnector,
|
|
37
41
|
setMenuState
|
|
@@ -82,7 +82,20 @@ const buildMultiValueChip = (baseChip, condition, field, fields, chipError)=>{
|
|
|
82
82
|
}
|
|
83
83
|
};
|
|
84
84
|
};
|
|
85
|
-
const
|
|
85
|
+
const buildPairChip = (condition, field, fields)=>{
|
|
86
|
+
if (!condition.pair || !field?.pairedField) return;
|
|
87
|
+
const pf = field.pairedField;
|
|
88
|
+
const { operator, value, error } = condition.pair;
|
|
89
|
+
return {
|
|
90
|
+
attribute: pf.label || pf.name,
|
|
91
|
+
operator: operator ? getOperatorLabel(operator, pf.type || DEFAULT_FIELD_TYPE) : void 0,
|
|
92
|
+
value: resolveValueLabel(value, pf, fields) ?? String(value ?? ''),
|
|
93
|
+
...error && {
|
|
94
|
+
error
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
const makeConditionChipBase = (i, conditions, fields, error)=>{
|
|
86
99
|
const condition = conditions[i];
|
|
87
100
|
if (!condition) return makeEmptyChip(i, error);
|
|
88
101
|
const chipError = condition.error || (error ? true : void 0);
|
|
@@ -101,6 +114,17 @@ const makeConditionChip = (i, conditions, fields, error)=>{
|
|
|
101
114
|
error: chipError
|
|
102
115
|
};
|
|
103
116
|
};
|
|
117
|
+
const makeConditionChip = (i, conditions, fields, error)=>{
|
|
118
|
+
const base = makeConditionChipBase(i, conditions, fields, error);
|
|
119
|
+
const condition = conditions[i];
|
|
120
|
+
if (!condition) return base;
|
|
121
|
+
const field = fields.find((f)=>f.name === condition.field);
|
|
122
|
+
const pair = buildPairChip(condition, field, fields);
|
|
123
|
+
return pair ? {
|
|
124
|
+
...base,
|
|
125
|
+
pair
|
|
126
|
+
} : base;
|
|
127
|
+
};
|
|
104
128
|
const buildChips = (conditions, connectors, fields, error)=>{
|
|
105
129
|
if (0 === conditions.length) return [];
|
|
106
130
|
const hasMixed = connectors.includes('and') && connectors.includes('or');
|
|
@@ -1,15 +1,21 @@
|
|
|
1
|
-
import type { Condition, ExprNode } from '../../types';
|
|
1
|
+
import type { Condition, ExprNode, FieldMetadata } from '../../types';
|
|
2
2
|
/**
|
|
3
3
|
* Build an ExprNode from flat conditions + per-gap connectors.
|
|
4
4
|
* AND has higher precedence than OR: conditions connected by AND
|
|
5
5
|
* are grouped together, then those groups are joined by OR.
|
|
6
|
+
*
|
|
7
|
+
* Pass `fields` to expand paired conditions into two AND-joined conditions
|
|
8
|
+
* (the serialization contract). Omit it to keep paired conditions intact.
|
|
6
9
|
*/
|
|
7
|
-
export declare const buildExpression: (
|
|
10
|
+
export declare const buildExpression: (rawConditions: Condition[], rawConnectors: Array<"and" | "or">, fields?: FieldMetadata[]) => ExprNode | null;
|
|
8
11
|
/**
|
|
9
12
|
* Flatten a (possibly nested) ExprNode back to flat conditions + connectors.
|
|
10
13
|
* Walks the tree depth-first; between siblings inserts the parent's operator.
|
|
14
|
+
*
|
|
15
|
+
* Pass `fields` to re-pair AND-adjacent conditions into paired conditions (the
|
|
16
|
+
* inverse of `buildExpression`'s expansion). Omit it to keep them separate.
|
|
11
17
|
*/
|
|
12
|
-
export declare const expressionToConditions: (expr: ExprNode | null) => {
|
|
18
|
+
export declare const expressionToConditions: (expr: ExprNode | null, fields?: FieldMetadata[]) => {
|
|
13
19
|
conditions: Condition[];
|
|
14
20
|
connectors: Array<"and" | "or">;
|
|
15
21
|
};
|
|
@@ -1,4 +1,39 @@
|
|
|
1
|
-
const
|
|
1
|
+
const expandPairs = (conditions, connectors, fields)=>{
|
|
2
|
+
const outConditions = [];
|
|
3
|
+
const outConnectors = [];
|
|
4
|
+
conditions.forEach((condition, i)=>{
|
|
5
|
+
if (i > 0) outConnectors.push(connectors[i - 1] ?? 'and');
|
|
6
|
+
const field = fields.find((f)=>f.name === condition.field);
|
|
7
|
+
if (condition.pair && field?.pairedField) {
|
|
8
|
+
const { pair, ...base } = condition;
|
|
9
|
+
outConditions.push(base);
|
|
10
|
+
outConnectors.push('and');
|
|
11
|
+
outConditions.push({
|
|
12
|
+
type: 'condition',
|
|
13
|
+
field: field.pairedField.name,
|
|
14
|
+
...pair.operator && {
|
|
15
|
+
operator: pair.operator
|
|
16
|
+
},
|
|
17
|
+
value: pair.value,
|
|
18
|
+
...pair.error && {
|
|
19
|
+
error: pair.error
|
|
20
|
+
},
|
|
21
|
+
...pair.dateOrigin && {
|
|
22
|
+
dateOrigin: pair.dateOrigin
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
} else outConditions.push(condition);
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
conditions: outConditions,
|
|
29
|
+
connectors: outConnectors
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
const buildExpression = (rawConditions, rawConnectors, fields)=>{
|
|
33
|
+
const { conditions, connectors } = fields ? expandPairs(rawConditions, rawConnectors, fields) : {
|
|
34
|
+
conditions: rawConditions,
|
|
35
|
+
connectors: rawConnectors
|
|
36
|
+
};
|
|
2
37
|
if (0 === conditions.length) return null;
|
|
3
38
|
if (1 === conditions.length) return conditions[0] ?? null;
|
|
4
39
|
const first = conditions[0];
|
|
@@ -27,17 +62,48 @@ const buildExpression = (conditions, connectors)=>{
|
|
|
27
62
|
children: andNodes
|
|
28
63
|
};
|
|
29
64
|
};
|
|
30
|
-
const
|
|
65
|
+
const repairPairs = (conditions, connectors, fields)=>{
|
|
66
|
+
const outConditions = [];
|
|
67
|
+
const outConnectors = [];
|
|
68
|
+
let i = 0;
|
|
69
|
+
while(i < conditions.length){
|
|
70
|
+
const condition = conditions[i];
|
|
71
|
+
if (i > 0) outConnectors.push(connectors[i - 1] ?? 'and');
|
|
72
|
+
const field = fields.find((f)=>f.name === condition.field);
|
|
73
|
+
const next = conditions[i + 1];
|
|
74
|
+
const canMerge = field?.pairedField && !condition.pair && 'and' === connectors[i] && next && next.field === field.pairedField.name;
|
|
75
|
+
if (canMerge && next) {
|
|
76
|
+
outConditions.push({
|
|
77
|
+
...condition,
|
|
78
|
+
pair: {
|
|
79
|
+
...next.operator && {
|
|
80
|
+
operator: next.operator
|
|
81
|
+
},
|
|
82
|
+
value: next.value,
|
|
83
|
+
...next.error && {
|
|
84
|
+
error: next.error
|
|
85
|
+
},
|
|
86
|
+
...next.dateOrigin && {
|
|
87
|
+
dateOrigin: next.dateOrigin
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
i += 2;
|
|
92
|
+
} else {
|
|
93
|
+
outConditions.push(condition);
|
|
94
|
+
i += 1;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
conditions: outConditions,
|
|
99
|
+
connectors: outConnectors
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
const expressionToConditions = (expr, fields)=>{
|
|
31
103
|
if (!expr) return {
|
|
32
104
|
conditions: [],
|
|
33
105
|
connectors: []
|
|
34
106
|
};
|
|
35
|
-
if ('condition' === expr.type) return {
|
|
36
|
-
conditions: [
|
|
37
|
-
expr
|
|
38
|
-
],
|
|
39
|
-
connectors: []
|
|
40
|
-
};
|
|
41
107
|
const conditions = [];
|
|
42
108
|
const connectors = [];
|
|
43
109
|
const walk = (node)=>{
|
|
@@ -48,7 +114,7 @@ const expressionToConditions = (expr)=>{
|
|
|
48
114
|
});
|
|
49
115
|
};
|
|
50
116
|
walk(expr);
|
|
51
|
-
return {
|
|
117
|
+
return fields ? repairPairs(conditions, connectors, fields) : {
|
|
52
118
|
conditions,
|
|
53
119
|
connectors
|
|
54
120
|
};
|
package/dist/components/FilterInput/hooks/useFilterInputExpression/useFilterInputExpression.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export declare const useFilterInputExpression: ({ fields, value, onChange, error
|
|
|
10
10
|
conditions: Condition[];
|
|
11
11
|
connectors: ("and" | "or")[];
|
|
12
12
|
chips: import("../..").FilterInputChipData[];
|
|
13
|
-
upsertCondition: (field: FieldMetadata, operator: FilterOperator | undefined, val: string | number | boolean | null | Array<string | number | boolean>, editingChipId?: string | null, atIndex?: number, error?: ChipErrorSegment, dateOrigin?: "relative" | "absolute") => void;
|
|
13
|
+
upsertCondition: (field: FieldMetadata, operator: FilterOperator | undefined, val: string | number | boolean | null | Array<string | number | boolean>, editingChipId?: string | null, atIndex?: number, error?: ChipErrorSegment, dateOrigin?: "relative" | "absolute", side?: 0 | 1) => void;
|
|
14
14
|
removeCondition: (chipId: string) => void;
|
|
15
15
|
removeConditionAtIndex: (idx: number) => void;
|
|
16
16
|
clearAll: () => void;
|
package/dist/components/FilterInput/hooks/useFilterInputExpression/useFilterInputExpression.js
CHANGED
|
@@ -96,7 +96,7 @@ const useFilterInputExpression = ({ fields, value, onChange, error, externalErro
|
|
|
96
96
|
const [state, setState] = useState(EMPTY_STATE);
|
|
97
97
|
useEffect(()=>{
|
|
98
98
|
if (void 0 !== value) {
|
|
99
|
-
const result = expressionToConditions(value);
|
|
99
|
+
const result = expressionToConditions(value, fields);
|
|
100
100
|
setState({
|
|
101
101
|
conditions: revalidateConditions(result.conditions, fields),
|
|
102
102
|
connectors: result.connectors
|
|
@@ -113,7 +113,36 @@ const useFilterInputExpression = ({ fields, value, onChange, error, externalErro
|
|
|
113
113
|
error,
|
|
114
114
|
externalErrors
|
|
115
115
|
]);
|
|
116
|
-
const upsertCondition = useCallback((field, operator, val, editingChipId, atIndex, error, dateOrigin)=>{
|
|
116
|
+
const upsertCondition = useCallback((field, operator, val, editingChipId, atIndex, error, dateOrigin, side)=>{
|
|
117
|
+
if (1 === side) return void setState((prev)=>{
|
|
118
|
+
const idx = null != editingChipId ? chipIdToConditionIndex(editingChipId) : prev.conditions.length - 1;
|
|
119
|
+
if (null == idx || idx < 0 || idx >= prev.conditions.length) return prev;
|
|
120
|
+
const base = prev.conditions[idx];
|
|
121
|
+
const updated = [
|
|
122
|
+
...prev.conditions
|
|
123
|
+
];
|
|
124
|
+
updated[idx] = {
|
|
125
|
+
...base,
|
|
126
|
+
pair: {
|
|
127
|
+
...operator && {
|
|
128
|
+
operator
|
|
129
|
+
},
|
|
130
|
+
value: val,
|
|
131
|
+
...error && {
|
|
132
|
+
error
|
|
133
|
+
},
|
|
134
|
+
...dateOrigin && {
|
|
135
|
+
dateOrigin
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
const next = {
|
|
140
|
+
conditions: updated,
|
|
141
|
+
connectors: prev.connectors
|
|
142
|
+
};
|
|
143
|
+
onChange?.(buildExpression(next.conditions, next.connectors, fields));
|
|
144
|
+
return next;
|
|
145
|
+
});
|
|
117
146
|
const condition = buildCondition(field, operator, val, error, dateOrigin);
|
|
118
147
|
setState((prev)=>{
|
|
119
148
|
const newConditions = applyCondition(prev.conditions, condition, editingChipId, atIndex);
|
|
@@ -122,11 +151,12 @@ const useFilterInputExpression = ({ fields, value, onChange, error, externalErro
|
|
|
122
151
|
conditions: newConditions,
|
|
123
152
|
connectors: newConnectors
|
|
124
153
|
};
|
|
125
|
-
onChange?.(buildExpression(next.conditions, next.connectors));
|
|
154
|
+
onChange?.(buildExpression(next.conditions, next.connectors, fields));
|
|
126
155
|
return next;
|
|
127
156
|
});
|
|
128
157
|
}, [
|
|
129
|
-
onChange
|
|
158
|
+
onChange,
|
|
159
|
+
fields
|
|
130
160
|
]);
|
|
131
161
|
const removeCondition = useCallback((chipId)=>{
|
|
132
162
|
const idx = chipIdToConditionIndex(chipId);
|
|
@@ -139,11 +169,12 @@ const useFilterInputExpression = ({ fields, value, onChange, error, externalErro
|
|
|
139
169
|
conditions: newConditions,
|
|
140
170
|
connectors: newConnectors
|
|
141
171
|
};
|
|
142
|
-
onChange?.(buildExpression(next.conditions, next.connectors));
|
|
172
|
+
onChange?.(buildExpression(next.conditions, next.connectors, fields));
|
|
143
173
|
return next;
|
|
144
174
|
});
|
|
145
175
|
}, [
|
|
146
|
-
onChange
|
|
176
|
+
onChange,
|
|
177
|
+
fields
|
|
147
178
|
]);
|
|
148
179
|
const removeConditionAtIndex = useCallback((idx)=>{
|
|
149
180
|
setState((prev)=>{
|
|
@@ -155,11 +186,12 @@ const useFilterInputExpression = ({ fields, value, onChange, error, externalErro
|
|
|
155
186
|
conditions: newConditions,
|
|
156
187
|
connectors: newConnectors
|
|
157
188
|
};
|
|
158
|
-
onChange?.(buildExpression(next.conditions, next.connectors));
|
|
189
|
+
onChange?.(buildExpression(next.conditions, next.connectors, fields));
|
|
159
190
|
return next;
|
|
160
191
|
});
|
|
161
192
|
}, [
|
|
162
|
-
onChange
|
|
193
|
+
onChange,
|
|
194
|
+
fields
|
|
163
195
|
]);
|
|
164
196
|
const clearAll = useCallback(()=>{
|
|
165
197
|
setState((prev)=>{
|
|
@@ -172,21 +204,23 @@ const useFilterInputExpression = ({ fields, value, onChange, error, externalErro
|
|
|
172
204
|
conditions: disabledConditions,
|
|
173
205
|
connectors: []
|
|
174
206
|
};
|
|
175
|
-
onChange?.(buildExpression(next.conditions, next.connectors));
|
|
207
|
+
onChange?.(buildExpression(next.conditions, next.connectors, fields));
|
|
176
208
|
return next;
|
|
177
209
|
});
|
|
178
210
|
}, [
|
|
179
|
-
onChange
|
|
211
|
+
onChange,
|
|
212
|
+
fields
|
|
180
213
|
]);
|
|
181
214
|
const replaceExpression = useCallback((expr)=>{
|
|
182
|
-
const result = expressionToConditions(expr);
|
|
215
|
+
const result = expressionToConditions(expr, fields);
|
|
183
216
|
setState({
|
|
184
217
|
conditions: result.conditions,
|
|
185
218
|
connectors: result.connectors
|
|
186
219
|
});
|
|
187
220
|
onChange?.(expr);
|
|
188
221
|
}, [
|
|
189
|
-
onChange
|
|
222
|
+
onChange,
|
|
223
|
+
fields
|
|
190
224
|
]);
|
|
191
225
|
const setConnectorValue = useCallback((connectorId, value)=>{
|
|
192
226
|
const match = connectorId.match(CONNECTOR_ID_PATTERN);
|
|
@@ -202,11 +236,12 @@ const useFilterInputExpression = ({ fields, value, onChange, error, externalErro
|
|
|
202
236
|
conditions: prev.conditions,
|
|
203
237
|
connectors: updated
|
|
204
238
|
};
|
|
205
|
-
onChange?.(buildExpression(next.conditions, next.connectors));
|
|
239
|
+
onChange?.(buildExpression(next.conditions, next.connectors, fields));
|
|
206
240
|
return next;
|
|
207
241
|
});
|
|
208
242
|
}, [
|
|
209
|
-
onChange
|
|
243
|
+
onChange,
|
|
244
|
+
fields
|
|
210
245
|
]);
|
|
211
246
|
return {
|
|
212
247
|
conditions: state.conditions,
|
|
@@ -14,7 +14,10 @@ export type ChipErrorSegment = boolean | 'attribute' | 'value';
|
|
|
14
14
|
* Re-declared in several option interfaces — exported here to keep the source of
|
|
15
15
|
* truth single-rooted (changes to the signature reach all consumers).
|
|
16
16
|
*/
|
|
17
|
-
export type UpsertCondition = (field: FieldMetadata, operator: FilterOperator | undefined, val: string | number | boolean | null | Array<string | number | boolean>, editingChipId?: string | null, atIndex?: number, error?: ChipErrorSegment, dateOrigin?: 'relative' | 'absolute'
|
|
17
|
+
export type UpsertCondition = (field: FieldMetadata, operator: FilterOperator | undefined, val: string | number | boolean | null | Array<string | number | boolean>, editingChipId?: string | null, atIndex?: number, error?: ChipErrorSegment, dateOrigin?: 'relative' | 'absolute',
|
|
18
|
+
/** When 1, write the paired (second) triplet onto the target condition instead
|
|
19
|
+
* of replacing the base triplet. Defaults to 0 (base triplet). */
|
|
20
|
+
side?: 0 | 1) => void;
|
|
18
21
|
export interface FilterInputChipData {
|
|
19
22
|
id: string;
|
|
20
23
|
variant: FilterInputChipVariant;
|
|
@@ -30,6 +33,13 @@ export interface FilterInputChipData {
|
|
|
30
33
|
errorValueIndices?: number[];
|
|
31
34
|
/** When true, the chip cannot be edited or removed */
|
|
32
35
|
disabled?: boolean;
|
|
36
|
+
/** Second paired triplet (display) for two-step fields. */
|
|
37
|
+
pair?: {
|
|
38
|
+
attribute: string;
|
|
39
|
+
operator?: string;
|
|
40
|
+
value?: string;
|
|
41
|
+
error?: ChipErrorSegment;
|
|
42
|
+
};
|
|
33
43
|
}
|
|
34
44
|
/**
|
|
35
45
|
* Field Type for filter attributes
|
|
@@ -120,6 +130,13 @@ export interface FieldMetadata {
|
|
|
120
130
|
* emitting the query. Display in the chip is unaffected.
|
|
121
131
|
*/
|
|
122
132
|
serializeValue?: (value: string | number | boolean) => string | number | boolean;
|
|
133
|
+
/**
|
|
134
|
+
* When set, this field is a two-step paired field: the chip holds a second
|
|
135
|
+
* attribute/operator/value triplet. The paired segment is a full field
|
|
136
|
+
* definition (label, type, operators, values, validate, …). Nesting is one
|
|
137
|
+
* level only — a pairedField's own `pairedField` is ignored.
|
|
138
|
+
*/
|
|
139
|
+
pairedField?: FieldMetadata;
|
|
123
140
|
}
|
|
124
141
|
/**
|
|
125
142
|
* Expression Tree Types
|
|
@@ -139,6 +156,13 @@ export interface Condition {
|
|
|
139
156
|
dateOrigin?: 'relative' | 'absolute';
|
|
140
157
|
/** When true, the condition cannot be edited or removed */
|
|
141
158
|
disabled?: boolean;
|
|
159
|
+
/** Second paired triplet. Present only for fields with `pairedField`. */
|
|
160
|
+
pair?: {
|
|
161
|
+
operator?: FilterOperator;
|
|
162
|
+
value: string | number | boolean | null | Array<string | number | boolean>;
|
|
163
|
+
error?: ChipErrorSegment;
|
|
164
|
+
dateOrigin?: 'relative' | 'absolute';
|
|
165
|
+
};
|
|
142
166
|
}
|
|
143
167
|
/**
|
|
144
168
|
* Group Node - Represents a grouped expression with AND/OR logic
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.66.
|
|
3
|
-
"generatedAt": "2026-06-
|
|
2
|
+
"version": "0.66.3",
|
|
3
|
+
"generatedAt": "2026-06-24T13:26:24.868Z",
|
|
4
4
|
"components": [
|
|
5
5
|
{
|
|
6
6
|
"name": "Accordion",
|
|
@@ -29593,6 +29593,12 @@
|
|
|
29593
29593
|
"required": false,
|
|
29594
29594
|
"description": "When true, the chip cannot be edited or removed (dimmed appearance)"
|
|
29595
29595
|
},
|
|
29596
|
+
{
|
|
29597
|
+
"name": "pair",
|
|
29598
|
+
"type": "complex",
|
|
29599
|
+
"required": false,
|
|
29600
|
+
"description": "Second paired triplet (two-step fields). The paired attribute is fixed."
|
|
29601
|
+
},
|
|
29596
29602
|
{
|
|
29597
29603
|
"name": "defaultChecked",
|
|
29598
29604
|
"type": "boolean | undefined",
|