import type { Directive, DirectiveBinding } from 'vue' /** * Ripples are rendered inside an overflow-hidden container instead of setting * `overflow: hidden` on the host element, so the host's extended touch-target * pseudo-element (see Btn.vue) is not clipped. */ function getRippleContainer(el: HTMLElement): HTMLElement { let container = (el as any).__rippleContainer as HTMLElement | undefined if (!container || !container.isConnected) { container = document.createElement('span') Object.assign(container.style, { position: 'absolute', inset: '0', overflow: 'hidden', borderRadius: 'inherit', pointerEvents: 'none', }) el.appendChild(container); (el as any).__rippleContainer = container } return container } const ripple: Directive = { mounted(el: HTMLElement, binding: DirectiveBinding) { const clickHandler = (e: MouseEvent) => { const rect = el.getBoundingClientRect() const size = Math.max(rect.width, rect.height) const ripple = document.createElement('span') ripple.className = 'ripple' Object.assign(ripple.style, { width: `${size}px`, height: `${size}px`, left: `${e.clientX - rect.left - size / 2}px`, top: `${e.clientY - rect.top - size / 2}px`, position: 'absolute', borderRadius: '50%', transform: 'scale(0)', background: 'rgba(0, 0, 0, 0.3)', pointerEvents: 'none', animation: 'rippleEffect 0.6s ease-out', }) getRippleContainer(el).appendChild(ripple) setTimeout(() => { ripple.remove() }, 600) }; (el as any).__rippleClickHandler = clickHandler if (binding.value !== false) { if (getComputedStyle(el).position === 'static') { el.style.position = 'relative' } el.addEventListener('mousedown', clickHandler) } }, updated(el: HTMLElement, binding: DirectiveBinding) { const clickHandler = (el as any).__rippleClickHandler if (binding.value === false) { el.removeEventListener('mousedown', clickHandler) } else { if (getComputedStyle(el).position === 'static') { el.style.position = 'relative' } el.addEventListener('mousedown', clickHandler) } }, unmounted(el: HTMLElement) { const clickHandler = (el as any).__rippleClickHandler if (clickHandler) { el.removeEventListener('mousedown', clickHandler) delete (el as any).__rippleClickHandler } }, getSSRProps() { return {} }, } export default ripple