11/** @import { ProxyMetadata, ProxyStateObject, Source, ValueOptions } from '#client' */
22import { DEV } from 'esm-env' ;
3- import { get , component_context , active_effect } from './runtime.js' ;
3+ import { UNINITIALIZED } from '../../constants.js' ;
4+ import { tracing_mode_flag } from '../flags/index.js' ;
45import {
56 array_prototype ,
67 get_descriptor ,
78 get_prototype_of ,
89 is_array ,
910 object_prototype
1011} from '../shared/utils.js' ;
11- import { check_ownership , widen_ownership } from './dev/ownership.js' ;
12- import { source , set , state , batch_onchange } from './reactivity/sources.js' ;
1312import { PROXY_ONCHANGE_SYMBOL , STATE_SYMBOL , STATE_SYMBOL_METADATA } from './constants.js' ;
14- import { UNINITIALIZED } from '../../constants.js' ;
15- import * as e from './errors.js' ;
13+ import { check_ownership , widen_ownership } from './dev/ownership.js' ;
1614import { get_stack } from './dev/tracing.js' ;
17- import { tracing_mode_flag } from '../flags/index.js' ;
15+ import * as e from './errors.js' ;
16+ import { batch_onchange , set , source , state } from './reactivity/sources.js' ;
17+ import { active_effect , component_context , get } from './runtime.js' ;
1818
1919const array_methods = [ 'push' , 'pop' , 'shift' , 'unshift' , 'splice' , 'reverse' , 'sort' ] ;
2020
21+ /**
22+ * @param {ValueOptions | undefined } options
23+ * @returns {ValueOptions | undefined }
24+ */
25+ function clone_options ( options ) {
26+ return options != null
27+ ? {
28+ onchange : options . onchange
29+ }
30+ : undefined ;
31+ }
32+
2133/**
2234 * @template T
2335 * @param {T } value
@@ -39,10 +51,21 @@ export function proxy(value, options, parent = null, prev) {
3951
4052 if ( STATE_SYMBOL in value ) {
4153 // @ts -ignore
42- value [ PROXY_ONCHANGE_SYMBOL ] = options ?. onchange ;
54+ value [ PROXY_ONCHANGE_SYMBOL ] ( options ?. onchange ) ;
4355 return value ;
4456 }
4557
58+ if ( options ?. onchange ) {
59+ // if there's an onchange we actually store that but override the value
60+ // to store every other onchange that new proxies might add
61+ var onchanges = new Set ( [ options . onchange ] ) ;
62+ options . onchange = ( ) => {
63+ for ( let onchange of onchanges ) {
64+ onchange ( ) ;
65+ }
66+ } ;
67+ }
68+
4669 const prototype = get_prototype_of ( value ) ;
4770
4871 if ( prototype !== object_prototype && prototype !== array_prototype ) {
@@ -103,7 +126,7 @@ export function proxy(value, options, parent = null, prev) {
103126 var s = sources . get ( prop ) ;
104127
105128 if ( s === undefined ) {
106- s = source ( descriptor . value , options , stack ) ;
129+ s = source ( descriptor . value , clone_options ( options ) , stack ) ;
107130 sources . set ( prop , s ) ;
108131 } else {
109132 set ( s , proxy ( descriptor . value , options , metadata ) ) ;
@@ -117,7 +140,7 @@ export function proxy(value, options, parent = null, prev) {
117140
118141 if ( s === undefined ) {
119142 if ( prop in target ) {
120- sources . set ( prop , source ( UNINITIALIZED , options , stack ) ) ;
143+ sources . set ( prop , source ( UNINITIALIZED , clone_options ( options ) , stack ) ) ;
121144 }
122145 } else {
123146 // When working with arrays, we need to also ensure we update the length when removing
@@ -130,6 +153,11 @@ export function proxy(value, options, parent = null, prev) {
130153 set ( ls , n ) ;
131154 }
132155 }
156+ // when we delete a property if the source is a proxy we remove the current onchange from
157+ // the proxy `onchanges` so that it doesn't trigger it anymore
158+ if ( typeof s . v === 'object' && s . v !== null && STATE_SYMBOL in s . v ) {
159+ s . v [ PROXY_ONCHANGE_SYMBOL ] ( options ?. onchange , true ) ;
160+ }
133161 set ( s , UNINITIALIZED ) ;
134162 update_version ( version ) ;
135163 }
@@ -146,12 +174,38 @@ export function proxy(value, options, parent = null, prev) {
146174 return value ;
147175 }
148176
177+ if ( prop === PROXY_ONCHANGE_SYMBOL ) {
178+ return (
179+ /** @type {(() => unknown) | undefined } */ value ,
180+ /** @type {boolean } */ remove
181+ ) => {
182+ // we either add or remove the passed in value
183+ // to the onchanges array or we set every source onchange
184+ // to the passed in value (if it's undefined it will make the chain stop)
185+ if ( options ?. onchange != null && value && ! remove ) {
186+ onchanges . add ( value ) ;
187+ } else if ( options ?. onchange != null && value ) {
188+ onchanges . delete ( value ) ;
189+ } else {
190+ options = {
191+ onchange : value
192+ } ;
193+ for ( let [ , s ] of sources ) {
194+ if ( s . o ) {
195+ s . o . onchange = value ;
196+ }
197+ }
198+ }
199+ } ;
200+ }
201+
149202 var s = sources . get ( prop ) ;
150203 var exists = prop in target ;
151204
152205 // create a source, but only if it's an own property and not a prototype property
153206 if ( s === undefined && ( ! exists || get_descriptor ( target , prop ) ?. writable ) ) {
154- s = source ( proxy ( exists ? target [ prop ] : UNINITIALIZED , options , metadata ) , options , stack ) ;
207+ let opt = clone_options ( options ) ;
208+ s = source ( proxy ( exists ? target [ prop ] : UNINITIALIZED , opt , metadata ) , opt , stack ) ;
155209 sources . set ( prop , s ) ;
156210 }
157211
@@ -229,7 +283,8 @@ export function proxy(value, options, parent = null, prev) {
229283 ( active_effect !== null && ( ! has || get_descriptor ( target , prop ) ?. writable ) )
230284 ) {
231285 if ( s === undefined ) {
232- s = source ( has ? proxy ( target [ prop ] , options , metadata ) : UNINITIALIZED , options , stack ) ;
286+ let opt = clone_options ( options ) ;
287+ s = source ( has ? proxy ( target [ prop ] , opt , metadata ) : UNINITIALIZED , opt , stack ) ;
233288 sources . set ( prop , s ) ;
234289 }
235290
@@ -243,14 +298,6 @@ export function proxy(value, options, parent = null, prev) {
243298 } ,
244299
245300 set ( target , prop , value , receiver ) {
246- if ( prop === PROXY_ONCHANGE_SYMBOL && options ?. onchange != null ) {
247- const old_onchange = options . onchange ;
248- options . onchange = ( ) => {
249- old_onchange ( ) ;
250- value ( ) ;
251- } ;
252- return true ;
253- }
254301 var s = sources . get ( prop ) ;
255302 var has = prop in target ;
256303
@@ -264,7 +311,7 @@ export function proxy(value, options, parent = null, prev) {
264311 // If the item exists in the original, we need to create a uninitialized source,
265312 // else a later read of the property would result in a source being created with
266313 // the value of the original item at that index.
267- other_s = source ( UNINITIALIZED , options , stack ) ;
314+ other_s = source ( UNINITIALIZED , clone_options ( options ) , stack ) ;
268315 sources . set ( i + '' , other_s ) ;
269316 }
270317 }
@@ -276,13 +323,19 @@ export function proxy(value, options, parent = null, prev) {
276323 // object property before writing to that property.
277324 if ( s === undefined ) {
278325 if ( ! has || get_descriptor ( target , prop ) ?. writable ) {
279- s = source ( undefined , options , stack ) ;
280- set ( s , proxy ( value , options , metadata ) ) ;
326+ const opt = clone_options ( options ) ;
327+ s = source ( undefined , opt , stack ) ;
328+ set ( s , proxy ( value , opt , metadata ) ) ;
281329 sources . set ( prop , s ) ;
282330 }
283331 } else {
284332 has = s . v !== UNINITIALIZED ;
285- set ( s , proxy ( value , options , metadata ) ) ;
333+ // when we set a property if the source is a proxy we remove the current onchange from
334+ // the proxy `onchanges` so that it doesn't trigger it anymore
335+ if ( typeof s . v === 'object' && s . v !== null && STATE_SYMBOL in s . v ) {
336+ s . v [ PROXY_ONCHANGE_SYMBOL ] ( options ?. onchange , true ) ;
337+ }
338+ set ( s , proxy ( value , clone_options ( options ) , metadata ) ) ;
286339 }
287340
288341 if ( DEV ) {
0 commit comments