UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

370 lines (369 loc) 15.3 kB
"use strict"; "use client"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = ModalContent; var _push = _interopRequireDefault(require("core-js-pure/stable/instance/push.js")); var _react = _interopRequireWildcard(require("react")); var _useMountEffect = _interopRequireDefault(require("../../shared/helpers/useMountEffect.js")); var _clsx = _interopRequireDefault(require("clsx")); var _bodyScrollLock = require("./bodyScrollLock.js"); var _useId = _interopRequireDefault(require("../../shared/helpers/useId.js")); var _componentHelper = require("../../shared/component-helper.js"); var _ModalContext = _interopRequireDefault(require("./ModalContext.js")); var _helpers = require("../../shared/helpers.js"); var _helpers2 = require("./helpers.js"); var _Theme = require("../../shared/Theme.js"); var _index = require("../../shared/index.js"); var _jsxRuntime = require("react/jsx-runtime"); function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function ModalContent(props) { const { hide, title, labelledBy, id: idProp, closeTitle = 'Lukk', dialogTitle = 'Vindu', hideCloseButton = false, closeButtonAttributes, noAnimation = false, noAnimationOnMobile = false, fullscreen = 'auto', containerPlacement = 'right', verticalAlignment = 'center', close, contentClass, overlayClass, contentId: contentIdProp, children, dialogRole = null, focusSelector = null, animationDuration = null, preventOverlayClose, open, contentRef: contentRefProp, scrollRef: scrollRefProp, modalContentCloseRef, bypassInvalidationSelectors, ...rest } = props; const context = (0, _react.useContext)(_index.Context); const internalContentRef = (0, _react.useRef)(null); const contentRef = contentRefProp || internalContentRef; const internalScrollRef = (0, _react.useRef)(null); const scrollRef = scrollRefProp || internalScrollRef; const overlayClickRef = (0, _react.useRef)(null); const lockTimeoutRef = (0, _react.useRef)(null); const focusTimeoutRef = (0, _react.useRef)(null); const androidFocusTimeoutRef = (0, _react.useRef)(null); const iiRef = (0, _react.useRef)(null); const mountedRef = (0, _react.useRef)(0); const lastFocusTimeRef = (0, _react.useRef)(0); const triggeredByRef = (0, _react.useRef)(undefined); const triggeredByEventRef = (0, _react.useRef)(undefined); const selfRef = (0, _react.useRef)(null); if (!selfRef.current) { selfRef.current = { _id: idProp, _scrollRef: scrollRef, _contentRef: contentRef, _iiLocal: undefined }; } selfRef.current._id = idProp; selfRef.current._scrollRef = scrollRef; selfRef.current._contentRef = contentRef; const setModalContentState = (0, _react.useCallback)((event, { triggeredBy }) => { triggeredByRef.current = triggeredBy; triggeredByEventRef.current = event; }, []); (0, _react.useEffect)(() => { if (modalContentCloseRef) { const mutableRef = modalContentCloseRef; mutableRef.current = setModalContentState; } }, [modalContentCloseRef, setModalContentState]); const usedContentId = (0, _useId.default)(contentIdProp); const wasOpenedManually = (0, _react.useCallback)(() => { if (triggeredByRef.current) { return true; } if (typeof open === 'boolean') { if (process.env.NODE_ENV !== 'test') { const delay = Date.now() - mountedRef.current; return delay > 30; } return true; } return false; }, [open]); const removeScrollPossibility = (0, _react.useCallback)(() => { if (scrollRef.current) { (0, _bodyScrollLock.disableBodyScroll)(scrollRef.current); } }, [scrollRef]); const revertScrollPossibility = (0, _react.useCallback)(() => { (0, _bodyScrollLock.enableBodyScroll)(scrollRef.current); (0, _bodyScrollLock.clearAllBodyScrollLocks)(); }, [scrollRef]); const setFocus = (0, _react.useCallback)(() => { const elem = contentRef.current; const timeoutDuration = typeof animationDuration === 'string' ? parseFloat(animationDuration) : animationDuration; if (elem) { if (lastFocusTimeRef.current && Date.now() - lastFocusTimeRef.current > 2000) { return; } lastFocusTimeRef.current = Date.now(); clearTimeout(focusTimeoutRef.current); focusTimeoutRef.current = setTimeout(() => { try { let focusElement = elem; const headerElem = elem.querySelector('.dnb-drawer__header, .dnb-dialog__header'); const firstHeading = (headerElem === null || headerElem === void 0 ? void 0 : headerElem.querySelector('h1, h2, h3')) || elem.querySelector('h1, h2, h3'); if (firstHeading) { if (firstHeading.tagName !== 'H1') { (0, _componentHelper.warn)('A Dialog or Drawer needs a h1 as its first element!'); } firstHeading.setAttribute('tabIndex', '-1'); firstHeading.classList.add('dnb-no-focus'); focusElement = firstHeading; } else { const focusHelper = elem.querySelector('.dnb-modal__close-button, .dnb-modal__focus-helper'); focusElement = focusHelper; } if (typeof focusSelector === 'string') { focusElement = elem.querySelector(focusSelector); } if (focusElement !== document.activeElement) { var _focusElement; (_focusElement = focusElement) === null || _focusElement === void 0 || _focusElement.focus({ preventScroll: true }); } } catch (e) { (0, _componentHelper.warn)(e); } }, noAnimation ? 0 : timeoutDuration || 0); } }, [contentRef, animationDuration, focusSelector, noAnimation]); const androidFocusHelperRef = (0, _react.useRef)(null); androidFocusHelperRef.current = () => { const timeoutDuration = typeof animationDuration === 'string' ? parseFloat(animationDuration) : animationDuration; clearTimeout(androidFocusTimeoutRef.current); androidFocusTimeoutRef.current = setTimeout(() => { try { const elem = contentRef.current; if ((elem === null || elem === void 0 ? void 0 : elem.tagName) === 'INPUT' || (elem === null || elem === void 0 ? void 0 : elem.tagName) === 'TEXTAREA') { elem.scrollIntoView(); } } catch (e) {} }, timeoutDuration / 2); }; const stableAndroidFocusHelper = (0, _react.useCallback)(() => { var _androidFocusHelperRe; (_androidFocusHelperRe = androidFocusHelperRef.current) === null || _androidFocusHelperRe === void 0 || _androidFocusHelperRe.call(androidFocusHelperRef); }, []); const closeModalContent = (0, _react.useCallback)((event, { triggeredBy, ...params }) => { close(event, { triggeredBy, ...params }); }, [close]); const onCloseClickHandler = (0, _react.useCallback)(event => { closeModalContent(event, { triggeredBy: 'button' }); }, [closeModalContent]); const onContentMouseDownHandler = (0, _react.useCallback)(event => { overlayClickRef.current = event.target === event.currentTarget ? event.target : null; }, []); const onContentClickHandler = (0, _react.useCallback)(event => { if (overlayClickRef.current !== event.target) { return; } overlayClickRef.current = null; if (!preventOverlayClose) { closeModalContent(event, { triggeredBy: 'overlay', ifIsLatest: false }); } }, [preventOverlayClose, closeModalContent]); const onKeyDownHandlerRef = (0, _react.useRef)(null); onKeyDownHandlerRef.current = event => { if (event.key === 'Escape') { const mostCurrent = (0, _helpers2.getModalRoot)(-1); if (mostCurrent === selfRef.current) { event.preventDefault(); closeModalContent(event, { triggeredBy: 'keyboard' }); } } }; const stableOnKeyDownHandler = (0, _react.useCallback)(event => { var _onKeyDownHandlerRef$; (_onKeyDownHandlerRef$ = onKeyDownHandlerRef.current) === null || _onKeyDownHandlerRef$ === void 0 || _onKeyDownHandlerRef$.call(onKeyDownHandlerRef, event); }, []); const preventClick = (0, _react.useCallback)(event => { if (event) { event.stopPropagation(); } }, []); const lockBody = (0, _react.useCallback)(() => { const modalRoots = (0, _helpers2.getListOfModalRoots)(); const firstLevel = modalRoots[0]; if (firstLevel === selfRef.current) { const contentElement = contentRef.current || document.querySelector(`#${usedContentId}`); const parentElements = getParents(contentElement); const ii = new _componentHelper.InteractionInvalidation(); ii.setBypassElements(parentElements); ii.setBypassSelector(['#eufemia-portal-root', '#eufemia-portal-root *', `#${usedContentId}`, `#${usedContentId} *`, '.dnb-modal--bypass-invalidation', '.dnb-modal--bypass-invalidation-deep *', ...(bypassInvalidationSelectors || [])].filter(Boolean)); ii.activate(); iiRef.current = ii; } else { modalRoots.forEach(modal => { if (modal !== selfRef.current && typeof modal._iiLocal === 'undefined' && typeof modal._scrollRef !== 'undefined') { modal._iiLocal = new _componentHelper.InteractionInvalidation(); modal._iiLocal.activate(modal._scrollRef.current); } }); } if (typeof document !== 'undefined') { document.addEventListener('keydown', stableOnKeyDownHandler); } }, [contentRef, usedContentId, bypassInvalidationSelectors, stableOnKeyDownHandler]); const removeLocks = (0, _react.useCallback)(() => { const modalRoots = (0, _helpers2.getListOfModalRoots)(); const firstLevel = modalRoots[0]; (0, _helpers2.removeFromIndex)(selfRef.current); if (firstLevel === selfRef.current) { var _iiRef$current; (_iiRef$current = iiRef.current) === null || _iiRef$current === void 0 || _iiRef$current.revert(); revertScrollPossibility(); } else { try { const modal = modalRoots[modalRoots.length - 2]; if (modal !== selfRef.current && modal._iiLocal) { modal._iiLocal.revert(); delete modal._iiLocal; } } catch (e) { (0, _componentHelper.warn)(e); } } window.removeEventListener('resize', stableAndroidFocusHelper); clearTimeout(androidFocusTimeoutRef.current); if (wasOpenedManually()) { (0, _componentHelper.dispatchCustomElementEvent)(props, 'onClose', { id: idProp, event: triggeredByEventRef.current, triggeredBy: triggeredByRef.current || 'unmount' }); } if (typeof document !== 'undefined') { document.removeEventListener('keydown', stableOnKeyDownHandler); } }, [revertScrollPossibility, stableAndroidFocusHelper, wasOpenedManually, props, idProp, stableOnKeyDownHandler]); const removeLocksRef = (0, _react.useRef)(removeLocks); removeLocksRef.current = removeLocks; (0, _useMountEffect.default)(() => { const timeoutDuration = typeof animationDuration === 'string' ? parseFloat(animationDuration) : animationDuration; (0, _helpers2.addToIndex)(selfRef.current); removeScrollPossibility(); setFocus(); if (typeof window !== 'undefined' && (0, _helpers.isAndroid)()) { window.addEventListener('resize', stableAndroidFocusHelper); } (0, _componentHelper.dispatchCustomElementEvent)(props, 'onOpen', { id: idProp }); if (noAnimation || process.env.NODE_ENV === 'test') { lockBody(); } else { lockTimeoutRef.current = setTimeout(lockBody, timeoutDuration * 1.2); } mountedRef.current = Date.now(); return () => { clearTimeout(focusTimeoutRef.current); clearTimeout(lockTimeoutRef.current); removeLocksRef.current(); mountedRef.current = 0; }; }); const prevChildrenRef = (0, _react.useRef)(children); (0, _react.useEffect)(() => { if (prevChildrenRef.current !== children) { prevChildrenRef.current = children; setFocus(); } }, [children, setFocus]); const useDialogRole = !(_helpers.IS_MAC || _helpers.IS_SAFARI || _helpers.IS_IOS); let role = dialogRole || 'dialog'; if (!useDialogRole && role === 'dialog') { role = 'region'; } const contentParams = { role, 'aria-modal': useDialogRole ? true : undefined, 'aria-labelledby': (0, _componentHelper.combineLabelledBy)(props, title ? usedContentId + '-title' : null, labelledBy), 'aria-describedby': (0, _componentHelper.combineDescribedBy)(props, usedContentId + '-content'), 'aria-label': !title && !labelledBy ? dialogTitle : undefined, className: (0, _clsx.default)(`dnb-modal__content dnb-modal__vertical-alignment--${verticalAlignment}`, fullscreen === true ? 'dnb-modal__content--fullscreen' : fullscreen === 'auto' && 'dnb-modal__content--auto-fullscreen', (0, _Theme.getThemeClasses)(context === null || context === void 0 ? void 0 : context.theme), contentClass, containerPlacement && `dnb-modal__content--${containerPlacement || 'right'}`), onMouseDown: onContentMouseDownHandler, onClick: onContentClickHandler }; const content = typeof children === 'function' ? children({ ...rest, close }) : children; const { colorScheme } = (context === null || context === void 0 ? void 0 : context.theme) || {}; return (0, _jsxRuntime.jsxs)(_ModalContext.default, { value: { id: idProp, title, hideCloseButton, closeButtonAttributes, closeTitle, hide, onCloseClickHandler, preventClick, onKeyDownHandler: stableOnKeyDownHandler, contentRef, scrollRef, contentId: usedContentId, close }, children: [(0, _jsxRuntime.jsx)("div", { id: usedContentId, ...contentParams, children: content }), (0, _jsxRuntime.jsx)("span", { className: (0, _clsx.default)('dnb-modal__overlay', overlayClass, colorScheme && `dnb-modal__color-scheme--${colorScheme}`, hide && 'dnb-modal__overlay--hide', noAnimation && 'dnb-modal__overlay--no-animation', noAnimationOnMobile && 'dnb-modal__overlay--no-animation-on-mobile'), "aria-hidden": true })] }); } function getParents(elem) { if (!elem || typeof document === 'undefined') { return []; } const parents = []; let current = elem.parentElement; while (current && current !== document.body) { (0, _push.default)(parents).call(parents, current); current = current.parentElement; } return parents; } //# sourceMappingURL=ModalContent.js.map