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 ): { 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};