End a select's long value before its arrow, with an ellipsis

Where the browser has the customizable select, select.css made the
select `display: flex` with `text-overflow: ellipsis`, but the closed
select draws its value in a block of its user-agent shadow tree: a flex
item at its automatic minimum width that takes no `text-overflow` from
the select, and no author style reaches it (`display: block` on the
select does not help either). So a long option in a narrow field, or
at `size="sm"` and `xs`, ran on past the end padding under the arrow,
which `overflow: hidden` only clips at the select's edge. The native
select in Firefox cut the value at the padding with no ellipsis.

The view now gives the select a first child `<button type="button"
wire:ignore>` holding `<selectedcontent>`, the customizable select's own
copy of the chosen option, and select.css makes that the flex item that
shrinks and ends in an ellipsis; the native select takes `nowrap`,
`overflow: hidden` and the ellipsis on itself. `wire:ignore`, because a
Livewire render parses the server's empty element into the first
option's label and morphed it back over the chosen one. A browser
without the customizable select never renders the button; `type` keeps
it from being a form's default button.

A browser test measures the value against the arrow in md, sm, xs,
filled and icon selects 240px wide, and again after choosing another
option re-renders the component; it fails without the change in Chrome
and Firefox.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Andreas Reinhold / reini
2026-09-17 06:50:53 +02:00
co-authored by Claude Opus 5
parent c5a3870b7f
commit 1885c3e54e
6 changed files with 117 additions and 4 deletions
+9 -2
View File
@@ -11,8 +11,9 @@
`option-value` and `option-label` to read other keys, and a `placeholder` that becomes the
first option, valued `placeholder-value` (empty by default). Or pass `<option>`s in the slot.
`label`, `hint`, `icon`, `variant`, `size`; `full` takes the 40rem bound off a field its container already bounds. `class` and `style` land on the field's root, every
other attribute on the `<select>`. The field's root carries `data-md-select`; what a
select changes in the field's chrome is resources/css/components/select.css. --}}
other attribute on the `<select>`. A value too long for the field ends before the arrow with
an ellipsis. The field's root carries `data-md-select`; what a select changes in the field's
chrome is resources/css/components/select.css. --}}
@props([
'label' => null,
@@ -46,6 +47,12 @@
@if ($messages !== [] || filled($hint)) aria-describedby="{{ $id }}-support" @endif
data-md-field-control
>
{{-- The customizable select draws the chosen option in `<selectedcontent>`, which select.css
cuts with an ellipsis before the arrow; a browser without it never renders the button.
`wire:ignore`, because the browser fills it: a render would put back the first option's
label, parsed from the server's HTML, or nothing. --}}
<button type="button" wire:ignore><selectedcontent></selectedcontent></button>
@if (filled($placeholder))
<option value="{{ $placeholderValue }}">{{ $placeholder }}</option>
@endif