From 83fa8cbc2fd44a4dc1879bfc3f9e9b14ef217443 Mon Sep 17 00:00:00 2001 From: William Martin Date: Tue, 28 Feb 2023 11:16:30 +0100 Subject: [PATCH 1/2] Update docs using correct types for render --- docs/_guide/rendering-2.md | 4 ++-- docs/_guide/rendering.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/_guide/rendering-2.md b/docs/_guide/rendering-2.md index 617066d3..f8a577b1 100644 --- a/docs/_guide/rendering-2.md +++ b/docs/_guide/rendering-2.md @@ -89,7 +89,7 @@ class HelloWorldElement extends HTMLElement { } attributeChangedCallback() { - render(() => html` + render(html`
Hello ${ this.name }
`, @@ -125,7 +125,7 @@ class HelloWorldElement extends HTMLElement { } update() { - render(() => html` + render(html`
Hello ${ this.#name }
`, diff --git a/docs/_guide/rendering.md b/docs/_guide/rendering.md index 677ff6a8..4adaa4dd 100644 --- a/docs/_guide/rendering.md +++ b/docs/_guide/rendering.md @@ -66,7 +66,7 @@ class HelloWorldElement extends HTMLElement { } attributeChangedCallback() { - render(() => html` + render(html`
Hello ${ this.name }
`, @@ -102,7 +102,7 @@ class HelloWorldElement extends HTMLElement { } update() { - render(() => html` + render(html`
Hello ${ this.#name }
`, From 71634970dec698409c15e6364956277e19fd3529 Mon Sep 17 00:00:00 2001 From: William Martin Date: Tue, 28 Feb 2023 11:19:16 +0100 Subject: [PATCH 2/2] Observe shadow root in controller connectedCallback --- src/core.ts | 6 +++++- src/lazy-define.ts | 40 +++++++++++++++++++++++----------------- test/controller.ts | 20 +++++++++++++++++++- test/lazy-define.ts | 17 ++++++++++++++++- 4 files changed, 63 insertions(+), 20 deletions(-) diff --git a/src/core.ts b/src/core.ts index 22759e0b..43b7edfe 100644 --- a/src/core.ts +++ b/src/core.ts @@ -3,6 +3,7 @@ import {bind, bindShadow} from './bind.js' import {autoShadowRoot} from './auto-shadow-root.js' import {defineObservedAttributes, initializeAttrs} from './attr.js' import type {CustomElementClass} from './custom-element.js' +import {observe} from './lazy-define.js' const symbol = Symbol.for('catalyst') @@ -57,7 +58,10 @@ export class CatalystDelegate { initializeAttrs(instance) bind(instance) connectedCallback?.call(instance) - if (instance.shadowRoot) bindShadow(instance.shadowRoot) + if (instance.shadowRoot) { + bindShadow(instance.shadowRoot) + observe(instance.shadowRoot) + } } disconnectedCallback(element: HTMLElement, disconnectedCallback: () => void) { diff --git a/src/lazy-define.ts b/src/lazy-define.ts index e9006231..cfe826e2 100644 --- a/src/lazy-define.ts +++ b/src/lazy-define.ts @@ -55,21 +55,24 @@ const strategies: Record = { visible } -const timers = new WeakMap() -function scan(node: Element) { - cancelAnimationFrame(timers.get(node) || 0) +type ElementLike = Element | Document | ShadowRoot + +const timers = new WeakMap() +function scan(element: ElementLike) { + cancelAnimationFrame(timers.get(element) || 0) timers.set( - node, + element, requestAnimationFrame(() => { for (const tagName of dynamicElements.keys()) { - const child: Element | null = node.matches(tagName) ? node : node.querySelector(tagName) + const child: Element | null = + element instanceof Element && element.matches(tagName) ? element : element.querySelector(tagName) if (customElements.get(tagName) || child) { const strategyName = (child?.getAttribute('data-load-on') || 'ready') as keyof typeof strategies const strategy = strategyName in strategies ? strategies[strategyName] : strategies.ready // eslint-disable-next-line github/no-then for (const cb of dynamicElements.get(tagName) || []) strategy(tagName).then(cb) dynamicElements.delete(tagName) - timers.delete(node) + timers.delete(element) } } }) @@ -82,17 +85,20 @@ export function lazyDefine(tagName: string, callback: () => void) { if (!dynamicElements.has(tagName)) dynamicElements.set(tagName, new Set<() => void>()) dynamicElements.get(tagName)!.add(callback) - scan(document.body) + observe(document) +} - if (!elementLoader) { - elementLoader = new MutationObserver(mutations => { - if (!dynamicElements.size) return - for (const mutation of mutations) { - for (const node of mutation.addedNodes) { - if (node instanceof Element) scan(node) - } +export function observe(target: ElementLike): void { + elementLoader ||= new MutationObserver(mutations => { + if (!dynamicElements.size) return + for (const mutation of mutations) { + for (const node of mutation.addedNodes) { + if (node instanceof Element) scan(node) } - }) - elementLoader.observe(document, {subtree: true, childList: true}) - } + } + }) + + scan(target) + + elementLoader.observe(target, {subtree: true, childList: true}) } diff --git a/test/controller.ts b/test/controller.ts index cb6abe57..ffd5e32d 100644 --- a/test/controller.ts +++ b/test/controller.ts @@ -1,7 +1,8 @@ import {expect, fixture, html} from '@open-wc/testing' -import {replace, fake} from 'sinon' +import {replace, fake, spy} from 'sinon' import {controller} from '../src/controller.js' import {attr} from '../src/attr.js' +import {lazyDefine} from '../src/lazy-define.js' describe('controller', () => { let instance @@ -65,6 +66,23 @@ describe('controller', () => { expect(instance.foo).to.have.callCount(1) }) + it('observes changes on shadowRoots', async () => { + const onDefine = spy() + lazyDefine('nested-shadow-element', onDefine) + + @controller + class ControllerObserveShadowElement extends HTMLElement { + connectedCallback() { + const shadowRoot = this.attachShadow({mode: 'open'}) + // eslint-disable-next-line github/unescaped-html-literal + shadowRoot.innerHTML = '
' + } + } + instance = await fixture(html``) + + expect(onDefine).to.be.callCount(1) + }) + it('binds auto shadowRoots', async () => { @controller class ControllerBindAutoShadowElement extends HTMLElement { diff --git a/test/lazy-define.ts b/test/lazy-define.ts index 4b7baa49..c3ee3a83 100644 --- a/test/lazy-define.ts +++ b/test/lazy-define.ts @@ -1,6 +1,6 @@ import {expect, fixture, html} from '@open-wc/testing' import {spy} from 'sinon' -import {lazyDefine} from '../src/lazy-define.js' +import {lazyDefine, observe} from '../src/lazy-define.js' const animationFrame = () => new Promise(resolve => requestAnimationFrame(resolve)) @@ -45,6 +45,21 @@ describe('lazyDefine', () => { expect(onDefine).to.be.callCount(2) }) + + it('lazy loads elements in shadow roots', async () => { + const onDefine = spy() + lazyDefine('nested-shadow-element', onDefine) + + const el = await fixture(html`
`) + const shadowRoot = el.attachShadow({mode: 'open'}) + observe(shadowRoot) + // eslint-disable-next-line github/unescaped-html-literal + shadowRoot.innerHTML = '
' + + await animationFrame() + + expect(onDefine).to.be.callCount(1) + }) }) describe('firstInteraction strategy', () => {