Show no tooltip on the control a sheet or dialog focuses as it opens

A modal side sheet, bottom sheet and the modal rail move the focus to
their first control as they open (x-trap), and `<x-modal>` does in
`showModal()`. On a page loaded with the sheet already open (a
`wire:model` set from `?workout=` in the URL) the browsers count that
focus as `:focus-visible`, and so they do for a sheet or dialog opened
from the keyboard, so tooltip.js showed the close button's "Close"
tooltip the moment the layer appeared, over its title, with no keyboard
use. Confirmed in Chrome, Firefox and Safari for the deep-linked sheet
and a full-screen dialog opened with Enter; a deep-linked `<x-modal>`'s
own focus was not counted as keyboard focus. M3 lets a tooltip appear
on hover or focus and says nothing that asks for one on a focus the
component moves itself.

layers.js gains `openingFocus(event)`: a focus is a modal layer's own
when the nearest open dialog, `aria-modal` panel or modal rail around
its target is entered from outside it, or from nowhere, and no Tab is
moving it (a Tab moves the focus while its keydown is handled, by the
browser or by focus-trap wrapping round the panel). tooltip.js and
rich-tooltip.js leave such a focus out; focus moved within the layer,
a Tab onto the control and a focus returned to it from a menu inside
still show the tooltip. The docblocks, the skill and UPGRADE.md say so.

A browser test loads a page with a modal sheet open at 393px, opens a
full-screen dialog and a dialog whose first control is a rich tooltip's
trigger from the keyboard, and checks that no tooltip is up on the
focused control, then that Tab wrapping round to the close button shows
its tooltip; it fails without the change in Chrome, Firefox and Safari,
each part on its own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Andreas Reinhold / reini
2026-09-17 07:16:32 +02:00
co-authored by Claude Opus 5
parent 16c52d46e9
commit b62c83f244
8 changed files with 170 additions and 8 deletions
@@ -273,7 +273,7 @@ Label button, icon button, toggle and responsive FAB in one component.
### `<x-tooltip>`
M3's plain tooltip, standalone around any trigger: `<x-tooltip text="Copy link" side="bottom"><button>…</button></x-tooltip>`. `side`: `top` (default), `bottom`, `left`, `right`. Shows on hover (fine pointers) and keyboard focus, and goes 1.5s after the pointer or the focus leaves it (M3's transient tooltip); only one is on screen at a time. It is `aria-hidden`, so the trigger has to carry the same words itself — as an icon button's `aria-label` does. Where the tip says something the trigger does not, use `<x-rich-tooltip>`, which points the trigger at its text. Buttons and FABs take a `tooltip` prop instead.
M3's plain tooltip, standalone around any trigger: `<x-tooltip text="Copy link" side="bottom"><button>…</button></x-tooltip>`. `side`: `top` (default), `bottom`, `left`, `right`. Shows on hover (fine pointers) and keyboard focus, and goes 1.5s after the pointer or the focus leaves it (M3's transient tooltip); the focus a sheet, a dialog or the modal rail moves to its first control as it opens does not show it, so a close button's tooltip waits for a Tab; only one is on screen at a time. It is `aria-hidden`, so the trigger has to carry the same words itself — as an icon button's `aria-label` does. Where the tip says something the trigger does not, use `<x-rich-tooltip>`, which points the trigger at its text. Buttons and FABs take a `tooltip` prop instead.
### `<x-menu>`, `<x-menu-item>`, `<x-menu-group>`, `<x-menu-separator>`
@@ -491,7 +491,7 @@ A few lines of context around a trigger, with an optional `title` and `actions`
</x-rich-tooltip>
```
Shows on hover and keyboard focus and goes 1.5s after the pointer or the focus leaves, as M3 times a plain tooltip too; `persistent` opens it on press and keeps it until a press elsewhere or Escape (use it when there are actions). The trigger is pointed at the bubble with `aria-describedby`, so its words are read out with the control. An open bubble stays open while the Livewire component around it renders, its actions' `wire:click` included. `side`: `bottom` (default), `top`, `left`, `right`.
Shows on hover and keyboard focus (not the focus a sheet or dialog moves to its trigger as it opens) and goes 1.5s after the pointer or the focus leaves, as M3 times a plain tooltip too; `persistent` opens it on press and keeps it until a press elsewhere or Escape (use it when there are actions). The trigger is pointed at the bubble with `aria-describedby`, so its words are read out with the control. An open bubble stays open while the Livewire component around it renders, its actions' `wire:click` included. `side`: `bottom` (default), `top`, `left`, `right`.
### `<x-stat>`