All files / molecule/modal/src Modal.Root.js

95.45% Statements 21/22
80% Branches 16/20
100% Functions 4/4
95.23% Lines 20/21

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118                          1x                       84x 84x 84x 84x             48x 28x     48x 20x 20x           84x   84x             84x 3x 3x 3x     3x     84x                                       1x   1x                                                            
import {useEffect, useRef, useState} from 'react'
 
import PropTypes from 'prop-types'
 
import {Root as RadixRoot} from '@radix-ui/react-dialog'
 
import useControlledState from '@s-ui/react-hooks/lib/useControlledState'
 
import ModalContext from './context/index.js'
import {useDelayedRender} from './hooks/index.js'
import {ANIMATION_ENTER_DELAY, ANIMATION_EXIT_DELAY} from './config.js'
 
/** Contains all the parts of a dialog. React context of contained Modal **/
const Root = ({
  defaultIsOpen = false,
  isOpen,
  onOpen,
  onOpenDelayed,
  onClose,
  onCloseDelayed,
  onOpenToggle,
  modal = true,
  children,
  ...props
}) => {
  const [open, setOpen] = useControlledState(isOpen, defaultIsOpen)
  const activeElementRef = useRef()
  const [forceMount, setForceMount] = useState(undefined)
  const [animation, setAnimation] = useState(undefined)
 
  /**
   * This function captures the active element when the Dialog is opened
   * and sets focus back to it when the Dialog is closed.
   */
  function handleActiveElementFocus() {
    if (isOpen && document.activeElement) {
      activeElementRef.current = document.activeElement
    }
 
    if (!isOpen) {
      setTimeout(() => {
        Eif (!(activeElementRef.current instanceof HTMLElement)) return
        activeElementRef.current.focus()
      }, 0)
    }
  }
 
  useEffect(handleActiveElementFocus, [open])
 
  const {isMounted, isRendered} = useDelayedRender(open, {
    enterDelay: ANIMATION_ENTER_DELAY,
    exitDelay: ANIMATION_EXIT_DELAY,
    onEnterDelayed: onOpenDelayed,
    onExitDelayed: onCloseDelayed
  })
 
  const onOpenChange = open => {
    const dialogEvent = new Event(open ? 'onOpenDialog' : 'onCloseDialog')
    setOpen(open)
    open
      ? typeof onOpen === 'function' && onOpen(dialogEvent, {isOpen: open})
      : typeof onClose === 'function' && onClose(dialogEvent, {isOpen: open})
    typeof onOpenToggle === 'function' && onOpenToggle(dialogEvent, {isOpen: open})
  }
 
  return (
    <ModalContext.Provider
      value={{
        isOpen: open,
        forceMount,
        setForceMount,
        animation,
        hasAnimation: animation !== 'none',
        setAnimation,
        isMounted,
        isRendered
      }}
    >
      <RadixRoot defaultOpen={defaultIsOpen} open={open} onOpenChange={onOpenChange} modal={modal} {...props}>
        {children}
      </RadixRoot>
    </ModalContext.Provider>
  )
}
 
Root.displayName = 'MoleculeModal.Root'
 
Root.propTypes = {
  /** The open state of the modal when it is initially rendered. Use when you do not need to control its open state. */
  defaultIsOpen: PropTypes.bool,
 
  /** The controlled open state of the modal. Must be used in conjunction with onOpenChange. **/
  isOpen: PropTypes.bool,
 
  /** Event handler called when the open state of the modal changes. **/
  onOpenToggle: PropTypes.func,
 
  /** Event handler called when the open the modal gets opened. **/
  onOpen: PropTypes.func,
 
  /** Event handler called when the open modal animation ended. **/
  onOpenDelayed: PropTypes.func,
 
  /** Event handler called when the open the modal gets closed. **/
  onClose: PropTypes.func,
 
  /** Event handler called when the close modal animation ended. **/
  onCloseDelayed: PropTypes.func,
 
  /** The modality of the dialog. When set to true, interaction with outside elements will be disabled and only dialog content will be visible to screen readers. **/
  modal: PropTypes.bool,
 
  /** The children of the modal. **/
  children: PropTypes.node
}
 
export default Root