react-morphing-modal
Version:
React morphing modal! The easiest way to be fancy!
257 lines (215 loc) • 8.42 kB
JavaScript
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var React = require('react');
var React__default = _interopDefault(React);
var bodyNode;
function getBodyNode() {
if (!bodyNode) {
bodyNode = document.querySelector('body');
}
return bodyNode;
}
function computeCoordinateScaleValue(initialCoordinate, nodeSize, windowCoordinate) {
var intermediateCoordinate = windowCoordinate - initialCoordinate - nodeSize;
var maxCoordinate = Math.max(initialCoordinate, intermediateCoordinate);
var scaleValue = (maxCoordinate * 2 + nodeSize) / nodeSize;
return Math.ceil(scaleValue * 10) / 10;
}
function getScaleValues(node, top, left) {
return {
scaleX: computeCoordinateScaleValue(left, node.offsetWidth, window.innerWidth),
scaleY: computeCoordinateScaleValue(top, node.offsetHeight, window.innerHeight)
};
}
var bodyScrolling = {
lock: function lock() {
getBodyNode().style.overflow = 'hidden';
},
unlock: function unlock() {
getBodyNode().style.removeProperty('overflow');
}
};
function getPlaceholderComputedStyle(trigger, placeholder) {
var top = trigger.offsetTop - window.scrollY;
var left = trigger.offsetLeft - window.scrollX;
var placeholderScale = getScaleValues(placeholder, top, left); // 👽1.5 to handle circle and rounded border
return "\n top: " + top + "px;\n left: " + left + "px;\n transform: scale(" + placeholderScale.scaleX * 1.5 + "," + placeholderScale.scaleY * 1.5 + ");\n";
}
function getBorderRadius(styles) {
var borderTopLeftRadius = styles.borderTopLeftRadius,
borderTopRightRadius = styles.borderTopRightRadius,
borderBottomLeftRadius = styles.borderBottomLeftRadius,
borderBottomRightRadius = styles.borderBottomRightRadius;
return "\n border-top-left-radius: " + borderTopLeftRadius + ";\n border-top-right-radius: " + borderTopRightRadius + ";\n border-bottom-left-radius: " + borderBottomLeftRadius + ";\n border-bottom-right-radius: " + borderBottomRightRadius + ";\n ";
}
function getBackground(styles) {
// work for chrome only. firefox do not return shorthand prop
if (styles.background && styles.background.length > 0) {
return styles.background;
} // inline style in ff return none.
// I could put styles.backgroundColor here so I don't need to check for none
// but I don't want to rely on call order
if (styles.backgroundImage && styles.backgroundImage.length > 0 && styles.backgroundImage !== 'none') {
return styles.backgroundImage;
}
if (styles.backgroundColor && styles.backgroundColor.length > 0) {
return styles.backgroundColor;
}
return '';
}
var STATE = {
IS_CLOSE: 0,
IS_IN_PROGRESS: 1,
IS_OPEN: 2
};
var noop = function noop() {};
function useModal(hookOptions) {
if (hookOptions === void 0) {
hookOptions = {};
}
var placeholderRef = React.useRef(null);
var activeTriggerRef = React.useRef({
nodeRef: null,
options: null
});
var _useState = React.useState(null),
activeModal = _useState[0],
setActiveModal = _useState[1];
var _useState2 = React.useState(STATE.IS_CLOSE),
state = _useState2[0],
setState = _useState2[1];
var event = hookOptions.event || 'onClick';
var onOpenCallback = hookOptions.onOpen || noop;
var onCloseCallback = hookOptions.onClose || noop;
function handleEscapeKey(e) {
var key = e.key || e.keyCode;
if (key === 'Escape' || key === 'Esc' || key === 27) close();
} // maybe throttle later if needed
function handleResize() {
requestAnimationFrame(updatePlaceholder);
}
function updatePlaceholder() {
var trigger = activeTriggerRef.current.nodeRef.current;
if (placeholderRef.current && trigger) {
var placeholderStyle = getPlaceholderComputedStyle(trigger, placeholderRef.current);
placeholderRef.current.style.cssText += placeholderStyle;
}
}
React.useEffect(function () {
if (state !== STATE.IS_CLOSE) {
document.addEventListener('keydown', handleEscapeKey);
window.addEventListener('resize', handleResize);
}
return function () {
document.removeEventListener('keydown', handleEscapeKey);
window.removeEventListener('resize', handleResize);
};
}, [state]);
var open = function open(triggerRef, triggerOptions) {
activeTriggerRef.current.nodeRef = triggerRef;
if (placeholderRef.current && triggerRef.current) {
var placeholder = placeholderRef.current;
var trigger = triggerRef.current;
var triggerStyles = window.getComputedStyle(trigger);
var borderRadius = getBorderRadius(triggerStyles);
var modalId = null;
var background = hookOptions.background || getBackground(triggerStyles);
var onOpen = onOpenCallback;
if (typeof triggerOptions === 'number' || typeof triggerOptions === 'string' || typeof triggerOptions === 'symbol') {
modalId = triggerOptions;
} else if (typeof triggerOptions === 'object' && triggerOptions !== null) {
activeTriggerRef.current.options = triggerOptions;
modalId = triggerOptions.id || modalId;
background = triggerOptions.background || background;
onOpen = triggerOptions.onOpen || onOpenCallback;
}
bodyScrolling.lock();
placeholder.style.cssText = "width: " + trigger.offsetWidth + "px; height: " + trigger.offsetHeight + "px; background: " + background + "; " + borderRadius;
if (modalId) {
setActiveModal(modalId);
}
setState(STATE.IS_IN_PROGRESS);
var placeholderStyle = getPlaceholderComputedStyle(trigger, placeholder);
placeholder.style.cssText += placeholderStyle;
placeholder.addEventListener('transitionend', function () {
onOpen();
setState(STATE.IS_OPEN);
}, {
once: true
});
}
};
var close = function close() {
if (placeholderRef.current) {
var triggerOptions = activeTriggerRef.current.options;
var placeholder = placeholderRef.current;
var onClose = triggerOptions && triggerOptions.onClose ? triggerOptions.onClose : onCloseCallback;
setState(STATE.IS_IN_PROGRESS);
bodyScrolling.unlock();
placeholder.style.removeProperty('transform');
placeholder.style.transform = 'scale(1,1);';
placeholder.addEventListener('transitionend', function () {
onClose();
setState(STATE.IS_CLOSE);
setActiveModal(null);
}, {
once: true
});
}
};
var getTriggerProps = function getTriggerProps(options) {
var _ref;
var ref = React.useRef();
return _ref = {
ref: ref
}, _ref[typeof options === 'object' && options !== null && options.event ? options.event : event] = open.bind(null, ref, options), _ref;
};
return {
open: open,
close: close,
activeModal: activeModal,
getTriggerProps: getTriggerProps,
modalProps: {
placeholderRef: placeholderRef,
state: state,
close: close
}
};
}
var classname = {
container: 'RMM__container',
closeButton: 'RMM__close-button',
placeholder: 'RMM__placeholder',
body: 'RMM__body',
noBodyPadding: 'RMM__body--no-padding',
get: function get(className, isActive) {
return isActive ? className + " " + className + "--is-active" : className;
}
};
var Modal = function Modal(_ref) {
var state = _ref.state,
placeholderRef = _ref.placeholderRef,
close = _ref.close,
closeButton = _ref.closeButton,
children = _ref.children,
padding = _ref.padding;
var bodyPadding = !padding ? " " + classname.noBodyPadding : '';
return React__default.createElement("div", {
className: classname.get(classname.container, state === STATE.IS_IN_PROGRESS || state === STATE.IS_OPEN)
}, React__default.createElement("div", {
className: classname.get(classname.body, STATE.IS_OPEN === state) + bodyPadding
}, children), React__default.createElement("div", {
className: classname.placeholder,
ref: placeholderRef
}), closeButton && React__default.createElement("div", {
className: classname.get(classname.closeButton, STATE.IS_OPEN === state),
onClick: close
}));
};
Modal.defaultProps = {
closeButton: true,
padding: true
};
exports.Modal = Modal;
exports.useModal = useModal;
//# sourceMappingURL=react-morphing-modal.cjs.development.js.map
;