mount-observer 0.1.14 → 0.1.16
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/ElementMountExtension.js +5 -2
- package/ElementMountExtension.ts +7 -2
- package/MountObserver.js +3 -0
- package/MountObserver.ts +3 -0
- package/RegistryMountCoordinator.js +5 -5
- package/RegistryMountCoordinator.ts +8 -6
- package/{DefineCustomElementHandler.js → handlers/DefineCustomElement.js} +103 -99
- package/handlers/DefineCustomElement.ts +123 -0
- package/{EnhanceMountedElementHandler.js → handlers/EnhanceMountedElement.js} +109 -96
- package/handlers/EnhanceMountedElement.ts +126 -0
- package/handlers/Events.js +110 -0
- package/handlers/EvtRt.js +59 -0
- package/handlers/GenIds.js +37 -0
- package/handlers/GenIds.ts +45 -0
- package/handlers/HTMLInclude.js +393 -0
- package/handlers/HTMLInclude.ts +453 -0
- package/handlers/HoistTemplate.js +77 -0
- package/handlers/HoistTemplate.ts +89 -0
- package/handlers/MountObserver.js +941 -0
- package/handlers/MountObserverScript.js +78 -0
- package/handlers/MountObserverScript.ts +89 -0
- package/handlers/ScriptExport.js +83 -0
- package/handlers/ScriptExport.ts +97 -0
- package/handlers/SharedMutationObserver.js +78 -0
- package/handlers/arr.js +16 -0
- package/handlers/connectionMonitor.js +122 -0
- package/handlers/elementIntersection.js +73 -0
- package/handlers/emitEvents.js +187 -0
- package/handlers/getRegistryRoot.js +52 -0
- package/handlers/loadImports.js +129 -0
- package/handlers/mediaQuery.js +90 -0
- package/handlers/rootSizeObserver.js +131 -0
- package/handlers/upShadowSearch.js +70 -0
- package/handlers/withScopePerimeter.js +22 -0
- package/package.json +12 -2
- package/types/assign-gingerly/types.d.ts +244 -0
- package/types/be-a-beacon/types.d.ts +3 -0
- package/types/global.d.ts +29 -0
- package/types/id-generation/types.d.ts +26 -0
- package/types/mount-observer/types.d.ts +332 -0
package/ElementMountExtension.js
CHANGED
|
@@ -71,6 +71,9 @@ Object.defineProperty(Node.prototype, 'mount', {
|
|
|
71
71
|
if (!(this instanceof Element)) {
|
|
72
72
|
throw new Error('mount() can only be called on Element, ShadowRoot, or Document');
|
|
73
73
|
}
|
|
74
|
+
if (this instanceof HTMLScriptElement) {
|
|
75
|
+
options.mose = new WeakRef(this);
|
|
76
|
+
}
|
|
74
77
|
const scope = options.scope ?? 'registry'; // NEW DEFAULT
|
|
75
78
|
let thingToObserve;
|
|
76
79
|
if (scope === 'registry') {
|
|
@@ -84,7 +87,7 @@ Object.defineProperty(Node.prototype, 'mount', {
|
|
|
84
87
|
const registry = this.customElementRegistry;
|
|
85
88
|
// Register with coordinator if registry exists
|
|
86
89
|
if (registry) {
|
|
87
|
-
await getOrInsertObserverEntry(registry, config, thingToObserve);
|
|
90
|
+
await getOrInsertObserverEntry(registry, config, thingToObserve, options);
|
|
88
91
|
}
|
|
89
92
|
else {
|
|
90
93
|
// No registry, just create a standalone observer
|
|
@@ -146,7 +149,7 @@ Object.defineProperty(Element.prototype, 'mountScope', {
|
|
|
146
149
|
const configs = registry.mountConfigRegistry.items;
|
|
147
150
|
// For each config, ensure an observer exists for this registry root
|
|
148
151
|
for (const config of configs) {
|
|
149
|
-
await getOrInsertObserverEntry(registry, config, registryRoot);
|
|
152
|
+
await getOrInsertObserverEntry(registry, config, registryRoot, {});
|
|
150
153
|
}
|
|
151
154
|
},
|
|
152
155
|
writable: true,
|
package/ElementMountExtension.ts
CHANGED
|
@@ -95,6 +95,7 @@ Object.defineProperty(Node.prototype, 'mount', {
|
|
|
95
95
|
config: MountConfig,
|
|
96
96
|
options: MountObserverOptions = {}
|
|
97
97
|
): Promise<T> {
|
|
98
|
+
|
|
98
99
|
// For ShadowRoot and Document, observe directly
|
|
99
100
|
if (this instanceof ShadowRoot || this instanceof Document) {
|
|
100
101
|
const mo = new MountObserver(config, options);
|
|
@@ -106,6 +107,10 @@ Object.defineProperty(Node.prototype, 'mount', {
|
|
|
106
107
|
if (!(this instanceof Element)) {
|
|
107
108
|
throw new Error('mount() can only be called on Element, ShadowRoot, or Document');
|
|
108
109
|
}
|
|
110
|
+
|
|
111
|
+
if(this instanceof HTMLScriptElement) {
|
|
112
|
+
options.mose = new WeakRef(this);
|
|
113
|
+
}
|
|
109
114
|
|
|
110
115
|
const scope = options.scope ?? 'registry'; // NEW DEFAULT
|
|
111
116
|
let thingToObserve: Node;
|
|
@@ -123,7 +128,7 @@ Object.defineProperty(Node.prototype, 'mount', {
|
|
|
123
128
|
|
|
124
129
|
// Register with coordinator if registry exists
|
|
125
130
|
if (registry) {
|
|
126
|
-
await getOrInsertObserverEntry(registry, config, thingToObserve);
|
|
131
|
+
await getOrInsertObserverEntry(registry, config, thingToObserve, options);
|
|
127
132
|
} else {
|
|
128
133
|
// No registry, just create a standalone observer
|
|
129
134
|
const mo = new MountObserver(config, options);
|
|
@@ -185,7 +190,7 @@ Object.defineProperty(Element.prototype, 'mountScope', {
|
|
|
185
190
|
|
|
186
191
|
// For each config, ensure an observer exists for this registry root
|
|
187
192
|
for (const config of configs) {
|
|
188
|
-
await getOrInsertObserverEntry(registry, config, registryRoot);
|
|
193
|
+
await getOrInsertObserverEntry(registry, config, registryRoot, {});
|
|
189
194
|
}
|
|
190
195
|
},
|
|
191
196
|
writable: true,
|
package/MountObserver.js
CHANGED
package/MountObserver.ts
CHANGED
|
@@ -37,6 +37,9 @@ export class MountObserver<TKeys extends string = string> extends EventTarget im
|
|
|
37
37
|
|
|
38
38
|
#init: MountConfig;
|
|
39
39
|
#options: MountObserverOptions;
|
|
40
|
+
get options(): MountObserverOptions {
|
|
41
|
+
return { ...this.#options };
|
|
42
|
+
}
|
|
40
43
|
#abortController: AbortController;
|
|
41
44
|
#modules: any[] = [];
|
|
42
45
|
#configFromPromise: Promise<void> | undefined;
|
|
@@ -55,10 +55,10 @@ if (typeof WeakMap.prototype.getOrInsertComputed !== 'function') {
|
|
|
55
55
|
* Helper to create an observer entry asynchronously.
|
|
56
56
|
* Separated to handle async operations cleanly.
|
|
57
57
|
*/
|
|
58
|
-
async function createObserverEntry(config, registryRoot) {
|
|
58
|
+
async function createObserverEntry(config, registryRoot, options) {
|
|
59
59
|
// Dynamically import to avoid circular dependency
|
|
60
60
|
const { MountObserver: MountObserverClass } = await import('./MountObserver.js');
|
|
61
|
-
const observer = new MountObserverClass(config);
|
|
61
|
+
const observer = new MountObserverClass(config, options);
|
|
62
62
|
await observer.observe(registryRoot);
|
|
63
63
|
return {
|
|
64
64
|
config,
|
|
@@ -76,7 +76,7 @@ async function createObserverEntry(config, registryRoot) {
|
|
|
76
76
|
*
|
|
77
77
|
* @returns The ObserverEntry for the requested combination
|
|
78
78
|
*/
|
|
79
|
-
export async function getOrInsertObserverEntry(registry, config, registryRoot) {
|
|
79
|
+
export async function getOrInsertObserverEntry(registry, config, registryRoot, options) {
|
|
80
80
|
// Add config to the registry's config list (if not already there)
|
|
81
81
|
registry.mountConfigRegistry.push(config);
|
|
82
82
|
// Get or create the nested map structure
|
|
@@ -85,7 +85,7 @@ export async function getOrInsertObserverEntry(registry, config, registryRoot) {
|
|
|
85
85
|
// Get or create the observer for this specific registry root
|
|
86
86
|
let observerEntry = nodeToObserverMap.get(registryRoot);
|
|
87
87
|
if (!observerEntry) {
|
|
88
|
-
observerEntry = await createObserverEntry(config, registryRoot);
|
|
88
|
+
observerEntry = await createObserverEntry(config, registryRoot, options);
|
|
89
89
|
nodeToObserverMap.set(registryRoot, observerEntry);
|
|
90
90
|
}
|
|
91
91
|
// Track this registry root in the scopes set
|
|
@@ -116,7 +116,7 @@ export async function getOrInsertObserverEntry(registry, config, registryRoot) {
|
|
|
116
116
|
const confObserverMap = mountConfigMap.getOrInsertComputed(conf, () => new WeakMap());
|
|
117
117
|
let existingEntry = confObserverMap.get(regRoot);
|
|
118
118
|
if (!existingEntry) {
|
|
119
|
-
existingEntry = await createObserverEntry(conf, regRoot);
|
|
119
|
+
existingEntry = await createObserverEntry(conf, regRoot, options);
|
|
120
120
|
confObserverMap.set(regRoot, existingEntry);
|
|
121
121
|
}
|
|
122
122
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* where all scopes with the same registry share mount observers.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { MountConfig, WeakDual } from './types/mount-observer/types.js';
|
|
7
|
+
import type { MountConfig, MountObserverOptions, WeakDual } from './types/mount-observer/types.js';
|
|
8
8
|
import type { MountObserver } from './MountObserver.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -98,11 +98,12 @@ if (typeof WeakMap.prototype.getOrInsertComputed !== 'function') {
|
|
|
98
98
|
*/
|
|
99
99
|
async function createObserverEntry(
|
|
100
100
|
config: MountConfig,
|
|
101
|
-
registryRoot: Node
|
|
101
|
+
registryRoot: Node,
|
|
102
|
+
options: MountObserverOptions
|
|
102
103
|
): Promise<ObserverEntry> {
|
|
103
104
|
// Dynamically import to avoid circular dependency
|
|
104
105
|
const { MountObserver: MountObserverClass } = await import('./MountObserver.js');
|
|
105
|
-
const observer = new MountObserverClass(config);
|
|
106
|
+
const observer = new MountObserverClass(config, options);
|
|
106
107
|
await observer.observe(registryRoot);
|
|
107
108
|
return {
|
|
108
109
|
config,
|
|
@@ -124,7 +125,8 @@ async function createObserverEntry(
|
|
|
124
125
|
export async function getOrInsertObserverEntry(
|
|
125
126
|
registry: CustomElementRegistry,
|
|
126
127
|
config: MountConfig,
|
|
127
|
-
registryRoot: Node
|
|
128
|
+
registryRoot: Node,
|
|
129
|
+
options: MountObserverOptions
|
|
128
130
|
): Promise<ObserverEntry> {
|
|
129
131
|
// Add config to the registry's config list (if not already there)
|
|
130
132
|
(registry as any).mountConfigRegistry.push(config);
|
|
@@ -136,7 +138,7 @@ export async function getOrInsertObserverEntry(
|
|
|
136
138
|
// Get or create the observer for this specific registry root
|
|
137
139
|
let observerEntry = nodeToObserverMap.get(registryRoot);
|
|
138
140
|
if (!observerEntry) {
|
|
139
|
-
observerEntry = await createObserverEntry(config, registryRoot);
|
|
141
|
+
observerEntry = await createObserverEntry(config, registryRoot, options);
|
|
140
142
|
nodeToObserverMap.set(registryRoot, observerEntry);
|
|
141
143
|
}
|
|
142
144
|
|
|
@@ -171,7 +173,7 @@ export async function getOrInsertObserverEntry(
|
|
|
171
173
|
const confObserverMap = mountConfigMap.getOrInsertComputed(conf, () => new WeakMap());
|
|
172
174
|
let existingEntry = confObserverMap.get(regRoot);
|
|
173
175
|
if (!existingEntry) {
|
|
174
|
-
existingEntry = await createObserverEntry(conf, regRoot);
|
|
176
|
+
existingEntry = await createObserverEntry(conf, regRoot, options);
|
|
175
177
|
confObserverMap.set(regRoot, existingEntry);
|
|
176
178
|
}
|
|
177
179
|
}
|
|
@@ -1,99 +1,103 @@
|
|
|
1
|
-
import { EvtRt } from '
|
|
2
|
-
export class DefineCustomElementHandler extends EvtRt {
|
|
3
|
-
mount(mountedElement, MountConfig, context) {
|
|
4
|
-
this.abort();
|
|
5
|
-
// Check if modules are specified
|
|
6
|
-
if (!context.modules || context.modules.length === 0) {
|
|
7
|
-
throw new Error('Must specify an ES Module');
|
|
8
|
-
}
|
|
9
|
-
const module = context.modules[0];
|
|
10
|
-
const tagName = mountedElement.localName;
|
|
11
|
-
// Check if already defined
|
|
12
|
-
if (customElements.get(tagName)) {
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
// Find suitable class
|
|
16
|
-
const ElementClass = this.findSuitableClass(module);
|
|
17
|
-
// Validate that ElementClass is a constructor
|
|
18
|
-
if (typeof ElementClass !== 'function') {
|
|
19
|
-
throw new Error(`Found class is not a constructor: ${typeof ElementClass}`);
|
|
20
|
-
}
|
|
21
|
-
// Create wrapper class to allow reuse
|
|
22
|
-
// Use anonymous class expression which works across all browsers
|
|
23
|
-
const WrapperClass = class extends ElementClass {
|
|
24
|
-
};
|
|
25
|
-
// Define the custom element using the define method
|
|
26
|
-
this.define(tagName, WrapperClass, mountedElement);
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Define the custom element in the appropriate registry.
|
|
30
|
-
* Override this method in subclasses to use scoped registries.
|
|
31
|
-
* @param tagName - The custom element tag name
|
|
32
|
-
* @param ElementClass - The element class constructor
|
|
33
|
-
* @param mountedElement - The mounted element (used for scoped registry access)
|
|
34
|
-
*/
|
|
35
|
-
define(tagName, ElementClass, mountedElement) {
|
|
36
|
-
customElements.define(tagName, ElementClass);
|
|
37
|
-
}
|
|
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
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Handler for defining custom elements in scoped registries.
|
|
78
|
-
* Uses the element's customElementRegistry property to define elements
|
|
79
|
-
* in the appropriate scoped registry instead of the global registry.
|
|
80
|
-
*/
|
|
81
|
-
export class DefineScopedCustomElementHandler extends DefineCustomElementHandler {
|
|
82
|
-
/**
|
|
83
|
-
* Define the custom element in the element's scoped registry.
|
|
84
|
-
* @param tagName - The custom element tag name
|
|
85
|
-
* @param ElementClass - The element class constructor
|
|
86
|
-
* @param mountedElement - The mounted element with customElementRegistry
|
|
87
|
-
*/
|
|
88
|
-
define(tagName, ElementClass, mountedElement) {
|
|
89
|
-
const registry = mountedElement.customElementRegistry;
|
|
90
|
-
if (!registry) {
|
|
91
|
-
throw new Error('Element does not have a customElementRegistry. Scoped registries require Chrome 146+ or latest WebKit/Safari.');
|
|
92
|
-
}
|
|
93
|
-
// Check if already defined in this scoped registry
|
|
94
|
-
if (registry.get(tagName)) {
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
registry.define(tagName, ElementClass);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
1
|
+
import { EvtRt } from '../EvtRt.js';
|
|
2
|
+
export class DefineCustomElementHandler extends EvtRt {
|
|
3
|
+
mount(mountedElement, MountConfig, context) {
|
|
4
|
+
this.abort();
|
|
5
|
+
// Check if modules are specified
|
|
6
|
+
if (!context.modules || context.modules.length === 0) {
|
|
7
|
+
throw new Error('Must specify an ES Module');
|
|
8
|
+
}
|
|
9
|
+
const module = context.modules[0];
|
|
10
|
+
const tagName = mountedElement.localName;
|
|
11
|
+
// Check if already defined
|
|
12
|
+
if (customElements.get(tagName)) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
// Find suitable class
|
|
16
|
+
const ElementClass = this.findSuitableClass(module);
|
|
17
|
+
// Validate that ElementClass is a constructor
|
|
18
|
+
if (typeof ElementClass !== 'function') {
|
|
19
|
+
throw new Error(`Found class is not a constructor: ${typeof ElementClass}`);
|
|
20
|
+
}
|
|
21
|
+
// Create wrapper class to allow reuse
|
|
22
|
+
// Use anonymous class expression which works across all browsers
|
|
23
|
+
const WrapperClass = class extends ElementClass {
|
|
24
|
+
};
|
|
25
|
+
// Define the custom element using the define method
|
|
26
|
+
this.define(tagName, WrapperClass, mountedElement);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Define the custom element in the appropriate registry.
|
|
30
|
+
* Override this method in subclasses to use scoped registries.
|
|
31
|
+
* @param tagName - The custom element tag name
|
|
32
|
+
* @param ElementClass - The element class constructor
|
|
33
|
+
* @param mountedElement - The mounted element (used for scoped registry access)
|
|
34
|
+
*/
|
|
35
|
+
define(tagName, ElementClass, mountedElement) {
|
|
36
|
+
customElements.define(tagName, ElementClass);
|
|
37
|
+
}
|
|
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
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Handler for defining custom elements in scoped registries.
|
|
78
|
+
* Uses the element's customElementRegistry property to define elements
|
|
79
|
+
* in the appropriate scoped registry instead of the global registry.
|
|
80
|
+
*/
|
|
81
|
+
export class DefineScopedCustomElementHandler extends DefineCustomElementHandler {
|
|
82
|
+
/**
|
|
83
|
+
* Define the custom element in the element's scoped registry.
|
|
84
|
+
* @param tagName - The custom element tag name
|
|
85
|
+
* @param ElementClass - The element class constructor
|
|
86
|
+
* @param mountedElement - The mounted element with customElementRegistry
|
|
87
|
+
*/
|
|
88
|
+
define(tagName, ElementClass, mountedElement) {
|
|
89
|
+
const registry = mountedElement.customElementRegistry;
|
|
90
|
+
if (!registry) {
|
|
91
|
+
throw new Error('Element does not have a customElementRegistry. Scoped registries require Chrome 146+ or latest WebKit/Safari.');
|
|
92
|
+
}
|
|
93
|
+
// Check if already defined in this scoped registry
|
|
94
|
+
if (registry.get(tagName)) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
registry.define(tagName, ElementClass);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Register built-in handlers
|
|
101
|
+
import { MountObserver } from '../MountObserver.js';
|
|
102
|
+
MountObserver.define('builtIns.defineCustomElement', DefineCustomElementHandler);
|
|
103
|
+
MountObserver.define('buildIns.defineScopedCustomElement', DefineScopedCustomElementHandler);
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { EvtRt } from '../EvtRt.js';
|
|
2
|
+
import { MountConfig, MountContext } from '../types/mount-observer/types.js';
|
|
3
|
+
|
|
4
|
+
export class DefineCustomElementHandler extends EvtRt {
|
|
5
|
+
mount(mountedElement: Element, MountConfig: MountConfig, context: MountContext): void {
|
|
6
|
+
this.abort();
|
|
7
|
+
// Check if modules are specified
|
|
8
|
+
if (!context.modules || context.modules.length === 0) {
|
|
9
|
+
throw new Error('Must specify an ES Module');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const module = context.modules[0];
|
|
13
|
+
const tagName = mountedElement.localName;
|
|
14
|
+
|
|
15
|
+
// Check if already defined
|
|
16
|
+
if (customElements.get(tagName)) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Find suitable class
|
|
21
|
+
const ElementClass = this.findSuitableClass(module);
|
|
22
|
+
|
|
23
|
+
// Validate that ElementClass is a constructor
|
|
24
|
+
if (typeof ElementClass !== 'function') {
|
|
25
|
+
throw new Error(`Found class is not a constructor: ${typeof ElementClass}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Create wrapper class to allow reuse
|
|
29
|
+
// Use anonymous class expression which works across all browsers
|
|
30
|
+
const WrapperClass = class extends ElementClass {};
|
|
31
|
+
|
|
32
|
+
// Define the custom element using the define method
|
|
33
|
+
this.define(tagName, WrapperClass, mountedElement);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Define the custom element in the appropriate registry.
|
|
38
|
+
* Override this method in subclasses to use scoped registries.
|
|
39
|
+
* @param tagName - The custom element tag name
|
|
40
|
+
* @param ElementClass - The element class constructor
|
|
41
|
+
* @param mountedElement - The mounted element (used for scoped registry access)
|
|
42
|
+
*/
|
|
43
|
+
protected define(tagName: string, ElementClass: CustomElementConstructor, mountedElement: Element): void {
|
|
44
|
+
customElements.define(tagName, ElementClass);
|
|
45
|
+
}
|
|
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
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Handler for defining custom elements in scoped registries.
|
|
93
|
+
* Uses the element's customElementRegistry property to define elements
|
|
94
|
+
* in the appropriate scoped registry instead of the global registry.
|
|
95
|
+
*/
|
|
96
|
+
export class DefineScopedCustomElementHandler extends DefineCustomElementHandler {
|
|
97
|
+
/**
|
|
98
|
+
* Define the custom element in the element's scoped registry.
|
|
99
|
+
* @param tagName - The custom element tag name
|
|
100
|
+
* @param ElementClass - The element class constructor
|
|
101
|
+
* @param mountedElement - The mounted element with customElementRegistry
|
|
102
|
+
*/
|
|
103
|
+
protected define(tagName: string, ElementClass: CustomElementConstructor, mountedElement: Element): void {
|
|
104
|
+
const registry = (mountedElement as any).customElementRegistry;
|
|
105
|
+
|
|
106
|
+
if (!registry) {
|
|
107
|
+
throw new Error('Element does not have a customElementRegistry. Scoped registries require Chrome 146+ or latest WebKit/Safari.');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Check if already defined in this scoped registry
|
|
111
|
+
if (registry.get(tagName)) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
registry.define(tagName, ElementClass);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Register built-in handlers
|
|
120
|
+
import { MountObserver } from '../MountObserver.js';
|
|
121
|
+
|
|
122
|
+
MountObserver.define('builtIns.defineCustomElement', DefineCustomElementHandler);
|
|
123
|
+
MountObserver.define('buildIns.defineScopedCustomElement', DefineScopedCustomElementHandler);
|