Files
livewire-material/resources/css/components/select.css
T
Andreas Reinhold / reiniandClaude Opus 5 1885c3e54e 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>
2026-09-17 06:50:53 +02:00

102 lines
4.4 KiB
CSS

/*
* <x-select>: the native <select> in the text field's chrome, drawn as M3's exposed dropdown menu
* (resources/views/components/select.blade.php).
*
* The chrome is the field's (components/field.css); this file is what a <select> changes in it. The
* select covers its whole field, so a press anywhere opens it and the list the browser anchors to
* it is the field's width. Where the browser has the customizable select (`appearance:
* base-select`), the closed select loses the button chrome that brings, the field reads as focused
* while the list is open, and the arrow turns over. The open list itself is M3's menu, drawn by
* components/menu.css.
*
* A value too long for the field ends before the arrow with an ellipsis. The native select cuts its
* own text at its end padding; the customizable one draws the value in a box of its shadow tree
* that no author style reaches, so the view gives it a `<button>` holding `<selectedcontent>`, the
* browser's copy of the chosen option, and the ellipsis is drawn there.
*
* The field's root carries `data-md-select`.
*/
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
@import './field.css';
@import './icon.css';
@import './menu.css';
@layer material.components {
/* 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).
A value longer than the field is cut at that padding, with an ellipsis, rather
than running on under the arrow. */
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);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
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 — in
the error colour when the field is in error, as a focused one is — and the
arrow turns over, as the searchable choices' does. Both outweigh field.css's
hover, so the pointer left on the field does not take the focus edge away. */
@supports (appearance: base-select) {
select[data-md-field-control] {
appearance: base-select;
display: flex;
align-items: center;
border: 0;
padding-block: 0;
border-radius: 0;
background: transparent;
}
/* The customizable select's value is `<selectedcontent>`, the only part of the
closed select an author style reaches: the box the browser draws otherwise
takes neither `text-overflow` nor a width from the select, and ran on under
the arrow. As the select's flex item it may shrink to the room left. */
select[data-md-field-control] selectedcontent {
flex: 1 1 0%;
min-width: 0;
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][data-md-invalid] [data-md-field-box]:has(select[data-md-field-control]:open) {
--field-edge: var(--md-sys-color-error);
--field-ink: var(--md-sys-color-error);
}
[data-md-field]:has(select[data-md-field-control]:open) [data-md-field-arrow] {
rotate: 180deg;
}
}
}