mount-observer-script-element 0.0.1
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/.hintrc +15 -0
- package/.kiro/steering/tech.md +251 -0
- package/.vscode/settings.json +2 -0
- package/LICENSE +21 -0
- package/MOSE.js +189 -0
- package/MOSE.ts +228 -0
- package/README.md +303 -0
- package/demo/scopedCustomElementRegistry.html +23 -0
- package/getHighestCERNode.js +37 -0
- package/getHighestCERNode.ts +41 -0
- package/imports.html +9 -0
- package/index.js +2 -0
- package/index.ts +2 -0
- package/package.json +50 -0
- package/requirements/Requirement1.md +14 -0
- package/requirements/Requirement2.md +7 -0
- package/requirements/Requirement3.md +15 -0
- package/requirements/Requirement4.md +9 -0
- package/requirements/Requirement5.md +16 -0
- package/requirements/Requirement6.md +23 -0
- package/requirements/Requirement7.md +3 -0
- package/tests/MOSE.html +173 -0
- package/tests/Requirement3.html +239 -0
- package/tests/buttonMatch.json +12 -0
- package/tests/exclude.html +33 -0
- package/tests/getHighestCERNode.html +119 -0
- package/tests/inheritance.html +33 -0
- package/tests/simple.html +25 -0
- package/tsconfig.json +20 -0
package/.hintrc
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# Technology Stack
|
|
2
|
+
|
|
3
|
+
## Language & Build System
|
|
4
|
+
|
|
5
|
+
- **Primary Language**: TypeScript (compiled to JavaScript ES modules)
|
|
6
|
+
- **Target**: ESNext with strict mode enabled
|
|
7
|
+
- **Module System**: ES Modules (ESM)
|
|
8
|
+
- **No Build Tool**: TypeScript compiler only, no bundler (Webpack, Rollup, etc.)
|
|
9
|
+
|
|
10
|
+
## TypeScript Configuration
|
|
11
|
+
|
|
12
|
+
- Strict mode enabled
|
|
13
|
+
- Experimental decorators: false
|
|
14
|
+
- Source maps: disabled
|
|
15
|
+
- Line endings: LF (Unix-style)
|
|
16
|
+
- Skip lib check: true
|
|
17
|
+
|
|
18
|
+
## Testing
|
|
19
|
+
|
|
20
|
+
- **Test Framework**: Playwright (browser automation testing)
|
|
21
|
+
- **Test Files**: Located in `tests/` directory with `.spec.mjs` extension
|
|
22
|
+
- **HTML Test Files**: Corresponding `.html` files for each test spec
|
|
23
|
+
|
|
24
|
+
## Dependencies
|
|
25
|
+
|
|
26
|
+
- **Runtime**: None (zero runtime dependencies)
|
|
27
|
+
- **Dev Dependencies**:
|
|
28
|
+
- `@playwright/test` - Browser testing
|
|
29
|
+
- `spa-ssi` - Development server
|
|
30
|
+
|
|
31
|
+
## Common Commands
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Install dependencies
|
|
35
|
+
npm ci
|
|
36
|
+
|
|
37
|
+
# Run tests
|
|
38
|
+
npm test
|
|
39
|
+
|
|
40
|
+
# Start development server
|
|
41
|
+
npm run serve
|
|
42
|
+
|
|
43
|
+
# Run Safari browser tests
|
|
44
|
+
npm run safari
|
|
45
|
+
|
|
46
|
+
# Update dependencies
|
|
47
|
+
npm run update
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Compilation
|
|
51
|
+
|
|
52
|
+
TypeScript files are compiled to JavaScript using `tsc`. Both `.ts` and `.js` files are committed to the repository. The JavaScript files are the actual runtime artifacts.
|
|
53
|
+
|
|
54
|
+
**CRITICAL**: Always compile TypeScript using the configuration in `tsconfig.json`:
|
|
55
|
+
```bash
|
|
56
|
+
tsc
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Do NOT use `tsc` with individual file arguments or custom flags. The `tsconfig.json` contains the correct compiler settings for the entire project.
|
|
60
|
+
|
|
61
|
+
**Legacy Folder**: The `legacy/` folder contains old code that is not maintained. Do not expect files in the `legacy/` folder to compile or pass tests. Focus only on the root-level code and tests.
|
|
62
|
+
|
|
63
|
+
## Type Definition Files
|
|
64
|
+
|
|
65
|
+
**Type-Only Files**: Files containing only TypeScript type definitions should use the `.d.ts` extension and must not generate a `.js` file when compiled.
|
|
66
|
+
|
|
67
|
+
**Key Rules**:
|
|
68
|
+
- Type definition files must end with `.d.ts` (e.g., `types.d.ts`)
|
|
69
|
+
- `.d.ts` files should contain only types, interfaces, and type aliases
|
|
70
|
+
- Never include runtime values (constants, functions, classes) in `.d.ts` files
|
|
71
|
+
- Constants and runtime values belong in separate `.ts` files that compile to `.js`
|
|
72
|
+
|
|
73
|
+
**Pattern**:
|
|
74
|
+
```typescript
|
|
75
|
+
// types.d.ts - Type definitions only
|
|
76
|
+
export interface MountInit {
|
|
77
|
+
whereElementMatches: string;
|
|
78
|
+
}
|
|
79
|
+
export type mountEventName = 'mount';
|
|
80
|
+
|
|
81
|
+
// constants.ts - Runtime values
|
|
82
|
+
export const mountEventName = 'mount';
|
|
83
|
+
export const dismountEventName = 'dismount';
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Why this matters**:
|
|
87
|
+
- Prevents unnecessary `.js` files from being generated for type-only code
|
|
88
|
+
- Keeps type definitions separate from runtime code
|
|
89
|
+
- Follows TypeScript best practices for library distribution
|
|
90
|
+
- Reduces bundle size by excluding type-only code from runtime
|
|
91
|
+
|
|
92
|
+
**When to apply**:
|
|
93
|
+
- Creating files that only contain TypeScript types, interfaces, or type aliases
|
|
94
|
+
- Separating type definitions from implementation
|
|
95
|
+
- Defining public API types for library consumers
|
|
96
|
+
|
|
97
|
+
## Custom Event Classes
|
|
98
|
+
|
|
99
|
+
**Event Classes over CustomEvent**: When dispatching events, define custom classes that extend the Event class rather than using CustomEvent with detail objects.
|
|
100
|
+
|
|
101
|
+
**Key Rules**:
|
|
102
|
+
- Create dedicated event classes that extend Event
|
|
103
|
+
- Define event properties as public class members
|
|
104
|
+
- Include a static eventName property for the event type string
|
|
105
|
+
- Export corresponding interfaces for type safety
|
|
106
|
+
|
|
107
|
+
**Pattern**:
|
|
108
|
+
```typescript
|
|
109
|
+
// Events.ts - Event class definitions
|
|
110
|
+
export class MountEvent extends Event implements IMountEvent {
|
|
111
|
+
static eventName: mountEventName = 'mount';
|
|
112
|
+
|
|
113
|
+
constructor(public mountedElement: Element, public modules: any[]) {
|
|
114
|
+
super(MountEvent.eventName);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Usage in code
|
|
119
|
+
this.dispatchEvent(new MountEvent(element, modules));
|
|
120
|
+
|
|
121
|
+
// Listening with proper typing
|
|
122
|
+
observer.addEventListener('mount', (e: MountEvent) => {
|
|
123
|
+
console.log(e.mountedElement, e.modules);
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Why this matters**:
|
|
128
|
+
- CustomEvent is a legacy approach that uses untyped detail objects
|
|
129
|
+
- Custom event classes provide better type safety and IDE autocomplete
|
|
130
|
+
- Properties are directly accessible without going through event.detail
|
|
131
|
+
- Follows modern JavaScript/TypeScript best practices
|
|
132
|
+
- Makes the API more discoverable and self-documenting
|
|
133
|
+
|
|
134
|
+
**When to apply**:
|
|
135
|
+
- All event dispatching in the library
|
|
136
|
+
- When defining public event APIs
|
|
137
|
+
- When you need strongly-typed event data
|
|
138
|
+
|
|
139
|
+
## Code Splitting Principle
|
|
140
|
+
|
|
141
|
+
**Conditional Code Loading**: If a significant block of code (>6 lines) only executes based on optional configuration settings, extract it to a separate module and load it dynamically using `import()`.
|
|
142
|
+
|
|
143
|
+
**Benefits**:
|
|
144
|
+
- Reduces initial bundle size for users who don't need the feature
|
|
145
|
+
- Improves tree-shaking effectiveness
|
|
146
|
+
- Keeps core modules lean and focused
|
|
147
|
+
|
|
148
|
+
**Example**:
|
|
149
|
+
```typescript
|
|
150
|
+
// Instead of including all import logic in MountObserver
|
|
151
|
+
async #loadImports(): Promise<void> {
|
|
152
|
+
// Dynamically load only when MountInit.import is specified
|
|
153
|
+
const { loadImports } = await import('./loadImports.js');
|
|
154
|
+
this.#modules = await loadImports(this.#init.import);
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**When to apply**:
|
|
159
|
+
- Feature-specific utilities (e.g., import loading, intersection observers)
|
|
160
|
+
- Complex conditional logic blocks
|
|
161
|
+
- Optional API surface areas
|
|
162
|
+
- Heavy dependencies used conditionally
|
|
163
|
+
|
|
164
|
+
## Memory Management
|
|
165
|
+
|
|
166
|
+
**WeakRef for DOM Nodes**: Store references to DOM nodes (especially observed root nodes) as `WeakRef<Node>` to prevent memory leaks when nodes are removed from the document.
|
|
167
|
+
|
|
168
|
+
**Why this matters**:
|
|
169
|
+
- If a MountObserver instance outlives the observed DOM subtree, a strong reference would prevent garbage collection
|
|
170
|
+
- Shadow roots and detached fragments are particularly vulnerable
|
|
171
|
+
- WeakRef allows the DOM to be GC'd even if the observer is still referenced
|
|
172
|
+
|
|
173
|
+
**Pattern**:
|
|
174
|
+
```typescript
|
|
175
|
+
class MountObserver {
|
|
176
|
+
#rootNode: WeakRef<Node> | undefined;
|
|
177
|
+
|
|
178
|
+
observe(rootNode: Node): void {
|
|
179
|
+
this.#rootNode = new WeakRef(rootNode);
|
|
180
|
+
// Use rootNode directly here while it's in scope
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
someMethod(): void {
|
|
184
|
+
const rootNode = this.#rootNode?.deref();
|
|
185
|
+
if (!rootNode) {
|
|
186
|
+
// Node was garbage collected, handle gracefully
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
// Use rootNode
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**When to apply**:
|
|
195
|
+
- Storing references to observed DOM nodes
|
|
196
|
+
- Caching DOM elements that might be removed
|
|
197
|
+
- Any long-lived object holding DOM references
|
|
198
|
+
|
|
199
|
+
## Package Exports
|
|
200
|
+
|
|
201
|
+
The package uses conditional exports in package.json, providing both default (JS) and types (TS) for each module. Main entry point is `MountObserver.js`.
|
|
202
|
+
|
|
203
|
+
## Bare Specifier Imports & Import Maps
|
|
204
|
+
|
|
205
|
+
**Import Pattern for Node Dependencies**: This package uses bare specifiers with explicit `.js` extensions when importing from node_modules dependencies.
|
|
206
|
+
|
|
207
|
+
**Example**:
|
|
208
|
+
```typescript
|
|
209
|
+
import { assignGingerly } from 'assign-gingerly/assignGingerly.js';
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**Key Rules**:
|
|
213
|
+
- Always include the `.js` extension in bare specifier imports
|
|
214
|
+
- Use the full path including the file (e.g., `/index.js`)
|
|
215
|
+
- This works natively in browsers via import maps
|
|
216
|
+
|
|
217
|
+
**Import Map Setup**: The project uses server-side includes (SSI) to inject import maps into HTML files during development.
|
|
218
|
+
|
|
219
|
+
**Pattern**:
|
|
220
|
+
```html
|
|
221
|
+
<!-- In demo/test HTML files -->
|
|
222
|
+
<!-- #include virtual="/imports.html" -->
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**What this does**:
|
|
226
|
+
- The `spa-ssi` development server (configured in `package.json` scripts) processes SSI directives
|
|
227
|
+
- The `imports.html` file at the project root contains the import map
|
|
228
|
+
- The import map maps bare specifiers to `/node_modules/` paths
|
|
229
|
+
- This enables native browser support for bare imports without bundling
|
|
230
|
+
|
|
231
|
+
**Import Map Structure** (`imports.html`):
|
|
232
|
+
```html
|
|
233
|
+
<script type=importmap>
|
|
234
|
+
{
|
|
235
|
+
"imports": {
|
|
236
|
+
"assign-gingerly/": "/node_modules/assign-gingerly/"
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
</script>
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**Benefits**:
|
|
243
|
+
- No build step required for development
|
|
244
|
+
- Native ES modules work directly in the browser
|
|
245
|
+
- Dependencies resolve naturally via import maps
|
|
246
|
+
- Matches production CDN patterns (e.g., unpkg, esm.sh)
|
|
247
|
+
|
|
248
|
+
**When to apply**:
|
|
249
|
+
- All imports from node_modules dependencies
|
|
250
|
+
- Demo and test HTML files need the SSI include directive
|
|
251
|
+
- Import map must be updated when adding new dependencies
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Bruce B. Anderson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/MOSE.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { getHighestCERNode } from './getHighestCERNode.js';
|
|
2
|
+
import { MountObserver } from 'mount-observer/MountObserver.js';
|
|
3
|
+
import { mountEventName } from 'mount-observer/Events.js';
|
|
4
|
+
/**
|
|
5
|
+
* Symbol to track if MountObserver has been set up for an element
|
|
6
|
+
*/
|
|
7
|
+
const MOUNT_OBSERVER_SETUP = Symbol.for('cteH9dMG-UWwxVaMwFgvQA');
|
|
8
|
+
/**
|
|
9
|
+
* MOSE (Mount Observer Script Element) Mixin
|
|
10
|
+
*
|
|
11
|
+
* This mixin adds functionality to:
|
|
12
|
+
* 1. Check for duplicate custom element registrations within the same custom element registry scope
|
|
13
|
+
* 2. Monitor for <script type="mountobserver"> elements and apply their configurations
|
|
14
|
+
*
|
|
15
|
+
* @param Base - The base class to extend
|
|
16
|
+
* @returns Extended class with MOSE functionality
|
|
17
|
+
*/
|
|
18
|
+
export function MOSE(Base) {
|
|
19
|
+
return class extends Base {
|
|
20
|
+
#mountObserver;
|
|
21
|
+
constructor(...args) {
|
|
22
|
+
super(...args);
|
|
23
|
+
this.#checkForDuplicateRegistration();
|
|
24
|
+
this.#setupMountObserver();
|
|
25
|
+
}
|
|
26
|
+
#checkForDuplicateRegistration() {
|
|
27
|
+
// Get the tag name of this element
|
|
28
|
+
// const tagName = this.tagName.toLowerCase();
|
|
29
|
+
const { localName } = this;
|
|
30
|
+
// Find the highest node with the same custom element registry
|
|
31
|
+
const highestCERNode = getHighestCERNode(this);
|
|
32
|
+
if (!highestCERNode) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
// Get the custom element registry for this scope
|
|
36
|
+
// const registry = (highestCERNode as any).customElementRegistry as CustomElementRegistry | undefined;
|
|
37
|
+
// // If there's no custom registry, use the global one
|
|
38
|
+
// const targetRegistry = registry || customElements;
|
|
39
|
+
// Check if an element with this name is already defined in this registry
|
|
40
|
+
try {
|
|
41
|
+
const existingTags = Array.from(highestCERNode.querySelectorAll(localName)).filter(x => x !== this);
|
|
42
|
+
if (existingTags.length > 0) {
|
|
43
|
+
throw new Error(`Custom element "${localName}" is already defined in this custom element registry scope.`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
// If get() throws, it means the element isn't registered yet, which is fine
|
|
48
|
+
// But if it's our error, re-throw it
|
|
49
|
+
if (error instanceof Error && error.message.includes('already defined')) {
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Copy mountobserver script elements from container
|
|
54
|
+
this.#getContainerMOSEs(highestCERNode);
|
|
55
|
+
}
|
|
56
|
+
#getContainerMOSEs(highestCERNode) {
|
|
57
|
+
// Step 1: Don't do anything if highestCERNode is the document root
|
|
58
|
+
if (highestCERNode === document) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
// Step 2: Get parent element or host
|
|
62
|
+
let parentNode = null;
|
|
63
|
+
if (highestCERNode instanceof Element) {
|
|
64
|
+
parentNode = highestCERNode.parentElement;
|
|
65
|
+
}
|
|
66
|
+
else if (highestCERNode instanceof ShadowRoot) {
|
|
67
|
+
parentNode = highestCERNode.host;
|
|
68
|
+
}
|
|
69
|
+
if (!parentNode) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Step 3: Find the highestCERNode of the parent
|
|
73
|
+
const parentHighestCERNode = getHighestCERNode(parentNode);
|
|
74
|
+
// Find parent custom element with same localName
|
|
75
|
+
if (!parentHighestCERNode || !('querySelector' in parentHighestCERNode)) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const parentCE = parentHighestCERNode.querySelector(this.localName);
|
|
79
|
+
if (!parentCE) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
// If highestCERNode contains parentCE, exit
|
|
83
|
+
if (highestCERNode.contains?.(parentCE)) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
// Clone and append script elements
|
|
87
|
+
this.#cloneAndAppendScripts(parentCE);
|
|
88
|
+
// Add event listener for future mount events
|
|
89
|
+
parentCE.addEventListener(mountEventName, (e) => {
|
|
90
|
+
const mountedElement = e.mountedElement;
|
|
91
|
+
if (mountedElement instanceof HTMLScriptElement && mountedElement.type === 'mountobserver') {
|
|
92
|
+
this.#cloneAndAppendScripts(parentCE);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
#cloneAndAppendScripts(sourceElement) {
|
|
97
|
+
const scripts = Array.from(sourceElement.querySelectorAll('script[type="mountobserver"]'));
|
|
98
|
+
// Get exclude value from property or attribute
|
|
99
|
+
const exclude = this.exclude ?? this.getAttribute('exclude');
|
|
100
|
+
for (const script of scripts) {
|
|
101
|
+
// Check if script matches exclude criteria
|
|
102
|
+
if (exclude && script.matches(exclude)) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const src = script.getAttribute('src');
|
|
106
|
+
// Check if we already have a script with the same src
|
|
107
|
+
const existingScripts = Array.from(this.querySelectorAll('script[type="mountobserver"]'));
|
|
108
|
+
const alreadyExists = existingScripts.some(existing => {
|
|
109
|
+
const existingSrc = existing.getAttribute('src');
|
|
110
|
+
return existingSrc === src;
|
|
111
|
+
});
|
|
112
|
+
if (!alreadyExists) {
|
|
113
|
+
const clonedScript = script.cloneNode(true);
|
|
114
|
+
this.appendChild(clonedScript);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async #setupMountObserver() {
|
|
119
|
+
// Find the highest node with the same custom element registry
|
|
120
|
+
const highestCERNode = getHighestCERNode(this);
|
|
121
|
+
if (!highestCERNode) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
// Check if MountObserver has already been set up for this element
|
|
125
|
+
const existingObserver = highestCERNode[MOUNT_OBSERVER_SETUP];
|
|
126
|
+
if (existingObserver) {
|
|
127
|
+
// Subscribe to the existing observer's mount event and re-dispatch from this element
|
|
128
|
+
existingObserver.addEventListener(mountEventName, (e) => {
|
|
129
|
+
const { mountedElement } = e;
|
|
130
|
+
if (this.contains(mountedElement)) {
|
|
131
|
+
this.dispatchEvent(e);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
// Set up MountObserver to watch for <script type="mountobserver"> elements
|
|
137
|
+
this.#mountObserver = new MountObserver({
|
|
138
|
+
whereElementMatches: 'script[type="mountobserver"]',
|
|
139
|
+
do: async (scriptElement) => {
|
|
140
|
+
await this.#processScriptElement(scriptElement, highestCERNode);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
// Mark that we've set up the MountObserver for this element
|
|
144
|
+
highestCERNode[MOUNT_OBSERVER_SETUP] = this.#mountObserver;
|
|
145
|
+
await this.#mountObserver.observe(highestCERNode);
|
|
146
|
+
}
|
|
147
|
+
async #processScriptElement(scriptElement, rootNode) {
|
|
148
|
+
let config = {};
|
|
149
|
+
// Step 1: Check if script has src attribute and load JSON
|
|
150
|
+
const src = scriptElement.getAttribute('src');
|
|
151
|
+
if (src) {
|
|
152
|
+
try {
|
|
153
|
+
const response = await import(src, { with: { type: 'json' } });
|
|
154
|
+
config = structuredClone(response.default);
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
console.error(`Failed to load JSON from ${scriptElement.src}:`, error);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Step 2: If innerHTML is non-trivial, parse and merge it
|
|
162
|
+
const innerHTML = scriptElement.innerHTML.trim();
|
|
163
|
+
if (innerHTML) {
|
|
164
|
+
try {
|
|
165
|
+
const parsedJSON = JSON.parse(innerHTML);
|
|
166
|
+
// Step 3: Import assignGingerly and merge
|
|
167
|
+
const { assignGingerly } = await import('assign-gingerly/assignGingerly.js');
|
|
168
|
+
config = assignGingerly(config, parsedJSON, {
|
|
169
|
+
registry: scriptElement.customElementRegistry.assignGingerlyRegistry
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
console.error('Failed to parse script innerHTML as JSON:', error);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Step 4: Apply MountObserver with the merged config
|
|
178
|
+
if (Object.keys(config).length > 0) {
|
|
179
|
+
try {
|
|
180
|
+
const observer = new MountObserver(config);
|
|
181
|
+
observer.observe(rootNode);
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
console.error('Failed to create MountObserver with config:', error);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|