From 2c2f843a6ec86673dee0dc01ed04aaf37f5d4742 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Sun, 30 Nov 2025 22:00:57 +0000 Subject: [PATCH] fix(consentsoftware-cookieconsent): Refine cookie consent modal shake behaviour and cleanup imports --- changelog.md | 9 ++ ts_web/00_commitinfo_data.ts | 2 +- .../elements/consentsoftware-cookieconsent.ts | 128 +++++++++++------- 3 files changed, 92 insertions(+), 47 deletions(-) diff --git a/changelog.md b/changelog.md index 2a2717d..87d0e6b 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,14 @@ # Changelog +## 2025-11-30 - 2.0.1 - fix(consentsoftware-cookieconsent) +Refine cookie consent modal shake behaviour and cleanup imports + +- Shorten shake animation delay from 2000ms to 300ms for snappier feedback +- Limit shake effect to the modal box only; stop toggling the page overlay to avoid unwanted overlay visual changes +- Remove unused 'property' import from @design.estate/dees-element +- Introduce an isMobile flag to detect mobile layout in connectedCallback +- Simplify DOM queries and use tighter type assertions when manipulating modal elements + ## 2025-11-30 - 2.0.0 - BREAKING CHANGE(elements) Migrate web components to @design.estate/dees-element, introduce shared theme colors and cssManager, and update imports/usages across ts_web. diff --git a/ts_web/00_commitinfo_data.ts b/ts_web/00_commitinfo_data.ts index 38a3cf0..74fc505 100644 --- a/ts_web/00_commitinfo_data.ts +++ b/ts_web/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@consent.software/catalog', - version: '2.0.0', + version: '2.0.1', description: 'A library of web components designed to integrate robust consent management capabilities into web applications, ensuring compliance with privacy regulations.' } diff --git a/ts_web/elements/consentsoftware-cookieconsent.ts b/ts_web/elements/consentsoftware-cookieconsent.ts index 7c6a867..0cbf896 100644 --- a/ts_web/elements/consentsoftware-cookieconsent.ts +++ b/ts_web/elements/consentsoftware-cookieconsent.ts @@ -6,7 +6,6 @@ import { html, css, type TemplateResult, - property, } from '@design.estate/dees-element'; import * as csInterfaces from '@consent.software/interfaces'; @@ -25,6 +24,7 @@ export class ConsentsoftwareCookieconsent extends DeesElement { public csWebclientInstance = new csWebclient.CsWebclient(); public csWebclientRan = false; + private isMobile = false; public static styles = [ cssManager.defaultStyles, @@ -43,7 +43,7 @@ export class ConsentsoftwareCookieconsent extends DeesElement { z-index: 1000; background: rgba(0, 0, 0, 0); backdrop-filter: blur(0px); - transition: all 0.2s ease-out; + transition: background 0.3s ease, backdrop-filter 0.3s ease; } .pageOverlay.shake { @@ -63,18 +63,42 @@ export class ConsentsoftwareCookieconsent extends DeesElement { min-width: 320px; box-sizing: border-box; overflow: hidden; - will-change: transform; - transition: all 0.2s ease-out; - transform: scale(0.96); + will-change: transform, opacity; + transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease; + transform: scale(0.92) translateY(20px); opacity: 0; } + .modalBox.visible { + transform: scale(1) translateY(0); + opacity: 1; + } + + .modalBox.hiding { + transform: scale(0.95) translateY(10px); + opacity: 0; + } + + /* Mobile: slide up from bottom */ @media (max-width: 560px) { + .pageOverlay { + align-items: flex-end; + } + .modalBox { max-width: 100%; min-width: 100%; - height: 100vh; - border-radius: 0; + border-radius: 16px 16px 0 0; + transform: translateY(100%); + opacity: 1; + } + + .modalBox.visible { + transform: translateY(0); + } + + .modalBox.hiding { + transform: translateY(100%); } } @@ -122,6 +146,7 @@ export class ConsentsoftwareCookieconsent extends DeesElement { @media (max-width: 560px) { .button-container { grid-template-columns: 1fr; + padding-bottom: max(16px, env(safe-area-inset-bottom)); } } @@ -143,6 +168,10 @@ export class ConsentsoftwareCookieconsent extends DeesElement { border-color: ${cssManager.bdTheme(colors.light.ring, colors.dark.ring)}; } + .consent-button:active { + transform: scale(0.98); + } + .consent-button:last-child { background: ${cssManager.bdTheme(colors.light.primary, colors.dark.primary)}; color: ${cssManager.bdTheme(colors.light.primaryForeground, colors.dark.primaryForeground)}; @@ -175,7 +204,6 @@ export class ConsentsoftwareCookieconsent extends DeesElement { constructor() { super(); - // Initially hide the consent banner until needed this.setAttribute('show', 'false'); } @@ -234,33 +262,21 @@ export class ConsentsoftwareCookieconsent extends DeesElement { public async connectedCallback() { await super.connectedCallback(); + // Check if mobile + this.isMobile = window.matchMedia('(max-width: 560px)').matches; + const cookieLevel = await this.csWebclientInstance.getCookieLevels(); if (!cookieLevel) { - // Show consent banner if cookie levels haven't been set yet this.setAttribute('show', 'true'); - requestAnimationFrame(async () => { - await this.updateComplete; - const pageOverlay: HTMLDivElement = this.shadowRoot?.querySelector('.pageOverlay'); - if (pageOverlay) { - // Apply subtle backdrop blur when modal appears - pageOverlay.style.background = 'rgba(0,0,0, 0.6)'; - pageOverlay.style.backdropFilter = 'blur(4px)'; - } - const modalBox: HTMLDivElement = this.shadowRoot?.querySelector('.modalBox'); - if (modalBox) { - // Animate modal box appearance - modalBox.style.transform = `scale(1)`; - modalBox.style.opacity = '1'; - } - }); + // Small delay to ensure DOM is ready, then animate in + await delayFor(50); + this.animateIn(); } else { - // Hide banner if cookie levels are already set this.setAttribute('show', 'false'); } } public async firstUpdated() { - // Run consent scripts if levels are already set const acceptedCookieLevels = await this.csWebclientInstance.getCookieLevels(); if (!this.csWebclientRan && acceptedCookieLevels) { this.csWebclientRan = true; @@ -268,29 +284,52 @@ export class ConsentsoftwareCookieconsent extends DeesElement { } } + private animateIn() { + const pageOverlay = this.shadowRoot?.querySelector('.pageOverlay') as HTMLDivElement; + const modalBox = this.shadowRoot?.querySelector('.modalBox') as HTMLDivElement; + + if (pageOverlay) { + pageOverlay.style.background = 'rgba(0, 0, 0, 0.5)'; + pageOverlay.style.backdropFilter = 'blur(4px)'; + } + + if (modalBox) { + modalBox.classList.remove('hiding'); + modalBox.classList.add('visible'); + } + } + + private async animateOut() { + const pageOverlay = this.shadowRoot?.querySelector('.pageOverlay') as HTMLDivElement; + const modalBox = this.shadowRoot?.querySelector('.modalBox') as HTMLDivElement; + + if (pageOverlay) { + pageOverlay.style.background = 'rgba(0, 0, 0, 0)'; + pageOverlay.style.backdropFilter = 'blur(0px)'; + } + + if (modalBox) { + modalBox.classList.remove('visible'); + modalBox.classList.add('hiding'); + } + + await delayFor(350); + } + /** * Handles consent button clicks, sets cookie levels, and hides the banner. */ private async handleConsentButtonClick( - event: MouseEvent, + _event: MouseEvent, levelsArg: csInterfaces.TCookieLevel[] ) { console.log(`Set level to ${levelsArg}`); - const pageOverlay: HTMLDivElement = this.shadowRoot?.querySelector('.pageOverlay'); - if (pageOverlay) { - pageOverlay.style.background = 'rgba(0,0,0, 0)'; - pageOverlay.style.backdropFilter = 'blur(0px)'; - } - const modalBox: HTMLDivElement = this.shadowRoot?.querySelector('.modalBox'); - if (modalBox) { - // Scale down and fade out modal box before hiding - modalBox.style.transform = `scale(0.95)`; - modalBox.style.opacity = '0'; - } + + await this.animateOut(); + // Save user consent preferences await this.csWebclientInstance.setCookieLevels(levelsArg); - await delayFor(300); - this.setAttribute('show', 'false'); // Hide the consent banner + this.setAttribute('show', 'false'); // Run consent scripts if (!this.csWebclientRan) { @@ -305,13 +344,10 @@ export class ConsentsoftwareCookieconsent extends DeesElement { */ private async pageOverlayClick(e: MouseEvent) { if (e.target === e.currentTarget) { - const pageOverlay: HTMLDivElement = this.shadowRoot?.querySelector('.pageOverlay'); - const modalBox: HTMLDivElement = this.shadowRoot?.querySelector('.modalBox'); - if (pageOverlay && modalBox) { - pageOverlay.classList.add('shake'); + const modalBox = this.shadowRoot?.querySelector('.modalBox') as HTMLDivElement; + if (modalBox) { modalBox.classList.add('shake'); - await delayFor(2000); - pageOverlay.classList.remove('shake'); + await delayFor(300); modalBox.classList.remove('shake'); } }