Skip to content

Commit dc284dc

Browse files
authored
Merge pull request #308 from github/wm-kc/observe-shadow-root-on-connected-callback
Observe shadow root on connected callback
2 parents cd2c17e + 7163497 commit dc284dc

File tree

6 files changed

+67
-24
lines changed

6 files changed

+67
-24
lines changed

docs/_guide/rendering-2.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ class HelloWorldElement extends HTMLElement {
8989
}
9090

9191
attributeChangedCallback() {
92-
render(() => html`
92+
render(html`
9393
<div>
9494
Hello <span>${ this.name }</span>
9595
</div>`,
@@ -125,7 +125,7 @@ class HelloWorldElement extends HTMLElement {
125125
}
126126

127127
update() {
128-
render(() => html`
128+
render(html`
129129
<div>
130130
Hello <span>${ this.#name }</span>
131131
</div>`,

docs/_guide/rendering.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class HelloWorldElement extends HTMLElement {
6666
}
6767

6868
attributeChangedCallback() {
69-
render(() => html`
69+
render(html`
7070
<div>
7171
Hello <span>${ this.name }</span>
7272
</div>`,
@@ -102,7 +102,7 @@ class HelloWorldElement extends HTMLElement {
102102
}
103103

104104
update() {
105-
render(() => html`
105+
render(html`
106106
<div>
107107
Hello <span>${ this.#name }</span>
108108
</div>`,

src/core.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {bind, bindShadow} from './bind.js'
33
import {autoShadowRoot} from './auto-shadow-root.js'
44
import {defineObservedAttributes, initializeAttrs} from './attr.js'
55
import type {CustomElementClass} from './custom-element.js'
6+
import {observe} from './lazy-define.js'
67

78
const symbol = Symbol.for('catalyst')
89

@@ -57,7 +58,10 @@ export class CatalystDelegate {
5758
initializeAttrs(instance)
5859
bind(instance)
5960
connectedCallback?.call(instance)
60-
if (instance.shadowRoot) bindShadow(instance.shadowRoot)
61+
if (instance.shadowRoot) {
62+
bindShadow(instance.shadowRoot)
63+
observe(instance.shadowRoot)
64+
}
6165
}
6266

6367
disconnectedCallback(element: HTMLElement, disconnectedCallback: () => void) {

src/lazy-define.ts

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -55,21 +55,24 @@ const strategies: Record<string, Strategy> = {
5555
visible
5656
}
5757

58-
const timers = new WeakMap<Element, number>()
59-
function scan(node: Element) {
60-
cancelAnimationFrame(timers.get(node) || 0)
58+
type ElementLike = Element | Document | ShadowRoot
59+
60+
const timers = new WeakMap<ElementLike, number>()
61+
function scan(element: ElementLike) {
62+
cancelAnimationFrame(timers.get(element) || 0)
6163
timers.set(
62-
node,
64+
element,
6365
requestAnimationFrame(() => {
6466
for (const tagName of dynamicElements.keys()) {
65-
const child: Element | null = node.matches(tagName) ? node : node.querySelector(tagName)
67+
const child: Element | null =
68+
element instanceof Element && element.matches(tagName) ? element : element.querySelector(tagName)
6669
if (customElements.get(tagName) || child) {
6770
const strategyName = (child?.getAttribute('data-load-on') || 'ready') as keyof typeof strategies
6871
const strategy = strategyName in strategies ? strategies[strategyName] : strategies.ready
6972
// eslint-disable-next-line github/no-then
7073
for (const cb of dynamicElements.get(tagName) || []) strategy(tagName).then(cb)
7174
dynamicElements.delete(tagName)
72-
timers.delete(node)
75+
timers.delete(element)
7376
}
7477
}
7578
})
@@ -82,17 +85,20 @@ export function lazyDefine(tagName: string, callback: () => void) {
8285
if (!dynamicElements.has(tagName)) dynamicElements.set(tagName, new Set<() => void>())
8386
dynamicElements.get(tagName)!.add(callback)
8487

85-
scan(document.body)
88+
observe(document)
89+
}
8690

87-
if (!elementLoader) {
88-
elementLoader = new MutationObserver(mutations => {
89-
if (!dynamicElements.size) return
90-
for (const mutation of mutations) {
91-
for (const node of mutation.addedNodes) {
92-
if (node instanceof Element) scan(node)
93-
}
91+
export function observe(target: ElementLike): void {
92+
elementLoader ||= new MutationObserver(mutations => {
93+
if (!dynamicElements.size) return
94+
for (const mutation of mutations) {
95+
for (const node of mutation.addedNodes) {
96+
if (node instanceof Element) scan(node)
9497
}
95-
})
96-
elementLoader.observe(document, {subtree: true, childList: true})
97-
}
98+
}
99+
})
100+
101+
scan(target)
102+
103+
elementLoader.observe(target, {subtree: true, childList: true})
98104
}

test/controller.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import {expect, fixture, html} from '@open-wc/testing'
2-
import {replace, fake} from 'sinon'
2+
import {replace, fake, spy} from 'sinon'
33
import {controller} from '../src/controller.js'
44
import {attr} from '../src/attr.js'
5+
import {lazyDefine} from '../src/lazy-define.js'
56

67
describe('controller', () => {
78
let instance
@@ -65,6 +66,23 @@ describe('controller', () => {
6566
expect(instance.foo).to.have.callCount(1)
6667
})
6768

69+
it('observes changes on shadowRoots', async () => {
70+
const onDefine = spy()
71+
lazyDefine('nested-shadow-element', onDefine)
72+
73+
@controller
74+
class ControllerObserveShadowElement extends HTMLElement {
75+
connectedCallback() {
76+
const shadowRoot = this.attachShadow({mode: 'open'})
77+
// eslint-disable-next-line github/unescaped-html-literal
78+
shadowRoot.innerHTML = '<div><nested-shadow-element></nested-shadow-element></div>'
79+
}
80+
}
81+
instance = await fixture<ControllerObserveShadowElement>(html`<controller-observe-shadow />`)
82+
83+
expect(onDefine).to.be.callCount(1)
84+
})
85+
6886
it('binds auto shadowRoots', async () => {
6987
@controller
7088
class ControllerBindAutoShadowElement extends HTMLElement {

test/lazy-define.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {expect, fixture, html} from '@open-wc/testing'
22
import {spy} from 'sinon'
3-
import {lazyDefine} from '../src/lazy-define.js'
3+
import {lazyDefine, observe} from '../src/lazy-define.js'
44

55
const animationFrame = () => new Promise<unknown>(resolve => requestAnimationFrame(resolve))
66

@@ -45,6 +45,21 @@ describe('lazyDefine', () => {
4545

4646
expect(onDefine).to.be.callCount(2)
4747
})
48+
49+
it('lazy loads elements in shadow roots', async () => {
50+
const onDefine = spy()
51+
lazyDefine('nested-shadow-element', onDefine)
52+
53+
const el = await fixture(html` <div></div> `)
54+
const shadowRoot = el.attachShadow({mode: 'open'})
55+
observe(shadowRoot)
56+
// eslint-disable-next-line github/unescaped-html-literal
57+
shadowRoot.innerHTML = '<div><nested-shadow-element></nested-shadow-element></div>'
58+
59+
await animationFrame()
60+
61+
expect(onDefine).to.be.callCount(1)
62+
})
4863
})
4964

5065
describe('firstInteraction strategy', () => {

0 commit comments

Comments
 (0)