@astryxdesign/core 0.7.7-canary.fc68123 → 0.7.7
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/CHANGELOG.md +104 -0
- package/dist/AlertDialog/AlertDialog.d.ts +1 -4
- package/dist/AlertDialog/AlertDialog.d.ts.map +1 -1
- package/dist/AlertDialog/AlertDialog.js +6 -36
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +4 -23
- package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
- package/dist/BottomSheet/useSheetGestures.js +1 -3
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +1 -17
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +2 -13
- package/dist/ComplexSelector/ComplexSelector.d.ts +1 -4
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +4 -14
- package/dist/ContextMenu/ContextMenu.js +2 -2
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +31 -7
- package/dist/Field/Field.js +2 -2
- package/dist/HoverCard/HoverCard.d.ts +3 -4
- package/dist/HoverCard/HoverCard.d.ts.map +1 -1
- package/dist/HoverCard/HoverCard.js +2 -27
- package/dist/HoverCard/useHoverCard.d.ts +4 -5
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +31 -17
- package/dist/Icon/Icon.d.ts +1 -5
- package/dist/Icon/Icon.d.ts.map +1 -1
- package/dist/Icon/Icon.js +2 -5
- package/dist/Layer/usePressPin.d.ts +5 -17
- package/dist/Layer/usePressPin.d.ts.map +1 -1
- package/dist/Layer/usePressPin.js +12 -62
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +0 -18
- package/dist/PowerSearch/PowerSearch.d.ts +3 -36
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +127 -433
- package/dist/PowerSearch/PowerSearchEditPopover.d.ts +1 -32
- package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchEditPopover.js +6 -17
- package/dist/PowerSearch/PowerSearchValueEditor.d.ts +3 -38
- package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchValueEditor.js +61 -170
- package/dist/PowerSearch/formatFilterValue.d.ts +0 -7
- package/dist/PowerSearch/formatFilterValue.d.ts.map +1 -1
- package/dist/PowerSearch/formatFilterValue.js +1 -11
- package/dist/RadioList/RadioList.d.ts.map +1 -1
- package/dist/RadioList/RadioList.js +2 -3
- package/dist/RadioList/RadioListItem.d.ts +1 -1
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +3 -5
- package/dist/Resizable/ResizeHandle.d.ts +1 -2
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +77 -99
- package/dist/SegmentedControl/useSegmentedSelectionMotion.d.ts.map +1 -1
- package/dist/SegmentedControl/useSegmentedSelectionMotion.js +3 -3
- package/dist/Slider/sliderTouchController.js +2 -2
- package/dist/Spinner/Spinner.d.ts +1 -1
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +58 -60
- package/dist/Switch/useSwitchTouch.js +4 -4
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +8 -29
- package/dist/TextInput/TextInput.d.ts +2 -2
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +7 -10
- package/dist/Toast/Toast.d.ts +1 -11
- package/dist/Toast/Toast.d.ts.map +1 -1
- package/dist/Toast/Toast.js +15 -25
- package/dist/Toast/ToastViewport.d.ts.map +1 -1
- package/dist/Toast/ToastViewport.js +34 -94
- package/dist/Toast/types.d.ts +3 -9
- package/dist/Toast/types.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.d.ts +1 -19
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +53 -34
- package/dist/Typeahead/TypeaheadItem.d.ts +1 -2
- package/dist/Typeahead/TypeaheadItem.d.ts.map +1 -1
- package/dist/astryx.css +6 -35
- package/dist/gestures/PressTracker.d.ts.map +1 -1
- package/dist/gestures/PressTracker.js +3 -4
- package/dist/hooks/actionGroupTracker.d.ts +1 -4
- package/dist/hooks/actionGroupTracker.d.ts.map +1 -1
- package/dist/hooks/actionGroupTracker.js +3 -13
- package/dist/hooks/index.d.ts +0 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +0 -1
- package/dist/hooks/useAttachment.d.ts +1 -1
- package/dist/hooks/useAttachment.js +1 -1
- package/dist/hooks/useInputStatusIcon.d.ts +1 -1
- package/dist/hooks/useInputStatusIcon.d.ts.map +1 -1
- package/dist/hooks/useInputStatusIcon.js +2 -2
- package/dist/hooks/useLongPress.d.ts +2 -6
- package/dist/hooks/useLongPress.d.ts.map +1 -1
- package/dist/hooks/useLongPress.js +15 -95
- package/dist/i18n/generated-english/PowerSearch/PowerSearchValueEditor.generated.d.ts.map +1 -1
- package/dist/i18n/generated-english/PowerSearch/PowerSearchValueEditor.generated.js +0 -6
- package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/en.generated.js +0 -30
- package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pseudo.generated.js +0 -30
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -4
- package/dist/motion/animatedLayout.d.ts.map +1 -1
- package/dist/motion/animatedLayout.js +6 -5
- package/dist/motion/animator.d.ts +14 -14
- package/dist/motion/animator.d.ts.map +1 -1
- package/dist/motion/animator.js +18 -41
- package/dist/utils/dateParser.d.ts.map +1 -1
- package/dist/utils/dateParser.js +0 -12
- package/dist/utils/focusReturn.d.ts +0 -15
- package/dist/utils/focusReturn.d.ts.map +1 -1
- package/dist/utils/focusReturn.js +0 -28
- package/dist/utils/interactionOverlay.stylex.d.ts +2 -3
- package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
- package/dist/utils/interactionOverlay.stylex.js +2 -3
- package/dist/utils/pressFeedback.d.ts.map +1 -1
- package/dist/utils/pressFeedback.js +6 -19
- package/locales/en.json +0 -120
- package/locales/pseudo.json +0 -90
- package/package.json +5 -10
- package/src/AlertDialog/AlertDialog.doc.mjs +2 -2
- package/src/AlertDialog/AlertDialog.tsx +5 -37
- package/src/BottomSheet/BottomSheetPanel.tsx +3 -37
- package/src/BottomSheet/useSheetGestures.ts +1 -3
- package/src/Button/Button.tsx +4 -26
- package/src/CheckboxInput/CheckboxInput.doc.mjs +0 -18
- package/src/CheckboxInput/CheckboxInput.tsx +9 -36
- package/src/ComplexSelector/ComplexSelector.tsx +5 -20
- package/src/ContextMenu/ContextMenu.tsx +2 -2
- package/src/Dialog/Dialog.tsx +31 -10
- package/src/Field/Field.tsx +2 -2
- package/src/HoverCard/HoverCard.doc.mjs +2 -4
- package/src/HoverCard/HoverCard.tsx +4 -35
- package/src/HoverCard/useHoverCard.doc.mjs +36 -178
- package/src/HoverCard/useHoverCard.tsx +52 -46
- package/src/Icon/Icon.doc.mjs +8 -49
- package/src/Icon/Icon.tsx +7 -15
- package/src/Layer/usePressPin.ts +15 -81
- package/src/Lightbox/Lightbox.tsx +0 -18
- package/src/PowerSearch/PowerSearch.doc.mjs +5 -54
- package/src/PowerSearch/PowerSearch.tsx +155 -609
- package/src/PowerSearch/PowerSearchEditPopover.tsx +6 -28
- package/src/PowerSearch/PowerSearchValueEditor.tsx +38 -203
- package/src/PowerSearch/formatFilterValue.ts +1 -18
- package/src/RadioList/RadioList.tsx +2 -3
- package/src/RadioList/RadioListItem.tsx +3 -5
- package/src/Resizable/Resizable.doc.mjs +4 -5
- package/src/Resizable/ResizeHandle.tsx +109 -167
- package/src/SegmentedControl/useSegmentedSelectionMotion.ts +3 -6
- package/src/Slider/sliderTouchController.ts +2 -2
- package/src/Spinner/Spinner.tsx +63 -77
- package/src/Switch/useSwitchTouch.ts +4 -4
- package/src/Table/BaseTable.tsx +12 -46
- package/src/TextInput/TextInput.tsx +10 -11
- package/src/Toast/Toast.doc.mjs +3 -4
- package/src/Toast/Toast.tsx +32 -52
- package/src/Toast/ToastViewport.tsx +35 -121
- package/src/Toast/types.ts +3 -9
- package/src/Tooltip/useTooltip.doc.mjs +0 -24
- package/src/Tooltip/useTooltip.tsx +56 -61
- package/src/Typeahead/TypeaheadItem.doc.mjs +3 -6
- package/src/Typeahead/TypeaheadItem.tsx +1 -2
- package/src/gestures/PressTracker.ts +3 -4
- package/src/gestures/README.md +1 -7
- package/src/hooks/actionGroupTracker.ts +5 -20
- package/src/hooks/index.ts +0 -2
- package/src/hooks/useAttachment.ts +1 -1
- package/src/hooks/useInputStatusIcon.tsx +2 -3
- package/src/hooks/useLongPress.doc.mjs +2 -2
- package/src/hooks/useLongPress.ts +15 -136
- package/src/i18n/generated-english/PowerSearch/PowerSearchValueEditor.generated.ts +0 -6
- package/src/i18n/generated-locales/en.generated.ts +0 -41
- package/src/i18n/generated-locales/pseudo.generated.ts +0 -42
- package/src/index.ts +0 -13
- package/src/motion/README.md +5 -9
- package/src/motion/animatedLayout.ts +4 -5
- package/src/motion/animator.ts +29 -61
- package/src/utils/dateParser.ts +0 -16
- package/src/utils/focusReturn.ts +0 -30
- package/src/utils/interactionOverlay.stylex.ts +2 -3
- package/src/utils/pressFeedback.ts +6 -19
- package/dist/PowerSearch/PowerSearchBottomSheet.d.ts +0 -100
- package/dist/PowerSearch/PowerSearchBottomSheet.d.ts.map +0 -1
- package/dist/PowerSearch/PowerSearchBottomSheet.js +0 -1913
- package/dist/PowerSearch/formatTokenValue.d.ts +0 -28
- package/dist/PowerSearch/formatTokenValue.d.ts.map +0 -1
- package/dist/PowerSearch/formatTokenValue.js +0 -160
- package/dist/PowerSearch/powerSearchSheetUtils.d.ts +0 -65
- package/dist/PowerSearch/powerSearchSheetUtils.d.ts.map +0 -1
- package/dist/PowerSearch/powerSearchSheetUtils.js +0 -168
- package/dist/animator/createAnimator.d.ts +0 -98
- package/dist/animator/createAnimator.d.ts.map +0 -1
- package/dist/animator/createAnimator.js +0 -259
- package/dist/hooks/pressGroupContext.d.ts +0 -6
- package/dist/hooks/pressGroupContext.d.ts.map +0 -1
- package/dist/hooks/pressGroupContext.js +0 -20
- package/dist/hooks/useActionGroupTracking.d.ts +0 -53
- package/dist/hooks/useActionGroupTracking.d.ts.map +0 -1
- package/dist/hooks/useActionGroupTracking.js +0 -174
- package/dist/hooks/useAnimator.d.ts +0 -41
- package/dist/hooks/useAnimator.d.ts.map +0 -1
- package/dist/hooks/useAnimator.js +0 -70
- package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.d.ts +0 -8
- package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.d.ts.map +0 -1
- package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.js +0 -42
- package/dist/i18n/generated-english/PowerSearch/formatTokenValue.generated.d.ts +0 -8
- package/dist/i18n/generated-english/PowerSearch/formatTokenValue.generated.d.ts.map +0 -1
- package/dist/i18n/generated-english/PowerSearch/formatTokenValue.generated.js +0 -14
- package/dist/internal/indicatorPress.stylex.d.ts +0 -20
- package/dist/internal/indicatorPress.stylex.d.ts.map +0 -1
- package/dist/internal/indicatorPress.stylex.js +0 -26
- package/dist/motion/progress.d.ts +0 -88
- package/dist/motion/progress.d.ts.map +0 -1
- package/dist/motion/progress.js +0 -128
- package/dist/motion/uikit/alert.d.ts +0 -41
- package/dist/motion/uikit/alert.d.ts.map +0 -1
- package/dist/motion/uikit/alert.js +0 -43
- package/dist/privateAttributes/pressGroupItem.stylex.d.ts +0 -15
- package/dist/privateAttributes/pressGroupItem.stylex.d.ts.map +0 -1
- package/dist/privateAttributes/pressGroupItem.stylex.js +0 -23
- package/dist/utils/triggerFocus.d.ts +0 -36
- package/dist/utils/triggerFocus.d.ts.map +0 -1
- package/dist/utils/triggerFocus.js +0 -65
- package/src/PowerSearch/PowerSearchBottomSheet.tsx +0 -2515
- package/src/PowerSearch/formatTokenValue.ts +0 -215
- package/src/PowerSearch/powerSearchSheetUtils.ts +0 -224
- package/src/animator/createAnimator.doc.mjs +0 -153
- package/src/animator/createAnimator.ts +0 -324
- package/src/hooks/pressGroupContext.ts +0 -21
- package/src/hooks/useActionGroupTracking.ts +0 -224
- package/src/hooks/useAnimator.doc.mjs +0 -109
- package/src/hooks/useAnimator.ts +0 -67
- package/src/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.ts +0 -43
- package/src/i18n/generated-english/PowerSearch/formatTokenValue.generated.ts +0 -15
- package/src/internal/indicatorPress.stylex.ts +0 -24
- package/src/motion/progress.ts +0 -155
- package/src/motion/uikit/alert.ts +0 -43
- package/src/privateAttributes/pressGroupItem.stylex.ts +0 -23
- package/src/utils/triggerFocus.ts +0 -80
|
@@ -4,25 +4,19 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file PowerSearch.tsx
|
|
7
|
-
* @input PowerSearchConfig, filters, onChange
|
|
8
|
-
* useAdaptivePresentation, and PowerSearchBottomSheet (lazy)
|
|
7
|
+
* @input PowerSearchConfig, filters, onChange
|
|
9
8
|
* @output Structured filter bar with token-based filter management
|
|
10
9
|
* @position Main component; forwards DOM ref and exposes tokenizer control via
|
|
11
|
-
* handleRef
|
|
12
|
-
* field and editor popover for one trigger that opens PowerSearchBottomSheet.
|
|
10
|
+
* handleRef
|
|
13
11
|
*
|
|
14
12
|
* SYNC: When modified, update:
|
|
15
13
|
* - /packages/core/src/PowerSearch/index.ts
|
|
16
|
-
* - /packages/core/src/PowerSearch/PowerSearchBottomSheet.tsx (sheet presentation)
|
|
17
14
|
* - /packages/cli/assets/templates/blocks/components/PowerSearch/ (showcase blocks)
|
|
18
15
|
*/
|
|
19
16
|
|
|
20
17
|
import React, {
|
|
21
|
-
lazy,
|
|
22
|
-
Suspense,
|
|
23
18
|
useCallback,
|
|
24
19
|
useEffect,
|
|
25
|
-
useId,
|
|
26
20
|
useImperativeHandle,
|
|
27
21
|
useMemo,
|
|
28
22
|
useRef,
|
|
@@ -43,45 +37,30 @@ import {Token} from '../Token';
|
|
|
43
37
|
import {TokenNamingContext} from '../Token/TokenNamingContext';
|
|
44
38
|
import {Avatar} from '../Avatar';
|
|
45
39
|
import {layerAnimations} from '../Layer/layerAnimations.stylex';
|
|
46
|
-
import {Icon
|
|
40
|
+
import {Icon} from '../Icon';
|
|
47
41
|
import type {IconType} from '../Icon';
|
|
48
42
|
import type {IconName} from '../Icon/globalIconRegistry';
|
|
49
43
|
import type {InputStatus} from '../Field';
|
|
50
|
-
import {Field} from '../Field/Field';
|
|
51
|
-
import {
|
|
52
|
-
inputStatusBorderStyles,
|
|
53
|
-
inputStatusFocusWithinStyles,
|
|
54
|
-
inputStatusHoverShadowStyles,
|
|
55
|
-
inputWrapperStyles,
|
|
56
|
-
} from '../Field/inputStyles.stylex';
|
|
57
44
|
import {usePopover} from '../Popover/usePopover';
|
|
58
|
-
import {useTooltip} from '../Tooltip/useTooltip';
|
|
59
45
|
import {layerViewportInset} from '../Layer/layerViewportInset.stylex';
|
|
60
46
|
import {useAnnounce} from '../hooks/useAnnounce';
|
|
61
|
-
import {useAdaptivePresentation} from '../hooks/useAdaptivePresentation';
|
|
62
|
-
import {useDevWarning} from '../hooks/useDevWarning';
|
|
63
|
-
import {useIsHydrating} from '../hooks/useIsHydrating';
|
|
64
47
|
import {
|
|
65
48
|
spacingVars,
|
|
66
49
|
colorVars,
|
|
67
|
-
sizeVars,
|
|
68
50
|
typeScaleVars,
|
|
69
|
-
typographyVars,
|
|
70
51
|
fontWeightVars,
|
|
71
52
|
} from '../theme/tokens.stylex';
|
|
72
53
|
import {useSize} from '../SizeContext/SizeContext';
|
|
73
|
-
import {isFocusDetached} from '../utils/focusReturn';
|
|
74
|
-
import {isRenderable} from '../utils/isRenderable';
|
|
75
54
|
import {useInternalConfig} from './useInternalConfig';
|
|
76
55
|
import {usePowerSearchSource} from './usePowerSearchSource';
|
|
77
|
-
import {formatFilterValue} from './formatFilterValue';
|
|
78
|
-
import {NestedEditor, PowerSearchEditPopover} from './PowerSearchEditPopover';
|
|
79
56
|
import {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
} from './
|
|
57
|
+
formatFilterValue,
|
|
58
|
+
formatDateAbsoluteCompact,
|
|
59
|
+
} from './formatFilterValue';
|
|
60
|
+
import {PowerSearchEditPopover} from './PowerSearchEditPopover';
|
|
83
61
|
import {resolveOperatorLabel} from './resolveOperatorLabel';
|
|
84
62
|
import {themeProps} from '../utils/themeProps';
|
|
63
|
+
import {truncateCharacters} from '../utils/characters';
|
|
85
64
|
import {useLocale} from '../i18n/useLocale';
|
|
86
65
|
import type {Locale} from '../i18n/types';
|
|
87
66
|
import type {SearchableItem} from '../Typeahead/types';
|
|
@@ -95,14 +74,10 @@ import type {
|
|
|
95
74
|
PowerSearchHandle,
|
|
96
75
|
FilterValue,
|
|
97
76
|
OperatorValue,
|
|
77
|
+
EnumItem,
|
|
98
78
|
PowerSearchComponents,
|
|
99
79
|
PowerSearchOperator,
|
|
100
80
|
} from './types';
|
|
101
|
-
import type {
|
|
102
|
-
PowerSearchBottomSheetProps,
|
|
103
|
-
PowerSearchSheetEditors,
|
|
104
|
-
} from './PowerSearchBottomSheet';
|
|
105
|
-
import {formatTokenValueText} from './formatTokenValue';
|
|
106
81
|
|
|
107
82
|
import {useMergedRefs} from '../hooks/useMergedRefs';
|
|
108
83
|
import type {FieldStatusPlacement} from '../Field/fieldStatusPlacement';
|
|
@@ -175,127 +150,130 @@ const resultCountStyles = stylex.create({
|
|
|
175
150
|
},
|
|
176
151
|
});
|
|
177
152
|
|
|
178
|
-
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
md: {minHeight: sizeVars['--size-element-md']},
|
|
184
|
-
lg: {minHeight: sizeVars['--size-element-lg']},
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
const sheetTriggerStyles = stylex.create({
|
|
188
|
-
wrapper: {
|
|
189
|
-
position: 'relative',
|
|
190
|
-
flexWrap: 'nowrap',
|
|
191
|
-
gap: spacingVars['--spacing-1'],
|
|
192
|
-
height: 'auto',
|
|
193
|
-
cursor: {
|
|
194
|
-
default: 'pointer',
|
|
195
|
-
':is([aria-disabled="true"])': 'default',
|
|
196
|
-
},
|
|
197
|
-
},
|
|
198
|
-
wrapperWithTokens: {
|
|
199
|
-
paddingBlock: `calc(${spacingVars['--spacing-1']} - 1px)`,
|
|
200
|
-
paddingInline: `calc(${spacingVars['--spacing-1']} - 1px)`,
|
|
201
|
-
},
|
|
202
|
-
contentSection: {
|
|
203
|
-
position: 'relative',
|
|
204
|
-
minWidth: 0,
|
|
205
|
-
flexGrow: 1,
|
|
206
|
-
display: 'flex',
|
|
207
|
-
alignItems: 'center',
|
|
208
|
-
flexWrap: 'wrap',
|
|
209
|
-
columnGap: spacingVars['--spacing-1'],
|
|
210
|
-
rowGap: `calc(${spacingVars['--spacing-1']} - 1px)`,
|
|
211
|
-
},
|
|
212
|
-
startIconWithTokens: {
|
|
213
|
-
marginInlineStart: `calc(${spacingVars['--spacing-2']} - ${spacingVars['--spacing-1']} + 1px)`,
|
|
214
|
-
},
|
|
215
|
-
// The only tab stop in the field. It stays visually borderless while the
|
|
216
|
-
// wrapper delegates taps on tokens and empty space to it.
|
|
217
|
-
trigger: {
|
|
218
|
-
appearance: 'none',
|
|
219
|
-
flex: '1 1 40px',
|
|
220
|
-
minWidth: '40px',
|
|
221
|
-
alignSelf: 'stretch',
|
|
222
|
-
display: 'flex',
|
|
223
|
-
alignItems: 'center',
|
|
224
|
-
paddingBlock: 0,
|
|
225
|
-
paddingInline: spacingVars['--spacing-1'],
|
|
226
|
-
margin: 0,
|
|
227
|
-
borderWidth: 0,
|
|
228
|
-
borderStyle: 'none',
|
|
229
|
-
backgroundColor: 'transparent',
|
|
230
|
-
color: colorVars['--color-text-secondary'],
|
|
231
|
-
fontFamily: typographyVars['--font-family-body'],
|
|
232
|
-
fontSize: typeScaleVars['--text-body-size'],
|
|
233
|
-
lineHeight: typeScaleVars['--text-body-leading'],
|
|
234
|
-
textAlign: 'start',
|
|
235
|
-
whiteSpace: 'nowrap',
|
|
236
|
-
cursor: {
|
|
237
|
-
default: 'pointer',
|
|
238
|
-
':is([aria-disabled="true"])': 'default',
|
|
239
|
-
},
|
|
240
|
-
outline: 'none',
|
|
241
|
-
},
|
|
242
|
-
// Once a token is present the empty trigger covers the token lane instead
|
|
243
|
-
// of reserving 40px or wrapping onto an otherwise blank row.
|
|
244
|
-
triggerWithTokens: {
|
|
245
|
-
position: 'absolute',
|
|
246
|
-
inset: 0,
|
|
247
|
-
minWidth: 0,
|
|
248
|
-
padding: 0,
|
|
249
|
-
},
|
|
250
|
-
endSection: {
|
|
251
|
-
display: 'flex',
|
|
252
|
-
alignItems: 'center',
|
|
253
|
-
gap: spacingVars['--spacing-2'],
|
|
254
|
-
flexShrink: 0,
|
|
255
|
-
paddingInlineEnd: spacingVars['--spacing-1'],
|
|
256
|
-
},
|
|
257
|
-
});
|
|
153
|
+
function truncateString(value: string, limit: number): string {
|
|
154
|
+
// Same semantics as before — strings within limit + 3 pass through, longer
|
|
155
|
+
// ones cut to limit + '...' — but counted in characters, not code units.
|
|
156
|
+
return truncateCharacters(value, limit + 3, '...');
|
|
157
|
+
}
|
|
258
158
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
// One import promise, shared by the preload and the lazy element: the sheet
|
|
263
|
-
// presentation loads the flow's chunk as soon as it is active, so the first
|
|
264
|
-
// press does not wait for the network, while nothing of the flow is rendered
|
|
265
|
-
// until a press.
|
|
266
|
-
const loadPowerSearchBottomSheet = async () =>
|
|
267
|
-
import('./PowerSearchBottomSheet');
|
|
268
|
-
const LazyPowerSearchBottomSheet = lazy(async () =>
|
|
269
|
-
loadPowerSearchBottomSheet().then(module => ({
|
|
270
|
-
default: module.PowerSearchBottomSheet,
|
|
271
|
-
})),
|
|
272
|
-
);
|
|
273
|
-
// The value step reuses the popover presentation's editors, which this
|
|
274
|
-
// module already loads; see PowerSearchSheetEditors.
|
|
275
|
-
const SHEET_EDITORS: PowerSearchSheetEditors = {
|
|
276
|
-
NestedEditor,
|
|
277
|
-
PowerSearchValueEditor,
|
|
278
|
-
buildRelativeDateOptions,
|
|
279
|
-
};
|
|
159
|
+
function getEnumLabel(values: ReadonlyArray<EnumItem>, value: string): string {
|
|
160
|
+
return values.find(v => v.value === value)?.label ?? value;
|
|
161
|
+
}
|
|
280
162
|
|
|
281
163
|
/**
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
* would leave server markup incomplete and abort hydration. The closed flow
|
|
285
|
-
* has no visible markup, so it first renders right after hydration.
|
|
164
|
+
* The visible text of a filter token's value. The token renders it, and the
|
|
165
|
+
* token's remove button and editor dialog are named by the same text.
|
|
286
166
|
*/
|
|
287
|
-
function
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
167
|
+
function tokenValueText(
|
|
168
|
+
operatorValue: OperatorValue,
|
|
169
|
+
filterValue: FilterValue,
|
|
170
|
+
maxLength: number,
|
|
171
|
+
locale: Locale,
|
|
172
|
+
): string | null {
|
|
173
|
+
switch (filterValue.type) {
|
|
174
|
+
case 'empty':
|
|
175
|
+
return null;
|
|
176
|
+
|
|
177
|
+
case 'string':
|
|
178
|
+
return truncateString(filterValue.value, maxLength);
|
|
179
|
+
|
|
180
|
+
case 'integer':
|
|
181
|
+
case 'float':
|
|
182
|
+
return String(filterValue.value);
|
|
183
|
+
|
|
184
|
+
case 'enum':
|
|
185
|
+
if (operatorValue.type === 'enum') {
|
|
186
|
+
return truncateString(
|
|
187
|
+
getEnumLabel(operatorValue.values, filterValue.value),
|
|
188
|
+
maxLength,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
return truncateString(filterValue.value, maxLength);
|
|
192
|
+
|
|
193
|
+
case 'string_list': {
|
|
194
|
+
const items = filterValue.value;
|
|
195
|
+
if (items.length === 0) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
if (items.length === 1) {
|
|
199
|
+
return truncateString(items[0], maxLength);
|
|
200
|
+
}
|
|
201
|
+
const totalLength = items.reduce((sum, s) => sum + s.length, 0);
|
|
202
|
+
if (totalLength > maxLength) {
|
|
203
|
+
return `${items.length} items`;
|
|
204
|
+
}
|
|
205
|
+
return items.join(', ');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
case 'enum_list': {
|
|
209
|
+
const items = filterValue.value;
|
|
210
|
+
if (items.length === 0) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
if (operatorValue.type === 'enum_list') {
|
|
214
|
+
const labels = items.map(v => getEnumLabel(operatorValue.values, v));
|
|
215
|
+
if (labels.length === 1) {
|
|
216
|
+
return truncateString(labels[0], maxLength);
|
|
217
|
+
}
|
|
218
|
+
const totalLength = labels.reduce((sum, s) => sum + s.length, 0);
|
|
219
|
+
if (totalLength > maxLength) {
|
|
220
|
+
return `${labels.length} items`;
|
|
221
|
+
}
|
|
222
|
+
return labels.join(', ');
|
|
223
|
+
}
|
|
224
|
+
if (items.length === 1) {
|
|
225
|
+
return truncateString(items[0], maxLength);
|
|
226
|
+
}
|
|
227
|
+
return `${items.length} items`;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
case 'entity_list': {
|
|
231
|
+
const entities = filterValue.value;
|
|
232
|
+
if (entities.length === 0) {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
if (entities.length === 1) {
|
|
236
|
+
return truncateString(entities[0].label, maxLength);
|
|
237
|
+
}
|
|
238
|
+
const totalLength = entities.reduce((sum, e) => sum + e.label.length, 0);
|
|
239
|
+
if (totalLength > maxLength) {
|
|
240
|
+
return `${entities.length} items`;
|
|
241
|
+
}
|
|
242
|
+
return entities.map(e => e.label).join(', ');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
case 'time':
|
|
246
|
+
return filterValue.value;
|
|
247
|
+
|
|
248
|
+
case 'date_absolute':
|
|
249
|
+
return truncateString(
|
|
250
|
+
formatDateAbsoluteCompact(filterValue.unixSeconds, locale),
|
|
251
|
+
maxLength,
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
case 'date_relative':
|
|
255
|
+
return truncateString(filterValue.value, maxLength);
|
|
256
|
+
|
|
257
|
+
case 'date_range':
|
|
258
|
+
return 'date range';
|
|
259
|
+
|
|
260
|
+
case 'custom':
|
|
261
|
+
if (operatorValue.type === 'custom') {
|
|
262
|
+
return truncateString(
|
|
263
|
+
operatorValue.getString(filterValue.value),
|
|
264
|
+
maxLength,
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
return filterValue.value;
|
|
268
|
+
|
|
269
|
+
case 'nested': {
|
|
270
|
+
const count = filterValue.value.length;
|
|
271
|
+
return count === 1 ? '1 filter' : `${count} filters`;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
default:
|
|
275
|
+
return null;
|
|
293
276
|
}
|
|
294
|
-
return (
|
|
295
|
-
<Suspense fallback={null}>
|
|
296
|
-
<LazyPowerSearchBottomSheet {...props} editors={SHEET_EDITORS} />
|
|
297
|
-
</Suspense>
|
|
298
|
-
);
|
|
299
277
|
}
|
|
300
278
|
|
|
301
279
|
function PowerSearchTokenValue({text}: {text: string}) {
|
|
@@ -410,37 +388,6 @@ export interface PowerSearchProps extends Omit<
|
|
|
410
388
|
* Keys are operator value types (e.g. 'string', 'enum', 'date_absolute').
|
|
411
389
|
*/
|
|
412
390
|
components?: PowerSearchComponents;
|
|
413
|
-
|
|
414
|
-
/**
|
|
415
|
-
* Experimental options, outside the stable compatibility promise: anything
|
|
416
|
-
* in this object may change or be removed in a patch release. Omit it for
|
|
417
|
-
* the stable behavior.
|
|
418
|
-
* @experimental
|
|
419
|
-
*/
|
|
420
|
-
experimental?: {
|
|
421
|
-
/**
|
|
422
|
-
* How the field presents filter editing.
|
|
423
|
-
* - `'popover'`: the tokenizer field with its typeahead and the editor
|
|
424
|
-
* popover — the stable behavior.
|
|
425
|
-
* - `'bottom-sheet'`: the whole field is one trigger showing display-only
|
|
426
|
-
* filter tokens; it opens a stack of sheets. Filters lists the filters
|
|
427
|
-
* as rows that edit or remove one, with Add filter, Done, and Clear
|
|
428
|
-
* all, which asks first and keeps read-only filters. Add filter opens
|
|
429
|
-
* above it with the fields (or the content search with inline
|
|
430
|
-
* suggestions), and a value step opens above that, stages operator →
|
|
431
|
-
* value, and confirms from its footer. Dismissing a step reveals the
|
|
432
|
-
* one beneath. Suited to compact touch screens, where the editor
|
|
433
|
-
* popover competes with the software keyboard.
|
|
434
|
-
* - `'adaptive'`: the bottom sheet on compact coarse-pointer screens,
|
|
435
|
-
* otherwise the popover — the same resolution Selector uses.
|
|
436
|
-
*
|
|
437
|
-
* In the sheet presentation `menuWidth` and `tokenOverflowBehavior` have
|
|
438
|
-
* no surface to act on and warn in development.
|
|
439
|
-
* @experimental
|
|
440
|
-
* @default 'popover'
|
|
441
|
-
*/
|
|
442
|
-
presentation?: 'popover' | 'bottom-sheet' | 'adaptive';
|
|
443
|
-
};
|
|
444
391
|
}
|
|
445
392
|
|
|
446
393
|
// =============================================================================
|
|
@@ -577,7 +524,6 @@ export function PowerSearch({
|
|
|
577
524
|
className,
|
|
578
525
|
style,
|
|
579
526
|
components: componentOverrides,
|
|
580
|
-
experimental,
|
|
581
527
|
}: PowerSearchProps) {
|
|
582
528
|
const size = useSize(sizeProp, 'md');
|
|
583
529
|
const config = useInternalConfig(configProp);
|
|
@@ -591,105 +537,6 @@ export function PowerSearch({
|
|
|
591
537
|
popoverSaveButtonLabelFromProps ?? t('@astryx.powersearch.editor.apply');
|
|
592
538
|
const tokenizerRef = useRef<TokenizerHandle>(null);
|
|
593
539
|
|
|
594
|
-
// Experimental presentation. `adaptive`
|
|
595
|
-
// resolves through the policy the selection controls share; an open flow
|
|
596
|
-
// keeps its presentation until it closes, so a resolution change mid-task
|
|
597
|
-
// cannot pull the surface out from under the person.
|
|
598
|
-
const presentation = experimental?.presentation ?? 'popover';
|
|
599
|
-
const resolvedPresentation = useAdaptivePresentation(presentation);
|
|
600
|
-
const [isSheetOpen, setIsSheetOpen] = useState(false);
|
|
601
|
-
// Latched for the flow's lifetime: the flow stays presented after it closes
|
|
602
|
-
// until the sheets have run their exit and handed focus back to the trigger.
|
|
603
|
-
// Without the latch, `adaptive` resolving to `popover` mid-session (a phone
|
|
604
|
-
// turned to landscape) would unmount the sheets and the trigger they return
|
|
605
|
-
// focus to in the same render, dropping focus to the page body.
|
|
606
|
-
const [isSheetPresented, setIsSheetPresented] = useState(false);
|
|
607
|
-
const isSheetPresentation =
|
|
608
|
-
isSheetOpen || isSheetPresented || resolvedPresentation === 'bottom-sheet';
|
|
609
|
-
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
610
|
-
// `hasAutoFocus` applies only to the control the field first mounts. A
|
|
611
|
-
// presentation swap remounts the control (combobox input or trigger), and
|
|
612
|
-
// React's `autoFocus` on the new one would pull focus from wherever on the
|
|
613
|
-
// page it was; the roots' callback refs hand over the focus the field held
|
|
614
|
-
// instead. A field that never swaps keeps `autoFocus` on every render, so a
|
|
615
|
-
// Dialog that keeps it mounted still finds its `data-autofocus` on open.
|
|
616
|
-
const [autoFocusState, setAutoFocusState] = useState({
|
|
617
|
-
presentation: isSheetPresentation,
|
|
618
|
-
isFirstControl: true,
|
|
619
|
-
});
|
|
620
|
-
if (autoFocusState.presentation !== isSheetPresentation) {
|
|
621
|
-
setAutoFocusState({
|
|
622
|
-
presentation: isSheetPresentation,
|
|
623
|
-
isFirstControl: false,
|
|
624
|
-
});
|
|
625
|
-
}
|
|
626
|
-
const autoFocusOnMount =
|
|
627
|
-
hasAutoFocus &&
|
|
628
|
-
autoFocusState.isFirstControl &&
|
|
629
|
-
autoFocusState.presentation === isSheetPresentation;
|
|
630
|
-
// A focus the swap moves continues the same visit to the field: the
|
|
631
|
-
// control that held focus is gone (no blur fires for a removed element), so
|
|
632
|
-
// the caller is told of neither a blur nor a new focus.
|
|
633
|
-
const isSwapFocusRef = useRef(false);
|
|
634
|
-
const focusAfterSwap = useCallback((control: {focus(): void} | null) => {
|
|
635
|
-
if (control == null) {
|
|
636
|
-
return;
|
|
637
|
-
}
|
|
638
|
-
isSwapFocusRef.current = true;
|
|
639
|
-
try {
|
|
640
|
-
control.focus();
|
|
641
|
-
} finally {
|
|
642
|
-
isSwapFocusRef.current = false;
|
|
643
|
-
}
|
|
644
|
-
}, []);
|
|
645
|
-
const handleFieldFocus = useCallback(
|
|
646
|
-
(event: React.FocusEvent) => {
|
|
647
|
-
if (!isSwapFocusRef.current) {
|
|
648
|
-
onFocus?.(event);
|
|
649
|
-
}
|
|
650
|
-
},
|
|
651
|
-
[onFocus],
|
|
652
|
-
);
|
|
653
|
-
const handleFieldBlur = useCallback(
|
|
654
|
-
(event: React.FocusEvent) => {
|
|
655
|
-
onBlur?.(event);
|
|
656
|
-
},
|
|
657
|
-
[onBlur],
|
|
658
|
-
);
|
|
659
|
-
// While the flow is open, the sheet announces its own content from inside
|
|
660
|
-
// its dialog; this component announces only with the flow closed, and
|
|
661
|
-
// `useAnnounce` routes those messages by where the field is — into a Dialog
|
|
662
|
-
// or modal Drawer that holds it, or the shared channel when no modal does.
|
|
663
|
-
// A result count that changed during the flow's exit is spoken once the
|
|
664
|
-
// flow has fully closed and focus is back on the trigger: the closing
|
|
665
|
-
// dialog stays modal through its exit, so neither its region nor the
|
|
666
|
-
// page-body channel is heard before.
|
|
667
|
-
const afterCloseResultCountRef = useRef<string | null>(null);
|
|
668
|
-
const triggerId = useId();
|
|
669
|
-
const triggerLabelId = useId();
|
|
670
|
-
const triggerContentId = useId();
|
|
671
|
-
const statusMessageId = useId();
|
|
672
|
-
// A field that becomes disabled cannot keep its flow open, and must not
|
|
673
|
-
// reopen it when re-enabled. Adjusted in the
|
|
674
|
-
// render that sees the change, not an effect, so no frame shows both.
|
|
675
|
-
const [wasDisabled, setWasDisabled] = useState(isDisabled);
|
|
676
|
-
if (isDisabled !== wasDisabled) {
|
|
677
|
-
setWasDisabled(isDisabled);
|
|
678
|
-
if (isDisabled && isSheetOpen) {
|
|
679
|
-
setIsSheetOpen(false);
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
useDevWarning(
|
|
683
|
-
'PowerSearch',
|
|
684
|
-
'`menuWidth` has no surface in the bottom-sheet presentation: the sheet flow has no typeahead popover to size. Scope it to the popover presentation or leave it out.',
|
|
685
|
-
isSheetPresentation && menuWidth != null,
|
|
686
|
-
);
|
|
687
|
-
useDevWarning(
|
|
688
|
-
'PowerSearch',
|
|
689
|
-
'`tokenOverflowBehavior` has no surface in the bottom-sheet presentation: the trigger wraps its display-only tokens. Scope it to the popover presentation or leave it out.',
|
|
690
|
-
isSheetPresentation && tokenOverflowBehavior != null,
|
|
691
|
-
);
|
|
692
|
-
|
|
693
540
|
const [popoverState, setPopoverStateRaw] = useState<PopoverState>({
|
|
694
541
|
type: 'idle',
|
|
695
542
|
});
|
|
@@ -705,7 +552,7 @@ export function PowerSearch({
|
|
|
705
552
|
const operatorLabel = operator ? resolveOperatorLabel(operator, t) : '';
|
|
706
553
|
const label = `${fieldLabel}: ${operatorLabel}`.trim();
|
|
707
554
|
const value = operator
|
|
708
|
-
?
|
|
555
|
+
? tokenValueText(
|
|
709
556
|
operator.value,
|
|
710
557
|
filter.value,
|
|
711
558
|
Math.max(
|
|
@@ -734,21 +581,9 @@ export function PowerSearch({
|
|
|
734
581
|
componentOverrides?.[editedOperator.value.type]?.Token == null
|
|
735
582
|
? editedFilter
|
|
736
583
|
: undefined;
|
|
737
|
-
// Whether the editor popover is open, for the hand-off below, which runs
|
|
738
|
-
// in the commit that swaps presentations.
|
|
739
|
-
const isPopoverOpenRef = useRef(false);
|
|
740
|
-
// When `adaptive` flips to the sheet while the editor popover is open (a
|
|
741
|
-
// phone rotated), the editing ends with the popover: its state returns to
|
|
742
|
-
// idle (through the raw setter, since the tokenizer input is gone), in the
|
|
743
|
-
// render that sees the swap, so a later switch back does not reopen a stale
|
|
744
|
-
// editor.
|
|
745
|
-
if (isSheetPresentation && popoverState.type !== 'idle') {
|
|
746
|
-
setPopoverStateRaw({type: 'idle'});
|
|
747
|
-
}
|
|
748
584
|
|
|
749
585
|
// Layer for positioning the edit popover anchored to the tokenizer
|
|
750
586
|
const handleLayerHide = useCallback(() => {
|
|
751
|
-
isPopoverOpenRef.current = false;
|
|
752
587
|
setPopoverStateRaw({type: 'idle'});
|
|
753
588
|
}, []);
|
|
754
589
|
|
|
@@ -769,54 +604,9 @@ export function PowerSearch({
|
|
|
769
604
|
: undefined,
|
|
770
605
|
});
|
|
771
606
|
|
|
772
|
-
// A presentation swap removes the focused control, so the other
|
|
773
|
-
// presentation's primary control (the combobox input or the trigger) takes
|
|
774
|
-
// focus and focus never falls to the page body. Each presentation keys its
|
|
775
|
-
// root, so a swap mounts a new root. The detaching root notes that it held
|
|
776
|
-
// focus, which also covers an input focused by server-rendered `autofocus`
|
|
777
|
-
// before hydration. The editor popover is portaled outside the root, so an
|
|
778
|
-
// open editor counts as held focus. The attaching root gives focus to its
|
|
779
|
-
// primary control when focus has fallen to the page body; the sheet root
|
|
780
|
-
// also hides a popover layer left open.
|
|
781
|
-
const isFocusPendingHandOffRef = useRef(false);
|
|
782
|
-
const hidePopover = popover.hide;
|
|
783
|
-
const handOffFocusRef = useCallback(
|
|
784
|
-
(node: HTMLDivElement | null) => {
|
|
785
|
-
if (node == null) {
|
|
786
|
-
return;
|
|
787
|
-
}
|
|
788
|
-
const trigger = triggerRef.current;
|
|
789
|
-
const isSheetRoot = trigger != null && node.contains(trigger);
|
|
790
|
-
// The sheet presentation preloads the flow's chunk, so the first press
|
|
791
|
-
// opens without waiting for the network; nothing renders.
|
|
792
|
-
if (isSheetRoot) {
|
|
793
|
-
void loadPowerSearchBottomSheet();
|
|
794
|
-
}
|
|
795
|
-
if (isSheetRoot && isPopoverOpenRef.current) {
|
|
796
|
-
isPopoverOpenRef.current = false;
|
|
797
|
-
hidePopover();
|
|
798
|
-
}
|
|
799
|
-
const takesFocus = isFocusPendingHandOffRef.current;
|
|
800
|
-
isFocusPendingHandOffRef.current = false;
|
|
801
|
-
if (takesFocus && isFocusDetached(node.ownerDocument)) {
|
|
802
|
-
focusAfterSwap(isSheetRoot ? trigger : tokenizerRef.current);
|
|
803
|
-
}
|
|
804
|
-
return () => {
|
|
805
|
-
if (
|
|
806
|
-
node.contains(node.ownerDocument.activeElement) ||
|
|
807
|
-
isPopoverOpenRef.current
|
|
808
|
-
) {
|
|
809
|
-
isFocusPendingHandOffRef.current = true;
|
|
810
|
-
}
|
|
811
|
-
};
|
|
812
|
-
},
|
|
813
|
-
[focusAfterSwap, hidePopover],
|
|
814
|
-
);
|
|
815
|
-
|
|
816
607
|
// Wrapper that manages layer visibility and tokenizer focus alongside state
|
|
817
608
|
const setPopoverState = useCallback(
|
|
818
609
|
(state: PopoverState) => {
|
|
819
|
-
isPopoverOpenRef.current = state.type !== 'idle';
|
|
820
610
|
setPopoverStateRaw(state);
|
|
821
611
|
if (state.type !== 'idle') {
|
|
822
612
|
// Schedule after the current frame so React can render the popover
|
|
@@ -833,22 +623,13 @@ export function PowerSearch({
|
|
|
833
623
|
[popover],
|
|
834
624
|
);
|
|
835
625
|
|
|
836
|
-
// Expose imperative handle
|
|
837
|
-
// field's focusable control.
|
|
626
|
+
// Expose imperative handle
|
|
838
627
|
useImperativeHandle(handleRef, () => ({
|
|
839
628
|
focusTypeahead() {
|
|
840
|
-
|
|
841
|
-
triggerRef.current?.focus();
|
|
842
|
-
} else {
|
|
843
|
-
tokenizerRef.current?.focus();
|
|
844
|
-
}
|
|
629
|
+
tokenizerRef.current?.focus();
|
|
845
630
|
},
|
|
846
631
|
blurTypeahead() {
|
|
847
|
-
|
|
848
|
-
triggerRef.current?.blur();
|
|
849
|
-
} else {
|
|
850
|
-
tokenizerRef.current?.blur();
|
|
851
|
-
}
|
|
632
|
+
tokenizerRef.current?.blur();
|
|
852
633
|
},
|
|
853
634
|
}));
|
|
854
635
|
|
|
@@ -1052,12 +833,10 @@ export function PowerSearch({
|
|
|
1052
833
|
setPopoverState({type: 'idle'});
|
|
1053
834
|
}, [setPopoverState]);
|
|
1054
835
|
|
|
1055
|
-
//
|
|
1056
|
-
//
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
const renderFilterToken = useCallback(
|
|
1060
|
-
(item: PowerSearchItem, onRemove: (() => void) | undefined) => {
|
|
836
|
+
// Custom token renderer
|
|
837
|
+
// Custom token renderer
|
|
838
|
+
const renderToken = useCallback(
|
|
839
|
+
(item: PowerSearchItem, onRemove: () => void) => {
|
|
1061
840
|
const auxData = item.auxiliaryData;
|
|
1062
841
|
const filterIndex = auxData?.filterIndex ?? -1;
|
|
1063
842
|
const filter = filterIndex >= 0 ? filters[filterIndex] : undefined;
|
|
@@ -1067,8 +846,7 @@ export function PowerSearch({
|
|
|
1067
846
|
? config.getOperator(auxData.fieldKey, auxData.operatorKey)
|
|
1068
847
|
: undefined;
|
|
1069
848
|
|
|
1070
|
-
const canInteract =
|
|
1071
|
-
onRemove != null && !isReadOnly && !isDisabled && !filter?.isReadOnly;
|
|
849
|
+
const canInteract = !isReadOnly && !isDisabled && !filter?.isReadOnly;
|
|
1072
850
|
const handleClick = canInteract
|
|
1073
851
|
? () => handleTokenClick(filterIndex)
|
|
1074
852
|
: undefined;
|
|
@@ -1181,12 +959,6 @@ export function PowerSearch({
|
|
|
1181
959
|
],
|
|
1182
960
|
);
|
|
1183
961
|
|
|
1184
|
-
const renderToken = useCallback(
|
|
1185
|
-
(item: PowerSearchItem, onRemove: () => void) =>
|
|
1186
|
-
renderFilterToken(item, onRemove),
|
|
1187
|
-
[renderFilterToken],
|
|
1188
|
-
);
|
|
1189
|
-
|
|
1190
962
|
// Custom typeahead item renderer — adds field icon and description
|
|
1191
963
|
const renderItem = useCallback(
|
|
1192
964
|
(item: PowerSearchItem) => {
|
|
@@ -1304,18 +1076,13 @@ export function PowerSearch({
|
|
|
1304
1076
|
return resultCount;
|
|
1305
1077
|
}, [resultCount, t]);
|
|
1306
1078
|
|
|
1307
|
-
//
|
|
1308
|
-
// mirroring the way Typeahead announces its dropdown result count
|
|
1309
|
-
// is otherwise only shown visually and stays silent to assistive
|
|
1310
|
-
//
|
|
1311
|
-
//
|
|
1312
|
-
//
|
|
1313
|
-
//
|
|
1314
|
-
// announced unprompted — and the guard compares against the last announced
|
|
1315
|
-
// text rather than a mounted flag, so StrictMode's effect replay announces
|
|
1316
|
-
// nothing either. A change that lands while the flow is closing waits for
|
|
1317
|
-
// the trigger focus, since the dialog is still modal through its exit. The
|
|
1318
|
-
// announcement acquires no resource, so there is no cleanup.
|
|
1079
|
+
// Announce result-count changes to screen readers through a polite live
|
|
1080
|
+
// region, mirroring the way Typeahead announces its dropdown result count.
|
|
1081
|
+
// The count is otherwise only shown visually and stays silent to assistive
|
|
1082
|
+
// tech. Only a change after mount is spoken: the guard compares against the
|
|
1083
|
+
// last announced text rather than a mounted flag, so StrictMode's effect
|
|
1084
|
+
// replay (which resets a flag and would speak the count present on mount
|
|
1085
|
+
// unprompted) announces nothing until the count changes.
|
|
1319
1086
|
const announce = useAnnounce();
|
|
1320
1087
|
// PowerSearch words its Clear all as its record does; the field announces
|
|
1321
1088
|
// these words instead of Tokenizer's (component:Tokenizer AR7).
|
|
@@ -1328,20 +1095,15 @@ export function PowerSearch({
|
|
|
1328
1095
|
[hasReadOnlyFilter, t],
|
|
1329
1096
|
);
|
|
1330
1097
|
const announcedResultCountRef = useRef(resultCountText);
|
|
1331
|
-
const isSheetExiting = isSheetPresented && !isSheetOpen;
|
|
1332
1098
|
useEffect(() => {
|
|
1333
1099
|
if (resultCountText === announcedResultCountRef.current) {
|
|
1334
1100
|
return;
|
|
1335
1101
|
}
|
|
1336
1102
|
announcedResultCountRef.current = resultCountText;
|
|
1337
1103
|
if (resultCountText != null) {
|
|
1338
|
-
|
|
1339
|
-
afterCloseResultCountRef.current = resultCountText;
|
|
1340
|
-
} else if (!isSheetOpen) {
|
|
1341
|
-
announce(resultCountText);
|
|
1342
|
-
}
|
|
1104
|
+
announce(resultCountText);
|
|
1343
1105
|
}
|
|
1344
|
-
}, [resultCountText, announce
|
|
1106
|
+
}, [resultCountText, announce]);
|
|
1345
1107
|
|
|
1346
1108
|
// Build combined endContent from resultCount + endContent props
|
|
1347
1109
|
const combinedEndContent = useMemo((): React.ReactNode => {
|
|
@@ -1362,230 +1124,14 @@ export function PowerSearch({
|
|
|
1362
1124
|
return resultCountNode || endContent || undefined;
|
|
1363
1125
|
}, [resultCountText, endContent]);
|
|
1364
1126
|
|
|
1365
|
-
// ------------------------------------------------------- sheet trigger --
|
|
1366
|
-
|
|
1367
|
-
// Disabled-reason tooltip for the sheet trigger. Disabled controls swallow
|
|
1368
|
-
// pointer events, so the listeners attach to the field's control box; the
|
|
1369
|
-
// trigger stays perceivable through aria-disabled.
|
|
1370
|
-
const showsDisabledMessage = isDisabled && !!disabledMessage;
|
|
1371
|
-
const disabledMessageTooltip = useTooltip({
|
|
1372
|
-
placement: 'above',
|
|
1373
|
-
focusTrigger: 'always',
|
|
1374
|
-
isEnabled: isSheetPresentation && showsDisabledMessage,
|
|
1375
|
-
});
|
|
1376
|
-
const rootRef = useMergedRefs(
|
|
1377
|
-
ref,
|
|
1378
|
-
popover.triggerRef as React.Ref<HTMLDivElement>,
|
|
1379
|
-
handOffFocusRef,
|
|
1380
|
-
);
|
|
1381
|
-
|
|
1382
|
-
const openSheet = useCallback(() => {
|
|
1383
|
-
if (isDisabled) {
|
|
1384
|
-
return;
|
|
1385
|
-
}
|
|
1386
|
-
// The flow restores focus to the element that was active when it opened;
|
|
1387
|
-
// make sure that is the trigger even for a tap on a token.
|
|
1388
|
-
triggerRef.current?.focus({preventScroll: true});
|
|
1389
|
-
setIsSheetPresented(true);
|
|
1390
|
-
setIsSheetOpen(true);
|
|
1391
|
-
}, [isDisabled]);
|
|
1392
|
-
|
|
1393
|
-
// The field's end lane — the result count and `endContent` — sits outside
|
|
1394
|
-
// the trigger button: a caller's action button there keeps its own tab stop
|
|
1395
|
-
// and taps, and an interactive control must not nest in a component
|
|
1396
|
-
// button, so a tap on it must not open the flow.
|
|
1397
|
-
const endLaneRef = useRef<HTMLDivElement>(null);
|
|
1398
|
-
const handleTriggerWrapperClick = useCallback(
|
|
1399
|
-
(event: React.MouseEvent<HTMLDivElement>) => {
|
|
1400
|
-
// Tokens are display-only; a tap on them or the empty space around them
|
|
1401
|
-
// opens the flow. The trigger button handles its own click, and the end
|
|
1402
|
-
// lane handles its own.
|
|
1403
|
-
if (
|
|
1404
|
-
event.target instanceof Node &&
|
|
1405
|
-
(triggerRef.current?.contains(event.target) ||
|
|
1406
|
-
endLaneRef.current?.contains(event.target))
|
|
1407
|
-
) {
|
|
1408
|
-
return;
|
|
1409
|
-
}
|
|
1410
|
-
openSheet();
|
|
1411
|
-
},
|
|
1412
|
-
[openSheet],
|
|
1413
|
-
);
|
|
1414
|
-
|
|
1415
|
-
const handleTriggerFocusWithin = useCallback(
|
|
1416
|
-
(event: React.FocusEvent<HTMLDivElement>) => {
|
|
1417
|
-
// The flow focuses the trigger only after its exit completes; that focus
|
|
1418
|
-
// is the signal that the retained sheets can leave. If the resolution
|
|
1419
|
-
// moved to the popover meanwhile, the tokenizer input takes over the
|
|
1420
|
-
// focus once it mounts (the hand-off above).
|
|
1421
|
-
if (!isSheetOpen && isSheetPresented) {
|
|
1422
|
-
setIsSheetPresented(false);
|
|
1423
|
-
// The dialogs have closed: the page-body channel is exposed again.
|
|
1424
|
-
const resultCount = afterCloseResultCountRef.current;
|
|
1425
|
-
afterCloseResultCountRef.current = null;
|
|
1426
|
-
if (resultCount != null) {
|
|
1427
|
-
announce(resultCount);
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
1430
|
-
if (
|
|
1431
|
-
!(event.relatedTarget instanceof Node) ||
|
|
1432
|
-
!event.currentTarget.contains(event.relatedTarget)
|
|
1433
|
-
) {
|
|
1434
|
-
handleFieldFocus(event);
|
|
1435
|
-
}
|
|
1436
|
-
},
|
|
1437
|
-
[announce, handleFieldFocus, isSheetOpen, isSheetPresented],
|
|
1438
|
-
);
|
|
1439
|
-
const handleTriggerBlurWithin = useCallback(
|
|
1440
|
-
(event: React.FocusEvent<HTMLDivElement>) => {
|
|
1441
|
-
if (
|
|
1442
|
-
!(event.relatedTarget instanceof Node) ||
|
|
1443
|
-
!event.currentTarget.contains(event.relatedTarget)
|
|
1444
|
-
) {
|
|
1445
|
-
handleFieldBlur(event);
|
|
1446
|
-
}
|
|
1447
|
-
},
|
|
1448
|
-
[handleFieldBlur],
|
|
1449
|
-
);
|
|
1450
|
-
|
|
1451
|
-
if (isSheetPresentation) {
|
|
1452
|
-
const triggerDescribedBy =
|
|
1453
|
-
[
|
|
1454
|
-
status?.message ? statusMessageId : null,
|
|
1455
|
-
showsDisabledMessage ? disabledMessageTooltip.describedBy : null,
|
|
1456
|
-
]
|
|
1457
|
-
.filter(Boolean)
|
|
1458
|
-
.join(' ') || undefined;
|
|
1459
|
-
|
|
1460
|
-
return (
|
|
1461
|
-
<div key="sheet" ref={rootRef} {...themeProps('power-search')}>
|
|
1462
|
-
<Field
|
|
1463
|
-
label={label}
|
|
1464
|
-
isLabelHidden={isLabelHidden}
|
|
1465
|
-
inputID={triggerId}
|
|
1466
|
-
labelID={triggerLabelId}
|
|
1467
|
-
isDisabled={isDisabled}
|
|
1468
|
-
status={
|
|
1469
|
-
status
|
|
1470
|
-
? {
|
|
1471
|
-
type: status.type,
|
|
1472
|
-
message: status.message,
|
|
1473
|
-
messageID: status.message ? statusMessageId : undefined,
|
|
1474
|
-
}
|
|
1475
|
-
: undefined
|
|
1476
|
-
}
|
|
1477
|
-
statusVariant={statusVariant}
|
|
1478
|
-
xstyle={xstyle}
|
|
1479
|
-
className={className}
|
|
1480
|
-
style={style}>
|
|
1481
|
-
<div
|
|
1482
|
-
ref={disabledMessageTooltip.ref}
|
|
1483
|
-
onClick={handleTriggerWrapperClick}
|
|
1484
|
-
onFocus={handleTriggerFocusWithin}
|
|
1485
|
-
onBlur={handleTriggerBlurWithin}
|
|
1486
|
-
aria-disabled={isDisabled ? true : undefined}
|
|
1487
|
-
{...stylex.props(
|
|
1488
|
-
inputWrapperStyles.base,
|
|
1489
|
-
sheetTriggerStyles.wrapper,
|
|
1490
|
-
filters.length > 0 && sheetTriggerStyles.wrapperWithTokens,
|
|
1491
|
-
sheetTriggerSizeStyles[size],
|
|
1492
|
-
isDisabled && inputWrapperStyles.disabled,
|
|
1493
|
-
status && inputStatusBorderStyles[status.type],
|
|
1494
|
-
status &&
|
|
1495
|
-
!isDisabled &&
|
|
1496
|
-
inputStatusHoverShadowStyles[status.type],
|
|
1497
|
-
status && inputStatusFocusWithinStyles[status.type],
|
|
1498
|
-
)}>
|
|
1499
|
-
{/* The trigger's name is the label plus everything visible here:
|
|
1500
|
-
the placeholder, or the tokens, so speech input can say what
|
|
1501
|
-
it sees and assistive technology hears the selected filters
|
|
1502
|
-
(WCAG 2.5.3). */}
|
|
1503
|
-
<div
|
|
1504
|
-
id={triggerContentId}
|
|
1505
|
-
{...stylex.props(sheetTriggerStyles.contentSection)}>
|
|
1506
|
-
{isRenderable(startIcon as React.ReactNode) && (
|
|
1507
|
-
<span
|
|
1508
|
-
{...stylex.props(
|
|
1509
|
-
filters.length > 0 &&
|
|
1510
|
-
sheetTriggerStyles.startIconWithTokens,
|
|
1511
|
-
)}>
|
|
1512
|
-
{renderIconSlot(startIcon, {size: 'sm', color: 'secondary'})}
|
|
1513
|
-
</span>
|
|
1514
|
-
)}
|
|
1515
|
-
{tokenizerValue.map(item => (
|
|
1516
|
-
<React.Fragment key={item.id}>
|
|
1517
|
-
{renderFilterToken(item, undefined)}
|
|
1518
|
-
</React.Fragment>
|
|
1519
|
-
))}
|
|
1520
|
-
<button
|
|
1521
|
-
type="button"
|
|
1522
|
-
id={triggerId}
|
|
1523
|
-
ref={triggerRef}
|
|
1524
|
-
// The field's primary control: `data-testid` lands here, as it
|
|
1525
|
-
// lands on the combobox input in the popover presentation.
|
|
1526
|
-
data-testid={testId}
|
|
1527
|
-
autoFocus={autoFocusOnMount}
|
|
1528
|
-
onClick={openSheet}
|
|
1529
|
-
aria-disabled={isDisabled ? true : undefined}
|
|
1530
|
-
aria-haspopup="dialog"
|
|
1531
|
-
aria-expanded={isSheetOpen}
|
|
1532
|
-
aria-labelledby={`${triggerLabelId} ${triggerContentId}`}
|
|
1533
|
-
aria-describedby={triggerDescribedBy}
|
|
1534
|
-
{...stylex.props(
|
|
1535
|
-
sheetTriggerStyles.trigger,
|
|
1536
|
-
filters.length > 0 && sheetTriggerStyles.triggerWithTokens,
|
|
1537
|
-
)}>
|
|
1538
|
-
{filters.length === 0 ? placeholder : null}
|
|
1539
|
-
</button>
|
|
1540
|
-
</div>
|
|
1541
|
-
{(endContent || isRenderable(resultCountText)) && (
|
|
1542
|
-
<div
|
|
1543
|
-
ref={endLaneRef}
|
|
1544
|
-
{...stylex.props(sheetTriggerStyles.endSection)}>
|
|
1545
|
-
{isRenderable(resultCountText) && (
|
|
1546
|
-
<span {...stylex.props(resultCountStyles.text)}>
|
|
1547
|
-
{resultCountText}
|
|
1548
|
-
</span>
|
|
1549
|
-
)}
|
|
1550
|
-
{endContent}
|
|
1551
|
-
</div>
|
|
1552
|
-
)}
|
|
1553
|
-
</div>
|
|
1554
|
-
{showsDisabledMessage &&
|
|
1555
|
-
disabledMessageTooltip.renderTooltip(disabledMessage)}
|
|
1556
|
-
</Field>
|
|
1557
|
-
{/* Mounted from the press until the flow's exit has completed and
|
|
1558
|
-
focus is back on the trigger (`isSheetPresented`): at rest the
|
|
1559
|
-
field renders its trigger alone — no closed dialogs, rows, search
|
|
1560
|
-
field or editors in the DOM. */}
|
|
1561
|
-
{(isSheetOpen || isSheetPresented) && (
|
|
1562
|
-
<PowerSearchSheetHost
|
|
1563
|
-
isOpen={isSheetOpen}
|
|
1564
|
-
onOpenChange={setIsSheetOpen}
|
|
1565
|
-
finalFocusRef={triggerRef}
|
|
1566
|
-
resultCountText={resultCountText}
|
|
1567
|
-
config={config}
|
|
1568
|
-
configProp={configProp}
|
|
1569
|
-
filters={filters}
|
|
1570
|
-
onChange={onChange}
|
|
1571
|
-
searchSource={searchSource}
|
|
1572
|
-
placeholder={placeholder}
|
|
1573
|
-
isReadOnly={isReadOnly}
|
|
1574
|
-
isDisabled={isDisabled}
|
|
1575
|
-
hasClear={hasClear}
|
|
1576
|
-
maxOperatorMenuItems={maxOperatorMenuItems}
|
|
1577
|
-
maxSearchResults={maxSearchResults}
|
|
1578
|
-
saveButtonLabel={popoverSaveButtonLabelFromProps}
|
|
1579
|
-
components={componentOverrides}
|
|
1580
|
-
/>
|
|
1581
|
-
)}
|
|
1582
|
-
</div>
|
|
1583
|
-
);
|
|
1584
|
-
}
|
|
1585
|
-
|
|
1586
1127
|
return (
|
|
1587
1128
|
<>
|
|
1588
|
-
<div
|
|
1129
|
+
<div
|
|
1130
|
+
ref={useMergedRefs(
|
|
1131
|
+
ref,
|
|
1132
|
+
popover.triggerRef as React.Ref<HTMLDivElement>,
|
|
1133
|
+
)}
|
|
1134
|
+
{...themeProps('power-search')}>
|
|
1589
1135
|
{/* The field's Clear all announces PowerSearch's own words
|
|
1590
1136
|
(component:PowerSearch; component:Tokenizer AR7). */}
|
|
1591
1137
|
<TokenizerClearAllMessageContext value={clearAllMessage}>
|
|
@@ -1602,7 +1148,7 @@ export function PowerSearch({
|
|
|
1602
1148
|
maxMenuItems={MAX_BROWSE_MENU_ITEMS}
|
|
1603
1149
|
menuWidth={menuWidth}
|
|
1604
1150
|
placeholder={filters.length === 0 ? placeholder : ''}
|
|
1605
|
-
hasAutoFocus={
|
|
1151
|
+
hasAutoFocus={hasAutoFocus}
|
|
1606
1152
|
hasClear={hasClear && !isReadOnly}
|
|
1607
1153
|
startIcon={startIcon}
|
|
1608
1154
|
endContent={combinedEndContent}
|
|
@@ -1614,8 +1160,8 @@ export function PowerSearch({
|
|
|
1614
1160
|
debounceMs={0}
|
|
1615
1161
|
status={status}
|
|
1616
1162
|
statusVariant={statusVariant}
|
|
1617
|
-
onFocus={
|
|
1618
|
-
onBlur={
|
|
1163
|
+
onFocus={onFocus}
|
|
1164
|
+
onBlur={onBlur}
|
|
1619
1165
|
containerXstyle={xstyle}
|
|
1620
1166
|
containerClassName={className}
|
|
1621
1167
|
containerStyle={style}
|