mount-observer 0.0.1 → 0.0.3
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 +92 -8
- package/MountObserver.ts +89 -9
- package/README.md +32 -2
- package/RootMutObs.js +1 -0
- package/RootMutObs.ts +1 -0
- package/package.json +1 -1
- package/tests/test6.html +33 -0
- package/tests/test6.spec.mjs +8 -0
- package/types.d.ts +33 -5
package/MountObserver.js
CHANGED
|
@@ -12,7 +12,12 @@ export class MountObserver extends EventTarget {
|
|
|
12
12
|
constructor(init) {
|
|
13
13
|
super();
|
|
14
14
|
const { match, whereElementIntersectsWith, whereMediaMatches } = init;
|
|
15
|
-
|
|
15
|
+
let isComplex = false;
|
|
16
|
+
if (match !== undefined) {
|
|
17
|
+
const reducedMatch = match.replaceAll(':not(', '');
|
|
18
|
+
isComplex = reducedMatch.includes(' ') || reducedMatch.includes(':');
|
|
19
|
+
}
|
|
20
|
+
this.#isComplex = isComplex;
|
|
16
21
|
if (whereElementIntersectsWith || whereMediaMatches)
|
|
17
22
|
throw 'NI'; //not implemented
|
|
18
23
|
this.#mountInit = init;
|
|
@@ -21,12 +26,31 @@ export class MountObserver extends EventTarget {
|
|
|
21
26
|
this.#disconnected = new WeakSet();
|
|
22
27
|
//this.#unmounted = new WeakSet();
|
|
23
28
|
}
|
|
29
|
+
#calculatedSelector;
|
|
30
|
+
get #selector() {
|
|
31
|
+
if (this.#calculatedSelector !== undefined)
|
|
32
|
+
return this.#calculatedSelector;
|
|
33
|
+
const { match, attribMatches } = this.#mountInit;
|
|
34
|
+
const base = match || '*';
|
|
35
|
+
if (attribMatches === undefined)
|
|
36
|
+
return base;
|
|
37
|
+
const matches = [];
|
|
38
|
+
attribMatches.forEach(x => {
|
|
39
|
+
const { names } = x;
|
|
40
|
+
names.forEach(y => {
|
|
41
|
+
matches.push(`${base}[${y}]`);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
this.#calculatedSelector = matches.join(',');
|
|
45
|
+
return this.#calculatedSelector;
|
|
46
|
+
}
|
|
24
47
|
async observe(within) {
|
|
25
48
|
const nodeToMonitor = this.#isComplex ? (within instanceof ShadowRoot ? within : within.getRootNode()) : within;
|
|
26
49
|
if (!mutationObserverLookup.has(nodeToMonitor)) {
|
|
27
50
|
mutationObserverLookup.set(nodeToMonitor, new RootMutObs(nodeToMonitor));
|
|
28
51
|
}
|
|
29
52
|
const rootMutObs = mutationObserverLookup.get(within);
|
|
53
|
+
const { attribMatches } = this.#mountInit;
|
|
30
54
|
rootMutObs.addEventListener('mutation-event', (e) => {
|
|
31
55
|
//TODO: disconnected
|
|
32
56
|
if (this.#isComplex) {
|
|
@@ -39,11 +63,38 @@ export class MountObserver extends EventTarget {
|
|
|
39
63
|
const doDisconnect = this.#mountInit.do?.onDisconnect;
|
|
40
64
|
for (const mutationRecord of mutationRecords) {
|
|
41
65
|
const { addedNodes, type, removedNodes } = mutationRecord;
|
|
42
|
-
//console.log(
|
|
66
|
+
//console.log(mutationRecord);
|
|
43
67
|
const addedElements = Array.from(addedNodes).filter(x => x instanceof Element);
|
|
44
68
|
addedElements.forEach(x => elsToInspect.push(x));
|
|
45
69
|
if (type === 'attributes') {
|
|
46
|
-
const { target } = mutationRecord;
|
|
70
|
+
const { target, attributeName, oldValue } = mutationRecord;
|
|
71
|
+
if (target instanceof Element && attributeName !== null && attribMatches !== undefined && this.#mounted.has(target)) {
|
|
72
|
+
let idx = 0;
|
|
73
|
+
for (const attrMatch of attribMatches) {
|
|
74
|
+
const { names } = attrMatch;
|
|
75
|
+
if (names.includes(attributeName)) {
|
|
76
|
+
const newValue = target.getAttribute(attributeName);
|
|
77
|
+
// let parsedNewValue = undefined;
|
|
78
|
+
// switch(type){
|
|
79
|
+
// case 'boolean':
|
|
80
|
+
// parsedNewValue = newValue === 'true' ? true : newValue === 'false' ? false : null;
|
|
81
|
+
// break;
|
|
82
|
+
// case 'date':
|
|
83
|
+
// parsedNewValue = newValue === null ? null : new Date(newValue);
|
|
84
|
+
// break;
|
|
85
|
+
// case ''
|
|
86
|
+
// }
|
|
87
|
+
const attrChangeInfo = {
|
|
88
|
+
name: attributeName,
|
|
89
|
+
oldValue,
|
|
90
|
+
newValue,
|
|
91
|
+
idx
|
|
92
|
+
};
|
|
93
|
+
this.dispatchEvent(new AttrChangeEvent(target, attrChangeInfo));
|
|
94
|
+
}
|
|
95
|
+
idx++;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
47
98
|
elsToInspect.push(target);
|
|
48
99
|
}
|
|
49
100
|
const deletedElements = Array.from(removedNodes).filter(x => x instanceof Element);
|
|
@@ -73,7 +124,7 @@ export class MountObserver extends EventTarget {
|
|
|
73
124
|
//first unmount non matching
|
|
74
125
|
const alreadyMounted = this.#filterAndDismount();
|
|
75
126
|
const onMount = this.#mountInit.do?.onMount;
|
|
76
|
-
const imp = this.#mountInit
|
|
127
|
+
const { import: imp, attribMatches } = this.#mountInit;
|
|
77
128
|
for (const match of matching) {
|
|
78
129
|
if (alreadyMounted.has(match))
|
|
79
130
|
continue;
|
|
@@ -96,6 +147,28 @@ export class MountObserver extends EventTarget {
|
|
|
96
147
|
if (onMount !== undefined)
|
|
97
148
|
onMount(match, this, 'PostImport');
|
|
98
149
|
this.dispatchEvent(new MountEvent(match));
|
|
150
|
+
if (attribMatches !== undefined) {
|
|
151
|
+
let idx = 0;
|
|
152
|
+
for (const attribMatch of attribMatches) {
|
|
153
|
+
let newValue = null;
|
|
154
|
+
const { names } = attribMatch;
|
|
155
|
+
let nonNullName = names[0];
|
|
156
|
+
for (const name of names) {
|
|
157
|
+
const attrVal = match.getAttribute(name);
|
|
158
|
+
if (attrVal !== null)
|
|
159
|
+
nonNullName = name;
|
|
160
|
+
newValue = newValue || attrVal;
|
|
161
|
+
}
|
|
162
|
+
const attribInfo = {
|
|
163
|
+
oldValue: null,
|
|
164
|
+
newValue,
|
|
165
|
+
idx,
|
|
166
|
+
name: nonNullName
|
|
167
|
+
};
|
|
168
|
+
this.dispatchEvent(new AttrChangeEvent(match, attribInfo));
|
|
169
|
+
idx++;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
99
172
|
this.#mountedList?.push(new WeakRef(match));
|
|
100
173
|
//if(this.#unmounted.has(match)) this.#unmounted.delete(match);
|
|
101
174
|
}
|
|
@@ -113,7 +186,8 @@ export class MountObserver extends EventTarget {
|
|
|
113
186
|
const returnSet = new Set();
|
|
114
187
|
if (this.#mountedList !== undefined) {
|
|
115
188
|
const previouslyMounted = this.#mountedList.map(x => x.deref());
|
|
116
|
-
const {
|
|
189
|
+
const { whereSatisfies, whereInstanceOf } = this.#mountInit;
|
|
190
|
+
const match = this.#selector;
|
|
117
191
|
const elsToUnMount = previouslyMounted.filter(x => {
|
|
118
192
|
if (x === undefined)
|
|
119
193
|
return false;
|
|
@@ -132,7 +206,8 @@ export class MountObserver extends EventTarget {
|
|
|
132
206
|
return returnSet;
|
|
133
207
|
}
|
|
134
208
|
async #filterAndMount(els, checkMatch) {
|
|
135
|
-
const {
|
|
209
|
+
const { whereSatisfies, whereInstanceOf } = this.#mountInit;
|
|
210
|
+
const match = this.#selector;
|
|
136
211
|
const elsToMount = els.filter(x => {
|
|
137
212
|
if (checkMatch) {
|
|
138
213
|
if (!x.matches(match))
|
|
@@ -151,8 +226,7 @@ export class MountObserver extends EventTarget {
|
|
|
151
226
|
this.#mount(elsToMount);
|
|
152
227
|
}
|
|
153
228
|
async #inspectWithin(within) {
|
|
154
|
-
const
|
|
155
|
-
const els = Array.from(within.querySelectorAll(match));
|
|
229
|
+
const els = Array.from(within.querySelectorAll(this.#selector));
|
|
156
230
|
this.#filterAndMount(els, false);
|
|
157
231
|
}
|
|
158
232
|
unobserve() {
|
|
@@ -188,3 +262,13 @@ export class DisconnectEvent extends Event {
|
|
|
188
262
|
this.disconnectedElement = disconnectedElement;
|
|
189
263
|
}
|
|
190
264
|
}
|
|
265
|
+
export class AttrChangeEvent extends Event {
|
|
266
|
+
mountedElement;
|
|
267
|
+
attrChangeInfo;
|
|
268
|
+
static eventName = 'attr-change';
|
|
269
|
+
constructor(mountedElement, attrChangeInfo) {
|
|
270
|
+
super(AttrChangeEvent.eventName);
|
|
271
|
+
this.mountedElement = mountedElement;
|
|
272
|
+
this.attrChangeInfo = attrChangeInfo;
|
|
273
|
+
}
|
|
274
|
+
}
|
package/MountObserver.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {MountInit, MountContext, AddMutationEventListener,
|
|
2
2
|
MutationEvent, dismountEventName, mountEventName, IMountEvent, IDismountEvent,
|
|
3
|
-
disconnectedEventName, IDisconnectEvent
|
|
3
|
+
disconnectedEventName, IDisconnectEvent, IAttrChangeEvent, attrChangeEventName, AttrChangeInfo
|
|
4
4
|
} from './types';
|
|
5
5
|
import {RootMutObs} from './RootMutObs.js';
|
|
6
6
|
|
|
@@ -20,7 +20,12 @@ export class MountObserver extends EventTarget implements MountContext{
|
|
|
20
20
|
constructor(init: MountInit){
|
|
21
21
|
super();
|
|
22
22
|
const {match, whereElementIntersectsWith, whereMediaMatches} = init;
|
|
23
|
-
|
|
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;
|
|
24
29
|
if(whereElementIntersectsWith || whereMediaMatches) throw 'NI'; //not implemented
|
|
25
30
|
this.#mountInit = init;
|
|
26
31
|
this.#abortController = new AbortController();
|
|
@@ -29,12 +34,30 @@ export class MountObserver extends EventTarget implements MountContext{
|
|
|
29
34
|
//this.#unmounted = new WeakSet();
|
|
30
35
|
}
|
|
31
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
|
+
|
|
32
54
|
async observe(within: Node){
|
|
33
55
|
const nodeToMonitor = this.#isComplex ? (within instanceof ShadowRoot ? within : within.getRootNode()) : within;
|
|
34
56
|
if(!mutationObserverLookup.has(nodeToMonitor)){
|
|
35
57
|
mutationObserverLookup.set(nodeToMonitor, new RootMutObs(nodeToMonitor));
|
|
36
58
|
}
|
|
37
59
|
const rootMutObs = mutationObserverLookup.get(within)!;
|
|
60
|
+
const {attribMatches} = this.#mountInit;
|
|
38
61
|
(rootMutObs as any as AddMutationEventListener).addEventListener('mutation-event', (e: MutationEvent) => {
|
|
39
62
|
//TODO: disconnected
|
|
40
63
|
if(this.#isComplex){
|
|
@@ -47,11 +70,39 @@ export class MountObserver extends EventTarget implements MountContext{
|
|
|
47
70
|
const doDisconnect = this.#mountInit.do?.onDisconnect;
|
|
48
71
|
for(const mutationRecord of mutationRecords){
|
|
49
72
|
const {addedNodes, type, removedNodes} = mutationRecord;
|
|
50
|
-
//console.log(
|
|
73
|
+
//console.log(mutationRecord);
|
|
51
74
|
const addedElements = Array.from(addedNodes).filter(x => x instanceof Element) as Array<Element>;
|
|
52
75
|
addedElements.forEach(x => elsToInspect.push(x));
|
|
53
76
|
if(type === 'attributes'){
|
|
54
|
-
const {target} = mutationRecord;
|
|
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
|
+
}
|
|
55
106
|
elsToInspect.push(target as Element);
|
|
56
107
|
}
|
|
57
108
|
const deletedElements = Array.from(removedNodes).filter(x => x instanceof Element) as Array<Element>;
|
|
@@ -83,7 +134,7 @@ export class MountObserver extends EventTarget implements MountContext{
|
|
|
83
134
|
//first unmount non matching
|
|
84
135
|
const alreadyMounted = this.#filterAndDismount();
|
|
85
136
|
const onMount = this.#mountInit.do?.onMount;
|
|
86
|
-
const imp = this.#mountInit
|
|
137
|
+
const {import: imp, attribMatches} = this.#mountInit;
|
|
87
138
|
for(const match of matching){
|
|
88
139
|
if(alreadyMounted.has(match)) continue;
|
|
89
140
|
this.#mounted.add(match);
|
|
@@ -104,6 +155,27 @@ export class MountObserver extends EventTarget implements MountContext{
|
|
|
104
155
|
}
|
|
105
156
|
if(onMount !== undefined) onMount(match, this, 'PostImport');
|
|
106
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
|
+
}
|
|
107
179
|
this.#mountedList?.push(new WeakRef(match));
|
|
108
180
|
//if(this.#unmounted.has(match)) this.#unmounted.delete(match);
|
|
109
181
|
}
|
|
@@ -123,7 +195,8 @@ export class MountObserver extends EventTarget implements MountContext{
|
|
|
123
195
|
const returnSet = new Set<Element>();
|
|
124
196
|
if(this.#mountedList !== undefined){
|
|
125
197
|
const previouslyMounted = this.#mountedList.map(x => x.deref());
|
|
126
|
-
const {
|
|
198
|
+
const {whereSatisfies, whereInstanceOf} = this.#mountInit;
|
|
199
|
+
const match = this.#selector;
|
|
127
200
|
const elsToUnMount = previouslyMounted.filter(x => {
|
|
128
201
|
if(x === undefined) return false;
|
|
129
202
|
if(!x.matches(match)) return true;
|
|
@@ -140,7 +213,8 @@ export class MountObserver extends EventTarget implements MountContext{
|
|
|
140
213
|
}
|
|
141
214
|
|
|
142
215
|
async #filterAndMount(els: Array<Element>, checkMatch: boolean){
|
|
143
|
-
const {
|
|
216
|
+
const {whereSatisfies, whereInstanceOf} = this.#mountInit;
|
|
217
|
+
const match = this.#selector;
|
|
144
218
|
const elsToMount = els.filter(x => {
|
|
145
219
|
if(checkMatch){
|
|
146
220
|
if(!x.matches(match)) return false;
|
|
@@ -157,8 +231,7 @@ export class MountObserver extends EventTarget implements MountContext{
|
|
|
157
231
|
}
|
|
158
232
|
|
|
159
233
|
async #inspectWithin(within: Node){
|
|
160
|
-
const
|
|
161
|
-
const els = Array.from((within as Element).querySelectorAll(match));
|
|
234
|
+
const els = Array.from((within as Element).querySelectorAll(this.#selector));
|
|
162
235
|
this.#filterAndMount(els, false);
|
|
163
236
|
}
|
|
164
237
|
|
|
@@ -200,3 +273,10 @@ export class DisconnectEvent extends Event implements IDisconnectEvent{
|
|
|
200
273
|
}
|
|
201
274
|
}
|
|
202
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/README.md
CHANGED
|
@@ -135,8 +135,6 @@ const observer = new MountObserver({
|
|
|
135
135
|
})
|
|
136
136
|
```
|
|
137
137
|
|
|
138
|
-
|
|
139
|
-
|
|
140
138
|
## Subscribing
|
|
141
139
|
|
|
142
140
|
Subscribing can be done via:
|
|
@@ -181,6 +179,38 @@ If an element that is in "mounted" state according to a MountObserver instance i
|
|
|
181
179
|
4) If the new place it was added remains within the original rootNode and remains either dismounted or mounted, the MountObserver instance dispatches event "reconfirmed".
|
|
182
180
|
5) If the element no longer satisfies the criteria of the MountObserver instance, the MountObserver instance will dispatch event "dismount". The same is done in reverse for moved elements that started out in a "dismounted" state.
|
|
183
181
|
|
|
182
|
+
## Special support for attributes
|
|
183
|
+
|
|
184
|
+
Extra support is provided for monitoring attributes.
|
|
185
|
+
|
|
186
|
+
Example:
|
|
187
|
+
|
|
188
|
+
```html
|
|
189
|
+
<div id=div>
|
|
190
|
+
<span id=span></span>
|
|
191
|
+
</div>
|
|
192
|
+
<script type=module>
|
|
193
|
+
import {MountObserver} from '../MountObserver.js';
|
|
194
|
+
const mo = new MountObserver({
|
|
195
|
+
match: '#span',
|
|
196
|
+
attribMatches:[
|
|
197
|
+
{
|
|
198
|
+
names: ['test-1']
|
|
199
|
+
}
|
|
200
|
+
]
|
|
201
|
+
});
|
|
202
|
+
mo.addEventListener('attr-change', e => {
|
|
203
|
+
console.log(e);
|
|
204
|
+
});
|
|
205
|
+
mo.observe(div);
|
|
206
|
+
setTimeout(() => {
|
|
207
|
+
span.setAttribute('test-1', 'hello')
|
|
208
|
+
}, 1000);
|
|
209
|
+
</script>
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
|
|
184
214
|
## Preemptive downloading
|
|
185
215
|
|
|
186
216
|
There are two significant steps to imports, each of which imposes a cost:
|
package/RootMutObs.js
CHANGED
package/RootMutObs.ts
CHANGED
package/package.json
CHANGED
package/tests/test6.html
ADDED
|
@@ -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>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>
|
|
@@ -0,0 +1,8 @@
|
|
|
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/types.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
export interface MountInit{
|
|
2
|
-
readonly match
|
|
3
|
-
readonly
|
|
4
|
-
readonly
|
|
2
|
+
readonly match?: CSSMatch,
|
|
3
|
+
readonly attribMatches?: Array<AttribMatch>,
|
|
4
|
+
readonly whereElementIntersectsWith?: IntersectionObserverInit,
|
|
5
|
+
readonly whereMediaMatches?: MediaQuery,
|
|
5
6
|
readonly whereInstanceOf?: Array<typeof Node>, //[TODO] What's the best way to type this?,
|
|
6
|
-
readonly whereSatisfies
|
|
7
|
+
readonly whereSatisfies?: PipelineProcessor<boolean>,
|
|
7
8
|
readonly import?: ImportString | [ImportString, ImportAssertions] | PipelineProcessor,
|
|
8
9
|
readonly do?: {
|
|
9
10
|
readonly onMount: PipelineProcessor,
|
|
@@ -18,6 +19,14 @@ type CSSMatch = string;
|
|
|
18
19
|
type ImportString = string;
|
|
19
20
|
type MediaQuery = string;
|
|
20
21
|
|
|
22
|
+
export interface AttribMatch{
|
|
23
|
+
names: string[],
|
|
24
|
+
//for boolean, support true/false/mixed
|
|
25
|
+
// type?: 'number' | 'string' | 'date' | 'json-object' | 'boolean',
|
|
26
|
+
// valConverter?: (s: string) => any,
|
|
27
|
+
// validator?: (v: any) => boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
21
30
|
export interface MountContext {
|
|
22
31
|
// readonly mountInit: MountInit,
|
|
23
32
|
// readonly mountedRefs: WeakRef<Element>[],
|
|
@@ -42,10 +51,18 @@ export interface AddMutationEventListener {
|
|
|
42
51
|
}
|
|
43
52
|
//#endregion
|
|
44
53
|
|
|
54
|
+
interface AttrChangeInfo{
|
|
55
|
+
name: string,
|
|
56
|
+
oldValue: string | null,
|
|
57
|
+
newValue: string | null,
|
|
58
|
+
idx: number,
|
|
59
|
+
//parsedNewValue?: any,
|
|
60
|
+
}
|
|
61
|
+
|
|
45
62
|
//#region mount event
|
|
46
63
|
export type mountEventName = 'mount';
|
|
47
64
|
export interface IMountEvent{
|
|
48
|
-
mountedElement: Element
|
|
65
|
+
mountedElement: Element,
|
|
49
66
|
}
|
|
50
67
|
export type mountEventHandler = (e: IMountEvent) => void;
|
|
51
68
|
|
|
@@ -80,3 +97,14 @@ export interface AddDisconnectEventListener {
|
|
|
80
97
|
}
|
|
81
98
|
//endregion
|
|
82
99
|
|
|
100
|
+
//#region attribute change event
|
|
101
|
+
export type attrChangeEventName = 'attr-change';
|
|
102
|
+
export interface IAttrChangeEvent extends IMountEvent {
|
|
103
|
+
attrChangeInfo: AttrChangeInfo,
|
|
104
|
+
}
|
|
105
|
+
export type attrChangeEventHander = (e: IAttrChangeEvent) => void;
|
|
106
|
+
export interface AddAttrChangeEventistener{
|
|
107
|
+
addEventListener(eventName: attrChangeEventName, handler: attrChangeEventHander, options?: AddEventListenerOptions): void;
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
|