mount-observer 0.0.6 → 0.0.7
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 +22 -18
- package/README.md +190 -55
- package/package.json +1 -1
- package/types.d.ts +15 -7
package/MountObserver.js
CHANGED
|
@@ -12,10 +12,10 @@ export class MountObserver extends EventTarget {
|
|
|
12
12
|
#isComplex;
|
|
13
13
|
constructor(init) {
|
|
14
14
|
super();
|
|
15
|
-
const {
|
|
15
|
+
const { on, whereElementIntersectsWith, whereMediaMatches } = init;
|
|
16
16
|
let isComplex = false;
|
|
17
|
-
if (
|
|
18
|
-
const reducedMatch =
|
|
17
|
+
if (on !== undefined) {
|
|
18
|
+
const reducedMatch = on.replaceAll(':not(', '');
|
|
19
19
|
isComplex = reducedMatch.includes(' ') || reducedMatch.includes(':');
|
|
20
20
|
}
|
|
21
21
|
this.#isComplex = isComplex;
|
|
@@ -31,17 +31,21 @@ export class MountObserver extends EventTarget {
|
|
|
31
31
|
get #selector() {
|
|
32
32
|
if (this.#calculatedSelector !== undefined)
|
|
33
33
|
return this.#calculatedSelector;
|
|
34
|
-
const {
|
|
35
|
-
const base =
|
|
36
|
-
if (
|
|
34
|
+
const { on, whereAttr } = this.#mountInit;
|
|
35
|
+
const base = on || '*';
|
|
36
|
+
if (whereAttr === undefined)
|
|
37
37
|
return base;
|
|
38
|
+
const { withFirstName, andQualifiers, withStemsIn } = whereAttr;
|
|
38
39
|
const matches = [];
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
const prefixLessMatches = andQualifiers === undefined ? [withFirstName]
|
|
41
|
+
: andQualifiers.map(x => `${withFirstName}-${x}`);
|
|
42
|
+
const stems = withStemsIn || ['data', 'enh', 'data-enh'];
|
|
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
|
+
}
|
|
45
49
|
this.#calculatedSelector = matches.join(',');
|
|
46
50
|
return this.#calculatedSelector;
|
|
47
51
|
}
|
|
@@ -96,7 +100,7 @@ export class MountObserver extends EventTarget {
|
|
|
96
100
|
const { mutationRecords } = e;
|
|
97
101
|
const elsToInspect = [];
|
|
98
102
|
//const elsToDisconnect: Array<Element> = [];
|
|
99
|
-
const doDisconnect = this.#mountInit.do?.
|
|
103
|
+
const doDisconnect = this.#mountInit.do?.disconnect;
|
|
100
104
|
for (const mutationRecord of mutationRecords) {
|
|
101
105
|
const { addedNodes, type, removedNodes } = mutationRecord;
|
|
102
106
|
//console.log(mutationRecord);
|
|
@@ -140,7 +144,7 @@ export class MountObserver extends EventTarget {
|
|
|
140
144
|
// this.#mountedList = this.#mountedList?.filter(x => x.deref() !== deletedElement);
|
|
141
145
|
this.#disconnected.add(deletedElement);
|
|
142
146
|
if (doDisconnect !== undefined) {
|
|
143
|
-
doDisconnect(deletedElement, this);
|
|
147
|
+
doDisconnect(deletedElement, this, {});
|
|
144
148
|
}
|
|
145
149
|
this.dispatchEvent(new DisconnectEvent(deletedElement));
|
|
146
150
|
}
|
|
@@ -161,7 +165,7 @@ export class MountObserver extends EventTarget {
|
|
|
161
165
|
async #mount(matching, initializing) {
|
|
162
166
|
//first unmount non matching
|
|
163
167
|
const alreadyMounted = this.#filterAndDismount();
|
|
164
|
-
const
|
|
168
|
+
const mount = this.#mountInit.do?.mount;
|
|
165
169
|
const { import: imp, attribMatches } = this.#mountInit;
|
|
166
170
|
for (const match of matching) {
|
|
167
171
|
if (alreadyMounted.has(match))
|
|
@@ -186,8 +190,8 @@ export class MountObserver extends EventTarget {
|
|
|
186
190
|
break;
|
|
187
191
|
}
|
|
188
192
|
}
|
|
189
|
-
if (
|
|
190
|
-
|
|
193
|
+
if (mount !== undefined) {
|
|
194
|
+
mount(match, this, {
|
|
191
195
|
stage: 'PostImport',
|
|
192
196
|
initializing
|
|
193
197
|
});
|
|
@@ -220,7 +224,7 @@ export class MountObserver extends EventTarget {
|
|
|
220
224
|
}
|
|
221
225
|
}
|
|
222
226
|
async #dismount(unmatching) {
|
|
223
|
-
const onDismount = this.#mountInit.do?.
|
|
227
|
+
const onDismount = this.#mountInit.do?.dismount;
|
|
224
228
|
for (const unmatch of unmatching) {
|
|
225
229
|
if (onDismount !== undefined) {
|
|
226
230
|
onDismount(unmatch, this, {});
|
package/README.md
CHANGED
|
@@ -10,13 +10,13 @@ Author: Bruce B. Anderson
|
|
|
10
10
|
|
|
11
11
|
Issues / pr's / polyfill: [mount-observer](https://github.com/bahrus/mount-observer)
|
|
12
12
|
|
|
13
|
-
Last Update:
|
|
13
|
+
Last Update: 2024-2-11
|
|
14
14
|
|
|
15
15
|
## Benefits of this API
|
|
16
16
|
|
|
17
|
-
What follows is a far more ambitious alternative to the [lazy custom element proposal](https://github.com/w3c/webcomponents/issues/782). The goals of the MountObserver api are more encompassing, and less focused on registering custom elements. In fact, this proposal addresses numerous use cases in one api. It is basically mapping common filtering conditions in the DOM, to
|
|
17
|
+
What follows is a far more ambitious alternative to the [lazy custom element proposal](https://github.com/w3c/webcomponents/issues/782). The goals of the MountObserver api are more encompassing, and less focused on registering custom elements. In fact, this proposal addresses numerous use cases in one api. It is basically mapping common filtering conditions in the DOM, to mounting a "campaign" of some sort, like importing a resource, and/or progressively enhancing an element, and/or "binding from a distance".
|
|
18
18
|
|
|
19
|
-
"Binding from a distance" refers to empowering the developer to essentially manage their own "stylesheets" -- but rather than for purposes of styling, using these rules to attach behaviors, set property values, etc.
|
|
19
|
+
["Binding from a distance"](https://github.com/WICG/webcomponents/issues/1035#issuecomment-1806393525) refers to empowering the developer to essentially manage their own "stylesheets" -- but rather than for purposes of styling, using these rules to attach behaviors, set property values, etc, to the HTML as it streams in. Libraries that take this approach include [Corset](https://corset.dev/) and [trans-render](https://github.com/bahrus/trans-render). The concept has been promoted by a [number](https://bkardell.com/blog/CSSLike.html) [of](https://www.w3.org/TR/NOTE-AS) prominent voices in the community.
|
|
20
20
|
|
|
21
21
|
The underlying theme is this api is meant to make it easy for the developer to do the right thing, by encouraging lazy loading and smaller footprints. It rolls up most all the other observer api's into one.
|
|
22
22
|
|
|
@@ -36,11 +36,10 @@ The amount of code necessary to accomplish these common tasks designed to improv
|
|
|
36
36
|
|
|
37
37
|
1. Give the developer a strong signal to do the right thing, by
|
|
38
38
|
1. Making lazy loading of resource dependencies easy, to the benefit of users with expensive networks.
|
|
39
|
-
2. Supporting "binding from a distance" that can
|
|
40
|
-
3. Supporting "progressive enhancement.
|
|
41
|
-
2.
|
|
42
|
-
3.
|
|
43
|
-
4. 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?
|
|
39
|
+
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
|
+
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 surfacing up the developer "found one!".
|
|
42
|
+
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?
|
|
44
43
|
|
|
45
44
|
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.
|
|
46
45
|
|
|
@@ -50,12 +49,12 @@ To specify the equivalent of what the alternative proposal linked to above would
|
|
|
50
49
|
|
|
51
50
|
```JavaScript
|
|
52
51
|
const observer = new MountObserver({
|
|
53
|
-
|
|
52
|
+
on:'my-element',
|
|
54
53
|
import: './my-element.js',
|
|
55
54
|
do: {
|
|
56
|
-
|
|
55
|
+
mount: ({localName}, {module}) => {
|
|
57
56
|
if(!customElements.get(localName)) {
|
|
58
|
-
customElements.define(localName, module.
|
|
57
|
+
customElements.define(localName, module.MyElement);
|
|
59
58
|
}
|
|
60
59
|
}
|
|
61
60
|
}
|
|
@@ -67,36 +66,41 @@ If no import is specified, it would go straight to do.* (if any such callbacks a
|
|
|
67
66
|
|
|
68
67
|
This only searches for elements matching 'my-element' outside any shadow DOM.
|
|
69
68
|
|
|
70
|
-
But the observe method can accept a shadowRoot, or a node inside a shadowRoot as well.
|
|
69
|
+
But the observe method can accept a node within the document, or a shadowRoot, or a node inside a shadowRoot as well.
|
|
71
70
|
|
|
72
71
|
The import can also be a function:
|
|
73
72
|
|
|
74
73
|
```JavaScript
|
|
75
74
|
const observer = new MountObserver({
|
|
76
|
-
|
|
75
|
+
on: 'my-element',
|
|
77
76
|
import: async (matchingElement, {module}) => await import('./my-element.js');
|
|
78
77
|
});
|
|
79
78
|
observer.observe(myRootNode);
|
|
80
79
|
```
|
|
81
80
|
|
|
82
|
-
which would work better with current bundlers, I suspect. Also, we can do interesting things like merge multiple imports into one "module". But should this API built into the platform, such functions wouldn't be necessary, as bundlers could start to recognize strings that are passed to the MountObserver's constructor.
|
|
81
|
+
which would work better with current bundlers, I suspect. Also, we can do interesting things like merge multiple imports into one "module". But should this API be built into the platform, such functions wouldn't be necessary, as bundlers could start to recognize strings that are passed to the MountObserver's constructor.
|
|
83
82
|
|
|
84
|
-
This proposal would also include support for CSS, JSON, HTML module imports.
|
|
85
|
-
|
|
86
|
-
"match" is a css query, and could include multiple matches using the comma separator, i.e. no limitation on CSS expressions.
|
|
83
|
+
This proposal would also include support for CSS, JSON, HTML module imports.
|
|
87
84
|
|
|
88
85
|
The "observer" constant above is a class instance that inherits from EventTarget, which means it can be subscribed to by outside interests.
|
|
89
86
|
|
|
90
|
-
|
|
87
|
+
## Binding from a distance
|
|
88
|
+
|
|
89
|
+
It is important to note that "on" is a css query with no restrictions. So something like:
|
|
91
90
|
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
91
|
+
```JavaScript
|
|
92
|
+
const observer = new MountObserver({
|
|
93
|
+
on:'div > p + p ~ span[class$="name"]',
|
|
94
|
+
do:{
|
|
95
|
+
mount: (matchingElement) => {
|
|
96
|
+
//attach some behavior or set some property value or add an event listener, etc.
|
|
97
|
+
matchingElement.textContent = 'hello';
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
})
|
|
97
101
|
```
|
|
98
102
|
|
|
99
|
-
This
|
|
103
|
+
This would allow developers to create "stylesheet" like capabilities.
|
|
100
104
|
|
|
101
105
|
|
|
102
106
|
## Extra lazy loading
|
|
@@ -107,7 +111,7 @@ However, we could make the loading even more lazy by specifying intersection opt
|
|
|
107
111
|
|
|
108
112
|
```JavaScript
|
|
109
113
|
const observer = new MountObserver({
|
|
110
|
-
|
|
114
|
+
on: 'my-element',
|
|
111
115
|
whereElementIntersectsWith:{
|
|
112
116
|
rootMargin: "0px",
|
|
113
117
|
threshold: 1.0,
|
|
@@ -122,27 +126,31 @@ Unlike traditional CSS @import, CSS Modules don't support specifying different i
|
|
|
122
126
|
|
|
123
127
|
```JavaScript
|
|
124
128
|
const observer = new MountObserver({
|
|
125
|
-
|
|
129
|
+
on: 'div > p + p ~ span[class$="name"]',
|
|
126
130
|
whereMediaMatches: '(max-width: 1250px)',
|
|
127
131
|
whereSizeOfContainerMatches: '(min-width: 700px)',
|
|
128
132
|
whereInstanceOf: [HTMLMarqueeElement],
|
|
129
133
|
whereSatisfies: async (matchingElement, context) => true,
|
|
134
|
+
whereLangIn: ['en-GB'],
|
|
135
|
+
whereConnection:{
|
|
136
|
+
effectiveTypeIn: ["slow-2g"],
|
|
137
|
+
},
|
|
130
138
|
import: ['./my-element-small.css', {type: 'css'}],
|
|
131
139
|
do: {
|
|
132
|
-
|
|
140
|
+
mount: ({localName}, {module}) => {
|
|
133
141
|
...
|
|
134
142
|
},
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
143
|
+
dismount: ...,
|
|
144
|
+
disconnect: ...,
|
|
145
|
+
reconnect: ...,
|
|
146
|
+
reconfirm: ...,
|
|
147
|
+
exit: ...,
|
|
148
|
+
forget: ...,
|
|
141
149
|
}
|
|
142
150
|
})
|
|
143
151
|
```
|
|
144
152
|
|
|
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
|
|
153
|
+
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 useful to also provide the ability for multiple parties to subscribe to these css rules. This can be done via:
|
|
146
154
|
|
|
147
155
|
## Subscribing
|
|
148
156
|
|
|
@@ -155,21 +163,22 @@ observer.addEventListener('mount', e => {
|
|
|
155
163
|
module: e.module
|
|
156
164
|
});
|
|
157
165
|
});
|
|
158
|
-
|
|
159
166
|
observer.addEventListener('dismount', e => {
|
|
160
167
|
...
|
|
161
168
|
});
|
|
162
|
-
|
|
163
|
-
observer.addEventListener('reconnect', e => {
|
|
169
|
+
observer.addEventListener('disconnect', e => {
|
|
164
170
|
...
|
|
165
171
|
});
|
|
166
|
-
observer.addEventListener('
|
|
172
|
+
observer.addEventListener('reconnect', e => {
|
|
167
173
|
...
|
|
168
174
|
});
|
|
169
175
|
observer.addEventListener('reconfirm', e => {
|
|
170
176
|
...
|
|
171
177
|
});
|
|
172
|
-
observer.addEventListener('
|
|
178
|
+
observer.addEventListener('exit', e => {
|
|
179
|
+
...
|
|
180
|
+
});
|
|
181
|
+
observer.addEventListener('forget', e => {
|
|
173
182
|
...
|
|
174
183
|
});
|
|
175
184
|
```
|
|
@@ -184,35 +193,162 @@ If an element that is in "mounted" state according to a MountObserver instance i
|
|
|
184
193
|
|
|
185
194
|
1) "disconnect" event is dispatched from the MountObserver instance the moment the mounted element is disconnected from the DOM fragment.
|
|
186
195
|
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]
|
|
187
|
-
3) If the mounted element is added outside the rootNode being observed, the mountObserver instance will dispatch event "
|
|
188
|
-
4)
|
|
189
|
-
5) If the
|
|
196
|
+
3) If the mounted element is added outside the rootNode being observed, the mountObserver instance will dispatch event "exit", and the MountObserver instance will relinquish any further responsibility for this element.
|
|
197
|
+
4) Ideally event "forget" would be dispatched just before the platform garbage collects an element the MountObserver instance is still monitoring, after all hard references are relinquished (or is that self-contradictory?).
|
|
198
|
+
5) If the new place it was added remains within the original rootNode and remains mounted, the MountObserver instance dispatches event "reconfirmed".
|
|
199
|
+
6) If the element no longer satisfies the criteria of the MountObserver instance, the MountObserver instance will dispatch event "dismount".
|
|
200
|
+
|
|
201
|
+
## A tribute to attributes
|
|
202
|
+
|
|
203
|
+
Extra support is provided for monitoring attributes. There are two primary reasons for needing to provide special support for attributes with this API:
|
|
190
204
|
|
|
191
|
-
|
|
205
|
+
Being that for both custom elements, as well as (hopefully) [custom enhancements](https://github.com/WICG/webcomponents/issues/1000) we need to carefully work with sets of "owned" [observed](https://github.com/WICG/webcomponents/issues/1045) attributes, and in some cases we may need to manage combinations of prefixes and suffixes for better name-spacing management, creating the most effective css query becomes challenging.
|
|
192
206
|
|
|
193
|
-
|
|
207
|
+
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
|
+
|
|
209
|
+
### Scenario 1 -- Custom Element integration with ObserveObservableAttributes API [WIP]
|
|
194
210
|
|
|
195
211
|
Example:
|
|
196
212
|
|
|
197
213
|
```html
|
|
198
214
|
<div id=div>
|
|
199
|
-
<
|
|
215
|
+
<my-custom-element my-first-observed-attribute="hello"></my-custom-element>
|
|
200
216
|
</div>
|
|
201
217
|
<script type=module>
|
|
202
218
|
import {MountObserver} from '../MountObserver.js';
|
|
203
219
|
const mo = new MountObserver({
|
|
204
|
-
|
|
205
|
-
|
|
220
|
+
on: '*',
|
|
221
|
+
whereInstanceOf: [MyCustomElement]
|
|
222
|
+
});
|
|
223
|
+
mo.addEventListener('parsed-attrs-changed', e => {
|
|
224
|
+
const {matchingElement, modifiedObjectFieldValues, preModifiedFieldValues} = e;
|
|
225
|
+
console.log({matchingElement, modifiedObjectFieldValues, preModifiedFieldValues});
|
|
226
|
+
});
|
|
227
|
+
mo.observe(div);
|
|
228
|
+
setTimeout(() => {
|
|
229
|
+
const myCustomElement = document.querySelector('my-custom-element');
|
|
230
|
+
myCustomElement.setAttribute('my-first-observed-attribute', 'good-bye');
|
|
231
|
+
}, 1000);
|
|
232
|
+
</script>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
### Scenario 2 -- Custom Enhancements in userland
|
|
237
|
+
|
|
238
|
+
Based on [the proposal as it currently stands](https://github.com/WICG/webcomponents/issues/1000), in this case the class prototype would *not* have the attributes defined as a static property of the class, so that the constructor arguments in the previous scenario wouldn't be sufficient. So instead, what would seem to provide the most help for providing for custom enhancements in userland, and for any other kind of progressive enhancement based on attributes going forward.
|
|
239
|
+
|
|
240
|
+
Suppose we have a progressive enhancement that we want to apply based on the presence of 1 or more attributes.
|
|
241
|
+
|
|
242
|
+
To make this discussion concrete, let's suppose the "canonical" names of those attributes are:
|
|
243
|
+
|
|
244
|
+
```html
|
|
245
|
+
<div id=div>
|
|
246
|
+
<section
|
|
247
|
+
my-enhancement=greetings
|
|
248
|
+
my-enhancement-first-aspect=hello
|
|
249
|
+
my-enhancement-second-aspect=goodbye
|
|
250
|
+
></section>
|
|
251
|
+
</div>
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Now suppose we are worried about namespace clashes, plus we want to serve environments where HTML5 compliance is a must.
|
|
255
|
+
|
|
256
|
+
So we also want to recognize additional attributes that should map to these canonical attributes:
|
|
257
|
+
|
|
258
|
+
We want to also support:
|
|
259
|
+
|
|
260
|
+
```html
|
|
261
|
+
<div id=div>
|
|
262
|
+
<section class=hello
|
|
263
|
+
data-my-enhancement=greetings
|
|
264
|
+
data-my-enhancement-first-aspect=hello
|
|
265
|
+
data-my-enhancement-second-aspect=goodbye
|
|
266
|
+
></section>
|
|
267
|
+
</div>
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
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
|
+
|
|
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-first-aspect? (Okay, with this particular example, the names are so long and generic it's unlikely, but who would ever use such a long, generic name in practice?)
|
|
273
|
+
|
|
274
|
+
So let's say we want to insist that on custom elements, we must have the dat- prefix?
|
|
275
|
+
|
|
276
|
+
And we want to support an alternative prefix to data, say enh-*, endorsed by [this proposal](https://github.com/WICG/webcomponents/issues/1000).
|
|
277
|
+
|
|
278
|
+
Here's what the api provides:
|
|
279
|
+
|
|
280
|
+
## Option 1 -- The carpal syndrome syntax
|
|
281
|
+
|
|
282
|
+
```JavaScript
|
|
283
|
+
import {MountObserver} from '../MountObserver.js';
|
|
284
|
+
const mo = new MountObserver({
|
|
285
|
+
on: '*',
|
|
286
|
+
whereAttr:{
|
|
287
|
+
isIn: [
|
|
288
|
+
'data-my-enhancement',
|
|
289
|
+
'data-my-enhancement-first-aspect',
|
|
290
|
+
'data-my-enhancement-second-aspect',
|
|
291
|
+
'enh-my-enhancement',
|
|
292
|
+
'enh-my-enhancement-first-aspect',
|
|
293
|
+
'enh-my-enhancement-second-aspect',
|
|
206
294
|
{
|
|
207
|
-
|
|
295
|
+
name: 'my-enhancement',
|
|
296
|
+
builtIn: true
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
name: 'my-enhancement-first-attr',
|
|
300
|
+
builtIn: true
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
name: 'my-enhancement-second-aspect',
|
|
304
|
+
builtIn: true
|
|
208
305
|
}
|
|
209
306
|
]
|
|
307
|
+
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Option 2 -- The DRY Way
|
|
313
|
+
|
|
314
|
+
```JavaScript
|
|
315
|
+
import {MountObserver} from '../MountObserver.js';
|
|
316
|
+
const mo = new MountObserver({
|
|
317
|
+
on: '*',
|
|
318
|
+
whereAttr:{
|
|
319
|
+
withFirstName: 'my-enhancement',
|
|
320
|
+
andQualifiers: ['first-attr', 'second-attr', ''],
|
|
321
|
+
withStemsIn: ['data', 'enh', 'data-enh'],
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
MountObserver supports both approaches
|
|
327
|
+
|
|
328
|
+
```html
|
|
329
|
+
<div id=div>
|
|
330
|
+
<section class=hello my-enhancement-first-attr="hello"></section>
|
|
331
|
+
</div>
|
|
332
|
+
<script type=module>
|
|
333
|
+
import {MountObserver} from '../MountObserver.js';
|
|
334
|
+
const mo = new MountObserver({
|
|
335
|
+
on: '*',
|
|
336
|
+
whereAttr:{
|
|
337
|
+
hasPrefixesIn: ['enh-', 'data-enh-'],
|
|
338
|
+
hasSuffixesIn:[
|
|
339
|
+
'my-enhancement-first-attr',
|
|
340
|
+
'my-enhancement-second-aspect'
|
|
341
|
+
],
|
|
342
|
+
|
|
343
|
+
}
|
|
210
344
|
});
|
|
211
|
-
mo.addEventListener('attr-change', e => {
|
|
345
|
+
mo.addEventListener('observed-attr-change', e => {
|
|
212
346
|
console.log(e);
|
|
213
347
|
// {
|
|
348
|
+
// matchingElement,
|
|
214
349
|
// attrChangeInfo:{
|
|
215
|
-
//
|
|
350
|
+
// fullName: 'data-enh-my-first-enhancement-attr',
|
|
351
|
+
// suffix: 'my-first-enhancement-attr',
|
|
216
352
|
// oldValue: null,
|
|
217
353
|
// newValue: 'hello'
|
|
218
354
|
// idx: 0,
|
|
@@ -221,13 +357,12 @@ Example:
|
|
|
221
357
|
});
|
|
222
358
|
mo.observe(div);
|
|
223
359
|
setTimeout(() => {
|
|
224
|
-
|
|
360
|
+
const myCustomElement = document.querySelector('my-custom-element');
|
|
361
|
+
myCustomElement.setAttribute('my-first-observed-attribute', 'good-bye');
|
|
225
362
|
}, 1000);
|
|
226
363
|
</script>
|
|
227
364
|
```
|
|
228
365
|
|
|
229
|
-
|
|
230
|
-
|
|
231
366
|
## Preemptive downloading
|
|
232
367
|
|
|
233
368
|
There are two significant steps to imports, each of which imposes a cost:
|
|
@@ -243,11 +378,11 @@ So for this we add option:
|
|
|
243
378
|
|
|
244
379
|
```JavaScript
|
|
245
380
|
const observer = new MountObserver({
|
|
246
|
-
|
|
381
|
+
on: 'my-element',
|
|
247
382
|
loading: 'eager',
|
|
248
383
|
import: './my-element.js',
|
|
249
384
|
do:{
|
|
250
|
-
|
|
385
|
+
mount: (matchingElement, {module}) => customElements.define(module.MyElement)
|
|
251
386
|
}
|
|
252
387
|
})
|
|
253
388
|
```
|
package/package.json
CHANGED
package/types.d.ts
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
export interface MountInit{
|
|
2
|
-
readonly
|
|
3
|
-
readonly attribMatches?: Array<AttribMatch>,
|
|
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
|
+
},
|
|
4
12
|
readonly whereElementIntersectsWith?: IntersectionObserverInit,
|
|
5
13
|
readonly whereMediaMatches?: MediaQuery,
|
|
6
14
|
readonly whereInstanceOf?: Array<typeof Node>, //[TODO] What's the best way to type this?,
|
|
7
15
|
readonly whereSatisfies?: PipelineProcessor<boolean>,
|
|
8
16
|
readonly import?: ImportString | [ImportString, ImportAssertions] | PipelineProcessor,
|
|
9
17
|
readonly do?: {
|
|
10
|
-
readonly
|
|
11
|
-
readonly
|
|
12
|
-
readonly
|
|
13
|
-
readonly
|
|
14
|
-
readonly
|
|
18
|
+
readonly mount?: PipelineProcessor,
|
|
19
|
+
readonly dismount?: PipelineProcessor,
|
|
20
|
+
readonly disconnect?: PipelineProcessor,
|
|
21
|
+
readonly reconfirm?: PipelineProcessor,
|
|
22
|
+
readonly exit?: PipelineProcessor,
|
|
15
23
|
}
|
|
16
24
|
// /**
|
|
17
25
|
// * Purpose -- there are scenarios where we may only want to affect changes that occur after the initial
|