@wallarm-org/design-system 1.0.0 → 1.1.0-rc-feature-AS-1060.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/FilterInputField/ChipsWithGaps.js +1 -0
- package/dist/components/FilterInput/FilterInputField/FilterInputChip/FilterInputChip.d.ts +3 -0
- package/dist/components/FilterInput/FilterInputField/FilterInputChip/FilterInputChip.js +23 -10
- package/dist/components/FilterInput/FilterInputField/FilterInputChip/Segment.d.ts +4 -1
- package/dist/components/FilterInput/FilterInputField/FilterInputChip/Segment.js +2 -1
- package/dist/components/FilterInput/FilterInputMenu/FilterInputFieldMenu/FieldMenuPopover.d.ts +21 -0
- package/dist/components/FilterInput/FilterInputMenu/FilterInputFieldMenu/FieldMenuPopover.js +75 -0
- package/dist/components/FilterInput/FilterInputMenu/FilterInputFieldMenu/FilterInputFieldMenu.js +97 -61
- package/dist/components/FilterInput/FilterInputMenu/hooks/useKeyboardNav.d.ts +1 -0
- package/dist/components/FilterInput/FilterInputMenu/hooks/useKeyboardNav.js +3 -1
- package/dist/components/FilterInput/FilterInputMenu/hooks/useMenuScrollHighlightSync.d.ts +13 -0
- package/dist/components/FilterInput/FilterInputMenu/hooks/useMenuScrollHighlightSync.js +46 -0
- package/dist/components/FilterInput/hooks/useFilterInputExpression/buildChips.js +3 -0
- package/dist/components/FilterInput/types.d.ts +10 -0
- package/dist/metadata/components.json +8 -2
- package/package.json +1 -1
|
@@ -22,6 +22,7 @@ const ChipsWithGaps = ({ chips, hideLeadingGap, hideTrailingGap, onChipClick, on
|
|
|
22
22
|
children: /*#__PURE__*/ jsx(FilterInputChip, {
|
|
23
23
|
chipId: chip.id,
|
|
24
24
|
attribute: chip.attribute ?? '',
|
|
25
|
+
attributeDescription: chip.attributeDescription,
|
|
25
26
|
operator: chip.operator,
|
|
26
27
|
value: chip.value,
|
|
27
28
|
error: chip.error,
|
|
@@ -8,6 +8,9 @@ export interface FilterInputChipProps extends Omit<HTMLAttributes<HTMLDivElement
|
|
|
8
8
|
ref?: Ref<HTMLDivElement>;
|
|
9
9
|
chipId?: string;
|
|
10
10
|
attribute: string;
|
|
11
|
+
/** Description shown in a dark tooltip on hover of the attribute segment. When
|
|
12
|
+
* absent, no tooltip renders. The operator/value segments are unaffected (AS-1060). */
|
|
13
|
+
attributeDescription?: string;
|
|
11
14
|
operator?: string;
|
|
12
15
|
value?: string;
|
|
13
16
|
error?: ChipErrorSegment;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useRef } from "react";
|
|
3
3
|
import { cn } from "../../../../utils/cn.js";
|
|
4
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "../../../Tooltip/index.js";
|
|
4
5
|
import { ChipSearchInput } from "./ChipSearchInput.js";
|
|
5
6
|
import { chipVariants, emptyValueHitTarget } from "./classes.js";
|
|
6
7
|
import { useEditingContext } from "./context/EditingContext.js";
|
|
@@ -8,7 +9,7 @@ import { FilterInputRemoveButton } from "./FilterInputRemoveButton.js";
|
|
|
8
9
|
import { PairSeparator } from "./PairSeparator.js";
|
|
9
10
|
import { Segment } from "./Segment.js";
|
|
10
11
|
import { SEGMENT_VARIANT } from "./segmentVariant.js";
|
|
11
|
-
const FilterInputChip = ({ ref, chipId, attribute, 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, pair, onRemove, onSegmentClick, onPairSegmentClick, className, ...props })=>{
|
|
12
13
|
const interactive = !disabled;
|
|
13
14
|
const internalRef = useRef(null);
|
|
14
15
|
const editing = useEditingContext();
|
|
@@ -50,6 +51,15 @@ const FilterInputChip = ({ ref, chipId, attribute, operator, value, error = fals
|
|
|
50
51
|
} : {};
|
|
51
52
|
const baseActiveSegment = 0 === editingSide ? activeSegment : null;
|
|
52
53
|
const pairActiveSegment = 1 === editingSide ? activeSegment : null;
|
|
54
|
+
const attributeSegment = /*#__PURE__*/ jsx(Segment, {
|
|
55
|
+
variant: SEGMENT_VARIANT.attribute,
|
|
56
|
+
className: "shrink-0",
|
|
57
|
+
error: true === effectiveError || effectiveError === SEGMENT_VARIANT.attribute,
|
|
58
|
+
onClick: interactive ? (e)=>handleSegmentClick(SEGMENT_VARIANT.attribute, e) : void 0,
|
|
59
|
+
onMouseDown: interactive && building ? handleSegmentMouseDown : void 0,
|
|
60
|
+
...segmentEditProps(SEGMENT_VARIANT.attribute),
|
|
61
|
+
children: attribute
|
|
62
|
+
});
|
|
53
63
|
const setRefs = useCallback((node)=>{
|
|
54
64
|
internalRef.current = node;
|
|
55
65
|
if ('function' == typeof ref) ref(node);
|
|
@@ -71,15 +81,18 @@ const FilterInputChip = ({ ref, chipId, attribute, operator, value, error = fals
|
|
|
71
81
|
},
|
|
72
82
|
...props,
|
|
73
83
|
children: [
|
|
74
|
-
/*#__PURE__*/
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
attributeDescription ? /*#__PURE__*/ jsxs(Tooltip, {
|
|
85
|
+
disabled: baseActiveSegment === SEGMENT_VARIANT.attribute,
|
|
86
|
+
children: [
|
|
87
|
+
/*#__PURE__*/ jsx(TooltipTrigger, {
|
|
88
|
+
asChild: true,
|
|
89
|
+
children: attributeSegment
|
|
90
|
+
}),
|
|
91
|
+
/*#__PURE__*/ jsx(TooltipContent, {
|
|
92
|
+
children: attributeDescription
|
|
93
|
+
})
|
|
94
|
+
]
|
|
95
|
+
}) : attributeSegment,
|
|
83
96
|
(operator || baseActiveSegment === SEGMENT_VARIANT.operator) && /*#__PURE__*/ jsx(Segment, {
|
|
84
97
|
variant: SEGMENT_VARIANT.operator,
|
|
85
98
|
className: "shrink-0",
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import type { FC, FocusEvent, HTMLAttributes, KeyboardEvent } from 'react';
|
|
1
|
+
import type { FC, FocusEvent, HTMLAttributes, KeyboardEvent, Ref } from 'react';
|
|
2
2
|
import { type SegmentVariant } from './segmentVariant';
|
|
3
3
|
export type SegmentProps = HTMLAttributes<HTMLDivElement> & {
|
|
4
|
+
/** Forwarded to the segment container — lets a Tooltip/Popover trigger anchor
|
|
5
|
+
* to it via `asChild` (attribute-segment chip tooltip, AS-1060). */
|
|
6
|
+
ref?: Ref<HTMLDivElement>;
|
|
4
7
|
variant: SegmentVariant;
|
|
5
8
|
children: string;
|
|
6
9
|
error?: boolean;
|
|
@@ -7,7 +7,7 @@ import { CHAR_WIDTH_PX } from "./constants.js";
|
|
|
7
7
|
import { MultiValueSegment } from "./MultiValueSegment.js";
|
|
8
8
|
import { useSizerWidth } from "./model/useSizerWidth.js";
|
|
9
9
|
import { SEGMENT_VARIANT } from "./segmentVariant.js";
|
|
10
|
-
const Segment = ({ variant, children, className, error, editing, editText, onEditChange, onEditKeyDown, onEditBlur, valueParts, valueSeparator = ', ', errorValueIndices, ...props })=>{
|
|
10
|
+
const Segment = ({ ref, variant, children, className, error, editing, editText, onEditChange, onEditKeyDown, onEditBlur, valueParts, valueSeparator = ', ', errorValueIndices, ...props })=>{
|
|
11
11
|
const textRef = useRef(null);
|
|
12
12
|
const inputRef = useRef(null);
|
|
13
13
|
const sizerRef = useRef(null);
|
|
@@ -58,6 +58,7 @@ const Segment = ({ variant, children, className, error, editing, editText, onEdi
|
|
|
58
58
|
...props
|
|
59
59
|
});
|
|
60
60
|
return /*#__PURE__*/ jsx("div", {
|
|
61
|
+
ref: ref,
|
|
61
62
|
className: cn(segmentContainer, className),
|
|
62
63
|
"data-slot": `segment-${variant}`,
|
|
63
64
|
...isInteractive && {
|
package/dist/components/FilterInput/FilterInputMenu/FilterInputFieldMenu/FieldMenuPopover.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type FC } from 'react';
|
|
2
|
+
export interface FieldMenuPopoverProps {
|
|
3
|
+
/** Menu open AND a described field is highlighted. */
|
|
4
|
+
open: boolean;
|
|
5
|
+
/** Filter name, shown monospace, matching the menu label's casing. */
|
|
6
|
+
title: string;
|
|
7
|
+
/** Short "what it filters by" description. */
|
|
8
|
+
description: string;
|
|
9
|
+
/** Optional monospace example block (wildcards, path patterns, ranges, IDs). */
|
|
10
|
+
example?: string;
|
|
11
|
+
/** Live rect of the highlighted row the popover aligns to. */
|
|
12
|
+
getAnchorRect: () => DOMRect | null;
|
|
13
|
+
/** Highlighted id; a change pokes a reposition as the anchor swaps between rows. */
|
|
14
|
+
repositionKey: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Discovery popover for the field-selection menu: on hover or keyboard focus of a
|
|
18
|
+
* filter attribute, surfaces its title, description, and optional example (AS-1060).
|
|
19
|
+
* Fully controlled by `open` + `getAnchorRect`; no trigger, never takes focus.
|
|
20
|
+
*/
|
|
21
|
+
export declare const FieldMenuPopover: FC<FieldMenuPopoverProps>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
import { Popover } from "@ark-ui/react";
|
|
4
|
+
import { PopoverContent } from "../../../Popover/index.js";
|
|
5
|
+
import { Separator } from "../../../Separator/index.js";
|
|
6
|
+
import { useFloatingRecomputeOn } from "../../hooks/useFloatingRecomputeOn.js";
|
|
7
|
+
const POPOVER_POSITIONING = {
|
|
8
|
+
placement: 'right-start',
|
|
9
|
+
gutter: 4,
|
|
10
|
+
flip: true,
|
|
11
|
+
overflowPadding: 8
|
|
12
|
+
};
|
|
13
|
+
const FieldMenuPopover = ({ open, title, description, example, getAnchorRect, repositionKey })=>{
|
|
14
|
+
useFloatingRecomputeOn(repositionKey, open);
|
|
15
|
+
useEffect(()=>{
|
|
16
|
+
if (!open) return;
|
|
17
|
+
let raf = 0;
|
|
18
|
+
const onScroll = ()=>{
|
|
19
|
+
if (raf) return;
|
|
20
|
+
raf = requestAnimationFrame(()=>{
|
|
21
|
+
raf = 0;
|
|
22
|
+
window.dispatchEvent(new Event('resize'));
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
window.addEventListener('scroll', onScroll, true);
|
|
26
|
+
return ()=>{
|
|
27
|
+
window.removeEventListener('scroll', onScroll, true);
|
|
28
|
+
if (raf) cancelAnimationFrame(raf);
|
|
29
|
+
};
|
|
30
|
+
}, [
|
|
31
|
+
open
|
|
32
|
+
]);
|
|
33
|
+
return /*#__PURE__*/ jsx(Popover.Root, {
|
|
34
|
+
open: open,
|
|
35
|
+
positioning: {
|
|
36
|
+
...POPOVER_POSITIONING,
|
|
37
|
+
getAnchorRect
|
|
38
|
+
},
|
|
39
|
+
autoFocus: false,
|
|
40
|
+
closeOnInteractOutside: false,
|
|
41
|
+
closeOnEscape: false,
|
|
42
|
+
modal: false,
|
|
43
|
+
lazyMount: true,
|
|
44
|
+
unmountOnExit: true,
|
|
45
|
+
children: /*#__PURE__*/ jsxs(PopoverContent, {
|
|
46
|
+
minWidth: "240px",
|
|
47
|
+
maxWidth: "340px",
|
|
48
|
+
className: "gap-6 pointer-events-none",
|
|
49
|
+
"data-testid": "field-menu-popover",
|
|
50
|
+
children: [
|
|
51
|
+
/*#__PURE__*/ jsx("p", {
|
|
52
|
+
className: "font-mono text-sm leading-sm text-text-primary",
|
|
53
|
+
children: title
|
|
54
|
+
}),
|
|
55
|
+
/*#__PURE__*/ jsx("p", {
|
|
56
|
+
className: "text-xs leading-xs text-text-secondary",
|
|
57
|
+
children: description
|
|
58
|
+
}),
|
|
59
|
+
example && /*#__PURE__*/ jsxs(Fragment, {
|
|
60
|
+
children: [
|
|
61
|
+
/*#__PURE__*/ jsx(Separator, {
|
|
62
|
+
spacing: 8
|
|
63
|
+
}),
|
|
64
|
+
/*#__PURE__*/ jsx("pre", {
|
|
65
|
+
className: "m-0 font-mono text-xs leading-xs text-text-secondary whitespace-pre-wrap",
|
|
66
|
+
children: example
|
|
67
|
+
})
|
|
68
|
+
]
|
|
69
|
+
})
|
|
70
|
+
]
|
|
71
|
+
})
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
FieldMenuPopover.displayName = 'FieldMenuPopover';
|
|
75
|
+
export { FieldMenuPopover };
|
package/dist/components/FilterInput/FilterInputMenu/FilterInputFieldMenu/FilterInputFieldMenu.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useMemo } from "react";
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useMemo, useRef } from "react";
|
|
3
3
|
import { cn } from "../../../../utils/cn.js";
|
|
4
4
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuFooter } from "../../../DropdownMenu/index.js";
|
|
5
5
|
import { Kbd } from "../../../Kbd/Kbd.js";
|
|
@@ -7,7 +7,9 @@ import { KbdGroup } from "../../../Kbd/KbdGroup.js";
|
|
|
7
7
|
import { buildFieldMenuSections } from "../../lib/index.js";
|
|
8
8
|
import { useFieldMenuNavItems } from "../hooks/useFieldMenuNavItems.js";
|
|
9
9
|
import { useKeyboardNav } from "../hooks/useKeyboardNav.js";
|
|
10
|
+
import { useMenuScrollHighlightSync } from "../hooks/useMenuScrollHighlightSync.js";
|
|
10
11
|
import { MenuEmptyState } from "../MenuEmptyState.js";
|
|
12
|
+
import { FieldMenuPopover } from "./FieldMenuPopover.js";
|
|
11
13
|
import { FieldSections, OperatorsSection, RecentSection, SuggestionsSection } from "./FieldMenuSections.js";
|
|
12
14
|
const FilterInputFieldMenu = ({ fields, filterText = '', onSelect, open = false, onOpenChange, recentConditions = [], suggestedFields = [], fieldGroups, onSelectAnd, onSelectOr, onEscape, positioning, inputRef, menuRef, className })=>{
|
|
13
15
|
const limitedRecentConditions = useMemo(()=>recentConditions.slice(0, 3), [
|
|
@@ -38,7 +40,7 @@ const FilterInputFieldMenu = ({ fields, filterText = '', onSelect, open = false,
|
|
|
38
40
|
} else if ('and' === data.type) onSelectAnd?.();
|
|
39
41
|
else if ('or' === data.type) onSelectOr?.();
|
|
40
42
|
};
|
|
41
|
-
const { highlightedValue, onHighlightChange, registerItem } = useKeyboardNav({
|
|
43
|
+
const { highlightedValue, onHighlightChange, registerItem, getItemElement } = useKeyboardNav({
|
|
42
44
|
items: flatItems,
|
|
43
45
|
open,
|
|
44
46
|
onSelect: handleItemSelect,
|
|
@@ -49,74 +51,108 @@ const FilterInputFieldMenu = ({ fields, filterText = '', onSelect, open = false,
|
|
|
49
51
|
menuRef
|
|
50
52
|
});
|
|
51
53
|
const hasResults = sections.length > 0 || !filterText;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
54
|
+
const highlightedField = useMemo(()=>{
|
|
55
|
+
if (!highlightedValue) return;
|
|
56
|
+
const data = flatItems.find((i)=>i.id === highlightedValue)?.value;
|
|
57
|
+
return data?.type === 'field' ? data.field : void 0;
|
|
58
|
+
}, [
|
|
59
|
+
flatItems,
|
|
60
|
+
highlightedValue
|
|
61
|
+
]);
|
|
62
|
+
const highlightedValueRef = useRef(highlightedValue);
|
|
63
|
+
highlightedValueRef.current = highlightedValue;
|
|
64
|
+
const getPopoverAnchorRect = useCallback(()=>{
|
|
65
|
+
const el = getItemElement(highlightedValueRef.current);
|
|
66
|
+
if (!el) return null;
|
|
67
|
+
const item = el.getBoundingClientRect();
|
|
68
|
+
const menu = el.closest('[data-filter-input-menu="true"]')?.getBoundingClientRect();
|
|
69
|
+
return menu ? new DOMRect(menu.x, item.y, menu.width, item.height) : item;
|
|
70
|
+
}, [
|
|
71
|
+
getItemElement
|
|
72
|
+
]);
|
|
73
|
+
const popoverOpen = open && hasResults && !!highlightedField?.description;
|
|
74
|
+
const menuVisible = open && hasResults;
|
|
75
|
+
useMenuScrollHighlightSync(menuVisible, onHighlightChange);
|
|
76
|
+
return /*#__PURE__*/ jsxs(Fragment, {
|
|
77
|
+
children: [
|
|
78
|
+
/*#__PURE__*/ jsx(DropdownMenu, {
|
|
79
|
+
open: open && hasResults,
|
|
80
|
+
onOpenChange: onOpenChange,
|
|
81
|
+
closeOnSelect: false,
|
|
82
|
+
positioning: positioning,
|
|
83
|
+
highlightedValue: highlightedValue,
|
|
84
|
+
onHighlightChange: onHighlightChange,
|
|
85
|
+
children: /*#__PURE__*/ jsxs(DropdownMenuContent, {
|
|
86
|
+
ref: menuRef,
|
|
87
|
+
className: cn('w-[300px] max-h-[430px]', className),
|
|
88
|
+
"data-slot": "filter-input-field-menu",
|
|
89
|
+
"data-filter-input-menu": "true",
|
|
88
90
|
children: [
|
|
89
|
-
/*#__PURE__*/
|
|
90
|
-
|
|
91
|
+
!filterText && showRecent && /*#__PURE__*/ jsx(RecentSection, {
|
|
92
|
+
conditions: limitedRecentConditions,
|
|
93
|
+
fields: fields,
|
|
94
|
+
onSelect: onSelect,
|
|
95
|
+
registerItem: registerItem
|
|
96
|
+
}),
|
|
97
|
+
!filterText && showSuggestions && !showRecent && /*#__PURE__*/ jsx(SuggestionsSection, {
|
|
98
|
+
fields: suggestedFields,
|
|
99
|
+
onSelect: onSelect,
|
|
100
|
+
registerItem: registerItem
|
|
101
|
+
}),
|
|
102
|
+
sections.length > 0 ? /*#__PURE__*/ jsx(FieldSections, {
|
|
103
|
+
sections: sections,
|
|
104
|
+
onSelect: onSelect,
|
|
105
|
+
registerItem: registerItem
|
|
106
|
+
}) : /*#__PURE__*/ jsx(MenuEmptyState, {}),
|
|
107
|
+
!filterText && (onSelectAnd || onSelectOr) && /*#__PURE__*/ jsx(OperatorsSection, {
|
|
108
|
+
onSelectAnd: onSelectAnd,
|
|
109
|
+
onSelectOr: onSelectOr,
|
|
110
|
+
registerItem: registerItem
|
|
111
|
+
}),
|
|
112
|
+
/*#__PURE__*/ jsxs(DropdownMenuFooter, {
|
|
113
|
+
className: "justify-start",
|
|
91
114
|
children: [
|
|
92
|
-
/*#__PURE__*/ jsxs(
|
|
115
|
+
/*#__PURE__*/ jsxs("span", {
|
|
116
|
+
className: "flex items-center gap-4",
|
|
93
117
|
children: [
|
|
94
|
-
/*#__PURE__*/
|
|
95
|
-
children:
|
|
118
|
+
/*#__PURE__*/ jsxs(KbdGroup, {
|
|
119
|
+
children: [
|
|
120
|
+
/*#__PURE__*/ jsx(Kbd, {
|
|
121
|
+
children: "↑"
|
|
122
|
+
}),
|
|
123
|
+
/*#__PURE__*/ jsx(Kbd, {
|
|
124
|
+
children: "↓"
|
|
125
|
+
})
|
|
126
|
+
]
|
|
96
127
|
}),
|
|
97
|
-
|
|
98
|
-
children: "↓"
|
|
99
|
-
})
|
|
128
|
+
"to navigate"
|
|
100
129
|
]
|
|
101
130
|
}),
|
|
102
|
-
"
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
})
|
|
113
|
-
"to select"
|
|
131
|
+
/*#__PURE__*/ jsxs("span", {
|
|
132
|
+
className: "flex items-center gap-4",
|
|
133
|
+
children: [
|
|
134
|
+
/*#__PURE__*/ jsx(KbdGroup, {
|
|
135
|
+
children: /*#__PURE__*/ jsx(Kbd, {
|
|
136
|
+
children: "↵"
|
|
137
|
+
})
|
|
138
|
+
}),
|
|
139
|
+
"to select"
|
|
140
|
+
]
|
|
141
|
+
})
|
|
114
142
|
]
|
|
115
143
|
})
|
|
116
144
|
]
|
|
117
145
|
})
|
|
118
|
-
|
|
119
|
-
|
|
146
|
+
}),
|
|
147
|
+
/*#__PURE__*/ jsx(FieldMenuPopover, {
|
|
148
|
+
open: popoverOpen,
|
|
149
|
+
title: highlightedField?.label ?? '',
|
|
150
|
+
description: highlightedField?.description ?? '',
|
|
151
|
+
example: highlightedField?.example,
|
|
152
|
+
getAnchorRect: getPopoverAnchorRect,
|
|
153
|
+
repositionKey: highlightedValue
|
|
154
|
+
})
|
|
155
|
+
]
|
|
120
156
|
});
|
|
121
157
|
};
|
|
122
158
|
FilterInputFieldMenu.displayName = 'FilterInputFieldMenu';
|
|
@@ -35,5 +35,6 @@ export declare const useKeyboardNav: ({ items, open, onSelect, onClose, onArrowR
|
|
|
35
35
|
}) => void;
|
|
36
36
|
pendingIds: Set<string>;
|
|
37
37
|
registerItem: (id: string) => (el: HTMLElement | null) => void;
|
|
38
|
+
getItemElement: (id: string) => HTMLElement | null;
|
|
38
39
|
};
|
|
39
40
|
export {};
|
|
@@ -7,6 +7,7 @@ const useKeyboardNav = ({ items, open, onSelect, onClose, onArrowRight, onArrowL
|
|
|
7
7
|
if (el) itemRegistryRef.current.set(id, el);
|
|
8
8
|
else itemRegistryRef.current.delete(id);
|
|
9
9
|
}, []);
|
|
10
|
+
const getItemElement = useCallback((id)=>itemRegistryRef.current.get(id) ?? null, []);
|
|
10
11
|
const activeIndexRef = useRef(-1);
|
|
11
12
|
const pendingIdsRef = useRef(pendingIds);
|
|
12
13
|
pendingIdsRef.current = pendingIds;
|
|
@@ -253,7 +254,8 @@ const useKeyboardNav = ({ items, open, onSelect, onClose, onArrowRight, onArrowL
|
|
|
253
254
|
highlightedValue,
|
|
254
255
|
onHighlightChange,
|
|
255
256
|
pendingIds,
|
|
256
|
-
registerItem
|
|
257
|
+
registerItem,
|
|
258
|
+
getItemElement
|
|
257
259
|
};
|
|
258
260
|
};
|
|
259
261
|
export { useKeyboardNav };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keeps the field-menu highlight — and the description popover that follows it —
|
|
3
|
+
* on the row under the cursor when the list scrolls beneath a stationary pointer
|
|
4
|
+
* (wheel/trackpad). Ark moves the highlight only on `pointermove`, so a scroll
|
|
5
|
+
* would otherwise strand it. On scroll we hit-test the last pointer and drive the
|
|
6
|
+
* highlight via `onHighlightChange` (a synthetic `pointermove` is ignored by zag
|
|
7
|
+
* when the position is unchanged). Guarded off during arrow-key nav so a resting
|
|
8
|
+
* pointer can't hijack the keyboard. Pass `enabled` for the whole time the menu
|
|
9
|
+
* is visible so the pointer is tracked before the first scroll (AS-1060).
|
|
10
|
+
*/
|
|
11
|
+
export declare const useMenuScrollHighlightSync: (enabled: boolean, onHighlightChange: (details: {
|
|
12
|
+
highlightedValue: string | null;
|
|
13
|
+
}) => void) => void;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
const useMenuScrollHighlightSync = (enabled, onHighlightChange)=>{
|
|
3
|
+
const lastPointerRef = useRef(null);
|
|
4
|
+
const keyboardNavRef = useRef(false);
|
|
5
|
+
useEffect(()=>{
|
|
6
|
+
if (!enabled) return;
|
|
7
|
+
const trackPointer = (e)=>{
|
|
8
|
+
keyboardNavRef.current = false;
|
|
9
|
+
lastPointerRef.current = {
|
|
10
|
+
x: e.clientX,
|
|
11
|
+
y: e.clientY
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
const trackKeyboard = (e)=>{
|
|
15
|
+
if ('ArrowDown' === e.key || 'ArrowUp' === e.key) keyboardNavRef.current = true;
|
|
16
|
+
};
|
|
17
|
+
let raf = 0;
|
|
18
|
+
const onScroll = ()=>{
|
|
19
|
+
if (raf || keyboardNavRef.current) return;
|
|
20
|
+
raf = requestAnimationFrame(()=>{
|
|
21
|
+
raf = 0;
|
|
22
|
+
const p = lastPointerRef.current;
|
|
23
|
+
if (!p) return;
|
|
24
|
+
const item = document.elementFromPoint(p.x, p.y)?.closest('[role="menuitem"]');
|
|
25
|
+
if (!item?.closest('[data-filter-input-menu="true"]')) return;
|
|
26
|
+
const value = item.getAttribute('data-value');
|
|
27
|
+
if (value) onHighlightChange({
|
|
28
|
+
highlightedValue: value
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
window.addEventListener('pointermove', trackPointer, true);
|
|
33
|
+
window.addEventListener('keydown', trackKeyboard, true);
|
|
34
|
+
window.addEventListener('scroll', onScroll, true);
|
|
35
|
+
return ()=>{
|
|
36
|
+
window.removeEventListener('pointermove', trackPointer, true);
|
|
37
|
+
window.removeEventListener('keydown', trackKeyboard, true);
|
|
38
|
+
window.removeEventListener('scroll', onScroll, true);
|
|
39
|
+
if (raf) cancelAnimationFrame(raf);
|
|
40
|
+
};
|
|
41
|
+
}, [
|
|
42
|
+
enabled,
|
|
43
|
+
onHighlightChange
|
|
44
|
+
]);
|
|
45
|
+
};
|
|
46
|
+
export { useMenuScrollHighlightSync };
|
|
@@ -36,6 +36,9 @@ const buildBaseChip = (i, condition, field)=>({
|
|
|
36
36
|
id: chipId(i),
|
|
37
37
|
variant: 'chip',
|
|
38
38
|
attribute: field?.label || condition.field,
|
|
39
|
+
...field?.description && {
|
|
40
|
+
attributeDescription: field.description
|
|
41
|
+
},
|
|
39
42
|
operator: condition.operator ? getOperatorLabel(condition.operator, field?.type || DEFAULT_FIELD_TYPE) : void 0,
|
|
40
43
|
...condition.disabled && {
|
|
41
44
|
disabled: true
|
|
@@ -22,6 +22,11 @@ export interface FilterInputChipData {
|
|
|
22
22
|
id: string;
|
|
23
23
|
variant: FilterInputChipVariant;
|
|
24
24
|
attribute?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Description shown as a dark tooltip on the chip's attribute segment; absent →
|
|
27
|
+
* no tooltip. Recall counterpart of the field-menu popover (AS-1060).
|
|
28
|
+
*/
|
|
29
|
+
attributeDescription?: string;
|
|
25
30
|
operator?: string;
|
|
26
31
|
value?: string;
|
|
27
32
|
error?: ChipErrorSegment;
|
|
@@ -106,6 +111,11 @@ export interface FieldMetadata {
|
|
|
106
111
|
label: string;
|
|
107
112
|
type: FieldType;
|
|
108
113
|
description?: string;
|
|
114
|
+
/**
|
|
115
|
+
* Monospace example block shown under the description in the field-menu popover,
|
|
116
|
+
* for non-obvious value formats (wildcards, ranges, IDs). Multi-line via `\n` (AS-1060).
|
|
117
|
+
*/
|
|
118
|
+
example?: string;
|
|
109
119
|
operators?: FilterOperator[];
|
|
110
120
|
default?: string | number | boolean;
|
|
111
121
|
values?: FieldValueOption[];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
3
|
-
"generatedAt": "2026-08-
|
|
2
|
+
"version": "1.0.0",
|
|
3
|
+
"generatedAt": "2026-08-20T14:13:55.149Z",
|
|
4
4
|
"components": [
|
|
5
5
|
{
|
|
6
6
|
"name": "Accordion",
|
|
@@ -30188,6 +30188,12 @@
|
|
|
30188
30188
|
"type": "string",
|
|
30189
30189
|
"required": true
|
|
30190
30190
|
},
|
|
30191
|
+
{
|
|
30192
|
+
"name": "attributeDescription",
|
|
30193
|
+
"type": "string | undefined",
|
|
30194
|
+
"required": false,
|
|
30195
|
+
"description": "Description shown in a dark tooltip on hover of the attribute segment. When\n absent, no tooltip renders. The operator/value segments are unaffected (AS-1060)."
|
|
30196
|
+
},
|
|
30191
30197
|
{
|
|
30192
30198
|
"name": "operator",
|
|
30193
30199
|
"type": "string | undefined",
|
package/package.json
CHANGED