Give a text-only dialog's scrolling body the first focus in every engine
With nothing focusable inside, showModal() focuses the <dialog> itself. Chrome's scroll containers are keyboard-focusable, so there the scrolling body became the focus delegate and modal.css drew its inset ring; Firefox and WebKit focused the dialog, where the arrow keys scroll nothing, and WebKit never lets Tab reach a scroll container, so a keyboard could not read a long text-only dialog there at all. <x-modal> now opens through materialShowModal() (resources/js/dialog.js): showModal(), and when the dialog took the focus itself and its body overflows, the body gets tabindex="0" and the focus, as in Chrome. The tabindex goes when the dialog closes. The body is wire:ignore.self, like the dialog: a Livewire render would otherwise morph the tabindex away and WebKit dropped the focus out of the dialog. ContainmentTest now also checks the focus stays on the body through a render. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
b4a82a4dbe
commit
921c8cef04
@@ -13,7 +13,10 @@
|
||||
|
||||
`wire:ignore.self`, because `showModal()` sets the `open` attribute, which the server's HTML
|
||||
does not have: without it the next Livewire render morphs the attribute away and the dialog
|
||||
shuts under the person using it. The contents still morph.
|
||||
shuts under the person using it. The contents still morph. The body is `wire:ignore.self` for
|
||||
the same reason: `materialShowModal()` (resources/js/dialog.js) gives a text-only body the
|
||||
`tabindex` that lets it hold the first focus, and a render that took it away would drop that
|
||||
focus out of the dialog in WebKit.
|
||||
|
||||
M3's basic dialog (DialogTokens, androidx Compose Material 3, Apache-2.0): surface-container-
|
||||
high, extra-large corner, elevation 3, a headline-small `title`, body-medium `subtitle` in
|
||||
@@ -93,7 +96,7 @@
|
||||
@else
|
||||
x-data="{ close() { this.open = false } }"
|
||||
@endif
|
||||
x-effect="open ? ($el.open || $el.showModal()) : ($el.open && $el.close())"
|
||||
x-effect="open ? ($el.open || materialShowModal($el)) : ($el.open && $el.close())"
|
||||
x-on:cancel.prevent="{{ $persistent ? '' : 'close()' }}"
|
||||
x-on:close="if (open) close()"
|
||||
@if (! $persistent) x-on:click.self="close()" @endif
|
||||
@@ -141,7 +144,7 @@
|
||||
@endif
|
||||
|
||||
@if ($body)
|
||||
<div id="{{ $id }}-body" data-md-modal-body x-dialog-dividers>
|
||||
<div id="{{ $id }}-body" wire:ignore.self data-md-modal-body x-dialog-dividers>
|
||||
<div data-md-modal-content>{{ $slot }}</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
Reference in New Issue
Block a user