Take full on the searchable choice and the pickers, as on every field
`full` takes the 40rem bound off `[data-md-field]` from `medium`, and `<x-input>`, `<x-password>`, `<x-textarea>`, `<x-select>` and `<x-file>` pass it to the field they draw. `<x-choices searchable>`, `<x-datepicker>` and `<x-timepicker>` draw the same field but did not declare the prop, and their roots only forward `class` and `style`, so `full` reached no element and the field stopped at 40rem: ReStride's time zone choice stood narrower than the rows filling its card. The three now declare `full` and pass it to their field (the date picker's own field, not the dialog's entry fields). `<x-choices>`' chips are not a field and have no bound, so they take `full` and render nothing for it, and a call site can switch `searchable` on and off without it. The docblocks and the skill list every component that takes it. The browser test that bounds a field at 600px now measures a bounded and a `full` searchable choice, date picker and time picker too; it fails without the change in Chrome, Firefox and Safari. A feature test renders `full` on every field-shaped component and none on the chips. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
6d4487feb3
commit
7aa6dc7d33
@@ -81,6 +81,12 @@
|
||||
`medium` it is the disabled filled button, as before. A `spinner` that disables the button while
|
||||
its action runs leaves the FAB on screen. An application's own `hide-below` or condition around
|
||||
a disabled fab button can go.
|
||||
- **`<x-choices searchable>`, `<x-datepicker>` and `<x-timepicker>` take `full`**, as `<x-input>`,
|
||||
`<x-textarea>`, `<x-select>` and `<x-file>` do, to take the 40rem bound off a field from
|
||||
`medium`; the prop did nothing there, so the field stopped at 40rem beside rows that filled their
|
||||
card. `<x-choices>`' chips have no bound and take `full` without a change, so a call site can
|
||||
switch `searchable` on and off. A width rule an application wrote around one of them for this
|
||||
can go.
|
||||
|
||||
## From 2.0.0 to 2.1.0
|
||||
|
||||
|
||||
@@ -669,7 +669,7 @@ M3 text fields. `variant`: `outlined` or `filled`; without it, `config('livewire
|
||||
- `<x-input>`: `icon`, `icon-right`, `prefix`, `suffix`, `clearable`, `copyable` (copies the value, confirms with a snackbar), `counter`, `size` (`sm` 40px, `xs` 32px — for unlabelled toolbar controls; give them `aria-label`), `mono`.
|
||||
- `<x-password>`: a reveal button; `icon`, `size`.
|
||||
- `<x-textarea>`: grows from `rows` (3) to `max-rows`, then scrolls; `:autogrow="false"` for a fixed, hand-resizable one; `counter`.
|
||||
- Width: M3 asks that a text field never span the full width of a large screen, so from `medium` (600px) every field stops at **40rem**; below that it fills its pane. A width rule of your own on its `class` (`max-inline-size: 24rem`) narrows or widens it, because your unlayered CSS outranks the package's, and `full` (on `<x-field>`, `<x-input>`, `<x-textarea>`) takes the bound off for a field that really is the width of its pane — a search row, an editor. `<x-search>`'s bar carries M3's own bound, 720px.
|
||||
- Width: M3 asks that a text field never span the full width of a large screen, so from `medium` (600px) every field stops at **40rem**; below that it fills its pane. A width rule of your own on its `class` (`max-inline-size: 24rem`) narrows or widens it, because your unlayered CSS outranks the package's, and `full` (on `<x-field>`, `<x-input>`, `<x-password>`, `<x-textarea>`, `<x-select>`, `<x-file>`, `<x-choices searchable>`, `<x-datepicker>` and `<x-timepicker>`) takes the bound off for a field that really is the width of its pane — a search row, an editor. `<x-search>`'s bar carries M3's own bound, 720px.
|
||||
- `counter` (on `<x-input>` and `<x-textarea>`) puts M3's character counter at the end of the supporting-text row, beside the hint or the error: `n/max`, counted on every keystroke against the field's own `maxlength`, and in the error colour once the value is past it. It needs `maxlength` — without one there is nothing to count against and nothing is drawn. It is said as "Character count, 5/20" from a polite region a second after typing stops.
|
||||
- `<x-select>`: native `<select>` (M3 menu where the browser supports customizable selects). `options` as `['id' => …, 'name' => …, 'disabled' => bool]`, `option-value`, `option-label`, `placeholder` + `placeholder-value`, or `<option>`s in the slot; `icon`, `size`. A value too long for the field ends before the arrow with an ellipsis; the customizable select draws it in a `<button>` holding `<selectedcontent>`, which the view renders as the select's first child.
|
||||
- `<x-file>`: native file input; errors from `photos` and `photos.*`. Show previews of what was chosen yourself.
|
||||
@@ -739,7 +739,7 @@ M3 date pickers on a text field. `wire:model` stores `Y-m-d` strings (`x-model`
|
||||
| `mode` | `docked` | `docked`: type a date (in the locale's numeric format) or pick one from a calendar under the field, which opens as a dialog on a compact window (below `medium`), as M3 asks; `modal`: the field opens a calendar dialog; `input`: the dialog opens on a text field. Both dialogs switch between calendar and typing |
|
||||
| `range` | `false` | binds one array property, `['start' => 'Y-m-d', 'end' => 'Y-m-d']` (either may be null); errors for `trip`, `trip.start` and `trip.end` show on the field. On a compact window (below `medium`) it opens as M3's full-screen range picker: an app bar with a close button and **Save**, the range as the headline, and the months in one scrolling list instead of stepped one at a time |
|
||||
| `min`, `max` | `null` | `Y-m-d` or a date; days outside are disabled and the keyboard stays inside |
|
||||
| `label`, `hint`, `icon`, `variant`, `size` | | the field's |
|
||||
| `label`, `hint`, `icon`, `variant`, `size`, `full` | | the field's |
|
||||
| `value` | `null` | the initial value without `wire:model` |
|
||||
| `name` | | adds hidden inputs with `Y-m-d` for a plain form post (`name[start]`, `name[end]` for a range) |
|
||||
| `clearable` | `false` | a button that empties the field (both ends of a range) once it holds a date |
|
||||
@@ -766,7 +766,7 @@ M3's time picker in a modal dialog, opened from a read-only text field (a press,
|
||||
| `min`, `max` | | `H:i`, inclusive; `min` later than `max` spans midnight. Outside values are greyed out and refused in the picker — validate on the server as well |
|
||||
| `clearable` | `false` | a button that empties the field |
|
||||
| `name` | | posts the value from a hidden input |
|
||||
| `label`, `hint`, `icon`, `variant`, `size` | | the field's; `required`, `disabled` and `placeholder` reach its input |
|
||||
| `label`, `hint`, `icon`, `variant`, `size`, `full` | | the field's; `required`, `disabled` and `placeholder` reach its input |
|
||||
|
||||
Errors under the `wire:model` name replace the hint. The dialog is `wire:ignore`: a Livewire render leaves an open picker open with its draft. Never name a Livewire property `$slot`: it renders empty in the component's view.
|
||||
|
||||
@@ -775,7 +775,7 @@ Errors under the `wire:model` name replace the hint. The dialog is `wire:ignore`
|
||||
Choosing from a list, with typed values (an array of integers stays integers). `options` (`id`, `name`, `disabled`; `option-value`, `option-label`), `label`, `hint`, `single`. Errors for the property and its items replace the hint.
|
||||
|
||||
- Default: filter chips, every option on screen — `single` for choice chips.
|
||||
- `searchable`: a text field that filters a menu as you type (single value; arrow keys, Enter, Escape); `icon`, `variant`, `placeholder`. Its list is a popover, so it is never clipped by a card.
|
||||
- `searchable`: a text field that filters a menu as you type (single value; arrow keys, Enter, Escape); `icon`, `variant`, `placeholder`, `full`. Its list is a popover, so it is never clipped by a card.
|
||||
|
||||
```blade
|
||||
<x-choices label="Days you are free" wire:model.live="days" :options="$weekdays" />
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
caret), Enter chooses, Escape puts the field back — WAI-ARIA's combobox keyboard, since M3
|
||||
has no combobox. One value only. The list is a popover in the top
|
||||
layer, placed by CSS anchor positioning, so a card's clipping never cuts it off. `icon`,
|
||||
`variant` and `placeholder` are the field's.
|
||||
`variant` and `placeholder` are the field's, and `full` takes the 40rem bound off the field
|
||||
as it does off every field. Chips have no such bound, so they take `full` and change nothing.
|
||||
|
||||
The selection lives in Alpine, entangled with the `wire:model` property (live with
|
||||
`wire:model.live`), rather than in native checkboxes, which would send every value back as a
|
||||
@@ -38,6 +39,7 @@
|
||||
'variant' => null,
|
||||
'placeholder' => null,
|
||||
'value' => null,
|
||||
'full' => false,
|
||||
])
|
||||
|
||||
@php
|
||||
@@ -134,7 +136,7 @@
|
||||
@if ($model === null) x-modelable="value" @endif
|
||||
>
|
||||
<div style="anchor-name: {{ $anchor }}">
|
||||
<x-livewire-material::field :$id :$label :$hint :$messages :$icon :$variant>
|
||||
<x-livewire-material::field :$id :$label :$hint :$messages :$icon :$variant :$full>
|
||||
<input
|
||||
id="{{ $id }}"
|
||||
type="text"
|
||||
|
||||
@@ -26,9 +26,10 @@
|
||||
range as one update so `after_or_equal:period.start` validates against the end it came
|
||||
with, and the errors for `period`, `period.start` and `period.end` all belong to this field.
|
||||
`min` and `max` (`Y-m-d` or a date object) disable the days outside them and keep the
|
||||
keyboard inside them. `label`, `hint`, `icon`, `variant` (`outlined`, `filled`) and `size`
|
||||
are the field's; `clearable` adds a button that empties it (a date, or both ends of a range)
|
||||
once it holds one; `name` adds hidden inputs carrying `Y-m-d` for a plain form post. `class` and
|
||||
keyboard inside them. `label`, `hint`, `icon`, `variant` (`outlined`, `filled`), `size` and
|
||||
`full` (the 40rem bound taken off) are the field's; `clearable` adds a button that empties it
|
||||
(a date, or both ends of a range) once it holds one; `name` adds hidden inputs carrying
|
||||
`Y-m-d` for a plain form post. `class` and
|
||||
`style` land on the root; every other attribute (`required`, `disabled`, `readonly`) reaches
|
||||
the text field. Errors under
|
||||
the `wire:model` name replace the hint, and so does a typed date that cannot be read.
|
||||
@@ -67,6 +68,7 @@
|
||||
'clearable' => false,
|
||||
'weekStart' => null,
|
||||
'format' => null,
|
||||
'full' => false,
|
||||
])
|
||||
|
||||
@php
|
||||
@@ -168,6 +170,7 @@
|
||||
:$icon
|
||||
:$size
|
||||
:$variant
|
||||
:$full
|
||||
:data-md-invalid="$invalid ? '' : null"
|
||||
:data-md-readonly="$attributes->get('readonly') ? '' : null"
|
||||
x-bind:data-md-invalid="(fieldError !== '' || {{ $invalid ? 'true' : 'false' }}) ? '' : null"
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
multiple of the step; a drag and the arrows move by the step). `min` and `max` (`H:i`, inclusive;
|
||||
a `min` later than `max` spans midnight) grey out and skip what lies outside them and turn typed
|
||||
values outside them into errors — validate on the server as well. `clearable` adds a button that
|
||||
empties the field; `name` posts the value from a hidden input. `label`, `hint`, `icon`, `variant`
|
||||
and `size` are the field's; `class` and `style` land on the root, other attributes (`required`,
|
||||
`disabled`, `placeholder`) reach the field's input.
|
||||
empties the field; `name` posts the value from a hidden input. `label`, `hint`, `icon`, `variant`,
|
||||
`size` and `full` (the 40rem bound taken off) are the field's; `class` and `style` land on the
|
||||
root, other attributes (`required`, `disabled`, `placeholder`) reach the field's input.
|
||||
|
||||
From androidx Compose Material 3 at commit 27cf9a7d5788aa0f5f2d8b6699ce279560daf326 (Apache-2.0):
|
||||
TimePickerTokens and TimeInputTokens — a surface-container-high dialog with an extra-large
|
||||
@@ -56,6 +56,7 @@
|
||||
'value' => null,
|
||||
'name' => null,
|
||||
'clearable' => false,
|
||||
'full' => false,
|
||||
])
|
||||
|
||||
@php
|
||||
@@ -143,7 +144,7 @@
|
||||
x-data="materialTimepicker(@if ($model !== null) @entangle($attributes->wire('model')) @else @js($current) @endif, @js($config))"
|
||||
@if ($model === null) x-modelable="value" @endif
|
||||
>
|
||||
<x-livewire-material::field :$id :$label :$hint :$messages :$icon :$size :$variant data-md-timepicker-field>
|
||||
<x-livewire-material::field :$id :$label :$hint :$messages :$icon :$size :$variant :$full data-md-timepicker-field>
|
||||
<input
|
||||
{{ $inputAttributes }}
|
||||
id="{{ $id }}"
|
||||
|
||||
@@ -443,6 +443,12 @@ function fieldWidthProbe(int $width)
|
||||
<x-input id="bounded" label="Bounded" />
|
||||
<x-input id="narrow" label="Narrow" class="probe-narrow" />
|
||||
<x-input id="full" label="Full" full />
|
||||
<x-choices id="choices-bounded" label="Time zone" searchable :options="[['id' => 'Europe/Zurich', 'name' => 'Zurich']]" />
|
||||
<x-choices id="choices-full" label="Time zone" searchable full :options="[['id' => 'Europe/Zurich', 'name' => 'Zurich']]" />
|
||||
<x-datepicker id="date-bounded" label="Starts" />
|
||||
<x-datepicker id="date-full" label="Starts" full />
|
||||
<x-timepicker id="time-bounded" label="At" />
|
||||
<x-timepicker id="time-full" label="At" full />
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -459,10 +465,16 @@ it('bounds a field to 40rem from 600px, unless the caller\'s width rule or full
|
||||
->assertScript("({$width('narrow')}) === 200")
|
||||
->assertScript("({$width('full')}) === 1400");
|
||||
|
||||
fieldWidthProbe(600)
|
||||
$page = fieldWidthProbe(600)
|
||||
->assertScript("({$width('bounded')}) === 640")
|
||||
->assertScript("({$width('narrow')}) === 200")
|
||||
->assertScript("({$width('full')}) === 1400");
|
||||
|
||||
// The fields the searchable choice and the pickers draw take `full` as the others do.
|
||||
foreach (['choices', 'date', 'time'] as $field) {
|
||||
expect($page->script($width("{$field}-bounded")))->toBe(640)
|
||||
->and($page->script($width("{$field}-full")))->toBe(1400);
|
||||
}
|
||||
});
|
||||
|
||||
it('draws the field at M3\'s geometry and the file picker\'s button as a tonal pill', function () {
|
||||
|
||||
@@ -201,6 +201,28 @@ it('bounds a field\'s width from medium unless it is told to fill its pane', fun
|
||||
expect((string) $this->blade('<x-input label="Share name" full />'))->toContain('data-md-full')
|
||||
->and((string) $this->blade('<x-textarea label="Message" full />'))->toContain('data-md-full')
|
||||
->and((string) $this->blade('<x-input label="Share name" />'))->not->toContain('data-md-full');
|
||||
|
||||
// Every field-shaped component takes it: the searchable choice and the pickers draw a field too.
|
||||
$zones = "[['id' => 'Europe/Zurich', 'name' => 'Zurich']]";
|
||||
|
||||
foreach ([
|
||||
'<x-password label="Password" full />',
|
||||
'<x-select label="Expires" full />',
|
||||
'<x-file label="Photos" full />',
|
||||
"<x-choices label=\"Time zone\" searchable full :options=\"{$zones}\" />",
|
||||
'<x-datepicker label="Starts" full />',
|
||||
'<x-datepicker label="Trip" range full />',
|
||||
'<x-timepicker label="At" full />',
|
||||
] as $blade) {
|
||||
expect(substr_count((string) $this->blade($blade), 'data-md-full'))->toBe(1, $blade);
|
||||
}
|
||||
|
||||
expect((string) $this->blade("<x-choices label=\"Time zone\" searchable :options=\"{$zones}\" />"))->not->toContain('data-md-full')
|
||||
->and((string) $this->blade('<x-datepicker label="Starts" />'))->not->toContain('data-md-full')
|
||||
->and((string) $this->blade('<x-timepicker label="At" />'))->not->toContain('data-md-full')
|
||||
// Chips are no field and have no bound to take off: `full` is taken, and reaches no element.
|
||||
->and((string) $this->blade("<x-choices label=\"Days\" full :options=\"{$zones}\" />"))->not->toMatch('/\\bfull\\b|data-md-full/')
|
||||
->and((string) $this->blade("<x-choices label=\"Days\" single full :options=\"{$zones}\" />"))->not->toMatch('/\\bfull\\b|data-md-full/');
|
||||
});
|
||||
|
||||
it('lays out a form with its actions under an optional divider', function () {
|
||||
|
||||
Reference in New Issue
Block a user