@wallarm-org/design-system 0.41.0 → 0.42.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.
Files changed (93) hide show
  1. package/dist/components/FilterInput/FilterInput.d.ts +7 -8
  2. package/dist/components/FilterInput/FilterInputContext/types.d.ts +12 -9
  3. package/dist/components/FilterInput/FilterInputContext/useFilterInputContextValue.d.ts +4 -2
  4. package/dist/components/FilterInput/FilterInputContext/useFilterInputContextValue.js +4 -0
  5. package/dist/components/FilterInput/FilterInputErrors/parseFilterInputErrors.js +1 -2
  6. package/dist/components/FilterInput/FilterInputField/ChipsWithGaps.d.ts +1 -1
  7. package/dist/components/FilterInput/FilterInputField/ChipsWithGaps.js +1 -1
  8. package/dist/components/FilterInput/FilterInputField/FilterInputChip/FilterInputChip.d.ts +1 -1
  9. package/dist/components/FilterInput/FilterInputField/FilterInputChip/FilterInputChip.js +2 -2
  10. package/dist/components/FilterInput/FilterInputField/FilterInputField.js +11 -40
  11. package/dist/components/FilterInput/FilterInputField/hooks/useSegmentEditKeyboard.d.ts +37 -0
  12. package/dist/components/FilterInput/FilterInputField/hooks/useSegmentEditKeyboard.js +78 -0
  13. package/dist/components/FilterInput/FilterInputMenu/FilterInputDateValueMenu/constants.d.ts +2 -2
  14. package/dist/components/FilterInput/FilterInputMenu/FilterInputFieldMenu/FilterInputFieldMenu.d.ts +1 -1
  15. package/dist/components/FilterInput/FilterInputMenu/FilterInputMenu.d.ts +1 -1
  16. package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/FilterInputValueMenu.d.ts +5 -6
  17. package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/useValueMenuDisplayValues.d.ts +4 -14
  18. package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/useValueMenuState.d.ts +3 -5
  19. package/dist/components/FilterInput/FilterInputMenu/hooks/useKeyboardNav.d.ts +1 -1
  20. package/dist/components/FilterInput/FilterInputMenu/hooks/useKeyboardNav.js +5 -2
  21. package/dist/components/FilterInput/hooks/useAutoCleanupDetachedElement.d.ts +13 -0
  22. package/dist/components/FilterInput/hooks/useAutoCleanupDetachedElement.js +22 -0
  23. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/{deriveAutocompleteValues.d.ts → lib/deriveAutocompleteValues.d.ts} +3 -3
  24. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/{deriveAutocompleteValues.js → lib/deriveAutocompleteValues.js} +1 -1
  25. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/lib/getInitialSegmentText.d.ts +9 -0
  26. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/lib/getInitialSegmentText.js +8 -0
  27. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/lib/index.d.ts +3 -0
  28. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/lib/index.js +4 -0
  29. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/{valueCommitHelpers.d.ts → lib/valueResolution.d.ts} +1 -7
  30. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/{valueCommitHelpers.js → lib/valueResolution.js} +3 -25
  31. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useAutocompleteState.d.ts +36 -0
  32. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useAutocompleteState.js +53 -0
  33. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useBlurCommit.d.ts +5 -16
  34. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useBlurCommit.js +12 -5
  35. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useChipActions.d.ts +2 -2
  36. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useChipActions.js +5 -5
  37. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useChipCascade.d.ts +44 -0
  38. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useChipCascade.js +99 -0
  39. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useChipEditing.d.ts +7 -8
  40. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useChipEditing.js +21 -16
  41. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useFilterInputAutocomplete.d.ts +9 -10
  42. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useFilterInputAutocomplete.js +50 -83
  43. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useFocusManagement.d.ts +9 -11
  44. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useFocusManagement.js +47 -20
  45. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useInputHandlers.d.ts +6 -4
  46. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useInputHandlers.js +32 -15
  47. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useMenuFlow/index.d.ts +1 -0
  48. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useMenuFlow/index.js +2 -0
  49. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/{useMenuFlow.d.ts → useMenuFlow/types.d.ts} +15 -18
  50. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useMenuFlow/types.js +0 -0
  51. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useMenuFlow/useFieldFlow.d.ts +11 -0
  52. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useMenuFlow/useFieldFlow.js +95 -0
  53. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useMenuFlow/useMenuFlow.d.ts +20 -0
  54. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useMenuFlow/useMenuFlow.js +47 -0
  55. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useMenuFlow/useOperatorFlow.d.ts +11 -0
  56. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useMenuFlow/useOperatorFlow.js +87 -0
  57. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useMenuFlow/useValueFlow.d.ts +14 -0
  58. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useMenuFlow/useValueFlow.js +107 -0
  59. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useMenuPositioning.d.ts +25 -8
  60. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useMenuPositioning.js +38 -22
  61. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useResetState.d.ts +10 -19
  62. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useResetState.js +3 -3
  63. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useSegmentEditFlow.d.ts +36 -0
  64. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useSegmentEditFlow.js +69 -0
  65. package/dist/components/FilterInput/hooks/useFilterInputExpression/buildChips.js +1 -2
  66. package/dist/components/FilterInput/hooks/useFilterInputPositioning.d.ts +9 -4
  67. package/dist/components/FilterInput/hooks/useFilterInputPositioning.js +7 -6
  68. package/dist/components/FilterInput/hooks/useFilterInputSelection/lib/dom.d.ts +4 -4
  69. package/dist/components/FilterInput/hooks/useFilterInputSelection/useFilterInputSelection.d.ts +2 -2
  70. package/dist/components/FilterInput/hooks/useFilterInputSelection/useSelectionClipboard.d.ts +2 -5
  71. package/dist/components/FilterInput/hooks/useFloatingRecomputeOn.d.ts +14 -0
  72. package/dist/components/FilterInput/hooks/useFloatingRecomputeOn.js +18 -0
  73. package/dist/components/FilterInput/hooks/useResizeTracker.d.ts +10 -0
  74. package/dist/components/FilterInput/hooks/useResizeTracker.js +21 -0
  75. package/dist/components/FilterInput/lib/applyAcceptChar.d.ts +3 -4
  76. package/dist/components/FilterInput/lib/applyKnownFieldHelpers.d.ts +13 -25
  77. package/dist/components/FilterInput/lib/constants.d.ts +16 -34
  78. package/dist/components/FilterInput/lib/constants.js +3 -1
  79. package/dist/components/FilterInput/lib/dom.d.ts +15 -11
  80. package/dist/components/FilterInput/lib/dom.js +14 -9
  81. package/dist/components/FilterInput/lib/fields.d.ts +10 -19
  82. package/dist/components/FilterInput/lib/index.d.ts +4 -2
  83. package/dist/components/FilterInput/lib/index.js +5 -3
  84. package/dist/components/FilterInput/lib/menuFilterText.d.ts +8 -18
  85. package/dist/components/FilterInput/lib/operators.d.ts +11 -21
  86. package/dist/components/FilterInput/lib/segmentMenu.d.ts +4 -0
  87. package/dist/components/FilterInput/lib/segmentMenu.js +7 -0
  88. package/dist/components/FilterInput/lib/serializeExpression.d.ts +5 -12
  89. package/dist/components/FilterInput/lib/validation.d.ts +9 -0
  90. package/dist/components/FilterInput/lib/validation.js +24 -0
  91. package/dist/metadata/components.json +8 -8
  92. package/package.json +1 -1
  93. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useMenuFlow.js +0 -260
@@ -2,18 +2,17 @@ import type { FC, HTMLAttributes } from 'react';
2
2
  import type { ExprNode, FieldMetadata } from './types';
3
3
  export interface FilterInputProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'onChange'> {
4
4
  /**
5
- * Filter-field configurations driving the autocomplete. Most fields are
6
- * passed through as-is, but a few names are **reserved** and auto-wired
7
- * with design-system helpers (`acceptChar` / `normalize` / `getSuggestions`
8
- * / `validate`). Current reserved names:
5
+ * Filter-field configurations driving the autocomplete. A few names are
6
+ * **reserved** and auto-wired with design-system helpers DS-supplied
7
+ * callbacks **override** consumer values for the reserved slots, because
8
+ * the field semantics (mask range, accepted chars, backend form) are fixed
9
+ * by DS:
9
10
  *
10
11
  * - `status_code` — HTTP status code field (mask suggestions, format
11
12
  * validation, digit-or-X input filter, partial-input normalization).
12
13
  *
13
- * Consumer-supplied callbacks always override the auto-wiring, so you can
14
- * opt out per-field. For the same helpers on a field with a different
15
- * `name`, import the factories (`createStatusCodeSuggestions`, …) and
16
- * attach them manually.
14
+ * To opt out, use a different `name` and attach the factories
15
+ * (`createStatusCodeSuggestions`, …) manually.
17
16
  */
18
17
  fields?: FieldMetadata[];
19
18
  value?: ExprNode | null;
@@ -22,10 +22,13 @@ export interface FilterInputContextValue {
22
22
  onInputKeyDown: (e: KeyboardEvent<HTMLInputElement>) => void;
23
23
  onInputClick: () => void;
24
24
  onGapClick: (conditionIndex: number, afterConnector: boolean) => void;
25
- onChipClick: (chipId: string, segment: ChipSegment, anchorRect: DOMRect) => void;
26
- /** Click on a segment of the *building* (in-progress) chip re-opens the
27
- * corresponding menu and enters inline-edit without committing the chip. */
28
- onBuildingChipClick: (segment: ChipSegment, anchorRect: DOMRect) => void;
25
+ onChipClick: (chipId: string, segment: ChipSegment, anchorEl: HTMLElement) => void;
26
+ /** Click on a building-chip segment reopens its menu and enters inline-edit. */
27
+ onBuildingChipClick: (segment: ChipSegment, anchorEl: HTMLElement) => void;
28
+ /** Move inline-edit to another segment of the edited chip (Backspace cascade). */
29
+ onSwitchEditSegment: (targetSegment: ChipSegment) => boolean;
30
+ /** Remove the inline-edited chip — Backspace on empty attribute w/o op/value. */
31
+ onRemoveEditingChip: () => void;
29
32
  onConnectorChange: (chipId: string, value: 'and' | 'or') => void;
30
33
  onChipRemove: (chipId: string) => void;
31
34
  onClear: () => void;
@@ -39,14 +42,14 @@ export interface FilterInputContextValue {
39
42
  onCustomOperatorCommit: (customText: string) => void;
40
43
  /** Ref to the currently open menu content element */
41
44
  menuRef: RefObject<HTMLDivElement | null>;
42
- /** Close autocomplete menu (used by connector chip to enforce single-dropdown constraint) */
45
+ /** Close autocomplete menu (connector chip enforces single-dropdown). */
43
46
  closeAutocompleteMenu: () => void;
44
- /** Register/unregister a chip DOM element for selection tracking */
47
+ /** Register/unregister a chip DOM element for selection tracking. */
45
48
  registerChipRef: (id: string, el: HTMLElement | null) => void;
46
- /** Direct ref to the attribute segment <input>attached by Segment when editing. */
49
+ /** Attribute segment input ref set by Segment when editing. */
47
50
  segmentAttributeInputRef: RefObject<HTMLInputElement | null>;
48
- /** Direct ref to the operator segment <input>attached by Segment when editing. */
51
+ /** Operator segment input ref set by Segment when editing. */
49
52
  segmentOperatorInputRef: RefObject<HTMLInputElement | null>;
50
- /** Direct ref to the value segment <input>attached by Segment when editing. */
53
+ /** Value segment input ref set by Segment when editing. */
51
54
  segmentValueInputRef: RefObject<HTMLInputElement | null>;
52
55
  }
@@ -9,8 +9,10 @@ interface AutocompleteForContext {
9
9
  handleInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
10
10
  handleKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => void;
11
11
  handleInputClick: () => void;
12
- handleChipClick: (chipId: string, segment: ChipSegment, anchorRect: DOMRect) => void;
13
- handleBuildingChipClick: (segment: ChipSegment, anchorRect: DOMRect) => void;
12
+ handleChipClick: (chipId: string, segment: ChipSegment, anchorEl: HTMLElement) => void;
13
+ handleBuildingChipClick: (segment: ChipSegment, anchorEl: HTMLElement) => void;
14
+ switchEditSegment: (targetSegment: ChipSegment) => boolean;
15
+ removeEditingChip: () => void;
14
16
  handleConnectorChange: (chipId: string, value: 'and' | 'or') => void;
15
17
  handleChipRemove: (chipId: string) => void;
16
18
  handleClear: () => void;
@@ -17,6 +17,8 @@ const useFilterInputContextValue = ({ chips, autocomplete, buildingChipRef, inpu
17
17
  onInputClick: autocomplete.handleInputClick,
18
18
  onChipClick: autocomplete.handleChipClick,
19
19
  onBuildingChipClick: autocomplete.handleBuildingChipClick,
20
+ onSwitchEditSegment: autocomplete.switchEditSegment,
21
+ onRemoveEditingChip: autocomplete.removeEditingChip,
20
22
  onConnectorChange: autocomplete.handleConnectorChange,
21
23
  onChipRemove: autocomplete.handleChipRemove,
22
24
  onClear: autocomplete.handleClear,
@@ -47,6 +49,8 @@ const useFilterInputContextValue = ({ chips, autocomplete, buildingChipRef, inpu
47
49
  autocomplete.handleInputClick,
48
50
  autocomplete.handleChipClick,
49
51
  autocomplete.handleBuildingChipClick,
52
+ autocomplete.switchEditSegment,
53
+ autocomplete.removeEditingChip,
50
54
  autocomplete.handleConnectorChange,
51
55
  autocomplete.handleChipRemove,
52
56
  autocomplete.handleClear,
@@ -1,6 +1,5 @@
1
1
  import { SEGMENT_VARIANT } from "../FilterInputField/FilterInputChip/index.js";
2
- import { isValidFieldValue } from "../hooks/useFilterInputAutocomplete/valueCommitHelpers.js";
3
- import { getFieldValues, hasStaticAllowlist } from "../lib/index.js";
2
+ import { getFieldValues, hasStaticAllowlist, isValidFieldValue } from "../lib/index.js";
4
3
  const parseFilterInputErrors = (conditions, fields)=>{
5
4
  const errors = [];
6
5
  for (const condition of conditions){
@@ -5,7 +5,7 @@ interface ChipsWithGapsProps {
5
5
  chips: FilterInputChipData[];
6
6
  hideLeadingGap?: boolean;
7
7
  hideTrailingGap?: boolean;
8
- onChipClick: (chipId: string, segment: ChipSegment, anchorRect: DOMRect) => void;
8
+ onChipClick: (chipId: string, segment: ChipSegment, anchorEl: HTMLElement) => void;
9
9
  onConnectorChange: (chipId: string, value: 'and' | 'or') => void;
10
10
  onChipRemove: (chipId: string) => void;
11
11
  onGapClick: (conditionIndex: number, afterConnector: boolean) => void;
@@ -30,7 +30,7 @@ const ChipsWithGaps = ({ chips, hideLeadingGap, hideTrailingGap, onChipClick, on
30
30
  errorValueIndices: chip.errorValueIndices,
31
31
  disabled: chip.disabled,
32
32
  onRemove: chip.disabled ? void 0 : ()=>onChipRemove(chip.id),
33
- onSegmentClick: chip.disabled ? void 0 : (segment, anchorRect)=>onChipClick(chip.id, segment, anchorRect)
33
+ onSegmentClick: chip.disabled ? void 0 : (segment, anchorEl)=>onChipClick(chip.id, segment, anchorEl)
34
34
  })
35
35
  }, chip.id));
36
36
  else if (isConnector) {
@@ -16,6 +16,6 @@ export interface FilterInputChipProps extends Omit<HTMLAttributes<HTMLDivElement
16
16
  /** When true, the chip cannot be edited or removed (dimmed appearance) */
17
17
  disabled?: boolean;
18
18
  onRemove?: () => void;
19
- onSegmentClick?: (segment: ChipSegment, anchorRect: DOMRect) => void;
19
+ onSegmentClick?: (segment: ChipSegment, anchorEl: HTMLElement) => void;
20
20
  }
21
21
  export declare const FilterInputChip: FC<FilterInputChipProps>;
@@ -18,9 +18,9 @@ const FilterInputChip = ({ ref, chipId, attribute, operator, value, error = fals
18
18
  if (!onSegmentClick) return;
19
19
  if (activeSegment === segment) return;
20
20
  e.stopPropagation();
21
- const anchorEl = segment === SEGMENT_VARIANT.attribute ? internalRef.current : e.currentTarget;
21
+ const anchorEl = internalRef.current;
22
22
  if (!anchorEl) return;
23
- onSegmentClick(segment, anchorEl.getBoundingClientRect());
23
+ onSegmentClick(segment, anchorEl);
24
24
  }, [
25
25
  onSegmentClick,
26
26
  activeSegment
@@ -1,21 +1,19 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { useCallback } from "react";
3
2
  import { cn } from "../../../utils/cn.js";
4
3
  import { inputVariants } from "../../Input/classes.js";
5
4
  import { ScrollArea, ScrollAreaScrollbar, ScrollAreaViewport } from "../../ScrollArea/index.js";
6
5
  import { useFilterInputContext } from "../FilterInputContext/index.js";
7
- import { isMenuRelated } from "../lib/index.js";
8
6
  import { ChipsWithGaps, TrailingGap } from "./ChipsWithGaps.js";
9
7
  import { ACTIONS_PADDING, COLLAPSED_MAX_HEIGHT, filterInputContainerVariants, filterInputInnerVariants } from "./classes.js";
10
8
  import { EditingProvider } from "./FilterInputChip/context/EditingContext.js";
11
9
  import { FilterInputChip } from "./FilterInputChip/FilterInputChip.js";
12
- import { SEGMENT_VARIANT } from "./FilterInputChip/segmentVariant.js";
13
10
  import { FilterInputFieldActions } from "./FilterInputFieldActions.js";
14
11
  import { FilterInputSearch } from "./FilterInputSearch.js";
15
12
  import { useChipsSplitting } from "./hooks/useChipsSplitting.js";
16
13
  import { useExpandCollapse } from "./hooks/useExpandCollapse.js";
14
+ import { useSegmentEditKeyboard } from "./hooks/useSegmentEditKeyboard.js";
17
15
  const FilterInputField = ({ className, ...props })=>{
18
- const { chips, buildingChipData, buildingChipRef, insertIndex, insertAfterConnector, error, onInputClick, onGapClick, onChipClick, onBuildingChipClick, onConnectorChange, onChipRemove, editingChipId, editingSegment, segmentFilterText, onSegmentFilterChange, onCancelSegmentEdit, onCustomValueCommit, onCustomAttributeCommit, onCustomOperatorCommit, menuRef } = useFilterInputContext();
16
+ const { chips, buildingChipData, buildingChipRef, insertIndex, insertAfterConnector, error, onInputClick, onGapClick, onChipClick, onBuildingChipClick, onConnectorChange, onChipRemove, editingChipId, editingSegment, segmentFilterText, onSegmentFilterChange, onCancelSegmentEdit, onCustomValueCommit, onCustomAttributeCommit, onCustomOperatorCommit, onSwitchEditSegment, onRemoveEditingChip, menuRef } = useFilterInputContext();
19
17
  const hasContent = chips.length > 0 || null != buildingChipData;
20
18
  const { isExpanded, isOverflowing, innerRef, toggleExpand, multiRow } = useExpandCollapse();
21
19
  const { chipsBefore, chipsAfter, hideTrailingGap, hideLeadingGap } = useChipsSplitting(chips, insertIndex, insertAfterConnector);
@@ -25,47 +23,20 @@ const FilterInputField = ({ className, ...props })=>{
25
23
  onChipRemove,
26
24
  onGapClick
27
25
  };
28
- const handleSegmentEditKeyDown = useCallback((e)=>{
29
- if ('Escape' === e.key) {
30
- e.preventDefault();
31
- onCancelSegmentEdit();
32
- return;
33
- }
34
- if ('Enter' === e.key && !e.defaultPrevented) {
35
- if (editingSegment === SEGMENT_VARIANT.value) {
36
- e.preventDefault();
37
- onCustomValueCommit(segmentFilterText);
38
- return;
39
- }
40
- if (editingSegment === SEGMENT_VARIANT.attribute) {
41
- e.preventDefault();
42
- onCustomAttributeCommit(segmentFilterText);
43
- return;
44
- }
45
- if (editingSegment === SEGMENT_VARIANT.operator) {
46
- e.preventDefault();
47
- onCustomOperatorCommit(segmentFilterText);
48
- return;
49
- }
50
- }
51
- if ('ArrowDown' === e.key) {
52
- e.preventDefault();
53
- menuRef.current?.focus();
54
- }
55
- }, [
56
- onCancelSegmentEdit,
26
+ const { handleSegmentEditKeyDown, handleSegmentEditBlur } = useSegmentEditKeyboard({
27
+ editingChipId,
57
28
  editingSegment,
58
29
  segmentFilterText,
30
+ chips,
31
+ buildingChipData,
32
+ menuRef,
33
+ onCancelSegmentEdit,
59
34
  onCustomValueCommit,
60
35
  onCustomAttributeCommit,
61
36
  onCustomOperatorCommit,
62
- menuRef
63
- ]);
64
- const handleSegmentEditBlur = useCallback((e)=>{
65
- if (!isMenuRelated(e.relatedTarget)) onCancelSegmentEdit();
66
- }, [
67
- onCancelSegmentEdit
68
- ]);
37
+ onSwitchEditSegment,
38
+ onRemoveEditingChip
39
+ });
69
40
  return /*#__PURE__*/ jsx(EditingProvider, {
70
41
  editingChipId: editingChipId,
71
42
  editingSegment: editingSegment,
@@ -0,0 +1,37 @@
1
+ import type { FocusEvent, KeyboardEvent, RefObject } from 'react';
2
+ import type { BuildingChipData } from '../../FilterInputContext/types';
3
+ import type { FilterInputChipData } from '../../types';
4
+ import { type ChipSegment } from '../FilterInputChip';
5
+ interface UseSegmentEditKeyboardOptions {
6
+ editingChipId: string | null;
7
+ editingSegment: ChipSegment | null;
8
+ segmentFilterText: string;
9
+ chips: FilterInputChipData[];
10
+ buildingChipData: BuildingChipData | null;
11
+ menuRef: RefObject<HTMLDivElement | null>;
12
+ onCancelSegmentEdit: () => void;
13
+ onCustomValueCommit: (text: string) => void;
14
+ onCustomAttributeCommit: (text: string) => void;
15
+ onCustomOperatorCommit: (text: string) => void;
16
+ onSwitchEditSegment: (target: ChipSegment) => boolean;
17
+ onRemoveEditingChip: () => void;
18
+ }
19
+ /**
20
+ * Keyboard handlers for a chip segment's inline-edit input:
21
+ *
22
+ * - Escape cancels.
23
+ * - Backspace on an empty input walks the cascade (value → operator →
24
+ * attribute) and, if the attribute is empty with no operator/value left,
25
+ * removes the chip.
26
+ * - Enter commits the typed text via the segment-specific commit callback.
27
+ * - ArrowDown is intercepted (preventDefault) but focus stays on the input —
28
+ * highlight navigation runs through useKeyboardNav's window-capture listener.
29
+ *
30
+ * Returns the keydown + blur handlers fed to `EditingProvider` so every
31
+ * segment input dispatches through the same logic.
32
+ */
33
+ export declare const useSegmentEditKeyboard: ({ editingChipId, editingSegment, segmentFilterText, chips, buildingChipData, menuRef, onCancelSegmentEdit, onCustomValueCommit, onCustomAttributeCommit, onCustomOperatorCommit, onSwitchEditSegment, onRemoveEditingChip, }: UseSegmentEditKeyboardOptions) => {
34
+ handleSegmentEditKeyDown: (e: KeyboardEvent<HTMLInputElement>) => void;
35
+ handleSegmentEditBlur: (e: FocusEvent<HTMLInputElement>) => void;
36
+ };
37
+ export {};
@@ -0,0 +1,78 @@
1
+ import { useCallback } from "react";
2
+ import { isMenuRelated } from "../../lib/index.js";
3
+ import { SEGMENT_VARIANT } from "../FilterInputChip/index.js";
4
+ const useSegmentEditKeyboard = ({ editingChipId, editingSegment, segmentFilterText, chips, buildingChipData, menuRef, onCancelSegmentEdit, onCustomValueCommit, onCustomAttributeCommit, onCustomOperatorCommit, onSwitchEditSegment, onRemoveEditingChip })=>{
5
+ const handleSegmentEditKeyDown = useCallback((e)=>{
6
+ if ('Escape' === e.key) {
7
+ e.preventDefault();
8
+ onCancelSegmentEdit();
9
+ return;
10
+ }
11
+ if ('Backspace' === e.key && '' === segmentFilterText) {
12
+ if (e.nativeEvent.isComposing || e.repeat) return;
13
+ if (editingSegment === SEGMENT_VARIANT.attribute) {
14
+ const chipForEdit = editingChipId ? chips.find((c)=>c.id === editingChipId && 'chip' === c.variant) : null;
15
+ const operator = chipForEdit?.operator ?? buildingChipData?.operator ?? '';
16
+ if (!operator) {
17
+ e.preventDefault();
18
+ onRemoveEditingChip();
19
+ }
20
+ return;
21
+ }
22
+ if (editingSegment === SEGMENT_VARIANT.operator) {
23
+ e.preventDefault();
24
+ onSwitchEditSegment(SEGMENT_VARIANT.attribute);
25
+ return;
26
+ }
27
+ if (editingSegment === SEGMENT_VARIANT.value) {
28
+ e.preventDefault();
29
+ onSwitchEditSegment(SEGMENT_VARIANT.operator);
30
+ return;
31
+ }
32
+ }
33
+ if ('Enter' === e.key && !e.defaultPrevented) {
34
+ if (editingSegment === SEGMENT_VARIANT.value) {
35
+ e.preventDefault();
36
+ onCustomValueCommit(segmentFilterText);
37
+ return;
38
+ }
39
+ if (editingSegment === SEGMENT_VARIANT.attribute) {
40
+ e.preventDefault();
41
+ onCustomAttributeCommit(segmentFilterText);
42
+ return;
43
+ }
44
+ if (editingSegment === SEGMENT_VARIANT.operator) {
45
+ e.preventDefault();
46
+ onCustomOperatorCommit(segmentFilterText);
47
+ return;
48
+ }
49
+ }
50
+ if ('ArrowDown' === e.key) {
51
+ e.preventDefault();
52
+ menuRef.current?.focus();
53
+ }
54
+ }, [
55
+ onCancelSegmentEdit,
56
+ editingSegment,
57
+ segmentFilterText,
58
+ onCustomValueCommit,
59
+ onCustomAttributeCommit,
60
+ onCustomOperatorCommit,
61
+ onSwitchEditSegment,
62
+ onRemoveEditingChip,
63
+ editingChipId,
64
+ chips,
65
+ buildingChipData,
66
+ menuRef
67
+ ]);
68
+ const handleSegmentEditBlur = useCallback((e)=>{
69
+ if (!isMenuRelated(e.relatedTarget)) onCancelSegmentEdit();
70
+ }, [
71
+ onCancelSegmentEdit
72
+ ]);
73
+ return {
74
+ handleSegmentEditKeyDown,
75
+ handleSegmentEditBlur
76
+ };
77
+ };
78
+ export { useSegmentEditKeyboard };
@@ -10,8 +10,8 @@ export interface DatePreset {
10
10
  export declare const DATE_PRESETS: DatePreset[];
11
11
  /** Check if a value string is a relative date preset (e.g. "30m", "7d") */
12
12
  export declare const isDatePreset: (value: string) => boolean;
13
- /** Format a date string for chip display (e.g. "Mar 4, 2026").
14
- * Handles both ISO (YYYY-MM-DD, parsed as UTC) and locale formats (parsed as local time). */
13
+ /** Format a date string for chip display (e.g. "Mar 4, 2026"). ISO parsed as
14
+ * UTC, locale strings as local time. */
15
15
  export declare const formatDateForChip: (value: string) => string;
16
16
  /** Get display label for a date value — preset label or formatted date */
17
17
  export declare const getDateDisplayLabel: (value: string) => string;
@@ -16,7 +16,7 @@ export interface FilterInputFieldMenuProps {
16
16
  positioning?: Record<string, unknown>;
17
17
  /** Ref to the query bar input — ArrowUp on first item returns focus here */
18
18
  inputRef?: RefObject<HTMLInputElement | null>;
19
- /** Ref to the menu content element — shared across menus for focus management */
19
+ /** Ref to the menu content (shared across menus for focus management). */
20
20
  menuRef?: RefObject<HTMLDivElement | null>;
21
21
  className?: string;
22
22
  }
@@ -24,7 +24,7 @@ export interface FilterInputAutocompleteState {
24
24
  segmentFilterText: string;
25
25
  segmentMenuFilterText: string;
26
26
  editingSegment: ChipSegment | null;
27
- /** Ref for multi-select blur commit set by value menu, called by blur handler */
27
+ /** Multi-select blur commit; set by value menu, called by blur handler. */
28
28
  blurCommitRef: RefObject<(() => boolean) | null>;
29
29
  }
30
30
  export interface FilterInputMenuProps {
@@ -23,16 +23,15 @@ export interface FilterInputValueMenuProps {
23
23
  width?: 'standard' | 'compact' | number;
24
24
  positioning?: Record<string, unknown>;
25
25
  onBuildingValueChange?: (preview: string | undefined) => void;
26
- /** Fires on explicit multi-select toggle (click or keyboard) — use to react
27
- * only to user-initiated toggles, not to initialization. */
26
+ /** Fires only on user-initiated multi-select toggle (not on init). */
28
27
  onItemToggle?: () => void;
29
- /** Ref to the query bar input — ArrowUp on first item returns focus here */
28
+ /** Query bar input — ArrowUp on first item returns focus here. */
30
29
  inputRef?: RefObject<HTMLInputElement | null>;
31
- /** Text to filter values by label */
30
+ /** Filter values by label. */
32
31
  filterText?: string;
33
- /** Ref to the menu content element shared across menus for focus management */
32
+ /** Menu content ref (shared across menus for focus management). */
34
33
  menuRef?: RefObject<HTMLDivElement | null>;
35
- /** Ref set by this component to allow blur handler to commit multi-select values */
34
+ /** Set here so blur handler can commit multi-select values. */
36
35
  blurCommitRef?: RefObject<(() => boolean) | null>;
37
36
  className?: string;
38
37
  }
@@ -12,20 +12,10 @@ interface UseValueMenuDisplayValuesOptions {
12
12
  highlightValue?: ConditionValue;
13
13
  }
14
14
  /**
15
- * Compose the final dropdown list shown to the user.
16
- *
17
- * The parent helper (`values`) is free to change shape between renders — for
18
- * instance a dynamic `getSuggestions` may narrow its result after a selection
19
- * is made. This hook keeps the user's currently-selected entries pinned at
20
- * the top of the list with a stable presentation:
21
- *
22
- * 1. Every option the menu has ever rendered is remembered in a `Map` keyed
23
- * by value. When a selected entry is no longer in the current `values`,
24
- * the remembered option (with its original label/badge) is used.
25
- * 2. If nothing has been seen for that value either, a plain-text option is
26
- * fabricated so the user can still see and toggle it.
27
- * 3. Unchecked items come from `filteredValues` (already filter-sorted) so
28
- * the search query still applies to them.
15
+ * Compose the dropdown list, pinning selected entries at the top.
16
+ * Remembers every option ever rendered so a narrowed `values` (e.g. dynamic
17
+ * getSuggestions) can still display the selected label/badge; fabricates a
18
+ * plain-text option as last resort. Unselected items still respect the filter.
29
19
  */
30
20
  export declare const useValueMenuDisplayValues: ({ values, filteredValues, multiSelect, checkedValues, highlightValue, }: UseValueMenuDisplayValuesOptions) => ValueOption[];
31
21
  export {};
@@ -13,14 +13,12 @@ interface UseValueMenuStateOptions {
13
13
  onEscape?: () => void;
14
14
  onOpenChange?: (open: boolean) => void;
15
15
  onBuildingValueChange?: (preview: string | undefined) => void;
16
- /** Fires whenever the user explicitly toggles an item in multi-select mode
17
- * (click or keyboard). Distinct from onBuildingValueChange, which also fires
18
- * on mount with the initial preview — use this when you need to react only
19
- * to actual user-initiated toggles. */
16
+ /** Fires only on user-initiated toggles in multi-select (unlike
17
+ * onBuildingValueChange which also fires on mount). */
20
18
  onItemToggle?: () => void;
21
19
  inputRef?: RefObject<HTMLInputElement | null>;
22
20
  menuRef?: RefObject<HTMLDivElement | null>;
23
- /** Ref to register blur commit function — called by blur handler before state reset. Returns true if committed. */
21
+ /** Register blur commit fn — called by blur handler before state reset. */
24
22
  blurCommitRef?: RefObject<(() => boolean) | null>;
25
23
  }
26
24
  export declare const useValueMenuState: ({ values, open, multiSelect, initialValues, highlightValue, onSelect, onCommit, onEscape, onOpenChange, onBuildingValueChange, onItemToggle, inputRef, menuRef, blurCommitRef, }: UseValueMenuStateOptions) => {
@@ -12,7 +12,7 @@ interface UseKeyboardNavOptions {
12
12
  arrowRightSelectsActive?: boolean;
13
13
  /** When provided, ArrowUp on the first item returns focus to this input */
14
14
  inputRef?: RefObject<HTMLInputElement | null>;
15
- /** Ref to the menu content element — used for scoped queries and focus management */
15
+ /** Ref to the menu content element for scoped queries. */
16
16
  menuRef?: RefObject<HTMLDivElement | null>;
17
17
  }
18
18
  /**
@@ -141,7 +141,11 @@ const useKeyboardNav = ({ items, open, onSelect, onClose, onArrowRight, onPendin
141
141
  return;
142
142
  }
143
143
  const isSegmentInput = e.target?.closest?.('[data-slot^="segment-"]');
144
- if (isSegmentInput && 'Escape' !== e.key && 'ArrowDown' !== e.key && 'ArrowUp' !== e.key) return;
144
+ if (isSegmentInput) {
145
+ const isEnterWithHighlight = 'Enter' === e.key && activeIndexRef.current >= 0;
146
+ const isNavOrClose = 'Escape' === e.key || 'ArrowDown' === e.key || 'ArrowUp' === e.key;
147
+ if (!isNavOrClose && !isEnterWithHighlight) return;
148
+ }
145
149
  const { items: list, onSelect: select, onClose: close, onArrowRight: arrowRight } = stateRef.current;
146
150
  if (0 === list.length) return;
147
151
  switch(e.key){
@@ -154,7 +158,6 @@ const useKeyboardNav = ({ items, open, onSelect, onClose, onArrowRight, onPendin
154
158
  e.preventDefault();
155
159
  e.stopPropagation();
156
160
  navigate('ArrowDown' === e.key ? 1 : -1);
157
- stateRef.current.menuRef?.current?.focus();
158
161
  break;
159
162
  case 'Enter':
160
163
  {
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Watches a captured DOM element and runs `onDetached` as soon as the element
3
+ * leaves the document tree. Useful for state that stores a reference to a
4
+ * conditionally rendered element (e.g. a clicked chip) — if the parent removes
5
+ * the element via a controlled prop change while the state still holds it,
6
+ * downstream consumers can otherwise read a stale rect from a detached node.
7
+ *
8
+ * The MutationObserver is scoped to `el.parentElement` (subtree+childList) so
9
+ * unrelated mutations elsewhere on the page don't wake the callback. If the
10
+ * parent itself is removed in the same tick `el.parentElement` is null and we
11
+ * fall back to `documentElement` to keep the safety net.
12
+ */
13
+ export declare const useAutoCleanupDetachedElement: (el: HTMLElement | null, onDetached: () => void) => void;
@@ -0,0 +1,22 @@
1
+ import { useEffect } from "react";
2
+ const useAutoCleanupDetachedElement = (el, onDetached)=>{
3
+ useEffect(()=>{
4
+ if (!el) return;
5
+ if (!el.isConnected) return void onDetached();
6
+ if ("u" < typeof MutationObserver) return;
7
+ const root = el.parentElement ?? el.ownerDocument?.documentElement;
8
+ if (!root) return;
9
+ const observer = new MutationObserver(()=>{
10
+ if (!el.isConnected) onDetached();
11
+ });
12
+ observer.observe(root, {
13
+ childList: true,
14
+ subtree: true
15
+ });
16
+ return ()=>observer.disconnect();
17
+ }, [
18
+ el,
19
+ onDetached
20
+ ]);
21
+ };
22
+ export { useAutoCleanupDetachedElement };
@@ -1,5 +1,5 @@
1
- import type { BuildingChipData } from '../../FilterInputContext/types';
2
- import type { Condition, FieldMetadata, FilterOperator } from '../../types';
1
+ import type { BuildingChipData } from '../../../FilterInputContext/types';
2
+ import type { Condition, FieldMetadata, FilterOperator } from '../../../types';
3
3
  interface DeriveOptions {
4
4
  editingChipId: string | null;
5
5
  selectedField: FieldMetadata | null;
@@ -7,7 +7,7 @@ interface DeriveOptions {
7
7
  conditions: Condition[];
8
8
  buildingMultiValue: string | undefined;
9
9
  dateRangeFromValue: string | null | undefined;
10
- /** Segment text when inline-editing a value — used to derive checked values from text */
10
+ /** Segment text when inline-editing a value. */
11
11
  segmentFilterText?: string;
12
12
  }
13
13
  export interface DerivedAutocompleteValues {
@@ -1,4 +1,4 @@
1
- import { NO_VALUE_PLACEHOLDER, chipIdToConditionIndex, getDateDisplayLabel, getFieldValues, getOperatorLabel, hasStaticAllowlist, isMultiSelectOperator, isNoValueOperator } from "../../lib/index.js";
1
+ import { NO_VALUE_PLACEHOLDER, chipIdToConditionIndex, getDateDisplayLabel, getFieldValues, getOperatorLabel, hasStaticAllowlist, isMultiSelectOperator, isNoValueOperator } from "../../../lib/index.js";
2
2
  const getEditingCondition = (editingChipId, conditions)=>{
3
3
  if (!editingChipId) return null;
4
4
  const idx = chipIdToConditionIndex(editingChipId);
@@ -0,0 +1,9 @@
1
+ import { type ChipSegment } from '../../../FilterInputField/FilterInputChip';
2
+ import type { FieldMetadata, FilterOperator } from '../../../types';
3
+ /**
4
+ * Initial text shown in a building chip's inline-edit input for the given
5
+ * segment. Mirrors what the chip is currently displaying so the user can
6
+ * backspace through it (segment-click reopen + Backspace cascade share this
7
+ * derivation, hence the standalone helper).
8
+ */
9
+ export declare const getInitialSegmentText: (segment: ChipSegment, selectedField: FieldMetadata, selectedOperator: FilterOperator | null, buildingMultiValue: string | undefined) => string;
@@ -0,0 +1,8 @@
1
+ import { SEGMENT_VARIANT } from "../../../FilterInputField/FilterInputChip/index.js";
2
+ import { getOperatorLabel } from "../../../lib/index.js";
3
+ const getInitialSegmentText = (segment, selectedField, selectedOperator, buildingMultiValue)=>{
4
+ if (segment === SEGMENT_VARIANT.attribute) return selectedField.label;
5
+ if (segment === SEGMENT_VARIANT.operator) return selectedOperator ? getOperatorLabel(selectedOperator, selectedField.type) : '';
6
+ return buildingMultiValue ?? '';
7
+ };
8
+ export { getInitialSegmentText };
@@ -0,0 +1,3 @@
1
+ export { deriveAutocompleteValues } from './deriveAutocompleteValues';
2
+ export { getInitialSegmentText } from './getInitialSegmentText';
3
+ export { displayDateToIso, resolveDateRangeValue, resolveDateValue, resolveFieldValue, resolveMultiValues, resolveSingleValue, } from './valueResolution';
@@ -0,0 +1,4 @@
1
+ import { deriveAutocompleteValues } from "./deriveAutocompleteValues.js";
2
+ import { getInitialSegmentText } from "./getInitialSegmentText.js";
3
+ import { displayDateToIso, resolveDateRangeValue, resolveDateValue, resolveFieldValue, resolveMultiValues, resolveSingleValue } from "./valueResolution.js";
4
+ export { deriveAutocompleteValues, displayDateToIso, getInitialSegmentText, resolveDateRangeValue, resolveDateValue, resolveFieldValue, resolveMultiValues, resolveSingleValue };
@@ -1,10 +1,4 @@
1
- import type { Condition, FieldMetadata, FieldValueOption } from '../../types';
2
- /** Check if a single value matches any option in the field's values list */
3
- export declare const isValidFieldValue: (fieldValues: FieldValueOption[], v: string | number | boolean) => boolean;
4
- /** Return indices of values that don't match any field option. Empty array = all valid. */
5
- export declare const getInvalidValueIndices: (field: FieldMetadata, values: Array<string | number | boolean>) => number[];
6
- /** Check if condition value(s) are valid for the given field. Returns true if error. */
7
- export declare const validateValueForField: (field: FieldMetadata, value: Condition["value"]) => boolean;
1
+ import type { Condition, FieldMetadata } from '../../../types';
8
2
  /** Resolve a text input to the actual field value (e.g. label "Active" → value "active") */
9
3
  export declare const resolveFieldValue: (field: FieldMetadata, text: string) => string | number | boolean;
10
4
  /** Resolve and validate a single-select value from text */