1// dom.js 2 3export const $ = (sel, root = document) => root.querySelector(sel); 4export const $$ = (sel, root = document) => [...root.querySelectorAll(sel)]; 5 6// HELPERS 7export const resolveElement = (elOrSelector, root = document) => { 8 if (!elOrSelector) return null; 9 if (typeof elOrSelector === "string") return $(elOrSelector, root); 10 return elOrSelector?.nodeType === 1 ? elOrSelector : null; 11}; 12 13const normaliseErrorMessage = (error) => { 14 if (typeof error === "string") return error; 15 return error?.message ?? "Unknown error"; 16}; 17 18const normaliseNotificationColour = (colour) => colour || "red"; 19 20const normaliseIcon = (icon) => (icon ? icon : "â ï¸"); 21 22const normaliseTitle = (title) => (title ? title : "Error"); 23 24// EVENTS 25export const eventElement = (e) => { 26 const path = e?.composedPath?.(); 27 if (path?.length) { 28 for (const n of path) { 29 if (n?.nodeType === 1) return n; // Element 30 } 31 } 32 33 const t = e?.target; 34 if (t?.nodeType === 1) return t; 35 return t?.parentElement ?? null; 36}; 37 38export const closestFromEvent = (e, selector) => { 39 const el = eventElement(e); 40 if (!el) return null; 41 return el.closest?.(selector) ?? null; 42}; 43 44export const closestFromEventWithin = (e, withinSelector, targetSelector) => { 45 const el = eventElement(e); 46 if (!el) return null; 47 48 if (withinSelector) { 49 const within = el.closest?.(withinSelector); 50 if (!within) return null; 51 } 52 53 return el.closest?.(targetSelector) ?? null; 54}; 55 56// ASYNC DOM 57export const runWhenSelectorExists = ( 58 selector, 59 fn, 60 { root = document, observeRoot = document.documentElement, timeout = 8000 } = {} 61) => { 62 const find = () => root.querySelector(selector); 63 64 let stopped = false; 65 let timeoutId = null; 66 let observer = null; 67 68 const stop = () => { 69 stopped = true; 70 if (observer) observer.disconnect(); 71 observer = null; 72 if (timeoutId) clearTimeout(timeoutId); 73 timeoutId = null; 74 }; 75 76 const run = (el) => { 77 if (stopped) return; 78 stop(); 79 fn?.(el); 80 }; 81 82 const existing = find(); 83 if (existing) { 84 run(existing); 85 return stop; 86 } 87 88 observer = new MutationObserver(() => { 89 const el = find(); 90 if (el) run(el); 91 }); 92 93 observer.observe(observeRoot, { childList: true, subtree: true }); 94 95 if (timeout) { 96 timeoutId = setTimeout(() => { 97 stop(); 98 }, timeout); 99 } 100 101 return stop; 102}; 103 104export const waitForElement = (selector, { timeout = 8000, root = document } = {}) => { 105 const existing = root.querySelector(selector); 106 if (existing) return Promise.resolve(existing); 107 108 return new Promise((resolve, reject) => { 109 let timeoutId = null; 110 111 const stop = () => { 112 obs.disconnect(); 113 if (timeoutId) clearTimeout(timeoutId); 114 }; 115 116 const obs = new MutationObserver(() => { 117 const el = root.querySelector(selector); 118 if (!el) return; 119 stop(); 120 resolve(el); 121 }); 122 123 obs.observe(document.documentElement, { childList: true, subtree: true }); 124 125 if (timeout) { 126 timeoutId = setTimeout(() => { 127 stop(); 128 reject(new Error(`Timed out waiting for ${selector}`)); 129 }, timeout); 130 } 131 }); 132}; 133 134// DOM 135export const setHTML = (elOrSelector, html = "") => { 136 const el = resolveElement(elOrSelector); 137 if (!el) return; 138 el.innerHTML = html ?? ""; 139}; 140 141export const removeClass = (elOrSelector, cls) => { 142 const el = resolveElement(elOrSelector); 143 el?.classList?.remove(cls); 144}; 145 146export const addClass = (elOrSelector, cls) => { 147 const el = resolveElement(elOrSelector); 148 el?.classList?.add(cls); 149}; 150 151export const toggleClass = (elOrSelector, cls) => { 152 const el = resolveElement(elOrSelector); 153 el?.classList?.toggle(cls); 154}; 155 156export const addAttribute = (elOrSelector, attr, value) => { 157 const el = resolveElement(elOrSelector); 158 if (!el) return; 159 160 // Preserve existing behaviour: undefined becomes empty string 161 const val = value === undefined ? "" : value; 162 el.setAttribute(attr, val); 163};
164 165export const removeAttribute = (elOrSelector, attr) => { 166 const el = resolveElement(elOrSelector); 167 if (!el) return; 168 el.removeAttribute(attr); 169}; 170 171// UI 172export const addFallbackError = ( 173 containerOrSelector, 174 { title, error, icon, colour } = {} 175) => { 176 const container = resolveElement(containerOrSelector); 177 if (!container) return; 178 179 const notificationColour = normaliseNotificationColour(colour); 180 181 setHTML( 182 container, 183 ` 184 <div class="notification-wrap"> 185 <ul class="notification notification-${notificationColour} notification-with-icon"> 186 <li class="notification-icon">${normaliseIcon(icon)}</li> 187 <li class="notification-text"> 188 <strong>${normaliseTitle(title)}</strong> 189 <p>${normaliseErrorMessage(error)}</p> 190 </li> 191 </ul> 192 <br> 193 </div> 194 ` 195 ); 196};
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.