@jobber/components 8.21.1 → 8.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/Dialog.d.ts +1 -0
  2. package/Dialog.js +17 -0
  3. package/dist/BottomSheet-cjs.js +34 -7
  4. package/dist/BottomSheet-es.js +34 -8
  5. package/dist/Card/index.cjs +1 -0
  6. package/dist/Card/index.mjs +1 -0
  7. package/dist/ComboboxChipRemove-es.js +3 -3
  8. package/dist/DataDump/index.cjs +1 -0
  9. package/dist/DataDump/index.mjs +1 -0
  10. package/dist/Dialog/Dialog.d.ts +3 -0
  11. package/dist/Dialog/Dialog.types.d.ts +205 -0
  12. package/dist/Dialog/DialogBottomSheet.d.ts +28 -0
  13. package/dist/Dialog/DialogModal.d.ts +20 -0
  14. package/dist/Dialog/constants.d.ts +1 -0
  15. package/dist/Dialog/dialogComposableShared.d.ts +53 -0
  16. package/dist/Dialog/dialogReturnFocus.d.ts +43 -0
  17. package/dist/Dialog/index.cjs +32 -0
  18. package/dist/Dialog/index.d.ts +4 -0
  19. package/dist/Dialog/index.mjs +24 -0
  20. package/dist/DrawerDescription-cjs.js +409 -0
  21. package/dist/DrawerDescription-es.js +382 -0
  22. package/dist/DrawerTitle-cjs.js +116 -0
  23. package/dist/DrawerTitle-es.js +92 -0
  24. package/dist/InputNumberExperimental-es.js +1 -1
  25. package/dist/InternalBackdrop-es.js +2 -2
  26. package/dist/Menu/index.cjs +1 -0
  27. package/dist/Menu/index.mjs +1 -0
  28. package/dist/Menu-cjs.js +5 -3
  29. package/dist/Menu-es.js +5 -3
  30. package/dist/MenuSubmenuTrigger-cjs.js +49 -91
  31. package/dist/MenuSubmenuTrigger-es.js +51 -91
  32. package/dist/NumberFieldInput-es.js +1 -1
  33. package/dist/Page/index.cjs +1 -0
  34. package/dist/Page/index.mjs +1 -0
  35. package/dist/ScrollAreaViewport-cjs.js +938 -935
  36. package/dist/ScrollAreaViewport-es.js +938 -938
  37. package/dist/dialogReturnFocus-cjs.js +427 -0
  38. package/dist/dialogReturnFocus-es.js +423 -0
  39. package/dist/index.cjs +13 -7
  40. package/dist/index.d.mts +1 -0
  41. package/dist/index.d.ts +1 -0
  42. package/dist/index.mjs +10 -7
  43. package/dist/primitives/BottomSheet/BottomSheet.d.ts +19 -1
  44. package/dist/primitives/BottomSheet/BottomSheet.types.d.ts +8 -0
  45. package/dist/primitives/BottomSheet/index.cjs +1 -0
  46. package/dist/primitives/BottomSheet/index.d.ts +1 -1
  47. package/dist/primitives/BottomSheet/index.mjs +1 -1
  48. package/dist/primitives/index.cjs +1 -0
  49. package/dist/primitives/index.mjs +1 -1
  50. package/dist/styles.css +360 -0
  51. package/dist/unstyledPrimitives/index.cjs +32 -249
  52. package/dist/unstyledPrimitives/index.d.ts +1 -0
  53. package/dist/unstyledPrimitives/index.mjs +30 -248
  54. package/dist/useButton-es.js +1 -1
  55. package/dist/useCompositeListItem-es.js +2 -2
  56. package/dist/useLabel-es.js +1 -1
  57. package/dist/utils/meta/meta.json +11 -1
  58. package/package.json +2 -2
@@ -414,179 +414,6 @@ function useDialogRootContext(optional) {
414
414
  return dialogRootContext;
415
415
  }
416
416
 
417
- let DrawerPopupCssVars = /*#__PURE__*/function (DrawerPopupCssVars) {
418
- /**
419
- * The number of nested drawers that are currently open.
420
- * @type {number}
421
- */
422
- DrawerPopupCssVars["nestedDrawers"] = "--nested-drawers";
423
- /**
424
- * The height of the drawer popup.
425
- * @type {CSS length}
426
- */
427
- DrawerPopupCssVars["height"] = "--drawer-height";
428
- /**
429
- * The height of the frontmost open drawer in the current nested drawer stack.
430
- * @type {CSS length}
431
- */
432
- DrawerPopupCssVars["frontmostHeight"] = "--drawer-frontmost-height";
433
- /**
434
- * The swipe movement on the X axis.
435
- * @type {CSS length}
436
- */
437
- DrawerPopupCssVars["swipeMovementX"] = "--drawer-swipe-movement-x";
438
- /**
439
- * The swipe movement on the Y axis.
440
- * @type {CSS length}
441
- */
442
- DrawerPopupCssVars["swipeMovementY"] = "--drawer-swipe-movement-y";
443
- /**
444
- * The snap point offset used for translating the drawer.
445
- * @type {CSS length}
446
- */
447
- DrawerPopupCssVars["snapPointOffset"] = "--drawer-snap-point-offset";
448
- /**
449
- * A scalar (0.1-1) used to scale the swipe release transition duration in CSS.
450
- * @type {number}
451
- */
452
- DrawerPopupCssVars["swipeStrength"] = "--drawer-swipe-strength";
453
- return DrawerPopupCssVars;
454
- }({});
455
-
456
- let DrawerBackdropCssVars = /*#__PURE__*/function (DrawerBackdropCssVars) {
457
- /**
458
- * The swipe progress of the drawer gesture.
459
- * @type {number}
460
- */
461
- DrawerBackdropCssVars["swipeProgress"] = "--drawer-swipe-progress";
462
- return DrawerBackdropCssVars;
463
- }({});
464
-
465
- const stateAttributesMapping$1 = {
466
- ...InternalBackdrop.popupStateMapping,
467
- ...useButton.transitionStatusMapping
468
- };
469
-
470
- /**
471
- * An overlay displayed beneath the popup.
472
- * Renders a `<div>` element.
473
- *
474
- * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)
475
- */
476
- const DrawerBackdrop = /*#__PURE__*/React__namespace.forwardRef(function DrawerBackdrop(componentProps, forwardedRef) {
477
- const {
478
- render,
479
- className,
480
- forceRender = false,
481
- ...elementProps
482
- } = componentProps;
483
- const {
484
- store
485
- } = useDialogRootContext();
486
- const open = store.useState('open');
487
- const nested = store.useState('nested');
488
- const mounted = store.useState('mounted');
489
- const transitionStatus = store.useState('transitionStatus');
490
- const state = {
491
- open,
492
- transitionStatus
493
- };
494
- return useRenderElement.useRenderElement('div', componentProps, {
495
- state,
496
- ref: [store.context.backdropRef, forwardedRef],
497
- stateAttributesMapping: stateAttributesMapping$1,
498
- props: [{
499
- role: 'presentation',
500
- hidden: !mounted,
501
- style: {
502
- pointerEvents: !open ? 'none' : undefined,
503
- userSelect: 'none',
504
- WebkitUserSelect: 'none',
505
- [DrawerBackdropCssVars.swipeProgress]: '0',
506
- [DrawerPopupCssVars.swipeStrength]: '1'
507
- }
508
- }, elementProps],
509
- enabled: forceRender || !nested
510
- });
511
- });
512
- if (process.env.NODE_ENV !== "production") DrawerBackdrop.displayName = "DrawerBackdrop";
513
-
514
- const DRAWER_CONTENT_ATTRIBUTE = 'data-drawer-content';
515
-
516
- /**
517
- * A container for the drawer contents.
518
- * Renders a `<div>` element.
519
- *
520
- * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)
521
- */
522
- const DrawerContent = /*#__PURE__*/React__namespace.forwardRef(function DrawerContent(componentProps, forwardedRef) {
523
- const {
524
- render,
525
- className,
526
- ...elementProps
527
- } = componentProps;
528
- useDialogRootContext();
529
- return useRenderElement.useRenderElement('div', componentProps, {
530
- ref: forwardedRef,
531
- props: [{
532
- [DRAWER_CONTENT_ATTRIBUTE]: ''
533
- }, elementProps]
534
- });
535
- });
536
- if (process.env.NODE_ENV !== "production") DrawerContent.displayName = "DrawerContent";
537
-
538
- const DrawerProviderContext = /*#__PURE__*/React__namespace.createContext(undefined);
539
- if (process.env.NODE_ENV !== "production") DrawerProviderContext.displayName = "DrawerProviderContext";
540
- function useDrawerProviderContext(optional) {
541
- const context = React__namespace.useContext(DrawerProviderContext);
542
- return context;
543
- }
544
-
545
- let DrawerPopupDataAttributes = function (DrawerPopupDataAttributes) {
546
- /**
547
- * Present when the drawer is open.
548
- */
549
- DrawerPopupDataAttributes[DrawerPopupDataAttributes["open"] = InternalBackdrop.CommonPopupDataAttributes.open] = "open";
550
- /**
551
- * Present when the drawer is closed.
552
- */
553
- DrawerPopupDataAttributes[DrawerPopupDataAttributes["closed"] = InternalBackdrop.CommonPopupDataAttributes.closed] = "closed";
554
- /**
555
- * Present when the drawer is animating in.
556
- */
557
- DrawerPopupDataAttributes[DrawerPopupDataAttributes["startingStyle"] = InternalBackdrop.CommonPopupDataAttributes.startingStyle] = "startingStyle";
558
- /**
559
- * Present when the drawer is animating out.
560
- */
561
- DrawerPopupDataAttributes[DrawerPopupDataAttributes["endingStyle"] = InternalBackdrop.CommonPopupDataAttributes.endingStyle] = "endingStyle";
562
- /**
563
- * Present when the drawer is at the expanded (full-height) snap point.
564
- */
565
- DrawerPopupDataAttributes["expanded"] = "data-expanded";
566
- /**
567
- * Present when a nested drawer is open.
568
- */
569
- DrawerPopupDataAttributes["nestedDrawerOpen"] = "data-nested-drawer-open";
570
- /**
571
- * Present when a nested drawer is being swiped.
572
- */
573
- DrawerPopupDataAttributes["nestedDrawerSwiping"] = "data-nested-drawer-swiping";
574
- /**
575
- * Present when the drawer is dismissed by swiping.
576
- */
577
- DrawerPopupDataAttributes["swipeDismiss"] = "data-swipe-dismiss";
578
- /**
579
- * Indicates the swipe direction.
580
- * @type {'up' | 'down' | 'left' | 'right'}
581
- */
582
- DrawerPopupDataAttributes["swipeDirection"] = "data-swipe-direction";
583
- /**
584
- * Present when the drawer is being swiped.
585
- */
586
- DrawerPopupDataAttributes["swiping"] = "data-swiping";
587
- return DrawerPopupDataAttributes;
588
- }({});
589
-
590
417
  const DialogPortalContext = /*#__PURE__*/React__namespace.createContext(undefined);
591
418
  if (process.env.NODE_ENV !== "production") DialogPortalContext.displayName = "DialogPortalContext";
592
419
  function useDialogPortalContext() {
@@ -609,852 +436,1025 @@ const ARROW_KEYS = new Set([...HORIZONTAL_KEYS, ...VERTICAL_KEYS]);
609
436
  new Set([...ARROW_KEYS, HOME, END]);
610
437
  const COMPOSITE_KEYS = new Set([ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, HOME, END]);
611
438
 
612
- const DrawerRootContext = /*#__PURE__*/React__namespace.createContext(undefined);
613
- if (process.env.NODE_ENV !== "production") DrawerRootContext.displayName = "DrawerRootContext";
614
- function useDrawerRootContext(optional) {
615
- const drawerRootContext = React__namespace.useContext(DrawerRootContext);
616
- if (optional === false && drawerRootContext === undefined) {
617
- throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: DrawerRootContext is missing. Drawer parts must be placed within <Drawer.Root>.' : useRenderElement.formatErrorMessage(90));
618
- }
619
- return drawerRootContext;
620
- }
621
-
622
- function resolveSnapPointValue(snapPoint, viewportHeight, rootFontSize) {
623
- if (!Number.isFinite(viewportHeight) || viewportHeight <= 0) {
624
- return null;
625
- }
626
- if (typeof snapPoint === 'number') {
627
- if (!Number.isFinite(snapPoint)) {
628
- return null;
629
- }
630
- if (snapPoint <= 1) {
631
- return clamp.clamp(snapPoint, 0, 1) * viewportHeight;
632
- }
633
- return snapPoint;
634
- }
635
- const trimmed = snapPoint.trim();
636
- if (trimmed.endsWith('px')) {
637
- const value = Number.parseFloat(trimmed);
638
- return Number.isFinite(value) ? value : null;
639
- }
640
- if (trimmed.endsWith('rem')) {
641
- const value = Number.parseFloat(trimmed);
642
- return Number.isFinite(value) ? value * rootFontSize : null;
643
- }
644
- return null;
645
- }
646
- function findClosestSnapPoint(height, points) {
647
- let closest = null;
648
- let closestDistance = Infinity;
649
- for (const point of points) {
650
- const distance = Math.abs(point.height - height);
651
- if (distance < closestDistance) {
652
- closestDistance = distance;
653
- closest = point;
654
- }
655
- }
656
- return closest;
657
- }
658
- function useDrawerSnapPoints() {
439
+ const DialogPortal = /*#__PURE__*/React__namespace.forwardRef(function DialogPortal(props, forwardedRef) {
440
+ const {
441
+ keepMounted = false,
442
+ ...portalProps
443
+ } = props;
659
444
  const {
660
445
  store
661
446
  } = useDialogRootContext();
447
+ const mounted = store.useState('mounted');
448
+ const modal = store.useState('modal');
449
+ const open = store.useState('open');
450
+ const shouldRender = mounted || keepMounted;
451
+ if (!shouldRender) {
452
+ return null;
453
+ }
454
+ return /*#__PURE__*/jsxRuntime.jsx(DialogPortalContext.Provider, {
455
+ value: keepMounted,
456
+ children: /*#__PURE__*/jsxRuntime.jsxs(InternalBackdrop.FloatingPortal, {
457
+ ref: forwardedRef,
458
+ ...portalProps,
459
+ children: [mounted && modal === true && /*#__PURE__*/jsxRuntime.jsx(InternalBackdrop.InternalBackdrop, {
460
+ ref: store.context.internalBackdropRef,
461
+ inert: InternalBackdrop.inertValue(!open)
462
+ }), props.children]
463
+ })
464
+ });
465
+ });
466
+ if (process.env.NODE_ENV !== "production") DialogPortal.displayName = "DialogPortal";
467
+
468
+ function useDialogRoot(params) {
662
469
  const {
663
- snapPoints,
664
- activeSnapPoint,
665
- setActiveSnapPoint,
666
- popupHeight
667
- } = useDrawerRootContext();
668
- const viewportElement = store.useState('viewportElement');
669
- const [viewportHeight, setViewportHeight] = React__namespace.useState(0);
670
- const [rootFontSize, setRootFontSize] = React__namespace.useState(16);
671
- const measureViewportHeight = useButton.useStableCallback(() => {
672
- const doc = useButton.ownerDocument(viewportElement);
673
- const html = doc.documentElement;
674
- if (viewportElement) {
675
- setViewportHeight(viewportElement.offsetHeight);
676
- }
677
- if (!viewportElement) {
678
- setViewportHeight(html.clientHeight);
679
- }
680
- const fontSize = parseFloat(getComputedStyle(html).fontSize);
681
- if (Number.isFinite(fontSize)) {
682
- setRootFontSize(fontSize);
683
- }
684
- });
685
- useButton.useIsoLayoutEffect(() => {
686
- measureViewportHeight();
687
- if (!viewportElement || typeof ResizeObserver !== 'function') {
688
- return undefined;
689
- }
690
- const resizeObserver = new ResizeObserver(measureViewportHeight);
691
- resizeObserver.observe(viewportElement);
692
- return () => {
693
- resizeObserver.disconnect();
470
+ store,
471
+ parentContext,
472
+ actionsRef
473
+ } = params;
474
+ const open = store.useState('open');
475
+ const disablePointerDismissal = store.useState('disablePointerDismissal');
476
+ const modal = store.useState('modal');
477
+ const popupElement = store.useState('popupElement');
478
+ const {
479
+ openMethod,
480
+ triggerProps
481
+ } = InternalBackdrop.useOpenInteractionType(open);
482
+ useImplicitActiveTrigger(store);
483
+ const {
484
+ forceUnmount
485
+ } = useOpenStateTransitions(open, store);
486
+ const createDialogEventDetails = useButton.useStableCallback(reason => {
487
+ const details = useButton.createChangeEventDetails(reason);
488
+ details.preventUnmountOnClose = () => {
489
+ store.set('preventUnmountingOnClose', true);
694
490
  };
695
- }, [measureViewportHeight, viewportElement]);
696
- const resolvedSnapPoints = React__namespace.useMemo(() => {
697
- if (!snapPoints || snapPoints.length === 0 || viewportHeight <= 0 || popupHeight <= 0) {
698
- return [];
699
- }
700
- const maxHeight = Math.min(popupHeight, viewportHeight);
701
- if (!Number.isFinite(maxHeight) || maxHeight <= 0) {
702
- return [];
703
- }
704
- const resolved = snapPoints.map(value => {
705
- const resolvedHeight = resolveSnapPointValue(value, viewportHeight, rootFontSize);
706
- if (resolvedHeight === null || !Number.isFinite(resolvedHeight)) {
707
- return null;
491
+ return details;
492
+ });
493
+ const handleImperativeClose = React__namespace.useCallback(() => {
494
+ store.setOpen(false, createDialogEventDetails(useButton.imperativeAction));
495
+ }, [store, createDialogEventDetails]);
496
+ React__namespace.useImperativeHandle(actionsRef, () => ({
497
+ unmount: forceUnmount,
498
+ close: handleImperativeClose
499
+ }), [forceUnmount, handleImperativeClose]);
500
+ const floatingRootContext = useSyncedFloatingRootContext({
501
+ popupStore: store,
502
+ onOpenChange: store.setOpen,
503
+ treatPopupAsFloatingElement: true,
504
+ noEmit: true
505
+ });
506
+ const [ownNestedOpenDialogs, setOwnNestedOpenDialogs] = React__namespace.useState(0);
507
+ const isTopmost = ownNestedOpenDialogs === 0;
508
+ const role = useRole(floatingRootContext);
509
+ const dismiss = InternalBackdrop.useDismiss(floatingRootContext, {
510
+ outsidePressEvent() {
511
+ if (store.context.internalBackdropRef.current || store.context.backdropRef.current) {
512
+ return 'intentional';
708
513
  }
709
- const clampedHeight = clamp.clamp(resolvedHeight, 0, maxHeight);
514
+ // Ensure `aria-hidden` on outside elements is removed immediately
515
+ // on outside press when trapping focus.
710
516
  return {
711
- value,
712
- height: clampedHeight,
713
- offset: Math.max(0, popupHeight - clampedHeight)
517
+ mouse: modal === 'trap-focus' ? 'sloppy' : 'intentional',
518
+ touch: 'sloppy'
714
519
  };
715
- }).filter(point => Boolean(point));
716
- if (resolved.length <= 1) {
717
- return resolved;
718
- }
719
- const deduped = [];
720
- const seenHeights = [];
721
- for (let index = resolved.length - 1; index >= 0; index -= 1) {
722
- const point = resolved[index];
723
- const isDuplicate = seenHeights.some(height => Math.abs(height - point.height) <= 1);
724
- if (isDuplicate) {
725
- continue;
520
+ },
521
+ outsidePress(event) {
522
+ if (!store.context.outsidePressEnabledRef.current) {
523
+ return false;
726
524
  }
727
- seenHeights.push(point.height);
728
- deduped.push(point);
525
+
526
+ // For mouse events, only accept left button (button 0)
527
+ // For touch events, a single touch is equivalent to left button
528
+ if ('button' in event && event.button !== 0) {
529
+ return false;
530
+ }
531
+ if ('touches' in event && event.touches.length !== 1) {
532
+ return false;
533
+ }
534
+ const target = useButton.getTarget(event);
535
+ if (isTopmost && !disablePointerDismissal) {
536
+ const eventTarget = target;
537
+ // Only close if the click occurred on the dialog's owning backdrop.
538
+ // This supports multiple modal dialogs that aren't nested in the React tree:
539
+ // https://github.com/mui/base-ui/issues/1320
540
+ if (modal) {
541
+ return store.context.internalBackdropRef.current || store.context.backdropRef.current ? store.context.internalBackdropRef.current === eventTarget || store.context.backdropRef.current === eventTarget || useButton.contains(eventTarget, popupElement) && !eventTarget?.hasAttribute('data-base-ui-portal') : true;
542
+ }
543
+ return true;
544
+ }
545
+ return false;
546
+ },
547
+ escapeKey: isTopmost
548
+ });
549
+ InternalBackdrop.useScrollLock(open && modal === true, popupElement);
550
+ const {
551
+ getReferenceProps,
552
+ getFloatingProps,
553
+ getTriggerProps
554
+ } = InternalBackdrop.useInteractions([role, dismiss]);
555
+
556
+ // Listen for nested open/close events on this store to maintain the count
557
+ store.useContextCallback('onNestedDialogOpen', ownChildrenCount => {
558
+ setOwnNestedOpenDialogs(ownChildrenCount + 1);
559
+ });
560
+ store.useContextCallback('onNestedDialogClose', () => {
561
+ setOwnNestedOpenDialogs(0);
562
+ });
563
+
564
+ // Notify parent of our open/close state using parent callbacks, if any
565
+ React__namespace.useEffect(() => {
566
+ if (parentContext?.onNestedDialogOpen && open) {
567
+ parentContext.onNestedDialogOpen(ownNestedOpenDialogs);
729
568
  }
730
- deduped.reverse();
731
- return deduped;
732
- }, [popupHeight, rootFontSize, snapPoints, viewportHeight]);
733
- const resolvedActiveSnapPoint = React__namespace.useMemo(() => {
734
- if (activeSnapPoint === undefined) {
735
- return resolvedSnapPoints[0];
569
+ if (parentContext?.onNestedDialogClose && !open) {
570
+ parentContext.onNestedDialogClose();
736
571
  }
737
- if (activeSnapPoint === null) {
738
- return undefined;
572
+ return () => {
573
+ if (parentContext?.onNestedDialogClose && open) {
574
+ parentContext.onNestedDialogClose();
575
+ }
576
+ };
577
+ }, [open, parentContext, ownNestedOpenDialogs]);
578
+ const activeTriggerProps = React__namespace.useMemo(() => getReferenceProps(triggerProps), [getReferenceProps, triggerProps]);
579
+ const inactiveTriggerProps = React__namespace.useMemo(() => getTriggerProps(triggerProps), [getTriggerProps, triggerProps]);
580
+ const popupProps = React__namespace.useMemo(() => getFloatingProps(), [getFloatingProps]);
581
+ store.useSyncedValues({
582
+ openMethod,
583
+ activeTriggerProps,
584
+ inactiveTriggerProps,
585
+ popupProps,
586
+ floatingRootContext,
587
+ nestedOpenDialogCount: ownNestedOpenDialogs
588
+ });
589
+ }
590
+
591
+ const selectors = {
592
+ ...popupStoreSelectors,
593
+ modal: InternalBackdrop.createSelector(state => state.modal),
594
+ nested: InternalBackdrop.createSelector(state => state.nested),
595
+ nestedOpenDialogCount: InternalBackdrop.createSelector(state => state.nestedOpenDialogCount),
596
+ disablePointerDismissal: InternalBackdrop.createSelector(state => state.disablePointerDismissal),
597
+ openMethod: InternalBackdrop.createSelector(state => state.openMethod),
598
+ descriptionElementId: InternalBackdrop.createSelector(state => state.descriptionElementId),
599
+ titleElementId: InternalBackdrop.createSelector(state => state.titleElementId),
600
+ viewportElement: InternalBackdrop.createSelector(state => state.viewportElement),
601
+ role: InternalBackdrop.createSelector(state => state.role)
602
+ };
603
+ class DialogStore extends InternalBackdrop.ReactStore {
604
+ constructor(initialState) {
605
+ super(createInitialState(initialState), {
606
+ popupRef: /*#__PURE__*/React__namespace.createRef(),
607
+ backdropRef: /*#__PURE__*/React__namespace.createRef(),
608
+ internalBackdropRef: /*#__PURE__*/React__namespace.createRef(),
609
+ outsidePressEnabledRef: {
610
+ current: true
611
+ },
612
+ triggerElements: new InternalBackdrop.PopupTriggerMap(),
613
+ onOpenChange: undefined,
614
+ onOpenChangeComplete: undefined
615
+ }, selectors);
616
+ }
617
+ setOpen = (nextOpen, eventDetails) => {
618
+ eventDetails.preventUnmountOnClose = () => {
619
+ this.set('preventUnmountingOnClose', true);
620
+ };
621
+ if (!nextOpen && eventDetails.trigger == null && this.state.activeTriggerId != null) {
622
+ // When closing the dialog, pass the old trigger to the onOpenChange event
623
+ // so it's not reset too early (potentially causing focus issues in controlled scenarios).
624
+ eventDetails.trigger = this.state.activeTriggerElement ?? undefined;
739
625
  }
740
- const exactMatch = resolvedSnapPoints.find(point => Object.is(point.value, activeSnapPoint));
741
- if (exactMatch) {
742
- return exactMatch;
626
+ this.context.onOpenChange?.(nextOpen, eventDetails);
627
+ if (eventDetails.isCanceled) {
628
+ return;
743
629
  }
744
- const maxHeight = Math.min(popupHeight, viewportHeight);
745
- const resolvedHeight = resolveSnapPointValue(activeSnapPoint, viewportHeight, rootFontSize);
746
- if (resolvedHeight === null || !Number.isFinite(resolvedHeight)) {
747
- return undefined;
630
+ const details = {
631
+ open: nextOpen,
632
+ nativeEvent: eventDetails.event,
633
+ reason: eventDetails.reason,
634
+ nested: this.state.nested
635
+ };
636
+ this.state.floatingRootContext.context.events?.emit('openchange', details);
637
+ const updatedState = {
638
+ open: nextOpen
639
+ };
640
+
641
+ // If a popup is closing, the `trigger` may be null.
642
+ // We want to keep the previous value so that exit animations are played and focus is returned correctly.
643
+ const newTriggerId = eventDetails.trigger?.id ?? null;
644
+ if (newTriggerId || nextOpen) {
645
+ updatedState.activeTriggerId = newTriggerId;
646
+ updatedState.activeTriggerElement = eventDetails.trigger ?? null;
748
647
  }
749
- const clampedHeight = clamp.clamp(resolvedHeight, 0, maxHeight);
750
- return findClosestSnapPoint(clampedHeight, resolvedSnapPoints) ?? undefined;
751
- }, [activeSnapPoint, popupHeight, resolvedSnapPoints, rootFontSize, viewportHeight]);
752
- return {
753
- snapPoints,
754
- activeSnapPoint,
755
- setActiveSnapPoint,
756
- popupHeight,
757
- viewportHeight,
758
- resolvedSnapPoints,
759
- activeSnapPointOffset: resolvedActiveSnapPoint?.offset ?? null
648
+ this.update(updatedState);
649
+ };
650
+ }
651
+ function createInitialState(initialState = {}) {
652
+ return {
653
+ ...createInitialPopupStoreState(),
654
+ modal: true,
655
+ disablePointerDismissal: false,
656
+ popupElement: null,
657
+ viewportElement: null,
658
+ descriptionElementId: undefined,
659
+ titleElementId: undefined,
660
+ openMethod: null,
661
+ nested: false,
662
+ nestedOpenDialogCount: 0,
663
+ role: 'dialog',
664
+ ...initialState
760
665
  };
761
666
  }
762
-
763
- const DrawerViewportContext = /*#__PURE__*/React__namespace.createContext(null);
764
- if (process.env.NODE_ENV !== "production") DrawerViewportContext.displayName = "DrawerViewportContext";
765
- function useDrawerViewportContext(optional) {
766
- const context = React__namespace.useContext(DrawerViewportContext);
767
- return context;
768
- }
769
-
770
- let drawerSwipeVarsRegistered = false;
771
667
 
772
668
  /**
773
- * Removes inheritance of high-frequency drawer swipe CSS variables, which
774
- * reduces style recalculation cost in complex drawers with deep subtrees.
775
- * Child elements that need these values can still opt-in by using `inherit`.
776
- * See https://motion.dev/blog/web-animation-performance-tier-list
777
- * under the "Improving CSS variable performance" section.
669
+ * Groups all parts of the dialog.
670
+ * Doesn’t render its own HTML element.
671
+ *
672
+ * Documentation: [Base UI Dialog](https://base-ui.com/react/components/dialog)
778
673
  */
779
- function removeCSSVariableInheritance$1() {
780
- if (drawerSwipeVarsRegistered) {
781
- return;
782
- }
783
-
784
- // Intentionally keep inheritance disabled on WebKit as well. Safari doesn't support
785
- // opting descendants back in via `--var: inherit` for custom properties registered
786
- // with `inherits: false`, but Drawer does not rely on descendant access to these vars
787
- // (unlike ScrollArea), so we keep the performance optimization enabled.
788
- if (typeof CSS !== 'undefined' && 'registerProperty' in CSS) {
789
- [DrawerPopupCssVars.swipeMovementX, DrawerPopupCssVars.swipeMovementY, DrawerPopupCssVars.snapPointOffset].forEach(name => {
790
- try {
791
- CSS.registerProperty({
792
- name,
793
- syntax: '<length>',
794
- inherits: false,
795
- initialValue: '0px'
796
- });
797
- } catch {
798
- /* ignore already-registered */
799
- }
800
- });
801
- [{
802
- name: DrawerBackdropCssVars.swipeProgress,
803
- initialValue: '0'
804
- }, {
805
- name: DrawerPopupCssVars.swipeStrength,
806
- initialValue: '1'
807
- }].forEach(({
808
- name,
809
- initialValue
810
- }) => {
811
- try {
812
- CSS.registerProperty({
813
- name,
814
- syntax: '<number>',
815
- inherits: false,
816
- initialValue
817
- });
818
- } catch {
819
- /* ignore already-registered */
820
- }
674
+ function DialogRoot(props) {
675
+ const {
676
+ children,
677
+ open: openProp,
678
+ defaultOpen = false,
679
+ onOpenChange,
680
+ onOpenChangeComplete,
681
+ disablePointerDismissal = false,
682
+ modal = true,
683
+ actionsRef,
684
+ handle,
685
+ triggerId: triggerIdProp,
686
+ defaultTriggerId: defaultTriggerIdProp = null
687
+ } = props;
688
+ const parentDialogRootContext = useDialogRootContext(true);
689
+ const nested = Boolean(parentDialogRootContext);
690
+ const store = useRenderElement.useRefWithInit(() => {
691
+ return handle?.store ?? new DialogStore({
692
+ open: defaultOpen,
693
+ openProp,
694
+ activeTriggerId: defaultTriggerIdProp,
695
+ triggerIdProp,
696
+ modal,
697
+ disablePointerDismissal,
698
+ nested
821
699
  });
822
- }
823
- drawerSwipeVarsRegistered = true;
700
+ }).current;
701
+
702
+ // Support initially open state when uncontrolled
703
+ InternalBackdrop.useOnFirstRender(() => {
704
+ if (openProp === undefined && store.state.open === false && defaultOpen === true) {
705
+ store.update({
706
+ open: true,
707
+ activeTriggerId: defaultTriggerIdProp
708
+ });
709
+ }
710
+ });
711
+ store.useControlledProp('openProp', openProp);
712
+ store.useControlledProp('triggerIdProp', triggerIdProp);
713
+ store.useSyncedValues({
714
+ disablePointerDismissal,
715
+ nested,
716
+ modal
717
+ });
718
+ store.useContextCallback('onOpenChange', onOpenChange);
719
+ store.useContextCallback('onOpenChangeComplete', onOpenChangeComplete);
720
+ const payload = store.useState('payload');
721
+ useDialogRoot({
722
+ store,
723
+ actionsRef,
724
+ parentContext: parentDialogRootContext?.store.context,
725
+ onOpenChange,
726
+ triggerIdProp
727
+ });
728
+ const contextValue = React__namespace.useMemo(() => ({
729
+ store
730
+ }), [store]);
731
+ return /*#__PURE__*/jsxRuntime.jsx(DialogRootContext.Provider, {
732
+ value: contextValue,
733
+ children: typeof children === 'function' ? children({
734
+ payload
735
+ }) : children
736
+ });
824
737
  }
825
- const stateAttributesMapping = {
826
- ...InternalBackdrop.popupStateMapping,
827
- ...useButton.transitionStatusMapping,
828
- expanded(value) {
829
- return value ? {
830
- [DrawerPopupDataAttributes.expanded]: ''
831
- } : null;
832
- },
833
- nestedDrawerOpen(value) {
834
- return value ? {
835
- [DrawerPopupDataAttributes.nestedDrawerOpen]: ''
836
- } : null;
837
- },
838
- nestedDrawerSwiping(value) {
839
- return value ? {
840
- [DrawerPopupDataAttributes.nestedDrawerSwiping]: ''
841
- } : null;
842
- },
843
- swipeDirection(value) {
844
- return value ? {
845
- [DrawerPopupDataAttributes.swipeDirection]: value
846
- } : null;
847
- },
848
- swiping(value) {
849
- return value ? {
850
- [DrawerPopupDataAttributes.swiping]: ''
851
- } : null;
852
- }
853
- };
854
738
 
855
739
  /**
856
- * A container for the drawer contents.
857
- * Renders a `<div>` element.
740
+ * A button that opens the dialog.
741
+ * Renders a `<button>` element.
858
742
  *
859
- * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)
743
+ * Documentation: [Base UI Dialog](https://base-ui.com/react/components/dialog)
860
744
  */
861
- const DrawerPopup = /*#__PURE__*/React__namespace.forwardRef(function DrawerPopup(componentProps, forwardedRef) {
745
+ const DialogTrigger = /*#__PURE__*/React__namespace.forwardRef(function DialogTrigger(componentProps, forwardedRef) {
862
746
  const {
863
- className,
864
- finalFocus,
865
- initialFocus,
866
747
  render,
748
+ className,
749
+ disabled = false,
750
+ nativeButton = true,
751
+ id: idProp,
752
+ payload,
753
+ handle,
867
754
  ...elementProps
868
755
  } = componentProps;
756
+ const dialogRootContext = useDialogRootContext(true);
757
+ const store = handle?.store ?? dialogRootContext?.store;
758
+ if (!store) {
759
+ throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: <Dialog.Trigger> must be used within <Dialog.Root> or provided with a handle.' : useRenderElement.formatErrorMessage(79));
760
+ }
761
+ const thisTriggerId = useButton.useBaseUiId(idProp);
762
+ const floatingContext = store.useState('floatingRootContext');
763
+ const isOpenedByThisTrigger = store.useState('isOpenedByTrigger', thisTriggerId);
764
+ const triggerElementRef = React__namespace.useRef(null);
869
765
  const {
870
- store
871
- } = useDialogRootContext();
872
- const {
873
- swipeDirection,
874
- frontmostHeight,
875
- hasNestedDrawer,
876
- nestedSwiping,
877
- nestedSwipeProgressStore,
878
- onPopupHeightChange,
879
- notifyParentFrontmostHeight,
880
- notifyParentHasNestedDrawer
881
- } = useDrawerRootContext();
882
- const descriptionElementId = store.useState('descriptionElementId');
883
- const disablePointerDismissal = store.useState('disablePointerDismissal');
884
- const floatingRootContext = store.useState('floatingRootContext');
885
- const rootPopupProps = store.useState('popupProps');
886
- const modal = store.useState('modal');
887
- const mounted = store.useState('mounted');
888
- const nested = store.useState('nested');
889
- const nestedOpenDialogCount = store.useState('nestedOpenDialogCount');
890
- const transitionStatus = store.useState('transitionStatus');
891
- const open = store.useState('open');
892
- const openMethod = store.useState('openMethod');
893
- const titleElementId = store.useState('titleElementId');
894
- const role = store.useState('role');
895
- const nestedDrawerOpen = nestedOpenDialogCount > 0;
896
- const swipe = useDrawerViewportContext();
897
- const swiping = swipe?.swiping ?? false;
898
- const swipeStrength = swipe?.swipeStrength ?? null;
899
- const {
900
- snapPoints,
901
- activeSnapPoint,
902
- activeSnapPointOffset
903
- } = useDrawerSnapPoints();
904
- useDialogPortalContext();
905
- const [popupHeight, setPopupHeight] = React__namespace.useState(0);
906
- const popupHeightRef = React__namespace.useRef(0);
907
- const measureHeight = useButton.useStableCallback(() => {
908
- const popupElement = store.context.popupRef.current;
909
- if (!popupElement) {
910
- return;
911
- }
912
- const offsetHeight = popupElement.offsetHeight;
913
-
914
- // Only skip while the element is still actually stretched beyond its last measured height.
915
- if (popupHeightRef.current > 0 && frontmostHeight > popupHeightRef.current && offsetHeight > popupHeightRef.current) {
916
- return;
917
- }
918
- const keepHeightWhileNested = popupHeightRef.current > 0 && hasNestedDrawer;
919
- if (keepHeightWhileNested) {
920
- const oldHeight = popupHeightRef.current;
921
- setPopupHeight(oldHeight);
922
- onPopupHeightChange(oldHeight);
923
- return;
924
- }
925
- const nextHeight = offsetHeight;
926
- if (nextHeight === popupHeightRef.current) {
927
- return;
928
- }
929
- popupHeightRef.current = nextHeight;
930
- setPopupHeight(nextHeight);
931
- onPopupHeightChange(nextHeight);
766
+ registerTrigger,
767
+ isMountedByThisTrigger
768
+ } = useTriggerDataForwarding(thisTriggerId, triggerElementRef, store, {
769
+ payload
932
770
  });
933
- useButton.useIsoLayoutEffect(() => {
934
- if (!mounted) {
935
- popupHeightRef.current = 0;
936
- setPopupHeight(0);
937
- onPopupHeightChange(0);
938
- return undefined;
939
- }
940
- const popupElement = store.context.popupRef.current;
941
- if (!popupElement) {
942
- return undefined;
943
- }
944
- removeCSSVariableInheritance$1();
945
- measureHeight();
946
- if (typeof ResizeObserver !== 'function') {
947
- return undefined;
948
- }
949
- const resizeObserver = new ResizeObserver(measureHeight);
950
- resizeObserver.observe(popupElement);
951
- return () => {
952
- resizeObserver.disconnect();
953
- };
954
- }, [measureHeight, mounted, nestedDrawerOpen, onPopupHeightChange, store.context.popupRef]);
955
- useButton.useIsoLayoutEffect(() => {
956
- const popupRef = store.context.popupRef;
957
- const syncNestedSwipeProgress = () => {
958
- const popupElement = popupRef.current;
959
- if (!popupElement) {
960
- return;
961
- }
962
- const progress = nestedSwipeProgressStore.getSnapshot();
963
- if (progress > 0) {
964
- popupElement.style.setProperty(DrawerBackdropCssVars.swipeProgress, `${progress}`);
965
- } else {
966
- popupElement.style.setProperty(DrawerBackdropCssVars.swipeProgress, '0');
967
- }
968
- };
969
- syncNestedSwipeProgress();
970
- const unsubscribe = nestedSwipeProgressStore.subscribe(syncNestedSwipeProgress);
971
- return () => {
972
- unsubscribe();
973
- const popupElement = popupRef.current;
974
- if (popupElement) {
975
- popupElement.style.setProperty(DrawerBackdropCssVars.swipeProgress, '0');
976
- }
977
- };
978
- }, [nestedSwipeProgressStore, store.context.popupRef]);
979
- React__namespace.useEffect(() => {
980
- if (!open) {
981
- return undefined;
982
- }
983
- notifyParentFrontmostHeight?.(frontmostHeight);
984
- return () => {
985
- notifyParentFrontmostHeight?.(0);
986
- };
987
- }, [frontmostHeight, open, notifyParentFrontmostHeight]);
988
- React__namespace.useEffect(() => {
989
- if (!notifyParentHasNestedDrawer) {
990
- return undefined;
991
- }
992
- const present = open || transitionStatus === 'ending';
993
- notifyParentHasNestedDrawer(present);
994
- return () => {
995
- notifyParentHasNestedDrawer(false);
996
- };
997
- }, [notifyParentHasNestedDrawer, open, transitionStatus]);
998
- useButton.useOpenChangeComplete({
999
- open,
1000
- ref: store.context.popupRef,
1001
- onComplete() {
1002
- if (open) {
1003
- store.context.onOpenChangeComplete?.(true);
1004
- }
1005
- }
771
+ const {
772
+ getButtonProps,
773
+ buttonRef
774
+ } = useButton.useButton({
775
+ disabled,
776
+ native: nativeButton
1006
777
  });
1007
- const resolvedInitialFocus = initialFocus === undefined ? store.context.popupRef : initialFocus;
778
+ const click = InternalBackdrop.useClick(floatingContext, {
779
+ enabled: floatingContext != null
780
+ });
781
+ const localInteractionProps = InternalBackdrop.useInteractions([click]);
1008
782
  const state = {
1009
- open,
1010
- nested,
1011
- transitionStatus,
1012
- expanded: activeSnapPoint === 1,
1013
- nestedDrawerOpen,
1014
- nestedDrawerSwiping: nestedSwiping,
1015
- swipeDirection,
1016
- swiping
783
+ disabled,
784
+ open: isOpenedByThisTrigger
1017
785
  };
1018
- let popupHeightCssVarValue;
1019
- const shouldUseAutoHeight = !hasNestedDrawer && transitionStatus !== 'ending';
1020
- if (popupHeight && !shouldUseAutoHeight) {
1021
- popupHeightCssVarValue = `${popupHeight}px`;
1022
- }
1023
- const shouldApplySnapPoints = snapPoints && snapPoints.length > 0 && (swipeDirection === 'down' || swipeDirection === 'up');
1024
- let snapPointOffsetValue = null;
1025
- if (shouldApplySnapPoints && activeSnapPointOffset !== null) {
1026
- snapPointOffsetValue = swipeDirection === 'up' ? -activeSnapPointOffset : activeSnapPointOffset;
1027
- }
1028
- let dragStyles = swipe ? swipe.getDragStyles() : useRenderElement.EMPTY_OBJECT;
1029
- if (shouldApplySnapPoints && swipeDirection === 'down') {
1030
- const baseOffset = activeSnapPointOffset ?? 0;
1031
- const movementValue = Number.parseFloat(String(dragStyles[DrawerPopupCssVars.swipeMovementY] ?? 0));
1032
- const nextOffset = Number.isFinite(movementValue) ? baseOffset + movementValue : baseOffset;
1033
- const shouldDamp = nextOffset < 0;
1034
- if (swiping && shouldDamp && Number.isFinite(movementValue)) {
1035
- const overshoot = Math.abs(nextOffset);
1036
- const dampedOffset = -Math.sqrt(overshoot);
1037
- const dampedMovement = dampedOffset - baseOffset;
1038
- dragStyles = {
1039
- ...dragStyles,
1040
- transform: undefined,
1041
- [DrawerPopupCssVars.swipeMovementY]: `${dampedMovement}px`
1042
- };
1043
- } else {
1044
- dragStyles = {
1045
- ...dragStyles,
1046
- transform: undefined
1047
- };
1048
- }
1049
- }
1050
- const element = useRenderElement.useRenderElement('div', componentProps, {
786
+ const rootTriggerProps = store.useState('triggerProps', isMountedByThisTrigger);
787
+ return useRenderElement.useRenderElement('button', componentProps, {
1051
788
  state,
1052
- props: [rootPopupProps, {
1053
- 'aria-labelledby': titleElementId,
1054
- 'aria-describedby': descriptionElementId,
1055
- role,
1056
- tabIndex: -1,
1057
- hidden: !mounted,
1058
- onKeyDown(event) {
1059
- if (COMPOSITE_KEYS.has(event.key)) {
1060
- event.stopPropagation();
1061
- }
1062
- },
1063
- style: {
1064
- ...dragStyles,
1065
- [DrawerBackdropCssVars.swipeProgress]: '0',
1066
- [DrawerPopupCssVars.nestedDrawers]: nestedOpenDialogCount,
1067
- [DrawerPopupCssVars.height]: popupHeightCssVarValue,
1068
- [DrawerPopupCssVars.snapPointOffset]: typeof snapPointOffsetValue === 'number' ? `${snapPointOffsetValue}px` : '0px',
1069
- [DrawerPopupCssVars.frontmostHeight]: frontmostHeight ? `${frontmostHeight}px` : undefined,
1070
- [DrawerPopupCssVars.swipeStrength]: typeof swipeStrength === 'number' && Number.isFinite(swipeStrength) && swipeStrength > 0 ? `${swipeStrength}` : '1'
1071
- }
1072
- }, elementProps],
1073
- ref: [forwardedRef, store.context.popupRef, store.useStateSetter('popupElement')],
1074
- stateAttributesMapping
1075
- });
1076
- return /*#__PURE__*/jsxRuntime.jsx(InternalBackdrop.FloatingFocusManager, {
1077
- context: floatingRootContext,
1078
- openInteractionType: openMethod,
1079
- disabled: !mounted,
1080
- closeOnFocusOut: !disablePointerDismissal,
1081
- initialFocus: resolvedInitialFocus,
1082
- returnFocus: finalFocus,
1083
- modal: modal !== false,
1084
- restoreFocus: "popup",
1085
- children: element
789
+ ref: [buttonRef, forwardedRef, registerTrigger, triggerElementRef],
790
+ props: [localInteractionProps.getReferenceProps(), rootTriggerProps, {
791
+ [InternalBackdrop.CLICK_TRIGGER_IDENTIFIER]: '',
792
+ id: thisTriggerId
793
+ }, elementProps, getButtonProps],
794
+ stateAttributesMapping: InternalBackdrop.triggerOpenStateMapping
1086
795
  });
1087
796
  });
1088
- if (process.env.NODE_ENV !== "production") DrawerPopup.displayName = "DrawerPopup";
797
+ if (process.env.NODE_ENV !== "production") DialogTrigger.displayName = "DialogTrigger";
1089
798
 
1090
- const DialogPortal = /*#__PURE__*/React__namespace.forwardRef(function DialogPortal(props, forwardedRef) {
1091
- const {
1092
- keepMounted = false,
1093
- ...portalProps
1094
- } = props;
1095
- const {
1096
- store
1097
- } = useDialogRootContext();
1098
- const mounted = store.useState('mounted');
1099
- const modal = store.useState('modal');
1100
- const open = store.useState('open');
1101
- const shouldRender = mounted || keepMounted;
1102
- if (!shouldRender) {
1103
- return null;
1104
- }
1105
- return /*#__PURE__*/jsxRuntime.jsx(DialogPortalContext.Provider, {
1106
- value: keepMounted,
1107
- children: /*#__PURE__*/jsxRuntime.jsxs(InternalBackdrop.FloatingPortal, {
1108
- ref: forwardedRef,
1109
- ...portalProps,
1110
- children: [mounted && modal === true && /*#__PURE__*/jsxRuntime.jsx(InternalBackdrop.InternalBackdrop, {
1111
- ref: store.context.internalBackdropRef,
1112
- inert: InternalBackdrop.inertValue(!open)
1113
- }), props.children]
1114
- })
1115
- });
1116
- });
1117
- if (process.env.NODE_ENV !== "production") DialogPortal.displayName = "DialogPortal";
799
+ let DrawerPopupCssVars = /*#__PURE__*/function (DrawerPopupCssVars) {
800
+ /**
801
+ * The number of nested drawers that are currently open.
802
+ * @type {number}
803
+ */
804
+ DrawerPopupCssVars["nestedDrawers"] = "--nested-drawers";
805
+ /**
806
+ * The height of the drawer popup.
807
+ * @type {CSS length}
808
+ */
809
+ DrawerPopupCssVars["height"] = "--drawer-height";
810
+ /**
811
+ * The height of the frontmost open drawer in the current nested drawer stack.
812
+ * @type {CSS length}
813
+ */
814
+ DrawerPopupCssVars["frontmostHeight"] = "--drawer-frontmost-height";
815
+ /**
816
+ * The swipe movement on the X axis.
817
+ * @type {CSS length}
818
+ */
819
+ DrawerPopupCssVars["swipeMovementX"] = "--drawer-swipe-movement-x";
820
+ /**
821
+ * The swipe movement on the Y axis.
822
+ * @type {CSS length}
823
+ */
824
+ DrawerPopupCssVars["swipeMovementY"] = "--drawer-swipe-movement-y";
825
+ /**
826
+ * The snap point offset used for translating the drawer.
827
+ * @type {CSS length}
828
+ */
829
+ DrawerPopupCssVars["snapPointOffset"] = "--drawer-snap-point-offset";
830
+ /**
831
+ * A scalar (0.1-1) used to scale the swipe release transition duration in CSS.
832
+ * @type {number}
833
+ */
834
+ DrawerPopupCssVars["swipeStrength"] = "--drawer-swipe-strength";
835
+ return DrawerPopupCssVars;
836
+ }({});
837
+
838
+ let DrawerBackdropCssVars = /*#__PURE__*/function (DrawerBackdropCssVars) {
839
+ /**
840
+ * The swipe progress of the drawer gesture.
841
+ * @type {number}
842
+ */
843
+ DrawerBackdropCssVars["swipeProgress"] = "--drawer-swipe-progress";
844
+ return DrawerBackdropCssVars;
845
+ }({});
846
+
847
+ const stateAttributesMapping$1 = {
848
+ ...InternalBackdrop.popupStateMapping,
849
+ ...useButton.transitionStatusMapping
850
+ };
1118
851
 
1119
852
  /**
1120
- * A portal element that moves the popup to a different part of the DOM.
1121
- * By default, the portal element is appended to `<body>`.
853
+ * An overlay displayed beneath the popup.
1122
854
  * Renders a `<div>` element.
1123
855
  *
1124
856
  * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)
1125
857
  */
1126
- const DrawerPortal = DialogPortal;
1127
-
1128
- function useDialogRoot(params) {
858
+ const DrawerBackdrop = /*#__PURE__*/React__namespace.forwardRef(function DrawerBackdrop(componentProps, forwardedRef) {
1129
859
  const {
1130
- store,
1131
- parentContext,
1132
- actionsRef
1133
- } = params;
1134
- const open = store.useState('open');
1135
- const disablePointerDismissal = store.useState('disablePointerDismissal');
1136
- const modal = store.useState('modal');
1137
- const popupElement = store.useState('popupElement');
1138
- const {
1139
- openMethod,
1140
- triggerProps
1141
- } = InternalBackdrop.useOpenInteractionType(open);
1142
- useImplicitActiveTrigger(store);
860
+ render,
861
+ className,
862
+ forceRender = false,
863
+ ...elementProps
864
+ } = componentProps;
1143
865
  const {
1144
- forceUnmount
1145
- } = useOpenStateTransitions(open, store);
1146
- const createDialogEventDetails = useButton.useStableCallback(reason => {
1147
- const details = useButton.createChangeEventDetails(reason);
1148
- details.preventUnmountOnClose = () => {
1149
- store.set('preventUnmountingOnClose', true);
1150
- };
1151
- return details;
1152
- });
1153
- const handleImperativeClose = React__namespace.useCallback(() => {
1154
- store.setOpen(false, createDialogEventDetails(useButton.imperativeAction));
1155
- }, [store, createDialogEventDetails]);
1156
- React__namespace.useImperativeHandle(actionsRef, () => ({
1157
- unmount: forceUnmount,
1158
- close: handleImperativeClose
1159
- }), [forceUnmount, handleImperativeClose]);
1160
- const floatingRootContext = useSyncedFloatingRootContext({
1161
- popupStore: store,
1162
- onOpenChange: store.setOpen,
1163
- treatPopupAsFloatingElement: true,
1164
- noEmit: true
1165
- });
1166
- const [ownNestedOpenDialogs, setOwnNestedOpenDialogs] = React__namespace.useState(0);
1167
- const isTopmost = ownNestedOpenDialogs === 0;
1168
- const role = useRole(floatingRootContext);
1169
- const dismiss = InternalBackdrop.useDismiss(floatingRootContext, {
1170
- outsidePressEvent() {
1171
- if (store.context.internalBackdropRef.current || store.context.backdropRef.current) {
1172
- return 'intentional';
1173
- }
1174
- // Ensure `aria-hidden` on outside elements is removed immediately
1175
- // on outside press when trapping focus.
1176
- return {
1177
- mouse: modal === 'trap-focus' ? 'sloppy' : 'intentional',
1178
- touch: 'sloppy'
1179
- };
1180
- },
1181
- outsidePress(event) {
1182
- if (!store.context.outsidePressEnabledRef.current) {
1183
- return false;
1184
- }
1185
-
1186
- // For mouse events, only accept left button (button 0)
1187
- // For touch events, a single touch is equivalent to left button
1188
- if ('button' in event && event.button !== 0) {
1189
- return false;
1190
- }
1191
- if ('touches' in event && event.touches.length !== 1) {
1192
- return false;
1193
- }
1194
- const target = useButton.getTarget(event);
1195
- if (isTopmost && !disablePointerDismissal) {
1196
- const eventTarget = target;
1197
- // Only close if the click occurred on the dialog's owning backdrop.
1198
- // This supports multiple modal dialogs that aren't nested in the React tree:
1199
- // https://github.com/mui/base-ui/issues/1320
1200
- if (modal) {
1201
- return store.context.internalBackdropRef.current || store.context.backdropRef.current ? store.context.internalBackdropRef.current === eventTarget || store.context.backdropRef.current === eventTarget || useButton.contains(eventTarget, popupElement) && !eventTarget?.hasAttribute('data-base-ui-portal') : true;
1202
- }
1203
- return true;
866
+ store
867
+ } = useDialogRootContext();
868
+ const open = store.useState('open');
869
+ const nested = store.useState('nested');
870
+ const mounted = store.useState('mounted');
871
+ const transitionStatus = store.useState('transitionStatus');
872
+ const state = {
873
+ open,
874
+ transitionStatus
875
+ };
876
+ return useRenderElement.useRenderElement('div', componentProps, {
877
+ state,
878
+ ref: [store.context.backdropRef, forwardedRef],
879
+ stateAttributesMapping: stateAttributesMapping$1,
880
+ props: [{
881
+ role: 'presentation',
882
+ hidden: !mounted,
883
+ style: {
884
+ pointerEvents: !open ? 'none' : undefined,
885
+ userSelect: 'none',
886
+ WebkitUserSelect: 'none',
887
+ [DrawerBackdropCssVars.swipeProgress]: '0',
888
+ [DrawerPopupCssVars.swipeStrength]: '1'
1204
889
  }
1205
- return false;
1206
- },
1207
- escapeKey: isTopmost
890
+ }, elementProps],
891
+ enabled: forceRender || !nested
1208
892
  });
1209
- InternalBackdrop.useScrollLock(open && modal === true, popupElement);
1210
- const {
1211
- getReferenceProps,
1212
- getFloatingProps,
1213
- getTriggerProps
1214
- } = InternalBackdrop.useInteractions([role, dismiss]);
893
+ });
894
+ if (process.env.NODE_ENV !== "production") DrawerBackdrop.displayName = "DrawerBackdrop";
1215
895
 
1216
- // Listen for nested open/close events on this store to maintain the count
1217
- store.useContextCallback('onNestedDialogOpen', ownChildrenCount => {
1218
- setOwnNestedOpenDialogs(ownChildrenCount + 1);
1219
- });
1220
- store.useContextCallback('onNestedDialogClose', () => {
1221
- setOwnNestedOpenDialogs(0);
896
+ const DRAWER_CONTENT_ATTRIBUTE = 'data-drawer-content';
897
+
898
+ /**
899
+ * A container for the drawer contents.
900
+ * Renders a `<div>` element.
901
+ *
902
+ * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)
903
+ */
904
+ const DrawerContent = /*#__PURE__*/React__namespace.forwardRef(function DrawerContent(componentProps, forwardedRef) {
905
+ const {
906
+ render,
907
+ className,
908
+ ...elementProps
909
+ } = componentProps;
910
+ useDialogRootContext();
911
+ return useRenderElement.useRenderElement('div', componentProps, {
912
+ ref: forwardedRef,
913
+ props: [{
914
+ [DRAWER_CONTENT_ATTRIBUTE]: ''
915
+ }, elementProps]
1222
916
  });
917
+ });
918
+ if (process.env.NODE_ENV !== "production") DrawerContent.displayName = "DrawerContent";
1223
919
 
1224
- // Notify parent of our open/close state using parent callbacks, if any
1225
- React__namespace.useEffect(() => {
1226
- if (parentContext?.onNestedDialogOpen && open) {
1227
- parentContext.onNestedDialogOpen(ownNestedOpenDialogs);
920
+ const DrawerProviderContext = /*#__PURE__*/React__namespace.createContext(undefined);
921
+ if (process.env.NODE_ENV !== "production") DrawerProviderContext.displayName = "DrawerProviderContext";
922
+ function useDrawerProviderContext(optional) {
923
+ const context = React__namespace.useContext(DrawerProviderContext);
924
+ return context;
925
+ }
926
+
927
+ let DrawerPopupDataAttributes = function (DrawerPopupDataAttributes) {
928
+ /**
929
+ * Present when the drawer is open.
930
+ */
931
+ DrawerPopupDataAttributes[DrawerPopupDataAttributes["open"] = InternalBackdrop.CommonPopupDataAttributes.open] = "open";
932
+ /**
933
+ * Present when the drawer is closed.
934
+ */
935
+ DrawerPopupDataAttributes[DrawerPopupDataAttributes["closed"] = InternalBackdrop.CommonPopupDataAttributes.closed] = "closed";
936
+ /**
937
+ * Present when the drawer is animating in.
938
+ */
939
+ DrawerPopupDataAttributes[DrawerPopupDataAttributes["startingStyle"] = InternalBackdrop.CommonPopupDataAttributes.startingStyle] = "startingStyle";
940
+ /**
941
+ * Present when the drawer is animating out.
942
+ */
943
+ DrawerPopupDataAttributes[DrawerPopupDataAttributes["endingStyle"] = InternalBackdrop.CommonPopupDataAttributes.endingStyle] = "endingStyle";
944
+ /**
945
+ * Present when the drawer is at the expanded (full-height) snap point.
946
+ */
947
+ DrawerPopupDataAttributes["expanded"] = "data-expanded";
948
+ /**
949
+ * Present when a nested drawer is open.
950
+ */
951
+ DrawerPopupDataAttributes["nestedDrawerOpen"] = "data-nested-drawer-open";
952
+ /**
953
+ * Present when a nested drawer is being swiped.
954
+ */
955
+ DrawerPopupDataAttributes["nestedDrawerSwiping"] = "data-nested-drawer-swiping";
956
+ /**
957
+ * Present when the drawer is dismissed by swiping.
958
+ */
959
+ DrawerPopupDataAttributes["swipeDismiss"] = "data-swipe-dismiss";
960
+ /**
961
+ * Indicates the swipe direction.
962
+ * @type {'up' | 'down' | 'left' | 'right'}
963
+ */
964
+ DrawerPopupDataAttributes["swipeDirection"] = "data-swipe-direction";
965
+ /**
966
+ * Present when the drawer is being swiped.
967
+ */
968
+ DrawerPopupDataAttributes["swiping"] = "data-swiping";
969
+ return DrawerPopupDataAttributes;
970
+ }({});
971
+
972
+ const DrawerRootContext = /*#__PURE__*/React__namespace.createContext(undefined);
973
+ if (process.env.NODE_ENV !== "production") DrawerRootContext.displayName = "DrawerRootContext";
974
+ function useDrawerRootContext(optional) {
975
+ const drawerRootContext = React__namespace.useContext(DrawerRootContext);
976
+ if (optional === false && drawerRootContext === undefined) {
977
+ throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: DrawerRootContext is missing. Drawer parts must be placed within <Drawer.Root>.' : useRenderElement.formatErrorMessage(90));
978
+ }
979
+ return drawerRootContext;
980
+ }
981
+
982
+ function resolveSnapPointValue(snapPoint, viewportHeight, rootFontSize) {
983
+ if (!Number.isFinite(viewportHeight) || viewportHeight <= 0) {
984
+ return null;
985
+ }
986
+ if (typeof snapPoint === 'number') {
987
+ if (!Number.isFinite(snapPoint)) {
988
+ return null;
1228
989
  }
1229
- if (parentContext?.onNestedDialogClose && !open) {
1230
- parentContext.onNestedDialogClose();
990
+ if (snapPoint <= 1) {
991
+ return clamp.clamp(snapPoint, 0, 1) * viewportHeight;
1231
992
  }
1232
- return () => {
1233
- if (parentContext?.onNestedDialogClose && open) {
1234
- parentContext.onNestedDialogClose();
1235
- }
1236
- };
1237
- }, [open, parentContext, ownNestedOpenDialogs]);
1238
- const activeTriggerProps = React__namespace.useMemo(() => getReferenceProps(triggerProps), [getReferenceProps, triggerProps]);
1239
- const inactiveTriggerProps = React__namespace.useMemo(() => getTriggerProps(triggerProps), [getTriggerProps, triggerProps]);
1240
- const popupProps = React__namespace.useMemo(() => getFloatingProps(), [getFloatingProps]);
1241
- store.useSyncedValues({
1242
- openMethod,
1243
- activeTriggerProps,
1244
- inactiveTriggerProps,
1245
- popupProps,
1246
- floatingRootContext,
1247
- nestedOpenDialogCount: ownNestedOpenDialogs
1248
- });
993
+ return snapPoint;
994
+ }
995
+ const trimmed = snapPoint.trim();
996
+ if (trimmed.endsWith('px')) {
997
+ const value = Number.parseFloat(trimmed);
998
+ return Number.isFinite(value) ? value : null;
999
+ }
1000
+ if (trimmed.endsWith('rem')) {
1001
+ const value = Number.parseFloat(trimmed);
1002
+ return Number.isFinite(value) ? value * rootFontSize : null;
1003
+ }
1004
+ return null;
1249
1005
  }
1250
-
1251
- const selectors = {
1252
- ...popupStoreSelectors,
1253
- modal: InternalBackdrop.createSelector(state => state.modal),
1254
- nested: InternalBackdrop.createSelector(state => state.nested),
1255
- nestedOpenDialogCount: InternalBackdrop.createSelector(state => state.nestedOpenDialogCount),
1256
- disablePointerDismissal: InternalBackdrop.createSelector(state => state.disablePointerDismissal),
1257
- openMethod: InternalBackdrop.createSelector(state => state.openMethod),
1258
- descriptionElementId: InternalBackdrop.createSelector(state => state.descriptionElementId),
1259
- titleElementId: InternalBackdrop.createSelector(state => state.titleElementId),
1260
- viewportElement: InternalBackdrop.createSelector(state => state.viewportElement),
1261
- role: InternalBackdrop.createSelector(state => state.role)
1262
- };
1263
- class DialogStore extends InternalBackdrop.ReactStore {
1264
- constructor(initialState) {
1265
- super(createInitialState(initialState), {
1266
- popupRef: /*#__PURE__*/React__namespace.createRef(),
1267
- backdropRef: /*#__PURE__*/React__namespace.createRef(),
1268
- internalBackdropRef: /*#__PURE__*/React__namespace.createRef(),
1269
- outsidePressEnabledRef: {
1270
- current: true
1271
- },
1272
- triggerElements: new InternalBackdrop.PopupTriggerMap(),
1273
- onOpenChange: undefined,
1274
- onOpenChangeComplete: undefined
1275
- }, selectors);
1006
+ function findClosestSnapPoint(height, points) {
1007
+ let closest = null;
1008
+ let closestDistance = Infinity;
1009
+ for (const point of points) {
1010
+ const distance = Math.abs(point.height - height);
1011
+ if (distance < closestDistance) {
1012
+ closestDistance = distance;
1013
+ closest = point;
1014
+ }
1276
1015
  }
1277
- setOpen = (nextOpen, eventDetails) => {
1278
- eventDetails.preventUnmountOnClose = () => {
1279
- this.set('preventUnmountingOnClose', true);
1016
+ return closest;
1017
+ }
1018
+ function useDrawerSnapPoints() {
1019
+ const {
1020
+ store
1021
+ } = useDialogRootContext();
1022
+ const {
1023
+ snapPoints,
1024
+ activeSnapPoint,
1025
+ setActiveSnapPoint,
1026
+ popupHeight
1027
+ } = useDrawerRootContext();
1028
+ const viewportElement = store.useState('viewportElement');
1029
+ const [viewportHeight, setViewportHeight] = React__namespace.useState(0);
1030
+ const [rootFontSize, setRootFontSize] = React__namespace.useState(16);
1031
+ const measureViewportHeight = useButton.useStableCallback(() => {
1032
+ const doc = useButton.ownerDocument(viewportElement);
1033
+ const html = doc.documentElement;
1034
+ if (viewportElement) {
1035
+ setViewportHeight(viewportElement.offsetHeight);
1036
+ }
1037
+ if (!viewportElement) {
1038
+ setViewportHeight(html.clientHeight);
1039
+ }
1040
+ const fontSize = parseFloat(getComputedStyle(html).fontSize);
1041
+ if (Number.isFinite(fontSize)) {
1042
+ setRootFontSize(fontSize);
1043
+ }
1044
+ });
1045
+ useButton.useIsoLayoutEffect(() => {
1046
+ measureViewportHeight();
1047
+ if (!viewportElement || typeof ResizeObserver !== 'function') {
1048
+ return undefined;
1049
+ }
1050
+ const resizeObserver = new ResizeObserver(measureViewportHeight);
1051
+ resizeObserver.observe(viewportElement);
1052
+ return () => {
1053
+ resizeObserver.disconnect();
1280
1054
  };
1281
- if (!nextOpen && eventDetails.trigger == null && this.state.activeTriggerId != null) {
1282
- // When closing the dialog, pass the old trigger to the onOpenChange event
1283
- // so it's not reset too early (potentially causing focus issues in controlled scenarios).
1284
- eventDetails.trigger = this.state.activeTriggerElement ?? undefined;
1055
+ }, [measureViewportHeight, viewportElement]);
1056
+ const resolvedSnapPoints = React__namespace.useMemo(() => {
1057
+ if (!snapPoints || snapPoints.length === 0 || viewportHeight <= 0 || popupHeight <= 0) {
1058
+ return [];
1059
+ }
1060
+ const maxHeight = Math.min(popupHeight, viewportHeight);
1061
+ if (!Number.isFinite(maxHeight) || maxHeight <= 0) {
1062
+ return [];
1285
1063
  }
1286
- this.context.onOpenChange?.(nextOpen, eventDetails);
1287
- if (eventDetails.isCanceled) {
1288
- return;
1064
+ const resolved = snapPoints.map(value => {
1065
+ const resolvedHeight = resolveSnapPointValue(value, viewportHeight, rootFontSize);
1066
+ if (resolvedHeight === null || !Number.isFinite(resolvedHeight)) {
1067
+ return null;
1068
+ }
1069
+ const clampedHeight = clamp.clamp(resolvedHeight, 0, maxHeight);
1070
+ return {
1071
+ value,
1072
+ height: clampedHeight,
1073
+ offset: Math.max(0, popupHeight - clampedHeight)
1074
+ };
1075
+ }).filter(point => Boolean(point));
1076
+ if (resolved.length <= 1) {
1077
+ return resolved;
1289
1078
  }
1290
- const details = {
1291
- open: nextOpen,
1292
- nativeEvent: eventDetails.event,
1293
- reason: eventDetails.reason,
1294
- nested: this.state.nested
1295
- };
1296
- this.state.floatingRootContext.context.events?.emit('openchange', details);
1297
- const updatedState = {
1298
- open: nextOpen
1299
- };
1300
-
1301
- // If a popup is closing, the `trigger` may be null.
1302
- // We want to keep the previous value so that exit animations are played and focus is returned correctly.
1303
- const newTriggerId = eventDetails.trigger?.id ?? null;
1304
- if (newTriggerId || nextOpen) {
1305
- updatedState.activeTriggerId = newTriggerId;
1306
- updatedState.activeTriggerElement = eventDetails.trigger ?? null;
1079
+ const deduped = [];
1080
+ const seenHeights = [];
1081
+ for (let index = resolved.length - 1; index >= 0; index -= 1) {
1082
+ const point = resolved[index];
1083
+ const isDuplicate = seenHeights.some(height => Math.abs(height - point.height) <= 1);
1084
+ if (isDuplicate) {
1085
+ continue;
1086
+ }
1087
+ seenHeights.push(point.height);
1088
+ deduped.push(point);
1307
1089
  }
1308
- this.update(updatedState);
1309
- };
1310
- }
1311
- function createInitialState(initialState = {}) {
1090
+ deduped.reverse();
1091
+ return deduped;
1092
+ }, [popupHeight, rootFontSize, snapPoints, viewportHeight]);
1093
+ const resolvedActiveSnapPoint = React__namespace.useMemo(() => {
1094
+ if (activeSnapPoint === undefined) {
1095
+ return resolvedSnapPoints[0];
1096
+ }
1097
+ if (activeSnapPoint === null) {
1098
+ return undefined;
1099
+ }
1100
+ const exactMatch = resolvedSnapPoints.find(point => Object.is(point.value, activeSnapPoint));
1101
+ if (exactMatch) {
1102
+ return exactMatch;
1103
+ }
1104
+ const maxHeight = Math.min(popupHeight, viewportHeight);
1105
+ const resolvedHeight = resolveSnapPointValue(activeSnapPoint, viewportHeight, rootFontSize);
1106
+ if (resolvedHeight === null || !Number.isFinite(resolvedHeight)) {
1107
+ return undefined;
1108
+ }
1109
+ const clampedHeight = clamp.clamp(resolvedHeight, 0, maxHeight);
1110
+ return findClosestSnapPoint(clampedHeight, resolvedSnapPoints) ?? undefined;
1111
+ }, [activeSnapPoint, popupHeight, resolvedSnapPoints, rootFontSize, viewportHeight]);
1312
1112
  return {
1313
- ...createInitialPopupStoreState(),
1314
- modal: true,
1315
- disablePointerDismissal: false,
1316
- popupElement: null,
1317
- viewportElement: null,
1318
- descriptionElementId: undefined,
1319
- titleElementId: undefined,
1320
- openMethod: null,
1321
- nested: false,
1322
- nestedOpenDialogCount: 0,
1323
- role: 'dialog',
1324
- ...initialState
1113
+ snapPoints,
1114
+ activeSnapPoint,
1115
+ setActiveSnapPoint,
1116
+ popupHeight,
1117
+ viewportHeight,
1118
+ resolvedSnapPoints,
1119
+ activeSnapPointOffset: resolvedActiveSnapPoint?.offset ?? null
1325
1120
  };
1326
1121
  }
1327
1122
 
1123
+ const DrawerViewportContext = /*#__PURE__*/React__namespace.createContext(null);
1124
+ if (process.env.NODE_ENV !== "production") DrawerViewportContext.displayName = "DrawerViewportContext";
1125
+ function useDrawerViewportContext(optional) {
1126
+ const context = React__namespace.useContext(DrawerViewportContext);
1127
+ return context;
1128
+ }
1129
+
1130
+ let drawerSwipeVarsRegistered = false;
1131
+
1328
1132
  /**
1329
- * Groups all parts of the dialog.
1330
- * Doesn’t render its own HTML element.
1331
- *
1332
- * Documentation: [Base UI Dialog](https://base-ui.com/react/components/dialog)
1133
+ * Removes inheritance of high-frequency drawer swipe CSS variables, which
1134
+ * reduces style recalculation cost in complex drawers with deep subtrees.
1135
+ * Child elements that need these values can still opt-in by using `inherit`.
1136
+ * See https://motion.dev/blog/web-animation-performance-tier-list
1137
+ * under the "Improving CSS variable performance" section.
1333
1138
  */
1334
- function DialogRoot(props) {
1335
- const {
1336
- children,
1337
- open: openProp,
1338
- defaultOpen = false,
1339
- onOpenChange,
1340
- onOpenChangeComplete,
1341
- disablePointerDismissal = false,
1342
- modal = true,
1343
- actionsRef,
1344
- handle,
1345
- triggerId: triggerIdProp,
1346
- defaultTriggerId: defaultTriggerIdProp = null
1347
- } = props;
1348
- const parentDialogRootContext = useDialogRootContext(true);
1349
- const nested = Boolean(parentDialogRootContext);
1350
- const store = useRenderElement.useRefWithInit(() => {
1351
- return handle?.store ?? new DialogStore({
1352
- open: defaultOpen,
1353
- openProp,
1354
- activeTriggerId: defaultTriggerIdProp,
1355
- triggerIdProp,
1356
- modal,
1357
- disablePointerDismissal,
1358
- nested
1359
- });
1360
- }).current;
1139
+ function removeCSSVariableInheritance$1() {
1140
+ if (drawerSwipeVarsRegistered) {
1141
+ return;
1142
+ }
1361
1143
 
1362
- // Support initially open state when uncontrolled
1363
- InternalBackdrop.useOnFirstRender(() => {
1364
- if (openProp === undefined && store.state.open === false && defaultOpen === true) {
1365
- store.update({
1366
- open: true,
1367
- activeTriggerId: defaultTriggerIdProp
1368
- });
1369
- }
1370
- });
1371
- store.useControlledProp('openProp', openProp);
1372
- store.useControlledProp('triggerIdProp', triggerIdProp);
1373
- store.useSyncedValues({
1374
- disablePointerDismissal,
1375
- nested,
1376
- modal
1377
- });
1378
- store.useContextCallback('onOpenChange', onOpenChange);
1379
- store.useContextCallback('onOpenChangeComplete', onOpenChangeComplete);
1380
- const payload = store.useState('payload');
1381
- useDialogRoot({
1382
- store,
1383
- actionsRef,
1384
- parentContext: parentDialogRootContext?.store.context,
1385
- onOpenChange,
1386
- triggerIdProp
1387
- });
1388
- const contextValue = React__namespace.useMemo(() => ({
1389
- store
1390
- }), [store]);
1391
- return /*#__PURE__*/jsxRuntime.jsx(DialogRootContext.Provider, {
1392
- value: contextValue,
1393
- children: typeof children === 'function' ? children({
1394
- payload
1395
- }) : children
1396
- });
1144
+ // Intentionally keep inheritance disabled on WebKit as well. Safari doesn't support
1145
+ // opting descendants back in via `--var: inherit` for custom properties registered
1146
+ // with `inherits: false`, but Drawer does not rely on descendant access to these vars
1147
+ // (unlike ScrollArea), so we keep the performance optimization enabled.
1148
+ if (typeof CSS !== 'undefined' && 'registerProperty' in CSS) {
1149
+ [DrawerPopupCssVars.swipeMovementX, DrawerPopupCssVars.swipeMovementY, DrawerPopupCssVars.snapPointOffset].forEach(name => {
1150
+ try {
1151
+ CSS.registerProperty({
1152
+ name,
1153
+ syntax: '<length>',
1154
+ inherits: false,
1155
+ initialValue: '0px'
1156
+ });
1157
+ } catch {
1158
+ /* ignore already-registered */
1159
+ }
1160
+ });
1161
+ [{
1162
+ name: DrawerBackdropCssVars.swipeProgress,
1163
+ initialValue: '0'
1164
+ }, {
1165
+ name: DrawerPopupCssVars.swipeStrength,
1166
+ initialValue: '1'
1167
+ }].forEach(({
1168
+ name,
1169
+ initialValue
1170
+ }) => {
1171
+ try {
1172
+ CSS.registerProperty({
1173
+ name,
1174
+ syntax: '<number>',
1175
+ inherits: false,
1176
+ initialValue
1177
+ });
1178
+ } catch {
1179
+ /* ignore already-registered */
1180
+ }
1181
+ });
1182
+ }
1183
+ drawerSwipeVarsRegistered = true;
1397
1184
  }
1185
+ const stateAttributesMapping = {
1186
+ ...InternalBackdrop.popupStateMapping,
1187
+ ...useButton.transitionStatusMapping,
1188
+ expanded(value) {
1189
+ return value ? {
1190
+ [DrawerPopupDataAttributes.expanded]: ''
1191
+ } : null;
1192
+ },
1193
+ nestedDrawerOpen(value) {
1194
+ return value ? {
1195
+ [DrawerPopupDataAttributes.nestedDrawerOpen]: ''
1196
+ } : null;
1197
+ },
1198
+ nestedDrawerSwiping(value) {
1199
+ return value ? {
1200
+ [DrawerPopupDataAttributes.nestedDrawerSwiping]: ''
1201
+ } : null;
1202
+ },
1203
+ swipeDirection(value) {
1204
+ return value ? {
1205
+ [DrawerPopupDataAttributes.swipeDirection]: value
1206
+ } : null;
1207
+ },
1208
+ swiping(value) {
1209
+ return value ? {
1210
+ [DrawerPopupDataAttributes.swiping]: ''
1211
+ } : null;
1212
+ }
1213
+ };
1398
1214
 
1399
1215
  /**
1400
- * A button that opens the dialog.
1401
- * Renders a `<button>` element.
1216
+ * A container for the drawer contents.
1217
+ * Renders a `<div>` element.
1402
1218
  *
1403
- * Documentation: [Base UI Dialog](https://base-ui.com/react/components/dialog)
1219
+ * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)
1404
1220
  */
1405
- const DialogTrigger = /*#__PURE__*/React__namespace.forwardRef(function DialogTrigger(componentProps, forwardedRef) {
1221
+ const DrawerPopup = /*#__PURE__*/React__namespace.forwardRef(function DrawerPopup(componentProps, forwardedRef) {
1406
1222
  const {
1407
- render,
1408
1223
  className,
1409
- disabled = false,
1410
- nativeButton = true,
1411
- id: idProp,
1412
- payload,
1413
- handle,
1224
+ finalFocus,
1225
+ initialFocus,
1226
+ render,
1414
1227
  ...elementProps
1415
1228
  } = componentProps;
1416
- const dialogRootContext = useDialogRootContext(true);
1417
- const store = handle?.store ?? dialogRootContext?.store;
1418
- if (!store) {
1419
- throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: <Dialog.Trigger> must be used within <Dialog.Root> or provided with a handle.' : useRenderElement.formatErrorMessage(79));
1420
- }
1421
- const thisTriggerId = useButton.useBaseUiId(idProp);
1422
- const floatingContext = store.useState('floatingRootContext');
1423
- const isOpenedByThisTrigger = store.useState('isOpenedByTrigger', thisTriggerId);
1424
- const triggerElementRef = React__namespace.useRef(null);
1425
1229
  const {
1426
- registerTrigger,
1427
- isMountedByThisTrigger
1428
- } = useTriggerDataForwarding(thisTriggerId, triggerElementRef, store, {
1429
- payload
1430
- });
1230
+ store
1231
+ } = useDialogRootContext();
1431
1232
  const {
1432
- getButtonProps,
1433
- buttonRef
1434
- } = useButton.useButton({
1435
- disabled,
1436
- native: nativeButton
1233
+ swipeDirection,
1234
+ frontmostHeight,
1235
+ hasNestedDrawer,
1236
+ nestedSwiping,
1237
+ nestedSwipeProgressStore,
1238
+ onPopupHeightChange,
1239
+ notifyParentFrontmostHeight,
1240
+ notifyParentHasNestedDrawer
1241
+ } = useDrawerRootContext();
1242
+ const descriptionElementId = store.useState('descriptionElementId');
1243
+ const disablePointerDismissal = store.useState('disablePointerDismissal');
1244
+ const floatingRootContext = store.useState('floatingRootContext');
1245
+ const rootPopupProps = store.useState('popupProps');
1246
+ const modal = store.useState('modal');
1247
+ const mounted = store.useState('mounted');
1248
+ const nested = store.useState('nested');
1249
+ const nestedOpenDialogCount = store.useState('nestedOpenDialogCount');
1250
+ const transitionStatus = store.useState('transitionStatus');
1251
+ const open = store.useState('open');
1252
+ const openMethod = store.useState('openMethod');
1253
+ const titleElementId = store.useState('titleElementId');
1254
+ const role = store.useState('role');
1255
+ const nestedDrawerOpen = nestedOpenDialogCount > 0;
1256
+ const swipe = useDrawerViewportContext();
1257
+ const swiping = swipe?.swiping ?? false;
1258
+ const swipeStrength = swipe?.swipeStrength ?? null;
1259
+ const {
1260
+ snapPoints,
1261
+ activeSnapPoint,
1262
+ activeSnapPointOffset
1263
+ } = useDrawerSnapPoints();
1264
+ useDialogPortalContext();
1265
+ const [popupHeight, setPopupHeight] = React__namespace.useState(0);
1266
+ const popupHeightRef = React__namespace.useRef(0);
1267
+ const measureHeight = useButton.useStableCallback(() => {
1268
+ const popupElement = store.context.popupRef.current;
1269
+ if (!popupElement) {
1270
+ return;
1271
+ }
1272
+ const offsetHeight = popupElement.offsetHeight;
1273
+
1274
+ // Only skip while the element is still actually stretched beyond its last measured height.
1275
+ if (popupHeightRef.current > 0 && frontmostHeight > popupHeightRef.current && offsetHeight > popupHeightRef.current) {
1276
+ return;
1277
+ }
1278
+ const keepHeightWhileNested = popupHeightRef.current > 0 && hasNestedDrawer;
1279
+ if (keepHeightWhileNested) {
1280
+ const oldHeight = popupHeightRef.current;
1281
+ setPopupHeight(oldHeight);
1282
+ onPopupHeightChange(oldHeight);
1283
+ return;
1284
+ }
1285
+ const nextHeight = offsetHeight;
1286
+ if (nextHeight === popupHeightRef.current) {
1287
+ return;
1288
+ }
1289
+ popupHeightRef.current = nextHeight;
1290
+ setPopupHeight(nextHeight);
1291
+ onPopupHeightChange(nextHeight);
1437
1292
  });
1438
- const click = InternalBackdrop.useClick(floatingContext, {
1439
- enabled: floatingContext != null
1293
+ useButton.useIsoLayoutEffect(() => {
1294
+ if (!mounted) {
1295
+ popupHeightRef.current = 0;
1296
+ setPopupHeight(0);
1297
+ onPopupHeightChange(0);
1298
+ return undefined;
1299
+ }
1300
+ const popupElement = store.context.popupRef.current;
1301
+ if (!popupElement) {
1302
+ return undefined;
1303
+ }
1304
+ removeCSSVariableInheritance$1();
1305
+ measureHeight();
1306
+ if (typeof ResizeObserver !== 'function') {
1307
+ return undefined;
1308
+ }
1309
+ const resizeObserver = new ResizeObserver(measureHeight);
1310
+ resizeObserver.observe(popupElement);
1311
+ return () => {
1312
+ resizeObserver.disconnect();
1313
+ };
1314
+ }, [measureHeight, mounted, nestedDrawerOpen, onPopupHeightChange, store.context.popupRef]);
1315
+ useButton.useIsoLayoutEffect(() => {
1316
+ const popupRef = store.context.popupRef;
1317
+ const syncNestedSwipeProgress = () => {
1318
+ const popupElement = popupRef.current;
1319
+ if (!popupElement) {
1320
+ return;
1321
+ }
1322
+ const progress = nestedSwipeProgressStore.getSnapshot();
1323
+ if (progress > 0) {
1324
+ popupElement.style.setProperty(DrawerBackdropCssVars.swipeProgress, `${progress}`);
1325
+ } else {
1326
+ popupElement.style.setProperty(DrawerBackdropCssVars.swipeProgress, '0');
1327
+ }
1328
+ };
1329
+ syncNestedSwipeProgress();
1330
+ const unsubscribe = nestedSwipeProgressStore.subscribe(syncNestedSwipeProgress);
1331
+ return () => {
1332
+ unsubscribe();
1333
+ const popupElement = popupRef.current;
1334
+ if (popupElement) {
1335
+ popupElement.style.setProperty(DrawerBackdropCssVars.swipeProgress, '0');
1336
+ }
1337
+ };
1338
+ }, [nestedSwipeProgressStore, store.context.popupRef]);
1339
+ React__namespace.useEffect(() => {
1340
+ if (!open) {
1341
+ return undefined;
1342
+ }
1343
+ notifyParentFrontmostHeight?.(frontmostHeight);
1344
+ return () => {
1345
+ notifyParentFrontmostHeight?.(0);
1346
+ };
1347
+ }, [frontmostHeight, open, notifyParentFrontmostHeight]);
1348
+ React__namespace.useEffect(() => {
1349
+ if (!notifyParentHasNestedDrawer) {
1350
+ return undefined;
1351
+ }
1352
+ const present = open || transitionStatus === 'ending';
1353
+ notifyParentHasNestedDrawer(present);
1354
+ return () => {
1355
+ notifyParentHasNestedDrawer(false);
1356
+ };
1357
+ }, [notifyParentHasNestedDrawer, open, transitionStatus]);
1358
+ useButton.useOpenChangeComplete({
1359
+ open,
1360
+ ref: store.context.popupRef,
1361
+ onComplete() {
1362
+ if (open) {
1363
+ store.context.onOpenChangeComplete?.(true);
1364
+ }
1365
+ }
1440
1366
  });
1441
- const localInteractionProps = InternalBackdrop.useInteractions([click]);
1367
+ const resolvedInitialFocus = initialFocus === undefined ? store.context.popupRef : initialFocus;
1442
1368
  const state = {
1443
- disabled,
1444
- open: isOpenedByThisTrigger
1369
+ open,
1370
+ nested,
1371
+ transitionStatus,
1372
+ expanded: activeSnapPoint === 1,
1373
+ nestedDrawerOpen,
1374
+ nestedDrawerSwiping: nestedSwiping,
1375
+ swipeDirection,
1376
+ swiping
1445
1377
  };
1446
- const rootTriggerProps = store.useState('triggerProps', isMountedByThisTrigger);
1447
- return useRenderElement.useRenderElement('button', componentProps, {
1378
+ let popupHeightCssVarValue;
1379
+ const shouldUseAutoHeight = !hasNestedDrawer && transitionStatus !== 'ending';
1380
+ if (popupHeight && !shouldUseAutoHeight) {
1381
+ popupHeightCssVarValue = `${popupHeight}px`;
1382
+ }
1383
+ const shouldApplySnapPoints = snapPoints && snapPoints.length > 0 && (swipeDirection === 'down' || swipeDirection === 'up');
1384
+ let snapPointOffsetValue = null;
1385
+ if (shouldApplySnapPoints && activeSnapPointOffset !== null) {
1386
+ snapPointOffsetValue = swipeDirection === 'up' ? -activeSnapPointOffset : activeSnapPointOffset;
1387
+ }
1388
+ let dragStyles = swipe ? swipe.getDragStyles() : useRenderElement.EMPTY_OBJECT;
1389
+ if (shouldApplySnapPoints && swipeDirection === 'down') {
1390
+ const baseOffset = activeSnapPointOffset ?? 0;
1391
+ const movementValue = Number.parseFloat(String(dragStyles[DrawerPopupCssVars.swipeMovementY] ?? 0));
1392
+ const nextOffset = Number.isFinite(movementValue) ? baseOffset + movementValue : baseOffset;
1393
+ const shouldDamp = nextOffset < 0;
1394
+ if (swiping && shouldDamp && Number.isFinite(movementValue)) {
1395
+ const overshoot = Math.abs(nextOffset);
1396
+ const dampedOffset = -Math.sqrt(overshoot);
1397
+ const dampedMovement = dampedOffset - baseOffset;
1398
+ dragStyles = {
1399
+ ...dragStyles,
1400
+ transform: undefined,
1401
+ [DrawerPopupCssVars.swipeMovementY]: `${dampedMovement}px`
1402
+ };
1403
+ } else {
1404
+ dragStyles = {
1405
+ ...dragStyles,
1406
+ transform: undefined
1407
+ };
1408
+ }
1409
+ }
1410
+ const element = useRenderElement.useRenderElement('div', componentProps, {
1448
1411
  state,
1449
- ref: [buttonRef, forwardedRef, registerTrigger, triggerElementRef],
1450
- props: [localInteractionProps.getReferenceProps(), rootTriggerProps, {
1451
- [InternalBackdrop.CLICK_TRIGGER_IDENTIFIER]: '',
1452
- id: thisTriggerId
1453
- }, elementProps, getButtonProps],
1454
- stateAttributesMapping: InternalBackdrop.triggerOpenStateMapping
1412
+ props: [rootPopupProps, {
1413
+ 'aria-labelledby': titleElementId,
1414
+ 'aria-describedby': descriptionElementId,
1415
+ role,
1416
+ tabIndex: -1,
1417
+ hidden: !mounted,
1418
+ onKeyDown(event) {
1419
+ if (COMPOSITE_KEYS.has(event.key)) {
1420
+ event.stopPropagation();
1421
+ }
1422
+ },
1423
+ style: {
1424
+ ...dragStyles,
1425
+ [DrawerBackdropCssVars.swipeProgress]: '0',
1426
+ [DrawerPopupCssVars.nestedDrawers]: nestedOpenDialogCount,
1427
+ [DrawerPopupCssVars.height]: popupHeightCssVarValue,
1428
+ [DrawerPopupCssVars.snapPointOffset]: typeof snapPointOffsetValue === 'number' ? `${snapPointOffsetValue}px` : '0px',
1429
+ [DrawerPopupCssVars.frontmostHeight]: frontmostHeight ? `${frontmostHeight}px` : undefined,
1430
+ [DrawerPopupCssVars.swipeStrength]: typeof swipeStrength === 'number' && Number.isFinite(swipeStrength) && swipeStrength > 0 ? `${swipeStrength}` : '1'
1431
+ }
1432
+ }, elementProps],
1433
+ ref: [forwardedRef, store.context.popupRef, store.useStateSetter('popupElement')],
1434
+ stateAttributesMapping
1435
+ });
1436
+ return /*#__PURE__*/jsxRuntime.jsx(InternalBackdrop.FloatingFocusManager, {
1437
+ context: floatingRootContext,
1438
+ openInteractionType: openMethod,
1439
+ disabled: !mounted,
1440
+ closeOnFocusOut: !disablePointerDismissal,
1441
+ initialFocus: resolvedInitialFocus,
1442
+ returnFocus: finalFocus,
1443
+ modal: modal !== false,
1444
+ restoreFocus: "popup",
1445
+ children: element
1455
1446
  });
1456
1447
  });
1457
- if (process.env.NODE_ENV !== "production") DialogTrigger.displayName = "DialogTrigger";
1448
+ if (process.env.NODE_ENV !== "production") DrawerPopup.displayName = "DrawerPopup";
1449
+
1450
+ /**
1451
+ * A portal element that moves the popup to a different part of the DOM.
1452
+ * By default, the portal element is appended to `<body>`.
1453
+ * Renders a `<div>` element.
1454
+ *
1455
+ * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)
1456
+ */
1457
+ const DrawerPortal = DialogPortal;
1458
1458
 
1459
1459
  var _DrawerProviderReport, _DrawerProviderReport2;
1460
1460
  /**
@@ -2534,7 +2534,10 @@ function mergeHiddenState(prevState, nextState) {
2534
2534
 
2535
2535
  exports.COMPOSITE_KEYS = COMPOSITE_KEYS;
2536
2536
  exports.DRAWER_CONTENT_ATTRIBUTE = DRAWER_CONTENT_ATTRIBUTE;
2537
+ exports.DialogPortal = DialogPortal;
2538
+ exports.DialogRoot = DialogRoot;
2537
2539
  exports.DialogStore = DialogStore;
2540
+ exports.DialogTrigger = DialogTrigger;
2538
2541
  exports.DrawerBackdrop = DrawerBackdrop;
2539
2542
  exports.DrawerBackdropCssVars = DrawerBackdropCssVars;
2540
2543
  exports.DrawerContent = DrawerContent;