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.defaultPrevented || event.isComposing) return; 19 20 if (event.code === 'Escape') { 21 // The current dialog can be dismissed by pressing escape if it either has focus 22 // or it has priority 23 const active = document.activeElement; 24 if (hasPriority || (active && contains(ref.current, active))) { 25 event.preventDefault(); 26 onDismiss(); 27 } 28 } 29 } 30 31 function onClick(event: MouseEvent | TouchEvent) { 32 const { target } = event; 33 if (contains(ref.current, target) || event.defaultPrevented) { 34 return; 35 } 36 37 event.preventDefault(); 38 onDismiss(); 39 } 40 41 if (hasPriority) { 42 document.addEventListener('mousedown', onClick); 43 document.addEventListener('touchstart', onClick); 44 } 45 46 document.addEventListener('keydown', onKey); 47 48 return () => { 49 if (hasPriority) { 50 document.removeEventListener('mousedown', onClick); 51 document.removeEventListener('touchstart', onClick); 52 } 53 54 document.removeEventListener('keydown', onKey); 55 }; 56 }, [ref, hasPriority, onDismiss]); 57}