@jobber/components 8.27.0 → 8.27.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,12 +10,12 @@ var DrawerDescription = require('../DrawerDescription-cjs.js');
10
10
  var DrawerTitle = require('../DrawerTitle-cjs.js');
11
11
  var ScrollAreaViewport = require('../ScrollAreaViewport-cjs.js');
12
12
  var useRenderElement = require('../useRenderElement-cjs.js');
13
+ var useScrollLock = require('../useScrollLock-cjs.js');
13
14
  var floatingUi_utils_dom = require('../floating-ui.utils.dom-cjs.js');
14
15
  var clamp = require('../clamp-cjs.js');
15
- var useScrollLock = require('../useScrollLock-cjs.js');
16
- var ReactDOM = require('react-dom');
17
16
  var MenuSubmenuTrigger = require('../MenuSubmenuTrigger-cjs.js');
18
17
  var useCompositeListItem = require('../useCompositeListItem-cjs.js');
18
+ var ReactDOM = require('react-dom');
19
19
  var SelectGroupLabel = require('../SelectGroupLabel-cjs.js');
20
20
  var floatingUi_utils = require('../floating-ui.utils-cjs.js');
21
21
  var NumberFieldInput = require('../NumberFieldInput-cjs.js');
@@ -217,7 +217,7 @@ var index_parts$6 = /*#__PURE__*/Object.freeze({
217
217
  Root: ScrollAreaViewport.DialogRoot,
218
218
  Title: DrawerTitle.DialogTitle,
219
219
  Trigger: ScrollAreaViewport.DialogTrigger,
220
- Viewport: DrawerDescription.DialogViewport,
220
+ Viewport: ScrollAreaViewport.DialogViewport,
221
221
  createHandle: DrawerDescription.createDialogHandle
222
222
  });
223
223
 
@@ -420,1198 +420,188 @@ function createVisualStateStore() {
420
420
  };
421
421
  }
422
422
 
423
- function isScrollable(element, axis,
424
- // When true, a container that overflows only once extra space is added (e.g. drawer
425
- // keyboard scroll slack) still counts, as long as it has layout size on the axis.
426
- allowOverflowIntent = false) {
427
- const style = floatingUi_utils_dom.getComputedStyle(element);
428
- if (axis === 'vertical') {
429
- const overflowY = style.overflowY;
430
- if (overflowY !== 'auto' && overflowY !== 'scroll') {
431
- return false;
432
- }
433
- return allowOverflowIntent ? element.clientHeight > 0 : element.scrollHeight > element.clientHeight;
434
- }
435
- const overflowX = style.overflowX;
436
- if (overflowX !== 'auto' && overflowX !== 'scroll') {
437
- return false;
438
- }
439
- return allowOverflowIntent ? element.clientWidth > 0 : element.scrollWidth > element.clientWidth;
440
- }
441
- function hasScrollableAncestor(target, root, axes) {
442
- // `getParentNode` crosses shadow boundaries (and slots), so a target inside a shadow root
443
- // still walks up to scrollable ancestors in the light DOM.
444
- let node = target;
445
- while (floatingUi_utils_dom.isHTMLElement(node) && node !== root && !floatingUi_utils_dom.isLastTraversableNode(node)) {
446
- for (const axis of axes) {
447
- if (isScrollable(node, axis)) {
448
- return true;
449
- }
450
- }
451
- node = floatingUi_utils_dom.getParentNode(node);
452
- }
453
- return false;
454
- }
455
- function findScrollableTouchTarget(target, root, axis = 'vertical', allowOverflowIntent = false) {
456
- // `getParentNode` crosses shadow boundaries (and slots), so a target inside a shadow root
457
- // still reaches a scrollable ancestor in the light DOM.
458
- let node = floatingUi_utils_dom.isHTMLElement(target) ? target : null;
459
- while (floatingUi_utils_dom.isHTMLElement(node) && node !== root && !floatingUi_utils_dom.isLastTraversableNode(node)) {
460
- if (isScrollable(node, axis, allowOverflowIntent)) {
461
- return node;
462
- }
463
- node = floatingUi_utils_dom.getParentNode(node);
464
- }
465
- return isScrollable(root, axis, allowOverflowIntent) ? root : null;
466
- }
467
-
468
- function getElementAtPoint(doc, x, y) {
469
- return typeof doc?.elementFromPoint === 'function' ? doc.elementFromPoint(x, y) : null;
470
- }
423
+ let DrawerSwipeAreaDataAttributes = function (DrawerSwipeAreaDataAttributes) {
424
+ /**
425
+ * Present when the drawer is open.
426
+ */
427
+ DrawerSwipeAreaDataAttributes[DrawerSwipeAreaDataAttributes["open"] = useScrollLock.CommonPopupDataAttributes.open] = "open";
428
+ /**
429
+ * Present when the drawer is closed.
430
+ */
431
+ DrawerSwipeAreaDataAttributes[DrawerSwipeAreaDataAttributes["closed"] = useScrollLock.CommonPopupDataAttributes.closed] = "closed";
432
+ /**
433
+ * Present when the swipe area is disabled.
434
+ */
435
+ DrawerSwipeAreaDataAttributes["disabled"] = "data-disabled";
436
+ /**
437
+ * Indicates the swipe direction.
438
+ * @type {'up' | 'down' | 'left' | 'right'}
439
+ */
440
+ DrawerSwipeAreaDataAttributes["swipeDirection"] = "data-swipe-direction";
441
+ /**
442
+ * Present when the drawer is being swiped.
443
+ */
444
+ DrawerSwipeAreaDataAttributes["swiping"] = "data-swiping";
445
+ return DrawerSwipeAreaDataAttributes;
446
+ }({});
471
447
 
472
- const DEFAULT_SWIPE_THRESHOLD = 40;
473
- const REVERSE_CANCEL_THRESHOLD = 10;
474
- const MIN_DRAG_THRESHOLD = 1;
475
- const MIN_VELOCITY_DURATION_MS = 50;
476
- const MIN_RELEASE_VELOCITY_DURATION_MS = 16;
477
- const MAX_RELEASE_VELOCITY_AGE_MS = 80;
478
- const DEFAULT_IGNORE_SELECTOR = 'button,a,input,select,textarea,label,[role="button"]';
479
- function getDisplacement(direction, deltaX, deltaY) {
480
- switch (direction) {
481
- case 'up':
482
- return -deltaY;
483
- case 'down':
484
- return deltaY;
485
- case 'left':
486
- return -deltaX;
487
- case 'right':
488
- return deltaX;
489
- default:
490
- return 0;
491
- }
492
- }
493
- function getElementTransform(element) {
494
- const computedStyle = floatingUi_utils_dom.getWindow(element).getComputedStyle(element);
495
- const transform = computedStyle.transform;
496
- let translateX = 0;
497
- let translateY = 0;
498
- let scale = 1;
499
- if (transform && transform !== 'none') {
500
- const matrix = transform.match(/matrix(?:3d)?\(([^)]+)\)/);
501
- if (matrix) {
502
- const values = matrix[1].split(', ').map(parseFloat);
503
- if (values.length === 6) {
504
- translateX = values[4];
505
- translateY = values[5];
506
- scale = Math.sqrt(values[0] * values[0] + values[1] * values[1]);
507
- } else if (values.length === 16) {
508
- translateX = values[12];
509
- translateY = values[13];
510
- scale = values[0];
511
- }
512
- }
513
- }
514
- return {
515
- x: translateX,
516
- y: translateY,
517
- scale
518
- };
519
- }
520
- function getValidTimeStamp(timeStamp) {
521
- return Number.isFinite(timeStamp) && timeStamp > 0 ? timeStamp : null;
522
- }
523
- function getDragTransform(dragOffset, scale) {
524
- return `translate3d(${dragOffset.x}px,${dragOffset.y}px,0) scale(${scale})`;
525
- }
526
- function hasPrimaryMouseButton(buttons) {
527
- return buttons % 2 === 1;
528
- }
529
- function safelyChangePointerCapture(element, pointerId, method) {
530
- const pointerCaptureMethod = element[method];
531
- if (typeof pointerCaptureMethod !== 'function') {
532
- return;
533
- }
534
- try {
535
- pointerCaptureMethod.call(element, pointerId);
536
- } catch (error) {
537
- if (error && typeof error === 'object' && 'name' in error && error.name === 'NotFoundError') {
538
- return;
539
- }
540
- throw error;
448
+ const DEFAULT_SWIPE_OPEN_RATIO = 0.5;
449
+ const MIN_SWIPE_START_DISTANCE = 1;
450
+ const VELOCITY_THRESHOLD = 0.1;
451
+ const FALLBACK_SWIPE_OPEN_THRESHOLD = 40;
452
+ const SWIPE_AREA_OPEN_HOOK = {
453
+ [DrawerSwipeAreaDataAttributes.open]: ''
454
+ };
455
+ const SWIPE_AREA_CLOSED_HOOK = {
456
+ [DrawerSwipeAreaDataAttributes.closed]: ''
457
+ };
458
+ const SWIPE_AREA_SWIPING_HOOK = {
459
+ [DrawerSwipeAreaDataAttributes.swiping]: ''
460
+ };
461
+ const SWIPE_AREA_DISABLED_HOOK = {
462
+ [DrawerSwipeAreaDataAttributes.disabled]: ''
463
+ };
464
+ const stateAttributesMapping$3 = {
465
+ open(value) {
466
+ return value ? SWIPE_AREA_OPEN_HOOK : SWIPE_AREA_CLOSED_HOOK;
467
+ },
468
+ swiping(value) {
469
+ return value ? SWIPE_AREA_SWIPING_HOOK : null;
470
+ },
471
+ swipeDirection(value) {
472
+ return value ? {
473
+ [DrawerSwipeAreaDataAttributes.swipeDirection]: value
474
+ } : null;
475
+ },
476
+ disabled(value) {
477
+ return value ? SWIPE_AREA_DISABLED_HOOK : null;
541
478
  }
479
+ };
480
+ const oppositeSwipeDirection = {
481
+ up: 'down',
482
+ down: 'up',
483
+ left: 'right',
484
+ right: 'left'
485
+ };
486
+ function resolveTouchAction(direction) {
487
+ return direction === 'left' || direction === 'right' ? 'pan-y' : 'pan-x';
542
488
  }
543
- function useSwipeDismiss(options) {
489
+
490
+ /**
491
+ * An invisible area that listens for swipe gestures to open the drawer.
492
+ * Renders a `<div>` element.
493
+ *
494
+ * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)
495
+ */
496
+ const DrawerSwipeArea = /*#__PURE__*/React__namespace.forwardRef(function DrawerSwipeArea(componentProps, forwardedRef) {
544
497
  const {
545
- enabled,
546
- directions,
547
- elementRef,
548
- movementCssVars,
549
- canStart,
550
- ignoreSelectorWhenTouch = true,
551
- ignoreScrollableAncestors = false,
552
- swipeThreshold: swipeThresholdProp,
553
- onDismiss,
554
- onProgress,
555
- onCancel,
556
- onSwipeStart,
557
- onRelease,
558
- onSwipingChange,
559
- trackDrag = true
560
- } = options;
561
- const ignoreSelector = DEFAULT_IGNORE_SELECTOR;
562
- const primaryDirection = directions.length === 1 ? directions[0] : undefined;
563
- const swipeThresholdDefault = Math.max(0, typeof swipeThresholdProp === 'number' ? swipeThresholdProp : DEFAULT_SWIPE_THRESHOLD);
564
- const allowLeft = directions.includes('left');
565
- const allowRight = directions.includes('right');
566
- const allowUp = directions.includes('up');
567
- const allowDown = directions.includes('down');
568
- const hasHorizontal = allowLeft || allowRight;
569
- const hasVertical = allowUp || allowDown;
570
- const scrollAxes = React__namespace.useMemo(() => {
571
- const axes = [];
572
- if (hasVertical) {
573
- axes.push('vertical');
574
- }
575
- if (hasHorizontal) {
576
- axes.push('horizontal');
577
- }
578
- return axes;
579
- }, [hasHorizontal, hasVertical]);
580
- const [currentSwipeDirection, setCurrentSwipeDirection] = React__namespace.useState(undefined);
581
- const [isSwiping, setIsSwiping] = React__namespace.useState(false);
582
- const [dragDismissed, setDragDismissed] = React__namespace.useState(false);
583
- const dragStartPosRef = React__namespace.useRef({
584
- x: 0,
585
- y: 0
586
- });
587
- const dragOffsetRef = React__namespace.useRef({
588
- x: 0,
589
- y: 0
590
- });
591
- const lastMovePosRef = React__namespace.useRef(null);
592
- const initialTransformRef = React__namespace.useRef({
593
- x: 0,
594
- y: 0,
595
- scale: 1
596
- });
597
- const intendedSwipeDirectionRef = React__namespace.useRef(undefined);
598
- const maxSwipeDisplacementRef = React__namespace.useRef(0);
599
- const cancelledSwipeRef = React__namespace.useRef(false);
600
- const swipeCancelBaselineRef = React__namespace.useRef({
601
- x: 0,
602
- y: 0
603
- });
604
- const lockedDirectionRef = React__namespace.useRef(null);
605
- const isFirstPointerMoveRef = React__namespace.useRef(false);
606
- const pendingSwipeRef = React__namespace.useRef(false);
607
- const pendingSwipeStartPosRef = React__namespace.useRef(null);
608
- const swipeFromScrollableRef = React__namespace.useRef(false);
609
- const sawPrimaryButtonsOnMoveRef = React__namespace.useRef(false);
610
- const elementSizeRef = React__namespace.useRef({
611
- width: 0,
612
- height: 0
613
- });
614
- const swipeProgressRef = React__namespace.useRef(0);
615
- const swipeThresholdRef = React__namespace.useRef(swipeThresholdDefault);
616
- const swipeStartTimeRef = React__namespace.useRef(null);
617
- const lastDragSampleRef = React__namespace.useRef(null);
618
- const lastDragVelocityRef = React__namespace.useRef({
498
+ render,
499
+ className,
500
+ style,
501
+ disabled = false,
502
+ swipeDirection: swipeDirectionProp,
503
+ ...elementProps
504
+ } = componentProps;
505
+ const {
506
+ store
507
+ } = ScrollAreaViewport.useDialogRootContext();
508
+ const {
509
+ swipeDirection,
510
+ frontmostHeight
511
+ } = ScrollAreaViewport.useDrawerRootContext();
512
+ const providerContext = ScrollAreaViewport.useDrawerProviderContext();
513
+ const [swipeActive, setSwipeActive] = React__namespace.useState(false);
514
+ const releaseDismissTimeout = useButton.useTimeout();
515
+ const swipeAreaRef = React__namespace.useRef(null);
516
+ const swipeStartEventRef = React__namespace.useRef(null);
517
+ const openedBySwipeRef = React__namespace.useRef(false);
518
+ const dragDeltaRef = React__namespace.useRef({
619
519
  x: 0,
620
520
  y: 0
621
521
  });
622
- const lastProgressDetailsRef = React__namespace.useRef(null);
623
- const isSwipingRef = React__namespace.useRef(false);
624
- const dragStyleSnapshotRef = React__namespace.useRef(null);
625
- const setSwiping = useButton.useStableCallback(nextSwiping => {
626
- if (isSwipingRef.current === nextSwiping) {
627
- return;
628
- }
629
- isSwipingRef.current = nextSwiping;
630
- setIsSwiping(nextSwiping);
631
- onSwipingChange?.(nextSwiping);
522
+ const closedOffsetRef = React__namespace.useRef(null);
523
+ const appliedSwipeStylesRef = React__namespace.useRef(false);
524
+ const popupTransitionRef = React__namespace.useRef(null);
525
+ const swipeAreaId = useButton.useBaseUiId(componentProps.id);
526
+ const registerTrigger = useScrollLock.useTriggerRegistration(swipeAreaId, store);
527
+ const open = store.useState('open');
528
+ const resetDragDelta = useButton.useStableCallback(() => {
529
+ dragDeltaRef.current.x = 0;
530
+ dragDeltaRef.current.y = 0;
632
531
  });
633
- function resolveSwipeThreshold(direction) {
634
- if (!direction) {
635
- return;
636
- }
637
- if (typeof swipeThresholdProp !== 'function') {
638
- swipeThresholdRef.current = swipeThresholdDefault;
639
- return;
640
- }
641
- const element = elementRef.current;
642
- if (!element) {
643
- return;
644
- }
645
- const value = swipeThresholdProp({
646
- element,
647
- direction
532
+ const resolvedSwipeDirection = swipeDirectionProp ?? oppositeSwipeDirection[swipeDirection];
533
+ const dismissDirection = oppositeSwipeDirection[resolvedSwipeDirection];
534
+ const enabled = !disabled && (!open || swipeActive);
535
+ function disableDismissForSwipe() {
536
+ releaseDismissTimeout.clear();
537
+ store.context.outsidePressEnabledRef.current = false;
538
+ }
539
+ function enableDismissAfterRelease() {
540
+ // Safari can dispatch outside-press for the same swipe-open gesture
541
+ // after release, so defer re-enabling dismissal to the next macrotask.
542
+ releaseDismissTimeout.start(0, () => {
543
+ store.context.outsidePressEnabledRef.current = true;
648
544
  });
649
- swipeThresholdRef.current = Math.max(0, value);
650
545
  }
651
- const updateSwipeProgress = useButton.useStableCallback((progress, details) => {
652
- const nextProgress = Number.isFinite(progress) ? clamp.clamp(progress, 0, 1) : 0;
653
- const progressChanged = nextProgress !== swipeProgressRef.current;
654
- let detailsChanged = false;
655
- if (details) {
656
- const lastDetails = lastProgressDetailsRef.current;
657
- detailsChanged = !lastDetails || lastDetails.deltaX !== details.deltaX || lastDetails.deltaY !== details.deltaY || lastDetails.direction !== details.direction;
658
- }
659
- if (!progressChanged && !detailsChanged) {
660
- return;
661
- }
662
- swipeProgressRef.current = nextProgress;
663
- if (details) {
664
- lastProgressDetailsRef.current = details;
665
- } else if (progressChanged) {
666
- lastProgressDetailsRef.current = null;
667
- }
668
- onProgress?.(nextProgress, details);
669
- });
670
- const syncDragStyles = useButton.useStableCallback(swiping => {
671
- const element = elementRef.current;
672
- if (!trackDrag || !element) {
673
- if (!swiping) {
674
- dragStyleSnapshotRef.current = null;
675
- }
676
- return;
677
- }
678
- const style = element.style;
679
- const dragStyleSnapshot = dragStyleSnapshotRef.current;
680
- if (swiping) {
681
- if (!dragStyleSnapshot) {
682
- dragStyleSnapshotRef.current = [style.transition, style.transform];
683
- }
684
- style.transition = 'none';
685
- } else if (dragStyleSnapshot) {
686
- [style.transition, style.transform] = dragStyleSnapshot;
687
- dragStyleSnapshotRef.current = null;
688
- }
689
- const dragOffset = dragOffsetRef.current;
690
- const initialTransform = initialTransformRef.current;
691
- const deltaX = dragOffset.x - initialTransform.x;
692
- const deltaY = dragOffset.y - initialTransform.y;
693
- if (swiping) {
694
- style.transform = getDragTransform(dragOffset, initialTransform.scale);
695
- }
696
- style.setProperty(movementCssVars.x, `${deltaX}px`);
697
- style.setProperty(movementCssVars.y, `${deltaY}px`);
698
- });
699
- function recordDragSample(offset, timeStamp) {
700
- if (timeStamp === null) {
701
- return;
546
+ function resolvePopupSize() {
547
+ const popupElement = store.context.popupRef.current;
548
+ if (!popupElement) {
549
+ return null;
702
550
  }
703
- const lastSample = lastDragSampleRef.current;
704
- if (lastSample && timeStamp > lastSample.time) {
705
- const durationMs = Math.max(timeStamp - lastSample.time, MIN_RELEASE_VELOCITY_DURATION_MS);
706
- lastDragVelocityRef.current = {
707
- x: (offset.x - lastSample.x) / durationMs,
708
- y: (offset.y - lastSample.y) / durationMs
709
- };
551
+ const isHorizontal = dismissDirection === 'left' || dismissDirection === 'right';
552
+ const size = isHorizontal ? popupElement.offsetWidth : popupElement.offsetHeight;
553
+ if (size <= 0) {
554
+ return null;
710
555
  }
711
- lastDragSampleRef.current = {
712
- x: offset.x,
713
- y: offset.y,
714
- time: timeStamp
715
- };
556
+ return size;
716
557
  }
717
- const reset = React__namespace.useCallback(() => {
718
- setCurrentSwipeDirection(undefined);
719
- setSwiping(false);
720
- setDragDismissed(false);
721
- updateSwipeProgress(0);
722
- swipeThresholdRef.current = swipeThresholdDefault;
723
- dragStartPosRef.current = {
724
- x: 0,
725
- y: 0
726
- };
727
- dragOffsetRef.current = {
728
- x: 0,
729
- y: 0
730
- };
731
- initialTransformRef.current = {
732
- x: 0,
733
- y: 0,
734
- scale: 1
735
- };
736
- intendedSwipeDirectionRef.current = undefined;
737
- maxSwipeDisplacementRef.current = 0;
738
- cancelledSwipeRef.current = false;
739
- swipeCancelBaselineRef.current = {
740
- x: 0,
741
- y: 0
742
- };
743
- lockedDirectionRef.current = null;
744
- isFirstPointerMoveRef.current = false;
745
- lastMovePosRef.current = null;
746
- pendingSwipeRef.current = false;
747
- pendingSwipeStartPosRef.current = null;
748
- swipeFromScrollableRef.current = false;
749
- sawPrimaryButtonsOnMoveRef.current = false;
750
- elementSizeRef.current = {
751
- width: 0,
752
- height: 0
753
- };
754
- swipeStartTimeRef.current = null;
755
- lastDragSampleRef.current = null;
756
- lastDragVelocityRef.current = {
757
- x: 0,
758
- y: 0
759
- };
760
- lastProgressDetailsRef.current = null;
761
- syncDragStyles(false);
762
- }, [setSwiping, swipeThresholdDefault, syncDragStyles, updateSwipeProgress]);
763
- React__namespace.useEffect(() => {
764
- if (typeof swipeThresholdProp !== 'function') {
765
- swipeThresholdRef.current = swipeThresholdDefault;
558
+ function resolveClosedOffset() {
559
+ const offset = resolvePopupSize();
560
+ if (offset == null) {
561
+ return null;
766
562
  }
767
- }, [swipeThresholdDefault, swipeThresholdProp]);
768
- function getPrimaryPointerPosition(event) {
769
- if ('touches' in event) {
770
- const touch = event.touches[0];
771
- return touch ? {
772
- x: touch.clientX,
773
- y: touch.clientY
774
- } : null;
563
+ const popupElement = store.context.popupRef.current;
564
+ if (!popupElement) {
565
+ return offset;
775
566
  }
776
- return {
777
- x: event.clientX,
778
- y: event.clientY
779
- };
780
- }
781
- function isTouchLikeEvent(event) {
782
- if ('touches' in event) {
783
- return true;
567
+ const isHorizontal = dismissDirection === 'left' || dismissDirection === 'right';
568
+ const transform = ScrollAreaViewport.getElementTransform(popupElement);
569
+ const transformOffset = isHorizontal ? transform.x : transform.y;
570
+ if (Number.isFinite(transformOffset) && Math.abs(transformOffset) > 0.5) {
571
+ return Math.min(offset, Math.abs(transformOffset));
784
572
  }
785
- return event.pointerType === 'touch';
786
- }
787
- function getTargetAtPoint(position, nativeEvent) {
788
- const doc = useButton.ownerDocument(elementRef.current);
789
- const elementAtPoint = getElementAtPoint(doc, position.x, position.y);
790
- const target = elementAtPoint ?? useButton.getTarget(nativeEvent);
791
- return target;
573
+ return offset;
792
574
  }
793
- function findGestureScrollableTouchTarget(target, root) {
794
- if (hasHorizontal && !hasVertical) {
795
- return findScrollableTouchTarget(target, root, 'horizontal');
796
- }
797
- if (hasVertical && !hasHorizontal) {
798
- return findScrollableTouchTarget(target, root, 'vertical');
575
+ function resolveSwipeOpenThreshold() {
576
+ const popupSize = resolvePopupSize();
577
+ if (popupSize == null) {
578
+ return FALLBACK_SWIPE_OPEN_THRESHOLD;
799
579
  }
800
- return findScrollableTouchTarget(target, root, 'vertical') ?? findScrollableTouchTarget(target, root, 'horizontal');
580
+ return popupSize * DEFAULT_SWIPE_OPEN_RATIO;
801
581
  }
802
- function startSwipeAtPosition(event, position, startOptions) {
803
- swipeFromScrollableRef.current = false;
804
- const touchLike = isTouchLikeEvent(event);
805
- const target = getTargetAtPoint(position, event.nativeEvent);
806
- const doc = useButton.ownerDocument(elementRef.current);
807
- const body = doc.body;
808
- const scrollableTarget = touchLike && body ? findGestureScrollableTouchTarget(target, body) : null;
809
- const ignoreScrollableTarget = startOptions?.ignoreScrollableTarget ?? false;
810
- if (scrollableTarget && !ignoreScrollableTarget) {
811
- return false;
582
+ function applySwipeMovement() {
583
+ if (!swipeActive) {
584
+ return;
812
585
  }
813
- swipeFromScrollableRef.current = Boolean(scrollableTarget && ignoreScrollableTarget);
814
- const isInteractiveElement = target ? target.closest(ignoreSelector) : false;
815
- if (isInteractiveElement && (!touchLike || ignoreSelectorWhenTouch)) {
816
- return false;
586
+ const popupElement = store.context.popupRef.current;
587
+ if (!popupElement) {
588
+ return;
817
589
  }
818
- const element = elementRef.current;
819
- if (ignoreScrollableAncestors && element && target && scrollAxes.length > 0) {
820
- const ignoreAncestors = startOptions?.ignoreScrollableAncestors ?? false;
821
- if (!ignoreAncestors && hasScrollableAncestor(target, element, scrollAxes)) {
822
- return false;
823
- }
590
+ if (!store.select('open') || !store.select('mounted')) {
591
+ return;
824
592
  }
825
- cancelledSwipeRef.current = false;
826
- intendedSwipeDirectionRef.current = undefined;
827
- maxSwipeDisplacementRef.current = 0;
828
- dragStartPosRef.current = position;
829
- swipeStartTimeRef.current = getValidTimeStamp(event.timeStamp);
830
- swipeCancelBaselineRef.current = position;
831
- lastMovePosRef.current = position;
832
- if (element) {
833
- elementSizeRef.current = {
834
- width: element.offsetWidth,
835
- height: element.offsetHeight
836
- };
837
- resolveSwipeThreshold(primaryDirection);
838
- const transform = getElementTransform(element);
839
- initialTransformRef.current = transform;
840
- dragOffsetRef.current = {
841
- x: transform.x,
842
- y: transform.y
843
- };
844
- recordDragSample({
845
- x: transform.x,
846
- y: transform.y
847
- }, swipeStartTimeRef.current);
848
- if (!('touches' in event)) {
849
- safelyChangePointerCapture(element, event.pointerId, 'setPointerCapture');
850
- }
593
+ if (closedOffsetRef.current == null) {
594
+ closedOffsetRef.current = resolveClosedOffset();
851
595
  }
852
- onSwipeStart?.(event.nativeEvent);
853
- setSwiping(true);
854
- lockedDirectionRef.current = null;
855
- isFirstPointerMoveRef.current = true;
856
- updateSwipeProgress(0);
857
- syncDragStyles(true);
858
- return true;
859
- }
860
- function resetPendingSwipeState() {
861
- clearPendingSwipeStartState();
862
- swipeFromScrollableRef.current = false;
863
- lastMovePosRef.current = null;
864
- }
865
- function clearPendingSwipeStartState() {
866
- pendingSwipeRef.current = false;
867
- pendingSwipeStartPosRef.current = null;
868
- }
869
- function cancelSwipeInteraction(event) {
870
- resetPendingSwipeState();
871
- if (!isSwipingRef.current) {
872
- return;
873
- }
874
- setSwiping(false);
875
- lockedDirectionRef.current = null;
876
- const resolvedInitialTransform = initialTransformRef.current;
877
- dragOffsetRef.current = {
878
- x: resolvedInitialTransform.x,
879
- y: resolvedInitialTransform.y
880
- };
881
- setCurrentSwipeDirection(undefined);
882
- sawPrimaryButtonsOnMoveRef.current = false;
883
- syncDragStyles(false);
884
- const element = elementRef.current;
885
- if (element) {
886
- safelyChangePointerCapture(element, event.pointerId, 'releasePointerCapture');
887
- }
888
- updateSwipeProgress(0, {
889
- deltaX: 0,
890
- deltaY: 0,
891
- direction: undefined
892
- });
893
- onCancel?.(event.nativeEvent);
894
- }
895
- function applyDirectionalDamping(deltaX, deltaY) {
896
- const exponent = value => value >= 0 ? value ** 0.5 : -(Math.abs(value) ** 0.5);
897
- const dampAxis = (delta, allowNegative, allowPositive) => {
898
- if (!allowNegative && delta < 0) {
899
- return exponent(delta);
900
- }
901
- if (!allowPositive && delta > 0) {
902
- return exponent(delta);
903
- }
904
- return delta;
905
- };
906
- const newDeltaX = hasHorizontal ? dampAxis(deltaX, allowLeft, allowRight) : exponent(deltaX);
907
- const newDeltaY = hasVertical ? dampAxis(deltaY, allowUp, allowDown) : exponent(deltaY);
908
- return {
909
- x: newDeltaX,
910
- y: newDeltaY
911
- };
912
- }
913
- function canSwipeFromScrollEdgeOnPendingMove(scrollTarget, deltaX, deltaY) {
914
- const absDeltaX = Math.abs(deltaX);
915
- const absDeltaY = Math.abs(deltaY);
916
- const useVerticalAxis = hasVertical && deltaY !== 0 && (!hasHorizontal || absDeltaY >= absDeltaX);
917
- if (useVerticalAxis) {
918
- const maxScrollTop = Math.max(0, scrollTarget.scrollHeight - scrollTarget.clientHeight);
919
- const atTop = scrollTarget.scrollTop <= 0;
920
- const atBottom = scrollTarget.scrollTop >= maxScrollTop;
921
- const movingDown = deltaY > 0;
922
- const movingUp = deltaY < 0;
923
- const canSwipeDown = movingDown && atTop && allowDown;
924
- const canSwipeUp = movingUp && atBottom && allowUp;
925
- return canSwipeDown || canSwipeUp;
926
- }
927
- const useHorizontalAxis = hasHorizontal && deltaX !== 0 && (!hasVertical || absDeltaX > absDeltaY);
928
- if (useHorizontalAxis) {
929
- const maxScrollLeft = Math.max(0, scrollTarget.scrollWidth - scrollTarget.clientWidth);
930
- const atLeft = scrollTarget.scrollLeft <= 0;
931
- const atRight = scrollTarget.scrollLeft >= maxScrollLeft;
932
- const movingRight = deltaX > 0;
933
- const movingLeft = deltaX < 0;
934
- const canSwipeRight = movingRight && atLeft && allowRight;
935
- const canSwipeLeft = movingLeft && atRight && allowLeft;
936
- return canSwipeRight || canSwipeLeft;
937
- }
938
- return null;
939
- }
940
- const handleStart = useButton.useStableCallback(event => {
941
- if (!enabled) {
942
- return;
943
- }
944
- if (event.defaultPrevented || event.nativeEvent.defaultPrevented) {
945
- return;
946
- }
947
- if (!('touches' in event) && event.button !== 0) {
948
- return;
949
- }
950
- const startPos = getPrimaryPointerPosition(event);
951
- if (!startPos) {
952
- return;
953
- }
954
- pendingSwipeRef.current = true;
955
- pendingSwipeStartPosRef.current = startPos;
956
- swipeFromScrollableRef.current = false;
957
- sawPrimaryButtonsOnMoveRef.current = false;
958
- const allowedToStart = canStart ? canStart(startPos, {
959
- nativeEvent: event.nativeEvent,
960
- direction: primaryDirection
961
- }) : true;
962
- if (!allowedToStart) {
963
- return;
964
- }
965
- if (startSwipeAtPosition(event, startPos)) {
966
- clearPendingSwipeStartState();
967
- }
968
- });
969
- function handleMoveCore(event, position, movement) {
970
- if (!enabled || !isSwipingRef.current) {
971
- return;
972
- }
973
- const target = useButton.getTarget(event.nativeEvent);
974
- if (isTouchLikeEvent(event) && !swipeFromScrollableRef.current) {
975
- const boundaryElement = event.currentTarget;
976
- if (findGestureScrollableTouchTarget(target, boundaryElement)) {
977
- return;
978
- }
979
- }
980
- if (!('touches' in event)) {
981
- // Prevent text selection on Safari
982
- event.preventDefault();
983
- }
984
- if (isFirstPointerMoveRef.current) {
985
- // Adjust the starting position to the current position on the first move
986
- // to account for the delay between pointerdown and the first pointermove on iOS.
987
- dragStartPosRef.current = position;
988
- const moveTime = getValidTimeStamp(event.timeStamp);
989
- if (moveTime !== null) {
990
- swipeStartTimeRef.current = moveTime;
991
- }
992
- isFirstPointerMoveRef.current = false;
993
- }
994
- const clientX = position.x;
995
- const clientY = position.y;
996
- const movementX = movement.x;
997
- const movementY = movement.y;
998
- if (movementY < 0 && clientY > swipeCancelBaselineRef.current.y || movementY > 0 && clientY < swipeCancelBaselineRef.current.y) {
999
- swipeCancelBaselineRef.current = {
1000
- x: swipeCancelBaselineRef.current.x,
1001
- y: clientY
1002
- };
1003
- }
1004
- if (movementX < 0 && clientX > swipeCancelBaselineRef.current.x || movementX > 0 && clientX < swipeCancelBaselineRef.current.x) {
1005
- swipeCancelBaselineRef.current = {
1006
- x: clientX,
1007
- y: swipeCancelBaselineRef.current.y
1008
- };
1009
- }
1010
- const deltaX = clientX - dragStartPosRef.current.x;
1011
- const deltaY = clientY - dragStartPosRef.current.y;
1012
- const cancelDeltaY = clientY - swipeCancelBaselineRef.current.y;
1013
- const cancelDeltaX = clientX - swipeCancelBaselineRef.current.x;
1014
- let lockedDirection = lockedDirectionRef.current;
1015
- if (lockedDirection === null && hasHorizontal && hasVertical) {
1016
- const movementDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
1017
- if (movementDistance >= MIN_DRAG_THRESHOLD) {
1018
- lockedDirection = Math.abs(deltaX) > Math.abs(deltaY) ? 'horizontal' : 'vertical';
1019
- lockedDirectionRef.current = lockedDirection;
1020
- }
1021
- }
1022
- let candidate;
1023
- if (!intendedSwipeDirectionRef.current) {
1024
- if (lockedDirection === 'vertical') {
1025
- if (deltaY > 0) {
1026
- candidate = 'down';
1027
- } else if (deltaY < 0) {
1028
- candidate = 'up';
1029
- }
1030
- } else if (lockedDirection === 'horizontal') {
1031
- if (deltaX > 0) {
1032
- candidate = 'right';
1033
- } else if (deltaX < 0) {
1034
- candidate = 'left';
1035
- }
1036
- } else if (Math.abs(deltaX) >= Math.abs(deltaY)) {
1037
- candidate = deltaX > 0 ? 'right' : 'left';
1038
- } else {
1039
- candidate = deltaY > 0 ? 'down' : 'up';
1040
- }
1041
- if (candidate) {
1042
- const isAllowed = candidate === 'left' && allowLeft || candidate === 'right' && allowRight || candidate === 'up' && allowUp || candidate === 'down' && allowDown;
1043
- if (isAllowed) {
1044
- intendedSwipeDirectionRef.current = candidate;
1045
- maxSwipeDisplacementRef.current = getDisplacement(candidate, deltaX, deltaY);
1046
- setCurrentSwipeDirection(candidate);
1047
- resolveSwipeThreshold(candidate);
1048
- }
1049
- }
1050
- } else {
1051
- const direction = intendedSwipeDirectionRef.current;
1052
- const currentDisplacement = getDisplacement(direction, cancelDeltaX, cancelDeltaY);
1053
- if (currentDisplacement > swipeThresholdRef.current) {
1054
- cancelledSwipeRef.current = false;
1055
- setCurrentSwipeDirection(direction);
1056
- } else if (!(allowLeft && allowRight) && !(allowUp && allowDown) && maxSwipeDisplacementRef.current - currentDisplacement >= REVERSE_CANCEL_THRESHOLD) {
1057
- // Mark that a change-of-mind has occurred
1058
- cancelledSwipeRef.current = true;
1059
- }
1060
- }
1061
- const dampedDelta = applyDirectionalDamping(deltaX, deltaY);
1062
- let newOffsetX = initialTransformRef.current.x;
1063
- let newOffsetY = initialTransformRef.current.y;
1064
- if (lockedDirection === 'horizontal') {
1065
- if (hasHorizontal) {
1066
- newOffsetX += dampedDelta.x;
1067
- }
1068
- } else if (lockedDirection === 'vertical') {
1069
- if (hasVertical) {
1070
- newOffsetY += dampedDelta.y;
1071
- }
1072
- } else {
1073
- if (hasHorizontal) {
1074
- newOffsetX += dampedDelta.x;
1075
- }
1076
- if (hasVertical) {
1077
- newOffsetY += dampedDelta.y;
1078
- }
1079
- }
1080
- dragOffsetRef.current = {
1081
- x: newOffsetX,
1082
- y: newOffsetY
1083
- };
1084
- syncDragStyles(true);
1085
- recordDragSample({
1086
- x: newOffsetX,
1087
- y: newOffsetY
1088
- }, getValidTimeStamp(event.timeStamp));
1089
- const dragDeltaX = newOffsetX - initialTransformRef.current.x;
1090
- const dragDeltaY = newOffsetY - initialTransformRef.current.y;
1091
- const swipeDirectionDetails = intendedSwipeDirectionRef.current;
1092
- const progressDirection = primaryDirection ?? intendedSwipeDirectionRef.current;
1093
- if (!progressDirection) {
1094
- updateSwipeProgress(0, {
1095
- deltaX: dragDeltaX,
1096
- deltaY: dragDeltaY,
1097
- direction: swipeDirectionDetails
1098
- });
1099
- return;
1100
- }
1101
- const size = progressDirection === 'left' || progressDirection === 'right' ? elementSizeRef.current.width : elementSizeRef.current.height;
1102
- const scale = initialTransformRef.current.scale || 1;
1103
- if (size <= 0 || scale <= 0) {
1104
- updateSwipeProgress(0, {
1105
- deltaX: dragDeltaX,
1106
- deltaY: dragDeltaY,
1107
- direction: swipeDirectionDetails
1108
- });
1109
- return;
1110
- }
1111
- const progressDisplacement = getDisplacement(progressDirection, newOffsetX - initialTransformRef.current.x, newOffsetY - initialTransformRef.current.y);
1112
- if (progressDisplacement <= 0) {
1113
- updateSwipeProgress(0, {
1114
- deltaX: dragDeltaX,
1115
- deltaY: dragDeltaY,
1116
- direction: swipeDirectionDetails
1117
- });
1118
- return;
1119
- }
1120
- updateSwipeProgress(progressDisplacement / (size * scale), {
1121
- deltaX: dragDeltaX,
1122
- deltaY: dragDeltaY,
1123
- direction: swipeDirectionDetails
1124
- });
1125
- }
1126
- const handleEnd = useButton.useStableCallback(event => {
1127
- if (!enabled) {
1128
- return;
1129
- }
1130
- const resolvedDragOffset = dragOffsetRef.current;
1131
- const resolvedInitialTransform = initialTransformRef.current;
1132
- const releaseDeltaX = resolvedDragOffset.x - resolvedInitialTransform.x;
1133
- const releaseDeltaY = resolvedDragOffset.y - resolvedInitialTransform.y;
1134
- const progressDetails = {
1135
- deltaX: releaseDeltaX,
1136
- deltaY: releaseDeltaY,
1137
- direction: intendedSwipeDirectionRef.current
1138
- };
1139
- if (!isSwipingRef.current) {
1140
- resetPendingSwipeState();
1141
- updateSwipeProgress(0, progressDetails);
1142
- return;
1143
- }
1144
- setSwiping(false);
1145
- lockedDirectionRef.current = null;
1146
- resetPendingSwipeState();
1147
- sawPrimaryButtonsOnMoveRef.current = false;
1148
- const element = elementRef.current;
1149
- if (element) {
1150
- if (!('touches' in event)) {
1151
- safelyChangePointerCapture(element, event.pointerId, 'releasePointerCapture');
1152
- }
1153
- }
1154
- const deltaX = releaseDeltaX;
1155
- const deltaY = releaseDeltaY;
1156
- const startTime = swipeStartTimeRef.current;
1157
- const endTime = getValidTimeStamp(event.timeStamp);
1158
- const durationMs = startTime !== null && endTime !== null && endTime > startTime ? endTime - startTime : 0;
1159
- const velocityDurationMs = durationMs > 0 ? Math.max(durationMs, MIN_VELOCITY_DURATION_MS) : 0;
1160
- const velocityX = velocityDurationMs > 0 ? deltaX / velocityDurationMs : 0;
1161
- const velocityY = velocityDurationMs > 0 ? deltaY / velocityDurationMs : 0;
1162
- let releaseVelocityX = lastDragVelocityRef.current.x;
1163
- let releaseVelocityY = lastDragVelocityRef.current.y;
1164
- const lastSample = lastDragSampleRef.current;
1165
- if (lastSample && endTime !== null && endTime >= lastSample.time) {
1166
- const ageMs = endTime - lastSample.time;
1167
- if (ageMs <= MAX_RELEASE_VELOCITY_AGE_MS) {
1168
- const sampleDurationMs = Math.max(ageMs, MIN_RELEASE_VELOCITY_DURATION_MS);
1169
- const deltaFromLastSampleX = resolvedDragOffset.x - lastSample.x;
1170
- const deltaFromLastSampleY = resolvedDragOffset.y - lastSample.y;
1171
- const sampleVelocityX = deltaFromLastSampleX / sampleDurationMs;
1172
- const sampleVelocityY = deltaFromLastSampleY / sampleDurationMs;
1173
- if (sampleVelocityX !== 0) {
1174
- releaseVelocityX = sampleVelocityX;
1175
- }
1176
- if (sampleVelocityY !== 0) {
1177
- releaseVelocityY = sampleVelocityY;
1178
- }
1179
- } else {
1180
- releaseVelocityX = 0;
1181
- releaseVelocityY = 0;
1182
- }
1183
- }
1184
- const releaseDecision = onRelease?.({
1185
- event: event.nativeEvent,
1186
- direction: intendedSwipeDirectionRef.current,
1187
- deltaX,
1188
- deltaY,
1189
- velocityX,
1190
- velocityY,
1191
- releaseVelocityX,
1192
- releaseVelocityY
1193
- });
1194
- const hasReleaseDecision = typeof releaseDecision === 'boolean';
1195
- if (cancelledSwipeRef.current && !hasReleaseDecision) {
1196
- dragOffsetRef.current = {
1197
- x: resolvedInitialTransform.x,
1198
- y: resolvedInitialTransform.y
1199
- };
1200
- setCurrentSwipeDirection(undefined);
1201
- syncDragStyles(false);
1202
- updateSwipeProgress(0, progressDetails);
1203
- return;
1204
- }
1205
- let shouldClose = false;
1206
- let dismissDirection;
1207
- if (hasReleaseDecision) {
1208
- shouldClose = releaseDecision;
1209
- dismissDirection = intendedSwipeDirectionRef.current ?? primaryDirection;
1210
- } else {
1211
- for (const direction of directions) {
1212
- switch (direction) {
1213
- case 'right':
1214
- if (deltaX > swipeThresholdRef.current) {
1215
- shouldClose = true;
1216
- dismissDirection = 'right';
1217
- }
1218
- break;
1219
- case 'left':
1220
- if (deltaX < -swipeThresholdRef.current) {
1221
- shouldClose = true;
1222
- dismissDirection = 'left';
1223
- }
1224
- break;
1225
- case 'down':
1226
- if (deltaY > swipeThresholdRef.current) {
1227
- shouldClose = true;
1228
- dismissDirection = 'down';
1229
- }
1230
- break;
1231
- case 'up':
1232
- if (deltaY < -swipeThresholdRef.current) {
1233
- shouldClose = true;
1234
- dismissDirection = 'up';
1235
- }
1236
- break;
1237
- }
1238
- if (shouldClose) {
1239
- break;
1240
- }
1241
- }
1242
- }
1243
- if (shouldClose && dismissDirection) {
1244
- setCurrentSwipeDirection(dismissDirection);
1245
- setDragDismissed(true);
1246
- syncDragStyles(false);
1247
- onDismiss?.(event.nativeEvent, {
1248
- direction: dismissDirection
1249
- });
1250
- } else {
1251
- dragOffsetRef.current = {
1252
- x: resolvedInitialTransform.x,
1253
- y: resolvedInitialTransform.y
1254
- };
1255
- setCurrentSwipeDirection(undefined);
1256
- syncDragStyles(false);
1257
- updateSwipeProgress(0, progressDetails);
1258
- }
1259
- });
1260
- const handleMove = useButton.useStableCallback(event => {
1261
- const currentPos = getPrimaryPointerPosition(event);
1262
- if (!currentPos) {
1263
- return;
1264
- }
1265
- let endAfterMove = false;
1266
- if (!('touches' in event)) {
1267
- const hasPrimaryButton = hasPrimaryMouseButton(event.buttons);
1268
- if (hasPrimaryButton) {
1269
- sawPrimaryButtonsOnMoveRef.current = true;
1270
- }
1271
-
1272
- // Cancel the swipe if a non-primary button takes over the interaction.
1273
- // This handles cases where a right-click interrupts dragging.
1274
- if (event.buttons !== 0 && !hasPrimaryButton) {
1275
- cancelSwipeInteraction(event);
1276
- return;
1277
- }
1278
-
1279
- // A `buttons: 0` pointermove means the primary button was already released, so the gesture is
1280
- // over even if no pointerup reached us. On fast trackpad flicks this trailing move is
1281
- // dispatched just before pointerup; treat it as the release (mirroring touchend) instead of
1282
- // cancelling and snapping the element back.
1283
- if (event.buttons === 0 && sawPrimaryButtonsOnMoveRef.current) {
1284
- if (!isSwipingRef.current) {
1285
- // The gesture never activated — discard it.
1286
- handleEnd(event);
1287
- return;
1288
- }
1289
- // This release move can itself carry the threshold-crossing displacement (and the peak
1290
- // release velocity), so let it flow through `handleMoveCore` below to update the drag
1291
- // offset / velocity sample, then commit the release afterwards.
1292
- endAfterMove = true;
1293
- }
1294
- }
1295
- if (!isSwiping && pendingSwipeRef.current) {
1296
- if (!isTouchLikeEvent(event) && (event.defaultPrevented || event.nativeEvent.defaultPrevented)) {
1297
- resetPendingSwipeState();
1298
- return;
1299
- }
1300
- const allowedToStart = canStart ? canStart(currentPos, {
1301
- nativeEvent: event.nativeEvent,
1302
- direction: primaryDirection
1303
- }) : true;
1304
- if (allowedToStart) {
1305
- const pendingStartPos = pendingSwipeStartPosRef.current;
1306
- let ignoreScrollableOnStart = false;
1307
- if (isTouchLikeEvent(event)) {
1308
- const element = elementRef.current;
1309
- if (pendingStartPos && element) {
1310
- const target = getTargetAtPoint(currentPos, event.nativeEvent);
1311
- const doc = useButton.ownerDocument(element);
1312
- const body = doc.body;
1313
- const scrollTarget = body ? findGestureScrollableTouchTarget(target, body) : null;
1314
- if (scrollTarget && (useButton.contains(element, scrollTarget) || useButton.contains(scrollTarget, element))) {
1315
- const deltaX = currentPos.x - pendingStartPos.x;
1316
- const deltaY = currentPos.y - pendingStartPos.y;
1317
- const canSwipeFromEdge = canSwipeFromScrollEdgeOnPendingMove(scrollTarget, deltaX, deltaY);
1318
- if (canSwipeFromEdge === false) {
1319
- return;
1320
- }
1321
- if (canSwipeFromEdge === true) {
1322
- ignoreScrollableOnStart = true;
1323
- }
1324
- }
1325
- }
1326
- }
1327
- const started = startSwipeAtPosition(event, currentPos, {
1328
- ignoreScrollableTarget: ignoreScrollableOnStart,
1329
- ignoreScrollableAncestors: ignoreScrollableOnStart
1330
- });
1331
- if (started) {
1332
- if (pendingStartPos && ignoreScrollableOnStart) {
1333
- // Preserve displacement between touchstart and the move that activates swipe from
1334
- // a scroll-edge so quick flicks can dismiss.
1335
- clearPendingSwipeStartState();
1336
- dragStartPosRef.current = pendingStartPos;
1337
- swipeCancelBaselineRef.current = pendingStartPos;
1338
- lastMovePosRef.current = pendingStartPos;
1339
- isFirstPointerMoveRef.current = false;
1340
- } else {
1341
- // Start from the current in-bounds position without dropping follow-up move
1342
- // displacement; this avoids jumps when entering from outside the element while
1343
- // keeping swipe tracking responsive on the next move.
1344
- clearPendingSwipeStartState();
1345
- swipeFromScrollableRef.current = false;
1346
- }
1347
- }
1348
- }
1349
- }
1350
- const previousPos = lastMovePosRef.current;
1351
- const movement = previousPos === null ? {
1352
- x: 0,
1353
- y: 0
1354
- } : {
1355
- x: currentPos.x - previousPos.x,
1356
- y: currentPos.y - previousPos.y
1357
- };
1358
- lastMovePosRef.current = currentPos;
1359
- handleMoveCore(event, currentPos, movement);
1360
-
1361
- // `endAfterMove` is only set in the non-touch branch above; the `'touches'` guard re-narrows the
1362
- // event type for `handleEnd` after the shared move handling has run.
1363
- if (endAfterMove && !('touches' in event)) {
1364
- handleEnd(event);
1365
- }
1366
- });
1367
-
1368
- // Feeds a native touchmove into the swipe pipeline. Used by consumers that claim the gesture
1369
- // in a capture-phase listener and stop it from reaching React's delegated touch handlers.
1370
- const moveNative = useButton.useStableCallback((nativeEvent, currentTarget) => {
1371
- handleMove({
1372
- touches: nativeEvent.touches,
1373
- currentTarget,
1374
- nativeEvent,
1375
- defaultPrevented: nativeEvent.defaultPrevented,
1376
- timeStamp: nativeEvent.timeStamp
1377
- });
1378
- });
1379
- const getDragStyles = React__namespace.useCallback(() => {
1380
- const dragOffset = dragOffsetRef.current;
1381
- const initialTransform = initialTransformRef.current;
1382
- const deltaX = dragOffset.x - initialTransform.x;
1383
- const deltaY = dragOffset.y - initialTransform.y;
1384
- if (!isSwiping && deltaX === 0 && deltaY === 0 && !dragDismissed) {
1385
- return {
1386
- [movementCssVars.x]: '0px',
1387
- [movementCssVars.y]: '0px'
1388
- };
1389
- }
1390
- return {
1391
- transition: isSwiping ? 'none' : undefined,
1392
- // While swiping, freeze the element at its current visual transform so it doesn't snap to the
1393
- // end position.
1394
- transform: isSwiping ? getDragTransform(dragOffset, initialTransform.scale) : undefined,
1395
- [movementCssVars.x]: `${deltaX}px`,
1396
- [movementCssVars.y]: `${deltaY}px`
1397
- };
1398
- }, [dragDismissed, isSwiping, movementCssVars]);
1399
- const getPointerProps = React__namespace.useCallback(() => {
1400
- if (!enabled) {
1401
- return {};
1402
- }
1403
- return {
1404
- onPointerDown: handleStart,
1405
- onPointerMove: handleMove,
1406
- onPointerUp: handleEnd,
1407
- onPointerCancel: handleEnd
1408
- };
1409
- }, [enabled, handleEnd, handleMove, handleStart]);
1410
- const getTouchProps = React__namespace.useCallback(() => {
1411
- if (!enabled) {
1412
- return {};
1413
- }
1414
- return {
1415
- onTouchStart: handleStart,
1416
- onTouchMove: handleMove,
1417
- onTouchEnd: handleEnd,
1418
- onTouchCancel: handleEnd
1419
- };
1420
- }, [enabled, handleEnd, handleMove, handleStart]);
1421
- return {
1422
- swiping: isSwiping,
1423
- swipeDirection: currentSwipeDirection,
1424
- dragDismissed,
1425
- getPointerProps,
1426
- getTouchProps,
1427
- moveNative,
1428
- getDragStyles,
1429
- reset
1430
- };
1431
- }
1432
-
1433
- let DrawerSwipeAreaDataAttributes = function (DrawerSwipeAreaDataAttributes) {
1434
- /**
1435
- * Present when the drawer is open.
1436
- */
1437
- DrawerSwipeAreaDataAttributes[DrawerSwipeAreaDataAttributes["open"] = useScrollLock.CommonPopupDataAttributes.open] = "open";
1438
- /**
1439
- * Present when the drawer is closed.
1440
- */
1441
- DrawerSwipeAreaDataAttributes[DrawerSwipeAreaDataAttributes["closed"] = useScrollLock.CommonPopupDataAttributes.closed] = "closed";
1442
- /**
1443
- * Present when the swipe area is disabled.
1444
- */
1445
- DrawerSwipeAreaDataAttributes["disabled"] = "data-disabled";
1446
- /**
1447
- * Indicates the swipe direction.
1448
- * @type {'up' | 'down' | 'left' | 'right'}
1449
- */
1450
- DrawerSwipeAreaDataAttributes["swipeDirection"] = "data-swipe-direction";
1451
- /**
1452
- * Present when the drawer is being swiped.
1453
- */
1454
- DrawerSwipeAreaDataAttributes["swiping"] = "data-swiping";
1455
- return DrawerSwipeAreaDataAttributes;
1456
- }({});
1457
-
1458
- const DEFAULT_SWIPE_OPEN_RATIO = 0.5;
1459
- const MIN_SWIPE_START_DISTANCE = 1;
1460
- const VELOCITY_THRESHOLD = 0.1;
1461
- const FALLBACK_SWIPE_OPEN_THRESHOLD = 40;
1462
- const SWIPE_AREA_OPEN_HOOK = {
1463
- [DrawerSwipeAreaDataAttributes.open]: ''
1464
- };
1465
- const SWIPE_AREA_CLOSED_HOOK = {
1466
- [DrawerSwipeAreaDataAttributes.closed]: ''
1467
- };
1468
- const SWIPE_AREA_SWIPING_HOOK = {
1469
- [DrawerSwipeAreaDataAttributes.swiping]: ''
1470
- };
1471
- const SWIPE_AREA_DISABLED_HOOK = {
1472
- [DrawerSwipeAreaDataAttributes.disabled]: ''
1473
- };
1474
- const stateAttributesMapping$3 = {
1475
- open(value) {
1476
- return value ? SWIPE_AREA_OPEN_HOOK : SWIPE_AREA_CLOSED_HOOK;
1477
- },
1478
- swiping(value) {
1479
- return value ? SWIPE_AREA_SWIPING_HOOK : null;
1480
- },
1481
- swipeDirection(value) {
1482
- return value ? {
1483
- [DrawerSwipeAreaDataAttributes.swipeDirection]: value
1484
- } : null;
1485
- },
1486
- disabled(value) {
1487
- return value ? SWIPE_AREA_DISABLED_HOOK : null;
1488
- }
1489
- };
1490
- const oppositeSwipeDirection = {
1491
- up: 'down',
1492
- down: 'up',
1493
- left: 'right',
1494
- right: 'left'
1495
- };
1496
- function resolveTouchAction(direction) {
1497
- return direction === 'left' || direction === 'right' ? 'pan-y' : 'pan-x';
1498
- }
1499
-
1500
- /**
1501
- * An invisible area that listens for swipe gestures to open the drawer.
1502
- * Renders a `<div>` element.
1503
- *
1504
- * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)
1505
- */
1506
- const DrawerSwipeArea = /*#__PURE__*/React__namespace.forwardRef(function DrawerSwipeArea(componentProps, forwardedRef) {
1507
- const {
1508
- render,
1509
- className,
1510
- style,
1511
- disabled = false,
1512
- swipeDirection: swipeDirectionProp,
1513
- ...elementProps
1514
- } = componentProps;
1515
- const {
1516
- store
1517
- } = ScrollAreaViewport.useDialogRootContext();
1518
- const {
1519
- swipeDirection,
1520
- frontmostHeight
1521
- } = ScrollAreaViewport.useDrawerRootContext();
1522
- const providerContext = ScrollAreaViewport.useDrawerProviderContext();
1523
- const [swipeActive, setSwipeActive] = React__namespace.useState(false);
1524
- const releaseDismissTimeout = useButton.useTimeout();
1525
- const swipeAreaRef = React__namespace.useRef(null);
1526
- const swipeStartEventRef = React__namespace.useRef(null);
1527
- const openedBySwipeRef = React__namespace.useRef(false);
1528
- const dragDeltaRef = React__namespace.useRef({
1529
- x: 0,
1530
- y: 0
1531
- });
1532
- const closedOffsetRef = React__namespace.useRef(null);
1533
- const appliedSwipeStylesRef = React__namespace.useRef(false);
1534
- const popupTransitionRef = React__namespace.useRef(null);
1535
- const swipeAreaId = useButton.useBaseUiId(componentProps.id);
1536
- const registerTrigger = useScrollLock.useTriggerRegistration(swipeAreaId, store);
1537
- const open = store.useState('open');
1538
- const resetDragDelta = useButton.useStableCallback(() => {
1539
- dragDeltaRef.current.x = 0;
1540
- dragDeltaRef.current.y = 0;
1541
- });
1542
- const resolvedSwipeDirection = swipeDirectionProp ?? oppositeSwipeDirection[swipeDirection];
1543
- const dismissDirection = oppositeSwipeDirection[resolvedSwipeDirection];
1544
- const enabled = !disabled && (!open || swipeActive);
1545
- function disableDismissForSwipe() {
1546
- releaseDismissTimeout.clear();
1547
- store.context.outsidePressEnabledRef.current = false;
1548
- }
1549
- function enableDismissAfterRelease() {
1550
- // Safari can dispatch outside-press for the same swipe-open gesture
1551
- // after release, so defer re-enabling dismissal to the next macrotask.
1552
- releaseDismissTimeout.start(0, () => {
1553
- store.context.outsidePressEnabledRef.current = true;
1554
- });
1555
- }
1556
- function resolvePopupSize() {
1557
- const popupElement = store.context.popupRef.current;
1558
- if (!popupElement) {
1559
- return null;
1560
- }
1561
- const isHorizontal = dismissDirection === 'left' || dismissDirection === 'right';
1562
- const size = isHorizontal ? popupElement.offsetWidth : popupElement.offsetHeight;
1563
- if (size <= 0) {
1564
- return null;
1565
- }
1566
- return size;
1567
- }
1568
- function resolveClosedOffset() {
1569
- const offset = resolvePopupSize();
1570
- if (offset == null) {
1571
- return null;
1572
- }
1573
- const popupElement = store.context.popupRef.current;
1574
- if (!popupElement) {
1575
- return offset;
1576
- }
1577
- const isHorizontal = dismissDirection === 'left' || dismissDirection === 'right';
1578
- const transform = getElementTransform(popupElement);
1579
- const transformOffset = isHorizontal ? transform.x : transform.y;
1580
- if (Number.isFinite(transformOffset) && Math.abs(transformOffset) > 0.5) {
1581
- return Math.min(offset, Math.abs(transformOffset));
1582
- }
1583
- return offset;
1584
- }
1585
- function resolveSwipeOpenThreshold() {
1586
- const popupSize = resolvePopupSize();
1587
- if (popupSize == null) {
1588
- return FALLBACK_SWIPE_OPEN_THRESHOLD;
1589
- }
1590
- return popupSize * DEFAULT_SWIPE_OPEN_RATIO;
1591
- }
1592
- function applySwipeMovement() {
1593
- if (!swipeActive) {
1594
- return;
1595
- }
1596
- const popupElement = store.context.popupRef.current;
1597
- if (!popupElement) {
1598
- return;
1599
- }
1600
- if (!store.select('open') || !store.select('mounted')) {
1601
- return;
1602
- }
1603
- if (closedOffsetRef.current == null) {
1604
- closedOffsetRef.current = resolveClosedOffset();
1605
- }
1606
- const closedOffset = closedOffsetRef.current;
1607
- if (!closedOffset || !Number.isFinite(closedOffset) || closedOffset <= 0) {
596
+ const closedOffset = closedOffsetRef.current;
597
+ if (!closedOffset || !Number.isFinite(closedOffset) || closedOffset <= 0) {
1608
598
  return;
1609
599
  }
1610
600
  const {
1611
601
  x,
1612
602
  y
1613
603
  } = dragDeltaRef.current;
1614
- const displacement = getDisplacement(resolvedSwipeDirection, x, y);
604
+ const displacement = ScrollAreaViewport.getDisplacement(resolvedSwipeDirection, x, y);
1615
605
  const clampedDisplacement = Math.max(0, displacement);
1616
606
  const dampedDisplacement = clampedDisplacement > closedOffset ? closedOffset + Math.sqrt(clampedDisplacement - closedOffset) : clampedDisplacement;
1617
607
  const remaining = closedOffset - dampedDisplacement;
@@ -1693,7 +683,7 @@ const DrawerSwipeArea = /*#__PURE__*/React__namespace.forwardRef(function Drawer
1693
683
  resetDragDelta();
1694
684
  clearSwipeStyles();
1695
685
  }
1696
- const swipe = useSwipeDismiss({
686
+ const swipe = ScrollAreaViewport.useSwipeDismiss({
1697
687
  enabled,
1698
688
  directions: [resolvedSwipeDirection],
1699
689
  elementRef: swipeAreaRef,
@@ -1716,856 +706,89 @@ const DrawerSwipeArea = /*#__PURE__*/React__namespace.forwardRef(function Drawer
1716
706
  if (!swipeStartEventRef.current) {
1717
707
  return;
1718
708
  }
1719
- dragDeltaRef.current.x = details.deltaX;
1720
- dragDeltaRef.current.y = details.deltaY;
1721
- if (details.direction !== resolvedSwipeDirection) {
1722
- return;
1723
- }
1724
- const displacement = getDisplacement(resolvedSwipeDirection, details.deltaX, details.deltaY);
1725
- if (displacement < MIN_SWIPE_START_DISTANCE && !openedBySwipeRef.current) {
1726
- return;
1727
- }
1728
- if (!openedBySwipeRef.current) {
1729
- openDrawer(swipeStartEventRef.current);
1730
- }
1731
- applySwipeMovement();
1732
- },
1733
- onRelease({
1734
- event,
1735
- direction,
1736
- deltaX,
1737
- deltaY,
1738
- releaseVelocityX,
1739
- releaseVelocityY
1740
- }) {
1741
- const displacement = getDisplacement(resolvedSwipeDirection, deltaX, deltaY);
1742
- const releaseVelocity = getDisplacement(resolvedSwipeDirection, releaseVelocityX, releaseVelocityY);
1743
- const threshold = resolveSwipeOpenThreshold();
1744
- const hasEnoughDistance = threshold != null && displacement >= threshold;
1745
- const hasEnoughVelocity = releaseVelocity >= VELOCITY_THRESHOLD;
1746
- const shouldOpen = threshold != null && direction === resolvedSwipeDirection && (hasEnoughDistance || hasEnoughVelocity) && !disabled;
1747
- if (shouldOpen) {
1748
- if (!store.select('open')) {
1749
- openDrawer(event);
1750
- }
1751
- } else if (openedBySwipeRef.current) {
1752
- closeDrawer(event);
1753
- }
1754
- finishSwipeInteraction();
1755
- return false;
1756
- },
1757
- onCancel: finishSwipeInteraction
1758
- });
1759
- const swipePointerProps = swipe.getPointerProps();
1760
- const swipeTouchProps = swipe.getTouchProps();
1761
- const resetSwipe = swipe.reset;
1762
- React__namespace.useEffect(() => {
1763
- if (!enabled) {
1764
- resetSwipe();
1765
- resetDragDelta();
1766
- clearSwipeStyles();
1767
- resetSwipeInteractionState();
1768
- }
1769
- }, [clearSwipeStyles, enabled, resetDragDelta, resetSwipe]);
1770
- React__namespace.useEffect(() => {
1771
- return () => {
1772
- store.context.outsidePressEnabledRef.current = true;
1773
- };
1774
- }, [store]);
1775
- const state = {
1776
- open,
1777
- swiping: swipe.swiping,
1778
- swipeDirection: resolvedSwipeDirection,
1779
- disabled
1780
- };
1781
- return useRenderElement.useRenderElement('div', componentProps, {
1782
- state,
1783
- ref: [forwardedRef, swipeAreaRef, registerTrigger],
1784
- stateAttributesMapping: stateAttributesMapping$3,
1785
- props: [{
1786
- role: 'presentation',
1787
- 'aria-hidden': true,
1788
- style: {
1789
- pointerEvents: !enabled ? 'none' : undefined,
1790
- touchAction: resolveTouchAction(resolvedSwipeDirection)
1791
- },
1792
- onPointerDown(event) {
1793
- if (event.pointerType === 'touch') {
1794
- return;
1795
- }
1796
- swipePointerProps.onPointerDown?.(event);
1797
-
1798
- // Prevent native text selection/drag gestures from competing with swipe-open dragging.
1799
- if (event.cancelable) {
1800
- event.preventDefault();
1801
- }
1802
- },
1803
- onPointerMove(event) {
1804
- if (event.pointerType === 'touch') {
1805
- return;
1806
- }
1807
- swipePointerProps.onPointerMove?.(event);
1808
- },
1809
- onPointerUp(event) {
1810
- if (event.pointerType === 'touch') {
1811
- return;
1812
- }
1813
- swipePointerProps.onPointerUp?.(event);
1814
- },
1815
- onPointerCancel(event) {
1816
- if (event.pointerType === 'touch') {
1817
- return;
1818
- }
1819
- swipePointerProps.onPointerCancel?.(event);
1820
- }
1821
- }, swipeTouchProps, swipeAreaId ? {
1822
- id: swipeAreaId
1823
- } : undefined, elementProps]
1824
- });
1825
- });
1826
- if (process.env.NODE_ENV !== "production") DrawerSwipeArea.displayName = "DrawerSwipeArea";
1827
-
1828
- const DrawerVirtualKeyboardContext = /*#__PURE__*/React__namespace.createContext(undefined);
1829
- if (process.env.NODE_ENV !== "production") DrawerVirtualKeyboardContext.displayName = "DrawerVirtualKeyboardContext";
1830
- function useDrawerVirtualKeyboardContext() {
1831
- return React__namespace.useContext(DrawerVirtualKeyboardContext);
1832
- }
1833
-
1834
- const MIN_SWIPE_THRESHOLD = 10;
1835
- const FAST_SWIPE_VELOCITY = 0.5;
1836
- const SNAP_VELOCITY_THRESHOLD = 0.5;
1837
- const SNAP_VELOCITY_MULTIPLIER = 300;
1838
- const MAX_SNAP_VELOCITY = 4;
1839
- const MIN_SWIPE_RELEASE_VELOCITY = 0.2;
1840
- const MAX_SWIPE_RELEASE_VELOCITY = 4;
1841
- const MIN_SWIPE_RELEASE_DURATION_MS = 80;
1842
- const MAX_SWIPE_RELEASE_DURATION_MS = 360;
1843
- const MIN_SWIPE_RELEASE_SCALAR = 0.1;
1844
- const MAX_SWIPE_RELEASE_SCALAR = 1;
1845
- const DRAWER_CONTENT_SELECTOR = `[${ScrollAreaViewport.DRAWER_CONTENT_ATTRIBUTE}]`;
1846
- /**
1847
- * A positioning container for the drawer popup that can be made scrollable.
1848
- * Renders a `<div>` element.
1849
- *
1850
- * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)
1851
- */
1852
- const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerViewport(props, forwardedRef) {
1853
- const {
1854
- render,
1855
- className,
1856
- style,
1857
- children,
1858
- ...elementProps
1859
- } = props;
1860
- const {
1861
- store
1862
- } = ScrollAreaViewport.useDialogRootContext();
1863
- const popupRef = store.context.popupRef;
1864
- const backdropRef = store.context.backdropRef;
1865
- const {
1866
- swipeDirection,
1867
- notifyParentSwipingChange,
1868
- notifyParentSwipeProgressChange,
1869
- frontmostHeight,
1870
- snapToSequentialPoints
1871
- } = ScrollAreaViewport.useDrawerRootContext();
1872
- const providerContext = ScrollAreaViewport.useDrawerProviderContext();
1873
- const {
1874
- snapPoints,
1875
- resolvedSnapPoints,
1876
- activeSnapPoint,
1877
- activeSnapPointOffset,
1878
- setActiveSnapPoint,
1879
- popupHeight
1880
- } = ScrollAreaViewport.useDrawerSnapPoints();
1881
- const open = store.useState('open');
1882
- const mounted = store.useState('mounted');
1883
- const nested = store.useState('nested');
1884
- const nestedOpenDrawerCount = store.useState('nestedOpenDrawerCount');
1885
- const viewportElement = store.useState('viewportElement');
1886
- const popupElementState = store.useState('popupElement');
1887
- const visualStateStore = providerContext?.visualStateStore;
1888
- const nestedDrawerOpen = nestedOpenDrawerCount > 0;
1889
- const scrollAxis = swipeDirection === 'left' || swipeDirection === 'right' ? 'horizontal' : 'vertical';
1890
- const isVerticalScrollAxis = scrollAxis === 'vertical';
1891
- const crossScrollAxis = isVerticalScrollAxis ? 'horizontal' : 'vertical';
1892
- const [swipeRelease, setSwipeRelease] = React__namespace.useState(null);
1893
- const pendingSwipeCloseSnapPointRef = React__namespace.useRef(undefined);
1894
- const resetSwipeRef = React__namespace.useRef(null);
1895
- const controlledDismissFrame = useButton.useAnimationFrame();
1896
- const swipingRef = React__namespace.useRef(false);
1897
- const nestedSwipeActiveRef = React__namespace.useRef(false);
1898
- const lastPointerTypeRef = React__namespace.useRef('');
1899
- const ignoreNextTouchStartFromPenRef = React__namespace.useRef(false);
1900
- const ignoreTouchSwipeRef = React__namespace.useRef(false);
1901
- const touchScrollStateRef = React__namespace.useRef(null);
1902
- const virtualKeyboard = useDrawerVirtualKeyboardContext();
1903
- const snapPointRange = React__namespace.useMemo(() => {
1904
- if (!snapPoints || snapPoints.length < 2) {
1905
- return null;
1906
- }
1907
- if (swipeDirection !== 'down' && swipeDirection !== 'up') {
1908
- return null;
1909
- }
1910
- if (resolvedSnapPoints.length < 2) {
1911
- return null;
1912
- }
1913
- const offsets = resolvedSnapPoints.map(point => point.offset).filter(offset => Number.isFinite(offset)).sort((a, b) => a - b);
1914
- if (offsets.length < 2) {
1915
- return null;
1916
- }
1917
- const minOffset = offsets[0];
1918
- const nextOffset = offsets[1];
1919
- const maxOffset = offsets[offsets.length - 1];
1920
- let range = nextOffset - minOffset;
1921
- if (!Number.isFinite(range) || range <= 0) {
1922
- const fallbackRange = maxOffset - minOffset;
1923
- if (!Number.isFinite(fallbackRange) || fallbackRange <= 0) {
1924
- return null;
1925
- }
1926
- range = fallbackRange;
1927
- }
1928
- return {
1929
- minOffset,
1930
- range
1931
- };
1932
- }, [resolvedSnapPoints, snapPoints, swipeDirection]);
1933
- const snapPointProgress = React__namespace.useMemo(() => {
1934
- if (!snapPointRange || activeSnapPointOffset === null) {
1935
- return null;
1936
- }
1937
- return clamp.clamp((activeSnapPointOffset - snapPointRange.minOffset) / snapPointRange.range, 0, 1);
1938
- }, [activeSnapPointOffset, snapPointRange]);
1939
- const swipeDirections = React__namespace.useMemo(() => {
1940
- if (snapPoints && snapPoints.length > 0 && (swipeDirection === 'down' || swipeDirection === 'up')) {
1941
- return swipeDirection === 'down' ? ['down', 'up'] : ['up', 'down'];
1942
- }
1943
- return [swipeDirection];
1944
- }, [snapPoints, swipeDirection]);
1945
- const setSwipeDismissed = useButton.useStableCallback(dismissed => {
1946
- setSwipeDismissedElements(popupRef.current, backdropRef.current, dismissed);
1947
- });
1948
- const clearSwipeRelease = useButton.useStableCallback(() => {
1949
- setSwipeDismissed(false);
1950
- popupRef.current?.removeAttribute(useButton.TransitionStatusDataAttributes.endingStyle);
1951
- setSwipeRelease(null);
1952
- });
1953
- const finishNestedSwipe = useButton.useStableCallback(() => {
1954
- if (!nestedSwipeActiveRef.current) {
1955
- return;
1956
- }
1957
- nestedSwipeActiveRef.current = false;
1958
- notifyParentSwipingChange?.(false);
1959
- });
1960
- const applySwipeProgress = useButton.useStableCallback(({
1961
- resolvedProgress,
1962
- shouldTrackProgress,
1963
- notifyParent
1964
- }) => {
1965
- const isActive = open && !nested && shouldTrackProgress;
1966
- const swipeProgress = isActive ? resolvedProgress : 0;
1967
- const nestedSwipeProgress = open && shouldTrackProgress ? resolvedProgress : 0;
1968
- if (notifyParent && notifyParentSwipeProgressChange) {
1969
- notifyParentSwipeProgressChange(nestedSwipeProgress);
1970
- if (nestedSwipeProgress <= 0) {
1971
- finishNestedSwipe();
1972
- }
1973
- }
1974
- visualStateStore?.set({
1975
- swipeProgress,
1976
- frontmostHeight: swipeProgress > 0 ? frontmostHeight : 0
1977
- });
1978
- const backdropElement = backdropRef.current;
1979
- if (!backdropElement) {
1980
- return;
1981
- }
1982
- if (!isActive || swipeProgress <= 0) {
1983
- backdropElement.style.setProperty(ScrollAreaViewport.DrawerBackdropCssVars.swipeProgress, '0');
1984
- backdropElement.style.removeProperty(ScrollAreaViewport.DrawerPopupCssVars.height);
1985
- return;
1986
- }
1987
- backdropElement.style.setProperty(ScrollAreaViewport.DrawerBackdropCssVars.swipeProgress, `${swipeProgress}`);
1988
- if (frontmostHeight > 0) {
1989
- backdropElement.style.setProperty(ScrollAreaViewport.DrawerPopupCssVars.height, `${frontmostHeight}px`);
1990
- } else {
1991
- backdropElement.style.removeProperty(ScrollAreaViewport.DrawerPopupCssVars.height);
1992
- }
1993
- });
1994
- function resolveSwipeRelease({
1995
- direction,
1996
- deltaX,
1997
- deltaY,
1998
- velocityX,
1999
- velocityY,
2000
- releaseVelocityX,
2001
- releaseVelocityY
2002
- }) {
2003
- if (!direction) {
2004
- return null;
2005
- }
2006
- const popupElement = store.context.popupRef.current;
2007
- if (!popupElement) {
2008
- return null;
2009
- }
2010
- const size = direction === 'left' || direction === 'right' ? popupElement.offsetWidth : popupElement.offsetHeight;
2011
- if (!Number.isFinite(size) || size <= 0) {
2012
- return null;
2013
- }
2014
- const axisDelta = direction === 'left' || direction === 'right' ? deltaX : deltaY;
2015
- const snapPointBaseOffset = snapPoints && snapPoints.length > 0 ? activeSnapPointOffset ?? 0 : 0;
2016
- let baseOffset = 0;
2017
- if (direction === 'down') {
2018
- baseOffset = snapPointBaseOffset;
2019
- } else if (direction === 'up') {
2020
- baseOffset = -snapPointBaseOffset;
2021
- }
2022
- const translation = baseOffset + axisDelta;
2023
- const translationAlongDirection = direction === 'left' || direction === 'up' ? -translation : translation;
2024
- const remainingDistance = Math.max(0, size - translationAlongDirection);
2025
- if (!Number.isFinite(remainingDistance) || remainingDistance <= 0) {
2026
- return null;
2027
- }
2028
- const axisVelocity = direction === 'left' || direction === 'right' ? releaseVelocityX : releaseVelocityY;
2029
- const fallbackVelocity = direction === 'left' || direction === 'right' ? velocityX : velocityY;
2030
- const resolvedVelocity = Math.abs(axisVelocity) > 0 && Number.isFinite(axisVelocity) ? axisVelocity : fallbackVelocity;
2031
- const directionalVelocity = direction === 'left' || direction === 'up' ? -resolvedVelocity : resolvedVelocity;
2032
- if (!Number.isFinite(directionalVelocity) || directionalVelocity <= MIN_SWIPE_RELEASE_VELOCITY) {
2033
- return null;
2034
- }
2035
- const clampedVelocity = clamp.clamp(directionalVelocity, MIN_SWIPE_RELEASE_VELOCITY, MAX_SWIPE_RELEASE_VELOCITY);
2036
- const durationMs = clamp.clamp(remainingDistance / clampedVelocity, MIN_SWIPE_RELEASE_DURATION_MS, MAX_SWIPE_RELEASE_DURATION_MS);
2037
- if (!Number.isFinite(durationMs)) {
2038
- return null;
2039
- }
2040
- const normalizedDuration = (durationMs - MIN_SWIPE_RELEASE_DURATION_MS) / (MAX_SWIPE_RELEASE_DURATION_MS - MIN_SWIPE_RELEASE_DURATION_MS);
2041
- 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);
2042
- if (!Number.isFinite(durationScalar) || durationScalar <= 0) {
2043
- return null;
2044
- }
2045
- return durationScalar;
2046
- }
2047
- function updateNestedSwipeActive(details) {
2048
- if (nestedSwipeActiveRef.current || !details) {
2049
- return;
2050
- }
2051
- const direction = details.direction ?? swipeDirection;
2052
- const delta = direction === 'left' || direction === 'right' ? details.deltaX : details.deltaY;
2053
- if (!Number.isFinite(delta) || Math.abs(delta) < MIN_SWIPE_THRESHOLD) {
2054
- return;
2055
- }
2056
- nestedSwipeActiveRef.current = true;
2057
- notifyParentSwipingChange?.(true);
2058
- }
2059
- const swipe = useSwipeDismiss({
2060
- enabled: mounted && !nestedDrawerOpen,
2061
- directions: swipeDirections,
2062
- elementRef: store.context.popupRef,
2063
- ignoreSelectorWhenTouch: false,
2064
- ignoreScrollableAncestors: true,
2065
- movementCssVars: {
2066
- x: ScrollAreaViewport.DrawerPopupCssVars.swipeMovementX,
2067
- y: ScrollAreaViewport.DrawerPopupCssVars.swipeMovementY
2068
- },
2069
- onSwipeStart(event) {
2070
- if ('touches' in event || 'pointerType' in event && event.pointerType === 'touch') {
2071
- return;
2072
- }
2073
- const popupElement = popupRef.current;
2074
- if (!popupElement) {
2075
- return;
2076
- }
2077
- const doc = useButton.ownerDocument(popupElement);
2078
- const selection = doc.getSelection?.();
2079
- if (!selection || selection.isCollapsed) {
709
+ dragDeltaRef.current.x = details.deltaX;
710
+ dragDeltaRef.current.y = details.deltaY;
711
+ if (details.direction !== resolvedSwipeDirection) {
2080
712
  return;
2081
713
  }
2082
- const anchorElement = floatingUi_utils_dom.isElement(selection.anchorNode) ? selection.anchorNode : selection.anchorNode?.parentElement;
2083
- const focusElement = floatingUi_utils_dom.isElement(selection.focusNode) ? selection.focusNode : selection.focusNode?.parentElement;
2084
- if (!useButton.contains(popupElement, anchorElement) && !useButton.contains(popupElement, focusElement)) {
714
+ const displacement = ScrollAreaViewport.getDisplacement(resolvedSwipeDirection, details.deltaX, details.deltaY);
715
+ if (displacement < MIN_SWIPE_START_DISTANCE && !openedBySwipeRef.current) {
2085
716
  return;
2086
717
  }
2087
- selection.removeAllRanges();
2088
- },
2089
- onSwipingChange(swiping) {
2090
- swipingRef.current = swiping;
2091
- setBackdropSwipingAttribute(store.context.backdropRef.current, swiping);
2092
- if (!swiping && !notifyParentSwipeProgressChange) {
2093
- finishNestedSwipe();
2094
- }
2095
- },
2096
- swipeThreshold({
2097
- element,
2098
- direction
2099
- }) {
2100
- return getBaseSwipeThreshold(element, direction);
2101
- },
2102
- canStart(position, details) {
2103
- const popupElement = store.context.popupRef.current;
2104
- if (!popupElement) {
2105
- return false;
2106
- }
2107
- const doc = popupElement.ownerDocument;
2108
- const elementAtPoint = getElementAtPoint(doc, position.x, position.y);
2109
- if (!elementAtPoint || !useButton.contains(popupElement, elementAtPoint)) {
2110
- return false;
2111
- }
2112
- const nativeEvent = details.nativeEvent;
2113
- const touchLike = 'touches' in nativeEvent || 'pointerType' in nativeEvent && nativeEvent.pointerType === 'touch';
2114
- if (touchLike && shouldIgnoreSwipeForTextSelection(doc, popupElement)) {
2115
- return false;
2116
- }
2117
- if (nativeEvent.type === 'touchstart' && isSwipeIgnoredTarget(elementAtPoint)) {
2118
- return false;
2119
- }
2120
- return true;
2121
- },
2122
- onProgress(progress, details) {
2123
- updateNestedSwipeActive(details);
2124
- const hasSnapPoints = Boolean(snapPoints && snapPoints.length > 0);
2125
- if (swipingRef.current && swipeDirection === 'down' && hasSnapPoints && details && Number.isFinite(details.deltaY)) {
2126
- const popupElement = store.context.popupRef.current;
2127
- if (popupElement) {
2128
- popupElement.style.removeProperty('transform');
2129
- popupElement.style.setProperty(ScrollAreaViewport.DrawerPopupCssVars.swipeMovementY, `${ScrollAreaViewport.getSnapPointSwipeMovement(activeSnapPointOffset ?? 0, details.deltaY)}px`);
2130
- }
2131
- }
2132
- const currentDirection = details?.direction ?? swipe.swipeDirection;
2133
- const isDismissSwipe = currentDirection === undefined || currentDirection === swipeDirection;
2134
- const isVerticalSwipe = swipeDirection === 'down' || swipeDirection === 'up';
2135
- const shouldTrackProgress = hasSnapPoints && isVerticalSwipe || !hasSnapPoints || swipeDirection === 'left' || swipeDirection === 'right' || isDismissSwipe;
2136
- let resolvedProgress = progress;
2137
- if (snapPointRange && popupHeight > 0) {
2138
- if (details && Number.isFinite(details.deltaY)) {
2139
- const baseOffset = activeSnapPointOffset ?? snapPointRange.minOffset;
2140
- const nextOffset = clamp.clamp(baseOffset + details.deltaY, 0, popupHeight);
2141
- resolvedProgress = clamp.clamp((nextOffset - snapPointRange.minOffset) / snapPointRange.range, 0, 1);
2142
- } else if (snapPointProgress !== null) {
2143
- resolvedProgress = snapPointProgress;
2144
- } else if (currentDirection === 'down' || currentDirection === 'up') {
2145
- const displacement = progress * popupHeight;
2146
- const baseOffset = activeSnapPointOffset ?? snapPointRange.minOffset;
2147
- const nextOffset = currentDirection === 'down' ? baseOffset + displacement : baseOffset - displacement;
2148
- resolvedProgress = clamp.clamp((nextOffset - snapPointRange.minOffset) / snapPointRange.range, 0, 1);
2149
- }
718
+ if (!openedBySwipeRef.current) {
719
+ openDrawer(swipeStartEventRef.current);
2150
720
  }
2151
- applySwipeProgress({
2152
- resolvedProgress,
2153
- shouldTrackProgress,
2154
- notifyParent: true
2155
- });
721
+ applySwipeMovement();
2156
722
  },
2157
723
  onRelease({
2158
724
  event,
725
+ direction,
2159
726
  deltaX,
2160
727
  deltaY,
2161
- direction,
2162
- velocityX,
2163
- velocityY,
2164
728
  releaseVelocityX,
2165
729
  releaseVelocityY
2166
730
  }) {
2167
- const swipeReleasePayload = {
2168
- deltaX,
2169
- deltaY,
2170
- velocityX,
2171
- velocityY,
2172
- releaseVelocityX,
2173
- releaseVelocityY
2174
- };
2175
- function startSwipeRelease(resolvedDirection) {
2176
- // Start ending transition styles earlier and synchronously to prevent a period where
2177
- // the popup appears stuck on release before the actual closing animation starts.
2178
- const popupElement = store.context.popupRef.current;
2179
- if (!popupElement) {
2180
- return;
2181
- }
2182
- finishNestedSwipe();
2183
- setSwipeDismissed(true);
2184
- popupElement.style.removeProperty('transition');
2185
- popupElement.setAttribute(useButton.TransitionStatusDataAttributes.endingStyle, '');
2186
- ReactDOM__namespace.flushSync(() => {
2187
- setSwipeRelease(resolveSwipeRelease({
2188
- direction: resolvedDirection,
2189
- ...swipeReleasePayload
2190
- }));
2191
- });
2192
- }
2193
- if (!snapPoints || snapPoints.length === 0) {
2194
- if (!direction) {
2195
- clearSwipeRelease();
2196
- return undefined;
2197
- }
2198
- const element = store.context.popupRef.current;
2199
- if (!element) {
2200
- clearSwipeRelease();
2201
- return undefined;
2202
- }
2203
- const baseThreshold = getBaseSwipeThreshold(element, direction);
2204
- const delta = direction === 'left' || direction === 'right' ? deltaX : deltaY;
2205
- if (!Number.isFinite(delta)) {
2206
- clearSwipeRelease();
2207
- return undefined;
2208
- }
2209
- const directionalDelta = direction === 'left' || direction === 'up' ? -delta : delta;
2210
- if (directionalDelta <= 0) {
2211
- clearSwipeRelease();
2212
- return false;
2213
- }
2214
- const velocity = direction === 'left' || direction === 'right' ? velocityX : velocityY;
2215
- const directionalVelocity = direction === 'left' || direction === 'up' ? -velocity : velocity;
2216
- if (directionalVelocity >= FAST_SWIPE_VELOCITY && directionalDelta > 0) {
2217
- startSwipeRelease(direction);
2218
- return true;
2219
- }
2220
- const shouldClose = directionalDelta > baseThreshold;
2221
- if (shouldClose) {
2222
- startSwipeRelease(direction);
2223
- } else {
2224
- clearSwipeRelease();
2225
- }
2226
- return shouldClose;
2227
- }
2228
- if (swipeDirection !== 'down' && swipeDirection !== 'up') {
2229
- clearSwipeRelease();
2230
- return undefined;
2231
- }
2232
- if (!popupHeight || resolvedSnapPoints.length === 0) {
2233
- clearSwipeRelease();
2234
- return undefined;
2235
- }
2236
- const dragDelta = swipeDirection === 'down' ? deltaY : -deltaY;
2237
- if (!Number.isFinite(dragDelta)) {
2238
- clearSwipeRelease();
2239
- return undefined;
2240
- }
2241
- const dragDirection = Math.sign(dragDelta);
2242
- const releaseDirectionalVelocity = swipeDirection === 'down' ? releaseVelocityY : -releaseVelocityY;
2243
- const fallbackDirectionalVelocity = swipeDirection === 'down' ? velocityY : -velocityY;
2244
- let resolvedDirectionalVelocity = Number.isFinite(releaseDirectionalVelocity) ? releaseDirectionalVelocity : fallbackDirectionalVelocity;
2245
- if (dragDirection !== 0 && Math.abs(dragDelta) >= MIN_SWIPE_THRESHOLD && Number.isFinite(resolvedDirectionalVelocity)) {
2246
- const velocityDirection = Math.sign(resolvedDirectionalVelocity);
2247
- if (velocityDirection !== 0 && velocityDirection !== dragDirection) {
2248
- // Ignore touch reversals that would otherwise flip the snap decision.
2249
- resolvedDirectionalVelocity = fallbackDirectionalVelocity;
2250
- }
2251
- }
2252
- const currentOffset = activeSnapPointOffset ?? 0;
2253
- const dragTargetOffset = clamp.clamp(currentOffset + dragDelta, 0, popupHeight);
2254
- const velocityOffset = Number.isFinite(resolvedDirectionalVelocity) && Math.abs(resolvedDirectionalVelocity) >= SNAP_VELOCITY_THRESHOLD ? clamp.clamp(resolvedDirectionalVelocity, -MAX_SNAP_VELOCITY, MAX_SNAP_VELOCITY) * SNAP_VELOCITY_MULTIPLIER : 0;
2255
- const targetOffset = snapToSequentialPoints ? dragTargetOffset : clamp.clamp(dragTargetOffset + velocityOffset, 0, popupHeight);
2256
- const snapPointEventDetails = useButton.createChangeEventDetails(useButton.swipe, event);
2257
- const closeFromSnapPoints = () => {
2258
- pendingSwipeCloseSnapPointRef.current = activeSnapPoint;
2259
- setActiveSnapPoint?.(null, snapPointEventDetails);
2260
- startSwipeRelease(swipeDirection);
2261
- return true;
2262
- };
2263
- if (snapToSequentialPoints) {
2264
- const orderedSnapPoints = [...resolvedSnapPoints].sort((first, second) => first.offset - second.offset);
2265
- if (orderedSnapPoints.length === 0) {
2266
- clearSwipeRelease();
2267
- return false;
2268
- }
2269
- let currentIndex = 0;
2270
- let closestDistance = Math.abs(currentOffset - orderedSnapPoints[0].offset);
2271
- for (let index = 1; index < orderedSnapPoints.length; index += 1) {
2272
- const distance = Math.abs(currentOffset - orderedSnapPoints[index].offset);
2273
- if (distance < closestDistance) {
2274
- closestDistance = distance;
2275
- currentIndex = index;
2276
- }
2277
- }
2278
- let targetSnapPoint = orderedSnapPoints[0];
2279
- closestDistance = Math.abs(targetOffset - targetSnapPoint.offset);
2280
- for (const snapPoint of orderedSnapPoints) {
2281
- const distance = Math.abs(targetOffset - snapPoint.offset);
2282
- if (distance < closestDistance) {
2283
- closestDistance = distance;
2284
- targetSnapPoint = snapPoint;
2285
- }
2286
- }
2287
- const velocityDirection = Math.sign(resolvedDirectionalVelocity);
2288
- const shouldAdvance = dragDirection !== 0 && velocityDirection !== 0 && velocityDirection === dragDirection && Math.abs(resolvedDirectionalVelocity) >= SNAP_VELOCITY_THRESHOLD;
2289
- let effectiveTargetOffset = targetOffset;
2290
- if (shouldAdvance) {
2291
- const adjacentIndex = clamp.clamp(currentIndex + dragDirection, 0, orderedSnapPoints.length - 1);
2292
- if (adjacentIndex !== currentIndex) {
2293
- const adjacentPoint = orderedSnapPoints[adjacentIndex];
2294
- const shouldForceAdjacent = dragDirection > 0 ? targetOffset < adjacentPoint.offset : targetOffset > adjacentPoint.offset;
2295
- if (shouldForceAdjacent) {
2296
- targetSnapPoint = adjacentPoint;
2297
- effectiveTargetOffset = adjacentPoint.offset;
2298
- }
2299
- } else if (dragDirection > 0) {
2300
- return closeFromSnapPoints();
2301
- }
2302
- }
2303
- const closeOffset = popupHeight;
2304
- const closeDistance = Math.abs(effectiveTargetOffset - closeOffset);
2305
- const snapDistance = Math.abs(effectiveTargetOffset - targetSnapPoint.offset);
2306
- if (closeDistance < snapDistance) {
2307
- return closeFromSnapPoints();
2308
- }
2309
- setActiveSnapPoint?.(targetSnapPoint.value, snapPointEventDetails);
2310
- clearSwipeRelease();
2311
- return false;
2312
- }
2313
- if (resolvedDirectionalVelocity >= FAST_SWIPE_VELOCITY && dragDelta > 0) {
2314
- return closeFromSnapPoints();
2315
- }
2316
- let closestSnapPoint = resolvedSnapPoints[0];
2317
- let closestDistance = Math.abs(targetOffset - closestSnapPoint.offset);
2318
- for (const snapPoint of resolvedSnapPoints) {
2319
- const distance = Math.abs(targetOffset - snapPoint.offset);
2320
- if (distance < closestDistance) {
2321
- closestDistance = distance;
2322
- closestSnapPoint = snapPoint;
731
+ const displacement = ScrollAreaViewport.getDisplacement(resolvedSwipeDirection, deltaX, deltaY);
732
+ const releaseVelocity = ScrollAreaViewport.getDisplacement(resolvedSwipeDirection, releaseVelocityX, releaseVelocityY);
733
+ const threshold = resolveSwipeOpenThreshold();
734
+ const hasEnoughDistance = threshold != null && displacement >= threshold;
735
+ const hasEnoughVelocity = releaseVelocity >= VELOCITY_THRESHOLD;
736
+ const shouldOpen = threshold != null && direction === resolvedSwipeDirection && (hasEnoughDistance || hasEnoughVelocity) && !disabled;
737
+ if (shouldOpen) {
738
+ if (!store.select('open')) {
739
+ openDrawer(event);
2323
740
  }
741
+ } else if (openedBySwipeRef.current) {
742
+ closeDrawer(event);
2324
743
  }
2325
- const closeOffset = popupHeight;
2326
- const closeDistance = Math.abs(targetOffset - closeOffset);
2327
- if (closeDistance < closestDistance) {
2328
- return closeFromSnapPoints();
2329
- }
2330
- setActiveSnapPoint?.(closestSnapPoint.value, snapPointEventDetails);
2331
- clearSwipeRelease();
744
+ finishSwipeInteraction();
2332
745
  return false;
2333
746
  },
2334
- onDismiss(event) {
2335
- visualStateStore?.set({
2336
- swipeProgress: 0,
2337
- frontmostHeight: 0
2338
- });
2339
- const backdropElement = store.context.backdropRef.current;
2340
- if (backdropElement) {
2341
- backdropElement.style.setProperty(ScrollAreaViewport.DrawerBackdropCssVars.swipeProgress, '0');
2342
- backdropElement.style.removeProperty(ScrollAreaViewport.DrawerPopupCssVars.height);
2343
- }
2344
- const dismissEventDetails = useButton.createChangeEventDetails(useButton.swipe, event);
2345
- store.setOpen(false, dismissEventDetails);
2346
- if (dismissEventDetails.isCanceled) {
2347
- const pendingSnapPoint = pendingSwipeCloseSnapPointRef.current;
2348
- if (pendingSnapPoint !== undefined) {
2349
- setActiveSnapPoint?.(pendingSnapPoint, useButton.createChangeEventDetails(useButton.swipe, event));
2350
- }
2351
- pendingSwipeCloseSnapPointRef.current = undefined;
2352
- resetSwipeRef.current?.();
2353
- clearSwipeRelease();
2354
- return;
2355
- }
2356
-
2357
- // In controlled mode, the effective open state may not have changed yet
2358
- // (openProp takes precedence over state.open). Proceed optimistically with the
2359
- // dismiss animation — React's Scheduler flushes before the next rAF, so we can
2360
- // reliably check whether the parent accepted or rejected the close.
2361
- // Note: if onOpenChange is asynchronous (e.g., closes the drawer after a network
2362
- // call), the rAF check will see open === true, revert the animation, and the
2363
- // drawer will close without animation when the parent eventually sets open={false}.
2364
- if (store.select('open')) {
2365
- const savedEvent = event;
2366
- controlledDismissFrame.request(() => {
2367
- if (store.select('open')) {
2368
- // Parent rejected: revert animation and restore snap point.
2369
- const pendingSnapPoint = pendingSwipeCloseSnapPointRef.current;
2370
- if (pendingSnapPoint !== undefined) {
2371
- setActiveSnapPoint?.(pendingSnapPoint, useButton.createChangeEventDetails(useButton.swipe, savedEvent));
2372
- }
2373
- pendingSwipeCloseSnapPointRef.current = undefined;
2374
- clearSwipeRelease();
2375
- resetSwipeRef.current?.();
2376
- } else {
2377
- // Parent accepted: clean up the ref.
2378
- pendingSwipeCloseSnapPointRef.current = undefined;
2379
- }
2380
- });
2381
- return;
2382
- }
2383
- pendingSwipeCloseSnapPointRef.current = undefined;
2384
- setSwipeDismissed(true);
2385
- }
747
+ onCancel: finishSwipeInteraction
2386
748
  });
2387
749
  const swipePointerProps = swipe.getPointerProps();
2388
750
  const swipeTouchProps = swipe.getTouchProps();
2389
- const {
2390
- moveNative: moveSwipeNative,
2391
- reset: resetSwipe
2392
- } = swipe;
2393
- resetSwipeRef.current = resetSwipe;
2394
- React__namespace.useEffect(() => {
2395
- const rootElement = viewportElement ?? popupElementState;
2396
- if (!rootElement) {
2397
- return undefined;
2398
- }
2399
- const resolvedRootElement = rootElement;
2400
- const doc = useButton.ownerDocument(resolvedRootElement);
2401
- const win = floatingUi_utils_dom.getWindow(doc);
2402
- function handleNativeTouchMove(event) {
2403
- // The virtual keyboard provider observes the move to tell a tap apart from a drag.
2404
- // It must run even when the swipe gesture below claims the event with
2405
- // `stopPropagation()`, which would otherwise prevent React's delegated handlers
2406
- // (and the provider) from ever seeing the move.
2407
- virtualKeyboard?.onTouchMove(event);
2408
- if (ignoreTouchSwipeRef.current) {
2409
- return;
2410
- }
2411
- const touchState = touchScrollStateRef.current;
2412
- const touch = event.touches[0];
2413
- if (!touch || !touchState) {
2414
- return;
2415
- }
2416
- const drawerAxisDelta = isVerticalScrollAxis ? touch.clientY - touchState.lastY : touch.clientX - touchState.lastX;
2417
-
2418
- // Preserve native range interaction by never locking touchmove for range inputs.
2419
- if (isEventOnRangeInput(event, win)) {
2420
- touchState.allowSwipe = false;
2421
- updateTouchScrollPosition(touchState, touch);
2422
- return;
2423
- }
2424
-
2425
- // Avoid blocking pinch zoom or text selection adjustments on iOS Safari.
2426
- if (event.touches.length === 2) {
2427
- updateTouchScrollPosition(touchState, touch);
2428
- return;
2429
- }
2430
- const allowTouchMove = shouldIgnoreSwipeForTextSelection(doc, resolvedRootElement);
2431
- if (allowTouchMove || !open || !mounted || nestedDrawerOpen) {
2432
- updateTouchScrollPosition(touchState, touch);
2433
- return;
2434
- }
2435
- if (preserveNativeCrossAxisScrollOnMove(touchState, touch, isVerticalScrollAxis)) {
2436
- updateTouchScrollPosition(touchState, touch);
2437
- return;
2438
- }
2439
- const scrollTarget = touchState.scrollTarget;
2440
- if (!scrollTarget || scrollTarget === doc.documentElement || scrollTarget === doc.body) {
2441
- if (event.cancelable) {
2442
- event.preventDefault();
2443
- }
2444
- // Claim the gesture before React's delegated touch handlers see it; dispatching the
2445
- // move through React re-rasterizes the popup content on every frame.
2446
- event.stopPropagation();
2447
- moveSwipeNative(event, resolvedRootElement);
2448
- updateTouchScrollPosition(touchState, touch);
2449
- return;
2450
- }
2451
- const hasScrollableContent = hasScrollableContentOnAxis(scrollTarget, scrollAxis);
2452
- if (!hasScrollableContent) {
2453
- // If the scroll container doesn't overflow on the drawer axis, prevent the window from
2454
- // scrolling instead.
2455
- if (event.cancelable) {
2456
- event.preventDefault();
2457
- }
2458
- event.stopPropagation();
2459
- updateTouchScrollPosition(touchState, touch);
2460
- return;
2461
- }
2462
- const delta = drawerAxisDelta;
2463
- if (delta !== 0) {
2464
- const canSwipeFromScrollEdge = canSwipeFromScrollEdgeOnMove(scrollTarget, scrollAxis, swipeDirection, delta);
2465
- if (!touchState.allowSwipe) {
2466
- if (!event.cancelable) {
2467
- touchState.allowSwipe = false;
2468
- } else if (canSwipeFromScrollEdge) {
2469
- touchState.allowSwipe = true;
2470
- event.preventDefault();
2471
- } else {
2472
- touchState.allowSwipe = false;
2473
- }
2474
- } else if (event.cancelable) {
2475
- event.preventDefault();
2476
- }
2477
- }
2478
- if (touchState.allowSwipe === true) {
2479
- event.stopPropagation();
2480
- moveSwipeNative(event, resolvedRootElement);
2481
- }
2482
- updateTouchScrollPosition(touchState, touch);
2483
- }
2484
- return useButton.addEventListener(doc, 'touchmove', handleNativeTouchMove, {
2485
- passive: false,
2486
- capture: true
2487
- });
2488
- }, [mounted, nestedDrawerOpen, open, popupElementState, isVerticalScrollAxis, scrollAxis, swipeDirection, moveSwipeNative, viewportElement, virtualKeyboard]);
2489
- React__namespace.useEffect(() => {
2490
- if (!snapPointRange || swipe.swiping) {
2491
- return;
2492
- }
2493
- const resolvedProgress = !open || nested ? 0 : snapPointProgress ?? 0;
2494
- applySwipeProgress({
2495
- resolvedProgress,
2496
- shouldTrackProgress: true,
2497
- notifyParent: false
2498
- });
2499
- }, [applySwipeProgress, frontmostHeight, nested, notifyParentSwipeProgressChange, open, snapPointProgress, snapPointRange, swipe.swiping, store, visualStateStore]);
2500
- React__namespace.useEffect(() => {
2501
- if (!notifyParentSwipeProgressChange) {
2502
- return undefined;
2503
- }
2504
- if (!open) {
2505
- notifyParentSwipeProgressChange(0);
2506
- }
2507
- return () => {
2508
- notifyParentSwipeProgressChange(0);
2509
- };
2510
- }, [notifyParentSwipeProgressChange, open]);
751
+ const resetSwipe = swipe.reset;
2511
752
  React__namespace.useEffect(() => {
2512
- if (open) {
753
+ if (!enabled) {
2513
754
  resetSwipe();
2514
- clearSwipeRelease();
755
+ resetDragDelta();
756
+ clearSwipeStyles();
757
+ resetSwipeInteractionState();
2515
758
  }
2516
- }, [clearSwipeRelease, open, resetSwipe]);
759
+ }, [clearSwipeStyles, enabled, resetDragDelta, resetSwipe]);
2517
760
  React__namespace.useEffect(() => {
2518
- const backdropElement = backdropRef.current;
2519
761
  return () => {
2520
- visualStateStore?.set({
2521
- swipeProgress: 0,
2522
- frontmostHeight: 0
2523
- });
2524
- setBackdropSwipingAttribute(backdropElement, false);
2525
- // `data-swiping` is set on whichever backdrop is current when a swipe starts, which can
2526
- // differ from the captured element if the backdrop mounted late or changed identity.
2527
- // Reading the live ref here is intentional so the current backdrop is cleared too.
2528
- // eslint-disable-next-line react-hooks/exhaustive-deps
2529
- const currentBackdrop = backdropRef.current;
2530
- if (currentBackdrop !== backdropElement) {
2531
- setBackdropSwipingAttribute(currentBackdrop, false);
2532
- }
2533
- finishNestedSwipe();
762
+ store.context.outsidePressEnabledRef.current = true;
2534
763
  };
2535
- }, [backdropRef, finishNestedSwipe, visualStateStore]);
2536
- const swipeProviderValue = React__namespace.useMemo(() => ({
764
+ }, [store]);
765
+ const state = {
766
+ open,
2537
767
  swiping: swipe.swiping,
2538
- getDragStyles: swipe.getDragStyles,
2539
- swipeStrength: swipeRelease ?? null,
2540
- setSwipeDismissed
2541
- }), [setSwipeDismissed, swipe.getDragStyles, swipe.swiping, swipeRelease]);
2542
- function resetTouchTrackingState() {
2543
- ignoreTouchSwipeRef.current = false;
2544
- touchScrollStateRef.current = null;
2545
- lastPointerTypeRef.current = '';
2546
- ignoreNextTouchStartFromPenRef.current = false;
2547
- }
2548
- return /*#__PURE__*/jsxRuntime.jsx(DrawerDescription.DialogViewport, {
2549
- ref: forwardedRef,
2550
- className: className,
2551
- style: style,
2552
- render: render,
2553
- ...useRenderElement.mergeProps(elementProps, {
768
+ swipeDirection: resolvedSwipeDirection,
769
+ disabled
770
+ };
771
+ return useRenderElement.useRenderElement('div', componentProps, {
772
+ state,
773
+ ref: [forwardedRef, swipeAreaRef, registerTrigger],
774
+ stateAttributesMapping: stateAttributesMapping$3,
775
+ props: [{
776
+ role: 'presentation',
777
+ 'aria-hidden': true,
778
+ style: {
779
+ pointerEvents: !enabled ? 'none' : undefined,
780
+ touchAction: resolveTouchAction(resolvedSwipeDirection)
781
+ },
2554
782
  onPointerDown(event) {
2555
- lastPointerTypeRef.current = event.pointerType;
2556
- ignoreNextTouchStartFromPenRef.current = event.pointerType === 'pen';
2557
- if (!open || !mounted || nestedDrawerOpen) {
2558
- return;
2559
- }
2560
- const doc = useButton.ownerDocument(event.currentTarget);
2561
- const elementAtPoint = getElementAtPoint(doc, event.clientX, event.clientY);
2562
- if (isSwipeIgnoredTarget(elementAtPoint) || isDrawerContentTarget(elementAtPoint)) {
2563
- return;
2564
- }
2565
783
  if (event.pointerType === 'touch') {
2566
784
  return;
2567
785
  }
2568
786
  swipePointerProps.onPointerDown?.(event);
787
+
788
+ // Prevent native text selection/drag gestures from competing with swipe-open dragging.
789
+ if (event.cancelable) {
790
+ event.preventDefault();
791
+ }
2569
792
  },
2570
793
  onPointerMove(event) {
2571
794
  if (event.pointerType === 'touch') {
@@ -2574,258 +797,23 @@ const DrawerViewport = /*#__PURE__*/React__namespace.forwardRef(function DrawerV
2574
797
  swipePointerProps.onPointerMove?.(event);
2575
798
  },
2576
799
  onPointerUp(event) {
2577
- if (lastPointerTypeRef.current === event.pointerType) {
2578
- lastPointerTypeRef.current = '';
2579
- }
2580
800
  if (event.pointerType === 'touch') {
2581
801
  return;
2582
802
  }
2583
803
  swipePointerProps.onPointerUp?.(event);
2584
804
  },
2585
805
  onPointerCancel(event) {
2586
- if (lastPointerTypeRef.current === event.pointerType) {
2587
- lastPointerTypeRef.current = '';
2588
- }
2589
806
  if (event.pointerType === 'touch') {
2590
807
  return;
2591
808
  }
2592
809
  swipePointerProps.onPointerCancel?.(event);
2593
- },
2594
- onTouchStart(event) {
2595
- const startedFromPenPointerDown = lastPointerTypeRef.current === 'pen' && ignoreNextTouchStartFromPenRef.current;
2596
- if (startedFromPenPointerDown) {
2597
- ignoreNextTouchStartFromPenRef.current = false;
2598
- ignoreTouchSwipeRef.current = false;
2599
- touchScrollStateRef.current = null;
2600
- return;
2601
- }
2602
- if (!open || !mounted || nestedDrawerOpen) {
2603
- ignoreTouchSwipeRef.current = false;
2604
- touchScrollStateRef.current = null;
2605
- return;
2606
- }
2607
- const touch = event.touches[0];
2608
- if (!touch) {
2609
- return;
2610
- }
2611
- if (isReactTouchEventOnRangeInput(event)) {
2612
- ignoreTouchSwipeRef.current = false;
2613
- touchScrollStateRef.current = null;
2614
- return;
2615
- }
2616
- const doc = useButton.ownerDocument(event.currentTarget);
2617
- const elementAtPoint = getElementAtPoint(doc, touch.clientX, touch.clientY);
2618
- const rootElement = viewportElement ?? popupElementState;
2619
- const eventTarget = useButton.getTarget(event.nativeEvent);
2620
- const target = floatingUi_utils_dom.isElement(eventTarget) ? eventTarget : null;
2621
- if (rootElement && target && !useButton.contains(rootElement, target)) {
2622
- ignoreTouchSwipeRef.current = true;
2623
- touchScrollStateRef.current = null;
2624
- return;
2625
- }
2626
- virtualKeyboard?.onTouchStart(event);
2627
- ignoreTouchSwipeRef.current = isSwipeIgnoredTarget(elementAtPoint);
2628
- if (ignoreTouchSwipeRef.current) {
2629
- touchScrollStateRef.current = null;
2630
- return;
2631
- }
2632
- let scrollTarget = null;
2633
- let hasCrossAxisScrollableContent = false;
2634
- if (rootElement && target) {
2635
- scrollTarget = findScrollableTouchTarget(target, rootElement, scrollAxis);
2636
- hasCrossAxisScrollableContent = findScrollableTouchTarget(target, rootElement, crossScrollAxis) != null;
2637
- }
2638
- let allowSwipe = null;
2639
- if (scrollTarget) {
2640
- const canSwipeFromEdge = isAtSwipeStartEdge(scrollTarget, scrollAxis, swipeDirection);
2641
- allowSwipe = canSwipeFromEdge ? null : false;
2642
- }
2643
- touchScrollStateRef.current = {
2644
- startX: touch.clientX,
2645
- startY: touch.clientY,
2646
- lastX: touch.clientX,
2647
- lastY: touch.clientY,
2648
- scrollTarget,
2649
- hasCrossAxisScrollableContent,
2650
- allowSwipe,
2651
- preserveNativeCrossAxisScroll: false
2652
- };
2653
- swipeTouchProps.onTouchStart?.(event);
2654
- },
2655
- onTouchEnd(event) {
2656
- virtualKeyboard?.onTouchEnd(event);
2657
- resetTouchTrackingState();
2658
- swipeTouchProps.onTouchEnd?.(event);
2659
- },
2660
- onTouchCancel(event) {
2661
- virtualKeyboard?.onTouchCancel();
2662
- resetTouchTrackingState();
2663
- swipeTouchProps.onTouchCancel?.(event);
2664
- },
2665
- // Drawer popups use drawer-specific nested state attributes.
2666
- // Suppress DialogViewport's generic nested dialog attribute.
2667
- ['data-nested-dialog-open']: undefined
2668
- }),
2669
- children: /*#__PURE__*/jsxRuntime.jsx(ScrollAreaViewport.DrawerViewportContext.Provider, {
2670
- value: swipeProviderValue,
2671
- children: children
2672
- })
810
+ }
811
+ }, swipeTouchProps, swipeAreaId ? {
812
+ id: swipeAreaId
813
+ } : undefined, elementProps]
2673
814
  });
2674
815
  });
2675
- if (process.env.NODE_ENV !== "production") DrawerViewport.displayName = "DrawerViewport";
2676
- function setSwipeDismissedElements(popupElement, backdropElement, dismissed) {
2677
- if (dismissed) {
2678
- popupElement?.setAttribute(ScrollAreaViewport.DrawerPopupDataAttributes.swipeDismiss, '');
2679
- backdropElement?.setAttribute(ScrollAreaViewport.DrawerPopupDataAttributes.swipeDismiss, '');
2680
- return;
2681
- }
2682
- popupElement?.removeAttribute(ScrollAreaViewport.DrawerPopupDataAttributes.swipeDismiss);
2683
- backdropElement?.removeAttribute(ScrollAreaViewport.DrawerPopupDataAttributes.swipeDismiss);
2684
- }
2685
- function setBackdropSwipingAttribute(backdropElement, swiping) {
2686
- if (!backdropElement) {
2687
- return;
2688
- }
2689
- if (swiping) {
2690
- backdropElement.setAttribute(ScrollAreaViewport.DrawerPopupDataAttributes.swiping, '');
2691
- return;
2692
- }
2693
- backdropElement.removeAttribute(ScrollAreaViewport.DrawerPopupDataAttributes.swiping);
2694
- }
2695
- function isSwipeIgnoredTarget(target) {
2696
- return Boolean(target?.closest(useScrollLock.BASE_UI_SWIPE_IGNORE_SELECTOR));
2697
- }
2698
- function isDrawerContentTarget(target) {
2699
- return Boolean(target?.closest(DRAWER_CONTENT_SELECTOR));
2700
- }
2701
- function getBaseSwipeThreshold(element, direction) {
2702
- const size = direction === 'left' || direction === 'right' ? element.offsetWidth : element.offsetHeight;
2703
- return Math.max(size * 0.5, MIN_SWIPE_THRESHOLD);
2704
- }
2705
- function isRangeInput(target, win) {
2706
- return target instanceof win.HTMLInputElement && target.type === 'range';
2707
- }
2708
- function isTextSelectionControl(target) {
2709
- if (!floatingUi_utils_dom.isElement(target)) {
2710
- return false;
2711
- }
2712
- return target.tagName === 'INPUT' || target.tagName === 'TEXTAREA';
2713
- }
2714
- function hasExpandedSelectionWithinTarget(selection, target) {
2715
- const anchorElement = floatingUi_utils_dom.isElement(selection.anchorNode) ? selection.anchorNode : selection.anchorNode?.parentElement;
2716
- const focusElement = floatingUi_utils_dom.isElement(selection.focusNode) ? selection.focusNode : selection.focusNode?.parentElement;
2717
- return selection.containsNode(target, true) || useButton.contains(target, anchorElement) || useButton.contains(target, focusElement);
2718
- }
2719
- function shouldIgnoreSwipeForTextSelection(doc, rootElement) {
2720
- const activeEl = useButton.activeElement(doc);
2721
- const activeElementWithinRoot = Boolean(activeEl && useButton.contains(rootElement, activeEl));
2722
- if (activeElementWithinRoot && isTextSelectionControl(activeEl)) {
2723
- const {
2724
- selectionStart,
2725
- selectionEnd
2726
- } = activeEl;
2727
- if (selectionStart != null && selectionEnd != null && selectionStart < selectionEnd) {
2728
- return true;
2729
- }
2730
- }
2731
- const selection = doc.getSelection?.();
2732
- if (!selection || selection.isCollapsed) {
2733
- return false;
2734
- }
2735
- return hasExpandedSelectionWithinTarget(selection, rootElement);
2736
- }
2737
- function isEventOnRangeInput(event, win) {
2738
- const composedPath = event.composedPath();
2739
- if (composedPath) {
2740
- return composedPath.some(pathTarget => isRangeInput(pathTarget, win));
2741
- }
2742
- return isRangeInput(useButton.getTarget(event), win);
2743
- }
2744
- function isReactTouchEventOnRangeInput(event) {
2745
- return isEventOnRangeInput(event.nativeEvent, floatingUi_utils_dom.getWindow(event.currentTarget));
2746
- }
2747
- function updateTouchScrollPosition(touchState, touch) {
2748
- touchState.lastX = touch.clientX;
2749
- touchState.lastY = touch.clientY;
2750
- }
2751
- function preserveNativeCrossAxisScrollOnMove(touchState, touch, isVerticalScrollAxis) {
2752
- if (touchState.preserveNativeCrossAxisScroll) {
2753
- return true;
2754
- }
2755
- if (touchState.allowSwipe === true || !touchState.hasCrossAxisScrollableContent) {
2756
- return false;
2757
- }
2758
- const drawerAxisGestureDelta = isVerticalScrollAxis ? touch.clientY - touchState.startY : touch.clientX - touchState.startX;
2759
- const crossAxisGestureDelta = isVerticalScrollAxis ? touch.clientX - touchState.startX : touch.clientY - touchState.startY;
2760
- const absDrawerAxisGestureDelta = Math.abs(drawerAxisGestureDelta);
2761
- const absCrossAxisGestureDelta = Math.abs(crossAxisGestureDelta);
2762
- if (absCrossAxisGestureDelta < 6 || absCrossAxisGestureDelta <= absDrawerAxisGestureDelta + 2) {
2763
- return false;
2764
- }
2765
- touchState.preserveNativeCrossAxisScroll = true;
2766
- return true;
2767
- }
2768
- function hasScrollableContentOnAxis(scrollTarget, axis) {
2769
- return axis === 'vertical' ? scrollTarget.scrollHeight > scrollTarget.clientHeight : scrollTarget.scrollWidth > scrollTarget.clientWidth;
2770
- }
2771
- function getScrollMetrics(scrollTarget, axis) {
2772
- if (axis === 'vertical') {
2773
- const max = Math.max(0, scrollTarget.scrollHeight - scrollTarget.clientHeight);
2774
- return {
2775
- offset: scrollTarget.scrollTop,
2776
- max
2777
- };
2778
- }
2779
- const max = Math.max(0, scrollTarget.scrollWidth - scrollTarget.clientWidth);
2780
- return {
2781
- offset: scrollTarget.scrollLeft,
2782
- max
2783
- };
2784
- }
2785
- function isAtSwipeStartEdge(scrollTarget, axis, direction) {
2786
- const {
2787
- offset,
2788
- max
2789
- } = getScrollMetrics(scrollTarget, axis);
2790
- const dismissFromStartEdge = shouldDismissFromStartEdge(direction, axis);
2791
- if (dismissFromStartEdge === null) {
2792
- return false;
2793
- }
2794
- return dismissFromStartEdge ? offset <= 0 : offset >= max;
2795
- }
2796
- function canSwipeFromScrollEdgeOnMove(scrollTarget, axis, direction, delta) {
2797
- const {
2798
- offset,
2799
- max
2800
- } = getScrollMetrics(scrollTarget, axis);
2801
- const dismissFromStartEdge = shouldDismissFromStartEdge(direction, axis);
2802
- if (dismissFromStartEdge === null) {
2803
- return false;
2804
- }
2805
- const movingTowardDismiss = dismissFromStartEdge ? delta > 0 : delta < 0;
2806
- if (!movingTowardDismiss) {
2807
- return false;
2808
- }
2809
- return dismissFromStartEdge ? offset <= 0 : offset >= max;
2810
- }
2811
- function shouldDismissFromStartEdge(direction, axis) {
2812
- if (axis === 'vertical') {
2813
- if (direction === 'down') {
2814
- return true;
2815
- }
2816
- if (direction === 'up') {
2817
- return false;
2818
- }
2819
- return null;
2820
- }
2821
- if (direction === 'right') {
2822
- return true;
2823
- }
2824
- if (direction === 'left') {
2825
- return false;
2826
- }
2827
- return null;
2828
- }
816
+ if (process.env.NODE_ENV !== "production") DrawerSwipeArea.displayName = "DrawerSwipeArea";
2829
817
 
2830
818
  let DrawerViewportCssVars = /*#__PURE__*/function (DrawerViewportCssVars) {
2831
819
  /**
@@ -3148,7 +1136,7 @@ function DrawerVirtualKeyboardProvider(props) {
3148
1136
  onTouchEnd,
3149
1137
  onTouchCancel: resetTouchTrackingState
3150
1138
  }), [onTouchEnd, onTouchMove, onTouchStart, resetTouchTrackingState]);
3151
- return /*#__PURE__*/jsxRuntime.jsx(DrawerVirtualKeyboardContext.Provider, {
1139
+ return /*#__PURE__*/jsxRuntime.jsx(ScrollAreaViewport.DrawerVirtualKeyboardContext.Provider, {
3152
1140
  value: contextValue,
3153
1141
  children: children
3154
1142
  });
@@ -3197,7 +1185,7 @@ function getContentEditableHost(element) {
3197
1185
  return host;
3198
1186
  }
3199
1187
  function resolveKeyboardTouchTargetFromPoint(doc, clientX, clientY) {
3200
- const exactTarget = getElementAtPoint(doc, clientX, clientY);
1188
+ const exactTarget = ScrollAreaViewport.getElementAtPoint(doc, clientX, clientY);
3201
1189
  const exactKeyboardTarget = resolveKeyboardInputTarget(exactTarget);
3202
1190
  if (exactKeyboardTarget) {
3203
1191
  return {
@@ -3216,7 +1204,7 @@ function resolveKeyboardTouchTargetFromPoint(doc, clientX, clientY) {
3216
1204
  return KEYBOARD_TAP_BLOCKED;
3217
1205
  }
3218
1206
  for (const [offsetX, offsetY] of [[0, INPUT_TAP_HIT_SLOP], [0, -INPUT_TAP_HIT_SLOP], [INPUT_TAP_HIT_SLOP, 0], [-INPUT_TAP_HIT_SLOP, 0]]) {
3219
- const keyboardTarget = resolveKeyboardInputTarget(getElementAtPoint(doc, clientX + offsetX, clientY + offsetY));
1207
+ const keyboardTarget = resolveKeyboardInputTarget(ScrollAreaViewport.getElementAtPoint(doc, clientX + offsetX, clientY + offsetY));
3220
1208
  if (keyboardTarget) {
3221
1209
  return {
3222
1210
  focusTarget: keyboardTarget,
@@ -3270,7 +1258,7 @@ function findKeyboardScrollTarget(target, root) {
3270
1258
  // that only becomes scrollable once keyboard slack is added (overflow intent without
3271
1259
  // current overflow).
3272
1260
  const scrollStart = floatingUi_utils_dom.getParentNode(target);
3273
- return findScrollableTouchTarget(scrollStart, root, 'vertical') ?? findScrollableTouchTarget(scrollStart, root, 'vertical', true);
1261
+ return ScrollAreaViewport.findScrollableTouchTarget(scrollStart, root, 'vertical') ?? ScrollAreaViewport.findScrollableTouchTarget(scrollStart, root, 'vertical', true);
3274
1262
  }
3275
1263
  function getKeyboardVisualViewport(win) {
3276
1264
  const visualViewport = win.visualViewport;
@@ -3311,7 +1299,7 @@ var index_parts$5 = /*#__PURE__*/Object.freeze({
3311
1299
  SwipeArea: DrawerSwipeArea,
3312
1300
  Title: DrawerTitle.DrawerTitle,
3313
1301
  Trigger: ScrollAreaViewport.DrawerTrigger,
3314
- Viewport: DrawerViewport,
1302
+ Viewport: ScrollAreaViewport.DrawerViewport,
3315
1303
  VirtualKeyboardProvider: DrawerVirtualKeyboardProvider,
3316
1304
  createHandle: DrawerDescription.createDialogHandle
3317
1305
  });