diff --git a/packages/cookie-banner/src/activation.ts b/packages/cookie-banner/src/activation.ts index 292dea02e..f9a2a20e0 100644 --- a/packages/cookie-banner/src/activation.ts +++ b/packages/cookie-banner/src/activation.ts @@ -16,6 +16,8 @@ const ATTR_CATEGORY = "data-cookie-consent"; const ATTR_SRC = "data-src"; const ATTR_HREF = "data-href"; const ATTR_ACTIVATED = "data-cookie-consent-activated"; +const ATTR_PLACEHOLDER = "data-cookie-consent-placeholder"; +const ATTR_HIDDEN = "data-cookie-consent-hidden"; const ACTIVATABLE_TAGS = new Set([ "SCRIPT", @@ -28,6 +30,132 @@ const ACTIVATABLE_TAGS = new Set([ "LINK", ]); +const VISUAL_TAGS = new Set([ + "IFRAME", + "IMG", + "VIDEO", + "AUDIO", + "EMBED", + "OBJECT", +]); + +const LOCK_ICON = ``; + +const PLACEHOLDER_STYLES = ` +[${ATTR_HIDDEN}] { + display: none !important; +} +[${ATTR_PLACEHOLDER}] { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + gap: 12px; + padding: 24px; + background: var(--probo-bg, #ffffff); + color: var(--probo-text-secondary, #555555); + border: 1px dashed var(--probo-border, #e0e0e0); + border-radius: var(--probo-radius, 12px); + font-family: var(--probo-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif); + font-size: var(--probo-font-size, 14px); + line-height: 1.5; + text-align: center; + box-sizing: border-box; + min-height: 120px; +} +[${ATTR_PLACEHOLDER}] .probo-ph-icon { + color: var(--probo-text-secondary, #555555); +} +[${ATTR_PLACEHOLDER}] .probo-ph-text { + margin: 0; +} +[${ATTR_PLACEHOLDER}] .probo-ph-text strong { + font-weight: 600; +} +[${ATTR_PLACEHOLDER}] .probo-ph-link { + background: none; + border: none; + color: var(--probo-accent, #1a1a1a); + text-decoration: underline; + cursor: pointer; + font-family: inherit; + font-size: inherit; + padding: 0; +} +[${ATTR_PLACEHOLDER}] .probo-ph-link:hover { + opacity: 0.8; +} +`; + +let stylesInjected = false; + +function injectPlaceholderStyles(): void { + if (stylesInjected) return; + stylesInjected = true; + + const style = document.createElement("style"); + style.id = "probo-placeholder-styles"; + style.textContent = PLACEHOLDER_STYLES; + document.head.appendChild(style); +} + +function escapeHtml(s: string): string { + const el = document.createElement("span"); + el.textContent = s; + return el.innerHTML; +} + +function createPlaceholder( + el: Element, + category: string, + label?: string, +): void { + if (el.hasAttribute(ATTR_HIDDEN)) return; + + injectPlaceholderStyles(); + + const displayLabel = label || category; + + const placeholder = document.createElement("div"); + placeholder.setAttribute(ATTR_PLACEHOLDER, category); + + const htmlEl = el as HTMLElement; + + if (htmlEl.className) placeholder.className = htmlEl.className; + + const w = el.getAttribute("width"); + if (w) placeholder.style.width = /^\d+$/.test(w) ? w + "px" : w; + + const h = el.getAttribute("height"); + if (h) placeholder.style.height = /^\d+$/.test(h) ? h + "px" : h; + + if (htmlEl.style?.cssText) placeholder.style.cssText += htmlEl.style.cssText; + + placeholder.innerHTML = [ + ``, + `
This content requires ${escapeHtml(displayLabel)} cookies.
`, + ``, + ].join(""); + + placeholder.querySelector(".probo-ph-link")!.addEventListener("click", () => { + document.dispatchEvent(new CustomEvent("probo-open-preferences")); + }); + + el.setAttribute(ATTR_HIDDEN, ""); + el.parentNode!.insertBefore(placeholder, el.nextSibling); +} + +function removePlaceholder(el: Element): void { + if (!el.hasAttribute(ATTR_HIDDEN)) return; + + const next = el.nextElementSibling; + if (next?.hasAttribute(ATTR_PLACEHOLDER)) { + next.remove(); + } + + el.removeAttribute(ATTR_HIDDEN); +} + function activateScript(el: HTMLScriptElement): void { const replacement = document.createElement("script"); const originalType = el.getAttribute("data-type"); @@ -61,6 +189,8 @@ function activateScript(el: HTMLScriptElement): void { } function activateElement(el: Element): void { + removePlaceholder(el); + const category = el.getAttribute(ATTR_CATEGORY); const src = el.getAttribute(ATTR_SRC); @@ -140,7 +270,7 @@ function deactivateScript(el: HTMLScriptElement): void { el.parentNode!.replaceChild(replacement, el); } -function deactivateElement(el: Element): void { +function deactivateElement(el: Element, label?: string): void { const category = el.getAttribute(ATTR_ACTIVATED); const src = el.getAttribute("src"); @@ -159,6 +289,10 @@ function deactivateElement(el: Element): void { el.setAttribute(ATTR_CATEGORY, category); } el.removeAttribute(ATTR_ACTIVATED); + + if (category && VISUAL_TAGS.has(el.tagName)) { + createPlaceholder(el, category, label); + } } function removeCookies(names: string[]): void { @@ -175,6 +309,7 @@ function removeCookies(names: string[]): void { export function deactivateElements( consentData: Record