@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
370 lines (369 loc) • 15.3 kB
JavaScript
;
"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