mount-observer 0.1.11 → 0.1.13
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 +99 -98
- package/ElementMountExtension.js +183 -8
- package/ElementMountExtension.ts +218 -11
- package/EnhanceMountedElementHandler.js +96 -95
- package/Events.js +18 -18
- package/Events.ts +6 -6
- package/EvtRt.js +24 -17
- package/EvtRt.ts +30 -18
- package/MountObserver.js +296 -81
- package/MountObserver.ts +387 -121
- package/README.md +1508 -235
- package/RegistryMountCoordinator.js +125 -0
- package/RegistryMountCoordinator.ts +181 -0
- package/connectionMonitor.js +116 -0
- package/connectionMonitor.ts +164 -0
- package/elementIntersection.js +67 -0
- package/elementIntersection.ts +96 -0
- package/{getRootRegistryContainer.js → getRegistryRoot.js} +1 -1
- package/{getRootRegistryContainer.ts → getRegistryRoot.ts} +1 -1
- package/index.js +15 -10
- package/index.ts +15 -10
- package/mediaQuery.js +1 -1
- package/mediaQuery.ts +1 -1
- package/observedRootHas.js +87 -0
- package/package.json +67 -61
- package/playwright.config.ts +1 -0
- package/rootSizeObserver.js +124 -0
- package/rootSizeObserver.ts +157 -0
- package/upShadowSearch.js +64 -0
- package/upShadowSearch.ts +62 -0
- package/DefineCustomElementHandler.ts +0 -116
- package/EnhanceMountedElementHandler.ts +0 -110
package/MountObserver.js
CHANGED
|
@@ -15,6 +15,7 @@ export class MountObserver extends EventTarget {
|
|
|
15
15
|
#options;
|
|
16
16
|
#abortController;
|
|
17
17
|
#modules = [];
|
|
18
|
+
#configFromPromise;
|
|
18
19
|
#mountedElements = {
|
|
19
20
|
weakSet: new WeakSet(),
|
|
20
21
|
setWeak: new Set()
|
|
@@ -25,7 +26,13 @@ export class MountObserver extends EventTarget {
|
|
|
25
26
|
#rootNode;
|
|
26
27
|
#importsLoaded = false;
|
|
27
28
|
#mediaQueryCleanup;
|
|
29
|
+
#rootSizeCleanup;
|
|
30
|
+
#intersectionCleanup;
|
|
31
|
+
#connectionCleanup;
|
|
32
|
+
#intersectionObserver;
|
|
28
33
|
#mediaMatches = true;
|
|
34
|
+
#rootSizeMatches = true;
|
|
35
|
+
#connectionMatches = true;
|
|
29
36
|
#asgMtSource;
|
|
30
37
|
#asgDisMtSource;
|
|
31
38
|
#stageMtSource;
|
|
@@ -33,12 +40,40 @@ export class MountObserver extends EventTarget {
|
|
|
33
40
|
#assignTentatively;
|
|
34
41
|
#elementNotifiers = new WeakMap();
|
|
35
42
|
#notifierMountedElements = new WeakSet();
|
|
43
|
+
#subObservers;
|
|
44
|
+
#mergeHandlerDefaults(config) {
|
|
45
|
+
const doValue = config.do;
|
|
46
|
+
// Only process if do is a string (single handler reference)
|
|
47
|
+
if (typeof doValue !== 'string') {
|
|
48
|
+
return config;
|
|
49
|
+
}
|
|
50
|
+
// Look up the handler class
|
|
51
|
+
const HandlerClass = MountObserver.#handlerRegistry.get(doValue);
|
|
52
|
+
if (!HandlerClass) {
|
|
53
|
+
// Validation will catch this later
|
|
54
|
+
return config;
|
|
55
|
+
}
|
|
56
|
+
// Extract static properties from the handler class
|
|
57
|
+
const handlerDefaults = {};
|
|
58
|
+
const proto = HandlerClass;
|
|
59
|
+
// Get all static properties
|
|
60
|
+
for (const key of Object.getOwnPropertyNames(proto)) {
|
|
61
|
+
if (key !== 'prototype' && key !== 'length' && key !== 'name') {
|
|
62
|
+
handlerDefaults[key] = proto[key];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Merge: handler defaults first, then inline config (inline trumps)
|
|
66
|
+
// Using object spread - inline config overwrites handler defaults
|
|
67
|
+
return { ...handlerDefaults, ...config };
|
|
68
|
+
}
|
|
36
69
|
constructor(config, options = {}) {
|
|
37
70
|
super();
|
|
38
|
-
|
|
71
|
+
// Merge handler defaults if do is a string reference
|
|
72
|
+
const mergedConfig = this.#mergeHandlerDefaults(config);
|
|
73
|
+
this.#init = mergedConfig;
|
|
39
74
|
this.#options = options;
|
|
40
75
|
this.#abortController = new AbortController();
|
|
41
|
-
const { assignOnMount, assignOnDismount, stageOnMount, do: doValue,
|
|
76
|
+
const { assignOnMount, assignOnDismount, stageOnMount, do: doValue, loadingEagerness, import: imp, configFrom } = mergedConfig;
|
|
42
77
|
// Make a copy of assignOnMount config using structuredClone
|
|
43
78
|
if (assignOnMount !== undefined) {
|
|
44
79
|
this.#asgMtSource = structuredClone(assignOnMount);
|
|
@@ -58,9 +93,9 @@ export class MountObserver extends EventTarget {
|
|
|
58
93
|
if (doValue !== undefined) {
|
|
59
94
|
this.#validateDoHandlers();
|
|
60
95
|
}
|
|
61
|
-
//
|
|
62
|
-
if (
|
|
63
|
-
this.#
|
|
96
|
+
// Load configFrom modules if specified
|
|
97
|
+
if (configFrom !== undefined) {
|
|
98
|
+
this.#configFromPromise = this.#loadConfigFrom();
|
|
64
99
|
}
|
|
65
100
|
// Start loading imports if eager
|
|
66
101
|
if (loadingEagerness === 'eager' && imp) {
|
|
@@ -80,28 +115,73 @@ export class MountObserver extends EventTarget {
|
|
|
80
115
|
}
|
|
81
116
|
}
|
|
82
117
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
// Normalize
|
|
92
|
-
const
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (
|
|
97
|
-
throw new Error(`
|
|
118
|
+
/**
|
|
119
|
+
* Loads configuration from external modules specified in configFrom property.
|
|
120
|
+
* Merges multiple configs left-to-right, with inline config taking final precedence.
|
|
121
|
+
*/
|
|
122
|
+
async #loadConfigFrom() {
|
|
123
|
+
const { configFrom } = this.#init;
|
|
124
|
+
if (!configFrom)
|
|
125
|
+
return;
|
|
126
|
+
// Normalize to array
|
|
127
|
+
const configPaths = Array.isArray(configFrom) ? configFrom : [configFrom];
|
|
128
|
+
// Check for duplicates
|
|
129
|
+
const pathSet = new Set();
|
|
130
|
+
for (const path of configPaths) {
|
|
131
|
+
if (pathSet.has(path)) {
|
|
132
|
+
throw new Error(`Duplicate configFrom module: '${path}'`);
|
|
98
133
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
134
|
+
pathSet.add(path);
|
|
135
|
+
}
|
|
136
|
+
// Load all modules
|
|
137
|
+
const loadedConfigs = [];
|
|
138
|
+
for (const path of configPaths) {
|
|
139
|
+
try {
|
|
140
|
+
const module = await import(path);
|
|
141
|
+
if (!module.mountConfig) {
|
|
142
|
+
throw new Error(`Module '${path}' does not export 'mountConfig'`);
|
|
143
|
+
}
|
|
144
|
+
if (typeof module.mountConfig !== 'object' || module.mountConfig === null) {
|
|
145
|
+
throw new Error(`Module '${path}' exports invalid mountConfig: must be an object`);
|
|
146
|
+
}
|
|
147
|
+
loadedConfigs.push(module.mountConfig);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
// Re-throw with better context if it's not already our error
|
|
151
|
+
if (error instanceof Error && !error.message.includes(path)) {
|
|
152
|
+
throw new Error(`Failed to load config from '${path}': ${error.message}`);
|
|
153
|
+
}
|
|
154
|
+
throw error;
|
|
103
155
|
}
|
|
104
156
|
}
|
|
157
|
+
// Merge configs: loaded configs first (left-to-right), then inline config
|
|
158
|
+
// Save the original inline config
|
|
159
|
+
const inlineConfig = { ...this.#init };
|
|
160
|
+
// Start with empty object, merge all loaded configs, then merge inline
|
|
161
|
+
let mergedConfig = {};
|
|
162
|
+
for (const loadedConfig of loadedConfigs) {
|
|
163
|
+
mergedConfig = Object.assign(mergedConfig, loadedConfig);
|
|
164
|
+
}
|
|
165
|
+
// Inline config takes final precedence
|
|
166
|
+
mergedConfig = Object.assign(mergedConfig, inlineConfig);
|
|
167
|
+
// Update the init config with merged result
|
|
168
|
+
this.#init = mergedConfig;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Creates and initializes sub-observers from the `with` property.
|
|
172
|
+
* Each sub-observer observes the same root node as the parent.
|
|
173
|
+
* Sub-observers are stored in #subObservers Map for lifecycle management.
|
|
174
|
+
*/
|
|
175
|
+
async #createSubObservers(rootNode) {
|
|
176
|
+
const withConfig = this.#init.with;
|
|
177
|
+
if (!withConfig)
|
|
178
|
+
return;
|
|
179
|
+
this.#subObservers = new Map();
|
|
180
|
+
for (const [key, subConfig] of Object.entries(withConfig)) {
|
|
181
|
+
const subObserver = new MountObserver(subConfig);
|
|
182
|
+
this.#subObservers.set(key, subObserver);
|
|
183
|
+
await subObserver.observe(rootNode);
|
|
184
|
+
}
|
|
105
185
|
}
|
|
106
186
|
async #setupMediaQuery() {
|
|
107
187
|
if (!this.#rootNode) {
|
|
@@ -112,9 +192,46 @@ export class MountObserver extends EventTarget {
|
|
|
112
192
|
this.#mediaMatches = result.mediaMatches;
|
|
113
193
|
this.#mediaQueryCleanup = result.cleanup;
|
|
114
194
|
}
|
|
195
|
+
async #setupRootSizeObserver() {
|
|
196
|
+
if (!this.#rootNode) {
|
|
197
|
+
throw new Error('Cannot setup root size observer before observe() is called');
|
|
198
|
+
}
|
|
199
|
+
const { setupRootSizeObserver } = await import('./rootSizeObserver.js');
|
|
200
|
+
const result = setupRootSizeObserver(this.#init, this.#rootNode, this.#mountedElements, this.#modules, this, (node) => this.#processNode(node));
|
|
201
|
+
this.#rootSizeMatches = result.conditionMatches;
|
|
202
|
+
this.#rootSizeCleanup = result.cleanup;
|
|
203
|
+
}
|
|
204
|
+
async #setupElementIntersection() {
|
|
205
|
+
if (!this.#rootNode) {
|
|
206
|
+
throw new Error('Cannot setup element intersection before observe() is called');
|
|
207
|
+
}
|
|
208
|
+
const { setupElementIntersection } = await import('./elementIntersection.js');
|
|
209
|
+
const result = setupElementIntersection(this.#init, this.#rootNode, this.#mountedElements, this.#modules, this, (element) => this.#matchesSelector(element), (element) => this.#handleMatch(element));
|
|
210
|
+
this.#intersectionObserver = result.intersectionObserver;
|
|
211
|
+
this.#intersectionCleanup = result.cleanup;
|
|
212
|
+
}
|
|
213
|
+
async #setupConnectionMonitor() {
|
|
214
|
+
if (!this.#rootNode) {
|
|
215
|
+
throw new Error('Cannot setup connection monitor before observe() is called');
|
|
216
|
+
}
|
|
217
|
+
const { setupConnectionMonitor } = await import('./connectionMonitor.js');
|
|
218
|
+
const result = setupConnectionMonitor(this.#init, this.#rootNode, this.#mountedElements, this.#modules, this, (node) => this.#processNode(node));
|
|
219
|
+
this.#connectionMatches = result.conditionMatches;
|
|
220
|
+
this.#connectionCleanup = result.cleanup;
|
|
221
|
+
}
|
|
115
222
|
get disconnectedSignal() {
|
|
116
223
|
return this.#abortController.signal;
|
|
117
224
|
}
|
|
225
|
+
get mountedElements() {
|
|
226
|
+
const elements = [];
|
|
227
|
+
for (const ref of this.#mountedElements.setWeak) {
|
|
228
|
+
const element = ref.deref();
|
|
229
|
+
if (element !== undefined) {
|
|
230
|
+
elements.push(element);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return elements;
|
|
234
|
+
}
|
|
118
235
|
getNotifier(element) {
|
|
119
236
|
// Return cached notifier if it exists
|
|
120
237
|
let notifier = this.#elementNotifiers.get(element);
|
|
@@ -126,10 +243,27 @@ export class MountObserver extends EventTarget {
|
|
|
126
243
|
this.#elementNotifiers.set(element, notifier);
|
|
127
244
|
return notifier;
|
|
128
245
|
}
|
|
129
|
-
|
|
246
|
+
/**
|
|
247
|
+
* Begins observing elements within the provided node.
|
|
248
|
+
*
|
|
249
|
+
* @param observedNode - The node to observe for matching elements. This is the root
|
|
250
|
+
* of the observation scope where the mutation observer will be
|
|
251
|
+
* registered. All matching elements within this node (and its
|
|
252
|
+
* descendants) will trigger mount callbacks.
|
|
253
|
+
*
|
|
254
|
+
* Common values:
|
|
255
|
+
* - `document` - Observe the entire document
|
|
256
|
+
* - `element` - Observe a specific subtree
|
|
257
|
+
* - `shadowRoot` - Observe within a shadow DOM
|
|
258
|
+
*/
|
|
259
|
+
async observe(observedNode) {
|
|
130
260
|
if (this.#rootNode) {
|
|
131
261
|
throw new Error('Already observing');
|
|
132
262
|
}
|
|
263
|
+
// Wait for configFrom loading to complete if it was started
|
|
264
|
+
if (this.#configFromPromise) {
|
|
265
|
+
await this.#configFromPromise;
|
|
266
|
+
}
|
|
133
267
|
if (this.#asgMtSource || this.#asgDisMtSource) {
|
|
134
268
|
await import('assign-gingerly/object-extension.js');
|
|
135
269
|
}
|
|
@@ -137,23 +271,37 @@ export class MountObserver extends EventTarget {
|
|
|
137
271
|
const { assignTentatively } = await import('assign-gingerly/assignTentatively.js');
|
|
138
272
|
this.#assignTentatively = assignTentatively;
|
|
139
273
|
}
|
|
140
|
-
this.#rootNode = new WeakRef(
|
|
274
|
+
this.#rootNode = new WeakRef(observedNode);
|
|
275
|
+
// Create sub-observers from `with` property
|
|
276
|
+
await this.#createSubObservers(observedNode);
|
|
141
277
|
// Set up media query if specified (needs rootNode to be set first)
|
|
142
278
|
if (this.#init.withMediaMatching) {
|
|
143
279
|
await this.#setupMediaQuery();
|
|
144
280
|
}
|
|
281
|
+
// Set up root size observer if specified (needs rootNode to be set first)
|
|
282
|
+
if (this.#init.whereObservedRootSizeMatches) {
|
|
283
|
+
await this.#setupRootSizeObserver();
|
|
284
|
+
}
|
|
285
|
+
// Set up element intersection observer if specified (needs rootNode to be set first)
|
|
286
|
+
if (this.#init.whereElementIntersectsWith) {
|
|
287
|
+
await this.#setupElementIntersection();
|
|
288
|
+
}
|
|
289
|
+
// Set up connection monitor if specified (needs rootNode to be set first)
|
|
290
|
+
if (this.#init.whereConnectionHas) {
|
|
291
|
+
await this.#setupConnectionMonitor();
|
|
292
|
+
}
|
|
145
293
|
// Wait for eager imports to complete if they were started in constructor
|
|
146
294
|
if (this.#init.loadingEagerness === 'eager' && this.#init.import && !this.#importsLoaded) {
|
|
147
295
|
await this.#loadImports();
|
|
148
296
|
}
|
|
149
|
-
// Process existing elements only if
|
|
150
|
-
if (this.#mediaMatches) {
|
|
151
|
-
this.#processNode(
|
|
297
|
+
// Process existing elements only if all conditions match
|
|
298
|
+
if (this.#mediaMatches && this.#rootSizeMatches && this.#connectionMatches) {
|
|
299
|
+
this.#processNode(observedNode);
|
|
152
300
|
}
|
|
153
301
|
// Create mutation callback
|
|
154
302
|
this.#mutationCallback = (mutations) => {
|
|
155
|
-
// Skip processing if
|
|
156
|
-
if (!this.#mediaMatches) {
|
|
303
|
+
// Skip processing if any condition doesn't match
|
|
304
|
+
if (!this.#mediaMatches || !this.#rootSizeMatches || !this.#connectionMatches) {
|
|
157
305
|
return;
|
|
158
306
|
}
|
|
159
307
|
for (const mutation of mutations) {
|
|
@@ -176,10 +324,18 @@ export class MountObserver extends EventTarget {
|
|
|
176
324
|
subtree: true
|
|
177
325
|
};
|
|
178
326
|
// Register with shared mutation observer
|
|
179
|
-
registerSharedObserver(
|
|
327
|
+
registerSharedObserver(observedNode, this.#mutationCallback, observerConfig);
|
|
180
328
|
}
|
|
181
329
|
disconnect() {
|
|
182
330
|
const rootNode = this.#rootNode?.deref();
|
|
331
|
+
// Disconnect all sub-observers first (recursive)
|
|
332
|
+
if (this.#subObservers) {
|
|
333
|
+
for (const subObserver of this.#subObservers.values()) {
|
|
334
|
+
subObserver.disconnect();
|
|
335
|
+
}
|
|
336
|
+
this.#subObservers.clear();
|
|
337
|
+
this.#subObservers = undefined;
|
|
338
|
+
}
|
|
183
339
|
// Unregister from shared mutation observer
|
|
184
340
|
if (rootNode && this.#mutationCallback) {
|
|
185
341
|
unregisterSharedObserver(rootNode, this.#mutationCallback);
|
|
@@ -190,6 +346,21 @@ export class MountObserver extends EventTarget {
|
|
|
190
346
|
this.#mediaQueryCleanup();
|
|
191
347
|
this.#mediaQueryCleanup = undefined;
|
|
192
348
|
}
|
|
349
|
+
// Remove root size observer
|
|
350
|
+
if (this.#rootSizeCleanup) {
|
|
351
|
+
this.#rootSizeCleanup();
|
|
352
|
+
this.#rootSizeCleanup = undefined;
|
|
353
|
+
}
|
|
354
|
+
// Remove intersection observer
|
|
355
|
+
if (this.#intersectionCleanup) {
|
|
356
|
+
this.#intersectionCleanup();
|
|
357
|
+
this.#intersectionCleanup = undefined;
|
|
358
|
+
}
|
|
359
|
+
// Remove connection monitor
|
|
360
|
+
if (this.#connectionCleanup) {
|
|
361
|
+
this.#connectionCleanup();
|
|
362
|
+
this.#connectionCleanup = undefined;
|
|
363
|
+
}
|
|
193
364
|
this.#abortController.abort();
|
|
194
365
|
this.#rootNode = undefined;
|
|
195
366
|
}
|
|
@@ -201,30 +372,18 @@ export class MountObserver extends EventTarget {
|
|
|
201
372
|
const { loadImports } = await import('./loadImports.js');
|
|
202
373
|
this.#modules = await loadImports(this.#init.import);
|
|
203
374
|
this.#importsLoaded = true;
|
|
204
|
-
// Validate referenced withInstance if reference is specified
|
|
205
|
-
if (this.#init.reference !== undefined) {
|
|
206
|
-
const references = arr(this.#init.reference);
|
|
207
|
-
for (const index of references) {
|
|
208
|
-
const module = this.#modules[index];
|
|
209
|
-
if (module && module.withInstance !== undefined) {
|
|
210
|
-
// Validate that it's a Constructor or array of Constructors
|
|
211
|
-
const withInstance = module.withInstance;
|
|
212
|
-
const constructors = arr(withInstance);
|
|
213
|
-
for (const constructor of constructors) {
|
|
214
|
-
if (typeof constructor !== 'function') {
|
|
215
|
-
throw new Error(`Referenced module at index ${index} exports invalid withInstance: must be a Constructor or array of Constructors`);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
375
|
this.dispatchEvent(new LoadEvent(this.#modules, this.#init));
|
|
222
376
|
}
|
|
223
377
|
#processNode(node) {
|
|
224
378
|
// If it's an element node, check if it matches
|
|
225
379
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
226
380
|
const element = node;
|
|
227
|
-
|
|
381
|
+
// If intersection observer is active, start observing the element
|
|
382
|
+
// The intersection callback will handle mounting when it intersects
|
|
383
|
+
if (this.#intersectionObserver) {
|
|
384
|
+
this.#intersectionObserver.observe(element);
|
|
385
|
+
}
|
|
386
|
+
else if (this.#matchesSelector(element)) {
|
|
228
387
|
this.#handleMatch(element);
|
|
229
388
|
}
|
|
230
389
|
}
|
|
@@ -232,8 +391,13 @@ export class MountObserver extends EventTarget {
|
|
|
232
391
|
if ('querySelectorAll' in node && this.#init.matching) {
|
|
233
392
|
const root = node;
|
|
234
393
|
// Get all elements matching the CSS selector first
|
|
235
|
-
root.querySelectorAll(this.#init.matching)
|
|
236
|
-
|
|
394
|
+
const matches = root.querySelectorAll(this.#init.matching);
|
|
395
|
+
matches.forEach(child => {
|
|
396
|
+
// If intersection observer is active, start observing the element
|
|
397
|
+
if (this.#intersectionObserver) {
|
|
398
|
+
this.#intersectionObserver.observe(child);
|
|
399
|
+
}
|
|
400
|
+
else if (this.#matchesSelector(child)) {
|
|
237
401
|
this.#handleMatch(child);
|
|
238
402
|
}
|
|
239
403
|
});
|
|
@@ -249,35 +413,47 @@ export class MountObserver extends EventTarget {
|
|
|
249
413
|
if (!matchesElement) {
|
|
250
414
|
return false;
|
|
251
415
|
}
|
|
416
|
+
// Check that element's customElementRegistry matches root node's registry
|
|
417
|
+
const rootNode = this.#rootNode?.deref();
|
|
418
|
+
if (rootNode) {
|
|
419
|
+
const registriesMatch = rootNode.customElementRegistry === element.customElementRegistry;
|
|
420
|
+
// If whereDifferentCustomElementRegistry is true, exclude matching registries
|
|
421
|
+
if (this.#init.whereDifferentCustomElementRegistry) {
|
|
422
|
+
if (registriesMatch)
|
|
423
|
+
return false;
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
// Default behavior: exclude non-matching registries
|
|
427
|
+
if (!registriesMatch)
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
252
431
|
// Check withScopePerimeter condition if specified (donut hole scoping)
|
|
253
432
|
if (this.#init.withScopePerimeter) {
|
|
254
|
-
const rootNode = this.#rootNode?.deref();
|
|
255
433
|
if (!rootNode || !withScopePerimeter(rootNode, element, this.#init.withScopePerimeter)) {
|
|
256
434
|
return false;
|
|
257
435
|
}
|
|
258
436
|
}
|
|
259
|
-
// Check
|
|
260
|
-
if (this.#init.
|
|
261
|
-
|
|
437
|
+
// Check whereObservedRootSizeMatches condition if specified
|
|
438
|
+
if (this.#init.whereObservedRootSizeMatches && !this.#rootSizeMatches) {
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
// Check whereInstanceOf condition if specified
|
|
442
|
+
if (this.#init.whereInstanceOf) {
|
|
443
|
+
const constructors = arr(this.#init.whereInstanceOf);
|
|
262
444
|
// Element must be an instance of at least one constructor (OR logic for array)
|
|
263
445
|
const matchesInstanceOf = constructors.some(constructor => element instanceof constructor);
|
|
264
446
|
if (!matchesInstanceOf) {
|
|
265
447
|
return false;
|
|
266
448
|
}
|
|
267
449
|
}
|
|
268
|
-
// Check
|
|
269
|
-
if (this.#
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
// Element must be an instance of at least one constructor (OR logic within this module)
|
|
276
|
-
const matchesInstanceOf = constructors.some((constructor) => element instanceof constructor);
|
|
277
|
-
if (!matchesInstanceOf) {
|
|
278
|
-
return false;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
450
|
+
// Check whereLocalNameMatches condition if specified
|
|
451
|
+
if (this.#init.whereLocalNameMatches) {
|
|
452
|
+
const pattern = typeof this.#init.whereLocalNameMatches === 'string'
|
|
453
|
+
? new RegExp(this.#init.whereLocalNameMatches)
|
|
454
|
+
: this.#init.whereLocalNameMatches;
|
|
455
|
+
if (!pattern.test(element.localName)) {
|
|
456
|
+
return false;
|
|
281
457
|
}
|
|
282
458
|
}
|
|
283
459
|
// All conditions passed
|
|
@@ -306,8 +482,50 @@ export class MountObserver extends EventTarget {
|
|
|
306
482
|
modules: this.#modules,
|
|
307
483
|
observer: this,
|
|
308
484
|
rootNode,
|
|
309
|
-
|
|
485
|
+
mountConfig: this.#init,
|
|
310
486
|
};
|
|
487
|
+
// Add withObservers if sub-observers exist
|
|
488
|
+
if (this.#subObservers && this.#subObservers.size > 0) {
|
|
489
|
+
context.withObservers = {};
|
|
490
|
+
for (const [key, subObserver] of this.#subObservers.entries()) {
|
|
491
|
+
context.withObservers[key] = subObserver;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
// Check shouldMount condition if specified (final gate before mounting)
|
|
495
|
+
if (this.#init.shouldMount) {
|
|
496
|
+
try {
|
|
497
|
+
const shouldMount = this.#init.shouldMount(element, context);
|
|
498
|
+
if (!shouldMount) {
|
|
499
|
+
// shouldMount returned false - don't mount this element
|
|
500
|
+
// Remove from processed set so it can be re-evaluated later
|
|
501
|
+
this.#processedDoForElement.delete(element);
|
|
502
|
+
// Remove from mounted elements tracking
|
|
503
|
+
this.#mountedElements.weakSet.delete(element);
|
|
504
|
+
for (const ref of this.#mountedElements.setWeak) {
|
|
505
|
+
if (ref.deref() === element) {
|
|
506
|
+
this.#mountedElements.setWeak.delete(ref);
|
|
507
|
+
break;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
catch (error) {
|
|
514
|
+
// shouldMount threw an error - treat as false and log
|
|
515
|
+
console.error('shouldMount check failed:', error);
|
|
516
|
+
// Remove from processed set so it can be re-evaluated later
|
|
517
|
+
this.#processedDoForElement.delete(element);
|
|
518
|
+
// Remove from mounted elements tracking
|
|
519
|
+
this.#mountedElements.weakSet.delete(element);
|
|
520
|
+
for (const ref of this.#mountedElements.setWeak) {
|
|
521
|
+
if (ref.deref() === element) {
|
|
522
|
+
this.#mountedElements.setWeak.delete(ref);
|
|
523
|
+
break;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
311
529
|
// Apply assignGingerly if specified
|
|
312
530
|
if (this.#asgMtSource) {
|
|
313
531
|
element.assignGingerly(this.#asgMtSource);
|
|
@@ -337,16 +555,6 @@ export class MountObserver extends EventTarget {
|
|
|
337
555
|
}
|
|
338
556
|
}
|
|
339
557
|
}
|
|
340
|
-
// Call referenced do functions from imported modules
|
|
341
|
-
if (this.#init.reference !== undefined) {
|
|
342
|
-
const references = arr(this.#init.reference);
|
|
343
|
-
for (const index of references) {
|
|
344
|
-
const module = this.#modules[index];
|
|
345
|
-
if (module && typeof module.do === 'function') {
|
|
346
|
-
module.do(element, context);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
558
|
// Dispatch mount event
|
|
351
559
|
const mountEvent = new MountEvent(element, this.#modules, this.#init, context);
|
|
352
560
|
this.dispatchEvent(mountEvent);
|
|
@@ -429,8 +637,15 @@ export class MountObserver extends EventTarget {
|
|
|
429
637
|
modules: this.#modules,
|
|
430
638
|
observer: this,
|
|
431
639
|
rootNode,
|
|
432
|
-
|
|
640
|
+
mountConfig: this.#init,
|
|
433
641
|
};
|
|
642
|
+
// Add withObservers if sub-observers exist
|
|
643
|
+
if (this.#subObservers && this.#subObservers.size > 0) {
|
|
644
|
+
context.withObservers = {};
|
|
645
|
+
for (const [key, subObserver] of this.#subObservers.entries()) {
|
|
646
|
+
context.withObservers[key] = subObserver;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
434
649
|
// Dispatch dismount event
|
|
435
650
|
const dismountEvent = new DismountEvent(element, 'with-matching-failed', this.#init);
|
|
436
651
|
this.dispatchEvent(dismountEvent);
|