PageSourceSearch

https://www.oliverbonas.com/assets/equal-web/component.js?v3

js oliverbonas.com collected 2026-09-24 22:31:48 UTC 9,238 bytes, 255 lines download raw bytes

1// Intercept attachShadow to capture EqualWeb's shadow root
2const nativeAttachShadow = Element.prototype.attachShadow;
3let equalWebShadowRoot;
4
5Element.prototype.attachShadow = function (init) {
6    const root = nativeAttachShadow.call(this, init);
7
8    if (this.id === 'INDShadowRootHost') {
9        equalWebShadowRoot = root;
10
11        // OBR-8339: Inject CSS synchronously to hide the default button
12        // before it is even added to the shadow DOM — prevents any flash
13        const style = document.createElement('style');
14        style.textContent = '#INDmenu-btn { display: none !important; visibility: hidden !important; opacity: 0 !important; pointer-events: none !important; }';
15        root.appendChild(style);
16
17        // Restore original method after capturing
18        Element.prototype.attachShadow = nativeAttachShadow;
19    }
20
21    return root;
22};
23
24// OBR-9001: Dispatch custom event on URL change so equal-web-button can show/hide by path
25const dispatchUrlChange = () => window.dispatchEvent(new CustomEvent('equalweb-urlchange'));
26const originalPushState = history.pushState;
27const originalReplaceState = history.replaceState;
28history.pushState = function (...args) {
29    originalPushState.apply(this, args);
30    dispatchUrlChange();
31};
32history.replaceState = function (...args) {
33    originalReplaceState.apply(this, args);
34    dispatchUrlChange();
35};
36window.addEventListener('popstate', dispatchUrlChange);
37
38const hideEqualWebButton = () => {
39    const button = equalWebShadowRoot?.querySelector('#INDmenu-btn');
40
41    if (!button) {
42        return;
43    }
44
45    button.style.setProperty('display', 'none', 'important');
46    button.style.setProperty('visibility', 'hidden', 'important');
47    button.style.setProperty('opacity', '0', 'important');
48    button.style.setProperty('pointer-events', 'none', 'important');
49};
50
51customElements.define(
52    'equal-web-button',
53    class extends HTMLElement {
54        constructor() {
55            super();
56
57            // Keep hidden until _init() makes it visible
58            this.style.setProperty('display', 'none', 'important');
59
60            if (window.interdeal) {
61                this._init();
62            } else {
63                this._waitForInterdeal();
64            }
65        }
66
67        _waitForInterdeal() {
68            const self = this;
69            let resolved = false;
70
71            const resolve = () => {
72                if (resolved) return;
73                resolved = true;
74                clearInterval(pollTimer);
75                // Restore normal property after intercepting
76                try {
77                    Object.defineProperty(window, 'interdeal', {
78                        configurable: true,
79                        writable: true,
80                        value: window.interdeal,
81                    });
82                } catch (_) {}
83                self._init();
84            };
85
86            try {
87                Object.defineProperty(window, 'interdeal', {
88                    configurable: true,
89                    get() { return undefined; },
90                    set(value) {
91                        Object.defineProperty(window, 'interdeal', {
92                            configurable: true,
93                            writable: true,
94                            value,
95                        });
96                        resolve();
97                    },
98                });
99            } catch (_) {}
100
101            // Fallback: poll in case setter is bypassed (e.g. Object.assign)
102            const pollTimer = setInterval(() => {
103                if (window.interdeal) resolve();
104            }, 200);
105
106            // Give up after 15s if EqualWeb never loads
107            setTimeout(() => {
108                clearInterval(pollTimer);
109            }, 15000);
110        }
111
112        _init() {
113            const supportsDeclarative = HTMLElement.prototype.hasOwnProperty('attachInternals');
114            const internals = supportsDeclarative ? this.attachInternals() : undefined;
115
116            const close = () => {
117                // set cookie on close button click
118                document.cookie = 'OBRhideAccBtn=true';
119
120                // hide the button
121                this.style.display = 'none';
122                this.style.visibility = 'hidden';
123                this.style.opacity = '0';
124                this.style.pointerEvents = 'none';
125            };
126
127            const openMenu = () => {
128                window.interdeal.a11y.openMenu();
129            };
130
131            // check for a Declarative Shadow Root.
132            let shadow = internals?.shadowRoot;
133
134            if (!shadow) {
135                // there wasn't one. create a new Shadow Root:
136                shadow = this.attachShadow({
137                    mode: 'open'
138                });
139                shadow.innerHTML = `
140                <style>
141                    :host {
142                        position: fixed;
143                        left: 0;
144                        top: 50%;
145                        transform: translateY(-50%);
146                        z-index: 1000;
147                        display: flex;
148                        align-items: center;
149                        justify-content: center;
150                        gap: 0;
151                        background-color: var(--atomic-background-color-12);
152                        color: #fff;
153                        border: 2px solid #fff;
154                        border-top-right-radius: 56px;
155                        border-bottom-right-radius: 56px;
156                        height: 52px;
157                        min-width: 76px;
158                        padding: 8px 4px 8px 2px;
159                    }
160
161                    :host:has(._accessibility:not([style*='overflow: hidden'])) {
162                        display: none;
163                    }
164
165                    button {
166                        background: transparent;
167                        border: none;
168                        cursor: pointer;
169                        padding: 4px;
170                    }
171
172                    button#INDMenu-close {
173                        width: 32px;
174                        height: 32px;
175                    }
176
177                    button#INDMenu-close img {
178                        width: 24px;
179                        height: 24px;
180                    }
181
182                    button#INDmenu-btn {
183                        width: 35px;
184                        height: 35px;
185                    }
186
187                    button#INDmenu-btn img {
188                        width: 27px;
189                        height: 27px;
190                    }
191                </style>
192                <button type="button" aria-label="Hide accessibility button" id="INDMenu-close">
193                    <img src="/assets/equal-web/close-24.svg"
194                        alt="Close">
195                </button>
196                <button aria-label="Open accessibility options menu"
197                        id="INDmenu-btn">
198                    <img src="/assets/equal-web/accessibility.svg"
199                        alt="Accessibility">
200                </button>
201            `;
202            }
203
204            // add event listeners to the buttons
205            shadow.querySelector('#INDMenu-close').addEventListener('click', close);
206            shadow.querySelector('#INDmenu-btn').addEventListener('click', openMenu);
207
208            // OBR-9001: On /accessibility always show button and hide close button; otherwise respect cookie
209            const updateForUrl = () => {
210                const onAccessibility = window.location.pathname.includes('/accessibility');
211                const closeBtn = shadow.querySelector('#INDMenu-close');
212                if (onAccessibility) {
213                    this.style.display = '';
214                    this.style.visibility = '';
215                    this.style.opacity = '';
216                    this.style.pointerEvents = '';
217                    closeBtn.style.display = 'none';
218                } else {
219                    closeBtn.style.display = '';
220                    if (document.cookie.includes('OBRhideAccBtn=true')) {
221                        close();
222                    } else {
223                        this.style.display = '';
224                        this.style.visibility = '';
225                        this.style.opacity = '';
226                        this.style.pointerEvents = '';
227                    }
228                }
229            };
230
231            updateForUrl();
232            this._urlChangeHandler = () => updateForUrl();
233            window.addEventListener('equalweb-urlchange', this._urlChangeHandler);
234
235            // Additional fallback: try to hide EqualWeb button if it appears later
236            const hideDefaultEqualWebButton = () => {
237                if (equalWebShadowRoot) {
238                    hideEqualWebButton();
239                    return;
240                }
241            };
242
243            hideDefaultEqualWebButton();
244
245            const observer = new MutationObserver(hideDefaultEqualWebButton);
246            observer.observe(document.body, { childList: true, subtree: true });
247        }
248
249        disconnectedCallback() {
250            if (this._urlChangeHandler) {
251                window.removeEventListener('equalweb-urlchange', this._urlChangeHandler);
252            }
253        }
254    }
255);

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.