+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Maurisscelerisque varius ornare. Etiam convallis sollicitudin
+ scelerisque.Maecenas in velit vehicula, aliquet orci et, consequat
+ purus. Donec egetsodales lectus, vel sollicitudin ligula.
+ Suspendisse volutpat auctor diam,vel mattis lorem venenatis in.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Heading
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Maurisscelerisque varius ornare. Etiam convallis sollicitudin
+ scelerisque.Maecenas in velit vehicula, aliquet orci et, consequat
+ purus. Donec egetsodales lectus, vel sollicitudin ligula.
+ Suspendisse volutpat auctor diam,vel mattis lorem venenatis in.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Heading
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Maurisscelerisque varius ornare. Etiam convallis sollicitudin
+ scelerisque.Maecenas in velit vehicula, aliquet orci et, consequat
+ purus. Donec egetsodales lectus, vel sollicitudin ligula.
+ Suspendisse volutpat auctor diam,vel mattis lorem venenatis in.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Heading
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Maurisscelerisque varius ornare. Etiam convallis sollicitudin
+ scelerisque.Maecenas in velit vehicula, aliquet orci et, consequat
+ purus. Donec egetsodales lectus, vel sollicitudin ligula.
+ Suspendisse volutpat auctor diam,vel mattis lorem venenatis in.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Heading
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Maurisscelerisque varius ornare. Etiam convallis sollicitudin
+ scelerisque.Maecenas in velit vehicula, aliquet orci et, consequat
+ purus. Donec egetsodales lectus, vel sollicitudin ligula.
+ Suspendisse volutpat auctor diam,vel mattis lorem venenatis in.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Heading
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Maurisscelerisque varius ornare. Etiam convallis sollicitudin
+ scelerisque.Maecenas in velit vehicula, aliquet orci et, consequat
+ purus. Donec egetsodales lectus, vel sollicitudin ligula.
+ Suspendisse volutpat auctor diam,vel mattis lorem venenatis in.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Heading
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Maurisscelerisque varius ornare. Etiam convallis sollicitudin
+ scelerisque.Maecenas in velit vehicula, aliquet orci et, consequat
+ purus. Donec egetsodales lectus, vel sollicitudin ligula.
+ Suspendisse volutpat auctor diam,vel mattis lorem venenatis in.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Heading
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Maurisscelerisque varius ornare. Etiam convallis sollicitudin
+ scelerisque.Maecenas in velit vehicula, aliquet orci et, consequat
+ purus. Donec egetsodales lectus, vel sollicitudin ligula.
+ Suspendisse volutpat auctor diam,vel mattis lorem venenatis in.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Heading
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Maurisscelerisque varius ornare. Etiam convallis sollicitudin
+ scelerisque.Maecenas in velit vehicula, aliquet orci et, consequat
+ purus. Donec egetsodales lectus, vel sollicitudin ligula.
+ Suspendisse volutpat auctor diam,vel mattis lorem venenatis in.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Heading
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Maurisscelerisque varius ornare. Etiam convallis sollicitudin
+ scelerisque.Maecenas in velit vehicula, aliquet orci et, consequat
+ purus. Donec egetsodales lectus, vel sollicitudin ligula.
+ Suspendisse volutpat auctor diam,vel mattis lorem venenatis in.
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/web-components/fast-ssr/src/fast-style/index.ts b/packages/web-components/fast-ssr/src/fast-style/index.ts
new file mode 100644
index 00000000000..cb480301651
--- /dev/null
+++ b/packages/web-components/fast-ssr/src/fast-style/index.ts
@@ -0,0 +1,79 @@
+interface StyleCache {
+ [key: string]: CSSStyleSheet | string;
+}
+
+/**
+ * The FASTStyle web component that takes attributes:
+ * - css - a string version of the CSS to be applied to the parent web component, this can be omitted if the data-style-id has been used in the DOM as the cached value will be fetched
+ * - data-style-id - a dataset attribute used as an identifier for the CSS attr string
+ */
+export default class FASTStyle extends HTMLElement {
+ private static cache: StyleCache = {};
+ private static hashIdDataSetName: string = "data-style-id";
+ private static supportsAdoptedStyleSheets: boolean =
+ Array.isArray((document as any).adoptedStyleSheets) &&
+ "replace" in CSSStyleSheet.prototype;
+
+ /**
+ * @internal
+ */
+ public connectedCallback(): void {
+ const hashId: string = this.getAttribute(FASTStyle.hashIdDataSetName) as string;
+ const css: string = this.getAttribute("css") as string;
+ this.registerStyles(hashId, css);
+ }
+
+ /**
+ * Register styles if they are not part of the cache and attach them
+ */
+ private registerStyles = (hashId: string, css: string): void => {
+ if (FASTStyle.supportsAdoptedStyleSheets) {
+ if (!(hashId in FASTStyle.cache)) {
+ this.memoizeAdoptedStylesheetStyles(hashId, css);
+ }
+ this.attachAdoptedStylesheetStyles(this.parentNode as ShadowRoot, hashId);
+ } else {
+ if (!(hashId in FASTStyle.cache)) {
+ this.memoizeStyleElementStyles(hashId, css);
+ }
+ this.attachStyleElementStyles(this.parentNode as ShadowRoot, hashId);
+ }
+ };
+
+ /**
+ * Memoize CSSStyleSheets
+ */
+ private memoizeAdoptedStylesheetStyles(hashId: string, css: string) {
+ const sheet = new CSSStyleSheet();
+ (sheet as any).replaceSync(css);
+ FASTStyle.cache[hashId] = sheet;
+ }
+
+ /**
+ * Attach CSSStyleSheets
+ */
+ private attachAdoptedStylesheetStyles(shadowRoot: ShadowRoot, hashId: string) {
+ (shadowRoot as any).adoptedStyleSheets = [
+ ...(shadowRoot as any).adoptedStyleSheets!,
+ FASTStyle.cache[hashId] as CSSStyleSheet,
+ ];
+ }
+
+ /**
+ * Memoize css strings
+ */
+ private memoizeStyleElementStyles(hashId: string, css: string) {
+ FASTStyle.cache[hashId] = css;
+ }
+
+ /**
+ * Attach style elements
+ */
+ private attachStyleElementStyles(shadowRoot: ShadowRoot, hashId: string) {
+ const element = document.createElement("style");
+ element.innerHTML = FASTStyle.cache[hashId] as string;
+ shadowRoot.append(element);
+ }
+}
+
+customElements.define("fast-style", FASTStyle);
diff --git a/packages/web-components/fast-ssr/test/fast-style.spec.ts b/packages/web-components/fast-ssr/test/fast-style.spec.ts
new file mode 100644
index 00000000000..08f74b18e39
--- /dev/null
+++ b/packages/web-components/fast-ssr/test/fast-style.spec.ts
@@ -0,0 +1,62 @@
+import { expect, test } from "@playwright/test"
+
+test("Check that the first element has styles assigned", async ({ page }) => {
+ await page.goto("/fast-style");
+
+ const cards = page.locator("fast-card");
+ const styles = await cards.evaluateAll((cardList) => {
+ return cardList.map((card) => {
+ return window.getComputedStyle(card, null).getPropertyValue("background-color");
+ });
+ });
+
+ expect(styles[0]).toEqual("rgb(26, 26, 26)");
+});
+test("Check that the nested element in the first element has styles assigned", async ({ page }) => {
+ await page.goto("/fast-style");
+
+ const cards = page.locator("fast-card");
+ const styles = await cards.evaluateAll((cardList) => {
+ return cardList.map((card) => {
+ return window.getComputedStyle(
+ (card.shadowRoot?.querySelector("fast-button") as Element), null
+ ).getPropertyValue("background-color");
+ });
+ });
+
+ expect(styles[0]).toEqual("rgb(43, 43, 43)");
+});
+test("Check that all elements have styles assigned", async ({ page }) => {
+ await page.goto("/fast-style");
+
+ const cards = page.locator("fast-card");
+ const styles = await cards.evaluateAll((cardList) => {
+ return cardList.map((card) => {
+ return window.getComputedStyle(card, null).getPropertyValue("background-color");
+ });
+ });
+
+ expect(styles).toHaveLength(10);
+
+ styles.forEach((style) => {
+ expect(style).toEqual("rgb(26, 26, 26)");
+ });
+});
+test("Check that all nested elements have styles assigned", async ({ page}) => {
+ await page.goto("/fast-style");
+
+ const cards = page.locator("fast-card");
+ const styles = await cards.evaluateAll((cardList) => {
+ return cardList.map((card) => {
+ return window.getComputedStyle(
+ (card.shadowRoot?.querySelector("fast-button") as Element), null
+ ).getPropertyValue("background-color");
+ });
+ });
+
+ expect(styles).toHaveLength(10);
+
+ styles.forEach((style) => {
+ expect(style).toEqual("rgb(43, 43, 43)");
+ });
+});