@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
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import React, { useCallback, useEffect, useId, useMemo, useOptimistic, useRef, useState, useTransition } from 'react';
|
|
19
19
|
import * as stylex from '@stylexjs/stylex';
|
|
20
|
-
import { usePopoverInternal } from "../Popover/usePopover.js";
|
|
21
20
|
import { useTooltip } from "../Tooltip/index.js";
|
|
22
21
|
import { Icon, renderIconSlot } from "../Icon/index.js";
|
|
23
22
|
import { Field, inputStatusBorderStyles, inputStatusHoverShadowStyles, inputWrapperStyles } from "../Field/index.js";
|
|
@@ -44,6 +43,9 @@ import { groupStyles } from "../InputGroup/groupStyles.js";
|
|
|
44
43
|
import { useInputGroup } from "../InputGroup/InputGroupContext.js";
|
|
45
44
|
import { VisuallyHidden } from "../VisuallyHidden/index.js";
|
|
46
45
|
import { useTranslator } from "../i18n/index.js";
|
|
46
|
+
import { SelectorBottomSheet } from "../Selector/SelectorBottomSheet.js";
|
|
47
|
+
import { useSelectorPresentation } from "../Selector/useSelectorPresentation.js";
|
|
48
|
+
import { selectorPresentationStyles } from "../Selector/selectorPresentation.stylex.js";
|
|
47
49
|
|
|
48
50
|
// Sentinel value for the select-all item in keyboard navigation
|
|
49
51
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
@@ -284,13 +286,15 @@ export function MultiSelector({
|
|
|
284
286
|
maxBadges = 3,
|
|
285
287
|
renderOption,
|
|
286
288
|
indicatorPosition = 'start',
|
|
289
|
+
presentation = 'popover',
|
|
287
290
|
isDefaultOpen = false,
|
|
288
291
|
'data-testid': testId,
|
|
289
292
|
htmlName,
|
|
290
293
|
width,
|
|
291
294
|
xstyle,
|
|
292
295
|
className,
|
|
293
|
-
style
|
|
296
|
+
style,
|
|
297
|
+
onFocus
|
|
294
298
|
}) {
|
|
295
299
|
const t = useTranslator();
|
|
296
300
|
const isEffectivelyRequired = useResolvedRequired({
|
|
@@ -312,6 +316,7 @@ export function MultiSelector({
|
|
|
312
316
|
const searchId = useId();
|
|
313
317
|
const triggerRef = useRef(null);
|
|
314
318
|
const searchRef = useRef(null);
|
|
319
|
+
const listboxRef = useRef(null);
|
|
315
320
|
const inputGroup = useInputGroup();
|
|
316
321
|
const [searchQuery, setSearchQuery] = useState('');
|
|
317
322
|
// A typed query shows the search row's clear (✕) button, which becomes
|
|
@@ -432,26 +437,32 @@ export function MultiSelector({
|
|
|
432
437
|
// Clear any lingering result count when the popover closes so stale status
|
|
433
438
|
// text does not linger in the a11y tree.
|
|
434
439
|
announce('');
|
|
435
|
-
triggerRef.current?.focus();
|
|
436
440
|
}, [announce]);
|
|
437
|
-
const
|
|
438
|
-
|
|
441
|
+
const surface = useSelectorPresentation({
|
|
442
|
+
presentation,
|
|
439
443
|
onHide: handleLayerHide,
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
444
|
+
triggerRef,
|
|
445
|
+
popoverOptions: {
|
|
446
|
+
hasLightDismiss: true,
|
|
447
|
+
hasCloseButton: false,
|
|
448
|
+
hasAutoFocus: false,
|
|
449
|
+
// The popup's own role="listbox" is the exposed semantics; the trigger
|
|
450
|
+
// keeps DOM focus, so wrapping it in a modal dialog would misrepresent it.
|
|
451
|
+
role: 'none',
|
|
452
|
+
// The theme target belongs on the SURFACE that paints the popup, which
|
|
453
|
+
// `usePopover` owns — not on the scrolling list inside it.
|
|
454
|
+
surfaceTarget: 'multi-selector-popup'
|
|
455
|
+
}
|
|
448
456
|
});
|
|
457
|
+
const {
|
|
458
|
+
popover
|
|
459
|
+
} = surface;
|
|
449
460
|
const keepOpenProps = useKeepLayerOpenProps(popover.id, popover.isOpen);
|
|
450
461
|
|
|
451
462
|
// Open dropdown on mount when isDefaultOpen is true
|
|
452
463
|
useEffect(() => {
|
|
453
464
|
if (isDefaultOpen) {
|
|
454
|
-
|
|
465
|
+
surface.show();
|
|
455
466
|
}
|
|
456
467
|
// eslint-disable-next-line @eslint-react/exhaustive-deps -- mount-only: isDefaultOpen is not reactive
|
|
457
468
|
}, []);
|
|
@@ -489,7 +500,7 @@ export function MultiSelector({
|
|
|
489
500
|
// per arrival at that state rather than on every re-render.
|
|
490
501
|
const announcedEmptyRef = useRef(null);
|
|
491
502
|
useEffect(() => {
|
|
492
|
-
const isPanelEmpty =
|
|
503
|
+
const isPanelEmpty = surface.isOpen && !isLoading && searchQuery === '' && selectableItems.length === 0;
|
|
493
504
|
if (!isPanelEmpty) {
|
|
494
505
|
announcedEmptyRef.current = null;
|
|
495
506
|
return;
|
|
@@ -499,7 +510,7 @@ export function MultiSelector({
|
|
|
499
510
|
}
|
|
500
511
|
announcedEmptyRef.current = emptyAnnouncement;
|
|
501
512
|
announce(emptyAnnouncement);
|
|
502
|
-
}, [
|
|
513
|
+
}, [surface.isOpen, isLoading, searchQuery, selectableItems.length, emptyAnnouncement, announce]);
|
|
503
514
|
|
|
504
515
|
// Handle toggle
|
|
505
516
|
// Clear all selected values. Shared by the clear button and the keyboard
|
|
@@ -582,23 +593,23 @@ export function MultiSelector({
|
|
|
582
593
|
onKeyDown,
|
|
583
594
|
onItemMouseEnter
|
|
584
595
|
} = useMultiCombobox({
|
|
585
|
-
wasJustDismissed:
|
|
596
|
+
wasJustDismissed: surface.wasJustDismissed,
|
|
586
597
|
selectableItems: sortedItems,
|
|
587
598
|
isDisabled,
|
|
588
|
-
isOpen:
|
|
599
|
+
isOpen: surface.isOpen,
|
|
589
600
|
hasSearch,
|
|
590
601
|
onOpen: useCallback(() => {
|
|
591
602
|
// Snapshot which items are selected at open time — sort is frozen until close
|
|
592
603
|
setSelectedAtOpen(new Set(optimisticValue));
|
|
593
|
-
|
|
604
|
+
surface.show();
|
|
594
605
|
if (hasSearch) {
|
|
595
606
|
// Focus search after popover opens
|
|
596
607
|
requestAnimationFrame(() => {
|
|
597
608
|
searchRef.current?.focus();
|
|
598
609
|
});
|
|
599
610
|
}
|
|
600
|
-
}, [
|
|
601
|
-
onClose:
|
|
611
|
+
}, [surface, hasSearch, optimisticValue]),
|
|
612
|
+
onClose: surface.hide,
|
|
602
613
|
onToggle: handleNavigableToggle,
|
|
603
614
|
onClear: hasClear ? clearValues : undefined,
|
|
604
615
|
hasValue,
|
|
@@ -610,13 +621,13 @@ export function MultiSelector({
|
|
|
610
621
|
// cursor walks off-screen once navigation passes the visible window. Mirrors
|
|
611
622
|
// CommandPaletteItem's scrollIntoView({block: 'nearest'}) behavior.
|
|
612
623
|
useEffect(() => {
|
|
613
|
-
if (!
|
|
624
|
+
if (!surface.isOpen || highlightedIndex < 0) {
|
|
614
625
|
return;
|
|
615
626
|
}
|
|
616
627
|
document.getElementById(getItemId(highlightedIndex))?.scrollIntoView?.({
|
|
617
628
|
block: 'nearest'
|
|
618
629
|
});
|
|
619
|
-
}, [
|
|
630
|
+
}, [surface.isOpen, highlightedIndex, getItemId]);
|
|
620
631
|
|
|
621
632
|
// Build trigger display content
|
|
622
633
|
const selectedItems = useMemo(() => {
|
|
@@ -706,10 +717,10 @@ export function MultiSelector({
|
|
|
706
717
|
// not the trigger — must be the combobox reporting the highlighted
|
|
707
718
|
// option via aria-activedescendant (comboboxes-4).
|
|
708
719
|
role: "combobox",
|
|
709
|
-
"aria-expanded":
|
|
720
|
+
"aria-expanded": surface.isOpen,
|
|
710
721
|
"aria-controls": listboxId,
|
|
711
722
|
"aria-autocomplete": "list",
|
|
712
|
-
"aria-activedescendant":
|
|
723
|
+
"aria-activedescendant": surface.isOpen && highlightedIndex >= 0 ? getItemId(highlightedIndex) : undefined,
|
|
713
724
|
value: searchQuery,
|
|
714
725
|
onValueChange: handleSearchChange,
|
|
715
726
|
onContainerKeyDown: e => {
|
|
@@ -752,7 +763,7 @@ export function MultiSelector({
|
|
|
752
763
|
},
|
|
753
764
|
placeholder: searchPlaceholder
|
|
754
765
|
});
|
|
755
|
-
}, [hasSearch, searchId, listboxId, searchQuery, hasQuery, searchPlaceholder, handleSearchChange, onKeyDown,
|
|
766
|
+
}, [hasSearch, searchId, listboxId, searchQuery, hasQuery, searchPlaceholder, handleSearchChange, onKeyDown, surface.isOpen, highlightedIndex, getItemId, t]);
|
|
756
767
|
|
|
757
768
|
// Render an individual item (index-based)
|
|
758
769
|
const renderItem = useCallback((item, flatIndex) => {
|
|
@@ -928,6 +939,55 @@ export function MultiSelector({
|
|
|
928
939
|
// on-field icon would duplicate it — keep the chevron indicator instead.
|
|
929
940
|
const showStatusIcon = status != null && effectiveStatusVariant !== 'detached';
|
|
930
941
|
const showStatusTooltip = status != null && effectiveStatusVariant === 'tooltip' && !!status.message;
|
|
942
|
+
const panelContent = hasSearch ? /*#__PURE__*/_jsxs("div", {
|
|
943
|
+
children: [renderSearch(), /*#__PURE__*/_jsx(Divider, {}), /*#__PURE__*/_jsx("div", {
|
|
944
|
+
...{
|
|
945
|
+
className: "x9f619 xuyqlj2 x1odjw0f x9epnlk"
|
|
946
|
+
},
|
|
947
|
+
children: /*#__PURE__*/_jsx("div", {
|
|
948
|
+
ref: listboxRef,
|
|
949
|
+
id: listboxId,
|
|
950
|
+
role: "listbox",
|
|
951
|
+
"aria-multiselectable": "true",
|
|
952
|
+
"aria-labelledby": triggerId,
|
|
953
|
+
...{
|
|
954
|
+
className: "x1a2a7pz"
|
|
955
|
+
},
|
|
956
|
+
children: renderOptions()
|
|
957
|
+
})
|
|
958
|
+
})]
|
|
959
|
+
}) : /*#__PURE__*/_jsx("div", {
|
|
960
|
+
...{
|
|
961
|
+
className: "x9f619 xuyqlj2 x1odjw0f x9epnlk"
|
|
962
|
+
},
|
|
963
|
+
children: /*#__PURE__*/_jsx("div", {
|
|
964
|
+
ref: listboxRef,
|
|
965
|
+
id: listboxId,
|
|
966
|
+
role: "listbox",
|
|
967
|
+
"aria-multiselectable": "true",
|
|
968
|
+
"aria-labelledby": triggerId,
|
|
969
|
+
"aria-activedescendant": surface.isOpen && highlightedIndex >= 0 ? getItemId(highlightedIndex) : undefined,
|
|
970
|
+
tabIndex: surface.activePresentation === 'bottom-sheet' ? 0 : undefined,
|
|
971
|
+
onKeyDown: surface.activePresentation === 'bottom-sheet' ? onKeyDown : undefined,
|
|
972
|
+
...{
|
|
973
|
+
className: "x1a2a7pz"
|
|
974
|
+
},
|
|
975
|
+
children: renderOptions()
|
|
976
|
+
})
|
|
977
|
+
});
|
|
978
|
+
const selectionSurface = surface.activePresentation === 'bottom-sheet' ? /*#__PURE__*/_jsx(SelectorBottomSheet, {
|
|
979
|
+
isOpen: surface.isSheetOpen,
|
|
980
|
+
onOpenChange: surface.onSheetOpenChange,
|
|
981
|
+
finalFocusRef: triggerRef,
|
|
982
|
+
initialFocusRef: hasSearch ? searchRef : listboxRef,
|
|
983
|
+
label: label,
|
|
984
|
+
children: panelContent
|
|
985
|
+
}) : popover.render(panelContent, {
|
|
986
|
+
placement: 'below',
|
|
987
|
+
alignment: 'start',
|
|
988
|
+
offset: spacingVars['--spacing-1'],
|
|
989
|
+
xstyle: styles.popover
|
|
990
|
+
});
|
|
931
991
|
const multiSelectorContent = /*#__PURE__*/_jsxs(_Fragment, {
|
|
932
992
|
children: [/*#__PURE__*/_jsxs("div", {
|
|
933
993
|
ref: el => {
|
|
@@ -944,7 +1004,7 @@ export function MultiSelector({
|
|
|
944
1004
|
size,
|
|
945
1005
|
status: status?.type ?? null,
|
|
946
1006
|
disabled: isDisabled ? 'disabled' : null
|
|
947
|
-
}), stylex.props(inputWrapperStyles.base, styles.triggerContainer, sizeStyles[size], variant === 'ghost' && styles.triggerGhost, variant === 'ghost' && interactionOverlayStyles.backgroundImage, variant === 'ghost' && focusOutlineStyles.focusWithin, isDisabled && inputWrapperStyles.disabled, variant === 'ghost' && isDisabled && styles.triggerGhostDisabled, optimisticValue.length === 0 && styles.triggerPlaceholder, variant !== 'ghost' && status && inputStatusBorderStyles[status.type], variant !== 'ghost' && status && !isDisabled && inputStatusHoverShadowStyles[status.type], variant !== 'ghost' && inputGroup && groupStyles.inGroup, xstyle), className, style),
|
|
1007
|
+
}), stylex.props(inputWrapperStyles.base, styles.triggerContainer, sizeStyles[size], variant === 'ghost' && styles.triggerGhost, variant === 'ghost' && interactionOverlayStyles.backgroundImage, variant === 'ghost' && focusOutlineStyles.focusWithin, surface.isTriggerFocusRingSuppressed && selectorPresentationStyles.pointerRestoredFocus, isDisabled && inputWrapperStyles.disabled, variant === 'ghost' && isDisabled && styles.triggerGhostDisabled, optimisticValue.length === 0 && styles.triggerPlaceholder, variant !== 'ghost' && status && inputStatusBorderStyles[status.type], variant !== 'ghost' && status && !isDisabled && inputStatusHoverShadowStyles[status.type], variant !== 'ghost' && inputGroup && groupStyles.inGroup, xstyle), className, style),
|
|
948
1008
|
children: [startIcon && renderIconSlot(startIcon, {
|
|
949
1009
|
size: 'sm',
|
|
950
1010
|
color: 'secondary'
|
|
@@ -960,10 +1020,10 @@ export function MultiSelector({
|
|
|
960
1020
|
// plain button that opens the listbox — not a second combobox.
|
|
961
1021
|
,
|
|
962
1022
|
role: hasSearch ? undefined : 'combobox',
|
|
963
|
-
"aria-haspopup":
|
|
964
|
-
"aria-expanded":
|
|
1023
|
+
"aria-haspopup": surface.activePresentation === 'bottom-sheet' ? 'dialog' : 'listbox',
|
|
1024
|
+
"aria-expanded": surface.isOpen,
|
|
965
1025
|
"aria-controls": listboxId,
|
|
966
|
-
"aria-activedescendant": !hasSearch &&
|
|
1026
|
+
"aria-activedescendant": !hasSearch && surface.isOpen && highlightedIndex >= 0 ? getItemId(highlightedIndex) : undefined,
|
|
967
1027
|
"aria-describedby": ariaDescribedBy,
|
|
968
1028
|
"aria-labelledby": ariaLabelledBy,
|
|
969
1029
|
"aria-required": isEffectivelyRequired ? 'true' : undefined,
|
|
@@ -976,6 +1036,10 @@ export function MultiSelector({
|
|
|
976
1036
|
disabled: isDisabled && !showsDisabledMessage,
|
|
977
1037
|
"aria-disabled": showsDisabledMessage ? 'true' : undefined,
|
|
978
1038
|
onKeyDown: onKeyDown,
|
|
1039
|
+
onFocus: event => {
|
|
1040
|
+
onFocus?.(event);
|
|
1041
|
+
surface.onTriggerFocus(event);
|
|
1042
|
+
},
|
|
979
1043
|
tabIndex: isDisabled && !showsDisabledMessage ? -1 : 0,
|
|
980
1044
|
...{
|
|
981
1045
|
className: "x78zum5 x6s0dn4 x1qughib x1txdalj x1iyjqo2 xs83m0k x1r8uery xeuugli x1717udv x1ghz6dp xc342km xng3xce xjbqb8w xjb2p0i x1qlqyl8 x15bjb6t x1heor9g x1ypdohk x16khyan x1a2a7pz xh6dtrn"
|
|
@@ -1030,7 +1094,7 @@ export function MultiSelector({
|
|
|
1030
1094
|
// the wrapper used to provide, so one element carries the mark,
|
|
1031
1095
|
// its open/closed transform, and the theme target.
|
|
1032
1096
|
,
|
|
1033
|
-
xstyle: [styles.triggerIcon, styles.triggerIconRotation,
|
|
1097
|
+
xstyle: [styles.triggerIcon, styles.triggerIconRotation, surface.isOpen && styles.triggerIconOpen]
|
|
1034
1098
|
// Stable theme target on the chevron glyph itself, so a theme can
|
|
1035
1099
|
// restyle just this icon (color, size, hover) — and its
|
|
1036
1100
|
// open/closed state — via `defineTheme`. Same-element rules in
|
|
@@ -1038,45 +1102,10 @@ export function MultiSelector({
|
|
|
1038
1102
|
// which a button-level target could not reach.
|
|
1039
1103
|
,
|
|
1040
1104
|
...themeProps('multi-selector-indicator-icon', {
|
|
1041
|
-
state:
|
|
1105
|
+
state: surface.isOpen ? 'expanded' : 'collapsed'
|
|
1042
1106
|
})
|
|
1043
1107
|
})]
|
|
1044
|
-
}),
|
|
1045
|
-
/*#__PURE__*/
|
|
1046
|
-
// With a search row the panel splits: the header stays put while the
|
|
1047
|
-
// options scroll under it, so the field does not slide out of reach
|
|
1048
|
-
// in a long list. Without one the panel is a single scroll container,
|
|
1049
|
-
// exactly as before.
|
|
1050
|
-
_jsxs("div", {
|
|
1051
|
-
children: [renderSearch(), /*#__PURE__*/_jsx(Divider, {}), /*#__PURE__*/_jsx("div", {
|
|
1052
|
-
...{
|
|
1053
|
-
className: "x9f619 xuyqlj2 x1odjw0f x9epnlk"
|
|
1054
|
-
},
|
|
1055
|
-
children: /*#__PURE__*/_jsx("div", {
|
|
1056
|
-
id: listboxId,
|
|
1057
|
-
role: "listbox",
|
|
1058
|
-
"aria-multiselectable": "true",
|
|
1059
|
-
"aria-labelledby": triggerId,
|
|
1060
|
-
children: renderOptions()
|
|
1061
|
-
})
|
|
1062
|
-
})]
|
|
1063
|
-
}) : /*#__PURE__*/_jsx("div", {
|
|
1064
|
-
...{
|
|
1065
|
-
className: "x9f619 xuyqlj2 x1odjw0f x9epnlk"
|
|
1066
|
-
},
|
|
1067
|
-
children: /*#__PURE__*/_jsx("div", {
|
|
1068
|
-
id: listboxId,
|
|
1069
|
-
role: "listbox",
|
|
1070
|
-
"aria-multiselectable": "true",
|
|
1071
|
-
"aria-labelledby": triggerId,
|
|
1072
|
-
children: renderOptions()
|
|
1073
|
-
})
|
|
1074
|
-
}), {
|
|
1075
|
-
placement: 'below',
|
|
1076
|
-
alignment: 'start',
|
|
1077
|
-
offset: spacingVars['--spacing-1'],
|
|
1078
|
-
xstyle: styles.popover
|
|
1079
|
-
}), showStatusTooltip && statusTooltip.renderTooltip(status?.message ?? ''), showsDisabledMessage && disabledMessageTooltip.renderTooltip(disabledMessage)]
|
|
1108
|
+
}), selectionSurface, showStatusTooltip && statusTooltip.renderTooltip(status?.message ?? ''), showsDisabledMessage && disabledMessageTooltip.renderTooltip(disabledMessage)]
|
|
1080
1109
|
});
|
|
1081
1110
|
if (inputGroup) {
|
|
1082
1111
|
return multiSelectorContent;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @output Exports MultiSelector and types
|
|
4
4
|
* @position Public API entry point
|
|
5
5
|
*/
|
|
6
|
-
export { MultiSelector, type MultiSelectorProps, type MultiSelectorSize, type MultiSelectorStatusType, type MultiSelectorSelectedItem, } from './MultiSelector';
|
|
6
|
+
export { MultiSelector, type MultiSelectorProps, type MultiSelectorPresentation, type MultiSelectorSize, type MultiSelectorStatusType, type MultiSelectorSelectedItem, } from './MultiSelector';
|
|
7
7
|
export type { MultiSelectorOptionType, MultiSelectorOptionData, MultiSelectorDivider, MultiSelectorSection, MultiSelectorStatus, } from './types';
|
|
8
8
|
export { useMultiCombobox } from './hooks';
|
|
9
9
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/MultiSelector/index.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AAEH,OAAO,EACL,aAAa,EACb,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAC5B,KAAK,yBAAyB,GAC/B,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,uBAAuB,EACvB,uBAAuB,EACvB,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAC,gBAAgB,EAAC,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/MultiSelector/index.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AAEH,OAAO,EACL,aAAa,EACb,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAC9B,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAC5B,KAAK,yBAAyB,GAC/B,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,uBAAuB,EACvB,uBAAuB,EACvB,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAC,gBAAgB,EAAC,MAAM,SAAS,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file Popover.tsx
|
|
3
|
-
* @input Uses React
|
|
4
|
-
* @output Exports Popover
|
|
3
|
+
* @input Uses React layout measurement and the usePopover hook
|
|
4
|
+
* @output Exports Popover with viewport fitting and conditional overflow
|
|
5
5
|
* @position Layer component; declarative wrapper around usePopover hook
|
|
6
6
|
*
|
|
7
7
|
* For hover-triggered overlays, use HoverCard instead.
|
|
@@ -21,9 +21,11 @@ import type { LayerAlignment, LayerPlacement } from '../Layer/useLayer';
|
|
|
21
21
|
export interface PopoverTriggerRenderProps {
|
|
22
22
|
/** Ref callback — attach to the trigger element for anchor positioning. */
|
|
23
23
|
ref: (el: HTMLElement | null) => void;
|
|
24
|
-
/** Toggle the popover open/closed. */
|
|
25
|
-
onClick: (
|
|
26
|
-
|
|
24
|
+
/** Toggle the popover open/closed. Pass the click event through so pointer and keyboard focus behavior can differ. */
|
|
25
|
+
onClick: (event?: {
|
|
26
|
+
detail: number;
|
|
27
|
+
}) => void;
|
|
28
|
+
/** ARIA attribute: indicates the trigger opens a dialog-style popover. */
|
|
27
29
|
'aria-haspopup': 'dialog';
|
|
28
30
|
/** ARIA attribute: whether the popover is currently open. */
|
|
29
31
|
'aria-expanded': boolean;
|
|
@@ -140,7 +142,9 @@ export interface PopoverProps extends Pick<BaseProps, 'xstyle' | 'className' | '
|
|
|
140
142
|
*/
|
|
141
143
|
closeButtonLabel?: string;
|
|
142
144
|
/**
|
|
143
|
-
* Whether to
|
|
145
|
+
* Whether to move focus into the popover when it opens. Keyboard activation
|
|
146
|
+
* focuses the first content control; pointer activation focuses the labeled
|
|
147
|
+
* dialog container so an action does not appear preselected.
|
|
144
148
|
* Set to `false` for inline showcases or documentation previews.
|
|
145
149
|
* @default true
|
|
146
150
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Popover.d.ts","sourceRoot":"","sources":["../../src/Popover/Popover.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"Popover.d.ts","sourceRoot":"","sources":["../../src/Popover/Popover.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,EAIZ,KAAK,YAAY,EACjB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAIf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAE5C,OAAO,KAAK,EAAC,cAAc,EAAE,cAAc,EAAC,MAAM,mBAAmB,CAAC;AAmCtE;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,2EAA2E;IAC3E,GAAG,EAAE,CAAC,EAAE,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IACtC,sHAAsH;IACtH,OAAO,EAAE,CAAC,KAAK,CAAC,EAAE;QAAC,MAAM,EAAE,MAAM,CAAA;KAAC,KAAK,IAAI,CAAC;IAC5C,0EAA0E;IAC1E,eAAe,EAAE,QAAQ,CAAC;IAC1B,6DAA6D;IAC7D,eAAe,EAAE,OAAO,CAAC;IACzB,4DAA4D;IAC5D,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,YAAa,SAAQ,IAAI,CACxC,SAAS,EACT,QAAQ,GAAG,WAAW,GAAG,OAAO,CACjC;IACC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,QAAQ,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,KAAK,EAAE,yBAAyB,KAAK,SAAS,CAAC,CAAC;IAEzE;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IAEzC;;OAEG;IACH,OAAO,EAAE,SAAS,CAAC;IAEnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,cAAc,CAAC;IAE3B;;;OAGG;IACH,SAAS,CAAC,EAAE,cAAc,CAAC;IAE3B;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;OAEG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAEzC;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAExB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAEzB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAsGD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,OAAO,CAAC,EACtB,QAAQ,EACR,SAAS,EACT,OAAO,EACP,SAAmB,EACnB,SAAmB,EACnB,MAAM,EACN,YAAY,EACZ,SAAgB,EAChB,KAAK,EACL,KAAK,EACL,IAAe,EACf,OAAO,EACP,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,eAAsB,EACtB,gBAAuB,EACvB,MAAM,EACN,SAAS,EACT,KAAK,EACL,aAAa,EAAE,MAAM,GACtB,EAAE,YAAY,GAAG,YAAY,CAmX7B;yBAzYe,OAAO"}
|
package/dist/Popover/Popover.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file Popover.tsx
|
|
7
|
-
* @input Uses React
|
|
8
|
-
* @output Exports Popover
|
|
7
|
+
* @input Uses React layout measurement and the usePopover hook
|
|
8
|
+
* @output Exports Popover with viewport fitting and conditional overflow
|
|
9
9
|
* @position Layer component; declarative wrapper around usePopover hook
|
|
10
10
|
*
|
|
11
11
|
* For hover-triggered overlays, use HoverCard instead.
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
* - /apps/storybook/stories/Popover.stories.tsx
|
|
17
17
|
* - /packages/cli/assets/templates/blocks/components/Popover/ (showcase blocks)
|
|
18
18
|
*/
|
|
19
|
-
import React, { useCallback, useRef } from 'react';
|
|
19
|
+
import React, { useCallback, useRef, useState } from 'react';
|
|
20
20
|
import { useIsomorphicLayoutEffect } from "../hooks/useIsomorphicLayoutEffect.js";
|
|
21
21
|
import * as stylex from '@stylexjs/stylex';
|
|
22
22
|
import { devWarn } from "../utils/devWarning.js";
|
|
23
|
-
import {
|
|
23
|
+
import { usePopoverInternal } from "./usePopover.js";
|
|
24
24
|
import { layerAnimations } from "../Layer/layerAnimations.stylex.js";
|
|
25
25
|
import "../theme/tokens.stylex.js";
|
|
26
26
|
import { spacingVars } from "../theme/tokens.stylex.js";
|
|
@@ -31,6 +31,14 @@ import { InteractiveRoleContext } from "../InteractiveRoleContext/InteractiveRol
|
|
|
31
31
|
// =============================================================================
|
|
32
32
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
33
33
|
const BUTTON_SELECTOR = 'button, [role="button"]';
|
|
34
|
+
const POPOVER_VIEWPORT_GUTTER = spacingVars['--spacing-4'];
|
|
35
|
+
const POPOVER_MAX_INLINE_SIZE = `calc(100vi - max(${POPOVER_VIEWPORT_GUTTER}, env(safe-area-inset-left, 0px)) - max(${POPOVER_VIEWPORT_GUTTER}, env(safe-area-inset-right, 0px)))`;
|
|
36
|
+
const POPOVER_MAX_INLINE_SIZE_FALLBACK = `calc(100vw - ${POPOVER_VIEWPORT_GUTTER} - ${POPOVER_VIEWPORT_GUTTER})`;
|
|
37
|
+
const POPOVER_MAX_BLOCK_SIZE = `calc(100dvb - max(${POPOVER_VIEWPORT_GUTTER}, env(safe-area-inset-top, 0px)) - max(${POPOVER_VIEWPORT_GUTTER}, env(safe-area-inset-bottom, 0px)))`;
|
|
38
|
+
const POPOVER_MAX_BLOCK_SIZE_FALLBACK = `calc(100vh - ${POPOVER_VIEWPORT_GUTTER} - ${POPOVER_VIEWPORT_GUTTER})`;
|
|
39
|
+
const POPOVER_POSITION_AREA_MAX_INLINE_SIZE = `calc(100% - max(${POPOVER_VIEWPORT_GUTTER}, env(safe-area-inset-left, 0px), env(safe-area-inset-right, 0px)))`;
|
|
40
|
+
const POPOVER_POSITION_AREA_MAX_INLINE_SIZE_FALLBACK = `calc(100% - ${POPOVER_VIEWPORT_GUTTER})`;
|
|
41
|
+
const POPOVER_INLINE_EDGE_GUTTER = `max(${POPOVER_VIEWPORT_GUTTER}, env(safe-area-inset-left, 0px), env(safe-area-inset-right, 0px))`;
|
|
34
42
|
|
|
35
43
|
/**
|
|
36
44
|
* Find the trigger button inside a container element.
|
|
@@ -57,6 +65,54 @@ function findTriggerButton(el) {
|
|
|
57
65
|
// =============================================================================
|
|
58
66
|
|
|
59
67
|
const styles = {
|
|
68
|
+
viewportFit: {
|
|
69
|
+
kB7OPa: "x9f619",
|
|
70
|
+
kskxy: "xtm2925",
|
|
71
|
+
$$css: true
|
|
72
|
+
},
|
|
73
|
+
viewportAligned: {
|
|
74
|
+
ks0D6T: "xbd96yr",
|
|
75
|
+
$$css: true
|
|
76
|
+
},
|
|
77
|
+
viewportStart: {
|
|
78
|
+
k71WvV: "xxeicf8",
|
|
79
|
+
$$css: true
|
|
80
|
+
},
|
|
81
|
+
viewportEnd: {
|
|
82
|
+
keTefX: "x47fqr9",
|
|
83
|
+
$$css: true
|
|
84
|
+
},
|
|
85
|
+
viewportBlockStart: {
|
|
86
|
+
k1K539: "x1kps3mo",
|
|
87
|
+
$$css: true
|
|
88
|
+
},
|
|
89
|
+
viewportBlockEnd: {
|
|
90
|
+
keoZOQ: "xsoubt3",
|
|
91
|
+
$$css: true
|
|
92
|
+
},
|
|
93
|
+
viewportCentered: {
|
|
94
|
+
keTefX: "x47fqr9",
|
|
95
|
+
k71WvV: "xxeicf8",
|
|
96
|
+
ks0D6T: "xs2v1xk",
|
|
97
|
+
$$css: true
|
|
98
|
+
},
|
|
99
|
+
viewportBlockCentered: {
|
|
100
|
+
keoZOQ: "xsoubt3",
|
|
101
|
+
k1K539: "x1kps3mo",
|
|
102
|
+
ks0D6T: "xs2v1xk",
|
|
103
|
+
$$css: true
|
|
104
|
+
},
|
|
105
|
+
surfaceViewportFit: {
|
|
106
|
+
kB7OPa: "x9f619",
|
|
107
|
+
ks0D6T: "xs2v1xk",
|
|
108
|
+
kskxy: "xtm2925",
|
|
109
|
+
$$css: true
|
|
110
|
+
},
|
|
111
|
+
surfaceScrollable: {
|
|
112
|
+
kVQacm: "xysyzu8",
|
|
113
|
+
kZeWKH: "xish69e",
|
|
114
|
+
$$css: true
|
|
115
|
+
},
|
|
60
116
|
contentPadding: {
|
|
61
117
|
kLKAdn: "x1vlblms",
|
|
62
118
|
kGO01o: "xvmdzux",
|
|
@@ -70,8 +126,12 @@ const styles = {
|
|
|
70
126
|
}, {
|
|
71
127
|
"--x-width": (val => typeof val === "number" ? val + "px" : val != null ? val : undefined)(typeof width === 'number' ? `${width}px` : width)
|
|
72
128
|
}],
|
|
73
|
-
|
|
74
|
-
k7Eaqz: "
|
|
129
|
+
matchTriggerAligned: {
|
|
130
|
+
k7Eaqz: "x1ks9poc",
|
|
131
|
+
$$css: true
|
|
132
|
+
},
|
|
133
|
+
matchTriggerCentered: {
|
|
134
|
+
k7Eaqz: "xagy28z",
|
|
75
135
|
$$css: true
|
|
76
136
|
}
|
|
77
137
|
};
|
|
@@ -139,6 +199,8 @@ export function Popover({
|
|
|
139
199
|
'data-testid': testId
|
|
140
200
|
}) {
|
|
141
201
|
const wrapperRef = useRef(null);
|
|
202
|
+
const measurementFrameRef = useRef(null);
|
|
203
|
+
const [hasOverflow, setHasOverflow] = useState(false);
|
|
142
204
|
const isControlled = isOpen !== undefined;
|
|
143
205
|
const handlePopoverShow = useCallback(() => {
|
|
144
206
|
onOpenChange?.(true);
|
|
@@ -146,7 +208,7 @@ export function Popover({
|
|
|
146
208
|
const handlePopoverHide = useCallback(() => {
|
|
147
209
|
onOpenChange?.(false);
|
|
148
210
|
}, [onOpenChange]);
|
|
149
|
-
const popover =
|
|
211
|
+
const popover = usePopoverInternal({
|
|
150
212
|
dialogLabel: label,
|
|
151
213
|
role,
|
|
152
214
|
isModal,
|
|
@@ -159,21 +221,82 @@ export function Popover({
|
|
|
159
221
|
// it is the element the `popover` theme target has to sit on — a target on
|
|
160
222
|
// the content div inside it styles a box that paints nothing.
|
|
161
223
|
surfaceTarget: 'popover',
|
|
162
|
-
xstyle: [styles.contentPadding, xstyle],
|
|
224
|
+
xstyle: [styles.contentPadding, styles.surfaceViewportFit, hasOverflow && styles.surfaceScrollable, xstyle],
|
|
163
225
|
className,
|
|
164
226
|
style,
|
|
165
227
|
onShow: handlePopoverShow,
|
|
166
228
|
onHide: handlePopoverHide
|
|
167
229
|
});
|
|
230
|
+
const measureOverflow = useCallback(() => {
|
|
231
|
+
const surface = popover.contentRef.current;
|
|
232
|
+
if (!surface) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const nextHasOverflow = surface.scrollHeight > surface.clientHeight + 1 || surface.scrollWidth > surface.clientWidth + 1;
|
|
236
|
+
// eslint-disable-next-line @eslint-react/set-state-in-effect -- DOM overflow measurement controls whether this surface becomes a scroll container
|
|
237
|
+
setHasOverflow(current => current === nextHasOverflow ? current : nextHasOverflow);
|
|
238
|
+
}, [popover.contentRef]);
|
|
239
|
+
const scheduleOverflowMeasurement = useCallback(() => {
|
|
240
|
+
if (measurementFrameRef.current != null) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
measurementFrameRef.current = window.requestAnimationFrame(() => {
|
|
244
|
+
measurementFrameRef.current = null;
|
|
245
|
+
measureOverflow();
|
|
246
|
+
});
|
|
247
|
+
}, [measureOverflow]);
|
|
248
|
+
useIsomorphicLayoutEffect(() => {
|
|
249
|
+
if (!popover.isOpen) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const surface = popover.contentRef.current;
|
|
253
|
+
if (!surface) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
measureOverflow();
|
|
257
|
+
const resizeObserver = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(scheduleOverflowMeasurement);
|
|
258
|
+
const mutationObserver = typeof MutationObserver === 'undefined' ? null : new MutationObserver(scheduleOverflowMeasurement);
|
|
259
|
+
resizeObserver?.observe(surface);
|
|
260
|
+
mutationObserver?.observe(surface, {
|
|
261
|
+
childList: true,
|
|
262
|
+
characterData: true,
|
|
263
|
+
subtree: true
|
|
264
|
+
});
|
|
265
|
+
surface.addEventListener('load', scheduleOverflowMeasurement, true);
|
|
266
|
+
window.addEventListener('resize', scheduleOverflowMeasurement);
|
|
267
|
+
window.visualViewport?.addEventListener('resize', scheduleOverflowMeasurement);
|
|
268
|
+
return () => {
|
|
269
|
+
resizeObserver?.disconnect();
|
|
270
|
+
mutationObserver?.disconnect();
|
|
271
|
+
surface.removeEventListener('load', scheduleOverflowMeasurement, true);
|
|
272
|
+
window.removeEventListener('resize', scheduleOverflowMeasurement);
|
|
273
|
+
window.visualViewport?.removeEventListener('resize', scheduleOverflowMeasurement);
|
|
274
|
+
if (measurementFrameRef.current != null) {
|
|
275
|
+
window.cancelAnimationFrame(measurementFrameRef.current);
|
|
276
|
+
measurementFrameRef.current = null;
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
}, [measureOverflow, popover.isOpen, scheduleOverflowMeasurement]);
|
|
280
|
+
useIsomorphicLayoutEffect(() => {
|
|
281
|
+
if (!popover.isOpen) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
scheduleOverflowMeasurement();
|
|
285
|
+
}, [content, popover.isOpen, scheduleOverflowMeasurement]);
|
|
168
286
|
|
|
169
287
|
// Shared handler for click events on the trigger button.
|
|
170
|
-
const handleTriggerClick = useCallback(
|
|
288
|
+
const handleTriggerClick = useCallback(event => {
|
|
171
289
|
if (!isEnabled) {
|
|
172
290
|
return;
|
|
173
291
|
}
|
|
174
|
-
//
|
|
175
|
-
|
|
176
|
-
|
|
292
|
+
// Pointer/touch activation should not make the first action look
|
|
293
|
+
// preselected. Keep focus inside the modal dialog by focusing its
|
|
294
|
+
// labeled container; keyboard and AT activation still focus the first
|
|
295
|
+
// content control and expose the expected focus ring.
|
|
296
|
+
popover.toggleWithOptions({
|
|
297
|
+
focusTarget: role === 'dialog' && event != null && event.detail > 0 ? 'container' : 'first'
|
|
298
|
+
});
|
|
299
|
+
}, [isEnabled, popover, role]);
|
|
177
300
|
|
|
178
301
|
// Shared handler for keydown events on role="button" elements.
|
|
179
302
|
// Native <button> synthesizes click on Enter/Space, but role="button"
|
|
@@ -289,7 +412,9 @@ export function Popover({
|
|
|
289
412
|
}, [isOpen, isControlled, popover]);
|
|
290
413
|
|
|
291
414
|
// Determine popover xstyle
|
|
292
|
-
const
|
|
415
|
+
const popoverSizeXstyle = width ? styles.customWidth(width) : alignment === 'center' ? styles.matchTriggerCentered : styles.matchTriggerAligned;
|
|
416
|
+
const isSidePlacement = placement === 'start' || placement === 'end';
|
|
417
|
+
const popoverViewportXstyle = alignment === 'center' ? isSidePlacement ? styles.viewportBlockCentered : styles.viewportCentered : [styles.viewportAligned, isSidePlacement ? alignment === 'start' ? styles.viewportBlockStart : styles.viewportBlockEnd : alignment === 'start' ? styles.viewportStart : styles.viewportEnd];
|
|
293
418
|
|
|
294
419
|
// Sibling mode: render only the popover (no wrapper needed)
|
|
295
420
|
if (anchorRef && children == null) {
|
|
@@ -301,7 +426,7 @@ export function Popover({
|
|
|
301
426
|
placement,
|
|
302
427
|
alignment,
|
|
303
428
|
offset: spacingVars['--spacing-1'],
|
|
304
|
-
xstyle: [
|
|
429
|
+
xstyle: [styles.viewportFit, popoverViewportXstyle, popoverSizeXstyle, layerAnimations[placement]]
|
|
305
430
|
})
|
|
306
431
|
});
|
|
307
432
|
}
|
|
@@ -324,7 +449,7 @@ export function Popover({
|
|
|
324
449
|
placement,
|
|
325
450
|
alignment,
|
|
326
451
|
offset: spacingVars['--spacing-1'],
|
|
327
|
-
xstyle: [
|
|
452
|
+
xstyle: [styles.viewportFit, popoverViewportXstyle, popoverSizeXstyle, layerAnimations[placement]]
|
|
328
453
|
})]
|
|
329
454
|
});
|
|
330
455
|
}
|
|
@@ -347,7 +472,7 @@ export function Popover({
|
|
|
347
472
|
placement,
|
|
348
473
|
alignment,
|
|
349
474
|
offset: spacingVars['--spacing-1'],
|
|
350
|
-
xstyle: [
|
|
475
|
+
xstyle: [styles.viewportFit, popoverViewportXstyle, popoverSizeXstyle, layerAnimations[placement]]
|
|
351
476
|
})]
|
|
352
477
|
});
|
|
353
478
|
}
|
|
@@ -201,6 +201,10 @@ export interface UsePopoverReturn {
|
|
|
201
201
|
}
|
|
202
202
|
interface InternalUsePopoverReturn extends UsePopoverReturn {
|
|
203
203
|
wasJustDismissed: () => boolean;
|
|
204
|
+
toggleWithOptions: (options?: {
|
|
205
|
+
skipAutoFocus?: boolean;
|
|
206
|
+
focusTarget?: 'first' | 'container';
|
|
207
|
+
}) => void;
|
|
204
208
|
}
|
|
205
209
|
export declare function usePopover(options?: UsePopoverOptions): UsePopoverReturn;
|
|
206
210
|
/** @internal Used by trigger components; not exported from package barrels. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePopover.d.ts","sourceRoot":"","sources":["../../src/Popover/usePopover.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,EAAiC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAE5E,OAAO,EAAmB,KAAK,kBAAkB,EAAC,MAAM,mBAAmB,CAAC;AAG5E,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"usePopover.d.ts","sourceRoot":"","sources":["../../src/Popover/usePopover.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,EAAiC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAE5E,OAAO,EAAmB,KAAK,kBAAkB,EAAC,MAAM,mBAAmB,CAAC;AAG5E,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,kBAAkB,CAAC;AA8FnD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IAEpB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IAEpB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IAEtB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAE5B;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B;;;;;;;;;;OAUG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;;;;;;;;;OAYG;IACH,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAEzB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;;;;;;;OASG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;;;;;;;;;;;OAaG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,UAAU,EAAE,CAAC,EAAE,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IAE7C;;;;OAIG;IACH,UAAU,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAEnD;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;;OAIG;IACH,IAAI,EAAE,CAAC,OAAO,CAAC,EAAE;QAAC,aAAa,CAAC,EAAE,OAAO,CAAA;KAAC,KAAK,IAAI,CAAC;IAEpD;;OAEG;IACH,IAAI,EAAE,MAAM,IAAI,CAAC;IAEjB;;OAEG;IACH,MAAM,EAAE,MAAM,IAAI,CAAC;IAEnB;;OAEG;IACH,MAAM,EAAE,OAAO,CAAC;IAEhB;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;;;;;;;;;;OAWG;IACH,MAAM,EAAE,CAAC,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,kBAAkB,KAAK,SAAS,CAAC;IAEvE;;OAEG;IACH,YAAY,EAAE;QACZ,eAAe,EAAE,QAAQ,GAAG,MAAM,CAAC;QACnC,eAAe,EAAE,OAAO,CAAC;QACzB,eAAe,EAAE,MAAM,CAAC;KACzB,CAAC;CACH;AAED,UAAU,wBAAyB,SAAQ,gBAAgB;IACzD,gBAAgB,EAAE,MAAM,OAAO,CAAC;IAChC,iBAAiB,EAAE,CAAC,OAAO,CAAC,EAAE;QAC5B,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB,WAAW,CAAC,EAAE,OAAO,GAAG,WAAW,CAAC;KACrC,KAAK,IAAI,CAAC;CACZ;AA6QD,wBAAgB,UAAU,CAAC,OAAO,GAAE,iBAAsB,GAAG,gBAAgB,CAO5E;AAED,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,iBAAsB,GAC9B,wBAAwB,CAE1B"}
|