Move the textarea's rules out of the field into textarea.css
<x-textarea> marks its field root data-md-textarea, and what a <textarea> changes in the field — the grip in the corner, the label on the first line, autogrow between rows and max-rows, the filled offsets — moves from field.css to components/textarea.css (plan step 36). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
co-authored by
Claude Opus 5
parent
cfdb4619ad
commit
c2fe64e00d
@@ -18,6 +18,7 @@
|
||||
@import './components/field.css';
|
||||
@import './components/input.css';
|
||||
@import './components/password.css';
|
||||
@import './components/textarea.css';
|
||||
|
||||
/* Containment */
|
||||
|
||||
|
||||
@@ -138,35 +138,6 @@
|
||||
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
|
||||
@@ -413,11 +384,6 @@
|
||||
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;
|
||||
}
|
||||
@@ -561,14 +527,6 @@
|
||||
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);
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* <x-textarea>: the text field grown to several lines (resources/views/components/textarea.blade.php).
|
||||
*
|
||||
* The chrome is the field's (components/field.css); this file is what a <textarea> changes in it.
|
||||
* The browser's resize grip belongs in the field's corner, so the textarea reaches to just inside
|
||||
* the outline's end and bottom and its padding puts the text back. Its label rests on the first
|
||||
* line rather than in the middle of the box. With `data-md-autogrow` it grows with its text from
|
||||
* `--field-rows` lines to `--field-max-rows` (both set inline by the view) through
|
||||
* `field-sizing: content`, and resources/js/field.js sets the height where the browser cannot.
|
||||
* A line is body-large's 1.5rem, so the row arithmetic stays in rem with the text.
|
||||
*
|
||||
* The field's root carries `data-md-textarea`.
|
||||
*/
|
||||
|
||||
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
|
||||
|
||||
@import './field.css';
|
||||
|
||||
@layer material.components {
|
||||
/* 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 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);
|
||||
}
|
||||
|
||||
/* Filled, the label floats inside the box: the text starts under it, 24px down, and the
|
||||
bottom keeps the field's 8px. */
|
||||
[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);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,8 @@
|
||||
form's column never moves. The label rests on the first line rather than in the middle of the
|
||||
box. `label`, `hint`, `variant`, `counter` for M3's character counter against `maxlength`, and
|
||||
`full` to span the pane past the 40rem M3 bounds a field to from `medium`; every other
|
||||
attribute reaches the `<textarea>`. --}}
|
||||
attribute reaches the `<textarea>`. The field's root carries `data-md-textarea`; what a
|
||||
textarea changes in the field's chrome is resources/css/components/textarea.css. --}}
|
||||
|
||||
@props([
|
||||
'label' => null,
|
||||
@@ -30,7 +31,7 @@
|
||||
$max = $counter ? $attributes->get('maxlength') : null;
|
||||
@endphp
|
||||
|
||||
<x-livewire-material::field :$id :$label :$hint :hint-class="$hintClass" :$messages :$variant :counter="$max" :$full :class="$attributes->get('class')" :data-md-readonly="$attributes->get('readonly') ? '' : null">
|
||||
<x-livewire-material::field :$id :$label :$hint :hint-class="$hintClass" :$messages :$variant :counter="$max" :$full :class="$attributes->get('class')" :data-md-readonly="$attributes->get('readonly') ? '' : null" data-md-textarea>
|
||||
<textarea
|
||||
{{ $attributes->except(['class', 'id', 'placeholder']) }}
|
||||
id="{{ $id }}"
|
||||
|
||||
@@ -108,6 +108,7 @@ it('lets a password be shown and hidden again', function () {
|
||||
it('grows a textarea from its rows up to its maximum, or leaves it to be resized', function () {
|
||||
expect((string) $this->blade('<x-textarea label="Message" rows="2" max-rows="6" />'))
|
||||
->toContain('rows="2"')
|
||||
->toContain('data-md-textarea')
|
||||
->toContain('data-md-autogrow')
|
||||
->toContain('--field-rows: 2;')
|
||||
->toContain('--field-max-rows: 6;')
|
||||
@@ -254,3 +255,14 @@ it('gives the password field a stylesheet that brings the field and its icons',
|
||||
->toContain("@import './icon.css';")
|
||||
->and((string) file_get_contents(__DIR__.'/../../../resources/css/components.css'))->toContain("@import './components/password.css';");
|
||||
});
|
||||
|
||||
it('draws what a textarea changes in the field from its own stylesheet', function () {
|
||||
$css = (string) file_get_contents(__DIR__.'/../../../resources/css/components/textarea.css');
|
||||
|
||||
expect($css)->toContain("@import './field.css';")
|
||||
->toContain('@layer material.components')
|
||||
->toContain('textarea[data-md-field-control][data-md-autogrow]')
|
||||
->toContain('field-sizing: content;')
|
||||
->and((string) file_get_contents(__DIR__.'/../../../resources/css/components/field.css'))->not->toContain('textarea[')
|
||||
->and((string) file_get_contents(__DIR__.'/../../../resources/css/components.css'))->toContain("@import './components/textarea.css';");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user