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>
74 lines
3.3 KiB
PHP
74 lines
3.3 KiB
PHP
{{-- An M3 rich tooltip: a few lines of context for a control, with an optional subhead and actions.
|
|
|
|
<x-rich-tooltip title="Expiry" text="Recipients lose access after this time. Admins can change the longest allowed.">
|
|
<x-button icon="help" aria-label="About expiry" />
|
|
<x-slot:actions><x-button label="Learn more" link="/help/expiry" /></x-slot:actions>
|
|
</x-rich-tooltip>
|
|
|
|
It wraps its trigger. By default it shows on hover and keyboard focus like a plain tooltip (not
|
|
on the focus a sheet or dialog moves to its trigger as it opens);
|
|
`persistent` makes it open on press instead and stay until a press elsewhere or Escape — the
|
|
form M3 asks for when it has actions. The bubble is a popover in surface-container with a
|
|
medium corner and elevation 2, 312px at most, placed by anchor positioning on `side`.
|
|
|
|
The bubble's id and anchor name are new with every render; its `wire:key` (see `<x-menu>`)
|
|
lets a Livewire morph patch it in place, so an open bubble stays open — through its own
|
|
action's `wire:click` too — and resources/js/rich-tooltip.js keeps showing and hiding the
|
|
element on the page rather than one the morph took away.
|
|
|
|
resources/js/rich-tooltip.js points the trigger at the bubble with `aria-describedby`, and a
|
|
`persistent` one also carries `aria-haspopup="dialog"` and `aria-expanded` (ACT-22): the
|
|
explanation is the whole point of a rich tooltip, and without the association a screen reader
|
|
reads only the trigger's own label. The trigger arrives in a slot, so only script can find the
|
|
element in it that takes the focus. It stands for 1.5s after the pointer or focus leaves,
|
|
M3's transient tooltip timing (ACT-25), unless `persistent` keeps it open until dismissed.
|
|
|
|
RichTooltipTokens (androidx Compose Material 3, Apache-2.0): title-small subhead and body-medium
|
|
text in on-surface-variant, label-large actions in primary. Drawn by
|
|
resources/css/components/rich-tooltip.css from `data-md-rich-tooltip` and `data-md-side`. --}}
|
|
|
|
@props([
|
|
'title' => null,
|
|
'text' => null,
|
|
'side' => 'bottom',
|
|
'persistent' => false,
|
|
])
|
|
|
|
@php
|
|
$side = in_array($side, ['top', 'bottom', 'left', 'right'], true) ? $side : 'bottom';
|
|
$key = \Illuminate\Support\Str::lower(\Illuminate\Support\Str::random(10));
|
|
$anchor = "--material-rich-tooltip-{$key}";
|
|
@endphp
|
|
|
|
<span
|
|
data-md-rich-tooltip
|
|
{{ $attributes }}
|
|
style="anchor-name: {{ $anchor }}"
|
|
x-data="materialRichTooltip({{ $persistent ? 'true' : 'false' }})"
|
|
>
|
|
{{ $trigger ?? $slot }}
|
|
|
|
<span
|
|
x-ref="bubble"
|
|
{{ new \Illuminate\View\ComponentAttributeBag(['wire:key' => 'material-rich-tooltip']) }}
|
|
id="material-rich-tooltip-{{ $key }}"
|
|
popover="{{ $persistent ? 'auto' : 'manual' }}"
|
|
role="{{ $persistent ? 'dialog' : 'tooltip' }}"
|
|
@if ($title) aria-label="{{ $title }}" @endif
|
|
data-md-rich-tooltip-bubble
|
|
data-md-popover-exit
|
|
data-md-side="{{ $side }}"
|
|
style="position-anchor: {{ $anchor }}"
|
|
>
|
|
@if ($title)
|
|
<span data-md-rich-tooltip-title>{{ $title }}</span>
|
|
@endif
|
|
|
|
<span data-md-rich-tooltip-text>{{ $text }}</span>
|
|
|
|
@isset($actions)
|
|
<span data-md-rich-tooltip-actions>{{ $actions }}</span>
|
|
@endisset
|
|
</span>
|
|
</span>
|