@jobber/components 8.24.1 → 8.24.2-visual-reg-d5e3ec5.19

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 (73) hide show
  1. package/ProgressIndicator.d.ts +1 -0
  2. package/{DescriptionList.js → ProgressIndicator.js} +3 -3
  3. package/dist/AtlantisThemeContext/AtlantisThemeContext.d.ts +1 -1
  4. package/dist/AtlantisThemeContext/index.cjs +1 -2
  5. package/dist/AtlantisThemeContext/index.mjs +1 -2
  6. package/dist/AtlantisThemeContext/themeStore.d.ts +11 -0
  7. package/dist/AtlantisThemeContext/types.d.ts +14 -0
  8. package/dist/AtlantisThemeContext/updateTheme.d.ts +1 -1
  9. package/dist/AtlantisThemeContext-cjs.js +58 -17
  10. package/dist/AtlantisThemeContext-es.js +60 -20
  11. package/dist/BottomSheet-cjs.js +8 -7
  12. package/dist/BottomSheet-es.js +9 -8
  13. package/dist/ComboboxChipRemove-es.js +2 -2
  14. package/dist/DatePicker-es.js +1 -1
  15. package/dist/Dialog/Dialog.d.ts +47 -3
  16. package/dist/Dialog/Dialog.types.d.ts +1 -15
  17. package/dist/Dialog/DialogBottomSheet.d.ts +12 -13
  18. package/dist/Dialog/DialogModal.d.ts +11 -11
  19. package/dist/Dialog/dialogComposableShared.d.ts +13 -3
  20. package/dist/DrawerDescription-cjs.js +4 -97
  21. package/dist/DrawerDescription-es.js +6 -98
  22. package/dist/InputNumberExperimental-cjs.js +21 -25
  23. package/dist/InputNumberExperimental-es.js +22 -26
  24. package/dist/MenuSubmenuTrigger-es.js +3 -3
  25. package/dist/Modal/index.cjs +12 -2
  26. package/dist/Modal/index.mjs +13 -3
  27. package/dist/ProgressBar/ProgressBar.d.ts +7 -0
  28. package/dist/ProgressBar-cjs.js +4 -0
  29. package/dist/ProgressBar-es.js +4 -0
  30. package/dist/ProgressIndicator/ProgressIndicator.d.ts +60 -0
  31. package/dist/ProgressIndicator/index.cjs +9 -0
  32. package/dist/ProgressIndicator/index.d.ts +1 -0
  33. package/dist/ProgressIndicator/index.mjs +3 -0
  34. package/dist/ProgressIndicator-cjs.js +43 -0
  35. package/dist/ProgressIndicator-es.js +41 -0
  36. package/dist/ScrollAreaViewport-cjs.js +2122 -12
  37. package/dist/ScrollAreaViewport-es.js +2118 -13
  38. package/dist/dialogReturnFocus-cjs.js +64 -35
  39. package/dist/dialogReturnFocus-es.js +66 -37
  40. package/dist/docs/AtlantisThemeContext/AtlantisThemeContext.md +41 -0
  41. package/dist/docs/DataTable/DataTable.md +0 -4
  42. package/dist/docs/Dialog/Dialog.md +1006 -0
  43. package/dist/docs/Icon/Icon.md +60 -53
  44. package/dist/docs/InputNumberExperimental/InputNumberExperimental.md +578 -0
  45. package/dist/docs/ProgressBar/ProgressBar.md +9 -0
  46. package/dist/docs/ProgressIndicator/ProgressIndicator.md +105 -0
  47. package/dist/docs/Table/Table.md +1 -3
  48. package/dist/docs/index.md +3 -1
  49. package/dist/index.cjs +3 -4
  50. package/dist/index.d.mts +1 -1
  51. package/dist/index.d.ts +1 -1
  52. package/dist/index.mjs +2 -3
  53. package/dist/isTypedArray-es.js +1 -1
  54. package/dist/primitives/InputNumberExperimental/InputNumberExperimental.d.ts +31 -15
  55. package/dist/primitives/InputNumberExperimental/types.d.ts +0 -1
  56. package/dist/styles.css +172 -51
  57. package/dist/unstyledPrimitives/index.cjs +218 -2230
  58. package/dist/unstyledPrimitives/index.mjs +224 -2236
  59. package/dist/useButton-es.js +1 -1
  60. package/dist/useScrollLock-es.js +2 -2
  61. package/dist/useScrollToActive-es.js +1 -1
  62. package/dist/utils/meta/meta.json +1 -1
  63. package/package.json +7 -7
  64. package/DescriptionList.d.ts +0 -1
  65. package/dist/DescriptionList/DescriptionList.d.ts +0 -11
  66. package/dist/DescriptionList/index.cjs +0 -10
  67. package/dist/DescriptionList/index.d.ts +0 -1
  68. package/dist/DescriptionList/index.mjs +0 -4
  69. package/dist/DescriptionList-cjs.js +0 -14
  70. package/dist/DescriptionList-es.js +0 -12
  71. package/dist/docs/DescriptionList/DescriptionList.md +0 -25
  72. package/dist/updateTheme-cjs.js +0 -20
  73. package/dist/updateTheme-es.js +0 -18
@@ -5,9 +5,10 @@ var useRenderElement = require('./useRenderElement-cjs.js');
5
5
  var useScrollLock = require('./useScrollLock-cjs.js');
6
6
  var useButton = require('./useButton-cjs.js');
7
7
  var scrollEdges = require('./scrollEdges-cjs.js');
8
- var clamp = require('./clamp-cjs.js');
9
8
  var jsxRuntime = require('react/jsx-runtime');
10
9
  var floatingUi_utils_dom = require('./floating-ui.utils.dom-cjs.js');
10
+ var ReactDOM = require('react-dom');
11
+ var clamp = require('./clamp-cjs.js');
11
12
 
12
13
  function _interopNamespaceDefault(e) {
13
14
  var n = Object.create(null);
@@ -27,6 +28,7 @@ function _interopNamespaceDefault(e) {
27
28
  }
28
29
 
29
30
  var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
31
+ var ReactDOM__namespace = /*#__PURE__*/_interopNamespaceDefault(ReactDOM);
30
32
 
31
33
  function getEmptyRootContext() {
32
34
  return new useScrollLock.FloatingRootStore({
@@ -460,6 +462,98 @@ function DialogRoot(props) {
460
462
  return useRenderDialogRoot(props, mode);
461
463
  }
462
464
 
465
+ let DialogViewportDataAttributes = function (DialogViewportDataAttributes) {
466
+ /**
467
+ * Present when the dialog is open.
468
+ */
469
+ DialogViewportDataAttributes[DialogViewportDataAttributes["open"] = useScrollLock.CommonPopupDataAttributes.open] = "open";
470
+ /**
471
+ * Present when the dialog is closed.
472
+ */
473
+ DialogViewportDataAttributes[DialogViewportDataAttributes["closed"] = useScrollLock.CommonPopupDataAttributes.closed] = "closed";
474
+ /**
475
+ * Present when the dialog is animating in.
476
+ */
477
+ DialogViewportDataAttributes[DialogViewportDataAttributes["startingStyle"] = useScrollLock.CommonPopupDataAttributes.startingStyle] = "startingStyle";
478
+ /**
479
+ * Present when the dialog is animating out.
480
+ */
481
+ DialogViewportDataAttributes[DialogViewportDataAttributes["endingStyle"] = useScrollLock.CommonPopupDataAttributes.endingStyle] = "endingStyle";
482
+ /**
483
+ * Present when the dialog is nested within another dialog.
484
+ */
485
+ DialogViewportDataAttributes["nested"] = "data-nested";
486
+ /**
487
+ * Present when the dialog has other open dialogs nested within it.
488
+ */
489
+ DialogViewportDataAttributes["nestedDialogOpen"] = "data-nested-dialog-open";
490
+ return DialogViewportDataAttributes;
491
+ }({});
492
+
493
+ const stateAttributesMapping$2 = {
494
+ ...useScrollLock.popupStateMapping,
495
+ ...useButton.transitionStatusMapping,
496
+ nested(value) {
497
+ return value ? {
498
+ [DialogViewportDataAttributes.nested]: ''
499
+ } : null;
500
+ },
501
+ nestedDialogOpen(value) {
502
+ return value ? {
503
+ [DialogViewportDataAttributes.nestedDialogOpen]: ''
504
+ } : null;
505
+ }
506
+ };
507
+
508
+ /**
509
+ * A positioning container for the dialog popup that can be made scrollable.
510
+ * Renders a `<div>` element.
511
+ *
512
+ * Documentation: [Base UI Dialog](https://base-ui.com/react/components/dialog)
513
+ */
514
+ const DialogViewport = /*#__PURE__*/React__namespace.forwardRef(function DialogViewport(componentProps, forwardedRef) {
515
+ const {
516
+ render,
517
+ className,
518
+ style,
519
+ children,
520
+ ...elementProps
521
+ } = componentProps;
522
+ const keepMounted = useDialogPortalContext();
523
+ const {
524
+ store
525
+ } = useDialogRootContext();
526
+ const open = store.useState('open');
527
+ const nested = store.useState('nested');
528
+ const transitionStatus = store.useState('transitionStatus');
529
+ const nestedOpenDialogCount = store.useState('nestedOpenDialogCount');
530
+ const mounted = store.useState('mounted');
531
+ const setViewportElement = store.useStateSetter('viewportElement');
532
+ const nestedDialogOpen = nestedOpenDialogCount > 0;
533
+ const state = {
534
+ open,
535
+ nested,
536
+ transitionStatus,
537
+ nestedDialogOpen
538
+ };
539
+ const shouldRender = keepMounted || mounted;
540
+ return useRenderElement.useRenderElement('div', componentProps, {
541
+ enabled: shouldRender,
542
+ state,
543
+ ref: [forwardedRef, setViewportElement],
544
+ stateAttributesMapping: stateAttributesMapping$2,
545
+ props: [{
546
+ role: 'presentation',
547
+ hidden: !mounted,
548
+ style: {
549
+ pointerEvents: !open ? 'none' : undefined
550
+ },
551
+ children
552
+ }, elementProps]
553
+ });
554
+ });
555
+ if (process.env.NODE_ENV !== "production") DialogViewport.displayName = "DialogViewport";
556
+
463
557
  /**
464
558
  * A button that opens the dialog.
465
559
  * Renders a `<button>` element.
@@ -1433,13 +1527,2025 @@ function DrawerProviderReporter() {
1433
1527
  return null;
1434
1528
  }
1435
1529
 
1436
- /**
1437
- * A button that opens the drawer.
1438
- * Renders a `<button>` element.
1439
- *
1440
- * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)
1441
- */
1442
- const DrawerTrigger = DialogTrigger;
1530
+ function isScrollable(element, axis,
1531
+ // When true, a container that overflows only once extra space is added (e.g. drawer
1532
+ // keyboard scroll slack) still counts, as long as it has layout size on the axis.
1533
+ allowOverflowIntent = false) {
1534
+ const style = floatingUi_utils_dom.getComputedStyle(element);
1535
+ if (axis === 'vertical') {
1536
+ const overflowY = style.overflowY;
1537
+ if (overflowY !== 'auto' && overflowY !== 'scroll') {
1538
+ return false;
1539
+ }
1540
+ return allowOverflowIntent ? element.clientHeight > 0 : element.scrollHeight > element.clientHeight;
1541
+ }
1542
+ const overflowX = style.overflowX;
1543
+ if (overflowX !== 'auto' && overflowX !== 'scroll') {
1544
+ return false;
1545
+ }
1546
+ return allowOverflowIntent ? element.clientWidth > 0 : element.scrollWidth > element.clientWidth;
1547
+ }
1548
+ function hasScrollableAncestor(target, root, axes) {
1549
+ // `getParentNode` crosses shadow boundaries (and slots), so a target inside a shadow root
1550
+ // still walks up to scrollable ancestors in the light DOM.
1551
+ let node = target;
1552
+ while (floatingUi_utils_dom.isHTMLElement(node) && node !== root && !floatingUi_utils_dom.isLastTraversableNode(node)) {
1553
+ for (const axis of axes) {
1554
+ if (isScrollable(node, axis)) {
1555
+ return true;
1556
+ }
1557
+ }
1558
+ node = floatingUi_utils_dom.getParentNode(node);
1559
+ }
1560
+ return false;
1561
+ }
1562
+ function findScrollableTouchTarget(target, root, axis = 'vertical', allowOverflowIntent = false) {
1563
+ // `getParentNode` crosses shadow boundaries (and slots), so a target inside a shadow root
1564
+ // still reaches a scrollable ancestor in the light DOM.
1565
+ let node = floatingUi_utils_dom.isHTMLElement(target) ? target : null;
1566
+ while (floatingUi_utils_dom.isHTMLElement(node) && node !== root && !floatingUi_utils_dom.isLastTraversableNode(node)) {
1567
+ if (isScrollable(node, axis, allowOverflowIntent)) {
1568
+ return node;
1569
+ }
1570
+ node = floatingUi_utils_dom.getParentNode(node);
1571
+ }
1572
+ return isScrollable(root, axis, allowOverflowIntent) ? root : null;
1573
+ }
1574
+
1575
+ function getElementAtPoint(doc, x, y) {
1576
+ return typeof doc?.elementFromPoint === 'function' ? doc.elementFromPoint(x, y) : null;
1577
+ }
1578
+
1579
+ const DEFAULT_SWIPE_THRESHOLD = 40;
1580
+ const REVERSE_CANCEL_THRESHOLD = 10;
1581
+ const MIN_DRAG_THRESHOLD = 1;
1582
+ const MIN_VELOCITY_DURATION_MS = 50;
1583
+ const MIN_RELEASE_VELOCITY_DURATION_MS = 16;
1584
+ const MAX_RELEASE_VELOCITY_AGE_MS = 80;
1585
+ const DEFAULT_IGNORE_SELECTOR = 'button,a,input,select,textarea,label,[role="button"]';
1586
+ function getDisplacement(direction, deltaX, deltaY) {
1587
+ switch (direction) {
1588
+ case 'up':
1589
+ return -deltaY;
1590
+ case 'down':
1591
+ return deltaY;
1592
+ case 'left':
1593
+ return -deltaX;
1594
+ case 'right':
1595
+ return deltaX;
1596
+ default:
1597
+ return 0;
1598
+ }
1599
+ }
1600
+ function getElementTransform(element) {
1601
+ const computedStyle = floatingUi_utils_dom.getWindow(element).getComputedStyle(element);
1602
+ const transform = computedStyle.transform;
1603
+ let translateX = 0;
1604
+ let translateY = 0;
1605
+ let scale = 1;
1606
+ if (transform && transform !== 'none') {
1607
+ const matrix = transform.match(/matrix(?:3d)?\(([^)]+)\)/);
1608
+ if (matrix) {
1609
+ const values = matrix[1].split(', ').map(parseFloat);
1610
+ if (values.length === 6) {
1611
+ translateX = values[4];
1612
+ translateY = values[5];
1613
+ scale = Math.sqrt(values[0] * values[0] + values[1] * values[1]);
1614
+ } else if (values.length === 16) {
1615
+ translateX = values[12];
1616
+ translateY = values[13];
1617
+ scale = values[0];
1618
+ }
1619
+ }
1620
+ }
1621
+ return {
1622
+ x: translateX,
1623
+ y: translateY,
1624
+ scale
1625
+ };
1626
+ }
1627
+ function getValidTimeStamp(timeStamp) {
1628
+ return Number.isFinite(timeStamp) && timeStamp > 0 ? timeStamp : null;
1629
+ }
1630
+ function getDragTransform(dragOffset, scale) {
1631
+ return `translate3d(${dragOffset.x}px,${dragOffset.y}px,0) scale(${scale})`;
1632
+ }
1633
+ function hasPrimaryMouseButton(buttons) {
1634
+ return buttons % 2 === 1;
1635
+ }
1636
+ function safelyChangePointerCapture(element, pointerId, method) {
1637
+ const pointerCaptureMethod = element[method];
1638
+ if (typeof pointerCaptureMethod !== 'function') {
1639
+ return;
1640
+ }
1641
+ try {
1642
+ pointerCaptureMethod.call(element, pointerId);
1643
+ } catch (error) {
1644
+ if (error && typeof error === 'object' && 'name' in error && error.name === 'NotFoundError') {
1645
+ return;
1646
+ }
1647
+ throw error;
1648
+ }
1649
+ }
1650
+ function useSwipeDismiss(options) {
1651
+ const {
1652
+ enabled,
1653
+ directions,
1654
+ elementRef,
1655
+ movementCssVars,
1656
+ canStart,
1657
+ ignoreSelectorWhenTouch = true,
1658
+ ignoreScrollableAncestors = false,
1659
+ swipeThreshold: swipeThresholdProp,
1660
+ onDismiss,
1661
+ onProgress,
1662
+ onCancel,
1663
+ onSwipeStart,
1664
+ onRelease,
1665
+ onSwipingChange,
1666
+ trackDrag = true
1667
+ } = options;
1668
+ const ignoreSelector = DEFAULT_IGNORE_SELECTOR;
1669
+ const primaryDirection = directions.length === 1 ? directions[0] : undefined;
1670
+ const swipeThresholdDefault = Math.max(0, typeof swipeThresholdProp === 'number' ? swipeThresholdProp : DEFAULT_SWIPE_THRESHOLD);
1671
+ const allowLeft = directions.includes('left');
1672
+ const allowRight = directions.includes('right');
1673
+ const allowUp = directions.includes('up');
1674
+ const allowDown = directions.includes('down');
1675
+ const hasHorizontal = allowLeft || allowRight;
1676
+ const hasVertical = allowUp || allowDown;
1677
+ const scrollAxes = React__namespace.useMemo(() => {
1678
+ const axes = [];
1679
+ if (hasVertical) {
1680
+ axes.push('vertical');
1681
+ }
1682
+ if (hasHorizontal) {
1683
+ axes.push('horizontal');
1684
+ }
1685
+ return axes;
1686
+ }, [hasHorizontal, hasVertical]);
1687
+ const [currentSwipeDirection, setCurrentSwipeDirection] = React__namespace.useState(undefined);
1688
+ const [isSwiping, setIsSwiping] = React__namespace.useState(false);
1689
+ const [dragDismissed, setDragDismissed] = React__namespace.useState(false);
1690
+ const dragStartPosRef = React__namespace.useRef({
1691
+ x: 0,
1692
+ y: 0
1693
+ });
1694
+ const dragOffsetRef = React__namespace.useRef({
1695
+ x: 0,
1696
+ y: 0
1697
+ });
1698
+ const lastMovePosRef = React__namespace.useRef(null);
1699
+ const initialTransformRef = React__namespace.useRef({
1700
+ x: 0,
1701
+ y: 0,
1702
+ scale: 1
1703
+ });
1704
+ const intendedSwipeDirectionRef = React__namespace.useRef(undefined);
1705
+ const maxSwipeDisplacementRef = React__namespace.useRef(0);
1706
+ const cancelledSwipeRef = React__namespace.useRef(false);
1707
+ const swipeCancelBaselineRef = React__namespace.useRef({
1708
+ x: 0,
1709
+ y: 0
1710
+ });
1711
+ const lockedDirectionRef = React__namespace.useRef(null);
1712
+ const isFirstPointerMoveRef = React__namespace.useRef(false);
1713
+ const pendingSwipeRef = React__namespace.useRef(false);
1714
+ const pendingSwipeStartPosRef = React__namespace.useRef(null);
1715
+ const swipeFromScrollableRef = React__namespace.useRef(false);
1716
+ const sawPrimaryButtonsOnMoveRef = React__namespace.useRef(false);
1717
+ const elementSizeRef = React__namespace.useRef({
1718
+ width: 0,
1719
+ height: 0
1720
+ });
1721
+ const swipeProgressRef = React__namespace.useRef(0);
1722
+ const swipeThresholdRef = React__namespace.useRef(swipeThresholdDefault);
1723
+ const swipeStartTimeRef = React__namespace.useRef(null);
1724
+ const lastDragSampleRef = React__namespace.useRef(null);
1725
+ const lastDragVelocityRef = React__namespace.useRef({
1726
+ x: 0,
1727
+ y: 0
1728
+ });
1729
+ const lastProgressDetailsRef = React__namespace.useRef(null);
1730
+ const isSwipingRef = React__namespace.useRef(false);
1731
+ const dragStyleSnapshotRef = React__namespace.useRef(null);
1732
+ const setSwiping = useButton.useStableCallback(nextSwiping => {
1733
+ if (isSwipingRef.current === nextSwiping) {
1734
+ return;
1735
+ }
1736
+ isSwipingRef.current = nextSwiping;
1737
+ setIsSwiping(nextSwiping);
1738
+ onSwipingChange?.(nextSwiping);
1739
+ });
1740
+ function resolveSwipeThreshold(direction) {
1741
+ if (!direction) {
1742
+ return;
1743
+ }
1744
+ if (typeof swipeThresholdProp !== 'function') {
1745
+ swipeThresholdRef.current = swipeThresholdDefault;
1746
+ return;
1747
+ }
1748
+ const element = elementRef.current;
1749
+ if (!element) {
1750
+ return;
1751
+ }
1752
+ const value = swipeThresholdProp({
1753
+ element,
1754
+ direction
1755
+ });
1756
+ swipeThresholdRef.current = Math.max(0, value);
1757
+ }
1758
+ const updateSwipeProgress = useButton.useStableCallback((progress, details) => {
1759
+ const nextProgress = Number.isFinite(progress) ? clamp.clamp(progress, 0, 1) : 0;
1760
+ const progressChanged = nextProgress !== swipeProgressRef.current;
1761
+ let detailsChanged = false;
1762
+ if (details) {
1763
+ const lastDetails = lastProgressDetailsRef.current;
1764
+ detailsChanged = !lastDetails || lastDetails.deltaX !== details.deltaX || lastDetails.deltaY !== details.deltaY || lastDetails.direction !== details.direction;
1765
+ }
1766
+ if (!progressChanged && !detailsChanged) {
1767
+ return;
1768
+ }
1769
+ swipeProgressRef.current = nextProgress;
1770
+ if (details) {
1771
+ lastProgressDetailsRef.current = details;
1772
+ } else if (progressChanged) {
1773
+ lastProgressDetailsRef.current = null;
1774
+ }
1775
+ onProgress?.(nextProgress, details);
1776
+ });
1777
+ const syncDragStyles = useButton.useStableCallback(swiping => {
1778
+ const element = elementRef.current;
1779
+ if (!trackDrag || !element) {
1780
+ if (!swiping) {
1781
+ dragStyleSnapshotRef.current = null;
1782
+ }
1783
+ return;
1784
+ }
1785
+ const style = element.style;
1786
+ const dragStyleSnapshot = dragStyleSnapshotRef.current;
1787
+ if (swiping) {
1788
+ if (!dragStyleSnapshot) {
1789
+ dragStyleSnapshotRef.current = [style.transition, style.transform];
1790
+ }
1791
+ style.transition = 'none';
1792
+ } else if (dragStyleSnapshot) {
1793
+ [style.transition, style.transform] = dragStyleSnapshot;
1794
+ dragStyleSnapshotRef.current = null;
1795
+ }
1796
+ const dragOffset = dragOffsetRef.current;
1797
+ const initialTransform = initialTransformRef.current;
1798
+ const deltaX = dragOffset.x - initialTransform.x;
1799
+ const deltaY = dragOffset.y - initialTransform.y;
1800
+ if (swiping) {
1801
+ style.transform = getDragTransform(dragOffset, initialTransform.scale);
1802
+ }
1803
+ style.setProperty(movementCssVars.x, `${deltaX}px`);
1804
+ style.setProperty(movementCssVars.y, `${deltaY}px`);
1805
+ });
1806
+ function recordDragSample(offset, timeStamp) {
1807
+ if (timeStamp === null) {
1808
+ return;
1809
+ }
1810
+ const lastSample = lastDragSampleRef.current;
1811
+ if (lastSample && timeStamp > lastSample.time) {
1812
+ const durationMs = Math.max(timeStamp - lastSample.time, MIN_RELEASE_VELOCITY_DURATION_MS);
1813
+ lastDragVelocityRef.current = {
1814
+ x: (offset.x - lastSample.x) / durationMs,
1815
+ y: (offset.y - lastSample.y) / durationMs
1816
+ };
1817
+ }
1818
+ lastDragSampleRef.current = {
1819
+ x: offset.x,
1820
+ y: offset.y,
1821
+ time: timeStamp
1822
+ };
1823
+ }
1824
+ const reset = React__namespace.useCallback(() => {
1825
+ setCurrentSwipeDirection(undefined);
1826
+ setSwiping(false);
1827
+ setDragDismissed(false);
1828
+ updateSwipeProgress(0);
1829
+ swipeThresholdRef.current = swipeThresholdDefault;
1830
+ dragStartPosRef.current = {
1831
+ x: 0,
1832
+ y: 0
1833
+ };
1834
+ dragOffsetRef.current = {
1835
+ x: 0,
1836
+ y: 0
1837
+ };
1838
+ initialTransformRef.current = {
1839
+ x: 0,
1840
+ y: 0,
1841
+ scale: 1
1842
+ };
1843
+ intendedSwipeDirectionRef.current = undefined;
1844
+ maxSwipeDisplacementRef.current = 0;
1845
+ cancelledSwipeRef.current = false;
1846
+ swipeCancelBaselineRef.current = {
1847
+ x: 0,
1848
+ y: 0
1849
+ };
1850
+ lockedDirectionRef.current = null;
1851
+ isFirstPointerMoveRef.current = false;
1852
+ lastMovePosRef.current = null;
1853
+ pendingSwipeRef.current = false;
1854
+ pendingSwipeStartPosRef.current = null;
1855
+ swipeFromScrollableRef.current = false;
1856
+ sawPrimaryButtonsOnMoveRef.current = false;
1857
+ elementSizeRef.current = {
1858
+ width: 0,
1859
+ height: 0
1860
+ };
1861
+ swipeStartTimeRef.current = null;
1862
+ lastDragSampleRef.current = null;
1863
+ lastDragVelocityRef.current = {
1864
+ x: 0,
1865
+ y: 0
1866
+ };
1867
+ lastProgressDetailsRef.current = null;
1868
+ syncDragStyles(false);
1869
+ }, [setSwiping, swipeThresholdDefault, syncDragStyles, updateSwipeProgress]);
1870
+ React__namespace.useEffect(() => {
1871
+ if (typeof swipeThresholdProp !== 'function') {
1872
+ swipeThresholdRef.current = swipeThresholdDefault;
1873
+ }
1874
+ }, [swipeThresholdDefault, swipeThresholdProp]);
1875
+ function getPrimaryPointerPosition(event) {
1876
+ if ('touches' in event) {
1877
+ const touch = event.touches[0];
1878
+ return touch ? {
1879
+ x: touch.clientX,
1880
+ y: touch.clientY
1881
+ } : null;
1882
+ }
1883
+ return {
1884
+ x: event.clientX,
1885
+ y: event.clientY
1886
+ };
1887
+ }
1888
+ function isTouchLikeEvent(event) {
1889
+ if ('touches' in event) {
1890
+ return true;
1891
+ }
1892
+ return event.pointerType === 'touch';
1893
+ }
1894
+ function getTargetAtPoint(position, nativeEvent) {
1895
+ const doc = useButton.ownerDocument(elementRef.current);
1896
+ const elementAtPoint = getElementAtPoint(doc, position.x, position.y);
1897
+ const target = elementAtPoint ?? useButton.getTarget(nativeEvent);
1898
+ return target;
1899
+ }
1900
+ function findGestureScrollableTouchTarget(target, root) {
1901
+ if (hasHorizontal && !hasVertical) {
1902
+ return findScrollableTouchTarget(target, root, 'horizontal');
1903
+ }
1904
+ if (hasVertical && !hasHorizontal) {
1905
+ return findScrollableTouchTarget(target, root, 'vertical');
1906
+ }
1907
+ return findScrollableTouchTarget(target, root, 'vertical') ?? findScrollableTouchTarget(target, root, 'horizontal');
1908
+ }
1909
+ function startSwipeAtPosition(event, position, startOptions) {
1910
+ swipeFromScrollableRef.current = false;
1911
+ const touchLike = isTouchLikeEvent(event);
1912
+ const target = getTargetAtPoint(position, event.nativeEvent);
1913
+ const doc = useButton.ownerDocument(elementRef.current);
1914
+ const body = doc.body;
1915
+ const scrollableTarget = touchLike && body ? findGestureScrollableTouchTarget(target, body) : null;
1916
+ const ignoreScrollableTarget = startOptions?.ignoreScrollableTarget ?? false;
1917
+ if (scrollableTarget && !ignoreScrollableTarget) {
1918
+ return false;
1919
+ }
1920
+ swipeFromScrollableRef.current = Boolean(scrollableTarget && ignoreScrollableTarget);
1921
+ const isInteractiveElement = target ? target.closest(ignoreSelector) : false;
1922
+ if (isInteractiveElement && (!touchLike || ignoreSelectorWhenTouch)) {
1923
+ return false;
1924
+ }
1925
+ const element = elementRef.current;
1926
+ if (ignoreScrollableAncestors && element && target && scrollAxes.length > 0) {
1927
+ const ignoreAncestors = startOptions?.ignoreScrollableAncestors ?? false;
1928
+ if (!ignoreAncestors && hasScrollableAncestor(target, element, scrollAxes)) {
1929
+ return false;
1930
+ }
1931
+ }
1932
+ cancelledSwipeRef.current = false;
1933
+ intendedSwipeDirectionRef.current = undefined;
1934
+ maxSwipeDisplacementRef.current = 0;
1935
+ dragStartPosRef.current = position;
1936
+ swipeStartTimeRef.current = getValidTimeStamp(event.timeStamp);
1937
+ swipeCancelBaselineRef.current = position;
1938
+ lastMovePosRef.current = position;
1939
+ if (element) {
1940
+ elementSizeRef.current = {
1941
+ width: element.offsetWidth,
1942
+ height: element.offsetHeight
1943
+ };
1944
+ resolveSwipeThreshold(primaryDirection);
1945
+ const transform = getElementTransform(element);
1946
+ initialTransformRef.current = transform;
1947
+ dragOffsetRef.current = {
1948
+ x: transform.x,
1949
+ y: transform.y
1950
+ };
1951
+ recordDragSample({
1952
+ x: transform.x,
1953
+ y: transform.y
1954
+ }, swipeStartTimeRef.current);
1955
+ if (!('touches' in event)) {
1956
+ safelyChangePointerCapture(element, event.pointerId, 'setPointerCapture');
1957
+ }
1958
+ }
1959
+ onSwipeStart?.(event.nativeEvent);
1960
+ setSwiping(true);
1961
+ lockedDirectionRef.current = null;
1962
+ isFirstPointerMoveRef.current = true;
1963
+ updateSwipeProgress(0);
1964
+ syncDragStyles(true);
1965
+ return true;
1966
+ }
1967
+ function resetPendingSwipeState() {
1968
+ clearPendingSwipeStartState();
1969
+ swipeFromScrollableRef.current = false;
1970
+ lastMovePosRef.current = null;
1971
+ }
1972
+ function clearPendingSwipeStartState() {
1973
+ pendingSwipeRef.current = false;
1974
+ pendingSwipeStartPosRef.current = null;
1975
+ }
1976
+ function cancelSwipeInteraction(event) {
1977
+ resetPendingSwipeState();
1978
+ if (!isSwipingRef.current) {
1979
+ return;
1980
+ }
1981
+ setSwiping(false);
1982
+ lockedDirectionRef.current = null;
1983
+ const resolvedInitialTransform = initialTransformRef.current;
1984
+ dragOffsetRef.current = {
1985
+ x: resolvedInitialTransform.x,
1986
+ y: resolvedInitialTransform.y
1987
+ };
1988
+ setCurrentSwipeDirection(undefined);
1989
+ sawPrimaryButtonsOnMoveRef.current = false;
1990
+ syncDragStyles(false);
1991
+ const element = elementRef.current;
1992
+ if (element) {
1993
+ safelyChangePointerCapture(element, event.pointerId, 'releasePointerCapture');
1994
+ }
1995
+ updateSwipeProgress(0, {
1996
+ deltaX: 0,
1997
+ deltaY: 0,
1998
+ direction: undefined
1999
+ });
2000
+ onCancel?.(event.nativeEvent);
2001
+ }
2002
+ function applyDirectionalDamping(deltaX, deltaY) {
2003
+ const exponent = value => value >= 0 ? value ** 0.5 : -(Math.abs(value) ** 0.5);
2004
+ const dampAxis = (delta, allowNegative, allowPositive) => {
2005
+ if (!allowNegative && delta < 0) {
2006
+ return exponent(delta);
2007
+ }
2008
+ if (!allowPositive && delta > 0) {
2009
+ return exponent(delta);
2010
+ }
2011
+ return delta;
2012
+ };
2013
+ const newDeltaX = hasHorizontal ? dampAxis(deltaX, allowLeft, allowRight) : exponent(deltaX);
2014
+ const newDeltaY = hasVertical ? dampAxis(deltaY, allowUp, allowDown) : exponent(deltaY);
2015
+ return {
2016
+ x: newDeltaX,
2017
+ y: newDeltaY
2018
+ };
2019
+ }
2020
+ function canSwipeFromScrollEdgeOnPendingMove(scrollTarget, deltaX, deltaY) {
2021
+ const absDeltaX = Math.abs(deltaX);
2022
+ const absDeltaY = Math.abs(deltaY);
2023
+ const useVerticalAxis = hasVertical && deltaY !== 0 && (!hasHorizontal || absDeltaY >= absDeltaX);
2024
+ if (useVerticalAxis) {
2025
+ const maxScrollTop = Math.max(0, scrollTarget.scrollHeight - scrollTarget.clientHeight);
2026
+ const atTop = scrollTarget.scrollTop <= 0;
2027
+ const atBottom = scrollTarget.scrollTop >= maxScrollTop;
2028
+ const movingDown = deltaY > 0;
2029
+ const movingUp = deltaY < 0;
2030
+ const canSwipeDown = movingDown && atTop && allowDown;
2031
+ const canSwipeUp = movingUp && atBottom && allowUp;
2032
+ return canSwipeDown || canSwipeUp;
2033
+ }
2034
+ const useHorizontalAxis = hasHorizontal && deltaX !== 0 && (!hasVertical || absDeltaX > absDeltaY);
2035
+ if (useHorizontalAxis) {
2036
+ const maxScrollLeft = Math.max(0, scrollTarget.scrollWidth - scrollTarget.clientWidth);
2037
+ const atLeft = scrollTarget.scrollLeft <= 0;
2038
+ const atRight = scrollTarget.scrollLeft >= maxScrollLeft;
2039
+ const movingRight = deltaX > 0;
2040
+ const movingLeft = deltaX < 0;
2041
+ const canSwipeRight = movingRight && atLeft && allowRight;
2042
+ const canSwipeLeft = movingLeft && atRight && allowLeft;
2043
+ return canSwipeRight || canSwipeLeft;
2044
+ }
2045
+ return null;
2046
+ }
2047
+ const handleStart = useButton.useStableCallback(event => {
2048
+ if (!enabled) {
2049
+ return;
2050
+ }
2051
+ if (event.defaultPrevented || event.nativeEvent.defaultPrevented) {
2052
+ return;
2053
+ }
2054
+ if (!('touches' in event) && event.button !== 0) {
2055
+ return;
2056
+ }
2057
+ const startPos = getPrimaryPointerPosition(event);
2058
+ if (!startPos) {
2059
+ return;
2060
+ }
2061
+ pendingSwipeRef.current = true;
2062
+ pendingSwipeStartPosRef.current = startPos;
2063
+ swipeFromScrollableRef.current = false;
2064
+ sawPrimaryButtonsOnMoveRef.current = false;
2065
+ const allowedToStart = canStart ? canStart(startPos, {
2066
+ nativeEvent: event.nativeEvent,
2067
+ direction: primaryDirection
2068
+ }) : true;
2069
+ if (!allowedToStart) {
2070
+ return;
2071
+ }
2072
+ if (startSwipeAtPosition(event, startPos)) {
2073
+ clearPendingSwipeStartState();
2074
+ }
2075
+ });
2076
+ function handleMoveCore(event, position, movement) {
2077
+ if (!enabled || !isSwipingRef.current) {
2078
+ return;
2079
+ }
2080
+ const target = useButton.getTarget(event.nativeEvent);
2081
+ if (isTouchLikeEvent(event) && !swipeFromScrollableRef.current) {
2082
+ const boundaryElement = event.currentTarget;
2083
+ if (findGestureScrollableTouchTarget(target, boundaryElement)) {
2084
+ return;
2085
+ }
2086
+ }
2087
+ if (!('touches' in event)) {
2088
+ // Prevent text selection on Safari
2089
+ event.preventDefault();
2090
+ }
2091
+ if (isFirstPointerMoveRef.current) {
2092
+ // Adjust the starting position to the current position on the first move
2093
+ // to account for the delay between pointerdown and the first pointermove on iOS.
2094
+ dragStartPosRef.current = position;
2095
+ const moveTime = getValidTimeStamp(event.timeStamp);
2096
+ if (moveTime !== null) {
2097
+ swipeStartTimeRef.current = moveTime;
2098
+ }
2099
+ isFirstPointerMoveRef.current = false;
2100
+ }
2101
+ const clientX = position.x;
2102
+ const clientY = position.y;
2103
+ const movementX = movement.x;
2104
+ const movementY = movement.y;
2105
+ if (movementY < 0 && clientY > swipeCancelBaselineRef.current.y || movementY > 0 && clientY < swipeCancelBaselineRef.current.y) {
2106
+ swipeCancelBaselineRef.current = {
2107
+ x: swipeCancelBaselineRef.current.x,
2108
+ y: clientY
2109
+ };
2110
+ }
2111
+ if (movementX < 0 && clientX > swipeCancelBaselineRef.current.x || movementX > 0 && clientX < swipeCancelBaselineRef.current.x) {
2112
+ swipeCancelBaselineRef.current = {
2113
+ x: clientX,
2114
+ y: swipeCancelBaselineRef.current.y
2115
+ };
2116
+ }
2117
+ const deltaX = clientX - dragStartPosRef.current.x;
2118
+ const deltaY = clientY - dragStartPosRef.current.y;
2119
+ const cancelDeltaY = clientY - swipeCancelBaselineRef.current.y;
2120
+ const cancelDeltaX = clientX - swipeCancelBaselineRef.current.x;
2121
+ let lockedDirection = lockedDirectionRef.current;
2122
+ if (lockedDirection === null && hasHorizontal && hasVertical) {
2123
+ const movementDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
2124
+ if (movementDistance >= MIN_DRAG_THRESHOLD) {
2125
+ lockedDirection = Math.abs(deltaX) > Math.abs(deltaY) ? 'horizontal' : 'vertical';
2126
+ lockedDirectionRef.current = lockedDirection;
2127
+ }
2128
+ }
2129
+ let candidate;
2130
+ if (!intendedSwipeDirectionRef.current) {
2131
+ if (lockedDirection === 'vertical') {
2132
+ if (deltaY > 0) {
2133
+ candidate = 'down';
2134
+ } else if (deltaY < 0) {
2135
+ candidate = 'up';
2136
+ }
2137
+ } else if (lockedDirection === 'horizontal') {
2138
+ if (deltaX > 0) {
2139
+ candidate = 'right';
2140
+ } else if (deltaX < 0) {
2141
+ candidate = 'left';
2142
+ }
2143
+ } else if (Math.abs(deltaX) >= Math.abs(deltaY)) {
2144
+ candidate = deltaX > 0 ? 'right' : 'left';
2145
+ } else {
2146
+ candidate = deltaY > 0 ? 'down' : 'up';
2147
+ }
2148
+ if (candidate) {
2149
+ const isAllowed = candidate === 'left' && allowLeft || candidate === 'right' && allowRight || candidate === 'up' && allowUp || candidate === 'down' && allowDown;
2150
+ if (isAllowed) {
2151
+ intendedSwipeDirectionRef.current = candidate;
2152
+ maxSwipeDisplacementRef.current = getDisplacement(candidate, deltaX, deltaY);
2153
+ setCurrentSwipeDirection(candidate);
2154
+ resolveSwipeThreshold(candidate);
2155
+ }
2156
+ }
2157
+ } else {
2158
+ const direction = intendedSwipeDirectionRef.current;
2159
+ const currentDisplacement = getDisplacement(direction, cancelDeltaX, cancelDeltaY);
2160
+ if (currentDisplacement > swipeThresholdRef.current) {
2161
+ cancelledSwipeRef.current = false;
2162
+ setCurrentSwipeDirection(direction);
2163
+ } else if (!(allowLeft && allowRight) && !(allowUp && allowDown) && maxSwipeDisplacementRef.current - currentDisplacement >= REVERSE_CANCEL_THRESHOLD) {
2164
+ // Mark that a change-of-mind has occurred
2165
+ cancelledSwipeRef.current = true;
2166
+ }
2167
+ }
2168
+ const dampedDelta = applyDirectionalDamping(deltaX, deltaY);
2169
+ let newOffsetX = initialTransformRef.current.x;
2170
+ let newOffsetY = initialTransformRef.current.y;
2171
+ if (lockedDirection === 'horizontal') {
2172
+ if (hasHorizontal) {
2173
+ newOffsetX += dampedDelta.x;
2174
+ }
2175
+ } else if (lockedDirection === 'vertical') {
2176
+ if (hasVertical) {
2177
+ newOffsetY += dampedDelta.y;
2178
+ }
2179
+ } else {
2180
+ if (hasHorizontal) {
2181
+ newOffsetX += dampedDelta.x;
2182
+ }
2183
+ if (hasVertical) {
2184
+ newOffsetY += dampedDelta.y;
2185
+ }
2186
+ }
2187
+ dragOffsetRef.current = {
2188
+ x: newOffsetX,
2189
+ y: newOffsetY
2190
+ };
2191
+ syncDragStyles(true);
2192
+ recordDragSample({
2193
+ x: newOffsetX,
2194
+ y: newOffsetY
2195
+ }, getValidTimeStamp(event.timeStamp));
2196
+ const dragDeltaX = newOffsetX - initialTransformRef.current.x;
2197
+ const dragDeltaY = newOffsetY - initialTransformRef.current.y;
2198
+ const swipeDirectionDetails = intendedSwipeDirectionRef.current;
2199
+ const progressDirection = primaryDirection ?? intendedSwipeDirectionRef.current;
2200
+ if (!progressDirection) {
2201
+ updateSwipeProgress(0, {
2202
+ deltaX: dragDeltaX,
2203
+ deltaY: dragDeltaY,
2204
+ direction: swipeDirectionDetails
2205
+ });
2206
+ return;
2207
+ }
2208
+ const size = progressDirection === 'left' || progressDirection === 'right' ? elementSizeRef.current.width : elementSizeRef.current.height;
2209
+ const scale = initialTransformRef.current.scale || 1;
2210
+ if (size <= 0 || scale <= 0) {
2211
+ updateSwipeProgress(0, {
2212
+ deltaX: dragDeltaX,
2213
+ deltaY: dragDeltaY,
2214
+ direction: swipeDirectionDetails
2215
+ });
2216
+ return;
2217
+ }
2218
+ const progressDisplacement = getDisplacement(progressDirection, newOffsetX - initialTransformRef.current.x, newOffsetY - initialTransformRef.current.y);
2219
+ if (progressDisplacement <= 0) {
2220
+ updateSwipeProgress(0, {
2221
+ deltaX: dragDeltaX,
2222
+ deltaY: dragDeltaY,
2223
+ direction: swipeDirectionDetails
2224
+ });
2225
+ return;
2226
+ }
2227
+ updateSwipeProgress(progressDisplacement / (size * scale), {
2228
+ deltaX: dragDeltaX,
2229
+ deltaY: dragDeltaY,
2230
+ direction: swipeDirectionDetails
2231
+ });
2232
+ }
2233
+ const handleEnd = useButton.useStableCallback(event => {
2234
+ if (!enabled) {
2235
+ return;
2236
+ }
2237
+ const resolvedDragOffset = dragOffsetRef.current;
2238
+ const resolvedInitialTransform = initialTransformRef.current;
2239
+ const releaseDeltaX = resolvedDragOffset.x - resolvedInitialTransform.x;
2240
+ const releaseDeltaY = resolvedDragOffset.y - resolvedInitialTransform.y;
2241
+ const progressDetails = {
2242
+ deltaX: releaseDeltaX,
2243
+ deltaY: releaseDeltaY,
2244
+ direction: intendedSwipeDirectionRef.current
2245
+ };
2246
+ if (!isSwipingRef.current) {
2247
+ resetPendingSwipeState();
2248
+ updateSwipeProgress(0, progressDetails);
2249
+ return;
2250
+ }
2251
+ setSwiping(false);
2252
+ lockedDirectionRef.current = null;
2253
+ resetPendingSwipeState();
2254
+ sawPrimaryButtonsOnMoveRef.current = false;
2255
+ const element = elementRef.current;
2256
+ if (element) {
2257
+ if (!('touches' in event)) {
2258
+ safelyChangePointerCapture(element, event.pointerId, 'releasePointerCapture');
2259
+ }
2260
+ }
2261
+ const deltaX = releaseDeltaX;
2262
+ const deltaY = releaseDeltaY;
2263
+ const startTime = swipeStartTimeRef.current;
2264
+ const endTime = getValidTimeStamp(event.timeStamp);
2265
+ const durationMs = startTime !== null && endTime !== null && endTime > startTime ? endTime - startTime : 0;
2266
+ const velocityDurationMs = durationMs > 0 ? Math.max(durationMs, MIN_VELOCITY_DURATION_MS) : 0;
2267
+ const velocityX = velocityDurationMs > 0 ? deltaX / velocityDurationMs : 0;
2268
+ const velocityY = velocityDurationMs > 0 ? deltaY / velocityDurationMs : 0;
2269
+ let releaseVelocityX = lastDragVelocityRef.current.x;
2270
+ let releaseVelocityY = lastDragVelocityRef.current.y;
2271
+ const lastSample = lastDragSampleRef.current;
2272
+ if (lastSample && endTime !== null && endTime >= lastSample.time) {
2273
+ const ageMs = endTime - lastSample.time;
2274
+ if (ageMs <= MAX_RELEASE_VELOCITY_AGE_MS) {
2275
+ const sampleDurationMs = Math.max(ageMs, MIN_RELEASE_VELOCITY_DURATION_MS);
2276
+ const deltaFromLastSampleX = resolvedDragOffset.x - lastSample.x;
2277
+ const deltaFromLastSampleY = resolvedDragOffset.y - lastSample.y;
2278
+ const sampleVelocityX = deltaFromLastSampleX / sampleDurationMs;
2279
+ const sampleVelocityY = deltaFromLastSampleY / sampleDurationMs;
2280
+ if (sampleVelocityX !== 0) {
2281
+ releaseVelocityX = sampleVelocityX;
2282
+ }
2283
+ if (sampleVelocityY !== 0) {
2284
+ releaseVelocityY = sampleVelocityY;
2285
+ }
2286
+ } else {
2287
+ releaseVelocityX = 0;
2288
+ releaseVelocityY = 0;
2289
+ }
2290
+ }
2291
+ const releaseDecision = onRelease?.({
2292
+ event: event.nativeEvent,
2293
+ direction: intendedSwipeDirectionRef.current,
2294
+ deltaX,
2295
+ deltaY,
2296
+ velocityX,
2297
+ velocityY,
2298
+ releaseVelocityX,
2299
+ releaseVelocityY
2300
+ });
2301
+ const hasReleaseDecision = typeof releaseDecision === 'boolean';
2302
+ if (cancelledSwipeRef.current && !hasReleaseDecision) {
2303
+ dragOffsetRef.current = {
2304
+ x: resolvedInitialTransform.x,
2305
+ y: resolvedInitialTransform.y
2306
+ };
2307
+ setCurrentSwipeDirection(undefined);
2308
+ syncDragStyles(false);
2309
+ updateSwipeProgress(0, progressDetails);
2310
+ return;
2311
+ }
2312
+ let shouldClose = false;
2313
+ let dismissDirection;
2314
+ if (hasReleaseDecision) {
2315
+ shouldClose = releaseDecision;
2316
+ dismissDirection = intendedSwipeDirectionRef.current ?? primaryDirection;
2317
+ } else {
2318
+ for (const direction of directions) {
2319
+ switch (direction) {
2320
+ case 'right':
2321
+ if (deltaX > swipeThresholdRef.current) {
2322
+ shouldClose = true;
2323
+ dismissDirection = 'right';
2324
+ }
2325
+ break;
2326
+ case 'left':
2327
+ if (deltaX < -swipeThresholdRef.current) {
2328
+ shouldClose = true;
2329
+ dismissDirection = 'left';
2330
+ }
2331
+ break;
2332
+ case 'down':
2333
+ if (deltaY > swipeThresholdRef.current) {
2334
+ shouldClose = true;
2335
+ dismissDirection = 'down';
2336
+ }
2337
+ break;
2338
+ case 'up':
2339
+ if (deltaY < -swipeThresholdRef.current) {
2340
+ shouldClose = true;
2341
+ dismissDirection = 'up';
2342
+ }
2343
+ break;
2344
+ }
2345
+ if (shouldClose) {
2346
+ break;
2347
+ }
2348
+ }
2349
+ }
2350
+ if (shouldClose && dismissDirection) {
2351
+ setCurrentSwipeDirection(dismissDirection);
2352
+ setDragDismissed(true);
2353
+ syncDragStyles(false);
2354
+ onDismiss?.(event.nativeEvent, {
2355
+ direction: dismissDirection
2356
+ });
2357
+ } else {
2358
+ dragOffsetRef.current = {
2359
+ x: resolvedInitialTransform.x,
2360
+ y: resolvedInitialTransform.y
2361
+ };
2362
+ setCurrentSwipeDirection(undefined);
2363
+ syncDragStyles(false);
2364
+ updateSwipeProgress(0, progressDetails);
2365
+ }
2366
+ });
2367
+ const handleMove = useButton.useStableCallback(event => {
2368
+ const currentPos = getPrimaryPointerPosition(event);
2369
+ if (!currentPos) {
2370
+ return;
2371
+ }
2372
+ let endAfterMove = false;
2373
+ if (!('touches' in event)) {
2374
+ const hasPrimaryButton = hasPrimaryMouseButton(event.buttons);
2375
+ if (hasPrimaryButton) {
2376
+ sawPrimaryButtonsOnMoveRef.current = true;
2377
+ }
2378
+
2379
+ // Cancel the swipe if a non-primary button takes over the interaction.
2380
+ // This handles cases where a right-click interrupts dragging.
2381
+ if (event.buttons !== 0 && !hasPrimaryButton) {
2382
+ cancelSwipeInteraction(event);
2383
+ return;
2384
+ }
2385
+
2386
+ // A `buttons: 0` pointermove means the primary button was already released, so the gesture is
2387
+ // over even if no pointerup reached us. On fast trackpad flicks this trailing move is
2388
+ // dispatched just before pointerup; treat it as the release (mirroring touchend) instead of
2389
+ // cancelling and snapping the element back.
2390
+ if (event.buttons === 0 && sawPrimaryButtonsOnMoveRef.current) {
2391
+ if (!isSwipingRef.current) {
2392
+ // The gesture never activated — discard it.
2393
+ handleEnd(event);
2394
+ return;
2395
+ }
2396
+ // This release move can itself carry the threshold-crossing displacement (and the peak
2397
+ // release velocity), so let it flow through `handleMoveCore` below to update the drag
2398
+ // offset / velocity sample, then commit the release afterwards.
2399
+ endAfterMove = true;
2400
+ }
2401
+ }
2402
+ if (!isSwiping && pendingSwipeRef.current) {
2403
+ if (!isTouchLikeEvent(event) && (event.defaultPrevented || event.nativeEvent.defaultPrevented)) {
2404
+ resetPendingSwipeState();
2405
+ return;
2406
+ }
2407
+ const allowedToStart = canStart ? canStart(currentPos, {
2408
+ nativeEvent: event.nativeEvent,
2409
+ direction: primaryDirection
2410
+ }) : true;
2411
+ if (allowedToStart) {
2412
+ const pendingStartPos = pendingSwipeStartPosRef.current;
2413
+ let ignoreScrollableOnStart = false;
2414
+ if (isTouchLikeEvent(event)) {
2415
+ const element = elementRef.current;
2416
+ if (pendingStartPos && element) {
2417
+ const target = getTargetAtPoint(currentPos, event.nativeEvent);
2418
+ const doc = useButton.ownerDocument(element);
2419
+ const body = doc.body;
2420
+ const scrollTarget = body ? findGestureScrollableTouchTarget(target, body) : null;
2421
+ if (scrollTarget && (useButton.contains(element, scrollTarget) || useButton.contains(scrollTarget, element))) {
2422
+ const deltaX = currentPos.x - pendingStartPos.x;
2423
+ const deltaY = currentPos.y - pendingStartPos.y;
2424
+ const canSwipeFromEdge = canSwipeFromScrollEdgeOnPendingMove(scrollTarget, deltaX, deltaY);
2425
+ if (canSwipeFromEdge === false) {
2426
+ return;
2427
+ }
2428
+ if (canSwipeFromEdge === true) {
2429
+ ignoreScrollableOnStart = true;
2430
+ }
2431
+ }
2432
+ }
2433
+ }
2434
+ const started = startSwipeAtPosition(event, currentPos, {
2435
+ ignoreScrollableTarget: ignoreScrollableOnStart,
2436
+ ignoreScrollableAncestors: ignoreScrollableOnStart
2437
+ });
2438
+ if (started) {
2439
+ if (pendingStartPos && ignoreScrollableOnStart) {
2440
+ // Preserve displacement between touchstart and the move that activates swipe from
2441
+ // a scroll-edge so quick flicks can dismiss.
2442
+ clearPendingSwipeStartState();
2443
+ dragStartPosRef.current = pendingStartPos;
2444
+ swipeCancelBaselineRef.current = pendingStartPos;
2445
+ lastMovePosRef.current = pendingStartPos;
2446
+ isFirstPointerMoveRef.current = false;
2447
+ } else {
2448
+ // Start from the current in-bounds position without dropping follow-up move
2449
+ // displacement; this avoids jumps when entering from outside the element while
2450
+ // keeping swipe tracking responsive on the next move.
2451
+ clearPendingSwipeStartState();
2452
+ swipeFromScrollableRef.current = false;
2453
+ }
2454
+ }
2455
+ }
2456
+ }
2457
+ const previousPos = lastMovePosRef.current;
2458
+ const movement = previousPos === null ? {
2459
+ x: 0,
2460
+ y: 0
2461
+ } : {
2462
+ x: currentPos.x - previousPos.x,
2463
+ y: currentPos.y - previousPos.y
2464
+ };
2465
+ lastMovePosRef.current = currentPos;
2466
+ handleMoveCore(event, currentPos, movement);
2467
+
2468
+ // `endAfterMove` is only set in the non-touch branch above; the `'touches'` guard re-narrows the
2469
+ // event type for `handleEnd` after the shared move handling has run.
2470
+ if (endAfterMove && !('touches' in event)) {
2471
+ handleEnd(event);
2472
+ }
2473
+ });
2474
+
2475
+ // Feeds a native touchmove into the swipe pipeline. Used by consumers that claim the gesture
2476
+ // in a capture-phase listener and stop it from reaching React's delegated touch handlers.
2477
+ const moveNative = useButton.useStableCallback((nativeEvent, currentTarget) => {
2478
+ handleMove({
2479
+ touches: nativeEvent.touches,
2480
+ currentTarget,
2481
+ nativeEvent,
2482
+ defaultPrevented: nativeEvent.defaultPrevented,
2483
+ timeStamp: nativeEvent.timeStamp
2484
+ });
2485
+ });
2486
+ const getDragStyles = React__namespace.useCallback(() => {
2487
+ const dragOffset = dragOffsetRef.current;
2488
+ const initialTransform = initialTransformRef.current;
2489
+ const deltaX = dragOffset.x - initialTransform.x;
2490
+ const deltaY = dragOffset.y - initialTransform.y;
2491
+ if (!isSwiping && deltaX === 0 && deltaY === 0 && !dragDismissed) {
2492
+ return {
2493
+ [movementCssVars.x]: '0px',
2494
+ [movementCssVars.y]: '0px'
2495
+ };
2496
+ }
2497
+ return {
2498
+ transition: isSwiping ? 'none' : undefined,
2499
+ // While swiping, freeze the element at its current visual transform so it doesn't snap to the
2500
+ // end position.
2501
+ transform: isSwiping ? getDragTransform(dragOffset, initialTransform.scale) : undefined,
2502
+ [movementCssVars.x]: `${deltaX}px`,
2503
+ [movementCssVars.y]: `${deltaY}px`
2504
+ };
2505
+ }, [dragDismissed, isSwiping, movementCssVars]);
2506
+ const getPointerProps = React__namespace.useCallback(() => {
2507
+ if (!enabled) {
2508
+ return {};
2509
+ }
2510
+ return {
2511
+ onPointerDown: handleStart,
2512
+ onPointerMove: handleMove,
2513
+ onPointerUp: handleEnd,
2514
+ onPointerCancel: handleEnd
2515
+ };
2516
+ }, [enabled, handleEnd, handleMove, handleStart]);
2517
+ const getTouchProps = React__namespace.useCallback(() => {
2518
+ if (!enabled) {
2519
+ return {};
2520
+ }
2521
+ return {
2522
+ onTouchStart: handleStart,
2523
+ onTouchMove: handleMove,
2524
+ onTouchEnd: handleEnd,
2525
+ onTouchCancel: handleEnd
2526
+ };
2527
+ }, [enabled, handleEnd, handleMove, handleStart]);
2528
+ return {
2529
+ swiping: isSwiping,
2530
+ swipeDirection: currentSwipeDirection,
2531
+ dragDismissed,
2532
+ getPointerProps,
2533
+ getTouchProps,
2534
+ moveNative,
2535
+ getDragStyles,
2536
+ reset
2537
+ };
2538
+ }
2539
+
2540
+ /**
2541
+ * A button that opens the drawer.
2542
+ * Renders a `<button>` element.
2543
+ *
2544
+ * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)
2545
+ */
2546
+ const DrawerTrigger = DialogTrigger;
2547
+
2548
+ const DrawerVirtualKeyboardContext = /*#__PURE__*/React__namespace.createContext(undefined);
2549
+ if (process.env.NODE_ENV !== "production") DrawerVirtualKeyboardContext.displayName = "DrawerVirtualKeyboardContext";
2550
+ function useDrawerVirtualKeyboardContext() {
2551
+ return React__namespace.useContext(DrawerVirtualKeyboardContext);
2552
+ }
2553
+
2554
+ const MIN_SWIPE_THRESHOLD = 10;
2555
+ const FAST_SWIPE_VELOCITY = 0.5;
2556
+ const SNAP_VELOCITY_THRESHOLD = 0.5;
2557
+ const SNAP_VELOCITY_MULTIPLIER = 300;
2558
+ const MAX_SNAP_VELOCITY = 4;
2559
+ const MIN_SWIPE_RELEASE_VELOCITY = 0.2;
2560
+ const MAX_SWIPE_RELEASE_VELOCITY = 4;
2561
+ const MIN_SWIPE_RELEASE_DURATION_MS = 80;
2562
+ const MAX_SWIPE_RELEASE_DURATION_MS = 360;
2563
+ const MIN_SWIPE_RELEASE_SCALAR = 0.1;
2564
+ const MAX_SWIPE_RELEASE_SCALAR = 1;
2565
+ const DRAWER_CONTENT_SELECTOR = `[${DRAWER_CONTENT_ATTRIBUTE}]`;
2566
+ /**
2567
+ * A positioning container for the drawer popup that can be made scrollable.
2568
+ * Renders a `<div>` element.
2569
+ *
2570
+ * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)
2571
+ */
2572
+ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerViewport(props, forwardedRef) {
2573
+ const {
2574
+ render,
2575
+ className,
2576
+ style,
2577
+ children,
2578
+ ...elementProps
2579
+ } = props;
2580
+ const {
2581
+ store
2582
+ } = useDialogRootContext();
2583
+ const popupRef = store.context.popupRef;
2584
+ const backdropRef = store.context.backdropRef;
2585
+ const {
2586
+ swipeDirection,
2587
+ notifyParentSwipingChange,
2588
+ notifyParentSwipeProgressChange,
2589
+ frontmostHeight,
2590
+ snapToSequentialPoints
2591
+ } = useDrawerRootContext();
2592
+ const providerContext = useDrawerProviderContext();
2593
+ const {
2594
+ snapPoints,
2595
+ resolvedSnapPoints,
2596
+ activeSnapPoint,
2597
+ activeSnapPointOffset,
2598
+ setActiveSnapPoint,
2599
+ popupHeight
2600
+ } = useDrawerSnapPoints();
2601
+ const open = store.useState('open');
2602
+ const mounted = store.useState('mounted');
2603
+ const nested = store.useState('nested');
2604
+ const nestedOpenDrawerCount = store.useState('nestedOpenDrawerCount');
2605
+ const viewportElement = store.useState('viewportElement');
2606
+ const popupElementState = store.useState('popupElement');
2607
+ const visualStateStore = providerContext?.visualStateStore;
2608
+ const nestedDrawerOpen = nestedOpenDrawerCount > 0;
2609
+ const scrollAxis = swipeDirection === 'left' || swipeDirection === 'right' ? 'horizontal' : 'vertical';
2610
+ const isVerticalScrollAxis = scrollAxis === 'vertical';
2611
+ const crossScrollAxis = isVerticalScrollAxis ? 'horizontal' : 'vertical';
2612
+ const [swipeRelease, setSwipeRelease] = React__namespace.useState(null);
2613
+ const pendingSwipeCloseSnapPointRef = React__namespace.useRef(undefined);
2614
+ const resetSwipeRef = React__namespace.useRef(null);
2615
+ const controlledDismissFrame = useButton.useAnimationFrame();
2616
+ const swipingRef = React__namespace.useRef(false);
2617
+ const nestedSwipeActiveRef = React__namespace.useRef(false);
2618
+ const lastPointerTypeRef = React__namespace.useRef('');
2619
+ const ignoreNextTouchStartFromPenRef = React__namespace.useRef(false);
2620
+ const ignoreTouchSwipeRef = React__namespace.useRef(false);
2621
+ const touchScrollStateRef = React__namespace.useRef(null);
2622
+ const virtualKeyboard = useDrawerVirtualKeyboardContext();
2623
+ const snapPointRange = React__namespace.useMemo(() => {
2624
+ if (!snapPoints || snapPoints.length < 2) {
2625
+ return null;
2626
+ }
2627
+ if (swipeDirection !== 'down' && swipeDirection !== 'up') {
2628
+ return null;
2629
+ }
2630
+ if (resolvedSnapPoints.length < 2) {
2631
+ return null;
2632
+ }
2633
+ const offsets = resolvedSnapPoints.map(point => point.offset).filter(offset => Number.isFinite(offset)).sort((a, b) => a - b);
2634
+ if (offsets.length < 2) {
2635
+ return null;
2636
+ }
2637
+ const minOffset = offsets[0];
2638
+ const nextOffset = offsets[1];
2639
+ const maxOffset = offsets[offsets.length - 1];
2640
+ let range = nextOffset - minOffset;
2641
+ if (!Number.isFinite(range) || range <= 0) {
2642
+ const fallbackRange = maxOffset - minOffset;
2643
+ if (!Number.isFinite(fallbackRange) || fallbackRange <= 0) {
2644
+ return null;
2645
+ }
2646
+ range = fallbackRange;
2647
+ }
2648
+ return {
2649
+ minOffset,
2650
+ range
2651
+ };
2652
+ }, [resolvedSnapPoints, snapPoints, swipeDirection]);
2653
+ const snapPointProgress = React__namespace.useMemo(() => {
2654
+ if (!snapPointRange || activeSnapPointOffset === null) {
2655
+ return null;
2656
+ }
2657
+ return clamp.clamp((activeSnapPointOffset - snapPointRange.minOffset) / snapPointRange.range, 0, 1);
2658
+ }, [activeSnapPointOffset, snapPointRange]);
2659
+ const swipeDirections = React__namespace.useMemo(() => {
2660
+ if (snapPoints && snapPoints.length > 0 && (swipeDirection === 'down' || swipeDirection === 'up')) {
2661
+ return swipeDirection === 'down' ? ['down', 'up'] : ['up', 'down'];
2662
+ }
2663
+ return [swipeDirection];
2664
+ }, [snapPoints, swipeDirection]);
2665
+ const setSwipeDismissed = useButton.useStableCallback(dismissed => {
2666
+ setSwipeDismissedElements(popupRef.current, backdropRef.current, dismissed);
2667
+ });
2668
+ const clearSwipeRelease = useButton.useStableCallback(() => {
2669
+ setSwipeDismissed(false);
2670
+ popupRef.current?.removeAttribute(useButton.TransitionStatusDataAttributes.endingStyle);
2671
+ setSwipeRelease(null);
2672
+ });
2673
+ const finishNestedSwipe = useButton.useStableCallback(() => {
2674
+ if (!nestedSwipeActiveRef.current) {
2675
+ return;
2676
+ }
2677
+ nestedSwipeActiveRef.current = false;
2678
+ notifyParentSwipingChange?.(false);
2679
+ });
2680
+ const applySwipeProgress = useButton.useStableCallback(({
2681
+ resolvedProgress,
2682
+ shouldTrackProgress,
2683
+ notifyParent
2684
+ }) => {
2685
+ const isActive = open && !nested && shouldTrackProgress;
2686
+ const swipeProgress = isActive ? resolvedProgress : 0;
2687
+ const nestedSwipeProgress = open && shouldTrackProgress ? resolvedProgress : 0;
2688
+ if (notifyParent && notifyParentSwipeProgressChange) {
2689
+ notifyParentSwipeProgressChange(nestedSwipeProgress);
2690
+ if (nestedSwipeProgress <= 0) {
2691
+ finishNestedSwipe();
2692
+ }
2693
+ }
2694
+ visualStateStore?.set({
2695
+ swipeProgress,
2696
+ frontmostHeight: swipeProgress > 0 ? frontmostHeight : 0
2697
+ });
2698
+ const backdropElement = backdropRef.current;
2699
+ if (!backdropElement) {
2700
+ return;
2701
+ }
2702
+ if (!isActive || swipeProgress <= 0) {
2703
+ backdropElement.style.setProperty(DrawerBackdropCssVars.swipeProgress, '0');
2704
+ backdropElement.style.removeProperty(DrawerPopupCssVars.height);
2705
+ return;
2706
+ }
2707
+ backdropElement.style.setProperty(DrawerBackdropCssVars.swipeProgress, `${swipeProgress}`);
2708
+ if (frontmostHeight > 0) {
2709
+ backdropElement.style.setProperty(DrawerPopupCssVars.height, `${frontmostHeight}px`);
2710
+ } else {
2711
+ backdropElement.style.removeProperty(DrawerPopupCssVars.height);
2712
+ }
2713
+ });
2714
+ function resolveSwipeRelease({
2715
+ direction,
2716
+ deltaX,
2717
+ deltaY,
2718
+ velocityX,
2719
+ velocityY,
2720
+ releaseVelocityX,
2721
+ releaseVelocityY
2722
+ }) {
2723
+ if (!direction) {
2724
+ return null;
2725
+ }
2726
+ const popupElement = store.context.popupRef.current;
2727
+ if (!popupElement) {
2728
+ return null;
2729
+ }
2730
+ const size = direction === 'left' || direction === 'right' ? popupElement.offsetWidth : popupElement.offsetHeight;
2731
+ if (!Number.isFinite(size) || size <= 0) {
2732
+ return null;
2733
+ }
2734
+ const axisDelta = direction === 'left' || direction === 'right' ? deltaX : deltaY;
2735
+ const snapPointBaseOffset = snapPoints && snapPoints.length > 0 ? activeSnapPointOffset ?? 0 : 0;
2736
+ let baseOffset = 0;
2737
+ if (direction === 'down') {
2738
+ baseOffset = snapPointBaseOffset;
2739
+ } else if (direction === 'up') {
2740
+ baseOffset = -snapPointBaseOffset;
2741
+ }
2742
+ const translation = baseOffset + axisDelta;
2743
+ const translationAlongDirection = direction === 'left' || direction === 'up' ? -translation : translation;
2744
+ const remainingDistance = Math.max(0, size - translationAlongDirection);
2745
+ if (!Number.isFinite(remainingDistance) || remainingDistance <= 0) {
2746
+ return null;
2747
+ }
2748
+ const axisVelocity = direction === 'left' || direction === 'right' ? releaseVelocityX : releaseVelocityY;
2749
+ const fallbackVelocity = direction === 'left' || direction === 'right' ? velocityX : velocityY;
2750
+ const resolvedVelocity = Math.abs(axisVelocity) > 0 && Number.isFinite(axisVelocity) ? axisVelocity : fallbackVelocity;
2751
+ const directionalVelocity = direction === 'left' || direction === 'up' ? -resolvedVelocity : resolvedVelocity;
2752
+ if (!Number.isFinite(directionalVelocity) || directionalVelocity <= MIN_SWIPE_RELEASE_VELOCITY) {
2753
+ return null;
2754
+ }
2755
+ const clampedVelocity = clamp.clamp(directionalVelocity, MIN_SWIPE_RELEASE_VELOCITY, MAX_SWIPE_RELEASE_VELOCITY);
2756
+ const durationMs = clamp.clamp(remainingDistance / clampedVelocity, MIN_SWIPE_RELEASE_DURATION_MS, MAX_SWIPE_RELEASE_DURATION_MS);
2757
+ if (!Number.isFinite(durationMs)) {
2758
+ return null;
2759
+ }
2760
+ const normalizedDuration = (durationMs - MIN_SWIPE_RELEASE_DURATION_MS) / (MAX_SWIPE_RELEASE_DURATION_MS - MIN_SWIPE_RELEASE_DURATION_MS);
2761
+ const durationScalar = clamp.clamp(MIN_SWIPE_RELEASE_SCALAR + normalizedDuration * (MAX_SWIPE_RELEASE_SCALAR - MIN_SWIPE_RELEASE_SCALAR), MIN_SWIPE_RELEASE_SCALAR, MAX_SWIPE_RELEASE_SCALAR);
2762
+ if (!Number.isFinite(durationScalar) || durationScalar <= 0) {
2763
+ return null;
2764
+ }
2765
+ return durationScalar;
2766
+ }
2767
+ function updateNestedSwipeActive(details) {
2768
+ if (nestedSwipeActiveRef.current || !details) {
2769
+ return;
2770
+ }
2771
+ const direction = details.direction ?? swipeDirection;
2772
+ const delta = direction === 'left' || direction === 'right' ? details.deltaX : details.deltaY;
2773
+ if (!Number.isFinite(delta) || Math.abs(delta) < MIN_SWIPE_THRESHOLD) {
2774
+ return;
2775
+ }
2776
+ nestedSwipeActiveRef.current = true;
2777
+ notifyParentSwipingChange?.(true);
2778
+ }
2779
+ const swipe = useSwipeDismiss({
2780
+ enabled: mounted && !nestedDrawerOpen,
2781
+ directions: swipeDirections,
2782
+ elementRef: store.context.popupRef,
2783
+ ignoreSelectorWhenTouch: false,
2784
+ ignoreScrollableAncestors: true,
2785
+ movementCssVars: {
2786
+ x: DrawerPopupCssVars.swipeMovementX,
2787
+ y: DrawerPopupCssVars.swipeMovementY
2788
+ },
2789
+ onSwipeStart(event) {
2790
+ if ('touches' in event || 'pointerType' in event && event.pointerType === 'touch') {
2791
+ return;
2792
+ }
2793
+ const popupElement = popupRef.current;
2794
+ if (!popupElement) {
2795
+ return;
2796
+ }
2797
+ const doc = useButton.ownerDocument(popupElement);
2798
+ const selection = doc.getSelection?.();
2799
+ if (!selection || selection.isCollapsed) {
2800
+ return;
2801
+ }
2802
+ const anchorElement = floatingUi_utils_dom.isElement(selection.anchorNode) ? selection.anchorNode : selection.anchorNode?.parentElement;
2803
+ const focusElement = floatingUi_utils_dom.isElement(selection.focusNode) ? selection.focusNode : selection.focusNode?.parentElement;
2804
+ if (!useButton.contains(popupElement, anchorElement) && !useButton.contains(popupElement, focusElement)) {
2805
+ return;
2806
+ }
2807
+ selection.removeAllRanges();
2808
+ },
2809
+ onSwipingChange(swiping) {
2810
+ swipingRef.current = swiping;
2811
+ setBackdropSwipingAttribute(store.context.backdropRef.current, swiping);
2812
+ if (!swiping && !notifyParentSwipeProgressChange) {
2813
+ finishNestedSwipe();
2814
+ }
2815
+ },
2816
+ swipeThreshold({
2817
+ element,
2818
+ direction
2819
+ }) {
2820
+ return getBaseSwipeThreshold(element, direction);
2821
+ },
2822
+ canStart(position, details) {
2823
+ const popupElement = store.context.popupRef.current;
2824
+ if (!popupElement) {
2825
+ return false;
2826
+ }
2827
+ const doc = popupElement.ownerDocument;
2828
+ const elementAtPoint = getElementAtPoint(doc, position.x, position.y);
2829
+ if (!elementAtPoint || !useButton.contains(popupElement, elementAtPoint)) {
2830
+ return false;
2831
+ }
2832
+ const nativeEvent = details.nativeEvent;
2833
+ const touchLike = 'touches' in nativeEvent || 'pointerType' in nativeEvent && nativeEvent.pointerType === 'touch';
2834
+ if (touchLike && shouldIgnoreSwipeForTextSelection(doc, popupElement)) {
2835
+ return false;
2836
+ }
2837
+ if (nativeEvent.type === 'touchstart' && isSwipeIgnoredTarget(elementAtPoint)) {
2838
+ return false;
2839
+ }
2840
+ return true;
2841
+ },
2842
+ onProgress(progress, details) {
2843
+ updateNestedSwipeActive(details);
2844
+ const hasSnapPoints = Boolean(snapPoints && snapPoints.length > 0);
2845
+ if (swipingRef.current && swipeDirection === 'down' && hasSnapPoints && details && Number.isFinite(details.deltaY)) {
2846
+ const popupElement = store.context.popupRef.current;
2847
+ if (popupElement) {
2848
+ popupElement.style.removeProperty('transform');
2849
+ popupElement.style.setProperty(DrawerPopupCssVars.swipeMovementY, `${getSnapPointSwipeMovement(activeSnapPointOffset ?? 0, details.deltaY)}px`);
2850
+ }
2851
+ }
2852
+ const currentDirection = details?.direction ?? swipe.swipeDirection;
2853
+ const isDismissSwipe = currentDirection === undefined || currentDirection === swipeDirection;
2854
+ const isVerticalSwipe = swipeDirection === 'down' || swipeDirection === 'up';
2855
+ const shouldTrackProgress = hasSnapPoints && isVerticalSwipe || !hasSnapPoints || swipeDirection === 'left' || swipeDirection === 'right' || isDismissSwipe;
2856
+ let resolvedProgress = progress;
2857
+ if (snapPointRange && popupHeight > 0) {
2858
+ if (details && Number.isFinite(details.deltaY)) {
2859
+ const baseOffset = activeSnapPointOffset ?? snapPointRange.minOffset;
2860
+ const nextOffset = clamp.clamp(baseOffset + details.deltaY, 0, popupHeight);
2861
+ resolvedProgress = clamp.clamp((nextOffset - snapPointRange.minOffset) / snapPointRange.range, 0, 1);
2862
+ } else if (snapPointProgress !== null) {
2863
+ resolvedProgress = snapPointProgress;
2864
+ } else if (currentDirection === 'down' || currentDirection === 'up') {
2865
+ const displacement = progress * popupHeight;
2866
+ const baseOffset = activeSnapPointOffset ?? snapPointRange.minOffset;
2867
+ const nextOffset = currentDirection === 'down' ? baseOffset + displacement : baseOffset - displacement;
2868
+ resolvedProgress = clamp.clamp((nextOffset - snapPointRange.minOffset) / snapPointRange.range, 0, 1);
2869
+ }
2870
+ }
2871
+ applySwipeProgress({
2872
+ resolvedProgress,
2873
+ shouldTrackProgress,
2874
+ notifyParent: true
2875
+ });
2876
+ },
2877
+ onRelease({
2878
+ event,
2879
+ deltaX,
2880
+ deltaY,
2881
+ direction,
2882
+ velocityX,
2883
+ velocityY,
2884
+ releaseVelocityX,
2885
+ releaseVelocityY
2886
+ }) {
2887
+ const swipeReleasePayload = {
2888
+ deltaX,
2889
+ deltaY,
2890
+ velocityX,
2891
+ velocityY,
2892
+ releaseVelocityX,
2893
+ releaseVelocityY
2894
+ };
2895
+ function startSwipeRelease(resolvedDirection) {
2896
+ // Start ending transition styles earlier and synchronously to prevent a period where
2897
+ // the popup appears stuck on release before the actual closing animation starts.
2898
+ const popupElement = store.context.popupRef.current;
2899
+ if (!popupElement) {
2900
+ return;
2901
+ }
2902
+ finishNestedSwipe();
2903
+ setSwipeDismissed(true);
2904
+ popupElement.style.removeProperty('transition');
2905
+ popupElement.setAttribute(useButton.TransitionStatusDataAttributes.endingStyle, '');
2906
+ ReactDOM__namespace.flushSync(() => {
2907
+ setSwipeRelease(resolveSwipeRelease({
2908
+ direction: resolvedDirection,
2909
+ ...swipeReleasePayload
2910
+ }));
2911
+ });
2912
+ }
2913
+ if (!snapPoints || snapPoints.length === 0) {
2914
+ if (!direction) {
2915
+ clearSwipeRelease();
2916
+ return undefined;
2917
+ }
2918
+ const element = store.context.popupRef.current;
2919
+ if (!element) {
2920
+ clearSwipeRelease();
2921
+ return undefined;
2922
+ }
2923
+ const baseThreshold = getBaseSwipeThreshold(element, direction);
2924
+ const delta = direction === 'left' || direction === 'right' ? deltaX : deltaY;
2925
+ if (!Number.isFinite(delta)) {
2926
+ clearSwipeRelease();
2927
+ return undefined;
2928
+ }
2929
+ const directionalDelta = direction === 'left' || direction === 'up' ? -delta : delta;
2930
+ if (directionalDelta <= 0) {
2931
+ clearSwipeRelease();
2932
+ return false;
2933
+ }
2934
+ const velocity = direction === 'left' || direction === 'right' ? velocityX : velocityY;
2935
+ const directionalVelocity = direction === 'left' || direction === 'up' ? -velocity : velocity;
2936
+ if (directionalVelocity >= FAST_SWIPE_VELOCITY && directionalDelta > 0) {
2937
+ startSwipeRelease(direction);
2938
+ return true;
2939
+ }
2940
+ const shouldClose = directionalDelta > baseThreshold;
2941
+ if (shouldClose) {
2942
+ startSwipeRelease(direction);
2943
+ } else {
2944
+ clearSwipeRelease();
2945
+ }
2946
+ return shouldClose;
2947
+ }
2948
+ if (swipeDirection !== 'down' && swipeDirection !== 'up') {
2949
+ clearSwipeRelease();
2950
+ return undefined;
2951
+ }
2952
+ if (!popupHeight || resolvedSnapPoints.length === 0) {
2953
+ clearSwipeRelease();
2954
+ return undefined;
2955
+ }
2956
+ const dragDelta = swipeDirection === 'down' ? deltaY : -deltaY;
2957
+ if (!Number.isFinite(dragDelta)) {
2958
+ clearSwipeRelease();
2959
+ return undefined;
2960
+ }
2961
+ const dragDirection = Math.sign(dragDelta);
2962
+ const releaseDirectionalVelocity = swipeDirection === 'down' ? releaseVelocityY : -releaseVelocityY;
2963
+ const fallbackDirectionalVelocity = swipeDirection === 'down' ? velocityY : -velocityY;
2964
+ let resolvedDirectionalVelocity = Number.isFinite(releaseDirectionalVelocity) ? releaseDirectionalVelocity : fallbackDirectionalVelocity;
2965
+ if (dragDirection !== 0 && Math.abs(dragDelta) >= MIN_SWIPE_THRESHOLD && Number.isFinite(resolvedDirectionalVelocity)) {
2966
+ const velocityDirection = Math.sign(resolvedDirectionalVelocity);
2967
+ if (velocityDirection !== 0 && velocityDirection !== dragDirection) {
2968
+ // Ignore touch reversals that would otherwise flip the snap decision.
2969
+ resolvedDirectionalVelocity = fallbackDirectionalVelocity;
2970
+ }
2971
+ }
2972
+ const currentOffset = activeSnapPointOffset ?? 0;
2973
+ const dragTargetOffset = clamp.clamp(currentOffset + dragDelta, 0, popupHeight);
2974
+ const velocityOffset = Number.isFinite(resolvedDirectionalVelocity) && Math.abs(resolvedDirectionalVelocity) >= SNAP_VELOCITY_THRESHOLD ? clamp.clamp(resolvedDirectionalVelocity, -MAX_SNAP_VELOCITY, MAX_SNAP_VELOCITY) * SNAP_VELOCITY_MULTIPLIER : 0;
2975
+ const targetOffset = snapToSequentialPoints ? dragTargetOffset : clamp.clamp(dragTargetOffset + velocityOffset, 0, popupHeight);
2976
+ const snapPointEventDetails = useButton.createChangeEventDetails(useButton.swipe, event);
2977
+ const closeFromSnapPoints = () => {
2978
+ pendingSwipeCloseSnapPointRef.current = activeSnapPoint;
2979
+ setActiveSnapPoint?.(null, snapPointEventDetails);
2980
+ startSwipeRelease(swipeDirection);
2981
+ return true;
2982
+ };
2983
+ if (snapToSequentialPoints) {
2984
+ const orderedSnapPoints = [...resolvedSnapPoints].sort((first, second) => first.offset - second.offset);
2985
+ if (orderedSnapPoints.length === 0) {
2986
+ clearSwipeRelease();
2987
+ return false;
2988
+ }
2989
+ let currentIndex = 0;
2990
+ let closestDistance = Math.abs(currentOffset - orderedSnapPoints[0].offset);
2991
+ for (let index = 1; index < orderedSnapPoints.length; index += 1) {
2992
+ const distance = Math.abs(currentOffset - orderedSnapPoints[index].offset);
2993
+ if (distance < closestDistance) {
2994
+ closestDistance = distance;
2995
+ currentIndex = index;
2996
+ }
2997
+ }
2998
+ let targetSnapPoint = orderedSnapPoints[0];
2999
+ closestDistance = Math.abs(targetOffset - targetSnapPoint.offset);
3000
+ for (const snapPoint of orderedSnapPoints) {
3001
+ const distance = Math.abs(targetOffset - snapPoint.offset);
3002
+ if (distance < closestDistance) {
3003
+ closestDistance = distance;
3004
+ targetSnapPoint = snapPoint;
3005
+ }
3006
+ }
3007
+ const velocityDirection = Math.sign(resolvedDirectionalVelocity);
3008
+ const shouldAdvance = dragDirection !== 0 && velocityDirection !== 0 && velocityDirection === dragDirection && Math.abs(resolvedDirectionalVelocity) >= SNAP_VELOCITY_THRESHOLD;
3009
+ let effectiveTargetOffset = targetOffset;
3010
+ if (shouldAdvance) {
3011
+ const adjacentIndex = clamp.clamp(currentIndex + dragDirection, 0, orderedSnapPoints.length - 1);
3012
+ if (adjacentIndex !== currentIndex) {
3013
+ const adjacentPoint = orderedSnapPoints[adjacentIndex];
3014
+ const shouldForceAdjacent = dragDirection > 0 ? targetOffset < adjacentPoint.offset : targetOffset > adjacentPoint.offset;
3015
+ if (shouldForceAdjacent) {
3016
+ targetSnapPoint = adjacentPoint;
3017
+ effectiveTargetOffset = adjacentPoint.offset;
3018
+ }
3019
+ } else if (dragDirection > 0) {
3020
+ return closeFromSnapPoints();
3021
+ }
3022
+ }
3023
+ const closeOffset = popupHeight;
3024
+ const closeDistance = Math.abs(effectiveTargetOffset - closeOffset);
3025
+ const snapDistance = Math.abs(effectiveTargetOffset - targetSnapPoint.offset);
3026
+ if (closeDistance < snapDistance) {
3027
+ return closeFromSnapPoints();
3028
+ }
3029
+ setActiveSnapPoint?.(targetSnapPoint.value, snapPointEventDetails);
3030
+ clearSwipeRelease();
3031
+ return false;
3032
+ }
3033
+ if (resolvedDirectionalVelocity >= FAST_SWIPE_VELOCITY && dragDelta > 0) {
3034
+ return closeFromSnapPoints();
3035
+ }
3036
+ let closestSnapPoint = resolvedSnapPoints[0];
3037
+ let closestDistance = Math.abs(targetOffset - closestSnapPoint.offset);
3038
+ for (const snapPoint of resolvedSnapPoints) {
3039
+ const distance = Math.abs(targetOffset - snapPoint.offset);
3040
+ if (distance < closestDistance) {
3041
+ closestDistance = distance;
3042
+ closestSnapPoint = snapPoint;
3043
+ }
3044
+ }
3045
+ const closeOffset = popupHeight;
3046
+ const closeDistance = Math.abs(targetOffset - closeOffset);
3047
+ if (closeDistance < closestDistance) {
3048
+ return closeFromSnapPoints();
3049
+ }
3050
+ setActiveSnapPoint?.(closestSnapPoint.value, snapPointEventDetails);
3051
+ clearSwipeRelease();
3052
+ return false;
3053
+ },
3054
+ onDismiss(event) {
3055
+ visualStateStore?.set({
3056
+ swipeProgress: 0,
3057
+ frontmostHeight: 0
3058
+ });
3059
+ const backdropElement = store.context.backdropRef.current;
3060
+ if (backdropElement) {
3061
+ backdropElement.style.setProperty(DrawerBackdropCssVars.swipeProgress, '0');
3062
+ backdropElement.style.removeProperty(DrawerPopupCssVars.height);
3063
+ }
3064
+ const dismissEventDetails = useButton.createChangeEventDetails(useButton.swipe, event);
3065
+ store.setOpen(false, dismissEventDetails);
3066
+ if (dismissEventDetails.isCanceled) {
3067
+ const pendingSnapPoint = pendingSwipeCloseSnapPointRef.current;
3068
+ if (pendingSnapPoint !== undefined) {
3069
+ setActiveSnapPoint?.(pendingSnapPoint, useButton.createChangeEventDetails(useButton.swipe, event));
3070
+ }
3071
+ pendingSwipeCloseSnapPointRef.current = undefined;
3072
+ resetSwipeRef.current?.();
3073
+ clearSwipeRelease();
3074
+ return;
3075
+ }
3076
+
3077
+ // In controlled mode, the effective open state may not have changed yet
3078
+ // (openProp takes precedence over state.open). Proceed optimistically with the
3079
+ // dismiss animation — React's Scheduler flushes before the next rAF, so we can
3080
+ // reliably check whether the parent accepted or rejected the close.
3081
+ // Note: if onOpenChange is asynchronous (e.g., closes the drawer after a network
3082
+ // call), the rAF check will see open === true, revert the animation, and the
3083
+ // drawer will close without animation when the parent eventually sets open={false}.
3084
+ if (store.select('open')) {
3085
+ const savedEvent = event;
3086
+ controlledDismissFrame.request(() => {
3087
+ if (store.select('open')) {
3088
+ // Parent rejected: revert animation and restore snap point.
3089
+ const pendingSnapPoint = pendingSwipeCloseSnapPointRef.current;
3090
+ if (pendingSnapPoint !== undefined) {
3091
+ setActiveSnapPoint?.(pendingSnapPoint, useButton.createChangeEventDetails(useButton.swipe, savedEvent));
3092
+ }
3093
+ pendingSwipeCloseSnapPointRef.current = undefined;
3094
+ clearSwipeRelease();
3095
+ resetSwipeRef.current?.();
3096
+ } else {
3097
+ // Parent accepted: clean up the ref.
3098
+ pendingSwipeCloseSnapPointRef.current = undefined;
3099
+ }
3100
+ });
3101
+ return;
3102
+ }
3103
+ pendingSwipeCloseSnapPointRef.current = undefined;
3104
+ setSwipeDismissed(true);
3105
+ }
3106
+ });
3107
+ const swipePointerProps = swipe.getPointerProps();
3108
+ const swipeTouchProps = swipe.getTouchProps();
3109
+ const {
3110
+ moveNative: moveSwipeNative,
3111
+ reset: resetSwipe
3112
+ } = swipe;
3113
+ resetSwipeRef.current = resetSwipe;
3114
+ React__namespace.useEffect(() => {
3115
+ const rootElement = viewportElement ?? popupElementState;
3116
+ if (!rootElement) {
3117
+ return undefined;
3118
+ }
3119
+ const resolvedRootElement = rootElement;
3120
+ const doc = useButton.ownerDocument(resolvedRootElement);
3121
+ const win = floatingUi_utils_dom.getWindow(doc);
3122
+ function handleNativeTouchMove(event) {
3123
+ // The virtual keyboard provider observes the move to tell a tap apart from a drag.
3124
+ // It must run even when the swipe gesture below claims the event with
3125
+ // `stopPropagation()`, which would otherwise prevent React's delegated handlers
3126
+ // (and the provider) from ever seeing the move.
3127
+ virtualKeyboard?.onTouchMove(event);
3128
+ if (ignoreTouchSwipeRef.current) {
3129
+ return;
3130
+ }
3131
+ const touchState = touchScrollStateRef.current;
3132
+ const touch = event.touches[0];
3133
+ if (!touch || !touchState) {
3134
+ return;
3135
+ }
3136
+ const drawerAxisDelta = isVerticalScrollAxis ? touch.clientY - touchState.lastY : touch.clientX - touchState.lastX;
3137
+
3138
+ // Preserve native range interaction by never locking touchmove for range inputs.
3139
+ if (isEventOnRangeInput(event, win)) {
3140
+ touchState.allowSwipe = false;
3141
+ updateTouchScrollPosition(touchState, touch);
3142
+ return;
3143
+ }
3144
+
3145
+ // Avoid blocking pinch zoom or text selection adjustments on iOS Safari.
3146
+ if (event.touches.length === 2) {
3147
+ updateTouchScrollPosition(touchState, touch);
3148
+ return;
3149
+ }
3150
+ const allowTouchMove = shouldIgnoreSwipeForTextSelection(doc, resolvedRootElement);
3151
+ if (allowTouchMove || !open || !mounted || nestedDrawerOpen) {
3152
+ updateTouchScrollPosition(touchState, touch);
3153
+ return;
3154
+ }
3155
+ if (preserveNativeCrossAxisScrollOnMove(touchState, touch, isVerticalScrollAxis)) {
3156
+ updateTouchScrollPosition(touchState, touch);
3157
+ return;
3158
+ }
3159
+ const scrollTarget = touchState.scrollTarget;
3160
+ if (!scrollTarget || scrollTarget === doc.documentElement || scrollTarget === doc.body) {
3161
+ if (event.cancelable) {
3162
+ event.preventDefault();
3163
+ }
3164
+ // Claim the gesture before React's delegated touch handlers see it; dispatching the
3165
+ // move through React re-rasterizes the popup content on every frame.
3166
+ event.stopPropagation();
3167
+ moveSwipeNative(event, resolvedRootElement);
3168
+ updateTouchScrollPosition(touchState, touch);
3169
+ return;
3170
+ }
3171
+ const hasScrollableContent = hasScrollableContentOnAxis(scrollTarget, scrollAxis);
3172
+ if (!hasScrollableContent) {
3173
+ // If the scroll container doesn't overflow on the drawer axis, prevent the window from
3174
+ // scrolling instead.
3175
+ if (event.cancelable) {
3176
+ event.preventDefault();
3177
+ }
3178
+ event.stopPropagation();
3179
+ updateTouchScrollPosition(touchState, touch);
3180
+ return;
3181
+ }
3182
+ const delta = drawerAxisDelta;
3183
+ if (delta !== 0) {
3184
+ const canSwipeFromScrollEdge = canSwipeFromScrollEdgeOnMove(scrollTarget, scrollAxis, swipeDirection, delta);
3185
+ if (!touchState.allowSwipe) {
3186
+ if (!event.cancelable) {
3187
+ touchState.allowSwipe = false;
3188
+ } else if (canSwipeFromScrollEdge) {
3189
+ touchState.allowSwipe = true;
3190
+ event.preventDefault();
3191
+ } else {
3192
+ touchState.allowSwipe = false;
3193
+ }
3194
+ } else if (event.cancelable) {
3195
+ event.preventDefault();
3196
+ }
3197
+ }
3198
+ if (touchState.allowSwipe === true) {
3199
+ event.stopPropagation();
3200
+ moveSwipeNative(event, resolvedRootElement);
3201
+ }
3202
+ updateTouchScrollPosition(touchState, touch);
3203
+ }
3204
+ return useButton.addEventListener(doc, 'touchmove', handleNativeTouchMove, {
3205
+ passive: false,
3206
+ capture: true
3207
+ });
3208
+ }, [mounted, nestedDrawerOpen, open, popupElementState, isVerticalScrollAxis, scrollAxis, swipeDirection, moveSwipeNative, viewportElement, virtualKeyboard]);
3209
+ React__namespace.useEffect(() => {
3210
+ if (!snapPointRange || swipe.swiping) {
3211
+ return;
3212
+ }
3213
+ const resolvedProgress = !open || nested ? 0 : snapPointProgress ?? 0;
3214
+ applySwipeProgress({
3215
+ resolvedProgress,
3216
+ shouldTrackProgress: true,
3217
+ notifyParent: false
3218
+ });
3219
+ }, [applySwipeProgress, frontmostHeight, nested, notifyParentSwipeProgressChange, open, snapPointProgress, snapPointRange, swipe.swiping, store, visualStateStore]);
3220
+ React__namespace.useEffect(() => {
3221
+ if (!notifyParentSwipeProgressChange) {
3222
+ return undefined;
3223
+ }
3224
+ if (!open) {
3225
+ notifyParentSwipeProgressChange(0);
3226
+ }
3227
+ return () => {
3228
+ notifyParentSwipeProgressChange(0);
3229
+ };
3230
+ }, [notifyParentSwipeProgressChange, open]);
3231
+ React__namespace.useEffect(() => {
3232
+ if (open) {
3233
+ resetSwipe();
3234
+ clearSwipeRelease();
3235
+ }
3236
+ }, [clearSwipeRelease, open, resetSwipe]);
3237
+ React__namespace.useEffect(() => {
3238
+ const backdropElement = backdropRef.current;
3239
+ return () => {
3240
+ visualStateStore?.set({
3241
+ swipeProgress: 0,
3242
+ frontmostHeight: 0
3243
+ });
3244
+ setBackdropSwipingAttribute(backdropElement, false);
3245
+ // `data-swiping` is set on whichever backdrop is current when a swipe starts, which can
3246
+ // differ from the captured element if the backdrop mounted late or changed identity.
3247
+ // Reading the live ref here is intentional so the current backdrop is cleared too.
3248
+ // eslint-disable-next-line react-hooks/exhaustive-deps
3249
+ const currentBackdrop = backdropRef.current;
3250
+ if (currentBackdrop !== backdropElement) {
3251
+ setBackdropSwipingAttribute(currentBackdrop, false);
3252
+ }
3253
+ finishNestedSwipe();
3254
+ };
3255
+ }, [backdropRef, finishNestedSwipe, visualStateStore]);
3256
+ const swipeProviderValue = React__namespace.useMemo(() => ({
3257
+ swiping: swipe.swiping,
3258
+ getDragStyles: swipe.getDragStyles,
3259
+ swipeStrength: swipeRelease ?? null,
3260
+ setSwipeDismissed
3261
+ }), [setSwipeDismissed, swipe.getDragStyles, swipe.swiping, swipeRelease]);
3262
+ function resetTouchTrackingState() {
3263
+ ignoreTouchSwipeRef.current = false;
3264
+ touchScrollStateRef.current = null;
3265
+ lastPointerTypeRef.current = '';
3266
+ ignoreNextTouchStartFromPenRef.current = false;
3267
+ }
3268
+ return /*#__PURE__*/jsxRuntime.jsx(DialogViewport, {
3269
+ ref: forwardedRef,
3270
+ className: className,
3271
+ style: style,
3272
+ render: render,
3273
+ ...useRenderElement.mergeProps(elementProps, {
3274
+ onPointerDown(event) {
3275
+ lastPointerTypeRef.current = event.pointerType;
3276
+ ignoreNextTouchStartFromPenRef.current = event.pointerType === 'pen';
3277
+ if (!open || !mounted || nestedDrawerOpen) {
3278
+ return;
3279
+ }
3280
+ const doc = useButton.ownerDocument(event.currentTarget);
3281
+ const elementAtPoint = getElementAtPoint(doc, event.clientX, event.clientY);
3282
+ if (isSwipeIgnoredTarget(elementAtPoint) || isDrawerContentTarget(elementAtPoint)) {
3283
+ return;
3284
+ }
3285
+ if (event.pointerType === 'touch') {
3286
+ return;
3287
+ }
3288
+ swipePointerProps.onPointerDown?.(event);
3289
+ },
3290
+ onPointerMove(event) {
3291
+ if (event.pointerType === 'touch') {
3292
+ return;
3293
+ }
3294
+ swipePointerProps.onPointerMove?.(event);
3295
+ },
3296
+ onPointerUp(event) {
3297
+ if (lastPointerTypeRef.current === event.pointerType) {
3298
+ lastPointerTypeRef.current = '';
3299
+ }
3300
+ if (event.pointerType === 'touch') {
3301
+ return;
3302
+ }
3303
+ swipePointerProps.onPointerUp?.(event);
3304
+ },
3305
+ onPointerCancel(event) {
3306
+ if (lastPointerTypeRef.current === event.pointerType) {
3307
+ lastPointerTypeRef.current = '';
3308
+ }
3309
+ if (event.pointerType === 'touch') {
3310
+ return;
3311
+ }
3312
+ swipePointerProps.onPointerCancel?.(event);
3313
+ },
3314
+ onTouchStart(event) {
3315
+ const startedFromPenPointerDown = lastPointerTypeRef.current === 'pen' && ignoreNextTouchStartFromPenRef.current;
3316
+ if (startedFromPenPointerDown) {
3317
+ ignoreNextTouchStartFromPenRef.current = false;
3318
+ ignoreTouchSwipeRef.current = false;
3319
+ touchScrollStateRef.current = null;
3320
+ return;
3321
+ }
3322
+ if (!open || !mounted || nestedDrawerOpen) {
3323
+ ignoreTouchSwipeRef.current = false;
3324
+ touchScrollStateRef.current = null;
3325
+ return;
3326
+ }
3327
+ const touch = event.touches[0];
3328
+ if (!touch) {
3329
+ return;
3330
+ }
3331
+ if (isReactTouchEventOnRangeInput(event)) {
3332
+ ignoreTouchSwipeRef.current = false;
3333
+ touchScrollStateRef.current = null;
3334
+ return;
3335
+ }
3336
+ const doc = useButton.ownerDocument(event.currentTarget);
3337
+ const elementAtPoint = getElementAtPoint(doc, touch.clientX, touch.clientY);
3338
+ const rootElement = viewportElement ?? popupElementState;
3339
+ const eventTarget = useButton.getTarget(event.nativeEvent);
3340
+ const target = floatingUi_utils_dom.isElement(eventTarget) ? eventTarget : null;
3341
+ if (rootElement && target && !useButton.contains(rootElement, target)) {
3342
+ ignoreTouchSwipeRef.current = true;
3343
+ touchScrollStateRef.current = null;
3344
+ return;
3345
+ }
3346
+ virtualKeyboard?.onTouchStart(event);
3347
+ ignoreTouchSwipeRef.current = isSwipeIgnoredTarget(elementAtPoint);
3348
+ if (ignoreTouchSwipeRef.current) {
3349
+ touchScrollStateRef.current = null;
3350
+ return;
3351
+ }
3352
+ let scrollTarget = null;
3353
+ let hasCrossAxisScrollableContent = false;
3354
+ if (rootElement && target) {
3355
+ scrollTarget = findScrollableTouchTarget(target, rootElement, scrollAxis);
3356
+ hasCrossAxisScrollableContent = findScrollableTouchTarget(target, rootElement, crossScrollAxis) != null;
3357
+ }
3358
+ let allowSwipe = null;
3359
+ if (scrollTarget) {
3360
+ const canSwipeFromEdge = isAtSwipeStartEdge(scrollTarget, scrollAxis, swipeDirection);
3361
+ allowSwipe = canSwipeFromEdge ? null : false;
3362
+ }
3363
+ touchScrollStateRef.current = {
3364
+ startX: touch.clientX,
3365
+ startY: touch.clientY,
3366
+ lastX: touch.clientX,
3367
+ lastY: touch.clientY,
3368
+ scrollTarget,
3369
+ hasCrossAxisScrollableContent,
3370
+ allowSwipe,
3371
+ preserveNativeCrossAxisScroll: false
3372
+ };
3373
+ swipeTouchProps.onTouchStart?.(event);
3374
+ },
3375
+ onTouchEnd(event) {
3376
+ virtualKeyboard?.onTouchEnd(event);
3377
+ resetTouchTrackingState();
3378
+ swipeTouchProps.onTouchEnd?.(event);
3379
+ },
3380
+ onTouchCancel(event) {
3381
+ virtualKeyboard?.onTouchCancel();
3382
+ resetTouchTrackingState();
3383
+ swipeTouchProps.onTouchCancel?.(event);
3384
+ },
3385
+ // Drawer popups use drawer-specific nested state attributes.
3386
+ // Suppress DialogViewport's generic nested dialog attribute.
3387
+ ['data-nested-dialog-open']: undefined
3388
+ }),
3389
+ children: /*#__PURE__*/jsxRuntime.jsx(DrawerViewportContext.Provider, {
3390
+ value: swipeProviderValue,
3391
+ children: children
3392
+ })
3393
+ });
3394
+ });
3395
+ if (process.env.NODE_ENV !== "production") DrawerViewport.displayName = "DrawerViewport";
3396
+ function setSwipeDismissedElements(popupElement, backdropElement, dismissed) {
3397
+ if (dismissed) {
3398
+ popupElement?.setAttribute(DrawerPopupDataAttributes.swipeDismiss, '');
3399
+ backdropElement?.setAttribute(DrawerPopupDataAttributes.swipeDismiss, '');
3400
+ return;
3401
+ }
3402
+ popupElement?.removeAttribute(DrawerPopupDataAttributes.swipeDismiss);
3403
+ backdropElement?.removeAttribute(DrawerPopupDataAttributes.swipeDismiss);
3404
+ }
3405
+ function setBackdropSwipingAttribute(backdropElement, swiping) {
3406
+ if (!backdropElement) {
3407
+ return;
3408
+ }
3409
+ if (swiping) {
3410
+ backdropElement.setAttribute(DrawerPopupDataAttributes.swiping, '');
3411
+ return;
3412
+ }
3413
+ backdropElement.removeAttribute(DrawerPopupDataAttributes.swiping);
3414
+ }
3415
+ function isSwipeIgnoredTarget(target) {
3416
+ return Boolean(target?.closest(useScrollLock.BASE_UI_SWIPE_IGNORE_SELECTOR));
3417
+ }
3418
+ function isDrawerContentTarget(target) {
3419
+ return Boolean(target?.closest(DRAWER_CONTENT_SELECTOR));
3420
+ }
3421
+ function getBaseSwipeThreshold(element, direction) {
3422
+ const size = direction === 'left' || direction === 'right' ? element.offsetWidth : element.offsetHeight;
3423
+ return Math.max(size * 0.5, MIN_SWIPE_THRESHOLD);
3424
+ }
3425
+ function isRangeInput(target, win) {
3426
+ return target instanceof win.HTMLInputElement && target.type === 'range';
3427
+ }
3428
+ function isTextSelectionControl(target) {
3429
+ if (!floatingUi_utils_dom.isElement(target)) {
3430
+ return false;
3431
+ }
3432
+ return target.tagName === 'INPUT' || target.tagName === 'TEXTAREA';
3433
+ }
3434
+ function hasExpandedSelectionWithinTarget(selection, target) {
3435
+ const anchorElement = floatingUi_utils_dom.isElement(selection.anchorNode) ? selection.anchorNode : selection.anchorNode?.parentElement;
3436
+ const focusElement = floatingUi_utils_dom.isElement(selection.focusNode) ? selection.focusNode : selection.focusNode?.parentElement;
3437
+ return selection.containsNode(target, true) || useButton.contains(target, anchorElement) || useButton.contains(target, focusElement);
3438
+ }
3439
+ function shouldIgnoreSwipeForTextSelection(doc, rootElement) {
3440
+ const activeEl = useButton.activeElement(doc);
3441
+ const activeElementWithinRoot = Boolean(activeEl && useButton.contains(rootElement, activeEl));
3442
+ if (activeElementWithinRoot && isTextSelectionControl(activeEl)) {
3443
+ const {
3444
+ selectionStart,
3445
+ selectionEnd
3446
+ } = activeEl;
3447
+ if (selectionStart != null && selectionEnd != null && selectionStart < selectionEnd) {
3448
+ return true;
3449
+ }
3450
+ }
3451
+ const selection = doc.getSelection?.();
3452
+ if (!selection || selection.isCollapsed) {
3453
+ return false;
3454
+ }
3455
+ return hasExpandedSelectionWithinTarget(selection, rootElement);
3456
+ }
3457
+ function isEventOnRangeInput(event, win) {
3458
+ const composedPath = event.composedPath();
3459
+ if (composedPath) {
3460
+ return composedPath.some(pathTarget => isRangeInput(pathTarget, win));
3461
+ }
3462
+ return isRangeInput(useButton.getTarget(event), win);
3463
+ }
3464
+ function isReactTouchEventOnRangeInput(event) {
3465
+ return isEventOnRangeInput(event.nativeEvent, floatingUi_utils_dom.getWindow(event.currentTarget));
3466
+ }
3467
+ function updateTouchScrollPosition(touchState, touch) {
3468
+ touchState.lastX = touch.clientX;
3469
+ touchState.lastY = touch.clientY;
3470
+ }
3471
+ function preserveNativeCrossAxisScrollOnMove(touchState, touch, isVerticalScrollAxis) {
3472
+ if (touchState.preserveNativeCrossAxisScroll) {
3473
+ return true;
3474
+ }
3475
+ if (touchState.allowSwipe === true || !touchState.hasCrossAxisScrollableContent) {
3476
+ return false;
3477
+ }
3478
+ const drawerAxisGestureDelta = isVerticalScrollAxis ? touch.clientY - touchState.startY : touch.clientX - touchState.startX;
3479
+ const crossAxisGestureDelta = isVerticalScrollAxis ? touch.clientX - touchState.startX : touch.clientY - touchState.startY;
3480
+ const absDrawerAxisGestureDelta = Math.abs(drawerAxisGestureDelta);
3481
+ const absCrossAxisGestureDelta = Math.abs(crossAxisGestureDelta);
3482
+ if (absCrossAxisGestureDelta < 6 || absCrossAxisGestureDelta <= absDrawerAxisGestureDelta + 2) {
3483
+ return false;
3484
+ }
3485
+ touchState.preserveNativeCrossAxisScroll = true;
3486
+ return true;
3487
+ }
3488
+ function hasScrollableContentOnAxis(scrollTarget, axis) {
3489
+ return axis === 'vertical' ? scrollTarget.scrollHeight > scrollTarget.clientHeight : scrollTarget.scrollWidth > scrollTarget.clientWidth;
3490
+ }
3491
+ function getScrollMetrics(scrollTarget, axis) {
3492
+ if (axis === 'vertical') {
3493
+ const max = Math.max(0, scrollTarget.scrollHeight - scrollTarget.clientHeight);
3494
+ return {
3495
+ offset: scrollTarget.scrollTop,
3496
+ max
3497
+ };
3498
+ }
3499
+ const max = Math.max(0, scrollTarget.scrollWidth - scrollTarget.clientWidth);
3500
+ return {
3501
+ offset: scrollTarget.scrollLeft,
3502
+ max
3503
+ };
3504
+ }
3505
+ function isAtSwipeStartEdge(scrollTarget, axis, direction) {
3506
+ const {
3507
+ offset,
3508
+ max
3509
+ } = getScrollMetrics(scrollTarget, axis);
3510
+ const dismissFromStartEdge = shouldDismissFromStartEdge(direction, axis);
3511
+ if (dismissFromStartEdge === null) {
3512
+ return false;
3513
+ }
3514
+ return dismissFromStartEdge ? offset <= 0 : offset >= max;
3515
+ }
3516
+ function canSwipeFromScrollEdgeOnMove(scrollTarget, axis, direction, delta) {
3517
+ const {
3518
+ offset,
3519
+ max
3520
+ } = getScrollMetrics(scrollTarget, axis);
3521
+ const dismissFromStartEdge = shouldDismissFromStartEdge(direction, axis);
3522
+ if (dismissFromStartEdge === null) {
3523
+ return false;
3524
+ }
3525
+ const movingTowardDismiss = dismissFromStartEdge ? delta > 0 : delta < 0;
3526
+ if (!movingTowardDismiss) {
3527
+ return false;
3528
+ }
3529
+ return dismissFromStartEdge ? offset <= 0 : offset >= max;
3530
+ }
3531
+ function shouldDismissFromStartEdge(direction, axis) {
3532
+ if (axis === 'vertical') {
3533
+ if (direction === 'down') {
3534
+ return true;
3535
+ }
3536
+ if (direction === 'up') {
3537
+ return false;
3538
+ }
3539
+ return null;
3540
+ }
3541
+ if (direction === 'right') {
3542
+ return true;
3543
+ }
3544
+ if (direction === 'left') {
3545
+ return false;
3546
+ }
3547
+ return null;
3548
+ }
1443
3549
 
1444
3550
  const ScrollAreaRootContext = /*#__PURE__*/React__namespace.createContext(undefined);
1445
3551
  if (process.env.NODE_ENV !== "production") ScrollAreaRootContext.displayName = "ScrollAreaRootContext";
@@ -2221,11 +4327,11 @@ function mergeHiddenState(prevState, nextState) {
2221
4327
  return nextState;
2222
4328
  }
2223
4329
 
2224
- exports.DRAWER_CONTENT_ATTRIBUTE = DRAWER_CONTENT_ATTRIBUTE;
2225
4330
  exports.DialogPortal = DialogPortal;
2226
4331
  exports.DialogRoot = DialogRoot;
2227
4332
  exports.DialogStore = DialogStore;
2228
4333
  exports.DialogTrigger = DialogTrigger;
4334
+ exports.DialogViewport = DialogViewport;
2229
4335
  exports.DrawerBackdrop = DrawerBackdrop;
2230
4336
  exports.DrawerBackdropCssVars = DrawerBackdropCssVars;
2231
4337
  exports.DrawerContent = DrawerContent;
@@ -2236,19 +4342,23 @@ exports.DrawerPortal = DrawerPortal;
2236
4342
  exports.DrawerProviderContext = DrawerProviderContext;
2237
4343
  exports.DrawerRoot = DrawerRoot;
2238
4344
  exports.DrawerTrigger = DrawerTrigger;
2239
- exports.DrawerViewportContext = DrawerViewportContext;
4345
+ exports.DrawerViewport = DrawerViewport;
4346
+ exports.DrawerVirtualKeyboardContext = DrawerVirtualKeyboardContext;
2240
4347
  exports.ScrollAreaRoot = ScrollAreaRoot;
2241
4348
  exports.ScrollAreaRootCssVars = ScrollAreaRootCssVars;
2242
4349
  exports.ScrollAreaViewport = ScrollAreaViewport;
2243
4350
  exports.createInitialPopupStoreState = createInitialPopupStoreState;
4351
+ exports.findScrollableTouchTarget = findScrollableTouchTarget;
4352
+ exports.getDisplacement = getDisplacement;
4353
+ exports.getElementAtPoint = getElementAtPoint;
4354
+ exports.getElementTransform = getElementTransform;
2244
4355
  exports.getOffset = getOffset;
2245
- exports.getSnapPointSwipeMovement = getSnapPointSwipeMovement;
2246
4356
  exports.popupStoreSelectors = popupStoreSelectors;
2247
4357
  exports.scrollAreaStateAttributesMapping = scrollAreaStateAttributesMapping;
2248
4358
  exports.useDialogPortalContext = useDialogPortalContext;
2249
4359
  exports.useDialogRootContext = useDialogRootContext;
2250
4360
  exports.useDrawerProviderContext = useDrawerProviderContext;
2251
4361
  exports.useDrawerRootContext = useDrawerRootContext;
2252
- exports.useDrawerSnapPoints = useDrawerSnapPoints;
2253
4362
  exports.useScrollAreaRootContext = useScrollAreaRootContext;
2254
4363
  exports.useScrollAreaViewportContext = useScrollAreaViewportContext;
4364
+ exports.useSwipeDismiss = useSwipeDismiss;