@@ -85,7 +85,7 @@ export const proxyComponent = (
85
85
if ( BUILD . observeAttribute && ( ! BUILD . lazyLoad || flags & PROXY_FLAGS . isElementConstructor ) ) {
86
86
const attrNameToPropName = new Map ( ) ;
87
87
88
- prototype . attributeChangedCallback = function ( attrName : string , _oldValue : string , newValue : string ) {
88
+ prototype . attributeChangedCallback = function ( attrName : string , oldValue : string , newValue : string ) {
89
89
plt . jmp ( ( ) => {
90
90
const propName = attrNameToPropName . get ( attrName ) ;
91
91
@@ -133,25 +133,60 @@ export const proxyComponent = (
133
133
// if the propName exists on the prototype of `Cstr`, this update may be a result of Stencil using native
134
134
// APIs to reflect props as attributes. Calls to `setAttribute(someElement, propName)` will result in
135
135
// `propName` to be converted to a `DOMString`, which may not be what we want for other primitive props.
136
+ return ;
137
+ } else if ( propName == null ) {
138
+ // At this point we should know this is not a "member", so we can treat it like watching an attribute
139
+ // on a vanilla web component
140
+ const hostRef = getHostRef ( this ) ;
141
+ const flags = hostRef ?. $flags$ ;
142
+
143
+ // We only want to trigger the callback(s) if:
144
+ // 1. The instance is ready
145
+ // 2. The watchers are ready
146
+ // 3. The value has changed
147
+ if (
148
+ ! ( flags & HOST_FLAGS . isConstructingInstance ) &&
149
+ flags & HOST_FLAGS . isWatchReady &&
150
+ newValue !== oldValue
151
+ ) {
152
+ const elm = BUILD . lazyLoad ? hostRef . $hostElement$ : this ;
153
+ const instance = BUILD . lazyLoad ? hostRef . $lazyInstance$ : ( elm as any ) ;
154
+ const entry = cmpMeta . $watchers$ [ attrName ] ;
155
+ entry ?. forEach ( ( callbackName ) => {
156
+ if ( instance [ callbackName ] != null ) {
157
+ instance [ callbackName ] . call ( instance , newValue , oldValue , attrName ) ;
158
+ }
159
+ } ) ;
160
+ }
161
+
136
162
return ;
137
163
}
138
164
139
165
this [ propName ] = newValue === null && typeof this [ propName ] === 'boolean' ? false : newValue ;
140
166
} ) ;
141
167
} ;
142
168
143
- // create an array of attributes to observe
144
- // and also create a map of html attribute name to js property name
145
- Cstr . observedAttributes = members
146
- . filter ( ( [ _ , m ] ) => m [ 0 ] & MEMBER_FLAGS . HasAttribute ) // filter to only keep props that should match attributes
147
- . map ( ( [ propName , m ] ) => {
148
- const attrName = m [ 1 ] || propName ;
149
- attrNameToPropName . set ( attrName , propName ) ;
150
- if ( BUILD . reflect && m [ 0 ] & MEMBER_FLAGS . ReflectAttr ) {
151
- cmpMeta . $attrsToReflect$ . push ( [ propName , attrName ] ) ;
152
- }
153
- return attrName ;
154
- } ) ;
169
+ // Create an array of attributes to observe
170
+ // This list in comprised of all strings used within a `@Watch()` decorator
171
+ // on a component as well as any Stencil-specific "members" (`@Prop()`s and `@State()`s).
172
+ // As such, there is no way to guarantee type-safety here that a user hasn't entered
173
+ // an invalid attribute.
174
+ Cstr . observedAttributes = Array . from (
175
+ new Set ( [
176
+ ...Object . keys ( cmpMeta . $watchers$ ?? { } ) ,
177
+ ...members
178
+ . filter ( ( [ _ , m ] ) => m [ 0 ] & MEMBER_FLAGS . HasAttribute )
179
+ . map ( ( [ propName , m ] ) => {
180
+ const attrName = m [ 1 ] || propName ;
181
+ attrNameToPropName . set ( attrName , propName ) ;
182
+ if ( BUILD . reflect && m [ 0 ] & MEMBER_FLAGS . ReflectAttr ) {
183
+ cmpMeta . $attrsToReflect$ . push ( [ propName , attrName ] ) ;
184
+ }
185
+
186
+ return attrName ;
187
+ } ) ,
188
+ ] ) ,
189
+ ) ;
155
190
}
156
191
}
157
192
0 commit comments