import { FocusScope } from '@react-aria/focus'; import { Modal, AnimatedVisibility, Button, Box, ModalHeader, ModalThumb, ModalTitle, ModalClose, ModalContent, ModalFooter, ModalFooterControllers, } from '@rocket.chat/fuselage'; import { UiKitComponent, UiKitModal, modalParser } from '@rocket.chat/fuselage-ui-kit'; import type * as UiKit from '@rocket.chat/ui-kit'; import type { FormEvent, FormEventHandler } from 'react'; import { useId, useCallback, useEffect, useMemo, useRef } from 'react'; import { getButtonStyle } from './getButtonStyle'; import { getURL } from '../../../lib/getURL'; const focusableElementsString = ` a[href]:not([tabindex="-1"]), area[href]:not([tabindex="-1"]), input:not([disabled]):not([tabindex="-1"]), select:not([disabled]):not([tabindex="-1"]), textarea:not([disabled]):not([tabindex="-1"]), button:not([disabled]):not([tabindex="-1"]), iframe, object, embed, [tabindex]:not([tabindex="-1"]), [contenteditable]`; const focusableElementsStringInvalid = ` a[href]:not([tabindex="-1"]):invalid, area[href]:not([tabindex="-1"]):invalid, input:not([disabled]):not([tabindex="-1"]):invalid, select:not([disabled]):not([tabindex="-1"]):invalid, textarea:not([disabled]):not([tabindex="-1"]):invalid, button:not([disabled]):not([tabindex="-1"]):invalid, iframe:invalid, object:invalid, embed:invalid, [tabindex]:not([tabindex="-1"]):invalid, [contenteditable]:invalid`; type ModalBlockParams = { view: UiKit.ModalView; errors: any; appId: string; onSubmit: (event: FormEvent) => void; onClose: () => void; onCancel: FormEventHandler; }; const isFocusable = (element: Element | null): element is HTMLElement => element !== null && 'focus' in element && typeof element.focus === 'function'; const KeyboardCode = new Map([ ['ENTER', 13], ['ESC', 27], ['TAB', 9], ]); const ModalBlock = ({ view, errors, onSubmit, onClose, onCancel }: ModalBlockParams) => { const id = `modal_id_${useId()}`; const ref = useRef(null); useEffect(() => { if (!ref.current) { return; } if (errors && Object.keys(errors).length) { const element = ref.current.querySelector(focusableElementsStringInvalid); element?.focus(); } else { const element = ref.current.querySelector(focusableElementsString); element?.focus(); } }, [errors]); const previousFocus = useMemo(() => document.activeElement, []); useEffect( () => () => { if (previousFocus && isFocusable(previousFocus)) { return previousFocus.focus(); } }, [previousFocus], ); const formRef = useRef(null); const handleKeyDown = useCallback( (event: KeyboardEvent) => { switch (event.keyCode) { case KeyboardCode.get('ENTER'): if ((event?.target as Node | null)?.nodeName !== 'TEXTAREA') { formRef.current?.submit(); return; } return; case KeyboardCode.get('ESC'): event.stopPropagation(); event.preventDefault(); onClose(); return; case KeyboardCode.get('TAB'): if (!ref.current) { return; } const elements = Array.from(ref.current.querySelectorAll(focusableElementsString)); const [first] = elements; const last = elements.pop(); if (!ref.current.contains(document.activeElement)) { return first.focus(); } if (event.shiftKey) { if (!first || first === document.activeElement) { last?.focus(); event.stopPropagation(); event.preventDefault(); } return; } if (!last || last === document.activeElement) { first.focus(); event.stopPropagation(); event.preventDefault(); } } }, [onClose], ); useEffect(() => { const element = document.querySelector('#modal-root') as HTMLElement; const container = element.querySelector('.rcx-modal__content') as HTMLElement; const close = (e: Event) => { if (e.target !== element) { return; } e.preventDefault(); e.stopPropagation(); onClose(); return false; }; const ignoreIfNotContains = (e: KeyboardEvent) => { if (e.target !== element) { return; } if (!container.contains(e.target as HTMLElement)) { return; } return handleKeyDown(e); }; document.addEventListener('keydown', ignoreIfNotContains); element.addEventListener('click', close); return () => { document.removeEventListener('keydown', ignoreIfNotContains); element.removeEventListener('click', close); }; }, [handleKeyDown, onClose]); return ( {view.showIcon ? : null} {modalParser.renderTextObject(view.title, 0)} {view.close && ( )} {view.submit && ( )} ); }; export default ModalBlock;