@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
@@ -22,6 +22,7 @@ import { LayerDepthProvider } from "../Layer/LayerDepthContext.js";
22
22
  import "../theme/tokens.stylex.js";
23
23
  import { colorVars, spacingVars, radiusVars, shadowVars } from "../theme/tokens.stylex.js";
24
24
  import { Button } from "../Button/index.js";
25
+ import { FOCUSABLE_SELECTOR } from "../hooks/focusableSelector.js";
25
26
  import { rtlStyles } from "../utils/index.js";
26
27
  import { useTranslator } from "../i18n/index.js";
27
28
  import { useDevWarning } from "../hooks/useDevWarning.js";
@@ -29,6 +30,24 @@ import { mergeProps } from "../utils/mergeProps.js";
29
30
  import { themeProps } from "../utils/themeProps.js";
30
31
  import { stableClassName } from "../naming.js";
31
32
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
33
+ const FALLBACK_CLOSE_SELECTOR = '[data-astryx-popover-fallback-close]';
34
+ function attemptFocus(element) {
35
+ try {
36
+ element.focus();
37
+ } catch {
38
+ // Ignore non-focusable elements; the caller will try the next target.
39
+ }
40
+ return document.activeElement === element;
41
+ }
42
+ function focusFirstContentControl(container) {
43
+ const focusable = Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter(element => element.closest(FALLBACK_CLOSE_SELECTOR) == null);
44
+ for (const element of focusable) {
45
+ if (attemptFocus(element)) {
46
+ return true;
47
+ }
48
+ }
49
+ return false;
50
+ }
32
51
  const styles = {
33
52
  surface: {
34
53
  kWkggS: "x1prclbq",
@@ -39,6 +58,7 @@ const styles = {
39
58
  },
40
59
  contentWrapper: {
41
60
  kVAEAm: "x1n2onr6",
61
+ kI3sdo: "x1a2a7pz",
42
62
  $$css: true
43
63
  },
44
64
  closeButtonWrapper: {
@@ -129,6 +149,7 @@ function usePopoverImplementation(options = {}) {
129
149
 
130
150
  // Track whether to skip auto-focus for the current open event
131
151
  const skipAutoFocusRef = useRef(false);
152
+ const focusTargetRef = useRef('first');
132
153
 
133
154
  // Core layer for popover positioning
134
155
  const layer = useLayerInternal({
@@ -148,20 +169,40 @@ function usePopoverImplementation(options = {}) {
148
169
  isActive: layer.isOpen,
149
170
  onEscape: hasEscapeDismiss || hasLightDismiss ? layer.hide : undefined
150
171
  });
172
+ const focusInitialTarget = useCallback(() => {
173
+ const container = contentRef.current;
174
+ if (!container) {
175
+ return;
176
+ }
177
+ if (focusFirstContentControl(container)) {
178
+ return;
179
+ }
180
+ if (role === 'dialog') {
181
+ attemptFocus(container);
182
+ return;
183
+ }
184
+ focusFirst();
185
+ }, [contentRef, focusFirst, role]);
151
186
 
152
187
  // Auto-focus first element when popover opens (unless skipped)
153
188
  useEffect(() => {
154
189
  if (layer.isOpen && hasAutoFocus && !skipAutoFocusRef.current) {
155
190
  // Use requestAnimationFrame to ensure DOM is ready
156
191
  requestAnimationFrame(() => {
157
- focusFirst();
192
+ const container = contentRef.current;
193
+ if (focusTargetRef.current === 'container' && role === 'dialog' && container) {
194
+ attemptFocus(container);
195
+ } else {
196
+ focusInitialTarget();
197
+ }
158
198
  });
159
199
  }
160
- // Reset the skip flag after the effect runs
200
+ // Reset per-open focus preferences after the popover closes.
161
201
  if (!layer.isOpen) {
162
202
  skipAutoFocusRef.current = false;
203
+ focusTargetRef.current = 'first';
163
204
  }
164
- }, [layer.isOpen, hasAutoFocus, focusFirst]);
205
+ }, [contentRef, layer.isOpen, hasAutoFocus, focusInitialTarget, role]);
165
206
 
166
207
  // Combined ref for trigger element (layer anchor + our ref)
167
208
  const triggerRef = useCallback(el => {
@@ -172,20 +213,22 @@ function usePopoverImplementation(options = {}) {
172
213
  // Show function with optional skipAutoFocus
173
214
  const show = useCallback(showOptions => {
174
215
  skipAutoFocusRef.current = showOptions?.skipAutoFocus ?? false;
216
+ focusTargetRef.current = showOptions?.focusTarget ?? 'first';
175
217
  layer.show();
176
218
  }, [layer]);
177
219
 
178
220
  // Toggle function
179
- const toggle = useCallback(() => {
221
+ const toggleWithOptions = useCallback(showOptions => {
180
222
  if (layer.wasJustDismissed()) {
181
223
  return;
182
224
  }
183
225
  if (layer.isOpen) {
184
226
  layer.hide();
185
227
  } else {
186
- show();
228
+ show(showOptions);
187
229
  }
188
230
  }, [layer, show]);
231
+ const toggle = useCallback(() => toggleWithOptions(), [toggleWithOptions]);
189
232
 
190
233
  // ARIA attributes for the trigger
191
234
  const triggerProps = {
@@ -210,11 +253,13 @@ function usePopoverImplementation(options = {}) {
210
253
  role: role === 'dialog' ? 'dialog' : undefined,
211
254
  "aria-modal": role === 'dialog' && isModal ? true : undefined,
212
255
  "aria-label": role === 'dialog' ? dialogLabel : undefined,
256
+ tabIndex: role === 'dialog' ? -1 : undefined,
213
257
  ...mergeProps({
214
258
  ...surfaceProps,
215
259
  className: surfaceClassName
216
260
  }, stylex.props(styles.contentWrapper, hasSurface && styles.surface, xstyle), className, style),
217
261
  children: [children, hasCloseButton && /*#__PURE__*/_jsx("div", {
262
+ "data-astryx-popover-fallback-close": "",
218
263
  ...stylex.props(styles.closeButtonWrapper, rtlStyles.centerInline('100%')),
219
264
  children: /*#__PURE__*/_jsx(Button, {
220
265
  variant: "secondary",
@@ -235,6 +280,7 @@ function usePopoverImplementation(options = {}) {
235
280
  show,
236
281
  hide: layer.hide,
237
282
  toggle,
283
+ toggleWithOptions,
238
284
  wasJustDismissed: layer.wasJustDismissed,
239
285
  isOpen: layer.isOpen,
240
286
  id: layer.id,
@@ -245,6 +291,7 @@ function usePopoverImplementation(options = {}) {
245
291
  export function usePopover(options = {}) {
246
292
  const {
247
293
  wasJustDismissed: _,
294
+ toggleWithOptions: __,
248
295
  ...popover
249
296
  } = usePopoverImplementation(options);
250
297
  return popover;
@@ -20,8 +20,10 @@ import { type LayerPlacement } from '../Layer/useLayer';
20
20
  import type { SelectorOptionType, SelectorOptionData } from './types';
21
21
  import type { BaseProps } from '../BaseProps';
22
22
  import type { SizeValue } from '../utils/types';
23
+ import type { AdaptivePresentation } from '../hooks/useAdaptivePresentation';
23
24
  export type SelectorSize = 'sm' | 'md' | 'lg';
24
25
  export type SelectorVariant = 'input' | 'ghost';
26
+ export type SelectorPresentation = AdaptivePresentation;
25
27
  export type SelectorStatusType = 'warning' | 'error' | 'success';
26
28
  export interface SelectorStatus {
27
29
  /**
@@ -215,6 +217,14 @@ interface SelectorPropsBase<T extends SelectorOptionType = SelectorOptionType> e
215
217
  * `placement="above"` for bottom-fixed toolbars).
216
218
  */
217
219
  placement?: LayerPlacement;
220
+ /**
221
+ * How the option list is presented.
222
+ * - 'popover': anchored to the trigger
223
+ * - 'bottom-sheet': modal sheet suited to compact touch screens
224
+ * - 'adaptive': bottom sheet on compact coarse-pointer screens, otherwise popover
225
+ * @default 'popover'
226
+ */
227
+ presentation?: SelectorPresentation;
218
228
  /**
219
229
  * Whether the dropdown starts open on mount.
220
230
  * Useful for showcases and previews.
@@ -1 +1 @@
1
- {"version":3,"file":"Selector.d.ts","sourceRoot":"","sources":["../../src/Selector/Selector.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Selector.d.ts","sourceRoot":"","sources":["../../src/Selector/Selector.tsx"],"names":[],"mappings":"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"}
@@ -18,7 +18,6 @@
18
18
  */
19
19
  import React, { useCallback, useEffect, useId, useMemo, useOptimistic, useRef, useState, useTransition } from 'react';
20
20
  import * as stylex from '@stylexjs/stylex';
21
- import { usePopoverInternal } from "../Popover/usePopover.js";
22
21
  import { useTooltip } from "../Tooltip/index.js";
23
22
  import { Icon, renderIconSlot } from "../Icon/index.js";
24
23
  import { useIndicator } from "../Indicator/index.js";
@@ -48,6 +47,9 @@ import { groupStyles } from "../InputGroup/groupStyles.js";
48
47
  import { useInputGroup } from "../InputGroup/InputGroupContext.js";
49
48
  import { VisuallyHidden } from "../VisuallyHidden/index.js";
50
49
  import { useTranslator } from "../i18n/index.js";
50
+ import { SelectorBottomSheet } from "./SelectorBottomSheet.js";
51
+ import { useSelectorPresentation } from "./useSelectorPresentation.js";
52
+ import { selectorPresentationStyles } from "./selectorPresentation.stylex.js";
51
53
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
52
54
  const styles = {
53
55
  triggerContainer: {
@@ -316,12 +318,14 @@ export function Selector(props) {
316
318
  emptyText: emptyTextFromProps,
317
319
  emptySearchText: emptySearchTextFromProps,
318
320
  placement,
321
+ presentation = 'popover',
319
322
  isDefaultOpen = false,
320
323
  'data-testid': testId,
321
324
  width,
322
325
  xstyle,
323
326
  className,
324
327
  style,
328
+ onFocus,
325
329
  hasClear: hasClearProp,
326
330
  id,
327
331
  ...rest
@@ -426,26 +430,32 @@ export function Selector(props) {
426
430
  // Clear any lingering result count when the popover closes so stale status
427
431
  // text does not linger in the a11y tree.
428
432
  announce('');
429
- triggerRef.current?.focus();
430
433
  }, [announce]);
431
- const popover = usePopoverInternal({
434
+ const surface = useSelectorPresentation({
435
+ presentation,
432
436
  onHide: handleLayerHide,
433
- hasLightDismiss: true,
434
- hasCloseButton: false,
435
- hasAutoFocus: false,
436
- // The popup's own role="listbox" is the exposed semantics; the trigger
437
- // keeps DOM focus, so wrapping it in a modal dialog would misrepresent it.
438
- role: 'none',
439
- // The theme target belongs on the SURFACE that paints the popup, which
440
- // `usePopover` owns — not on the scrolling list inside it.
441
- surfaceTarget: 'selector-popup'
437
+ triggerRef,
438
+ popoverOptions: {
439
+ hasLightDismiss: true,
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: 'selector-popup'
448
+ }
442
449
  });
450
+ const {
451
+ popover
452
+ } = surface;
443
453
  const keepOpenProps = useKeepLayerOpenProps(popover.id, popover.isOpen);
444
454
 
445
455
  // Open dropdown on mount when isDefaultOpen is true
446
456
  useEffect(() => {
447
457
  if (isDefaultOpen) {
448
- popover.show();
458
+ surface.show();
449
459
  }
450
460
  // eslint-disable-next-line @eslint-react/exhaustive-deps -- mount-only: isDefaultOpen is not reactive
451
461
  }, []);
@@ -488,7 +498,7 @@ export function Selector(props) {
488
498
  // per arrival at that state rather than on every re-render.
489
499
  const announcedEmptyRef = useRef(null);
490
500
  useEffect(() => {
491
- const isPanelEmpty = popover.isOpen && !isLoading && searchQuery === '' && selectableItems.length === 0;
501
+ const isPanelEmpty = surface.isOpen && !isLoading && searchQuery === '' && selectableItems.length === 0;
492
502
  if (!isPanelEmpty) {
493
503
  announcedEmptyRef.current = null;
494
504
  return;
@@ -498,7 +508,7 @@ export function Selector(props) {
498
508
  }
499
509
  announcedEmptyRef.current = emptyAnnouncement;
500
510
  announce(emptyAnnouncement);
501
- }, [popover.isOpen, isLoading, searchQuery, selectableItems.length, emptyAnnouncement, announce]);
511
+ }, [surface.isOpen, isLoading, searchQuery, selectableItems.length, emptyAnnouncement, announce]);
502
512
 
503
513
  // Calculate offset to position selected item over trigger. Explicit
504
514
  // placement opts out of the selector-specific overlay behavior and uses the
@@ -567,17 +577,17 @@ export function Selector(props) {
567
577
  onItemMouseEnter
568
578
  } = useCombobox({
569
579
  selectableItems: filteredItems,
570
- wasJustDismissed: popover.wasJustDismissed,
580
+ wasJustDismissed: surface.wasJustDismissed,
571
581
  // The optimistic value, not the raw prop: with a pending changeAction the
572
582
  // prop still holds the old selection, so the popup would open with the
573
583
  // highlight on it and Delete/Backspace could clear a value the action has
574
584
  // already replaced.
575
585
  value: optimisticValue,
576
586
  isDisabled,
577
- isOpen: popover.isOpen,
587
+ isOpen: surface.isOpen,
578
588
  hasSearch,
579
589
  onOpen: useCallback(() => {
580
- popover.show();
590
+ surface.show();
581
591
  if (hasSearch) {
582
592
  requestAnimationFrame(() => {
583
593
  const input = searchRef.current;
@@ -589,8 +599,8 @@ export function Selector(props) {
589
599
  }
590
600
  });
591
601
  }
592
- }, [popover, hasSearch]),
593
- onClose: popover.hide,
602
+ }, [surface, hasSearch]),
603
+ onClose: surface.hide,
594
604
  onSelect: commitValue,
595
605
  onClear: hasClear ? clearValue : undefined,
596
606
  onSearchSeed: appendSearchQuery,
@@ -609,10 +619,10 @@ export function Selector(props) {
609
619
  // when closed — the optimistic one, so a pending changeAction cannot strand
610
620
  // cycling on the first match. -1 means nothing is selected or highlighted,
611
621
  // which the hook reads as "search from the top".
612
- getCurrentIndex: () => popover.isOpen ? highlightedIndex : selectedItemIndex,
622
+ getCurrentIndex: () => surface.isOpen ? highlightedIndex : selectedItemIndex,
613
623
  onMatch: index => {
614
624
  const item = selectableItems[index];
615
- if (popover.isOpen) {
625
+ if (surface.isOpen) {
616
626
  setHighlightedIndex(index);
617
627
  } else if (item.value !== optimisticValue) {
618
628
  commitValue(item.value);
@@ -635,13 +645,13 @@ export function Selector(props) {
635
645
  // cursor walks off-screen once navigation passes the visible window. Mirrors
636
646
  // CommandPaletteItem's scrollIntoView({block: 'nearest'}) behavior.
637
647
  useEffect(() => {
638
- if (!popover.isOpen || highlightedIndex < 0) {
648
+ if (!surface.isOpen || highlightedIndex < 0) {
639
649
  return;
640
650
  }
641
651
  document.getElementById(getItemId(highlightedIndex))?.scrollIntoView?.({
642
652
  block: 'nearest'
643
653
  });
644
- }, [popover.isOpen, highlightedIndex, getItemId]);
654
+ }, [surface.isOpen, highlightedIndex, getItemId]);
645
655
 
646
656
  // Handle clear button click
647
657
  const handleClear = useCallback(e => {
@@ -671,17 +681,17 @@ export function Selector(props) {
671
681
  label: t('@astryx.selector.searchOptions')
672
682
  }),
673
683
  ...themeProps('selector-search'),
674
- xstyle: variant !== 'ghost' && styles.searchRowInput
684
+ xstyle: surface.activePresentation === 'popover' && variant !== 'ghost' && styles.searchRowInput
675
685
  // When hasSearch is set, focus moves into this input on open, so it —
676
686
  // not the trigger — must be the combobox that reports the highlighted
677
687
  // option via aria-activedescendant (comboboxes-4). A bare searchbox
678
688
  // left the highlight silent to screen readers.
679
689
  ,
680
690
  role: "combobox",
681
- "aria-expanded": popover.isOpen,
691
+ "aria-expanded": surface.isOpen,
682
692
  "aria-controls": listboxId,
683
693
  "aria-autocomplete": "list",
684
- "aria-activedescendant": popover.isOpen && highlightedIndex >= 0 ? getItemId(highlightedIndex) : undefined,
694
+ "aria-activedescendant": surface.isOpen && highlightedIndex >= 0 ? getItemId(highlightedIndex) : undefined,
685
695
  value: searchQuery,
686
696
  onValueChange: handleSearchChange,
687
697
  onContainerKeyDown: e => {
@@ -724,7 +734,7 @@ export function Selector(props) {
724
734
  },
725
735
  placeholder: searchPlaceholder
726
736
  });
727
- }, [hasSearch, searchId, listboxId, searchQuery, hasQuery, searchPlaceholder, handleSearchChange, onKeyDown, popover.isOpen, highlightedIndex, getItemId, variant, t]);
737
+ }, [hasSearch, searchId, listboxId, searchQuery, hasQuery, searchPlaceholder, handleSearchChange, onKeyDown, surface.isOpen, surface.activePresentation, highlightedIndex, getItemId, variant, t]);
728
738
 
729
739
  // The single-selection mark, resolved from the theme once per render. A
730
740
  // theme that maps `check` to another indicator (a radio, say) changes every
@@ -917,6 +927,63 @@ export function Selector(props) {
917
927
  children: selectedItem?.label ?? placeholder
918
928
  })]
919
929
  });
930
+ const panelContent = hasSearch ? /*#__PURE__*/_jsxs("div", {
931
+ children: [renderSearch(), /*#__PURE__*/_jsx(Divider, {}), /*#__PURE__*/_jsx("div", {
932
+ ref: listboxRef,
933
+ id: listboxId,
934
+ role: "listbox",
935
+ "aria-labelledby": triggerId,
936
+ ...{
937
+ 0: {
938
+ className: "x9f619 xuyqlj2 x1odjw0f x1a2a7pz xu0wf1k x7a5moj x1hc1fzr x1ey1afo"
939
+ },
940
+ 1: {
941
+ className: "x9f619 xuyqlj2 x1odjw0f x1a2a7pz xu0wf1k x1hc1fzr x1ey1afo x2hg6jq"
942
+ }
943
+ }[!!(surface.activePresentation === 'popover' && variant !== 'ghost') << 0],
944
+ children: renderOptions()
945
+ })]
946
+ }) : /*#__PURE__*/_jsx("div", {
947
+ ref: listboxRef,
948
+ id: listboxId,
949
+ role: "listbox",
950
+ "aria-labelledby": triggerId,
951
+ "aria-activedescendant": surface.isOpen && highlightedIndex >= 0 ? getItemId(highlightedIndex) : undefined,
952
+ tabIndex: surface.activePresentation === 'bottom-sheet' ? 0 : undefined,
953
+ onKeyDown: surface.activePresentation === 'bottom-sheet' ? handleTriggerKeyDown : undefined,
954
+ ...{
955
+ 0: {
956
+ className: "x9f619 xuyqlj2 x1odjw0f x1a2a7pz xu0wf1k x7a5moj x1hc1fzr x1ey1afo"
957
+ },
958
+ 2: {
959
+ className: "x9f619 xuyqlj2 x1odjw0f x1a2a7pz xu0wf1k x1hc1fzr x1ey1afo x2hg6jq"
960
+ },
961
+ 1: {
962
+ className: "x9f619 xuyqlj2 x1odjw0f x1a2a7pz xu0wf1k x7a5moj xg01cxk xq2gx43"
963
+ },
964
+ 3: {
965
+ className: "x9f619 xuyqlj2 x1odjw0f x1a2a7pz xu0wf1k x2hg6jq xg01cxk xq2gx43"
966
+ }
967
+ }[!!(surface.activePresentation === 'popover' && variant !== 'ghost') << 1 | !!(surface.activePresentation === 'popover' && !isPositioned) << 0],
968
+ children: renderOptions()
969
+ });
970
+ const selectionSurface = surface.activePresentation === 'bottom-sheet' ? /*#__PURE__*/_jsx(SelectorBottomSheet, {
971
+ isOpen: surface.isSheetOpen,
972
+ onOpenChange: surface.onSheetOpenChange,
973
+ finalFocusRef: triggerRef,
974
+ initialFocusRef: hasSearch ? searchRef : listboxRef,
975
+ label: label,
976
+ children: panelContent
977
+ }) : popover.render(panelContent, {
978
+ placement: popoverPlacement,
979
+ alignment: 'start',
980
+ // The system's standard menu clearance, except in overlay mode:
981
+ // there the measured negative margin owns the block geometry and
982
+ // the menu is meant to sit on the trigger, not clear it.
983
+ offset: shouldOverlaySelectedItem ? undefined : spacingVars['--spacing-1'],
984
+ xstyle: [styles.popover, layerAnimations[popoverPlacement]],
985
+ style: popoverOffsetStyle
986
+ });
920
987
  const selectorContent = /*#__PURE__*/_jsxs(_Fragment, {
921
988
  children: [/*#__PURE__*/_jsxs("div", {
922
989
  ref: el => {
@@ -934,7 +1001,7 @@ export function Selector(props) {
934
1001
  size,
935
1002
  status: status?.type ?? null,
936
1003
  disabled: isDisabled ? 'disabled' : null
937
- }), 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, !selectedItem && styles.triggerPlaceholder, variant !== 'ghost' && status && inputStatusBorderStyles[status.type], variant !== 'ghost' && status && !isDisabled && inputStatusHoverShadowStyles[status.type], variant !== 'ghost' && inputGroup && groupStyles.inGroup, inputGroup && styles.triggerInGroup, xstyle), className, style),
1004
+ }), 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, !selectedItem && styles.triggerPlaceholder, variant !== 'ghost' && status && inputStatusBorderStyles[status.type], variant !== 'ghost' && status && !isDisabled && inputStatusHoverShadowStyles[status.type], variant !== 'ghost' && inputGroup && groupStyles.inGroup, inputGroup && styles.triggerInGroup, xstyle), className, style),
938
1005
  children: [startIcon && renderIconSlot(startIcon, {
939
1006
  size: 'sm',
940
1007
  color: 'secondary'
@@ -951,10 +1018,10 @@ export function Selector(props) {
951
1018
  ,
952
1019
  role: hasSearch ? undefined : 'combobox',
953
1020
  ...rest,
954
- "aria-haspopup": "listbox",
955
- "aria-expanded": popover.isOpen,
1021
+ "aria-haspopup": surface.activePresentation === 'bottom-sheet' ? 'dialog' : 'listbox',
1022
+ "aria-expanded": surface.isOpen,
956
1023
  "aria-controls": listboxId,
957
- "aria-activedescendant": !hasSearch && popover.isOpen && highlightedIndex >= 0 ? getItemId(highlightedIndex) : undefined,
1024
+ "aria-activedescendant": !hasSearch && surface.isOpen && highlightedIndex >= 0 ? getItemId(highlightedIndex) : undefined,
958
1025
  "aria-describedby": ariaDescribedBy,
959
1026
  "aria-labelledby": ariaLabelledBy,
960
1027
  "aria-required": isEffectivelyRequired ? 'true' : undefined,
@@ -967,6 +1034,10 @@ export function Selector(props) {
967
1034
  disabled: isDisabled && !showsDisabledMessage,
968
1035
  "aria-disabled": showsDisabledMessage ? 'true' : undefined,
969
1036
  onKeyDown: handleTriggerKeyDown,
1037
+ onFocus: event => {
1038
+ onFocus?.(event);
1039
+ surface.onTriggerFocus(event);
1040
+ },
970
1041
  tabIndex: isDisabled && !showsDisabledMessage ? -1 : 0,
971
1042
  ...{
972
1043
  0: {
@@ -1021,7 +1092,7 @@ export function Selector(props) {
1021
1092
  // the wrapper used to provide, so one element carries the mark,
1022
1093
  // its open/closed transform, and the theme target.
1023
1094
  ,
1024
- xstyle: [styles.triggerIcon, styles.triggerIconRotation, popover.isOpen && styles.triggerIconOpen]
1095
+ xstyle: [styles.triggerIcon, styles.triggerIconRotation, surface.isOpen && styles.triggerIconOpen]
1025
1096
  // Stable theme target on the chevron glyph itself, so a theme can
1026
1097
  // restyle just this icon (color, size, hover) — and its
1027
1098
  // open/closed state — via `defineTheme`. Same-element rules in
@@ -1029,55 +1100,10 @@ export function Selector(props) {
1029
1100
  // which a button-level target could not reach.
1030
1101
  ,
1031
1102
  ...themeProps('selector-indicator-icon', {
1032
- state: popover.isOpen ? 'expanded' : 'collapsed'
1103
+ state: surface.isOpen ? 'expanded' : 'collapsed'
1033
1104
  })
1034
1105
  })]
1035
- }), popover.render(hasSearch ? /*#__PURE__*/_jsxs("div", {
1036
- children: [renderSearch(), /*#__PURE__*/_jsx(Divider, {}), /*#__PURE__*/_jsx("div", {
1037
- ref: listboxRef,
1038
- id: listboxId,
1039
- role: "listbox",
1040
- "aria-labelledby": triggerId,
1041
- ...{
1042
- 0: {
1043
- className: "x9f619 xuyqlj2 x1odjw0f xu0wf1k x7a5moj x1hc1fzr x1ey1afo"
1044
- },
1045
- 1: {
1046
- className: "x9f619 xuyqlj2 x1odjw0f xu0wf1k x1hc1fzr x1ey1afo x2hg6jq"
1047
- }
1048
- }[!!(variant !== 'ghost') << 0],
1049
- children: renderOptions()
1050
- })]
1051
- }) : /*#__PURE__*/_jsx("div", {
1052
- ref: listboxRef,
1053
- id: listboxId,
1054
- role: "listbox",
1055
- "aria-labelledby": triggerId,
1056
- ...{
1057
- 0: {
1058
- className: "x9f619 xuyqlj2 x1odjw0f xu0wf1k x7a5moj x1hc1fzr x1ey1afo"
1059
- },
1060
- 2: {
1061
- className: "x9f619 xuyqlj2 x1odjw0f xu0wf1k x1hc1fzr x1ey1afo x2hg6jq"
1062
- },
1063
- 1: {
1064
- className: "x9f619 xuyqlj2 x1odjw0f xu0wf1k x7a5moj xg01cxk xq2gx43"
1065
- },
1066
- 3: {
1067
- className: "x9f619 xuyqlj2 x1odjw0f xu0wf1k x2hg6jq xg01cxk xq2gx43"
1068
- }
1069
- }[!!(variant !== 'ghost') << 1 | !!!isPositioned << 0],
1070
- children: renderOptions()
1071
- }), {
1072
- placement: popoverPlacement,
1073
- alignment: 'start',
1074
- // The system's standard menu clearance, except in overlay mode:
1075
- // there the measured negative margin owns the block geometry and
1076
- // the menu is meant to sit on the trigger, not clear it.
1077
- offset: shouldOverlaySelectedItem ? undefined : spacingVars['--spacing-1'],
1078
- xstyle: [styles.popover, layerAnimations[popoverPlacement]],
1079
- style: popoverOffsetStyle
1080
- }), showStatusTooltip && statusTooltip.renderTooltip(status?.message ?? ''), showsDisabledMessage && disabledMessageTooltip.renderTooltip(disabledMessage)]
1106
+ }), selectionSurface, showStatusTooltip && statusTooltip.renderTooltip(status?.message ?? ''), showsDisabledMessage && disabledMessageTooltip.renderTooltip(disabledMessage)]
1081
1107
  });
1082
1108
  if (inputGroup) {
1083
1109
  return selectorContent;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @file SelectorBottomSheet.tsx
3
+ * @input Uses BottomSheet, Section, and Heading
4
+ * @output Shared mobile listbox sheet for Selector and MultiSelector
5
+ * @position Internal presentation primitive for selection controls
6
+ */
7
+ import { type ReactNode, type RefObject } from 'react';
8
+ interface SelectorBottomSheetProps {
9
+ children: ReactNode;
10
+ finalFocusRef: RefObject<HTMLElement | null>;
11
+ initialFocusRef: RefObject<HTMLElement | null>;
12
+ isOpen: boolean;
13
+ label: string;
14
+ onOpenChange: (isOpen: boolean) => void;
15
+ }
16
+ export declare function SelectorBottomSheet({ children, finalFocusRef, initialFocusRef, isOpen, label, onOpenChange, }: SelectorBottomSheetProps): import("react").JSX.Element;
17
+ export declare namespace SelectorBottomSheet {
18
+ var displayName: string;
19
+ }
20
+ export {};
21
+ //# sourceMappingURL=SelectorBottomSheet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SelectorBottomSheet.d.ts","sourceRoot":"","sources":["../../src/Selector/SelectorBottomSheet.tsx"],"names":[],"mappings":"AAIA;;;;;GAKG;AAEH,OAAO,EAA4B,KAAK,SAAS,EAAE,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAsBhF,UAAU,wBAAwB;IAChC,QAAQ,EAAE,SAAS,CAAC;IACpB,aAAa,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC7C,eAAe,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC/C,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CACzC;AAsBD,wBAAgB,mBAAmB,CAAC,EAClC,QAAQ,EACR,aAAa,EACb,eAAe,EACf,MAAM,EACN,KAAK,EACL,YAAY,GACb,EAAE,wBAAwB,+BAyB1B;yBAhCe,mBAAmB"}
@@ -0,0 +1,83 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file SelectorBottomSheet.tsx
7
+ * @input Uses BottomSheet, Section, and Heading
8
+ * @output Shared mobile listbox sheet for Selector and MultiSelector
9
+ * @position Internal presentation primitive for selection controls
10
+ */
11
+ import { lazy, Suspense, useEffect } from 'react';
12
+ import * as stylex from '@stylexjs/stylex';
13
+ import { Heading } from "../Heading/index.js";
14
+ import { Section } from "../Section/index.js";
15
+ import "../theme/tokens.stylex.js";
16
+ import { spacingVars } from "../theme/tokens.stylex.js";
17
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
18
+ const LazyBottomSheet = /*#__PURE__*/lazy(async () => import("../BottomSheet/BottomSheet.js").then(module => ({
19
+ default: module.BottomSheet
20
+ })));
21
+ const styles = {
22
+ heading: {
23
+ k1K539: "x1p37lm5",
24
+ kUOVxO: "xam6yil",
25
+ $$css: true
26
+ }
27
+ };
28
+ function SelectorBottomSheetInitialFocus({
29
+ isOpen,
30
+ targetRef
31
+ }) {
32
+ useEffect(() => {
33
+ if (!isOpen) {
34
+ return;
35
+ }
36
+ const frame = requestAnimationFrame(() => {
37
+ targetRef.current?.focus({
38
+ preventScroll: true
39
+ });
40
+ });
41
+ return () => cancelAnimationFrame(frame);
42
+ }, [isOpen, targetRef]);
43
+ return null;
44
+ }
45
+ export function SelectorBottomSheet({
46
+ children,
47
+ finalFocusRef,
48
+ initialFocusRef,
49
+ isOpen,
50
+ label,
51
+ onOpenChange
52
+ }) {
53
+ return /*#__PURE__*/_jsx(Suspense, {
54
+ fallback: null,
55
+ children: /*#__PURE__*/_jsx(LazyBottomSheet, {
56
+ isOpen: isOpen,
57
+ onOpenChange: onOpenChange,
58
+ finalFocusRef: finalFocusRef,
59
+ label: label,
60
+ height: "hug",
61
+ purpose: "info",
62
+ children: /*#__PURE__*/_jsx(Section, {
63
+ paddingBlockStart: 4,
64
+ paddingBlockEnd: 0,
65
+ paddingInline: 1,
66
+ children: /*#__PURE__*/_jsxs("div", {
67
+ ...{
68
+ className: "xh8yej3"
69
+ },
70
+ children: [/*#__PURE__*/_jsx(Heading, {
71
+ level: 3,
72
+ xstyle: styles.heading,
73
+ children: label
74
+ }), children, /*#__PURE__*/_jsx(SelectorBottomSheetInitialFocus, {
75
+ isOpen: isOpen,
76
+ targetRef: initialFocusRef
77
+ })]
78
+ })
79
+ })
80
+ })
81
+ });
82
+ }
83
+ SelectorBottomSheet.displayName = 'SelectorBottomSheet';
@@ -3,7 +3,7 @@
3
3
  * @output Exports Selector and types
4
4
  * @position Public API entry point
5
5
  */
6
- export { Selector, type SelectorProps, type SelectorSize, type SelectorStatus, type SelectorStatusType, } from './Selector';
6
+ export { Selector, type SelectorProps, type SelectorPresentation, type SelectorSize, type SelectorStatus, type SelectorStatusType, } from './Selector';
7
7
  export { SelectorOption } from './SelectorOption';
8
8
  export type { SelectorOptionType, SelectorOptionData, SelectorDivider, SelectorSection, } from './types';
9
9
  export { useCombobox, useSelectedItemOffset } from './hooks';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/Selector/index.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AAEH,OAAO,EACL,QAAQ,EACR,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,kBAAkB,GACxB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAC;AAChD,YAAY,EACV,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,EACf,eAAe,GAChB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAC,WAAW,EAAE,qBAAqB,EAAC,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/Selector/index.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AAEH,OAAO,EACL,QAAQ,EACR,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,kBAAkB,GACxB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAC;AAChD,YAAY,EACV,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,EACf,eAAe,GAChB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAC,WAAW,EAAE,qBAAqB,EAAC,MAAM,SAAS,CAAC"}