Add reopen-widget attribute and probo-settings-link element
Allow customers to replace the built-in floating settings button with their own re-open trigger (e.g. a footer link) by either placing a <probo-settings-link> element anywhere on the page or manually setting reopen-widget="custom" on the root. The settings-link auto-hides the floating button on connect via a reopen-widget attribute change event. Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -55,6 +55,7 @@ export interface ProboRootElement extends ProboElement {
|
|||||||
readonly client: CookieBannerClient;
|
readonly client: CookieBannerClient;
|
||||||
readonly bannerConfig: BannerConfig;
|
readonly bannerConfig: BannerConfig;
|
||||||
readonly state: ProboState;
|
readonly state: ProboState;
|
||||||
|
readonly reopenWidget: string;
|
||||||
readonly consentDraft: ConsentDraft;
|
readonly consentDraft: ConsentDraft;
|
||||||
setState(state: ProboState): void;
|
setState(state: ProboState): void;
|
||||||
updateDraft(category: string, value: boolean): void;
|
updateDraft(category: string, value: boolean): void;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export class ProboCookieBannerRoot extends ProboElement implements ProboRootElem
|
|||||||
private _draft: ConsentDraft = {};
|
private _draft: ConsentDraft = {};
|
||||||
|
|
||||||
static get observedAttributes(): string[] {
|
static get observedAttributes(): string[] {
|
||||||
return ["banner-id", "base-url"];
|
return ["banner-id", "base-url", "reopen-widget"];
|
||||||
}
|
}
|
||||||
|
|
||||||
get client(): CookieBannerClient {
|
get client(): CookieBannerClient {
|
||||||
@@ -41,6 +41,10 @@ export class ProboCookieBannerRoot extends ProboElement implements ProboRootElem
|
|||||||
return this._config;
|
return this._config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get reopenWidget(): string {
|
||||||
|
return this.getAttribute("reopen-widget") ?? "floating";
|
||||||
|
}
|
||||||
|
|
||||||
get state(): ProboState {
|
get state(): ProboState {
|
||||||
return this._state;
|
return this._state;
|
||||||
}
|
}
|
||||||
@@ -49,6 +53,18 @@ export class ProboCookieBannerRoot extends ProboElement implements ProboRootElem
|
|||||||
return this._draft;
|
return this._draft;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {
|
||||||
|
if (name === "reopen-widget" && oldValue !== newValue) {
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent("probo-reopen-widget", {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
detail: { value: newValue ?? "floating" },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
connectedCallback(): void {
|
connectedCallback(): void {
|
||||||
this.initClient();
|
this.initClient();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,4 +27,5 @@ export { ProboCookieBannerRoot } from "./cookie-banner-root";
|
|||||||
export { ProboCookie, ProboCookieList } from "./cookie-list";
|
export { ProboCookie, ProboCookieList } from "./cookie-list";
|
||||||
export { ProboPreferencePanel, ProboSaveButton } from "./preference-panel";
|
export { ProboPreferencePanel, ProboSaveButton } from "./preference-panel";
|
||||||
export { ProboSettingsButton } from "./settings-button";
|
export { ProboSettingsButton } from "./settings-button";
|
||||||
|
export { ProboSettingsLink } from "./settings-link";
|
||||||
export { registerComponents } from "./register";
|
export { registerComponents } from "./register";
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import { ProboCookieBannerRoot } from "./cookie-banner-root";
|
|||||||
import { ProboCookie, ProboCookieList } from "./cookie-list";
|
import { ProboCookie, ProboCookieList } from "./cookie-list";
|
||||||
import { ProboPreferencePanel, ProboSaveButton } from "./preference-panel";
|
import { ProboPreferencePanel, ProboSaveButton } from "./preference-panel";
|
||||||
import { ProboSettingsButton } from "./settings-button";
|
import { ProboSettingsButton } from "./settings-button";
|
||||||
|
import { ProboSettingsLink } from "./settings-link";
|
||||||
|
|
||||||
const elements: [string, CustomElementConstructor][] = [
|
const elements: [string, CustomElementConstructor][] = [
|
||||||
["probo-cookie-banner-root", ProboCookieBannerRoot],
|
["probo-cookie-banner-root", ProboCookieBannerRoot],
|
||||||
@@ -40,6 +41,7 @@ const elements: [string, CustomElementConstructor][] = [
|
|||||||
["probo-cookie", ProboCookie],
|
["probo-cookie", ProboCookie],
|
||||||
["probo-save-button", ProboSaveButton],
|
["probo-save-button", ProboSaveButton],
|
||||||
["probo-settings-button", ProboSettingsButton],
|
["probo-settings-button", ProboSettingsButton],
|
||||||
|
["probo-settings-link", ProboSettingsLink],
|
||||||
];
|
];
|
||||||
|
|
||||||
export function registerComponents(): void {
|
export function registerComponents(): void {
|
||||||
|
|||||||
@@ -77,7 +77,12 @@ export class ProboSettingsButton extends HTMLElement {
|
|||||||
this.root = this.findRoot();
|
this.root = this.findRoot();
|
||||||
|
|
||||||
if (this.root) {
|
if (this.root) {
|
||||||
|
if (this.root.reopenWidget === "custom") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.root.addEventListener("probo-state", this.onStateChange);
|
this.root.addEventListener("probo-state", this.onStateChange);
|
||||||
|
this.root.addEventListener("probo-reopen-widget", this.onReopenWidgetChange);
|
||||||
if (this.root.state === "hidden") {
|
if (this.root.state === "hidden") {
|
||||||
this.hidden = false;
|
this.hidden = false;
|
||||||
}
|
}
|
||||||
@@ -90,6 +95,7 @@ export class ProboSettingsButton extends HTMLElement {
|
|||||||
disconnectedCallback(): void {
|
disconnectedCallback(): void {
|
||||||
if (this.root) {
|
if (this.root) {
|
||||||
this.root.removeEventListener("probo-state", this.onStateChange);
|
this.root.removeEventListener("probo-state", this.onStateChange);
|
||||||
|
this.root.removeEventListener("probo-reopen-widget", this.onReopenWidgetChange);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,6 +115,15 @@ export class ProboSettingsButton extends HTMLElement {
|
|||||||
this.hidden = state !== "hidden";
|
this.hidden = state !== "hidden";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private onReopenWidgetChange = (e: Event): void => {
|
||||||
|
const { value } = (e as CustomEvent).detail;
|
||||||
|
if (value === "custom") {
|
||||||
|
this.hidden = true;
|
||||||
|
this.root?.removeEventListener("probo-state", this.onStateChange);
|
||||||
|
this.root?.removeEventListener("probo-reopen-widget", this.onReopenWidgetChange);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private handleClick = (): void => {
|
private handleClick = (): void => {
|
||||||
if (!this.root) return;
|
if (!this.root) return;
|
||||||
this.root.setState("panel");
|
this.root.setState("panel");
|
||||||
|
|||||||
73
packages/cookie-banner/src/components/settings-link.ts
Normal file
73
packages/cookie-banner/src/components/settings-link.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
import type { ProboCookieBannerRoot } from "./cookie-banner-root";
|
||||||
|
|
||||||
|
export class ProboSettingsLink extends HTMLElement {
|
||||||
|
private root: ProboCookieBannerRoot | null = null;
|
||||||
|
|
||||||
|
connectedCallback(): void {
|
||||||
|
this.root = this.findRoot();
|
||||||
|
|
||||||
|
if (this.root) {
|
||||||
|
this.attach(this.root);
|
||||||
|
} else {
|
||||||
|
document.addEventListener("probo-ready", this.onProboReady, { once: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.addEventListener("click", this.handleClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback(): void {
|
||||||
|
this.removeEventListener("click", this.handleClick);
|
||||||
|
document.removeEventListener("probo-ready", this.onProboReady);
|
||||||
|
this.root = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private attach(root: ProboCookieBannerRoot): void {
|
||||||
|
this.root = root;
|
||||||
|
root.setAttribute("reopen-widget", "custom");
|
||||||
|
}
|
||||||
|
|
||||||
|
private findRoot(): ProboCookieBannerRoot | null {
|
||||||
|
const direct = document.querySelector("probo-cookie-banner-root") as ProboCookieBannerRoot | null;
|
||||||
|
if (direct) return direct;
|
||||||
|
|
||||||
|
const themed = document.querySelector("probo-cookie-banner");
|
||||||
|
if (themed?.shadowRoot) {
|
||||||
|
return themed.shadowRoot.querySelector("probo-cookie-banner-root") as ProboCookieBannerRoot | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private onProboReady = (e: Event): void => {
|
||||||
|
const root = (e as CustomEvent).target as ProboCookieBannerRoot | null;
|
||||||
|
if (root?.tagName.toLowerCase() === "probo-cookie-banner-root") {
|
||||||
|
this.attach(root);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const found = this.findRoot();
|
||||||
|
if (found) {
|
||||||
|
this.attach(found);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleClick = (e: Event): void => {
|
||||||
|
if (!this.root) return;
|
||||||
|
e.preventDefault();
|
||||||
|
this.root.setState("panel");
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -29,7 +29,7 @@ export class ProboThemedBanner extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static get observedAttributes(): string[] {
|
static get observedAttributes(): string[] {
|
||||||
return ["banner-id", "base-url"];
|
return ["banner-id", "base-url", "reopen-widget"];
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback(): void {
|
connectedCallback(): void {
|
||||||
@@ -44,10 +44,12 @@ export class ProboThemedBanner extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const position = this.getAttribute("position") ?? "bottom-left";
|
const position = this.getAttribute("position") ?? "bottom-left";
|
||||||
|
const reopenWidget = this.getAttribute("reopen-widget");
|
||||||
|
const reopenAttr = reopenWidget ? ` reopen-widget="${this.esc(reopenWidget)}"` : "";
|
||||||
|
|
||||||
this.shadow.innerHTML = `
|
this.shadow.innerHTML = `
|
||||||
<style>${THEMED_STYLES}</style>
|
<style>${THEMED_STYLES}</style>
|
||||||
<probo-cookie-banner-root banner-id="${this.esc(bannerId)}" base-url="${this.esc(baseUrl)}">
|
<probo-cookie-banner-root banner-id="${this.esc(bannerId)}" base-url="${this.esc(baseUrl)}"${reopenAttr}>
|
||||||
<probo-banner>
|
<probo-banner>
|
||||||
<div class="floating" data-position="${this.esc(position)}">
|
<div class="floating" data-position="${this.esc(position)}">
|
||||||
<div class="card" role="dialog" aria-label="Cookie consent">
|
<div class="card" role="dialog" aria-label="Cookie consent">
|
||||||
|
|||||||
Reference in New Issue
Block a user