@windmorph/react 0.2.3 → 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.
- package/dist/components/autocomplete/Autocomplete.d.ts +4 -1
- package/dist/components/autocomplete/Autocomplete.js +2 -4
- package/dist/components/autocomplete/Autocomplete.js.map +1 -1
- package/dist/components/badge/Badge.d.ts +5 -2
- package/dist/components/badge/Badge.js +7 -3
- package/dist/components/badge/Badge.js.map +1 -1
- package/dist/components/button/Button.d.ts +4 -2
- package/dist/components/button/Button.js.map +1 -1
- package/dist/components/button/ButtonContext.d.ts +1 -1
- package/dist/components/button/button-pending.js +2 -4
- package/dist/components/button/button-pending.js.map +1 -1
- package/dist/{composites → components}/button-group/ButtonGroup.d.ts +4 -4
- package/dist/{composites → components}/button-group/ButtonGroup.js +9 -7
- package/dist/components/button-group/ButtonGroup.js.map +1 -0
- package/dist/{composites → components}/button-group/button-group-styles.d.ts +1 -1
- package/dist/{composites → components}/button-group/button-group-styles.js +1 -1
- package/dist/{composites → components}/button-group/button-group-styles.js.map +1 -1
- package/dist/{composites → components}/button-group/lazy-overflow-menu.js +2 -2
- package/dist/components/button-group/lazy-overflow-menu.js.map +1 -0
- package/dist/{composites → components}/button-group/overflow-menu-list.js +2 -2
- package/dist/components/button-group/overflow-menu-list.js.map +1 -0
- package/dist/{composites → components}/button-group/use-overflow.js +1 -1
- package/dist/{composites → components}/button-group/use-overflow.js.map +1 -1
- package/dist/{composites → components}/button-group/use-press-expansion.js +1 -1
- package/dist/{composites → components}/button-group/use-press-expansion.js.map +1 -1
- package/dist/components/carousel/Carousel.d.ts +4 -1
- package/dist/components/carousel/Carousel.js +2 -4
- package/dist/components/carousel/Carousel.js.map +1 -1
- package/dist/components/checkbox/checkbox-styles.d.ts +3 -3
- package/dist/components/checkbox/checkbox-tokens.d.ts +1 -1
- package/dist/components/chip/Chip.d.ts +8 -2
- package/dist/components/chip/Chip.js +3 -8
- package/dist/components/chip/Chip.js.map +1 -1
- package/dist/components/date-picker/DateField.d.ts +4 -1
- package/dist/components/date-picker/DateField.js +3 -1
- package/dist/components/date-picker/DateField.js.map +1 -1
- package/dist/components/date-picker/DatePicker.d.ts +4 -1
- package/dist/components/date-picker/DatePicker.js +5 -2
- package/dist/components/date-picker/DatePicker.js.map +1 -1
- package/dist/components/date-picker/PickerDialog.d.ts +4 -1
- package/dist/components/date-picker/PickerDialog.js +3 -1
- package/dist/components/date-picker/PickerDialog.js.map +1 -1
- package/dist/components/date-picker/date-picker-styles.d.ts +15 -15
- package/dist/components/dialog/Dialog.d.ts +5 -2
- package/dist/components/dialog/Dialog.js +4 -2
- package/dist/components/dialog/Dialog.js.map +1 -1
- package/dist/components/icon-button/IconButton.d.ts +4 -2
- package/dist/components/icon-button/IconButton.js.map +1 -1
- package/dist/components/labels/LabelsProvider.d.ts +35 -0
- package/dist/components/labels/LabelsProvider.js +47 -0
- package/dist/components/labels/LabelsProvider.js.map +1 -0
- package/dist/components/labels/use-labels.d.ts +16 -0
- package/dist/components/labels/use-labels.js +42 -0
- package/dist/components/labels/use-labels.js.map +1 -0
- package/dist/components/menu/Menu.d.ts +4 -1
- package/dist/components/menu/Menu.js +7 -10
- package/dist/components/menu/Menu.js.map +1 -1
- package/dist/components/menu/menu-styles.js +12 -12
- package/dist/components/menu/menu-styles.js.map +1 -1
- package/dist/components/menu/menu-tokens.js.map +1 -1
- package/dist/components/navigation/NavItem.d.ts +1 -1
- package/dist/components/navigation/NavItem.js +9 -6
- package/dist/components/navigation/NavItem.js.map +1 -1
- package/dist/components/pull-to-refresh/PullToRefresh.d.ts +1 -1
- package/dist/components/pull-to-refresh/PullToRefresh.js +2 -4
- package/dist/components/pull-to-refresh/PullToRefresh.js.map +1 -1
- package/dist/components/search/SearchField.d.ts +4 -1
- package/dist/components/search/SearchField.js +2 -4
- package/dist/components/search/SearchField.js.map +1 -1
- package/dist/components/select/Select.d.ts +4 -1
- package/dist/components/select/Select.js +2 -4
- package/dist/components/select/Select.js.map +1 -1
- package/dist/components/sheet/BottomSheet.d.ts +4 -1
- package/dist/components/sheet/BottomSheet.js +2 -4
- package/dist/components/sheet/BottomSheet.js.map +1 -1
- package/dist/components/sheet/SideSheet.d.ts +4 -1
- package/dist/components/sheet/SideSheet.js +2 -4
- package/dist/components/sheet/SideSheet.js.map +1 -1
- package/dist/components/slider/Slider.d.ts +4 -1
- package/dist/components/slider/Slider.js +2 -4
- package/dist/components/slider/Slider.js.map +1 -1
- package/dist/components/snackbar/Snackbar.d.ts +8 -2
- package/dist/components/snackbar/Snackbar.js +2 -4
- package/dist/components/snackbar/Snackbar.js.map +1 -1
- package/dist/components/snackbar/snackbar-tokens.d.ts +1 -1
- package/dist/components/split-button/SplitButton.d.ts +4 -1
- package/dist/components/split-button/SplitButton.js +4 -2
- package/dist/components/split-button/SplitButton.js.map +1 -1
- package/dist/components/switch/switch-styles.d.ts +3 -3
- package/dist/components/text-field/TextField.d.ts +4 -1
- package/dist/components/text-field/TextField.js +2 -4
- package/dist/components/text-field/TextField.js.map +1 -1
- package/dist/components/time-picker/TimeField.d.ts +24 -2
- package/dist/components/time-picker/TimeField.js +40 -12
- package/dist/components/time-picker/TimeField.js.map +1 -1
- package/dist/components/time-picker/TimeFieldList.js +95 -0
- package/dist/components/time-picker/TimeFieldList.js.map +1 -0
- package/dist/components/time-picker/TimePicker.d.ts +9 -5
- package/dist/components/time-picker/TimePicker.js +5 -2
- package/dist/components/time-picker/TimePicker.js.map +1 -1
- package/dist/components/time-picker/time-list.js +33 -0
- package/dist/components/time-picker/time-list.js.map +1 -0
- package/dist/components/tooltip/Tooltip.d.ts +1 -1
- package/dist/index.d.ts +40 -38
- package/dist/index.js +4 -3
- package/dist/primitives.d.ts +7 -4
- package/dist/primitives.js +4 -1
- package/dist/styles.compiled.css +1 -1
- package/dist/utils/use-scroll-keys.d.ts +29 -0
- package/dist/utils/use-scroll-keys.js +66 -0
- package/dist/utils/use-scroll-keys.js.map +1 -0
- package/dist/utils/use-scroll-restoration.d.ts +43 -0
- package/dist/utils/use-scroll-restoration.js +173 -0
- package/dist/utils/use-scroll-restoration.js.map +1 -0
- package/dist/windmorph-labels.d.ts +101 -0
- package/package.json +1 -1
- package/dist/composites/button-group/ButtonGroup.js.map +0 -1
- package/dist/composites/button-group/lazy-overflow-menu.js.map +0 -1
- package/dist/composites/button-group/overflow-menu-list.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Select.js","names":[],"sources":["../../../src/components/select/Select.tsx"],"sourcesContent":["'use client';\n\nimport {\n useId,\n useRef,\n useSyncExternalStore,\n type AriaAttributes,\n type CSSProperties,\n type HTMLAttributes,\n type ReactNode,\n type Ref,\n} from 'react';\nimport {\n HiddenSelect,\n mergeProps,\n useButton,\n useFocusRing,\n useHover,\n useObjectRef,\n useSelect,\n type AriaSelectOptions,\n} from 'react-aria';\nimport {\n useSelectState,\n type SelectionMode,\n type SelectProps as StatelySelectProps,\n} from 'react-stately/useSelectState';\nimport { useControlledState } from 'react-stately/useControlledState';\nimport type { Key } from 'react-stately';\nimport { matchesSearch } from '../../utils/search';\nimport { assertCollectionChildren } from '../../utils/assert-collection-children';\nimport { densityAttribute, type Density } from '../../tokens/density';\nimport { cn } from '../../utils/cn';\nimport { renderedDescription } from '../../utils/rendered-description';\nimport {\n ariaFlag,\n pickDescriptionAria,\n splitRootProps,\n type WrapperRootDOMProps,\n} from '../../utils/root-props';\nimport { useTypeaheadDelegate } from '../../utils/typeahead';\nimport { usePreloadLoadingIndicator } from '../loading-indicator/lazy-loading-indicator';\nimport { LazyBottomSheet, usePreloadBottomSheet } from '../sheet/lazy-bottom-sheet';\nimport type { TextFieldVariant } from '../text-field/text-field-styles';\nimport { DropdownArrow, FieldShell, fieldState, type FieldShellClassNames } from './field-shell';\nimport {\n FOCUSABLE_DISABLED_OPTIONS,\n OptionList,\n OptionPopover,\n type OptionListClassNames,\n} from './option-list';\nimport { SearchList } from './search-list';\nimport { useSelectionCap } from './selection-cap';\n\n/** How the options open: a menu under the field (M3), a bottom sheet, or a sheet on phones. */\nexport type SelectPresentation = 'menu' | 'sheet' | 'auto';\n\n/** Words the Select shows or announces; override them for other languages. */\nexport interface SelectLabels {\n /** Shown in an empty list. @default \"No options\" */\n empty: string;\n /** The loading indicator's name. @default \"Loading options\" */\n loading: string;\n /** For more chosen options than fit: `+{count}`. @default (count) => `+${count}` */\n more: (count: number) => string;\n /** With `searchable`: the search field's placeholder and name. @default \"Search\" */\n search: string;\n /** With `searchable`: shown when no option matches the search. @default \"No results\" */\n noResults: string;\n /** The error icon's name (M3: \"Error\"). @default \"Error\" */\n error: string;\n}\n\nconst DEFAULT_LABELS: SelectLabels = {\n empty: 'No options',\n loading: 'Loading options',\n more: (count) => `+${count}`,\n search: 'Search',\n noResults: 'No results',\n error: 'Error',\n};\n\n/** A chosen option, as `renderValue` receives it. */\nexport interface SelectedOption<T> {\n key: Key;\n /** Its plain text (`textValue`, or the item's text). */\n textValue: string;\n /** Its object from `items`, if the options come from `items`. */\n value: T | null;\n}\n\nexport interface SelectClassNames extends FieldShellClassNames, OptionListClassNames {\n /** The text showing the chosen option(s) or the placeholder. */\n value?: string;\n /** With `searchable`: the search field. */\n search?: string;\n /**\n * The bottom sheet the options open in (`presentation=\"sheet\"`, or `\"auto\"` on compact\n * windows): the sheet's surface, as its `classNames.panel`.\n */\n sheet?: string;\n}\n\n/** React Aria props this API names the library's way (`disabled`, `open`…), or leaves out. */\ntype RenamedProps =\n | 'isDisabled'\n | 'isRequired'\n | 'isInvalid'\n | 'isOpen'\n | 'label'\n | 'description'\n | 'validationState'\n // React Stately's deprecated single-selection API; `value` / `defaultValue` / `onChange` replace it.\n | 'selectedKey'\n | 'defaultSelectedKey'\n | 'onSelectionChange'\n | 'shouldCloseOnSelect'\n | 'allowsEmptyCollection';\n\nexport type SelectProps<T extends object, M extends SelectionMode = 'single'> = Omit<\n StatelySelectProps<T, M>,\n RenamedProps\n> &\n Pick<AriaSelectOptions<T, M>, 'id' | 'name' | 'autoComplete' | 'form'> &\n Omit<WrapperRootDOMProps, keyof StatelySelectProps<T, M>> & {\n /** The trigger button's id, for a `<label for>` or an error summary's link. */\n id?: string;\n /** The container style, as `TextField`'s: a filled surface or an outline. @default \"filled\" */\n variant?: TextFieldVariant;\n /**\n * Web density, `0` to `-3`: each step takes 4px off the field (56px at 0, 44px at -3) and\n * off its menu's options (44px at 0, 32px at -3). Left out, it takes the density of the\n * nearest `ThemeProvider` or `ThemeScope`. M3 asks for density to be the reader's choice\n * rather than a default, so prefer setting it there, from a setting.\n * @default 0\n */\n density?: Density;\n /** The field's label. Give `aria-label` instead when there's no visible label. */\n label?: ReactNode;\n /** Accessible name when there's no visible label. */\n 'aria-label'?: string;\n /**\n * Shown in the field when nothing is chosen. With a `label`, it appears once the label\n * floats (on focus), as in `TextField`.\n */\n placeholder?: string;\n /**\n * Helper text below the field. Replaced visually by the error when invalid, and shown in\n * the error colour while the field is invalid.\n */\n supportingText?: ReactNode;\n /** Marks the field invalid (in addition to `validate` and `required`). */\n invalid?: boolean;\n /** A choice is required: adds the `*` to the label and validates on submit. */\n required?: boolean;\n /**\n * Read as `invalid` when that's left out, for a form library that sets the attribute. The\n * trigger is a button, which ARIA doesn't let carry `aria-invalid`, so the state reaches\n * assistive technology as the field's error text, its description.\n */\n 'aria-invalid'?: AriaAttributes['aria-invalid'];\n /**\n * Read as `required` when that's left out, for a form library that sets the attribute: the\n * label's `*`, in the trigger's name (a button can't carry `aria-required`).\n */\n 'aria-required'?: AriaAttributes['aria-required'];\n /** Disables the field: it can't be focused or opened. @default false */\n disabled?: boolean;\n /**\n * The choice can be seen but not changed: the menu doesn't open and the keyboard doesn't\n * change it. The root gets `data-readonly`; say so in the supporting text (M3).\n * @default false\n */\n readOnly?: boolean;\n /** Decorative icon at the start, in a 48px box. */\n leadingIcon?: ReactNode;\n /**\n * The icon shown before the arrow while the field is invalid (M3 strongly recommends one).\n * Defaults to Material Symbols `error`, named by `labels.error`; `false` turns it off.\n */\n errorIcon?: ReactNode | false;\n /** Whether the menu is open (controlled); with `defaultOpen` and `onOpenChange`. */\n open?: boolean;\n /**\n * Whether choosing an option closes the menu.\n * @default true for a single choice, false with `selectionMode=\"multiple\"`\n */\n closeOnSelect?: boolean;\n /**\n * With `selectionMode=\"multiple\"`, the most options that can be chosen; the rest show as\n * disabled until one is removed.\n */\n maxSelections?: number;\n /**\n * `menu` (default) opens an M3 menu under the field on every window, as Compose's exposed\n * dropdown menu does; `sheet` always opens a bottom sheet; `auto` opens a sheet on\n * compact windows and a menu on larger ones, for long lists.\n * @default \"menu\"\n */\n presentation?: SelectPresentation;\n /**\n * A search field at the top of the menu or sheet that filters the options as you type,\n * for long lists (pair it with `presentation=\"auto\"`). The search takes focus in the menu;\n * a sheet shows the list first, so the keyboard doesn't cover it.\n * @default false\n */\n searchable?: boolean;\n /**\n * With `searchable`: whether an option matches the search. Defaults to a match anywhere in\n * its text, ignoring case and accents (\"thai\" finds \"Thailand\"). The option's `key` lets\n * it match on more than its text, e.g. a country's ISO code.\n */\n filter?: (textValue: string, search: string, key: Key) => boolean;\n /** With `searchable`: the typed text (controlled); cleared when the list closes. */\n searchValue?: string;\n /** With `searchable`: the initial typed text, when uncontrolled. @default \"\" */\n defaultSearchValue?: string;\n /**\n * With `searchable`: called as the search changes. Given it, the app filters `items` itself\n * (e.g. on a server, with `loading` meanwhile) and the built-in filter is off.\n */\n onSearchChange?: (search: string) => void;\n /**\n * What the field shows for the chosen option(s), e.g. a dialling code or a flag, instead of\n * their text. Screen readers still hear the options' text.\n */\n renderValue?: (selected: SelectedOption<T>[]) => ReactNode;\n /**\n * Shows a loading indicator in the list, e.g. while options are fetched.\n * The indicator's code loads when it first shows, and ahead of that when the field is\n * hovered or focused while this prop is set at all, so pass `loading={false}` (rather than\n * leaving it out) on a field that loads options.\n */\n loading?: boolean;\n /** Words the Select shows or announces, for other languages. */\n labels?: Partial<SelectLabels>;\n /** Classes for the root element, merged with `classNames.root`; consumer classes win. */\n className?: string;\n /** Classes for each part, by slot. */\n classNames?: SelectClassNames;\n /** Inline styles for the root element. */\n style?: CSSProperties;\n /** Ref to the root `<div>` element. Use `triggerRef` for the field's button. */\n ref?: Ref<HTMLDivElement>;\n /** Ref to the field's button. */\n triggerRef?: Ref<HTMLButtonElement>;\n };\n\nconst COMPACT_WINDOW = '(max-width: 599.98px)';\n\n/**\n * A searchable list's sheet takes the full height it may (above an on-screen keyboard too), so\n * filtering changes the list inside, never the sheet: at its own height it shrank as the\n * results did, and sank behind the keyboard (2026-11-07).\n */\nconst SEARCH_SHEET_HEIGHT =\n 'h-[calc(100dvh_-_72px_-_var(--m3-sheet-keyboard-inset,0px))] min-[641px]:h-[calc(100dvh_-_56px_-_var(--m3-sheet-keyboard-inset,0px))]';\n\n/** Options scrolled to by the keyboard stop below the sticky search (52px at most). */\nconst SEARCH_SHEET_CONTENT = 'scroll-pt-[52px]';\n\n/** A read-only Select's keyboard finds no option, so arrows and typeahead change nothing. */\nconst READ_ONLY_DELEGATE: NonNullable<AriaSelectOptions<object>['keyboardDelegate']> = {\n getFirstKey: () => null,\n getLastKey: () => null,\n getKeyBelow: () => null,\n getKeyAbove: () => null,\n getKeyForSearch: () => null,\n};\n\nfunction subscribeToWindow(onChange: () => void) {\n const query = window.matchMedia(COMPACT_WINDOW);\n query.addEventListener('change', onChange);\n return () => query.removeEventListener('change', onChange);\n}\n\n/**\n * M3 exposed dropdown menu, read-only (Compose's `ExposedDropdownMenuBox` with a\n * `PrimaryNotEditable` anchor): a text-field-styled button that opens a menu of options.\n * Single or multiple selection, sections, descriptions and icons, typeahead, a hidden\n * native `<select>` for forms, and validation as in `TextField` (React Aria `useSelect`).\n *\n * @example\n * <Select label=\"Sort by\" defaultValue=\"newest\">\n * <SelectItem key=\"newest\">Newest first</SelectItem>\n * <SelectItem key=\"price\">Price, low to high</SelectItem>\n * </Select>\n */\nexport function Select<T extends object, M extends SelectionMode = 'single'>(\n props: SelectProps<T, M>,\n) {\n const {\n variant = 'filled',\n density,\n label,\n placeholder,\n supportingText,\n invalid: invalidProp,\n required: requiredProp,\n 'aria-invalid': ariaInvalid,\n 'aria-required': ariaRequired,\n disabled = false,\n readOnly = false,\n leadingIcon,\n errorIcon,\n open,\n maxSelections,\n presentation = 'menu',\n searchable = false,\n filter = matchesSearch,\n searchValue,\n defaultSearchValue,\n onSearchChange,\n renderValue,\n loading,\n labels: labelsProp,\n className,\n classNames,\n style,\n ref,\n triggerRef: triggerRefProp,\n name,\n autoComplete,\n form,\n errorMessage,\n closeOnSelect,\n ...otherProps\n } = props;\n // Items given as elements (not `items` with a render function) must come from a client.\n if (typeof props.children !== 'function') {\n assertCollectionChildren(props.children, 'Select', 'SelectItem and SelectSection elements');\n }\n const { root: rootData, rest } = splitRootProps(otherProps, { wrapper: true });\n const invalid = invalidProp ?? ariaFlag(ariaInvalid);\n const required = requiredProp ?? ariaFlag(ariaRequired);\n const labels = { ...DEFAULT_LABELS, ...labelsProp };\n const stately = rest as Omit<StatelySelectProps<T, M>, RenamedProps>;\n const cap = useSelectionCap({\n multiple: stately.selectionMode === 'multiple',\n maxSelections,\n value: stately.value,\n defaultValue: stately.defaultValue,\n onChange: stately.onChange,\n });\n const [search, setSearch] = useControlledState(\n searchValue,\n defaultSearchValue ?? '',\n onSearchChange,\n );\n const ariaProps = {\n ...stately,\n ...cap.valueProps,\n // The search starts afresh each time the list opens.\n onOpenChange: (isOpen: boolean) => {\n if (!isOpen && search !== '') setSearch('');\n stately.onOpenChange?.(isOpen);\n },\n label,\n description: supportingText,\n errorMessage,\n isInvalid: invalid,\n isRequired: required,\n isDisabled: disabled,\n ...(closeOnSelect !== undefined && { shouldCloseOnSelect: closeOnSelect }),\n ...FOCUSABLE_DISABLED_OPTIONS,\n ...(readOnly\n ? { isOpen: false, keyboardDelegate: READ_ONLY_DELEGATE }\n : open !== undefined && { isOpen: open }),\n placeholder,\n name,\n autoComplete,\n form,\n } as AriaSelectOptions<T, M>;\n\n const state = useSelectState<T, M>(ariaProps);\n const triggerRef = useObjectRef(triggerRefProp);\n // Typeahead on the closed field moves to the next option starting with the letter (M3),\n // and still skips disabled options.\n const typeaheadDelegate = useTypeaheadDelegate(state, triggerRef, {\n disabledBehavior: 'all',\n currentKey: state.selectionManager.selectionMode === 'single' ? state.selectedKey : null,\n });\n const containerRef = useRef<HTMLDivElement>(null);\n // Set by a press outside the menu and the field: the focus the menu hands back to the\n // field on closing (WAI-ARIA, right after Escape or a choice) is let go, so one press\n // outside leaves the field, as it does any other field.\n const releaseFocusRef = useRef(false);\n // Chrome's touch adjustment can split a tap between the trigger and the padding just above\n // or below it (the 8px either side of the value line): `pointerdown` reaches the trigger\n // while `pointerup` or the click lands beside it, so React Aria cancels the press or never\n // sees it, and about a third of the field ignored a finger (2026-10-17). A touch that\n // starts on the trigger but doesn't end there (its `pointerup` and its click both on it)\n // opens the menu on its click instead. The mouse never gets here: it opens on the press\n // start.\n const touchOnTriggerRef = useRef(false);\n const touchLeftTriggerRef = useRef(false);\n const onTrigger = (target: EventTarget) => Boolean(triggerRef.current?.contains(target as Node));\n const strayTouchProps: HTMLAttributes<HTMLDivElement> = {\n onPointerDownCapture: (event) => {\n touchOnTriggerRef.current = event.pointerType !== 'mouse' && onTrigger(event.target);\n touchLeftTriggerRef.current = false;\n },\n onPointerUpCapture: (event) => {\n touchLeftTriggerRef.current = !onTrigger(event.target);\n },\n onClickCapture: (event) => {\n if (!touchOnTriggerRef.current) return;\n touchOnTriggerRef.current = false;\n if (!touchLeftTriggerRef.current && onTrigger(event.target)) return;\n if (disabled || state.isOpen) return;\n releaseFocusRef.current = false;\n triggerRef.current?.focus({ preventScroll: true });\n if (!readOnly) state.open();\n },\n };\n const listBoxRef = useRef<HTMLUListElement>(null);\n const popoverRef = useRef<HTMLDivElement>(null);\n const dialogId = useId();\n const {\n labelProps,\n triggerProps,\n valueProps,\n menuProps,\n descriptionProps,\n errorMessageProps,\n isInvalid,\n validationErrors,\n validationDetails,\n } = useSelect<T, M>(\n readOnly ? ariaProps : { ...ariaProps, keyboardDelegate: typeaheadDelegate },\n state,\n triggerRef,\n );\n const isCompact = useSyncExternalStore(\n subscribeToWindow,\n () => window.matchMedia(COMPACT_WINDOW).matches,\n () => false,\n );\n const asSheet = presentation === 'sheet' || (presentation === 'auto' && isCompact);\n // The sheet's code loads on first open, or ahead of it when the field is hovered or focused.\n usePreloadBottomSheet(containerRef, asSheet);\n // A field that takes `loading` (it loads options) preloads the indicator on hover or focus.\n usePreloadLoadingIndicator(containerRef, loading !== undefined);\n // `useSelect`'s trigger props are React Aria button props; `useButton` makes them DOM props.\n const { buttonProps } = useButton(\n // A searchable field opens a dialog holding the search and the list (as PhoneField's).\n searchable\n ? {\n ...triggerProps,\n 'aria-haspopup': 'dialog',\n 'aria-controls': state.isOpen && !asSheet ? dialogId : undefined,\n }\n : triggerProps,\n triggerRef,\n );\n const { focusProps, isFocusVisible, isFocused } = useFocusRing();\n // The trigger sets its own widget attributes; an app's description ones join them.\n const fieldButtonProps = mergeProps(pickDescriptionAria(rest), buttonProps, focusProps);\n const { hoverProps, isHovered } = useHover({ isDisabled: disabled });\n\n // In the list's order, not the order they were picked, so the field reads like the list.\n const chosenOptions: SelectedOption<T>[] = [...state.collection.getKeys()]\n .filter((key) => state.selectionManager.isSelected(key))\n .map((key) => {\n const item = state.collection.getItem(key);\n return { key, textValue: item?.textValue ?? '', value: item?.value ?? null };\n });\n const chosen = chosenOptions.map((option) => option.textValue);\n const valueText =\n chosen.length > 2\n ? `${chosen.slice(0, 2).join(', ')} ${labels.more(chosen.length - 2)}`\n : chosen.join(', ');\n const hasValue = chosen.length > 0;\n const hasLabel = label !== undefined && label !== null;\n const focused = isFocused || state.isOpen;\n const resolvedError =\n typeof errorMessage === 'function'\n ? errorMessage({ isInvalid, validationErrors, validationDetails })\n : (errorMessage ?? validationErrors.join(' '));\n\n const searchLabelling = hasLabel\n ? { 'aria-labelledby': labelProps.id }\n : { 'aria-label': (rest as { 'aria-label'?: string })['aria-label'] };\n const list = searchable ? (\n <SearchList\n listProps={{\n ...(stately.items !== undefined && { items: stately.items }),\n children: stately.children,\n ...(stately.disabledKeys !== undefined && { disabledKeys: stately.disabledKeys }),\n selectionMode: stately.selectionMode ?? 'single',\n // A single choice stays chosen when pressed again, as in a non-searchable Select.\n disallowEmptySelection: stately.selectionMode !== 'multiple',\n selectedKeys: state.selectionManager.selectedKeys,\n onSelectionChange: (keys) => {\n state.selectionManager.setSelectedKeys(keys === 'all' ? [] : keys);\n if (stately.selectionMode !== 'multiple') state.close();\n },\n }}\n search={search}\n onSearchChange={setSearch}\n filter={onSearchChange ? null : filter}\n autoFocusSearch={!asSheet}\n labelling={searchLabelling}\n dialogProps={asSheet ? undefined : { id: dialogId, role: 'dialog', ...searchLabelling }}\n labels={{ search: labels.search, noResults: labels.noResults, loading: labels.loading }}\n loading={loading ?? false}\n isBlocked={cap.isBlocked}\n embedded={asSheet}\n classNames={classNames}\n />\n ) : (\n <OptionList\n embedded={asSheet}\n listBoxProps={menuProps}\n state={state}\n listBoxRef={listBoxRef}\n emptyLabel={labels.empty}\n loading={loading}\n loadingLabel={labels.loading}\n isBlocked={cap.isBlocked}\n classNames={classNames}\n />\n );\n\n return (\n <>\n <FieldShell\n variant={variant}\n density={density}\n label={label}\n labelProps={labelProps}\n required={required}\n state={fieldState({ disabled, invalid: isInvalid, focused, hovered: isHovered })}\n floated={hasValue || focused}\n open={state.isOpen}\n leadingIcon={leadingIcon}\n trailing={<DropdownArrow open={state.isOpen} />}\n errorIcon={errorIcon}\n errorIconLabel={labels.error}\n readOnly={readOnly}\n supportingText={supportingText}\n descriptionProps={descriptionProps}\n errorText={resolvedError}\n errorMessageProps={errorMessageProps}\n showError={isInvalid && Boolean(resolvedError)}\n containerRef={containerRef}\n // No handler is shared: the hover ones and the touch fallback's capture ones.\n containerProps={{ ...hoverProps, ...strayTouchProps }}\n onContainerPointerDown={(event) => {\n // A press anywhere on the field (not only its text) opens it, like Compose.\n if (disabled || triggerRef.current?.contains(event.target as Node)) return;\n event.preventDefault();\n releaseFocusRef.current = false;\n triggerRef.current?.focus();\n if (!readOnly) state.toggle();\n }}\n rootProps={{ ...rootData, 'data-focus-visible': isFocusVisible || undefined }}\n rootRef={ref}\n className={className}\n classNames={classNames}\n style={style}\n >\n {/* The native select is for forms: submitting under `name`, autofill and native\n validation. Without them it's left out, so a long list's text (e.g. country names\n from Intl, which can differ between server and browser) isn't rendered twice. */}\n {name || autoComplete || form || stately.validationBehavior === 'native' ? (\n <HiddenSelect\n state={state}\n triggerRef={triggerRef}\n label={label}\n name={name}\n isDisabled={disabled}\n {...(autoComplete && { autoComplete })}\n {...(form && { form })}\n />\n ) : null}\n <button\n {...fieldButtonProps}\n aria-describedby={renderedDescription(fieldButtonProps['aria-describedby'], [\n !supportingText && descriptionProps.id,\n !(isInvalid && resolvedError) && errorMessageProps.id,\n ])}\n onFocus={(event) => {\n fieldButtonProps.onFocus?.(event);\n if (!releaseFocusRef.current) return;\n releaseFocusRef.current = false;\n triggerRef.current?.blur();\n }}\n // Using the field again means it's wanted, whatever came before.\n onPointerDown={(event) => {\n releaseFocusRef.current = false;\n fieldButtonProps.onPointerDown?.(event);\n }}\n onKeyDown={(event) => {\n releaseFocusRef.current = false;\n fieldButtonProps.onKeyDown?.(event);\n }}\n ref={triggerRef}\n type=\"button\"\n className={cn(\n 'flex min-w-0 flex-1 cursor-pointer items-center self-stretch bg-transparent p-0 text-start outline-none',\n 'disabled:cursor-default',\n )}\n >\n <span\n {...valueProps}\n className={cn(\n 'min-w-0 flex-1 truncate text-body-large',\n hasValue\n ? 'text-[color:var(--_md-comp-text-field-input-text-color)]'\n : 'text-on-surface-variant',\n // With a label, the placeholder shows once the label floats (as TextField).\n !hasValue && hasLabel && 'opacity-0 group-data-floated/field:opacity-100',\n 'group-data-[field-state=disabled]/field:text-on-surface/38',\n classNames?.value,\n )}\n >\n {hasValue && renderValue ? (\n <>\n <span aria-hidden=\"true\">{renderValue(chosenOptions)}</span>\n <span className=\"sr-only\">{valueText}</span>\n </>\n ) : hasValue ? (\n valueText\n ) : (\n (placeholder ?? ' ')\n )}\n </span>\n </button>\n </FieldShell>\n {asSheet ? (\n <LazyBottomSheet\n // Opens at its own height (up to the window, the list scrolling inside), never half\n // the window, which left the last options off-screen on a short phone.\n skipPartiallyExpanded\n open={state.isOpen}\n onOpenChange={(open) => (open ? state.open() : state.close())}\n classNames={\n searchable\n ? {\n panel: cn(SEARCH_SHEET_HEIGHT, classNames?.sheet),\n content: SEARCH_SHEET_CONTENT,\n }\n : classNames?.sheet\n ? { panel: classNames.sheet }\n : undefined\n }\n {...(typeof label === 'string'\n ? { 'aria-label': label }\n : { 'aria-label': (rest as { 'aria-label'?: string })['aria-label'] ?? '' })}\n >\n <div\n {...densityAttribute(density)}\n className=\"flex min-h-0 flex-1 flex-col px-[8px] pb-[16px]\"\n >\n {list}\n </div>\n </LazyBottomSheet>\n ) : (\n <OptionPopover\n state={state}\n triggerRef={containerRef}\n popoverRef={popoverRef}\n density={density}\n onPressOutside={() => {\n releaseFocusRef.current = true;\n }}\n // Room to type in a searchable menu, however narrow the field.\n minWidth={searchable ? 280 : 0}\n >\n {list}\n </OptionPopover>\n )}\n </>\n );\n}\n\nexport type { Key as SelectKey } from 'react-stately';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Select.js","names":[],"sources":["../../../src/components/select/Select.tsx"],"sourcesContent":["'use client';\n\nimport {\n useId,\n useRef,\n useSyncExternalStore,\n type AriaAttributes,\n type CSSProperties,\n type HTMLAttributes,\n type ReactNode,\n type Ref,\n} from 'react';\nimport {\n HiddenSelect,\n mergeProps,\n useButton,\n useFocusRing,\n useHover,\n useObjectRef,\n useSelect,\n type AriaSelectOptions,\n} from 'react-aria';\nimport {\n useSelectState,\n type SelectionMode,\n type SelectProps as StatelySelectProps,\n} from 'react-stately/useSelectState';\nimport { useControlledState } from 'react-stately/useControlledState';\nimport type { Key } from 'react-stately';\nimport { useLabels } from '../labels/use-labels';\nimport { matchesSearch } from '../../utils/search';\nimport { assertCollectionChildren } from '../../utils/assert-collection-children';\nimport { densityAttribute, type Density } from '../../tokens/density';\nimport { cn } from '../../utils/cn';\nimport { renderedDescription } from '../../utils/rendered-description';\nimport {\n ariaFlag,\n pickDescriptionAria,\n splitRootProps,\n type WrapperRootDOMProps,\n} from '../../utils/root-props';\nimport { useTypeaheadDelegate } from '../../utils/typeahead';\nimport { usePreloadLoadingIndicator } from '../loading-indicator/lazy-loading-indicator';\nimport { LazyBottomSheet, usePreloadBottomSheet } from '../sheet/lazy-bottom-sheet';\nimport type { TextFieldVariant } from '../text-field/text-field-styles';\nimport { DropdownArrow, FieldShell, fieldState, type FieldShellClassNames } from './field-shell';\nimport {\n FOCUSABLE_DISABLED_OPTIONS,\n OptionList,\n OptionPopover,\n type OptionListClassNames,\n} from './option-list';\nimport { SearchList } from './search-list';\nimport { useSelectionCap } from './selection-cap';\n\n/** How the options open: a menu under the field (M3), a bottom sheet, or a sheet on phones. */\nexport type SelectPresentation = 'menu' | 'sheet' | 'auto';\n\n/** Words the Select shows or announces; override them for other languages. */\nexport interface SelectLabels {\n /** Shown in an empty list. @default \"No options\" */\n empty: string;\n /** The loading indicator's name. @default \"Loading options\" */\n loading: string;\n /** For more chosen options than fit: `+{count}`. @default (count) => `+${count}` */\n more: (count: number) => string;\n /** With `searchable`: the search field's placeholder and name. @default \"Search\" */\n search: string;\n /** With `searchable`: shown when no option matches the search. @default \"No results\" */\n noResults: string;\n /** The error icon's name (M3: \"Error\"). @default \"Error\" */\n error: string;\n}\n\nconst DEFAULT_LABELS: SelectLabels = {\n empty: 'No options',\n loading: 'Loading options',\n more: (count) => `+${count}`,\n search: 'Search',\n noResults: 'No results',\n error: 'Error',\n};\n\n/** A chosen option, as `renderValue` receives it. */\nexport interface SelectedOption<T> {\n key: Key;\n /** Its plain text (`textValue`, or the item's text). */\n textValue: string;\n /** Its object from `items`, if the options come from `items`. */\n value: T | null;\n}\n\nexport interface SelectClassNames extends FieldShellClassNames, OptionListClassNames {\n /** The text showing the chosen option(s) or the placeholder. */\n value?: string;\n /** With `searchable`: the search field. */\n search?: string;\n /**\n * The bottom sheet the options open in (`presentation=\"sheet\"`, or `\"auto\"` on compact\n * windows): the sheet's surface, as its `classNames.panel`.\n */\n sheet?: string;\n}\n\n/** React Aria props this API names the library's way (`disabled`, `open`…), or leaves out. */\ntype RenamedProps =\n | 'isDisabled'\n | 'isRequired'\n | 'isInvalid'\n | 'isOpen'\n | 'label'\n | 'description'\n | 'validationState'\n // React Stately's deprecated single-selection API; `value` / `defaultValue` / `onChange` replace it.\n | 'selectedKey'\n | 'defaultSelectedKey'\n | 'onSelectionChange'\n | 'shouldCloseOnSelect'\n | 'allowsEmptyCollection';\n\nexport type SelectProps<T extends object, M extends SelectionMode = 'single'> = Omit<\n StatelySelectProps<T, M>,\n RenamedProps\n> &\n Pick<AriaSelectOptions<T, M>, 'id' | 'name' | 'autoComplete' | 'form'> &\n Omit<WrapperRootDOMProps, keyof StatelySelectProps<T, M>> & {\n /** The trigger button's id, for a `<label for>` or an error summary's link. */\n id?: string;\n /** The container style, as `TextField`'s: a filled surface or an outline. @default \"filled\" */\n variant?: TextFieldVariant;\n /**\n * Web density, `0` to `-3`: each step takes 4px off the field (56px at 0, 44px at -3) and\n * off its menu's options (44px at 0, 32px at -3). Left out, it takes the density of the\n * nearest `ThemeProvider` or `ThemeScope`. M3 asks for density to be the reader's choice\n * rather than a default, so prefer setting it there, from a setting.\n * @default 0\n */\n density?: Density;\n /** The field's label. Give `aria-label` instead when there's no visible label. */\n label?: ReactNode;\n /** Accessible name when there's no visible label. */\n 'aria-label'?: string;\n /**\n * Shown in the field when nothing is chosen. With a `label`, it appears once the label\n * floats (on focus), as in `TextField`.\n */\n placeholder?: string;\n /**\n * Helper text below the field. Replaced visually by the error when invalid, and shown in\n * the error colour while the field is invalid.\n */\n supportingText?: ReactNode;\n /** Marks the field invalid (in addition to `validate` and `required`). */\n invalid?: boolean;\n /** A choice is required: adds the `*` to the label and validates on submit. */\n required?: boolean;\n /**\n * Read as `invalid` when that's left out, for a form library that sets the attribute. The\n * trigger is a button, which ARIA doesn't let carry `aria-invalid`, so the state reaches\n * assistive technology as the field's error text, its description.\n */\n 'aria-invalid'?: AriaAttributes['aria-invalid'];\n /**\n * Read as `required` when that's left out, for a form library that sets the attribute: the\n * label's `*`, in the trigger's name (a button can't carry `aria-required`).\n */\n 'aria-required'?: AriaAttributes['aria-required'];\n /** Disables the field: it can't be focused or opened. @default false */\n disabled?: boolean;\n /**\n * The choice can be seen but not changed: the menu doesn't open and the keyboard doesn't\n * change it. The root gets `data-readonly`; say so in the supporting text (M3).\n * @default false\n */\n readOnly?: boolean;\n /** Decorative icon at the start, in a 48px box. */\n leadingIcon?: ReactNode;\n /**\n * The icon shown before the arrow while the field is invalid (M3 strongly recommends one).\n * Defaults to Material Symbols `error`, named by `labels.error`; `false` turns it off.\n */\n errorIcon?: ReactNode | false;\n /** Whether the menu is open (controlled); with `defaultOpen` and `onOpenChange`. */\n open?: boolean;\n /**\n * Whether choosing an option closes the menu.\n * @default true for a single choice, false with `selectionMode=\"multiple\"`\n */\n closeOnSelect?: boolean;\n /**\n * With `selectionMode=\"multiple\"`, the most options that can be chosen; the rest show as\n * disabled until one is removed.\n */\n maxSelections?: number;\n /**\n * `menu` (default) opens an M3 menu under the field on every window, as Compose's exposed\n * dropdown menu does; `sheet` always opens a bottom sheet; `auto` opens a sheet on\n * compact windows and a menu on larger ones, for long lists.\n * @default \"menu\"\n */\n presentation?: SelectPresentation;\n /**\n * A search field at the top of the menu or sheet that filters the options as you type,\n * for long lists (pair it with `presentation=\"auto\"`). The search takes focus in the menu;\n * a sheet shows the list first, so the keyboard doesn't cover it.\n * @default false\n */\n searchable?: boolean;\n /**\n * With `searchable`: whether an option matches the search. Defaults to a match anywhere in\n * its text, ignoring case and accents (\"thai\" finds \"Thailand\"). The option's `key` lets\n * it match on more than its text, e.g. a country's ISO code.\n */\n filter?: (textValue: string, search: string, key: Key) => boolean;\n /** With `searchable`: the typed text (controlled); cleared when the list closes. */\n searchValue?: string;\n /** With `searchable`: the initial typed text, when uncontrolled. @default \"\" */\n defaultSearchValue?: string;\n /**\n * With `searchable`: called as the search changes. Given it, the app filters `items` itself\n * (e.g. on a server, with `loading` meanwhile) and the built-in filter is off.\n */\n onSearchChange?: (search: string) => void;\n /**\n * What the field shows for the chosen option(s), e.g. a dialling code or a flag, instead of\n * their text. Screen readers still hear the options' text.\n */\n renderValue?: (selected: SelectedOption<T>[]) => ReactNode;\n /**\n * Shows a loading indicator in the list, e.g. while options are fetched.\n * The indicator's code loads when it first shows, and ahead of that when the field is\n * hovered or focused while this prop is set at all, so pass `loading={false}` (rather than\n * leaving it out) on a field that loads options.\n */\n loading?: boolean;\n /**\n * Words the Select shows or announces, for other languages. A {@link LabelsProvider} sets them\n * for the whole app.\n */\n labels?: Partial<SelectLabels>;\n /** Classes for the root element, merged with `classNames.root`; consumer classes win. */\n className?: string;\n /** Classes for each part, by slot. */\n classNames?: SelectClassNames;\n /** Inline styles for the root element. */\n style?: CSSProperties;\n /** Ref to the root `<div>` element. Use `triggerRef` for the field's button. */\n ref?: Ref<HTMLDivElement>;\n /** Ref to the field's button. */\n triggerRef?: Ref<HTMLButtonElement>;\n };\n\nconst COMPACT_WINDOW = '(max-width: 599.98px)';\n\n/**\n * A searchable list's sheet takes the full height it may (above an on-screen keyboard too), so\n * filtering changes the list inside, never the sheet: at its own height it shrank as the\n * results did, and sank behind the keyboard (2026-11-07).\n */\nconst SEARCH_SHEET_HEIGHT =\n 'h-[calc(100dvh_-_72px_-_var(--m3-sheet-keyboard-inset,0px))] min-[641px]:h-[calc(100dvh_-_56px_-_var(--m3-sheet-keyboard-inset,0px))]';\n\n/** Options scrolled to by the keyboard stop below the sticky search (52px at most). */\nconst SEARCH_SHEET_CONTENT = 'scroll-pt-[52px]';\n\n/** A read-only Select's keyboard finds no option, so arrows and typeahead change nothing. */\nconst READ_ONLY_DELEGATE: NonNullable<AriaSelectOptions<object>['keyboardDelegate']> = {\n getFirstKey: () => null,\n getLastKey: () => null,\n getKeyBelow: () => null,\n getKeyAbove: () => null,\n getKeyForSearch: () => null,\n};\n\nfunction subscribeToWindow(onChange: () => void) {\n const query = window.matchMedia(COMPACT_WINDOW);\n query.addEventListener('change', onChange);\n return () => query.removeEventListener('change', onChange);\n}\n\n/**\n * M3 exposed dropdown menu, read-only (Compose's `ExposedDropdownMenuBox` with a\n * `PrimaryNotEditable` anchor): a text-field-styled button that opens a menu of options.\n * Single or multiple selection, sections, descriptions and icons, typeahead, a hidden\n * native `<select>` for forms, and validation as in `TextField` (React Aria `useSelect`).\n *\n * @example\n * <Select label=\"Sort by\" defaultValue=\"newest\">\n * <SelectItem key=\"newest\">Newest first</SelectItem>\n * <SelectItem key=\"price\">Price, low to high</SelectItem>\n * </Select>\n */\nexport function Select<T extends object, M extends SelectionMode = 'single'>(\n props: SelectProps<T, M>,\n) {\n const {\n variant = 'filled',\n density,\n label,\n placeholder,\n supportingText,\n invalid: invalidProp,\n required: requiredProp,\n 'aria-invalid': ariaInvalid,\n 'aria-required': ariaRequired,\n disabled = false,\n readOnly = false,\n leadingIcon,\n errorIcon,\n open,\n maxSelections,\n presentation = 'menu',\n searchable = false,\n filter = matchesSearch,\n searchValue,\n defaultSearchValue,\n onSearchChange,\n renderValue,\n loading,\n labels: labelsProp,\n className,\n classNames,\n style,\n ref,\n triggerRef: triggerRefProp,\n name,\n autoComplete,\n form,\n errorMessage,\n closeOnSelect,\n ...otherProps\n } = props;\n // Items given as elements (not `items` with a render function) must come from a client.\n if (typeof props.children !== 'function') {\n assertCollectionChildren(props.children, 'Select', 'SelectItem and SelectSection elements');\n }\n const { root: rootData, rest } = splitRootProps(otherProps, { wrapper: true });\n const invalid = invalidProp ?? ariaFlag(ariaInvalid);\n const required = requiredProp ?? ariaFlag(ariaRequired);\n const labels = useLabels('Select', DEFAULT_LABELS, labelsProp);\n const stately = rest as Omit<StatelySelectProps<T, M>, RenamedProps>;\n const cap = useSelectionCap({\n multiple: stately.selectionMode === 'multiple',\n maxSelections,\n value: stately.value,\n defaultValue: stately.defaultValue,\n onChange: stately.onChange,\n });\n const [search, setSearch] = useControlledState(\n searchValue,\n defaultSearchValue ?? '',\n onSearchChange,\n );\n const ariaProps = {\n ...stately,\n ...cap.valueProps,\n // The search starts afresh each time the list opens.\n onOpenChange: (isOpen: boolean) => {\n if (!isOpen && search !== '') setSearch('');\n stately.onOpenChange?.(isOpen);\n },\n label,\n description: supportingText,\n errorMessage,\n isInvalid: invalid,\n isRequired: required,\n isDisabled: disabled,\n ...(closeOnSelect !== undefined && { shouldCloseOnSelect: closeOnSelect }),\n ...FOCUSABLE_DISABLED_OPTIONS,\n ...(readOnly\n ? { isOpen: false, keyboardDelegate: READ_ONLY_DELEGATE }\n : open !== undefined && { isOpen: open }),\n placeholder,\n name,\n autoComplete,\n form,\n } as AriaSelectOptions<T, M>;\n\n const state = useSelectState<T, M>(ariaProps);\n const triggerRef = useObjectRef(triggerRefProp);\n // Typeahead on the closed field moves to the next option starting with the letter (M3),\n // and still skips disabled options.\n const typeaheadDelegate = useTypeaheadDelegate(state, triggerRef, {\n disabledBehavior: 'all',\n currentKey: state.selectionManager.selectionMode === 'single' ? state.selectedKey : null,\n });\n const containerRef = useRef<HTMLDivElement>(null);\n // Set by a press outside the menu and the field: the focus the menu hands back to the\n // field on closing (WAI-ARIA, right after Escape or a choice) is let go, so one press\n // outside leaves the field, as it does any other field.\n const releaseFocusRef = useRef(false);\n // Chrome's touch adjustment can split a tap between the trigger and the padding just above\n // or below it (the 8px either side of the value line): `pointerdown` reaches the trigger\n // while `pointerup` or the click lands beside it, so React Aria cancels the press or never\n // sees it, and about a third of the field ignored a finger (2026-10-17). A touch that\n // starts on the trigger but doesn't end there (its `pointerup` and its click both on it)\n // opens the menu on its click instead. The mouse never gets here: it opens on the press\n // start.\n const touchOnTriggerRef = useRef(false);\n const touchLeftTriggerRef = useRef(false);\n const onTrigger = (target: EventTarget) => Boolean(triggerRef.current?.contains(target as Node));\n const strayTouchProps: HTMLAttributes<HTMLDivElement> = {\n onPointerDownCapture: (event) => {\n touchOnTriggerRef.current = event.pointerType !== 'mouse' && onTrigger(event.target);\n touchLeftTriggerRef.current = false;\n },\n onPointerUpCapture: (event) => {\n touchLeftTriggerRef.current = !onTrigger(event.target);\n },\n onClickCapture: (event) => {\n if (!touchOnTriggerRef.current) return;\n touchOnTriggerRef.current = false;\n if (!touchLeftTriggerRef.current && onTrigger(event.target)) return;\n if (disabled || state.isOpen) return;\n releaseFocusRef.current = false;\n triggerRef.current?.focus({ preventScroll: true });\n if (!readOnly) state.open();\n },\n };\n const listBoxRef = useRef<HTMLUListElement>(null);\n const popoverRef = useRef<HTMLDivElement>(null);\n const dialogId = useId();\n const {\n labelProps,\n triggerProps,\n valueProps,\n menuProps,\n descriptionProps,\n errorMessageProps,\n isInvalid,\n validationErrors,\n validationDetails,\n } = useSelect<T, M>(\n readOnly ? ariaProps : { ...ariaProps, keyboardDelegate: typeaheadDelegate },\n state,\n triggerRef,\n );\n const isCompact = useSyncExternalStore(\n subscribeToWindow,\n () => window.matchMedia(COMPACT_WINDOW).matches,\n () => false,\n );\n const asSheet = presentation === 'sheet' || (presentation === 'auto' && isCompact);\n // The sheet's code loads on first open, or ahead of it when the field is hovered or focused.\n usePreloadBottomSheet(containerRef, asSheet);\n // A field that takes `loading` (it loads options) preloads the indicator on hover or focus.\n usePreloadLoadingIndicator(containerRef, loading !== undefined);\n // `useSelect`'s trigger props are React Aria button props; `useButton` makes them DOM props.\n const { buttonProps } = useButton(\n // A searchable field opens a dialog holding the search and the list (as PhoneField's).\n searchable\n ? {\n ...triggerProps,\n 'aria-haspopup': 'dialog',\n 'aria-controls': state.isOpen && !asSheet ? dialogId : undefined,\n }\n : triggerProps,\n triggerRef,\n );\n const { focusProps, isFocusVisible, isFocused } = useFocusRing();\n // The trigger sets its own widget attributes; an app's description ones join them.\n const fieldButtonProps = mergeProps(pickDescriptionAria(rest), buttonProps, focusProps);\n const { hoverProps, isHovered } = useHover({ isDisabled: disabled });\n\n // In the list's order, not the order they were picked, so the field reads like the list.\n const chosenOptions: SelectedOption<T>[] = [...state.collection.getKeys()]\n .filter((key) => state.selectionManager.isSelected(key))\n .map((key) => {\n const item = state.collection.getItem(key);\n return { key, textValue: item?.textValue ?? '', value: item?.value ?? null };\n });\n const chosen = chosenOptions.map((option) => option.textValue);\n const valueText =\n chosen.length > 2\n ? `${chosen.slice(0, 2).join(', ')} ${labels.more(chosen.length - 2)}`\n : chosen.join(', ');\n const hasValue = chosen.length > 0;\n const hasLabel = label !== undefined && label !== null;\n const focused = isFocused || state.isOpen;\n const resolvedError =\n typeof errorMessage === 'function'\n ? errorMessage({ isInvalid, validationErrors, validationDetails })\n : (errorMessage ?? validationErrors.join(' '));\n\n const searchLabelling = hasLabel\n ? { 'aria-labelledby': labelProps.id }\n : { 'aria-label': (rest as { 'aria-label'?: string })['aria-label'] };\n const list = searchable ? (\n <SearchList\n listProps={{\n ...(stately.items !== undefined && { items: stately.items }),\n children: stately.children,\n ...(stately.disabledKeys !== undefined && { disabledKeys: stately.disabledKeys }),\n selectionMode: stately.selectionMode ?? 'single',\n // A single choice stays chosen when pressed again, as in a non-searchable Select.\n disallowEmptySelection: stately.selectionMode !== 'multiple',\n selectedKeys: state.selectionManager.selectedKeys,\n onSelectionChange: (keys) => {\n state.selectionManager.setSelectedKeys(keys === 'all' ? [] : keys);\n if (stately.selectionMode !== 'multiple') state.close();\n },\n }}\n search={search}\n onSearchChange={setSearch}\n filter={onSearchChange ? null : filter}\n autoFocusSearch={!asSheet}\n labelling={searchLabelling}\n dialogProps={asSheet ? undefined : { id: dialogId, role: 'dialog', ...searchLabelling }}\n labels={{ search: labels.search, noResults: labels.noResults, loading: labels.loading }}\n loading={loading ?? false}\n isBlocked={cap.isBlocked}\n embedded={asSheet}\n classNames={classNames}\n />\n ) : (\n <OptionList\n embedded={asSheet}\n listBoxProps={menuProps}\n state={state}\n listBoxRef={listBoxRef}\n emptyLabel={labels.empty}\n loading={loading}\n loadingLabel={labels.loading}\n isBlocked={cap.isBlocked}\n classNames={classNames}\n />\n );\n\n return (\n <>\n <FieldShell\n variant={variant}\n density={density}\n label={label}\n labelProps={labelProps}\n required={required}\n state={fieldState({ disabled, invalid: isInvalid, focused, hovered: isHovered })}\n floated={hasValue || focused}\n open={state.isOpen}\n leadingIcon={leadingIcon}\n trailing={<DropdownArrow open={state.isOpen} />}\n errorIcon={errorIcon}\n errorIconLabel={labels.error}\n readOnly={readOnly}\n supportingText={supportingText}\n descriptionProps={descriptionProps}\n errorText={resolvedError}\n errorMessageProps={errorMessageProps}\n showError={isInvalid && Boolean(resolvedError)}\n containerRef={containerRef}\n // No handler is shared: the hover ones and the touch fallback's capture ones.\n containerProps={{ ...hoverProps, ...strayTouchProps }}\n onContainerPointerDown={(event) => {\n // A press anywhere on the field (not only its text) opens it, like Compose.\n if (disabled || triggerRef.current?.contains(event.target as Node)) return;\n event.preventDefault();\n releaseFocusRef.current = false;\n triggerRef.current?.focus();\n if (!readOnly) state.toggle();\n }}\n rootProps={{ ...rootData, 'data-focus-visible': isFocusVisible || undefined }}\n rootRef={ref}\n className={className}\n classNames={classNames}\n style={style}\n >\n {/* The native select is for forms: submitting under `name`, autofill and native\n validation. Without them it's left out, so a long list's text (e.g. country names\n from Intl, which can differ between server and browser) isn't rendered twice. */}\n {name || autoComplete || form || stately.validationBehavior === 'native' ? (\n <HiddenSelect\n state={state}\n triggerRef={triggerRef}\n label={label}\n name={name}\n isDisabled={disabled}\n {...(autoComplete && { autoComplete })}\n {...(form && { form })}\n />\n ) : null}\n <button\n {...fieldButtonProps}\n aria-describedby={renderedDescription(fieldButtonProps['aria-describedby'], [\n !supportingText && descriptionProps.id,\n !(isInvalid && resolvedError) && errorMessageProps.id,\n ])}\n onFocus={(event) => {\n fieldButtonProps.onFocus?.(event);\n if (!releaseFocusRef.current) return;\n releaseFocusRef.current = false;\n triggerRef.current?.blur();\n }}\n // Using the field again means it's wanted, whatever came before.\n onPointerDown={(event) => {\n releaseFocusRef.current = false;\n fieldButtonProps.onPointerDown?.(event);\n }}\n onKeyDown={(event) => {\n releaseFocusRef.current = false;\n fieldButtonProps.onKeyDown?.(event);\n }}\n ref={triggerRef}\n type=\"button\"\n className={cn(\n 'flex min-w-0 flex-1 cursor-pointer items-center self-stretch bg-transparent p-0 text-start outline-none',\n 'disabled:cursor-default',\n )}\n >\n <span\n {...valueProps}\n className={cn(\n 'min-w-0 flex-1 truncate text-body-large',\n hasValue\n ? 'text-[color:var(--_md-comp-text-field-input-text-color)]'\n : 'text-on-surface-variant',\n // With a label, the placeholder shows once the label floats (as TextField).\n !hasValue && hasLabel && 'opacity-0 group-data-floated/field:opacity-100',\n 'group-data-[field-state=disabled]/field:text-on-surface/38',\n classNames?.value,\n )}\n >\n {hasValue && renderValue ? (\n <>\n <span aria-hidden=\"true\">{renderValue(chosenOptions)}</span>\n <span className=\"sr-only\">{valueText}</span>\n </>\n ) : hasValue ? (\n valueText\n ) : (\n (placeholder ?? ' ')\n )}\n </span>\n </button>\n </FieldShell>\n {asSheet ? (\n <LazyBottomSheet\n // Opens at its own height (up to the window, the list scrolling inside), never half\n // the window, which left the last options off-screen on a short phone.\n skipPartiallyExpanded\n open={state.isOpen}\n onOpenChange={(open) => (open ? state.open() : state.close())}\n classNames={\n searchable\n ? {\n panel: cn(SEARCH_SHEET_HEIGHT, classNames?.sheet),\n content: SEARCH_SHEET_CONTENT,\n }\n : classNames?.sheet\n ? { panel: classNames.sheet }\n : undefined\n }\n {...(typeof label === 'string'\n ? { 'aria-label': label }\n : { 'aria-label': (rest as { 'aria-label'?: string })['aria-label'] ?? '' })}\n >\n <div\n {...densityAttribute(density)}\n className=\"flex min-h-0 flex-1 flex-col px-[8px] pb-[16px]\"\n >\n {list}\n </div>\n </LazyBottomSheet>\n ) : (\n <OptionPopover\n state={state}\n triggerRef={containerRef}\n popoverRef={popoverRef}\n density={density}\n onPressOutside={() => {\n releaseFocusRef.current = true;\n }}\n // Room to type in a searchable menu, however narrow the field.\n minWidth={searchable ? 280 : 0}\n >\n {list}\n </OptionPopover>\n )}\n </>\n );\n}\n\nexport type { Key as SelectKey } from 'react-stately';\n"],"mappings":"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"}
|
|
@@ -115,7 +115,10 @@ interface BottomSheetOwnProps {
|
|
|
115
115
|
closeIcon?: ReactElement;
|
|
116
116
|
/** Replaces the collapse button's icon. */
|
|
117
117
|
collapseIcon?: ReactElement;
|
|
118
|
-
/**
|
|
118
|
+
/**
|
|
119
|
+
* Words the sheet announces, with English defaults. A {@link LabelsProvider} sets them for the
|
|
120
|
+
* whole app.
|
|
121
|
+
*/
|
|
119
122
|
labels?: Partial<BottomSheetLabels>;
|
|
120
123
|
children?: ReactNode | ((props: {
|
|
121
124
|
close: () => void;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useThemeScope } from "../../theme/context.js";
|
|
3
3
|
import { cn } from "../../utils/cn.js";
|
|
4
|
+
import { useLabels } from "../labels/use-labels.js";
|
|
4
5
|
import { TriggerContext } from "../../primitives/TriggerContext.js";
|
|
5
6
|
import { mergeDataTokens } from "../../utils/merge-data-tokens.js";
|
|
6
7
|
import { usePresence } from "../../primitives/use-presence.js";
|
|
@@ -101,10 +102,7 @@ function ModalBottomSheet({ state, isExiting, triggerOverlayProps, closedBy = "a
|
|
|
101
102
|
}
|
|
102
103
|
/** The sheet itself: the moving wrapper, the panel, its handle and its detents. */
|
|
103
104
|
function BottomSheetPanel({ variant, state, isExiting, exitProps, panelProps: overlayPanelProps, dragHandle = true, swipeable = true, skipPartiallyExpanded = false, peekHeight = 56, detent: detentProp, defaultDetent = "collapsed", onDetentChange, closedBy = "any", closeButton = true, closeIcon, collapseIcon, labels: labelsProp, children, className, classNames, style, ref, ...rest }) {
|
|
104
|
-
const labels =
|
|
105
|
-
...DEFAULT_LABELS,
|
|
106
|
-
...labelsProp
|
|
107
|
-
};
|
|
105
|
+
const labels = useLabels("BottomSheet", DEFAULT_LABELS, labelsProp);
|
|
108
106
|
const standard = variant === "standard";
|
|
109
107
|
const panelRef = useObjectRef(ref);
|
|
110
108
|
const motionRef = useRef(null);
|
|
@@ -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":"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"}
|
|
@@ -93,7 +93,10 @@ interface SideSheetOwnProps {
|
|
|
93
93
|
closeIcon?: ReactElement;
|
|
94
94
|
/** Replaces the back button's icon. */
|
|
95
95
|
backIcon?: ReactElement;
|
|
96
|
-
/**
|
|
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
|
-
/**
|
|
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);
|