@wallarm-org/design-system 0.26.0 → 0.27.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/Badge/index.d.ts +1 -0
- package/dist/components/FilterInput/FilterInputErrors/parseFilterInputErrors.js +11 -0
- package/dist/components/FilterInput/FilterInputMenu/FilterInputMenu.d.ts +2 -1
- package/dist/components/FilterInput/FilterInputMenu/FilterInputMenu.js +18 -5
- package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/FilterInputValueMenu.d.ts +5 -1
- package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/FilterInputValueMenu.js +13 -84
- package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/ValueMenuFooter.d.ts +6 -0
- package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/ValueMenuFooter.js +72 -0
- package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/ValueMenuItem.js +6 -9
- package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/useValueMenuDisplayValues.d.ts +31 -0
- package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/useValueMenuDisplayValues.js +39 -0
- package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/useValueMenuState.d.ts +6 -1
- package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/useValueMenuState.js +5 -3
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useChipEditing.d.ts +1 -0
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useChipEditing.js +5 -0
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useFilterInputAutocomplete.d.ts +1 -0
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useFilterInputAutocomplete.js +8 -3
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useInputHandlers.js +7 -2
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useMenuFlow.d.ts +2 -0
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useMenuFlow.js +8 -0
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/valueCommitHelpers.js +13 -2
- package/dist/components/FilterInput/index.d.ts +1 -1
- package/dist/components/FilterInput/index.js +2 -2
- package/dist/components/FilterInput/lib/applyAcceptChar.d.ts +7 -0
- package/dist/components/FilterInput/lib/applyAcceptChar.js +5 -0
- package/dist/components/FilterInput/lib/fields.d.ts +8 -2
- package/dist/components/FilterInput/lib/fields.js +2 -2
- package/dist/components/FilterInput/lib/index.d.ts +3 -2
- package/dist/components/FilterInput/lib/index.js +4 -3
- package/dist/components/FilterInput/lib/menuFilterText.d.ts +19 -4
- package/dist/components/FilterInput/lib/menuFilterText.js +11 -8
- package/dist/components/FilterInput/lib/statusCode/createStatusCodeInputFilter.d.ts +6 -0
- package/dist/components/FilterInput/lib/statusCode/createStatusCodeInputFilter.js +2 -0
- package/dist/components/FilterInput/lib/statusCode/createStatusCodeNormalizer.d.ts +10 -0
- package/dist/components/FilterInput/lib/statusCode/createStatusCodeNormalizer.js +9 -0
- package/dist/components/FilterInput/lib/statusCode/createStatusCodeSuggestions.d.ts +16 -0
- package/dist/components/FilterInput/lib/statusCode/createStatusCodeSuggestions.js +21 -0
- package/dist/components/FilterInput/lib/statusCode/createStatusCodeValidator.d.ts +19 -0
- package/dist/components/FilterInput/lib/statusCode/createStatusCodeValidator.js +8 -0
- package/dist/components/FilterInput/lib/statusCode/index.d.ts +5 -0
- package/dist/components/FilterInput/lib/statusCode/index.js +5 -0
- package/dist/components/FilterInput/lib/statusCode/utils/canonicalize.d.ts +20 -0
- package/dist/components/FilterInput/lib/statusCode/utils/canonicalize.js +19 -0
- package/dist/components/FilterInput/lib/statusCode/utils/constants.d.ts +14 -0
- package/dist/components/FilterInput/lib/statusCode/utils/constants.js +17 -0
- package/dist/components/FilterInput/lib/statusCode/utils/index.d.ts +5 -0
- package/dist/components/FilterInput/lib/statusCode/utils/index.js +5 -0
- package/dist/components/FilterInput/lib/statusCode/utils/makeMask.d.ts +7 -0
- package/dist/components/FilterInput/lib/statusCode/utils/makeMask.js +14 -0
- package/dist/components/FilterInput/lib/statusCode/utils/maskRoots.d.ts +4 -0
- package/dist/components/FilterInput/lib/statusCode/utils/maskRoots.js +3 -0
- package/dist/components/FilterInput/lib/statusCode/utils/types.d.ts +8 -0
- package/dist/components/FilterInput/lib/statusCode/utils/types.js +0 -0
- package/dist/components/FilterInput/stories/mockStatusCodes.js +2 -0
- package/dist/components/FilterInput/types.d.ts +32 -2
- package/dist/metadata/components.json +2 -2
- package/package.json +1 -1
- package/dist/components/FilterInput/lib/statusCodeSuggestions.d.ts +0 -10
- package/dist/components/FilterInput/lib/statusCodeSuggestions.js +0 -45
|
@@ -11,6 +11,17 @@ const parseFilterInputErrors = (conditions, fields)=>{
|
|
|
11
11
|
errors.push(`Unknown field ${condition.field}`);
|
|
12
12
|
break;
|
|
13
13
|
case 'value':
|
|
14
|
+
if (field?.validate) {
|
|
15
|
+
const values = Array.isArray(condition.value) ? condition.value : [
|
|
16
|
+
condition.value
|
|
17
|
+
];
|
|
18
|
+
const invalidValues = values.filter((v)=>null != v && field.validate(v));
|
|
19
|
+
if (invalidValues.length > 0) {
|
|
20
|
+
const formatted = invalidValues.map((v)=>String(v)).join(', ');
|
|
21
|
+
errors.push(`Invalid value for ${label}: ${formatted}`);
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
14
25
|
if (field && hasStaticAllowlist(field) && Array.isArray(condition.value)) {
|
|
15
26
|
const fv = getFieldValues(field);
|
|
16
27
|
if (fv.length > 0) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type FC, type RefObject } from 'react';
|
|
2
2
|
import type { ChipSegment } from '../FilterInputField/FilterInputChip';
|
|
3
3
|
import type { FieldMetadata, FilterOperator, MenuState } from '../types';
|
|
4
4
|
export interface FilterInputAutocompleteState {
|
|
@@ -20,6 +20,7 @@ export interface FilterInputAutocompleteState {
|
|
|
20
20
|
handleMenuClose: () => void;
|
|
21
21
|
handleMenuDiscard: () => void;
|
|
22
22
|
handleBuildingValueChange: (preview: string | undefined) => void;
|
|
23
|
+
handleMultiSelectToggle: () => void;
|
|
23
24
|
segmentFilterText: string;
|
|
24
25
|
segmentMenuFilterText: string;
|
|
25
26
|
editingSegment: ChipSegment | null;
|
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { getCurrentValueTokenText, getFieldValues, getValueFilterText, isBetweenOperator, isMultiSelectOperator } from "../lib/index.js";
|
|
3
4
|
import { FilterInputDateValueMenu } from "./FilterInputDateValueMenu/index.js";
|
|
4
5
|
import { FilterInputFieldMenu } from "./FilterInputFieldMenu/index.js";
|
|
5
6
|
import { FilterInputOperatorMenu } from "./FilterInputOperatorMenu.js";
|
|
6
7
|
import { FilterInputValueMenu } from "./FilterInputValueMenu/index.js";
|
|
7
8
|
const FilterInputMenu = ({ fields, autocomplete })=>{
|
|
8
|
-
const { inputText, menuState, selectedField, selectedOperator, menuPositioning, editingMultiValues, editingSingleValue, editingDateRange, inputRef, menuRef, handleFieldSelect, handleOperatorSelect, handleValueSelect, handleMultiCommit, handleRangeSelect, handleMenuClose, handleMenuDiscard, handleBuildingValueChange, segmentMenuFilterText, editingSegment, blurCommitRef } = autocomplete;
|
|
9
|
+
const { inputText, menuState, selectedField, selectedOperator, menuPositioning, editingMultiValues, editingSingleValue, editingDateRange, inputRef, menuRef, handleFieldSelect, handleOperatorSelect, handleValueSelect, handleMultiCommit, handleRangeSelect, handleMenuClose, handleMenuDiscard, handleBuildingValueChange, handleMultiSelectToggle, segmentMenuFilterText, editingSegment, blurCommitRef } = autocomplete;
|
|
9
10
|
const fieldFilterText = 'attribute' === editingSegment ? segmentMenuFilterText : inputText;
|
|
10
11
|
const operatorFilterText = 'operator' === editingSegment ? segmentMenuFilterText : inputText;
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
12
|
+
const currentTokenText = getCurrentValueTokenText(editingSegment, inputText, segmentMenuFilterText, selectedOperator);
|
|
13
|
+
const selectedContext = useMemo(()=>({
|
|
14
|
+
selectedValues: [
|
|
15
|
+
...editingMultiValues,
|
|
16
|
+
...null != editingSingleValue ? [
|
|
17
|
+
editingSingleValue
|
|
18
|
+
] : []
|
|
19
|
+
]
|
|
20
|
+
}), [
|
|
21
|
+
editingMultiValues,
|
|
22
|
+
editingSingleValue
|
|
23
|
+
]);
|
|
24
|
+
const selectedFieldValues = selectedField ? getFieldValues(selectedField, currentTokenText, selectedContext) : [];
|
|
25
|
+
const valueFilterText = getValueFilterText(currentTokenText, selectedOperator, selectedFieldValues);
|
|
14
26
|
const showOperatorMenu = !!selectedField;
|
|
15
27
|
const showValueMenu = !!selectedField && !!selectedOperator;
|
|
16
28
|
const isDateField = selectedField?.type === 'date';
|
|
@@ -64,6 +76,7 @@ const FilterInputMenu = ({ fields, autocomplete })=>{
|
|
|
64
76
|
highlightValue: editingSingleValue,
|
|
65
77
|
positioning: menuPositioning,
|
|
66
78
|
onBuildingValueChange: handleBuildingValueChange,
|
|
79
|
+
onItemToggle: handleMultiSelectToggle,
|
|
67
80
|
inputRef: inputRef,
|
|
68
81
|
menuRef: menuRef,
|
|
69
82
|
filterText: valueFilterText,
|
package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/FilterInputValueMenu.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type FC, type RefObject } from 'react';
|
|
2
|
+
import type { BadgeColor } from '../../../Badge';
|
|
2
3
|
export interface ValueOption {
|
|
3
4
|
value: string | number | boolean;
|
|
4
5
|
label: string;
|
|
5
6
|
badge?: {
|
|
6
|
-
color:
|
|
7
|
+
color: BadgeColor;
|
|
7
8
|
text: string;
|
|
8
9
|
};
|
|
9
10
|
hasSubmenu?: boolean;
|
|
@@ -22,6 +23,9 @@ export interface FilterInputValueMenuProps {
|
|
|
22
23
|
width?: 'standard' | 'compact' | number;
|
|
23
24
|
positioning?: Record<string, unknown>;
|
|
24
25
|
onBuildingValueChange?: (preview: string | undefined) => void;
|
|
26
|
+
/** Fires on explicit multi-select toggle (click or keyboard) — use to react
|
|
27
|
+
* only to user-initiated toggles, not to initialization. */
|
|
28
|
+
onItemToggle?: () => void;
|
|
25
29
|
/** Ref to the query bar input — ArrowUp on first item returns focus here */
|
|
26
30
|
inputRef?: RefObject<HTMLInputElement | null>;
|
|
27
31
|
/** Text to filter values by label */
|
package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/FilterInputValueMenu.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useMemo } from "react";
|
|
3
3
|
import { cn } from "../../../../utils/cn.js";
|
|
4
|
-
import { DropdownMenu, DropdownMenuContent,
|
|
5
|
-
import { Kbd } from "../../../Kbd/Kbd.js";
|
|
6
|
-
import { KbdGroup } from "../../../Kbd/KbdGroup.js";
|
|
4
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup } from "../../../DropdownMenu/index.js";
|
|
7
5
|
import { filterAndSort } from "../../lib/index.js";
|
|
8
6
|
import { MenuEmptyState } from "../MenuEmptyState.js";
|
|
7
|
+
import { useValueMenuDisplayValues } from "./useValueMenuDisplayValues.js";
|
|
9
8
|
import { useValueMenuState } from "./useValueMenuState.js";
|
|
9
|
+
import { ValueMenuFooter } from "./ValueMenuFooter.js";
|
|
10
10
|
import { ValueMenuItem } from "./ValueMenuItem.js";
|
|
11
|
-
const FilterInputValueMenu = ({ values, onSelect, onCommit, open = false, onOpenChange, onEscape, multiSelect = false, initialValues = [], highlightValue, width = 'standard', positioning, onBuildingValueChange, inputRef, filterText = '', menuRef, blurCommitRef, className })=>{
|
|
11
|
+
const FilterInputValueMenu = ({ values, onSelect, onCommit, open = false, onOpenChange, onEscape, multiSelect = false, initialValues = [], highlightValue, width = 'standard', positioning, onBuildingValueChange, onItemToggle, inputRef, filterText = '', menuRef, blurCommitRef, className })=>{
|
|
12
12
|
const filteredValues = useMemo(()=>filterAndSort(values, filterText, (v)=>[
|
|
13
13
|
v.label,
|
|
14
14
|
String(v.value)
|
|
@@ -27,26 +27,18 @@ const FilterInputValueMenu = ({ values, onSelect, onCommit, open = false, onOpen
|
|
|
27
27
|
onEscape,
|
|
28
28
|
onOpenChange,
|
|
29
29
|
onBuildingValueChange,
|
|
30
|
+
onItemToggle,
|
|
30
31
|
inputRef,
|
|
31
32
|
menuRef,
|
|
32
33
|
blurCommitRef
|
|
33
34
|
});
|
|
34
|
-
const displayValues =
|
|
35
|
-
if (!multiSelect) return filteredValues;
|
|
36
|
-
const checkedSet = new Set(checkedValues.map(String));
|
|
37
|
-
if (0 === checkedSet.size) return filteredValues;
|
|
38
|
-
const checkedItems = values.filter((v)=>checkedSet.has(String(v.value)));
|
|
39
|
-
const uncheckedFiltered = filteredValues.filter((v)=>!checkedSet.has(String(v.value)));
|
|
40
|
-
return [
|
|
41
|
-
...checkedItems,
|
|
42
|
-
...uncheckedFiltered
|
|
43
|
-
];
|
|
44
|
-
}, [
|
|
45
|
-
filteredValues,
|
|
35
|
+
const displayValues = useValueMenuDisplayValues({
|
|
46
36
|
values,
|
|
37
|
+
filteredValues,
|
|
47
38
|
multiSelect,
|
|
48
|
-
checkedValues
|
|
49
|
-
|
|
39
|
+
checkedValues,
|
|
40
|
+
highlightValue
|
|
41
|
+
});
|
|
50
42
|
const widthClass = 'compact' === width ? 'w-[172px]' : 'w-[300px]';
|
|
51
43
|
const widthStyle = 'number' == typeof width ? {
|
|
52
44
|
width: `${width}px`
|
|
@@ -76,71 +68,8 @@ const FilterInputValueMenu = ({ values, onSelect, onCommit, open = false, onOpen
|
|
|
76
68
|
})
|
|
77
69
|
}, String(option.value)))
|
|
78
70
|
}) : /*#__PURE__*/ jsx(MenuEmptyState, {}),
|
|
79
|
-
/*#__PURE__*/ jsx(
|
|
80
|
-
|
|
81
|
-
children: [
|
|
82
|
-
/*#__PURE__*/ jsxs("span", {
|
|
83
|
-
className: "flex items-center gap-4",
|
|
84
|
-
children: [
|
|
85
|
-
/*#__PURE__*/ jsx(KbdGroup, {
|
|
86
|
-
children: /*#__PURE__*/ jsx(Kbd, {
|
|
87
|
-
children: "↵"
|
|
88
|
-
})
|
|
89
|
-
}),
|
|
90
|
-
"to select"
|
|
91
|
-
]
|
|
92
|
-
}),
|
|
93
|
-
/*#__PURE__*/ jsxs("span", {
|
|
94
|
-
className: "flex items-center gap-4",
|
|
95
|
-
children: [
|
|
96
|
-
/*#__PURE__*/ jsxs(KbdGroup, {
|
|
97
|
-
children: [
|
|
98
|
-
/*#__PURE__*/ jsx(Kbd, {
|
|
99
|
-
children: "⌘"
|
|
100
|
-
}),
|
|
101
|
-
/*#__PURE__*/ jsx(Kbd, {
|
|
102
|
-
children: "↑"
|
|
103
|
-
}),
|
|
104
|
-
/*#__PURE__*/ jsx(Kbd, {
|
|
105
|
-
children: "↓"
|
|
106
|
-
})
|
|
107
|
-
]
|
|
108
|
-
}),
|
|
109
|
-
"to multi-select"
|
|
110
|
-
]
|
|
111
|
-
})
|
|
112
|
-
]
|
|
113
|
-
}) : /*#__PURE__*/ jsxs(Fragment, {
|
|
114
|
-
children: [
|
|
115
|
-
/*#__PURE__*/ jsxs("span", {
|
|
116
|
-
className: "flex items-center gap-4",
|
|
117
|
-
children: [
|
|
118
|
-
/*#__PURE__*/ jsxs(KbdGroup, {
|
|
119
|
-
children: [
|
|
120
|
-
/*#__PURE__*/ jsx(Kbd, {
|
|
121
|
-
children: "↑"
|
|
122
|
-
}),
|
|
123
|
-
/*#__PURE__*/ jsx(Kbd, {
|
|
124
|
-
children: "↓"
|
|
125
|
-
})
|
|
126
|
-
]
|
|
127
|
-
}),
|
|
128
|
-
"to navigate"
|
|
129
|
-
]
|
|
130
|
-
}),
|
|
131
|
-
/*#__PURE__*/ jsxs("span", {
|
|
132
|
-
className: "flex items-center gap-4",
|
|
133
|
-
children: [
|
|
134
|
-
/*#__PURE__*/ jsx(KbdGroup, {
|
|
135
|
-
children: /*#__PURE__*/ jsx(Kbd, {
|
|
136
|
-
children: "↵"
|
|
137
|
-
})
|
|
138
|
-
}),
|
|
139
|
-
"to select"
|
|
140
|
-
]
|
|
141
|
-
})
|
|
142
|
-
]
|
|
143
|
-
})
|
|
71
|
+
/*#__PURE__*/ jsx(ValueMenuFooter, {
|
|
72
|
+
multiSelect: multiSelect
|
|
144
73
|
})
|
|
145
74
|
]
|
|
146
75
|
})
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { DropdownMenuFooter } from "../../../DropdownMenu/index.js";
|
|
3
|
+
import { Kbd } from "../../../Kbd/Kbd.js";
|
|
4
|
+
import { KbdGroup } from "../../../Kbd/KbdGroup.js";
|
|
5
|
+
const ValueMenuFooter = ({ multiSelect })=>/*#__PURE__*/ jsx(DropdownMenuFooter, {
|
|
6
|
+
children: multiSelect ? /*#__PURE__*/ jsxs(Fragment, {
|
|
7
|
+
children: [
|
|
8
|
+
/*#__PURE__*/ jsxs("span", {
|
|
9
|
+
className: "flex items-center gap-4",
|
|
10
|
+
children: [
|
|
11
|
+
/*#__PURE__*/ jsx(KbdGroup, {
|
|
12
|
+
children: /*#__PURE__*/ jsx(Kbd, {
|
|
13
|
+
children: "↵"
|
|
14
|
+
})
|
|
15
|
+
}),
|
|
16
|
+
"to select"
|
|
17
|
+
]
|
|
18
|
+
}),
|
|
19
|
+
/*#__PURE__*/ jsxs("span", {
|
|
20
|
+
className: "flex items-center gap-4",
|
|
21
|
+
children: [
|
|
22
|
+
/*#__PURE__*/ jsxs(KbdGroup, {
|
|
23
|
+
children: [
|
|
24
|
+
/*#__PURE__*/ jsx(Kbd, {
|
|
25
|
+
children: "⌘"
|
|
26
|
+
}),
|
|
27
|
+
/*#__PURE__*/ jsx(Kbd, {
|
|
28
|
+
children: "↑"
|
|
29
|
+
}),
|
|
30
|
+
/*#__PURE__*/ jsx(Kbd, {
|
|
31
|
+
children: "↓"
|
|
32
|
+
})
|
|
33
|
+
]
|
|
34
|
+
}),
|
|
35
|
+
"to multi-select"
|
|
36
|
+
]
|
|
37
|
+
})
|
|
38
|
+
]
|
|
39
|
+
}) : /*#__PURE__*/ jsxs(Fragment, {
|
|
40
|
+
children: [
|
|
41
|
+
/*#__PURE__*/ jsxs("span", {
|
|
42
|
+
className: "flex items-center gap-4",
|
|
43
|
+
children: [
|
|
44
|
+
/*#__PURE__*/ jsxs(KbdGroup, {
|
|
45
|
+
children: [
|
|
46
|
+
/*#__PURE__*/ jsx(Kbd, {
|
|
47
|
+
children: "↑"
|
|
48
|
+
}),
|
|
49
|
+
/*#__PURE__*/ jsx(Kbd, {
|
|
50
|
+
children: "↓"
|
|
51
|
+
})
|
|
52
|
+
]
|
|
53
|
+
}),
|
|
54
|
+
"to navigate"
|
|
55
|
+
]
|
|
56
|
+
}),
|
|
57
|
+
/*#__PURE__*/ jsxs("span", {
|
|
58
|
+
className: "flex items-center gap-4",
|
|
59
|
+
children: [
|
|
60
|
+
/*#__PURE__*/ jsx(KbdGroup, {
|
|
61
|
+
children: /*#__PURE__*/ jsx(Kbd, {
|
|
62
|
+
children: "↵"
|
|
63
|
+
})
|
|
64
|
+
}),
|
|
65
|
+
"to select"
|
|
66
|
+
]
|
|
67
|
+
})
|
|
68
|
+
]
|
|
69
|
+
})
|
|
70
|
+
});
|
|
71
|
+
ValueMenuFooter.displayName = 'ValueMenuFooter';
|
|
72
|
+
export { ValueMenuFooter };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { ChevronRight } from "../../../../icons/ChevronRight.js";
|
|
3
|
+
import { Badge } from "../../../Badge/index.js";
|
|
3
4
|
import { Checkmark } from "../../../Checkmark/index.js";
|
|
4
5
|
import { DropdownMenuItem } from "../../../DropdownMenu/index.js";
|
|
5
6
|
import { Text } from "../../../Text/index.js";
|
|
@@ -8,15 +9,11 @@ const ValueMenuItem = ({ option, isChecked, isPending, multiSelect, onSelect })=
|
|
|
8
9
|
onSelect: onSelect,
|
|
9
10
|
className: isPending ? 'bg-states-primary-hover' : void 0,
|
|
10
11
|
children: [
|
|
11
|
-
option.badge ? /*#__PURE__*/ jsx(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
children: /*#__PURE__*/ jsx("span", {
|
|
17
|
-
className: "min-w-0 truncate leading-4",
|
|
18
|
-
children: option.badge.text
|
|
19
|
-
})
|
|
12
|
+
option.badge ? /*#__PURE__*/ jsx(Badge, {
|
|
13
|
+
color: option.badge.color,
|
|
14
|
+
type: "secondary",
|
|
15
|
+
variant: "default",
|
|
16
|
+
children: option.badge.text
|
|
20
17
|
}) : /*#__PURE__*/ jsx("div", {
|
|
21
18
|
className: "min-w-0",
|
|
22
19
|
children: /*#__PURE__*/ jsx(Text, {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ValueOption } from './FilterInputValueMenu';
|
|
2
|
+
type ConditionValue = string | number | boolean;
|
|
3
|
+
interface UseValueMenuDisplayValuesOptions {
|
|
4
|
+
/** Raw option list coming from the parent (e.g. `getSuggestions` output). */
|
|
5
|
+
values: ValueOption[];
|
|
6
|
+
/** Filtered / sorted view of `values` used for normal rendering. */
|
|
7
|
+
filteredValues: ValueOption[];
|
|
8
|
+
multiSelect: boolean;
|
|
9
|
+
/** Currently checked values when `multiSelect` is true. */
|
|
10
|
+
checkedValues: ConditionValue[];
|
|
11
|
+
/** Currently highlighted value when `multiSelect` is false. */
|
|
12
|
+
highlightValue?: ConditionValue;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Compose the final dropdown list shown to the user.
|
|
16
|
+
*
|
|
17
|
+
* The parent helper (`values`) is free to change shape between renders — for
|
|
18
|
+
* instance a dynamic `getSuggestions` may narrow its result after a selection
|
|
19
|
+
* is made. This hook keeps the user's currently-selected entries pinned at
|
|
20
|
+
* the top of the list with a stable presentation:
|
|
21
|
+
*
|
|
22
|
+
* 1. Every option the menu has ever rendered is remembered in a `Map` keyed
|
|
23
|
+
* by value. When a selected entry is no longer in the current `values`,
|
|
24
|
+
* the remembered option (with its original label/badge) is used.
|
|
25
|
+
* 2. If nothing has been seen for that value either, a plain-text option is
|
|
26
|
+
* fabricated so the user can still see and toggle it.
|
|
27
|
+
* 3. Unchecked items come from `filteredValues` (already filter-sorted) so
|
|
28
|
+
* the search query still applies to them.
|
|
29
|
+
*/
|
|
30
|
+
export declare const useValueMenuDisplayValues: ({ values, filteredValues, multiSelect, checkedValues, highlightValue, }: UseValueMenuDisplayValuesOptions) => ValueOption[];
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef } from "react";
|
|
2
|
+
const useValueMenuDisplayValues = ({ values, filteredValues, multiSelect, checkedValues, highlightValue })=>{
|
|
3
|
+
const optionMemoryRef = useRef(new Map());
|
|
4
|
+
useEffect(()=>{
|
|
5
|
+
for (const opt of values)optionMemoryRef.current.set(String(opt.value), opt);
|
|
6
|
+
}, [
|
|
7
|
+
values
|
|
8
|
+
]);
|
|
9
|
+
return useMemo(()=>{
|
|
10
|
+
const selectedList = multiSelect ? checkedValues : null != highlightValue ? [
|
|
11
|
+
highlightValue
|
|
12
|
+
] : [];
|
|
13
|
+
if (0 === selectedList.length) return filteredValues;
|
|
14
|
+
const selectedSet = new Set(selectedList.map(String));
|
|
15
|
+
const selectedItems = selectedList.map((v)=>{
|
|
16
|
+
const key = String(v);
|
|
17
|
+
const match = values.find((opt)=>String(opt.value) === key);
|
|
18
|
+
if (match) return match;
|
|
19
|
+
const remembered = optionMemoryRef.current.get(key);
|
|
20
|
+
if (remembered) return remembered;
|
|
21
|
+
return {
|
|
22
|
+
value: v,
|
|
23
|
+
label: key
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
const restFiltered = filteredValues.filter((v)=>!selectedSet.has(String(v.value)));
|
|
27
|
+
return [
|
|
28
|
+
...selectedItems,
|
|
29
|
+
...restFiltered
|
|
30
|
+
];
|
|
31
|
+
}, [
|
|
32
|
+
filteredValues,
|
|
33
|
+
values,
|
|
34
|
+
multiSelect,
|
|
35
|
+
checkedValues,
|
|
36
|
+
highlightValue
|
|
37
|
+
]);
|
|
38
|
+
};
|
|
39
|
+
export { useValueMenuDisplayValues };
|
package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/useValueMenuState.d.ts
CHANGED
|
@@ -13,12 +13,17 @@ interface UseValueMenuStateOptions {
|
|
|
13
13
|
onEscape?: () => void;
|
|
14
14
|
onOpenChange?: (open: boolean) => void;
|
|
15
15
|
onBuildingValueChange?: (preview: string | undefined) => void;
|
|
16
|
+
/** Fires whenever the user explicitly toggles an item in multi-select mode
|
|
17
|
+
* (click or keyboard). Distinct from onBuildingValueChange, which also fires
|
|
18
|
+
* on mount with the initial preview — use this when you need to react only
|
|
19
|
+
* to actual user-initiated toggles. */
|
|
20
|
+
onItemToggle?: () => void;
|
|
16
21
|
inputRef?: RefObject<HTMLInputElement | null>;
|
|
17
22
|
menuRef?: RefObject<HTMLDivElement | null>;
|
|
18
23
|
/** Ref to register blur commit function — called by blur handler before state reset. Returns true if committed. */
|
|
19
24
|
blurCommitRef?: RefObject<(() => boolean) | null>;
|
|
20
25
|
}
|
|
21
|
-
export declare const useValueMenuState: ({ values, open, multiSelect, initialValues, highlightValue, onSelect, onCommit, onEscape, onOpenChange, onBuildingValueChange, inputRef, menuRef, blurCommitRef, }: UseValueMenuStateOptions) => {
|
|
26
|
+
export declare const useValueMenuState: ({ values, open, multiSelect, initialValues, highlightValue, onSelect, onCommit, onEscape, onOpenChange, onBuildingValueChange, onItemToggle, inputRef, menuRef, blurCommitRef, }: UseValueMenuStateOptions) => {
|
|
22
27
|
checkedValues: ConditionValue[];
|
|
23
28
|
selectedValues: ConditionValue[];
|
|
24
29
|
highlightedValue: string;
|
package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/useValueMenuState.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { useKeyboardNav } from "../hooks/useKeyboardNav.js";
|
|
3
|
-
const useValueMenuState = ({ values, open, multiSelect, initialValues, highlightValue, onSelect, onCommit, onEscape, onOpenChange, onBuildingValueChange, inputRef, menuRef, blurCommitRef })=>{
|
|
3
|
+
const useValueMenuState = ({ values, open, multiSelect, initialValues, highlightValue, onSelect, onCommit, onEscape, onOpenChange, onBuildingValueChange, onItemToggle, inputRef, menuRef, blurCommitRef })=>{
|
|
4
4
|
const [checkedValues, setCheckedValues] = useState(initialValues);
|
|
5
5
|
const checkedValuesRef = useRef(checkedValues);
|
|
6
6
|
checkedValuesRef.current = checkedValues;
|
|
@@ -59,8 +59,10 @@ const useValueMenuState = ({ values, open, multiSelect, initialValues, highlight
|
|
|
59
59
|
values
|
|
60
60
|
]);
|
|
61
61
|
const handleItemSelect = (item)=>{
|
|
62
|
-
if (multiSelect)
|
|
63
|
-
|
|
62
|
+
if (multiSelect) {
|
|
63
|
+
toggleValue(item.value);
|
|
64
|
+
onItemToggle?.();
|
|
65
|
+
} else onSelect(item.value);
|
|
64
66
|
};
|
|
65
67
|
const handleClose = ()=>{
|
|
66
68
|
if (multiSelect) commitChecked();
|
|
@@ -24,6 +24,7 @@ export declare const useChipEditing: ({ conditions, chips, fields, containerRef,
|
|
|
24
24
|
segmentFilterText: string;
|
|
25
25
|
segmentMenuFilterText: string;
|
|
26
26
|
setSegmentFilterText: (text: string) => void;
|
|
27
|
+
resetSegmentTyping: () => void;
|
|
27
28
|
handleChipClick: (chipId: string, segment: ChipSegment, anchorRect: DOMRect) => void;
|
|
28
29
|
clearEditing: () => void;
|
|
29
30
|
cancelSegmentEdit: () => void;
|
|
@@ -82,6 +82,9 @@ const useChipEditing = ({ conditions, chips, fields, containerRef, setMenuOffset
|
|
|
82
82
|
setSegmentFilterText(text);
|
|
83
83
|
setUserHasTyped(true);
|
|
84
84
|
}, []);
|
|
85
|
+
const resetSegmentTyping = useCallback(()=>{
|
|
86
|
+
setUserHasTyped(false);
|
|
87
|
+
}, []);
|
|
85
88
|
const segmentDisplayText = segmentFilterText;
|
|
86
89
|
const segmentMenuFilterText = userHasTyped ? segmentFilterText : '';
|
|
87
90
|
return useMemo(()=>({
|
|
@@ -91,6 +94,7 @@ const useChipEditing = ({ conditions, chips, fields, containerRef, setMenuOffset
|
|
|
91
94
|
segmentFilterText: segmentDisplayText,
|
|
92
95
|
segmentMenuFilterText,
|
|
93
96
|
setSegmentFilterText: handleSegmentFilterChange,
|
|
97
|
+
resetSegmentTyping,
|
|
94
98
|
handleChipClick,
|
|
95
99
|
clearEditing,
|
|
96
100
|
cancelSegmentEdit
|
|
@@ -100,6 +104,7 @@ const useChipEditing = ({ conditions, chips, fields, containerRef, setMenuOffset
|
|
|
100
104
|
segmentDisplayText,
|
|
101
105
|
segmentMenuFilterText,
|
|
102
106
|
handleSegmentFilterChange,
|
|
107
|
+
resetSegmentTyping,
|
|
103
108
|
handleChipClick,
|
|
104
109
|
clearEditing,
|
|
105
110
|
cancelSegmentEdit
|
package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useFilterInputAutocomplete.d.ts
CHANGED
|
@@ -44,6 +44,7 @@ export declare const useFilterInputAutocomplete: ({ fields, conditions, chips, u
|
|
|
44
44
|
handleValueSelect: (val: string | number | boolean) => void;
|
|
45
45
|
handleMultiCommit: (values: Array<string | number | boolean>) => void;
|
|
46
46
|
handleBuildingValueChange: (preview: string | undefined) => void;
|
|
47
|
+
handleMultiSelectToggle: () => void;
|
|
47
48
|
handleMenuClose: () => void;
|
|
48
49
|
handleMenuDiscard: (continueBuilding?: boolean) => void;
|
|
49
50
|
handleChipClick: (chipId: string, segment: import("../../FilterInputField").ChipSegment, anchorRect: DOMRect) => void;
|
package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useFilterInputAutocomplete.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback, useRef, useState } from "react";
|
|
2
2
|
import { flushSync } from "react-dom";
|
|
3
3
|
import { useDateRange } from "../../FilterInputMenu/FilterInputDateValueMenu/hooks.js";
|
|
4
|
-
import { chipIdToConditionIndex } from "../../lib/index.js";
|
|
4
|
+
import { applyAcceptChar, chipIdToConditionIndex } from "../../lib/index.js";
|
|
5
5
|
import { deriveAutocompleteValues } from "./deriveAutocompleteValues.js";
|
|
6
6
|
import { useChipEditing } from "./useChipEditing.js";
|
|
7
7
|
import { useFocusManagement } from "./useFocusManagement.js";
|
|
@@ -75,7 +75,7 @@ const useFilterInputAutocomplete = ({ fields, conditions, chips, upsertCondition
|
|
|
75
75
|
const inputTextRef = useRef(inputText);
|
|
76
76
|
inputTextRef.current = inputText;
|
|
77
77
|
const commitBuildingOnBlurRef = useRef(()=>false);
|
|
78
|
-
const { handleMenuClose, handleFieldSelect, handleOperatorSelect, handleValueSelect, handleMultiCommit, handleBuildingValueChange, handleRangeSelect, handleCustomValueCommit, handleCustomAttributeCommit } = useMenuFlow({
|
|
78
|
+
const { handleMenuClose, handleFieldSelect, handleOperatorSelect, handleValueSelect, handleMultiCommit, handleBuildingValueChange, handleMultiSelectToggle, handleRangeSelect, handleCustomValueCommit, handleCustomAttributeCommit } = useMenuFlow({
|
|
79
79
|
editing,
|
|
80
80
|
selectedField,
|
|
81
81
|
selectedOperator,
|
|
@@ -209,6 +209,7 @@ const useFilterInputAutocomplete = ({ fields, conditions, chips, upsertCondition
|
|
|
209
209
|
handleValueSelect,
|
|
210
210
|
handleMultiCommit,
|
|
211
211
|
handleBuildingValueChange,
|
|
212
|
+
handleMultiSelectToggle,
|
|
212
213
|
handleMenuClose,
|
|
213
214
|
handleMenuDiscard: resetState,
|
|
214
215
|
handleChipClick: editing.handleChipClick,
|
|
@@ -227,7 +228,11 @@ const useFilterInputAutocomplete = ({ fields, conditions, chips, upsertCondition
|
|
|
227
228
|
editingSegment: editing.editingSegment,
|
|
228
229
|
segmentFilterText: editing.segmentFilterText,
|
|
229
230
|
segmentMenuFilterText: editing.segmentMenuFilterText,
|
|
230
|
-
handleSegmentFilterChange:
|
|
231
|
+
handleSegmentFilterChange: (text)=>{
|
|
232
|
+
const accept = selectedField?.acceptChar;
|
|
233
|
+
const next = 'value' === editing.editingSegment && accept ? applyAcceptChar(text, accept) : text;
|
|
234
|
+
editing.setSegmentFilterText(next);
|
|
235
|
+
},
|
|
231
236
|
cancelSegmentEdit: editing.cancelSegmentEdit,
|
|
232
237
|
handleCustomValueCommit,
|
|
233
238
|
handleCustomAttributeCommit,
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { useCallback, useRef } from "react";
|
|
2
|
-
import { OPERATOR_SYMBOLS, getOperatorFromLabel, hasFieldValues } from "../../lib/index.js";
|
|
2
|
+
import { OPERATOR_SYMBOLS, applyAcceptChar, getOperatorFromLabel, hasFieldValues } from "../../lib/index.js";
|
|
3
3
|
const useInputHandlers = ({ inputText, menuState, selectedField, isFocused, fields, inputRef, conditionsRef, conditionsLengthRef, effectiveInsertIndexRef, setInputText, setMenuState, setInsertIndex, resetMenuOffset, removeConditionAtIndex, handleFieldSelect, handleOperatorSelect, handleCustomValueCommit })=>{
|
|
4
4
|
const menuRef = useRef(null);
|
|
5
5
|
const handleInputChange = useCallback((e)=>{
|
|
6
|
-
|
|
6
|
+
let text = e.target.value;
|
|
7
|
+
if ('value' === menuState && selectedField?.acceptChar) {
|
|
8
|
+
text = applyAcceptChar(text, selectedField.acceptChar);
|
|
9
|
+
if (text !== e.target.value) e.target.value = text;
|
|
10
|
+
}
|
|
7
11
|
setInputText(text);
|
|
8
12
|
if (text && !selectedField) setMenuState('field');
|
|
9
13
|
else if (!text && !selectedField) setMenuState(isFocused && 0 === conditionsLengthRef.current ? 'field' : 'closed');
|
|
10
14
|
}, [
|
|
15
|
+
menuState,
|
|
11
16
|
selectedField,
|
|
12
17
|
isFocused,
|
|
13
18
|
setInputText,
|
|
@@ -7,6 +7,7 @@ interface MenuFlowDeps {
|
|
|
7
7
|
editingSegment: string | null;
|
|
8
8
|
setEditingSegment: (segment: ChipSegment | null) => void;
|
|
9
9
|
setSegmentFilterText: (text: string) => void;
|
|
10
|
+
resetSegmentTyping: () => void;
|
|
10
11
|
};
|
|
11
12
|
selectedField: FieldMetadata | null;
|
|
12
13
|
selectedOperator: FilterOperator | null;
|
|
@@ -34,6 +35,7 @@ export declare const useMenuFlow: ({ editing, selectedField, selectedOperator, f
|
|
|
34
35
|
handleValueSelect: (val: string | number | boolean) => void;
|
|
35
36
|
handleMultiCommit: (values: Array<string | number | boolean>) => void;
|
|
36
37
|
handleBuildingValueChange: (preview: string | undefined) => void;
|
|
38
|
+
handleMultiSelectToggle: () => void;
|
|
37
39
|
handleRangeSelect: (from: string, to: string) => void;
|
|
38
40
|
handleCustomValueCommit: (customText: string) => void;
|
|
39
41
|
handleCustomAttributeCommit: (customText: string) => void;
|
|
@@ -109,6 +109,13 @@ const useMenuFlow = ({ editing, selectedField, selectedOperator, fields, inputRe
|
|
|
109
109
|
}, [
|
|
110
110
|
setBuildingMultiValue
|
|
111
111
|
]);
|
|
112
|
+
const handleMultiSelectToggle = useCallback(()=>{
|
|
113
|
+
if ('value' === editing.editingSegment) editing.resetSegmentTyping();
|
|
114
|
+
else setInputText('');
|
|
115
|
+
}, [
|
|
116
|
+
editing,
|
|
117
|
+
setInputText
|
|
118
|
+
]);
|
|
112
119
|
const handleRangeSelect = useCallback((from, to)=>{
|
|
113
120
|
if (!selectedField || !selectedOperator) return;
|
|
114
121
|
const isEditing = !!editing.editingChipId;
|
|
@@ -184,6 +191,7 @@ const useMenuFlow = ({ editing, selectedField, selectedOperator, fields, inputRe
|
|
|
184
191
|
handleValueSelect,
|
|
185
192
|
handleMultiCommit,
|
|
186
193
|
handleBuildingValueChange,
|
|
194
|
+
handleMultiSelectToggle,
|
|
187
195
|
handleRangeSelect,
|
|
188
196
|
handleCustomValueCommit,
|
|
189
197
|
handleCustomAttributeCommit
|
|
@@ -3,6 +3,10 @@ import { chipIdToConditionIndex, getFieldValues, hasStaticAllowlist, isDatePrese
|
|
|
3
3
|
const findMatchingFieldValue = (fieldValues, text)=>fieldValues.find((v)=>v.label.toLowerCase() === text.toLowerCase() || String(v.value).toLowerCase() === text.toLowerCase());
|
|
4
4
|
const isValidFieldValue = (fieldValues, v)=>fieldValues.some((opt)=>opt.value === v || String(opt.value).toLowerCase() === String(v).toLowerCase());
|
|
5
5
|
const getInvalidValueIndices = (field, values)=>{
|
|
6
|
+
if (field.validate) return values.reduce((acc, v, idx)=>{
|
|
7
|
+
if (field.validate(v)) acc.push(idx);
|
|
8
|
+
return acc;
|
|
9
|
+
}, []);
|
|
6
10
|
if (!hasStaticAllowlist(field)) return [];
|
|
7
11
|
const fv = getFieldValues(field);
|
|
8
12
|
if (0 === fv.length) return [];
|
|
@@ -25,14 +29,21 @@ const resolveFieldValue = (field, text)=>{
|
|
|
25
29
|
const resolveSingleValue = (field, trimmed)=>{
|
|
26
30
|
const fv = getFieldValues(field);
|
|
27
31
|
const match = findMatchingFieldValue(fv, trimmed);
|
|
32
|
+
const raw = match ? match.value : trimmed;
|
|
33
|
+
const resolved = field.normalize ? field.normalize(raw) : raw;
|
|
34
|
+
if (field.validate) return {
|
|
35
|
+
resolved,
|
|
36
|
+
error: field.validate(resolved) ? true : void 0
|
|
37
|
+
};
|
|
28
38
|
return {
|
|
29
|
-
resolved
|
|
39
|
+
resolved,
|
|
30
40
|
error: hasStaticAllowlist(field) && fv.length > 0 && !match ? true : void 0
|
|
31
41
|
};
|
|
32
42
|
};
|
|
33
43
|
const resolveMultiValues = (field, trimmed)=>{
|
|
34
44
|
const parts = trimmed.split(',').map((s)=>s.trim()).filter(Boolean);
|
|
35
|
-
const
|
|
45
|
+
const raw = parts.map((part)=>resolveFieldValue(field, part));
|
|
46
|
+
const resolved = field.normalize ? raw.map((v)=>field.normalize(v)) : raw;
|
|
36
47
|
const error = getInvalidValueIndices(field, resolved).length > 0 ? true : void 0;
|
|
37
48
|
return {
|
|
38
49
|
resolved,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { FilterInput, type FilterInputProps } from './FilterInput';
|
|
2
2
|
export { FilterInputChip, type FilterInputChipProps } from './FilterInputField';
|
|
3
3
|
export { FilterInputFieldMenu, type FilterInputFieldMenuProps, FilterInputOperatorMenu, type FilterInputOperatorMenuProps, FilterInputValueMenu, type FilterInputValueMenuProps, type ValueOption, } from './FilterInputMenu';
|
|
4
|
-
export { createStatusCodeSuggestions, type FilterParseError, isFilterParseError, parseExpression, type StatusCodeSuggestionsOptions, serializeExpression, } from './lib';
|
|
4
|
+
export { createStatusCodeInputFilter, createStatusCodeNormalizer, createStatusCodeSuggestions, createStatusCodeValidator, type FilterParseError, isFilterParseError, parseExpression, type StatusCodeSuggestionsOptions, serializeExpression, } from './lib';
|
|
5
5
|
export type { Condition, ExprNode, FieldMetadata, FieldType, FieldValueOption, FilterInputChipData, FilterInputChipVariant, FilterOperator, Group, } from './types';
|