PageSourceSearch

https://www.schloss-ratibor.de/fileadmin/templates/js/parts/hw-content-overlay.js

js schloss-ratibor.de collected 2026-10-01 12:31:59 UTC 12,753 bytes, 421 lines download raw bytes

1export class HwContentOverlay {
2	#overlaySelector;
3	static #instance = null;
4
5	/**
6	 * Initializes content overlays.
7	 *
8	 * If new dialog elements are dynamically added via JavaScript,
9	 * call the following to initialize any missing dialogs:
10	 *
11	 * document.body._HwContentOverlay.init();
12	 */
13	constructor() {
14		if (HwContentOverlay.#instance) {
15
16			// Return instance
17			return HwContentOverlay.#instance;
18		}
19
20		this.overlayList = new Set();
21
22		// Configuration
23		this.#overlaySelector = 'dialog:not([id^="hw_datenschutz_banner"])';
24
25		// Init all currently existing content overlays
26		this.init();
27
28		// Add handler for url hashes
29		this.#initHash();
30
31		// Global Events per Instance
32		this.#setupGlobalCloseEvent();
33
34		// Add to document.body for external scripts
35		document.body._HwContentOverlay = this;
36
37		// Set instance
38		HwContentOverlay.#instance = this;
39
40		// Return instance
41		return HwContentOverlay.#instance;
42	}
43
44	#updateOverlayList() {
45		document.querySelectorAll(this.#overlaySelector).forEach(overlay => {
46			this.overlayList.add(overlay);
47		});
48	}
49
50	init(reInit = false) {
51
52		// Update list of overlays
53		this.#updateOverlayList();
54
55		// Init not yet initialized content overlays
56		this.overlayList.forEach(overlay => {
57			if (reInit) {
58				if (overlay.parentNode !== document.body ) {
59					delete overlay._contentOverlay;
60
61					// Clone Element to remove all event listeners
62					const overlayClone = overlay.cloneNode(true);
63					overlay.parentNode.replaceChild(overlayClone, overlay);
64
65					overlay.remove();
66				}
67			}
68
69			if (overlay._contentOverlay) {
70
71				// Already initialized
72				return;
73			}
74
75			new HwContentOverlayElement(overlay);
76		});
77	}
78
79	#initHash() {
80
81		// Hash on page load
82		if (window.location.hash) {
83			const hash = window.location.hash.slice(1);
84
85			this.#hashHandler(hash);
86		}
87
88		// Listen for hashchange event
89		window.addEventListener('hashchange', () => {
90			const hash = window.location.hash.slice(1);
91
92			this.#hashHandler(hash);
93		});
94
95		// On anchor link click
96		document.querySelectorAll('a[href^="#"]').forEach((anchorLink) => {
97			anchorLink.addEventListener('click', (e) => {
98				e.preventDefault();
99
100				const hash = anchorLink.href.split('#')[1];
101
102				this.#hashHandler(hash);
103			});
104		});
105	}
106
107	#setupGlobalCloseEvent() {
108
109		// Keydown Event
110		document.addEventListener('keydown', (event) => {
111			if (event.key !== 'Escape') {
112				return;
113			}
114
115			event.preventDefault();
116
117			document.querySelectorAll(this.#overlaySelector + '[open]').forEach((overlayElement) => {
118				overlayElement.close();
119			});
120		});
121	}
122
123	#hashHandler(hash) {
124		const element = document.getElementById(hash);
125
126		if (element) {
127
128			const dialogElement = element.closest('dialog');
129
130			if (dialogElement && dialogElement.overlayElement) {
131				dialogElement.overlayElement.open();
132			}
133		}
134	}
135}
136
137class HwContentOverlayElement {
138	#overlayButtonOpen;
139	#overlayButtonClose;
140	#overlayContentWrap;
141	#overlayHeader;
142	#overlayContentElements;
143	#overlayMansoryEvent;
144
145	constructor(element) {
146		this.overlayElement = element;
147
148		// Added object to node for external scripts
149		element._contentOverlay = this;
150
151		this.#updateOverlayProperties();
152
153		// Custom Event for Mansory layout
154		this.#overlayMansoryEvent = new CustomEvent('Hw:content_overlay_mansory', {
155			bubbles: true, // Allow the event to bubble up the DOM tree
156			cancelable: true // Allow the event to be canceled
157		});
158
159		// Initialize overlay
160		this.#initializeOverlay();
161
162		// Move element to document.body
163		if (element.parentNode !== document.body) {
164			document.body.appendChild(element);
165		}
166	}
167
168	#updateOverlayProperties() {
169
170		// Initialize overlay open button
171		this.#overlayButtonOpen = null;
172
173		if (this.overlayElement.id && document.querySelector(`[aria-controls="${this.overlayElement.id}"]`)) {
174
175			// Open button found for dialog id
176			this.#overlayButtonOpen = document.querySelector(`[aria-controls="${this.overlayElement.id}"]`);
177		} else if (this.overlayElement?.previousElementSibling?.querySelector('&:not(main) button')) {
178
179			// Open button found as previous element sibling
180			this.#overlayButtonOpen = this.overlayElement.previousElementSibling;
181		}
182
183		// Initialize overlay close button
184		this.#overlayButtonClose = this.overlayElement.querySelector('hw-content-overlay-button-close > button') || null;
185
186		// Initialize overlay content wrap
187		this.#overlayContentWrap = this.overlayElement.querySelector('hw-content-overlay-wrap') || null;
188
189		// Initialize overlay content header
190		this.#overlayHeader = this.overlayElement?.closest('hw-content-header, hw-content-element')?.querySelector('*[data-hw-content-role="overlay-header"], .hw_content__first_title') || null;
191
192		// Initialize overlay content elements
193		this.#overlayContentElements = this.overlayElement.querySelectorAll('hw-content-overlay-element') || null;
194	}
195
196	#initializeOverlay() {
197		this.#setupOpenButton();
198		this.#setupCloseButton();
199		this.#setupCloseEvent();
200
201		// Save the original .show() method
202		const originalShowFunction = this.overlayElement.show.bind(this.overlayElement);
203
204		// Overwrite open function
205		this.overlayElement.show = () => {
206			this.#updateOverlayProperties();
207
208			// Call the original .show() method to perform the default behavior
209			originalShowFunction();
210		};
211
212		if (this.overlayElement.closest('#structure-wrap')) {
213			const resizeObserver = new ResizeObserver(() => {
214				this.#setupMansoryLayout();
215			});
216
217			resizeObserver.observe(this.overlayElement);
218		}
219	}
220
221	#setupOpenButton() {
222		if (!this.#overlayButtonOpen) {
223			return;
224		}
225
226		if (this.#overlayHeader && !this.overlayElement.closest('hw-content-image-links-wrap')) {
227			this.#overlayHeader.after(this.#overlayHeader);
228		}
229
230		this.#overlayButtonOpen.addEventListener('click', (event) => {
231			event.preventDefault();
232			this.overlayElement.show();
233		});
234	}
235
236	#setupCloseButton() {
237		if (!this.#overlayButtonClose) {
238			return;
239		}
240
241		this.#overlayButtonClose.addEventListener('click', (event) => {
242			event.preventDefault();
243			this.overlayElement.close();
244		});
245	}
246
247	#setupCloseEvent() {
248
249		this.overlayElement.addEventListener('pointerdown', (event) => {
250
251			// Only clicks with main button
252			if (!(event.pointerType === 'mouse' && event.button === 0)) {
253				return;
254			}
255
256			const focusoutElements = this.overlayElement.querySelectorAll('hw-content-overlay-header, hw-content-overlay > *, hw-content-overlay-button-close, hw-content-overlay-wrap');
257
258			// Elements that focus out without checking child nodes
259			const forceFocusoutElements = this.overlayElement.querySelectorAll('hw-content-overlay, hw-content-overlay-element');
260
261			let elementWithin = false;
262			let forceClose = false;
263
264			forceFocusoutElements.forEach((forceFocusoutElement) => {
265				if (event.target === forceFocusoutElement) {
266					forceClose = true;
267				}
268			});
269
270			focusoutElements.forEach((focusoutElement) => {
271				if (event.target === focusoutElement || focusoutElement.contains(event.target)) {
272					elementWithin = true;
273				}
274			});
275
276			if (!elementWithin || forceClose) {
277				this.overlayElement.close();
278			}
279		});
280
281		// Focusout Event
282		this.overlayElement.addEventListener('focusout', (event) => {
283			event.preventDefault();
284
285			if (this.overlayElement.contains(event.target)) {
286
287				// Focus is still within the container, don't treat it as a real focusout
288				event.stopPropagation();
289
290				return;
291			}
292
293			this.overlayElement.close();
294		});
295	}
296
297	#setupMansoryLayout() {
298		this.overlayElement.addEventListener('hw:content_overlay_mansory', (event) => {
299			event.target.removeAttribute('style');
300		}, {
301			once: true
302		});
303
304		this.#initMansoryLayout();
305	}
306
307	#initMansoryLayout() {
308		const hwContentOverlayElementsColumnCount = this.#hwGetCSSVariableValue(this.#overlayContentWrap, '--hw-content-overlay-columns');
309
310		if (this.#overlayContentElements.length > 0) {
311
312			const hwContentOverlayElementContent = Array.from(this.#overlayContentElements[0].parentElement.children);
313			const hwContentOverlayElementColumns = Array.from({
314				length: hwContentOverlayElementsColumnCount
315			}, () => []);
316
317			hwContentOverlayElementContent.forEach((hwContentOverlayElement, index) => {
318				hwContentOverlayElement.dataset.hwContentOverlayColumns = hwContentOverlayElementContent.length === 1 ? 1 : hwContentOverlayElementsColumnCount;
319
320				const hwContentOverlayColumnIndex = index % hwContentOverlayElementsColumnCount;
321
322				hwContentOverlayElementColumns[hwContentOverlayColumnIndex].push(hwContentOverlayElement);
323				hwContentOverlayElement.style.setProperty('--hw-content-overlay-animation-delay', index);
324			});
325
326			hwContentOverlayElementColumns.forEach((hwContentOverlayColumn, hwContentOverlayColumnIndex) => {
327				let hwContentOverlayElementsCurrentTop = 0;
328
329				hwContentOverlayColumn.forEach(hwContentOverlayElement => {
330					hwContentOverlayElement.style.top = `${hwContentOverlayElementsCurrentTop / 16}rem`;
331
332					if (hwContentOverlayElementContent.length !== 1) {
333						hwContentOverlayElement.style.left = `calc((100% / (var(--hw-content-overlay-columns)) * ${hwContentOverlayColumnIndex})`;
334					}
335
336					hwContentOverlayElementsCurrentTop += hwContentOverlayElement.offsetHeight;
337				});
338
339				hwContentOverlayColumn.hwContentOverlayColumnHeight = hwContentOverlayElementsCurrentTop;
340			});
341
342
343			// Balancing (Only for the last item)
344			const hwContentOverlayLastItemIndex = hwContentOverlayElementContent.length - 1;
345			const hwContentOverlayLastItemOriginalColumnIndex = hwContentOverlayLastItemIndex % hwContentOverlayElementsColumnCount;
346			const hwContentOverlayLastItem = hwContentOverlayElementColumns[hwContentOverlayLastItemOriginalColumnIndex].pop();
347
348			let hwContentOverlayShortestColumnIndex = 0;
349			let hwContentOverlayShortestColumnHeight = hwContentOverlayElementColumns[0].hwContentOverlayColumnHeight || 0;
350
351			for (let i = 0; i < hwContentOverlayElementColumns.length; i++) {
352				const currentColumnHeight = hwContentOverlayElementColumns[i].hwContentOverlayColumnHeight || 0;
353
354				if (currentColumnHeight < hwContentOverlayShortestColumnHeight) {
355					hwContentOverlayShortestColumnIndex = i;
356				}
357			}
358
359
360			if (hwContentOverlayElementsColumnCount > 1) {
361				let hwContentOverlayNewColumnIndex;
362
363				if (hwContentOverlayElementColumns.length === 2 && hwContentOverlayShortestColumnIndex === 0) {
364					hwContentOverlayNewColumnIndex = 1;
365				} else {
366					hwContentOverlayNewColumnIndex = hwContentOverlayElementColumns.length - hwContentOverlayShortestColumnIndex;
367				}
368
369				hwContentOverlayElementColumns[hwContentOverlayNewColumnIndex].push(hwContentOverlayLastItem);
370
371				let hwContentOverlayLastItemTop = 0;
372
373				hwContentOverlayShortestColumnHeight = 0;
374
375				hwContentOverlayElementColumns[hwContentOverlayNewColumnIndex].forEach(hwContentOverlayElement => {
376					if (hwContentOverlayElement !== hwContentOverlayLastItem) {
377						hwContentOverlayLastItemTop += hwContentOverlayElement.offsetHeight;
378					}
379
380					hwContentOverlayShortestColumnHeight += hwContentOverlayElement.offsetHeight;
381				});
382
383				hwContentOverlayElementColumns[hwContentOverlayNewColumnIndex].hwContentOverlayColumnHeight = hwContentOverlayShortestColumnHeight;
384				hwContentOverlayLastItem.style.top = `${hwContentOverlayLastItemTop / 16}rem`;
385
386				if (hwContentOverlayElementContent.length !== 1) {
387					hwContentOverlayLastItem.style.left = `calc((100% / (var(--hw-content-overlay-columns)) * ${hwContentOverlayNewColumnIndex})`;
388				} else {
389					hwContentOverlayLastItem.style.left = `calc((100% / (var(--hw-content-overlay-columns)) * ${hwContentOverlayNewColumnIndex} / 2)`;
390				}
391
392				hwContentOverlayElementColumns.forEach((hwContentElementColumn, index) => {
393					hwContentElementColumn.forEach(hwContentOverlayElement => {
394						hwContentOverlayElement.removeAttribute('data-hw-content-overlay-first-column');
395						hwContentOverlayElement.removeAttribute('data-hw-content-overlay-last-column');
396
397						if (index === 0) {
398							hwContentOverlayElement.dataset.hwContentOverlayFirstColumn = '';
399						} else if (index === hwContentOverlayElementColumns.length - 1) {
400							hwContentOverlayElement.dataset.hwContentOverlayLastColumn = '';
401						}
402					});
403				});
404				this.overlayElement.querySelector('hw-content-overlay-elements').style.height = `${(hwContentOverlayLastItemTop + hwContentOverlayLastItem.offsetHeight) / 16}rem`;
405			} else {
406				this.overlayElement.querySelector('hw-content-overlay-elements').style.height = `${(hwContentOverlayLastItem.offsetHeight) / 16}rem`;
407			}
408
409			requestAnimationFrame(() => {
410				this.overlayElement.dispatchEvent(this.#overlayMansoryEvent);
411			});
412		}
413	}
414
415	#hwGetCSSVariableValue(element, variableName) {
416
417		const computedStyle = getComputedStyle(element);
418
419		return computedStyle.getPropertyValue(variableName).trim();
420	}
421}

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.