@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,11 +1,9 @@
1
1
  import * as React from 'react';
2
- import { N as createSelector, n as useControlled, m as useTransitionStatus, O as useOpenInteractionType, P as useStore, q as useValueAsRef, b as useStableCallback, a as useIsoLayoutEffect, K as useValueChanged, f as useOpenChangeComplete, Q as useClick, R as useDismiss, F as FOCUSABLE_POPUP_PROPS, U as useOnFirstRender, d as createChangeEventDetails, v as none, w as visuallyHiddenInput, x as visuallyHidden, V as Store, W as focusOut, X as outsidePress, Y as pressableTriggerOpenStateMapping, c as useButton, k as useTimeout, Z as getFloatingFocusElement, _ as contains, o as ownerDocument, $ as cancelOpen, a0 as FloatingPortal, a1 as InternalBackdrop, a2 as inertValue, a3 as DROPDOWN_COLLISION_AVOIDANCE, p as popupStateMapping, t as transitionStatusMapping, a4 as useDirection, a5 as useAnimationFrame, a6 as webkit, s as addEventListener, i as FloatingFocusManager, a7 as windowResize, a8 as isVirtualClick, a9 as itemPress, u as useBaseUiId } from './useScrollLock-es.js';
2
+ import { v as useControlled, s as useTransitionStatus, R as useOpenInteractionType, U as useStore, w as useValueAsRef, d as useStableCallback, c as useIsoLayoutEffect, L as useValueChanged, l as useOpenChangeComplete, V as useClick, W as useDismiss, F as FOCUSABLE_POPUP_PROPS, h as createChangeEventDetails, z as none, B as visuallyHiddenInput, C as visuallyHidden, X as ReactStore, Y as focusOut, Z as outsidePress, _ as pressableTriggerOpenStateMapping, f as useButton, q as useTimeout, $ as getFloatingFocusElement, a0 as contains, o as ownerDocument, a1 as cancelOpen, a2 as FloatingPortal, a3 as InternalBackdrop, a4 as inertValue, a5 as DROPDOWN_COLLISION_AVOIDANCE, a6 as popupStateMapping, t as transitionStatusMapping, a7 as useDirection, a8 as useAnimationFrame, I as getWindow, a9 as webkit, y as addEventListener, n as FloatingFocusManager, aa as rectToClientRect, ab as windowResize, ac as isVirtualClick, ad as itemPress, u as useBaseUiId } from './useScrollLock-es.js';
3
3
  import { f as formatErrorMessage, a as useRefWithInit, m as mergeProps, E as EMPTY_OBJECT, b as useMergedRefs, c as EMPTY_ARRAY, u as useRenderElement } from './useRenderElement-es.js';
4
- import { s as stringifyAsValue, h as hasNullItemLabel, e as compareItemEquality, a as useFormContext, b as useFieldRootContext, c as useLabelableId, d as useRegisterFieldControl, g as findItemIndex, i as useFloatingRootContext, j as useListNavigation, k as useTypeahead, l as defaultItemEquality, m as stringifyAsLabel, n as areArraysEqual, f as fieldValidityMapping, u as useLabelableContext, r as resolveAriaLabelledBy, o as getPseudoElementBounds, p as resolveMultipleLabels, q as resolveSelectedLabel, t as useAnchoredPopupScrollLock, v as useAnchorPositioning, w as usePositioner, C as CompositeList, x as selectedValueIncludes, y as getDisabledMountTransitionStyles, z as useCompositeListItem, I as IndexGuessBehavior, A as removeItem } from './useCompositeListItem-es.js';
4
+ import { s as stringifyAsValue, h as hasNullItemLabel, e as compareItemEquality, a as useFormContext, b as useFieldRootContext, c as useLabelableId, d as useRegisterFieldControl, g as findItemIndex, i as useFloatingRootContext, j as useListNavigation, k as useTypeahead, l as useOnFirstRender, m as defaultItemEquality, n as stringifyAsLabel, o as areArraysEqual, f as fieldValidityMapping, u as useLabelableContext, r as resolveAriaLabelledBy, p as isMouseWithinBounds, q as resolveMultipleLabels, t as resolveSelectedLabel, v as useAnchoredPopupScrollLock, w as useAnchorPositioning, x as usePositioner, C as CompositeList, y as platform, z as getDisabledMountTransitionStyles, A as useCompositeListItem, B as removeItem } from './useCompositeListItem-es.js';
5
5
  import { g as getMaxScrollOffset, n as normalizeScrollOffset, u as useCSPContext, c as clamp, S as SCROLL_EDGE_TOLERANCE_PX, C as COMPOSITE_KEYS, s as styleDisableScrollbar } from './scrollEdges-es.js';
6
6
  import { jsx, jsxs } from 'react/jsx-runtime';
7
- import { K as getWindow, r as rectToClientRect } from './floating-ui.utils-es.js';
8
- import { p as platform } from './floating-ui.react-dom-es.js';
9
7
 
10
8
  const ToolbarRootContext = /*#__PURE__*/React.createContext(undefined);
11
9
  if (process.env.NODE_ENV !== "production") ToolbarRootContext.displayName = "ToolbarRootContext";
@@ -27,7 +25,7 @@ function usePreviousValue(value) {
27
25
  current: value,
28
26
  previous: null
29
27
  });
30
- if (value !== state.current) {
28
+ if (!Object.is(value, state.current)) {
31
29
  setState({
32
30
  current: value,
33
31
  previous: state.current
@@ -42,8 +40,6 @@ function isElementDisabled(element) {
42
40
 
43
41
  const SelectRootContext = /*#__PURE__*/React.createContext(null);
44
42
  if (process.env.NODE_ENV !== "production") SelectRootContext.displayName = "SelectRootContext";
45
- const SelectFloatingContext = /*#__PURE__*/React.createContext(null);
46
- if (process.env.NODE_ENV !== "production") SelectFloatingContext.displayName = "SelectFloatingContext";
47
43
  function useSelectRootContext() {
48
44
  const context = React.useContext(SelectRootContext);
49
45
  if (context === null) {
@@ -51,25 +47,16 @@ function useSelectRootContext() {
51
47
  }
52
48
  return context;
53
49
  }
54
- function useSelectFloatingContext() {
55
- const context = React.useContext(SelectFloatingContext);
56
- if (context === null) {
57
- throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: SelectFloatingContext is missing. Select parts must be placed within <Select.Root>.' : formatErrorMessage(61));
58
- }
59
- return context;
60
- }
61
50
 
62
51
  const selectors = {
63
- id: createSelector(state => state.id),
64
- labelId: createSelector(state => state.labelId),
65
- modal: createSelector(state => state.modal),
66
- multiple: createSelector(state => state.multiple),
67
- items: createSelector(state => state.items),
68
- itemToStringLabel: createSelector(state => state.itemToStringLabel),
69
- itemToStringValue: createSelector(state => state.itemToStringValue),
70
- isItemEqualToValue: createSelector(state => state.isItemEqualToValue),
71
- value: createSelector(state => state.value),
72
- hasSelectedValue: createSelector(state => {
52
+ id: state => state.id,
53
+ labelId: state => state.labelId,
54
+ modal: state => state.modal,
55
+ items: state => state.items,
56
+ itemToStringLabel: state => state.itemToStringLabel,
57
+ isItemEqualToValue: state => state.isItemEqualToValue,
58
+ value: state => state.value,
59
+ hasSelectedValue: state => {
73
60
  const {
74
61
  value,
75
62
  multiple,
@@ -82,19 +69,19 @@ const selectors = {
82
69
  return value.length > 0;
83
70
  }
84
71
  return stringifyAsValue(value, itemToStringValue) !== '';
85
- }),
86
- hasNullItemLabel: createSelector((state, enabled) => {
72
+ },
73
+ hasNullItemLabel: (state, enabled) => {
87
74
  return enabled ? hasNullItemLabel(state.items) : false;
88
- }),
89
- open: createSelector(state => state.open),
90
- mounted: createSelector(state => state.mounted),
91
- forceMount: createSelector(state => state.forceMount),
92
- transitionStatus: createSelector(state => state.transitionStatus),
93
- openMethod: createSelector(state => state.openMethod),
94
- activeIndex: createSelector(state => state.activeIndex),
95
- selectedIndex: createSelector(state => state.selectedIndex),
96
- isActive: createSelector((state, index) => state.activeIndex === index),
97
- isSelected: createSelector((state, itemValue) => {
75
+ },
76
+ open: state => state.open,
77
+ mounted: state => state.mounted,
78
+ forceMount: state => state.forceMount,
79
+ transitionStatus: state => state.transitionStatus,
80
+ openMethod: state => state.openMethod,
81
+ activeIndex: state => state.activeIndex,
82
+ selectedIndex: state => state.selectedIndex,
83
+ isActive: (state, index) => state.activeIndex === index,
84
+ isSelected: (state, itemValue) => {
98
85
  const comparer = state.isItemEqualToValue;
99
86
  const storeValue = state.value;
100
87
  if (state.multiple) {
@@ -105,19 +92,19 @@ const selectors = {
105
92
  // value changes while the popup is open, where the index sync is deferred) must not
106
93
  // keep a previously selected item marked as selected.
107
94
  return compareItemEquality(itemValue, storeValue, comparer);
108
- }),
109
- isSelectedByFocus: createSelector((state, index) => {
95
+ },
96
+ isSelectedByFocus: (state, index) => {
110
97
  return state.selectedIndex === index;
111
- }),
112
- popupProps: createSelector(state => state.popupProps),
113
- triggerProps: createSelector(state => state.triggerProps),
114
- triggerElement: createSelector(state => state.triggerElement),
115
- positionerElement: createSelector(state => state.positionerElement),
116
- listElement: createSelector(state => state.listElement),
117
- popupSide: createSelector(state => state.popupSide),
118
- scrollUpArrowVisible: createSelector(state => state.scrollUpArrowVisible),
119
- scrollDownArrowVisible: createSelector(state => state.scrollDownArrowVisible),
120
- hasScrollArrows: createSelector(state => state.hasScrollArrows)
98
+ },
99
+ popupProps: state => state.popupProps,
100
+ triggerProps: state => state.triggerProps,
101
+ triggerElement: state => state.triggerElement,
102
+ positionerElement: state => state.positionerElement,
103
+ listElement: state => state.listElement,
104
+ popupSide: state => state.popupSide,
105
+ scrollUpArrowVisible: state => state.scrollUpArrowVisible,
106
+ scrollDownArrowVisible: state => state.scrollDownArrowVisible,
107
+ hasScrollArrows: state => state.hasScrollArrows
121
108
  };
122
109
 
123
110
  function SelectRoot(props) {
@@ -203,7 +190,7 @@ function SelectRoot(props) {
203
190
  openMethod,
204
191
  triggerProps: interactionTypeProps
205
192
  } = useOpenInteractionType(open);
206
- const store = useRefWithInit(() => new Store({
193
+ const store = useRefWithInit(() => new ReactStore({
207
194
  id: generatedId,
208
195
  labelId: undefined,
209
196
  modal,
@@ -235,7 +222,7 @@ function SelectRoot(props) {
235
222
  const triggerElement = useStore(store, selectors.triggerElement);
236
223
  const positionerElement = useStore(store, selectors.positionerElement);
237
224
  const previousOpenMethod = usePreviousValue(openMethod);
238
- const renderedOpenMethod = openMethod ?? previousOpenMethod ?? null;
225
+ const renderedOpenMethod = openMethod ?? previousOpenMethod;
239
226
  const serializedValue = React.useMemo(() => {
240
227
  // In multiple mode the shared input is nameless; per-value entries are submitted via
241
228
  // `hiddenInputs`. Its value is therefore irrelevant, and passing the whole array to
@@ -251,38 +238,26 @@ function SelectRoot(props) {
251
238
  }
252
239
  return stringifyAsValue(value, itemToStringValue);
253
240
  }, [multiple, value, itemToStringValue]);
254
- const controlRef = useValueAsRef(store.state.triggerElement);
241
+ const controlRef = useValueAsRef(triggerElement);
255
242
  const getStringifiedValueForForm = useStableCallback(() => fieldStringValue);
256
243
  useRegisterFieldControl(controlRef, generatedId, value, getStringifiedValueForForm, !disabled, nameProp);
257
244
  const initialValueRef = React.useRef(value);
258
245
  // Mirror the `hasSelectedValue` store selector so the Field's filled state agrees with the
259
246
  // trigger/value placeholder semantics (a value serializing to `''` counts as empty).
260
- const hasSelectedValue = multiple ? Array.isArray(value) && value.length > 0 : value != null && stringifyAsValue(value, itemToStringValue) !== '';
261
- useIsoLayoutEffect(() => {
262
- // Ensure the values and labels are registered for programmatic value changes.
263
- if (value !== initialValueRef.current) {
264
- store.set('forceMount', true);
265
- }
266
- }, [store, value]);
247
+ const hasSelectedValue = multiple ? Array.isArray(value) && value.length > 0 : value != null && serializedValue !== '';
267
248
  useIsoLayoutEffect(() => {
268
249
  setFilled(hasSelectedValue);
269
250
  }, [hasSelectedValue, setFilled]);
270
251
  useIsoLayoutEffect(function syncSelectedIndex() {
271
- const registry = valuesRef.current;
272
- let nextIndex;
252
+ let target = value;
253
+ let empty = false;
273
254
  if (multiple) {
274
255
  const currentValue = Array.isArray(value) ? value : [];
275
- if (currentValue.length === 0) {
276
- nextIndex = null;
277
- } else {
278
- const lastValue = currentValue[currentValue.length - 1];
279
- const lastIndex = findItemIndex(registry, lastValue, isItemEqualToValue);
280
- nextIndex = lastIndex === -1 ? null : lastIndex;
281
- }
282
- } else {
283
- const index = findItemIndex(registry, value, isItemEqualToValue);
284
- nextIndex = index === -1 ? null : index;
256
+ empty = currentValue.length === 0;
257
+ target = currentValue[currentValue.length - 1];
285
258
  }
259
+ const index = empty ? -1 : findItemIndex(valuesRef.current, target, isItemEqualToValue);
260
+ const nextIndex = index === -1 ? null : index;
286
261
  if (nextIndex === null) {
287
262
  selectedItemTextRef.current = null;
288
263
  }
@@ -290,7 +265,7 @@ function SelectRoot(props) {
290
265
  return;
291
266
  }
292
267
  store.set('selectedIndex', nextIndex);
293
- }, [hasSelectedValue, multiple, open, value, valuesRef, isItemEqualToValue, store, selectedItemTextRef]);
268
+ }, [multiple, open, value, isItemEqualToValue, store]);
294
269
  function isSelectedValueDirty(currentValue) {
295
270
  const initialValue = validityData.initialValue;
296
271
  if (Array.isArray(currentValue) && Array.isArray(initialValue)) {
@@ -321,7 +296,9 @@ function SelectRoot(props) {
321
296
  setMounted(false);
322
297
  store.update({
323
298
  activeIndex: null,
324
- openMethod: null
299
+ openMethod: null,
300
+ scrollUpArrowVisible: false,
301
+ scrollDownArrowVisible: false
325
302
  });
326
303
  onOpenChangeComplete?.(false);
327
304
  });
@@ -345,21 +322,13 @@ function SelectRoot(props) {
345
322
  }
346
323
  setValueUnwrapped(nextValue);
347
324
  });
348
- const handleScrollArrowVisibility = useStableCallback(() => {
349
- const scroller = store.state.listElement || popupRef.current;
350
- if (!scroller) {
351
- return;
352
- }
325
+ const handleScrollArrowVisibility = useStableCallback(scroller => {
353
326
  const maxScrollTop = getMaxScrollOffset(scroller.scrollHeight, scroller.clientHeight);
354
327
  const scrollTop = normalizeScrollOffset(scroller.scrollTop, maxScrollTop);
355
328
  const shouldShowUp = scrollTop > 0;
356
329
  const shouldShowDown = scrollTop < maxScrollTop;
357
- if (store.state.scrollUpArrowVisible !== shouldShowUp) {
358
- store.set('scrollUpArrowVisible', shouldShowUp);
359
- }
360
- if (store.state.scrollDownArrowVisible !== shouldShowDown) {
361
- store.set('scrollDownArrowVisible', shouldShowDown);
362
- }
330
+ store.set('scrollUpArrowVisible', shouldShowUp);
331
+ store.set('scrollDownArrowVisible', shouldShowDown);
363
332
  });
364
333
  const floatingContext = useFloatingRootContext({
365
334
  open,
@@ -404,20 +373,16 @@ function SelectRoot(props) {
404
373
  if (open) {
405
374
  store.set('activeIndex', index);
406
375
  } else {
407
- setValue(valuesRef.current[index], createChangeEventDetails('none'));
376
+ setValue(valuesRef.current[index], createChangeEventDetails(none));
408
377
  }
409
378
  },
410
379
  onTyping(typing) {
411
380
  typingRef.current = typing;
412
381
  }
413
382
  });
414
- const mergedTriggerProps = React.useMemo(() => {
415
- const triggerInteractionProps = mergeProps(typeahead.reference, listNavigation.reference, dismiss.reference, click.reference, interactionTypeProps);
416
- if (generatedId) {
417
- triggerInteractionProps.id = generatedId;
418
- }
419
- return triggerInteractionProps;
420
- }, [click.reference, typeahead.reference, listNavigation.reference, dismiss.reference, interactionTypeProps, generatedId]);
383
+
384
+ // `Select.Trigger` applies the id itself from the store, so it's deliberately not merged here.
385
+ const mergedTriggerProps = React.useMemo(() => mergeProps(typeahead.reference, listNavigation.reference, dismiss.reference, click.reference, interactionTypeProps), [click.reference, typeahead.reference, listNavigation.reference, dismiss.reference, interactionTypeProps]);
421
386
  const popupProps = React.useMemo(() => mergeProps(FOCUSABLE_POPUP_PROPS, typeahead.floating, listNavigation.floating, dismiss.floating), [typeahead.floating, listNavigation.floating, dismiss.floating]);
422
387
  const itemProps = listNavigation.item ?? EMPTY_OBJECT;
423
388
  useOnFirstRender(() => {
@@ -426,27 +391,25 @@ function SelectRoot(props) {
426
391
  triggerProps: mergedTriggerProps
427
392
  });
428
393
  });
429
- useIsoLayoutEffect(() => {
430
- store.update({
431
- id: generatedId,
432
- modal,
433
- multiple,
434
- value,
435
- open,
436
- mounted,
437
- transitionStatus,
438
- popupProps,
439
- triggerProps: mergedTriggerProps,
440
- items,
441
- itemToStringLabel,
442
- itemToStringValue,
443
- isItemEqualToValue,
444
- openMethod: renderedOpenMethod
445
- });
446
- }, [store, generatedId, modal, multiple, value, open, mounted, transitionStatus, popupProps, mergedTriggerProps, items, itemToStringLabel, itemToStringValue, isItemEqualToValue, renderedOpenMethod]);
394
+ store.useSyncedValues({
395
+ id: generatedId,
396
+ modal,
397
+ multiple,
398
+ value,
399
+ open,
400
+ mounted,
401
+ transitionStatus,
402
+ popupProps,
403
+ triggerProps: mergedTriggerProps,
404
+ items,
405
+ itemToStringLabel,
406
+ itemToStringValue,
407
+ isItemEqualToValue,
408
+ openMethod: renderedOpenMethod
409
+ });
447
410
  const contextValue = React.useMemo(() => ({
448
411
  store,
449
- name,
412
+ floatingContext,
450
413
  required,
451
414
  disabled,
452
415
  readOnly,
@@ -471,9 +434,8 @@ function SelectRoot(props) {
471
434
  onOpenChangeComplete,
472
435
  alignItemWithTriggerActiveRef,
473
436
  initialValueRef
474
- }), [store, name, required, disabled, readOnly, multiple, highlightItemOnHover, setValue, setOpen, itemProps, validation, onOpenChangeComplete, handleScrollArrowVisibility]);
437
+ }), [store, floatingContext, required, disabled, readOnly, multiple, highlightItemOnHover, setValue, setOpen, itemProps, validation, onOpenChangeComplete, handleScrollArrowVisibility]);
475
438
  const ref = useMergedRefs(inputRef, validation.inputRef);
476
- const hasMultipleSelection = multiple && Array.isArray(value) && value.length > 0;
477
439
  const hiddenInputName = multiple ? undefined : name;
478
440
  const hiddenInputs = React.useMemo(() => {
479
441
  if (!multiple || !Array.isArray(value) || !name) {
@@ -490,73 +452,69 @@ function SelectRoot(props) {
490
452
  }, currentSerializedValue);
491
453
  });
492
454
  }, [multiple, value, form, name, itemToStringValue, disabled]);
493
- return /*#__PURE__*/jsx(SelectRootContext.Provider, {
455
+ return /*#__PURE__*/jsxs(SelectRootContext.Provider, {
494
456
  value: contextValue,
495
- children: /*#__PURE__*/jsxs(SelectFloatingContext.Provider, {
496
- value: floatingContext,
497
- children: [children, /*#__PURE__*/jsx("input", {
498
- ...validation.getValidationProps(disabled, {
499
- onFocus() {
500
- // Move focus to the trigger element when the hidden input is focused.
501
- store.state.triggerElement?.focus({
502
- // Supported in Chrome from 144 (January 2026)
503
- focusVisible: true
504
- });
505
- },
506
- // Handle browser autofill.
507
- onChange(event) {
508
- // Workaround for https://github.com/facebook/react/issues/9023
509
- if (event.nativeEvent.defaultPrevented || disabled || readOnly) {
457
+ children: [children, /*#__PURE__*/jsx("input", {
458
+ ...validation.getValidationProps(disabled, {
459
+ onFocus() {
460
+ // Move focus to the trigger element when the hidden input is focused.
461
+ store.state.triggerElement?.focus({
462
+ // Supported in Chrome from 144 (January 2026)
463
+ focusVisible: true
464
+ });
465
+ },
466
+ // Handle browser autofill.
467
+ onChange(event) {
468
+ // Workaround for https://github.com/react/react/issues/9023
469
+ if (event.nativeEvent.defaultPrevented || disabled || readOnly) {
470
+ return;
471
+ }
472
+ const nextValue = event.currentTarget.value;
473
+ const details = createChangeEventDetails(none, event.nativeEvent);
474
+ function handleChange() {
475
+ if (multiple) {
476
+ // Browser autofill only writes a single scalar value.
510
477
  return;
511
478
  }
512
- const nextValue = event.currentTarget.value;
513
- const details = createChangeEventDetails(none, event.nativeEvent);
514
- function handleChange() {
515
- if (multiple) {
516
- // Browser autofill only writes a single scalar value.
517
- return;
518
- }
519
479
 
520
- // Preserve the original serialized matching, then fall back to rendered text,
521
- // which browsers can autofill for primitive values like `value="US">United States`.
522
- const nextValueLower = nextValue.toLowerCase();
523
- let matchingIndex = valuesRef.current.findIndex(candidate => stringifyAsValue(candidate, itemToStringValue).toLowerCase() === nextValueLower || stringifyAsLabel(candidate, itemToStringLabel).toLowerCase() === nextValueLower);
524
- if (matchingIndex === -1) {
525
- matchingIndex = valuesRef.current.findIndex((_, index) => {
526
- const renderedLabel = labelsRef.current[index];
527
- return renderedLabel != null && renderedLabel.toLowerCase() === nextValueLower;
528
- });
529
- }
530
- const matchingValue = matchingIndex === -1 ? undefined : valuesRef.current[matchingIndex];
531
- if (matchingValue != null) {
532
- // `setValue` may be canceled by `onValueChange`; rely on `useValueChanged` to
533
- // mark the field dirty and run validation only when the value actually changes.
534
- setValue(matchingValue, details);
535
- }
480
+ // Preserve the original serialized matching, then fall back to rendered text,
481
+ // which browsers can autofill for primitive values like `value="US">United States`.
482
+ const nextValueLower = nextValue.toLowerCase();
483
+ let matchingIndex = valuesRef.current.findIndex(candidate => stringifyAsValue(candidate, itemToStringValue).toLowerCase() === nextValueLower || stringifyAsLabel(candidate, itemToStringLabel).toLowerCase() === nextValueLower);
484
+ if (matchingIndex === -1) {
485
+ matchingIndex = valuesRef.current.findIndex((_, index) => {
486
+ const renderedLabel = labelsRef.current[index];
487
+ return renderedLabel != null && renderedLabel.toLowerCase() === nextValueLower;
488
+ });
489
+ }
490
+ const matchingValue = valuesRef.current[matchingIndex];
491
+ if (matchingValue != null) {
492
+ // `setValue` may be canceled by `onValueChange`; rely on `useValueChanged` to
493
+ // mark the field dirty and run validation only when the value actually changes.
494
+ setValue(matchingValue, details);
536
495
  }
537
- store.set('forceMount', true);
538
- queueMicrotask(handleChange);
539
496
  }
540
- }),
541
- id: generatedId && hiddenInputName == null ? `${generatedId}-hidden-input` : undefined,
542
- form: form,
543
- name: hiddenInputName,
544
- autoComplete: autoComplete,
545
- value: serializedValue,
546
- disabled: disabled,
547
- required: required && !hasMultipleSelection,
548
- readOnly: readOnly,
549
- ref: ref,
550
- style: name ? visuallyHiddenInput : visuallyHidden,
551
- tabIndex: -1,
552
- "aria-hidden": true,
553
- suppressHydrationWarning: true
554
- }), hiddenInputs]
555
- })
497
+ store.set('forceMount', true);
498
+ queueMicrotask(handleChange);
499
+ }
500
+ }),
501
+ id: generatedId && hiddenInputName == null ? `${generatedId}-hidden-input` : undefined,
502
+ form: form,
503
+ name: hiddenInputName,
504
+ autoComplete: autoComplete,
505
+ value: serializedValue,
506
+ disabled: disabled,
507
+ required: required && !(multiple && hasSelectedValue),
508
+ readOnly: readOnly,
509
+ ref: ref,
510
+ style: name ? visuallyHiddenInput : visuallyHidden,
511
+ tabIndex: -1,
512
+ "aria-hidden": true,
513
+ suppressHydrationWarning: true
514
+ }), hiddenInputs]
556
515
  });
557
516
  }
558
517
 
559
- const BOUNDARY_OFFSET = 2;
560
518
  const SELECTED_DELAY = 400;
561
519
  const stateAttributesMapping$2 = {
562
520
  ...pressableTriggerOpenStateMapping,
@@ -629,9 +587,7 @@ const SelectTrigger = /*#__PURE__*/React.forwardRef(function SelectTrigger(compo
629
587
  disabled,
630
588
  native: nativeButton
631
589
  });
632
- const setTriggerElement = useStableCallback(element => {
633
- store.set('triggerElement', element);
634
- });
590
+ const setTriggerElement = store.useStateSetter('triggerElement');
635
591
  const timeoutFocus = useTimeout();
636
592
  const timeoutMouseDown = useTimeout();
637
593
  const selectedDelayTimeout = useTimeout();
@@ -660,7 +616,7 @@ const SelectTrigger = /*#__PURE__*/React.forwardRef(function SelectTrigger(compo
660
616
  const mergedProps = mergeProps(triggerProps, {
661
617
  id,
662
618
  role: 'combobox',
663
- 'aria-expanded': open ? 'true' : 'false',
619
+ 'aria-expanded': open,
664
620
  'aria-haspopup': 'listbox',
665
621
  'aria-controls': open ? listElement?.id ?? getFloatingFocusElement(positionerElement)?.id : undefined,
666
622
  'aria-labelledby': ariaLabelledBy,
@@ -709,8 +665,7 @@ const SelectTrigger = /*#__PURE__*/React.forwardRef(function SelectTrigger(compo
709
665
  if (contains(triggerRef.current, mouseUpTarget) || contains(positionerRef.current, mouseUpTarget)) {
710
666
  return;
711
667
  }
712
- const bounds = getPseudoElementBounds(triggerRef.current);
713
- if (mouseEvent.clientX >= bounds.left - BOUNDARY_OFFSET && mouseEvent.clientX <= bounds.right + BOUNDARY_OFFSET && mouseEvent.clientY >= bounds.top - BOUNDARY_OFFSET && mouseEvent.clientY <= bounds.bottom + BOUNDARY_OFFSET) {
668
+ if (isMouseWithinBounds(mouseEvent, triggerRef.current)) {
714
669
  return;
715
670
  }
716
671
  setOpen(false, createChangeEventDetails(cancelOpen, mouseEvent));
@@ -785,7 +740,7 @@ const SelectValue = /*#__PURE__*/React.forwardRef(function SelectValue(component
785
740
  children = childrenProp(value);
786
741
  } else if (childrenProp != null) {
787
742
  children = childrenProp;
788
- } else if (!hasSelectedValue && placeholder != null && !hasNullLabel) {
743
+ } else if (shouldCheckNullItemLabel && !hasNullLabel) {
789
744
  children = placeholder;
790
745
  } else if (Array.isArray(value)) {
791
746
  children = resolveMultipleLabels(value, items, itemToStringLabel);
@@ -804,9 +759,6 @@ const SelectValue = /*#__PURE__*/React.forwardRef(function SelectValue(component
804
759
  });
805
760
  if (process.env.NODE_ENV !== "production") SelectValue.displayName = "SelectValue";
806
761
 
807
- const SelectPortalContext = /*#__PURE__*/React.createContext(undefined);
808
- if (process.env.NODE_ENV !== "production") SelectPortalContext.displayName = "SelectPortalContext";
809
-
810
762
  const SelectPortal = /*#__PURE__*/React.forwardRef(function SelectPortal(portalProps, forwardedRef) {
811
763
  const {
812
764
  store
@@ -817,12 +769,9 @@ const SelectPortal = /*#__PURE__*/React.forwardRef(function SelectPortal(portalP
817
769
  if (!shouldRender) {
818
770
  return null;
819
771
  }
820
- return /*#__PURE__*/jsx(SelectPortalContext.Provider, {
821
- value: true,
822
- children: /*#__PURE__*/jsx(FloatingPortal, {
823
- ref: forwardedRef,
824
- ...portalProps
825
- })
772
+ return /*#__PURE__*/jsx(FloatingPortal, {
773
+ ref: forwardedRef,
774
+ ...portalProps
826
775
  });
827
776
  });
828
777
  if (process.env.NODE_ENV !== "production") SelectPortal.displayName = "SelectPortal";
@@ -862,17 +811,19 @@ const FIXED = {
862
811
  const SelectPositioner = /*#__PURE__*/React.forwardRef(function SelectPositioner(componentProps, forwardedRef) {
863
812
  const {
864
813
  anchor,
865
- positionMethod = 'absolute',
866
814
  className,
867
815
  render,
868
- side = 'bottom',
869
- align = 'center',
870
- sideOffset = 0,
871
- alignOffset = 0,
816
+ // `useAnchorPositioning` applies the same defaults to the undefined values; the names
817
+ // remain destructured to exclude the props from `elementProps`.
818
+ positionMethod,
819
+ side,
820
+ align,
821
+ sideOffset,
822
+ alignOffset,
872
823
  collisionBoundary = 'clipping-ancestors',
873
824
  collisionPadding,
874
- arrowPadding = 5,
875
- sticky = false,
825
+ arrowPadding,
826
+ sticky,
876
827
  disableAnchorTracking,
877
828
  alignItemWithTrigger = true,
878
829
  collisionAvoidance = DROPDOWN_COLLISION_AVOIDANCE,
@@ -888,9 +839,9 @@ const SelectPositioner = /*#__PURE__*/React.forwardRef(function SelectPositioner
888
839
  valuesRef,
889
840
  initialValueRef,
890
841
  popupRef,
891
- setValue
842
+ setValue,
843
+ floatingContext: floatingRootContext
892
844
  } = useSelectRootContext();
893
- const floatingRootContext = useSelectFloatingContext();
894
845
  const open = useStore(store, selectors.open);
895
846
  const mounted = useStore(store, selectors.mounted);
896
847
  const modal = useStore(store, selectors.modal);
@@ -907,16 +858,6 @@ const SelectPositioner = /*#__PURE__*/React.forwardRef(function SelectPositioner
907
858
  if (!mounted && controlledAlignItemWithTrigger !== alignItemWithTrigger) {
908
859
  setControlledAlignItemWithTrigger(alignItemWithTrigger);
909
860
  }
910
- useIsoLayoutEffect(() => {
911
- if (!mounted) {
912
- if (selectors.scrollUpArrowVisible(store.state)) {
913
- store.set('scrollUpArrowVisible', false);
914
- }
915
- if (selectors.scrollDownArrowVisible(store.state)) {
916
- store.set('scrollDownArrowVisible', false);
917
- }
918
- }
919
- }, [store, mounted]);
920
861
  React.useImperativeHandle(alignItemWithTriggerActiveRef, () => alignItemWithTriggerActive);
921
862
  useAnchoredPopupScrollLock((alignItemWithTriggerActive || modal) && open, openMethod === 'touch', positionerElement, triggerElement);
922
863
  const positioning = useAnchorPositioning({
@@ -947,9 +888,7 @@ const SelectPositioner = /*#__PURE__*/React.forwardRef(function SelectPositioner
947
888
  useIsoLayoutEffect(() => {
948
889
  store.set('popupSide', positioning.side);
949
890
  }, [store, positioning.side]);
950
- const setPositionerElement = useStableCallback(element => {
951
- store.set('positionerElement', element);
952
- });
891
+ const setPositionerElement = store.useStateSetter('positionerElement');
953
892
  const element = usePositioner(componentProps, state, {
954
893
  styles: positionerStyles,
955
894
  transitionStatus,
@@ -960,9 +899,6 @@ const SelectPositioner = /*#__PURE__*/React.forwardRef(function SelectPositioner
960
899
  });
961
900
  const prevMapSizeRef = React.useRef(0);
962
901
  const onMapChange = useStableCallback(map => {
963
- if (map.size === 0 && prevMapSizeRef.current === 0) {
964
- return;
965
- }
966
902
  if (valuesRef.current.length === 0) {
967
903
  return;
968
904
  }
@@ -986,9 +922,8 @@ const SelectPositioner = /*#__PURE__*/React.forwardRef(function SelectPositioner
986
922
  }
987
923
  }
988
924
  if (prevSize !== 0 && store.state.multiple && Array.isArray(value)) {
989
- const hasVisibleItem = selectedItemValue => findItemIndex(valuesRef.current, selectedItemValue, isItemEqualToValue) !== -1;
990
- const nextValue = value.filter(selectedItemValue => hasVisibleItem(selectedItemValue));
991
- if (nextValue.length !== value.length || nextValue.some(selectedItemValue => !selectedValueIncludes(value, selectedItemValue, isItemEqualToValue))) {
925
+ const nextValue = value.filter(selectedItemValue => findItemIndex(valuesRef.current, selectedItemValue, isItemEqualToValue) !== -1);
926
+ if (nextValue.length !== value.length) {
992
927
  setValue(nextValue, eventDetails);
993
928
  if (nextValue.length === 0) {
994
929
  store.set('selectedIndex', null);
@@ -1062,7 +997,8 @@ const SelectPopup = /*#__PURE__*/React.forwardRef(function SelectPopup(component
1062
997
  handleScrollArrowVisibility,
1063
998
  scrollHandlerRef,
1064
999
  listRef,
1065
- highlightItemOnHover
1000
+ highlightItemOnHover,
1001
+ floatingContext: floatingRootContext
1066
1002
  } = useSelectRootContext();
1067
1003
  const {
1068
1004
  side,
@@ -1072,7 +1008,6 @@ const SelectPopup = /*#__PURE__*/React.forwardRef(function SelectPopup(component
1072
1008
  setControlledAlignItemWithTrigger
1073
1009
  } = useSelectPositionerContext();
1074
1010
  const insideToolbar = useToolbarRootContext(true) != null;
1075
- const floatingRootContext = useSelectFloatingContext();
1076
1011
  const direction = useDirection();
1077
1012
  const {
1078
1013
  nonce,
@@ -1095,14 +1030,10 @@ const SelectPopup = /*#__PURE__*/React.forwardRef(function SelectPopup(component
1095
1030
  if (!positionerElement || !popupRef.current || !initialPlacedRef.current) {
1096
1031
  return;
1097
1032
  }
1098
- if (reachedMaxHeightRef.current || !alignItemWithTriggerActive) {
1099
- handleScrollArrowVisibility();
1100
- return;
1101
- }
1102
1033
  const isTopPositioned = positionerElement.style.top === '0px';
1103
1034
  const isBottomPositioned = positionerElement.style.bottom === '0px';
1104
- if (!isTopPositioned && !isBottomPositioned) {
1105
- handleScrollArrowVisibility();
1035
+ if (reachedMaxHeightRef.current || !alignItemWithTriggerActive || !isTopPositioned && !isBottomPositioned) {
1036
+ handleScrollArrowVisibility(scroller);
1106
1037
  return;
1107
1038
  }
1108
1039
  const scale = getScale(positionerElement);
@@ -1116,63 +1047,50 @@ const SelectPopup = /*#__PURE__*/React.forwardRef(function SelectPopup(component
1116
1047
  const maxAvailableHeight = Math.min(doc.documentElement.clientHeight - marginTop - marginBottom, maxPopupHeight);
1117
1048
  const scrollTop = scroller.scrollTop;
1118
1049
  const maxScrollTop = getMaxScrollTop(scroller);
1119
- let nextPositionerHeight = 0;
1050
+
1051
+ // `Infinity` requests a scroll to the recomputed maximum offset.
1120
1052
  let nextScrollTop = null;
1121
- let setReachedMax = false;
1122
- let scrollToMax = false;
1123
1053
  const setHeight = height => {
1124
1054
  positionerElement.style.height = `${height}px`;
1125
1055
  };
1126
- const handleSmallDiff = (diff, targetScrollTop) => {
1056
+ const diff = isTopPositioned ? maxScrollTop - scrollTop : scrollTop;
1057
+ const nextHeight = Math.min(currentHeight + diff, maxAvailableHeight);
1058
+ if (diff <= SCROLL_EDGE_TOLERANCE_PX) {
1127
1059
  const heightDelta = clamp(diff, 0, maxAvailableHeight - currentHeight);
1128
1060
  if (heightDelta > 0) {
1129
1061
  // Consume the remaining scroll in height.
1130
1062
  setHeight(currentHeight + heightDelta);
1131
1063
  }
1132
- scroller.scrollTop = targetScrollTop;
1064
+ scroller.scrollTop = isTopPositioned ? maxScrollTop : 0;
1133
1065
  if (maxAvailableHeight - (currentHeight + heightDelta) <= SCROLL_EDGE_TOLERANCE_PX) {
1134
1066
  reachedMaxHeightRef.current = true;
1135
1067
  }
1136
- handleScrollArrowVisibility();
1137
- };
1138
- const diff = isTopPositioned ? maxScrollTop - scrollTop : scrollTop;
1139
- const nextHeight = Math.min(currentHeight + diff, maxAvailableHeight);
1140
- nextPositionerHeight = nextHeight;
1141
- if (diff <= SCROLL_EDGE_TOLERANCE_PX) {
1142
- handleSmallDiff(diff, isTopPositioned ? maxScrollTop : 0);
1068
+ handleScrollArrowVisibility(scroller);
1143
1069
  return;
1144
1070
  }
1145
1071
  if (maxAvailableHeight - nextHeight > SCROLL_EDGE_TOLERANCE_PX) {
1146
- if (isTopPositioned) {
1147
- scrollToMax = true;
1148
- } else {
1149
- nextScrollTop = 0;
1150
- }
1151
- } else {
1152
- setReachedMax = true;
1153
- if (isBottomPositioned && scrollTop < maxScrollTop) {
1154
- const overshoot = currentHeight + diff - maxAvailableHeight;
1155
- nextScrollTop = scrollTop - (diff - overshoot);
1156
- }
1072
+ nextScrollTop = isTopPositioned ? Infinity : 0;
1073
+ } else if (isBottomPositioned && scrollTop < maxScrollTop) {
1074
+ const overshoot = currentHeight + diff - maxAvailableHeight;
1075
+ nextScrollTop = scrollTop - (diff - overshoot);
1157
1076
  }
1158
- nextPositionerHeight = Math.ceil(nextPositionerHeight);
1077
+ const nextPositionerHeight = Math.ceil(nextHeight);
1159
1078
  if (nextPositionerHeight !== 0) {
1160
1079
  setHeight(nextPositionerHeight);
1161
1080
  }
1162
- if (scrollToMax || nextScrollTop != null) {
1081
+ if (nextScrollTop != null) {
1163
1082
  // Recompute bounds after resizing (clientHeight likely changed).
1164
- const nextMaxScrollTop = getMaxScrollTop(scroller);
1165
- const target = scrollToMax ? nextMaxScrollTop : clamp(nextScrollTop, 0, nextMaxScrollTop);
1083
+ const target = clamp(nextScrollTop, 0, getMaxScrollTop(scroller));
1166
1084
 
1167
1085
  // Avoid adjustments that re-trigger scroll events forever.
1168
1086
  if (Math.abs(scroller.scrollTop - target) > SCROLL_EDGE_TOLERANCE_PX) {
1169
1087
  scroller.scrollTop = target;
1170
1088
  }
1171
1089
  }
1172
- if (setReachedMax || nextPositionerHeight >= maxAvailableHeight - SCROLL_EDGE_TOLERANCE_PX) {
1090
+ if (nextPositionerHeight >= maxAvailableHeight - SCROLL_EDGE_TOLERANCE_PX) {
1173
1091
  reachedMaxHeightRef.current = true;
1174
1092
  }
1175
- handleScrollArrowVisibility();
1093
+ handleScrollArrowVisibility(scroller);
1176
1094
  });
1177
1095
  React.useImperativeHandle(scrollHandlerRef, () => handleScroll, [handleScroll]);
1178
1096
  useOpenChangeComplete({
@@ -1223,17 +1141,18 @@ const SelectPopup = /*#__PURE__*/React.forwardRef(function SelectPopup(component
1223
1141
  if (!open || !triggerElement || !positionerElement || !popupElement || alignItemWithTriggerActive && !isPositioned || store.state.transitionStatus === 'ending') {
1224
1142
  return;
1225
1143
  }
1144
+ initialPlacedRef.current = true;
1145
+ popupElement.style.removeProperty('--transform-origin');
1226
1146
  if (!alignItemWithTriggerActive) {
1227
- initialPlacedRef.current = true;
1228
- scrollArrowFrame.request(handleScrollArrowVisibility);
1229
- popupElement.style.removeProperty('--transform-origin');
1147
+ // The wrapper supplies the scroller: the list owns scrolling once it has mounted, and
1148
+ // this effect re-runs (cancelling the stale frame) when that happens.
1149
+ scrollArrowFrame.request(() => handleScrollArrowVisibility(listElement || popupElement));
1230
1150
  return;
1231
1151
  }
1232
1152
 
1233
1153
  // Ensure we remove any transforms that can affect the location of the popup
1234
1154
  // and therefore the calculations.
1235
1155
  const restoreTransformStyles = unsetTransformStyles(popupElement);
1236
- popupElement.style.removeProperty('--transform-origin');
1237
1156
  try {
1238
1157
  let textElement = selectedItemTextRef.current;
1239
1158
  if (!textElement?.isConnected) {
@@ -1302,7 +1221,6 @@ const SelectPopup = /*#__PURE__*/React.forwardRef(function SelectPopup(component
1302
1221
  // Safari doesn't position the popup correctly when pinch-zoomed.
1303
1222
  const isPinchZoomed = (win.visualViewport?.scale ?? 1) !== 1 && webkit;
1304
1223
  if (fallbackToAlignPopupToTrigger || isPinchZoomed) {
1305
- initialPlacedRef.current = true;
1306
1224
  clearStyles(positionerElement, originalPositionerStylesRef.current);
1307
1225
  setControlledAlignItemWithTrigger(false);
1308
1226
  return;
@@ -1321,18 +1239,16 @@ const SelectPopup = /*#__PURE__*/React.forwardRef(function SelectPopup(component
1321
1239
  const popupTop = positionerRect.top;
1322
1240
  const popupHeight = positionerRect.height;
1323
1241
  const textCenterY = textRect.top + textRect.height / 2;
1324
- const transformOriginY = popupHeight > 0 ? (textCenterY - popupTop) / popupHeight * 100 : 50;
1325
- const clampedY = clamp(transformOriginY, 0, 100);
1242
+ const clampedY = clamp(popupHeight > 0 ? (textCenterY - popupTop) / popupHeight * 100 : 50, 0, 100);
1326
1243
  popupElement.style.setProperty('--transform-origin', `50% ${clampedY}%`);
1327
1244
  }
1328
1245
  if (initialHeight === viewportHeight || height >= maxPopupHeight) {
1329
1246
  reachedMaxHeightRef.current = true;
1330
1247
  }
1331
- handleScrollArrowVisibility();
1248
+ handleScrollArrowVisibility(scroller);
1332
1249
  if (highlightItemOnHover && store.state.selectedIndex === null && store.state.activeIndex === null && listRef.current[0] != null) {
1333
1250
  store.set('activeIndex', 0);
1334
1251
  }
1335
- initialPlacedRef.current = true;
1336
1252
  } finally {
1337
1253
  restoreTransformStyles();
1338
1254
  }
@@ -1371,15 +1287,14 @@ const SelectPopup = /*#__PURE__*/React.forwardRef(function SelectPopup(component
1371
1287
  style: listElement ? {
1372
1288
  height: '100%'
1373
1289
  } : LIST_FUNCTIONAL_STYLES
1374
- })
1290
+ }),
1291
+ className: !listElement && alignItemWithTriggerActive ? styleDisableScrollbar.className : undefined
1375
1292
  };
1376
1293
  const element = useRenderElement('div', componentProps, {
1377
1294
  ref: [forwardedRef, popupRef],
1378
1295
  state,
1379
1296
  stateAttributesMapping,
1380
- props: [popupProps, defaultProps, getDisabledMountTransitionStyles(transitionStatus), {
1381
- className: !listElement && alignItemWithTriggerActive ? styleDisableScrollbar.className : undefined
1382
- }, elementProps]
1297
+ props: [popupProps, defaultProps, getDisabledMountTransitionStyles(transitionStatus), elementProps]
1383
1298
  });
1384
1299
  return /*#__PURE__*/jsxs(React.Fragment, {
1385
1300
  children: [!disableStyleElements && styleDisableScrollbar.getElement(nonce), /*#__PURE__*/jsx(FloatingFocusManager, {
@@ -1395,7 +1310,7 @@ const SelectPopup = /*#__PURE__*/React.forwardRef(function SelectPopup(component
1395
1310
  });
1396
1311
  if (process.env.NODE_ENV !== "production") SelectPopup.displayName = "SelectPopup";
1397
1312
  function getMaxPopupHeight(popupStyles) {
1398
- const maxHeightStyle = popupStyles.maxHeight || '';
1313
+ const maxHeightStyle = popupStyles.maxHeight;
1399
1314
  return maxHeightStyle.endsWith('px') ? parseFloat(maxHeightStyle) || Infinity : Infinity;
1400
1315
  }
1401
1316
  function getMaxScrollTop(scroller) {
@@ -1453,14 +1368,14 @@ const SelectList = /*#__PURE__*/React.forwardRef(function SelectList(componentPr
1453
1368
  } = componentProps;
1454
1369
  const {
1455
1370
  store,
1456
- scrollHandlerRef
1371
+ scrollHandlerRef,
1372
+ multiple
1457
1373
  } = useSelectRootContext();
1458
1374
  const {
1459
1375
  alignItemWithTriggerActive
1460
1376
  } = useSelectPositionerContext();
1461
1377
  const hasScrollArrows = useStore(store, selectors.hasScrollArrows);
1462
1378
  const openMethod = useStore(store, selectors.openMethod);
1463
- const multiple = useStore(store, selectors.multiple);
1464
1379
  const id = useStore(store, selectors.id);
1465
1380
  const defaultProps = {
1466
1381
  id: `${id}-list`,
@@ -1474,9 +1389,7 @@ const SelectList = /*#__PURE__*/React.forwardRef(function SelectList(componentPr
1474
1389
  }),
1475
1390
  className: hasScrollArrows && openMethod !== 'touch' ? styleDisableScrollbar.className : undefined
1476
1391
  };
1477
- const setListElement = useStableCallback(element => {
1478
- store.set('listElement', element);
1479
- });
1392
+ const setListElement = store.useStateSetter('listElement');
1480
1393
  return useRenderElement('div', componentProps, {
1481
1394
  ref: [forwardedRef, setListElement],
1482
1395
  props: [defaultProps, elementProps]
@@ -1501,15 +1414,15 @@ const SelectItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(functio
1501
1414
  style,
1502
1415
  value: itemValue = null,
1503
1416
  label,
1504
- disabled = false,
1417
+ disabled: disabledProp = false,
1505
1418
  nativeButton = false,
1506
1419
  ...elementProps
1507
1420
  } = componentProps;
1508
1421
  const textRef = React.useRef(null);
1509
1422
  const listItem = useCompositeListItem({
1423
+ guess: true,
1510
1424
  label,
1511
- textRef,
1512
- indexGuessBehavior: IndexGuessBehavior.GuessFromOrder
1425
+ textRef
1513
1426
  });
1514
1427
  const {
1515
1428
  store,
@@ -1524,28 +1437,22 @@ const SelectItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(functio
1524
1437
  disabled: selectDisabled,
1525
1438
  readOnly
1526
1439
  } = useSelectRootContext();
1440
+ const disabled = selectDisabled || disabledProp;
1527
1441
  const highlighted = useStore(store, selectors.isActive, listItem.index);
1528
1442
  const open = useStore(store, selectors.open);
1529
1443
  const selected = useStore(store, selectors.isSelected, itemValue);
1530
1444
  const selectedByFocus = useStore(store, selectors.isSelectedByFocus, listItem.index);
1531
1445
  const isItemEqualToValue = useStore(store, selectors.isItemEqualToValue);
1532
1446
  const index = listItem.index;
1533
- const hasRegistered = index !== -1;
1534
1447
  const itemRef = React.useRef(null);
1535
1448
  useIsoLayoutEffect(() => {
1536
- if (!hasRegistered) {
1537
- return undefined;
1538
- }
1539
1449
  const values = valuesRef.current;
1540
1450
  values[index] = itemValue;
1541
1451
  return () => {
1542
1452
  delete values[index];
1543
1453
  };
1544
- }, [hasRegistered, index, itemValue, valuesRef]);
1454
+ }, [index, itemValue, valuesRef]);
1545
1455
  useIsoLayoutEffect(() => {
1546
- if (!hasRegistered) {
1547
- return;
1548
- }
1549
1456
  const selectedValue = store.state.value;
1550
1457
  let selectedCandidate = selectedValue;
1551
1458
  if (multiple && Array.isArray(selectedValue)) {
@@ -1561,8 +1468,7 @@ const SelectItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(functio
1561
1468
  selectedItemTextRef.current = textRef.current;
1562
1469
  }
1563
1470
  }
1564
- }, [hasRegistered, index, multiple, isItemEqualToValue, store, itemValue, selectedItemTextRef]);
1565
- const lastKeyRef = React.useRef(null);
1471
+ }, [index, multiple, isItemEqualToValue, store, itemValue, selectedItemTextRef]);
1566
1472
  const pointerTypeRef = React.useRef('mouse');
1567
1473
  const allowMouseSelectionRef = React.useRef(false);
1568
1474
  const {
@@ -1603,14 +1509,15 @@ const SelectItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(functio
1603
1509
  'aria-selected': selected,
1604
1510
  tabIndex: open && highlighted ? 0 : -1,
1605
1511
  onKeyDown(event) {
1606
- lastKeyRef.current = event.key;
1607
1512
  store.set('activeIndex', index);
1608
1513
  if (event.key === ' ' && typingRef.current) {
1514
+ // `useButton` skips Space activation for `role="option"` items when the keydown
1515
+ // is `defaultPrevented`, keeping typeahead spaces from committing a selection.
1609
1516
  event.preventDefault();
1610
1517
  }
1611
1518
  },
1612
1519
  onClick(event) {
1613
- const isMouseClick = event.type === 'click' && pointerTypeRef.current !== 'touch';
1520
+ const isMouseClick = pointerTypeRef.current !== 'touch';
1614
1521
  const clickPointerType = event.nativeEvent.pointerType;
1615
1522
  const isVirtualMouseClick = isMouseClick && isVirtualClick(event.nativeEvent) && (
1616
1523
  // Generic no-pointer `detail === 0` clicks stay tied to highlight state. Virtual
@@ -1622,15 +1529,9 @@ const SelectItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(functio
1622
1529
  // assistive technology activation.
1623
1530
  const isInvalidMouseClick = isMouseClick && !isVirtualMouseClick && !allowMouseSelectionRef.current;
1624
1531
  allowMouseSelectionRef.current = false;
1625
-
1626
- // Prevent double commit on {Enter}
1627
- if (event.type === 'keydown' && lastKeyRef.current === null) {
1628
- return;
1629
- }
1630
- if (disabled || event.type === 'keydown' && lastKeyRef.current === ' ' && typingRef.current || isInvalidMouseClick) {
1532
+ if (disabled || isInvalidMouseClick) {
1631
1533
  return;
1632
1534
  }
1633
- lastKeyRef.current = null;
1634
1535
  commitSelection(event.nativeEvent);
1635
1536
  },
1636
1537
  onPointerEnter(event) {
@@ -1679,9 +1580,8 @@ const SelectItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(functio
1679
1580
  selected,
1680
1581
  index,
1681
1582
  textRef,
1682
- selectedByFocus,
1683
- hasRegistered
1684
- }), [selected, index, textRef, selectedByFocus, hasRegistered]);
1583
+ selectedByFocus
1584
+ }), [selected, index, textRef, selectedByFocus]);
1685
1585
  return /*#__PURE__*/jsx(SelectItemContext.Provider, {
1686
1586
  value: contextValue,
1687
1587
  children: element
@@ -1690,11 +1590,10 @@ const SelectItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(functio
1690
1590
  if (process.env.NODE_ENV !== "production") SelectItem.displayName = "SelectItem";
1691
1591
 
1692
1592
  const SelectItemIndicator = /*#__PURE__*/React.forwardRef(function SelectItemIndicator(componentProps, forwardedRef) {
1693
- const keepMounted = componentProps.keepMounted ?? false;
1694
1593
  const {
1695
1594
  selected
1696
1595
  } = useSelectItemContext();
1697
- const shouldRender = keepMounted || selected;
1596
+ const shouldRender = componentProps.keepMounted || selected;
1698
1597
  if (!shouldRender) {
1699
1598
  return null;
1700
1599
  }
@@ -1760,8 +1659,7 @@ const SelectItemText = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(fun
1760
1659
  const {
1761
1660
  index,
1762
1661
  textRef,
1763
- selectedByFocus,
1764
- hasRegistered
1662
+ selectedByFocus
1765
1663
  } = useSelectItemContext();
1766
1664
  const {
1767
1665
  firstItemTextRef,
@@ -1777,13 +1675,13 @@ const SelectItemText = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(fun
1777
1675
  if (!node) {
1778
1676
  return;
1779
1677
  }
1780
- if (hasRegistered && index === 0) {
1678
+ if (index === 0) {
1781
1679
  firstItemTextRef.current = node;
1782
1680
  }
1783
- if (hasRegistered && selectedByFocus) {
1681
+ if (selectedByFocus) {
1784
1682
  selectedItemTextRef.current = node;
1785
1683
  }
1786
- }, [firstItemTextRef, selectedItemTextRef, index, selectedByFocus, hasRegistered]);
1684
+ }, [firstItemTextRef, selectedItemTextRef, index, selectedByFocus]);
1787
1685
  const element = useRenderElement('div', componentProps, {
1788
1686
  ref: [localRef, forwardedRef, textRef],
1789
1687
  props: elementProps
@@ -1848,6 +1746,9 @@ const SelectGroupLabel = /*#__PURE__*/React.forwardRef(function SelectGroupLabel
1848
1746
  const id = useBaseUiId(idProp);
1849
1747
  useIsoLayoutEffect(() => {
1850
1748
  setLabelId(id);
1749
+ return () => {
1750
+ setLabelId(currentId => currentId === id ? undefined : currentId);
1751
+ };
1851
1752
  }, [id, setLabelId]);
1852
1753
  const element = useRenderElement('div', componentProps, {
1853
1754
  ref: forwardedRef,