UNPKG

@bianic-ui/modal

Version:

An accessible dialog (modal) component for React & Bianic UI

159 lines (143 loc) 4.75 kB
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); } import { useIds } from "@bianic-ui/hooks"; import { callAllHandlers, mergeRefs } from "@bianic-ui/utils"; import { hideOthers } from "aria-hidden"; import { useCallback, useEffect, useRef, useState } from "react"; import { manager, useModalManager } from "./modal-manager"; /** * Modal hook that manages all the logic for the modal dialog widget * and returns prop getters, state and actions. * * @param props */ export function useModal(props) { var { isOpen, onClose, id, closeOnOverlayClick = true, closeOnEsc = true, useInert = true, onOverlayClick: onOverlayClickProp, onEsc } = props; var dialogRef = useRef(null); var overlayRef = useRef(null); var [dialogId, headerId, bodyId] = useIds(id, "bianic-modal", "bianic-modal--header", "bianic-modal--body"); /** * 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 */ useModalManager(dialogRef, isOpen); var mouseDownTarget = useRef(null); var onMouseDown = useCallback(event => { mouseDownTarget.current = event.target; }, []); var onKeyDown = useCallback(event => { if (event.key === "Escape") { event.stopPropagation(); if (closeOnEsc) { onClose == null ? void 0 : onClose(); } onEsc == null ? void 0 : onEsc(); } }, [closeOnEsc, onClose, onEsc]); var onOverlayClick = useCallback(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 (manager.isTopModal(dialogRef)) { if (closeOnOverlayClick) { onClose == null ? void 0 : onClose(); } onOverlayClickProp == null ? void 0 : onOverlayClickProp(); } }, [onClose, closeOnOverlayClick, onOverlayClickProp]); var [headerMounted, setHeaderMounted] = useState(false); var [bodyMounted, setBodyMounted] = useState(false); var getContentProps = useCallback(function (props, ref) { if (props === void 0) { props = {}; } if (ref === void 0) { ref = null; } return _extends({ role: "dialog" }, props, { ref: mergeRefs(ref, dialogRef), id: dialogId, tabIndex: -1, "aria-modal": true, "aria-labelledby": headerMounted ? headerId : undefined, "aria-describedby": bodyMounted ? bodyId : undefined, onClick: callAllHandlers(props.onClick, event => event.stopPropagation()) }); }, [bodyId, bodyMounted, dialogId, headerId, headerMounted]); var getOverlayProps = useCallback(function (props, ref) { if (props === void 0) { props = {}; } if (ref === void 0) { ref = null; } return _extends({}, props, { ref: mergeRefs(ref, overlayRef), onClick: callAllHandlers(props.onClick, onOverlayClick), onKeyDown: callAllHandlers(props.onKeyDown, onKeyDown), onMouseDown: callAllHandlers(props.onMouseDown, onMouseDown) }); }, [onKeyDown, onMouseDown, onOverlayClick]); return { isOpen, onClose, headerId, bodyId, setBodyMounted, setHeaderMounted, dialogRef, overlayRef, getContentProps, 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 */ export function useAriaHidden(ref, shouldHide) { useEffect(() => { if (!ref.current) return; var undo = null; if (shouldHide && ref.current) { undo = hideOthers(ref.current); } return () => { if (shouldHide) { undo == null ? void 0 : undo(); } }; }, [shouldHide, ref]); } //# sourceMappingURL=use-modal.js.map