Announce a selectable list as M3's list box of options

Plan step 12, containment.md C-03. `selected` was colour alone on a
`listitem`, which cannot carry a selected state at all. `<x-list
selectable>` (or `selection="single|multi"`) now makes the container a
`role="listbox"` and each item an `option` with `aria-selected`; a plain
list keeps `role="list"` and marks a selected item `aria-current`. A
selected option draws a trailing check as M3's second cue, which
`icon-right` replaces. The item reads its list's mode with `@aware`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
Andreas Reinhold / reini
2026-09-14 05:57:00 +02:00
co-authored by Claude Opus 5
parent 7f67856916
commit d64fb91303
5 changed files with 74 additions and 6 deletions
+22
View File
@@ -38,6 +38,28 @@ it('makes a linked item a row that opens from anywhere', function () {
->toContain('wire:navigate');
});
it('is a list box of options when it is one a person chooses from', function () {
expect((string) $this->blade('<x-list selectable label="Recipients"><x-list-item title="Anna" :selected="true" /><x-list-item title="Ben" /></x-list>'))
->toContain('role="listbox"')
->toMatch('/role="option"\s+aria-selected="true"/')
->toMatch('/role="option"\s+aria-selected="false"/')
->not->toContain('aria-multiselectable')
->and((string) $this->blade('<x-list selection="multi"><x-list-item title="Anna" /></x-list>'))
->toContain('role="listbox"')
->toContain('aria-multiselectable="true"')
->and((string) $this->blade('<x-list><x-list-item title="Anna" :selected="true" /></x-list>'))
->toContain('role="list"')
->toMatch('/role="listitem"\s+aria-current="true"/')
->not->toContain('aria-selected');
});
it('draws a second cue on a selected option, which icon-right replaces', function () {
expect(substr_count((string) $this->blade('<x-list selectable><x-list-item title="Anna" :selected="true" /></x-list>'), '<svg'))->toBe(1)
->and(substr_count((string) $this->blade('<x-list selectable><x-list-item title="Anna" :selected="true" icon-right="chevron_right" /></x-list>'), '<svg'))->toBe(1)
->and(substr_count((string) $this->blade('<x-list selectable><x-list-item title="Anna" /></x-list>'), '<svg'))->toBe(0)
->and(substr_count((string) $this->blade('<x-list><x-list-item title="Anna" :selected="true" /></x-list>'), '<svg'))->toBe(0);
});
it('leaves a disabled item out of the keyboard and announces it disabled', function () {
expect((string) $this->blade('<x-list-item title="Settings" link="/settings" icon="settings" disabled />'))
->toContain('aria-disabled="true"')