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
<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
58 lines
2.0 KiB
JavaScript
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())
|
|
},
|
|
}))
|
|
})
|