v-wave
Version:
The material-ripple directive for Vue that actually works
358 lines (342 loc) • 13.9 kB
JavaScript
;
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);
})();