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 }
`,
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', () => {