@bianic-ui/modal
Version:
An accessible dialog (modal) component for React & Bianic UI
184 lines (157 loc) • 5.77 kB
JavaScript
;
exports.__esModule = true;
exports.useModal = useModal;
exports.useAriaHidden = useAriaHidden;
var _hooks = require("@bianic-ui/hooks");
var _utils = require("@bianic-ui/utils");
var _ariaHidden = require("aria-hidden");
var _react = require("react");
var _modalManager = require("./modal-manager");
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
/**
* Modal hook that manages all the logic for the modal dialog widget
* and returns prop getters, state and actions.
*
* @param props
*/
function useModal(props) {
var isOpen = props.isOpen,
onClose = props.onClose,
id = props.id,
_props$closeOnOverlay = props.closeOnOverlayClick,
closeOnOverlayClick = _props$closeOnOverlay === void 0 ? true : _props$closeOnOverlay,
_props$closeOnEsc = props.closeOnEsc,
closeOnEsc = _props$closeOnEsc === void 0 ? true : _props$closeOnEsc,
_props$useInert = props.useInert,
useInert = _props$useInert === void 0 ? true : _props$useInert,
onOverlayClickProp = props.onOverlayClick,
onEsc = props.onEsc;
var dialogRef = (0, _react.useRef)(null);
var overlayRef = (0, _react.useRef)(null);
var _useIds = (0, _hooks.useIds)(id, "bianic-modal", "bianic-modal--header", "bianic-modal--body"),
dialogId = _useIds[0],
headerId = _useIds[1],
bodyId = _useIds[2];
/**
* Hook used to polyfill `aria-modal` for older browsers.
* It uses `aria-hidden` to all other nodes.
*
* @see https://developer.paciellogroup.com/blog/2018/06/the-current-state-of-modal-dialog-accessibility/
*/
useAriaHidden(dialogRef, isOpen && useInert);
/**
* Hook use to manage multiple or nested modals
*/
(0, _modalManager.useModalManager)(dialogRef, isOpen);
var mouseDownTarget = (0, _react.useRef)(null);
var onMouseDown = (0, _react.useCallback)(function (event) {
mouseDownTarget.current = event.target;
}, []);
var onKeyDown = (0, _react.useCallback)(function (event) {
if (event.key === "Escape") {
event.stopPropagation();
if (closeOnEsc) {
onClose == null ? void 0 : onClose();
}
onEsc == null ? void 0 : onEsc();
}
}, [closeOnEsc, onClose, onEsc]);
var onOverlayClick = (0, _react.useCallback)(function (event) {
event.stopPropagation();
/**
* Make sure the event starts and ends on the same DOM element.
*
* This is used to prevent the modal from closing when you
* start dragging from the content, and release drag outside the content.
*
* We prevent this because it's technically not a considered "click outside"
*/
if (mouseDownTarget.current !== event.target) return;
/**
* When you click on the overlay, we want to remove only the topmost modal
*/
if (_modalManager.manager.isTopModal(dialogRef)) {
if (closeOnOverlayClick) {
onClose == null ? void 0 : onClose();
}
onOverlayClickProp == null ? void 0 : onOverlayClickProp();
}
}, [onClose, closeOnOverlayClick, onOverlayClickProp]);
var _useState = (0, _react.useState)(false),
headerMounted = _useState[0],
setHeaderMounted = _useState[1];
var _useState2 = (0, _react.useState)(false),
bodyMounted = _useState2[0],
setBodyMounted = _useState2[1];
var getContentProps = (0, _react.useCallback)(function (props, ref) {
if (props === void 0) {
props = {};
}
if (ref === void 0) {
ref = null;
}
return _extends({
role: "dialog"
}, props, {
ref: (0, _utils.mergeRefs)(ref, dialogRef),
id: dialogId,
tabIndex: -1,
"aria-modal": true,
"aria-labelledby": headerMounted ? headerId : undefined,
"aria-describedby": bodyMounted ? bodyId : undefined,
onClick: (0, _utils.callAllHandlers)(props.onClick, function (event) {
return event.stopPropagation();
})
});
}, [bodyId, bodyMounted, dialogId, headerId, headerMounted]);
var getOverlayProps = (0, _react.useCallback)(function (props, ref) {
if (props === void 0) {
props = {};
}
if (ref === void 0) {
ref = null;
}
return _extends({}, props, {
ref: (0, _utils.mergeRefs)(ref, overlayRef),
onClick: (0, _utils.callAllHandlers)(props.onClick, onOverlayClick),
onKeyDown: (0, _utils.callAllHandlers)(props.onKeyDown, onKeyDown),
onMouseDown: (0, _utils.callAllHandlers)(props.onMouseDown, onMouseDown)
});
}, [onKeyDown, onMouseDown, onOverlayClick]);
return {
isOpen: isOpen,
onClose: onClose,
headerId: headerId,
bodyId: bodyId,
setBodyMounted: setBodyMounted,
setHeaderMounted: setHeaderMounted,
dialogRef: dialogRef,
overlayRef: overlayRef,
getContentProps: getContentProps,
getOverlayProps: getOverlayProps
};
}
/**
* Modal hook to polyfill `aria-modal`.
*
* It applies `aria-hidden` to elements behind the modal
* to indicate that they're `inert`.
*
* @param ref React ref of the node
* @param shouldHide whether `aria-hidden` should be applied
*/
function useAriaHidden(ref, shouldHide) {
(0, _react.useEffect)(function () {
if (!ref.current) return;
var undo = null;
if (shouldHide && ref.current) {
undo = (0, _ariaHidden.hideOthers)(ref.current);
}
return function () {
if (shouldHide) {
undo == null ? void 0 : undo();
}
};
}, [shouldHide, ref]);
}
//# sourceMappingURL=use-modal.js.map