Remove shadow DOM from headless components
Headless components are behavioral wrappers meant to be styled by the consumer with regular CSS. Shadow DOM on each child created unnecessary style boundaries with no visual content to protect. Only ProboSettingsButton retains its own shadow root since it renders encapsulated styled markup. Also removes the auto-registerComponents() side effect from the main entrypoint so consumers opt in explicitly. Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -30,14 +30,6 @@ export class ProboBanner extends ProboElement {
|
||||
};
|
||||
|
||||
connectedCallback(): void {
|
||||
this.shadow.innerHTML = `
|
||||
<style>
|
||||
:host { display: block; }
|
||||
:host([hidden]) { display: none; }
|
||||
</style>
|
||||
<slot></slot>
|
||||
`;
|
||||
|
||||
this.hidden = true;
|
||||
this.root = this.findAncestor<ProboCookieBannerRoot>("probo-cookie-banner-root");
|
||||
|
||||
|
||||
@@ -21,25 +21,13 @@ export interface ConsentDraft {
|
||||
}
|
||||
|
||||
export class ProboElement extends HTMLElement {
|
||||
protected shadow: ShadowRoot;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.shadow = this.attachShadow({ mode: "open" });
|
||||
}
|
||||
|
||||
protected findAncestor<T extends HTMLElement>(tagName: string): T | null {
|
||||
let node: Node | null = this as Node;
|
||||
while (node) {
|
||||
const root = node.getRootNode();
|
||||
if (root instanceof ShadowRoot) {
|
||||
node = root.host;
|
||||
} else {
|
||||
node = (node as HTMLElement).parentElement;
|
||||
}
|
||||
if (node instanceof HTMLElement && node.tagName.toLowerCase() === tagName) {
|
||||
return node as T;
|
||||
let el: HTMLElement | null = this.parentElement;
|
||||
while (el) {
|
||||
if (el.tagName.toLowerCase() === tagName) {
|
||||
return el as T;
|
||||
}
|
||||
el = el.parentElement;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -20,10 +20,6 @@ class ProboActionButton extends ProboElement {
|
||||
protected root: ProboRootElement | null = null;
|
||||
|
||||
connectedCallback(): void {
|
||||
this.shadow.innerHTML = `
|
||||
<style>:host { display: contents; }</style>
|
||||
<slot></slot>
|
||||
`;
|
||||
this.root = this.findAncestor<ProboCookieBannerRoot>("probo-cookie-banner-root");
|
||||
this.addEventListener("click", this.handleClick);
|
||||
}
|
||||
|
||||
@@ -26,11 +26,6 @@ export class ProboCategoryList extends ProboElement {
|
||||
};
|
||||
|
||||
connectedCallback(): void {
|
||||
this.shadow.innerHTML = `
|
||||
<style>:host { display: contents; }</style>
|
||||
<slot name="items"></slot>
|
||||
`;
|
||||
|
||||
this.template = this.querySelector("template");
|
||||
if (!this.template) {
|
||||
this.warn("<probo-category-list> requires a <template> child");
|
||||
@@ -62,7 +57,6 @@ export class ProboCategoryList extends ProboElement {
|
||||
for (const cat of categories) {
|
||||
const wrapper = document.createElement("probo-category");
|
||||
wrapper.setAttribute("name", cat.name);
|
||||
wrapper.setAttribute("slot", "items");
|
||||
if (cat.required) {
|
||||
wrapper.setAttribute("required", "");
|
||||
}
|
||||
|
||||
@@ -23,13 +23,6 @@ export class ProboCategoryToggle extends ProboElement {
|
||||
private checkbox: HTMLInputElement | null = null;
|
||||
|
||||
connectedCallback(): void {
|
||||
this.shadow.innerHTML = `
|
||||
<style>
|
||||
:host { display: inline-block; }
|
||||
</style>
|
||||
<slot></slot>
|
||||
`;
|
||||
|
||||
this.root = this.findAncestor<ProboCookieBannerRoot>("probo-cookie-banner-root");
|
||||
this.category = this.findAncestor<ProboCategory>("probo-category");
|
||||
|
||||
|
||||
@@ -16,12 +16,6 @@ import type { CookieItem } from "../client";
|
||||
import { ProboElement } from "./base";
|
||||
|
||||
export class ProboCategory extends ProboElement {
|
||||
connectedCallback(): void {
|
||||
this.shadow.innerHTML = `
|
||||
<style>:host { display: contents; }</style>
|
||||
<slot></slot>
|
||||
`;
|
||||
}
|
||||
|
||||
get categoryName(): string {
|
||||
return this.getAttribute("name") ?? "";
|
||||
|
||||
@@ -50,7 +50,6 @@ export class ProboCookieBannerRoot extends ProboElement implements ProboRootElem
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
this.shadow.innerHTML = `<style>:host { display: contents; }</style><slot></slot>`;
|
||||
this.initClient();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,11 +21,6 @@ export class ProboCookieList extends ProboElement {
|
||||
private template: HTMLTemplateElement | null = null;
|
||||
|
||||
connectedCallback(): void {
|
||||
this.shadow.innerHTML = `
|
||||
<style>:host { display: contents; }</style>
|
||||
<slot name="items"></slot>
|
||||
`;
|
||||
|
||||
this.template = this.querySelector("template");
|
||||
if (!this.template) {
|
||||
this.warn("<probo-cookie-list> requires a <template> child");
|
||||
@@ -51,8 +46,6 @@ export class ProboCookieList extends ProboElement {
|
||||
|
||||
const wrapper = document.createElement("probo-cookie");
|
||||
wrapper.setAttribute("name", cookie.name);
|
||||
wrapper.setAttribute("slot", "items");
|
||||
|
||||
const clone = this.template.content.cloneNode(true) as DocumentFragment;
|
||||
this.fillSlots(clone, {
|
||||
name: cookie.name,
|
||||
@@ -77,11 +70,4 @@ export class ProboCookieList extends ProboElement {
|
||||
}
|
||||
}
|
||||
|
||||
export class ProboCookie extends ProboElement {
|
||||
connectedCallback(): void {
|
||||
this.shadow.innerHTML = `
|
||||
<style>:host { display: contents; }</style>
|
||||
<slot></slot>
|
||||
`;
|
||||
}
|
||||
}
|
||||
export class ProboCookie extends ProboElement {}
|
||||
|
||||
@@ -29,14 +29,6 @@ export class ProboPreferencePanel extends ProboElement {
|
||||
};
|
||||
|
||||
connectedCallback(): void {
|
||||
this.shadow.innerHTML = `
|
||||
<style>
|
||||
:host { display: block; }
|
||||
:host([hidden]) { display: none; }
|
||||
</style>
|
||||
<slot></slot>
|
||||
`;
|
||||
|
||||
this.hidden = true;
|
||||
this.root = this.findAncestor<ProboCookieBannerRoot>("probo-cookie-banner-root");
|
||||
|
||||
@@ -77,10 +69,6 @@ export class ProboSaveButton extends ProboElement {
|
||||
private root: ProboRootElement | null = null;
|
||||
|
||||
connectedCallback(): void {
|
||||
this.shadow.innerHTML = `
|
||||
<style>:host { display: contents; }</style>
|
||||
<slot></slot>
|
||||
`;
|
||||
this.root = this.findAncestor<ProboCookieBannerRoot>("probo-cookie-banner-root");
|
||||
this.addEventListener("click", this.handleClick);
|
||||
}
|
||||
|
||||
@@ -12,15 +12,20 @@
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { ProboElement } from "./base";
|
||||
import type { ProboRootElement } from "./base";
|
||||
import type { ProboCookieBannerRoot } from "./cookie-banner-root";
|
||||
|
||||
const COOKIE_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><circle cx="8" cy="9" r="1" fill="currentColor"/><circle cx="15" cy="11" r="1" fill="currentColor"/><circle cx="10" cy="15" r="1" fill="currentColor"/><circle cx="13" cy="7" r="1" fill="currentColor"/></svg>`;
|
||||
|
||||
export class ProboSettingsButton extends ProboElement {
|
||||
export class ProboSettingsButton extends HTMLElement {
|
||||
private shadow: ShadowRoot;
|
||||
private root: ProboRootElement | null = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.shadow = this.attachShadow({ mode: "open" });
|
||||
}
|
||||
|
||||
static get observedAttributes(): string[] {
|
||||
return ["position"];
|
||||
}
|
||||
@@ -69,7 +74,7 @@ export class ProboSettingsButton extends ProboElement {
|
||||
`;
|
||||
|
||||
this.hidden = true;
|
||||
this.root = this.findAncestor<ProboCookieBannerRoot>("probo-cookie-banner-root");
|
||||
this.root = this.findRoot();
|
||||
|
||||
if (this.root) {
|
||||
this.root.addEventListener("probo-state", this.onStateChange);
|
||||
@@ -88,6 +93,17 @@ export class ProboSettingsButton extends ProboElement {
|
||||
}
|
||||
}
|
||||
|
||||
private findRoot(): ProboCookieBannerRoot | null {
|
||||
let el: HTMLElement | null = this.parentElement;
|
||||
while (el) {
|
||||
if (el.tagName.toLowerCase() === "probo-cookie-banner-root") {
|
||||
return el as ProboCookieBannerRoot;
|
||||
}
|
||||
el = el.parentElement;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private onStateChange = (e: Event): void => {
|
||||
const { state } = (e as CustomEvent).detail;
|
||||
this.hidden = state !== "hidden";
|
||||
|
||||
@@ -54,6 +54,3 @@ export {
|
||||
} from "./errors";
|
||||
export { fetchJSON } from "./http";
|
||||
export type { RequestOptions } from "./http";
|
||||
|
||||
import { registerComponents } from "./components";
|
||||
registerComponents();
|
||||
|
||||
Reference in New Issue
Block a user