@windmorph/react 0.1.2 → 0.2.0

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 (45) hide show
  1. package/dist/components/checkbox/checkbox-tokens.d.ts +1 -1
  2. package/dist/components/date-picker/date-picker-styles.d.ts +3 -3
  3. package/dist/components/dialog/dialog-styles.js +5 -5
  4. package/dist/components/dialog/dialog-styles.js.map +1 -1
  5. package/dist/components/menu/use-reveal-entering.d.ts +19 -0
  6. package/dist/components/navigation/NavItem.js +1 -1
  7. package/dist/components/navigation/NavItem.js.map +1 -1
  8. package/dist/components/navigation/NavigationRail.d.ts +21 -2
  9. package/dist/components/navigation/NavigationRail.js +18 -4
  10. package/dist/components/navigation/NavigationRail.js.map +1 -1
  11. package/dist/components/navigation/navigation-styles.d.ts +21 -0
  12. package/dist/components/navigation/navigation-styles.js +6 -1
  13. package/dist/components/navigation/navigation-styles.js.map +1 -1
  14. package/dist/components/select/Select.js +6 -1
  15. package/dist/components/select/Select.js.map +1 -1
  16. package/dist/components/select/search-list.js +1 -1
  17. package/dist/components/select/search-list.js.map +1 -1
  18. package/dist/components/snackbar/snackbar-tokens.d.ts +1 -1
  19. package/dist/components/text-field/TextField.js +9 -1
  20. package/dist/components/text-field/TextField.js.map +1 -1
  21. package/dist/components/time-picker/TimeField.d.ts +118 -0
  22. package/dist/components/time-picker/TimeField.js +183 -0
  23. package/dist/components/time-picker/TimeField.js.map +1 -0
  24. package/dist/components/time-picker/TimeFieldDialog.js +70 -0
  25. package/dist/components/time-picker/TimeFieldDialog.js.map +1 -0
  26. package/dist/components/time-picker/TimePicker.js +1 -4
  27. package/dist/components/time-picker/TimePicker.js.map +1 -1
  28. package/dist/components/time-picker/hour-cycle.js +10 -0
  29. package/dist/components/time-picker/hour-cycle.js.map +1 -0
  30. package/dist/components/time-picker/time-format.js +84 -0
  31. package/dist/components/time-picker/time-format.js.map +1 -0
  32. package/dist/composites/button-group/ButtonGroup.d.ts +7 -0
  33. package/dist/composites/button-group/ButtonGroup.js +3 -2
  34. package/dist/composites/button-group/ButtonGroup.js.map +1 -1
  35. package/dist/composites/button-group/button-group-styles.d.ts +15 -3
  36. package/dist/composites/button-group/button-group-styles.js +16 -1
  37. package/dist/composites/button-group/button-group-styles.js.map +1 -1
  38. package/dist/index.d.ts +4 -2
  39. package/dist/index.js +4 -2
  40. package/dist/primitives.d.ts +4 -1
  41. package/dist/primitives.js +4 -1
  42. package/dist/styles.compiled.css +1 -1
  43. package/dist/utils/closed-by.d.ts +5 -0
  44. package/dist/utils/placement.d.ts +11 -0
  45. package/package.json +2 -2
@@ -17,6 +17,13 @@ export declare const navigationRailStyles: import("tailwind-variants").TVReturnT
17
17
  items: string;
18
18
  };
19
19
  };
20
+ /** `hideOnCollapse="compact"`: the collapsed rail shows from 600px wide. */
21
+ hiddenOnCompact: {
22
+ true: {
23
+ root: string;
24
+ };
25
+ false: {};
26
+ };
20
27
  /** A divider on the edge next to the content. */
21
28
  divider: {
22
29
  true: {
@@ -69,6 +76,13 @@ export declare const navigationRailStyles: import("tailwind-variants").TVReturnT
69
76
  items: string;
70
77
  };
71
78
  };
79
+ /** `hideOnCollapse="compact"`: the collapsed rail shows from 600px wide. */
80
+ hiddenOnCompact: {
81
+ true: {
82
+ root: string;
83
+ };
84
+ false: {};
85
+ };
72
86
  /** A divider on the edge next to the content. */
73
87
  divider: {
74
88
  true: {
@@ -121,6 +135,13 @@ export declare const navigationRailStyles: import("tailwind-variants").TVReturnT
121
135
  items: string;
122
136
  };
123
137
  };
138
+ /** `hideOnCollapse="compact"`: the collapsed rail shows from 600px wide. */
139
+ hiddenOnCompact: {
140
+ true: {
141
+ root: string;
142
+ };
143
+ false: {};
144
+ };
124
145
  /** A divider on the edge next to the content. */
125
146
  divider: {
126
147
  true: {
@@ -12,7 +12,7 @@ const oneLineWhileResizing = "[&[data-widening]_[data-nav-label]]:text-nowrap";
12
12
  const navigationRailStyles = tv({
13
13
  slots: {
14
14
  root: [
15
- "flex w-(--m3-rail-width) shrink-0 flex-col text-on-surface",
15
+ "relative flex w-(--m3-rail-width) shrink-0 flex-col text-on-surface",
16
16
  "bg-[var(--_md-comp-nav-rail-collapsed-container-color)] data-expanded:bg-[var(--_md-comp-nav-rail-expanded-container-color)]",
17
17
  itemInset,
18
18
  oneLineWhileResizing,
@@ -55,6 +55,11 @@ const navigationRailStyles = tv({
55
55
  top: {},
56
56
  center: { items: "my-auto" }
57
57
  },
58
+ /** `hideOnCollapse="compact"`: the collapsed rail shows from 600px wide. */
59
+ hiddenOnCompact: {
60
+ true: { root: "max-medium:hidden" },
61
+ false: {}
62
+ },
58
63
  /** A divider on the edge next to the content. */
59
64
  divider: {
60
65
  true: { root: "border-e border-outline-variant" },
@@ -1 +1 @@
1
- {"version":3,"file":"navigation-styles.js","names":[],"sources":["../../../src/components/navigation/navigation-styles.ts"],"sourcesContent":["import { reducedMotionFade } from '../../motion/reduced-motion';\nimport { tv, type VariantProps } from '../../utils/tv';\n\n/*\n * Values follow Compose Material 3 (WideNavigationRail.kt, ShortNavigationBar.kt and the\n * NavigationRail* / NavigationBar* token files).\n *\n * Rail: `surface`, 96px collapsed; expanded it is as wide as its widest item plus the 20px\n * insets, 220–360px, and the width springs on the default spatial spring. Content starts\n * 44px down; a header sits 40px above the items, which are 4px apart collapsed and flush\n * expanded. The modal expanded rail is `surface-container` at level 2 with 16px end\n * corners over a 32% scrim; it grows from the collapsed width on the fast spatial spring, or\n * slides in on the default one when the collapsed rail is hidden. It stays mounted until its\n * own width or slide (or, under reduced motion, its fade) has ended.\n *\n * Icons stay on one line in both states, through the middle of the collapsed rail, as in\n * M3's rail motion: only the labels and the rail's edge move. While the width springs open\n * (and while the modal sheet shrinks away) labels stay on one line, clipped by the rail, so\n * none wraps letter by letter in a rail that is still narrow and no item grows taller. So nothing is centred in\n * the animated width. The collapsed width is `--m3-rail-collapsed-width` (96px), and the\n * item inset `--m3-rail-item-inset` is half of what's left beside a 56px pill (20px). The\n * header is a 56px column at that inset: anything up to 56px (the menu button, a FAB) is\n * centred on the icon line, and anything wider (an extended FAB) keeps its width and starts\n * at the column's edge.\n *\n * Items align as a group to the top (default) or, with `itemAlignment=\"center\"`, to the\n * middle of the space below the header (M3: centre alignment on tablets); the header stays\n * at the top. An optional divider (`outline-variant`) marks the edge next to the content.\n *\n * Bar: `surface-container`, at least 64px (it grows when labels wrap), no elevation.\n * Stacked items share the width equally, or, when centred, sit within side padding of\n * (100% − 10% × (items + 3)) / 2 (up to 6 items). Items beside their icons\n * (`iconPlacement=\"start\"`) are centred by default and sized to their content, so the extra\n * space goes to the bar's ends (M3: \"Horizontal navigation items have a fixed width\").\n * With `scrollBehavior=\"hide-on-scroll\"` the bar is sticky at the bottom and its offset\n * (`--m3-nav-bar-offset`, 0 down to minus its height) follows the page scroll.\n *\n * The rail's width is the `--m3-rail-width` variable, so a consumer `w-*` class still wins;\n * a narrower or wider collapsed rail sets `--m3-rail-collapsed-width` instead.\n *\n * Container colours and the modal rail's corners come from the component tokens\n * (`navigation-tokens.ts`, `--md-comp-nav-bar-*` and `--md-comp-nav-rail-*`): the bar, the\n * rail and the modal rail's sheet (`data-modal`) carry `data-tokens`. A consumer class still\n * wins in `cn()`.\n * Every class is written out in full so Tailwind can find it.\n */\n\n/** Half of what's left beside a 56px pill in the collapsed rail. */\nconst itemInset = '[--m3-rail-item-inset:calc((var(--m3-rail-collapsed-width)_-_56px)/2)]';\n\n/**\n * While the expanded rail is still widening (`data-widening`), labels stay on one line and\n * the rail clips them, so the items keep their height and the icons their places.\n */\nconst oneLineWhileResizing = '[&[data-widening]_[data-nav-label]]:text-nowrap';\n\nconst widthMotion = [\n '[transition-property:width]',\n '[transition-duration:var(--md-sys-motion-spring-spatial-default-duration)]',\n '[transition-timing-function:var(--md-sys-motion-spring-spatial-default-easing)]',\n];\n\n/** Variant definitions for {@link NavigationRail}; extend them to add variants. */\nexport const navigationRailStyles = tv({\n slots: {\n root: [\n 'flex w-(--m3-rail-width) shrink-0 flex-col text-on-surface',\n 'bg-[var(--_md-comp-nav-rail-collapsed-container-color)] data-expanded:bg-[var(--_md-comp-nav-rail-expanded-container-color)]',\n itemInset,\n oneLineWhileResizing,\n ...widthMotion,\n ],\n // Library-owned wrapper: clips labels while the width animates.\n body: 'flex min-h-0 flex-1 flex-col overflow-x-clip overflow-y-auto pt-[44px]',\n // Children keep their own width (a centred grid item would shrink to the 56px column).\n header:\n 'grid grid-cols-[56px] justify-items-center-safe ps-(--m3-rail-item-inset) pb-[40px] [&>*]:min-w-max',\n // The gap between items springs 4 → 0px as the rail expands (default spatial, as\n // Compose's itemVerticalSpacedBy; 2026-10-17), instant under reduced motion.\n items: [\n 'flex flex-col',\n '[transition-property:gap] [transition-duration:var(--md-sys-motion-spring-spatial-default-duration)] [transition-timing-function:var(--md-sys-motion-spring-spatial-default-easing)]',\n ],\n scrim: [\n 'fixed inset-0 z-(--md-sys-z-scrim) bg-scrim/32 opacity-100',\n '[transition-property:opacity]',\n '[transition-duration:var(--md-sys-motion-spring-effects-default-duration)]',\n '[transition-timing-function:var(--md-sys-motion-spring-effects-default-easing)]',\n // In and out on the default effects spring, as Compose's modal rail scrim (2026-10-15).\n 'starting:opacity-0 data-exiting:opacity-0',\n ],\n sheet: [\n 'fixed inset-y-0 start-0 z-(--md-sys-z-sheet) flex w-(--m3-rail-width) flex-col overflow-x-clip overflow-y-auto pt-[44px]',\n 'rounded-e-[var(--_md-comp-nav-rail-expanded-modal-container-shape)] bg-[var(--_md-comp-nav-rail-expanded-modal-container-color)]',\n 'text-on-surface shadow-elevation-2 outline-none',\n itemInset,\n oneLineWhileResizing,\n '[&[data-exiting]_[data-nav-label]]:text-nowrap',\n '[transition-property:width,translate]',\n '[transition-duration:var(--md-sys-motion-spring-spatial-fast-duration)]',\n '[transition-timing-function:var(--md-sys-motion-spring-spatial-fast-easing)]',\n // Under reduced motion it fades in and out at its full width.\n ...reducedMotionFade,\n ],\n },\n variants: {\n expanded: {\n true: { items: 'gap-0' },\n false: { items: 'gap-[4px]' },\n },\n /** Auto margins centre the items without hiding any when the rail overflows. */\n itemAlignment: {\n top: {},\n center: { items: 'my-auto' },\n },\n /** A divider on the edge next to the content. */\n divider: {\n true: { root: 'border-e border-outline-variant' },\n false: {},\n },\n /**\n * `indicatorMotion=\"slide\"` in the expanded rail: the list is isolated, so the items'\n * raised content stays above a sliding pill and no higher.\n */\n slidingIndicator: {\n true: { items: 'isolate' },\n false: {},\n },\n /**\n * Modal sheet entry: from the collapsed width, or sliding in from the start edge. Under\n * reduced motion it fades instead.\n */\n sheetEntry: {\n grow: {\n sheet:\n 'motion-safe:starting:w-(--m3-rail-collapsed-width) motion-safe:data-exiting:w-(--m3-rail-collapsed-width)',\n },\n // Sliding in, the sheet takes the default spatial spring, as Compose's modal rail\n // (`WideNavigationRail.kt`): the fast one overshot 21px off the window edge (2026-10-15).\n slide: {\n sheet: [\n 'motion-safe:starting:-translate-x-full motion-safe:data-exiting:-translate-x-full motion-safe:rtl:starting:translate-x-full motion-safe:rtl:data-exiting:translate-x-full',\n '[transition-duration:var(--md-sys-motion-spring-spatial-default-duration)]',\n '[transition-timing-function:var(--md-sys-motion-spring-spatial-default-easing)]',\n ],\n },\n },\n },\n defaultVariants: {\n expanded: false,\n sheetEntry: 'grow',\n itemAlignment: 'top',\n divider: false,\n slidingIndicator: false,\n },\n});\n\n/** Variant definitions for {@link NavigationBar}; extend them to add variants. */\nexport const navigationBarStyles = tv({\n slots: {\n root: 'flex min-h-[64px] w-full bg-[var(--_md-comp-nav-bar-container-color)] text-on-surface',\n item: 'min-w-0',\n },\n variants: {\n arrangement: {\n equal: { item: 'flex-1 basis-0' },\n centered: { root: 'justify-center', item: 'flex-1 basis-0' },\n },\n iconPlacement: { top: {}, start: {} },\n hideOnScroll: {\n // Settling after a scroll ends (`data-settling`), the offset eases to shown or hidden on\n // the default effects spring; while scrolling it follows the scroll directly.\n true: {\n root: [\n 'sticky bottom-(--m3-nav-bar-offset) z-(--md-sys-z-sticky)',\n 'data-settling:[transition-property:bottom]',\n 'data-settling:[transition-duration:var(--md-sys-motion-spring-effects-default-duration)]',\n 'data-settling:[transition-timing-function:var(--md-sys-motion-spring-effects-default-easing)]',\n ],\n },\n false: {},\n },\n },\n compoundVariants: [\n // Items beside their icons keep their own width; the extra space goes to the ends.\n { arrangement: 'centered', iconPlacement: 'start', class: { item: 'flex-initial basis-auto' } },\n ],\n defaultVariants: { arrangement: 'equal', iconPlacement: 'top', hideOnScroll: false },\n});\n\n/**\n * The variants of {@link navigationBarStyles}, 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 NavigationBarStyleProps = VariantProps<typeof navigationBarStyles>;\nexport type NavigationBarArrangement = NonNullable<NavigationBarStyleProps['arrangement']>;\n"],"mappings":";;;;AAgDA,MAAM,YAAY;;;;;AAMlB,MAAM,uBAAuB;;AAS7B,MAAa,uBAAuB,GAAG;CACrC,OAAO;EACL,MAAM;GACJ;GACA;GACA;GACA;GACA,GAAG;IAbP;IACA;IACA;GAWO;EACL;EAEA,MAAM;EAEN,QACE;EAGF,OAAO,CACL,iBACA,sLACF;EACA,OAAO;GACL;GACA;GACA;GACA;GAEA;EACF;EACA,OAAO;GACL;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GAEA,GAAG;EACL;CACF;CACA,UAAU;EACR,UAAU;GACR,MAAM,EAAE,OAAO,QAAQ;GACvB,OAAO,EAAE,OAAO,YAAY;EAC9B;;EAEA,eAAe;GACb,KAAK,CAAC;GACN,QAAQ,EAAE,OAAO,UAAU;EAC7B;;EAEA,SAAS;GACP,MAAM,EAAE,MAAM,kCAAkC;GAChD,OAAO,CAAC;EACV;;;;;EAKA,kBAAkB;GAChB,MAAM,EAAE,OAAO,UAAU;GACzB,OAAO,CAAC;EACV;;;;;EAKA,YAAY;GACV,MAAM,EACJ,OACE,4GACJ;GAGA,OAAO,EACL,OAAO;IACL;IACA;IACA;GACF,EACF;EACF;CACF;CACA,iBAAiB;EACf,UAAU;EACV,YAAY;EACZ,eAAe;EACf,SAAS;EACT,kBAAkB;CACpB;AACF,CAAC;;AAGD,MAAa,sBAAsB,GAAG;CACpC,OAAO;EACL,MAAM;EACN,MAAM;CACR;CACA,UAAU;EACR,aAAa;GACX,OAAO,EAAE,MAAM,iBAAiB;GAChC,UAAU;IAAE,MAAM;IAAkB,MAAM;GAAiB;EAC7D;EACA,eAAe;GAAE,KAAK,CAAC;GAAG,OAAO,CAAC;EAAE;EACpC,cAAc;GAGZ,MAAM,EACJ,MAAM;IACJ;IACA;IACA;IACA;GACF,EACF;GACA,OAAO,CAAC;EACV;CACF;CACA,kBAAkB,CAEhB;EAAE,aAAa;EAAY,eAAe;EAAS,OAAO,EAAE,MAAM,0BAA0B;CAAE,CAChG;CACA,iBAAiB;EAAE,aAAa;EAAS,eAAe;EAAO,cAAc;CAAM;AACrF,CAAC"}
1
+ {"version":3,"file":"navigation-styles.js","names":[],"sources":["../../../src/components/navigation/navigation-styles.ts"],"sourcesContent":["import { reducedMotionFade } from '../../motion/reduced-motion';\nimport { tv, type VariantProps } from '../../utils/tv';\n\n/*\n * Values follow Compose Material 3 (WideNavigationRail.kt, ShortNavigationBar.kt and the\n * NavigationRail* / NavigationBar* token files).\n *\n * Rail: `surface`, 96px collapsed; expanded it is as wide as its widest item plus the 20px\n * insets, 220–360px, and the width springs on the default spatial spring. Content starts\n * 44px down; a header sits 40px above the items, which are 4px apart collapsed and flush\n * expanded. The modal expanded rail is `surface-container` at level 2 with 16px end\n * corners over a 32% scrim; it grows from the collapsed width on the fast spatial spring, or\n * slides in on the default one when the collapsed rail is hidden. It stays mounted until its\n * own width or slide (or, under reduced motion, its fade) has ended.\n *\n * Icons stay on one line in both states, through the middle of the collapsed rail, as in\n * M3's rail motion: only the labels and the rail's edge move. While the width springs open\n * (and while the modal sheet shrinks away) labels stay on one line, clipped by the rail, so\n * none wraps letter by letter in a rail that is still narrow and no item grows taller. So nothing is centred in\n * the animated width. The collapsed width is `--m3-rail-collapsed-width` (96px), and the\n * item inset `--m3-rail-item-inset` is half of what's left beside a 56px pill (20px). The\n * header is a 56px column at that inset: anything up to 56px (the menu button, a FAB) is\n * centred on the icon line, and anything wider (an extended FAB) keeps its width and starts\n * at the column's edge.\n *\n * Items align as a group to the top (default) or, with `itemAlignment=\"center\"`, to the\n * middle of the space below the header (M3: centre alignment on tablets); the header stays\n * at the top. An optional divider (`outline-variant`) marks the edge next to the content.\n *\n * Bar: `surface-container`, at least 64px (it grows when labels wrap), no elevation.\n * Stacked items share the width equally, or, when centred, sit within side padding of\n * (100% − 10% × (items + 3)) / 2 (up to 6 items). Items beside their icons\n * (`iconPlacement=\"start\"`) are centred by default and sized to their content, so the extra\n * space goes to the bar's ends (M3: \"Horizontal navigation items have a fixed width\").\n * With `scrollBehavior=\"hide-on-scroll\"` the bar is sticky at the bottom and its offset\n * (`--m3-nav-bar-offset`, 0 down to minus its height) follows the page scroll.\n *\n * The rail's width is the `--m3-rail-width` variable, so a consumer `w-*` class still wins;\n * a narrower or wider collapsed rail sets `--m3-rail-collapsed-width` instead.\n *\n * Container colours and the modal rail's corners come from the component tokens\n * (`navigation-tokens.ts`, `--md-comp-nav-bar-*` and `--md-comp-nav-rail-*`): the bar, the\n * rail and the modal rail's sheet (`data-modal`) carry `data-tokens`. A consumer class still\n * wins in `cn()`.\n * Every class is written out in full so Tailwind can find it.\n */\n\n/** Half of what's left beside a 56px pill in the collapsed rail. */\nconst itemInset = '[--m3-rail-item-inset:calc((var(--m3-rail-collapsed-width)_-_56px)/2)]';\n\n/**\n * While the expanded rail is still widening (`data-widening`), labels stay on one line and\n * the rail clips them, so the items keep their height and the icons their places.\n */\nconst oneLineWhileResizing = '[&[data-widening]_[data-nav-label]]:text-nowrap';\n\nconst widthMotion = [\n '[transition-property:width]',\n '[transition-duration:var(--md-sys-motion-spring-spatial-default-duration)]',\n '[transition-timing-function:var(--md-sys-motion-spring-spatial-default-easing)]',\n];\n\n/** Variant definitions for {@link NavigationRail}; extend them to add variants. */\nexport const navigationRailStyles = tv({\n slots: {\n root: [\n // Positioned, so a secondary column placed against it escapes the body's scrolling.\n 'relative flex w-(--m3-rail-width) shrink-0 flex-col text-on-surface',\n 'bg-[var(--_md-comp-nav-rail-collapsed-container-color)] data-expanded:bg-[var(--_md-comp-nav-rail-expanded-container-color)]',\n itemInset,\n oneLineWhileResizing,\n ...widthMotion,\n ],\n // Library-owned wrapper: clips labels while the width animates.\n body: 'flex min-h-0 flex-1 flex-col overflow-x-clip overflow-y-auto pt-[44px]',\n // Children keep their own width (a centred grid item would shrink to the 56px column).\n header:\n 'grid grid-cols-[56px] justify-items-center-safe ps-(--m3-rail-item-inset) pb-[40px] [&>*]:min-w-max',\n // The gap between items springs 4 → 0px as the rail expands (default spatial, as\n // Compose's itemVerticalSpacedBy; 2026-10-17), instant under reduced motion.\n items: [\n 'flex flex-col',\n '[transition-property:gap] [transition-duration:var(--md-sys-motion-spring-spatial-default-duration)] [transition-timing-function:var(--md-sys-motion-spring-spatial-default-easing)]',\n ],\n scrim: [\n 'fixed inset-0 z-(--md-sys-z-scrim) bg-scrim/32 opacity-100',\n '[transition-property:opacity]',\n '[transition-duration:var(--md-sys-motion-spring-effects-default-duration)]',\n '[transition-timing-function:var(--md-sys-motion-spring-effects-default-easing)]',\n // In and out on the default effects spring, as Compose's modal rail scrim (2026-10-15).\n 'starting:opacity-0 data-exiting:opacity-0',\n ],\n sheet: [\n 'fixed inset-y-0 start-0 z-(--md-sys-z-sheet) flex w-(--m3-rail-width) flex-col overflow-x-clip overflow-y-auto pt-[44px]',\n 'rounded-e-[var(--_md-comp-nav-rail-expanded-modal-container-shape)] bg-[var(--_md-comp-nav-rail-expanded-modal-container-color)]',\n 'text-on-surface shadow-elevation-2 outline-none',\n itemInset,\n oneLineWhileResizing,\n '[&[data-exiting]_[data-nav-label]]:text-nowrap',\n '[transition-property:width,translate]',\n '[transition-duration:var(--md-sys-motion-spring-spatial-fast-duration)]',\n '[transition-timing-function:var(--md-sys-motion-spring-spatial-fast-easing)]',\n // Under reduced motion it fades in and out at its full width.\n ...reducedMotionFade,\n ],\n },\n variants: {\n expanded: {\n true: { items: 'gap-0' },\n false: { items: 'gap-[4px]' },\n },\n /** Auto margins centre the items without hiding any when the rail overflows. */\n itemAlignment: {\n top: {},\n center: { items: 'my-auto' },\n },\n /** `hideOnCollapse=\"compact\"`: the collapsed rail shows from 600px wide. */\n hiddenOnCompact: {\n true: { root: 'max-medium:hidden' },\n false: {},\n },\n /** A divider on the edge next to the content. */\n divider: {\n true: { root: 'border-e border-outline-variant' },\n false: {},\n },\n /**\n * `indicatorMotion=\"slide\"` in the expanded rail: the list is isolated, so the items'\n * raised content stays above a sliding pill and no higher.\n */\n slidingIndicator: {\n true: { items: 'isolate' },\n false: {},\n },\n /**\n * Modal sheet entry: from the collapsed width, or sliding in from the start edge. Under\n * reduced motion it fades instead.\n */\n sheetEntry: {\n grow: {\n sheet:\n 'motion-safe:starting:w-(--m3-rail-collapsed-width) motion-safe:data-exiting:w-(--m3-rail-collapsed-width)',\n },\n // Sliding in, the sheet takes the default spatial spring, as Compose's modal rail\n // (`WideNavigationRail.kt`): the fast one overshot 21px off the window edge (2026-10-15).\n slide: {\n sheet: [\n 'motion-safe:starting:-translate-x-full motion-safe:data-exiting:-translate-x-full motion-safe:rtl:starting:translate-x-full motion-safe:rtl:data-exiting:translate-x-full',\n '[transition-duration:var(--md-sys-motion-spring-spatial-default-duration)]',\n '[transition-timing-function:var(--md-sys-motion-spring-spatial-default-easing)]',\n ],\n },\n },\n },\n defaultVariants: {\n expanded: false,\n sheetEntry: 'grow',\n itemAlignment: 'top',\n divider: false,\n slidingIndicator: false,\n },\n});\n\n/** Variant definitions for {@link NavigationBar}; extend them to add variants. */\nexport const navigationBarStyles = tv({\n slots: {\n root: 'flex min-h-[64px] w-full bg-[var(--_md-comp-nav-bar-container-color)] text-on-surface',\n item: 'min-w-0',\n },\n variants: {\n arrangement: {\n equal: { item: 'flex-1 basis-0' },\n centered: { root: 'justify-center', item: 'flex-1 basis-0' },\n },\n iconPlacement: { top: {}, start: {} },\n hideOnScroll: {\n // Settling after a scroll ends (`data-settling`), the offset eases to shown or hidden on\n // the default effects spring; while scrolling it follows the scroll directly.\n true: {\n root: [\n 'sticky bottom-(--m3-nav-bar-offset) z-(--md-sys-z-sticky)',\n 'data-settling:[transition-property:bottom]',\n 'data-settling:[transition-duration:var(--md-sys-motion-spring-effects-default-duration)]',\n 'data-settling:[transition-timing-function:var(--md-sys-motion-spring-effects-default-easing)]',\n ],\n },\n false: {},\n },\n },\n compoundVariants: [\n // Items beside their icons keep their own width; the extra space goes to the ends.\n { arrangement: 'centered', iconPlacement: 'start', class: { item: 'flex-initial basis-auto' } },\n ],\n defaultVariants: { arrangement: 'equal', iconPlacement: 'top', hideOnScroll: false },\n});\n\n/**\n * The variants of {@link navigationBarStyles}, 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 NavigationBarStyleProps = VariantProps<typeof navigationBarStyles>;\nexport type NavigationBarArrangement = NonNullable<NavigationBarStyleProps['arrangement']>;\n"],"mappings":";;;;AAgDA,MAAM,YAAY;;;;;AAMlB,MAAM,uBAAuB;;AAS7B,MAAa,uBAAuB,GAAG;CACrC,OAAO;EACL,MAAM;GAEJ;GACA;GACA;GACA;GACA,GAAG;IAdP;IACA;IACA;GAYO;EACL;EAEA,MAAM;EAEN,QACE;EAGF,OAAO,CACL,iBACA,sLACF;EACA,OAAO;GACL;GACA;GACA;GACA;GAEA;EACF;EACA,OAAO;GACL;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GAEA,GAAG;EACL;CACF;CACA,UAAU;EACR,UAAU;GACR,MAAM,EAAE,OAAO,QAAQ;GACvB,OAAO,EAAE,OAAO,YAAY;EAC9B;;EAEA,eAAe;GACb,KAAK,CAAC;GACN,QAAQ,EAAE,OAAO,UAAU;EAC7B;;EAEA,iBAAiB;GACf,MAAM,EAAE,MAAM,oBAAoB;GAClC,OAAO,CAAC;EACV;;EAEA,SAAS;GACP,MAAM,EAAE,MAAM,kCAAkC;GAChD,OAAO,CAAC;EACV;;;;;EAKA,kBAAkB;GAChB,MAAM,EAAE,OAAO,UAAU;GACzB,OAAO,CAAC;EACV;;;;;EAKA,YAAY;GACV,MAAM,EACJ,OACE,4GACJ;GAGA,OAAO,EACL,OAAO;IACL;IACA;IACA;GACF,EACF;EACF;CACF;CACA,iBAAiB;EACf,UAAU;EACV,YAAY;EACZ,eAAe;EACf,SAAS;EACT,kBAAkB;CACpB;AACF,CAAC;;AAGD,MAAa,sBAAsB,GAAG;CACpC,OAAO;EACL,MAAM;EACN,MAAM;CACR;CACA,UAAU;EACR,aAAa;GACX,OAAO,EAAE,MAAM,iBAAiB;GAChC,UAAU;IAAE,MAAM;IAAkB,MAAM;GAAiB;EAC7D;EACA,eAAe;GAAE,KAAK,CAAC;GAAG,OAAO,CAAC;EAAE;EACpC,cAAc;GAGZ,MAAM,EACJ,MAAM;IACJ;IACA;IACA;IACA;GACF,EACF;GACA,OAAO,CAAC;EACV;CACF;CACA,kBAAkB,CAEhB;EAAE,aAAa;EAAY,eAAe;EAAS,OAAO,EAAE,MAAM,0BAA0B;CAAE,CAChG;CACA,iBAAiB;EAAE,aAAa;EAAS,eAAe;EAAO,cAAc;CAAM;AACrF,CAAC"}
@@ -34,6 +34,8 @@ const COMPACT_WINDOW = "(max-width: 599.98px)";
34
34
  * results did, and sank behind the keyboard (2026-11-07).
35
35
  */
36
36
  const SEARCH_SHEET_HEIGHT = "h-[calc(100dvh_-_72px_-_var(--m3-sheet-keyboard-inset,0px))] min-[641px]:h-[calc(100dvh_-_56px_-_var(--m3-sheet-keyboard-inset,0px))]";
37
+ /** Options scrolled to by the keyboard stop below the sticky search (52px at most). */
38
+ const SEARCH_SHEET_CONTENT = "scroll-pt-[52px]";
37
39
  /** A read-only Select's keyboard finds no option, so arrows and typeahead change nothing. */
38
40
  const READ_ONLY_DELEGATE = {
39
41
  getFirstKey: () => null,
@@ -300,7 +302,10 @@ function Select(props) {
300
302
  skipPartiallyExpanded: true,
301
303
  open: state.isOpen,
302
304
  onOpenChange: (open) => open ? state.open() : state.close(),
303
- classNames: searchable || classNames?.sheet ? { panel: cn(searchable && SEARCH_SHEET_HEIGHT, classNames?.sheet) } : void 0,
305
+ classNames: searchable ? {
306
+ panel: cn(SEARCH_SHEET_HEIGHT, classNames?.sheet),
307
+ content: SEARCH_SHEET_CONTENT
308
+ } : classNames?.sheet ? { panel: classNames.sheet } : void 0,
304
309
  ...typeof label === "string" ? { "aria-label": label } : { "aria-label": rest["aria-label"] ?? "" },
305
310
  children: /* @__PURE__ */ jsx("div", {
306
311
  ...densityAttribute(density),
@@ -1 +1 @@
1
- {"version":3,"file":"Select.js","names":[],"sources":["../../../src/components/select/Select.tsx"],"sourcesContent":["'use client';\n\nimport {\n useId,\n useRef,\n useSyncExternalStore,\n type AriaAttributes,\n type CSSProperties,\n type HTMLAttributes,\n type ReactNode,\n type Ref,\n} from 'react';\nimport {\n HiddenSelect,\n mergeProps,\n useButton,\n useFocusRing,\n useHover,\n useObjectRef,\n useSelect,\n type AriaSelectOptions,\n} from 'react-aria';\nimport {\n useSelectState,\n type SelectionMode,\n type SelectProps as StatelySelectProps,\n} from 'react-stately/useSelectState';\nimport { useControlledState } from 'react-stately/useControlledState';\nimport type { Key } from 'react-stately';\nimport { matchesSearch } from '../../utils/search';\nimport { assertCollectionChildren } from '../../utils/assert-collection-children';\nimport { densityAttribute, type Density } from '../../tokens/density';\nimport { cn } from '../../utils/cn';\nimport { renderedDescription } from '../../utils/rendered-description';\nimport {\n ariaFlag,\n pickDescriptionAria,\n splitRootProps,\n type WrapperRootDOMProps,\n} from '../../utils/root-props';\nimport { useTypeaheadDelegate } from '../../utils/typeahead';\nimport { usePreloadLoadingIndicator } from '../loading-indicator/lazy-loading-indicator';\nimport { LazyBottomSheet, usePreloadBottomSheet } from '../sheet/lazy-bottom-sheet';\nimport type { TextFieldVariant } from '../text-field/text-field-styles';\nimport { DropdownArrow, FieldShell, fieldState, type FieldShellClassNames } from './field-shell';\nimport {\n FOCUSABLE_DISABLED_OPTIONS,\n OptionList,\n OptionPopover,\n type OptionListClassNames,\n} from './option-list';\nimport { SearchList } from './search-list';\nimport { useSelectionCap } from './selection-cap';\n\n/** How the options open: a menu under the field (M3), a bottom sheet, or a sheet on phones. */\nexport type SelectPresentation = 'menu' | 'sheet' | 'auto';\n\n/** Words the Select shows or announces; override them for other languages. */\nexport interface SelectLabels {\n /** Shown in an empty list. @default \"No options\" */\n empty: string;\n /** The loading indicator's name. @default \"Loading options\" */\n loading: string;\n /** For more chosen options than fit: `+{count}`. @default (count) => `+${count}` */\n more: (count: number) => string;\n /** With `searchable`: the search field's placeholder and name. @default \"Search\" */\n search: string;\n /** With `searchable`: shown when no option matches the search. @default \"No results\" */\n noResults: string;\n /** The error icon's name (M3: \"Error\"). @default \"Error\" */\n error: string;\n}\n\nconst DEFAULT_LABELS: SelectLabels = {\n empty: 'No options',\n loading: 'Loading options',\n more: (count) => `+${count}`,\n search: 'Search',\n noResults: 'No results',\n error: 'Error',\n};\n\n/** A chosen option, as `renderValue` receives it. */\nexport interface SelectedOption<T> {\n key: Key;\n /** Its plain text (`textValue`, or the item's text). */\n textValue: string;\n /** Its object from `items`, if the options come from `items`. */\n value: T | null;\n}\n\nexport interface SelectClassNames extends FieldShellClassNames, OptionListClassNames {\n /** The text showing the chosen option(s) or the placeholder. */\n value?: string;\n /** With `searchable`: the search field. */\n search?: string;\n /**\n * The bottom sheet the options open in (`presentation=\"sheet\"`, or `\"auto\"` on compact\n * windows): the sheet's surface, as its `classNames.panel`.\n */\n sheet?: string;\n}\n\n/** React Aria props this API names the library's way (`disabled`, `open`…), or leaves out. */\ntype RenamedProps =\n | 'isDisabled'\n | 'isRequired'\n | 'isInvalid'\n | 'isOpen'\n | 'label'\n | 'description'\n | 'validationState'\n // React Stately's deprecated single-selection API; `value` / `defaultValue` / `onChange` replace it.\n | 'selectedKey'\n | 'defaultSelectedKey'\n | 'onSelectionChange'\n | 'shouldCloseOnSelect'\n | 'allowsEmptyCollection';\n\nexport type SelectProps<T extends object, M extends SelectionMode = 'single'> = Omit<\n StatelySelectProps<T, M>,\n RenamedProps\n> &\n Pick<AriaSelectOptions<T, M>, 'id' | 'name' | 'autoComplete' | 'form'> &\n Omit<WrapperRootDOMProps, keyof StatelySelectProps<T, M>> & {\n /** The trigger button's id, for a `<label for>` or an error summary's link. */\n id?: string;\n /** The container style, as `TextField`'s: a filled surface or an outline. @default \"filled\" */\n variant?: TextFieldVariant;\n /**\n * Web density, `0` to `-3`: each step takes 4px off the field (56px at 0, 44px at -3) and\n * off its menu's options (44px at 0, 32px at -3). Left out, it takes the density of the\n * nearest `ThemeProvider` or `ThemeScope`. M3 asks for density to be the reader's choice\n * rather than a default, so prefer setting it there, from a setting.\n * @default 0\n */\n density?: Density;\n /** The field's label. Give `aria-label` instead when there's no visible label. */\n label?: ReactNode;\n /** Accessible name when there's no visible label. */\n 'aria-label'?: string;\n /**\n * Shown in the field when nothing is chosen. With a `label`, it appears once the label\n * floats (on focus), as in `TextField`.\n */\n placeholder?: string;\n /**\n * Helper text below the field. Replaced visually by the error when invalid, and shown in\n * the error colour while the field is invalid.\n */\n supportingText?: ReactNode;\n /** Marks the field invalid (in addition to `validate` and `required`). */\n invalid?: boolean;\n /** A choice is required: adds the `*` to the label and validates on submit. */\n required?: boolean;\n /**\n * Read as `invalid` when that's left out, for a form library that sets the attribute. The\n * trigger is a button, which ARIA doesn't let carry `aria-invalid`, so the state reaches\n * assistive technology as the field's error text, its description.\n */\n 'aria-invalid'?: AriaAttributes['aria-invalid'];\n /**\n * Read as `required` when that's left out, for a form library that sets the attribute: the\n * label's `*`, in the trigger's name (a button can't carry `aria-required`).\n */\n 'aria-required'?: AriaAttributes['aria-required'];\n /** Disables the field: it can't be focused or opened. @default false */\n disabled?: boolean;\n /**\n * The choice can be seen but not changed: the menu doesn't open and the keyboard doesn't\n * change it. The root gets `data-readonly`; say so in the supporting text (M3).\n * @default false\n */\n readOnly?: boolean;\n /** Decorative icon at the start, in a 48px box. */\n leadingIcon?: ReactNode;\n /**\n * The icon shown before the arrow while the field is invalid (M3 strongly recommends one).\n * Defaults to Material Symbols `error`, named by `labels.error`; `false` turns it off.\n */\n errorIcon?: ReactNode | false;\n /** Whether the menu is open (controlled); with `defaultOpen` and `onOpenChange`. */\n open?: boolean;\n /**\n * Whether choosing an option closes the menu.\n * @default true for a single choice, false with `selectionMode=\"multiple\"`\n */\n closeOnSelect?: boolean;\n /**\n * With `selectionMode=\"multiple\"`, the most options that can be chosen; the rest show as\n * disabled until one is removed.\n */\n maxSelections?: number;\n /**\n * `menu` (default) opens an M3 menu under the field on every window, as Compose's exposed\n * dropdown menu does; `sheet` always opens a bottom sheet; `auto` opens a sheet on\n * compact windows and a menu on larger ones, for long lists.\n * @default \"menu\"\n */\n presentation?: SelectPresentation;\n /**\n * A search field at the top of the menu or sheet that filters the options as you type,\n * for long lists (pair it with `presentation=\"auto\"`). The search takes focus in the menu;\n * a sheet shows the list first, so the keyboard doesn't cover it.\n * @default false\n */\n searchable?: boolean;\n /**\n * With `searchable`: whether an option matches the search. Defaults to a match anywhere in\n * its text, ignoring case and accents (\"thai\" finds \"Thailand\"). The option's `key` lets\n * it match on more than its text, e.g. a country's ISO code.\n */\n filter?: (textValue: string, search: string, key: Key) => boolean;\n /** With `searchable`: the typed text (controlled); cleared when the list closes. */\n searchValue?: string;\n /** With `searchable`: the initial typed text, when uncontrolled. @default \"\" */\n defaultSearchValue?: string;\n /**\n * With `searchable`: called as the search changes. Given it, the app filters `items` itself\n * (e.g. on a server, with `loading` meanwhile) and the built-in filter is off.\n */\n onSearchChange?: (search: string) => void;\n /**\n * What the field shows for the chosen option(s), e.g. a dialling code or a flag, instead of\n * their text. Screen readers still hear the options' text.\n */\n renderValue?: (selected: SelectedOption<T>[]) => ReactNode;\n /**\n * Shows a loading indicator in the list, e.g. while options are fetched.\n * The indicator's code loads when it first shows, and ahead of that when the field is\n * hovered or focused while this prop is set at all, so pass `loading={false}` (rather than\n * leaving it out) on a field that loads options.\n */\n loading?: boolean;\n /** Words the Select shows or announces, for other languages. */\n labels?: Partial<SelectLabels>;\n /** Classes for the root element, merged with `classNames.root`; consumer classes win. */\n className?: string;\n /** Classes for each part, by slot. */\n classNames?: SelectClassNames;\n /** Inline styles for the root element. */\n style?: CSSProperties;\n /** Ref to the root `<div>` element. Use `triggerRef` for the field's button. */\n ref?: Ref<HTMLDivElement>;\n /** Ref to the field's button. */\n triggerRef?: Ref<HTMLButtonElement>;\n };\n\nconst COMPACT_WINDOW = '(max-width: 599.98px)';\n\n/**\n * A searchable list's sheet takes the full height it may (above an on-screen keyboard too), so\n * filtering changes the list inside, never the sheet: at its own height it shrank as the\n * results did, and sank behind the keyboard (2026-11-07).\n */\nconst SEARCH_SHEET_HEIGHT =\n 'h-[calc(100dvh_-_72px_-_var(--m3-sheet-keyboard-inset,0px))] min-[641px]:h-[calc(100dvh_-_56px_-_var(--m3-sheet-keyboard-inset,0px))]';\n\n/** A read-only Select's keyboard finds no option, so arrows and typeahead change nothing. */\nconst READ_ONLY_DELEGATE: NonNullable<AriaSelectOptions<object>['keyboardDelegate']> = {\n getFirstKey: () => null,\n getLastKey: () => null,\n getKeyBelow: () => null,\n getKeyAbove: () => null,\n getKeyForSearch: () => null,\n};\n\nfunction subscribeToWindow(onChange: () => void) {\n const query = window.matchMedia(COMPACT_WINDOW);\n query.addEventListener('change', onChange);\n return () => query.removeEventListener('change', onChange);\n}\n\n/**\n * M3 exposed dropdown menu, read-only (Compose's `ExposedDropdownMenuBox` with a\n * `PrimaryNotEditable` anchor): a text-field-styled button that opens a menu of options.\n * Single or multiple selection, sections, descriptions and icons, typeahead, a hidden\n * native `<select>` for forms, and validation as in `TextField` (React Aria `useSelect`).\n *\n * @example\n * <Select label=\"Sort by\" defaultValue=\"newest\">\n * <SelectItem key=\"newest\">Newest first</SelectItem>\n * <SelectItem key=\"price\">Price, low to high</SelectItem>\n * </Select>\n */\nexport function Select<T extends object, M extends SelectionMode = 'single'>(\n props: SelectProps<T, M>,\n) {\n const {\n variant = 'filled',\n density,\n label,\n placeholder,\n supportingText,\n invalid: invalidProp,\n required: requiredProp,\n 'aria-invalid': ariaInvalid,\n 'aria-required': ariaRequired,\n disabled = false,\n readOnly = false,\n leadingIcon,\n errorIcon,\n open,\n maxSelections,\n presentation = 'menu',\n searchable = false,\n filter = matchesSearch,\n searchValue,\n defaultSearchValue,\n onSearchChange,\n renderValue,\n loading,\n labels: labelsProp,\n className,\n classNames,\n style,\n ref,\n triggerRef: triggerRefProp,\n name,\n autoComplete,\n form,\n errorMessage,\n closeOnSelect,\n ...otherProps\n } = props;\n // Items given as elements (not `items` with a render function) must come from a client.\n if (typeof props.children !== 'function') {\n assertCollectionChildren(props.children, 'Select', 'SelectItem and SelectSection elements');\n }\n const { root: rootData, rest } = splitRootProps(otherProps, { wrapper: true });\n const invalid = invalidProp ?? ariaFlag(ariaInvalid);\n const required = requiredProp ?? ariaFlag(ariaRequired);\n const labels = { ...DEFAULT_LABELS, ...labelsProp };\n const stately = rest as Omit<StatelySelectProps<T, M>, RenamedProps>;\n const cap = useSelectionCap({\n multiple: stately.selectionMode === 'multiple',\n maxSelections,\n value: stately.value,\n defaultValue: stately.defaultValue,\n onChange: stately.onChange,\n });\n const [search, setSearch] = useControlledState(\n searchValue,\n defaultSearchValue ?? '',\n onSearchChange,\n );\n const ariaProps = {\n ...stately,\n ...cap.valueProps,\n // The search starts afresh each time the list opens.\n onOpenChange: (isOpen: boolean) => {\n if (!isOpen && search !== '') setSearch('');\n stately.onOpenChange?.(isOpen);\n },\n label,\n description: supportingText,\n errorMessage,\n isInvalid: invalid,\n isRequired: required,\n isDisabled: disabled,\n ...(closeOnSelect !== undefined && { shouldCloseOnSelect: closeOnSelect }),\n ...FOCUSABLE_DISABLED_OPTIONS,\n ...(readOnly\n ? { isOpen: false, keyboardDelegate: READ_ONLY_DELEGATE }\n : open !== undefined && { isOpen: open }),\n placeholder,\n name,\n autoComplete,\n form,\n } as AriaSelectOptions<T, M>;\n\n const state = useSelectState<T, M>(ariaProps);\n const triggerRef = useObjectRef(triggerRefProp);\n // Typeahead on the closed field moves to the next option starting with the letter (M3),\n // and still skips disabled options.\n const typeaheadDelegate = useTypeaheadDelegate(state, triggerRef, {\n disabledBehavior: 'all',\n currentKey: state.selectionManager.selectionMode === 'single' ? state.selectedKey : null,\n });\n const containerRef = useRef<HTMLDivElement>(null);\n // Set by a press outside the menu and the field: the focus the menu hands back to the\n // field on closing (WAI-ARIA, right after Escape or a choice) is let go, so one press\n // outside leaves the field, as it does any other field.\n const releaseFocusRef = useRef(false);\n // Chrome's touch adjustment can split a tap between the trigger and the padding just above\n // or below it (the 8px either side of the value line): `pointerdown` reaches the trigger\n // while `pointerup` or the click lands beside it, so React Aria cancels the press or never\n // sees it, and about a third of the field ignored a finger (2026-10-17). A touch that\n // starts on the trigger but doesn't end there (its `pointerup` and its click both on it)\n // opens the menu on its click instead. The mouse never gets here: it opens on the press\n // start.\n const touchOnTriggerRef = useRef(false);\n const touchLeftTriggerRef = useRef(false);\n const onTrigger = (target: EventTarget) => Boolean(triggerRef.current?.contains(target as Node));\n const strayTouchProps: HTMLAttributes<HTMLDivElement> = {\n onPointerDownCapture: (event) => {\n touchOnTriggerRef.current = event.pointerType !== 'mouse' && onTrigger(event.target);\n touchLeftTriggerRef.current = false;\n },\n onPointerUpCapture: (event) => {\n touchLeftTriggerRef.current = !onTrigger(event.target);\n },\n onClickCapture: (event) => {\n if (!touchOnTriggerRef.current) return;\n touchOnTriggerRef.current = false;\n if (!touchLeftTriggerRef.current && onTrigger(event.target)) return;\n if (disabled || state.isOpen) return;\n releaseFocusRef.current = false;\n triggerRef.current?.focus({ preventScroll: true });\n if (!readOnly) state.open();\n },\n };\n const listBoxRef = useRef<HTMLUListElement>(null);\n const popoverRef = useRef<HTMLDivElement>(null);\n const dialogId = useId();\n const {\n labelProps,\n triggerProps,\n valueProps,\n menuProps,\n descriptionProps,\n errorMessageProps,\n isInvalid,\n validationErrors,\n validationDetails,\n } = useSelect<T, M>(\n readOnly ? ariaProps : { ...ariaProps, keyboardDelegate: typeaheadDelegate },\n state,\n triggerRef,\n );\n const isCompact = useSyncExternalStore(\n subscribeToWindow,\n () => window.matchMedia(COMPACT_WINDOW).matches,\n () => false,\n );\n const asSheet = presentation === 'sheet' || (presentation === 'auto' && isCompact);\n // The sheet's code loads on first open, or ahead of it when the field is hovered or focused.\n usePreloadBottomSheet(containerRef, asSheet);\n // A field that takes `loading` (it loads options) preloads the indicator on hover or focus.\n usePreloadLoadingIndicator(containerRef, loading !== undefined);\n // `useSelect`'s trigger props are React Aria button props; `useButton` makes them DOM props.\n const { buttonProps } = useButton(\n // A searchable field opens a dialog holding the search and the list (as PhoneField's).\n searchable\n ? {\n ...triggerProps,\n 'aria-haspopup': 'dialog',\n 'aria-controls': state.isOpen && !asSheet ? dialogId : undefined,\n }\n : triggerProps,\n triggerRef,\n );\n const { focusProps, isFocusVisible, isFocused } = useFocusRing();\n // The trigger sets its own widget attributes; an app's description ones join them.\n const fieldButtonProps = mergeProps(pickDescriptionAria(rest), buttonProps, focusProps);\n const { hoverProps, isHovered } = useHover({ isDisabled: disabled });\n\n // In the list's order, not the order they were picked, so the field reads like the list.\n const chosenOptions: SelectedOption<T>[] = [...state.collection.getKeys()]\n .filter((key) => state.selectionManager.isSelected(key))\n .map((key) => {\n const item = state.collection.getItem(key);\n return { key, textValue: item?.textValue ?? '', value: item?.value ?? null };\n });\n const chosen = chosenOptions.map((option) => option.textValue);\n const valueText =\n chosen.length > 2\n ? `${chosen.slice(0, 2).join(', ')} ${labels.more(chosen.length - 2)}`\n : chosen.join(', ');\n const hasValue = chosen.length > 0;\n const hasLabel = label !== undefined && label !== null;\n const focused = isFocused || state.isOpen;\n const resolvedError =\n typeof errorMessage === 'function'\n ? errorMessage({ isInvalid, validationErrors, validationDetails })\n : (errorMessage ?? validationErrors.join(' '));\n\n const searchLabelling = hasLabel\n ? { 'aria-labelledby': labelProps.id }\n : { 'aria-label': (rest as { 'aria-label'?: string })['aria-label'] };\n const list = searchable ? (\n <SearchList\n listProps={{\n ...(stately.items !== undefined && { items: stately.items }),\n children: stately.children,\n ...(stately.disabledKeys !== undefined && { disabledKeys: stately.disabledKeys }),\n selectionMode: stately.selectionMode ?? 'single',\n // A single choice stays chosen when pressed again, as in a non-searchable Select.\n disallowEmptySelection: stately.selectionMode !== 'multiple',\n selectedKeys: state.selectionManager.selectedKeys,\n onSelectionChange: (keys) => {\n state.selectionManager.setSelectedKeys(keys === 'all' ? [] : keys);\n if (stately.selectionMode !== 'multiple') state.close();\n },\n }}\n search={search}\n onSearchChange={setSearch}\n filter={onSearchChange ? null : filter}\n autoFocusSearch={!asSheet}\n labelling={searchLabelling}\n dialogProps={asSheet ? undefined : { id: dialogId, role: 'dialog', ...searchLabelling }}\n labels={{ search: labels.search, noResults: labels.noResults, loading: labels.loading }}\n loading={loading ?? false}\n isBlocked={cap.isBlocked}\n embedded={asSheet}\n classNames={classNames}\n />\n ) : (\n <OptionList\n embedded={asSheet}\n listBoxProps={menuProps}\n state={state}\n listBoxRef={listBoxRef}\n emptyLabel={labels.empty}\n loading={loading}\n loadingLabel={labels.loading}\n isBlocked={cap.isBlocked}\n classNames={classNames}\n />\n );\n\n return (\n <>\n <FieldShell\n variant={variant}\n density={density}\n label={label}\n labelProps={labelProps}\n required={required}\n state={fieldState({ disabled, invalid: isInvalid, focused, hovered: isHovered })}\n floated={hasValue || focused}\n open={state.isOpen}\n leadingIcon={leadingIcon}\n trailing={<DropdownArrow open={state.isOpen} />}\n errorIcon={errorIcon}\n errorIconLabel={labels.error}\n readOnly={readOnly}\n supportingText={supportingText}\n descriptionProps={descriptionProps}\n errorText={resolvedError}\n errorMessageProps={errorMessageProps}\n showError={isInvalid && Boolean(resolvedError)}\n containerRef={containerRef}\n // No handler is shared: the hover ones and the touch fallback's capture ones.\n containerProps={{ ...hoverProps, ...strayTouchProps }}\n onContainerPointerDown={(event) => {\n // A press anywhere on the field (not only its text) opens it, like Compose.\n if (disabled || triggerRef.current?.contains(event.target as Node)) return;\n event.preventDefault();\n releaseFocusRef.current = false;\n triggerRef.current?.focus();\n if (!readOnly) state.toggle();\n }}\n rootProps={{ ...rootData, 'data-focus-visible': isFocusVisible || undefined }}\n rootRef={ref}\n className={className}\n classNames={classNames}\n style={style}\n >\n {/* The native select is for forms: submitting under `name`, autofill and native\n validation. Without them it's left out, so a long list's text (e.g. country names\n from Intl, which can differ between server and browser) isn't rendered twice. */}\n {name || autoComplete || form || stately.validationBehavior === 'native' ? (\n <HiddenSelect\n state={state}\n triggerRef={triggerRef}\n label={label}\n name={name}\n isDisabled={disabled}\n {...(autoComplete && { autoComplete })}\n {...(form && { form })}\n />\n ) : null}\n <button\n {...fieldButtonProps}\n aria-describedby={renderedDescription(fieldButtonProps['aria-describedby'], [\n !supportingText && descriptionProps.id,\n !(isInvalid && resolvedError) && errorMessageProps.id,\n ])}\n onFocus={(event) => {\n fieldButtonProps.onFocus?.(event);\n if (!releaseFocusRef.current) return;\n releaseFocusRef.current = false;\n triggerRef.current?.blur();\n }}\n // Using the field again means it's wanted, whatever came before.\n onPointerDown={(event) => {\n releaseFocusRef.current = false;\n fieldButtonProps.onPointerDown?.(event);\n }}\n onKeyDown={(event) => {\n releaseFocusRef.current = false;\n fieldButtonProps.onKeyDown?.(event);\n }}\n ref={triggerRef}\n type=\"button\"\n className={cn(\n 'flex min-w-0 flex-1 cursor-pointer items-center self-stretch bg-transparent p-0 text-start outline-none',\n 'disabled:cursor-default',\n )}\n >\n <span\n {...valueProps}\n className={cn(\n 'min-w-0 flex-1 truncate text-body-large',\n hasValue\n ? 'text-[color:var(--_md-comp-text-field-input-text-color)]'\n : 'text-on-surface-variant',\n // With a label, the placeholder shows once the label floats (as TextField).\n !hasValue && hasLabel && 'opacity-0 group-data-floated/field:opacity-100',\n 'group-data-[field-state=disabled]/field:text-on-surface/38',\n classNames?.value,\n )}\n >\n {hasValue && renderValue ? (\n <>\n <span aria-hidden=\"true\">{renderValue(chosenOptions)}</span>\n <span className=\"sr-only\">{valueText}</span>\n </>\n ) : hasValue ? (\n valueText\n ) : (\n (placeholder ?? ' ')\n )}\n </span>\n </button>\n </FieldShell>\n {asSheet ? (\n <LazyBottomSheet\n // Opens at its own height (up to the window, the list scrolling inside), never half\n // the window, which left the last options off-screen on a short phone.\n skipPartiallyExpanded\n open={state.isOpen}\n onOpenChange={(open) => (open ? state.open() : state.close())}\n classNames={\n searchable || classNames?.sheet\n ? { panel: cn(searchable && SEARCH_SHEET_HEIGHT, classNames?.sheet) }\n : undefined\n }\n {...(typeof label === 'string'\n ? { 'aria-label': label }\n : { 'aria-label': (rest as { 'aria-label'?: string })['aria-label'] ?? '' })}\n >\n <div\n {...densityAttribute(density)}\n className=\"flex min-h-0 flex-1 flex-col px-[8px] pb-[16px]\"\n >\n {list}\n </div>\n </LazyBottomSheet>\n ) : (\n <OptionPopover\n state={state}\n triggerRef={containerRef}\n popoverRef={popoverRef}\n density={density}\n onPressOutside={() => {\n releaseFocusRef.current = true;\n }}\n // Room to type in a searchable menu, however narrow the field.\n minWidth={searchable ? 280 : 0}\n >\n {list}\n </OptionPopover>\n )}\n </>\n );\n}\n\nexport type { Key as SelectKey } from 'react-stately';\n"],"mappings":"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"}
1
+ {"version":3,"file":"Select.js","names":[],"sources":["../../../src/components/select/Select.tsx"],"sourcesContent":["'use client';\n\nimport {\n useId,\n useRef,\n useSyncExternalStore,\n type AriaAttributes,\n type CSSProperties,\n type HTMLAttributes,\n type ReactNode,\n type Ref,\n} from 'react';\nimport {\n HiddenSelect,\n mergeProps,\n useButton,\n useFocusRing,\n useHover,\n useObjectRef,\n useSelect,\n type AriaSelectOptions,\n} from 'react-aria';\nimport {\n useSelectState,\n type SelectionMode,\n type SelectProps as StatelySelectProps,\n} from 'react-stately/useSelectState';\nimport { useControlledState } from 'react-stately/useControlledState';\nimport type { Key } from 'react-stately';\nimport { matchesSearch } from '../../utils/search';\nimport { assertCollectionChildren } from '../../utils/assert-collection-children';\nimport { densityAttribute, type Density } from '../../tokens/density';\nimport { cn } from '../../utils/cn';\nimport { renderedDescription } from '../../utils/rendered-description';\nimport {\n ariaFlag,\n pickDescriptionAria,\n splitRootProps,\n type WrapperRootDOMProps,\n} from '../../utils/root-props';\nimport { useTypeaheadDelegate } from '../../utils/typeahead';\nimport { usePreloadLoadingIndicator } from '../loading-indicator/lazy-loading-indicator';\nimport { LazyBottomSheet, usePreloadBottomSheet } from '../sheet/lazy-bottom-sheet';\nimport type { TextFieldVariant } from '../text-field/text-field-styles';\nimport { DropdownArrow, FieldShell, fieldState, type FieldShellClassNames } from './field-shell';\nimport {\n FOCUSABLE_DISABLED_OPTIONS,\n OptionList,\n OptionPopover,\n type OptionListClassNames,\n} from './option-list';\nimport { SearchList } from './search-list';\nimport { useSelectionCap } from './selection-cap';\n\n/** How the options open: a menu under the field (M3), a bottom sheet, or a sheet on phones. */\nexport type SelectPresentation = 'menu' | 'sheet' | 'auto';\n\n/** Words the Select shows or announces; override them for other languages. */\nexport interface SelectLabels {\n /** Shown in an empty list. @default \"No options\" */\n empty: string;\n /** The loading indicator's name. @default \"Loading options\" */\n loading: string;\n /** For more chosen options than fit: `+{count}`. @default (count) => `+${count}` */\n more: (count: number) => string;\n /** With `searchable`: the search field's placeholder and name. @default \"Search\" */\n search: string;\n /** With `searchable`: shown when no option matches the search. @default \"No results\" */\n noResults: string;\n /** The error icon's name (M3: \"Error\"). @default \"Error\" */\n error: string;\n}\n\nconst DEFAULT_LABELS: SelectLabels = {\n empty: 'No options',\n loading: 'Loading options',\n more: (count) => `+${count}`,\n search: 'Search',\n noResults: 'No results',\n error: 'Error',\n};\n\n/** A chosen option, as `renderValue` receives it. */\nexport interface SelectedOption<T> {\n key: Key;\n /** Its plain text (`textValue`, or the item's text). */\n textValue: string;\n /** Its object from `items`, if the options come from `items`. */\n value: T | null;\n}\n\nexport interface SelectClassNames extends FieldShellClassNames, OptionListClassNames {\n /** The text showing the chosen option(s) or the placeholder. */\n value?: string;\n /** With `searchable`: the search field. */\n search?: string;\n /**\n * The bottom sheet the options open in (`presentation=\"sheet\"`, or `\"auto\"` on compact\n * windows): the sheet's surface, as its `classNames.panel`.\n */\n sheet?: string;\n}\n\n/** React Aria props this API names the library's way (`disabled`, `open`…), or leaves out. */\ntype RenamedProps =\n | 'isDisabled'\n | 'isRequired'\n | 'isInvalid'\n | 'isOpen'\n | 'label'\n | 'description'\n | 'validationState'\n // React Stately's deprecated single-selection API; `value` / `defaultValue` / `onChange` replace it.\n | 'selectedKey'\n | 'defaultSelectedKey'\n | 'onSelectionChange'\n | 'shouldCloseOnSelect'\n | 'allowsEmptyCollection';\n\nexport type SelectProps<T extends object, M extends SelectionMode = 'single'> = Omit<\n StatelySelectProps<T, M>,\n RenamedProps\n> &\n Pick<AriaSelectOptions<T, M>, 'id' | 'name' | 'autoComplete' | 'form'> &\n Omit<WrapperRootDOMProps, keyof StatelySelectProps<T, M>> & {\n /** The trigger button's id, for a `<label for>` or an error summary's link. */\n id?: string;\n /** The container style, as `TextField`'s: a filled surface or an outline. @default \"filled\" */\n variant?: TextFieldVariant;\n /**\n * Web density, `0` to `-3`: each step takes 4px off the field (56px at 0, 44px at -3) and\n * off its menu's options (44px at 0, 32px at -3). Left out, it takes the density of the\n * nearest `ThemeProvider` or `ThemeScope`. M3 asks for density to be the reader's choice\n * rather than a default, so prefer setting it there, from a setting.\n * @default 0\n */\n density?: Density;\n /** The field's label. Give `aria-label` instead when there's no visible label. */\n label?: ReactNode;\n /** Accessible name when there's no visible label. */\n 'aria-label'?: string;\n /**\n * Shown in the field when nothing is chosen. With a `label`, it appears once the label\n * floats (on focus), as in `TextField`.\n */\n placeholder?: string;\n /**\n * Helper text below the field. Replaced visually by the error when invalid, and shown in\n * the error colour while the field is invalid.\n */\n supportingText?: ReactNode;\n /** Marks the field invalid (in addition to `validate` and `required`). */\n invalid?: boolean;\n /** A choice is required: adds the `*` to the label and validates on submit. */\n required?: boolean;\n /**\n * Read as `invalid` when that's left out, for a form library that sets the attribute. The\n * trigger is a button, which ARIA doesn't let carry `aria-invalid`, so the state reaches\n * assistive technology as the field's error text, its description.\n */\n 'aria-invalid'?: AriaAttributes['aria-invalid'];\n /**\n * Read as `required` when that's left out, for a form library that sets the attribute: the\n * label's `*`, in the trigger's name (a button can't carry `aria-required`).\n */\n 'aria-required'?: AriaAttributes['aria-required'];\n /** Disables the field: it can't be focused or opened. @default false */\n disabled?: boolean;\n /**\n * The choice can be seen but not changed: the menu doesn't open and the keyboard doesn't\n * change it. The root gets `data-readonly`; say so in the supporting text (M3).\n * @default false\n */\n readOnly?: boolean;\n /** Decorative icon at the start, in a 48px box. */\n leadingIcon?: ReactNode;\n /**\n * The icon shown before the arrow while the field is invalid (M3 strongly recommends one).\n * Defaults to Material Symbols `error`, named by `labels.error`; `false` turns it off.\n */\n errorIcon?: ReactNode | false;\n /** Whether the menu is open (controlled); with `defaultOpen` and `onOpenChange`. */\n open?: boolean;\n /**\n * Whether choosing an option closes the menu.\n * @default true for a single choice, false with `selectionMode=\"multiple\"`\n */\n closeOnSelect?: boolean;\n /**\n * With `selectionMode=\"multiple\"`, the most options that can be chosen; the rest show as\n * disabled until one is removed.\n */\n maxSelections?: number;\n /**\n * `menu` (default) opens an M3 menu under the field on every window, as Compose's exposed\n * dropdown menu does; `sheet` always opens a bottom sheet; `auto` opens a sheet on\n * compact windows and a menu on larger ones, for long lists.\n * @default \"menu\"\n */\n presentation?: SelectPresentation;\n /**\n * A search field at the top of the menu or sheet that filters the options as you type,\n * for long lists (pair it with `presentation=\"auto\"`). The search takes focus in the menu;\n * a sheet shows the list first, so the keyboard doesn't cover it.\n * @default false\n */\n searchable?: boolean;\n /**\n * With `searchable`: whether an option matches the search. Defaults to a match anywhere in\n * its text, ignoring case and accents (\"thai\" finds \"Thailand\"). The option's `key` lets\n * it match on more than its text, e.g. a country's ISO code.\n */\n filter?: (textValue: string, search: string, key: Key) => boolean;\n /** With `searchable`: the typed text (controlled); cleared when the list closes. */\n searchValue?: string;\n /** With `searchable`: the initial typed text, when uncontrolled. @default \"\" */\n defaultSearchValue?: string;\n /**\n * With `searchable`: called as the search changes. Given it, the app filters `items` itself\n * (e.g. on a server, with `loading` meanwhile) and the built-in filter is off.\n */\n onSearchChange?: (search: string) => void;\n /**\n * What the field shows for the chosen option(s), e.g. a dialling code or a flag, instead of\n * their text. Screen readers still hear the options' text.\n */\n renderValue?: (selected: SelectedOption<T>[]) => ReactNode;\n /**\n * Shows a loading indicator in the list, e.g. while options are fetched.\n * The indicator's code loads when it first shows, and ahead of that when the field is\n * hovered or focused while this prop is set at all, so pass `loading={false}` (rather than\n * leaving it out) on a field that loads options.\n */\n loading?: boolean;\n /** Words the Select shows or announces, for other languages. */\n labels?: Partial<SelectLabels>;\n /** Classes for the root element, merged with `classNames.root`; consumer classes win. */\n className?: string;\n /** Classes for each part, by slot. */\n classNames?: SelectClassNames;\n /** Inline styles for the root element. */\n style?: CSSProperties;\n /** Ref to the root `<div>` element. Use `triggerRef` for the field's button. */\n ref?: Ref<HTMLDivElement>;\n /** Ref to the field's button. */\n triggerRef?: Ref<HTMLButtonElement>;\n };\n\nconst COMPACT_WINDOW = '(max-width: 599.98px)';\n\n/**\n * A searchable list's sheet takes the full height it may (above an on-screen keyboard too), so\n * filtering changes the list inside, never the sheet: at its own height it shrank as the\n * results did, and sank behind the keyboard (2026-11-07).\n */\nconst SEARCH_SHEET_HEIGHT =\n 'h-[calc(100dvh_-_72px_-_var(--m3-sheet-keyboard-inset,0px))] min-[641px]:h-[calc(100dvh_-_56px_-_var(--m3-sheet-keyboard-inset,0px))]';\n\n/** Options scrolled to by the keyboard stop below the sticky search (52px at most). */\nconst SEARCH_SHEET_CONTENT = 'scroll-pt-[52px]';\n\n/** A read-only Select's keyboard finds no option, so arrows and typeahead change nothing. */\nconst READ_ONLY_DELEGATE: NonNullable<AriaSelectOptions<object>['keyboardDelegate']> = {\n getFirstKey: () => null,\n getLastKey: () => null,\n getKeyBelow: () => null,\n getKeyAbove: () => null,\n getKeyForSearch: () => null,\n};\n\nfunction subscribeToWindow(onChange: () => void) {\n const query = window.matchMedia(COMPACT_WINDOW);\n query.addEventListener('change', onChange);\n return () => query.removeEventListener('change', onChange);\n}\n\n/**\n * M3 exposed dropdown menu, read-only (Compose's `ExposedDropdownMenuBox` with a\n * `PrimaryNotEditable` anchor): a text-field-styled button that opens a menu of options.\n * Single or multiple selection, sections, descriptions and icons, typeahead, a hidden\n * native `<select>` for forms, and validation as in `TextField` (React Aria `useSelect`).\n *\n * @example\n * <Select label=\"Sort by\" defaultValue=\"newest\">\n * <SelectItem key=\"newest\">Newest first</SelectItem>\n * <SelectItem key=\"price\">Price, low to high</SelectItem>\n * </Select>\n */\nexport function Select<T extends object, M extends SelectionMode = 'single'>(\n props: SelectProps<T, M>,\n) {\n const {\n variant = 'filled',\n density,\n label,\n placeholder,\n supportingText,\n invalid: invalidProp,\n required: requiredProp,\n 'aria-invalid': ariaInvalid,\n 'aria-required': ariaRequired,\n disabled = false,\n readOnly = false,\n leadingIcon,\n errorIcon,\n open,\n maxSelections,\n presentation = 'menu',\n searchable = false,\n filter = matchesSearch,\n searchValue,\n defaultSearchValue,\n onSearchChange,\n renderValue,\n loading,\n labels: labelsProp,\n className,\n classNames,\n style,\n ref,\n triggerRef: triggerRefProp,\n name,\n autoComplete,\n form,\n errorMessage,\n closeOnSelect,\n ...otherProps\n } = props;\n // Items given as elements (not `items` with a render function) must come from a client.\n if (typeof props.children !== 'function') {\n assertCollectionChildren(props.children, 'Select', 'SelectItem and SelectSection elements');\n }\n const { root: rootData, rest } = splitRootProps(otherProps, { wrapper: true });\n const invalid = invalidProp ?? ariaFlag(ariaInvalid);\n const required = requiredProp ?? ariaFlag(ariaRequired);\n const labels = { ...DEFAULT_LABELS, ...labelsProp };\n const stately = rest as Omit<StatelySelectProps<T, M>, RenamedProps>;\n const cap = useSelectionCap({\n multiple: stately.selectionMode === 'multiple',\n maxSelections,\n value: stately.value,\n defaultValue: stately.defaultValue,\n onChange: stately.onChange,\n });\n const [search, setSearch] = useControlledState(\n searchValue,\n defaultSearchValue ?? '',\n onSearchChange,\n );\n const ariaProps = {\n ...stately,\n ...cap.valueProps,\n // The search starts afresh each time the list opens.\n onOpenChange: (isOpen: boolean) => {\n if (!isOpen && search !== '') setSearch('');\n stately.onOpenChange?.(isOpen);\n },\n label,\n description: supportingText,\n errorMessage,\n isInvalid: invalid,\n isRequired: required,\n isDisabled: disabled,\n ...(closeOnSelect !== undefined && { shouldCloseOnSelect: closeOnSelect }),\n ...FOCUSABLE_DISABLED_OPTIONS,\n ...(readOnly\n ? { isOpen: false, keyboardDelegate: READ_ONLY_DELEGATE }\n : open !== undefined && { isOpen: open }),\n placeholder,\n name,\n autoComplete,\n form,\n } as AriaSelectOptions<T, M>;\n\n const state = useSelectState<T, M>(ariaProps);\n const triggerRef = useObjectRef(triggerRefProp);\n // Typeahead on the closed field moves to the next option starting with the letter (M3),\n // and still skips disabled options.\n const typeaheadDelegate = useTypeaheadDelegate(state, triggerRef, {\n disabledBehavior: 'all',\n currentKey: state.selectionManager.selectionMode === 'single' ? state.selectedKey : null,\n });\n const containerRef = useRef<HTMLDivElement>(null);\n // Set by a press outside the menu and the field: the focus the menu hands back to the\n // field on closing (WAI-ARIA, right after Escape or a choice) is let go, so one press\n // outside leaves the field, as it does any other field.\n const releaseFocusRef = useRef(false);\n // Chrome's touch adjustment can split a tap between the trigger and the padding just above\n // or below it (the 8px either side of the value line): `pointerdown` reaches the trigger\n // while `pointerup` or the click lands beside it, so React Aria cancels the press or never\n // sees it, and about a third of the field ignored a finger (2026-10-17). A touch that\n // starts on the trigger but doesn't end there (its `pointerup` and its click both on it)\n // opens the menu on its click instead. The mouse never gets here: it opens on the press\n // start.\n const touchOnTriggerRef = useRef(false);\n const touchLeftTriggerRef = useRef(false);\n const onTrigger = (target: EventTarget) => Boolean(triggerRef.current?.contains(target as Node));\n const strayTouchProps: HTMLAttributes<HTMLDivElement> = {\n onPointerDownCapture: (event) => {\n touchOnTriggerRef.current = event.pointerType !== 'mouse' && onTrigger(event.target);\n touchLeftTriggerRef.current = false;\n },\n onPointerUpCapture: (event) => {\n touchLeftTriggerRef.current = !onTrigger(event.target);\n },\n onClickCapture: (event) => {\n if (!touchOnTriggerRef.current) return;\n touchOnTriggerRef.current = false;\n if (!touchLeftTriggerRef.current && onTrigger(event.target)) return;\n if (disabled || state.isOpen) return;\n releaseFocusRef.current = false;\n triggerRef.current?.focus({ preventScroll: true });\n if (!readOnly) state.open();\n },\n };\n const listBoxRef = useRef<HTMLUListElement>(null);\n const popoverRef = useRef<HTMLDivElement>(null);\n const dialogId = useId();\n const {\n labelProps,\n triggerProps,\n valueProps,\n menuProps,\n descriptionProps,\n errorMessageProps,\n isInvalid,\n validationErrors,\n validationDetails,\n } = useSelect<T, M>(\n readOnly ? ariaProps : { ...ariaProps, keyboardDelegate: typeaheadDelegate },\n state,\n triggerRef,\n );\n const isCompact = useSyncExternalStore(\n subscribeToWindow,\n () => window.matchMedia(COMPACT_WINDOW).matches,\n () => false,\n );\n const asSheet = presentation === 'sheet' || (presentation === 'auto' && isCompact);\n // The sheet's code loads on first open, or ahead of it when the field is hovered or focused.\n usePreloadBottomSheet(containerRef, asSheet);\n // A field that takes `loading` (it loads options) preloads the indicator on hover or focus.\n usePreloadLoadingIndicator(containerRef, loading !== undefined);\n // `useSelect`'s trigger props are React Aria button props; `useButton` makes them DOM props.\n const { buttonProps } = useButton(\n // A searchable field opens a dialog holding the search and the list (as PhoneField's).\n searchable\n ? {\n ...triggerProps,\n 'aria-haspopup': 'dialog',\n 'aria-controls': state.isOpen && !asSheet ? dialogId : undefined,\n }\n : triggerProps,\n triggerRef,\n );\n const { focusProps, isFocusVisible, isFocused } = useFocusRing();\n // The trigger sets its own widget attributes; an app's description ones join them.\n const fieldButtonProps = mergeProps(pickDescriptionAria(rest), buttonProps, focusProps);\n const { hoverProps, isHovered } = useHover({ isDisabled: disabled });\n\n // In the list's order, not the order they were picked, so the field reads like the list.\n const chosenOptions: SelectedOption<T>[] = [...state.collection.getKeys()]\n .filter((key) => state.selectionManager.isSelected(key))\n .map((key) => {\n const item = state.collection.getItem(key);\n return { key, textValue: item?.textValue ?? '', value: item?.value ?? null };\n });\n const chosen = chosenOptions.map((option) => option.textValue);\n const valueText =\n chosen.length > 2\n ? `${chosen.slice(0, 2).join(', ')} ${labels.more(chosen.length - 2)}`\n : chosen.join(', ');\n const hasValue = chosen.length > 0;\n const hasLabel = label !== undefined && label !== null;\n const focused = isFocused || state.isOpen;\n const resolvedError =\n typeof errorMessage === 'function'\n ? errorMessage({ isInvalid, validationErrors, validationDetails })\n : (errorMessage ?? validationErrors.join(' '));\n\n const searchLabelling = hasLabel\n ? { 'aria-labelledby': labelProps.id }\n : { 'aria-label': (rest as { 'aria-label'?: string })['aria-label'] };\n const list = searchable ? (\n <SearchList\n listProps={{\n ...(stately.items !== undefined && { items: stately.items }),\n children: stately.children,\n ...(stately.disabledKeys !== undefined && { disabledKeys: stately.disabledKeys }),\n selectionMode: stately.selectionMode ?? 'single',\n // A single choice stays chosen when pressed again, as in a non-searchable Select.\n disallowEmptySelection: stately.selectionMode !== 'multiple',\n selectedKeys: state.selectionManager.selectedKeys,\n onSelectionChange: (keys) => {\n state.selectionManager.setSelectedKeys(keys === 'all' ? [] : keys);\n if (stately.selectionMode !== 'multiple') state.close();\n },\n }}\n search={search}\n onSearchChange={setSearch}\n filter={onSearchChange ? null : filter}\n autoFocusSearch={!asSheet}\n labelling={searchLabelling}\n dialogProps={asSheet ? undefined : { id: dialogId, role: 'dialog', ...searchLabelling }}\n labels={{ search: labels.search, noResults: labels.noResults, loading: labels.loading }}\n loading={loading ?? false}\n isBlocked={cap.isBlocked}\n embedded={asSheet}\n classNames={classNames}\n />\n ) : (\n <OptionList\n embedded={asSheet}\n listBoxProps={menuProps}\n state={state}\n listBoxRef={listBoxRef}\n emptyLabel={labels.empty}\n loading={loading}\n loadingLabel={labels.loading}\n isBlocked={cap.isBlocked}\n classNames={classNames}\n />\n );\n\n return (\n <>\n <FieldShell\n variant={variant}\n density={density}\n label={label}\n labelProps={labelProps}\n required={required}\n state={fieldState({ disabled, invalid: isInvalid, focused, hovered: isHovered })}\n floated={hasValue || focused}\n open={state.isOpen}\n leadingIcon={leadingIcon}\n trailing={<DropdownArrow open={state.isOpen} />}\n errorIcon={errorIcon}\n errorIconLabel={labels.error}\n readOnly={readOnly}\n supportingText={supportingText}\n descriptionProps={descriptionProps}\n errorText={resolvedError}\n errorMessageProps={errorMessageProps}\n showError={isInvalid && Boolean(resolvedError)}\n containerRef={containerRef}\n // No handler is shared: the hover ones and the touch fallback's capture ones.\n containerProps={{ ...hoverProps, ...strayTouchProps }}\n onContainerPointerDown={(event) => {\n // A press anywhere on the field (not only its text) opens it, like Compose.\n if (disabled || triggerRef.current?.contains(event.target as Node)) return;\n event.preventDefault();\n releaseFocusRef.current = false;\n triggerRef.current?.focus();\n if (!readOnly) state.toggle();\n }}\n rootProps={{ ...rootData, 'data-focus-visible': isFocusVisible || undefined }}\n rootRef={ref}\n className={className}\n classNames={classNames}\n style={style}\n >\n {/* The native select is for forms: submitting under `name`, autofill and native\n validation. Without them it's left out, so a long list's text (e.g. country names\n from Intl, which can differ between server and browser) isn't rendered twice. */}\n {name || autoComplete || form || stately.validationBehavior === 'native' ? (\n <HiddenSelect\n state={state}\n triggerRef={triggerRef}\n label={label}\n name={name}\n isDisabled={disabled}\n {...(autoComplete && { autoComplete })}\n {...(form && { form })}\n />\n ) : null}\n <button\n {...fieldButtonProps}\n aria-describedby={renderedDescription(fieldButtonProps['aria-describedby'], [\n !supportingText && descriptionProps.id,\n !(isInvalid && resolvedError) && errorMessageProps.id,\n ])}\n onFocus={(event) => {\n fieldButtonProps.onFocus?.(event);\n if (!releaseFocusRef.current) return;\n releaseFocusRef.current = false;\n triggerRef.current?.blur();\n }}\n // Using the field again means it's wanted, whatever came before.\n onPointerDown={(event) => {\n releaseFocusRef.current = false;\n fieldButtonProps.onPointerDown?.(event);\n }}\n onKeyDown={(event) => {\n releaseFocusRef.current = false;\n fieldButtonProps.onKeyDown?.(event);\n }}\n ref={triggerRef}\n type=\"button\"\n className={cn(\n 'flex min-w-0 flex-1 cursor-pointer items-center self-stretch bg-transparent p-0 text-start outline-none',\n 'disabled:cursor-default',\n )}\n >\n <span\n {...valueProps}\n className={cn(\n 'min-w-0 flex-1 truncate text-body-large',\n hasValue\n ? 'text-[color:var(--_md-comp-text-field-input-text-color)]'\n : 'text-on-surface-variant',\n // With a label, the placeholder shows once the label floats (as TextField).\n !hasValue && hasLabel && 'opacity-0 group-data-floated/field:opacity-100',\n 'group-data-[field-state=disabled]/field:text-on-surface/38',\n classNames?.value,\n )}\n >\n {hasValue && renderValue ? (\n <>\n <span aria-hidden=\"true\">{renderValue(chosenOptions)}</span>\n <span className=\"sr-only\">{valueText}</span>\n </>\n ) : hasValue ? (\n valueText\n ) : (\n (placeholder ?? ' ')\n )}\n </span>\n </button>\n </FieldShell>\n {asSheet ? (\n <LazyBottomSheet\n // Opens at its own height (up to the window, the list scrolling inside), never half\n // the window, which left the last options off-screen on a short phone.\n skipPartiallyExpanded\n open={state.isOpen}\n onOpenChange={(open) => (open ? state.open() : state.close())}\n classNames={\n searchable\n ? {\n panel: cn(SEARCH_SHEET_HEIGHT, classNames?.sheet),\n content: SEARCH_SHEET_CONTENT,\n }\n : classNames?.sheet\n ? { panel: classNames.sheet }\n : undefined\n }\n {...(typeof label === 'string'\n ? { 'aria-label': label }\n : { 'aria-label': (rest as { 'aria-label'?: string })['aria-label'] ?? '' })}\n >\n <div\n {...densityAttribute(density)}\n className=\"flex min-h-0 flex-1 flex-col px-[8px] pb-[16px]\"\n >\n {list}\n </div>\n </LazyBottomSheet>\n ) : (\n <OptionPopover\n state={state}\n triggerRef={containerRef}\n popoverRef={popoverRef}\n density={density}\n onPressOutside={() => {\n releaseFocusRef.current = true;\n }}\n // Room to type in a searchable menu, however narrow the field.\n minWidth={searchable ? 280 : 0}\n >\n {list}\n </OptionPopover>\n )}\n </>\n );\n}\n\nexport type { Key as SelectKey } from 'react-stately';\n"],"mappings":"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"}
@@ -89,7 +89,7 @@ function SearchList({ listProps, search, onSearchChange, filter, autoFocusSearch
89
89
  "data-variant": "standard",
90
90
  className: cn("flex min-h-0 flex-col", embedded ? "flex-1" : [menu.group(), "max-h-[inherit] overflow-hidden"]),
91
91
  children: [/* @__PURE__ */ jsx("div", {
92
- className: "flex h-[calc(52px+var(--md-sys-density,0)*4px)] shrink-0 pb-[4px]",
92
+ className: cn("flex h-[calc(52px+var(--md-sys-density,0)*4px)] shrink-0 pb-[4px]", embedded && "sticky top-0 z-[1] bg-[var(--_md-comp-sheet-bottom-docked-container-color)]"),
93
93
  children: /* @__PURE__ */ jsxs("div", {
94
94
  ...focusProps,
95
95
  "data-tokens": "md-comp-search-bar",
@@ -1 +1 @@
1
- {"version":3,"file":"search-list.js","names":[],"sources":["../../../src/components/select/search-list.tsx"],"sourcesContent":["'use client';\n\nimport {\n useCallback,\n useEffect,\n useRef,\n useState,\n type HTMLAttributes,\n type RefObject,\n} from 'react';\nimport { useAutocomplete, useFocusRing, useSearchField } from 'react-aria';\nimport { useListState, type Node } from 'react-stately';\nimport type { ListProps } from 'react-stately/useListState';\nimport { cn } from '../../utils/cn';\nimport { menuStyles } from '../menu/menu-styles';\nimport { searchBarStyles } from '../search/search-styles';\nimport { FOCUSABLE_DISABLED_OPTIONS, OptionList, type OptionListClassNames } from './option-list';\n\n/** Material Symbols `search`. */\nconst SearchIcon = () => (\n <svg viewBox=\"0 -960 960 960\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z\" />\n </svg>\n);\n\n/**\n * Keeps the items that match, and the sections that still hold one (as React Stately's\n * combobox filters), so a section with no matches disappears with its heading.\n */\nfunction filterNodes<T>(nodes: Iterable<Node<T>>, matches: (node: Node<T>) => boolean): Node<T>[] {\n const kept: Node<T>[] = [];\n for (const node of nodes) {\n if (node.type === 'section') {\n const children = filterNodes(node.childNodes, matches);\n if (children.some((child) => child.type === 'item')) {\n kept.push({ ...node, childNodes: children });\n }\n } else if (node.type !== 'item' || matches(node)) {\n kept.push(node);\n }\n }\n return kept;\n}\n\nexport interface SearchListProps<T> {\n /** The options, their selection and disabled keys, as for `useListState`. */\n listProps: ListProps<T>;\n /** The typed text. */\n search: string;\n onSearchChange: (search: string) => void;\n /** Whether an option matches the search; `null` when the app filters the options itself. */\n filter: ((textValue: string, search: string, key: Node<T>['key']) => boolean) | null;\n /** Focus the search when the list opens (in a menu; a sheet shows the list first). */\n autoFocusSearch: boolean;\n /** Names the list (the field's label): `aria-labelledby`, or `aria-label` without one. */\n labelling: { 'aria-label'?: string | undefined; 'aria-labelledby'?: string | undefined };\n /** In a menu, the panel is the dialog the field opens (`role`, `id`, its name). */\n dialogProps?: HTMLAttributes<HTMLDivElement> | undefined;\n labels: { search: string; noResults: string; loading: string };\n loading: boolean;\n isBlocked: ((key: Node<T>['key']) => boolean) | undefined;\n /** In a sheet: no menu panel of its own. */\n embedded: boolean;\n classNames?: (OptionListClassNames & { search?: string }) | undefined;\n}\n\n/*\n * A dense search field (44, 40 or 36px tall at -1 to -3) extends its hit area by up to 4px\n * below it (the gap above the list) and up to 4px above it (the panel's padding, which clips\n * anything further): 48px at -1 and -2, 44px at -3. Like dense menu rows, it can't reach 48px\n * at -3 without covering the first option; it stays well above WCAG 2.5.8's 24px.\n */\nconst denseSearchTarget = [\n 'relative',\n \"before:absolute before:inset-x-0 before:bottom-full before:h-[clamp(0px,calc(var(--md-sys-density,0)*-4px-clamp(0px,calc(var(--md-sys-density,0)*-2px),4px)),4px)] before:content-['']\",\n \"after:absolute after:inset-x-0 after:top-full after:h-[clamp(0px,calc(var(--md-sys-density,0)*-2px),4px)] after:content-['']\",\n];\n\n/**\n * A search field over an option list, wired as one editable combobox with list autocomplete\n * (React Aria `useAutocomplete`): typing filters the options, the arrow keys move through\n * them while focus stays in the search (`aria-activedescendant`), Enter chooses, and Escape\n * clears the search, then reaches the menu or sheet to close it. The search is the M3 search\n * bar's field at 48px, M3's minimum touch target, so it doesn't dominate the 44px rows.\n * Used by `Select`'s `searchable` and `PhoneField`'s country field.\n */\nexport function SearchList<T>({\n listProps,\n search,\n onSearchChange,\n filter,\n autoFocusSearch,\n labelling,\n dialogProps,\n labels,\n loading,\n isBlocked,\n embedded,\n classNames,\n}: SearchListProps<T>) {\n const matches = useCallback(\n (nodes: Iterable<Node<T>>) =>\n filter ? filterNodes(nodes, (node) => filter(node.textValue, search, node.key)) : [...nodes],\n [filter, search],\n );\n const state = useListState<T>({ ...listProps, ...FOCUSABLE_DISABLED_OPTIONS, filter: matches });\n\n const [focusedNodeId, setFocusedNodeId] = useState<string | null>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const listBoxRef = useRef<HTMLUListElement>(null);\n const {\n inputProps: autocompleteInputProps,\n collectionProps,\n collectionRef,\n } = useAutocomplete(\n { inputRef, collectionRef: listBoxRef },\n { inputValue: search, setInputValue: onSearchChange, focusedNodeId, setFocusedNodeId },\n );\n const { inputProps } = useSearchField(\n { ...autocompleteInputProps, 'aria-label': labels.search, placeholder: labels.search },\n { value: search, setValue: onSearchChange },\n inputRef,\n );\n const { focusProps, isFocusVisible } = useFocusRing({ within: true, isTextInput: true });\n\n useEffect(() => {\n if (autoFocusSearch) inputRef.current?.focus();\n }, [autoFocusSearch]);\n\n const field = searchBarStyles({ hasLeading: true });\n const menu = menuStyles({ variant: 'standard', groupPosition: 'only' });\n\n return (\n <div\n {...dialogProps}\n // The menu's component tokens (menu-tokens.ts) for its panel and options.\n data-tokens=\"md-comp-menus\"\n data-variant=\"standard\"\n className={cn(\n 'flex min-h-0 flex-col',\n embedded ? 'flex-1' : [menu.group(), 'max-h-[inherit] overflow-hidden'],\n )}\n >\n {/* 48px (4px less per density step), 4px above the list, like the panel's 4px padding\n around it. */}\n <div className=\"flex h-[calc(52px+var(--md-sys-density,0)*4px)] shrink-0 pb-[4px]\">\n <div\n {...focusProps}\n data-tokens=\"md-comp-search-bar\"\n data-focus-visible={isFocusVisible || undefined}\n // A press on the hit area around a dense field (below) puts the caret in the input.\n onPointerDown={(event) => {\n if (event.target !== event.currentTarget) return;\n event.preventDefault();\n inputRef.current?.focus();\n }}\n className={field.field({ class: cn(denseSearchTarget, classNames?.search) })}\n >\n <span data-slot=\"leading\" className={field.leading({ class: 'h-full' })}>\n <SearchIcon />\n </span>\n <input {...inputProps} ref={inputRef} data-slot=\"input\" className={field.input()} />\n </div>\n </div>\n <OptionList\n embedded\n animateFiltering\n listBoxProps={{\n ...collectionProps,\n ...labelling,\n // Escape belongs to the search and then the menu; the list must not take it to\n // clear the selection (which also kept a chosen option's menu from closing).\n escapeKeyBehavior: 'none',\n }}\n state={state}\n listBoxRef={collectionRef as RefObject<HTMLUListElement | null>}\n emptyLabel={labels.noResults}\n loading={loading}\n loadingLabel={labels.loading}\n isBlocked={isBlocked}\n classNames={classNames}\n />\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAmBA,MAAM,mBACJ,oBAAC,OAAD;CAAK,SAAQ;CAAiB,MAAK;CAAe,eAAY;CAC5D,UAAA,oBAAC,QAAD,EAAM,GAAE,8PAA+P,CAAA;AACpQ,CAAA;;;;;AAOP,SAAS,YAAe,OAA0B,SAAgD;CAChG,MAAM,OAAkB,CAAC;CACzB,KAAK,MAAM,QAAQ,OACjB,IAAI,KAAK,SAAS,WAAW;EAC3B,MAAM,WAAW,YAAY,KAAK,YAAY,OAAO;EACrD,IAAI,SAAS,MAAM,UAAU,MAAM,SAAS,MAAM,GAChD,KAAK,KAAK;GAAE,GAAG;GAAM,YAAY;EAAS,CAAC;CAE/C,OAAO,IAAI,KAAK,SAAS,UAAU,QAAQ,IAAI,GAC7C,KAAK,KAAK,IAAI;CAGlB,OAAO;AACT;AA8BA,MAAM,oBAAoB;CACxB;CACA;CACA;AACF;;;;;;;;;AAUA,SAAgB,WAAc,EAC5B,WACA,QACA,gBACA,QACA,iBACA,WACA,aACA,QACA,SACA,WACA,UACA,cACqB;CACrB,MAAM,UAAU,aACb,UACC,SAAS,YAAY,QAAQ,SAAS,OAAO,KAAK,WAAW,QAAQ,KAAK,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,GAC7F,CAAC,QAAQ,MAAM,CACjB;CACA,MAAM,QAAQ,aAAgB;EAAE,GAAG;EAAW,GAAG;EAA4B,QAAQ;CAAQ,CAAC;CAE9F,MAAM,CAAC,eAAe,oBAAoB,SAAwB,IAAI;CACtE,MAAM,WAAW,OAAyB,IAAI;CAC9C,MAAM,aAAa,OAAyB,IAAI;CAChD,MAAM,EACJ,YAAY,wBACZ,iBACA,kBACE,gBACF;EAAE;EAAU,eAAe;CAAW,GACtC;EAAE,YAAY;EAAQ,eAAe;EAAgB;EAAe;CAAiB,CACvF;CACA,MAAM,EAAE,eAAe,eACrB;EAAE,GAAG;EAAwB,cAAc,OAAO;EAAQ,aAAa,OAAO;CAAO,GACrF;EAAE,OAAO;EAAQ,UAAU;CAAe,GAC1C,QACF;CACA,MAAM,EAAE,YAAY,mBAAmB,aAAa;EAAE,QAAQ;EAAM,aAAa;CAAK,CAAC;CAEvF,gBAAgB;EACd,IAAI,iBAAiB,SAAS,SAAS,MAAM;CAC/C,GAAG,CAAC,eAAe,CAAC;CAEpB,MAAM,QAAQ,gBAAgB,EAAE,YAAY,KAAK,CAAC;CAClD,MAAM,OAAO,WAAW;EAAE,SAAS;EAAY,eAAe;CAAO,CAAC;CAEtE,OACE,qBAAC,OAAD;EACE,GAAI;EAEJ,eAAY;EACZ,gBAAa;EACb,WAAW,GACT,yBACA,WAAW,WAAW,CAAC,KAAK,MAAM,GAAG,iCAAiC,CACxE;EARF,UAAA,CAYE,oBAAC,OAAD;GAAK,WAAU;GACb,UAAA,qBAAC,OAAD;IACE,GAAI;IACJ,eAAY;IACZ,sBAAoB,kBAAkB,KAAA;IAEtC,gBAAgB,UAAU;KACxB,IAAI,MAAM,WAAW,MAAM,eAAe;KAC1C,MAAM,eAAe;KACrB,SAAS,SAAS,MAAM;IAC1B;IACA,WAAW,MAAM,MAAM,EAAE,OAAO,GAAG,mBAAmB,YAAY,MAAM,EAAE,CAAC;IAV7E,UAAA,CAYE,oBAAC,QAAD;KAAM,aAAU;KAAU,WAAW,MAAM,QAAQ,EAAE,OAAO,SAAS,CAAC;KACpE,UAAA,oBAAC,YAAD,CAAa,CAAA;IACT,CAAA,GACN,oBAAC,SAAD;KAAO,GAAI;KAAY,KAAK;KAAU,aAAU;KAAQ,WAAW,MAAM,MAAM;IAAI,CAAA,CAChF;;EACF,CAAA,GACL,oBAAC,YAAD;GACE,UAAA;GACA,kBAAA;GACA,cAAc;IACZ,GAAG;IACH,GAAG;IAGH,mBAAmB;GACrB;GACO;GACP,YAAY;GACZ,YAAY,OAAO;GACV;GACT,cAAc,OAAO;GACV;GACC;EACb,CAAA,CACE;;AAET"}
1
+ {"version":3,"file":"search-list.js","names":[],"sources":["../../../src/components/select/search-list.tsx"],"sourcesContent":["'use client';\n\nimport {\n useCallback,\n useEffect,\n useRef,\n useState,\n type HTMLAttributes,\n type RefObject,\n} from 'react';\nimport { useAutocomplete, useFocusRing, useSearchField } from 'react-aria';\nimport { useListState, type Node } from 'react-stately';\nimport type { ListProps } from 'react-stately/useListState';\nimport { cn } from '../../utils/cn';\nimport { menuStyles } from '../menu/menu-styles';\nimport { searchBarStyles } from '../search/search-styles';\nimport { FOCUSABLE_DISABLED_OPTIONS, OptionList, type OptionListClassNames } from './option-list';\n\n/** Material Symbols `search`. */\nconst SearchIcon = () => (\n <svg viewBox=\"0 -960 960 960\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z\" />\n </svg>\n);\n\n/**\n * Keeps the items that match, and the sections that still hold one (as React Stately's\n * combobox filters), so a section with no matches disappears with its heading.\n */\nfunction filterNodes<T>(nodes: Iterable<Node<T>>, matches: (node: Node<T>) => boolean): Node<T>[] {\n const kept: Node<T>[] = [];\n for (const node of nodes) {\n if (node.type === 'section') {\n const children = filterNodes(node.childNodes, matches);\n if (children.some((child) => child.type === 'item')) {\n kept.push({ ...node, childNodes: children });\n }\n } else if (node.type !== 'item' || matches(node)) {\n kept.push(node);\n }\n }\n return kept;\n}\n\nexport interface SearchListProps<T> {\n /** The options, their selection and disabled keys, as for `useListState`. */\n listProps: ListProps<T>;\n /** The typed text. */\n search: string;\n onSearchChange: (search: string) => void;\n /** Whether an option matches the search; `null` when the app filters the options itself. */\n filter: ((textValue: string, search: string, key: Node<T>['key']) => boolean) | null;\n /** Focus the search when the list opens (in a menu; a sheet shows the list first). */\n autoFocusSearch: boolean;\n /** Names the list (the field's label): `aria-labelledby`, or `aria-label` without one. */\n labelling: { 'aria-label'?: string | undefined; 'aria-labelledby'?: string | undefined };\n /** In a menu, the panel is the dialog the field opens (`role`, `id`, its name). */\n dialogProps?: HTMLAttributes<HTMLDivElement> | undefined;\n labels: { search: string; noResults: string; loading: string };\n loading: boolean;\n isBlocked: ((key: Node<T>['key']) => boolean) | undefined;\n /** In a sheet: no menu panel of its own. */\n embedded: boolean;\n classNames?: (OptionListClassNames & { search?: string }) | undefined;\n}\n\n/*\n * A dense search field (44, 40 or 36px tall at -1 to -3) extends its hit area by up to 4px\n * below it (the gap above the list) and up to 4px above it (the panel's padding, which clips\n * anything further): 48px at -1 and -2, 44px at -3. Like dense menu rows, it can't reach 48px\n * at -3 without covering the first option; it stays well above WCAG 2.5.8's 24px.\n */\nconst denseSearchTarget = [\n 'relative',\n \"before:absolute before:inset-x-0 before:bottom-full before:h-[clamp(0px,calc(var(--md-sys-density,0)*-4px-clamp(0px,calc(var(--md-sys-density,0)*-2px),4px)),4px)] before:content-['']\",\n \"after:absolute after:inset-x-0 after:top-full after:h-[clamp(0px,calc(var(--md-sys-density,0)*-2px),4px)] after:content-['']\",\n];\n\n/**\n * A search field over an option list, wired as one editable combobox with list autocomplete\n * (React Aria `useAutocomplete`): typing filters the options, the arrow keys move through\n * them while focus stays in the search (`aria-activedescendant`), Enter chooses, and Escape\n * clears the search, then reaches the menu or sheet to close it. The search is the M3 search\n * bar's field at 48px, M3's minimum touch target, so it doesn't dominate the 44px rows.\n * Used by `Select`'s `searchable` and `PhoneField`'s country field.\n */\nexport function SearchList<T>({\n listProps,\n search,\n onSearchChange,\n filter,\n autoFocusSearch,\n labelling,\n dialogProps,\n labels,\n loading,\n isBlocked,\n embedded,\n classNames,\n}: SearchListProps<T>) {\n const matches = useCallback(\n (nodes: Iterable<Node<T>>) =>\n filter ? filterNodes(nodes, (node) => filter(node.textValue, search, node.key)) : [...nodes],\n [filter, search],\n );\n const state = useListState<T>({ ...listProps, ...FOCUSABLE_DISABLED_OPTIONS, filter: matches });\n\n const [focusedNodeId, setFocusedNodeId] = useState<string | null>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const listBoxRef = useRef<HTMLUListElement>(null);\n const {\n inputProps: autocompleteInputProps,\n collectionProps,\n collectionRef,\n } = useAutocomplete(\n { inputRef, collectionRef: listBoxRef },\n { inputValue: search, setInputValue: onSearchChange, focusedNodeId, setFocusedNodeId },\n );\n const { inputProps } = useSearchField(\n { ...autocompleteInputProps, 'aria-label': labels.search, placeholder: labels.search },\n { value: search, setValue: onSearchChange },\n inputRef,\n );\n const { focusProps, isFocusVisible } = useFocusRing({ within: true, isTextInput: true });\n\n useEffect(() => {\n if (autoFocusSearch) inputRef.current?.focus();\n }, [autoFocusSearch]);\n\n const field = searchBarStyles({ hasLeading: true });\n const menu = menuStyles({ variant: 'standard', groupPosition: 'only' });\n\n return (\n <div\n {...dialogProps}\n // The menu's component tokens (menu-tokens.ts) for its panel and options.\n data-tokens=\"md-comp-menus\"\n data-variant=\"standard\"\n className={cn(\n 'flex min-h-0 flex-col',\n embedded ? 'flex-1' : [menu.group(), 'max-h-[inherit] overflow-hidden'],\n )}\n >\n {/* 48px (4px less per density step), 4px above the list, like the panel's 4px padding\n around it. In a sheet the whole content scrolls (its swipe-to-dismiss reads that\n scroller's position), so the search sticks to the top, over the options passing under. */}\n <div\n className={cn(\n 'flex h-[calc(52px+var(--md-sys-density,0)*4px)] shrink-0 pb-[4px]',\n embedded && 'sticky top-0 z-[1] bg-[var(--_md-comp-sheet-bottom-docked-container-color)]',\n )}\n >\n <div\n {...focusProps}\n data-tokens=\"md-comp-search-bar\"\n data-focus-visible={isFocusVisible || undefined}\n // A press on the hit area around a dense field (below) puts the caret in the input.\n onPointerDown={(event) => {\n if (event.target !== event.currentTarget) return;\n event.preventDefault();\n inputRef.current?.focus();\n }}\n className={field.field({ class: cn(denseSearchTarget, classNames?.search) })}\n >\n <span data-slot=\"leading\" className={field.leading({ class: 'h-full' })}>\n <SearchIcon />\n </span>\n <input {...inputProps} ref={inputRef} data-slot=\"input\" className={field.input()} />\n </div>\n </div>\n <OptionList\n embedded\n animateFiltering\n listBoxProps={{\n ...collectionProps,\n ...labelling,\n // Escape belongs to the search and then the menu; the list must not take it to\n // clear the selection (which also kept a chosen option's menu from closing).\n escapeKeyBehavior: 'none',\n }}\n state={state}\n listBoxRef={collectionRef as RefObject<HTMLUListElement | null>}\n emptyLabel={labels.noResults}\n loading={loading}\n loadingLabel={labels.loading}\n isBlocked={isBlocked}\n classNames={classNames}\n />\n </div>\n );\n}\n"],"mappings":"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"}
@@ -2,6 +2,6 @@ import { ComponentTokenSet } from "../../tokens/component.js";
2
2
  import "../../index.js";
3
3
  //#region src/components/snackbar/snackbar-tokens.d.ts
4
4
  /** The tokens of {@link Snackbar}: `--md-comp-snackbar-<part>`. */
5
- export declare const snackbarTokenSet: ComponentTokenSet<"container-color" | "container-elevation" | "icon-color" | "container-shape" | "supporting-text-color" | "action-label-text-color">;
5
+ export declare const snackbarTokenSet: ComponentTokenSet<"icon-color" | "container-color" | "container-elevation" | "container-shape" | "supporting-text-color" | "action-label-text-color">;
6
6
  //#endregion
7
7
  //# sourceMappingURL=snackbar-tokens.d.ts.map
@@ -15,6 +15,14 @@ const DEFAULT_LABELS = {
15
15
  error: "Error",
16
16
  characterCount: (count, max) => `Character count: ${count} of ${max}`
17
17
  };
18
+ /** Input types the browser draws its own format in when empty, which a resting label would cover. */
19
+ const FORMAT_SHOWING_TYPES = /* @__PURE__ */ new Set([
20
+ "date",
21
+ "time",
22
+ "datetime-local",
23
+ "month",
24
+ "week"
25
+ ]);
18
26
  const INTERACTIVE = "input, textarea, button, a, [role=\"button\"]";
19
27
  /**
20
28
  * M3 text field: filled or outlined, with a floating label, leading / trailing icons,
@@ -71,7 +79,7 @@ function TextField(props) {
71
79
  domInputRef
72
80
  ]);
73
81
  const hasLabel = label !== void 0 && label !== null;
74
- const floated = hasLabel && (isFocused || value.length > 0);
82
+ const floated = hasLabel && (isFocused || value.length > 0 || FORMAT_SHOWING_TYPES.has(ariaProps.type ?? ""));
75
83
  const state = disabled ? "disabled" : isInvalid ? isFocused ? "error-focus" : isHovered ? "error-hover" : "error" : isFocused ? "focus" : isHovered ? "hover" : "rest";
76
84
  const resolvedError = typeof errorMessage === "function" ? errorMessage({
77
85
  isInvalid,
@@ -1 +1 @@
1
- {"version":3,"file":"TextField.js","names":[],"sources":["../../../src/components/text-field/TextField.tsx"],"sourcesContent":["'use client';\n\nimport {\n useId,\n useLayoutEffect,\n type AriaAttributes,\n type CSSProperties,\n type MouseEvent,\n type PointerEvent,\n type ReactNode,\n type Ref,\n} from 'react';\nimport {\n mergeProps,\n useFocusRing,\n useHover,\n useObjectRef,\n useTextField,\n type AriaTextFieldOptions,\n type AriaTextFieldProps,\n} from 'react-aria';\nimport { useControlledState } from 'react-stately/useControlledState';\nimport {\n CharacterCounter,\n ErrorText,\n FieldErrorIcon,\n FieldLabel,\n SupportingText,\n} from '../../primitives/Field';\nimport { densityAttribute, type Density } from '../../tokens/density';\nimport { cn } from '../../utils/cn';\nimport { renderedDescription } from '../../utils/rendered-description';\nimport { mergeDataTokens } from '../../utils/merge-data-tokens';\nimport { pickWidgetAria, splitRootProps, type WrapperRootDOMProps } from '../../utils/root-props';\nimport { textFieldStyles, type TextFieldVariant } from './text-field-styles';\n\nexport interface TextFieldClassNames {\n /** The root element, merged with `className`. */\n root?: string;\n /** The filled or outlined container around the label, input and icons. */\n container?: string;\n /** The floating label. */\n label?: string;\n /** The `<input>` or `<textarea>`. */\n input?: string;\n /** The leading icon's 48px box. */\n leadingIcon?: string;\n /** The trailing icon's 48px box. */\n trailingIcon?: string;\n /** The prefix text before the input. */\n prefix?: string;\n /** The suffix text after the input. */\n suffix?: string;\n /** The helper text below the field. */\n supportingText?: string;\n /** The error message below the field. */\n errorText?: string;\n /** The character counter below the field. */\n counter?: string;\n /** The built-in error icon. */\n errorIcon?: string;\n /** The outlined field's border (a `<fieldset>`, notched for the floated label). */\n outline?: string;\n /** The filled field's active indicator, the line along the container's bottom. */\n activeIndicator?: string;\n}\n\n/** Words the text field announces; override them for other languages. */\nexport interface TextFieldLabels {\n /** The error icon's name (M3: \"Error\"). @default \"Error\" */\n error: string;\n /**\n * What screen readers hear for the character counter (M3 calls it the \"character count\").\n * @default (count, max) => `Character count: ${count} of ${max}`\n */\n characterCount: (count: number, max: number) => string;\n}\n\nconst DEFAULT_LABELS: TextFieldLabels = {\n error: 'Error',\n characterCount: (count, max) => `Character count: ${count} of ${max}`,\n};\n\ntype FieldState = 'disabled' | 'error-focus' | 'error-hover' | 'error' | 'focus' | 'hover' | 'rest';\n\n/** Props React Aria names with an `is` prefix; we use the HTML-style names instead. */\ntype RenamedAriaProps =\n | 'isDisabled'\n | 'isReadOnly'\n | 'isRequired'\n | 'isInvalid'\n | 'label'\n | 'description'\n | 'inputElementType'\n | 'value'\n | 'defaultValue'\n | 'onChange'\n // Left out: the deprecated `validationState` (`invalid` replaces it) and React Aria's\n // `excludeFromTabOrder` (`tabIndex={-1}` covers the rare case).\n | 'validationState'\n | 'excludeFromTabOrder';\n\ninterface TextFieldOwnProps\n extends\n Omit<AriaTextFieldProps<HTMLInputElement>, RenamedAriaProps>,\n Omit<WrapperRootDOMProps, keyof AriaTextFieldProps<HTMLInputElement>>,\n // The attributes that describe the input go to it, with React Aria's `aria-controls`,\n // `aria-haspopup`, `aria-autocomplete` and `aria-activedescendant`: a field that opens its\n // own suggestions can say so.\n Pick<\n AriaAttributes,\n | 'aria-expanded'\n | 'aria-required'\n | 'aria-invalid'\n | 'aria-readonly'\n | 'aria-multiline'\n | 'aria-placeholder'\n > {\n /** The `<input>`'s (or `<textarea>`'s) id, for a `<label for>` or an error summary's link. */\n id?: string;\n /** The container style: a filled surface or an outline. @default \"filled\" */\n variant?: TextFieldVariant;\n /**\n * Web density, `0` to `-3`: each step takes 4px off the container (56px at 0, 44px at -3).\n * Left out, the field takes the density of the nearest `ThemeProvider` or `ThemeScope`. M3\n * asks for density to be the reader's choice rather than a default, so prefer setting it\n * there, from a setting.\n * @default 0\n */\n density?: Density;\n /** The text, when controlled. */\n value?: string;\n /** The initial text, when uncontrolled. @default \"\" */\n defaultValue?: string;\n /** Called with the new text on every change. */\n onChange?: (value: string) => void;\n /**\n * Helper text below the field. Replaced visually by the error message when invalid, and\n * shown in the error colour while the field is invalid.\n */\n supportingText?: ReactNode;\n /**\n * Shows the supporting text only while the field has focus (M3 allows it \"persistently\n * visible or visible only on focus\"). Its space is kept, and screen readers always get it.\n * @default false\n */\n supportingTextOnFocus?: boolean;\n /** Marks the field invalid (in addition to `validate` and native constraints). */\n invalid?: boolean;\n /** A value is required: adds the `*` to the label and native `required` validation. */\n required?: boolean;\n /** Disables the field: it can't be focused or edited. @default false */\n disabled?: boolean;\n /**\n * The value can be read and copied but not changed. M3 styles it as a regular field and\n * asks for it to be \"clearly labeled as read-only\", e.g. in the supporting text. The root\n * gets `data-readonly`.\n * @default false\n */\n readOnly?: boolean;\n /** Decorative icon at the start, in a 48px box. */\n leadingIcon?: ReactNode;\n /**\n * Icon or icon button at the end (e.g. clear, visibility toggle), in a 48px box. It takes\n * the place of the error icon.\n */\n trailingIcon?: ReactNode;\n /**\n * The icon shown at the end while the field is invalid and has no `trailingIcon` (M3\n * strongly recommends one). Defaults to Material Symbols `error`, named by `labels.error`;\n * `false` turns it off.\n */\n errorIcon?: ReactNode | false;\n /** Text before the input, e.g. \"$\". Shown once the label floats. */\n prefix?: ReactNode;\n /**\n * What screen readers hear for the prefix, e.g. \"US dollars\" for \"$\". The prefix is in the\n * input's description either way; without this, its visible text is read.\n */\n prefixLabel?: string;\n /** Text after the input, e.g. \"kg\". Shown once the label floats. */\n suffix?: ReactNode;\n /** What screen readers hear for the suffix, e.g. \"kilograms\" for \"kg\". */\n suffixLabel?: string;\n /**\n * Renders a `<textarea>`. By default it's an M3 text area: a fixed height of `rows` lines\n * that scrolls vertically, which M3 recommends over growing fields on the web.\n * @default false\n */\n multiline?: boolean;\n /**\n * With `multiline`: grow with the content from `rows` up to `maxRows` (M3's multi-line\n * field) instead of keeping a fixed height.\n * @default false\n */\n autoGrow?: boolean;\n /** Visible lines of a multiline field. @default 4, or 1 with autoGrow */\n rows?: number;\n /** With `autoGrow`: lines after which the field scrolls instead of growing. */\n maxRows?: number;\n /** Shows `count/maxLength` below the field. Defaults to `true` when `maxLength` is set. */\n counter?: boolean;\n /** Words the field announces, for other languages. */\n labels?: Partial<TextFieldLabels>;\n /** Classes for the root element, merged with `classNames.root`; consumer classes win. */\n className?: string;\n /** Classes for each part, by slot. */\n classNames?: TextFieldClassNames;\n /** Inline styles for the root element. */\n style?: CSSProperties;\n /** The root element. Use `inputRef` for the input itself. */\n ref?: Ref<HTMLDivElement>;\n /** The `<input>` (or `<textarea>` when multiline), e.g. for form libraries. */\n inputRef?: Ref<HTMLInputElement | HTMLTextAreaElement>;\n}\n\n/** A visible label, or an accessible name for fields without one. */\ntype TextFieldLabel =\n | {\n /** The visible label, which floats above the input once it has focus or a value. */\n label: ReactNode;\n }\n | {\n label?: undefined;\n /** Accessible name when there's no visible label. */\n 'aria-label': string;\n }\n | {\n label?: undefined;\n /** The id of the element that names the field, when there's no visible label. */\n 'aria-labelledby': string;\n };\n\nexport type TextFieldProps = TextFieldOwnProps & TextFieldLabel;\n\nconst INTERACTIVE = 'input, textarea, button, a, [role=\"button\"]';\n\n/**\n * M3 text field: filled or outlined, with a floating label, leading / trailing icons,\n * prefix and suffix, supporting text, error message and icon, character counter, and a\n * multiline form (a fixed-height text area, or a growing field with `autoGrow`). Built on React Aria `useTextField`, so labelling,\n * descriptions, `validate` and native constraints work as in React Aria.\n *\n * `className` and `style` go on the root; `inputRef` reaches the input.\n *\n * @example\n * <TextField label=\"Email\" type=\"email\" required supportingText=\"We never share it.\" />\n * <TextField variant=\"outlined\" label=\"Price\" prefix=\"$\" inputMode=\"decimal\" />\n */\nexport function TextField(props: TextFieldProps) {\n const {\n variant = 'filled',\n density,\n label,\n value: valueProp,\n defaultValue,\n onChange,\n supportingText,\n supportingTextOnFocus = false,\n errorMessage,\n invalid,\n required,\n disabled = false,\n readOnly,\n leadingIcon,\n trailingIcon,\n errorIcon,\n prefix,\n prefixLabel,\n suffix,\n suffixLabel,\n multiline = false,\n autoGrow = false,\n rows = autoGrow ? 1 : 4,\n maxRows,\n counter,\n labels: labelsProp,\n className,\n classNames,\n style,\n ref,\n inputRef,\n ...otherProps\n } = props;\n\n // `data-*` attributes and the root's DOM attributes describe the component, so they go on the\n // root with `className`; everything else (the input's ARIA, attributes and events) goes to\n // the input via React Aria.\n const { root: rootData, rest: ariaProps } = splitRootProps(otherProps, { wrapper: true });\n const labels = { ...DEFAULT_LABELS, ...labelsProp };\n const prefixId = useId();\n const suffixId = useId();\n const counterId = useId();\n\n const [value, setValue] = useControlledState(valueProp, defaultValue ?? '', onChange);\n const domInputRef = useObjectRef(inputRef as Ref<HTMLInputElement>);\n const {\n labelProps,\n inputProps,\n descriptionProps,\n errorMessageProps,\n isInvalid,\n validationErrors,\n validationDetails,\n } = useTextField(\n {\n ...ariaProps,\n label,\n description: supportingText,\n errorMessage,\n value,\n onChange: setValue,\n isInvalid: invalid,\n isRequired: required,\n isDisabled: disabled,\n isReadOnly: readOnly,\n inputElementType: multiline ? 'textarea' : 'input',\n } as AriaTextFieldOptions<'input' | 'textarea'>,\n domInputRef,\n );\n const { focusProps, isFocused } = useFocusRing({ isTextInput: true });\n const { hoverProps, isHovered } = useHover({ isDisabled: disabled });\n\n const growing = multiline && autoGrow;\n // Grow a multiline field with its content, up to maxRows.\n useLayoutEffect(() => {\n const element = domInputRef.current as HTMLTextAreaElement | null;\n if (!growing || !element) return;\n element.style.height = 'auto';\n const lineHeight = Number.parseFloat(getComputedStyle(element).lineHeight) || 24;\n const max = maxRows ? lineHeight * maxRows : Number.POSITIVE_INFINITY;\n element.style.height = `${Math.min(element.scrollHeight, max)}px`;\n element.style.overflowY = element.scrollHeight > max ? 'auto' : 'hidden';\n }, [value, growing, maxRows, domInputRef]);\n\n const hasLabel = label !== undefined && label !== null;\n const floated = hasLabel && (isFocused || value.length > 0);\n const state: FieldState = disabled\n ? 'disabled'\n : isInvalid\n ? isFocused\n ? 'error-focus'\n : isHovered\n ? 'error-hover'\n : 'error'\n : isFocused\n ? 'focus'\n : isHovered\n ? 'hover'\n : 'rest';\n\n const resolvedError =\n typeof errorMessage === 'function'\n ? errorMessage({ isInvalid, validationErrors, validationDetails })\n : (errorMessage ?? validationErrors.join(' '));\n const showError = isInvalid && Boolean(resolvedError);\n const maxLength = ariaProps.maxLength;\n const showCounter = maxLength !== undefined && counter !== false;\n const showErrorIcon = isInvalid && !trailingIcon && errorIcon !== false;\n\n const styles = textFieldStyles({\n variant,\n hasLabel,\n hasLeadingIcon: Boolean(leadingIcon),\n hasTrailingIcon: Boolean(trailingIcon) || showErrorIcon,\n multiline,\n fixedHeight: multiline && !autoGrow,\n supportingTextOnFocus,\n });\n // The affixes and the counter join React Aria's description (supporting text, then error).\n const describedBy =\n [\n prefix ? prefixId : null,\n suffix ? suffixId : null,\n renderedDescription(inputProps['aria-describedby'], [\n !supportingText && descriptionProps.id,\n !showError && errorMessageProps.id,\n ]),\n showCounter ? counterId : null,\n ]\n .filter(Boolean)\n .join(' ') || undefined;\n\n // Pressing the container (not an icon button) focuses the input, like Compose.\n const focusInput = (event: PointerEvent<HTMLDivElement>) => {\n if ((event.target as Element).closest(INTERACTIVE) || disabled) return;\n event.preventDefault();\n // The field is where the press landed: the page never scrolls to it (2026-10-15).\n domInputRef.current?.focus({ preventScroll: true });\n };\n // Chrome's touch adjustment can send a tap's pointer events to the input while the mouse\n // events it emulates, and the click, land on the padding beside it, so the input never\n // takes focus (2026-10-17). A click on the field that leaves the input unfocused focuses it.\n // The mouse never needs it: its press on the padding already focused the input.\n const focusInputOnClick = (event: MouseEvent<HTMLDivElement>) => {\n const target = event.target as Element;\n if (\n (event.nativeEvent as globalThis.PointerEvent).pointerType === 'mouse' ||\n !event.currentTarget.contains(target) ||\n target.closest(INTERACTIVE) ||\n disabled ||\n document.activeElement === domInputRef.current\n ) {\n return;\n }\n domInputRef.current?.focus({ preventScroll: true });\n };\n\n // Part of the accessible name, as M3 asks (\"The accessibility label must include the\n // asterisk\"); the copy in the outline's notch is hidden with the fieldset.\n const requiredMark = required ? ' *' : null;\n const Input = multiline ? 'textarea' : 'input';\n\n return (\n <div\n {...rootData}\n ref={ref}\n style={style}\n className={styles.root({ class: cn(classNames?.root, className) })}\n data-tokens={mergeDataTokens('md-comp-text-field', rootData)}\n data-variant={variant}\n {...densityAttribute(density)}\n data-field-state={state}\n data-focused={isFocused || undefined}\n data-hovered={(isHovered && !disabled) || undefined}\n data-invalid={isInvalid || undefined}\n data-disabled={disabled || undefined}\n data-readonly={readOnly || undefined}\n data-floated={floated || undefined}\n >\n <div\n {...hoverProps}\n onPointerDown={focusInput}\n onClick={focusInputOnClick}\n data-slot=\"container\"\n className={styles.container({ class: classNames?.container })}\n >\n {variant === 'outlined' && (\n <fieldset\n aria-hidden=\"true\"\n data-slot=\"outline\"\n className={styles.outline({ class: classNames?.outline })}\n >\n {hasLabel && (\n <legend className={styles.notch()}>\n <span className={styles.notchClip()}>\n <span className={styles.notchText()}>\n {label}\n {requiredMark}\n </span>\n </span>\n </legend>\n )}\n </fieldset>\n )}\n {leadingIcon && (\n <span\n aria-hidden=\"true\"\n data-slot=\"leading-icon\"\n className={styles.icon({ class: cn(styles.leadingIcon(), classNames?.leadingIcon) })}\n >\n {leadingIcon}\n </span>\n )}\n <div className={styles.field()}>\n {prefix && (\n <span\n id={prefixId}\n className={styles.affix({ class: cn(styles.prefix(), classNames?.prefix) })}\n >\n <Affix text={prefix} label={prefixLabel} />\n </span>\n )}\n <Input\n {...(mergeProps(inputProps, pickWidgetAria(ariaProps), focusProps) as object)}\n aria-describedby={describedBy}\n ref={domInputRef as Ref<never>}\n data-slot=\"input\"\n rows={multiline ? rows : undefined}\n className={styles.input({ class: classNames?.input })}\n />\n {suffix && (\n <span\n id={suffixId}\n className={styles.affix({ class: cn(styles.suffix(), classNames?.suffix) })}\n >\n <Affix text={suffix} label={suffixLabel} />\n </span>\n )}\n </div>\n {trailingIcon ? (\n <span\n data-slot=\"trailing-icon\"\n className={styles.icon({\n class: cn(styles.trailingIcon(), classNames?.trailingIcon),\n })}\n >\n {trailingIcon}\n </span>\n ) : showErrorIcon ? (\n <span className={styles.icon({ class: styles.trailingIcon() })}>\n <FieldErrorIcon\n label={labels.error}\n icon={errorIcon}\n className={styles.errorIcon({ class: classNames?.errorIcon })}\n />\n </span>\n ) : null}\n {hasLabel && (\n <FieldLabel\n {...labelProps}\n data-slot=\"label\"\n className={styles.label({ class: classNames?.label })}\n >\n {label}\n {requiredMark}\n </FieldLabel>\n )}\n <span\n aria-hidden=\"true\"\n data-slot=\"active-indicator\"\n className={styles.indicator({ class: classNames?.activeIndicator })}\n />\n </div>\n {(supportingText || showError || showCounter) && (\n <div className={styles.supporting()}>\n {supportingText && (\n <SupportingText\n {...descriptionProps}\n data-slot=\"supporting-text\"\n className={cn(\n styles.supportingText({\n class: cn(styles.supportingTextColor(), classNames?.supportingText),\n }),\n showError && 'sr-only',\n )}\n >\n {supportingText}\n </SupportingText>\n )}\n {showError && (\n <ErrorText\n {...errorMessageProps}\n className={styles.supportingText({\n supportingTextOnFocus: false,\n class: classNames?.errorText,\n })}\n >\n {resolvedError}\n </ErrorText>\n )}\n {showCounter && (\n <CharacterCounter\n id={counterId}\n count={value.length}\n max={maxLength}\n label={labels.characterCount(value.length, maxLength)}\n className={styles.counter({ class: classNames?.counter })}\n />\n )}\n </div>\n )}\n </div>\n );\n}\n\n/** An affix's visible text, read as its `label` when there is one. */\nfunction Affix({ text, label }: { text: ReactNode; label: string | undefined }) {\n if (label === undefined) return text;\n return (\n <>\n <span aria-hidden=\"true\">{text}</span>\n <span className=\"sr-only\">{label}</span>\n </>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TextField.js","names":[],"sources":["../../../src/components/text-field/TextField.tsx"],"sourcesContent":["'use client';\n\nimport {\n useId,\n useLayoutEffect,\n type AriaAttributes,\n type CSSProperties,\n type MouseEvent,\n type PointerEvent,\n type ReactNode,\n type Ref,\n} from 'react';\nimport {\n mergeProps,\n useFocusRing,\n useHover,\n useObjectRef,\n useTextField,\n type AriaTextFieldOptions,\n type AriaTextFieldProps,\n} from 'react-aria';\nimport { useControlledState } from 'react-stately/useControlledState';\nimport {\n CharacterCounter,\n ErrorText,\n FieldErrorIcon,\n FieldLabel,\n SupportingText,\n} from '../../primitives/Field';\nimport { densityAttribute, type Density } from '../../tokens/density';\nimport { cn } from '../../utils/cn';\nimport { renderedDescription } from '../../utils/rendered-description';\nimport { mergeDataTokens } from '../../utils/merge-data-tokens';\nimport { pickWidgetAria, splitRootProps, type WrapperRootDOMProps } from '../../utils/root-props';\nimport { textFieldStyles, type TextFieldVariant } from './text-field-styles';\n\nexport interface TextFieldClassNames {\n /** The root element, merged with `className`. */\n root?: string;\n /** The filled or outlined container around the label, input and icons. */\n container?: string;\n /** The floating label. */\n label?: string;\n /** The `<input>` or `<textarea>`. */\n input?: string;\n /** The leading icon's 48px box. */\n leadingIcon?: string;\n /** The trailing icon's 48px box. */\n trailingIcon?: string;\n /** The prefix text before the input. */\n prefix?: string;\n /** The suffix text after the input. */\n suffix?: string;\n /** The helper text below the field. */\n supportingText?: string;\n /** The error message below the field. */\n errorText?: string;\n /** The character counter below the field. */\n counter?: string;\n /** The built-in error icon. */\n errorIcon?: string;\n /** The outlined field's border (a `<fieldset>`, notched for the floated label). */\n outline?: string;\n /** The filled field's active indicator, the line along the container's bottom. */\n activeIndicator?: string;\n}\n\n/** Words the text field announces; override them for other languages. */\nexport interface TextFieldLabels {\n /** The error icon's name (M3: \"Error\"). @default \"Error\" */\n error: string;\n /**\n * What screen readers hear for the character counter (M3 calls it the \"character count\").\n * @default (count, max) => `Character count: ${count} of ${max}`\n */\n characterCount: (count: number, max: number) => string;\n}\n\nconst DEFAULT_LABELS: TextFieldLabels = {\n error: 'Error',\n characterCount: (count, max) => `Character count: ${count} of ${max}`,\n};\n\n/** Input types the browser draws its own format in when empty, which a resting label would cover. */\nconst FORMAT_SHOWING_TYPES = new Set(['date', 'time', 'datetime-local', 'month', 'week']);\n\ntype FieldState = 'disabled' | 'error-focus' | 'error-hover' | 'error' | 'focus' | 'hover' | 'rest';\n\n/** Props React Aria names with an `is` prefix; we use the HTML-style names instead. */\ntype RenamedAriaProps =\n | 'isDisabled'\n | 'isReadOnly'\n | 'isRequired'\n | 'isInvalid'\n | 'label'\n | 'description'\n | 'inputElementType'\n | 'value'\n | 'defaultValue'\n | 'onChange'\n // Left out: the deprecated `validationState` (`invalid` replaces it) and React Aria's\n // `excludeFromTabOrder` (`tabIndex={-1}` covers the rare case).\n | 'validationState'\n | 'excludeFromTabOrder';\n\ninterface TextFieldOwnProps\n extends\n Omit<AriaTextFieldProps<HTMLInputElement>, RenamedAriaProps>,\n Omit<WrapperRootDOMProps, keyof AriaTextFieldProps<HTMLInputElement>>,\n // The attributes that describe the input go to it, with React Aria's `aria-controls`,\n // `aria-haspopup`, `aria-autocomplete` and `aria-activedescendant`: a field that opens its\n // own suggestions can say so.\n Pick<\n AriaAttributes,\n | 'aria-expanded'\n | 'aria-required'\n | 'aria-invalid'\n | 'aria-readonly'\n | 'aria-multiline'\n | 'aria-placeholder'\n > {\n /** The `<input>`'s (or `<textarea>`'s) id, for a `<label for>` or an error summary's link. */\n id?: string;\n /** The container style: a filled surface or an outline. @default \"filled\" */\n variant?: TextFieldVariant;\n /**\n * Web density, `0` to `-3`: each step takes 4px off the container (56px at 0, 44px at -3).\n * Left out, the field takes the density of the nearest `ThemeProvider` or `ThemeScope`. M3\n * asks for density to be the reader's choice rather than a default, so prefer setting it\n * there, from a setting.\n * @default 0\n */\n density?: Density;\n /** The text, when controlled. */\n value?: string;\n /** The initial text, when uncontrolled. @default \"\" */\n defaultValue?: string;\n /** Called with the new text on every change. */\n onChange?: (value: string) => void;\n /**\n * Helper text below the field. Replaced visually by the error message when invalid, and\n * shown in the error colour while the field is invalid.\n */\n supportingText?: ReactNode;\n /**\n * Shows the supporting text only while the field has focus (M3 allows it \"persistently\n * visible or visible only on focus\"). Its space is kept, and screen readers always get it.\n * @default false\n */\n supportingTextOnFocus?: boolean;\n /** Marks the field invalid (in addition to `validate` and native constraints). */\n invalid?: boolean;\n /** A value is required: adds the `*` to the label and native `required` validation. */\n required?: boolean;\n /** Disables the field: it can't be focused or edited. @default false */\n disabled?: boolean;\n /**\n * The value can be read and copied but not changed. M3 styles it as a regular field and\n * asks for it to be \"clearly labeled as read-only\", e.g. in the supporting text. The root\n * gets `data-readonly`.\n * @default false\n */\n readOnly?: boolean;\n /** Decorative icon at the start, in a 48px box. */\n leadingIcon?: ReactNode;\n /**\n * Icon or icon button at the end (e.g. clear, visibility toggle), in a 48px box. It takes\n * the place of the error icon.\n */\n trailingIcon?: ReactNode;\n /**\n * The icon shown at the end while the field is invalid and has no `trailingIcon` (M3\n * strongly recommends one). Defaults to Material Symbols `error`, named by `labels.error`;\n * `false` turns it off.\n */\n errorIcon?: ReactNode | false;\n /** Text before the input, e.g. \"$\". Shown once the label floats. */\n prefix?: ReactNode;\n /**\n * What screen readers hear for the prefix, e.g. \"US dollars\" for \"$\". The prefix is in the\n * input's description either way; without this, its visible text is read.\n */\n prefixLabel?: string;\n /** Text after the input, e.g. \"kg\". Shown once the label floats. */\n suffix?: ReactNode;\n /** What screen readers hear for the suffix, e.g. \"kilograms\" for \"kg\". */\n suffixLabel?: string;\n /**\n * Renders a `<textarea>`. By default it's an M3 text area: a fixed height of `rows` lines\n * that scrolls vertically, which M3 recommends over growing fields on the web.\n * @default false\n */\n multiline?: boolean;\n /**\n * With `multiline`: grow with the content from `rows` up to `maxRows` (M3's multi-line\n * field) instead of keeping a fixed height.\n * @default false\n */\n autoGrow?: boolean;\n /** Visible lines of a multiline field. @default 4, or 1 with autoGrow */\n rows?: number;\n /** With `autoGrow`: lines after which the field scrolls instead of growing. */\n maxRows?: number;\n /** Shows `count/maxLength` below the field. Defaults to `true` when `maxLength` is set. */\n counter?: boolean;\n /** Words the field announces, for other languages. */\n labels?: Partial<TextFieldLabels>;\n /** Classes for the root element, merged with `classNames.root`; consumer classes win. */\n className?: string;\n /** Classes for each part, by slot. */\n classNames?: TextFieldClassNames;\n /** Inline styles for the root element. */\n style?: CSSProperties;\n /** The root element. Use `inputRef` for the input itself. */\n ref?: Ref<HTMLDivElement>;\n /** The `<input>` (or `<textarea>` when multiline), e.g. for form libraries. */\n inputRef?: Ref<HTMLInputElement | HTMLTextAreaElement>;\n}\n\n/** A visible label, or an accessible name for fields without one. */\ntype TextFieldLabel =\n | {\n /** The visible label, which floats above the input once it has focus or a value. */\n label: ReactNode;\n }\n | {\n label?: undefined;\n /** Accessible name when there's no visible label. */\n 'aria-label': string;\n }\n | {\n label?: undefined;\n /** The id of the element that names the field, when there's no visible label. */\n 'aria-labelledby': string;\n };\n\nexport type TextFieldProps = TextFieldOwnProps & TextFieldLabel;\n\nconst INTERACTIVE = 'input, textarea, button, a, [role=\"button\"]';\n\n/**\n * M3 text field: filled or outlined, with a floating label, leading / trailing icons,\n * prefix and suffix, supporting text, error message and icon, character counter, and a\n * multiline form (a fixed-height text area, or a growing field with `autoGrow`). Built on React Aria `useTextField`, so labelling,\n * descriptions, `validate` and native constraints work as in React Aria.\n *\n * `className` and `style` go on the root; `inputRef` reaches the input.\n *\n * @example\n * <TextField label=\"Email\" type=\"email\" required supportingText=\"We never share it.\" />\n * <TextField variant=\"outlined\" label=\"Price\" prefix=\"$\" inputMode=\"decimal\" />\n */\nexport function TextField(props: TextFieldProps) {\n const {\n variant = 'filled',\n density,\n label,\n value: valueProp,\n defaultValue,\n onChange,\n supportingText,\n supportingTextOnFocus = false,\n errorMessage,\n invalid,\n required,\n disabled = false,\n readOnly,\n leadingIcon,\n trailingIcon,\n errorIcon,\n prefix,\n prefixLabel,\n suffix,\n suffixLabel,\n multiline = false,\n autoGrow = false,\n rows = autoGrow ? 1 : 4,\n maxRows,\n counter,\n labels: labelsProp,\n className,\n classNames,\n style,\n ref,\n inputRef,\n ...otherProps\n } = props;\n\n // `data-*` attributes and the root's DOM attributes describe the component, so they go on the\n // root with `className`; everything else (the input's ARIA, attributes and events) goes to\n // the input via React Aria.\n const { root: rootData, rest: ariaProps } = splitRootProps(otherProps, { wrapper: true });\n const labels = { ...DEFAULT_LABELS, ...labelsProp };\n const prefixId = useId();\n const suffixId = useId();\n const counterId = useId();\n\n const [value, setValue] = useControlledState(valueProp, defaultValue ?? '', onChange);\n const domInputRef = useObjectRef(inputRef as Ref<HTMLInputElement>);\n const {\n labelProps,\n inputProps,\n descriptionProps,\n errorMessageProps,\n isInvalid,\n validationErrors,\n validationDetails,\n } = useTextField(\n {\n ...ariaProps,\n label,\n description: supportingText,\n errorMessage,\n value,\n onChange: setValue,\n isInvalid: invalid,\n isRequired: required,\n isDisabled: disabled,\n isReadOnly: readOnly,\n inputElementType: multiline ? 'textarea' : 'input',\n } as AriaTextFieldOptions<'input' | 'textarea'>,\n domInputRef,\n );\n const { focusProps, isFocused } = useFocusRing({ isTextInput: true });\n const { hoverProps, isHovered } = useHover({ isDisabled: disabled });\n\n const growing = multiline && autoGrow;\n // Grow a multiline field with its content, up to maxRows.\n useLayoutEffect(() => {\n const element = domInputRef.current as HTMLTextAreaElement | null;\n if (!growing || !element) return;\n element.style.height = 'auto';\n const lineHeight = Number.parseFloat(getComputedStyle(element).lineHeight) || 24;\n const max = maxRows ? lineHeight * maxRows : Number.POSITIVE_INFINITY;\n element.style.height = `${Math.min(element.scrollHeight, max)}px`;\n element.style.overflowY = element.scrollHeight > max ? 'auto' : 'hidden';\n }, [value, growing, maxRows, domInputRef]);\n\n const hasLabel = label !== undefined && label !== null;\n const floated =\n hasLabel && (isFocused || value.length > 0 || FORMAT_SHOWING_TYPES.has(ariaProps.type ?? ''));\n const state: FieldState = disabled\n ? 'disabled'\n : isInvalid\n ? isFocused\n ? 'error-focus'\n : isHovered\n ? 'error-hover'\n : 'error'\n : isFocused\n ? 'focus'\n : isHovered\n ? 'hover'\n : 'rest';\n\n const resolvedError =\n typeof errorMessage === 'function'\n ? errorMessage({ isInvalid, validationErrors, validationDetails })\n : (errorMessage ?? validationErrors.join(' '));\n const showError = isInvalid && Boolean(resolvedError);\n const maxLength = ariaProps.maxLength;\n const showCounter = maxLength !== undefined && counter !== false;\n const showErrorIcon = isInvalid && !trailingIcon && errorIcon !== false;\n\n const styles = textFieldStyles({\n variant,\n hasLabel,\n hasLeadingIcon: Boolean(leadingIcon),\n hasTrailingIcon: Boolean(trailingIcon) || showErrorIcon,\n multiline,\n fixedHeight: multiline && !autoGrow,\n supportingTextOnFocus,\n });\n // The affixes and the counter join React Aria's description (supporting text, then error).\n const describedBy =\n [\n prefix ? prefixId : null,\n suffix ? suffixId : null,\n renderedDescription(inputProps['aria-describedby'], [\n !supportingText && descriptionProps.id,\n !showError && errorMessageProps.id,\n ]),\n showCounter ? counterId : null,\n ]\n .filter(Boolean)\n .join(' ') || undefined;\n\n // Pressing the container (not an icon button) focuses the input, like Compose.\n const focusInput = (event: PointerEvent<HTMLDivElement>) => {\n if ((event.target as Element).closest(INTERACTIVE) || disabled) return;\n event.preventDefault();\n // The field is where the press landed: the page never scrolls to it (2026-10-15).\n domInputRef.current?.focus({ preventScroll: true });\n };\n // Chrome's touch adjustment can send a tap's pointer events to the input while the mouse\n // events it emulates, and the click, land on the padding beside it, so the input never\n // takes focus (2026-10-17). A click on the field that leaves the input unfocused focuses it.\n // The mouse never needs it: its press on the padding already focused the input.\n const focusInputOnClick = (event: MouseEvent<HTMLDivElement>) => {\n const target = event.target as Element;\n if (\n (event.nativeEvent as globalThis.PointerEvent).pointerType === 'mouse' ||\n !event.currentTarget.contains(target) ||\n target.closest(INTERACTIVE) ||\n disabled ||\n document.activeElement === domInputRef.current\n ) {\n return;\n }\n domInputRef.current?.focus({ preventScroll: true });\n };\n\n // Part of the accessible name, as M3 asks (\"The accessibility label must include the\n // asterisk\"); the copy in the outline's notch is hidden with the fieldset.\n const requiredMark = required ? ' *' : null;\n const Input = multiline ? 'textarea' : 'input';\n\n return (\n <div\n {...rootData}\n ref={ref}\n style={style}\n className={styles.root({ class: cn(classNames?.root, className) })}\n data-tokens={mergeDataTokens('md-comp-text-field', rootData)}\n data-variant={variant}\n {...densityAttribute(density)}\n data-field-state={state}\n data-focused={isFocused || undefined}\n data-hovered={(isHovered && !disabled) || undefined}\n data-invalid={isInvalid || undefined}\n data-disabled={disabled || undefined}\n data-readonly={readOnly || undefined}\n data-floated={floated || undefined}\n >\n <div\n {...hoverProps}\n onPointerDown={focusInput}\n onClick={focusInputOnClick}\n data-slot=\"container\"\n className={styles.container({ class: classNames?.container })}\n >\n {variant === 'outlined' && (\n <fieldset\n aria-hidden=\"true\"\n data-slot=\"outline\"\n className={styles.outline({ class: classNames?.outline })}\n >\n {hasLabel && (\n <legend className={styles.notch()}>\n <span className={styles.notchClip()}>\n <span className={styles.notchText()}>\n {label}\n {requiredMark}\n </span>\n </span>\n </legend>\n )}\n </fieldset>\n )}\n {leadingIcon && (\n <span\n aria-hidden=\"true\"\n data-slot=\"leading-icon\"\n className={styles.icon({ class: cn(styles.leadingIcon(), classNames?.leadingIcon) })}\n >\n {leadingIcon}\n </span>\n )}\n <div className={styles.field()}>\n {prefix && (\n <span\n id={prefixId}\n className={styles.affix({ class: cn(styles.prefix(), classNames?.prefix) })}\n >\n <Affix text={prefix} label={prefixLabel} />\n </span>\n )}\n <Input\n {...(mergeProps(inputProps, pickWidgetAria(ariaProps), focusProps) as object)}\n aria-describedby={describedBy}\n ref={domInputRef as Ref<never>}\n data-slot=\"input\"\n rows={multiline ? rows : undefined}\n className={styles.input({ class: classNames?.input })}\n />\n {suffix && (\n <span\n id={suffixId}\n className={styles.affix({ class: cn(styles.suffix(), classNames?.suffix) })}\n >\n <Affix text={suffix} label={suffixLabel} />\n </span>\n )}\n </div>\n {trailingIcon ? (\n <span\n data-slot=\"trailing-icon\"\n className={styles.icon({\n class: cn(styles.trailingIcon(), classNames?.trailingIcon),\n })}\n >\n {trailingIcon}\n </span>\n ) : showErrorIcon ? (\n <span className={styles.icon({ class: styles.trailingIcon() })}>\n <FieldErrorIcon\n label={labels.error}\n icon={errorIcon}\n className={styles.errorIcon({ class: classNames?.errorIcon })}\n />\n </span>\n ) : null}\n {hasLabel && (\n <FieldLabel\n {...labelProps}\n data-slot=\"label\"\n className={styles.label({ class: classNames?.label })}\n >\n {label}\n {requiredMark}\n </FieldLabel>\n )}\n <span\n aria-hidden=\"true\"\n data-slot=\"active-indicator\"\n className={styles.indicator({ class: classNames?.activeIndicator })}\n />\n </div>\n {(supportingText || showError || showCounter) && (\n <div className={styles.supporting()}>\n {supportingText && (\n <SupportingText\n {...descriptionProps}\n data-slot=\"supporting-text\"\n className={cn(\n styles.supportingText({\n class: cn(styles.supportingTextColor(), classNames?.supportingText),\n }),\n showError && 'sr-only',\n )}\n >\n {supportingText}\n </SupportingText>\n )}\n {showError && (\n <ErrorText\n {...errorMessageProps}\n className={styles.supportingText({\n supportingTextOnFocus: false,\n class: classNames?.errorText,\n })}\n >\n {resolvedError}\n </ErrorText>\n )}\n {showCounter && (\n <CharacterCounter\n id={counterId}\n count={value.length}\n max={maxLength}\n label={labels.characterCount(value.length, maxLength)}\n className={styles.counter({ class: classNames?.counter })}\n />\n )}\n </div>\n )}\n </div>\n );\n}\n\n/** An affix's visible text, read as its `label` when there is one. */\nfunction Affix({ text, label }: { text: ReactNode; label: string | undefined }) {\n if (label === undefined) return text;\n return (\n <>\n <span aria-hidden=\"true\">{text}</span>\n <span className=\"sr-only\">{label}</span>\n </>\n );\n}\n"],"mappings":"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"}