@@ -113,26 +113,63 @@ export function init(forceStart = false) {
113113 } , MSG_DEBOUNCE_MS * 2 ) ;
114114 }
115115
116- getAlpineDataInstance ( node ) {
116+ /**
117+ * @param {{ _x_dataStack: Array<unknown>} | { __x: unknown} } node
118+ * @param {{ getRawInstance: boolean } } config
119+ * @returns
120+ */
121+ getAlpineDataInstance ( node , config = { getRawInstance : true } ) {
117122 if ( this . isV3 ) {
118- return node . _x_dataStack ?. [ 0 ] ;
123+ if ( config . getRawInstance || node . _x_dataStack . length === 1 ) {
124+ return node . _x_dataStack ?. [ 0 ] ;
125+ }
126+ // For accessing the data contents, we need to inherit scoped data.
127+ let i = node . _x_dataStack . length - 1 ;
128+ let mergedDataStack = { } ;
129+ const leafDataObj = { } ;
130+ while ( i >= 0 ) {
131+ const stackEntry = node . _x_dataStack [ i ] ;
132+ const isLeafStackEntry = i === 0 ;
133+ if ( ! isLeafStackEntry ) {
134+ mergedDataStack = Object . assign ( mergedDataStack , stackEntry ) ;
135+ } else {
136+ Object . entries ( Object . getOwnPropertyDescriptors ( stackEntry ) ) . forEach (
137+ ( [ prop , descriptor ] ) => {
138+ if ( ! descriptor . enumerable ) {
139+ // magics are non-enumerable
140+ return ;
141+ }
142+ if ( typeof descriptor . get === 'function' ) {
143+ // this is a getter, evaluate with nested context
144+ leafDataObj [ prop ] = descriptor . get . call ( mergedDataStack ) ;
145+ // TODO: need to hide the edit button etc, if this
146+ // doesn't have a `descriptor.set !== 'function'` function
147+ // and/or `!!descriptor.writable`
148+ } else {
149+ leafDataObj [ prop ] = descriptor . value ;
150+ }
151+ return ;
152+ } ,
153+ ) ;
154+ }
155+
156+ i -- ;
157+ }
158+ return leafDataObj ;
119159 }
120160 return node . __x ;
121161 }
122162
123163 getReadOnlyAlpineData ( node ) {
124- const alpineDataInstance = this . getAlpineDataInstance ( node ) ;
164+ const alpineDataInstance = this . getAlpineDataInstance ( node , { getRawInstance : false } ) ;
125165 if ( ! alpineDataInstance ) {
126166 if ( import . meta. env . DEV ) {
127167 console . warn ( 'element has no dataStack' , node ) ;
128168 }
129169 return ;
130170 }
131171 if ( this . isV3 ) {
132- // in v3 magics are registered on the data stack
133- return Object . fromEntries (
134- Object . entries ( alpineDataInstance ) . filter ( ( [ key ] ) => ! key . startsWith ( '$' ) ) ,
135- ) ;
172+ return alpineDataInstance ;
136173 } else {
137174 return alpineDataInstance ?. getUnobservedData ( ) ;
138175 }
@@ -293,6 +330,19 @@ export function init(forceStart = false) {
293330 let recursionDepth = 0 ;
294331 function visit ( componentData , key ) {
295332 recursionDepth += 1 ;
333+ const descriptor = Object . getOwnPropertyDescriptor ( componentData , key ) ;
334+ if ( descriptor . get && ! descriptor . set && ! descriptor . value ) {
335+ // this is a getter, we don't need to re-run getters
336+ // unless there's a setter
337+ for ( const stack of rootEl . _x_dataStack . slice ( 1 ) ) {
338+ // But ensure Alpine recomputes this effect if any of
339+ // the parents change as they could be used in the getter
340+ Object . keys ( stack ) . forEach ( ( k ) => {
341+ visit ( stack , k ) ;
342+ } ) ;
343+ }
344+ return ;
345+ }
296346 // since effects track which dependencies are accessed,
297347 // run a fake component data access so that the effect runs
298348 void componentData [ key ] ;
0 commit comments