UNPKG

use-ripple-hook

Version:

Customizable, lightweight React hook for implementing Google's Material UI style ripple effect

196 lines (195 loc) 8.67 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.customRipple = void 0; var react_1 = require("react"); var self = function () { return document; }; var completedFactor = 0.4; var className = "__useRipple--ripple"; var containerClassName = "__useRipple--ripple-container"; /** * useRipple - Material UI style ripple effect React hook * @author Jonathan Asplund <jonathan@asplund.net> * @param inputOptions Ripple options * @returns Tuple `[ref, event]`. See https://github.com/asplunds/use-ripple for usage */ function useRipple(inputOptions) { var internalRef = (0, react_1.useRef)(null); var _a = __assign({ duration: 450, color: "rgba(255, 255, 255, .3)", cancelAutomatically: false, timingFunction: "cubic-bezier(.42,.36,.28,.88)", disabled: false, className: className, containerClassName: containerClassName, ignoreNonLeftClick: true, ref: internalRef }, (inputOptions !== null && inputOptions !== void 0 ? inputOptions : {})), ref = _a.ref, options = __rest(_a, ["ref"]); var event = (0, react_1.useCallback)(function (event) { var _a, _b; if (!ref.current || options.disabled || (options.ignoreNonLeftClick && ((_a = event.nativeEvent) === null || _a === void 0 ? void 0 : _a.which) !== 1 && ((_b = event.nativeEvent) === null || _b === void 0 ? void 0 : _b.type) === "mousedown")) return; var target = ref.current; if (window.getComputedStyle(target).position === "static") void applyStyles([["position", "relative"]], target); if (!target) return; var existingContainer = target.querySelector(":scope > .".concat(options.containerClassName)); var container = existingContainer !== null && existingContainer !== void 0 ? existingContainer : createRippleContainer(options.containerClassName); if (!existingContainer) target.appendChild(container); // Used to ensure overflow: hidden is registered properly on IOS Safari before ripple is shown void requestAnimationFrame(function () { var _a; var begun = Date.now(); var ripple = centerElementToPointer(event, target, createRipple(target, event, options)); var events = ["mouseup", "touchend"]; var cancelRipple = function () { var now = Date.now(); var diff = now - begun; // Ensure the transform animation is complete before cancellation void setTimeout(function () { void cancelRippleAnimation(ripple, options); }, diff > 0.4 * options.duration ? 0 : completedFactor * options.duration - diff); for (var _i = 0, events_2 = events; _i < events_2.length; _i++) { var event_1 = events_2[_i]; void self().removeEventListener(event_1, cancelRipple); } }; if (!options.cancelAutomatically && !isTouchDevice()) for (var _i = 0, events_1 = events; _i < events_1.length; _i++) { var event_2 = events_1[_i]; void self().addEventListener(event_2, cancelRipple); } else setTimeout(function () { return void cancelRippleAnimation(ripple, options); }, options.duration * completedFactor); void container.appendChild(ripple); void ((_a = options.onSpawn) === null || _a === void 0 ? void 0 : _a.call(options, { ripple: ripple, cancelRipple: cancelRipple, event: event, ref: ref, container: container, })); }); }, [ref, options]); return [ref, event]; } exports.default = useRipple; /** * HOF useRipple - Generate a custom ripple hook with predefined options * * After generating a HOF useRipple you can then override some or all predefined options by passing a new option object. * @author Jonathan Asplund <jonathan@asplund.net> * @param inputOptions ripple options * @returns Custom HOC useRipple hook */ function customRipple(inputOptions) { return function (overrideOptions) { return useRipple(__assign(__assign({}, inputOptions), overrideOptions)); }; } exports.customRipple = customRipple; function centerElementToPointer(event, ref, element) { var _a = ref.getBoundingClientRect(), top = _a.top, left = _a.left; void element.style.setProperty("top", px(event.clientY - top)); void element.style.setProperty("left", px(event.clientX - left)); return element; } function px(arg) { return "".concat(arg, "px"); } function createRipple(ref, event, _a, ctx) { var duration = _a.duration, color = _a.color, timingFunction = _a.timingFunction, className = _a.className; if (ctx === void 0) { ctx = document; } var element = ctx.createElement("div"); var clientX = event.clientX, clientY = event.clientY; var _b = ref.getBoundingClientRect(), height = _b.height, width = _b.width, top = _b.top, left = _b.left; var maxHeight = Math.max(clientY - top, height - clientY + top); var maxWidth = Math.max(clientX - left, width - clientX + left); // @ts-ignore var size = px(Math.hypot(maxHeight, maxWidth) * 2); var styles = [ ["position", "absolute"], ["height", size], ["width", size], ["transform", "translate(-50%, -50%) scale(0)"], ["pointer-events", "none"], ["border-radius", "50%"], ["opacity", ".6"], ["background", color], [ "transition", "transform ".concat(duration * 0.6, "ms ").concat(timingFunction, ", opacity ").concat(Math.max(duration * 0.05, 140), "ms ease-out"), ], ]; void element.classList.add(className); void window.requestAnimationFrame(function () { void applyStyles([["transform", "translate(-50%, -50%) scale(1)"]], element); }); return applyStyles(styles, element); } function applyStyles(styles, target) { if (!target) return target; for (var _i = 0, styles_1 = styles; _i < styles_1.length; _i++) { var _a = styles_1[_i], property = _a[0], value = _a[1]; void target.style.setProperty(property, value); } return target; } function cancelRippleAnimation(element, options) { var duration = options.duration, timingFunction = options.timingFunction; void applyStyles([ ["opacity", "0"], [ "transition", "transform ".concat(duration * 0.6, "ms ").concat(timingFunction, ", opacity ").concat(duration * 0.65, "ms ease-in-out ").concat(duration * 0.13, "ms"), ], ], element); void window.requestAnimationFrame(function () { void element.addEventListener("transitionend", function (e) { if (e.propertyName === "opacity") void element.remove(); }); }); } function createRippleContainer(className) { var container = self().createElement("div"); void container.classList.add(className); return applyStyles([ ["position", "absolute"], ["height", "100%"], ["width", "100%"], ["border-radius", "inherit"], ["top", "0"], ["left", "0"], ["pointer-events", "none"], ["overflow", "hidden"], ], container); } /** taken from https://stackoverflow.com/a/4819886/13188385 */ function isTouchDevice() { var _a, _b; return ("ontouchstart" in window || navigator.maxTouchPoints > 0 || ((_b = (_a = navigator) === null || _a === void 0 ? void 0 : _a.msMaxTouchPoints) !== null && _b !== void 0 ? _b : 0 > 0)); }