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};