@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.
Files changed (146) hide show
  1. package/dist/BottomSheet/BottomSheet.d.ts +3 -1
  2. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  3. package/dist/BottomSheet/BottomSheet.js +4 -3
  4. package/dist/BottomSheet/BottomSheetEdgeTint.d.ts +2 -2
  5. package/dist/BottomSheet/BottomSheetEdgeTint.d.ts.map +1 -1
  6. package/dist/BottomSheet/BottomSheetEdgeTint.js +7 -10
  7. package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
  8. package/dist/BottomSheet/BottomSheetSwitcher.js +1 -1
  9. package/dist/Button/Button.d.ts +2 -1
  10. package/dist/Button/Button.d.ts.map +1 -1
  11. package/dist/Button/Button.js +11 -4
  12. package/dist/Carousel/Carousel.d.ts.map +1 -1
  13. package/dist/Carousel/Carousel.js +2 -2
  14. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  15. package/dist/Chat/ChatToolCalls.js +34 -17
  16. package/dist/ContextMenu/ContextMenu.d.ts +14 -3
  17. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  18. package/dist/ContextMenu/ContextMenu.js +149 -26
  19. package/dist/ContextMenu/index.d.ts +1 -0
  20. package/dist/ContextMenu/index.d.ts.map +1 -1
  21. package/dist/DropdownMenu/DropdownMenu.d.ts +24 -5
  22. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  23. package/dist/DropdownMenu/DropdownMenu.js +340 -26
  24. package/dist/DropdownMenu/DropdownMenuContext.d.ts +1 -1
  25. package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
  26. package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
  27. package/dist/DropdownMenu/DropdownMenuItem.js +1 -1
  28. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +6 -2
  29. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  30. package/dist/DropdownMenu/DropdownMenuSubMenu.js +43 -14
  31. package/dist/DropdownMenu/MenuBottomSheet.d.ts +20 -0
  32. package/dist/DropdownMenu/MenuBottomSheet.d.ts.map +1 -0
  33. package/dist/DropdownMenu/MenuBottomSheet.js +36 -0
  34. package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts +10 -0
  35. package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts.map +1 -0
  36. package/dist/DropdownMenu/MenuBottomSheetActionList.js +111 -0
  37. package/dist/DropdownMenu/index.d.ts +2 -1
  38. package/dist/DropdownMenu/index.d.ts.map +1 -1
  39. package/dist/DropdownMenu/menuWidth.d.ts +14 -0
  40. package/dist/DropdownMenu/menuWidth.d.ts.map +1 -0
  41. package/dist/DropdownMenu/menuWidth.js +35 -0
  42. package/dist/DropdownMenu/useMenuOverflow.d.ts +10 -0
  43. package/dist/DropdownMenu/useMenuOverflow.d.ts.map +1 -0
  44. package/dist/DropdownMenu/useMenuOverflow.js +60 -0
  45. package/dist/MoreMenu/MoreMenu.d.ts +8 -2
  46. package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
  47. package/dist/MoreMenu/MoreMenu.js +2 -0
  48. package/dist/MultiSelector/MultiSelector.d.ts +11 -1
  49. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  50. package/dist/MultiSelector/MultiSelector.js +97 -68
  51. package/dist/MultiSelector/index.d.ts +1 -1
  52. package/dist/MultiSelector/index.d.ts.map +1 -1
  53. package/dist/Popover/Popover.d.ts +10 -6
  54. package/dist/Popover/Popover.d.ts.map +1 -1
  55. package/dist/Popover/Popover.js +141 -16
  56. package/dist/Popover/usePopover.d.ts +4 -0
  57. package/dist/Popover/usePopover.d.ts.map +1 -1
  58. package/dist/Popover/usePopover.js +52 -5
  59. package/dist/Selector/Selector.d.ts +10 -0
  60. package/dist/Selector/Selector.d.ts.map +1 -1
  61. package/dist/Selector/Selector.js +106 -80
  62. package/dist/Selector/SelectorBottomSheet.d.ts +21 -0
  63. package/dist/Selector/SelectorBottomSheet.d.ts.map +1 -0
  64. package/dist/Selector/SelectorBottomSheet.js +83 -0
  65. package/dist/Selector/index.d.ts +1 -1
  66. package/dist/Selector/index.d.ts.map +1 -1
  67. package/dist/Selector/selectorPresentation.stylex.d.ts +16 -0
  68. package/dist/Selector/selectorPresentation.stylex.d.ts.map +1 -0
  69. package/dist/Selector/selectorPresentation.stylex.js +21 -0
  70. package/dist/Selector/useSelectorPresentation.d.ts +32 -0
  71. package/dist/Selector/useSelectorPresentation.d.ts.map +1 -0
  72. package/dist/Selector/useSelectorPresentation.js +90 -0
  73. package/dist/Toast/useToastGesture.d.ts.map +1 -1
  74. package/dist/Toast/useToastGesture.js +46 -9
  75. package/dist/astryx.css +19 -1
  76. package/dist/hooks/useAdaptivePresentation.d.ts +5 -0
  77. package/dist/hooks/useAdaptivePresentation.d.ts.map +1 -0
  78. package/dist/hooks/useAdaptivePresentation.js +19 -0
  79. package/dist/hooks/useFocusReturnVisibility.d.ts +7 -0
  80. package/dist/hooks/useFocusReturnVisibility.d.ts.map +1 -0
  81. package/dist/hooks/useFocusReturnVisibility.js +35 -0
  82. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  83. package/dist/hooks/useFocusTrap.js +12 -0
  84. package/locales/en.json +20 -0
  85. package/locales/pseudo.json +15 -0
  86. package/package.json +2 -2
  87. package/src/BottomSheet/BottomSheet.doc.mjs +6 -0
  88. package/src/BottomSheet/BottomSheet.test.tsx +29 -0
  89. package/src/BottomSheet/BottomSheet.tsx +7 -4
  90. package/src/BottomSheet/BottomSheetEdgeTint.test.tsx +4 -6
  91. package/src/BottomSheet/BottomSheetEdgeTint.tsx +7 -10
  92. package/src/BottomSheet/BottomSheetSwitcher.tsx +1 -2
  93. package/src/Button/Button.doc.mjs +56 -0
  94. package/src/Button/Button.test.tsx +10 -0
  95. package/src/Button/Button.tsx +13 -5
  96. package/src/ButtonGroup/ButtonGroup.doc.mjs +47 -0
  97. package/src/Carousel/Carousel.test.tsx +83 -0
  98. package/src/Carousel/Carousel.tsx +8 -2
  99. package/src/Chat/ChatToolCalls.test.tsx +44 -0
  100. package/src/Chat/ChatToolCalls.tsx +36 -15
  101. package/src/ContextMenu/ContextMenu.doc.mjs +13 -1
  102. package/src/ContextMenu/ContextMenu.test.tsx +131 -1
  103. package/src/ContextMenu/ContextMenu.tsx +199 -32
  104. package/src/ContextMenu/index.ts +1 -0
  105. package/src/DropdownMenu/DropdownMenu.doc.mjs +201 -40
  106. package/src/DropdownMenu/DropdownMenu.test.tsx +518 -7
  107. package/src/DropdownMenu/DropdownMenu.tsx +459 -28
  108. package/src/DropdownMenu/DropdownMenuContext.tsx +1 -1
  109. package/src/DropdownMenu/DropdownMenuItem.tsx +2 -0
  110. package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +4 -2
  111. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +124 -0
  112. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +72 -17
  113. package/src/DropdownMenu/MenuBottomSheet.tsx +46 -0
  114. package/src/DropdownMenu/MenuBottomSheetActionList.tsx +141 -0
  115. package/src/DropdownMenu/index.ts +2 -0
  116. package/src/DropdownMenu/menuWidth.ts +51 -0
  117. package/src/DropdownMenu/useMenuOverflow.ts +77 -0
  118. package/src/IconButton/IconButton.doc.mjs +38 -0
  119. package/src/MoreMenu/MoreMenu.doc.mjs +17 -0
  120. package/src/MoreMenu/MoreMenu.test.tsx +47 -0
  121. package/src/MoreMenu/MoreMenu.tsx +13 -1
  122. package/src/MultiSelector/MultiSelector.doc.mjs +16 -0
  123. package/src/MultiSelector/MultiSelector.test.tsx +127 -0
  124. package/src/MultiSelector/MultiSelector.tsx +121 -73
  125. package/src/MultiSelector/index.ts +1 -0
  126. package/src/Popover/Popover.doc.mjs +11 -11
  127. package/src/Popover/Popover.test.tsx +397 -2
  128. package/src/Popover/Popover.tsx +228 -21
  129. package/src/Popover/usePopover.tsx +92 -15
  130. package/src/ProgressBar/ProgressBar.spec.md +208 -0
  131. package/src/SegmentedControl/SegmentedControl.doc.mjs +56 -0
  132. package/src/Selector/Selector.doc.mjs +12 -0
  133. package/src/Selector/Selector.test.tsx +135 -0
  134. package/src/Selector/Selector.tsx +141 -82
  135. package/src/Selector/SelectorBottomSheet.tsx +97 -0
  136. package/src/Selector/index.ts +1 -0
  137. package/src/Selector/selectorPresentation.stylex.ts +20 -0
  138. package/src/Selector/useSelectorPresentation.ts +147 -0
  139. package/src/Toast/ToastViewport.test.tsx +118 -4
  140. package/src/Toast/useToastGesture.ts +61 -8
  141. package/src/ToggleButton/ToggleButton.doc.mjs +56 -0
  142. package/src/hooks/useAdaptivePresentation.ts +35 -0
  143. package/src/hooks/useFocusReturnVisibility.ts +45 -0
  144. package/src/hooks/useFocusTrap.test.tsx +40 -0
  145. package/src/hooks/useFocusTrap.ts +13 -0
  146. 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 popover = usePopoverInternal({
438
- hasLightDismiss: true,
441
+ const surface = useSelectorPresentation({
442
+ presentation,
439
443
  onHide: handleLayerHide,
440
- hasCloseButton: false,
441
- hasAutoFocus: false,
442
- // The popup's own role="listbox" is the exposed semantics; the trigger
443
- // keeps DOM focus, so wrapping it in a modal dialog would misrepresent it.
444
- role: 'none',
445
- // The theme target belongs on the SURFACE that paints the popup, which
446
- // `usePopover` owns not on the scrolling list inside it.
447
- surfaceTarget: 'multi-selector-popup'
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
- popover.show();
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 = popover.isOpen && !isLoading && searchQuery === '' && selectableItems.length === 0;
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
- }, [popover.isOpen, isLoading, searchQuery, selectableItems.length, emptyAnnouncement, announce]);
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: popover.wasJustDismissed,
596
+ wasJustDismissed: surface.wasJustDismissed,
586
597
  selectableItems: sortedItems,
587
598
  isDisabled,
588
- isOpen: popover.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
- popover.show();
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
- }, [popover, hasSearch, optimisticValue]),
601
- onClose: popover.hide,
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 (!popover.isOpen || highlightedIndex < 0) {
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
- }, [popover.isOpen, highlightedIndex, getItemId]);
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": popover.isOpen,
720
+ "aria-expanded": surface.isOpen,
710
721
  "aria-controls": listboxId,
711
722
  "aria-autocomplete": "list",
712
- "aria-activedescendant": popover.isOpen && highlightedIndex >= 0 ? getItemId(highlightedIndex) : undefined,
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, popover.isOpen, highlightedIndex, getItemId, t]);
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": "listbox",
964
- "aria-expanded": popover.isOpen,
1023
+ "aria-haspopup": surface.activePresentation === 'bottom-sheet' ? 'dialog' : 'listbox',
1024
+ "aria-expanded": surface.isOpen,
965
1025
  "aria-controls": listboxId,
966
- "aria-activedescendant": !hasSearch && popover.isOpen && highlightedIndex >= 0 ? getItemId(highlightedIndex) : undefined,
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, popover.isOpen && styles.triggerIconOpen]
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: popover.isOpen ? 'expanded' : 'collapsed'
1105
+ state: surface.isOpen ? 'expanded' : 'collapsed'
1042
1106
  })
1043
1107
  })]
1044
- }), popover.render(hasSearch ?
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, usePopover hook
4
- * @output Exports Popover component for click-triggered popovers
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: () => void;
26
- /** ARIA attribute: indicates the element triggers a dialog. */
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 auto-focus the first focusable element when the popover opens.
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,EAGZ,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;AA2BtE;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,2EAA2E;IAC3E,GAAG,EAAE,CAAC,EAAE,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IACtC,sCAAsC;IACtC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,+DAA+D;IAC/D,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;;;;OAIG;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;AAmCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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,CAkP7B;yBAxQe,OAAO"}
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"}
@@ -4,8 +4,8 @@
4
4
 
5
5
  /**
6
6
  * @file Popover.tsx
7
- * @input Uses React, usePopover hook
8
- * @output Exports Popover component for click-triggered popovers
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 { usePopover } from "./usePopover.js";
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
- matchTrigger: {
74
- k7Eaqz: "xrzjruh",
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 = usePopover({
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
- // `toggle` absorbs a click that belongs to its own light dismiss.
175
- popover.toggle();
176
- }, [isEnabled, popover]);
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 popoverXstyle = width ? styles.customWidth(width) : styles.matchTrigger;
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: [popoverXstyle, layerAnimations[placement]]
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: [popoverXstyle, layerAnimations[placement]]
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: [popoverXstyle, layerAnimations[placement]]
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;AAiEnD;;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;CACjC;AAqOD,wBAAgB,UAAU,CAAC,OAAO,GAAE,iBAAsB,GAAG,gBAAgB,CAG5E;AAED,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,iBAAsB,GAC9B,wBAAwB,CAE1B"}
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"}