@@ -6,17 +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
- connectedCallback ( ) : void {
10
- const listId = this . getAttribute ( 'for' )
11
- if ( ! listId ) return
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
+ }
12
21
13
- // eslint-disable-next-line custom-elements/no-dom-traversal-in-connectedcallback
14
- const input = this . querySelector ( 'input' )
15
- const results = document . getElementById ( listId )
16
- if ( ! ( input instanceof HTMLInputElement ) || ! results ) return
17
- const autoselectEnabled = this . getAttribute ( 'data-autoselect' ) === 'true'
18
- state . set ( this , new Autocomplete ( this , input , results , autoselectEnabled ) )
19
- results . setAttribute ( 'role' , 'listbox' )
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
+
40
+ connectedCallback ( ) : void {
41
+ if ( ! this . isConnected ) return
42
+ this . #reattachState( )
20
43
}
21
44
22
45
disconnectedCallback ( ) : void {
@@ -27,6 +50,15 @@ export default class AutocompleteElement extends HTMLElement {
27
50
}
28
51
}
29
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
+
30
62
get src ( ) : string {
31
63
return this . getAttribute ( 'src' ) || ''
32
64
}
@@ -66,7 +98,7 @@ export default class AutocompleteElement extends HTMLElement {
66
98
fetchResult = fragment
67
99
68
100
static get observedAttributes ( ) : string [ ] {
69
- return [ 'open' , 'value' ]
101
+ return [ 'open' , 'value' , 'for' ]
70
102
}
71
103
72
104
attributeChangedCallback ( name : string , oldValue : string , newValue : string ) : void {
@@ -75,6 +107,10 @@ export default class AutocompleteElement extends HTMLElement {
75
107
const autocomplete = state . get ( this )
76
108
if ( ! autocomplete ) return
77
109
110
+ if ( this . forElement !== state . get ( this ) ?. results || this . inputElement !== state . get ( this ) ?. input ) {
111
+ this . #reattachState( )
112
+ }
113
+
78
114
switch ( name ) {
79
115
case 'open' :
80
116
newValue === null ? autocomplete . close ( ) : autocomplete . open ( )
0 commit comments