UNPKG

@wordpress/components

Version:
8 lines (7 loc) 17.2 kB
{ "version": 3, "sources": ["../../src/modal/index.tsx"], "sourcesContent": ["import clsx from 'clsx';\nimport { createPortal, useCallback, useEffect, useRef, useState, forwardRef, useLayoutEffect, useContext } from '@wordpress/element';\nimport { useInstanceId, useFocusReturn, useFocusOnMount, useConstrainedTabbing, useMergeRefs } from '@wordpress/compose';\nimport { __ } from '@wordpress/i18n';\nimport { close } from '@wordpress/icons';\nimport { getScrollContainer } from '@wordpress/dom';\nimport * as ariaHelper from './aria-helper';\nimport Button from '../button';\nimport StyleProvider from '../style-provider';\nimport { withIgnoreIMEEvents } from '../utils/with-ignore-ime-events';\nimport { Spacer } from '../spacer';\nimport { useModalExitAnimation } from './use-modal-exit-animation';\nimport { ModalContext } from './context';\n\n// Used to track body class names applied while modals are open.\nimport { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from \"react/jsx-runtime\";\nconst bodyOpenClasses = new Map();\nfunction UnforwardedModal(props, forwardedRef) {\n const {\n bodyOpenClassName = 'modal-open',\n role = 'dialog',\n title = null,\n focusOnMount = true,\n shouldCloseOnEsc = true,\n shouldCloseOnClickOutside = true,\n isDismissible = true,\n /* Accessibility. */\n aria = {\n labelledby: undefined,\n describedby: undefined\n },\n onRequestClose,\n icon,\n closeButtonLabel,\n children,\n style,\n overlayClassName: overlayClassnameProp,\n className,\n contentLabel,\n onKeyDown,\n isFullScreen = false,\n size,\n headerActions = null,\n __experimentalHideHeader = false\n } = props;\n const ref = useRef(null);\n const instanceId = useInstanceId(Modal);\n const headingId = title ? `components-modal-header-${instanceId}` : aria.labelledby;\n\n // The focus hook does not support 'firstContentElement' but this is a valid\n // value for the Modal's focusOnMount prop. The following code ensures the focus\n // hook will focus the first focusable node within the element to which it is applied.\n // When `firstContentElement` is passed as the value of the focusOnMount prop,\n // the focus hook is applied to the Modal's content element.\n // Otherwise, the focus hook is applied to the Modal's ref. This ensures that the\n // focus hook will focus the first element in the Modal's **content** when\n // `firstContentElement` is passed.\n const focusOnMountRef = useFocusOnMount(focusOnMount === 'firstContentElement' ? 'firstElement' : focusOnMount);\n const constrainedTabbingRef = useConstrainedTabbing();\n const focusReturnRef = useFocusReturn();\n const contentRef = useRef(null);\n const childrenContainerRef = useRef(null);\n const [hasScrolledContent, setHasScrolledContent] = useState(false);\n const [hasScrollableContent, setHasScrollableContent] = useState(false);\n let sizeClass;\n if (isFullScreen || size === 'fill') {\n sizeClass = 'is-full-screen';\n } else if (size) {\n sizeClass = `has-size-${size}`;\n }\n\n // Determines whether the Modal content is scrollable and updates the state.\n const isContentScrollable = useCallback(() => {\n if (!contentRef.current) {\n return;\n }\n const closestScrollContainer = getScrollContainer(contentRef.current);\n if (contentRef.current === closestScrollContainer) {\n setHasScrollableContent(true);\n } else {\n setHasScrollableContent(false);\n }\n }, [contentRef]);\n\n // Accessibly isolates/unisolates the modal.\n useEffect(() => {\n ariaHelper.modalize(ref.current);\n return () => ariaHelper.unmodalize();\n }, []);\n\n // Keeps a fresh ref for the subsequent effect.\n const onRequestCloseRef = useRef(undefined);\n useEffect(() => {\n onRequestCloseRef.current = onRequestClose;\n }, [onRequestClose]);\n\n // The list of `onRequestClose` callbacks of open (non-nested) Modals. Only\n // one should remain open at a time and the list enables closing prior ones.\n const dismissers = useContext(ModalContext);\n // Used for the tracking and dismissing any nested modals.\n const [nestedDismissers] = useState(() => new Set());\n\n // Updates the stack tracking open modals at this level and calls\n // onRequestClose for any prior and/or nested modals as applicable.\n useEffect(() => {\n // add this modal instance to the dismissers set\n dismissers.add(onRequestCloseRef);\n // request that all the other modals close themselves\n for (const dismisser of dismissers) {\n if (dismisser !== onRequestCloseRef) {\n dismisser.current?.();\n }\n }\n return () => {\n // request that all the nested modals close themselves\n for (const dismisser of nestedDismissers) {\n dismisser.current?.();\n }\n // remove this modal instance from the dismissers set\n dismissers.delete(onRequestCloseRef);\n };\n }, [dismissers, nestedDismissers]);\n\n // Adds/removes the value of bodyOpenClassName to body element.\n useEffect(() => {\n const theClass = bodyOpenClassName;\n const oneMore = 1 + (bodyOpenClasses.get(theClass) ?? 0);\n bodyOpenClasses.set(theClass, oneMore);\n document.body.classList.add(bodyOpenClassName);\n return () => {\n const oneLess = bodyOpenClasses.get(theClass) - 1;\n if (oneLess === 0) {\n document.body.classList.remove(theClass);\n bodyOpenClasses.delete(theClass);\n } else {\n bodyOpenClasses.set(theClass, oneLess);\n }\n };\n }, [bodyOpenClassName]);\n const {\n closeModal,\n frameRef,\n frameStyle,\n overlayClassname\n } = useModalExitAnimation();\n\n // Calls the isContentScrollable callback when the Modal children container resizes.\n useLayoutEffect(() => {\n if (!window.ResizeObserver || !childrenContainerRef.current) {\n return;\n }\n const resizeObserver = new ResizeObserver(isContentScrollable);\n resizeObserver.observe(childrenContainerRef.current);\n isContentScrollable();\n return () => {\n resizeObserver.disconnect();\n };\n }, [isContentScrollable, childrenContainerRef]);\n function handleEscapeKeyDown(event) {\n if (shouldCloseOnEsc && (event.code === 'Escape' || event.key === 'Escape') && !event.defaultPrevented) {\n event.preventDefault();\n closeModal().then(() => onRequestClose(event));\n }\n }\n const onContentContainerScroll = useCallback(e => {\n const scrollY = e?.currentTarget?.scrollTop ?? -1;\n if (!hasScrolledContent && scrollY > 0) {\n setHasScrolledContent(true);\n } else if (hasScrolledContent && scrollY <= 0) {\n setHasScrolledContent(false);\n }\n }, [hasScrolledContent]);\n let pressTarget = null;\n const overlayPressHandlers = {\n onPointerDown: event => {\n if (event.target === event.currentTarget) {\n pressTarget = event.target;\n // Avoids focus changing so that focus return works as expected.\n event.preventDefault();\n }\n },\n // Closes the modal with two exceptions. 1. Opening the context menu on\n // the overlay. 2. Pressing on the overlay then dragging the pointer\n // over the modal and releasing. Due to the modal being a child of the\n // overlay, such a gesture is a `click` on the overlay and cannot be\n // excepted by a `click` handler. Thus the tactic of handling\n // `pointerup` and comparing its target to that of the `pointerdown`.\n onPointerUp: ({\n target,\n button\n }) => {\n const isSameTarget = target === pressTarget;\n pressTarget = null;\n if (button === 0 && isSameTarget) {\n closeModal().then(() => onRequestClose());\n }\n }\n };\n const modal =\n /*#__PURE__*/\n // eslint-disable-next-line jsx-a11y/no-static-element-interactions\n _jsx(\"div\", {\n ref: useMergeRefs([ref, forwardedRef]),\n className: clsx('components-modal__screen-overlay', overlayClassname, overlayClassnameProp),\n onKeyDown: withIgnoreIMEEvents(handleEscapeKeyDown),\n ...(shouldCloseOnClickOutside ? overlayPressHandlers : {}),\n children: /*#__PURE__*/_jsx(StyleProvider, {\n document: document,\n children: /*#__PURE__*/_jsx(\"div\", {\n className: clsx('components-modal__frame', sizeClass, className),\n style: {\n ...frameStyle,\n ...style\n },\n ref: useMergeRefs([frameRef, constrainedTabbingRef, focusReturnRef, focusOnMount !== 'firstContentElement' ? focusOnMountRef : null]),\n role: role,\n \"aria-label\": contentLabel,\n \"aria-labelledby\": contentLabel ? undefined : headingId,\n \"aria-describedby\": aria.describedby,\n tabIndex: -1,\n onKeyDown: onKeyDown,\n children: /*#__PURE__*/_jsxs(\"div\", {\n className: clsx('components-modal__content', {\n 'hide-header': __experimentalHideHeader,\n 'is-scrollable': hasScrollableContent,\n 'has-scrolled-content': hasScrolledContent\n }),\n role: \"document\",\n onScroll: onContentContainerScroll,\n ref: contentRef,\n \"aria-label\": hasScrollableContent ? __('Scrollable section') : undefined,\n tabIndex: hasScrollableContent ? 0 : undefined,\n children: [!__experimentalHideHeader && /*#__PURE__*/_jsxs(\"div\", {\n className: \"components-modal__header\",\n children: [/*#__PURE__*/_jsxs(\"div\", {\n className: \"components-modal__header-heading-container\",\n children: [icon && /*#__PURE__*/_jsx(\"span\", {\n className: \"components-modal__icon-container\",\n \"aria-hidden\": true,\n children: icon\n }), title && /*#__PURE__*/_jsx(\"h1\", {\n id: headingId,\n className: \"components-modal__header-heading\",\n children: title\n })]\n }), headerActions, isDismissible && /*#__PURE__*/_jsxs(_Fragment, {\n children: [/*#__PURE__*/_jsx(Spacer, {\n marginBottom: 0,\n marginLeft: 2\n }), /*#__PURE__*/_jsx(Button, {\n size: \"compact\",\n onClick: event => closeModal().then(() => onRequestClose(event)),\n icon: close,\n label: closeButtonLabel || __('Close')\n })]\n })]\n }), /*#__PURE__*/_jsx(\"div\", {\n ref: useMergeRefs([childrenContainerRef, focusOnMount === 'firstContentElement' ? focusOnMountRef : null]),\n className: \"components-modal__children-container\",\n children: children\n })]\n })\n })\n })\n });\n return createPortal(/*#__PURE__*/_jsx(ModalContext.Provider, {\n value: nestedDismissers,\n children: modal\n }), document.body);\n}\n\n/**\n * Modals give users information and choices related to a task they\u2019re trying to\n * accomplish. They can contain critical information, require decisions, or\n * involve multiple tasks.\n *\n * ```jsx\n * import { Button, Modal } from '@wordpress/components';\n * import { useState } from '@wordpress/element';\n *\n * const MyModal = () => {\n * const [ isOpen, setOpen ] = useState( false );\n * const openModal = () => setOpen( true );\n * const closeModal = () => setOpen( false );\n *\n * return (\n * <>\n * <Button variant=\"secondary\" onClick={ openModal }>\n * Open Modal\n * </Button>\n * { isOpen && (\n * <Modal title=\"This is my modal\" onRequestClose={ closeModal }>\n * <Button variant=\"secondary\" onClick={ closeModal }>\n * My custom close button\n * </Button>\n * </Modal>\n * ) }\n * </>\n * );\n * };\n * ```\n */\nexport const Modal = forwardRef(UnforwardedModal);\nModal.displayName = 'Modal';\nexport default Modal;"], "mappings": "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", "names": ["_jsx", "clsx", "StyleProvider", "_jsxs", "_Fragment", "Button"] }