mount-observer 0.1.1 → 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 +275 -106
- package/MountObserver.ts +328 -118
- package/README.md +857 -203
- package/SharedMutationObserver.js +9 -6
- package/SharedMutationObserver.ts +11 -8
- package/arr.js +13 -0
- package/arr.ts +13 -0
- package/attrChanges.js +70 -0
- package/attrChanges.ts +90 -0
- package/emitEvents.js +103 -0
- package/emitEvents.ts +126 -0
- package/index.js +13 -1
- package/index.ts +14 -1
- package/loadImports.js +2 -1
- package/loadImports.ts +2 -1
- package/mediaQuery.js +15 -17
- package/mediaQuery.ts +18 -20
- package/package.json +27 -3
- package/types.d.ts +38 -15
- package/whereOutside.js +19 -0
- package/whereOutside.ts +25 -0
package/MountObserver.js
CHANGED
|
@@ -1,12 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { arr } from './arr.js';
|
|
2
|
+
import { MountEvent, DismountEvent, DisconnectEvent, LoadEvent, AttrChangeEvent, } from './Events.js';
|
|
2
3
|
import { registerSharedObserver, unregisterSharedObserver } from './SharedMutationObserver.js';
|
|
4
|
+
import { whereOutside } from './whereOutside.js';
|
|
3
5
|
export class MountObserver extends EventTarget {
|
|
6
|
+
// Static registry for registered handlers
|
|
7
|
+
static #handlerRegistry = new Map();
|
|
8
|
+
static define(name, handler) {
|
|
9
|
+
if (this.#handlerRegistry.has(name)) {
|
|
10
|
+
throw new Error(`${name} already in use`);
|
|
11
|
+
}
|
|
12
|
+
this.#handlerRegistry.set(name, handler);
|
|
13
|
+
}
|
|
4
14
|
#init;
|
|
5
15
|
#options;
|
|
6
16
|
#abortController;
|
|
7
17
|
#modules = [];
|
|
8
|
-
#mountedElements =
|
|
9
|
-
|
|
18
|
+
#mountedElements = {
|
|
19
|
+
weakSet: new WeakSet(),
|
|
20
|
+
setWeak: new Set()
|
|
21
|
+
};
|
|
22
|
+
#processedDoForElement = new WeakSet();
|
|
23
|
+
#processedEventsForElement = new WeakMap();
|
|
10
24
|
#mutationCallback;
|
|
11
25
|
#rootNode;
|
|
12
26
|
#importsLoaded = false;
|
|
@@ -14,27 +28,84 @@ export class MountObserver extends EventTarget {
|
|
|
14
28
|
#elementOnceAttrs = new WeakMap();
|
|
15
29
|
#matchesWhereAttrFn = null;
|
|
16
30
|
#buildAttrCoordinateMapFn = null;
|
|
31
|
+
#checkAttrChangesFn = null;
|
|
17
32
|
#mediaQueryCleanup;
|
|
18
33
|
#mediaMatches = true;
|
|
34
|
+
#asgMtSource;
|
|
35
|
+
#asgDisMtSource;
|
|
36
|
+
#elementNotifiers = new WeakMap();
|
|
37
|
+
#notifierMountedElements = new WeakSet();
|
|
19
38
|
constructor(init, options = {}) {
|
|
20
39
|
super();
|
|
21
40
|
this.#init = init;
|
|
22
41
|
this.#options = options;
|
|
23
42
|
this.#abortController = new AbortController();
|
|
43
|
+
const { assignOnMount, assignOnDismount, do: doValue, reference, whereAttr, loadingEagerness, import: imp } = init;
|
|
44
|
+
// Make a copy of assignOnMount config using structuredClone
|
|
45
|
+
if (assignOnMount !== undefined) {
|
|
46
|
+
this.#asgMtSource = structuredClone(assignOnMount);
|
|
47
|
+
}
|
|
48
|
+
if (assignOnDismount !== undefined) {
|
|
49
|
+
this.#asgDisMtSource = structuredClone(assignOnDismount);
|
|
50
|
+
}
|
|
24
51
|
if (options.disconnectedSignal) {
|
|
25
52
|
options.disconnectedSignal.addEventListener('abort', () => {
|
|
26
53
|
this.disconnect();
|
|
27
54
|
});
|
|
28
55
|
}
|
|
56
|
+
// Validate do property if it contains string references
|
|
57
|
+
if (doValue !== undefined) {
|
|
58
|
+
this.#validateDoHandlers();
|
|
59
|
+
}
|
|
60
|
+
// Validate reference property if present
|
|
61
|
+
if (reference !== undefined) {
|
|
62
|
+
this.#validateReference();
|
|
63
|
+
}
|
|
29
64
|
// Preload whereAttr utilities if needed
|
|
30
|
-
if (
|
|
65
|
+
if (whereAttr) {
|
|
31
66
|
this.#preloadWhereAttrUtilities();
|
|
32
67
|
}
|
|
33
68
|
// Start loading imports if eager
|
|
34
|
-
if (
|
|
69
|
+
if (loadingEagerness === 'eager' && imp) {
|
|
35
70
|
this.#loadImports();
|
|
36
71
|
}
|
|
37
72
|
}
|
|
73
|
+
#validateDoHandlers() {
|
|
74
|
+
const doValue = this.#init.do;
|
|
75
|
+
if (doValue === undefined)
|
|
76
|
+
return;
|
|
77
|
+
const handlers = Array.isArray(doValue) ? doValue : [doValue];
|
|
78
|
+
for (const handler of handlers) {
|
|
79
|
+
if (typeof handler === 'string') {
|
|
80
|
+
if (!MountObserver.#handlerRegistry.has(handler)) {
|
|
81
|
+
throw new Error(`No handler defined for ${handler}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
#validateReference() {
|
|
87
|
+
if (!this.#init.import) {
|
|
88
|
+
throw new Error('reference property requires import to be defined');
|
|
89
|
+
}
|
|
90
|
+
// Normalize import to array
|
|
91
|
+
const imports = Array.isArray(this.#init.import)
|
|
92
|
+
? this.#init.import
|
|
93
|
+
: [this.#init.import];
|
|
94
|
+
// Normalize reference to array
|
|
95
|
+
const references = arr(this.#init.reference);
|
|
96
|
+
// Validate each reference index
|
|
97
|
+
for (const index of references) {
|
|
98
|
+
// Check if index is within bounds
|
|
99
|
+
if (index < 0 || index >= imports.length) {
|
|
100
|
+
throw new Error(`reference index ${index} is out of bounds (import array length: ${imports.length})`);
|
|
101
|
+
}
|
|
102
|
+
const importItem = imports[index];
|
|
103
|
+
// Check if it's a JS module (not a 2D array with type option)
|
|
104
|
+
if (Array.isArray(importItem)) {
|
|
105
|
+
throw new Error(`reference index ${index} points to a non-JS module import (array with type option)`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
38
109
|
async #preloadWhereAttrUtilities() {
|
|
39
110
|
if (!this.#matchesWhereAttrFn) {
|
|
40
111
|
const { matchesWhereAttr } = await import('./whereAttr.js');
|
|
@@ -44,6 +115,13 @@ export class MountObserver extends EventTarget {
|
|
|
44
115
|
const { buildAttrCoordinateMap } = await import('./attrCoordinates.js');
|
|
45
116
|
this.#buildAttrCoordinateMapFn = buildAttrCoordinateMap;
|
|
46
117
|
}
|
|
118
|
+
if (!this.#checkAttrChangesFn) {
|
|
119
|
+
const { checkAttrChanges } = await import('./attrChanges.js');
|
|
120
|
+
// Create a bound function that passes the required parameters
|
|
121
|
+
this.#checkAttrChangesFn = (element) => {
|
|
122
|
+
return checkAttrChanges(element, this.#init, this.#buildAttrCoordinateMapFn, this.#elementAttrStates, this.#elementOnceAttrs);
|
|
123
|
+
};
|
|
124
|
+
}
|
|
47
125
|
}
|
|
48
126
|
async #setupMediaQuery() {
|
|
49
127
|
if (!this.#rootNode) {
|
|
@@ -57,10 +135,24 @@ export class MountObserver extends EventTarget {
|
|
|
57
135
|
get disconnectedSignal() {
|
|
58
136
|
return this.#abortController.signal;
|
|
59
137
|
}
|
|
138
|
+
getNotifier(element) {
|
|
139
|
+
// Return cached notifier if it exists
|
|
140
|
+
let notifier = this.#elementNotifiers.get(element);
|
|
141
|
+
if (notifier) {
|
|
142
|
+
return notifier;
|
|
143
|
+
}
|
|
144
|
+
// Create new EventTarget for this element
|
|
145
|
+
notifier = new EventTarget();
|
|
146
|
+
this.#elementNotifiers.set(element, notifier);
|
|
147
|
+
return notifier;
|
|
148
|
+
}
|
|
60
149
|
async observe(rootNode) {
|
|
61
150
|
if (this.#rootNode) {
|
|
62
151
|
throw new Error('Already observing');
|
|
63
152
|
}
|
|
153
|
+
if (this.#asgMtSource || this.#asgDisMtSource) {
|
|
154
|
+
await import('assign-gingerly/object-extension.js');
|
|
155
|
+
}
|
|
64
156
|
this.#rootNode = new WeakRef(rootNode);
|
|
65
157
|
// Set up media query if specified (needs rootNode to be set first)
|
|
66
158
|
if (this.#init.whereMediaMatches) {
|
|
@@ -70,6 +162,10 @@ export class MountObserver extends EventTarget {
|
|
|
70
162
|
if (this.#init.whereAttr && !this.#matchesWhereAttrFn) {
|
|
71
163
|
await this.#preloadWhereAttrUtilities();
|
|
72
164
|
}
|
|
165
|
+
// Wait for eager imports to complete if they were started in constructor
|
|
166
|
+
if (this.#init.loadingEagerness === 'eager' && this.#init.import && !this.#importsLoaded) {
|
|
167
|
+
await this.#loadImports();
|
|
168
|
+
}
|
|
73
169
|
// Process existing elements only if media matches
|
|
74
170
|
if (this.#mediaMatches) {
|
|
75
171
|
this.#processNode(rootNode);
|
|
@@ -97,8 +193,8 @@ export class MountObserver extends EventTarget {
|
|
|
97
193
|
else if (mutation.type === 'attributes' && mutation.target.nodeType === Node.ELEMENT_NODE) {
|
|
98
194
|
// Handle attribute changes for mounted elements
|
|
99
195
|
const element = mutation.target;
|
|
100
|
-
if (this.#mountedElements.has(element) && this.#
|
|
101
|
-
const changes = this.#
|
|
196
|
+
if (this.#mountedElements.weakSet.has(element) && this.#checkAttrChangesFn) {
|
|
197
|
+
const changes = this.#checkAttrChangesFn(element);
|
|
102
198
|
attrChanges.push(...changes);
|
|
103
199
|
}
|
|
104
200
|
}
|
|
@@ -106,6 +202,20 @@ export class MountObserver extends EventTarget {
|
|
|
106
202
|
// Batch and dispatch attribute changes
|
|
107
203
|
if (attrChanges.length > 0) {
|
|
108
204
|
this.dispatchEvent(new AttrChangeEvent(attrChanges, this.#init));
|
|
205
|
+
// Dispatch filtered attrchange events to element-specific notifiers
|
|
206
|
+
const changesByElement = new Map();
|
|
207
|
+
for (const change of attrChanges) {
|
|
208
|
+
if (!changesByElement.has(change.element)) {
|
|
209
|
+
changesByElement.set(change.element, []);
|
|
210
|
+
}
|
|
211
|
+
changesByElement.get(change.element).push(change);
|
|
212
|
+
}
|
|
213
|
+
for (const [element, changes] of changesByElement) {
|
|
214
|
+
const notifier = this.#elementNotifiers.get(element);
|
|
215
|
+
if (notifier) {
|
|
216
|
+
notifier.dispatchEvent(new AttrChangeEvent(changes, this.#init));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
109
219
|
}
|
|
110
220
|
};
|
|
111
221
|
const observerConfig = {
|
|
@@ -143,6 +253,23 @@ export class MountObserver extends EventTarget {
|
|
|
143
253
|
const { loadImports } = await import('./loadImports.js');
|
|
144
254
|
this.#modules = await loadImports(this.#init.import);
|
|
145
255
|
this.#importsLoaded = true;
|
|
256
|
+
// Validate referenced whereInstanceOf if reference is specified
|
|
257
|
+
if (this.#init.reference !== undefined) {
|
|
258
|
+
const references = arr(this.#init.reference);
|
|
259
|
+
for (const index of references) {
|
|
260
|
+
const module = this.#modules[index];
|
|
261
|
+
if (module && module.whereInstanceOf !== undefined) {
|
|
262
|
+
// Validate that it's a Constructor or array of Constructors
|
|
263
|
+
const whereInstanceOf = module.whereInstanceOf;
|
|
264
|
+
const constructors = arr(whereInstanceOf);
|
|
265
|
+
for (const constructor of constructors) {
|
|
266
|
+
if (typeof constructor !== 'function') {
|
|
267
|
+
throw new Error(`Referenced module at index ${index} exports invalid whereInstanceOf: must be a Constructor or array of Constructors`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
146
273
|
this.dispatchEvent(new LoadEvent(this.#modules, this.#init));
|
|
147
274
|
}
|
|
148
275
|
#processNode(node) {
|
|
@@ -171,6 +298,13 @@ export class MountObserver extends EventTarget {
|
|
|
171
298
|
if (!matchesElement) {
|
|
172
299
|
return false;
|
|
173
300
|
}
|
|
301
|
+
// Check whereOutside condition if specified (donut hole scoping)
|
|
302
|
+
if (this.#init.whereOutside) {
|
|
303
|
+
const rootNode = this.#rootNode?.deref();
|
|
304
|
+
if (!rootNode || !whereOutside(rootNode, element, this.#init.whereOutside)) {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
174
308
|
// Check whereAttr condition if specified
|
|
175
309
|
if (this.#init.whereAttr) {
|
|
176
310
|
// Use cached function (should be loaded by now from constructor)
|
|
@@ -184,28 +318,45 @@ export class MountObserver extends EventTarget {
|
|
|
184
318
|
}
|
|
185
319
|
// Check whereInstanceOf condition if specified
|
|
186
320
|
if (this.#init.whereInstanceOf) {
|
|
187
|
-
const constructors =
|
|
188
|
-
? this.#init.whereInstanceOf
|
|
189
|
-
: [this.#init.whereInstanceOf];
|
|
321
|
+
const constructors = arr(this.#init.whereInstanceOf);
|
|
190
322
|
// Element must be an instance of at least one constructor (OR logic for array)
|
|
191
323
|
const matchesInstanceOf = constructors.some(constructor => element instanceof constructor);
|
|
192
324
|
if (!matchesInstanceOf) {
|
|
193
325
|
return false;
|
|
194
326
|
}
|
|
195
327
|
}
|
|
328
|
+
// Check referenced whereInstanceOf if imports are loaded and reference is specified
|
|
329
|
+
if (this.#importsLoaded && this.#init.reference !== undefined) {
|
|
330
|
+
const references = arr(this.#init.reference);
|
|
331
|
+
for (const index of references) {
|
|
332
|
+
const module = this.#modules[index];
|
|
333
|
+
if (module && module.whereInstanceOf !== undefined) {
|
|
334
|
+
const constructors = arr(module.whereInstanceOf);
|
|
335
|
+
// Element must be an instance of at least one constructor (OR logic within this module)
|
|
336
|
+
const matchesInstanceOf = constructors.some((constructor) => element instanceof constructor);
|
|
337
|
+
if (!matchesInstanceOf) {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
196
343
|
// All conditions passed
|
|
197
344
|
return true;
|
|
198
345
|
}
|
|
199
346
|
async #handleMatch(element) {
|
|
200
|
-
if (this.#
|
|
347
|
+
if (this.#processedDoForElement.has(element)) {
|
|
201
348
|
return;
|
|
202
349
|
}
|
|
203
350
|
// Load imports if not already loaded
|
|
204
351
|
if (!this.#importsLoaded && this.#init.import) {
|
|
205
352
|
await this.#loadImports();
|
|
206
353
|
}
|
|
207
|
-
this.#
|
|
208
|
-
|
|
354
|
+
this.#processedDoForElement.add(element);
|
|
355
|
+
// Add to both WeakSet and Set<WeakRef> for efficient operations
|
|
356
|
+
if (!this.#mountedElements.weakSet.has(element)) {
|
|
357
|
+
this.#mountedElements.weakSet.add(element);
|
|
358
|
+
this.#mountedElements.setWeak.add(new WeakRef(element));
|
|
359
|
+
}
|
|
209
360
|
const rootNode = this.#rootNode?.deref();
|
|
210
361
|
if (!rootNode) {
|
|
211
362
|
// Root node was garbage collected
|
|
@@ -214,105 +365,119 @@ export class MountObserver extends EventTarget {
|
|
|
214
365
|
const context = {
|
|
215
366
|
modules: this.#modules,
|
|
216
367
|
observer: this,
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
368
|
+
rootNode,
|
|
369
|
+
mountInit: this.#init,
|
|
220
370
|
};
|
|
221
371
|
// Apply assignGingerly if specified
|
|
222
|
-
if (this.#
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
372
|
+
if (this.#asgMtSource) {
|
|
373
|
+
element.assignGingerly(this.#asgMtSource);
|
|
374
|
+
}
|
|
375
|
+
// Check if notifier exists BEFORE calling do callback
|
|
376
|
+
const notifierExistedBeforeDo = this.#elementNotifiers.has(element);
|
|
377
|
+
// Call do callback(s) - can be string, function, or array
|
|
378
|
+
if (this.#init.do !== undefined) {
|
|
379
|
+
const doHandlers = Array.isArray(this.#init.do) ? this.#init.do : [this.#init.do];
|
|
380
|
+
for (const handler of doHandlers) {
|
|
381
|
+
if (typeof handler === 'string') {
|
|
382
|
+
// Registered handler - instantiate it
|
|
383
|
+
const HandlerClass = MountObserver.#handlerRegistry.get(handler);
|
|
384
|
+
if (HandlerClass) {
|
|
385
|
+
new HandlerClass(element, context);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
else if (typeof handler === 'function') {
|
|
389
|
+
// Inline function
|
|
390
|
+
handler(element, context);
|
|
391
|
+
}
|
|
230
392
|
}
|
|
231
|
-
|
|
232
|
-
|
|
393
|
+
}
|
|
394
|
+
// Call referenced do functions from imported modules
|
|
395
|
+
if (this.#init.reference !== undefined) {
|
|
396
|
+
const references = arr(this.#init.reference);
|
|
397
|
+
for (const index of references) {
|
|
398
|
+
const module = this.#modules[index];
|
|
399
|
+
if (module && typeof module.do === 'function') {
|
|
400
|
+
module.do(element, context);
|
|
401
|
+
}
|
|
233
402
|
}
|
|
234
403
|
}
|
|
235
404
|
// Dispatch mount event
|
|
236
|
-
|
|
405
|
+
const mountEvent = new MountEvent(element, this.#modules, this.#init, context);
|
|
406
|
+
this.dispatchEvent(mountEvent);
|
|
407
|
+
// Dispatch to element-specific notifier only if:
|
|
408
|
+
// 1. Notifier existed before do callback (wasn't just created), AND
|
|
409
|
+
// 2. Element hasn't already received a mount event on its notifier
|
|
410
|
+
if (notifierExistedBeforeDo && !this.#notifierMountedElements.has(element)) {
|
|
411
|
+
const notifier = this.#elementNotifiers.get(element);
|
|
412
|
+
if (notifier) {
|
|
413
|
+
this.#notifierMountedElements.add(element);
|
|
414
|
+
notifier.dispatchEvent(mountEvent);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
// Emit events from mounted element if configured
|
|
418
|
+
if (this.#init.mountedElemEmits) {
|
|
419
|
+
const { emitMountedElementEvents } = await import('./emitEvents.js');
|
|
420
|
+
await emitMountedElementEvents(element, this.#init, this.#processedEventsForElement);
|
|
421
|
+
}
|
|
237
422
|
// Check for initial attribute changes if whereAttr is configured
|
|
238
|
-
if (this.#
|
|
239
|
-
const changes = this.#
|
|
423
|
+
if (this.#checkAttrChangesFn) {
|
|
424
|
+
const changes = this.#checkAttrChangesFn(element);
|
|
240
425
|
if (changes.length > 0) {
|
|
241
426
|
this.dispatchEvent(new AttrChangeEvent(changes, this.#init));
|
|
427
|
+
// Also dispatch to element-specific notifier
|
|
428
|
+
const notifier = this.#elementNotifiers.get(element);
|
|
429
|
+
if (notifier) {
|
|
430
|
+
notifier.dispatchEvent(new AttrChangeEvent(changes, this.#init));
|
|
431
|
+
}
|
|
242
432
|
}
|
|
243
433
|
}
|
|
244
434
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
if (
|
|
254
|
-
|
|
255
|
-
this.#
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const mapEntry = this.#init.map?.[coordinate] || null;
|
|
269
|
-
const isOnce = mapEntry?.once === true;
|
|
270
|
-
// If "once" is true, check if we've already seen this attribute
|
|
271
|
-
if (isOnce) {
|
|
272
|
-
let onceAttrs = this.#elementOnceAttrs.get(element);
|
|
273
|
-
if (!onceAttrs) {
|
|
274
|
-
onceAttrs = new Set();
|
|
275
|
-
this.#elementOnceAttrs.set(element, onceAttrs);
|
|
276
|
-
}
|
|
277
|
-
// If we've already seen this attribute, skip it
|
|
278
|
-
if (onceAttrs.has(attrName)) {
|
|
279
|
-
continue;
|
|
280
|
-
}
|
|
281
|
-
// Mark this attribute as seen if it currently has a value
|
|
282
|
-
if (currentValue !== null) {
|
|
283
|
-
onceAttrs.add(attrName);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
// Include if: currently has value OR previously had value but now removed
|
|
287
|
-
if (currentValue !== null || (previousValue !== undefined && currentValue === null)) {
|
|
288
|
-
// Check if value changed
|
|
289
|
-
if (currentValue !== previousValue) {
|
|
290
|
-
const attrNode = currentValue !== null ? element.getAttributeNode(attrName) : null;
|
|
291
|
-
changes.push({
|
|
292
|
-
value: currentValue,
|
|
293
|
-
attrNode,
|
|
294
|
-
mapEntry,
|
|
295
|
-
attrName,
|
|
296
|
-
coordinate,
|
|
297
|
-
element
|
|
298
|
-
});
|
|
299
|
-
// Update state
|
|
300
|
-
if (currentValue !== null) {
|
|
301
|
-
attrState.set(attrName, currentValue);
|
|
302
|
-
}
|
|
303
|
-
else {
|
|
304
|
-
attrState.delete(attrName);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
435
|
+
async assignGingerly(config) {
|
|
436
|
+
// Handle undefined case
|
|
437
|
+
if (config === undefined) {
|
|
438
|
+
this.#asgMtSource = undefined;
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
await import('assign-gingerly/object-extension.js');
|
|
442
|
+
// Update the source config for future mounted elements
|
|
443
|
+
if (this.#asgMtSource === undefined) {
|
|
444
|
+
// No existing config, just clone the passed in object
|
|
445
|
+
this.#asgMtSource = structuredClone(config);
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
// Merge into existing config using assignGingerly
|
|
449
|
+
this.#asgMtSource.assignGingerly(config);
|
|
450
|
+
//assignGingerly(this.#asgMtSource, config);
|
|
451
|
+
}
|
|
452
|
+
// Apply to already mounted elements using setWeak for iteration
|
|
453
|
+
for (const ref of this.#mountedElements.setWeak) {
|
|
454
|
+
const element = ref.deref();
|
|
455
|
+
if (element) {
|
|
456
|
+
element.assignGingerly(config);
|
|
457
|
+
//assignGingerly(element, config);
|
|
307
458
|
}
|
|
308
459
|
}
|
|
309
|
-
return changes;
|
|
310
460
|
}
|
|
311
|
-
#handleRemoval(element) {
|
|
312
|
-
if (!this.#mountedElements.has(element)) {
|
|
461
|
+
async #handleRemoval(element) {
|
|
462
|
+
if (!this.#mountedElements.weakSet.has(element)) {
|
|
313
463
|
return;
|
|
314
464
|
}
|
|
315
|
-
|
|
465
|
+
// Apply assignGingerly if specified for dismount
|
|
466
|
+
if (this.#asgDisMtSource) {
|
|
467
|
+
element.assignGingerly(this.#asgDisMtSource);
|
|
468
|
+
}
|
|
469
|
+
// Remove from both structures
|
|
470
|
+
this.#mountedElements.weakSet.delete(element);
|
|
471
|
+
for (const ref of this.#mountedElements.setWeak) {
|
|
472
|
+
if (ref.deref() === element) {
|
|
473
|
+
this.#mountedElements.setWeak.delete(ref);
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
// Remove from processed set so element can be re-mounted
|
|
478
|
+
this.#processedDoForElement.delete(element);
|
|
479
|
+
// Remove from notifier mounted tracking so mount event can fire again
|
|
480
|
+
this.#notifierMountedElements.delete(element);
|
|
316
481
|
const rootNode = this.#rootNode?.deref();
|
|
317
482
|
if (!rootNode) {
|
|
318
483
|
// Root node was garbage collected
|
|
@@ -321,24 +486,28 @@ export class MountObserver extends EventTarget {
|
|
|
321
486
|
const context = {
|
|
322
487
|
modules: this.#modules,
|
|
323
488
|
observer: this,
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}
|
|
489
|
+
rootNode,
|
|
490
|
+
mountInit: this.#init,
|
|
327
491
|
};
|
|
328
|
-
// Call dismount callback
|
|
329
|
-
if (this.#init.do && typeof this.#init.do !== 'function' && this.#init.do.dismount) {
|
|
330
|
-
this.#init.do.dismount(element, context);
|
|
331
|
-
}
|
|
332
492
|
// Dispatch dismount event
|
|
333
|
-
|
|
493
|
+
const dismountEvent = new DismountEvent(element, 'where-element-matches-failed', this.#init);
|
|
494
|
+
this.dispatchEvent(dismountEvent);
|
|
495
|
+
// Dispatch to element-specific notifier
|
|
496
|
+
const notifier = this.#elementNotifiers.get(element);
|
|
497
|
+
if (notifier) {
|
|
498
|
+
notifier.dispatchEvent(dismountEvent);
|
|
499
|
+
}
|
|
334
500
|
// Check if element is being moved within the same root
|
|
335
501
|
// If it's truly disconnected, dispatch disconnect event
|
|
336
502
|
setTimeout(() => {
|
|
337
503
|
if (!rootNode.contains(element)) {
|
|
338
|
-
|
|
339
|
-
|
|
504
|
+
const disconnectEvent = new DisconnectEvent(element, this.#init);
|
|
505
|
+
this.dispatchEvent(disconnectEvent);
|
|
506
|
+
// Dispatch to element-specific notifier
|
|
507
|
+
const notifier = this.#elementNotifiers.get(element);
|
|
508
|
+
if (notifier) {
|
|
509
|
+
notifier.dispatchEvent(disconnectEvent);
|
|
340
510
|
}
|
|
341
|
-
this.dispatchEvent(new DisconnectEvent(element, this.#init));
|
|
342
511
|
}
|
|
343
512
|
}, 0);
|
|
344
513
|
}
|