diff --git a/change/@fluentui-web-components-22e3e36e-c30c-4866-85ad-9e28421977d6.json b/change/@fluentui-web-components-22e3e36e-c30c-4866-85ad-9e28421977d6.json
new file mode 100644
index 00000000000000..a13aba16cffb7a
--- /dev/null
+++ b/change/@fluentui-web-components-22e3e36e-c30c-4866-85ad-9e28421977d6.json
@@ -0,0 +1,7 @@
+{
+ "type": "prerelease",
+ "comment": "refactor setTheme() to use adoptedStyleSheets",
+ "packageName": "@fluentui/web-components",
+ "email": "machi@microsoft.com",
+ "dependentChangeType": "patch"
+}
diff --git a/packages/web-components/.eslintrc.json b/packages/web-components/.eslintrc.json
index 0b1511be1817a2..5fd2fd28e79aae 100644
--- a/packages/web-components/.eslintrc.json
+++ b/packages/web-components/.eslintrc.json
@@ -15,6 +15,12 @@
}
},
"rules": {
+ "no-empty": [
+ "error",
+ {
+ "allowEmptyCatch": true
+ }
+ ],
"no-extra-boolean-cast": "off",
"no-prototype-builtins": "off",
"no-fallthrough": "off",
diff --git a/packages/web-components/docs/api-report.md b/packages/web-components/docs/api-report.md
index a70ded09f57b86..bbeaa020f12592 100644
--- a/packages/web-components/docs/api-report.md
+++ b/packages/web-components/docs/api-report.md
@@ -4,6 +4,8 @@
```ts
+///
+
import type { Constructable } from '@microsoft/fast-element';
import { CSSDirective } from '@microsoft/fast-element';
import { Direction } from '@microsoft/fast-web-utilities';
@@ -3152,6 +3154,8 @@ class Text_2 extends FASTElement {
block: boolean;
// (undocumented)
connectedCallback(): void;
+ // (undocumented)
+ disconnectedCallback(): void;
// @internal
elementInternals: ElementInternals;
font?: TextFont;
diff --git a/packages/web-components/package.json b/packages/web-components/package.json
index a39ef4039725cd..b594145bc865e1 100644
--- a/packages/web-components/package.json
+++ b/packages/web-components/package.json
@@ -230,9 +230,9 @@
},
"devDependencies": {
"@microsoft/fast-element": "2.0.0-beta.26",
+ "@tensile-perf/web-components": "~0.2.0",
"@types/web": "^0.0.142",
"@storybook/html": "6.5.15",
- "@tensile-perf/web-components": "~0.1.15",
"chromedriver": "^125.0.0"
},
"dependencies": {
diff --git a/packages/web-components/src/theme/set-theme.bench.ts b/packages/web-components/src/theme/set-theme.bench.ts
new file mode 100644
index 00000000000000..9edcc959fc33b9
--- /dev/null
+++ b/packages/web-components/src/theme/set-theme.bench.ts
@@ -0,0 +1,30 @@
+import { measurePerformance, type TestRenderFunction } from '@tensile-perf/web-components';
+import { teamsDarkTheme, teamsLightTheme, webDarkTheme, webLightTheme } from '@fluentui/tokens';
+
+import { setTheme } from './set-theme.js';
+
+const tests: Record = {
+ mount: ({ onComplete }) => {
+ const { startMeasure, endMeasure } = measurePerformance();
+
+ startMeasure();
+
+ // Newly set themes
+ setTheme(webLightTheme);
+ setTheme(webDarkTheme);
+ setTheme(teamsDarkTheme);
+ setTheme(teamsLightTheme);
+
+ // Cached themes
+ setTheme(webLightTheme);
+ setTheme(webDarkTheme);
+ setTheme(teamsDarkTheme);
+ setTheme(teamsLightTheme);
+
+ endMeasure();
+
+ onComplete();
+ },
+};
+
+export { tests };
diff --git a/packages/web-components/src/theme/set-theme.stories.ts b/packages/web-components/src/theme/set-theme.stories.ts
new file mode 100644
index 00000000000000..2ce35466847d2d
--- /dev/null
+++ b/packages/web-components/src/theme/set-theme.stories.ts
@@ -0,0 +1,16 @@
+import { html } from '@microsoft/fast-element';
+import { teamsDarkTheme, teamsLightTheme, webDarkTheme, webLightTheme } from '@fluentui/tokens';
+
+import { renderComponent } from '../helpers.stories.js';
+import { setTheme } from './set-theme.js';
+
+export default {
+ title: 'Theme/SetTheme',
+};
+
+export const SetTheme = renderComponent(html`
+ setTheme(webLightTheme)}">webLightTheme
+ setTheme(webDarkTheme)}">webDarkTheme
+ setTheme(teamsLightTheme)}">teamsLightTheme
+ setTheme(teamsDarkTheme)}">teamsDarkTheme
+`);
diff --git a/packages/web-components/src/theme/set-theme.ts b/packages/web-components/src/theme/set-theme.ts
index bbd2c5d969dc6e..252975dd5cf5a8 100644
--- a/packages/web-components/src/theme/set-theme.ts
+++ b/packages/web-components/src/theme/set-theme.ts
@@ -3,34 +3,52 @@ import * as tokens from './design-tokens.js';
const tokenNames = Object.keys(tokens) as (keyof Theme)[];
+const SUPPORTS_REGISTER_PROPERTY = 'registerProperty' in CSS;
+const SUPPORTS_ADOPTED_STYLE_SHEETS = 'adoptedStyleSheets' in document;
+const themeStyleSheet = new CSSStyleSheet();
+const themeStyleTextMap = new Map();
+
/**
* Sets the theme tokens on defaultNode.
* @param theme - Flat object of theme token values.
* @internal
*/
export const setTheme = (theme: Theme) => {
- for (const t of tokenNames) {
- let registered = false;
-
- if ('registerProperty' in CSS) {
- try {
- CSS.registerProperty({
- name: `--${t}`,
- inherits: true,
- initialValue: theme[t] as string,
- });
- registered = true;
- } catch {
- // Do nothing.
+ // Fallback to setting token custom properties on `` element’s `style`
+ // attribute, only checking the support of `document.adoptedStyleSheets`
+ // here because it has broader support than `CSS.registerProperty()`, which
+ // is checked later.
+ if (!SUPPORTS_ADOPTED_STYLE_SHEETS) {
+ setThemeFor(document.documentElement, theme);
+ return;
+ }
+
+ if (!themeStyleTextMap.has(theme)) {
+ const tokenDeclarations: string[] = [];
+
+ for (const t of tokenNames) {
+ if (SUPPORTS_REGISTER_PROPERTY) {
+ try {
+ CSS.registerProperty({
+ name: `--${t}`,
+ inherits: true,
+ initialValue: theme[t] as string,
+ });
+ } catch {}
}
+ tokenDeclarations.push(`--${t}: ${theme[t] as string};`);
}
- if (!registered) {
- // TODO: Find a better way to update the values. Current approach adds
- // lots of code to the `style` attribute on ``. Maybe look into
- // `document.adoptedStyleSheets`.
- setThemeFor(document.body, theme);
- }
+ themeStyleTextMap.set(theme, `html{${tokenDeclarations.join('')}}`);
+ }
+
+ if (!document.adoptedStyleSheets.includes(themeStyleSheet)) {
+ document.adoptedStyleSheets.push(themeStyleSheet);
+ } else {
+ // The very first call to `setTheme()` within a document doesn’t need to
+ // call `replaceSync()`, because `CSS.registerProperty()` above is
+ // sufficient to set the tokens.
+ themeStyleSheet.replaceSync(themeStyleTextMap.get(theme)!);
}
};
diff --git a/yarn.lock b/yarn.lock
index 5ce6a679a24aef..2e2f8468acea70 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4640,10 +4640,10 @@
dependencies:
"@tensile-perf/tools" "0.1.7"
-"@tensile-perf/web-components@~0.1.15":
- version "0.1.15"
- resolved "https://registry.yarnpkg.com/@tensile-perf/web-components/-/web-components-0.1.15.tgz#95587b220d0b09b79b339da4e4f8694772fab91a"
- integrity sha512-gawc20t7uvZyWUZOqBCA6/2Z7wEGECy2ytxe2/FMplDOuUj/CLXKoDY9mtS1dlo+6HjERuL8iP4IjdqURXo2UA==
+"@tensile-perf/web-components@~0.2.0":
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/@tensile-perf/web-components/-/web-components-0.2.0.tgz#355ac463a121656e68c0567f9c15accf19b61734"
+ integrity sha512-NEKH6d/2HrOJgW60E/oZ/O2D4vjzBv9xgSQmAz1cQnrEyQ/Odz/l3OWDwfEobcg8VHm6iODVoNAyvYAr4ESTKQ==
dependencies:
"@tensile-perf/runner" "0.4.0"
"@tensile-perf/tools" "0.1.7"