/** Adds or removes a custom state, which CSS matches as `:state(name)`. */ export function setState(internals: ElementInternals, name: string, on: boolean): void { if (on) internals.states.add(name); else internals.states.delete(name); } /** * Defines a custom element unless a hot reload already did. Elements defined here keep their shadow * styles when moved into another document (a torn-off panel's popup): see `adopt`. */ export function define(name: string, constructor: CustomElementConstructor): void { if (customElements.get(name)) return; const own = constructor.prototype.adoptedCallback as ((...args: unknown[]) => void) | undefined; constructor.prototype.adoptedCallback = function (this: HTMLElement, ...args: unknown[]) { if (this.shadowRoot) restyle(this.shadowRoot); own?.apply(this, args); }; customElements.define(name, constructor); } /** Builds a constructable stylesheet for a shadow root. */ export function sheet(css: string): CSSStyleSheet { const result = new CSSStyleSheet(); result.replaceSync(css); return result; } const adopted = new WeakMap(); const copies = new WeakMap>(); /** * Sets a shadow root's stylesheets. Use it instead of assigning `adoptedStyleSheets`: every engine * empties that list when a node moves to another document, and refuses a sheet constructed in a * different one, so `define`'s `adoptedCallback` re-adopts these as copies built in the new document. */ export function adopt(root: ShadowRoot, ...sheets: CSSStyleSheet[]): void { adopted.set(root, sheets); restyle(root); } function restyle(root: ShadowRoot): void { const sheets = adopted.get(root); if (sheets) root.adoptedStyleSheets = sheets.map((original) => inDocument(original, root.ownerDocument)); } /** `original`, or a copy constructed in `doc` when `original` belongs to another document. */ function inDocument(original: CSSStyleSheet, doc: Document): CSSStyleSheet { const view = doc.defaultView; if (!view || doc === document) return original; let byDocument = copies.get(original); if (!byDocument) copies.set(original, (byDocument = new WeakMap())); let copy = byDocument.get(doc); if (!copy) { copy = new view.CSSStyleSheet(); copy.replaceSync([...original.cssRules].map((rule) => rule.cssText).join("\n")); byDocument.set(doc, copy); } return copy; }