@wallarm-org/design-system 0.60.0 → 0.61.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/FilterInput.d.ts +15 -0
- package/dist/components/FilterInput/FilterInput.js +9 -2
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useInputHandlers.js +2 -2
- package/dist/components/FilterInput/hooks/useFilterInputExpression/applyExternalErrors.d.ts +11 -0
- package/dist/components/FilterInput/hooks/useFilterInputExpression/applyExternalErrors.js +16 -0
- package/dist/components/FilterInput/hooks/useFilterInputExpression/buildChips.js +7 -9
- package/dist/components/FilterInput/hooks/useFilterInputExpression/useFilterInputExpression.d.ts +2 -1
- package/dist/components/FilterInput/hooks/useFilterInputExpression/useFilterInputExpression.js +5 -3
- package/dist/components/FilterInput/lib/fields.d.ts +2 -1
- package/dist/components/FilterInput/lib/fields.js +1 -0
- package/dist/components/FilterInput/types.d.ts +7 -0
- package/dist/metadata/components.json +8 -2
- package/package.json +1 -1
|
@@ -19,6 +19,21 @@ export interface FilterInputProps extends Omit<HTMLAttributes<HTMLDivElement>, '
|
|
|
19
19
|
onChange?: (expression: ExprNode | null) => void;
|
|
20
20
|
placeholder?: string;
|
|
21
21
|
error?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Field names whose values were rejected by the backend. Matching chips are
|
|
24
|
+
* rendered with a value error (red). Purely presentational: conditions and
|
|
25
|
+
* `onChange` output are unaffected, and the consumer is expected to render
|
|
26
|
+
* its own message (e.g. an alert with the backend error text) and clear the
|
|
27
|
+
* prop when the filter changes or the query succeeds.
|
|
28
|
+
*/
|
|
29
|
+
externalErrors?: string[];
|
|
30
|
+
/**
|
|
31
|
+
* Notified whenever the set of validation messages FilterInput renders below
|
|
32
|
+
* the input changes (empty array = no visible error). Lets a consumer avoid
|
|
33
|
+
* stacking its own message (e.g. a backend-error alert) on top of one the
|
|
34
|
+
* input already shows. Pass a stable (memoized) callback.
|
|
35
|
+
*/
|
|
36
|
+
onErrorsChange?: (errors: string[]) => void;
|
|
22
37
|
showKeyboardHint?: boolean;
|
|
23
38
|
}
|
|
24
39
|
export declare const FilterInput: FC<FilterInputProps>;
|
|
@@ -7,7 +7,7 @@ import { FilterInputField } from "./FilterInputField/index.js";
|
|
|
7
7
|
import { FilterInputMenu } from "./FilterInputMenu/FilterInputMenu.js";
|
|
8
8
|
import { useFilterInputAutocomplete, useFilterInputExpression, useFilterInputSelection } from "./hooks/index.js";
|
|
9
9
|
import { applyKnownFieldHelpers } from "./lib/applyKnownFieldHelpers.js";
|
|
10
|
-
const FilterInput = ({ fields: rawFields = [], value, onChange, placeholder = 'Type to filter...', error = false, showKeyboardHint = false, className, ...props })=>{
|
|
10
|
+
const FilterInput = ({ fields: rawFields = [], value, onChange, placeholder = 'Type to filter...', error = false, externalErrors, onErrorsChange, showKeyboardHint = false, className, ...props })=>{
|
|
11
11
|
const inputRef = useRef(null);
|
|
12
12
|
const containerRef = useRef(null);
|
|
13
13
|
const buildingChipRef = useRef(null);
|
|
@@ -23,7 +23,8 @@ const FilterInput = ({ fields: rawFields = [], value, onChange, placeholder = 'T
|
|
|
23
23
|
fields,
|
|
24
24
|
value,
|
|
25
25
|
onChange,
|
|
26
|
-
error
|
|
26
|
+
error,
|
|
27
|
+
externalErrors
|
|
27
28
|
});
|
|
28
29
|
const autocomplete = useFilterInputAutocomplete({
|
|
29
30
|
fields,
|
|
@@ -82,6 +83,12 @@ const FilterInput = ({ fields: rawFields = [], value, onChange, placeholder = 'T
|
|
|
82
83
|
pasteError,
|
|
83
84
|
fieldErrors
|
|
84
85
|
]);
|
|
86
|
+
useEffect(()=>{
|
|
87
|
+
onErrorsChange?.(errors);
|
|
88
|
+
}, [
|
|
89
|
+
errors,
|
|
90
|
+
onErrorsChange
|
|
91
|
+
]);
|
|
85
92
|
return /*#__PURE__*/ jsxs("div", {
|
|
86
93
|
ref: containerRef,
|
|
87
94
|
className: cn('group/filter-input relative flex w-full flex-col gap-4', className),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useCallback, useRef } from "react";
|
|
2
|
-
import { OPERATOR_SYMBOLS, applyAcceptChar, getOperatorFromLabel,
|
|
2
|
+
import { OPERATOR_SYMBOLS, applyAcceptChar, getOperatorFromLabel, hasStaticAllowlist, nextBuildingMenu } from "../../lib/index.js";
|
|
3
3
|
const useInputHandlers = ({ inputText, menuState, selectedField, selectedOperator, isFocused, fields, inputRef, blurCommitRef, commitBuildingForceRef, conditionsRef, conditionsLengthRef, effectiveInsertIndexRef, setInputText, setMenuState, setInsertIndex, resetMenuAnchor, removeConditionAtIndex, handleFieldSelect, handleOperatorSelect, handleCustomValueCommit, stepBackBuildingMenu })=>{
|
|
4
4
|
const menuRef = useRef(null);
|
|
5
5
|
const handleInputChange = useCallback((e)=>{
|
|
@@ -82,7 +82,7 @@ const useInputHandlers = ({ inputText, menuState, selectedField, selectedOperato
|
|
|
82
82
|
return;
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
|
-
if ('Enter' === e.key && 'value' === menuState && selectedField && !
|
|
85
|
+
if ('Enter' === e.key && 'value' === menuState && selectedField && !hasStaticAllowlist(selectedField) && inputText.trim()) {
|
|
86
86
|
e.preventDefault();
|
|
87
87
|
handleCustomValueCommit(inputText);
|
|
88
88
|
return;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Condition, FilterInputChipData } from '../../types';
|
|
2
|
+
/**
|
|
3
|
+
* Display-only overlay for backend-rejected fields: marks built chips whose
|
|
4
|
+
* condition's field is listed, leaving conditions untouched. Working on chips
|
|
5
|
+
* (not conditions) keeps `condition.error` clear, so the flag never leaks into
|
|
6
|
+
* `onChange` round-trips, `parseFilterInputErrors` produces no duplicate
|
|
7
|
+
* message, and `buildChips`' cross-field label resolution (which keys off
|
|
8
|
+
* `condition.error`) is not falsely activated for raw freeform values.
|
|
9
|
+
* The consumer owns the error text (e.g. an alert with the backend response).
|
|
10
|
+
*/
|
|
11
|
+
export declare const applyExternalErrors: (chips: FilterInputChipData[], conditions: Condition[], externalErrors: string[] | undefined) => FilterInputChipData[];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SEGMENT_VARIANT } from "../../FilterInputField/FilterInputChip/index.js";
|
|
2
|
+
const applyExternalErrors = (chips, conditions, externalErrors)=>{
|
|
3
|
+
if (!externalErrors?.length) return chips;
|
|
4
|
+
const errored = new Set(externalErrors);
|
|
5
|
+
let conditionIdx = -1;
|
|
6
|
+
return chips.map((chip)=>{
|
|
7
|
+
if ('chip' !== chip.variant) return chip;
|
|
8
|
+
conditionIdx += 1;
|
|
9
|
+
const condition = conditions[conditionIdx];
|
|
10
|
+
return condition && !chip.disabled && !chip.error && errored.has(condition.field) ? {
|
|
11
|
+
...chip,
|
|
12
|
+
error: SEGMENT_VARIANT.value
|
|
13
|
+
} : chip;
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
export { applyExternalErrors };
|
|
@@ -22,15 +22,14 @@ const makeEmptyChip = (i, error)=>({
|
|
|
22
22
|
value: '',
|
|
23
23
|
error: error || void 0
|
|
24
24
|
});
|
|
25
|
-
const resolveValueLabel = (value, field, fields
|
|
25
|
+
const resolveValueLabel = (value, field, fields)=>{
|
|
26
26
|
const own = findOptionByValue(field, value)?.label;
|
|
27
27
|
if (void 0 !== own) return own;
|
|
28
|
-
if (!crossField) return;
|
|
29
28
|
return findValueLabelInFields(value, fields);
|
|
30
29
|
};
|
|
31
|
-
const resolveDisplayValue = (condition, field, fields
|
|
30
|
+
const resolveDisplayValue = (condition, field, fields)=>{
|
|
32
31
|
const raw = String(condition.value ?? '');
|
|
33
|
-
return resolveValueLabel(condition.value, field, fields
|
|
32
|
+
return resolveValueLabel(condition.value, field, fields) ?? raw;
|
|
34
33
|
};
|
|
35
34
|
const buildBaseChip = (i, condition, field)=>({
|
|
36
35
|
id: chipId(i),
|
|
@@ -67,9 +66,9 @@ const buildDateChip = (baseChip, condition, chipError)=>{
|
|
|
67
66
|
error: chipError || (displayValue === INVALID_DATE ? SEGMENT_VARIANT.value : void 0)
|
|
68
67
|
};
|
|
69
68
|
};
|
|
70
|
-
const buildMultiValueChip = (baseChip, condition, field, fields, chipError
|
|
69
|
+
const buildMultiValueChip = (baseChip, condition, field, fields, chipError)=>{
|
|
71
70
|
const values = condition.value;
|
|
72
|
-
const valueParts = values.map((v)=>resolveValueLabel(v, field, fields
|
|
71
|
+
const valueParts = values.map((v)=>resolveValueLabel(v, field, fields) ?? String(v));
|
|
73
72
|
const invalidIndices = field ? getInvalidValueIndices(field, values) : [];
|
|
74
73
|
return {
|
|
75
74
|
...baseChip,
|
|
@@ -89,17 +88,16 @@ const makeConditionChip = (i, conditions, fields, error)=>{
|
|
|
89
88
|
const chipError = condition.error || (error ? true : void 0);
|
|
90
89
|
const field = fields.find((f)=>f.name === condition.field);
|
|
91
90
|
const baseChip = buildBaseChip(i, condition, field);
|
|
92
|
-
const crossField = chipError === SEGMENT_VARIANT.value || true === chipError;
|
|
93
91
|
if (condition.operator && isNoValueOperator(condition.operator)) return {
|
|
94
92
|
...baseChip,
|
|
95
93
|
value: NO_VALUE_PLACEHOLDER,
|
|
96
94
|
error: chipError
|
|
97
95
|
};
|
|
98
96
|
if (field?.type === 'date') return Array.isArray(condition.value) ? buildDateRangeChip(baseChip, condition, chipError) : buildDateChip(baseChip, condition, chipError);
|
|
99
|
-
if (Array.isArray(condition.value)) return buildMultiValueChip(baseChip, condition, field, fields, chipError
|
|
97
|
+
if (Array.isArray(condition.value)) return buildMultiValueChip(baseChip, condition, field, fields, chipError);
|
|
100
98
|
return {
|
|
101
99
|
...baseChip,
|
|
102
|
-
value: resolveDisplayValue(condition, field, fields
|
|
100
|
+
value: resolveDisplayValue(condition, field, fields),
|
|
103
101
|
error: chipError
|
|
104
102
|
};
|
|
105
103
|
};
|
package/dist/components/FilterInput/hooks/useFilterInputExpression/useFilterInputExpression.d.ts
CHANGED
|
@@ -4,8 +4,9 @@ interface UseFilterInputExpressionOptions {
|
|
|
4
4
|
value?: ExprNode | null;
|
|
5
5
|
onChange?: (expression: ExprNode | null) => void;
|
|
6
6
|
error: boolean;
|
|
7
|
+
externalErrors?: string[];
|
|
7
8
|
}
|
|
8
|
-
export declare const useFilterInputExpression: ({ fields, value, onChange, error, }: UseFilterInputExpressionOptions) => {
|
|
9
|
+
export declare const useFilterInputExpression: ({ fields, value, onChange, error, externalErrors, }: UseFilterInputExpressionOptions) => {
|
|
9
10
|
conditions: Condition[];
|
|
10
11
|
connectors: ("and" | "or")[];
|
|
11
12
|
chips: import("../..").FilterInputChipData[];
|
package/dist/components/FilterInput/hooks/useFilterInputExpression/useFilterInputExpression.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
2
|
import { SEGMENT_VARIANT } from "../../FilterInputField/FilterInputChip/index.js";
|
|
3
3
|
import { CONNECTOR_ID_PATTERN, chipIdToConditionIndex, validateValueForField } from "../../lib/index.js";
|
|
4
|
+
import { applyExternalErrors } from "./applyExternalErrors.js";
|
|
4
5
|
import { buildChips } from "./buildChips.js";
|
|
5
6
|
import { buildExpression, expressionToConditions } from "./expression.js";
|
|
6
7
|
const EMPTY_STATE = {
|
|
@@ -91,7 +92,7 @@ const addConnectorIfNeeded = (connectors, newConditionsLength, editingChipId, at
|
|
|
91
92
|
...new Array(missing).fill(DEFAULT_CONNECTOR)
|
|
92
93
|
] : connectors;
|
|
93
94
|
};
|
|
94
|
-
const useFilterInputExpression = ({ fields, value, onChange, error })=>{
|
|
95
|
+
const useFilterInputExpression = ({ fields, value, onChange, error, externalErrors })=>{
|
|
95
96
|
const [state, setState] = useState(EMPTY_STATE);
|
|
96
97
|
useEffect(()=>{
|
|
97
98
|
if (void 0 !== value) {
|
|
@@ -105,11 +106,12 @@ const useFilterInputExpression = ({ fields, value, onChange, error })=>{
|
|
|
105
106
|
value,
|
|
106
107
|
fields
|
|
107
108
|
]);
|
|
108
|
-
const chips = useMemo(()=>buildChips(state.conditions, state.connectors, fields, error), [
|
|
109
|
+
const chips = useMemo(()=>applyExternalErrors(buildChips(state.conditions, state.connectors, fields, error), state.conditions, externalErrors), [
|
|
109
110
|
state.conditions,
|
|
110
111
|
state.connectors,
|
|
111
112
|
fields,
|
|
112
|
-
error
|
|
113
|
+
error,
|
|
114
|
+
externalErrors
|
|
113
115
|
]);
|
|
114
116
|
const upsertCondition = useCallback((field, operator, val, editingChipId, atIndex, error, dateOrigin)=>{
|
|
115
117
|
const condition = buildCondition(field, operator, val, error, dateOrigin);
|
|
@@ -27,6 +27,7 @@ export declare const getFieldValues: (field: FieldMetadata, inputText?: string,
|
|
|
27
27
|
export declare const hasFieldValues: (field: FieldMetadata) => boolean;
|
|
28
28
|
/**
|
|
29
29
|
* True if the field has an exhaustive static allowlist. getSuggestions
|
|
30
|
-
* fields return false — their list is a
|
|
30
|
+
* fields and `strictValues: false` fields return false — their list is a
|
|
31
|
+
* hint, not a strict allowlist.
|
|
31
32
|
*/
|
|
32
33
|
export declare const hasStaticAllowlist: (field: FieldMetadata) => boolean;
|
|
@@ -26,6 +26,7 @@ const hasFieldValues = (field)=>{
|
|
|
26
26
|
return (field.options?.length ?? 0) > 0;
|
|
27
27
|
};
|
|
28
28
|
const hasStaticAllowlist = (field)=>{
|
|
29
|
+
if (false === field.strictValues) return false;
|
|
29
30
|
if (field.getSuggestions) return false;
|
|
30
31
|
if ((field.values?.length ?? 0) > 0) return true;
|
|
31
32
|
return (field.options?.length ?? 0) > 0;
|
|
@@ -68,6 +68,13 @@ export interface FieldMetadata {
|
|
|
68
68
|
* Empty array `[]` means freeform input — no dropdown, user types any value.
|
|
69
69
|
*/
|
|
70
70
|
options?: string[];
|
|
71
|
+
/**
|
|
72
|
+
* When `false`, `values`/`options` are suggestions rather than an exhaustive
|
|
73
|
+
* allowlist: the dropdown still offers them, but any typed value commits
|
|
74
|
+
* without an allowlist error. Data-type validation (`isValueOfType`) still
|
|
75
|
+
* applies. Defaults to `true` (options are a strict allowlist).
|
|
76
|
+
*/
|
|
77
|
+
strictValues?: boolean;
|
|
71
78
|
/**
|
|
72
79
|
* Optional callback to compute value suggestions dynamically from the current
|
|
73
80
|
* input text. When provided, takes precedence over `values` and `options`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
3
|
-
"generatedAt": "2026-06-
|
|
2
|
+
"version": "0.60.0",
|
|
3
|
+
"generatedAt": "2026-06-16T08:44:33.684Z",
|
|
4
4
|
"components": [
|
|
5
5
|
{
|
|
6
6
|
"name": "Accordion",
|
|
@@ -22031,6 +22031,12 @@
|
|
|
22031
22031
|
"required": false,
|
|
22032
22032
|
"defaultValue": "false"
|
|
22033
22033
|
},
|
|
22034
|
+
{
|
|
22035
|
+
"name": "externalErrors",
|
|
22036
|
+
"type": "string[] | undefined",
|
|
22037
|
+
"required": false,
|
|
22038
|
+
"description": "Field names whose values were rejected by the backend. Matching chips are\nrendered with a value error (red). Purely presentational: conditions and\n`onChange` output are unaffected, and the consumer is expected to render\nits own message (e.g. an alert with the backend error text) and clear the\nprop when the filter changes or the query succeeds."
|
|
22039
|
+
},
|
|
22034
22040
|
{
|
|
22035
22041
|
"name": "showKeyboardHint",
|
|
22036
22042
|
"type": "boolean | undefined",
|