Recently Written · git

scan-subread

PDF reader that follows an audiobook with the SubRead suite

git clone https://github.com/equwal/scan-subread

Log | Files | Refs


src/drawer.ts (1829 bytes)

1 // The menu. On a phone it is a drawer over the page. On a desktop it is a
2 // side panel that always shows.
3 
4 /** The width from which the menu is a side panel. The same as the media query in style.css. */
5 const desktop = window.matchMedia('(min-width: 900px)');
6 
7 export interface Drawer {
8   /** True while the drawer covers the page: a phone with the drawer open. */
9   covers(): boolean;
10   /** True while the menu shows: a desktop, or an open drawer. */
11   shows(): boolean;
12   /** Opens or closes the drawer. */
13   set(open: boolean): void;
14   /** Closes the drawer on a phone, so the user sees the page after an action in the menu. */
15   closeOnPhone(): void;
16 }
17 
18 /** `onOpen` runs each time the drawer opens. */
19 export function setupDrawer(
20   ui: { menu: HTMLButtonElement; panel: HTMLElement },
21   onOpen: () => void,
22 ): Drawer {
23   const open = (): boolean => document.body.classList.contains('menu-open');
24   const covers = (): boolean => !desktop.matches && open();
25 
26   function set(value: boolean): void {
27     if (value === open()) return;
28     document.body.classList.toggle('menu-open', value);
29     ui.menu.setAttribute('aria-expanded', String(value));
30     if (value) onOpen();
31   }
32 
33   ui.menu.addEventListener('click', () => set(!open()));
34 
35   // With the drawer open, a tap outside it only closes it. The capture
36   // phase runs before the handlers of the page, so the tap does no lookup.
37   document.addEventListener(
38     'click',
39     (e) => {
40       if (!covers()) return;
41       const target = e.target as Node;
42       if (ui.panel.contains(target) || ui.menu.contains(target)) return;
43       e.stopPropagation();
44       e.preventDefault();
45       set(false);
46     },
47     true,
48   );
49 
50   return {
51     covers,
52     shows: () => desktop.matches || open(),
53     set,
54     closeOnPhone() {
55       if (!desktop.matches) set(false);
56     },
57   };
58 }