UNPKG

6.24 kBJavaScriptView Raw
1'use client';
2
3import _extends from "@babel/runtime/helpers/esm/extends";
4import * as React from 'react';
5import { unstable_ownerDocument as ownerDocument, unstable_useForkRef as useForkRef, unstable_useEventCallback as useEventCallback, unstable_createChainedFunction as createChainedFunction } from '@mui/utils';
6import { extractEventHandlers } from '../utils';
7import { ModalManager, ariaHidden } from './ModalManager';
8function getContainer(container) {
9 return typeof container === 'function' ? container() : container;
10}
11function getHasTransition(children) {
12 return children ? children.props.hasOwnProperty('in') : false;
13}
14
15// A modal manager used to track and manage the state of open Modals.
16// Modals don't open on the server so this won't conflict with concurrent requests.
17const defaultManager = new ModalManager();
18/**
19 *
20 * Demos:
21 *
22 * - [Modal](https://mui.com/base-ui/react-modal/#hook)
23 *
24 * API:
25 *
26 * - [useModal API](https://mui.com/base-ui/react-modal/hooks-api/#use-modal)
27 */
28export function useModal(parameters) {
29 const {
30 container,
31 disableEscapeKeyDown = false,
32 disableScrollLock = false,
33 // @ts-ignore internal logic - Base UI supports the manager as a prop too
34 manager = defaultManager,
35 closeAfterTransition = false,
36 onTransitionEnter,
37 onTransitionExited,
38 children,
39 onClose,
40 open,
41 rootRef
42 } = parameters;
43
44 // @ts-ignore internal logic
45 const modal = React.useRef({});
46 const mountNodeRef = React.useRef(null);
47 const modalRef = React.useRef(null);
48 const handleRef = useForkRef(modalRef, rootRef);
49 const [exited, setExited] = React.useState(!open);
50 const hasTransition = getHasTransition(children);
51 let ariaHiddenProp = true;
52 if (parameters['aria-hidden'] === 'false' || parameters['aria-hidden'] === false) {
53 ariaHiddenProp = false;
54 }
55 const getDoc = () => ownerDocument(mountNodeRef.current);
56 const getModal = () => {
57 modal.current.modalRef = modalRef.current;
58 modal.current.mount = mountNodeRef.current;
59 return modal.current;
60 };
61 const handleMounted = () => {
62 manager.mount(getModal(), {
63 disableScrollLock
64 });
65
66 // Fix a bug on Chrome where the scroll isn't initially 0.
67 if (modalRef.current) {
68 modalRef.current.scrollTop = 0;
69 }
70 };
71 const handleOpen = useEventCallback(() => {
72 const resolvedContainer = getContainer(container) || getDoc().body;
73 manager.add(getModal(), resolvedContainer);
74
75 // The element was already mounted.
76 if (modalRef.current) {
77 handleMounted();
78 }
79 });
80 const isTopModal = React.useCallback(() => manager.isTopModal(getModal()), [manager]);
81 const handlePortalRef = useEventCallback(node => {
82 mountNodeRef.current = node;
83 if (!node) {
84 return;
85 }
86 if (open && isTopModal()) {
87 handleMounted();
88 } else if (modalRef.current) {
89 ariaHidden(modalRef.current, ariaHiddenProp);
90 }
91 });
92 const handleClose = React.useCallback(() => {
93 manager.remove(getModal(), ariaHiddenProp);
94 }, [ariaHiddenProp, manager]);
95 React.useEffect(() => {
96 return () => {
97 handleClose();
98 };
99 }, [handleClose]);
100 React.useEffect(() => {
101 if (open) {
102 handleOpen();
103 } else if (!hasTransition || !closeAfterTransition) {
104 handleClose();
105 }
106 }, [open, handleClose, hasTransition, closeAfterTransition, handleOpen]);
107 const createHandleKeyDown = otherHandlers => event => {
108 var _otherHandlers$onKeyD;
109 (_otherHandlers$onKeyD = otherHandlers.onKeyDown) == null || _otherHandlers$onKeyD.call(otherHandlers, event);
110
111 // The handler doesn't take event.defaultPrevented into account:
112 //
113 // event.preventDefault() is meant to stop default behaviors like
114 // clicking a checkbox to check it, hitting a button to submit a form,
115 // and hitting left arrow to move the cursor in a text input etc.
116 // Only special HTML elements have these default behaviors.
117 if (event.key !== 'Escape' || event.which === 229 ||
118 // Wait until IME is settled.
119 !isTopModal()) {
120 return;
121 }
122 if (!disableEscapeKeyDown) {
123 // Swallow the event, in case someone is listening for the escape key on the body.
124 event.stopPropagation();
125 if (onClose) {
126 onClose(event, 'escapeKeyDown');
127 }
128 }
129 };
130 const createHandleBackdropClick = otherHandlers => event => {
131 var _otherHandlers$onClic;
132 (_otherHandlers$onClic = otherHandlers.onClick) == null || _otherHandlers$onClic.call(otherHandlers, event);
133 if (event.target !== event.currentTarget) {
134 return;
135 }
136 if (onClose) {
137 onClose(event, 'backdropClick');
138 }
139 };
140 const getRootProps = (otherHandlers = {}) => {
141 const propsEventHandlers = extractEventHandlers(parameters);
142
143 // The custom event handlers shouldn't be spread on the root element
144 delete propsEventHandlers.onTransitionEnter;
145 delete propsEventHandlers.onTransitionExited;
146 const externalEventHandlers = _extends({}, propsEventHandlers, otherHandlers);
147 return _extends({
148 role: 'presentation'
149 }, externalEventHandlers, {
150 onKeyDown: createHandleKeyDown(externalEventHandlers),
151 ref: handleRef
152 });
153 };
154 const getBackdropProps = (otherHandlers = {}) => {
155 const externalEventHandlers = otherHandlers;
156 return _extends({
157 'aria-hidden': true
158 }, externalEventHandlers, {
159 onClick: createHandleBackdropClick(externalEventHandlers),
160 open
161 });
162 };
163 const getTransitionProps = () => {
164 const handleEnter = () => {
165 setExited(false);
166 if (onTransitionEnter) {
167 onTransitionEnter();
168 }
169 };
170 const handleExited = () => {
171 setExited(true);
172 if (onTransitionExited) {
173 onTransitionExited();
174 }
175 if (closeAfterTransition) {
176 handleClose();
177 }
178 };
179 return {
180 onEnter: createChainedFunction(handleEnter, children == null ? void 0 : children.props.onEnter),
181 onExited: createChainedFunction(handleExited, children == null ? void 0 : children.props.onExited)
182 };
183 };
184 return {
185 getRootProps,
186 getBackdropProps,
187 getTransitionProps,
188 rootRef: handleRef,
189 portalRef: handlePortalRef,
190 isTopModal,
191 exited,
192 hasTransition
193 };
194}
\No newline at end of file