mount-observer 0.0.7 → 0.0.8
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 +57 -58
- package/README.md +71 -24
- package/doWhereAttr.js +43 -0
- package/getWhereAttrSelector.js +35 -0
- package/package.json +1 -1
- package/types.d.ts +14 -9
package/MountObserver.js
CHANGED
|
@@ -28,25 +28,20 @@ export class MountObserver extends EventTarget {
|
|
|
28
28
|
//this.#unmounted = new WeakSet();
|
|
29
29
|
}
|
|
30
30
|
#calculatedSelector;
|
|
31
|
-
|
|
31
|
+
#fullListOfAttrs;
|
|
32
|
+
//get #attrVals
|
|
33
|
+
async #selector() {
|
|
32
34
|
if (this.#calculatedSelector !== undefined)
|
|
33
35
|
return this.#calculatedSelector;
|
|
34
36
|
const { on, whereAttr } = this.#mountInit;
|
|
35
|
-
const
|
|
37
|
+
const withoutAttrs = on || '*';
|
|
36
38
|
if (whereAttr === undefined)
|
|
37
|
-
return
|
|
38
|
-
const {
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
for (const stem of stems) {
|
|
44
|
-
const prefix = typeof stem === 'string' ? stem : stem.stem;
|
|
45
|
-
for (const prefixLessMatch of prefixLessMatches) {
|
|
46
|
-
matches.push(`${prefix}-${prefixLessMatch}`);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
this.#calculatedSelector = matches.join(',');
|
|
39
|
+
return withoutAttrs;
|
|
40
|
+
const { getWhereAttrSelector } = await import('./getWhereAttrSelector.js');
|
|
41
|
+
const info = getWhereAttrSelector(whereAttr, withoutAttrs);
|
|
42
|
+
const { fullListOfAttrs, calculatedSelector } = info;
|
|
43
|
+
this.#fullListOfAttrs = fullListOfAttrs;
|
|
44
|
+
this.#calculatedSelector = calculatedSelector;
|
|
50
45
|
return this.#calculatedSelector;
|
|
51
46
|
}
|
|
52
47
|
unobserve(within) {
|
|
@@ -90,8 +85,9 @@ export class MountObserver extends EventTarget {
|
|
|
90
85
|
}
|
|
91
86
|
}
|
|
92
87
|
const rootMutObs = mutationObserverLookup.get(within);
|
|
93
|
-
const {
|
|
94
|
-
|
|
88
|
+
//const {whereAttr} = this.#mountInit;
|
|
89
|
+
const fullListOfAttrs = this.#fullListOfAttrs;
|
|
90
|
+
rootMutObs.addEventListener('mutation-event', async (e) => {
|
|
95
91
|
//TODO: disconnected
|
|
96
92
|
if (this.#isComplex) {
|
|
97
93
|
this.#inspectWithin(within, false);
|
|
@@ -108,22 +104,11 @@ export class MountObserver extends EventTarget {
|
|
|
108
104
|
addedElements.forEach(x => elsToInspect.push(x));
|
|
109
105
|
if (type === 'attributes') {
|
|
110
106
|
const { target, attributeName, oldValue } = mutationRecord;
|
|
111
|
-
if (target instanceof Element && attributeName !== null &&
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (names.includes(attributeName)) {
|
|
107
|
+
if (target instanceof Element && attributeName !== null && this.#mounted.has(target)) {
|
|
108
|
+
if (fullListOfAttrs !== undefined) {
|
|
109
|
+
const idx = fullListOfAttrs.indexOf(attributeName);
|
|
110
|
+
if (idx > -1) {
|
|
116
111
|
const newValue = target.getAttribute(attributeName);
|
|
117
|
-
// let parsedNewValue = undefined;
|
|
118
|
-
// switch(type){
|
|
119
|
-
// case 'boolean':
|
|
120
|
-
// parsedNewValue = newValue === 'true' ? true : newValue === 'false' ? false : null;
|
|
121
|
-
// break;
|
|
122
|
-
// case 'date':
|
|
123
|
-
// parsedNewValue = newValue === null ? null : new Date(newValue);
|
|
124
|
-
// break;
|
|
125
|
-
// case ''
|
|
126
|
-
// }
|
|
127
112
|
const attrChangeInfo = {
|
|
128
113
|
name: attributeName,
|
|
129
114
|
oldValue,
|
|
@@ -132,7 +117,13 @@ export class MountObserver extends EventTarget {
|
|
|
132
117
|
};
|
|
133
118
|
this.dispatchEvent(new AttrChangeEvent(target, attrChangeInfo));
|
|
134
119
|
}
|
|
135
|
-
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
const { whereAttr } = this.#mountInit;
|
|
123
|
+
if (whereAttr !== undefined) {
|
|
124
|
+
const { doWhereAttr } = await import('./doWhereAttr.js');
|
|
125
|
+
doWhereAttr(whereAttr, attributeName, target, oldValue, this);
|
|
126
|
+
}
|
|
136
127
|
}
|
|
137
128
|
}
|
|
138
129
|
elsToInspect.push(target);
|
|
@@ -164,9 +155,10 @@ export class MountObserver extends EventTarget {
|
|
|
164
155
|
}
|
|
165
156
|
async #mount(matching, initializing) {
|
|
166
157
|
//first unmount non matching
|
|
167
|
-
const alreadyMounted = this.#filterAndDismount();
|
|
158
|
+
const alreadyMounted = await this.#filterAndDismount();
|
|
168
159
|
const mount = this.#mountInit.do?.mount;
|
|
169
|
-
const { import: imp
|
|
160
|
+
const { import: imp } = this.#mountInit;
|
|
161
|
+
const fullListOfAttrs = this.#fullListOfAttrs;
|
|
170
162
|
for (const match of matching) {
|
|
171
163
|
if (alreadyMounted.has(match))
|
|
172
164
|
continue;
|
|
@@ -197,27 +189,33 @@ export class MountObserver extends EventTarget {
|
|
|
197
189
|
});
|
|
198
190
|
}
|
|
199
191
|
this.dispatchEvent(new MountEvent(match, initializing));
|
|
200
|
-
if (
|
|
201
|
-
|
|
202
|
-
for (const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
for (const name of names) {
|
|
207
|
-
const attrVal = match.getAttribute(name);
|
|
208
|
-
if (attrVal !== null)
|
|
209
|
-
nonNullName = name;
|
|
210
|
-
newValue = newValue || attrVal;
|
|
192
|
+
if (fullListOfAttrs !== undefined) {
|
|
193
|
+
const { whereAttr } = this.#mountInit;
|
|
194
|
+
for (const name of fullListOfAttrs) {
|
|
195
|
+
if (whereAttr !== undefined) {
|
|
196
|
+
const { doWhereAttr } = await import('./doWhereAttr.js');
|
|
197
|
+
doWhereAttr(whereAttr, name, match, null, this);
|
|
211
198
|
}
|
|
212
|
-
const attribInfo = {
|
|
213
|
-
oldValue: null,
|
|
214
|
-
newValue,
|
|
215
|
-
idx,
|
|
216
|
-
name: nonNullName
|
|
217
|
-
};
|
|
218
|
-
this.dispatchEvent(new AttrChangeEvent(match, attribInfo));
|
|
219
|
-
idx++;
|
|
220
199
|
}
|
|
200
|
+
// let idx = 0;
|
|
201
|
+
// for(const attribMatch of attribMatches){
|
|
202
|
+
// let newValue = null;
|
|
203
|
+
// const {names} = attribMatch;
|
|
204
|
+
// let nonNullName = names[0];
|
|
205
|
+
// for(const name of names){
|
|
206
|
+
// const attrVal = match.getAttribute(name);
|
|
207
|
+
// if(attrVal !== null) nonNullName = name;
|
|
208
|
+
// newValue = newValue || attrVal;
|
|
209
|
+
// }
|
|
210
|
+
// const attribInfo: AttrChangeInfo = {
|
|
211
|
+
// oldValue: null,
|
|
212
|
+
// newValue,
|
|
213
|
+
// idx,
|
|
214
|
+
// name: nonNullName
|
|
215
|
+
// };
|
|
216
|
+
// this.dispatchEvent(new AttrChangeEvent(match, attribInfo));
|
|
217
|
+
// idx++;
|
|
218
|
+
// }
|
|
221
219
|
}
|
|
222
220
|
this.#mountedList?.push(new WeakRef(match));
|
|
223
221
|
//if(this.#unmounted.has(match)) this.#unmounted.delete(match);
|
|
@@ -232,12 +230,12 @@ export class MountObserver extends EventTarget {
|
|
|
232
230
|
this.dispatchEvent(new DismountEvent(unmatch));
|
|
233
231
|
}
|
|
234
232
|
}
|
|
235
|
-
#filterAndDismount() {
|
|
233
|
+
async #filterAndDismount() {
|
|
236
234
|
const returnSet = new Set();
|
|
237
235
|
if (this.#mountedList !== undefined) {
|
|
238
236
|
const previouslyMounted = this.#mountedList.map(x => x.deref());
|
|
239
237
|
const { whereSatisfies, whereInstanceOf } = this.#mountInit;
|
|
240
|
-
const match = this.#selector;
|
|
238
|
+
const match = await this.#selector();
|
|
241
239
|
const elsToUnMount = previouslyMounted.filter(x => {
|
|
242
240
|
if (x === undefined)
|
|
243
241
|
return false;
|
|
@@ -257,7 +255,7 @@ export class MountObserver extends EventTarget {
|
|
|
257
255
|
}
|
|
258
256
|
async #filterAndMount(els, checkMatch, initializing) {
|
|
259
257
|
const { whereSatisfies, whereInstanceOf } = this.#mountInit;
|
|
260
|
-
const match = this.#selector;
|
|
258
|
+
const match = await this.#selector();
|
|
261
259
|
const elsToMount = els.filter(x => {
|
|
262
260
|
if (checkMatch) {
|
|
263
261
|
if (!x.matches(match))
|
|
@@ -276,7 +274,7 @@ export class MountObserver extends EventTarget {
|
|
|
276
274
|
this.#mount(elsToMount, initializing);
|
|
277
275
|
}
|
|
278
276
|
async #inspectWithin(within, initializing) {
|
|
279
|
-
const els = Array.from(within.querySelectorAll(this.#selector));
|
|
277
|
+
const els = Array.from(within.querySelectorAll(await this.#selector()));
|
|
280
278
|
this.#filterAndMount(els, false, initializing);
|
|
281
279
|
}
|
|
282
280
|
}
|
|
@@ -322,3 +320,4 @@ export class AttrChangeEvent extends Event {
|
|
|
322
320
|
this.attrChangeInfo = attrChangeInfo;
|
|
323
321
|
}
|
|
324
322
|
}
|
|
323
|
+
//const hasRootInDefault = ['data', 'enh', 'data-enh']
|
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
[](https://bundlephobia.com/result?p=mount-observer)
|
|
4
4
|
<img src="http://img.badgesize.io/https://cdn.jsdelivr.net/npm/mount-observer?compression=gzip">
|
|
5
5
|
|
|
6
|
+
Note that much of what is described below has not yet been polyfilled.
|
|
6
7
|
|
|
7
8
|
# The MountObserver api.
|
|
8
9
|
|
|
@@ -10,7 +11,7 @@ Author: Bruce B. Anderson
|
|
|
10
11
|
|
|
11
12
|
Issues / pr's / polyfill: [mount-observer](https://github.com/bahrus/mount-observer)
|
|
12
13
|
|
|
13
|
-
Last Update: 2024-2-
|
|
14
|
+
Last Update: 2024-2-14
|
|
14
15
|
|
|
15
16
|
## Benefits of this API
|
|
16
17
|
|
|
@@ -24,7 +25,7 @@ The underlying theme is this api is meant to make it easy for the developer to d
|
|
|
24
25
|
|
|
25
26
|
There is quite a bit of functionality this proposal would open up, that is exceedingly difficult to polyfill reliably:
|
|
26
27
|
|
|
27
|
-
1. It is unclear how to use mutation observers to observe changes to [custom state](https://developer.mozilla.org/en-US/docs/Web/API/CustomStateSet). The closest thing might be a solution like [this](https://davidwalsh.name/detect-node-insertion), but that falls short for elements that aren't visible, or during template instantiation.
|
|
28
|
+
1. It is unclear how to use mutation observers to observe changes to [custom state](https://developer.mozilla.org/en-US/docs/Web/API/CustomStateSet). The closest thing might be a solution like [this](https://davidwalsh.name/detect-node-insertion), but that falls short for elements that aren't visible, or during template instantiation, and requires carefully constructed "negating" queries if needing to know when the css selector is no longer matching.
|
|
28
29
|
|
|
29
30
|
2. For simple css matches, like "my-element", or "[name='hello']" it is enough to use a mutation observer, and only observe the elements within the specified DOM region (more on that below). But as CSS has evolved, it is quite easy to think of numerous css selectors that would require us to expand our mutation observer to need to scan the entire Shadow DOM realm, or the entire DOM tree outside any Shadow DOM, for any and all mutations (including attribute changes), and re-evaluate every single element within the specified DOM region for new matches or old matches that no longer match. Things like child selectors, :has, and so on. All this is done, miraculously, by the browser in a performant way. Reproducing this in userland using JavaScript alone, matching the same performance seems impossible.
|
|
30
31
|
|
|
@@ -38,7 +39,7 @@ The amount of code necessary to accomplish these common tasks designed to improv
|
|
|
38
39
|
1. Making lazy loading of resource dependencies easy, to the benefit of users with expensive networks.
|
|
39
40
|
2. Supporting "binding from a distance" that can set property values of elements in bulk as the HTML streams in. For example, say a web page is streaming in HTML with thousands of input elements (say a long tax form). We want to have some indication in the head tag of the HTML (for example) to make all the input elements read only as they stream through the page. With css, we could do similar things, for example set the background to red of all input elements. Why can't we do something similar with setting properties like readOnly, disabled, etc? With this api, giving developers the "keys" to css filtering, so they can "mount a campaign" to apply common settings on them all feels like something that almost every web developer has mentally screamed to themselves "why can't I do that?", doesn't it?
|
|
40
41
|
3. Supporting "progressive enhancement" more effectively.
|
|
41
|
-
2. Potentially by allowing the platform to do more work in the low-level (c/c++/rust?) code, without as much context switching into the JavaScript memory space, which may reduce cpu cycles as well. This is done by passing into the API substantial number of conditions, which can all be evaluated at a lower level, before
|
|
42
|
+
2. Potentially by allowing the platform to do more work in the low-level (c/c++/rust?) code, without as much context switching into the JavaScript memory space, which may reduce cpu cycles as well. This is done by passing into the API substantial number of conditions, which can all be evaluated at a lower level, before the api needs to surface up to the developer "found one!".
|
|
42
43
|
3. As discussed earlier, to do the job right, polyfills really need to reexamine **all** the elements within the observed node for matches **anytime any element within the Shadow Root so much as sneezes (has attribute modified, changes custom state, etc)**, due to modern selectors such as the :has selector. Surely, the platform has found ways to do this more efficiently?
|
|
43
44
|
|
|
44
45
|
The extra flexibility this new primitive would provide could be quite useful to things other than lazy loading of custom elements, such as implementing [custom enhancements](https://github.com/WICG/webcomponents/issues/1000) as well as [binding from a distance](https://github.com/WICG/webcomponents/issues/1035#issuecomment-1806393525) in userland.
|
|
@@ -100,6 +101,8 @@ const observer = new MountObserver({
|
|
|
100
101
|
})
|
|
101
102
|
```
|
|
102
103
|
|
|
104
|
+
... would work.
|
|
105
|
+
|
|
103
106
|
This would allow developers to create "stylesheet" like capabilities.
|
|
104
107
|
|
|
105
108
|
|
|
@@ -206,7 +209,7 @@ Being that for both custom elements, as well as (hopefully) [custom enhancements
|
|
|
206
209
|
|
|
207
210
|
We want to be alerted by the discovery of elements adorned by these attributes, but then continue to be alerted to changes of their values, and we can't enumerate which values we are interested in, so we must subscribe to all values as they change.
|
|
208
211
|
|
|
209
|
-
### Scenario 1 -- Custom Element integration with
|
|
212
|
+
### Scenario 1 -- Custom Element integration with ObserveObservedAttributes API [WIP]
|
|
210
213
|
|
|
211
214
|
Example:
|
|
212
215
|
|
|
@@ -247,6 +250,8 @@ To make this discussion concrete, let's suppose the "canonical" names of those a
|
|
|
247
250
|
my-enhancement=greetings
|
|
248
251
|
my-enhancement-first-aspect=hello
|
|
249
252
|
my-enhancement-second-aspect=goodbye
|
|
253
|
+
my-enhancement-first-aspect-wow-this-is-deep
|
|
254
|
+
my-enhancement-first-aspect-have-you-considered-using-json-for-this=just-saying
|
|
250
255
|
></section>
|
|
251
256
|
</div>
|
|
252
257
|
```
|
|
@@ -263,17 +268,19 @@ We want to also support:
|
|
|
263
268
|
data-my-enhancement=greetings
|
|
264
269
|
data-my-enhancement-first-aspect=hello
|
|
265
270
|
data-my-enhancement-second-aspect=goodbye
|
|
271
|
+
data-my-enhancement-first-aspect-wow-this-is-deep
|
|
272
|
+
data-my-enhancement-first-aspect-have-you-considered-using-json-for-this=just-saying
|
|
266
273
|
></section>
|
|
267
274
|
</div>
|
|
268
275
|
```
|
|
269
276
|
|
|
270
277
|
Based on the current unspoken rules, no one will raise an eyebrow with these attributes, because the platform has indicated it will generally avoid dashes in attributes (with an exception or two that will only happen in a blue moon, like aria-*).
|
|
271
278
|
|
|
272
|
-
But now when we consider applying this enhancement to custom elements, we have a new risk. What's to prevent the custom element from having an attribute named my-enhancement
|
|
279
|
+
But now when we consider applying this enhancement to custom elements, we have a new risk. What's to prevent the custom element from having an attribute named my-enhancement?
|
|
273
280
|
|
|
274
|
-
So let's say we want to insist that on custom elements, we must have the
|
|
281
|
+
So let's say we want to insist that on custom elements, we must have the data- prefix?
|
|
275
282
|
|
|
276
|
-
And we want to support an alternative prefix to data, say enh-*, endorsed by [this proposal](https://github.com/WICG/webcomponents/issues/1000).
|
|
283
|
+
And we want to support an alternative, more semantic sounding prefix to data, say enh-*, endorsed by [this proposal](https://github.com/WICG/webcomponents/issues/1000).
|
|
277
284
|
|
|
278
285
|
Here's what the api provides:
|
|
279
286
|
|
|
@@ -291,6 +298,7 @@ const mo = new MountObserver({
|
|
|
291
298
|
'enh-my-enhancement',
|
|
292
299
|
'enh-my-enhancement-first-aspect',
|
|
293
300
|
'enh-my-enhancement-second-aspect',
|
|
301
|
+
//...some ten more combinations not listed
|
|
294
302
|
{
|
|
295
303
|
name: 'my-enhancement',
|
|
296
304
|
builtIn: true
|
|
@@ -302,7 +310,8 @@ const mo = new MountObserver({
|
|
|
302
310
|
{
|
|
303
311
|
name: 'my-enhancement-second-aspect',
|
|
304
312
|
builtIn: true
|
|
305
|
-
}
|
|
313
|
+
},
|
|
314
|
+
...
|
|
306
315
|
]
|
|
307
316
|
|
|
308
317
|
}
|
|
@@ -316,30 +325,33 @@ import {MountObserver} from '../MountObserver.js';
|
|
|
316
325
|
const mo = new MountObserver({
|
|
317
326
|
on: '*',
|
|
318
327
|
whereAttr:{
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
328
|
+
hasRootIn: ['data', 'enh', 'data-enh'],
|
|
329
|
+
hasBase: 'my-enhancement',
|
|
330
|
+
hasBranchIn: ['first-aspect', 'second-aspect', ''],
|
|
331
|
+
hasLeafIn: {
|
|
332
|
+
'first-aspect': ['wow-this-is-deep', 'have-you-considered-using-json-for-this'],
|
|
333
|
+
}
|
|
322
334
|
}
|
|
323
335
|
});
|
|
324
336
|
```
|
|
325
337
|
|
|
326
|
-
MountObserver
|
|
338
|
+
MountObserver provides a breakdown of the matching attribute when encountered:
|
|
327
339
|
|
|
328
340
|
```html
|
|
329
341
|
<div id=div>
|
|
330
|
-
<section class=hello my-enhancement-first-attr="hello"></section>
|
|
342
|
+
<section class=hello my-enhancement-first-attr-wow-this-is-deep="hello"></section>
|
|
331
343
|
</div>
|
|
332
344
|
<script type=module>
|
|
333
345
|
import {MountObserver} from '../MountObserver.js';
|
|
334
346
|
const mo = new MountObserver({
|
|
335
347
|
on: '*',
|
|
336
348
|
whereAttr:{
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
349
|
+
hasRootIn: ['data', 'enh', 'data-enh'],
|
|
350
|
+
hasBase: 'my-enhancement',
|
|
351
|
+
hasBranchIn: ['first-attr', 'second-attr', ''],
|
|
352
|
+
hasLeafIn: {
|
|
353
|
+
'first-attr': ['wow-this-is-deep', 'have-you-considered-using-json-for-this'],
|
|
354
|
+
}
|
|
343
355
|
}
|
|
344
356
|
});
|
|
345
357
|
mo.addEventListener('observed-attr-change', e => {
|
|
@@ -347,10 +359,13 @@ MountObserver supports both approaches
|
|
|
347
359
|
// {
|
|
348
360
|
// matchingElement,
|
|
349
361
|
// attrChangeInfo:{
|
|
350
|
-
//
|
|
351
|
-
//
|
|
362
|
+
// name: 'data-my-enhancement-first-aspect-wow-this-is-deep'
|
|
363
|
+
// root: 'data',
|
|
364
|
+
// base: 'my-enhancement',
|
|
365
|
+
// branch: 'first-attr',
|
|
366
|
+
// leaf: 'wow-this-is-deep',
|
|
352
367
|
// oldValue: null,
|
|
353
|
-
// newValue: '
|
|
368
|
+
// newValue: 'good-bye'
|
|
354
369
|
// idx: 0,
|
|
355
370
|
// }
|
|
356
371
|
// }
|
|
@@ -358,11 +373,44 @@ MountObserver supports both approaches
|
|
|
358
373
|
mo.observe(div);
|
|
359
374
|
setTimeout(() => {
|
|
360
375
|
const myCustomElement = document.querySelector('my-custom-element');
|
|
361
|
-
myCustomElement.setAttribute('my-first-
|
|
376
|
+
myCustomElement.setAttribute('data-my-enhancement-first-aspect-wow-this-is-deep', 'good-bye');
|
|
362
377
|
}, 1000);
|
|
363
378
|
</script>
|
|
364
379
|
```
|
|
365
380
|
|
|
381
|
+
Some libraries prefer to use the colon (:) rather than a dash to separate these levels of settings:
|
|
382
|
+
|
|
383
|
+
Possibly some libraries may prefer to mix it up a bit:
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
```html
|
|
387
|
+
<div id=div>
|
|
388
|
+
<section class=hello
|
|
389
|
+
data-my-enhancement=greetings
|
|
390
|
+
data-my-enhancement:first-aspect=hello
|
|
391
|
+
data-my-enhancement:second-aspect=goodbye
|
|
392
|
+
data-my-enhancement:first-aspect--wow-this-is-deep
|
|
393
|
+
data-my-enhancement:first-aspect--have-you-considered-using-json-for-this=just-saying
|
|
394
|
+
></section>
|
|
395
|
+
</div>
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
To support, specify the delimiter thusly:
|
|
399
|
+
|
|
400
|
+
```JavaScript
|
|
401
|
+
const mo = new MountObserver({
|
|
402
|
+
on: '*',
|
|
403
|
+
whereAttr:{
|
|
404
|
+
hasRootIn: ['data', 'enh', 'data-enh'],
|
|
405
|
+
hasBase: ['-', 'my-enhancement'],
|
|
406
|
+
hasBranchIn: [':', ['first-attr', 'second-attr', '']],
|
|
407
|
+
hasLeafIn: {
|
|
408
|
+
'first-attr': ['--', ['wow-this-is-deep', 'have-you-considered-using-json-for-this']],
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
```
|
|
413
|
+
|
|
366
414
|
## Preemptive downloading
|
|
367
415
|
|
|
368
416
|
There are two significant steps to imports, each of which imposes a cost:
|
|
@@ -389,4 +437,3 @@ const observer = new MountObserver({
|
|
|
389
437
|
|
|
390
438
|
So what this does is only check for the presence of an element with tag name "my-element", and it starts downloading the resource, even before the element has "mounted" based on other criteria.
|
|
391
439
|
|
|
392
|
-
|
package/doWhereAttr.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { AttrChangeEvent } from "./MountObserver.js";
|
|
2
|
+
export function doWhereAttr(whereAttr, attributeName, target, oldValue, mo) {
|
|
3
|
+
const { hasRootIn, hasBase, hasBranchIn } = whereAttr;
|
|
4
|
+
const name = attributeName;
|
|
5
|
+
let restOfName = name;
|
|
6
|
+
let root;
|
|
7
|
+
let branch;
|
|
8
|
+
let idx = 0;
|
|
9
|
+
const hasBaseIsString = typeof hasBase === 'string';
|
|
10
|
+
const baseSelector = hasBaseIsString ? hasBase : hasBase[1];
|
|
11
|
+
const rootToBaseDelimiter = hasBaseIsString ? '-' : hasBase[0];
|
|
12
|
+
if (hasRootIn !== undefined) {
|
|
13
|
+
for (const rootTest in hasRootIn) {
|
|
14
|
+
if (restOfName.startsWith(rootTest)) {
|
|
15
|
+
root = rootTest;
|
|
16
|
+
restOfName = restOfName.substring(root.length + rootToBaseDelimiter.length);
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (!restOfName.startsWith(baseSelector))
|
|
22
|
+
return;
|
|
23
|
+
restOfName = restOfName.substring(hasBase.length);
|
|
24
|
+
if (hasBranchIn) {
|
|
25
|
+
for (const branchTest in hasBranchIn) {
|
|
26
|
+
if (restOfName.startsWith(branchTest)) {
|
|
27
|
+
branch = branchTest;
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const newValue = target.getAttribute(attributeName);
|
|
33
|
+
const attrChangeInfo = {
|
|
34
|
+
name,
|
|
35
|
+
root,
|
|
36
|
+
base: baseSelector,
|
|
37
|
+
branch,
|
|
38
|
+
oldValue,
|
|
39
|
+
newValue,
|
|
40
|
+
idx
|
|
41
|
+
};
|
|
42
|
+
mo.dispatchEvent(new AttrChangeEvent(target, attrChangeInfo));
|
|
43
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export function getWhereAttrSelector(whereAttr, withoutAttrs) {
|
|
2
|
+
const { hasBase, hasBranchIn, hasRootIn } = whereAttr;
|
|
3
|
+
const fullListOfAttrs = [];
|
|
4
|
+
//TODO: share this block with doWhereAttr?
|
|
5
|
+
const hasBaseIsString = typeof hasBase === 'string';
|
|
6
|
+
const baseSelector = hasBaseIsString ? hasBase : hasBase[1];
|
|
7
|
+
const rootToBaseDelimiter = hasBaseIsString ? '-' : hasBase[0];
|
|
8
|
+
//end TODO
|
|
9
|
+
let prefixLessMatches = [baseSelector];
|
|
10
|
+
if (hasBranchIn !== undefined) {
|
|
11
|
+
let baseToBranchDelimiter = '-';
|
|
12
|
+
let branches;
|
|
13
|
+
if (hasBranchIn.length === 2 && Array.isArray(hasBranchIn[1])) {
|
|
14
|
+
baseToBranchDelimiter = hasBranchIn[0];
|
|
15
|
+
branches = hasBranchIn[1];
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
branches = hasBranchIn;
|
|
19
|
+
}
|
|
20
|
+
prefixLessMatches = branches.map(x => `${baseSelector}${baseToBranchDelimiter}x`);
|
|
21
|
+
}
|
|
22
|
+
const stems = hasRootIn || [''];
|
|
23
|
+
for (const stem of stems) {
|
|
24
|
+
const prefix = typeof stem === 'string' ? stem : stem.path;
|
|
25
|
+
for (const prefixLessMatch of prefixLessMatches) {
|
|
26
|
+
fullListOfAttrs.push(prefix.length === 0 ? prefixLessMatch : `${prefix}${rootToBaseDelimiter}${prefixLessMatch}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const listOfSelectors = fullListOfAttrs.map(s => `${withoutAttrs}[${s}]`);
|
|
30
|
+
const calculatedSelector = listOfSelectors.join(',');
|
|
31
|
+
return {
|
|
32
|
+
fullListOfAttrs,
|
|
33
|
+
calculatedSelector
|
|
34
|
+
};
|
|
35
|
+
}
|
package/package.json
CHANGED
package/types.d.ts
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
export interface MountInit{
|
|
2
2
|
readonly on?: CSSMatch,
|
|
3
|
-
readonly attribMatches?: Array<AttribMatch>,
|
|
4
|
-
readonly whereAttr?:
|
|
5
|
-
withFirstName: string,
|
|
6
|
-
andQualifiers?: Array<string>,
|
|
7
|
-
withStemsIn?: Array<string | {
|
|
8
|
-
stem: string,
|
|
9
|
-
context: 'BuiltIn' | 'CustomElement'
|
|
10
|
-
}>,
|
|
11
|
-
},
|
|
3
|
+
//readonly attribMatches?: Array<AttribMatch>,
|
|
4
|
+
readonly whereAttr?: WhereAttr,
|
|
12
5
|
readonly whereElementIntersectsWith?: IntersectionObserverInit,
|
|
13
6
|
readonly whereMediaMatches?: MediaQuery,
|
|
14
7
|
readonly whereInstanceOf?: Array<typeof Node>, //[TODO] What's the best way to type this?,
|
|
@@ -27,6 +20,14 @@ export interface MountInit{
|
|
|
27
20
|
// */
|
|
28
21
|
// readonly ignoreInitialMatches?: boolean,
|
|
29
22
|
}
|
|
23
|
+
export interface WhereAttr{
|
|
24
|
+
hasBase: string | [string, string],
|
|
25
|
+
hasBranchIn?: Array<string> | [string, Array<string>],
|
|
26
|
+
hasRootIn?: Array<string | {
|
|
27
|
+
path: string,
|
|
28
|
+
context: 'BuiltIn' | 'CustomElement' | 'Both'
|
|
29
|
+
}>,
|
|
30
|
+
}
|
|
30
31
|
type CSSMatch = string;
|
|
31
32
|
type ImportString = string;
|
|
32
33
|
type MediaQuery = string;
|
|
@@ -70,6 +71,10 @@ export interface AddMutationEventListener {
|
|
|
70
71
|
|
|
71
72
|
interface AttrChangeInfo{
|
|
72
73
|
name: string,
|
|
74
|
+
root?: string,
|
|
75
|
+
base?: string,
|
|
76
|
+
branch?: string,
|
|
77
|
+
leaf?: string, //TODO
|
|
73
78
|
oldValue: string | null,
|
|
74
79
|
newValue: string | null,
|
|
75
80
|
idx: number,
|