@jobber/components 9.6.1 → 9.6.2

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 (141) hide show
  1. package/dist/Autocomplete/index.cjs +2 -0
  2. package/dist/Autocomplete/index.mjs +2 -0
  3. package/dist/Autocomplete-cjs.js +5 -4
  4. package/dist/Autocomplete-es.js +2 -1
  5. package/dist/Card/index.cjs +2 -1
  6. package/dist/Card/index.mjs +2 -1
  7. package/dist/Chip/index.cjs +2 -0
  8. package/dist/Chip/index.mjs +2 -0
  9. package/dist/Chips/InternalChipDismissible/hooks/index.cjs +2 -0
  10. package/dist/Chips/InternalChipDismissible/hooks/index.mjs +2 -0
  11. package/dist/Chips/InternalChipDismissible/index.cjs +2 -0
  12. package/dist/Chips/InternalChipDismissible/index.mjs +2 -0
  13. package/dist/Chips/index.cjs +2 -0
  14. package/dist/Chips/index.mjs +2 -0
  15. package/dist/Combobox/index.cjs +4 -2
  16. package/dist/Combobox/index.mjs +4 -2
  17. package/dist/{ComboboxSeparator-cjs.js → ComboboxChipRemove-cjs.js} +455 -501
  18. package/dist/{ComboboxSeparator-es.js → ComboboxChipRemove-es.js} +456 -500
  19. package/dist/ComboboxPrimitive-cjs.js +30 -29
  20. package/dist/ComboboxPrimitive-es.js +3 -2
  21. package/dist/ConfirmationModal/index.cjs +2 -0
  22. package/dist/ConfirmationModal/index.mjs +2 -0
  23. package/dist/DataDump/index.cjs +2 -1
  24. package/dist/DataDump/index.mjs +2 -1
  25. package/dist/DataList/components/DataListActions/index.cjs +2 -0
  26. package/dist/DataList/components/DataListActions/index.mjs +2 -0
  27. package/dist/DataList/components/DataListBulkActions/index.cjs +2 -0
  28. package/dist/DataList/components/DataListBulkActions/index.mjs +2 -0
  29. package/dist/DataList/components/DataListFilters/components/DataListSort/index.cjs +2 -0
  30. package/dist/DataList/components/DataListFilters/components/DataListSort/index.mjs +2 -0
  31. package/dist/DataList/components/DataListFilters/index.cjs +2 -0
  32. package/dist/DataList/components/DataListFilters/index.mjs +2 -0
  33. package/dist/DataList/components/DataListHeader/index.cjs +2 -0
  34. package/dist/DataList/components/DataListHeader/index.mjs +2 -0
  35. package/dist/DataList/components/DataListItem/index.cjs +2 -0
  36. package/dist/DataList/components/DataListItem/index.mjs +2 -0
  37. package/dist/DataList/components/DataListItemActions/index.cjs +2 -0
  38. package/dist/DataList/components/DataListItemActions/index.mjs +2 -0
  39. package/dist/DataList/components/DataListItemActionsOverflow/index.cjs +2 -0
  40. package/dist/DataList/components/DataListItemActionsOverflow/index.mjs +2 -0
  41. package/dist/DataList/components/DataListItems/index.cjs +2 -0
  42. package/dist/DataList/components/DataListItems/index.mjs +2 -0
  43. package/dist/DataList/components/DataListLayout/index.cjs +2 -0
  44. package/dist/DataList/components/DataListLayout/index.mjs +2 -0
  45. package/dist/DataList/components/DataListLayoutActions/index.cjs +2 -0
  46. package/dist/DataList/components/DataListLayoutActions/index.mjs +2 -0
  47. package/dist/DataList/index.cjs +2 -0
  48. package/dist/DataList/index.mjs +2 -0
  49. package/dist/DatePicker/index.cjs +2 -0
  50. package/dist/DatePicker/index.mjs +2 -0
  51. package/dist/DatePicker-cjs.js +5 -4
  52. package/dist/DatePicker-es.js +2 -1
  53. package/dist/Dialog/index.cjs +2 -1
  54. package/dist/Dialog/index.mjs +2 -1
  55. package/dist/DrawerDescription-cjs.js +99 -268
  56. package/dist/DrawerDescription-es.js +103 -270
  57. package/dist/DrawerTitle-cjs.js +6 -2
  58. package/dist/DrawerTitle-es.js +7 -3
  59. package/dist/FieldDescription-cjs.js +181 -184
  60. package/dist/FieldDescription-es.js +184 -187
  61. package/dist/FilterPicker/components/FilterPickerActivator/index.cjs +2 -0
  62. package/dist/FilterPicker/components/FilterPickerActivator/index.mjs +2 -0
  63. package/dist/FilterPicker/components/FilterPickerContent/index.cjs +2 -0
  64. package/dist/FilterPicker/components/FilterPickerContent/index.mjs +2 -0
  65. package/dist/FilterPicker/components/FilterPickerTrigger/index.cjs +2 -0
  66. package/dist/FilterPicker/components/FilterPickerTrigger/index.mjs +2 -0
  67. package/dist/FilterPicker/index.cjs +2 -0
  68. package/dist/FilterPicker/index.mjs +2 -0
  69. package/dist/FilterPickerContent-cjs.js +5 -4
  70. package/dist/FilterPickerContent-es.js +2 -1
  71. package/dist/FormatFile/index.cjs +2 -0
  72. package/dist/FormatFile/index.mjs +2 -0
  73. package/dist/Gallery/index.cjs +2 -0
  74. package/dist/Gallery/index.mjs +2 -0
  75. package/dist/InputDate/index.cjs +2 -0
  76. package/dist/InputDate/index.mjs +2 -0
  77. package/dist/InputNumber/index.cjs +2 -1
  78. package/dist/InputNumber/index.mjs +2 -1
  79. package/dist/LightBox/index.cjs +2 -0
  80. package/dist/LightBox/index.mjs +2 -0
  81. package/dist/Menu/index.cjs +2 -1
  82. package/dist/Menu/index.mjs +2 -1
  83. package/dist/Menu-cjs.js +5 -4
  84. package/dist/Menu-es.js +2 -1
  85. package/dist/MenuSubmenuTrigger-cjs.js +164 -206
  86. package/dist/MenuSubmenuTrigger-es.js +152 -194
  87. package/dist/Modal/index.cjs +2 -0
  88. package/dist/Modal/index.mjs +3 -1
  89. package/dist/NumberFieldInput-cjs.js +308 -187
  90. package/dist/NumberFieldInput-es.js +308 -187
  91. package/dist/Page/index.cjs +2 -1
  92. package/dist/Page/index.mjs +2 -1
  93. package/dist/Popover/index.cjs +2 -0
  94. package/dist/Popover/index.mjs +2 -0
  95. package/dist/Popover-cjs.js +8 -7
  96. package/dist/Popover-es.js +2 -1
  97. package/dist/ScrollAreaViewport-cjs.js +1093 -747
  98. package/dist/ScrollAreaViewport-es.js +1081 -734
  99. package/dist/Select/index.cjs +2 -1
  100. package/dist/Select/index.mjs +2 -1
  101. package/dist/SelectGroupLabel-cjs.js +306 -207
  102. package/dist/SelectGroupLabel-es.js +302 -203
  103. package/dist/Tooltip/index.cjs +2 -0
  104. package/dist/Tooltip/index.mjs +2 -0
  105. package/dist/Tooltip-cjs.js +7 -6
  106. package/dist/Tooltip-es.js +2 -1
  107. package/dist/dialogReturnFocus-cjs.js +1 -1
  108. package/dist/dialogReturnFocus-es.js +2 -2
  109. package/dist/floating-ui.react-cjs.js +192 -2564
  110. package/dist/floating-ui.react-dom-cjs.js +2103 -0
  111. package/dist/floating-ui.react-dom-es.js +2072 -0
  112. package/dist/floating-ui.react-es.js +156 -2519
  113. package/dist/floating-ui.utils-cjs.js +342 -0
  114. package/dist/floating-ui.utils-es.js +300 -0
  115. package/dist/index.cjs +4 -3
  116. package/dist/index.mjs +4 -3
  117. package/dist/primitives/BottomSheet/index.cjs +1 -1
  118. package/dist/primitives/BottomSheet/index.mjs +1 -1
  119. package/dist/primitives/ComboboxPrimitive/index.cjs +4 -2
  120. package/dist/primitives/ComboboxPrimitive/index.mjs +4 -2
  121. package/dist/primitives/SelectPrimitive/index.cjs +2 -0
  122. package/dist/primitives/SelectPrimitive/index.mjs +2 -0
  123. package/dist/primitives/index.cjs +3 -2
  124. package/dist/primitives/index.mjs +3 -2
  125. package/dist/scrollEdges-cjs.js +11 -1
  126. package/dist/scrollEdges-es.js +11 -1
  127. package/dist/unstyledPrimitives/index.cjs +478 -720
  128. package/dist/unstyledPrimitives/index.mjs +421 -663
  129. package/dist/useCompositeListItem-cjs.js +361 -2108
  130. package/dist/useCompositeListItem-es.js +331 -2076
  131. package/dist/useLabel-cjs.js +3 -2
  132. package/dist/useLabel-es.js +3 -2
  133. package/dist/useRenderElement-cjs.js +7 -9
  134. package/dist/useRenderElement-es.js +7 -9
  135. package/dist/useScrollLock-cjs.js +344 -819
  136. package/dist/useScrollLock-es.js +296 -732
  137. package/dist/useScrollToActive-cjs.js +5 -4
  138. package/dist/useScrollToActive-es.js +2 -1
  139. package/package.json +3 -3
  140. package/dist/scrollable-cjs.js +0 -58
  141. package/dist/scrollable-es.js +0 -54
@@ -1,9 +1,11 @@
1
1
  import * as React from 'react';
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';
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';
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 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';
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';
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';
7
9
 
8
10
  const ToolbarRootContext = /*#__PURE__*/React.createContext(undefined);
9
11
  if (process.env.NODE_ENV !== "production") ToolbarRootContext.displayName = "ToolbarRootContext";
@@ -25,7 +27,7 @@ function usePreviousValue(value) {
25
27
  current: value,
26
28
  previous: null
27
29
  });
28
- if (!Object.is(value, state.current)) {
30
+ if (value !== state.current) {
29
31
  setState({
30
32
  current: value,
31
33
  previous: state.current
@@ -40,6 +42,8 @@ function isElementDisabled(element) {
40
42
 
41
43
  const SelectRootContext = /*#__PURE__*/React.createContext(null);
42
44
  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";
43
47
  function useSelectRootContext() {
44
48
  const context = React.useContext(SelectRootContext);
45
49
  if (context === null) {
@@ -47,16 +51,25 @@ function useSelectRootContext() {
47
51
  }
48
52
  return context;
49
53
  }
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
+ }
50
61
 
51
62
  const selectors = {
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 => {
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 => {
60
73
  const {
61
74
  value,
62
75
  multiple,
@@ -69,19 +82,19 @@ const selectors = {
69
82
  return value.length > 0;
70
83
  }
71
84
  return stringifyAsValue(value, itemToStringValue) !== '';
72
- },
73
- hasNullItemLabel: (state, enabled) => {
85
+ }),
86
+ hasNullItemLabel: createSelector((state, enabled) => {
74
87
  return enabled ? hasNullItemLabel(state.items) : false;
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) => {
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) => {
85
98
  const comparer = state.isItemEqualToValue;
86
99
  const storeValue = state.value;
87
100
  if (state.multiple) {
@@ -92,19 +105,19 @@ const selectors = {
92
105
  // value changes while the popup is open, where the index sync is deferred) must not
93
106
  // keep a previously selected item marked as selected.
94
107
  return compareItemEquality(itemValue, storeValue, comparer);
95
- },
96
- isSelectedByFocus: (state, index) => {
108
+ }),
109
+ isSelectedByFocus: createSelector((state, index) => {
97
110
  return state.selectedIndex === index;
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
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)
108
121
  };
109
122
 
110
123
  function SelectRoot(props) {
@@ -190,7 +203,7 @@ function SelectRoot(props) {
190
203
  openMethod,
191
204
  triggerProps: interactionTypeProps
192
205
  } = useOpenInteractionType(open);
193
- const store = useRefWithInit(() => new ReactStore({
206
+ const store = useRefWithInit(() => new Store({
194
207
  id: generatedId,
195
208
  labelId: undefined,
196
209
  modal,
@@ -222,7 +235,7 @@ function SelectRoot(props) {
222
235
  const triggerElement = useStore(store, selectors.triggerElement);
223
236
  const positionerElement = useStore(store, selectors.positionerElement);
224
237
  const previousOpenMethod = usePreviousValue(openMethod);
225
- const renderedOpenMethod = openMethod ?? previousOpenMethod;
238
+ const renderedOpenMethod = openMethod ?? previousOpenMethod ?? null;
226
239
  const serializedValue = React.useMemo(() => {
227
240
  // In multiple mode the shared input is nameless; per-value entries are submitted via
228
241
  // `hiddenInputs`. Its value is therefore irrelevant, and passing the whole array to
@@ -238,26 +251,38 @@ function SelectRoot(props) {
238
251
  }
239
252
  return stringifyAsValue(value, itemToStringValue);
240
253
  }, [multiple, value, itemToStringValue]);
241
- const controlRef = useValueAsRef(triggerElement);
254
+ const controlRef = useValueAsRef(store.state.triggerElement);
242
255
  const getStringifiedValueForForm = useStableCallback(() => fieldStringValue);
243
256
  useRegisterFieldControl(controlRef, generatedId, value, getStringifiedValueForForm, !disabled, nameProp);
244
257
  const initialValueRef = React.useRef(value);
245
258
  // Mirror the `hasSelectedValue` store selector so the Field's filled state agrees with the
246
259
  // trigger/value placeholder semantics (a value serializing to `''` counts as empty).
247
- const hasSelectedValue = multiple ? Array.isArray(value) && value.length > 0 : value != null && serializedValue !== '';
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]);
248
267
  useIsoLayoutEffect(() => {
249
268
  setFilled(hasSelectedValue);
250
269
  }, [hasSelectedValue, setFilled]);
251
270
  useIsoLayoutEffect(function syncSelectedIndex() {
252
- let target = value;
253
- let empty = false;
271
+ const registry = valuesRef.current;
272
+ let nextIndex;
254
273
  if (multiple) {
255
274
  const currentValue = Array.isArray(value) ? value : [];
256
- empty = currentValue.length === 0;
257
- target = currentValue[currentValue.length - 1];
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;
258
285
  }
259
- const index = empty ? -1 : findItemIndex(valuesRef.current, target, isItemEqualToValue);
260
- const nextIndex = index === -1 ? null : index;
261
286
  if (nextIndex === null) {
262
287
  selectedItemTextRef.current = null;
263
288
  }
@@ -265,7 +290,7 @@ function SelectRoot(props) {
265
290
  return;
266
291
  }
267
292
  store.set('selectedIndex', nextIndex);
268
- }, [multiple, open, value, isItemEqualToValue, store]);
293
+ }, [hasSelectedValue, multiple, open, value, valuesRef, isItemEqualToValue, store, selectedItemTextRef]);
269
294
  function isSelectedValueDirty(currentValue) {
270
295
  const initialValue = validityData.initialValue;
271
296
  if (Array.isArray(currentValue) && Array.isArray(initialValue)) {
@@ -296,9 +321,7 @@ function SelectRoot(props) {
296
321
  setMounted(false);
297
322
  store.update({
298
323
  activeIndex: null,
299
- openMethod: null,
300
- scrollUpArrowVisible: false,
301
- scrollDownArrowVisible: false
324
+ openMethod: null
302
325
  });
303
326
  onOpenChangeComplete?.(false);
304
327
  });
@@ -322,13 +345,21 @@ function SelectRoot(props) {
322
345
  }
323
346
  setValueUnwrapped(nextValue);
324
347
  });
325
- const handleScrollArrowVisibility = useStableCallback(scroller => {
348
+ const handleScrollArrowVisibility = useStableCallback(() => {
349
+ const scroller = store.state.listElement || popupRef.current;
350
+ if (!scroller) {
351
+ return;
352
+ }
326
353
  const maxScrollTop = getMaxScrollOffset(scroller.scrollHeight, scroller.clientHeight);
327
354
  const scrollTop = normalizeScrollOffset(scroller.scrollTop, maxScrollTop);
328
355
  const shouldShowUp = scrollTop > 0;
329
356
  const shouldShowDown = scrollTop < maxScrollTop;
330
- store.set('scrollUpArrowVisible', shouldShowUp);
331
- store.set('scrollDownArrowVisible', shouldShowDown);
357
+ if (store.state.scrollUpArrowVisible !== shouldShowUp) {
358
+ store.set('scrollUpArrowVisible', shouldShowUp);
359
+ }
360
+ if (store.state.scrollDownArrowVisible !== shouldShowDown) {
361
+ store.set('scrollDownArrowVisible', shouldShowDown);
362
+ }
332
363
  });
333
364
  const floatingContext = useFloatingRootContext({
334
365
  open,
@@ -373,16 +404,20 @@ function SelectRoot(props) {
373
404
  if (open) {
374
405
  store.set('activeIndex', index);
375
406
  } else {
376
- setValue(valuesRef.current[index], createChangeEventDetails(none));
407
+ setValue(valuesRef.current[index], createChangeEventDetails('none'));
377
408
  }
378
409
  },
379
410
  onTyping(typing) {
380
411
  typingRef.current = typing;
381
412
  }
382
413
  });
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]);
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]);
386
421
  const popupProps = React.useMemo(() => mergeProps(FOCUSABLE_POPUP_PROPS, typeahead.floating, listNavigation.floating, dismiss.floating), [typeahead.floating, listNavigation.floating, dismiss.floating]);
387
422
  const itemProps = listNavigation.item ?? EMPTY_OBJECT;
388
423
  useOnFirstRender(() => {
@@ -391,25 +426,27 @@ function SelectRoot(props) {
391
426
  triggerProps: mergedTriggerProps
392
427
  });
393
428
  });
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
- });
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]);
410
447
  const contextValue = React.useMemo(() => ({
411
448
  store,
412
- floatingContext,
449
+ name,
413
450
  required,
414
451
  disabled,
415
452
  readOnly,
@@ -434,8 +471,9 @@ function SelectRoot(props) {
434
471
  onOpenChangeComplete,
435
472
  alignItemWithTriggerActiveRef,
436
473
  initialValueRef
437
- }), [store, floatingContext, required, disabled, readOnly, multiple, highlightItemOnHover, setValue, setOpen, itemProps, validation, onOpenChangeComplete, handleScrollArrowVisibility]);
474
+ }), [store, name, required, disabled, readOnly, multiple, highlightItemOnHover, setValue, setOpen, itemProps, validation, onOpenChangeComplete, handleScrollArrowVisibility]);
438
475
  const ref = useMergedRefs(inputRef, validation.inputRef);
476
+ const hasMultipleSelection = multiple && Array.isArray(value) && value.length > 0;
439
477
  const hiddenInputName = multiple ? undefined : name;
440
478
  const hiddenInputs = React.useMemo(() => {
441
479
  if (!multiple || !Array.isArray(value) || !name) {
@@ -452,69 +490,73 @@ function SelectRoot(props) {
452
490
  }, currentSerializedValue);
453
491
  });
454
492
  }, [multiple, value, form, name, itemToStringValue, disabled]);
455
- return /*#__PURE__*/jsxs(SelectRootContext.Provider, {
493
+ return /*#__PURE__*/jsx(SelectRootContext.Provider, {
456
494
  value: contextValue,
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.
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) {
477
510
  return;
478
511
  }
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
+ }
479
519
 
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);
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
+ }
495
536
  }
537
+ store.set('forceMount', true);
538
+ queueMicrotask(handleChange);
496
539
  }
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]
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
+ })
515
556
  });
516
557
  }
517
558
 
559
+ const BOUNDARY_OFFSET = 2;
518
560
  const SELECTED_DELAY = 400;
519
561
  const stateAttributesMapping$2 = {
520
562
  ...pressableTriggerOpenStateMapping,
@@ -587,7 +629,9 @@ const SelectTrigger = /*#__PURE__*/React.forwardRef(function SelectTrigger(compo
587
629
  disabled,
588
630
  native: nativeButton
589
631
  });
590
- const setTriggerElement = store.useStateSetter('triggerElement');
632
+ const setTriggerElement = useStableCallback(element => {
633
+ store.set('triggerElement', element);
634
+ });
591
635
  const timeoutFocus = useTimeout();
592
636
  const timeoutMouseDown = useTimeout();
593
637
  const selectedDelayTimeout = useTimeout();
@@ -616,7 +660,7 @@ const SelectTrigger = /*#__PURE__*/React.forwardRef(function SelectTrigger(compo
616
660
  const mergedProps = mergeProps(triggerProps, {
617
661
  id,
618
662
  role: 'combobox',
619
- 'aria-expanded': open,
663
+ 'aria-expanded': open ? 'true' : 'false',
620
664
  'aria-haspopup': 'listbox',
621
665
  'aria-controls': open ? listElement?.id ?? getFloatingFocusElement(positionerElement)?.id : undefined,
622
666
  'aria-labelledby': ariaLabelledBy,
@@ -665,7 +709,8 @@ const SelectTrigger = /*#__PURE__*/React.forwardRef(function SelectTrigger(compo
665
709
  if (contains(triggerRef.current, mouseUpTarget) || contains(positionerRef.current, mouseUpTarget)) {
666
710
  return;
667
711
  }
668
- if (isMouseWithinBounds(mouseEvent, triggerRef.current)) {
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) {
669
714
  return;
670
715
  }
671
716
  setOpen(false, createChangeEventDetails(cancelOpen, mouseEvent));
@@ -740,7 +785,7 @@ const SelectValue = /*#__PURE__*/React.forwardRef(function SelectValue(component
740
785
  children = childrenProp(value);
741
786
  } else if (childrenProp != null) {
742
787
  children = childrenProp;
743
- } else if (shouldCheckNullItemLabel && !hasNullLabel) {
788
+ } else if (!hasSelectedValue && placeholder != null && !hasNullLabel) {
744
789
  children = placeholder;
745
790
  } else if (Array.isArray(value)) {
746
791
  children = resolveMultipleLabels(value, items, itemToStringLabel);
@@ -759,6 +804,9 @@ const SelectValue = /*#__PURE__*/React.forwardRef(function SelectValue(component
759
804
  });
760
805
  if (process.env.NODE_ENV !== "production") SelectValue.displayName = "SelectValue";
761
806
 
807
+ const SelectPortalContext = /*#__PURE__*/React.createContext(undefined);
808
+ if (process.env.NODE_ENV !== "production") SelectPortalContext.displayName = "SelectPortalContext";
809
+
762
810
  const SelectPortal = /*#__PURE__*/React.forwardRef(function SelectPortal(portalProps, forwardedRef) {
763
811
  const {
764
812
  store
@@ -769,9 +817,12 @@ const SelectPortal = /*#__PURE__*/React.forwardRef(function SelectPortal(portalP
769
817
  if (!shouldRender) {
770
818
  return null;
771
819
  }
772
- return /*#__PURE__*/jsx(FloatingPortal, {
773
- ref: forwardedRef,
774
- ...portalProps
820
+ return /*#__PURE__*/jsx(SelectPortalContext.Provider, {
821
+ value: true,
822
+ children: /*#__PURE__*/jsx(FloatingPortal, {
823
+ ref: forwardedRef,
824
+ ...portalProps
825
+ })
775
826
  });
776
827
  });
777
828
  if (process.env.NODE_ENV !== "production") SelectPortal.displayName = "SelectPortal";
@@ -811,19 +862,17 @@ const FIXED = {
811
862
  const SelectPositioner = /*#__PURE__*/React.forwardRef(function SelectPositioner(componentProps, forwardedRef) {
812
863
  const {
813
864
  anchor,
865
+ positionMethod = 'absolute',
814
866
  className,
815
867
  render,
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,
868
+ side = 'bottom',
869
+ align = 'center',
870
+ sideOffset = 0,
871
+ alignOffset = 0,
823
872
  collisionBoundary = 'clipping-ancestors',
824
873
  collisionPadding,
825
- arrowPadding,
826
- sticky,
874
+ arrowPadding = 5,
875
+ sticky = false,
827
876
  disableAnchorTracking,
828
877
  alignItemWithTrigger = true,
829
878
  collisionAvoidance = DROPDOWN_COLLISION_AVOIDANCE,
@@ -839,9 +888,9 @@ const SelectPositioner = /*#__PURE__*/React.forwardRef(function SelectPositioner
839
888
  valuesRef,
840
889
  initialValueRef,
841
890
  popupRef,
842
- setValue,
843
- floatingContext: floatingRootContext
891
+ setValue
844
892
  } = useSelectRootContext();
893
+ const floatingRootContext = useSelectFloatingContext();
845
894
  const open = useStore(store, selectors.open);
846
895
  const mounted = useStore(store, selectors.mounted);
847
896
  const modal = useStore(store, selectors.modal);
@@ -858,6 +907,16 @@ const SelectPositioner = /*#__PURE__*/React.forwardRef(function SelectPositioner
858
907
  if (!mounted && controlledAlignItemWithTrigger !== alignItemWithTrigger) {
859
908
  setControlledAlignItemWithTrigger(alignItemWithTrigger);
860
909
  }
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]);
861
920
  React.useImperativeHandle(alignItemWithTriggerActiveRef, () => alignItemWithTriggerActive);
862
921
  useAnchoredPopupScrollLock((alignItemWithTriggerActive || modal) && open, openMethod === 'touch', positionerElement, triggerElement);
863
922
  const positioning = useAnchorPositioning({
@@ -888,7 +947,9 @@ const SelectPositioner = /*#__PURE__*/React.forwardRef(function SelectPositioner
888
947
  useIsoLayoutEffect(() => {
889
948
  store.set('popupSide', positioning.side);
890
949
  }, [store, positioning.side]);
891
- const setPositionerElement = store.useStateSetter('positionerElement');
950
+ const setPositionerElement = useStableCallback(element => {
951
+ store.set('positionerElement', element);
952
+ });
892
953
  const element = usePositioner(componentProps, state, {
893
954
  styles: positionerStyles,
894
955
  transitionStatus,
@@ -899,6 +960,9 @@ const SelectPositioner = /*#__PURE__*/React.forwardRef(function SelectPositioner
899
960
  });
900
961
  const prevMapSizeRef = React.useRef(0);
901
962
  const onMapChange = useStableCallback(map => {
963
+ if (map.size === 0 && prevMapSizeRef.current === 0) {
964
+ return;
965
+ }
902
966
  if (valuesRef.current.length === 0) {
903
967
  return;
904
968
  }
@@ -922,8 +986,9 @@ const SelectPositioner = /*#__PURE__*/React.forwardRef(function SelectPositioner
922
986
  }
923
987
  }
924
988
  if (prevSize !== 0 && store.state.multiple && Array.isArray(value)) {
925
- const nextValue = value.filter(selectedItemValue => findItemIndex(valuesRef.current, selectedItemValue, isItemEqualToValue) !== -1);
926
- if (nextValue.length !== value.length) {
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))) {
927
992
  setValue(nextValue, eventDetails);
928
993
  if (nextValue.length === 0) {
929
994
  store.set('selectedIndex', null);
@@ -997,8 +1062,7 @@ const SelectPopup = /*#__PURE__*/React.forwardRef(function SelectPopup(component
997
1062
  handleScrollArrowVisibility,
998
1063
  scrollHandlerRef,
999
1064
  listRef,
1000
- highlightItemOnHover,
1001
- floatingContext: floatingRootContext
1065
+ highlightItemOnHover
1002
1066
  } = useSelectRootContext();
1003
1067
  const {
1004
1068
  side,
@@ -1008,6 +1072,7 @@ const SelectPopup = /*#__PURE__*/React.forwardRef(function SelectPopup(component
1008
1072
  setControlledAlignItemWithTrigger
1009
1073
  } = useSelectPositionerContext();
1010
1074
  const insideToolbar = useToolbarRootContext(true) != null;
1075
+ const floatingRootContext = useSelectFloatingContext();
1011
1076
  const direction = useDirection();
1012
1077
  const {
1013
1078
  nonce,
@@ -1030,10 +1095,14 @@ const SelectPopup = /*#__PURE__*/React.forwardRef(function SelectPopup(component
1030
1095
  if (!positionerElement || !popupRef.current || !initialPlacedRef.current) {
1031
1096
  return;
1032
1097
  }
1098
+ if (reachedMaxHeightRef.current || !alignItemWithTriggerActive) {
1099
+ handleScrollArrowVisibility();
1100
+ return;
1101
+ }
1033
1102
  const isTopPositioned = positionerElement.style.top === '0px';
1034
1103
  const isBottomPositioned = positionerElement.style.bottom === '0px';
1035
- if (reachedMaxHeightRef.current || !alignItemWithTriggerActive || !isTopPositioned && !isBottomPositioned) {
1036
- handleScrollArrowVisibility(scroller);
1104
+ if (!isTopPositioned && !isBottomPositioned) {
1105
+ handleScrollArrowVisibility();
1037
1106
  return;
1038
1107
  }
1039
1108
  const scale = getScale(positionerElement);
@@ -1047,50 +1116,63 @@ const SelectPopup = /*#__PURE__*/React.forwardRef(function SelectPopup(component
1047
1116
  const maxAvailableHeight = Math.min(doc.documentElement.clientHeight - marginTop - marginBottom, maxPopupHeight);
1048
1117
  const scrollTop = scroller.scrollTop;
1049
1118
  const maxScrollTop = getMaxScrollTop(scroller);
1050
-
1051
- // `Infinity` requests a scroll to the recomputed maximum offset.
1119
+ let nextPositionerHeight = 0;
1052
1120
  let nextScrollTop = null;
1121
+ let setReachedMax = false;
1122
+ let scrollToMax = false;
1053
1123
  const setHeight = height => {
1054
1124
  positionerElement.style.height = `${height}px`;
1055
1125
  };
1056
- const diff = isTopPositioned ? maxScrollTop - scrollTop : scrollTop;
1057
- const nextHeight = Math.min(currentHeight + diff, maxAvailableHeight);
1058
- if (diff <= SCROLL_EDGE_TOLERANCE_PX) {
1126
+ const handleSmallDiff = (diff, targetScrollTop) => {
1059
1127
  const heightDelta = clamp(diff, 0, maxAvailableHeight - currentHeight);
1060
1128
  if (heightDelta > 0) {
1061
1129
  // Consume the remaining scroll in height.
1062
1130
  setHeight(currentHeight + heightDelta);
1063
1131
  }
1064
- scroller.scrollTop = isTopPositioned ? maxScrollTop : 0;
1132
+ scroller.scrollTop = targetScrollTop;
1065
1133
  if (maxAvailableHeight - (currentHeight + heightDelta) <= SCROLL_EDGE_TOLERANCE_PX) {
1066
1134
  reachedMaxHeightRef.current = true;
1067
1135
  }
1068
- handleScrollArrowVisibility(scroller);
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);
1069
1143
  return;
1070
1144
  }
1071
1145
  if (maxAvailableHeight - nextHeight > SCROLL_EDGE_TOLERANCE_PX) {
1072
- nextScrollTop = isTopPositioned ? Infinity : 0;
1073
- } else if (isBottomPositioned && scrollTop < maxScrollTop) {
1074
- const overshoot = currentHeight + diff - maxAvailableHeight;
1075
- nextScrollTop = scrollTop - (diff - overshoot);
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
+ }
1076
1157
  }
1077
- const nextPositionerHeight = Math.ceil(nextHeight);
1158
+ nextPositionerHeight = Math.ceil(nextPositionerHeight);
1078
1159
  if (nextPositionerHeight !== 0) {
1079
1160
  setHeight(nextPositionerHeight);
1080
1161
  }
1081
- if (nextScrollTop != null) {
1162
+ if (scrollToMax || nextScrollTop != null) {
1082
1163
  // Recompute bounds after resizing (clientHeight likely changed).
1083
- const target = clamp(nextScrollTop, 0, getMaxScrollTop(scroller));
1164
+ const nextMaxScrollTop = getMaxScrollTop(scroller);
1165
+ const target = scrollToMax ? nextMaxScrollTop : clamp(nextScrollTop, 0, nextMaxScrollTop);
1084
1166
 
1085
1167
  // Avoid adjustments that re-trigger scroll events forever.
1086
1168
  if (Math.abs(scroller.scrollTop - target) > SCROLL_EDGE_TOLERANCE_PX) {
1087
1169
  scroller.scrollTop = target;
1088
1170
  }
1089
1171
  }
1090
- if (nextPositionerHeight >= maxAvailableHeight - SCROLL_EDGE_TOLERANCE_PX) {
1172
+ if (setReachedMax || nextPositionerHeight >= maxAvailableHeight - SCROLL_EDGE_TOLERANCE_PX) {
1091
1173
  reachedMaxHeightRef.current = true;
1092
1174
  }
1093
- handleScrollArrowVisibility(scroller);
1175
+ handleScrollArrowVisibility();
1094
1176
  });
1095
1177
  React.useImperativeHandle(scrollHandlerRef, () => handleScroll, [handleScroll]);
1096
1178
  useOpenChangeComplete({
@@ -1141,18 +1223,17 @@ const SelectPopup = /*#__PURE__*/React.forwardRef(function SelectPopup(component
1141
1223
  if (!open || !triggerElement || !positionerElement || !popupElement || alignItemWithTriggerActive && !isPositioned || store.state.transitionStatus === 'ending') {
1142
1224
  return;
1143
1225
  }
1144
- initialPlacedRef.current = true;
1145
- popupElement.style.removeProperty('--transform-origin');
1146
1226
  if (!alignItemWithTriggerActive) {
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));
1227
+ initialPlacedRef.current = true;
1228
+ scrollArrowFrame.request(handleScrollArrowVisibility);
1229
+ popupElement.style.removeProperty('--transform-origin');
1150
1230
  return;
1151
1231
  }
1152
1232
 
1153
1233
  // Ensure we remove any transforms that can affect the location of the popup
1154
1234
  // and therefore the calculations.
1155
1235
  const restoreTransformStyles = unsetTransformStyles(popupElement);
1236
+ popupElement.style.removeProperty('--transform-origin');
1156
1237
  try {
1157
1238
  let textElement = selectedItemTextRef.current;
1158
1239
  if (!textElement?.isConnected) {
@@ -1221,6 +1302,7 @@ const SelectPopup = /*#__PURE__*/React.forwardRef(function SelectPopup(component
1221
1302
  // Safari doesn't position the popup correctly when pinch-zoomed.
1222
1303
  const isPinchZoomed = (win.visualViewport?.scale ?? 1) !== 1 && webkit;
1223
1304
  if (fallbackToAlignPopupToTrigger || isPinchZoomed) {
1305
+ initialPlacedRef.current = true;
1224
1306
  clearStyles(positionerElement, originalPositionerStylesRef.current);
1225
1307
  setControlledAlignItemWithTrigger(false);
1226
1308
  return;
@@ -1239,16 +1321,18 @@ const SelectPopup = /*#__PURE__*/React.forwardRef(function SelectPopup(component
1239
1321
  const popupTop = positionerRect.top;
1240
1322
  const popupHeight = positionerRect.height;
1241
1323
  const textCenterY = textRect.top + textRect.height / 2;
1242
- const clampedY = clamp(popupHeight > 0 ? (textCenterY - popupTop) / popupHeight * 100 : 50, 0, 100);
1324
+ const transformOriginY = popupHeight > 0 ? (textCenterY - popupTop) / popupHeight * 100 : 50;
1325
+ const clampedY = clamp(transformOriginY, 0, 100);
1243
1326
  popupElement.style.setProperty('--transform-origin', `50% ${clampedY}%`);
1244
1327
  }
1245
1328
  if (initialHeight === viewportHeight || height >= maxPopupHeight) {
1246
1329
  reachedMaxHeightRef.current = true;
1247
1330
  }
1248
- handleScrollArrowVisibility(scroller);
1331
+ handleScrollArrowVisibility();
1249
1332
  if (highlightItemOnHover && store.state.selectedIndex === null && store.state.activeIndex === null && listRef.current[0] != null) {
1250
1333
  store.set('activeIndex', 0);
1251
1334
  }
1335
+ initialPlacedRef.current = true;
1252
1336
  } finally {
1253
1337
  restoreTransformStyles();
1254
1338
  }
@@ -1287,14 +1371,15 @@ const SelectPopup = /*#__PURE__*/React.forwardRef(function SelectPopup(component
1287
1371
  style: listElement ? {
1288
1372
  height: '100%'
1289
1373
  } : LIST_FUNCTIONAL_STYLES
1290
- }),
1291
- className: !listElement && alignItemWithTriggerActive ? styleDisableScrollbar.className : undefined
1374
+ })
1292
1375
  };
1293
1376
  const element = useRenderElement('div', componentProps, {
1294
1377
  ref: [forwardedRef, popupRef],
1295
1378
  state,
1296
1379
  stateAttributesMapping,
1297
- props: [popupProps, defaultProps, getDisabledMountTransitionStyles(transitionStatus), elementProps]
1380
+ props: [popupProps, defaultProps, getDisabledMountTransitionStyles(transitionStatus), {
1381
+ className: !listElement && alignItemWithTriggerActive ? styleDisableScrollbar.className : undefined
1382
+ }, elementProps]
1298
1383
  });
1299
1384
  return /*#__PURE__*/jsxs(React.Fragment, {
1300
1385
  children: [!disableStyleElements && styleDisableScrollbar.getElement(nonce), /*#__PURE__*/jsx(FloatingFocusManager, {
@@ -1310,7 +1395,7 @@ const SelectPopup = /*#__PURE__*/React.forwardRef(function SelectPopup(component
1310
1395
  });
1311
1396
  if (process.env.NODE_ENV !== "production") SelectPopup.displayName = "SelectPopup";
1312
1397
  function getMaxPopupHeight(popupStyles) {
1313
- const maxHeightStyle = popupStyles.maxHeight;
1398
+ const maxHeightStyle = popupStyles.maxHeight || '';
1314
1399
  return maxHeightStyle.endsWith('px') ? parseFloat(maxHeightStyle) || Infinity : Infinity;
1315
1400
  }
1316
1401
  function getMaxScrollTop(scroller) {
@@ -1368,14 +1453,14 @@ const SelectList = /*#__PURE__*/React.forwardRef(function SelectList(componentPr
1368
1453
  } = componentProps;
1369
1454
  const {
1370
1455
  store,
1371
- scrollHandlerRef,
1372
- multiple
1456
+ scrollHandlerRef
1373
1457
  } = useSelectRootContext();
1374
1458
  const {
1375
1459
  alignItemWithTriggerActive
1376
1460
  } = useSelectPositionerContext();
1377
1461
  const hasScrollArrows = useStore(store, selectors.hasScrollArrows);
1378
1462
  const openMethod = useStore(store, selectors.openMethod);
1463
+ const multiple = useStore(store, selectors.multiple);
1379
1464
  const id = useStore(store, selectors.id);
1380
1465
  const defaultProps = {
1381
1466
  id: `${id}-list`,
@@ -1389,7 +1474,9 @@ const SelectList = /*#__PURE__*/React.forwardRef(function SelectList(componentPr
1389
1474
  }),
1390
1475
  className: hasScrollArrows && openMethod !== 'touch' ? styleDisableScrollbar.className : undefined
1391
1476
  };
1392
- const setListElement = store.useStateSetter('listElement');
1477
+ const setListElement = useStableCallback(element => {
1478
+ store.set('listElement', element);
1479
+ });
1393
1480
  return useRenderElement('div', componentProps, {
1394
1481
  ref: [forwardedRef, setListElement],
1395
1482
  props: [defaultProps, elementProps]
@@ -1414,15 +1501,15 @@ const SelectItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(functio
1414
1501
  style,
1415
1502
  value: itemValue = null,
1416
1503
  label,
1417
- disabled: disabledProp = false,
1504
+ disabled = false,
1418
1505
  nativeButton = false,
1419
1506
  ...elementProps
1420
1507
  } = componentProps;
1421
1508
  const textRef = React.useRef(null);
1422
1509
  const listItem = useCompositeListItem({
1423
- guess: true,
1424
1510
  label,
1425
- textRef
1511
+ textRef,
1512
+ indexGuessBehavior: IndexGuessBehavior.GuessFromOrder
1426
1513
  });
1427
1514
  const {
1428
1515
  store,
@@ -1437,22 +1524,28 @@ const SelectItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(functio
1437
1524
  disabled: selectDisabled,
1438
1525
  readOnly
1439
1526
  } = useSelectRootContext();
1440
- const disabled = selectDisabled || disabledProp;
1441
1527
  const highlighted = useStore(store, selectors.isActive, listItem.index);
1442
1528
  const open = useStore(store, selectors.open);
1443
1529
  const selected = useStore(store, selectors.isSelected, itemValue);
1444
1530
  const selectedByFocus = useStore(store, selectors.isSelectedByFocus, listItem.index);
1445
1531
  const isItemEqualToValue = useStore(store, selectors.isItemEqualToValue);
1446
1532
  const index = listItem.index;
1533
+ const hasRegistered = index !== -1;
1447
1534
  const itemRef = React.useRef(null);
1448
1535
  useIsoLayoutEffect(() => {
1536
+ if (!hasRegistered) {
1537
+ return undefined;
1538
+ }
1449
1539
  const values = valuesRef.current;
1450
1540
  values[index] = itemValue;
1451
1541
  return () => {
1452
1542
  delete values[index];
1453
1543
  };
1454
- }, [index, itemValue, valuesRef]);
1544
+ }, [hasRegistered, index, itemValue, valuesRef]);
1455
1545
  useIsoLayoutEffect(() => {
1546
+ if (!hasRegistered) {
1547
+ return;
1548
+ }
1456
1549
  const selectedValue = store.state.value;
1457
1550
  let selectedCandidate = selectedValue;
1458
1551
  if (multiple && Array.isArray(selectedValue)) {
@@ -1468,7 +1561,8 @@ const SelectItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(functio
1468
1561
  selectedItemTextRef.current = textRef.current;
1469
1562
  }
1470
1563
  }
1471
- }, [index, multiple, isItemEqualToValue, store, itemValue, selectedItemTextRef]);
1564
+ }, [hasRegistered, index, multiple, isItemEqualToValue, store, itemValue, selectedItemTextRef]);
1565
+ const lastKeyRef = React.useRef(null);
1472
1566
  const pointerTypeRef = React.useRef('mouse');
1473
1567
  const allowMouseSelectionRef = React.useRef(false);
1474
1568
  const {
@@ -1509,15 +1603,14 @@ const SelectItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(functio
1509
1603
  'aria-selected': selected,
1510
1604
  tabIndex: open && highlighted ? 0 : -1,
1511
1605
  onKeyDown(event) {
1606
+ lastKeyRef.current = event.key;
1512
1607
  store.set('activeIndex', index);
1513
1608
  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.
1516
1609
  event.preventDefault();
1517
1610
  }
1518
1611
  },
1519
1612
  onClick(event) {
1520
- const isMouseClick = pointerTypeRef.current !== 'touch';
1613
+ const isMouseClick = event.type === 'click' && pointerTypeRef.current !== 'touch';
1521
1614
  const clickPointerType = event.nativeEvent.pointerType;
1522
1615
  const isVirtualMouseClick = isMouseClick && isVirtualClick(event.nativeEvent) && (
1523
1616
  // Generic no-pointer `detail === 0` clicks stay tied to highlight state. Virtual
@@ -1529,9 +1622,15 @@ const SelectItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(functio
1529
1622
  // assistive technology activation.
1530
1623
  const isInvalidMouseClick = isMouseClick && !isVirtualMouseClick && !allowMouseSelectionRef.current;
1531
1624
  allowMouseSelectionRef.current = false;
1532
- if (disabled || isInvalidMouseClick) {
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) {
1533
1631
  return;
1534
1632
  }
1633
+ lastKeyRef.current = null;
1535
1634
  commitSelection(event.nativeEvent);
1536
1635
  },
1537
1636
  onPointerEnter(event) {
@@ -1580,8 +1679,9 @@ const SelectItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(functio
1580
1679
  selected,
1581
1680
  index,
1582
1681
  textRef,
1583
- selectedByFocus
1584
- }), [selected, index, textRef, selectedByFocus]);
1682
+ selectedByFocus,
1683
+ hasRegistered
1684
+ }), [selected, index, textRef, selectedByFocus, hasRegistered]);
1585
1685
  return /*#__PURE__*/jsx(SelectItemContext.Provider, {
1586
1686
  value: contextValue,
1587
1687
  children: element
@@ -1590,10 +1690,11 @@ const SelectItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(functio
1590
1690
  if (process.env.NODE_ENV !== "production") SelectItem.displayName = "SelectItem";
1591
1691
 
1592
1692
  const SelectItemIndicator = /*#__PURE__*/React.forwardRef(function SelectItemIndicator(componentProps, forwardedRef) {
1693
+ const keepMounted = componentProps.keepMounted ?? false;
1593
1694
  const {
1594
1695
  selected
1595
1696
  } = useSelectItemContext();
1596
- const shouldRender = componentProps.keepMounted || selected;
1697
+ const shouldRender = keepMounted || selected;
1597
1698
  if (!shouldRender) {
1598
1699
  return null;
1599
1700
  }
@@ -1659,7 +1760,8 @@ const SelectItemText = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(fun
1659
1760
  const {
1660
1761
  index,
1661
1762
  textRef,
1662
- selectedByFocus
1763
+ selectedByFocus,
1764
+ hasRegistered
1663
1765
  } = useSelectItemContext();
1664
1766
  const {
1665
1767
  firstItemTextRef,
@@ -1675,13 +1777,13 @@ const SelectItemText = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(fun
1675
1777
  if (!node) {
1676
1778
  return;
1677
1779
  }
1678
- if (index === 0) {
1780
+ if (hasRegistered && index === 0) {
1679
1781
  firstItemTextRef.current = node;
1680
1782
  }
1681
- if (selectedByFocus) {
1783
+ if (hasRegistered && selectedByFocus) {
1682
1784
  selectedItemTextRef.current = node;
1683
1785
  }
1684
- }, [firstItemTextRef, selectedItemTextRef, index, selectedByFocus]);
1786
+ }, [firstItemTextRef, selectedItemTextRef, index, selectedByFocus, hasRegistered]);
1685
1787
  const element = useRenderElement('div', componentProps, {
1686
1788
  ref: [localRef, forwardedRef, textRef],
1687
1789
  props: elementProps
@@ -1746,9 +1848,6 @@ const SelectGroupLabel = /*#__PURE__*/React.forwardRef(function SelectGroupLabel
1746
1848
  const id = useBaseUiId(idProp);
1747
1849
  useIsoLayoutEffect(() => {
1748
1850
  setLabelId(id);
1749
- return () => {
1750
- setLabelId(currentId => currentId === id ? undefined : currentId);
1751
- };
1752
1851
  }, [id, setLabelId]);
1753
1852
  const element = useRenderElement('div', componentProps, {
1754
1853
  ref: forwardedRef,