@wallarm-org/design-system 1.7.0 → 1.8.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.
@@ -30,6 +30,8 @@ const ChipsWithGaps = ({ chips, hideLeadingGap, hideTrailingGap, onChipClick, on
30
30
  valueSeparator: chip.valueSeparator,
31
31
  errorValueIndices: chip.errorValueIndices,
32
32
  disabled: chip.disabled,
33
+ valueMaxWidth: chip.valueMaxWidth,
34
+ caretMode: chip.caretMode,
33
35
  pair: chip.pair,
34
36
  onRemove: chip.disabled ? void 0 : ()=>onChipRemove(chip.id),
35
37
  onSegmentClick: chip.disabled ? void 0 : (segment, anchorEl)=>onChipClick(chip.id, segment, anchorEl),
@@ -20,6 +20,11 @@ export interface FilterInputChipProps extends Omit<HTMLAttributes<HTMLDivElement
20
20
  building?: boolean;
21
21
  /** When true, the chip cannot be edited or removed (dimmed appearance) */
22
22
  disabled?: boolean;
23
+ /** Caps the applied value segment at this width (px), truncating with an
24
+ * ellipsis; lifts the chip's default max-width so it grows to fit (AS-1064). */
25
+ valueMaxWidth?: number;
26
+ /** `'end'` places the caret at the end on value inline-edit; omit to select all (AS-1064). */
27
+ caretMode?: 'end';
23
28
  /** Second paired triplet (two-step fields). The paired attribute is fixed. */
24
29
  pair?: FilterInputChipData['pair'];
25
30
  onRemove?: () => void;
@@ -9,7 +9,7 @@ import { FilterInputRemoveButton } from "./FilterInputRemoveButton.js";
9
9
  import { PairSeparator } from "./PairSeparator.js";
10
10
  import { Segment } from "./Segment.js";
11
11
  import { SEGMENT_VARIANT } from "./segmentVariant.js";
12
- const FilterInputChip = ({ ref, chipId, attribute, attributeDescription, operator, value, error = false, valueParts, valueSeparator, errorValueIndices, building = false, disabled = false, pair, onRemove, onSegmentClick, onPairSegmentClick, className, ...props })=>{
12
+ const FilterInputChip = ({ ref, chipId, attribute, attributeDescription, operator, value, error = false, valueParts, valueSeparator, errorValueIndices, building = false, disabled = false, valueMaxWidth, caretMode, pair, onRemove, onSegmentClick, onPairSegmentClick, className, style, ...props })=>{
13
13
  const interactive = !disabled;
14
14
  const internalRef = useRef(null);
15
15
  const editing = useEditingContext();
@@ -75,6 +75,10 @@ const FilterInputChip = ({ ref, chipId, attribute, attributeDescription, operato
75
75
  disabled,
76
76
  building
77
77
  }), pair ? 'max-w-[380px]' : 'max-w-[320px]', className),
78
+ style: valueMaxWidth && !pair ? {
79
+ maxWidth: 'none',
80
+ ...style
81
+ } : style,
78
82
  "data-slot": "filter-input-condition-chip",
79
83
  ...building && {
80
84
  'data-building': ''
@@ -104,10 +108,17 @@ const FilterInputChip = ({ ref, chipId, attribute, attributeDescription, operato
104
108
  (value || baseActiveSegment === SEGMENT_VARIANT.value || !pair && effectiveError === SEGMENT_VARIANT.value) && /*#__PURE__*/ jsx(Segment, {
105
109
  variant: SEGMENT_VARIANT.value,
106
110
  className: value || pair ? pair ? 'max-w-[90px] shrink-0' : 'min-w-0' : emptyValueHitTarget,
111
+ ...valueMaxWidth && value && !pair && {
112
+ style: {
113
+ maxWidth: valueMaxWidth
114
+ }
115
+ },
107
116
  error: baseActiveSegment !== SEGMENT_VARIANT.value && (true === effectiveError || effectiveError === SEGMENT_VARIANT.value),
108
117
  valueParts: valueParts,
109
118
  valueSeparator: valueSeparator,
110
119
  errorValueIndices: errorValueIndices,
120
+ caretMode: caretMode,
121
+ valueMaxWidth: valueMaxWidth,
111
122
  onClick: interactive ? (e)=>handleSegmentClick(SEGMENT_VARIANT.value, e) : void 0,
112
123
  onMouseDown: interactive && building ? handleSegmentMouseDown : void 0,
113
124
  ...segmentEditProps(SEGMENT_VARIANT.value),
@@ -18,5 +18,11 @@ export type SegmentProps = HTMLAttributes<HTMLDivElement> & {
18
18
  valueSeparator?: string;
19
19
  /** Indices of invalid values in valueParts */
20
20
  errorValueIndices?: number[];
21
+ /** `'end'` places the caret at the end on edit; omit for default select-all (AS-1064). */
22
+ caretMode?: 'end';
23
+ /** Caps the edit input's width (px) to match the applied value cap, so the
24
+ * segment keeps the same width entering/leaving edit instead of snapping to
25
+ * the default MAX_INPUT_WIDTH. Value variant only (AS-1064). */
26
+ valueMaxWidth?: number;
21
27
  };
22
28
  export declare const Segment: FC<SegmentProps>;
@@ -2,12 +2,13 @@ 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 { applyEditCaret } from "./applyEditCaret.js";
5
6
  import { editingValueMinWidth, segmentContainer, segmentTextVariants } from "./classes.js";
6
7
  import { CHAR_WIDTH_PX } from "./constants.js";
7
8
  import { MultiValueSegment } from "./MultiValueSegment.js";
8
9
  import { useSizerWidth } from "./model/useSizerWidth.js";
9
10
  import { SEGMENT_VARIANT } from "./segmentVariant.js";
10
- const Segment = ({ ref, variant, children, className, error, editing, editText, onEditChange, onEditKeyDown, onEditBlur, valueParts, valueSeparator = ', ', errorValueIndices, ...props })=>{
11
+ const Segment = ({ ref, variant, children, className, error, editing, editText, onEditChange, onEditKeyDown, onEditBlur, valueParts, valueSeparator = ', ', errorValueIndices, caretMode, valueMaxWidth, ...props })=>{
11
12
  const textRef = useRef(null);
12
13
  const inputRef = useRef(null);
13
14
  const sizerRef = useRef(null);
@@ -28,7 +29,7 @@ const Segment = ({ ref, variant, children, className, error, editing, editText,
28
29
  const outerFrame = requestAnimationFrame(()=>{
29
30
  innerFrame = requestAnimationFrame(()=>{
30
31
  inputRef.current?.focus();
31
- inputRef.current?.select();
32
+ applyEditCaret(inputRef.current);
32
33
  });
33
34
  });
34
35
  return ()=>{
@@ -40,7 +41,10 @@ const Segment = ({ ref, variant, children, className, error, editing, editText,
40
41
  ]);
41
42
  const inputWidth = useSizerWidth({
42
43
  sizerRef,
43
- text: editText ?? ''
44
+ text: editText ?? '',
45
+ ...variant === SEGMENT_VARIANT.value && valueMaxWidth && {
46
+ maxWidth: valueMaxWidth
47
+ }
44
48
  });
45
49
  const setInputRef = useCallback((node)=>{
46
50
  inputRef.current = node;
@@ -76,6 +80,9 @@ const Segment = ({ ref, variant, children, className, error, editing, editText,
76
80
  onKeyDown: onEditKeyDown,
77
81
  onBlur: onEditBlur,
78
82
  "aria-label": `Filter ${variant}`,
83
+ ...'end' === caretMode && {
84
+ 'data-caret-mode': 'end'
85
+ },
79
86
  className: cn(segmentTextVariants({
80
87
  variant,
81
88
  error
@@ -0,0 +1 @@
1
+ export declare const applyEditCaret: (input: HTMLInputElement | null) => void;
@@ -0,0 +1,10 @@
1
+ const applyEditCaret = (input)=>{
2
+ if (!input) return;
3
+ if ('end' === input.dataset.caretMode) {
4
+ const end = input.value.length;
5
+ input.setSelectionRange(end, end);
6
+ return;
7
+ }
8
+ input.select();
9
+ };
10
+ export { applyEditCaret };
@@ -1,4 +1,5 @@
1
1
  export { type ConnectorVariant, FilterInputConnectorChip, type FilterInputConnectorChipProps, } from '../FilterInputConnectorChip';
2
+ export { applyEditCaret } from './applyEditCaret';
2
3
  export { type EditingContextValue, EditingProvider, useEditingContext, } from './context/EditingContext';
3
4
  export { type ChipSegment, FilterInputChip, type FilterInputChipProps, type PairSegment, } from './FilterInputChip';
4
5
  export { FilterInputRemoveButton } from './FilterInputRemoveButton';
@@ -1,4 +1,5 @@
1
1
  export { FilterInputConnectorChip } from "../FilterInputConnectorChip/index.js";
2
+ export { applyEditCaret } from "./applyEditCaret.js";
2
3
  export { EditingProvider, useEditingContext } from "./context/EditingContext.js";
3
4
  export { FilterInputChip } from "./FilterInputChip.js";
4
5
  export { FilterInputRemoveButton } from "./FilterInputRemoveButton.js";
@@ -1,6 +1,7 @@
1
1
  import { useCallback } from "react";
2
2
  import { SEGMENT_VARIANT } from "../../FilterInputField/FilterInputChip/index.js";
3
3
  import { useDateRange } from "../../FilterInputMenu/FilterInputDateValueMenu/hooks.js";
4
+ import { isMultiSelectOperator } from "../../lib/index.js";
4
5
  import { deriveAutocompleteValues } from "./lib/index.js";
5
6
  import { useAutocompleteState } from "./useAutocompleteState.js";
6
7
  import { useBlurCommit } from "./useBlurCommit.js";
@@ -218,6 +219,8 @@ const useFilterInputAutocomplete = ({ fields, conditions, chips, upsertCondition
218
219
  buildingBase,
219
220
  editingSide: editing.editingSide
220
221
  });
222
+ const isMultiValueValueEdit = null != editing.editingChipId && editing.editingSegment === SEGMENT_VARIANT.value && null != selectedOperator && isMultiSelectOperator(selectedOperator);
223
+ const liveSegmentFilterText = isMultiValueValueEdit && null != buildingMultiValue && '' === editing.segmentMenuFilterText ? buildingMultiValue : editing.segmentFilterText;
221
224
  return {
222
225
  inputText,
223
226
  menuState,
@@ -260,7 +263,7 @@ const useFilterInputAutocomplete = ({ fields, conditions, chips, upsertCondition
260
263
  editingChipId: editing.editingChipId,
261
264
  editingSegment: editing.editingSegment,
262
265
  editingSide: editing.editingSide,
263
- segmentFilterText: editing.segmentFilterText,
266
+ segmentFilterText: liveSegmentFilterText,
264
267
  segmentMenuFilterText: editing.segmentMenuFilterText,
265
268
  handleSegmentFilterChange,
266
269
  cancelSegmentEdit,
@@ -1,5 +1,5 @@
1
1
  import { useCallback, useEffect, useMemo, useRef } from "react";
2
- import { SEGMENT_VARIANT } from "../../FilterInputField/FilterInputChip/index.js";
2
+ import { SEGMENT_VARIANT, applyEditCaret } from "../../FilterInputField/FilterInputChip/index.js";
3
3
  import { isMenuRelated, nextBuildingMenu } from "../../lib/index.js";
4
4
  const useFocusManagement = ({ menuState, isFocused, conditionsLength, inputText, selectedField, selectedOperator, containerRef, inputRef, editingSegment, segmentAttributeInputRef, segmentOperatorInputRef, segmentValueInputRef, blurCommitRef, commitBuildingOnBlur, hasIncompleteBuilding, setIsFocused, setMenuState, resetMenuAnchor, resetState })=>{
5
5
  const handleFocus = useCallback((e)=>{
@@ -12,12 +12,19 @@ const useFocusManagement = ({ menuState, isFocused, conditionsLength, inputText,
12
12
  const handleBlur = useCallback((e)=>{
13
13
  if (handlingBlurRef.current) return;
14
14
  const related = e.relatedTarget;
15
- if (containerRef.current?.contains(related)) return;
16
15
  if (isMenuRelated(related)) return;
16
+ if (null != related && (related === segmentAttributeInputRef.current || related === segmentOperatorInputRef.current || related === segmentValueInputRef.current)) return;
17
17
  handlingBlurRef.current = true;
18
18
  try {
19
+ const focusStaysInside = !!containerRef.current?.contains(related);
20
+ if ((null != editingSegment || !focusStaysInside) && blurCommitRef.current?.()) {
21
+ setIsFocused(false);
22
+ related?.focus();
23
+ return;
24
+ }
25
+ if (focusStaysInside) return;
19
26
  setIsFocused(false);
20
- const committed = blurCommitRef.current?.() || commitBuildingOnBlur();
27
+ const committed = commitBuildingOnBlur();
21
28
  if (!committed) if (hasIncompleteBuilding()) setMenuState('closed');
22
29
  else resetState();
23
30
  related?.focus();
@@ -33,7 +40,11 @@ const useFocusManagement = ({ menuState, isFocused, conditionsLength, inputText,
33
40
  resetState,
34
41
  setIsFocused,
35
42
  setMenuState,
36
- inputRef
43
+ inputRef,
44
+ editingSegment,
45
+ segmentAttributeInputRef,
46
+ segmentOperatorInputRef,
47
+ segmentValueInputRef
37
48
  ]);
38
49
  const prevFocusedRef = useRef(false);
39
50
  useEffect(()=>{
@@ -87,7 +98,7 @@ const useFocusManagement = ({ menuState, isFocused, conditionsLength, inputText,
87
98
  if (!dest) return;
88
99
  if (document.activeElement === dest) return;
89
100
  dest.focus();
90
- if (editingSegment) dest.select();
101
+ if (editingSegment) applyEditCaret(dest);
91
102
  });
92
103
  });
93
104
  return ()=>{
@@ -39,6 +39,12 @@ const buildBaseChip = (i, condition, field)=>({
39
39
  ...field?.description && {
40
40
  attributeDescription: field.description
41
41
  },
42
+ ...field?.valueMaxWidth && {
43
+ valueMaxWidth: field.valueMaxWidth
44
+ },
45
+ ...field?.caretMode && {
46
+ caretMode: field.caretMode
47
+ },
42
48
  operator: condition.operator ? getOperatorLabel(condition.operator, field?.type || DEFAULT_FIELD_TYPE) : void 0,
43
49
  ...condition.disabled && {
44
50
  disabled: true
@@ -38,6 +38,10 @@ export interface FilterInputChipData {
38
38
  errorValueIndices?: number[];
39
39
  /** When true, the chip cannot be edited or removed */
40
40
  disabled?: boolean;
41
+ /** Applied value-segment width cap in px (see FieldMetadata.valueMaxWidth). */
42
+ valueMaxWidth?: number;
43
+ /** Caret placement on value inline-edit (see FieldMetadata.caretMode). */
44
+ caretMode?: 'end';
41
45
  /** Second paired triplet (display) for two-step fields. */
42
46
  pair?: {
43
47
  attribute: string;
@@ -199,6 +203,18 @@ export interface FieldMetadata {
199
203
  * set it while an async options fetch is in flight.
200
204
  */
201
205
  loadingOptions?: boolean;
206
+ /**
207
+ * Caps the applied value segment at this width in px: a longer value truncates
208
+ * with a trailing ellipsis while the full value is preserved for matching. When
209
+ * set, the chip's default max-width is lifted so it grows to fit the wider value.
210
+ * Omit to keep the chip's default cap. (AS-1064: long parameter-path expressions.)
211
+ */
212
+ valueMaxWidth?: number;
213
+ /**
214
+ * `'end'` places the caret at the end of the value on inline-edit (natural for
215
+ * long values); omit to select the whole value so typing replaces it. (AS-1064)
216
+ */
217
+ caretMode?: 'end';
202
218
  }
203
219
  /**
204
220
  * A labeled group of filter fields for the field-selection menu, referenced by