mount-observer 0.1.18 → 0.1.19
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/README.md +98 -0
- package/handlers/DefineCustomElement.js +51 -39
- package/handlers/DefineCustomElement.ts +57 -45
- package/handlers/EMCScript.js +178 -0
- package/handlers/EMCScript.ts +216 -0
- package/handlers/EnhanceMountedElement.js +9 -9
- package/handlers/EnhanceMountedElement.ts +9 -9
- package/index.js +2 -0
- package/index.ts +4 -2
- package/package.json +1 -1
- package/types/assign-gingerly/types.d.ts +22 -17
- package/types/mount-observer/types.d.ts +31 -0
package/README.md
CHANGED
|
@@ -568,6 +568,104 @@ The handler automatically hoists templates that:
|
|
|
568
568
|
|
|
569
569
|
</details>
|
|
570
570
|
|
|
571
|
+
## Element Mount Configuration (EMC) Scripts
|
|
572
|
+
|
|
573
|
+
The `builtIns.emcScript` handler provides declarative element enhancement using `<script type="emc">` elements. EMC scripts combine mount observation with the [assign-gingerly](https://github.com/bahrus/assign-gingerly) enhancement system to apply behaviors, properties, and classes to elements as they mount.
|
|
574
|
+
|
|
575
|
+
**Why use EMC scripts?**
|
|
576
|
+
|
|
577
|
+
- Declaratively enhance elements without writing JavaScript
|
|
578
|
+
- Lazy load enhancement classes only when needed
|
|
579
|
+
- Automatically register and spawn enhancements
|
|
580
|
+
- Works with scoped custom element registries
|
|
581
|
+
- Supports attribute-based element matching
|
|
582
|
+
- Reuses enhancement definitions across multiple elements
|
|
583
|
+
|
|
584
|
+
**Basic usage:**
|
|
585
|
+
|
|
586
|
+
```html
|
|
587
|
+
<!-- Define enhancement configuration -->
|
|
588
|
+
<script type="emc">
|
|
589
|
+
{
|
|
590
|
+
"matching": ".interactive",
|
|
591
|
+
"enhConfig": {
|
|
592
|
+
"spawn": "./my-enhancement.js",
|
|
593
|
+
"enhKey": "myEnhancement"
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
</script>
|
|
597
|
+
|
|
598
|
+
<!-- Elements matching the selector get enhanced -->
|
|
599
|
+
<div class="interactive">This will be enhanced</div>
|
|
600
|
+
<div class="interactive">This too</div>
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
**External JSON configuration:**
|
|
604
|
+
|
|
605
|
+
```html
|
|
606
|
+
<!-- Load configuration from external file -->
|
|
607
|
+
<script type="emc" src="./enh-config.json"></script>
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
**With attribute matching:**
|
|
611
|
+
|
|
612
|
+
```html
|
|
613
|
+
<script type="emc">
|
|
614
|
+
{
|
|
615
|
+
"matching": "button",
|
|
616
|
+
"enhConfig": {
|
|
617
|
+
"spawn": "./button-enhancement.js",
|
|
618
|
+
"enhKey": "fancyButton",
|
|
619
|
+
"withAttrs": {
|
|
620
|
+
"base": "variant"
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
</script>
|
|
625
|
+
|
|
626
|
+
<!-- Only buttons with variant="primary" get enhanced -->
|
|
627
|
+
<button variant="primary">Enhanced</button>
|
|
628
|
+
<button>Not enhanced</button>
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
**How it works:**
|
|
632
|
+
|
|
633
|
+
1. EMC script is parsed (inline JSON or external via `src`)
|
|
634
|
+
2. Configuration is stored on `scriptElement.export`
|
|
635
|
+
3. `resolved` event is dispatched
|
|
636
|
+
4. Script ID is auto-generated as `${parentElement.localName}.${enhKey}` if not specified
|
|
637
|
+
5. MountObserver watches for elements matching the configuration
|
|
638
|
+
6. When element mounts:
|
|
639
|
+
- Checks if already enhanced (via `element.enh[enhKey]`)
|
|
640
|
+
- Registers enhancement class if not already registered
|
|
641
|
+
- Spawns enhancement instance via `element.enh.get(enhancementConfig)`
|
|
642
|
+
|
|
643
|
+
**Enhancement class example:**
|
|
644
|
+
|
|
645
|
+
```javascript
|
|
646
|
+
// my-enhancement.js
|
|
647
|
+
export default class MyEnhancement {
|
|
648
|
+
constructor(element, ctx, initVals) {
|
|
649
|
+
this.element = element;
|
|
650
|
+
// Apply enhancement
|
|
651
|
+
this.element.classList.add('enhanced');
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
dispose() {
|
|
655
|
+
// Cleanup
|
|
656
|
+
this.element.classList.remove('enhanced');
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
**Requirements:**
|
|
662
|
+
|
|
663
|
+
- Must import `ElementMountExtension.js` to enable `element.enh` property
|
|
664
|
+
- Must import `assign-gingerly/object-extension.js` for enhancement registry
|
|
665
|
+
- Enhancement classes should be constructors that accept `(element, ctx, initVals)`
|
|
666
|
+
|
|
667
|
+
[Implemented as EMCScript requirement](requirements/Done/EMCScript.md)
|
|
668
|
+
|
|
571
669
|
## Intra-Document HTML Includes with HTMLInclude
|
|
572
670
|
|
|
573
671
|
The `builtIns.HTMLInclude` handler enables declarative HTML fragment reuse within a document using `<template src="#id">` syntax. Think of it as "constants for HTML" - define content once with an ID, then reference it multiple times throughout your document.
|
|
@@ -1,4 +1,53 @@
|
|
|
1
1
|
import { EvtRt } from '../EvtRt.js';
|
|
2
|
+
/**
|
|
3
|
+
* Find a suitable HTMLElement class from a module.
|
|
4
|
+
* Checks the default export first, then searches all exports.
|
|
5
|
+
* @param module - The imported module
|
|
6
|
+
* @returns The HTMLElement class constructor
|
|
7
|
+
* @throws Error if no suitable class is found or multiple classes are found
|
|
8
|
+
*/
|
|
9
|
+
function findSuitableClass(module) {
|
|
10
|
+
// Check default export first
|
|
11
|
+
const defaultExport = module.default;
|
|
12
|
+
if (defaultExport && extendsHTMLElement(defaultExport)) {
|
|
13
|
+
return defaultExport;
|
|
14
|
+
}
|
|
15
|
+
// Find all exports that extend HTMLElement
|
|
16
|
+
const htmlElementClasses = Object.values(module)
|
|
17
|
+
.filter(exp => typeof exp === 'function' && extendsHTMLElement(exp));
|
|
18
|
+
if (htmlElementClasses.length === 0) {
|
|
19
|
+
throw new Error('No suitable class found in module');
|
|
20
|
+
}
|
|
21
|
+
if (htmlElementClasses.length > 1) {
|
|
22
|
+
throw new Error('More than one class found in module');
|
|
23
|
+
}
|
|
24
|
+
return htmlElementClasses[0];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Check if a class extends HTMLElement.
|
|
28
|
+
* @param cls - The class to check
|
|
29
|
+
* @returns true if the class extends HTMLElement
|
|
30
|
+
*/
|
|
31
|
+
function extendsHTMLElement(cls) {
|
|
32
|
+
try {
|
|
33
|
+
// Must be a function
|
|
34
|
+
if (typeof cls !== 'function') {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
// Handle direct HTMLElement export
|
|
38
|
+
if (cls === HTMLElement) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
// Check if it has a prototype and extends HTMLElement
|
|
42
|
+
if (cls.prototype && cls.prototype instanceof HTMLElement) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
2
51
|
export class DefineCustomElementHandler extends EvtRt {
|
|
3
52
|
mount(mountedElement, MountConfig, context) {
|
|
4
53
|
this.abort();
|
|
@@ -12,8 +61,8 @@ export class DefineCustomElementHandler extends EvtRt {
|
|
|
12
61
|
if (customElements.get(tagName)) {
|
|
13
62
|
return;
|
|
14
63
|
}
|
|
15
|
-
// Find suitable class
|
|
16
|
-
const ElementClass =
|
|
64
|
+
// Find suitable class using shared utility
|
|
65
|
+
const ElementClass = findSuitableClass(module);
|
|
17
66
|
// Validate that ElementClass is a constructor
|
|
18
67
|
if (typeof ElementClass !== 'function') {
|
|
19
68
|
throw new Error(`Found class is not a constructor: ${typeof ElementClass}`);
|
|
@@ -35,43 +84,6 @@ export class DefineCustomElementHandler extends EvtRt {
|
|
|
35
84
|
define(tagName, ElementClass, mountedElement) {
|
|
36
85
|
customElements.define(tagName, ElementClass);
|
|
37
86
|
}
|
|
38
|
-
findSuitableClass(module) {
|
|
39
|
-
// Check default export first
|
|
40
|
-
const defaultExport = module.default;
|
|
41
|
-
if (defaultExport && this.extendsHTMLElement(defaultExport)) {
|
|
42
|
-
return defaultExport;
|
|
43
|
-
}
|
|
44
|
-
// Find all exports that extend HTMLElement
|
|
45
|
-
const htmlElementClasses = Object.values(module)
|
|
46
|
-
.filter(exp => typeof exp === 'function' && this.extendsHTMLElement(exp));
|
|
47
|
-
if (htmlElementClasses.length === 0) {
|
|
48
|
-
throw new Error('No suitable class found in module');
|
|
49
|
-
}
|
|
50
|
-
if (htmlElementClasses.length > 1) {
|
|
51
|
-
throw new Error('More than one class found in module');
|
|
52
|
-
}
|
|
53
|
-
return htmlElementClasses[0];
|
|
54
|
-
}
|
|
55
|
-
extendsHTMLElement(cls) {
|
|
56
|
-
try {
|
|
57
|
-
// Must be a function
|
|
58
|
-
if (typeof cls !== 'function') {
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
// Handle direct HTMLElement export
|
|
62
|
-
if (cls === HTMLElement) {
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
// Check if it has a prototype and extends HTMLElement
|
|
66
|
-
if (cls.prototype && cls.prototype instanceof HTMLElement) {
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
return false;
|
|
70
|
-
}
|
|
71
|
-
catch {
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
87
|
}
|
|
76
88
|
/**
|
|
77
89
|
* Handler for defining custom elements in scoped registries.
|
|
@@ -1,6 +1,61 @@
|
|
|
1
1
|
import { EvtRt } from '../EvtRt.js';
|
|
2
2
|
import { MountConfig, MountContext } from '../types/mount-observer/types.js';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Find a suitable HTMLElement class from a module.
|
|
6
|
+
* Checks the default export first, then searches all exports.
|
|
7
|
+
* @param module - The imported module
|
|
8
|
+
* @returns The HTMLElement class constructor
|
|
9
|
+
* @throws Error if no suitable class is found or multiple classes are found
|
|
10
|
+
*/
|
|
11
|
+
function findSuitableClass(module: any): typeof HTMLElement {
|
|
12
|
+
// Check default export first
|
|
13
|
+
const defaultExport = module.default;
|
|
14
|
+
|
|
15
|
+
if (defaultExport && extendsHTMLElement(defaultExport)) {
|
|
16
|
+
return defaultExport;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Find all exports that extend HTMLElement
|
|
20
|
+
const htmlElementClasses = Object.values(module)
|
|
21
|
+
.filter(exp => typeof exp === 'function' && extendsHTMLElement(exp));
|
|
22
|
+
|
|
23
|
+
if (htmlElementClasses.length === 0) {
|
|
24
|
+
throw new Error('No suitable class found in module');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (htmlElementClasses.length > 1) {
|
|
28
|
+
throw new Error('More than one class found in module');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return htmlElementClasses[0] as typeof HTMLElement;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if a class extends HTMLElement.
|
|
36
|
+
* @param cls - The class to check
|
|
37
|
+
* @returns true if the class extends HTMLElement
|
|
38
|
+
*/
|
|
39
|
+
function extendsHTMLElement(cls: any): boolean {
|
|
40
|
+
try {
|
|
41
|
+
// Must be a function
|
|
42
|
+
if (typeof cls !== 'function') {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
// Handle direct HTMLElement export
|
|
46
|
+
if (cls === HTMLElement) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
// Check if it has a prototype and extends HTMLElement
|
|
50
|
+
if (cls.prototype && cls.prototype instanceof HTMLElement) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
4
59
|
export class DefineCustomElementHandler extends EvtRt {
|
|
5
60
|
mount(mountedElement: Element, MountConfig: MountConfig, context: MountContext): void {
|
|
6
61
|
this.abort();
|
|
@@ -17,8 +72,8 @@ export class DefineCustomElementHandler extends EvtRt {
|
|
|
17
72
|
return;
|
|
18
73
|
}
|
|
19
74
|
|
|
20
|
-
// Find suitable class
|
|
21
|
-
const ElementClass =
|
|
75
|
+
// Find suitable class using shared utility
|
|
76
|
+
const ElementClass = findSuitableClass(module);
|
|
22
77
|
|
|
23
78
|
// Validate that ElementClass is a constructor
|
|
24
79
|
if (typeof ElementClass !== 'function') {
|
|
@@ -43,49 +98,6 @@ export class DefineCustomElementHandler extends EvtRt {
|
|
|
43
98
|
protected define(tagName: string, ElementClass: CustomElementConstructor, mountedElement: Element): void {
|
|
44
99
|
customElements.define(tagName, ElementClass);
|
|
45
100
|
}
|
|
46
|
-
|
|
47
|
-
private findSuitableClass(module: any): typeof HTMLElement {
|
|
48
|
-
// Check default export first
|
|
49
|
-
const defaultExport = module.default;
|
|
50
|
-
|
|
51
|
-
if (defaultExport && this.extendsHTMLElement(defaultExport)) {
|
|
52
|
-
return defaultExport;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Find all exports that extend HTMLElement
|
|
56
|
-
const htmlElementClasses = Object.values(module)
|
|
57
|
-
.filter(exp => typeof exp === 'function' && this.extendsHTMLElement(exp));
|
|
58
|
-
|
|
59
|
-
if (htmlElementClasses.length === 0) {
|
|
60
|
-
throw new Error('No suitable class found in module');
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (htmlElementClasses.length > 1) {
|
|
64
|
-
throw new Error('More than one class found in module');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return htmlElementClasses[0] as typeof HTMLElement;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
private extendsHTMLElement(cls: any): boolean {
|
|
71
|
-
try {
|
|
72
|
-
// Must be a function
|
|
73
|
-
if (typeof cls !== 'function') {
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
// Handle direct HTMLElement export
|
|
77
|
-
if (cls === HTMLElement) {
|
|
78
|
-
return true;
|
|
79
|
-
}
|
|
80
|
-
// Check if it has a prototype and extends HTMLElement
|
|
81
|
-
if (cls.prototype && cls.prototype instanceof HTMLElement) {
|
|
82
|
-
return true;
|
|
83
|
-
}
|
|
84
|
-
return false;
|
|
85
|
-
} catch {
|
|
86
|
-
return false;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
101
|
}
|
|
90
102
|
|
|
91
103
|
/**
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { EvtRt } from '../EvtRt.js';
|
|
2
|
+
import { MountObserver } from '../MountObserver.js';
|
|
3
|
+
import '../ElementMountExtension.js';
|
|
4
|
+
import 'assign-gingerly/object-extension.js';
|
|
5
|
+
/**
|
|
6
|
+
* Handler for EMC (Element Mount Configuration) Script Elements.
|
|
7
|
+
* Processes script[type="emc"] elements to declaratively configure element enhancements.
|
|
8
|
+
*
|
|
9
|
+
* Supports two modes:
|
|
10
|
+
* 1. External JSON: <script type="emc" src="./config.json"></script>
|
|
11
|
+
* 2. Inline JSON: <script type="emc">{ "matching": "div", "enhConfig": {...} }</script>
|
|
12
|
+
*
|
|
13
|
+
* Unlike MountObserverScript, EMC scripts only support single config objects (not arrays).
|
|
14
|
+
*/
|
|
15
|
+
export class EMCScriptHandler extends EvtRt {
|
|
16
|
+
// Static properties define default MountConfig constraints
|
|
17
|
+
static matching = 'script[type="emc"]';
|
|
18
|
+
static whereInstanceOf = HTMLScriptElement;
|
|
19
|
+
async mount(mountedElement, MountConfig, context) {
|
|
20
|
+
this.abort(); // Clean up event listeners (one-time operation)
|
|
21
|
+
const scriptElement = mountedElement;
|
|
22
|
+
let emcConfig = scriptElement.export;
|
|
23
|
+
if (!emcConfig) {
|
|
24
|
+
// Check if script has src attribute
|
|
25
|
+
const srcAttr = scriptElement.getAttribute('src');
|
|
26
|
+
if (srcAttr) {
|
|
27
|
+
// External JSON mode: import from src
|
|
28
|
+
try {
|
|
29
|
+
const module = await import(srcAttr, { with: { type: 'json' } });
|
|
30
|
+
emcConfig = module.default;
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
throw new Error(`Failed to import JSON from '${srcAttr}': ${error instanceof Error ? error.message : String(error)}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
// Inline JSON mode: parse textContent
|
|
38
|
+
const jsonText = scriptElement.textContent?.trim();
|
|
39
|
+
if (!jsonText) {
|
|
40
|
+
throw new Error('Script element must have either src attribute or JSON content');
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
emcConfig = JSON.parse(jsonText);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
throw new Error(`Failed to parse JSON content: ${error instanceof Error ? error.message : String(error)}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Validate that config is an object (not array)
|
|
50
|
+
if (typeof emcConfig !== 'object' || emcConfig === null || Array.isArray(emcConfig)) {
|
|
51
|
+
throw new Error('EMC config must be an object (not an array)');
|
|
52
|
+
}
|
|
53
|
+
// Store the parsed config on the script element's export property
|
|
54
|
+
scriptElement.export = emcConfig;
|
|
55
|
+
// Dispatch resolved event
|
|
56
|
+
const { ResolvedEvent } = await import('../Events.js');
|
|
57
|
+
scriptElement.dispatchEvent(new ResolvedEvent(emcConfig));
|
|
58
|
+
}
|
|
59
|
+
// Validate EMC config has required properties
|
|
60
|
+
if (!emcConfig.enhConfig) {
|
|
61
|
+
throw new Error('EMC config must have enhConfig property');
|
|
62
|
+
}
|
|
63
|
+
const enhKey = emcConfig.enhConfig.enhKey;
|
|
64
|
+
if (!enhKey) {
|
|
65
|
+
throw new Error('EMC config enhConfig must have enhKey property');
|
|
66
|
+
}
|
|
67
|
+
// Set ID if not specified
|
|
68
|
+
if (!scriptElement.id && scriptElement.parentElement) {
|
|
69
|
+
scriptElement.id = `${scriptElement.parentElement.localName}.${enhKey}`;
|
|
70
|
+
}
|
|
71
|
+
// Construct MountConfig from EMC config
|
|
72
|
+
const mountConfig = await this.buildMountConfig(emcConfig);
|
|
73
|
+
// Create a MountObserver to watch for elements matching the config
|
|
74
|
+
const observer = new MountObserver(mountConfig);
|
|
75
|
+
// Store observer reference for cleanup
|
|
76
|
+
scriptElement.emcObserver = observer;
|
|
77
|
+
// Observe from the script element's parent or root node
|
|
78
|
+
const observeTarget = scriptElement.parentElement || scriptElement.getRootNode();
|
|
79
|
+
await observer.observe(observeTarget);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Build a MountConfig from an EMC config.
|
|
83
|
+
* Combines the matching selector with withAttrs if present.
|
|
84
|
+
*/
|
|
85
|
+
async buildMountConfig(emcConfig) {
|
|
86
|
+
const { enhConfig, ...mountConfigBase } = emcConfig;
|
|
87
|
+
let matching = mountConfigBase.matching || '';
|
|
88
|
+
// If withAttrs is defined, use buildCSSQuery to combine with matching
|
|
89
|
+
if (enhConfig.withAttrs) {
|
|
90
|
+
const { buildCSSQuery } = await import('assign-gingerly/buildCSSQuery.js');
|
|
91
|
+
// Cast to any to avoid type mismatch with spawn property
|
|
92
|
+
const attrQuery = buildCSSQuery(enhConfig);
|
|
93
|
+
// Combine matching with attribute query
|
|
94
|
+
if (matching) {
|
|
95
|
+
matching = `${matching}${attrQuery}`;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
matching = attrQuery;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Create the mount config with a custom handler
|
|
102
|
+
const mountConfig = {
|
|
103
|
+
...mountConfigBase,
|
|
104
|
+
matching,
|
|
105
|
+
do: (mountedElement) => {
|
|
106
|
+
return this.handleMount(mountedElement, emcConfig);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
return mountConfig;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Handle when an element mounts that matches the EMC config.
|
|
113
|
+
*/
|
|
114
|
+
async handleMount(mountedElement, emcConfig) {
|
|
115
|
+
const enhKey = emcConfig.enhConfig.enhKey;
|
|
116
|
+
// Step 1: Check if element already has this enhancement
|
|
117
|
+
const enh = mountedElement.enh;
|
|
118
|
+
if (enh && enh[enhKey]) {
|
|
119
|
+
// Already enhanced, do nothing
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// Step 2: Get enhancement registry from the element's custom element registry
|
|
123
|
+
const customElementRegistry = mountedElement.customElementRegistry || customElements;
|
|
124
|
+
const enhancementRegistry = customElementRegistry.enhancementRegistry;
|
|
125
|
+
if (!enhancementRegistry) {
|
|
126
|
+
throw new Error('Enhancement registry not found on custom element registry');
|
|
127
|
+
}
|
|
128
|
+
// Check if enhancement is already registered using findByEnhKey method
|
|
129
|
+
let enhancementConfig = enhancementRegistry.findByEnhKey(enhKey);
|
|
130
|
+
// Step 3: If not registered, register it
|
|
131
|
+
if (!enhancementConfig) {
|
|
132
|
+
enhancementConfig = await this.registerEnhancement(emcConfig, enhancementRegistry);
|
|
133
|
+
}
|
|
134
|
+
// Step 4: Spawn enhancement instance
|
|
135
|
+
if (!enh) {
|
|
136
|
+
throw new Error('Element does not have enh property. Make sure ElementMountExtension is loaded.');
|
|
137
|
+
}
|
|
138
|
+
await enh.get(enhancementConfig);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Register an enhancement in the enhancement registry.
|
|
142
|
+
*/
|
|
143
|
+
async registerEnhancement(emcConfig, enhancementRegistry) {
|
|
144
|
+
const { enhConfig } = emcConfig;
|
|
145
|
+
const { spawn } = enhConfig;
|
|
146
|
+
if (!spawn) {
|
|
147
|
+
throw new Error('EMC enhConfig must have spawn property');
|
|
148
|
+
}
|
|
149
|
+
// Step 3.1: Import the module
|
|
150
|
+
const module = await import(spawn);
|
|
151
|
+
// Get the enhancement class - it should be the default export or any exported class
|
|
152
|
+
let ElementClass = module.default;
|
|
153
|
+
// If no default export, try to find a suitable class
|
|
154
|
+
if (!ElementClass) {
|
|
155
|
+
// Look for any exported constructor function
|
|
156
|
+
for (const key of Object.keys(module)) {
|
|
157
|
+
if (typeof module[key] === 'function') {
|
|
158
|
+
ElementClass = module[key];
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (!ElementClass) {
|
|
164
|
+
throw new Error(`No suitable class found in module ${spawn}`);
|
|
165
|
+
}
|
|
166
|
+
// Step 3.2: Construct enhancement config
|
|
167
|
+
const enhancementConfig = {
|
|
168
|
+
...enhConfig,
|
|
169
|
+
spawn: ElementClass
|
|
170
|
+
};
|
|
171
|
+
// Step 3.3: Register in enhancement registry
|
|
172
|
+
enhancementRegistry.push(enhancementConfig);
|
|
173
|
+
return enhancementConfig;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Register built-in handler
|
|
177
|
+
export const emc = 'builtIns.emcScript';
|
|
178
|
+
MountObserver.define(emc, EMCScriptHandler);
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { EvtRt } from '../EvtRt.js';
|
|
2
|
+
import { EMC, MountConfig, MountContext } from '../types/mount-observer/types.js';
|
|
3
|
+
import { MountObserver } from '../MountObserver.js';
|
|
4
|
+
import '../ElementMountExtension.js';
|
|
5
|
+
import 'assign-gingerly/object-extension.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Handler for EMC (Element Mount Configuration) Script Elements.
|
|
9
|
+
* Processes script[type="emc"] elements to declaratively configure element enhancements.
|
|
10
|
+
*
|
|
11
|
+
* Supports two modes:
|
|
12
|
+
* 1. External JSON: <script type="emc" src="./config.json"></script>
|
|
13
|
+
* 2. Inline JSON: <script type="emc">{ "matching": "div", "enhConfig": {...} }</script>
|
|
14
|
+
*
|
|
15
|
+
* Unlike MountObserverScript, EMC scripts only support single config objects (not arrays).
|
|
16
|
+
*/
|
|
17
|
+
export class EMCScriptHandler extends EvtRt {
|
|
18
|
+
// Static properties define default MountConfig constraints
|
|
19
|
+
static matching = 'script[type="emc"]';
|
|
20
|
+
static whereInstanceOf = HTMLScriptElement;
|
|
21
|
+
|
|
22
|
+
async mount(mountedElement: Element, MountConfig: MountConfig, context: MountContext): Promise<void> {
|
|
23
|
+
this.abort(); // Clean up event listeners (one-time operation)
|
|
24
|
+
|
|
25
|
+
const scriptElement = mountedElement as HTMLScriptElement;
|
|
26
|
+
|
|
27
|
+
let emcConfig = (scriptElement as any).export;
|
|
28
|
+
if (!emcConfig) {
|
|
29
|
+
// Check if script has src attribute
|
|
30
|
+
const srcAttr = scriptElement.getAttribute('src');
|
|
31
|
+
|
|
32
|
+
if (srcAttr) {
|
|
33
|
+
// External JSON mode: import from src
|
|
34
|
+
try {
|
|
35
|
+
const module = await import(srcAttr, { with: { type: 'json' } } as any);
|
|
36
|
+
emcConfig = module.default;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
throw new Error(`Failed to import JSON from '${srcAttr}': ${error instanceof Error ? error.message : String(error)}`);
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
// Inline JSON mode: parse textContent
|
|
42
|
+
const jsonText = scriptElement.textContent?.trim();
|
|
43
|
+
|
|
44
|
+
if (!jsonText) {
|
|
45
|
+
throw new Error('Script element must have either src attribute or JSON content');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
emcConfig = JSON.parse(jsonText);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
throw new Error(`Failed to parse JSON content: ${error instanceof Error ? error.message : String(error)}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Validate that config is an object (not array)
|
|
56
|
+
if (typeof emcConfig !== 'object' || emcConfig === null || Array.isArray(emcConfig)) {
|
|
57
|
+
throw new Error('EMC config must be an object (not an array)');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Store the parsed config on the script element's export property
|
|
61
|
+
(scriptElement as any).export = emcConfig;
|
|
62
|
+
|
|
63
|
+
// Dispatch resolved event
|
|
64
|
+
const { ResolvedEvent } = await import('../Events.js');
|
|
65
|
+
scriptElement.dispatchEvent(new ResolvedEvent(emcConfig));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Validate EMC config has required properties
|
|
69
|
+
if (!emcConfig.enhConfig) {
|
|
70
|
+
throw new Error('EMC config must have enhConfig property');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const enhKey = emcConfig.enhConfig.enhKey;
|
|
74
|
+
if (!enhKey) {
|
|
75
|
+
throw new Error('EMC config enhConfig must have enhKey property');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Set ID if not specified
|
|
79
|
+
if (!scriptElement.id && scriptElement.parentElement) {
|
|
80
|
+
scriptElement.id = `${scriptElement.parentElement.localName}.${enhKey}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Construct MountConfig from EMC config
|
|
84
|
+
const mountConfig = await this.buildMountConfig(emcConfig);
|
|
85
|
+
|
|
86
|
+
// Create a MountObserver to watch for elements matching the config
|
|
87
|
+
const observer = new MountObserver(mountConfig);
|
|
88
|
+
|
|
89
|
+
// Store observer reference for cleanup
|
|
90
|
+
(scriptElement as any).emcObserver = observer;
|
|
91
|
+
|
|
92
|
+
// Observe from the script element's parent or root node
|
|
93
|
+
const observeTarget = scriptElement.parentElement || scriptElement.getRootNode() as Node;
|
|
94
|
+
await observer.observe(observeTarget);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Build a MountConfig from an EMC config.
|
|
99
|
+
* Combines the matching selector with withAttrs if present.
|
|
100
|
+
*/
|
|
101
|
+
private async buildMountConfig(emcConfig: EMC): Promise<MountConfig> {
|
|
102
|
+
const { enhConfig, ...mountConfigBase } = emcConfig;
|
|
103
|
+
|
|
104
|
+
let matching = mountConfigBase.matching || '';
|
|
105
|
+
|
|
106
|
+
// If withAttrs is defined, use buildCSSQuery to combine with matching
|
|
107
|
+
if (enhConfig.withAttrs) {
|
|
108
|
+
const { buildCSSQuery } = await import('assign-gingerly/buildCSSQuery.js');
|
|
109
|
+
// Cast to any to avoid type mismatch with spawn property
|
|
110
|
+
const attrQuery = buildCSSQuery(enhConfig as any);
|
|
111
|
+
|
|
112
|
+
// Combine matching with attribute query
|
|
113
|
+
if (matching) {
|
|
114
|
+
matching = `${matching}${attrQuery}`;
|
|
115
|
+
} else {
|
|
116
|
+
matching = attrQuery;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Create the mount config with a custom handler
|
|
121
|
+
const mountConfig: MountConfig = {
|
|
122
|
+
...mountConfigBase,
|
|
123
|
+
matching,
|
|
124
|
+
do: (mountedElement: Element) => {
|
|
125
|
+
return this.handleMount(mountedElement, emcConfig);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
return mountConfig;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Handle when an element mounts that matches the EMC config.
|
|
134
|
+
*/
|
|
135
|
+
private async handleMount(mountedElement: Element, emcConfig: EMC): Promise<void> {
|
|
136
|
+
const enhKey = emcConfig.enhConfig.enhKey;
|
|
137
|
+
|
|
138
|
+
// Step 1: Check if element already has this enhancement
|
|
139
|
+
const enh = (mountedElement as any).enh;
|
|
140
|
+
if (enh && enh[enhKey]) {
|
|
141
|
+
// Already enhanced, do nothing
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Step 2: Get enhancement registry from the element's custom element registry
|
|
146
|
+
const customElementRegistry = (mountedElement as any).customElementRegistry || customElements;
|
|
147
|
+
const enhancementRegistry = (customElementRegistry as any).enhancementRegistry;
|
|
148
|
+
|
|
149
|
+
if (!enhancementRegistry) {
|
|
150
|
+
throw new Error('Enhancement registry not found on custom element registry');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Check if enhancement is already registered using findByEnhKey method
|
|
154
|
+
let enhancementConfig = enhancementRegistry.findByEnhKey(enhKey);
|
|
155
|
+
|
|
156
|
+
// Step 3: If not registered, register it
|
|
157
|
+
if (!enhancementConfig) {
|
|
158
|
+
enhancementConfig = await this.registerEnhancement(emcConfig, enhancementRegistry);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Step 4: Spawn enhancement instance
|
|
162
|
+
if (!enh) {
|
|
163
|
+
throw new Error('Element does not have enh property. Make sure ElementMountExtension is loaded.');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
await enh.get(enhancementConfig);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Register an enhancement in the enhancement registry.
|
|
171
|
+
*/
|
|
172
|
+
private async registerEnhancement(emcConfig: EMC, enhancementRegistry: any): Promise<any> {
|
|
173
|
+
const { enhConfig } = emcConfig;
|
|
174
|
+
const { spawn } = enhConfig;
|
|
175
|
+
|
|
176
|
+
if (!spawn) {
|
|
177
|
+
throw new Error('EMC enhConfig must have spawn property');
|
|
178
|
+
}
|
|
179
|
+
// Step 3.1: Import the module
|
|
180
|
+
const module = await import(spawn);
|
|
181
|
+
|
|
182
|
+
// Get the enhancement class - it should be the default export or any exported class
|
|
183
|
+
let ElementClass = module.default;
|
|
184
|
+
|
|
185
|
+
// If no default export, try to find a suitable class
|
|
186
|
+
if (!ElementClass) {
|
|
187
|
+
// Look for any exported constructor function
|
|
188
|
+
for (const key of Object.keys(module)) {
|
|
189
|
+
if (typeof module[key] === 'function') {
|
|
190
|
+
ElementClass = module[key];
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (!ElementClass) {
|
|
197
|
+
throw new Error(`No suitable class found in module ${spawn}`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Step 3.2: Construct enhancement config
|
|
201
|
+
const enhancementConfig = {
|
|
202
|
+
...enhConfig,
|
|
203
|
+
spawn: ElementClass
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// Step 3.3: Register in enhancement registry
|
|
207
|
+
enhancementRegistry.push(enhancementConfig);
|
|
208
|
+
|
|
209
|
+
return enhancementConfig;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Register built-in handler
|
|
214
|
+
export const emc = 'builtIns.emcScript';
|
|
215
|
+
|
|
216
|
+
MountObserver.define(emc, EMCScriptHandler);
|
|
@@ -23,15 +23,15 @@ export class EnhanceMountedElementHandler extends EvtRt {
|
|
|
23
23
|
if (typeof registryItem.spawn !== 'function') {
|
|
24
24
|
throw new Error('Registry item "spawn" property must be a constructor function');
|
|
25
25
|
}
|
|
26
|
-
const mose = context?.observer?.options?.mose;
|
|
27
|
-
if
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
26
|
+
// const mose = context?.observer?.options?.mose;
|
|
27
|
+
// if(mose){
|
|
28
|
+
// const se = mose.deref() as HTMLScriptElement;
|
|
29
|
+
// const {parentElement} = se;
|
|
30
|
+
// const {enhKey} = registryItem;
|
|
31
|
+
// if(!se.id && enhKey){
|
|
32
|
+
// se.id = `${parentElement?.localName}.${ enhKey}`;
|
|
33
|
+
// }
|
|
34
|
+
// }
|
|
35
35
|
// Spawn the enhancement
|
|
36
36
|
this.#spawnEnhancement(mountedElement, registryItem, context);
|
|
37
37
|
}
|
|
@@ -30,15 +30,15 @@ export class EnhanceMountedElementHandler extends EvtRt {
|
|
|
30
30
|
if (typeof registryItem.spawn !== 'function') {
|
|
31
31
|
throw new Error('Registry item "spawn" property must be a constructor function');
|
|
32
32
|
}
|
|
33
|
-
const mose = context?.observer?.options?.mose;
|
|
34
|
-
if(mose){
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
33
|
+
// const mose = context?.observer?.options?.mose;
|
|
34
|
+
// if(mose){
|
|
35
|
+
// const se = mose.deref() as HTMLScriptElement;
|
|
36
|
+
// const {parentElement} = se;
|
|
37
|
+
// const {enhKey} = registryItem;
|
|
38
|
+
// if(!se.id && enhKey){
|
|
39
|
+
// se.id = `${parentElement?.localName}.${ enhKey}`;
|
|
40
|
+
// }
|
|
41
|
+
// }
|
|
42
42
|
|
|
43
43
|
// Spawn the enhancement
|
|
44
44
|
this.#spawnEnhancement(mountedElement, registryItem, context);
|
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
|
@@ -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
|
-
|
|
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
|
|