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/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mount-observer-script-element",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "A TypeScript mixin that enables declarative configuration of MountObserver instances through script elements with support for Scoped Custom Element Registries",
|
|
5
|
+
"homepage": "https://github.com/bahrus/mount-observer-script-element#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/bahrus/mount-observer-script-element/issues"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/bahrus/mount-observer-script-element.git"
|
|
12
|
+
},
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "Bruce B. Anderson <anderson.bruce.b@gmail.com>",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "index.js",
|
|
17
|
+
"types": "index.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./index.ts",
|
|
21
|
+
"default": "./index.js"
|
|
22
|
+
},
|
|
23
|
+
"./MOSE.js": {
|
|
24
|
+
"types": "./MOSE.ts",
|
|
25
|
+
"default": "./MOSE.js"
|
|
26
|
+
},
|
|
27
|
+
"./getHighestCERNode.js": {
|
|
28
|
+
"types": "./getHighestCERNode.ts",
|
|
29
|
+
"default": "./getHighestCERNode.js"
|
|
30
|
+
},
|
|
31
|
+
"./index.js": {
|
|
32
|
+
"types": "./index.ts",
|
|
33
|
+
"default": "./index.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"mount-observer": "0.1.4",
|
|
38
|
+
"assign-gingerly": "0.0.6"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@playwright/test": "1.58.2",
|
|
42
|
+
"spa-ssi": "0.0.26"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"serve": "node ./node_modules/spa-ssi/serve.js",
|
|
46
|
+
"test": "playwright test",
|
|
47
|
+
"safari": "npx playwright wk http://localhost:8000",
|
|
48
|
+
"update": "ncu -u && npm install"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# getHighestCERNode
|
|
2
|
+
|
|
3
|
+
Chrome Canary now has support for Scoped Custom Element Registries.
|
|
4
|
+
|
|
5
|
+
Because it is so cutting edge, we can't create and run playwright tests for now.
|
|
6
|
+
|
|
7
|
+
However, it makes sense to create test html pages in the tests folder that can be used to test this functionality manually for now.
|
|
8
|
+
|
|
9
|
+
See demo/scopedCustomElementRegistry.html as an example of such a page.
|
|
10
|
+
|
|
11
|
+
Each element now has a property, "customElementRegistry". "Scoping" is allowed within a shadowRoot, meaning elements within a shadowRoot can have their own customElementRegistry.
|
|
12
|
+
|
|
13
|
+
Define a module, 'getHighestCERNode.ts' with a function of the same name that takes as a parameter a Node, and recursively traverses up using parentElement and getRootNode(), checking if the customElementRegistry matches. Stop at the root node and return the highest node with a customElementRegistry that matches the passed in element.
|
|
14
|
+
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# mount-observer-script-element custom element mixin
|
|
2
|
+
|
|
3
|
+
Please define a custom element mixin called MOSE that:
|
|
4
|
+
|
|
5
|
+
1. Finds the highest CERNode containing the element using getHighestCERNode.ts
|
|
6
|
+
2. Checks if there is already a custom element with the same localName as the current element within the highestCERNode scope by using querySelectorAll and filtering out the current element. If any matching elements are found, throws an error.
|
|
7
|
+
3. After the duplicate check, calls `#getContainerMOSEs(highestCERNode)` to copy mountobserver script elements from parent containers (see Requirement 6).
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Monitor for mount-observer-script-elements
|
|
2
|
+
|
|
3
|
+
In the MOSE.ts mixin, after getting the highestCERNode, add a MountObserver observer, documented in node_modules/mount-observer/ReadME.md, that watches for elements of type:
|
|
4
|
+
|
|
5
|
+
```JavaScript
|
|
6
|
+
<script type="mountobserver"></script>
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
It should:
|
|
10
|
+
|
|
11
|
+
1. Check if the script element has a src attribute using getAttribute('src'). If so, use a JSON import with `import(src, {with: {type: 'json'}})` to read the JSON, and use response.default as the config.
|
|
12
|
+
2. If no src attribute found, start with empty object {}
|
|
13
|
+
3. If the script element contains non-trivial innerHTML, use JSON.parse on the script's innerHTML property.
|
|
14
|
+
4. Import assign-gingerly/assignGingerly.js (documented in node_modules/assign-gingerly/ReadMe.md) and use assignGingerly to merge the parsed JSON into the config object.
|
|
15
|
+
5. Apply MountObserver on the highestCERNode with the merged config object.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Unique mountobserver for mountobserver script elements
|
|
2
|
+
|
|
3
|
+
Method #setupMountObserver() should only be invoked once per highestCERNode, regardless of how many custom elements utilize the MOSE mixin.
|
|
4
|
+
|
|
5
|
+
Implementation:
|
|
6
|
+
|
|
7
|
+
1. Use `Symbol.for('cteH9dMG-UWwxVaMwFgvQA')` as the key to track if a MountObserver has been set up
|
|
8
|
+
2. Store the actual MountObserver instance (not just a boolean) in `(highestCERNode as any)[MOUNT_OBSERVER_SETUP]`
|
|
9
|
+
3. When an existing observer is found, subscribe to its mount events and re-dispatch them from the current element (see Requirement 5)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Notify of mount observer children
|
|
2
|
+
|
|
3
|
+
In #setupMountObserver, if an observer is already found, instead of just returning, we need to add code that does the following:
|
|
4
|
+
|
|
5
|
+
1. Import `MountEvent` and `mountEventName` from 'mount-observer/Events.js'
|
|
6
|
+
2. Subscribe to the existing observer's mount event using `mountEventName`
|
|
7
|
+
3. In the event handler, extract the `mountedElement` from the `MountEvent`
|
|
8
|
+
4. Check if `this.contains(mountedElement)` - only proceed if the mounted element is within this custom element
|
|
9
|
+
5. If the check passes, re-dispatch the event from this custom element instance using `this.dispatchEvent(e)`
|
|
10
|
+
|
|
11
|
+
Additionally:
|
|
12
|
+
|
|
13
|
+
6. Change the `MOUNT_OBSERVER_SETUP` symbol to use `Symbol.for('cteH9dMG-UWwxVaMwFgvQA')` instead of a regular Symbol
|
|
14
|
+
7. Store the actual MountObserver instance (not just `true`) in `(highestCERNode as any)[MOUNT_OBSERVER_SETUP]`
|
|
15
|
+
8. In `#processScriptElement`, when loading JSON from src, use `structuredClone(response.default)` instead of just `response.default`
|
|
16
|
+
9. When calling `assignGingerly`, pass a third parameter with the registry: `{registry: (<any>scriptElement).customElementRegistry.assignGingerlyRegistry}`
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Copy mountobserver script elements from container
|
|
2
|
+
|
|
3
|
+
Add a method `#getContainerMOSEs(highestCERNode: Node)` that is called at the end of `#checkForDuplicateRegistration()`.
|
|
4
|
+
|
|
5
|
+
What this method does:
|
|
6
|
+
|
|
7
|
+
1. If highestCERNode is the document root, return early (don't do anything)
|
|
8
|
+
2. Get the parent node:
|
|
9
|
+
- If highestCERNode is an Element, get its parentElement
|
|
10
|
+
- If highestCERNode is a ShadowRoot, get its host property
|
|
11
|
+
3. Find the highestCERNode of the parent node obtained from step 2
|
|
12
|
+
4. If the parent's highestCERNode exists and has a querySelector method, search for an element with the same localName as the current element instance. Call it "parentCE". If not found, exit.
|
|
13
|
+
5. If the highestCERNode passed into the method contains the parentCE, exit quietly
|
|
14
|
+
6. Call `#cloneAndAppendScripts(parentCE)` to clone all script elements with type="mountobserver" from parentCE
|
|
15
|
+
7. Add an event listener to parentCE for the "mount" event (using mountEventName). When a mount event occurs, check if the mountedElement is an HTMLScriptElement with type="mountobserver", and if so, call `#cloneAndAppendScripts(parentCE)` again
|
|
16
|
+
|
|
17
|
+
Create a helper method `#cloneAndAppendScripts(sourceElement: Element)`:
|
|
18
|
+
|
|
19
|
+
1. Find all script elements with type="mountobserver" in the sourceElement
|
|
20
|
+
2. For each script, get its src attribute
|
|
21
|
+
3. Check if a script with the same src attribute already exists in the current element
|
|
22
|
+
4. Only clone and append scripts that don't already exist (to avoid duplicates)
|
|
23
|
+
|
package/tests/MOSE.html
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Test MOSE Mixin</title>
|
|
7
|
+
<!-- #include virtual="/imports.html" -->
|
|
8
|
+
<style>
|
|
9
|
+
body {
|
|
10
|
+
font-family: Arial, sans-serif;
|
|
11
|
+
padding: 20px;
|
|
12
|
+
}
|
|
13
|
+
.test-result {
|
|
14
|
+
margin: 10px 0;
|
|
15
|
+
padding: 10px;
|
|
16
|
+
border-radius: 4px;
|
|
17
|
+
}
|
|
18
|
+
.pass {
|
|
19
|
+
background-color: #d4edda;
|
|
20
|
+
color: #155724;
|
|
21
|
+
}
|
|
22
|
+
.fail {
|
|
23
|
+
background-color: #f8d7da;
|
|
24
|
+
color: #721c24;
|
|
25
|
+
}
|
|
26
|
+
#test-container {
|
|
27
|
+
margin-top: 20px;
|
|
28
|
+
}
|
|
29
|
+
</style>
|
|
30
|
+
</head>
|
|
31
|
+
<body>
|
|
32
|
+
<h1>MOSE Mixin Tests</h1>
|
|
33
|
+
<div id="results"></div>
|
|
34
|
+
<div id="test-container"></div>
|
|
35
|
+
|
|
36
|
+
<script type="module">
|
|
37
|
+
import { MOSE } from '../MOSE.js';
|
|
38
|
+
|
|
39
|
+
const results = document.getElementById('results');
|
|
40
|
+
const container = document.getElementById('test-container');
|
|
41
|
+
|
|
42
|
+
function logResult(testName, passed, message) {
|
|
43
|
+
const div = document.createElement('div');
|
|
44
|
+
div.className = `test-result ${passed ? 'pass' : 'fail'}`;
|
|
45
|
+
div.textContent = `${passed ? '✓' : '✗'} ${testName}: ${message}`;
|
|
46
|
+
results.appendChild(div);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Test 1: Define a custom element using MOSE mixin
|
|
50
|
+
try {
|
|
51
|
+
class TestElement1 extends MOSE(HTMLElement) {
|
|
52
|
+
constructor() {
|
|
53
|
+
super();
|
|
54
|
+
this.innerHTML = '<p>Test Element 1</p>';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
customElements.define('test-element-1', TestElement1);
|
|
58
|
+
|
|
59
|
+
const el1 = document.createElement('test-element-1');
|
|
60
|
+
container.appendChild(el1);
|
|
61
|
+
|
|
62
|
+
logResult(
|
|
63
|
+
'Test 1',
|
|
64
|
+
true,
|
|
65
|
+
'Successfully defined custom element with MOSE mixin'
|
|
66
|
+
);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
logResult(
|
|
69
|
+
'Test 1',
|
|
70
|
+
false,
|
|
71
|
+
`Failed to define element: ${error.message}`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Test 2: Try to create duplicate element in same registry (should not throw during creation)
|
|
76
|
+
try {
|
|
77
|
+
const el2 = document.createElement('test-element-1');
|
|
78
|
+
container.appendChild(el2);
|
|
79
|
+
|
|
80
|
+
logResult(
|
|
81
|
+
'Test 2',
|
|
82
|
+
true,
|
|
83
|
+
'Can create multiple instances of same custom element'
|
|
84
|
+
);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
logResult(
|
|
87
|
+
'Test 2',
|
|
88
|
+
false,
|
|
89
|
+
`Unexpected error: ${error.message}`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Test 3: Define element with scoped registry
|
|
94
|
+
try {
|
|
95
|
+
const customRegistry = new CustomElementRegistry();
|
|
96
|
+
|
|
97
|
+
class TestElement2 extends MOSE(HTMLElement) {
|
|
98
|
+
constructor() {
|
|
99
|
+
super();
|
|
100
|
+
this.innerHTML = '<p>Test Element 2 (Scoped)</p>';
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
customRegistry.define('test-element-2', TestElement2);
|
|
105
|
+
|
|
106
|
+
const el3 = document.createElement('test-element-2', {
|
|
107
|
+
customElementRegistry: customRegistry
|
|
108
|
+
});
|
|
109
|
+
container.appendChild(el3);
|
|
110
|
+
|
|
111
|
+
logResult(
|
|
112
|
+
'Test 3',
|
|
113
|
+
true,
|
|
114
|
+
'Successfully defined element in scoped registry'
|
|
115
|
+
);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
logResult(
|
|
118
|
+
'Test 3',
|
|
119
|
+
false,
|
|
120
|
+
`Failed with scoped registry: ${error.message}`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Test 4: Test MountObserver script element processing
|
|
125
|
+
try {
|
|
126
|
+
class TestElement3 extends MOSE(HTMLElement) {
|
|
127
|
+
constructor() {
|
|
128
|
+
super();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
customElements.define('test-element-3', TestElement3);
|
|
132
|
+
|
|
133
|
+
const el4 = document.createElement('test-element-3');
|
|
134
|
+
container.appendChild(el4);
|
|
135
|
+
|
|
136
|
+
// Add a mount observer script element
|
|
137
|
+
const script = document.createElement('script');
|
|
138
|
+
script.type = 'mountobserver';
|
|
139
|
+
script.textContent = JSON.stringify({
|
|
140
|
+
whereElementMatches: 'div.test-target',
|
|
141
|
+
assignGingerly: {
|
|
142
|
+
'?.dataset?.processed': 'true'
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
el4.appendChild(script);
|
|
146
|
+
|
|
147
|
+
// Add a target element
|
|
148
|
+
setTimeout(() => {
|
|
149
|
+
const targetDiv = document.createElement('div');
|
|
150
|
+
targetDiv.className = 'test-target';
|
|
151
|
+
el4.appendChild(targetDiv);
|
|
152
|
+
|
|
153
|
+
setTimeout(() => {
|
|
154
|
+
const processed = targetDiv.dataset.processed === 'true';
|
|
155
|
+
logResult(
|
|
156
|
+
'Test 4',
|
|
157
|
+
processed,
|
|
158
|
+
processed ? 'MountObserver script element processed successfully' : 'MountObserver script element not processed'
|
|
159
|
+
);
|
|
160
|
+
}, 100);
|
|
161
|
+
}, 100);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
logResult(
|
|
164
|
+
'Test 4',
|
|
165
|
+
false,
|
|
166
|
+
`Error with MountObserver script: ${error.message}`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
console.log('MOSE tests initiated. Check results above.');
|
|
171
|
+
</script>
|
|
172
|
+
</body>
|
|
173
|
+
</html>
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Test Requirement 3 - MountObserver Script Elements</title>
|
|
7
|
+
<!-- #include virtual="/imports.html" -->
|
|
8
|
+
<style>
|
|
9
|
+
body {
|
|
10
|
+
font-family: Arial, sans-serif;
|
|
11
|
+
padding: 20px;
|
|
12
|
+
}
|
|
13
|
+
.test-result {
|
|
14
|
+
margin: 10px 0;
|
|
15
|
+
padding: 10px;
|
|
16
|
+
border-radius: 4px;
|
|
17
|
+
}
|
|
18
|
+
.pass {
|
|
19
|
+
background-color: #d4edda;
|
|
20
|
+
color: #155724;
|
|
21
|
+
}
|
|
22
|
+
.fail {
|
|
23
|
+
background-color: #f8d7da;
|
|
24
|
+
color: #721c24;
|
|
25
|
+
}
|
|
26
|
+
.info {
|
|
27
|
+
background-color: #d1ecf1;
|
|
28
|
+
color: #0c5460;
|
|
29
|
+
}
|
|
30
|
+
#test-container {
|
|
31
|
+
margin-top: 20px;
|
|
32
|
+
}
|
|
33
|
+
</style>
|
|
34
|
+
</head>
|
|
35
|
+
<body>
|
|
36
|
+
<h1>Requirement 3: MountObserver Script Elements</h1>
|
|
37
|
+
<div id="results"></div>
|
|
38
|
+
<div id="test-container"></div>
|
|
39
|
+
|
|
40
|
+
<script type="module">
|
|
41
|
+
import { MOSE } from '../MOSE.js';
|
|
42
|
+
|
|
43
|
+
const results = document.getElementById('results');
|
|
44
|
+
const container = document.getElementById('test-container');
|
|
45
|
+
|
|
46
|
+
function logResult(testName, passed, message, isInfo = false) {
|
|
47
|
+
const div = document.createElement('div');
|
|
48
|
+
div.className = `test-result ${isInfo ? 'info' : (passed ? 'pass' : 'fail')}`;
|
|
49
|
+
div.textContent = `${isInfo ? 'ℹ' : (passed ? '✓' : '✗')} ${testName}: ${message}`;
|
|
50
|
+
results.appendChild(div);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
logResult('Info', true, 'Testing MOSE mixin with MountObserver script elements', true);
|
|
54
|
+
|
|
55
|
+
// Test 1: Basic MOSE element with inline JSON config
|
|
56
|
+
try {
|
|
57
|
+
class MoseElement1 extends MOSE(HTMLElement) {
|
|
58
|
+
constructor() {
|
|
59
|
+
super();
|
|
60
|
+
this.attachShadow({ mode: 'open' });
|
|
61
|
+
this.shadowRoot.innerHTML = '<div id="content"></div>';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
customElements.define('mose-element-1', MoseElement1);
|
|
65
|
+
|
|
66
|
+
const el1 = document.createElement('mose-element-1');
|
|
67
|
+
container.appendChild(el1);
|
|
68
|
+
|
|
69
|
+
// Add a mount observer script with inline JSON
|
|
70
|
+
const script1 = document.createElement('script');
|
|
71
|
+
script1.type = 'mountobserver';
|
|
72
|
+
script1.textContent = JSON.stringify({
|
|
73
|
+
whereElementMatches: 'button.auto-disable',
|
|
74
|
+
assignGingerly: {
|
|
75
|
+
disabled: true,
|
|
76
|
+
'?.dataset?.autoDisabled': 'true'
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
el1.shadowRoot.appendChild(script1);
|
|
80
|
+
|
|
81
|
+
// Add target button after a delay
|
|
82
|
+
setTimeout(() => {
|
|
83
|
+
const button = document.createElement('button');
|
|
84
|
+
button.className = 'auto-disable';
|
|
85
|
+
button.textContent = 'Test Button';
|
|
86
|
+
el1.shadowRoot.getElementById('content').appendChild(button);
|
|
87
|
+
|
|
88
|
+
setTimeout(() => {
|
|
89
|
+
const isDisabled = button.disabled === true;
|
|
90
|
+
const hasDataset = button.dataset.autoDisabled === 'true';
|
|
91
|
+
logResult(
|
|
92
|
+
'Test 1',
|
|
93
|
+
isDisabled && hasDataset,
|
|
94
|
+
`Inline JSON config ${isDisabled && hasDataset ? 'applied successfully' : 'failed to apply'}`
|
|
95
|
+
);
|
|
96
|
+
}, 150);
|
|
97
|
+
}, 100);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
logResult(
|
|
100
|
+
'Test 1',
|
|
101
|
+
false,
|
|
102
|
+
`Failed: ${error.message}`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Test 2: Empty config (no src, no innerHTML)
|
|
107
|
+
try {
|
|
108
|
+
class MoseElement2 extends MOSE(HTMLElement) {
|
|
109
|
+
constructor() {
|
|
110
|
+
super();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
customElements.define('mose-element-2', MoseElement2);
|
|
114
|
+
|
|
115
|
+
const el2 = document.createElement('mose-element-2');
|
|
116
|
+
container.appendChild(el2);
|
|
117
|
+
|
|
118
|
+
// Add empty script
|
|
119
|
+
const script2 = document.createElement('script');
|
|
120
|
+
script2.type = 'mountobserver';
|
|
121
|
+
el2.appendChild(script2);
|
|
122
|
+
|
|
123
|
+
setTimeout(() => {
|
|
124
|
+
logResult(
|
|
125
|
+
'Test 2',
|
|
126
|
+
true,
|
|
127
|
+
'Empty script element handled gracefully'
|
|
128
|
+
);
|
|
129
|
+
}, 300);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
logResult(
|
|
132
|
+
'Test 2',
|
|
133
|
+
false,
|
|
134
|
+
`Failed: ${error.message}`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Test 3: Multiple properties with assignGingerly
|
|
139
|
+
try {
|
|
140
|
+
class MoseElement3 extends MOSE(HTMLElement) {
|
|
141
|
+
constructor() {
|
|
142
|
+
super();
|
|
143
|
+
this.innerHTML = '<div id="target-container"></div>';
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
customElements.define('mose-element-3', MoseElement3);
|
|
147
|
+
|
|
148
|
+
const el3 = document.createElement('mose-element-3');
|
|
149
|
+
container.appendChild(el3);
|
|
150
|
+
|
|
151
|
+
const script3 = document.createElement('script');
|
|
152
|
+
script3.type = 'mountobserver';
|
|
153
|
+
script3.textContent = JSON.stringify({
|
|
154
|
+
whereElementMatches: 'input.styled-input',
|
|
155
|
+
assignGingerly: {
|
|
156
|
+
placeholder: 'Enter text...',
|
|
157
|
+
'?.style?.backgroundColor': '#f0f0f0',
|
|
158
|
+
'?.style?.padding': '10px',
|
|
159
|
+
'?.dataset?.styled': 'true'
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
el3.appendChild(script3);
|
|
163
|
+
|
|
164
|
+
setTimeout(() => {
|
|
165
|
+
const input = document.createElement('input');
|
|
166
|
+
input.className = 'styled-input';
|
|
167
|
+
el3.querySelector('#target-container').appendChild(input);
|
|
168
|
+
|
|
169
|
+
setTimeout(() => {
|
|
170
|
+
const hasPlaceholder = input.placeholder === 'Enter text...';
|
|
171
|
+
const hasBackground = input.style.backgroundColor === 'rgb(240, 240, 240)';
|
|
172
|
+
const hasPadding = input.style.padding === '10px';
|
|
173
|
+
const hasDataset = input.dataset.styled === 'true';
|
|
174
|
+
|
|
175
|
+
const allPassed = hasPlaceholder && hasBackground && hasPadding && hasDataset;
|
|
176
|
+
logResult(
|
|
177
|
+
'Test 3',
|
|
178
|
+
allPassed,
|
|
179
|
+
`Multiple properties ${allPassed ? 'applied correctly' : 'not all applied'}`
|
|
180
|
+
);
|
|
181
|
+
}, 150);
|
|
182
|
+
}, 400);
|
|
183
|
+
} catch (error) {
|
|
184
|
+
logResult(
|
|
185
|
+
'Test 3',
|
|
186
|
+
false,
|
|
187
|
+
`Failed: ${error.message}`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Test 4: Scoped registry with MOSE
|
|
192
|
+
try {
|
|
193
|
+
const customRegistry = new CustomElementRegistry();
|
|
194
|
+
|
|
195
|
+
class MoseElement4 extends MOSE(HTMLElement) {
|
|
196
|
+
constructor() {
|
|
197
|
+
super();
|
|
198
|
+
this.innerHTML = '<span class="target-span">Target</span>';
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
customRegistry.define('mose-element-4', MoseElement4);
|
|
203
|
+
|
|
204
|
+
const el4 = document.createElement('mose-element-4', {
|
|
205
|
+
customElementRegistry: customRegistry
|
|
206
|
+
});
|
|
207
|
+
container.appendChild(el4);
|
|
208
|
+
|
|
209
|
+
const script4 = document.createElement('script');
|
|
210
|
+
script4.type = 'mountobserver';
|
|
211
|
+
script4.textContent = JSON.stringify({
|
|
212
|
+
whereElementMatches: 'span.target-span',
|
|
213
|
+
assignGingerly: {
|
|
214
|
+
'?.dataset?.scopedRegistry': 'true'
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
el4.appendChild(script4);
|
|
218
|
+
|
|
219
|
+
setTimeout(() => {
|
|
220
|
+
const span = el4.querySelector('.target-span');
|
|
221
|
+
const hasDataset = span?.dataset?.scopedRegistry === 'true';
|
|
222
|
+
logResult(
|
|
223
|
+
'Test 4',
|
|
224
|
+
hasDataset,
|
|
225
|
+
`Scoped registry ${hasDataset ? 'works correctly' : 'failed'}`
|
|
226
|
+
);
|
|
227
|
+
}, 550);
|
|
228
|
+
} catch (error) {
|
|
229
|
+
logResult(
|
|
230
|
+
'Test 4',
|
|
231
|
+
false,
|
|
232
|
+
`Failed: ${error.message}`
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
console.log('Requirement 3 tests initiated. Results will appear above.');
|
|
237
|
+
</script>
|
|
238
|
+
</body>
|
|
239
|
+
</html>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Exclude</title>
|
|
7
|
+
<!-- #include virtual="/imports.html" -->
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<button>I am here</button>
|
|
11
|
+
|
|
12
|
+
<script type=module>
|
|
13
|
+
import { MOSE } from '../MOSE.js';
|
|
14
|
+
class MoseElement extends MOSE(HTMLElement){
|
|
15
|
+
|
|
16
|
+
}
|
|
17
|
+
customElements.define('mose-element', MoseElement);
|
|
18
|
+
</script>
|
|
19
|
+
<mose-element>
|
|
20
|
+
<script type=mountobserver src="mount-observer-script-element/tests/buttonMatch.json">
|
|
21
|
+
{
|
|
22
|
+
"?.assignOnMount?.style?.color": "red"
|
|
23
|
+
}
|
|
24
|
+
</script>
|
|
25
|
+
</mose-element>
|
|
26
|
+
<my-child-element>
|
|
27
|
+
<template shadowrootmode=open>
|
|
28
|
+
<button>You are here</button>
|
|
29
|
+
<mose-element exclude="[src='mount-observer-script-element/tests/buttonMatch.json']" id=child></mose-element>
|
|
30
|
+
</template>
|
|
31
|
+
</my-child-element>
|
|
32
|
+
</body>
|
|
33
|
+
</html>
|