@jobber/components 9.5.3 → 9.6.0

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 (178) hide show
  1. package/Combobox.d.ts +1 -0
  2. package/Combobox.js +17 -0
  3. package/dist/Autocomplete/index.cjs +0 -2
  4. package/dist/Autocomplete/index.mjs +0 -2
  5. package/dist/Autocomplete-cjs.js +4 -5
  6. package/dist/Autocomplete-es.js +1 -2
  7. package/dist/Card/index.cjs +2 -2
  8. package/dist/Card/index.mjs +2 -2
  9. package/dist/Chip/index.cjs +0 -2
  10. package/dist/Chip/index.mjs +0 -2
  11. package/dist/Chips/InternalChipDismissible/hooks/index.cjs +0 -2
  12. package/dist/Chips/InternalChipDismissible/hooks/index.mjs +0 -2
  13. package/dist/Chips/InternalChipDismissible/index.cjs +0 -2
  14. package/dist/Chips/InternalChipDismissible/index.mjs +0 -2
  15. package/dist/Chips/index.cjs +0 -2
  16. package/dist/Chips/index.mjs +0 -2
  17. package/dist/Combobox/Combobox.contentSlots.d.ts +5 -0
  18. package/dist/Combobox/Combobox.context.d.ts +34 -0
  19. package/dist/Combobox/Combobox.d.ts +69 -0
  20. package/dist/Combobox/Combobox.footer.d.ts +7 -0
  21. package/dist/Combobox/Combobox.items.d.ts +25 -0
  22. package/dist/Combobox/Combobox.responsive.d.ts +54 -0
  23. package/dist/Combobox/Combobox.selectAll.d.ts +4 -0
  24. package/dist/Combobox/Combobox.slotFactory.d.ts +7 -0
  25. package/dist/Combobox/Combobox.testUtils.d.ts +12 -0
  26. package/dist/Combobox/Combobox.types.d.ts +166 -0
  27. package/dist/Combobox/index.cjs +35 -0
  28. package/dist/Combobox/index.d.ts +2 -0
  29. package/dist/Combobox/index.mjs +29 -0
  30. package/dist/Combobox-cjs.js +675 -0
  31. package/dist/Combobox-es.js +673 -0
  32. package/dist/ComboboxPrimitive-cjs.js +88 -47
  33. package/dist/ComboboxPrimitive-es.js +39 -23
  34. package/dist/{ComboboxChipRemove-cjs.js → ComboboxSeparator-cjs.js} +501 -455
  35. package/dist/{ComboboxChipRemove-es.js → ComboboxSeparator-es.js} +500 -456
  36. package/dist/ConfirmationModal/index.cjs +0 -2
  37. package/dist/ConfirmationModal/index.mjs +0 -2
  38. package/dist/ContentBlock/index.cjs +1 -0
  39. package/dist/ContentBlock/index.mjs +1 -0
  40. package/dist/ContentBlock-cjs.js +2 -18
  41. package/dist/ContentBlock-es.js +1 -17
  42. package/dist/DataDump/index.cjs +2 -2
  43. package/dist/DataDump/index.mjs +2 -2
  44. package/dist/DataList/components/DataListActions/index.cjs +0 -2
  45. package/dist/DataList/components/DataListActions/index.mjs +0 -2
  46. package/dist/DataList/components/DataListBulkActions/index.cjs +0 -2
  47. package/dist/DataList/components/DataListBulkActions/index.mjs +0 -2
  48. package/dist/DataList/components/DataListFilters/components/DataListSort/index.cjs +0 -2
  49. package/dist/DataList/components/DataListFilters/components/DataListSort/index.mjs +0 -2
  50. package/dist/DataList/components/DataListFilters/index.cjs +0 -2
  51. package/dist/DataList/components/DataListFilters/index.mjs +0 -2
  52. package/dist/DataList/components/DataListHeader/index.cjs +0 -2
  53. package/dist/DataList/components/DataListHeader/index.mjs +0 -2
  54. package/dist/DataList/components/DataListItem/index.cjs +0 -2
  55. package/dist/DataList/components/DataListItem/index.mjs +0 -2
  56. package/dist/DataList/components/DataListItemActions/index.cjs +0 -2
  57. package/dist/DataList/components/DataListItemActions/index.mjs +0 -2
  58. package/dist/DataList/components/DataListItemActionsOverflow/index.cjs +0 -2
  59. package/dist/DataList/components/DataListItemActionsOverflow/index.mjs +0 -2
  60. package/dist/DataList/components/DataListItems/index.cjs +0 -2
  61. package/dist/DataList/components/DataListItems/index.mjs +0 -2
  62. package/dist/DataList/components/DataListLayout/index.cjs +0 -2
  63. package/dist/DataList/components/DataListLayout/index.mjs +0 -2
  64. package/dist/DataList/components/DataListLayoutActions/index.cjs +0 -2
  65. package/dist/DataList/components/DataListLayoutActions/index.mjs +0 -2
  66. package/dist/DataList/index.cjs +0 -2
  67. package/dist/DataList/index.mjs +0 -2
  68. package/dist/DatePicker/index.cjs +0 -2
  69. package/dist/DatePicker/index.mjs +0 -2
  70. package/dist/DatePicker-cjs.js +4 -5
  71. package/dist/DatePicker-es.js +1 -2
  72. package/dist/Dialog/index.cjs +2 -2
  73. package/dist/Dialog/index.mjs +2 -2
  74. package/dist/DrawerDescription-cjs.js +268 -99
  75. package/dist/DrawerDescription-es.js +270 -103
  76. package/dist/DrawerTitle-cjs.js +2 -6
  77. package/dist/DrawerTitle-es.js +3 -7
  78. package/dist/FieldDescription-cjs.js +184 -181
  79. package/dist/FieldDescription-es.js +187 -184
  80. package/dist/FilterPicker/components/FilterPickerActivator/index.cjs +0 -2
  81. package/dist/FilterPicker/components/FilterPickerActivator/index.mjs +0 -2
  82. package/dist/FilterPicker/components/FilterPickerContent/index.cjs +0 -2
  83. package/dist/FilterPicker/components/FilterPickerContent/index.mjs +0 -2
  84. package/dist/FilterPicker/components/FilterPickerTrigger/index.cjs +0 -2
  85. package/dist/FilterPicker/components/FilterPickerTrigger/index.mjs +0 -2
  86. package/dist/FilterPicker/index.cjs +0 -2
  87. package/dist/FilterPicker/index.mjs +0 -2
  88. package/dist/FilterPickerContent-cjs.js +4 -5
  89. package/dist/FilterPickerContent-es.js +1 -2
  90. package/dist/FormatFile/index.cjs +0 -2
  91. package/dist/FormatFile/index.mjs +0 -2
  92. package/dist/Gallery/index.cjs +0 -2
  93. package/dist/Gallery/index.mjs +0 -2
  94. package/dist/InputDate/index.cjs +0 -2
  95. package/dist/InputDate/index.mjs +0 -2
  96. package/dist/InputNumber/index.cjs +2 -2
  97. package/dist/InputNumber/index.mjs +2 -2
  98. package/dist/InputNumber/types.d.ts +5 -0
  99. package/dist/InputNumber-cjs.js +2 -2
  100. package/dist/InputNumber-es.js +2 -2
  101. package/dist/LightBox/index.cjs +0 -2
  102. package/dist/LightBox/index.mjs +0 -2
  103. package/dist/Menu/index.cjs +2 -2
  104. package/dist/Menu/index.mjs +2 -2
  105. package/dist/Menu-cjs.js +4 -5
  106. package/dist/Menu-es.js +1 -2
  107. package/dist/MenuSubmenuTrigger-cjs.js +206 -164
  108. package/dist/MenuSubmenuTrigger-es.js +194 -152
  109. package/dist/Modal/index.cjs +0 -2
  110. package/dist/Modal/index.mjs +1 -3
  111. package/dist/NumberFieldInput-cjs.js +187 -308
  112. package/dist/NumberFieldInput-es.js +187 -308
  113. package/dist/Page/index.cjs +2 -2
  114. package/dist/Page/index.mjs +2 -2
  115. package/dist/Popover/index.cjs +0 -2
  116. package/dist/Popover/index.mjs +0 -2
  117. package/dist/Popover-cjs.js +7 -8
  118. package/dist/Popover-es.js +1 -2
  119. package/dist/ScrollAreaViewport-cjs.js +747 -1093
  120. package/dist/ScrollAreaViewport-es.js +734 -1081
  121. package/dist/Select/index.cjs +2 -2
  122. package/dist/Select/index.mjs +2 -2
  123. package/dist/SelectGroupLabel-cjs.js +207 -306
  124. package/dist/SelectGroupLabel-es.js +203 -302
  125. package/dist/SelectPrimitive-cjs.js +11 -17
  126. package/dist/SelectPrimitive-es.js +1 -7
  127. package/dist/Tooltip/index.cjs +0 -2
  128. package/dist/Tooltip/index.mjs +0 -2
  129. package/dist/Tooltip-cjs.js +6 -7
  130. package/dist/Tooltip-es.js +1 -2
  131. package/dist/buttonRenderAdapter-cjs.js +2 -1
  132. package/dist/buttonRenderAdapter-es.js +2 -1
  133. package/dist/dialogReturnFocus-cjs.js +1 -1
  134. package/dist/dialogReturnFocus-es.js +2 -2
  135. package/dist/docs/InputNumber/InputNumber.md +2 -2
  136. package/dist/docs/SelectPrimitive/SelectPrimitive.md +1 -1
  137. package/dist/floating-ui.react-cjs.js +2557 -185
  138. package/dist/floating-ui.react-es.js +2512 -149
  139. package/dist/getMappedBreakpointWidth-cjs.js +21 -0
  140. package/dist/getMappedBreakpointWidth-es.js +18 -0
  141. package/dist/index.cjs +15 -10
  142. package/dist/index.d.mts +1 -0
  143. package/dist/index.d.ts +1 -0
  144. package/dist/index.mjs +14 -10
  145. package/dist/mergeClassName-cjs.js +12 -0
  146. package/dist/mergeClassName-es.js +10 -0
  147. package/dist/primitives/BottomSheet/index.cjs +1 -1
  148. package/dist/primitives/BottomSheet/index.mjs +1 -1
  149. package/dist/primitives/ComboboxPrimitive/ComboboxPrimitive.d.ts +2 -2
  150. package/dist/primitives/ComboboxPrimitive/index.cjs +4 -5
  151. package/dist/primitives/ComboboxPrimitive/index.mjs +4 -5
  152. package/dist/primitives/SelectPrimitive/index.cjs +1 -2
  153. package/dist/primitives/SelectPrimitive/index.mjs +1 -2
  154. package/dist/primitives/index.cjs +3 -3
  155. package/dist/primitives/index.mjs +3 -3
  156. package/dist/scrollEdges-cjs.js +1 -11
  157. package/dist/scrollEdges-es.js +1 -11
  158. package/dist/scrollable-cjs.js +58 -0
  159. package/dist/scrollable-es.js +54 -0
  160. package/dist/styles.css +759 -42
  161. package/dist/unstyledPrimitives/index.cjs +720 -478
  162. package/dist/unstyledPrimitives/index.mjs +663 -421
  163. package/dist/useCompositeListItem-cjs.js +2108 -361
  164. package/dist/useCompositeListItem-es.js +2076 -331
  165. package/dist/useLabel-cjs.js +2 -3
  166. package/dist/useLabel-es.js +2 -3
  167. package/dist/useRenderElement-cjs.js +9 -7
  168. package/dist/useRenderElement-es.js +9 -7
  169. package/dist/useScrollLock-cjs.js +819 -344
  170. package/dist/useScrollLock-es.js +732 -296
  171. package/dist/useScrollToActive-cjs.js +4 -5
  172. package/dist/useScrollToActive-es.js +1 -2
  173. package/dist/utils/meta/meta.json +43 -1
  174. package/package.json +8 -3
  175. package/dist/floating-ui.react-dom-cjs.js +0 -2103
  176. package/dist/floating-ui.react-dom-es.js +0 -2072
  177. package/dist/floating-ui.utils-cjs.js +0 -342
  178. package/dist/floating-ui.utils-es.js +0 -300
@@ -1,22 +1,21 @@
1
1
  import * as React from 'react';
2
- import { A as useCoreFilter, B as AriaCombobox, D as useComboboxInputValueContext, e as ComboboxTrigger, d as ComboboxInputGroup, p as ComboboxItem, l as ComboboxArrow, i as ComboboxBackdrop, x as ComboboxClear, v as ComboboxCollection, w as ComboboxEmpty, n as ComboboxGroup, o as ComboboxGroupLabel, m as ComboboxIcon, c as ComboboxInput, f as ComboboxList, k as ComboboxPopup, h as ComboboxPortal, j as ComboboxPositioner, u as ComboboxRow, g as ComboboxStatus, z as useFilteredItems, s as ComboboxChip, t as ComboboxChipRemove, r as ComboboxChips, q as ComboboxItemIndicator, a as ComboboxLabel, C as ComboboxRoot, b as ComboboxValue, y as useComboboxFilter } from '../ComboboxChipRemove-es.js';
3
- import { m as stringifyAsLabel, z as useCompositeListItem, b as useFieldRootContext, f as fieldValidityMapping, J as getDefaultLabelId } from '../useCompositeListItem-es.js';
2
+ import { B as useCoreFilter, D as AriaCombobox, E as useComboboxInputValueContext, e as ComboboxTrigger, d as ComboboxInputGroup, p as ComboboxItem, L as ListboxSeparator, l as ComboboxArrow, i as ComboboxBackdrop, x as ComboboxClear, v as ComboboxCollection, w as ComboboxEmpty, n as ComboboxGroup, o as ComboboxGroupLabel, m as ComboboxIcon, c as ComboboxInput, f as ComboboxList, k as ComboboxPopup, h as ComboboxPortal, j as ComboboxPositioner, u as ComboboxRow, g as ComboboxStatus, A as useFilteredItems, s as ComboboxChip, t as ComboboxChipRemove, r as ComboboxChips, q as ComboboxItemIndicator, a as ComboboxLabel, C as ComboboxRoot, y as ComboboxSeparator, b as ComboboxValue, z as useComboboxFilter } from '../ComboboxSeparator-es.js';
3
+ import { n as stringifyAsLabel, A as useCompositeListItem, M as DEFAULT_SIDES, b as useFieldRootContext, f as fieldValidityMapping, K as getDefaultLabelId } from '../useCompositeListItem-es.js';
4
4
  import { jsx, jsxs } from 'react/jsx-runtime';
5
- import { bn as pointer, a as useIsoLayoutEffect, b as useStableCallback, C as CommonPopupDataAttributes, k as useTimeout, u as useBaseUiId, b7 as useTriggerRegistration, d as createChangeEventDetails, bh as swipe, a5 as useAnimationFrame, o as ownerDocument, s as addEventListener, D as activeElement, _ as contains, g as getTarget, aG as isInteractiveElement, p as popupStateMapping, n as useControlled, a9 as itemPress, m as useTransitionStatus, f as useOpenChangeComplete, aJ as useAnimationsFinished, a4 as useDirection, a2 as inertValue, ax as mergeCleanups, bk as gecko, bo as scrub, a6 as webkit, E as createGenericEventDetails, P as useStore, b8 as triggerOpenStateMapping, t as transitionStatusMapping } from '../useScrollLock-es.js';
6
- import { S as Separator } from '../Separator-es.js';
7
- import { a as DialogBackdrop, c as DialogDescription, e as DialogHandle, b as DialogPopup, d as createDialogHandle, D as DrawerDescription } from '../DrawerDescription-es.js';
5
+ import { bX as pointer, c as useIsoLayoutEffect, d as useStableCallback, bf as CommonPopupDataAttributes, u as useBaseUiId, bS as useTriggerRegistration, o as ownerDocument, h as createChangeEventDetails, bj as swipe, ac as isVirtualClick, a8 as useAnimationFrame, q as useTimeout, I as getWindow, y as addEventListener, D as activeElement, g as getComputedStyle, a0 as contains, e as getTarget, i as isHTMLElement, bv as isInteractiveElement, b as getParentNode, a6 as popupStateMapping, v as useControlled, ad as itemPress, s as useTransitionStatus, l as useOpenChangeComplete, aw as round, bz as useAnimationsFinished, af as getSide, a7 as useDirection, a4 as inertValue, bm as mergeCleanups, bY as gecko, b$ as scrub, a9 as webkit, G as createGenericEventDetails, U as useStore, be as triggerOpenStateMapping, t as transitionStatusMapping, p as popupTransitionStateMapping } from '../useScrollLock-es.js';
6
+ import { b as DialogBackdrop, d as DialogDescription, f as DialogHandle, c as DialogPopup, a as DialogRoot, e as createDialogHandle, D as DrawerDescription, B as BasePopupHandle } from '../DrawerDescription-es.js';
8
7
  import { c as DialogClose, b as DialogTitle, a as DrawerClose, D as DrawerTitle } from '../DrawerTitle-es.js';
9
- import { j as DialogPortal, h as DialogRoot, i as DialogTrigger, k as DialogViewport, o as useDrawerProviderContext, q as DrawerBackdropCssVars, r as DrawerPopupCssVars, s as DrawerProviderContext, u as useDialogRootContext, t as useDrawerRootContext, v as useSwipeDismiss, w as getDisplacement, x as DrawerPopupDataAttributes, y as getElementTransform, z as DrawerVirtualKeyboardContext, A as getElementAtPoint, B as findScrollableTouchTarget, c as DrawerBackdrop, g as DrawerContent, e as DrawerPopup, b as DrawerPortal, a as DrawerRoot, D as DrawerTrigger, d as DrawerViewport, C as useScrollAreaRootContext, E as getOffset, F as ScrollAreaRootCssVars, G as scrollAreaStateAttributesMapping, H as useScrollAreaViewportContext, S as ScrollAreaRoot, f as ScrollAreaViewport } from '../ScrollAreaViewport-es.js';
10
- import { u as useRenderElement, f as formatErrorMessage, N as NOOP, E as EMPTY_OBJECT } from '../useRenderElement-es.js';
11
- import { K as getWindow, h as getComputedStyle, i as isHTMLElement, e as getParentNode, G as round } from '../floating-ui.utils-es.js';
8
+ import { i as DialogPortal, h as DialogTrigger, j as DialogViewport, r as useDrawerProviderContext, s as DrawerBackdropCssVars, t as DrawerPopupCssVars, v as DrawerProviderContext, u as useDialogRootContext, w as useDrawerRootContext, x as useSwipeDismiss, y as getDisplacement, z as DrawerPopupDataAttributes, A as getElementTransform, B as DrawerVirtualKeyboardContext, C as getElementAtPoint, c as DrawerBackdrop, g as DrawerContent, e as DrawerPopup, b as DrawerPortal, a as DrawerRoot, D as DrawerTrigger, d as DrawerViewport, E as useScrollAreaRootContext, F as getOffset, G as scrollAreaStateAttributesMapping, H as useScrollAreaViewportContext, S as ScrollAreaRoot, f as ScrollAreaViewport } from '../ScrollAreaViewport-es.js';
9
+ import { u as useRenderElement, N as NOOP, f as formatErrorMessage, E as EMPTY_OBJECT } from '../useRenderElement-es.js';
12
10
  import { c as clamp, g as getMaxScrollOffset, n as normalizeScrollOffset, S as SCROLL_EDGE_TOLERANCE_PX } from '../scrollEdges-es.js';
13
- import { u as useMenuRootContext, o as useMenuPositionerContext, p as useMenuItem, R as REGULAR_ITEM, q as itemMapping, r as MenuStore, h as MenuBackdrop, l as MenuGroup, n as MenuGroupLabel, b as MenuItem, a as MenuLinkItem, j as MenuPopup, g as MenuPortal, i as MenuPositioner, k as MenuRadioGroup, c as MenuRadioItem, d as MenuRadioItemIndicator, f as MenuRoot, m as MenuSubmenuRoot, e as MenuSubmenuTrigger, M as MenuTrigger } from '../MenuSubmenuTrigger-es.js';
11
+ import { f as findScrollableTouchTarget } from '../scrollable-es.js';
12
+ import { u as useMenuRootContext, o as useMenuPositionerContext, p as useMenuItem, R as REGULAR_ITEM, q as itemMapping, r as createNullMenuStore, h as MenuBackdrop, l as MenuGroup, n as MenuGroupLabel, b as MenuItem, a as MenuLinkItem, j as MenuPopup, g as MenuPortal, i as MenuPositioner, k as MenuRadioGroup, c as MenuRadioItem, d as MenuRadioItemIndicator, f as MenuRoot, m as MenuSubmenuRoot, e as MenuSubmenuTrigger, M as MenuTrigger } from '../MenuSubmenuTrigger-es.js';
14
13
  import * as ReactDOM from 'react-dom';
15
14
  import { m as usePreviousValue, n as useSelectRootContext, s as selectors, o as useSelectPositionerContext, j as SelectGroup, k as SelectGroupLabel, g as SelectItem, i as SelectItemIndicator, h as SelectItemText, f as SelectList, e as SelectPopup, a as SelectPortal, d as SelectPositioner, S as SelectRoot, b as SelectTrigger, c as SelectValue } from '../SelectGroupLabel-es.js';
16
- import { u as useNumberFieldRootContext, s as stateAttributesMapping$6, d as NumberFieldDecrement, a as NumberFieldGroup, c as NumberFieldIncrement, b as NumberFieldInput, N as NumberFieldRoot } from '../NumberFieldInput-es.js';
15
+ import { S as Separator } from '../Separator-es.js';
16
+ import { u as useNumberFieldRootContext, s as stateAttributesMapping$4, d as NumberFieldDecrement, a as NumberFieldGroup, c as NumberFieldIncrement, b as NumberFieldInput, N as NumberFieldRoot } from '../NumberFieldInput-es.js';
17
17
  import { u as useLabel } from '../useLabel-es.js';
18
18
  import '../stringifyLocale-es.js';
19
- import '../floating-ui.react-dom-es.js';
20
19
 
21
20
  function AutocompleteRoot(props) {
22
21
  const {
@@ -51,7 +50,9 @@ function AutocompleteRoot(props) {
51
50
  } else {
52
51
  resolvedInputValue = internalValue;
53
52
  }
54
- const collator = useCoreFilter();
53
+ const collator = useCoreFilter({
54
+ locale: other.locale
55
+ });
55
56
  const baseFilter = React.useMemo(() => {
56
57
  if (other.filter !== undefined) {
57
58
  return other.filter;
@@ -84,15 +85,7 @@ function AutocompleteRoot(props) {
84
85
  if (eventDetails.reason === pointer) {
85
86
  return;
86
87
  }
87
- if (enableInline) {
88
- if (highlightedValue == null) {
89
- setInlineInputValue('');
90
- } else {
91
- setInlineInputValue(stringifyAsLabel(highlightedValue, itemToStringValue));
92
- }
93
- } else {
94
- setInlineInputValue('');
95
- }
88
+ setInlineInputValue(enableInline && highlightedValue != null ? stringifyAsLabel(highlightedValue, itemToStringValue) : '');
96
89
  }
97
90
  return /*#__PURE__*/jsx(AriaCombobox, {
98
91
  ...other,
@@ -151,6 +144,14 @@ const AutocompleteInputGroup = ComboboxInputGroup;
151
144
  */
152
145
  const AutocompleteItem = ComboboxItem;
153
146
 
147
+ /**
148
+ * A visual separator between items or groups.
149
+ * Renders a `<div>` element.
150
+ *
151
+ * Documentation: [Base UI Autocomplete](https://base-ui.com/react/components/autocomplete)
152
+ */
153
+ const AutocompleteSeparator = ListboxSeparator;
154
+
154
155
  var index_parts$7 = /*#__PURE__*/Object.freeze({
155
156
  __proto__: null,
156
157
  Arrow: ComboboxArrow,
@@ -170,7 +171,7 @@ var index_parts$7 = /*#__PURE__*/Object.freeze({
170
171
  Positioner: ComboboxPositioner,
171
172
  Root: AutocompleteRoot,
172
173
  Row: ComboboxRow,
173
- Separator: Separator,
174
+ Separator: AutocompleteSeparator,
174
175
  Status: ComboboxStatus,
175
176
  Trigger: AutocompleteTrigger,
176
177
  Value: AutocompleteValue,
@@ -193,7 +194,7 @@ var index_parts$6 = /*#__PURE__*/Object.freeze({
193
194
  createHandle: createDialogHandle
194
195
  });
195
196
 
196
- const stateAttributesMapping$5 = {
197
+ const stateAttributesMapping$3 = {
197
198
  active(value) {
198
199
  if (value) {
199
200
  return {
@@ -264,12 +265,12 @@ const DrawerIndent = /*#__PURE__*/React.forwardRef(function DrawerIndent(compone
264
265
  [DrawerBackdropCssVars.swipeProgress]: '0'
265
266
  }
266
267
  }, elementProps],
267
- stateAttributesMapping: stateAttributesMapping$5
268
+ stateAttributesMapping: stateAttributesMapping$3
268
269
  });
269
270
  });
270
271
  if (process.env.NODE_ENV !== "production") DrawerIndent.displayName = "DrawerIndent";
271
272
 
272
- const stateAttributesMapping$4 = {
273
+ const stateAttributesMapping$2 = {
273
274
  active(value) {
274
275
  if (value) {
275
276
  return {
@@ -304,7 +305,7 @@ const DrawerIndentBackground = /*#__PURE__*/React.forwardRef(function DrawerInde
304
305
  ref: forwardedRef,
305
306
  state,
306
307
  props: elementProps,
307
- stateAttributesMapping: stateAttributesMapping$4
308
+ stateAttributesMapping: stateAttributesMapping$2
308
309
  });
309
310
  });
310
311
  if (process.env.NODE_ENV !== "production") DrawerIndentBackground.displayName = "DrawerIndentBackground";
@@ -313,37 +314,26 @@ function DrawerProvider(props) {
313
314
  const {
314
315
  children
315
316
  } = props;
316
- const [openById, setOpenById] = React.useState(() => new Map());
317
+ const [openDrawers, setOpenDrawers] = React.useState(() => new Set());
317
318
  const [visualStateStore] = React.useState(createVisualStateStore);
318
- const setDrawerOpen = useStableCallback((drawerId, open) => {
319
- setOpenById(prev => {
320
- const prevOpen = prev.get(drawerId);
321
- if (prevOpen === open) {
319
+ const setDrawerOpen = useStableCallback((drawer, open) => {
320
+ setOpenDrawers(prev => {
321
+ if (prev.has(drawer) === open) {
322
322
  return prev;
323
323
  }
324
- const next = new Map(prev);
325
- next.set(drawerId, open);
326
- return next;
327
- });
328
- });
329
- const removeDrawer = useStableCallback(drawerId => {
330
- setOpenById(prev => {
331
- if (!prev.has(drawerId)) {
332
- return prev;
324
+ const next = new Set(prev);
325
+ if (open) {
326
+ next.add(drawer);
327
+ } else {
328
+ next.delete(drawer);
333
329
  }
334
- const next = new Map(prev);
335
- next.delete(drawerId);
336
330
  return next;
337
331
  });
338
332
  });
339
- const active = React.useMemo(() => {
340
- for (const open of openById.values()) {
341
- if (open) {
342
- return true;
343
- }
344
- }
345
- return false;
346
- }, [openById]);
333
+ const removeDrawer = useStableCallback(drawer => {
334
+ setDrawerOpen(drawer, false);
335
+ });
336
+ const active = openDrawers.size > 0;
347
337
  const contextValue = React.useMemo(() => ({
348
338
  setDrawerOpen,
349
339
  removeDrawer,
@@ -433,7 +423,7 @@ const SWIPE_AREA_SWIPING_HOOK = {
433
423
  const SWIPE_AREA_DISABLED_HOOK = {
434
424
  [DrawerSwipeAreaDataAttributes.disabled]: ''
435
425
  };
436
- const stateAttributesMapping$3 = {
426
+ const stateAttributesMapping$1 = {
437
427
  open(value) {
438
428
  return value ? SWIPE_AREA_OPEN_HOOK : SWIPE_AREA_CLOSED_HOOK;
439
429
  },
@@ -441,9 +431,9 @@ const stateAttributesMapping$3 = {
441
431
  return value ? SWIPE_AREA_SWIPING_HOOK : null;
442
432
  },
443
433
  swipeDirection(value) {
444
- return value ? {
434
+ return {
445
435
  [DrawerSwipeAreaDataAttributes.swipeDirection]: value
446
- } : null;
436
+ };
447
437
  },
448
438
  disabled(value) {
449
439
  return value ? SWIPE_AREA_DISABLED_HOOK : null;
@@ -474,16 +464,14 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
474
464
  swipeDirection: swipeDirectionProp,
475
465
  ...elementProps
476
466
  } = componentProps;
477
- const {
478
- store
479
- } = useDialogRootContext();
467
+ const store = useDialogRootContext();
480
468
  const {
481
469
  swipeDirection,
482
- frontmostHeight
470
+ frontmostHeight,
471
+ swipeAreaActiveRef
483
472
  } = useDrawerRootContext();
484
473
  const providerContext = useDrawerProviderContext();
485
474
  const [swipeActive, setSwipeActive] = React.useState(false);
486
- const releaseDismissTimeout = useTimeout();
487
475
  const swipeAreaRef = React.useRef(null);
488
476
  const swipeStartEventRef = React.useRef(null);
489
477
  const openedBySwipeRef = React.useRef(false);
@@ -493,7 +481,10 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
493
481
  });
494
482
  const closedOffsetRef = React.useRef(null);
495
483
  const appliedSwipeStylesRef = React.useRef(false);
484
+ const swipePopupElementRef = React.useRef(null);
485
+ const swipeBackdropElementRef = React.useRef(null);
496
486
  const popupTransitionRef = React.useRef(null);
487
+ const releaseGuardCleanupRef = React.useRef(NOOP);
497
488
  const swipeAreaId = useBaseUiId(componentProps.id);
498
489
  const registerTrigger = useTriggerRegistration(swipeAreaId, store);
499
490
  const open = store.useState('open');
@@ -505,21 +496,40 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
505
496
  const dismissDirection = oppositeSwipeDirection[resolvedSwipeDirection];
506
497
  const enabled = !disabled && (!open || swipeActive);
507
498
  function disableDismissForSwipe() {
508
- releaseDismissTimeout.clear();
499
+ releaseGuardCleanupRef.current();
509
500
  store.context.outsidePressEnabledRef.current = false;
510
501
  }
511
- function enableDismissAfterRelease() {
512
- // Safari can dispatch outside-press for the same swipe-open gesture
513
- // after release, so defer re-enabling dismissal to the next macrotask.
514
- releaseDismissTimeout.start(0, () => {
502
+ const enableDismissAfterRelease = useStableCallback(() => {
503
+ releaseGuardCleanupRef.current();
504
+ const doc = ownerDocument(swipeAreaRef.current);
505
+ function restore(event) {
506
+ // The gesture's trailing release click is the one physical click with no `pointerdown` of
507
+ // its own. Ignore it and keep waiting, so it cannot dismiss the drawer it just opened,
508
+ // while a click-only activation (keyboard or assistive tech) still re-enables in time.
509
+ if (event?.type === 'click' && event.detail !== 0 && !isVirtualClick(event)) {
510
+ return;
511
+ }
512
+ releaseGuardCleanupRef.current = NOOP;
513
+ doc.removeEventListener('pointerdown', restore, true);
514
+ doc.removeEventListener('click', restore, true);
515
515
  store.context.outsidePressEnabledRef.current = true;
516
- });
517
- }
518
- function resolvePopupSize() {
519
- const popupElement = store.context.popupRef.current;
520
- if (!popupElement) {
521
- return null;
522
516
  }
517
+
518
+ // The pointerup that ends a swipe-open gesture synthesizes a `click`. When the drag released
519
+ // outside the popup (e.g. it was dragged past the popup's size), that click would be treated as
520
+ // an outside press and immediately dismiss the drawer that was just opened. Keep outside-press
521
+ // dismissal disabled until the next interaction that isn't that release click: a deliberate
522
+ // outside press starts with a `pointerdown`, and a click-only activation (keyboard or
523
+ // assistive tech) is distinguishable from a physical release. This is deterministic, unlike
524
+ // re-enabling on a timer that can race the synthesized click and dismiss at random.
525
+ //
526
+ // `restore` runs in document capture, ahead of floating-ui's own outside-press check (which
527
+ // happens on the event target, after capture), so the triggering press still dismisses.
528
+ releaseGuardCleanupRef.current = restore;
529
+ doc.addEventListener('pointerdown', restore, true);
530
+ doc.addEventListener('click', restore, true);
531
+ });
532
+ function getPopupSize(popupElement) {
523
533
  const isHorizontal = dismissDirection === 'left' || dismissDirection === 'right';
524
534
  const size = isHorizontal ? popupElement.offsetWidth : popupElement.offsetHeight;
525
535
  if (size <= 0) {
@@ -527,15 +537,15 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
527
537
  }
528
538
  return size;
529
539
  }
530
- function resolveClosedOffset() {
531
- const offset = resolvePopupSize();
540
+ function resolvePopupSize() {
541
+ const popupElement = store.context.popupRef.current;
542
+ return popupElement ? getPopupSize(popupElement) : null;
543
+ }
544
+ function resolveClosedOffset(popupElement) {
545
+ const offset = getPopupSize(popupElement);
532
546
  if (offset == null) {
533
547
  return null;
534
548
  }
535
- const popupElement = store.context.popupRef.current;
536
- if (!popupElement) {
537
- return offset;
538
- }
539
549
  const isHorizontal = dismissDirection === 'left' || dismissDirection === 'right';
540
550
  const transform = getElementTransform(popupElement);
541
551
  const transformOffset = isHorizontal ? transform.x : transform.y;
@@ -552,9 +562,6 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
552
562
  return popupSize * DEFAULT_SWIPE_OPEN_RATIO;
553
563
  }
554
564
  function applySwipeMovement() {
555
- if (!swipeActive) {
556
- return;
557
- }
558
565
  const popupElement = store.context.popupRef.current;
559
566
  if (!popupElement) {
560
567
  return;
@@ -563,10 +570,10 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
563
570
  return;
564
571
  }
565
572
  if (closedOffsetRef.current == null) {
566
- closedOffsetRef.current = resolveClosedOffset();
573
+ closedOffsetRef.current = resolveClosedOffset(popupElement);
567
574
  }
568
575
  const closedOffset = closedOffsetRef.current;
569
- if (!closedOffset || !Number.isFinite(closedOffset) || closedOffset <= 0) {
576
+ if (closedOffset === null) {
570
577
  return;
571
578
  }
572
579
  const {
@@ -587,6 +594,7 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
587
594
  popupElement.style.setProperty(DrawerPopupCssVars.swipeMovementX, `${movementX}px`);
588
595
  popupElement.style.setProperty(DrawerPopupCssVars.swipeMovementY, `${movementY}px`);
589
596
  popupElement.setAttribute(DrawerPopupDataAttributes.swiping, '');
597
+ swipePopupElementRef.current = popupElement;
590
598
  if (popupTransitionRef.current === null) {
591
599
  popupTransitionRef.current = popupElement.style.transition;
592
600
  }
@@ -594,6 +602,7 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
594
602
  const backdropElement = store.context.backdropRef.current;
595
603
  if (backdropElement) {
596
604
  backdropElement.setAttribute(DrawerPopupDataAttributes.swiping, '');
605
+ swipeBackdropElementRef.current = backdropElement;
597
606
  backdropElement.style.setProperty(DrawerBackdropCssVars.swipeProgress, `${backdropProgress}`);
598
607
  if (openProgress > 0 && frontmostHeight > 0) {
599
608
  backdropElement.style.setProperty(DrawerPopupCssVars.height, `${frontmostHeight}px`);
@@ -606,10 +615,11 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
606
615
  frontmostHeight: openProgress > 0 ? frontmostHeight : 0
607
616
  });
608
617
  appliedSwipeStylesRef.current = true;
618
+ swipeAreaActiveRef.current = true;
609
619
  }
610
620
  const clearSwipeStyles = useStableCallback(() => {
611
- const popupElement = store.context.popupRef.current;
612
- if (popupElement && appliedSwipeStylesRef.current) {
621
+ const popupElement = swipePopupElementRef.current;
622
+ if (popupElement) {
613
623
  popupElement.style.removeProperty(DrawerPopupCssVars.swipeMovementX);
614
624
  popupElement.style.removeProperty(DrawerPopupCssVars.swipeMovementY);
615
625
  popupElement.removeAttribute(DrawerPopupDataAttributes.swiping);
@@ -618,7 +628,7 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
618
628
  popupElement.style.transition = popupTransitionRef.current;
619
629
  popupTransitionRef.current = null;
620
630
  }
621
- const backdropElement = store.context.backdropRef.current;
631
+ const backdropElement = swipeBackdropElementRef.current;
622
632
  if (backdropElement) {
623
633
  backdropElement.removeAttribute(DrawerPopupDataAttributes.swiping);
624
634
  backdropElement.style.setProperty(DrawerBackdropCssVars.swipeProgress, '0');
@@ -629,19 +639,16 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
629
639
  frontmostHeight: 0
630
640
  });
631
641
  appliedSwipeStylesRef.current = false;
642
+ swipePopupElementRef.current = null;
643
+ swipeBackdropElementRef.current = null;
644
+ swipeAreaActiveRef.current = false;
632
645
  });
633
646
  function openDrawer(event) {
634
- if (store.select('open')) {
635
- return;
636
- }
637
647
  openedBySwipeRef.current = true;
638
- store.setOpen(true, createChangeEventDetails(swipe, event, swipeAreaRef.current ?? undefined));
648
+ store.setOpen(true, createChangeEventDetails(swipe, event, swipeAreaRef.current));
639
649
  }
640
650
  function closeDrawer(event) {
641
- if (!store.select('open')) {
642
- return;
643
- }
644
- store.setOpen(false, createChangeEventDetails(swipe, event, swipeAreaRef.current ?? undefined));
651
+ store.setOpen(false, createChangeEventDetails(swipe, event, swipeAreaRef.current));
645
652
  }
646
653
  function resetSwipeInteractionState() {
647
654
  swipeStartEventRef.current = null;
@@ -684,10 +691,10 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
684
691
  return;
685
692
  }
686
693
  const displacement = getDisplacement(resolvedSwipeDirection, details.deltaX, details.deltaY);
687
- if (displacement < MIN_SWIPE_START_DISTANCE && !openedBySwipeRef.current) {
694
+ if (!openedBySwipeRef.current && displacement < MIN_SWIPE_START_DISTANCE) {
688
695
  return;
689
696
  }
690
- if (!openedBySwipeRef.current) {
697
+ if (!openedBySwipeRef.current && !store.select('open')) {
691
698
  openDrawer(swipeStartEventRef.current);
692
699
  }
693
700
  applySwipeMovement();
@@ -703,14 +710,14 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
703
710
  const displacement = getDisplacement(resolvedSwipeDirection, deltaX, deltaY);
704
711
  const releaseVelocity = getDisplacement(resolvedSwipeDirection, releaseVelocityX, releaseVelocityY);
705
712
  const threshold = resolveSwipeOpenThreshold();
706
- const hasEnoughDistance = threshold != null && displacement >= threshold;
713
+ const hasEnoughDistance = displacement >= threshold;
707
714
  const hasEnoughVelocity = releaseVelocity >= VELOCITY_THRESHOLD;
708
- const shouldOpen = threshold != null && direction === resolvedSwipeDirection && (hasEnoughDistance || hasEnoughVelocity) && !disabled;
715
+ const shouldOpen = direction === resolvedSwipeDirection && (hasEnoughDistance || hasEnoughVelocity) && !disabled;
709
716
  if (shouldOpen) {
710
717
  if (!store.select('open')) {
711
718
  openDrawer(event);
712
719
  }
713
- } else if (openedBySwipeRef.current) {
720
+ } else if (openedBySwipeRef.current && store.select('open')) {
714
721
  closeDrawer(event);
715
722
  }
716
723
  finishSwipeInteraction();
@@ -721,16 +728,29 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
721
728
  const swipePointerProps = swipe$1.getPointerProps();
722
729
  const swipeTouchProps = swipe$1.getTouchProps();
723
730
  const resetSwipe = swipe$1.reset;
724
- React.useEffect(() => {
731
+
732
+ // The commit that opens the drawer re-renders the popup, resetting `--swipe-movement-*` to `0px`
733
+ // (the viewport isn't swiping). Re-assert after the DOM mutation but before paint. No deps: must
734
+ // run on every commit the swipe area participates in.
735
+ useIsoLayoutEffect(() => {
736
+ if (swipeActive && appliedSwipeStylesRef.current) {
737
+ applySwipeMovement();
738
+ }
739
+ });
740
+ useIsoLayoutEffect(() => {
725
741
  if (!enabled) {
742
+ if (swipeActive) {
743
+ enableDismissAfterRelease();
744
+ }
726
745
  resetSwipe();
727
746
  resetDragDelta();
728
747
  clearSwipeStyles();
729
748
  resetSwipeInteractionState();
730
749
  }
731
- }, [clearSwipeStyles, enabled, resetDragDelta, resetSwipe]);
750
+ }, [clearSwipeStyles, enableDismissAfterRelease, enabled, resetDragDelta, resetSwipe, swipeActive]);
732
751
  React.useEffect(() => {
733
752
  return () => {
753
+ releaseGuardCleanupRef.current();
734
754
  store.context.outsidePressEnabledRef.current = true;
735
755
  };
736
756
  }, [store]);
@@ -743,7 +763,7 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
743
763
  return useRenderElement('div', componentProps, {
744
764
  state,
745
765
  ref: [forwardedRef, swipeAreaRef, registerTrigger],
746
- stateAttributesMapping: stateAttributesMapping$3,
766
+ stateAttributesMapping: stateAttributesMapping$1,
747
767
  props: [{
748
768
  role: 'presentation',
749
769
  'aria-hidden': true,
@@ -803,6 +823,15 @@ const KEYBOARD_VISIBILITY_MARGIN = 16;
803
823
  // keyboard overlap, so the field can be scrolled clear of the keyboard instead of
804
824
  // ending up flush against it. Only applied when there is actual overlap.
805
825
  const KEYBOARD_SCROLL_SLACK = 48;
826
+ // Cadence of the settle-watching realign passes after focus moves with the keyboard open:
827
+ // long enough for a smooth scroll to show progress between passes, short enough to recover
828
+ // quickly from a scroll canceled by WebKit's reveal; the pass count covers CSS transitions
829
+ // reacting to the focus change (e.g. a 260ms footer resize) with room to spare.
830
+ const KEYBOARD_REALIGN_INTERVAL = 150;
831
+ const KEYBOARD_REALIGN_MAX_PASSES = 4;
832
+ // Frames the alignment waits for the scroll destination to stop moving (layout reacting to
833
+ // the focus change, e.g. a footer resizing over a CSS transition) before scrolling anyway.
834
+ const KEYBOARD_SETTLE_FRAME_LIMIT = 60;
806
835
  const INPUT_TAP_MOVE_THRESHOLD = 10;
807
836
  const INPUT_TAP_HIT_SLOP = 16;
808
837
  const KEYBOARD_INPUT_TYPES = new Set(['email', 'number', 'password', 'search', 'tel', 'text', 'url']);
@@ -826,11 +855,10 @@ function DrawerVirtualKeyboardProvider(props) {
826
855
  const {
827
856
  children
828
857
  } = props;
829
- const {
830
- store
831
- } = useDialogRootContext();
858
+ const store = useDialogRootContext();
832
859
  const open = store.useState('open');
833
860
  const mounted = store.useState('mounted');
861
+ const modal = store.useState('modal');
834
862
  const nestedOpenDialogCount = store.useState('nestedOpenDialogCount');
835
863
  const viewportElement = store.useState('viewportElement');
836
864
 
@@ -843,7 +871,9 @@ function DrawerVirtualKeyboardProvider(props) {
843
871
  const keyboardTouchStartRef = React.useRef(null);
844
872
  const focusedKeyboardTargetRef = React.useRef(null);
845
873
  const keyboardScrollAdjustmentRef = React.useRef(null);
874
+ const programmaticKeyboardFocusRef = React.useRef(false);
846
875
  const keyboardFocusFrame = useAnimationFrame();
876
+ const keyboardRealignTimeout = useTimeout();
847
877
  const restoreKeyboardScrollAdjustment = useStableCallback(() => {
848
878
  const adjustment = keyboardScrollAdjustmentRef.current;
849
879
  if (!adjustment) {
@@ -911,46 +941,100 @@ function DrawerVirtualKeyboardProvider(props) {
911
941
  const doc = ownerDocument(rootElement);
912
942
  const win = getWindow(rootElement);
913
943
  const visualViewport = win.visualViewport;
944
+
945
+ // Alignment scroll bookkeeping: destination stability, whether a scroll was issued,
946
+ // and the last observed progress so delayed passes can distinguish moving from stalled.
947
+ let keyboardScrollElement = null;
948
+ let keyboardScrollDestination = 0;
949
+ let keyboardScrollChecks = 0;
950
+ let keyboardScrollObserved = -1;
914
951
  const setDrawerKeyboardInset = inset => {
915
952
  rootElement.style.setProperty(DrawerViewportCssVars.keyboardInset, `${Math.max(0, Math.ceil(inset))}px`);
916
953
  };
917
- const resetDrawerKeyboardInset = () => {
918
- setDrawerKeyboardInset(0);
919
- };
920
954
  const clearFocusedKeyboardTarget = () => {
921
955
  focusedKeyboardTargetRef.current = null;
922
- resetDrawerKeyboardInset();
956
+ keyboardScrollElement = null;
957
+ setDrawerKeyboardInset(0);
923
958
  restoreKeyboardScrollAdjustment();
924
959
  keyboardFocusFrame.cancel();
960
+ keyboardRealignTimeout.clear();
961
+ };
962
+
963
+ // WebKit's native reveal scroll can move the page even while the scroll lock hides
964
+ // overflow (e.g. when the software keyboard's previous/next field arrows move focus).
965
+ // While the drawer is modal, any window scroll during keyboard interaction is spurious,
966
+ // so pin the page to the position it had when the drawer opened.
967
+ const baseScrollX = win.scrollX;
968
+ const baseScrollY = win.scrollY;
969
+ const restoreWindowScroll = () => {
970
+ if (modal !== true || nestedDrawerOpen || !focusedKeyboardTargetRef.current || getKeyboardVisualViewport(win) == null) {
971
+ return false;
972
+ }
973
+ if (win.scrollX !== baseScrollX || win.scrollY !== baseScrollY) {
974
+ // Force an instant jump: the two-argument form defaults `behavior` to `auto`, which
975
+ // obeys the page's `scroll-behavior`, so a global `scroll-behavior: smooth` would
976
+ // animate the restore. The measurements that follow assume the page is already back
977
+ // at rest, and a smooth restore also re-emits `scroll`, re-entering this handler.
978
+ win.scrollTo({
979
+ left: baseScrollX,
980
+ top: baseScrollY,
981
+ behavior: 'instant'
982
+ });
983
+ return true;
984
+ }
985
+ return false;
986
+ };
987
+
988
+ // Focus moved by the drawer itself goes through `focusKeyboardInputWithoutPageScroll`,
989
+ // but native focus changes (the iOS keyboard's previous/next field arrows) commit
990
+ // WebKit's reveal scroll before `focusin` reaches us — cancelling it afterwards
991
+ // visibly jitters the sheet. `focusout` on the outgoing field fires before focus
992
+ // lands, so override the incoming field's geometry there and restore it in `focusin`.
993
+ let restorePreemptedFocus = null;
994
+ const consumePreemptedFocus = () => {
995
+ restorePreemptedFocus?.();
996
+ restorePreemptedFocus = null;
997
+ };
998
+ const preemptFocusReveal = (target, keyboardViewport) => {
999
+ consumePreemptedFocus();
1000
+
1001
+ // Native focus carries no preventScroll, so a fake off-screen rect would make WebKit
1002
+ // scroll the drawer's own scroll containers thousands of pixels chasing it. Presenting
1003
+ // the field as already centered in the visible band above the keyboard instead makes
1004
+ // both the in-page reveal and the viewport pan no-ops.
1005
+ const rect = target.getBoundingClientRect();
1006
+ restorePreemptedFocus = overrideGeometryDuringFocus(target, (keyboardViewport.top + keyboardViewport.bottom - rect.top - rect.bottom) / 2);
925
1007
  };
926
1008
  const alignFocusedKeyboardTarget = () => {
1009
+ // If focus never lands on a preempted target, the focusout-scheduled alignment still
1010
+ // restores it on the next frame before paint.
1011
+ consumePreemptedFocus();
927
1012
  const target = focusedKeyboardTargetRef.current;
928
1013
  // If the focused field is removed from the DOM without firing `focusout` (e.g. it is
929
1014
  // conditionally rendered away), any applied scroll slack is restored here on the next
930
1015
  // focus/viewport event or when the drawer closes. This self-corrects rather than
931
1016
  // tracking each field's lifecycle.
932
1017
  if (nestedDrawerOpen || !target || !contains(rootElement, target)) {
933
- resetDrawerKeyboardInset();
1018
+ setDrawerKeyboardInset(0);
934
1019
  restoreKeyboardScrollAdjustment();
935
1020
  return;
936
1021
  }
1022
+
1023
+ // Undo any reveal scroll WebKit applied to the locked page before measuring, so the
1024
+ // keyboard inset and alignment are computed against the resting viewport.
1025
+ restoreWindowScroll();
937
1026
  const keyboardViewport = getKeyboardVisualViewport(win);
938
1027
  if (!keyboardViewport) {
939
- resetDrawerKeyboardInset();
1028
+ setDrawerKeyboardInset(0);
940
1029
  restoreKeyboardScrollAdjustment();
941
1030
  return;
942
1031
  }
943
- setDrawerKeyboardInset(getDrawerKeyboardInset(win, keyboardViewport));
1032
+ setDrawerKeyboardInset(Math.max(0, win.innerHeight - keyboardViewport.bottom));
944
1033
  const scrollTarget = findKeyboardScrollTarget(target, rootElement);
945
1034
  if (!scrollTarget) {
946
1035
  restoreKeyboardScrollAdjustment();
947
1036
  return;
948
1037
  }
949
- if (!scrollTarget.isConnected || !contains(rootElement, scrollTarget)) {
950
- resetDrawerKeyboardInset();
951
- restoreKeyboardScrollAdjustment();
952
- return;
953
- }
954
1038
  const scrollTargetRect = scrollTarget.getBoundingClientRect();
955
1039
  const clippedBottom = Math.min(scrollTargetRect.bottom, keyboardViewport.bottom);
956
1040
  const overlap = Math.max(0, scrollTargetRect.bottom - keyboardViewport.bottom);
@@ -966,14 +1050,70 @@ function DrawerVirtualKeyboardProvider(props) {
966
1050
  return;
967
1051
  }
968
1052
  const targetRect = target.getBoundingClientRect();
969
- const targetCenter = (targetRect.top + targetRect.bottom) / 2;
970
- const visibleCenter = (visibleTop + visibleBottom) / 2;
971
- const nextScrollTop = scrollTarget.scrollTop + targetCenter - visibleCenter;
972
- animateKeyboardScroll(scrollTarget, clamp(nextScrollTop, 0, maxScrollTop));
1053
+ const nextScrollTop = scrollTarget.scrollTop + (targetRect.top + targetRect.bottom - visibleTop - visibleBottom) / 2;
1054
+ const destination = Math.round(clamp(nextScrollTop, 0, maxScrollTop));
1055
+ const settled = keyboardScrollElement === scrollTarget && Math.abs(keyboardScrollDestination - destination) <= 1;
1056
+ if (!settled) {
1057
+ // Commit the scroll only once the destination holds across two consecutive checks.
1058
+ // Layout may still be reacting to the focus change (e.g. a footer sized by
1059
+ // `:focus-within` resizing over a transition), and scrolling toward a
1060
+ // mid-transition destination overshoots, then visibly pulls back once corrected.
1061
+ // The destination is scroll-position-invariant, so an in-flight scroll of the
1062
+ // container itself never defers the commit.
1063
+ const checks = keyboardScrollElement === scrollTarget ? keyboardScrollChecks + 1 : 1;
1064
+ keyboardScrollElement = scrollTarget;
1065
+ keyboardScrollDestination = destination;
1066
+ keyboardScrollChecks = checks;
1067
+ keyboardScrollObserved = -1;
1068
+ // Re-check next frame; give up and scroll anyway if layout never settles.
1069
+ if (checks <= KEYBOARD_SETTLE_FRAME_LIMIT) {
1070
+ keyboardFocusFrame.request(alignFocusedKeyboardTarget);
1071
+ return;
1072
+ }
1073
+ } else if (keyboardScrollObserved >= 0) {
1074
+ // A scroll toward this destination is already out. Leave it alone while it has
1075
+ // arrived or is still progressing — re-issuing restarts the smooth-scroll easing
1076
+ // curve, a visible stutter on every realign pass — and re-issue only when it
1077
+ // stalled short (WebKit cancels in-flight smooth scrolls when its native reveal
1078
+ // for the focus resolves).
1079
+ const current = scrollTarget.scrollTop;
1080
+ if (Math.abs(current - destination) <= 1) {
1081
+ return;
1082
+ }
1083
+ if (current !== keyboardScrollObserved) {
1084
+ keyboardScrollObserved = current;
1085
+ return;
1086
+ }
1087
+ }
1088
+ keyboardScrollElement = scrollTarget;
1089
+ keyboardScrollDestination = destination;
1090
+ keyboardScrollChecks = 0;
1091
+ keyboardScrollObserved = scrollTarget.scrollTop;
1092
+ animateKeyboardScroll(scrollTarget, destination);
973
1093
  };
974
1094
  const scheduleKeyboardFocusAlignment = () => {
975
1095
  keyboardFocusFrame.request(alignFocusedKeyboardTarget);
976
1096
  };
1097
+
1098
+ // When focus moves with the keyboard already up, no viewport resize events follow to
1099
+ // re-run alignment, and the single frame-scheduled pass is unreliable: WebKit resolves
1100
+ // the reveal for the focus asynchronously (a UI-process round trip) and cancels any
1101
+ // in-flight scroll the pass started when it lands, and layout reacting to the focus
1102
+ // change (e.g. a footer sized by `:focus-within` and the keyboard inset) settles only
1103
+ // after its transition, so the pass measures stale geometry. Re-align on an interval
1104
+ // until both settle; the alignment's scroll bookkeeping observes before re-issuing,
1105
+ // so passes that find the scroll on course are inert.
1106
+ const scheduleDelayedKeyboardRealign = () => {
1107
+ let remainingPasses = KEYBOARD_REALIGN_MAX_PASSES;
1108
+ const realign = () => {
1109
+ alignFocusedKeyboardTarget();
1110
+ remainingPasses -= 1;
1111
+ if (remainingPasses > 0) {
1112
+ keyboardRealignTimeout.start(KEYBOARD_REALIGN_INTERVAL, realign);
1113
+ }
1114
+ };
1115
+ keyboardRealignTimeout.start(KEYBOARD_REALIGN_INTERVAL, realign);
1116
+ };
977
1117
  const captureFocusedKeyboardTarget = eventTarget => {
978
1118
  if (nestedDrawerOpen) {
979
1119
  return false;
@@ -985,16 +1125,56 @@ function DrawerVirtualKeyboardProvider(props) {
985
1125
  if (!target || !contains(rootElement, target)) {
986
1126
  return false;
987
1127
  }
1128
+
1129
+ // A new field starts a fresh alignment; the scroll bookkeeping must not carry
1130
+ // over from the previous field's scroll.
1131
+ if (focusedKeyboardTargetRef.current !== target) {
1132
+ keyboardScrollElement = null;
1133
+ }
988
1134
  focusedKeyboardTargetRef.current = target;
989
1135
  return true;
990
1136
  };
991
1137
  const handleFocusIn = event => {
992
- if (captureFocusedKeyboardTarget(getTarget(event))) {
993
- scheduleKeyboardFocusAlignment();
1138
+ // The programmatic transition is over once focus lands, which happens before
1139
+ // `.focus()` returns. Any later `focusout` is the consumer's own — an `onFocus`
1140
+ // handler that blurs or moves focus — and must be reconciled rather than
1141
+ // suppressed, so stop suppressing here instead of waiting for `.focus()` to return.
1142
+ programmaticKeyboardFocusRef.current = false;
1143
+ consumePreemptedFocus();
1144
+ if (!captureFocusedKeyboardTarget(getTarget(event))) {
1145
+ // Focus landed outside any drawer keyboard input. The `focusout` on the previous
1146
+ // field normally clears the tracked target, but the tap path suppresses that
1147
+ // `focusout` via `programmaticKeyboardFocusRef`, so a consumer `onFocus` handler that
1148
+ // redirects focus out of the drawer would otherwise leave a stale target holding its
1149
+ // inset, slack, and pending realign. Reconcile against the real focus here.
1150
+ // A handler that ends focus entirely emits no `focusin` to reconcile from; that case
1151
+ // is covered by clearing the suppression flag above so its `focusout` is handled.
1152
+ clearFocusedKeyboardTarget();
1153
+ return;
1154
+ }
1155
+
1156
+ // Covers every way focus can land with the keyboard already up: native moves (the
1157
+ // keyboard's previous/next arrows) and the tap path (which suppresses `focusout`
1158
+ // handling via the programmatic flag).
1159
+ if (getKeyboardVisualViewport(win) != null) {
1160
+ scheduleDelayedKeyboardRealign();
994
1161
  }
1162
+ scheduleKeyboardFocusAlignment();
995
1163
  };
996
1164
  const handleFocusOut = event => {
1165
+ // The blur inside `focusKeyboardInputWithoutPageScroll` is followed synchronously by
1166
+ // a re-focus; clearing state here would drop the keyboard inset for a frame.
1167
+ if (programmaticKeyboardFocusRef.current) {
1168
+ return;
1169
+ }
997
1170
  if (captureFocusedKeyboardTarget(event.relatedTarget)) {
1171
+ const target = focusedKeyboardTargetRef.current;
1172
+ const keyboardViewport = getKeyboardVisualViewport(win);
1173
+ // The delayed realign passes are scheduled by the `focusin` that follows once
1174
+ // focus lands on the captured target.
1175
+ if (target && keyboardViewport) {
1176
+ preemptFocusReveal(target, keyboardViewport);
1177
+ }
998
1178
  scheduleKeyboardFocusAlignment();
999
1179
  return;
1000
1180
  }
@@ -1009,25 +1189,36 @@ function DrawerVirtualKeyboardProvider(props) {
1009
1189
  if (visualViewport) {
1010
1190
  cleanupListeners.push(addEventListener(visualViewport, 'resize', handleViewportUpdate), addEventListener(visualViewport, 'scroll', handleViewportUpdate));
1011
1191
  }
1012
- cleanupListeners.push(addEventListener(doc, 'focusin', handleFocusIn, true), addEventListener(doc, 'focusout', handleFocusOut, true));
1192
+ const handleWindowScroll = () => {
1193
+ if (restoreWindowScroll()) {
1194
+ // Recompute the keyboard inset once the page is back at rest; measurements taken
1195
+ // mid-reveal (a non-zero visual viewport offset) inflate it and push the popup up.
1196
+ scheduleKeyboardFocusAlignment();
1197
+ }
1198
+ };
1199
+
1200
+ // Once the user puts a finger down they own the scroll position. Stop both the frame settle
1201
+ // loop, which can center after changing layout settles, and the delayed passes, which can't
1202
+ // distinguish a user scroll from a reveal scroll WebKit canceled. A later focus or viewport
1203
+ // change reschedules alignment if it's still needed.
1204
+ const cancelKeyboardRealignOnPointerDown = () => {
1205
+ keyboardFocusFrame.cancel();
1206
+ keyboardRealignTimeout.clear();
1207
+ keyboardScrollElement = null;
1208
+ };
1209
+ cleanupListeners.push(addEventListener(doc, 'focusin', handleFocusIn, true), addEventListener(doc, 'focusout', handleFocusOut, true), addEventListener(win, 'scroll', handleWindowScroll), addEventListener(doc, 'pointerdown', cancelKeyboardRealignOnPointerDown, true));
1013
1210
  if (captureFocusedKeyboardTarget(activeElement(doc))) {
1014
1211
  scheduleKeyboardFocusAlignment();
1015
1212
  }
1016
1213
  return () => {
1017
1214
  cleanupListeners.forEach(cleanup => cleanup());
1215
+ consumePreemptedFocus();
1018
1216
  clearFocusedKeyboardTarget();
1019
1217
  rootElement.style.removeProperty(DrawerViewportCssVars.keyboardInset);
1020
1218
  };
1021
- }, [animateKeyboardScroll, keyboardFocusFrame, mounted, nestedDrawerOpen, open, restoreKeyboardScrollAdjustment, rootElement, setKeyboardScrollSlack]);
1219
+ }, [animateKeyboardScroll, keyboardFocusFrame, keyboardRealignTimeout, modal, mounted, nestedDrawerOpen, open, restoreKeyboardScrollAdjustment, rootElement, setKeyboardScrollSlack]);
1022
1220
  const onTouchStart = useStableCallback(event => {
1023
- if (!open || !mounted || nestedDrawerOpen) {
1024
- resetTouchTrackingState();
1025
- return;
1026
- }
1027
1221
  const touch = event.touches[0];
1028
- if (!touch) {
1029
- return;
1030
- }
1031
1222
  pendingKeyboardFocusMovedRef.current = false;
1032
1223
  keyboardTouchStartRef.current = {
1033
1224
  x: touch.clientX,
@@ -1053,9 +1244,9 @@ function DrawerVirtualKeyboardProvider(props) {
1053
1244
  return;
1054
1245
  }
1055
1246
  const touch = event.changedTouches[0] ?? event.touches[0];
1056
- const doc = ownerDocument(event.currentTarget);
1247
+ const root = rootElement.getRootNode();
1057
1248
  const nativeEventTarget = getTarget(event.nativeEvent);
1058
- const pointTarget = touch ? resolveKeyboardTouchTargetFromPoint(doc, touch.clientX, touch.clientY) : null;
1249
+ const pointTarget = touch ? resolveKeyboardTouchTargetFromPoint(root, touch.clientX, touch.clientY) : null;
1059
1250
 
1060
1251
  // The lift point landed on another interactive/label element; let its native tap
1061
1252
  // through instead of stealing it for the touchstart input.
@@ -1084,15 +1275,24 @@ function DrawerVirtualKeyboardProvider(props) {
1084
1275
 
1085
1276
  // Already focused with the keyboard up: let the native tap through so it can
1086
1277
  // reposition the caret, rather than blurring and re-focusing the same input.
1087
- if (activeElement(ownerDocument(keyboardFocusTarget)) === keyboardFocusTarget && isKeyboardVisualViewportOpen(win)) {
1278
+ if (activeElement(ownerDocument(keyboardFocusTarget)) === keyboardFocusTarget && (!win.visualViewport || getKeyboardVisualViewport(win) != null)) {
1088
1279
  resetTouchTrackingState();
1089
1280
  return;
1090
1281
  }
1091
1282
 
1092
1283
  // iOS only opens the software keyboard when focus happens synchronously
1093
- // inside the touch gesture.
1284
+ // inside the touch gesture. The flag suppresses the `focusout` cleanup the
1285
+ // intermediate blur would otherwise trigger, so the keyboard inset isn't
1286
+ // dropped for a frame between the blur and the re-focus. It is cleared by the
1287
+ // `focusin` that lands, so only that blur is suppressed; the `finally` is the
1288
+ // backstop for a target that never takes focus.
1094
1289
  event.preventDefault();
1095
- focusKeyboardInputWithoutPageScroll(keyboardFocusTarget);
1290
+ programmaticKeyboardFocusRef.current = true;
1291
+ try {
1292
+ focusKeyboardInputWithoutPageScroll(keyboardFocusTarget);
1293
+ } finally {
1294
+ programmaticKeyboardFocusRef.current = false;
1295
+ }
1096
1296
  // Preventing the touchend default also suppresses the compatibility mouse
1097
1297
  // events, including `click`; redispatch an untrusted replacement on the
1098
1298
  // original tap target so click handlers still run with the tap coordinates.
@@ -1137,13 +1337,16 @@ function resolveKeyboardInputTarget(target) {
1137
1337
  return isHTMLElement(control) && isKeyboardInputElement(control) ? control : null;
1138
1338
  }
1139
1339
  function resolveKeyboardTouchTarget(target) {
1340
+ if (!isHTMLElement(target)) {
1341
+ return null;
1342
+ }
1140
1343
  const focusTarget = resolveKeyboardInputTarget(target);
1141
1344
  if (!focusTarget) {
1142
1345
  return null;
1143
1346
  }
1144
1347
  return {
1145
1348
  focusTarget,
1146
- clickTarget: isHTMLElement(target) ? target : focusTarget
1349
+ clickTarget: target
1147
1350
  };
1148
1351
  }
1149
1352
 
@@ -1156,14 +1359,16 @@ function getContentEditableHost(element) {
1156
1359
  }
1157
1360
  return host;
1158
1361
  }
1159
- function resolveKeyboardTouchTargetFromPoint(doc, clientX, clientY) {
1160
- const exactTarget = getElementAtPoint(doc, clientX, clientY);
1161
- const exactKeyboardTarget = resolveKeyboardInputTarget(exactTarget);
1162
- if (exactKeyboardTarget) {
1163
- return {
1164
- focusTarget: exactKeyboardTarget,
1165
- clickTarget: isHTMLElement(exactTarget) ? exactTarget : exactKeyboardTarget
1166
- };
1362
+ function resolveKeyboardTouchTargetFromPoint(root, clientX, clientY) {
1363
+ const exactTarget = getElementAtPoint(root, clientX, clientY);
1364
+ if (isHTMLElement(exactTarget)) {
1365
+ const exactKeyboardTarget = resolveKeyboardInputTarget(exactTarget);
1366
+ if (exactKeyboardTarget) {
1367
+ return {
1368
+ focusTarget: exactKeyboardTarget,
1369
+ clickTarget: exactTarget
1370
+ };
1371
+ }
1167
1372
  }
1168
1373
 
1169
1374
  // Probing nearby points compensates for iOS retargeting taps while the page reacts
@@ -1176,7 +1381,7 @@ function resolveKeyboardTouchTargetFromPoint(doc, clientX, clientY) {
1176
1381
  return KEYBOARD_TAP_BLOCKED;
1177
1382
  }
1178
1383
  for (const [offsetX, offsetY] of [[0, INPUT_TAP_HIT_SLOP], [0, -INPUT_TAP_HIT_SLOP], [INPUT_TAP_HIT_SLOP, 0], [-INPUT_TAP_HIT_SLOP, 0]]) {
1179
- const keyboardTarget = resolveKeyboardInputTarget(getElementAtPoint(doc, clientX + offsetX, clientY + offsetY));
1384
+ const keyboardTarget = resolveKeyboardInputTarget(getElementAtPoint(root, clientX + offsetX, clientY + offsetY));
1180
1385
  if (keyboardTarget) {
1181
1386
  return {
1182
1387
  focusTarget: keyboardTarget,
@@ -1200,15 +1405,10 @@ function dispatchKeyboardClick(target, touch) {
1200
1405
  }
1201
1406
  function focusKeyboardInputWithoutPageScroll(target) {
1202
1407
  const wasFocused = activeElement(ownerDocument(target)) === target;
1203
- const previousOpacity = target.style.opacity;
1204
- const previousTransform = target.style.transform;
1205
- const previousTransition = target.style.transition;
1206
-
1207
1408
  // iOS Safari can still scroll the page for transformed sheets even with preventScroll.
1208
- // Move the input off-screen only for the synchronous focus call.
1209
- target.style.transition = 'none';
1210
- target.style.opacity = '0';
1211
- target.style.transform = 'translateY(-2000px)';
1409
+ // Move the input off-screen only for the synchronous focus call; `preventScroll`
1410
+ // suppresses the in-page ancestor scrolling that would otherwise chase the fake rect.
1411
+ const restoreStyles = overrideGeometryDuringFocus(target, -2000);
1212
1412
  try {
1213
1413
  if (wasFocused) {
1214
1414
  target.blur();
@@ -1217,10 +1417,25 @@ function focusKeyboardInputWithoutPageScroll(target) {
1217
1417
  preventScroll: true
1218
1418
  });
1219
1419
  } finally {
1420
+ restoreStyles();
1421
+ }
1422
+ }
1423
+
1424
+ // Overrides the painted geometry WebKit samples when an element gains focus. The rect is
1425
+ // hidden (opacity) rather than detached so layout is unaffected; the caller must restore
1426
+ // synchronously before the next paint.
1427
+ function overrideGeometryDuringFocus(target, translateY) {
1428
+ const previousOpacity = target.style.opacity;
1429
+ const previousTransform = target.style.transform;
1430
+ const previousTransition = target.style.transition;
1431
+ target.style.transition = 'none';
1432
+ target.style.opacity = '0';
1433
+ target.style.transform = `translateY(${translateY}px)`;
1434
+ return () => {
1220
1435
  target.style.opacity = previousOpacity;
1221
1436
  target.style.transform = previousTransform;
1222
1437
  target.style.transition = previousTransition;
1223
- }
1438
+ };
1224
1439
  }
1225
1440
  function findKeyboardScrollTarget(target, root) {
1226
1441
  // Start at the parent: scrolling the focused field's own content (an overflowing
@@ -1248,11 +1463,22 @@ function getKeyboardVisualViewport(win) {
1248
1463
  bottom: Math.min(win.innerHeight, top + visualViewport.height)
1249
1464
  };
1250
1465
  }
1251
- function getDrawerKeyboardInset(win, keyboardViewport) {
1252
- return Math.max(0, win.innerHeight - keyboardViewport.bottom);
1253
- }
1254
- function isKeyboardVisualViewportOpen(win) {
1255
- return !win.visualViewport || getKeyboardVisualViewport(win) != null;
1466
+
1467
+ /**
1468
+ * Controls a Drawer imperatively and associates detached `Drawer.Trigger` components with a
1469
+ * `Drawer.Root`. Create one with `Drawer.createHandle()` and pass it to the `handle` prop of the
1470
+ * root and of any triggers rendered outside of it.
1471
+ *
1472
+ * The imperative methods take effect only while a root using this handle is mounted; calls made
1473
+ * before a root attaches (or after it unmounts) are ignored.
1474
+ */
1475
+ class DrawerHandle extends DialogHandle {}
1476
+
1477
+ /**
1478
+ * Creates a new handle to connect a Drawer.Root with detached Drawer.Trigger components.
1479
+ */
1480
+ function createDrawerHandle() {
1481
+ return new DrawerHandle();
1256
1482
  }
1257
1483
 
1258
1484
  var index_parts$5 = /*#__PURE__*/Object.freeze({
@@ -1261,7 +1487,7 @@ var index_parts$5 = /*#__PURE__*/Object.freeze({
1261
1487
  Close: DrawerClose,
1262
1488
  Content: DrawerContent,
1263
1489
  Description: DrawerDescription,
1264
- Handle: DialogHandle,
1490
+ Handle: DrawerHandle,
1265
1491
  Indent: DrawerIndent,
1266
1492
  IndentBackground: DrawerIndentBackground,
1267
1493
  Popup: DrawerPopup,
@@ -1273,7 +1499,7 @@ var index_parts$5 = /*#__PURE__*/Object.freeze({
1273
1499
  Trigger: DrawerTrigger,
1274
1500
  Viewport: DrawerViewport,
1275
1501
  VirtualKeyboardProvider: DrawerVirtualKeyboardProvider,
1276
- createHandle: createDialogHandle
1502
+ createHandle: createDrawerHandle
1277
1503
  });
1278
1504
 
1279
1505
  /**
@@ -1342,7 +1568,7 @@ const MenuCheckboxItem = /*#__PURE__*/React.forwardRef(function MenuCheckboxItem
1342
1568
  id: idProp,
1343
1569
  label,
1344
1570
  nativeButton = false,
1345
- disabled = false,
1571
+ disabled: disabledProp = false,
1346
1572
  closeOnClick = false,
1347
1573
  checked: checkedProp,
1348
1574
  defaultChecked,
@@ -1351,6 +1577,7 @@ const MenuCheckboxItem = /*#__PURE__*/React.forwardRef(function MenuCheckboxItem
1351
1577
  ...elementProps
1352
1578
  } = componentProps;
1353
1579
  const listItem = useCompositeListItem({
1580
+ guess: true,
1354
1581
  label
1355
1582
  });
1356
1583
  const menuPositionerContext = useMenuPositionerContext(true);
@@ -1358,6 +1585,8 @@ const MenuCheckboxItem = /*#__PURE__*/React.forwardRef(function MenuCheckboxItem
1358
1585
  const {
1359
1586
  store
1360
1587
  } = useMenuRootContext();
1588
+ const rootDisabled = store.useState('disabled');
1589
+ const disabled = disabledProp || rootDisabled;
1361
1590
  const highlighted = store.useState('isActive', listItem.index);
1362
1591
  const itemProps = store.useState('itemProps');
1363
1592
  const [checked, setChecked] = useControlled({
@@ -1386,7 +1615,7 @@ const MenuCheckboxItem = /*#__PURE__*/React.forwardRef(function MenuCheckboxItem
1386
1615
  }), [disabled, highlighted, checked]);
1387
1616
  function handleClick(event) {
1388
1617
  const details = createChangeEventDetails(itemPress, event.nativeEvent, undefined, {
1389
- preventUnmountOnClose() {}
1618
+ preventUnmountOnClose: NOOP
1390
1619
  });
1391
1620
  onCheckedChange?.(!checked, details);
1392
1621
  if (details.isCanceled) {
@@ -1429,6 +1658,7 @@ const MenuCheckboxItemIndicator = /*#__PURE__*/React.forwardRef(function MenuChe
1429
1658
  const indicatorRef = React.useRef(null);
1430
1659
  const {
1431
1660
  transitionStatus,
1661
+ mounted,
1432
1662
  setMounted
1433
1663
  } = useTransitionStatus(item.checked);
1434
1664
  useOpenChangeComplete({
@@ -1454,7 +1684,7 @@ const MenuCheckboxItemIndicator = /*#__PURE__*/React.forwardRef(function MenuChe
1454
1684
  'aria-hidden': true,
1455
1685
  ...elementProps
1456
1686
  },
1457
- enabled: keepMounted || item.checked
1687
+ enabled: keepMounted || mounted
1458
1688
  });
1459
1689
  return element;
1460
1690
  });
@@ -1494,30 +1724,14 @@ function usePopupAutoResize(parameters) {
1494
1724
  side,
1495
1725
  direction
1496
1726
  } = parameters;
1497
- const runOnceAnimationsFinish = useAnimationsFinished(popupElement, true, false);
1727
+ const runOnceAnimationsFinish = useAnimationsFinished(popupElement, true);
1498
1728
  const animationFrame = useAnimationFrame();
1499
1729
  const committedDimensionsRef = React.useRef(null);
1500
1730
  const isInitialRenderRef = React.useRef(true);
1501
1731
  const restoreAnchoringStylesRef = React.useRef(NOOP);
1502
1732
  const onMeasureLayout = useStableCallback(onMeasureLayoutParam);
1503
1733
  const onMeasureLayoutComplete = useStableCallback(onMeasureLayoutCompleteParam);
1504
- const anchoringStyles = React.useMemo(() => {
1505
- // Ensure popup size transitions correctly when anchored to `bottom` (side=top) or `right` (side=left).
1506
- let isOriginSide = side === 'top';
1507
- let isPhysicalLeft = side === 'left';
1508
- if (direction === 'rtl') {
1509
- isOriginSide = isOriginSide || side === 'inline-end';
1510
- isPhysicalLeft = isPhysicalLeft || side === 'inline-end';
1511
- } else {
1512
- isOriginSide = isOriginSide || side === 'inline-start';
1513
- isPhysicalLeft = isPhysicalLeft || side === 'inline-start';
1514
- }
1515
- return isOriginSide ? {
1516
- position: 'absolute',
1517
- [side === 'top' ? 'bottom' : 'top']: '0',
1518
- [isPhysicalLeft ? 'right' : 'left']: '0'
1519
- } : EMPTY_OBJECT;
1520
- }, [side, direction]);
1734
+ const anchoringStyles = React.useMemo(() => getPopupAnchoringStyles(side, direction), [side, direction]);
1521
1735
  useIsoLayoutEffect(() => {
1522
1736
  // Reset the state when the popup is closed.
1523
1737
  if (!mounted) {
@@ -1593,6 +1807,19 @@ function usePopupAutoResize(parameters) {
1593
1807
  };
1594
1808
  }, [content, popupElement, positionerElement, runOnceAnimationsFinish, animationFrame, mounted, onMeasureLayout, onMeasureLayoutComplete, anchoringStyles]);
1595
1809
  }
1810
+ function getPopupAnchoringStyles(side, direction) {
1811
+ // Ensure popup size transitions correctly when anchored to `bottom` (side=top) or `right` (side=left).
1812
+ const isPhysicalTop = side === 'top';
1813
+ const isPhysicalLeft = side === 'left' || side === (direction === 'rtl' ? 'inline-end' : 'inline-start');
1814
+ if (!isPhysicalTop && !isPhysicalLeft) {
1815
+ return EMPTY_OBJECT;
1816
+ }
1817
+ return {
1818
+ position: 'absolute',
1819
+ [isPhysicalTop ? 'bottom' : 'top']: '0',
1820
+ [isPhysicalLeft ? 'right' : 'left']: '0'
1821
+ };
1822
+ }
1596
1823
  function overrideElementStyle(element, property, value) {
1597
1824
  const originalValue = element.style.getPropertyValue(property);
1598
1825
  element.style.setProperty(property, value);
@@ -1622,6 +1849,80 @@ function setPositionerCssSize(positionerElement, size) {
1622
1849
  positionerElement.style.setProperty('--positioner-height', height);
1623
1850
  }
1624
1851
 
1852
+ const adaptiveOrigin = {
1853
+ name: 'adaptiveOrigin',
1854
+ async fn(state) {
1855
+ const {
1856
+ x: rawX,
1857
+ y: rawY,
1858
+ rects: {
1859
+ floating: floatRect
1860
+ },
1861
+ elements: {
1862
+ floating
1863
+ },
1864
+ platform,
1865
+ strategy,
1866
+ placement
1867
+ } = state;
1868
+ const win = getWindow(floating);
1869
+ const styles = win.getComputedStyle(floating);
1870
+ const hasTransition = styles.transitionDuration !== '0s' && styles.transitionDuration !== '';
1871
+ if (!hasTransition) {
1872
+ return {
1873
+ x: rawX,
1874
+ y: rawY,
1875
+ data: DEFAULT_SIDES
1876
+ };
1877
+ }
1878
+ const offsetParent = await platform.getOffsetParent?.(floating);
1879
+ let offsetDimensions = {
1880
+ width: 0,
1881
+ height: 0
1882
+ };
1883
+
1884
+ // For fixed strategy, prefer visualViewport if available
1885
+ if (strategy === 'fixed' && win?.visualViewport) {
1886
+ offsetDimensions = {
1887
+ width: win.visualViewport.width,
1888
+ height: win.visualViewport.height
1889
+ };
1890
+ } else if (offsetParent === win) {
1891
+ const doc = ownerDocument(floating);
1892
+ offsetDimensions = {
1893
+ width: doc.documentElement.clientWidth,
1894
+ height: doc.documentElement.clientHeight
1895
+ };
1896
+ } else if (await platform.isElement?.(offsetParent)) {
1897
+ offsetDimensions = await platform.getDimensions(offsetParent);
1898
+ }
1899
+ const currentSide = getSide(placement);
1900
+ let x = rawX;
1901
+ let y = rawY;
1902
+ if (currentSide === 'left') {
1903
+ x = offsetDimensions.width - (rawX + floatRect.width);
1904
+ }
1905
+ if (currentSide === 'top') {
1906
+ y = offsetDimensions.height - (rawY + floatRect.height);
1907
+ }
1908
+ const sideX = currentSide === 'left' ? 'right' : DEFAULT_SIDES.sideX;
1909
+ const sideY = currentSide === 'top' ? 'bottom' : DEFAULT_SIDES.sideY;
1910
+ return {
1911
+ x,
1912
+ y,
1913
+ data: {
1914
+ sideX,
1915
+ sideY
1916
+ }
1917
+ };
1918
+ }
1919
+ };
1920
+
1921
+ const popupViewportStateMapping = {
1922
+ activationDirection: value => value ? {
1923
+ 'data-activation-direction': value
1924
+ } : null
1925
+ };
1625
1926
  /**
1626
1927
  * Builds morphing viewport containers for popups that animate between trigger-based content.
1627
1928
  * Handles previous-content snapshots, auto-resize, and state attributes for transitions.
@@ -1630,7 +1931,6 @@ function usePopupViewport(parameters) {
1630
1931
  const {
1631
1932
  store,
1632
1933
  side,
1633
- cssVars,
1634
1934
  children
1635
1935
  } = parameters;
1636
1936
  const direction = useDirection();
@@ -1650,14 +1950,15 @@ function usePopupViewport(parameters) {
1650
1950
  const [newTriggerOffset, setNewTriggerOffset] = React.useState(null);
1651
1951
  const currentContainerRef = React.useRef(null);
1652
1952
  const previousContainerRef = React.useRef(null);
1653
- const onAnimationsFinished = useAnimationsFinished(currentContainerRef, true, false);
1953
+ const onAnimationsFinished = useAnimationsFinished(currentContainerRef, true);
1654
1954
  const cleanupFrame = useAnimationFrame();
1955
+ const cleanupControllerRef = React.useRef(null);
1655
1956
  const [previousContentDimensions, setPreviousContentDimensions] = React.useState(null);
1656
1957
  const [showStartingStyleAttribute, setShowStartingStyleAttribute] = React.useState(false);
1657
1958
  useIsoLayoutEffect(() => {
1658
- store.set('hasViewport', true);
1959
+ store.set('adaptiveOrigin', adaptiveOrigin);
1659
1960
  return () => {
1660
- store.set('hasViewport', false);
1961
+ store.set('adaptiveOrigin', undefined);
1661
1962
  };
1662
1963
  }, [store]);
1663
1964
  const handleMeasureLayout = useStableCallback(() => {
@@ -1673,6 +1974,16 @@ function usePopupViewport(parameters) {
1673
1974
  setPreviousContentDimensions(previousDimensions);
1674
1975
  }
1675
1976
  });
1977
+ const armViewportCleanup = useStableCallback(() => {
1978
+ cleanupControllerRef.current?.abort();
1979
+ const controller = new AbortController();
1980
+ cleanupControllerRef.current = controller;
1981
+ onAnimationsFinished(() => {
1982
+ setPreviousContentNode(null);
1983
+ setPreviousContentDimensions(null);
1984
+ capturedNodeRef.current = null;
1985
+ }, controller.signal);
1986
+ });
1676
1987
  const lastHandledTriggerRef = React.useRef(null);
1677
1988
  useIsoLayoutEffect(() => {
1678
1989
  if (!open || !mounted) {
@@ -1690,19 +2001,33 @@ function usePopupViewport(parameters) {
1690
2001
  // so we can pass it to the style hook for animation purposes.
1691
2002
  const offset = calculateRelativePosition(previousActiveTrigger, activeTrigger);
1692
2003
  setNewTriggerOffset(offset);
1693
- cleanupFrame.request(() => {
1694
- ReactDOM.flushSync(() => {
1695
- setShowStartingStyleAttribute(false);
1696
- });
1697
- onAnimationsFinished(() => {
1698
- setPreviousContentNode(null);
1699
- setPreviousContentDimensions(null);
1700
- capturedNodeRef.current = null;
1701
- });
1702
- });
1703
2004
  lastHandledTriggerRef.current = activeTrigger;
1704
2005
  }
1705
- }, [activeTrigger, previousActiveTrigger, previousContentNode, onAnimationsFinished, cleanupFrame]);
2006
+ }, [activeTrigger, previousActiveTrigger]);
2007
+
2008
+ // Arm cleanup after a trigger change, and re-arm it if the current container remounts
2009
+ // mid-transition when a lagging payload bumps `currentContentKey`. The remount discards
2010
+ // the running entry animation (and with transition-style CSS the replacement mounts at
2011
+ // final styles with no animation at all), so re-run the starting-style choreography —
2012
+ // otherwise the watcher either strands or fires before the previous container's exit
2013
+ // animation finishes.
2014
+ useIsoLayoutEffect(() => {
2015
+ if (previousContentNode == null) {
2016
+ return;
2017
+ }
2018
+
2019
+ // Abort the stale watcher synchronously. The remount cancels the old container's
2020
+ // animations, and the resulting promise rejection would otherwise run the cleanup
2021
+ // in a microtask before the re-armed watcher below is in place.
2022
+ cleanupControllerRef.current?.abort();
2023
+ setShowStartingStyleAttribute(true);
2024
+ cleanupFrame.request(() => {
2025
+ ReactDOM.flushSync(() => {
2026
+ setShowStartingStyleAttribute(false);
2027
+ });
2028
+ armViewportCleanup();
2029
+ });
2030
+ }, [currentContentKey, previousContentNode, armViewportCleanup, cleanupFrame]);
1706
2031
 
1707
2032
  // Capture a clone of the current content DOM subtree when not transitioning.
1708
2033
  // We can't store previous React nodes as they may be stateful; instead we capture DOM clones for visual continuity.
@@ -1739,8 +2064,8 @@ function usePopupViewport(parameters) {
1739
2064
  ref: previousContainerRef,
1740
2065
  style: {
1741
2066
  ...(previousContentDimensions ? {
1742
- [cssVars.popupWidth]: `${previousContentDimensions.width}px`,
1743
- [cssVars.popupHeight]: `${previousContentDimensions.height}px`
2067
+ '--popup-width': `${previousContentDimensions.width}px`,
2068
+ '--popup-height': `${previousContentDimensions.height}px`
1744
2069
  } : null),
1745
2070
  position: 'absolute'
1746
2071
  },
@@ -1865,28 +2190,6 @@ function usePopupContentKey(activeTriggerId, payload) {
1865
2190
  return `${activeTriggerId ?? 'current'}-${contentKey}`;
1866
2191
  }
1867
2192
 
1868
- let MenuViewportCssVars = /*#__PURE__*/function (MenuViewportCssVars) {
1869
- /**
1870
- * The width of the parent popup.
1871
- * This variable is placed on the 'previous' container and stores the width of the popup when the previous content was rendered.
1872
- * It can be used to freeze the dimensions of the popup when animating between different content.
1873
- */
1874
- MenuViewportCssVars["popupWidth"] = "--popup-width";
1875
- /**
1876
- * The height of the parent popup.
1877
- * This variable is placed on the 'previous' container and stores the height of the popup when the previous content was rendered.
1878
- * It can be used to freeze the dimensions of the popup when animating between different content.
1879
- */
1880
- MenuViewportCssVars["popupHeight"] = "--popup-height";
1881
- return MenuViewportCssVars;
1882
- }({});
1883
-
1884
- const stateAttributesMapping$2 = {
1885
- activationDirection: value => value ? {
1886
- 'data-activation-direction': value
1887
- } : null
1888
- };
1889
-
1890
2193
  /**
1891
2194
  * A viewport for displaying content transitions.
1892
2195
  * This component is only required if one popup can be opened by multiple triggers, its content
@@ -1916,7 +2219,6 @@ const MenuViewport = /*#__PURE__*/React.forwardRef(function MenuViewport(compone
1916
2219
  } = usePopupViewport({
1917
2220
  store,
1918
2221
  side,
1919
- cssVars: MenuViewportCssVars,
1920
2222
  children
1921
2223
  });
1922
2224
  const state = {
@@ -1930,47 +2232,50 @@ const MenuViewport = /*#__PURE__*/React.forwardRef(function MenuViewport(compone
1930
2232
  props: [elementProps, {
1931
2233
  children: childrenToRender
1932
2234
  }],
1933
- stateAttributesMapping: stateAttributesMapping$2
2235
+ stateAttributesMapping: popupViewportStateMapping
1934
2236
  });
1935
2237
  });
1936
2238
  if (process.env.NODE_ENV !== "production") MenuViewport.displayName = "MenuViewport";
1937
2239
 
1938
- class MenuHandle {
1939
- /**
1940
- * Internal store holding the menu's state.
1941
- * @internal
1942
- */
1943
-
2240
+ /**
2241
+ * Controls a Menu imperatively and associates detached `Menu.Trigger` components with a `Menu.Root`.
2242
+ * Create one with `Menu.createHandle()` and pass it to the `handle` prop of the root and of any
2243
+ * triggers rendered outside of it.
2244
+ *
2245
+ * The imperative methods take effect only while a root using this handle is mounted; calls made
2246
+ * before a root attaches (or after it unmounts) are ignored.
2247
+ */
2248
+ class MenuHandle extends BasePopupHandle {
1944
2249
  constructor() {
1945
- this.store = new MenuStore();
2250
+ super(createNullMenuStore(), 'Menu');
1946
2251
  }
1947
2252
 
1948
2253
  /**
1949
2254
  * Opens the menu and associates it with the trigger with the given id.
1950
- * The trigger must be a Menu.Trigger component with this handle passed as a prop.
1951
2255
  *
1952
- * @param triggerId ID of the trigger to associate with the menu.
2256
+ * This method should only be called in an event handler or an effect (not during rendering).
2257
+ *
2258
+ * @param triggerId ID of the trigger to associate with the menu. The trigger must be a matching
2259
+ * `Menu.Trigger` with this handle passed as a prop.
1953
2260
  */
1954
2261
  open(triggerId) {
1955
- const triggerElement = triggerId ? this.store.context.triggerElements.getById(triggerId) : undefined;
1956
- if (triggerId && !triggerElement) {
1957
- throw new Error(process.env.NODE_ENV !== "production" ? `Base UI: MenuHandle.open: No trigger found with id "${triggerId}".` : formatErrorMessage(83, triggerId));
1958
- }
1959
- this.store.setOpen(true, createChangeEventDetails('imperative-action', undefined, triggerElement));
2262
+ this.openByTrigger(triggerId);
1960
2263
  }
1961
2264
 
1962
2265
  /**
1963
2266
  * Closes the menu.
2267
+ *
2268
+ * This method should only be called in an event handler or an effect (not during rendering).
1964
2269
  */
1965
2270
  close() {
1966
- this.store.setOpen(false, createChangeEventDetails('imperative-action', undefined, undefined));
2271
+ this.closePopup();
1967
2272
  }
1968
2273
 
1969
2274
  /**
1970
- * Indicates whether the menu is currently open.
2275
+ * Whether the menu is currently open. Returns `false` while no root is attached to the handle.
1971
2276
  */
1972
2277
  get isOpen() {
1973
- return this.store.select('open');
2278
+ return this.attachedStore?.select('open') ?? false;
1974
2279
  }
1975
2280
  }
1976
2281
 
@@ -2017,20 +2322,6 @@ function useScrollAreaScrollbarContext() {
2017
2322
  return context;
2018
2323
  }
2019
2324
 
2020
- let ScrollAreaScrollbarCssVars = /*#__PURE__*/function (ScrollAreaScrollbarCssVars) {
2021
- /**
2022
- * The scroll area thumb's height.
2023
- * @type {number}
2024
- */
2025
- ScrollAreaScrollbarCssVars["scrollAreaThumbHeight"] = "--scroll-area-thumb-height";
2026
- /**
2027
- * The scroll area thumb's width.
2028
- * @type {number}
2029
- */
2030
- ScrollAreaScrollbarCssVars["scrollAreaThumbWidth"] = "--scroll-area-thumb-width";
2031
- return ScrollAreaScrollbarCssVars;
2032
- }({});
2033
-
2034
2325
  /**
2035
2326
  * A vertical or horizontal scrollbar for the scroll area.
2036
2327
  * Renders a `<div>` element.
@@ -2051,7 +2342,6 @@ const ScrollAreaScrollbar = /*#__PURE__*/React.forwardRef(function ScrollAreaScr
2051
2342
  scrollingX,
2052
2343
  scrollingY,
2053
2344
  hiddenState,
2054
- overflowEdges,
2055
2345
  scrollbarYRef,
2056
2346
  scrollbarXRef,
2057
2347
  viewportRef,
@@ -2060,43 +2350,37 @@ const ScrollAreaScrollbar = /*#__PURE__*/React.forwardRef(function ScrollAreaScr
2060
2350
  handlePointerDown,
2061
2351
  handlePointerUp,
2062
2352
  handleScroll,
2353
+ disableViewportSnap,
2063
2354
  rootId,
2064
2355
  thumbSize,
2065
- hasMeasuredScrollbar
2356
+ hasMeasuredScrollbar,
2357
+ viewportState
2066
2358
  } = useScrollAreaRootContext();
2359
+ const vertical = orientation === 'vertical';
2067
2360
  const state = {
2361
+ ...viewportState,
2068
2362
  hovering,
2069
- scrolling: {
2070
- horizontal: scrollingX,
2071
- vertical: scrollingY
2072
- }[orientation],
2073
- orientation,
2074
- hasOverflowX: !hiddenState.x,
2075
- hasOverflowY: !hiddenState.y,
2076
- overflowXStart: overflowEdges.xStart,
2077
- overflowXEnd: overflowEdges.xEnd,
2078
- overflowYStart: overflowEdges.yStart,
2079
- overflowYEnd: overflowEdges.yEnd,
2080
- cornerHidden: hiddenState.corner
2363
+ scrolling: vertical ? scrollingY : scrollingX,
2364
+ orientation
2081
2365
  };
2082
2366
  const direction = useDirection();
2083
2367
  const hideTrackUntilMeasured = !hasMeasuredScrollbar && !keepMounted;
2084
- const isHidden = orientation === 'vertical' ? hiddenState.y : hiddenState.x;
2368
+ const isHidden = vertical ? hiddenState.y : hiddenState.x;
2085
2369
  const shouldRender = keepMounted || !isHidden;
2086
2370
  React.useEffect(() => {
2087
2371
  if (!shouldRender) {
2088
2372
  return undefined;
2089
2373
  }
2090
2374
  const viewportEl = viewportRef.current;
2091
- const scrollbarEl = orientation === 'vertical' ? scrollbarYRef.current : scrollbarXRef.current;
2375
+ const scrollbarEl = vertical ? scrollbarYRef.current : scrollbarXRef.current;
2092
2376
  if (!scrollbarEl) {
2093
2377
  return undefined;
2094
2378
  }
2095
2379
  function handleWheel(event) {
2096
- if (!viewportEl || !scrollbarEl || event.ctrlKey) {
2380
+ if (!viewportEl || event.ctrlKey) {
2097
2381
  return;
2098
2382
  }
2099
- const horizontal = orientation === 'horizontal';
2383
+ const horizontal = !vertical;
2100
2384
  const scrollProperty = horizontal ? 'scrollLeft' : 'scrollTop';
2101
2385
  const delta = horizontal ? event.deltaX : event.deltaY;
2102
2386
  if (delta === 0) {
@@ -2123,7 +2407,7 @@ const ScrollAreaScrollbar = /*#__PURE__*/React.forwardRef(function ScrollAreaScr
2123
2407
  return addEventListener(scrollbarEl, 'wheel', handleWheel, {
2124
2408
  passive: false
2125
2409
  });
2126
- }, [direction, handleScroll, orientation, scrollbarXRef, scrollbarYRef, shouldRender, viewportRef]);
2410
+ }, [direction, handleScroll, vertical, scrollbarXRef, scrollbarYRef, shouldRender, viewportRef]);
2127
2411
  const props = {
2128
2412
  ...(rootId && {
2129
2413
  'data-id': `${rootId}-scrollbar`
@@ -2133,58 +2417,55 @@ const ScrollAreaScrollbar = /*#__PURE__*/React.forwardRef(function ScrollAreaScr
2133
2417
  return;
2134
2418
  }
2135
2419
  const target = getTarget(event.nativeEvent);
2136
- const thumb = orientation === 'vertical' ? thumbYRef.current : thumbXRef.current;
2420
+ const thumbEl = vertical ? thumbYRef.current : thumbXRef.current;
2137
2421
 
2138
2422
  // Ignore clicks on thumb, including cases where React retargets the
2139
2423
  // synthetic event to the track host across a shadow boundary.
2140
- if (thumb && contains(thumb, target)) {
2424
+ if (thumbEl && contains(thumbEl, target)) {
2141
2425
  return;
2142
2426
  }
2143
- if (!viewportRef.current) {
2427
+ const viewportEl = viewportRef.current;
2428
+ if (!viewportEl) {
2144
2429
  return;
2145
2430
  }
2146
-
2147
- // Handle Y-axis (vertical) scroll
2148
- if (thumbYRef.current && scrollbarYRef.current && orientation === 'vertical') {
2149
- const thumbYOffset = getOffset(thumbYRef.current, 'margin', 'y');
2150
- const scrollbarYOffset = getOffset(scrollbarYRef.current, 'padding', 'y');
2151
- const thumbHeight = thumbYRef.current.offsetHeight;
2152
- const trackRectY = scrollbarYRef.current.getBoundingClientRect();
2153
- const clickY = event.clientY - trackRectY.top - thumbHeight / 2 - scrollbarYOffset + thumbYOffset / 2;
2154
- const scrollableContentHeight = viewportRef.current.scrollHeight;
2155
- const viewportHeight = viewportRef.current.clientHeight;
2156
- const maxThumbOffsetY = scrollbarYRef.current.offsetHeight - thumbHeight - scrollbarYOffset - thumbYOffset;
2157
- const scrollRatioY = clickY / maxThumbOffsetY;
2158
- const newScrollTop = scrollRatioY * (scrollableContentHeight - viewportHeight);
2159
- viewportRef.current.scrollTop = newScrollTop;
2160
- }
2161
- if (thumbXRef.current && scrollbarXRef.current && orientation === 'horizontal') {
2162
- const thumbXOffset = getOffset(thumbXRef.current, 'margin', 'x');
2163
- const scrollbarXOffset = getOffset(scrollbarXRef.current, 'padding', 'x');
2164
- const thumbWidth = thumbXRef.current.offsetWidth;
2165
- const trackRectX = scrollbarXRef.current.getBoundingClientRect();
2166
- const clickX = event.clientX - trackRectX.left - thumbWidth / 2 - scrollbarXOffset + thumbXOffset / 2;
2167
- const scrollableContentWidth = viewportRef.current.scrollWidth;
2168
- const viewportWidth = viewportRef.current.clientWidth;
2169
- const maxThumbOffsetX = scrollbarXRef.current.offsetWidth - thumbWidth - scrollbarXOffset - thumbXOffset;
2170
- const scrollRatioX = clickX / maxThumbOffsetX;
2171
- let newScrollLeft;
2172
- if (direction === 'rtl') {
2173
- // In RTL, invert the scroll direction
2174
- newScrollLeft = (1 - scrollRatioX) * (scrollableContentWidth - viewportWidth);
2175
-
2176
- // Adjust for browsers that use negative scrollLeft in RTL
2177
- if (viewportRef.current.scrollLeft <= 0) {
2178
- newScrollLeft = -newScrollLeft;
2179
- }
2180
- } else {
2181
- newScrollLeft = scrollRatioX * (scrollableContentWidth - viewportWidth);
2182
- }
2183
- viewportRef.current.scrollLeft = newScrollLeft;
2431
+ const scrollbarEl = vertical ? scrollbarYRef.current : scrollbarXRef.current;
2432
+ if (!thumbEl || !scrollbarEl) {
2433
+ return;
2434
+ }
2435
+ const axis = vertical ? 'y' : 'x';
2436
+ const thumbOffset = getOffset(thumbEl, 'margin', axis);
2437
+ const scrollbarOffset = getOffset(scrollbarEl, 'padding', axis);
2438
+ const thumbSizePx = vertical ? thumbEl.offsetHeight : thumbEl.offsetWidth;
2439
+ const trackRect = scrollbarEl.getBoundingClientRect();
2440
+ const clickPosition = vertical ? event.clientY - trackRect.top - thumbSizePx / 2 - scrollbarOffset + thumbOffset / 2 : event.clientX - trackRect.left - thumbSizePx / 2 - scrollbarOffset + thumbOffset / 2;
2441
+ const scrollableSize = vertical ? viewportEl.scrollHeight : viewportEl.scrollWidth;
2442
+ const viewportSize = vertical ? viewportEl.clientHeight : viewportEl.clientWidth;
2443
+ const trackSize = vertical ? scrollbarEl.offsetHeight : scrollbarEl.offsetWidth;
2444
+ const maxThumbOffset = trackSize - thumbSizePx - scrollbarOffset - thumbOffset;
2445
+ // A short or heavily padded track can drive `maxThumbOffset` to zero or
2446
+ // negative once the thumb hits its `MIN_THUMB_SIZE` floor. Dividing by it
2447
+ // would yield a non-finite (`Infinity`/`NaN`) or inverted scroll position.
2448
+ if (maxThumbOffset <= 0) {
2449
+ return;
2450
+ }
2451
+ const scrollRatio = clickPosition / maxThumbOffset;
2452
+ const maxScrollDistance = scrollableSize - viewportSize;
2453
+
2454
+ // Disable snapping before the jump-to-click assignment, or the
2455
+ // assigned position quantizes to the nearest snap point and the thumb
2456
+ // stays offset from the pointer for the whole drag. `handlePointerDown`
2457
+ // below re-runs this as a guarded no-op for the thumb-drag path.
2458
+ disableViewportSnap();
2459
+ if (vertical) {
2460
+ viewportEl.scrollTop = scrollRatio * maxScrollDistance;
2461
+ } else if (direction === 'rtl') {
2462
+ viewportEl.scrollLeft = -(1 - scrollRatio) * maxScrollDistance;
2463
+ } else {
2464
+ viewportEl.scrollLeft = scrollRatio * maxScrollDistance;
2184
2465
  }
2185
2466
  handleScroll({
2186
- x: viewportRef.current.scrollLeft,
2187
- y: viewportRef.current.scrollTop
2467
+ x: viewportEl.scrollLeft,
2468
+ y: viewportEl.scrollTop
2188
2469
  });
2189
2470
  handlePointerDown(event);
2190
2471
  },
@@ -2198,34 +2479,30 @@ const ScrollAreaScrollbar = /*#__PURE__*/React.forwardRef(function ScrollAreaScr
2198
2479
  WebkitUserSelect: 'none',
2199
2480
  userSelect: 'none',
2200
2481
  visibility: hideTrackUntilMeasured ? 'hidden' : undefined,
2201
- ...(orientation === 'vertical' && {
2482
+ ...(vertical ? {
2202
2483
  top: 0,
2203
- bottom: `var(${ScrollAreaRootCssVars.scrollAreaCornerHeight})`,
2484
+ bottom: 'var(--scroll-area-corner-height)',
2204
2485
  insetInlineEnd: 0,
2205
- [ScrollAreaScrollbarCssVars.scrollAreaThumbHeight]: `${thumbSize.height}px`
2206
- }),
2207
- ...(orientation === 'horizontal' && {
2486
+ ['--scroll-area-thumb-height']: `${thumbSize.height}px`
2487
+ } : {
2208
2488
  insetInlineStart: 0,
2209
- insetInlineEnd: `var(${ScrollAreaRootCssVars.scrollAreaCornerWidth})`,
2489
+ insetInlineEnd: 'var(--scroll-area-corner-width)',
2210
2490
  bottom: 0,
2211
- [ScrollAreaScrollbarCssVars.scrollAreaThumbWidth]: `${thumbSize.width}px`
2491
+ ['--scroll-area-thumb-width']: `${thumbSize.width}px`
2212
2492
  })
2213
2493
  }
2214
2494
  };
2215
2495
  const element = useRenderElement('div', componentProps, {
2216
- ref: [forwardedRef, orientation === 'vertical' ? scrollbarYRef : scrollbarXRef],
2496
+ ref: [forwardedRef, vertical ? scrollbarYRef : scrollbarXRef],
2217
2497
  state,
2218
2498
  props: [props, elementProps],
2219
2499
  stateAttributesMapping: scrollAreaStateAttributesMapping
2220
2500
  });
2221
- const contextValue = React.useMemo(() => ({
2222
- orientation
2223
- }), [orientation]);
2224
2501
  if (!shouldRender) {
2225
2502
  return null;
2226
2503
  }
2227
2504
  return /*#__PURE__*/jsx(ScrollAreaScrollbarContext.Provider, {
2228
- value: contextValue,
2505
+ value: orientation,
2229
2506
  children: element
2230
2507
  });
2231
2508
  });
@@ -2313,43 +2590,30 @@ const ScrollAreaThumb = /*#__PURE__*/React.forwardRef(function ScrollAreaThumb(c
2313
2590
  handlePointerDown,
2314
2591
  handlePointerMove,
2315
2592
  handlePointerUp,
2316
- setScrollingX,
2317
- setScrollingY,
2318
2593
  scrollingX,
2319
2594
  scrollingY,
2320
2595
  hasMeasuredScrollbar
2321
2596
  } = useScrollAreaRootContext();
2322
- const {
2323
- orientation
2324
- } = useScrollAreaScrollbarContext();
2597
+ const orientation = useScrollAreaScrollbarContext();
2598
+ const vertical = orientation === 'vertical';
2325
2599
  const state = {
2326
- scrolling: orientation === 'horizontal' ? scrollingX : scrollingY,
2600
+ scrolling: vertical ? scrollingY : scrollingX,
2327
2601
  orientation
2328
2602
  };
2329
- function endDrag(event) {
2330
- if (orientation === 'vertical') {
2331
- setScrollingY(false);
2332
- }
2333
- if (orientation === 'horizontal') {
2334
- setScrollingX(false);
2335
- }
2336
- handlePointerUp(event);
2337
- }
2338
2603
  const element = useRenderElement('div', componentProps, {
2339
- ref: [forwardedRef, orientation === 'vertical' ? thumbYRef : thumbXRef],
2604
+ ref: [forwardedRef, vertical ? thumbYRef : thumbXRef],
2340
2605
  state,
2341
2606
  props: [{
2342
2607
  onPointerDown: handlePointerDown,
2343
2608
  onPointerMove: handlePointerMove,
2344
- onPointerUp: endDrag,
2345
- onPointerCancel: endDrag,
2609
+ onPointerUp: handlePointerUp,
2610
+ onPointerCancel: handlePointerUp,
2346
2611
  style: {
2347
2612
  visibility: hasMeasuredScrollbar ? undefined : 'hidden',
2348
- ...(orientation === 'vertical' && {
2349
- height: `var(${ScrollAreaScrollbarCssVars.scrollAreaThumbHeight})`
2350
- }),
2351
- ...(orientation === 'horizontal' && {
2352
- width: `var(${ScrollAreaScrollbarCssVars.scrollAreaThumbWidth})`
2613
+ ...(vertical ? {
2614
+ height: 'var(--scroll-area-thumb-height)'
2615
+ } : {
2616
+ width: 'var(--scroll-area-thumb-width)'
2353
2617
  })
2354
2618
  }
2355
2619
  }, elementProps]
@@ -2429,7 +2693,7 @@ var index_parts$2 = /*#__PURE__*/Object.freeze({
2429
2693
  Positioner: ComboboxPositioner,
2430
2694
  Root: ComboboxRoot,
2431
2695
  Row: ComboboxRow,
2432
- Separator: Separator,
2696
+ Separator: ComboboxSeparator,
2433
2697
  Status: ComboboxStatus,
2434
2698
  Trigger: ComboboxTrigger,
2435
2699
  Value: ComboboxValue,
@@ -2476,22 +2740,6 @@ function getViewportRect(teleportDistance, scrubAreaEl) {
2476
2740
  };
2477
2741
  }
2478
2742
 
2479
- // This lets us invert the scale of the cursor to match the OS scale, in which the cursor doesn't
2480
- // scale with the content on pinch-zoom.
2481
- function subscribeToVisualViewportResize(element, visualScaleRef) {
2482
- const vV = getWindow(element).visualViewport;
2483
- if (!vV) {
2484
- return () => {};
2485
- }
2486
- function handleVisualResize() {
2487
- if (vV) {
2488
- visualScaleRef.current = vV.scale;
2489
- }
2490
- }
2491
- handleVisualResize();
2492
- return addEventListener(vV, 'resize', handleVisualResize);
2493
- }
2494
-
2495
2743
  const SCRUB_AREA_STYLE = {
2496
2744
  touchAction: 'none',
2497
2745
  WebkitUserSelect: 'none',
@@ -2517,8 +2765,6 @@ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldS
2517
2765
  const {
2518
2766
  state,
2519
2767
  setIsScrubbing: setRootScrubbing,
2520
- disabled,
2521
- readOnly,
2522
2768
  inputRef,
2523
2769
  incrementValue,
2524
2770
  allowInputSyncRef,
@@ -2527,6 +2773,10 @@ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldS
2527
2773
  lastChangedValueRef,
2528
2774
  valueRef
2529
2775
  } = useNumberFieldRootContext();
2776
+ const {
2777
+ disabled,
2778
+ readOnly
2779
+ } = state;
2530
2780
  const scrubAreaRef = React.useRef(null);
2531
2781
  const isScrubbingRef = React.useRef(false);
2532
2782
  const didMoveRef = React.useRef(false);
@@ -2536,21 +2786,15 @@ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldS
2536
2786
  x: 0,
2537
2787
  y: 0
2538
2788
  });
2539
- const visualScaleRef = React.useRef(1);
2540
2789
  const exitPointerLockTimeout = useTimeout();
2541
2790
  const [isTouchInput, setIsTouchInput] = React.useState(false);
2542
2791
  const [isPointerLockDenied, setIsPointerLockDenied] = React.useState(false);
2543
2792
  const [isScrubbing, setIsScrubbing] = React.useState(false);
2544
- React.useEffect(() => {
2545
- if (!isScrubbing || !scrubAreaCursorRef.current) {
2546
- return undefined;
2547
- }
2548
- return subscribeToVisualViewportResize(scrubAreaCursorRef.current, visualScaleRef);
2549
- }, [isScrubbing]);
2550
- function updateCursorTransform(x, y) {
2551
- if (scrubAreaCursorRef.current) {
2552
- scrubAreaCursorRef.current.style.transform = `translate3d(${x}px,${y}px,0) scale(${1 / visualScaleRef.current})`;
2553
- }
2793
+ function updateCursorTransform(virtualCursor, x, y) {
2794
+ // Invert the visual viewport scale so the cursor matches the OS cursor, which doesn't
2795
+ // scale with the content on pinch-zoom.
2796
+ const scale = getWindow(virtualCursor).visualViewport?.scale ?? 1;
2797
+ virtualCursor.style.transform = `translate3d(${x}px,${y}px,0) scale(${1 / scale})`;
2554
2798
  }
2555
2799
  const onScrub = useStableCallback(({
2556
2800
  movementX,
@@ -2563,24 +2807,23 @@ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldS
2563
2807
  }
2564
2808
  const rect = getViewportRect(teleportDistance, scrubAreaEl);
2565
2809
  const coords = virtualCursorCoords.current;
2810
+
2811
+ // Wrap the cursor to the opposite edge when its center crosses a viewport bound.
2812
+ const wrap = (coord, halfSize, low, high) => {
2813
+ if (coord + halfSize < low) {
2814
+ return high - halfSize;
2815
+ }
2816
+ if (coord + halfSize > high) {
2817
+ return low - halfSize;
2818
+ }
2819
+ return coord;
2820
+ };
2566
2821
  const newCoords = {
2567
- x: Math.round(coords.x + movementX),
2568
- y: Math.round(coords.y + movementY)
2822
+ x: wrap(Math.round(coords.x + movementX), virtualCursor.offsetWidth / 2, rect.left, rect.right),
2823
+ y: wrap(Math.round(coords.y + movementY), virtualCursor.offsetHeight / 2, rect.top, rect.bottom)
2569
2824
  };
2570
- const cursorWidth = virtualCursor.offsetWidth;
2571
- const cursorHeight = virtualCursor.offsetHeight;
2572
- if (newCoords.x + cursorWidth / 2 < rect.left) {
2573
- newCoords.x = rect.right - cursorWidth / 2;
2574
- } else if (newCoords.x + cursorWidth / 2 > rect.right) {
2575
- newCoords.x = rect.left - cursorWidth / 2;
2576
- }
2577
- if (newCoords.y + cursorHeight / 2 < rect.top) {
2578
- newCoords.y = rect.bottom - cursorHeight / 2;
2579
- } else if (newCoords.y + cursorHeight / 2 > rect.bottom) {
2580
- newCoords.y = rect.top - cursorHeight / 2;
2581
- }
2582
2825
  virtualCursorCoords.current = newCoords;
2583
- updateCursorTransform(newCoords.x, newCoords.y);
2826
+ updateCursorTransform(virtualCursor, newCoords.x, newCoords.y);
2584
2827
  });
2585
2828
  const onScrubbingChange = useStableCallback((scrubbingValue, {
2586
2829
  clientX,
@@ -2599,7 +2842,7 @@ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldS
2599
2842
  y: clientY - virtualCursor.offsetHeight / 2
2600
2843
  };
2601
2844
  virtualCursorCoords.current = initialCoords;
2602
- updateCursorTransform(initialCoords.x, initialCoords.y);
2845
+ updateCursorTransform(virtualCursor, initialCoords.x, initialCoords.y);
2603
2846
  });
2604
2847
  React.useEffect(function registerGlobalScrubbingEventListeners() {
2605
2848
  // Only listen while actively scrubbing; avoids unrelated pointerup events committing.
@@ -2641,6 +2884,9 @@ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldS
2641
2884
  }
2642
2885
  }
2643
2886
  function handleScrubPointerMove(event) {
2887
+ // The effects below can tear down and re-run without unmounting (`<Activity>`), which
2888
+ // clears the ref while `isScrubbing` stays `true` and re-attaches this listener. The ref
2889
+ // is the source of truth for whether a pointer is actually down.
2644
2890
  if (!isScrubbingRef.current) {
2645
2891
  return;
2646
2892
  }
@@ -2708,8 +2954,7 @@ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldS
2708
2954
  role: 'presentation',
2709
2955
  style: SCRUB_AREA_STYLE,
2710
2956
  async onPointerDown(event) {
2711
- const isMainButton = !event.button || event.button === 0;
2712
- if (event.defaultPrevented || readOnly || !isMainButton || disabled) {
2957
+ if (event.defaultPrevented || readOnly || event.button || disabled) {
2713
2958
  return;
2714
2959
  }
2715
2960
  const isTouch = event.pointerType === 'touch';
@@ -2748,7 +2993,7 @@ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldS
2748
2993
  ref: [forwardedRef, scrubAreaRef],
2749
2994
  state,
2750
2995
  props: [defaultProps, elementProps],
2751
- stateAttributesMapping: stateAttributesMapping$6
2996
+ stateAttributesMapping: stateAttributesMapping$4
2752
2997
  });
2753
2998
  const contextValue = React.useMemo(() => ({
2754
2999
  isScrubbing,
@@ -2805,7 +3050,7 @@ const NumberFieldScrubAreaCursor = /*#__PURE__*/React.forwardRef(function Number
2805
3050
  role: 'presentation',
2806
3051
  style: CURSOR_STYLE
2807
3052
  }, elementProps],
2808
- stateAttributesMapping: stateAttributesMapping$6
3053
+ stateAttributesMapping: stateAttributesMapping$4
2809
3054
  });
2810
3055
  return element && /*#__PURE__*/ReactDOM.createPortal(element, ownerDocument(domElement).body);
2811
3056
  });
@@ -2849,7 +3094,8 @@ const SelectLabel = /*#__PURE__*/React.forwardRef(function SelectLabel(component
2849
3094
  id: defaultLabelId,
2850
3095
  fallbackControlId: triggerElement?.id ?? rootId,
2851
3096
  setLabelId(nextLabelId) {
2852
- store.set('labelId', nextLabelId);
3097
+ const resolvedLabelId = typeof nextLabelId === 'function' ? nextLabelId(store.state.labelId) : nextLabelId;
3098
+ store.set('labelId', resolvedLabelId);
2853
3099
  }
2854
3100
  });
2855
3101
  return useRenderElement('div', componentProps, {
@@ -2894,7 +3140,7 @@ const SelectIcon = /*#__PURE__*/React.forwardRef(function SelectIcon(componentPr
2894
3140
  });
2895
3141
  if (process.env.NODE_ENV !== "production") SelectIcon.displayName = "SelectIcon";
2896
3142
 
2897
- const stateAttributesMapping$1 = {
3143
+ const stateAttributesMapping = {
2898
3144
  ...popupStateMapping,
2899
3145
  ...transitionStatusMapping
2900
3146
  };
@@ -2933,17 +3179,12 @@ const SelectBackdrop = /*#__PURE__*/React.forwardRef(function SelectBackdrop(com
2933
3179
  WebkitUserSelect: 'none'
2934
3180
  }
2935
3181
  }, elementProps],
2936
- stateAttributesMapping: stateAttributesMapping$1
3182
+ stateAttributesMapping
2937
3183
  });
2938
3184
  return element;
2939
3185
  });
2940
3186
  if (process.env.NODE_ENV !== "production") SelectBackdrop.displayName = "SelectBackdrop";
2941
3187
 
2942
- const stateAttributesMapping = {
2943
- ...popupStateMapping,
2944
- ...transitionStatusMapping
2945
- };
2946
-
2947
3188
  /**
2948
3189
  * Displays an element positioned against the select popup anchor.
2949
3190
  * Renders a `<div>` element.
@@ -2982,7 +3223,7 @@ const SelectArrow = /*#__PURE__*/React.forwardRef(function SelectArrow(component
2982
3223
  style: arrowStyles,
2983
3224
  'aria-hidden': true
2984
3225
  }, elementProps],
2985
- stateAttributesMapping
3226
+ stateAttributesMapping: popupTransitionStateMapping
2986
3227
  });
2987
3228
  if (alignItemWithTriggerActive) {
2988
3229
  return null;
@@ -3000,7 +3241,7 @@ const SelectScrollArrow = /*#__PURE__*/React.forwardRef(function SelectScrollArr
3000
3241
  className,
3001
3242
  style,
3002
3243
  direction,
3003
- keepMounted = false,
3244
+ keepMounted,
3004
3245
  ...elementProps
3005
3246
  } = componentProps;
3006
3247
  const isUp = direction === 'up';
@@ -3031,12 +3272,10 @@ const SelectScrollArrow = /*#__PURE__*/React.forwardRef(function SelectScrollArr
3031
3272
  } = useTransitionStatus(visible);
3032
3273
  useIsoLayoutEffect(() => {
3033
3274
  scrollArrowsMountedCountRef.current += 1;
3034
- if (!store.state.hasScrollArrows) {
3035
- store.set('hasScrollArrows', true);
3036
- }
3275
+ store.set('hasScrollArrows', true);
3037
3276
  return () => {
3038
3277
  scrollArrowsMountedCountRef.current = Math.max(0, scrollArrowsMountedCountRef.current - 1);
3039
- if (scrollArrowsMountedCountRef.current === 0 && store.state.hasScrollArrows) {
3278
+ if (scrollArrowsMountedCountRef.current === 0) {
3040
3279
  store.set('hasScrollArrows', false);
3041
3280
  }
3042
3281
  };
@@ -3073,7 +3312,7 @@ const SelectScrollArrow = /*#__PURE__*/React.forwardRef(function SelectScrollArr
3073
3312
  return;
3074
3313
  }
3075
3314
  store.set('activeIndex', null);
3076
- handleScrollArrowVisibility();
3315
+ handleScrollArrowVisibility(scroller);
3077
3316
  const maxScrollTop = getMaxScrollOffset(scroller.scrollHeight, scroller.clientHeight);
3078
3317
  const scrollTop = normalizeScrollOffset(scroller.scrollTop, maxScrollTop);
3079
3318
  const isScrolledToEdge = scrollTop === (isUp ? 0 : maxScrollTop);
@@ -3081,11 +3320,6 @@ const SelectScrollArrow = /*#__PURE__*/React.forwardRef(function SelectScrollArr
3081
3320
  if (scrollTop !== scroller.scrollTop) {
3082
3321
  scroller.scrollTop = scrollTop;
3083
3322
  }
3084
-
3085
- // Fallback when there are no items registered yet.
3086
- if (items.length === 0) {
3087
- store.set(isUp ? 'scrollUpArrowVisible' : 'scrollDownArrowVisible', !isScrolledToEdge);
3088
- }
3089
3323
  if (isScrolledToEdge) {
3090
3324
  timeout.clear();
3091
3325
  return;
@@ -3174,6 +3408,14 @@ const SelectScrollUpArrow = /*#__PURE__*/React.forwardRef(function SelectScrollU
3174
3408
  });
3175
3409
  if (process.env.NODE_ENV !== "production") SelectScrollUpArrow.displayName = "SelectScrollUpArrow";
3176
3410
 
3411
+ /**
3412
+ * A visual separator between items or groups.
3413
+ * Renders a `<div>` element.
3414
+ *
3415
+ * Documentation: [Base UI Select](https://base-ui.com/react/components/select)
3416
+ */
3417
+ const SelectSeparator = ListboxSeparator;
3418
+
3177
3419
  var index_parts = /*#__PURE__*/Object.freeze({
3178
3420
  __proto__: null,
3179
3421
  Arrow: SelectArrow,
@@ -3192,7 +3434,7 @@ var index_parts = /*#__PURE__*/Object.freeze({
3192
3434
  Root: SelectRoot,
3193
3435
  ScrollDownArrow: SelectScrollDownArrow,
3194
3436
  ScrollUpArrow: SelectScrollUpArrow,
3195
- Separator: Separator,
3437
+ Separator: SelectSeparator,
3196
3438
  Trigger: SelectTrigger,
3197
3439
  Value: SelectValue
3198
3440
  });