6
6
import { mount , unmount } from 'svelte' ;
7
7
8
8
class ArizonaSvelteLifecycle {
9
- constructor ( registry ) {
9
+ constructor ( registry , options = { } ) {
10
10
if ( ! registry ) {
11
11
throw new Error ( 'ArizonaSvelteLifecycle requires a registry instance' ) ;
12
12
}
13
13
14
14
this . registry = registry ;
15
15
this . mountedComponents = new Map ( ) ; // target -> component instance
16
+ this . observers = new Set ( ) ; // Set of active observers
17
+ this . isMonitoring = false ;
18
+ this . options = {
19
+ autoMount : options . autoMount !== false , // Default true
20
+ autoUnmount : options . autoUnmount !== false , // Default true
21
+ observeSubtree : options . observeSubtree !== false , // Default true
22
+ debounceMs : options . debounceMs || 100 , // Debounce DOM changes
23
+ ...options
24
+ } ;
25
+ this . debounceTimer = null ;
16
26
}
17
27
18
28
/**
@@ -38,15 +48,22 @@ class ArizonaSvelteLifecycle {
38
48
const instance = mount ( ComponentClass , { target, props } ) ;
39
49
this . mountedComponents . set ( target , instance ) ;
40
50
mountedCount ++ ;
51
+ console . log ( `[Arizona Svelte] ✅ Mounted '${ componentName } ' component` , {
52
+ target : target . id || target . className || 'unnamed' ,
53
+ props,
54
+ totalMounted : this . mountedComponents . size
55
+ } ) ;
41
56
} catch ( error ) {
42
- console . error ( `[Arizona Svelte] Failed to mount component '${ componentName } ':` , error ) ;
57
+ console . error ( `[Arizona Svelte] ❌ Failed to mount component '${ componentName } ':` , error ) ;
43
58
}
44
59
} else {
45
60
console . warn ( `[Arizona Svelte] Component '${ componentName } ' not found in registry` ) ;
46
61
}
47
62
} ) ;
48
63
49
- console . log ( `[Arizona Svelte] Mounted ${ mountedCount } components` ) ;
64
+ if ( mountedCount > 0 ) {
65
+ console . log ( `[Arizona Svelte] Mounted ${ mountedCount } components` ) ;
66
+ }
50
67
return mountedCount ;
51
68
}
52
69
@@ -60,11 +77,16 @@ class ArizonaSvelteLifecycle {
60
77
61
78
if ( instance ) {
62
79
try {
80
+ const componentName = target . dataset . svelteComponent || 'unknown' ;
63
81
unmount ( instance ) ;
64
82
this . mountedComponents . delete ( target ) ;
83
+ console . log ( `[Arizona Svelte] 🗑️ Unmounted '${ componentName } ' component` , {
84
+ target : target . id || target . className || 'unnamed' ,
85
+ totalMounted : this . mountedComponents . size
86
+ } ) ;
65
87
return true ;
66
88
} catch ( error ) {
67
- console . error ( `[Arizona Svelte] Failed to unmount component:` , error ) ;
89
+ console . error ( `[Arizona Svelte] ❌ Failed to unmount component:` , error ) ;
68
90
return false ;
69
91
}
70
92
}
@@ -123,6 +145,285 @@ class ArizonaSvelteLifecycle {
123
145
isComponentMounted ( target ) {
124
146
return this . mountedComponents . has ( target ) ;
125
147
}
148
+
149
+ /**
150
+ * Start automatic monitoring for component lifecycle
151
+ * @returns {void }
152
+ */
153
+ startMonitoring ( ) {
154
+ if ( this . isMonitoring ) {
155
+ console . warn ( '[Arizona Svelte] Monitoring already started' ) ;
156
+ return ;
157
+ }
158
+
159
+ this . isMonitoring = true ;
160
+ console . log ( '[Arizona Svelte] Starting automatic component monitoring' ) ;
161
+
162
+ // Initial mount
163
+ if ( this . options . autoMount ) {
164
+ this . mountComponents ( ) ;
165
+ }
166
+
167
+ // Set up DOM mutation observer
168
+ this . setupDOMObserver ( ) ;
169
+
170
+ // Set up Arizona WebSocket listener
171
+ this . setupArizonaListener ( ) ;
172
+
173
+ // Set up page visibility listener
174
+ this . setupVisibilityListener ( ) ;
175
+
176
+ // Set up cleanup on page unload
177
+ this . setupUnloadListener ( ) ;
178
+ }
179
+
180
+ /**
181
+ * Stop automatic monitoring
182
+ * @returns {void }
183
+ */
184
+ stopMonitoring ( ) {
185
+ if ( ! this . isMonitoring ) {
186
+ return ;
187
+ }
188
+
189
+ this . isMonitoring = false ;
190
+ console . log ( '[Arizona Svelte] Stopping automatic component monitoring' ) ;
191
+
192
+ // Clean up all observers
193
+ this . observers . forEach ( observer => {
194
+ if ( observer . disconnect ) {
195
+ observer . disconnect ( ) ;
196
+ } else if ( typeof observer === 'function' ) {
197
+ observer ( ) ; // Cleanup function
198
+ }
199
+ } ) ;
200
+ this . observers . clear ( ) ;
201
+
202
+ // Clear debounce timer
203
+ if ( this . debounceTimer ) {
204
+ clearTimeout ( this . debounceTimer ) ;
205
+ this . debounceTimer = null ;
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Setup DOM mutation observer to detect component additions/removals
211
+ * @private
212
+ */
213
+ setupDOMObserver ( ) {
214
+ const observer = new MutationObserver ( ( mutations ) => {
215
+ this . debouncedHandleMutations ( mutations ) ;
216
+ } ) ;
217
+
218
+ observer . observe ( document . body , {
219
+ childList : true ,
220
+ subtree : this . options . observeSubtree ,
221
+ attributes : true ,
222
+ attributeFilter : [ 'data-svelte-component' , 'data-svelte-props' ]
223
+ } ) ;
224
+
225
+ this . observers . add ( observer ) ;
226
+ }
227
+
228
+ /**
229
+ * Setup Arizona WebSocket event listener for patches
230
+ * @private
231
+ */
232
+ setupArizonaListener ( ) {
233
+ const handleArizonaEvent = ( event ) => {
234
+ const { type, data } = event . detail ;
235
+
236
+ if ( type === 'html_patch' ) {
237
+ // When Arizona applies HTML patches, we need to check for new components
238
+ this . debouncedScanAndMount ( ) ;
239
+ }
240
+ } ;
241
+
242
+ document . addEventListener ( 'arizonaEvent' , handleArizonaEvent ) ;
243
+
244
+ // Return cleanup function
245
+ const cleanup = ( ) => {
246
+ document . removeEventListener ( 'arizonaEvent' , handleArizonaEvent ) ;
247
+ } ;
248
+
249
+ this . observers . add ( cleanup ) ;
250
+ }
251
+
252
+ /**
253
+ * Setup page visibility listener to pause/resume components
254
+ * @private
255
+ */
256
+ setupVisibilityListener ( ) {
257
+ const handleVisibilityChange = ( ) => {
258
+ if ( document . hidden ) {
259
+ console . log ( '[Arizona Svelte] Page hidden - components may pause updates' ) ;
260
+ } else {
261
+ console . log ( '[Arizona Svelte] Page visible - checking for component updates' ) ;
262
+ this . debouncedScanAndMount ( ) ;
263
+ }
264
+ } ;
265
+
266
+ document . addEventListener ( 'visibilitychange' , handleVisibilityChange ) ;
267
+
268
+ const cleanup = ( ) => {
269
+ document . removeEventListener ( 'visibilitychange' , handleVisibilityChange ) ;
270
+ } ;
271
+
272
+ this . observers . add ( cleanup ) ;
273
+ }
274
+
275
+ /**
276
+ * Setup page unload listener for cleanup
277
+ * @private
278
+ */
279
+ setupUnloadListener ( ) {
280
+ const handleUnload = ( ) => {
281
+ console . log ( '[Arizona Svelte] Page unloading - cleaning up components' ) ;
282
+ if ( this . options . autoUnmount ) {
283
+ this . unmountAllComponents ( ) ;
284
+ }
285
+ this . stopMonitoring ( ) ;
286
+ } ;
287
+
288
+ window . addEventListener ( 'beforeunload' , handleUnload ) ;
289
+ window . addEventListener ( 'unload' , handleUnload ) ;
290
+
291
+ const cleanup = ( ) => {
292
+ window . removeEventListener ( 'beforeunload' , handleUnload ) ;
293
+ window . removeEventListener ( 'unload' , handleUnload ) ;
294
+ } ;
295
+
296
+ this . observers . add ( cleanup ) ;
297
+ }
298
+
299
+ /**
300
+ * Debounced mutation handler to avoid excessive re-scanning
301
+ * @private
302
+ */
303
+ debouncedHandleMutations ( mutations ) {
304
+ if ( this . debounceTimer ) {
305
+ clearTimeout ( this . debounceTimer ) ;
306
+ }
307
+
308
+ this . debounceTimer = setTimeout ( ( ) => {
309
+ this . handleMutations ( mutations ) ;
310
+ } , this . options . debounceMs ) ;
311
+ }
312
+
313
+ /**
314
+ * Debounced scan and mount to avoid excessive operations
315
+ * @private
316
+ */
317
+ debouncedScanAndMount ( ) {
318
+ if ( this . debounceTimer ) {
319
+ clearTimeout ( this . debounceTimer ) ;
320
+ }
321
+
322
+ this . debounceTimer = setTimeout ( ( ) => {
323
+ this . scanAndMount ( ) ;
324
+ } , this . options . debounceMs ) ;
325
+ }
326
+
327
+ /**
328
+ * Handle DOM mutations and update components accordingly
329
+ * @private
330
+ */
331
+ handleMutations ( mutations ) {
332
+ let shouldScan = false ;
333
+ const removedNodes = new Set ( ) ;
334
+
335
+ mutations . forEach ( mutation => {
336
+ // Handle removed nodes
337
+ if ( mutation . type === 'childList' && mutation . removedNodes . length > 0 ) {
338
+ mutation . removedNodes . forEach ( node => {
339
+ if ( node . nodeType === Node . ELEMENT_NODE ) {
340
+ removedNodes . add ( node ) ;
341
+ // Check if removed node or its children had mounted components
342
+ this . unmountRemovedComponents ( node ) ;
343
+ }
344
+ } ) ;
345
+ }
346
+
347
+ // Handle added nodes or attribute changes
348
+ if ( mutation . type === 'childList' && mutation . addedNodes . length > 0 ) {
349
+ shouldScan = true ;
350
+ } else if ( mutation . type === 'attributes' &&
351
+ ( mutation . attributeName === 'data-svelte-component' ||
352
+ mutation . attributeName === 'data-svelte-props' ) ) {
353
+ shouldScan = true ;
354
+ }
355
+ } ) ;
356
+
357
+ if ( shouldScan && this . options . autoMount ) {
358
+ this . scanAndMount ( ) ;
359
+ }
360
+ }
361
+
362
+ /**
363
+ * Scan for new components and mount them
364
+ * @private
365
+ */
366
+ async scanAndMount ( ) {
367
+ try {
368
+ const mounted = await this . mountComponents ( ) ;
369
+ if ( mounted > 0 ) {
370
+ console . log ( `[Arizona Svelte] 🔄 Auto-mounted ${ mounted } new components` ) ;
371
+ }
372
+ } catch ( error ) {
373
+ console . error ( '[Arizona Svelte] Error during auto-mount:' , error ) ;
374
+ }
375
+ }
376
+
377
+ /**
378
+ * Unmount components that were removed from DOM
379
+ * @private
380
+ */
381
+ unmountRemovedComponents ( removedNode ) {
382
+ if ( ! this . options . autoUnmount ) {
383
+ return ;
384
+ }
385
+
386
+ // Check if the removed node itself was a component target
387
+ if ( this . mountedComponents . has ( removedNode ) ) {
388
+ console . log ( '[Arizona Svelte] Auto-unmounting removed component' ) ;
389
+ this . unmountComponent ( removedNode ) ;
390
+ }
391
+
392
+ // Check children of removed node
393
+ if ( removedNode . querySelectorAll ) {
394
+ const childTargets = removedNode . querySelectorAll ( '[data-svelte-component]' ) ;
395
+ childTargets . forEach ( target => {
396
+ if ( this . mountedComponents . has ( target ) ) {
397
+ console . log ( '[Arizona Svelte] Auto-unmounting removed child component' ) ;
398
+ this . unmountComponent ( target ) ;
399
+ }
400
+ } ) ;
401
+ }
402
+ }
403
+
404
+ /**
405
+ * Get monitoring status
406
+ * @returns {boolean }
407
+ */
408
+ isMonitoringActive ( ) {
409
+ return this . isMonitoring ;
410
+ }
411
+
412
+ /**
413
+ * Get current monitoring options
414
+ * @returns {Object }
415
+ */
416
+ getMonitoringOptions ( ) {
417
+ return { ...this . options } ;
418
+ }
419
+
420
+ /**
421
+ * Update monitoring options
422
+ * @param {Object } newOptions - New options to merge
423
+ */
424
+ updateMonitoringOptions ( newOptions ) {
425
+ this . options = { ...this . options , ...newOptions } ;
426
+ }
126
427
}
127
428
128
429
export { ArizonaSvelteLifecycle } ;
0 commit comments