UNPKG

v-wave

Version:

The material-ripple directive for Vue that actually works

358 lines (342 loc) 13.9 kB
"use strict"; var VWave = (() => { var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { default: () => index_default }); // src/options.ts var DEFAULT_PLUGIN_OPTIONS = { directive: "wave", color: "currentColor", initialOpacity: 0.2, finalOpacity: 0.1, duration: 0.4, dissolveDuration: 0.15, waitForRelease: true, easing: "ease-out", cancellationPeriod: 75, trigger: "auto", tagName: "div", disabled: false, respectDisabledAttribute: true, respectPrefersReducedMotion: true, stopPropagation: false }; // src/utils/isVue3.ts var isVue3 = (app) => "config" in app && "globalProperties" in app.config; // src/utils/hookKeys.ts var getHooks = (app) => { let vue3; if (app === "vue2") vue3 = false; else if (app === "vue3") vue3 = true; else vue3 = isVue3(app); return vue3 ? { mounted: "mounted", updated: "updated" } : { mounted: "inserted", updated: "componentUpdated" }; }; // src/utils/triggerIsID.ts var triggerIsID = (trigger) => typeof trigger === "string" && trigger !== "auto"; // src/utils/markWaveBoundary.ts var markWaveBoundary = (el, trigger) => { el.dataset.vWaveBoundary = triggerIsID(trigger) ? trigger : "true"; }; // src/utils/createContainerElement.ts var createContainer = ({ borderTopLeftRadius, borderTopRightRadius, borderBottomLeftRadius, borderBottomRightRadius }, tagName) => { const waveContainer = document.createElement(tagName); waveContainer.style.top = "0"; waveContainer.style.left = "0"; waveContainer.style.width = "100%"; waveContainer.style.height = "100%"; waveContainer.style.display = "block"; waveContainer.style.position = "absolute"; waveContainer.style.borderRadius = `${borderTopLeftRadius} ${borderTopRightRadius} ${borderBottomRightRadius} ${borderBottomLeftRadius}`; waveContainer.style.overflow = "hidden"; waveContainer.style.pointerEvents = "none"; waveContainer.style.webkitMaskImage = "-webkit-radial-gradient(white, black)"; waveContainer.dataset.vWaveContainerInternal = "true"; return waveContainer; }; // src/utils/createWaveElement.ts var createWaveElement = ({ x, y }, size, options) => { const waveElement = document.createElement("div"); waveElement.style.position = "absolute"; waveElement.style.width = `${size}px`; waveElement.style.height = `${size}px`; waveElement.style.top = `${y}px`; waveElement.style.left = `${x}px`; waveElement.style.background = options.color; waveElement.style.borderRadius = "50%"; waveElement.style.opacity = `${options.initialOpacity}`; waveElement.style.transform = "translate(-50%,-50%) scale(0)"; waveElement.style.transition = `transform ${options.duration}s ${options.easing}, opacity ${options.duration}s ${options.easing}`; return waveElement; }; // src/utils/magnitude.ts function magnitude(x1, y1, x2, y2) { const deltaX = x1 - x2; const deltaY = y1 - y2; return Math.sqrt(deltaX * deltaX + deltaY * deltaY); } // src/utils/getDistanceToFurthestCorner.ts function getDistanceToFurthestCorner({ x, y }, { width, height }) { const topLeft = magnitude(x, y, 0, 0); const topRight = magnitude(x, y, width, 0); const bottomLeft = magnitude(x, y, 0, height); const bottomRight = magnitude(x, y, width, height); return Math.max(topLeft, topRight, bottomLeft, bottomRight); } // src/utils/getRelativePointer.ts var getRelativePointer = ({ x, y }, { top, left }) => ({ x: x - left, y: y - top }); // src/utils/parentElementStyles.ts var saveParentElementStyles = (el, computedStyles) => { if (computedStyles.position === "static") { ; ["top", "left", "right", "bottom"].forEach((dir) => { if (computedStyles[dir] && computedStyles[dir] !== "auto") console.warn( "[v-wave]:", el, `You're using a \`static\` positioned element with a non-auto value (${computedStyles[dir]}) for \`${dir}\`.`, "It's position will be changed to relative while displaying the wave which might cause the element to visually jump." ); }); el.dataset.originalPositionValue = el.style.position; el.style.position = "relative"; } }; var restoreParentElementStyles = (el) => { var _a; el.style.position = (_a = el.dataset.originalPositionValue) != null ? _a : ""; delete el.dataset.originalPositionValue; }; // src/utils/wave-count.ts var WAVE_COUNT = "vWaveCountInternal"; function incrementWaveCount(el) { const count = getWaveCount(el); setWaveCount(el, count + 1); } function decrementWaveCount(el) { const count = getWaveCount(el); setWaveCount(el, count - 1); } function setWaveCount(el, count) { el.dataset[WAVE_COUNT] = count.toString(); } function getWaveCount(el) { var _a; return Number.parseInt((_a = el.dataset[WAVE_COUNT]) != null ? _a : "0", 10); } function deleteWaveCount(el) { delete el.dataset[WAVE_COUNT]; } // src/wave.ts var SCALE_FACTOR = 2.05; var wave = (screenPos, el, options) => { if (options.disabled) return; if (options.respectDisabledAttribute && el.hasAttribute("disabled")) return; if (options.respectPrefersReducedMotion && window.matchMedia("(prefers-reduced-motion: reduce)").matches) return; const rect = el.getBoundingClientRect(); const computedStyles = window.getComputedStyle(el); const relativePos = getRelativePointer(screenPos, rect); const size = SCALE_FACTOR * getDistanceToFurthestCorner(relativePos, rect); const existingWaveContainer = el.querySelector("[data-v-wave-container-internal]"); const waveContainer = existingWaveContainer != null ? existingWaveContainer : createContainer(computedStyles, options.tagName); const waveEl = createWaveElement(relativePos, size, options); incrementWaveCount(el); saveParentElementStyles(el, computedStyles); waveContainer.appendChild(waveEl); if (!existingWaveContainer) el.appendChild(waveContainer); let shouldDissolveWave = !options.waitForRelease; const releaseWave = (e) => { if (typeof e !== "undefined") { document.removeEventListener("pointerup", releaseWave); document.removeEventListener("pointercancel", releaseWave); } if (shouldDissolveWave) dissolveWave(); else shouldDissolveWave = true; }; const dissolveWave = () => { waveEl.style.transition = `opacity ${options.dissolveDuration}s linear`; waveEl.style.opacity = "0"; setTimeout(() => { waveEl.remove(); decrementWaveCount(el); if (getWaveCount(el) === 0) { deleteWaveCount(el); waveContainer.remove(); restoreParentElementStyles(el); } }, options.dissolveDuration * 1e3); }; if (options.waitForRelease) { document.addEventListener("pointerup", releaseWave); document.addEventListener("pointercancel", releaseWave); } const token = setTimeout(() => { document.removeEventListener("pointercancel", cancelWave); requestAnimationFrame(() => { waveEl.style.transform = "translate(-50%,-50%) scale(1)"; waveEl.style.opacity = `${options.finalOpacity}`; setTimeout(() => releaseWave(), options.duration * 1e3); }); }, options.cancellationPeriod); const cancelWave = () => { clearTimeout(token); waveEl.remove(); decrementWaveCount(el); if (getWaveCount(el) === 0) { deleteWaveCount(el); waveContainer.remove(); restoreParentElementStyles(el); } document.removeEventListener("pointerup", releaseWave); document.removeEventListener("pointercancel", releaseWave); document.removeEventListener("pointercancel", cancelWave); }; document.addEventListener("pointercancel", cancelWave); }; // src/index.ts var optionMap = /* @__PURE__ */ new WeakMap(); var createWaveActivationHandler = (globalOptions, el) => (event, position) => { if (!optionMap.has(el)) return; const options = __spreadValues(__spreadValues({}, globalOptions), optionMap.get(el)); if (options.stopPropagation) event.stopPropagation(); if (options.trigger === false) return wave(event, el, options); if (triggerIsID(options.trigger)) return; const trigger = el.querySelector('[data-v-wave-trigger="true"]'); if (!trigger && options.trigger === true) return; if (trigger && !event.composedPath().includes(trigger)) return; wave(position != null ? position : event, el, __spreadProps(__spreadValues({}, options), { waitForRelease: position ? false : options.waitForRelease })); }; var createDirective = (globalUserOptions = {}, app = "vue3") => { const globalOptions = __spreadValues(__spreadValues({}, DEFAULT_PLUGIN_OPTIONS), globalUserOptions); const hooks = getHooks(app); const handleTrigger = (event) => { if (event.detail !== 0) return; const triggerEl = event.currentTarget; const trigger = triggerEl.dataset.vWaveTrigger; const associatedElements = document.querySelectorAll( `[data-v-wave-boundary="${trigger}"]` ); associatedElements.forEach((el) => { const isSyntheticClick = event.type === "click"; let origin; if (isSyntheticClick) { const rect = triggerEl.getBoundingClientRect(); origin = { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }; } else { origin = event; } const options = __spreadValues(__spreadValues({}, globalOptions), optionMap.get(el)); wave(origin, el, __spreadProps(__spreadValues({}, options), { waitForRelease: isSyntheticClick ? false : options.waitForRelease })); }); }; const waveDirective = { [hooks.mounted](el, { value = {} }) { var _a; optionMap.set(el, value); markWaveBoundary(el, (_a = value == null ? void 0 : value.trigger) != null ? _a : globalOptions.trigger); const activationHandler = createWaveActivationHandler(globalOptions, el); el.addEventListener("pointerdown", activationHandler); el.addEventListener("click", (event) => { if (event.detail !== 0) return; const rect = el.getBoundingClientRect(); const center = { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }; activationHandler(event, center); }); }, [hooks.updated](el, { value = {} }) { var _a; optionMap.set(el, value); markWaveBoundary(el, (_a = value == null ? void 0 : value.trigger) != null ? _a : globalOptions.trigger); } }; const triggerDirective = { [hooks.mounted](el, { arg: trigger = "true" }) { el.dataset.vWaveTrigger = trigger; if (trigger !== "true") { el.addEventListener("pointerdown", handleTrigger); el.addEventListener("click", handleTrigger); } }, [hooks.updated](el, { arg: trigger = "true" }) { el.dataset.vWaveTrigger = trigger; if (trigger === "true") { el.removeEventListener("pointerdown", handleTrigger); el.removeEventListener("click", handleTrigger); } else { el.addEventListener("pointerdown", handleTrigger); el.addEventListener("click", handleTrigger); } } }; return { wave: waveDirective, vWave: waveDirective, waveTrigger: triggerDirective, vWaveTrigger: triggerDirective }; }; var VWave = { install(app, globalUserOptions = {}) { if (this.installed) return; this.installed = true; const globalOptions = __spreadValues(__spreadValues({}, DEFAULT_PLUGIN_OPTIONS), globalUserOptions); const { vWave, vWaveTrigger } = createDirective(globalOptions, app); app.directive(globalOptions.directive, vWave); app.directive(`${globalOptions.directive}-trigger`, vWaveTrigger); }, installed: false, createLocalWaveDirective: createDirective }; var index_default = VWave; return __toCommonJS(index_exports); })();