From 16c52d46e99be6985904da8e2dc232580d79498c Mon Sep 17 00:00:00 2001 From: Andreas Reinhold / reini Date: Thu, 17 Sep 2026 07:06:25 +0200 Subject: [PATCH] Hang a searchable choice's list as wide as its field The searchable choice put its `anchor-name` on a `
` around its field, and menu.css sizes the popover list with `anchor-size(width)`. The wrapper fills its container, but from `medium` the field inside it stops at 40rem, so in a wider pane the list ran past the field's end across the whole container: 1168px under a 640px field. Found while giving the choice `full`, which only hid it. The field itself carries the anchor now and the wrapper is gone, so the list is as wide as the field, bounded or `full`, and still hangs under the field's supporting text as before. A browser test opens a bounded and a `full` searchable choice in a 1200px column and compares each list's width and start with its field; it fails without the change in Chrome, Firefox and Safari. Co-Authored-By: Claude Opus 5 (1M context) --- UPGRADE.md | 4 ++ resources/views/components/choices.blade.php | 71 ++++++++++---------- tests/Browser/PickingTest.php | 41 +++++++++++ 3 files changed, 80 insertions(+), 36 deletions(-) diff --git a/UPGRADE.md b/UPGRADE.md index bb533e54..d9c7e321 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -87,6 +87,10 @@ card. ``' 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. +- **A searchable ``' list is as wide as its field.** It was anchored to a wrapper + around the field, which fills its container, so from `medium`, where the field stops at 40rem, + the list ran on past the field's end across the whole container. The field itself is the anchor + now, and the wrapper `
` is gone; the list keeps hanging under the field's supporting text. ## From 2.0.0 to 2.1.0 diff --git a/resources/views/components/choices.blade.php b/resources/views/components/choices.blade.php index caa0085e..739b6734 100644 --- a/resources/views/components/choices.blade.php +++ b/resources/views/components/choices.blade.php @@ -11,7 +11,8 @@ and End go to the ends of the open list (with it closed they are the field's own and move the 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`, + layer, placed by CSS anchor positioning on the field itself, so a card's clipping never cuts + it off and it is as wide as the field, 40rem bound and all. `icon`, `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. @@ -135,42 +136,40 @@ }" @if ($model === null) x-modelable="value" @endif > -
- - + + - - - - -
+ + + +
    get('/wide-choices-probe', fn () => Blade::render(<<<'BLADE' + + + + + @vite(config('livewire-material.showcase.vite')) + @livewireStyles + + +
    + + +
    +
    + @livewireScripts + + + BLADE)); + + // [the field's width, the list's width, the list's start against the field's]. + $placed = fn (string $id): string => "(() => { + const field = document.querySelector('[data-md-field]:has(#{$id})').getBoundingClientRect(); + const list = document.getElementById('{$id}-list').getBoundingClientRect(); + + return [Math.round(field.width), Math.round(list.width), Math.round(list.left - field.left)]; + })()"; + + $page = visit('/wide-choices-probe')->resize(1280, 800)->waitForEvent('networkidle') + ->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined'"); + + $page->click('#bounded-zone')->assertScript("document.getElementById('bounded-zone-list').matches(':popover-open')"); + + expect($page->script($placed('bounded-zone')))->toBe([640, 640, 0]); + + $page->keys('#bounded-zone', 'Escape')->assertScript("! document.getElementById('bounded-zone-list').matches(':popover-open')"); + $page->click('#full-zone')->assertScript("document.getElementById('full-zone-list').matches(':popover-open')"); + + expect($page->script($placed('full-zone')))->toBe([1168, 1168, 0]); +}); + it('opens the search view with the results Livewire renders for the query', function () { $view = "document.querySelector('#find-view')";