@wallarm-org/design-system 0.68.2 → 0.68.3-rc-feature-AS-1179.1
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 +9 -2
- package/dist/components/FilterInput/FilterInputField/FilterInputChip/ChipSearchInput.js +2 -1
- package/dist/components/FilterInput/FilterInputField/FilterInputChip/FilterInputChip.js +9 -6
- package/dist/components/FilterInput/FilterInputField/FilterInputChip/classes.d.ts +5 -1
- package/dist/components/FilterInput/FilterInputField/FilterInputChip/classes.js +1 -1
- package/dist/components/FilterInput/FilterInputField/FilterInputChip/constants.d.ts +7 -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/hooks/useFilterInputAutocomplete/useBlurCommit.d.ts +8 -1
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useBlurCommit.js +18 -2
- 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 +35 -0
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useResumeBuilding.js +77 -0
- package/dist/components/FilterInput/hooks/useFilterInputExpression/buildChips.js +17 -4
- package/dist/components/FilterInput/hooks/useFilterInputExpression/useFilterInputExpression.js +5 -0
- package/dist/metadata/components.json +2 -2
- package/package.json +1 -1
|
@@ -13,9 +13,16 @@ const parseFilterInputErrors = (conditions, fields)=>{
|
|
|
13
13
|
const field = fields.find((f)=>f.name === condition.field);
|
|
14
14
|
const baseIsNoValue = null != condition.operator && isNoValueOperator(condition.operator);
|
|
15
15
|
if (field?.pairedField && !baseIsNoValue && isPairValueMissing(condition)) errors.push(`${field.pairedField.label} is required`);
|
|
16
|
-
if (!condition.error) continue;
|
|
17
16
|
const label = field?.label || condition.field;
|
|
18
|
-
|
|
17
|
+
if (field && !field.pairedField && !condition.error) {
|
|
18
|
+
const operatorMissing = !condition.operator;
|
|
19
|
+
const valueMissing = !baseIsNoValue && !operatorMissing && (null == condition.value || '' === condition.value || Array.isArray(condition.value) && 0 === condition.value.length);
|
|
20
|
+
if (operatorMissing || valueMissing) {
|
|
21
|
+
errors.push(`${label} is incomplete`);
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (condition.error) switch(condition.error){
|
|
19
26
|
case SEGMENT_VARIANT.attribute:
|
|
20
27
|
errors.push(`Unknown field ${condition.field}`);
|
|
21
28
|
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,
|
|
@@ -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),
|
|
@@ -88,8 +90,8 @@ const FilterInputChip = ({ ref, chipId, attribute, operator, value, error = fals
|
|
|
88
90
|
}),
|
|
89
91
|
(value || baseActiveSegment === SEGMENT_VARIANT.value) && /*#__PURE__*/ jsx(Segment, {
|
|
90
92
|
variant: SEGMENT_VARIANT.value,
|
|
91
|
-
className:
|
|
92
|
-
error: baseActiveSegment !== SEGMENT_VARIANT.value && (true ===
|
|
93
|
+
className: pair ? 'max-w-[90px] shrink-0' : 'min-w-0',
|
|
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(SEGMENT_VARIANT.value, e) : void 0,
|
|
107
110
|
children: pair.attribute
|
|
108
111
|
}),
|
|
109
112
|
(pair.operator || pairActiveSegment === SEGMENT_VARIANT.operator) && /*#__PURE__*/ jsx(Segment, {
|
|
@@ -116,7 +119,7 @@ const FilterInputChip = ({ ref, chipId, attribute, operator, value, error = fals
|
|
|
116
119
|
(null != pair.value || pairActiveSegment === SEGMENT_VARIANT.value) && /*#__PURE__*/ jsx(Segment, {
|
|
117
120
|
variant: SEGMENT_VARIANT.value,
|
|
118
121
|
className: "min-w-0",
|
|
119
|
-
error: pairActiveSegment !== SEGMENT_VARIANT.value && (true ===
|
|
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 ?? ''
|
|
@@ -12,7 +12,11 @@ export declare const segmentTextVariants: (props?: ({
|
|
|
12
12
|
variant?: "value" | "operator" | "attribute" | null | undefined;
|
|
13
13
|
error?: boolean | null | undefined;
|
|
14
14
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
15
|
-
/** Remove button styles — hidden by default, shown on chip hover or button focus
|
|
15
|
+
/** Remove button styles — hidden by default, shown on chip hover or button focus.
|
|
16
|
+
* It is `opacity-0` until hover/focus, but sits over the chip's right edge and the
|
|
17
|
+
* field area just past it; `pointer-events-none` while invisible stops it from
|
|
18
|
+
* swallowing clicks meant for the chip/input and silently deleting the chip
|
|
19
|
+
* (AS-1179). Hover/focus restore both visibility and clickability. */
|
|
16
20
|
export declare const removeButtonVariants: (props?: ({
|
|
17
21
|
error?: boolean | null | undefined;
|
|
18
22
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
@@ -68,7 +68,7 @@ const segmentTextVariants = cva('truncate text-sm', {
|
|
|
68
68
|
error: false
|
|
69
69
|
}
|
|
70
70
|
});
|
|
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}`, {
|
|
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 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
72
|
variants: {
|
|
73
73
|
error: {
|
|
74
74
|
true: 'border-border-danger bg-bg-light-danger text-text-danger',
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
/** Minimum input width in px */
|
|
2
2
|
export declare const MIN_INPUT_WIDTH = 4;
|
|
3
|
+
/**
|
|
4
|
+
* Maximum input width in px. Caps the content-sized edit/search input so a long
|
|
5
|
+
* value can't grow the input past the chip (`max-w-[320px]`) and spill its text
|
|
6
|
+
* outside the chip border. Beyond this width the input scrolls its content
|
|
7
|
+
* natively instead of widening.
|
|
8
|
+
*/
|
|
9
|
+
export declare const MAX_INPUT_WIDTH = 280;
|
|
3
10
|
/** Extra pixels added to measured width to prevent text clipping */
|
|
4
11
|
export declare const WIDTH_OFFSET = 1;
|
|
5
12
|
/** 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
|
};
|
|
@@ -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,14 +1,18 @@
|
|
|
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);
|
|
14
18
|
const commitBuildingOnBlur = useCallback(()=>{
|
|
@@ -68,6 +72,16 @@ const useBlurCommit = ({ selectedField, selectedOperator, inputText, editingChip
|
|
|
68
72
|
handleCustomValueCommit(text);
|
|
69
73
|
return true;
|
|
70
74
|
}
|
|
75
|
+
if (1 === buildingSideRef.current && buildingBaseRef.current) {
|
|
76
|
+
const base = buildingBaseRef.current;
|
|
77
|
+
upsertCondition(base.field, base.operator, base.value, null, effectiveInsertIndexRef.current);
|
|
78
|
+
const pairError = operator ? SEGMENT_VARIANT.value : true;
|
|
79
|
+
upsertCondition(field, operator ?? void 0, null, void 0, void 0, pairError, void 0, 1);
|
|
80
|
+
setBuildingBase(null);
|
|
81
|
+
setBuildingSide(0);
|
|
82
|
+
resetState();
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
71
85
|
if (isBuildingComplete(field, operator, null)) {
|
|
72
86
|
upsertCondition(field, operator, null, void 0, effectiveInsertIndexRef.current);
|
|
73
87
|
resetState();
|
|
@@ -85,7 +99,9 @@ const useBlurCommit = ({ selectedField, selectedOperator, inputText, editingChip
|
|
|
85
99
|
handleCustomValueCommit,
|
|
86
100
|
upsertCondition,
|
|
87
101
|
resetState,
|
|
88
|
-
effectiveInsertIndexRef
|
|
102
|
+
effectiveInsertIndexRef,
|
|
103
|
+
setBuildingBase,
|
|
104
|
+
setBuildingSide
|
|
89
105
|
]);
|
|
90
106
|
useLayoutEffect(()=>{
|
|
91
107
|
commitBuildingForceRef.current = commitBuildingForce;
|
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)) 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)) 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,35 @@
|
|
|
1
|
+
import type { RefObject } from 'react';
|
|
2
|
+
import type { Condition, FieldMetadata, FilterOperator, MenuState } from '../../types';
|
|
3
|
+
import type { BuildingBase } from './useAutocompleteState';
|
|
4
|
+
interface UseResumeBuildingDeps {
|
|
5
|
+
conditions: Condition[];
|
|
6
|
+
fields: FieldMetadata[];
|
|
7
|
+
removeConditionAtIndex: (index: number) => void;
|
|
8
|
+
setInsertIndex: (index: number | null) => void;
|
|
9
|
+
setBuildingBase: (base: BuildingBase | null) => void;
|
|
10
|
+
setBuildingSide: (side: 0 | 1) => void;
|
|
11
|
+
setSelectedField: (field: FieldMetadata | null) => void;
|
|
12
|
+
setSelectedOperator: (op: FilterOperator | null) => void;
|
|
13
|
+
setBuildingMultiValue: (val: string | undefined) => void;
|
|
14
|
+
setInputText: (text: string) => void;
|
|
15
|
+
setMenuState: (state: MenuState) => void;
|
|
16
|
+
resetMenuAnchor: () => void;
|
|
17
|
+
/** Exit any inline-edit so the resumed flow is a clean building chip. */
|
|
18
|
+
clearEditing: () => void;
|
|
19
|
+
inputRef: RefObject<HTMLInputElement | null>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Resume building a committed-but-incomplete chip instead of inline-editing it.
|
|
23
|
+
*
|
|
24
|
+
* A chip left half-built (e.g. force-committed via an area click) keeps its
|
|
25
|
+
* triplets but lacks a required value. Clicking it converts the committed
|
|
26
|
+
* condition back into a building chip and reopens the menu at the first missing
|
|
27
|
+
* step, so the normal building cascade carries on — including the paired
|
|
28
|
+
* second triplet for two-step fields. This is what makes "press anywhere → red
|
|
29
|
+
* chip → click to continue" work for paired fields (AS-1179).
|
|
30
|
+
*
|
|
31
|
+
* Returns a predicate: `true` when it took over (caller must not also run the
|
|
32
|
+
* inline-edit handler), `false` when the chip is complete (edit it normally).
|
|
33
|
+
*/
|
|
34
|
+
export declare const useResumeBuilding: ({ conditions, fields, removeConditionAtIndex, setInsertIndex, setBuildingBase, setBuildingSide, setSelectedField, setSelectedOperator, setBuildingMultiValue, setInputText, setMenuState, resetMenuAnchor, clearEditing, inputRef, }: UseResumeBuildingDeps) => (chipId: string) => boolean;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import { chipIdToConditionIndex, isNoValueOperator } from "../../lib/index.js";
|
|
3
|
+
const isEmptyValue = (value)=>null == value || '' === value || Array.isArray(value) && 0 === value.length;
|
|
4
|
+
const useResumeBuilding = ({ conditions, fields, removeConditionAtIndex, setInsertIndex, setBuildingBase, setBuildingSide, setSelectedField, setSelectedOperator, setBuildingMultiValue, setInputText, setMenuState, resetMenuAnchor, clearEditing, inputRef })=>useCallback((chipId)=>{
|
|
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 && isEmptyValue(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) 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 (field.pairedField && !isNoValueOperator(condition.operator)) {
|
|
41
|
+
const pairOperator = condition.pair?.operator;
|
|
42
|
+
const pairOperatorMissing = !pairOperator;
|
|
43
|
+
const pairOperatorTakesValue = null != pairOperator && !isNoValueOperator(pairOperator);
|
|
44
|
+
const pairValueMissing = pairOperatorTakesValue && isEmptyValue(condition.pair?.value);
|
|
45
|
+
if (pairOperatorMissing || pairValueMissing) {
|
|
46
|
+
startBuilding({
|
|
47
|
+
base: {
|
|
48
|
+
field,
|
|
49
|
+
operator: condition.operator,
|
|
50
|
+
value: condition.value
|
|
51
|
+
},
|
|
52
|
+
side: 1,
|
|
53
|
+
selectedField: field.pairedField,
|
|
54
|
+
selectedOperator: pairOperator ?? null,
|
|
55
|
+
menuState: pairOperatorMissing ? 'operator' : 'value'
|
|
56
|
+
});
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}, [
|
|
62
|
+
conditions,
|
|
63
|
+
fields,
|
|
64
|
+
removeConditionAtIndex,
|
|
65
|
+
setInsertIndex,
|
|
66
|
+
setBuildingBase,
|
|
67
|
+
setBuildingSide,
|
|
68
|
+
setSelectedField,
|
|
69
|
+
setSelectedOperator,
|
|
70
|
+
setBuildingMultiValue,
|
|
71
|
+
setInputText,
|
|
72
|
+
setMenuState,
|
|
73
|
+
resetMenuAnchor,
|
|
74
|
+
clearEditing,
|
|
75
|
+
inputRef
|
|
76
|
+
]);
|
|
77
|
+
export { useResumeBuilding };
|
|
@@ -84,23 +84,36 @@ 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 && null != condition.value && '' !== condition.value && !(Array.isArray(condition.value) && 0 === condition.value.length);
|
|
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)) && (null == value || '' === value || Array.isArray(value) && 0 === value.length);
|
|
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
|
|
115
|
+
const incompleteError = condition.operator ? isNoValueOperator(condition.operator) ? void 0 : null == condition.value || '' === condition.value || Array.isArray(condition.value) && 0 === condition.value.length ? SEGMENT_VARIANT.value : void 0 : true;
|
|
116
|
+
const chipError = condition.error || (error ? true : void 0) || incompleteError;
|
|
104
117
|
const field = fields.find((f)=>f.name === condition.field);
|
|
105
118
|
const baseChip = buildBaseChip(i, condition, field);
|
|
106
119
|
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({
|
package/package.json
CHANGED