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 const isDisabled = !!disabled; 27 const [hasPriority, setHasPriority] = useState(() => { 28 if (!ref.current) return false; 29 const tempStack = priorityStack.concat(ref.current).sort(sortByHierarchy); 30 return tempStack[0] === ref.current; 31 }); 32 33 useLayoutEffect(() => { 34 const { current: element } = ref; 35 if (!element || isDisabled) return; 36 37 function onChange() { 38 setHasPriority(() => priorityStack[0] === ref.current); 39 } 40 41 priorityStack.push(element); 42 priorityStack.sort(sortByHierarchy); 43 listeners.add(onChange); 44 listeners.forEach(fn => fn()); 45 46 return () => { 47 const index = priorityStack.indexOf(element); 48 priorityStack.splice(index, 1); 49 listeners.delete(onChange); 50 listeners.forEach(fn => fn()); 51 }; 52 }, [ref.current, isDisabled]); 53 54 return hasPriority; 55 }; 56};