PageSourceSearch

https://shop.soarvalleypress.co.uk/static/script/modules-v4/helpers/dom.js?v=20260710T1533

js soarvalleypress.co.uk collected 2026-10-03 23:22:00 UTC 5,288 bytes, 196 lines download raw bytes

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.