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