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 bannerConfig: BannerConfig;
|
||||
readonly state: ProboState;
|
||||
readonly reopenWidget: string;
|
||||
readonly consentDraft: ConsentDraft;
|
||||
setState(state: ProboState): void;
|
||||
updateDraft(category: string, value: boolean): void;
|
||||
|
||||
@@ -24,7 +24,7 @@ export class ProboCookieBannerRoot extends ProboElement implements ProboRootElem
|
||||
private _draft: ConsentDraft = {};
|
||||
|
||||
static get observedAttributes(): string[] {
|
||||
return ["banner-id", "base-url"];
|
||||
return ["banner-id", "base-url", "reopen-widget"];
|
||||
}
|
||||
|
||||
get client(): CookieBannerClient {
|
||||
@@ -41,6 +41,10 @@ export class ProboCookieBannerRoot extends ProboElement implements ProboRootElem
|
||||
return this._config;
|
||||
}
|
||||
|
||||
get reopenWidget(): string {
|
||||
return this.getAttribute("reopen-widget") ?? "floating";
|
||||
}
|
||||
|
||||
get state(): ProboState {
|
||||
return this._state;
|
||||
}
|
||||
@@ -49,6 +53,18 @@ export class ProboCookieBannerRoot extends ProboElement implements ProboRootElem
|
||||
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 {
|
||||
this.initClient();
|
||||
}
|
||||
|
||||
@@ -27,4 +27,5 @@ export { ProboCookieBannerRoot } from "./cookie-banner-root";
|
||||
export { ProboCookie, ProboCookieList } from "./cookie-list";
|
||||
export { ProboPreferencePanel, ProboSaveButton } from "./preference-panel";
|
||||
export { ProboSettingsButton } from "./settings-button";
|
||||
export { ProboSettingsLink } from "./settings-link";
|
||||
export { registerComponents } from "./register";
|
||||
|
||||
@@ -25,6 +25,7 @@ import { ProboCookieBannerRoot } from "./cookie-banner-root";
|
||||
import { ProboCookie, ProboCookieList } from "./cookie-list";
|
||||
import { ProboPreferencePanel, ProboSaveButton } from "./preference-panel";
|
||||
import { ProboSettingsButton } from "./settings-button";
|
||||
import { ProboSettingsLink } from "./settings-link";
|
||||
|
||||
const elements: [string, CustomElementConstructor][] = [
|
||||
["probo-cookie-banner-root", ProboCookieBannerRoot],
|
||||
@@ -40,6 +41,7 @@ const elements: [string, CustomElementConstructor][] = [
|
||||
["probo-cookie", ProboCookie],
|
||||
["probo-save-button", ProboSaveButton],
|
||||
["probo-settings-button", ProboSettingsButton],
|
||||
["probo-settings-link", ProboSettingsLink],
|
||||
];
|
||||
|
||||
export function registerComponents(): void {
|
||||
|
||||
@@ -77,7 +77,12 @@ export class ProboSettingsButton extends HTMLElement {
|
||||
this.root = this.findRoot();
|
||||
|
||||
if (this.root) {
|
||||
if (this.root.reopenWidget === "custom") {
|
||||
return;
|
||||
}
|
||||
|
||||
this.root.addEventListener("probo-state", this.onStateChange);
|
||||
this.root.addEventListener("probo-reopen-widget", this.onReopenWidgetChange);
|
||||
if (this.root.state === "hidden") {
|
||||
this.hidden = false;
|
||||
}
|
||||
@@ -90,6 +95,7 @@ export class ProboSettingsButton extends HTMLElement {
|
||||
disconnectedCallback(): void {
|
||||
if (this.root) {
|
||||
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";
|
||||
};
|
||||
|
||||
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 => {
|
||||
if (!this.root) return;
|
||||
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[] {
|
||||
return ["banner-id", "base-url"];
|
||||
return ["banner-id", "base-url", "reopen-widget"];
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
@@ -44,10 +44,12 @@ export class ProboThemedBanner extends HTMLElement {
|
||||
}
|
||||
|
||||
const position = this.getAttribute("position") ?? "bottom-left";
|
||||
const reopenWidget = this.getAttribute("reopen-widget");
|
||||
const reopenAttr = reopenWidget ? ` reopen-widget="${this.esc(reopenWidget)}"` : "";
|
||||
|
||||
this.shadow.innerHTML = `
|
||||
<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>
|
||||
<div class="floating" data-position="${this.esc(position)}">
|
||||
<div class="card" role="dialog" aria-label="Cookie consent">
|
||||
|
||||
Reference in New Issue
Block a user