<x-field> renders data-md-field with its props and parts as data-md-* attributes (box, control, label, outline, support, counter, trailing buttons), and field.css moves into material.components on tokens: px geometry, spacing and state tokens, the 600px breakpoint (plan step 36). Its icons take a size prop, 24/20/16 by the field's size. Every control the field wraps now marks itself data-md-field-control, so the inputs, the pickers, choices, field.js, menu.css's select and listbox rules and timepicker.css follow the renamed hooks. Browser tests cover the error icon, the disabled field's hover, the counter and the width bound from 600px. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
594 lines
25 KiB
CSS
594 lines
25 KiB
CSS
/*
|
|
* M3's text fields — the chrome every form control here shares: outlined (the default) and filled.
|
|
*
|
|
* The states are selectors rather than classes: whether the label rests in the field or floats is a
|
|
* question about the control's value and focus, and `:has()` asks it without JavaScript, so it is
|
|
* right before Alpine starts, in a test, and after a Livewire morph rewrites the value underneath.
|
|
*
|
|
* The outlined field's notch is MUI's technique: a `<fieldset>` draws the outline and a hidden
|
|
* `<legend>`, carrying the label at its floated size, cuts the gap the label sits in, so the gap is
|
|
* right on any background. The filled field has no outline: a surface-container-highest box with
|
|
* extra-small top corners and an active indicator line, the label floating inside it
|
|
* (FilledTextFieldTokens / OutlinedTextFieldTokens, androidx Compose Material 3, Apache-2.0): 56px
|
|
* tall, 16px padding, 24px icons 16px from the text, the label in body-small once it floats.
|
|
* The `sm` (40px) and `xs` (32px) sizes are this package's, for an unlabelled field in a toolbar.
|
|
* Disabled is on-surface at M3's 38% content and 12% container opacities (tokens/state.css); the
|
|
* filled field's disabled container is on-surface at 4%, FilledTextFieldTokens'
|
|
* DisabledContainerOpacity, which has no system token.
|
|
*
|
|
* Anatomy (resources/views/components/field.blade.php):
|
|
*
|
|
* [data-md-field] the whole thing; `class` from the call site lands here;
|
|
* data-md-variant, data-md-size, data-md-invalid, data-md-floated,
|
|
* data-md-mono, data-md-full, data-md-readonly
|
|
* [data-md-field-box] the 56px row: icon, prefix, control, suffix, trailing
|
|
* [data-md-field-icon] the leading icon
|
|
* [data-md-field-affix] a prefix or suffix beside the value
|
|
* [data-md-field-control] the <input>, <select> or <textarea>
|
|
* [data-md-field-trailing] what ends the row; [data-md-field-error] is the error icon M3 asks
|
|
* for as the error state's second indicator; [data-md-field-button]
|
|
* a trailing icon button (clear, copy, reveal, open a picker)
|
|
* [data-md-field-outline] the fieldset and its legend (the filled field's indicator line)
|
|
* [data-md-field-label] the visible label
|
|
* [data-md-field-support-row] the row under the field
|
|
* [data-md-field-support] the hint, or the error in its place
|
|
* [data-md-field-counter] M3's character counter, `n/max`, at the end of that row
|
|
*
|
|
* A select's open list is not part of the field: it is the dropdown menu in components/menu.css.
|
|
*/
|
|
|
|
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
|
|
|
|
@import './icon.css';
|
|
|
|
@layer material.components {
|
|
[data-md-field] {
|
|
--field-height: 56px;
|
|
--field-pad: var(--md-sys-measurement-space200);
|
|
--field-icon: 24px;
|
|
--field-gap: var(--md-sys-measurement-space200);
|
|
--field-start: var(--field-pad);
|
|
--field-edge: var(--md-sys-color-outline);
|
|
--field-ink: var(--md-sys-color-on-surface-variant);
|
|
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-width: 0;
|
|
}
|
|
|
|
/* M3 § Text Fields, Behaviour: "compact breakpoints can let a text field span full width;
|
|
* medium/expanded should bound it with flexible margins/other containers — never let it span the
|
|
* full width of a large screen." The site names no number, so 40rem is this package's: it is
|
|
* under half the `large` breakpoint's 1200px, a little over half the `expanded` one's, and holds
|
|
* about the 70 characters body-large reads best at — a measure of text, so it is in rem and grows
|
|
* with the text. It is a ceiling, not a width — a narrower pane still gets a narrower field. A
|
|
* width rule from the call site beats it, because an application's CSS and a utility both
|
|
* outrank this layer, and `full` takes it off for a field that really is the width of its pane (a
|
|
* search-and-filter row, an editor). Below `medium` nothing is bounded. */
|
|
@media (width >= 600px) {
|
|
[data-md-field]:not([data-md-full]) {
|
|
max-width: 40rem;
|
|
}
|
|
}
|
|
|
|
/* A filled field's resting indicator line is on-surface-variant, where an outline is outline. */
|
|
[data-md-field][data-md-variant='filled'] {
|
|
--field-edge: var(--md-sys-color-on-surface-variant);
|
|
}
|
|
|
|
[data-md-field][data-md-size='sm'] {
|
|
--field-height: 40px;
|
|
--field-pad: 12px;
|
|
--field-icon: 20px;
|
|
--field-gap: var(--md-sys-measurement-space100);
|
|
}
|
|
|
|
[data-md-field][data-md-size='xs'] {
|
|
--field-height: 32px;
|
|
--field-pad: var(--md-sys-measurement-space125);
|
|
--field-icon: 16px;
|
|
--field-gap: var(--md-sys-measurement-space75);
|
|
}
|
|
|
|
[data-md-field]:has([data-md-field-icon]) {
|
|
--field-start: calc(var(--field-pad) + var(--field-icon) + var(--field-gap));
|
|
}
|
|
|
|
[data-md-field-box] {
|
|
position: relative;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--field-gap);
|
|
min-height: var(--field-height);
|
|
padding-inline: var(--field-pad);
|
|
border-radius: var(--md-sys-shape-corner-xs);
|
|
cursor: text;
|
|
}
|
|
|
|
[data-md-field-control] {
|
|
flex: 1 1 0%;
|
|
min-width: 0;
|
|
align-self: stretch;
|
|
background: transparent;
|
|
color: var(--md-sys-color-on-surface);
|
|
caret-color: var(--md-sys-color-primary);
|
|
outline: none;
|
|
font: var(--md-sys-typescale-body-lg);
|
|
letter-spacing: var(--md-sys-typescale-body-lg-tracking);
|
|
}
|
|
|
|
[data-md-field][data-md-size='xs'] [data-md-field-control] {
|
|
font: var(--md-sys-typescale-body-md);
|
|
letter-spacing: var(--md-sys-typescale-body-md-tracking);
|
|
}
|
|
|
|
[data-md-field][data-md-mono] [data-md-field-control] {
|
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
|
|
}
|
|
|
|
[data-md-field-control]::placeholder {
|
|
color: var(--md-sys-color-on-surface-variant);
|
|
opacity: 1;
|
|
}
|
|
|
|
/* Autofill paints its own background, which on a card is a pale block in the
|
|
wrong colour. Delaying the transition for a week keeps the field's own. */
|
|
[data-md-field-control]:-webkit-autofill {
|
|
-webkit-text-fill-color: var(--md-sys-color-on-surface);
|
|
transition: background-color 604800s, color 604800s;
|
|
}
|
|
|
|
/* The browser draws the resize grip in the textarea's own corner, so the
|
|
textarea reaches to just inside the outline's end and bottom — the grip sits
|
|
in the field's corner, not on the bottom outline a padding in from it — and
|
|
its padding puts the text back where it was. */
|
|
textarea[data-md-field-control] {
|
|
--field-grip: var(--md-sys-measurement-space50);
|
|
|
|
/* The first half-line of the top offset is a margin, not padding: text scrolled up in a full
|
|
textarea disappears under the edge instead of running through the label. */
|
|
--textarea-clear: var(--md-sys-measurement-space100);
|
|
--textarea-pad-top: calc((var(--field-height) - 1.5rem) / 2 - var(--textarea-clear));
|
|
--textarea-pad-bottom: calc((var(--field-height) - 1.5rem) / 2 - var(--field-grip));
|
|
|
|
margin-block: var(--textarea-clear) var(--field-grip);
|
|
margin-inline-end: calc(var(--field-grip) - var(--field-pad));
|
|
padding-block: var(--textarea-pad-top) var(--textarea-pad-bottom);
|
|
padding-inline-end: calc(var(--field-pad) - var(--field-grip));
|
|
resize: vertical;
|
|
}
|
|
|
|
/* `<x-textarea>` grows with what is typed, from `rows` lines up to `max-rows`. Where the browser
|
|
cannot size a field to its content, resources/js/field.js sets the height instead. */
|
|
textarea[data-md-field-control][data-md-autogrow] {
|
|
field-sizing: content;
|
|
min-block-size: calc(var(--field-rows, 3) * 1.5rem + var(--textarea-pad-top) + var(--textarea-pad-bottom));
|
|
max-block-size: calc(var(--field-max-rows, 1000) * 1.5rem + var(--textarea-pad-top) + var(--textarea-pad-bottom));
|
|
resize: none;
|
|
}
|
|
|
|
/* A select covers its whole field — out over the padding and the leading icon at
|
|
the start, under the arrow to the end — and its own padding puts the value back
|
|
where it was. So a press anywhere on the field opens it, and the list, which
|
|
the browser anchors to the select, is the field's width (components/menu.css). */
|
|
select[data-md-field-control] {
|
|
--field-end: calc(var(--field-icon) + var(--field-gap) + var(--field-pad));
|
|
|
|
appearance: none;
|
|
cursor: pointer;
|
|
margin-inline: calc(-1 * var(--field-start)) calc(-1 * var(--field-end));
|
|
padding-inline: var(--field-start) var(--field-end);
|
|
}
|
|
|
|
select[data-md-field-control]:disabled {
|
|
cursor: default;
|
|
}
|
|
|
|
/* Where the browser has a customizable select, the closed select is its own
|
|
button: without the border, padding and arrow that button brings, it is the
|
|
same field as before. While its list is open the field reads as focused —
|
|
said from `:open`, because focus inside the list is in the top layer — and
|
|
the arrow turns over, as the searchable choices' does. */
|
|
@supports (appearance: base-select) {
|
|
select[data-md-field-control] {
|
|
display: flex;
|
|
align-items: center;
|
|
border: 0;
|
|
padding-block: 0;
|
|
border-radius: 0;
|
|
background: transparent;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
select[data-md-field-control]::picker-icon {
|
|
display: none;
|
|
}
|
|
|
|
[data-md-field-box]:has(select[data-md-field-control]:open) {
|
|
--field-edge: var(--md-sys-color-primary);
|
|
--field-ink: var(--md-sys-color-primary);
|
|
|
|
[data-md-field-outline] {
|
|
border-width: 2px;
|
|
}
|
|
}
|
|
|
|
[data-md-field]:has(select[data-md-field-control]:open) [data-md-field-arrow] {
|
|
rotate: 180deg;
|
|
}
|
|
}
|
|
|
|
/* `<x-file>`: the browser's own "No file chosen" line is transparent (the caller shows what was
|
|
chosen), and its button is a tonal pill — the thing to press. */
|
|
input[type='file'][data-md-field-control] {
|
|
align-self: center;
|
|
color: transparent;
|
|
cursor: pointer;
|
|
}
|
|
|
|
input[type='file'][data-md-field-control]::file-selector-button {
|
|
height: 32px;
|
|
margin-inline-end: 12px;
|
|
padding-inline: var(--md-sys-measurement-space200);
|
|
border: 0;
|
|
border-radius: var(--md-sys-shape-corner-full);
|
|
background-color: var(--md-sys-color-secondary-container);
|
|
color: var(--md-sys-color-on-secondary-container);
|
|
cursor: pointer;
|
|
font: var(--md-sys-typescale-label-lg);
|
|
letter-spacing: var(--md-sys-typescale-label-lg-tracking);
|
|
transition: background-color var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
|
|
}
|
|
|
|
@media (hover: hover) {
|
|
input[type='file'][data-md-field-control]:hover::file-selector-button {
|
|
background-color: color-mix(in srgb, var(--md-sys-color-on-secondary-container) calc(var(--md-sys-state-hover-state-layer-opacity) * 100%), var(--md-sys-color-secondary-container));
|
|
}
|
|
}
|
|
|
|
input[type='file'][data-md-field-control]:disabled::file-selector-button {
|
|
background-color: color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-container-opacity) * 100%), transparent);
|
|
color: color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-content-opacity) * 100%), transparent);
|
|
cursor: default;
|
|
}
|
|
|
|
/* A combobox's arrow turns over while its list is open, as a select's does. */
|
|
[data-md-field]:has([data-md-field-control][aria-expanded='true']) [data-md-field-arrow] {
|
|
rotate: 180deg;
|
|
}
|
|
|
|
[data-md-field-arrow] {
|
|
transition: rotate var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
|
|
}
|
|
|
|
[data-md-field-icon],
|
|
[data-md-field-trailing] {
|
|
flex: none;
|
|
width: var(--field-icon);
|
|
height: var(--field-icon);
|
|
color: var(--md-sys-color-on-surface-variant);
|
|
}
|
|
|
|
/* The error's second indicator: M3 asks that the colour change never stand alone. */
|
|
[data-md-field-error] {
|
|
color: var(--md-sys-color-error);
|
|
}
|
|
|
|
[data-md-field-icon],
|
|
[data-md-field-arrow] {
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* A trailing button — clear, copy, reveal — is an icon button: the icon in a 40px state layer. */
|
|
[data-md-field-button] {
|
|
position: relative;
|
|
display: grid;
|
|
place-items: center;
|
|
margin-inline-end: calc(-1 * var(--md-sys-measurement-space50));
|
|
border-radius: var(--md-sys-shape-corner-full);
|
|
cursor: pointer;
|
|
outline: none;
|
|
}
|
|
|
|
[data-md-field-button]::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 50% auto auto 50%;
|
|
width: calc(var(--field-icon) + var(--md-sys-measurement-space200));
|
|
height: calc(var(--field-icon) + var(--md-sys-measurement-space200));
|
|
translate: -50% -50%;
|
|
border-radius: inherit;
|
|
transition: background-color var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
|
|
}
|
|
|
|
@media (hover: hover) {
|
|
[data-md-field-button]:hover::before {
|
|
background-color: color-mix(in srgb, var(--md-sys-color-on-surface-variant) calc(var(--md-sys-state-hover-state-layer-opacity) * 100%), transparent);
|
|
}
|
|
}
|
|
|
|
[data-md-field-button]:focus-visible::before {
|
|
background-color: color-mix(in srgb, var(--md-sys-color-on-surface-variant) calc(var(--md-sys-state-focus-state-layer-opacity) * 100%), transparent);
|
|
outline: 3px solid var(--md-sys-color-secondary);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
[data-md-field-button]:active::before {
|
|
background-color: color-mix(in srgb, var(--md-sys-color-on-surface-variant) calc(var(--md-sys-state-pressed-state-layer-opacity) * 100%), transparent);
|
|
}
|
|
|
|
[data-md-field-affix] {
|
|
flex: none;
|
|
color: var(--md-sys-color-on-surface-variant);
|
|
font: var(--md-sys-typescale-body-lg);
|
|
letter-spacing: var(--md-sys-typescale-body-lg-tracking);
|
|
}
|
|
|
|
[data-md-field][data-md-size='xs'] [data-md-field-affix] {
|
|
font: var(--md-sys-typescale-body-md);
|
|
}
|
|
|
|
/* The legend is body-small's 16px line less its 5px above the outline: the fieldset starts
|
|
5px up so the notch's edge runs through the middle of the floated label. */
|
|
[data-md-field-outline] {
|
|
position: absolute;
|
|
inset: -0.3125rem 0 0;
|
|
margin: 0;
|
|
padding: 0 calc(var(--field-pad) - var(--md-sys-measurement-space50));
|
|
border: 1px solid var(--field-edge);
|
|
border-radius: inherit;
|
|
pointer-events: none;
|
|
min-width: 0;
|
|
transition: border-color var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
|
|
}
|
|
|
|
[data-md-field-outline] > legend {
|
|
display: block;
|
|
width: auto;
|
|
max-width: 100%;
|
|
height: 0.6875rem;
|
|
padding: 0;
|
|
overflow: hidden;
|
|
visibility: hidden;
|
|
white-space: nowrap;
|
|
font: var(--md-sys-typescale-body-sm);
|
|
letter-spacing: var(--md-sys-typescale-body-sm-tracking);
|
|
transition: max-width var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
|
|
}
|
|
|
|
[data-md-field-outline] > legend > span {
|
|
display: inline-block;
|
|
padding-inline: var(--md-sys-measurement-space50);
|
|
}
|
|
|
|
[data-md-field-label] {
|
|
position: absolute;
|
|
inset-inline-start: var(--field-pad);
|
|
top: 0;
|
|
max-width: calc(100% - 2 * var(--field-pad));
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
translate: 0 -50%;
|
|
color: var(--field-ink);
|
|
font: var(--md-sys-typescale-body-sm);
|
|
letter-spacing: var(--md-sys-typescale-body-sm-tracking);
|
|
pointer-events: none;
|
|
transition-property: top, inset-inline-start, font-size, line-height, color;
|
|
transition-duration: var(--md-sys-motion-effects-fast-duration);
|
|
transition-timing-function: var(--md-sys-motion-effects-fast);
|
|
}
|
|
|
|
/* `required` is marked from the control itself, in the label and in the notch,
|
|
so the gap is cut for the asterisk too. */
|
|
[data-md-field]:has([data-md-field-control]:required) [data-md-field-label]::after,
|
|
[data-md-field]:has([data-md-field-control]:required) [data-md-field-outline] > legend > span::after {
|
|
content: ' *';
|
|
}
|
|
|
|
/* Resting: a label, an empty control, and no focus. The label sits where the
|
|
text will go, at the text's size; the notch closes; the placeholder and the
|
|
prefix wait, because the label is standing where they would be. A select and
|
|
a date always hold a value, so they mark themselves `data-md-floated`. */
|
|
[data-md-field]:not([data-md-floated]):has([data-md-field-label]):has([data-md-field-control]:placeholder-shown):not(:focus-within) {
|
|
[data-md-field-label] {
|
|
top: 50%;
|
|
inset-inline-start: var(--field-start);
|
|
font: var(--md-sys-typescale-body-lg);
|
|
letter-spacing: var(--md-sys-typescale-body-lg-tracking);
|
|
}
|
|
|
|
[data-md-field-outline] > legend {
|
|
max-width: 0.01px;
|
|
}
|
|
|
|
[data-md-field-affix] {
|
|
visibility: hidden;
|
|
}
|
|
}
|
|
|
|
[data-md-field][data-md-size='xs']:not([data-md-floated]):has([data-md-field-label]):has([data-md-field-control]:placeholder-shown):not(:focus-within) [data-md-field-label] {
|
|
font: var(--md-sys-typescale-body-md);
|
|
}
|
|
|
|
/* A textarea's label rests on its first line, not in the middle of the box. */
|
|
[data-md-field]:not([data-md-floated]):has([data-md-field-label]):has(textarea[data-md-field-control]:placeholder-shown):not(:focus-within) [data-md-field-label] {
|
|
top: calc(var(--field-height) / 2);
|
|
}
|
|
|
|
[data-md-field]:has([data-md-field-label]):not(:focus-within) [data-md-field-control]::placeholder {
|
|
color: transparent;
|
|
}
|
|
|
|
/* Clearing is offered only once there is something to clear. */
|
|
[data-md-field]:has([data-md-field-control]:placeholder-shown) [data-md-field-clear] {
|
|
display: none;
|
|
}
|
|
|
|
/* Hover only where a pointer can hover, and never on a disabled field: M3 gives a disabled
|
|
control no state layer at all, and the disabled edge below is inherited, so a declaration
|
|
here would beat it whatever the selector weighs. */
|
|
@media (hover: hover) {
|
|
[data-md-field]:not(:has([data-md-field-control]:disabled)) [data-md-field-box]:hover {
|
|
--field-edge: var(--md-sys-color-on-surface);
|
|
}
|
|
}
|
|
|
|
[data-md-field-box]:focus-within {
|
|
--field-edge: var(--md-sys-color-primary);
|
|
--field-ink: var(--md-sys-color-primary);
|
|
}
|
|
|
|
[data-md-field-box]:focus-within [data-md-field-outline] {
|
|
border-width: 2px;
|
|
}
|
|
|
|
[data-md-field][data-md-invalid] {
|
|
--field-edge: var(--md-sys-color-error);
|
|
--field-ink: var(--md-sys-color-error);
|
|
}
|
|
|
|
@media (hover: hover) {
|
|
[data-md-field][data-md-invalid]:not(:has([data-md-field-control]:disabled)) [data-md-field-box]:hover {
|
|
--field-edge: var(--md-sys-color-on-error-container);
|
|
}
|
|
}
|
|
|
|
[data-md-field][data-md-invalid] [data-md-field-box]:focus-within {
|
|
--field-edge: var(--md-sys-color-error);
|
|
--field-ink: var(--md-sys-color-error);
|
|
}
|
|
|
|
/* `data-md-readonly` marks a field the caller made read-only — on the field, not read off the
|
|
control, because a date picker makes its own input read-only too. Nothing here draws it
|
|
differently: M3 says a read-only field keeps "the same visual style as an editable field, but
|
|
clearly labeled read-only", and the native `readonly` attribute is what carries that. The hook
|
|
stays for an application that wants a mark of its own. */
|
|
|
|
[data-md-field]:has([data-md-field-control]:disabled) {
|
|
--field-edge: color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-container-opacity) * 100%), transparent);
|
|
--field-ink: color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-content-opacity) * 100%), transparent);
|
|
|
|
[data-md-field-box] {
|
|
cursor: default;
|
|
}
|
|
|
|
[data-md-field-control],
|
|
[data-md-field-icon],
|
|
[data-md-field-affix],
|
|
[data-md-field-trailing] {
|
|
color: color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-content-opacity) * 100%), transparent);
|
|
}
|
|
}
|
|
|
|
/* The hint or the error, and — when the field has a maximum — M3's character counter at the far
|
|
end of the same row (specs: "Padding between supporting text and counter | 16dp", which the
|
|
two paddings between them already make). Supporting text sits 4px under the field. */
|
|
[data-md-field-support-row] {
|
|
display: flex;
|
|
align-items: baseline;
|
|
}
|
|
|
|
[data-md-field-support] {
|
|
min-width: 0;
|
|
padding: var(--md-sys-measurement-space50) var(--field-pad) 0;
|
|
color: var(--md-sys-color-on-surface-variant);
|
|
font: var(--md-sys-typescale-body-sm);
|
|
letter-spacing: var(--md-sys-typescale-body-sm-tracking);
|
|
}
|
|
|
|
[data-md-field][data-md-invalid] [data-md-field-support] {
|
|
color: var(--md-sys-color-error);
|
|
}
|
|
|
|
[data-md-field-counter] {
|
|
flex: none;
|
|
margin-inline-start: auto;
|
|
padding-block-start: var(--md-sys-measurement-space50);
|
|
padding-inline-end: var(--field-pad);
|
|
color: var(--md-sys-color-on-surface-variant);
|
|
font: var(--md-sys-typescale-body-sm);
|
|
letter-spacing: var(--md-sys-typescale-body-sm-tracking);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
/* Past the maximum the count is an error, and says so in the error colour. */
|
|
[data-md-field-counter][data-md-over] {
|
|
color: var(--md-sys-color-error);
|
|
}
|
|
|
|
/* ---- The filled text field ---------------------------------------------------------------- */
|
|
|
|
[data-md-field][data-md-variant='filled'] [data-md-field-box] {
|
|
border-radius: var(--md-sys-shape-corner-xs) var(--md-sys-shape-corner-xs) 0 0;
|
|
background-color: var(--md-sys-color-surface-container-highest);
|
|
transition: background-color var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
|
|
}
|
|
|
|
@media (hover: hover) {
|
|
[data-md-field][data-md-variant='filled'] [data-md-field-box]:hover {
|
|
background-color: color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-hover-state-layer-opacity) * 100%), var(--md-sys-color-surface-container-highest));
|
|
}
|
|
}
|
|
|
|
/* The outline becomes the active indicator: one line along the bottom. */
|
|
[data-md-field][data-md-variant='filled'] [data-md-field-outline] {
|
|
inset: auto 0 0;
|
|
padding: 0;
|
|
border: 0;
|
|
border-block-end: 1px solid var(--field-edge);
|
|
border-radius: 0;
|
|
}
|
|
|
|
[data-md-field][data-md-variant='filled'] [data-md-field-outline] > legend {
|
|
display: none;
|
|
}
|
|
|
|
[data-md-field][data-md-variant='filled'] [data-md-field-box]:focus-within [data-md-field-outline] {
|
|
border-block-end-width: 2px;
|
|
}
|
|
|
|
/* The label floats inside the box, above the value, and the value sits below it. */
|
|
[data-md-field][data-md-variant='filled'] [data-md-field-label] {
|
|
top: var(--md-sys-measurement-space200);
|
|
inset-inline-start: var(--field-start);
|
|
max-width: calc(100% - var(--field-start) - var(--field-pad));
|
|
}
|
|
|
|
[data-md-field][data-md-variant='filled']:has([data-md-field-label]) [data-md-field-control] {
|
|
padding-block: var(--md-sys-measurement-space300) var(--md-sys-measurement-space100);
|
|
}
|
|
|
|
[data-md-field][data-md-variant='filled']:has([data-md-field-label]) textarea[data-md-field-control] {
|
|
--textarea-clear: var(--md-sys-measurement-space300);
|
|
--textarea-pad-top: 0px;
|
|
--textarea-pad-bottom: calc(var(--md-sys-measurement-space100) - var(--field-grip));
|
|
|
|
padding-block: var(--textarea-pad-top) var(--textarea-pad-bottom);
|
|
}
|
|
|
|
/* A file picker's button is taller than a line of text: it sits lower, and a little smaller. */
|
|
[data-md-field][data-md-variant='filled']:has([data-md-field-label]) input[type='file'][data-md-field-control] {
|
|
padding-block: 22px var(--md-sys-measurement-space75);
|
|
}
|
|
|
|
[data-md-field][data-md-variant='filled'] input[type='file'][data-md-field-control]::file-selector-button {
|
|
height: 28px;
|
|
}
|
|
|
|
[data-md-field][data-md-variant='filled']:has([data-md-field-label]) [data-md-field-affix] {
|
|
align-self: stretch;
|
|
padding-block: var(--md-sys-measurement-space300) var(--md-sys-measurement-space100);
|
|
}
|
|
|
|
[data-md-field][data-md-variant='filled']:has([data-md-field-control]:disabled) {
|
|
--field-edge: color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-content-opacity) * 100%), transparent);
|
|
|
|
[data-md-field-box] {
|
|
background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 4%, transparent);
|
|
}
|
|
}
|
|
}
|