@windmorph/react 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/components/carousel/carousel-styles.d.ts +6 -15
  2. package/dist/components/carousel/carousel-styles.js +4 -7
  3. package/dist/components/carousel/carousel-styles.js.map +1 -1
  4. package/dist/components/checkbox/checkbox-tokens.d.ts +1 -1
  5. package/dist/components/chip/chip-tokens.d.ts +4 -4
  6. package/dist/components/date-picker/DatePicker.js +11 -4
  7. package/dist/components/date-picker/DatePicker.js.map +1 -1
  8. package/dist/components/date-picker/date-picker-styles.d.ts +6 -6
  9. package/dist/components/date-picker/date-picker-tokens.d.ts +1 -1
  10. package/dist/components/dialog/Dialog.d.ts +4 -2
  11. package/dist/components/dialog/Dialog.js +2 -2
  12. package/dist/components/dialog/Dialog.js.map +1 -1
  13. package/dist/components/dialog/dialog-tokens.d.ts +1 -1
  14. package/dist/components/icon-button/IconButton.d.ts +3 -0
  15. package/dist/components/icon-button/IconButton.js +3 -0
  16. package/dist/components/icon-button/IconButton.js.map +1 -1
  17. package/dist/components/list/List.d.ts +9 -0
  18. package/dist/components/list/List.js +7 -4
  19. package/dist/components/list/List.js.map +1 -1
  20. package/dist/components/list/list-styles.js +6 -6
  21. package/dist/components/list/list-styles.js.map +1 -1
  22. package/dist/components/list/list-tokens.d.ts +1 -1
  23. package/dist/components/list/list-tokens.js +50 -0
  24. package/dist/components/list/list-tokens.js.map +1 -1
  25. package/dist/components/menu/menu-tokens.d.ts +1 -1
  26. package/dist/components/navigation/NavigationRail.js +52 -15
  27. package/dist/components/navigation/NavigationRail.js.map +1 -1
  28. package/dist/components/navigation/nav-item-styles.js +4 -4
  29. package/dist/components/navigation/nav-item-styles.js.map +1 -1
  30. package/dist/components/search/SearchBar.js +19 -34
  31. package/dist/components/search/SearchBar.js.map +1 -1
  32. package/dist/components/search/SearchField.d.ts +111 -0
  33. package/dist/components/search/SearchField.js +128 -0
  34. package/dist/components/search/SearchField.js.map +1 -0
  35. package/dist/components/search/search-field-frame.js +36 -0
  36. package/dist/components/search/search-field-frame.js.map +1 -0
  37. package/dist/components/search/search-styles.d.ts +58 -0
  38. package/dist/components/search/search-styles.js +27 -2
  39. package/dist/components/search/search-styles.js.map +1 -1
  40. package/dist/components/select/search-list.js +8 -13
  41. package/dist/components/select/search-list.js.map +1 -1
  42. package/dist/components/sheet/SideSheet.d.ts +2 -0
  43. package/dist/components/sheet/SideSheet.js +1 -1
  44. package/dist/components/sheet/SideSheet.js.map +1 -1
  45. package/dist/components/snackbar/snackbar-tokens.d.ts +1 -1
  46. package/dist/components/text-field/TextField.js +19 -5
  47. package/dist/components/text-field/TextField.js.map +1 -1
  48. package/dist/components/toolbar/toolbar-styles.js +2 -2
  49. package/dist/components/toolbar/toolbar-styles.js.map +1 -1
  50. package/dist/components/tooltip/tooltip-tokens.d.ts +1 -1
  51. package/dist/index.d.ts +3 -2
  52. package/dist/index.js +3 -2
  53. package/dist/primitives/ButtonBase.js +1 -0
  54. package/dist/primitives/ButtonBase.js.map +1 -1
  55. package/dist/styles/generated/components.css +5 -0
  56. package/dist/styles/utilities.css +8 -0
  57. package/dist/styles.compiled.css +1 -1
  58. package/dist/theme/ThemeProvider.js +17 -0
  59. package/dist/theme/ThemeProvider.js.map +1 -1
  60. package/package.json +1 -1
@@ -50,6 +50,19 @@ export declare const searchBarStyles: import("tailwind-variants").TVReturnType<{
50
50
  input: string;
51
51
  };
52
52
  };
53
+ /**
54
+ * A disabled `SearchField`: M3's disabled colours, the content `on-surface` at 38% and the
55
+ * container at 12%; forced colours draw it in `GrayText`.
56
+ */
57
+ disabled: {
58
+ true: {
59
+ field: string;
60
+ leading: string;
61
+ trailing: string;
62
+ input: string;
63
+ };
64
+ false: {};
65
+ };
53
66
  }, {
54
67
  root: string;
55
68
  field: string[];
@@ -113,6 +126,19 @@ export declare const searchBarStyles: import("tailwind-variants").TVReturnType<{
113
126
  input: string;
114
127
  };
115
128
  };
129
+ /**
130
+ * A disabled `SearchField`: M3's disabled colours, the content `on-surface` at 38% and the
131
+ * container at 12%; forced colours draw it in `GrayText`.
132
+ */
133
+ disabled: {
134
+ true: {
135
+ field: string;
136
+ leading: string;
137
+ trailing: string;
138
+ input: string;
139
+ };
140
+ false: {};
141
+ };
116
142
  }, {
117
143
  root: string;
118
144
  field: string[];
@@ -176,6 +202,19 @@ export declare const searchBarStyles: import("tailwind-variants").TVReturnType<{
176
202
  input: string;
177
203
  };
178
204
  };
205
+ /**
206
+ * A disabled `SearchField`: M3's disabled colours, the content `on-surface` at 38% and the
207
+ * container at 12%; forced colours draw it in `GrayText`.
208
+ */
209
+ disabled: {
210
+ true: {
211
+ field: string;
212
+ leading: string;
213
+ trailing: string;
214
+ input: string;
215
+ };
216
+ false: {};
217
+ };
179
218
  }, {
180
219
  root: string;
181
220
  field: string[];
@@ -191,6 +230,20 @@ export declare const searchBarStyles: import("tailwind-variants").TVReturnType<{
191
230
  fullScreenHeader: string[];
192
231
  fullScreenContent: string[];
193
232
  }>>;
233
+ /**
234
+ * Variant definitions for {@link SearchField}'s root. Its pill and the parts in it are the search
235
+ * bar's ({@link searchBarStyles}), so the two can't drift.
236
+ */
237
+ export declare const searchFieldStyles: import("tailwind-variants").TVReturnType<{}, {
238
+ root: string;
239
+ slot: string;
240
+ }, undefined, {}, {
241
+ root: string;
242
+ slot: string;
243
+ }, import("tailwind-variants").TVReturnTypeLike<{}, {
244
+ root: string;
245
+ slot: string;
246
+ }>>;
194
247
  /** Variant definitions for {@link SearchAppBar}. */
195
248
  export declare const searchAppBarStyles: import("tailwind-variants").TVReturnType<{
196
249
  scrolling: {
@@ -239,5 +292,10 @@ export type SearchBarStyleProps = VariantProps<typeof searchBarStyles>;
239
292
  * component props: their names (`hasIcon`, `isOpen`…) follow the classes and may change.
240
293
  */
241
294
  export type SearchAppBarStyleProps = VariantProps<typeof searchAppBarStyles>;
295
+ /**
296
+ * The variants of {@link searchFieldStyles}, for building on its styles. They are the recipe's keys, not
297
+ * component props: their names follow the classes and may change.
298
+ */
299
+ export type SearchFieldStyleProps = VariantProps<typeof searchFieldStyles>;
242
300
  //#endregion
243
301
  //# sourceMappingURL=search-styles.d.ts.map
@@ -107,6 +107,19 @@ const searchBarStyles = tv({
107
107
  textAlign: {
108
108
  start: {},
109
109
  center: { input: "text-center" }
110
+ },
111
+ /**
112
+ * A disabled `SearchField`: M3's disabled colours, the content `on-surface` at 38% and the
113
+ * container at 12%; forced colours draw it in `GrayText`.
114
+ */
115
+ disabled: {
116
+ true: {
117
+ field: "bg-on-surface/12 forced-colors:border-[GrayText]",
118
+ leading: "text-on-surface/38 forced-colors:text-[GrayText]",
119
+ trailing: "text-on-surface/38 forced-colors:text-[GrayText]",
120
+ input: "cursor-default text-on-surface/38 placeholder:text-on-surface/38 forced-colors:text-[GrayText]"
121
+ },
122
+ false: {}
110
123
  }
111
124
  },
112
125
  defaultVariants: {
@@ -114,9 +127,21 @@ const searchBarStyles = tv({
114
127
  hasLeading: false,
115
128
  hasTrailing: false,
116
129
  tone: "default",
117
- textAlign: "start"
130
+ textAlign: "start",
131
+ disabled: false
118
132
  }
119
133
  });
134
+ /**
135
+ * Variant definitions for {@link SearchField}'s root. Its pill and the parts in it are the search
136
+ * bar's ({@link searchBarStyles}), so the two can't drift.
137
+ */
138
+ const searchFieldStyles = tv({
139
+ slots: {
140
+ root: "flex h-[calc(56px+var(--md-sys-density,0)*4px)] w-full min-w-0",
141
+ slot: "max-h-full"
142
+ },
143
+ variants: {}
144
+ });
120
145
  /** Variant definitions for {@link SearchAppBar}. */
121
146
  const searchAppBarStyles = tv({
122
147
  slots: {
@@ -138,6 +163,6 @@ const searchAppBarStyles = tv({
138
163
  defaultVariants: { scrolling: false }
139
164
  });
140
165
  //#endregion
141
- export { searchAppBarStyles, searchBarStyles };
166
+ export { searchAppBarStyles, searchBarStyles, searchFieldStyles };
142
167
 
143
168
  //# sourceMappingURL=search-styles.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"search-styles.js","names":[],"sources":["../../../src/components/search/search-styles.ts"],"sourcesContent":["import { tv, type VariantProps } from '../../utils/tv';\n\n/*\n * Values follow Compose Material 3 (SearchBar.kt: SearchBar, AppBarWithSearch,\n * ExpandedDockedSearchBarWithGap, ExpandedFullScreenSearchBar, SearchBarDefaults.InputField;\n * SearchBar / SearchView tokens).\n *\n * Field: 56px, full corners, `surface-container-high`, 360px wide by default and at most\n * 720px. Input and placeholder `body-large`, `on-surface` / `on-surface-variant`. Icons sit\n * in 48px boxes shifted 4px inwards (leading `on-surface`, trailing `on-surface-variant`);\n * the text keeps 4px from the leading and trailing tap targets (the spec's \"Leading icon and\n * label padding (from tap target): 4dp\" and \"Label and trailing icon padding\"; Compose's\n * code leaves none), so it starts 56px in after a leading icon, 16px without one.\n *\n * Docked (with gap): the expanded field, 56px tall whatever the collapsed bar's height\n * (M3's focused search bar container), sits centred over the collapsed one and widens by 12px on\n * each side (M3 Expressive: the margins change from 24dp to 12dp when focused). It morphs from\n * the bar's bounds (position and size) on the default spatial spring and back on the same\n * spring floored at the bar, so it lands on the bar exactly; a dropdown 2px below it (12px corners,\n * `surface-container-high`; with the bar, at least 240px and at most 2/3 of the window\n * tall, per the M3 spec) slides down from half its height on the default spatial spring\n * (fast spatial back) while its content fades in over 100ms after 50ms, and its height follows\n * the results on the default spatial spring; a 32% scrim covers the page.\n *\n * Full screen, in M3 Expressive's contained style: a `surface-container-low` surface grows\n * from the bar's bounds (28px corners) to the whole window on the slow spatial spring\n * (default spatial back), clamped so nothing overshoots; the field keeps its\n * `surface-container-high` pill and moves and grows from the bar to the top, 8px down and\n * 16px from each side, and the content starts 72px down.\n *\n * In a search app bar the field is `surface-container` (`surface-container-highest` once\n * content scrolls under the bar) and fills the free space up to 312px, then half of it,\n * at most 720px (M3 app bar guidelines).\n *\n * The field's colours and corners come from the search bar's component tokens and the\n * views' from the search view's (`search-tokens.ts`, `--md-comp-search-bar-*` and\n * `--md-comp-search-view-*`); the pill, the dropdown and the full-screen view carry\n * `data-tokens`. The search app bar reads the app bar's tokens (`app-bar-tokens.ts`). A\n * consumer class still wins in `cn()`.\n *\n * Every class is written out in full so Tailwind can find it.\n */\n\nconst contentFade = [\n 'opacity-100 starting:opacity-0 data-exiting:opacity-0',\n // Compose: fade in over 100ms after 50ms (standard accelerate), out over 100ms.\n '[transition-delay:0s,50ms] data-exiting:[transition-delay:0s,0s]',\n];\n\n/** Variant definitions for {@link SearchBar}; extend them to add variants. */\nexport const searchBarStyles = tv({\n slots: {\n // The bar's height is set here alone (56px, Compose's); a consumer `h-*` resizes it.\n root: 'flex h-[56px] w-[360px] max-w-[min(720px,100%)] shrink-0',\n // The pill holding the icons and the input fills the bar.\n // Keyboard focus in the input draws Compose's inset focus ring around the pill.\n // Forced colours drop the pill's colour: a system-colour border keeps its edge.\n field: [\n 'focus-ring-inset flex h-full w-full min-w-0 items-center rounded-[var(--_md-comp-search-bar-container-shape)] text-on-surface',\n 'forced-colors:border forced-colors:border-solid forced-colors:border-[CanvasText]',\n ],\n // Icon buttons in the slots take the slot's colour (and so its icon colour token), as\n // in the app bar's slots: Compose colours the search bar's and view's leading icon\n // `on-surface` and trailing icons `on-surface-variant`, buttons included.\n leading:\n 'ms-[4px] flex size-[48px] shrink-0 items-center justify-center text-[color:var(--_md-comp-search-bar-leading-icon-color)] [--md-comp-icon-button-icon-color:currentColor] [&>svg]:size-[24px]',\n // One or two trailing icon buttons, or an avatar (30px) and an icon button (M3). Two\n // 40px buttons sit 8px apart inside 4px of padding, so their 48px targets meet edge to\n // edge within the slot (a single one is centred in 48px, as before).\n trailing: [\n 'me-[4px] flex h-[48px] min-w-[48px] shrink-0 items-center justify-center gap-[8px] px-[4px] text-[color:var(--_md-comp-search-bar-trailing-icon-color)] [--md-comp-icon-button-icon-color:currentColor]',\n '[&>svg]:size-[24px] [&>img]:size-[30px] [&>img]:rounded-full',\n ],\n input: [\n 'h-full min-w-0 flex-1 bg-transparent text-body-large text-[color:var(--_md-comp-search-bar-input-text-color)] caret-primary outline-none',\n 'placeholder:text-on-surface-variant',\n '[&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none',\n ],\n scrim: [\n 'fixed inset-0 z-(--md-sys-z-scrim) bg-scrim/32 opacity-100 data-exiting:pointer-events-none',\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 'starting:opacity-0 data-exiting:opacity-0',\n ],\n /*\n * Docked view: the panel sits over the collapsed bar and morphs from the bar's bounds\n * (`--m3-search-left/top/width/height`) to the focused field's (the `-dx/-dy/-dw/-dh`\n * deltas) as `--m3-search-progress` runs 0 → 1, as Compose lerps the layout with its\n * search bar state's progress. `--m3-search-p` is that progress floored at 0 (Compose's\n * `coerceAtLeast(0f)`): the spring may overshoot past the focused bounds on the way in,\n * but never past the bar's on the way back, so the field lands on the bar exactly.\n * Reduced motion: the progress stays at 1, so the field is at its focused size at once.\n */\n dockedPanel: [\n // Leaving, the view takes no presses: the spring's tail runs on after the field has\n // landed on the bar, and a press there ran a suggestion of the invisible dropdown.\n 'fixed z-(--md-sys-z-sheet) flex flex-col outline-none data-exiting:pointer-events-none',\n '[--m3-search-progress:1] [--m3-search-p:max(0,var(--m3-search-progress))]',\n 'motion-safe:starting:[--m3-search-progress:0] motion-safe:data-exiting:[--m3-search-progress:0]',\n 'top-[calc(var(--m3-search-top)_+_var(--m3-search-p)_*_var(--m3-search-dy))]',\n 'left-[calc(var(--m3-search-left)_+_var(--m3-search-p)_*_var(--m3-search-dx))]',\n 'w-[calc(var(--m3-search-width)_+_var(--m3-search-p)_*_var(--m3-search-dw))]',\n // Both ways on the default spatial spring (Compose's collapse spec).\n '[transition-property:--m3-search-progress]',\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 // The focused field is 56px (M3), even when the collapsed bar is resized; it grows from\n // the bar's height with the panel.\n // The full-window layer the docked view sits in (React Aria's underlay).\n underlay: 'fixed inset-0 z-(--md-sys-z-sheet) data-exiting:pointer-events-none',\n dockedHeader:\n 'flex h-[calc(var(--m3-search-height)_+_var(--m3-search-p)_*_var(--m3-search-dh))] shrink-0',\n dropdown: [\n 'mt-[2px] overflow-y-auto overscroll-contain data-resizing:overflow-y-hidden data-exiting:pointer-events-none',\n // 58px is the field and the gap above the dropdown.\n 'min-h-[min(182px,calc(66.67dvh-58px))] max-h-[calc(66.67dvh-58px)]',\n // The height follows the content (`--m3-search-content-height`, measured) within those\n // bounds, so it animates as the results change. Without the measurement (before\n // hydration, or under reduced motion) it's `auto`.\n 'h-[clamp(min(182px,calc(66.67dvh_-_58px)),var(--m3-search-content-height),calc(66.67dvh_-_58px))]',\n 'rounded-[var(--_md-comp-search-view-docked-container-shape)] bg-[var(--_md-comp-search-view-container-color)] text-on-surface',\n 'forced-colors:border forced-colors:border-solid forced-colors:border-[CanvasText]',\n // Reduced motion: it only fades.\n 'translate-y-0 motion-safe:starting:-translate-y-1/2 motion-safe:data-exiting:-translate-y-1/2',\n // The delays below cycle, so the height (third) starts at once like the slide.\n '[transition-property:translate,opacity,height]',\n '[transition-duration:var(--md-sys-motion-spring-spatial-default-duration),100ms,var(--md-sys-motion-spring-spatial-default-duration)]',\n '[transition-timing-function:var(--md-sys-motion-spring-spatial-default-easing),cubic-bezier(0.3,0,1,1),var(--md-sys-motion-spring-spatial-default-easing)]',\n 'data-exiting:[transition-duration:var(--md-sys-motion-spring-spatial-fast-duration),100ms,var(--md-sys-motion-spring-spatial-fast-duration)]',\n 'data-exiting:[transition-timing-function:var(--md-sys-motion-spring-spatial-fast-easing),cubic-bezier(0,0,0,1),var(--md-sys-motion-spring-spatial-fast-easing)]',\n ...contentFade,\n ],\n /*\n * Full-screen view: the surface grows from the bar's bounds to the window as\n * `--m3-search-progress` runs 0 → 1, through a clip (nothing is transformed). Compose\n * clamps the full-screen progress to 0..1 (`coerceIn`), so `--m3-search-p` is clamped too:\n * the clip and the field never overshoot the window's or the bar's bounds.\n */\n fullScreenPanel: [\n 'fixed inset-0 z-(--md-sys-z-sheet) bg-[var(--_md-comp-search-view-container-color)] text-on-surface outline-none data-exiting:pointer-events-none',\n '[--m3-search-progress:1] [--m3-search-p:clamp(0,var(--m3-search-progress),1)]',\n 'motion-safe:starting:[--m3-search-progress:0] motion-safe:data-exiting:[--m3-search-progress:0]',\n '[clip-path:inset(calc((1_-_var(--m3-search-p))_*_var(--m3-search-top))_calc((1_-_var(--m3-search-p))_*_var(--m3-search-right))_calc((1_-_var(--m3-search-p))_*_var(--m3-search-bottom))_calc((1_-_var(--m3-search-p))_*_var(--m3-search-left))_round_calc((1_-_var(--m3-search-p))_*_28px))]',\n 'motion-safe:[transition-property:--m3-search-progress]',\n 'motion-safe:[transition-duration:var(--md-sys-motion-spring-spatial-slow-duration)]',\n 'motion-safe:[transition-timing-function:var(--md-sys-motion-spring-spatial-slow-easing)]',\n 'motion-safe:data-exiting:[transition-duration:var(--md-sys-motion-spring-spatial-default-duration)]',\n 'motion-safe:data-exiting:[transition-timing-function:var(--md-sys-motion-spring-spatial-default-easing)]',\n // Reduced motion: the view fades in and out over the window instead of growing.\n 'motion-reduce:starting:opacity-0 motion-reduce:data-exiting:opacity-0',\n 'motion-reduce:[transition-property:opacity]',\n 'motion-reduce:[transition-duration:var(--md-sys-motion-spring-effects-fast-duration)]',\n 'motion-reduce:[transition-timing-function:var(--md-sys-motion-spring-effects-fast-easing)]',\n ],\n // Compose's full-screen field is 56px whatever the bar's size, 8px down and 16px from each\n // side; it moves and grows there from the bar's bounds with the surface.\n fullScreenHeader: [\n 'absolute',\n 'start-[calc(var(--m3-search-start)_+_var(--m3-search-p)_*_(16px_-_var(--m3-search-start)))]',\n 'top-[calc(var(--m3-search-top)_+_var(--m3-search-p)_*_(8px_-_var(--m3-search-top)))]',\n 'w-[calc(var(--m3-search-width)_+_var(--m3-search-p)_*_(100%_-_32px_-_var(--m3-search-width)))]',\n 'h-[calc(var(--m3-search-height)_+_var(--m3-search-p)_*_(56px_-_var(--m3-search-height)))]',\n ],\n fullScreenContent: [\n 'absolute inset-x-0 top-[72px] bottom-0 overflow-y-auto overscroll-contain',\n '[transition-property:opacity] [transition-duration:100ms]',\n '[transition-timing-function:cubic-bezier(0.3,0,1,1)]',\n 'data-exiting:[transition-timing-function:cubic-bezier(0,0,0,1)]',\n ...contentFade,\n ],\n },\n variants: {\n /**\n * Inside a search app bar the bar fills the free space up to 312px, then grows to half\n * of it (M3), up to 720px.\n */\n inAppBar: { true: { root: 'w-[max(min(100%,312px),50%)]' }, false: {} },\n // 4px from a tap target (M3), else 16px from the pill's edge.\n hasLeading: { true: { input: 'ps-[4px]' }, false: { input: 'ps-[16px]' } },\n hasTrailing: { true: { input: 'pe-[4px]' }, false: { input: 'pe-[16px]' } },\n /**\n * Container colour: the bar's own; in a search app bar `surface-container`, and one\n * step higher than the bar once content scrolls under it.\n */\n tone: {\n default: { field: 'bg-[var(--_md-comp-search-bar-container-color)]' },\n appBar: { field: 'bg-surface-container' },\n scrolled: { field: 'bg-surface-container-highest' },\n },\n /** Centre the placeholder and the query while the bar is collapsed. */\n textAlign: { start: {}, center: { input: 'text-center' } },\n },\n defaultVariants: {\n inAppBar: false,\n hasLeading: false,\n hasTrailing: false,\n tone: 'default',\n textAlign: 'start',\n },\n});\n\n/** Variant definitions for {@link SearchAppBar}. */\nexport const searchAppBarStyles = tv({\n slots: {\n root: [\n 'flex min-h-[64px] w-full items-center text-on-surface',\n 'bg-[var(--_md-comp-app-bar-container-color)] data-scrolled:bg-[var(--_md-comp-app-bar-on-scroll-container-color)]',\n '[transition-property:background-color]',\n '[transition-duration:var(--md-sys-motion-spring-effects-default-duration)]',\n '[transition-timing-function:var(--md-sys-motion-spring-effects-default-easing)]',\n ],\n // Icon buttons take each slot's content colour.\n navigation:\n 'flex shrink-0 items-center ps-[8px] pe-[4px] text-[color:var(--_md-comp-app-bar-leading-icon-color)] [--md-comp-icon-button-icon-color:currentColor]',\n // Centres the search bar in the free space, 8px from its neighbours and 4px vertically.\n search: 'flex min-w-0 flex-1 justify-center ps-[8px] pe-[8px] pt-[4px] pb-[4px]',\n actions:\n 'flex shrink-0 items-center gap-[8px] ps-[4px] pe-[8px] text-[color:var(--_md-comp-app-bar-trailing-icon-color)] [--md-comp-icon-button-icon-color:currentColor]',\n },\n variants: {\n scrolling: {\n true: { root: 'sticky top-(--m3-app-bar-offset) z-(--md-sys-z-sticky)' },\n false: {},\n },\n },\n defaultVariants: { scrolling: false },\n});\n\n/**\n * The variants of {@link searchBarStyles}, 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 SearchBarStyleProps = VariantProps<typeof searchBarStyles>;\n/**\n * The variants of {@link searchAppBarStyles}, 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 SearchAppBarStyleProps = VariantProps<typeof searchAppBarStyles>;\n"],"mappings":";;AA2CA,MAAM,cAAc,CAClB,yDAEA,kEACF;;AAGA,MAAa,kBAAkB,GAAG;CAChC,OAAO;EAEL,MAAM;EAIN,OAAO,CACL,iIACA,mFACF;EAIA,SACE;EAIF,UAAU,CACR,2MACA,8DACF;EACA,OAAO;GACL;GACA;GACA;EACF;EACA,OAAO;GACL;GACA;GACA;GACA;GACA;EACF;EAUA,aAAa;GAGX;GACA;GACA;GACA;GACA;GACA;GAEA;GACA;GACA;EACF;EAIA,UAAU;EACV,cACE;EACF,UAAU;GACR;GAEA;GAIA;GACA;GACA;GAEA;GAEA;GACA;GACA;GACA;GACA;GACA,GAAG;EACL;EAOA,iBAAiB;GACf;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GAEA;GACA;GACA;GACA;EACF;EAGA,kBAAkB;GAChB;GACA;GACA;GACA;GACA;EACF;EACA,mBAAmB;GACjB;GACA;GACA;GACA;GACA,GAAG;EACL;CACF;CACA,UAAU;;;;;EAKR,UAAU;GAAE,MAAM,EAAE,MAAM,+BAA+B;GAAG,OAAO,CAAC;EAAE;EAEtE,YAAY;GAAE,MAAM,EAAE,OAAO,WAAW;GAAG,OAAO,EAAE,OAAO,YAAY;EAAE;EACzE,aAAa;GAAE,MAAM,EAAE,OAAO,WAAW;GAAG,OAAO,EAAE,OAAO,YAAY;EAAE;;;;;EAK1E,MAAM;GACJ,SAAS,EAAE,OAAO,kDAAkD;GACpE,QAAQ,EAAE,OAAO,uBAAuB;GACxC,UAAU,EAAE,OAAO,+BAA+B;EACpD;;EAEA,WAAW;GAAE,OAAO,CAAC;GAAG,QAAQ,EAAE,OAAO,cAAc;EAAE;CAC3D;CACA,iBAAiB;EACf,UAAU;EACV,YAAY;EACZ,aAAa;EACb,MAAM;EACN,WAAW;CACb;AACF,CAAC;;AAGD,MAAa,qBAAqB,GAAG;CACnC,OAAO;EACL,MAAM;GACJ;GACA;GACA;GACA;GACA;EACF;EAEA,YACE;EAEF,QAAQ;EACR,SACE;CACJ;CACA,UAAU,EACR,WAAW;EACT,MAAM,EAAE,MAAM,yDAAyD;EACvE,OAAO,CAAC;CACV,EACF;CACA,iBAAiB,EAAE,WAAW,MAAM;AACtC,CAAC"}
1
+ {"version":3,"file":"search-styles.js","names":[],"sources":["../../../src/components/search/search-styles.ts"],"sourcesContent":["import { tv, type VariantProps } from '../../utils/tv';\n\n/*\n * Values follow Compose Material 3 (SearchBar.kt: SearchBar, AppBarWithSearch,\n * ExpandedDockedSearchBarWithGap, ExpandedFullScreenSearchBar, SearchBarDefaults.InputField;\n * SearchBar / SearchView tokens).\n *\n * Field: 56px, full corners, `surface-container-high`, 360px wide by default and at most\n * 720px. Input and placeholder `body-large`, `on-surface` / `on-surface-variant`. Icons sit\n * in 48px boxes shifted 4px inwards (leading `on-surface`, trailing `on-surface-variant`);\n * the text keeps 4px from the leading and trailing tap targets (the spec's \"Leading icon and\n * label padding (from tap target): 4dp\" and \"Label and trailing icon padding\"; Compose's\n * code leaves none), so it starts 56px in after a leading icon, 16px without one.\n *\n * Docked (with gap): the expanded field, 56px tall whatever the collapsed bar's height\n * (M3's focused search bar container), sits centred over the collapsed one and widens by 12px on\n * each side (M3 Expressive: the margins change from 24dp to 12dp when focused). It morphs from\n * the bar's bounds (position and size) on the default spatial spring and back on the same\n * spring floored at the bar, so it lands on the bar exactly; a dropdown 2px below it (12px corners,\n * `surface-container-high`; with the bar, at least 240px and at most 2/3 of the window\n * tall, per the M3 spec) slides down from half its height on the default spatial spring\n * (fast spatial back) while its content fades in over 100ms after 50ms, and its height follows\n * the results on the default spatial spring; a 32% scrim covers the page.\n *\n * Full screen, in M3 Expressive's contained style: a `surface-container-low` surface grows\n * from the bar's bounds (28px corners) to the whole window on the slow spatial spring\n * (default spatial back), clamped so nothing overshoots; the field keeps its\n * `surface-container-high` pill and moves and grows from the bar to the top, 8px down and\n * 16px from each side, and the content starts 72px down.\n *\n * In a search app bar the field is `surface-container` (`surface-container-highest` once\n * content scrolls under the bar) and fills the free space up to 312px, then half of it,\n * at most 720px (M3 app bar guidelines).\n *\n * The field's colours and corners come from the search bar's component tokens and the\n * views' from the search view's (`search-tokens.ts`, `--md-comp-search-bar-*` and\n * `--md-comp-search-view-*`); the pill, the dropdown and the full-screen view carry\n * `data-tokens`. The search app bar reads the app bar's tokens (`app-bar-tokens.ts`). A\n * consumer class still wins in `cn()`.\n *\n * Every class is written out in full so Tailwind can find it.\n */\n\nconst contentFade = [\n 'opacity-100 starting:opacity-0 data-exiting:opacity-0',\n // Compose: fade in over 100ms after 50ms (standard accelerate), out over 100ms.\n '[transition-delay:0s,50ms] data-exiting:[transition-delay:0s,0s]',\n];\n\n/** Variant definitions for {@link SearchBar}; extend them to add variants. */\nexport const searchBarStyles = tv({\n slots: {\n // The bar's height is set here alone (56px, Compose's); a consumer `h-*` resizes it.\n root: 'flex h-[56px] w-[360px] max-w-[min(720px,100%)] shrink-0',\n // The pill holding the icons and the input fills the bar.\n // Keyboard focus in the input draws Compose's inset focus ring around the pill.\n // Forced colours drop the pill's colour: a system-colour border keeps its edge.\n field: [\n 'focus-ring-inset flex h-full w-full min-w-0 items-center rounded-[var(--_md-comp-search-bar-container-shape)] text-on-surface',\n 'forced-colors:border forced-colors:border-solid forced-colors:border-[CanvasText]',\n ],\n // Icon buttons in the slots take the slot's colour (and so its icon colour token), as\n // in the app bar's slots: Compose colours the search bar's and view's leading icon\n // `on-surface` and trailing icons `on-surface-variant`, buttons included.\n leading:\n 'ms-[4px] flex size-[48px] shrink-0 items-center justify-center text-[color:var(--_md-comp-search-bar-leading-icon-color)] [--md-comp-icon-button-icon-color:currentColor] [&>svg]:size-[24px]',\n // One or two trailing icon buttons, or an avatar (30px) and an icon button (M3). Two\n // 40px buttons sit 8px apart inside 4px of padding, so their 48px targets meet edge to\n // edge within the slot (a single one is centred in 48px, as before).\n trailing: [\n 'me-[4px] flex h-[48px] min-w-[48px] shrink-0 items-center justify-center gap-[8px] px-[4px] text-[color:var(--_md-comp-search-bar-trailing-icon-color)] [--md-comp-icon-button-icon-color:currentColor]',\n '[&>svg]:size-[24px] [&>img]:size-[30px] [&>img]:rounded-full',\n ],\n input: [\n 'h-full min-w-0 flex-1 bg-transparent text-body-large text-[color:var(--_md-comp-search-bar-input-text-color)] caret-primary outline-none',\n 'placeholder:text-on-surface-variant',\n '[&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none',\n ],\n scrim: [\n 'fixed inset-0 z-(--md-sys-z-scrim) bg-scrim/32 opacity-100 data-exiting:pointer-events-none',\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 'starting:opacity-0 data-exiting:opacity-0',\n ],\n /*\n * Docked view: the panel sits over the collapsed bar and morphs from the bar's bounds\n * (`--m3-search-left/top/width/height`) to the focused field's (the `-dx/-dy/-dw/-dh`\n * deltas) as `--m3-search-progress` runs 0 → 1, as Compose lerps the layout with its\n * search bar state's progress. `--m3-search-p` is that progress floored at 0 (Compose's\n * `coerceAtLeast(0f)`): the spring may overshoot past the focused bounds on the way in,\n * but never past the bar's on the way back, so the field lands on the bar exactly.\n * Reduced motion: the progress stays at 1, so the field is at its focused size at once.\n */\n dockedPanel: [\n // Leaving, the view takes no presses: the spring's tail runs on after the field has\n // landed on the bar, and a press there ran a suggestion of the invisible dropdown.\n 'fixed z-(--md-sys-z-sheet) flex flex-col outline-none data-exiting:pointer-events-none',\n '[--m3-search-progress:1] [--m3-search-p:max(0,var(--m3-search-progress))]',\n 'motion-safe:starting:[--m3-search-progress:0] motion-safe:data-exiting:[--m3-search-progress:0]',\n 'top-[calc(var(--m3-search-top)_+_var(--m3-search-p)_*_var(--m3-search-dy))]',\n 'left-[calc(var(--m3-search-left)_+_var(--m3-search-p)_*_var(--m3-search-dx))]',\n 'w-[calc(var(--m3-search-width)_+_var(--m3-search-p)_*_var(--m3-search-dw))]',\n // Both ways on the default spatial spring (Compose's collapse spec).\n '[transition-property:--m3-search-progress]',\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 // The focused field is 56px (M3), even when the collapsed bar is resized; it grows from\n // the bar's height with the panel.\n // The full-window layer the docked view sits in (React Aria's underlay).\n underlay: 'fixed inset-0 z-(--md-sys-z-sheet) data-exiting:pointer-events-none',\n dockedHeader:\n 'flex h-[calc(var(--m3-search-height)_+_var(--m3-search-p)_*_var(--m3-search-dh))] shrink-0',\n dropdown: [\n 'mt-[2px] overflow-y-auto overscroll-contain data-resizing:overflow-y-hidden data-exiting:pointer-events-none',\n // 58px is the field and the gap above the dropdown.\n 'min-h-[min(182px,calc(66.67dvh-58px))] max-h-[calc(66.67dvh-58px)]',\n // The height follows the content (`--m3-search-content-height`, measured) within those\n // bounds, so it animates as the results change. Without the measurement (before\n // hydration, or under reduced motion) it's `auto`.\n 'h-[clamp(min(182px,calc(66.67dvh_-_58px)),var(--m3-search-content-height),calc(66.67dvh_-_58px))]',\n 'rounded-[var(--_md-comp-search-view-docked-container-shape)] bg-[var(--_md-comp-search-view-container-color)] text-on-surface',\n 'forced-colors:border forced-colors:border-solid forced-colors:border-[CanvasText]',\n // Reduced motion: it only fades.\n 'translate-y-0 motion-safe:starting:-translate-y-1/2 motion-safe:data-exiting:-translate-y-1/2',\n // The delays below cycle, so the height (third) starts at once like the slide.\n '[transition-property:translate,opacity,height]',\n '[transition-duration:var(--md-sys-motion-spring-spatial-default-duration),100ms,var(--md-sys-motion-spring-spatial-default-duration)]',\n '[transition-timing-function:var(--md-sys-motion-spring-spatial-default-easing),cubic-bezier(0.3,0,1,1),var(--md-sys-motion-spring-spatial-default-easing)]',\n 'data-exiting:[transition-duration:var(--md-sys-motion-spring-spatial-fast-duration),100ms,var(--md-sys-motion-spring-spatial-fast-duration)]',\n 'data-exiting:[transition-timing-function:var(--md-sys-motion-spring-spatial-fast-easing),cubic-bezier(0,0,0,1),var(--md-sys-motion-spring-spatial-fast-easing)]',\n ...contentFade,\n ],\n /*\n * Full-screen view: the surface grows from the bar's bounds to the window as\n * `--m3-search-progress` runs 0 → 1, through a clip (nothing is transformed). Compose\n * clamps the full-screen progress to 0..1 (`coerceIn`), so `--m3-search-p` is clamped too:\n * the clip and the field never overshoot the window's or the bar's bounds.\n */\n fullScreenPanel: [\n 'fixed inset-0 z-(--md-sys-z-sheet) bg-[var(--_md-comp-search-view-container-color)] text-on-surface outline-none data-exiting:pointer-events-none',\n '[--m3-search-progress:1] [--m3-search-p:clamp(0,var(--m3-search-progress),1)]',\n 'motion-safe:starting:[--m3-search-progress:0] motion-safe:data-exiting:[--m3-search-progress:0]',\n '[clip-path:inset(calc((1_-_var(--m3-search-p))_*_var(--m3-search-top))_calc((1_-_var(--m3-search-p))_*_var(--m3-search-right))_calc((1_-_var(--m3-search-p))_*_var(--m3-search-bottom))_calc((1_-_var(--m3-search-p))_*_var(--m3-search-left))_round_calc((1_-_var(--m3-search-p))_*_28px))]',\n 'motion-safe:[transition-property:--m3-search-progress]',\n 'motion-safe:[transition-duration:var(--md-sys-motion-spring-spatial-slow-duration)]',\n 'motion-safe:[transition-timing-function:var(--md-sys-motion-spring-spatial-slow-easing)]',\n 'motion-safe:data-exiting:[transition-duration:var(--md-sys-motion-spring-spatial-default-duration)]',\n 'motion-safe:data-exiting:[transition-timing-function:var(--md-sys-motion-spring-spatial-default-easing)]',\n // Reduced motion: the view fades in and out over the window instead of growing.\n 'motion-reduce:starting:opacity-0 motion-reduce:data-exiting:opacity-0',\n 'motion-reduce:[transition-property:opacity]',\n 'motion-reduce:[transition-duration:var(--md-sys-motion-spring-effects-fast-duration)]',\n 'motion-reduce:[transition-timing-function:var(--md-sys-motion-spring-effects-fast-easing)]',\n ],\n // Compose's full-screen field is 56px whatever the bar's size, 8px down and 16px from each\n // side; it moves and grows there from the bar's bounds with the surface.\n fullScreenHeader: [\n 'absolute',\n 'start-[calc(var(--m3-search-start)_+_var(--m3-search-p)_*_(16px_-_var(--m3-search-start)))]',\n 'top-[calc(var(--m3-search-top)_+_var(--m3-search-p)_*_(8px_-_var(--m3-search-top)))]',\n 'w-[calc(var(--m3-search-width)_+_var(--m3-search-p)_*_(100%_-_32px_-_var(--m3-search-width)))]',\n 'h-[calc(var(--m3-search-height)_+_var(--m3-search-p)_*_(56px_-_var(--m3-search-height)))]',\n ],\n fullScreenContent: [\n 'absolute inset-x-0 top-[72px] bottom-0 overflow-y-auto overscroll-contain',\n '[transition-property:opacity] [transition-duration:100ms]',\n '[transition-timing-function:cubic-bezier(0.3,0,1,1)]',\n 'data-exiting:[transition-timing-function:cubic-bezier(0,0,0,1)]',\n ...contentFade,\n ],\n },\n variants: {\n /**\n * Inside a search app bar the bar fills the free space up to 312px, then grows to half\n * of it (M3), up to 720px.\n */\n inAppBar: { true: { root: 'w-[max(min(100%,312px),50%)]' }, false: {} },\n // 4px from a tap target (M3), else 16px from the pill's edge.\n hasLeading: { true: { input: 'ps-[4px]' }, false: { input: 'ps-[16px]' } },\n hasTrailing: { true: { input: 'pe-[4px]' }, false: { input: 'pe-[16px]' } },\n /**\n * Container colour: the bar's own; in a search app bar `surface-container`, and one\n * step higher than the bar once content scrolls under it.\n */\n tone: {\n default: { field: 'bg-[var(--_md-comp-search-bar-container-color)]' },\n appBar: { field: 'bg-surface-container' },\n scrolled: { field: 'bg-surface-container-highest' },\n },\n /** Centre the placeholder and the query while the bar is collapsed. */\n textAlign: { start: {}, center: { input: 'text-center' } },\n /**\n * A disabled `SearchField`: M3's disabled colours, the content `on-surface` at 38% and the\n * container at 12%; forced colours draw it in `GrayText`.\n */\n disabled: {\n true: {\n field: 'bg-on-surface/12 forced-colors:border-[GrayText]',\n leading: 'text-on-surface/38 forced-colors:text-[GrayText]',\n trailing: 'text-on-surface/38 forced-colors:text-[GrayText]',\n input:\n 'cursor-default text-on-surface/38 placeholder:text-on-surface/38 forced-colors:text-[GrayText]',\n },\n false: {},\n },\n },\n defaultVariants: {\n inAppBar: false,\n hasLeading: false,\n hasTrailing: false,\n tone: 'default',\n textAlign: 'start',\n disabled: false,\n },\n});\n\n/**\n * Variant definitions for {@link SearchField}'s root. Its pill and the parts in it are the search\n * bar's ({@link searchBarStyles}), so the two can't drift.\n */\nexport const searchFieldStyles = tv({\n slots: {\n // 56px, the search bar's height, less 4px a density step (44px at -3), filling its\n // container: it filters a page, a list or a pane in place. A consumer `w-*` or `h-*` wins.\n root: 'flex h-[calc(56px+var(--md-sys-density,0)*4px)] w-full min-w-0',\n // The 48px icon slots keep to a dense pill's height; their buttons keep 48px targets.\n slot: 'max-h-full',\n },\n variants: {},\n});\n\n/** Variant definitions for {@link SearchAppBar}. */\nexport const searchAppBarStyles = tv({\n slots: {\n root: [\n 'flex min-h-[64px] w-full items-center text-on-surface',\n 'bg-[var(--_md-comp-app-bar-container-color)] data-scrolled:bg-[var(--_md-comp-app-bar-on-scroll-container-color)]',\n '[transition-property:background-color]',\n '[transition-duration:var(--md-sys-motion-spring-effects-default-duration)]',\n '[transition-timing-function:var(--md-sys-motion-spring-effects-default-easing)]',\n ],\n // Icon buttons take each slot's content colour.\n navigation:\n 'flex shrink-0 items-center ps-[8px] pe-[4px] text-[color:var(--_md-comp-app-bar-leading-icon-color)] [--md-comp-icon-button-icon-color:currentColor]',\n // Centres the search bar in the free space, 8px from its neighbours and 4px vertically.\n search: 'flex min-w-0 flex-1 justify-center ps-[8px] pe-[8px] pt-[4px] pb-[4px]',\n actions:\n 'flex shrink-0 items-center gap-[8px] ps-[4px] pe-[8px] text-[color:var(--_md-comp-app-bar-trailing-icon-color)] [--md-comp-icon-button-icon-color:currentColor]',\n },\n variants: {\n scrolling: {\n true: { root: 'sticky top-(--m3-app-bar-offset) z-(--md-sys-z-sticky)' },\n false: {},\n },\n },\n defaultVariants: { scrolling: false },\n});\n\n/**\n * The variants of {@link searchBarStyles}, 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 SearchBarStyleProps = VariantProps<typeof searchBarStyles>;\n/**\n * The variants of {@link searchAppBarStyles}, 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 SearchAppBarStyleProps = VariantProps<typeof searchAppBarStyles>;\n/**\n * The variants of {@link searchFieldStyles}, for building on its styles. They are the recipe's keys, not\n * component props: their names follow the classes and may change.\n */\nexport type SearchFieldStyleProps = VariantProps<typeof searchFieldStyles>;\n"],"mappings":";;AA2CA,MAAM,cAAc,CAClB,yDAEA,kEACF;;AAGA,MAAa,kBAAkB,GAAG;CAChC,OAAO;EAEL,MAAM;EAIN,OAAO,CACL,iIACA,mFACF;EAIA,SACE;EAIF,UAAU,CACR,2MACA,8DACF;EACA,OAAO;GACL;GACA;GACA;EACF;EACA,OAAO;GACL;GACA;GACA;GACA;GACA;EACF;EAUA,aAAa;GAGX;GACA;GACA;GACA;GACA;GACA;GAEA;GACA;GACA;EACF;EAIA,UAAU;EACV,cACE;EACF,UAAU;GACR;GAEA;GAIA;GACA;GACA;GAEA;GAEA;GACA;GACA;GACA;GACA;GACA,GAAG;EACL;EAOA,iBAAiB;GACf;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GAEA;GACA;GACA;GACA;EACF;EAGA,kBAAkB;GAChB;GACA;GACA;GACA;GACA;EACF;EACA,mBAAmB;GACjB;GACA;GACA;GACA;GACA,GAAG;EACL;CACF;CACA,UAAU;;;;;EAKR,UAAU;GAAE,MAAM,EAAE,MAAM,+BAA+B;GAAG,OAAO,CAAC;EAAE;EAEtE,YAAY;GAAE,MAAM,EAAE,OAAO,WAAW;GAAG,OAAO,EAAE,OAAO,YAAY;EAAE;EACzE,aAAa;GAAE,MAAM,EAAE,OAAO,WAAW;GAAG,OAAO,EAAE,OAAO,YAAY;EAAE;;;;;EAK1E,MAAM;GACJ,SAAS,EAAE,OAAO,kDAAkD;GACpE,QAAQ,EAAE,OAAO,uBAAuB;GACxC,UAAU,EAAE,OAAO,+BAA+B;EACpD;;EAEA,WAAW;GAAE,OAAO,CAAC;GAAG,QAAQ,EAAE,OAAO,cAAc;EAAE;;;;;EAKzD,UAAU;GACR,MAAM;IACJ,OAAO;IACP,SAAS;IACT,UAAU;IACV,OACE;GACJ;GACA,OAAO,CAAC;EACV;CACF;CACA,iBAAiB;EACf,UAAU;EACV,YAAY;EACZ,aAAa;EACb,MAAM;EACN,WAAW;EACX,UAAU;CACZ;AACF,CAAC;;;;;AAMD,MAAa,oBAAoB,GAAG;CAClC,OAAO;EAGL,MAAM;EAEN,MAAM;CACR;CACA,UAAU,CAAC;AACb,CAAC;;AAGD,MAAa,qBAAqB,GAAG;CACnC,OAAO;EACL,MAAM;GACJ;GACA;GACA;GACA;GACA;EACF;EAEA,YACE;EAEF,QAAQ;EACR,SACE;CACJ;CACA,UAAU,EACR,WAAW;EACT,MAAM,EAAE,MAAM,yDAAyD;EACvE,OAAO,CAAC;CACV,EACF;CACA,iBAAiB,EAAE,WAAW,MAAM;AACtC,CAAC"}
@@ -4,9 +4,10 @@ import { FOCUSABLE_DISABLED_OPTIONS } from "../../utils/focusable-disabled.js";
4
4
  import { menuStyles } from "../menu/menu-styles.js";
5
5
  import { OptionList } from "./option-list.js";
6
6
  import { searchBarStyles } from "../search/search-styles.js";
7
+ import { SearchFieldFrame } from "../search/search-field-frame.js";
7
8
  import { useCallback, useEffect, useRef, useState } from "react";
8
9
  import { jsx, jsxs } from "react/jsx-runtime";
9
- import { useAutocomplete, useFocusRing, useSearchField } from "react-aria";
10
+ import { useAutocomplete, useSearchField } from "react-aria";
10
11
  import { useListState } from "react-stately";
11
12
  //#region src/components/select/search-list.tsx
12
13
  /** Material Symbols `search`. */
@@ -71,10 +72,6 @@ function SearchList({ listProps, search, onSearchChange, filter, autoFocusSearch
71
72
  value: search,
72
73
  setValue: onSearchChange
73
74
  }, inputRef);
74
- const { focusProps, isFocusVisible } = useFocusRing({
75
- within: true,
76
- isTextInput: true
77
- });
78
75
  useEffect(() => {
79
76
  if (autoFocusSearch) inputRef.current?.focus();
80
77
  }, [autoFocusSearch]);
@@ -90,26 +87,24 @@ function SearchList({ listProps, search, onSearchChange, filter, autoFocusSearch
90
87
  className: cn("flex min-h-0 flex-col", embedded ? "flex-1" : [menu.group(), "max-h-[inherit] overflow-hidden"]),
91
88
  children: [/* @__PURE__ */ jsx("div", {
92
89
  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
- children: /* @__PURE__ */ jsxs("div", {
94
- ...focusProps,
95
- "data-tokens": "md-comp-search-bar",
96
- "data-focus-visible": isFocusVisible || void 0,
90
+ children: /* @__PURE__ */ jsx(SearchFieldFrame, {
97
91
  onPointerDown: (event) => {
98
92
  if (event.target !== event.currentTarget) return;
99
93
  event.preventDefault();
100
94
  inputRef.current?.focus();
101
95
  },
102
- className: field.field({ class: cn(denseSearchTarget, classNames?.search) }),
103
- children: [/* @__PURE__ */ jsx("span", {
96
+ className: cn(denseSearchTarget, classNames?.search),
97
+ leading: /* @__PURE__ */ jsx("span", {
104
98
  "data-slot": "leading",
105
99
  className: field.leading({ class: "h-full" }),
106
100
  children: /* @__PURE__ */ jsx(SearchIcon, {})
107
- }), /* @__PURE__ */ jsx("input", {
101
+ }),
102
+ children: /* @__PURE__ */ jsx("input", {
108
103
  ...inputProps,
109
104
  ref: inputRef,
110
105
  "data-slot": "input",
111
106
  className: field.input()
112
- })]
107
+ })
113
108
  })
114
109
  }), /* @__PURE__ */ jsx(OptionList, {
115
110
  embedded: true,
@@ -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. 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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"}
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, 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 { SearchFieldFrame } from '../search/search-field-frame';\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\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 {/* The search bar's pill, as `SearchField` draws it, at 48px. */}\n <SearchFieldFrame\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={cn(denseSearchTarget, classNames?.search)}\n leading={\n <span data-slot=\"leading\" className={field.leading({ class: 'h-full' })}>\n <SearchIcon />\n </span>\n }\n >\n <input {...inputProps} ref={inputRef} data-slot=\"input\" className={field.input()} />\n </SearchFieldFrame>\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":";;;;;;;;;;;;;AAoBA,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;CAEA,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,CAaE,oBAAC,OAAD;GACE,WAAW,GACT,qEACA,YAAY,6EACd;GAGA,UAAA,oBAAC,kBAAD;IAEE,gBAAgB,UAAU;KACxB,IAAI,MAAM,WAAW,MAAM,eAAe;KAC1C,MAAM,eAAe;KACrB,SAAS,SAAS,MAAM;IAC1B;IACA,WAAW,GAAG,mBAAmB,YAAY,MAAM;IACnD,SACE,oBAAC,QAAD;KAAM,aAAU;KAAU,WAAW,MAAM,QAAQ,EAAE,OAAO,SAAS,CAAC;KACpE,UAAA,oBAAC,YAAD,CAAa,CAAA;IACT,CAAA;IAGR,UAAA,oBAAC,SAAD;KAAO,GAAI;KAAY,KAAK;KAAU,aAAU;KAAQ,WAAW,MAAM,MAAM;IAAI,CAAA;GACnE,CAAA;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"}
@@ -9,6 +9,8 @@ export interface SideSheetClassNames {
9
9
  container?: string;
10
10
  /** The dimmed layer behind a modal sheet. */
11
11
  scrim?: string;
12
+ /** The box around a standard sheet's surface, which holds a detached sheet's margins. */
13
+ frame?: string;
12
14
  /** The sheet's surface: its colour, corners, width and shadow. */
13
15
  panel?: string;
14
16
  /** The header row: back button, title, actions and close button. */
@@ -268,7 +268,7 @@ function StandardSideSheet({ state, detached = false, edge = "end", swipeable: _
268
268
  className: styles.standardClip(),
269
269
  children: /* @__PURE__ */ jsx("div", {
270
270
  ref: frameRef,
271
- className: styles.standardFrame(),
271
+ className: styles.standardFrame({ class: body.classNames?.frame }),
272
272
  children: /* @__PURE__ */ jsx("div", {
273
273
  "data-slot": "panel",
274
274
  className: styles.panel({ class: body.classNames?.panel }),
@@ -1 +1 @@
1
- {"version":3,"file":"SideSheet.js","names":["Overlay"],"sources":["../../../src/components/sheet/SideSheet.tsx"],"sourcesContent":["'use client';\n\nimport {\n useContext,\n useEffect,\n useId,\n useLayoutEffect,\n useRef,\n useState,\n type ComponentPropsWithRef,\n type CSSProperties,\n type FocusEvent,\n type ReactElement,\n type ReactNode,\n type Ref,\n} from 'react';\nimport {\n mergeProps,\n useDialog,\n useModalOverlay,\n useObjectRef,\n type AriaDialogProps,\n} from 'react-aria';\nimport { useOverlayTriggerState, type OverlayTriggerState } from 'react-stately';\nimport { Overlay } from '../../primitives/Overlay';\nimport { TriggerContext } from '../../primitives/TriggerContext';\nimport { usePresence } from '../../primitives/use-presence';\nimport { closedByOverlayOptions, type ClosedBy } from '../../utils/closed-by';\nimport { cn } from '../../utils/cn';\nimport { focusFirstInteractive, tabbables } from '../../utils/focus';\nimport { DensityZeroButtons } from '../button/DensityZeroButtons';\nimport { IconButton } from '../icon-button/IconButton';\nimport { BackIcon, CloseIcon } from './sheet-icons';\nimport { SHEET_EXIT_PROPERTIES, sideSheetStyles } from './sheet-styles';\nimport { SheetTriggerContext } from './SheetTrigger';\nimport { useSideSwipe } from './use-side-swipe';\nimport { mergeDataTokens } from '../../utils/merge-data-tokens';\n\nexport interface SideSheetClassNames {\n /**\n * The outermost element, which places the sheet (where `className` and `style` go too):\n * a modal sheet's box `fixed` to the window's `edge`, or a standard sheet's layout box.\n */\n container?: string;\n /** The dimmed layer behind a modal sheet. */\n scrim?: string;\n /** The sheet's surface: its colour, corners, width and shadow. */\n panel?: string;\n /** The header row: back button, title, actions and close button. */\n header?: string;\n /** The back button. */\n back?: string;\n /** The headline. */\n title?: string;\n /** The end of the header: `actions` and the close button. */\n headerActions?: string;\n /** The close button. */\n close?: string;\n /** The scrollable content area. */\n content?: string;\n /** The bottom row of action buttons. */\n footer?: string;\n}\n\n/** Words the side sheet announces. */\nexport interface SideSheetLabels {\n /** The close button. @default \"Close\" */\n close: string;\n /** The back button (with `onBack`). @default \"Back\" */\n back: string;\n}\n\nconst DEFAULT_LABELS: SideSheetLabels = { close: 'Close', back: 'Back' };\n\ninterface SideSheetRenderProps {\n /** Closes a modal sheet, or reports `onOpenChange(false)` for a standard one. */\n close: () => void;\n}\n\ninterface SideSheetOwnProps {\n /**\n * `modal` opens over a scrim as a dialog; `standard` sits in the layout beside the\n * content and opens and closes its width. @default \"modal\"\n */\n variant?: 'modal' | 'standard';\n /** Float 16px from the edges with corners all round. @default false */\n detached?: boolean;\n /**\n * The window edge the sheet is anchored to: `end` (the right in a left-to-right page, the\n * left in RTL, as M3 asks) or `start`. A modal sheet slides in from it and swipes out toward\n * it; a standard sheet faces it, with its divider on the other side. The root's `data-edge`\n * says which.\n * @default \"end\"\n */\n edge?: 'start' | 'end';\n /**\n * Whether a touch or pen swipe toward the sheet's edge closes a modal sheet (past half its\n * width, or a fling). The close button, Escape and the scrim still close it either way, and\n * a mouse drag never moves it. Off when `closedBy` is `\"none\"`; a standard sheet ignores\n * it. To keep the sheet open after a swipe (unsaved changes), control `open` and leave it\n * `true`: the sheet springs back.\n * @default true\n */\n swipeable?: boolean;\n /** Whether the sheet is open (controlled). */\n open?: boolean;\n /** Whether the sheet starts open when uncontrolled. @default false, or true when standard */\n defaultOpen?: boolean;\n /** Called when the sheet opens or closes. */\n onOpenChange?: (open: boolean) => void;\n /** The headline. It names the sheet. */\n title?: ReactNode;\n /** Shows a back button before the title, for a sheet with several levels. */\n onBack?: () => void;\n /** Header controls before the close button. */\n actions?: ReactNode | ((props: SideSheetRenderProps) => ReactNode);\n /** Bottom action buttons, start-aligned, e.g. Save and Cancel. */\n footer?: ReactNode | ((props: SideSheetRenderProps) => ReactNode);\n /** A divider between the content and `footer`. @default false */\n footerDivider?: boolean;\n /** A divider between a standard sheet and the content beside it. @default true */\n divider?: boolean;\n /**\n * How the user can close a modal sheet, as HTML's `<dialog closedby>`: `any` (Escape, a\n * press on the scrim or a swipe), `closerequest` (Escape or a swipe) or `none` (only its\n * buttons or the app).\n * @default \"any\"\n */\n closedBy?: ClosedBy;\n /** Replaces the close button's icon. */\n closeIcon?: ReactElement;\n /** Replaces the back button's icon. */\n backIcon?: ReactElement;\n /** Words the sheet announces, with English defaults. */\n labels?: Partial<SideSheetLabels>;\n children?: ReactNode | ((props: SideSheetRenderProps) => ReactNode);\n /** Classes for each part, by slot. */\n classNames?: SideSheetClassNames;\n /**\n * Goes with `style` on the outermost element, the box that places the sheet, so a layout class\n * moves the whole sheet and never clips or stretches its surface. Style the surface with\n * `classNames.panel`; `ref`, `data-*` and other attributes go on the panel (a standard\n * sheet's are on its outer box).\n */\n className?: string;\n /** Goes on the outermost element, with `className`. */\n style?: CSSProperties;\n /** Ref to the panel, the sheet's surface (`role=\"dialog\"`); a standard sheet's outer box. */\n ref?: Ref<HTMLDivElement>;\n}\n\ntype Naming =\n | {\n title: ReactNode;\n /** Accessible name when there's no visible title; overrides `title` when both are set. */\n 'aria-label'?: string;\n }\n | {\n /** Accessible name when there's no visible title. */\n 'aria-label': string;\n }\n | {\n /** Id of a visible element that names the sheet, when there's no `title`. */\n 'aria-labelledby': string;\n };\n\nexport type SideSheetProps = SideSheetOwnProps &\n Naming &\n Omit<ComponentPropsWithRef<'div'>, keyof SideSheetOwnProps | 'aria-label' | 'aria-labelledby'>;\n\n/**\n * M3 side sheet: supplementary content at the end edge of the window (`edge=\"start\"` for the\n * other). A modal sheet slides in over a scrim as a dialog (Escape, a press outside or a\n * touch swipe toward its edge closes it); a standard sheet sits in the layout as a non-modal\n * dialog and opens and closes its width. Both always show a close button, as M3 requires.\n *\n * @example\n * <SheetTrigger>\n * <Button>Filters</Button>\n * <SideSheet title=\"Filters\" footer={({ close }) => <Button onPress={close}>Apply</Button>}>\n * …\n * </SideSheet>\n * </SheetTrigger>\n */\nexport function SideSheet({\n variant = 'modal',\n open,\n defaultOpen,\n onOpenChange,\n ...props\n}: SideSheetProps) {\n const trigger = useContext(SheetTriggerContext);\n const ownState = useOverlayTriggerState({\n isOpen: open,\n defaultOpen: defaultOpen ?? (variant === 'standard' ? true : undefined),\n onOpenChange,\n });\n const state = (variant === 'modal' ? trigger?.state : undefined) ?? ownState;\n const { isPresent, isExiting, exitProps } = usePresence(state.isOpen, SHEET_EXIT_PROPERTIES);\n if (variant === 'standard') return <StandardSideSheet {...props} state={state} />;\n if (!isPresent) return null;\n return (\n <Overlay exiting={isExiting}>\n <ModalSideSheet\n {...props}\n state={state}\n isExiting={isExiting}\n exitProps={exitProps}\n triggerOverlayProps={trigger?.overlayProps}\n />\n </Overlay>\n );\n}\n\ntype InnerProps = Omit<SideSheetProps, 'variant' | 'open' | 'defaultOpen' | 'onOpenChange'> & {\n state: OverlayTriggerState;\n};\n\ntype BodyProps = Pick<\n SideSheetOwnProps,\n | 'title'\n | 'onBack'\n | 'actions'\n | 'footer'\n | 'children'\n | 'closeIcon'\n | 'backIcon'\n | 'labels'\n | 'classNames'\n> & {\n titleId: string;\n close: () => void;\n styles: ReturnType<typeof sideSheetStyles>;\n contentRef?: Ref<HTMLDivElement>;\n};\n\nfunction SheetBody({\n title,\n titleId,\n onBack,\n actions,\n footer,\n children,\n closeIcon,\n backIcon,\n labels: labelsProp,\n close,\n styles,\n classNames,\n contentRef,\n}: BodyProps) {\n const labels = { ...DEFAULT_LABELS, ...labelsProp };\n const renderProps = { close };\n const render = (slot: SideSheetOwnProps['children']) =>\n typeof slot === 'function' ? slot(renderProps) : slot;\n return (\n <>\n <div className={styles.header({ class: classNames?.header })}>\n {onBack && (\n <IconButton\n variant=\"standard\"\n density={0}\n icon={backIcon ?? <BackIcon />}\n aria-label={labels.back}\n onPress={onBack}\n className={classNames?.back}\n />\n )}\n {title != null && (\n <h2 id={titleId} className={styles.title({ class: classNames?.title })}>\n {title}\n </h2>\n )}\n <div className={styles.headerActions({ class: classNames?.headerActions })}>\n <DensityZeroButtons>{render(actions)}</DensityZeroButtons>\n <IconButton\n variant=\"standard\"\n density={0}\n icon={closeIcon ?? <CloseIcon />}\n aria-label={labels.close}\n onPress={close}\n className={classNames?.close}\n />\n </div>\n </div>\n <div ref={contentRef} className={styles.content({ class: classNames?.content })}>\n {render(children)}\n </div>\n {footer != null && (\n <div className={styles.footer({ class: classNames?.footer })}>{render(footer)}</div>\n )}\n </>\n );\n}\n\nconst BODY_KEYS = [\n 'title',\n 'onBack',\n 'actions',\n 'footer',\n 'children',\n 'closeIcon',\n 'backIcon',\n 'labels',\n 'classNames',\n] as const;\n\n/** Splits the header, body and footer props from those of the panel element. */\nfunction splitBodyProps<T extends Record<string, unknown>>(props: T) {\n const body: Record<string, unknown> = {};\n const rest: Record<string, unknown> = { ...props };\n for (const key of BODY_KEYS) {\n body[key] = rest[key];\n delete rest[key];\n }\n return {\n body: body as Omit<BodyProps, 'titleId' | 'close' | 'styles' | 'contentRef'>,\n rest: rest as Omit<T, (typeof BODY_KEYS)[number]>,\n };\n}\n\n/** `aria-labelledby` for the panel: the consumer's, else the title's unless `aria-label` names it. */\nfunction labelledBy(props: Record<string, unknown>, hasTitle: boolean, titleId: string) {\n const own = props['aria-labelledby'] as string | undefined;\n return own ?? (props['aria-label'] == null && hasTitle ? titleId : undefined);\n}\n\nfunction ModalSideSheet({\n state,\n isExiting,\n exitProps,\n triggerOverlayProps,\n detached = false,\n edge = 'end',\n swipeable = true,\n closedBy = 'any',\n divider: _divider,\n footerDivider = false,\n className,\n style,\n ref,\n ...props\n}: InnerProps & {\n isExiting: boolean;\n exitProps: ReturnType<typeof usePresence>['exitProps'];\n triggerOverlayProps?: { id?: string };\n}) {\n const { body, rest } = splitBodyProps(props);\n const panelRef = useObjectRef(ref as Ref<HTMLDivElement>);\n const motionRef = useRef<HTMLDivElement>(null);\n const contentRef = useRef<HTMLDivElement>(null);\n const titleId = useId();\n // `closedBy=\"none\"` leaves closing to the app, so a swipe doesn't close it either.\n const canSwipe = swipeable && closedBy !== 'none';\n const styles = sideSheetStyles({\n variant: 'modal',\n edge,\n swipeable: canSwipe,\n detached,\n hasBack: body.onBack != null,\n footerDivider,\n });\n const { panelHandlers, motionProps, dragging } = useSideSwipe({\n enabled: canSwipe && !isExiting,\n edge,\n isOpen: state.isOpen,\n close: state.close,\n panelRef,\n motionRef,\n contentRef,\n });\n const { modalProps, underlayProps } = useModalOverlay(\n closedByOverlayOptions(closedBy, isExiting),\n state,\n panelRef,\n );\n const { dialogProps } = useDialog(\n {\n ...(rest as AriaDialogProps),\n 'aria-labelledby': labelledBy(rest, body.title != null, titleId),\n },\n panelRef,\n );\n // M3: focus lands on the first (non-disabled) icon button, as in a Dialog, not the panel.\n useEffect(() => focusFirstInteractive(panelRef.current), [panelRef]);\n const exiting = isExiting || undefined;\n return (\n <>\n <div\n aria-hidden=\"true\"\n data-exiting={exiting}\n className={styles.scrim({ class: body.classNames?.scrim })}\n />\n <div {...underlayProps} className=\"fixed inset-0 z-(--md-sys-z-sheet)\">\n <div\n style={style}\n className={styles.container({ class: cn(body.classNames?.container, className) })}\n >\n <div\n {...mergeProps(exitProps, motionProps)}\n ref={motionRef}\n data-exiting={exiting}\n className={styles.motion()}\n >\n <div\n {...mergeProps(\n rest,\n triggerOverlayProps ?? {},\n modalProps,\n dialogProps,\n panelHandlers,\n )}\n ref={panelRef}\n // The component tokens' hooks (sheet-tokens.ts); `data-variant` and `data-edge`\n // are also public.\n data-tokens={mergeDataTokens('md-comp-sheet-side', rest)}\n data-variant=\"modal\"\n data-edge={edge}\n data-detached={detached || undefined}\n data-dragging={dragging || undefined}\n data-exiting={exiting}\n className={styles.panel({ class: body.classNames?.panel })}\n >\n <TriggerContext value={null}>\n <SheetBody\n {...body}\n titleId={titleId}\n close={state.close}\n styles={styles}\n contentRef={contentRef}\n />\n </TriggerContext>\n </div>\n </div>\n </div>\n </div>\n </>\n );\n}\n\nfunction StandardSideSheet({\n state,\n detached = false,\n edge = 'end',\n swipeable: _swipeable,\n divider = true,\n footerDivider = false,\n closedBy: _closedBy,\n className,\n style,\n ref,\n ...props\n}: InnerProps) {\n const { body, rest } = splitBodyProps(props);\n const titleId = useId();\n const styles = sideSheetStyles({\n variant: 'standard',\n edge,\n detached,\n divider,\n hasBack: body.onBack != null,\n footerDivider,\n });\n const closed = !state.isOpen || undefined;\n const rootRef = useObjectRef(ref as Ref<HTMLDivElement>);\n const frameRef = useRef<HTMLDivElement>(null);\n // The sheet's open width, measured, so the root's width (not a grid fraction, which takes\n // only part of the shrinking free space and left the panel floating off its edge) moves\n // between it and 0. Until it's measured (the server's HTML) the open root is `auto` wide.\n const [width, setWidth] = useState<number | null>(null);\n useLayoutEffect(() => {\n const frame = frameRef.current;\n if (!frame) return;\n // The layout width, not the box on screen, which a consumer transform scales or turns.\n const measure = () => setWidth(frame.offsetWidth);\n measure();\n if (typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver(measure);\n observer.observe(frame);\n return () => observer.disconnect();\n }, []);\n // Focus that came from outside returns there when the sheet closes with focus inside (its\n // close button, Escape in content, or the app closing it): the closed sheet is `inert`, and\n // focus would otherwise drop to the page.\n const returnFocus = useRef<HTMLElement | null>(null);\n useLayoutEffect(() => {\n const root = rootRef.current;\n const active = root?.ownerDocument.activeElement;\n if (state.isOpen || !root || !(active instanceof HTMLElement) || !root.contains(active)) return;\n const saved = returnFocus.current;\n const target =\n saved?.isConnected && !root.contains(saved) && !saved.closest('[inert]')\n ? saved\n : (tabbables(root.ownerDocument.body, (el) => root.contains(el))\n .filter((el) => el.compareDocumentPosition(root) & Node.DOCUMENT_POSITION_FOLLOWING)\n .at(-1) ?? null);\n if (target) target.focus({ preventScroll: true });\n else active.blur();\n }, [state.isOpen, rootRef]);\n const rootStyle = {\n ...style,\n ...(width === null ? {} : { '--m3-side-sheet-width': `${width}px` }),\n } as CSSProperties;\n // A non-modal dialog (m3.material.io: the side sheet's role is dialog); focus isn't moved.\n return (\n <div\n {...rest}\n onFocus={(event: FocusEvent<HTMLDivElement>) => {\n (rest as { onFocus?: (event: FocusEvent<HTMLDivElement>) => void }).onFocus?.(event);\n const from = event.relatedTarget;\n if (from instanceof HTMLElement && !event.currentTarget.contains(from)) {\n returnFocus.current = from;\n }\n }}\n ref={rootRef}\n role=\"dialog\"\n style={rootStyle}\n // The component tokens' hooks (sheet-tokens.ts), read by the panel inside.\n data-tokens={mergeDataTokens('md-comp-sheet-side', rest)}\n data-variant=\"standard\"\n data-edge={edge}\n data-detached={detached || undefined}\n data-closed={closed}\n inert={!state.isOpen}\n aria-labelledby={labelledBy(rest, body.title != null, titleId)}\n className={styles.standard({ class: cn(body.classNames?.container, className) })}\n >\n <div className={styles.standardClip()}>\n <div ref={frameRef} className={styles.standardFrame()}>\n <div data-slot=\"panel\" className={styles.panel({ class: body.classNames?.panel })}>\n <SheetBody {...body} titleId={titleId} close={state.close} styles={styles} />\n </div>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"SideSheet.js","names":["Overlay"],"sources":["../../../src/components/sheet/SideSheet.tsx"],"sourcesContent":["'use client';\n\nimport {\n useContext,\n useEffect,\n useId,\n useLayoutEffect,\n useRef,\n useState,\n type ComponentPropsWithRef,\n type CSSProperties,\n type FocusEvent,\n type ReactElement,\n type ReactNode,\n type Ref,\n} from 'react';\nimport {\n mergeProps,\n useDialog,\n useModalOverlay,\n useObjectRef,\n type AriaDialogProps,\n} from 'react-aria';\nimport { useOverlayTriggerState, type OverlayTriggerState } from 'react-stately';\nimport { Overlay } from '../../primitives/Overlay';\nimport { TriggerContext } from '../../primitives/TriggerContext';\nimport { usePresence } from '../../primitives/use-presence';\nimport { closedByOverlayOptions, type ClosedBy } from '../../utils/closed-by';\nimport { cn } from '../../utils/cn';\nimport { focusFirstInteractive, tabbables } from '../../utils/focus';\nimport { DensityZeroButtons } from '../button/DensityZeroButtons';\nimport { IconButton } from '../icon-button/IconButton';\nimport { BackIcon, CloseIcon } from './sheet-icons';\nimport { SHEET_EXIT_PROPERTIES, sideSheetStyles } from './sheet-styles';\nimport { SheetTriggerContext } from './SheetTrigger';\nimport { useSideSwipe } from './use-side-swipe';\nimport { mergeDataTokens } from '../../utils/merge-data-tokens';\n\nexport interface SideSheetClassNames {\n /**\n * The outermost element, which places the sheet (where `className` and `style` go too):\n * a modal sheet's box `fixed` to the window's `edge`, or a standard sheet's layout box.\n */\n container?: string;\n /** The dimmed layer behind a modal sheet. */\n scrim?: string;\n /** The box around a standard sheet's surface, which holds a detached sheet's margins. */\n frame?: string;\n /** The sheet's surface: its colour, corners, width and shadow. */\n panel?: string;\n /** The header row: back button, title, actions and close button. */\n header?: string;\n /** The back button. */\n back?: string;\n /** The headline. */\n title?: string;\n /** The end of the header: `actions` and the close button. */\n headerActions?: string;\n /** The close button. */\n close?: string;\n /** The scrollable content area. */\n content?: string;\n /** The bottom row of action buttons. */\n footer?: string;\n}\n\n/** Words the side sheet announces. */\nexport interface SideSheetLabels {\n /** The close button. @default \"Close\" */\n close: string;\n /** The back button (with `onBack`). @default \"Back\" */\n back: string;\n}\n\nconst DEFAULT_LABELS: SideSheetLabels = { close: 'Close', back: 'Back' };\n\ninterface SideSheetRenderProps {\n /** Closes a modal sheet, or reports `onOpenChange(false)` for a standard one. */\n close: () => void;\n}\n\ninterface SideSheetOwnProps {\n /**\n * `modal` opens over a scrim as a dialog; `standard` sits in the layout beside the\n * content and opens and closes its width. @default \"modal\"\n */\n variant?: 'modal' | 'standard';\n /** Float 16px from the edges with corners all round. @default false */\n detached?: boolean;\n /**\n * The window edge the sheet is anchored to: `end` (the right in a left-to-right page, the\n * left in RTL, as M3 asks) or `start`. A modal sheet slides in from it and swipes out toward\n * it; a standard sheet faces it, with its divider on the other side. The root's `data-edge`\n * says which.\n * @default \"end\"\n */\n edge?: 'start' | 'end';\n /**\n * Whether a touch or pen swipe toward the sheet's edge closes a modal sheet (past half its\n * width, or a fling). The close button, Escape and the scrim still close it either way, and\n * a mouse drag never moves it. Off when `closedBy` is `\"none\"`; a standard sheet ignores\n * it. To keep the sheet open after a swipe (unsaved changes), control `open` and leave it\n * `true`: the sheet springs back.\n * @default true\n */\n swipeable?: boolean;\n /** Whether the sheet is open (controlled). */\n open?: boolean;\n /** Whether the sheet starts open when uncontrolled. @default false, or true when standard */\n defaultOpen?: boolean;\n /** Called when the sheet opens or closes. */\n onOpenChange?: (open: boolean) => void;\n /** The headline. It names the sheet. */\n title?: ReactNode;\n /** Shows a back button before the title, for a sheet with several levels. */\n onBack?: () => void;\n /** Header controls before the close button. */\n actions?: ReactNode | ((props: SideSheetRenderProps) => ReactNode);\n /** Bottom action buttons, start-aligned, e.g. Save and Cancel. */\n footer?: ReactNode | ((props: SideSheetRenderProps) => ReactNode);\n /** A divider between the content and `footer`. @default false */\n footerDivider?: boolean;\n /** A divider between a standard sheet and the content beside it. @default true */\n divider?: boolean;\n /**\n * How the user can close a modal sheet, as HTML's `<dialog closedby>`: `any` (Escape, a\n * press on the scrim or a swipe), `closerequest` (Escape or a swipe) or `none` (only its\n * buttons or the app).\n * @default \"any\"\n */\n closedBy?: ClosedBy;\n /** Replaces the close button's icon. */\n closeIcon?: ReactElement;\n /** Replaces the back button's icon. */\n backIcon?: ReactElement;\n /** Words the sheet announces, with English defaults. */\n labels?: Partial<SideSheetLabels>;\n children?: ReactNode | ((props: SideSheetRenderProps) => ReactNode);\n /** Classes for each part, by slot. */\n classNames?: SideSheetClassNames;\n /**\n * Goes with `style` on the outermost element, the box that places the sheet, so a layout class\n * moves the whole sheet and never clips or stretches its surface. Style the surface with\n * `classNames.panel`; `ref`, `data-*` and other attributes go on the panel (a standard\n * sheet's are on its outer box).\n */\n className?: string;\n /** Goes on the outermost element, with `className`. */\n style?: CSSProperties;\n /** Ref to the panel, the sheet's surface (`role=\"dialog\"`); a standard sheet's outer box. */\n ref?: Ref<HTMLDivElement>;\n}\n\ntype Naming =\n | {\n title: ReactNode;\n /** Accessible name when there's no visible title; overrides `title` when both are set. */\n 'aria-label'?: string;\n }\n | {\n /** Accessible name when there's no visible title. */\n 'aria-label': string;\n }\n | {\n /** Id of a visible element that names the sheet, when there's no `title`. */\n 'aria-labelledby': string;\n };\n\nexport type SideSheetProps = SideSheetOwnProps &\n Naming &\n Omit<ComponentPropsWithRef<'div'>, keyof SideSheetOwnProps | 'aria-label' | 'aria-labelledby'>;\n\n/**\n * M3 side sheet: supplementary content at the end edge of the window (`edge=\"start\"` for the\n * other). A modal sheet slides in over a scrim as a dialog (Escape, a press outside or a\n * touch swipe toward its edge closes it); a standard sheet sits in the layout as a non-modal\n * dialog and opens and closes its width. Both always show a close button, as M3 requires.\n *\n * @example\n * <SheetTrigger>\n * <Button>Filters</Button>\n * <SideSheet title=\"Filters\" footer={({ close }) => <Button onPress={close}>Apply</Button>}>\n * …\n * </SideSheet>\n * </SheetTrigger>\n */\nexport function SideSheet({\n variant = 'modal',\n open,\n defaultOpen,\n onOpenChange,\n ...props\n}: SideSheetProps) {\n const trigger = useContext(SheetTriggerContext);\n const ownState = useOverlayTriggerState({\n isOpen: open,\n defaultOpen: defaultOpen ?? (variant === 'standard' ? true : undefined),\n onOpenChange,\n });\n const state = (variant === 'modal' ? trigger?.state : undefined) ?? ownState;\n const { isPresent, isExiting, exitProps } = usePresence(state.isOpen, SHEET_EXIT_PROPERTIES);\n if (variant === 'standard') return <StandardSideSheet {...props} state={state} />;\n if (!isPresent) return null;\n return (\n <Overlay exiting={isExiting}>\n <ModalSideSheet\n {...props}\n state={state}\n isExiting={isExiting}\n exitProps={exitProps}\n triggerOverlayProps={trigger?.overlayProps}\n />\n </Overlay>\n );\n}\n\ntype InnerProps = Omit<SideSheetProps, 'variant' | 'open' | 'defaultOpen' | 'onOpenChange'> & {\n state: OverlayTriggerState;\n};\n\ntype BodyProps = Pick<\n SideSheetOwnProps,\n | 'title'\n | 'onBack'\n | 'actions'\n | 'footer'\n | 'children'\n | 'closeIcon'\n | 'backIcon'\n | 'labels'\n | 'classNames'\n> & {\n titleId: string;\n close: () => void;\n styles: ReturnType<typeof sideSheetStyles>;\n contentRef?: Ref<HTMLDivElement>;\n};\n\nfunction SheetBody({\n title,\n titleId,\n onBack,\n actions,\n footer,\n children,\n closeIcon,\n backIcon,\n labels: labelsProp,\n close,\n styles,\n classNames,\n contentRef,\n}: BodyProps) {\n const labels = { ...DEFAULT_LABELS, ...labelsProp };\n const renderProps = { close };\n const render = (slot: SideSheetOwnProps['children']) =>\n typeof slot === 'function' ? slot(renderProps) : slot;\n return (\n <>\n <div className={styles.header({ class: classNames?.header })}>\n {onBack && (\n <IconButton\n variant=\"standard\"\n density={0}\n icon={backIcon ?? <BackIcon />}\n aria-label={labels.back}\n onPress={onBack}\n className={classNames?.back}\n />\n )}\n {title != null && (\n <h2 id={titleId} className={styles.title({ class: classNames?.title })}>\n {title}\n </h2>\n )}\n <div className={styles.headerActions({ class: classNames?.headerActions })}>\n <DensityZeroButtons>{render(actions)}</DensityZeroButtons>\n <IconButton\n variant=\"standard\"\n density={0}\n icon={closeIcon ?? <CloseIcon />}\n aria-label={labels.close}\n onPress={close}\n className={classNames?.close}\n />\n </div>\n </div>\n <div ref={contentRef} className={styles.content({ class: classNames?.content })}>\n {render(children)}\n </div>\n {footer != null && (\n <div className={styles.footer({ class: classNames?.footer })}>{render(footer)}</div>\n )}\n </>\n );\n}\n\nconst BODY_KEYS = [\n 'title',\n 'onBack',\n 'actions',\n 'footer',\n 'children',\n 'closeIcon',\n 'backIcon',\n 'labels',\n 'classNames',\n] as const;\n\n/** Splits the header, body and footer props from those of the panel element. */\nfunction splitBodyProps<T extends Record<string, unknown>>(props: T) {\n const body: Record<string, unknown> = {};\n const rest: Record<string, unknown> = { ...props };\n for (const key of BODY_KEYS) {\n body[key] = rest[key];\n delete rest[key];\n }\n return {\n body: body as Omit<BodyProps, 'titleId' | 'close' | 'styles' | 'contentRef'>,\n rest: rest as Omit<T, (typeof BODY_KEYS)[number]>,\n };\n}\n\n/** `aria-labelledby` for the panel: the consumer's, else the title's unless `aria-label` names it. */\nfunction labelledBy(props: Record<string, unknown>, hasTitle: boolean, titleId: string) {\n const own = props['aria-labelledby'] as string | undefined;\n return own ?? (props['aria-label'] == null && hasTitle ? titleId : undefined);\n}\n\nfunction ModalSideSheet({\n state,\n isExiting,\n exitProps,\n triggerOverlayProps,\n detached = false,\n edge = 'end',\n swipeable = true,\n closedBy = 'any',\n divider: _divider,\n footerDivider = false,\n className,\n style,\n ref,\n ...props\n}: InnerProps & {\n isExiting: boolean;\n exitProps: ReturnType<typeof usePresence>['exitProps'];\n triggerOverlayProps?: { id?: string };\n}) {\n const { body, rest } = splitBodyProps(props);\n const panelRef = useObjectRef(ref as Ref<HTMLDivElement>);\n const motionRef = useRef<HTMLDivElement>(null);\n const contentRef = useRef<HTMLDivElement>(null);\n const titleId = useId();\n // `closedBy=\"none\"` leaves closing to the app, so a swipe doesn't close it either.\n const canSwipe = swipeable && closedBy !== 'none';\n const styles = sideSheetStyles({\n variant: 'modal',\n edge,\n swipeable: canSwipe,\n detached,\n hasBack: body.onBack != null,\n footerDivider,\n });\n const { panelHandlers, motionProps, dragging } = useSideSwipe({\n enabled: canSwipe && !isExiting,\n edge,\n isOpen: state.isOpen,\n close: state.close,\n panelRef,\n motionRef,\n contentRef,\n });\n const { modalProps, underlayProps } = useModalOverlay(\n closedByOverlayOptions(closedBy, isExiting),\n state,\n panelRef,\n );\n const { dialogProps } = useDialog(\n {\n ...(rest as AriaDialogProps),\n 'aria-labelledby': labelledBy(rest, body.title != null, titleId),\n },\n panelRef,\n );\n // M3: focus lands on the first (non-disabled) icon button, as in a Dialog, not the panel.\n useEffect(() => focusFirstInteractive(panelRef.current), [panelRef]);\n const exiting = isExiting || undefined;\n return (\n <>\n <div\n aria-hidden=\"true\"\n data-exiting={exiting}\n className={styles.scrim({ class: body.classNames?.scrim })}\n />\n <div {...underlayProps} className=\"fixed inset-0 z-(--md-sys-z-sheet)\">\n <div\n style={style}\n className={styles.container({ class: cn(body.classNames?.container, className) })}\n >\n <div\n {...mergeProps(exitProps, motionProps)}\n ref={motionRef}\n data-exiting={exiting}\n className={styles.motion()}\n >\n <div\n {...mergeProps(\n rest,\n triggerOverlayProps ?? {},\n modalProps,\n dialogProps,\n panelHandlers,\n )}\n ref={panelRef}\n // The component tokens' hooks (sheet-tokens.ts); `data-variant` and `data-edge`\n // are also public.\n data-tokens={mergeDataTokens('md-comp-sheet-side', rest)}\n data-variant=\"modal\"\n data-edge={edge}\n data-detached={detached || undefined}\n data-dragging={dragging || undefined}\n data-exiting={exiting}\n className={styles.panel({ class: body.classNames?.panel })}\n >\n <TriggerContext value={null}>\n <SheetBody\n {...body}\n titleId={titleId}\n close={state.close}\n styles={styles}\n contentRef={contentRef}\n />\n </TriggerContext>\n </div>\n </div>\n </div>\n </div>\n </>\n );\n}\n\nfunction StandardSideSheet({\n state,\n detached = false,\n edge = 'end',\n swipeable: _swipeable,\n divider = true,\n footerDivider = false,\n closedBy: _closedBy,\n className,\n style,\n ref,\n ...props\n}: InnerProps) {\n const { body, rest } = splitBodyProps(props);\n const titleId = useId();\n const styles = sideSheetStyles({\n variant: 'standard',\n edge,\n detached,\n divider,\n hasBack: body.onBack != null,\n footerDivider,\n });\n const closed = !state.isOpen || undefined;\n const rootRef = useObjectRef(ref as Ref<HTMLDivElement>);\n const frameRef = useRef<HTMLDivElement>(null);\n // The sheet's open width, measured, so the root's width (not a grid fraction, which takes\n // only part of the shrinking free space and left the panel floating off its edge) moves\n // between it and 0. Until it's measured (the server's HTML) the open root is `auto` wide.\n const [width, setWidth] = useState<number | null>(null);\n useLayoutEffect(() => {\n const frame = frameRef.current;\n if (!frame) return;\n // The layout width, not the box on screen, which a consumer transform scales or turns.\n const measure = () => setWidth(frame.offsetWidth);\n measure();\n if (typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver(measure);\n observer.observe(frame);\n return () => observer.disconnect();\n }, []);\n // Focus that came from outside returns there when the sheet closes with focus inside (its\n // close button, Escape in content, or the app closing it): the closed sheet is `inert`, and\n // focus would otherwise drop to the page.\n const returnFocus = useRef<HTMLElement | null>(null);\n useLayoutEffect(() => {\n const root = rootRef.current;\n const active = root?.ownerDocument.activeElement;\n if (state.isOpen || !root || !(active instanceof HTMLElement) || !root.contains(active)) return;\n const saved = returnFocus.current;\n const target =\n saved?.isConnected && !root.contains(saved) && !saved.closest('[inert]')\n ? saved\n : (tabbables(root.ownerDocument.body, (el) => root.contains(el))\n .filter((el) => el.compareDocumentPosition(root) & Node.DOCUMENT_POSITION_FOLLOWING)\n .at(-1) ?? null);\n if (target) target.focus({ preventScroll: true });\n else active.blur();\n }, [state.isOpen, rootRef]);\n const rootStyle = {\n ...style,\n ...(width === null ? {} : { '--m3-side-sheet-width': `${width}px` }),\n } as CSSProperties;\n // A non-modal dialog (m3.material.io: the side sheet's role is dialog); focus isn't moved.\n return (\n <div\n {...rest}\n onFocus={(event: FocusEvent<HTMLDivElement>) => {\n (rest as { onFocus?: (event: FocusEvent<HTMLDivElement>) => void }).onFocus?.(event);\n const from = event.relatedTarget;\n if (from instanceof HTMLElement && !event.currentTarget.contains(from)) {\n returnFocus.current = from;\n }\n }}\n ref={rootRef}\n role=\"dialog\"\n style={rootStyle}\n // The component tokens' hooks (sheet-tokens.ts), read by the panel inside.\n data-tokens={mergeDataTokens('md-comp-sheet-side', rest)}\n data-variant=\"standard\"\n data-edge={edge}\n data-detached={detached || undefined}\n data-closed={closed}\n inert={!state.isOpen}\n aria-labelledby={labelledBy(rest, body.title != null, titleId)}\n className={styles.standard({ class: cn(body.classNames?.container, className) })}\n >\n <div className={styles.standardClip()}>\n <div ref={frameRef} className={styles.standardFrame({ class: body.classNames?.frame })}>\n <div data-slot=\"panel\" className={styles.panel({ class: body.classNames?.panel })}>\n <SheetBody {...body} titleId={titleId} close={state.close} styles={styles} />\n </div>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA0EA,MAAM,iBAAkC;CAAE,OAAO;CAAS,MAAM;AAAO;;;;;;;;;;;;;;;AAgHvE,SAAgB,UAAU,EACxB,UAAU,SACV,MACA,aACA,cACA,GAAG,SACc;CACjB,MAAM,UAAU,WAAW,mBAAmB;CAC9C,MAAM,WAAW,uBAAuB;EACtC,QAAQ;EACR,aAAa,gBAAgB,YAAY,aAAa,OAAO,KAAA;EAC7D;CACF,CAAC;CACD,MAAM,SAAS,YAAY,UAAU,SAAS,QAAQ,KAAA,MAAc;CACpE,MAAM,EAAE,WAAW,WAAW,cAAc,YAAY,MAAM,QAAQ,qBAAqB;CAC3F,IAAI,YAAY,YAAY,OAAO,oBAAC,mBAAD;EAAmB,GAAI;EAAc;CAAQ,CAAA;CAChF,IAAI,CAAC,WAAW,OAAO;CACvB,OACE,oBAACA,WAAD;EAAS,SAAS;EAChB,UAAA,oBAAC,gBAAD;GACE,GAAI;GACG;GACI;GACA;GACX,qBAAqB,SAAS;EAC/B,CAAA;CACM,CAAA;AAEb;AAwBA,SAAS,UAAU,EACjB,OACA,SACA,QACA,SACA,QACA,UACA,WACA,UACA,QAAQ,YACR,OACA,QACA,YACA,cACY;CACZ,MAAM,SAAS;EAAE,GAAG;EAAgB,GAAG;CAAW;CAClD,MAAM,cAAc,EAAE,MAAM;CAC5B,MAAM,UAAU,SACd,OAAO,SAAS,aAAa,KAAK,WAAW,IAAI;CACnD,OACE,qBAAA,YAAA,EAAA,UAAA;EACE,qBAAC,OAAD;GAAK,WAAW,OAAO,OAAO,EAAE,OAAO,YAAY,OAAO,CAAC;GAA3D,UAAA;IACG,UACC,oBAAC,YAAD;KACE,SAAQ;KACR,SAAS;KACT,MAAM,YAAY,oBAAC,UAAD,CAAW,CAAA;KAC7B,cAAY,OAAO;KACnB,SAAS;KACT,WAAW,YAAY;IACxB,CAAA;IAEF,SAAS,QACR,oBAAC,MAAD;KAAI,IAAI;KAAS,WAAW,OAAO,MAAM,EAAE,OAAO,YAAY,MAAM,CAAC;KAClE,UAAA;IACC,CAAA;IAEN,qBAAC,OAAD;KAAK,WAAW,OAAO,cAAc,EAAE,OAAO,YAAY,cAAc,CAAC;KAAzE,UAAA,CACE,oBAAC,oBAAD,EAAA,UAAqB,OAAO,OAAO,EAAsB,CAAA,GACzD,oBAAC,YAAD;MACE,SAAQ;MACR,SAAS;MACT,MAAM,aAAa,oBAAC,WAAD,CAAY,CAAA;MAC/B,cAAY,OAAO;MACnB,SAAS;MACT,WAAW,YAAY;KACxB,CAAA,CACE;;GACF;;EACL,oBAAC,OAAD;GAAK,KAAK;GAAY,WAAW,OAAO,QAAQ,EAAE,OAAO,YAAY,QAAQ,CAAC;GAC3E,UAAA,OAAO,QAAQ;EACb,CAAA;EACJ,UAAU,QACT,oBAAC,OAAD;GAAK,WAAW,OAAO,OAAO,EAAE,OAAO,YAAY,OAAO,CAAC;GAAI,UAAA,OAAO,MAAM;EAAO,CAAA;CAErF,EAAA,CAAA;AAEN;AAEA,MAAM,YAAY;CAChB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;AAGA,SAAS,eAAkD,OAAU;CACnE,MAAM,OAAgC,CAAC;CACvC,MAAM,OAAgC,EAAE,GAAG,MAAM;CACjD,KAAK,MAAM,OAAO,WAAW;EAC3B,KAAK,OAAO,KAAK;EACjB,OAAO,KAAK;CACd;CACA,OAAO;EACC;EACA;CACR;AACF;;AAGA,SAAS,WAAW,OAAgC,UAAmB,SAAiB;CAEtF,OADY,MAAM,uBACH,MAAM,iBAAiB,QAAQ,WAAW,UAAU,KAAA;AACrE;AAEA,SAAS,eAAe,EACtB,OACA,WACA,WACA,qBACA,WAAW,OACX,OAAO,OACP,YAAY,MACZ,WAAW,OACX,SAAS,UACT,gBAAgB,OAChB,WACA,OACA,KACA,GAAG,SAKF;CACD,MAAM,EAAE,MAAM,SAAS,eAAe,KAAK;CAC3C,MAAM,WAAW,aAAa,GAA0B;CACxD,MAAM,YAAY,OAAuB,IAAI;CAC7C,MAAM,aAAa,OAAuB,IAAI;CAC9C,MAAM,UAAU,MAAM;CAEtB,MAAM,WAAW,aAAa,aAAa;CAC3C,MAAM,SAAS,gBAAgB;EAC7B,SAAS;EACT;EACA,WAAW;EACX;EACA,SAAS,KAAK,UAAU;EACxB;CACF,CAAC;CACD,MAAM,EAAE,eAAe,aAAa,aAAa,aAAa;EAC5D,SAAS,YAAY,CAAC;EACtB;EACA,QAAQ,MAAM;EACd,OAAO,MAAM;EACb;EACA;EACA;CACF,CAAC;CACD,MAAM,EAAE,YAAY,kBAAkB,gBACpC,uBAAuB,UAAU,SAAS,GAC1C,OACA,QACF;CACA,MAAM,EAAE,gBAAgB,UACtB;EACE,GAAI;EACJ,mBAAmB,WAAW,MAAM,KAAK,SAAS,MAAM,OAAO;CACjE,GACA,QACF;CAEA,gBAAgB,sBAAsB,SAAS,OAAO,GAAG,CAAC,QAAQ,CAAC;CACnE,MAAM,UAAU,aAAa,KAAA;CAC7B,OACE,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,OAAD;EACE,eAAY;EACZ,gBAAc;EACd,WAAW,OAAO,MAAM,EAAE,OAAO,KAAK,YAAY,MAAM,CAAC;CAC1D,CAAA,GACD,oBAAC,OAAD;EAAK,GAAI;EAAe,WAAU;EAChC,UAAA,oBAAC,OAAD;GACS;GACP,WAAW,OAAO,UAAU,EAAE,OAAO,GAAG,KAAK,YAAY,WAAW,SAAS,EAAE,CAAC;GAEhF,UAAA,oBAAC,OAAD;IACE,GAAI,WAAW,WAAW,WAAW;IACrC,KAAK;IACL,gBAAc;IACd,WAAW,OAAO,OAAO;IAEzB,UAAA,oBAAC,OAAD;KACE,GAAI,WACF,MACA,uBAAuB,CAAC,GACxB,YACA,aACA,aACF;KACA,KAAK;KAGL,eAAa,gBAAgB,sBAAsB,IAAI;KACvD,gBAAa;KACb,aAAW;KACX,iBAAe,YAAY,KAAA;KAC3B,iBAAe,YAAY,KAAA;KAC3B,gBAAc;KACd,WAAW,OAAO,MAAM,EAAE,OAAO,KAAK,YAAY,MAAM,CAAC;KAEzD,UAAA,oBAAC,gBAAD;MAAgB,OAAO;MACrB,UAAA,oBAAC,WAAD;OACE,GAAI;OACK;OACT,OAAO,MAAM;OACL;OACI;MACb,CAAA;KACa,CAAA;IACb,CAAA;GACF,CAAA;EACF,CAAA;CACF,CAAA,CACL,EAAA,CAAA;AAEN;AAEA,SAAS,kBAAkB,EACzB,OACA,WAAW,OACX,OAAO,OACP,WAAW,YACX,UAAU,MACV,gBAAgB,OAChB,UAAU,WACV,WACA,OACA,KACA,GAAG,SACU;CACb,MAAM,EAAE,MAAM,SAAS,eAAe,KAAK;CAC3C,MAAM,UAAU,MAAM;CACtB,MAAM,SAAS,gBAAgB;EAC7B,SAAS;EACT;EACA;EACA;EACA,SAAS,KAAK,UAAU;EACxB;CACF,CAAC;CACD,MAAM,SAAS,CAAC,MAAM,UAAU,KAAA;CAChC,MAAM,UAAU,aAAa,GAA0B;CACvD,MAAM,WAAW,OAAuB,IAAI;CAI5C,MAAM,CAAC,OAAO,YAAY,SAAwB,IAAI;CACtD,sBAAsB;EACpB,MAAM,QAAQ,SAAS;EACvB,IAAI,CAAC,OAAO;EAEZ,MAAM,gBAAgB,SAAS,MAAM,WAAW;EAChD,QAAQ;EACR,IAAI,OAAO,mBAAmB,aAAa;EAC3C,MAAM,WAAW,IAAI,eAAe,OAAO;EAC3C,SAAS,QAAQ,KAAK;EACtB,aAAa,SAAS,WAAW;CACnC,GAAG,CAAC,CAAC;CAIL,MAAM,cAAc,OAA2B,IAAI;CACnD,sBAAsB;EACpB,MAAM,OAAO,QAAQ;EACrB,MAAM,SAAS,MAAM,cAAc;EACnC,IAAI,MAAM,UAAU,CAAC,QAAQ,EAAE,kBAAkB,gBAAgB,CAAC,KAAK,SAAS,MAAM,GAAG;EACzF,MAAM,QAAQ,YAAY;EAC1B,MAAM,SACJ,OAAO,eAAe,CAAC,KAAK,SAAS,KAAK,KAAK,CAAC,MAAM,QAAQ,SAAS,IACnE,QACC,UAAU,KAAK,cAAc,OAAO,OAAO,KAAK,SAAS,EAAE,CAAC,CAAC,CAC3D,QAAQ,OAAO,GAAG,wBAAwB,IAAI,IAAI,KAAK,2BAA2B,CAAC,CACnF,GAAG,EAAE,KAAK;EACnB,IAAI,QAAQ,OAAO,MAAM,EAAE,eAAe,KAAK,CAAC;OAC3C,OAAO,KAAK;CACnB,GAAG,CAAC,MAAM,QAAQ,OAAO,CAAC;CAC1B,MAAM,YAAY;EAChB,GAAG;EACH,GAAI,UAAU,OAAO,CAAC,IAAI,EAAE,yBAAyB,GAAG,MAAM,IAAI;CACpE;CAEA,OACE,oBAAC,OAAD;EACE,GAAI;EACJ,UAAU,UAAsC;GAC9C,KAAoE,UAAU,KAAK;GACnF,MAAM,OAAO,MAAM;GACnB,IAAI,gBAAgB,eAAe,CAAC,MAAM,cAAc,SAAS,IAAI,GACnE,YAAY,UAAU;EAE1B;EACA,KAAK;EACL,MAAK;EACL,OAAO;EAEP,eAAa,gBAAgB,sBAAsB,IAAI;EACvD,gBAAa;EACb,aAAW;EACX,iBAAe,YAAY,KAAA;EAC3B,eAAa;EACb,OAAO,CAAC,MAAM;EACd,mBAAiB,WAAW,MAAM,KAAK,SAAS,MAAM,OAAO;EAC7D,WAAW,OAAO,SAAS,EAAE,OAAO,GAAG,KAAK,YAAY,WAAW,SAAS,EAAE,CAAC;EAE/E,UAAA,oBAAC,OAAD;GAAK,WAAW,OAAO,aAAa;GAClC,UAAA,oBAAC,OAAD;IAAK,KAAK;IAAU,WAAW,OAAO,cAAc,EAAE,OAAO,KAAK,YAAY,MAAM,CAAC;IACnF,UAAA,oBAAC,OAAD;KAAK,aAAU;KAAQ,WAAW,OAAO,MAAM,EAAE,OAAO,KAAK,YAAY,MAAM,CAAC;KAC9E,UAAA,oBAAC,WAAD;MAAW,GAAI;MAAe;MAAS,OAAO,MAAM;MAAe;KAAS,CAAA;IACzE,CAAA;GACF,CAAA;EACF,CAAA;CACF,CAAA;AAET"}
@@ -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<"icon-color" | "container-color" | "container-elevation" | "container-shape" | "supporting-text-color" | "action-label-text-color">;
5
+ export declare const snackbarTokenSet: ComponentTokenSet<"container-color" | "container-shape" | "supporting-text-color" | "container-elevation" | "icon-color" | "action-label-text-color">;
6
6
  //#endregion
7
7
  //# sourceMappingURL=snackbar-tokens.d.ts.map
@@ -67,11 +67,25 @@ function TextField(props) {
67
67
  useLayoutEffect(() => {
68
68
  const element = domInputRef.current;
69
69
  if (!growing || !element) return;
70
- element.style.height = "auto";
71
- const lineHeight = Number.parseFloat(getComputedStyle(element).lineHeight) || 24;
72
- const max = maxRows ? lineHeight * maxRows : Number.POSITIVE_INFINITY;
73
- element.style.height = `${Math.min(element.scrollHeight, max)}px`;
74
- element.style.overflowY = element.scrollHeight > max ? "auto" : "hidden";
70
+ const fit = () => {
71
+ element.style.height = "auto";
72
+ const lineHeight = Number.parseFloat(getComputedStyle(element).lineHeight) || 24;
73
+ const max = maxRows ? lineHeight * maxRows : Number.POSITIVE_INFINITY;
74
+ element.style.height = `${Math.min(element.scrollHeight, max)}px`;
75
+ element.style.overflowY = element.scrollHeight > max ? "auto" : "hidden";
76
+ };
77
+ if (element.getClientRects().length > 0 || typeof ResizeObserver === "undefined") {
78
+ fit();
79
+ return;
80
+ }
81
+ element.style.height = "";
82
+ const observer = new ResizeObserver(() => {
83
+ if (element.getClientRects().length === 0) return;
84
+ observer.disconnect();
85
+ fit();
86
+ });
87
+ observer.observe(element);
88
+ return () => observer.disconnect();
75
89
  }, [
76
90
  value,
77
91
  growing,