Files
livewire-material/resources/views/components/tooltip.blade.php
T
Andreas Reinhold / reiniandClaude Opus 5 b62c83f244 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>
2026-09-17 07:16:32 +02:00

59 lines
2.5 KiB
PHP

{{-- An M3 plain tooltip: a short label for a control, in the inverse surface.
Two ways in. Inside a component that already names its own anchor `<x-button tooltip="…">`
passes `anchor` it is a bare popover. Standalone, it wraps its trigger:
<x-tooltip text="Copy link"><button ></button></x-tooltip>
It shows after a short hover on a pointer that can hover, at once on keyboard focus, and hides
at once on a press or Escape; leaving or blurring lets it stand for M3's 1.5s. The focus a
sheet, a dialog or the modal rail moves to its first control as it opens is not keyboard focus,
so a sheet's close button shows no tooltip the moment the sheet appears; a Tab onto it does.
Only one
tooltip is on screen at a time, as M3 asks. A phone has no hover, so a control there carries its
words. The bubble is a `popover="manual"` in the top layer never clipped by an
`overflow: hidden` parent, never widening a scroll container placed by CSS anchor
positioning on `side` (`top`, `bottom`, `left`, `right`), flipping when there is no room.
Drawn by resources/css/components/tooltip.css.
It is aria-hidden: an icon button takes the same words as its aria-label, and a labelled
control would otherwise read them twice.
The caller's `class` and `style` land on the root — standalone, that is the wrapper this
component draws around the trigger (`data-md-tooltip-anchor`), merged with its own
`anchor-name`. Passed an `anchor` instead, the component renders no wrapper of its own: only
the popover bubble, a fragment beside another component's root (`<x-button tooltip>` and
friends never forward a caller's attributes to it), so there is nothing of the caller's to
place. --}}
@props([
'text',
'side' => 'top',
'anchor' => null,
])
@php
$side = in_array($side, ['top', 'bottom', 'left', 'right'], true) ? $side : 'top';
$standalone = $anchor === null;
$anchor ??= '--material-tooltip-'.\Illuminate\Support\Str::lower(\Illuminate\Support\Str::random(10));
@endphp
@if ($standalone)
<span data-md-tooltip-anchor {{ $attributes->merge(['style' => "anchor-name: {$anchor}"]) }}>
{{ $slot }}
@endif
<span
popover="manual"
aria-hidden="true"
x-data="materialTooltip"
data-md-tooltip
data-md-popover-exit
data-md-side="{{ $side }}"
style="position-anchor: {{ $anchor }}"
>{{ $text }}</span>
@if ($standalone)
</span>
@endif