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 & 2 /* b follows a */ && -1) || 16 (x & 4 /* a follows b */ && 1) 17 ) || 0; 18 }; 19 20 /** Indicates whether a given element on a stack of active priority hooks is the deepest element. */ 21 return function usePriority<T extends HTMLElement>(ref: Ref<T>, disabled?: boolean): boolean { 22 function computeHasPriority(): boolean { 23 if (!ref.current) return false; 24 const tempStack = priorityStack.concat(ref.current).sort(sortByHierarchy); 25 return tempStack[tempStack.length - 1] === ref.current; 26 } 27 28 const isDisabled = !!disabled; 29 const [hasPriority, setHasPriority] = useState(computeHasPriority); 30 31 useLayoutEffect(() => { 32 if (!ref.current || isDisabled) return; 33 34 const { current } = ref; 35 36 function onChange() { 37 setHasPriority(computeHasPriority); 38 } 39 40 priorityStack.push(current); 41 priorityStack.sort(sortByHierarchy); 42 listeners.forEach(fn => fn()); 43 listeners.add(onChange); 44 45 return () => { 46 const index = priorityStack.indexOf(current); 47 priorityStack.splice(index, 1); 48 listeners.delete(onChange); 49 listeners.forEach(fn => fn()); 50 }; 51 }, [ref, isDisabled]); 52 53 return hasPriority; 54 } 55};