mount-observer 0.0.3 → 0.0.5
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/MountObserver.js +4 -3
- package/README.md +24 -7
- package/package.json +11 -2
- package/types.d.ts +6 -6
- package/.github/workflows/CI.yml +0 -18
- package/MountObserver.ts +0 -282
- package/RootMutObs.ts +0 -37
- package/demo/test1.html +0 -28
- package/playwright.config.ts +0 -29
- package/tests/test1.html +0 -30
- package/tests/test1.spec.mjs +0 -8
- package/tests/test2.html +0 -34
- package/tests/test2.spec.mjs +0 -8
- package/tests/test3.html +0 -34
- package/tests/test3.spec.mjs +0 -8
- package/tests/test4.html +0 -31
- package/tests/test4.spec.mjs +0 -8
- package/tests/test5.html +0 -34
- package/tests/test5.spec.mjs +0 -8
- package/tests/test6.html +0 -33
- package/tests/test6.spec.mjs +0 -8
- package/tsconfig.json +0 -17
package/MountObserver.js
CHANGED
|
@@ -136,7 +136,8 @@ export class MountObserver extends EventTarget {
|
|
|
136
136
|
break;
|
|
137
137
|
case 'object':
|
|
138
138
|
if (Array.isArray(imp)) {
|
|
139
|
-
|
|
139
|
+
throw 'NI: Firefox';
|
|
140
|
+
//this.module = await import(imp[0], imp[1]);
|
|
140
141
|
}
|
|
141
142
|
break;
|
|
142
143
|
case 'function':
|
|
@@ -229,8 +230,8 @@ export class MountObserver extends EventTarget {
|
|
|
229
230
|
const els = Array.from(within.querySelectorAll(this.#selector));
|
|
230
231
|
this.#filterAndMount(els, false);
|
|
231
232
|
}
|
|
232
|
-
unobserve() {
|
|
233
|
-
|
|
233
|
+
unobserve(within) {
|
|
234
|
+
//mutationObserverLookup
|
|
234
235
|
}
|
|
235
236
|
}
|
|
236
237
|
// https://github.com/webcomponents-cg/community-protocols/issues/12#issuecomment-872415080
|
package/README.md
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
[](https://github.com/bahrus/mount-observer/actions/workflows/CI.yml)
|
|
2
|
+
[](http://badge.fury.io/js/mount-observer)
|
|
3
|
+
[](https://bundlephobia.com/result?p=mount-observer)
|
|
4
|
+
<img src="http://img.badgesize.io/https://cdn.jsdelivr.net/npm/mount-observer?compression=gzip">
|
|
5
|
+
|
|
6
|
+
|
|
1
7
|
# The MountObserver api.
|
|
2
8
|
|
|
3
9
|
Author: Bruce B. Anderson
|
|
4
10
|
|
|
5
11
|
Issues / pr's / polyfill: [mount-observer](https://github.com/bahrus/mount-observer)
|
|
6
12
|
|
|
7
|
-
Last Update: 2023-11-
|
|
13
|
+
Last Update: 2023-11-23
|
|
8
14
|
|
|
9
15
|
## Benefits of this API
|
|
10
16
|
|
|
@@ -128,13 +134,16 @@ const observer = new MountObserver({
|
|
|
128
134
|
},
|
|
129
135
|
onDismount: ...,
|
|
130
136
|
onDisconnect: ...,
|
|
137
|
+
onOutsideRootNode: ...,
|
|
131
138
|
onReconnect: ...,
|
|
132
|
-
onReconfirm:
|
|
139
|
+
onReconfirm: ...,
|
|
133
140
|
onOutOfScope: ...,
|
|
134
141
|
}
|
|
135
142
|
})
|
|
136
143
|
```
|
|
137
144
|
|
|
145
|
+
Callbacks like we see above are useful for tight coupling, and probably are unmatched in terms of performance. However, since these rules may be of interest to multiple parties, it is usesful to also provide the ability for multiple parties to subscribe to these css rules. This can be done via:
|
|
146
|
+
|
|
138
147
|
## Subscribing
|
|
139
148
|
|
|
140
149
|
Subscribing can be done via:
|
|
@@ -173,13 +182,13 @@ The moment a MountObserver instance's "observe" method is called (passing in a r
|
|
|
173
182
|
|
|
174
183
|
If an element that is in "mounted" state according to a MountObserver instance is moved from one parent DOM element to another:
|
|
175
184
|
|
|
176
|
-
1) "disconnect" event is dispatched from the MountObserver instance the moment the element is disconnected from the DOM fragment.
|
|
185
|
+
1) "disconnect" event is dispatched from the MountObserver instance the moment the mounted element is disconnected from the DOM fragment.
|
|
177
186
|
2) If/when the element is added somewhere else in the DOM tree, the mountObserver instance will dispatch event "reconnect", regardless of where. [Note: can't polyfill this very easily]
|
|
178
|
-
3) If the element is added outside the rootNode being observed, the mountObserver instance will dispatch event "outside-root-node", and the MountObserver instance will relinquish any further responsibility for this element. Ideally this would also be dispatched when the platform garbage collects the element as well after all hard references are relinquished.
|
|
179
|
-
4) If the new place it was added remains within the original rootNode and remains
|
|
180
|
-
5) If the element no longer satisfies the criteria of the MountObserver instance, the MountObserver instance will dispatch event "dismount".
|
|
187
|
+
3) If the mounted element is added outside the rootNode being observed, the mountObserver instance will dispatch event "outside-root-node", and the MountObserver instance will relinquish any further responsibility for this element. Ideally this would also be dispatched when the platform garbage collects the element as well after all hard references are relinquished.
|
|
188
|
+
4) If the new place it was added remains within the original rootNode and remains mounted, the MountObserver instance dispatches event "reconfirmed".
|
|
189
|
+
5) If the element no longer satisfies the criteria of the MountObserver instance, the MountObserver instance will dispatch event "dismount".
|
|
181
190
|
|
|
182
|
-
## Special support for attributes
|
|
191
|
+
## Special support for observable attributes
|
|
183
192
|
|
|
184
193
|
Extra support is provided for monitoring attributes.
|
|
185
194
|
|
|
@@ -201,6 +210,14 @@ Example:
|
|
|
201
210
|
});
|
|
202
211
|
mo.addEventListener('attr-change', e => {
|
|
203
212
|
console.log(e);
|
|
213
|
+
// {
|
|
214
|
+
// attrChangeInfo:{
|
|
215
|
+
// name: 'test-1',
|
|
216
|
+
// oldValue: null,
|
|
217
|
+
// newValue: 'hello'
|
|
218
|
+
// idx: 0,
|
|
219
|
+
// }
|
|
220
|
+
// }
|
|
204
221
|
});
|
|
205
222
|
mo.observe(div);
|
|
206
223
|
setTimeout(() => {
|
package/package.json
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mount-observer",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "",
|
|
3
|
+
"version": "0.0.5",
|
|
4
|
+
"description": "Observe and act on css matches.",
|
|
5
5
|
"main": "MountObserver.js",
|
|
6
6
|
"module": "MountObserver.js",
|
|
7
7
|
"devDependencies": {
|
|
8
8
|
"@playwright/test": "1.39.0",
|
|
9
9
|
"may-it-serve": "0.0.6"
|
|
10
10
|
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": "./MountObserver.js",
|
|
13
|
+
"./MountObserver.js": "./MountObserver.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"*.js",
|
|
17
|
+
"types.d.ts"
|
|
18
|
+
],
|
|
19
|
+
"types": "types.d.ts",
|
|
11
20
|
"scripts": {
|
|
12
21
|
"serve": "node node_modules/may-it-serve",
|
|
13
22
|
"test": "playwright test",
|
package/types.d.ts
CHANGED
|
@@ -7,11 +7,11 @@ export interface MountInit{
|
|
|
7
7
|
readonly whereSatisfies?: PipelineProcessor<boolean>,
|
|
8
8
|
readonly import?: ImportString | [ImportString, ImportAssertions] | PipelineProcessor,
|
|
9
9
|
readonly do?: {
|
|
10
|
-
readonly onMount
|
|
11
|
-
readonly onDismount
|
|
12
|
-
readonly onDisconnect
|
|
13
|
-
readonly onReconfirmed
|
|
14
|
-
readonly onOutsideRootNode
|
|
10
|
+
readonly onMount?: PipelineProcessor,
|
|
11
|
+
readonly onDismount?: PipelineProcessor,
|
|
12
|
+
readonly onDisconnect?: PipelineProcessor,
|
|
13
|
+
readonly onReconfirmed?: PipelineProcessor,
|
|
14
|
+
readonly onOutsideRootNode?: PipelineProcessor,
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
}
|
|
@@ -32,7 +32,7 @@ export interface MountContext {
|
|
|
32
32
|
// readonly mountedRefs: WeakRef<Element>[],
|
|
33
33
|
// readonly dismountedRefs: WeakRef<Element>[],
|
|
34
34
|
observe(within: Node): void;
|
|
35
|
-
unobserve(): void;
|
|
35
|
+
unobserve(within: Node): void;
|
|
36
36
|
module?: any;
|
|
37
37
|
}
|
|
38
38
|
|
package/.github/workflows/CI.yml
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
name: Playwright Tests
|
|
2
|
-
on: [push]
|
|
3
|
-
jobs:
|
|
4
|
-
test:
|
|
5
|
-
timeout-minutes: 10
|
|
6
|
-
runs-on: ubuntu-latest
|
|
7
|
-
steps:
|
|
8
|
-
- uses: actions/checkout@v2
|
|
9
|
-
- uses: actions/setup-node@v2
|
|
10
|
-
with:
|
|
11
|
-
node-version: '16.x'
|
|
12
|
-
- name: Install dependencies
|
|
13
|
-
run: npm ci
|
|
14
|
-
- name: Install Playwright
|
|
15
|
-
run: npx playwright install --with-deps
|
|
16
|
-
- name: Run Playwright tests
|
|
17
|
-
run: npm run test
|
|
18
|
-
|
package/MountObserver.ts
DELETED
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
import {MountInit, MountContext, AddMutationEventListener,
|
|
2
|
-
MutationEvent, dismountEventName, mountEventName, IMountEvent, IDismountEvent,
|
|
3
|
-
disconnectedEventName, IDisconnectEvent, IAttrChangeEvent, attrChangeEventName, AttrChangeInfo
|
|
4
|
-
} from './types';
|
|
5
|
-
import {RootMutObs} from './RootMutObs.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const mutationObserverLookup = new WeakMap<Node, RootMutObs>();
|
|
9
|
-
export class MountObserver extends EventTarget implements MountContext{
|
|
10
|
-
|
|
11
|
-
#mountInit: MountInit;
|
|
12
|
-
#rootMutObs: RootMutObs | undefined;
|
|
13
|
-
#abortController: AbortController;
|
|
14
|
-
#mounted: WeakSet<Element>;
|
|
15
|
-
#mountedList: Array<WeakRef<Element>> | undefined;
|
|
16
|
-
#disconnected: WeakSet<Element>;
|
|
17
|
-
//#unmounted: WeakSet<Element>;
|
|
18
|
-
#isComplex: boolean;
|
|
19
|
-
|
|
20
|
-
constructor(init: MountInit){
|
|
21
|
-
super();
|
|
22
|
-
const {match, whereElementIntersectsWith, whereMediaMatches} = init;
|
|
23
|
-
let isComplex = false;
|
|
24
|
-
if(match !== undefined){
|
|
25
|
-
const reducedMatch = match.replaceAll(':not(', '');
|
|
26
|
-
isComplex = reducedMatch.includes(' ') || reducedMatch.includes(':');
|
|
27
|
-
}
|
|
28
|
-
this.#isComplex = isComplex;
|
|
29
|
-
if(whereElementIntersectsWith || whereMediaMatches) throw 'NI'; //not implemented
|
|
30
|
-
this.#mountInit = init;
|
|
31
|
-
this.#abortController = new AbortController();
|
|
32
|
-
this.#mounted = new WeakSet();
|
|
33
|
-
this.#disconnected = new WeakSet();
|
|
34
|
-
//this.#unmounted = new WeakSet();
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
#calculatedSelector: string | undefined;
|
|
38
|
-
get #selector(){
|
|
39
|
-
if(this.#calculatedSelector !== undefined) return this.#calculatedSelector;
|
|
40
|
-
const {match, attribMatches} = this.#mountInit;
|
|
41
|
-
const base = match || '*';
|
|
42
|
-
if(attribMatches === undefined) return base;
|
|
43
|
-
const matches: Array<string> = [];
|
|
44
|
-
attribMatches.forEach(x => {
|
|
45
|
-
const {names} = x;
|
|
46
|
-
names.forEach(y => {
|
|
47
|
-
matches.push(`${base}[${y}]`)
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
this.#calculatedSelector = matches.join(',');
|
|
51
|
-
return this.#calculatedSelector;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
async observe(within: Node){
|
|
55
|
-
const nodeToMonitor = this.#isComplex ? (within instanceof ShadowRoot ? within : within.getRootNode()) : within;
|
|
56
|
-
if(!mutationObserverLookup.has(nodeToMonitor)){
|
|
57
|
-
mutationObserverLookup.set(nodeToMonitor, new RootMutObs(nodeToMonitor));
|
|
58
|
-
}
|
|
59
|
-
const rootMutObs = mutationObserverLookup.get(within)!;
|
|
60
|
-
const {attribMatches} = this.#mountInit;
|
|
61
|
-
(rootMutObs as any as AddMutationEventListener).addEventListener('mutation-event', (e: MutationEvent) => {
|
|
62
|
-
//TODO: disconnected
|
|
63
|
-
if(this.#isComplex){
|
|
64
|
-
this.#inspectWithin(within);
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
const {mutationRecords} = e;
|
|
68
|
-
const elsToInspect: Array<Element> = [];
|
|
69
|
-
//const elsToDisconnect: Array<Element> = [];
|
|
70
|
-
const doDisconnect = this.#mountInit.do?.onDisconnect;
|
|
71
|
-
for(const mutationRecord of mutationRecords){
|
|
72
|
-
const {addedNodes, type, removedNodes} = mutationRecord;
|
|
73
|
-
//console.log(mutationRecord);
|
|
74
|
-
const addedElements = Array.from(addedNodes).filter(x => x instanceof Element) as Array<Element>;
|
|
75
|
-
addedElements.forEach(x => elsToInspect.push(x));
|
|
76
|
-
if(type === 'attributes'){
|
|
77
|
-
const {target, attributeName, oldValue} = mutationRecord;
|
|
78
|
-
if(target instanceof Element && attributeName !== null && attribMatches !== undefined && this.#mounted.has(target)){
|
|
79
|
-
let idx = 0;
|
|
80
|
-
for(const attrMatch of attribMatches){
|
|
81
|
-
const {names} = attrMatch;
|
|
82
|
-
if(names.includes(attributeName)){
|
|
83
|
-
const newValue = target.getAttribute(attributeName);
|
|
84
|
-
// let parsedNewValue = undefined;
|
|
85
|
-
// switch(type){
|
|
86
|
-
// case 'boolean':
|
|
87
|
-
// parsedNewValue = newValue === 'true' ? true : newValue === 'false' ? false : null;
|
|
88
|
-
// break;
|
|
89
|
-
// case 'date':
|
|
90
|
-
// parsedNewValue = newValue === null ? null : new Date(newValue);
|
|
91
|
-
// break;
|
|
92
|
-
// case ''
|
|
93
|
-
|
|
94
|
-
// }
|
|
95
|
-
const attrChangeInfo: AttrChangeInfo = {
|
|
96
|
-
name: attributeName,
|
|
97
|
-
oldValue,
|
|
98
|
-
newValue,
|
|
99
|
-
idx
|
|
100
|
-
};
|
|
101
|
-
this.dispatchEvent(new AttrChangeEvent(target, attrChangeInfo));
|
|
102
|
-
}
|
|
103
|
-
idx++;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
elsToInspect.push(target as Element);
|
|
107
|
-
}
|
|
108
|
-
const deletedElements = Array.from(removedNodes).filter(x => x instanceof Element) as Array<Element>;
|
|
109
|
-
for(const deletedElement of deletedElements){
|
|
110
|
-
// if(!this.#mounted.has(deletedElement)) continue;
|
|
111
|
-
// this.#mounted.delete(deletedElement);
|
|
112
|
-
// this.#mountedList = this.#mountedList?.filter(x => x.deref() !== deletedElement);
|
|
113
|
-
this.#disconnected.add(deletedElement);
|
|
114
|
-
if(doDisconnect !== undefined){
|
|
115
|
-
doDisconnect(deletedElement, this);
|
|
116
|
-
}
|
|
117
|
-
this.dispatchEvent(new DisconnectEvent(deletedElement));
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
this.#filterAndMount(elsToInspect, true);
|
|
122
|
-
}, {signal: this.#abortController.signal});
|
|
123
|
-
await this.#inspectWithin(within);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
#confirmInstanceOf(el: Element, whereInstanceOf: Array<typeof Node>){
|
|
127
|
-
for(const test of whereInstanceOf){
|
|
128
|
-
if(el instanceof test) return true;
|
|
129
|
-
}
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
async #mount(matching: Array<Element>){
|
|
134
|
-
//first unmount non matching
|
|
135
|
-
const alreadyMounted = this.#filterAndDismount();
|
|
136
|
-
const onMount = this.#mountInit.do?.onMount;
|
|
137
|
-
const {import: imp, attribMatches} = this.#mountInit;
|
|
138
|
-
for(const match of matching){
|
|
139
|
-
if(alreadyMounted.has(match)) continue;
|
|
140
|
-
this.#mounted.add(match);
|
|
141
|
-
if(imp !== undefined){
|
|
142
|
-
switch(typeof imp){
|
|
143
|
-
case 'string':
|
|
144
|
-
this.module = await import(imp);
|
|
145
|
-
break;
|
|
146
|
-
case 'object':
|
|
147
|
-
if(Array.isArray(imp)){
|
|
148
|
-
this.module = await import(imp[0], imp[1]);
|
|
149
|
-
}
|
|
150
|
-
break;
|
|
151
|
-
case 'function':
|
|
152
|
-
this.module = await imp(match, this, 'Import');
|
|
153
|
-
break;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
if(onMount !== undefined) onMount(match, this, 'PostImport');
|
|
157
|
-
this.dispatchEvent(new MountEvent(match));
|
|
158
|
-
if(attribMatches !== undefined){
|
|
159
|
-
let idx = 0;
|
|
160
|
-
for(const attribMatch of attribMatches){
|
|
161
|
-
let newValue = null;
|
|
162
|
-
const {names} = attribMatch;
|
|
163
|
-
let nonNullName = names[0];
|
|
164
|
-
for(const name of names){
|
|
165
|
-
const attrVal = match.getAttribute(name);
|
|
166
|
-
if(attrVal !== null) nonNullName = name;
|
|
167
|
-
newValue = newValue || attrVal;
|
|
168
|
-
}
|
|
169
|
-
const attribInfo: AttrChangeInfo = {
|
|
170
|
-
oldValue: null,
|
|
171
|
-
newValue,
|
|
172
|
-
idx,
|
|
173
|
-
name: nonNullName
|
|
174
|
-
};
|
|
175
|
-
this.dispatchEvent(new AttrChangeEvent(match, attribInfo));
|
|
176
|
-
idx++;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
this.#mountedList?.push(new WeakRef(match));
|
|
180
|
-
//if(this.#unmounted.has(match)) this.#unmounted.delete(match);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
async #dismount(unmatching: Array<Element>){
|
|
185
|
-
const onDismount = this.#mountInit.do?.onDismount
|
|
186
|
-
for(const unmatch of unmatching){
|
|
187
|
-
if(onDismount !== undefined){
|
|
188
|
-
onDismount(unmatch, this);
|
|
189
|
-
}
|
|
190
|
-
this.dispatchEvent(new DismountEvent(unmatch));
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
#filterAndDismount(): Set<Element>{
|
|
195
|
-
const returnSet = new Set<Element>();
|
|
196
|
-
if(this.#mountedList !== undefined){
|
|
197
|
-
const previouslyMounted = this.#mountedList.map(x => x.deref());
|
|
198
|
-
const {whereSatisfies, whereInstanceOf} = this.#mountInit;
|
|
199
|
-
const match = this.#selector;
|
|
200
|
-
const elsToUnMount = previouslyMounted.filter(x => {
|
|
201
|
-
if(x === undefined) return false;
|
|
202
|
-
if(!x.matches(match)) return true;
|
|
203
|
-
if(whereSatisfies !== undefined){
|
|
204
|
-
if(!whereSatisfies(x, this, 'Inspecting')) return true;
|
|
205
|
-
}
|
|
206
|
-
returnSet.add(x);
|
|
207
|
-
return false;
|
|
208
|
-
}) as Array<Element>;
|
|
209
|
-
this.#dismount(elsToUnMount);
|
|
210
|
-
}
|
|
211
|
-
this.#mountedList = Array.from(returnSet).map(x => new WeakRef(x));
|
|
212
|
-
return returnSet;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
async #filterAndMount(els: Array<Element>, checkMatch: boolean){
|
|
216
|
-
const {whereSatisfies, whereInstanceOf} = this.#mountInit;
|
|
217
|
-
const match = this.#selector;
|
|
218
|
-
const elsToMount = els.filter(x => {
|
|
219
|
-
if(checkMatch){
|
|
220
|
-
if(!x.matches(match)) return false;
|
|
221
|
-
}
|
|
222
|
-
if(whereSatisfies !== undefined){
|
|
223
|
-
if(!whereSatisfies(x, this, 'Inspecting')) return false;
|
|
224
|
-
}
|
|
225
|
-
if(whereInstanceOf !== undefined){
|
|
226
|
-
if(!this.#confirmInstanceOf(x, whereInstanceOf)) return false;
|
|
227
|
-
}
|
|
228
|
-
return true;
|
|
229
|
-
});
|
|
230
|
-
this.#mount(elsToMount);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
async #inspectWithin(within: Node){
|
|
234
|
-
const els = Array.from((within as Element).querySelectorAll(this.#selector));
|
|
235
|
-
this.#filterAndMount(els, false);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
unobserve(){
|
|
239
|
-
throw 'NI';
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
export interface MountObserver extends MountContext{}
|
|
245
|
-
|
|
246
|
-
// https://github.com/webcomponents-cg/community-protocols/issues/12#issuecomment-872415080
|
|
247
|
-
/**
|
|
248
|
-
* The `mutation-event` event represents something that happened.
|
|
249
|
-
* We can document it here.
|
|
250
|
-
*/
|
|
251
|
-
export class MountEvent extends Event implements IMountEvent {
|
|
252
|
-
static eventName: mountEventName = 'mount';
|
|
253
|
-
|
|
254
|
-
constructor(public mountedElement: Element) {
|
|
255
|
-
super(MountEvent.eventName);
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
export class DismountEvent extends Event implements IDismountEvent{
|
|
261
|
-
static eventName: dismountEventName = 'dismount';
|
|
262
|
-
|
|
263
|
-
constructor(public dismountedElement: Element){
|
|
264
|
-
super(DismountEvent.eventName)
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
export class DisconnectEvent extends Event implements IDisconnectEvent{
|
|
269
|
-
static eventName: disconnectedEventName = 'disconnect';
|
|
270
|
-
|
|
271
|
-
constructor(public disconnectedElement: Element){
|
|
272
|
-
super(DisconnectEvent.eventName);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
export class AttrChangeEvent extends Event implements IAttrChangeEvent{
|
|
277
|
-
static eventName: attrChangeEventName = 'attr-change';
|
|
278
|
-
constructor(public mountedElement: Element, public attrChangeInfo: AttrChangeInfo){
|
|
279
|
-
super(AttrChangeEvent.eventName);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
package/RootMutObs.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import {mutationEventName, AddMutationEventListener} from './types';
|
|
2
|
-
|
|
3
|
-
export class RootMutObs extends EventTarget{
|
|
4
|
-
constructor(rootNode: Node ){
|
|
5
|
-
super();
|
|
6
|
-
this.#mutationObserver = new MutationObserver(mutationRecords => {
|
|
7
|
-
this.dispatchEvent(new MutationEvent(mutationRecords))
|
|
8
|
-
})
|
|
9
|
-
this.#mutationObserver.observe(rootNode, {
|
|
10
|
-
subtree: true,
|
|
11
|
-
childList: true,
|
|
12
|
-
attributes: true,
|
|
13
|
-
attributeOldValue: true,
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
#mutationObserver: MutationObserver;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// https://github.com/webcomponents-cg/community-protocols/issues/12#issuecomment-872415080
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* The `mutation-event` event represents something that happened.
|
|
25
|
-
* We can document it here.
|
|
26
|
-
*/
|
|
27
|
-
export class MutationEvent extends Event implements MutationEvent {
|
|
28
|
-
static eventName: mutationEventName = 'mutation-event';
|
|
29
|
-
|
|
30
|
-
constructor(public mutationRecords: Array<MutationRecord>) {
|
|
31
|
-
// Since these are hard-coded, dispatchers can't get them wrong
|
|
32
|
-
super(MutationEvent.eventName);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
package/demo/test1.html
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
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>Document</title>
|
|
7
|
-
</head>
|
|
8
|
-
<body>
|
|
9
|
-
<div id=div>
|
|
10
|
-
<span id=span></span>
|
|
11
|
-
</div>
|
|
12
|
-
<script type=module>
|
|
13
|
-
import {MountObserver} from '../MountObserver.js';
|
|
14
|
-
const mo = new MountObserver({
|
|
15
|
-
match: '#span',
|
|
16
|
-
do:{
|
|
17
|
-
onMount: (el, ctx) => {
|
|
18
|
-
console.log({el, ctx});
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
mo.addEventListener('mount', e => {
|
|
23
|
-
console.log(e);
|
|
24
|
-
});
|
|
25
|
-
mo.observe(div);
|
|
26
|
-
</script>
|
|
27
|
-
</body>
|
|
28
|
-
</html>
|
package/playwright.config.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// playwright.config.ts
|
|
2
|
-
import { PlaywrightTestConfig, devices } from '@playwright/test';
|
|
3
|
-
const config: PlaywrightTestConfig = {
|
|
4
|
-
webServer: {
|
|
5
|
-
command: 'npm run serve',
|
|
6
|
-
url: 'http://localhost:3030/',
|
|
7
|
-
timeout: 120 * 1000,
|
|
8
|
-
reuseExistingServer: !process.env.CI,
|
|
9
|
-
},
|
|
10
|
-
use: {
|
|
11
|
-
baseURL: 'http://localhost:3030/',
|
|
12
|
-
},
|
|
13
|
-
projects: [
|
|
14
|
-
{
|
|
15
|
-
name: 'chromium',
|
|
16
|
-
use: { ...devices['Desktop Chrome'] },
|
|
17
|
-
},
|
|
18
|
-
//firefox lacks support for import assertions
|
|
19
|
-
// {
|
|
20
|
-
// name: 'firefox',
|
|
21
|
-
// use: { ...devices['Desktop Firefox'] },
|
|
22
|
-
// },
|
|
23
|
-
{
|
|
24
|
-
name: 'webkit',
|
|
25
|
-
use: { ...devices['Desktop Safari'] },
|
|
26
|
-
},
|
|
27
|
-
],
|
|
28
|
-
};
|
|
29
|
-
export default config;
|
package/tests/test1.html
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
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>Document</title>
|
|
7
|
-
</head>
|
|
8
|
-
<body>
|
|
9
|
-
<div id=div>
|
|
10
|
-
<span id=span></span>
|
|
11
|
-
</div>
|
|
12
|
-
<div id=target></div>
|
|
13
|
-
<script type=module>
|
|
14
|
-
import {MountObserver} from '../MountObserver.js';
|
|
15
|
-
const mo = new MountObserver({
|
|
16
|
-
match: '#span',
|
|
17
|
-
do:{
|
|
18
|
-
onMount: (el, ctx) => {
|
|
19
|
-
console.log({el, ctx});
|
|
20
|
-
target.setAttribute('mark', 'good');
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
mo.addEventListener('mount', e => {
|
|
25
|
-
console.log(e);
|
|
26
|
-
});
|
|
27
|
-
mo.observe(div);
|
|
28
|
-
</script>
|
|
29
|
-
</body>
|
|
30
|
-
</html>
|
package/tests/test1.spec.mjs
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { test, expect } from '@playwright/test';
|
|
2
|
-
test('test1', async ({ page }) => {
|
|
3
|
-
await page.goto('./tests/test1.html');
|
|
4
|
-
// wait for 1 second
|
|
5
|
-
await page.waitForTimeout(1000);
|
|
6
|
-
const editor = page.locator('#target');
|
|
7
|
-
await expect(editor).toHaveAttribute('mark', 'good');
|
|
8
|
-
});
|
package/tests/test2.html
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
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>Document</title>
|
|
7
|
-
</head>
|
|
8
|
-
<body>
|
|
9
|
-
<div id=div>
|
|
10
|
-
</div>
|
|
11
|
-
<div id=target></div>
|
|
12
|
-
<script type=module>
|
|
13
|
-
import {MountObserver} from '../MountObserver.js';
|
|
14
|
-
const mo = new MountObserver({
|
|
15
|
-
match: '#span',
|
|
16
|
-
do:{
|
|
17
|
-
onMount: (el, ctx) => {
|
|
18
|
-
console.log({el, ctx});
|
|
19
|
-
target.setAttribute('mark', 'good');
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
mo.addEventListener('mount', e => {
|
|
24
|
-
console.log(e);
|
|
25
|
-
});
|
|
26
|
-
mo.observe(div);
|
|
27
|
-
setTimeout(() => {
|
|
28
|
-
const span = document.createElement('span');
|
|
29
|
-
span.id = 'span';
|
|
30
|
-
div.appendChild(span);
|
|
31
|
-
}, 500);
|
|
32
|
-
</script>
|
|
33
|
-
</body>
|
|
34
|
-
</html>
|
package/tests/test2.spec.mjs
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { test, expect } from '@playwright/test';
|
|
2
|
-
test('test1', async ({ page }) => {
|
|
3
|
-
await page.goto('./tests/test2.html');
|
|
4
|
-
// wait for 1 second
|
|
5
|
-
await page.waitForTimeout(1000);
|
|
6
|
-
const editor = page.locator('#target');
|
|
7
|
-
await expect(editor).toHaveAttribute('mark', 'good');
|
|
8
|
-
});
|
package/tests/test3.html
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
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>Document</title>
|
|
7
|
-
</head>
|
|
8
|
-
<body>
|
|
9
|
-
<div id=div>
|
|
10
|
-
<span></span>
|
|
11
|
-
</div>
|
|
12
|
-
<div id=target></div>
|
|
13
|
-
<script type=module>
|
|
14
|
-
import {MountObserver} from '../MountObserver.js';
|
|
15
|
-
const mo = new MountObserver({
|
|
16
|
-
match: '#span',
|
|
17
|
-
do:{
|
|
18
|
-
onMount: (el, ctx) => {
|
|
19
|
-
console.log({el, ctx});
|
|
20
|
-
target.setAttribute('mark', 'good');
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
mo.addEventListener('mount', e => {
|
|
25
|
-
console.log(e);
|
|
26
|
-
});
|
|
27
|
-
mo.observe(div);
|
|
28
|
-
setTimeout(() => {
|
|
29
|
-
const span = div.querySelector('span');
|
|
30
|
-
span.id = 'span';
|
|
31
|
-
}, 500);
|
|
32
|
-
</script>
|
|
33
|
-
</body>
|
|
34
|
-
</html>
|
package/tests/test3.spec.mjs
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { test, expect } from '@playwright/test';
|
|
2
|
-
test('test1', async ({ page }) => {
|
|
3
|
-
await page.goto('./tests/test3.html');
|
|
4
|
-
// wait for 1 second
|
|
5
|
-
await page.waitForTimeout(1000);
|
|
6
|
-
const editor = page.locator('#target');
|
|
7
|
-
await expect(editor).toHaveAttribute('mark', 'good');
|
|
8
|
-
});
|
package/tests/test4.html
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
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>Document</title>
|
|
7
|
-
</head>
|
|
8
|
-
<body>
|
|
9
|
-
<div id=div>
|
|
10
|
-
<span id=span></span>
|
|
11
|
-
</div>
|
|
12
|
-
<div id=target></div>
|
|
13
|
-
<script type=module>
|
|
14
|
-
import {MountObserver} from '../MountObserver.js';
|
|
15
|
-
const mo = new MountObserver({
|
|
16
|
-
match: '#span',
|
|
17
|
-
do:{
|
|
18
|
-
onMount: (el, ctx) => {
|
|
19
|
-
console.log({el, ctx});
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
mo.addEventListener('mount', e => {
|
|
25
|
-
console.log(e);
|
|
26
|
-
target.setAttribute('mark', 'good');
|
|
27
|
-
});
|
|
28
|
-
mo.observe(div);
|
|
29
|
-
</script>
|
|
30
|
-
</body>
|
|
31
|
-
</html>
|
package/tests/test4.spec.mjs
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { test, expect } from '@playwright/test';
|
|
2
|
-
test('test1', async ({ page }) => {
|
|
3
|
-
await page.goto('./tests/test4.html');
|
|
4
|
-
// wait for 1 second
|
|
5
|
-
await page.waitForTimeout(1000);
|
|
6
|
-
const editor = page.locator('#target');
|
|
7
|
-
await expect(editor).toHaveAttribute('mark', 'good');
|
|
8
|
-
});
|
package/tests/test5.html
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
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>Document</title>
|
|
7
|
-
</head>
|
|
8
|
-
<body>
|
|
9
|
-
<div id=div>
|
|
10
|
-
<span id=span></span>
|
|
11
|
-
</div>
|
|
12
|
-
<div id=target></div>
|
|
13
|
-
<script type=module>
|
|
14
|
-
import {MountObserver} from '../MountObserver.js';
|
|
15
|
-
const mo = new MountObserver({
|
|
16
|
-
match: '#span',
|
|
17
|
-
do:{
|
|
18
|
-
onDisconnect: (el, ctx) => {
|
|
19
|
-
console.log({el, ctx});
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
mo.addEventListener('disconnect', e => {
|
|
25
|
-
console.log(e);
|
|
26
|
-
target.setAttribute('mark', 'good');
|
|
27
|
-
});
|
|
28
|
-
mo.observe(div);
|
|
29
|
-
setTimeout(() => {
|
|
30
|
-
span.remove();
|
|
31
|
-
}, 1000);
|
|
32
|
-
</script>
|
|
33
|
-
</body>
|
|
34
|
-
</html>
|
package/tests/test5.spec.mjs
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { test, expect } from '@playwright/test';
|
|
2
|
-
test('test1', async ({ page }) => {
|
|
3
|
-
await page.goto('./tests/test5.html');
|
|
4
|
-
// wait for 1 second
|
|
5
|
-
await page.waitForTimeout(1000);
|
|
6
|
-
const editor = page.locator('#target');
|
|
7
|
-
await expect(editor).toHaveAttribute('mark', 'good');
|
|
8
|
-
});
|
package/tests/test6.html
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
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>Document</title>
|
|
7
|
-
</head>
|
|
8
|
-
<body>
|
|
9
|
-
<div id=div>
|
|
10
|
-
<span id=span></span>
|
|
11
|
-
</div>
|
|
12
|
-
<div id=target></div>
|
|
13
|
-
<script type=module>
|
|
14
|
-
import {MountObserver} from '../MountObserver.js';
|
|
15
|
-
const mo = new MountObserver({
|
|
16
|
-
match: '#span',
|
|
17
|
-
attribMatches:[
|
|
18
|
-
{
|
|
19
|
-
names: ['test-1']
|
|
20
|
-
}
|
|
21
|
-
]
|
|
22
|
-
});
|
|
23
|
-
mo.addEventListener('attr-change', e => {
|
|
24
|
-
console.log(e);
|
|
25
|
-
target.setAttribute('mark', 'good');
|
|
26
|
-
});
|
|
27
|
-
mo.observe(div);
|
|
28
|
-
setTimeout(() => {
|
|
29
|
-
span.setAttribute('test-1', 'hello')
|
|
30
|
-
}, 1000);
|
|
31
|
-
</script>
|
|
32
|
-
</body>
|
|
33
|
-
</html>
|
package/tests/test6.spec.mjs
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { test, expect } from '@playwright/test';
|
|
2
|
-
test('test1', async ({ page }) => {
|
|
3
|
-
await page.goto('./tests/test6.html');
|
|
4
|
-
// wait for 1 second
|
|
5
|
-
await page.waitForTimeout(1000);
|
|
6
|
-
const editor = page.locator('#target');
|
|
7
|
-
await expect(editor).toHaveAttribute('mark', 'good');
|
|
8
|
-
});
|
package/tsconfig.json
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ESNext",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"sourceMap": false,
|
|
6
|
-
"experimentalDecorators": false,
|
|
7
|
-
"newLine": "LF",
|
|
8
|
-
"strict": true,
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"MountObserver.ts",
|
|
12
|
-
],
|
|
13
|
-
"exclude":[
|
|
14
|
-
"node_modules"
|
|
15
|
-
]
|
|
16
|
-
|
|
17
|
-
}
|