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/field.css';
|
||||||
@import './components/input.css';
|
@import './components/input.css';
|
||||||
@import './components/password.css';
|
@import './components/password.css';
|
||||||
|
@import './components/textarea.css';
|
||||||
|
|
||||||
/* Containment */
|
/* Containment */
|
||||||
|
|
||||||
|
|||||||
@@ -138,35 +138,6 @@
|
|||||||
transition: background-color 604800s, color 604800s;
|
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
|
/* 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
|
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
|
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);
|
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 {
|
[data-md-field]:has([data-md-field-label]):not(:focus-within) [data-md-field-control]::placeholder {
|
||||||
color: transparent;
|
color: transparent;
|
||||||
}
|
}
|
||||||
@@ -561,14 +527,6 @@
|
|||||||
padding-block: var(--md-sys-measurement-space300) var(--md-sys-measurement-space100);
|
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. */
|
/* 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] {
|
[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);
|
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
|
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
|
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
|
`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([
|
@props([
|
||||||
'label' => null,
|
'label' => null,
|
||||||
@@ -30,7 +31,7 @@
|
|||||||
$max = $counter ? $attributes->get('maxlength') : null;
|
$max = $counter ? $attributes->get('maxlength') : null;
|
||||||
@endphp
|
@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
|
<textarea
|
||||||
{{ $attributes->except(['class', 'id', 'placeholder']) }}
|
{{ $attributes->except(['class', 'id', 'placeholder']) }}
|
||||||
id="{{ $id }}"
|
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 () {
|
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" />'))
|
expect((string) $this->blade('<x-textarea label="Message" rows="2" max-rows="6" />'))
|
||||||
->toContain('rows="2"')
|
->toContain('rows="2"')
|
||||||
|
->toContain('data-md-textarea')
|
||||||
->toContain('data-md-autogrow')
|
->toContain('data-md-autogrow')
|
||||||
->toContain('--field-rows: 2;')
|
->toContain('--field-rows: 2;')
|
||||||
->toContain('--field-max-rows: 6;')
|
->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';")
|
->toContain("@import './icon.css';")
|
||||||
->and((string) file_get_contents(__DIR__.'/../../../resources/css/components.css'))->toContain("@import './components/password.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