@windmorph/react 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/components/carousel/carousel-styles.d.ts +6 -15
  2. package/dist/components/carousel/carousel-styles.js +4 -7
  3. package/dist/components/carousel/carousel-styles.js.map +1 -1
  4. package/dist/components/chip/chip-tokens.d.ts +4 -4
  5. package/dist/components/date-picker/DatePicker.js +11 -4
  6. package/dist/components/date-picker/DatePicker.js.map +1 -1
  7. package/dist/components/date-picker/date-picker-styles.d.ts +3 -3
  8. package/dist/components/date-picker/date-picker-tokens.d.ts +1 -1
  9. package/dist/components/dialog/dialog-tokens.d.ts +1 -1
  10. package/dist/components/list/List.d.ts +9 -0
  11. package/dist/components/list/List.js +7 -4
  12. package/dist/components/list/List.js.map +1 -1
  13. package/dist/components/list/list-styles.js +6 -6
  14. package/dist/components/list/list-styles.js.map +1 -1
  15. package/dist/components/list/list-tokens.d.ts +1 -1
  16. package/dist/components/list/list-tokens.js +50 -0
  17. package/dist/components/list/list-tokens.js.map +1 -1
  18. package/dist/components/menu/menu-tokens.d.ts +1 -1
  19. package/dist/components/navigation/NavigationRail.js +52 -15
  20. package/dist/components/navigation/NavigationRail.js.map +1 -1
  21. package/dist/components/search/SearchBar.js +19 -34
  22. package/dist/components/search/SearchBar.js.map +1 -1
  23. package/dist/components/search/SearchField.d.ts +111 -0
  24. package/dist/components/search/SearchField.js +128 -0
  25. package/dist/components/search/SearchField.js.map +1 -0
  26. package/dist/components/search/search-field-frame.js +36 -0
  27. package/dist/components/search/search-field-frame.js.map +1 -0
  28. package/dist/components/search/search-styles.d.ts +58 -0
  29. package/dist/components/search/search-styles.js +27 -2
  30. package/dist/components/search/search-styles.js.map +1 -1
  31. package/dist/components/select/search-list.js +8 -13
  32. package/dist/components/select/search-list.js.map +1 -1
  33. package/dist/components/snackbar/snackbar-tokens.d.ts +1 -1
  34. package/dist/components/text-field/TextField.js +19 -5
  35. package/dist/components/text-field/TextField.js.map +1 -1
  36. package/dist/components/toolbar/toolbar-styles.js +2 -2
  37. package/dist/components/toolbar/toolbar-styles.js.map +1 -1
  38. package/dist/components/tooltip/tooltip-tokens.d.ts +1 -1
  39. package/dist/index.d.ts +3 -2
  40. package/dist/index.js +3 -2
  41. package/dist/styles/generated/components.css +5 -0
  42. package/dist/styles.compiled.css +1 -1
  43. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"list-styles.js","names":[],"sources":["../../../src/components/list/list-styles.ts"],"sourcesContent":["import { tv, type VariantProps } from '../../utils/tv';\n\n/*\n * Values follow the M3 Expressive list spec (m3.material.io) with Compose Material 3's\n * Expressive list tokens (ListItem.kt's interactive overloads, SegmentedListItem,\n * ListItemDefaults, ListTokens' `*Expressive*` values; 2026-10-13).\n *\n * Item: 16px start / end and 10px top / bottom padding, 12px between leading content, the\n * text and trailing content. The 12px belongs to the leading and trailing cells (rendered\n * only when present), so a text-only item's text spans 16px to 16px. At least 56px tall with one line, 72px with two (overline or\n * supporting text) and 88px with three (both), where content aligns to the top. Headline\n * `body-large` `on-surface`; supporting text `body-medium`, overline `label-small`, leading\n * and trailing content `on-surface-variant` (trailing text `label-small`, 20px icons). Selected\n * items show a trailing check as a second cue besides colour. Selected items\n * are `secondary-container` with `on-secondary-container` content; disabled content is\n * `on-surface` 38%.\n *\n * Shape (interactive items): 4px at rest, 12px hovered, 16px focused, selected or pressed,\n * morphing on the fast spatial spring; colours change on the default effects spring.\n * Segmented lists put 2px between items and round the outer corners of the first and last\n * items to 16px at rest. Static items in a standard list have no corners.\n *\n * Leading images are 56dp squares and video thumbnails 100 × 56dp (small) or 114 × 64dp\n * (large), all with 8dp corners; avatars are 40dp circles in `primary-container`\n * (`ListMedia.tsx`). The item grows to fit them. Expandable items indent their nested items\n * 16dp per level and end with a chevron that turns over on the fast spatial spring; their\n * nested items open and close their height on the default spatial spring while fading on\n * the default effects spring (only the fade under reduced motion). Dividers (standard lists) are 1px of\n * `outline-variant` on the item's bottom edge, full width or 16dp in from each side, drawn\n * by its `::after` (the state layer uses its background), never under the last item.\n *\n * Web density (`--md-sys-density`, 0 to -3; M3: a list \"can adjust margins, spacing, or\n * density\"): each step takes 2px off the padding above and below an item (10 → 4px) and 4px off\n * its minimum height (56 → 44px, 72 → 60px, 88 → 76px), so items that media make taller lose\n * 4px a step too, as in Material Components Web's list density (every item height 4px a step).\n * Section subheaders lose 4px a step (48 → 36px). Items are stacked rows, so dense ones keep\n * non-overlapping full-row targets above WCAG 2.5.8's 24px, and the expand chevron's target is\n * at most a one-line item's height.\n *\n * Sections (`ListSection`) put a subheader above their items: 48dp tall, `title-small`\n * `on-surface-variant`, 16dp from each side. The M3 spec defines no list subheader, so this\n * follows the deprecated navigation drawer's section headline. Items take their position\n * (segmented corners, dividers) within their section.\n *\n * Item colours come from the list's component tokens (`list-tokens.ts`,\n * `--md-comp-list-list-item-*`): the root and each item carry `data-tokens=\"md-comp-list\"`\n * and `data-variant`. A consumer class still wins in `cn()`.\n *\n * Every class is written out in full so Tailwind can find it.\n */\n\n/** Variant definitions for {@link List}; extend them to add variants. */\nexport const listStyles = tv({\n slots: {\n root: 'm-0 flex list-none flex-col p-0 outline-none',\n // A `ListSection`: its subheader, then its items' group.\n section: 'flex flex-col',\n // The M3 spec has no list subheader; this follows the deprecated navigation drawer's\n // section headline (`title-small`, `on-surface-variant`), on a 48dp row with the items'\n // 16dp side padding, so its text lines up with the headlines below it.\n subheader:\n 'flex min-h-[calc(48px+var(--md-sys-density,0)*4px)] items-center ps-[16px] pe-[16px] text-title-small text-on-surface-variant',\n group: 'm-0 flex list-none flex-col p-0',\n item: [\n 'group/list-item flex ps-[16px] pe-[16px] pt-[calc(10px+var(--md-sys-density,0)*2px)] pb-[calc(10px+var(--md-sys-density,0)*2px)] text-start outline-none',\n 'bg-[var(--_md-comp-list-container-color)] data-selected:bg-[var(--_md-comp-list-list-item-selected-container-color)]',\n '[--m3-transition-property:border-radius,background-color,color]',\n '[--m3-transition-duration:var(--md-sys-motion-spring-spatial-fast-duration),var(--md-sys-motion-spring-effects-default-duration),var(--md-sys-motion-spring-effects-default-duration)]',\n '[--m3-transition-easing:var(--md-sys-motion-spring-spatial-fast-easing),var(--md-sys-motion-spring-effects-default-easing),var(--md-sys-motion-spring-effects-default-easing)]',\n '[--m3-transition-delay:0s,0s,0s]',\n ],\n // The item's content grid (the grid cell of an interactive item).\n cell: 'grid w-full grid-cols-[auto_minmax(0,1fr)_auto]',\n leading: [\n 'col-start-1 me-[12px] flex items-center gap-x-[12px] text-title-medium text-[color:var(--_md-comp-list-list-item-leading-icon-color)] [&>svg]:size-[20px]',\n 'group-data-selected/list-item:text-on-secondary-container',\n 'group-data-disabled/list-item:text-on-surface/38',\n ],\n text: 'col-start-2 flex min-w-0 flex-col',\n overline: [\n 'text-label-small text-[color:var(--_md-comp-list-list-item-overline-color)]',\n 'group-data-selected/list-item:text-on-secondary-container',\n 'group-data-disabled/list-item:text-on-surface/38',\n ],\n headline: [\n 'text-body-large text-[color:var(--_md-comp-list-list-item-label-text-color)]',\n 'group-data-selected/list-item:text-on-secondary-container',\n 'group-data-disabled/list-item:text-on-surface/38',\n ],\n supporting: [\n 'text-body-medium text-[color:var(--_md-comp-list-list-item-supporting-text-color)]',\n 'group-data-selected/list-item:text-on-secondary-container',\n 'group-data-disabled/list-item:text-on-surface/38',\n ],\n trailing: [\n 'col-start-3 ms-[12px] flex items-center gap-x-[12px] text-label-small text-[color:var(--_md-comp-list-list-item-trailing-icon-color)] [&>svg]:size-[20px]',\n 'group-data-selected/list-item:text-on-secondary-container',\n 'group-data-disabled/list-item:text-on-surface/38',\n ],\n // The check a selected item shows besides its colour (M3: two cues for selection).\n selectionIndicator: [\n 'inline-flex size-[20px] shrink-0 items-center justify-center opacity-0 [&>svg]:size-full',\n 'group-data-selected/list-item:opacity-100 group-data-selected/list-item:text-on-secondary-container',\n 'transition-opacity duration-(--md-sys-motion-spring-effects-fast-duration) ease-(--md-sys-motion-spring-effects-fast-easing)',\n ],\n // An expandable item's chevron: a button (out of the Tab order) with a 48px target.\n expand: [\n 'relative inline-flex size-[24px] shrink-0 cursor-pointer items-center justify-center rounded-full',\n 'state-layer [&>svg]:size-[20px] [&>svg]:fill-current',\n '[&>svg]:transition-transform [&>svg]:duration-(--md-sys-motion-spring-spatial-fast-duration) [&>svg]:ease-(--md-sys-motion-spring-spatial-fast-easing)',\n 'group-aria-expanded/list-item:[&>svg]:rotate-180 motion-reduce:[&>svg]:transition-none',\n 'data-disabled:cursor-default',\n ],\n // A tree row's wrapper: nested rows open and close their height (grid rows 0fr ↔ 1fr)\n // on the default spatial spring and fade on the default effects spring; under reduced\n // motion they only fade.\n reveal: [\n 'grid grid-rows-[1fr] opacity-100',\n '[transition-property:grid-template-rows,opacity] motion-reduce:[transition-property:opacity]',\n '[transition-duration:var(--md-sys-motion-spring-spatial-default-duration),var(--md-sys-motion-spring-effects-default-duration)]',\n '[transition-timing-function:var(--md-sys-motion-spring-spatial-default-easing),var(--md-sys-motion-spring-effects-default-easing)]',\n // Reduced motion: the fade alone, still on the default effects spring.\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 // The height opens and closes only with motion: under reduced motion a leaving row\n // stays open and fades where it is (with no height transition, a closed height would\n // apply in the first frame and hide the fade).\n 'motion-safe:data-appear:starting:grid-rows-[0fr] data-appear:starting:opacity-0',\n 'motion-safe:data-exiting:grid-rows-[0fr] data-exiting:opacity-0',\n // A closing row is never the scroll anchor: the browser would pick it (its top stays\n // put) and let a row pressed below it slide up by its height. The pressed row is held\n // in place by the tree itself when the anchor is above it (2026-10-17).\n 'data-exiting:[overflow-anchor:none]',\n ],\n // Lets the row's height close to nothing inside the reveal.\n revealClip: 'flex min-h-0 flex-col overflow-hidden',\n // The drawn checkbox, radio button or switch of an item that is one. Decorative: the\n // item is the control, so the drawing only follows its state attributes.\n control: 'group/control inline-flex shrink-0 items-center justify-center',\n // A link item's leading and trailing content: above the link's overlay, but only its\n // controls take the pointer, so a press anywhere else reaches the link.\n raised:\n 'pointer-events-none relative z-1 [&_a]:pointer-events-auto [&_button]:pointer-events-auto [&_input]:pointer-events-auto [&_select]:pointer-events-auto [&_textarea]:pointer-events-auto',\n // A link item's anchor: its overlay covers the item, so the whole item is the link.\n link: \"outline-none after:absolute after:inset-0 after:content-['']\",\n },\n variants: {\n variant: {\n // Item colours are tokens: a standard item is see-through, so it takes its\n // container's colour (a sheet, a card); a segmented item is `surface`.\n standard: {},\n segmented: {\n // A tree's 2px gaps are inside each row's reveal (above every row but the first), so\n // a closing row takes its gap with it: a gap left between rows of no height would\n // shift what follows by 2px per row as the ghosts are removed, and the browser's\n // scroll anchoring then scrolled the page by as much (2026-10-17).\n root: 'gap-[2px] [&[role=treegrid]]:gap-0',\n group: 'gap-[2px]',\n reveal: 'not-first:[&>*>*]:mt-[2px]',\n },\n },\n interactive: {\n true: {\n item: [\n 'cursor-pointer state-layer focus-ring-inset select-none',\n 'data-disabled:cursor-default',\n // One shape state at a time (see `data-shape`), so no two rules compete.\n 'data-[shape=rest]:rounded-corner-extra-small',\n 'data-[shape=hovered]:rounded-corner-medium',\n 'data-[shape=active]:rounded-corner-large',\n ],\n },\n false: {},\n },\n lines: {\n 1: {\n item: 'min-h-[calc(56px+var(--md-sys-density,0)*4px)] items-center',\n cell: 'items-center',\n },\n 2: {\n item: 'min-h-[calc(72px+var(--md-sys-density,0)*4px)] items-center',\n cell: 'items-center',\n },\n 3: {\n item: 'min-h-[calc(88px+var(--md-sys-density,0)*4px)] items-start',\n cell: 'items-start',\n },\n },\n dividers: {\n none: {},\n full: {\n item: [\n \"relative after:pointer-events-none after:absolute after:inset-x-0 after:bottom-0 after:h-px after:bg-outline-variant after:content-['']\",\n 'data-[position=last]:after:hidden data-[position=only]:after:hidden',\n // A plain list's item is last in its group by structure too, so the server's HTML\n // is right before the list learns where its sections are (see \\`segmented\\`).\n '[&[data-list-entry]:where(:last-child,:has(+[data-slot=section]))]:after:hidden',\n '[[data-list-entry]:where(:last-child,:has(+[data-slot=section]))>&]:after:hidden',\n 'forced-colors:after:bg-[GrayText] forced-colors:after:forced-color-adjust-none',\n ],\n },\n inset: {\n item: [\n \"relative after:pointer-events-none after:absolute after:inset-x-[16px] after:bottom-0 after:h-px after:bg-outline-variant after:content-['']\",\n 'data-[position=last]:after:hidden data-[position=only]:after:hidden',\n // A plain list's item is last in its group by structure too, so the server's HTML\n // is right before the list learns where its sections are (see \\`segmented\\`).\n '[&[data-list-entry]:where(:last-child,:has(+[data-slot=section]))]:after:hidden',\n '[[data-list-entry]:where(:last-child,:has(+[data-slot=section]))>&]:after:hidden',\n 'forced-colors:after:bg-[GrayText] forced-colors:after:forced-color-adjust-none',\n ],\n },\n },\n // How many lines a headline or supporting text shows (the `text` slot's children).\n headlineLines: {\n 1: { headline: 'line-clamp-1' },\n 2: { headline: 'line-clamp-2' },\n 3: { headline: 'line-clamp-3' },\n },\n supportingTextLines: {\n 1: { supporting: 'line-clamp-1' },\n 2: { supporting: 'line-clamp-2' },\n 3: { supporting: 'line-clamp-3' },\n },\n // Nested items of an expandable list, indented 16dp per level (`--m3-list-level`).\n nested: {\n true: { item: 'ps-[calc(16px+var(--m3-list-level,0)*16px)]' },\n false: {},\n },\n // Whether a section shows a subheader.\n titled: { true: {}, false: {} },\n // Overrides the alignment that follows from the line count.\n align: {\n center: { item: 'items-center', cell: 'items-center' },\n top: { item: 'items-start', cell: 'items-start' },\n },\n },\n compoundVariants: [\n // Segmented sections without a subheader stand 16dp apart, so each reads as its own group.\n { variant: 'segmented', titled: false, class: { section: 'not-first:pt-[16px]' } },\n // Segmented lists round the outer corners of the group at rest (static items too).\n // A plain list's items (\\`data-list-entry\\`, the item or the \\`li\\` holding it) also take\n // them from their place among their siblings: a group starts at the first child or after\n // a section and ends at the last child or before one. The list learns which entries are\n // sections only once it renders on the client, so without these the server's HTML would\n // show loose items next to a section with square corners until hydration.\n {\n variant: 'segmented',\n class: {\n item: [\n 'data-[shape=rest]:rounded-corner-extra-small',\n 'data-[shape=rest]:data-[position=first]:rounded-t-corner-large',\n 'data-[shape=rest]:data-[position=last]:rounded-b-corner-large',\n 'data-[shape=rest]:data-[position=only]:rounded-corner-large',\n 'data-[shape=rest]:[&[data-list-entry]:where(:first-child,[data-slot=section]+*)]:rounded-t-corner-large',\n 'data-[shape=rest]:[[data-list-entry]:where(:first-child,[data-slot=section]+*)>&]:rounded-t-corner-large',\n 'data-[shape=rest]:[&[data-list-entry]:where(:last-child,:has(+[data-slot=section]))]:rounded-b-corner-large',\n 'data-[shape=rest]:[[data-list-entry]:where(:last-child,:has(+[data-slot=section]))>&]:rounded-b-corner-large',\n ],\n },\n },\n ],\n defaultVariants: { variant: 'standard', interactive: false, lines: 1, dividers: 'none' },\n});\n\n/**\n * The variants of {@link listStyles}, for building on its styles. They are the recipe's keys, not\n * component props: their names (`hasIcon`, `isOpen`…) follow the classes and may change.\n */\nexport type ListStyleProps = VariantProps<typeof listStyles>;\nexport type ListVariant = NonNullable<ListStyleProps['variant']>;\nexport type ListDividers = Exclude<NonNullable<ListStyleProps['dividers']>, 'none'>;\n\n/** Variant definitions for `ListItemImage`, `ListItemVideo` and `ListItemAvatar` (M3's leading media). */\nexport const listMediaStyles = tv({\n slots: {\n // 8dp corners (M3 Expressive's `corner-small`), the image cropped to fill, faded with a\n // disabled item. The tonal fill shows behind a fallback icon.\n frame: [\n 'relative flex shrink-0 items-center justify-center overflow-hidden',\n 'bg-secondary-container text-on-secondary-container [&>svg]:size-[24px] [&>svg]:fill-current',\n '[&>img]:size-full [&>img]:object-cover',\n 'group-data-disabled/list-item:opacity-38',\n ],\n badge: [\n 'absolute flex size-[24px] items-center justify-center rounded-full',\n 'bg-inverse-surface/70 text-inverse-on-surface [&>svg]:size-[16px] [&>svg]:fill-current',\n 'forced-colors:border forced-colors:border-solid forced-colors:border-[CanvasText]',\n ],\n duration:\n 'absolute end-[4px] bottom-[4px] rounded-corner-extra-small bg-inverse-surface/80 px-[4px] text-label-small text-inverse-on-surface',\n },\n variants: {\n kind: {\n image: { frame: 'size-[56px] rounded-corner-small' },\n video: { frame: 'rounded-corner-small' },\n // A 40dp circle in `primary-container`, its initials `title-medium`.\n avatar: {\n frame:\n 'size-[40px] rounded-full bg-primary-container text-title-medium text-on-primary-container',\n },\n },\n size: {\n sm: {},\n lg: {},\n },\n },\n compoundVariants: [\n { kind: 'video', size: 'sm', class: { frame: 'h-[56px] w-[100px]' } },\n { kind: 'video', size: 'lg', class: { frame: 'h-[64px] w-[114px]' } },\n ],\n defaultVariants: { kind: 'image', size: 'sm' },\n});\n"],"mappings":";;;AAoDA,MAAa,aAAa,GAAG;CAC3B,OAAO;EACL,MAAM;EAEN,SAAS;EAIT,WACE;EACF,OAAO;EACP,MAAM;GACJ;GACA;GACA;GACA;GACA;GACA;EACF;EAEA,MAAM;EACN,SAAS;GACP;GACA;GACA;EACF;EACA,MAAM;EACN,UAAU;GACR;GACA;GACA;EACF;EACA,UAAU;GACR;GACA;GACA;EACF;EACA,YAAY;GACV;GACA;GACA;EACF;EACA,UAAU;GACR;GACA;GACA;EACF;EAEA,oBAAoB;GAClB;GACA;GACA;EACF;EAEA,QAAQ;GACN;GACA;GACA;GACA;GACA;EACF;EAIA,QAAQ;GACN;GACA;GACA;GACA;GAEA;GACA;GAIA;GACA;GAIA;EACF;EAEA,YAAY;EAGZ,SAAS;EAGT,QACE;EAEF,MAAM;CACR;CACA,UAAU;EACR,SAAS;GAGP,UAAU,CAAC;GACX,WAAW;IAKT,MAAM;IACN,OAAO;IACP,QAAQ;GACV;EACF;EACA,aAAa;GACX,MAAM,EACJ,MAAM;IACJ;IACA;IAEA;IACA;IACA;GACF,EACF;GACA,OAAO,CAAC;EACV;EACA,OAAO;GACL,GAAG;IACD,MAAM;IACN,MAAM;GACR;GACA,GAAG;IACD,MAAM;IACN,MAAM;GACR;GACA,GAAG;IACD,MAAM;IACN,MAAM;GACR;EACF;EACA,UAAU;GACR,MAAM,CAAC;GACP,MAAM,EACJ,MAAM;IACJ;IACA;IAGA;IACA;IACA;GACF,EACF;GACA,OAAO,EACL,MAAM;IACJ;IACA;IAGA;IACA;IACA;GACF,EACF;EACF;EAEA,eAAe;GACb,GAAG,EAAE,UAAU,eAAe;GAC9B,GAAG,EAAE,UAAU,eAAe;GAC9B,GAAG,EAAE,UAAU,eAAe;EAChC;EACA,qBAAqB;GACnB,GAAG,EAAE,YAAY,eAAe;GAChC,GAAG,EAAE,YAAY,eAAe;GAChC,GAAG,EAAE,YAAY,eAAe;EAClC;EAEA,QAAQ;GACN,MAAM,EAAE,MAAM,8CAA8C;GAC5D,OAAO,CAAC;EACV;EAEA,QAAQ;GAAE,MAAM,CAAC;GAAG,OAAO,CAAC;EAAE;EAE9B,OAAO;GACL,QAAQ;IAAE,MAAM;IAAgB,MAAM;GAAe;GACrD,KAAK;IAAE,MAAM;IAAe,MAAM;GAAc;EAClD;CACF;CACA,kBAAkB,CAEhB;EAAE,SAAS;EAAa,QAAQ;EAAO,OAAO,EAAE,SAAS,sBAAsB;CAAE,GAOjF;EACE,SAAS;EACT,OAAO,EACL,MAAM;GACJ;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF,EACF;CACF,CACF;CACA,iBAAiB;EAAE,SAAS;EAAY,aAAa;EAAO,OAAO;EAAG,UAAU;CAAO;AACzF,CAAC;;AAWD,MAAa,kBAAkB,GAAG;CAChC,OAAO;EAGL,OAAO;GACL;GACA;GACA;GACA;EACF;EACA,OAAO;GACL;GACA;GACA;EACF;EACA,UACE;CACJ;CACA,UAAU;EACR,MAAM;GACJ,OAAO,EAAE,OAAO,mCAAmC;GACnD,OAAO,EAAE,OAAO,uBAAuB;GAEvC,QAAQ,EACN,OACE,4FACJ;EACF;EACA,MAAM;GACJ,IAAI,CAAC;GACL,IAAI,CAAC;EACP;CACF;CACA,kBAAkB,CAChB;EAAE,MAAM;EAAS,MAAM;EAAM,OAAO,EAAE,OAAO,qBAAqB;CAAE,GACpE;EAAE,MAAM;EAAS,MAAM;EAAM,OAAO,EAAE,OAAO,qBAAqB;CAAE,CACtE;CACA,iBAAiB;EAAE,MAAM;EAAS,MAAM;CAAK;AAC/C,CAAC"}
1
+ {"version":3,"file":"list-styles.js","names":[],"sources":["../../../src/components/list/list-styles.ts"],"sourcesContent":["import { tv, type VariantProps } from '../../utils/tv';\n\n/*\n * Values follow the M3 Expressive list spec (m3.material.io) with Compose Material 3's\n * Expressive list tokens (ListItem.kt's interactive overloads, SegmentedListItem,\n * ListItemDefaults, ListTokens' `*Expressive*` values; 2026-10-13).\n *\n * Item: 16px start / end and 10px top / bottom padding, 12px between leading content, the\n * text and trailing content. The 12px belongs to the leading and trailing cells (rendered\n * only when present), so a text-only item's text spans 16px to 16px. At least 56px tall with one line, 72px with two (overline or\n * supporting text) and 88px with three (both), where content aligns to the top. Headline\n * `body-large` `on-surface`; supporting text `body-medium`, overline `label-small`, leading\n * and trailing content `on-surface-variant` (trailing text `label-small`, 20px icons). Selected\n * items show a trailing check as a second cue besides colour. Selected items\n * are `secondary-container` with `on-secondary-container` content (the `selected-*` tokens);\n * a switch item that's on stays plain unless it asks for them (`selectedWhenChecked`);\n * disabled content is `on-surface` 38%.\n *\n * Shape (interactive items): 4px at rest, 12px hovered, 16px focused, selected or pressed,\n * morphing on the fast spatial spring; colours change on the default effects spring.\n * Segmented lists put 2px between items and round the outer corners of the first and last\n * items to 16px at rest. Static items in a standard list have no corners.\n *\n * Leading images are 56dp squares and video thumbnails 100 × 56dp (small) or 114 × 64dp\n * (large), all with 8dp corners; avatars are 40dp circles in `primary-container`\n * (`ListMedia.tsx`). The item grows to fit them. Expandable items indent their nested items\n * 16dp per level and end with a chevron that turns over on the fast spatial spring; their\n * nested items open and close their height on the default spatial spring while fading on\n * the default effects spring (only the fade under reduced motion). Dividers (standard lists) are 1px of\n * `outline-variant` on the item's bottom edge, full width or 16dp in from each side, drawn\n * by its `::after` (the state layer uses its background), never under the last item.\n *\n * Web density (`--md-sys-density`, 0 to -3; M3: a list \"can adjust margins, spacing, or\n * density\"): each step takes 2px off the padding above and below an item (10 → 4px) and 4px off\n * its minimum height (56 → 44px, 72 → 60px, 88 → 76px), so items that media make taller lose\n * 4px a step too, as in Material Components Web's list density (every item height 4px a step).\n * Section subheaders lose 4px a step (48 → 36px). Items are stacked rows, so dense ones keep\n * non-overlapping full-row targets above WCAG 2.5.8's 24px, and the expand chevron's target is\n * at most a one-line item's height.\n *\n * Sections (`ListSection`) put a subheader above their items: 48dp tall, `title-small`\n * `on-surface-variant`, 16dp from each side. The M3 spec defines no list subheader, so this\n * follows the deprecated navigation drawer's section headline. Items take their position\n * (segmented corners, dividers) within their section.\n *\n * Item colours come from the list's component tokens (`list-tokens.ts`,\n * `--md-comp-list-list-item-*`): the root and each item carry `data-tokens=\"md-comp-list\"`\n * and `data-variant`. A consumer class still wins in `cn()`.\n *\n * Every class is written out in full so Tailwind can find it.\n */\n\n/** Variant definitions for {@link List}; extend them to add variants. */\nexport const listStyles = tv({\n slots: {\n root: 'm-0 flex list-none flex-col p-0 outline-none',\n // A `ListSection`: its subheader, then its items' group.\n section: 'flex flex-col',\n // The M3 spec has no list subheader; this follows the deprecated navigation drawer's\n // section headline (`title-small`, `on-surface-variant`), on a 48dp row with the items'\n // 16dp side padding, so its text lines up with the headlines below it.\n subheader:\n 'flex min-h-[calc(48px+var(--md-sys-density,0)*4px)] items-center ps-[16px] pe-[16px] text-title-small text-on-surface-variant',\n group: 'm-0 flex list-none flex-col p-0',\n item: [\n 'group/list-item flex ps-[16px] pe-[16px] pt-[calc(10px+var(--md-sys-density,0)*2px)] pb-[calc(10px+var(--md-sys-density,0)*2px)] text-start outline-none',\n 'bg-[var(--_md-comp-list-container-color)] data-selected:bg-[var(--_md-comp-list-list-item-selected-container-color)]',\n '[--m3-transition-property:border-radius,background-color,color]',\n '[--m3-transition-duration:var(--md-sys-motion-spring-spatial-fast-duration),var(--md-sys-motion-spring-effects-default-duration),var(--md-sys-motion-spring-effects-default-duration)]',\n '[--m3-transition-easing:var(--md-sys-motion-spring-spatial-fast-easing),var(--md-sys-motion-spring-effects-default-easing),var(--md-sys-motion-spring-effects-default-easing)]',\n '[--m3-transition-delay:0s,0s,0s]',\n ],\n // The item's content grid (the grid cell of an interactive item).\n cell: 'grid w-full grid-cols-[auto_minmax(0,1fr)_auto]',\n leading: [\n 'col-start-1 me-[12px] flex items-center gap-x-[12px] text-title-medium text-[color:var(--_md-comp-list-list-item-leading-icon-color)] [&>svg]:size-[20px]',\n 'group-data-selected/list-item:text-[color:var(--_md-comp-list-list-item-selected-leading-icon-color)]',\n 'group-data-disabled/list-item:text-on-surface/38',\n ],\n text: 'col-start-2 flex min-w-0 flex-col',\n overline: [\n 'text-label-small text-[color:var(--_md-comp-list-list-item-overline-color)]',\n 'group-data-selected/list-item:text-[color:var(--_md-comp-list-list-item-selected-overline-color)]',\n 'group-data-disabled/list-item:text-on-surface/38',\n ],\n headline: [\n 'text-body-large text-[color:var(--_md-comp-list-list-item-label-text-color)]',\n 'group-data-selected/list-item:text-[color:var(--_md-comp-list-list-item-selected-label-text-color)]',\n 'group-data-disabled/list-item:text-on-surface/38',\n ],\n supporting: [\n 'text-body-medium text-[color:var(--_md-comp-list-list-item-supporting-text-color)]',\n 'group-data-selected/list-item:text-[color:var(--_md-comp-list-list-item-selected-supporting-text-color)]',\n 'group-data-disabled/list-item:text-on-surface/38',\n ],\n trailing: [\n 'col-start-3 ms-[12px] flex items-center gap-x-[12px] text-label-small text-[color:var(--_md-comp-list-list-item-trailing-icon-color)] [&>svg]:size-[20px]',\n 'group-data-selected/list-item:text-[color:var(--_md-comp-list-list-item-selected-trailing-icon-color)]',\n 'group-data-disabled/list-item:text-on-surface/38',\n ],\n // The check a selected item shows besides its colour (M3: two cues for selection).\n selectionIndicator: [\n 'inline-flex size-[20px] shrink-0 items-center justify-center opacity-0 [&>svg]:size-full',\n 'group-data-selected/list-item:opacity-100 group-data-selected/list-item:text-[color:var(--_md-comp-list-list-item-selected-trailing-icon-color)]',\n 'transition-opacity duration-(--md-sys-motion-spring-effects-fast-duration) ease-(--md-sys-motion-spring-effects-fast-easing)',\n ],\n // An expandable item's chevron: a button (out of the Tab order) with a 48px target.\n expand: [\n 'relative inline-flex size-[24px] shrink-0 cursor-pointer items-center justify-center rounded-full',\n 'state-layer [&>svg]:size-[20px] [&>svg]:fill-current',\n '[&>svg]:transition-transform [&>svg]:duration-(--md-sys-motion-spring-spatial-fast-duration) [&>svg]:ease-(--md-sys-motion-spring-spatial-fast-easing)',\n 'group-aria-expanded/list-item:[&>svg]:rotate-180 motion-reduce:[&>svg]:transition-none',\n 'data-disabled:cursor-default',\n ],\n // A tree row's wrapper: nested rows open and close their height (grid rows 0fr ↔ 1fr)\n // on the default spatial spring and fade on the default effects spring; under reduced\n // motion they only fade.\n reveal: [\n 'grid grid-rows-[1fr] opacity-100',\n '[transition-property:grid-template-rows,opacity] motion-reduce:[transition-property:opacity]',\n '[transition-duration:var(--md-sys-motion-spring-spatial-default-duration),var(--md-sys-motion-spring-effects-default-duration)]',\n '[transition-timing-function:var(--md-sys-motion-spring-spatial-default-easing),var(--md-sys-motion-spring-effects-default-easing)]',\n // Reduced motion: the fade alone, still on the default effects spring.\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 // The height opens and closes only with motion: under reduced motion a leaving row\n // stays open and fades where it is (with no height transition, a closed height would\n // apply in the first frame and hide the fade).\n 'motion-safe:data-appear:starting:grid-rows-[0fr] data-appear:starting:opacity-0',\n 'motion-safe:data-exiting:grid-rows-[0fr] data-exiting:opacity-0',\n // A closing row is never the scroll anchor: the browser would pick it (its top stays\n // put) and let a row pressed below it slide up by its height. The pressed row is held\n // in place by the tree itself when the anchor is above it (2026-10-17).\n 'data-exiting:[overflow-anchor:none]',\n ],\n // Lets the row's height close to nothing inside the reveal.\n revealClip: 'flex min-h-0 flex-col overflow-hidden',\n // The drawn checkbox, radio button or switch of an item that is one. Decorative: the\n // item is the control, so the drawing only follows its state attributes.\n control: 'group/control inline-flex shrink-0 items-center justify-center',\n // A link item's leading and trailing content: above the link's overlay, but only its\n // controls take the pointer, so a press anywhere else reaches the link.\n raised:\n 'pointer-events-none relative z-1 [&_a]:pointer-events-auto [&_button]:pointer-events-auto [&_input]:pointer-events-auto [&_select]:pointer-events-auto [&_textarea]:pointer-events-auto',\n // A link item's anchor: its overlay covers the item, so the whole item is the link.\n link: \"outline-none after:absolute after:inset-0 after:content-['']\",\n },\n variants: {\n variant: {\n // Item colours are tokens: a standard item is see-through, so it takes its\n // container's colour (a sheet, a card); a segmented item is `surface`.\n standard: {},\n segmented: {\n // A tree's 2px gaps are inside each row's reveal (above every row but the first), so\n // a closing row takes its gap with it: a gap left between rows of no height would\n // shift what follows by 2px per row as the ghosts are removed, and the browser's\n // scroll anchoring then scrolled the page by as much (2026-10-17).\n root: 'gap-[2px] [&[role=treegrid]]:gap-0',\n group: 'gap-[2px]',\n reveal: 'not-first:[&>*>*]:mt-[2px]',\n },\n },\n interactive: {\n true: {\n item: [\n 'cursor-pointer state-layer focus-ring-inset select-none',\n 'data-disabled:cursor-default',\n // One shape state at a time (see `data-shape`), so no two rules compete.\n 'data-[shape=rest]:rounded-corner-extra-small',\n 'data-[shape=hovered]:rounded-corner-medium',\n 'data-[shape=active]:rounded-corner-large',\n ],\n },\n false: {},\n },\n lines: {\n 1: {\n item: 'min-h-[calc(56px+var(--md-sys-density,0)*4px)] items-center',\n cell: 'items-center',\n },\n 2: {\n item: 'min-h-[calc(72px+var(--md-sys-density,0)*4px)] items-center',\n cell: 'items-center',\n },\n 3: {\n item: 'min-h-[calc(88px+var(--md-sys-density,0)*4px)] items-start',\n cell: 'items-start',\n },\n },\n dividers: {\n none: {},\n full: {\n item: [\n \"relative after:pointer-events-none after:absolute after:inset-x-0 after:bottom-0 after:h-px after:bg-outline-variant after:content-['']\",\n 'data-[position=last]:after:hidden data-[position=only]:after:hidden',\n // A plain list's item is last in its group by structure too, so the server's HTML\n // is right before the list learns where its sections are (see \\`segmented\\`).\n '[&[data-list-entry]:where(:last-child,:has(+[data-slot=section]))]:after:hidden',\n '[[data-list-entry]:where(:last-child,:has(+[data-slot=section]))>&]:after:hidden',\n 'forced-colors:after:bg-[GrayText] forced-colors:after:forced-color-adjust-none',\n ],\n },\n inset: {\n item: [\n \"relative after:pointer-events-none after:absolute after:inset-x-[16px] after:bottom-0 after:h-px after:bg-outline-variant after:content-['']\",\n 'data-[position=last]:after:hidden data-[position=only]:after:hidden',\n // A plain list's item is last in its group by structure too, so the server's HTML\n // is right before the list learns where its sections are (see \\`segmented\\`).\n '[&[data-list-entry]:where(:last-child,:has(+[data-slot=section]))]:after:hidden',\n '[[data-list-entry]:where(:last-child,:has(+[data-slot=section]))>&]:after:hidden',\n 'forced-colors:after:bg-[GrayText] forced-colors:after:forced-color-adjust-none',\n ],\n },\n },\n // How many lines a headline or supporting text shows (the `text` slot's children).\n headlineLines: {\n 1: { headline: 'line-clamp-1' },\n 2: { headline: 'line-clamp-2' },\n 3: { headline: 'line-clamp-3' },\n },\n supportingTextLines: {\n 1: { supporting: 'line-clamp-1' },\n 2: { supporting: 'line-clamp-2' },\n 3: { supporting: 'line-clamp-3' },\n },\n // Nested items of an expandable list, indented 16dp per level (`--m3-list-level`).\n nested: {\n true: { item: 'ps-[calc(16px+var(--m3-list-level,0)*16px)]' },\n false: {},\n },\n // Whether a section shows a subheader.\n titled: { true: {}, false: {} },\n // Overrides the alignment that follows from the line count.\n align: {\n center: { item: 'items-center', cell: 'items-center' },\n top: { item: 'items-start', cell: 'items-start' },\n },\n },\n compoundVariants: [\n // Segmented sections without a subheader stand 16dp apart, so each reads as its own group.\n { variant: 'segmented', titled: false, class: { section: 'not-first:pt-[16px]' } },\n // Segmented lists round the outer corners of the group at rest (static items too).\n // A plain list's items (\\`data-list-entry\\`, the item or the \\`li\\` holding it) also take\n // them from their place among their siblings: a group starts at the first child or after\n // a section and ends at the last child or before one. The list learns which entries are\n // sections only once it renders on the client, so without these the server's HTML would\n // show loose items next to a section with square corners until hydration.\n {\n variant: 'segmented',\n class: {\n item: [\n 'data-[shape=rest]:rounded-corner-extra-small',\n 'data-[shape=rest]:data-[position=first]:rounded-t-corner-large',\n 'data-[shape=rest]:data-[position=last]:rounded-b-corner-large',\n 'data-[shape=rest]:data-[position=only]:rounded-corner-large',\n 'data-[shape=rest]:[&[data-list-entry]:where(:first-child,[data-slot=section]+*)]:rounded-t-corner-large',\n 'data-[shape=rest]:[[data-list-entry]:where(:first-child,[data-slot=section]+*)>&]:rounded-t-corner-large',\n 'data-[shape=rest]:[&[data-list-entry]:where(:last-child,:has(+[data-slot=section]))]:rounded-b-corner-large',\n 'data-[shape=rest]:[[data-list-entry]:where(:last-child,:has(+[data-slot=section]))>&]:rounded-b-corner-large',\n ],\n },\n },\n ],\n defaultVariants: { variant: 'standard', interactive: false, lines: 1, dividers: 'none' },\n});\n\n/**\n * The variants of {@link listStyles}, for building on its styles. They are the recipe's keys, not\n * component props: their names (`hasIcon`, `isOpen`…) follow the classes and may change.\n */\nexport type ListStyleProps = VariantProps<typeof listStyles>;\nexport type ListVariant = NonNullable<ListStyleProps['variant']>;\nexport type ListDividers = Exclude<NonNullable<ListStyleProps['dividers']>, 'none'>;\n\n/** Variant definitions for `ListItemImage`, `ListItemVideo` and `ListItemAvatar` (M3's leading media). */\nexport const listMediaStyles = tv({\n slots: {\n // 8dp corners (M3 Expressive's `corner-small`), the image cropped to fill, faded with a\n // disabled item. The tonal fill shows behind a fallback icon.\n frame: [\n 'relative flex shrink-0 items-center justify-center overflow-hidden',\n 'bg-secondary-container text-on-secondary-container [&>svg]:size-[24px] [&>svg]:fill-current',\n '[&>img]:size-full [&>img]:object-cover',\n 'group-data-disabled/list-item:opacity-38',\n ],\n badge: [\n 'absolute flex size-[24px] items-center justify-center rounded-full',\n 'bg-inverse-surface/70 text-inverse-on-surface [&>svg]:size-[16px] [&>svg]:fill-current',\n 'forced-colors:border forced-colors:border-solid forced-colors:border-[CanvasText]',\n ],\n duration:\n 'absolute end-[4px] bottom-[4px] rounded-corner-extra-small bg-inverse-surface/80 px-[4px] text-label-small text-inverse-on-surface',\n },\n variants: {\n kind: {\n image: { frame: 'size-[56px] rounded-corner-small' },\n video: { frame: 'rounded-corner-small' },\n // A 40dp circle in `primary-container`, its initials `title-medium`.\n avatar: {\n frame:\n 'size-[40px] rounded-full bg-primary-container text-title-medium text-on-primary-container',\n },\n },\n size: {\n sm: {},\n lg: {},\n },\n },\n compoundVariants: [\n { kind: 'video', size: 'sm', class: { frame: 'h-[56px] w-[100px]' } },\n { kind: 'video', size: 'lg', class: { frame: 'h-[64px] w-[114px]' } },\n ],\n defaultVariants: { kind: 'image', size: 'sm' },\n});\n"],"mappings":";;;AAqDA,MAAa,aAAa,GAAG;CAC3B,OAAO;EACL,MAAM;EAEN,SAAS;EAIT,WACE;EACF,OAAO;EACP,MAAM;GACJ;GACA;GACA;GACA;GACA;GACA;EACF;EAEA,MAAM;EACN,SAAS;GACP;GACA;GACA;EACF;EACA,MAAM;EACN,UAAU;GACR;GACA;GACA;EACF;EACA,UAAU;GACR;GACA;GACA;EACF;EACA,YAAY;GACV;GACA;GACA;EACF;EACA,UAAU;GACR;GACA;GACA;EACF;EAEA,oBAAoB;GAClB;GACA;GACA;EACF;EAEA,QAAQ;GACN;GACA;GACA;GACA;GACA;EACF;EAIA,QAAQ;GACN;GACA;GACA;GACA;GAEA;GACA;GAIA;GACA;GAIA;EACF;EAEA,YAAY;EAGZ,SAAS;EAGT,QACE;EAEF,MAAM;CACR;CACA,UAAU;EACR,SAAS;GAGP,UAAU,CAAC;GACX,WAAW;IAKT,MAAM;IACN,OAAO;IACP,QAAQ;GACV;EACF;EACA,aAAa;GACX,MAAM,EACJ,MAAM;IACJ;IACA;IAEA;IACA;IACA;GACF,EACF;GACA,OAAO,CAAC;EACV;EACA,OAAO;GACL,GAAG;IACD,MAAM;IACN,MAAM;GACR;GACA,GAAG;IACD,MAAM;IACN,MAAM;GACR;GACA,GAAG;IACD,MAAM;IACN,MAAM;GACR;EACF;EACA,UAAU;GACR,MAAM,CAAC;GACP,MAAM,EACJ,MAAM;IACJ;IACA;IAGA;IACA;IACA;GACF,EACF;GACA,OAAO,EACL,MAAM;IACJ;IACA;IAGA;IACA;IACA;GACF,EACF;EACF;EAEA,eAAe;GACb,GAAG,EAAE,UAAU,eAAe;GAC9B,GAAG,EAAE,UAAU,eAAe;GAC9B,GAAG,EAAE,UAAU,eAAe;EAChC;EACA,qBAAqB;GACnB,GAAG,EAAE,YAAY,eAAe;GAChC,GAAG,EAAE,YAAY,eAAe;GAChC,GAAG,EAAE,YAAY,eAAe;EAClC;EAEA,QAAQ;GACN,MAAM,EAAE,MAAM,8CAA8C;GAC5D,OAAO,CAAC;EACV;EAEA,QAAQ;GAAE,MAAM,CAAC;GAAG,OAAO,CAAC;EAAE;EAE9B,OAAO;GACL,QAAQ;IAAE,MAAM;IAAgB,MAAM;GAAe;GACrD,KAAK;IAAE,MAAM;IAAe,MAAM;GAAc;EAClD;CACF;CACA,kBAAkB,CAEhB;EAAE,SAAS;EAAa,QAAQ;EAAO,OAAO,EAAE,SAAS,sBAAsB;CAAE,GAOjF;EACE,SAAS;EACT,OAAO,EACL,MAAM;GACJ;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF,EACF;CACF,CACF;CACA,iBAAiB;EAAE,SAAS;EAAY,aAAa;EAAO,OAAO;EAAG,UAAU;CAAO;AACzF,CAAC;;AAWD,MAAa,kBAAkB,GAAG;CAChC,OAAO;EAGL,OAAO;GACL;GACA;GACA;GACA;EACF;EACA,OAAO;GACL;GACA;GACA;EACF;EACA,UACE;CACJ;CACA,UAAU;EACR,MAAM;GACJ,OAAO,EAAE,OAAO,mCAAmC;GACnD,OAAO,EAAE,OAAO,uBAAuB;GAEvC,QAAQ,EACN,OACE,4FACJ;EACF;EACA,MAAM;GACJ,IAAI,CAAC;GACL,IAAI,CAAC;EACP;CACF;CACA,kBAAkB,CAChB;EAAE,MAAM;EAAS,MAAM;EAAM,OAAO,EAAE,OAAO,qBAAqB;CAAE,GACpE;EAAE,MAAM;EAAS,MAAM;EAAM,OAAO,EAAE,OAAO,qBAAqB;CAAE,CACtE;CACA,iBAAiB;EAAE,MAAM;EAAS,MAAM;CAAK;AAC/C,CAAC"}
@@ -2,6 +2,6 @@ import { ComponentTokenSet } from "../../tokens/component.js";
2
2
  import "../../index.js";
3
3
  //#region src/components/list/list-tokens.d.ts
4
4
  /** The tokens of {@link List}: `--md-comp-list-list-item-<part>`. */
5
- export declare const listTokenSet: ComponentTokenSet<"list-item-label-text-color" | "list-item-supporting-text-color" | "list-item-overline-color" | "list-item-leading-icon-color" | "list-item-trailing-icon-color" | "list-item-selected-container-color">;
5
+ export declare const listTokenSet: ComponentTokenSet<"list-item-label-text-color" | "list-item-supporting-text-color" | "list-item-overline-color" | "list-item-leading-icon-color" | "list-item-trailing-icon-color" | "list-item-selected-container-color" | "list-item-selected-label-text-color" | "list-item-selected-supporting-text-color" | "list-item-selected-overline-color" | "list-item-selected-leading-icon-color" | "list-item-selected-trailing-icon-color">;
6
6
  //#endregion
7
7
  //# sourceMappingURL=list-tokens.d.ts.map
@@ -57,6 +57,56 @@ const listTokenSet = defineComponentTokens({
57
57
  property: "background-color",
58
58
  state: "selected"
59
59
  }
60
+ },
61
+ "list-item-selected-label-text-color": {
62
+ value: color("on-secondary-container"),
63
+ description: "A selected item's headline.",
64
+ compose: "ListTokens.ItemSelectedLabelTextColor",
65
+ paints: {
66
+ property: "color",
67
+ slot: "headline",
68
+ state: "selected"
69
+ }
70
+ },
71
+ "list-item-selected-supporting-text-color": {
72
+ value: color("on-secondary-container"),
73
+ description: "A selected item's supporting text.",
74
+ compose: "ListTokens.ItemSelectedSupportingTextColor",
75
+ paints: {
76
+ property: "color",
77
+ slot: "supporting",
78
+ state: "selected"
79
+ }
80
+ },
81
+ "list-item-selected-overline-color": {
82
+ value: color("on-secondary-container"),
83
+ description: "A selected item's overline.",
84
+ compose: "ListTokens.ItemSelectedOverlineColor",
85
+ paints: {
86
+ property: "color",
87
+ slot: "overline",
88
+ state: "selected"
89
+ }
90
+ },
91
+ "list-item-selected-leading-icon-color": {
92
+ value: color("on-secondary-container"),
93
+ description: "A selected item's leading content.",
94
+ compose: "ListTokens.ItemSelectedLeadingIconColor",
95
+ paints: {
96
+ property: "color",
97
+ slot: "leading",
98
+ state: "selected"
99
+ }
100
+ },
101
+ "list-item-selected-trailing-icon-color": {
102
+ value: color("on-secondary-container"),
103
+ description: "A selected item's trailing content and its check.",
104
+ compose: "ListTokens.ItemSelectedTrailingIconColor",
105
+ paints: {
106
+ property: "color",
107
+ slot: "trailing",
108
+ state: "selected"
109
+ }
60
110
  }
61
111
  },
62
112
  scopes: { variant: {
@@ -1 +1 @@
1
- {"version":3,"file":"list-tokens.js","names":[],"sources":["../../../src/components/list/list-tokens.ts"],"sourcesContent":["import { color, defineComponentTokens } from '../../tokens/component';\n\n/*\n * The list's component tokens, M3's `md.comp.list.list-item.*` names (checked against\n * m3.material.io's token tables, 2026-10-14), with the defaults of `list-styles.ts`.\n *\n * The list's root and each item carry `data-tokens=\"md-comp-list\"` and `data-variant`, so an\n * item's own state (`data-selected`) picks its colours. A standard item is see-through by\n * default (it takes its container's colour: a sheet, a card); a segmented item is `surface`.\n * Selected content, hover, focus, press, disabled and the item shapes stay classes.\n */\n\n/** The tokens of {@link List}: `--md-comp-list-list-item-<part>`. */\nexport const listTokenSet = defineComponentTokens({\n name: 'md-comp-list',\n tokens: {\n 'list-item-label-text-color': {\n value: color('on-surface'),\n description: 'The headline.',\n compose: 'ListTokens.ItemLabelTextColor',\n paints: { property: 'color', slot: 'headline' },\n },\n 'list-item-supporting-text-color': {\n value: color('on-surface-variant'),\n description: 'The supporting text.',\n compose: 'ListTokens.ItemSupportingTextColor',\n paints: { property: 'color', slot: 'supporting' },\n },\n 'list-item-overline-color': {\n value: color('on-surface-variant'),\n description: 'The overline.',\n compose: 'ListTokens.ItemOverlineColor',\n paints: { property: 'color', slot: 'overline' },\n },\n 'list-item-leading-icon-color': {\n value: color('on-surface-variant'),\n description: 'The leading content: an icon, or text.',\n compose: 'ListTokens.ItemLeadingIconColor',\n paints: { property: 'color', slot: 'leading' },\n },\n 'list-item-trailing-icon-color': {\n value: color('on-surface-variant'),\n description: 'The trailing content: an icon, or supporting text.',\n compose: 'ListTokens.ItemTrailingIconColor',\n paints: { property: 'color', slot: 'trailing' },\n },\n 'list-item-selected-container-color': {\n value: color('secondary-container'),\n description: 'A selected item.',\n compose: 'ListTokens.ItemSelectedContainerColor',\n paints: { property: 'background-color', state: 'selected' },\n },\n },\n scopes: {\n variant: {\n standard: {\n prefix: 'md-comp-list-list-item',\n tokens: {\n 'container-color': {\n value: 'transparent',\n description: \"An item in a standard list: see-through, on its container's colour.\",\n compose: 'ListTokens.ItemContainerColor',\n paints: { property: 'background-color', slot: 'item' },\n },\n },\n },\n segmented: {\n prefix: 'md-comp-list-list-item-segmented',\n tokens: {\n 'container-color': {\n value: color('surface'),\n description: 'An item in a segmented list.',\n compose: 'ListTokens.ItemSegmentedContainerColor',\n paints: { property: 'background-color', slot: 'item' },\n },\n },\n },\n },\n },\n});\n"],"mappings":";;;AAaA,MAAa,eAAe,sBAAsB;CAChD,MAAM;CACN,QAAQ;EACN,8BAA8B;GAC5B,OAAO,MAAM,YAAY;GACzB,aAAa;GACb,SAAS;GACT,QAAQ;IAAE,UAAU;IAAS,MAAM;GAAW;EAChD;EACA,mCAAmC;GACjC,OAAO,MAAM,oBAAoB;GACjC,aAAa;GACb,SAAS;GACT,QAAQ;IAAE,UAAU;IAAS,MAAM;GAAa;EAClD;EACA,4BAA4B;GAC1B,OAAO,MAAM,oBAAoB;GACjC,aAAa;GACb,SAAS;GACT,QAAQ;IAAE,UAAU;IAAS,MAAM;GAAW;EAChD;EACA,gCAAgC;GAC9B,OAAO,MAAM,oBAAoB;GACjC,aAAa;GACb,SAAS;GACT,QAAQ;IAAE,UAAU;IAAS,MAAM;GAAU;EAC/C;EACA,iCAAiC;GAC/B,OAAO,MAAM,oBAAoB;GACjC,aAAa;GACb,SAAS;GACT,QAAQ;IAAE,UAAU;IAAS,MAAM;GAAW;EAChD;EACA,sCAAsC;GACpC,OAAO,MAAM,qBAAqB;GAClC,aAAa;GACb,SAAS;GACT,QAAQ;IAAE,UAAU;IAAoB,OAAO;GAAW;EAC5D;CACF;CACA,QAAQ,EACN,SAAS;EACP,UAAU;GACR,QAAQ;GACR,QAAQ,EACN,mBAAmB;IACjB,OAAO;IACP,aAAa;IACb,SAAS;IACT,QAAQ;KAAE,UAAU;KAAoB,MAAM;IAAO;GACvD,EACF;EACF;EACA,WAAW;GACT,QAAQ;GACR,QAAQ,EACN,mBAAmB;IACjB,OAAO,MAAM,SAAS;IACtB,aAAa;IACb,SAAS;IACT,QAAQ;KAAE,UAAU;KAAoB,MAAM;IAAO;GACvD,EACF;EACF;CACF,EACF;AACF,CAAC"}
1
+ {"version":3,"file":"list-tokens.js","names":[],"sources":["../../../src/components/list/list-tokens.ts"],"sourcesContent":["import { color, defineComponentTokens } from '../../tokens/component';\n\n/*\n * The list's component tokens, M3's `md.comp.list.list-item.*` names (checked against\n * m3.material.io's token tables, 2026-10-14), with the defaults of `list-styles.ts`.\n *\n * The list's root and each item carry `data-tokens=\"md-comp-list\"` and `data-variant`, so an\n * item's own state (`data-selected`) picks its colours. A standard item is see-through by\n * default (it takes its container's colour: a sheet, a card); a segmented item is `surface`.\n * Hover, focus, press, disabled and the item shapes stay classes.\n */\n\n/** The tokens of {@link List}: `--md-comp-list-list-item-<part>`. */\nexport const listTokenSet = defineComponentTokens({\n name: 'md-comp-list',\n tokens: {\n 'list-item-label-text-color': {\n value: color('on-surface'),\n description: 'The headline.',\n compose: 'ListTokens.ItemLabelTextColor',\n paints: { property: 'color', slot: 'headline' },\n },\n 'list-item-supporting-text-color': {\n value: color('on-surface-variant'),\n description: 'The supporting text.',\n compose: 'ListTokens.ItemSupportingTextColor',\n paints: { property: 'color', slot: 'supporting' },\n },\n 'list-item-overline-color': {\n value: color('on-surface-variant'),\n description: 'The overline.',\n compose: 'ListTokens.ItemOverlineColor',\n paints: { property: 'color', slot: 'overline' },\n },\n 'list-item-leading-icon-color': {\n value: color('on-surface-variant'),\n description: 'The leading content: an icon, or text.',\n compose: 'ListTokens.ItemLeadingIconColor',\n paints: { property: 'color', slot: 'leading' },\n },\n 'list-item-trailing-icon-color': {\n value: color('on-surface-variant'),\n description: 'The trailing content: an icon, or supporting text.',\n compose: 'ListTokens.ItemTrailingIconColor',\n paints: { property: 'color', slot: 'trailing' },\n },\n 'list-item-selected-container-color': {\n value: color('secondary-container'),\n description: 'A selected item.',\n compose: 'ListTokens.ItemSelectedContainerColor',\n paints: { property: 'background-color', state: 'selected' },\n },\n 'list-item-selected-label-text-color': {\n value: color('on-secondary-container'),\n description: \"A selected item's headline.\",\n compose: 'ListTokens.ItemSelectedLabelTextColor',\n paints: { property: 'color', slot: 'headline', state: 'selected' },\n },\n 'list-item-selected-supporting-text-color': {\n value: color('on-secondary-container'),\n description: \"A selected item's supporting text.\",\n compose: 'ListTokens.ItemSelectedSupportingTextColor',\n paints: { property: 'color', slot: 'supporting', state: 'selected' },\n },\n 'list-item-selected-overline-color': {\n value: color('on-secondary-container'),\n description: \"A selected item's overline.\",\n compose: 'ListTokens.ItemSelectedOverlineColor',\n paints: { property: 'color', slot: 'overline', state: 'selected' },\n },\n 'list-item-selected-leading-icon-color': {\n value: color('on-secondary-container'),\n description: \"A selected item's leading content.\",\n compose: 'ListTokens.ItemSelectedLeadingIconColor',\n paints: { property: 'color', slot: 'leading', state: 'selected' },\n },\n 'list-item-selected-trailing-icon-color': {\n value: color('on-secondary-container'),\n description: \"A selected item's trailing content and its check.\",\n compose: 'ListTokens.ItemSelectedTrailingIconColor',\n paints: { property: 'color', slot: 'trailing', state: 'selected' },\n },\n },\n scopes: {\n variant: {\n standard: {\n prefix: 'md-comp-list-list-item',\n tokens: {\n 'container-color': {\n value: 'transparent',\n description: \"An item in a standard list: see-through, on its container's colour.\",\n compose: 'ListTokens.ItemContainerColor',\n paints: { property: 'background-color', slot: 'item' },\n },\n },\n },\n segmented: {\n prefix: 'md-comp-list-list-item-segmented',\n tokens: {\n 'container-color': {\n value: color('surface'),\n description: 'An item in a segmented list.',\n compose: 'ListTokens.ItemSegmentedContainerColor',\n paints: { property: 'background-color', slot: 'item' },\n },\n },\n },\n },\n },\n});\n"],"mappings":";;;AAaA,MAAa,eAAe,sBAAsB;CAChD,MAAM;CACN,QAAQ;EACN,8BAA8B;GAC5B,OAAO,MAAM,YAAY;GACzB,aAAa;GACb,SAAS;GACT,QAAQ;IAAE,UAAU;IAAS,MAAM;GAAW;EAChD;EACA,mCAAmC;GACjC,OAAO,MAAM,oBAAoB;GACjC,aAAa;GACb,SAAS;GACT,QAAQ;IAAE,UAAU;IAAS,MAAM;GAAa;EAClD;EACA,4BAA4B;GAC1B,OAAO,MAAM,oBAAoB;GACjC,aAAa;GACb,SAAS;GACT,QAAQ;IAAE,UAAU;IAAS,MAAM;GAAW;EAChD;EACA,gCAAgC;GAC9B,OAAO,MAAM,oBAAoB;GACjC,aAAa;GACb,SAAS;GACT,QAAQ;IAAE,UAAU;IAAS,MAAM;GAAU;EAC/C;EACA,iCAAiC;GAC/B,OAAO,MAAM,oBAAoB;GACjC,aAAa;GACb,SAAS;GACT,QAAQ;IAAE,UAAU;IAAS,MAAM;GAAW;EAChD;EACA,sCAAsC;GACpC,OAAO,MAAM,qBAAqB;GAClC,aAAa;GACb,SAAS;GACT,QAAQ;IAAE,UAAU;IAAoB,OAAO;GAAW;EAC5D;EACA,uCAAuC;GACrC,OAAO,MAAM,wBAAwB;GACrC,aAAa;GACb,SAAS;GACT,QAAQ;IAAE,UAAU;IAAS,MAAM;IAAY,OAAO;GAAW;EACnE;EACA,4CAA4C;GAC1C,OAAO,MAAM,wBAAwB;GACrC,aAAa;GACb,SAAS;GACT,QAAQ;IAAE,UAAU;IAAS,MAAM;IAAc,OAAO;GAAW;EACrE;EACA,qCAAqC;GACnC,OAAO,MAAM,wBAAwB;GACrC,aAAa;GACb,SAAS;GACT,QAAQ;IAAE,UAAU;IAAS,MAAM;IAAY,OAAO;GAAW;EACnE;EACA,yCAAyC;GACvC,OAAO,MAAM,wBAAwB;GACrC,aAAa;GACb,SAAS;GACT,QAAQ;IAAE,UAAU;IAAS,MAAM;IAAW,OAAO;GAAW;EAClE;EACA,0CAA0C;GACxC,OAAO,MAAM,wBAAwB;GACrC,aAAa;GACb,SAAS;GACT,QAAQ;IAAE,UAAU;IAAS,MAAM;IAAY,OAAO;GAAW;EACnE;CACF;CACA,QAAQ,EACN,SAAS;EACP,UAAU;GACR,QAAQ;GACR,QAAQ,EACN,mBAAmB;IACjB,OAAO;IACP,aAAa;IACb,SAAS;IACT,QAAQ;KAAE,UAAU;KAAoB,MAAM;IAAO;GACvD,EACF;EACF;EACA,WAAW;GACT,QAAQ;GACR,QAAQ,EACN,mBAAmB;IACjB,OAAO,MAAM,SAAS;IACtB,aAAa;IACb,SAAS;IACT,QAAQ;KAAE,UAAU;KAAoB,MAAM;IAAO;GACvD,EACF;EACF;CACF,EACF;AACF,CAAC"}
@@ -2,6 +2,6 @@ import { ComponentTokenSet } from "../../tokens/component.js";
2
2
  import "../../index.js";
3
3
  //#region src/components/menu/menu-tokens.d.ts
4
4
  /** The tokens of {@link Menu}: `--md-comp-menus-<part>` and `--md-comp-menus-standard-<part>`. */
5
- export declare const menuTokenSet: ComponentTokenSet<"container-elevation" | "container-shape">;
5
+ export declare const menuTokenSet: ComponentTokenSet<"container-shape" | "container-elevation">;
6
6
  //#endregion
7
7
  //# sourceMappingURL=menu-tokens.d.ts.map
@@ -31,28 +31,65 @@ function expandedChrome(el) {
31
31
  const RailContext = createContext(null);
32
32
  /**
33
33
  * Expanded width: the widest label on one line plus the item chrome, 220–360px (Compose).
34
- * Past 360px labels wrap.
34
+ * Past 360px labels wrap. With `settle`, a width that comes from measuring rather than from
35
+ * expanding (the rail mounting expanded, its font arriving, the rail coming into sight) is
36
+ * taken at once: the rail never springs open from the 220px guess on a page's first paint.
35
37
  */
36
- function useExpandedWidth(ref, active, content) {
38
+ function useExpandedWidth(ref, active, content, settle = false) {
37
39
  const [width, setWidth] = useState(EXPANDED_MIN);
40
+ const mounted = useRef(false);
38
41
  useLayoutEffect(() => {
39
42
  const el = ref.current;
43
+ const first = !mounted.current;
44
+ mounted.current = true;
40
45
  if (!active || !el) return;
41
- const labels = [...el.querySelectorAll("[data-nav-label]")];
42
- const widest = Math.max(0, ...labels.map((label) => {
43
- label.style.whiteSpace = "nowrap";
44
- const natural = label.scrollWidth;
45
- label.style.whiteSpace = "";
46
- return natural;
47
- }));
48
- const header = el.querySelector("[data-rail-header]");
49
- const headerWidth = header ? header.scrollWidth : 0;
50
- const chrome = expandedChrome(el);
51
- setWidth(Math.min(Math.max(widest + chrome, headerWidth, EXPANDED_MIN), EXPANDED_MAX));
46
+ const measure = (instant) => {
47
+ const labels = [...el.querySelectorAll("[data-nav-label]")];
48
+ const range = el.ownerDocument.createRange();
49
+ const widest = Math.max(0, ...labels.map((label) => {
50
+ label.style.whiteSpace = "nowrap";
51
+ range.selectNodeContents(label);
52
+ const text = typeof range.getBoundingClientRect === "function" ? range.getBoundingClientRect().width : 0;
53
+ const natural = Math.ceil(Math.max(text, label.scrollWidth));
54
+ label.style.whiteSpace = "";
55
+ return natural;
56
+ }));
57
+ const header = el.querySelector("[data-rail-header]");
58
+ const headerWidth = header ? header.scrollWidth : 0;
59
+ const chrome = expandedChrome(el);
60
+ const next = Math.min(Math.max(widest + chrome, headerWidth, EXPANDED_MIN), EXPANDED_MAX);
61
+ if (instant && settle) {
62
+ el.style.transitionProperty = "none";
63
+ el.style.setProperty("--m3-rail-width", `${next}px`);
64
+ el.offsetWidth;
65
+ el.style.transitionProperty = "";
66
+ }
67
+ setWidth(next);
68
+ };
69
+ const fonts = document.fonts;
70
+ const remeasure = () => {
71
+ if (el.getClientRects().length > 0) measure(true);
72
+ };
73
+ fonts?.addEventListener("loadingdone", remeasure);
74
+ if (el.getClientRects().length > 0 || typeof ResizeObserver === "undefined") {
75
+ measure(first);
76
+ return () => fonts?.removeEventListener("loadingdone", remeasure);
77
+ }
78
+ const observer = new ResizeObserver(() => {
79
+ if (el.getClientRects().length === 0) return;
80
+ observer.disconnect();
81
+ measure(true);
82
+ });
83
+ observer.observe(el);
84
+ return () => {
85
+ fonts?.removeEventListener("loadingdone", remeasure);
86
+ observer.disconnect();
87
+ };
52
88
  }, [
53
89
  ref,
54
90
  active,
55
- content
91
+ content,
92
+ settle
56
93
  ]);
57
94
  return width;
58
95
  }
@@ -110,7 +147,7 @@ function NavigationRail({ expanded, defaultExpanded = false, onExpandedChange, m
110
147
  const rootRef = useRef(null);
111
148
  const lastIndicatorRef = useRef(null);
112
149
  const slide = indicatorMotion === "slide";
113
- const width = useExpandedWidth(rootRef, inlineExpanded, children);
150
+ const width = useExpandedWidth(rootRef, inlineExpanded, children, true);
114
151
  const widening = useWidening(rootRef, inlineExpanded);
115
152
  const [mode, setMode] = useState(inlineExpanded);
116
153
  const [modeChanged, setModeChanged] = useState(false);
@@ -1 +1 @@
1
- {"version":3,"file":"NavigationRail.js","names":["Overlay"],"sources":["../../../src/components/navigation/NavigationRail.tsx"],"sourcesContent":["'use client';\n\nimport {\n createContext,\n useContext,\n useLayoutEffect,\n useRef,\n useState,\n type ComponentPropsWithRef,\n type CSSProperties,\n type ReactNode,\n type RefObject,\n} from 'react';\nimport { mergeProps, useDialog, useModalOverlay } from 'react-aria';\nimport { useOverlayTriggerState, type OverlayTriggerState } from 'react-stately';\nimport { useControlledState } from 'react-stately/useControlledState';\nimport { Overlay } from '../../primitives/Overlay';\nimport { usePresence } from '../../primitives/use-presence';\nimport { cn } from '../../utils/cn';\nimport type { NavItemLayout } from './nav-item-styles';\nimport { DensityZeroButtons } from '../button/DensityZeroButtons';\nimport { NavItem, type NavItemProps } from './NavItem';\nimport { navigationRailStyles } from './navigation-styles';\nimport { onItemArrowKeys } from './use-item-arrow-keys';\nimport { useRailItemMorph } from './use-rail-item-morph';\nimport { mergeDataTokens } from '../../utils/merge-data-tokens';\n\n/** Registered in utilities.css (96px); every rail inset is derived from it. */\nconst COLLAPSED_WIDTH_VAR = '--m3-rail-collapsed-width';\nconst DEFAULT_COLLAPSED_WIDTH = 96;\nconst EXPANDED_MIN = 220;\nconst EXPANDED_MAX = 360;\n/** Elements that take focus with Tab, for the modal rail's initial focus. */\nconst FOCUSABLE =\n 'a[href]:not([aria-disabled=\"true\"]), button:not(:disabled), input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * The item chrome beside an expanded label: an item inset each side, (collapsed − 56) / 2,\n * plus 16px pill padding each side, the 24px icon and the 8px gap. 104px at 96px.\n */\nfunction expandedChrome(el: HTMLElement) {\n const collapsed =\n Number.parseFloat(getComputedStyle(el).getPropertyValue(COLLAPSED_WIDTH_VAR)) ||\n DEFAULT_COLLAPSED_WIDTH;\n return collapsed - 56 + 64;\n}\n\ninterface RailContextValue {\n layout: NavItemLayout;\n enterLabel: boolean;\n /** The expanded list's last current pill, which the next current item's pill slides from. */\n lastIndicatorRef?: RefObject<HTMLElement | null>;\n /** The modal rail collapses after an item is chosen. */\n onNavigate?: () => void;\n}\n\nconst RailContext = createContext<RailContextValue | null>(null);\n\ntype Naming =\n | {\n /** Accessible name for the navigation rail. Required unless `aria-labelledby` is set. */\n 'aria-label': string;\n }\n | {\n /** The id of the element that names the navigation rail. Required unless `aria-label` is set. */\n 'aria-labelledby': string;\n };\n\nexport interface NavigationRailClassNames {\n /** The `<nav>` container, merged with `className`. */\n root?: string;\n /** The in-flow rail's scrolling column, which holds the 44px top inset. */\n body?: string;\n /** The content above the items (`header`). */\n header?: string;\n /** The list of items. */\n items?: string;\n /** The expanded modal rail's sheet. */\n sheet?: string;\n /** The scrim behind the expanded modal rail. */\n scrim?: string;\n}\n\ninterface NavigationRailOwnProps {\n /** Whether the rail is expanded (controlled). */\n expanded?: boolean;\n /** Whether the rail starts expanded (uncontrolled). @default false */\n defaultExpanded?: boolean;\n /** Called when the rail expands or collapses. */\n onExpandedChange?: (expanded: boolean) => void;\n /**\n * Expand over the page as a modal sheet with a scrim instead of pushing content.\n * Pressing outside, Escape or choosing an item collapses it.\n * @default false\n */\n modal?: boolean;\n /**\n * With `modal`, show nothing while collapsed (the expanded rail slides in). `compact` does so\n * only below 600px wide, in CSS so the server's HTML is right: one rail then serves as the\n * rail on larger windows and as the drawer on a phone.\n * @default false\n */\n hideOnCollapse?: boolean | 'compact';\n /** Content above the items, e.g. a menu button and a FAB; a function receives the state. */\n header?: ReactNode | ((state: { expanded: boolean; toggle: () => void }) => ReactNode);\n /**\n * Align the items as a group to the top, or to the middle of the space below the header\n * (M3 recommends centre on tablets, within easier reach). The header stays at the top.\n * @default \"top\"\n */\n itemAlignment?: 'top' | 'center';\n /** Draw a divider on the edge next to the content. @default false */\n divider?: boolean;\n /**\n * How the current item's pill moves to a newly chosen item. `expand` (M3: \"the active\n * indicator expands from the center of the icon\") grows it in place. `slide` moves it from\n * the previously chosen item on the fast spatial spring while the rail is expanded (in\n * place or modal), as in Google's Expressive showcase; an extension beyond the M3 spec.\n * The collapsed rail always expands, and reduced motion fades the pill in place.\n * @default \"expand\"\n */\n indicatorMotion?: 'expand' | 'slide';\n /** `NavigationRailItem` elements. */\n children: ReactNode;\n /** Classes for each part, by slot. */\n classNames?: NavigationRailClassNames;\n}\n\nexport type NavigationRailProps = NavigationRailOwnProps &\n Naming &\n Omit<\n ComponentPropsWithRef<'nav'>,\n keyof NavigationRailOwnProps | 'aria-label' | 'aria-labelledby'\n >;\n\n/**\n * Expanded width: the widest label on one line plus the item chrome, 220–360px (Compose).\n * Past 360px labels wrap.\n */\nfunction useExpandedWidth(ref: RefObject<HTMLElement | null>, active: boolean, content: ReactNode) {\n const [width, setWidth] = useState(EXPANDED_MIN);\n useLayoutEffect(() => {\n const el = ref.current;\n if (!active || !el) return;\n const labels = [...el.querySelectorAll<HTMLElement>('[data-nav-label]')];\n // Labels wrap in a narrow rail; measure each on one line.\n const widest = Math.max(\n 0,\n ...labels.map((label) => {\n label.style.whiteSpace = 'nowrap';\n const natural = label.scrollWidth;\n label.style.whiteSpace = '';\n return natural;\n }),\n );\n const header = el.querySelector<HTMLElement>('[data-rail-header]');\n const headerWidth = header ? header.scrollWidth : 0;\n const chrome = expandedChrome(el);\n setWidth(Math.min(Math.max(widest + chrome, headerWidth, EXPANDED_MIN), EXPANDED_MAX));\n }, [ref, active, content]);\n return width;\n}\n\n/**\n * Whether `ref` is still widening after it became `expanding` (or mounted expanding, as the\n * modal sheet grows from the collapsed width): from that render until its width transition\n * ends, and false at once when none runs (reduced motion, or a consumer width). Meanwhile the\n * rail keeps its labels on one line, so none wraps letter by letter in a rail that is still\n * narrow and no icon jumps; the rail's clipping hides what doesn't fit yet.\n */\nfunction useWidening(ref: RefObject<HTMLElement | null>, expanding: boolean) {\n const [widening, setWidening] = useState(expanding);\n const [wasExpanding, setWasExpanding] = useState(expanding);\n if (expanding !== wasExpanding) {\n setWasExpanding(expanding);\n setWidening(expanding);\n }\n useLayoutEffect(() => {\n const el = ref.current;\n if (!widening || !el) return;\n // Reading the running transitions resolves this render's styles, starting the spring.\n const running =\n typeof el.getAnimations === 'function' &&\n el\n .getAnimations()\n .some(\n (animation) =>\n 'transitionProperty' in animation && animation.transitionProperty === 'width',\n );\n if (!running) {\n setWidening(false);\n return;\n }\n // A later width (the measured one replacing the minimum) retargets the same spring.\n const done = (event: TransitionEvent) => {\n if (event.target === el && event.propertyName === 'width') setWidening(false);\n };\n el.addEventListener('transitionend', done);\n return () => el.removeEventListener('transitionend', done);\n }, [ref, widening]);\n return widening;\n}\n\n/**\n * M3 Expressive navigation rail: destinations along the start edge. Collapsed (96px) items\n * show the icon above the label; expanded (220–360px) they show it beside the label and\n * the rail's width springs open. With `modal`, the expanded rail opens over the page as a\n * sheet with a scrim, focusing its first interactive element. Put a menu button in\n * `header` to toggle `expanded`. Tab or the Up / Down arrows move between items, and\n * labels wrap rather than truncate.\n *\n * Icons stay on one line through the middle of the collapsed rail in both states. For a\n * different collapsed width, set `--m3-rail-collapsed-width` (e.g. the class\n * `[--m3-rail-collapsed-width:80px]`) rather than a `w-*` class; M3's widths are 96px\n * (Expressive) and 80px (baseline).\n *\n * @example\n * <NavigationRail aria-label=\"Main\" header={({ toggle }) => <IconButton icon={<MenuIcon />} aria-label=\"Menu\" onPress={toggle} />}>\n * <NavigationRailItem href=\"/inbox\" icon={<InboxIcon />} selected>Inbox</NavigationRailItem>\n * </NavigationRail>\n */\nexport function NavigationRail({\n expanded,\n defaultExpanded = false,\n onExpandedChange,\n modal = false,\n hideOnCollapse = false,\n header,\n itemAlignment = 'top',\n divider = false,\n indicatorMotion = 'expand',\n children,\n classNames,\n className,\n style,\n ...rest\n}: NavigationRailProps) {\n const [isExpanded, setExpanded] = useControlledState(expanded, defaultExpanded, onExpandedChange);\n const toggle = () => setExpanded(!isExpanded);\n const inlineExpanded = isExpanded && !modal;\n const rootRef = useRef<HTMLElement>(null);\n const lastIndicatorRef = useRef<HTMLElement>(null);\n const slide = indicatorMotion === 'slide';\n const width = useExpandedWidth(rootRef, inlineExpanded, children);\n const widening = useWidening(rootRef, inlineExpanded);\n // Labels fade in at their new place once the rail has changed mode (not on first render).\n const [mode, setMode] = useState(inlineExpanded);\n const [modeChanged, setModeChanged] = useState(false);\n const captureItems = useRailItemMorph(rootRef, mode);\n if (mode !== inlineExpanded) {\n // The DOM still shows the old layout: note where each item's icon is.\n captureItems();\n setMode(inlineExpanded);\n setModeChanged(true);\n }\n\n const styles = navigationRailStyles({\n expanded: inlineExpanded,\n itemAlignment,\n divider,\n slidingIndicator: slide && inlineExpanded,\n hiddenOnCompact: modal && hideOnCollapse === 'compact',\n });\n const headerContent =\n typeof header === 'function' ? header({ expanded: isExpanded, toggle }) : header;\n const showInline = !(modal && hideOnCollapse === true);\n\n return (\n <>\n {showInline && (\n <nav\n {...rest}\n ref={rootRef}\n style={\n {\n '--m3-rail-width': inlineExpanded ? `${width}px` : `var(${COLLAPSED_WIDTH_VAR})`,\n ...style,\n } as CSSProperties\n }\n data-tokens={mergeDataTokens('md-comp-nav-rail', rest)}\n data-expanded={inlineExpanded || undefined}\n data-widening={widening || undefined}\n className={styles.root({ class: cn(classNames?.root, className) })}\n >\n <div className={styles.body({ class: classNames?.body })}>\n {headerContent != null && (\n <div data-rail-header=\"\" className={styles.header({ class: classNames?.header })}>\n <DensityZeroButtons>{headerContent}</DensityZeroButtons>\n </div>\n )}\n <div\n data-item-alignment={itemAlignment}\n onKeyDown={onItemArrowKeys('vertical')}\n className={styles.items({ class: classNames?.items })}\n >\n <RailContext\n value={{\n layout: inlineExpanded ? 'rail-start' : 'rail-top',\n enterLabel: modeChanged,\n ...(slide && inlineExpanded && { lastIndicatorRef }),\n }}\n >\n {children}\n </RailContext>\n </div>\n </div>\n </nav>\n )}\n {modal && (\n <ModalRail\n open={isExpanded}\n onClose={() => setExpanded(false)}\n header={headerContent}\n naming={rest as Naming}\n slide={hideOnCollapse !== false}\n itemAlignment={itemAlignment}\n slidingIndicator={slide}\n railRef={rootRef}\n classNames={classNames}\n >\n {children}\n </ModalRail>\n )}\n </>\n );\n}\n\n/** The sheet's exit transitions; whichever runs marks the end of its exit. */\nconst SHEET_EXIT_PROPERTIES = ['width', 'translate', 'opacity'] as const;\n\nfunction ModalRail({\n open,\n onClose,\n header,\n naming,\n slide,\n itemAlignment,\n slidingIndicator,\n railRef,\n classNames,\n children,\n}: {\n open: boolean;\n onClose: () => void;\n header: ReactNode;\n naming: Naming;\n slide: boolean;\n itemAlignment: 'top' | 'center';\n /** `indicatorMotion=\"slide\"`: the pill slides between items. */\n slidingIndicator: boolean;\n railRef: RefObject<HTMLElement | null>;\n classNames?: NavigationRailClassNames;\n children: ReactNode;\n}) {\n const state = useOverlayTriggerState({\n isOpen: open,\n onOpenChange: (isOpen) => {\n if (!isOpen) onClose();\n },\n });\n // Presence ends with the sheet's own motion, not the scrim's shorter fade, which removed\n // the sheet mid-spring inside the collapsed rail (2026-10-15). Width when it shrinks back,\n // translate when it slides out, opacity under reduced motion.\n const { isPresent, isExiting, exitProps } = usePresence(open, SHEET_EXIT_PROPERTIES);\n if (!isPresent) return null;\n return (\n <Overlay exiting={isExiting}>\n <ModalSheet\n state={state}\n isExiting={isExiting}\n exitProps={exitProps}\n header={header}\n naming={naming}\n slide={slide}\n itemAlignment={itemAlignment}\n slidingIndicator={slidingIndicator}\n railRef={railRef}\n classNames={classNames}\n >\n {children}\n </ModalSheet>\n </Overlay>\n );\n}\n\nfunction ModalSheet({\n state,\n isExiting,\n exitProps,\n header,\n naming,\n slide,\n itemAlignment,\n slidingIndicator,\n railRef,\n classNames,\n children,\n}: {\n state: OverlayTriggerState;\n isExiting: boolean;\n exitProps: ReturnType<typeof usePresence>['exitProps'];\n header: ReactNode;\n naming: Naming;\n slide: boolean;\n itemAlignment: 'top' | 'center';\n /** `indicatorMotion=\"slide\"`: the pill slides between items. */\n slidingIndicator: boolean;\n railRef: RefObject<HTMLElement | null>;\n classNames?: NavigationRailClassNames;\n children: ReactNode;\n}) {\n const ref = useRef<HTMLDivElement>(null);\n const lastIndicatorRef = useRef<HTMLElement>(null);\n const { modalProps, underlayProps } = useModalOverlay({ isDismissable: !isExiting }, state, ref);\n const { dialogProps } = useDialog(naming, ref);\n // The sheet is portalled out of the rail, so it can't inherit a collapsed width set on the\n // rail itself. Copy it before the width is measured and the sheet first paints.\n useLayoutEffect(() => {\n const rail = railRef.current;\n if (!rail || !ref.current) return;\n ref.current.style.setProperty(\n COLLAPSED_WIDTH_VAR,\n getComputedStyle(rail).getPropertyValue(COLLAPSED_WIDTH_VAR),\n );\n }, [railRef]);\n // M3: initial focus lands on the first interactive item, whether it's the menu button,\n // the FAB or the first destination (useDialog would otherwise focus the sheet itself).\n useLayoutEffect(() => {\n const first = ref.current?.querySelector<HTMLElement>(FOCUSABLE);\n first?.focus();\n }, []);\n const width = useExpandedWidth(ref, true, children);\n const widening = useWidening(ref, true);\n const styles = navigationRailStyles({\n expanded: true,\n sheetEntry: slide ? 'slide' : 'grow',\n itemAlignment,\n slidingIndicator,\n });\n const exiting = isExiting || undefined;\n return (\n <>\n <div\n {...underlayProps}\n aria-hidden=\"true\"\n data-exiting={exiting}\n className={styles.scrim({ class: classNames?.scrim })}\n />\n <div\n {...mergeProps(modalProps, dialogProps, exitProps)}\n ref={ref}\n data-tokens=\"md-comp-nav-rail\"\n data-modal=\"\"\n data-exiting={exiting}\n data-widening={widening || undefined}\n style={{ '--m3-rail-width': `${width}px` } as CSSProperties}\n className={styles.sheet({ class: classNames?.sheet })}\n >\n {header != null && (\n <div data-rail-header=\"\" className={styles.header({ class: classNames?.header })}>\n <DensityZeroButtons>{header}</DensityZeroButtons>\n </div>\n )}\n <nav\n aria-label={'aria-label' in naming ? naming['aria-label'] : undefined}\n aria-labelledby={'aria-labelledby' in naming ? naming['aria-labelledby'] : undefined}\n data-item-alignment={itemAlignment}\n onKeyDown={onItemArrowKeys('vertical')}\n className={styles.items({ class: classNames?.items })}\n >\n <RailContext\n value={{\n layout: 'rail-start',\n enterLabel: false,\n ...(slidingIndicator && { lastIndicatorRef }),\n onNavigate: state.close,\n }}\n >\n {children}\n </RailContext>\n </nav>\n </div>\n </>\n );\n}\n\nexport type NavigationRailItemProps = NavItemProps;\n\n/** What {@link useNavigationRail} reports about the rail around a component. */\nexport interface NavigationRailState {\n /** Whether the rail shows its labels beside the icons (expanded, or the modal sheet). */\n expanded: boolean;\n /** Whether this is the rail's modal sheet. */\n modal: boolean;\n /** Collapses the modal sheet; does nothing in the rail itself. */\n close: () => void;\n}\n\n/**\n * The rail around a component, for a custom destination that sits in a {@link NavigationRail}\n * beside its items and changes with it. Throws outside a rail.\n */\nexport function useNavigationRail(): NavigationRailState {\n const rail = useContext(RailContext);\n if (!rail) throw new Error('useNavigationRail must be used inside a NavigationRail');\n return {\n expanded: rail.layout === 'rail-start',\n modal: rail.onNavigate !== undefined,\n close: () => rail.onNavigate?.(),\n };\n}\n\n/** A destination in a {@link NavigationRail}: a link (`href`) or a button. */\nexport function NavigationRailItem({ onPress, ...props }: NavigationRailItemProps) {\n const rail = useContext(RailContext);\n if (!rail) throw new Error('NavigationRailItem must be rendered inside a NavigationRail');\n return (\n <NavItem\n {...(props as NavItemProps)}\n layout={rail.layout}\n enterLabel={rail.enterLabel}\n {...(rail.lastIndicatorRef && { lastIndicatorRef: rail.lastIndicatorRef })}\n onPress={(event) => {\n onPress?.(event);\n rail.onNavigate?.();\n }}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA4BA,MAAM,sBAAsB;AAC5B,MAAM,0BAA0B;AAChC,MAAM,eAAe;AACrB,MAAM,eAAe;;AAErB,MAAM,YACJ;;;;;AAMF,SAAS,eAAe,IAAiB;CAIvC,QAFE,OAAO,WAAW,iBAAiB,EAAE,CAAC,CAAC,iBAAiB,mBAAmB,CAAC,KAC5E,2BACiB,KAAK;AAC1B;AAWA,MAAM,cAAc,cAAuC,IAAI;;;;;AAmF/D,SAAS,iBAAiB,KAAoC,QAAiB,SAAoB;CACjG,MAAM,CAAC,OAAO,YAAY,SAAS,YAAY;CAC/C,sBAAsB;EACpB,MAAM,KAAK,IAAI;EACf,IAAI,CAAC,UAAU,CAAC,IAAI;EACpB,MAAM,SAAS,CAAC,GAAG,GAAG,iBAA8B,kBAAkB,CAAC;EAEvE,MAAM,SAAS,KAAK,IAClB,GACA,GAAG,OAAO,KAAK,UAAU;GACvB,MAAM,MAAM,aAAa;GACzB,MAAM,UAAU,MAAM;GACtB,MAAM,MAAM,aAAa;GACzB,OAAO;EACT,CAAC,CACH;EACA,MAAM,SAAS,GAAG,cAA2B,oBAAoB;EACjE,MAAM,cAAc,SAAS,OAAO,cAAc;EAClD,MAAM,SAAS,eAAe,EAAE;EAChC,SAAS,KAAK,IAAI,KAAK,IAAI,SAAS,QAAQ,aAAa,YAAY,GAAG,YAAY,CAAC;CACvF,GAAG;EAAC;EAAK;EAAQ;CAAO,CAAC;CACzB,OAAO;AACT;;;;;;;;AASA,SAAS,YAAY,KAAoC,WAAoB;CAC3E,MAAM,CAAC,UAAU,eAAe,SAAS,SAAS;CAClD,MAAM,CAAC,cAAc,mBAAmB,SAAS,SAAS;CAC1D,IAAI,cAAc,cAAc;EAC9B,gBAAgB,SAAS;EACzB,YAAY,SAAS;CACvB;CACA,sBAAsB;EACpB,MAAM,KAAK,IAAI;EACf,IAAI,CAAC,YAAY,CAAC,IAAI;EAUtB,IAAI,EAPF,OAAO,GAAG,kBAAkB,cAC5B,GACG,cAAc,CAAC,CACf,MACE,cACC,wBAAwB,aAAa,UAAU,uBAAuB,OAC1E,IACU;GACZ,YAAY,KAAK;GACjB;EACF;EAEA,MAAM,QAAQ,UAA2B;GACvC,IAAI,MAAM,WAAW,MAAM,MAAM,iBAAiB,SAAS,YAAY,KAAK;EAC9E;EACA,GAAG,iBAAiB,iBAAiB,IAAI;EACzC,aAAa,GAAG,oBAAoB,iBAAiB,IAAI;CAC3D,GAAG,CAAC,KAAK,QAAQ,CAAC;CAClB,OAAO;AACT;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,eAAe,EAC7B,UACA,kBAAkB,OAClB,kBACA,QAAQ,OACR,iBAAiB,OACjB,QACA,gBAAgB,OAChB,UAAU,OACV,kBAAkB,UAClB,UACA,YACA,WACA,OACA,GAAG,QACmB;CACtB,MAAM,CAAC,YAAY,eAAe,mBAAmB,UAAU,iBAAiB,gBAAgB;CAChG,MAAM,eAAe,YAAY,CAAC,UAAU;CAC5C,MAAM,iBAAiB,cAAc,CAAC;CACtC,MAAM,UAAU,OAAoB,IAAI;CACxC,MAAM,mBAAmB,OAAoB,IAAI;CACjD,MAAM,QAAQ,oBAAoB;CAClC,MAAM,QAAQ,iBAAiB,SAAS,gBAAgB,QAAQ;CAChE,MAAM,WAAW,YAAY,SAAS,cAAc;CAEpD,MAAM,CAAC,MAAM,WAAW,SAAS,cAAc;CAC/C,MAAM,CAAC,aAAa,kBAAkB,SAAS,KAAK;CACpD,MAAM,eAAe,iBAAiB,SAAS,IAAI;CACnD,IAAI,SAAS,gBAAgB;EAE3B,aAAa;EACb,QAAQ,cAAc;EACtB,eAAe,IAAI;CACrB;CAEA,MAAM,SAAS,qBAAqB;EAClC,UAAU;EACV;EACA;EACA,kBAAkB,SAAS;EAC3B,iBAAiB,SAAS,mBAAmB;CAC/C,CAAC;CACD,MAAM,gBACJ,OAAO,WAAW,aAAa,OAAO;EAAE,UAAU;EAAY;CAAO,CAAC,IAAI;CAG5E,OACE,qBAAA,YAAA,EAAA,UAAA,CACG,EAJgB,SAAS,mBAAmB,SAK3C,oBAAC,OAAD;EACE,GAAI;EACJ,KAAK;EACL,OACE;GACE,mBAAmB,iBAAiB,GAAG,MAAM,MAAM,OAAO,oBAAoB;GAC9E,GAAG;EACL;EAEF,eAAa,gBAAgB,oBAAoB,IAAI;EACrD,iBAAe,kBAAkB,KAAA;EACjC,iBAAe,YAAY,KAAA;EAC3B,WAAW,OAAO,KAAK,EAAE,OAAO,GAAG,YAAY,MAAM,SAAS,EAAE,CAAC;EAEjE,UAAA,qBAAC,OAAD;GAAK,WAAW,OAAO,KAAK,EAAE,OAAO,YAAY,KAAK,CAAC;GAAvD,UAAA,CACG,iBAAiB,QAChB,oBAAC,OAAD;IAAK,oBAAiB;IAAG,WAAW,OAAO,OAAO,EAAE,OAAO,YAAY,OAAO,CAAC;IAC7E,UAAA,oBAAC,oBAAD,EAAA,UAAqB,cAAkC,CAAA;GACpD,CAAA,GAEP,oBAAC,OAAD;IACE,uBAAqB;IACrB,WAAW,gBAAgB,UAAU;IACrC,WAAW,OAAO,MAAM,EAAE,OAAO,YAAY,MAAM,CAAC;IAEpD,UAAA,oBAAC,aAAD;KACE,OAAO;MACL,QAAQ,iBAAiB,eAAe;MACxC,YAAY;MACZ,GAAI,SAAS,kBAAkB,EAAE,iBAAiB;KACpD;KAEC;IACU,CAAA;GACV,CAAA,CACF;;CACF,CAAA,GAEN,SACC,oBAAC,WAAD;EACE,MAAM;EACN,eAAe,YAAY,KAAK;EAChC,QAAQ;EACR,QAAQ;EACR,OAAO,mBAAmB;EACX;EACf,kBAAkB;EAClB,SAAS;EACG;EAEX;CACQ,CAAA,CAEb,EAAA,CAAA;AAEN;;AAGA,MAAM,wBAAwB;CAAC;CAAS;CAAa;AAAS;AAE9D,SAAS,UAAU,EACjB,MACA,SACA,QACA,QACA,OACA,eACA,kBACA,SACA,YACA,YAaC;CACD,MAAM,QAAQ,uBAAuB;EACnC,QAAQ;EACR,eAAe,WAAW;GACxB,IAAI,CAAC,QAAQ,QAAQ;EACvB;CACF,CAAC;CAID,MAAM,EAAE,WAAW,WAAW,cAAc,YAAY,MAAM,qBAAqB;CACnF,IAAI,CAAC,WAAW,OAAO;CACvB,OACE,oBAACA,WAAD;EAAS,SAAS;EAChB,UAAA,oBAAC,YAAD;GACS;GACI;GACA;GACH;GACA;GACD;GACQ;GACG;GACT;GACG;GAEX;EACS,CAAA;CACL,CAAA;AAEb;AAEA,SAAS,WAAW,EAClB,OACA,WACA,WACA,QACA,QACA,OACA,eACA,kBACA,SACA,YACA,YAcC;CACD,MAAM,MAAM,OAAuB,IAAI;CACvC,MAAM,mBAAmB,OAAoB,IAAI;CACjD,MAAM,EAAE,YAAY,kBAAkB,gBAAgB,EAAE,eAAe,CAAC,UAAU,GAAG,OAAO,GAAG;CAC/F,MAAM,EAAE,gBAAgB,UAAU,QAAQ,GAAG;CAG7C,sBAAsB;EACpB,MAAM,OAAO,QAAQ;EACrB,IAAI,CAAC,QAAQ,CAAC,IAAI,SAAS;EAC3B,IAAI,QAAQ,MAAM,YAChB,qBACA,iBAAiB,IAAI,CAAC,CAAC,iBAAiB,mBAAmB,CAC7D;CACF,GAAG,CAAC,OAAO,CAAC;CAGZ,sBAAsB;EAEpB,CADc,IAAI,SAAS,cAA2B,SAAS,EAAA,EACxD,MAAM;CACf,GAAG,CAAC,CAAC;CACL,MAAM,QAAQ,iBAAiB,KAAK,MAAM,QAAQ;CAClD,MAAM,WAAW,YAAY,KAAK,IAAI;CACtC,MAAM,SAAS,qBAAqB;EAClC,UAAU;EACV,YAAY,QAAQ,UAAU;EAC9B;EACA;CACF,CAAC;CACD,MAAM,UAAU,aAAa,KAAA;CAC7B,OACE,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,OAAD;EACE,GAAI;EACJ,eAAY;EACZ,gBAAc;EACd,WAAW,OAAO,MAAM,EAAE,OAAO,YAAY,MAAM,CAAC;CACrD,CAAA,GACD,qBAAC,OAAD;EACE,GAAI,WAAW,YAAY,aAAa,SAAS;EAC5C;EACL,eAAY;EACZ,cAAW;EACX,gBAAc;EACd,iBAAe,YAAY,KAAA;EAC3B,OAAO,EAAE,mBAAmB,GAAG,MAAM,IAAI;EACzC,WAAW,OAAO,MAAM,EAAE,OAAO,YAAY,MAAM,CAAC;EARtD,UAAA,CAUG,UAAU,QACT,oBAAC,OAAD;GAAK,oBAAiB;GAAG,WAAW,OAAO,OAAO,EAAE,OAAO,YAAY,OAAO,CAAC;GAC7E,UAAA,oBAAC,oBAAD,EAAA,UAAqB,OAA2B,CAAA;EAC7C,CAAA,GAEP,oBAAC,OAAD;GACE,cAAY,gBAAgB,SAAS,OAAO,gBAAgB,KAAA;GAC5D,mBAAiB,qBAAqB,SAAS,OAAO,qBAAqB,KAAA;GAC3E,uBAAqB;GACrB,WAAW,gBAAgB,UAAU;GACrC,WAAW,OAAO,MAAM,EAAE,OAAO,YAAY,MAAM,CAAC;GAEpD,UAAA,oBAAC,aAAD;IACE,OAAO;KACL,QAAQ;KACR,YAAY;KACZ,GAAI,oBAAoB,EAAE,iBAAiB;KAC3C,YAAY,MAAM;IACpB;IAEC;GACU,CAAA;EACV,CAAA,CACF;CACL,CAAA,CAAA,EAAA,CAAA;AAEN;;;;;AAkBA,SAAgB,oBAAyC;CACvD,MAAM,OAAO,WAAW,WAAW;CACnC,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,wDAAwD;CACnF,OAAO;EACL,UAAU,KAAK,WAAW;EAC1B,OAAO,KAAK,eAAe,KAAA;EAC3B,aAAa,KAAK,aAAa;CACjC;AACF;;AAGA,SAAgB,mBAAmB,EAAE,SAAS,GAAG,SAAkC;CACjF,MAAM,OAAO,WAAW,WAAW;CACnC,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,6DAA6D;CACxF,OACE,oBAAC,SAAD;EACE,GAAK;EACL,QAAQ,KAAK;EACb,YAAY,KAAK;EACjB,GAAK,KAAK,oBAAoB,EAAE,kBAAkB,KAAK,iBAAiB;EACxE,UAAU,UAAU;GAClB,UAAU,KAAK;GACf,KAAK,aAAa;EACpB;CACD,CAAA;AAEL"}
1
+ {"version":3,"file":"NavigationRail.js","names":["Overlay"],"sources":["../../../src/components/navigation/NavigationRail.tsx"],"sourcesContent":["'use client';\n\nimport {\n createContext,\n useContext,\n useLayoutEffect,\n useRef,\n useState,\n type ComponentPropsWithRef,\n type CSSProperties,\n type ReactNode,\n type RefObject,\n} from 'react';\nimport { mergeProps, useDialog, useModalOverlay } from 'react-aria';\nimport { useOverlayTriggerState, type OverlayTriggerState } from 'react-stately';\nimport { useControlledState } from 'react-stately/useControlledState';\nimport { Overlay } from '../../primitives/Overlay';\nimport { usePresence } from '../../primitives/use-presence';\nimport { cn } from '../../utils/cn';\nimport type { NavItemLayout } from './nav-item-styles';\nimport { DensityZeroButtons } from '../button/DensityZeroButtons';\nimport { NavItem, type NavItemProps } from './NavItem';\nimport { navigationRailStyles } from './navigation-styles';\nimport { onItemArrowKeys } from './use-item-arrow-keys';\nimport { useRailItemMorph } from './use-rail-item-morph';\nimport { mergeDataTokens } from '../../utils/merge-data-tokens';\n\n/** Registered in utilities.css (96px); every rail inset is derived from it. */\nconst COLLAPSED_WIDTH_VAR = '--m3-rail-collapsed-width';\nconst DEFAULT_COLLAPSED_WIDTH = 96;\nconst EXPANDED_MIN = 220;\nconst EXPANDED_MAX = 360;\n/** Elements that take focus with Tab, for the modal rail's initial focus. */\nconst FOCUSABLE =\n 'a[href]:not([aria-disabled=\"true\"]), button:not(:disabled), input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * The item chrome beside an expanded label: an item inset each side, (collapsed − 56) / 2,\n * plus 16px pill padding each side, the 24px icon and the 8px gap. 104px at 96px.\n */\nfunction expandedChrome(el: HTMLElement) {\n const collapsed =\n Number.parseFloat(getComputedStyle(el).getPropertyValue(COLLAPSED_WIDTH_VAR)) ||\n DEFAULT_COLLAPSED_WIDTH;\n return collapsed - 56 + 64;\n}\n\ninterface RailContextValue {\n layout: NavItemLayout;\n enterLabel: boolean;\n /** The expanded list's last current pill, which the next current item's pill slides from. */\n lastIndicatorRef?: RefObject<HTMLElement | null>;\n /** The modal rail collapses after an item is chosen. */\n onNavigate?: () => void;\n}\n\nconst RailContext = createContext<RailContextValue | null>(null);\n\ntype Naming =\n | {\n /** Accessible name for the navigation rail. Required unless `aria-labelledby` is set. */\n 'aria-label': string;\n }\n | {\n /** The id of the element that names the navigation rail. Required unless `aria-label` is set. */\n 'aria-labelledby': string;\n };\n\nexport interface NavigationRailClassNames {\n /** The `<nav>` container, merged with `className`. */\n root?: string;\n /** The in-flow rail's scrolling column, which holds the 44px top inset. */\n body?: string;\n /** The content above the items (`header`). */\n header?: string;\n /** The list of items. */\n items?: string;\n /** The expanded modal rail's sheet. */\n sheet?: string;\n /** The scrim behind the expanded modal rail. */\n scrim?: string;\n}\n\ninterface NavigationRailOwnProps {\n /** Whether the rail is expanded (controlled). */\n expanded?: boolean;\n /** Whether the rail starts expanded (uncontrolled). @default false */\n defaultExpanded?: boolean;\n /** Called when the rail expands or collapses. */\n onExpandedChange?: (expanded: boolean) => void;\n /**\n * Expand over the page as a modal sheet with a scrim instead of pushing content.\n * Pressing outside, Escape or choosing an item collapses it.\n * @default false\n */\n modal?: boolean;\n /**\n * With `modal`, show nothing while collapsed (the expanded rail slides in). `compact` does so\n * only below 600px wide, in CSS so the server's HTML is right: one rail then serves as the\n * rail on larger windows and as the drawer on a phone.\n * @default false\n */\n hideOnCollapse?: boolean | 'compact';\n /** Content above the items, e.g. a menu button and a FAB; a function receives the state. */\n header?: ReactNode | ((state: { expanded: boolean; toggle: () => void }) => ReactNode);\n /**\n * Align the items as a group to the top, or to the middle of the space below the header\n * (M3 recommends centre on tablets, within easier reach). The header stays at the top.\n * @default \"top\"\n */\n itemAlignment?: 'top' | 'center';\n /** Draw a divider on the edge next to the content. @default false */\n divider?: boolean;\n /**\n * How the current item's pill moves to a newly chosen item. `expand` (M3: \"the active\n * indicator expands from the center of the icon\") grows it in place. `slide` moves it from\n * the previously chosen item on the fast spatial spring while the rail is expanded (in\n * place or modal), as in Google's Expressive showcase; an extension beyond the M3 spec.\n * The collapsed rail always expands, and reduced motion fades the pill in place.\n * @default \"expand\"\n */\n indicatorMotion?: 'expand' | 'slide';\n /** `NavigationRailItem` elements. */\n children: ReactNode;\n /** Classes for each part, by slot. */\n classNames?: NavigationRailClassNames;\n}\n\nexport type NavigationRailProps = NavigationRailOwnProps &\n Naming &\n Omit<\n ComponentPropsWithRef<'nav'>,\n keyof NavigationRailOwnProps | 'aria-label' | 'aria-labelledby'\n >;\n\n/**\n * Expanded width: the widest label on one line plus the item chrome, 220–360px (Compose).\n * Past 360px labels wrap. With `settle`, a width that comes from measuring rather than from\n * expanding (the rail mounting expanded, its font arriving, the rail coming into sight) is\n * taken at once: the rail never springs open from the 220px guess on a page's first paint.\n */\nfunction useExpandedWidth(\n ref: RefObject<HTMLElement | null>,\n active: boolean,\n content: ReactNode,\n settle = false,\n) {\n const [width, setWidth] = useState(EXPANDED_MIN);\n const mounted = useRef(false);\n useLayoutEffect(() => {\n const el = ref.current;\n const first = !mounted.current;\n mounted.current = true;\n if (!active || !el) return;\n const measure = (instant: boolean) => {\n const labels = [...el.querySelectorAll<HTMLElement>('[data-nav-label]')];\n // Labels wrap in a narrow rail; measure each one's text on one line, to the fraction of a\n // pixel (`scrollWidth` rounds, and a label 145.3px wide wrapped in 145px), rounded up.\n const range = el.ownerDocument.createRange();\n const widest = Math.max(\n 0,\n ...labels.map((label) => {\n label.style.whiteSpace = 'nowrap';\n range.selectNodeContents(label);\n // A test environment's Range may have no layout (jsdom): `scrollWidth` alone there.\n const text =\n typeof range.getBoundingClientRect === 'function'\n ? range.getBoundingClientRect().width\n : 0;\n const natural = Math.ceil(Math.max(text, label.scrollWidth));\n label.style.whiteSpace = '';\n return natural;\n }),\n );\n const header = el.querySelector<HTMLElement>('[data-rail-header]');\n const headerWidth = header ? header.scrollWidth : 0;\n const chrome = expandedChrome(el);\n const next = Math.min(Math.max(widest + chrome, headerWidth, EXPANDED_MIN), EXPANDED_MAX);\n if (instant && settle) {\n // Applied with no transition, then the spring is back for the next expand or collapse.\n el.style.transitionProperty = 'none';\n el.style.setProperty('--m3-rail-width', `${next}px`);\n void el.offsetWidth;\n el.style.transitionProperty = '';\n }\n setWidth(next);\n };\n // Measured again each time the page's fonts finish loading: the rail usually mounts before\n // its font arrives, and the fallback font's labels are narrower, so they wrapped once it did.\n const fonts = document.fonts as FontFaceSet | undefined;\n const remeasure = () => {\n if (el.getClientRects().length > 0) measure(true);\n };\n fonts?.addEventListener('loadingdone', remeasure);\n // A rail out of sight (`display: none` above it, as a rail shown only on wider windows)\n // lays nothing out, so it's measured once it shows.\n if (el.getClientRects().length > 0 || typeof ResizeObserver === 'undefined') {\n measure(first);\n return () => fonts?.removeEventListener('loadingdone', remeasure);\n }\n const observer = new ResizeObserver(() => {\n if (el.getClientRects().length === 0) return;\n observer.disconnect();\n measure(true);\n });\n observer.observe(el);\n return () => {\n fonts?.removeEventListener('loadingdone', remeasure);\n observer.disconnect();\n };\n }, [ref, active, content, settle]);\n return width;\n}\n\n/**\n * Whether `ref` is still widening after it became `expanding` (or mounted expanding, as the\n * modal sheet grows from the collapsed width): from that render until its width transition\n * ends, and false at once when none runs (reduced motion, or a consumer width). Meanwhile the\n * rail keeps its labels on one line, so none wraps letter by letter in a rail that is still\n * narrow and no icon jumps; the rail's clipping hides what doesn't fit yet.\n */\nfunction useWidening(ref: RefObject<HTMLElement | null>, expanding: boolean) {\n const [widening, setWidening] = useState(expanding);\n const [wasExpanding, setWasExpanding] = useState(expanding);\n if (expanding !== wasExpanding) {\n setWasExpanding(expanding);\n setWidening(expanding);\n }\n useLayoutEffect(() => {\n const el = ref.current;\n if (!widening || !el) return;\n // Reading the running transitions resolves this render's styles, starting the spring.\n const running =\n typeof el.getAnimations === 'function' &&\n el\n .getAnimations()\n .some(\n (animation) =>\n 'transitionProperty' in animation && animation.transitionProperty === 'width',\n );\n if (!running) {\n setWidening(false);\n return;\n }\n // A later width (the measured one replacing the minimum) retargets the same spring.\n const done = (event: TransitionEvent) => {\n if (event.target === el && event.propertyName === 'width') setWidening(false);\n };\n el.addEventListener('transitionend', done);\n return () => el.removeEventListener('transitionend', done);\n }, [ref, widening]);\n return widening;\n}\n\n/**\n * M3 Expressive navigation rail: destinations along the start edge. Collapsed (96px) items\n * show the icon above the label; expanded (220–360px) they show it beside the label and\n * the rail's width springs open. With `modal`, the expanded rail opens over the page as a\n * sheet with a scrim, focusing its first interactive element. Put a menu button in\n * `header` to toggle `expanded`. Tab or the Up / Down arrows move between items, and\n * labels wrap rather than truncate.\n *\n * Icons stay on one line through the middle of the collapsed rail in both states. For a\n * different collapsed width, set `--m3-rail-collapsed-width` (e.g. the class\n * `[--m3-rail-collapsed-width:80px]`) rather than a `w-*` class; M3's widths are 96px\n * (Expressive) and 80px (baseline).\n *\n * @example\n * <NavigationRail aria-label=\"Main\" header={({ toggle }) => <IconButton icon={<MenuIcon />} aria-label=\"Menu\" onPress={toggle} />}>\n * <NavigationRailItem href=\"/inbox\" icon={<InboxIcon />} selected>Inbox</NavigationRailItem>\n * </NavigationRail>\n */\nexport function NavigationRail({\n expanded,\n defaultExpanded = false,\n onExpandedChange,\n modal = false,\n hideOnCollapse = false,\n header,\n itemAlignment = 'top',\n divider = false,\n indicatorMotion = 'expand',\n children,\n classNames,\n className,\n style,\n ...rest\n}: NavigationRailProps) {\n const [isExpanded, setExpanded] = useControlledState(expanded, defaultExpanded, onExpandedChange);\n const toggle = () => setExpanded(!isExpanded);\n const inlineExpanded = isExpanded && !modal;\n const rootRef = useRef<HTMLElement>(null);\n const lastIndicatorRef = useRef<HTMLElement>(null);\n const slide = indicatorMotion === 'slide';\n const width = useExpandedWidth(rootRef, inlineExpanded, children, true);\n const widening = useWidening(rootRef, inlineExpanded);\n // Labels fade in at their new place once the rail has changed mode (not on first render).\n const [mode, setMode] = useState(inlineExpanded);\n const [modeChanged, setModeChanged] = useState(false);\n const captureItems = useRailItemMorph(rootRef, mode);\n if (mode !== inlineExpanded) {\n // The DOM still shows the old layout: note where each item's icon is.\n captureItems();\n setMode(inlineExpanded);\n setModeChanged(true);\n }\n\n const styles = navigationRailStyles({\n expanded: inlineExpanded,\n itemAlignment,\n divider,\n slidingIndicator: slide && inlineExpanded,\n hiddenOnCompact: modal && hideOnCollapse === 'compact',\n });\n const headerContent =\n typeof header === 'function' ? header({ expanded: isExpanded, toggle }) : header;\n const showInline = !(modal && hideOnCollapse === true);\n\n return (\n <>\n {showInline && (\n <nav\n {...rest}\n ref={rootRef}\n style={\n {\n '--m3-rail-width': inlineExpanded ? `${width}px` : `var(${COLLAPSED_WIDTH_VAR})`,\n ...style,\n } as CSSProperties\n }\n data-tokens={mergeDataTokens('md-comp-nav-rail', rest)}\n data-expanded={inlineExpanded || undefined}\n data-widening={widening || undefined}\n className={styles.root({ class: cn(classNames?.root, className) })}\n >\n <div className={styles.body({ class: classNames?.body })}>\n {headerContent != null && (\n <div data-rail-header=\"\" className={styles.header({ class: classNames?.header })}>\n <DensityZeroButtons>{headerContent}</DensityZeroButtons>\n </div>\n )}\n <div\n data-item-alignment={itemAlignment}\n onKeyDown={onItemArrowKeys('vertical')}\n className={styles.items({ class: classNames?.items })}\n >\n <RailContext\n value={{\n layout: inlineExpanded ? 'rail-start' : 'rail-top',\n enterLabel: modeChanged,\n ...(slide && inlineExpanded && { lastIndicatorRef }),\n }}\n >\n {children}\n </RailContext>\n </div>\n </div>\n </nav>\n )}\n {modal && (\n <ModalRail\n open={isExpanded}\n onClose={() => setExpanded(false)}\n header={headerContent}\n naming={rest as Naming}\n slide={hideOnCollapse !== false}\n itemAlignment={itemAlignment}\n slidingIndicator={slide}\n railRef={rootRef}\n classNames={classNames}\n >\n {children}\n </ModalRail>\n )}\n </>\n );\n}\n\n/** The sheet's exit transitions; whichever runs marks the end of its exit. */\nconst SHEET_EXIT_PROPERTIES = ['width', 'translate', 'opacity'] as const;\n\nfunction ModalRail({\n open,\n onClose,\n header,\n naming,\n slide,\n itemAlignment,\n slidingIndicator,\n railRef,\n classNames,\n children,\n}: {\n open: boolean;\n onClose: () => void;\n header: ReactNode;\n naming: Naming;\n slide: boolean;\n itemAlignment: 'top' | 'center';\n /** `indicatorMotion=\"slide\"`: the pill slides between items. */\n slidingIndicator: boolean;\n railRef: RefObject<HTMLElement | null>;\n classNames?: NavigationRailClassNames;\n children: ReactNode;\n}) {\n const state = useOverlayTriggerState({\n isOpen: open,\n onOpenChange: (isOpen) => {\n if (!isOpen) onClose();\n },\n });\n // Presence ends with the sheet's own motion, not the scrim's shorter fade, which removed\n // the sheet mid-spring inside the collapsed rail (2026-10-15). Width when it shrinks back,\n // translate when it slides out, opacity under reduced motion.\n const { isPresent, isExiting, exitProps } = usePresence(open, SHEET_EXIT_PROPERTIES);\n if (!isPresent) return null;\n return (\n <Overlay exiting={isExiting}>\n <ModalSheet\n state={state}\n isExiting={isExiting}\n exitProps={exitProps}\n header={header}\n naming={naming}\n slide={slide}\n itemAlignment={itemAlignment}\n slidingIndicator={slidingIndicator}\n railRef={railRef}\n classNames={classNames}\n >\n {children}\n </ModalSheet>\n </Overlay>\n );\n}\n\nfunction ModalSheet({\n state,\n isExiting,\n exitProps,\n header,\n naming,\n slide,\n itemAlignment,\n slidingIndicator,\n railRef,\n classNames,\n children,\n}: {\n state: OverlayTriggerState;\n isExiting: boolean;\n exitProps: ReturnType<typeof usePresence>['exitProps'];\n header: ReactNode;\n naming: Naming;\n slide: boolean;\n itemAlignment: 'top' | 'center';\n /** `indicatorMotion=\"slide\"`: the pill slides between items. */\n slidingIndicator: boolean;\n railRef: RefObject<HTMLElement | null>;\n classNames?: NavigationRailClassNames;\n children: ReactNode;\n}) {\n const ref = useRef<HTMLDivElement>(null);\n const lastIndicatorRef = useRef<HTMLElement>(null);\n const { modalProps, underlayProps } = useModalOverlay({ isDismissable: !isExiting }, state, ref);\n const { dialogProps } = useDialog(naming, ref);\n // The sheet is portalled out of the rail, so it can't inherit a collapsed width set on the\n // rail itself. Copy it before the width is measured and the sheet first paints.\n useLayoutEffect(() => {\n const rail = railRef.current;\n if (!rail || !ref.current) return;\n ref.current.style.setProperty(\n COLLAPSED_WIDTH_VAR,\n getComputedStyle(rail).getPropertyValue(COLLAPSED_WIDTH_VAR),\n );\n }, [railRef]);\n // M3: initial focus lands on the first interactive item, whether it's the menu button,\n // the FAB or the first destination (useDialog would otherwise focus the sheet itself).\n useLayoutEffect(() => {\n const first = ref.current?.querySelector<HTMLElement>(FOCUSABLE);\n first?.focus();\n }, []);\n const width = useExpandedWidth(ref, true, children);\n const widening = useWidening(ref, true);\n const styles = navigationRailStyles({\n expanded: true,\n sheetEntry: slide ? 'slide' : 'grow',\n itemAlignment,\n slidingIndicator,\n });\n const exiting = isExiting || undefined;\n return (\n <>\n <div\n {...underlayProps}\n aria-hidden=\"true\"\n data-exiting={exiting}\n className={styles.scrim({ class: classNames?.scrim })}\n />\n <div\n {...mergeProps(modalProps, dialogProps, exitProps)}\n ref={ref}\n data-tokens=\"md-comp-nav-rail\"\n data-modal=\"\"\n data-exiting={exiting}\n data-widening={widening || undefined}\n style={{ '--m3-rail-width': `${width}px` } as CSSProperties}\n className={styles.sheet({ class: classNames?.sheet })}\n >\n {header != null && (\n <div data-rail-header=\"\" className={styles.header({ class: classNames?.header })}>\n <DensityZeroButtons>{header}</DensityZeroButtons>\n </div>\n )}\n <nav\n aria-label={'aria-label' in naming ? naming['aria-label'] : undefined}\n aria-labelledby={'aria-labelledby' in naming ? naming['aria-labelledby'] : undefined}\n data-item-alignment={itemAlignment}\n onKeyDown={onItemArrowKeys('vertical')}\n className={styles.items({ class: classNames?.items })}\n >\n <RailContext\n value={{\n layout: 'rail-start',\n enterLabel: false,\n ...(slidingIndicator && { lastIndicatorRef }),\n onNavigate: state.close,\n }}\n >\n {children}\n </RailContext>\n </nav>\n </div>\n </>\n );\n}\n\nexport type NavigationRailItemProps = NavItemProps;\n\n/** What {@link useNavigationRail} reports about the rail around a component. */\nexport interface NavigationRailState {\n /** Whether the rail shows its labels beside the icons (expanded, or the modal sheet). */\n expanded: boolean;\n /** Whether this is the rail's modal sheet. */\n modal: boolean;\n /** Collapses the modal sheet; does nothing in the rail itself. */\n close: () => void;\n}\n\n/**\n * The rail around a component, for a custom destination that sits in a {@link NavigationRail}\n * beside its items and changes with it. Throws outside a rail.\n */\nexport function useNavigationRail(): NavigationRailState {\n const rail = useContext(RailContext);\n if (!rail) throw new Error('useNavigationRail must be used inside a NavigationRail');\n return {\n expanded: rail.layout === 'rail-start',\n modal: rail.onNavigate !== undefined,\n close: () => rail.onNavigate?.(),\n };\n}\n\n/** A destination in a {@link NavigationRail}: a link (`href`) or a button. */\nexport function NavigationRailItem({ onPress, ...props }: NavigationRailItemProps) {\n const rail = useContext(RailContext);\n if (!rail) throw new Error('NavigationRailItem must be rendered inside a NavigationRail');\n return (\n <NavItem\n {...(props as NavItemProps)}\n layout={rail.layout}\n enterLabel={rail.enterLabel}\n {...(rail.lastIndicatorRef && { lastIndicatorRef: rail.lastIndicatorRef })}\n onPress={(event) => {\n onPress?.(event);\n rail.onNavigate?.();\n }}\n />\n );\n}\n"],"mappings":"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"}
@@ -6,10 +6,11 @@ import { Overlay as Overlay$1 } from "../../primitives/Overlay.js";
6
6
  import { pickDescriptionAria, splitRootProps } from "../../utils/root-props.js";
7
7
  import { COMPACT_WINDOW, useMediaQuery } from "../date-picker/use-media-query.js";
8
8
  import { searchBarStyles } from "./search-styles.js";
9
+ import { SearchFieldFrame } from "./search-field-frame.js";
9
10
  import { createContext, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
10
11
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
11
12
  import { useReducedMotion } from "motion/react";
12
- import { mergeProps, mergeRefs, useDialog, useFocusRing, useModalOverlay, useObjectRef } from "react-aria";
13
+ import { mergeProps, mergeRefs, useDialog, useModalOverlay, useObjectRef } from "react-aria";
13
14
  import { useOverlayTriggerState } from "react-stately";
14
15
  import { useControlledState } from "react-stately/useControlledState";
15
16
  //#region src/components/search/SearchBar.tsx
@@ -93,19 +94,23 @@ function SearchBar(props) {
93
94
  const { "aria-describedby": describedBy, "aria-details": details } = own;
94
95
  const describedByInput = [describedBy, consumerInputProps?.["aria-describedby"]].filter(Boolean).join(" ") || void 0;
95
96
  const collapsedTone = appBar ? appBar.scrolled ? "scrolled" : "appBar" : "default";
96
- const field = (input, tone) => /* @__PURE__ */ jsx(SearchField, {
97
- className: searchBarStyles({ tone }).field({ class: classNames?.field }),
98
- leading: leading != null && /* @__PURE__ */ jsx("span", {
99
- "data-slot": "leading",
100
- className: styles.leading({ class: classNames?.leading }),
101
- children: leading
102
- }),
103
- trailing: trailing != null && /* @__PURE__ */ jsx("span", {
104
- "data-slot": "trailing",
105
- className: styles.trailing({ class: classNames?.trailing }),
106
- children: trailing
107
- }),
108
- children: input
97
+ const field = (input, tone) => /* @__PURE__ */ jsx(ButtonContext, {
98
+ value: standardIconButtons,
99
+ children: /* @__PURE__ */ jsx(SearchFieldFrame, {
100
+ tone,
101
+ className: classNames?.field,
102
+ leading: leading != null && /* @__PURE__ */ jsx("span", {
103
+ "data-slot": "leading",
104
+ className: styles.leading({ class: classNames?.leading }),
105
+ children: leading
106
+ }),
107
+ trailing: trailing != null && /* @__PURE__ */ jsx("span", {
108
+ "data-slot": "trailing",
109
+ className: styles.trailing({ class: classNames?.trailing }),
110
+ children: trailing
111
+ }),
112
+ children: input
113
+ })
109
114
  });
110
115
  const sharedInputProps = (handlers) => ({
111
116
  ...mergeProps({
@@ -209,26 +214,6 @@ function SearchBar(props) {
209
214
  })]
210
215
  });
211
216
  }
212
- function SearchField({ className, leading, trailing, children }) {
213
- const { isFocusVisible, focusProps } = useFocusRing({
214
- within: true,
215
- isTextInput: true
216
- });
217
- return /* @__PURE__ */ jsx("div", {
218
- ...focusProps,
219
- "data-tokens": "md-comp-search-bar",
220
- "data-focus-visible": isFocusVisible || void 0,
221
- className,
222
- children: /* @__PURE__ */ jsxs(ButtonContext, {
223
- value: standardIconButtons,
224
- children: [
225
- leading,
226
- children,
227
- trailing
228
- ]
229
- })
230
- });
231
- }
232
217
  function ExpandedInput({ inputRef, open, ...props }) {
233
218
  const ref = useObjectRef(inputRef);
234
219
  useEffect(() => {