Skip to content

Commit c1a201f

Browse files
committed
feat(findtarget): support traversing shadowDOM
1 parent add9f8f commit c1a201f

2 files changed

Lines changed: 63 additions & 16 deletions

File tree

src/findtarget.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
2-
* findTarget will run `querySelectorAll` against the given controller,
3-
* returning any the first child that:
2+
* findTarget will run `querySelectorAll` against the given controller, plus
3+
* its shadowRoot, returning any the first child that:
44
*
55
* - Matches the selector of `[data-target~="tag.name"]` where tag is the
66
* tagName of the given HTMLElement, and `name` is the given `name` argument.
@@ -12,6 +12,11 @@
1212
*/
1313
export function findTarget(controller: HTMLElement, name: string): Element | undefined {
1414
const tag = controller.tagName.toLowerCase()
15+
if (controller.shadowRoot) {
16+
for (const el of controller.shadowRoot.querySelectorAll(`[data-target~="${tag}.${name}"]`)) {
17+
if (!el.closest(tag)) return el
18+
}
19+
}
1520
for (const el of controller.querySelectorAll(`[data-target~="${tag}.${name}"]`)) {
1621
if (el.closest(tag) === controller) return el
1722
}
@@ -20,6 +25,11 @@ export function findTarget(controller: HTMLElement, name: string): Element | und
2025
export function findTargets(controller: HTMLElement, name: string): Element[] {
2126
const tag = controller.tagName.toLowerCase()
2227
const targets = []
28+
if (controller.shadowRoot) {
29+
for (const el of controller.shadowRoot.querySelectorAll(`[data-targets~="${tag}.${name}"]`)) {
30+
if (!el.closest(tag)) targets.push(el)
31+
}
32+
}
2333
for (const el of controller.querySelectorAll(`[data-targets~="${tag}.${name}"]`)) {
2434
if (el.closest(tag) === controller) targets.push(el)
2535
}

test/findtarget.js

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -79,29 +79,66 @@ describe('findTarget', () => {
7979
expect(foundElement1).to.equal(el)
8080
expect(foundElement2).to.equal(undefined)
8181
})
82+
83+
it('returns targets from the shadowRoot, if available', () => {
84+
const instance = document.createElement('find-target-test-element')
85+
instance.attachShadow({mode: 'open'})
86+
const el = document.createElement('div')
87+
el.setAttribute('data-target', 'find-target-test-element.foobar')
88+
89+
instance.shadowRoot.appendChild(el)
90+
91+
expect(findTarget(instance, 'foobar')).to.equal(el)
92+
})
93+
94+
it('prioritises shadowRoot targets over others', () => {
95+
const instance = document.createElement('find-target-test-element')
96+
instance.attachShadow({mode: 'open'})
97+
const shadowEl = document.createElement('div')
98+
shadowEl.setAttribute('data-target', 'find-target-test-element.foobar')
99+
const lightEl = document.createElement('div')
100+
lightEl.setAttribute('data-target', 'find-target-test-element.foobar')
101+
102+
instance.shadowRoot.appendChild(shadowEl)
103+
instance.appendChild(lightEl)
104+
105+
expect(findTarget(instance, 'foobar')).to.equal(shadowEl)
106+
})
82107
})
83108

84109
describe('findTargets', () => {
85110
it('calls querySelectorAll with the controller name and target name', () => {
86111
const instance = document.createElement('find-target-test-element')
87-
chai.spy.on(instance, 'querySelectorAll', () => [])
88-
findTargets(instance, 'foo')
89-
expect(instance.querySelectorAll).to.have.been.called.once.with.exactly(
90-
'[data-targets~="find-target-test-element.foo"]'
91-
)
112+
const els = [document.createElement('div'), document.createElement('div'), document.createElement('div')]
113+
instance.append(...els)
114+
115+
els[0].setAttribute('data-targets', 'find-target-test-element.foo')
116+
els[1].setAttribute('data-targets', 'find-target-test-element.foo')
117+
118+
expect(findTargets(instance, 'foo')).to.eql([els[0], els[1]])
92119
})
93120

94121
it('returns all elements where closest tag is the controller', () => {
122+
const instance = document.createElement('find-target-test-element')
95123
const els = [document.createElement('div'), document.createElement('div'), document.createElement('div')]
124+
for (const el of els) el.setAttribute('data-targets', 'find-target-test-element.foo')
125+
const nested = document.createElement('find-target-test-element')
126+
127+
nested.append(els[1])
128+
instance.append(els[0], nested, els[2])
129+
130+
expect(findTargets(instance, 'foo')).to.eql([els[0], els[2]])
131+
})
132+
133+
it('returns all elements inside a shadow root', () => {
96134
const instance = document.createElement('find-target-test-element')
97-
chai.spy.on(instance, 'querySelectorAll', () => els)
98-
chai.spy.on(els[0], 'closest', () => instance)
99-
chai.spy.on(els[1], 'closest', () => null)
100-
chai.spy.on(els[2], 'closest', () => instance)
101-
const targets = findTargets(instance, 'foo')
102-
expect(els[0].closest).to.have.been.called.once.with.exactly('find-target-test-element')
103-
expect(els[1].closest).to.have.been.called.once.with.exactly('find-target-test-element')
104-
expect(els[2].closest).to.have.been.called.once.with.exactly('find-target-test-element')
105-
expect(targets).to.deep.equal([els[0], els[2]])
135+
instance.attachShadow({mode: 'open'})
136+
const els = [document.createElement('div'), document.createElement('div'), document.createElement('div')]
137+
for (const el of els) el.setAttribute('data-targets', 'find-target-test-element.foo')
138+
139+
instance.shadowRoot.append(els[1])
140+
instance.append(els[0], els[2])
141+
142+
expect(findTargets(instance, 'foo')).to.eql([els[1], els[0], els[2]])
106143
})
107144
})

0 commit comments

Comments
 (0)