mount-observer 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DefineCustomElementHandler.js +64 -0
- package/DefineCustomElementHandler.ts +77 -0
- package/Events.js +11 -9
- package/Events.ts +9 -4
- package/EvtRt.js +34 -0
- package/EvtRt.ts +42 -0
- package/MountObserver.js +211 -41
- package/MountObserver.ts +252 -42
- package/README.md +519 -185
- package/SharedMutationObserver.js +9 -6
- package/SharedMutationObserver.ts +11 -8
- package/arr.js +13 -0
- package/arr.ts +13 -0
- package/emitEvents.js +1 -1
- package/emitEvents.ts +1 -1
- package/index.js +13 -1
- package/index.ts +10 -1
- package/loadImports.js +2 -1
- package/loadImports.ts +2 -1
- package/mediaQuery.js +2 -7
- package/mediaQuery.ts +2 -7
- package/package.json +15 -3
- package/types.d.ts +21 -15
package/MountObserver.ts
CHANGED
|
@@ -6,8 +6,10 @@ import {
|
|
|
6
6
|
AttrChange,
|
|
7
7
|
WeakDual,
|
|
8
8
|
EventConfig,
|
|
9
|
-
EventConstructor
|
|
9
|
+
EventConstructor,
|
|
10
|
+
Constructor
|
|
10
11
|
} from './types.js';
|
|
12
|
+
import { arr } from './arr.js';
|
|
11
13
|
import {
|
|
12
14
|
MountEvent,
|
|
13
15
|
DismountEvent,
|
|
@@ -23,6 +25,16 @@ import {
|
|
|
23
25
|
import { whereOutside } from './whereOutside.js';
|
|
24
26
|
|
|
25
27
|
export class MountObserver extends EventTarget implements IMountObserver {
|
|
28
|
+
// Static registry for registered handlers
|
|
29
|
+
static #handlerRegistry = new Map<string, Constructor>();
|
|
30
|
+
|
|
31
|
+
static define(name: string, handler: Constructor): void {
|
|
32
|
+
if (this.#handlerRegistry.has(name)) {
|
|
33
|
+
throw new Error(`${name} already in use`);
|
|
34
|
+
}
|
|
35
|
+
this.#handlerRegistry.set(name, handler);
|
|
36
|
+
}
|
|
37
|
+
|
|
26
38
|
#init: MountInit;
|
|
27
39
|
#options: MountObserverOptions;
|
|
28
40
|
#abortController: AbortController;
|
|
@@ -43,7 +55,10 @@ export class MountObserver extends EventTarget implements IMountObserver {
|
|
|
43
55
|
#checkAttrChangesFn: ((element: Element) => AttrChange[]) | null = null;
|
|
44
56
|
#mediaQueryCleanup?: () => void;
|
|
45
57
|
#mediaMatches: boolean = true;
|
|
46
|
-
#
|
|
58
|
+
#asgMtSource: Record<string, any> | undefined;
|
|
59
|
+
#asgDisMtSource: Record<string, any> | undefined;
|
|
60
|
+
#elementNotifiers = new WeakMap<Element, EventTarget>();
|
|
61
|
+
#notifierMountedElements = new WeakSet<Element>();
|
|
47
62
|
|
|
48
63
|
constructor(init: MountInit, options: MountObserverOptions = {}) {
|
|
49
64
|
super();
|
|
@@ -51,9 +66,16 @@ export class MountObserver extends EventTarget implements IMountObserver {
|
|
|
51
66
|
this.#options = options;
|
|
52
67
|
this.#abortController = new AbortController();
|
|
53
68
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
69
|
+
const {
|
|
70
|
+
assignOnMount, assignOnDismount, do: doValue, reference, whereAttr, loadingEagerness,
|
|
71
|
+
import: imp
|
|
72
|
+
} = init;
|
|
73
|
+
// Make a copy of assignOnMount config using structuredClone
|
|
74
|
+
if (assignOnMount !== undefined) {
|
|
75
|
+
this.#asgMtSource = structuredClone(assignOnMount);
|
|
76
|
+
}
|
|
77
|
+
if (assignOnDismount !== undefined) {
|
|
78
|
+
this.#asgDisMtSource = structuredClone(assignOnDismount);
|
|
57
79
|
}
|
|
58
80
|
|
|
59
81
|
if (options.disconnectedSignal) {
|
|
@@ -62,17 +84,71 @@ export class MountObserver extends EventTarget implements IMountObserver {
|
|
|
62
84
|
});
|
|
63
85
|
}
|
|
64
86
|
|
|
87
|
+
// Validate do property if it contains string references
|
|
88
|
+
if (doValue !== undefined) {
|
|
89
|
+
this.#validateDoHandlers();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Validate reference property if present
|
|
93
|
+
if (reference !== undefined) {
|
|
94
|
+
this.#validateReference();
|
|
95
|
+
}
|
|
96
|
+
|
|
65
97
|
// Preload whereAttr utilities if needed
|
|
66
|
-
if (
|
|
98
|
+
if (whereAttr) {
|
|
67
99
|
this.#preloadWhereAttrUtilities();
|
|
68
100
|
}
|
|
69
101
|
|
|
70
102
|
// Start loading imports if eager
|
|
71
|
-
if (
|
|
103
|
+
if (loadingEagerness === 'eager' && imp) {
|
|
72
104
|
this.#loadImports();
|
|
73
105
|
}
|
|
74
106
|
}
|
|
75
107
|
|
|
108
|
+
#validateDoHandlers(): void {
|
|
109
|
+
const doValue = this.#init.do;
|
|
110
|
+
if (doValue === undefined) return;
|
|
111
|
+
|
|
112
|
+
const handlers = Array.isArray(doValue) ? doValue : [doValue];
|
|
113
|
+
|
|
114
|
+
for (const handler of handlers) {
|
|
115
|
+
if (typeof handler === 'string') {
|
|
116
|
+
if (!MountObserver.#handlerRegistry.has(handler)) {
|
|
117
|
+
throw new Error(`No handler defined for ${handler}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
#validateReference(): void {
|
|
124
|
+
if (!this.#init.import) {
|
|
125
|
+
throw new Error('reference property requires import to be defined');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Normalize import to array
|
|
129
|
+
const imports = Array.isArray(this.#init.import)
|
|
130
|
+
? this.#init.import
|
|
131
|
+
: [this.#init.import];
|
|
132
|
+
|
|
133
|
+
// Normalize reference to array
|
|
134
|
+
const references = arr(this.#init.reference);
|
|
135
|
+
|
|
136
|
+
// Validate each reference index
|
|
137
|
+
for (const index of references) {
|
|
138
|
+
// Check if index is within bounds
|
|
139
|
+
if (index < 0 || index >= imports.length) {
|
|
140
|
+
throw new Error(`reference index ${index} is out of bounds (import array length: ${imports.length})`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const importItem = imports[index];
|
|
144
|
+
|
|
145
|
+
// Check if it's a JS module (not a 2D array with type option)
|
|
146
|
+
if (Array.isArray(importItem)) {
|
|
147
|
+
throw new Error(`reference index ${index} points to a non-JS module import (array with type option)`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
76
152
|
async #preloadWhereAttrUtilities(): Promise<void> {
|
|
77
153
|
if (!this.#matchesWhereAttrFn) {
|
|
78
154
|
const { matchesWhereAttr } = await import('./whereAttr.js');
|
|
@@ -120,10 +196,26 @@ export class MountObserver extends EventTarget implements IMountObserver {
|
|
|
120
196
|
return this.#abortController.signal;
|
|
121
197
|
}
|
|
122
198
|
|
|
199
|
+
getNotifier(element: Element): EventTarget {
|
|
200
|
+
// Return cached notifier if it exists
|
|
201
|
+
let notifier = this.#elementNotifiers.get(element);
|
|
202
|
+
if (notifier) {
|
|
203
|
+
return notifier;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Create new EventTarget for this element
|
|
207
|
+
notifier = new EventTarget();
|
|
208
|
+
this.#elementNotifiers.set(element, notifier);
|
|
209
|
+
return notifier;
|
|
210
|
+
}
|
|
211
|
+
|
|
123
212
|
async observe(rootNode: Node): Promise<void> {
|
|
124
213
|
if (this.#rootNode) {
|
|
125
214
|
throw new Error('Already observing');
|
|
126
215
|
}
|
|
216
|
+
if(this.#asgMtSource || this.#asgDisMtSource){
|
|
217
|
+
await import('assign-gingerly/object-extension.js');
|
|
218
|
+
}
|
|
127
219
|
|
|
128
220
|
this.#rootNode = new WeakRef(rootNode);
|
|
129
221
|
|
|
@@ -137,6 +229,11 @@ export class MountObserver extends EventTarget implements IMountObserver {
|
|
|
137
229
|
await this.#preloadWhereAttrUtilities();
|
|
138
230
|
}
|
|
139
231
|
|
|
232
|
+
// Wait for eager imports to complete if they were started in constructor
|
|
233
|
+
if (this.#init.loadingEagerness === 'eager' && this.#init.import && !this.#importsLoaded) {
|
|
234
|
+
await this.#loadImports();
|
|
235
|
+
}
|
|
236
|
+
|
|
140
237
|
// Process existing elements only if media matches
|
|
141
238
|
if (this.#mediaMatches) {
|
|
142
239
|
this.#processNode(rootNode);
|
|
@@ -176,6 +273,22 @@ export class MountObserver extends EventTarget implements IMountObserver {
|
|
|
176
273
|
// Batch and dispatch attribute changes
|
|
177
274
|
if (attrChanges.length > 0) {
|
|
178
275
|
this.dispatchEvent(new AttrChangeEvent(attrChanges, this.#init));
|
|
276
|
+
|
|
277
|
+
// Dispatch filtered attrchange events to element-specific notifiers
|
|
278
|
+
const changesByElement = new Map<Element, AttrChange[]>();
|
|
279
|
+
for (const change of attrChanges) {
|
|
280
|
+
if (!changesByElement.has(change.element)) {
|
|
281
|
+
changesByElement.set(change.element, []);
|
|
282
|
+
}
|
|
283
|
+
changesByElement.get(change.element)!.push(change);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
for (const [element, changes] of changesByElement) {
|
|
287
|
+
const notifier = this.#elementNotifiers.get(element);
|
|
288
|
+
if (notifier) {
|
|
289
|
+
notifier.dispatchEvent(new AttrChangeEvent(changes, this.#init));
|
|
290
|
+
}
|
|
291
|
+
}
|
|
179
292
|
}
|
|
180
293
|
};
|
|
181
294
|
|
|
@@ -223,6 +336,26 @@ export class MountObserver extends EventTarget implements IMountObserver {
|
|
|
223
336
|
this.#modules = await loadImports(this.#init.import);
|
|
224
337
|
this.#importsLoaded = true;
|
|
225
338
|
|
|
339
|
+
// Validate referenced whereInstanceOf if reference is specified
|
|
340
|
+
if (this.#init.reference !== undefined) {
|
|
341
|
+
const references = arr(this.#init.reference);
|
|
342
|
+
|
|
343
|
+
for (const index of references) {
|
|
344
|
+
const module = this.#modules[index];
|
|
345
|
+
if (module && module.whereInstanceOf !== undefined) {
|
|
346
|
+
// Validate that it's a Constructor or array of Constructors
|
|
347
|
+
const whereInstanceOf = module.whereInstanceOf;
|
|
348
|
+
const constructors = arr(whereInstanceOf);
|
|
349
|
+
|
|
350
|
+
for (const constructor of constructors) {
|
|
351
|
+
if (typeof constructor !== 'function') {
|
|
352
|
+
throw new Error(`Referenced module at index ${index} exports invalid whereInstanceOf: must be a Constructor or array of Constructors`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
226
359
|
this.dispatchEvent(new LoadEvent(this.#modules, this.#init));
|
|
227
360
|
}
|
|
228
361
|
|
|
@@ -280,9 +413,7 @@ export class MountObserver extends EventTarget implements IMountObserver {
|
|
|
280
413
|
|
|
281
414
|
// Check whereInstanceOf condition if specified
|
|
282
415
|
if (this.#init.whereInstanceOf) {
|
|
283
|
-
const constructors =
|
|
284
|
-
? this.#init.whereInstanceOf
|
|
285
|
-
: [this.#init.whereInstanceOf];
|
|
416
|
+
const constructors = arr(this.#init.whereInstanceOf);
|
|
286
417
|
|
|
287
418
|
// Element must be an instance of at least one constructor (OR logic for array)
|
|
288
419
|
const matchesInstanceOf = constructors.some(constructor => element instanceof constructor);
|
|
@@ -292,6 +423,25 @@ export class MountObserver extends EventTarget implements IMountObserver {
|
|
|
292
423
|
}
|
|
293
424
|
}
|
|
294
425
|
|
|
426
|
+
// Check referenced whereInstanceOf if imports are loaded and reference is specified
|
|
427
|
+
if (this.#importsLoaded && this.#init.reference !== undefined) {
|
|
428
|
+
const references = arr(this.#init.reference);
|
|
429
|
+
|
|
430
|
+
for (const index of references) {
|
|
431
|
+
const module = this.#modules[index];
|
|
432
|
+
if (module && module.whereInstanceOf !== undefined) {
|
|
433
|
+
const constructors = arr(module.whereInstanceOf);
|
|
434
|
+
|
|
435
|
+
// Element must be an instance of at least one constructor (OR logic within this module)
|
|
436
|
+
const matchesInstanceOf = constructors.some((constructor: Constructor) => element instanceof constructor);
|
|
437
|
+
|
|
438
|
+
if (!matchesInstanceOf) {
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
295
445
|
// All conditions passed
|
|
296
446
|
return true;
|
|
297
447
|
}
|
|
@@ -323,28 +473,62 @@ export class MountObserver extends EventTarget implements IMountObserver {
|
|
|
323
473
|
const context: MountContext = {
|
|
324
474
|
modules: this.#modules,
|
|
325
475
|
observer: this,
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
476
|
+
rootNode,
|
|
477
|
+
mountInit: this.#init,
|
|
329
478
|
};
|
|
330
479
|
|
|
331
480
|
// Apply assignGingerly if specified
|
|
332
|
-
if (this.#
|
|
333
|
-
|
|
334
|
-
assignGingerly(element, this.#assignGingerlySource);
|
|
481
|
+
if (this.#asgMtSource) {
|
|
482
|
+
element.assignGingerly(this.#asgMtSource);
|
|
335
483
|
}
|
|
336
484
|
|
|
337
|
-
//
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
485
|
+
// Check if notifier exists BEFORE calling do callback
|
|
486
|
+
const notifierExistedBeforeDo = this.#elementNotifiers.has(element);
|
|
487
|
+
|
|
488
|
+
// Call do callback(s) - can be string, function, or array
|
|
489
|
+
if (this.#init.do !== undefined) {
|
|
490
|
+
const doHandlers = Array.isArray(this.#init.do) ? this.#init.do : [this.#init.do];
|
|
491
|
+
|
|
492
|
+
for (const handler of doHandlers) {
|
|
493
|
+
if (typeof handler === 'string') {
|
|
494
|
+
// Registered handler - instantiate it
|
|
495
|
+
const HandlerClass = MountObserver.#handlerRegistry.get(handler);
|
|
496
|
+
if (HandlerClass) {
|
|
497
|
+
new HandlerClass(element, context);
|
|
498
|
+
}
|
|
499
|
+
} else if (typeof handler === 'function') {
|
|
500
|
+
// Inline function
|
|
501
|
+
handler(element, context);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Call referenced do functions from imported modules
|
|
507
|
+
if (this.#init.reference !== undefined) {
|
|
508
|
+
const references = arr(this.#init.reference);
|
|
509
|
+
|
|
510
|
+
for (const index of references) {
|
|
511
|
+
const module = this.#modules[index];
|
|
512
|
+
if (module && typeof module.do === 'function') {
|
|
513
|
+
module.do(element, context);
|
|
514
|
+
}
|
|
343
515
|
}
|
|
344
516
|
}
|
|
345
517
|
|
|
346
518
|
// Dispatch mount event
|
|
347
|
-
|
|
519
|
+
const mountEvent = new MountEvent(element, this.#modules, this.#init, context);
|
|
520
|
+
this.dispatchEvent(mountEvent);
|
|
521
|
+
|
|
522
|
+
// Dispatch to element-specific notifier only if:
|
|
523
|
+
// 1. Notifier existed before do callback (wasn't just created), AND
|
|
524
|
+
// 2. Element hasn't already received a mount event on its notifier
|
|
525
|
+
if (notifierExistedBeforeDo && !this.#notifierMountedElements.has(element)) {
|
|
526
|
+
const notifier = this.#elementNotifiers.get(element);
|
|
527
|
+
if (notifier) {
|
|
528
|
+
this.#notifierMountedElements.add(element);
|
|
529
|
+
notifier.dispatchEvent(mountEvent);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
348
532
|
|
|
349
533
|
// Emit events from mounted element if configured
|
|
350
534
|
if (this.#init.mountedElemEmits) {
|
|
@@ -357,6 +541,12 @@ export class MountObserver extends EventTarget implements IMountObserver {
|
|
|
357
541
|
const changes = this.#checkAttrChangesFn(element);
|
|
358
542
|
if (changes.length > 0) {
|
|
359
543
|
this.dispatchEvent(new AttrChangeEvent(changes, this.#init));
|
|
544
|
+
|
|
545
|
+
// Also dispatch to element-specific notifier
|
|
546
|
+
const notifier = this.#elementNotifiers.get(element);
|
|
547
|
+
if (notifier) {
|
|
548
|
+
notifier.dispatchEvent(new AttrChangeEvent(changes, this.#init));
|
|
549
|
+
}
|
|
360
550
|
}
|
|
361
551
|
}
|
|
362
552
|
}
|
|
@@ -364,35 +554,43 @@ export class MountObserver extends EventTarget implements IMountObserver {
|
|
|
364
554
|
async assignGingerly(config: Record<string, any> | undefined): Promise<void> {
|
|
365
555
|
// Handle undefined case
|
|
366
556
|
if (config === undefined) {
|
|
367
|
-
this.#
|
|
557
|
+
this.#asgMtSource = undefined;
|
|
368
558
|
return;
|
|
369
559
|
}
|
|
370
560
|
|
|
371
|
-
|
|
561
|
+
await import('assign-gingerly/object-extension.js');
|
|
372
562
|
|
|
373
563
|
// Update the source config for future mounted elements
|
|
374
|
-
if (this.#
|
|
564
|
+
if (this.#asgMtSource === undefined) {
|
|
375
565
|
// No existing config, just clone the passed in object
|
|
376
|
-
this.#
|
|
566
|
+
this.#asgMtSource = structuredClone(config);
|
|
377
567
|
} else {
|
|
378
568
|
// Merge into existing config using assignGingerly
|
|
379
|
-
assignGingerly(
|
|
569
|
+
this.#asgMtSource.assignGingerly(config);
|
|
570
|
+
//assignGingerly(this.#asgMtSource, config);
|
|
380
571
|
}
|
|
381
572
|
|
|
382
573
|
// Apply to already mounted elements using setWeak for iteration
|
|
383
574
|
for (const ref of this.#mountedElements.setWeak) {
|
|
384
575
|
const element = ref.deref();
|
|
385
576
|
if (element) {
|
|
386
|
-
assignGingerly(
|
|
577
|
+
element.assignGingerly(config);
|
|
578
|
+
//assignGingerly(element, config);
|
|
387
579
|
}
|
|
388
580
|
}
|
|
389
581
|
}
|
|
390
582
|
|
|
391
|
-
#handleRemoval(element: Element): void {
|
|
583
|
+
async #handleRemoval(element: Element): Promise<void> {
|
|
392
584
|
if (!this.#mountedElements.weakSet.has(element)) {
|
|
393
585
|
return;
|
|
394
586
|
}
|
|
395
587
|
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
// Apply assignGingerly if specified for dismount
|
|
591
|
+
if (this.#asgDisMtSource) {
|
|
592
|
+
element.assignGingerly(this.#asgDisMtSource);
|
|
593
|
+
}
|
|
396
594
|
// Remove from both structures
|
|
397
595
|
this.#mountedElements.weakSet.delete(element);
|
|
398
596
|
for (const ref of this.#mountedElements.setWeak) {
|
|
@@ -401,6 +599,12 @@ export class MountObserver extends EventTarget implements IMountObserver {
|
|
|
401
599
|
break;
|
|
402
600
|
}
|
|
403
601
|
}
|
|
602
|
+
|
|
603
|
+
// Remove from processed set so element can be re-mounted
|
|
604
|
+
this.#processedDoForElement.delete(element);
|
|
605
|
+
|
|
606
|
+
// Remove from notifier mounted tracking so mount event can fire again
|
|
607
|
+
this.#notifierMountedElements.delete(element);
|
|
404
608
|
|
|
405
609
|
const rootNode = this.#rootNode?.deref();
|
|
406
610
|
if (!rootNode) {
|
|
@@ -411,28 +615,34 @@ export class MountObserver extends EventTarget implements IMountObserver {
|
|
|
411
615
|
const context: MountContext = {
|
|
412
616
|
modules: this.#modules,
|
|
413
617
|
observer: this,
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
}
|
|
618
|
+
rootNode,
|
|
619
|
+
mountInit: this.#init,
|
|
417
620
|
};
|
|
418
621
|
|
|
419
|
-
// Call dismount callback
|
|
420
|
-
if (this.#init.do && typeof this.#init.do !== 'function' && this.#init.do.dismount) {
|
|
421
|
-
this.#init.do.dismount(element, context);
|
|
422
|
-
}
|
|
423
622
|
|
|
424
623
|
// Dispatch dismount event
|
|
425
|
-
|
|
624
|
+
const dismountEvent = new DismountEvent(element, 'where-element-matches-failed', this.#init);
|
|
625
|
+
this.dispatchEvent(dismountEvent);
|
|
626
|
+
|
|
627
|
+
// Dispatch to element-specific notifier
|
|
628
|
+
const notifier = this.#elementNotifiers.get(element);
|
|
629
|
+
if (notifier) {
|
|
630
|
+
notifier.dispatchEvent(dismountEvent);
|
|
631
|
+
}
|
|
426
632
|
|
|
427
633
|
// Check if element is being moved within the same root
|
|
428
634
|
// If it's truly disconnected, dispatch disconnect event
|
|
429
635
|
setTimeout(() => {
|
|
430
636
|
if (!rootNode.contains(element)) {
|
|
431
|
-
if (this.#init.do && typeof this.#init.do !== 'function' && this.#init.do.disconnect) {
|
|
432
|
-
this.#init.do.disconnect(element, context);
|
|
433
|
-
}
|
|
434
637
|
|
|
435
|
-
|
|
638
|
+
const disconnectEvent = new DisconnectEvent(element, this.#init);
|
|
639
|
+
this.dispatchEvent(disconnectEvent);
|
|
640
|
+
|
|
641
|
+
// Dispatch to element-specific notifier
|
|
642
|
+
const notifier = this.#elementNotifiers.get(element);
|
|
643
|
+
if (notifier) {
|
|
644
|
+
notifier.dispatchEvent(disconnectEvent);
|
|
645
|
+
}
|
|
436
646
|
}
|
|
437
647
|
}, 0);
|
|
438
648
|
}
|