@wallarm-org/design-system 0.70.0 → 0.71.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/FilterInputErrors/parseFilterInputErrors.js +7 -5
- package/dist/components/FilterInput/FilterInputField/FilterInputChip/ChipSearchInput.js +2 -1
- package/dist/components/FilterInput/FilterInputField/FilterInputChip/FilterInputChip.js +12 -9
- package/dist/components/FilterInput/FilterInputField/FilterInputChip/Segment.js +2 -2
- package/dist/components/FilterInput/FilterInputField/FilterInputChip/classes.d.ts +8 -1
- package/dist/components/FilterInput/FilterInputField/FilterInputChip/classes.js +4 -2
- package/dist/components/FilterInput/FilterInputField/FilterInputChip/constants.d.ts +3 -0
- package/dist/components/FilterInput/FilterInputField/FilterInputChip/constants.js +2 -1
- package/dist/components/FilterInput/FilterInputField/FilterInputChip/model/useSizerWidth.d.ts +5 -2
- package/dist/components/FilterInput/FilterInputField/FilterInputChip/model/useSizerWidth.js +5 -4
- package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/FilterInputValueMenu.d.ts +2 -0
- package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/FilterInputValueMenu.js +2 -4
- package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/ValueMenuItem.js +14 -6
- package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/valueOptionSearchText.d.ts +7 -0
- package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/valueOptionSearchText.js +9 -0
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useBlurCommit.d.ts +8 -1
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useBlurCommit.js +40 -3
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useChipEditing.js +7 -6
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useFilterInputAutocomplete.d.ts +5 -4
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useFilterInputAutocomplete.js +38 -2
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useResumeBuilding.d.ts +32 -0
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useResumeBuilding.js +75 -0
- package/dist/components/FilterInput/hooks/useFilterInputExpression/buildChips.js +17 -5
- package/dist/components/FilterInput/hooks/useFilterInputExpression/useFilterInputExpression.js +5 -0
- package/dist/components/FilterInput/lib/conditions.d.ts +9 -1
- package/dist/components/FilterInput/lib/conditions.js +5 -1
- package/dist/components/FilterInput/lib/index.d.ts +1 -1
- package/dist/components/FilterInput/lib/index.js +2 -2
- package/dist/components/FilterInput/types.d.ts +8 -0
- package/dist/metadata/components.json +2 -2
- package/package.json +1 -1
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { SEGMENT_VARIANT } from "../FilterInputField/FilterInputChip/index.js";
|
|
2
|
-
import { getFieldValues, hasStaticAllowlist, isNoValueOperator, isValidFieldValue } from "../lib/index.js";
|
|
2
|
+
import { getFieldValues, hasStaticAllowlist, incompleteTripletError, isEmptyFilterValue, isNoValueOperator, isValidFieldValue } from "../lib/index.js";
|
|
3
3
|
const isPairValueMissing = (condition)=>{
|
|
4
4
|
const pair = condition.pair;
|
|
5
5
|
if (!pair) return true;
|
|
6
6
|
if (pair.operator && isNoValueOperator(pair.operator)) return false;
|
|
7
|
-
|
|
8
|
-
return null == value || '' === value || Array.isArray(value) && 0 === value.length;
|
|
7
|
+
return isEmptyFilterValue(pair.value);
|
|
9
8
|
};
|
|
10
9
|
const parseFilterInputErrors = (conditions, fields)=>{
|
|
11
10
|
const errors = [];
|
|
@@ -13,9 +12,12 @@ const parseFilterInputErrors = (conditions, fields)=>{
|
|
|
13
12
|
const field = fields.find((f)=>f.name === condition.field);
|
|
14
13
|
const baseIsNoValue = null != condition.operator && isNoValueOperator(condition.operator);
|
|
15
14
|
if (field?.pairedField && !baseIsNoValue && isPairValueMissing(condition)) errors.push(`${field.pairedField.label} is required`);
|
|
16
|
-
if (!condition.error) continue;
|
|
17
15
|
const label = field?.label || condition.field;
|
|
18
|
-
|
|
16
|
+
if (field && !field.pairedField && !condition.error && incompleteTripletError(condition.operator, condition.value)) {
|
|
17
|
+
errors.push(`${label} is incomplete`);
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (condition.error) switch(condition.error){
|
|
19
21
|
case SEGMENT_VARIANT.attribute:
|
|
20
22
|
errors.push(`Unknown field ${condition.field}`);
|
|
21
23
|
break;
|
|
@@ -20,6 +20,7 @@ const ChipSearchInput = ()=>{
|
|
|
20
20
|
ref: inputRef,
|
|
21
21
|
type: "text",
|
|
22
22
|
role: "combobox",
|
|
23
|
+
size: 1,
|
|
23
24
|
"aria-expanded": menuOpen,
|
|
24
25
|
"aria-invalid": error,
|
|
25
26
|
"aria-label": "Filter value",
|
|
@@ -31,7 +32,7 @@ const ChipSearchInput = ()=>{
|
|
|
31
32
|
style: {
|
|
32
33
|
width: `${inputWidth}px`
|
|
33
34
|
},
|
|
34
|
-
className: "h-22 border-none bg-transparent p-0 text-sm shadow-none outline-none ring-0"
|
|
35
|
+
className: "h-22 min-w-0 border-none bg-transparent p-0 text-sm shadow-none outline-none ring-0"
|
|
35
36
|
}),
|
|
36
37
|
/*#__PURE__*/ jsx("span", {
|
|
37
38
|
ref: sizerRef,
|
|
@@ -2,7 +2,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useCallback, useRef } from "react";
|
|
3
3
|
import { cn } from "../../../../utils/cn.js";
|
|
4
4
|
import { ChipSearchInput } from "./ChipSearchInput.js";
|
|
5
|
-
import { chipVariants } from "./classes.js";
|
|
5
|
+
import { chipVariants, emptyValueHitTarget } from "./classes.js";
|
|
6
6
|
import { useEditingContext } from "./context/EditingContext.js";
|
|
7
7
|
import { FilterInputRemoveButton } from "./FilterInputRemoveButton.js";
|
|
8
8
|
import { PairSeparator } from "./PairSeparator.js";
|
|
@@ -10,11 +10,13 @@ import { Segment } from "./Segment.js";
|
|
|
10
10
|
import { SEGMENT_VARIANT } from "./segmentVariant.js";
|
|
11
11
|
const FilterInputChip = ({ ref, chipId, attribute, operator, value, error = false, valueParts, valueSeparator, errorValueIndices, building = false, disabled = false, pair, onRemove, onSegmentClick, onPairSegmentClick, className, ...props })=>{
|
|
12
12
|
const interactive = !disabled;
|
|
13
|
-
const hasError = !!error;
|
|
14
13
|
const internalRef = useRef(null);
|
|
15
14
|
const editing = useEditingContext();
|
|
16
15
|
const isEditingThisChip = null != editing && null != editing.editingSegment && (building ? null == editing.editingChipId : null != chipId && editing.editingChipId === chipId);
|
|
17
16
|
const activeSegment = isEditingThisChip ? editing.editingSegment : null;
|
|
17
|
+
const effectiveError = isEditingThisChip ? false : error;
|
|
18
|
+
const effectivePairError = isEditingThisChip ? void 0 : pair?.error;
|
|
19
|
+
const hasError = !!effectiveError || !!effectivePairError;
|
|
18
20
|
const handleSegmentClick = useCallback((segment, e)=>{
|
|
19
21
|
if (!onSegmentClick) return;
|
|
20
22
|
if (activeSegment === segment) return;
|
|
@@ -62,7 +64,7 @@ const FilterInputChip = ({ ref, chipId, attribute, operator, value, error = fals
|
|
|
62
64
|
interactive,
|
|
63
65
|
disabled,
|
|
64
66
|
building
|
|
65
|
-
}), 'max-w-[320px]', className),
|
|
67
|
+
}), pair ? 'max-w-[380px]' : 'max-w-[320px]', className),
|
|
66
68
|
"data-slot": "filter-input-condition-chip",
|
|
67
69
|
...building && {
|
|
68
70
|
'data-building': ''
|
|
@@ -72,7 +74,7 @@ const FilterInputChip = ({ ref, chipId, attribute, operator, value, error = fals
|
|
|
72
74
|
/*#__PURE__*/ jsx(Segment, {
|
|
73
75
|
variant: SEGMENT_VARIANT.attribute,
|
|
74
76
|
className: "shrink-0",
|
|
75
|
-
error: true ===
|
|
77
|
+
error: true === effectiveError || effectiveError === SEGMENT_VARIANT.attribute,
|
|
76
78
|
onClick: interactive ? (e)=>handleSegmentClick(SEGMENT_VARIANT.attribute, e) : void 0,
|
|
77
79
|
onMouseDown: interactive && building ? handleSegmentMouseDown : void 0,
|
|
78
80
|
...segmentEditProps(SEGMENT_VARIANT.attribute),
|
|
@@ -86,10 +88,10 @@ const FilterInputChip = ({ ref, chipId, attribute, operator, value, error = fals
|
|
|
86
88
|
...segmentEditProps(SEGMENT_VARIANT.operator),
|
|
87
89
|
children: operator ?? ''
|
|
88
90
|
}),
|
|
89
|
-
(value || baseActiveSegment === SEGMENT_VARIANT.value) && /*#__PURE__*/ jsx(Segment, {
|
|
91
|
+
(value || baseActiveSegment === SEGMENT_VARIANT.value || !pair && effectiveError === SEGMENT_VARIANT.value) && /*#__PURE__*/ jsx(Segment, {
|
|
90
92
|
variant: SEGMENT_VARIANT.value,
|
|
91
|
-
className:
|
|
92
|
-
error: baseActiveSegment !== SEGMENT_VARIANT.value && (true ===
|
|
93
|
+
className: value || pair ? pair ? 'max-w-[90px] shrink-0' : 'min-w-0' : emptyValueHitTarget,
|
|
94
|
+
error: baseActiveSegment !== SEGMENT_VARIANT.value && (true === effectiveError || effectiveError === SEGMENT_VARIANT.value),
|
|
93
95
|
valueParts: valueParts,
|
|
94
96
|
valueSeparator: valueSeparator,
|
|
95
97
|
errorValueIndices: errorValueIndices,
|
|
@@ -104,6 +106,7 @@ const FilterInputChip = ({ ref, chipId, attribute, operator, value, error = fals
|
|
|
104
106
|
/*#__PURE__*/ jsx(Segment, {
|
|
105
107
|
variant: SEGMENT_VARIANT.attribute,
|
|
106
108
|
className: "shrink-0",
|
|
109
|
+
onClick: interactive && pair.error ? (e)=>handlePairSegmentClick(pair.operator ? SEGMENT_VARIANT.value : SEGMENT_VARIANT.operator, e) : void 0,
|
|
107
110
|
children: pair.attribute
|
|
108
111
|
}),
|
|
109
112
|
(pair.operator || pairActiveSegment === SEGMENT_VARIANT.operator) && /*#__PURE__*/ jsx(Segment, {
|
|
@@ -115,8 +118,8 @@ const FilterInputChip = ({ ref, chipId, attribute, operator, value, error = fals
|
|
|
115
118
|
}),
|
|
116
119
|
(null != pair.value || pairActiveSegment === SEGMENT_VARIANT.value) && /*#__PURE__*/ jsx(Segment, {
|
|
117
120
|
variant: SEGMENT_VARIANT.value,
|
|
118
|
-
className:
|
|
119
|
-
error: pairActiveSegment !== SEGMENT_VARIANT.value && (true ===
|
|
121
|
+
className: pair.value ? 'min-w-0' : emptyValueHitTarget,
|
|
122
|
+
error: pairActiveSegment !== SEGMENT_VARIANT.value && (true === effectivePairError || effectivePairError === SEGMENT_VARIANT.value),
|
|
120
123
|
onClick: interactive ? (e)=>handlePairSegmentClick('value', e) : void 0,
|
|
121
124
|
...segmentEditProps(SEGMENT_VARIANT.value, 1),
|
|
122
125
|
children: pair.value ?? ''
|
|
@@ -2,7 +2,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useCallback, useContext, useEffect, useRef } from "react";
|
|
3
3
|
import { cn } from "../../../../utils/cn.js";
|
|
4
4
|
import { FilterInputContext } from "../../FilterInputContext/FilterInputContext.js";
|
|
5
|
-
import { segmentContainer, segmentTextVariants } from "./classes.js";
|
|
5
|
+
import { editingValueMinWidth, segmentContainer, segmentTextVariants } from "./classes.js";
|
|
6
6
|
import { CHAR_WIDTH_PX } from "./constants.js";
|
|
7
7
|
import { MultiValueSegment } from "./MultiValueSegment.js";
|
|
8
8
|
import { useSizerWidth } from "./model/useSizerWidth.js";
|
|
@@ -78,7 +78,7 @@ const Segment = ({ variant, children, className, error, editing, editText, onEdi
|
|
|
78
78
|
className: cn(segmentTextVariants({
|
|
79
79
|
variant,
|
|
80
80
|
error
|
|
81
|
-
}), 'bg-transparent outline-none p-0 m-0'),
|
|
81
|
+
}), 'bg-transparent outline-none p-0 m-0', variant === SEGMENT_VARIANT.value && editingValueMinWidth),
|
|
82
82
|
style: {
|
|
83
83
|
width: `${inputWidth}px`
|
|
84
84
|
}
|
|
@@ -7,12 +7,19 @@ export declare const chipVariants: (props?: ({
|
|
|
7
7
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
8
8
|
/** Segment container */
|
|
9
9
|
export declare const segmentContainer = "flex flex-col justify-center overflow-hidden leading-none";
|
|
10
|
+
/** Hit target for an empty required value: a small full-height box so the value
|
|
11
|
+
* is clickable and the trailing × can't sit on top of it (AS-1192). */
|
|
12
|
+
export declare const emptyValueHitTarget = "min-w-[4px] self-stretch";
|
|
13
|
+
/** Min width for the value edit input so an empty freeform value stays clickable. */
|
|
14
|
+
export declare const editingValueMinWidth = "min-w-[3.5rem]";
|
|
10
15
|
/** Segment text styles by variant */
|
|
11
16
|
export declare const segmentTextVariants: (props?: ({
|
|
12
17
|
variant?: "value" | "operator" | "attribute" | null | undefined;
|
|
13
18
|
error?: boolean | null | undefined;
|
|
14
19
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
15
|
-
/** Remove button styles — hidden by default, shown on chip hover or button focus
|
|
20
|
+
/** Remove button styles — hidden by default, shown on chip hover or button focus.
|
|
21
|
+
* `pointer-events-none` while invisible stops it swallowing clicks meant for the
|
|
22
|
+
* chip/input and silently deleting the chip (AS-1179). */
|
|
16
23
|
export declare const removeButtonVariants: (props?: ({
|
|
17
24
|
error?: boolean | null | undefined;
|
|
18
25
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
@@ -35,6 +35,8 @@ const chipVariants = cva(`h-22 group/chip relative flex items-center justify-cen
|
|
|
35
35
|
}
|
|
36
36
|
});
|
|
37
37
|
const segmentContainer = 'flex flex-col justify-center overflow-hidden leading-none';
|
|
38
|
+
const emptyValueHitTarget = 'min-w-[4px] self-stretch';
|
|
39
|
+
const editingValueMinWidth = 'min-w-[3.5rem]';
|
|
38
40
|
const segmentTextVariants = cva('truncate text-sm', {
|
|
39
41
|
variants: {
|
|
40
42
|
variant: {
|
|
@@ -68,7 +70,7 @@ const segmentTextVariants = cva('truncate text-sm', {
|
|
|
68
70
|
error: false
|
|
69
71
|
}
|
|
70
72
|
});
|
|
71
|
-
const removeButtonVariants = cva(`absolute -right-[13px] top-[-1px] bottom-[-1px] flex items-center justify-center p-0 cursor-pointer w-[18px] border border-solid border-l-0 rounded-r-8 opacity-0 group-hover/chip:opacity-100 focus:opacity-100 transition-opacity ${hiddenWhenSelected}`, {
|
|
73
|
+
const removeButtonVariants = cva(`absolute -right-[13px] top-[-1px] bottom-[-1px] flex items-center justify-center p-0 cursor-pointer w-[18px] border border-solid border-l-0 rounded-r-8 opacity-0 pointer-events-none group-hover/chip:opacity-100 group-hover/chip:pointer-events-auto focus:opacity-100 focus:pointer-events-auto transition-opacity ${hiddenWhenSelected}`, {
|
|
72
74
|
variants: {
|
|
73
75
|
error: {
|
|
74
76
|
true: 'border-border-danger bg-bg-light-danger text-text-danger',
|
|
@@ -79,4 +81,4 @@ const removeButtonVariants = cva(`absolute -right-[13px] top-[-1px] bottom-[-1px
|
|
|
79
81
|
error: false
|
|
80
82
|
}
|
|
81
83
|
});
|
|
82
|
-
export { chipVariants, removeButtonVariants, segmentContainer, segmentTextVariants };
|
|
84
|
+
export { chipVariants, editingValueMinWidth, emptyValueHitTarget, removeButtonVariants, segmentContainer, segmentTextVariants };
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/** Minimum input width in px */
|
|
2
2
|
export declare const MIN_INPUT_WIDTH = 4;
|
|
3
|
+
/** Maximum input width in px — caps the content-sized input so a long value
|
|
4
|
+
* can't grow it past the chip; beyond this it scrolls content natively. */
|
|
5
|
+
export declare const MAX_INPUT_WIDTH = 280;
|
|
3
6
|
/** Extra pixels added to measured width to prevent text clipping */
|
|
4
7
|
export declare const WIDTH_OFFSET = 1;
|
|
5
8
|
/** Approximate width of a single character in px (text-sm fallback) */
|
package/dist/components/FilterInput/FilterInputField/FilterInputChip/model/useSizerWidth.d.ts
CHANGED
|
@@ -6,11 +6,14 @@ interface UseSizerWidthOptions {
|
|
|
6
6
|
text: string;
|
|
7
7
|
/** Minimum width floor (defaults to MIN_INPUT_WIDTH) */
|
|
8
8
|
minWidth?: number;
|
|
9
|
+
/** Maximum width ceiling (defaults to MAX_INPUT_WIDTH) */
|
|
10
|
+
maxWidth?: number;
|
|
9
11
|
}
|
|
10
12
|
/**
|
|
11
13
|
* Measures input width from a hidden sizer span.
|
|
12
14
|
* Falls back to character count * CHAR_WIDTH_PX when the DOM element is unavailable.
|
|
13
|
-
* Returns the measured width + WIDTH_OFFSET to prevent text clipping
|
|
15
|
+
* Returns the measured width + WIDTH_OFFSET to prevent text clipping, clamped to
|
|
16
|
+
* [minWidth, maxWidth] so a long value can't widen the input past the chip.
|
|
14
17
|
*/
|
|
15
|
-
export declare const useSizerWidth: ({ sizerRef, text, minWidth, }: UseSizerWidthOptions) => number;
|
|
18
|
+
export declare const useSizerWidth: ({ sizerRef, text, minWidth, maxWidth, }: UseSizerWidthOptions) => number;
|
|
16
19
|
export {};
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
|
-
import { CHAR_WIDTH_PX, MIN_INPUT_WIDTH, WIDTH_OFFSET } from "../constants.js";
|
|
3
|
-
const useSizerWidth = ({ sizerRef, text, minWidth = MIN_INPUT_WIDTH })=>{
|
|
2
|
+
import { CHAR_WIDTH_PX, MAX_INPUT_WIDTH, MIN_INPUT_WIDTH, WIDTH_OFFSET } from "../constants.js";
|
|
3
|
+
const useSizerWidth = ({ sizerRef, text, minWidth = MIN_INPUT_WIDTH, maxWidth = MAX_INPUT_WIDTH })=>{
|
|
4
4
|
const [width, setWidth] = useState(minWidth);
|
|
5
5
|
useEffect(()=>{
|
|
6
6
|
const sizerWidth = sizerRef.current?.getBoundingClientRect().width ?? text.length * CHAR_WIDTH_PX;
|
|
7
|
-
setWidth(Math.max(minWidth, sizerWidth));
|
|
7
|
+
setWidth(Math.min(maxWidth, Math.max(minWidth, sizerWidth)));
|
|
8
8
|
}, [
|
|
9
9
|
text,
|
|
10
|
-
minWidth
|
|
10
|
+
minWidth,
|
|
11
|
+
maxWidth
|
|
11
12
|
]);
|
|
12
13
|
return width + WIDTH_OFFSET;
|
|
13
14
|
};
|
package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/FilterInputValueMenu.js
CHANGED
|
@@ -8,11 +8,9 @@ import { useValueMenuDisplayValues } from "./useValueMenuDisplayValues.js";
|
|
|
8
8
|
import { useValueMenuState } from "./useValueMenuState.js";
|
|
9
9
|
import { ValueMenuFooter } from "./ValueMenuFooter.js";
|
|
10
10
|
import { ValueMenuItem } from "./ValueMenuItem.js";
|
|
11
|
+
import { valueOptionSearchText } from "./valueOptionSearchText.js";
|
|
11
12
|
const FilterInputValueMenu = ({ values, onSelect, onCommit, open = false, onOpenChange, onEscape, multiSelect = false, initialValues = [], highlightValue, width = 'standard', positioning, onBuildingValueChange, onItemToggle, inputRef, filterText = '', menuRef, blurCommitRef, className })=>{
|
|
12
|
-
const filteredValues = useMemo(()=>filterAndSort(values, filterText,
|
|
13
|
-
v.label,
|
|
14
|
-
String(v.value)
|
|
15
|
-
]), [
|
|
13
|
+
const filteredValues = useMemo(()=>filterAndSort(values, filterText, valueOptionSearchText), [
|
|
16
14
|
values,
|
|
17
15
|
filterText
|
|
18
16
|
]);
|
|
@@ -15,13 +15,21 @@ const ValueMenuItem = ({ option, isChecked, isPending, multiSelect, onSelect, re
|
|
|
15
15
|
type: "secondary",
|
|
16
16
|
variant: "default",
|
|
17
17
|
children: option.badge.text
|
|
18
|
-
}) : /*#__PURE__*/
|
|
18
|
+
}) : /*#__PURE__*/ jsxs("div", {
|
|
19
19
|
className: "min-w-0",
|
|
20
|
-
children:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
children: [
|
|
21
|
+
/*#__PURE__*/ jsx(Text, {
|
|
22
|
+
size: "sm",
|
|
23
|
+
truncate: true,
|
|
24
|
+
children: option.label
|
|
25
|
+
}),
|
|
26
|
+
option.description && /*#__PURE__*/ jsx(Text, {
|
|
27
|
+
size: "xs",
|
|
28
|
+
color: "secondary",
|
|
29
|
+
truncate: true,
|
|
30
|
+
children: option.description
|
|
31
|
+
})
|
|
32
|
+
]
|
|
25
33
|
}),
|
|
26
34
|
multiSelect ? /*#__PURE__*/ jsx("div", {
|
|
27
35
|
className: "flex shrink-0 items-start justify-end py-2 ml-auto",
|
package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/valueOptionSearchText.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ValueOption } from './FilterInputValueMenu';
|
|
2
|
+
/**
|
|
3
|
+
* Searchable strings for a value option: its label, its stringified value, and
|
|
4
|
+
* its optional `description` (so a path fragment finds the row). Fed to
|
|
5
|
+
* `filterAndSort`. The description entry is omitted when absent.
|
|
6
|
+
*/
|
|
7
|
+
export declare const valueOptionSearchText: (option: Pick<ValueOption, "value" | "label" | "description">) => string[];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { RefObject } from 'react';
|
|
2
2
|
import type { FieldMetadata, FilterOperator, UpsertCondition } from '../../types';
|
|
3
|
+
import type { BuildingBase } from './useAutocompleteState';
|
|
3
4
|
interface UseBlurCommitDeps {
|
|
4
5
|
selectedField: FieldMetadata | null;
|
|
5
6
|
selectedOperator: FilterOperator | null;
|
|
@@ -9,6 +10,12 @@ interface UseBlurCommitDeps {
|
|
|
9
10
|
handleCustomValueCommit: (text: string) => void;
|
|
10
11
|
upsertCondition: UpsertCondition;
|
|
11
12
|
resetState: () => void;
|
|
13
|
+
/** Which triplet is being built: 0 = base, 1 = paired second. */
|
|
14
|
+
buildingSide: 0 | 1;
|
|
15
|
+
setBuildingSide: (side: 0 | 1) => void;
|
|
16
|
+
/** Base triplet stashed while building a paired chip's second value. */
|
|
17
|
+
buildingBase: BuildingBase | null;
|
|
18
|
+
setBuildingBase: (base: BuildingBase | null) => void;
|
|
12
19
|
/** Indirection ref breaking the useMenuFlow ↔ useBlurCommit cycle. */
|
|
13
20
|
commitBuildingOnBlurRef: RefObject<() => boolean>;
|
|
14
21
|
/** Same indirection for force-commit (area-click → incomplete becomes error). */
|
|
@@ -20,7 +27,7 @@ interface UseBlurCommitDeps {
|
|
|
20
27
|
* editable. Re-entry-guarded: refs cleared synchronously so concurrent callers
|
|
21
28
|
* (multiple onOpenChange + blur in one tick) don't create duplicate chips.
|
|
22
29
|
*/
|
|
23
|
-
export declare const useBlurCommit: ({ selectedField, selectedOperator, inputText, editingChipId, effectiveInsertIndexRef, handleCustomValueCommit, upsertCondition, resetState, commitBuildingOnBlurRef, commitBuildingForceRef, }: UseBlurCommitDeps) => {
|
|
30
|
+
export declare const useBlurCommit: ({ selectedField, selectedOperator, inputText, editingChipId, effectiveInsertIndexRef, handleCustomValueCommit, upsertCondition, resetState, buildingSide, setBuildingSide, buildingBase, setBuildingBase, commitBuildingOnBlurRef, commitBuildingForceRef, }: UseBlurCommitDeps) => {
|
|
24
31
|
commitBuildingOnBlur: () => boolean;
|
|
25
32
|
hasIncompleteBuilding: () => boolean;
|
|
26
33
|
};
|
|
@@ -1,16 +1,34 @@
|
|
|
1
1
|
import { useCallback, useLayoutEffect, useRef } from "react";
|
|
2
2
|
import { SEGMENT_VARIANT } from "../../FilterInputField/FilterInputChip/index.js";
|
|
3
3
|
import { isBuildingComplete, isNoValueOperator } from "../../lib/index.js";
|
|
4
|
-
const useBlurCommit = ({ selectedField, selectedOperator, inputText, editingChipId, effectiveInsertIndexRef, handleCustomValueCommit, upsertCondition, resetState, commitBuildingOnBlurRef, commitBuildingForceRef })=>{
|
|
4
|
+
const useBlurCommit = ({ selectedField, selectedOperator, inputText, editingChipId, effectiveInsertIndexRef, handleCustomValueCommit, upsertCondition, resetState, buildingSide, setBuildingSide, buildingBase, setBuildingBase, commitBuildingOnBlurRef, commitBuildingForceRef })=>{
|
|
5
5
|
const selectedFieldRef = useRef(selectedField);
|
|
6
6
|
const selectedOperatorRef = useRef(selectedOperator);
|
|
7
7
|
const inputTextRef = useRef(inputText);
|
|
8
|
+
const buildingSideRef = useRef(buildingSide);
|
|
9
|
+
const buildingBaseRef = useRef(buildingBase);
|
|
8
10
|
useLayoutEffect(()=>{
|
|
9
11
|
selectedFieldRef.current = selectedField;
|
|
10
12
|
selectedOperatorRef.current = selectedOperator;
|
|
11
13
|
inputTextRef.current = inputText;
|
|
14
|
+
buildingSideRef.current = buildingSide;
|
|
15
|
+
buildingBaseRef.current = buildingBase;
|
|
12
16
|
});
|
|
13
17
|
const committingRef = useRef(false);
|
|
18
|
+
const commitPairedBaseWithErroredValue = useCallback((base, field, operator)=>{
|
|
19
|
+
upsertCondition(base.field, base.operator, base.value, null, effectiveInsertIndexRef.current);
|
|
20
|
+
const pairError = operator ? SEGMENT_VARIANT.value : true;
|
|
21
|
+
upsertCondition(field, operator ?? void 0, null, void 0, void 0, pairError, void 0, 1);
|
|
22
|
+
setBuildingBase(null);
|
|
23
|
+
setBuildingSide(0);
|
|
24
|
+
resetState();
|
|
25
|
+
}, [
|
|
26
|
+
upsertCondition,
|
|
27
|
+
resetState,
|
|
28
|
+
effectiveInsertIndexRef,
|
|
29
|
+
setBuildingBase,
|
|
30
|
+
setBuildingSide
|
|
31
|
+
]);
|
|
14
32
|
const commitBuildingOnBlur = useCallback(()=>{
|
|
15
33
|
if (committingRef.current) return false;
|
|
16
34
|
const field = selectedFieldRef.current;
|
|
@@ -19,6 +37,19 @@ const useBlurCommit = ({ selectedField, selectedOperator, inputText, editingChip
|
|
|
19
37
|
if (!field) return false;
|
|
20
38
|
if (editingChipId) return false;
|
|
21
39
|
const hasTypedValue = !!operator && !isNoValueOperator(operator) && text.length > 0;
|
|
40
|
+
if (!hasTypedValue && 1 === buildingSideRef.current && buildingBaseRef.current) {
|
|
41
|
+
committingRef.current = true;
|
|
42
|
+
try {
|
|
43
|
+
const base = buildingBaseRef.current;
|
|
44
|
+
selectedFieldRef.current = null;
|
|
45
|
+
selectedOperatorRef.current = null;
|
|
46
|
+
inputTextRef.current = '';
|
|
47
|
+
commitPairedBaseWithErroredValue(base, field, operator);
|
|
48
|
+
return true;
|
|
49
|
+
} finally{
|
|
50
|
+
committingRef.current = false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
22
53
|
if (!isBuildingComplete(field, operator, null) && !hasTypedValue) return false;
|
|
23
54
|
committingRef.current = true;
|
|
24
55
|
try {
|
|
@@ -40,7 +71,8 @@ const useBlurCommit = ({ selectedField, selectedOperator, inputText, editingChip
|
|
|
40
71
|
handleCustomValueCommit,
|
|
41
72
|
upsertCondition,
|
|
42
73
|
resetState,
|
|
43
|
-
effectiveInsertIndexRef
|
|
74
|
+
effectiveInsertIndexRef,
|
|
75
|
+
commitPairedBaseWithErroredValue
|
|
44
76
|
]);
|
|
45
77
|
useLayoutEffect(()=>{
|
|
46
78
|
commitBuildingOnBlurRef.current = commitBuildingOnBlur;
|
|
@@ -68,6 +100,10 @@ const useBlurCommit = ({ selectedField, selectedOperator, inputText, editingChip
|
|
|
68
100
|
handleCustomValueCommit(text);
|
|
69
101
|
return true;
|
|
70
102
|
}
|
|
103
|
+
if (1 === buildingSideRef.current && buildingBaseRef.current) {
|
|
104
|
+
commitPairedBaseWithErroredValue(buildingBaseRef.current, field, operator);
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
71
107
|
if (isBuildingComplete(field, operator, null)) {
|
|
72
108
|
upsertCondition(field, operator, null, void 0, effectiveInsertIndexRef.current);
|
|
73
109
|
resetState();
|
|
@@ -85,7 +121,8 @@ const useBlurCommit = ({ selectedField, selectedOperator, inputText, editingChip
|
|
|
85
121
|
handleCustomValueCommit,
|
|
86
122
|
upsertCondition,
|
|
87
123
|
resetState,
|
|
88
|
-
effectiveInsertIndexRef
|
|
124
|
+
effectiveInsertIndexRef,
|
|
125
|
+
commitPairedBaseWithErroredValue
|
|
89
126
|
]);
|
|
90
127
|
useLayoutEffect(()=>{
|
|
91
128
|
commitBuildingForceRef.current = commitBuildingForce;
|
|
@@ -64,17 +64,18 @@ const useChipEditing = ({ conditions, chips, fields, setMenuAnchor, setSelectedF
|
|
|
64
64
|
const field = condition && fieldsRef.current.find((f)=>f.name === condition.field);
|
|
65
65
|
const pairedField = field ? field.pairedField : void 0;
|
|
66
66
|
const chip = chipsRef.current.find((c)=>c.id === chipId);
|
|
67
|
-
if (!condition
|
|
67
|
+
if (!condition || !pairedField || !chip?.pair) return;
|
|
68
|
+
const rawOperator = getOperatorFromLabel(chip.pair.operator || '', pairedField.type) ?? condition.pair?.operator ?? null;
|
|
69
|
+
const targetSegment = segment !== SEGMENT_VARIANT.value || rawOperator ? segment : SEGMENT_VARIANT.operator;
|
|
68
70
|
setMenuAnchor(anchorEl);
|
|
69
71
|
setEditingChipId(chipId);
|
|
70
72
|
setEditingSide(1);
|
|
71
73
|
setSelectedField(pairedField);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
setSegmentFilterText(segment === SEGMENT_VARIANT.operator ? chip.pair.operator ?? '' : chip.pair.value ?? '');
|
|
74
|
+
setSelectedOperator(targetSegment === SEGMENT_VARIANT.value ? rawOperator : null);
|
|
75
|
+
setEditingSegment(targetSegment);
|
|
76
|
+
setSegmentFilterText(targetSegment === SEGMENT_VARIANT.operator ? chip.pair.operator ?? '' : chip.pair.value ?? '');
|
|
76
77
|
setUserHasTyped(false);
|
|
77
|
-
setMenuState(SEGMENT_TO_MENU[
|
|
78
|
+
setMenuState(SEGMENT_TO_MENU[targetSegment]);
|
|
78
79
|
}, [
|
|
79
80
|
setMenuAnchor,
|
|
80
81
|
setSelectedField,
|
package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useFilterInputAutocomplete.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { RefObject } from 'react';
|
|
2
|
+
import { type ChipSegment } from '../../FilterInputField/FilterInputChip';
|
|
2
3
|
import type { Condition, FieldMetadata, FilterInputChipData, UpsertCondition } from '../../types';
|
|
3
4
|
interface UseFilterInputAutocompleteOptions {
|
|
4
5
|
fields: FieldMetadata[];
|
|
@@ -49,10 +50,10 @@ export declare const useFilterInputAutocomplete: ({ fields, conditions, chips, u
|
|
|
49
50
|
handleMenuDiscard: () => void;
|
|
50
51
|
/** Hard reset for paste/clipboard flows — scraps in-progress building. */
|
|
51
52
|
resetAutocompleteState: (continueBuilding?: boolean) => void;
|
|
52
|
-
handleChipClick: (chipId: string, segment:
|
|
53
|
-
handlePairChipClick: (chipId: string, segment:
|
|
54
|
-
handleBuildingChipClick: (segment:
|
|
55
|
-
switchEditSegment: (targetSegment:
|
|
53
|
+
handleChipClick: (chipId: string, segment: ChipSegment, anchorEl: HTMLElement) => void;
|
|
54
|
+
handlePairChipClick: (chipId: string, segment: ChipSegment, anchorEl: HTMLElement) => void;
|
|
55
|
+
handleBuildingChipClick: (segment: ChipSegment, anchorEl: HTMLElement) => void;
|
|
56
|
+
switchEditSegment: (targetSegment: ChipSegment) => boolean;
|
|
56
57
|
removeEditingChip: () => void;
|
|
57
58
|
handleConnectorChange: (connectorId: string, value: "and" | "or") => void;
|
|
58
59
|
handleChipRemove: (chipId: string) => void;
|
package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useFilterInputAutocomplete.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
1
2
|
import { SEGMENT_VARIANT } from "../../FilterInputField/FilterInputChip/index.js";
|
|
2
3
|
import { useDateRange } from "../../FilterInputMenu/FilterInputDateValueMenu/hooks.js";
|
|
3
4
|
import { deriveAutocompleteValues } from "./lib/index.js";
|
|
@@ -11,6 +12,7 @@ import { useInputHandlers } from "./useInputHandlers.js";
|
|
|
11
12
|
import { useMenuFlow } from "./useMenuFlow/index.js";
|
|
12
13
|
import { useMenuPositioning } from "./useMenuPositioning.js";
|
|
13
14
|
import { useResetState } from "./useResetState.js";
|
|
15
|
+
import { useResumeBuilding } from "./useResumeBuilding.js";
|
|
14
16
|
import { useSegmentEditFlow } from "./useSegmentEditFlow.js";
|
|
15
17
|
const useFilterInputAutocomplete = ({ fields, conditions, chips, upsertCondition, removeCondition, removeConditionAtIndex, clearAll, setConnectorValue, containerRef, buildingChipRef, inputRef })=>{
|
|
16
18
|
const state = useAutocompleteState({
|
|
@@ -123,6 +125,10 @@ const useFilterInputAutocomplete = ({ fields, conditions, chips, upsertCondition
|
|
|
123
125
|
handleCustomValueCommit,
|
|
124
126
|
upsertCondition,
|
|
125
127
|
resetState,
|
|
128
|
+
buildingSide,
|
|
129
|
+
setBuildingSide,
|
|
130
|
+
buildingBase,
|
|
131
|
+
setBuildingBase,
|
|
126
132
|
commitBuildingOnBlurRef,
|
|
127
133
|
commitBuildingForceRef
|
|
128
134
|
});
|
|
@@ -170,6 +176,36 @@ const useFilterInputAutocomplete = ({ fields, conditions, chips, upsertCondition
|
|
|
170
176
|
setMenuState,
|
|
171
177
|
setMenuAnchor
|
|
172
178
|
});
|
|
179
|
+
const tryResumeBuilding = useResumeBuilding({
|
|
180
|
+
conditions,
|
|
181
|
+
fields,
|
|
182
|
+
removeConditionAtIndex,
|
|
183
|
+
setInsertIndex,
|
|
184
|
+
setBuildingBase,
|
|
185
|
+
setBuildingSide,
|
|
186
|
+
setSelectedField,
|
|
187
|
+
setSelectedOperator,
|
|
188
|
+
setBuildingMultiValue,
|
|
189
|
+
setInputText,
|
|
190
|
+
setMenuState,
|
|
191
|
+
resetMenuAnchor,
|
|
192
|
+
clearEditing: editing.clearEditing,
|
|
193
|
+
inputRef
|
|
194
|
+
});
|
|
195
|
+
const handleChipClick = useCallback((chipId, segment, anchorEl)=>{
|
|
196
|
+
if (tryResumeBuilding(chipId, segment, 0)) return;
|
|
197
|
+
editing.handleChipClick(chipId, segment, anchorEl);
|
|
198
|
+
}, [
|
|
199
|
+
tryResumeBuilding,
|
|
200
|
+
editing.handleChipClick
|
|
201
|
+
]);
|
|
202
|
+
const handlePairChipClick = useCallback((chipId, segment, anchorEl)=>{
|
|
203
|
+
if (tryResumeBuilding(chipId, segment, 1)) return;
|
|
204
|
+
editing.handlePairChipClick(chipId, segment, anchorEl);
|
|
205
|
+
}, [
|
|
206
|
+
tryResumeBuilding,
|
|
207
|
+
editing.handlePairChipClick
|
|
208
|
+
]);
|
|
173
209
|
const { isBuilding, buildingChipData, editingMultiValues, editingSingleValue, editingDateRange } = deriveAutocompleteValues({
|
|
174
210
|
editingChipId: editing.editingChipId,
|
|
175
211
|
selectedField,
|
|
@@ -204,8 +240,8 @@ const useFilterInputAutocomplete = ({ fields, conditions, chips, upsertCondition
|
|
|
204
240
|
handleMenuClose,
|
|
205
241
|
handleMenuDiscard,
|
|
206
242
|
resetAutocompleteState: resetState,
|
|
207
|
-
handleChipClick
|
|
208
|
-
handlePairChipClick
|
|
243
|
+
handleChipClick,
|
|
244
|
+
handlePairChipClick,
|
|
209
245
|
handleBuildingChipClick,
|
|
210
246
|
switchEditSegment,
|
|
211
247
|
removeEditingChip,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { RefObject } from 'react';
|
|
2
|
+
import { type ChipSegment } from '../../FilterInputField/FilterInputChip';
|
|
3
|
+
import type { Condition, FieldMetadata, FilterOperator, MenuState } from '../../types';
|
|
4
|
+
import type { BuildingBase } from './useAutocompleteState';
|
|
5
|
+
interface UseResumeBuildingDeps {
|
|
6
|
+
conditions: Condition[];
|
|
7
|
+
fields: FieldMetadata[];
|
|
8
|
+
removeConditionAtIndex: (index: number) => void;
|
|
9
|
+
setInsertIndex: (index: number | null) => void;
|
|
10
|
+
setBuildingBase: (base: BuildingBase | null) => void;
|
|
11
|
+
setBuildingSide: (side: 0 | 1) => void;
|
|
12
|
+
setSelectedField: (field: FieldMetadata | null) => void;
|
|
13
|
+
setSelectedOperator: (op: FilterOperator | null) => void;
|
|
14
|
+
setBuildingMultiValue: (val: string | undefined) => void;
|
|
15
|
+
setInputText: (text: string) => void;
|
|
16
|
+
setMenuState: (state: MenuState) => void;
|
|
17
|
+
resetMenuAnchor: () => void;
|
|
18
|
+
/** Exit any inline-edit so the resumed flow is a clean building chip. */
|
|
19
|
+
clearEditing: () => void;
|
|
20
|
+
inputRef: RefObject<HTMLInputElement | null>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Resume building a committed-but-incomplete chip instead of inline-editing it,
|
|
24
|
+
* reopening the menu at the first missing step so the building cascade — incl.
|
|
25
|
+
* the paired second triplet — carries on. Makes "press anywhere → red chip →
|
|
26
|
+
* click to continue" work for paired fields (AS-1179).
|
|
27
|
+
*
|
|
28
|
+
* Returns `true` when it took over (caller must skip inline-edit), `false` when
|
|
29
|
+
* the chip is complete (edit it normally).
|
|
30
|
+
*/
|
|
31
|
+
export declare const useResumeBuilding: ({ conditions, fields, removeConditionAtIndex, setInsertIndex, setBuildingBase, setBuildingSide, setSelectedField, setSelectedOperator, setBuildingMultiValue, setInputText, setMenuState, resetMenuAnchor, clearEditing, inputRef, }: UseResumeBuildingDeps) => (chipId: string, segment?: ChipSegment, side?: 0 | 1) => boolean;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import { SEGMENT_VARIANT } from "../../FilterInputField/FilterInputChip/index.js";
|
|
3
|
+
import { chipIdToConditionIndex, isEmptyFilterValue, isNoValueOperator } from "../../lib/index.js";
|
|
4
|
+
const useResumeBuilding = ({ conditions, fields, removeConditionAtIndex, setInsertIndex, setBuildingBase, setBuildingSide, setSelectedField, setSelectedOperator, setBuildingMultiValue, setInputText, setMenuState, resetMenuAnchor, clearEditing, inputRef })=>useCallback((chipId, segment, side = 0)=>{
|
|
5
|
+
const idx = chipIdToConditionIndex(chipId);
|
|
6
|
+
if (null === idx) return false;
|
|
7
|
+
const condition = conditions[idx];
|
|
8
|
+
if (!condition || condition.disabled) return false;
|
|
9
|
+
const field = fields.find((f)=>f.name === condition.field);
|
|
10
|
+
if (!field) return false;
|
|
11
|
+
const baseOperatorMissing = !condition.operator;
|
|
12
|
+
const baseOperatorTakesValue = null != condition.operator && !isNoValueOperator(condition.operator);
|
|
13
|
+
const baseValueMissing = baseOperatorTakesValue && isEmptyFilterValue(condition.value);
|
|
14
|
+
const startBuilding = (params)=>{
|
|
15
|
+
clearEditing();
|
|
16
|
+
removeConditionAtIndex(idx);
|
|
17
|
+
setInsertIndex(idx);
|
|
18
|
+
setBuildingBase(params.base);
|
|
19
|
+
setBuildingSide(params.side);
|
|
20
|
+
setSelectedField(params.selectedField);
|
|
21
|
+
setSelectedOperator(params.selectedOperator);
|
|
22
|
+
setBuildingMultiValue(void 0);
|
|
23
|
+
setInputText('');
|
|
24
|
+
resetMenuAnchor();
|
|
25
|
+
setMenuState(params.menuState);
|
|
26
|
+
requestAnimationFrame(()=>inputRef.current?.focus());
|
|
27
|
+
};
|
|
28
|
+
if (baseOperatorMissing) return false;
|
|
29
|
+
if (baseValueMissing) {
|
|
30
|
+
if (!field.pairedField && segment !== SEGMENT_VARIANT.value) return false;
|
|
31
|
+
startBuilding({
|
|
32
|
+
base: null,
|
|
33
|
+
side: 0,
|
|
34
|
+
selectedField: field,
|
|
35
|
+
selectedOperator: condition.operator ?? null,
|
|
36
|
+
menuState: 'value'
|
|
37
|
+
});
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
if (1 === side && segment === SEGMENT_VARIANT.value && field.pairedField && !isNoValueOperator(condition.operator)) {
|
|
41
|
+
const pairOperator = condition.pair?.operator;
|
|
42
|
+
const pairValueMissing = null != pairOperator && !isNoValueOperator(pairOperator) && isEmptyFilterValue(condition.pair?.value);
|
|
43
|
+
if (pairValueMissing) {
|
|
44
|
+
startBuilding({
|
|
45
|
+
base: {
|
|
46
|
+
field,
|
|
47
|
+
operator: condition.operator,
|
|
48
|
+
value: condition.value
|
|
49
|
+
},
|
|
50
|
+
side: 1,
|
|
51
|
+
selectedField: field.pairedField,
|
|
52
|
+
selectedOperator: pairOperator,
|
|
53
|
+
menuState: 'value'
|
|
54
|
+
});
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}, [
|
|
60
|
+
conditions,
|
|
61
|
+
fields,
|
|
62
|
+
removeConditionAtIndex,
|
|
63
|
+
setInsertIndex,
|
|
64
|
+
setBuildingBase,
|
|
65
|
+
setBuildingSide,
|
|
66
|
+
setSelectedField,
|
|
67
|
+
setSelectedOperator,
|
|
68
|
+
setBuildingMultiValue,
|
|
69
|
+
setInputText,
|
|
70
|
+
setMenuState,
|
|
71
|
+
resetMenuAnchor,
|
|
72
|
+
clearEditing,
|
|
73
|
+
inputRef
|
|
74
|
+
]);
|
|
75
|
+
export { useResumeBuilding };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SEGMENT_VARIANT } from "../../FilterInputField/FilterInputChip/index.js";
|
|
2
|
-
import { NO_VALUE_PLACEHOLDER, canBorrowCrossFieldLabel, findOptionByValue, findValueLabelInFields, getDateDisplayLabel, getInvalidValueIndices, getOperatorLabel, isNoValueOperator } from "../../lib/index.js";
|
|
2
|
+
import { NO_VALUE_PLACEHOLDER, canBorrowCrossFieldLabel, findOptionByValue, findValueLabelInFields, getDateDisplayLabel, getInvalidValueIndices, getOperatorLabel, incompleteTripletError, isEmptyFilterValue, isNoValueOperator } from "../../lib/index.js";
|
|
3
3
|
const INVALID_DATE = 'Invalid Date';
|
|
4
4
|
const DATE_RANGE_SEPARATOR = ' – ';
|
|
5
5
|
const MULTI_VALUE_SEPARATOR = ', ';
|
|
@@ -84,23 +84,35 @@ const buildMultiValueChip = (baseChip, condition, field, fields, chipError)=>{
|
|
|
84
84
|
};
|
|
85
85
|
};
|
|
86
86
|
const buildPairChip = (condition, field, fields)=>{
|
|
87
|
-
if (!
|
|
87
|
+
if (!field?.pairedField) return;
|
|
88
|
+
if (null != condition.operator && isNoValueOperator(condition.operator)) return;
|
|
88
89
|
const pf = field.pairedField;
|
|
90
|
+
if (!condition.pair) {
|
|
91
|
+
const baseComplete = null != condition.operator && !isEmptyFilterValue(condition.value);
|
|
92
|
+
if (!baseComplete) return;
|
|
93
|
+
return {
|
|
94
|
+
attribute: pf.label || pf.name,
|
|
95
|
+
value: '',
|
|
96
|
+
error: SEGMENT_VARIANT.value
|
|
97
|
+
};
|
|
98
|
+
}
|
|
89
99
|
const { operator, value, error } = condition.pair;
|
|
90
100
|
const displayValue = operator && isNoValueOperator(operator) ? NO_VALUE_PLACEHOLDER : resolveValueLabel(value, pf, fields) ?? String(value ?? '');
|
|
101
|
+
const valueRequiredButMissing = !(operator && isNoValueOperator(operator)) && isEmptyFilterValue(value);
|
|
102
|
+
const pairError = error || (valueRequiredButMissing ? SEGMENT_VARIANT.value : void 0);
|
|
91
103
|
return {
|
|
92
104
|
attribute: pf.label || pf.name,
|
|
93
105
|
operator: operator ? getOperatorLabel(operator, pf.type || DEFAULT_FIELD_TYPE) : void 0,
|
|
94
106
|
value: displayValue,
|
|
95
|
-
...
|
|
96
|
-
error
|
|
107
|
+
...pairError && {
|
|
108
|
+
error: pairError
|
|
97
109
|
}
|
|
98
110
|
};
|
|
99
111
|
};
|
|
100
112
|
const makeConditionChipBase = (i, conditions, fields, error)=>{
|
|
101
113
|
const condition = conditions[i];
|
|
102
114
|
if (!condition) return makeEmptyChip(i, error);
|
|
103
|
-
const chipError = condition.error || (error ? true : void 0);
|
|
115
|
+
const chipError = condition.error || (error ? true : void 0) || incompleteTripletError(condition.operator, condition.value);
|
|
104
116
|
const field = fields.find((f)=>f.name === condition.field);
|
|
105
117
|
const baseChip = buildBaseChip(i, condition, field);
|
|
106
118
|
if (condition.operator && isNoValueOperator(condition.operator)) return {
|
package/dist/components/FilterInput/hooks/useFilterInputExpression/useFilterInputExpression.js
CHANGED
|
@@ -155,6 +155,11 @@ const useFilterInputExpression = ({ fields, value, onChange, error, externalErro
|
|
|
155
155
|
return;
|
|
156
156
|
}
|
|
157
157
|
const condition = buildCondition(field, operator, val, error, dateOrigin);
|
|
158
|
+
if (editingChipId) {
|
|
159
|
+
const idx = chipIdToConditionIndex(editingChipId);
|
|
160
|
+
const prevCondition = null !== idx ? prev.conditions[idx] : void 0;
|
|
161
|
+
if (prevCondition?.pair && prevCondition.field === condition.field) condition.pair = prevCondition.pair;
|
|
162
|
+
}
|
|
158
163
|
const newConditions = applyCondition(prev.conditions, condition, editingChipId, atIndex);
|
|
159
164
|
const newConnectors = addConnectorIfNeeded(prev.connectors, newConditions.length, editingChipId, atIndex, prev.conditions.length);
|
|
160
165
|
applyState({
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
import type { FilterInputChipData } from '../types';
|
|
1
|
+
import type { ChipErrorSegment, Condition, FilterInputChipData, FilterOperator } from '../types';
|
|
2
2
|
/** Map a chip ID (e.g. "chip-2") back to condition index */
|
|
3
3
|
export declare const chipIdToConditionIndex: (chipId: string) => number | null;
|
|
4
|
+
/** A filter value is empty when null/undefined, '', or an empty array. */
|
|
5
|
+
export declare const isEmptyFilterValue: (value: Condition["value"] | undefined) => boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Error segment for an incomplete triplet, or `undefined` when complete:
|
|
8
|
+
* missing operator → whole triplet (`true`); value-bearing operator with an
|
|
9
|
+
* empty value → the value segment; no-value operator ("is set") → complete.
|
|
10
|
+
*/
|
|
11
|
+
export declare const incompleteTripletError: (operator: FilterOperator | null | undefined, value: Condition["value"] | undefined) => ChipErrorSegment | undefined;
|
|
4
12
|
/**
|
|
5
13
|
* Convert a condition-level insert index to a position in the chips array.
|
|
6
14
|
* When `afterConnector` is true, split AFTER the connector preceding the target chip.
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import { SEGMENT_VARIANT } from "../FilterInputField/FilterInputChip/segmentVariant.js";
|
|
1
2
|
import { CHIP_ID_PATTERN } from "./constants.js";
|
|
3
|
+
import { isNoValueOperator } from "./operators.js";
|
|
2
4
|
const chipIdToConditionIndex = (chipId)=>{
|
|
3
5
|
const match = chipId.match(CHIP_ID_PATTERN);
|
|
4
6
|
return match ? Number(match[1]) : null;
|
|
5
7
|
};
|
|
8
|
+
const isEmptyFilterValue = (value)=>null == value || '' === value || Array.isArray(value) && 0 === value.length;
|
|
9
|
+
const incompleteTripletError = (operator, value)=>operator ? isNoValueOperator(operator) ? void 0 : isEmptyFilterValue(value) ? SEGMENT_VARIANT.value : void 0 : true;
|
|
6
10
|
const findChipSplitIndex = (chips, conditionInsertIndex, afterConnector = false)=>{
|
|
7
11
|
let conditionCount = 0;
|
|
8
12
|
for(let i = 0; i < chips.length; i++)if ('chip' === chips[i].variant) {
|
|
@@ -15,4 +19,4 @@ const findChipSplitIndex = (chips, conditionInsertIndex, afterConnector = false)
|
|
|
15
19
|
}
|
|
16
20
|
return chips.length;
|
|
17
21
|
};
|
|
18
|
-
export { chipIdToConditionIndex, findChipSplitIndex };
|
|
22
|
+
export { chipIdToConditionIndex, findChipSplitIndex, incompleteTripletError, isEmptyFilterValue };
|
|
@@ -3,7 +3,7 @@ export { DATE_PRESETS, formatDateForChip, getDateDisplayLabel, isDatePreset, } f
|
|
|
3
3
|
export { applyAcceptChar } from './applyAcceptChar';
|
|
4
4
|
export { applyFieldValueTransforms } from './applyFieldValueTransforms';
|
|
5
5
|
export { applyKnownFieldHelpers, getKnownFieldSerializer } from './applyKnownFieldHelpers';
|
|
6
|
-
export { chipIdToConditionIndex, findChipSplitIndex } from './conditions';
|
|
6
|
+
export { chipIdToConditionIndex, findChipSplitIndex, incompleteTripletError, isEmptyFilterValue, } from './conditions';
|
|
7
7
|
export { CONNECTOR_ID_PATTERN, MENU_BASE_GUTTER, MENU_CHIP_GUTTER_OFFSET, NO_VALUE_OPERATORS, OPERATOR_LABELS, OPERATOR_LABELS_BY_TYPE, OPERATOR_SYMBOLS, OPERATORS_BY_TYPE, QUERY_BAR_SELECTOR, VARIANT_LABELS, } from './constants';
|
|
8
8
|
export { COUNTRY_OPTIONS } from './country';
|
|
9
9
|
export { type AnchorBounds, buildAnchoredRect, isMenuRelated, toAnchorBounds } from './dom';
|
|
@@ -2,7 +2,7 @@ import { DATE_PRESETS, formatDateForChip, getDateDisplayLabel, isDatePreset } fr
|
|
|
2
2
|
import { applyAcceptChar } from "./applyAcceptChar.js";
|
|
3
3
|
import { applyFieldValueTransforms } from "./applyFieldValueTransforms.js";
|
|
4
4
|
import { applyKnownFieldHelpers, getKnownFieldSerializer } from "./applyKnownFieldHelpers.js";
|
|
5
|
-
import { chipIdToConditionIndex, findChipSplitIndex } from "./conditions.js";
|
|
5
|
+
import { chipIdToConditionIndex, findChipSplitIndex, incompleteTripletError, isEmptyFilterValue } from "./conditions.js";
|
|
6
6
|
import { CONNECTOR_ID_PATTERN, MENU_BASE_GUTTER, MENU_CHIP_GUTTER_OFFSET, NO_VALUE_OPERATORS, OPERATORS_BY_TYPE, OPERATOR_LABELS, OPERATOR_LABELS_BY_TYPE, OPERATOR_SYMBOLS, QUERY_BAR_SELECTOR, VARIANT_LABELS } from "./constants.js";
|
|
7
7
|
import { COUNTRY_OPTIONS } from "./country/index.js";
|
|
8
8
|
import { buildAnchoredRect, isMenuRelated, toAnchorBounds } from "./dom.js";
|
|
@@ -15,4 +15,4 @@ import { SEGMENT_TO_MENU } from "./segmentMenu.js";
|
|
|
15
15
|
import { serializeExpression } from "./serializeExpression.js";
|
|
16
16
|
import { createStatusCodeInputFilter, createStatusCodeNormalizer, createStatusCodeSerializer, createStatusCodeSuggestions, createStatusCodeValidator } from "./statusCode/index.js";
|
|
17
17
|
import { canBorrowCrossFieldLabel, findMatchingFieldValue, getInvalidValueIndices, isValidFieldValue, validateValueForField } from "./validation.js";
|
|
18
|
-
export { CONNECTOR_ID_PATTERN, COUNTRY_OPTIONS, DATE_PRESETS, MENU_BASE_GUTTER, MENU_CHIP_GUTTER_OFFSET, NO_VALUE_OPERATORS, NO_VALUE_PLACEHOLDER, OPERATORS_BY_TYPE, OPERATOR_LABELS, OPERATOR_LABELS_BY_TYPE, OPERATOR_SYMBOLS, QUERY_BAR_SELECTOR, SEGMENT_TO_MENU, VARIANT_LABELS, applyAcceptChar, applyFieldValueTransforms, applyKnownFieldHelpers, buildAnchoredRect, canBorrowCrossFieldLabel, chipIdToConditionIndex, createStatusCodeInputFilter, createStatusCodeNormalizer, createStatusCodeSerializer, createStatusCodeSuggestions, createStatusCodeValidator, filterAndSort, findChipSplitIndex, findMatchingFieldValue, findOptionByValue, findValueLabelInFields, formatDateForChip, getCurrentValueTokenText, getDateDisplayLabel, getFieldOperators, getFieldValues, getInvalidValueIndices, getKnownFieldSerializer, getOperatorFromLabel, getOperatorLabel, getValueFilterText, hasFieldValues, hasStaticAllowlist, isBetweenOperator, isBuildingComplete, isDatePreset, isFilterParseError, isMenuRelated, isMultiSelectOperator, isNoValueOperator, isOperatorAllowedForField, isValidFieldValue, isValueShapeCompatible, nextBuildingMenu, parseExpression, serializeExpression, toAnchorBounds, validateValueForField };
|
|
18
|
+
export { CONNECTOR_ID_PATTERN, COUNTRY_OPTIONS, DATE_PRESETS, MENU_BASE_GUTTER, MENU_CHIP_GUTTER_OFFSET, NO_VALUE_OPERATORS, NO_VALUE_PLACEHOLDER, OPERATORS_BY_TYPE, OPERATOR_LABELS, OPERATOR_LABELS_BY_TYPE, OPERATOR_SYMBOLS, QUERY_BAR_SELECTOR, SEGMENT_TO_MENU, VARIANT_LABELS, applyAcceptChar, applyFieldValueTransforms, applyKnownFieldHelpers, buildAnchoredRect, canBorrowCrossFieldLabel, chipIdToConditionIndex, createStatusCodeInputFilter, createStatusCodeNormalizer, createStatusCodeSerializer, createStatusCodeSuggestions, createStatusCodeValidator, filterAndSort, findChipSplitIndex, findMatchingFieldValue, findOptionByValue, findValueLabelInFields, formatDateForChip, getCurrentValueTokenText, getDateDisplayLabel, getFieldOperators, getFieldValues, getInvalidValueIndices, getKnownFieldSerializer, getOperatorFromLabel, getOperatorLabel, getValueFilterText, hasFieldValues, hasStaticAllowlist, incompleteTripletError, isBetweenOperator, isBuildingComplete, isDatePreset, isEmptyFilterValue, isFilterParseError, isMenuRelated, isMultiSelectOperator, isNoValueOperator, isOperatorAllowedForField, isValidFieldValue, isValueShapeCompatible, nextBuildingMenu, parseExpression, serializeExpression, toAnchorBounds, validateValueForField };
|
|
@@ -59,6 +59,14 @@ export interface FieldValueOption {
|
|
|
59
59
|
color: BadgeColor;
|
|
60
60
|
text: string;
|
|
61
61
|
};
|
|
62
|
+
/**
|
|
63
|
+
* Optional secondary text rendered as a muted line beneath the bold `label`
|
|
64
|
+
* in the value dropdown (e.g. a backend path that explains or disambiguates
|
|
65
|
+
* the option). Display-only — never committed as a filter value. Because the
|
|
66
|
+
* bold `label` need not be unique, this line is what distinguishes rows that
|
|
67
|
+
* share a label (two `request-id` rows with different paths).
|
|
68
|
+
*/
|
|
69
|
+
description?: string;
|
|
62
70
|
}
|
|
63
71
|
/**
|
|
64
72
|
* Field Metadata Interface
|