@@ -6,18 +6,40 @@ const state = new WeakMap()
6
6
7
7
// eslint-disable-next-line custom-elements/file-name-matches-element
8
8
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
+
9
40
connectedCallback ( ) : void {
10
- const listId = this . getAttribute ( 'for' )
11
41
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( )
21
43
}
22
44
23
45
disconnectedCallback ( ) : void {
@@ -28,6 +50,15 @@ export default class AutocompleteElement extends HTMLElement {
28
50
}
29
51
}
30
52
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
+
31
62
get src ( ) : string {
32
63
return this . getAttribute ( 'src' ) || ''
33
64
}
@@ -67,7 +98,7 @@ export default class AutocompleteElement extends HTMLElement {
67
98
fetchResult = fragment
68
99
69
100
static get observedAttributes ( ) : string [ ] {
70
- return [ 'open' , 'value' ]
101
+ return [ 'open' , 'value' , 'for' ]
71
102
}
72
103
73
104
attributeChangedCallback ( name : string , oldValue : string , newValue : string ) : void {
@@ -76,6 +107,10 @@ export default class AutocompleteElement extends HTMLElement {
76
107
const autocomplete = state . get ( this )
77
108
if ( ! autocomplete ) return
78
109
110
+ if ( this . forElement !== state . get ( this ) ?. results || this . inputElement !== state . get ( this ) ?. input ) {
111
+ this . #reattachState( )
112
+ }
113
+
79
114
switch ( name ) {
80
115
case 'open' :
81
116
newValue === null ? autocomplete . close ( ) : autocomplete . open ( )
0 commit comments