@astryxdesign/core 0.5.2-canary.c9c8564 → 0.5.2-canary.e4f8e4e
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/BottomSheet/BottomSheet.d.ts +3 -1
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +4 -3
- package/dist/BottomSheet/BottomSheetEdgeTint.d.ts +2 -2
- package/dist/BottomSheet/BottomSheetEdgeTint.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetEdgeTint.js +7 -10
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetSwitcher.js +1 -1
- package/dist/Button/Button.d.ts +2 -1
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +11 -4
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +2 -2
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +34 -17
- package/dist/ContextMenu/ContextMenu.d.ts +14 -3
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +149 -26
- package/dist/ContextMenu/index.d.ts +1 -0
- package/dist/ContextMenu/index.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.d.ts +24 -5
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +340 -26
- package/dist/DropdownMenu/DropdownMenuContext.d.ts +1 -1
- package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuItem.js +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +6 -2
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +43 -14
- package/dist/DropdownMenu/MenuBottomSheet.d.ts +20 -0
- package/dist/DropdownMenu/MenuBottomSheet.d.ts.map +1 -0
- package/dist/DropdownMenu/MenuBottomSheet.js +36 -0
- package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts +10 -0
- package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts.map +1 -0
- package/dist/DropdownMenu/MenuBottomSheetActionList.js +111 -0
- package/dist/DropdownMenu/index.d.ts +2 -1
- package/dist/DropdownMenu/index.d.ts.map +1 -1
- package/dist/DropdownMenu/menuWidth.d.ts +14 -0
- package/dist/DropdownMenu/menuWidth.d.ts.map +1 -0
- package/dist/DropdownMenu/menuWidth.js +35 -0
- package/dist/DropdownMenu/useMenuOverflow.d.ts +10 -0
- package/dist/DropdownMenu/useMenuOverflow.d.ts.map +1 -0
- package/dist/DropdownMenu/useMenuOverflow.js +60 -0
- package/dist/MoreMenu/MoreMenu.d.ts +8 -2
- package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
- package/dist/MoreMenu/MoreMenu.js +2 -0
- package/dist/MultiSelector/MultiSelector.d.ts +11 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +97 -68
- package/dist/MultiSelector/index.d.ts +1 -1
- package/dist/MultiSelector/index.d.ts.map +1 -1
- package/dist/Popover/Popover.d.ts +10 -6
- package/dist/Popover/Popover.d.ts.map +1 -1
- package/dist/Popover/Popover.js +141 -16
- package/dist/Popover/usePopover.d.ts +4 -0
- package/dist/Popover/usePopover.d.ts.map +1 -1
- package/dist/Popover/usePopover.js +52 -5
- package/dist/Selector/Selector.d.ts +10 -0
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +106 -80
- package/dist/Selector/SelectorBottomSheet.d.ts +21 -0
- package/dist/Selector/SelectorBottomSheet.d.ts.map +1 -0
- package/dist/Selector/SelectorBottomSheet.js +83 -0
- package/dist/Selector/index.d.ts +1 -1
- package/dist/Selector/index.d.ts.map +1 -1
- package/dist/Selector/selectorPresentation.stylex.d.ts +16 -0
- package/dist/Selector/selectorPresentation.stylex.d.ts.map +1 -0
- package/dist/Selector/selectorPresentation.stylex.js +21 -0
- package/dist/Selector/useSelectorPresentation.d.ts +32 -0
- package/dist/Selector/useSelectorPresentation.d.ts.map +1 -0
- package/dist/Selector/useSelectorPresentation.js +90 -0
- package/dist/Toast/useToastGesture.d.ts.map +1 -1
- package/dist/Toast/useToastGesture.js +46 -9
- package/dist/astryx.css +19 -1
- package/dist/hooks/useAdaptivePresentation.d.ts +5 -0
- package/dist/hooks/useAdaptivePresentation.d.ts.map +1 -0
- package/dist/hooks/useAdaptivePresentation.js +19 -0
- package/dist/hooks/useFocusReturnVisibility.d.ts +7 -0
- package/dist/hooks/useFocusReturnVisibility.d.ts.map +1 -0
- package/dist/hooks/useFocusReturnVisibility.js +35 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +12 -0
- package/locales/en.json +20 -0
- package/locales/pseudo.json +15 -0
- package/package.json +2 -2
- package/src/BottomSheet/BottomSheet.doc.mjs +6 -0
- package/src/BottomSheet/BottomSheet.test.tsx +29 -0
- package/src/BottomSheet/BottomSheet.tsx +7 -4
- package/src/BottomSheet/BottomSheetEdgeTint.test.tsx +4 -6
- package/src/BottomSheet/BottomSheetEdgeTint.tsx +7 -10
- package/src/BottomSheet/BottomSheetSwitcher.tsx +1 -2
- package/src/Button/Button.doc.mjs +56 -0
- package/src/Button/Button.test.tsx +10 -0
- package/src/Button/Button.tsx +13 -5
- package/src/ButtonGroup/ButtonGroup.doc.mjs +47 -0
- package/src/Carousel/Carousel.test.tsx +83 -0
- package/src/Carousel/Carousel.tsx +8 -2
- package/src/Chat/ChatToolCalls.test.tsx +44 -0
- package/src/Chat/ChatToolCalls.tsx +36 -15
- package/src/ContextMenu/ContextMenu.doc.mjs +13 -1
- package/src/ContextMenu/ContextMenu.test.tsx +131 -1
- package/src/ContextMenu/ContextMenu.tsx +199 -32
- package/src/ContextMenu/index.ts +1 -0
- package/src/DropdownMenu/DropdownMenu.doc.mjs +201 -40
- package/src/DropdownMenu/DropdownMenu.test.tsx +518 -7
- package/src/DropdownMenu/DropdownMenu.tsx +459 -28
- package/src/DropdownMenu/DropdownMenuContext.tsx +1 -1
- package/src/DropdownMenu/DropdownMenuItem.tsx +2 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +4 -2
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +124 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +72 -17
- package/src/DropdownMenu/MenuBottomSheet.tsx +46 -0
- package/src/DropdownMenu/MenuBottomSheetActionList.tsx +141 -0
- package/src/DropdownMenu/index.ts +2 -0
- package/src/DropdownMenu/menuWidth.ts +51 -0
- package/src/DropdownMenu/useMenuOverflow.ts +77 -0
- package/src/IconButton/IconButton.doc.mjs +38 -0
- package/src/MoreMenu/MoreMenu.doc.mjs +17 -0
- package/src/MoreMenu/MoreMenu.test.tsx +47 -0
- package/src/MoreMenu/MoreMenu.tsx +13 -1
- package/src/MultiSelector/MultiSelector.doc.mjs +16 -0
- package/src/MultiSelector/MultiSelector.test.tsx +127 -0
- package/src/MultiSelector/MultiSelector.tsx +121 -73
- package/src/MultiSelector/index.ts +1 -0
- package/src/Popover/Popover.doc.mjs +11 -11
- package/src/Popover/Popover.test.tsx +397 -2
- package/src/Popover/Popover.tsx +228 -21
- package/src/Popover/usePopover.tsx +92 -15
- package/src/ProgressBar/ProgressBar.spec.md +208 -0
- package/src/SegmentedControl/SegmentedControl.doc.mjs +56 -0
- package/src/Selector/Selector.doc.mjs +12 -0
- package/src/Selector/Selector.test.tsx +135 -0
- package/src/Selector/Selector.tsx +141 -82
- package/src/Selector/SelectorBottomSheet.tsx +97 -0
- package/src/Selector/index.ts +1 -0
- package/src/Selector/selectorPresentation.stylex.ts +20 -0
- package/src/Selector/useSelectorPresentation.ts +147 -0
- package/src/Toast/ToastViewport.test.tsx +118 -4
- package/src/Toast/useToastGesture.ts +61 -8
- package/src/ToggleButton/ToggleButton.doc.mjs +56 -0
- package/src/hooks/useAdaptivePresentation.ts +35 -0
- package/src/hooks/useFocusReturnVisibility.ts +45 -0
- package/src/hooks/useFocusTrap.test.tsx +40 -0
- package/src/hooks/useFocusTrap.ts +13 -0
- package/src/theme/themingTargets.test.ts +91 -25
|
@@ -28,7 +28,6 @@ import React, {
|
|
|
28
28
|
type ReactNode,
|
|
29
29
|
} from 'react';
|
|
30
30
|
import * as stylex from '@stylexjs/stylex';
|
|
31
|
-
import {usePopoverInternal} from '../Popover/usePopover';
|
|
32
31
|
import {useTooltip} from '../Tooltip';
|
|
33
32
|
import {Icon, renderIconSlot, type IconType} from '../Icon';
|
|
34
33
|
import type {IconName} from '../Icon';
|
|
@@ -57,7 +56,6 @@ import {
|
|
|
57
56
|
typographyVars,
|
|
58
57
|
fontWeightVars,
|
|
59
58
|
typeScaleVars,
|
|
60
|
-
borderVars,
|
|
61
59
|
} from '../theme/tokens.stylex';
|
|
62
60
|
import type {
|
|
63
61
|
MultiSelectorOptionType,
|
|
@@ -86,6 +84,10 @@ import {groupStyles} from '../InputGroup/groupStyles';
|
|
|
86
84
|
import {useInputGroup} from '../InputGroup/InputGroupContext';
|
|
87
85
|
import {VisuallyHidden} from '../VisuallyHidden';
|
|
88
86
|
import {useTranslator} from '../i18n';
|
|
87
|
+
import type {AdaptivePresentation} from '../hooks/useAdaptivePresentation';
|
|
88
|
+
import {SelectorBottomSheet} from '../Selector/SelectorBottomSheet';
|
|
89
|
+
import {useSelectorPresentation} from '../Selector/useSelectorPresentation';
|
|
90
|
+
import {selectorPresentationStyles} from '../Selector/selectorPresentation.stylex';
|
|
89
91
|
|
|
90
92
|
// Sentinel value for the select-all item in keyboard navigation
|
|
91
93
|
const SELECT_ALL_VALUE = '__xds_select_all__';
|
|
@@ -245,6 +247,9 @@ const styles = stylex.create({
|
|
|
245
247
|
overflowY: 'auto',
|
|
246
248
|
padding: spacingVars['--spacing-1'],
|
|
247
249
|
},
|
|
250
|
+
listbox: {
|
|
251
|
+
outline: 'none',
|
|
252
|
+
},
|
|
248
253
|
|
|
249
254
|
// Popover container (for anchor positioning)
|
|
250
255
|
popover: {
|
|
@@ -415,6 +420,8 @@ export type MultiSelectorSize = 'sm' | 'md' | 'lg';
|
|
|
415
420
|
|
|
416
421
|
export type MultiSelectorVariant = 'input' | 'ghost';
|
|
417
422
|
|
|
423
|
+
export type MultiSelectorPresentation = AdaptivePresentation;
|
|
424
|
+
|
|
418
425
|
export type MultiSelectorStatusType = 'warning' | 'error' | 'success';
|
|
419
426
|
|
|
420
427
|
export type {MultiSelectorStatus};
|
|
@@ -658,6 +665,15 @@ export interface MultiSelectorProps<
|
|
|
658
665
|
*/
|
|
659
666
|
indicatorPosition?: IndicatorPosition;
|
|
660
667
|
|
|
668
|
+
/**
|
|
669
|
+
* How the option list is presented.
|
|
670
|
+
* - 'popover': anchored to the trigger
|
|
671
|
+
* - 'bottom-sheet': modal sheet suited to compact touch screens
|
|
672
|
+
* - 'adaptive': bottom sheet on compact coarse-pointer screens, otherwise popover
|
|
673
|
+
* @default 'popover'
|
|
674
|
+
*/
|
|
675
|
+
presentation?: MultiSelectorPresentation;
|
|
676
|
+
|
|
661
677
|
/**
|
|
662
678
|
* Whether the dropdown starts open on mount.
|
|
663
679
|
* Useful for showcases and previews.
|
|
@@ -747,6 +763,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
747
763
|
maxBadges = 3,
|
|
748
764
|
renderOption,
|
|
749
765
|
indicatorPosition = 'start',
|
|
766
|
+
presentation = 'popover',
|
|
750
767
|
isDefaultOpen = false,
|
|
751
768
|
'data-testid': testId,
|
|
752
769
|
htmlName,
|
|
@@ -754,6 +771,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
754
771
|
xstyle,
|
|
755
772
|
className,
|
|
756
773
|
style,
|
|
774
|
+
onFocus,
|
|
757
775
|
}: MultiSelectorProps<T>) {
|
|
758
776
|
const t = useTranslator();
|
|
759
777
|
const isEffectivelyRequired = useResolvedRequired({isRequired, isOptional});
|
|
@@ -780,6 +798,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
780
798
|
const searchId = useId();
|
|
781
799
|
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
782
800
|
const searchRef = useRef<HTMLInputElement>(null);
|
|
801
|
+
const listboxRef = useRef<HTMLDivElement>(null);
|
|
783
802
|
const inputGroup = useInputGroup();
|
|
784
803
|
|
|
785
804
|
const [searchQuery, setSearchQuery] = useState('');
|
|
@@ -935,27 +954,31 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
935
954
|
// Clear any lingering result count when the popover closes so stale status
|
|
936
955
|
// text does not linger in the a11y tree.
|
|
937
956
|
announce('');
|
|
938
|
-
triggerRef.current?.focus();
|
|
939
957
|
}, [announce]);
|
|
940
958
|
|
|
941
|
-
const
|
|
942
|
-
|
|
959
|
+
const surface = useSelectorPresentation({
|
|
960
|
+
presentation,
|
|
943
961
|
onHide: handleLayerHide,
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
962
|
+
triggerRef,
|
|
963
|
+
popoverOptions: {
|
|
964
|
+
hasLightDismiss: true,
|
|
965
|
+
hasCloseButton: false,
|
|
966
|
+
hasAutoFocus: false,
|
|
967
|
+
// The popup's own role="listbox" is the exposed semantics; the trigger
|
|
968
|
+
// keeps DOM focus, so wrapping it in a modal dialog would misrepresent it.
|
|
969
|
+
role: 'none',
|
|
970
|
+
// The theme target belongs on the SURFACE that paints the popup, which
|
|
971
|
+
// `usePopover` owns — not on the scrolling list inside it.
|
|
972
|
+
surfaceTarget: 'multi-selector-popup',
|
|
973
|
+
},
|
|
952
974
|
});
|
|
975
|
+
const {popover} = surface;
|
|
953
976
|
const keepOpenProps = useKeepLayerOpenProps(popover.id, popover.isOpen);
|
|
954
977
|
|
|
955
978
|
// Open dropdown on mount when isDefaultOpen is true
|
|
956
979
|
useEffect(() => {
|
|
957
980
|
if (isDefaultOpen) {
|
|
958
|
-
|
|
981
|
+
surface.show();
|
|
959
982
|
}
|
|
960
983
|
// eslint-disable-next-line @eslint-react/exhaustive-deps -- mount-only: isDefaultOpen is not reactive
|
|
961
984
|
}, []);
|
|
@@ -999,7 +1022,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
999
1022
|
const announcedEmptyRef = useRef<string | null>(null);
|
|
1000
1023
|
useEffect(() => {
|
|
1001
1024
|
const isPanelEmpty =
|
|
1002
|
-
|
|
1025
|
+
surface.isOpen &&
|
|
1003
1026
|
!isLoading &&
|
|
1004
1027
|
searchQuery === '' &&
|
|
1005
1028
|
selectableItems.length === 0;
|
|
@@ -1013,7 +1036,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1013
1036
|
announcedEmptyRef.current = emptyAnnouncement;
|
|
1014
1037
|
announce(emptyAnnouncement);
|
|
1015
1038
|
}, [
|
|
1016
|
-
|
|
1039
|
+
surface.isOpen,
|
|
1017
1040
|
isLoading,
|
|
1018
1041
|
searchQuery,
|
|
1019
1042
|
selectableItems.length,
|
|
@@ -1158,24 +1181,24 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1158
1181
|
onKeyDown,
|
|
1159
1182
|
onItemMouseEnter,
|
|
1160
1183
|
} = useMultiCombobox({
|
|
1161
|
-
wasJustDismissed:
|
|
1184
|
+
wasJustDismissed: surface.wasJustDismissed,
|
|
1162
1185
|
selectableItems: sortedItems,
|
|
1163
1186
|
isDisabled,
|
|
1164
|
-
isOpen:
|
|
1187
|
+
isOpen: surface.isOpen,
|
|
1165
1188
|
hasSearch,
|
|
1166
1189
|
onOpen: useCallback(() => {
|
|
1167
1190
|
// Snapshot which items are selected at open time — sort is frozen until close
|
|
1168
1191
|
setSelectedAtOpen(new Set(optimisticValue));
|
|
1169
1192
|
|
|
1170
|
-
|
|
1193
|
+
surface.show();
|
|
1171
1194
|
if (hasSearch) {
|
|
1172
1195
|
// Focus search after popover opens
|
|
1173
1196
|
requestAnimationFrame(() => {
|
|
1174
1197
|
searchRef.current?.focus();
|
|
1175
1198
|
});
|
|
1176
1199
|
}
|
|
1177
|
-
}, [
|
|
1178
|
-
onClose:
|
|
1200
|
+
}, [surface, hasSearch, optimisticValue]),
|
|
1201
|
+
onClose: surface.hide,
|
|
1179
1202
|
onToggle: handleNavigableToggle,
|
|
1180
1203
|
onClear: hasClear ? clearValues : undefined,
|
|
1181
1204
|
hasValue,
|
|
@@ -1187,13 +1210,13 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1187
1210
|
// cursor walks off-screen once navigation passes the visible window. Mirrors
|
|
1188
1211
|
// CommandPaletteItem's scrollIntoView({block: 'nearest'}) behavior.
|
|
1189
1212
|
useEffect(() => {
|
|
1190
|
-
if (!
|
|
1213
|
+
if (!surface.isOpen || highlightedIndex < 0) {
|
|
1191
1214
|
return;
|
|
1192
1215
|
}
|
|
1193
1216
|
document
|
|
1194
1217
|
.getElementById(getItemId(highlightedIndex))
|
|
1195
1218
|
?.scrollIntoView?.({block: 'nearest'});
|
|
1196
|
-
}, [
|
|
1219
|
+
}, [surface.isOpen, highlightedIndex, getItemId]);
|
|
1197
1220
|
|
|
1198
1221
|
// Build trigger display content
|
|
1199
1222
|
const selectedItems = useMemo(() => {
|
|
@@ -1288,11 +1311,11 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1288
1311
|
// not the trigger — must be the combobox reporting the highlighted
|
|
1289
1312
|
// option via aria-activedescendant (comboboxes-4).
|
|
1290
1313
|
role="combobox"
|
|
1291
|
-
aria-expanded={
|
|
1314
|
+
aria-expanded={surface.isOpen}
|
|
1292
1315
|
aria-controls={listboxId}
|
|
1293
1316
|
aria-autocomplete="list"
|
|
1294
1317
|
aria-activedescendant={
|
|
1295
|
-
|
|
1318
|
+
surface.isOpen && highlightedIndex >= 0
|
|
1296
1319
|
? getItemId(highlightedIndex)
|
|
1297
1320
|
: undefined
|
|
1298
1321
|
}
|
|
@@ -1355,7 +1378,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1355
1378
|
searchPlaceholder,
|
|
1356
1379
|
handleSearchChange,
|
|
1357
1380
|
onKeyDown,
|
|
1358
|
-
|
|
1381
|
+
surface.isOpen,
|
|
1359
1382
|
highlightedIndex,
|
|
1360
1383
|
getItemId,
|
|
1361
1384
|
t,
|
|
@@ -1608,6 +1631,64 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1608
1631
|
const showStatusTooltip =
|
|
1609
1632
|
status != null && effectiveStatusVariant === 'tooltip' && !!status.message;
|
|
1610
1633
|
|
|
1634
|
+
const panelContent = hasSearch ? (
|
|
1635
|
+
<div>
|
|
1636
|
+
{renderSearch()}
|
|
1637
|
+
<Divider />
|
|
1638
|
+
<div {...stylex.props(styles.dropdown)}>
|
|
1639
|
+
<div
|
|
1640
|
+
ref={listboxRef}
|
|
1641
|
+
id={listboxId}
|
|
1642
|
+
role="listbox"
|
|
1643
|
+
aria-multiselectable="true"
|
|
1644
|
+
aria-labelledby={triggerId}
|
|
1645
|
+
{...stylex.props(styles.listbox)}>
|
|
1646
|
+
{renderOptions()}
|
|
1647
|
+
</div>
|
|
1648
|
+
</div>
|
|
1649
|
+
</div>
|
|
1650
|
+
) : (
|
|
1651
|
+
<div {...stylex.props(styles.dropdown)}>
|
|
1652
|
+
<div
|
|
1653
|
+
ref={listboxRef}
|
|
1654
|
+
id={listboxId}
|
|
1655
|
+
role="listbox"
|
|
1656
|
+
aria-multiselectable="true"
|
|
1657
|
+
aria-labelledby={triggerId}
|
|
1658
|
+
aria-activedescendant={
|
|
1659
|
+
surface.isOpen && highlightedIndex >= 0
|
|
1660
|
+
? getItemId(highlightedIndex)
|
|
1661
|
+
: undefined
|
|
1662
|
+
}
|
|
1663
|
+
tabIndex={surface.activePresentation === 'bottom-sheet' ? 0 : undefined}
|
|
1664
|
+
onKeyDown={
|
|
1665
|
+
surface.activePresentation === 'bottom-sheet' ? onKeyDown : undefined
|
|
1666
|
+
}
|
|
1667
|
+
{...stylex.props(styles.listbox)}>
|
|
1668
|
+
{renderOptions()}
|
|
1669
|
+
</div>
|
|
1670
|
+
</div>
|
|
1671
|
+
);
|
|
1672
|
+
|
|
1673
|
+
const selectionSurface =
|
|
1674
|
+
surface.activePresentation === 'bottom-sheet' ? (
|
|
1675
|
+
<SelectorBottomSheet
|
|
1676
|
+
isOpen={surface.isSheetOpen}
|
|
1677
|
+
onOpenChange={surface.onSheetOpenChange}
|
|
1678
|
+
finalFocusRef={triggerRef}
|
|
1679
|
+
initialFocusRef={hasSearch ? searchRef : listboxRef}
|
|
1680
|
+
label={label}>
|
|
1681
|
+
{panelContent}
|
|
1682
|
+
</SelectorBottomSheet>
|
|
1683
|
+
) : (
|
|
1684
|
+
popover.render(panelContent, {
|
|
1685
|
+
placement: 'below',
|
|
1686
|
+
alignment: 'start',
|
|
1687
|
+
offset: spacingVars['--spacing-1'],
|
|
1688
|
+
xstyle: styles.popover,
|
|
1689
|
+
})
|
|
1690
|
+
);
|
|
1691
|
+
|
|
1611
1692
|
const multiSelectorContent = (
|
|
1612
1693
|
<>
|
|
1613
1694
|
<div
|
|
@@ -1634,6 +1715,8 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1634
1715
|
variant === 'ghost' && styles.triggerGhost,
|
|
1635
1716
|
variant === 'ghost' && interactionOverlayStyles.backgroundImage,
|
|
1636
1717
|
variant === 'ghost' && focusOutlineStyles.focusWithin,
|
|
1718
|
+
surface.isTriggerFocusRingSuppressed &&
|
|
1719
|
+
selectorPresentationStyles.pointerRestoredFocus,
|
|
1637
1720
|
isDisabled && inputWrapperStyles.disabled,
|
|
1638
1721
|
variant === 'ghost' && isDisabled && styles.triggerGhostDisabled,
|
|
1639
1722
|
optimisticValue.length === 0 && styles.triggerPlaceholder,
|
|
@@ -1663,11 +1746,13 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1663
1746
|
// focus + aria-activedescendant, comboboxes-4), so the trigger is a
|
|
1664
1747
|
// plain button that opens the listbox — not a second combobox.
|
|
1665
1748
|
role={hasSearch ? undefined : 'combobox'}
|
|
1666
|
-
aria-haspopup=
|
|
1667
|
-
|
|
1749
|
+
aria-haspopup={
|
|
1750
|
+
surface.activePresentation === 'bottom-sheet' ? 'dialog' : 'listbox'
|
|
1751
|
+
}
|
|
1752
|
+
aria-expanded={surface.isOpen}
|
|
1668
1753
|
aria-controls={listboxId}
|
|
1669
1754
|
aria-activedescendant={
|
|
1670
|
-
!hasSearch &&
|
|
1755
|
+
!hasSearch && surface.isOpen && highlightedIndex >= 0
|
|
1671
1756
|
? getItemId(highlightedIndex)
|
|
1672
1757
|
: undefined
|
|
1673
1758
|
}
|
|
@@ -1682,6 +1767,10 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1682
1767
|
disabled={isDisabled && !showsDisabledMessage}
|
|
1683
1768
|
aria-disabled={showsDisabledMessage ? 'true' : undefined}
|
|
1684
1769
|
onKeyDown={onKeyDown}
|
|
1770
|
+
onFocus={event => {
|
|
1771
|
+
onFocus?.(event);
|
|
1772
|
+
surface.onTriggerFocus(event);
|
|
1773
|
+
}}
|
|
1685
1774
|
tabIndex={isDisabled && !showsDisabledMessage ? -1 : 0}
|
|
1686
1775
|
{...stylex.props(styles.trigger)}>
|
|
1687
1776
|
<span {...stylex.props(styles.triggerContent)}>
|
|
@@ -1754,7 +1843,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1754
1843
|
xstyle={[
|
|
1755
1844
|
styles.triggerIcon,
|
|
1756
1845
|
styles.triggerIconRotation,
|
|
1757
|
-
|
|
1846
|
+
surface.isOpen && styles.triggerIconOpen,
|
|
1758
1847
|
]}
|
|
1759
1848
|
// Stable theme target on the chevron glyph itself, so a theme can
|
|
1760
1849
|
// restyle just this icon (color, size, hover) — and its
|
|
@@ -1762,54 +1851,13 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1762
1851
|
// @layer astryx-theme win over the icon's own base color/size,
|
|
1763
1852
|
// which a button-level target could not reach.
|
|
1764
1853
|
{...themeProps('multi-selector-indicator-icon', {
|
|
1765
|
-
state:
|
|
1854
|
+
state: surface.isOpen ? 'expanded' : 'collapsed',
|
|
1766
1855
|
})}
|
|
1767
1856
|
/>
|
|
1768
1857
|
)}
|
|
1769
1858
|
</div>
|
|
1770
1859
|
|
|
1771
|
-
{
|
|
1772
|
-
hasSearch ? (
|
|
1773
|
-
// With a search row the panel splits: the header stays put while the
|
|
1774
|
-
// options scroll under it, so the field does not slide out of reach
|
|
1775
|
-
// in a long list. Without one the panel is a single scroll container,
|
|
1776
|
-
// exactly as before.
|
|
1777
|
-
<div>
|
|
1778
|
-
{renderSearch()}
|
|
1779
|
-
{/*
|
|
1780
|
-
Separates the header from the options and spans the panel: the
|
|
1781
|
-
search row and the option list each hold their own inline
|
|
1782
|
-
padding, the line does not, so it reads as the panel's own edge.
|
|
1783
|
-
*/}
|
|
1784
|
-
<Divider />
|
|
1785
|
-
<div {...stylex.props(styles.dropdown)}>
|
|
1786
|
-
<div
|
|
1787
|
-
id={listboxId}
|
|
1788
|
-
role="listbox"
|
|
1789
|
-
aria-multiselectable="true"
|
|
1790
|
-
aria-labelledby={triggerId}>
|
|
1791
|
-
{renderOptions()}
|
|
1792
|
-
</div>
|
|
1793
|
-
</div>
|
|
1794
|
-
</div>
|
|
1795
|
-
) : (
|
|
1796
|
-
<div {...stylex.props(styles.dropdown)}>
|
|
1797
|
-
<div
|
|
1798
|
-
id={listboxId}
|
|
1799
|
-
role="listbox"
|
|
1800
|
-
aria-multiselectable="true"
|
|
1801
|
-
aria-labelledby={triggerId}>
|
|
1802
|
-
{renderOptions()}
|
|
1803
|
-
</div>
|
|
1804
|
-
</div>
|
|
1805
|
-
),
|
|
1806
|
-
{
|
|
1807
|
-
placement: 'below',
|
|
1808
|
-
alignment: 'start',
|
|
1809
|
-
offset: spacingVars['--spacing-1'],
|
|
1810
|
-
xstyle: styles.popover,
|
|
1811
|
-
},
|
|
1812
|
-
)}
|
|
1860
|
+
{selectionSurface}
|
|
1813
1861
|
|
|
1814
1862
|
{showStatusTooltip && statusTooltip.renderTooltip(status?.message ?? '')}
|
|
1815
1863
|
|
|
@@ -64,7 +64,7 @@ export const docs = {
|
|
|
64
64
|
{
|
|
65
65
|
name: 'width',
|
|
66
66
|
type: 'number | string',
|
|
67
|
-
description: 'Width of the popover container.',
|
|
67
|
+
description: 'Width of the popover container. The layer still caps to the viewport with alignment-aware safe-area gutters before scrolling long content.',
|
|
68
68
|
default: "'auto'",
|
|
69
69
|
},
|
|
70
70
|
{
|
|
@@ -101,7 +101,7 @@ export const docs = {
|
|
|
101
101
|
{
|
|
102
102
|
name: 'hasAutoFocus',
|
|
103
103
|
type: 'boolean',
|
|
104
|
-
description: 'Whether to
|
|
104
|
+
description: 'Whether to move focus into the popover when it opens. Keyboard activation focuses the first content control; pointer activation focuses the labeled dialog container so an action does not appear preselected. Set to false for inline showcases or documentation previews.',
|
|
105
105
|
default: 'true',
|
|
106
106
|
},
|
|
107
107
|
{
|
|
@@ -149,8 +149,8 @@ export const docs = {
|
|
|
149
149
|
{ guidance: true, description: 'Keep popover content focused on a single task or piece of information.' },
|
|
150
150
|
{ guidance: true, description: 'Provide a clear way to close: either by clicking outside or with an explicit close button.' },
|
|
151
151
|
{ guidance: false, description: 'Nest popovers inside other popovers; it creates confusing focus and navigation.' },
|
|
152
|
-
{ guidance: false, description: '
|
|
153
|
-
{ guidance: false, description: '
|
|
152
|
+
{ guidance: false, description: 'Assume input complexity alone determines the presentation; evaluate the task’s focus, space, and interaction requirements.' },
|
|
153
|
+
{ guidance: false, description: 'Assume scrolling alone means Popover is the wrong component; a bounded Popover may scroll while a focused anchored interaction remains appropriate.' },
|
|
154
154
|
],
|
|
155
155
|
anatomy: [
|
|
156
156
|
{name: 'Header', required: true, description: 'Contains the title, optional subheader, and close button.'},
|
|
@@ -220,7 +220,7 @@ export const docsZh = {
|
|
|
220
220
|
{
|
|
221
221
|
name: 'width',
|
|
222
222
|
type: 'number | string',
|
|
223
|
-
description: '
|
|
223
|
+
description: '弹出框容器的宽度。弹出层仍会限制在视口和安全区域留白内,长内容再滚动。',
|
|
224
224
|
default: "'auto'",
|
|
225
225
|
},
|
|
226
226
|
{
|
|
@@ -294,8 +294,8 @@ export const docsZh = {
|
|
|
294
294
|
{ guidance: true, description: 'Keep popover content focused on a single task or piece of information.' },
|
|
295
295
|
{ guidance: true, description: 'Provide a clear way to close: either by clicking outside or with an explicit close button.' },
|
|
296
296
|
{ guidance: false, description: 'Nest popovers inside other popovers; it creates confusing focus and navigation.' },
|
|
297
|
-
{ guidance: false, description: '
|
|
298
|
-
{ guidance: false, description: '
|
|
297
|
+
{ guidance: false, description: 'Assume input complexity alone determines the presentation; evaluate the task’s focus, space, and interaction requirements.' },
|
|
298
|
+
{ guidance: false, description: 'Assume scrolling alone means Popover is the wrong component; a bounded Popover may scroll while a focused anchored interaction remains appropriate.' },
|
|
299
299
|
],
|
|
300
300
|
anatomy: [
|
|
301
301
|
{name: 'Header', required: true, description: 'Contains the title, optional subheader, and close button.'},
|
|
@@ -316,8 +316,8 @@ export const docsDense = {
|
|
|
316
316
|
{ guidance: true, description: 'Keep popover content focused on a single task or piece of information.' },
|
|
317
317
|
{ guidance: true, description: 'Provide a clear way to close: either by clicking outside or with an explicit close button.' },
|
|
318
318
|
{ guidance: false, description: 'Nest popovers inside other popovers; it creates confusing focus and navigation.' },
|
|
319
|
-
{ guidance: false, description: '
|
|
320
|
-
{ guidance: false, description: '
|
|
319
|
+
{ guidance: false, description: 'Assume input complexity alone determines the presentation; evaluate the task’s focus, space, and interaction requirements.' },
|
|
320
|
+
{ guidance: false, description: 'Assume scrolling alone means Popover is the wrong component; a bounded Popover may scroll while a focused anchored interaction remains appropriate.' },
|
|
321
321
|
],
|
|
322
322
|
anatomy: [
|
|
323
323
|
{name: 'Header', required: true, description: 'Contains the title, optional subheader, and close button.'},
|
|
@@ -340,11 +340,11 @@ export const docsDense = {
|
|
|
340
340
|
isOpen: 'Whether popover shown in controlled mode.',
|
|
341
341
|
onOpenChange: 'Callback fired when popover visibility changes.',
|
|
342
342
|
isEnabled: 'When false, trigger interactions ignored.',
|
|
343
|
-
width: 'Popover container width.',
|
|
343
|
+
width: 'Popover container width; capped to alignment-aware viewport/safe-area gutters before long content scrolls.',
|
|
344
344
|
label: 'Accessible label for popover dialog.',
|
|
345
345
|
hasCloseButton: 'Whether to include hidden close button for accessibility.',
|
|
346
346
|
closeButtonLabel: 'Label for hidden close button.',
|
|
347
|
-
hasAutoFocus: '
|
|
347
|
+
hasAutoFocus: 'Move focus into the popover on open; keyboard targets the first control and pointer targets the dialog container.',
|
|
348
348
|
hasLightDismiss: 'Outside click dismisses; false for explicit-dismiss surfaces (coachmarks).',
|
|
349
349
|
hasEscapeDismiss: 'Escape dismisses; full effect only with hasLightDismiss=false.',
|
|
350
350
|
},
|