@windmorph/react 0.2.0 → 0.2.1

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.
@@ -2,6 +2,6 @@ import { ComponentTokenSet } from "../../tokens/component.js";
2
2
  import "../../index.js";
3
3
  //#region src/components/checkbox/checkbox-tokens.d.ts
4
4
  /** The tokens of {@link Checkbox}: `--md-comp-checkbox-<part>`. */
5
- export declare const checkboxTokenSet: ComponentTokenSet<"selected-icon-color" | "selected-container-color" | "unselected-outline-color">;
5
+ export declare const checkboxTokenSet: ComponentTokenSet<"selected-container-color" | "selected-icon-color" | "unselected-outline-color">;
6
6
  //#endregion
7
7
  //# sourceMappingURL=checkbox-tokens.d.ts.map
@@ -158,12 +158,12 @@ export declare const dockedDatePickerStyles: import("tailwind-variants").TVRetur
158
158
  [key: string]: import("tailwind-variants").ClassValue | {
159
159
  panel?: import("tailwind-variants").ClassValue;
160
160
  actions?: import("tailwind-variants").ClassValue;
161
- item?: import("tailwind-variants").ClassValue;
162
161
  header?: import("tailwind-variants").ClassValue;
163
162
  view?: import("tailwind-variants").ClassValue;
164
- list?: import("tailwind-variants").ClassValue;
163
+ item?: import("tailwind-variants").ClassValue;
165
164
  check?: import("tailwind-variants").ClassValue;
166
165
  menuIcon?: import("tailwind-variants").ClassValue;
166
+ list?: import("tailwind-variants").ClassValue;
167
167
  group?: import("tailwind-variants").ClassValue;
168
168
  calendar?: import("tailwind-variants").ClassValue;
169
169
  menuButton?: import("tailwind-variants").ClassValue;
@@ -174,12 +174,12 @@ export declare const dockedDatePickerStyles: import("tailwind-variants").TVRetur
174
174
  [x: string]: import("tailwind-variants").ClassValue | {
175
175
  panel?: import("tailwind-variants").ClassValue;
176
176
  actions?: import("tailwind-variants").ClassValue;
177
- item?: import("tailwind-variants").ClassValue;
178
177
  header?: import("tailwind-variants").ClassValue;
179
178
  view?: import("tailwind-variants").ClassValue;
180
- list?: import("tailwind-variants").ClassValue;
179
+ item?: import("tailwind-variants").ClassValue;
181
180
  check?: import("tailwind-variants").ClassValue;
182
181
  menuIcon?: import("tailwind-variants").ClassValue;
182
+ list?: import("tailwind-variants").ClassValue;
183
183
  group?: import("tailwind-variants").ClassValue;
184
184
  calendar?: import("tailwind-variants").ClassValue;
185
185
  menuButton?: import("tailwind-variants").ClassValue;
@@ -202,12 +202,12 @@ export declare const dockedDatePickerStyles: import("tailwind-variants").TVRetur
202
202
  [key: string]: import("tailwind-variants").ClassValue | {
203
203
  panel?: import("tailwind-variants").ClassValue;
204
204
  actions?: import("tailwind-variants").ClassValue;
205
- item?: import("tailwind-variants").ClassValue;
206
205
  header?: import("tailwind-variants").ClassValue;
207
206
  view?: import("tailwind-variants").ClassValue;
208
- list?: import("tailwind-variants").ClassValue;
207
+ item?: import("tailwind-variants").ClassValue;
209
208
  check?: import("tailwind-variants").ClassValue;
210
209
  menuIcon?: import("tailwind-variants").ClassValue;
210
+ list?: import("tailwind-variants").ClassValue;
211
211
  group?: import("tailwind-variants").ClassValue;
212
212
  calendar?: import("tailwind-variants").ClassValue;
213
213
  menuButton?: import("tailwind-variants").ClassValue;
@@ -1,5 +1,5 @@
1
1
  import { ClosedBy } from "../../utils/closed-by.js";
2
- import { CSSProperties, ComponentPropsWithRef, ReactNode } from "react";
2
+ import { CSSProperties, ComponentPropsWithRef, ReactElement, ReactNode } from "react";
3
3
  import { AriaDialogProps } from "react-aria";
4
4
  //#region src/components/dialog/Dialog.d.ts
5
5
  /**
@@ -216,6 +216,8 @@ export interface DialogHeaderProps extends Omit<ComponentPropsWithRef<'div'>, 't
216
216
  * from closing it too. @default false
217
217
  */
218
218
  closeDisabled?: boolean;
219
+ /** Replaces the full-screen close button's icon. */
220
+ closeIcon?: ReactElement;
219
221
  /** Classes for each part, by slot. */
220
222
  classNames?: DialogHeaderClassNames;
221
223
  }
@@ -233,7 +235,7 @@ export interface DialogHeaderProps extends Omit<ComponentPropsWithRef<'div'>, 't
233
235
  * <DialogActions>…</DialogActions>
234
236
  * </Dialog>
235
237
  */
236
- export declare function DialogHeader({ children, labels, action, divider, closeDisabled, className, classNames, ...props }: DialogHeaderProps): import("react").JSX.Element;
238
+ export declare function DialogHeader({ children, labels, action, divider, closeDisabled, closeIcon, className, classNames, ...props }: DialogHeaderProps): import("react").JSX.Element;
237
239
  export interface DialogContentProps extends ComponentPropsWithRef<'div'> {
238
240
  /**
239
241
  * The basic dialog's optional dividers: 1px `outline-variant` lines across the panel
@@ -325,7 +325,7 @@ const DEFAULT_HEADER_LABELS = { close: "Close" };
325
325
  * <DialogActions>…</DialogActions>
326
326
  * </Dialog>
327
327
  */
328
- function DialogHeader({ children, labels, action, divider = false, closeDisabled = false, className, classNames, ...props }) {
328
+ function DialogHeader({ children, labels, action, divider = false, closeDisabled = false, closeIcon, className, classNames, ...props }) {
329
329
  const { titleProps, hasIcon, fullScreen, close, setHeaderAction } = useContext(DialogPartsContext);
330
330
  const hasAction = action !== void 0;
331
331
  useLayoutEffect(() => {
@@ -344,7 +344,7 @@ function DialogHeader({ children, labels, action, divider = false, closeDisabled
344
344
  fullScreen !== "never" && /* @__PURE__ */ jsx(IconButton, {
345
345
  variant: "standard",
346
346
  density: 0,
347
- icon: /* @__PURE__ */ jsx(CloseIcon, {}),
347
+ icon: closeIcon ?? /* @__PURE__ */ jsx(CloseIcon, {}),
348
348
  "aria-label": labels?.close ?? DEFAULT_HEADER_LABELS.close,
349
349
  disabled: closeDisabled,
350
350
  onPress: 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 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 /** 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 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 />}\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 { 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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"}
@@ -113,6 +113,9 @@ export type IconButtonProps = IconButtonActionProps | IconButtonLinkProps | Icon
113
113
  * Shares `size`, `shape` and `disabled` with `ButtonContext`, so it fits in button groups,
114
114
  * and takes its default variant from it (`iconButtonVariant`).
115
115
  *
116
+ * To animate its size or position, put the transition on a wrapper: while pressed, the button's
117
+ * own motion replaces the timing of a transition added through `className`.
118
+ *
116
119
  * @example
117
120
  * <IconButton icon={<SearchIcon />} aria-label="Search" onPress={openSearch} />
118
121
  * <IconButton variant="standard" icon={<MoreIcon />} aria-label="More options" />
@@ -24,6 +24,9 @@ import { jsx, jsxs } from "react/jsx-runtime";
24
24
  * Shares `size`, `shape` and `disabled` with `ButtonContext`, so it fits in button groups,
25
25
  * and takes its default variant from it (`iconButtonVariant`).
26
26
  *
27
+ * To animate its size or position, put the transition on a wrapper: while pressed, the button's
28
+ * own motion replaces the timing of a transition added through `className`.
29
+ *
27
30
  * @example
28
31
  * <IconButton icon={<SearchIcon />} aria-label="Search" onPress={openSearch} />
29
32
  * <IconButton variant="standard" icon={<MoreIcon />} aria-label="More options" />
@@ -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 * @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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2KA,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 /** 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"}
@@ -49,7 +49,7 @@ const navItemStyles = tv({
49
49
  label: [
50
50
  "mt-[4px] max-w-full text-center text-label-medium",
51
51
  "text-[color:var(--_md-comp-nav-rail-item-inactive-label-text-color)]",
52
- "group-data-current/nav:text-label-medium-emphasized group-data-current/nav:text-[color:var(--_md-comp-nav-rail-item-active-label-text-color)]"
52
+ "group-data-current/nav:text-[color:var(--_md-comp-nav-rail-item-active-label-text-color)]"
53
53
  ]
54
54
  },
55
55
  "rail-start": {
@@ -61,7 +61,7 @@ const navItemStyles = tv({
61
61
  label: [
62
62
  "forced-colors:group-data-current/nav:text-[HighlightText] forced-colors:group-data-current/nav:forced-color-adjust-none",
63
63
  "text-label-large text-[color:var(--_md-comp-nav-rail-item-inactive-label-text-color)]",
64
- "group-data-current/nav:text-label-large-emphasized group-data-current/nav:text-[color:var(--_md-comp-nav-rail-item-active-label-text-color)]"
64
+ "group-data-current/nav:text-[color:var(--_md-comp-nav-rail-item-active-label-text-color)]"
65
65
  ]
66
66
  },
67
67
  "bar-top": {
@@ -72,7 +72,7 @@ const navItemStyles = tv({
72
72
  label: [
73
73
  "mt-[4px] max-w-full px-[4px] text-center text-label-medium",
74
74
  "text-[color:var(--_md-comp-nav-bar-item-inactive-label-text-color)]",
75
- "group-data-current/nav:text-label-medium-emphasized group-data-current/nav:text-[color:var(--_md-comp-nav-bar-item-active-label-text-color)]"
75
+ "group-data-current/nav:text-[color:var(--_md-comp-nav-bar-item-active-label-text-color)]"
76
76
  ]
77
77
  },
78
78
  "bar-start": {
@@ -89,7 +89,7 @@ const navItemStyles = tv({
89
89
  label: [
90
90
  "forced-colors:group-data-current/nav:text-[HighlightText] forced-colors:group-data-current/nav:forced-color-adjust-none",
91
91
  "text-label-medium text-[color:var(--_md-comp-nav-bar-item-inactive-label-text-color)]",
92
- "group-data-current/nav:text-label-medium-emphasized group-data-current/nav:text-[color:var(--_md-comp-nav-bar-item-active-label-text-color)]"
92
+ "group-data-current/nav:text-[color:var(--_md-comp-nav-bar-item-active-label-text-color)]"
93
93
  ]
94
94
  }
95
95
  },
@@ -1 +1 @@
1
- {"version":3,"file":"nav-item-styles.js","names":[],"sources":["../../../src/components/navigation/nav-item-styles.ts"],"sourcesContent":["import { tv, type VariantProps } from '../../utils/tv';\n\n/*\n * Navigation items shared by the navigation rail and the flexible navigation bar. Values\n * follow Compose Material 3 (NavigationItem.kt, WideNavigationRail.kt, ShortNavigationBar.kt\n * and the NavigationRail* / NavigationBar* token files):\n *\n * - rail-top (collapsed rail): 56×32 pill around a 24px icon, `label-medium` 4px below,\n * centred in the 96px collapsed width (labels may use all of it), at least 64px tall.\n * The item is that wide even while the rail's width animates, so its icon never moves.\n * - rail-start (expanded rail): 56px pill holding icon · 8px · `label-large`, 16px inside,\n * 20px item inset, which puts the icon on the same line as when collapsed. The pill hugs\n * its icon and label, as in M3 Expressive (the full-width pill was the navigation\n * drawer's), while the whole row stays the link or button: a larger target than Compose's\n * pill-only one, with the state layer and focus ring still on the pill.\n * - bar-top: 6px · 56×32 pill · 4px · `label-medium` · 6px (64px).\n * - bar-start: 40px pill holding icon · 4px · `label-medium`, 16px inside, centred in an\n * item as tall as the bar (64px), which is the touch target.\n *\n * Colours: icons `on-surface-variant`, selected `on-secondary-container` on a\n * `secondary-container` pill; labels `on-surface-variant`, selected `secondary` below the\n * icon or `on-secondary-container` beside it; disabled 38%. The state layer is\n * `on-secondary-container` on the pill, and a press ripples through it from the press point\n * (M3: \"a ripple passes through the indicator\"), as on every other M3 control. In every layout the current item's pill expands from\n * the centre of its icon and fades in on the default spatial spring (M3: \"the active\n * indicator expands from the center of the icon\"), and colours change on default effects.\n * The current label is bold (the emphasized label role). With a `selectedIcon`, the two\n * icons cross-fade on default effects.\n *\n * `slide` (the expanded rail's `indicatorMotion=\"slide\"`, an extension beyond the spec that\n * matches Google's Expressive showcase): the pill is whole while current and Motion moves it\n * from the previous current item on the fast spatial spring; the old pill goes at once, and\n * the content sits above every pill so a moving one never covers another item's label.\n * Under reduced motion it fades in place instead.\n *\n * Under reduced motion every pill fades in whole on the default effects spring, without\n * expanding (architecture §6).\n *\n * Labels don't truncate (M3: \"Don't truncate or display an ellipsis in place of label\n * text\"): they wrap between words, and the item grows taller, so the full label stays\n * visible under large text sizes. A word wider than the item may break where the browser\n * hyphenates it for the document's `lang` (`hyphens: auto`), but never bare (\"Searc/h\"):\n * `overflow-wrap: normal` keeps it whole, and a line it still overflows ends with an\n * ellipsis (`overflow: hidden` with `text-overflow: ellipsis`, applied per line), while the\n * other words keep wrapping. The text stays whole in the DOM, so the accessible name is the\n * full label (2026-10-16, refining 2026-10-13 and 2026-10-15).\n *\n * Badges overlap the icon's top end (BadgedBox), except in the expanded rail, where M3 puts\n * them next to the label, 4px after it.\n *\n * Forced colours (2026-10-15): the current pill keeps a `Highlight` fill\n * (`forced-color-adjust: none`), with its icon, and a label inside it, in `HighlightText`;\n * otherwise the pill became Canvas and only the bold label marked the current item. The\n * icon and a label inside the pill opt out of forced colours too (2026-10-17): with\n * `forced-color-adjust: auto` the browser paints a `Canvas` backplate behind text, so a\n * `HighlightText` label read white on white.\n *\n * The pill, its state layer and its content share one grid cell, so nothing is positioned.\n *\n * The pill, icon and label colours come from the bar's or rail's component tokens\n * (`navigation-tokens.ts`, `--md-comp-nav-{bar,rail}-item-*`): each item carries\n * `data-tokens` and `data-layout`, so the bar's layouts read the bar's item parts and the\n * rail's layouts the rail's. Disabled colours stay classes.\n *\n * Every class is written out in full so Tailwind can find it.\n */\n\n/** Variant definitions for navigation items; extend them to add variants. */\nexport const navItemStyles = tv({\n slots: {\n root: [\n 'group/nav grid min-w-0 cursor-pointer justify-items-center outline-none select-none',\n 'font-plain text-on-surface-variant',\n 'data-disabled:cursor-default',\n ],\n // The pill area: indicator, state layer and content overlap here; it carries the focus ring.\n pill: [\n 'grid rounded-full outline-offset-(--md-sys-focus-indicator-offset) outline-secondary',\n 'group-data-focus-visible/nav:outline-(length:--md-sys-focus-indicator-thickness) group-data-focus-visible/nav:outline-solid',\n ],\n indicator: [\n 'col-start-1 row-start-1 rounded-full opacity-0',\n 'group-data-current/nav:opacity-100 group-data-current/nav:[clip-path:inset(0_0_round_9999px)]',\n // Forced colours would paint the pill as Canvas: it keeps a Highlight fill instead.\n 'forced-colors:bg-[Highlight] forced-colors:forced-color-adjust-none',\n '[transition-property:clip-path,opacity]',\n '[transition-duration:var(--md-sys-motion-spring-spatial-default-duration)]',\n '[transition-timing-function:var(--md-sys-motion-spring-spatial-default-easing)]',\n // Reduced motion: the pill fades in whole on the default effects spring.\n 'motion-reduce:[transition-property:opacity]',\n 'motion-reduce:[transition-duration:var(--md-sys-motion-spring-effects-default-duration)]',\n 'motion-reduce:[transition-timing-function:var(--md-sys-motion-spring-effects-default-easing)]',\n ],\n // The shared state layer and ripple, on the pill: the item's hover and focus show\n // through it, and a press ripples from the press point (NavItem sets the origin).\n stateLayer: [\n 'col-start-1 row-start-1 rounded-full state-layer',\n '[--m3-state-layer-color:var(--md-sys-color-on-secondary-container)]',\n 'group-data-hovered/nav:[--m3-state-opacity:var(--md-sys-state-hover-state-layer-opacity)]',\n 'group-data-focus-visible/nav:[--m3-state-opacity:var(--md-sys-state-focus-state-layer-opacity)]',\n ],\n // `relative` keeps the content above the indicator and state layer: elements with\n // opacity or clip-path paint in the positioned layer, above plain in-flow content.\n content: 'relative col-start-1 row-start-1 flex min-w-0 items-center justify-center',\n icon: [\n 'inline-grid size-[24px] shrink-0 place-items-center [&>svg]:size-full',\n 'group-data-disabled/nav:text-on-surface-variant/38',\n // HighlightText on the Highlight pill, kept as painted (no Canvas backplate).\n 'forced-colors:group-data-current/nav:text-[HighlightText] forced-colors:group-data-current/nav:forced-color-adjust-none',\n 'transition-colors duration-(--md-sys-motion-spring-effects-default-duration) ease-(--md-sys-motion-spring-effects-default-easing)',\n ],\n // An icon and its selected variant share one cell and cross-fade.\n iconFace: [\n 'col-start-1 row-start-1 size-full opacity-0 data-shown:opacity-100 [&>svg]:size-full',\n '[transition-property:opacity] duration-(--md-sys-motion-spring-effects-default-duration) ease-(--md-sys-motion-spring-effects-default-easing)',\n ],\n // Holds the icon (or both faces) inside a badge's anchor.\n iconStack: 'inline-grid size-[24px] place-items-center [&>svg]:size-full',\n // The expanded rail's badge, 4px after the label (the content gap is 8px).\n badge: 'inline-flex shrink-0 -ms-[4px]',\n // The `BadgedBox` around a badged icon (its root; `classNames.badge` merges in). The\n // badge takes forced colours back, which the current icon opts out of.\n badgedBox: 'forced-colors:[&>span:last-child]:forced-color-adjust-auto',\n label: [\n 'min-w-0 overflow-hidden text-ellipsis text-balance hyphens-auto wrap-normal group-data-disabled/nav:text-on-surface-variant/38',\n '[transition-property:color,opacity] duration-(--md-sys-motion-spring-effects-default-duration) ease-(--md-sys-motion-spring-effects-default-easing)',\n ],\n },\n variants: {\n layout: {\n 'rail-top': {\n // The 56px pill centres in the collapsed width; labels may use all of it (Compose).\n // The minimum height springs 64 ↔ 48px with the rail's width (`railMorph`).\n root: 'min-h-[64px] w-(--m3-rail-collapsed-width) content-start',\n pill: 'h-[32px] w-[56px]',\n indicator:\n '[clip-path:inset(0_50%_round_9999px)] bg-[var(--_md-comp-nav-rail-item-active-indicator-color)]',\n icon: 'text-[color:var(--_md-comp-nav-rail-item-inactive-icon-color)] group-data-current/nav:text-[color:var(--_md-comp-nav-rail-item-active-icon-color)]',\n label: [\n 'mt-[4px] max-w-full text-center text-label-medium',\n 'text-[color:var(--_md-comp-nav-rail-item-inactive-label-text-color)]',\n 'group-data-current/nav:text-label-medium-emphasized group-data-current/nav:text-[color:var(--_md-comp-nav-rail-item-active-label-text-color)]',\n ],\n },\n 'rail-start': {\n root: 'min-h-[48px] w-full justify-items-start px-(--m3-rail-item-inset)',\n pill: 'min-h-[56px] max-w-full',\n // The icon's centre: 16px padding + half the 24px icon from the start edge.\n indicator: [\n '[clip-path:inset(0_calc(100%_-_28px)_0_28px_round_9999px)] rtl:[clip-path:inset(0_28px_0_calc(100%_-_28px)_round_9999px)]',\n 'bg-[var(--_md-comp-nav-rail-item-active-indicator-color)]',\n ],\n content: 'justify-start gap-[8px] ps-[16px] pe-[16px] py-[8px]',\n icon: 'text-[color:var(--_md-comp-nav-rail-item-inactive-icon-color)] group-data-current/nav:text-[color:var(--_md-comp-nav-rail-item-active-icon-color)]',\n label: [\n // Inside the Highlight pill: HighlightText, without the Canvas backplate forced\n // colours paint behind text (white on white otherwise).\n 'forced-colors:group-data-current/nav:text-[HighlightText] forced-colors:group-data-current/nav:forced-color-adjust-none',\n 'text-label-large text-[color:var(--_md-comp-nav-rail-item-inactive-label-text-color)]',\n 'group-data-current/nav:text-label-large-emphasized group-data-current/nav:text-[color:var(--_md-comp-nav-rail-item-active-label-text-color)]',\n ],\n },\n // Bar items stretch to the bar's height (64px, or taller when a label wraps), so the\n // whole height is the touch target. Stacked items keep their icons on one line at the\n // top; inline items centre their pill.\n 'bar-top': {\n root: 'self-stretch content-start py-[6px]',\n pill: 'h-[32px] w-[56px]',\n indicator:\n '[clip-path:inset(0_50%_round_9999px)] bg-[var(--_md-comp-nav-bar-item-active-indicator-color)]',\n icon: 'text-[color:var(--_md-comp-nav-bar-item-inactive-icon-color)] group-data-current/nav:text-[color:var(--_md-comp-nav-bar-item-active-icon-color)]',\n label: [\n 'mt-[4px] max-w-full px-[4px] text-center text-label-medium',\n 'text-[color:var(--_md-comp-nav-bar-item-inactive-label-text-color)]',\n 'group-data-current/nav:text-label-medium-emphasized group-data-current/nav:text-[color:var(--_md-comp-nav-bar-item-active-label-text-color)]',\n ],\n },\n 'bar-start': {\n // Content-sized; 4px each side keeps neighbouring pills 8px apart.\n root: 'self-stretch content-center px-[4px]',\n pill: 'min-h-[40px] max-w-full',\n indicator: [\n '[clip-path:inset(0_calc(100%_-_28px)_0_28px_round_9999px)] rtl:[clip-path:inset(0_28px_0_calc(100%_-_28px)_round_9999px)]',\n 'bg-[var(--_md-comp-nav-bar-item-active-indicator-color)]',\n ],\n content: 'gap-[4px] ps-[16px] pe-[16px] py-[4px]',\n // A large badge reaches past the icon's end edge into the label's place (M3: badges\n // overlap the icon). Its overhang takes room in the row so the label starts 4px after\n // the badge (2026-10-15): the icon box grows to the badge, which hangs from a box as\n // wide as itself, 12px inside the 24px icon, as `BadgedBox` places it.\n icon: [\n 'text-[color:var(--_md-comp-nav-bar-item-inactive-icon-color)] group-data-current/nav:text-[color:var(--_md-comp-nav-bar-item-active-icon-color)]',\n 'has-[[data-size=large]]:w-auto has-[[data-size=large]]:min-w-[24px] has-[[data-size=large]]:justify-items-start',\n ],\n badgedBox: [\n 'has-[[data-size=large]]:grid-cols-[auto]',\n '[&:has([data-size=large])>span:first-child]:w-[24px] [&:has([data-size=large])>span:first-child]:justify-self-start',\n '[&>span:last-child:has([data-size=large])]:w-max [&>span:last-child:has([data-size=large])]:ms-[12px]',\n ],\n label: [\n // Inside the Highlight pill: HighlightText, without the Canvas backplate forced\n // colours paint behind text (white on white otherwise).\n 'forced-colors:group-data-current/nav:text-[HighlightText] forced-colors:group-data-current/nav:forced-color-adjust-none',\n 'text-label-medium text-[color:var(--_md-comp-nav-bar-item-inactive-label-text-color)]',\n 'group-data-current/nav:text-label-medium-emphasized group-data-current/nav:text-[color:var(--_md-comp-nav-bar-item-active-label-text-color)]',\n ],\n },\n },\n /** Labels that move when the rail changes width fade in at their new place. */\n enterLabel: { true: { label: 'starting:opacity-0' }, false: {} },\n /** The pill slides between items (Motion moves it) instead of expanding in place. */\n slide: {\n true: {\n indicator: '[transition-property:none] motion-reduce:[transition-property:opacity]',\n content: 'z-[1]',\n },\n false: {},\n },\n },\n compoundVariants: [\n // As the rail expands or collapses, an item's minimum height springs between 64px and\n // 48px on the default spatial spring, as Compose's itemMinHeight (WideNavigationRail.kt;\n // 2026-10-17: it swapped in one frame, so the icons jumped). Instant under reduced motion.\n {\n layout: ['rail-top', 'rail-start'],\n class: {\n root: '[transition-property:min-height] [transition-duration:var(--md-sys-motion-spring-spatial-default-duration)] [transition-timing-function:var(--md-sys-motion-spring-spatial-default-easing)]',\n },\n },\n ],\n defaultVariants: { layout: 'bar-top', enterLabel: false, slide: false },\n});\n\nexport type NavItemLayout = NonNullable<VariantProps<typeof navItemStyles>['layout']>;\n"],"mappings":";;;AAoEA,MAAa,gBAAgB,GAAG;CAC9B,OAAO;EACL,MAAM;GACJ;GACA;GACA;EACF;EAEA,MAAM,CACJ,wFACA,6HACF;EACA,WAAW;GACT;GACA;GAEA;GACA;GACA;GACA;GAEA;GACA;GACA;EACF;EAGA,YAAY;GACV;GACA;GACA;GACA;EACF;EAGA,SAAS;EACT,MAAM;GACJ;GACA;GAEA;GACA;EACF;EAEA,UAAU,CACR,wFACA,+IACF;EAEA,WAAW;EAEX,OAAO;EAGP,WAAW;EACX,OAAO,CACL,kIACA,qJACF;CACF;CACA,UAAU;EACR,QAAQ;GACN,YAAY;IAGV,MAAM;IACN,MAAM;IACN,WACE;IACF,MAAM;IACN,OAAO;KACL;KACA;KACA;IACF;GACF;GACA,cAAc;IACZ,MAAM;IACN,MAAM;IAEN,WAAW,CACT,6HACA,2DACF;IACA,SAAS;IACT,MAAM;IACN,OAAO;KAGL;KACA;KACA;IACF;GACF;GAIA,WAAW;IACT,MAAM;IACN,MAAM;IACN,WACE;IACF,MAAM;IACN,OAAO;KACL;KACA;KACA;IACF;GACF;GACA,aAAa;IAEX,MAAM;IACN,MAAM;IACN,WAAW,CACT,6HACA,0DACF;IACA,SAAS;IAKT,MAAM,CACJ,oJACA,iHACF;IACA,WAAW;KACT;KACA;KACA;IACF;IACA,OAAO;KAGL;KACA;KACA;IACF;GACF;EACF;;EAEA,YAAY;GAAE,MAAM,EAAE,OAAO,qBAAqB;GAAG,OAAO,CAAC;EAAE;;EAE/D,OAAO;GACL,MAAM;IACJ,WAAW;IACX,SAAS;GACX;GACA,OAAO,CAAC;EACV;CACF;CACA,kBAAkB,CAIhB;EACE,QAAQ,CAAC,YAAY,YAAY;EACjC,OAAO,EACL,MAAM,8LACR;CACF,CACF;CACA,iBAAiB;EAAE,QAAQ;EAAW,YAAY;EAAO,OAAO;CAAM;AACxE,CAAC"}
1
+ {"version":3,"file":"nav-item-styles.js","names":[],"sources":["../../../src/components/navigation/nav-item-styles.ts"],"sourcesContent":["import { tv, type VariantProps } from '../../utils/tv';\n\n/*\n * Navigation items shared by the navigation rail and the flexible navigation bar. Values\n * follow Compose Material 3 (NavigationItem.kt, WideNavigationRail.kt, ShortNavigationBar.kt\n * and the NavigationRail* / NavigationBar* token files):\n *\n * - rail-top (collapsed rail): 56×32 pill around a 24px icon, `label-medium` 4px below,\n * centred in the 96px collapsed width (labels may use all of it), at least 64px tall.\n * The item is that wide even while the rail's width animates, so its icon never moves.\n * - rail-start (expanded rail): 56px pill holding icon · 8px · `label-large`, 16px inside,\n * 20px item inset, which puts the icon on the same line as when collapsed. The pill hugs\n * its icon and label, as in M3 Expressive (the full-width pill was the navigation\n * drawer's), while the whole row stays the link or button: a larger target than Compose's\n * pill-only one, with the state layer and focus ring still on the pill.\n * - bar-top: 6px · 56×32 pill · 4px · `label-medium` · 6px (64px).\n * - bar-start: 40px pill holding icon · 4px · `label-medium`, 16px inside, centred in an\n * item as tall as the bar (64px), which is the touch target.\n *\n * Colours: icons `on-surface-variant`, selected `on-secondary-container` on a\n * `secondary-container` pill; labels `on-surface-variant`, selected `secondary` below the\n * icon or `on-secondary-container` beside it; disabled 38%. The state layer is\n * `on-secondary-container` on the pill, and a press ripples through it from the press point\n * (M3: \"a ripple passes through the indicator\"), as on every other M3 control. In every layout the current item's pill expands from\n * the centre of its icon and fades in on the default spatial spring (M3: \"the active\n * indicator expands from the center of the icon\"), and colours change on default effects.\n * The current label keeps its weight (M3 Expressive). With a `selectedIcon`, the two\n * icons cross-fade on default effects.\n *\n * `slide` (the expanded rail's `indicatorMotion=\"slide\"`, an extension beyond the spec that\n * matches Google's Expressive showcase): the pill is whole while current and Motion moves it\n * from the previous current item on the fast spatial spring; the old pill goes at once, and\n * the content sits above every pill so a moving one never covers another item's label.\n * Under reduced motion it fades in place instead.\n *\n * Under reduced motion every pill fades in whole on the default effects spring, without\n * expanding (architecture §6).\n *\n * Labels don't truncate (M3: \"Don't truncate or display an ellipsis in place of label\n * text\"): they wrap between words, and the item grows taller, so the full label stays\n * visible under large text sizes. A word wider than the item may break where the browser\n * hyphenates it for the document's `lang` (`hyphens: auto`), but never bare (\"Searc/h\"):\n * `overflow-wrap: normal` keeps it whole, and a line it still overflows ends with an\n * ellipsis (`overflow: hidden` with `text-overflow: ellipsis`, applied per line), while the\n * other words keep wrapping. The text stays whole in the DOM, so the accessible name is the\n * full label (2026-10-16, refining 2026-10-13 and 2026-10-15).\n *\n * Badges overlap the icon's top end (BadgedBox), except in the expanded rail, where M3 puts\n * them next to the label, 4px after it.\n *\n * Forced colours (2026-10-15): the current pill keeps a `Highlight` fill\n * (`forced-color-adjust: none`), with its icon, and a label inside it, in `HighlightText`;\n * otherwise the pill became Canvas and only the bold label marked the current item. The\n * icon and a label inside the pill opt out of forced colours too (2026-10-17): with\n * `forced-color-adjust: auto` the browser paints a `Canvas` backplate behind text, so a\n * `HighlightText` label read white on white.\n *\n * The pill, its state layer and its content share one grid cell, so nothing is positioned.\n *\n * The pill, icon and label colours come from the bar's or rail's component tokens\n * (`navigation-tokens.ts`, `--md-comp-nav-{bar,rail}-item-*`): each item carries\n * `data-tokens` and `data-layout`, so the bar's layouts read the bar's item parts and the\n * rail's layouts the rail's. Disabled colours stay classes.\n *\n * Every class is written out in full so Tailwind can find it.\n */\n\n/** Variant definitions for navigation items; extend them to add variants. */\nexport const navItemStyles = tv({\n slots: {\n root: [\n 'group/nav grid min-w-0 cursor-pointer justify-items-center outline-none select-none',\n 'font-plain text-on-surface-variant',\n 'data-disabled:cursor-default',\n ],\n // The pill area: indicator, state layer and content overlap here; it carries the focus ring.\n pill: [\n 'grid rounded-full outline-offset-(--md-sys-focus-indicator-offset) outline-secondary',\n 'group-data-focus-visible/nav:outline-(length:--md-sys-focus-indicator-thickness) group-data-focus-visible/nav:outline-solid',\n ],\n indicator: [\n 'col-start-1 row-start-1 rounded-full opacity-0',\n 'group-data-current/nav:opacity-100 group-data-current/nav:[clip-path:inset(0_0_round_9999px)]',\n // Forced colours would paint the pill as Canvas: it keeps a Highlight fill instead.\n 'forced-colors:bg-[Highlight] forced-colors:forced-color-adjust-none',\n '[transition-property:clip-path,opacity]',\n '[transition-duration:var(--md-sys-motion-spring-spatial-default-duration)]',\n '[transition-timing-function:var(--md-sys-motion-spring-spatial-default-easing)]',\n // Reduced motion: the pill fades in whole on the default effects spring.\n 'motion-reduce:[transition-property:opacity]',\n 'motion-reduce:[transition-duration:var(--md-sys-motion-spring-effects-default-duration)]',\n 'motion-reduce:[transition-timing-function:var(--md-sys-motion-spring-effects-default-easing)]',\n ],\n // The shared state layer and ripple, on the pill: the item's hover and focus show\n // through it, and a press ripples from the press point (NavItem sets the origin).\n stateLayer: [\n 'col-start-1 row-start-1 rounded-full state-layer',\n '[--m3-state-layer-color:var(--md-sys-color-on-secondary-container)]',\n 'group-data-hovered/nav:[--m3-state-opacity:var(--md-sys-state-hover-state-layer-opacity)]',\n 'group-data-focus-visible/nav:[--m3-state-opacity:var(--md-sys-state-focus-state-layer-opacity)]',\n ],\n // `relative` keeps the content above the indicator and state layer: elements with\n // opacity or clip-path paint in the positioned layer, above plain in-flow content.\n content: 'relative col-start-1 row-start-1 flex min-w-0 items-center justify-center',\n icon: [\n 'inline-grid size-[24px] shrink-0 place-items-center [&>svg]:size-full',\n 'group-data-disabled/nav:text-on-surface-variant/38',\n // HighlightText on the Highlight pill, kept as painted (no Canvas backplate).\n 'forced-colors:group-data-current/nav:text-[HighlightText] forced-colors:group-data-current/nav:forced-color-adjust-none',\n 'transition-colors duration-(--md-sys-motion-spring-effects-default-duration) ease-(--md-sys-motion-spring-effects-default-easing)',\n ],\n // An icon and its selected variant share one cell and cross-fade.\n iconFace: [\n 'col-start-1 row-start-1 size-full opacity-0 data-shown:opacity-100 [&>svg]:size-full',\n '[transition-property:opacity] duration-(--md-sys-motion-spring-effects-default-duration) ease-(--md-sys-motion-spring-effects-default-easing)',\n ],\n // Holds the icon (or both faces) inside a badge's anchor.\n iconStack: 'inline-grid size-[24px] place-items-center [&>svg]:size-full',\n // The expanded rail's badge, 4px after the label (the content gap is 8px).\n badge: 'inline-flex shrink-0 -ms-[4px]',\n // The `BadgedBox` around a badged icon (its root; `classNames.badge` merges in). The\n // badge takes forced colours back, which the current icon opts out of.\n badgedBox: 'forced-colors:[&>span:last-child]:forced-color-adjust-auto',\n label: [\n 'min-w-0 overflow-hidden text-ellipsis text-balance hyphens-auto wrap-normal group-data-disabled/nav:text-on-surface-variant/38',\n '[transition-property:color,opacity] duration-(--md-sys-motion-spring-effects-default-duration) ease-(--md-sys-motion-spring-effects-default-easing)',\n ],\n },\n variants: {\n layout: {\n 'rail-top': {\n // The 56px pill centres in the collapsed width; labels may use all of it (Compose).\n // The minimum height springs 64 ↔ 48px with the rail's width (`railMorph`).\n root: 'min-h-[64px] w-(--m3-rail-collapsed-width) content-start',\n pill: 'h-[32px] w-[56px]',\n indicator:\n '[clip-path:inset(0_50%_round_9999px)] bg-[var(--_md-comp-nav-rail-item-active-indicator-color)]',\n icon: 'text-[color:var(--_md-comp-nav-rail-item-inactive-icon-color)] group-data-current/nav:text-[color:var(--_md-comp-nav-rail-item-active-icon-color)]',\n label: [\n 'mt-[4px] max-w-full text-center text-label-medium',\n 'text-[color:var(--_md-comp-nav-rail-item-inactive-label-text-color)]',\n 'group-data-current/nav:text-[color:var(--_md-comp-nav-rail-item-active-label-text-color)]',\n ],\n },\n 'rail-start': {\n root: 'min-h-[48px] w-full justify-items-start px-(--m3-rail-item-inset)',\n pill: 'min-h-[56px] max-w-full',\n // The icon's centre: 16px padding + half the 24px icon from the start edge.\n indicator: [\n '[clip-path:inset(0_calc(100%_-_28px)_0_28px_round_9999px)] rtl:[clip-path:inset(0_28px_0_calc(100%_-_28px)_round_9999px)]',\n 'bg-[var(--_md-comp-nav-rail-item-active-indicator-color)]',\n ],\n content: 'justify-start gap-[8px] ps-[16px] pe-[16px] py-[8px]',\n icon: 'text-[color:var(--_md-comp-nav-rail-item-inactive-icon-color)] group-data-current/nav:text-[color:var(--_md-comp-nav-rail-item-active-icon-color)]',\n label: [\n // Inside the Highlight pill: HighlightText, without the Canvas backplate forced\n // colours paint behind text (white on white otherwise).\n 'forced-colors:group-data-current/nav:text-[HighlightText] forced-colors:group-data-current/nav:forced-color-adjust-none',\n 'text-label-large text-[color:var(--_md-comp-nav-rail-item-inactive-label-text-color)]',\n 'group-data-current/nav:text-[color:var(--_md-comp-nav-rail-item-active-label-text-color)]',\n ],\n },\n // Bar items stretch to the bar's height (64px, or taller when a label wraps), so the\n // whole height is the touch target. Stacked items keep their icons on one line at the\n // top; inline items centre their pill.\n 'bar-top': {\n root: 'self-stretch content-start py-[6px]',\n pill: 'h-[32px] w-[56px]',\n indicator:\n '[clip-path:inset(0_50%_round_9999px)] bg-[var(--_md-comp-nav-bar-item-active-indicator-color)]',\n icon: 'text-[color:var(--_md-comp-nav-bar-item-inactive-icon-color)] group-data-current/nav:text-[color:var(--_md-comp-nav-bar-item-active-icon-color)]',\n label: [\n 'mt-[4px] max-w-full px-[4px] text-center text-label-medium',\n 'text-[color:var(--_md-comp-nav-bar-item-inactive-label-text-color)]',\n 'group-data-current/nav:text-[color:var(--_md-comp-nav-bar-item-active-label-text-color)]',\n ],\n },\n 'bar-start': {\n // Content-sized; 4px each side keeps neighbouring pills 8px apart.\n root: 'self-stretch content-center px-[4px]',\n pill: 'min-h-[40px] max-w-full',\n indicator: [\n '[clip-path:inset(0_calc(100%_-_28px)_0_28px_round_9999px)] rtl:[clip-path:inset(0_28px_0_calc(100%_-_28px)_round_9999px)]',\n 'bg-[var(--_md-comp-nav-bar-item-active-indicator-color)]',\n ],\n content: 'gap-[4px] ps-[16px] pe-[16px] py-[4px]',\n // A large badge reaches past the icon's end edge into the label's place (M3: badges\n // overlap the icon). Its overhang takes room in the row so the label starts 4px after\n // the badge (2026-10-15): the icon box grows to the badge, which hangs from a box as\n // wide as itself, 12px inside the 24px icon, as `BadgedBox` places it.\n icon: [\n 'text-[color:var(--_md-comp-nav-bar-item-inactive-icon-color)] group-data-current/nav:text-[color:var(--_md-comp-nav-bar-item-active-icon-color)]',\n 'has-[[data-size=large]]:w-auto has-[[data-size=large]]:min-w-[24px] has-[[data-size=large]]:justify-items-start',\n ],\n badgedBox: [\n 'has-[[data-size=large]]:grid-cols-[auto]',\n '[&:has([data-size=large])>span:first-child]:w-[24px] [&:has([data-size=large])>span:first-child]:justify-self-start',\n '[&>span:last-child:has([data-size=large])]:w-max [&>span:last-child:has([data-size=large])]:ms-[12px]',\n ],\n label: [\n // Inside the Highlight pill: HighlightText, without the Canvas backplate forced\n // colours paint behind text (white on white otherwise).\n 'forced-colors:group-data-current/nav:text-[HighlightText] forced-colors:group-data-current/nav:forced-color-adjust-none',\n 'text-label-medium text-[color:var(--_md-comp-nav-bar-item-inactive-label-text-color)]',\n 'group-data-current/nav:text-[color:var(--_md-comp-nav-bar-item-active-label-text-color)]',\n ],\n },\n },\n /** Labels that move when the rail changes width fade in at their new place. */\n enterLabel: { true: { label: 'starting:opacity-0' }, false: {} },\n /** The pill slides between items (Motion moves it) instead of expanding in place. */\n slide: {\n true: {\n indicator: '[transition-property:none] motion-reduce:[transition-property:opacity]',\n content: 'z-[1]',\n },\n false: {},\n },\n },\n compoundVariants: [\n // As the rail expands or collapses, an item's minimum height springs between 64px and\n // 48px on the default spatial spring, as Compose's itemMinHeight (WideNavigationRail.kt;\n // 2026-10-17: it swapped in one frame, so the icons jumped). Instant under reduced motion.\n {\n layout: ['rail-top', 'rail-start'],\n class: {\n root: '[transition-property:min-height] [transition-duration:var(--md-sys-motion-spring-spatial-default-duration)] [transition-timing-function:var(--md-sys-motion-spring-spatial-default-easing)]',\n },\n },\n ],\n defaultVariants: { layout: 'bar-top', enterLabel: false, slide: false },\n});\n\nexport type NavItemLayout = NonNullable<VariantProps<typeof navItemStyles>['layout']>;\n"],"mappings":";;;AAoEA,MAAa,gBAAgB,GAAG;CAC9B,OAAO;EACL,MAAM;GACJ;GACA;GACA;EACF;EAEA,MAAM,CACJ,wFACA,6HACF;EACA,WAAW;GACT;GACA;GAEA;GACA;GACA;GACA;GAEA;GACA;GACA;EACF;EAGA,YAAY;GACV;GACA;GACA;GACA;EACF;EAGA,SAAS;EACT,MAAM;GACJ;GACA;GAEA;GACA;EACF;EAEA,UAAU,CACR,wFACA,+IACF;EAEA,WAAW;EAEX,OAAO;EAGP,WAAW;EACX,OAAO,CACL,kIACA,qJACF;CACF;CACA,UAAU;EACR,QAAQ;GACN,YAAY;IAGV,MAAM;IACN,MAAM;IACN,WACE;IACF,MAAM;IACN,OAAO;KACL;KACA;KACA;IACF;GACF;GACA,cAAc;IACZ,MAAM;IACN,MAAM;IAEN,WAAW,CACT,6HACA,2DACF;IACA,SAAS;IACT,MAAM;IACN,OAAO;KAGL;KACA;KACA;IACF;GACF;GAIA,WAAW;IACT,MAAM;IACN,MAAM;IACN,WACE;IACF,MAAM;IACN,OAAO;KACL;KACA;KACA;IACF;GACF;GACA,aAAa;IAEX,MAAM;IACN,MAAM;IACN,WAAW,CACT,6HACA,0DACF;IACA,SAAS;IAKT,MAAM,CACJ,oJACA,iHACF;IACA,WAAW;KACT;KACA;KACA;IACF;IACA,OAAO;KAGL;KACA;KACA;IACF;GACF;EACF;;EAEA,YAAY;GAAE,MAAM,EAAE,OAAO,qBAAqB;GAAG,OAAO,CAAC;EAAE;;EAE/D,OAAO;GACL,MAAM;IACJ,WAAW;IACX,SAAS;GACX;GACA,OAAO,CAAC;EACV;CACF;CACA,kBAAkB,CAIhB;EACE,QAAQ,CAAC,YAAY,YAAY;EACjC,OAAO,EACL,MAAM,8LACR;CACF,CACF;CACA,iBAAiB;EAAE,QAAQ;EAAW,YAAY;EAAO,OAAO;CAAM;AACxE,CAAC"}
@@ -9,6 +9,8 @@ export interface SideSheetClassNames {
9
9
  container?: string;
10
10
  /** The dimmed layer behind a modal sheet. */
11
11
  scrim?: string;
12
+ /** The box around a standard sheet's surface, which holds a detached sheet's margins. */
13
+ frame?: string;
12
14
  /** The sheet's surface: its colour, corners, width and shadow. */
13
15
  panel?: string;
14
16
  /** The header row: back button, title, actions and close button. */
@@ -268,7 +268,7 @@ function StandardSideSheet({ state, detached = false, edge = "end", swipeable: _
268
268
  className: styles.standardClip(),
269
269
  children: /* @__PURE__ */ jsx("div", {
270
270
  ref: frameRef,
271
- className: styles.standardFrame(),
271
+ className: styles.standardFrame({ class: body.classNames?.frame }),
272
272
  children: /* @__PURE__ */ jsx("div", {
273
273
  "data-slot": "panel",
274
274
  className: styles.panel({ class: body.classNames?.panel }),