mount-observer 0.1.19 → 0.1.20
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/handlers/HTMLInclude.js +48 -47
- package/handlers/HTMLInclude.ts +92 -87
- package/package.json +1 -1
package/handlers/HTMLInclude.js
CHANGED
|
@@ -333,57 +333,58 @@ export class HTMLIncludeHandler extends EvtRt {
|
|
|
333
333
|
if (sourceRootNode === templateRootNode) {
|
|
334
334
|
return;
|
|
335
335
|
}
|
|
336
|
-
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
let cloneScripts;
|
|
343
|
-
if (clone instanceof Element) {
|
|
344
|
-
cloneScripts = clone.querySelectorAll('script[type="mountobserver"]');
|
|
345
|
-
}
|
|
346
|
-
else if (clone instanceof DocumentFragment) {
|
|
347
|
-
cloneScripts = clone.querySelectorAll('script[type="mountobserver"]');
|
|
348
|
-
}
|
|
349
|
-
else {
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
|
-
// Copy exports from source scripts to cloned scripts (matching by ID)
|
|
353
|
-
for (let i = 0; i < sourceScripts.length; i++) {
|
|
354
|
-
const sourceScript = sourceScripts[i];
|
|
355
|
-
const sourceId = sourceScript.getAttribute('id');
|
|
356
|
-
if (!sourceId)
|
|
336
|
+
const types = ['mountobserver', 'emc'];
|
|
337
|
+
for (const t of types) {
|
|
338
|
+
const qry = `script[type="${t}"]`;
|
|
339
|
+
// Find all MOSE scripts in the source element
|
|
340
|
+
const sourceScripts = sourceElement.querySelectorAll(qry);
|
|
341
|
+
if (sourceScripts.length === 0) {
|
|
357
342
|
continue;
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
343
|
+
}
|
|
344
|
+
// Find all MOSE scripts in the clone
|
|
345
|
+
let cloneScripts;
|
|
346
|
+
if (clone instanceof Element || clone instanceof DocumentFragment) {
|
|
347
|
+
cloneScripts = clone.querySelectorAll('script[type="mountobserver"]');
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
361
350
|
continue;
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
const event = await new Promise((resolve, reject) => {
|
|
369
|
-
const timeout = setTimeout(() => {
|
|
370
|
-
reject(new Error('Timeout'));
|
|
371
|
-
}, 5000);
|
|
372
|
-
sourceScript.addEventListener('resolved', (e) => {
|
|
373
|
-
clearTimeout(timeout);
|
|
374
|
-
resolve(e);
|
|
375
|
-
}, { once: true });
|
|
376
|
-
});
|
|
377
|
-
sourceExport = event.export;
|
|
378
|
-
}
|
|
379
|
-
catch (error) {
|
|
380
|
-
console.warn(`HTMLInclude: Timeout waiting for MOSE script #${sourceId} to resolve`);
|
|
351
|
+
}
|
|
352
|
+
// Copy exports from source scripts to cloned scripts (matching by ID)
|
|
353
|
+
for (let i = 0; i < sourceScripts.length; i++) {
|
|
354
|
+
const sourceScript = sourceScripts[i];
|
|
355
|
+
const sourceId = sourceScript.getAttribute('id');
|
|
356
|
+
if (!sourceId)
|
|
381
357
|
continue;
|
|
358
|
+
// Find matching clone script by ID
|
|
359
|
+
const cloneScript = Array.from(cloneScripts).find(s => s.getAttribute('id') === sourceId);
|
|
360
|
+
if (!cloneScript)
|
|
361
|
+
continue;
|
|
362
|
+
// Check if source script has export
|
|
363
|
+
let sourceExport = sourceScript.export;
|
|
364
|
+
if (!sourceExport) {
|
|
365
|
+
// Wait for the source script to resolve
|
|
366
|
+
try {
|
|
367
|
+
// Create a promise that waits for the resolved event
|
|
368
|
+
const event = await new Promise((resolve, reject) => {
|
|
369
|
+
const timeout = setTimeout(() => {
|
|
370
|
+
reject(new Error('Timeout'));
|
|
371
|
+
}, 5000);
|
|
372
|
+
sourceScript.addEventListener('resolved', (e) => {
|
|
373
|
+
clearTimeout(timeout);
|
|
374
|
+
resolve(e);
|
|
375
|
+
}, { once: true });
|
|
376
|
+
});
|
|
377
|
+
sourceExport = event.export;
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
console.warn(`HTMLInclude: Timeout waiting for MOSE script #${sourceId} to resolve`);
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
// Copy export to cloned script
|
|
385
|
+
if (sourceExport) {
|
|
386
|
+
cloneScript.export = sourceExport;
|
|
382
387
|
}
|
|
383
|
-
}
|
|
384
|
-
// Copy export to cloned script
|
|
385
|
-
if (sourceExport) {
|
|
386
|
-
cloneScript.export = sourceExport;
|
|
387
388
|
}
|
|
388
389
|
}
|
|
389
390
|
}
|
package/handlers/HTMLInclude.ts
CHANGED
|
@@ -31,19 +31,19 @@ function toQuery(el: Element): string {
|
|
|
31
31
|
// Get the list of attributes to exclude from the selector
|
|
32
32
|
const insertAttrs = el.getAttribute('-i');
|
|
33
33
|
const excludeAttrs = new Set(['-i']); // Always exclude -i itself
|
|
34
|
-
|
|
34
|
+
|
|
35
35
|
if (insertAttrs !== null) {
|
|
36
36
|
const attrs = splitRefs(insertAttrs);
|
|
37
37
|
attrs.forEach(attr => excludeAttrs.add(attr));
|
|
38
38
|
}
|
|
39
|
-
|
|
39
|
+
|
|
40
40
|
const classes = Array.from(el.classList).map(c => `.${c}`).join('');
|
|
41
41
|
const parts = Array.from(el.part).map(p => `[part~="${p}"]`).join('');
|
|
42
42
|
const attributes = Array.from(el.attributes)
|
|
43
43
|
.filter(attr => !excludeAttrs.has(attr.name))
|
|
44
44
|
.map(attr => `[${attr.name}="${attr.value}"]`)
|
|
45
45
|
.join('');
|
|
46
|
-
const {localName} = el;
|
|
46
|
+
const { localName } = el;
|
|
47
47
|
return `${localName}${classes}${parts}${attributes}`;
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -51,19 +51,19 @@ function toQuery(el: Element): string {
|
|
|
51
51
|
* Prepares an element for insertion by extracting its children and insertion attributes.
|
|
52
52
|
* Returns a DocumentFragment with the children and a map of attributes to insert.
|
|
53
53
|
*/
|
|
54
|
-
function prepareForInsertion(el: Element): { fragment: DocumentFragment, attributeMap: {[key: string]: string} | null } {
|
|
54
|
+
function prepareForInsertion(el: Element): { fragment: DocumentFragment, attributeMap: { [key: string]: string } | null } {
|
|
55
55
|
const fragment = new DocumentFragment();
|
|
56
56
|
const clone = el.cloneNode(true) as Element;
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
// Move all children to the fragment
|
|
59
59
|
while (clone.firstChild) {
|
|
60
60
|
fragment.appendChild(clone.firstChild);
|
|
61
61
|
}
|
|
62
|
-
|
|
62
|
+
|
|
63
63
|
// Check for -i attribute which specifies which attributes to insert
|
|
64
64
|
const insertAttrs = el.getAttribute('-i');
|
|
65
|
-
let attributeMap: {[key: string]: string} | null = null;
|
|
66
|
-
|
|
65
|
+
let attributeMap: { [key: string]: string } | null = null;
|
|
66
|
+
|
|
67
67
|
if (insertAttrs !== null) {
|
|
68
68
|
const attrs = splitRefs(insertAttrs);
|
|
69
69
|
attributeMap = {};
|
|
@@ -74,7 +74,7 @@ function prepareForInsertion(el: Element): { fragment: DocumentFragment, attribu
|
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
|
-
|
|
77
|
+
|
|
78
78
|
return { fragment, attributeMap };
|
|
79
79
|
}
|
|
80
80
|
|
|
@@ -84,14 +84,14 @@ function prepareForInsertion(el: Element): { fragment: DocumentFragment, attribu
|
|
|
84
84
|
function applyInsertion(
|
|
85
85
|
targetElement: Element,
|
|
86
86
|
sourceFragment: DocumentFragment,
|
|
87
|
-
attributeMap: {[key: string]: string} | null
|
|
87
|
+
attributeMap: { [key: string]: string } | null
|
|
88
88
|
): void {
|
|
89
89
|
// Clone the fragment so it can be reused
|
|
90
90
|
const fragmentClone = sourceFragment.cloneNode(true) as DocumentFragment;
|
|
91
|
-
|
|
91
|
+
|
|
92
92
|
// Replace all children of the target element
|
|
93
93
|
targetElement.replaceChildren(fragmentClone);
|
|
94
|
-
|
|
94
|
+
|
|
95
95
|
// Update attributes if specified
|
|
96
96
|
if (attributeMap !== null) {
|
|
97
97
|
for (const key in attributeMap) {
|
|
@@ -151,7 +151,7 @@ function applyInsertion(
|
|
|
151
151
|
export class HTMLIncludeHandler extends EvtRt {
|
|
152
152
|
static matching = 'template[src^="#"]';
|
|
153
153
|
static whereInstanceOf = HTMLTemplateElement;
|
|
154
|
-
|
|
154
|
+
|
|
155
155
|
async mount(mountedElement: Element): Promise<void> {
|
|
156
156
|
try {
|
|
157
157
|
const template = mountedElement as HTMLTemplateElement;
|
|
@@ -210,7 +210,7 @@ export class HTMLIncludeHandler extends EvtRt {
|
|
|
210
210
|
console.warn(`HTMLInclude: ${error}`);
|
|
211
211
|
return;
|
|
212
212
|
}
|
|
213
|
-
|
|
213
|
+
|
|
214
214
|
// Optimization 4: Copy MOSE exports if cloning live element from different root
|
|
215
215
|
if (isLiveElement) {
|
|
216
216
|
await this.copyMoseExports(sourceElement, clone, rootNode);
|
|
@@ -278,27 +278,27 @@ export class HTMLIncludeHandler extends EvtRt {
|
|
|
278
278
|
}
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
-
|
|
281
|
+
|
|
282
282
|
/**
|
|
283
283
|
* Gets a cached element reference if available and still valid.
|
|
284
284
|
*/
|
|
285
285
|
getCachedElement(rootNode: Node, id: string): Element | null {
|
|
286
286
|
const rootCache = idCache.get(rootNode);
|
|
287
287
|
if (!rootCache) return null;
|
|
288
|
-
|
|
288
|
+
|
|
289
289
|
const weakRef = rootCache.get(id);
|
|
290
290
|
if (!weakRef) return null;
|
|
291
|
-
|
|
291
|
+
|
|
292
292
|
const element = weakRef.deref();
|
|
293
293
|
if (!element) {
|
|
294
294
|
// Element was garbage collected, remove from cache
|
|
295
295
|
rootCache.delete(id);
|
|
296
296
|
return null;
|
|
297
297
|
}
|
|
298
|
-
|
|
298
|
+
|
|
299
299
|
return element;
|
|
300
300
|
}
|
|
301
|
-
|
|
301
|
+
|
|
302
302
|
/**
|
|
303
303
|
* Caches an element reference for future lookups.
|
|
304
304
|
*/
|
|
@@ -310,7 +310,7 @@ export class HTMLIncludeHandler extends EvtRt {
|
|
|
310
310
|
}
|
|
311
311
|
rootCache.set(id, new WeakRef(element));
|
|
312
312
|
}
|
|
313
|
-
|
|
313
|
+
|
|
314
314
|
/**
|
|
315
315
|
* Processes matching insertions by finding elements in the cloned content that match
|
|
316
316
|
* the selectors from template children and applying insertions to them.
|
|
@@ -320,13 +320,13 @@ export class HTMLIncludeHandler extends EvtRt {
|
|
|
320
320
|
for (const templateChild of templateChildren) {
|
|
321
321
|
// Generate a selector from the template child
|
|
322
322
|
const selector = toQuery(templateChild);
|
|
323
|
-
|
|
323
|
+
|
|
324
324
|
// Prepare the insertion content and attribute map
|
|
325
325
|
const { fragment, attributeMap } = prepareForInsertion(templateChild);
|
|
326
|
-
|
|
326
|
+
|
|
327
327
|
// Find all matching elements in the cloned content
|
|
328
328
|
let matchingElements: Element[] = [];
|
|
329
|
-
|
|
329
|
+
|
|
330
330
|
if (clonedContent instanceof Element) {
|
|
331
331
|
// Check if the cloned element itself matches
|
|
332
332
|
if (clonedContent.matches(selector)) {
|
|
@@ -339,14 +339,14 @@ export class HTMLIncludeHandler extends EvtRt {
|
|
|
339
339
|
// Search within the fragment
|
|
340
340
|
matchingElements = Array.from(clonedContent.querySelectorAll(selector));
|
|
341
341
|
}
|
|
342
|
-
|
|
342
|
+
|
|
343
343
|
// Apply insertion to each matching element
|
|
344
344
|
for (const matchingElement of matchingElements) {
|
|
345
345
|
applyInsertion(matchingElement, fragment, attributeMap);
|
|
346
346
|
}
|
|
347
347
|
}
|
|
348
348
|
}
|
|
349
|
-
|
|
349
|
+
|
|
350
350
|
/**
|
|
351
351
|
* Clones content from the source element.
|
|
352
352
|
* Priority: remoteContent (hoisted templates) > content (templates) > element itself
|
|
@@ -362,88 +362,93 @@ export class HTMLIncludeHandler extends EvtRt {
|
|
|
362
362
|
console.warn('HTMLInclude: Failed to access remoteContent', e);
|
|
363
363
|
}
|
|
364
364
|
}
|
|
365
|
-
|
|
365
|
+
|
|
366
366
|
// Check for content property (regular templates)
|
|
367
367
|
if (sourceElement instanceof HTMLTemplateElement && sourceElement.content) {
|
|
368
368
|
return { clone: sourceElement.content.cloneNode(true), isLiveElement: false };
|
|
369
369
|
}
|
|
370
|
-
|
|
370
|
+
|
|
371
371
|
// Clone the element itself (live DOM element)
|
|
372
372
|
return { clone: sourceElement.cloneNode(true), isLiveElement: true };
|
|
373
373
|
}
|
|
374
|
-
|
|
374
|
+
|
|
375
375
|
/**
|
|
376
376
|
* Copies MOSE script exports from source to cloned scripts.
|
|
377
377
|
* This optimization avoids re-parsing JSON when cloning MOSE scripts across shadow boundaries.
|
|
378
378
|
*/
|
|
379
379
|
async copyMoseExports(sourceElement: Element, clone: Node, templateRootNode: Node): Promise<void> {
|
|
380
380
|
const sourceRootNode = sourceElement.getRootNode();
|
|
381
|
-
|
|
381
|
+
|
|
382
382
|
// Only process if source and template are in different root nodes
|
|
383
383
|
if (sourceRootNode === templateRootNode) {
|
|
384
384
|
return;
|
|
385
385
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
if (clone instanceof Element) {
|
|
397
|
-
cloneScripts = clone.querySelectorAll('script[type="mountobserver"]');
|
|
398
|
-
} else if (clone instanceof DocumentFragment) {
|
|
399
|
-
cloneScripts = clone.querySelectorAll('script[type="mountobserver"]');
|
|
400
|
-
} else {
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// Copy exports from source scripts to cloned scripts (matching by ID)
|
|
405
|
-
for (let i = 0; i < sourceScripts.length; i++) {
|
|
406
|
-
const sourceScript = sourceScripts[i] as HTMLScriptElement;
|
|
407
|
-
const sourceId = sourceScript.getAttribute('id');
|
|
408
|
-
|
|
409
|
-
if (!sourceId) continue;
|
|
410
|
-
|
|
411
|
-
// Find matching clone script by ID
|
|
412
|
-
const cloneScript = Array.from(cloneScripts).find(
|
|
413
|
-
s => s.getAttribute('id') === sourceId
|
|
414
|
-
) as HTMLScriptElement | undefined;
|
|
415
|
-
|
|
416
|
-
if (!cloneScript) continue;
|
|
417
|
-
|
|
418
|
-
// Check if source script has export
|
|
419
|
-
let sourceExport = (sourceScript as any).export;
|
|
420
|
-
|
|
421
|
-
if (!sourceExport) {
|
|
422
|
-
// Wait for the source script to resolve
|
|
423
|
-
try {
|
|
424
|
-
// Create a promise that waits for the resolved event
|
|
425
|
-
const event = await new Promise<Event>((resolve, reject) => {
|
|
426
|
-
const timeout = setTimeout(() => {
|
|
427
|
-
reject(new Error('Timeout'));
|
|
428
|
-
}, 5000);
|
|
429
|
-
|
|
430
|
-
sourceScript.addEventListener('resolved', (e) => {
|
|
431
|
-
clearTimeout(timeout);
|
|
432
|
-
resolve(e);
|
|
433
|
-
}, { once: true });
|
|
434
|
-
});
|
|
435
|
-
sourceExport = (event as any).export;
|
|
436
|
-
} catch (error) {
|
|
437
|
-
console.warn(`HTMLInclude: Timeout waiting for MOSE script #${sourceId} to resolve`);
|
|
438
|
-
continue;
|
|
439
|
-
}
|
|
386
|
+
|
|
387
|
+
const types = ['mountobserver', 'emc'];
|
|
388
|
+
|
|
389
|
+
for (const t of types) {
|
|
390
|
+
const qry = `script[type="${t}"]`;
|
|
391
|
+
// Find all MOSE scripts in the source element
|
|
392
|
+
const sourceScripts = sourceElement.querySelectorAll(qry);
|
|
393
|
+
|
|
394
|
+
if (sourceScripts.length === 0) {
|
|
395
|
+
continue;
|
|
440
396
|
}
|
|
441
|
-
|
|
442
|
-
//
|
|
443
|
-
|
|
444
|
-
|
|
397
|
+
|
|
398
|
+
// Find all MOSE scripts in the clone
|
|
399
|
+
let cloneScripts: NodeListOf<Element>;
|
|
400
|
+
if (clone instanceof Element || clone instanceof DocumentFragment) {
|
|
401
|
+
cloneScripts = clone.querySelectorAll('script[type="mountobserver"]');
|
|
402
|
+
} else {
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Copy exports from source scripts to cloned scripts (matching by ID)
|
|
407
|
+
for (let i = 0; i < sourceScripts.length; i++) {
|
|
408
|
+
const sourceScript = sourceScripts[i] as HTMLScriptElement;
|
|
409
|
+
const sourceId = sourceScript.getAttribute('id');
|
|
410
|
+
|
|
411
|
+
if (!sourceId) continue;
|
|
412
|
+
|
|
413
|
+
// Find matching clone script by ID
|
|
414
|
+
const cloneScript = Array.from(cloneScripts).find(
|
|
415
|
+
s => s.getAttribute('id') === sourceId
|
|
416
|
+
) as HTMLScriptElement | undefined;
|
|
417
|
+
|
|
418
|
+
if (!cloneScript) continue;
|
|
419
|
+
|
|
420
|
+
// Check if source script has export
|
|
421
|
+
let sourceExport = (sourceScript as any).export;
|
|
422
|
+
|
|
423
|
+
if (!sourceExport) {
|
|
424
|
+
// Wait for the source script to resolve
|
|
425
|
+
try {
|
|
426
|
+
// Create a promise that waits for the resolved event
|
|
427
|
+
const event = await new Promise<Event>((resolve, reject) => {
|
|
428
|
+
const timeout = setTimeout(() => {
|
|
429
|
+
reject(new Error('Timeout'));
|
|
430
|
+
}, 5000);
|
|
431
|
+
|
|
432
|
+
sourceScript.addEventListener('resolved', (e) => {
|
|
433
|
+
clearTimeout(timeout);
|
|
434
|
+
resolve(e);
|
|
435
|
+
}, { once: true });
|
|
436
|
+
});
|
|
437
|
+
sourceExport = (event as any).export;
|
|
438
|
+
} catch (error) {
|
|
439
|
+
console.warn(`HTMLInclude: Timeout waiting for MOSE script #${sourceId} to resolve`);
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Copy export to cloned script
|
|
445
|
+
if (sourceExport) {
|
|
446
|
+
(cloneScript as any).export = sourceExport;
|
|
447
|
+
}
|
|
445
448
|
}
|
|
446
449
|
}
|
|
450
|
+
|
|
451
|
+
|
|
447
452
|
}
|
|
448
453
|
}
|
|
449
454
|
|