@astryxdesign/core 0.7.7-canary.4a8a76e → 0.7.7-canary.4e495ec

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 (266) hide show
  1. package/dist/AppShell/useCloseMobileDrawerOnActivate.d.ts +13 -0
  2. package/dist/AppShell/useCloseMobileDrawerOnActivate.d.ts.map +1 -0
  3. package/dist/AppShell/useCloseMobileDrawerOnActivate.js +45 -0
  4. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  5. package/dist/BottomSheet/BottomSheet.js +79 -9
  6. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  7. package/dist/BottomSheet/BottomSheetPanel.js +1 -1
  8. package/dist/BottomSheet/bottomSheetStack.d.ts.map +1 -1
  9. package/dist/BottomSheet/bottomSheetStack.js +5 -4
  10. package/dist/Breadcrumbs/BreadcrumbItem.d.ts +2 -1
  11. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  12. package/dist/Breadcrumbs/BreadcrumbItem.js +7 -3
  13. package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
  14. package/dist/CheckboxList/CheckboxList.js +7 -1
  15. package/dist/CheckboxList/CheckboxListContext.d.ts +6 -0
  16. package/dist/CheckboxList/CheckboxListContext.d.ts.map +1 -1
  17. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  18. package/dist/CheckboxList/CheckboxListItem.js +10 -2
  19. package/dist/DateInput/DateInput.d.ts.map +1 -1
  20. package/dist/DateInput/DateInput.js +5 -2
  21. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  22. package/dist/DateTimeInput/DateTimeInput.js +5 -2
  23. package/dist/Dialog/Dialog.d.ts.map +1 -1
  24. package/dist/Dialog/Dialog.js +16 -14
  25. package/dist/Drawer/Drawer.d.ts.map +1 -1
  26. package/dist/Drawer/Drawer.js +15 -11
  27. package/dist/Drawer/useDrawerDialogPresence.d.ts.map +1 -1
  28. package/dist/Drawer/useDrawerDialogPresence.js +5 -1
  29. package/dist/Field/PanelSearchInput.d.ts +9 -1
  30. package/dist/Field/PanelSearchInput.d.ts.map +1 -1
  31. package/dist/Field/PanelSearchInput.js +3 -1
  32. package/dist/InfoTip/InfoTip.d.ts +2 -2
  33. package/dist/InfoTip/InfoTip.js +4 -4
  34. package/dist/Layer/layerAnimations.stylex.d.ts +7 -8
  35. package/dist/Layer/layerAnimations.stylex.d.ts.map +1 -1
  36. package/dist/Layer/layerAnimations.stylex.js +25 -20
  37. package/dist/Layer/useBackdropPress.d.ts +27 -0
  38. package/dist/Layer/useBackdropPress.d.ts.map +1 -0
  39. package/dist/Layer/useBackdropPress.js +53 -0
  40. package/dist/Layer/usePressPin.d.ts +3 -5
  41. package/dist/Layer/usePressPin.d.ts.map +1 -1
  42. package/dist/Layer/usePressPin.js +4 -6
  43. package/dist/MobileNav/MobileNav.js +0 -1
  44. package/dist/MultiSelector/hooks.d.ts.map +1 -1
  45. package/dist/MultiSelector/hooks.js +2 -4
  46. package/dist/Outline/Outline.d.ts +2 -4
  47. package/dist/Outline/Outline.d.ts.map +1 -1
  48. package/dist/Outline/Outline.js +4 -52
  49. package/dist/Outline/useOutlineFromMarkdown.d.ts.map +1 -1
  50. package/dist/Outline/useOutlineFromMarkdown.js +1 -0
  51. package/dist/Outline/useScrollSpy.js +3 -3
  52. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  53. package/dist/PowerSearch/PowerSearch.js +134 -179
  54. package/dist/RadioList/RadioList.d.ts.map +1 -1
  55. package/dist/RadioList/RadioList.js +5 -0
  56. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  57. package/dist/Resizable/ResizeHandle.js +118 -260
  58. package/dist/Resizable/resizeGesture.d.ts +91 -0
  59. package/dist/Resizable/resizeGesture.d.ts.map +1 -0
  60. package/dist/Resizable/resizeGesture.js +251 -0
  61. package/dist/SegmentedControl/SegmentedControl.d.ts +3 -1
  62. package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
  63. package/dist/SegmentedControl/SegmentedControl.js +115 -8
  64. package/dist/SegmentedControl/SegmentedControlContext.d.ts +6 -0
  65. package/dist/SegmentedControl/SegmentedControlContext.d.ts.map +1 -1
  66. package/dist/SegmentedControl/SegmentedControlItem.d.ts +1 -0
  67. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  68. package/dist/SegmentedControl/SegmentedControlItem.js +8 -1
  69. package/dist/SegmentedControl/useSegmentedSelectionMotion.d.ts +357 -0
  70. package/dist/SegmentedControl/useSegmentedSelectionMotion.d.ts.map +1 -0
  71. package/dist/SegmentedControl/useSegmentedSelectionMotion.js +1349 -0
  72. package/dist/SegmentedControl/useSegmentedTouchTracking.d.ts +70 -0
  73. package/dist/SegmentedControl/useSegmentedTouchTracking.d.ts.map +1 -0
  74. package/dist/SegmentedControl/useSegmentedTouchTracking.js +264 -0
  75. package/dist/Selector/Selector.d.ts.map +1 -1
  76. package/dist/Selector/Selector.js +96 -67
  77. package/dist/Selector/optionShape.d.ts +24 -0
  78. package/dist/Selector/optionShape.d.ts.map +1 -0
  79. package/dist/Selector/optionShape.js +48 -0
  80. package/dist/Selector/selectableOptionEntries.d.ts +20 -0
  81. package/dist/Selector/selectableOptionEntries.d.ts.map +1 -0
  82. package/dist/Selector/selectableOptionEntries.js +38 -0
  83. package/dist/Selector/utils.d.ts +2 -17
  84. package/dist/Selector/utils.d.ts.map +1 -1
  85. package/dist/Selector/utils.js +3 -51
  86. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  87. package/dist/SideNav/SideNavItem.js +3 -9
  88. package/dist/Switch/switchTouch.d.ts +0 -7
  89. package/dist/Switch/switchTouch.d.ts.map +1 -1
  90. package/dist/Switch/switchTouch.js +2 -10
  91. package/dist/Switch/useSwitchTouch.d.ts +2 -11
  92. package/dist/Switch/useSwitchTouch.d.ts.map +1 -1
  93. package/dist/Switch/useSwitchTouch.js +26 -85
  94. package/dist/Token/Token.d.ts +2 -2
  95. package/dist/Token/Token.d.ts.map +1 -1
  96. package/dist/Token/Token.js +12 -4
  97. package/dist/Token/TokenNamingContext.d.ts +14 -0
  98. package/dist/Token/TokenNamingContext.d.ts.map +1 -0
  99. package/dist/Token/TokenNamingContext.js +23 -0
  100. package/dist/Tokenizer/Tokenizer.d.ts +46 -59
  101. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  102. package/dist/Tokenizer/Tokenizer.js +35 -58
  103. package/dist/Tokenizer/TokenizerBottomSheet.d.ts +25 -28
  104. package/dist/Tokenizer/TokenizerBottomSheet.d.ts.map +1 -1
  105. package/dist/Tokenizer/TokenizerBottomSheet.js +157 -54
  106. package/dist/Tokenizer/TokenizerItemRemovableContext.d.ts.map +1 -1
  107. package/dist/Tokenizer/TokenizerItemRemovableContext.js +5 -3
  108. package/dist/Tooltip/Tooltip.d.ts +25 -30
  109. package/dist/Tooltip/Tooltip.d.ts.map +1 -1
  110. package/dist/Tooltip/Tooltip.js +24 -5
  111. package/dist/Tooltip/index.d.ts +1 -1
  112. package/dist/Tooltip/index.d.ts.map +1 -1
  113. package/dist/Tooltip/useTooltip.d.ts +28 -29
  114. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  115. package/dist/Tooltip/useTooltip.js +20 -8
  116. package/dist/TopNav/TopNavItem.d.ts.map +1 -1
  117. package/dist/TopNav/TopNavItem.js +4 -7
  118. package/dist/TopNav/TopNavMegaMenuItem.d.ts.map +1 -1
  119. package/dist/TopNav/TopNavMegaMenuItem.js +4 -7
  120. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  121. package/dist/TopNav/TopNavMenu.js +4 -7
  122. package/dist/astryx.css +76 -5
  123. package/dist/gestures/GestureHost.d.ts.map +1 -1
  124. package/dist/gestures/GestureHost.js +53 -5
  125. package/dist/hooks/actionGroupTracker.d.ts +3 -3
  126. package/dist/hooks/actionGroupTracker.d.ts.map +1 -1
  127. package/dist/hooks/actionGroupTracker.js +6 -53
  128. package/dist/hooks/useHighlightedOptionScroll.d.ts +11 -1
  129. package/dist/hooks/useHighlightedOptionScroll.d.ts.map +1 -1
  130. package/dist/hooks/useHighlightedOptionScroll.js +112 -11
  131. package/dist/hooks/useMediaQuery.d.ts +2 -1
  132. package/dist/hooks/useMediaQuery.d.ts.map +1 -1
  133. package/dist/hooks/useMediaQuery.js +7 -4
  134. package/dist/hooks/useMenuPress.d.ts.map +1 -1
  135. package/dist/hooks/useMenuPress.js +4 -2
  136. package/dist/i18n/generated-english/PowerSearch/PowerSearch.generated.d.ts.map +1 -1
  137. package/dist/i18n/generated-english/PowerSearch/PowerSearch.generated.js +1 -0
  138. package/dist/i18n/generated-english/Tokenizer/TokenizerBottomSheet.generated.d.ts.map +1 -1
  139. package/dist/i18n/generated-english/Tokenizer/TokenizerBottomSheet.generated.js +7 -1
  140. package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
  141. package/dist/i18n/generated-locales/en.generated.js +1 -0
  142. package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
  143. package/dist/i18n/generated-locales/pseudo.generated.js +1 -0
  144. package/dist/internal/SheetConfirmation.d.ts +11 -0
  145. package/dist/internal/SheetConfirmation.d.ts.map +1 -0
  146. package/dist/internal/SheetConfirmation.js +92 -0
  147. package/dist/internal/sheetDraft.d.ts +35 -0
  148. package/dist/internal/sheetDraft.d.ts.map +1 -0
  149. package/dist/internal/sheetDraft.js +65 -0
  150. package/dist/internal/touch-sheet.d.ts +21 -0
  151. package/dist/internal/touch-sheet.d.ts.map +1 -0
  152. package/dist/internal/touch-sheet.js +19 -0
  153. package/dist/internal/useDismissSearchOnTouchDrag.d.ts +24 -0
  154. package/dist/internal/useDismissSearchOnTouchDrag.d.ts.map +1 -0
  155. package/dist/internal/useDismissSearchOnTouchDrag.js +56 -0
  156. package/dist/motion/roles.d.ts +7 -1
  157. package/dist/motion/roles.d.ts.map +1 -1
  158. package/dist/motion/roles.js +20 -1
  159. package/dist/motion/uikit/index.d.ts +6 -5
  160. package/dist/motion/uikit/index.d.ts.map +1 -1
  161. package/dist/motion/uikit/index.js +6 -5
  162. package/dist/motion/uikit/segmented.d.ts +55 -0
  163. package/dist/motion/uikit/segmented.d.ts.map +1 -0
  164. package/dist/motion/uikit/segmented.js +77 -0
  165. package/dist/motion/uikit/springs.d.ts +0 -18
  166. package/dist/motion/uikit/springs.d.ts.map +1 -1
  167. package/dist/motion/uikit/springs.js +5 -21
  168. package/dist/privateAttributes/segmentTitle.stylex.d.ts +5 -0
  169. package/dist/privateAttributes/segmentTitle.stylex.d.ts.map +1 -0
  170. package/dist/privateAttributes/segmentTitle.stylex.js +22 -0
  171. package/dist/utils/browserClickWait.d.ts +35 -0
  172. package/dist/utils/browserClickWait.d.ts.map +1 -0
  173. package/dist/utils/browserClickWait.js +123 -0
  174. package/dist/utils/clickGuard.d.ts +8 -1
  175. package/dist/utils/clickGuard.d.ts.map +1 -1
  176. package/dist/utils/clickGuard.js +24 -1
  177. package/locales/en.json +4 -0
  178. package/locales/pseudo.json +3 -0
  179. package/package.json +8 -8
  180. package/src/AppShell/useCloseMobileDrawerOnActivate.ts +52 -0
  181. package/src/BottomSheet/BottomSheet.tsx +95 -10
  182. package/src/BottomSheet/BottomSheetPanel.tsx +7 -2
  183. package/src/BottomSheet/bottomSheetStack.ts +8 -4
  184. package/src/Breadcrumbs/BreadcrumbItem.tsx +13 -3
  185. package/src/CheckboxList/CheckboxList.doc.mjs +4 -2
  186. package/src/CheckboxList/CheckboxList.tsx +6 -0
  187. package/src/CheckboxList/CheckboxListContext.tsx +6 -0
  188. package/src/CheckboxList/CheckboxListItem.tsx +11 -2
  189. package/src/DateInput/DateInput.tsx +8 -2
  190. package/src/DateTimeInput/DateTimeInput.tsx +8 -2
  191. package/src/Dialog/Dialog.tsx +44 -14
  192. package/src/Drawer/Drawer.tsx +28 -11
  193. package/src/Drawer/useDrawerDialogPresence.ts +5 -1
  194. package/src/Field/PanelSearchInput.tsx +11 -0
  195. package/src/InfoTip/InfoTip.doc.mjs +3 -3
  196. package/src/InfoTip/InfoTip.tsx +5 -5
  197. package/src/Layer/layerAnimations.stylex.ts +24 -16
  198. package/src/Layer/useBackdropPress.ts +81 -0
  199. package/src/Layer/usePressPin.ts +5 -11
  200. package/src/MobileNav/MobileNav.tsx +1 -1
  201. package/src/MultiSelector/hooks.ts +2 -4
  202. package/src/Outline/Outline.doc.mjs +177 -51
  203. package/src/Outline/Outline.tsx +5 -57
  204. package/src/Outline/useOutlineFromMarkdown.doc.mjs +157 -0
  205. package/src/Outline/useOutlineFromMarkdown.ts +1 -0
  206. package/src/Outline/useScrollSpy.ts +3 -3
  207. package/src/PowerSearch/PowerSearch.doc.mjs +3 -3
  208. package/src/PowerSearch/PowerSearch.tsx +196 -183
  209. package/src/RadioList/RadioList.doc.mjs +4 -2
  210. package/src/RadioList/RadioList.tsx +4 -0
  211. package/src/Resizable/ResizeHandle.tsx +133 -328
  212. package/src/Resizable/resizeGesture.ts +368 -0
  213. package/src/SegmentedControl/SegmentedControl.doc.mjs +3 -3
  214. package/src/SegmentedControl/SegmentedControl.tsx +179 -5
  215. package/src/SegmentedControl/SegmentedControlContext.ts +6 -0
  216. package/src/SegmentedControl/SegmentedControlItem.tsx +12 -1
  217. package/src/SegmentedControl/useSegmentedSelectionMotion.ts +1637 -0
  218. package/src/SegmentedControl/useSegmentedTouchTracking.ts +308 -0
  219. package/src/Selector/Selector.tsx +136 -89
  220. package/src/Selector/optionShape.ts +64 -0
  221. package/src/Selector/selectableOptionEntries.ts +39 -0
  222. package/src/Selector/utils.ts +9 -68
  223. package/src/SideNav/SideNavItem.tsx +3 -7
  224. package/src/Switch/switchTouch.ts +2 -10
  225. package/src/Switch/useSwitchTouch.ts +26 -96
  226. package/src/Token/Token.tsx +13 -4
  227. package/src/Token/TokenNamingContext.ts +28 -0
  228. package/src/Tokenizer/Tokenizer.doc.mjs +16 -0
  229. package/src/Tokenizer/Tokenizer.tsx +81 -86
  230. package/src/Tokenizer/TokenizerBottomSheet.tsx +219 -93
  231. package/src/Tokenizer/TokenizerItemRemovableContext.ts +5 -3
  232. package/src/Tooltip/Tooltip.doc.mjs +123 -54
  233. package/src/Tooltip/Tooltip.tsx +36 -39
  234. package/src/Tooltip/index.ts +0 -1
  235. package/src/Tooltip/useTooltip.doc.mjs +147 -33
  236. package/src/Tooltip/useTooltip.tsx +39 -32
  237. package/src/TopNav/TopNavItem.tsx +6 -5
  238. package/src/TopNav/TopNavMegaMenuItem.tsx +6 -5
  239. package/src/TopNav/TopNavMenu.tsx +6 -5
  240. package/src/gestures/GestureHost.ts +73 -4
  241. package/src/hooks/actionGroupTracker.ts +12 -56
  242. package/src/hooks/useHighlightedOptionScroll.ts +123 -15
  243. package/src/hooks/useMediaQuery.doc.mjs +2 -2
  244. package/src/hooks/useMediaQuery.ts +13 -4
  245. package/src/hooks/useMenuPress.ts +8 -2
  246. package/src/i18n/generated-english/PowerSearch/PowerSearch.generated.ts +1 -0
  247. package/src/i18n/generated-english/Tokenizer/TokenizerBottomSheet.generated.ts +7 -1
  248. package/src/i18n/generated-locales/en.generated.ts +1 -0
  249. package/src/i18n/generated-locales/pseudo.generated.ts +1 -0
  250. package/src/internal/SheetConfirmation.tsx +101 -0
  251. package/src/internal/sheetDraft.ts +80 -0
  252. package/src/internal/touch-sheet.ts +21 -0
  253. package/src/internal/useDismissSearchOnTouchDrag.ts +69 -0
  254. package/src/motion/MEASUREMENTS.md +7 -0
  255. package/src/motion/README.md +1 -0
  256. package/src/motion/roles.ts +20 -1
  257. package/src/motion/uikit/index.ts +5 -5
  258. package/src/motion/uikit/segmented.ts +74 -0
  259. package/src/motion/uikit/springs.ts +5 -21
  260. package/src/privateAttributes/segmentTitle.stylex.ts +22 -0
  261. package/src/utils/browserClickWait.ts +133 -0
  262. package/src/utils/clickGuard.ts +37 -1
  263. package/dist/internal/tokenizer-presentation.d.ts +0 -21
  264. package/dist/internal/tokenizer-presentation.d.ts.map +0 -1
  265. package/dist/internal/tokenizer-presentation.js +0 -21
  266. package/src/internal/tokenizer-presentation.ts +0 -25
@@ -24,6 +24,7 @@ import {colorVars} from '../theme/tokens.stylex';
24
24
  import type {BaseProps} from '../BaseProps';
25
25
  import {CheckboxInput} from '../CheckboxInput/CheckboxInput';
26
26
  import type {CheckboxInputProps} from '../CheckboxInput/CheckboxInput';
27
+ import type {InputStatus} from '../Field/types';
27
28
  import {ListItem} from '../List/ListItem';
28
29
  import {ListContext} from '../List/ListContext';
29
30
  import {CheckboxListContext} from './CheckboxListContext';
@@ -149,6 +150,8 @@ export interface CheckboxListItemProps extends BaseProps<HTMLLIElement> {
149
150
  // Component
150
151
  // =============================================================================
151
152
 
153
+ const ERROR_STATUS: InputStatus = {type: 'error'};
154
+
152
155
  /**
153
156
  * The row's visible description is the checkbox's accessible description. Item
154
157
  * renders that element and owns its id, and publishes the id through
@@ -167,11 +170,17 @@ function DescribedCheckboxInput(
167
170
  props: Omit<CheckboxInputProps, 'aria-describedby'>,
168
171
  ) {
169
172
  const describedBy = use(ItemDescriptionContext);
170
- const reasonID = use(CheckboxListContext)?.disabledMessageID;
173
+ const listContext = use(CheckboxListContext);
171
174
  return (
172
175
  <CheckboxInput
173
176
  {...props}
174
- aria-describedby={joinAriaIDs(describedBy ?? undefined, reasonID)}
177
+ aria-describedby={joinAriaIDs(
178
+ describedBy ?? undefined,
179
+ listContext?.disabledMessageID,
180
+ )}
181
+ // The status has no message, so CheckboxInput renders nothing. The list
182
+ // renders the message.
183
+ status={listContext?.isInvalid ? ERROR_STATUS : undefined}
175
184
  />
176
185
  );
177
186
  }
@@ -884,12 +884,18 @@ function PointerDateField({
884
884
  if (!isEffectivelyDisabled) {
885
885
  popover.show({skipAutoFocus: true});
886
886
  }
887
- } else if (e.key === 'Enter') {
887
+ } else if (
888
+ e.key === 'Enter' &&
889
+ (pendingInput !== null || isEffectivelyDisabled)
890
+ ) {
891
+ // Typed text holds Enter back, or a form would submit the value from
892
+ // before the commit. A disabled or busy field must not submit its
893
+ // form either. Otherwise Enter keeps its default, as in any text field.
888
894
  e.preventDefault();
889
895
  commitPendingInput();
890
896
  }
891
897
  },
892
- [popover, commitPendingInput, isEffectivelyDisabled],
898
+ [popover, commitPendingInput, isEffectivelyDisabled, pendingInput],
893
899
  );
894
900
 
895
901
  const inputWrapper = (
@@ -1143,12 +1143,18 @@ function PointerDateTimeField({
1143
1143
  if (!isEffectivelyDisabled) {
1144
1144
  popover.show({skipAutoFocus: true});
1145
1145
  }
1146
- } else if (e.key === 'Enter') {
1146
+ } else if (
1147
+ e.key === 'Enter' &&
1148
+ (datePendingInput !== null || isEffectivelyDisabled)
1149
+ ) {
1150
+ // Typed text holds Enter back, or a form would submit the value from
1151
+ // before the commit. A disabled or busy field must not submit its
1152
+ // form either. Otherwise Enter keeps its default, as in any text field.
1147
1153
  e.preventDefault();
1148
1154
  commitDatePendingInput();
1149
1155
  }
1150
1156
  },
1151
- [popover, commitDatePendingInput, isEffectivelyDisabled],
1157
+ [popover, commitDatePendingInput, isEffectivelyDisabled, datePendingInput],
1152
1158
  );
1153
1159
 
1154
1160
  // --- Time-option popover (#2727) ---
@@ -52,6 +52,7 @@ import {LayerContentBoundary} from '../Layer/layerScopedContext';
52
52
  import {layerClickBoundary} from '../Layer/layerClickBoundary';
53
53
  import {LayerDepthProvider} from '../Layer/LayerDepthContext';
54
54
  import {useLayerDismissal} from '../Layer/useLayerDismissal';
55
+ import {useBackdropPress} from '../Layer/useBackdropPress';
55
56
  import {useModalOutlet} from '../Layer/modalOutlet';
56
57
  import {
57
58
  colorVars,
@@ -84,6 +85,8 @@ import {focusForLastInput, isFocusLost} from '../utils/focusReturn';
84
85
  import {ModalAnnounceProvider} from '../hooks/ModalAnnounceProvider';
85
86
 
86
87
  import {useMergedRefs} from '../hooks/useMergedRefs';
88
+
89
+ type PointerHandler = (event: React.PointerEvent<HTMLDialogElement>) => void;
87
90
  /**
88
91
  * Calculate a directional translate offset for dialog entry animation.
89
92
  * Returns a normalized vector from the trigger element toward the viewport
@@ -186,8 +189,22 @@ const styles = stylex.create({
186
189
  position: 'fixed',
187
190
  margin: 'auto',
188
191
  padding: 0,
189
- borderWidth: 0,
190
- borderStyle: 'none',
192
+ // The backdrop does not blur the page. Forced colors override the
193
+ // backdrop's colour, so a 1px border keeps the modal separate from the
194
+ // page. It is a border, not an outline: the focus ring
195
+ // (focusOutlineProps) owns the outline longhands on this element.
196
+ borderWidth: {
197
+ default: 0,
198
+ '@media (forced-colors: active)': 1,
199
+ },
200
+ borderStyle: {
201
+ default: 'none',
202
+ '@media (forced-colors: active)': 'solid',
203
+ },
204
+ borderColor: {
205
+ default: null,
206
+ '@media (forced-colors: active)': 'CanvasText',
207
+ },
191
208
  backgroundColor: colorVars['--color-background-surface'],
192
209
  '--_dialog-radius': radiusVars['--radius-container'],
193
210
  borderRadius: 'var(--_dialog-radius)',
@@ -233,11 +250,10 @@ const styles = stylex.create({
233
250
  openSettled: {
234
251
  animationName: 'none',
235
252
  },
236
- // Backdrop using ::backdrop pseudo-element
253
+ // No backdrop-filter: a full-viewport blur is costly to repaint.
237
254
  backdrop: {
238
255
  '::backdrop': {
239
256
  backgroundColor: colorVars['--color-overlay'],
240
- backdropFilter: 'blur(2px)',
241
257
  },
242
258
  },
243
259
  fullscreen: {
@@ -246,6 +262,8 @@ const styles = stylex.create({
246
262
  maxWidth: '100dvw',
247
263
  maxHeight: '100dvh',
248
264
  borderRadius: 0,
265
+ // Nothing of the page shows around a full-screen dialog: no edge.
266
+ borderWidth: 0,
249
267
  margin: 0,
250
268
  inset: 0,
251
269
  },
@@ -723,16 +741,16 @@ export function Dialog({
723
741
  }
724
742
  }, [isOpen, isInline, hasConsumerName, titleId]);
725
743
 
726
- // Handle backdrop click — when the user clicks the ::backdrop pseudo-element,
727
- // the event target is the <dialog> element itself; clicks on child content
728
- // always target the child. This avoids false positives from native browser
744
+ // A press on the ::backdrop targets the <dialog> itself, so native browser
729
745
  // popups (date pickers, selects, color pickers) that render outside the
730
- // dialog's bounding rect.
731
- const handleClick = (event: React.MouseEvent<HTMLDialogElement>) => {
732
- if (event.target === event.currentTarget && allowBackdropClick) {
733
- onOpenChange(false);
734
- }
735
- };
746
+ // dialog's rect never count. The press must start and end there.
747
+ const handleBackdropPress = useCallback(() => {
748
+ onOpenChange(false);
749
+ }, [onOpenChange]);
750
+ const backdropPress = useBackdropPress(
751
+ handleBackdropPress,
752
+ allowBackdropClick,
753
+ );
736
754
 
737
755
  // The native `cancel` event is the browser's own close-watcher firing: an
738
756
  // Android back gesture, or a close request the stack never saw a press for.
@@ -890,8 +908,20 @@ export function Dialog({
890
908
  // written.
891
909
  {...layerClickBoundary({
892
910
  ...safeProps,
893
- onClick: chainEventHandlers(handleClick, onClickProp),
911
+ onClick: chainEventHandlers(backdropPress.onClick, onClickProp),
894
912
  })}
913
+ onPointerDownCapture={chainEventHandlers(
914
+ backdropPress.onPointerDownCapture,
915
+ safeProps.onPointerDownCapture as PointerHandler | undefined,
916
+ )}
917
+ onPointerUpCapture={chainEventHandlers(
918
+ backdropPress.onPointerUpCapture,
919
+ safeProps.onPointerUpCapture as PointerHandler | undefined,
920
+ )}
921
+ onPointerCancelCapture={chainEventHandlers(
922
+ backdropPress.onPointerCancelCapture,
923
+ safeProps.onPointerCancelCapture as PointerHandler | undefined,
924
+ )}
895
925
  onCancel={handleCancel}
896
926
  aria-modal={isHydrating ? undefined : 'true'}
897
927
  // The default aria-labelledby (from a DialogHeader title) is set
@@ -78,9 +78,11 @@ import {layerTextReset} from '../Layer/layerTextReset.stylex';
78
78
  import {layerStructureReset} from '../Layer/layerStructureReset.stylex';
79
79
  import {LayerContentBoundary} from '../Layer/layerScopedContext';
80
80
  import {useModalOutlet} from '../Layer/modalOutlet';
81
+ import {useBackdropPress} from '../Layer/useBackdropPress';
81
82
  import {layerClickBoundary} from '../Layer/layerClickBoundary';
82
83
  import {ModalAnnounceProvider} from '../hooks/ModalAnnounceProvider';
83
84
  import {composeEventHandlers, mergeProps, themeProps} from '../utils';
85
+ import {chainEventHandlers} from '../utils/chainEventHandlers';
84
86
  import {container, overlayPaddingReset} from '../Layout';
85
87
  import {
86
88
  containerPaddingBlockEndVarStyles,
@@ -92,6 +94,8 @@ import {
92
94
  import type {SpacingStep, SpacingToken} from '../Layout';
93
95
  import {useDrawerDialogPresence} from './useDrawerDialogPresence';
94
96
 
97
+ type PointerHandler = (event: React.PointerEvent<HTMLDialogElement>) => void;
98
+
95
99
  // =============================================================================
96
100
  // Styles
97
101
  // =============================================================================
@@ -218,8 +222,8 @@ const styles = stylex.create({
218
222
  // Scrim via the browser's ::backdrop pseudo-element (top layer).
219
223
  scrim: {
220
224
  '::backdrop': {
225
+ // No backdrop-filter: a full-viewport blur is costly to repaint.
221
226
  backgroundColor: colorVars['--color-overlay'],
222
- backdropFilter: 'blur(2px)',
223
227
  opacity: 0,
224
228
  transitionProperty: 'opacity',
225
229
  transitionDuration: durationVars['--duration-medium'],
@@ -543,15 +547,14 @@ export function Drawer({
543
547
  [allowEscape, handleDismiss, shouldDismissOnCloseRequest],
544
548
  );
545
549
 
546
- // Clicks on the ::backdrop target the <dialog> element itself; clicks on
547
- // drawer content always target a child (the content area fills the panel).
548
- const handleClick = useCallback(
549
- (event: React.MouseEvent<HTMLDialogElement>) => {
550
- if (event.target === event.currentTarget && hasScrim && allowScrimClick) {
551
- onOpenChange(false);
552
- }
553
- },
554
- [allowScrimClick, hasScrim, onOpenChange],
550
+ // A press on the ::backdrop targets the <dialog> itself (the content area
551
+ // fills the panel). The press must start and end there.
552
+ const handleScrimPress = useCallback(() => {
553
+ onOpenChange(false);
554
+ }, [onOpenChange]);
555
+ const scrimPress = useBackdropPress(
556
+ handleScrimPress,
557
+ hasScrim && allowScrimClick,
555
558
  );
556
559
 
557
560
  const widthValue = typeof width === 'number' ? `${width}px` : width;
@@ -612,8 +615,22 @@ export function Drawer({
612
615
  // written.
613
616
  {...layerClickBoundary({
614
617
  ...safeProps,
615
- onClick: composeEventHandlers(onClickProp, handleClick),
618
+ onClick: composeEventHandlers(onClickProp, scrimPress.onClick),
616
619
  })}
620
+ // The hook records where the press started first. The caller's capture
621
+ // handler then runs, also when a child prevented default.
622
+ onPointerDownCapture={chainEventHandlers(
623
+ scrimPress.onPointerDownCapture,
624
+ safeProps.onPointerDownCapture as PointerHandler | undefined,
625
+ )}
626
+ onPointerUpCapture={chainEventHandlers(
627
+ scrimPress.onPointerUpCapture,
628
+ safeProps.onPointerUpCapture as PointerHandler | undefined,
629
+ )}
630
+ onPointerCancelCapture={chainEventHandlers(
631
+ scrimPress.onPointerCancelCapture,
632
+ safeProps.onPointerCancelCapture as PointerHandler | undefined,
633
+ )}
617
634
  onKeyDown={onKeyDownProp}
618
635
  onCancel={handleCancel}>
619
636
  <ModalAnnounceProvider dialogRef={dialogRef} isOpen={isOpen && hasScrim}>
@@ -33,6 +33,7 @@ import {
33
33
  } from 'react';
34
34
  import {flushSync} from 'react-dom';
35
35
  import {notifyTopLayerEntry} from '../Layer/modalOutlet';
36
+ import {runPopoverOperation} from '../Layer/popoverOperations';
36
37
 
37
38
  /** Slack past the computed transition before the backstop gives up waiting. */
38
39
  const EXIT_BACKSTOP_BUFFER_MS = 50;
@@ -71,7 +72,10 @@ function showDrawerHost(dialog: HTMLDialogElement, isModal: boolean): void {
71
72
  if (isModal) {
72
73
  dialog.showModal();
73
74
  } else if (typeof dialog.showPopover === 'function') {
74
- dialog.showPopover();
75
+ // The dialog's show focuses its first control. A tooltip on that control
76
+ // then asks to show. The browser refuses a show inside another show.
77
+ // Counting this show makes the tooltip wait until it ends.
78
+ runPopoverOperation(dialog.ownerDocument, () => dialog.showPopover());
75
79
  // The drawer is now the newest top-layer entry: global hosts shown before
76
80
  // it (the toast viewport) re-enter above it, as they do above a modal.
77
81
  notifyTopLayerEntry();
@@ -58,6 +58,7 @@ import {
58
58
  } from '../theme/tokens.stylex';
59
59
  import type {BaseProps} from '../BaseProps';
60
60
  import {textEntryTraitAttributes} from './textEntryTraits';
61
+ import {inputWrapperStyles} from './inputStyles.stylex';
61
62
 
62
63
  const styles = stylex.create({
63
64
  // Gutter only. Matches the option list's own padding so the field below
@@ -185,6 +186,14 @@ export interface PanelSearchInputProps extends Omit<
185
186
  * the field to the same height as its rows.
186
187
  */
187
188
  fieldXstyle?: StyleXStyles;
189
+ /**
190
+ * Draw the field as the shared input box — the input border, accent while
191
+ * focused, surface and radius — instead of the borderless panel row. A
192
+ * touch sheet is a full-width surface, not a bordered panel, so its field
193
+ * reads as a form input.
194
+ * @default false
195
+ */
196
+ hasInputBox?: boolean;
188
197
  }
189
198
 
190
199
  /**
@@ -208,6 +217,7 @@ export function PanelSearchInput({
208
217
  onBlur,
209
218
  onContainerKeyDown,
210
219
  fieldXstyle,
220
+ hasInputBox = false,
211
221
  xstyle,
212
222
  className,
213
223
  style,
@@ -268,6 +278,7 @@ export function PanelSearchInput({
268
278
  {...stylex.props(
269
279
  styles.field,
270
280
  isKeyboardFocus && styles.fieldKeyboardFocus,
281
+ hasInputBox && inputWrapperStyles.base,
271
282
  fieldXstyle,
272
283
  )}>
273
284
  <Icon icon="search" size="sm" color="secondary" xstyle={styles.icon} />
@@ -66,7 +66,7 @@ export const docs = {
66
66
  {
67
67
  guidance: true,
68
68
  description:
69
- 'Rely on InfoTip rather than a hand-rolled info button on touch: it sets Tooltip triggerBehavior to toggle, so a tap, a click, Enter, or Space pins the tooltip even though the trigger is a button, and a tap outside dismisses it.',
69
+ 'Rely on InfoTip rather than a hand-rolled info button on touch: it pins its help on a press, so a tap, a click, Enter, or Space pins the tooltip even though the trigger is a button, and a tap outside dismisses it.',
70
70
  },
71
71
  {
72
72
  guidance: false,
@@ -131,7 +131,7 @@ export const docsZh = {
131
131
  {
132
132
  guidance: true,
133
133
  description:
134
- 'Rely on InfoTip rather than a hand-rolled info button on touch: it sets Tooltip triggerBehavior to toggle, so a tap, a click, Enter, or Space pins the tooltip even though the trigger is a button, and a tap outside dismisses it.',
134
+ 'Rely on InfoTip rather than a hand-rolled info button on touch: it pins its help on a press, so a tap, a click, Enter, or Space pins the tooltip even though the trigger is a button, and a tap outside dismisses it.',
135
135
  },
136
136
  {
137
137
  guidance: false,
@@ -168,7 +168,7 @@ export const docsDense = {
168
168
  {
169
169
  guidance: true,
170
170
  description:
171
- 'Rely on InfoTip rather than a hand-rolled info button on touch: it sets Tooltip triggerBehavior to toggle, so a tap, a click, Enter, or Space pins the tooltip even though the trigger is a button, and a tap outside dismisses it.',
171
+ 'Rely on InfoTip rather than a hand-rolled info button on touch: it pins its help on a press, so a tap, a click, Enter, or Space pins the tooltip even though the trigger is a button, and a tap outside dismisses it.',
172
172
  },
173
173
  {
174
174
  guidance: false,
@@ -23,7 +23,7 @@ import * as stylex from '@stylexjs/stylex';
23
23
  import type {BaseProps} from '../BaseProps';
24
24
  import {useMergedRefs} from '../hooks/useMergedRefs';
25
25
  import {Icon, type IconSize} from '../Icon/Icon';
26
- import {Tooltip} from '../Tooltip/Tooltip';
26
+ import {TooltipWithTriggerBehavior} from '../Tooltip/Tooltip';
27
27
  import {
28
28
  registerEnglishFallback,
29
29
  useComponentTranslator,
@@ -122,8 +122,8 @@ const styles = stylex.create({
122
122
  * labels, values, and metrics for permission notes, metric definitions, and
123
123
  * field help.
124
124
  *
125
- * InfoTip is Tooltip with `triggerBehavior="toggle"` on a named button: Tooltip
126
- * opens, pins, announces, and closes the help. The info icon resolves from
125
+ * InfoTip is a press-to-pin Tooltip on a named button: Tooltip opens, pins,
126
+ * announces, and closes the help. The info icon resolves from
127
127
  * the icon registry, so themes can replace it.
128
128
  *
129
129
  * @example
@@ -157,7 +157,7 @@ export function InfoTip({
157
157
  // stack takes Escape, so one press never closes both the help and an
158
158
  // enclosing Dialog.
159
159
  return (
160
- <Tooltip
160
+ <TooltipWithTriggerBehavior
161
161
  content={content}
162
162
  // The tooltip is the button's only purpose. A click, a tap, Enter, or
163
163
  // Space pins it open, and the press stops at the button. So an InfoTip's
@@ -179,7 +179,7 @@ export function InfoTip({
179
179
  )}>
180
180
  <Icon icon="info" size={size} />
181
181
  </button>
182
- </Tooltip>
182
+ </TooltipWithTriggerBehavior>
183
183
  );
184
184
  }
185
185
 
@@ -2,7 +2,8 @@
2
2
 
3
3
  /**
4
4
  * @file layerAnimations.stylex.ts
5
- * @input Uses StyleX, theme duration/easing tokens
5
+ * @input Uses StyleX, theme duration/easing tokens, the Reduce Motion fade
6
+ * constants
6
7
  * @output Exports shared entry animation styles for layer-based components
7
8
  * @position Shared animation primitives; consumed by DropdownMenu, Popover,
8
9
  * HoverCard, Tooltip, Selector, and any component using useLayer
@@ -27,6 +28,7 @@
27
28
  */
28
29
 
29
30
  import * as stylex from '@stylexjs/stylex';
31
+ import {reducedMotionFadeConsts} from '../motion/reducedMotion.stylex';
30
32
  import {durationVars, easeVars, spacingVars} from '../theme/tokens.stylex';
31
33
 
32
34
  const enterBelow = stylex.keyframes({
@@ -84,17 +86,21 @@ const enterStartRtl = stylex.keyframes({
84
86
 
85
87
  // Reduce Motion under a finger: the same entrance without its travel. A touch
86
88
  // presentation substitutes a cross-fade for motion, never a jump.
89
+ // The fade's curve rides in its first keyframe, so the published class type
90
+ // of `animationTimingFunction` stays the theme ease's.
87
91
  const fadeIn = stylex.keyframes({
88
- from: {opacity: 0},
92
+ from: {opacity: 0, animationTimingFunction: reducedMotionFadeConsts.easing},
89
93
  to: {opacity: 1},
90
94
  });
91
95
 
96
+ // No theme value lengthens the Reduce Motion fade, so its values are constants.
97
+ // The media keys stay literal: StyleX gives an imported at-rule key a later
98
+ // priority layer (see reducedMotionKeys).
92
99
  const animationBase = {
93
100
  animationDuration: {
94
101
  default: durationVars['--duration-fast-max'],
95
- // The cross-fade is 200 ms or less, whatever the theme's duration
96
- // (design:reduced-motion DR2).
97
- '@media (prefers-reduced-motion: reduce) and (pointer: coarse)': `min(${durationVars['--duration-fast-max']}, 200ms)`,
102
+ '@media (prefers-reduced-motion: reduce) and (pointer: coarse)':
103
+ reducedMotionFadeConsts.duration,
98
104
  },
99
105
  animationTimingFunction: easeVars['--ease-standard'],
100
106
  animationFillMode: 'backwards' as const,
@@ -106,19 +112,18 @@ const animationBase = {
106
112
  * Keyed by LayerPlacement ('above' | 'below' | 'start' | 'end')
107
113
  * for easy lookup: `layerAnimations[placement]`.
108
114
  *
109
- * Each entry disables its keyframe animation under
110
- * `prefers-reduced-motion: reduce` so the layer appears instantly instead of
111
- * translating/scaling in (infra-6). Under a finger (the primary pointer is
112
- * coarse) it fades in instead, opacity only and in 200 ms or less: on a touch
113
- * screen Reduce Motion replaces motion with a cross-fade rather than a jump
114
- * (design:reduced-motion DR2, DR3), as iOS does for its menus, popovers and
115
- * tips.
115
+ * Under `prefers-reduced-motion: reduce` each entry drops its travel and
116
+ * scale. Under a finger (the primary pointer is coarse) the layer fades in,
117
+ * opacity only, in 200 ms on easeInOut: on a touch screen Reduce Motion
118
+ * replaces motion with a cross-fade rather than a jump; under any other
119
+ * pointer it appears at once.
116
120
  */
117
121
  export const layerAnimations = stylex.create({
118
122
  below: {
119
123
  animationName: {
120
124
  default: enterBelow,
121
- '@media (prefers-reduced-motion: reduce)': 'none',
125
+ '@media (prefers-reduced-motion: reduce) and (not (pointer: coarse))':
126
+ 'none',
122
127
  '@media (prefers-reduced-motion: reduce) and (pointer: coarse)': fadeIn,
123
128
  },
124
129
  ...animationBase,
@@ -126,7 +131,8 @@ export const layerAnimations = stylex.create({
126
131
  above: {
127
132
  animationName: {
128
133
  default: enterAbove,
129
- '@media (prefers-reduced-motion: reduce)': 'none',
134
+ '@media (prefers-reduced-motion: reduce) and (not (pointer: coarse))':
135
+ 'none',
130
136
  '@media (prefers-reduced-motion: reduce) and (pointer: coarse)': fadeIn,
131
137
  },
132
138
  ...animationBase,
@@ -135,7 +141,8 @@ export const layerAnimations = stylex.create({
135
141
  animationName: {
136
142
  default: enterEnd,
137
143
  ':is([dir="rtl"] *)': enterEndRtl,
138
- '@media (prefers-reduced-motion: reduce)': 'none',
144
+ '@media (prefers-reduced-motion: reduce) and (not (pointer: coarse))':
145
+ 'none',
139
146
  '@media (prefers-reduced-motion: reduce) and (pointer: coarse)': fadeIn,
140
147
  },
141
148
  ...animationBase,
@@ -144,7 +151,8 @@ export const layerAnimations = stylex.create({
144
151
  animationName: {
145
152
  default: enterStart,
146
153
  ':is([dir="rtl"] *)': enterStartRtl,
147
- '@media (prefers-reduced-motion: reduce)': 'none',
154
+ '@media (prefers-reduced-motion: reduce) and (not (pointer: coarse))':
155
+ 'none',
148
156
  '@media (prefers-reduced-motion: reduce) and (pointer: coarse)': fadeIn,
149
157
  },
150
158
  ...animationBase,
@@ -0,0 +1,81 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file useBackdropPress.ts
7
+ * @input A modal layer's backdrop dismissal and whether it is allowed now
8
+ * @output Capture-phase pointer handlers and a click handler for the layer's
9
+ * <dialog> root
10
+ * @position Shared backdrop-press rule for Dialog and Drawer
11
+ */
12
+
13
+ import {useCallback, useRef, type PointerEvent, type MouseEvent} from 'react';
14
+
15
+ export interface BackdropPressHandlers {
16
+ onPointerDownCapture: (event: PointerEvent<HTMLElement>) => void;
17
+ onPointerUpCapture: (event: PointerEvent<HTMLElement>) => void;
18
+ onPointerCancelCapture: (event: PointerEvent<HTMLElement>) => void;
19
+ onClick: (event: MouseEvent<HTMLElement>) => void;
20
+ }
21
+
22
+ /**
23
+ * A press on a modal layer's backdrop targets the <dialog> root itself. A
24
+ * click there is also what the browser sends when a press starts on one side
25
+ * of the layer's edge and ends on the other (it goes to the nearest common
26
+ * ancestor), so a backdrop press dismisses only when its pointer-down and its
27
+ * pointer-up both landed on the backdrop. A cancelled pointer ends the press.
28
+ *
29
+ * The pointer handlers run in the capture phase. A nested layer, such as a
30
+ * popover open in the dialog, stops its presses at its own root, so a
31
+ * bubble-phase handler here does not see them.
32
+ */
33
+ export function useBackdropPress(
34
+ onBackdropPress: () => void,
35
+ isEnabled: boolean,
36
+ ): BackdropPressHandlers {
37
+ const startedOnBackdropRef = useRef(false);
38
+ const endedOnBackdropRef = useRef(false);
39
+
40
+ const onPointerDownCapture = useCallback(
41
+ (event: PointerEvent<HTMLElement>) => {
42
+ startedOnBackdropRef.current = event.target === event.currentTarget;
43
+ endedOnBackdropRef.current = false;
44
+ },
45
+ [],
46
+ );
47
+
48
+ const onPointerUpCapture = useCallback((event: PointerEvent<HTMLElement>) => {
49
+ endedOnBackdropRef.current =
50
+ startedOnBackdropRef.current && event.target === event.currentTarget;
51
+ startedOnBackdropRef.current = false;
52
+ }, []);
53
+
54
+ const onPointerCancelCapture = useCallback(() => {
55
+ startedOnBackdropRef.current = false;
56
+ endedOnBackdropRef.current = false;
57
+ }, []);
58
+
59
+ const onClick = useCallback(
60
+ (event: MouseEvent<HTMLElement>) => {
61
+ const pressWasOnBackdrop = endedOnBackdropRef.current;
62
+ startedOnBackdropRef.current = false;
63
+ endedOnBackdropRef.current = false;
64
+ if (
65
+ isEnabled &&
66
+ pressWasOnBackdrop &&
67
+ event.target === event.currentTarget
68
+ ) {
69
+ onBackdropPress();
70
+ }
71
+ },
72
+ [isEnabled, onBackdropPress],
73
+ );
74
+
75
+ return {
76
+ onPointerDownCapture,
77
+ onPointerUpCapture,
78
+ onPointerCancelCapture,
79
+ onClick,
80
+ };
81
+ }
@@ -35,12 +35,10 @@ import {
35
35
 
36
36
  /**
37
37
  * How the trigger opens the layer. `adaptive`, the default, previews it on
38
- * hover and focus, and a tap opens it unless the trigger acts. While
39
- * `touchTrigger` remains, it decides those taps. `hover` previews it on hover
40
- * and focus, and no touch or pen tap opens it. `toggle` pins it on every
41
- * press of a button trigger.
38
+ * hover and focus, and `touchTrigger` decides taps. `toggle` pins it on every
39
+ * press of a button trigger. Internal: InfoTip's press-to-pin uses `toggle`.
42
40
  */
43
- export type LayerTriggerBehavior = 'adaptive' | 'hover' | 'toggle';
41
+ export type LayerTriggerBehavior = 'adaptive' | 'toggle';
44
42
 
45
43
  /** The clicks a press pin answered, so the wrapper can stop them (`stopPressPinClick`). */
46
44
  const pressPinClicks = new WeakSet<Event>();
@@ -214,11 +212,7 @@ export function usePressPin(options: UsePressPinOptions): UsePressPinReturn {
214
212
  clearTapOpen,
215
213
  armOutsideDismiss,
216
214
  } = useTouchTriggerInternal({
217
- touchTrigger: isToggleMode
218
- ? 'tap'
219
- : triggerBehavior === 'hover'
220
- ? 'none'
221
- : options.touchTrigger,
215
+ touchTrigger: isToggleMode ? 'tap' : options.touchTrigger,
222
216
  isEnabled: options.isEnabled,
223
217
  isControlled: isOpen !== undefined,
224
218
  isOpen: isLayerOpen,
@@ -466,7 +460,7 @@ export function usePressPin(options: UsePressPinOptions): UsePressPinReturn {
466
460
  const isWired = isToggleMode && offeredKind === 'button';
467
461
  useDevWarning(
468
462
  options.component,
469
- '`triggerBehavior="toggle"` pins only a button trigger (a <button> or role="button"); this trigger keeps tap-only opening.',
463
+ 'A press-to-pin tooltip pins only a button trigger (a <button> or role="button"); this trigger keeps tap-only opening.',
470
464
  isToggleMode && offeredKind === 'other',
471
465
  );
472
466
 
@@ -143,8 +143,8 @@ const styles = stylex.create({
143
143
  // ::backdrop is provided by the browser's top layer
144
144
  backdrop: {
145
145
  '::backdrop': {
146
+ // No backdrop-filter: a full-viewport blur is costly to repaint.
146
147
  backgroundColor: colorVars['--color-overlay'],
147
- backdropFilter: 'blur(2px)',
148
148
  opacity: 0,
149
149
  transitionProperty: 'opacity',
150
150
  transitionDuration: durationVars['--duration-medium'],
@@ -174,13 +174,11 @@ export function useMultiCombobox({
174
174
  // The action cell is the caller's control, usable whether or not the
175
175
  // option itself is disabled (spec:AST-058 FR4).
176
176
  if (cell === 'action') {
177
- highlightOnHover(index);
178
- setHighlightedCell('action');
177
+ highlightOnHover(index, () => setHighlightedCell('action'));
179
178
  return;
180
179
  }
181
180
  if (!item.disabled) {
182
- highlightOnHover(index);
183
- setHighlightedCell('option');
181
+ highlightOnHover(index, () => setHighlightedCell('option'));
184
182
  }
185
183
  },
186
184
  [highlightOnHover],