Mirror: React hooks for accessible, common web interactions. UI super powers without the UI.
1import { useLayoutEffect } from './utils/react'; 2import { contains } from './utils/element'; 3import { makePriorityHook } from './usePriority'; 4import { Ref } from './types'; 5 6const usePriority = makePriorityHook(); 7 8export function useDialogDismiss<T extends HTMLElement>( 9 ref: Ref<T>, 10 onDismiss: () => void 11) { 12 const hasPriority = usePriority(ref); 13 14 useLayoutEffect(() => { 15 if (!ref.current || !hasPriority) return; 16 17 function onKey(event: KeyboardEvent) { 18 if (!event.isComposing && event.code === 'Escape') { 19 // The current dialog can be dismissed by pressing escape if it either has focus 20 // or it has priority 21 const active = document.activeElement; 22 if (hasPriority || (active && contains(ref.current, active))) { 23 event.preventDefault(); 24 onDismiss(); 25 } 26 } 27 } 28 29 function onClick(event: MouseEvent | TouchEvent) { 30 const { target } = event; 31 if (contains(ref.current, target) || event.defaultPrevented) { 32 return; 33 } 34 35 // The current dialog can be dismissed by pressing outside of it if it either has 36 // focus or it has priority 37 const active = document.activeElement; 38 if (hasPriority || (active && contains(ref.current, active))) { 39 event.preventDefault(); 40 onDismiss(); 41 } 42 } 43 44 document.addEventListener('mousedown', onClick); 45 document.addEventListener('touchstart', onClick); 46 document.addEventListener('keydown', onKey); 47 48 return () => { 49 document.removeEventListener('mousedown', onClick); 50 document.removeEventListener('touchstart', onClick); 51 document.removeEventListener('keydown', onKey); 52 }; 53 }, [ref, hasPriority, onDismiss]); 54}