mount-observer 0.1.18 → 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.
@@ -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
- // Find all MOSE scripts in the source element
388
- const sourceScripts = sourceElement.querySelectorAll('script[type="mountobserver"]');
389
-
390
- if (sourceScripts.length === 0) {
391
- return;
392
- }
393
-
394
- // Find all MOSE scripts in the clone
395
- let cloneScripts: NodeListOf<Element>;
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
- // Copy export to cloned script
443
- if (sourceExport) {
444
- (cloneScript as any).export = sourceExport;
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
 
package/index.js CHANGED
@@ -8,6 +8,7 @@ export { DefineCustomElementHandler, DefineScopedCustomElementHandler } from './
8
8
  export { EnhanceMountedElementHandler } from './handlers/EnhanceMountedElement.js';
9
9
  export { ScriptExportHandler } from './handlers/ScriptExport.js';
10
10
  export { MountObserverScriptHandler } from './handlers/MountObserverScript.js';
11
+ export { EMCScriptHandler } from './handlers/EMCScript.js';
11
12
  export { HoistTemplateHandler } from './handlers/HoistTemplate.js';
12
13
  export { HTMLIncludeHandler } from './handlers/HTMLInclude.js';
13
14
  export { upShadowSearch } from './upShadowSearch.js';
@@ -19,5 +20,6 @@ import './handlers/EnhanceMountedElement.js';
19
20
  import './handlers/GenIds.js'; // Temporarily disabled due to missing dependency
20
21
  import './handlers/ScriptExport.js';
21
22
  import './handlers/MountObserverScript.js';
23
+ import './handlers/EMCScript.js';
22
24
  import './handlers/HoistTemplate.js';
23
25
  import './handlers/HTMLInclude.js';
package/index.ts CHANGED
@@ -8,6 +8,7 @@ export { DefineCustomElementHandler, DefineScopedCustomElementHandler } from './
8
8
  export { EnhanceMountedElementHandler } from './handlers/EnhanceMountedElement.js';
9
9
  export { ScriptExportHandler } from './handlers/ScriptExport.js';
10
10
  export { MountObserverScriptHandler } from './handlers/MountObserverScript.js';
11
+ export { EMCScriptHandler } from './handlers/EMCScript.js';
11
12
  export { HoistTemplateHandler } from './handlers/HoistTemplate.js';
12
13
  export { HTMLIncludeHandler } from './handlers/HTMLInclude.js';
13
14
  export { upShadowSearch } from './upShadowSearch.js';
@@ -19,7 +20,8 @@ export type {
19
20
  DoCallback,
20
21
  ImportSpec,
21
22
  IMountEvent,
22
- IDismountEvent
23
+ IDismountEvent,
24
+ EMC
23
25
  } from './types/mount-observer/types.js';
24
26
  export {
25
27
  mountEventName,
@@ -37,6 +39,6 @@ import './handlers/EnhanceMountedElement.js';
37
39
  import './handlers/GenIds.js'; // Temporarily disabled due to missing dependency
38
40
  import './handlers/ScriptExport.js';
39
41
  import './handlers/MountObserverScript.js';
42
+ import './handlers/EMCScript.js';
40
43
  import './handlers/HoistTemplate.js';
41
44
  import './handlers/HTMLInclude.js';
42
-
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mount-observer",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "Observe and act on css matches.",
5
5
  "main": "MountObserver.js",
6
6
  "module": "MountObserver.js",
@@ -30,35 +30,40 @@ export type Spawner<T = any, Obj = Element> = {
30
30
  canSpawn?: (obj: any, ctx?: SpawnContext<T>) => boolean;
31
31
  }
32
32
 
33
+ export interface EnhancementConfigBase<T = any> {
34
+ //Allow unprefixed attributes for custom elements and SVG when element tag name matches pattern
35
+ allowUnprefixed?: string | RegExp;
36
+
37
+ //keys of type symbol are used for dependency injection
38
+ //and are used by assign-gingerly
39
+ symlinks?: { [key: symbol]: keyof T };
40
+
41
+ lifecycleKeys?:
42
+ | true // Use standard names: "dispose" method, "resolved" property/event
43
+ | {
44
+ dispose?: string | symbol,
45
+ resolved?: string | symbol
46
+ }
47
+ //used by mount-observer, not by assign-gingerly
48
+ //impossible to polyfill, but will always be disposed
49
+ //when oElement's reference count goes to zero
50
+ disposeOn?: DisposeEvent | DisposeEvent[]
51
+ }
52
+
33
53
  /**
34
54
  * Configuration for enhancing elements with class instances
35
55
  * Defines how to spawn and initialize enhancement classes
36
56
  */
37
- export interface EnhancementConfig<T = any, Obj = Element> {
57
+ export interface EnhancementConfig<T = any, Obj = Element> extends EnhancementConfigBase<T> {
38
58
 
39
59
  spawn: Spawner<T, Obj>;
40
60
 
41
61
  //Applicable to passing in the initVals during the spawn lifecycle event
42
62
  withAttrs?: AttrPatterns<T>;
43
63
 
44
- //Allow unprefixed attributes for custom elements and SVG when element tag name matches pattern
45
- allowUnprefixed?: string | RegExp;
46
-
47
- //keys of type symbol are used for dependency injection
48
- //and are used by assign-gingerly
49
- symlinks?: { [key: symbol]: keyof T };
64
+
50
65
  //only applicable when spawning from a DOM Element reference
51
66
  enhKey?: EnhKey;
52
- lifecycleKeys?:
53
- | true // Use standard names: "dispose" method, "resolved" property/event
54
- | {
55
- dispose?: string | symbol,
56
- resolved?: string | symbol
57
- }
58
- //used by mount-observer, not by assign-gingerly
59
- //impossible to polyfill, but will always be disposed
60
- //when oElement's reference count goes to zero
61
- disposeOn?: DisposeEvent | DisposeEvent[]
62
67
 
63
68
  }
64
69
 
@@ -1,5 +1,7 @@
1
1
  // Core types for MountObserver v2 - Polyfill Supported Scenario I
2
2
 
3
+ import {Spawner, EnhancementConfigBase, EnhKey} from '../assign-gingerly/types';
4
+
3
5
  export type Constructor = new (...args: any[]) => any;
4
6
 
5
7
  export type EventConstructor = {new(...args: any[]): Event};
@@ -25,6 +27,26 @@ export interface ConnectionCondition {
25
27
  rttMax?: number;
26
28
  }
27
29
 
30
+ /**
31
+ * Configuration for enhancing elements with class instances
32
+ * Defines how to spawn and initialize enhancement classes
33
+ */
34
+ export interface EnhConfig<T = any, Obj = Element> extends EnhancementConfigBase<T, Obj> {
35
+
36
+ // bare import specifier path
37
+ spawn: string;
38
+
39
+ enhKey: EnhKey;
40
+
41
+ //Applicable to passing in the initVals during the spawn lifecycle event
42
+ withAttrs?: AttrPatterns<T>;
43
+
44
+ }
45
+
46
+
47
+
48
+
49
+
28
50
  /**
29
51
  * Configuration object for MountObserver that defines what elements to observe and how to handle them.
30
52
  * All `where*` properties form an AND condition - elements must satisfy ALL specified conditions to mount.
@@ -218,6 +240,15 @@ export interface MountConfig<TKeys extends string = string> {
218
240
  * ```
219
241
  */
220
242
  with?: {[K in TKeys]: MountConfig};
243
+
244
+
245
+ }
246
+
247
+ export interface EMC<
248
+ TKeys extends string = string,
249
+ T = any, Obj = Element
250
+ > extends MountConfig<TKeys>{
251
+ enhConfig: EnhConfig<T, Obj>
221
252
  }
222
253
 
223
254