@jobber/components 8.23.0 → 8.23.1

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 (134) hide show
  1. package/dist/Autocomplete/index.cjs +1 -1
  2. package/dist/Autocomplete/index.mjs +1 -1
  3. package/dist/Card/index.cjs +4 -4
  4. package/dist/Card/index.mjs +4 -4
  5. package/dist/Chip/index.cjs +1 -1
  6. package/dist/Chip/index.mjs +1 -1
  7. package/dist/Chips/InternalChipDismissible/hooks/index.cjs +1 -1
  8. package/dist/Chips/InternalChipDismissible/hooks/index.mjs +1 -1
  9. package/dist/Chips/InternalChipDismissible/index.cjs +1 -1
  10. package/dist/Chips/InternalChipDismissible/index.mjs +1 -1
  11. package/dist/Chips/index.cjs +1 -1
  12. package/dist/Chips/index.mjs +1 -1
  13. package/dist/ComboboxChipRemove-cjs.js +668 -379
  14. package/dist/ComboboxChipRemove-es.js +508 -219
  15. package/dist/ConfirmationModal/index.cjs +1 -1
  16. package/dist/ConfirmationModal/index.mjs +1 -1
  17. package/dist/DataDump/index.cjs +4 -4
  18. package/dist/DataDump/index.mjs +4 -4
  19. package/dist/DataList/components/DataListActions/index.cjs +1 -1
  20. package/dist/DataList/components/DataListActions/index.mjs +1 -1
  21. package/dist/DataList/components/DataListBulkActions/index.cjs +1 -1
  22. package/dist/DataList/components/DataListBulkActions/index.mjs +1 -1
  23. package/dist/DataList/components/DataListFilters/components/DataListSort/index.cjs +1 -1
  24. package/dist/DataList/components/DataListFilters/components/DataListSort/index.mjs +1 -1
  25. package/dist/DataList/components/DataListFilters/index.cjs +1 -1
  26. package/dist/DataList/components/DataListFilters/index.mjs +1 -1
  27. package/dist/DataList/components/DataListHeader/index.cjs +1 -1
  28. package/dist/DataList/components/DataListHeader/index.mjs +1 -1
  29. package/dist/DataList/components/DataListItem/index.cjs +1 -1
  30. package/dist/DataList/components/DataListItem/index.mjs +1 -1
  31. package/dist/DataList/components/DataListItemActions/index.cjs +1 -1
  32. package/dist/DataList/components/DataListItemActions/index.mjs +1 -1
  33. package/dist/DataList/components/DataListItemActionsOverflow/index.cjs +1 -1
  34. package/dist/DataList/components/DataListItemActionsOverflow/index.mjs +1 -1
  35. package/dist/DataList/components/DataListItems/index.cjs +1 -1
  36. package/dist/DataList/components/DataListItems/index.mjs +1 -1
  37. package/dist/DataList/components/DataListLayout/index.cjs +1 -1
  38. package/dist/DataList/components/DataListLayout/index.mjs +1 -1
  39. package/dist/DataList/components/DataListLayoutActions/index.cjs +1 -1
  40. package/dist/DataList/components/DataListLayoutActions/index.mjs +1 -1
  41. package/dist/DataList/index.cjs +1 -1
  42. package/dist/DataList/index.mjs +1 -1
  43. package/dist/DatePicker/index.cjs +1 -1
  44. package/dist/DatePicker/index.mjs +1 -1
  45. package/dist/Dialog/index.cjs +3 -3
  46. package/dist/Dialog/index.mjs +3 -3
  47. package/dist/DrawerDescription-cjs.js +32 -33
  48. package/dist/DrawerDescription-es.js +19 -20
  49. package/dist/DrawerTitle-cjs.js +7 -5
  50. package/dist/DrawerTitle-es.js +7 -5
  51. package/dist/FilterPicker/components/FilterPickerActivator/index.cjs +1 -1
  52. package/dist/FilterPicker/components/FilterPickerActivator/index.mjs +1 -1
  53. package/dist/FilterPicker/components/FilterPickerContent/index.cjs +1 -1
  54. package/dist/FilterPicker/components/FilterPickerContent/index.mjs +1 -1
  55. package/dist/FilterPicker/components/FilterPickerTrigger/index.cjs +1 -1
  56. package/dist/FilterPicker/components/FilterPickerTrigger/index.mjs +1 -1
  57. package/dist/FilterPicker/index.cjs +1 -1
  58. package/dist/FilterPicker/index.mjs +1 -1
  59. package/dist/FormFieldWrapper-cjs.js +2 -2
  60. package/dist/FormFieldWrapper-es.js +2 -2
  61. package/dist/FormatFile/index.cjs +1 -1
  62. package/dist/FormatFile/index.mjs +1 -1
  63. package/dist/Gallery/index.cjs +1 -1
  64. package/dist/Gallery/index.mjs +1 -1
  65. package/dist/InputDate/index.cjs +1 -1
  66. package/dist/InputDate/index.mjs +1 -1
  67. package/dist/InputNumberExperimental-cjs.js +319 -102
  68. package/dist/InputNumberExperimental-es.js +321 -104
  69. package/dist/LightBox/index.cjs +1 -1
  70. package/dist/LightBox/index.mjs +1 -1
  71. package/dist/Menu/index.cjs +4 -4
  72. package/dist/Menu/index.mjs +4 -4
  73. package/dist/MenuSubmenuTrigger-cjs.js +545 -498
  74. package/dist/MenuSubmenuTrigger-es.js +490 -443
  75. package/dist/Modal/index.cjs +1 -1
  76. package/dist/Modal/index.mjs +1 -1
  77. package/dist/NumberFieldInput-cjs.js +609 -496
  78. package/dist/NumberFieldInput-es.js +612 -498
  79. package/dist/Page/index.cjs +4 -4
  80. package/dist/Page/index.mjs +4 -4
  81. package/dist/Popover/index.cjs +1 -1
  82. package/dist/Popover/index.mjs +1 -1
  83. package/dist/ScrollAreaViewport-cjs.js +326 -601
  84. package/dist/ScrollAreaViewport-es.js +278 -548
  85. package/dist/SelectGroupLabel-cjs.js +494 -528
  86. package/dist/SelectGroupLabel-es.js +411 -445
  87. package/dist/Separator-cjs.js +1 -0
  88. package/dist/Separator-es.js +1 -0
  89. package/dist/Tooltip/index.cjs +1 -1
  90. package/dist/Tooltip/index.mjs +1 -1
  91. package/dist/docs/InputTime/InputTime.md +8 -0
  92. package/dist/docs/SelectPrimitive/SelectPrimitive.md +38 -25
  93. package/dist/floating-ui.react-cjs.js +586 -10
  94. package/dist/floating-ui.react-dom-cjs.js +92 -92
  95. package/dist/floating-ui.react-dom-es.js +1 -1
  96. package/dist/floating-ui.react-es.js +580 -4
  97. package/dist/floating-ui.utils-cjs.js +159 -0
  98. package/dist/floating-ui.utils-es.js +136 -0
  99. package/dist/index.cjs +4 -4
  100. package/dist/index.mjs +4 -4
  101. package/dist/{ToolbarRootContext-cjs.js → isElementDisabled-cjs.js} +5 -0
  102. package/dist/{ToolbarRootContext-es.js → isElementDisabled-es.js} +5 -1
  103. package/dist/primitives/BottomSheet/index.cjs +3 -3
  104. package/dist/primitives/BottomSheet/index.mjs +3 -3
  105. package/dist/primitives/ComboboxPrimitive/index.cjs +2 -2
  106. package/dist/primitives/ComboboxPrimitive/index.mjs +2 -2
  107. package/dist/primitives/InputNumberExperimental/index.cjs +1 -1
  108. package/dist/primitives/InputNumberExperimental/index.mjs +1 -1
  109. package/dist/primitives/SelectPrimitive/index.cjs +5 -5
  110. package/dist/primitives/SelectPrimitive/index.mjs +5 -5
  111. package/dist/primitives/index.cjs +4 -4
  112. package/dist/primitives/index.mjs +4 -4
  113. package/dist/resolveAriaLabelledBy-cjs.js +5 -0
  114. package/dist/resolveAriaLabelledBy-es.js +5 -1
  115. package/dist/{CSPContext-cjs.js → scrollEdges-cjs.js} +30 -2
  116. package/dist/{CSPContext-es.js → scrollEdges-es.js} +28 -3
  117. package/dist/styles.css +9 -3
  118. package/dist/unstyledPrimitives/index.cjs +1217 -639
  119. package/dist/unstyledPrimitives/index.mjs +1163 -585
  120. package/dist/useButton-cjs.js +283 -317
  121. package/dist/useButton-es.js +277 -298
  122. package/dist/useCompositeListItem-cjs.js +328 -279
  123. package/dist/useCompositeListItem-es.js +293 -246
  124. package/dist/useLabel-cjs.js +11 -1
  125. package/dist/useLabel-es.js +11 -2
  126. package/dist/useLabelableId-cjs.js +37 -91
  127. package/dist/useLabelableId-es.js +37 -90
  128. package/dist/useRenderElement-cjs.js +81 -26
  129. package/dist/useRenderElement-es.js +81 -27
  130. package/dist/{InternalBackdrop-cjs.js → useScrollLock-cjs.js} +1286 -1220
  131. package/dist/{InternalBackdrop-es.js → useScrollLock-es.js} +1250 -1207
  132. package/package.json +3 -3
  133. package/dist/index.esm-cjs.js +0 -738
  134. package/dist/index.esm-es.js +0 -712
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  var React = require('react');
4
- var useButton = require('../useButton-cjs.js');
5
4
  var ComboboxChipRemove = require('../ComboboxChipRemove-cjs.js');
6
5
  var resolveAriaLabelledBy = require('../resolveAriaLabelledBy-cjs.js');
7
6
  var jsxRuntime = require('react/jsx-runtime');
7
+ var useButton = require('../useButton-cjs.js');
8
8
  var Separator = require('../Separator-cjs.js');
9
9
  var DrawerDescription = require('../DrawerDescription-cjs.js');
10
10
  var DrawerTitle = require('../DrawerTitle-cjs.js');
@@ -12,17 +12,17 @@ var ScrollAreaViewport = require('../ScrollAreaViewport-cjs.js');
12
12
  var useRenderElement = require('../useRenderElement-cjs.js');
13
13
  var floatingUi_utils_dom = require('../floating-ui.utils.dom-cjs.js');
14
14
  var clamp = require('../clamp-cjs.js');
15
- var InternalBackdrop = require('../InternalBackdrop-cjs.js');
15
+ var useScrollLock = require('../useScrollLock-cjs.js');
16
16
  var ReactDOM = require('react-dom');
17
17
  var MenuSubmenuTrigger = require('../MenuSubmenuTrigger-cjs.js');
18
18
  var useCompositeListItem = require('../useCompositeListItem-cjs.js');
19
- var index_esm = require('../index.esm-cjs.js');
20
- var NumberFieldInput = require('../NumberFieldInput-cjs.js');
21
19
  var SelectGroupLabel = require('../SelectGroupLabel-cjs.js');
20
+ var floatingUi_utils = require('../floating-ui.utils-cjs.js');
21
+ var NumberFieldInput = require('../NumberFieldInput-cjs.js');
22
22
  var useLabelableId = require('../useLabelableId-cjs.js');
23
23
  var useLabel = require('../useLabel-cjs.js');
24
- require('../CSPContext-cjs.js');
25
- require('../ToolbarRootContext-cjs.js');
24
+ var scrollEdges = require('../scrollEdges-cjs.js');
25
+ require('../isElementDisabled-cjs.js');
26
26
  require('../floating-ui.react-dom-cjs.js');
27
27
 
28
28
  function _interopNamespaceDefault(e) {
@@ -78,13 +78,6 @@ function AutocompleteRoot(props) {
78
78
  } else {
79
79
  resolvedInputValue = internalValue;
80
80
  }
81
- const handleValueChange = useButton.useStableCallback((nextValue, eventDetails) => {
82
- setInlineInputValue('');
83
- if (!isControlled) {
84
- setInternalValue(nextValue);
85
- }
86
- onValueChange?.(nextValue, eventDetails);
87
- });
88
81
  const collator = ComboboxChipRemove.useCoreFilter();
89
82
  const baseFilter = React__namespace.useMemo(() => {
90
83
  if (other.filter !== undefined) {
@@ -106,7 +99,14 @@ function AutocompleteRoot(props) {
106
99
  return baseFilter(item, resolvedQuery, toString);
107
100
  };
108
101
  }, [baseFilter, mode, resolvedQuery, staticItems]);
109
- const handleItemHighlighted = useButton.useStableCallback((highlightedValue, eventDetails) => {
102
+ function handleValueChange(nextValue, eventDetails) {
103
+ setInlineInputValue('');
104
+ if (!isControlled) {
105
+ setInternalValue(nextValue);
106
+ }
107
+ onValueChange?.(nextValue, eventDetails);
108
+ }
109
+ function handleItemHighlighted(highlightedValue, eventDetails) {
110
110
  props.onItemHighlighted?.(highlightedValue, eventDetails);
111
111
  if (eventDetails.reason === useButton.pointer) {
112
112
  return;
@@ -120,7 +120,7 @@ function AutocompleteRoot(props) {
120
120
  } else {
121
121
  setInlineInputValue('');
122
122
  }
123
- });
123
+ }
124
124
  return /*#__PURE__*/jsxRuntime.jsx(ComboboxChipRemove.AriaCombobox, {
125
125
  ...other,
126
126
  itemToStringLabel: itemToStringValue,
@@ -154,6 +154,30 @@ function AutocompleteValue(props) {
154
154
  });
155
155
  }
156
156
 
157
+ /**
158
+ * A button that opens the popup.
159
+ * Renders a `<button>` element.
160
+ *
161
+ * Documentation: [Base UI Autocomplete](https://base-ui.com/react/components/autocomplete)
162
+ */
163
+ const AutocompleteTrigger = ComboboxChipRemove.ComboboxTrigger;
164
+
165
+ /**
166
+ * A wrapper for the input and its associated controls.
167
+ * Renders a `<div>` element.
168
+ *
169
+ * Documentation: [Base UI Autocomplete](https://base-ui.com/react/components/autocomplete)
170
+ */
171
+ const AutocompleteInputGroup = ComboboxChipRemove.ComboboxInputGroup;
172
+
173
+ /**
174
+ * An individual item in the list.
175
+ * Renders a `<div>` element.
176
+ *
177
+ * Documentation: [Base UI Autocomplete](https://base-ui.com/react/components/autocomplete)
178
+ */
179
+ const AutocompleteItem = ComboboxChipRemove.ComboboxItem;
180
+
157
181
  var index_parts$7 = /*#__PURE__*/Object.freeze({
158
182
  __proto__: null,
159
183
  Arrow: ComboboxChipRemove.ComboboxArrow,
@@ -165,8 +189,8 @@ var index_parts$7 = /*#__PURE__*/Object.freeze({
165
189
  GroupLabel: ComboboxChipRemove.ComboboxGroupLabel,
166
190
  Icon: ComboboxChipRemove.ComboboxIcon,
167
191
  Input: ComboboxChipRemove.ComboboxInput,
168
- InputGroup: ComboboxChipRemove.ComboboxInputGroup,
169
- Item: ComboboxChipRemove.ComboboxItem,
192
+ InputGroup: AutocompleteInputGroup,
193
+ Item: AutocompleteItem,
170
194
  List: ComboboxChipRemove.ComboboxList,
171
195
  Popup: ComboboxChipRemove.ComboboxPopup,
172
196
  Portal: ComboboxChipRemove.ComboboxPortal,
@@ -175,7 +199,7 @@ var index_parts$7 = /*#__PURE__*/Object.freeze({
175
199
  Row: ComboboxChipRemove.ComboboxRow,
176
200
  Separator: Separator.Separator,
177
201
  Status: ComboboxChipRemove.ComboboxStatus,
178
- Trigger: ComboboxChipRemove.ComboboxTrigger,
202
+ Trigger: AutocompleteTrigger,
179
203
  Value: AutocompleteValue,
180
204
  useFilter: ComboboxChipRemove.useCoreFilter,
181
205
  useFilteredItems: ComboboxChipRemove.useFilteredItems
@@ -220,6 +244,7 @@ const DrawerIndent = /*#__PURE__*/React__namespace.forwardRef(function DrawerInd
220
244
  const {
221
245
  render,
222
246
  className,
247
+ style,
223
248
  ...elementProps
224
249
  } = componentProps;
225
250
  const providerContext = ScrollAreaViewport.useDrawerProviderContext();
@@ -294,6 +319,7 @@ const DrawerIndentBackground = /*#__PURE__*/React__namespace.forwardRef(function
294
319
  const {
295
320
  render,
296
321
  className,
322
+ style,
297
323
  ...elementProps
298
324
  } = componentProps;
299
325
  const providerContext = ScrollAreaViewport.useDrawerProviderContext();
@@ -393,36 +419,49 @@ function createVisualStateStore() {
393
419
  };
394
420
  }
395
421
 
396
- function isScrollable(element, axis) {
422
+ function isScrollable(element, axis,
423
+ // When true, a container that overflows only once extra space is added (e.g. drawer
424
+ // keyboard scroll slack) still counts, as long as it has layout size on the axis.
425
+ allowOverflowIntent = false) {
397
426
  const style = floatingUi_utils_dom.getComputedStyle(element);
398
427
  if (axis === 'vertical') {
399
428
  const overflowY = style.overflowY;
400
- return (overflowY === 'auto' || overflowY === 'scroll') && element.scrollHeight > element.clientHeight;
429
+ if (overflowY !== 'auto' && overflowY !== 'scroll') {
430
+ return false;
431
+ }
432
+ return allowOverflowIntent ? element.clientHeight > 0 : element.scrollHeight > element.clientHeight;
401
433
  }
402
434
  const overflowX = style.overflowX;
403
- return (overflowX === 'auto' || overflowX === 'scroll') && element.scrollWidth > element.clientWidth;
435
+ if (overflowX !== 'auto' && overflowX !== 'scroll') {
436
+ return false;
437
+ }
438
+ return allowOverflowIntent ? element.clientWidth > 0 : element.scrollWidth > element.clientWidth;
404
439
  }
405
440
  function hasScrollableAncestor(target, root, axes) {
441
+ // `getParentNode` crosses shadow boundaries (and slots), so a target inside a shadow root
442
+ // still walks up to scrollable ancestors in the light DOM.
406
443
  let node = target;
407
- while (node && node !== root) {
444
+ while (floatingUi_utils_dom.isHTMLElement(node) && node !== root && !floatingUi_utils_dom.isLastTraversableNode(node)) {
408
445
  for (const axis of axes) {
409
446
  if (isScrollable(node, axis)) {
410
447
  return true;
411
448
  }
412
449
  }
413
- node = node.parentElement;
450
+ node = floatingUi_utils_dom.getParentNode(node);
414
451
  }
415
452
  return false;
416
453
  }
417
- function findScrollableTouchTarget(target, root, axis = 'vertical') {
454
+ function findScrollableTouchTarget(target, root, axis = 'vertical', allowOverflowIntent = false) {
455
+ // `getParentNode` crosses shadow boundaries (and slots), so a target inside a shadow root
456
+ // still reaches a scrollable ancestor in the light DOM.
418
457
  let node = floatingUi_utils_dom.isHTMLElement(target) ? target : null;
419
- while (node && node !== root) {
420
- if (isScrollable(node, axis)) {
458
+ while (floatingUi_utils_dom.isHTMLElement(node) && node !== root && !floatingUi_utils_dom.isLastTraversableNode(node)) {
459
+ if (isScrollable(node, axis, allowOverflowIntent)) {
421
460
  return node;
422
461
  }
423
- node = node.parentElement;
462
+ node = floatingUi_utils_dom.getParentNode(node);
424
463
  }
425
- return isScrollable(root, axis) ? root : null;
464
+ return isScrollable(root, axis, allowOverflowIntent) ? root : null;
426
465
  }
427
466
 
428
467
  function getElementAtPoint(doc, x, y) {
@@ -451,7 +490,7 @@ function getDisplacement(direction, deltaX, deltaY) {
451
490
  }
452
491
  }
453
492
  function getElementTransform(element) {
454
- const computedStyle = window.getComputedStyle(element);
493
+ const computedStyle = floatingUi_utils_dom.getWindow(element).getComputedStyle(element);
455
494
  const transform = computedStyle.transform;
456
495
  let translateX = 0;
457
496
  let translateY = 0;
@@ -480,6 +519,9 @@ function getElementTransform(element) {
480
519
  function getValidTimeStamp(timeStamp) {
481
520
  return Number.isFinite(timeStamp) && timeStamp > 0 ? timeStamp : null;
482
521
  }
522
+ function getDragTransform(dragOffset, scale) {
523
+ return `translate3d(${dragOffset.x}px,${dragOffset.y}px,0) scale(${scale})`;
524
+ }
483
525
  function hasPrimaryMouseButton(buttons) {
484
526
  return buttons % 2 === 1;
485
527
  }
@@ -509,6 +551,7 @@ function useSwipeDismiss(options) {
509
551
  swipeThreshold: swipeThresholdProp,
510
552
  onDismiss,
511
553
  onProgress,
554
+ onCancel,
512
555
  onSwipeStart,
513
556
  onRelease,
514
557
  onSwipingChange,
@@ -535,18 +578,7 @@ function useSwipeDismiss(options) {
535
578
  }, [hasHorizontal, hasVertical]);
536
579
  const [currentSwipeDirection, setCurrentSwipeDirection] = React__namespace.useState(undefined);
537
580
  const [isSwiping, setIsSwiping] = React__namespace.useState(false);
538
- const [isRealSwipe, setIsRealSwipe] = React__namespace.useState(false);
539
581
  const [dragDismissed, setDragDismissed] = React__namespace.useState(false);
540
- const [dragOffset, setDragOffset] = React__namespace.useState({
541
- x: 0,
542
- y: 0
543
- });
544
- const [initialTransform, setInitialTransform] = React__namespace.useState({
545
- x: 0,
546
- y: 0,
547
- scale: 1
548
- });
549
- const [lockedDirection, setLockedDirection] = React__namespace.useState(null);
550
582
  const dragStartPosRef = React__namespace.useRef({
551
583
  x: 0,
552
584
  y: 0
@@ -568,6 +600,7 @@ function useSwipeDismiss(options) {
568
600
  x: 0,
569
601
  y: 0
570
602
  });
603
+ const lockedDirectionRef = React__namespace.useRef(null);
571
604
  const isFirstPointerMoveRef = React__namespace.useRef(false);
572
605
  const pendingSwipeRef = React__namespace.useRef(false);
573
606
  const pendingSwipeStartPosRef = React__namespace.useRef(null);
@@ -587,6 +620,7 @@ function useSwipeDismiss(options) {
587
620
  });
588
621
  const lastProgressDetailsRef = React__namespace.useRef(null);
589
622
  const isSwipingRef = React__namespace.useRef(false);
623
+ const dragStyleSnapshotRef = React__namespace.useRef(null);
590
624
  const setSwiping = useButton.useStableCallback(nextSwiping => {
591
625
  if (isSwipingRef.current === nextSwiping) {
592
626
  return;
@@ -632,6 +666,35 @@ function useSwipeDismiss(options) {
632
666
  }
633
667
  onProgress?.(nextProgress, details);
634
668
  });
669
+ const syncDragStyles = useButton.useStableCallback(swiping => {
670
+ const element = elementRef.current;
671
+ if (!trackDrag || !element) {
672
+ if (!swiping) {
673
+ dragStyleSnapshotRef.current = null;
674
+ }
675
+ return;
676
+ }
677
+ const style = element.style;
678
+ const dragStyleSnapshot = dragStyleSnapshotRef.current;
679
+ if (swiping) {
680
+ if (!dragStyleSnapshot) {
681
+ dragStyleSnapshotRef.current = [style.transition, style.transform];
682
+ }
683
+ style.transition = 'none';
684
+ } else if (dragStyleSnapshot) {
685
+ [style.transition, style.transform] = dragStyleSnapshot;
686
+ dragStyleSnapshotRef.current = null;
687
+ }
688
+ const dragOffset = dragOffsetRef.current;
689
+ const initialTransform = initialTransformRef.current;
690
+ const deltaX = dragOffset.x - initialTransform.x;
691
+ const deltaY = dragOffset.y - initialTransform.y;
692
+ if (swiping) {
693
+ style.transform = getDragTransform(dragOffset, initialTransform.scale);
694
+ }
695
+ style.setProperty(movementCssVars.x, `${deltaX}px`);
696
+ style.setProperty(movementCssVars.y, `${deltaY}px`);
697
+ });
635
698
  function recordDragSample(offset, timeStamp) {
636
699
  if (timeStamp === null) {
637
700
  return;
@@ -653,18 +716,7 @@ function useSwipeDismiss(options) {
653
716
  const reset = React__namespace.useCallback(() => {
654
717
  setCurrentSwipeDirection(undefined);
655
718
  setSwiping(false);
656
- setIsRealSwipe(false);
657
719
  setDragDismissed(false);
658
- setDragOffset({
659
- x: 0,
660
- y: 0
661
- });
662
- setInitialTransform({
663
- x: 0,
664
- y: 0,
665
- scale: 1
666
- });
667
- setLockedDirection(null);
668
720
  updateSwipeProgress(0);
669
721
  swipeThresholdRef.current = swipeThresholdDefault;
670
722
  dragStartPosRef.current = {
@@ -687,6 +739,7 @@ function useSwipeDismiss(options) {
687
739
  x: 0,
688
740
  y: 0
689
741
  };
742
+ lockedDirectionRef.current = null;
690
743
  isFirstPointerMoveRef.current = false;
691
744
  lastMovePosRef.current = null;
692
745
  pendingSwipeRef.current = false;
@@ -704,7 +757,8 @@ function useSwipeDismiss(options) {
704
757
  y: 0
705
758
  };
706
759
  lastProgressDetailsRef.current = null;
707
- }, [setSwiping, swipeThresholdDefault, updateSwipeProgress]);
760
+ syncDragStyles(false);
761
+ }, [setSwiping, swipeThresholdDefault, syncDragStyles, updateSwipeProgress]);
708
762
  React__namespace.useEffect(() => {
709
763
  if (typeof swipeThresholdProp !== 'function') {
710
764
  swipeThresholdRef.current = swipeThresholdDefault;
@@ -786,11 +840,6 @@ function useSwipeDismiss(options) {
786
840
  x: transform.x,
787
841
  y: transform.y
788
842
  };
789
- setInitialTransform(transform);
790
- setDragOffset({
791
- x: transform.x,
792
- y: transform.y
793
- });
794
843
  recordDragSample({
795
844
  x: transform.x,
796
845
  y: transform.y
@@ -801,10 +850,10 @@ function useSwipeDismiss(options) {
801
850
  }
802
851
  onSwipeStart?.(event.nativeEvent);
803
852
  setSwiping(true);
804
- setIsRealSwipe(false);
805
- setLockedDirection(null);
853
+ lockedDirectionRef.current = null;
806
854
  isFirstPointerMoveRef.current = true;
807
855
  updateSwipeProgress(0);
856
+ syncDragStyles(true);
808
857
  return true;
809
858
  }
810
859
  function resetPendingSwipeState() {
@@ -822,19 +871,15 @@ function useSwipeDismiss(options) {
822
871
  return;
823
872
  }
824
873
  setSwiping(false);
825
- setIsRealSwipe(false);
826
- setLockedDirection(null);
827
- const resolvedInitialTransform = trackDrag ? initialTransform : initialTransformRef.current;
874
+ lockedDirectionRef.current = null;
875
+ const resolvedInitialTransform = initialTransformRef.current;
828
876
  dragOffsetRef.current = {
829
877
  x: resolvedInitialTransform.x,
830
878
  y: resolvedInitialTransform.y
831
879
  };
832
- setDragOffset({
833
- x: resolvedInitialTransform.x,
834
- y: resolvedInitialTransform.y
835
- });
836
880
  setCurrentSwipeDirection(undefined);
837
881
  sawPrimaryButtonsOnMoveRef.current = false;
882
+ syncDragStyles(false);
838
883
  const element = elementRef.current;
839
884
  if (element) {
840
885
  safelyChangePointerCapture(element, event.pointerId, 'releasePointerCapture');
@@ -844,6 +889,7 @@ function useSwipeDismiss(options) {
844
889
  deltaY: 0,
845
890
  direction: undefined
846
891
  });
892
+ onCancel?.(event.nativeEvent);
847
893
  }
848
894
  function applyDirectionalDamping(deltaX, deltaY) {
849
895
  const exponent = value => value >= 0 ? value ** 0.5 : -(Math.abs(value) ** 0.5);
@@ -964,17 +1010,12 @@ function useSwipeDismiss(options) {
964
1010
  const deltaY = clientY - dragStartPosRef.current.y;
965
1011
  const cancelDeltaY = clientY - swipeCancelBaselineRef.current.y;
966
1012
  const cancelDeltaX = clientX - swipeCancelBaselineRef.current.x;
967
- if (!isRealSwipe) {
1013
+ let lockedDirection = lockedDirectionRef.current;
1014
+ if (lockedDirection === null && hasHorizontal && hasVertical) {
968
1015
  const movementDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
969
1016
  if (movementDistance >= MIN_DRAG_THRESHOLD) {
970
- setIsRealSwipe(true);
971
- if (lockedDirection === null) {
972
- if (hasHorizontal && hasVertical) {
973
- const absX = Math.abs(deltaX);
974
- const absY = Math.abs(deltaY);
975
- setLockedDirection(absX > absY ? 'horizontal' : 'vertical');
976
- }
977
- }
1017
+ lockedDirection = Math.abs(deltaX) > Math.abs(deltaY) ? 'horizontal' : 'vertical';
1018
+ lockedDirectionRef.current = lockedDirection;
978
1019
  }
979
1020
  }
980
1021
  let candidate;
@@ -1039,12 +1080,7 @@ function useSwipeDismiss(options) {
1039
1080
  x: newOffsetX,
1040
1081
  y: newOffsetY
1041
1082
  };
1042
- if (trackDrag) {
1043
- setDragOffset({
1044
- x: newOffsetX,
1045
- y: newOffsetY
1046
- });
1047
- }
1083
+ syncDragStyles(true);
1048
1084
  recordDragSample({
1049
1085
  x: newOffsetX,
1050
1086
  y: newOffsetY
@@ -1086,91 +1122,6 @@ function useSwipeDismiss(options) {
1086
1122
  direction: swipeDirectionDetails
1087
1123
  });
1088
1124
  }
1089
- const handleMove = useButton.useStableCallback(event => {
1090
- const currentPos = getPrimaryPointerPosition(event);
1091
- if (!currentPos) {
1092
- return;
1093
- }
1094
- if (!('touches' in event)) {
1095
- const hasPrimaryButton = hasPrimaryMouseButton(event.buttons);
1096
- if (hasPrimaryButton) {
1097
- sawPrimaryButtonsOnMoveRef.current = true;
1098
- }
1099
-
1100
- // Cancel the swipe if a non-primary button takes over the interaction.
1101
- // This handles cases where a right-click interrupts dragging.
1102
- const lostPrimaryButtonDuringSwipe = event.buttons === 0 && sawPrimaryButtonsOnMoveRef.current;
1103
- if (event.buttons !== 0 && !hasPrimaryButton || lostPrimaryButtonDuringSwipe) {
1104
- cancelSwipeInteraction(event);
1105
- return;
1106
- }
1107
- }
1108
- if (!isSwiping && pendingSwipeRef.current) {
1109
- if (!isTouchLikeEvent(event) && (event.defaultPrevented || event.nativeEvent.defaultPrevented)) {
1110
- resetPendingSwipeState();
1111
- return;
1112
- }
1113
- const allowedToStart = canStart ? canStart(currentPos, {
1114
- nativeEvent: event.nativeEvent,
1115
- direction: primaryDirection
1116
- }) : true;
1117
- if (allowedToStart) {
1118
- const pendingStartPos = pendingSwipeStartPosRef.current;
1119
- let ignoreScrollableOnStart = false;
1120
- if (isTouchLikeEvent(event)) {
1121
- const element = elementRef.current;
1122
- if (pendingStartPos && element) {
1123
- const target = getTargetAtPoint(currentPos, event.nativeEvent);
1124
- const doc = useButton.ownerDocument(element);
1125
- const body = doc.body;
1126
- const scrollTarget = body ? findGestureScrollableTouchTarget(target, body) : null;
1127
- if (scrollTarget && (useButton.contains(element, scrollTarget) || useButton.contains(scrollTarget, element))) {
1128
- const deltaX = currentPos.x - pendingStartPos.x;
1129
- const deltaY = currentPos.y - pendingStartPos.y;
1130
- const canSwipeFromEdge = canSwipeFromScrollEdgeOnPendingMove(scrollTarget, deltaX, deltaY);
1131
- if (canSwipeFromEdge === false) {
1132
- return;
1133
- }
1134
- if (canSwipeFromEdge === true) {
1135
- ignoreScrollableOnStart = true;
1136
- }
1137
- }
1138
- }
1139
- }
1140
- const started = startSwipeAtPosition(event, currentPos, {
1141
- ignoreScrollableTarget: ignoreScrollableOnStart,
1142
- ignoreScrollableAncestors: ignoreScrollableOnStart
1143
- });
1144
- if (started) {
1145
- if (pendingStartPos && ignoreScrollableOnStart) {
1146
- // Preserve displacement between touchstart and the move that activates swipe from
1147
- // a scroll-edge so quick flicks can dismiss.
1148
- clearPendingSwipeStartState();
1149
- dragStartPosRef.current = pendingStartPos;
1150
- swipeCancelBaselineRef.current = pendingStartPos;
1151
- lastMovePosRef.current = pendingStartPos;
1152
- isFirstPointerMoveRef.current = false;
1153
- } else {
1154
- // Start from the current in-bounds position without dropping follow-up move
1155
- // displacement; this avoids jumps when entering from outside the element while
1156
- // keeping swipe tracking responsive on the next move.
1157
- clearPendingSwipeStartState();
1158
- swipeFromScrollableRef.current = false;
1159
- }
1160
- }
1161
- }
1162
- }
1163
- const previousPos = lastMovePosRef.current;
1164
- const movement = previousPos === null ? {
1165
- x: 0,
1166
- y: 0
1167
- } : {
1168
- x: currentPos.x - previousPos.x,
1169
- y: currentPos.y - previousPos.y
1170
- };
1171
- lastMovePosRef.current = currentPos;
1172
- handleMoveCore(event, currentPos, movement);
1173
- });
1174
1125
  const handleEnd = useButton.useStableCallback(event => {
1175
1126
  if (!enabled) {
1176
1127
  return;
@@ -1182,7 +1133,7 @@ function useSwipeDismiss(options) {
1182
1133
  const progressDetails = {
1183
1134
  deltaX: releaseDeltaX,
1184
1135
  deltaY: releaseDeltaY,
1185
- direction: currentSwipeDirection ?? intendedSwipeDirectionRef.current
1136
+ direction: intendedSwipeDirectionRef.current
1186
1137
  };
1187
1138
  if (!isSwipingRef.current) {
1188
1139
  resetPendingSwipeState();
@@ -1190,8 +1141,7 @@ function useSwipeDismiss(options) {
1190
1141
  return;
1191
1142
  }
1192
1143
  setSwiping(false);
1193
- setIsRealSwipe(false);
1194
- setLockedDirection(null);
1144
+ lockedDirectionRef.current = null;
1195
1145
  resetPendingSwipeState();
1196
1146
  sawPrimaryButtonsOnMoveRef.current = false;
1197
1147
  const element = elementRef.current;
@@ -1232,7 +1182,7 @@ function useSwipeDismiss(options) {
1232
1182
  }
1233
1183
  const releaseDecision = onRelease?.({
1234
1184
  event: event.nativeEvent,
1235
- direction: currentSwipeDirection ?? intendedSwipeDirectionRef.current,
1185
+ direction: intendedSwipeDirectionRef.current,
1236
1186
  deltaX,
1237
1187
  deltaY,
1238
1188
  velocityX,
@@ -1246,11 +1196,8 @@ function useSwipeDismiss(options) {
1246
1196
  x: resolvedInitialTransform.x,
1247
1197
  y: resolvedInitialTransform.y
1248
1198
  };
1249
- setDragOffset({
1250
- x: resolvedInitialTransform.x,
1251
- y: resolvedInitialTransform.y
1252
- });
1253
1199
  setCurrentSwipeDirection(undefined);
1200
+ syncDragStyles(false);
1254
1201
  updateSwipeProgress(0, progressDetails);
1255
1202
  return;
1256
1203
  }
@@ -1258,7 +1205,7 @@ function useSwipeDismiss(options) {
1258
1205
  let dismissDirection;
1259
1206
  if (hasReleaseDecision) {
1260
1207
  shouldClose = releaseDecision;
1261
- dismissDirection = currentSwipeDirection ?? intendedSwipeDirectionRef.current ?? primaryDirection;
1208
+ dismissDirection = intendedSwipeDirectionRef.current ?? primaryDirection;
1262
1209
  } else {
1263
1210
  for (const direction of directions) {
1264
1211
  switch (direction) {
@@ -1295,6 +1242,7 @@ function useSwipeDismiss(options) {
1295
1242
  if (shouldClose && dismissDirection) {
1296
1243
  setCurrentSwipeDirection(dismissDirection);
1297
1244
  setDragDismissed(true);
1245
+ syncDragStyles(false);
1298
1246
  onDismiss?.(event.nativeEvent, {
1299
1247
  direction: dismissDirection
1300
1248
  });
@@ -1303,34 +1251,150 @@ function useSwipeDismiss(options) {
1303
1251
  x: resolvedInitialTransform.x,
1304
1252
  y: resolvedInitialTransform.y
1305
1253
  };
1306
- setDragOffset({
1307
- x: resolvedInitialTransform.x,
1308
- y: resolvedInitialTransform.y
1309
- });
1310
1254
  setCurrentSwipeDirection(undefined);
1255
+ syncDragStyles(false);
1311
1256
  updateSwipeProgress(0, progressDetails);
1312
1257
  }
1313
1258
  });
1259
+ const handleMove = useButton.useStableCallback(event => {
1260
+ const currentPos = getPrimaryPointerPosition(event);
1261
+ if (!currentPos) {
1262
+ return;
1263
+ }
1264
+ let endAfterMove = false;
1265
+ if (!('touches' in event)) {
1266
+ const hasPrimaryButton = hasPrimaryMouseButton(event.buttons);
1267
+ if (hasPrimaryButton) {
1268
+ sawPrimaryButtonsOnMoveRef.current = true;
1269
+ }
1270
+
1271
+ // Cancel the swipe if a non-primary button takes over the interaction.
1272
+ // This handles cases where a right-click interrupts dragging.
1273
+ if (event.buttons !== 0 && !hasPrimaryButton) {
1274
+ cancelSwipeInteraction(event);
1275
+ return;
1276
+ }
1277
+
1278
+ // A `buttons: 0` pointermove means the primary button was already released, so the gesture is
1279
+ // over even if no pointerup reached us. On fast trackpad flicks this trailing move is
1280
+ // dispatched just before pointerup; treat it as the release (mirroring touchend) instead of
1281
+ // cancelling and snapping the element back.
1282
+ if (event.buttons === 0 && sawPrimaryButtonsOnMoveRef.current) {
1283
+ if (!isSwipingRef.current) {
1284
+ // The gesture never activated — discard it.
1285
+ handleEnd(event);
1286
+ return;
1287
+ }
1288
+ // This release move can itself carry the threshold-crossing displacement (and the peak
1289
+ // release velocity), so let it flow through `handleMoveCore` below to update the drag
1290
+ // offset / velocity sample, then commit the release afterwards.
1291
+ endAfterMove = true;
1292
+ }
1293
+ }
1294
+ if (!isSwiping && pendingSwipeRef.current) {
1295
+ if (!isTouchLikeEvent(event) && (event.defaultPrevented || event.nativeEvent.defaultPrevented)) {
1296
+ resetPendingSwipeState();
1297
+ return;
1298
+ }
1299
+ const allowedToStart = canStart ? canStart(currentPos, {
1300
+ nativeEvent: event.nativeEvent,
1301
+ direction: primaryDirection
1302
+ }) : true;
1303
+ if (allowedToStart) {
1304
+ const pendingStartPos = pendingSwipeStartPosRef.current;
1305
+ let ignoreScrollableOnStart = false;
1306
+ if (isTouchLikeEvent(event)) {
1307
+ const element = elementRef.current;
1308
+ if (pendingStartPos && element) {
1309
+ const target = getTargetAtPoint(currentPos, event.nativeEvent);
1310
+ const doc = useButton.ownerDocument(element);
1311
+ const body = doc.body;
1312
+ const scrollTarget = body ? findGestureScrollableTouchTarget(target, body) : null;
1313
+ if (scrollTarget && (useButton.contains(element, scrollTarget) || useButton.contains(scrollTarget, element))) {
1314
+ const deltaX = currentPos.x - pendingStartPos.x;
1315
+ const deltaY = currentPos.y - pendingStartPos.y;
1316
+ const canSwipeFromEdge = canSwipeFromScrollEdgeOnPendingMove(scrollTarget, deltaX, deltaY);
1317
+ if (canSwipeFromEdge === false) {
1318
+ return;
1319
+ }
1320
+ if (canSwipeFromEdge === true) {
1321
+ ignoreScrollableOnStart = true;
1322
+ }
1323
+ }
1324
+ }
1325
+ }
1326
+ const started = startSwipeAtPosition(event, currentPos, {
1327
+ ignoreScrollableTarget: ignoreScrollableOnStart,
1328
+ ignoreScrollableAncestors: ignoreScrollableOnStart
1329
+ });
1330
+ if (started) {
1331
+ if (pendingStartPos && ignoreScrollableOnStart) {
1332
+ // Preserve displacement between touchstart and the move that activates swipe from
1333
+ // a scroll-edge so quick flicks can dismiss.
1334
+ clearPendingSwipeStartState();
1335
+ dragStartPosRef.current = pendingStartPos;
1336
+ swipeCancelBaselineRef.current = pendingStartPos;
1337
+ lastMovePosRef.current = pendingStartPos;
1338
+ isFirstPointerMoveRef.current = false;
1339
+ } else {
1340
+ // Start from the current in-bounds position without dropping follow-up move
1341
+ // displacement; this avoids jumps when entering from outside the element while
1342
+ // keeping swipe tracking responsive on the next move.
1343
+ clearPendingSwipeStartState();
1344
+ swipeFromScrollableRef.current = false;
1345
+ }
1346
+ }
1347
+ }
1348
+ }
1349
+ const previousPos = lastMovePosRef.current;
1350
+ const movement = previousPos === null ? {
1351
+ x: 0,
1352
+ y: 0
1353
+ } : {
1354
+ x: currentPos.x - previousPos.x,
1355
+ y: currentPos.y - previousPos.y
1356
+ };
1357
+ lastMovePosRef.current = currentPos;
1358
+ handleMoveCore(event, currentPos, movement);
1359
+
1360
+ // `endAfterMove` is only set in the non-touch branch above; the `'touches'` guard re-narrows the
1361
+ // event type for `handleEnd` after the shared move handling has run.
1362
+ if (endAfterMove && !('touches' in event)) {
1363
+ handleEnd(event);
1364
+ }
1365
+ });
1366
+
1367
+ // Feeds a native touchmove into the swipe pipeline. Used by consumers that claim the gesture
1368
+ // in a capture-phase listener and stop it from reaching React's delegated touch handlers.
1369
+ const moveNative = useButton.useStableCallback((nativeEvent, currentTarget) => {
1370
+ handleMove({
1371
+ touches: nativeEvent.touches,
1372
+ currentTarget,
1373
+ nativeEvent,
1374
+ defaultPrevented: nativeEvent.defaultPrevented,
1375
+ timeStamp: nativeEvent.timeStamp
1376
+ });
1377
+ });
1314
1378
  const getDragStyles = React__namespace.useCallback(() => {
1315
- const resolvedDragOffset = trackDrag ? dragOffset : dragOffsetRef.current;
1316
- const resolvedInitialTransform = trackDrag ? initialTransform : initialTransformRef.current;
1317
- if (!isSwiping && resolvedDragOffset.x === resolvedInitialTransform.x && resolvedDragOffset.y === resolvedInitialTransform.y && !dragDismissed) {
1379
+ const dragOffset = dragOffsetRef.current;
1380
+ const initialTransform = initialTransformRef.current;
1381
+ const deltaX = dragOffset.x - initialTransform.x;
1382
+ const deltaY = dragOffset.y - initialTransform.y;
1383
+ if (!isSwiping && deltaX === 0 && deltaY === 0 && !dragDismissed) {
1318
1384
  return {
1319
1385
  [movementCssVars.x]: '0px',
1320
1386
  [movementCssVars.y]: '0px'
1321
1387
  };
1322
1388
  }
1323
- const deltaX = resolvedDragOffset.x - resolvedInitialTransform.x;
1324
- const deltaY = resolvedDragOffset.y - resolvedInitialTransform.y;
1325
1389
  return {
1326
1390
  transition: isSwiping ? 'none' : undefined,
1327
1391
  // While swiping, freeze the element at its current visual transform so it doesn't snap to the
1328
1392
  // end position.
1329
- transform: isSwiping ? `translateX(${resolvedDragOffset.x}px) translateY(${resolvedDragOffset.y}px) scale(${resolvedInitialTransform.scale})` : undefined,
1393
+ transform: isSwiping ? getDragTransform(dragOffset, initialTransform.scale) : undefined,
1330
1394
  [movementCssVars.x]: `${deltaX}px`,
1331
1395
  [movementCssVars.y]: `${deltaY}px`
1332
1396
  };
1333
- }, [dragDismissed, dragOffset, initialTransform, isSwiping, movementCssVars, trackDrag]);
1397
+ }, [dragDismissed, isSwiping, movementCssVars]);
1334
1398
  const getPointerProps = React__namespace.useCallback(() => {
1335
1399
  if (!enabled) {
1336
1400
  return {};
@@ -1359,6 +1423,7 @@ function useSwipeDismiss(options) {
1359
1423
  dragDismissed,
1360
1424
  getPointerProps,
1361
1425
  getTouchProps,
1426
+ moveNative,
1362
1427
  getDragStyles,
1363
1428
  reset
1364
1429
  };
@@ -1368,11 +1433,11 @@ let DrawerSwipeAreaDataAttributes = function (DrawerSwipeAreaDataAttributes) {
1368
1433
  /**
1369
1434
  * Present when the drawer is open.
1370
1435
  */
1371
- DrawerSwipeAreaDataAttributes[DrawerSwipeAreaDataAttributes["open"] = InternalBackdrop.CommonPopupDataAttributes.open] = "open";
1436
+ DrawerSwipeAreaDataAttributes[DrawerSwipeAreaDataAttributes["open"] = useScrollLock.CommonPopupDataAttributes.open] = "open";
1372
1437
  /**
1373
1438
  * Present when the drawer is closed.
1374
1439
  */
1375
- DrawerSwipeAreaDataAttributes[DrawerSwipeAreaDataAttributes["closed"] = InternalBackdrop.CommonPopupDataAttributes.closed] = "closed";
1440
+ DrawerSwipeAreaDataAttributes[DrawerSwipeAreaDataAttributes["closed"] = useScrollLock.CommonPopupDataAttributes.closed] = "closed";
1376
1441
  /**
1377
1442
  * Present when the swipe area is disabled.
1378
1443
  */
@@ -1439,8 +1504,9 @@ function resolveTouchAction(direction) {
1439
1504
  */
1440
1505
  const DrawerSwipeArea = /*#__PURE__*/React__namespace.forwardRef(function DrawerSwipeArea(componentProps, forwardedRef) {
1441
1506
  const {
1442
- className,
1443
1507
  render,
1508
+ className,
1509
+ style,
1444
1510
  disabled = false,
1445
1511
  swipeDirection: swipeDirectionProp,
1446
1512
  ...elementProps
@@ -1466,15 +1532,15 @@ const DrawerSwipeArea = /*#__PURE__*/React__namespace.forwardRef(function Drawer
1466
1532
  const appliedSwipeStylesRef = React__namespace.useRef(false);
1467
1533
  const popupTransitionRef = React__namespace.useRef(null);
1468
1534
  const swipeAreaId = useButton.useBaseUiId(componentProps.id);
1469
- const registerTrigger = ScrollAreaViewport.useTriggerRegistration(swipeAreaId, store);
1535
+ const registerTrigger = useScrollLock.useTriggerRegistration(swipeAreaId, store);
1470
1536
  const open = store.useState('open');
1471
- const resolvedSwipeDirection = swipeDirectionProp ?? oppositeSwipeDirection[swipeDirection];
1472
- const dismissDirection = oppositeSwipeDirection[resolvedSwipeDirection];
1473
- const enabled = !disabled && (!open || swipeActive);
1474
1537
  const resetDragDelta = useButton.useStableCallback(() => {
1475
1538
  dragDeltaRef.current.x = 0;
1476
1539
  dragDeltaRef.current.y = 0;
1477
1540
  });
1541
+ const resolvedSwipeDirection = swipeDirectionProp ?? oppositeSwipeDirection[swipeDirection];
1542
+ const dismissDirection = oppositeSwipeDirection[resolvedSwipeDirection];
1543
+ const enabled = !disabled && (!open || swipeActive);
1478
1544
  function disableDismissForSwipe() {
1479
1545
  releaseDismissTimeout.clear();
1480
1546
  store.context.outsidePressEnabledRef.current = false;
@@ -1614,6 +1680,18 @@ const DrawerSwipeArea = /*#__PURE__*/React__namespace.forwardRef(function Drawer
1614
1680
  }
1615
1681
  store.setOpen(false, useButton.createChangeEventDetails(useButton.swipe, event, swipeAreaRef.current ?? undefined));
1616
1682
  }
1683
+ function resetSwipeInteractionState() {
1684
+ swipeStartEventRef.current = null;
1685
+ openedBySwipeRef.current = false;
1686
+ closedOffsetRef.current = null;
1687
+ setSwipeActive(false);
1688
+ }
1689
+ function finishSwipeInteraction() {
1690
+ resetSwipeInteractionState();
1691
+ enableDismissAfterRelease();
1692
+ resetDragDelta();
1693
+ clearSwipeStyles();
1694
+ }
1617
1695
  const swipe = useSwipeDismiss({
1618
1696
  enabled,
1619
1697
  directions: [resolvedSwipeDirection],
@@ -1672,15 +1750,10 @@ const DrawerSwipeArea = /*#__PURE__*/React__namespace.forwardRef(function Drawer
1672
1750
  } else if (openedBySwipeRef.current) {
1673
1751
  closeDrawer(event);
1674
1752
  }
1675
- swipeStartEventRef.current = null;
1676
- openedBySwipeRef.current = false;
1677
- setSwipeActive(false);
1678
- closedOffsetRef.current = null;
1679
- enableDismissAfterRelease();
1680
- resetDragDelta();
1681
- clearSwipeStyles();
1753
+ finishSwipeInteraction();
1682
1754
  return false;
1683
- }
1755
+ },
1756
+ onCancel: finishSwipeInteraction
1684
1757
  });
1685
1758
  const swipePointerProps = swipe.getPointerProps();
1686
1759
  const swipeTouchProps = swipe.getTouchProps();
@@ -1690,10 +1763,7 @@ const DrawerSwipeArea = /*#__PURE__*/React__namespace.forwardRef(function Drawer
1690
1763
  resetSwipe();
1691
1764
  resetDragDelta();
1692
1765
  clearSwipeStyles();
1693
- setSwipeActive(false);
1694
- openedBySwipeRef.current = false;
1695
- swipeStartEventRef.current = null;
1696
- closedOffsetRef.current = null;
1766
+ resetSwipeInteractionState();
1697
1767
  }
1698
1768
  }, [clearSwipeStyles, enabled, resetDragDelta, resetSwipe]);
1699
1769
  React__namespace.useEffect(() => {
@@ -1723,6 +1793,7 @@ const DrawerSwipeArea = /*#__PURE__*/React__namespace.forwardRef(function Drawer
1723
1793
  return;
1724
1794
  }
1725
1795
  swipePointerProps.onPointerDown?.(event);
1796
+
1726
1797
  // Prevent native text selection/drag gestures from competing with swipe-open dragging.
1727
1798
  if (event.cancelable) {
1728
1799
  event.preventDefault();
@@ -1753,6 +1824,12 @@ const DrawerSwipeArea = /*#__PURE__*/React__namespace.forwardRef(function Drawer
1753
1824
  });
1754
1825
  if (process.env.NODE_ENV !== "production") DrawerSwipeArea.displayName = "DrawerSwipeArea";
1755
1826
 
1827
+ const DrawerVirtualKeyboardContext = /*#__PURE__*/React__namespace.createContext(undefined);
1828
+ if (process.env.NODE_ENV !== "production") DrawerVirtualKeyboardContext.displayName = "DrawerVirtualKeyboardContext";
1829
+ function useDrawerVirtualKeyboardContext() {
1830
+ return React__namespace.useContext(DrawerVirtualKeyboardContext);
1831
+ }
1832
+
1756
1833
  const MIN_SWIPE_THRESHOLD = 10;
1757
1834
  const FAST_SWIPE_VELOCITY = 0.5;
1758
1835
  const SNAP_VELOCITY_THRESHOLD = 0.5;
@@ -1773,14 +1850,17 @@ const DRAWER_CONTENT_SELECTOR = `[${ScrollAreaViewport.DRAWER_CONTENT_ATTRIBUTE}
1773
1850
  */
1774
1851
  const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerViewport(props, forwardedRef) {
1775
1852
  const {
1776
- className,
1777
1853
  render,
1854
+ className,
1855
+ style,
1778
1856
  children,
1779
1857
  ...elementProps
1780
1858
  } = props;
1781
1859
  const {
1782
1860
  store
1783
1861
  } = ScrollAreaViewport.useDialogRootContext();
1862
+ const popupRef = store.context.popupRef;
1863
+ const backdropRef = store.context.backdropRef;
1784
1864
  const {
1785
1865
  swipeDirection,
1786
1866
  notifyParentSwipingChange,
@@ -1789,17 +1869,6 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
1789
1869
  snapToSequentialPoints
1790
1870
  } = ScrollAreaViewport.useDrawerRootContext();
1791
1871
  const providerContext = ScrollAreaViewport.useDrawerProviderContext();
1792
- const visualStateStore = providerContext?.visualStateStore;
1793
- const open = store.useState('open');
1794
- const mounted = store.useState('mounted');
1795
- const nested = store.useState('nested');
1796
- const nestedOpenDialogCount = store.useState('nestedOpenDialogCount');
1797
- const viewportElement = store.useState('viewportElement');
1798
- const popupElementState = store.useState('popupElement');
1799
- const nestedDrawerOpen = nestedOpenDialogCount > 0;
1800
- const scrollAxis = swipeDirection === 'left' || swipeDirection === 'right' ? 'horizontal' : 'vertical';
1801
- const isVerticalScrollAxis = scrollAxis === 'vertical';
1802
- const crossScrollAxis = isVerticalScrollAxis ? 'horizontal' : 'vertical';
1803
1872
  const {
1804
1873
  snapPoints,
1805
1874
  resolvedSnapPoints,
@@ -1808,15 +1877,28 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
1808
1877
  setActiveSnapPoint,
1809
1878
  popupHeight
1810
1879
  } = ScrollAreaViewport.useDrawerSnapPoints();
1880
+ const open = store.useState('open');
1881
+ const mounted = store.useState('mounted');
1882
+ const nested = store.useState('nested');
1883
+ const nestedOpenDrawerCount = store.useState('nestedOpenDrawerCount');
1884
+ const viewportElement = store.useState('viewportElement');
1885
+ const popupElementState = store.useState('popupElement');
1886
+ const visualStateStore = providerContext?.visualStateStore;
1887
+ const nestedDrawerOpen = nestedOpenDrawerCount > 0;
1888
+ const scrollAxis = swipeDirection === 'left' || swipeDirection === 'right' ? 'horizontal' : 'vertical';
1889
+ const isVerticalScrollAxis = scrollAxis === 'vertical';
1890
+ const crossScrollAxis = isVerticalScrollAxis ? 'horizontal' : 'vertical';
1811
1891
  const [swipeRelease, setSwipeRelease] = React__namespace.useState(null);
1812
1892
  const pendingSwipeCloseSnapPointRef = React__namespace.useRef(undefined);
1813
1893
  const resetSwipeRef = React__namespace.useRef(null);
1814
1894
  const controlledDismissFrame = useButton.useAnimationFrame();
1895
+ const swipingRef = React__namespace.useRef(false);
1815
1896
  const nestedSwipeActiveRef = React__namespace.useRef(false);
1816
1897
  const lastPointerTypeRef = React__namespace.useRef('');
1817
1898
  const ignoreNextTouchStartFromPenRef = React__namespace.useRef(false);
1818
1899
  const ignoreTouchSwipeRef = React__namespace.useRef(false);
1819
1900
  const touchScrollStateRef = React__namespace.useRef(null);
1901
+ const virtualKeyboard = useDrawerVirtualKeyboardContext();
1820
1902
  const snapPointRange = React__namespace.useMemo(() => {
1821
1903
  if (!snapPoints || snapPoints.length < 2) {
1822
1904
  return null;
@@ -1860,13 +1942,20 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
1860
1942
  return [swipeDirection];
1861
1943
  }, [snapPoints, swipeDirection]);
1862
1944
  const setSwipeDismissed = useButton.useStableCallback(dismissed => {
1863
- setSwipeDismissedElements(store.context.popupRef.current, store.context.backdropRef.current, dismissed);
1945
+ setSwipeDismissedElements(popupRef.current, backdropRef.current, dismissed);
1864
1946
  });
1865
1947
  const clearSwipeRelease = useButton.useStableCallback(() => {
1866
1948
  setSwipeDismissed(false);
1867
- store.context.popupRef.current?.removeAttribute(useButton.TransitionStatusDataAttributes.endingStyle);
1949
+ popupRef.current?.removeAttribute(useButton.TransitionStatusDataAttributes.endingStyle);
1868
1950
  setSwipeRelease(null);
1869
1951
  });
1952
+ const finishNestedSwipe = useButton.useStableCallback(() => {
1953
+ if (!nestedSwipeActiveRef.current) {
1954
+ return;
1955
+ }
1956
+ nestedSwipeActiveRef.current = false;
1957
+ notifyParentSwipingChange?.(false);
1958
+ });
1870
1959
  const applySwipeProgress = useButton.useStableCallback(({
1871
1960
  resolvedProgress,
1872
1961
  shouldTrackProgress,
@@ -1874,15 +1963,18 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
1874
1963
  }) => {
1875
1964
  const isActive = open && !nested && shouldTrackProgress;
1876
1965
  const swipeProgress = isActive ? resolvedProgress : 0;
1966
+ const nestedSwipeProgress = open && shouldTrackProgress ? resolvedProgress : 0;
1877
1967
  if (notifyParent && notifyParentSwipeProgressChange) {
1878
- const nestedSwipeProgress = open && shouldTrackProgress ? resolvedProgress : 0;
1879
1968
  notifyParentSwipeProgressChange(nestedSwipeProgress);
1969
+ if (nestedSwipeProgress <= 0) {
1970
+ finishNestedSwipe();
1971
+ }
1880
1972
  }
1881
1973
  visualStateStore?.set({
1882
1974
  swipeProgress,
1883
1975
  frontmostHeight: swipeProgress > 0 ? frontmostHeight : 0
1884
1976
  });
1885
- const backdropElement = store.context.backdropRef.current;
1977
+ const backdropElement = backdropRef.current;
1886
1978
  if (!backdropElement) {
1887
1979
  return;
1888
1980
  }
@@ -1977,7 +2069,7 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
1977
2069
  if ('touches' in event || 'pointerType' in event && event.pointerType === 'touch') {
1978
2070
  return;
1979
2071
  }
1980
- const popupElement = store.context.popupRef.current;
2072
+ const popupElement = popupRef.current;
1981
2073
  if (!popupElement) {
1982
2074
  return;
1983
2075
  }
@@ -1994,10 +2086,10 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
1994
2086
  selection.removeAllRanges();
1995
2087
  },
1996
2088
  onSwipingChange(swiping) {
2089
+ swipingRef.current = swiping;
1997
2090
  setBackdropSwipingAttribute(store.context.backdropRef.current, swiping);
1998
- if (!swiping) {
1999
- nestedSwipeActiveRef.current = false;
2000
- notifyParentSwipingChange?.(false);
2091
+ if (!swiping && !notifyParentSwipeProgressChange) {
2092
+ finishNestedSwipe();
2001
2093
  }
2002
2094
  },
2003
2095
  swipeThreshold({
@@ -2028,9 +2120,16 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
2028
2120
  },
2029
2121
  onProgress(progress, details) {
2030
2122
  updateNestedSwipeActive(details);
2123
+ const hasSnapPoints = Boolean(snapPoints && snapPoints.length > 0);
2124
+ if (swipingRef.current && swipeDirection === 'down' && hasSnapPoints && details && Number.isFinite(details.deltaY)) {
2125
+ const popupElement = store.context.popupRef.current;
2126
+ if (popupElement) {
2127
+ popupElement.style.removeProperty('transform');
2128
+ popupElement.style.setProperty(ScrollAreaViewport.DrawerPopupCssVars.swipeMovementY, `${ScrollAreaViewport.getSnapPointSwipeMovement(activeSnapPointOffset ?? 0, details.deltaY)}px`);
2129
+ }
2130
+ }
2031
2131
  const currentDirection = details?.direction ?? swipe.swipeDirection;
2032
2132
  const isDismissSwipe = currentDirection === undefined || currentDirection === swipeDirection;
2033
- const hasSnapPoints = Boolean(snapPoints && snapPoints.length > 0);
2034
2133
  const isVerticalSwipe = swipeDirection === 'down' || swipeDirection === 'up';
2035
2134
  const shouldTrackProgress = hasSnapPoints && isVerticalSwipe || !hasSnapPoints || swipeDirection === 'left' || swipeDirection === 'right' || isDismissSwipe;
2036
2135
  let resolvedProgress = progress;
@@ -2079,7 +2178,7 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
2079
2178
  if (!popupElement) {
2080
2179
  return;
2081
2180
  }
2082
- notifyParentSwipingChange?.(false);
2181
+ finishNestedSwipe();
2083
2182
  setSwipeDismissed(true);
2084
2183
  popupElement.style.removeProperty('transition');
2085
2184
  popupElement.setAttribute(useButton.TransitionStatusDataAttributes.endingStyle, '');
@@ -2286,7 +2385,10 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
2286
2385
  });
2287
2386
  const swipePointerProps = swipe.getPointerProps();
2288
2387
  const swipeTouchProps = swipe.getTouchProps();
2289
- const resetSwipe = swipe.reset;
2388
+ const {
2389
+ moveNative: moveSwipeNative,
2390
+ reset: resetSwipe
2391
+ } = swipe;
2290
2392
  resetSwipeRef.current = resetSwipe;
2291
2393
  React__namespace.useEffect(() => {
2292
2394
  const rootElement = viewportElement ?? popupElementState;
@@ -2297,6 +2399,11 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
2297
2399
  const doc = useButton.ownerDocument(resolvedRootElement);
2298
2400
  const win = floatingUi_utils_dom.getWindow(doc);
2299
2401
  function handleNativeTouchMove(event) {
2402
+ // The virtual keyboard provider observes the move to tell a tap apart from a drag.
2403
+ // It must run even when the swipe gesture below claims the event with
2404
+ // `stopPropagation()`, which would otherwise prevent React's delegated handlers
2405
+ // (and the provider) from ever seeing the move.
2406
+ virtualKeyboard?.onTouchMove(event);
2300
2407
  if (ignoreTouchSwipeRef.current) {
2301
2408
  return;
2302
2409
  }
@@ -2333,6 +2440,10 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
2333
2440
  if (event.cancelable) {
2334
2441
  event.preventDefault();
2335
2442
  }
2443
+ // Claim the gesture before React's delegated touch handlers see it; dispatching the
2444
+ // move through React re-rasterizes the popup content on every frame.
2445
+ event.stopPropagation();
2446
+ moveSwipeNative(event, resolvedRootElement);
2336
2447
  updateTouchScrollPosition(touchState, touch);
2337
2448
  return;
2338
2449
  }
@@ -2343,6 +2454,7 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
2343
2454
  if (event.cancelable) {
2344
2455
  event.preventDefault();
2345
2456
  }
2457
+ event.stopPropagation();
2346
2458
  updateTouchScrollPosition(touchState, touch);
2347
2459
  return;
2348
2460
  }
@@ -2362,18 +2474,17 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
2362
2474
  event.preventDefault();
2363
2475
  }
2364
2476
  }
2477
+ if (touchState.allowSwipe === true) {
2478
+ event.stopPropagation();
2479
+ moveSwipeNative(event, resolvedRootElement);
2480
+ }
2365
2481
  updateTouchScrollPosition(touchState, touch);
2366
2482
  }
2367
- doc.addEventListener('touchmove', handleNativeTouchMove, {
2483
+ return useButton.addEventListener(doc, 'touchmove', handleNativeTouchMove, {
2368
2484
  passive: false,
2369
2485
  capture: true
2370
2486
  });
2371
- return () => {
2372
- doc.removeEventListener('touchmove', handleNativeTouchMove, {
2373
- capture: true
2374
- });
2375
- };
2376
- }, [mounted, nestedDrawerOpen, open, popupElementState, isVerticalScrollAxis, scrollAxis, swipeDirection, viewportElement]);
2487
+ }, [mounted, nestedDrawerOpen, open, popupElementState, isVerticalScrollAxis, scrollAxis, swipeDirection, moveSwipeNative, viewportElement, virtualKeyboard]);
2377
2488
  React__namespace.useEffect(() => {
2378
2489
  if (!snapPointRange || swipe.swiping) {
2379
2490
  return;
@@ -2403,23 +2514,30 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
2403
2514
  }
2404
2515
  }, [clearSwipeRelease, open, resetSwipe]);
2405
2516
  React__namespace.useEffect(() => {
2517
+ const backdropElement = backdropRef.current;
2406
2518
  return () => {
2407
2519
  visualStateStore?.set({
2408
2520
  swipeProgress: 0,
2409
2521
  frontmostHeight: 0
2410
2522
  });
2411
- setBackdropSwipingAttribute(store.context.backdropRef.current, false);
2412
- notifyParentSwipingChange?.(false);
2523
+ setBackdropSwipingAttribute(backdropElement, false);
2524
+ // `data-swiping` is set on whichever backdrop is current when a swipe starts, which can
2525
+ // differ from the captured element if the backdrop mounted late or changed identity.
2526
+ // Reading the live ref here is intentional so the current backdrop is cleared too.
2527
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2528
+ const currentBackdrop = backdropRef.current;
2529
+ if (currentBackdrop !== backdropElement) {
2530
+ setBackdropSwipingAttribute(currentBackdrop, false);
2531
+ }
2532
+ finishNestedSwipe();
2413
2533
  };
2414
- }, [notifyParentSwipingChange, store, visualStateStore]);
2534
+ }, [backdropRef, finishNestedSwipe, visualStateStore]);
2415
2535
  const swipeProviderValue = React__namespace.useMemo(() => ({
2416
2536
  swiping: swipe.swiping,
2417
2537
  getDragStyles: swipe.getDragStyles,
2418
2538
  swipeStrength: swipeRelease ?? null,
2419
- setSwipeDismissed(dismissed) {
2420
- setSwipeDismissedElements(store.context.popupRef.current, store.context.backdropRef.current, dismissed);
2421
- }
2422
- }), [store, swipe.getDragStyles, swipe.swiping, swipeRelease]);
2539
+ setSwipeDismissed
2540
+ }), [setSwipeDismissed, swipe.getDragStyles, swipe.swiping, swipeRelease]);
2423
2541
  function resetTouchTrackingState() {
2424
2542
  ignoreTouchSwipeRef.current = false;
2425
2543
  touchScrollStateRef.current = null;
@@ -2429,6 +2547,7 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
2429
2547
  return /*#__PURE__*/jsxRuntime.jsx(DrawerDescription.DialogViewport, {
2430
2548
  ref: forwardedRef,
2431
2549
  className: className,
2550
+ style: style,
2432
2551
  render: render,
2433
2552
  ...useRenderElement.mergeProps(elementProps, {
2434
2553
  onPointerDown(event) {
@@ -2495,16 +2614,23 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
2495
2614
  }
2496
2615
  const doc = useButton.ownerDocument(event.currentTarget);
2497
2616
  const elementAtPoint = getElementAtPoint(doc, touch.clientX, touch.clientY);
2617
+ const rootElement = viewportElement ?? popupElementState;
2618
+ const eventTarget = useButton.getTarget(event.nativeEvent);
2619
+ const target = floatingUi_utils_dom.isElement(eventTarget) ? eventTarget : null;
2620
+ if (rootElement && target && !useButton.contains(rootElement, target)) {
2621
+ ignoreTouchSwipeRef.current = true;
2622
+ touchScrollStateRef.current = null;
2623
+ return;
2624
+ }
2625
+ virtualKeyboard?.onTouchStart(event);
2498
2626
  ignoreTouchSwipeRef.current = isSwipeIgnoredTarget(elementAtPoint);
2499
2627
  if (ignoreTouchSwipeRef.current) {
2500
2628
  touchScrollStateRef.current = null;
2501
2629
  return;
2502
2630
  }
2503
- const rootElement = viewportElement ?? popupElementState;
2504
- const target = floatingUi_utils_dom.isElement(event.target) ? event.target : null;
2505
2631
  let scrollTarget = null;
2506
2632
  let hasCrossAxisScrollableContent = false;
2507
- if (rootElement && target && useButton.contains(rootElement, target)) {
2633
+ if (rootElement && target) {
2508
2634
  scrollTarget = findScrollableTouchTarget(target, rootElement, scrollAxis);
2509
2635
  hasCrossAxisScrollableContent = findScrollableTouchTarget(target, rootElement, crossScrollAxis) != null;
2510
2636
  }
@@ -2525,27 +2651,13 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
2525
2651
  };
2526
2652
  swipeTouchProps.onTouchStart?.(event);
2527
2653
  },
2528
- onTouchMove(event) {
2529
- if (ignoreTouchSwipeRef.current) {
2530
- return;
2531
- }
2532
- if (isReactTouchEventOnRangeInput(event)) {
2533
- return;
2534
- }
2535
- const touchState = touchScrollStateRef.current;
2536
- if (touchState?.preserveNativeCrossAxisScroll) {
2537
- return;
2538
- }
2539
- if (touchState?.allowSwipe === false || touchState?.scrollTarget != null && !touchState.allowSwipe) {
2540
- return;
2541
- }
2542
- swipeTouchProps.onTouchMove?.(event);
2543
- },
2544
2654
  onTouchEnd(event) {
2655
+ virtualKeyboard?.onTouchEnd(event);
2545
2656
  resetTouchTrackingState();
2546
2657
  swipeTouchProps.onTouchEnd?.(event);
2547
2658
  },
2548
2659
  onTouchCancel(event) {
2660
+ virtualKeyboard?.onTouchCancel();
2549
2661
  resetTouchTrackingState();
2550
2662
  swipeTouchProps.onTouchCancel?.(event);
2551
2663
  },
@@ -2569,149 +2681,617 @@ function setSwipeDismissedElements(popupElement, backdropElement, dismissed) {
2569
2681
  popupElement?.removeAttribute(ScrollAreaViewport.DrawerPopupDataAttributes.swipeDismiss);
2570
2682
  backdropElement?.removeAttribute(ScrollAreaViewport.DrawerPopupDataAttributes.swipeDismiss);
2571
2683
  }
2572
- function setBackdropSwipingAttribute(backdropElement, swiping) {
2573
- if (!backdropElement) {
2574
- return;
2575
- }
2576
- if (swiping) {
2577
- backdropElement.setAttribute(ScrollAreaViewport.DrawerPopupDataAttributes.swiping, '');
2578
- return;
2684
+ function setBackdropSwipingAttribute(backdropElement, swiping) {
2685
+ if (!backdropElement) {
2686
+ return;
2687
+ }
2688
+ if (swiping) {
2689
+ backdropElement.setAttribute(ScrollAreaViewport.DrawerPopupDataAttributes.swiping, '');
2690
+ return;
2691
+ }
2692
+ backdropElement.removeAttribute(ScrollAreaViewport.DrawerPopupDataAttributes.swiping);
2693
+ }
2694
+ function isSwipeIgnoredTarget(target) {
2695
+ return Boolean(target?.closest(useScrollLock.BASE_UI_SWIPE_IGNORE_SELECTOR));
2696
+ }
2697
+ function isDrawerContentTarget(target) {
2698
+ return Boolean(target?.closest(DRAWER_CONTENT_SELECTOR));
2699
+ }
2700
+ function getBaseSwipeThreshold(element, direction) {
2701
+ const size = direction === 'left' || direction === 'right' ? element.offsetWidth : element.offsetHeight;
2702
+ return Math.max(size * 0.5, MIN_SWIPE_THRESHOLD);
2703
+ }
2704
+ function isRangeInput(target, win) {
2705
+ return target instanceof win.HTMLInputElement && target.type === 'range';
2706
+ }
2707
+ function isTextSelectionControl(target) {
2708
+ if (!floatingUi_utils_dom.isElement(target)) {
2709
+ return false;
2710
+ }
2711
+ return target.tagName === 'INPUT' || target.tagName === 'TEXTAREA';
2712
+ }
2713
+ function hasExpandedSelectionWithinTarget(selection, target) {
2714
+ const anchorElement = floatingUi_utils_dom.isElement(selection.anchorNode) ? selection.anchorNode : selection.anchorNode?.parentElement;
2715
+ const focusElement = floatingUi_utils_dom.isElement(selection.focusNode) ? selection.focusNode : selection.focusNode?.parentElement;
2716
+ return selection.containsNode(target, true) || useButton.contains(target, anchorElement) || useButton.contains(target, focusElement);
2717
+ }
2718
+ function shouldIgnoreSwipeForTextSelection(doc, rootElement) {
2719
+ const activeEl = useButton.activeElement(doc);
2720
+ const activeElementWithinRoot = Boolean(activeEl && useButton.contains(rootElement, activeEl));
2721
+ if (activeElementWithinRoot && isTextSelectionControl(activeEl)) {
2722
+ const {
2723
+ selectionStart,
2724
+ selectionEnd
2725
+ } = activeEl;
2726
+ if (selectionStart != null && selectionEnd != null && selectionStart < selectionEnd) {
2727
+ return true;
2728
+ }
2729
+ }
2730
+ const selection = doc.getSelection?.();
2731
+ if (!selection || selection.isCollapsed) {
2732
+ return false;
2733
+ }
2734
+ return hasExpandedSelectionWithinTarget(selection, rootElement);
2735
+ }
2736
+ function isEventOnRangeInput(event, win) {
2737
+ const composedPath = event.composedPath();
2738
+ if (composedPath) {
2739
+ return composedPath.some(pathTarget => isRangeInput(pathTarget, win));
2740
+ }
2741
+ return isRangeInput(useButton.getTarget(event), win);
2742
+ }
2743
+ function isReactTouchEventOnRangeInput(event) {
2744
+ return isEventOnRangeInput(event.nativeEvent, floatingUi_utils_dom.getWindow(event.currentTarget));
2745
+ }
2746
+ function updateTouchScrollPosition(touchState, touch) {
2747
+ touchState.lastX = touch.clientX;
2748
+ touchState.lastY = touch.clientY;
2749
+ }
2750
+ function preserveNativeCrossAxisScrollOnMove(touchState, touch, isVerticalScrollAxis) {
2751
+ if (touchState.preserveNativeCrossAxisScroll) {
2752
+ return true;
2753
+ }
2754
+ if (touchState.allowSwipe === true || !touchState.hasCrossAxisScrollableContent) {
2755
+ return false;
2756
+ }
2757
+ const drawerAxisGestureDelta = isVerticalScrollAxis ? touch.clientY - touchState.startY : touch.clientX - touchState.startX;
2758
+ const crossAxisGestureDelta = isVerticalScrollAxis ? touch.clientX - touchState.startX : touch.clientY - touchState.startY;
2759
+ const absDrawerAxisGestureDelta = Math.abs(drawerAxisGestureDelta);
2760
+ const absCrossAxisGestureDelta = Math.abs(crossAxisGestureDelta);
2761
+ if (absCrossAxisGestureDelta < 6 || absCrossAxisGestureDelta <= absDrawerAxisGestureDelta + 2) {
2762
+ return false;
2763
+ }
2764
+ touchState.preserveNativeCrossAxisScroll = true;
2765
+ return true;
2766
+ }
2767
+ function hasScrollableContentOnAxis(scrollTarget, axis) {
2768
+ return axis === 'vertical' ? scrollTarget.scrollHeight > scrollTarget.clientHeight : scrollTarget.scrollWidth > scrollTarget.clientWidth;
2769
+ }
2770
+ function getScrollMetrics(scrollTarget, axis) {
2771
+ if (axis === 'vertical') {
2772
+ const max = Math.max(0, scrollTarget.scrollHeight - scrollTarget.clientHeight);
2773
+ return {
2774
+ offset: scrollTarget.scrollTop,
2775
+ max
2776
+ };
2777
+ }
2778
+ const max = Math.max(0, scrollTarget.scrollWidth - scrollTarget.clientWidth);
2779
+ return {
2780
+ offset: scrollTarget.scrollLeft,
2781
+ max
2782
+ };
2783
+ }
2784
+ function isAtSwipeStartEdge(scrollTarget, axis, direction) {
2785
+ const {
2786
+ offset,
2787
+ max
2788
+ } = getScrollMetrics(scrollTarget, axis);
2789
+ const dismissFromStartEdge = shouldDismissFromStartEdge(direction, axis);
2790
+ if (dismissFromStartEdge === null) {
2791
+ return false;
2792
+ }
2793
+ return dismissFromStartEdge ? offset <= 0 : offset >= max;
2794
+ }
2795
+ function canSwipeFromScrollEdgeOnMove(scrollTarget, axis, direction, delta) {
2796
+ const {
2797
+ offset,
2798
+ max
2799
+ } = getScrollMetrics(scrollTarget, axis);
2800
+ const dismissFromStartEdge = shouldDismissFromStartEdge(direction, axis);
2801
+ if (dismissFromStartEdge === null) {
2802
+ return false;
2803
+ }
2804
+ const movingTowardDismiss = dismissFromStartEdge ? delta > 0 : delta < 0;
2805
+ if (!movingTowardDismiss) {
2806
+ return false;
2807
+ }
2808
+ return dismissFromStartEdge ? offset <= 0 : offset >= max;
2809
+ }
2810
+ function shouldDismissFromStartEdge(direction, axis) {
2811
+ if (axis === 'vertical') {
2812
+ if (direction === 'down') {
2813
+ return true;
2814
+ }
2815
+ if (direction === 'up') {
2816
+ return false;
2817
+ }
2818
+ return null;
2819
+ }
2820
+ if (direction === 'right') {
2821
+ return true;
2822
+ }
2823
+ if (direction === 'left') {
2824
+ return false;
2825
+ }
2826
+ return null;
2827
+ }
2828
+
2829
+ let DrawerViewportCssVars = /*#__PURE__*/function (DrawerViewportCssVars) {
2830
+ /**
2831
+ * The software keyboard inset, measured from the bottom edge of the layout viewport.
2832
+ * Present only when the drawer is wrapped in `Drawer.VirtualKeyboardProvider`.
2833
+ * @type {CSS length}
2834
+ */
2835
+ DrawerViewportCssVars["keyboardInset"] = "--drawer-keyboard-inset";
2836
+ return DrawerViewportCssVars;
2837
+ }({});
2838
+
2839
+ const KEYBOARD_RESIZE_THRESHOLD = 60;
2840
+ const KEYBOARD_VISIBILITY_MARGIN = 16;
2841
+ // Extra breathing room (px) added below the focused field, on top of its measured
2842
+ // keyboard overlap, so the field can be scrolled clear of the keyboard instead of
2843
+ // ending up flush against it. Only applied when there is actual overlap.
2844
+ const KEYBOARD_SCROLL_SLACK = 48;
2845
+ const INPUT_TAP_MOVE_THRESHOLD = 10;
2846
+ const INPUT_TAP_HIT_SLOP = 16;
2847
+ const KEYBOARD_INPUT_TYPES = new Set(['email', 'number', 'password', 'search', 'tel', 'text', 'url']);
2848
+
2849
+ // Snapshot of a scroll container's relevant styles taken before keyboard slack is
2850
+ // applied. The string fields are the exact inline values to restore on cleanup;
2851
+ // the parsed numbers are the computed baselines that slack is added on top of.
2852
+
2853
+ // Returned by the point-based resolver when the lift point lands on another
2854
+ // interactive/label element. It signals that the tap was intentionally rejected, so the
2855
+ // caller must NOT fall back to the touchstart target (`touchend.target` stays at the
2856
+ // touchstart node on mobile) — doing so would steal a tap meant for that element.
2857
+ const KEYBOARD_TAP_BLOCKED = Symbol('KeyboardTapBlocked');
2858
+
2859
+ /**
2860
+ * Provides keyboard-aware focus and scroll handling for bottom-sheet drawers with form fields.
2861
+ *
2862
+ * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)
2863
+ */
2864
+ function DrawerVirtualKeyboardProvider(props) {
2865
+ const {
2866
+ children
2867
+ } = props;
2868
+ const {
2869
+ store
2870
+ } = ScrollAreaViewport.useDialogRootContext();
2871
+ const open = store.useState('open');
2872
+ const mounted = store.useState('mounted');
2873
+ const nestedOpenDialogCount = store.useState('nestedOpenDialogCount');
2874
+ const viewportElement = store.useState('viewportElement');
2875
+
2876
+ // The provider requires a `<Drawer.Viewport>` to act as the measurement and containment
2877
+ // root and to host the keyboard inset variable; `<Drawer.Popup>` already warns when the
2878
+ // viewport is missing, so there is no need to fall back to the popup element here.
2879
+ const rootElement = viewportElement;
2880
+ const nestedDrawerOpen = nestedOpenDialogCount > 0;
2881
+ const pendingKeyboardFocusMovedRef = React__namespace.useRef(false);
2882
+ const keyboardTouchStartRef = React__namespace.useRef(null);
2883
+ const focusedKeyboardTargetRef = React__namespace.useRef(null);
2884
+ const keyboardScrollAdjustmentRef = React__namespace.useRef(null);
2885
+ const keyboardFocusFrame = useButton.useAnimationFrame();
2886
+ const restoreKeyboardScrollAdjustment = useButton.useStableCallback(() => {
2887
+ const adjustment = keyboardScrollAdjustmentRef.current;
2888
+ if (!adjustment) {
2889
+ return;
2890
+ }
2891
+ adjustment.element.style.overflowAnchor = adjustment.overflowAnchor;
2892
+ adjustment.element.style.paddingBottom = adjustment.paddingBottom;
2893
+ adjustment.element.style.scrollPaddingBottom = adjustment.scrollPaddingBottom;
2894
+ keyboardScrollAdjustmentRef.current = null;
2895
+ });
2896
+ const setKeyboardScrollSlack = useButton.useStableCallback((element, slack) => {
2897
+ const roundedSlack = Math.max(0, Math.ceil(slack));
2898
+ let adjustment = keyboardScrollAdjustmentRef.current;
2899
+ if (adjustment && !adjustment.element.isConnected) {
2900
+ restoreKeyboardScrollAdjustment();
2901
+ adjustment = null;
2902
+ }
2903
+ if (roundedSlack === 0) {
2904
+ restoreKeyboardScrollAdjustment();
2905
+ return;
2906
+ }
2907
+ if (adjustment && adjustment.element !== element) {
2908
+ restoreKeyboardScrollAdjustment();
2909
+ adjustment = null;
2910
+ }
2911
+ if (!adjustment) {
2912
+ const styles = floatingUi_utils_dom.getComputedStyle(element);
2913
+ adjustment = {
2914
+ element,
2915
+ overflowAnchor: element.style.overflowAnchor,
2916
+ paddingBottom: element.style.paddingBottom,
2917
+ scrollPaddingBottom: element.style.scrollPaddingBottom,
2918
+ computedPaddingBottom: Number.parseFloat(styles.paddingBottom) || 0,
2919
+ computedScrollPaddingBottom: Number.parseFloat(styles.scrollPaddingBottom) || 0
2920
+ };
2921
+ keyboardScrollAdjustmentRef.current = adjustment;
2922
+ }
2923
+ element.style.overflowAnchor = 'none';
2924
+ element.style.paddingBottom = `${adjustment.computedPaddingBottom + roundedSlack}px`;
2925
+ element.style.scrollPaddingBottom = `${adjustment.computedScrollPaddingBottom + KEYBOARD_VISIBILITY_MARGIN}px`;
2926
+ });
2927
+ const animateKeyboardScroll = useButton.useStableCallback((element, scrollTop) => {
2928
+ const win = floatingUi_utils_dom.getWindow(element);
2929
+ const behavior = win.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches ? 'auto' : 'smooth';
2930
+ element.scrollTo({
2931
+ top: scrollTop,
2932
+ behavior
2933
+ });
2934
+ });
2935
+ const resetTouchTrackingState = useButton.useStableCallback(() => {
2936
+ pendingKeyboardFocusMovedRef.current = false;
2937
+ keyboardTouchStartRef.current = null;
2938
+ });
2939
+ React__namespace.useEffect(() => {
2940
+ if (!mounted || !open) {
2941
+ focusedKeyboardTargetRef.current = null;
2942
+ restoreKeyboardScrollAdjustment();
2943
+ keyboardFocusFrame.cancel();
2944
+ return undefined;
2945
+ }
2946
+ if (!rootElement) {
2947
+ restoreKeyboardScrollAdjustment();
2948
+ return undefined;
2949
+ }
2950
+ const doc = useButton.ownerDocument(rootElement);
2951
+ const win = floatingUi_utils_dom.getWindow(rootElement);
2952
+ const visualViewport = win.visualViewport;
2953
+ const setDrawerKeyboardInset = inset => {
2954
+ rootElement.style.setProperty(DrawerViewportCssVars.keyboardInset, `${Math.max(0, Math.ceil(inset))}px`);
2955
+ };
2956
+ const resetDrawerKeyboardInset = () => {
2957
+ setDrawerKeyboardInset(0);
2958
+ };
2959
+ const clearFocusedKeyboardTarget = () => {
2960
+ focusedKeyboardTargetRef.current = null;
2961
+ resetDrawerKeyboardInset();
2962
+ restoreKeyboardScrollAdjustment();
2963
+ keyboardFocusFrame.cancel();
2964
+ };
2965
+ const alignFocusedKeyboardTarget = () => {
2966
+ const target = focusedKeyboardTargetRef.current;
2967
+ // If the focused field is removed from the DOM without firing `focusout` (e.g. it is
2968
+ // conditionally rendered away), any applied scroll slack is restored here on the next
2969
+ // focus/viewport event or when the drawer closes. This self-corrects rather than
2970
+ // tracking each field's lifecycle.
2971
+ if (nestedDrawerOpen || !target || !useButton.contains(rootElement, target)) {
2972
+ resetDrawerKeyboardInset();
2973
+ restoreKeyboardScrollAdjustment();
2974
+ return;
2975
+ }
2976
+ const keyboardViewport = getKeyboardVisualViewport(win);
2977
+ if (!keyboardViewport) {
2978
+ resetDrawerKeyboardInset();
2979
+ restoreKeyboardScrollAdjustment();
2980
+ return;
2981
+ }
2982
+ setDrawerKeyboardInset(getDrawerKeyboardInset(win, keyboardViewport));
2983
+ const scrollTarget = findKeyboardScrollTarget(target, rootElement);
2984
+ if (!scrollTarget) {
2985
+ restoreKeyboardScrollAdjustment();
2986
+ return;
2987
+ }
2988
+ if (!scrollTarget.isConnected || !useButton.contains(rootElement, scrollTarget)) {
2989
+ resetDrawerKeyboardInset();
2990
+ restoreKeyboardScrollAdjustment();
2991
+ return;
2992
+ }
2993
+ const scrollTargetRect = scrollTarget.getBoundingClientRect();
2994
+ const clippedBottom = Math.min(scrollTargetRect.bottom, keyboardViewport.bottom);
2995
+ const overlap = Math.max(0, scrollTargetRect.bottom - keyboardViewport.bottom);
2996
+ setKeyboardScrollSlack(scrollTarget, overlap > 0 ? overlap + KEYBOARD_SCROLL_SLACK : 0);
2997
+ const maxScrollTop = Math.max(0, scrollTarget.scrollHeight - scrollTarget.clientHeight);
2998
+ if (maxScrollTop <= 0) {
2999
+ return;
3000
+ }
3001
+ const clippedTop = Math.max(scrollTargetRect.top, keyboardViewport.top);
3002
+ const visibleTop = clippedTop + KEYBOARD_VISIBILITY_MARGIN;
3003
+ const visibleBottom = clippedBottom - KEYBOARD_VISIBILITY_MARGIN;
3004
+ if (visibleBottom <= visibleTop) {
3005
+ return;
3006
+ }
3007
+ const targetRect = target.getBoundingClientRect();
3008
+ const targetCenter = (targetRect.top + targetRect.bottom) / 2;
3009
+ const visibleCenter = (visibleTop + visibleBottom) / 2;
3010
+ const nextScrollTop = scrollTarget.scrollTop + targetCenter - visibleCenter;
3011
+ animateKeyboardScroll(scrollTarget, clamp.clamp(nextScrollTop, 0, maxScrollTop));
3012
+ };
3013
+ const scheduleKeyboardFocusAlignment = () => {
3014
+ keyboardFocusFrame.request(alignFocusedKeyboardTarget);
3015
+ };
3016
+ const captureFocusedKeyboardTarget = eventTarget => {
3017
+ if (nestedDrawerOpen) {
3018
+ return false;
3019
+ }
3020
+
3021
+ // Resolve through the same path as taps so contentEditable hosts (and labelled
3022
+ // controls) are normalized identically for the focus and touch paths.
3023
+ const target = resolveKeyboardInputTarget(eventTarget);
3024
+ if (!target || !useButton.contains(rootElement, target)) {
3025
+ return false;
3026
+ }
3027
+ focusedKeyboardTargetRef.current = target;
3028
+ return true;
3029
+ };
3030
+ const handleFocusIn = event => {
3031
+ if (captureFocusedKeyboardTarget(useButton.getTarget(event))) {
3032
+ scheduleKeyboardFocusAlignment();
3033
+ }
3034
+ };
3035
+ const handleFocusOut = event => {
3036
+ if (captureFocusedKeyboardTarget(event.relatedTarget)) {
3037
+ scheduleKeyboardFocusAlignment();
3038
+ return;
3039
+ }
3040
+ clearFocusedKeyboardTarget();
3041
+ };
3042
+ const handleViewportUpdate = () => {
3043
+ if (focusedKeyboardTargetRef.current || captureFocusedKeyboardTarget(useButton.activeElement(doc))) {
3044
+ scheduleKeyboardFocusAlignment();
3045
+ }
3046
+ };
3047
+ const cleanupListeners = [];
3048
+ if (visualViewport) {
3049
+ cleanupListeners.push(useButton.addEventListener(visualViewport, 'resize', handleViewportUpdate), useButton.addEventListener(visualViewport, 'scroll', handleViewportUpdate));
3050
+ }
3051
+ cleanupListeners.push(useButton.addEventListener(doc, 'focusin', handleFocusIn, true), useButton.addEventListener(doc, 'focusout', handleFocusOut, true));
3052
+ if (captureFocusedKeyboardTarget(useButton.activeElement(doc))) {
3053
+ scheduleKeyboardFocusAlignment();
3054
+ }
3055
+ return () => {
3056
+ cleanupListeners.forEach(cleanup => cleanup());
3057
+ clearFocusedKeyboardTarget();
3058
+ rootElement.style.removeProperty(DrawerViewportCssVars.keyboardInset);
3059
+ };
3060
+ }, [animateKeyboardScroll, keyboardFocusFrame, mounted, nestedDrawerOpen, open, restoreKeyboardScrollAdjustment, rootElement, setKeyboardScrollSlack]);
3061
+ const onTouchStart = useButton.useStableCallback(event => {
3062
+ if (!open || !mounted || nestedDrawerOpen) {
3063
+ resetTouchTrackingState();
3064
+ return;
3065
+ }
3066
+ const touch = event.touches[0];
3067
+ if (!touch) {
3068
+ return;
3069
+ }
3070
+ pendingKeyboardFocusMovedRef.current = false;
3071
+ keyboardTouchStartRef.current = {
3072
+ x: touch.clientX,
3073
+ y: touch.clientY
3074
+ };
3075
+ });
3076
+ const onTouchMove = useButton.useStableCallback(event => {
3077
+ const touch = event.touches[0];
3078
+ const touchStart = keyboardTouchStartRef.current;
3079
+ if (!touch || !touchStart || pendingKeyboardFocusMovedRef.current) {
3080
+ return;
3081
+ }
3082
+
3083
+ // Treat the gesture as a scroll/swipe (not a tap-to-focus) once the finger
3084
+ // moves past the threshold, so we don't open the keyboard on a drag.
3085
+ if (Math.abs(touch.clientX - touchStart.x) > INPUT_TAP_MOVE_THRESHOLD || Math.abs(touch.clientY - touchStart.y) > INPUT_TAP_MOVE_THRESHOLD) {
3086
+ pendingKeyboardFocusMovedRef.current = true;
3087
+ }
3088
+ });
3089
+ const onTouchEnd = useButton.useStableCallback(event => {
3090
+ if (!open || !mounted || nestedDrawerOpen || !rootElement || !keyboardTouchStartRef.current || pendingKeyboardFocusMovedRef.current) {
3091
+ resetTouchTrackingState();
3092
+ return;
3093
+ }
3094
+ const touch = event.changedTouches[0] ?? event.touches[0];
3095
+ const doc = useButton.ownerDocument(event.currentTarget);
3096
+ const nativeEventTarget = useButton.getTarget(event.nativeEvent);
3097
+ const pointTarget = touch ? resolveKeyboardTouchTargetFromPoint(doc, touch.clientX, touch.clientY) : null;
3098
+
3099
+ // The lift point landed on another interactive/label element; let its native tap
3100
+ // through instead of stealing it for the touchstart input.
3101
+ if (pointTarget === KEYBOARD_TAP_BLOCKED) {
3102
+ resetTouchTrackingState();
3103
+ return;
3104
+ }
3105
+ const keyboardTarget = touch && (pointTarget ?? resolveKeyboardTouchTarget(nativeEventTarget));
3106
+ if (keyboardTarget && (!useButton.contains(rootElement, keyboardTarget.focusTarget) || !useButton.contains(rootElement, keyboardTarget.clickTarget))) {
3107
+ resetTouchTrackingState();
3108
+ return;
3109
+ }
3110
+ if (keyboardTarget) {
3111
+ const {
3112
+ clickTarget: keyboardClickTarget,
3113
+ focusTarget: keyboardFocusTarget
3114
+ } = keyboardTarget;
3115
+ const win = floatingUi_utils_dom.getWindow(keyboardFocusTarget);
3116
+
3117
+ // While pinch-zoomed, keyboard alignment is suspended; let native behavior
3118
+ // handle focus and caret placement instead of blurring and re-focusing.
3119
+ if (win.visualViewport && win.visualViewport.scale !== 1) {
3120
+ resetTouchTrackingState();
3121
+ return;
3122
+ }
3123
+
3124
+ // Already focused with the keyboard up: let the native tap through so it can
3125
+ // reposition the caret, rather than blurring and re-focusing the same input.
3126
+ if (useButton.activeElement(useButton.ownerDocument(keyboardFocusTarget)) === keyboardFocusTarget && isKeyboardVisualViewportOpen(win)) {
3127
+ resetTouchTrackingState();
3128
+ return;
3129
+ }
3130
+
3131
+ // iOS only opens the software keyboard when focus happens synchronously
3132
+ // inside the touch gesture.
3133
+ event.preventDefault();
3134
+ focusKeyboardInputWithoutPageScroll(keyboardFocusTarget);
3135
+ // Preventing the touchend default also suppresses the compatibility mouse
3136
+ // events, including `click`; redispatch an untrusted replacement on the
3137
+ // original tap target so click handlers still run with the tap coordinates.
3138
+ dispatchKeyboardClick(keyboardClickTarget, touch);
3139
+ resetTouchTrackingState();
3140
+ return;
3141
+ }
3142
+ resetTouchTrackingState();
3143
+ });
3144
+ const contextValue = React__namespace.useMemo(() => ({
3145
+ onTouchStart,
3146
+ onTouchMove,
3147
+ onTouchEnd,
3148
+ onTouchCancel: resetTouchTrackingState
3149
+ }), [onTouchEnd, onTouchMove, onTouchStart, resetTouchTrackingState]);
3150
+ return /*#__PURE__*/jsxRuntime.jsx(DrawerVirtualKeyboardContext.Provider, {
3151
+ value: contextValue,
3152
+ children: children
3153
+ });
3154
+ }
3155
+ function isKeyboardInputElement(element) {
3156
+ if (element.isContentEditable) {
3157
+ return true;
2579
3158
  }
2580
- backdropElement.removeAttribute(ScrollAreaViewport.DrawerPopupDataAttributes.swiping);
2581
- }
2582
- function isSwipeIgnoredTarget(target) {
2583
- return Boolean(target?.closest(InternalBackdrop.BASE_UI_SWIPE_IGNORE_SELECTOR));
2584
- }
2585
- function isDrawerContentTarget(target) {
2586
- return Boolean(target?.closest(DRAWER_CONTENT_SELECTOR));
2587
- }
2588
- function getBaseSwipeThreshold(element, direction) {
2589
- const size = direction === 'left' || direction === 'right' ? element.offsetWidth : element.offsetHeight;
2590
- return Math.max(size * 0.5, MIN_SWIPE_THRESHOLD);
2591
- }
2592
- function isRangeInput(target, win) {
2593
- return target instanceof win.HTMLInputElement && target.type === 'range';
2594
- }
2595
- function isTextSelectionControl(target) {
2596
- if (!floatingUi_utils_dom.isElement(target)) {
2597
- return false;
3159
+ const win = floatingUi_utils_dom.getWindow(element);
3160
+ if (element instanceof win.HTMLTextAreaElement || element instanceof win.HTMLInputElement && KEYBOARD_INPUT_TYPES.has(element.type)) {
3161
+ // Disabled controls can't focus or open the keyboard, so tap-to-focus must skip them —
3162
+ // otherwise the dispatched click fires handlers a native tap on a disabled control never would.
3163
+ return !element.matches(':disabled');
2598
3164
  }
2599
- return target.tagName === 'INPUT' || target.tagName === 'TEXTAREA';
2600
- }
2601
- function hasExpandedSelectionWithinTarget(selection, target) {
2602
- const anchorElement = floatingUi_utils_dom.isElement(selection.anchorNode) ? selection.anchorNode : selection.anchorNode?.parentElement;
2603
- const focusElement = floatingUi_utils_dom.isElement(selection.focusNode) ? selection.focusNode : selection.focusNode?.parentElement;
2604
- return selection.containsNode(target, true) || useButton.contains(target, anchorElement) || useButton.contains(target, focusElement);
3165
+ return false;
2605
3166
  }
2606
- function shouldIgnoreSwipeForTextSelection(doc, rootElement) {
2607
- const activeElement = doc.activeElement;
2608
- const activeElementWithinRoot = Boolean(activeElement && useButton.contains(rootElement, activeElement));
2609
- if (activeElementWithinRoot && isTextSelectionControl(activeElement)) {
2610
- const {
2611
- selectionStart,
2612
- selectionEnd
2613
- } = activeElement;
2614
- if (selectionStart != null && selectionEnd != null && selectionStart < selectionEnd) {
2615
- return true;
2616
- }
3167
+ function resolveKeyboardInputTarget(target) {
3168
+ if (!floatingUi_utils_dom.isHTMLElement(target)) {
3169
+ return null;
2617
3170
  }
2618
- const selection = doc.getSelection?.();
2619
- if (!selection || selection.isCollapsed) {
2620
- return false;
3171
+ if (isKeyboardInputElement(target)) {
3172
+ return target.isContentEditable ? getContentEditableHost(target) : target;
2621
3173
  }
2622
- return hasExpandedSelectionWithinTarget(selection, rootElement);
3174
+ const label = target.closest('label');
3175
+ const control = label?.control ?? null;
3176
+ return floatingUi_utils_dom.isHTMLElement(control) && isKeyboardInputElement(control) ? control : null;
2623
3177
  }
2624
- function isEventOnRangeInput(event, win) {
2625
- const composedPath = event.composedPath();
2626
- if (composedPath) {
2627
- return composedPath.some(pathTarget => isRangeInput(pathTarget, win));
3178
+ function resolveKeyboardTouchTarget(target) {
3179
+ const focusTarget = resolveKeyboardInputTarget(target);
3180
+ if (!focusTarget) {
3181
+ return null;
2628
3182
  }
2629
- return isRangeInput(event.target, win);
2630
- }
2631
- function isReactTouchEventOnRangeInput(event) {
2632
- return isEventOnRangeInput(event.nativeEvent, floatingUi_utils_dom.getWindow(event.currentTarget));
2633
- }
2634
- function updateTouchScrollPosition(touchState, touch) {
2635
- touchState.lastX = touch.clientX;
2636
- touchState.lastY = touch.clientY;
3183
+ return {
3184
+ focusTarget,
3185
+ clickTarget: floatingUi_utils_dom.isHTMLElement(target) ? target : focusTarget
3186
+ };
2637
3187
  }
2638
- function preserveNativeCrossAxisScrollOnMove(touchState, touch, isVerticalScrollAxis) {
2639
- if (touchState.preserveNativeCrossAxisScroll) {
2640
- return true;
2641
- }
2642
- if (touchState.allowSwipe === true || !touchState.hasCrossAxisScrollableContent) {
2643
- return false;
2644
- }
2645
- const drawerAxisGestureDelta = isVerticalScrollAxis ? touch.clientY - touchState.startY : touch.clientX - touchState.startX;
2646
- const crossAxisGestureDelta = isVerticalScrollAxis ? touch.clientX - touchState.startX : touch.clientY - touchState.startY;
2647
- const absDrawerAxisGestureDelta = Math.abs(drawerAxisGestureDelta);
2648
- const absCrossAxisGestureDelta = Math.abs(crossAxisGestureDelta);
2649
- if (absCrossAxisGestureDelta < 6 || absCrossAxisGestureDelta <= absDrawerAxisGestureDelta + 2) {
2650
- return false;
3188
+
3189
+ // Inherited-editable descendants (no `contenteditable` attribute of their own) are not
3190
+ // focusable, so focusing them is a no-op; resolve taps on them to the editing host.
3191
+ function getContentEditableHost(element) {
3192
+ let host = element;
3193
+ while (host.parentElement?.isContentEditable) {
3194
+ host = host.parentElement;
2651
3195
  }
2652
- touchState.preserveNativeCrossAxisScroll = true;
2653
- return true;
2654
- }
2655
- function hasScrollableContentOnAxis(scrollTarget, axis) {
2656
- return axis === 'vertical' ? scrollTarget.scrollHeight > scrollTarget.clientHeight : scrollTarget.scrollWidth > scrollTarget.clientWidth;
3196
+ return host;
2657
3197
  }
2658
- function getScrollMetrics(scrollTarget, axis) {
2659
- if (axis === 'vertical') {
2660
- const max = Math.max(0, scrollTarget.scrollHeight - scrollTarget.clientHeight);
3198
+ function resolveKeyboardTouchTargetFromPoint(doc, clientX, clientY) {
3199
+ const exactTarget = getElementAtPoint(doc, clientX, clientY);
3200
+ const exactKeyboardTarget = resolveKeyboardInputTarget(exactTarget);
3201
+ if (exactKeyboardTarget) {
2661
3202
  return {
2662
- offset: scrollTarget.scrollTop,
2663
- max
3203
+ focusTarget: exactKeyboardTarget,
3204
+ clickTarget: floatingUi_utils_dom.isHTMLElement(exactTarget) ? exactTarget : exactKeyboardTarget
2664
3205
  };
2665
3206
  }
2666
- const max = Math.max(0, scrollTarget.scrollWidth - scrollTarget.clientWidth);
2667
- return {
2668
- offset: scrollTarget.scrollLeft,
2669
- max
2670
- };
2671
- }
2672
- function isAtSwipeStartEdge(scrollTarget, axis, direction) {
2673
- const {
2674
- offset,
2675
- max
2676
- } = getScrollMetrics(scrollTarget, axis);
2677
- const dismissFromStartEdge = shouldDismissFromStartEdge(direction, axis);
2678
- if (dismissFromStartEdge === null) {
2679
- return false;
2680
- }
2681
- return dismissFromStartEdge ? offset <= 0 : offset >= max;
2682
- }
2683
- function canSwipeFromScrollEdgeOnMove(scrollTarget, axis, direction, delta) {
2684
- const {
2685
- offset,
2686
- max
2687
- } = getScrollMetrics(scrollTarget, axis);
2688
- const dismissFromStartEdge = shouldDismissFromStartEdge(direction, axis);
2689
- if (dismissFromStartEdge === null) {
2690
- return false;
3207
+
3208
+ // Probing nearby points compensates for iOS retargeting taps while the page reacts
3209
+ // to the keyboard, but it must not steal a tap that lands on another interactive
3210
+ // element — that would suppress its click and focus a neighboring field instead.
3211
+ // `closest('label')` covers labels of non-keyboard controls (e.g. checkboxes).
3212
+ // Returning the blocked sentinel (rather than `null`) stops the caller from falling
3213
+ // back to the touchstart target, which would re-steal the very tap rejected here.
3214
+ if (useScrollLock.isInteractiveElement(exactTarget) || exactTarget?.closest('label') != null) {
3215
+ return KEYBOARD_TAP_BLOCKED;
2691
3216
  }
2692
- const movingTowardDismiss = dismissFromStartEdge ? delta > 0 : delta < 0;
2693
- if (!movingTowardDismiss) {
2694
- return false;
3217
+ for (const [offsetX, offsetY] of [[0, INPUT_TAP_HIT_SLOP], [0, -INPUT_TAP_HIT_SLOP], [INPUT_TAP_HIT_SLOP, 0], [-INPUT_TAP_HIT_SLOP, 0]]) {
3218
+ const keyboardTarget = resolveKeyboardInputTarget(getElementAtPoint(doc, clientX + offsetX, clientY + offsetY));
3219
+ if (keyboardTarget) {
3220
+ return {
3221
+ focusTarget: keyboardTarget,
3222
+ clickTarget: keyboardTarget
3223
+ };
3224
+ }
2695
3225
  }
2696
- return dismissFromStartEdge ? offset <= 0 : offset >= max;
3226
+ return null;
2697
3227
  }
2698
- function shouldDismissFromStartEdge(direction, axis) {
2699
- if (axis === 'vertical') {
2700
- if (direction === 'down') {
2701
- return true;
2702
- }
2703
- if (direction === 'up') {
2704
- return false;
3228
+ function dispatchKeyboardClick(target, touch) {
3229
+ const win = floatingUi_utils_dom.getWindow(target);
3230
+ const ClickEvent = win.PointerEvent ?? win.MouseEvent;
3231
+ target.dispatchEvent(new ClickEvent('click', {
3232
+ bubbles: true,
3233
+ cancelable: true,
3234
+ clientX: touch.clientX,
3235
+ clientY: touch.clientY,
3236
+ detail: 1,
3237
+ view: win
3238
+ }));
3239
+ }
3240
+ function focusKeyboardInputWithoutPageScroll(target) {
3241
+ const wasFocused = useButton.activeElement(useButton.ownerDocument(target)) === target;
3242
+ const previousOpacity = target.style.opacity;
3243
+ const previousTransform = target.style.transform;
3244
+ const previousTransition = target.style.transition;
3245
+
3246
+ // iOS Safari can still scroll the page for transformed sheets even with preventScroll.
3247
+ // Move the input off-screen only for the synchronous focus call.
3248
+ target.style.transition = 'none';
3249
+ target.style.opacity = '0';
3250
+ target.style.transform = 'translateY(-2000px)';
3251
+ try {
3252
+ if (wasFocused) {
3253
+ target.blur();
2705
3254
  }
2706
- return null;
3255
+ target.focus({
3256
+ preventScroll: true
3257
+ });
3258
+ } finally {
3259
+ target.style.opacity = previousOpacity;
3260
+ target.style.transform = previousTransform;
3261
+ target.style.transition = previousTransition;
2707
3262
  }
2708
- if (direction === 'right') {
2709
- return true;
3263
+ }
3264
+ function findKeyboardScrollTarget(target, root) {
3265
+ // Start at the parent: scrolling the focused field's own content (an overflowing
3266
+ // textarea is scrollable itself) can never move its box out from under the keyboard.
3267
+ // `getParentNode` crosses shadow boundaries so an input inside a shadow root still reaches
3268
+ // the drawer body scroller. Prefer an already-scrollable ancestor, then fall back to one
3269
+ // that only becomes scrollable once keyboard slack is added (overflow intent without
3270
+ // current overflow).
3271
+ const scrollStart = floatingUi_utils_dom.getParentNode(target);
3272
+ return findScrollableTouchTarget(scrollStart, root, 'vertical') ?? findScrollableTouchTarget(scrollStart, root, 'vertical', true);
3273
+ }
3274
+ function getKeyboardVisualViewport(win) {
3275
+ const visualViewport = win.visualViewport;
3276
+ if (!visualViewport || visualViewport.scale !== 1) {
3277
+ return null;
2710
3278
  }
2711
- if (direction === 'left') {
2712
- return false;
3279
+ const reducedHeight = win.innerHeight - visualViewport.height;
3280
+ // Treat small viewport changes as browser chrome movement, not the software keyboard.
3281
+ if (reducedHeight <= KEYBOARD_RESIZE_THRESHOLD) {
3282
+ return null;
2713
3283
  }
2714
- return null;
3284
+ const top = Math.max(0, visualViewport.offsetTop);
3285
+ return {
3286
+ top,
3287
+ bottom: Math.min(win.innerHeight, top + visualViewport.height)
3288
+ };
3289
+ }
3290
+ function getDrawerKeyboardInset(win, keyboardViewport) {
3291
+ return Math.max(0, win.innerHeight - keyboardViewport.bottom);
3292
+ }
3293
+ function isKeyboardVisualViewportOpen(win) {
3294
+ return !win.visualViewport || getKeyboardVisualViewport(win) != null;
2715
3295
  }
2716
3296
 
2717
3297
  var index_parts$5 = /*#__PURE__*/Object.freeze({
@@ -2731,6 +3311,7 @@ var index_parts$5 = /*#__PURE__*/Object.freeze({
2731
3311
  Title: DrawerTitle.DrawerTitle,
2732
3312
  Trigger: ScrollAreaViewport.DrawerTrigger,
2733
3313
  Viewport: DrawerViewport,
3314
+ VirtualKeyboardProvider: DrawerVirtualKeyboardProvider,
2734
3315
  createHandle: DrawerDescription.createDialogHandle
2735
3316
  });
2736
3317
 
@@ -2742,8 +3323,9 @@ var index_parts$5 = /*#__PURE__*/Object.freeze({
2742
3323
  */
2743
3324
  const MenuArrow = /*#__PURE__*/React__namespace.forwardRef(function MenuArrow(componentProps, forwardedRef) {
2744
3325
  const {
2745
- className,
2746
3326
  render,
3327
+ className,
3328
+ style,
2747
3329
  ...elementProps
2748
3330
  } = componentProps;
2749
3331
  const {
@@ -2765,7 +3347,7 @@ const MenuArrow = /*#__PURE__*/React__namespace.forwardRef(function MenuArrow(co
2765
3347
  };
2766
3348
  return useRenderElement.useRenderElement('div', componentProps, {
2767
3349
  ref: [arrowRef, forwardedRef],
2768
- stateAttributesMapping: InternalBackdrop.popupStateMapping,
3350
+ stateAttributesMapping: useScrollLock.popupStateMapping,
2769
3351
  state,
2770
3352
  props: {
2771
3353
  style: arrowStyles,
@@ -2804,6 +3386,7 @@ const MenuCheckboxItem = /*#__PURE__*/React__namespace.forwardRef(function MenuC
2804
3386
  checked: checkedProp,
2805
3387
  defaultChecked,
2806
3388
  onCheckedChange,
3389
+ style,
2807
3390
  ...elementProps
2808
3391
  } = componentProps;
2809
3392
  const listItem = useCompositeListItem.useCompositeListItem({
@@ -2832,7 +3415,7 @@ const MenuCheckboxItem = /*#__PURE__*/React__namespace.forwardRef(function MenuC
2832
3415
  id,
2833
3416
  store,
2834
3417
  nativeButton,
2835
- nodeId: menuPositionerContext?.nodeId,
3418
+ nodeId: menuPositionerContext?.context.nodeId,
2836
3419
  itemMetadata: MenuSubmenuTrigger.REGULAR_ITEM
2837
3420
  });
2838
3421
  const state = React__namespace.useMemo(() => ({
@@ -2840,17 +3423,16 @@ const MenuCheckboxItem = /*#__PURE__*/React__namespace.forwardRef(function MenuC
2840
3423
  highlighted,
2841
3424
  checked
2842
3425
  }), [disabled, highlighted, checked]);
2843
- const handleClick = useButton.useStableCallback(event => {
2844
- const details = {
2845
- ...useButton.createChangeEventDetails(useButton.itemPress, event.nativeEvent),
2846
- preventUnmountOnClose: () => {}
2847
- };
3426
+ function handleClick(event) {
3427
+ const details = useButton.createChangeEventDetails(useButton.itemPress, event.nativeEvent, undefined, {
3428
+ preventUnmountOnClose() {}
3429
+ });
2848
3430
  onCheckedChange?.(!checked, details);
2849
3431
  if (details.isCanceled) {
2850
3432
  return;
2851
3433
  }
2852
3434
  setChecked(currentlyChecked => !currentlyChecked);
2853
- });
3435
+ }
2854
3436
  const element = useRenderElement.useRenderElement('div', componentProps, {
2855
3437
  state,
2856
3438
  stateAttributesMapping: MenuSubmenuTrigger.itemMapping,
@@ -2878,6 +3460,7 @@ const MenuCheckboxItemIndicator = /*#__PURE__*/React__namespace.forwardRef(funct
2878
3460
  const {
2879
3461
  render,
2880
3462
  className,
3463
+ style,
2881
3464
  keepMounted = false,
2882
3465
  ...elementProps
2883
3466
  } = componentProps;
@@ -2916,25 +3499,6 @@ const MenuCheckboxItemIndicator = /*#__PURE__*/React__namespace.forwardRef(funct
2916
3499
  });
2917
3500
  if (process.env.NODE_ENV !== "production") MenuCheckboxItemIndicator.displayName = "MenuCheckboxItemIndicator";
2918
3501
 
2919
- /**
2920
- * Returns a previous value of its argument.
2921
- * @param value Current value.
2922
- * @returns Previous value, or null if there is no previous value.
2923
- */
2924
- function usePreviousValue(value) {
2925
- const [state, setState] = React__namespace.useState({
2926
- current: value,
2927
- previous: null
2928
- });
2929
- if (value !== state.current) {
2930
- setState({
2931
- current: value,
2932
- previous: state.current
2933
- });
2934
- }
2935
- return state.previous;
2936
- }
2937
-
2938
3502
  function getCssDimensions(element) {
2939
3503
  const css = floatingUi_utils_dom.getComputedStyle(element);
2940
3504
  // In testing environments, the `width` and `height` properties are empty
@@ -2944,7 +3508,7 @@ function getCssDimensions(element) {
2944
3508
  const hasOffset = floatingUi_utils_dom.isHTMLElement(element);
2945
3509
  const offsetWidth = hasOffset ? element.offsetWidth : width;
2946
3510
  const offsetHeight = hasOffset ? element.offsetHeight : height;
2947
- const shouldFallback = index_esm.round(width) !== offsetWidth || index_esm.round(height) !== offsetHeight;
3511
+ const shouldFallback = floatingUi_utils.round(width) !== offsetWidth || floatingUi_utils.round(height) !== offsetHeight;
2948
3512
  if (shouldFallback) {
2949
3513
  width = offsetWidth;
2950
3514
  height = offsetHeight;
@@ -2955,8 +3519,6 @@ function getCssDimensions(element) {
2955
3519
  };
2956
3520
  }
2957
3521
 
2958
- const DEFAULT_ENABLED = () => true;
2959
-
2960
3522
  /**
2961
3523
  * Allows the element to automatically resize based on its content while supporting animations.
2962
3524
  */
@@ -2966,7 +3528,6 @@ function usePopupAutoResize(parameters) {
2966
3528
  positionerElement,
2967
3529
  content,
2968
3530
  mounted,
2969
- enabled = DEFAULT_ENABLED,
2970
3531
  onMeasureLayout: onMeasureLayoutParam,
2971
3532
  onMeasureLayoutComplete: onMeasureLayoutCompleteParam,
2972
3533
  side,
@@ -2975,7 +3536,6 @@ function usePopupAutoResize(parameters) {
2975
3536
  const runOnceAnimationsFinish = useButton.useAnimationsFinished(popupElement, true, false);
2976
3537
  const animationFrame = useButton.useAnimationFrame();
2977
3538
  const committedDimensionsRef = React__namespace.useRef(null);
2978
- const liveDimensionsRef = React__namespace.useRef(null);
2979
3539
  const isInitialRenderRef = React__namespace.useRef(true);
2980
3540
  const restoreAnchoringStylesRef = React__namespace.useRef(useRenderElement.NOOP);
2981
3541
  const onMeasureLayout = useButton.useStableCallback(onMeasureLayoutParam);
@@ -2999,27 +3559,16 @@ function usePopupAutoResize(parameters) {
2999
3559
  }, [side, direction]);
3000
3560
  useButton.useIsoLayoutEffect(() => {
3001
3561
  // Reset the state when the popup is closed.
3002
- if (!mounted || !enabled() || typeof ResizeObserver !== 'function') {
3562
+ if (!mounted) {
3003
3563
  restoreAnchoringStylesRef.current = useRenderElement.NOOP;
3004
3564
  isInitialRenderRef.current = true;
3005
3565
  committedDimensionsRef.current = null;
3006
- liveDimensionsRef.current = null;
3007
3566
  return undefined;
3008
3567
  }
3009
3568
  if (!popupElement || !positionerElement) {
3010
3569
  return undefined;
3011
3570
  }
3012
3571
  restoreAnchoringStylesRef.current = applyElementStyles(popupElement, anchoringStyles);
3013
- const observer = new ResizeObserver(entries => {
3014
- const entry = entries[0];
3015
- if (entry) {
3016
- liveDimensionsRef.current = {
3017
- width: Math.ceil(entry.borderBoxSize[0].inlineSize),
3018
- height: Math.ceil(entry.borderBoxSize[0].blockSize)
3019
- };
3020
- }
3021
- });
3022
- observer.observe(popupElement);
3023
3572
 
3024
3573
  // Measure the rendered size to enable transitions:
3025
3574
  setPopupCssSize(popupElement, 'auto');
@@ -3051,32 +3600,18 @@ function usePopupAutoResize(parameters) {
3051
3600
  onMeasureLayoutComplete?.(null, dimensions);
3052
3601
  isInitialRenderRef.current = false;
3053
3602
  return () => {
3054
- observer.disconnect();
3055
3603
  restoreAnchoringStylesRef.current();
3056
3604
  restoreAnchoringStylesRef.current = useRenderElement.NOOP;
3057
3605
  };
3058
3606
  }
3059
3607
 
3060
3608
  // Subsequent renders while open (when `content` changes).
3061
- setPopupCssSize(popupElement, 'auto');
3062
3609
  setPositionerCssSize(positionerElement, 'max-content');
3063
- const previousDimensions = committedDimensionsRef.current ?? liveDimensionsRef.current;
3610
+ const previousDimensions = committedDimensionsRef.current;
3064
3611
  const newDimensions = getCssDimensions(popupElement);
3065
3612
 
3066
- // Commit immediately so future content changes have a stable previous size, even if
3067
- // ResizeObserver runs after this point.
3613
+ // Commit immediately so future content changes have a stable previous size.
3068
3614
  committedDimensionsRef.current = newDimensions;
3069
- if (!previousDimensions) {
3070
- setPositionerCssSize(positionerElement, newDimensions);
3071
- restoreMeasurementOverridesIncludingScale();
3072
- onMeasureLayoutComplete?.(null, newDimensions);
3073
- return () => {
3074
- observer.disconnect();
3075
- animationFrame.cancel();
3076
- restoreAnchoringStylesRef.current();
3077
- restoreAnchoringStylesRef.current = useRenderElement.NOOP;
3078
- };
3079
- }
3080
3615
  setPopupCssSize(popupElement, previousDimensions);
3081
3616
  restoreMeasurementOverridesIncludingScale();
3082
3617
  onMeasureLayoutComplete?.(previousDimensions, newDimensions);
@@ -3090,13 +3625,12 @@ function usePopupAutoResize(parameters) {
3090
3625
  }, abortController.signal);
3091
3626
  });
3092
3627
  return () => {
3093
- observer.disconnect();
3094
3628
  abortController.abort();
3095
3629
  animationFrame.cancel();
3096
3630
  restoreAnchoringStylesRef.current();
3097
3631
  restoreAnchoringStylesRef.current = useRenderElement.NOOP;
3098
3632
  };
3099
- }, [content, popupElement, positionerElement, runOnceAnimationsFinish, animationFrame, enabled, mounted, onMeasureLayout, onMeasureLayoutComplete, anchoringStyles]);
3633
+ }, [content, popupElement, positionerElement, runOnceAnimationsFinish, animationFrame, mounted, onMeasureLayout, onMeasureLayoutComplete, anchoringStyles]);
3100
3634
  }
3101
3635
  function overrideElementStyle(element, property, value) {
3102
3636
  const originalValue = element.style.getPropertyValue(property);
@@ -3138,7 +3672,7 @@ function usePopupViewport(parameters) {
3138
3672
  cssVars,
3139
3673
  children
3140
3674
  } = parameters;
3141
- const direction = InternalBackdrop.useDirection();
3675
+ const direction = useScrollLock.useDirection();
3142
3676
  const activeTrigger = store.useState('activeTriggerElement');
3143
3677
  const activeTriggerId = store.useState('activeTriggerId');
3144
3678
  const open = store.useState('open');
@@ -3146,7 +3680,7 @@ function usePopupViewport(parameters) {
3146
3680
  const mounted = store.useState('mounted');
3147
3681
  const popupElement = store.useState('popupElement');
3148
3682
  const positionerElement = store.useState('positionerElement');
3149
- const previousActiveTrigger = usePreviousValue(open ? activeTrigger : null);
3683
+ const previousActiveTrigger = SelectGroupLabel.usePreviousValue(open ? activeTrigger : null);
3150
3684
  // Remount current content on trigger changes (and once more when payload lags) to avoid DOM reuse flashes.
3151
3685
  // The key bumps immediately on trigger switches, then again if the payload arrives on a later render.
3152
3686
  const currentContentKey = usePopupContentKey(activeTriggerId, payload);
@@ -3179,6 +3713,11 @@ function usePopupViewport(parameters) {
3179
3713
  }
3180
3714
  });
3181
3715
  const lastHandledTriggerRef = React__namespace.useRef(null);
3716
+ useButton.useIsoLayoutEffect(() => {
3717
+ if (!open || !mounted) {
3718
+ lastHandledTriggerRef.current = null;
3719
+ }
3720
+ }, [open, mounted]);
3182
3721
  useButton.useIsoLayoutEffect(() => {
3183
3722
  // When a trigger changes, set the captured children HTML to state,
3184
3723
  // so we can render both new and old content.
@@ -3217,7 +3756,7 @@ function usePopupViewport(parameters) {
3217
3756
  if (!source) {
3218
3757
  return;
3219
3758
  }
3220
- const wrapper = document.createElement('div');
3759
+ const wrapper = useButton.ownerDocument(source).createElement('div');
3221
3760
  for (const child of Array.from(source.childNodes)) {
3222
3761
  wrapper.appendChild(child.cloneNode(true));
3223
3762
  }
@@ -3235,7 +3774,7 @@ function usePopupViewport(parameters) {
3235
3774
  childrenToRender = /*#__PURE__*/jsxRuntime.jsxs(React__namespace.Fragment, {
3236
3775
  children: [/*#__PURE__*/jsxRuntime.jsx("div", {
3237
3776
  "data-previous": true,
3238
- inert: InternalBackdrop.inertValue(true),
3777
+ inert: useScrollLock.inertValue(true),
3239
3778
  ref: previousContainerRef,
3240
3779
  style: {
3241
3780
  ...(previousContentDimensions ? {
@@ -3389,8 +3928,8 @@ const stateAttributesMapping$2 = {
3389
3928
 
3390
3929
  /**
3391
3930
  * A viewport for displaying content transitions.
3392
- * This component is only required if one popup can be opened by multiple triggers, its content change based on the trigger
3393
- * and switching between them is animated.
3931
+ * This component is only required if one popup can be opened by multiple triggers, its content
3932
+ * changes based on the trigger, and switching between them is animated.
3394
3933
  * Renders a `<div>` element.
3395
3934
  *
3396
3935
  * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)
@@ -3399,6 +3938,7 @@ const MenuViewport = /*#__PURE__*/React__namespace.forwardRef(function MenuViewp
3399
3938
  const {
3400
3939
  render,
3401
3940
  className,
3941
+ style,
3402
3942
  children,
3403
3943
  ...elementProps
3404
3944
  } = componentProps;
@@ -3469,7 +4009,7 @@ class MenuHandle {
3469
4009
  * Indicates whether the menu is currently open.
3470
4010
  */
3471
4011
  get isOpen() {
3472
- return this.store.state.open;
4012
+ return this.store.select('open');
3473
4013
  }
3474
4014
  }
3475
4015
 
@@ -3542,6 +4082,7 @@ const ScrollAreaScrollbar = /*#__PURE__*/React__namespace.forwardRef(function Sc
3542
4082
  className,
3543
4083
  orientation = 'vertical',
3544
4084
  keepMounted = false,
4085
+ style,
3545
4086
  ...elementProps
3546
4087
  } = componentProps;
3547
4088
  const {
@@ -3557,6 +4098,7 @@ const ScrollAreaScrollbar = /*#__PURE__*/React__namespace.forwardRef(function Sc
3557
4098
  thumbXRef,
3558
4099
  handlePointerDown,
3559
4100
  handlePointerUp,
4101
+ handleScroll,
3560
4102
  rootId,
3561
4103
  thumbSize,
3562
4104
  hasMeasuredScrollbar
@@ -3576,9 +4118,14 @@ const ScrollAreaScrollbar = /*#__PURE__*/React__namespace.forwardRef(function Sc
3576
4118
  overflowYEnd: overflowEdges.yEnd,
3577
4119
  cornerHidden: hiddenState.corner
3578
4120
  };
3579
- const direction = InternalBackdrop.useDirection();
4121
+ const direction = useScrollLock.useDirection();
3580
4122
  const hideTrackUntilMeasured = !hasMeasuredScrollbar && !keepMounted;
4123
+ const isHidden = orientation === 'vertical' ? hiddenState.y : hiddenState.x;
4124
+ const shouldRender = keepMounted || !isHidden;
3581
4125
  React__namespace.useEffect(() => {
4126
+ if (!shouldRender) {
4127
+ return undefined;
4128
+ }
3582
4129
  const viewportEl = viewportRef.current;
3583
4130
  const scrollbarEl = orientation === 'vertical' ? scrollbarYRef.current : scrollbarXRef.current;
3584
4131
  if (!scrollbarEl) {
@@ -3588,34 +4135,34 @@ const ScrollAreaScrollbar = /*#__PURE__*/React__namespace.forwardRef(function Sc
3588
4135
  if (!viewportEl || !scrollbarEl || event.ctrlKey) {
3589
4136
  return;
3590
4137
  }
3591
- event.preventDefault();
3592
- if (orientation === 'vertical') {
3593
- if (viewportEl.scrollTop === 0 && event.deltaY < 0) {
3594
- return;
3595
- }
3596
- } else if (viewportEl.scrollLeft === 0 && event.deltaX < 0) {
4138
+ const horizontal = orientation === 'horizontal';
4139
+ const scrollProperty = horizontal ? 'scrollLeft' : 'scrollTop';
4140
+ const delta = horizontal ? event.deltaX : event.deltaY;
4141
+ if (delta === 0) {
3597
4142
  return;
3598
4143
  }
3599
- if (orientation === 'vertical') {
3600
- if (viewportEl.scrollTop === viewportEl.scrollHeight - viewportEl.clientHeight && event.deltaY > 0) {
3601
- return;
3602
- }
3603
- } else if (viewportEl.scrollLeft === viewportEl.scrollWidth - viewportEl.clientWidth && event.deltaX > 0) {
4144
+ const maxScroll = horizontal ? viewportEl.scrollWidth - viewportEl.clientWidth : viewportEl.scrollHeight - viewportEl.clientHeight;
4145
+ // RTL horizontal scrolling uses a negative `scrollLeft` range, from 0 to `-maxScroll`.
4146
+ const minScroll = horizontal && direction === 'rtl' ? -maxScroll : 0;
4147
+ const maxScrollValue = horizontal && direction === 'rtl' ? 0 : maxScroll;
4148
+ const scrollValue = viewportEl[scrollProperty];
4149
+
4150
+ // At an edge (or with no overflow), let the wheel event chain to the
4151
+ // parent/page instead of swallowing it via `preventDefault`.
4152
+ if (scrollValue <= minScroll && delta < 0 || scrollValue >= maxScrollValue && delta > 0) {
3604
4153
  return;
3605
4154
  }
3606
- if (orientation === 'vertical') {
3607
- viewportEl.scrollTop += event.deltaY;
3608
- } else {
3609
- viewportEl.scrollLeft += event.deltaX;
3610
- }
4155
+ event.preventDefault();
4156
+ viewportEl[scrollProperty] = Math.min(maxScrollValue, Math.max(minScroll, scrollValue + delta));
4157
+ handleScroll({
4158
+ x: viewportEl.scrollLeft,
4159
+ y: viewportEl.scrollTop
4160
+ });
3611
4161
  }
3612
- scrollbarEl.addEventListener('wheel', handleWheel, {
4162
+ return useButton.addEventListener(scrollbarEl, 'wheel', handleWheel, {
3613
4163
  passive: false
3614
4164
  });
3615
- return () => {
3616
- scrollbarEl.removeEventListener('wheel', handleWheel);
3617
- };
3618
- }, [orientation, scrollbarXRef, scrollbarYRef, viewportRef]);
4165
+ }, [direction, handleScroll, orientation, scrollbarXRef, scrollbarYRef, shouldRender, viewportRef]);
3619
4166
  const props = {
3620
4167
  ...(rootId && {
3621
4168
  'data-id': `${rootId}-scrollbar`
@@ -3624,9 +4171,12 @@ const ScrollAreaScrollbar = /*#__PURE__*/React__namespace.forwardRef(function Sc
3624
4171
  if (event.button !== 0) {
3625
4172
  return;
3626
4173
  }
4174
+ const target = useButton.getTarget(event.nativeEvent);
4175
+ const thumb = orientation === 'vertical' ? thumbYRef.current : thumbXRef.current;
3627
4176
 
3628
- // Ignore clicks on thumb
3629
- if (event.currentTarget !== event.target) {
4177
+ // Ignore clicks on thumb, including cases where React retargets the
4178
+ // synthetic event to the track host across a shadow boundary.
4179
+ if (thumb && useButton.contains(thumb, target)) {
3630
4180
  return;
3631
4181
  }
3632
4182
  if (!viewportRef.current) {
@@ -3671,9 +4221,16 @@ const ScrollAreaScrollbar = /*#__PURE__*/React__namespace.forwardRef(function Sc
3671
4221
  }
3672
4222
  viewportRef.current.scrollLeft = newScrollLeft;
3673
4223
  }
4224
+ handleScroll({
4225
+ x: viewportRef.current.scrollLeft,
4226
+ y: viewportRef.current.scrollTop
4227
+ });
3674
4228
  handlePointerDown(event);
3675
4229
  },
3676
4230
  onPointerUp: handlePointerUp,
4231
+ // Mirror `onPointerUp` so a browser-cancelled gesture on the track (no thumb
4232
+ // child captures the pointer) still clears the drag state.
4233
+ onPointerCancel: handlePointerUp,
3677
4234
  style: {
3678
4235
  position: 'absolute',
3679
4236
  touchAction: 'none',
@@ -3703,8 +4260,6 @@ const ScrollAreaScrollbar = /*#__PURE__*/React__namespace.forwardRef(function Sc
3703
4260
  const contextValue = React__namespace.useMemo(() => ({
3704
4261
  orientation
3705
4262
  }), [orientation]);
3706
- const isHidden = orientation === 'vertical' ? hiddenState.y : hiddenState.x;
3707
- const shouldRender = keepMounted || !isHidden;
3708
4263
  if (!shouldRender) {
3709
4264
  return null;
3710
4265
  }
@@ -3725,34 +4280,42 @@ const ScrollAreaContent = /*#__PURE__*/React__namespace.forwardRef(function Scro
3725
4280
  const {
3726
4281
  render,
3727
4282
  className,
4283
+ style,
3728
4284
  ...elementProps
3729
4285
  } = componentProps;
3730
- const contentWrapperRef = React__namespace.useRef(null);
3731
4286
  const {
3732
4287
  computeThumbPosition
3733
4288
  } = ScrollAreaViewport.useScrollAreaViewportContext();
3734
4289
  const {
4290
+ hasMeasuredScrollbar,
3735
4291
  viewportState
3736
4292
  } = ScrollAreaViewport.useScrollAreaRootContext();
4293
+ const contentWrapperRef = React__namespace.useRef(null);
4294
+ const computeOnInitialResizeRef = React__namespace.useRef(hasMeasuredScrollbar);
3737
4295
  useButton.useIsoLayoutEffect(() => {
3738
4296
  if (typeof ResizeObserver === 'undefined') {
3739
4297
  return undefined;
3740
4298
  }
3741
4299
  let hasInitialized = false;
3742
- const ro = new ResizeObserver(() => {
3743
- // ResizeObserver fires once upon observing, so we skip the initial call
3744
- // to avoid double-calculating the thumb position on mount.
4300
+ const resizeObserver = new ResizeObserver(() => {
3745
4301
  if (!hasInitialized) {
3746
4302
  hasInitialized = true;
3747
- return;
4303
+
4304
+ // ResizeObserver fires once upon observing. Skip that initial call to avoid
4305
+ // double-calculating the thumb position on mount, unless the content mounted
4306
+ // after the viewport's initial measurement (in which case this fire is what
4307
+ // brings the overflow state in sync).
4308
+ if (!computeOnInitialResizeRef.current) {
4309
+ return;
4310
+ }
3748
4311
  }
3749
4312
  computeThumbPosition();
3750
4313
  });
3751
4314
  if (contentWrapperRef.current) {
3752
- ro.observe(contentWrapperRef.current);
4315
+ resizeObserver.observe(contentWrapperRef.current);
3753
4316
  }
3754
4317
  return () => {
3755
- ro.disconnect();
4318
+ resizeObserver.disconnect();
3756
4319
  };
3757
4320
  }, [computeThumbPosition]);
3758
4321
  const element = useRenderElement.useRenderElement('div', componentProps, {
@@ -3780,6 +4343,7 @@ const ScrollAreaThumb = /*#__PURE__*/React__namespace.forwardRef(function Scroll
3780
4343
  const {
3781
4344
  render,
3782
4345
  className,
4346
+ style,
3783
4347
  ...elementProps
3784
4348
  } = componentProps;
3785
4349
  const {
@@ -3790,29 +4354,34 @@ const ScrollAreaThumb = /*#__PURE__*/React__namespace.forwardRef(function Scroll
3790
4354
  handlePointerUp,
3791
4355
  setScrollingX,
3792
4356
  setScrollingY,
4357
+ scrollingX,
4358
+ scrollingY,
3793
4359
  hasMeasuredScrollbar
3794
4360
  } = ScrollAreaViewport.useScrollAreaRootContext();
3795
4361
  const {
3796
4362
  orientation
3797
4363
  } = useScrollAreaScrollbarContext();
3798
4364
  const state = {
4365
+ scrolling: orientation === 'horizontal' ? scrollingX : scrollingY,
3799
4366
  orientation
3800
4367
  };
4368
+ function endDrag(event) {
4369
+ if (orientation === 'vertical') {
4370
+ setScrollingY(false);
4371
+ }
4372
+ if (orientation === 'horizontal') {
4373
+ setScrollingX(false);
4374
+ }
4375
+ handlePointerUp(event);
4376
+ }
3801
4377
  const element = useRenderElement.useRenderElement('div', componentProps, {
3802
4378
  ref: [forwardedRef, orientation === 'vertical' ? thumbYRef : thumbXRef],
3803
4379
  state,
3804
4380
  props: [{
3805
4381
  onPointerDown: handlePointerDown,
3806
4382
  onPointerMove: handlePointerMove,
3807
- onPointerUp(event) {
3808
- if (orientation === 'vertical') {
3809
- setScrollingY(false);
3810
- }
3811
- if (orientation === 'horizontal') {
3812
- setScrollingX(false);
3813
- }
3814
- handlePointerUp(event);
3815
- },
4383
+ onPointerUp: endDrag,
4384
+ onPointerCancel: endDrag,
3816
4385
  style: {
3817
4386
  visibility: hasMeasuredScrollbar ? undefined : 'hidden',
3818
4387
  ...(orientation === 'vertical' && {
@@ -3838,6 +4407,7 @@ const ScrollAreaCorner = /*#__PURE__*/React__namespace.forwardRef(function Scrol
3838
4407
  const {
3839
4408
  render,
3840
4409
  className,
4410
+ style,
3841
4411
  ...elementProps
3842
4412
  } = componentProps;
3843
4413
  const {
@@ -3916,32 +4486,32 @@ function useNumberFieldScrubAreaContext() {
3916
4486
  return context;
3917
4487
  }
3918
4488
 
3919
- // Calculates the viewport rect for the virtual cursor.
4489
+ // Calculates the bounds the virtual cursor wraps within, as absolute edge coordinates.
3920
4490
  function getViewportRect(teleportDistance, scrubAreaEl) {
3921
4491
  const win = floatingUi_utils_dom.getWindow(scrubAreaEl);
3922
- const rect = scrubAreaEl.getBoundingClientRect();
3923
- if (rect && teleportDistance != null) {
4492
+ if (teleportDistance != null) {
4493
+ const rect = scrubAreaEl.getBoundingClientRect();
3924
4494
  return {
3925
- x: rect.left - teleportDistance / 2,
3926
- y: rect.top - teleportDistance / 2,
3927
- width: rect.right + teleportDistance / 2,
3928
- height: rect.bottom + teleportDistance / 2
4495
+ left: rect.left - teleportDistance / 2,
4496
+ top: rect.top - teleportDistance / 2,
4497
+ right: rect.right + teleportDistance / 2,
4498
+ bottom: rect.bottom + teleportDistance / 2
3929
4499
  };
3930
4500
  }
3931
4501
  const vV = win.visualViewport;
3932
4502
  if (vV) {
3933
4503
  return {
3934
- x: vV.offsetLeft,
3935
- y: vV.offsetTop,
3936
- width: vV.offsetLeft + vV.width,
3937
- height: vV.offsetTop + vV.height
4504
+ left: vV.offsetLeft,
4505
+ top: vV.offsetTop,
4506
+ right: vV.offsetLeft + vV.width,
4507
+ bottom: vV.offsetTop + vV.height
3938
4508
  };
3939
4509
  }
3940
4510
  return {
3941
- x: 0,
3942
- y: 0,
3943
- width: win.document.documentElement.clientWidth,
3944
- height: win.document.documentElement.clientHeight
4511
+ left: 0,
4512
+ top: 0,
4513
+ right: win.document.documentElement.clientWidth,
4514
+ bottom: win.document.documentElement.clientHeight
3945
4515
  };
3946
4516
  }
3947
4517
 
@@ -3958,12 +4528,21 @@ function subscribeToVisualViewportResize(element, visualScaleRef) {
3958
4528
  }
3959
4529
  }
3960
4530
  handleVisualResize();
3961
- vV.addEventListener('resize', handleVisualResize);
3962
- return () => {
3963
- vV.removeEventListener('resize', handleVisualResize);
3964
- };
4531
+ return useButton.addEventListener(vV, 'resize', handleVisualResize);
3965
4532
  }
3966
4533
 
4534
+ const SCRUB_AREA_STYLE = {
4535
+ touchAction: 'none',
4536
+ WebkitUserSelect: 'none',
4537
+ userSelect: 'none'
4538
+ };
4539
+
4540
+ /**
4541
+ * An interactive area where the user can click and drag to change the field value.
4542
+ * Renders a `<span>` element.
4543
+ *
4544
+ * Documentation: [Base UI Number Field](https://base-ui.com/react/components/number-field)
4545
+ */
3967
4546
  const NumberFieldScrubArea = /*#__PURE__*/React__namespace.forwardRef(function NumberFieldScrubArea(componentProps, forwardedRef) {
3968
4547
  const {
3969
4548
  render,
@@ -3971,6 +4550,7 @@ const NumberFieldScrubArea = /*#__PURE__*/React__namespace.forwardRef(function N
3971
4550
  direction = 'horizontal',
3972
4551
  pixelSensitivity = 2,
3973
4552
  teleportDistance,
4553
+ style,
3974
4554
  ...elementProps
3975
4555
  } = componentProps;
3976
4556
  const {
@@ -3980,6 +4560,7 @@ const NumberFieldScrubArea = /*#__PURE__*/React__namespace.forwardRef(function N
3980
4560
  readOnly,
3981
4561
  inputRef,
3982
4562
  incrementValue,
4563
+ allowInputSyncRef,
3983
4564
  getStepAmount,
3984
4565
  onValueCommitted,
3985
4566
  lastChangedValueRef,
@@ -4027,15 +4608,15 @@ const NumberFieldScrubArea = /*#__PURE__*/React__namespace.forwardRef(function N
4027
4608
  };
4028
4609
  const cursorWidth = virtualCursor.offsetWidth;
4029
4610
  const cursorHeight = virtualCursor.offsetHeight;
4030
- if (newCoords.x + cursorWidth / 2 < rect.x) {
4031
- newCoords.x = rect.width - cursorWidth / 2;
4032
- } else if (newCoords.x + cursorWidth / 2 > rect.width) {
4033
- newCoords.x = rect.x - cursorWidth / 2;
4611
+ if (newCoords.x + cursorWidth / 2 < rect.left) {
4612
+ newCoords.x = rect.right - cursorWidth / 2;
4613
+ } else if (newCoords.x + cursorWidth / 2 > rect.right) {
4614
+ newCoords.x = rect.left - cursorWidth / 2;
4034
4615
  }
4035
- if (newCoords.y + cursorHeight / 2 < rect.y) {
4036
- newCoords.y = rect.height - cursorHeight / 2;
4037
- } else if (newCoords.y + cursorHeight / 2 > rect.height) {
4038
- newCoords.y = rect.y - cursorHeight / 2;
4616
+ if (newCoords.y + cursorHeight / 2 < rect.top) {
4617
+ newCoords.y = rect.bottom - cursorHeight / 2;
4618
+ } else if (newCoords.y + cursorHeight / 2 > rect.bottom) {
4619
+ newCoords.y = rect.top - cursorHeight / 2;
4039
4620
  }
4040
4621
  virtualCursorCoords.current = newCoords;
4041
4622
  updateCursorTransform(newCoords.x, newCoords.y);
@@ -4078,8 +4659,10 @@ const NumberFieldScrubArea = /*#__PURE__*/React__namespace.forwardRef(function N
4078
4659
 
4079
4660
  // Manually dispatch a click event if no movement happened, since
4080
4661
  // preventDefault on pointerdown prevents the browser click event.
4081
- if (!didMoveRef.current && pointerDownTargetRef.current != null) {
4082
- pointerDownTargetRef.current.dispatchEvent(new MouseEvent('click', {
4662
+ const pointerDownTarget = pointerDownTargetRef.current;
4663
+ const input = inputRef.current;
4664
+ if (!didMoveRef.current && pointerDownTarget != null && input) {
4665
+ pointerDownTarget.dispatchEvent(new (floatingUi_utils_dom.getWindow(input).MouseEvent)('click', {
4083
4666
  bubbles: true,
4084
4667
  cancelable: true
4085
4668
  }));
@@ -4088,7 +4671,7 @@ const NumberFieldScrubArea = /*#__PURE__*/React__namespace.forwardRef(function N
4088
4671
  pointerDownTargetRef.current = null;
4089
4672
  }
4090
4673
  }
4091
- if (useButton.isFirefox) {
4674
+ if (useScrollLock.gecko) {
4092
4675
  // Firefox needs a small delay here when soft-clicking as the pointer
4093
4676
  // lock will not release otherwise.
4094
4677
  exitPointerLockTimeout.start(20, handler);
@@ -4113,9 +4696,10 @@ const NumberFieldScrubArea = /*#__PURE__*/React__namespace.forwardRef(function N
4113
4696
  cumulativeDelta = 0;
4114
4697
  didMoveRef.current = true;
4115
4698
  const dValue = direction === 'vertical' ? -movementY : movementX;
4116
- const stepAmount = getStepAmount(event) ?? NumberFieldInput.DEFAULT_STEP;
4699
+ const stepAmount = getStepAmount(event);
4117
4700
  const rawAmount = dValue * stepAmount;
4118
4701
  if (rawAmount !== 0) {
4702
+ allowInputSyncRef.current = true;
4119
4703
  incrementValue(Math.abs(rawAmount), {
4120
4704
  direction: rawAmount >= 0 ? 1 : -1,
4121
4705
  event,
@@ -4125,14 +4709,26 @@ const NumberFieldScrubArea = /*#__PURE__*/React__namespace.forwardRef(function N
4125
4709
  }
4126
4710
  }
4127
4711
  const win = floatingUi_utils_dom.getWindow(inputRef.current);
4128
- win.addEventListener('pointerup', handleScrubPointerUp, true);
4129
- win.addEventListener('pointermove', handleScrubPointerMove, true);
4712
+ const unsubscribe = useScrollLock.mergeCleanups(useButton.addEventListener(win, 'pointerup', handleScrubPointerUp, true), useButton.addEventListener(win, 'pointermove', handleScrubPointerMove, true));
4130
4713
  return () => {
4131
4714
  exitPointerLockTimeout.clear();
4132
- win.removeEventListener('pointerup', handleScrubPointerUp, true);
4133
- win.removeEventListener('pointermove', handleScrubPointerMove, true);
4715
+ unsubscribe();
4134
4716
  };
4135
- }, [disabled, readOnly, incrementValue, isScrubbing, getStepAmount, inputRef, onScrubbingChange, onScrub, direction, pixelSensitivity, lastChangedValueRef, onValueCommitted, valueRef, exitPointerLockTimeout]);
4717
+ }, [disabled, readOnly, allowInputSyncRef, incrementValue, isScrubbing, getStepAmount, inputRef, onScrubbingChange, onScrub, direction, pixelSensitivity, lastChangedValueRef, onValueCommitted, valueRef, exitPointerLockTimeout]);
4718
+
4719
+ // If the scrub area unmounts mid-scrub, release pointer lock and clear the root's scrubbing
4720
+ // state so it doesn't stay locked or stuck. (No commit: there's no pointer release here.)
4721
+ React__namespace.useEffect(() => () => {
4722
+ if (isScrubbingRef.current) {
4723
+ isScrubbingRef.current = false;
4724
+ setRootScrubbing(false);
4725
+ try {
4726
+ useButton.ownerDocument(scrubAreaRef.current).exitPointerLock();
4727
+ } catch {
4728
+ // Ignore errors.
4729
+ }
4730
+ }
4731
+ }, [setRootScrubbing]);
4136
4732
 
4137
4733
  // Prevent scrolling using touch input when scrubbing.
4138
4734
  React__namespace.useEffect(function registerScrubberTouchPreventListener() {
@@ -4145,18 +4741,11 @@ const NumberFieldScrubArea = /*#__PURE__*/React__namespace.forwardRef(function N
4145
4741
  event.preventDefault();
4146
4742
  }
4147
4743
  }
4148
- element.addEventListener('touchstart', handleTouchStart);
4149
- return () => {
4150
- element.removeEventListener('touchstart', handleTouchStart);
4151
- };
4744
+ return useButton.addEventListener(element, 'touchstart', handleTouchStart);
4152
4745
  }, [disabled, readOnly]);
4153
4746
  const defaultProps = {
4154
4747
  role: 'presentation',
4155
- style: {
4156
- touchAction: 'none',
4157
- WebkitUserSelect: 'none',
4158
- userSelect: 'none'
4159
- },
4748
+ style: SCRUB_AREA_STYLE,
4160
4749
  async onPointerDown(event) {
4161
4750
  const isMainButton = !event.button || event.button === 0;
4162
4751
  if (event.defaultPrevented || readOnly || !isMainButton || disabled) {
@@ -4170,11 +4759,11 @@ const NumberFieldScrubArea = /*#__PURE__*/React__namespace.forwardRef(function N
4170
4759
  }
4171
4760
  isScrubbingRef.current = true;
4172
4761
  didMoveRef.current = false;
4173
- pointerDownTargetRef.current = event.target;
4762
+ pointerDownTargetRef.current = useButton.getTarget(event.nativeEvent);
4174
4763
  onScrubbingChange(true, event.nativeEvent);
4175
4764
 
4176
4765
  // WebKit causes significant layout shift with the native message, so we can't use it.
4177
- if (!isTouch && !useButton.isWebKit) {
4766
+ if (!isTouch && !useScrollLock.webkit) {
4178
4767
  try {
4179
4768
  // Avoid non-deterministic errors in testing environments. This error sometimes
4180
4769
  // appears:
@@ -4184,10 +4773,11 @@ const NumberFieldScrubArea = /*#__PURE__*/React__namespace.forwardRef(function N
4184
4773
  } catch (error) {
4185
4774
  setIsPointerLockDenied(true);
4186
4775
  } finally {
4776
+ // `onScrubbingChange` already wraps its state updates in `flushSync`, so re-emit the
4777
+ // scrubbing state directly (no extra nested `flushSync`) to reflect the resolved
4778
+ // pointer-lock result on the cursor.
4187
4779
  if (isScrubbingRef.current) {
4188
- ReactDOM__namespace.flushSync(() => {
4189
- onScrubbingChange(true, event.nativeEvent);
4190
- });
4780
+ onScrubbingChange(true, event.nativeEvent);
4191
4781
  }
4192
4782
  }
4193
4783
  }
@@ -4203,12 +4793,8 @@ const NumberFieldScrubArea = /*#__PURE__*/React__namespace.forwardRef(function N
4203
4793
  isScrubbing,
4204
4794
  isTouchInput,
4205
4795
  isPointerLockDenied,
4206
- scrubAreaCursorRef,
4207
- scrubAreaRef,
4208
- direction,
4209
- pixelSensitivity,
4210
- teleportDistance
4211
- }), [isScrubbing, isTouchInput, isPointerLockDenied, direction, pixelSensitivity, teleportDistance]);
4796
+ scrubAreaCursorRef
4797
+ }), [isScrubbing, isTouchInput, isPointerLockDenied]);
4212
4798
  return /*#__PURE__*/jsxRuntime.jsx(NumberFieldScrubAreaContext.Provider, {
4213
4799
  value: contextValue,
4214
4800
  children: element
@@ -4216,6 +4802,13 @@ const NumberFieldScrubArea = /*#__PURE__*/React__namespace.forwardRef(function N
4216
4802
  });
4217
4803
  if (process.env.NODE_ENV !== "production") NumberFieldScrubArea.displayName = "NumberFieldScrubArea";
4218
4804
 
4805
+ const CURSOR_STYLE = {
4806
+ position: 'fixed',
4807
+ top: 0,
4808
+ left: 0,
4809
+ pointerEvents: 'none'
4810
+ };
4811
+
4219
4812
  /**
4220
4813
  * A custom element to display instead of the native cursor while using the scrub area.
4221
4814
  * Renders a `<span>` element.
@@ -4229,6 +4822,7 @@ const NumberFieldScrubAreaCursor = /*#__PURE__*/React__namespace.forwardRef(func
4229
4822
  const {
4230
4823
  render,
4231
4824
  className,
4825
+ style,
4232
4826
  ...elementProps
4233
4827
  } = componentProps;
4234
4828
  const {
@@ -4241,19 +4835,14 @@ const NumberFieldScrubAreaCursor = /*#__PURE__*/React__namespace.forwardRef(func
4241
4835
  scrubAreaCursorRef
4242
4836
  } = useNumberFieldScrubAreaContext();
4243
4837
  const [domElement, setDomElement] = React__namespace.useState(null);
4244
- const shouldRender = isScrubbing && !useButton.isWebKit && !isTouchInput && !isPointerLockDenied;
4838
+ const shouldRender = isScrubbing && !useScrollLock.webkit && !isTouchInput && !isPointerLockDenied;
4245
4839
  const element = useRenderElement.useRenderElement('span', componentProps, {
4246
4840
  enabled: shouldRender,
4247
4841
  ref: [forwardedRef, scrubAreaCursorRef, setDomElement],
4248
4842
  state,
4249
4843
  props: [{
4250
4844
  role: 'presentation',
4251
- style: {
4252
- position: 'fixed',
4253
- top: 0,
4254
- left: 0,
4255
- pointerEvents: 'none'
4256
- }
4845
+ style: CURSOR_STYLE
4257
4846
  }, elementProps],
4258
4847
  stateAttributesMapping: NumberFieldInput.stateAttributesMapping
4259
4848
  });
@@ -4282,6 +4871,7 @@ const SelectLabel = /*#__PURE__*/React__namespace.forwardRef(function SelectLabe
4282
4871
  const {
4283
4872
  render,
4284
4873
  className,
4874
+ style,
4285
4875
  ...elementProps
4286
4876
  } = componentProps;
4287
4877
  // Keep label id derived from the root and ignore runtime `id` overrides from untyped consumers.
@@ -4291,8 +4881,8 @@ const SelectLabel = /*#__PURE__*/React__namespace.forwardRef(function SelectLabe
4291
4881
  const {
4292
4882
  store
4293
4883
  } = SelectGroupLabel.useSelectRootContext();
4294
- const triggerElement = InternalBackdrop.useStore(store, SelectGroupLabel.selectors.triggerElement);
4295
- const rootId = InternalBackdrop.useStore(store, SelectGroupLabel.selectors.id);
4884
+ const triggerElement = useScrollLock.useStore(store, SelectGroupLabel.selectors.triggerElement);
4885
+ const rootId = useScrollLock.useStore(store, SelectGroupLabel.selectors.id);
4296
4886
  const defaultLabelId = resolveAriaLabelledBy.getDefaultLabelId(rootId);
4297
4887
  const labelProps = useLabel.useLabel({
4298
4888
  id: defaultLabelId,
@@ -4318,14 +4908,15 @@ if (process.env.NODE_ENV !== "production") SelectLabel.displayName = "SelectLabe
4318
4908
  */
4319
4909
  const SelectIcon = /*#__PURE__*/React__namespace.forwardRef(function SelectIcon(componentProps, forwardedRef) {
4320
4910
  const {
4321
- className,
4322
4911
  render,
4912
+ className,
4913
+ style,
4323
4914
  ...elementProps
4324
4915
  } = componentProps;
4325
4916
  const {
4326
4917
  store
4327
4918
  } = SelectGroupLabel.useSelectRootContext();
4328
- const open = InternalBackdrop.useStore(store, SelectGroupLabel.selectors.open);
4919
+ const open = useScrollLock.useStore(store, SelectGroupLabel.selectors.open);
4329
4920
  const state = {
4330
4921
  open
4331
4922
  };
@@ -4336,35 +4927,36 @@ const SelectIcon = /*#__PURE__*/React__namespace.forwardRef(function SelectIcon(
4336
4927
  'aria-hidden': true,
4337
4928
  children: '▼'
4338
4929
  }, elementProps],
4339
- stateAttributesMapping: InternalBackdrop.triggerOpenStateMapping
4930
+ stateAttributesMapping: useScrollLock.triggerOpenStateMapping
4340
4931
  });
4341
4932
  return element;
4342
4933
  });
4343
4934
  if (process.env.NODE_ENV !== "production") SelectIcon.displayName = "SelectIcon";
4344
4935
 
4345
4936
  const stateAttributesMapping$1 = {
4346
- ...InternalBackdrop.popupStateMapping,
4937
+ ...useScrollLock.popupStateMapping,
4347
4938
  ...useButton.transitionStatusMapping
4348
4939
  };
4349
4940
 
4350
4941
  /**
4351
- * An overlay displayed beneath the menu popup.
4942
+ * An overlay displayed beneath the select popup.
4352
4943
  * Renders a `<div>` element.
4353
4944
  *
4354
4945
  * Documentation: [Base UI Select](https://base-ui.com/react/components/select)
4355
4946
  */
4356
4947
  const SelectBackdrop = /*#__PURE__*/React__namespace.forwardRef(function SelectBackdrop(componentProps, forwardedRef) {
4357
4948
  const {
4358
- className,
4359
4949
  render,
4950
+ className,
4951
+ style,
4360
4952
  ...elementProps
4361
4953
  } = componentProps;
4362
4954
  const {
4363
4955
  store
4364
4956
  } = SelectGroupLabel.useSelectRootContext();
4365
- const open = InternalBackdrop.useStore(store, SelectGroupLabel.selectors.open);
4366
- const mounted = InternalBackdrop.useStore(store, SelectGroupLabel.selectors.mounted);
4367
- const transitionStatus = InternalBackdrop.useStore(store, SelectGroupLabel.selectors.transitionStatus);
4957
+ const open = useScrollLock.useStore(store, SelectGroupLabel.selectors.open);
4958
+ const mounted = useScrollLock.useStore(store, SelectGroupLabel.selectors.mounted);
4959
+ const transitionStatus = useScrollLock.useStore(store, SelectGroupLabel.selectors.transitionStatus);
4368
4960
  const state = {
4369
4961
  open,
4370
4962
  transitionStatus
@@ -4387,7 +4979,7 @@ const SelectBackdrop = /*#__PURE__*/React__namespace.forwardRef(function SelectB
4387
4979
  if (process.env.NODE_ENV !== "production") SelectBackdrop.displayName = "SelectBackdrop";
4388
4980
 
4389
4981
  const stateAttributesMapping = {
4390
- ...InternalBackdrop.popupStateMapping,
4982
+ ...useScrollLock.popupStateMapping,
4391
4983
  ...useButton.transitionStatusMapping
4392
4984
  };
4393
4985
 
@@ -4399,8 +4991,9 @@ const stateAttributesMapping = {
4399
4991
  */
4400
4992
  const SelectArrow = /*#__PURE__*/React__namespace.forwardRef(function SelectArrow(componentProps, forwardedRef) {
4401
4993
  const {
4402
- className,
4403
4994
  render,
4995
+ className,
4996
+ style,
4404
4997
  ...elementProps
4405
4998
  } = componentProps;
4406
4999
  const {
@@ -4414,7 +5007,7 @@ const SelectArrow = /*#__PURE__*/React__namespace.forwardRef(function SelectArro
4414
5007
  arrowUncentered,
4415
5008
  alignItemWithTriggerActive
4416
5009
  } = SelectGroupLabel.useSelectPositionerContext();
4417
- const open = InternalBackdrop.useStore(store, SelectGroupLabel.selectors.open, true);
5010
+ const open = useScrollLock.useStore(store, SelectGroupLabel.selectors.open);
4418
5011
  const state = {
4419
5012
  open,
4420
5013
  side,
@@ -4444,10 +5037,12 @@ const SelectScrollArrow = /*#__PURE__*/React__namespace.forwardRef(function Sele
4444
5037
  const {
4445
5038
  render,
4446
5039
  className,
5040
+ style,
4447
5041
  direction,
4448
5042
  keepMounted = false,
4449
5043
  ...elementProps
4450
5044
  } = componentProps;
5045
+ const isUp = direction === 'up';
4451
5046
  const {
4452
5047
  store,
4453
5048
  popupRef,
@@ -4460,15 +5055,16 @@ const SelectScrollArrow = /*#__PURE__*/React__namespace.forwardRef(function Sele
4460
5055
  scrollDownArrowRef,
4461
5056
  scrollUpArrowRef
4462
5057
  } = SelectGroupLabel.useSelectPositionerContext();
4463
- const visibleSelector = direction === 'up' ? SelectGroupLabel.selectors.scrollUpArrowVisible : SelectGroupLabel.selectors.scrollDownArrowVisible;
4464
- const stateVisible = InternalBackdrop.useStore(store, visibleSelector);
4465
- const openMethod = InternalBackdrop.useStore(store, SelectGroupLabel.selectors.openMethod);
5058
+ const visibleSelector = isUp ? SelectGroupLabel.selectors.scrollUpArrowVisible : SelectGroupLabel.selectors.scrollDownArrowVisible;
5059
+ const stateVisible = useScrollLock.useStore(store, visibleSelector);
5060
+ const openMethod = useScrollLock.useStore(store, SelectGroupLabel.selectors.openMethod);
4466
5061
 
4467
5062
  // Scroll arrows are disabled for touch modality as they are a hover-only element.
4468
5063
  const visible = stateVisible && openMethod !== 'touch';
4469
5064
  const timeout = useButton.useTimeout();
4470
- const scrollArrowRef = direction === 'up' ? scrollUpArrowRef : scrollDownArrowRef;
5065
+ const scrollArrowRef = isUp ? scrollUpArrowRef : scrollDownArrowRef;
4471
5066
  const {
5067
+ mounted,
4472
5068
  transitionStatus,
4473
5069
  setMounted
4474
5070
  } = useButton.useTransitionStatus(visible);
@@ -4501,7 +5097,7 @@ const SelectScrollArrow = /*#__PURE__*/React__namespace.forwardRef(function Sele
4501
5097
  };
4502
5098
  const defaultProps = {
4503
5099
  'aria-hidden': true,
4504
- children: direction === 'up' ? '▲' : '▼',
5100
+ children: isUp ? '▲' : '▼',
4505
5101
  style: {
4506
5102
  position: 'absolute'
4507
5103
  },
@@ -4517,72 +5113,25 @@ const SelectScrollArrow = /*#__PURE__*/React__namespace.forwardRef(function Sele
4517
5113
  }
4518
5114
  store.set('activeIndex', null);
4519
5115
  handleScrollArrowVisibility();
4520
- const isScrolledToTop = scroller.scrollTop === 0;
4521
- const isScrolledToBottom = Math.round(scroller.scrollTop + scroller.clientHeight) >= scroller.scrollHeight;
4522
- const list = listRef.current;
5116
+ const maxScrollTop = scrollEdges.getMaxScrollOffset(scroller.scrollHeight, scroller.clientHeight);
5117
+ const scrollTop = scrollEdges.normalizeScrollOffset(scroller.scrollTop, maxScrollTop);
5118
+ const isScrolledToEdge = scrollTop === (isUp ? 0 : maxScrollTop);
5119
+ const items = listRef.current;
5120
+ if (scrollTop !== scroller.scrollTop) {
5121
+ scroller.scrollTop = scrollTop;
5122
+ }
4523
5123
 
4524
5124
  // Fallback when there are no items registered yet.
4525
- if (list.length === 0) {
4526
- if (direction === 'up') {
4527
- store.set('scrollUpArrowVisible', !isScrolledToTop);
4528
- } else {
4529
- store.set('scrollDownArrowVisible', !isScrolledToBottom);
4530
- }
5125
+ if (items.length === 0) {
5126
+ store.set(isUp ? 'scrollUpArrowVisible' : 'scrollDownArrowVisible', !isScrolledToEdge);
4531
5127
  }
4532
- if (direction === 'up' && isScrolledToTop || direction === 'down' && isScrolledToBottom) {
5128
+ if (isScrolledToEdge) {
4533
5129
  timeout.clear();
4534
5130
  return;
4535
5131
  }
4536
- if ((store.state.listElement || popupRef.current) && listRef.current && listRef.current.length > 0) {
4537
- const items = listRef.current;
5132
+ if (items.length > 0) {
4538
5133
  const scrollArrowHeight = scrollArrowRef.current?.offsetHeight || 0;
4539
- if (direction === 'up') {
4540
- let firstVisibleIndex = 0;
4541
- const scrollTop = scroller.scrollTop + scrollArrowHeight;
4542
- for (let i = 0; i < items.length; i += 1) {
4543
- const item = items[i];
4544
- if (item) {
4545
- const itemTop = item.offsetTop;
4546
- if (itemTop >= scrollTop) {
4547
- firstVisibleIndex = i;
4548
- break;
4549
- }
4550
- }
4551
- }
4552
- const targetIndex = Math.max(0, firstVisibleIndex - 1);
4553
- if (targetIndex < firstVisibleIndex) {
4554
- const targetItem = items[targetIndex];
4555
- if (targetItem) {
4556
- scroller.scrollTop = Math.max(0, targetItem.offsetTop - scrollArrowHeight);
4557
- }
4558
- } else {
4559
- // Already at the first item; ensure we reach the absolute top to account for group labels.
4560
- scroller.scrollTop = 0;
4561
- }
4562
- } else {
4563
- let lastVisibleIndex = items.length - 1;
4564
- const scrollBottom = scroller.scrollTop + scroller.clientHeight - scrollArrowHeight;
4565
- for (let i = 0; i < items.length; i += 1) {
4566
- const item = items[i];
4567
- if (item) {
4568
- const itemBottom = item.offsetTop + item.offsetHeight;
4569
- if (itemBottom > scrollBottom) {
4570
- lastVisibleIndex = Math.max(0, i - 1);
4571
- break;
4572
- }
4573
- }
4574
- }
4575
- const targetIndex = Math.min(items.length - 1, lastVisibleIndex + 1);
4576
- if (targetIndex > lastVisibleIndex) {
4577
- const targetItem = items[targetIndex];
4578
- if (targetItem) {
4579
- scroller.scrollTop = targetItem.offsetTop + targetItem.offsetHeight - scroller.clientHeight + scrollArrowHeight;
4580
- }
4581
- } else {
4582
- // Already at the last item; ensure we reach the true bottom.
4583
- scroller.scrollTop = scroller.scrollHeight - scroller.clientHeight;
4584
- }
4585
- }
5134
+ scroller.scrollTop = getTargetScrollTop(items, isUp, scrollTop, scroller.clientHeight, scrollArrowHeight, maxScrollTop);
4586
5135
  }
4587
5136
  timeout.start(40, scrollNextItem);
4588
5137
  }
@@ -4595,15 +5144,44 @@ const SelectScrollArrow = /*#__PURE__*/React__namespace.forwardRef(function Sele
4595
5144
  const element = useRenderElement.useRenderElement('div', componentProps, {
4596
5145
  ref: [forwardedRef, scrollArrowRef],
4597
5146
  state,
4598
- props: [defaultProps, elementProps]
5147
+ props: [defaultProps, elementProps],
5148
+ stateAttributesMapping: useButton.transitionStatusMapping
4599
5149
  });
4600
- const shouldRender = visible || keepMounted;
5150
+ const shouldRender = mounted || keepMounted;
4601
5151
  if (!shouldRender) {
4602
5152
  return null;
4603
5153
  }
4604
5154
  return element;
4605
5155
  });
4606
5156
  if (process.env.NODE_ENV !== "production") SelectScrollArrow.displayName = "SelectScrollArrow";
5157
+ function getTargetScrollTop(items, isUp, scrollTop, clientHeight, scrollArrowHeight, maxScrollTop) {
5158
+ if (isUp) {
5159
+ let firstVisibleIndex = 0;
5160
+ const visibleTop = scrollTop + scrollArrowHeight - scrollEdges.SCROLL_EDGE_TOLERANCE_PX;
5161
+ for (let i = 0; i < items.length; i += 1) {
5162
+ const item = items[i];
5163
+ if (item && item.offsetTop >= visibleTop) {
5164
+ firstVisibleIndex = i;
5165
+ break;
5166
+ }
5167
+ }
5168
+ const targetIndex = Math.max(0, firstVisibleIndex - 1);
5169
+ const targetItem = items[targetIndex];
5170
+ return targetIndex < firstVisibleIndex && targetItem ? scrollEdges.normalizeScrollOffset(targetItem.offsetTop - scrollArrowHeight, maxScrollTop) : 0;
5171
+ }
5172
+ let lastVisibleIndex = items.length - 1;
5173
+ const visibleBottom = scrollTop + clientHeight - scrollArrowHeight + scrollEdges.SCROLL_EDGE_TOLERANCE_PX;
5174
+ for (let i = 0; i < items.length; i += 1) {
5175
+ const item = items[i];
5176
+ if (item && item.offsetTop + item.offsetHeight > visibleBottom) {
5177
+ lastVisibleIndex = Math.max(0, i - 1);
5178
+ break;
5179
+ }
5180
+ }
5181
+ const targetIndex = Math.min(items.length - 1, lastVisibleIndex + 1);
5182
+ const targetItem = items[targetIndex];
5183
+ return targetIndex > lastVisibleIndex && targetItem ? scrollEdges.normalizeScrollOffset(targetItem.offsetTop + targetItem.offsetHeight - clientHeight + scrollArrowHeight, maxScrollTop) : maxScrollTop;
5184
+ }
4607
5185
 
4608
5186
  /**
4609
5187
  * An element that scrolls the select popup down when hovered. Does not render when using touch input.