Skip to content

Commit dae2068

Browse files
committed
allow imperative defining of associated elements
1 parent e592012 commit dae2068

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
}
@@ -59,7 +90,7 @@ export default class AutocompleteElement extends HTMLElement {
5990
fetchResult = fragment
6091

6192
static get observedAttributes(): string[] {
62-
return ['open', 'value']
93+
return ['open', 'value', 'for']
6394
}
6495

6596
attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
@@ -68,6 +99,10 @@ export default class AutocompleteElement extends HTMLElement {
6899
const autocomplete = state.get(this)
69100
if (!autocomplete) return
70101

102+
if (this.forElement !== state.get(this)?.results || this.inputElement !== state.get(this)?.input) {
103+
this.#reattachState()
104+
}
105+
71106
switch (name) {
72107
case 'open':
73108
newValue === null ? autocomplete.close() : autocomplete.open()

test/test.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,34 @@ describe('auto-complete element', function () {
270270
})
271271
})
272272

273+
describe('redefining elements', () => {
274+
beforeEach(function () {
275+
document.body.innerHTML = `
276+
<div id="mocha-fixture">
277+
<auto-complete src="/search" data-autoselect="true">
278+
<input type="text">
279+
<input id="second" type="text">
280+
<ul></ul>
281+
<div id="popup-feedback"></div>
282+
</auto-complete>
283+
</div>
284+
`
285+
})
286+
287+
it('changes where content gets rendered based on properties', async function () {
288+
const container = document.querySelector('auto-complete')
289+
const input = container.querySelector('input#second')
290+
const list = container.querySelector('ul')
291+
container.forElement = list
292+
container.inputElement = input
293+
294+
triggerInput(input, 'hub')
295+
await once(container, 'loadend')
296+
297+
assert.equal(5, list.children.length)
298+
})
299+
})
300+
273301
describe('shadowdom', () => {
274302
let shadow = null
275303
beforeEach(function () {

0 commit comments

Comments
 (0)