UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

411 lines (410 loc) 12.5 kB
"use client"; import _extends from "@babel/runtime/helpers/esm/extends"; import React from 'react'; import classnames from 'classnames'; import { SuffixContext } from "../../shared/helpers/Suffix.js"; import Context from "../../shared/Context.js"; import { warn, isTrue, makeUniqueId, extendPropsWithContextInClassComponent, processChildren, dispatchCustomElementEvent } from "../../shared/component-helper.js"; import { createSpacingClasses } from "../space/SpacingHelper.js"; import HelpButtonInstance from "../help-button/HelpButtonInstance.js"; import { getListOfModalRoots, getModalRoot } from "./helpers.js"; import ModalInner from "./parts/ModalInner.js"; import ModalHeader from "./parts/ModalHeader.js"; import ModalHeaderBar from "./parts/ModalHeaderBar.js"; import CloseButton from "./parts/CloseButton.js"; import ModalRoot from "./ModalRoot.js"; import { ParagraphContext } from "../../elements/typography/P.js"; import { classWithCamelCaseProps } from "../../shared/helpers/withCamelCaseProps.js"; export const ANIMATION_DURATION = 300; class Modal extends React.PureComponent { static contextType = Context; static Bar = ModalHeaderBar; static Header = ModalHeader; static Content = ModalInner; static getContent(props) { if (typeof props.modal_content === 'string') { return props.modal_content; } else if (typeof props.modal_content === 'function') { return props.modal_content(props); } return processChildren(props); } state = { hide: false, modalActive: false, preventAutoFocus: true, animation_duration: ANIMATION_DURATION, no_animation: false }; static defaultProps = { id: null, focus_selector: null, labelled_by: null, title: null, disabled: null, spacing: true, open_delay: null, content_id: null, dialog_title: 'Vindu', close_title: 'Lukk', hide_close_button: false, close_button_attributes: null, prevent_close: false, prevent_core_style: false, animation_duration: ANIMATION_DURATION, no_animation: false, no_animation_on_mobile: false, fullscreen: 'auto', min_width: null, max_width: null, align_content: 'left', container_placement: null, vertical_alignment: null, open_state: null, direct_dom_return: false, root_id: 'root', omit_trigger_button: false, className: null, children: null, on_open: null, on_close: null, on_close_prevent: null, open_modal: null, close_modal: null, trigger: null, trigger_attributes: null, overlay_class: null, content_class: null, modal_content: null, header_content: null, bar_content: null }; static getDerivedStateFromProps(props, state) { if (typeof window !== 'undefined' && window['IS_TEST']) { state.animation_duration = 0; state.no_animation = true; } else { state.animation_duration = props.animation_duration; state.no_animation = props.no_animation; } if (props.open_state !== state._open_state) { switch (props.open_state) { case 'opened': case true: state.hide = false; if (isTrue(state.no_animation)) { state.modalActive = true; } break; case 'closed': case false: state.hide = true; if (isTrue(state.no_animation)) { state.modalActive = false; } break; } } state._open_state = props.open_state; return state; } constructor(props) { super(props); this._id = props.id || makeUniqueId('modal-'); this._triggerRef = React.createRef(); this.modalContentCloseRef = React.createRef(); this._onUnmount = []; } componentDidMount() { this.openBasedOnStateUpdate(); } componentWillUnmount() { clearTimeout(this._openTimeout); clearTimeout(this._closeTimeout); this.removeActiveState(); this._onUnmount.forEach(fn => { if (typeof fn === 'function') { fn(); } }); } componentDidUpdate(prevProps) { if (this.isInTransition) { return; } if (prevProps !== this.props) { this.openBasedOnStateUpdate(); } } openBasedOnStateUpdate() { const { hide } = this.state; const { open_state } = this.props; if (!this.activeElement && typeof document !== 'undefined') { this.activeElement = document.activeElement; } if (!hide && (open_state === 'opened' || open_state === true)) { this.toggleOpenClose(null, true); } else if (hide && (open_state === 'closed' || open_state === false)) { this.toggleOpenClose(null, false); } } toggleOpenClose = (event = null, showModal = null) => { if (event && event.preventDefault) { event.preventDefault(); } const toggleNow = () => { const { animation_duration = ANIMATION_DURATION, no_animation = false } = this.state; const timeoutDuration = typeof animation_duration === 'string' ? parseFloat(animation_duration) : animation_duration; const modalActive = typeof showModal === 'boolean' ? showModal : !this.state.modalActive; this.isInTransition = true; const doItNow = () => { this.setState({ hide: false, modalActive }, () => { this.isInTransition = false; this.handleSideEffects(); }); }; if (modalActive === false && !isTrue(no_animation)) { this.setState({ hide: true }); this._closeTimeout = setTimeout(doItNow, timeoutDuration); } else { doItNow(); } }; const waitBeforeOpen = () => { const { open_delay } = this.props; const { no_animation } = this.state; const delay = typeof open_delay === 'string' ? parseFloat(open_delay) : open_delay; if (delay > 0 && !isTrue(no_animation)) { this._openTimeout = setTimeout(toggleNow, delay); } else { toggleNow(); } }; clearTimeout(this._closeTimeout); clearTimeout(this._openTimeout); const { open_modal } = this.props; if (typeof open_modal === 'function') { const fn = open_modal(waitBeforeOpen, this); if (fn) { this._onUnmount.push(fn); } } else { waitBeforeOpen(); } }; handleSideEffects = () => { const { modalActive, preventAutoFocus, animation_duration } = this.state; const { close_modal, open_state } = this.props; if (modalActive) { if (typeof close_modal === 'function') { const fn = close_modal(() => { this.toggleOpenClose(null, false); }, this); if (fn) { this._onUnmount.push(fn); } } this.setActiveState(this._id); } else if (modalActive === false && !preventAutoFocus) { const focus = elem => { elem.setAttribute('data-autofocus', 'true'); elem.focus({ preventScroll: true }); return new Promise(resolve => { setTimeout(() => { elem?.removeAttribute('data-autofocus'); resolve(); }, parseFloat(String(animation_duration)) / 3); }); }; if (this._triggerRef?.current) { focus(this._triggerRef.current); } if ((open_state === 'opened' || open_state === true) && this.activeElement instanceof HTMLElement) { try { focus(this.activeElement).then(() => { this.activeElement = null; }); } catch (e) {} } this.removeActiveState(); } if (preventAutoFocus) { this.setState({ preventAutoFocus: false }); } }; open = e => { this.toggleOpenClose(e, true); }; close = (event, { ifIsLatest, triggeredBy = 'handler' } = { ifIsLatest: true }) => { this.modalContentCloseRef.current?.(event, { triggeredBy }); const { prevent_close = false } = this.props; if (isTrue(prevent_close)) { const id = this._id; dispatchCustomElementEvent(this, 'on_close_prevent', { id, event, triggeredBy, close: e => { this.toggleOpenClose(e, false); } }); } else { if (ifIsLatest) { const list = getListOfModalRoots(); if (list.length > 1) { const last = getModalRoot(-1); if (last !== this) { return; } } } this.toggleOpenClose(event, false); } }; removeActiveState() { const last = getModalRoot(-1); if (last?._id && last._id !== this._id) { return this.setActiveState(last._id); } try { document.documentElement.removeAttribute('data-dnb-modal-active'); } catch (e) { warn('Modal: Error on remove "data-dnb-modal-active"', e); } } setActiveState(modalId) { if (!modalId) { warn('Modal: A valid modalId is required'); } if (typeof document !== 'undefined') { try { document.documentElement.setAttribute('data-dnb-modal-active', modalId); } catch (e) { warn('Modal: Error on set "data-dnb-modal-active"', e); } } } render() { const visualTestsPropsOverride = typeof window !== 'undefined' && window['IS_TEST'] ? { animation_duration: 0, no_animation: true } : {}; const props = extendPropsWithContextInClassComponent(this.props, Modal.defaultProps, this.context.getTranslation(this.props).Modal, this.context.Modal, visualTestsPropsOverride); const { root_id = 'root', content_id = null, disabled = null, labelled_by = null, focus_selector = null, header_content = null, bar_content = null, bypass_invalidation_selectors = null, vertical_alignment = 'center', id, open_delay, omit_trigger_button = false, trigger = null, trigger_attributes = null, ...rest } = props; const { hide, modalActive } = this.state; const modal_content = Modal.getContent(typeof this.props.children === 'function' ? Object.freeze({ ...this.props, close: this.close }) : this.props); const render = suffixProps => { const triggerAttributes = { hidden: false, variant: 'secondary', icon_position: 'left', ...trigger_attributes }; if (isTrue(disabled)) { triggerAttributes.disabled = true; } if (triggerAttributes.id) { this._id = triggerAttributes.id; } let fallbackTitle; if (triggerAttributes.title) { fallbackTitle = triggerAttributes.title; } else if (suffixProps) { fallbackTitle = this.context.translation.HelpButton.title; } const headerTitle = rest.title || fallbackTitle; const TriggerButton = trigger ? trigger : HelpButtonInstance; const title = !triggerAttributes.text && headerTitle ? headerTitle || fallbackTitle : null; return React.createElement(React.Fragment, null, TriggerButton && !isTrue(omit_trigger_button) && React.createElement(TriggerButton, _extends({}, triggerAttributes, { id: this._id, title: title, onClick: this.toggleOpenClose, innerRef: this._triggerRef, className: classnames('dnb-modal__trigger', createSpacingClasses(rest), triggerAttributes.className, triggerAttributes.class) })), modalActive && modal_content && React.createElement(ParagraphContext.Provider, { value: { isNested: false } }, React.createElement(ModalRoot, _extends({}, rest, { id: this._id, root_id: root_id, content_id: content_id || `dnb-modal-${this._id}`, labelled_by: labelled_by, focus_selector: focus_selector, modal_content: modal_content, header_content: header_content, vertical_alignment: vertical_alignment, bar_content: bar_content, bypass_invalidation_selectors: bypass_invalidation_selectors, close: this.close, hide: hide, title: headerTitle, modalContentCloseRef: this.modalContentCloseRef })))); }; return React.createElement(SuffixContext.Consumer, null, render); } } export { CloseButton, Modal as OriginalComponent }; export default classWithCamelCaseProps(Modal); //# sourceMappingURL=Modal.js.map