Mirror: React hooks for accessible, common web interactions. UI super powers without the UI.
1import { useState } from 'react'; 2import { useLayoutEffect } from './utils/react'; 3import { Ref } from './types'; 4 5/** Creates a priority stack of elements so that we can determine the "deepest" one to be the active hook */ 6export const makePriorityHook = () => { 7 const listeners: Set<Function> = new Set(); 8 const priorityStack: HTMLElement[] = []; 9 10 const sortByHierarchy = (a: HTMLElement, b: HTMLElement) => { 11 const x = a.compareDocumentPosition(b); 12 return ( 13 (x & 16 /* a contains b */ && 1) || 14 (x & 8 /* b contains a */ && -1) || 15 (x & 4 /* a follows b */ && 1) || 16 (x & 2 /* b follows a */ && -1) || 17 0 18 ); 19 }; 20 21 /** Indicates whether a given element on a stack of active priority hooks is the deepest element. */ 22 return function usePriority<T extends HTMLElement>( 23 ref: Ref<T>, 24 disabled?: boolean 25 ): boolean { 26 function computeHasPriority(): boolean { 27 if (!ref.current) return false; 28 const tempStack = priorityStack.concat(ref.current).sort(sortByHierarchy); 29 return tempStack[0] === ref.current; 30 } 31 32 const isDisabled = !!disabled; 33 const [hasPriority, setHasPriority] = useState(computeHasPriority); 34 35 useLayoutEffect(() => { 36 if (!ref.current || isDisabled) return; 37 38 const { current } = ref; 39 40 function onChange() { 41 setHasPriority(() => priorityStack[0] === ref.current); 42 } 43 44 priorityStack.push(current); 45 priorityStack.sort(sortByHierarchy); 46 listeners.add(onChange); 47 listeners.forEach(fn => fn()); 48 49 return () => { 50 const index = priorityStack.indexOf(current); 51 priorityStack.splice(index, 1); 52 listeners.delete(onChange); 53 listeners.forEach(fn => fn()); 54 }; 55 }, [ref, isDisabled]); 56 57 return hasPriority; 58 }; 59};