Files
livewire-material/resources/js/tooltip.js
T
Andreas Reinhold / reiniandClaude Opus 5 cd64f4f371
tests / browser (firefox, firefox) (push) Successful in 1m54s
tests / browser (safari, webkit) (push) Successful in 2m17s
tests / lint (push) Successful in 59s
tests / feature (8.4) (push) Successful in 1m7s
tests / feature (8.5) (push) Successful in 1m0s
tests / browser (chrome, chromium) (push) Successful in 1m49s
Add buttons, menus and the rest of M3 Expressive's actions
<x-button> (label buttons, icon buttons and toggles in five sizes, with
filled, tonal, outlined, elevated and text variants in any colour role),
<x-tooltip>, <x-menu> with items, groups and separators, <x-button-group>,
<x-group> as a connected button group, <x-split-button>, <x-fab>,
<x-fab-menu> and <x-loading>. Sizes, colours and shapes come from
androidx Compose Material 3's tokens; the loading indicator ports its
Morph into SVG + SMIL.

Menus follow WAI-ARIA's menu button pattern on popovers placed by CSS
anchor positioning. Browser tests run in Chromium, Firefox and WebKit.
The showcase fetches the icon names on demand: inlined, they tripped
Pest's test server into HTTP 431s under Firefox.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 06:09:28 +02:00

58 lines
2.0 KiB
JavaScript

/**
* `materialTooltip`: shows an `<x-tooltip>` popover for the control it belongs to.
*
* The trigger is the popover's parent element (the button, or the standalone wrapper). Hover
* counts only for a pointer that can hover, after a short delay so a pointer passing over a
* toolbar does not flash every label; keyboard focus shows it at once. A press, leaving, blur
* and Escape hide it.
*/
const HOVER_DELAY_MS = 500
document.addEventListener('alpine:init', () => {
window.Alpine.data('materialTooltip', () => ({
timer: null,
listeners: [],
init() {
const tip = this.$el
const trigger = tip.parentElement
const open = () => tip.matches(':popover-open')
const show = (delay) => {
clearTimeout(this.timer)
this.timer = setTimeout(() => {
if (tip.isConnected && !open()) {
tip.showPopover()
}
}, delay)
}
const hide = () => {
clearTimeout(this.timer)
if (open()) {
tip.hidePopover()
}
}
this.listen(trigger, 'pointerenter', (event) => event.pointerType === 'mouse' && show(HOVER_DELAY_MS))
this.listen(trigger, 'pointerleave', hide)
this.listen(trigger, 'pointerdown', hide)
this.listen(trigger, 'focusin', () => trigger.matches(':focus-within:has(:focus-visible), :focus-visible') && show(0))
this.listen(trigger, 'focusout', hide)
this.listen(document, 'keydown', (event) => event.key === 'Escape' && hide())
},
listen(target, type, handler) {
target.addEventListener(type, handler)
this.listeners.push(() => target.removeEventListener(type, handler))
},
destroy() {
clearTimeout(this.timer)
this.listeners.forEach((remove) => remove())
},
}))
})