/** * `materialRichTooltip`: shows an ``. * * Transient (the default): like a plain tooltip — after a short hover on a pointer that can hover, * at once on keyboard focus, and it stays while the pointer moves onto the bubble to reach its * actions. Persistent: a press on the trigger opens it as a light-dismiss popover. * * The trigger is pointed at the bubble with `aria-describedby`, so the explanation — the whole * point of a rich tooltip — is read out with the control rather than never at all; a persistent * one also says `aria-haspopup="dialog"` and whether it is open. The server cannot write any of * it: the trigger comes from a slot, and only script knows which element in it takes the focus. * A Livewire morph strips attributes the server did not render and gives the bubble a new id, so * they are written again whenever the trigger is reached. */ const HOVER_DELAY_MS = 500 const LEAVE_GRACE_MS = 200 document.addEventListener('alpine:init', () => { window.Alpine.data('materialRichTooltip', (persistent = false) => ({ timer: null, init() { const bubble = this.$refs.bubble const wrapper = this.$el const open = () => bubble.matches(':popover-open') const control = () => [...wrapper.querySelectorAll('button, a[href], [tabindex]')].find((element) => !bubble.contains(element)) const describe = () => { const trigger = control() if (!trigger) { return } trigger.setAttribute('aria-describedby', bubble.id) if (persistent) { trigger.setAttribute('aria-haspopup', 'dialog') trigger.setAttribute('aria-expanded', String(open())) } } describe() bubble.addEventListener('toggle', describe) wrapper.addEventListener('focusin', describe) wrapper.addEventListener('pointerenter', describe) if (persistent) { wrapper.addEventListener('click', (event) => { if (bubble.contains(event.target)) { return } open() ? bubble.hidePopover() : bubble.showPopover() }) return } const show = (delay) => { clearTimeout(this.timer) this.timer = setTimeout(() => !open() && bubble.showPopover(), delay) } const hide = (delay = 0) => { clearTimeout(this.timer) this.timer = setTimeout(() => open() && bubble.hidePopover(), delay) } wrapper.addEventListener('pointerenter', (event) => event.pointerType === 'mouse' && show(HOVER_DELAY_MS)) wrapper.addEventListener('pointerleave', () => hide(LEAVE_GRACE_MS)) wrapper.addEventListener('focusin', (event) => event.target.matches(':focus-visible') && show(0)) wrapper.addEventListener('focusout', (event) => !wrapper.contains(event.relatedTarget) && hide()) document.addEventListener('keydown', (event) => event.key === 'Escape' && hide()) }, destroy() { clearTimeout(this.timer) }, })) })