@wallarm-org/design-system 0.29.2-rc-feature-AS-882.1 → 0.29.2
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/hooks/useFilterInputAutocomplete/useBlurCommit.d.ts +2 -2
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useBlurCommit.js +15 -8
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useChipEditing.d.ts +2 -2
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useFilterInputAutocomplete.d.ts +2 -2
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useFocusManagement.js +6 -1
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useMenuFlow.d.ts +2 -2
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useResetState.d.ts +16 -5
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useResetState.js +4 -1
- package/dist/components/FilterInput/hooks/useFilterInputExpression/buildChips.js +3 -8
- package/dist/components/FilterInput/lib/fields.d.ts +9 -0
- package/dist/components/FilterInput/lib/fields.js +6 -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 +6 -0
- package/dist/metadata/components.json +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RefObject } from 'react';
|
|
2
|
-
import type {
|
|
2
|
+
import type { FieldMetadata, FilterOperator, UpsertCondition } from '../../types';
|
|
3
3
|
interface UseBlurCommitDeps {
|
|
4
4
|
selectedField: FieldMetadata | null;
|
|
5
5
|
selectedOperator: FilterOperator | null;
|
|
@@ -7,7 +7,7 @@ interface UseBlurCommitDeps {
|
|
|
7
7
|
editingChipId: string | null;
|
|
8
8
|
effectiveInsertIndexRef: RefObject<number>;
|
|
9
9
|
handleCustomValueCommit: (text: string) => void;
|
|
10
|
-
upsertCondition:
|
|
10
|
+
upsertCondition: UpsertCondition;
|
|
11
11
|
resetState: () => void;
|
|
12
12
|
/**
|
|
13
13
|
* Indirection ref consumed by useMenuFlow to break the circular dependency
|
|
@@ -6,22 +6,29 @@ const useBlurCommit = ({ selectedField, selectedOperator, inputText, editingChip
|
|
|
6
6
|
selectedOperatorRef.current = selectedOperator;
|
|
7
7
|
const inputTextRef = useRef(inputText);
|
|
8
8
|
inputTextRef.current = inputText;
|
|
9
|
+
const committingRef = useRef(false);
|
|
9
10
|
const commitBuildingOnBlur = useCallback(()=>{
|
|
11
|
+
if (committingRef.current) return false;
|
|
10
12
|
const field = selectedFieldRef.current;
|
|
11
13
|
const operator = selectedOperatorRef.current;
|
|
12
14
|
const text = inputTextRef.current.trim();
|
|
13
15
|
if (!field) return false;
|
|
14
16
|
if (editingChipId) return false;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
committingRef.current = true;
|
|
18
|
+
try {
|
|
19
|
+
selectedFieldRef.current = null;
|
|
20
|
+
selectedOperatorRef.current = null;
|
|
21
|
+
inputTextRef.current = '';
|
|
22
|
+
if (operator && text) {
|
|
23
|
+
handleCustomValueCommit(text);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
upsertCondition(field, operator ?? void 0, null, void 0, effectiveInsertIndexRef.current, true);
|
|
27
|
+
resetState();
|
|
20
28
|
return true;
|
|
29
|
+
} finally{
|
|
30
|
+
committingRef.current = false;
|
|
21
31
|
}
|
|
22
|
-
upsertCondition(field, operator ?? void 0, null, void 0, effectiveInsertIndexRef.current, true);
|
|
23
|
-
resetState();
|
|
24
|
-
return true;
|
|
25
32
|
}, [
|
|
26
33
|
editingChipId,
|
|
27
34
|
handleCustomValueCommit,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { RefObject } from 'react';
|
|
2
2
|
import { type ChipSegment } from '../../FilterInputField/FilterInputChip';
|
|
3
|
-
import type { Condition, FieldMetadata, FilterInputChipData, FilterOperator, MenuState } from '../../types';
|
|
3
|
+
import type { Condition, FieldMetadata, FilterInputChipData, FilterOperator, MenuState, UpsertCondition } from '../../types';
|
|
4
4
|
interface UseChipEditingOptions {
|
|
5
5
|
conditions: Condition[];
|
|
6
6
|
chips: FilterInputChipData[];
|
|
@@ -10,7 +10,7 @@ interface UseChipEditingOptions {
|
|
|
10
10
|
setSelectedField: (field: FieldMetadata | null) => void;
|
|
11
11
|
setSelectedOperator: (op: FilterOperator | null) => void;
|
|
12
12
|
setMenuState: (state: MenuState) => void;
|
|
13
|
-
upsertCondition:
|
|
13
|
+
upsertCondition: UpsertCondition;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
16
|
* Manages editing of existing filter chips.
|
package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useFilterInputAutocomplete.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { RefObject } from 'react';
|
|
2
|
-
import type {
|
|
2
|
+
import type { Condition, FieldMetadata, FilterInputChipData, FilterOperator, MenuState, UpsertCondition } from '../../types';
|
|
3
3
|
interface UseFilterInputAutocompleteOptions {
|
|
4
4
|
fields: FieldMetadata[];
|
|
5
5
|
conditions: Condition[];
|
|
6
6
|
chips: FilterInputChipData[];
|
|
7
|
-
upsertCondition:
|
|
7
|
+
upsertCondition: UpsertCondition;
|
|
8
8
|
removeCondition: (chipId: string) => void;
|
|
9
9
|
removeConditionAtIndex: (index: number) => void;
|
|
10
10
|
clearAll: () => void;
|
|
@@ -58,7 +58,12 @@ const useFocusManagement = ({ menuState, isFocused, conditionsLength, inputText,
|
|
|
58
58
|
const active = document.activeElement;
|
|
59
59
|
if (active && !container.contains(active) && !isMenuRelated(active)) return;
|
|
60
60
|
if (editingSegment) {
|
|
61
|
-
const
|
|
61
|
+
const segmentInputRefs = {
|
|
62
|
+
[SEGMENT_VARIANT.attribute]: segmentAttributeInputRef,
|
|
63
|
+
[SEGMENT_VARIANT.operator]: segmentOperatorInputRef,
|
|
64
|
+
[SEGMENT_VARIANT.value]: segmentValueInputRef
|
|
65
|
+
};
|
|
66
|
+
const segmentInput = segmentInputRefs[editingSegment]?.current ?? null;
|
|
62
67
|
if (segmentInput && document.activeElement !== segmentInput) {
|
|
63
68
|
segmentInput.focus();
|
|
64
69
|
segmentInput.select();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { RefObject } from 'react';
|
|
2
2
|
import { type ChipSegment } from '../../FilterInputField/FilterInputChip';
|
|
3
|
-
import type {
|
|
3
|
+
import type { Condition, FieldMetadata, FilterOperator, MenuState, UpsertCondition } from '../../types';
|
|
4
4
|
interface MenuFlowDeps {
|
|
5
5
|
editing: {
|
|
6
6
|
editingChipId: string | null;
|
|
@@ -14,7 +14,7 @@ interface MenuFlowDeps {
|
|
|
14
14
|
fields: FieldMetadata[];
|
|
15
15
|
inputRef: RefObject<HTMLInputElement | null>;
|
|
16
16
|
insertIndex: number;
|
|
17
|
-
upsertCondition:
|
|
17
|
+
upsertCondition: UpsertCondition;
|
|
18
18
|
conditions: Condition[];
|
|
19
19
|
resetState: (continueBuilding?: boolean) => void;
|
|
20
20
|
/** Try to commit the building chip on menu close. Returns true if committed. */
|
|
@@ -21,11 +21,22 @@ interface UseResetStateDeps {
|
|
|
21
21
|
/**
|
|
22
22
|
* Resets all autocomplete state and conditionally returns focus to the main input.
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
24
|
+
* ── Body-focus policy (READ THIS) ─────────────────────────────────────────────
|
|
25
|
+
* `document.activeElement === document.body` is treated **as "stayed inside"** here
|
|
26
|
+
* — i.e. we DO refocus the main input. The reasoning: state mutations in
|
|
27
|
+
* `doReset()` (e.g. `editing.clearEditing()`) can synchronously unmount the
|
|
28
|
+
* element that previously had focus (a chip's inline input, a menu item), and
|
|
29
|
+
* the browser drops focus to body in that case. We want to honor "user is still
|
|
30
|
+
* working in the FilterInput" intent and put the caret back in the input.
|
|
31
|
+
*
|
|
32
|
+
* The opposite policy lives in `useFocusManagement.ts`'s rAF effect: there,
|
|
33
|
+
* body-focus means "user clicked outside" (e.g. tenant switcher) and we MUST
|
|
34
|
+
* NOT recapture. The two policies coexist because the triggers are different
|
|
35
|
+
* (state-driven re-render vs. genuine outside click).
|
|
36
|
+
*
|
|
37
|
+
* If you find yourself unifying these — make sure the "DOM dropped focus on
|
|
38
|
+
* re-render" case still refocuses, otherwise resetState during commit chains
|
|
39
|
+
* breaks. AS-882.
|
|
29
40
|
*/
|
|
30
41
|
export declare const useResetState: ({ editing, dateRange, containerRef, inputRef, resetMenuOffset, setInputText, setSelectedField, setSelectedOperator, setBuildingMultiValue, setInsertIndex, setInsertAfterConnector, setMenuState, }: UseResetStateDeps) => (continueBuilding?: boolean) => void;
|
|
31
42
|
export {};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { useCallback } from "react";
|
|
2
2
|
import { flushSync } from "react-dom";
|
|
3
3
|
import { isMenuRelated } from "../../lib/index.js";
|
|
4
|
-
const useResetState = ({ editing, dateRange, containerRef, inputRef, resetMenuOffset, setInputText, setSelectedField, setSelectedOperator, setBuildingMultiValue, setInsertIndex, setInsertAfterConnector, setMenuState })=>
|
|
4
|
+
const useResetState = ({ editing, dateRange, containerRef, inputRef, resetMenuOffset, setInputText, setSelectedField, setSelectedOperator, setBuildingMultiValue, setInsertIndex, setInsertAfterConnector, setMenuState })=>{
|
|
5
|
+
const resetState = useCallback((continueBuilding = false)=>{
|
|
5
6
|
const doReset = ()=>{
|
|
6
7
|
setInputText('');
|
|
7
8
|
setSelectedField(null);
|
|
@@ -35,4 +36,6 @@ const useResetState = ({ editing, dateRange, containerRef, inputRef, resetMenuOf
|
|
|
35
36
|
setInsertAfterConnector,
|
|
36
37
|
setMenuState
|
|
37
38
|
]);
|
|
39
|
+
return resetState;
|
|
40
|
+
};
|
|
38
41
|
export { useResetState };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SEGMENT_VARIANT } from "../../FilterInputField/FilterInputChip/index.js";
|
|
2
|
-
import { getDateDisplayLabel, getOperatorLabel } from "../../lib/index.js";
|
|
2
|
+
import { findOptionByValue, getDateDisplayLabel, getOperatorLabel } from "../../lib/index.js";
|
|
3
3
|
import { getInvalidValueIndices } from "../useFilterInputAutocomplete/valueCommitHelpers.js";
|
|
4
4
|
const INVALID_DATE = 'Invalid Date';
|
|
5
5
|
const DATE_RANGE_SEPARATOR = ' – ';
|
|
@@ -25,9 +25,7 @@ const makeEmptyChip = (i, error)=>({
|
|
|
25
25
|
});
|
|
26
26
|
const resolveDisplayValue = (condition, field)=>{
|
|
27
27
|
const raw = String(condition.value ?? '');
|
|
28
|
-
|
|
29
|
-
const opt = field.values.find((o)=>String(o.value) === raw);
|
|
30
|
-
return opt?.label ?? raw;
|
|
28
|
+
return findOptionByValue(field, condition.value)?.label ?? raw;
|
|
31
29
|
};
|
|
32
30
|
const buildBaseChip = (i, condition, field)=>({
|
|
33
31
|
id: chipId(i),
|
|
@@ -66,10 +64,7 @@ const buildDateChip = (baseChip, condition, chipError)=>{
|
|
|
66
64
|
};
|
|
67
65
|
const buildMultiValueChip = (baseChip, condition, field, chipError)=>{
|
|
68
66
|
const values = condition.value;
|
|
69
|
-
const valueParts = values.map((v)=>
|
|
70
|
-
const key = String(v);
|
|
71
|
-
return field?.values?.find((opt)=>String(opt.value) === key)?.label ?? key;
|
|
72
|
-
});
|
|
67
|
+
const valueParts = values.map((v)=>findOptionByValue(field, v)?.label ?? String(v));
|
|
73
68
|
const invalidIndices = field ? getInvalidValueIndices(field, values) : [];
|
|
74
69
|
return {
|
|
75
70
|
...baseChip,
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import type { FieldMetadata, FieldValueOption } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Find an option in `field.values` matching the given value, using stringified
|
|
4
|
+
* comparison. Loose-match is required because parser/serializer round-trip
|
|
5
|
+
* coerces typed primitives to strings (e.g. integer field value `5` → string
|
|
6
|
+
* `"5"` after `(field = "5")` parses back), and strict `===` would miss the
|
|
7
|
+
* canonical option. Returns undefined when there is no match or the field
|
|
8
|
+
* has no `values` allowlist.
|
|
9
|
+
*/
|
|
10
|
+
export declare const findOptionByValue: (field: FieldMetadata | undefined, value: string | number | boolean | null | undefined) => FieldValueOption | undefined;
|
|
2
11
|
/**
|
|
3
12
|
* Get value options for a field.
|
|
4
13
|
* Priority: `getSuggestions(inputText, context)` > `values` > `options` (converted to `{value, label}`).
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
const findOptionByValue = (field, value)=>{
|
|
2
|
+
if (!field?.values || null == value) return;
|
|
3
|
+
const key = String(value);
|
|
4
|
+
return field.values.find((opt)=>String(opt.value) === key);
|
|
5
|
+
};
|
|
1
6
|
const getFieldValues = (field, inputText = '', context)=>{
|
|
2
7
|
if (field.getSuggestions) return field.getSuggestions(inputText, context);
|
|
3
8
|
const fromValues = field.values ?? [];
|
|
@@ -17,4 +22,4 @@ const hasStaticAllowlist = (field)=>{
|
|
|
17
22
|
if ((field.values?.length ?? 0) > 0) return true;
|
|
18
23
|
return (field.options?.length ?? 0) > 0;
|
|
19
24
|
};
|
|
20
|
-
export { getFieldValues, hasFieldValues, hasStaticAllowlist };
|
|
25
|
+
export { findOptionByValue, getFieldValues, hasFieldValues, hasStaticAllowlist };
|
|
@@ -6,7 +6,7 @@ export { applyKnownFieldHelpers } from './applyKnownFieldHelpers';
|
|
|
6
6
|
export { chipIdToConditionIndex, findChipSplitIndex } from './conditions';
|
|
7
7
|
export { CONNECTOR_ID_PATTERN, NO_VALUE_OPERATORS, OPERATOR_LABELS, OPERATOR_LABELS_BY_TYPE, OPERATOR_SYMBOLS, OPERATORS_BY_TYPE, QUERY_BAR_SELECTOR, VARIANT_LABELS, } from './constants';
|
|
8
8
|
export { buildContainerAnchoredRect, isMenuRelated } from './dom';
|
|
9
|
-
export { getFieldValues, hasFieldValues, hasStaticAllowlist } from './fields';
|
|
9
|
+
export { findOptionByValue, getFieldValues, hasFieldValues, hasStaticAllowlist } from './fields';
|
|
10
10
|
export { filterAndSort } from './filterSort';
|
|
11
11
|
export { getCurrentValueTokenText, getValueFilterText } from './menuFilterText';
|
|
12
12
|
export { getOperatorFromLabel, getOperatorLabel, isBetweenOperator, isMultiSelectOperator, isNoValueOperator, } from './operators';
|
|
@@ -5,11 +5,11 @@ import { applyKnownFieldHelpers } from "./applyKnownFieldHelpers.js";
|
|
|
5
5
|
import { chipIdToConditionIndex, findChipSplitIndex } from "./conditions.js";
|
|
6
6
|
import { CONNECTOR_ID_PATTERN, 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 { buildContainerAnchoredRect, isMenuRelated } from "./dom.js";
|
|
8
|
-
import { getFieldValues, hasFieldValues, hasStaticAllowlist } from "./fields.js";
|
|
8
|
+
import { findOptionByValue, getFieldValues, hasFieldValues, hasStaticAllowlist } from "./fields.js";
|
|
9
9
|
import { filterAndSort } from "./filterSort.js";
|
|
10
10
|
import { getCurrentValueTokenText, getValueFilterText } from "./menuFilterText.js";
|
|
11
11
|
import { getOperatorFromLabel, getOperatorLabel, isBetweenOperator, isMultiSelectOperator, isNoValueOperator } from "./operators.js";
|
|
12
12
|
import { isFilterParseError, parseExpression } from "./parseExpression/index.js";
|
|
13
13
|
import { serializeExpression } from "./serializeExpression.js";
|
|
14
14
|
import { createStatusCodeInputFilter, createStatusCodeNormalizer, createStatusCodeSerializer, createStatusCodeSuggestions, createStatusCodeValidator } from "./statusCode/index.js";
|
|
15
|
-
export { CONNECTOR_ID_PATTERN, DATE_PRESETS, NO_VALUE_OPERATORS, OPERATORS_BY_TYPE, OPERATOR_LABELS, OPERATOR_LABELS_BY_TYPE, OPERATOR_SYMBOLS, QUERY_BAR_SELECTOR, VARIANT_LABELS, applyAcceptChar, applyFieldValueTransforms, applyKnownFieldHelpers, buildContainerAnchoredRect, chipIdToConditionIndex, createStatusCodeInputFilter, createStatusCodeNormalizer, createStatusCodeSerializer, createStatusCodeSuggestions, createStatusCodeValidator, filterAndSort, findChipSplitIndex, formatDateForChip, getCurrentValueTokenText, getDateDisplayLabel, getFieldValues, getOperatorFromLabel, getOperatorLabel, getValueFilterText, hasFieldValues, hasStaticAllowlist, isBetweenOperator, isDatePreset, isFilterParseError, isMenuRelated, isMultiSelectOperator, isNoValueOperator, parseExpression, serializeExpression };
|
|
15
|
+
export { CONNECTOR_ID_PATTERN, DATE_PRESETS, NO_VALUE_OPERATORS, OPERATORS_BY_TYPE, OPERATOR_LABELS, OPERATOR_LABELS_BY_TYPE, OPERATOR_SYMBOLS, QUERY_BAR_SELECTOR, VARIANT_LABELS, applyAcceptChar, applyFieldValueTransforms, applyKnownFieldHelpers, buildContainerAnchoredRect, chipIdToConditionIndex, createStatusCodeInputFilter, createStatusCodeNormalizer, createStatusCodeSerializer, createStatusCodeSuggestions, createStatusCodeValidator, filterAndSort, findChipSplitIndex, findOptionByValue, formatDateForChip, getCurrentValueTokenText, getDateDisplayLabel, getFieldValues, getOperatorFromLabel, getOperatorLabel, getValueFilterText, hasFieldValues, hasStaticAllowlist, isBetweenOperator, isDatePreset, isFilterParseError, isMenuRelated, isMultiSelectOperator, isNoValueOperator, parseExpression, serializeExpression };
|
|
@@ -9,6 +9,12 @@ export type FilterInputChipVariant = 'chip' | 'and' | 'or' | '(' | ')';
|
|
|
9
9
|
*/
|
|
10
10
|
/** Which segment of a chip has an error: attribute or value (true = whole chip) */
|
|
11
11
|
export type ChipErrorSegment = boolean | 'attribute' | 'value';
|
|
12
|
+
/**
|
|
13
|
+
* Shared signature of the upsertCondition callback owned by useFilterInputExpression.
|
|
14
|
+
* Re-declared in several option interfaces — exported here to keep the source of
|
|
15
|
+
* truth single-rooted (changes to the signature reach all consumers).
|
|
16
|
+
*/
|
|
17
|
+
export type UpsertCondition = (field: FieldMetadata, operator: FilterOperator | undefined, val: string | number | boolean | null | Array<string | number | boolean>, editingChipId?: string | null, atIndex?: number, error?: ChipErrorSegment, dateOrigin?: 'relative' | 'absolute') => void;
|
|
12
18
|
export interface FilterInputChipData {
|
|
13
19
|
id: string;
|
|
14
20
|
variant: FilterInputChipVariant;
|
package/package.json
CHANGED