diff --git a/change/@microsoft-fast-element-19ee7846-01d0-4dc8-babc-29a4537526a5.json b/change/@microsoft-fast-element-19ee7846-01d0-4dc8-babc-29a4537526a5.json new file mode 100644 index 00000000000..adaa1c19fae --- /dev/null +++ b/change/@microsoft-fast-element-19ee7846-01d0-4dc8-babc-29a4537526a5.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "feat: handle existing shadow roots when upgrading", + "packageName": "@microsoft/fast-element", + "email": "roeisenb@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/web-components/fast-element/src/components/controller.spec.ts b/packages/web-components/fast-element/src/components/controller.spec.ts index 172d741baee..33bcd7a35da 100644 --- a/packages/web-components/fast-element/src/components/controller.spec.ts +++ b/packages/web-components/fast-element/src/components/controller.spec.ts @@ -388,6 +388,7 @@ describe("The Controller", () => { expect(element.shadowRoot?.contains(style)).to.equal(false); }); + it("should attach and detach the HTMLStyleElement supplied to .addStyles() and .removeStyles() to the shadowRoot", () => { const { controller, element } = createController({ shadowOptions: { @@ -510,5 +511,29 @@ describe("The Controller", () => { controller.removeBehaviors([behavior], true); expect(behavior.bound).to.equal(false); }); - }) + }); + + context("with pre-existing shadow dom on the host", () => { + it("re-renders the view during connect", async () => { + const name = uniqueElementName(); + const element = document.createElement(name); + const root = element.attachShadow({ mode: 'open' }); + root.innerHTML = 'Test 1'; + + document.body.append(element); + + new FASTElementDefinition( + class TestElement extends FASTElement { + static definition = { + name, + template: html`Test 2` + }; + } + ).define(); + + expect(root.innerHTML).to.equal("Test 2"); + + document.body.removeChild(element); + }); + }); }); diff --git a/packages/web-components/fast-element/src/components/controller.ts b/packages/web-components/fast-element/src/components/controller.ts index 6730134b29f..521d2457a9a 100644 --- a/packages/web-components/fast-element/src/components/controller.ts +++ b/packages/web-components/fast-element/src/components/controller.ts @@ -26,6 +26,7 @@ export class Controller extends PropertyChangeNotifier { private boundObservables: Record | null = null; private behaviors: Map, number> | null = null; private needsInitialization: boolean = true; + private hasExistingShadowRoot = false; private _template: ElementViewTemplate | null = null; private _styles: ElementStyles | null = null; private _isConnected: boolean = false; @@ -159,10 +160,16 @@ export class Controller extends PropertyChangeNotifier { const shadowOptions = definition.shadowOptions; if (shadowOptions !== void 0) { - const shadowRoot = element.attachShadow(shadowOptions); + let shadowRoot = element.shadowRoot; - if (shadowOptions.mode === "closed") { - shadowRoots.set(element, shadowRoot); + if (shadowRoot) { + this.hasExistingShadowRoot = true; + } else { + shadowRoot = element.attachShadow(shadowOptions); + + if (shadowOptions.mode === "closed") { + shadowRoots.set(element, shadowRoot); + } } } @@ -434,7 +441,9 @@ export class Controller extends PropertyChangeNotifier { // If there's already a view, we need to unbind and remove through dispose. this.view.dispose(); (this as Mutable).view = null; - } else if (!this.needsInitialization) { + } else if (!this.needsInitialization || this.hasExistingShadowRoot) { + this.hasExistingShadowRoot = false; + // If there was previous custom rendering, we need to clear out the host. for (let child = host.firstChild; child !== null; child = host.firstChild) { host.removeChild(child);