@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
|
@@ -81,7 +81,10 @@ interface PickerDialogOwnProps extends Omit<RootDOMProps, 'title'> {
|
|
|
81
81
|
* @default "any"
|
|
82
82
|
*/
|
|
83
83
|
closedBy?: ClosedBy;
|
|
84
|
-
/**
|
|
84
|
+
/**
|
|
85
|
+
* Words the full-screen form shows and announces, with English defaults. A {@link LabelsProvider}
|
|
86
|
+
* sets them for the whole app.
|
|
87
|
+
*/
|
|
85
88
|
labels?: Partial<PickerDialogLabels>;
|
|
86
89
|
/** The picker. */
|
|
87
90
|
children: ReactNode | ((props: {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { NO_DEFAULT_LABELS, useLabels } from "../labels/use-labels.js";
|
|
2
3
|
import { TriggerContext } from "../../primitives/TriggerContext.js";
|
|
3
4
|
import { mergeDataTokens } from "../../utils/merge-data-tokens.js";
|
|
4
5
|
import { usePresence } from "../../primitives/use-presence.js";
|
|
@@ -48,7 +49,8 @@ function PickerDialog({ open, defaultOpen, onOpenChange, ...props }) {
|
|
|
48
49
|
})
|
|
49
50
|
});
|
|
50
51
|
}
|
|
51
|
-
function PickerDialogModal({ state, isExiting, exitProps, variant = "date", title, onConfirm, confirmButton, dismissButton, modeToggleButton, fullScreen = "never", closeIcon, closedBy = "any", transition = "reveal", labels
|
|
52
|
+
function PickerDialogModal({ state, isExiting, exitProps, variant = "date", title, onConfirm, confirmButton, dismissButton, modeToggleButton, fullScreen = "never", closeIcon, closedBy = "any", transition = "reveal", labels: labelsProp, children, className, style, ref, classNames, ...labelling }) {
|
|
53
|
+
const labels = useLabels("PickerDialog", NO_DEFAULT_LABELS, labelsProp);
|
|
52
54
|
const panelRef = useObjectRef(ref);
|
|
53
55
|
const { modalProps, underlayProps } = useModalOverlay(closedByOverlayOptions(closedBy, isExiting), state, panelRef);
|
|
54
56
|
const { root, rest: naming } = splitRootProps(labelling);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PickerDialog.js","names":["Overlay"],"sources":["../../../src/components/date-picker/PickerDialog.tsx"],"sourcesContent":["'use client';\n\nimport {\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type ReactElement,\n type ReactNode,\n type CSSProperties,\n type Ref,\n} from 'react';\nimport { mergeProps, useDialog, useModalOverlay, useObjectRef } 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 { Button } from '../button/Button';\nimport { DIALOG_ROOT_STYLE, dialogStyles } from '../dialog/dialog-styles';\nimport type { DialogTransition } from '../dialog/Dialog';\nimport { IconButton } from '../icon-button/IconButton';\nimport { PickerDialogContext } from './picker-dialog-context';\nimport { pickerDialogStyles } from './picker-dialog-styles';\nimport { CloseIcon } from './picker-icons';\nimport { COMPACT_WINDOW, useMediaQuery } from './use-media-query';\nimport { closedByOverlayOptions, type ClosedBy } from '../../utils/closed-by';\nimport { mergeDataTokens } from '../../utils/merge-data-tokens';\nimport { splitRootProps, type RootDOMProps } from '../../utils/root-props';\n\ntype Naming =\n | {\n /** Accessible name of the dialog when there's no visible title. */\n 'aria-label': string;\n }\n | {\n /** Id of a visible element that names the dialog. */\n 'aria-labelledby': string;\n };\n\nexport interface PickerDialogClassNames {\n /** The dimmed layer behind the dialog. */\n scrim?: string;\n /** The dialog's surface: its colour, corners, padding and shadow. */\n panel?: string;\n /** The full-screen top row with the close and Save buttons. */\n header?: string;\n /** The full-screen close (x) icon button. */\n close?: string;\n /** The time picker dialog's `title` (\"Select time\"). */\n title?: string;\n /** The bottom row of actions: mode toggle, dismiss and confirm buttons. */\n actions?: string;\n}\n\n/** Words of a {@link PickerDialog}, for localisation. */\nexport interface PickerDialogLabels {\n /** The full-screen close (x) button. @default \"Close\" */\n close: string;\n /** The full-screen confirm button. @default \"Save\" */\n save: string;\n}\n\n/** The panel (the `dialog` element, where `ref` and `id` land) takes the DOM attributes. */\ninterface PickerDialogOwnProps extends Omit<RootDOMProps, 'title'> {\n /** Whether the dialog is open (controlled). */\n open?: boolean;\n /** Whether the dialog starts open when uncontrolled. @default false */\n defaultOpen?: boolean;\n /** Called when the dialog opens or closes. */\n onOpenChange?: (open: boolean) => void;\n /**\n * `date` holds a date picker edge to edge with the actions below (Compose's\n * `DatePickerDialog`); `time` pads the content 24px under a \"Select time\" style title\n * (Compose's `TimePickerDialog`). @default \"date\"\n */\n variant?: 'date' | 'time';\n /** Title for the time variant, e.g. \"Select time\". */\n title?: ReactNode;\n /**\n * Saves the picked value. With it, Enter on a day (or in a typed date) confirms and\n * closes the dialog (the M3 date picker keyboard table), and the full-screen form gets\n * its Save button. Called after the picker's latest value has rendered.\n */\n onConfirm?: () => void;\n /** The confirm button (last, at the end), e.g. `<Button variant=\"text\">OK</Button>`. */\n confirmButton: ReactNode;\n /** The dismiss button, before the confirm button. */\n dismissButton?: ReactNode;\n /** A control at the start of the actions row, e.g. the time picker's mode toggle. */\n modeToggleButton?: ReactNode;\n /**\n * Fill the window, with a close (x) icon button and a Save button at the top instead of\n * the actions (M3: a full-screen modal date picker is recommended on compact windows).\n * `compact` does so below 600px wide. @default \"never\"\n */\n fullScreen?: 'never' | 'always' | 'compact';\n /** Icon of the full-screen close button. */\n closeIcon?: ReactElement;\n /**\n * How the dialog enters and leaves, as `Dialog`'s `transition`: `reveal`, the M3 dialog\n * motion (it slides down while its container grows, then the picker and the actions fade\n * in), or `scale`, the original pop (a fade with a scale from 90%). Reduced motion keeps only\n * the fades. Ignored while the dialog is full screen.\n * @default \"reveal\"\n */\n transition?: DialogTransition;\n /**\n * How the user can close the dialog, as HTML's `<dialog closedby>`: `any` (Escape or a\n * press outside), `closerequest` (Escape only) or `none` (only its actions or the app).\n * @default \"any\"\n */\n closedBy?: ClosedBy;\n /** Words the full-screen form shows and announces, with English defaults. */\n labels?: Partial<PickerDialogLabels>;\n /** The picker. */\n children: ReactNode | ((props: { close: () => void }) => ReactNode);\n /**\n * Goes on the outermost element, a full-window layer whose inline placement it can't\n * move, so a layout class (`static`, `overflow-hidden`, a transform) never hides or clips\n * the dialog. Style the surface with `classNames.panel`.\n */\n className?: string;\n /** Inline styles for the outermost element, with `className`. */\n style?: CSSProperties;\n /** The dialog's id (on the panel, the `dialog` element). */\n id?: string;\n /** Ref to the panel, the `dialog` element. */\n ref?: Ref<HTMLDivElement>;\n /** Classes for each part, by slot. */\n classNames?: PickerDialogClassNames;\n}\n\nexport type PickerDialogProps = PickerDialogOwnProps & Naming;\n\n/**\n * The modal dialog for a date or time picker (Compose's `DatePickerDialog` /\n * `TimePickerDialog`): the picker on a `surface-container-high` panel with 28px corners at\n * level 3, and its actions; or, full screen, edge to edge with close and Save at the top.\n *\n * @example\n * <PickerDialog aria-label=\"Select date\" open={open} onOpenChange={setOpen}\n * onConfirm={() => setDate(draft)}\n * dismissButton={<Button variant=\"text\" onPress={() => setOpen(false)}>Cancel</Button>}\n * confirmButton={<Button variant=\"text\" onPress={confirm}>OK</Button>}>\n * <DatePicker value={draft} onChange={setDraft} />\n * </PickerDialog>\n */\nexport function PickerDialog({ open, defaultOpen, onOpenChange, ...props }: PickerDialogProps) {\n const state = useOverlayTriggerState({ isOpen: open, defaultOpen, onOpenChange });\n const { isPresent, isExiting, exitProps } = usePresence(state.isOpen);\n if (!isPresent) return null;\n return (\n <Overlay exiting={isExiting}>\n <PickerDialogModal {...props} state={state} isExiting={isExiting} exitProps={exitProps} />\n </Overlay>\n );\n}\n\nfunction PickerDialogModal({\n state,\n isExiting,\n exitProps,\n variant = 'date',\n title,\n onConfirm,\n confirmButton,\n dismissButton,\n modeToggleButton,\n fullScreen = 'never',\n closeIcon,\n closedBy = 'any',\n transition = 'reveal',\n labels = {},\n children,\n className,\n style,\n ref,\n classNames,\n ...labelling\n}: Omit<PickerDialogProps, 'open' | 'defaultOpen' | 'onOpenChange'> & {\n state: OverlayTriggerState;\n isExiting: boolean;\n exitProps: ReturnType<typeof usePresence>['exitProps'];\n}) {\n const panelRef = useObjectRef(ref);\n const { modalProps, underlayProps } = useModalOverlay(\n closedByOverlayOptions(closedBy, isExiting),\n state,\n panelRef,\n );\n const { root, rest: naming } = splitRootProps(labelling);\n const { dialogProps, titleProps } = useDialog(naming, panelRef);\n const compact = useMediaQuery(COMPACT_WINDOW);\n const full = fullScreen === 'always' || (fullScreen === 'compact' && compact);\n const shell = dialogStyles({ fullScreen: full ? 'always' : 'never', transition });\n const styles = pickerDialogStyles({ variant, fullScreen: full });\n const exiting = isExiting || undefined;\n\n // Enter on a day selects it first; confirm once the consumer has the new value, so the\n // latest `onConfirm` (reading that value) runs.\n const latest = useRef({ onConfirm, close: state.close });\n useLayoutEffect(() => {\n latest.current = { onConfirm, close: state.close };\n });\n const [requestConfirm] = useState(() => () => {\n setTimeout(() => {\n latest.current.onConfirm?.();\n latest.current.close();\n });\n });\n const canConfirm = onConfirm !== undefined;\n const context = useMemo(\n () => (canConfirm ? { requestConfirm } : null),\n [canConfirm, requestConfirm],\n );\n\n return (\n <>\n <div\n aria-hidden=\"true\"\n data-exiting={exiting}\n className={shell.scrim({ class: classNames?.scrim })}\n />\n <div style={{ ...style, ...DIALOG_ROOT_STYLE }} className={shell.root({ class: className })}>\n <div {...underlayProps} className={shell.positioner()}>\n <div\n {...exitProps}\n data-exiting={exiting}\n className={shell.motion({ class: styles.motion() })}\n >\n <div\n {...mergeProps(root, modalProps, dialogProps)}\n ref={panelRef}\n data-exiting={exiting}\n // The picker's component tokens (date-picker-tokens.ts, time-picker-tokens.ts)\n // paint the panel; `data-variant` is also a public styling hook.\n data-tokens={mergeDataTokens(\n variant === 'time' ? 'md-comp-time-picker' : 'md-comp-date-picker-modal',\n root,\n )}\n data-variant={variant}\n data-full-screen={full || undefined}\n className={styles.panel({\n class: [transition === 'reveal' && shell.parts(), classNames?.panel],\n })}\n >\n <TriggerContext value={null}>\n {full && (\n <div className={styles.header({ class: classNames?.header })}>\n <IconButton\n variant=\"standard\"\n density={0}\n icon={closeIcon ?? <CloseIcon />}\n aria-label={labels.close ?? 'Close'}\n onPress={state.close}\n className={styles.close({ class: classNames?.close })}\n />\n <div className={styles.buttons()}>\n {modeToggleButton}\n {onConfirm ? (\n <Button variant=\"text\" onPress={requestConfirm}>\n {labels.save ?? 'Save'}\n </Button>\n ) : (\n confirmButton\n )}\n </div>\n </div>\n )}\n {variant === 'time' && title != null && (\n <h2 {...titleProps} className={styles.title({ class: classNames?.title })}>\n {title}\n </h2>\n )}\n <PickerDialogContext value={context}>\n <div className={styles.content()}>\n {typeof children === 'function' ? children({ close: state.close }) : children}\n </div>\n </PickerDialogContext>\n {!full && (\n <div\n data-slot=\"actions\"\n className={styles.actions({ class: classNames?.actions })}\n >\n {modeToggleButton}\n <div className={styles.buttons()}>\n {dismissButton}\n {confirmButton}\n </div>\n </div>\n )}\n </TriggerContext>\n </div>\n </div>\n </div>\n </div>\n </>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PickerDialog.js","names":["Overlay"],"sources":["../../../src/components/date-picker/PickerDialog.tsx"],"sourcesContent":["'use client';\n\nimport {\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type ReactElement,\n type ReactNode,\n type CSSProperties,\n type Ref,\n} from 'react';\nimport { mergeProps, useDialog, useModalOverlay, useObjectRef } from 'react-aria';\nimport { useOverlayTriggerState, type OverlayTriggerState } from 'react-stately';\nimport { NO_DEFAULT_LABELS, useLabels } from '../labels/use-labels';\nimport { Overlay } from '../../primitives/Overlay';\nimport { TriggerContext } from '../../primitives/TriggerContext';\nimport { usePresence } from '../../primitives/use-presence';\nimport { Button } from '../button/Button';\nimport { DIALOG_ROOT_STYLE, dialogStyles } from '../dialog/dialog-styles';\nimport type { DialogTransition } from '../dialog/Dialog';\nimport { IconButton } from '../icon-button/IconButton';\nimport { PickerDialogContext } from './picker-dialog-context';\nimport { pickerDialogStyles } from './picker-dialog-styles';\nimport { CloseIcon } from './picker-icons';\nimport { COMPACT_WINDOW, useMediaQuery } from './use-media-query';\nimport { closedByOverlayOptions, type ClosedBy } from '../../utils/closed-by';\nimport { mergeDataTokens } from '../../utils/merge-data-tokens';\nimport { splitRootProps, type RootDOMProps } from '../../utils/root-props';\n\ntype Naming =\n | {\n /** Accessible name of the dialog when there's no visible title. */\n 'aria-label': string;\n }\n | {\n /** Id of a visible element that names the dialog. */\n 'aria-labelledby': string;\n };\n\nexport interface PickerDialogClassNames {\n /** The dimmed layer behind the dialog. */\n scrim?: string;\n /** The dialog's surface: its colour, corners, padding and shadow. */\n panel?: string;\n /** The full-screen top row with the close and Save buttons. */\n header?: string;\n /** The full-screen close (x) icon button. */\n close?: string;\n /** The time picker dialog's `title` (\"Select time\"). */\n title?: string;\n /** The bottom row of actions: mode toggle, dismiss and confirm buttons. */\n actions?: string;\n}\n\n/** Words of a {@link PickerDialog}, for localisation. */\nexport interface PickerDialogLabels {\n /** The full-screen close (x) button. @default \"Close\" */\n close: string;\n /** The full-screen confirm button. @default \"Save\" */\n save: string;\n}\n\n/** The panel (the `dialog` element, where `ref` and `id` land) takes the DOM attributes. */\ninterface PickerDialogOwnProps extends Omit<RootDOMProps, 'title'> {\n /** Whether the dialog is open (controlled). */\n open?: boolean;\n /** Whether the dialog starts open when uncontrolled. @default false */\n defaultOpen?: boolean;\n /** Called when the dialog opens or closes. */\n onOpenChange?: (open: boolean) => void;\n /**\n * `date` holds a date picker edge to edge with the actions below (Compose's\n * `DatePickerDialog`); `time` pads the content 24px under a \"Select time\" style title\n * (Compose's `TimePickerDialog`). @default \"date\"\n */\n variant?: 'date' | 'time';\n /** Title for the time variant, e.g. \"Select time\". */\n title?: ReactNode;\n /**\n * Saves the picked value. With it, Enter on a day (or in a typed date) confirms and\n * closes the dialog (the M3 date picker keyboard table), and the full-screen form gets\n * its Save button. Called after the picker's latest value has rendered.\n */\n onConfirm?: () => void;\n /** The confirm button (last, at the end), e.g. `<Button variant=\"text\">OK</Button>`. */\n confirmButton: ReactNode;\n /** The dismiss button, before the confirm button. */\n dismissButton?: ReactNode;\n /** A control at the start of the actions row, e.g. the time picker's mode toggle. */\n modeToggleButton?: ReactNode;\n /**\n * Fill the window, with a close (x) icon button and a Save button at the top instead of\n * the actions (M3: a full-screen modal date picker is recommended on compact windows).\n * `compact` does so below 600px wide. @default \"never\"\n */\n fullScreen?: 'never' | 'always' | 'compact';\n /** Icon of the full-screen close button. */\n closeIcon?: ReactElement;\n /**\n * How the dialog enters and leaves, as `Dialog`'s `transition`: `reveal`, the M3 dialog\n * motion (it slides down while its container grows, then the picker and the actions fade\n * in), or `scale`, the original pop (a fade with a scale from 90%). Reduced motion keeps only\n * the fades. Ignored while the dialog is full screen.\n * @default \"reveal\"\n */\n transition?: DialogTransition;\n /**\n * How the user can close the dialog, as HTML's `<dialog closedby>`: `any` (Escape or a\n * press outside), `closerequest` (Escape only) or `none` (only its actions or the app).\n * @default \"any\"\n */\n closedBy?: ClosedBy;\n /**\n * Words the full-screen form shows and announces, with English defaults. A {@link LabelsProvider}\n * sets them for the whole app.\n */\n labels?: Partial<PickerDialogLabels>;\n /** The picker. */\n children: ReactNode | ((props: { close: () => void }) => ReactNode);\n /**\n * Goes on the outermost element, a full-window layer whose inline placement it can't\n * move, so a layout class (`static`, `overflow-hidden`, a transform) never hides or clips\n * the dialog. Style the surface with `classNames.panel`.\n */\n className?: string;\n /** Inline styles for the outermost element, with `className`. */\n style?: CSSProperties;\n /** The dialog's id (on the panel, the `dialog` element). */\n id?: string;\n /** Ref to the panel, the `dialog` element. */\n ref?: Ref<HTMLDivElement>;\n /** Classes for each part, by slot. */\n classNames?: PickerDialogClassNames;\n}\n\nexport type PickerDialogProps = PickerDialogOwnProps & Naming;\n\n/**\n * The modal dialog for a date or time picker (Compose's `DatePickerDialog` /\n * `TimePickerDialog`): the picker on a `surface-container-high` panel with 28px corners at\n * level 3, and its actions; or, full screen, edge to edge with close and Save at the top.\n *\n * @example\n * <PickerDialog aria-label=\"Select date\" open={open} onOpenChange={setOpen}\n * onConfirm={() => setDate(draft)}\n * dismissButton={<Button variant=\"text\" onPress={() => setOpen(false)}>Cancel</Button>}\n * confirmButton={<Button variant=\"text\" onPress={confirm}>OK</Button>}>\n * <DatePicker value={draft} onChange={setDraft} />\n * </PickerDialog>\n */\nexport function PickerDialog({ open, defaultOpen, onOpenChange, ...props }: PickerDialogProps) {\n const state = useOverlayTriggerState({ isOpen: open, defaultOpen, onOpenChange });\n const { isPresent, isExiting, exitProps } = usePresence(state.isOpen);\n if (!isPresent) return null;\n return (\n <Overlay exiting={isExiting}>\n <PickerDialogModal {...props} state={state} isExiting={isExiting} exitProps={exitProps} />\n </Overlay>\n );\n}\n\nfunction PickerDialogModal({\n state,\n isExiting,\n exitProps,\n variant = 'date',\n title,\n onConfirm,\n confirmButton,\n dismissButton,\n modeToggleButton,\n fullScreen = 'never',\n closeIcon,\n closedBy = 'any',\n transition = 'reveal',\n labels: labelsProp,\n children,\n className,\n style,\n ref,\n classNames,\n ...labelling\n}: Omit<PickerDialogProps, 'open' | 'defaultOpen' | 'onOpenChange'> & {\n state: OverlayTriggerState;\n isExiting: boolean;\n exitProps: ReturnType<typeof usePresence>['exitProps'];\n}) {\n const labels = useLabels('PickerDialog', NO_DEFAULT_LABELS, labelsProp);\n const panelRef = useObjectRef(ref);\n const { modalProps, underlayProps } = useModalOverlay(\n closedByOverlayOptions(closedBy, isExiting),\n state,\n panelRef,\n );\n const { root, rest: naming } = splitRootProps(labelling);\n const { dialogProps, titleProps } = useDialog(naming, panelRef);\n const compact = useMediaQuery(COMPACT_WINDOW);\n const full = fullScreen === 'always' || (fullScreen === 'compact' && compact);\n const shell = dialogStyles({ fullScreen: full ? 'always' : 'never', transition });\n const styles = pickerDialogStyles({ variant, fullScreen: full });\n const exiting = isExiting || undefined;\n\n // Enter on a day selects it first; confirm once the consumer has the new value, so the\n // latest `onConfirm` (reading that value) runs.\n const latest = useRef({ onConfirm, close: state.close });\n useLayoutEffect(() => {\n latest.current = { onConfirm, close: state.close };\n });\n const [requestConfirm] = useState(() => () => {\n setTimeout(() => {\n latest.current.onConfirm?.();\n latest.current.close();\n });\n });\n const canConfirm = onConfirm !== undefined;\n const context = useMemo(\n () => (canConfirm ? { requestConfirm } : null),\n [canConfirm, requestConfirm],\n );\n\n return (\n <>\n <div\n aria-hidden=\"true\"\n data-exiting={exiting}\n className={shell.scrim({ class: classNames?.scrim })}\n />\n <div style={{ ...style, ...DIALOG_ROOT_STYLE }} className={shell.root({ class: className })}>\n <div {...underlayProps} className={shell.positioner()}>\n <div\n {...exitProps}\n data-exiting={exiting}\n className={shell.motion({ class: styles.motion() })}\n >\n <div\n {...mergeProps(root, modalProps, dialogProps)}\n ref={panelRef}\n data-exiting={exiting}\n // The picker's component tokens (date-picker-tokens.ts, time-picker-tokens.ts)\n // paint the panel; `data-variant` is also a public styling hook.\n data-tokens={mergeDataTokens(\n variant === 'time' ? 'md-comp-time-picker' : 'md-comp-date-picker-modal',\n root,\n )}\n data-variant={variant}\n data-full-screen={full || undefined}\n className={styles.panel({\n class: [transition === 'reveal' && shell.parts(), classNames?.panel],\n })}\n >\n <TriggerContext value={null}>\n {full && (\n <div className={styles.header({ class: classNames?.header })}>\n <IconButton\n variant=\"standard\"\n density={0}\n icon={closeIcon ?? <CloseIcon />}\n aria-label={labels.close ?? 'Close'}\n onPress={state.close}\n className={styles.close({ class: classNames?.close })}\n />\n <div className={styles.buttons()}>\n {modeToggleButton}\n {onConfirm ? (\n <Button variant=\"text\" onPress={requestConfirm}>\n {labels.save ?? 'Save'}\n </Button>\n ) : (\n confirmButton\n )}\n </div>\n </div>\n )}\n {variant === 'time' && title != null && (\n <h2 {...titleProps} className={styles.title({ class: classNames?.title })}>\n {title}\n </h2>\n )}\n <PickerDialogContext value={context}>\n <div className={styles.content()}>\n {typeof children === 'function' ? children({ close: state.close }) : children}\n </div>\n </PickerDialogContext>\n {!full && (\n <div\n data-slot=\"actions\"\n className={styles.actions({ class: classNames?.actions })}\n >\n {modeToggleButton}\n <div className={styles.buttons()}>\n {dismissButton}\n {confirmButton}\n </div>\n </div>\n )}\n </TriggerContext>\n </div>\n </div>\n </div>\n </div>\n </>\n );\n}\n"],"mappings":"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"}
|
|
@@ -156,15 +156,15 @@ export declare const datePickerStyles: import("tailwind-variants").TVReturnType<
|
|
|
156
156
|
export declare const dockedDatePickerStyles: import("tailwind-variants").TVReturnType<{
|
|
157
157
|
[key: string]: {
|
|
158
158
|
[key: string]: import("tailwind-variants").ClassValue | {
|
|
159
|
-
panel?: import("tailwind-variants").ClassValue;
|
|
160
|
-
actions?: import("tailwind-variants").ClassValue;
|
|
161
159
|
header?: import("tailwind-variants").ClassValue;
|
|
162
160
|
view?: import("tailwind-variants").ClassValue;
|
|
163
|
-
item?: import("tailwind-variants").ClassValue;
|
|
164
|
-
list?: import("tailwind-variants").ClassValue;
|
|
165
|
-
check?: import("tailwind-variants").ClassValue;
|
|
166
161
|
menuIcon?: import("tailwind-variants").ClassValue;
|
|
167
162
|
group?: import("tailwind-variants").ClassValue;
|
|
163
|
+
item?: import("tailwind-variants").ClassValue;
|
|
164
|
+
check?: import("tailwind-variants").ClassValue;
|
|
165
|
+
actions?: import("tailwind-variants").ClassValue;
|
|
166
|
+
panel?: import("tailwind-variants").ClassValue;
|
|
167
|
+
list?: import("tailwind-variants").ClassValue;
|
|
168
168
|
calendar?: import("tailwind-variants").ClassValue;
|
|
169
169
|
menuButton?: import("tailwind-variants").ClassValue;
|
|
170
170
|
};
|
|
@@ -172,15 +172,15 @@ export declare const dockedDatePickerStyles: import("tailwind-variants").TVRetur
|
|
|
172
172
|
} | {
|
|
173
173
|
[x: string]: {
|
|
174
174
|
[x: string]: import("tailwind-variants").ClassValue | {
|
|
175
|
-
panel?: import("tailwind-variants").ClassValue;
|
|
176
|
-
actions?: import("tailwind-variants").ClassValue;
|
|
177
175
|
header?: import("tailwind-variants").ClassValue;
|
|
178
176
|
view?: import("tailwind-variants").ClassValue;
|
|
179
|
-
item?: import("tailwind-variants").ClassValue;
|
|
180
|
-
list?: import("tailwind-variants").ClassValue;
|
|
181
|
-
check?: import("tailwind-variants").ClassValue;
|
|
182
177
|
menuIcon?: import("tailwind-variants").ClassValue;
|
|
183
178
|
group?: import("tailwind-variants").ClassValue;
|
|
179
|
+
item?: import("tailwind-variants").ClassValue;
|
|
180
|
+
check?: import("tailwind-variants").ClassValue;
|
|
181
|
+
actions?: import("tailwind-variants").ClassValue;
|
|
182
|
+
panel?: import("tailwind-variants").ClassValue;
|
|
183
|
+
list?: import("tailwind-variants").ClassValue;
|
|
184
184
|
calendar?: import("tailwind-variants").ClassValue;
|
|
185
185
|
menuButton?: import("tailwind-variants").ClassValue;
|
|
186
186
|
};
|
|
@@ -200,15 +200,15 @@ export declare const dockedDatePickerStyles: import("tailwind-variants").TVRetur
|
|
|
200
200
|
}, undefined, {
|
|
201
201
|
[key: string]: {
|
|
202
202
|
[key: string]: import("tailwind-variants").ClassValue | {
|
|
203
|
-
panel?: import("tailwind-variants").ClassValue;
|
|
204
|
-
actions?: import("tailwind-variants").ClassValue;
|
|
205
203
|
header?: import("tailwind-variants").ClassValue;
|
|
206
204
|
view?: import("tailwind-variants").ClassValue;
|
|
207
|
-
item?: import("tailwind-variants").ClassValue;
|
|
208
|
-
list?: import("tailwind-variants").ClassValue;
|
|
209
|
-
check?: import("tailwind-variants").ClassValue;
|
|
210
205
|
menuIcon?: import("tailwind-variants").ClassValue;
|
|
211
206
|
group?: import("tailwind-variants").ClassValue;
|
|
207
|
+
item?: import("tailwind-variants").ClassValue;
|
|
208
|
+
check?: import("tailwind-variants").ClassValue;
|
|
209
|
+
actions?: import("tailwind-variants").ClassValue;
|
|
210
|
+
panel?: import("tailwind-variants").ClassValue;
|
|
211
|
+
list?: import("tailwind-variants").ClassValue;
|
|
212
212
|
calendar?: import("tailwind-variants").ClassValue;
|
|
213
213
|
menuButton?: import("tailwind-variants").ClassValue;
|
|
214
214
|
};
|
|
@@ -201,7 +201,10 @@ export interface DialogHeaderLabels {
|
|
|
201
201
|
export interface DialogHeaderProps extends Omit<ComponentPropsWithRef<'div'>, 'title'> {
|
|
202
202
|
/** The headline; it names the dialog. */
|
|
203
203
|
children: ReactNode;
|
|
204
|
-
/**
|
|
204
|
+
/**
|
|
205
|
+
* Words the header announces, with English defaults. A {@link LabelsProvider} sets them for the
|
|
206
|
+
* whole app.
|
|
207
|
+
*/
|
|
205
208
|
labels?: Partial<DialogHeaderLabels>;
|
|
206
209
|
/**
|
|
207
210
|
* The confirming action, usually a text `Button` ("Save"). Shown in full screen only;
|
|
@@ -235,7 +238,7 @@ export interface DialogHeaderProps extends Omit<ComponentPropsWithRef<'div'>, 't
|
|
|
235
238
|
* <DialogActions>…</DialogActions>
|
|
236
239
|
* </Dialog>
|
|
237
240
|
*/
|
|
238
|
-
export declare function DialogHeader({ children, labels, action, divider, closeDisabled, closeIcon, className, classNames, ...props }: DialogHeaderProps): import("react").JSX.Element;
|
|
241
|
+
export declare function DialogHeader({ children, labels: labelsProp, action, divider, closeDisabled, closeIcon, className, classNames, ...props }: DialogHeaderProps): import("react").JSX.Element;
|
|
239
242
|
export interface DialogContentProps extends ComponentPropsWithRef<'div'> {
|
|
240
243
|
/**
|
|
241
244
|
* The basic dialog's optional dividers: 1px `outline-variant` lines across the panel
|
|
@@ -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, useTriggerProps } from "../../primitives/TriggerContext.js";
|
|
4
5
|
import { mergeDataTokens } from "../../utils/merge-data-tokens.js";
|
|
5
6
|
import { usePresence } from "../../primitives/use-presence.js";
|
|
@@ -325,7 +326,8 @@ const DEFAULT_HEADER_LABELS = { close: "Close" };
|
|
|
325
326
|
* <DialogActions>…</DialogActions>
|
|
326
327
|
* </Dialog>
|
|
327
328
|
*/
|
|
328
|
-
function DialogHeader({ children, labels, action, divider = false, closeDisabled = false, closeIcon, className, classNames, ...props }) {
|
|
329
|
+
function DialogHeader({ children, labels: labelsProp, action, divider = false, closeDisabled = false, closeIcon, className, classNames, ...props }) {
|
|
330
|
+
const labels = useLabels("DialogHeader", DEFAULT_HEADER_LABELS, labelsProp);
|
|
329
331
|
const { titleProps, hasIcon, fullScreen, close, setHeaderAction } = useContext(DialogPartsContext);
|
|
330
332
|
const hasAction = action !== void 0;
|
|
331
333
|
useLayoutEffect(() => {
|
|
@@ -345,7 +347,7 @@ function DialogHeader({ children, labels, action, divider = false, closeDisabled
|
|
|
345
347
|
variant: "standard",
|
|
346
348
|
density: 0,
|
|
347
349
|
icon: closeIcon ?? /* @__PURE__ */ jsx(CloseIcon, {}),
|
|
348
|
-
"aria-label": labels
|
|
350
|
+
"aria-label": labels.close,
|
|
349
351
|
disabled: closeDisabled,
|
|
350
352
|
onPress: close,
|
|
351
353
|
className: styles.headerButton({ class: cn(styles.close(), classNames?.close) })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dialog.js","names":["Overlay"],"sources":["../../../src/components/dialog/Dialog.tsx"],"sourcesContent":["'use client';\n\nimport {\n Children,\n Fragment,\n createContext,\n isValidElement,\n useContext,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n type ComponentPropsWithRef,\n type RefObject,\n type CSSProperties,\n type DOMAttributes,\n type ReactElement,\n type ReactNode,\n type Ref,\n} from 'react';\nimport {\n mergeProps,\n useDialog,\n useFocusRing,\n useModalOverlay,\n useObjectRef,\n useOverlayTrigger,\n type AriaDialogProps,\n} from 'react-aria';\nimport { useOverlayTriggerState, type OverlayTriggerState } from 'react-stately';\nimport { Overlay } from '../../primitives/Overlay';\nimport {\n TriggerContext,\n useTriggerProps,\n type TriggerContextValue,\n} 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 { useWarnOnce } from '../../utils/warn-once';\nimport { DensityZeroButtons } from '../button/DensityZeroButtons';\nimport { IconButton } from '../icon-button/IconButton';\nimport { DIALOG_ROOT_STYLE, dialogStyles } from './dialog-styles';\nimport { mergeDataTokens } from '../../utils/merge-data-tokens';\n\n/** Material Symbols \"close\" (Apache-2.0). */\nfunction CloseIcon() {\n return (\n <svg viewBox=\"0 -960 960 960\" fill=\"currentColor\">\n <path d=\"m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z\" />\n </svg>\n );\n}\n\n/**\n * When a dialog fills the window (the M3 full-screen dialog): `never`, `always`, or\n * `compact` for windows narrower than 600px only, the M3 recommendation.\n */\nexport type DialogFullScreen = 'never' | 'always' | 'compact';\n\n/**\n * How a basic dialog enters and leaves: `reveal`, the M3 dialog motion, or `scale`, the\n * original pop (a fade with a scale from 90%).\n */\nexport type DialogTransition = 'reveal' | 'scale';\n\n/** Where a basic dialog sits in the window, keeping the spec's 56px margin on wider windows. */\nexport type DialogPlacement = 'center' | 'top' | 'bottom' | 'start' | 'end';\n\n/**\n * What a full-screen dialog grows out of (a container transform): `\"trigger\"` for the\n * `DialogTrigger`'s button, an element such as a FAB, or a box in window coordinates.\n */\nexport type DialogOrigin = 'trigger' | Element | DOMRectReadOnly;\n\ninterface DialogTriggerContextValue {\n state: OverlayTriggerState;\n /** Links the dialog to its trigger (`aria-controls`). */\n overlayProps: { id?: string };\n /** The trigger, for `origin=\"trigger\"`. */\n triggerRef: RefObject<HTMLElement | null>;\n}\n\n/** Where a container transform starts, measured from its origin. */\ninterface OriginBox {\n /** The origin's box and corners as a `clip-path` of the window. */\n clip: string;\n /** The origin's container colour, or the dialog's own when it has none. */\n color: string;\n}\n\n/** How browsers serialise a computed `transparent`. */\nconst TRANSPARENT = 'rgba(0, 0, 0, 0)';\n\n/** A corner radius in px; a percentage resolves against the origin's shorter side. */\nfunction cornerRadius(value: string, rect: DOMRectReadOnly): string {\n if (!value.endsWith('%')) return value || '0px';\n return `${(parseFloat(value) / 100) * Math.min(rect.width, rect.height)}px`;\n}\n\n/** The origin's box as insets from the window (the full-screen dialog's box). */\nfunction measureOrigin(origin: Element | DOMRectReadOnly): OriginBox {\n const isElement = origin instanceof Element;\n const rect = isElement ? origin.getBoundingClientRect() : origin;\n const style = isElement ? getComputedStyle(origin) : null;\n const radius = cornerRadius(style?.borderTopLeftRadius ?? '0px', rect);\n const top = rect.top;\n const left = rect.left;\n const right = window.innerWidth - rect.right;\n const bottom = window.innerHeight - rect.bottom;\n const background = style?.backgroundColor ?? TRANSPARENT;\n return {\n clip: `inset(${top}px ${right}px ${bottom}px ${left}px round ${radius})`,\n color: background === TRANSPARENT ? 'var(--md-sys-color-surface-container-high)' : background,\n };\n}\n\nconst DialogTriggerContext = createContext<DialogTriggerContextValue | null>(null);\n\n/**\n * Calls `onOpened` once the enter motion has finished: every CSS transition running on the\n * scrim, the motion wrapper, the panel and the panel's parts when they first render. Styles\n * are read on the first frame, so the `@starting-style` transitions have begun. A transition\n * cut short (the dialog closes while entering) never finishes, so a dialog that closes early\n * doesn't report opening.\n */\nfunction useOpened(\n scrimRef: RefObject<HTMLElement | null>,\n motionRef: RefObject<HTMLElement | null>,\n panelRef: RefObject<HTMLElement | null>,\n onOpened: (() => void) | undefined,\n) {\n const latest = useRef(onOpened);\n useLayoutEffect(() => {\n latest.current = onOpened;\n });\n useEffect(() => {\n let active = true;\n const frame = requestAnimationFrame(() => {\n const panel = panelRef.current;\n const elements = [scrimRef.current, motionRef.current, panel, ...(panel?.children ?? [])];\n const transitions = elements.flatMap((element) => {\n if (!element || typeof element.getAnimations !== 'function') return [];\n // Reading a style starts the transitions that haven't started yet.\n void getComputedStyle(element).opacity;\n return element.getAnimations().filter((animation) => 'transitionProperty' in animation);\n });\n Promise.all(transitions.map((transition) => transition.finished)).then(\n () => {\n if (active) latest.current?.();\n },\n () => undefined,\n );\n });\n return () => {\n active = false;\n cancelAnimationFrame(frame);\n };\n }, [scrimRef, motionRef, panelRef]);\n}\n\ninterface DialogPartsContextValue {\n titleProps: DOMAttributes<HTMLElement>;\n /** The id an alert dialog's description (`DialogContent`) takes. */\n contentProps: DOMAttributes<HTMLElement>;\n hasIcon: boolean;\n fullScreen: DialogFullScreen;\n close: () => void;\n /** `DialogContent` reports that it's there, so the dialog is described by it. */\n setHasContent: (has: boolean) => void;\n /** `DialogHeader` reports whether it holds the confirming action. */\n setHeaderAction: (has: boolean) => void;\n /** Full screen without a header action: `DialogActions` become a bottom bar. */\n bottomBar: boolean;\n}\n\nconst DialogPartsContext = createContext<DialogPartsContextValue>({\n titleProps: {},\n contentProps: {},\n hasIcon: false,\n fullScreen: 'never',\n close: () => undefined,\n setHasContent: () => undefined,\n setHeaderAction: () => undefined,\n bottomBar: false,\n});\n\nexport interface DialogTriggerProps {\n /** The trigger (a Button, IconButton, …) followed by the `Dialog`. */\n children: ReactNode;\n /** Controlled open state. */\n open?: boolean;\n /** Initial open state when uncontrolled. @default false */\n defaultOpen?: boolean;\n /** Called when the dialog opens or closes. */\n onOpenChange?: (open: boolean) => void;\n}\n\n/**\n * Opens a `Dialog` from a button. The first child is the trigger (any button-like\n * component); the rest is the dialog.\n *\n * @example\n * <DialogTrigger>\n * <Button>Delete</Button>\n * <Dialog>…</Dialog>\n * </DialogTrigger>\n */\nexport function DialogTrigger({ children, open, defaultOpen, onOpenChange }: DialogTriggerProps) {\n const state = useOverlayTriggerState({ isOpen: open, defaultOpen, onOpenChange });\n const triggerRef = useRef<HTMLElement>(null);\n const { triggerProps, overlayProps } = useOverlayTrigger({ type: 'dialog' }, state, triggerRef);\n const triggerValue = useTriggerProps({\n ...(triggerProps as TriggerContextValue),\n ref: triggerRef,\n });\n const [trigger, ...dialog] = Children.toArray(children);\n return (\n <DialogTriggerContext value={{ state, overlayProps, triggerRef }}>\n <TriggerContext value={triggerValue}>{trigger}</TriggerContext>\n {dialog}\n </DialogTriggerContext>\n );\n}\n\nexport interface DialogClassNames {\n /** The dimmed layer behind the dialog. */\n scrim?: string;\n /**\n * The fixed, full-window flex box that places the dialog. It pads 24px on compact windows\n * and 56px from the medium breakpoint, and `placement` sets its alignment, so alignment\n * classes here (`items-*`, `justify-*`) position the dialog anywhere within the spec's\n * margin, and padding classes change that margin.\n */\n positioner?: string;\n /** The dialog's surface: its colour, corners, padding, size and shadow. */\n panel?: string;\n /** The icon above the title. */\n icon?: string;\n}\n\nexport interface DialogRenderProps {\n /** Closes the dialog. */\n close: () => void;\n}\n\nexport interface DialogProps\n extends Omit<ComponentPropsWithRef<'div'>, 'children' | 'role'>, Omit<AriaDialogProps, 'role'> {\n /** Controlled open state, when not inside a `DialogTrigger`. */\n open?: boolean;\n /** Initial open state when uncontrolled, when not inside a `DialogTrigger`. @default false */\n defaultOpen?: boolean;\n /** Called when the dialog opens or closes. */\n onOpenChange?: (open: boolean) => void;\n /**\n * Basic dialogs are alert dialogs on the web (the spec): they need a decision, are\n * described by their `DialogContent`, and aren't dismissed by pressing outside. A dialog\n * that can be full screen is a `dialog`.\n * @default \"alertdialog\", or \"dialog\" when fullScreen isn't \"never\"\n */\n role?: 'dialog' | 'alertdialog';\n /**\n * How the user can close the dialog, as HTML's `<dialog closedby>`: `any` (Escape or a\n * press outside), `closerequest` (Escape only) or `none` (only its own buttons or the app).\n * @default \"closerequest\" for an alertdialog, \"any\" otherwise\n */\n closedBy?: ClosedBy;\n /** Icon above the title (which is then centred). Decorative. Not shown in full screen. */\n icon?: ReactNode;\n /**\n * Fills the window, with a `DialogHeader` holding the close button, title and confirming\n * action instead of `DialogActions` (or with them as a bottom bar when the header has no\n * action). `compact`, the spec's recommendation, does so only below 600px; `always` fills\n * every window. @default \"never\"\n */\n fullScreen?: DialogFullScreen;\n /**\n * Grows the full-screen dialog out of this origin and shrinks it back into it on close,\n * the spec's container transform from a FAB: `\"trigger\"` (the `DialogTrigger`'s button),\n * an element, or a box in window coordinates. The clip, corners and container colour\n * move from the origin to the window on the slow spatial spring and the content fades\n * in; reduced motion keeps only the fades. Used only while the dialog is full screen.\n */\n origin?: DialogOrigin;\n /**\n * Where a basic dialog sits: centred (the spec's default) or against one side of the\n * window, keeping 24px from its edges on compact windows and 56px on wider ones. For any\n * other position, align it with `classNames.positioner` (for example\n * `\"items-start justify-end\"` for the top end corner). It also sets the motion's\n * direction: centred or at the top the dialog slides down and grows downward as it\n * enters, at the bottom up, and at the start or end along the x axis (mirrored in\n * right-to-left layouts).\n * @default \"center\"\n */\n placement?: DialogPlacement;\n children?: ReactNode | ((props: DialogRenderProps) => ReactNode);\n /**\n * Classes for the dialog's parts, merged over the library's: `scrim`, `positioner` (the\n * full-window box that places the dialog; align it anywhere with `items-*` and\n * `justify-*`), `panel` (the surface) and `icon`.\n */\n classNames?: DialogClassNames;\n /** Goes on the outermost element, with `className`. */\n style?: CSSProperties;\n /**\n * How a basic dialog enters and leaves. `reveal` is the M3 dialog motion: it slides 50px\n * away from the edge it sits against while its container grows from that edge, and its\n * headline and content, then its actions, fade in. `scale` is the library's original pop,\n * an extension beyond the M3 motion: the dialog fades in while it scales up from 90%, and\n * fades out while it scales to 95%, from the centre (`placement=\"center\"`) or from the edge\n * it sits against. Either way reduced motion keeps only the fades and `instant` turns the\n * motion off. A full-screen dialog keeps its own motion and ignores it.\n * @default \"reveal\"\n */\n transition?: DialogTransition;\n /**\n * Skips the enter and exit motion: the dialog, its parts and the scrim appear and leave at\n * once, and the dialog leaves the document as soon as it closes. Reduced motion already\n * keeps only the fades. @default false\n */\n instant?: boolean;\n /**\n * Called when the dialog has finished opening: once its enter motion (the slide, the\n * container's growth and every part's fade) has ended, or right after it opens with\n * `instant`. Not called when it closes before then.\n */\n onOpened?: () => void;\n /**\n * Called when the dialog has finished closing: once its exit motion has ended and it has\n * left the document, or right after it closes with `instant`.\n */\n onClosed?: () => void;\n}\n\n/**\n * M3 basic dialog: a modal panel over a scrim, rendered in a portal that keeps the\n * surrounding theme. It's an alert dialog (described by its `DialogContent`). Focus moves\n * to its first interactive element (or one marked `autoFocus`), is contained, and is\n * restored on close; Escape closes it; the page behind does not scroll and is hidden from\n * assistive tech. Name it with a `DialogTitle` (or `aria-label`).\n *\n * It enters with the M3 dialog motion: it slides 50px away from the window edge it sits\n * against while its container grows from that edge, then its headline and content fade in,\n * then its actions; it leaves by fading its parts, sliding back and shrinking, then fading.\n * Any children take part: the actions phase is for `DialogActions` (or any child with\n * `data-slot=\"actions\"`), and every other child fades in with the content. `onOpened` and\n * `onClosed` report the end of each motion; `instant` turns it off.\n *\n * `className` and `style` go on the outermost element, a full-window layer around the\n * positioner whose inline placement they can't move, so a layout class (`static`,\n * `overflow-hidden`, a transform) never hides, clips or stretches the dialog; style the\n * surface with `classNames.panel`. `ref` and other attributes go on the dialog panel.\n *\n * @example\n * <Dialog icon={<DeleteIcon />}>\n * {({ close }) => (\n * <>\n * <DialogTitle>Delete 3 files?</DialogTitle>\n * <DialogContent>They will be removed from all devices.</DialogContent>\n * <DialogActions>\n * <Button variant=\"text\" onPress={close}>Cancel</Button>\n * <Button variant=\"text\" onPress={remove}>Delete</Button>\n * </DialogActions>\n * </>\n * )}\n * </Dialog>\n */\nexport function Dialog({\n open,\n defaultOpen,\n onOpenChange,\n origin,\n onClosed,\n ...props\n}: DialogProps) {\n const trigger = useContext(DialogTriggerContext);\n const ownState = useOverlayTriggerState({ isOpen: open, defaultOpen, onOpenChange });\n const state = trigger?.state ?? ownState;\n const { isPresent, isExiting, exitProps } = usePresence(state.isOpen, 'opacity', !props.instant);\n const onClosedRef = useRef(onClosed);\n useLayoutEffect(() => {\n onClosedRef.current = onClosed;\n });\n const wasPresent = useRef(isPresent);\n useEffect(() => {\n if (wasPresent.current && !isPresent) onClosedRef.current?.();\n wasPresent.current = isPresent;\n }, [isPresent]);\n const usesOrigin = origin !== undefined && (props.fullScreen ?? 'never') !== 'never';\n // The origin is measured before the dialog is in the document (a layout effect, then a\n // synchronous re-render before paint), so the dialog's first style already starts on the\n // origin; and again as it closes, in case the origin moved. `box` is null when there's\n // nothing to measure (`\"trigger\"` outside a `DialogTrigger`).\n const [measured, setMeasured] = useState<{ box: OriginBox | null } | null>(null);\n if (!isPresent && measured !== null) setMeasured(null);\n const triggerRef = trigger?.triggerRef;\n const isOpen = state.isOpen;\n useLayoutEffect(() => {\n if (!usesOrigin || !isPresent) return;\n const target = origin === 'trigger' ? triggerRef?.current : origin;\n setMeasured({ box: target ? measureOrigin(target) : null });\n }, [usesOrigin, origin, triggerRef, isOpen, isPresent]);\n if (!isPresent || (usesOrigin && measured === null)) return null;\n return (\n <Overlay exiting={isExiting}>\n <DialogModal\n {...props}\n state={state}\n isExiting={isExiting}\n exitProps={exitProps}\n triggerOverlayProps={trigger?.overlayProps}\n originBox={usesOrigin ? (measured?.box ?? null) : null}\n />\n </Overlay>\n );\n}\n\nfunction DialogModal({\n state,\n isExiting,\n exitProps,\n triggerOverlayProps,\n originBox,\n role: roleProp,\n closedBy,\n icon,\n fullScreen = 'never',\n placement = 'center',\n instant = false,\n transition = 'reveal',\n onOpened,\n children,\n className,\n classNames,\n style,\n ref,\n ...rest\n}: Omit<DialogProps, 'open' | 'defaultOpen' | 'onOpenChange' | 'onClosed'> & {\n state: OverlayTriggerState;\n isExiting: boolean;\n exitProps: ReturnType<typeof usePresence>['exitProps'];\n triggerOverlayProps?: { id?: string };\n originBox: OriginBox | null;\n}) {\n const role = roleProp ?? (fullScreen === 'never' ? 'alertdialog' : 'dialog');\n const panelRef = useObjectRef(ref as Ref<HTMLDivElement>);\n const motionRef = useRef<HTMLDivElement>(null);\n const scrimRef = useRef<HTMLDivElement>(null);\n useOpened(scrimRef, motionRef, panelRef, onOpened);\n const [hasContent, setHasContent] = useState(false);\n const [headerAction, setHeaderAction] = useState(false);\n const { modalProps, underlayProps } = useModalOverlay(\n closedByOverlayOptions(\n closedBy ?? (role === 'alertdialog' ? 'closerequest' : 'any'),\n isExiting,\n ),\n state,\n panelRef,\n );\n const { dialogProps, titleProps, contentProps } = useDialog({ ...rest, role }, panelRef);\n // An alert dialog is described by its content; leave the reference out when there's none.\n const describedBy =\n rest['aria-describedby'] ?? (hasContent ? dialogProps['aria-describedby'] : undefined);\n\n // The spec: \"focus should automatically land on the first interactive element within the\n // dialog\", not on the panel `useDialog` focuses. A scrolling `DialogContent` is focusable\n // (so the keyboard can scroll it) but isn't a control. When that element is one of the\n // actions, focus goes to the least destructive one (`leastDestructiveAction`).\n useEffect(() => focusFirstInteractive(panelRef.current, leastDestructiveAction), [panelRef]);\n\n const hasIcon = fullScreen !== 'always' && icon !== undefined && icon !== null;\n const bottomBar = fullScreen !== 'never' && !headerAction;\n const origin = originBox !== null;\n const styles = dialogStyles({\n hasIcon,\n fullScreen,\n placement,\n bottomBar,\n origin,\n instant,\n transition,\n });\n const exiting = isExiting || undefined;\n\n return (\n <>\n <div\n ref={scrimRef}\n aria-hidden=\"true\"\n data-exiting={exiting}\n className={styles.scrim({ class: classNames?.scrim })}\n />\n <div style={{ ...style, ...DIALOG_ROOT_STYLE }} className={styles.root({ class: className })}>\n <div {...underlayProps} className={styles.positioner({ class: classNames?.positioner })}>\n <div\n {...exitProps}\n ref={motionRef}\n data-exiting={exiting}\n style={\n originBox\n ? ({\n '--m3-dialog-origin': originBox.clip,\n '--m3-dialog-origin-color': originBox.color,\n } as CSSProperties)\n : undefined\n }\n className={styles.motion()}\n >\n <div\n {...mergeProps(rest, triggerOverlayProps ?? {}, modalProps, dialogProps)}\n aria-describedby={describedBy}\n ref={panelRef}\n data-exiting={exiting}\n // The component tokens' hook (dialog-tokens.ts).\n data-tokens={mergeDataTokens('md-comp-dialog', rest)}\n data-full-screen={fullScreen === 'never' ? undefined : fullScreen}\n data-origin={origin || undefined}\n className={styles.panel({\n class: [transition === 'reveal' && styles.parts(), classNames?.panel],\n })}\n >\n <TriggerContext value={null}>\n <DialogPartsContext\n value={{\n titleProps,\n contentProps,\n hasIcon,\n fullScreen,\n close: state.close,\n setHasContent,\n setHeaderAction,\n bottomBar,\n }}\n >\n {hasIcon && (\n <span\n aria-hidden=\"true\"\n data-slot=\"icon\"\n className={styles.icon({\n class: cn(\n fullScreen === 'compact' && 'max-medium:hidden',\n classNames?.icon,\n ),\n })}\n >\n {icon}\n </span>\n )}\n {typeof children === 'function' ? children({ close: state.close }) : children}\n </DialogPartsContext>\n </TriggerContext>\n </div>\n </div>\n </div>\n </div>\n </>\n );\n}\n\nexport type DialogTitleProps = ComponentPropsWithRef<'h2'>;\n\n/**\n * The dialog's headline; it also names the dialog for assistive tech. In a dialog that can\n * be full screen, use `DialogHeader` instead.\n */\nexport function DialogTitle({ className, ...props }: DialogTitleProps) {\n const { titleProps, hasIcon } = useContext(DialogPartsContext);\n return (\n <h2\n {...mergeProps(titleProps, props)}\n data-slot=\"title\"\n className={dialogStyles({ hasIcon }).title({ class: className })}\n />\n );\n}\n\nexport interface DialogHeaderClassNames {\n /** The header row; `className` is merged here too. */\n root?: string;\n /** The close button. */\n close?: string;\n /** The headline. */\n title?: string;\n}\n\n/** Words a {@link DialogHeader} announces. */\nexport interface DialogHeaderLabels {\n /** The full-screen close button's name. @default \"Close\" */\n close: string;\n}\n\nconst DEFAULT_HEADER_LABELS: DialogHeaderLabels = { close: 'Close' };\n\nexport interface DialogHeaderProps extends Omit<ComponentPropsWithRef<'div'>, 'title'> {\n /** The headline; it names the dialog. */\n children: ReactNode;\n /** Words the header announces, with English defaults. */\n labels?: Partial<DialogHeaderLabels>;\n /**\n * The confirming action, usually a text `Button` (\"Save\"). Shown in full screen only;\n * without it, a full-screen dialog shows its `DialogActions` as a bottom bar.\n */\n action?: ReactNode;\n /** A 1px `outline-variant` divider under the full-screen header. @default false */\n divider?: boolean;\n /**\n * Disables the full-screen close button, e.g. while the confirming action is saving, so the\n * dialog can't be left halfway. Pair it with the `Dialog`'s `closedBy=\"none\"` to keep Escape\n * from closing it too. @default false\n */\n closeDisabled?: boolean;\n /** Replaces the full-screen close button's icon. */\n closeIcon?: ReactElement;\n /** Classes for each part, by slot. */\n classNames?: DialogHeaderClassNames;\n}\n\n/**\n * The headline of a dialog that can be full screen. In full screen it is the M3 full-screen\n * dialog's header: a close icon button, the headline and the confirming action. Otherwise\n * it is a plain `DialogTitle`, and the dialog's `DialogActions` carry the actions.\n *\n * @example\n * <Dialog fullScreen=\"compact\">\n * <DialogHeader action={<Button variant=\"text\" onPress={save}>Save</Button>}>\n * Edit name\n * </DialogHeader>\n * <DialogContent>…</DialogContent>\n * <DialogActions>…</DialogActions>\n * </Dialog>\n */\nexport function DialogHeader({\n children,\n labels,\n action,\n divider = false,\n closeDisabled = false,\n closeIcon,\n className,\n classNames,\n ...props\n}: DialogHeaderProps) {\n const { titleProps, hasIcon, fullScreen, close, setHeaderAction } =\n useContext(DialogPartsContext);\n const hasAction = action !== undefined;\n useLayoutEffect(() => {\n setHeaderAction(hasAction);\n return () => setHeaderAction(false);\n }, [hasAction, setHeaderAction]);\n const styles = dialogStyles({ hasIcon, fullScreen, divider });\n return (\n <div {...props} className={styles.header({ class: cn(classNames?.root, className) })}>\n {fullScreen !== 'never' && (\n <IconButton\n variant=\"standard\"\n density={0}\n icon={closeIcon ?? <CloseIcon />}\n aria-label={labels?.close ?? DEFAULT_HEADER_LABELS.close}\n disabled={closeDisabled}\n onPress={close}\n className={styles.headerButton({ class: cn(styles.close(), classNames?.close) })}\n />\n )}\n <h2 {...titleProps} data-slot=\"title\" className={styles.title({ class: classNames?.title })}>\n {children}\n </h2>\n {fullScreen !== 'never' && action !== undefined && (\n <div className={styles.headerButton()}>\n <DensityZeroButtons>{action}</DensityZeroButtons>\n </div>\n )}\n </div>\n );\n}\n\nexport interface DialogContentProps extends ComponentPropsWithRef<'div'> {\n /**\n * The basic dialog's optional dividers: 1px `outline-variant` lines across the panel\n * above and below the content, e.g. around a scrolling list. Not drawn in full screen.\n * @default false\n */\n dividers?: boolean;\n}\n\n/**\n * Whether an element's content is taller than its box, from a `ResizeObserver` on it and\n * its children (so it is measured only when a size changes).\n */\nfunction useOverflows(ref: RefObject<HTMLElement | null>) {\n const [overflows, setOverflows] = useState(false);\n useLayoutEffect(() => {\n const element = ref.current;\n if (!element || typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver(() =>\n setOverflows(element.scrollHeight > element.clientHeight + 1),\n );\n observer.observe(element);\n for (const child of element.children) observer.observe(child);\n return () => observer.disconnect();\n }, [ref]);\n return overflows;\n}\n\n/**\n * The dialog's supporting text or content; scrolls when the dialog is too tall. It describes\n * an alert dialog. While it scrolls it is focusable (with a focus ring), so the keyboard can\n * scroll it (`data-scrollable`); the dialog's initial focus still goes to its first control.\n */\nexport function DialogContent({ className, dividers = false, ref, ...props }: DialogContentProps) {\n const { fullScreen, contentProps, setHasContent } = useContext(DialogPartsContext);\n const contentRef = useObjectRef(ref as Ref<HTMLDivElement>);\n const overflows = useOverflows(contentRef);\n const { isFocusVisible, focusProps } = useFocusRing();\n useLayoutEffect(() => {\n setHasContent(true);\n return () => setHasContent(false);\n }, [setHasContent]);\n return (\n <div\n {...mergeProps(contentProps, overflows ? focusProps : {}, props)}\n ref={contentRef}\n tabIndex={props.tabIndex ?? (overflows ? 0 : undefined)}\n data-scrollable={overflows || undefined}\n data-focus-visible={(overflows && isFocusVisible) || undefined}\n data-slot=\"content\"\n className={dialogStyles({ fullScreen, dividers }).content({ class: className })}\n />\n );\n}\n\nexport type DialogActionsProps = ComponentPropsWithRef<'div'>;\n\n/** The elements among `children`, counting a fragment's children rather than the fragment. */\nfunction countActions(children: ReactNode): number {\n let count = 0;\n Children.forEach(children, (child) => {\n if (!isValidElement<{ children?: ReactNode }>(child)) return;\n count += child.type === Fragment ? countActions(child.props.children) : 1;\n });\n return count;\n}\n\n/** `children` as a flat list, with fragments' children in place of the fragments. */\nfunction flatChildren(children: ReactNode): ReactNode[] {\n return Children.toArray(children).flatMap((child) =>\n isValidElement<{ children?: ReactNode }>(child) && child.type === Fragment\n ? flatChildren(child.props.children)\n : [child],\n );\n}\n\n/**\n * Where a dialog's initial focus goes when its first interactive element is one of its\n * actions: the dismissive, least destructive action (the WAI-ARIA alert dialog pattern:\n * \"focus the least destructive action\"). That's the first action in source order, which is\n * the row's first button on one line and its last when stacked, since stacked actions are\n * rendered with the confirming action on top. The row says which (`data-stacked`) in the\n * same render that reorders it, so the rule holds whenever the dialog takes focus, before\n * or after the actions stack.\n */\nexport function leastDestructiveAction(first: HTMLElement): HTMLElement {\n const row = first.closest<HTMLElement>('[data-slot=\"actions\"]');\n if (!row) return first;\n const actions = tabbables(row);\n return (row.hasAttribute('data-stacked') ? actions.at(-1) : actions[0]) ?? first;\n}\n\n/**\n * Whether the actions are stacked: they don't fit on one line, and the layout lets them\n * stack (`--m3-dialog-actions-stack`, off in the full-screen bottom bar). Measured on mount\n * and on every resize of the row or a button. Switching reorders the buttons, and moving\n * the focused one drops focus to the page, so it's focused again.\n */\nfunction useStackedActions(ref: RefObject<HTMLDivElement | null>) {\n const [stacked, setStacked] = useState(false);\n const current = useRef(false);\n // The button with focus, kept until focus moves somewhere else.\n const focused = useRef<HTMLElement | null>(null);\n useLayoutEffect(() => {\n const row = ref.current;\n if (!row) return;\n const measure = () => {\n // A hidden row (the header carries the confirming action) keeps its order.\n if (row.clientWidth === 0) return;\n const style = getComputedStyle(row);\n const items = [...row.children] as HTMLElement[];\n const gap = parseFloat(style.columnGap) || 0;\n const room =\n row.clientWidth -\n (parseFloat(style.paddingLeft) || 0) -\n (parseFloat(style.paddingRight) || 0);\n const needed =\n items.reduce((sum, item) => sum + item.offsetWidth, 0) + gap * (items.length - 1);\n const next =\n style.getPropertyValue('--m3-dialog-actions-stack').trim() === '1' &&\n items.length > 1 &&\n needed > room + 0.5;\n if (next === current.current) return;\n current.current = next;\n setStacked(next);\n };\n const onFocusIn = (event: FocusEvent) => {\n if (event.target instanceof HTMLElement) focused.current = event.target;\n };\n const onFocusOut = (event: FocusEvent) => {\n if (event.relatedTarget !== null) focused.current = null;\n };\n row.addEventListener('focusin', onFocusIn);\n row.addEventListener('focusout', onFocusOut);\n const observer = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(measure);\n observer?.observe(row);\n for (const item of row.children) observer?.observe(item);\n measure();\n return () => {\n row.removeEventListener('focusin', onFocusIn);\n row.removeEventListener('focusout', onFocusOut);\n observer?.disconnect();\n };\n }, [ref]);\n useLayoutEffect(() => {\n const element = focused.current;\n const active = element?.ownerDocument.activeElement;\n if (element?.isConnected && (!active || active === element.ownerDocument.body)) element.focus();\n }, [stacked]);\n return stacked;\n}\n\n/**\n * Row of actions at the end of the dialog, 8px apart. Put the confirming action last:\n * when the actions do not fit on one line they stack with it on top (M3: \"Confirming\n * actions appear above dismissive actions\"). Stacked, the buttons are rendered in that\n * visual order, so Tab and screen readers reach the confirming action first, top to bottom,\n * as they reach the dismissive one first in a row. When the actions are the dialog's first\n * controls, its initial focus goes to the dismissive action, stacked (the bottom one) or\n * not: the least destructive choice, as the WAI-ARIA alert dialog pattern asks; a stack\n * that forms or unforms later keeps focus where it is. While the dialog is full screen they're\n * hidden when `DialogHeader` carries the confirming action, and otherwise form the spec's\n * 56px bottom action bar. Use at most two actions (the spec); in development it warns once\n * when it holds more.\n */\nexport function DialogActions({ className, children, ref, ...props }: DialogActionsProps) {\n const { fullScreen, bottomBar } = useContext(DialogPartsContext);\n const actionCount = countActions(children);\n useWarnOnce(\n actionCount > 2,\n `[@windmorph/react] DialogActions holds ${actionCount} actions; M3 dialogs have at most two ` +\n '(no third such as \"Learn more\"). Show more inline, or before the dialog opens.',\n );\n const rowRef = useObjectRef(ref);\n const stacked = useStackedActions(rowRef);\n return (\n <div\n {...props}\n ref={rowRef}\n data-slot=\"actions\"\n data-stacked={stacked || undefined}\n className={dialogStyles({ fullScreen, bottomBar }).actions({ class: className })}\n >\n {/* The same keys either way, so switching moves the buttons rather than remounting them. */}\n {stacked ? flatChildren(children).reverse() : flatChildren(children)}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Dialog.js","names":["Overlay"],"sources":["../../../src/components/dialog/Dialog.tsx"],"sourcesContent":["'use client';\n\nimport {\n Children,\n Fragment,\n createContext,\n isValidElement,\n useContext,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n type ComponentPropsWithRef,\n type RefObject,\n type CSSProperties,\n type DOMAttributes,\n type ReactElement,\n type ReactNode,\n type Ref,\n} from 'react';\nimport {\n mergeProps,\n useDialog,\n useFocusRing,\n useModalOverlay,\n useObjectRef,\n useOverlayTrigger,\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 {\n TriggerContext,\n useTriggerProps,\n type TriggerContextValue,\n} 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 { useWarnOnce } from '../../utils/warn-once';\nimport { DensityZeroButtons } from '../button/DensityZeroButtons';\nimport { IconButton } from '../icon-button/IconButton';\nimport { DIALOG_ROOT_STYLE, dialogStyles } from './dialog-styles';\nimport { mergeDataTokens } from '../../utils/merge-data-tokens';\n\n/** Material Symbols \"close\" (Apache-2.0). */\nfunction CloseIcon() {\n return (\n <svg viewBox=\"0 -960 960 960\" fill=\"currentColor\">\n <path d=\"m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z\" />\n </svg>\n );\n}\n\n/**\n * When a dialog fills the window (the M3 full-screen dialog): `never`, `always`, or\n * `compact` for windows narrower than 600px only, the M3 recommendation.\n */\nexport type DialogFullScreen = 'never' | 'always' | 'compact';\n\n/**\n * How a basic dialog enters and leaves: `reveal`, the M3 dialog motion, or `scale`, the\n * original pop (a fade with a scale from 90%).\n */\nexport type DialogTransition = 'reveal' | 'scale';\n\n/** Where a basic dialog sits in the window, keeping the spec's 56px margin on wider windows. */\nexport type DialogPlacement = 'center' | 'top' | 'bottom' | 'start' | 'end';\n\n/**\n * What a full-screen dialog grows out of (a container transform): `\"trigger\"` for the\n * `DialogTrigger`'s button, an element such as a FAB, or a box in window coordinates.\n */\nexport type DialogOrigin = 'trigger' | Element | DOMRectReadOnly;\n\ninterface DialogTriggerContextValue {\n state: OverlayTriggerState;\n /** Links the dialog to its trigger (`aria-controls`). */\n overlayProps: { id?: string };\n /** The trigger, for `origin=\"trigger\"`. */\n triggerRef: RefObject<HTMLElement | null>;\n}\n\n/** Where a container transform starts, measured from its origin. */\ninterface OriginBox {\n /** The origin's box and corners as a `clip-path` of the window. */\n clip: string;\n /** The origin's container colour, or the dialog's own when it has none. */\n color: string;\n}\n\n/** How browsers serialise a computed `transparent`. */\nconst TRANSPARENT = 'rgba(0, 0, 0, 0)';\n\n/** A corner radius in px; a percentage resolves against the origin's shorter side. */\nfunction cornerRadius(value: string, rect: DOMRectReadOnly): string {\n if (!value.endsWith('%')) return value || '0px';\n return `${(parseFloat(value) / 100) * Math.min(rect.width, rect.height)}px`;\n}\n\n/** The origin's box as insets from the window (the full-screen dialog's box). */\nfunction measureOrigin(origin: Element | DOMRectReadOnly): OriginBox {\n const isElement = origin instanceof Element;\n const rect = isElement ? origin.getBoundingClientRect() : origin;\n const style = isElement ? getComputedStyle(origin) : null;\n const radius = cornerRadius(style?.borderTopLeftRadius ?? '0px', rect);\n const top = rect.top;\n const left = rect.left;\n const right = window.innerWidth - rect.right;\n const bottom = window.innerHeight - rect.bottom;\n const background = style?.backgroundColor ?? TRANSPARENT;\n return {\n clip: `inset(${top}px ${right}px ${bottom}px ${left}px round ${radius})`,\n color: background === TRANSPARENT ? 'var(--md-sys-color-surface-container-high)' : background,\n };\n}\n\nconst DialogTriggerContext = createContext<DialogTriggerContextValue | null>(null);\n\n/**\n * Calls `onOpened` once the enter motion has finished: every CSS transition running on the\n * scrim, the motion wrapper, the panel and the panel's parts when they first render. Styles\n * are read on the first frame, so the `@starting-style` transitions have begun. A transition\n * cut short (the dialog closes while entering) never finishes, so a dialog that closes early\n * doesn't report opening.\n */\nfunction useOpened(\n scrimRef: RefObject<HTMLElement | null>,\n motionRef: RefObject<HTMLElement | null>,\n panelRef: RefObject<HTMLElement | null>,\n onOpened: (() => void) | undefined,\n) {\n const latest = useRef(onOpened);\n useLayoutEffect(() => {\n latest.current = onOpened;\n });\n useEffect(() => {\n let active = true;\n const frame = requestAnimationFrame(() => {\n const panel = panelRef.current;\n const elements = [scrimRef.current, motionRef.current, panel, ...(panel?.children ?? [])];\n const transitions = elements.flatMap((element) => {\n if (!element || typeof element.getAnimations !== 'function') return [];\n // Reading a style starts the transitions that haven't started yet.\n void getComputedStyle(element).opacity;\n return element.getAnimations().filter((animation) => 'transitionProperty' in animation);\n });\n Promise.all(transitions.map((transition) => transition.finished)).then(\n () => {\n if (active) latest.current?.();\n },\n () => undefined,\n );\n });\n return () => {\n active = false;\n cancelAnimationFrame(frame);\n };\n }, [scrimRef, motionRef, panelRef]);\n}\n\ninterface DialogPartsContextValue {\n titleProps: DOMAttributes<HTMLElement>;\n /** The id an alert dialog's description (`DialogContent`) takes. */\n contentProps: DOMAttributes<HTMLElement>;\n hasIcon: boolean;\n fullScreen: DialogFullScreen;\n close: () => void;\n /** `DialogContent` reports that it's there, so the dialog is described by it. */\n setHasContent: (has: boolean) => void;\n /** `DialogHeader` reports whether it holds the confirming action. */\n setHeaderAction: (has: boolean) => void;\n /** Full screen without a header action: `DialogActions` become a bottom bar. */\n bottomBar: boolean;\n}\n\nconst DialogPartsContext = createContext<DialogPartsContextValue>({\n titleProps: {},\n contentProps: {},\n hasIcon: false,\n fullScreen: 'never',\n close: () => undefined,\n setHasContent: () => undefined,\n setHeaderAction: () => undefined,\n bottomBar: false,\n});\n\nexport interface DialogTriggerProps {\n /** The trigger (a Button, IconButton, …) followed by the `Dialog`. */\n children: ReactNode;\n /** Controlled open state. */\n open?: boolean;\n /** Initial open state when uncontrolled. @default false */\n defaultOpen?: boolean;\n /** Called when the dialog opens or closes. */\n onOpenChange?: (open: boolean) => void;\n}\n\n/**\n * Opens a `Dialog` from a button. The first child is the trigger (any button-like\n * component); the rest is the dialog.\n *\n * @example\n * <DialogTrigger>\n * <Button>Delete</Button>\n * <Dialog>…</Dialog>\n * </DialogTrigger>\n */\nexport function DialogTrigger({ children, open, defaultOpen, onOpenChange }: DialogTriggerProps) {\n const state = useOverlayTriggerState({ isOpen: open, defaultOpen, onOpenChange });\n const triggerRef = useRef<HTMLElement>(null);\n const { triggerProps, overlayProps } = useOverlayTrigger({ type: 'dialog' }, state, triggerRef);\n const triggerValue = useTriggerProps({\n ...(triggerProps as TriggerContextValue),\n ref: triggerRef,\n });\n const [trigger, ...dialog] = Children.toArray(children);\n return (\n <DialogTriggerContext value={{ state, overlayProps, triggerRef }}>\n <TriggerContext value={triggerValue}>{trigger}</TriggerContext>\n {dialog}\n </DialogTriggerContext>\n );\n}\n\nexport interface DialogClassNames {\n /** The dimmed layer behind the dialog. */\n scrim?: string;\n /**\n * The fixed, full-window flex box that places the dialog. It pads 24px on compact windows\n * and 56px from the medium breakpoint, and `placement` sets its alignment, so alignment\n * classes here (`items-*`, `justify-*`) position the dialog anywhere within the spec's\n * margin, and padding classes change that margin.\n */\n positioner?: string;\n /** The dialog's surface: its colour, corners, padding, size and shadow. */\n panel?: string;\n /** The icon above the title. */\n icon?: string;\n}\n\nexport interface DialogRenderProps {\n /** Closes the dialog. */\n close: () => void;\n}\n\nexport interface DialogProps\n extends Omit<ComponentPropsWithRef<'div'>, 'children' | 'role'>, Omit<AriaDialogProps, 'role'> {\n /** Controlled open state, when not inside a `DialogTrigger`. */\n open?: boolean;\n /** Initial open state when uncontrolled, when not inside a `DialogTrigger`. @default false */\n defaultOpen?: boolean;\n /** Called when the dialog opens or closes. */\n onOpenChange?: (open: boolean) => void;\n /**\n * Basic dialogs are alert dialogs on the web (the spec): they need a decision, are\n * described by their `DialogContent`, and aren't dismissed by pressing outside. A dialog\n * that can be full screen is a `dialog`.\n * @default \"alertdialog\", or \"dialog\" when fullScreen isn't \"never\"\n */\n role?: 'dialog' | 'alertdialog';\n /**\n * How the user can close the dialog, as HTML's `<dialog closedby>`: `any` (Escape or a\n * press outside), `closerequest` (Escape only) or `none` (only its own buttons or the app).\n * @default \"closerequest\" for an alertdialog, \"any\" otherwise\n */\n closedBy?: ClosedBy;\n /** Icon above the title (which is then centred). Decorative. Not shown in full screen. */\n icon?: ReactNode;\n /**\n * Fills the window, with a `DialogHeader` holding the close button, title and confirming\n * action instead of `DialogActions` (or with them as a bottom bar when the header has no\n * action). `compact`, the spec's recommendation, does so only below 600px; `always` fills\n * every window. @default \"never\"\n */\n fullScreen?: DialogFullScreen;\n /**\n * Grows the full-screen dialog out of this origin and shrinks it back into it on close,\n * the spec's container transform from a FAB: `\"trigger\"` (the `DialogTrigger`'s button),\n * an element, or a box in window coordinates. The clip, corners and container colour\n * move from the origin to the window on the slow spatial spring and the content fades\n * in; reduced motion keeps only the fades. Used only while the dialog is full screen.\n */\n origin?: DialogOrigin;\n /**\n * Where a basic dialog sits: centred (the spec's default) or against one side of the\n * window, keeping 24px from its edges on compact windows and 56px on wider ones. For any\n * other position, align it with `classNames.positioner` (for example\n * `\"items-start justify-end\"` for the top end corner). It also sets the motion's\n * direction: centred or at the top the dialog slides down and grows downward as it\n * enters, at the bottom up, and at the start or end along the x axis (mirrored in\n * right-to-left layouts).\n * @default \"center\"\n */\n placement?: DialogPlacement;\n children?: ReactNode | ((props: DialogRenderProps) => ReactNode);\n /**\n * Classes for the dialog's parts, merged over the library's: `scrim`, `positioner` (the\n * full-window box that places the dialog; align it anywhere with `items-*` and\n * `justify-*`), `panel` (the surface) and `icon`.\n */\n classNames?: DialogClassNames;\n /** Goes on the outermost element, with `className`. */\n style?: CSSProperties;\n /**\n * How a basic dialog enters and leaves. `reveal` is the M3 dialog motion: it slides 50px\n * away from the edge it sits against while its container grows from that edge, and its\n * headline and content, then its actions, fade in. `scale` is the library's original pop,\n * an extension beyond the M3 motion: the dialog fades in while it scales up from 90%, and\n * fades out while it scales to 95%, from the centre (`placement=\"center\"`) or from the edge\n * it sits against. Either way reduced motion keeps only the fades and `instant` turns the\n * motion off. A full-screen dialog keeps its own motion and ignores it.\n * @default \"reveal\"\n */\n transition?: DialogTransition;\n /**\n * Skips the enter and exit motion: the dialog, its parts and the scrim appear and leave at\n * once, and the dialog leaves the document as soon as it closes. Reduced motion already\n * keeps only the fades. @default false\n */\n instant?: boolean;\n /**\n * Called when the dialog has finished opening: once its enter motion (the slide, the\n * container's growth and every part's fade) has ended, or right after it opens with\n * `instant`. Not called when it closes before then.\n */\n onOpened?: () => void;\n /**\n * Called when the dialog has finished closing: once its exit motion has ended and it has\n * left the document, or right after it closes with `instant`.\n */\n onClosed?: () => void;\n}\n\n/**\n * M3 basic dialog: a modal panel over a scrim, rendered in a portal that keeps the\n * surrounding theme. It's an alert dialog (described by its `DialogContent`). Focus moves\n * to its first interactive element (or one marked `autoFocus`), is contained, and is\n * restored on close; Escape closes it; the page behind does not scroll and is hidden from\n * assistive tech. Name it with a `DialogTitle` (or `aria-label`).\n *\n * It enters with the M3 dialog motion: it slides 50px away from the window edge it sits\n * against while its container grows from that edge, then its headline and content fade in,\n * then its actions; it leaves by fading its parts, sliding back and shrinking, then fading.\n * Any children take part: the actions phase is for `DialogActions` (or any child with\n * `data-slot=\"actions\"`), and every other child fades in with the content. `onOpened` and\n * `onClosed` report the end of each motion; `instant` turns it off.\n *\n * `className` and `style` go on the outermost element, a full-window layer around the\n * positioner whose inline placement they can't move, so a layout class (`static`,\n * `overflow-hidden`, a transform) never hides, clips or stretches the dialog; style the\n * surface with `classNames.panel`. `ref` and other attributes go on the dialog panel.\n *\n * @example\n * <Dialog icon={<DeleteIcon />}>\n * {({ close }) => (\n * <>\n * <DialogTitle>Delete 3 files?</DialogTitle>\n * <DialogContent>They will be removed from all devices.</DialogContent>\n * <DialogActions>\n * <Button variant=\"text\" onPress={close}>Cancel</Button>\n * <Button variant=\"text\" onPress={remove}>Delete</Button>\n * </DialogActions>\n * </>\n * )}\n * </Dialog>\n */\nexport function Dialog({\n open,\n defaultOpen,\n onOpenChange,\n origin,\n onClosed,\n ...props\n}: DialogProps) {\n const trigger = useContext(DialogTriggerContext);\n const ownState = useOverlayTriggerState({ isOpen: open, defaultOpen, onOpenChange });\n const state = trigger?.state ?? ownState;\n const { isPresent, isExiting, exitProps } = usePresence(state.isOpen, 'opacity', !props.instant);\n const onClosedRef = useRef(onClosed);\n useLayoutEffect(() => {\n onClosedRef.current = onClosed;\n });\n const wasPresent = useRef(isPresent);\n useEffect(() => {\n if (wasPresent.current && !isPresent) onClosedRef.current?.();\n wasPresent.current = isPresent;\n }, [isPresent]);\n const usesOrigin = origin !== undefined && (props.fullScreen ?? 'never') !== 'never';\n // The origin is measured before the dialog is in the document (a layout effect, then a\n // synchronous re-render before paint), so the dialog's first style already starts on the\n // origin; and again as it closes, in case the origin moved. `box` is null when there's\n // nothing to measure (`\"trigger\"` outside a `DialogTrigger`).\n const [measured, setMeasured] = useState<{ box: OriginBox | null } | null>(null);\n if (!isPresent && measured !== null) setMeasured(null);\n const triggerRef = trigger?.triggerRef;\n const isOpen = state.isOpen;\n useLayoutEffect(() => {\n if (!usesOrigin || !isPresent) return;\n const target = origin === 'trigger' ? triggerRef?.current : origin;\n setMeasured({ box: target ? measureOrigin(target) : null });\n }, [usesOrigin, origin, triggerRef, isOpen, isPresent]);\n if (!isPresent || (usesOrigin && measured === null)) return null;\n return (\n <Overlay exiting={isExiting}>\n <DialogModal\n {...props}\n state={state}\n isExiting={isExiting}\n exitProps={exitProps}\n triggerOverlayProps={trigger?.overlayProps}\n originBox={usesOrigin ? (measured?.box ?? null) : null}\n />\n </Overlay>\n );\n}\n\nfunction DialogModal({\n state,\n isExiting,\n exitProps,\n triggerOverlayProps,\n originBox,\n role: roleProp,\n closedBy,\n icon,\n fullScreen = 'never',\n placement = 'center',\n instant = false,\n transition = 'reveal',\n onOpened,\n children,\n className,\n classNames,\n style,\n ref,\n ...rest\n}: Omit<DialogProps, 'open' | 'defaultOpen' | 'onOpenChange' | 'onClosed'> & {\n state: OverlayTriggerState;\n isExiting: boolean;\n exitProps: ReturnType<typeof usePresence>['exitProps'];\n triggerOverlayProps?: { id?: string };\n originBox: OriginBox | null;\n}) {\n const role = roleProp ?? (fullScreen === 'never' ? 'alertdialog' : 'dialog');\n const panelRef = useObjectRef(ref as Ref<HTMLDivElement>);\n const motionRef = useRef<HTMLDivElement>(null);\n const scrimRef = useRef<HTMLDivElement>(null);\n useOpened(scrimRef, motionRef, panelRef, onOpened);\n const [hasContent, setHasContent] = useState(false);\n const [headerAction, setHeaderAction] = useState(false);\n const { modalProps, underlayProps } = useModalOverlay(\n closedByOverlayOptions(\n closedBy ?? (role === 'alertdialog' ? 'closerequest' : 'any'),\n isExiting,\n ),\n state,\n panelRef,\n );\n const { dialogProps, titleProps, contentProps } = useDialog({ ...rest, role }, panelRef);\n // An alert dialog is described by its content; leave the reference out when there's none.\n const describedBy =\n rest['aria-describedby'] ?? (hasContent ? dialogProps['aria-describedby'] : undefined);\n\n // The spec: \"focus should automatically land on the first interactive element within the\n // dialog\", not on the panel `useDialog` focuses. A scrolling `DialogContent` is focusable\n // (so the keyboard can scroll it) but isn't a control. When that element is one of the\n // actions, focus goes to the least destructive one (`leastDestructiveAction`).\n useEffect(() => focusFirstInteractive(panelRef.current, leastDestructiveAction), [panelRef]);\n\n const hasIcon = fullScreen !== 'always' && icon !== undefined && icon !== null;\n const bottomBar = fullScreen !== 'never' && !headerAction;\n const origin = originBox !== null;\n const styles = dialogStyles({\n hasIcon,\n fullScreen,\n placement,\n bottomBar,\n origin,\n instant,\n transition,\n });\n const exiting = isExiting || undefined;\n\n return (\n <>\n <div\n ref={scrimRef}\n aria-hidden=\"true\"\n data-exiting={exiting}\n className={styles.scrim({ class: classNames?.scrim })}\n />\n <div style={{ ...style, ...DIALOG_ROOT_STYLE }} className={styles.root({ class: className })}>\n <div {...underlayProps} className={styles.positioner({ class: classNames?.positioner })}>\n <div\n {...exitProps}\n ref={motionRef}\n data-exiting={exiting}\n style={\n originBox\n ? ({\n '--m3-dialog-origin': originBox.clip,\n '--m3-dialog-origin-color': originBox.color,\n } as CSSProperties)\n : undefined\n }\n className={styles.motion()}\n >\n <div\n {...mergeProps(rest, triggerOverlayProps ?? {}, modalProps, dialogProps)}\n aria-describedby={describedBy}\n ref={panelRef}\n data-exiting={exiting}\n // The component tokens' hook (dialog-tokens.ts).\n data-tokens={mergeDataTokens('md-comp-dialog', rest)}\n data-full-screen={fullScreen === 'never' ? undefined : fullScreen}\n data-origin={origin || undefined}\n className={styles.panel({\n class: [transition === 'reveal' && styles.parts(), classNames?.panel],\n })}\n >\n <TriggerContext value={null}>\n <DialogPartsContext\n value={{\n titleProps,\n contentProps,\n hasIcon,\n fullScreen,\n close: state.close,\n setHasContent,\n setHeaderAction,\n bottomBar,\n }}\n >\n {hasIcon && (\n <span\n aria-hidden=\"true\"\n data-slot=\"icon\"\n className={styles.icon({\n class: cn(\n fullScreen === 'compact' && 'max-medium:hidden',\n classNames?.icon,\n ),\n })}\n >\n {icon}\n </span>\n )}\n {typeof children === 'function' ? children({ close: state.close }) : children}\n </DialogPartsContext>\n </TriggerContext>\n </div>\n </div>\n </div>\n </div>\n </>\n );\n}\n\nexport type DialogTitleProps = ComponentPropsWithRef<'h2'>;\n\n/**\n * The dialog's headline; it also names the dialog for assistive tech. In a dialog that can\n * be full screen, use `DialogHeader` instead.\n */\nexport function DialogTitle({ className, ...props }: DialogTitleProps) {\n const { titleProps, hasIcon } = useContext(DialogPartsContext);\n return (\n <h2\n {...mergeProps(titleProps, props)}\n data-slot=\"title\"\n className={dialogStyles({ hasIcon }).title({ class: className })}\n />\n );\n}\n\nexport interface DialogHeaderClassNames {\n /** The header row; `className` is merged here too. */\n root?: string;\n /** The close button. */\n close?: string;\n /** The headline. */\n title?: string;\n}\n\n/** Words a {@link DialogHeader} announces. */\nexport interface DialogHeaderLabels {\n /** The full-screen close button's name. @default \"Close\" */\n close: string;\n}\n\nconst DEFAULT_HEADER_LABELS: DialogHeaderLabels = { close: 'Close' };\n\nexport interface DialogHeaderProps extends Omit<ComponentPropsWithRef<'div'>, 'title'> {\n /** The headline; it names the dialog. */\n children: ReactNode;\n /**\n * Words the header announces, with English defaults. A {@link LabelsProvider} sets them for the\n * whole app.\n */\n labels?: Partial<DialogHeaderLabels>;\n /**\n * The confirming action, usually a text `Button` (\"Save\"). Shown in full screen only;\n * without it, a full-screen dialog shows its `DialogActions` as a bottom bar.\n */\n action?: ReactNode;\n /** A 1px `outline-variant` divider under the full-screen header. @default false */\n divider?: boolean;\n /**\n * Disables the full-screen close button, e.g. while the confirming action is saving, so the\n * dialog can't be left halfway. Pair it with the `Dialog`'s `closedBy=\"none\"` to keep Escape\n * from closing it too. @default false\n */\n closeDisabled?: boolean;\n /** Replaces the full-screen close button's icon. */\n closeIcon?: ReactElement;\n /** Classes for each part, by slot. */\n classNames?: DialogHeaderClassNames;\n}\n\n/**\n * The headline of a dialog that can be full screen. In full screen it is the M3 full-screen\n * dialog's header: a close icon button, the headline and the confirming action. Otherwise\n * it is a plain `DialogTitle`, and the dialog's `DialogActions` carry the actions.\n *\n * @example\n * <Dialog fullScreen=\"compact\">\n * <DialogHeader action={<Button variant=\"text\" onPress={save}>Save</Button>}>\n * Edit name\n * </DialogHeader>\n * <DialogContent>…</DialogContent>\n * <DialogActions>…</DialogActions>\n * </Dialog>\n */\nexport function DialogHeader({\n children,\n labels: labelsProp,\n action,\n divider = false,\n closeDisabled = false,\n closeIcon,\n className,\n classNames,\n ...props\n}: DialogHeaderProps) {\n const labels = useLabels('DialogHeader', DEFAULT_HEADER_LABELS, labelsProp);\n const { titleProps, hasIcon, fullScreen, close, setHeaderAction } =\n useContext(DialogPartsContext);\n const hasAction = action !== undefined;\n useLayoutEffect(() => {\n setHeaderAction(hasAction);\n return () => setHeaderAction(false);\n }, [hasAction, setHeaderAction]);\n const styles = dialogStyles({ hasIcon, fullScreen, divider });\n return (\n <div {...props} className={styles.header({ class: cn(classNames?.root, className) })}>\n {fullScreen !== 'never' && (\n <IconButton\n variant=\"standard\"\n density={0}\n icon={closeIcon ?? <CloseIcon />}\n aria-label={labels.close}\n disabled={closeDisabled}\n onPress={close}\n className={styles.headerButton({ class: cn(styles.close(), classNames?.close) })}\n />\n )}\n <h2 {...titleProps} data-slot=\"title\" className={styles.title({ class: classNames?.title })}>\n {children}\n </h2>\n {fullScreen !== 'never' && action !== undefined && (\n <div className={styles.headerButton()}>\n <DensityZeroButtons>{action}</DensityZeroButtons>\n </div>\n )}\n </div>\n );\n}\n\nexport interface DialogContentProps extends ComponentPropsWithRef<'div'> {\n /**\n * The basic dialog's optional dividers: 1px `outline-variant` lines across the panel\n * above and below the content, e.g. around a scrolling list. Not drawn in full screen.\n * @default false\n */\n dividers?: boolean;\n}\n\n/**\n * Whether an element's content is taller than its box, from a `ResizeObserver` on it and\n * its children (so it is measured only when a size changes).\n */\nfunction useOverflows(ref: RefObject<HTMLElement | null>) {\n const [overflows, setOverflows] = useState(false);\n useLayoutEffect(() => {\n const element = ref.current;\n if (!element || typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver(() =>\n setOverflows(element.scrollHeight > element.clientHeight + 1),\n );\n observer.observe(element);\n for (const child of element.children) observer.observe(child);\n return () => observer.disconnect();\n }, [ref]);\n return overflows;\n}\n\n/**\n * The dialog's supporting text or content; scrolls when the dialog is too tall. It describes\n * an alert dialog. While it scrolls it is focusable (with a focus ring), so the keyboard can\n * scroll it (`data-scrollable`); the dialog's initial focus still goes to its first control.\n */\nexport function DialogContent({ className, dividers = false, ref, ...props }: DialogContentProps) {\n const { fullScreen, contentProps, setHasContent } = useContext(DialogPartsContext);\n const contentRef = useObjectRef(ref as Ref<HTMLDivElement>);\n const overflows = useOverflows(contentRef);\n const { isFocusVisible, focusProps } = useFocusRing();\n useLayoutEffect(() => {\n setHasContent(true);\n return () => setHasContent(false);\n }, [setHasContent]);\n return (\n <div\n {...mergeProps(contentProps, overflows ? focusProps : {}, props)}\n ref={contentRef}\n tabIndex={props.tabIndex ?? (overflows ? 0 : undefined)}\n data-scrollable={overflows || undefined}\n data-focus-visible={(overflows && isFocusVisible) || undefined}\n data-slot=\"content\"\n className={dialogStyles({ fullScreen, dividers }).content({ class: className })}\n />\n );\n}\n\nexport type DialogActionsProps = ComponentPropsWithRef<'div'>;\n\n/** The elements among `children`, counting a fragment's children rather than the fragment. */\nfunction countActions(children: ReactNode): number {\n let count = 0;\n Children.forEach(children, (child) => {\n if (!isValidElement<{ children?: ReactNode }>(child)) return;\n count += child.type === Fragment ? countActions(child.props.children) : 1;\n });\n return count;\n}\n\n/** `children` as a flat list, with fragments' children in place of the fragments. */\nfunction flatChildren(children: ReactNode): ReactNode[] {\n return Children.toArray(children).flatMap((child) =>\n isValidElement<{ children?: ReactNode }>(child) && child.type === Fragment\n ? flatChildren(child.props.children)\n : [child],\n );\n}\n\n/**\n * Where a dialog's initial focus goes when its first interactive element is one of its\n * actions: the dismissive, least destructive action (the WAI-ARIA alert dialog pattern:\n * \"focus the least destructive action\"). That's the first action in source order, which is\n * the row's first button on one line and its last when stacked, since stacked actions are\n * rendered with the confirming action on top. The row says which (`data-stacked`) in the\n * same render that reorders it, so the rule holds whenever the dialog takes focus, before\n * or after the actions stack.\n */\nexport function leastDestructiveAction(first: HTMLElement): HTMLElement {\n const row = first.closest<HTMLElement>('[data-slot=\"actions\"]');\n if (!row) return first;\n const actions = tabbables(row);\n return (row.hasAttribute('data-stacked') ? actions.at(-1) : actions[0]) ?? first;\n}\n\n/**\n * Whether the actions are stacked: they don't fit on one line, and the layout lets them\n * stack (`--m3-dialog-actions-stack`, off in the full-screen bottom bar). Measured on mount\n * and on every resize of the row or a button. Switching reorders the buttons, and moving\n * the focused one drops focus to the page, so it's focused again.\n */\nfunction useStackedActions(ref: RefObject<HTMLDivElement | null>) {\n const [stacked, setStacked] = useState(false);\n const current = useRef(false);\n // The button with focus, kept until focus moves somewhere else.\n const focused = useRef<HTMLElement | null>(null);\n useLayoutEffect(() => {\n const row = ref.current;\n if (!row) return;\n const measure = () => {\n // A hidden row (the header carries the confirming action) keeps its order.\n if (row.clientWidth === 0) return;\n const style = getComputedStyle(row);\n const items = [...row.children] as HTMLElement[];\n const gap = parseFloat(style.columnGap) || 0;\n const room =\n row.clientWidth -\n (parseFloat(style.paddingLeft) || 0) -\n (parseFloat(style.paddingRight) || 0);\n const needed =\n items.reduce((sum, item) => sum + item.offsetWidth, 0) + gap * (items.length - 1);\n const next =\n style.getPropertyValue('--m3-dialog-actions-stack').trim() === '1' &&\n items.length > 1 &&\n needed > room + 0.5;\n if (next === current.current) return;\n current.current = next;\n setStacked(next);\n };\n const onFocusIn = (event: FocusEvent) => {\n if (event.target instanceof HTMLElement) focused.current = event.target;\n };\n const onFocusOut = (event: FocusEvent) => {\n if (event.relatedTarget !== null) focused.current = null;\n };\n row.addEventListener('focusin', onFocusIn);\n row.addEventListener('focusout', onFocusOut);\n const observer = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(measure);\n observer?.observe(row);\n for (const item of row.children) observer?.observe(item);\n measure();\n return () => {\n row.removeEventListener('focusin', onFocusIn);\n row.removeEventListener('focusout', onFocusOut);\n observer?.disconnect();\n };\n }, [ref]);\n useLayoutEffect(() => {\n const element = focused.current;\n const active = element?.ownerDocument.activeElement;\n if (element?.isConnected && (!active || active === element.ownerDocument.body)) element.focus();\n }, [stacked]);\n return stacked;\n}\n\n/**\n * Row of actions at the end of the dialog, 8px apart. Put the confirming action last:\n * when the actions do not fit on one line they stack with it on top (M3: \"Confirming\n * actions appear above dismissive actions\"). Stacked, the buttons are rendered in that\n * visual order, so Tab and screen readers reach the confirming action first, top to bottom,\n * as they reach the dismissive one first in a row. When the actions are the dialog's first\n * controls, its initial focus goes to the dismissive action, stacked (the bottom one) or\n * not: the least destructive choice, as the WAI-ARIA alert dialog pattern asks; a stack\n * that forms or unforms later keeps focus where it is. While the dialog is full screen they're\n * hidden when `DialogHeader` carries the confirming action, and otherwise form the spec's\n * 56px bottom action bar. Use at most two actions (the spec); in development it warns once\n * when it holds more.\n */\nexport function DialogActions({ className, children, ref, ...props }: DialogActionsProps) {\n const { fullScreen, bottomBar } = useContext(DialogPartsContext);\n const actionCount = countActions(children);\n useWarnOnce(\n actionCount > 2,\n `[@windmorph/react] DialogActions holds ${actionCount} actions; M3 dialogs have at most two ` +\n '(no third such as \"Learn more\"). Show more inline, or before the dialog opens.',\n );\n const rowRef = useObjectRef(ref);\n const stacked = useStackedActions(rowRef);\n return (\n <div\n {...props}\n ref={rowRef}\n data-slot=\"actions\"\n data-stacked={stacked || undefined}\n className={dialogStyles({ fullScreen, bottomBar }).actions({ class: className })}\n >\n {/* The same keys either way, so switching moves the buttons rather than remounting them. */}\n {stacked ? flatChildren(children).reverse() : flatChildren(children)}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Density } from "../../tokens/density.js";
|
|
2
|
-
import { ButtonBaseActionProps, ButtonBaseLinkProps, ButtonBaseToggleProps } from "../../primitives/ButtonBase.js";
|
|
3
2
|
import { ButtonShape, ButtonSize } from "../button/button-styles.js";
|
|
4
3
|
import { ButtonLabels } from "../button/button-pending.js";
|
|
4
|
+
import { ButtonBaseActionProps, ButtonBaseLinkProps, ButtonBaseToggleProps } from "../../primitives/ButtonBase.js";
|
|
5
5
|
import { IconButtonVariant, IconButtonWidth } from "./icon-button-styles.js";
|
|
6
6
|
import { IconTransition } from "../../primitives/icon-faces-styles.js";
|
|
7
7
|
import { ReactElement } from "react";
|
|
@@ -65,7 +65,9 @@ interface IconButtonOwnProps {
|
|
|
65
65
|
tooltip?: boolean;
|
|
66
66
|
/** Classes for each part. `classNames.root` is merged with `className`. */
|
|
67
67
|
classNames?: IconButtonClassNames;
|
|
68
|
-
/**
|
|
68
|
+
/**
|
|
69
|
+
* Words the button announces. A {@link LabelsProvider} sets them for the whole app.
|
|
70
|
+
*/
|
|
69
71
|
labels?: Partial<ButtonLabels>;
|
|
70
72
|
}
|
|
71
73
|
interface IconButtonPendingProps {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IconButton.js","names":[],"sources":["../../../src/components/icon-button/IconButton.tsx"],"sourcesContent":["'use client';\n\nimport { useContext, type ReactElement, type Ref } from 'react';\nimport {\n ButtonBase,\n type ButtonBaseActionProps,\n type ButtonBaseLinkProps,\n type ButtonBaseProps,\n type ButtonBaseToggleProps,\n} from '../../primitives/ButtonBase';\nimport { IconFaces } from '../../primitives/IconFaces';\nimport { iconFacesStyles, type IconTransition } from '../../primitives/icon-faces-styles';\nimport { TouchTarget } from '../../primitives/TouchTarget';\nimport { densityAttribute, type Density } from '../../tokens/density';\nimport { cn } from '../../utils/cn';\nimport { useWarnOnce } from '../../utils/warn-once';\nimport type { ButtonShape, ButtonSize } from '../button/button-styles';\nimport { ButtonContext } from '../button/ButtonContext';\nimport { ToolbarContext } from '../../primitives/ToolbarContext';\nimport { LazyTooltip } from '../tooltip/lazy-tooltip';\nimport {\n TooltipTrigger,\n useInTooltipTrigger,\n WithoutDescription,\n} from '../tooltip/tooltip-trigger';\nimport {\n iconButtonStyles,\n type IconButtonVariant,\n type IconButtonWidth,\n} from './icon-button-styles';\nimport { mergeDataTokens } from '../../utils/merge-data-tokens';\nimport { pendingClasses, useButtonPending, type ButtonLabels } from '../button/button-pending';\n\nexport interface IconButtonClassNames {\n /** The button element itself; merged with `className`. */\n root?: string;\n /** Inner wrapper around the icon. */\n content?: string;\n /** The icon. */\n icon?: string;\n /** The built-in plain tooltip that shows the label (`tooltip`). */\n tooltip?: string;\n /** The slot holding the loading indicator while `pending`, centred over the hidden icon. */\n pendingIndicator?: string;\n}\n\n/** Icon buttons have no visible text, so an accessible name is required. */\ntype AccessibleName =\n | {\n /** The button's accessible name, also shown in its tooltip. Required without `aria-labelledby`. */\n 'aria-label': string;\n /** The id of an element that names the button, instead of `aria-label`. */\n 'aria-labelledby'?: string;\n }\n | {\n /** The button's accessible name, also shown in its tooltip. Required without `aria-labelledby`. */\n 'aria-label'?: string;\n /** The id of an element that names the button, instead of `aria-label`. */\n 'aria-labelledby': string;\n };\n\ninterface IconButtonOwnProps {\n /**\n * Colour style. Filled is the M3 default; toolbars and app bars default their icon\n * buttons to `standard` through `ButtonContext`.\n * @default \"filled\"\n */\n variant?: IconButtonVariant;\n /** Container and icon size, from extra small to extra large. @default \"sm\" */\n size?: ButtonSize;\n /** Container width: narrow, default or wide, at the same height. @default \"default\" */\n width?: IconButtonWidth;\n /** Fully round corners, or square ones (still rounded, but less). @default \"round\" */\n shape?: ButtonShape;\n /**\n * Web density, `0` to `-3`: each step takes 4px off the container's height and width (an\n * S button: 40px at 0, 28px at -3), never below the icon plus 4px; the touch target stays\n * 48 × 48px. Left out, the button takes the density of the nearest `ThemeProvider` or\n * `ThemeScope`; icon buttons in a field's icon slots, and in app bars, toolbars, search bars,\n * a navigation rail's header and dialog and sheet headers, keep their size. M3 asks for density\n * to be the reader's choice rather than a default, so prefer setting it there.\n * @default 0\n */\n density?: Density;\n /** The icon, e.g. a Material Symbols SVG. Hidden from assistive tech; `aria-label` names the button. */\n icon: ReactElement;\n /**\n * Shows the `aria-label` in a plain tooltip on hover and keyboard focus, as M3 asks of\n * icon buttons on the web. The tooltip only repeats the name, so it isn't linked as a\n * description (no double announcement). It steps aside when the button is already inside a\n * `TooltipTrigger` or `RichTooltipTrigger`, and when it's named by `aria-labelledby`.\n * @default true\n */\n tooltip?: boolean;\n /** Classes for each part. `classNames.root` is merged with `className`. */\n classNames?: IconButtonClassNames;\n /** Words the button announces. */\n labels?: Partial<ButtonLabels>;\n}\n\ninterface IconButtonPendingProps {\n /**\n * Shows a loading indicator in place of the icon while an action runs, and blocks presses.\n * The button keeps its size, its colours and keyboard focus; its accessible name gains\n * `labels.pending` (the tooltip keeps showing only the `aria-label`), and a focused button\n * announces it. The indicator appears after 200ms, so a quick action doesn't flash. Use it\n * for actions that take a moment, not for every button in a list.\n * @default false\n */\n pending?: boolean;\n /**\n * Replaces the loading indicator shown while `pending`, e.g. a `CircularProgressIndicator`\n * for a wait that may pass five seconds. It's centred over the icon and takes the content\n * colour; a progress indicator also loses its track, as M3 asks inside a button.\n */\n pendingIndicator?: ReactElement;\n}\n\ntype OmitOwn<T> = Omit<\n T,\n | keyof IconButtonOwnProps\n | keyof IconButtonPendingProps\n | 'children'\n | 'aria-label'\n | 'aria-labelledby'\n>;\n\nexport type IconButtonActionProps = IconButtonOwnProps &\n IconButtonPendingProps &\n OmitOwn<ButtonBaseActionProps> &\n AccessibleName;\nexport type IconButtonLinkProps = IconButtonOwnProps &\n IconButtonPendingProps &\n OmitOwn<ButtonBaseLinkProps> &\n AccessibleName;\nexport type IconButtonToggleProps = IconButtonOwnProps &\n OmitOwn<ButtonBaseToggleProps> &\n AccessibleName & {\n /** Toggle buttons have no pending state. */\n pending?: never;\n /** Toggle buttons have no pending state. */\n pendingIndicator?: never;\n /** Icon shown while selected, e.g. the filled form of `icon`. */\n selectedIcon?: ReactElement;\n /**\n * How `icon` hands over to `selectedIcon`: `swap` changes it at once, `rotate` turns and\n * crossfades the two (counter-clockwise as it selects, in LTR). `rotate` is an extension\n * beyond the M3 spec, matching Google's Expressive showcase, like the FAB menu's.\n * Reduced motion keeps only the crossfade. Needs `selectedIcon`.\n * @default \"swap\"\n */\n iconTransition?: IconTransition;\n };\n\nexport type IconButtonProps = IconButtonActionProps | IconButtonLinkProps | IconButtonToggleProps;\n\n/**\n * M3 Expressive icon button: filled (default), tonal, outlined and standard variants;\n * XS–XL sizes; narrow, default or wide widths; round or square shape; an optional toggle\n * form; a press morph; and its label as a tooltip on hover and keyboard focus.\n *\n * Shares `size`, `shape` and `disabled` with `ButtonContext`, so it fits in button groups,\n * and takes its default variant from it (`iconButtonVariant`).\n *\n * To animate its size or position, put the transition on a wrapper: while pressed, the button's\n * own motion replaces the timing of a transition added through `className`.\n *\n * @example\n * <IconButton icon={<SearchIcon />} aria-label=\"Search\" onPress={openSearch} />\n * <IconButton variant=\"standard\" icon={<MoreIcon />} aria-label=\"More options\" />\n * <IconButton toggle variant=\"filled\" icon={<StarIcon />} selectedIcon={<StarFilledIcon />}\n * aria-label=\"Favourite\" selected={isFavourite} onSelectedChange={setFavourite} />\n * <IconButton icon={<RefreshIcon />} aria-label=\"Refresh\" pending={refreshing} onPress={refresh} />\n */\nexport function IconButton(props: IconButtonProps) {\n const {\n variant: variantProp,\n size: sizeProp,\n width = 'default',\n shape: shapeProp,\n density: densityProp,\n disabled: disabledProp,\n icon,\n tooltip = true,\n pendingIndicator,\n labels,\n classNames,\n className,\n // Only toggle props carry `selectedIcon`; the others read it as undefined.\n selectedIcon,\n iconTransition = 'swap',\n ...base\n } = props as Omit<IconButtonToggleProps, 'pendingIndicator'> &\n IconButtonProps & { pendingIndicator?: ReactElement };\n const rotates = iconTransition === 'rotate' && selectedIcon !== undefined;\n\n const context = useContext(ButtonContext);\n const toolbar = useContext(ToolbarContext);\n const inTooltipTrigger = useInTooltipTrigger();\n const size = sizeProp ?? context.size ?? 'sm';\n const shape = shapeProp ?? context.shape ?? 'round';\n const squareInFloatingToolbar = toolbar?.kind === 'floating' && shape === 'square';\n // Once per button, not on every render.\n useWarnOnce(\n squareInFloatingToolbar,\n '[@windmorph/react] Avoid square icon buttons in a floating toolbar (M3): their shape conflicts with its fully rounded container.',\n );\n useWarnOnce(\n toolbar?.orientation === 'vertical' && width === 'wide',\n '[@windmorph/react] Avoid wide icon buttons in a vertical toolbar (M3): use the default or narrow width.',\n );\n const variant = variantProp ?? context.iconButtonVariant ?? 'filled';\n const density = densityProp ?? context.density;\n const styles = iconButtonStyles({\n variant,\n size,\n width,\n shape,\n toggle: base.toggle === true,\n connected: context.connected ?? 'none',\n toolbar: toolbar?.color ?? 'none',\n });\n\n // Destructuring a union merges its members (see Button); `base` holds one member's props.\n // The component tokens' hooks (icon-button-tokens.ts), also public styling hooks for apps.\n const tokenAttributes = {\n 'data-tokens': mergeDataTokens('md-comp-icon-button', base),\n 'data-variant': variant,\n 'data-size': size,\n // Read by a button group, which spaces narrow icon buttons wider apart.\n 'data-width': width,\n };\n const pending = useButtonPending<HTMLElement>({\n pending: base.pending,\n pendingIndicator,\n labels,\n size,\n ariaLabel: base['aria-label'],\n ariaLabelledby: base['aria-labelledby'],\n ref: base.ref as Ref<HTMLElement> | undefined,\n indicatorClassName: cn(pendingClasses.indicator, classNames?.pendingIndicator),\n });\n const baseProps = {\n ...base,\n ...tokenAttributes,\n ...pending.rootProps,\n ...densityAttribute(density),\n disabled: disabledProp ?? context.disabled ?? false,\n className: styles.root({ class: cn(classNames?.root, className) }),\n } as ButtonBaseProps;\n\n const button = (\n <ButtonBase {...baseProps}>\n {({ isSelected }) => (\n <span\n {...pending.contentProps}\n className={styles.content({\n class: cn(pending.takesPending && pendingClasses.content, classNames?.content),\n })}\n >\n {(size === 'xs' || size === 'sm') && <TouchTarget />}\n <span\n aria-hidden=\"true\"\n className={styles.icon({\n class: cn(\n rotates && iconFacesStyles().cell(),\n pending.takesPending && pendingClasses.replaced,\n classNames?.icon,\n ),\n })}\n >\n {rotates ? (\n <IconFaces\n icon={icon}\n alternateIcon={selectedIcon}\n alternate={isSelected}\n transition=\"rotate\"\n />\n ) : isSelected && selectedIcon ? (\n selectedIcon\n ) : (\n icon\n )}\n </span>\n {pending.indicator}\n {pending.hiddenName}\n </span>\n )}\n </ButtonBase>\n );\n\n const label = base['aria-label'];\n if (!tooltip || inTooltipTrigger || !label || base['aria-labelledby']) return button;\n return (\n <TooltipTrigger>\n <WithoutDescription>{button}</WithoutDescription>\n <LazyTooltip classNames={{ panel: classNames?.tooltip }}>{label}</LazyTooltip>\n </TooltipTrigger>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8KA,SAAgB,WAAW,OAAwB;CACjD,MAAM,EACJ,SAAS,aACT,MAAM,UACN,QAAQ,WACR,OAAO,WACP,SAAS,aACT,UAAU,cACV,MACA,UAAU,MACV,kBACA,QACA,YACA,WAEA,cACA,iBAAiB,QACjB,GAAG,SACD;CAEJ,MAAM,UAAU,mBAAmB,YAAY,iBAAiB,KAAA;CAEhE,MAAM,UAAU,WAAW,aAAa;CACxC,MAAM,UAAU,WAAW,cAAc;CACzC,MAAM,mBAAmB,oBAAoB;CAC7C,MAAM,OAAO,YAAY,QAAQ,QAAQ;CACzC,MAAM,QAAQ,aAAa,QAAQ,SAAS;CAC5C,MAAM,0BAA0B,SAAS,SAAS,cAAc,UAAU;CAE1E,YACE,yBACA,kIACF;CACA,YACE,SAAS,gBAAgB,cAAc,UAAU,QACjD,yGACF;CACA,MAAM,UAAU,eAAe,QAAQ,qBAAqB;CAC5D,MAAM,UAAU,eAAe,QAAQ;CACvC,MAAM,SAAS,iBAAiB;EAC9B;EACA;EACA;EACA;EACA,QAAQ,KAAK,WAAW;EACxB,WAAW,QAAQ,aAAa;EAChC,SAAS,SAAS,SAAS;CAC7B,CAAC;CAID,MAAM,kBAAkB;EACtB,eAAe,gBAAgB,uBAAuB,IAAI;EAC1D,gBAAgB;EAChB,aAAa;EAEb,cAAc;CAChB;CACA,MAAM,UAAU,iBAA8B;EAC5C,SAAS,KAAK;EACd;EACA;EACA;EACA,WAAW,KAAK;EAChB,gBAAgB,KAAK;EACrB,KAAK,KAAK;EACV,oBAAoB,GAAG,eAAe,WAAW,YAAY,gBAAgB;CAC/E,CAAC;CACD,MAAM,YAAY;EAChB,GAAG;EACH,GAAG;EACH,GAAG,QAAQ;EACX,GAAG,iBAAiB,OAAO;EAC3B,UAAU,gBAAgB,QAAQ,YAAY;EAC9C,WAAW,OAAO,KAAK,EAAE,OAAO,GAAG,YAAY,MAAM,SAAS,EAAE,CAAC;CACnE;CAEA,MAAM,SACJ,oBAAC,YAAD;EAAY,GAAI;EACZ,WAAA,EAAE,iBACF,qBAAC,QAAD;GACE,GAAI,QAAQ;GACZ,WAAW,OAAO,QAAQ,EACxB,OAAO,GAAG,QAAQ,gBAAgB,eAAe,SAAS,YAAY,OAAO,EAC/E,CAAC;GAJH,UAAA;KAMI,SAAS,QAAQ,SAAS,SAAS,oBAAC,aAAD,CAAc,CAAA;IACnD,oBAAC,QAAD;KACE,eAAY;KACZ,WAAW,OAAO,KAAK,EACrB,OAAO,GACL,WAAW,gBAAgB,CAAC,CAAC,KAAK,GAClC,QAAQ,gBAAgB,eAAe,UACvC,YAAY,IACd,EACF,CAAC;KAEA,UAAA,UACC,oBAAC,WAAD;MACQ;MACN,eAAe;MACf,WAAW;MACX,YAAW;KACZ,CAAA,IACC,cAAc,eAChB,eAEA;IAEE,CAAA;IACL,QAAQ;IACR,QAAQ;GACL;;CAEE,CAAA;CAGd,MAAM,QAAQ,KAAK;CACnB,IAAI,CAAC,WAAW,oBAAoB,CAAC,SAAS,KAAK,oBAAoB,OAAO;CAC9E,OACE,qBAAC,gBAAD,EAAA,UAAA,CACE,oBAAC,oBAAD,EAAA,UAAqB,OAA2B,CAAA,GAChD,oBAAC,aAAD;EAAa,YAAY,EAAE,OAAO,YAAY,QAAQ;EAAI,UAAA;CAAmB,CAAA,CAC/D,EAAA,CAAA;AAEpB"}
|
|
1
|
+
{"version":3,"file":"IconButton.js","names":[],"sources":["../../../src/components/icon-button/IconButton.tsx"],"sourcesContent":["'use client';\n\nimport { useContext, type ReactElement, type Ref } from 'react';\nimport {\n ButtonBase,\n type ButtonBaseActionProps,\n type ButtonBaseLinkProps,\n type ButtonBaseProps,\n type ButtonBaseToggleProps,\n} from '../../primitives/ButtonBase';\nimport { IconFaces } from '../../primitives/IconFaces';\nimport { iconFacesStyles, type IconTransition } from '../../primitives/icon-faces-styles';\nimport { TouchTarget } from '../../primitives/TouchTarget';\nimport { densityAttribute, type Density } from '../../tokens/density';\nimport { cn } from '../../utils/cn';\nimport { useWarnOnce } from '../../utils/warn-once';\nimport type { ButtonShape, ButtonSize } from '../button/button-styles';\nimport { ButtonContext } from '../button/ButtonContext';\nimport { ToolbarContext } from '../../primitives/ToolbarContext';\nimport { LazyTooltip } from '../tooltip/lazy-tooltip';\nimport {\n TooltipTrigger,\n useInTooltipTrigger,\n WithoutDescription,\n} from '../tooltip/tooltip-trigger';\nimport {\n iconButtonStyles,\n type IconButtonVariant,\n type IconButtonWidth,\n} from './icon-button-styles';\nimport { mergeDataTokens } from '../../utils/merge-data-tokens';\nimport { pendingClasses, useButtonPending, type ButtonLabels } from '../button/button-pending';\n\nexport interface IconButtonClassNames {\n /** The button element itself; merged with `className`. */\n root?: string;\n /** Inner wrapper around the icon. */\n content?: string;\n /** The icon. */\n icon?: string;\n /** The built-in plain tooltip that shows the label (`tooltip`). */\n tooltip?: string;\n /** The slot holding the loading indicator while `pending`, centred over the hidden icon. */\n pendingIndicator?: string;\n}\n\n/** Icon buttons have no visible text, so an accessible name is required. */\ntype AccessibleName =\n | {\n /** The button's accessible name, also shown in its tooltip. Required without `aria-labelledby`. */\n 'aria-label': string;\n /** The id of an element that names the button, instead of `aria-label`. */\n 'aria-labelledby'?: string;\n }\n | {\n /** The button's accessible name, also shown in its tooltip. Required without `aria-labelledby`. */\n 'aria-label'?: string;\n /** The id of an element that names the button, instead of `aria-label`. */\n 'aria-labelledby': string;\n };\n\ninterface IconButtonOwnProps {\n /**\n * Colour style. Filled is the M3 default; toolbars and app bars default their icon\n * buttons to `standard` through `ButtonContext`.\n * @default \"filled\"\n */\n variant?: IconButtonVariant;\n /** Container and icon size, from extra small to extra large. @default \"sm\" */\n size?: ButtonSize;\n /** Container width: narrow, default or wide, at the same height. @default \"default\" */\n width?: IconButtonWidth;\n /** Fully round corners, or square ones (still rounded, but less). @default \"round\" */\n shape?: ButtonShape;\n /**\n * Web density, `0` to `-3`: each step takes 4px off the container's height and width (an\n * S button: 40px at 0, 28px at -3), never below the icon plus 4px; the touch target stays\n * 48 × 48px. Left out, the button takes the density of the nearest `ThemeProvider` or\n * `ThemeScope`; icon buttons in a field's icon slots, and in app bars, toolbars, search bars,\n * a navigation rail's header and dialog and sheet headers, keep their size. M3 asks for density\n * to be the reader's choice rather than a default, so prefer setting it there.\n * @default 0\n */\n density?: Density;\n /** The icon, e.g. a Material Symbols SVG. Hidden from assistive tech; `aria-label` names the button. */\n icon: ReactElement;\n /**\n * Shows the `aria-label` in a plain tooltip on hover and keyboard focus, as M3 asks of\n * icon buttons on the web. The tooltip only repeats the name, so it isn't linked as a\n * description (no double announcement). It steps aside when the button is already inside a\n * `TooltipTrigger` or `RichTooltipTrigger`, and when it's named by `aria-labelledby`.\n * @default true\n */\n tooltip?: boolean;\n /** Classes for each part. `classNames.root` is merged with `className`. */\n classNames?: IconButtonClassNames;\n /**\n * Words the button announces. A {@link LabelsProvider} sets them for the whole app.\n */\n labels?: Partial<ButtonLabels>;\n}\n\ninterface IconButtonPendingProps {\n /**\n * Shows a loading indicator in place of the icon while an action runs, and blocks presses.\n * The button keeps its size, its colours and keyboard focus; its accessible name gains\n * `labels.pending` (the tooltip keeps showing only the `aria-label`), and a focused button\n * announces it. The indicator appears after 200ms, so a quick action doesn't flash. Use it\n * for actions that take a moment, not for every button in a list.\n * @default false\n */\n pending?: boolean;\n /**\n * Replaces the loading indicator shown while `pending`, e.g. a `CircularProgressIndicator`\n * for a wait that may pass five seconds. It's centred over the icon and takes the content\n * colour; a progress indicator also loses its track, as M3 asks inside a button.\n */\n pendingIndicator?: ReactElement;\n}\n\ntype OmitOwn<T> = Omit<\n T,\n | keyof IconButtonOwnProps\n | keyof IconButtonPendingProps\n | 'children'\n | 'aria-label'\n | 'aria-labelledby'\n>;\n\nexport type IconButtonActionProps = IconButtonOwnProps &\n IconButtonPendingProps &\n OmitOwn<ButtonBaseActionProps> &\n AccessibleName;\nexport type IconButtonLinkProps = IconButtonOwnProps &\n IconButtonPendingProps &\n OmitOwn<ButtonBaseLinkProps> &\n AccessibleName;\nexport type IconButtonToggleProps = IconButtonOwnProps &\n OmitOwn<ButtonBaseToggleProps> &\n AccessibleName & {\n /** Toggle buttons have no pending state. */\n pending?: never;\n /** Toggle buttons have no pending state. */\n pendingIndicator?: never;\n /** Icon shown while selected, e.g. the filled form of `icon`. */\n selectedIcon?: ReactElement;\n /**\n * How `icon` hands over to `selectedIcon`: `swap` changes it at once, `rotate` turns and\n * crossfades the two (counter-clockwise as it selects, in LTR). `rotate` is an extension\n * beyond the M3 spec, matching Google's Expressive showcase, like the FAB menu's.\n * Reduced motion keeps only the crossfade. Needs `selectedIcon`.\n * @default \"swap\"\n */\n iconTransition?: IconTransition;\n };\n\nexport type IconButtonProps = IconButtonActionProps | IconButtonLinkProps | IconButtonToggleProps;\n\n/**\n * M3 Expressive icon button: filled (default), tonal, outlined and standard variants;\n * XS–XL sizes; narrow, default or wide widths; round or square shape; an optional toggle\n * form; a press morph; and its label as a tooltip on hover and keyboard focus.\n *\n * Shares `size`, `shape` and `disabled` with `ButtonContext`, so it fits in button groups,\n * and takes its default variant from it (`iconButtonVariant`).\n *\n * To animate its size or position, put the transition on a wrapper: while pressed, the button's\n * own motion replaces the timing of a transition added through `className`.\n *\n * @example\n * <IconButton icon={<SearchIcon />} aria-label=\"Search\" onPress={openSearch} />\n * <IconButton variant=\"standard\" icon={<MoreIcon />} aria-label=\"More options\" />\n * <IconButton toggle variant=\"filled\" icon={<StarIcon />} selectedIcon={<StarFilledIcon />}\n * aria-label=\"Favourite\" selected={isFavourite} onSelectedChange={setFavourite} />\n * <IconButton icon={<RefreshIcon />} aria-label=\"Refresh\" pending={refreshing} onPress={refresh} />\n */\nexport function IconButton(props: IconButtonProps) {\n const {\n variant: variantProp,\n size: sizeProp,\n width = 'default',\n shape: shapeProp,\n density: densityProp,\n disabled: disabledProp,\n icon,\n tooltip = true,\n pendingIndicator,\n labels,\n classNames,\n className,\n // Only toggle props carry `selectedIcon`; the others read it as undefined.\n selectedIcon,\n iconTransition = 'swap',\n ...base\n } = props as Omit<IconButtonToggleProps, 'pendingIndicator'> &\n IconButtonProps & { pendingIndicator?: ReactElement };\n const rotates = iconTransition === 'rotate' && selectedIcon !== undefined;\n\n const context = useContext(ButtonContext);\n const toolbar = useContext(ToolbarContext);\n const inTooltipTrigger = useInTooltipTrigger();\n const size = sizeProp ?? context.size ?? 'sm';\n const shape = shapeProp ?? context.shape ?? 'round';\n const squareInFloatingToolbar = toolbar?.kind === 'floating' && shape === 'square';\n // Once per button, not on every render.\n useWarnOnce(\n squareInFloatingToolbar,\n '[@windmorph/react] Avoid square icon buttons in a floating toolbar (M3): their shape conflicts with its fully rounded container.',\n );\n useWarnOnce(\n toolbar?.orientation === 'vertical' && width === 'wide',\n '[@windmorph/react] Avoid wide icon buttons in a vertical toolbar (M3): use the default or narrow width.',\n );\n const variant = variantProp ?? context.iconButtonVariant ?? 'filled';\n const density = densityProp ?? context.density;\n const styles = iconButtonStyles({\n variant,\n size,\n width,\n shape,\n toggle: base.toggle === true,\n connected: context.connected ?? 'none',\n toolbar: toolbar?.color ?? 'none',\n });\n\n // Destructuring a union merges its members (see Button); `base` holds one member's props.\n // The component tokens' hooks (icon-button-tokens.ts), also public styling hooks for apps.\n const tokenAttributes = {\n 'data-tokens': mergeDataTokens('md-comp-icon-button', base),\n 'data-variant': variant,\n 'data-size': size,\n // Read by a button group, which spaces narrow icon buttons wider apart.\n 'data-width': width,\n };\n const pending = useButtonPending<HTMLElement>({\n pending: base.pending,\n pendingIndicator,\n labels,\n size,\n ariaLabel: base['aria-label'],\n ariaLabelledby: base['aria-labelledby'],\n ref: base.ref as Ref<HTMLElement> | undefined,\n indicatorClassName: cn(pendingClasses.indicator, classNames?.pendingIndicator),\n });\n const baseProps = {\n ...base,\n ...tokenAttributes,\n ...pending.rootProps,\n ...densityAttribute(density),\n disabled: disabledProp ?? context.disabled ?? false,\n className: styles.root({ class: cn(classNames?.root, className) }),\n } as ButtonBaseProps;\n\n const button = (\n <ButtonBase {...baseProps}>\n {({ isSelected }) => (\n <span\n {...pending.contentProps}\n className={styles.content({\n class: cn(pending.takesPending && pendingClasses.content, classNames?.content),\n })}\n >\n {(size === 'xs' || size === 'sm') && <TouchTarget />}\n <span\n aria-hidden=\"true\"\n className={styles.icon({\n class: cn(\n rotates && iconFacesStyles().cell(),\n pending.takesPending && pendingClasses.replaced,\n classNames?.icon,\n ),\n })}\n >\n {rotates ? (\n <IconFaces\n icon={icon}\n alternateIcon={selectedIcon}\n alternate={isSelected}\n transition=\"rotate\"\n />\n ) : isSelected && selectedIcon ? (\n selectedIcon\n ) : (\n icon\n )}\n </span>\n {pending.indicator}\n {pending.hiddenName}\n </span>\n )}\n </ButtonBase>\n );\n\n const label = base['aria-label'];\n if (!tooltip || inTooltipTrigger || !label || base['aria-labelledby']) return button;\n return (\n <TooltipTrigger>\n <WithoutDescription>{button}</WithoutDescription>\n <LazyTooltip classNames={{ panel: classNames?.tooltip }}>{label}</LazyTooltip>\n </TooltipTrigger>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgLA,SAAgB,WAAW,OAAwB;CACjD,MAAM,EACJ,SAAS,aACT,MAAM,UACN,QAAQ,WACR,OAAO,WACP,SAAS,aACT,UAAU,cACV,MACA,UAAU,MACV,kBACA,QACA,YACA,WAEA,cACA,iBAAiB,QACjB,GAAG,SACD;CAEJ,MAAM,UAAU,mBAAmB,YAAY,iBAAiB,KAAA;CAEhE,MAAM,UAAU,WAAW,aAAa;CACxC,MAAM,UAAU,WAAW,cAAc;CACzC,MAAM,mBAAmB,oBAAoB;CAC7C,MAAM,OAAO,YAAY,QAAQ,QAAQ;CACzC,MAAM,QAAQ,aAAa,QAAQ,SAAS;CAC5C,MAAM,0BAA0B,SAAS,SAAS,cAAc,UAAU;CAE1E,YACE,yBACA,kIACF;CACA,YACE,SAAS,gBAAgB,cAAc,UAAU,QACjD,yGACF;CACA,MAAM,UAAU,eAAe,QAAQ,qBAAqB;CAC5D,MAAM,UAAU,eAAe,QAAQ;CACvC,MAAM,SAAS,iBAAiB;EAC9B;EACA;EACA;EACA;EACA,QAAQ,KAAK,WAAW;EACxB,WAAW,QAAQ,aAAa;EAChC,SAAS,SAAS,SAAS;CAC7B,CAAC;CAID,MAAM,kBAAkB;EACtB,eAAe,gBAAgB,uBAAuB,IAAI;EAC1D,gBAAgB;EAChB,aAAa;EAEb,cAAc;CAChB;CACA,MAAM,UAAU,iBAA8B;EAC5C,SAAS,KAAK;EACd;EACA;EACA;EACA,WAAW,KAAK;EAChB,gBAAgB,KAAK;EACrB,KAAK,KAAK;EACV,oBAAoB,GAAG,eAAe,WAAW,YAAY,gBAAgB;CAC/E,CAAC;CACD,MAAM,YAAY;EAChB,GAAG;EACH,GAAG;EACH,GAAG,QAAQ;EACX,GAAG,iBAAiB,OAAO;EAC3B,UAAU,gBAAgB,QAAQ,YAAY;EAC9C,WAAW,OAAO,KAAK,EAAE,OAAO,GAAG,YAAY,MAAM,SAAS,EAAE,CAAC;CACnE;CAEA,MAAM,SACJ,oBAAC,YAAD;EAAY,GAAI;EACZ,WAAA,EAAE,iBACF,qBAAC,QAAD;GACE,GAAI,QAAQ;GACZ,WAAW,OAAO,QAAQ,EACxB,OAAO,GAAG,QAAQ,gBAAgB,eAAe,SAAS,YAAY,OAAO,EAC/E,CAAC;GAJH,UAAA;KAMI,SAAS,QAAQ,SAAS,SAAS,oBAAC,aAAD,CAAc,CAAA;IACnD,oBAAC,QAAD;KACE,eAAY;KACZ,WAAW,OAAO,KAAK,EACrB,OAAO,GACL,WAAW,gBAAgB,CAAC,CAAC,KAAK,GAClC,QAAQ,gBAAgB,eAAe,UACvC,YAAY,IACd,EACF,CAAC;KAEA,UAAA,UACC,oBAAC,WAAD;MACQ;MACN,eAAe;MACf,WAAW;MACX,YAAW;KACZ,CAAA,IACC,cAAc,eAChB,eAEA;IAEE,CAAA;IACL,QAAQ;IACR,QAAQ;GACL;;CAEE,CAAA;CAGd,MAAM,QAAQ,KAAK;CACnB,IAAI,CAAC,WAAW,oBAAoB,CAAC,SAAS,KAAK,oBAAoB,OAAO;CAC9E,OACE,qBAAC,gBAAD,EAAA,UAAA,CACE,oBAAC,oBAAD,EAAA,UAAqB,OAA2B,CAAA,GAChD,oBAAC,aAAD;EAAa,YAAY,EAAE,OAAO,YAAY,QAAQ;EAAI,UAAA;CAAmB,CAAA,CAC/D,EAAA,CAAA;AAEpB"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { WindmorphLabels } from "../../windmorph-labels.js";
|
|
2
|
+
import { ReactNode } from "react";
|
|
3
|
+
//#region src/components/labels/LabelsProvider.d.ts
|
|
4
|
+
export interface LabelsProviderProps {
|
|
5
|
+
/**
|
|
6
|
+
* The words, keyed by component: `{ TextField: { error: '…' }, DialogHeader: { close: '…' } }`.
|
|
7
|
+
* Keep the object the same between renders (a constant, or `useMemo`), so the components
|
|
8
|
+
* below render again only when a word changes.
|
|
9
|
+
*/
|
|
10
|
+
labels: WindmorphLabels;
|
|
11
|
+
children?: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Sets the words every component below shows or announces by itself ("Close", "Error",
|
|
15
|
+
* "Character count: 0 of 160"), so an app in another language writes them once. Each
|
|
16
|
+
* component uses its English default, then the words of the providers above it (the nearest
|
|
17
|
+
* first, per word), then its own `labels` prop, which always wins.
|
|
18
|
+
*
|
|
19
|
+
* It ships no translations: the words are the app's. Some are functions, which a server
|
|
20
|
+
* component can't pass to a client one, so render it from a client file of the app's.
|
|
21
|
+
*
|
|
22
|
+
* ```tsx
|
|
23
|
+
* 'use client';
|
|
24
|
+
* const words: WindmorphLabels = {
|
|
25
|
+
* TextField: { error: 'Fehler', characterCount: (count, max) => `Zeichen: ${count} von ${max}` },
|
|
26
|
+
* DialogHeader: { close: 'Schließen' },
|
|
27
|
+
* };
|
|
28
|
+
* export const AppLabels = ({ children }: { children: ReactNode }) => (
|
|
29
|
+
* <LabelsProvider labels={words}>{children}</LabelsProvider>
|
|
30
|
+
* );
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare function LabelsProvider({ labels, children }: LabelsProviderProps): import("react").JSX.Element;
|
|
34
|
+
//#endregion
|
|
35
|
+
//# sourceMappingURL=LabelsProvider.d.ts.map
|