-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: simplify adopted stylesheets controller
- Loading branch information
Showing
1 changed file
with
56 additions
and
65 deletions.
There are no files selected for viewing
121 changes: 56 additions & 65 deletions
121
packages/controllers/adopted-stylesheets/src/adopted-stylesheets.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,91 +1,82 @@ | ||
import { ReactiveController, ReactiveControllerHost } from 'lit'; | ||
|
||
/** | ||
* `AdoptedStylesheets` is a class that implements the `ReactiveController` interface from the `lit` library. | ||
* This class is used to manage CSS stylesheets that are adopted into the document or a shadow root. | ||
* | ||
* @property {CSSStyleSheet} adoptedSheet - The CSSStyleSheet object that is adopted into the document or a shadow root. | ||
* @property {Document | ShadowRoot} root - The root where the stylesheet will be adopted. | ||
* A controller for managing adopted stylesheets in a Lit element. | ||
* This allows for styles to be dynamically applied to the component's | ||
* light DOM or shadow DOM. | ||
*/ | ||
export class AdoptedStylesheets implements ReactiveController { | ||
/** | ||
* A static map that stores CSSStyleSheet objects by their CSS text. | ||
* This allows for reuse of CSSStyleSheet objects across multiple instances of the class. | ||
* @type {Map<string, CSSStyleSheet>} | ||
*/ | ||
private static styleSheetMap = new Map<string, CSSStyleSheet>(); | ||
|
||
/** | ||
* The CSSStyleSheet object that is adopted into the document or a shadow root. | ||
* @type {CSSStyleSheet} | ||
*/ | ||
private adoptedSheet: CSSStyleSheet; | ||
export class AdoptedStyleSheets implements ReactiveController { | ||
// The host element that the controller is associated with. | ||
private host: ReactiveControllerHost & HTMLElement; | ||
// An object containing the CSS to be applied globally or encapsulated within the shadow DOM. | ||
private css: { globalCSS?: string; encapsulatedCSS?: string }; | ||
|
||
/** | ||
* The root where the stylesheet will be adopted. | ||
* This can be either the document or a shadow root. | ||
* @type {Document | ShadowRoot} | ||
*/ | ||
private root: Document | ShadowRoot; | ||
|
||
/** | ||
* The host that this controller is associated with. | ||
* @type {ReactiveControllerHost} | ||
*/ | ||
private host: ReactiveControllerHost; | ||
// A set to track the stylesheets applied to the light DOM. | ||
private static appliedLightDomStylesheets: Set<string> = new Set(); | ||
|
||
/** | ||
* The constructor for the `AdoptedStylesheets` class. | ||
* | ||
* @param {ReactiveControllerHost} host - The host that this controller is associated with. | ||
* @param {string} cssText - A string that contains the CSS styles to be adopted. | ||
* @param {Document | ShadowRoot} root - The root where the stylesheet will be adopted. | ||
* Constructs an instance of the AdoptedStyleSheets controller. | ||
* @param host The host element that the controller will be associated with. | ||
* @param css An object containing optional global and encapsulated CSS strings. | ||
*/ | ||
constructor( | ||
host: ReactiveControllerHost, | ||
cssText: string, | ||
root: Document | ShadowRoot = document | ||
host: ReactiveControllerHost & HTMLElement, | ||
css: { | ||
globalCSS?: string; | ||
encapsulatedCSS?: string; | ||
} = {} | ||
) { | ||
this.host = host; | ||
this.host.addController(this); | ||
this.root = root; | ||
this.css = css; | ||
this.host.addController(this); // Register this instance as a controller for the host element. | ||
} | ||
|
||
if (!AdoptedStylesheets.styleSheetMap.has(cssText)) { | ||
const newSheet = new CSSStyleSheet(); | ||
newSheet.replace(cssText).catch(error => { | ||
console.error('Failed to replace CSS text:', error); | ||
}); | ||
AdoptedStylesheets.styleSheetMap.set(cssText, newSheet); | ||
/** | ||
* Applies the given CSS text to the specified target (Document or ShadowRoot). | ||
* @param cssText The CSS text to apply. | ||
* @param target The target where the CSS should be applied. | ||
*/ | ||
private applyCssToDom(cssText: string, target: Document | ShadowRoot) { | ||
if (target instanceof Document) { | ||
const store = AdoptedStyleSheets.appliedLightDomStylesheets; | ||
|
||
if (store.has(cssText)) { | ||
// If the stylesheet has already been applied, no further action is required. | ||
return; | ||
} | ||
store.add(cssText); // Store the stylesheet with the provided key. | ||
} | ||
this.adoptedSheet = | ||
AdoptedStylesheets.styleSheetMap.get(cssText) || new CSSStyleSheet(); | ||
|
||
// Create a new stylesheet and replace its contents with the provided CSS text. | ||
const sheet = new CSSStyleSheet(); | ||
sheet.replaceSync(cssText); | ||
|
||
// Apply the stylesheet to the target's adoptedStyleSheets. | ||
target.adoptedStyleSheets = [...target.adoptedStyleSheets, sheet]; | ||
} | ||
|
||
/** | ||
* The `hostConnected` method is called when the host element is connected to the DOM. | ||
* This method adopts the CSSStyleSheet object into the root's adopted stylesheets if it's not already included. | ||
* Lifecycle callback called when the host element is connected to the document's DOM. | ||
* Applies global and encapsulated CSS to the respective DOM targets. | ||
*/ | ||
hostConnected() { | ||
if ( | ||
this.root && | ||
!this.root.adoptedStyleSheets.includes(this.adoptedSheet) | ||
) { | ||
this.root.adoptedStyleSheets = [ | ||
...this.root.adoptedStyleSheets, | ||
this.adoptedSheet, | ||
]; | ||
if (this.css.globalCSS) { | ||
this.applyCssToDom(this.css.globalCSS, document); // Apply global CSS to the document if it exists. | ||
} | ||
|
||
// Apply encapsulated CSS to the host's shadow root if it exists. | ||
if (this.css.encapsulatedCSS && this.host.shadowRoot) { | ||
this.applyCssToDom(this.css.encapsulatedCSS, this.host.shadowRoot); // Apply encapsulated CSS to the host's shadow root if it exists. | ||
} | ||
} | ||
|
||
/** | ||
* The `hostDisconnected` method is called when the host element is disconnected from the DOM. | ||
* This method removes the CSSStyleSheet object from the root's adopted stylesheets if it's included. | ||
* Lifecycle callback called when the host element is disconnected from the document's DOM. | ||
* Note: When a component with a Shadow DOM is disconnected from the document's DOM, the Shadow DOM is also removed along with the component. | ||
* However, for Light DOM styles, they are not removed here because other instances of the component | ||
* might still be present on the page and require these styles. | ||
*/ | ||
hostDisconnected() { | ||
if (this.root && this.root.adoptedStyleSheets.includes(this.adoptedSheet)) { | ||
this.root.adoptedStyleSheets = this.root.adoptedStyleSheets.filter( | ||
sheet => sheet !== this.adoptedSheet | ||
); | ||
} | ||
// No action is taken when the host is disconnected. | ||
} | ||
} |