@windmorph/react 0.1.0 → 0.1.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.
- package/dist/components/checkbox/checkbox-tokens.d.ts +1 -1
- package/dist/components/chip/chip-tokens.d.ts +4 -4
- package/dist/components/date-picker/date-picker-styles.d.ts +6 -6
- package/dist/components/date-picker/date-picker-styles.js +5 -5
- package/dist/components/date-picker/date-picker-styles.js.map +1 -1
- package/dist/components/date-picker/date-picker-tokens.d.ts +1 -1
- package/dist/components/dialog/dialog-tokens.d.ts +1 -1
- package/dist/components/menu/menu-tokens.d.ts +1 -1
- package/dist/components/select/Select.js +7 -1
- package/dist/components/select/Select.js.map +1 -1
- package/dist/components/sheet/BottomSheet.js +6 -1
- package/dist/components/sheet/BottomSheet.js.map +1 -1
- package/dist/components/sheet/sheet-styles.js +4 -4
- package/dist/components/sheet/sheet-styles.js.map +1 -1
- package/dist/components/sheet/use-keyboard-inset.js +33 -0
- package/dist/components/sheet/use-keyboard-inset.js.map +1 -0
- package/dist/components/snackbar/snackbar-tokens.d.ts +1 -1
- package/dist/components/tooltip/tooltip-tokens.d.ts +1 -1
- package/dist/styles.compiled.css +1 -1
- package/dist/theme/ThemeProvider.d.ts +6 -0
- package/dist/theme/ThemeProvider.js +44 -11
- package/dist/theme/ThemeProvider.js.map +1 -1
- package/dist/theme/ThemeScript.d.ts +4 -1
- package/dist/theme/ThemeScript.js +4 -1
- package/dist/theme/ThemeScript.js.map +1 -1
- package/dist/theme/context.js +18 -3
- package/dist/theme/context.js.map +1 -1
- package/package.json +1 -1
|
@@ -2,6 +2,6 @@ import { ComponentTokenSet } from "../../tokens/component.js";
|
|
|
2
2
|
import "../../index.js";
|
|
3
3
|
//#region src/components/checkbox/checkbox-tokens.d.ts
|
|
4
4
|
/** The tokens of {@link Checkbox}: `--md-comp-checkbox-<part>`. */
|
|
5
|
-
export declare const checkboxTokenSet: ComponentTokenSet<"selected-
|
|
5
|
+
export declare const checkboxTokenSet: ComponentTokenSet<"selected-container-color" | "selected-icon-color" | "unselected-outline-color">;
|
|
6
6
|
//#endregion
|
|
7
7
|
//# sourceMappingURL=checkbox-tokens.d.ts.map
|
|
@@ -2,12 +2,12 @@ import { ComponentTokenSet } from "../../tokens/component.js";
|
|
|
2
2
|
import "../../index.js";
|
|
3
3
|
//#region src/components/chip/chip-tokens.d.ts
|
|
4
4
|
/** The tokens of {@link AssistChip}: `--md-comp-assist-chip-*`. */
|
|
5
|
-
export declare const assistChipTokenSet: ComponentTokenSet<"
|
|
5
|
+
export declare const assistChipTokenSet: ComponentTokenSet<"label-text-color" | "container-shape" | "with-icon-icon-color" | "flat-outline-color" | "elevated-container-color">;
|
|
6
6
|
/** The tokens of {@link SuggestionChip}: `--md-comp-suggestion-chip-*`. */
|
|
7
|
-
export declare const suggestionChipTokenSet: ComponentTokenSet<"
|
|
7
|
+
export declare const suggestionChipTokenSet: ComponentTokenSet<"label-text-color" | "container-shape" | "flat-outline-color" | "elevated-container-color" | "with-leading-icon-leading-icon-color">;
|
|
8
8
|
/** The tokens of {@link FilterChip}: `--md-comp-filter-chip-*`. */
|
|
9
|
-
export declare const filterChipTokenSet: ComponentTokenSet<"
|
|
9
|
+
export declare const filterChipTokenSet: ComponentTokenSet<"selected-label-text-color" | "unselected-label-text-color" | "container-shape" | "with-leading-icon-unselected-leading-icon-color" | "flat-unselected-outline-color" | "flat-selected-container-color" | "elevated-unselected-container-color">;
|
|
10
10
|
/** The tokens of {@link InputChip}: `--md-comp-input-chip-*`. */
|
|
11
|
-
export declare const inputChipTokenSet: ComponentTokenSet<"selected-container-color" | "
|
|
11
|
+
export declare const inputChipTokenSet: ComponentTokenSet<"selected-container-color" | "selected-label-text-color" | "unselected-label-text-color" | "container-shape" | "unselected-outline-color">;
|
|
12
12
|
//#endregion
|
|
13
13
|
//# sourceMappingURL=chip-tokens.d.ts.map
|
|
@@ -156,12 +156,12 @@ export declare const datePickerStyles: import("tailwind-variants").TVReturnType<
|
|
|
156
156
|
export declare const dockedDatePickerStyles: import("tailwind-variants").TVReturnType<{
|
|
157
157
|
[key: string]: {
|
|
158
158
|
[key: string]: import("tailwind-variants").ClassValue | {
|
|
159
|
-
panel?: import("tailwind-variants").ClassValue;
|
|
160
159
|
actions?: import("tailwind-variants").ClassValue;
|
|
161
160
|
item?: import("tailwind-variants").ClassValue;
|
|
162
|
-
|
|
161
|
+
panel?: import("tailwind-variants").ClassValue;
|
|
163
162
|
header?: import("tailwind-variants").ClassValue;
|
|
164
163
|
view?: import("tailwind-variants").ClassValue;
|
|
164
|
+
list?: import("tailwind-variants").ClassValue;
|
|
165
165
|
check?: import("tailwind-variants").ClassValue;
|
|
166
166
|
menuIcon?: import("tailwind-variants").ClassValue;
|
|
167
167
|
group?: import("tailwind-variants").ClassValue;
|
|
@@ -172,12 +172,12 @@ export declare const dockedDatePickerStyles: import("tailwind-variants").TVRetur
|
|
|
172
172
|
} | {
|
|
173
173
|
[x: string]: {
|
|
174
174
|
[x: string]: import("tailwind-variants").ClassValue | {
|
|
175
|
-
panel?: import("tailwind-variants").ClassValue;
|
|
176
175
|
actions?: import("tailwind-variants").ClassValue;
|
|
177
176
|
item?: import("tailwind-variants").ClassValue;
|
|
178
|
-
|
|
177
|
+
panel?: import("tailwind-variants").ClassValue;
|
|
179
178
|
header?: import("tailwind-variants").ClassValue;
|
|
180
179
|
view?: import("tailwind-variants").ClassValue;
|
|
180
|
+
list?: import("tailwind-variants").ClassValue;
|
|
181
181
|
check?: import("tailwind-variants").ClassValue;
|
|
182
182
|
menuIcon?: import("tailwind-variants").ClassValue;
|
|
183
183
|
group?: import("tailwind-variants").ClassValue;
|
|
@@ -200,12 +200,12 @@ export declare const dockedDatePickerStyles: import("tailwind-variants").TVRetur
|
|
|
200
200
|
}, undefined, {
|
|
201
201
|
[key: string]: {
|
|
202
202
|
[key: string]: import("tailwind-variants").ClassValue | {
|
|
203
|
-
panel?: import("tailwind-variants").ClassValue;
|
|
204
203
|
actions?: import("tailwind-variants").ClassValue;
|
|
205
204
|
item?: import("tailwind-variants").ClassValue;
|
|
206
|
-
|
|
205
|
+
panel?: import("tailwind-variants").ClassValue;
|
|
207
206
|
header?: import("tailwind-variants").ClassValue;
|
|
208
207
|
view?: import("tailwind-variants").ClassValue;
|
|
208
|
+
list?: import("tailwind-variants").ClassValue;
|
|
209
209
|
check?: import("tailwind-variants").ClassValue;
|
|
210
210
|
menuIcon?: import("tailwind-variants").ClassValue;
|
|
211
211
|
group?: import("tailwind-variants").ClassValue;
|
|
@@ -63,7 +63,7 @@ const datePickerStyles = tv({
|
|
|
63
63
|
"relative mx-auto flex size-[40px] cursor-pointer items-center justify-center rounded-corner-full",
|
|
64
64
|
"before:absolute before:-inset-[4px] before:rounded-corner-full",
|
|
65
65
|
"text-body-large text-on-surface outline-none state-layer focus-ring",
|
|
66
|
-
"data-today:border data-in-range:text-on-secondary-container",
|
|
66
|
+
"data-today:not-data-selected:border data-in-range:not-data-selected:text-on-secondary-container",
|
|
67
67
|
"data-disabled:cursor-default data-disabled:text-on-surface/38",
|
|
68
68
|
"data-outside:invisible",
|
|
69
69
|
...forcedSelection,
|
|
@@ -83,7 +83,7 @@ const datePickerStyles = tv({
|
|
|
83
83
|
"relative flex h-[36px] w-[72px] cursor-pointer items-center justify-center rounded-corner-full",
|
|
84
84
|
"before:absolute before:-inset-y-[6px] before:inset-x-0",
|
|
85
85
|
"text-body-large text-on-surface-variant outline-none state-layer focus-ring",
|
|
86
|
-
"data-current:border data-current:border-primary data-current:text-primary",
|
|
86
|
+
"data-current:not-data-selected:border data-current:not-data-selected:border-primary data-current:not-data-selected:text-primary",
|
|
87
87
|
"data-selected:bg-primary data-selected:text-on-primary",
|
|
88
88
|
"data-disabled:cursor-default data-disabled:text-on-surface/38",
|
|
89
89
|
...forcedSelection
|
|
@@ -111,14 +111,14 @@ const datePickerStyles = tv({
|
|
|
111
111
|
true: {
|
|
112
112
|
body: "ps-0 pe-0",
|
|
113
113
|
day: [
|
|
114
|
-
"data-today:border-primary data-today:text-primary",
|
|
114
|
+
"data-today:not-data-selected:border-primary data-today:not-data-selected:text-primary",
|
|
115
115
|
"data-selected:bg-primary data-selected:text-on-primary",
|
|
116
116
|
"data-outside:visible data-outside:text-on-surface-variant"
|
|
117
117
|
]
|
|
118
118
|
},
|
|
119
119
|
false: { day: [
|
|
120
|
-
"data-today:border-[color:var(--_md-comp-date-picker-modal-date-today-container-outline-color)]",
|
|
121
|
-
"data-today:text-[color:var(--_md-comp-date-picker-modal-date-today-label-text-color)]",
|
|
120
|
+
"data-today:not-data-selected:border-[color:var(--_md-comp-date-picker-modal-date-today-container-outline-color)]",
|
|
121
|
+
"data-today:not-data-selected:text-[color:var(--_md-comp-date-picker-modal-date-today-label-text-color)]",
|
|
122
122
|
"data-selected:bg-[var(--_md-comp-date-picker-modal-date-selected-container-color)]",
|
|
123
123
|
"data-selected:text-[color:var(--_md-comp-date-picker-modal-date-selected-label-text-color)]"
|
|
124
124
|
] }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"date-picker-styles.js","names":[],"sources":["../../../src/components/date-picker/date-picker-styles.ts"],"sourcesContent":["import { tv, type VariantProps } from '../../utils/tv';\n\n/*\n * Values follow the M3 date picker spec, with sizes from Compose Material 3 (DatePicker.kt,\n * DateRangePicker.kt, DatePickerDialog.kt, DatePickerModalTokens).\n *\n * At least 360px wide. Header at least 120px (128px for ranges): a `label-large` title\n * (24px start, 12px end, 16px top) and a `headline-large` headline (`title-large` for\n * ranges; 24px start, 12px end and bottom), both `on-surface-variant`, with the mode toggle\n * at the end, above an `outline-variant` divider. The body has 12px sides: a 56px month row\n * (the month / year button, `on-surface-variant`, and previous / next buttons), a 48px\n * weekday row (`body-large` `on-surface`) and six 48px week rows. Days are 40px circles\n * with a 48px touch target: `body-large` `on-surface`; selected `primary` / `on-primary`;\n * today a 1px `primary` outline with `primary` text; disabled 38%. In a range, the days\n * between are a 40px `secondary-container` band with `on-secondary-container` text. Years\n * are 72 × 36px pills with a 48px-tall target, in three columns 16px apart (selected\n * `primary`, this year outlined), in a 335px area.\n *\n * Months slide in from the side they come from on the default spatial spring (a fade only\n * with reduced motion). The range picker's month list scrolls vertically: each month has a\n * 48px `title-small` `on-surface-variant` subhead (8px below it, 24px in) over its six\n * week rows, under one weekday row.\n *\n * The headline, the picked days, today and the range band come from the modal date\n * picker's component tokens (`date-picker-tokens.ts`, `--md-comp-date-picker-modal-*`): the\n * root carries `data-tokens=\"md-comp-date-picker-modal\"`, and the classes read one private\n * variable per part. A consumer class still wins in `cn()`.\n *\n * Every class is written out in full so Tailwind can find it.\n */\n\n/**\n * Forced colours paint backgrounds as Canvas, so a picked day or year looked like any\n * other: it keeps a Highlight fill with HighlightText, opting out of the forced palette\n * (2026-10-15). `!important` beats the token colours, whose selectors are as specific.\n */\nconst forcedSelection = [\n 'forced-colors:data-selected:bg-[Highlight]! forced-colors:data-selected:text-[HighlightText]!',\n 'forced-colors:data-selected:forced-color-adjust-none',\n];\n\n/** Variant definitions for {@link DatePicker}, {@link DateRangePicker} and the docked calendar. */\nexport const datePickerStyles = tv({\n slots: {\n root: [\n 'group/picker flex w-[360px] max-w-full shrink-0 flex-col text-on-surface',\n // A full-screen picker dialog spans the window, and the month list fills its height.\n 'group-data-full-screen/picker-dialog:min-h-0 group-data-full-screen/picker-dialog:w-full group-data-full-screen/picker-dialog:flex-1',\n ],\n header: 'flex shrink-0 flex-col justify-between border-b border-outline-variant',\n title: 'ps-[24px] pe-[12px] pt-[16px] text-label-large text-on-surface-variant',\n headlineRow: 'flex items-end justify-between gap-[8px] ps-[24px] pe-[12px] pb-[12px]',\n headline: 'min-w-0 flex-1 text-[color:var(--_md-comp-date-picker-modal-header-headline-color)]',\n body: 'relative flex min-h-0 flex-col ps-[12px] pe-[12px]',\n nav: 'flex h-[56px] shrink-0 items-center justify-between',\n navButtons: 'flex items-center text-on-surface-variant',\n yearButtonIcon: [\n 'inline-flex size-[18px] [&>svg]:size-full',\n '[transition-property:rotate] [transition-duration:var(--md-sys-motion-spring-spatial-default-duration)]',\n '[transition-timing-function:var(--md-sys-motion-spring-spatial-default-easing)]',\n 'group-data-[years]/picker:rotate-180',\n ],\n // Horizontal swipes change the month; vertical ones still scroll the page.\n swipe: 'touch-pan-y overflow-hidden',\n // A new month slides in from the side it comes from.\n slide: [\n 'translate-none opacity-100',\n '[transition-property:translate,opacity]',\n '[transition-duration:var(--md-sys-motion-spring-spatial-default-duration),var(--md-sys-motion-spring-effects-default-duration)]',\n '[transition-timing-function:var(--md-sys-motion-spring-spatial-default-easing),var(--md-sys-motion-spring-effects-default-easing)]',\n 'data-[direction=next]:starting:translate-x-[48px] data-[direction=next]:starting:opacity-0',\n 'data-[direction=previous]:starting:-translate-x-[48px] data-[direction=previous]:starting:opacity-0',\n 'rtl:data-[direction=next]:starting:-translate-x-[48px]',\n 'rtl:data-[direction=previous]:starting:translate-x-[48px]',\n 'motion-reduce:starting:translate-none',\n ],\n // Calendar ↔ text input (Compose's display mode animation, enter side): the input rises\n // from below its own height, the calendar comes down 48px, both fading in, inside a\n // clipping view. Reduced motion keeps the fade.\n // The view and the outgoing view's copy share one clipping cell; the copy's slot has no\n // height, so only the new view sizes the picker.\n modeStack:\n 'grid min-h-0 flex-1 grid-cols-[minmax(0,1fr)] grid-rows-[minmax(0,1fr)] overflow-hidden',\n modeExit: 'pointer-events-none h-0 [grid-area:1/1]',\n modeView: [\n 'flex min-h-0 flex-1 flex-col overflow-hidden [grid-area:1/1]',\n '[&>*]:[transition-property:translate,opacity]',\n '[&>*]:[transition-duration:var(--md-sys-motion-spring-spatial-default-duration),var(--md-sys-motion-spring-effects-default-duration)]',\n '[&>*]:[transition-timing-function:var(--md-sys-motion-spring-spatial-default-easing),var(--md-sys-motion-spring-effects-default-easing)]',\n 'data-[enter=input]:[&>*]:starting:translate-y-full data-[enter=input]:[&>*]:starting:opacity-0',\n 'data-[enter=calendar]:[&>*]:starting:-translate-y-[48px] data-[enter=calendar]:[&>*]:starting:opacity-0',\n 'motion-reduce:[&>*]:starting:translate-none',\n ],\n grid: 'w-full table-fixed border-collapse',\n weekday: 'h-[48px] cursor-default p-0 text-center text-body-large font-normal text-on-surface',\n weekdayRow: 'grid shrink-0 grid-cols-7',\n weekdayCell:\n 'flex h-[48px] cursor-default items-center justify-center text-body-large text-on-surface',\n cell: [\n 'h-[48px] p-0 text-center',\n // The range band: a 40px stripe behind the days between the ends.\n // 1px wider than the cell, so neighbouring bands never leave a hairline between them.\n 'bg-[length:calc(100%+1px)_40px] bg-center bg-no-repeat',\n 'data-[range=middle]:bg-[image:var(--_md-comp-date-picker-modal-range-middle)]',\n 'data-[range=start]:bg-[image:var(--_md-comp-date-picker-modal-range-to-right)]',\n 'data-[range=end]:bg-[image:var(--_md-comp-date-picker-modal-range-to-left)]',\n 'rtl:data-[range=start]:bg-[image:var(--_md-comp-date-picker-modal-range-to-left)]',\n 'rtl:data-[range=end]:bg-[image:var(--_md-comp-date-picker-modal-range-to-right)]',\n ],\n day: [\n // The 40px circle; its ::before widens the touch target to the 48px cell.\n 'relative mx-auto flex size-[40px] cursor-pointer items-center justify-center rounded-corner-full',\n 'before:absolute before:-inset-[4px] before:rounded-corner-full',\n 'text-body-large text-on-surface outline-none state-layer focus-ring',\n 'data-today:border data-in-range:text-on-secondary-container',\n 'data-disabled:cursor-default data-disabled:text-on-surface/38',\n 'data-outside:invisible',\n ...forcedSelection,\n // The range band is a background, which forced colours drop: the days between the\n // ends take a Highlight ring instead (2026-10-15).\n 'forced-colors:data-in-range:not-data-selected:border forced-colors:data-in-range:not-data-selected:border-[Highlight]',\n ],\n years: [\n 'grid h-[335px] grid-cols-3 content-start justify-items-center gap-y-[16px] overflow-y-auto overscroll-contain pt-[8px] pb-[8px]',\n // It expands down from the month row as it fades in from 60% (Compose's year picker\n // `expandVertically` + `fadeIn(initialAlpha = 0.6f)`, default effects); reduced motion\n // keeps the fade.\n '[clip-path:inset(0)] [transition-property:clip-path,opacity]',\n '[transition-duration:var(--md-sys-motion-spring-effects-default-duration)] [transition-timing-function:var(--md-sys-motion-spring-effects-default-easing)]',\n 'starting:[clip-path:inset(0_0_100%_0)] starting:opacity-60 motion-reduce:starting:[clip-path:inset(0)]',\n // Closing, it lies over the grid below the month row, on the picker's container colour,\n // and shrinks back up to the month row as it fades out (Compose's `shrinkVertically` +\n // `fadeOut`, fast effects). Reduced motion keeps the fade.\n 'data-exiting:absolute data-exiting:inset-x-[12px] data-exiting:top-[56px] data-exiting:z-10',\n 'data-exiting:bg-[var(--_md-comp-date-picker-modal-container-color)] data-exiting:pointer-events-none',\n 'data-exiting:[clip-path:inset(0_0_100%_0)] data-exiting:opacity-0 motion-reduce:data-exiting:[clip-path:inset(0)]',\n 'data-exiting:[transition-duration:var(--md-sys-motion-spring-effects-fast-duration)] data-exiting:[transition-timing-function:var(--md-sys-motion-spring-effects-fast-easing)]',\n ],\n year: [\n // The 36px pill; its ::before makes the touch target 48px tall.\n 'relative flex h-[36px] w-[72px] cursor-pointer items-center justify-center rounded-corner-full',\n 'before:absolute before:-inset-y-[6px] before:inset-x-0',\n 'text-body-large text-on-surface-variant outline-none state-layer focus-ring',\n 'data-current:border data-current:border-primary data-current:text-primary',\n 'data-selected:bg-primary data-selected:text-on-primary',\n 'data-disabled:cursor-default data-disabled:text-on-surface/38',\n ...forcedSelection,\n ],\n months: [\n 'h-[344px] min-h-0 overflow-y-auto overscroll-contain',\n 'group-data-full-screen/picker-dialog:h-auto group-data-full-screen/picker-dialog:flex-1',\n ],\n monthSubhead:\n 'flex h-[48px] items-end ps-[24px] pe-[12px] pb-[8px] text-title-small text-on-surface-variant',\n input: 'flex flex-col gap-[16px] ps-[24px] pe-[24px] pt-[16px] pb-[8px]',\n },\n variants: {\n range: {\n false: { header: 'min-h-[120px]', headline: 'text-headline-large' },\n // Full screen, the top row takes the title's space: Compose's range header is the\n // token's 128px less its 60px `HeaderHeightOffset`.\n true: {\n header: 'min-h-[128px] group-data-full-screen/picker-dialog:min-h-[68px]',\n headline: 'text-title-large',\n },\n },\n /**\n * The docked picker shows the days of the months around the shown one. Its days keep\n * their classes: the modal picker's tokens (`date-picker-tokens.ts`) colour the others.\n */\n docked: {\n true: {\n body: 'ps-0 pe-0',\n day: [\n 'data-today:border-primary data-today:text-primary',\n 'data-selected:bg-primary data-selected:text-on-primary',\n 'data-outside:visible data-outside:text-on-surface-variant',\n ],\n },\n false: {\n day: [\n 'data-today:border-[color:var(--_md-comp-date-picker-modal-date-today-container-outline-color)]',\n 'data-today:text-[color:var(--_md-comp-date-picker-modal-date-today-label-text-color)]',\n 'data-selected:bg-[var(--_md-comp-date-picker-modal-date-selected-container-color)]',\n 'data-selected:text-[color:var(--_md-comp-date-picker-modal-date-selected-label-text-color)]',\n ],\n },\n },\n },\n defaultVariants: { range: false, docked: false },\n});\n\n/*\n * The docked date picker (M3 \"Docked date picker\"): a `surface-container-high` panel with\n * 16px corners at level 3 below its date field, 360px wide with 12px sides. A 56px row\n * holds two menu buttons (month and year), each between its previous / next icon buttons;\n * the month or year menu replaces the calendar with a 48px-row list under an\n * `outline-variant` divider (the chosen row `surface-variant` with a check), and text\n * buttons end the panel.\n */\n\n/** Variant definitions for {@link DateField}'s docked calendar. */\nexport const dockedDatePickerStyles = tv({\n slots: {\n panel: [\n 'flex w-[360px] max-w-full flex-col rounded-corner-large bg-surface-container-high',\n 'ps-[12px] pe-[12px] text-on-surface shadow-elevation-3 outline-none',\n // Forced colours drop the surface and the shadow: a system-colour outline keeps the edge.\n 'forced-colors:outline-solid forced-colors:outline-1 forced-colors:outline-[CanvasText]',\n ],\n calendar: 'flex flex-col',\n header: 'flex h-[56px] shrink-0 items-center justify-between',\n group: 'flex items-center text-on-surface-variant',\n menuButton: 'min-w-0 ps-[8px] pe-[4px] text-on-surface-variant',\n menuIcon: [\n 'inline-flex size-[18px] [&>svg]:size-full',\n '[transition-property:rotate] [transition-duration:var(--md-sys-motion-spring-spatial-default-duration)]',\n '[transition-timing-function:var(--md-sys-motion-spring-spatial-default-easing)]',\n 'data-open:rotate-180',\n ],\n view: 'h-[336px] min-h-0',\n list: [\n '-ms-[12px] -me-[12px] h-[336px] overflow-y-auto overscroll-contain',\n 'border-t border-outline-variant pt-[8px] pb-[8px]',\n ],\n item: [\n 'flex h-[48px] w-full cursor-pointer items-center gap-[16px] ps-[16px] pe-[24px] text-start',\n 'text-body-large text-on-surface outline-none state-layer focus-ring-inset',\n 'data-selected:bg-surface-variant',\n 'data-disabled:cursor-default data-disabled:text-on-surface/38',\n ],\n check: 'inline-flex size-[24px] shrink-0 [&>svg]:size-full',\n actions: 'flex shrink-0 items-center justify-end gap-[8px] pt-[4px] pb-[8px]',\n },\n});\n\n/**\n * The variants of {@link datePickerStyles}, 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 DatePickerStyleProps = VariantProps<typeof datePickerStyles>;\n"],"mappings":";;;;;;;AAoCA,MAAM,kBAAkB,CACtB,iGACA,sDACF;;AAGA,MAAa,mBAAmB,GAAG;CACjC,OAAO;EACL,MAAM,CACJ,4EAEA,sIACF;EACA,QAAQ;EACR,OAAO;EACP,aAAa;EACb,UAAU;EACV,MAAM;EACN,KAAK;EACL,YAAY;EACZ,gBAAgB;GACd;GACA;GACA;GACA;EACF;EAEA,OAAO;EAEP,OAAO;GACL;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF;EAMA,WACE;EACF,UAAU;EACV,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACA;EACF;EACA,MAAM;EACN,SAAS;EACT,YAAY;EACZ,aACE;EACF,MAAM;GACJ;GAGA;GACA;GACA;GACA;GACA;GACA;EACF;EACA,KAAK;GAEH;GACA;GACA;GACA;GACA;GACA;GACA,GAAG;GAGH;EACF;EACA,OAAO;GACL;GAIA;GACA;GACA;GAIA;GACA;GACA;GACA;EACF;EACA,MAAM;GAEJ;GACA;GACA;GACA;GACA;GACA;GACA,GAAG;EACL;EACA,QAAQ,CACN,wDACA,yFACF;EACA,cACE;EACF,OAAO;CACT;CACA,UAAU;EACR,OAAO;GACL,OAAO;IAAE,QAAQ;IAAiB,UAAU;GAAsB;GAGlE,MAAM;IACJ,QAAQ;IACR,UAAU;GACZ;EACF;;;;;EAKA,QAAQ;GACN,MAAM;IACJ,MAAM;IACN,KAAK;KACH;KACA;KACA;IACF;GACF;GACA,OAAO,EACL,KAAK;IACH;IACA;IACA;IACA;GACF,EACF;EACF;CACF;CACA,iBAAiB;EAAE,OAAO;EAAO,QAAQ;CAAM;AACjD,CAAC;;AAYD,MAAa,yBAAyB,GAAG,EACvC,OAAO;CACL,OAAO;EACL;EACA;EAEA;CACF;CACA,UAAU;CACV,QAAQ;CACR,OAAO;CACP,YAAY;CACZ,UAAU;EACR;EACA;EACA;EACA;CACF;CACA,MAAM;CACN,MAAM,CACJ,sEACA,mDACF;CACA,MAAM;EACJ;EACA;EACA;EACA;CACF;CACA,OAAO;CACP,SAAS;AACX,EACF,CAAC"}
|
|
1
|
+
{"version":3,"file":"date-picker-styles.js","names":[],"sources":["../../../src/components/date-picker/date-picker-styles.ts"],"sourcesContent":["import { tv, type VariantProps } from '../../utils/tv';\n\n/*\n * Values follow the M3 date picker spec, with sizes from Compose Material 3 (DatePicker.kt,\n * DateRangePicker.kt, DatePickerDialog.kt, DatePickerModalTokens).\n *\n * At least 360px wide. Header at least 120px (128px for ranges): a `label-large` title\n * (24px start, 12px end, 16px top) and a `headline-large` headline (`title-large` for\n * ranges; 24px start, 12px end and bottom), both `on-surface-variant`, with the mode toggle\n * at the end, above an `outline-variant` divider. The body has 12px sides: a 56px month row\n * (the month / year button, `on-surface-variant`, and previous / next buttons), a 48px\n * weekday row (`body-large` `on-surface`) and six 48px week rows. Days are 40px circles\n * with a 48px touch target: `body-large` `on-surface`; selected `primary` / `on-primary`;\n * today a 1px `primary` outline with `primary` text; disabled 38%. In a range, the days\n * between are a 40px `secondary-container` band with `on-secondary-container` text. Years\n * are 72 × 36px pills with a 48px-tall target, in three columns 16px apart (selected\n * `primary`, this year outlined), in a 335px area.\n *\n * Months slide in from the side they come from on the default spatial spring (a fade only\n * with reduced motion). The range picker's month list scrolls vertically: each month has a\n * 48px `title-small` `on-surface-variant` subhead (8px below it, 24px in) over its six\n * week rows, under one weekday row.\n *\n * The headline, the picked days, today and the range band come from the modal date\n * picker's component tokens (`date-picker-tokens.ts`, `--md-comp-date-picker-modal-*`): the\n * root carries `data-tokens=\"md-comp-date-picker-modal\"`, and the classes read one private\n * variable per part. A consumer class still wins in `cn()`.\n *\n * Every class is written out in full so Tailwind can find it.\n */\n\n/**\n * Forced colours paint backgrounds as Canvas, so a picked day or year looked like any\n * other: it keeps a Highlight fill with HighlightText, opting out of the forced palette\n * (2026-10-15). `!important` beats the token colours, whose selectors are as specific.\n */\nconst forcedSelection = [\n 'forced-colors:data-selected:bg-[Highlight]! forced-colors:data-selected:text-[HighlightText]!',\n 'forced-colors:data-selected:forced-color-adjust-none',\n];\n\n/** Variant definitions for {@link DatePicker}, {@link DateRangePicker} and the docked calendar. */\nexport const datePickerStyles = tv({\n slots: {\n root: [\n 'group/picker flex w-[360px] max-w-full shrink-0 flex-col text-on-surface',\n // A full-screen picker dialog spans the window, and the month list fills its height.\n 'group-data-full-screen/picker-dialog:min-h-0 group-data-full-screen/picker-dialog:w-full group-data-full-screen/picker-dialog:flex-1',\n ],\n header: 'flex shrink-0 flex-col justify-between border-b border-outline-variant',\n title: 'ps-[24px] pe-[12px] pt-[16px] text-label-large text-on-surface-variant',\n headlineRow: 'flex items-end justify-between gap-[8px] ps-[24px] pe-[12px] pb-[12px]',\n headline: 'min-w-0 flex-1 text-[color:var(--_md-comp-date-picker-modal-header-headline-color)]',\n body: 'relative flex min-h-0 flex-col ps-[12px] pe-[12px]',\n nav: 'flex h-[56px] shrink-0 items-center justify-between',\n navButtons: 'flex items-center text-on-surface-variant',\n yearButtonIcon: [\n 'inline-flex size-[18px] [&>svg]:size-full',\n '[transition-property:rotate] [transition-duration:var(--md-sys-motion-spring-spatial-default-duration)]',\n '[transition-timing-function:var(--md-sys-motion-spring-spatial-default-easing)]',\n 'group-data-[years]/picker:rotate-180',\n ],\n // Horizontal swipes change the month; vertical ones still scroll the page.\n swipe: 'touch-pan-y overflow-hidden',\n // A new month slides in from the side it comes from.\n slide: [\n 'translate-none opacity-100',\n '[transition-property:translate,opacity]',\n '[transition-duration:var(--md-sys-motion-spring-spatial-default-duration),var(--md-sys-motion-spring-effects-default-duration)]',\n '[transition-timing-function:var(--md-sys-motion-spring-spatial-default-easing),var(--md-sys-motion-spring-effects-default-easing)]',\n 'data-[direction=next]:starting:translate-x-[48px] data-[direction=next]:starting:opacity-0',\n 'data-[direction=previous]:starting:-translate-x-[48px] data-[direction=previous]:starting:opacity-0',\n 'rtl:data-[direction=next]:starting:-translate-x-[48px]',\n 'rtl:data-[direction=previous]:starting:translate-x-[48px]',\n 'motion-reduce:starting:translate-none',\n ],\n // Calendar ↔ text input (Compose's display mode animation, enter side): the input rises\n // from below its own height, the calendar comes down 48px, both fading in, inside a\n // clipping view. Reduced motion keeps the fade.\n // The view and the outgoing view's copy share one clipping cell; the copy's slot has no\n // height, so only the new view sizes the picker.\n modeStack:\n 'grid min-h-0 flex-1 grid-cols-[minmax(0,1fr)] grid-rows-[minmax(0,1fr)] overflow-hidden',\n modeExit: 'pointer-events-none h-0 [grid-area:1/1]',\n modeView: [\n 'flex min-h-0 flex-1 flex-col overflow-hidden [grid-area:1/1]',\n '[&>*]:[transition-property:translate,opacity]',\n '[&>*]:[transition-duration:var(--md-sys-motion-spring-spatial-default-duration),var(--md-sys-motion-spring-effects-default-duration)]',\n '[&>*]:[transition-timing-function:var(--md-sys-motion-spring-spatial-default-easing),var(--md-sys-motion-spring-effects-default-easing)]',\n 'data-[enter=input]:[&>*]:starting:translate-y-full data-[enter=input]:[&>*]:starting:opacity-0',\n 'data-[enter=calendar]:[&>*]:starting:-translate-y-[48px] data-[enter=calendar]:[&>*]:starting:opacity-0',\n 'motion-reduce:[&>*]:starting:translate-none',\n ],\n grid: 'w-full table-fixed border-collapse',\n weekday: 'h-[48px] cursor-default p-0 text-center text-body-large font-normal text-on-surface',\n weekdayRow: 'grid shrink-0 grid-cols-7',\n weekdayCell:\n 'flex h-[48px] cursor-default items-center justify-center text-body-large text-on-surface',\n cell: [\n 'h-[48px] p-0 text-center',\n // The range band: a 40px stripe behind the days between the ends.\n // 1px wider than the cell, so neighbouring bands never leave a hairline between them.\n 'bg-[length:calc(100%+1px)_40px] bg-center bg-no-repeat',\n 'data-[range=middle]:bg-[image:var(--_md-comp-date-picker-modal-range-middle)]',\n 'data-[range=start]:bg-[image:var(--_md-comp-date-picker-modal-range-to-right)]',\n 'data-[range=end]:bg-[image:var(--_md-comp-date-picker-modal-range-to-left)]',\n 'rtl:data-[range=start]:bg-[image:var(--_md-comp-date-picker-modal-range-to-left)]',\n 'rtl:data-[range=end]:bg-[image:var(--_md-comp-date-picker-modal-range-to-right)]',\n ],\n day: [\n // The 40px circle; its ::before widens the touch target to the 48px cell.\n 'relative mx-auto flex size-[40px] cursor-pointer items-center justify-center rounded-corner-full',\n 'before:absolute before:-inset-[4px] before:rounded-corner-full',\n 'text-body-large text-on-surface outline-none state-layer focus-ring',\n // Today's outline and colour, and the range's text, give way to a selected day's fill\n // and on-colour: as equal-specificity rules, the later one in the CSS used to win, so a\n // selected today was primary on primary (2026-11-07).\n 'data-today:not-data-selected:border data-in-range:not-data-selected:text-on-secondary-container',\n 'data-disabled:cursor-default data-disabled:text-on-surface/38',\n 'data-outside:invisible',\n ...forcedSelection,\n // The range band is a background, which forced colours drop: the days between the\n // ends take a Highlight ring instead (2026-10-15).\n 'forced-colors:data-in-range:not-data-selected:border forced-colors:data-in-range:not-data-selected:border-[Highlight]',\n ],\n years: [\n 'grid h-[335px] grid-cols-3 content-start justify-items-center gap-y-[16px] overflow-y-auto overscroll-contain pt-[8px] pb-[8px]',\n // It expands down from the month row as it fades in from 60% (Compose's year picker\n // `expandVertically` + `fadeIn(initialAlpha = 0.6f)`, default effects); reduced motion\n // keeps the fade.\n '[clip-path:inset(0)] [transition-property:clip-path,opacity]',\n '[transition-duration:var(--md-sys-motion-spring-effects-default-duration)] [transition-timing-function:var(--md-sys-motion-spring-effects-default-easing)]',\n 'starting:[clip-path:inset(0_0_100%_0)] starting:opacity-60 motion-reduce:starting:[clip-path:inset(0)]',\n // Closing, it lies over the grid below the month row, on the picker's container colour,\n // and shrinks back up to the month row as it fades out (Compose's `shrinkVertically` +\n // `fadeOut`, fast effects). Reduced motion keeps the fade.\n 'data-exiting:absolute data-exiting:inset-x-[12px] data-exiting:top-[56px] data-exiting:z-10',\n 'data-exiting:bg-[var(--_md-comp-date-picker-modal-container-color)] data-exiting:pointer-events-none',\n 'data-exiting:[clip-path:inset(0_0_100%_0)] data-exiting:opacity-0 motion-reduce:data-exiting:[clip-path:inset(0)]',\n 'data-exiting:[transition-duration:var(--md-sys-motion-spring-effects-fast-duration)] data-exiting:[transition-timing-function:var(--md-sys-motion-spring-effects-fast-easing)]',\n ],\n year: [\n // The 36px pill; its ::before makes the touch target 48px tall.\n 'relative flex h-[36px] w-[72px] cursor-pointer items-center justify-center rounded-corner-full',\n 'before:absolute before:-inset-y-[6px] before:inset-x-0',\n 'text-body-large text-on-surface-variant outline-none state-layer focus-ring',\n 'data-current:not-data-selected:border data-current:not-data-selected:border-primary data-current:not-data-selected:text-primary',\n 'data-selected:bg-primary data-selected:text-on-primary',\n 'data-disabled:cursor-default data-disabled:text-on-surface/38',\n ...forcedSelection,\n ],\n months: [\n 'h-[344px] min-h-0 overflow-y-auto overscroll-contain',\n 'group-data-full-screen/picker-dialog:h-auto group-data-full-screen/picker-dialog:flex-1',\n ],\n monthSubhead:\n 'flex h-[48px] items-end ps-[24px] pe-[12px] pb-[8px] text-title-small text-on-surface-variant',\n input: 'flex flex-col gap-[16px] ps-[24px] pe-[24px] pt-[16px] pb-[8px]',\n },\n variants: {\n range: {\n false: { header: 'min-h-[120px]', headline: 'text-headline-large' },\n // Full screen, the top row takes the title's space: Compose's range header is the\n // token's 128px less its 60px `HeaderHeightOffset`.\n true: {\n header: 'min-h-[128px] group-data-full-screen/picker-dialog:min-h-[68px]',\n headline: 'text-title-large',\n },\n },\n /**\n * The docked picker shows the days of the months around the shown one. Its days keep\n * their classes: the modal picker's tokens (`date-picker-tokens.ts`) colour the others.\n */\n docked: {\n true: {\n body: 'ps-0 pe-0',\n day: [\n 'data-today:not-data-selected:border-primary data-today:not-data-selected:text-primary',\n 'data-selected:bg-primary data-selected:text-on-primary',\n 'data-outside:visible data-outside:text-on-surface-variant',\n ],\n },\n false: {\n day: [\n 'data-today:not-data-selected:border-[color:var(--_md-comp-date-picker-modal-date-today-container-outline-color)]',\n 'data-today:not-data-selected:text-[color:var(--_md-comp-date-picker-modal-date-today-label-text-color)]',\n 'data-selected:bg-[var(--_md-comp-date-picker-modal-date-selected-container-color)]',\n 'data-selected:text-[color:var(--_md-comp-date-picker-modal-date-selected-label-text-color)]',\n ],\n },\n },\n },\n defaultVariants: { range: false, docked: false },\n});\n\n/*\n * The docked date picker (M3 \"Docked date picker\"): a `surface-container-high` panel with\n * 16px corners at level 3 below its date field, 360px wide with 12px sides. A 56px row\n * holds two menu buttons (month and year), each between its previous / next icon buttons;\n * the month or year menu replaces the calendar with a 48px-row list under an\n * `outline-variant` divider (the chosen row `surface-variant` with a check), and text\n * buttons end the panel.\n */\n\n/** Variant definitions for {@link DateField}'s docked calendar. */\nexport const dockedDatePickerStyles = tv({\n slots: {\n panel: [\n 'flex w-[360px] max-w-full flex-col rounded-corner-large bg-surface-container-high',\n 'ps-[12px] pe-[12px] text-on-surface shadow-elevation-3 outline-none',\n // Forced colours drop the surface and the shadow: a system-colour outline keeps the edge.\n 'forced-colors:outline-solid forced-colors:outline-1 forced-colors:outline-[CanvasText]',\n ],\n calendar: 'flex flex-col',\n header: 'flex h-[56px] shrink-0 items-center justify-between',\n group: 'flex items-center text-on-surface-variant',\n menuButton: 'min-w-0 ps-[8px] pe-[4px] text-on-surface-variant',\n menuIcon: [\n 'inline-flex size-[18px] [&>svg]:size-full',\n '[transition-property:rotate] [transition-duration:var(--md-sys-motion-spring-spatial-default-duration)]',\n '[transition-timing-function:var(--md-sys-motion-spring-spatial-default-easing)]',\n 'data-open:rotate-180',\n ],\n view: 'h-[336px] min-h-0',\n list: [\n '-ms-[12px] -me-[12px] h-[336px] overflow-y-auto overscroll-contain',\n 'border-t border-outline-variant pt-[8px] pb-[8px]',\n ],\n item: [\n 'flex h-[48px] w-full cursor-pointer items-center gap-[16px] ps-[16px] pe-[24px] text-start',\n 'text-body-large text-on-surface outline-none state-layer focus-ring-inset',\n 'data-selected:bg-surface-variant',\n 'data-disabled:cursor-default data-disabled:text-on-surface/38',\n ],\n check: 'inline-flex size-[24px] shrink-0 [&>svg]:size-full',\n actions: 'flex shrink-0 items-center justify-end gap-[8px] pt-[4px] pb-[8px]',\n },\n});\n\n/**\n * The variants of {@link datePickerStyles}, 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 DatePickerStyleProps = VariantProps<typeof datePickerStyles>;\n"],"mappings":";;;;;;;AAoCA,MAAM,kBAAkB,CACtB,iGACA,sDACF;;AAGA,MAAa,mBAAmB,GAAG;CACjC,OAAO;EACL,MAAM,CACJ,4EAEA,sIACF;EACA,QAAQ;EACR,OAAO;EACP,aAAa;EACb,UAAU;EACV,MAAM;EACN,KAAK;EACL,YAAY;EACZ,gBAAgB;GACd;GACA;GACA;GACA;EACF;EAEA,OAAO;EAEP,OAAO;GACL;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF;EAMA,WACE;EACF,UAAU;EACV,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACA;EACF;EACA,MAAM;EACN,SAAS;EACT,YAAY;EACZ,aACE;EACF,MAAM;GACJ;GAGA;GACA;GACA;GACA;GACA;GACA;EACF;EACA,KAAK;GAEH;GACA;GACA;GAIA;GACA;GACA;GACA,GAAG;GAGH;EACF;EACA,OAAO;GACL;GAIA;GACA;GACA;GAIA;GACA;GACA;GACA;EACF;EACA,MAAM;GAEJ;GACA;GACA;GACA;GACA;GACA;GACA,GAAG;EACL;EACA,QAAQ,CACN,wDACA,yFACF;EACA,cACE;EACF,OAAO;CACT;CACA,UAAU;EACR,OAAO;GACL,OAAO;IAAE,QAAQ;IAAiB,UAAU;GAAsB;GAGlE,MAAM;IACJ,QAAQ;IACR,UAAU;GACZ;EACF;;;;;EAKA,QAAQ;GACN,MAAM;IACJ,MAAM;IACN,KAAK;KACH;KACA;KACA;IACF;GACF;GACA,OAAO,EACL,KAAK;IACH;IACA;IACA;IACA;GACF,EACF;EACF;CACF;CACA,iBAAiB;EAAE,OAAO;EAAO,QAAQ;CAAM;AACjD,CAAC;;AAYD,MAAa,yBAAyB,GAAG,EACvC,OAAO;CACL,OAAO;EACL;EACA;EAEA;CACF;CACA,UAAU;CACV,QAAQ;CACR,OAAO;CACP,YAAY;CACZ,UAAU;EACR;EACA;EACA;EACA;CACF;CACA,MAAM;CACN,MAAM,CACJ,sEACA,mDACF;CACA,MAAM;EACJ;EACA;EACA;EACA;CACF;CACA,OAAO;CACP,SAAS;AACX,EACF,CAAC"}
|
|
@@ -2,6 +2,6 @@ import { ComponentTokenSet } from "../../tokens/component.js";
|
|
|
2
2
|
import "../../index.js";
|
|
3
3
|
//#region src/components/date-picker/date-picker-tokens.d.ts
|
|
4
4
|
/** The tokens of {@link DatePicker}, {@link DateRangePicker} and the date {@link PickerDialog}. */
|
|
5
|
-
export declare const datePickerTokenSet: ComponentTokenSet<"container-color" | "container-
|
|
5
|
+
export declare const datePickerTokenSet: ComponentTokenSet<"container-color" | "container-elevation" | "container-shape" | "header-headline-color" | "date-selected-container-color" | "date-selected-label-text-color" | "date-today-container-outline-color" | "date-today-label-text-color" | "range-selection-active-indicator-container-color">;
|
|
6
6
|
//#endregion
|
|
7
7
|
//# sourceMappingURL=date-picker-tokens.d.ts.map
|
|
@@ -2,6 +2,6 @@ import { ComponentTokenSet } from "../../tokens/component.js";
|
|
|
2
2
|
import "../../index.js";
|
|
3
3
|
//#region src/components/dialog/dialog-tokens.d.ts
|
|
4
4
|
/** The tokens of {@link Dialog}: `--md-comp-dialog-<part>`. */
|
|
5
|
-
export declare const dialogTokenSet: ComponentTokenSet<"container-color" | "container-
|
|
5
|
+
export declare const dialogTokenSet: ComponentTokenSet<"container-color" | "container-elevation" | "container-shape" | "supporting-text-color" | "with-icon-icon-color" | "headline-color">;
|
|
6
6
|
//#endregion
|
|
7
7
|
//# sourceMappingURL=dialog-tokens.d.ts.map
|
|
@@ -2,6 +2,6 @@ import { ComponentTokenSet } from "../../tokens/component.js";
|
|
|
2
2
|
import "../../index.js";
|
|
3
3
|
//#region src/components/menu/menu-tokens.d.ts
|
|
4
4
|
/** The tokens of {@link Menu}: `--md-comp-menus-<part>` and `--md-comp-menus-standard-<part>`. */
|
|
5
|
-
export declare const menuTokenSet: ComponentTokenSet<"container-
|
|
5
|
+
export declare const menuTokenSet: ComponentTokenSet<"container-elevation" | "container-shape">;
|
|
6
6
|
//#endregion
|
|
7
7
|
//# sourceMappingURL=menu-tokens.d.ts.map
|
|
@@ -28,6 +28,12 @@ const DEFAULT_LABELS = {
|
|
|
28
28
|
error: "Error"
|
|
29
29
|
};
|
|
30
30
|
const COMPACT_WINDOW = "(max-width: 599.98px)";
|
|
31
|
+
/**
|
|
32
|
+
* A searchable list's sheet takes the full height it may (above an on-screen keyboard too), so
|
|
33
|
+
* filtering changes the list inside, never the sheet: at its own height it shrank as the
|
|
34
|
+
* results did, and sank behind the keyboard (2026-11-07).
|
|
35
|
+
*/
|
|
36
|
+
const SEARCH_SHEET_HEIGHT = "h-[calc(100dvh_-_72px_-_var(--m3-sheet-keyboard-inset,0px))] min-[641px]:h-[calc(100dvh_-_56px_-_var(--m3-sheet-keyboard-inset,0px))]";
|
|
31
37
|
/** A read-only Select's keyboard finds no option, so arrows and typeahead change nothing. */
|
|
32
38
|
const READ_ONLY_DELEGATE = {
|
|
33
39
|
getFirstKey: () => null,
|
|
@@ -294,7 +300,7 @@ function Select(props) {
|
|
|
294
300
|
skipPartiallyExpanded: true,
|
|
295
301
|
open: state.isOpen,
|
|
296
302
|
onOpenChange: (open) => open ? state.open() : state.close(),
|
|
297
|
-
classNames: classNames?.sheet ? { panel: classNames
|
|
303
|
+
classNames: searchable || classNames?.sheet ? { panel: cn(searchable && SEARCH_SHEET_HEIGHT, classNames?.sheet) } : void 0,
|
|
298
304
|
...typeof label === "string" ? { "aria-label": label } : { "aria-label": rest["aria-label"] ?? "" },
|
|
299
305
|
children: /* @__PURE__ */ jsx("div", {
|
|
300
306
|
...densityAttribute(density),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Select.js","names":[],"sources":["../../../src/components/select/Select.tsx"],"sourcesContent":["'use client';\n\nimport {\n useId,\n useRef,\n useSyncExternalStore,\n type AriaAttributes,\n type CSSProperties,\n type HTMLAttributes,\n type ReactNode,\n type Ref,\n} from 'react';\nimport {\n HiddenSelect,\n mergeProps,\n useButton,\n useFocusRing,\n useHover,\n useObjectRef,\n useSelect,\n type AriaSelectOptions,\n} from 'react-aria';\nimport {\n useSelectState,\n type SelectionMode,\n type SelectProps as StatelySelectProps,\n} from 'react-stately/useSelectState';\nimport { useControlledState } from 'react-stately/useControlledState';\nimport type { Key } from 'react-stately';\nimport { matchesSearch } from '../../utils/search';\nimport { assertCollectionChildren } from '../../utils/assert-collection-children';\nimport { densityAttribute, type Density } from '../../tokens/density';\nimport { cn } from '../../utils/cn';\nimport { renderedDescription } from '../../utils/rendered-description';\nimport {\n ariaFlag,\n pickDescriptionAria,\n splitRootProps,\n type WrapperRootDOMProps,\n} from '../../utils/root-props';\nimport { useTypeaheadDelegate } from '../../utils/typeahead';\nimport { usePreloadLoadingIndicator } from '../loading-indicator/lazy-loading-indicator';\nimport { LazyBottomSheet, usePreloadBottomSheet } from '../sheet/lazy-bottom-sheet';\nimport type { TextFieldVariant } from '../text-field/text-field-styles';\nimport { DropdownArrow, FieldShell, fieldState, type FieldShellClassNames } from './field-shell';\nimport {\n FOCUSABLE_DISABLED_OPTIONS,\n OptionList,\n OptionPopover,\n type OptionListClassNames,\n} from './option-list';\nimport { SearchList } from './search-list';\nimport { useSelectionCap } from './selection-cap';\n\n/** How the options open: a menu under the field (M3), a bottom sheet, or a sheet on phones. */\nexport type SelectPresentation = 'menu' | 'sheet' | 'auto';\n\n/** Words the Select shows or announces; override them for other languages. */\nexport interface SelectLabels {\n /** Shown in an empty list. @default \"No options\" */\n empty: string;\n /** The loading indicator's name. @default \"Loading options\" */\n loading: string;\n /** For more chosen options than fit: `+{count}`. @default (count) => `+${count}` */\n more: (count: number) => string;\n /** With `searchable`: the search field's placeholder and name. @default \"Search\" */\n search: string;\n /** With `searchable`: shown when no option matches the search. @default \"No results\" */\n noResults: string;\n /** The error icon's name (M3: \"Error\"). @default \"Error\" */\n error: string;\n}\n\nconst DEFAULT_LABELS: SelectLabels = {\n empty: 'No options',\n loading: 'Loading options',\n more: (count) => `+${count}`,\n search: 'Search',\n noResults: 'No results',\n error: 'Error',\n};\n\n/** A chosen option, as `renderValue` receives it. */\nexport interface SelectedOption<T> {\n key: Key;\n /** Its plain text (`textValue`, or the item's text). */\n textValue: string;\n /** Its object from `items`, if the options come from `items`. */\n value: T | null;\n}\n\nexport interface SelectClassNames extends FieldShellClassNames, OptionListClassNames {\n /** The text showing the chosen option(s) or the placeholder. */\n value?: string;\n /** With `searchable`: the search field. */\n search?: string;\n /**\n * The bottom sheet the options open in (`presentation=\"sheet\"`, or `\"auto\"` on compact\n * windows): the sheet's surface, as its `classNames.panel`.\n */\n sheet?: string;\n}\n\n/** React Aria props this API names the library's way (`disabled`, `open`…), or leaves out. */\ntype RenamedProps =\n | 'isDisabled'\n | 'isRequired'\n | 'isInvalid'\n | 'isOpen'\n | 'label'\n | 'description'\n | 'validationState'\n // React Stately's deprecated single-selection API; `value` / `defaultValue` / `onChange` replace it.\n | 'selectedKey'\n | 'defaultSelectedKey'\n | 'onSelectionChange'\n | 'shouldCloseOnSelect'\n | 'allowsEmptyCollection';\n\nexport type SelectProps<T extends object, M extends SelectionMode = 'single'> = Omit<\n StatelySelectProps<T, M>,\n RenamedProps\n> &\n Pick<AriaSelectOptions<T, M>, 'id' | 'name' | 'autoComplete' | 'form'> &\n Omit<WrapperRootDOMProps, keyof StatelySelectProps<T, M>> & {\n /** The trigger button's id, for a `<label for>` or an error summary's link. */\n id?: string;\n /** The container style, as `TextField`'s: a filled surface or an outline. @default \"filled\" */\n variant?: TextFieldVariant;\n /**\n * Web density, `0` to `-3`: each step takes 4px off the field (56px at 0, 44px at -3) and\n * off its menu's options (44px at 0, 32px at -3). Left out, it takes the density of the\n * nearest `ThemeProvider` or `ThemeScope`. M3 asks for density to be the reader's choice\n * rather than a default, so prefer setting it there, from a setting.\n * @default 0\n */\n density?: Density;\n /** The field's label. Give `aria-label` instead when there's no visible label. */\n label?: ReactNode;\n /** Accessible name when there's no visible label. */\n 'aria-label'?: string;\n /**\n * Shown in the field when nothing is chosen. With a `label`, it appears once the label\n * floats (on focus), as in `TextField`.\n */\n placeholder?: string;\n /**\n * Helper text below the field. Replaced visually by the error when invalid, and shown in\n * the error colour while the field is invalid.\n */\n supportingText?: ReactNode;\n /** Marks the field invalid (in addition to `validate` and `required`). */\n invalid?: boolean;\n /** A choice is required: adds the `*` to the label and validates on submit. */\n required?: boolean;\n /**\n * Read as `invalid` when that's left out, for a form library that sets the attribute. The\n * trigger is a button, which ARIA doesn't let carry `aria-invalid`, so the state reaches\n * assistive technology as the field's error text, its description.\n */\n 'aria-invalid'?: AriaAttributes['aria-invalid'];\n /**\n * Read as `required` when that's left out, for a form library that sets the attribute: the\n * label's `*`, in the trigger's name (a button can't carry `aria-required`).\n */\n 'aria-required'?: AriaAttributes['aria-required'];\n /** Disables the field: it can't be focused or opened. @default false */\n disabled?: boolean;\n /**\n * The choice can be seen but not changed: the menu doesn't open and the keyboard doesn't\n * change it. The root gets `data-readonly`; say so in the supporting text (M3).\n * @default false\n */\n readOnly?: boolean;\n /** Decorative icon at the start, in a 48px box. */\n leadingIcon?: ReactNode;\n /**\n * The icon shown before the arrow while the field is invalid (M3 strongly recommends one).\n * Defaults to Material Symbols `error`, named by `labels.error`; `false` turns it off.\n */\n errorIcon?: ReactNode | false;\n /** Whether the menu is open (controlled); with `defaultOpen` and `onOpenChange`. */\n open?: boolean;\n /**\n * Whether choosing an option closes the menu.\n * @default true for a single choice, false with `selectionMode=\"multiple\"`\n */\n closeOnSelect?: boolean;\n /**\n * With `selectionMode=\"multiple\"`, the most options that can be chosen; the rest show as\n * disabled until one is removed.\n */\n maxSelections?: number;\n /**\n * `menu` (default) opens an M3 menu under the field on every window, as Compose's exposed\n * dropdown menu does; `sheet` always opens a bottom sheet; `auto` opens a sheet on\n * compact windows and a menu on larger ones, for long lists.\n * @default \"menu\"\n */\n presentation?: SelectPresentation;\n /**\n * A search field at the top of the menu or sheet that filters the options as you type,\n * for long lists (pair it with `presentation=\"auto\"`). The search takes focus in the menu;\n * a sheet shows the list first, so the keyboard doesn't cover it.\n * @default false\n */\n searchable?: boolean;\n /**\n * With `searchable`: whether an option matches the search. Defaults to a match anywhere in\n * its text, ignoring case and accents (\"thai\" finds \"Thailand\"). The option's `key` lets\n * it match on more than its text, e.g. a country's ISO code.\n */\n filter?: (textValue: string, search: string, key: Key) => boolean;\n /** With `searchable`: the typed text (controlled); cleared when the list closes. */\n searchValue?: string;\n /** With `searchable`: the initial typed text, when uncontrolled. @default \"\" */\n defaultSearchValue?: string;\n /**\n * With `searchable`: called as the search changes. Given it, the app filters `items` itself\n * (e.g. on a server, with `loading` meanwhile) and the built-in filter is off.\n */\n onSearchChange?: (search: string) => void;\n /**\n * What the field shows for the chosen option(s), e.g. a dialling code or a flag, instead of\n * their text. Screen readers still hear the options' text.\n */\n renderValue?: (selected: SelectedOption<T>[]) => ReactNode;\n /**\n * Shows a loading indicator in the list, e.g. while options are fetched.\n * The indicator's code loads when it first shows, and ahead of that when the field is\n * hovered or focused while this prop is set at all, so pass `loading={false}` (rather than\n * leaving it out) on a field that loads options.\n */\n loading?: boolean;\n /** Words the Select shows or announces, for other languages. */\n labels?: Partial<SelectLabels>;\n /** Classes for the root element, merged with `classNames.root`; consumer classes win. */\n className?: string;\n /** Classes for each part, by slot. */\n classNames?: SelectClassNames;\n /** Inline styles for the root element. */\n style?: CSSProperties;\n /** Ref to the root `<div>` element. Use `triggerRef` for the field's button. */\n ref?: Ref<HTMLDivElement>;\n /** Ref to the field's button. */\n triggerRef?: Ref<HTMLButtonElement>;\n };\n\nconst COMPACT_WINDOW = '(max-width: 599.98px)';\n\n/** A read-only Select's keyboard finds no option, so arrows and typeahead change nothing. */\nconst READ_ONLY_DELEGATE: NonNullable<AriaSelectOptions<object>['keyboardDelegate']> = {\n getFirstKey: () => null,\n getLastKey: () => null,\n getKeyBelow: () => null,\n getKeyAbove: () => null,\n getKeyForSearch: () => null,\n};\n\nfunction subscribeToWindow(onChange: () => void) {\n const query = window.matchMedia(COMPACT_WINDOW);\n query.addEventListener('change', onChange);\n return () => query.removeEventListener('change', onChange);\n}\n\n/**\n * M3 exposed dropdown menu, read-only (Compose's `ExposedDropdownMenuBox` with a\n * `PrimaryNotEditable` anchor): a text-field-styled button that opens a menu of options.\n * Single or multiple selection, sections, descriptions and icons, typeahead, a hidden\n * native `<select>` for forms, and validation as in `TextField` (React Aria `useSelect`).\n *\n * @example\n * <Select label=\"Sort by\" defaultValue=\"newest\">\n * <SelectItem key=\"newest\">Newest first</SelectItem>\n * <SelectItem key=\"price\">Price, low to high</SelectItem>\n * </Select>\n */\nexport function Select<T extends object, M extends SelectionMode = 'single'>(\n props: SelectProps<T, M>,\n) {\n const {\n variant = 'filled',\n density,\n label,\n placeholder,\n supportingText,\n invalid: invalidProp,\n required: requiredProp,\n 'aria-invalid': ariaInvalid,\n 'aria-required': ariaRequired,\n disabled = false,\n readOnly = false,\n leadingIcon,\n errorIcon,\n open,\n maxSelections,\n presentation = 'menu',\n searchable = false,\n filter = matchesSearch,\n searchValue,\n defaultSearchValue,\n onSearchChange,\n renderValue,\n loading,\n labels: labelsProp,\n className,\n classNames,\n style,\n ref,\n triggerRef: triggerRefProp,\n name,\n autoComplete,\n form,\n errorMessage,\n closeOnSelect,\n ...otherProps\n } = props;\n // Items given as elements (not `items` with a render function) must come from a client.\n if (typeof props.children !== 'function') {\n assertCollectionChildren(props.children, 'Select', 'SelectItem and SelectSection elements');\n }\n const { root: rootData, rest } = splitRootProps(otherProps, { wrapper: true });\n const invalid = invalidProp ?? ariaFlag(ariaInvalid);\n const required = requiredProp ?? ariaFlag(ariaRequired);\n const labels = { ...DEFAULT_LABELS, ...labelsProp };\n const stately = rest as Omit<StatelySelectProps<T, M>, RenamedProps>;\n const cap = useSelectionCap({\n multiple: stately.selectionMode === 'multiple',\n maxSelections,\n value: stately.value,\n defaultValue: stately.defaultValue,\n onChange: stately.onChange,\n });\n const [search, setSearch] = useControlledState(\n searchValue,\n defaultSearchValue ?? '',\n onSearchChange,\n );\n const ariaProps = {\n ...stately,\n ...cap.valueProps,\n // The search starts afresh each time the list opens.\n onOpenChange: (isOpen: boolean) => {\n if (!isOpen && search !== '') setSearch('');\n stately.onOpenChange?.(isOpen);\n },\n label,\n description: supportingText,\n errorMessage,\n isInvalid: invalid,\n isRequired: required,\n isDisabled: disabled,\n ...(closeOnSelect !== undefined && { shouldCloseOnSelect: closeOnSelect }),\n ...FOCUSABLE_DISABLED_OPTIONS,\n ...(readOnly\n ? { isOpen: false, keyboardDelegate: READ_ONLY_DELEGATE }\n : open !== undefined && { isOpen: open }),\n placeholder,\n name,\n autoComplete,\n form,\n } as AriaSelectOptions<T, M>;\n\n const state = useSelectState<T, M>(ariaProps);\n const triggerRef = useObjectRef(triggerRefProp);\n // Typeahead on the closed field moves to the next option starting with the letter (M3),\n // and still skips disabled options.\n const typeaheadDelegate = useTypeaheadDelegate(state, triggerRef, {\n disabledBehavior: 'all',\n currentKey: state.selectionManager.selectionMode === 'single' ? state.selectedKey : null,\n });\n const containerRef = useRef<HTMLDivElement>(null);\n // Set by a press outside the menu and the field: the focus the menu hands back to the\n // field on closing (WAI-ARIA, right after Escape or a choice) is let go, so one press\n // outside leaves the field, as it does any other field.\n const releaseFocusRef = useRef(false);\n // Chrome's touch adjustment can split a tap between the trigger and the padding just above\n // or below it (the 8px either side of the value line): `pointerdown` reaches the trigger\n // while `pointerup` or the click lands beside it, so React Aria cancels the press or never\n // sees it, and about a third of the field ignored a finger (2026-10-17). A touch that\n // starts on the trigger but doesn't end there (its `pointerup` and its click both on it)\n // opens the menu on its click instead. The mouse never gets here: it opens on the press\n // start.\n const touchOnTriggerRef = useRef(false);\n const touchLeftTriggerRef = useRef(false);\n const onTrigger = (target: EventTarget) => Boolean(triggerRef.current?.contains(target as Node));\n const strayTouchProps: HTMLAttributes<HTMLDivElement> = {\n onPointerDownCapture: (event) => {\n touchOnTriggerRef.current = event.pointerType !== 'mouse' && onTrigger(event.target);\n touchLeftTriggerRef.current = false;\n },\n onPointerUpCapture: (event) => {\n touchLeftTriggerRef.current = !onTrigger(event.target);\n },\n onClickCapture: (event) => {\n if (!touchOnTriggerRef.current) return;\n touchOnTriggerRef.current = false;\n if (!touchLeftTriggerRef.current && onTrigger(event.target)) return;\n if (disabled || state.isOpen) return;\n releaseFocusRef.current = false;\n triggerRef.current?.focus({ preventScroll: true });\n if (!readOnly) state.open();\n },\n };\n const listBoxRef = useRef<HTMLUListElement>(null);\n const popoverRef = useRef<HTMLDivElement>(null);\n const dialogId = useId();\n const {\n labelProps,\n triggerProps,\n valueProps,\n menuProps,\n descriptionProps,\n errorMessageProps,\n isInvalid,\n validationErrors,\n validationDetails,\n } = useSelect<T, M>(\n readOnly ? ariaProps : { ...ariaProps, keyboardDelegate: typeaheadDelegate },\n state,\n triggerRef,\n );\n const isCompact = useSyncExternalStore(\n subscribeToWindow,\n () => window.matchMedia(COMPACT_WINDOW).matches,\n () => false,\n );\n const asSheet = presentation === 'sheet' || (presentation === 'auto' && isCompact);\n // The sheet's code loads on first open, or ahead of it when the field is hovered or focused.\n usePreloadBottomSheet(containerRef, asSheet);\n // A field that takes `loading` (it loads options) preloads the indicator on hover or focus.\n usePreloadLoadingIndicator(containerRef, loading !== undefined);\n // `useSelect`'s trigger props are React Aria button props; `useButton` makes them DOM props.\n const { buttonProps } = useButton(\n // A searchable field opens a dialog holding the search and the list (as PhoneField's).\n searchable\n ? {\n ...triggerProps,\n 'aria-haspopup': 'dialog',\n 'aria-controls': state.isOpen && !asSheet ? dialogId : undefined,\n }\n : triggerProps,\n triggerRef,\n );\n const { focusProps, isFocusVisible, isFocused } = useFocusRing();\n // The trigger sets its own widget attributes; an app's description ones join them.\n const fieldButtonProps = mergeProps(pickDescriptionAria(rest), buttonProps, focusProps);\n const { hoverProps, isHovered } = useHover({ isDisabled: disabled });\n\n // In the list's order, not the order they were picked, so the field reads like the list.\n const chosenOptions: SelectedOption<T>[] = [...state.collection.getKeys()]\n .filter((key) => state.selectionManager.isSelected(key))\n .map((key) => {\n const item = state.collection.getItem(key);\n return { key, textValue: item?.textValue ?? '', value: item?.value ?? null };\n });\n const chosen = chosenOptions.map((option) => option.textValue);\n const valueText =\n chosen.length > 2\n ? `${chosen.slice(0, 2).join(', ')} ${labels.more(chosen.length - 2)}`\n : chosen.join(', ');\n const hasValue = chosen.length > 0;\n const hasLabel = label !== undefined && label !== null;\n const focused = isFocused || state.isOpen;\n const resolvedError =\n typeof errorMessage === 'function'\n ? errorMessage({ isInvalid, validationErrors, validationDetails })\n : (errorMessage ?? validationErrors.join(' '));\n\n const searchLabelling = hasLabel\n ? { 'aria-labelledby': labelProps.id }\n : { 'aria-label': (rest as { 'aria-label'?: string })['aria-label'] };\n const list = searchable ? (\n <SearchList\n listProps={{\n ...(stately.items !== undefined && { items: stately.items }),\n children: stately.children,\n ...(stately.disabledKeys !== undefined && { disabledKeys: stately.disabledKeys }),\n selectionMode: stately.selectionMode ?? 'single',\n // A single choice stays chosen when pressed again, as in a non-searchable Select.\n disallowEmptySelection: stately.selectionMode !== 'multiple',\n selectedKeys: state.selectionManager.selectedKeys,\n onSelectionChange: (keys) => {\n state.selectionManager.setSelectedKeys(keys === 'all' ? [] : keys);\n if (stately.selectionMode !== 'multiple') state.close();\n },\n }}\n search={search}\n onSearchChange={setSearch}\n filter={onSearchChange ? null : filter}\n autoFocusSearch={!asSheet}\n labelling={searchLabelling}\n dialogProps={asSheet ? undefined : { id: dialogId, role: 'dialog', ...searchLabelling }}\n labels={{ search: labels.search, noResults: labels.noResults, loading: labels.loading }}\n loading={loading ?? false}\n isBlocked={cap.isBlocked}\n embedded={asSheet}\n classNames={classNames}\n />\n ) : (\n <OptionList\n embedded={asSheet}\n listBoxProps={menuProps}\n state={state}\n listBoxRef={listBoxRef}\n emptyLabel={labels.empty}\n loading={loading}\n loadingLabel={labels.loading}\n isBlocked={cap.isBlocked}\n classNames={classNames}\n />\n );\n\n return (\n <>\n <FieldShell\n variant={variant}\n density={density}\n label={label}\n labelProps={labelProps}\n required={required}\n state={fieldState({ disabled, invalid: isInvalid, focused, hovered: isHovered })}\n floated={hasValue || focused}\n open={state.isOpen}\n leadingIcon={leadingIcon}\n trailing={<DropdownArrow open={state.isOpen} />}\n errorIcon={errorIcon}\n errorIconLabel={labels.error}\n readOnly={readOnly}\n supportingText={supportingText}\n descriptionProps={descriptionProps}\n errorText={resolvedError}\n errorMessageProps={errorMessageProps}\n showError={isInvalid && Boolean(resolvedError)}\n containerRef={containerRef}\n // No handler is shared: the hover ones and the touch fallback's capture ones.\n containerProps={{ ...hoverProps, ...strayTouchProps }}\n onContainerPointerDown={(event) => {\n // A press anywhere on the field (not only its text) opens it, like Compose.\n if (disabled || triggerRef.current?.contains(event.target as Node)) return;\n event.preventDefault();\n releaseFocusRef.current = false;\n triggerRef.current?.focus();\n if (!readOnly) state.toggle();\n }}\n rootProps={{ ...rootData, 'data-focus-visible': isFocusVisible || undefined }}\n rootRef={ref}\n className={className}\n classNames={classNames}\n style={style}\n >\n {/* The native select is for forms: submitting under `name`, autofill and native\n validation. Without them it's left out, so a long list's text (e.g. country names\n from Intl, which can differ between server and browser) isn't rendered twice. */}\n {name || autoComplete || form || stately.validationBehavior === 'native' ? (\n <HiddenSelect\n state={state}\n triggerRef={triggerRef}\n label={label}\n name={name}\n isDisabled={disabled}\n {...(autoComplete && { autoComplete })}\n {...(form && { form })}\n />\n ) : null}\n <button\n {...fieldButtonProps}\n aria-describedby={renderedDescription(fieldButtonProps['aria-describedby'], [\n !supportingText && descriptionProps.id,\n !(isInvalid && resolvedError) && errorMessageProps.id,\n ])}\n onFocus={(event) => {\n fieldButtonProps.onFocus?.(event);\n if (!releaseFocusRef.current) return;\n releaseFocusRef.current = false;\n triggerRef.current?.blur();\n }}\n // Using the field again means it's wanted, whatever came before.\n onPointerDown={(event) => {\n releaseFocusRef.current = false;\n fieldButtonProps.onPointerDown?.(event);\n }}\n onKeyDown={(event) => {\n releaseFocusRef.current = false;\n fieldButtonProps.onKeyDown?.(event);\n }}\n ref={triggerRef}\n type=\"button\"\n className={cn(\n 'flex min-w-0 flex-1 cursor-pointer items-center self-stretch bg-transparent p-0 text-start outline-none',\n 'disabled:cursor-default',\n )}\n >\n <span\n {...valueProps}\n className={cn(\n 'min-w-0 flex-1 truncate text-body-large',\n hasValue\n ? 'text-[color:var(--_md-comp-text-field-input-text-color)]'\n : 'text-on-surface-variant',\n // With a label, the placeholder shows once the label floats (as TextField).\n !hasValue && hasLabel && 'opacity-0 group-data-floated/field:opacity-100',\n 'group-data-[field-state=disabled]/field:text-on-surface/38',\n classNames?.value,\n )}\n >\n {hasValue && renderValue ? (\n <>\n <span aria-hidden=\"true\">{renderValue(chosenOptions)}</span>\n <span className=\"sr-only\">{valueText}</span>\n </>\n ) : hasValue ? (\n valueText\n ) : (\n (placeholder ?? ' ')\n )}\n </span>\n </button>\n </FieldShell>\n {asSheet ? (\n <LazyBottomSheet\n // Opens at its own height (up to the window, the list scrolling inside), never half\n // the window, which left the last options off-screen on a short phone.\n skipPartiallyExpanded\n open={state.isOpen}\n onOpenChange={(open) => (open ? state.open() : state.close())}\n classNames={classNames?.sheet ? { panel: classNames.sheet } : undefined}\n {...(typeof label === 'string'\n ? { 'aria-label': label }\n : { 'aria-label': (rest as { 'aria-label'?: string })['aria-label'] ?? '' })}\n >\n <div\n {...densityAttribute(density)}\n className=\"flex min-h-0 flex-1 flex-col px-[8px] pb-[16px]\"\n >\n {list}\n </div>\n </LazyBottomSheet>\n ) : (\n <OptionPopover\n state={state}\n triggerRef={containerRef}\n popoverRef={popoverRef}\n density={density}\n onPressOutside={() => {\n releaseFocusRef.current = true;\n }}\n // Room to type in a searchable menu, however narrow the field.\n minWidth={searchable ? 280 : 0}\n >\n {list}\n </OptionPopover>\n )}\n </>\n );\n}\n\nexport type { Key as SelectKey } from 'react-stately';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Select.js","names":[],"sources":["../../../src/components/select/Select.tsx"],"sourcesContent":["'use client';\n\nimport {\n useId,\n useRef,\n useSyncExternalStore,\n type AriaAttributes,\n type CSSProperties,\n type HTMLAttributes,\n type ReactNode,\n type Ref,\n} from 'react';\nimport {\n HiddenSelect,\n mergeProps,\n useButton,\n useFocusRing,\n useHover,\n useObjectRef,\n useSelect,\n type AriaSelectOptions,\n} from 'react-aria';\nimport {\n useSelectState,\n type SelectionMode,\n type SelectProps as StatelySelectProps,\n} from 'react-stately/useSelectState';\nimport { useControlledState } from 'react-stately/useControlledState';\nimport type { Key } from 'react-stately';\nimport { matchesSearch } from '../../utils/search';\nimport { assertCollectionChildren } from '../../utils/assert-collection-children';\nimport { densityAttribute, type Density } from '../../tokens/density';\nimport { cn } from '../../utils/cn';\nimport { renderedDescription } from '../../utils/rendered-description';\nimport {\n ariaFlag,\n pickDescriptionAria,\n splitRootProps,\n type WrapperRootDOMProps,\n} from '../../utils/root-props';\nimport { useTypeaheadDelegate } from '../../utils/typeahead';\nimport { usePreloadLoadingIndicator } from '../loading-indicator/lazy-loading-indicator';\nimport { LazyBottomSheet, usePreloadBottomSheet } from '../sheet/lazy-bottom-sheet';\nimport type { TextFieldVariant } from '../text-field/text-field-styles';\nimport { DropdownArrow, FieldShell, fieldState, type FieldShellClassNames } from './field-shell';\nimport {\n FOCUSABLE_DISABLED_OPTIONS,\n OptionList,\n OptionPopover,\n type OptionListClassNames,\n} from './option-list';\nimport { SearchList } from './search-list';\nimport { useSelectionCap } from './selection-cap';\n\n/** How the options open: a menu under the field (M3), a bottom sheet, or a sheet on phones. */\nexport type SelectPresentation = 'menu' | 'sheet' | 'auto';\n\n/** Words the Select shows or announces; override them for other languages. */\nexport interface SelectLabels {\n /** Shown in an empty list. @default \"No options\" */\n empty: string;\n /** The loading indicator's name. @default \"Loading options\" */\n loading: string;\n /** For more chosen options than fit: `+{count}`. @default (count) => `+${count}` */\n more: (count: number) => string;\n /** With `searchable`: the search field's placeholder and name. @default \"Search\" */\n search: string;\n /** With `searchable`: shown when no option matches the search. @default \"No results\" */\n noResults: string;\n /** The error icon's name (M3: \"Error\"). @default \"Error\" */\n error: string;\n}\n\nconst DEFAULT_LABELS: SelectLabels = {\n empty: 'No options',\n loading: 'Loading options',\n more: (count) => `+${count}`,\n search: 'Search',\n noResults: 'No results',\n error: 'Error',\n};\n\n/** A chosen option, as `renderValue` receives it. */\nexport interface SelectedOption<T> {\n key: Key;\n /** Its plain text (`textValue`, or the item's text). */\n textValue: string;\n /** Its object from `items`, if the options come from `items`. */\n value: T | null;\n}\n\nexport interface SelectClassNames extends FieldShellClassNames, OptionListClassNames {\n /** The text showing the chosen option(s) or the placeholder. */\n value?: string;\n /** With `searchable`: the search field. */\n search?: string;\n /**\n * The bottom sheet the options open in (`presentation=\"sheet\"`, or `\"auto\"` on compact\n * windows): the sheet's surface, as its `classNames.panel`.\n */\n sheet?: string;\n}\n\n/** React Aria props this API names the library's way (`disabled`, `open`…), or leaves out. */\ntype RenamedProps =\n | 'isDisabled'\n | 'isRequired'\n | 'isInvalid'\n | 'isOpen'\n | 'label'\n | 'description'\n | 'validationState'\n // React Stately's deprecated single-selection API; `value` / `defaultValue` / `onChange` replace it.\n | 'selectedKey'\n | 'defaultSelectedKey'\n | 'onSelectionChange'\n | 'shouldCloseOnSelect'\n | 'allowsEmptyCollection';\n\nexport type SelectProps<T extends object, M extends SelectionMode = 'single'> = Omit<\n StatelySelectProps<T, M>,\n RenamedProps\n> &\n Pick<AriaSelectOptions<T, M>, 'id' | 'name' | 'autoComplete' | 'form'> &\n Omit<WrapperRootDOMProps, keyof StatelySelectProps<T, M>> & {\n /** The trigger button's id, for a `<label for>` or an error summary's link. */\n id?: string;\n /** The container style, as `TextField`'s: a filled surface or an outline. @default \"filled\" */\n variant?: TextFieldVariant;\n /**\n * Web density, `0` to `-3`: each step takes 4px off the field (56px at 0, 44px at -3) and\n * off its menu's options (44px at 0, 32px at -3). Left out, it takes the density of the\n * nearest `ThemeProvider` or `ThemeScope`. M3 asks for density to be the reader's choice\n * rather than a default, so prefer setting it there, from a setting.\n * @default 0\n */\n density?: Density;\n /** The field's label. Give `aria-label` instead when there's no visible label. */\n label?: ReactNode;\n /** Accessible name when there's no visible label. */\n 'aria-label'?: string;\n /**\n * Shown in the field when nothing is chosen. With a `label`, it appears once the label\n * floats (on focus), as in `TextField`.\n */\n placeholder?: string;\n /**\n * Helper text below the field. Replaced visually by the error when invalid, and shown in\n * the error colour while the field is invalid.\n */\n supportingText?: ReactNode;\n /** Marks the field invalid (in addition to `validate` and `required`). */\n invalid?: boolean;\n /** A choice is required: adds the `*` to the label and validates on submit. */\n required?: boolean;\n /**\n * Read as `invalid` when that's left out, for a form library that sets the attribute. The\n * trigger is a button, which ARIA doesn't let carry `aria-invalid`, so the state reaches\n * assistive technology as the field's error text, its description.\n */\n 'aria-invalid'?: AriaAttributes['aria-invalid'];\n /**\n * Read as `required` when that's left out, for a form library that sets the attribute: the\n * label's `*`, in the trigger's name (a button can't carry `aria-required`).\n */\n 'aria-required'?: AriaAttributes['aria-required'];\n /** Disables the field: it can't be focused or opened. @default false */\n disabled?: boolean;\n /**\n * The choice can be seen but not changed: the menu doesn't open and the keyboard doesn't\n * change it. The root gets `data-readonly`; say so in the supporting text (M3).\n * @default false\n */\n readOnly?: boolean;\n /** Decorative icon at the start, in a 48px box. */\n leadingIcon?: ReactNode;\n /**\n * The icon shown before the arrow while the field is invalid (M3 strongly recommends one).\n * Defaults to Material Symbols `error`, named by `labels.error`; `false` turns it off.\n */\n errorIcon?: ReactNode | false;\n /** Whether the menu is open (controlled); with `defaultOpen` and `onOpenChange`. */\n open?: boolean;\n /**\n * Whether choosing an option closes the menu.\n * @default true for a single choice, false with `selectionMode=\"multiple\"`\n */\n closeOnSelect?: boolean;\n /**\n * With `selectionMode=\"multiple\"`, the most options that can be chosen; the rest show as\n * disabled until one is removed.\n */\n maxSelections?: number;\n /**\n * `menu` (default) opens an M3 menu under the field on every window, as Compose's exposed\n * dropdown menu does; `sheet` always opens a bottom sheet; `auto` opens a sheet on\n * compact windows and a menu on larger ones, for long lists.\n * @default \"menu\"\n */\n presentation?: SelectPresentation;\n /**\n * A search field at the top of the menu or sheet that filters the options as you type,\n * for long lists (pair it with `presentation=\"auto\"`). The search takes focus in the menu;\n * a sheet shows the list first, so the keyboard doesn't cover it.\n * @default false\n */\n searchable?: boolean;\n /**\n * With `searchable`: whether an option matches the search. Defaults to a match anywhere in\n * its text, ignoring case and accents (\"thai\" finds \"Thailand\"). The option's `key` lets\n * it match on more than its text, e.g. a country's ISO code.\n */\n filter?: (textValue: string, search: string, key: Key) => boolean;\n /** With `searchable`: the typed text (controlled); cleared when the list closes. */\n searchValue?: string;\n /** With `searchable`: the initial typed text, when uncontrolled. @default \"\" */\n defaultSearchValue?: string;\n /**\n * With `searchable`: called as the search changes. Given it, the app filters `items` itself\n * (e.g. on a server, with `loading` meanwhile) and the built-in filter is off.\n */\n onSearchChange?: (search: string) => void;\n /**\n * What the field shows for the chosen option(s), e.g. a dialling code or a flag, instead of\n * their text. Screen readers still hear the options' text.\n */\n renderValue?: (selected: SelectedOption<T>[]) => ReactNode;\n /**\n * Shows a loading indicator in the list, e.g. while options are fetched.\n * The indicator's code loads when it first shows, and ahead of that when the field is\n * hovered or focused while this prop is set at all, so pass `loading={false}` (rather than\n * leaving it out) on a field that loads options.\n */\n loading?: boolean;\n /** Words the Select shows or announces, for other languages. */\n labels?: Partial<SelectLabels>;\n /** Classes for the root element, merged with `classNames.root`; consumer classes win. */\n className?: string;\n /** Classes for each part, by slot. */\n classNames?: SelectClassNames;\n /** Inline styles for the root element. */\n style?: CSSProperties;\n /** Ref to the root `<div>` element. Use `triggerRef` for the field's button. */\n ref?: Ref<HTMLDivElement>;\n /** Ref to the field's button. */\n triggerRef?: Ref<HTMLButtonElement>;\n };\n\nconst COMPACT_WINDOW = '(max-width: 599.98px)';\n\n/**\n * A searchable list's sheet takes the full height it may (above an on-screen keyboard too), so\n * filtering changes the list inside, never the sheet: at its own height it shrank as the\n * results did, and sank behind the keyboard (2026-11-07).\n */\nconst SEARCH_SHEET_HEIGHT =\n 'h-[calc(100dvh_-_72px_-_var(--m3-sheet-keyboard-inset,0px))] min-[641px]:h-[calc(100dvh_-_56px_-_var(--m3-sheet-keyboard-inset,0px))]';\n\n/** A read-only Select's keyboard finds no option, so arrows and typeahead change nothing. */\nconst READ_ONLY_DELEGATE: NonNullable<AriaSelectOptions<object>['keyboardDelegate']> = {\n getFirstKey: () => null,\n getLastKey: () => null,\n getKeyBelow: () => null,\n getKeyAbove: () => null,\n getKeyForSearch: () => null,\n};\n\nfunction subscribeToWindow(onChange: () => void) {\n const query = window.matchMedia(COMPACT_WINDOW);\n query.addEventListener('change', onChange);\n return () => query.removeEventListener('change', onChange);\n}\n\n/**\n * M3 exposed dropdown menu, read-only (Compose's `ExposedDropdownMenuBox` with a\n * `PrimaryNotEditable` anchor): a text-field-styled button that opens a menu of options.\n * Single or multiple selection, sections, descriptions and icons, typeahead, a hidden\n * native `<select>` for forms, and validation as in `TextField` (React Aria `useSelect`).\n *\n * @example\n * <Select label=\"Sort by\" defaultValue=\"newest\">\n * <SelectItem key=\"newest\">Newest first</SelectItem>\n * <SelectItem key=\"price\">Price, low to high</SelectItem>\n * </Select>\n */\nexport function Select<T extends object, M extends SelectionMode = 'single'>(\n props: SelectProps<T, M>,\n) {\n const {\n variant = 'filled',\n density,\n label,\n placeholder,\n supportingText,\n invalid: invalidProp,\n required: requiredProp,\n 'aria-invalid': ariaInvalid,\n 'aria-required': ariaRequired,\n disabled = false,\n readOnly = false,\n leadingIcon,\n errorIcon,\n open,\n maxSelections,\n presentation = 'menu',\n searchable = false,\n filter = matchesSearch,\n searchValue,\n defaultSearchValue,\n onSearchChange,\n renderValue,\n loading,\n labels: labelsProp,\n className,\n classNames,\n style,\n ref,\n triggerRef: triggerRefProp,\n name,\n autoComplete,\n form,\n errorMessage,\n closeOnSelect,\n ...otherProps\n } = props;\n // Items given as elements (not `items` with a render function) must come from a client.\n if (typeof props.children !== 'function') {\n assertCollectionChildren(props.children, 'Select', 'SelectItem and SelectSection elements');\n }\n const { root: rootData, rest } = splitRootProps(otherProps, { wrapper: true });\n const invalid = invalidProp ?? ariaFlag(ariaInvalid);\n const required = requiredProp ?? ariaFlag(ariaRequired);\n const labels = { ...DEFAULT_LABELS, ...labelsProp };\n const stately = rest as Omit<StatelySelectProps<T, M>, RenamedProps>;\n const cap = useSelectionCap({\n multiple: stately.selectionMode === 'multiple',\n maxSelections,\n value: stately.value,\n defaultValue: stately.defaultValue,\n onChange: stately.onChange,\n });\n const [search, setSearch] = useControlledState(\n searchValue,\n defaultSearchValue ?? '',\n onSearchChange,\n );\n const ariaProps = {\n ...stately,\n ...cap.valueProps,\n // The search starts afresh each time the list opens.\n onOpenChange: (isOpen: boolean) => {\n if (!isOpen && search !== '') setSearch('');\n stately.onOpenChange?.(isOpen);\n },\n label,\n description: supportingText,\n errorMessage,\n isInvalid: invalid,\n isRequired: required,\n isDisabled: disabled,\n ...(closeOnSelect !== undefined && { shouldCloseOnSelect: closeOnSelect }),\n ...FOCUSABLE_DISABLED_OPTIONS,\n ...(readOnly\n ? { isOpen: false, keyboardDelegate: READ_ONLY_DELEGATE }\n : open !== undefined && { isOpen: open }),\n placeholder,\n name,\n autoComplete,\n form,\n } as AriaSelectOptions<T, M>;\n\n const state = useSelectState<T, M>(ariaProps);\n const triggerRef = useObjectRef(triggerRefProp);\n // Typeahead on the closed field moves to the next option starting with the letter (M3),\n // and still skips disabled options.\n const typeaheadDelegate = useTypeaheadDelegate(state, triggerRef, {\n disabledBehavior: 'all',\n currentKey: state.selectionManager.selectionMode === 'single' ? state.selectedKey : null,\n });\n const containerRef = useRef<HTMLDivElement>(null);\n // Set by a press outside the menu and the field: the focus the menu hands back to the\n // field on closing (WAI-ARIA, right after Escape or a choice) is let go, so one press\n // outside leaves the field, as it does any other field.\n const releaseFocusRef = useRef(false);\n // Chrome's touch adjustment can split a tap between the trigger and the padding just above\n // or below it (the 8px either side of the value line): `pointerdown` reaches the trigger\n // while `pointerup` or the click lands beside it, so React Aria cancels the press or never\n // sees it, and about a third of the field ignored a finger (2026-10-17). A touch that\n // starts on the trigger but doesn't end there (its `pointerup` and its click both on it)\n // opens the menu on its click instead. The mouse never gets here: it opens on the press\n // start.\n const touchOnTriggerRef = useRef(false);\n const touchLeftTriggerRef = useRef(false);\n const onTrigger = (target: EventTarget) => Boolean(triggerRef.current?.contains(target as Node));\n const strayTouchProps: HTMLAttributes<HTMLDivElement> = {\n onPointerDownCapture: (event) => {\n touchOnTriggerRef.current = event.pointerType !== 'mouse' && onTrigger(event.target);\n touchLeftTriggerRef.current = false;\n },\n onPointerUpCapture: (event) => {\n touchLeftTriggerRef.current = !onTrigger(event.target);\n },\n onClickCapture: (event) => {\n if (!touchOnTriggerRef.current) return;\n touchOnTriggerRef.current = false;\n if (!touchLeftTriggerRef.current && onTrigger(event.target)) return;\n if (disabled || state.isOpen) return;\n releaseFocusRef.current = false;\n triggerRef.current?.focus({ preventScroll: true });\n if (!readOnly) state.open();\n },\n };\n const listBoxRef = useRef<HTMLUListElement>(null);\n const popoverRef = useRef<HTMLDivElement>(null);\n const dialogId = useId();\n const {\n labelProps,\n triggerProps,\n valueProps,\n menuProps,\n descriptionProps,\n errorMessageProps,\n isInvalid,\n validationErrors,\n validationDetails,\n } = useSelect<T, M>(\n readOnly ? ariaProps : { ...ariaProps, keyboardDelegate: typeaheadDelegate },\n state,\n triggerRef,\n );\n const isCompact = useSyncExternalStore(\n subscribeToWindow,\n () => window.matchMedia(COMPACT_WINDOW).matches,\n () => false,\n );\n const asSheet = presentation === 'sheet' || (presentation === 'auto' && isCompact);\n // The sheet's code loads on first open, or ahead of it when the field is hovered or focused.\n usePreloadBottomSheet(containerRef, asSheet);\n // A field that takes `loading` (it loads options) preloads the indicator on hover or focus.\n usePreloadLoadingIndicator(containerRef, loading !== undefined);\n // `useSelect`'s trigger props are React Aria button props; `useButton` makes them DOM props.\n const { buttonProps } = useButton(\n // A searchable field opens a dialog holding the search and the list (as PhoneField's).\n searchable\n ? {\n ...triggerProps,\n 'aria-haspopup': 'dialog',\n 'aria-controls': state.isOpen && !asSheet ? dialogId : undefined,\n }\n : triggerProps,\n triggerRef,\n );\n const { focusProps, isFocusVisible, isFocused } = useFocusRing();\n // The trigger sets its own widget attributes; an app's description ones join them.\n const fieldButtonProps = mergeProps(pickDescriptionAria(rest), buttonProps, focusProps);\n const { hoverProps, isHovered } = useHover({ isDisabled: disabled });\n\n // In the list's order, not the order they were picked, so the field reads like the list.\n const chosenOptions: SelectedOption<T>[] = [...state.collection.getKeys()]\n .filter((key) => state.selectionManager.isSelected(key))\n .map((key) => {\n const item = state.collection.getItem(key);\n return { key, textValue: item?.textValue ?? '', value: item?.value ?? null };\n });\n const chosen = chosenOptions.map((option) => option.textValue);\n const valueText =\n chosen.length > 2\n ? `${chosen.slice(0, 2).join(', ')} ${labels.more(chosen.length - 2)}`\n : chosen.join(', ');\n const hasValue = chosen.length > 0;\n const hasLabel = label !== undefined && label !== null;\n const focused = isFocused || state.isOpen;\n const resolvedError =\n typeof errorMessage === 'function'\n ? errorMessage({ isInvalid, validationErrors, validationDetails })\n : (errorMessage ?? validationErrors.join(' '));\n\n const searchLabelling = hasLabel\n ? { 'aria-labelledby': labelProps.id }\n : { 'aria-label': (rest as { 'aria-label'?: string })['aria-label'] };\n const list = searchable ? (\n <SearchList\n listProps={{\n ...(stately.items !== undefined && { items: stately.items }),\n children: stately.children,\n ...(stately.disabledKeys !== undefined && { disabledKeys: stately.disabledKeys }),\n selectionMode: stately.selectionMode ?? 'single',\n // A single choice stays chosen when pressed again, as in a non-searchable Select.\n disallowEmptySelection: stately.selectionMode !== 'multiple',\n selectedKeys: state.selectionManager.selectedKeys,\n onSelectionChange: (keys) => {\n state.selectionManager.setSelectedKeys(keys === 'all' ? [] : keys);\n if (stately.selectionMode !== 'multiple') state.close();\n },\n }}\n search={search}\n onSearchChange={setSearch}\n filter={onSearchChange ? null : filter}\n autoFocusSearch={!asSheet}\n labelling={searchLabelling}\n dialogProps={asSheet ? undefined : { id: dialogId, role: 'dialog', ...searchLabelling }}\n labels={{ search: labels.search, noResults: labels.noResults, loading: labels.loading }}\n loading={loading ?? false}\n isBlocked={cap.isBlocked}\n embedded={asSheet}\n classNames={classNames}\n />\n ) : (\n <OptionList\n embedded={asSheet}\n listBoxProps={menuProps}\n state={state}\n listBoxRef={listBoxRef}\n emptyLabel={labels.empty}\n loading={loading}\n loadingLabel={labels.loading}\n isBlocked={cap.isBlocked}\n classNames={classNames}\n />\n );\n\n return (\n <>\n <FieldShell\n variant={variant}\n density={density}\n label={label}\n labelProps={labelProps}\n required={required}\n state={fieldState({ disabled, invalid: isInvalid, focused, hovered: isHovered })}\n floated={hasValue || focused}\n open={state.isOpen}\n leadingIcon={leadingIcon}\n trailing={<DropdownArrow open={state.isOpen} />}\n errorIcon={errorIcon}\n errorIconLabel={labels.error}\n readOnly={readOnly}\n supportingText={supportingText}\n descriptionProps={descriptionProps}\n errorText={resolvedError}\n errorMessageProps={errorMessageProps}\n showError={isInvalid && Boolean(resolvedError)}\n containerRef={containerRef}\n // No handler is shared: the hover ones and the touch fallback's capture ones.\n containerProps={{ ...hoverProps, ...strayTouchProps }}\n onContainerPointerDown={(event) => {\n // A press anywhere on the field (not only its text) opens it, like Compose.\n if (disabled || triggerRef.current?.contains(event.target as Node)) return;\n event.preventDefault();\n releaseFocusRef.current = false;\n triggerRef.current?.focus();\n if (!readOnly) state.toggle();\n }}\n rootProps={{ ...rootData, 'data-focus-visible': isFocusVisible || undefined }}\n rootRef={ref}\n className={className}\n classNames={classNames}\n style={style}\n >\n {/* The native select is for forms: submitting under `name`, autofill and native\n validation. Without them it's left out, so a long list's text (e.g. country names\n from Intl, which can differ between server and browser) isn't rendered twice. */}\n {name || autoComplete || form || stately.validationBehavior === 'native' ? (\n <HiddenSelect\n state={state}\n triggerRef={triggerRef}\n label={label}\n name={name}\n isDisabled={disabled}\n {...(autoComplete && { autoComplete })}\n {...(form && { form })}\n />\n ) : null}\n <button\n {...fieldButtonProps}\n aria-describedby={renderedDescription(fieldButtonProps['aria-describedby'], [\n !supportingText && descriptionProps.id,\n !(isInvalid && resolvedError) && errorMessageProps.id,\n ])}\n onFocus={(event) => {\n fieldButtonProps.onFocus?.(event);\n if (!releaseFocusRef.current) return;\n releaseFocusRef.current = false;\n triggerRef.current?.blur();\n }}\n // Using the field again means it's wanted, whatever came before.\n onPointerDown={(event) => {\n releaseFocusRef.current = false;\n fieldButtonProps.onPointerDown?.(event);\n }}\n onKeyDown={(event) => {\n releaseFocusRef.current = false;\n fieldButtonProps.onKeyDown?.(event);\n }}\n ref={triggerRef}\n type=\"button\"\n className={cn(\n 'flex min-w-0 flex-1 cursor-pointer items-center self-stretch bg-transparent p-0 text-start outline-none',\n 'disabled:cursor-default',\n )}\n >\n <span\n {...valueProps}\n className={cn(\n 'min-w-0 flex-1 truncate text-body-large',\n hasValue\n ? 'text-[color:var(--_md-comp-text-field-input-text-color)]'\n : 'text-on-surface-variant',\n // With a label, the placeholder shows once the label floats (as TextField).\n !hasValue && hasLabel && 'opacity-0 group-data-floated/field:opacity-100',\n 'group-data-[field-state=disabled]/field:text-on-surface/38',\n classNames?.value,\n )}\n >\n {hasValue && renderValue ? (\n <>\n <span aria-hidden=\"true\">{renderValue(chosenOptions)}</span>\n <span className=\"sr-only\">{valueText}</span>\n </>\n ) : hasValue ? (\n valueText\n ) : (\n (placeholder ?? ' ')\n )}\n </span>\n </button>\n </FieldShell>\n {asSheet ? (\n <LazyBottomSheet\n // Opens at its own height (up to the window, the list scrolling inside), never half\n // the window, which left the last options off-screen on a short phone.\n skipPartiallyExpanded\n open={state.isOpen}\n onOpenChange={(open) => (open ? state.open() : state.close())}\n classNames={\n searchable || classNames?.sheet\n ? { panel: cn(searchable && SEARCH_SHEET_HEIGHT, classNames?.sheet) }\n : undefined\n }\n {...(typeof label === 'string'\n ? { 'aria-label': label }\n : { 'aria-label': (rest as { 'aria-label'?: string })['aria-label'] ?? '' })}\n >\n <div\n {...densityAttribute(density)}\n className=\"flex min-h-0 flex-1 flex-col px-[8px] pb-[16px]\"\n >\n {list}\n </div>\n </LazyBottomSheet>\n ) : (\n <OptionPopover\n state={state}\n triggerRef={containerRef}\n popoverRef={popoverRef}\n density={density}\n onPressOutside={() => {\n releaseFocusRef.current = true;\n }}\n // Room to type in a searchable menu, however narrow the field.\n minWidth={searchable ? 280 : 0}\n >\n {list}\n </OptionPopover>\n )}\n </>\n );\n}\n\nexport type { Key as SelectKey } from 'react-stately';\n"],"mappings":"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"}
|
|
@@ -15,6 +15,7 @@ import { contentGesture, scrolledAbove } from "./bottom-sheet-gesture.js";
|
|
|
15
15
|
import { EXIT_DISTANCE, releaseMotion, releaseVelocity, settleTarget } from "./sheet-settle.js";
|
|
16
16
|
import { usePeekInert } from "./use-peek-inert.js";
|
|
17
17
|
import { SHEET_EXIT_PROPERTIES, bottomSheetStyles } from "./sheet-styles.js";
|
|
18
|
+
import { useKeyboardInset } from "./use-keyboard-inset.js";
|
|
18
19
|
import { useContext, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
19
20
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
20
21
|
import { mergeProps, useDialog, useFocusRing, useModalOverlay, useObjectRef } from "react-aria";
|
|
@@ -325,8 +326,12 @@ function BottomSheetPanel({ variant, state, isExiting, exitProps, panelProps: ov
|
|
|
325
326
|
tabIndex: -1
|
|
326
327
|
} : overlayPanelProps ?? {}, { onKeyDown });
|
|
327
328
|
const zone = !dragHandle;
|
|
329
|
+
const keyboardInset = useKeyboardInset();
|
|
328
330
|
return /* @__PURE__ */ jsx("div", {
|
|
329
|
-
style
|
|
331
|
+
style: {
|
|
332
|
+
"--m3-sheet-keyboard-inset": `${keyboardInset}px`,
|
|
333
|
+
...style
|
|
334
|
+
},
|
|
330
335
|
className: styles.container({ class: cn(classNames?.container, className) }),
|
|
331
336
|
children: /* @__PURE__ */ jsx("div", {
|
|
332
337
|
...mergeProps(exitProps, { onTransitionEnd: (event) => {
|