From 557e291a8715369631fb3e41c8ffbab3653f4265 Mon Sep 17 00:00:00 2001 From: John Kreitlow <863023+radium-v@users.noreply.github.com> Date: Fri, 22 May 2026 11:59:28 -0700 Subject: [PATCH 01/44] feat(web-components): update dependencies and add SSR build scripts --- package.json | 5 +++-- packages/web-components/package.json | 16 +++++++++++++++- yarn.lock | 19 +++++++++++-------- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 54fd34fa3aee17..faa30d5a60204f 100644 --- a/package.json +++ b/package.json @@ -74,8 +74,9 @@ "@microsoft/eslint-plugin-sdl": "1.0.1", "@microsoft/fast-build": "0.6.0", "@microsoft/fast-element": "2.10.4", - "@microsoft/fast-test-harness": "0.1.0", - "@microsoft/focusgroup-polyfill": "^1.4.1", + "@microsoft/fast-html": "1.0.0-alpha.53", + "@microsoft/fast-test-harness": "file:.yalc/@microsoft/fast-test-harness", + "@microsoft/focusgroup-polyfill": "1.5.0", "@microsoft/load-themed-styles": "1.10.26", "@microsoft/loader-load-themed-styles": "2.0.17", "@microsoft/tsdoc": "0.15.1", diff --git a/packages/web-components/package.json b/packages/web-components/package.json index ee9183d33c02cd..654315cfcb7f53 100644 --- a/packages/web-components/package.json +++ b/packages/web-components/package.json @@ -46,10 +46,13 @@ "./theme/*.js": "./dist/esm/theme/*.js", "./*/base.js": "./dist/esm/*/*.base.js", "./*/define.js": "./dist/esm/*/define.js", + "./*/define-async.js": "./dist/esm/*/define-async.js", "./*/definition.js": "./dist/esm/*/*.definition.js", "./*/options.js": "./dist/esm/*/*.options.js", "./*/styles.js": "./dist/esm/*/*.styles.js", + "./*/styles.css": "./dist/esm/*/*.styles.css", "./*/template.js": "./dist/esm/*/*.template.js", + "./*/template.html": "./dist/esm/*/*.template.html", "./*/index.js": "./dist/esm/*/index.js", "./*.js": "./dist/esm/*/define.js", "./custom-elements.json": "./custom-elements.json", @@ -57,6 +60,7 @@ }, "sideEffects": [ "define.*", + "define-async.*", "define-all.*", "index-rollup.*", "index-all-rollup.*", @@ -75,7 +79,11 @@ "compile:benchmark": "rollup -c rollup.bench.js", "clean": "node ./scripts/clean dist", "generate-api": "api-extractor run --local", - "build": "yarn compile && yarn rollup -c && yarn generate-api && yarn analyze", + "build": "yarn compile && yarn build:rollup && yarn build:ssr && yarn generate-api && yarn analyze", + "build:ssr:templates": "fast-test-harness generate-templates --tag-prefix=fluent", + "build:ssr:styles": "fast-test-harness generate-stylesheets", + "build:ssr": "yarn build:ssr:templates && yarn build:ssr:styles", + "build:rollup": "rollup -c", "lint": "eslint . --ext .ts", "lint:fix": "eslint . --ext .ts --fix", "format": "prettier -w src/**/*.{ts,html} src/*.{ts,html} --ignore-path ../../.prettierignore", @@ -106,8 +114,14 @@ }, "peerDependencies": { "@microsoft/fast-element": "^2.0.0", + "@microsoft/fast-html": "^1.0.0-alpha.53", "@microsoft/focusgroup-polyfill": "^1.4.1" }, + "peerDependenciesMeta": { + "@microsoft/fast-html": { + "optional": true + } + }, "beachball": { "disallowedChangeTypes": [ "major", diff --git a/yarn.lock b/yarn.lock index b2e66256a2a602..d17d71d90a431c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2757,10 +2757,13 @@ resolved "https://registry.yarnpkg.com/@microsoft/fast-element/-/fast-element-2.10.4.tgz#12b8c6c90902f2a54d5b27f618d7d095e133546d" integrity sha512-OkHIlMztq7+PgkRF1LscgBd/MVzMhGrWPkJRDvOEqdqEdZOKocKvaKcUKZubIBz4RpLIrhLD3lOJzCsspk6jXg== -"@microsoft/fast-test-harness@0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@microsoft/fast-test-harness/-/fast-test-harness-0.1.0.tgz#e372cd6e23b921ee4c5aca35a5eb2bcf6b60843a" - integrity sha512-TEbNxPBbF9XDlqZTF21fymWrTUhBpPxqi4yznFzTdX+mBslNTLPfqOUXxd4zd0Vz1zn3SKtW4OIop0vCSu1oXQ== +"@microsoft/fast-html@1.0.0-alpha.53": + version "1.0.0-alpha.53" + resolved "https://registry.yarnpkg.com/@microsoft/fast-html/-/fast-html-1.0.0-alpha.53.tgz#e656a38386a998640fd7af40bbcd5aae321092f9" + integrity sha512-S6fUlNjk3aj9SP1U4sRWA1Cf1gns7FLJOed3NMvndWQBfmQczVX8CHXbfVKF55B7nKWRkAQwDHN0wOu7yxw+aw== + +"@microsoft/fast-test-harness@file:.yalc/@microsoft/fast-test-harness": + version "0.2.0" dependencies: cheerio "1.2.0" @@ -2771,10 +2774,10 @@ dependencies: exenv-es6 "^1.1.1" -"@microsoft/focusgroup-polyfill@^1.4.1": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@microsoft/focusgroup-polyfill/-/focusgroup-polyfill-1.4.1.tgz#0d3feee675775f7a692a3b1e974eea107503c78e" - integrity sha512-nXn/kJ5ZnzpR+TWYGV+nU2yaHPJORjWf2lSQJ90VT6Ydsmmkaz/yOYW3CizDGPPsA7ouAu9K8khCUeyfBcz7UA== +"@microsoft/focusgroup-polyfill@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@microsoft/focusgroup-polyfill/-/focusgroup-polyfill-1.5.0.tgz#f59ab909169b3c82921d845e2034f50426eeab87" + integrity sha512-2tw+AISULD5xhP+wIqUheej5wZbwiuPaqjuCa8A8jLdq2ikklZBIvSDLLa6j7zbDbtV5BXnrYgSuyW1Bj5JA4A== "@microsoft/load-themed-styles@1.10.26", "@microsoft/load-themed-styles@^1.10.26": version "1.10.26" From 230bf463359beee300e335d77639c2a120783aab Mon Sep 17 00:00:00 2001 From: John Kreitlow <863023+radium-v@users.noreply.github.com> Date: Sat, 23 May 2026 21:44:22 -0700 Subject: [PATCH 02/44] update fast-test-harness dependency to version 0.3.0 --- package.json | 2 +- yarn.lock | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index faa30d5a60204f..256344319b0950 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "@microsoft/fast-build": "0.6.0", "@microsoft/fast-element": "2.10.4", "@microsoft/fast-html": "1.0.0-alpha.53", - "@microsoft/fast-test-harness": "file:.yalc/@microsoft/fast-test-harness", + "@microsoft/fast-test-harness": "0.3.0", "@microsoft/focusgroup-polyfill": "1.5.0", "@microsoft/load-themed-styles": "1.10.26", "@microsoft/loader-load-themed-styles": "2.0.17", diff --git a/yarn.lock b/yarn.lock index d17d71d90a431c..a611ecd66bd487 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2762,8 +2762,10 @@ resolved "https://registry.yarnpkg.com/@microsoft/fast-html/-/fast-html-1.0.0-alpha.53.tgz#e656a38386a998640fd7af40bbcd5aae321092f9" integrity sha512-S6fUlNjk3aj9SP1U4sRWA1Cf1gns7FLJOed3NMvndWQBfmQczVX8CHXbfVKF55B7nKWRkAQwDHN0wOu7yxw+aw== -"@microsoft/fast-test-harness@file:.yalc/@microsoft/fast-test-harness": - version "0.2.0" +"@microsoft/fast-test-harness@0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@microsoft/fast-test-harness/-/fast-test-harness-0.3.0.tgz#9d5536c8cbf1e4ecae9d70bfe0fe105b5e215caf" + integrity sha512-EZ0iLxy/AEaOMthJmalD4fIp4rAM/ypBfqXmMJD4ZffVUVnLkfUw2NFZNUIsMbP/S4+tsfBnBlX5tyKSYD5TIQ== dependencies: cheerio "1.2.0" From 39ac8409953dfef0a2b14c7ce4ca64dd14de66f4 Mon Sep 17 00:00:00 2001 From: John Kreitlow <863023+radium-v@users.noreply.github.com> Date: Fri, 22 May 2026 12:00:19 -0700 Subject: [PATCH 03/44] add SSR entry modules and HTML template for testing --- .../web-components/test/src/entry-client.ts | 23 +++++++++++++++++ .../web-components/test/src/entry-server.ts | 25 +++++++++++++++++++ packages/web-components/test/ssr.html | 15 +++++++++++ 3 files changed, 63 insertions(+) create mode 100644 packages/web-components/test/src/entry-client.ts create mode 100644 packages/web-components/test/src/entry-server.ts create mode 100644 packages/web-components/test/ssr.html diff --git a/packages/web-components/test/src/entry-client.ts b/packages/web-components/test/src/entry-client.ts new file mode 100644 index 00000000000000..9d71f2c4f07402 --- /dev/null +++ b/packages/web-components/test/src/entry-client.ts @@ -0,0 +1,23 @@ +/** + * SSR client-side entry module for the Fluent UI web-components test harness. + * + * This module is loaded by the test harness on the SSR entry page (`ssr.html`) and is responsible for providing + * the necessary client-side setup to hydrate the server-rendered HTML and run the tests. This includes defining any + * components used in the SSR fixtures and importing any necessary polyfills or shared setup code. + * + * Note that this module is separate from the CSR test entry point (`main.ts`) to avoid loading unnecessary code during + * SSR tests. Only code that is required for hydrating the SSR fixtures and running the tests should be included here. + */ + +import '@microsoft/fast-test-harness/ssr/entry-client.js'; +import './common.js'; + +/** + * `import.meta.glob` is a Vite-specific feature that allows us to dynamically import all component definitions without + * having to manually maintain a list. The `eager: true` option ensures that these modules are imported immediately. + * + * The SSR/DSD fixtures require a slightly different set of component definitions than the CSR fixtures, facilitated + * by the `define-async` modules - these definitions don't include templates or styles, since those are already included + * in the server-rendered HTML and don't need to be reloaded on the client via the FAST Element component lifecycle. + */ +import.meta.glob('../../src/*/define-async.{ts,js}', { eager: true }); diff --git a/packages/web-components/test/src/entry-server.ts b/packages/web-components/test/src/entry-server.ts new file mode 100644 index 00000000000000..ce25a91654e1fb --- /dev/null +++ b/packages/web-components/test/src/entry-server.ts @@ -0,0 +1,25 @@ +/** + * SSR server-side entry module for the Fluent UI web-components test harness. + * + * This module is loaded by the test harness server via `vite.ssrLoadModule("/src/entry-server.js")` and must export a + * `render` function. The server calls `render()` for each SSR fixture request, injecting the returned HTML into the + * `ssr.html` template. + * + * The `<--stylespreload-->`, `<--templates-->`, and `<--fixture-->` markers in `ssr.html` are replaced with the appropriate content during rendering: + * - `<--stylespreload-->` is replaced with a `` tag for the theme stylesheet specified in the renderer options. + * - `<--templates-->` is replaced with the rendered HTML for the component templates defined in the renderer options. + * - `<--fixture-->` is replaced with the rendered HTML for the specific SSR fixture being requested, which is + * determined by the request URL and any query parameters. + * + * Note that this module should only include code necessary for rendering the SSR fixture source HTML. Any client-side + * setup or component definitions should be placed in the separate `entry-client.ts` module, to be loaded on the client + * after hydration. + */ + +import { createSSRRenderer } from '@microsoft/fast-test-harness/ssr/render.js'; + +export const { render } = createSSRRenderer({ + packageName: '@fluentui/web-components', + tagPrefix: 'fluent', + themeStylesheet: '/fluent-tokens.css', +}); diff --git a/packages/web-components/test/ssr.html b/packages/web-components/test/ssr.html new file mode 100644 index 00000000000000..6f8e371787f5de --- /dev/null +++ b/packages/web-components/test/ssr.html @@ -0,0 +1,15 @@ + + + + + + <!--fixturetitle--> + + + + + + + + + From eb9d85fdfd696119d0bb7a4fe7fad27eb0934801 Mon Sep 17 00:00:00 2001 From: John Kreitlow <863023+radium-v@users.noreply.github.com> Date: Fri, 22 May 2026 12:01:00 -0700 Subject: [PATCH 04/44] add DSD modules for accordion and accordion-item --- .../accordion-item.definition-async.ts | 15 +++++++++++++++ .../src/accordion-item/define-async.ts | 5 +++++ .../src/accordion/accordion.definition-async.ts | 7 +++++++ .../web-components/src/accordion/accordion.ts | 2 +- .../web-components/src/accordion/define-async.ts | 5 +++++ 5 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 packages/web-components/src/accordion-item/accordion-item.definition-async.ts create mode 100644 packages/web-components/src/accordion-item/define-async.ts create mode 100644 packages/web-components/src/accordion/accordion.definition-async.ts create mode 100644 packages/web-components/src/accordion/define-async.ts diff --git a/packages/web-components/src/accordion-item/accordion-item.definition-async.ts b/packages/web-components/src/accordion-item/accordion-item.definition-async.ts new file mode 100644 index 00000000000000..cd0784a8cc6b77 --- /dev/null +++ b/packages/web-components/src/accordion-item/accordion-item.definition-async.ts @@ -0,0 +1,15 @@ +import type { PartialFASTElementDefinition } from '@microsoft/fast-element'; +import { tagName } from './accordion-item.options.js'; + +/** + * The async definition configuration for the fluent-accordion-item element. + * + * @public + * @remarks + * This is used in server-side rendering (SSR) scenarios where the template + * is provided as a deferred option to be hydrated later. + */ +export const definition: PartialFASTElementDefinition = { + name: tagName, + templateOptions: 'defer-and-hydrate', +}; diff --git a/packages/web-components/src/accordion-item/define-async.ts b/packages/web-components/src/accordion-item/define-async.ts new file mode 100644 index 00000000000000..a9c63799400114 --- /dev/null +++ b/packages/web-components/src/accordion-item/define-async.ts @@ -0,0 +1,5 @@ +import { RenderableFASTElement } from '@microsoft/fast-html'; +import { AccordionItem } from './accordion-item.js'; +import { definition } from './accordion-item.definition-async.js'; + +RenderableFASTElement(AccordionItem).defineAsync(definition); diff --git a/packages/web-components/src/accordion/accordion.definition-async.ts b/packages/web-components/src/accordion/accordion.definition-async.ts new file mode 100644 index 00000000000000..d47e74dae59dfc --- /dev/null +++ b/packages/web-components/src/accordion/accordion.definition-async.ts @@ -0,0 +1,7 @@ +import { type PartialFASTElementDefinition } from '@microsoft/fast-element'; +import { tagName } from './accordion.options.js'; + +export const declarativeDefinition: PartialFASTElementDefinition = { + name: tagName, + templateOptions: 'defer-and-hydrate', +} as const; diff --git a/packages/web-components/src/accordion/accordion.ts b/packages/web-components/src/accordion/accordion.ts index cdfcbc24b14731..23bc871bbb5b89 100644 --- a/packages/web-components/src/accordion/accordion.ts +++ b/packages/web-components/src/accordion/accordion.ts @@ -110,7 +110,7 @@ export class Accordion extends FASTElement { */ private setItems = (): void => { waitForConnectedDescendants(this, () => { - if (this.slottedAccordionItems.length === 0) { + if (!this.slottedAccordionItems?.length) { return; } diff --git a/packages/web-components/src/accordion/define-async.ts b/packages/web-components/src/accordion/define-async.ts new file mode 100644 index 00000000000000..0e08c8e1611536 --- /dev/null +++ b/packages/web-components/src/accordion/define-async.ts @@ -0,0 +1,5 @@ +import { RenderableFASTElement } from '@microsoft/fast-html'; +import { declarativeDefinition } from './accordion.definition-async.js'; +import { Accordion } from './accordion.js'; + +RenderableFASTElement(Accordion).defineAsync(declarativeDefinition); From f482514787a7c5d3cd5935685f1a2f64bb64ad4a Mon Sep 17 00:00:00 2001 From: John Kreitlow <863023+radium-v@users.noreply.github.com> Date: Fri, 22 May 2026 16:20:22 -0700 Subject: [PATCH 05/44] add DSD modules for anchor-button --- .../anchor-button.definition-async.ts | 15 ++++++++++++ .../src/anchor-button/anchor-button.spec.ts | 24 ++++++++++++++----- .../src/anchor-button/define-async.ts | 5 ++++ 3 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 packages/web-components/src/anchor-button/anchor-button.definition-async.ts create mode 100644 packages/web-components/src/anchor-button/define-async.ts diff --git a/packages/web-components/src/anchor-button/anchor-button.definition-async.ts b/packages/web-components/src/anchor-button/anchor-button.definition-async.ts new file mode 100644 index 00000000000000..b3e7245dd34a7d --- /dev/null +++ b/packages/web-components/src/anchor-button/anchor-button.definition-async.ts @@ -0,0 +1,15 @@ +import type { PartialFASTElementDefinition } from '@microsoft/fast-element'; +import { tagName } from './anchor-button.options.js'; + +/** + * The async definition configuration for the fluent-anchor-button element. + * + * @public + * @remarks + * This is used in server-side rendering (SSR) scenarios where the template + * is provided as a deferred option to be hydrated later. + */ +export const definition: PartialFASTElementDefinition = { + name: tagName, + templateOptions: 'defer-and-hydrate', +}; diff --git a/packages/web-components/src/anchor-button/anchor-button.spec.ts b/packages/web-components/src/anchor-button/anchor-button.spec.ts index e34999cea9260f..36a586cbc88f83 100644 --- a/packages/web-components/src/anchor-button/anchor-button.spec.ts +++ b/packages/web-components/src/anchor-button/anchor-button.spec.ts @@ -172,9 +172,11 @@ test.describe('Anchor Button', () => { await fastPage.setTemplate({ attributes: { href: expectedUrl } }); + expect(page.url()).not.toMatch(expectedUrl); + await element.click(); - await expect(page).toHaveURL(expectedUrl); + expect(page.url()).toMatch(expectedUrl); }); test('should emit a single click event when clicked', async ({ fastPage, page }) => { @@ -197,8 +199,9 @@ test.describe('Anchor Button', () => { }); test('should navigate to the provided url when clicked while pressing the `Control` key on Windows or `Meta` on Mac', async ({ - fastPage, context, + fastPage, + page, }) => { const { element } = fastPage; @@ -212,7 +215,11 @@ test.describe('Anchor Button', () => { const newPage = await newPagePromise; - await expect(newPage).toHaveURL(expectedUrl); + expect(page.url()).not.toMatch(expectedUrl); + + expect(newPage.url()).toMatch(expectedUrl); + + await newPage.close(); }); test('should navigate to the provided url when `Enter` is pressed via keyboard', async ({ fastPage, page }) => { @@ -226,12 +233,13 @@ test.describe('Anchor Button', () => { await element.press('Enter'); - await expect(page).toHaveURL(expectedUrl); + expect(page.url()).toMatch(expectedUrl); }); test('should navigate to the provided url when `ctrl` and `Enter` are pressed via keyboard', async ({ - fastPage, context, + fastPage, + page, }) => { const { element } = fastPage; @@ -249,6 +257,10 @@ test.describe('Anchor Button', () => { const newPage = await newPagePromise; - await expect(newPage).toHaveURL(expectedUrl); + expect(page.url()).not.toMatch(expectedUrl); + + expect(newPage.url()).toMatch(expectedUrl); + + await newPage.close(); }); }); diff --git a/packages/web-components/src/anchor-button/define-async.ts b/packages/web-components/src/anchor-button/define-async.ts new file mode 100644 index 00000000000000..16ee22566f0634 --- /dev/null +++ b/packages/web-components/src/anchor-button/define-async.ts @@ -0,0 +1,5 @@ +import { RenderableFASTElement } from '@microsoft/fast-html'; +import { definition } from './anchor-button.definition-async.js'; +import { AnchorButton } from './anchor-button.js'; + +RenderableFASTElement(AnchorButton).defineAsync(definition); From a998a3c62cb15e657681d0f7244756b3c85d467c Mon Sep 17 00:00:00 2001 From: John Kreitlow <863023+radium-v@users.noreply.github.com> Date: Sat, 23 May 2026 21:45:58 -0700 Subject: [PATCH 06/44] add DSD modules for avatar --- .../src/avatar/avatar.definition-async.ts | 15 +++++++++++++++ packages/web-components/src/avatar/avatar.spec.ts | 1 + .../web-components/src/avatar/define-async.ts | 5 +++++ 3 files changed, 21 insertions(+) create mode 100644 packages/web-components/src/avatar/avatar.definition-async.ts create mode 100644 packages/web-components/src/avatar/define-async.ts diff --git a/packages/web-components/src/avatar/avatar.definition-async.ts b/packages/web-components/src/avatar/avatar.definition-async.ts new file mode 100644 index 00000000000000..f3a01757798bd4 --- /dev/null +++ b/packages/web-components/src/avatar/avatar.definition-async.ts @@ -0,0 +1,15 @@ +import type { PartialFASTElementDefinition } from '@microsoft/fast-element'; +import { tagName } from './avatar.options.js'; + +/** + * The async definition configuration for the fluent-avatar element. + * + * @public + * @remarks + * This is used in server-side rendering (SSR) scenarios where the template + * is provided as a deferred option to be hydrated later. + */ +export const definition: PartialFASTElementDefinition = { + name: tagName, + templateOptions: 'defer-and-hydrate', +}; diff --git a/packages/web-components/src/avatar/avatar.spec.ts b/packages/web-components/src/avatar/avatar.spec.ts index 0556964a2eb83e..f5d375173d46a4 100644 --- a/packages/web-components/src/avatar/avatar.spec.ts +++ b/packages/web-components/src/avatar/avatar.spec.ts @@ -142,6 +142,7 @@ test.describe('Avatar', () => { // eslint-disable-next-line playwright/no-conditional-in-test if (color !== AvatarColor.colorful) { + // eslint-disable-next-line playwright/no-conditional-expect await expect.soft(element).toHaveAttribute('data-color', color); } }); diff --git a/packages/web-components/src/avatar/define-async.ts b/packages/web-components/src/avatar/define-async.ts new file mode 100644 index 00000000000000..6a720918f63410 --- /dev/null +++ b/packages/web-components/src/avatar/define-async.ts @@ -0,0 +1,5 @@ +import { RenderableFASTElement } from '@microsoft/fast-html'; +import { definition } from './avatar.definition-async.js'; +import { Avatar } from './avatar.js'; + +RenderableFASTElement(Avatar).defineAsync(definition); From 3272a3e0b69656b5db94046d96bdc32fe3466dd3 Mon Sep 17 00:00:00 2001 From: John Kreitlow <863023+radium-v@users.noreply.github.com> Date: Sat, 23 May 2026 21:47:06 -0700 Subject: [PATCH 07/44] add DSD modules for badge --- .../src/badge/badge.definition-async.ts | 15 +++++++++++++++ packages/web-components/src/badge/define-async.ts | 5 +++++ 2 files changed, 20 insertions(+) create mode 100644 packages/web-components/src/badge/badge.definition-async.ts create mode 100644 packages/web-components/src/badge/define-async.ts diff --git a/packages/web-components/src/badge/badge.definition-async.ts b/packages/web-components/src/badge/badge.definition-async.ts new file mode 100644 index 00000000000000..14c467f1db83f5 --- /dev/null +++ b/packages/web-components/src/badge/badge.definition-async.ts @@ -0,0 +1,15 @@ +import type { PartialFASTElementDefinition } from '@microsoft/fast-element'; +import { tagName } from './badge.options.js'; + +/** + * The async definition configuration for the fluent-badge element. + * + * @public + * @remarks + * This is used in server-side rendering (SSR) scenarios where the template + * is provided as a deferred option to be hydrated later. + */ +export const definition: PartialFASTElementDefinition = { + name: tagName, + templateOptions: 'defer-and-hydrate', +}; diff --git a/packages/web-components/src/badge/define-async.ts b/packages/web-components/src/badge/define-async.ts new file mode 100644 index 00000000000000..17aaae438671f7 --- /dev/null +++ b/packages/web-components/src/badge/define-async.ts @@ -0,0 +1,5 @@ +import { RenderableFASTElement } from '@microsoft/fast-html'; +import { definition } from './badge.definition-async.js'; +import { Badge } from './badge.js'; + +RenderableFASTElement(Badge).defineAsync(definition); From e168e2206d35c902ae729483e51de9cd2e097c88 Mon Sep 17 00:00:00 2001 From: John Kreitlow <863023+radium-v@users.noreply.github.com> Date: Sat, 23 May 2026 21:49:57 -0700 Subject: [PATCH 08/44] add DSD modules for button --- .../src/button/button.definition-async.ts | 15 +++++++++++++++ packages/web-components/src/button/button.spec.ts | 12 ++++++++++-- .../web-components/src/button/define-async.ts | 5 +++++ 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 packages/web-components/src/button/button.definition-async.ts create mode 100644 packages/web-components/src/button/define-async.ts diff --git a/packages/web-components/src/button/button.definition-async.ts b/packages/web-components/src/button/button.definition-async.ts new file mode 100644 index 00000000000000..19ed5035d8c049 --- /dev/null +++ b/packages/web-components/src/button/button.definition-async.ts @@ -0,0 +1,15 @@ +import type { PartialFASTElementDefinition } from '@microsoft/fast-element'; +import { tagName } from './button.options.js'; + +/** + * The async definition configuration for the fluent-button element. + * + * @public + * @remarks + * This is used in server-side rendering (SSR) scenarios where the template + * is provided as a deferred option to be hydrated later. + */ +export const definition: PartialFASTElementDefinition = { + name: tagName, + templateOptions: 'defer-and-hydrate', +}; diff --git a/packages/web-components/src/button/button.spec.ts b/packages/web-components/src/button/button.spec.ts index 263a8282786752..825d6ea64d6e11 100644 --- a/packages/web-components/src/button/button.spec.ts +++ b/packages/web-components/src/button/button.spec.ts @@ -1,3 +1,4 @@ +import type { InitialTemplateAttributes } from '@microsoft/fast-test-harness/fixtures/csr-fixture.js'; import { expect, test } from '../../test/playwright/index.js'; import { tagName } from './button.options.js'; @@ -262,10 +263,17 @@ test.describe('Button', () => { await expect(notFocusable).not.toBeFocused(); }); - test('should focus the element when the `autofocus` attribute is present', async ({ fastPage }) => { + test('should focus the element when the `autofocus` attribute is present', async ({ fastPage, ssr }) => { const { element } = fastPage; - await fastPage.setTemplate({ attributes: { autofocus: true } }); + const attributes: InitialTemplateAttributes = { autofocus: true }; + + if (ssr) { + // the host element needs to be focusable for autofocus to work on the server, so we need to set tabindex="0" + attributes.tabindex = '0'; + } + + await fastPage.setTemplate({ attributes }); await expect(element).toBeFocused(); }); diff --git a/packages/web-components/src/button/define-async.ts b/packages/web-components/src/button/define-async.ts new file mode 100644 index 00000000000000..723c7c2fe9f928 --- /dev/null +++ b/packages/web-components/src/button/define-async.ts @@ -0,0 +1,5 @@ +import { RenderableFASTElement } from '@microsoft/fast-html'; +import { definition } from './button.definition-async.js'; +import { Button } from './button.js'; + +RenderableFASTElement(Button).defineAsync(definition); From d29a06660ddad10ab2142600a22161536ae3caf0 Mon Sep 17 00:00:00 2001 From: John Kreitlow <863023+radium-v@users.noreply.github.com> Date: Sat, 23 May 2026 21:55:23 -0700 Subject: [PATCH 09/44] add DSD modules for checkbox --- .../src/checkbox/checkbox.base.ts | 17 ++++++++++------- .../src/checkbox/checkbox.definition-async.ts | 15 +++++++++++++++ .../web-components/src/checkbox/define-async.ts | 5 +++++ 3 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 packages/web-components/src/checkbox/checkbox.definition-async.ts create mode 100644 packages/web-components/src/checkbox/define-async.ts diff --git a/packages/web-components/src/checkbox/checkbox.base.ts b/packages/web-components/src/checkbox/checkbox.base.ts index 79c8f4a32aa4ea..b378112216ff95 100644 --- a/packages/web-components/src/checkbox/checkbox.base.ts +++ b/packages/web-components/src/checkbox/checkbox.base.ts @@ -177,7 +177,7 @@ export class BaseCheckbox extends FASTElement { * @internal */ protected requiredChanged(prev: boolean, next: boolean): void { - if (this.$fastController.isConnected) { + if (this.elementInternals) { this.setValidity(); this.elementInternals.ariaRequired = this.required ? 'true' : 'false'; } @@ -248,7 +248,7 @@ export class BaseCheckbox extends FASTElement { * Reflects the {@link https://developer.mozilla.org/docs/Web/API/ElementInternals/validationMessage | `ElementInternals.validationMessage`} property. */ public get validationMessage(): string { - if (this.elementInternals.validationMessage) { + if (this.elementInternals?.validationMessage) { return this.elementInternals.validationMessage; } @@ -295,11 +295,12 @@ export class BaseCheckbox extends FASTElement { public set value(value: string) { this._value = value; - if (this.$fastController.isConnected) { + if (this.elementInternals) { this.setFormValue(value); this.setValidity(); - Observable.notify(this, 'value'); } + + Observable.notify(this, 'value'); } /** @@ -429,7 +430,9 @@ export class BaseCheckbox extends FASTElement { * @internal */ protected setAriaChecked(value: boolean = this.checked) { - this.elementInternals.ariaChecked = value ? 'true' : 'false'; + if (this.elementInternals) { + this.elementInternals.ariaChecked = value ? 'true' : 'false'; + } } /** @@ -438,7 +441,7 @@ export class BaseCheckbox extends FASTElement { * @internal */ public setFormValue(value: File | string | FormData | null, state?: File | string | FormData | null): void { - this.elementInternals.setFormValue(value, value ?? state); + this.elementInternals?.setFormValue(value, value ?? state); } /** @@ -462,7 +465,7 @@ export class BaseCheckbox extends FASTElement { * @internal */ public setValidity(flags?: Partial, message?: string, anchor?: HTMLElement): void { - if (this.$fastController.isConnected) { + if (this.elementInternals) { if (this.disabled || !this.required) { this.elementInternals.setValidity({}); return; diff --git a/packages/web-components/src/checkbox/checkbox.definition-async.ts b/packages/web-components/src/checkbox/checkbox.definition-async.ts new file mode 100644 index 00000000000000..2ecc0367055d5c --- /dev/null +++ b/packages/web-components/src/checkbox/checkbox.definition-async.ts @@ -0,0 +1,15 @@ +import type { PartialFASTElementDefinition } from '@microsoft/fast-element'; +import { tagName } from './checkbox.options.js'; + +/** + * The async definition configuration for the fluent-checkbox element. + * + * @public + * @remarks + * This is used in server-side rendering (SSR) scenarios where the template + * is provided as a deferred option to be hydrated later. + */ +export const definition: PartialFASTElementDefinition = { + name: tagName, + templateOptions: 'defer-and-hydrate', +}; diff --git a/packages/web-components/src/checkbox/define-async.ts b/packages/web-components/src/checkbox/define-async.ts new file mode 100644 index 00000000000000..c203ddd5c93456 --- /dev/null +++ b/packages/web-components/src/checkbox/define-async.ts @@ -0,0 +1,5 @@ +import { RenderableFASTElement } from '@microsoft/fast-html'; +import { definition } from './checkbox.definition-async.js'; +import { Checkbox } from './checkbox.js'; + +RenderableFASTElement(Checkbox).defineAsync(definition); From c1c5f62c429e4f7abb1d2ccf6fb578e600ed456d Mon Sep 17 00:00:00 2001 From: John Kreitlow <863023+radium-v@users.noreply.github.com> Date: Sat, 23 May 2026 21:56:26 -0700 Subject: [PATCH 10/44] add DSD modules for compound-button --- .../compound-button.definition-async.ts | 15 +++++++++++++++ .../src/compound-button/compound-button.spec.ts | 12 ++++++++++-- .../src/compound-button/define-async.ts | 5 +++++ 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 packages/web-components/src/compound-button/compound-button.definition-async.ts create mode 100644 packages/web-components/src/compound-button/define-async.ts diff --git a/packages/web-components/src/compound-button/compound-button.definition-async.ts b/packages/web-components/src/compound-button/compound-button.definition-async.ts new file mode 100644 index 00000000000000..bdcaa313420b97 --- /dev/null +++ b/packages/web-components/src/compound-button/compound-button.definition-async.ts @@ -0,0 +1,15 @@ +import type { PartialFASTElementDefinition } from '@microsoft/fast-element'; +import { tagName } from './compound-button.options.js'; + +/** + * The async definition configuration for the fluent-compound-button element. + * + * @public + * @remarks + * This is used in server-side rendering (SSR) scenarios where the template + * is provided as a deferred option to be hydrated later. + */ +export const definition: PartialFASTElementDefinition = { + name: tagName, + templateOptions: 'defer-and-hydrate', +}; diff --git a/packages/web-components/src/compound-button/compound-button.spec.ts b/packages/web-components/src/compound-button/compound-button.spec.ts index f0df3f89b8589a..6afd843b8dd7ec 100644 --- a/packages/web-components/src/compound-button/compound-button.spec.ts +++ b/packages/web-components/src/compound-button/compound-button.spec.ts @@ -1,3 +1,4 @@ +import type { InitialTemplateAttributes } from '@microsoft/fast-test-harness/fixtures/csr-fixture.js'; import { expect, test } from '../../test/playwright/index.js'; import { tagName } from './compound-button.options.js'; @@ -261,10 +262,17 @@ test.describe('Compound Button', () => { await expect(element).not.toBeFocused(); }); - test('should focus the element when the `autofocus` attribute is present', async ({ fastPage }) => { + test('should focus the element when the `autofocus` attribute is present', async ({ fastPage, ssr }) => { const { element } = fastPage; - await fastPage.setTemplate({ attributes: { autofocus: true } }); + const attributes: InitialTemplateAttributes = { autofocus: true }; + + if (ssr) { + // the host element needs to be focusable for autofocus to work on the server, so we need to set tabindex="0" + attributes.tabindex = '0'; + } + + await fastPage.setTemplate({ attributes }); await expect(element).toBeFocused(); }); diff --git a/packages/web-components/src/compound-button/define-async.ts b/packages/web-components/src/compound-button/define-async.ts new file mode 100644 index 00000000000000..4f869a39afd124 --- /dev/null +++ b/packages/web-components/src/compound-button/define-async.ts @@ -0,0 +1,5 @@ +import { RenderableFASTElement } from '@microsoft/fast-html'; +import { definition } from './compound-button.definition-async.js'; +import { CompoundButton } from './compound-button.js'; + +RenderableFASTElement(CompoundButton).defineAsync(definition); From 199bd60a875f37975c2e2d02efec7afdfc0b1709 Mon Sep 17 00:00:00 2001 From: John Kreitlow <863023+radium-v@users.noreply.github.com> Date: Sat, 23 May 2026 21:57:31 -0700 Subject: [PATCH 11/44] add DSD modules for counter-badge --- .../counter-badge.definition-async.ts | 15 +++++++++++++++ .../src/counter-badge/define-async.ts | 5 +++++ 2 files changed, 20 insertions(+) create mode 100644 packages/web-components/src/counter-badge/counter-badge.definition-async.ts create mode 100644 packages/web-components/src/counter-badge/define-async.ts diff --git a/packages/web-components/src/counter-badge/counter-badge.definition-async.ts b/packages/web-components/src/counter-badge/counter-badge.definition-async.ts new file mode 100644 index 00000000000000..9695542eb818e6 --- /dev/null +++ b/packages/web-components/src/counter-badge/counter-badge.definition-async.ts @@ -0,0 +1,15 @@ +import type { PartialFASTElementDefinition } from '@microsoft/fast-element'; +import { tagName } from './counter-badge.options.js'; + +/** + * The async definition configuration for the fluent-counter-badge element. + * + * @public + * @remarks + * This is used in server-side rendering (SSR) scenarios where the template + * is provided as a deferred option to be hydrated later. + */ +export const definition: PartialFASTElementDefinition = { + name: tagName, + templateOptions: 'defer-and-hydrate', +}; diff --git a/packages/web-components/src/counter-badge/define-async.ts b/packages/web-components/src/counter-badge/define-async.ts new file mode 100644 index 00000000000000..18bd0394962287 --- /dev/null +++ b/packages/web-components/src/counter-badge/define-async.ts @@ -0,0 +1,5 @@ +import { RenderableFASTElement } from '@microsoft/fast-html'; +import { definition } from './counter-badge.definition-async.js'; +import { CounterBadge } from './counter-badge.js'; + +RenderableFASTElement(CounterBadge).defineAsync(definition); From 4f029f974ca61ad7f724c743ec4844dc121918d7 Mon Sep 17 00:00:00 2001 From: John Kreitlow <863023+radium-v@users.noreply.github.com> Date: Sat, 23 May 2026 22:02:22 -0700 Subject: [PATCH 12/44] add DSD modules for dialog and dialog-body --- .../src/dialog-body/define-async.ts | 5 +++++ .../dialog-body/dialog-body.definition-async.ts | 15 +++++++++++++++ .../web-components/src/dialog/define-async.ts | 5 +++++ .../src/dialog/dialog.definition-async.ts | 15 +++++++++++++++ 4 files changed, 40 insertions(+) create mode 100644 packages/web-components/src/dialog-body/define-async.ts create mode 100644 packages/web-components/src/dialog-body/dialog-body.definition-async.ts create mode 100644 packages/web-components/src/dialog/define-async.ts create mode 100644 packages/web-components/src/dialog/dialog.definition-async.ts diff --git a/packages/web-components/src/dialog-body/define-async.ts b/packages/web-components/src/dialog-body/define-async.ts new file mode 100644 index 00000000000000..8b228a007c3848 --- /dev/null +++ b/packages/web-components/src/dialog-body/define-async.ts @@ -0,0 +1,5 @@ +import { RenderableFASTElement } from '@microsoft/fast-html'; +import { definition } from './dialog-body.definition-async.js'; +import { DialogBody } from './dialog-body.js'; + +RenderableFASTElement(DialogBody).defineAsync(definition); diff --git a/packages/web-components/src/dialog-body/dialog-body.definition-async.ts b/packages/web-components/src/dialog-body/dialog-body.definition-async.ts new file mode 100644 index 00000000000000..5bff152fdb8406 --- /dev/null +++ b/packages/web-components/src/dialog-body/dialog-body.definition-async.ts @@ -0,0 +1,15 @@ +import type { PartialFASTElementDefinition } from '@microsoft/fast-element'; +import { tagName } from './dialog-body.options.js'; + +/** + * The async definition configuration for the fluent-dialog-body element. + * + * @public + * @remarks + * This is used in server-side rendering (SSR) scenarios where the template + * is provided as a deferred option to be hydrated later. + */ +export const definition: PartialFASTElementDefinition = { + name: tagName, + templateOptions: 'defer-and-hydrate', +}; diff --git a/packages/web-components/src/dialog/define-async.ts b/packages/web-components/src/dialog/define-async.ts new file mode 100644 index 00000000000000..9200297217c28d --- /dev/null +++ b/packages/web-components/src/dialog/define-async.ts @@ -0,0 +1,5 @@ +import { RenderableFASTElement } from '@microsoft/fast-html'; +import { definition } from './dialog.definition-async.js'; +import { Dialog } from './dialog.js'; + +RenderableFASTElement(Dialog).defineAsync(definition); diff --git a/packages/web-components/src/dialog/dialog.definition-async.ts b/packages/web-components/src/dialog/dialog.definition-async.ts new file mode 100644 index 00000000000000..c1b2d03a92a3f5 --- /dev/null +++ b/packages/web-components/src/dialog/dialog.definition-async.ts @@ -0,0 +1,15 @@ +import type { PartialFASTElementDefinition } from '@microsoft/fast-element'; +import { tagName } from './dialog.options.js'; + +/** + * The async definition configuration for the fluent-dialog element. + * + * @public + * @remarks + * This is used in server-side rendering (SSR) scenarios where the template + * is provided as a deferred option to be hydrated later. + */ +export const definition: PartialFASTElementDefinition = { + name: tagName, + templateOptions: 'defer-and-hydrate', +}; From 746c0fce8c93586ef0ca778429eb2b67b631438f Mon Sep 17 00:00:00 2001 From: John Kreitlow <863023+radium-v@users.noreply.github.com> Date: Sat, 23 May 2026 22:03:13 -0700 Subject: [PATCH 13/44] add DSD modules for divider --- .../web-components/src/divider/define-async.ts | 5 +++++ .../src/divider/divider.definition-async.ts | 15 +++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 packages/web-components/src/divider/define-async.ts create mode 100644 packages/web-components/src/divider/divider.definition-async.ts diff --git a/packages/web-components/src/divider/define-async.ts b/packages/web-components/src/divider/define-async.ts new file mode 100644 index 00000000000000..43f51d9a29a99f --- /dev/null +++ b/packages/web-components/src/divider/define-async.ts @@ -0,0 +1,5 @@ +import { RenderableFASTElement } from '@microsoft/fast-html'; +import { definition } from './divider.definition-async.js'; +import { Divider } from './divider.js'; + +RenderableFASTElement(Divider).defineAsync(definition); diff --git a/packages/web-components/src/divider/divider.definition-async.ts b/packages/web-components/src/divider/divider.definition-async.ts new file mode 100644 index 00000000000000..912edb20cb22a5 --- /dev/null +++ b/packages/web-components/src/divider/divider.definition-async.ts @@ -0,0 +1,15 @@ +import type { PartialFASTElementDefinition } from '@microsoft/fast-element'; +import { tagName } from './divider.options.js'; + +/** + * The async definition configuration for the fluent-divider element. + * + * @public + * @remarks + * This is used in server-side rendering (SSR) scenarios where the template + * is provided as a deferred option to be hydrated later. + */ +export const definition: PartialFASTElementDefinition = { + name: tagName, + templateOptions: 'defer-and-hydrate', +}; From 0e594c9dc0f21635ecc37d8535902e271c3db90e Mon Sep 17 00:00:00 2001 From: John Kreitlow <863023+radium-v@users.noreply.github.com> Date: Sat, 23 May 2026 22:13:44 -0700 Subject: [PATCH 14/44] add DSD modules for drawer and drawer-body --- .../src/drawer-body/define-async.ts | 5 +++++ .../drawer-body/drawer-body.definition-async.ts | 15 +++++++++++++++ .../web-components/src/drawer/define-async.ts | 5 +++++ .../src/drawer/drawer.definition-async.ts | 15 +++++++++++++++ 4 files changed, 40 insertions(+) create mode 100644 packages/web-components/src/drawer-body/define-async.ts create mode 100644 packages/web-components/src/drawer-body/drawer-body.definition-async.ts create mode 100644 packages/web-components/src/drawer/define-async.ts create mode 100644 packages/web-components/src/drawer/drawer.definition-async.ts diff --git a/packages/web-components/src/drawer-body/define-async.ts b/packages/web-components/src/drawer-body/define-async.ts new file mode 100644 index 00000000000000..fa7d72dabfd88f --- /dev/null +++ b/packages/web-components/src/drawer-body/define-async.ts @@ -0,0 +1,5 @@ +import { RenderableFASTElement } from '@microsoft/fast-html'; +import { definition } from './drawer-body.definition-async.js'; +import { DrawerBody } from './drawer-body.js'; + +RenderableFASTElement(DrawerBody).defineAsync(definition); diff --git a/packages/web-components/src/drawer-body/drawer-body.definition-async.ts b/packages/web-components/src/drawer-body/drawer-body.definition-async.ts new file mode 100644 index 00000000000000..c89b63285d5f8d --- /dev/null +++ b/packages/web-components/src/drawer-body/drawer-body.definition-async.ts @@ -0,0 +1,15 @@ +import type { PartialFASTElementDefinition } from '@microsoft/fast-element'; +import { tagName } from './drawer-body.options.js'; + +/** + * The async definition configuration for the fluent-drawer-body element. + * + * @public + * @remarks + * This is used in server-side rendering (SSR) scenarios where the template + * is provided as a deferred option to be hydrated later. + */ +export const definition: PartialFASTElementDefinition = { + name: tagName, + templateOptions: 'defer-and-hydrate', +}; diff --git a/packages/web-components/src/drawer/define-async.ts b/packages/web-components/src/drawer/define-async.ts new file mode 100644 index 00000000000000..aa5fc1b908b2ba --- /dev/null +++ b/packages/web-components/src/drawer/define-async.ts @@ -0,0 +1,5 @@ +import { RenderableFASTElement } from '@microsoft/fast-html'; +import { definition } from './drawer.definition-async.js'; +import { Drawer } from './drawer.js'; + +RenderableFASTElement(Drawer).defineAsync(definition); diff --git a/packages/web-components/src/drawer/drawer.definition-async.ts b/packages/web-components/src/drawer/drawer.definition-async.ts new file mode 100644 index 00000000000000..b35e299b46b71d --- /dev/null +++ b/packages/web-components/src/drawer/drawer.definition-async.ts @@ -0,0 +1,15 @@ +import type { PartialFASTElementDefinition } from '@microsoft/fast-element'; +import { tagName } from './drawer.options.js'; + +/** + * The async definition configuration for the fluent-drawer element. + * + * @public + * @remarks + * This is used in server-side rendering (SSR) scenarios where the template + * is provided as a deferred option to be hydrated later. + */ +export const definition: PartialFASTElementDefinition = { + name: tagName, + templateOptions: 'defer-and-hydrate', +}; From 92aa7d873ae61f68211c7f8e1229cb878b0da36b Mon Sep 17 00:00:00 2001 From: John Kreitlow <863023+radium-v@users.noreply.github.com> Date: Sat, 23 May 2026 22:50:49 -0700 Subject: [PATCH 15/44] add DSD modules for dropdown, listbox, and option --- .../src/dropdown/define-async.ts | 5 +++ .../src/dropdown/dropdown.base.ts | 39 ++++++++++--------- .../src/dropdown/dropdown.definition-async.ts | 15 +++++++ .../src/listbox/define-async.ts | 5 +++ .../src/listbox/listbox.definition-async.ts | 15 +++++++ .../web-components/src/option/define-async.ts | 5 +++ .../src/option/option.definition-async.ts | 15 +++++++ 7 files changed, 81 insertions(+), 18 deletions(-) create mode 100644 packages/web-components/src/dropdown/define-async.ts create mode 100644 packages/web-components/src/dropdown/dropdown.definition-async.ts create mode 100644 packages/web-components/src/listbox/define-async.ts create mode 100644 packages/web-components/src/listbox/listbox.definition-async.ts create mode 100644 packages/web-components/src/option/define-async.ts create mode 100644 packages/web-components/src/option/option.definition-async.ts diff --git a/packages/web-components/src/dropdown/define-async.ts b/packages/web-components/src/dropdown/define-async.ts new file mode 100644 index 00000000000000..55b665c210b7fc --- /dev/null +++ b/packages/web-components/src/dropdown/define-async.ts @@ -0,0 +1,5 @@ +import { RenderableFASTElement } from '@microsoft/fast-html'; +import { definition } from './dropdown.definition-async.js'; +import { Dropdown } from './dropdown.js'; + +RenderableFASTElement(Dropdown).defineAsync(definition); diff --git a/packages/web-components/src/dropdown/dropdown.base.ts b/packages/web-components/src/dropdown/dropdown.base.ts index 8f14dbd1ae7243..029d83e608fb5f 100644 --- a/packages/web-components/src/dropdown/dropdown.base.ts +++ b/packages/web-components/src/dropdown/dropdown.base.ts @@ -232,24 +232,20 @@ export class BaseDropdown extends FASTElement { notifier.notify('multiple'); - waitForConnectedDescendants( - next, - () => { - this.options.forEach(option => { - option.disabled = option.disabledAttribute || this.disabled; - option.name = this.name; - }); + Updates.enqueue(() => { + this.options.forEach(option => { + option.disabled = option.disabledAttribute || this.disabled; + option.name = this.name; + }); - this.enabledOptions - .filter(x => x.defaultSelected) - .forEach((x, i) => { - x.selected = this.multiple || i === 0; - }); + this.enabledOptions + .filter(x => x.defaultSelected) + .forEach((x, i) => { + x.selected = this.multiple || i === 0; + }); - this.setValidity(); - }, - { idleCallback: true }, - ); + this.setValidity(); + }); if (AnchorPositioningCSSSupported) { // The `anchor-name` property seems to not be isolated between instances in Safari Technology Preview 220 (18.4). @@ -448,7 +444,12 @@ export class BaseDropdown extends FASTElement { * @public */ public get enabledOptions(): DropdownOption[] { - return this.listbox?.enabledOptions ?? []; + return ( + this.listbox?.enabledOptions ?? + Array.from(this.querySelectorAll('*')).filter( + (o): o is DropdownOption => isDropdownOption(o) && !o.disabled, + ) + ); } /** @@ -513,7 +514,9 @@ export class BaseDropdown extends FASTElement { * @public */ public get options(): DropdownOption[] { - return this.listbox?.options ?? []; + return ( + this.listbox?.options ?? Array.from(this.querySelectorAll('*')).filter(o => isDropdownOption(o)) + ); } /** diff --git a/packages/web-components/src/dropdown/dropdown.definition-async.ts b/packages/web-components/src/dropdown/dropdown.definition-async.ts new file mode 100644 index 00000000000000..bc13f53701b301 --- /dev/null +++ b/packages/web-components/src/dropdown/dropdown.definition-async.ts @@ -0,0 +1,15 @@ +import type { PartialFASTElementDefinition } from '@microsoft/fast-element'; +import { tagName } from './dropdown.options.js'; + +/** + * The async definition configuration for the fluent-dropdown element. + * + * @public + * @remarks + * This is used in server-side rendering (SSR) scenarios where the template + * is provided as a deferred option to be hydrated later. + */ +export const definition: PartialFASTElementDefinition = { + name: tagName, + templateOptions: 'defer-and-hydrate', +}; diff --git a/packages/web-components/src/listbox/define-async.ts b/packages/web-components/src/listbox/define-async.ts new file mode 100644 index 00000000000000..e24628e1317941 --- /dev/null +++ b/packages/web-components/src/listbox/define-async.ts @@ -0,0 +1,5 @@ +import { RenderableFASTElement } from '@microsoft/fast-html'; +import { definition } from './listbox.definition-async.js'; +import { Listbox } from './listbox.js'; + +RenderableFASTElement(Listbox).defineAsync(definition); diff --git a/packages/web-components/src/listbox/listbox.definition-async.ts b/packages/web-components/src/listbox/listbox.definition-async.ts new file mode 100644 index 00000000000000..1aa451393212d5 --- /dev/null +++ b/packages/web-components/src/listbox/listbox.definition-async.ts @@ -0,0 +1,15 @@ +import type { PartialFASTElementDefinition } from '@microsoft/fast-element'; +import { tagName } from './listbox.options.js'; + +/** + * The async definition configuration for the fluent-listbox element. + * + * @public + * @remarks + * This is used in server-side rendering (SSR) scenarios where the template + * is provided as a deferred option to be hydrated later. + */ +export const definition: PartialFASTElementDefinition = { + name: tagName, + templateOptions: 'defer-and-hydrate', +}; diff --git a/packages/web-components/src/option/define-async.ts b/packages/web-components/src/option/define-async.ts new file mode 100644 index 00000000000000..a8a18ce86fef03 --- /dev/null +++ b/packages/web-components/src/option/define-async.ts @@ -0,0 +1,5 @@ +import { RenderableFASTElement } from '@microsoft/fast-html'; +import { definition } from './option.definition-async.js'; +import { DropdownOption } from './option.js'; + +RenderableFASTElement(DropdownOption).defineAsync(definition); diff --git a/packages/web-components/src/option/option.definition-async.ts b/packages/web-components/src/option/option.definition-async.ts new file mode 100644 index 00000000000000..0c6a27c210d325 --- /dev/null +++ b/packages/web-components/src/option/option.definition-async.ts @@ -0,0 +1,15 @@ +import type { PartialFASTElementDefinition } from '@microsoft/fast-element'; +import { tagName } from './option.options.js'; + +/** + * The async definition configuration for the fluent-option element. + * + * @public + * @remarks + * This is used in server-side rendering (SSR) scenarios where the template + * is provided as a deferred option to be hydrated later. + */ +export const definition: PartialFASTElementDefinition = { + name: tagName, + templateOptions: 'defer-and-hydrate', +}; From a9f66f8362b83a6ba638cb91b51a855dbfb1ab81 Mon Sep 17 00:00:00 2001 From: John Kreitlow <863023+radium-v@users.noreply.github.com> Date: Sat, 23 May 2026 22:52:38 -0700 Subject: [PATCH 16/44] add DSD modules for field --- .../web-components/src/field/define-async.ts | 5 +++++ .../src/field/field.definition-async.ts | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 packages/web-components/src/field/define-async.ts create mode 100644 packages/web-components/src/field/field.definition-async.ts diff --git a/packages/web-components/src/field/define-async.ts b/packages/web-components/src/field/define-async.ts new file mode 100644 index 00000000000000..f8ed499e649d83 --- /dev/null +++ b/packages/web-components/src/field/define-async.ts @@ -0,0 +1,5 @@ +import { RenderableFASTElement } from '@microsoft/fast-html'; +import { definition } from './field.definition-async.js'; +import { Field } from './field.js'; + +RenderableFASTElement(Field).defineAsync(definition); diff --git a/packages/web-components/src/field/field.definition-async.ts b/packages/web-components/src/field/field.definition-async.ts new file mode 100644 index 00000000000000..0dc76c88961f24 --- /dev/null +++ b/packages/web-components/src/field/field.definition-async.ts @@ -0,0 +1,18 @@ +import type { PartialFASTElementDefinition } from '@microsoft/fast-element'; +import { tagName } from './field.options.js'; + +/** + * The async definition configuration for the fluent-field element. + * + * @public + * @remarks + * This is used in server-side rendering (SSR) scenarios where the template + * is provided as a deferred option to be hydrated later. + */ +export const definition: PartialFASTElementDefinition = { + name: tagName, + templateOptions: 'defer-and-hydrate', + shadowOptions: { + delegatesFocus: true, + }, +}; From bc6ea6e49099f004c9b949b066af0b943866aa37 Mon Sep 17 00:00:00 2001 From: John Kreitlow <863023+radium-v@users.noreply.github.com> Date: Sun, 24 May 2026 19:29:10 -0700 Subject: [PATCH 17/44] add DSD modules for image --- packages/web-components/src/image/define-async.ts | 5 +++++ .../src/image/image.definition-async.ts | 15 +++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 packages/web-components/src/image/define-async.ts create mode 100644 packages/web-components/src/image/image.definition-async.ts diff --git a/packages/web-components/src/image/define-async.ts b/packages/web-components/src/image/define-async.ts new file mode 100644 index 00000000000000..870830b404310a --- /dev/null +++ b/packages/web-components/src/image/define-async.ts @@ -0,0 +1,5 @@ +import { RenderableFASTElement } from '@microsoft/fast-html'; +import { definition } from './image.definition-async.js'; +import { Image } from './image.js'; + +RenderableFASTElement(Image).defineAsync(definition); diff --git a/packages/web-components/src/image/image.definition-async.ts b/packages/web-components/src/image/image.definition-async.ts new file mode 100644 index 00000000000000..6b8aafbd2206c0 --- /dev/null +++ b/packages/web-components/src/image/image.definition-async.ts @@ -0,0 +1,15 @@ +import type { PartialFASTElementDefinition } from '@microsoft/fast-element'; +import { tagName } from './image.options.js'; + +/** + * The async definition configuration for the fluent-image element. + * + * @public + * @remarks + * This is used in server-side rendering (SSR) scenarios where the template + * is provided as a deferred option to be hydrated later. + */ +export const definition: PartialFASTElementDefinition = { + name: tagName, + templateOptions: 'defer-and-hydrate', +}; From 2956f84070df64582279a6f2c6e6a4c899bdfe5f Mon Sep 17 00:00:00 2001 From: John Kreitlow <863023+radium-v@users.noreply.github.com> Date: Sun, 24 May 2026 19:34:28 -0700 Subject: [PATCH 18/44] add DSD modules for label --- packages/web-components/src/label/define-async.ts | 5 +++++ .../src/label/label.definition-async.ts | 15 +++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 packages/web-components/src/label/define-async.ts create mode 100644 packages/web-components/src/label/label.definition-async.ts diff --git a/packages/web-components/src/label/define-async.ts b/packages/web-components/src/label/define-async.ts new file mode 100644 index 00000000000000..8ebd43f6a12dd2 --- /dev/null +++ b/packages/web-components/src/label/define-async.ts @@ -0,0 +1,5 @@ +import { RenderableFASTElement } from '@microsoft/fast-html'; +import { definition } from './label.definition-async.js'; +import { Label } from './label.js'; + +RenderableFASTElement(Label).defineAsync(definition); diff --git a/packages/web-components/src/label/label.definition-async.ts b/packages/web-components/src/label/label.definition-async.ts new file mode 100644 index 00000000000000..920ed9210dd562 --- /dev/null +++ b/packages/web-components/src/label/label.definition-async.ts @@ -0,0 +1,15 @@ +import type { PartialFASTElementDefinition } from '@microsoft/fast-element'; +import { tagName } from './label.options.js'; + +/** + * The async definition configuration for the fluent-label element. + * + * @public + * @remarks + * This is used in server-side rendering (SSR) scenarios where the template + * is provided as a deferred option to be hydrated later. + */ +export const definition: PartialFASTElementDefinition = { + name: tagName, + templateOptions: 'defer-and-hydrate', +}; From 74a14d8e9d9d74d6018d0197aa04da8ff69b1f58 Mon Sep 17 00:00:00 2001 From: John Kreitlow <863023+radium-v@users.noreply.github.com> Date: Sun, 24 May 2026 19:35:06 -0700 Subject: [PATCH 19/44] add DSD modules for link --- packages/web-components/src/link/define-async.ts | 5 +++++ .../src/link/link.definition-async.ts | 15 +++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 packages/web-components/src/link/define-async.ts create mode 100644 packages/web-components/src/link/link.definition-async.ts diff --git a/packages/web-components/src/link/define-async.ts b/packages/web-components/src/link/define-async.ts new file mode 100644 index 00000000000000..5afa6875316223 --- /dev/null +++ b/packages/web-components/src/link/define-async.ts @@ -0,0 +1,5 @@ +import { RenderableFASTElement } from '@microsoft/fast-html'; +import { definition } from './link.definition-async.js'; +import { Link } from './link.js'; + +RenderableFASTElement(Link).defineAsync(definition); diff --git a/packages/web-components/src/link/link.definition-async.ts b/packages/web-components/src/link/link.definition-async.ts new file mode 100644 index 00000000000000..200ff916aa0e93 --- /dev/null +++ b/packages/web-components/src/link/link.definition-async.ts @@ -0,0 +1,15 @@ +import type { PartialFASTElementDefinition } from '@microsoft/fast-element'; +import { tagName } from './link.options.js'; + +/** + * The async definition configuration for the fluent-link element. + * + * @public + * @remarks + * This is used in server-side rendering (SSR) scenarios where the template + * is provided as a deferred option to be hydrated later. + */ +export const definition: PartialFASTElementDefinition = { + name: tagName, + templateOptions: 'defer-and-hydrate', +}; From d51875efd539e338af6475775f3bec5ac1fc4529 Mon Sep 17 00:00:00 2001 From: John Kreitlow <863023+radium-v@users.noreply.github.com> Date: Sun, 24 May 2026 19:48:11 -0700 Subject: [PATCH 20/44] add DSD modules for menu --- packages/web-components/src/menu/define-async.ts | 5 +++++ .../src/menu/menu.definition-async.ts | 15 +++++++++++++++ packages/web-components/src/menu/menu.template.ts | 8 +------- 3 files changed, 21 insertions(+), 7 deletions(-) create mode 100644 packages/web-components/src/menu/define-async.ts create mode 100644 packages/web-components/src/menu/menu.definition-async.ts diff --git a/packages/web-components/src/menu/define-async.ts b/packages/web-components/src/menu/define-async.ts new file mode 100644 index 00000000000000..07a9465cea837a --- /dev/null +++ b/packages/web-components/src/menu/define-async.ts @@ -0,0 +1,5 @@ +import { RenderableFASTElement } from '@microsoft/fast-html'; +import { Menu } from './menu.js'; +import { definition } from './menu.definition-async.js'; + +RenderableFASTElement(Menu).defineAsync(definition); diff --git a/packages/web-components/src/menu/menu.definition-async.ts b/packages/web-components/src/menu/menu.definition-async.ts new file mode 100644 index 00000000000000..d7c3bc1380d14f --- /dev/null +++ b/packages/web-components/src/menu/menu.definition-async.ts @@ -0,0 +1,15 @@ +import type { PartialFASTElementDefinition } from '@microsoft/fast-element'; +import { tagName } from './menu.options.js'; + +/** + * The async definition configuration for the fluent-menu element. + * + * @public + * @remarks + * This is used in server-side rendering (SSR) scenarios where the template + * is provided as a deferred option to be hydrated later. + */ +export const definition: PartialFASTElementDefinition = { + name: tagName, + templateOptions: 'defer-and-hydrate', +}; diff --git a/packages/web-components/src/menu/menu.template.ts b/packages/web-components/src/menu/menu.template.ts index c934c1f90b50be..96608950f72791 100644 --- a/packages/web-components/src/menu/menu.template.ts +++ b/packages/web-components/src/menu/menu.template.ts @@ -3,13 +3,7 @@ import type { Menu } from './menu.js'; export function menuTemplate(): ElementViewTemplate { return html` -