{"version":3,"file":"modal.mjs","names":[],"sources":["../../../../package/common/modal/modal.tsx"],"sourcesContent":["import cx from 'classnames';\nimport BodyEnd from 'components/common/bodyEnd';\n\nimport styles from './modal.module.pcss';\nimport sheetStyles from 'components/common/styles/sheet.module.pcss';\nimport { faXmark as closeIcon } from '@fortawesome/free-solid-svg-icons';\nimport { ModalProps } from './modal.types';\nimport type { ModalLabels } from './modal.types';\nimport { useEffect, useId, useRef, useState } from 'react';\nimport { useVisualViewportSize } from 'components/common/hooks/useVisualViewportSize';\nimport { useOverlay } from 'components/common/hooks/useOverlay';\nimport { useSheet } from 'components/common/hooks/useSheet';\nimport { useScrollEdges } from 'components/common/hooks/useScrollEdges';\nimport fadeStyles from 'components/common/styles/scrollFade.module.pcss';\nimport { useComponentDefaults, useLabels, usePresentationDefault } from 'components/common/configProvider';\nimport ActionIcon from 'components/common/actionIcon';\n\nconst DEFAULT_LABELS: Required<ModalLabels> = {\n  close: 'Close',\n};\n\nfunction Modal(props: ModalProps) {\n  const {\n    children = null,\n    title,\n    isOpen = false,\n    onClose = () => null,\n    footer = null,\n    className = '',\n    style,\n    hideHeader = false,\n    closeOnEscape = true,\n    presentation: presentationProp,\n    classNames,\n    styles: slotStyles,\n    labels,\n    ...rest\n  } = useComponentDefaults('Modal', props);\n  const text = useLabels('Modal', labels, DEFAULT_LABELS);\n  const [isRendered, setIsRendered] = useState(isOpen);\n  const [animateIn, setAnimateIn] = useState(isOpen);\n  const modalRef = useRef<HTMLDivElement>(null);\n  const bodyRef = useRef<HTMLDivElement>(null);\n  const titleId = useId();\n  // Centering/sizing against the actually-visible viewport (not the full layout viewport) keeps\n  // the modal — and whatever's focused inside it — from ending up under a mobile keyboard.\n  const { height: viewportHeight, offsetTop: viewportOffsetTop } = useVisualViewportSize();\n  // Explicit prop → this component's global default → the app-wide one. There's no built-in\n  // fallback to add: `Default` and no presentation at all both mean the ordinary centred dialog.\n  const appPresentation = usePresentationDefault();\n  const presentation = presentationProp ?? appPresentation;\n  const { isSheet, sheetStyle } = useSheet(presentation);\n\n  // Cancelled on a flip back, or a reopen during the close animation is hidden by its late unmount.\n  useEffect(() => {\n    if (isOpen) {\n      // eslint-disable-next-line react-hooks/set-state-in-effect\n      setIsRendered(true);\n      const id = setTimeout(() => setAnimateIn(true), 1);\n      return () => clearTimeout(id);\n    }\n    setAnimateIn(false);\n    const id = setTimeout(() => setIsRendered(false), 300);\n    return () => clearTimeout(id);\n  }, [isOpen]);\n\n  // Keyed on `animateIn` rather than `isOpen`, because the panel this needs to focus and trap\n  // focus inside of only mounts once that flips.\n  useOverlay({\n    isActive: isRendered && animateIn,\n    containerRef: modalRef,\n    onEscape: closeOnEscape ? onClose : undefined,\n  });\n\n  const { hasStart, hasEnd } = useScrollEdges(bodyRef, { isEnabled: isRendered && animateIn });\n\n  if (!isRendered) return null;\n\n  const hasHeaderTitle = !hideHeader && !!title;\n\n  return (\n    <BodyEnd>\n      <div\n        className={cx(styles.modalContainer, classNames?.container)}\n        style={{ height: viewportHeight, top: viewportOffsetTop, ...slotStyles?.container }}\n      >\n        <div\n          className={cx(styles.modalWrapper, animateIn && styles.modalWrapperShown, classNames?.backdrop)}\n          style={slotStyles?.backdrop}\n          onClick={onClose}\n        />\n        {animateIn && (\n          <div\n            {...rest}\n            ref={modalRef}\n            role='dialog'\n            aria-modal='true'\n            // aria-labelledby would outrank aria-label, which is the only way to name a title with no text.\n            aria-labelledby={hasHeaderTitle && !rest['aria-label'] ? titleId : undefined}\n            // Focusable so the trap has somewhere to put focus when the content has no tabbables.\n            tabIndex={-1}\n            className={cx(\n              styles.modal,\n              animateIn && styles.modalShown,\n              // The panel is its own sheet surface here: there's no inner panel to fill, since\n              // Modal already splits itself into a fixed header and a scrolling body.\n              isSheet && [sheetStyles.sheet, sheetStyles.sheetSurface, styles.modalSheet],\n              className,\n              classNames?.modal,\n            )}\n            style={{ ...(isSheet ? sheetStyle : undefined), ...style, ...slotStyles?.modal }}\n            onClick={(e) => e.stopPropagation()}\n          >\n            {!hideHeader && (\n              <div className={cx(styles.modalHeader, classNames?.header)} style={slotStyles?.header}>\n                <div id={titleId} className={classNames?.title} style={slotStyles?.title}>\n                  {title}\n                </div>\n                <ActionIcon\n                  icon={closeIcon}\n                  onClick={onClose}\n                  aria-label={text.close}\n                  className={cx(styles.modalCloseButton, classNames?.closeButton)}\n                  style={slotStyles?.closeButton}\n                />\n              </div>\n            )}\n            <div\n              ref={bodyRef}\n              className={cx(\n                styles.modalBody,\n                fadeStyles.scrollFade,\n                fadeStyles.scrollFadeY,\n                hasStart && fadeStyles.scrollFadeFadedStart,\n                hasEnd && fadeStyles.scrollFadeFadedEnd,\n                classNames?.body,\n              )}\n              style={slotStyles?.body}\n            >\n              {children}\n            </div>\n            {footer && (\n              <div className={cx(styles.modalFooter, classNames?.footer)} style={slotStyles?.footer}>\n                {footer}\n              </div>\n            )}\n          </div>\n        )}\n      </div>\n    </BodyEnd>\n  );\n}\n\nexport default Modal;\n"],"mappings":"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"}