Files
Wegwichtel/public/js/legal-ui.js
T
MrRobot cbe50b1a28
Sonarqube Scanner / Build and analyze (push) Canceled after 0s
fix missing info dialog selectors
2026-08-29 16:22:32 +02:00

221 lines
6.8 KiB
JavaScript

(function (ns) {
'use strict';
const DROPDOWN_ANIMATION_MS = 180;
const pageTitles = Object.freeze({
privacy: 'Datenschutzerklärung',
imprint: 'Impressum',
technology: 'Cookies und Technik'
});
const dialog = document.getElementById('info-dialog');
const title = document.getElementById('info-dialog-title')
?? dialog?.querySelector('h1, h2, .info-dialog-title')
?? null;
const dropdown = document.getElementById('info-dropdown')
?? document.querySelector('.info-dropdown')
?? null;
const infoButton = document.getElementById('info-button')
?? document.querySelector('[aria-controls="info-dropdown"], .info-button')
?? null;
const closeButton = document.getElementById('info-close')
?? dialog?.querySelector('[data-info-close], .info-close, .info-dialog-close, button[aria-label="Dialog schließen"], button[aria-label="Schließen"]')
?? null;
let dropdownOpen = false;
let dropdownCloseTimer = null;
function isActivation(event) {
if (event.type === 'keydown') return event.key === 'Enter' || event.key === ' ';
if (event.type !== 'pointerup') return false;
if (event.isPrimary === false) return false;
return event.pointerType !== 'mouse' || event.button === 0;
}
function activate(element, handler) {
if (!element || typeof element.addEventListener !== 'function') return;
element.addEventListener('pointerup', event => {
if (!isActivation(event)) return;
event.preventDefault();
event.stopPropagation();
handler(event);
});
element.addEventListener('keydown', event => {
if (!isActivation(event)) return;
event.preventDefault();
event.stopPropagation();
handler(event);
});
}
function setClass(element, className, enabled) {
if (!element || !element.classList) return;
element.classList.toggle(className, enabled);
}
function runNextFrame(callback) {
if (typeof globalThis.requestAnimationFrame === 'function') {
globalThis.requestAnimationFrame(callback);
return;
}
callback();
}
function schedule(callback, timeout) {
if (typeof globalThis.setTimeout === 'function') return globalThis.setTimeout(callback, timeout);
callback();
return null;
}
function cancelSchedule(timer) {
if (timer === null || typeof globalThis.clearTimeout !== 'function') return;
globalThis.clearTimeout(timer);
}
function setDropdownOpen(open) {
if (!dropdown || !infoButton) return;
dropdownOpen = open;
infoButton.setAttribute('aria-expanded', open ? 'true' : 'false');
cancelSchedule(dropdownCloseTimer);
dropdownCloseTimer = null;
if (open) {
dropdown.hidden = false;
runNextFrame(() => setClass(dropdown, 'is-open', true));
return;
}
setClass(dropdown, 'is-open', false);
dropdownCloseTimer = schedule(() => {
if (dropdownOpen) return;
dropdown.hidden = true;
}, DROPDOWN_ANIMATION_MS);
}
function toggleDropdown() {
setDropdownOpen(!dropdownOpen);
}
function elementContains(element, target) {
if (!element || !target) return false;
if (element === target) return true;
return typeof element.contains === 'function' && element.contains(target);
}
function isInsideInfoControl(target) {
return elementContains(infoButton, target) || elementContains(dropdown, target);
}
function isProgressStorageEnabled() {
if (!ns.PrivacyStorage || typeof ns.PrivacyStorage.isProgressStorageEnabled !== 'function') return false;
return ns.PrivacyStorage.isProgressStorageEnabled();
}
function setProgressStorageEnabled(enabled) {
if (!ns.PrivacyStorage || typeof ns.PrivacyStorage.setProgressStorageEnabled !== 'function') return false;
return ns.PrivacyStorage.setProgressStorageEnabled(enabled);
}
function syncStorageToggles() {
const enabled = isProgressStorageEnabled();
document.querySelectorAll('.progress-storage-toggle').forEach(toggle => {
toggle.checked = enabled;
});
document.querySelectorAll('.cookie-toggle').forEach(toggle => {
toggle.checked = false;
});
}
function showPage(name) {
const page = document.querySelector(`[data-info-page="${name}"]`);
if (!page) return;
if (title) title.textContent = pageTitles[name] ?? 'Informationen';
document.querySelectorAll('[data-info-page]').forEach(item => {
item.hidden = item !== page;
});
const content = document.querySelector('.info-dialog-content');
if (content) content.scrollTop = 0;
}
function focusSafely(element) {
if (!element || typeof element.focus !== 'function') return;
element.focus({ preventScroll: true });
}
function openDialog(name = 'privacy') {
if (!dialog) return;
const pageName = Object.prototype.hasOwnProperty.call(pageTitles, name) ? name : 'privacy';
setDropdownOpen(false);
showPage(pageName);
syncStorageToggles();
if (!dialog.open) {
if (typeof dialog.showModal === 'function') dialog.showModal();
else dialog.setAttribute('open', '');
}
focusSafely(closeButton ?? dialog);
}
function closeDialog() {
if (dialog) {
if (typeof dialog.close === 'function') dialog.close();
else dialog.removeAttribute('open');
}
setDropdownOpen(false);
focusSafely(infoButton);
}
function bindStorageToggles() {
document.querySelectorAll('.progress-storage-toggle').forEach(toggle => {
toggle.addEventListener('change', event => {
const enabled = setProgressStorageEnabled(event.currentTarget.checked);
syncStorageToggles();
document.dispatchEvent(new CustomEvent('wegwichtel:progress-storage-changed', {
detail: { enabled }
}));
});
});
}
activate(infoButton, toggleDropdown);
activate(closeButton, closeDialog);
document.querySelectorAll('[data-info-target]').forEach(button => {
activate(button, () => openDialog(button.dataset.infoTarget));
});
document.addEventListener('pointerup', event => {
if (!dropdownOpen || isInsideInfoControl(event.target)) return;
setDropdownOpen(false);
});
document.addEventListener('keydown', event => {
if (!dropdownOpen || event.key !== 'Escape' || dialog?.open) return;
event.preventDefault();
setDropdownOpen(false);
focusSafely(infoButton);
});
if (dialog) {
dialog.addEventListener('pointerup', event => {
if (event.target !== dialog || !isActivation(event)) return;
event.preventDefault();
event.stopPropagation();
closeDialog();
});
dialog.addEventListener('cancel', event => {
event.preventDefault();
closeDialog();
});
}
bindStorageToggles();
syncStorageToggles();
ns.LegalUi = Object.freeze({
open: openDialog,
close: closeDialog,
toggleMenu: toggleDropdown,
closeMenu: () => setDropdownOpen(false),
syncStorageToggles
});
}(globalThis.Wegwichtel ??= {}));