@astryxdesign/core 0.7.6 → 0.7.7-canary.0eb44a2

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 (272) hide show
  1. package/dist/AppShell/AppShell.d.ts.map +1 -1
  2. package/dist/AppShell/AppShell.js +23 -3
  3. package/dist/Avatar/Avatar.d.ts.map +1 -1
  4. package/dist/Avatar/Avatar.js +1 -1
  5. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  6. package/dist/BottomSheet/BottomSheetPanel.js +16 -9
  7. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  8. package/dist/Breadcrumbs/BreadcrumbItem.js +2 -1
  9. package/dist/Button/Button.d.ts.map +1 -1
  10. package/dist/Button/Button.js +3 -2
  11. package/dist/Chat/ChatComposerInput.d.ts +9 -0
  12. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  13. package/dist/Chat/ChatComposerInput.js +2 -0
  14. package/dist/Chat/ChatMessage.js +2 -2
  15. package/dist/Chat/chatMessage.vars.stylex.d.ts +8 -8
  16. package/dist/Chat/chatMessage.vars.stylex.js +8 -8
  17. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  18. package/dist/CommandPalette/CommandPaletteInput.js +8 -1
  19. package/dist/DateInput/MonthScroller.d.ts.map +1 -1
  20. package/dist/DateInput/MonthScroller.js +72 -2
  21. package/dist/DateInput/Wheel.d.ts +17 -1
  22. package/dist/DateInput/Wheel.d.ts.map +1 -1
  23. package/dist/DateInput/Wheel.js +179 -19
  24. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  25. package/dist/DateRangeInput/DateRangeInput.js +63 -49
  26. package/dist/Dialog/Dialog.d.ts.map +1 -1
  27. package/dist/Dialog/Dialog.js +37 -1
  28. package/dist/Drawer/Drawer.d.ts.map +1 -1
  29. package/dist/Drawer/Drawer.js +6 -0
  30. package/dist/Drawer/useDrawerDialogPresence.d.ts.map +1 -1
  31. package/dist/Drawer/useDrawerDialogPresence.js +4 -0
  32. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  33. package/dist/DropdownMenu/DropdownMenu.js +20 -4
  34. package/dist/DropdownMenu/useMenuOverflow.d.ts +13 -3
  35. package/dist/DropdownMenu/useMenuOverflow.d.ts.map +1 -1
  36. package/dist/DropdownMenu/useMenuOverflow.js +31 -8
  37. package/dist/Field/PanelSearchInput.d.ts.map +1 -1
  38. package/dist/Field/PanelSearchInput.js +8 -1
  39. package/dist/Field/textEntryTraits.d.ts +6 -6
  40. package/dist/Field/textEntryTraits.js +6 -6
  41. package/dist/Layer/layerClickBoundary.d.ts +7 -1
  42. package/dist/Layer/layerClickBoundary.d.ts.map +1 -1
  43. package/dist/Layer/layerClickBoundary.js +2 -2
  44. package/dist/Layer/usePressPin.d.ts +95 -0
  45. package/dist/Layer/usePressPin.d.ts.map +1 -0
  46. package/dist/Layer/usePressPin.js +365 -0
  47. package/dist/Layer/useTouchTrigger.d.ts +23 -1
  48. package/dist/Layer/useTouchTrigger.d.ts.map +1 -1
  49. package/dist/Layer/useTouchTrigger.js +45 -7
  50. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  51. package/dist/Lightbox/Lightbox.js +2 -1
  52. package/dist/Link/Link.d.ts.map +1 -1
  53. package/dist/Link/Link.js +1 -0
  54. package/dist/Popover/usePopover.d.ts.map +1 -1
  55. package/dist/Popover/usePopover.js +56 -7
  56. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  57. package/dist/PowerSearch/PowerSearch.js +38 -30
  58. package/dist/ProgressBar/ProgressBar.d.ts +1 -1
  59. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  60. package/dist/ProgressBar/ProgressBar.js +52 -14
  61. package/dist/ProgressBar/ProgressBarTouch.d.ts +29 -0
  62. package/dist/ProgressBar/ProgressBarTouch.d.ts.map +1 -0
  63. package/dist/ProgressBar/ProgressBarTouch.js +91 -0
  64. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  65. package/dist/SegmentedControl/SegmentedControlItem.js +2 -1
  66. package/dist/Selector/Selector.d.ts.map +1 -1
  67. package/dist/Selector/Selector.js +25 -13
  68. package/dist/Slider/Slider.d.ts.map +1 -1
  69. package/dist/Slider/Slider.js +404 -131
  70. package/dist/Slider/sliderTouchController.d.ts +189 -0
  71. package/dist/Slider/sliderTouchController.d.ts.map +1 -0
  72. package/dist/Slider/sliderTouchController.js +1017 -0
  73. package/dist/Slider/useSliderTouch.d.ts +22 -0
  74. package/dist/Slider/useSliderTouch.d.ts.map +1 -0
  75. package/dist/Slider/useSliderTouch.js +56 -0
  76. package/dist/Switch/Switch.d.ts +1 -1
  77. package/dist/Switch/Switch.d.ts.map +1 -1
  78. package/dist/Switch/Switch.js +200 -46
  79. package/dist/Switch/switchFill.d.ts +31 -0
  80. package/dist/Switch/switchFill.d.ts.map +1 -0
  81. package/dist/Switch/switchFill.js +88 -0
  82. package/dist/Switch/switchTouch.d.ts +81 -0
  83. package/dist/Switch/switchTouch.d.ts.map +1 -0
  84. package/dist/Switch/switchTouch.js +133 -0
  85. package/dist/Switch/useSwitchTouch.d.ts +285 -0
  86. package/dist/Switch/useSwitchTouch.d.ts.map +1 -0
  87. package/dist/Switch/useSwitchTouch.js +998 -0
  88. package/dist/TabList/Tab.d.ts.map +1 -1
  89. package/dist/TabList/Tab.js +4 -3
  90. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  91. package/dist/Tokenizer/Tokenizer.js +12 -5
  92. package/dist/Tokenizer/TokenizerItemRemovableContext.d.ts +3 -0
  93. package/dist/Tokenizer/TokenizerItemRemovableContext.d.ts.map +1 -0
  94. package/dist/Tokenizer/TokenizerItemRemovableContext.js +17 -0
  95. package/dist/Tooltip/Tooltip.d.ts +30 -6
  96. package/dist/Tooltip/Tooltip.d.ts.map +1 -1
  97. package/dist/Tooltip/Tooltip.js +5 -0
  98. package/dist/Tooltip/index.d.ts +1 -1
  99. package/dist/Tooltip/index.d.ts.map +1 -1
  100. package/dist/Tooltip/useTooltip.d.ts +31 -5
  101. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  102. package/dist/Tooltip/useTooltip.js +59 -18
  103. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  104. package/dist/Typeahead/BaseTypeahead.js +19 -9
  105. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  106. package/dist/Typeahead/Typeahead.js +26 -15
  107. package/dist/astryx.css +91 -8
  108. package/dist/gestures/GestureArena.d.ts +21 -2
  109. package/dist/gestures/GestureArena.d.ts.map +1 -1
  110. package/dist/gestures/GestureArena.js +23 -3
  111. package/dist/gestures/GestureHost.d.ts +123 -3
  112. package/dist/gestures/GestureHost.d.ts.map +1 -1
  113. package/dist/gestures/GestureHost.js +797 -15
  114. package/dist/gestures/GestureRecognizer.d.ts +15 -1
  115. package/dist/gestures/GestureRecognizer.d.ts.map +1 -1
  116. package/dist/gestures/GestureRecognizer.js +12 -0
  117. package/dist/gestures/PanGestureRecognizer.d.ts +8 -0
  118. package/dist/gestures/PanGestureRecognizer.d.ts.map +1 -1
  119. package/dist/gestures/PinchGestureRecognizer.d.ts +5 -1
  120. package/dist/gestures/PinchGestureRecognizer.d.ts.map +1 -1
  121. package/dist/gestures/PinchGestureRecognizer.js +10 -0
  122. package/dist/gestures/PressTracker.d.ts +6 -0
  123. package/dist/gestures/PressTracker.d.ts.map +1 -1
  124. package/dist/gestures/PressTracker.js +3 -0
  125. package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts +3 -3
  126. package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts.map +1 -1
  127. package/dist/gestures/ScreenEdgePanGestureRecognizer.js +7 -5
  128. package/dist/gestures/nativeScroll.d.ts +16 -2
  129. package/dist/gestures/nativeScroll.d.ts.map +1 -1
  130. package/dist/gestures/nativeScroll.js +39 -8
  131. package/dist/gestures/recognizerInternals.d.ts +4 -2
  132. package/dist/gestures/recognizerInternals.d.ts.map +1 -1
  133. package/dist/gestures/types.d.ts +17 -0
  134. package/dist/gestures/types.d.ts.map +1 -1
  135. package/dist/gestures/types.js +7 -0
  136. package/dist/gestures/usePressTracker.d.ts.map +1 -1
  137. package/dist/gestures/usePressTracker.js +1 -0
  138. package/dist/gestures/wheelSegmenter.d.ts +56 -0
  139. package/dist/gestures/wheelSegmenter.d.ts.map +1 -0
  140. package/dist/gestures/wheelSegmenter.js +122 -0
  141. package/dist/hooks/useAttachment.d.ts +16 -8
  142. package/dist/hooks/useAttachment.d.ts.map +1 -1
  143. package/dist/hooks/useAttachment.js +35 -15
  144. package/dist/hooks/useFocusTrap.d.ts +15 -0
  145. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  146. package/dist/hooks/useFocusTrap.js +13 -2
  147. package/dist/hooks/usePager.d.ts +261 -0
  148. package/dist/hooks/usePager.d.ts.map +1 -0
  149. package/dist/hooks/usePager.js +1169 -0
  150. package/dist/motion/animator.d.ts.map +1 -1
  151. package/dist/motion/animator.js +140 -19
  152. package/dist/motion/paging.d.ts +69 -0
  153. package/dist/motion/paging.d.ts.map +1 -0
  154. package/dist/motion/paging.js +148 -0
  155. package/dist/motion/uikit/index.d.ts +8 -4
  156. package/dist/motion/uikit/index.d.ts.map +1 -1
  157. package/dist/motion/uikit/index.js +8 -4
  158. package/dist/motion/uikit/paging.d.ts +171 -0
  159. package/dist/motion/uikit/paging.d.ts.map +1 -0
  160. package/dist/motion/uikit/paging.js +192 -0
  161. package/dist/motion/uikit/progressBar.d.ts +79 -0
  162. package/dist/motion/uikit/progressBar.d.ts.map +1 -0
  163. package/dist/motion/uikit/progressBar.js +102 -0
  164. package/dist/motion/uikit/scroll.d.ts +0 -19
  165. package/dist/motion/uikit/scroll.d.ts.map +1 -1
  166. package/dist/motion/uikit/scroll.js +2 -21
  167. package/dist/motion/uikit/slider.d.ts +97 -0
  168. package/dist/motion/uikit/slider.d.ts.map +1 -0
  169. package/dist/motion/uikit/slider.js +111 -0
  170. package/dist/motion/uikit/springs.d.ts +59 -30
  171. package/dist/motion/uikit/springs.d.ts.map +1 -1
  172. package/dist/motion/uikit/springs.js +64 -34
  173. package/dist/motion/uikit/switch.d.ts +166 -0
  174. package/dist/motion/uikit/switch.d.ts.map +1 -0
  175. package/dist/motion/uikit/switch.js +186 -0
  176. package/dist/privateAttributes/pressHoldsDrag.stylex.d.ts +18 -0
  177. package/dist/privateAttributes/pressHoldsDrag.stylex.d.ts.map +1 -0
  178. package/dist/privateAttributes/pressHoldsDrag.stylex.js +28 -0
  179. package/dist/utils/pressFeedback.d.ts.map +1 -1
  180. package/dist/utils/pressFeedback.js +5 -0
  181. package/dist/utils/selectionLock.d.ts +43 -0
  182. package/dist/utils/selectionLock.d.ts.map +1 -1
  183. package/dist/utils/selectionLock.js +394 -6
  184. package/package.json +5 -5
  185. package/src/AppShell/AppShell.doc.mjs +10 -0
  186. package/src/AppShell/AppShell.tsx +48 -2
  187. package/src/Avatar/Avatar.tsx +3 -1
  188. package/src/BottomSheet/BottomSheetPanel.tsx +16 -9
  189. package/src/Breadcrumbs/BreadcrumbItem.tsx +4 -1
  190. package/src/Button/Button.tsx +3 -2
  191. package/src/Chat/Chat.doc.mjs +1 -0
  192. package/src/Chat/ChatComposerInput.doc.mjs +11 -0
  193. package/src/Chat/ChatComposerInput.tsx +11 -0
  194. package/src/Chat/ChatMessage.tsx +2 -2
  195. package/src/Chat/chatMessage.vars.stylex.ts +8 -8
  196. package/src/CommandPalette/CommandPaletteInput.tsx +6 -0
  197. package/src/DateInput/MonthScroller.tsx +63 -5
  198. package/src/DateInput/Wheel.tsx +174 -14
  199. package/src/DateRangeInput/DateRangeInput.doc.mjs +3 -2
  200. package/src/DateRangeInput/DateRangeInput.tsx +117 -71
  201. package/src/Dialog/Dialog.tsx +37 -1
  202. package/src/Drawer/Drawer.tsx +6 -0
  203. package/src/Drawer/useDrawerDialogPresence.ts +4 -0
  204. package/src/DropdownMenu/DropdownMenu.tsx +28 -6
  205. package/src/DropdownMenu/useMenuOverflow.ts +44 -8
  206. package/src/Field/PanelSearchInput.tsx +6 -0
  207. package/src/Field/textEntryTraits.ts +6 -6
  208. package/src/Layer/layerClickBoundary.ts +2 -2
  209. package/src/Layer/usePressPin.ts +516 -0
  210. package/src/Layer/useTouchTrigger.ts +56 -6
  211. package/src/Lightbox/Lightbox.tsx +2 -1
  212. package/src/Link/Link.tsx +3 -0
  213. package/src/Popover/usePopover.tsx +71 -9
  214. package/src/PowerSearch/PowerSearch.tsx +38 -31
  215. package/src/ProgressBar/ProgressBar.doc.mjs +7 -5
  216. package/src/ProgressBar/ProgressBar.tsx +147 -16
  217. package/src/ProgressBar/ProgressBarTouch.ts +109 -0
  218. package/src/SegmentedControl/SegmentedControlItem.tsx +2 -1
  219. package/src/Selector/Selector.tsx +29 -2
  220. package/src/Slider/Slider.doc.mjs +11 -7
  221. package/src/Slider/Slider.tsx +539 -132
  222. package/src/Slider/sliderTouchController.ts +1209 -0
  223. package/src/Slider/useSliderTouch.ts +78 -0
  224. package/src/Switch/Switch.doc.mjs +3 -3
  225. package/src/Switch/Switch.tsx +412 -86
  226. package/src/Switch/switchFill.ts +106 -0
  227. package/src/Switch/switchTouch.ts +194 -0
  228. package/src/Switch/useSwitchTouch.ts +1121 -0
  229. package/src/TabList/Tab.tsx +5 -3
  230. package/src/Tokenizer/Tokenizer.tsx +15 -5
  231. package/src/Tokenizer/TokenizerItemRemovableContext.ts +22 -0
  232. package/src/Tooltip/Tooltip.doc.mjs +21 -6
  233. package/src/Tooltip/Tooltip.tsx +42 -5
  234. package/src/Tooltip/index.ts +1 -0
  235. package/src/Tooltip/useTooltip.doc.mjs +4 -2
  236. package/src/Tooltip/useTooltip.tsx +84 -21
  237. package/src/Typeahead/BaseTypeahead.tsx +16 -8
  238. package/src/Typeahead/Typeahead.tsx +29 -15
  239. package/src/gestures/GestureArena.ts +45 -4
  240. package/src/gestures/GestureHost.ts +958 -15
  241. package/src/gestures/GestureRecognizer.ts +28 -0
  242. package/src/gestures/PanGestureRecognizer.ts +8 -0
  243. package/src/gestures/PinchGestureRecognizer.ts +16 -1
  244. package/src/gestures/PressTracker.ts +10 -0
  245. package/src/gestures/README.md +82 -3
  246. package/src/gestures/ScreenEdgePanGestureRecognizer.ts +7 -5
  247. package/src/gestures/nativeScroll.ts +49 -9
  248. package/src/gestures/recognizerInternals.ts +9 -2
  249. package/src/gestures/types.ts +18 -0
  250. package/src/gestures/usePressTracker.ts +2 -0
  251. package/src/gestures/wheelSegmenter.ts +143 -0
  252. package/src/hooks/useAttachment.ts +56 -14
  253. package/src/hooks/useFocusTrap.ts +26 -2
  254. package/src/hooks/usePager.ts +1446 -0
  255. package/src/motion/MEASUREMENTS.md +36 -27
  256. package/src/motion/README.md +7 -3
  257. package/src/motion/animator.ts +193 -25
  258. package/src/motion/paging.ts +161 -0
  259. package/src/motion/uikit/index.ts +7 -4
  260. package/src/motion/uikit/paging.ts +193 -0
  261. package/src/motion/uikit/progressBar.ts +102 -0
  262. package/src/motion/uikit/scroll.ts +2 -18
  263. package/src/motion/uikit/slider.ts +105 -0
  264. package/src/motion/uikit/springs.ts +53 -32
  265. package/src/motion/uikit/switch.ts +174 -0
  266. package/src/privateAttributes/pressHoldsDrag.stylex.ts +28 -0
  267. package/src/utils/pressFeedback.ts +5 -0
  268. package/src/utils/selectionLock.ts +501 -6
  269. package/dist/motion/selectPage.d.ts +0 -26
  270. package/dist/motion/selectPage.d.ts.map +0 -1
  271. package/dist/motion/selectPage.js +0 -55
  272. package/src/motion/selectPage.ts +0 -55
@@ -88,6 +88,7 @@ import {
88
88
  useComponentTranslator,
89
89
  } from '../i18n/useComponentTranslator';
90
90
  import english from '../i18n/generated-english/DateRangeInput/DateRangeInput.generated';
91
+ import {layerViewportInset} from '../Layer/layerViewportInset.stylex';
91
92
 
92
93
  registerEnglishFallback(english);
93
94
 
@@ -160,8 +161,28 @@ const styles = stylex.create({
160
161
  iconButtonDisabled: {
161
162
  cursor: 'default',
162
163
  },
164
+ // The popover content scrolls inside the layer's block cap, so a stacked
165
+ // calendar stays reachable on a short screen.
166
+ popoverScroll: {
167
+ overflowY: 'auto',
168
+ overscrollBehavior: 'contain',
169
+ maxBlockSize: stylex.firstThatWorks(
170
+ `calc(${layerViewportInset.maxBlockSize})`,
171
+ `calc(${layerViewportInset.maxBlockSizeFallback})`,
172
+ ),
173
+ },
174
+ // When the presets and a one-month calendar do not fit side by side, the
175
+ // row wraps and the presets stack above the calendar, in DOM order. The
176
+ // preset group draws a divider on its inline end and its block end. A
177
+ // negative margin moves each divider 1 border width past the group's margin
178
+ // box, and this row clips at its edges. Thus only the divider between the
179
+ // two parts shows: the inline-end one side by side, the block-end one when
180
+ // stacked. The row clips, not the scroller, so the overhang is never
181
+ // scrollable overflow.
163
182
  popoverLayout: {
164
183
  display: 'flex',
184
+ flexWrap: 'wrap',
185
+ overflow: 'clip',
165
186
  },
166
187
  presetSidebar: {
167
188
  display: 'flex',
@@ -171,8 +192,26 @@ const styles = stylex.create({
171
192
  borderInlineEndWidth: borderVars['--border-width'],
172
193
  borderInlineEndStyle: 'solid',
173
194
  borderInlineEndColor: colorVars['--color-border-emphasized'],
195
+ borderBlockEndWidth: borderVars['--border-width'],
196
+ borderBlockEndStyle: 'solid',
197
+ borderBlockEndColor: colorVars['--color-border-emphasized'],
198
+ marginInlineEnd: `calc(-1 * ${borderVars['--border-width']})`,
199
+ marginBlockEnd: `calc(-1 * ${borderVars['--border-width']})`,
200
+ // Alone on its line, the group fills the line, so its inline-end divider
201
+ // reaches the clipped edge.
202
+ flexGrow: 1,
174
203
  minWidth: 140,
175
204
  },
205
+ calendarPane: {
206
+ // Gives back the border width the preset group's margin takes, so the
207
+ // calendar keeps its side-by-side position.
208
+ marginInlineStart: borderVars['--border-width'],
209
+ // The row wraps on the calendar's one-month width, and the calendar then
210
+ // takes the free space, so its months sit in a row where they fit. The
211
+ // large factor keeps the preset group at its own width beside it.
212
+ flexBasis: 'min-content',
213
+ flexGrow: 1_000_000,
214
+ },
176
215
  presetButton: {
177
216
  display: 'block',
178
217
  width: '100%',
@@ -805,79 +844,86 @@ export function DateRangeInput({
805
844
  </VisuallyHidden>
806
845
  )}
807
846
  {popover.render(
808
- <div {...stylex.props(styles.popoverLayout)}>
809
- {presets && presets.length > 0 && (
810
- <div
811
- role="group"
812
- aria-label={t('@astryx.dateRangeInput.presetDateRanges')}
813
- {...mergeProps(
814
- themeProps('date-range-input-presets'),
815
- stylex.props(styles.presetSidebar),
816
- )}>
817
- {presets.map(preset => {
818
- const presetRange = preset.getRange();
819
- const isActive = isRangeEqual(value, presetRange);
820
- const isPresetDisabled =
821
- isDisabled ||
822
- !isRangeWithinSpan(presetRange, maxRangeSpan, minRangeSpan) ||
823
- isDateDisabled(plainDateFromISO(presetRange.start)) ||
824
- isDateDisabled(plainDateFromISO(presetRange.end));
825
- return (
826
- <button
827
- key={preset.label}
828
- type="button"
829
- // These presets are independent action buttons navigated by
830
- // Tab, not a single-tab-stop listbox — so they are a labeled
831
- // group of buttons, and the currently-applied preset is
832
- // marked with aria-current (not aria-selected, a listbox
833
- // concept that contradicted the Tab interaction) (forms-5).
834
- aria-current={isActive ? 'true' : undefined}
835
- // Focusable while disabled (AR3, spec:AST-085 FR1).
836
- aria-disabled={isPresetDisabled || undefined}
837
- aria-describedby={
838
- showsDisabledMessage
839
- ? disabledMessageTooltip.describedBy
840
- : undefined
841
- }
842
- onClick={() => {
843
- if (!isPresetDisabled) {
844
- handleRangeSelect(presetRange);
847
+ <div {...stylex.props(styles.popoverScroll)}>
848
+ <div {...stylex.props(styles.popoverLayout)}>
849
+ {presets && presets.length > 0 && (
850
+ <div
851
+ role="group"
852
+ aria-label={t('@astryx.dateRangeInput.presetDateRanges')}
853
+ {...mergeProps(
854
+ themeProps('date-range-input-presets'),
855
+ stylex.props(styles.presetSidebar),
856
+ )}>
857
+ {presets.map(preset => {
858
+ const presetRange = preset.getRange();
859
+ const isActive = isRangeEqual(value, presetRange);
860
+ const isPresetDisabled =
861
+ isDisabled ||
862
+ !isRangeWithinSpan(
863
+ presetRange,
864
+ maxRangeSpan,
865
+ minRangeSpan,
866
+ ) ||
867
+ isDateDisabled(plainDateFromISO(presetRange.start)) ||
868
+ isDateDisabled(plainDateFromISO(presetRange.end));
869
+ return (
870
+ <button
871
+ key={preset.label}
872
+ type="button"
873
+ // These presets are independent action buttons navigated by
874
+ // Tab, not a single-tab-stop listbox — so they are a labeled
875
+ // group of buttons, and the currently-applied preset is
876
+ // marked with aria-current (not aria-selected, a listbox
877
+ // concept that contradicted the Tab interaction) (forms-5).
878
+ aria-current={isActive ? 'true' : undefined}
879
+ // Focusable while disabled (AR3, spec:AST-085 FR1).
880
+ aria-disabled={isPresetDisabled || undefined}
881
+ aria-describedby={
882
+ showsDisabledMessage
883
+ ? disabledMessageTooltip.describedBy
884
+ : undefined
845
885
  }
846
- }}
847
- {...mergeProps(
848
- themeProps('date-range-input-preset', {
849
- selected: isActive ? 'selected' : null,
850
- disabled: isPresetDisabled ? 'disabled' : null,
851
- }),
852
- stylex.props(
853
- focusOutlineStyles.focusVisible,
854
- styles.presetButton,
855
- isActive && styles.presetButtonActive,
856
- isPresetDisabled && styles.presetButtonDisabled,
857
- ),
858
- )}>
859
- {preset.label}
860
- </button>
861
- );
862
- })}
863
- </div>
864
- )}
865
- {/* The layer renders its children open or closed, so mount the
886
+ onClick={() => {
887
+ if (!isPresetDisabled) {
888
+ handleRangeSelect(presetRange);
889
+ }
890
+ }}
891
+ {...mergeProps(
892
+ themeProps('date-range-input-preset', {
893
+ selected: isActive ? 'selected' : null,
894
+ disabled: isPresetDisabled ? 'disabled' : null,
895
+ }),
896
+ stylex.props(
897
+ focusOutlineStyles.focusVisible,
898
+ styles.presetButton,
899
+ isActive && styles.presetButtonActive,
900
+ isPresetDisabled && styles.presetButtonDisabled,
901
+ ),
902
+ )}>
903
+ {preset.label}
904
+ </button>
905
+ );
906
+ })}
907
+ </div>
908
+ )}
909
+ {/* The layer renders its children open or closed, so mount the
866
910
  calendar only while it is open. */}
867
- {popover.isOpen && (
868
- <Calendar
869
- mode="range"
870
- value={optimisticValue}
871
- onChange={handleRangeSelect}
872
- min={min}
873
- max={max}
874
- dateConstraints={dateConstraints}
875
- maxRangeSpan={maxRangeSpan}
876
- minRangeSpan={minRangeSpan}
877
- numberOfMonths={numberOfMonths}
878
- weekStartsOn={weekStartsOn}
879
- />
880
- )}
911
+ {popover.isOpen && (
912
+ <Calendar
913
+ mode="range"
914
+ value={optimisticValue}
915
+ onChange={handleRangeSelect}
916
+ min={min}
917
+ max={max}
918
+ dateConstraints={dateConstraints}
919
+ maxRangeSpan={maxRangeSpan}
920
+ minRangeSpan={minRangeSpan}
921
+ numberOfMonths={numberOfMonths}
922
+ weekStartsOn={weekStartsOn}
923
+ xstyle={presets && presets.length > 0 && styles.calendarPane}
924
+ />
925
+ )}
926
+ </div>
881
927
  </div>,
882
928
  {placement: 'below', alignment: 'start'},
883
929
  )}
@@ -80,7 +80,7 @@ import {themeProps} from '../utils/themeProps';
80
80
  import type {DialogVariantMap} from './index';
81
81
  import {focusOutlineProps} from '../utils/focusOutline.stylex';
82
82
  import {useInteractionModalityTracking} from '../utils/interactionModality';
83
- import {focusForLastInput} from '../utils/focusReturn';
83
+ import {focusForLastInput, isFocusLost} from '../utils/focusReturn';
84
84
  import {ModalAnnounceProvider} from '../hooks/ModalAnnounceProvider';
85
85
 
86
86
  import {useMergedRefs} from '../hooks/useMergedRefs';
@@ -634,6 +634,42 @@ export function Dialog({
634
634
  }
635
635
  }, [isOpen, isInline]);
636
636
 
637
+ // A dialog that unmounts while open (rendered only while open, or removed
638
+ // with the rest of a view) never runs the close branch above, and removing a
639
+ // <dialog> does not run the browser's own focus return. Focus left inside
640
+ // it, or dropped to the body by the removal, goes back to the opener.
641
+ useEffect(() => {
642
+ if (isInline) {
643
+ return;
644
+ }
645
+ const dialog = dialogRef.current;
646
+ // Read now: a removed iframe's document has no window, so its frame
647
+ // element can no longer be found once the dialog is gone.
648
+ const host = dialog?.ownerDocument.defaultView?.frameElement ?? null;
649
+ return () => {
650
+ const trigger = triggerElementRef.current;
651
+ if (dialog == null || trigger == null) {
652
+ return;
653
+ }
654
+ // Checked once the commit has finished: StrictMode's simulated unmount
655
+ // leaves the dialog in the document, and only a real removal returns
656
+ // focus.
657
+ queueMicrotask(() => {
658
+ if (dialog.isConnected || !trigger.isConnected) {
659
+ return;
660
+ }
661
+ // isFocusLost reads the dialog's own document. For a dialog in an
662
+ // iframe, the document that hosts the frame must also have lost focus
663
+ // or keep it on that frame, wherever the opener is: a control the
664
+ // user pressed outside the frame to remove the dialog, or the frame
665
+ // itself, keeps its focus.
666
+ if (isFocusLost(dialog) && (host == null || isFocusLost(host))) {
667
+ focusForLastInput(trigger);
668
+ }
669
+ });
670
+ };
671
+ }, [isInline]);
672
+
637
673
  // Host app-global surfaces (toasts) inside this modal while it is open, so
638
674
  // they stay visible and operable above it.
639
675
  useModalOutlet(dialogRef, isOpen && !isInline);
@@ -77,6 +77,7 @@ import {LayerDepthProvider, useLayerDismissal} from '../Layer';
77
77
  import {layerTextReset} from '../Layer/layerTextReset.stylex';
78
78
  import {layerStructureReset} from '../Layer/layerStructureReset.stylex';
79
79
  import {LayerContentBoundary} from '../Layer/layerScopedContext';
80
+ import {useModalOutlet} from '../Layer/modalOutlet';
80
81
  import {layerClickBoundary} from '../Layer/layerClickBoundary';
81
82
  import {ModalAnnounceProvider} from '../hooks/ModalAnnounceProvider';
82
83
  import {composeEventHandlers, mergeProps, themeProps} from '../utils';
@@ -497,6 +498,11 @@ export function Drawer({
497
498
  setIsRendered,
498
499
  });
499
500
 
501
+ // Toasts outside an open modal are inert, so the open drawer hosts them.
502
+ // It releases them when its exit starts.
503
+ // showModal() must run first, so keep this call after useDrawerDialogPresence.
504
+ useModalOutlet(dialogRef, isOpen && hasScrim);
505
+
500
506
  const handleDismiss = useCallback(() => {
501
507
  onOpenChange(false);
502
508
  }, [onOpenChange]);
@@ -32,6 +32,7 @@ import {
32
32
  type SetStateAction,
33
33
  } from 'react';
34
34
  import {flushSync} from 'react-dom';
35
+ import {notifyTopLayerEntry} from '../Layer/modalOutlet';
35
36
 
36
37
  /** Slack past the computed transition before the backstop gives up waiting. */
37
38
  const EXIT_BACKSTOP_BUFFER_MS = 50;
@@ -71,6 +72,9 @@ function showDrawerHost(dialog: HTMLDialogElement, isModal: boolean): void {
71
72
  dialog.showModal();
72
73
  } else if (typeof dialog.showPopover === 'function') {
73
74
  dialog.showPopover();
75
+ // The drawer is now the newest top-layer entry: global hosts shown before
76
+ // it (the toast viewport) re-enter above it, as they do above a modal.
77
+ notifyTopLayerEntry();
74
78
  } else {
75
79
  // Reduced fallback for browsers below the Popover API support floor.
76
80
  dialog.show();
@@ -533,6 +533,20 @@ function holdInvokerThroughPress(
533
533
  doc.addEventListener('pointercancel', onPressEnd, true);
534
534
  }
535
535
 
536
+ /**
537
+ * The name of a menu opened from a custom trigger: the caller's `aria-label`
538
+ * when it is a non-empty string, else the trigger's own accessible name. The
539
+ * popover and the bottom sheet both name the menu through this.
540
+ */
541
+ function resolveCustomTriggerMenuName(
542
+ callerLabel: unknown,
543
+ triggerName: string | undefined,
544
+ ): string | undefined {
545
+ return typeof callerLabel === 'string' && callerLabel.trim() !== ''
546
+ ? callerLabel
547
+ : triggerName;
548
+ }
549
+
536
550
  function DropdownMenuBottomSheet({
537
551
  button: buttonFromProps,
538
552
  renderTrigger,
@@ -596,11 +610,22 @@ function DropdownMenuBottomSheet({
596
610
  useEffect(() => {
597
611
  void preloadMenuBottomSheetActionList();
598
612
  }, []);
613
+ // The control's name is only readable from the DOM once it is rendered.
614
+ const [triggerName, setTriggerName] = useState<string | undefined>(undefined);
615
+ useIsomorphicLayoutEffect(() => {
616
+ if (renderTrigger != null && isOpen) {
617
+ setTriggerName(readAccessibleName(buttonRef.current));
618
+ }
619
+ }, [renderTrigger, isOpen]);
620
+ const rootTitle =
621
+ (renderTrigger != null
622
+ ? resolveCustomTriggerMenuName(rest['aria-label'], triggerName)
623
+ : undefined) ?? button.label;
599
624
  const currentSubmenu = submenuPath.at(-1);
600
625
  const currentItems = currentSubmenu?.items ?? items;
601
- const currentTitle = currentSubmenu?.label ?? button.label;
626
+ const currentTitle = currentSubmenu?.label ?? rootTitle;
602
627
  const sheetLabel =
603
- typeof currentTitle === 'string' ? currentTitle : button.label;
628
+ typeof currentTitle === 'string' ? currentTitle : rootTitle;
604
629
 
605
630
  const setOpen = useCallback(
606
631
  (nextIsOpen: boolean) => {
@@ -1040,12 +1065,9 @@ function DropdownMenuPopover({
1040
1065
  // The design system's button names the menu by its label. A custom trigger
1041
1066
  // names it by reference (or the caller's `aria-label` does), so that name
1042
1067
  // is read off the control as the menu opens.
1043
- const callerMenuLabel = rest['aria-label'];
1044
1068
  const menuLabel =
1045
1069
  renderTrigger != null
1046
- ? typeof callerMenuLabel === 'string' && callerMenuLabel.trim() !== ''
1047
- ? callerMenuLabel
1048
- : triggerName
1070
+ ? resolveCustomTriggerMenuName(rest['aria-label'], triggerName)
1049
1071
  : typeof button.label === 'string'
1050
1072
  ? button.label
1051
1073
  : undefined;
@@ -4,31 +4,62 @@
4
4
 
5
5
  /**
6
6
  * @file useMenuOverflow.ts
7
- * @input Menu element ref, rendered content, and open state
7
+ * @input Menu element ref, rendered content, open state, and optionally the
8
+ * overflow its owner expects from the content alone
8
9
  * @output Returns whether the open menu overflows its block-size constraint
9
10
  * @position Shared responsive helper for DropdownMenu and DropdownMenuSubMenu
10
11
  */
11
12
 
12
- import {useCallback, useState, type ReactNode, type RefObject} from 'react';
13
+ import {
14
+ useCallback,
15
+ useRef,
16
+ useState,
17
+ type ReactNode,
18
+ type RefObject,
19
+ } from 'react';
13
20
  import {useIsomorphicLayoutEffect} from '../hooks/useIsomorphicLayoutEffect';
14
21
 
15
- /** Detect block-axis overflow without making a fitting menu a scroll container. */
22
+ /**
23
+ * Detect block-axis overflow without making a fitting menu a scroll container.
24
+ *
25
+ * `expectedOverflow` is the answer the owner can give before any measure (a
26
+ * long option list cannot fit). Until a measure of the same `content` says
27
+ * otherwise, it is the result, so the first open already renders the right
28
+ * class: a class that flips after the open measure restyles the whole list
29
+ * a second time. A measure still runs on every open and corrects a wrong
30
+ * guess.
31
+ */
16
32
  export function useMenuOverflow(
17
33
  menuRef: RefObject<HTMLElement | null>,
18
34
  content: ReactNode,
19
35
  isOpen: boolean,
36
+ expectedOverflow?: boolean,
20
37
  ): boolean {
21
- const [hasOverflow, setHasOverflow] = useState(false);
38
+ // The content each measure stands for, kept only for an owner that gives
39
+ // an expected answer (a caller whose content is a new element on every
40
+ // render compares nothing); set before the measuring effects run.
41
+ const contentRef = useRef<ReactNode>(null);
42
+ useIsomorphicLayoutEffect(() => {
43
+ contentRef.current = expectedOverflow === undefined ? null : content;
44
+ });
45
+ const [measured, setMeasured] = useState<{
46
+ content: ReactNode;
47
+ hasOverflow: boolean;
48
+ } | null>(null);
22
49
 
23
50
  const measureOverflow = useCallback(() => {
24
51
  const menu = menuRef.current;
25
52
  if (!menu) {
26
53
  return;
27
54
  }
28
- const nextHasOverflow = menu.scrollHeight > menu.clientHeight + 1;
55
+ const hasOverflow = menu.scrollHeight > menu.clientHeight + 1;
29
56
  // eslint-disable-next-line @eslint-react/set-state-in-effect -- DOM overflow measurement controls whether this menu becomes a scroll container
30
- setHasOverflow(current =>
31
- current === nextHasOverflow ? current : nextHasOverflow,
57
+ setMeasured(current =>
58
+ current != null &&
59
+ current.hasOverflow === hasOverflow &&
60
+ Object.is(current.content, contentRef.current)
61
+ ? current
62
+ : {content: contentRef.current, hasOverflow},
32
63
  );
33
64
  }, [menuRef]);
34
65
 
@@ -73,5 +104,10 @@ export function useMenuOverflow(
73
104
  }
74
105
  }, [content, isOpen, measureOverflow]);
75
106
 
76
- return hasOverflow;
107
+ if (expectedOverflow === undefined) {
108
+ return measured?.hasOverflow ?? false;
109
+ }
110
+ return measured != null && Object.is(measured.content, content)
111
+ ? measured.hasOverflow
112
+ : expectedOverflow;
77
113
  }
@@ -57,6 +57,7 @@ import {
57
57
  typographyVars,
58
58
  } from '../theme/tokens.stylex';
59
59
  import type {BaseProps} from '../BaseProps';
60
+ import {textEntryTraitAttributes} from './textEntryTraits';
60
61
 
61
62
  const styles = stylex.create({
62
63
  // Gutter only. Matches the option list's own padding so the field below
@@ -273,6 +274,11 @@ export function PanelSearchInput({
273
274
  <input
274
275
  ref={mergedInputRef}
275
276
  type="text"
277
+ // Autocorrect would rewrite names and identifiers.
278
+ {...textEntryTraitAttributes({
279
+ enterKeyHint: 'search',
280
+ hasAutoCorrect: false,
281
+ })}
276
282
  aria-label={label}
277
283
  placeholder={placeholder}
278
284
  value={value}
@@ -3,16 +3,16 @@
3
3
  /**
4
4
  * @file textEntryTraits.ts
5
5
  * @input The keyboard-trait props a text field accepts
6
- * @output Exports the closed value unions EnterKeyHint, InputMode and
7
- * AutoCapitalize (the package entry exports these three), the fields' prop
8
- * mixin TextEntryTraitProps, and textEntryTraitAttributes, which maps the
9
- * props onto the native attributes of the editable element
10
- * @position Shared by TextInput and TextArea: the one place that says how a
11
- * field tells a touch keyboard what it holds
6
+ * @output EnterKeyHint, InputMode and AutoCapitalize (the package entry exports
7
+ * these), TextEntryTraitProps and textEntryTraitAttributes
8
+ * @position Shared by TextInput, TextArea and the search inputs: the one place
9
+ * that says how a field tells a touch keyboard what it holds
12
10
  *
13
11
  * SYNC: When modified, update:
14
12
  * - /packages/core/src/TextInput/TextInput.tsx and TextInput.doc.mjs
15
13
  * - /packages/core/src/TextArea/TextArea.tsx and TextArea.doc.mjs
14
+ * - /packages/core/src/Typeahead/BaseTypeahead.tsx, Field/PanelSearchInput.tsx
15
+ * and CommandPalette/CommandPaletteInput.tsx (the search inputs)
16
16
  * - /packages/core/src/Field/index.ts (type exports)
17
17
  */
18
18
 
@@ -6,7 +6,7 @@
6
6
  * @file layerClickBoundary.ts
7
7
  * @input React pointer, mouse, touch and click events on a layer root
8
8
  * @output Exports layerClickBoundary, the props every layer content root
9
- * spreads, including the layer-root mark
9
+ * spreads, including the layer-root mark; also stopInReactTree
10
10
  * @position Internal to Layer; used by useLayer's renderers and the Dialog,
11
11
  * Drawer, Lightbox, MobileNav, BottomSheetPanel and ToastViewport roots.
12
12
  * Not exported from the package.
@@ -61,7 +61,7 @@ const returnTrue = () => true;
61
61
  * React's dispatch asks `isPropagationStopped()` before each further
62
62
  * listener; the native event is left alone.
63
63
  */
64
- function stopInReactTree(event: SyntheticEvent): void {
64
+ export function stopInReactTree(event: SyntheticEvent): void {
65
65
  event.isPropagationStopped = returnTrue;
66
66
  }
67
67