1import { Controller } from "@hotwired/stimulus"; 2 3const placements = { 4 top: { 5 base: ["top-0", "left-0", "right-0"], 6 active: ["transform-none"], 7 inactive: ["-translate-y-full"], 8 }, 9 right: { base: ["right-0", "top-0"], active: ["transform-none"], inactive: ["translate-x-full"] }, 10 bottom: { 11 base: ["bottom-0", "left-0", "right-0"], 12 active: ["transform-none"], 13 inactive: ["translate-y-full"], 14 }, 15 left: { base: ["left-0", "top-0"], active: ["transform-none"], inactive: ["-translate-x-full"] }, 16}; 17 18export default class extends Controller { 19 connect() { 20 this.drawers = new Map(); 21 this.handleKeydown = this.handleKeydown.bind(this); 22 document.addEventListener("keydown", this.handleKeydown); 23 } 24 25 disconnect() { 26 document.removeEventListener("keydown", this.handleKeydown); 27 this.drawers.forEach((drawer) => { 28 drawer.backdropElement?.remove(); 29 }); 30 document.body.classList.remove("overflow-hidden"); 31 } 32 33 show(event) { 34 event.preventDefault(); 35 36 const drawer = this.drawerFor(event.params); 37 if (!drawer || drawer.visible) return; 38 39 drawer.trigger = event.currentTarget; 40 drawer.panel.classList.add(...drawer.classes.active); 41 drawer.panel.classList.remove(...drawer.classes.inactive); 42 drawer.panel.setAttribute("aria-modal", "true"); 43 drawer.panel.setAttribute("role", "dialog"); 44 drawer.panel.removeAttribute("aria-hidden"); 45 drawer.trigger.setAttribute("aria-expanded", "true"); 46 drawer.visible = true; 47 48 if (!drawer.bodyScrolling) document.body.classList.add("overflow-hidden"); 49 if (drawer.backdrop) this.createBackdrop(drawer); 50 51 drawer.panel.focus({ preventScroll: true }); 52 } 53 54 hide(event) { 55 event.preventDefault(); 56 57 const drawer = this.drawers.get(event.params.id); 58 if (drawer) this.hideDrawer(drawer); 59 } 60 61 toggle(event) { 62 const drawer = this.drawerFor(event.params); 63 if (!drawer) return; 64 65 drawer.visible ? this.hide(event) : this.show(event); 66 } 67 68 handleKeydown(event) { 69 if (event.key !== "Escape") return; 70 71 const drawer = [...this.drawers.values()].reverse().find(({ visible }) => visible); 72 if (drawer) this.hideDrawer(drawer); 73 } 74 75 drawerFor({ id, placement = "right", backdrop = true, bodyScrolling = false }) { 76 if (!id) return; 77 78 const existingDrawer = this.drawers.get(id); 79 if (existingDrawer) return existingDrawer; 80 81 const panel = document.getElementById(id); 82 if (!panel) return; 83 84 const classes = placements[placement] || placements.right; 85 const drawer = { 86 panel, 87 classes, 88 backdrop, 89 bodyScrolling, 90 visible: false, 91 trigger: null, 92 backdropElement: null, 93 }; 94 95 panel.classList.add(...classes.base, ...classes.inactive); 96 panel.setAttribute("aria-hidden", "true"); 97 this.drawers.set(id, drawer); 98 99 return drawer; 100 } 101 102 hideDrawer(drawer) { 103 if (!drawer.visible) return; 104 105 drawer.panel.classList.remove(...drawer.classes.active); 106 drawer.panel.classList.add(...drawer.classes.inactive); 107 drawer.panel.setAttribute("aria-hidden", "true"); 108 drawer.panel.removeAttribute("aria-modal"); 109 drawer.panel.removeAttribute("role"); 110 drawer.trigger?.setAttribute("aria-expanded", "false"); 111 drawer.backdropElement?.remove(); 112 drawer.backdropElement = null; 113 drawer.visible = false; 114 115 if ( 116 ![...this.drawers.values()].some(({ visible, bodyScrolling }) => visible && !bodyScrolling) 117 ) { 118 document.body.classList.remove("overflow-hidden"); 119 } 120 121 drawer.trigger?.focus({ preventScroll: true }); 122 } 123 124 createBackdrop(drawer) { 125 if (drawer.backdropElement) return; 126 127 drawer.backdropElement = document.createElement("div"); 128 drawer.backdropElement.className = "fixed inset-0 z-30 bg-zinc-900/50 dark:bg-zinc-900/80"; 129 drawer.backdropElement.addEventListener("click", () => this.hideDrawer(drawer)); 130 document.body.appendChild(drawer.backdropElement); 131 } 132}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.