@windmorph/react 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/dist/components/autocomplete/Autocomplete.d.ts +4 -1
  2. package/dist/components/autocomplete/Autocomplete.js +3 -5
  3. package/dist/components/autocomplete/Autocomplete.js.map +1 -1
  4. package/dist/components/badge/Badge.d.ts +5 -2
  5. package/dist/components/badge/Badge.js +7 -3
  6. package/dist/components/badge/Badge.js.map +1 -1
  7. package/dist/components/button/Button.d.ts +4 -2
  8. package/dist/components/button/Button.js.map +1 -1
  9. package/dist/components/button/ButtonContext.d.ts +1 -1
  10. package/dist/components/button/button-pending.js +2 -4
  11. package/dist/components/button/button-pending.js.map +1 -1
  12. package/dist/{composites → components}/button-group/ButtonGroup.d.ts +4 -4
  13. package/dist/{composites → components}/button-group/ButtonGroup.js +9 -7
  14. package/dist/components/button-group/ButtonGroup.js.map +1 -0
  15. package/dist/{composites → components}/button-group/button-group-styles.d.ts +1 -1
  16. package/dist/{composites → components}/button-group/button-group-styles.js +1 -1
  17. package/dist/{composites → components}/button-group/button-group-styles.js.map +1 -1
  18. package/dist/{composites → components}/button-group/lazy-overflow-menu.js +2 -2
  19. package/dist/components/button-group/lazy-overflow-menu.js.map +1 -0
  20. package/dist/{composites → components}/button-group/overflow-menu-list.js +2 -2
  21. package/dist/components/button-group/overflow-menu-list.js.map +1 -0
  22. package/dist/{composites → components}/button-group/use-overflow.js +1 -1
  23. package/dist/{composites → components}/button-group/use-overflow.js.map +1 -1
  24. package/dist/{composites → components}/button-group/use-press-expansion.js +1 -1
  25. package/dist/{composites → components}/button-group/use-press-expansion.js.map +1 -1
  26. package/dist/components/carousel/Carousel.d.ts +4 -1
  27. package/dist/components/carousel/Carousel.js +2 -4
  28. package/dist/components/carousel/Carousel.js.map +1 -1
  29. package/dist/components/checkbox/checkbox-styles.d.ts +3 -3
  30. package/dist/components/chip/Chip.d.ts +8 -2
  31. package/dist/components/chip/Chip.js +3 -8
  32. package/dist/components/chip/Chip.js.map +1 -1
  33. package/dist/components/chip/chip-tokens.d.ts +4 -4
  34. package/dist/components/date-picker/DateField.d.ts +4 -1
  35. package/dist/components/date-picker/DateField.js +3 -1
  36. package/dist/components/date-picker/DateField.js.map +1 -1
  37. package/dist/components/date-picker/DatePicker.d.ts +4 -1
  38. package/dist/components/date-picker/DatePicker.js +5 -2
  39. package/dist/components/date-picker/DatePicker.js.map +1 -1
  40. package/dist/components/date-picker/PickerDialog.d.ts +4 -1
  41. package/dist/components/date-picker/PickerDialog.js +3 -1
  42. package/dist/components/date-picker/PickerDialog.js.map +1 -1
  43. package/dist/components/date-picker/date-picker-styles.d.ts +12 -12
  44. package/dist/components/date-picker/date-picker-tokens.d.ts +1 -1
  45. package/dist/components/dialog/Dialog.d.ts +5 -2
  46. package/dist/components/dialog/Dialog.js +4 -2
  47. package/dist/components/dialog/Dialog.js.map +1 -1
  48. package/dist/components/dialog/dialog-tokens.d.ts +1 -1
  49. package/dist/components/fab-menu/FabMenu.js +2 -2
  50. package/dist/components/fab-menu/FabMenu.js.map +1 -1
  51. package/dist/components/icon-button/IconButton.d.ts +4 -2
  52. package/dist/components/icon-button/IconButton.js.map +1 -1
  53. package/dist/components/labels/LabelsProvider.d.ts +35 -0
  54. package/dist/components/labels/LabelsProvider.js +47 -0
  55. package/dist/components/labels/LabelsProvider.js.map +1 -0
  56. package/dist/components/labels/use-labels.d.ts +16 -0
  57. package/dist/components/labels/use-labels.js +42 -0
  58. package/dist/components/labels/use-labels.js.map +1 -0
  59. package/dist/components/menu/Menu.d.ts +17 -1
  60. package/dist/components/menu/Menu.js +21 -11
  61. package/dist/components/menu/Menu.js.map +1 -1
  62. package/dist/components/menu/menu-styles.js +12 -12
  63. package/dist/components/menu/menu-styles.js.map +1 -1
  64. package/dist/components/menu/menu-tokens.d.ts +1 -1
  65. package/dist/components/menu/menu-tokens.js.map +1 -1
  66. package/dist/components/navigation/NavItem.d.ts +1 -1
  67. package/dist/components/navigation/NavItem.js +9 -6
  68. package/dist/components/navigation/NavItem.js.map +1 -1
  69. package/dist/components/navigation/navigation-styles.js +1 -1
  70. package/dist/components/navigation/navigation-styles.js.map +1 -1
  71. package/dist/components/pull-to-refresh/PullToRefresh.d.ts +1 -1
  72. package/dist/components/pull-to-refresh/PullToRefresh.js +2 -4
  73. package/dist/components/pull-to-refresh/PullToRefresh.js.map +1 -1
  74. package/dist/components/search/SearchField.d.ts +4 -1
  75. package/dist/components/search/SearchField.js +2 -4
  76. package/dist/components/search/SearchField.js.map +1 -1
  77. package/dist/components/search/search-styles.js +2 -2
  78. package/dist/components/search/search-styles.js.map +1 -1
  79. package/dist/components/select/Select.d.ts +4 -1
  80. package/dist/components/select/Select.js +2 -4
  81. package/dist/components/select/Select.js.map +1 -1
  82. package/dist/components/sheet/BottomSheet.d.ts +4 -1
  83. package/dist/components/sheet/BottomSheet.js +2 -4
  84. package/dist/components/sheet/BottomSheet.js.map +1 -1
  85. package/dist/components/sheet/SideSheet.d.ts +4 -1
  86. package/dist/components/sheet/SideSheet.js +2 -4
  87. package/dist/components/sheet/SideSheet.js.map +1 -1
  88. package/dist/components/slider/Slider.d.ts +4 -1
  89. package/dist/components/slider/Slider.js +7 -5
  90. package/dist/components/slider/Slider.js.map +1 -1
  91. package/dist/components/snackbar/Snackbar.d.ts +8 -2
  92. package/dist/components/snackbar/Snackbar.js +2 -4
  93. package/dist/components/snackbar/Snackbar.js.map +1 -1
  94. package/dist/components/snackbar/snackbar-tokens.d.ts +1 -1
  95. package/dist/components/split-button/SplitButton.d.ts +4 -1
  96. package/dist/components/split-button/SplitButton.js +4 -2
  97. package/dist/components/split-button/SplitButton.js.map +1 -1
  98. package/dist/components/switch/switch-styles.d.ts +3 -3
  99. package/dist/components/text-field/TextField.d.ts +4 -1
  100. package/dist/components/text-field/TextField.js +2 -4
  101. package/dist/components/text-field/TextField.js.map +1 -1
  102. package/dist/components/text-field/text-field-styles.js +2 -2
  103. package/dist/components/text-field/text-field-styles.js.map +1 -1
  104. package/dist/components/time-picker/TimeField.d.ts +24 -2
  105. package/dist/components/time-picker/TimeField.js +40 -12
  106. package/dist/components/time-picker/TimeField.js.map +1 -1
  107. package/dist/components/time-picker/TimeFieldList.js +95 -0
  108. package/dist/components/time-picker/TimeFieldList.js.map +1 -0
  109. package/dist/components/time-picker/TimePicker.d.ts +9 -5
  110. package/dist/components/time-picker/TimePicker.js +5 -2
  111. package/dist/components/time-picker/TimePicker.js.map +1 -1
  112. package/dist/components/time-picker/time-list.js +33 -0
  113. package/dist/components/time-picker/time-list.js.map +1 -0
  114. package/dist/components/tooltip/Tooltip.d.ts +1 -1
  115. package/dist/components/tooltip/tooltip-tokens.d.ts +1 -1
  116. package/dist/index.d.ts +40 -38
  117. package/dist/index.js +4 -3
  118. package/dist/primitives/use-m3-interaction.js +10 -6
  119. package/dist/primitives/use-m3-interaction.js.map +1 -1
  120. package/dist/primitives.d.ts +7 -4
  121. package/dist/primitives.js +4 -1
  122. package/dist/styles/utilities.css +22 -15
  123. package/dist/styles.compiled.css +1 -1
  124. package/dist/utils/use-scroll-keys.d.ts +29 -0
  125. package/dist/utils/use-scroll-keys.js +66 -0
  126. package/dist/utils/use-scroll-keys.js.map +1 -0
  127. package/dist/utils/use-scroll-restoration.d.ts +43 -0
  128. package/dist/utils/use-scroll-restoration.js +173 -0
  129. package/dist/utils/use-scroll-restoration.js.map +1 -0
  130. package/dist/windmorph-labels.d.ts +101 -0
  131. package/package.json +1 -1
  132. package/dist/composites/button-group/ButtonGroup.js.map +0 -1
  133. package/dist/composites/button-group/lazy-overflow-menu.js.map +0 -1
  134. package/dist/composites/button-group/overflow-menu-list.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"BottomSheet.js","names":["Overlay"],"sources":["../../../src/components/sheet/BottomSheet.tsx"],"sourcesContent":["'use client';\n\nimport {\n useContext,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n type ComponentPropsWithRef,\n type CSSProperties,\n type HTMLAttributes,\n type KeyboardEvent,\n type PointerEvent,\n type ReactElement,\n type ReactNode,\n type Ref,\n type TransitionEvent,\n} from 'react';\nimport {\n mergeProps,\n useDialog,\n useFocusRing,\n useModalOverlay,\n useObjectRef,\n type AriaDialogProps,\n} from 'react-aria';\nimport { useOverlayTriggerState, type OverlayTriggerState } from 'react-stately';\nimport { Overlay } from '../../primitives/Overlay';\nimport { TriggerContext } from '../../primitives/TriggerContext';\nimport { usePresence } from '../../primitives/use-presence';\nimport { closedByOverlayOptions, type ClosedBy } from '../../utils/closed-by';\nimport { cn } from '../../utils/cn';\nimport { focusFirstInteractive } from '../../utils/focus';\nimport { mergeDataTokens } from '../../utils/merge-data-tokens';\nimport { useThemeScope } from '../../theme/context';\nimport { IconButton } from '../icon-button/IconButton';\nimport { CloseIcon, CollapseIcon } from './sheet-icons';\nimport { contentGesture, scrolledAbove, type ContentGesture } from './bottom-sheet-gesture';\nimport { DRAG_SLOP, ownsGesture } from './sheet-gesture';\nimport {\n EXIT_DISTANCE,\n releaseMotion,\n releaseVelocity,\n settleTarget,\n type DragSample,\n type ReleaseMotion,\n} from './sheet-settle';\nimport { usePeekInert } from './use-peek-inert';\nimport { SHEET_EXIT_PROPERTIES, bottomSheetStyles } from './sheet-styles';\nimport { useKeyboardInset } from './use-keyboard-inset';\nimport { SheetTriggerContext } from './SheetTrigger';\n\nexport interface BottomSheetClassNames {\n /**\n * The outermost element, which places the sheet (where `className` and `style` go too):\n * `fixed` to the window's bottom by default, or, for example, `absolute` to a region.\n */\n container?: string;\n /** The dimmed layer behind a modal sheet. */\n scrim?: string;\n /** The sheet's surface: its colour, corners and shadow. */\n panel?: string;\n /** The drag handle's 48px pressable area. */\n handle?: string;\n /** The drag handle's visible bar, inside `handle`. */\n handleBar?: string;\n /** The row holding the close (modal) or collapse (standard) button. */\n closeRow?: string;\n /** The close or collapse icon button. */\n close?: string;\n /** The scrollable content area. */\n content?: string;\n}\n\n/** Words the bottom sheet announces. */\nexport interface BottomSheetLabels {\n /**\n * The drag handle while the sheet is half open or collapsed (it expands the sheet).\n * @default \"Expand sheet\"\n */\n expand: string;\n /** The drag handle and collapse button of a fully open standard sheet. @default \"Collapse sheet\" */\n collapse: string;\n /** The drag handle of a fully open modal sheet (it closes the sheet). @default \"Close sheet\" */\n dismiss: string;\n /** The close button of a modal sheet that fills the window height. @default \"Close\" */\n close: string;\n}\n\nconst DEFAULT_LABELS: BottomSheetLabels = {\n expand: 'Expand sheet',\n collapse: 'Collapse sheet',\n dismiss: 'Close sheet',\n close: 'Close',\n};\n\n/**\n * Where a standard bottom sheet rests: `collapsed` to its `peekHeight`, or `expanded` to its\n * full height (Compose `SheetValue.PartiallyExpanded` and `SheetValue.Expanded`). A sheet whose\n * content is no taller than the peek height is always expanded.\n */\nexport type BottomSheetDetent = 'collapsed' | 'expanded';\n\n/** The interactive strip at the top of a sheet without a drag handle (m3.material.io). */\nconst DRAG_ZONE = 48;\n\ntype Naming =\n | {\n /** Accessible name when there's no visible title. Required without `aria-labelledby`. */\n 'aria-label': string;\n }\n | {\n /** Id of the visible title that names the sheet. Required without `aria-label`. */\n 'aria-labelledby': string;\n };\n\ninterface BottomSheetOwnProps {\n /**\n * `modal` opens over a scrim as a dialog that blocks the page; `standard` sits over the\n * page without a scrim, so both can be used at once, and rests collapsed at `peekHeight`\n * or expanded (`detent`). @default \"modal\"\n */\n variant?: 'modal' | 'standard';\n /** Whether the sheet is open (controlled). */\n open?: boolean;\n /** Whether the sheet starts open when uncontrolled. @default false, or true when standard */\n defaultOpen?: boolean;\n /** Called when the sheet opens or closes. */\n onOpenChange?: (open: boolean) => void;\n /** Show the drag handle. Without it, the top 48px of the sheet still drags. @default true */\n dragHandle?: boolean;\n /**\n * Whether dragging the handle, the top of the sheet or (touch and pen) its content moves\n * the sheet. Without it the handle stays a button (a press, Enter or Space), and Escape,\n * the scrim and the close button work as before.\n * @default true\n */\n swipeable?: boolean;\n /**\n * Open fully instead of to half the window when the content is taller than half the\n * window (modal). @default false\n */\n skipPartiallyExpanded?: boolean;\n /**\n * Height of a collapsed standard sheet, px. While collapsed, the content's top-level parts\n * that don't fit entirely in it are `inert`. @default 56\n */\n peekHeight?: number;\n /**\n * Where a standard sheet rests (controlled): `collapsed` at `peekHeight` or `expanded` to\n * its full height, so the app can raise or lower it (\"can be set by the system or toggled\n * by the user\", m3.material.io). Its handle, a drag, Escape and its collapse button then\n * only call `onDetentChange`. A modal sheet ignores it.\n */\n detent?: BottomSheetDetent;\n /**\n * Where a standard sheet first rests when uncontrolled; `expanded` opens it fully raised\n * (Compose `rememberStandardBottomSheetState(initialValue = SheetValue.Expanded)`). It\n * applies each time the sheet opens. A modal sheet ignores it.\n * @default \"collapsed\"\n */\n defaultDetent?: BottomSheetDetent;\n /**\n * Called when a standard sheet moves to another detent: by its handle (a press, Enter or\n * Space, or a drag), Escape or its collapse button. Not called for a modal sheet.\n */\n onDetentChange?: (detent: BottomSheetDetent) => void;\n /**\n * How the user can close a modal sheet, as HTML's `<dialog closedby>`: `any` (Escape, a\n * press on the scrim or a drag down), `closerequest` (Escape or a drag down) or `none`\n * (only its buttons or the app). @default \"any\"\n */\n closedBy?: ClosedBy;\n /**\n * While the sheet fills the window height, show a close button (modal) or a collapse\n * button (standard) at its top end. @default true\n */\n closeButton?: boolean;\n /** Replaces the close button's icon. */\n closeIcon?: ReactElement;\n /** Replaces the collapse button's icon. */\n collapseIcon?: ReactElement;\n /** Words the sheet announces, with English defaults. */\n labels?: Partial<BottomSheetLabels>;\n children?: ReactNode | ((props: { close: () => void }) => ReactNode);\n /** Classes for each part, by slot. */\n classNames?: BottomSheetClassNames;\n /**\n * Goes with `style` on the outermost element, the container that places the sheet, so a\n * layout class (`absolute`, `fixed`, a transform) moves the whole sheet and never clips or\n * stretches its surface. Style the surface with `classNames.panel`; `ref`, `data-*` and\n * other attributes go on the panel.\n */\n className?: string;\n /** Goes on the outermost element, with `className`. */\n style?: CSSProperties;\n /** Ref to the panel, the sheet's surface (`role=\"dialog\"`). */\n ref?: Ref<HTMLDivElement>;\n}\n\nexport type BottomSheetProps = BottomSheetOwnProps &\n Naming &\n Omit<ComponentPropsWithRef<'div'>, keyof BottomSheetOwnProps | 'aria-label' | 'aria-labelledby'>;\n\n/**\n * M3 bottom sheet. A `modal` sheet slides up over a scrim, opening to half the window when\n * its content is taller (drag the handle up, or press it, to expand), and closes on a drag\n * down, a press on the scrim or Escape, in one press from any height. A\n * `standard` sheet sits over the page without a scrim, collapsed to `peekHeight` until it\n * is expanded, or opening expanded with `defaultDetent=\"expanded\"`; `detent` and\n * `onDetentChange` control it.\n *\n * @example\n * <SheetTrigger>\n * <Button>Share</Button>\n * <BottomSheet aria-label=\"Share\">{({ close }) => <ShareOptions onPick={close} />}</BottomSheet>\n * </SheetTrigger>\n */\nexport function BottomSheet({\n variant = 'modal',\n open,\n defaultOpen,\n onOpenChange,\n ...props\n}: BottomSheetProps) {\n const trigger = useContext(SheetTriggerContext);\n const ownState = useOverlayTriggerState({\n isOpen: open,\n defaultOpen: defaultOpen ?? (variant === 'standard' ? true : undefined),\n onOpenChange,\n });\n const state = (variant === 'modal' ? trigger?.state : undefined) ?? ownState;\n const { isPresent, isExiting, exitProps } = usePresence(state.isOpen, SHEET_EXIT_PROPERTIES);\n if (!isPresent) return null;\n if (variant === 'standard')\n return (\n <BottomSheetPanel\n {...props}\n variant=\"standard\"\n state={state}\n isExiting={isExiting}\n exitProps={exitProps}\n />\n );\n return (\n <Overlay exiting={isExiting}>\n <ModalBottomSheet\n {...props}\n state={state}\n isExiting={isExiting}\n exitProps={exitProps}\n triggerOverlayProps={trigger?.overlayProps}\n />\n </Overlay>\n );\n}\n\nfunction ModalBottomSheet({\n state,\n isExiting,\n triggerOverlayProps,\n closedBy = 'any',\n classNames,\n ref,\n ...props\n}: Omit<PanelProps, 'variant' | 'panelProps'> & { triggerOverlayProps?: { id?: string } }) {\n const panelRef = useObjectRef(ref as Ref<HTMLDivElement>);\n const { modalProps, underlayProps } = useModalOverlay(\n {\n ...closedByOverlayOptions(closedBy, isExiting),\n // The panel handles Escape itself (below), so a nested control's Escape stays its own.\n isKeyboardDismissDisabled: true,\n },\n state,\n panelRef,\n );\n const { dialogProps } = useDialog(props as AriaDialogProps, panelRef);\n // Initial focus goes to the first interactive element (the drag handle, when there is one;\n // M3), as in a Dialog, not to the panel.\n useEffect(() => focusFirstInteractive(panelRef.current), [panelRef]);\n const styles = bottomSheetStyles();\n return (\n <>\n <div\n aria-hidden=\"true\"\n data-exiting={isExiting || undefined}\n className={styles.scrim({ class: classNames?.scrim })}\n />\n <div {...underlayProps} className=\"fixed inset-0 z-(--md-sys-z-sheet)\">\n <BottomSheetPanel\n {...props}\n variant=\"modal\"\n closedBy={closedBy}\n state={state}\n isExiting={isExiting}\n classNames={classNames}\n ref={panelRef}\n panelProps={mergeProps(triggerOverlayProps ?? {}, modalProps, dialogProps)}\n />\n </div>\n </>\n );\n}\n\ntype PanelProps = Omit<BottomSheetProps, 'open' | 'defaultOpen' | 'onOpenChange' | 'variant'> & {\n variant: 'modal' | 'standard';\n state: OverlayTriggerState;\n isExiting: boolean;\n exitProps: ReturnType<typeof usePresence>['exitProps'];\n /** Props from the modal overlay and dialog hooks. */\n panelProps?: HTMLAttributes<HTMLElement>;\n};\n\n/** The sheet itself: the moving wrapper, the panel, its handle and its detents. */\nfunction BottomSheetPanel({\n variant,\n state,\n isExiting,\n exitProps,\n panelProps: overlayPanelProps,\n dragHandle = true,\n swipeable = true,\n skipPartiallyExpanded = false,\n peekHeight = 56,\n detent: detentProp,\n defaultDetent = 'collapsed',\n onDetentChange,\n closedBy = 'any',\n closeButton = true,\n closeIcon,\n collapseIcon,\n labels: labelsProp,\n children,\n className,\n classNames,\n style,\n ref,\n ...rest\n}: PanelProps) {\n const labels = { ...DEFAULT_LABELS, ...labelsProp };\n const standard = variant === 'standard';\n const panelRef = useObjectRef(ref as Ref<HTMLDivElement>);\n const motionRef = useRef<HTMLDivElement>(null);\n const styles = bottomSheetStyles({ variant, swipeable });\n const [height, setHeight] = useState(0);\n const [viewport, setViewport] = useState({ width: 0, height: 0 });\n // The lower detent is half the window (modal) or the peek height (standard); `null` rests\n // on the lower detent when there is one.\n const [ownDetent, setOwnDetent] = useState<'expanded' | 'lower' | null>(\n standard && defaultDetent === 'expanded' ? 'expanded' : null,\n );\n const controlledDetent =\n standard && detentProp !== undefined\n ? detentProp === 'expanded'\n ? 'expanded'\n : 'lower'\n : undefined;\n const detent = controlledDetent ?? ownDetent;\n const [drag, setDrag] = useState<number | null>(null);\n // A drag's settle continues at the speed the pointer let go with, as Compose's\n // `animateTo(target, velocity)` does: the spring starts with that velocity, as a `linear()`\n // easing and duration set on the moving layer until its move ends (2026-10-17; it started\n // from rest). Under reduced motion settling stays instant.\n const { motion: motionScheme } = useThemeScope();\n const [settleMotion, setSettleMotion] = useState<ReleaseMotion | null>(null);\n // A drag let go to close: the offset stays where the pointer left it, so the exit leaves\n // from there (and, under reduced motion, fades there, as the side sheet does). If the\n // sheet is still open after that render, the app refused and the sheet springs back.\n const [dragClosing, setDragClosing] = useState(false);\n if (dragClosing && state.isOpen) {\n setDragClosing(false);\n setDrag(null);\n setSettleMotion(null);\n }\n const contentRef = useRef<HTMLDivElement>(null);\n const pointer = useRef({\n pending: false,\n fromHandle: false,\n /** Started on the content, where a drag only moves the sheet once it picks `drag`. */\n fromContent: false,\n gesture: 'undecided' as ContentGesture,\n x: 0,\n y: 0,\n offset: 0,\n id: 0,\n samples: [] as DragSample[],\n });\n\n // The lower detent: a modal sheet taller than half the window opens to half (Compose's\n // PartiallyExpanded); a standard sheet collapses to its peek height (BottomSheetScaffold).\n const lowerOffset = standard ? height - peekHeight : height - viewport.height / 2;\n const hasLower = standard ? height > peekHeight : !skipPartiallyExpanded && lowerOffset > 0;\n // Where the sheet rests: the lower detent only when it has one.\n const current = detent === 'expanded' || !hasLower ? 'expanded' : 'lower';\n const restingOffset = current === 'lower' ? lowerOffset : 0;\n const offset = drag ?? restingOffset;\n const maxOffset = standard ? Math.max(0, lowerOffset) : height;\n const detentName = current === 'expanded' ? 'expanded' : standard ? 'collapsed' : 'partial';\n\n // Moving down to a lower detent while staying open (Escape, the handle or the collapse\n // button on a full sheet, or the app lowering it) runs on the fast effects spring, which\n // doesn't overshoot, as Compose's `partialExpand()` (`hideMotionSpec`); on the spatial\n // spring the sheet dropped a few pixels below the detent. A drag's settle keeps the\n // spatial spring (Compose's `anchoredDraggableMotion`), and so do the entry and every\n // move up. It's state, rendered with the move, so the transition starts with it.\n const [lowering, setLowering] = useState(false);\n const [previousControlled, setPreviousControlled] = useState(controlledDetent);\n if (controlledDetent !== previousControlled) {\n setPreviousControlled(controlledDetent);\n setLowering(controlledDetent === 'lower');\n setSettleMotion(null);\n }\n\n /** Moves to `next`, telling `onDetentChange` when a standard sheet's detent changes. */\n const setDetent = (next: 'expanded' | 'lower', fromDrag = false) => {\n setLowering(next === 'lower' && !fromDrag);\n if (!fromDrag) setSettleMotion(null);\n if (controlledDetent === undefined) setOwnDetent(next);\n const resting = next === 'lower' && hasLower ? 'lower' : 'expanded';\n if (standard && resting !== current)\n onDetentChange?.(resting === 'expanded' ? 'expanded' : 'collapsed');\n };\n\n useLayoutEffect(() => {\n const element = motionRef.current;\n if (!element) return;\n const measure = () => {\n setHeight(element.offsetHeight);\n setViewport({ width: window.innerWidth, height: window.innerHeight });\n };\n measure();\n const observer = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(measure);\n observer?.observe(element);\n window.addEventListener('resize', measure);\n return () => {\n observer?.disconnect();\n window.removeEventListener('resize', measure);\n };\n }, []);\n\n const close = state.close;\n\n // A modal sheet's last anchor is closed, unless the user may not close it (`closedBy=\"none\"`).\n const closable = !standard && closedBy !== 'none';\n const anchors = [0, ...(hasLower ? [lowerOffset] : []), ...(closable ? [height] : [])];\n\n const settle = (target: number, velocity: number) => {\n const closing = !standard && target >= height - 0.5;\n // A closing sheet leaves to 112% of its height on the fast spatial spring (its exit).\n const end = closing ? height * EXIT_DISTANCE : target;\n setSettleMotion(releaseMotion(motionScheme, closing, velocity, end - (drag ?? restingOffset)));\n if (closing) {\n setDragClosing(true);\n close();\n } else {\n setDrag(null);\n setDetent(target === 0 ? 'expanded' : 'lower', true);\n }\n };\n\n /** The handle's action: expand a lower sheet, collapse a standard one, close a modal one. */\n const cycle = () => {\n if (current === 'lower') setDetent('expanded');\n else if (standard) setDetent('lower');\n else close();\n };\n\n const startPointer = (\n event: PointerEvent<HTMLElement>,\n fromHandle: boolean,\n fromContent = false,\n ) => {\n // A leaving sheet takes no new drag.\n if (event.button !== 0 || isExiting) return;\n setSettleMotion(null);\n pointer.current = {\n pending: true,\n fromHandle,\n fromContent,\n gesture: 'undecided',\n x: event.clientX,\n y: event.clientY,\n offset: restingOffset,\n id: event.pointerId,\n samples: [{ position: event.clientY, time: event.timeStamp }],\n };\n };\n const movePointer = (event: PointerEvent<HTMLElement>) => {\n const start = pointer.current;\n if (!start.pending) return;\n if (start.fromContent && start.gesture !== 'drag') {\n // Once a touch on the content picks scrolling or dragging, it keeps that choice.\n start.gesture = contentGesture(\n event.clientX - start.x,\n event.clientY - start.y,\n scrolledAbove(event.target as Element, contentRef.current!),\n current === 'lower',\n );\n if (start.gesture === 'undecided') return;\n if (start.gesture === 'scroll') {\n start.pending = false;\n return;\n }\n }\n // Without `swipeable` the handle is only a button: a move never drags it.\n if (!swipeable) return;\n start.samples = [...start.samples, { position: event.clientY, time: event.timeStamp }];\n if (drag === null && Math.abs(event.clientY - start.y) <= DRAG_SLOP) return;\n if (drag === null) event.currentTarget.setPointerCapture?.(start.id);\n const raw = start.offset + event.clientY - start.y;\n setDrag(Math.min(maxOffset, Math.max(0, raw)));\n // A drag up from the content that has raised the sheet fully scrolls the content with the\n // rest of the move, as Compose's nested scroll hands it over.\n if (start.fromContent && contentRef.current) contentRef.current.scrollTop = Math.max(0, -raw);\n };\n const endPointer = (event: PointerEvent<HTMLElement>) => {\n const start = pointer.current;\n if (!start.pending) return;\n start.pending = false;\n if (drag === null) {\n // A press on the handle cycles the sheet (Compose's handle click).\n if (start.fromHandle) cycle();\n return;\n }\n // From the last 100ms of moves, so a slow drag ending in a flick reads as a fling.\n const velocity = releaseVelocity([\n ...start.samples,\n { position: event.clientY, time: event.timeStamp },\n ]);\n settle(settleTarget(anchors, start.offset, drag, velocity), velocity);\n };\n const cancelPointer = () => {\n pointer.current.pending = false;\n setSettleMotion(null);\n setDrag(null);\n };\n\n // Without a handle the top 48px of the sheet still drags it (m3.material.io).\n const onZonePointerDown = (event: PointerEvent<HTMLDivElement>) => {\n if (!swipeable) return;\n const top = event.currentTarget.getBoundingClientRect().top;\n if (event.clientY - top <= DRAG_ZONE) startPointer(event, false);\n };\n\n // A touch (or pen) drag down on content scrolled to its top moves the sheet, as the\n // handle does. Mouse drags select text instead, as they always have on the web.\n const onContentPointerDown = (event: PointerEvent<HTMLDivElement>) => {\n const content = contentRef.current;\n const target = event.target as Element;\n pointer.current.fromContent = false;\n if (!swipeable || event.pointerType === 'mouse' || !content?.contains(target)) return;\n if (ownsGesture(target, content)) return;\n startPointer(event, false, true);\n };\n\n // While the content is at its top, the browser only scrolls it for upward and sideways\n // moves (`touch-action: pan-x pan-down`), so a downward one reaches the sheet as pointer\n // events. Browsers without `pan-down` (iOS Safari) are stopped from scrolling or\n // rubber-banding by cancelling the touch moves of a drag.\n useEffect(() => {\n const content = contentRef.current;\n if (!content) return;\n const update = () => content.toggleAttribute('data-at-top', content.scrollTop <= 0);\n const onTouchMove = (event: TouchEvent) => {\n const start = pointer.current;\n if (start.pending && start.fromContent && start.gesture === 'drag' && event.cancelable)\n event.preventDefault();\n };\n update();\n content.addEventListener('scroll', update, { passive: true });\n content.addEventListener('touchmove', onTouchMove, { passive: false });\n return () => {\n content.removeEventListener('scroll', update);\n content.removeEventListener('touchmove', onTouchMove);\n };\n }, []);\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Escape' || isExiting) return;\n if (standard) {\n if (current !== 'expanded' || !hasLower) return;\n event.stopPropagation();\n setDetent('lower');\n return;\n }\n // Escape is a close request: it closes a modal sheet in one press, from any detent (WAI-ARIA\n // APG modal dialog; HTML's close request), unless the user may not close it.\n if (closedBy === 'none') return;\n event.stopPropagation();\n close();\n };\n\n const handleRef = useRef<HTMLDivElement>(null);\n usePeekInert(\n panelRef,\n contentRef,\n detentName === 'collapsed',\n peekHeight,\n () => handleRef.current ?? panelRef.current,\n );\n\n // The collapse button leaves once the sheet stops filling the window, so focus moves to the\n // handle (or the panel) first instead of dropping to the page.\n const collapseFromButton = () => {\n (handleRef.current ?? panelRef.current)?.focus({ preventScroll: true });\n setDetent('lower');\n };\n\n const { focusProps: handleFocusProps, isFocusVisible: handleFocusVisible } = useFocusRing();\n const exiting = isExiting || undefined;\n const dragging = (drag !== null && !dragClosing) || undefined;\n const handleLabel =\n current === 'lower' ? labels.expand : standard ? labels.collapse : labels.dismiss;\n // The sheet fills the window up to its top margin: 72px, or 56px above 640px wide.\n const topMargin = viewport.width > 640 ? 56 : 72;\n const fillsWindow =\n current === 'expanded' && viewport.height > 0 && height >= viewport.height - topMargin - 0.5;\n const showCloseButton = closeButton && fillsWindow && (!standard || hasLower);\n\n // A standard sheet is a non-modal dialog: no scrim, no focus trap, the page stays usable.\n const roleProps: HTMLAttributes<HTMLElement> = standard\n ? { role: 'dialog', tabIndex: -1 }\n : (overlayPanelProps ?? {});\n const panelProps = mergeProps(rest, roleProps, { onKeyDown });\n const zone = !dragHandle;\n const keyboardInset = useKeyboardInset();\n\n return (\n <div\n style={{ '--m3-sheet-keyboard-inset': `${keyboardInset}px`, ...style } as CSSProperties}\n className={styles.container({ class: cn(classNames?.container, className) })}\n >\n <div\n {...mergeProps(exitProps, {\n onTransitionEnd: (event: TransitionEvent<HTMLDivElement>) => {\n if (event.target === event.currentTarget && event.propertyName === 'translate')\n setSettleMotion(null);\n },\n })}\n ref={motionRef}\n data-exiting={exiting}\n data-dragging={dragging}\n data-lowering={lowering || undefined}\n data-settling={settleMotion ? '' : undefined}\n style={\n {\n '--m3-sheet-offset': `${offset}px`,\n ...(settleMotion && {\n transitionDuration: `${settleMotion.duration}ms`,\n transitionTimingFunction: settleMotion.easing,\n }),\n } as CSSProperties\n }\n className={styles.motion()}\n >\n <div\n {...panelProps}\n // Content drags listen in the capture phase: pressable content (list items, buttons)\n // stops its pointer events from bubbling.\n onPointerDownCapture={(event) => {\n panelProps.onPointerDownCapture?.(event);\n onContentPointerDown(event);\n }}\n onPointerMoveCapture={(event) => {\n panelProps.onPointerMoveCapture?.(event);\n if (pointer.current.fromContent) movePointer(event);\n }}\n onPointerUpCapture={(event) => {\n panelProps.onPointerUpCapture?.(event);\n if (pointer.current.fromContent) endPointer(event);\n }}\n onPointerCancelCapture={(event) => {\n panelProps.onPointerCancelCapture?.(event);\n if (pointer.current.fromContent) cancelPointer();\n }}\n onPointerDown={(event) => {\n panelProps.onPointerDown?.(event);\n if (zone && !pointer.current.fromContent) onZonePointerDown(event);\n }}\n onPointerMove={(event) => {\n panelProps.onPointerMove?.(event);\n if (zone && !pointer.current.fromContent) movePointer(event);\n }}\n onPointerUp={(event) => {\n panelProps.onPointerUp?.(event);\n if (zone && !pointer.current.fromContent) endPointer(event);\n }}\n onPointerCancel={(event) => {\n panelProps.onPointerCancel?.(event);\n if (zone && !pointer.current.fromContent) cancelPointer();\n }}\n ref={panelRef}\n // The component tokens' hooks (sheet-tokens.ts); `data-variant` is also public.\n data-tokens={mergeDataTokens('md-comp-sheet-bottom-docked', rest)}\n data-variant={variant}\n data-detent={detentName}\n data-exiting={exiting}\n data-dragging={dragging}\n className={styles.panel({ class: classNames?.panel })}\n >\n <TriggerContext value={null}>\n {dragHandle && (\n <div\n {...handleFocusProps}\n ref={handleRef}\n role=\"button\"\n tabIndex={0}\n data-focus-visible={handleFocusVisible || undefined}\n aria-label={handleLabel}\n data-dragging={dragging}\n onPointerDown={(event) => startPointer(event, true)}\n onPointerMove={movePointer}\n onPointerUp={endPointer}\n onPointerCancel={cancelPointer}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n cycle();\n }\n }}\n className={styles.handle({ class: classNames?.handle })}\n >\n <span\n data-slot=\"drag-handle\"\n className={styles.handleBar({ class: classNames?.handleBar })}\n />\n </div>\n )}\n {showCloseButton && (\n <div className={styles.closeRow({ class: classNames?.closeRow })}>\n <IconButton\n variant=\"standard\"\n density={0}\n icon={\n standard ? (collapseIcon ?? <CollapseIcon />) : (closeIcon ?? <CloseIcon />)\n }\n aria-label={standard ? labels.collapse : labels.close}\n onPress={standard ? collapseFromButton : close}\n className={classNames?.close}\n />\n </div>\n )}\n <div\n ref={contentRef}\n data-can-rise={current === 'lower' || undefined}\n className={styles.content({ class: classNames?.content })}\n >\n {typeof children === 'function' ? children({ close }) : children}\n </div>\n </TriggerContext>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"BottomSheet.js","names":["Overlay"],"sources":["../../../src/components/sheet/BottomSheet.tsx"],"sourcesContent":["'use client';\n\nimport {\n useContext,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n type ComponentPropsWithRef,\n type CSSProperties,\n type HTMLAttributes,\n type KeyboardEvent,\n type PointerEvent,\n type ReactElement,\n type ReactNode,\n type Ref,\n type TransitionEvent,\n} from 'react';\nimport {\n mergeProps,\n useDialog,\n useFocusRing,\n useModalOverlay,\n useObjectRef,\n type AriaDialogProps,\n} from 'react-aria';\nimport { useOverlayTriggerState, type OverlayTriggerState } from 'react-stately';\nimport { useLabels } from '../labels/use-labels';\nimport { Overlay } from '../../primitives/Overlay';\nimport { TriggerContext } from '../../primitives/TriggerContext';\nimport { usePresence } from '../../primitives/use-presence';\nimport { closedByOverlayOptions, type ClosedBy } from '../../utils/closed-by';\nimport { cn } from '../../utils/cn';\nimport { focusFirstInteractive } from '../../utils/focus';\nimport { mergeDataTokens } from '../../utils/merge-data-tokens';\nimport { useThemeScope } from '../../theme/context';\nimport { IconButton } from '../icon-button/IconButton';\nimport { CloseIcon, CollapseIcon } from './sheet-icons';\nimport { contentGesture, scrolledAbove, type ContentGesture } from './bottom-sheet-gesture';\nimport { DRAG_SLOP, ownsGesture } from './sheet-gesture';\nimport {\n EXIT_DISTANCE,\n releaseMotion,\n releaseVelocity,\n settleTarget,\n type DragSample,\n type ReleaseMotion,\n} from './sheet-settle';\nimport { usePeekInert } from './use-peek-inert';\nimport { SHEET_EXIT_PROPERTIES, bottomSheetStyles } from './sheet-styles';\nimport { useKeyboardInset } from './use-keyboard-inset';\nimport { SheetTriggerContext } from './SheetTrigger';\n\nexport interface BottomSheetClassNames {\n /**\n * The outermost element, which places the sheet (where `className` and `style` go too):\n * `fixed` to the window's bottom by default, or, for example, `absolute` to a region.\n */\n container?: string;\n /** The dimmed layer behind a modal sheet. */\n scrim?: string;\n /** The sheet's surface: its colour, corners and shadow. */\n panel?: string;\n /** The drag handle's 48px pressable area. */\n handle?: string;\n /** The drag handle's visible bar, inside `handle`. */\n handleBar?: string;\n /** The row holding the close (modal) or collapse (standard) button. */\n closeRow?: string;\n /** The close or collapse icon button. */\n close?: string;\n /** The scrollable content area. */\n content?: string;\n}\n\n/** Words the bottom sheet announces. */\nexport interface BottomSheetLabels {\n /**\n * The drag handle while the sheet is half open or collapsed (it expands the sheet).\n * @default \"Expand sheet\"\n */\n expand: string;\n /** The drag handle and collapse button of a fully open standard sheet. @default \"Collapse sheet\" */\n collapse: string;\n /** The drag handle of a fully open modal sheet (it closes the sheet). @default \"Close sheet\" */\n dismiss: string;\n /** The close button of a modal sheet that fills the window height. @default \"Close\" */\n close: string;\n}\n\nconst DEFAULT_LABELS: BottomSheetLabels = {\n expand: 'Expand sheet',\n collapse: 'Collapse sheet',\n dismiss: 'Close sheet',\n close: 'Close',\n};\n\n/**\n * Where a standard bottom sheet rests: `collapsed` to its `peekHeight`, or `expanded` to its\n * full height (Compose `SheetValue.PartiallyExpanded` and `SheetValue.Expanded`). A sheet whose\n * content is no taller than the peek height is always expanded.\n */\nexport type BottomSheetDetent = 'collapsed' | 'expanded';\n\n/** The interactive strip at the top of a sheet without a drag handle (m3.material.io). */\nconst DRAG_ZONE = 48;\n\ntype Naming =\n | {\n /** Accessible name when there's no visible title. Required without `aria-labelledby`. */\n 'aria-label': string;\n }\n | {\n /** Id of the visible title that names the sheet. Required without `aria-label`. */\n 'aria-labelledby': string;\n };\n\ninterface BottomSheetOwnProps {\n /**\n * `modal` opens over a scrim as a dialog that blocks the page; `standard` sits over the\n * page without a scrim, so both can be used at once, and rests collapsed at `peekHeight`\n * or expanded (`detent`). @default \"modal\"\n */\n variant?: 'modal' | 'standard';\n /** Whether the sheet is open (controlled). */\n open?: boolean;\n /** Whether the sheet starts open when uncontrolled. @default false, or true when standard */\n defaultOpen?: boolean;\n /** Called when the sheet opens or closes. */\n onOpenChange?: (open: boolean) => void;\n /** Show the drag handle. Without it, the top 48px of the sheet still drags. @default true */\n dragHandle?: boolean;\n /**\n * Whether dragging the handle, the top of the sheet or (touch and pen) its content moves\n * the sheet. Without it the handle stays a button (a press, Enter or Space), and Escape,\n * the scrim and the close button work as before.\n * @default true\n */\n swipeable?: boolean;\n /**\n * Open fully instead of to half the window when the content is taller than half the\n * window (modal). @default false\n */\n skipPartiallyExpanded?: boolean;\n /**\n * Height of a collapsed standard sheet, px. While collapsed, the content's top-level parts\n * that don't fit entirely in it are `inert`. @default 56\n */\n peekHeight?: number;\n /**\n * Where a standard sheet rests (controlled): `collapsed` at `peekHeight` or `expanded` to\n * its full height, so the app can raise or lower it (\"can be set by the system or toggled\n * by the user\", m3.material.io). Its handle, a drag, Escape and its collapse button then\n * only call `onDetentChange`. A modal sheet ignores it.\n */\n detent?: BottomSheetDetent;\n /**\n * Where a standard sheet first rests when uncontrolled; `expanded` opens it fully raised\n * (Compose `rememberStandardBottomSheetState(initialValue = SheetValue.Expanded)`). It\n * applies each time the sheet opens. A modal sheet ignores it.\n * @default \"collapsed\"\n */\n defaultDetent?: BottomSheetDetent;\n /**\n * Called when a standard sheet moves to another detent: by its handle (a press, Enter or\n * Space, or a drag), Escape or its collapse button. Not called for a modal sheet.\n */\n onDetentChange?: (detent: BottomSheetDetent) => void;\n /**\n * How the user can close a modal sheet, as HTML's `<dialog closedby>`: `any` (Escape, a\n * press on the scrim or a drag down), `closerequest` (Escape or a drag down) or `none`\n * (only its buttons or the app). @default \"any\"\n */\n closedBy?: ClosedBy;\n /**\n * While the sheet fills the window height, show a close button (modal) or a collapse\n * button (standard) at its top end. @default true\n */\n closeButton?: boolean;\n /** Replaces the close button's icon. */\n closeIcon?: ReactElement;\n /** Replaces the collapse button's icon. */\n collapseIcon?: ReactElement;\n /**\n * Words the sheet announces, with English defaults. A {@link LabelsProvider} sets them for the\n * whole app.\n */\n labels?: Partial<BottomSheetLabels>;\n children?: ReactNode | ((props: { close: () => void }) => ReactNode);\n /** Classes for each part, by slot. */\n classNames?: BottomSheetClassNames;\n /**\n * Goes with `style` on the outermost element, the container that places the sheet, so a\n * layout class (`absolute`, `fixed`, a transform) moves the whole sheet and never clips or\n * stretches its surface. Style the surface with `classNames.panel`; `ref`, `data-*` and\n * other attributes go on the panel.\n */\n className?: string;\n /** Goes on the outermost element, with `className`. */\n style?: CSSProperties;\n /** Ref to the panel, the sheet's surface (`role=\"dialog\"`). */\n ref?: Ref<HTMLDivElement>;\n}\n\nexport type BottomSheetProps = BottomSheetOwnProps &\n Naming &\n Omit<ComponentPropsWithRef<'div'>, keyof BottomSheetOwnProps | 'aria-label' | 'aria-labelledby'>;\n\n/**\n * M3 bottom sheet. A `modal` sheet slides up over a scrim, opening to half the window when\n * its content is taller (drag the handle up, or press it, to expand), and closes on a drag\n * down, a press on the scrim or Escape, in one press from any height. A\n * `standard` sheet sits over the page without a scrim, collapsed to `peekHeight` until it\n * is expanded, or opening expanded with `defaultDetent=\"expanded\"`; `detent` and\n * `onDetentChange` control it.\n *\n * @example\n * <SheetTrigger>\n * <Button>Share</Button>\n * <BottomSheet aria-label=\"Share\">{({ close }) => <ShareOptions onPick={close} />}</BottomSheet>\n * </SheetTrigger>\n */\nexport function BottomSheet({\n variant = 'modal',\n open,\n defaultOpen,\n onOpenChange,\n ...props\n}: BottomSheetProps) {\n const trigger = useContext(SheetTriggerContext);\n const ownState = useOverlayTriggerState({\n isOpen: open,\n defaultOpen: defaultOpen ?? (variant === 'standard' ? true : undefined),\n onOpenChange,\n });\n const state = (variant === 'modal' ? trigger?.state : undefined) ?? ownState;\n const { isPresent, isExiting, exitProps } = usePresence(state.isOpen, SHEET_EXIT_PROPERTIES);\n if (!isPresent) return null;\n if (variant === 'standard')\n return (\n <BottomSheetPanel\n {...props}\n variant=\"standard\"\n state={state}\n isExiting={isExiting}\n exitProps={exitProps}\n />\n );\n return (\n <Overlay exiting={isExiting}>\n <ModalBottomSheet\n {...props}\n state={state}\n isExiting={isExiting}\n exitProps={exitProps}\n triggerOverlayProps={trigger?.overlayProps}\n />\n </Overlay>\n );\n}\n\nfunction ModalBottomSheet({\n state,\n isExiting,\n triggerOverlayProps,\n closedBy = 'any',\n classNames,\n ref,\n ...props\n}: Omit<PanelProps, 'variant' | 'panelProps'> & { triggerOverlayProps?: { id?: string } }) {\n const panelRef = useObjectRef(ref as Ref<HTMLDivElement>);\n const { modalProps, underlayProps } = useModalOverlay(\n {\n ...closedByOverlayOptions(closedBy, isExiting),\n // The panel handles Escape itself (below), so a nested control's Escape stays its own.\n isKeyboardDismissDisabled: true,\n },\n state,\n panelRef,\n );\n const { dialogProps } = useDialog(props as AriaDialogProps, panelRef);\n // Initial focus goes to the first interactive element (the drag handle, when there is one;\n // M3), as in a Dialog, not to the panel.\n useEffect(() => focusFirstInteractive(panelRef.current), [panelRef]);\n const styles = bottomSheetStyles();\n return (\n <>\n <div\n aria-hidden=\"true\"\n data-exiting={isExiting || undefined}\n className={styles.scrim({ class: classNames?.scrim })}\n />\n <div {...underlayProps} className=\"fixed inset-0 z-(--md-sys-z-sheet)\">\n <BottomSheetPanel\n {...props}\n variant=\"modal\"\n closedBy={closedBy}\n state={state}\n isExiting={isExiting}\n classNames={classNames}\n ref={panelRef}\n panelProps={mergeProps(triggerOverlayProps ?? {}, modalProps, dialogProps)}\n />\n </div>\n </>\n );\n}\n\ntype PanelProps = Omit<BottomSheetProps, 'open' | 'defaultOpen' | 'onOpenChange' | 'variant'> & {\n variant: 'modal' | 'standard';\n state: OverlayTriggerState;\n isExiting: boolean;\n exitProps: ReturnType<typeof usePresence>['exitProps'];\n /** Props from the modal overlay and dialog hooks. */\n panelProps?: HTMLAttributes<HTMLElement>;\n};\n\n/** The sheet itself: the moving wrapper, the panel, its handle and its detents. */\nfunction BottomSheetPanel({\n variant,\n state,\n isExiting,\n exitProps,\n panelProps: overlayPanelProps,\n dragHandle = true,\n swipeable = true,\n skipPartiallyExpanded = false,\n peekHeight = 56,\n detent: detentProp,\n defaultDetent = 'collapsed',\n onDetentChange,\n closedBy = 'any',\n closeButton = true,\n closeIcon,\n collapseIcon,\n labels: labelsProp,\n children,\n className,\n classNames,\n style,\n ref,\n ...rest\n}: PanelProps) {\n const labels = useLabels('BottomSheet', DEFAULT_LABELS, labelsProp);\n const standard = variant === 'standard';\n const panelRef = useObjectRef(ref as Ref<HTMLDivElement>);\n const motionRef = useRef<HTMLDivElement>(null);\n const styles = bottomSheetStyles({ variant, swipeable });\n const [height, setHeight] = useState(0);\n const [viewport, setViewport] = useState({ width: 0, height: 0 });\n // The lower detent is half the window (modal) or the peek height (standard); `null` rests\n // on the lower detent when there is one.\n const [ownDetent, setOwnDetent] = useState<'expanded' | 'lower' | null>(\n standard && defaultDetent === 'expanded' ? 'expanded' : null,\n );\n const controlledDetent =\n standard && detentProp !== undefined\n ? detentProp === 'expanded'\n ? 'expanded'\n : 'lower'\n : undefined;\n const detent = controlledDetent ?? ownDetent;\n const [drag, setDrag] = useState<number | null>(null);\n // A drag's settle continues at the speed the pointer let go with, as Compose's\n // `animateTo(target, velocity)` does: the spring starts with that velocity, as a `linear()`\n // easing and duration set on the moving layer until its move ends (2026-10-17; it started\n // from rest). Under reduced motion settling stays instant.\n const { motion: motionScheme } = useThemeScope();\n const [settleMotion, setSettleMotion] = useState<ReleaseMotion | null>(null);\n // A drag let go to close: the offset stays where the pointer left it, so the exit leaves\n // from there (and, under reduced motion, fades there, as the side sheet does). If the\n // sheet is still open after that render, the app refused and the sheet springs back.\n const [dragClosing, setDragClosing] = useState(false);\n if (dragClosing && state.isOpen) {\n setDragClosing(false);\n setDrag(null);\n setSettleMotion(null);\n }\n const contentRef = useRef<HTMLDivElement>(null);\n const pointer = useRef({\n pending: false,\n fromHandle: false,\n /** Started on the content, where a drag only moves the sheet once it picks `drag`. */\n fromContent: false,\n gesture: 'undecided' as ContentGesture,\n x: 0,\n y: 0,\n offset: 0,\n id: 0,\n samples: [] as DragSample[],\n });\n\n // The lower detent: a modal sheet taller than half the window opens to half (Compose's\n // PartiallyExpanded); a standard sheet collapses to its peek height (BottomSheetScaffold).\n const lowerOffset = standard ? height - peekHeight : height - viewport.height / 2;\n const hasLower = standard ? height > peekHeight : !skipPartiallyExpanded && lowerOffset > 0;\n // Where the sheet rests: the lower detent only when it has one.\n const current = detent === 'expanded' || !hasLower ? 'expanded' : 'lower';\n const restingOffset = current === 'lower' ? lowerOffset : 0;\n const offset = drag ?? restingOffset;\n const maxOffset = standard ? Math.max(0, lowerOffset) : height;\n const detentName = current === 'expanded' ? 'expanded' : standard ? 'collapsed' : 'partial';\n\n // Moving down to a lower detent while staying open (Escape, the handle or the collapse\n // button on a full sheet, or the app lowering it) runs on the fast effects spring, which\n // doesn't overshoot, as Compose's `partialExpand()` (`hideMotionSpec`); on the spatial\n // spring the sheet dropped a few pixels below the detent. A drag's settle keeps the\n // spatial spring (Compose's `anchoredDraggableMotion`), and so do the entry and every\n // move up. It's state, rendered with the move, so the transition starts with it.\n const [lowering, setLowering] = useState(false);\n const [previousControlled, setPreviousControlled] = useState(controlledDetent);\n if (controlledDetent !== previousControlled) {\n setPreviousControlled(controlledDetent);\n setLowering(controlledDetent === 'lower');\n setSettleMotion(null);\n }\n\n /** Moves to `next`, telling `onDetentChange` when a standard sheet's detent changes. */\n const setDetent = (next: 'expanded' | 'lower', fromDrag = false) => {\n setLowering(next === 'lower' && !fromDrag);\n if (!fromDrag) setSettleMotion(null);\n if (controlledDetent === undefined) setOwnDetent(next);\n const resting = next === 'lower' && hasLower ? 'lower' : 'expanded';\n if (standard && resting !== current)\n onDetentChange?.(resting === 'expanded' ? 'expanded' : 'collapsed');\n };\n\n useLayoutEffect(() => {\n const element = motionRef.current;\n if (!element) return;\n const measure = () => {\n setHeight(element.offsetHeight);\n setViewport({ width: window.innerWidth, height: window.innerHeight });\n };\n measure();\n const observer = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(measure);\n observer?.observe(element);\n window.addEventListener('resize', measure);\n return () => {\n observer?.disconnect();\n window.removeEventListener('resize', measure);\n };\n }, []);\n\n const close = state.close;\n\n // A modal sheet's last anchor is closed, unless the user may not close it (`closedBy=\"none\"`).\n const closable = !standard && closedBy !== 'none';\n const anchors = [0, ...(hasLower ? [lowerOffset] : []), ...(closable ? [height] : [])];\n\n const settle = (target: number, velocity: number) => {\n const closing = !standard && target >= height - 0.5;\n // A closing sheet leaves to 112% of its height on the fast spatial spring (its exit).\n const end = closing ? height * EXIT_DISTANCE : target;\n setSettleMotion(releaseMotion(motionScheme, closing, velocity, end - (drag ?? restingOffset)));\n if (closing) {\n setDragClosing(true);\n close();\n } else {\n setDrag(null);\n setDetent(target === 0 ? 'expanded' : 'lower', true);\n }\n };\n\n /** The handle's action: expand a lower sheet, collapse a standard one, close a modal one. */\n const cycle = () => {\n if (current === 'lower') setDetent('expanded');\n else if (standard) setDetent('lower');\n else close();\n };\n\n const startPointer = (\n event: PointerEvent<HTMLElement>,\n fromHandle: boolean,\n fromContent = false,\n ) => {\n // A leaving sheet takes no new drag.\n if (event.button !== 0 || isExiting) return;\n setSettleMotion(null);\n pointer.current = {\n pending: true,\n fromHandle,\n fromContent,\n gesture: 'undecided',\n x: event.clientX,\n y: event.clientY,\n offset: restingOffset,\n id: event.pointerId,\n samples: [{ position: event.clientY, time: event.timeStamp }],\n };\n };\n const movePointer = (event: PointerEvent<HTMLElement>) => {\n const start = pointer.current;\n if (!start.pending) return;\n if (start.fromContent && start.gesture !== 'drag') {\n // Once a touch on the content picks scrolling or dragging, it keeps that choice.\n start.gesture = contentGesture(\n event.clientX - start.x,\n event.clientY - start.y,\n scrolledAbove(event.target as Element, contentRef.current!),\n current === 'lower',\n );\n if (start.gesture === 'undecided') return;\n if (start.gesture === 'scroll') {\n start.pending = false;\n return;\n }\n }\n // Without `swipeable` the handle is only a button: a move never drags it.\n if (!swipeable) return;\n start.samples = [...start.samples, { position: event.clientY, time: event.timeStamp }];\n if (drag === null && Math.abs(event.clientY - start.y) <= DRAG_SLOP) return;\n if (drag === null) event.currentTarget.setPointerCapture?.(start.id);\n const raw = start.offset + event.clientY - start.y;\n setDrag(Math.min(maxOffset, Math.max(0, raw)));\n // A drag up from the content that has raised the sheet fully scrolls the content with the\n // rest of the move, as Compose's nested scroll hands it over.\n if (start.fromContent && contentRef.current) contentRef.current.scrollTop = Math.max(0, -raw);\n };\n const endPointer = (event: PointerEvent<HTMLElement>) => {\n const start = pointer.current;\n if (!start.pending) return;\n start.pending = false;\n if (drag === null) {\n // A press on the handle cycles the sheet (Compose's handle click).\n if (start.fromHandle) cycle();\n return;\n }\n // From the last 100ms of moves, so a slow drag ending in a flick reads as a fling.\n const velocity = releaseVelocity([\n ...start.samples,\n { position: event.clientY, time: event.timeStamp },\n ]);\n settle(settleTarget(anchors, start.offset, drag, velocity), velocity);\n };\n const cancelPointer = () => {\n pointer.current.pending = false;\n setSettleMotion(null);\n setDrag(null);\n };\n\n // Without a handle the top 48px of the sheet still drags it (m3.material.io).\n const onZonePointerDown = (event: PointerEvent<HTMLDivElement>) => {\n if (!swipeable) return;\n const top = event.currentTarget.getBoundingClientRect().top;\n if (event.clientY - top <= DRAG_ZONE) startPointer(event, false);\n };\n\n // A touch (or pen) drag down on content scrolled to its top moves the sheet, as the\n // handle does. Mouse drags select text instead, as they always have on the web.\n const onContentPointerDown = (event: PointerEvent<HTMLDivElement>) => {\n const content = contentRef.current;\n const target = event.target as Element;\n pointer.current.fromContent = false;\n if (!swipeable || event.pointerType === 'mouse' || !content?.contains(target)) return;\n if (ownsGesture(target, content)) return;\n startPointer(event, false, true);\n };\n\n // While the content is at its top, the browser only scrolls it for upward and sideways\n // moves (`touch-action: pan-x pan-down`), so a downward one reaches the sheet as pointer\n // events. Browsers without `pan-down` (iOS Safari) are stopped from scrolling or\n // rubber-banding by cancelling the touch moves of a drag.\n useEffect(() => {\n const content = contentRef.current;\n if (!content) return;\n const update = () => content.toggleAttribute('data-at-top', content.scrollTop <= 0);\n const onTouchMove = (event: TouchEvent) => {\n const start = pointer.current;\n if (start.pending && start.fromContent && start.gesture === 'drag' && event.cancelable)\n event.preventDefault();\n };\n update();\n content.addEventListener('scroll', update, { passive: true });\n content.addEventListener('touchmove', onTouchMove, { passive: false });\n return () => {\n content.removeEventListener('scroll', update);\n content.removeEventListener('touchmove', onTouchMove);\n };\n }, []);\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Escape' || isExiting) return;\n if (standard) {\n if (current !== 'expanded' || !hasLower) return;\n event.stopPropagation();\n setDetent('lower');\n return;\n }\n // Escape is a close request: it closes a modal sheet in one press, from any detent (WAI-ARIA\n // APG modal dialog; HTML's close request), unless the user may not close it.\n if (closedBy === 'none') return;\n event.stopPropagation();\n close();\n };\n\n const handleRef = useRef<HTMLDivElement>(null);\n usePeekInert(\n panelRef,\n contentRef,\n detentName === 'collapsed',\n peekHeight,\n () => handleRef.current ?? panelRef.current,\n );\n\n // The collapse button leaves once the sheet stops filling the window, so focus moves to the\n // handle (or the panel) first instead of dropping to the page.\n const collapseFromButton = () => {\n (handleRef.current ?? panelRef.current)?.focus({ preventScroll: true });\n setDetent('lower');\n };\n\n const { focusProps: handleFocusProps, isFocusVisible: handleFocusVisible } = useFocusRing();\n const exiting = isExiting || undefined;\n const dragging = (drag !== null && !dragClosing) || undefined;\n const handleLabel =\n current === 'lower' ? labels.expand : standard ? labels.collapse : labels.dismiss;\n // The sheet fills the window up to its top margin: 72px, or 56px above 640px wide.\n const topMargin = viewport.width > 640 ? 56 : 72;\n const fillsWindow =\n current === 'expanded' && viewport.height > 0 && height >= viewport.height - topMargin - 0.5;\n const showCloseButton = closeButton && fillsWindow && (!standard || hasLower);\n\n // A standard sheet is a non-modal dialog: no scrim, no focus trap, the page stays usable.\n const roleProps: HTMLAttributes<HTMLElement> = standard\n ? { role: 'dialog', tabIndex: -1 }\n : (overlayPanelProps ?? {});\n const panelProps = mergeProps(rest, roleProps, { onKeyDown });\n const zone = !dragHandle;\n const keyboardInset = useKeyboardInset();\n\n return (\n <div\n style={{ '--m3-sheet-keyboard-inset': `${keyboardInset}px`, ...style } as CSSProperties}\n className={styles.container({ class: cn(classNames?.container, className) })}\n >\n <div\n {...mergeProps(exitProps, {\n onTransitionEnd: (event: TransitionEvent<HTMLDivElement>) => {\n if (event.target === event.currentTarget && event.propertyName === 'translate')\n setSettleMotion(null);\n },\n })}\n ref={motionRef}\n data-exiting={exiting}\n data-dragging={dragging}\n data-lowering={lowering || undefined}\n data-settling={settleMotion ? '' : undefined}\n style={\n {\n '--m3-sheet-offset': `${offset}px`,\n ...(settleMotion && {\n transitionDuration: `${settleMotion.duration}ms`,\n transitionTimingFunction: settleMotion.easing,\n }),\n } as CSSProperties\n }\n className={styles.motion()}\n >\n <div\n {...panelProps}\n // Content drags listen in the capture phase: pressable content (list items, buttons)\n // stops its pointer events from bubbling.\n onPointerDownCapture={(event) => {\n panelProps.onPointerDownCapture?.(event);\n onContentPointerDown(event);\n }}\n onPointerMoveCapture={(event) => {\n panelProps.onPointerMoveCapture?.(event);\n if (pointer.current.fromContent) movePointer(event);\n }}\n onPointerUpCapture={(event) => {\n panelProps.onPointerUpCapture?.(event);\n if (pointer.current.fromContent) endPointer(event);\n }}\n onPointerCancelCapture={(event) => {\n panelProps.onPointerCancelCapture?.(event);\n if (pointer.current.fromContent) cancelPointer();\n }}\n onPointerDown={(event) => {\n panelProps.onPointerDown?.(event);\n if (zone && !pointer.current.fromContent) onZonePointerDown(event);\n }}\n onPointerMove={(event) => {\n panelProps.onPointerMove?.(event);\n if (zone && !pointer.current.fromContent) movePointer(event);\n }}\n onPointerUp={(event) => {\n panelProps.onPointerUp?.(event);\n if (zone && !pointer.current.fromContent) endPointer(event);\n }}\n onPointerCancel={(event) => {\n panelProps.onPointerCancel?.(event);\n if (zone && !pointer.current.fromContent) cancelPointer();\n }}\n ref={panelRef}\n // The component tokens' hooks (sheet-tokens.ts); `data-variant` is also public.\n data-tokens={mergeDataTokens('md-comp-sheet-bottom-docked', rest)}\n data-variant={variant}\n data-detent={detentName}\n data-exiting={exiting}\n data-dragging={dragging}\n className={styles.panel({ class: classNames?.panel })}\n >\n <TriggerContext value={null}>\n {dragHandle && (\n <div\n {...handleFocusProps}\n ref={handleRef}\n role=\"button\"\n tabIndex={0}\n data-focus-visible={handleFocusVisible || undefined}\n aria-label={handleLabel}\n data-dragging={dragging}\n onPointerDown={(event) => startPointer(event, true)}\n onPointerMove={movePointer}\n onPointerUp={endPointer}\n onPointerCancel={cancelPointer}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n cycle();\n }\n }}\n className={styles.handle({ class: classNames?.handle })}\n >\n <span\n data-slot=\"drag-handle\"\n className={styles.handleBar({ class: classNames?.handleBar })}\n />\n </div>\n )}\n {showCloseButton && (\n <div className={styles.closeRow({ class: classNames?.closeRow })}>\n <IconButton\n variant=\"standard\"\n density={0}\n icon={\n standard ? (collapseIcon ?? <CollapseIcon />) : (closeIcon ?? <CloseIcon />)\n }\n aria-label={standard ? labels.collapse : labels.close}\n onPress={standard ? collapseFromButton : close}\n className={classNames?.close}\n />\n </div>\n )}\n <div\n ref={contentRef}\n data-can-rise={current === 'lower' || undefined}\n className={styles.content({ class: classNames?.content })}\n >\n {typeof children === 'function' ? children({ close }) : children}\n </div>\n </TriggerContext>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA0FA,MAAM,iBAAoC;CACxC,QAAQ;CACR,UAAU;CACV,SAAS;CACT,OAAO;AACT;;AAUA,MAAM,YAAY;;;;;;;;;;;;;;;AAqHlB,SAAgB,YAAY,EAC1B,UAAU,SACV,MACA,aACA,cACA,GAAG,SACgB;CACnB,MAAM,UAAU,WAAW,mBAAmB;CAC9C,MAAM,WAAW,uBAAuB;EACtC,QAAQ;EACR,aAAa,gBAAgB,YAAY,aAAa,OAAO,KAAA;EAC7D;CACF,CAAC;CACD,MAAM,SAAS,YAAY,UAAU,SAAS,QAAQ,KAAA,MAAc;CACpE,MAAM,EAAE,WAAW,WAAW,cAAc,YAAY,MAAM,QAAQ,qBAAqB;CAC3F,IAAI,CAAC,WAAW,OAAO;CACvB,IAAI,YAAY,YACd,OACE,oBAAC,kBAAD;EACE,GAAI;EACJ,SAAQ;EACD;EACI;EACA;CACZ,CAAA;CAEL,OACE,oBAACA,WAAD;EAAS,SAAS;EAChB,UAAA,oBAAC,kBAAD;GACE,GAAI;GACG;GACI;GACA;GACX,qBAAqB,SAAS;EAC/B,CAAA;CACM,CAAA;AAEb;AAEA,SAAS,iBAAiB,EACxB,OACA,WACA,qBACA,WAAW,OACX,YACA,KACA,GAAG,SACsF;CACzF,MAAM,WAAW,aAAa,GAA0B;CACxD,MAAM,EAAE,YAAY,kBAAkB,gBACpC;EACE,GAAG,uBAAuB,UAAU,SAAS;EAE7C,2BAA2B;CAC7B,GACA,OACA,QACF;CACA,MAAM,EAAE,gBAAgB,UAAU,OAA0B,QAAQ;CAGpE,gBAAgB,sBAAsB,SAAS,OAAO,GAAG,CAAC,QAAQ,CAAC;CACnE,MAAM,SAAS,kBAAkB;CACjC,OACE,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,OAAD;EACE,eAAY;EACZ,gBAAc,aAAa,KAAA;EAC3B,WAAW,OAAO,MAAM,EAAE,OAAO,YAAY,MAAM,CAAC;CACrD,CAAA,GACD,oBAAC,OAAD;EAAK,GAAI;EAAe,WAAU;EAChC,UAAA,oBAAC,kBAAD;GACE,GAAI;GACJ,SAAQ;GACE;GACH;GACI;GACC;GACZ,KAAK;GACL,YAAY,WAAW,uBAAuB,CAAC,GAAG,YAAY,WAAW;EAC1E,CAAA;CACE,CAAA,CACL,EAAA,CAAA;AAEN;;AAYA,SAAS,iBAAiB,EACxB,SACA,OACA,WACA,WACA,YAAY,mBACZ,aAAa,MACb,YAAY,MACZ,wBAAwB,OACxB,aAAa,IACb,QAAQ,YACR,gBAAgB,aAChB,gBACA,WAAW,OACX,cAAc,MACd,WACA,cACA,QAAQ,YACR,UACA,WACA,YACA,OACA,KACA,GAAG,QACU;CACb,MAAM,SAAS,UAAU,eAAe,gBAAgB,UAAU;CAClE,MAAM,WAAW,YAAY;CAC7B,MAAM,WAAW,aAAa,GAA0B;CACxD,MAAM,YAAY,OAAuB,IAAI;CAC7C,MAAM,SAAS,kBAAkB;EAAE;EAAS;CAAU,CAAC;CACvD,MAAM,CAAC,QAAQ,aAAa,SAAS,CAAC;CACtC,MAAM,CAAC,UAAU,eAAe,SAAS;EAAE,OAAO;EAAG,QAAQ;CAAE,CAAC;CAGhE,MAAM,CAAC,WAAW,gBAAgB,SAChC,YAAY,kBAAkB,aAAa,aAAa,IAC1D;CACA,MAAM,mBACJ,YAAY,eAAe,KAAA,IACvB,eAAe,aACb,aACA,UACF,KAAA;CACN,MAAM,SAAS,oBAAoB;CACnC,MAAM,CAAC,MAAM,WAAW,SAAwB,IAAI;CAKpD,MAAM,EAAE,QAAQ,iBAAiB,cAAc;CAC/C,MAAM,CAAC,cAAc,mBAAmB,SAA+B,IAAI;CAI3E,MAAM,CAAC,aAAa,kBAAkB,SAAS,KAAK;CACpD,IAAI,eAAe,MAAM,QAAQ;EAC/B,eAAe,KAAK;EACpB,QAAQ,IAAI;EACZ,gBAAgB,IAAI;CACtB;CACA,MAAM,aAAa,OAAuB,IAAI;CAC9C,MAAM,UAAU,OAAO;EACrB,SAAS;EACT,YAAY;;EAEZ,aAAa;EACb,SAAS;EACT,GAAG;EACH,GAAG;EACH,QAAQ;EACR,IAAI;EACJ,SAAS,CAAC;CACZ,CAAC;CAID,MAAM,cAAc,WAAW,SAAS,aAAa,SAAS,SAAS,SAAS;CAChF,MAAM,WAAW,WAAW,SAAS,aAAa,CAAC,yBAAyB,cAAc;CAE1F,MAAM,UAAU,WAAW,cAAc,CAAC,WAAW,aAAa;CAClE,MAAM,gBAAgB,YAAY,UAAU,cAAc;CAC1D,MAAM,SAAS,QAAQ;CACvB,MAAM,YAAY,WAAW,KAAK,IAAI,GAAG,WAAW,IAAI;CACxD,MAAM,aAAa,YAAY,aAAa,aAAa,WAAW,cAAc;CAQlF,MAAM,CAAC,UAAU,eAAe,SAAS,KAAK;CAC9C,MAAM,CAAC,oBAAoB,yBAAyB,SAAS,gBAAgB;CAC7E,IAAI,qBAAqB,oBAAoB;EAC3C,sBAAsB,gBAAgB;EACtC,YAAY,qBAAqB,OAAO;EACxC,gBAAgB,IAAI;CACtB;;CAGA,MAAM,aAAa,MAA4B,WAAW,UAAU;EAClE,YAAY,SAAS,WAAW,CAAC,QAAQ;EACzC,IAAI,CAAC,UAAU,gBAAgB,IAAI;EACnC,IAAI,qBAAqB,KAAA,GAAW,aAAa,IAAI;EACrD,MAAM,UAAU,SAAS,WAAW,WAAW,UAAU;EACzD,IAAI,YAAY,YAAY,SAC1B,iBAAiB,YAAY,aAAa,aAAa,WAAW;CACtE;CAEA,sBAAsB;EACpB,MAAM,UAAU,UAAU;EAC1B,IAAI,CAAC,SAAS;EACd,MAAM,gBAAgB;GACpB,UAAU,QAAQ,YAAY;GAC9B,YAAY;IAAE,OAAO,OAAO;IAAY,QAAQ,OAAO;GAAY,CAAC;EACtE;EACA,QAAQ;EACR,MAAM,WAAW,OAAO,mBAAmB,cAAc,OAAO,IAAI,eAAe,OAAO;EAC1F,UAAU,QAAQ,OAAO;EACzB,OAAO,iBAAiB,UAAU,OAAO;EACzC,aAAa;GACX,UAAU,WAAW;GACrB,OAAO,oBAAoB,UAAU,OAAO;EAC9C;CACF,GAAG,CAAC,CAAC;CAEL,MAAM,QAAQ,MAAM;CAGpB,MAAM,WAAW,CAAC,YAAY,aAAa;CAC3C,MAAM,UAAU;EAAC;EAAG,GAAI,WAAW,CAAC,WAAW,IAAI,CAAC;EAAI,GAAI,WAAW,CAAC,MAAM,IAAI,CAAC;CAAE;CAErF,MAAM,UAAU,QAAgB,aAAqB;EACnD,MAAM,UAAU,CAAC,YAAY,UAAU,SAAS;EAEhD,MAAM,MAAM,UAAU,SAAS,gBAAgB;EAC/C,gBAAgB,cAAc,cAAc,SAAS,UAAU,OAAO,QAAQ,cAAc,CAAC;EAC7F,IAAI,SAAS;GACX,eAAe,IAAI;GACnB,MAAM;EACR,OAAO;GACL,QAAQ,IAAI;GACZ,UAAU,WAAW,IAAI,aAAa,SAAS,IAAI;EACrD;CACF;;CAGA,MAAM,cAAc;EAClB,IAAI,YAAY,SAAS,UAAU,UAAU;OACxC,IAAI,UAAU,UAAU,OAAO;OAC/B,MAAM;CACb;CAEA,MAAM,gBACJ,OACA,YACA,cAAc,UACX;EAEH,IAAI,MAAM,WAAW,KAAK,WAAW;EACrC,gBAAgB,IAAI;EACpB,QAAQ,UAAU;GAChB,SAAS;GACT;GACA;GACA,SAAS;GACT,GAAG,MAAM;GACT,GAAG,MAAM;GACT,QAAQ;GACR,IAAI,MAAM;GACV,SAAS,CAAC;IAAE,UAAU,MAAM;IAAS,MAAM,MAAM;GAAU,CAAC;EAC9D;CACF;CACA,MAAM,eAAe,UAAqC;EACxD,MAAM,QAAQ,QAAQ;EACtB,IAAI,CAAC,MAAM,SAAS;EACpB,IAAI,MAAM,eAAe,MAAM,YAAY,QAAQ;GAEjD,MAAM,UAAU,eACd,MAAM,UAAU,MAAM,GACtB,MAAM,UAAU,MAAM,GACtB,cAAc,MAAM,QAAmB,WAAW,OAAQ,GAC1D,YAAY,OACd;GACA,IAAI,MAAM,YAAY,aAAa;GACnC,IAAI,MAAM,YAAY,UAAU;IAC9B,MAAM,UAAU;IAChB;GACF;EACF;EAEA,IAAI,CAAC,WAAW;EAChB,MAAM,UAAU,CAAC,GAAG,MAAM,SAAS;GAAE,UAAU,MAAM;GAAS,MAAM,MAAM;EAAU,CAAC;EACrF,IAAI,SAAS,QAAQ,KAAK,IAAI,MAAM,UAAU,MAAM,CAAC,KAAA,GAAgB;EACrE,IAAI,SAAS,MAAM,MAAM,cAAc,oBAAoB,MAAM,EAAE;EACnE,MAAM,MAAM,MAAM,SAAS,MAAM,UAAU,MAAM;EACjD,QAAQ,KAAK,IAAI,WAAW,KAAK,IAAI,GAAG,GAAG,CAAC,CAAC;EAG7C,IAAI,MAAM,eAAe,WAAW,SAAS,WAAW,QAAQ,YAAY,KAAK,IAAI,GAAG,CAAC,GAAG;CAC9F;CACA,MAAM,cAAc,UAAqC;EACvD,MAAM,QAAQ,QAAQ;EACtB,IAAI,CAAC,MAAM,SAAS;EACpB,MAAM,UAAU;EAChB,IAAI,SAAS,MAAM;GAEjB,IAAI,MAAM,YAAY,MAAM;GAC5B;EACF;EAEA,MAAM,WAAW,gBAAgB,CAC/B,GAAG,MAAM,SACT;GAAE,UAAU,MAAM;GAAS,MAAM,MAAM;EAAU,CACnD,CAAC;EACD,OAAO,aAAa,SAAS,MAAM,QAAQ,MAAM,QAAQ,GAAG,QAAQ;CACtE;CACA,MAAM,sBAAsB;EAC1B,QAAQ,QAAQ,UAAU;EAC1B,gBAAgB,IAAI;EACpB,QAAQ,IAAI;CACd;CAGA,MAAM,qBAAqB,UAAwC;EACjE,IAAI,CAAC,WAAW;EAChB,MAAM,MAAM,MAAM,cAAc,sBAAsB,CAAC,CAAC;EACxD,IAAI,MAAM,UAAU,OAAO,WAAW,aAAa,OAAO,KAAK;CACjE;CAIA,MAAM,wBAAwB,UAAwC;EACpE,MAAM,UAAU,WAAW;EAC3B,MAAM,SAAS,MAAM;EACrB,QAAQ,QAAQ,cAAc;EAC9B,IAAI,CAAC,aAAa,MAAM,gBAAgB,WAAW,CAAC,SAAS,SAAS,MAAM,GAAG;EAC/E,IAAI,YAAY,QAAQ,OAAO,GAAG;EAClC,aAAa,OAAO,OAAO,IAAI;CACjC;CAMA,gBAAgB;EACd,MAAM,UAAU,WAAW;EAC3B,IAAI,CAAC,SAAS;EACd,MAAM,eAAe,QAAQ,gBAAgB,eAAe,QAAQ,aAAa,CAAC;EAClF,MAAM,eAAe,UAAsB;GACzC,MAAM,QAAQ,QAAQ;GACtB,IAAI,MAAM,WAAW,MAAM,eAAe,MAAM,YAAY,UAAU,MAAM,YAC1E,MAAM,eAAe;EACzB;EACA,OAAO;EACP,QAAQ,iBAAiB,UAAU,QAAQ,EAAE,SAAS,KAAK,CAAC;EAC5D,QAAQ,iBAAiB,aAAa,aAAa,EAAE,SAAS,MAAM,CAAC;EACrE,aAAa;GACX,QAAQ,oBAAoB,UAAU,MAAM;GAC5C,QAAQ,oBAAoB,aAAa,WAAW;EACtD;CACF,GAAG,CAAC,CAAC;CAEL,MAAM,aAAa,UAAyB;EAC1C,IAAI,MAAM,QAAQ,YAAY,WAAW;EACzC,IAAI,UAAU;GACZ,IAAI,YAAY,cAAc,CAAC,UAAU;GACzC,MAAM,gBAAgB;GACtB,UAAU,OAAO;GACjB;EACF;EAGA,IAAI,aAAa,QAAQ;EACzB,MAAM,gBAAgB;EACtB,MAAM;CACR;CAEA,MAAM,YAAY,OAAuB,IAAI;CAC7C,aACE,UACA,YACA,eAAe,aACf,kBACM,UAAU,WAAW,SAAS,OACtC;CAIA,MAAM,2BAA2B;EAC/B,CAAC,UAAU,WAAW,SAAS,QAAA,EAAU,MAAM,EAAE,eAAe,KAAK,CAAC;EACtE,UAAU,OAAO;CACnB;CAEA,MAAM,EAAE,YAAY,kBAAkB,gBAAgB,uBAAuB,aAAa;CAC1F,MAAM,UAAU,aAAa,KAAA;CAC7B,MAAM,WAAY,SAAS,QAAQ,CAAC,eAAgB,KAAA;CACpD,MAAM,cACJ,YAAY,UAAU,OAAO,SAAS,WAAW,OAAO,WAAW,OAAO;CAE5E,MAAM,YAAY,SAAS,QAAQ,MAAM,KAAK;CAC9C,MAAM,cACJ,YAAY,cAAc,SAAS,SAAS,KAAK,UAAU,SAAS,SAAS,YAAY;CAC3F,MAAM,kBAAkB,eAAe,gBAAgB,CAAC,YAAY;CAMpE,MAAM,aAAa,WAAW,MAHiB,WAC3C;EAAE,MAAM;EAAU,UAAU;CAAG,IAC9B,qBAAqB,CAAC,GACoB,EAAE,UAAU,CAAC;CAC5D,MAAM,OAAO,CAAC;CACd,MAAM,gBAAgB,iBAAiB;CAEvC,OACE,oBAAC,OAAD;EACE,OAAO;GAAE,6BAA6B,GAAG,cAAc;GAAK,GAAG;EAAM;EACrE,WAAW,OAAO,UAAU,EAAE,OAAO,GAAG,YAAY,WAAW,SAAS,EAAE,CAAC;EAE3E,UAAA,oBAAC,OAAD;GACE,GAAI,WAAW,WAAW,EACxB,kBAAkB,UAA2C;IAC3D,IAAI,MAAM,WAAW,MAAM,iBAAiB,MAAM,iBAAiB,aACjE,gBAAgB,IAAI;GACxB,EACF,CAAC;GACD,KAAK;GACL,gBAAc;GACd,iBAAe;GACf,iBAAe,YAAY,KAAA;GAC3B,iBAAe,eAAe,KAAK,KAAA;GACnC,OACE;IACE,qBAAqB,GAAG,OAAO;IAC/B,GAAI,gBAAgB;KAClB,oBAAoB,GAAG,aAAa,SAAS;KAC7C,0BAA0B,aAAa;IACzC;GACF;GAEF,WAAW,OAAO,OAAO;GAEzB,UAAA,oBAAC,OAAD;IACE,GAAI;IAGJ,uBAAuB,UAAU;KAC/B,WAAW,uBAAuB,KAAK;KACvC,qBAAqB,KAAK;IAC5B;IACA,uBAAuB,UAAU;KAC/B,WAAW,uBAAuB,KAAK;KACvC,IAAI,QAAQ,QAAQ,aAAa,YAAY,KAAK;IACpD;IACA,qBAAqB,UAAU;KAC7B,WAAW,qBAAqB,KAAK;KACrC,IAAI,QAAQ,QAAQ,aAAa,WAAW,KAAK;IACnD;IACA,yBAAyB,UAAU;KACjC,WAAW,yBAAyB,KAAK;KACzC,IAAI,QAAQ,QAAQ,aAAa,cAAc;IACjD;IACA,gBAAgB,UAAU;KACxB,WAAW,gBAAgB,KAAK;KAChC,IAAI,QAAQ,CAAC,QAAQ,QAAQ,aAAa,kBAAkB,KAAK;IACnE;IACA,gBAAgB,UAAU;KACxB,WAAW,gBAAgB,KAAK;KAChC,IAAI,QAAQ,CAAC,QAAQ,QAAQ,aAAa,YAAY,KAAK;IAC7D;IACA,cAAc,UAAU;KACtB,WAAW,cAAc,KAAK;KAC9B,IAAI,QAAQ,CAAC,QAAQ,QAAQ,aAAa,WAAW,KAAK;IAC5D;IACA,kBAAkB,UAAU;KAC1B,WAAW,kBAAkB,KAAK;KAClC,IAAI,QAAQ,CAAC,QAAQ,QAAQ,aAAa,cAAc;IAC1D;IACA,KAAK;IAEL,eAAa,gBAAgB,+BAA+B,IAAI;IAChE,gBAAc;IACd,eAAa;IACb,gBAAc;IACd,iBAAe;IACf,WAAW,OAAO,MAAM,EAAE,OAAO,YAAY,MAAM,CAAC;IAEpD,UAAA,qBAAC,gBAAD;KAAgB,OAAO;KAAvB,UAAA;MACG,cACC,oBAAC,OAAD;OACE,GAAI;OACJ,KAAK;OACL,MAAK;OACL,UAAU;OACV,sBAAoB,sBAAsB,KAAA;OAC1C,cAAY;OACZ,iBAAe;OACf,gBAAgB,UAAU,aAAa,OAAO,IAAI;OAClD,eAAe;OACf,aAAa;OACb,iBAAiB;OACjB,YAAY,UAAU;QACpB,IAAI,MAAM,QAAQ,WAAW,MAAM,QAAQ,KAAK;SAC9C,MAAM,eAAe;SACrB,MAAM;QACR;OACF;OACA,WAAW,OAAO,OAAO,EAAE,OAAO,YAAY,OAAO,CAAC;OAEtD,UAAA,oBAAC,QAAD;QACE,aAAU;QACV,WAAW,OAAO,UAAU,EAAE,OAAO,YAAY,UAAU,CAAC;OAC7D,CAAA;MACE,CAAA;MAEN,mBACC,oBAAC,OAAD;OAAK,WAAW,OAAO,SAAS,EAAE,OAAO,YAAY,SAAS,CAAC;OAC7D,UAAA,oBAAC,YAAD;QACE,SAAQ;QACR,SAAS;QACT,MACE,WAAY,gBAAgB,oBAAC,cAAD,CAAe,CAAA,IAAM,aAAa,oBAAC,WAAD,CAAY,CAAA;QAE5E,cAAY,WAAW,OAAO,WAAW,OAAO;QAChD,SAAS,WAAW,qBAAqB;QACzC,WAAW,YAAY;OACxB,CAAA;MACE,CAAA;MAEP,oBAAC,OAAD;OACE,KAAK;OACL,iBAAe,YAAY,WAAW,KAAA;OACtC,WAAW,OAAO,QAAQ,EAAE,OAAO,YAAY,QAAQ,CAAC;OAEvD,UAAA,OAAO,aAAa,aAAa,SAAS,EAAE,MAAM,CAAC,IAAI;MACrD,CAAA;KACS;;GACb,CAAA;EACF,CAAA;CACF,CAAA;AAET"}
@@ -93,7 +93,10 @@ interface SideSheetOwnProps {
93
93
  closeIcon?: ReactElement;
94
94
  /** Replaces the back button's icon. */
95
95
  backIcon?: ReactElement;
96
- /** Words the sheet announces, with English defaults. */
96
+ /**
97
+ * Words the sheet announces, with English defaults. A {@link LabelsProvider} sets them for the
98
+ * whole app.
99
+ */
97
100
  labels?: Partial<SideSheetLabels>;
98
101
  children?: ReactNode | ((props: SideSheetRenderProps) => ReactNode);
99
102
  /** Classes for each part, by slot. */
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { cn } from "../../utils/cn.js";
3
+ import { useLabels } from "../labels/use-labels.js";
3
4
  import { TriggerContext } from "../../primitives/TriggerContext.js";
4
5
  import { mergeDataTokens } from "../../utils/merge-data-tokens.js";
5
6
  import { usePresence } from "../../primitives/use-presence.js";
@@ -61,10 +62,7 @@ function SideSheet({ variant = "modal", open, defaultOpen, onOpenChange, ...prop
61
62
  });
62
63
  }
63
64
  function SheetBody({ title, titleId, onBack, actions, footer, children, closeIcon, backIcon, labels: labelsProp, close, styles, classNames, contentRef }) {
64
- const labels = {
65
- ...DEFAULT_LABELS,
66
- ...labelsProp
67
- };
65
+ const labels = useLabels("SideSheet", DEFAULT_LABELS, labelsProp);
68
66
  const renderProps = { close };
69
67
  const render = (slot) => typeof slot === "function" ? slot(renderProps) : slot;
70
68
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [
@@ -1 +1 @@
1
- {"version":3,"file":"SideSheet.js","names":["Overlay"],"sources":["../../../src/components/sheet/SideSheet.tsx"],"sourcesContent":["'use client';\n\nimport {\n useContext,\n useEffect,\n useId,\n useLayoutEffect,\n useRef,\n useState,\n type ComponentPropsWithRef,\n type CSSProperties,\n type FocusEvent,\n type ReactElement,\n type ReactNode,\n type Ref,\n} from 'react';\nimport {\n mergeProps,\n useDialog,\n useModalOverlay,\n useObjectRef,\n type AriaDialogProps,\n} from 'react-aria';\nimport { useOverlayTriggerState, type OverlayTriggerState } from 'react-stately';\nimport { Overlay } from '../../primitives/Overlay';\nimport { TriggerContext } from '../../primitives/TriggerContext';\nimport { usePresence } from '../../primitives/use-presence';\nimport { closedByOverlayOptions, type ClosedBy } from '../../utils/closed-by';\nimport { cn } from '../../utils/cn';\nimport { focusFirstInteractive, tabbables } from '../../utils/focus';\nimport { DensityZeroButtons } from '../button/DensityZeroButtons';\nimport { IconButton } from '../icon-button/IconButton';\nimport { BackIcon, CloseIcon } from './sheet-icons';\nimport { SHEET_EXIT_PROPERTIES, sideSheetStyles } from './sheet-styles';\nimport { SheetTriggerContext } from './SheetTrigger';\nimport { useSideSwipe } from './use-side-swipe';\nimport { mergeDataTokens } from '../../utils/merge-data-tokens';\n\nexport interface SideSheetClassNames {\n /**\n * The outermost element, which places the sheet (where `className` and `style` go too):\n * a modal sheet's box `fixed` to the window's `edge`, or a standard sheet's layout box.\n */\n container?: string;\n /** The dimmed layer behind a modal sheet. */\n scrim?: string;\n /** The box around a standard sheet's surface, which holds a detached sheet's margins. */\n frame?: string;\n /** The sheet's surface: its colour, corners, width and shadow. */\n panel?: string;\n /** The header row: back button, title, actions and close button. */\n header?: string;\n /** The back button. */\n back?: string;\n /** The headline. */\n title?: string;\n /** The end of the header: `actions` and the close button. */\n headerActions?: string;\n /** The close button. */\n close?: string;\n /** The scrollable content area. */\n content?: string;\n /** The bottom row of action buttons. */\n footer?: string;\n}\n\n/** Words the side sheet announces. */\nexport interface SideSheetLabels {\n /** The close button. @default \"Close\" */\n close: string;\n /** The back button (with `onBack`). @default \"Back\" */\n back: string;\n}\n\nconst DEFAULT_LABELS: SideSheetLabels = { close: 'Close', back: 'Back' };\n\ninterface SideSheetRenderProps {\n /** Closes a modal sheet, or reports `onOpenChange(false)` for a standard one. */\n close: () => void;\n}\n\ninterface SideSheetOwnProps {\n /**\n * `modal` opens over a scrim as a dialog; `standard` sits in the layout beside the\n * content and opens and closes its width. @default \"modal\"\n */\n variant?: 'modal' | 'standard';\n /** Float 16px from the edges with corners all round. @default false */\n detached?: boolean;\n /**\n * The window edge the sheet is anchored to: `end` (the right in a left-to-right page, the\n * left in RTL, as M3 asks) or `start`. A modal sheet slides in from it and swipes out toward\n * it; a standard sheet faces it, with its divider on the other side. The root's `data-edge`\n * says which.\n * @default \"end\"\n */\n edge?: 'start' | 'end';\n /**\n * Whether a touch or pen swipe toward the sheet's edge closes a modal sheet (past half its\n * width, or a fling). The close button, Escape and the scrim still close it either way, and\n * a mouse drag never moves it. Off when `closedBy` is `\"none\"`; a standard sheet ignores\n * it. To keep the sheet open after a swipe (unsaved changes), control `open` and leave it\n * `true`: the sheet springs back.\n * @default true\n */\n swipeable?: boolean;\n /** Whether the sheet is open (controlled). */\n open?: boolean;\n /** Whether the sheet starts open when uncontrolled. @default false, or true when standard */\n defaultOpen?: boolean;\n /** Called when the sheet opens or closes. */\n onOpenChange?: (open: boolean) => void;\n /** The headline. It names the sheet. */\n title?: ReactNode;\n /** Shows a back button before the title, for a sheet with several levels. */\n onBack?: () => void;\n /** Header controls before the close button. */\n actions?: ReactNode | ((props: SideSheetRenderProps) => ReactNode);\n /** Bottom action buttons, start-aligned, e.g. Save and Cancel. */\n footer?: ReactNode | ((props: SideSheetRenderProps) => ReactNode);\n /** A divider between the content and `footer`. @default false */\n footerDivider?: boolean;\n /** A divider between a standard sheet and the content beside it. @default true */\n divider?: boolean;\n /**\n * How the user can close a modal sheet, as HTML's `<dialog closedby>`: `any` (Escape, a\n * press on the scrim or a swipe), `closerequest` (Escape or a swipe) or `none` (only its\n * buttons or the app).\n * @default \"any\"\n */\n closedBy?: ClosedBy;\n /** Replaces the close button's icon. */\n closeIcon?: ReactElement;\n /** Replaces the back button's icon. */\n backIcon?: ReactElement;\n /** Words the sheet announces, with English defaults. */\n labels?: Partial<SideSheetLabels>;\n children?: ReactNode | ((props: SideSheetRenderProps) => ReactNode);\n /** Classes for each part, by slot. */\n classNames?: SideSheetClassNames;\n /**\n * Goes with `style` on the outermost element, the box that places the sheet, so a layout class\n * moves the whole sheet and never clips or stretches its surface. Style the surface with\n * `classNames.panel`; `ref`, `data-*` and other attributes go on the panel (a standard\n * sheet's are on its outer box).\n */\n className?: string;\n /** Goes on the outermost element, with `className`. */\n style?: CSSProperties;\n /** Ref to the panel, the sheet's surface (`role=\"dialog\"`); a standard sheet's outer box. */\n ref?: Ref<HTMLDivElement>;\n}\n\ntype Naming =\n | {\n title: ReactNode;\n /** Accessible name when there's no visible title; overrides `title` when both are set. */\n 'aria-label'?: string;\n }\n | {\n /** Accessible name when there's no visible title. */\n 'aria-label': string;\n }\n | {\n /** Id of a visible element that names the sheet, when there's no `title`. */\n 'aria-labelledby': string;\n };\n\nexport type SideSheetProps = SideSheetOwnProps &\n Naming &\n Omit<ComponentPropsWithRef<'div'>, keyof SideSheetOwnProps | 'aria-label' | 'aria-labelledby'>;\n\n/**\n * M3 side sheet: supplementary content at the end edge of the window (`edge=\"start\"` for the\n * other). A modal sheet slides in over a scrim as a dialog (Escape, a press outside or a\n * touch swipe toward its edge closes it); a standard sheet sits in the layout as a non-modal\n * dialog and opens and closes its width. Both always show a close button, as M3 requires.\n *\n * @example\n * <SheetTrigger>\n * <Button>Filters</Button>\n * <SideSheet title=\"Filters\" footer={({ close }) => <Button onPress={close}>Apply</Button>}>\n * …\n * </SideSheet>\n * </SheetTrigger>\n */\nexport function SideSheet({\n variant = 'modal',\n open,\n defaultOpen,\n onOpenChange,\n ...props\n}: SideSheetProps) {\n const trigger = useContext(SheetTriggerContext);\n const ownState = useOverlayTriggerState({\n isOpen: open,\n defaultOpen: defaultOpen ?? (variant === 'standard' ? true : undefined),\n onOpenChange,\n });\n const state = (variant === 'modal' ? trigger?.state : undefined) ?? ownState;\n const { isPresent, isExiting, exitProps } = usePresence(state.isOpen, SHEET_EXIT_PROPERTIES);\n if (variant === 'standard') return <StandardSideSheet {...props} state={state} />;\n if (!isPresent) return null;\n return (\n <Overlay exiting={isExiting}>\n <ModalSideSheet\n {...props}\n state={state}\n isExiting={isExiting}\n exitProps={exitProps}\n triggerOverlayProps={trigger?.overlayProps}\n />\n </Overlay>\n );\n}\n\ntype InnerProps = Omit<SideSheetProps, 'variant' | 'open' | 'defaultOpen' | 'onOpenChange'> & {\n state: OverlayTriggerState;\n};\n\ntype BodyProps = Pick<\n SideSheetOwnProps,\n | 'title'\n | 'onBack'\n | 'actions'\n | 'footer'\n | 'children'\n | 'closeIcon'\n | 'backIcon'\n | 'labels'\n | 'classNames'\n> & {\n titleId: string;\n close: () => void;\n styles: ReturnType<typeof sideSheetStyles>;\n contentRef?: Ref<HTMLDivElement>;\n};\n\nfunction SheetBody({\n title,\n titleId,\n onBack,\n actions,\n footer,\n children,\n closeIcon,\n backIcon,\n labels: labelsProp,\n close,\n styles,\n classNames,\n contentRef,\n}: BodyProps) {\n const labels = { ...DEFAULT_LABELS, ...labelsProp };\n const renderProps = { close };\n const render = (slot: SideSheetOwnProps['children']) =>\n typeof slot === 'function' ? slot(renderProps) : slot;\n return (\n <>\n <div className={styles.header({ class: classNames?.header })}>\n {onBack && (\n <IconButton\n variant=\"standard\"\n density={0}\n icon={backIcon ?? <BackIcon />}\n aria-label={labels.back}\n onPress={onBack}\n className={classNames?.back}\n />\n )}\n {title != null && (\n <h2 id={titleId} className={styles.title({ class: classNames?.title })}>\n {title}\n </h2>\n )}\n <div className={styles.headerActions({ class: classNames?.headerActions })}>\n <DensityZeroButtons>{render(actions)}</DensityZeroButtons>\n <IconButton\n variant=\"standard\"\n density={0}\n icon={closeIcon ?? <CloseIcon />}\n aria-label={labels.close}\n onPress={close}\n className={classNames?.close}\n />\n </div>\n </div>\n <div ref={contentRef} className={styles.content({ class: classNames?.content })}>\n {render(children)}\n </div>\n {footer != null && (\n <div className={styles.footer({ class: classNames?.footer })}>{render(footer)}</div>\n )}\n </>\n );\n}\n\nconst BODY_KEYS = [\n 'title',\n 'onBack',\n 'actions',\n 'footer',\n 'children',\n 'closeIcon',\n 'backIcon',\n 'labels',\n 'classNames',\n] as const;\n\n/** Splits the header, body and footer props from those of the panel element. */\nfunction splitBodyProps<T extends Record<string, unknown>>(props: T) {\n const body: Record<string, unknown> = {};\n const rest: Record<string, unknown> = { ...props };\n for (const key of BODY_KEYS) {\n body[key] = rest[key];\n delete rest[key];\n }\n return {\n body: body as Omit<BodyProps, 'titleId' | 'close' | 'styles' | 'contentRef'>,\n rest: rest as Omit<T, (typeof BODY_KEYS)[number]>,\n };\n}\n\n/** `aria-labelledby` for the panel: the consumer's, else the title's unless `aria-label` names it. */\nfunction labelledBy(props: Record<string, unknown>, hasTitle: boolean, titleId: string) {\n const own = props['aria-labelledby'] as string | undefined;\n return own ?? (props['aria-label'] == null && hasTitle ? titleId : undefined);\n}\n\nfunction ModalSideSheet({\n state,\n isExiting,\n exitProps,\n triggerOverlayProps,\n detached = false,\n edge = 'end',\n swipeable = true,\n closedBy = 'any',\n divider: _divider,\n footerDivider = false,\n className,\n style,\n ref,\n ...props\n}: InnerProps & {\n isExiting: boolean;\n exitProps: ReturnType<typeof usePresence>['exitProps'];\n triggerOverlayProps?: { id?: string };\n}) {\n const { body, rest } = splitBodyProps(props);\n const panelRef = useObjectRef(ref as Ref<HTMLDivElement>);\n const motionRef = useRef<HTMLDivElement>(null);\n const contentRef = useRef<HTMLDivElement>(null);\n const titleId = useId();\n // `closedBy=\"none\"` leaves closing to the app, so a swipe doesn't close it either.\n const canSwipe = swipeable && closedBy !== 'none';\n const styles = sideSheetStyles({\n variant: 'modal',\n edge,\n swipeable: canSwipe,\n detached,\n hasBack: body.onBack != null,\n footerDivider,\n });\n const { panelHandlers, motionProps, dragging } = useSideSwipe({\n enabled: canSwipe && !isExiting,\n edge,\n isOpen: state.isOpen,\n close: state.close,\n panelRef,\n motionRef,\n contentRef,\n });\n const { modalProps, underlayProps } = useModalOverlay(\n closedByOverlayOptions(closedBy, isExiting),\n state,\n panelRef,\n );\n const { dialogProps } = useDialog(\n {\n ...(rest as AriaDialogProps),\n 'aria-labelledby': labelledBy(rest, body.title != null, titleId),\n },\n panelRef,\n );\n // M3: focus lands on the first (non-disabled) icon button, as in a Dialog, not the panel.\n useEffect(() => focusFirstInteractive(panelRef.current), [panelRef]);\n const exiting = isExiting || undefined;\n return (\n <>\n <div\n aria-hidden=\"true\"\n data-exiting={exiting}\n className={styles.scrim({ class: body.classNames?.scrim })}\n />\n <div {...underlayProps} className=\"fixed inset-0 z-(--md-sys-z-sheet)\">\n <div\n style={style}\n className={styles.container({ class: cn(body.classNames?.container, className) })}\n >\n <div\n {...mergeProps(exitProps, motionProps)}\n ref={motionRef}\n data-exiting={exiting}\n className={styles.motion()}\n >\n <div\n {...mergeProps(\n rest,\n triggerOverlayProps ?? {},\n modalProps,\n dialogProps,\n panelHandlers,\n )}\n ref={panelRef}\n // The component tokens' hooks (sheet-tokens.ts); `data-variant` and `data-edge`\n // are also public.\n data-tokens={mergeDataTokens('md-comp-sheet-side', rest)}\n data-variant=\"modal\"\n data-edge={edge}\n data-detached={detached || undefined}\n data-dragging={dragging || undefined}\n data-exiting={exiting}\n className={styles.panel({ class: body.classNames?.panel })}\n >\n <TriggerContext value={null}>\n <SheetBody\n {...body}\n titleId={titleId}\n close={state.close}\n styles={styles}\n contentRef={contentRef}\n />\n </TriggerContext>\n </div>\n </div>\n </div>\n </div>\n </>\n );\n}\n\nfunction StandardSideSheet({\n state,\n detached = false,\n edge = 'end',\n swipeable: _swipeable,\n divider = true,\n footerDivider = false,\n closedBy: _closedBy,\n className,\n style,\n ref,\n ...props\n}: InnerProps) {\n const { body, rest } = splitBodyProps(props);\n const titleId = useId();\n const styles = sideSheetStyles({\n variant: 'standard',\n edge,\n detached,\n divider,\n hasBack: body.onBack != null,\n footerDivider,\n });\n const closed = !state.isOpen || undefined;\n const rootRef = useObjectRef(ref as Ref<HTMLDivElement>);\n const frameRef = useRef<HTMLDivElement>(null);\n // The sheet's open width, measured, so the root's width (not a grid fraction, which takes\n // only part of the shrinking free space and left the panel floating off its edge) moves\n // between it and 0. Until it's measured (the server's HTML) the open root is `auto` wide.\n const [width, setWidth] = useState<number | null>(null);\n useLayoutEffect(() => {\n const frame = frameRef.current;\n if (!frame) return;\n // The layout width, not the box on screen, which a consumer transform scales or turns.\n const measure = () => setWidth(frame.offsetWidth);\n measure();\n if (typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver(measure);\n observer.observe(frame);\n return () => observer.disconnect();\n }, []);\n // Focus that came from outside returns there when the sheet closes with focus inside (its\n // close button, Escape in content, or the app closing it): the closed sheet is `inert`, and\n // focus would otherwise drop to the page.\n const returnFocus = useRef<HTMLElement | null>(null);\n useLayoutEffect(() => {\n const root = rootRef.current;\n const active = root?.ownerDocument.activeElement;\n if (state.isOpen || !root || !(active instanceof HTMLElement) || !root.contains(active)) return;\n const saved = returnFocus.current;\n const target =\n saved?.isConnected && !root.contains(saved) && !saved.closest('[inert]')\n ? saved\n : (tabbables(root.ownerDocument.body, (el) => root.contains(el))\n .filter((el) => el.compareDocumentPosition(root) & Node.DOCUMENT_POSITION_FOLLOWING)\n .at(-1) ?? null);\n if (target) target.focus({ preventScroll: true });\n else active.blur();\n }, [state.isOpen, rootRef]);\n const rootStyle = {\n ...style,\n ...(width === null ? {} : { '--m3-side-sheet-width': `${width}px` }),\n } as CSSProperties;\n // A non-modal dialog (m3.material.io: the side sheet's role is dialog); focus isn't moved.\n return (\n <div\n {...rest}\n onFocus={(event: FocusEvent<HTMLDivElement>) => {\n (rest as { onFocus?: (event: FocusEvent<HTMLDivElement>) => void }).onFocus?.(event);\n const from = event.relatedTarget;\n if (from instanceof HTMLElement && !event.currentTarget.contains(from)) {\n returnFocus.current = from;\n }\n }}\n ref={rootRef}\n role=\"dialog\"\n style={rootStyle}\n // The component tokens' hooks (sheet-tokens.ts), read by the panel inside.\n data-tokens={mergeDataTokens('md-comp-sheet-side', rest)}\n data-variant=\"standard\"\n data-edge={edge}\n data-detached={detached || undefined}\n data-closed={closed}\n inert={!state.isOpen}\n aria-labelledby={labelledBy(rest, body.title != null, titleId)}\n className={styles.standard({ class: cn(body.classNames?.container, className) })}\n >\n <div className={styles.standardClip()}>\n <div ref={frameRef} className={styles.standardFrame({ class: body.classNames?.frame })}>\n <div data-slot=\"panel\" className={styles.panel({ class: body.classNames?.panel })}>\n <SheetBody {...body} titleId={titleId} close={state.close} styles={styles} />\n </div>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"SideSheet.js","names":["Overlay"],"sources":["../../../src/components/sheet/SideSheet.tsx"],"sourcesContent":["'use client';\n\nimport {\n useContext,\n useEffect,\n useId,\n useLayoutEffect,\n useRef,\n useState,\n type ComponentPropsWithRef,\n type CSSProperties,\n type FocusEvent,\n type ReactElement,\n type ReactNode,\n type Ref,\n} from 'react';\nimport {\n mergeProps,\n useDialog,\n useModalOverlay,\n useObjectRef,\n type AriaDialogProps,\n} from 'react-aria';\nimport { useOverlayTriggerState, type OverlayTriggerState } from 'react-stately';\nimport { useLabels } from '../labels/use-labels';\nimport { Overlay } from '../../primitives/Overlay';\nimport { TriggerContext } from '../../primitives/TriggerContext';\nimport { usePresence } from '../../primitives/use-presence';\nimport { closedByOverlayOptions, type ClosedBy } from '../../utils/closed-by';\nimport { cn } from '../../utils/cn';\nimport { focusFirstInteractive, tabbables } from '../../utils/focus';\nimport { DensityZeroButtons } from '../button/DensityZeroButtons';\nimport { IconButton } from '../icon-button/IconButton';\nimport { BackIcon, CloseIcon } from './sheet-icons';\nimport { SHEET_EXIT_PROPERTIES, sideSheetStyles } from './sheet-styles';\nimport { SheetTriggerContext } from './SheetTrigger';\nimport { useSideSwipe } from './use-side-swipe';\nimport { mergeDataTokens } from '../../utils/merge-data-tokens';\n\nexport interface SideSheetClassNames {\n /**\n * The outermost element, which places the sheet (where `className` and `style` go too):\n * a modal sheet's box `fixed` to the window's `edge`, or a standard sheet's layout box.\n */\n container?: string;\n /** The dimmed layer behind a modal sheet. */\n scrim?: string;\n /** The box around a standard sheet's surface, which holds a detached sheet's margins. */\n frame?: string;\n /** The sheet's surface: its colour, corners, width and shadow. */\n panel?: string;\n /** The header row: back button, title, actions and close button. */\n header?: string;\n /** The back button. */\n back?: string;\n /** The headline. */\n title?: string;\n /** The end of the header: `actions` and the close button. */\n headerActions?: string;\n /** The close button. */\n close?: string;\n /** The scrollable content area. */\n content?: string;\n /** The bottom row of action buttons. */\n footer?: string;\n}\n\n/** Words the side sheet announces. */\nexport interface SideSheetLabels {\n /** The close button. @default \"Close\" */\n close: string;\n /** The back button (with `onBack`). @default \"Back\" */\n back: string;\n}\n\nconst DEFAULT_LABELS: SideSheetLabels = { close: 'Close', back: 'Back' };\n\ninterface SideSheetRenderProps {\n /** Closes a modal sheet, or reports `onOpenChange(false)` for a standard one. */\n close: () => void;\n}\n\ninterface SideSheetOwnProps {\n /**\n * `modal` opens over a scrim as a dialog; `standard` sits in the layout beside the\n * content and opens and closes its width. @default \"modal\"\n */\n variant?: 'modal' | 'standard';\n /** Float 16px from the edges with corners all round. @default false */\n detached?: boolean;\n /**\n * The window edge the sheet is anchored to: `end` (the right in a left-to-right page, the\n * left in RTL, as M3 asks) or `start`. A modal sheet slides in from it and swipes out toward\n * it; a standard sheet faces it, with its divider on the other side. The root's `data-edge`\n * says which.\n * @default \"end\"\n */\n edge?: 'start' | 'end';\n /**\n * Whether a touch or pen swipe toward the sheet's edge closes a modal sheet (past half its\n * width, or a fling). The close button, Escape and the scrim still close it either way, and\n * a mouse drag never moves it. Off when `closedBy` is `\"none\"`; a standard sheet ignores\n * it. To keep the sheet open after a swipe (unsaved changes), control `open` and leave it\n * `true`: the sheet springs back.\n * @default true\n */\n swipeable?: boolean;\n /** Whether the sheet is open (controlled). */\n open?: boolean;\n /** Whether the sheet starts open when uncontrolled. @default false, or true when standard */\n defaultOpen?: boolean;\n /** Called when the sheet opens or closes. */\n onOpenChange?: (open: boolean) => void;\n /** The headline. It names the sheet. */\n title?: ReactNode;\n /** Shows a back button before the title, for a sheet with several levels. */\n onBack?: () => void;\n /** Header controls before the close button. */\n actions?: ReactNode | ((props: SideSheetRenderProps) => ReactNode);\n /** Bottom action buttons, start-aligned, e.g. Save and Cancel. */\n footer?: ReactNode | ((props: SideSheetRenderProps) => ReactNode);\n /** A divider between the content and `footer`. @default false */\n footerDivider?: boolean;\n /** A divider between a standard sheet and the content beside it. @default true */\n divider?: boolean;\n /**\n * How the user can close a modal sheet, as HTML's `<dialog closedby>`: `any` (Escape, a\n * press on the scrim or a swipe), `closerequest` (Escape or a swipe) or `none` (only its\n * buttons or the app).\n * @default \"any\"\n */\n closedBy?: ClosedBy;\n /** Replaces the close button's icon. */\n closeIcon?: ReactElement;\n /** Replaces the back button's icon. */\n backIcon?: ReactElement;\n /**\n * Words the sheet announces, with English defaults. A {@link LabelsProvider} sets them for the\n * whole app.\n */\n labels?: Partial<SideSheetLabels>;\n children?: ReactNode | ((props: SideSheetRenderProps) => ReactNode);\n /** Classes for each part, by slot. */\n classNames?: SideSheetClassNames;\n /**\n * Goes with `style` on the outermost element, the box that places the sheet, so a layout class\n * moves the whole sheet and never clips or stretches its surface. Style the surface with\n * `classNames.panel`; `ref`, `data-*` and other attributes go on the panel (a standard\n * sheet's are on its outer box).\n */\n className?: string;\n /** Goes on the outermost element, with `className`. */\n style?: CSSProperties;\n /** Ref to the panel, the sheet's surface (`role=\"dialog\"`); a standard sheet's outer box. */\n ref?: Ref<HTMLDivElement>;\n}\n\ntype Naming =\n | {\n title: ReactNode;\n /** Accessible name when there's no visible title; overrides `title` when both are set. */\n 'aria-label'?: string;\n }\n | {\n /** Accessible name when there's no visible title. */\n 'aria-label': string;\n }\n | {\n /** Id of a visible element that names the sheet, when there's no `title`. */\n 'aria-labelledby': string;\n };\n\nexport type SideSheetProps = SideSheetOwnProps &\n Naming &\n Omit<ComponentPropsWithRef<'div'>, keyof SideSheetOwnProps | 'aria-label' | 'aria-labelledby'>;\n\n/**\n * M3 side sheet: supplementary content at the end edge of the window (`edge=\"start\"` for the\n * other). A modal sheet slides in over a scrim as a dialog (Escape, a press outside or a\n * touch swipe toward its edge closes it); a standard sheet sits in the layout as a non-modal\n * dialog and opens and closes its width. Both always show a close button, as M3 requires.\n *\n * @example\n * <SheetTrigger>\n * <Button>Filters</Button>\n * <SideSheet title=\"Filters\" footer={({ close }) => <Button onPress={close}>Apply</Button>}>\n * …\n * </SideSheet>\n * </SheetTrigger>\n */\nexport function SideSheet({\n variant = 'modal',\n open,\n defaultOpen,\n onOpenChange,\n ...props\n}: SideSheetProps) {\n const trigger = useContext(SheetTriggerContext);\n const ownState = useOverlayTriggerState({\n isOpen: open,\n defaultOpen: defaultOpen ?? (variant === 'standard' ? true : undefined),\n onOpenChange,\n });\n const state = (variant === 'modal' ? trigger?.state : undefined) ?? ownState;\n const { isPresent, isExiting, exitProps } = usePresence(state.isOpen, SHEET_EXIT_PROPERTIES);\n if (variant === 'standard') return <StandardSideSheet {...props} state={state} />;\n if (!isPresent) return null;\n return (\n <Overlay exiting={isExiting}>\n <ModalSideSheet\n {...props}\n state={state}\n isExiting={isExiting}\n exitProps={exitProps}\n triggerOverlayProps={trigger?.overlayProps}\n />\n </Overlay>\n );\n}\n\ntype InnerProps = Omit<SideSheetProps, 'variant' | 'open' | 'defaultOpen' | 'onOpenChange'> & {\n state: OverlayTriggerState;\n};\n\ntype BodyProps = Pick<\n SideSheetOwnProps,\n | 'title'\n | 'onBack'\n | 'actions'\n | 'footer'\n | 'children'\n | 'closeIcon'\n | 'backIcon'\n | 'labels'\n | 'classNames'\n> & {\n titleId: string;\n close: () => void;\n styles: ReturnType<typeof sideSheetStyles>;\n contentRef?: Ref<HTMLDivElement>;\n};\n\nfunction SheetBody({\n title,\n titleId,\n onBack,\n actions,\n footer,\n children,\n closeIcon,\n backIcon,\n labels: labelsProp,\n close,\n styles,\n classNames,\n contentRef,\n}: BodyProps) {\n const labels = useLabels('SideSheet', DEFAULT_LABELS, labelsProp);\n const renderProps = { close };\n const render = (slot: SideSheetOwnProps['children']) =>\n typeof slot === 'function' ? slot(renderProps) : slot;\n return (\n <>\n <div className={styles.header({ class: classNames?.header })}>\n {onBack && (\n <IconButton\n variant=\"standard\"\n density={0}\n icon={backIcon ?? <BackIcon />}\n aria-label={labels.back}\n onPress={onBack}\n className={classNames?.back}\n />\n )}\n {title != null && (\n <h2 id={titleId} className={styles.title({ class: classNames?.title })}>\n {title}\n </h2>\n )}\n <div className={styles.headerActions({ class: classNames?.headerActions })}>\n <DensityZeroButtons>{render(actions)}</DensityZeroButtons>\n <IconButton\n variant=\"standard\"\n density={0}\n icon={closeIcon ?? <CloseIcon />}\n aria-label={labels.close}\n onPress={close}\n className={classNames?.close}\n />\n </div>\n </div>\n <div ref={contentRef} className={styles.content({ class: classNames?.content })}>\n {render(children)}\n </div>\n {footer != null && (\n <div className={styles.footer({ class: classNames?.footer })}>{render(footer)}</div>\n )}\n </>\n );\n}\n\nconst BODY_KEYS = [\n 'title',\n 'onBack',\n 'actions',\n 'footer',\n 'children',\n 'closeIcon',\n 'backIcon',\n 'labels',\n 'classNames',\n] as const;\n\n/** Splits the header, body and footer props from those of the panel element. */\nfunction splitBodyProps<T extends Record<string, unknown>>(props: T) {\n const body: Record<string, unknown> = {};\n const rest: Record<string, unknown> = { ...props };\n for (const key of BODY_KEYS) {\n body[key] = rest[key];\n delete rest[key];\n }\n return {\n body: body as Omit<BodyProps, 'titleId' | 'close' | 'styles' | 'contentRef'>,\n rest: rest as Omit<T, (typeof BODY_KEYS)[number]>,\n };\n}\n\n/** `aria-labelledby` for the panel: the consumer's, else the title's unless `aria-label` names it. */\nfunction labelledBy(props: Record<string, unknown>, hasTitle: boolean, titleId: string) {\n const own = props['aria-labelledby'] as string | undefined;\n return own ?? (props['aria-label'] == null && hasTitle ? titleId : undefined);\n}\n\nfunction ModalSideSheet({\n state,\n isExiting,\n exitProps,\n triggerOverlayProps,\n detached = false,\n edge = 'end',\n swipeable = true,\n closedBy = 'any',\n divider: _divider,\n footerDivider = false,\n className,\n style,\n ref,\n ...props\n}: InnerProps & {\n isExiting: boolean;\n exitProps: ReturnType<typeof usePresence>['exitProps'];\n triggerOverlayProps?: { id?: string };\n}) {\n const { body, rest } = splitBodyProps(props);\n const panelRef = useObjectRef(ref as Ref<HTMLDivElement>);\n const motionRef = useRef<HTMLDivElement>(null);\n const contentRef = useRef<HTMLDivElement>(null);\n const titleId = useId();\n // `closedBy=\"none\"` leaves closing to the app, so a swipe doesn't close it either.\n const canSwipe = swipeable && closedBy !== 'none';\n const styles = sideSheetStyles({\n variant: 'modal',\n edge,\n swipeable: canSwipe,\n detached,\n hasBack: body.onBack != null,\n footerDivider,\n });\n const { panelHandlers, motionProps, dragging } = useSideSwipe({\n enabled: canSwipe && !isExiting,\n edge,\n isOpen: state.isOpen,\n close: state.close,\n panelRef,\n motionRef,\n contentRef,\n });\n const { modalProps, underlayProps } = useModalOverlay(\n closedByOverlayOptions(closedBy, isExiting),\n state,\n panelRef,\n );\n const { dialogProps } = useDialog(\n {\n ...(rest as AriaDialogProps),\n 'aria-labelledby': labelledBy(rest, body.title != null, titleId),\n },\n panelRef,\n );\n // M3: focus lands on the first (non-disabled) icon button, as in a Dialog, not the panel.\n useEffect(() => focusFirstInteractive(panelRef.current), [panelRef]);\n const exiting = isExiting || undefined;\n return (\n <>\n <div\n aria-hidden=\"true\"\n data-exiting={exiting}\n className={styles.scrim({ class: body.classNames?.scrim })}\n />\n <div {...underlayProps} className=\"fixed inset-0 z-(--md-sys-z-sheet)\">\n <div\n style={style}\n className={styles.container({ class: cn(body.classNames?.container, className) })}\n >\n <div\n {...mergeProps(exitProps, motionProps)}\n ref={motionRef}\n data-exiting={exiting}\n className={styles.motion()}\n >\n <div\n {...mergeProps(\n rest,\n triggerOverlayProps ?? {},\n modalProps,\n dialogProps,\n panelHandlers,\n )}\n ref={panelRef}\n // The component tokens' hooks (sheet-tokens.ts); `data-variant` and `data-edge`\n // are also public.\n data-tokens={mergeDataTokens('md-comp-sheet-side', rest)}\n data-variant=\"modal\"\n data-edge={edge}\n data-detached={detached || undefined}\n data-dragging={dragging || undefined}\n data-exiting={exiting}\n className={styles.panel({ class: body.classNames?.panel })}\n >\n <TriggerContext value={null}>\n <SheetBody\n {...body}\n titleId={titleId}\n close={state.close}\n styles={styles}\n contentRef={contentRef}\n />\n </TriggerContext>\n </div>\n </div>\n </div>\n </div>\n </>\n );\n}\n\nfunction StandardSideSheet({\n state,\n detached = false,\n edge = 'end',\n swipeable: _swipeable,\n divider = true,\n footerDivider = false,\n closedBy: _closedBy,\n className,\n style,\n ref,\n ...props\n}: InnerProps) {\n const { body, rest } = splitBodyProps(props);\n const titleId = useId();\n const styles = sideSheetStyles({\n variant: 'standard',\n edge,\n detached,\n divider,\n hasBack: body.onBack != null,\n footerDivider,\n });\n const closed = !state.isOpen || undefined;\n const rootRef = useObjectRef(ref as Ref<HTMLDivElement>);\n const frameRef = useRef<HTMLDivElement>(null);\n // The sheet's open width, measured, so the root's width (not a grid fraction, which takes\n // only part of the shrinking free space and left the panel floating off its edge) moves\n // between it and 0. Until it's measured (the server's HTML) the open root is `auto` wide.\n const [width, setWidth] = useState<number | null>(null);\n useLayoutEffect(() => {\n const frame = frameRef.current;\n if (!frame) return;\n // The layout width, not the box on screen, which a consumer transform scales or turns.\n const measure = () => setWidth(frame.offsetWidth);\n measure();\n if (typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver(measure);\n observer.observe(frame);\n return () => observer.disconnect();\n }, []);\n // Focus that came from outside returns there when the sheet closes with focus inside (its\n // close button, Escape in content, or the app closing it): the closed sheet is `inert`, and\n // focus would otherwise drop to the page.\n const returnFocus = useRef<HTMLElement | null>(null);\n useLayoutEffect(() => {\n const root = rootRef.current;\n const active = root?.ownerDocument.activeElement;\n if (state.isOpen || !root || !(active instanceof HTMLElement) || !root.contains(active)) return;\n const saved = returnFocus.current;\n const target =\n saved?.isConnected && !root.contains(saved) && !saved.closest('[inert]')\n ? saved\n : (tabbables(root.ownerDocument.body, (el) => root.contains(el))\n .filter((el) => el.compareDocumentPosition(root) & Node.DOCUMENT_POSITION_FOLLOWING)\n .at(-1) ?? null);\n if (target) target.focus({ preventScroll: true });\n else active.blur();\n }, [state.isOpen, rootRef]);\n const rootStyle = {\n ...style,\n ...(width === null ? {} : { '--m3-side-sheet-width': `${width}px` }),\n } as CSSProperties;\n // A non-modal dialog (m3.material.io: the side sheet's role is dialog); focus isn't moved.\n return (\n <div\n {...rest}\n onFocus={(event: FocusEvent<HTMLDivElement>) => {\n (rest as { onFocus?: (event: FocusEvent<HTMLDivElement>) => void }).onFocus?.(event);\n const from = event.relatedTarget;\n if (from instanceof HTMLElement && !event.currentTarget.contains(from)) {\n returnFocus.current = from;\n }\n }}\n ref={rootRef}\n role=\"dialog\"\n style={rootStyle}\n // The component tokens' hooks (sheet-tokens.ts), read by the panel inside.\n data-tokens={mergeDataTokens('md-comp-sheet-side', rest)}\n data-variant=\"standard\"\n data-edge={edge}\n data-detached={detached || undefined}\n data-closed={closed}\n inert={!state.isOpen}\n aria-labelledby={labelledBy(rest, body.title != null, titleId)}\n className={styles.standard({ class: cn(body.classNames?.container, className) })}\n >\n <div className={styles.standardClip()}>\n <div ref={frameRef} className={styles.standardFrame({ class: body.classNames?.frame })}>\n <div data-slot=\"panel\" className={styles.panel({ class: body.classNames?.panel })}>\n <SheetBody {...body} titleId={titleId} close={state.close} styles={styles} />\n </div>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -105,7 +105,10 @@ export type RangeSliderProps = SliderCommonProps & Naming & {
105
105
  onChange?: (value: [number, number]) => void;
106
106
  /** Called when the user stops dragging or releases a key. */
107
107
  onChangeEnd?: (value: [number, number]) => void;
108
- /** Words the range slider announces (its thumbs' names), with English defaults. */
108
+ /**
109
+ * Words the range slider announces (its thumbs' names), with English defaults.
110
+ * A {@link LabelsProvider} sets them for the whole app.
111
+ */
109
112
  labels?: Partial<RangeSliderLabels>;
110
113
  };
111
114
  /**
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { cn } from "../../utils/cn.js";
3
+ import { useLabels } from "../labels/use-labels.js";
3
4
  import { mergeDataTokens } from "../../utils/merge-data-tokens.js";
4
5
  import { DomDirectionLocale } from "../../primitives/DomDirectionLocale.js";
5
6
  import { splitRootProps } from "../../utils/root-props.js";
@@ -50,10 +51,7 @@ function SliderRoot(props) {
50
51
  const iconSize = size === "md" || size === "lg" || size === "xl";
51
52
  const startIcon = iconSize && !centered ? startIconProp : void 0;
52
53
  const endIcon = iconSize && !centered ? endIconProp : void 0;
53
- const labels = {
54
- ...DEFAULT_LABELS,
55
- ...props.labels
56
- };
54
+ const labels = useLabels("RangeSlider", DEFAULT_LABELS, props.labels);
57
55
  const { root: data, rest: labelling } = splitRootProps(rest);
58
56
  const { "aria-label": label, "aria-labelledby": labelledBy, "aria-describedby": describedBy, "aria-details": details, "aria-errormessage": errorMessage } = labelling;
59
57
  const numberFormatter = useNumberFormatter(formatOptions);
@@ -156,6 +154,8 @@ function SliderRoot(props) {
156
154
  state,
157
155
  trackRef,
158
156
  name,
157
+ groupId: groupProps.id,
158
+ labelledBy,
159
159
  label: range ? index === 0 ? labels.start : labels.end : void 0,
160
160
  errorMessage,
161
161
  valueLabel,
@@ -223,7 +223,7 @@ const INCREASE_KEYS = {
223
223
  ArrowDown: false
224
224
  }
225
225
  };
226
- function Thumb({ index, at, state, trackRef, name, label, errorMessage, valueLabel, styles, classNames }) {
226
+ function Thumb({ index, at, state, trackRef, name, groupId, labelledBy, label, errorMessage, valueLabel, styles, classNames }) {
227
227
  const inputRef = useRef(null);
228
228
  const { thumbProps, inputProps, isDragging } = useSliderThumb({
229
229
  index,
@@ -233,8 +233,10 @@ function Thumb({ index, at, state, trackRef, name, label, errorMessage, valueLab
233
233
  "aria-label": label,
234
234
  "aria-errormessage": errorMessage
235
235
  }, state);
236
+ const thumbLabelledBy = labelledBy?.trim() ? inputProps["aria-labelledby"]?.split(" ").flatMap((id) => id === groupId ? labelledBy.trim().split(/\s+/) : [id]).join(" ") : inputProps["aria-labelledby"];
236
237
  const thumbInputProps = {
237
238
  ...inputProps,
239
+ "aria-labelledby": thumbLabelledBy,
238
240
  "aria-describedby": inputProps["aria-describedby"] || void 0,
239
241
  "aria-details": inputProps["aria-details"] || void 0
240
242
  };
@@ -1 +1 @@
1
- {"version":3,"file":"Slider.js","names":[],"sources":["../../../src/components/slider/Slider.tsx"],"sourcesContent":["'use client';\n\nimport {\n useRef,\n useState,\n type CSSProperties,\n type KeyboardEvent,\n type ReactNode,\n type Ref,\n type RefObject,\n} from 'react';\nimport {\n mergeProps,\n useFocusRing,\n useLocale,\n useNumberFormatter,\n useObjectRef,\n useSlider,\n useSliderThumb,\n VisuallyHidden,\n} from 'react-aria';\nimport { useSliderState, type SliderState } from 'react-stately';\nimport { DomDirectionLocale } from '../../primitives/DomDirectionLocale';\nimport { cn } from '../../utils/cn';\nimport { mergeDataTokens } from '../../utils/merge-data-tokens';\nimport { splitRootProps, type RootDOMProps } from '../../utils/root-props';\nimport { useTouchTrack } from './use-touch-track';\nimport {\n iconSizeWhenFits,\n movedStartIcon,\n segmentLength,\n trackGeometry,\n type TrackSegment,\n} from './slider-geometry';\nimport { sliderStyles, type SliderOrientation, type SliderSize } from './slider-styles';\n\nexport interface SliderClassNames {\n /** The outer element. */\n root?: string;\n /** The track, holding the active and inactive segments and the stop indicators. */\n track?: string;\n /** Each active track segment (the filled part, from the start or the centre). */\n activeTrack?: string;\n /** Each inactive track segment. */\n inactiveTrack?: string;\n /** The stop indicators: at the track's ends, and at every step with `ticks`. */\n stop?: string;\n /** The inset icons (`leadingIcon`, `trailingIcon`). */\n icon?: string;\n /** Each thumb, the pressable area around its handle. */\n thumb?: string;\n /** The handle bar drawn inside each thumb. */\n handle?: string;\n /** The value indicator above a thumb (`valueLabel`). */\n label?: string;\n}\n\n/** A slider needs a name: `aria-label`, or `aria-labelledby` pointing at a visible label. */\ntype Naming =\n | {\n /** Accessible name when there's no visible label. */\n 'aria-label': string;\n }\n | {\n /** The id of the visible label that names the slider. */\n 'aria-labelledby': string;\n };\n\ninterface SliderCommonProps extends RootDOMProps {\n /** The track's thickness, from XS (a 16px track) to XL. @default \"xs\" */\n size?: SliderSize;\n /**\n * The direction the slider runs. Vertical sliders run bottom to top and default to 240px\n * tall. @default \"horizontal\"\n */\n orientation?: SliderOrientation;\n /** The lowest value. @default 0 */\n min?: number;\n /** The highest value. @default 100 */\n max?: number;\n /** The amount the value changes by in one step. @default 1 */\n step?: number;\n /** Discrete: show a stop indicator at every step (use a coarse `step`). @default false */\n ticks?: boolean;\n /** Turns the slider off: it can't be moved or focused. @default false */\n disabled?: boolean;\n /** Show the value above the thumb while it is dragged or keyboard-focused. @default false */\n valueLabel?: boolean;\n /** Formats the value for the value indicator and assistive tech. */\n formatOptions?: Intl.NumberFormatOptions;\n /** Form field name (one hidden input per thumb). */\n name?: string;\n /** The id of the slider's root, its `group` (an error summary can link to it). */\n id?: string;\n /** Ref to the root `<div>` element. */\n ref?: Ref<HTMLDivElement>;\n /** Classes for the root element, merged with `classNames.root`; consumer classes win. */\n className?: string;\n /** Inline styles for the root element. */\n style?: CSSProperties;\n /** Classes for each part, by slot. `classNames.root` is merged with `className`. */\n classNames?: SliderClassNames;\n /** `data-*` attributes, like the other DOM attributes, go to the root. */\n [data: `data-${string}`]: string | number | boolean | undefined;\n}\n\nexport type SliderProps = SliderCommonProps &\n Naming & {\n /** The value (controlled). */\n value?: number;\n /** The initial value when uncontrolled. @default min */\n defaultValue?: number;\n /** Called as the value changes, while dragging too. */\n onChange?: (value: number) => void;\n /** Called when the user stops dragging or releases a key. */\n onChangeEnd?: (value: number) => void;\n /**\n * Fill the track from its centre (for values around a midpoint). M3 doesn't use inset\n * icons on centred sliders, so `leadingIcon` and `trailingIcon` are ignored. @default false\n */\n centered?: boolean;\n /**\n * An icon inside the active track at its start (sizes md and up; ignored, with a dev\n * warning, on xs and sm). When the active track is too short for it, it moves to the\n * start of the inactive track.\n */\n leadingIcon?: ReactNode;\n /** An icon inside the inactive track at its end (sizes md and up; ignored on xs and sm). */\n trailingIcon?: ReactNode;\n };\n\nexport interface RangeSliderLabels {\n /** Name of the start thumb. @default \"Minimum\" */\n start: string;\n /** Name of the end thumb. @default \"Maximum\" */\n end: string;\n}\n\nconst DEFAULT_LABELS: RangeSliderLabels = { start: 'Minimum', end: 'Maximum' };\n\nexport type RangeSliderProps = SliderCommonProps &\n Naming & {\n /** The range, as `[start, end]` (controlled). */\n value?: [number, number];\n /** The initial range when uncontrolled. @default [min, max] */\n defaultValue?: [number, number];\n /** Called as the range changes, while dragging too. */\n onChange?: (value: [number, number]) => void;\n /** Called when the user stops dragging or releases a key. */\n onChangeEnd?: (value: [number, number]) => void;\n /** Words the range slider announces (its thumbs' names), with English defaults. */\n labels?: Partial<RangeSliderLabels>;\n };\n\n/**\n * M3 Expressive slider: XS–XL sizes, horizontal or vertical, continuous or discrete with\n * stop indicators, optionally centred, with inset icons and a value indicator. Arrow keys,\n * Page Up / Down and Home / End adjust it (mirrored in RTL).\n *\n * @example\n * <Slider aria-label=\"Volume\" size=\"md\" leadingIcon={<VolumeIcon />} defaultValue={40} />\n */\nexport function Slider(props: SliderProps) {\n return (\n <DomDirectionLocale>\n {(directionRef) => <SliderRoot {...props} range={false} directionRef={directionRef} />}\n </DomDirectionLocale>\n );\n}\n\n/**\n * M3 Expressive range slider: two thumbs selecting a range, with the same sizes,\n * orientations and discrete stops as {@link Slider}.\n *\n * @example\n * <RangeSlider aria-label=\"Price\" defaultValue={[20, 80]} />\n */\nexport function RangeSlider(props: RangeSliderProps) {\n return (\n <DomDirectionLocale>\n {(directionRef) => <SliderRoot {...props} range directionRef={directionRef} />}\n </DomDirectionLocale>\n );\n}\n\ntype RootProps = (SliderProps | RangeSliderProps) & {\n range: boolean;\n directionRef: (element: HTMLElement | null) => void;\n};\n\nfunction SliderRoot(props: RootProps) {\n const {\n size = 'xs',\n orientation = 'horizontal',\n min = 0,\n max = 100,\n step = 1,\n ticks = false,\n disabled = false,\n valueLabel = false,\n formatOptions,\n name,\n id,\n ref,\n className,\n style,\n classNames,\n range,\n directionRef,\n value,\n defaultValue,\n onChange,\n onChangeEnd,\n ...rest\n } = props;\n const {\n centered = false,\n leadingIcon: startIconProp,\n trailingIcon: endIconProp,\n } = props as SliderProps;\n // M3: only M, L and XL standard sliders take inset icons (\"Avoid adding inset icons to XS\n // or S\"; none on centred sliders).\n const iconSize = size === 'md' || size === 'lg' || size === 'xl';\n const startIcon = iconSize && !centered ? startIconProp : undefined;\n const endIcon = iconSize && !centered ? endIconProp : undefined;\n const labels = { ...DEFAULT_LABELS, ...(props as RangeSliderProps).labels };\n // The root is the slider's group: its DOM attributes join the hook's.\n const { root: data, rest: labelling } = splitRootProps(rest);\n // The naming attributes: the name goes to the group and each thumb (React Aria), and the\n // description, details and error message to each thumb's input, which takes focus.\n const {\n 'aria-label': label,\n 'aria-labelledby': labelledBy,\n 'aria-describedby': describedBy,\n 'aria-details': details,\n 'aria-errormessage': errorMessage,\n } = labelling as {\n 'aria-label'?: string;\n 'aria-labelledby'?: string;\n 'aria-describedby'?: string;\n 'aria-details'?: string;\n 'aria-errormessage'?: string;\n };\n\n const numberFormatter = useNumberFormatter(formatOptions);\n // React Stately works with arrays; a single slider wraps and unwraps its number.\n const wrap = (v: number | [number, number] | undefined) =>\n v === undefined ? undefined : Array.isArray(v) ? v : [v];\n const unwrap = (v: number[]) => (range ? (v as [number, number]) : v[0]!);\n const ariaProps = {\n id,\n minValue: min,\n maxValue: max,\n step,\n orientation,\n isDisabled: disabled,\n 'aria-label': label,\n 'aria-labelledby': labelledBy,\n 'aria-describedby': describedBy,\n 'aria-details': details,\n value: wrap(value),\n defaultValue: wrap(defaultValue) ?? (range ? [min, max] : [min]),\n onChange: onChange && ((v: number[]) => (onChange as (x: unknown) => void)(unwrap(v))),\n onChangeEnd: onChangeEnd && ((v: number[]) => (onChangeEnd as (x: unknown) => void)(unwrap(v))),\n };\n const state = useSliderState({ ...ariaProps, numberFormatter });\n const rootRef = useObjectRef(ref);\n const trackRef = useRef<HTMLDivElement>(null);\n const { groupProps, trackProps } = useSlider(ariaProps, state, trackRef);\n const { direction } = useLocale();\n const count = Math.round((max - min) / step);\n const touchTrackProps = useTouchTrack(trackProps, {\n state,\n trackRef,\n orientation,\n direction,\n disabled,\n discrete: ticks && count > 0,\n });\n\n const tickFractions =\n ticks && count > 0 ? Array.from({ length: count + 1 }, (_, i) => i / count) : [];\n const fractions = state.values.map((_, index) => state.getThumbPercent(index));\n const geometry = trackGeometry({ fractions, ticks: tickFractions, centered: centered && !range });\n const styles = sliderStyles({ size, orientation });\n if (process.env.NODE_ENV !== 'production' && (startIconProp != null || endIconProp != null)) {\n if (centered) {\n console.warn(\n 'Slider: inset icons are not used on centred sliders (M3), so they are ignored.',\n );\n } else if (!iconSize) {\n console.warn('Slider: inset icons need size md, lg or xl (M3), so they are ignored.');\n }\n }\n if (process.env.NODE_ENV !== 'production' && range && orientation === 'vertical') {\n console.warn('RangeSlider: M3 advises against vertical range sliders.');\n }\n const activeSegment = geometry.segments.find((segment) => segment.icon === 'start');\n const moved =\n activeSegment && startIcon != null && movedStartIcon(activeSegment, geometry.thumbs[0]!);\n\n return (\n <div\n {...mergeProps(data, groupProps)}\n ref={(element) => {\n rootRef.current = element;\n directionRef(element);\n }}\n style={style}\n data-tokens={mergeDataTokens('md-comp-slider', data)}\n data-size={size}\n data-orientation={orientation}\n data-disabled={disabled || undefined}\n className={styles.root({ class: cn(classNames?.root, className) })}\n >\n <div\n {...touchTrackProps}\n ref={trackRef}\n className={styles.track({ class: classNames?.track })}\n >\n {geometry.segments.map((segment, index) => (\n <Segment\n key={index}\n segment={segment}\n styles={styles}\n classNames={classNames}\n icon={\n segment.icon === 'start' ? startIcon : segment.icon === 'end' ? endIcon : undefined\n }\n />\n ))}\n {moved && (\n <span\n aria-hidden=\"true\"\n data-moved-icon=\"\"\n style={{ marginInlineStart: moved.start, inlineSize: moved.size }}\n className={styles.movedIcon({ class: classNames?.icon })}\n >\n {startIcon}\n </span>\n )}\n {geometry.ticks.map((tick, index) => (\n <span\n key={index}\n aria-hidden=\"true\"\n data-slot=\"stop-indicator\"\n data-active={tick.active || undefined}\n style={{ marginInlineStart: `calc(${tick.at} - 2px)` }}\n className={styles.tick({ class: classNames?.stop })}\n />\n ))}\n {geometry.thumbs.map((at, index) => (\n <Thumb\n key={index}\n index={index as 0 | 1}\n at={at}\n state={state}\n trackRef={trackRef}\n name={name}\n label={range ? (index === 0 ? labels.start : labels.end) : undefined}\n errorMessage={errorMessage}\n valueLabel={valueLabel}\n styles={styles}\n classNames={classNames}\n />\n ))}\n </div>\n </div>\n );\n}\n\nfunction Segment({\n segment,\n styles,\n classNames,\n icon,\n}: {\n segment: TrackSegment;\n styles: ReturnType<typeof sliderStyles>;\n classNames: SliderClassNames | undefined;\n icon: ReactNode;\n}) {\n const radius = {\n borderStartStartRadius: segment.startRadius,\n borderEndStartRadius: segment.startRadius,\n borderStartEndRadius: segment.endRadius,\n borderEndEndRadius: segment.endRadius,\n };\n // Inset icons sit 10px inside and show only when the segment fits them (MDC).\n const iconBox = (\n <span\n aria-hidden=\"true\"\n style={{\n inlineSize: iconSizeWhenFits('100%'),\n marginInlineStart: segment.icon === 'start' ? '10px' : 'auto',\n marginInlineEnd: segment.icon === 'end' ? '10px' : undefined,\n }}\n className={styles.icon({ class: classNames?.icon })}\n >\n {icon}\n </span>\n );\n // Stop indicators sit a corner's length in from the track's ends.\n const stop = (at: 'start' | 'end') => (\n <span\n aria-hidden=\"true\"\n data-slot=\"stop-indicator\"\n style={\n at === 'start'\n ? { marginInlineStart: 'calc(var(--m3-slider-corner) - 2px)' }\n : { marginInlineStart: 'auto', marginInlineEnd: 'calc(var(--m3-slider-corner) - 2px)' }\n }\n className={styles.stop({ class: classNames?.stop })}\n />\n );\n return (\n <span\n data-tone={segment.tone}\n data-slot={`${segment.tone}-track`}\n style={{ marginInlineStart: segment.start, inlineSize: segmentLength(segment), ...radius }}\n className={styles.segment({\n class: segment.tone === 'active' ? classNames?.activeTrack : classNames?.inactiveTrack,\n })}\n >\n {segment.stopAtStart && stop('start')}\n {icon != null && iconBox}\n {/* An end icon takes the stop indicator's place. */}\n {segment.stopAtEnd && icon == null && stop('end')}\n </span>\n );\n}\n\n/** Whether each arrow key increases the value (true) or decreases it (false), by direction. */\nconst INCREASE_KEYS: Record<'ltr' | 'rtl', Record<string, boolean>> = {\n ltr: { ArrowRight: true, ArrowUp: true, ArrowLeft: false, ArrowDown: false },\n rtl: { ArrowLeft: true, ArrowUp: true, ArrowRight: false, ArrowDown: false },\n};\n\nfunction Thumb({\n index,\n at,\n state,\n trackRef,\n name,\n label,\n errorMessage,\n valueLabel,\n styles,\n classNames,\n}: {\n index: 0 | 1;\n at: string;\n state: SliderState;\n trackRef: RefObject<HTMLDivElement | null>;\n name?: string;\n label?: string;\n errorMessage?: string;\n valueLabel: boolean;\n styles: ReturnType<typeof sliderStyles>;\n classNames?: SliderClassNames;\n}) {\n const inputRef = useRef<HTMLInputElement>(null);\n const { thumbProps, inputProps, isDragging } = useSliderThumb(\n { index, trackRef, inputRef, name, 'aria-label': label, 'aria-errormessage': errorMessage },\n state,\n );\n // React Aria joins the description's ids into an empty string when there are none.\n const thumbInputProps = {\n ...inputProps,\n 'aria-describedby': inputProps['aria-describedby'] || undefined,\n 'aria-details': inputProps['aria-details'] || undefined,\n };\n const { focusProps, isFocusVisible } = useFocusRing();\n const { direction } = useLocale();\n // M3: \"Space & Arrows — Increase and decrease the value by one interval\", so arrows step\n // by React Aria's page size while Space is held (as Shift + arrows already do).\n const [spaceHeld, setSpaceHeld] = useState(false);\n const intervalKeys = {\n onKeyDown: (event: KeyboardEvent<HTMLInputElement>) => {\n if (event.key === ' ') {\n event.preventDefault();\n setSpaceHeld(true);\n return;\n }\n const increase = INCREASE_KEYS[direction][event.key];\n if (!spaceHeld || increase === undefined) return;\n event.preventDefault();\n event.stopPropagation();\n state.setThumbDragging(index, true);\n if (increase) state.incrementThumb(index, state.pageSize);\n else state.decrementThumb(index, state.pageSize);\n state.setThumbDragging(index, false);\n },\n onKeyUp: (event: KeyboardEvent<HTMLInputElement>) => {\n if (event.key === ' ') setSpaceHeld(false);\n },\n onBlur: () => setSpaceHeld(false),\n };\n // The handle narrows to 2px while it is pressed or dragged (M3), or keyboard-focused.\n const active = isDragging || isFocusVisible;\n return (\n <>\n <div\n {...thumbProps}\n data-thumb={index}\n data-focus-visible={isFocusVisible || undefined}\n data-dragging={isDragging || undefined}\n style={{ marginInlineStart: `calc(${at} - 2px)` }}\n className={styles.thumb({ class: classNames?.thumb })}\n >\n <span\n data-slot=\"handle\"\n data-active={active || undefined}\n className={styles.handle({ class: classNames?.handle })}\n />\n <VisuallyHidden>\n <input ref={inputRef} {...mergeProps(thumbInputProps, focusProps, intervalKeys)} />\n </VisuallyHidden>\n </div>\n {valueLabel && (isDragging || isFocusVisible) && (\n <span aria-hidden=\"true\" style={{ marginInlineStart: at }} className={styles.labelAnchor()}>\n <span data-slot=\"value-indicator\" className={styles.label({ class: classNames?.label })}>\n {state.getThumbValueLabel(index)}\n </span>\n </span>\n )}\n </>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Slider.js","names":[],"sources":["../../../src/components/slider/Slider.tsx"],"sourcesContent":["'use client';\n\nimport {\n useRef,\n useState,\n type CSSProperties,\n type KeyboardEvent,\n type ReactNode,\n type Ref,\n type RefObject,\n} from 'react';\nimport {\n mergeProps,\n useFocusRing,\n useLocale,\n useNumberFormatter,\n useObjectRef,\n useSlider,\n useSliderThumb,\n VisuallyHidden,\n} from 'react-aria';\nimport { useSliderState, type SliderState } from 'react-stately';\nimport { useLabels } from '../labels/use-labels';\nimport { DomDirectionLocale } from '../../primitives/DomDirectionLocale';\nimport { cn } from '../../utils/cn';\nimport { mergeDataTokens } from '../../utils/merge-data-tokens';\nimport { splitRootProps, type RootDOMProps } from '../../utils/root-props';\nimport { useTouchTrack } from './use-touch-track';\nimport {\n iconSizeWhenFits,\n movedStartIcon,\n segmentLength,\n trackGeometry,\n type TrackSegment,\n} from './slider-geometry';\nimport { sliderStyles, type SliderOrientation, type SliderSize } from './slider-styles';\n\nexport interface SliderClassNames {\n /** The outer element. */\n root?: string;\n /** The track, holding the active and inactive segments and the stop indicators. */\n track?: string;\n /** Each active track segment (the filled part, from the start or the centre). */\n activeTrack?: string;\n /** Each inactive track segment. */\n inactiveTrack?: string;\n /** The stop indicators: at the track's ends, and at every step with `ticks`. */\n stop?: string;\n /** The inset icons (`leadingIcon`, `trailingIcon`). */\n icon?: string;\n /** Each thumb, the pressable area around its handle. */\n thumb?: string;\n /** The handle bar drawn inside each thumb. */\n handle?: string;\n /** The value indicator above a thumb (`valueLabel`). */\n label?: string;\n}\n\n/** A slider needs a name: `aria-label`, or `aria-labelledby` pointing at a visible label. */\ntype Naming =\n | {\n /** Accessible name when there's no visible label. */\n 'aria-label': string;\n }\n | {\n /** The id of the visible label that names the slider. */\n 'aria-labelledby': string;\n };\n\ninterface SliderCommonProps extends RootDOMProps {\n /** The track's thickness, from XS (a 16px track) to XL. @default \"xs\" */\n size?: SliderSize;\n /**\n * The direction the slider runs. Vertical sliders run bottom to top and default to 240px\n * tall. @default \"horizontal\"\n */\n orientation?: SliderOrientation;\n /** The lowest value. @default 0 */\n min?: number;\n /** The highest value. @default 100 */\n max?: number;\n /** The amount the value changes by in one step. @default 1 */\n step?: number;\n /** Discrete: show a stop indicator at every step (use a coarse `step`). @default false */\n ticks?: boolean;\n /** Turns the slider off: it can't be moved or focused. @default false */\n disabled?: boolean;\n /** Show the value above the thumb while it is dragged or keyboard-focused. @default false */\n valueLabel?: boolean;\n /** Formats the value for the value indicator and assistive tech. */\n formatOptions?: Intl.NumberFormatOptions;\n /** Form field name (one hidden input per thumb). */\n name?: string;\n /** The id of the slider's root, its `group` (an error summary can link to it). */\n id?: string;\n /** Ref to the root `<div>` element. */\n ref?: Ref<HTMLDivElement>;\n /** Classes for the root element, merged with `classNames.root`; consumer classes win. */\n className?: string;\n /** Inline styles for the root element. */\n style?: CSSProperties;\n /** Classes for each part, by slot. `classNames.root` is merged with `className`. */\n classNames?: SliderClassNames;\n /** `data-*` attributes, like the other DOM attributes, go to the root. */\n [data: `data-${string}`]: string | number | boolean | undefined;\n}\n\nexport type SliderProps = SliderCommonProps &\n Naming & {\n /** The value (controlled). */\n value?: number;\n /** The initial value when uncontrolled. @default min */\n defaultValue?: number;\n /** Called as the value changes, while dragging too. */\n onChange?: (value: number) => void;\n /** Called when the user stops dragging or releases a key. */\n onChangeEnd?: (value: number) => void;\n /**\n * Fill the track from its centre (for values around a midpoint). M3 doesn't use inset\n * icons on centred sliders, so `leadingIcon` and `trailingIcon` are ignored. @default false\n */\n centered?: boolean;\n /**\n * An icon inside the active track at its start (sizes md and up; ignored, with a dev\n * warning, on xs and sm). When the active track is too short for it, it moves to the\n * start of the inactive track.\n */\n leadingIcon?: ReactNode;\n /** An icon inside the inactive track at its end (sizes md and up; ignored on xs and sm). */\n trailingIcon?: ReactNode;\n };\n\nexport interface RangeSliderLabels {\n /** Name of the start thumb. @default \"Minimum\" */\n start: string;\n /** Name of the end thumb. @default \"Maximum\" */\n end: string;\n}\n\nconst DEFAULT_LABELS: RangeSliderLabels = { start: 'Minimum', end: 'Maximum' };\n\nexport type RangeSliderProps = SliderCommonProps &\n Naming & {\n /** The range, as `[start, end]` (controlled). */\n value?: [number, number];\n /** The initial range when uncontrolled. @default [min, max] */\n defaultValue?: [number, number];\n /** Called as the range changes, while dragging too. */\n onChange?: (value: [number, number]) => void;\n /** Called when the user stops dragging or releases a key. */\n onChangeEnd?: (value: [number, number]) => void;\n /**\n * Words the range slider announces (its thumbs' names), with English defaults.\n * A {@link LabelsProvider} sets them for the whole app.\n */\n labels?: Partial<RangeSliderLabels>;\n };\n\n/**\n * M3 Expressive slider: XS–XL sizes, horizontal or vertical, continuous or discrete with\n * stop indicators, optionally centred, with inset icons and a value indicator. Arrow keys,\n * Page Up / Down and Home / End adjust it (mirrored in RTL).\n *\n * @example\n * <Slider aria-label=\"Volume\" size=\"md\" leadingIcon={<VolumeIcon />} defaultValue={40} />\n */\nexport function Slider(props: SliderProps) {\n return (\n <DomDirectionLocale>\n {(directionRef) => <SliderRoot {...props} range={false} directionRef={directionRef} />}\n </DomDirectionLocale>\n );\n}\n\n/**\n * M3 Expressive range slider: two thumbs selecting a range, with the same sizes,\n * orientations and discrete stops as {@link Slider}.\n *\n * @example\n * <RangeSlider aria-label=\"Price\" defaultValue={[20, 80]} />\n */\nexport function RangeSlider(props: RangeSliderProps) {\n return (\n <DomDirectionLocale>\n {(directionRef) => <SliderRoot {...props} range directionRef={directionRef} />}\n </DomDirectionLocale>\n );\n}\n\ntype RootProps = (SliderProps | RangeSliderProps) & {\n range: boolean;\n directionRef: (element: HTMLElement | null) => void;\n};\n\nfunction SliderRoot(props: RootProps) {\n const {\n size = 'xs',\n orientation = 'horizontal',\n min = 0,\n max = 100,\n step = 1,\n ticks = false,\n disabled = false,\n valueLabel = false,\n formatOptions,\n name,\n id,\n ref,\n className,\n style,\n classNames,\n range,\n directionRef,\n value,\n defaultValue,\n onChange,\n onChangeEnd,\n ...rest\n } = props;\n const {\n centered = false,\n leadingIcon: startIconProp,\n trailingIcon: endIconProp,\n } = props as SliderProps;\n // M3: only M, L and XL standard sliders take inset icons (\"Avoid adding inset icons to XS\n // or S\"; none on centred sliders).\n const iconSize = size === 'md' || size === 'lg' || size === 'xl';\n const startIcon = iconSize && !centered ? startIconProp : undefined;\n const endIcon = iconSize && !centered ? endIconProp : undefined;\n const labels = useLabels('RangeSlider', DEFAULT_LABELS, (props as RangeSliderProps).labels);\n // The root is the slider's group: its DOM attributes join the hook's.\n const { root: data, rest: labelling } = splitRootProps(rest);\n // The naming attributes: the name goes to the group and each thumb (React Aria), and the\n // description, details and error message to each thumb's input, which takes focus.\n const {\n 'aria-label': label,\n 'aria-labelledby': labelledBy,\n 'aria-describedby': describedBy,\n 'aria-details': details,\n 'aria-errormessage': errorMessage,\n } = labelling as {\n 'aria-label'?: string;\n 'aria-labelledby'?: string;\n 'aria-describedby'?: string;\n 'aria-details'?: string;\n 'aria-errormessage'?: string;\n };\n\n const numberFormatter = useNumberFormatter(formatOptions);\n // React Stately works with arrays; a single slider wraps and unwraps its number.\n const wrap = (v: number | [number, number] | undefined) =>\n v === undefined ? undefined : Array.isArray(v) ? v : [v];\n const unwrap = (v: number[]) => (range ? (v as [number, number]) : v[0]!);\n const ariaProps = {\n id,\n minValue: min,\n maxValue: max,\n step,\n orientation,\n isDisabled: disabled,\n 'aria-label': label,\n 'aria-labelledby': labelledBy,\n 'aria-describedby': describedBy,\n 'aria-details': details,\n value: wrap(value),\n defaultValue: wrap(defaultValue) ?? (range ? [min, max] : [min]),\n onChange: onChange && ((v: number[]) => (onChange as (x: unknown) => void)(unwrap(v))),\n onChangeEnd: onChangeEnd && ((v: number[]) => (onChangeEnd as (x: unknown) => void)(unwrap(v))),\n };\n const state = useSliderState({ ...ariaProps, numberFormatter });\n const rootRef = useObjectRef(ref);\n const trackRef = useRef<HTMLDivElement>(null);\n const { groupProps, trackProps } = useSlider(ariaProps, state, trackRef);\n const { direction } = useLocale();\n const count = Math.round((max - min) / step);\n const touchTrackProps = useTouchTrack(trackProps, {\n state,\n trackRef,\n orientation,\n direction,\n disabled,\n discrete: ticks && count > 0,\n });\n\n const tickFractions =\n ticks && count > 0 ? Array.from({ length: count + 1 }, (_, i) => i / count) : [];\n const fractions = state.values.map((_, index) => state.getThumbPercent(index));\n const geometry = trackGeometry({ fractions, ticks: tickFractions, centered: centered && !range });\n const styles = sliderStyles({ size, orientation });\n if (process.env.NODE_ENV !== 'production' && (startIconProp != null || endIconProp != null)) {\n if (centered) {\n console.warn(\n 'Slider: inset icons are not used on centred sliders (M3), so they are ignored.',\n );\n } else if (!iconSize) {\n console.warn('Slider: inset icons need size md, lg or xl (M3), so they are ignored.');\n }\n }\n if (process.env.NODE_ENV !== 'production' && range && orientation === 'vertical') {\n console.warn('RangeSlider: M3 advises against vertical range sliders.');\n }\n const activeSegment = geometry.segments.find((segment) => segment.icon === 'start');\n const moved =\n activeSegment && startIcon != null && movedStartIcon(activeSegment, geometry.thumbs[0]!);\n\n return (\n <div\n {...mergeProps(data, groupProps)}\n ref={(element) => {\n rootRef.current = element;\n directionRef(element);\n }}\n style={style}\n data-tokens={mergeDataTokens('md-comp-slider', data)}\n data-size={size}\n data-orientation={orientation}\n data-disabled={disabled || undefined}\n className={styles.root({ class: cn(classNames?.root, className) })}\n >\n <div\n {...touchTrackProps}\n ref={trackRef}\n className={styles.track({ class: classNames?.track })}\n >\n {geometry.segments.map((segment, index) => (\n <Segment\n key={index}\n segment={segment}\n styles={styles}\n classNames={classNames}\n icon={\n segment.icon === 'start' ? startIcon : segment.icon === 'end' ? endIcon : undefined\n }\n />\n ))}\n {moved && (\n <span\n aria-hidden=\"true\"\n data-moved-icon=\"\"\n style={{ marginInlineStart: moved.start, inlineSize: moved.size }}\n className={styles.movedIcon({ class: classNames?.icon })}\n >\n {startIcon}\n </span>\n )}\n {geometry.ticks.map((tick, index) => (\n <span\n key={index}\n aria-hidden=\"true\"\n data-slot=\"stop-indicator\"\n data-active={tick.active || undefined}\n style={{ marginInlineStart: `calc(${tick.at} - 2px)` }}\n className={styles.tick({ class: classNames?.stop })}\n />\n ))}\n {geometry.thumbs.map((at, index) => (\n <Thumb\n key={index}\n index={index as 0 | 1}\n at={at}\n state={state}\n trackRef={trackRef}\n name={name}\n groupId={groupProps.id}\n labelledBy={labelledBy}\n label={range ? (index === 0 ? labels.start : labels.end) : undefined}\n errorMessage={errorMessage}\n valueLabel={valueLabel}\n styles={styles}\n classNames={classNames}\n />\n ))}\n </div>\n </div>\n );\n}\n\nfunction Segment({\n segment,\n styles,\n classNames,\n icon,\n}: {\n segment: TrackSegment;\n styles: ReturnType<typeof sliderStyles>;\n classNames: SliderClassNames | undefined;\n icon: ReactNode;\n}) {\n const radius = {\n borderStartStartRadius: segment.startRadius,\n borderEndStartRadius: segment.startRadius,\n borderStartEndRadius: segment.endRadius,\n borderEndEndRadius: segment.endRadius,\n };\n // Inset icons sit 10px inside and show only when the segment fits them (MDC).\n const iconBox = (\n <span\n aria-hidden=\"true\"\n style={{\n inlineSize: iconSizeWhenFits('100%'),\n marginInlineStart: segment.icon === 'start' ? '10px' : 'auto',\n marginInlineEnd: segment.icon === 'end' ? '10px' : undefined,\n }}\n className={styles.icon({ class: classNames?.icon })}\n >\n {icon}\n </span>\n );\n // Stop indicators sit a corner's length in from the track's ends.\n const stop = (at: 'start' | 'end') => (\n <span\n aria-hidden=\"true\"\n data-slot=\"stop-indicator\"\n style={\n at === 'start'\n ? { marginInlineStart: 'calc(var(--m3-slider-corner) - 2px)' }\n : { marginInlineStart: 'auto', marginInlineEnd: 'calc(var(--m3-slider-corner) - 2px)' }\n }\n className={styles.stop({ class: classNames?.stop })}\n />\n );\n return (\n <span\n data-tone={segment.tone}\n data-slot={`${segment.tone}-track`}\n style={{ marginInlineStart: segment.start, inlineSize: segmentLength(segment), ...radius }}\n className={styles.segment({\n class: segment.tone === 'active' ? classNames?.activeTrack : classNames?.inactiveTrack,\n })}\n >\n {segment.stopAtStart && stop('start')}\n {icon != null && iconBox}\n {/* An end icon takes the stop indicator's place. */}\n {segment.stopAtEnd && icon == null && stop('end')}\n </span>\n );\n}\n\n/** Whether each arrow key increases the value (true) or decreases it (false), by direction. */\nconst INCREASE_KEYS: Record<'ltr' | 'rtl', Record<string, boolean>> = {\n ltr: { ArrowRight: true, ArrowUp: true, ArrowLeft: false, ArrowDown: false },\n rtl: { ArrowLeft: true, ArrowUp: true, ArrowRight: false, ArrowDown: false },\n};\n\nfunction Thumb({\n index,\n at,\n state,\n trackRef,\n name,\n groupId,\n labelledBy,\n label,\n errorMessage,\n valueLabel,\n styles,\n classNames,\n}: {\n index: 0 | 1;\n at: string;\n state: SliderState;\n trackRef: RefObject<HTMLDivElement | null>;\n name?: string;\n /** The id of the slider's group, which React Aria names every thumb by. */\n groupId?: string;\n /** The ids that name the group (`aria-labelledby`), when it's named by a visible label. */\n labelledBy?: string;\n label?: string;\n errorMessage?: string;\n valueLabel: boolean;\n styles: ReturnType<typeof sliderStyles>;\n classNames?: SliderClassNames;\n}) {\n const inputRef = useRef<HTMLInputElement>(null);\n const { thumbProps, inputProps, isDragging } = useSliderThumb(\n { index, trackRef, inputRef, name, 'aria-label': label, 'aria-errormessage': errorMessage },\n state,\n );\n // React Aria names each thumb by the group (`aria-labelledby` → the group's id). A name is\n // never followed through a second `aria-labelledby`, so a group named by a visible label would\n // name the thumb by its own content, the value text (\"0%\"): the thumb points at the label\n // itself instead, as the group does. An `aria-label` on the group is read through it as is.\n const thumbLabelledBy = labelledBy?.trim()\n ? inputProps['aria-labelledby']\n ?.split(' ')\n .flatMap((id) => (id === groupId ? labelledBy.trim().split(/\\s+/) : [id]))\n .join(' ')\n : inputProps['aria-labelledby'];\n // React Aria joins the description's ids into an empty string when there are none.\n const thumbInputProps = {\n ...inputProps,\n 'aria-labelledby': thumbLabelledBy,\n 'aria-describedby': inputProps['aria-describedby'] || undefined,\n 'aria-details': inputProps['aria-details'] || undefined,\n };\n const { focusProps, isFocusVisible } = useFocusRing();\n const { direction } = useLocale();\n // M3: \"Space & Arrows — Increase and decrease the value by one interval\", so arrows step\n // by React Aria's page size while Space is held (as Shift + arrows already do).\n const [spaceHeld, setSpaceHeld] = useState(false);\n const intervalKeys = {\n onKeyDown: (event: KeyboardEvent<HTMLInputElement>) => {\n if (event.key === ' ') {\n event.preventDefault();\n setSpaceHeld(true);\n return;\n }\n const increase = INCREASE_KEYS[direction][event.key];\n if (!spaceHeld || increase === undefined) return;\n event.preventDefault();\n event.stopPropagation();\n state.setThumbDragging(index, true);\n if (increase) state.incrementThumb(index, state.pageSize);\n else state.decrementThumb(index, state.pageSize);\n state.setThumbDragging(index, false);\n },\n onKeyUp: (event: KeyboardEvent<HTMLInputElement>) => {\n if (event.key === ' ') setSpaceHeld(false);\n },\n onBlur: () => setSpaceHeld(false),\n };\n // The handle narrows to 2px while it is pressed or dragged (M3), or keyboard-focused.\n const active = isDragging || isFocusVisible;\n return (\n <>\n <div\n {...thumbProps}\n data-thumb={index}\n data-focus-visible={isFocusVisible || undefined}\n data-dragging={isDragging || undefined}\n style={{ marginInlineStart: `calc(${at} - 2px)` }}\n className={styles.thumb({ class: classNames?.thumb })}\n >\n <span\n data-slot=\"handle\"\n data-active={active || undefined}\n className={styles.handle({ class: classNames?.handle })}\n />\n <VisuallyHidden>\n <input ref={inputRef} {...mergeProps(thumbInputProps, focusProps, intervalKeys)} />\n </VisuallyHidden>\n </div>\n {valueLabel && (isDragging || isFocusVisible) && (\n <span aria-hidden=\"true\" style={{ marginInlineStart: at }} className={styles.labelAnchor()}>\n <span data-slot=\"value-indicator\" className={styles.label({ class: classNames?.label })}>\n {state.getThumbValueLabel(index)}\n </span>\n </span>\n )}\n </>\n );\n}\n"],"mappings":"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"}
@@ -31,7 +31,10 @@ interface SnackbarOwnProps {
31
31
  * button. @default onDismiss
32
32
  */
33
33
  onEscape?: () => void;
34
- /** Words the snackbar announces, with English defaults. */
34
+ /**
35
+ * Words the snackbar announces, with English defaults. A {@link LabelsProvider} sets them for the
36
+ * whole app.
37
+ */
35
38
  labels?: Partial<SnackbarLabels>;
36
39
  /** Put the actions under the message, for long action labels. @default false */
37
40
  actionOnNewLine?: boolean;
@@ -58,7 +61,10 @@ export interface SnackbarHostProps extends Omit<ComponentPropsWithRef<'div'>, 'c
58
61
  state: SnackbarHostState;
59
62
  /** Renders each snackbar; defaults to {@link Snackbar}. */
60
63
  snackbar?: (data: SnackbarData) => React.ReactNode;
61
- /** Words each default snackbar announces, with English defaults. */
64
+ /**
65
+ * Words each default snackbar announces, with English defaults. A {@link LabelsProvider} sets
66
+ * them for the whole app.
67
+ */
62
68
  labels?: Partial<SnackbarLabels>;
63
69
  /**
64
70
  * The key combination that moves focus to a snackbar with actions, as the spec asks for on
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { cn } from "../../utils/cn.js";
3
+ import { useLabels } from "../labels/use-labels.js";
3
4
  import { mergeDataTokens } from "../../utils/merge-data-tokens.js";
4
5
  import { usePresence } from "../../primitives/use-presence.js";
5
6
  import { announcePolite } from "../../utils/announce.js";
@@ -40,10 +41,7 @@ function Snackbar({ children, actionLabel, onAction, onDismiss, onEscape = onDis
40
41
  newLine: actionOnNewLine && actionLabel !== void 0,
41
42
  dismissible: onDismiss !== void 0
42
43
  });
43
- const labels = {
44
- ...DEFAULT_LABELS,
45
- ...labelsProp
46
- };
44
+ const labels = useLabels("Snackbar", DEFAULT_LABELS, labelsProp);
47
45
  const hasActions = actionLabel !== void 0 || onDismiss !== void 0;
48
46
  const inHost = useContext(InSnackbarHost);
49
47
  const messageRef = useRef(null);