mount-observer 0.1.11 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DefineCustomElementHandler.js +99 -98
- package/ElementMountExtension.js +183 -8
- package/ElementMountExtension.ts +218 -11
- package/EnhanceMountedElementHandler.js +96 -95
- package/Events.js +18 -18
- package/Events.ts +6 -6
- package/EvtRt.js +24 -17
- package/EvtRt.ts +30 -18
- package/MountObserver.js +296 -81
- package/MountObserver.ts +387 -121
- package/README.md +1508 -235
- package/RegistryMountCoordinator.js +125 -0
- package/RegistryMountCoordinator.ts +181 -0
- package/connectionMonitor.js +116 -0
- package/connectionMonitor.ts +164 -0
- package/elementIntersection.js +67 -0
- package/elementIntersection.ts +96 -0
- package/{getRootRegistryContainer.js → getRegistryRoot.js} +1 -1
- package/{getRootRegistryContainer.ts → getRegistryRoot.ts} +1 -1
- package/index.js +15 -10
- package/index.ts +15 -10
- package/mediaQuery.js +1 -1
- package/mediaQuery.ts +1 -1
- package/observedRootHas.js +87 -0
- package/package.json +67 -61
- package/playwright.config.ts +1 -0
- package/rootSizeObserver.js +124 -0
- package/rootSizeObserver.ts +157 -0
- package/upShadowSearch.js +64 -0
- package/upShadowSearch.ts +62 -0
- package/DefineCustomElementHandler.ts +0 -116
- package/EnhanceMountedElementHandler.ts +0 -110
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// Root size observation with container query matching for MountObserver
|
|
2
|
+
import type { MountConfig, MountContext, WeakDual } from './types/mount-observer/types.js';
|
|
3
|
+
import { DismountEvent } from './Events.js';
|
|
4
|
+
|
|
5
|
+
export function setupRootSizeObserver(
|
|
6
|
+
init: MountConfig,
|
|
7
|
+
rootNodeRef: WeakRef<Node>,
|
|
8
|
+
mountedElements: WeakDual<Element>,
|
|
9
|
+
modules: any[],
|
|
10
|
+
observer: EventTarget,
|
|
11
|
+
processNode: (node: Node) => void
|
|
12
|
+
): {
|
|
13
|
+
conditionMatches: boolean;
|
|
14
|
+
cleanup: () => void;
|
|
15
|
+
} {
|
|
16
|
+
const { whereObservedRootSizeMatches } = init;
|
|
17
|
+
|
|
18
|
+
if (!whereObservedRootSizeMatches) {
|
|
19
|
+
throw new Error('whereObservedRootSizeMatches is required');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const rootNode = rootNodeRef.deref();
|
|
23
|
+
if (!rootNode) {
|
|
24
|
+
throw new Error('Root node has been garbage collected');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Get the element to observe
|
|
28
|
+
const rootElement = rootNode instanceof Element
|
|
29
|
+
? rootNode
|
|
30
|
+
: (rootNode as Document).documentElement;
|
|
31
|
+
|
|
32
|
+
if (!rootElement) {
|
|
33
|
+
throw new Error('Could not determine root element for whereObservedRootSizeMatches');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Parse the container query condition
|
|
37
|
+
// Container queries use the same syntax as media queries: (min-width: 700px)
|
|
38
|
+
const containerQuery = whereObservedRootSizeMatches;
|
|
39
|
+
|
|
40
|
+
// Check if condition currently matches
|
|
41
|
+
let conditionMatches = evaluateContainerQuery(rootElement, containerQuery);
|
|
42
|
+
|
|
43
|
+
// Set up ResizeObserver to watch for size changes
|
|
44
|
+
const resizeObserver = new ResizeObserver((entries) => {
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
const previousMatches = conditionMatches;
|
|
47
|
+
conditionMatches = evaluateContainerQuery(entry.target as Element, containerQuery);
|
|
48
|
+
|
|
49
|
+
if (conditionMatches && !previousMatches) {
|
|
50
|
+
// Condition now matches - process elements
|
|
51
|
+
handleConditionMatch();
|
|
52
|
+
} else if (!conditionMatches && previousMatches) {
|
|
53
|
+
// Condition no longer matches - dismount all elements
|
|
54
|
+
handleConditionUnmatch();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
function handleConditionMatch(): void {
|
|
60
|
+
// Process all elements in the observed node
|
|
61
|
+
const rootNode = rootNodeRef.deref();
|
|
62
|
+
if (rootNode) {
|
|
63
|
+
processNode(rootNode);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function handleConditionUnmatch(): void {
|
|
68
|
+
// Dismount all currently mounted elements
|
|
69
|
+
const rootNode = rootNodeRef.deref();
|
|
70
|
+
if (!rootNode) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const context: MountContext = {
|
|
75
|
+
modules,
|
|
76
|
+
observer: observer as any,
|
|
77
|
+
rootNode,
|
|
78
|
+
mountConfig: init
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// Get all mounted elements from the WeakDual setWeak
|
|
82
|
+
const mountedElementsList: Element[] = [];
|
|
83
|
+
for (const ref of mountedElements.setWeak) {
|
|
84
|
+
const element = ref.deref();
|
|
85
|
+
if (element) {
|
|
86
|
+
mountedElementsList.push(element);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Dismount each element
|
|
91
|
+
for (const element of mountedElementsList) {
|
|
92
|
+
// Remove from both structures
|
|
93
|
+
mountedElements.weakSet.delete(element);
|
|
94
|
+
for (const ref of mountedElements.setWeak) {
|
|
95
|
+
if (ref.deref() === element) {
|
|
96
|
+
mountedElements.setWeak.delete(ref);
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Dispatch dismount event with reason
|
|
102
|
+
observer.dispatchEvent(new DismountEvent(element, 'root-size-failed', init));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Start observing the root element
|
|
107
|
+
resizeObserver.observe(rootElement);
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
conditionMatches,
|
|
111
|
+
cleanup: () => {
|
|
112
|
+
resizeObserver.disconnect();
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Evaluate a container query condition against an element
|
|
119
|
+
* Supports: min-width, max-width, min-height, max-height
|
|
120
|
+
*/
|
|
121
|
+
function evaluateContainerQuery(element: Element, query: string): boolean {
|
|
122
|
+
// Parse container query: (min-width: 700px) or (max-height: 500px)
|
|
123
|
+
const match = query.match(/\(([^:]+):\s*([^)]+)\)/);
|
|
124
|
+
if (!match) {
|
|
125
|
+
console.warn(`Invalid container query format: ${query}`);
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const [, property, valueStr] = match;
|
|
130
|
+
const prop = property.trim();
|
|
131
|
+
const value = parseFloat(valueStr);
|
|
132
|
+
|
|
133
|
+
if (isNaN(value)) {
|
|
134
|
+
console.warn(`Invalid container query value: ${valueStr}`);
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Get element dimensions
|
|
139
|
+
const rect = element.getBoundingClientRect();
|
|
140
|
+
const width = rect.width;
|
|
141
|
+
const height = rect.height;
|
|
142
|
+
|
|
143
|
+
// Evaluate condition
|
|
144
|
+
switch (prop) {
|
|
145
|
+
case 'min-width':
|
|
146
|
+
return width >= value;
|
|
147
|
+
case 'max-width':
|
|
148
|
+
return width <= value;
|
|
149
|
+
case 'min-height':
|
|
150
|
+
return height >= value;
|
|
151
|
+
case 'max-height':
|
|
152
|
+
return height <= value;
|
|
153
|
+
default:
|
|
154
|
+
console.warn(`Unsupported container query property: ${prop}`);
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Searches for an element by ID, traversing up through shadow DOM boundaries.
|
|
3
|
+
*
|
|
4
|
+
* This function searches for an element with the specified ID starting from the reference
|
|
5
|
+
* element's root node and continuing up through shadow DOM boundaries until the element
|
|
6
|
+
* is found or the document root is reached.
|
|
7
|
+
*
|
|
8
|
+
* Search order:
|
|
9
|
+
* 1. Check current root node using getElementById
|
|
10
|
+
* 2. If in shadow root, check host element's properties for the ID
|
|
11
|
+
* 3. Continue up to parent shadow root or document
|
|
12
|
+
* 4. Handle disconnected fragments via targetFragment property
|
|
13
|
+
*
|
|
14
|
+
* @param ref - The reference element to start searching from
|
|
15
|
+
* @param id - The ID of the element to find
|
|
16
|
+
* @returns The found element, or null if not found
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const template = document.querySelector('template[src="#myId"]');
|
|
21
|
+
* const source = upShadowSearch(template, 'myId');
|
|
22
|
+
* if (source) {
|
|
23
|
+
* const clone = source.cloneNode(true);
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function upShadowSearch(ref, id) {
|
|
28
|
+
let rn = ref.getRootNode();
|
|
29
|
+
while (rn) {
|
|
30
|
+
// Try getElementById on current root
|
|
31
|
+
if ('getElementById' in rn) {
|
|
32
|
+
const test = rn.getElementById(id);
|
|
33
|
+
if (test)
|
|
34
|
+
return test;
|
|
35
|
+
}
|
|
36
|
+
// If in shadow root, check host element
|
|
37
|
+
if ('host' in rn && rn.host) {
|
|
38
|
+
// Check if host has a property with this ID
|
|
39
|
+
const hostProp = rn.host[id];
|
|
40
|
+
if (hostProp instanceof HTMLElement)
|
|
41
|
+
return hostProp;
|
|
42
|
+
// Move up to host's root
|
|
43
|
+
rn = rn.host.getRootNode();
|
|
44
|
+
}
|
|
45
|
+
else if (rn === document) {
|
|
46
|
+
// Reached document root without finding element
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
else if (!('isConnected' in rn) || !rn.isConnected) {
|
|
50
|
+
// Handle disconnected fragments
|
|
51
|
+
if (rn.targetFragment) {
|
|
52
|
+
rn = rn.targetFragment;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
rn = document;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
// No more parents to check
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Searches for an element by ID, traversing up through shadow DOM boundaries.
|
|
3
|
+
*
|
|
4
|
+
* This function searches for an element with the specified ID starting from the reference
|
|
5
|
+
* element's root node and continuing up through shadow DOM boundaries until the element
|
|
6
|
+
* is found or the document root is reached.
|
|
7
|
+
*
|
|
8
|
+
* Search order:
|
|
9
|
+
* 1. Check current root node using getElementById
|
|
10
|
+
* 2. If in shadow root, check host element's properties for the ID
|
|
11
|
+
* 3. Continue up to parent shadow root or document
|
|
12
|
+
* 4. Handle disconnected fragments via targetFragment property
|
|
13
|
+
*
|
|
14
|
+
* @param ref - The reference element to start searching from
|
|
15
|
+
* @param id - The ID of the element to find
|
|
16
|
+
* @returns The found element, or null if not found
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const template = document.querySelector('template[src="#myId"]');
|
|
21
|
+
* const source = upShadowSearch(template, 'myId');
|
|
22
|
+
* if (source) {
|
|
23
|
+
* const clone = source.cloneNode(true);
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function upShadowSearch(ref: Element, id: string): Element | null {
|
|
28
|
+
let rn = ref.getRootNode() as DocumentFragment | ShadowRoot | Document;
|
|
29
|
+
|
|
30
|
+
while (rn) {
|
|
31
|
+
// Try getElementById on current root
|
|
32
|
+
if ('getElementById' in rn) {
|
|
33
|
+
const test = rn.getElementById(id);
|
|
34
|
+
if (test) return test;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// If in shadow root, check host element
|
|
38
|
+
if ('host' in rn && rn.host) {
|
|
39
|
+
// Check if host has a property with this ID
|
|
40
|
+
const hostProp = (rn.host as any)[id];
|
|
41
|
+
if (hostProp instanceof HTMLElement) return hostProp;
|
|
42
|
+
|
|
43
|
+
// Move up to host's root
|
|
44
|
+
rn = rn.host.getRootNode() as DocumentFragment | ShadowRoot | Document;
|
|
45
|
+
} else if (rn === document) {
|
|
46
|
+
// Reached document root without finding element
|
|
47
|
+
return null;
|
|
48
|
+
} else if (!('isConnected' in rn) || !(rn as any).isConnected) {
|
|
49
|
+
// Handle disconnected fragments
|
|
50
|
+
if ((rn as any).targetFragment) {
|
|
51
|
+
rn = (rn as any).targetFragment;
|
|
52
|
+
} else {
|
|
53
|
+
rn = document;
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
// No more parents to check
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
@@ -1,116 +0,0 @@
|
|
|
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
|
-
// Check if modules are specified
|
|
7
|
-
if (!context.modules || context.modules.length === 0) {
|
|
8
|
-
throw new Error('Must specify an ES Module');
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const module = context.modules[0];
|
|
12
|
-
const tagName = mountedElement.localName;
|
|
13
|
-
|
|
14
|
-
// Check if already defined
|
|
15
|
-
if (customElements.get(tagName)) {
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Find suitable class
|
|
20
|
-
const ElementClass = this.findSuitableClass(module);
|
|
21
|
-
|
|
22
|
-
// Validate that ElementClass is a constructor
|
|
23
|
-
if (typeof ElementClass !== 'function') {
|
|
24
|
-
throw new Error(`Found class is not a constructor: ${typeof ElementClass}`);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Create wrapper class to allow reuse
|
|
28
|
-
// Use anonymous class expression which works across all browsers
|
|
29
|
-
const WrapperClass = class extends ElementClass {};
|
|
30
|
-
|
|
31
|
-
// Define the custom element using the define method
|
|
32
|
-
this.define(tagName, WrapperClass, mountedElement);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Define the custom element in the appropriate registry.
|
|
37
|
-
* Override this method in subclasses to use scoped registries.
|
|
38
|
-
* @param tagName - The custom element tag name
|
|
39
|
-
* @param ElementClass - The element class constructor
|
|
40
|
-
* @param mountedElement - The mounted element (used for scoped registry access)
|
|
41
|
-
*/
|
|
42
|
-
protected define(tagName: string, ElementClass: CustomElementConstructor, mountedElement: Element): void {
|
|
43
|
-
customElements.define(tagName, ElementClass);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
private findSuitableClass(module: any): typeof HTMLElement {
|
|
47
|
-
// Check default export first
|
|
48
|
-
const defaultExport = module.default;
|
|
49
|
-
|
|
50
|
-
if (defaultExport && this.extendsHTMLElement(defaultExport)) {
|
|
51
|
-
return defaultExport;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Find all exports that extend HTMLElement
|
|
55
|
-
const htmlElementClasses = Object.values(module)
|
|
56
|
-
.filter(exp => typeof exp === 'function' && this.extendsHTMLElement(exp));
|
|
57
|
-
|
|
58
|
-
if (htmlElementClasses.length === 0) {
|
|
59
|
-
throw new Error('No suitable class found in module');
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (htmlElementClasses.length > 1) {
|
|
63
|
-
throw new Error('More than one class found in module');
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return htmlElementClasses[0] as typeof HTMLElement;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
private extendsHTMLElement(cls: any): boolean {
|
|
70
|
-
try {
|
|
71
|
-
// Must be a function
|
|
72
|
-
if (typeof cls !== 'function') {
|
|
73
|
-
return false;
|
|
74
|
-
}
|
|
75
|
-
// Handle direct HTMLElement export
|
|
76
|
-
if (cls === HTMLElement) {
|
|
77
|
-
return true;
|
|
78
|
-
}
|
|
79
|
-
// Check if it has a prototype and extends HTMLElement
|
|
80
|
-
if (cls.prototype && cls.prototype instanceof HTMLElement) {
|
|
81
|
-
return true;
|
|
82
|
-
}
|
|
83
|
-
return false;
|
|
84
|
-
} catch {
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Handler for defining custom elements in scoped registries.
|
|
92
|
-
* Uses the element's customElementRegistry property to define elements
|
|
93
|
-
* in the appropriate scoped registry instead of the global registry.
|
|
94
|
-
*/
|
|
95
|
-
export class DefineScopedCustomElementHandler extends DefineCustomElementHandler {
|
|
96
|
-
/**
|
|
97
|
-
* Define the custom element in the element's scoped registry.
|
|
98
|
-
* @param tagName - The custom element tag name
|
|
99
|
-
* @param ElementClass - The element class constructor
|
|
100
|
-
* @param mountedElement - The mounted element with customElementRegistry
|
|
101
|
-
*/
|
|
102
|
-
protected define(tagName: string, ElementClass: CustomElementConstructor, mountedElement: Element): void {
|
|
103
|
-
const registry = (mountedElement as any).customElementRegistry;
|
|
104
|
-
|
|
105
|
-
if (!registry) {
|
|
106
|
-
throw new Error('Element does not have a customElementRegistry. Scoped registries require Chrome 146+ or latest WebKit/Safari.');
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Check if already defined in this scoped registry
|
|
110
|
-
if (registry.get(tagName)) {
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
registry.define(tagName, ElementClass);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { EvtRt } from './EvtRt.js';
|
|
2
|
-
import {EnhancementConfig} from './types/assign-gingerly/types.js';
|
|
3
|
-
import { MountConfig, MountContext } from './types/mount-observer/types.js';
|
|
4
|
-
//import { buildCSSQuery } from 'assign-gingerly/buildCSSQuery.js';
|
|
5
|
-
import 'assign-gingerly/object-extension.js';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Handler for automatically enhancing mounted elements using assign-gingerly.
|
|
9
|
-
* Searches the first imported module for an export with a "spawn" property
|
|
10
|
-
* and uses element.enh.get() to spawn the enhancement.
|
|
11
|
-
*/
|
|
12
|
-
export class EnhanceMountedElementHandler extends EvtRt {
|
|
13
|
-
async mount(mountedElement: Element, MountConfig: MountConfig, context: MountContext){
|
|
14
|
-
// Check if modules are specified
|
|
15
|
-
if (!context.modules || context.modules.length === 0) {
|
|
16
|
-
throw new Error('Must specify an ES Module with import property');
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const module = context.modules[0];
|
|
20
|
-
|
|
21
|
-
// Find registry item (object with spawn property)
|
|
22
|
-
const registryItem = await this._findRegistryItem(module, mountedElement);
|
|
23
|
-
|
|
24
|
-
if (!registryItem) {
|
|
25
|
-
throw new Error('No registry item found in module. Expected an export with a "spawn" property.');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Validate spawn is a constructor
|
|
29
|
-
if (typeof registryItem.spawn !== 'function') {
|
|
30
|
-
throw new Error('Registry item "spawn" property must be a constructor function');
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Spawn the enhancement
|
|
34
|
-
this._spawnEnhancement(mountedElement, registryItem, context);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Spawn the enhancement using element.enh.get().
|
|
39
|
-
* Polyfills customElementRegistry if needed for browsers without scoped registry support.
|
|
40
|
-
*/
|
|
41
|
-
protected _spawnEnhancement(element: Element, registryItem: any, context: MountContext): void {
|
|
42
|
-
// Polyfill element.customElementRegistry if it doesn't exist (for browsers without scoped registries)
|
|
43
|
-
if (!(element as any).customElementRegistry) {
|
|
44
|
-
Object.defineProperty(element, 'customElementRegistry', {
|
|
45
|
-
value: customElements,
|
|
46
|
-
writable: true,
|
|
47
|
-
enumerable: false,
|
|
48
|
-
configurable: true
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Use element.enh.get() to spawn the enhancement
|
|
53
|
-
const enh = (element as any).enh;
|
|
54
|
-
if (!enh || typeof enh.get !== 'function') {
|
|
55
|
-
throw new Error('Element does not have enh.get() method. Make sure assign-gingerly/object-extension.js is loaded.');
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
enh.get(registryItem, context);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Find a registry item in the module exports.
|
|
63
|
-
* A registry item is an object with a "spawn" property.
|
|
64
|
-
* @param module - The imported module
|
|
65
|
-
* @returns The registry item or null if not found
|
|
66
|
-
*/
|
|
67
|
-
protected async _findRegistryItem(module: any, el: Element): Promise<any | null> {
|
|
68
|
-
// Check default export first
|
|
69
|
-
if (module.default && await this._isRegistryItem(module.default, el)) {
|
|
70
|
-
return module.default;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Search all exports for a registry item
|
|
74
|
-
const exports = Object.values(module);
|
|
75
|
-
const registryItems = [];
|
|
76
|
-
for(const e of exports){
|
|
77
|
-
const isRegistryItem = await this._isRegistryItem(e, el);
|
|
78
|
-
if(isRegistryItem) registryItems.push(e);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (registryItems.length === 0) {
|
|
82
|
-
return null;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (registryItems.length > 1) {
|
|
86
|
-
throw new Error('More than one registry item found in module. Expected exactly one export with a "spawn" property.');
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return registryItems[0];
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Check if an export is a registry item (has a spawn property).
|
|
94
|
-
* @param exp - The export to check
|
|
95
|
-
* @returns True if the export is a registry item
|
|
96
|
-
*/
|
|
97
|
-
protected async _isRegistryItem(exp: any, mountedElement: Element): Promise<boolean> {
|
|
98
|
-
let test = exp !== null
|
|
99
|
-
&& typeof exp === 'object'
|
|
100
|
-
&& 'spawn' in exp
|
|
101
|
-
&& typeof exp.spawn === 'function';
|
|
102
|
-
if(!test) return false;
|
|
103
|
-
const emc = exp as EnhancementConfig;
|
|
104
|
-
if(emc.withAttrs !== undefined){
|
|
105
|
-
const cssQuery = (await import('assign-gingerly/buildCSSQuery.js')).buildCSSQuery(emc);
|
|
106
|
-
return mountedElement.matches(cssQuery);
|
|
107
|
-
}
|
|
108
|
-
return true;
|
|
109
|
-
}
|
|
110
|
-
}
|