Mirror: React hooks for accessible, common web interactions. UI super powers without the UI.
at main 1.7 kB view raw
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 ): { current: boolean } { 26 const isDisabled = !!disabled; 27 const [hasPriority] = useState(() => ({ 28 current: 29 !!ref.current && 30 priorityStack.concat(ref.current).sort(sortByHierarchy)[0] === 31 ref.current, 32 })); 33 34 useLayoutEffect(() => { 35 const { current: element } = ref; 36 if (!element || isDisabled) return; 37 38 function onChange() { 39 hasPriority.current = priorityStack[0] === ref.current; 40 } 41 42 priorityStack.push(element); 43 priorityStack.sort(sortByHierarchy); 44 listeners.add(onChange); 45 for (const listener of listeners) listener(); 46 47 return () => { 48 priorityStack.splice(priorityStack.indexOf(element), 1); 49 listeners.delete(onChange); 50 for (const listener of listeners) listener(); 51 }; 52 }, [ref.current, hasPriority, isDisabled]); 53 54 return hasPriority; 55 }; 56};