use-ripple-hook
Version:
Customizable, lightweight React hook for implementing Google's Material UI style ripple effect
196 lines (195 loc) • 8.67 kB
JavaScript
;
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));
}