Skip to content

Commit f14f04d

Browse files
committed
allow imperative defining of associated elements
1 parent b02a30a commit f14f04d

File tree

2 files changed

+74
-11
lines changed

2 files changed

+74
-11
lines changed

src/auto-complete-element.ts

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,40 @@ const state = new WeakMap()
66

77
// eslint-disable-next-line custom-elements/file-name-matches-element
88
export default class AutocompleteElement extends HTMLElement {
9+
#forElement: HTMLElement | null = null
10+
get forElement(): HTMLElement | null {
11+
if (this.#forElement?.isConnected) {
12+
return this.#forElement
13+
}
14+
const id = this.getAttribute('for')
15+
const root = this.getRootNode()
16+
if (id && (root instanceof Document || root instanceof ShadowRoot)) {
17+
return root.getElementById(id)
18+
}
19+
return null
20+
}
21+
22+
set forElement(element: HTMLElement | null) {
23+
this.#forElement = element
24+
this.setAttribute('for', '')
25+
}
26+
27+
#inputElement: HTMLInputElement | null = null
28+
get inputElement(): HTMLInputElement | null {
29+
if (this.#inputElement?.isConnected) {
30+
return this.#inputElement
31+
}
32+
return this.querySelector<HTMLInputElement>('input')
33+
}
34+
35+
set inputElement(input: HTMLInputElement | null) {
36+
this.#inputElement = input
37+
this.#reattachState()
38+
}
39+
940
connectedCallback(): void {
10-
const listId = this.getAttribute('for')
1141
if (!this.isConnected) return
12-
if (!listId) return
13-
14-
// eslint-disable-next-line custom-elements/no-dom-traversal-in-connectedcallback
15-
const input = this.querySelector('input')
16-
const results = (this.getRootNode() as Document).getElementById(listId)
17-
if (!(input instanceof HTMLInputElement) || !results) return
18-
const autoselectEnabled = this.getAttribute('data-autoselect') === 'true'
19-
state.set(this, new Autocomplete(this, input, results, autoselectEnabled))
20-
results.setAttribute('role', 'listbox')
42+
this.#reattachState()
2143
}
2244

2345
disconnectedCallback(): void {
@@ -28,6 +50,15 @@ export default class AutocompleteElement extends HTMLElement {
2850
}
2951
}
3052

53+
#reattachState() {
54+
state.get(this)?.destroy()
55+
const {forElement, inputElement} = this
56+
if (!forElement || !inputElement) return
57+
const autoselectEnabled = this.getAttribute('data-autoselect') === 'true'
58+
state.set(this, new Autocomplete(this, inputElement, forElement, autoselectEnabled))
59+
forElement.setAttribute('role', 'listbox')
60+
}
61+
3162
get src(): string {
3263
return this.getAttribute('src') || ''
3364
}
@@ -67,7 +98,7 @@ export default class AutocompleteElement extends HTMLElement {
6798
fetchResult = fragment
6899

69100
static get observedAttributes(): string[] {
70-
return ['open', 'value']
101+
return ['open', 'value', 'for']
71102
}
72103

73104
attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
@@ -76,6 +107,10 @@ export default class AutocompleteElement extends HTMLElement {
76107
const autocomplete = state.get(this)
77108
if (!autocomplete) return
78109

110+
if (this.forElement !== state.get(this)?.results || this.inputElement !== state.get(this)?.input) {
111+
this.#reattachState()
112+
}
113+
79114
switch (name) {
80115
case 'open':
81116
newValue === null ? autocomplete.close() : autocomplete.open()

test/test.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,34 @@ describe('auto-complete element', function () {
342342
assert.equal(5, popup.children.length)
343343
})
344344
})
345+
346+
describe('redefining elements', () => {
347+
beforeEach(function () {
348+
document.body.innerHTML = `
349+
<div id="mocha-fixture">
350+
<auto-complete src="/search" data-autoselect="true">
351+
<input type="text">
352+
<input id="second" type="text">
353+
<ul></ul>
354+
<div id="popup-feedback"></div>
355+
</auto-complete>
356+
</div>
357+
`
358+
})
359+
360+
it('changes where content gets rendered based on properties', async function () {
361+
const container = document.querySelector('auto-complete')
362+
const input = container.querySelector('input#second')
363+
const list = container.querySelector('ul')
364+
container.forElement = list
365+
container.inputElement = input
366+
367+
triggerInput(input, 'hub')
368+
await once(container, 'loadend')
369+
370+
assert.equal(5, list.children.length)
371+
})
372+
})
345373
})
346374

347375
function waitForElementToChange(el) {

0 commit comments

Comments
 (0)