mount-observer 0.0.22 → 0.0.24
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 +2 -1
- package/README.md +40 -29
- package/Synthesizer.js +31 -18
- package/package.json +1 -1
- package/types.d.ts +9 -3
package/MountObserver.js
CHANGED
|
@@ -181,7 +181,8 @@ export class MountObserver extends EventTarget {
|
|
|
181
181
|
}, { signal: this.#abortController.signal });
|
|
182
182
|
await this.#inspectWithin(within, true);
|
|
183
183
|
}
|
|
184
|
-
synthesize(within, customElement, mose) {
|
|
184
|
+
static synthesize(within, customElement, mose) {
|
|
185
|
+
mose.type = 'mountobserver';
|
|
185
186
|
const name = customElements.getName(customElement);
|
|
186
187
|
if (name === null)
|
|
187
188
|
throw 400;
|
package/README.md
CHANGED
|
@@ -72,6 +72,8 @@ Invoking "disconnect" as shown above causes the observer to emit event "disconne
|
|
|
72
72
|
|
|
73
73
|
The argument can also be an array of objects that fit the pattern shown above.
|
|
74
74
|
|
|
75
|
+
In fact, as we will see, where it makes sense, where we see examples that are strings, we will also allow for arrays of such strings. For example, the "on" key can point to an array of CSS selectors (and in this case the mount/dismount callbacks would need to provide an index of which one matched). I only recommend adding this complexity if what I suspect is true -- providing this support can reduce "context switching" between threads / memory spaces (c++ vs JavaScript), and thus improve performance.
|
|
76
|
+
|
|
75
77
|
If no imports are specified, it would go straight to do.* (if any such callbacks are specified), and it will also dispatch events as discussed below.
|
|
76
78
|
|
|
77
79
|
This only searches for elements matching 'my-element' outside any shadow DOM.
|
|
@@ -103,15 +105,15 @@ const observer = new MountObserver({
|
|
|
103
105
|
observer.observe(document);
|
|
104
106
|
```
|
|
105
107
|
|
|
106
|
-
|
|
108
|
+
Once again, the key can accept either a single import or it can also support multiple imports (via an array).
|
|
107
109
|
|
|
108
110
|
The do event won't be invoked until all the imports have been successfully completed and inserted into the modules array.
|
|
109
111
|
|
|
110
|
-
Previously, this proposal called for allowing arrow functions as well, thinking that could be a good interim way to support bundlers. But the valuable input provided by [doeixd](https://github.com/doeixd) makes me think that that interim support could
|
|
112
|
+
Previously, this proposal called for allowing arrow functions as well, thinking that could be a good interim way to support bundlers, as well as multiple imports. But the valuable input provided by [doeixd](https://github.com/doeixd) makes me think that that interim support could more effectively be done by the developer in the do methods.
|
|
111
113
|
|
|
112
114
|
This proposal would also include support for JSON and HTML module imports.
|
|
113
115
|
|
|
114
|
-
## MountObserver script
|
|
116
|
+
## MountObserver script elements (MOSEs)
|
|
115
117
|
|
|
116
118
|
Following an approach similar to the [speculation api](https://developer.chrome.com/blog/speculation-rules-improvements), we can add a script element anywhere in the DOM:
|
|
117
119
|
|
|
@@ -161,7 +163,7 @@ If no id is found in the parent ShadowRoot (or in the parent window if the shado
|
|
|
161
163
|
|
|
162
164
|
But if a matching id is found, then the values from the parent script element get merged in with the one in the child, with the child settings, including the event handling attributes.
|
|
163
165
|
|
|
164
|
-
We will come back to some [additional features](#
|
|
166
|
+
We will come back to some [additional features](#creating-frameworks-that-revolve-around-moses) of using these script elements later, but wanted to cover the highlights of this proposal before getting bogged down in some tedious logistics.
|
|
165
167
|
|
|
166
168
|
## Binding from a distance
|
|
167
169
|
|
|
@@ -323,7 +325,7 @@ The alternative to providing this feature, which I'm leaning towards, is to just
|
|
|
323
325
|
|
|
324
326
|
## A tribute to attributes
|
|
325
327
|
|
|
326
|
-
Attributes of DOM elements are tricky. They've been around since the get-go, and they've survived multiple generations
|
|
328
|
+
Attributes of DOM elements are tricky. They've been around since the get-go of the Web, and they've survived multiple generations, where different philosophies have prevailed, so prepare yourself for some subtle discussion in what follows.
|
|
327
329
|
|
|
328
330
|
Extra support is provided for monitoring attributes. There are two primary reasons for needing to provide special support for attributes with this API:
|
|
329
331
|
|
|
@@ -583,7 +585,6 @@ Tentative rules:
|
|
|
583
585
|
The thinking here is that longer roots indicate higher "specificity", so it is safer to use that one.
|
|
584
586
|
|
|
585
587
|
|
|
586
|
-
|
|
587
588
|
## Preemptive downloading
|
|
588
589
|
|
|
589
590
|
There are two significant steps to imports, each of which imposes a cost:
|
|
@@ -715,19 +716,29 @@ This proposal (and polyfill) also supports the option to utilize ShadowDOM / slo
|
|
|
715
716
|
|
|
716
717
|
The discussion there leads to an open question whether a processing instruction would be better. I think the compose tag would make much more sense, vs a processing instruction, as it could then support slotted children (behaving similar to the Beatles' example above). Or maybe another tag should be introduced that is the equivalent of the slot, to avoid confusion. or some equivalent. But I strongly suspect that could significantly reduce the payload size of some documents, if we can reuse blocks of HTML, inserting sections of customized content for each instance.
|
|
717
718
|
|
|
718
|
-
##
|
|
719
|
+
## Creating "frameworks" that revolve around MOSEs.
|
|
720
|
+
|
|
721
|
+
Often, we will want to define a large number of "mount observer script elements (MOSEs)" programmatically, and we need it to be done in a generic way, that can be published and easily referenced.
|
|
719
722
|
|
|
720
|
-
|
|
723
|
+
This is a problem space that [be-hive](https://github.com/bahrus/be-hive) is grappling with, and is used as an example for this section, to simply make things more concrete. But we can certainly envision other "frameworks" that could leverage this feature for a variety of purposes, including other families of behaviors/enhancements, or "binding from a distance" syntaxes.
|
|
724
|
+
|
|
725
|
+
In particular, *be-hive* supports publishing [enhancements](https://github.com/bahrus/be-enhanced) that take advantage of the DOM filtering ability that the MountObserver provides, that "ties the knot" based on CSS matches in the DOM to behaviors/enhancements that we want to attach directly onto the matching elements. *be-hive" seeks to take advantage of the inheritable infrastructure that MountObserver script elements provide, but we don't want to burden the developer with having to manually list all these configurations, we want it to happen automatically, only expecting manual intervention when we need some special customizations within a specific ShadowDOM scope.
|
|
721
726
|
|
|
722
727
|
To support this, we propose these highlights:
|
|
723
728
|
|
|
724
|
-
1. Adding a "synthesize" method to the MountObserver api
|
|
725
|
-
2. Synthesize method
|
|
729
|
+
1. Adding a static "synthesize" method to the MountObserver api. This would provide a kind of passage-way from the imperative api to the declarative one.
|
|
730
|
+
2. As the Synthesize method is called repeatedly from different packages that work with that framework, it creates a cluster of MOSEs wrapped inside a custom element ("be-hive") that the framework developer authors. It appends script elements with type="mountobserver" to the custom element instance sitting in the DOM, that dispatches events from the synthesizing custom element it gets appended to, so subscribers in child Shadow DOM's don't need to add a general mutation observer in order to know when parent shadow roots had a MOSE inserted that it needs to act on. This allows the child Shadow DOM's to inherit (in this case) behaviors/enhancements from the parent Shadow DOM.
|
|
731
|
+
|
|
732
|
+
So framework developers can develop a bespoke custom element that inherits from the class "*Synthesizer*" that is part of this package / proposal, that is used to group families of MountObservers together.
|
|
726
733
|
|
|
734
|
+
What functionality do these "synthesizing" custom elements provide, what value-add proposition do they fulfill over what is built into the MountObserver polyfill / package?
|
|
727
735
|
|
|
728
|
-
|
|
736
|
+
The sky is the limit, but focusing on the first example, be-hive, they are:
|
|
729
737
|
|
|
730
|
-
|
|
738
|
+
1. Managing, interpreting and parsing the attributes that add semantic enhancement vocabularies onto exiting elements.
|
|
739
|
+
2. Establishing the "handshake" that imports the enhancement package, instantiates the enhancement, and passes properties that wee previously assigned to the pre-enhanced element to the attached enhancement/behavior.
|
|
740
|
+
|
|
741
|
+
If one inspects the DOM, one will see grouped (already "parsed") MOSEs, like so:
|
|
731
742
|
|
|
732
743
|
```html
|
|
733
744
|
<be-hive>
|
|
@@ -736,29 +747,19 @@ If one inspects the DOM, one would see grouped (already "parsed") MountObservers
|
|
|
736
747
|
</be-hive>
|
|
737
748
|
```
|
|
738
749
|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
Instead, the framework developer would define a custom element that inherits from base class that this proposal/polyfill provides.
|
|
750
|
+
Without the help of the synthesize method / Synthesizer base class, the developer would need to set these up manually, so this lifts a significant burden from the shoulders of people who want to leverage these behaviors/enhancements in a seamless way.
|
|
742
751
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
Then rather than invoking:
|
|
752
|
+
The developer of each package defines their MOSE "template", and then syndicates it via the synthesize method:
|
|
746
753
|
|
|
747
754
|
```JavaScript
|
|
748
|
-
mountObserver.
|
|
755
|
+
mountObserver.synthesize(rootNode, BeHive, mose)
|
|
749
756
|
```
|
|
750
757
|
|
|
751
|
-
|
|
758
|
+
What this method does is it:
|
|
752
759
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
The MountObserver api would:
|
|
758
|
-
|
|
759
|
-
1. Use [customElements.getName](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/getName) to get the name of the custom element (say it is 'be-hive').
|
|
760
|
-
2. Search for a be-hive tag inside the root node (with special logic for the "head" element). If not found, create it.
|
|
761
|
-
3. Place the script element inside.
|
|
760
|
+
1. Uses [customElements.getName](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/getName) to get the name of the custom element (say it is 'be-hive') from the provided constructor.
|
|
761
|
+
2. Searches for a be-hive tag inside the root node (with special logic for the "head" element). If not found, creates it.
|
|
762
|
+
3. Places the MOSE inside.
|
|
762
763
|
|
|
763
764
|
|
|
764
765
|
Then in our shadowroot, rather than adding a script type=mountobserver for every single mount observer we want to inherit, we could reference the group via simply:
|
|
@@ -767,5 +768,15 @@ Then in our shadowroot, rather than adding a script type=mountobserver for every
|
|
|
767
768
|
<be-hive></be-hive>
|
|
768
769
|
```
|
|
769
770
|
|
|
771
|
+
And we can give each inheriting ShadowRoot a personality of its own by customizing the settings within that shadow scope, by (manually?) adding a MOSE with matching id that overrides the inheriting settings with custom settings:
|
|
770
772
|
|
|
773
|
+
```html
|
|
774
|
+
<be-hive>
|
|
775
|
+
<script type=mountobserver id=be-searching>
|
|
776
|
+
{
|
|
777
|
+
...my custom settings
|
|
778
|
+
}
|
|
779
|
+
</script>
|
|
780
|
+
</be-hive>
|
|
781
|
+
```
|
|
771
782
|
|
package/Synthesizer.js
CHANGED
|
@@ -6,12 +6,12 @@ export class Synthesizer extends HTMLElement {
|
|
|
6
6
|
for (const mutation of mutationList) {
|
|
7
7
|
const { addedNodes } = mutation;
|
|
8
8
|
for (const node of addedNodes) {
|
|
9
|
-
if (!(node instanceof HTMLScriptElement))
|
|
9
|
+
if (!(node instanceof HTMLScriptElement) || node.type !== 'mountobserver')
|
|
10
10
|
continue;
|
|
11
11
|
const mose = node;
|
|
12
12
|
this.mountObserverElements.push(mose);
|
|
13
|
-
this
|
|
14
|
-
const e = new
|
|
13
|
+
this.activate(mose);
|
|
14
|
+
const e = new SynthesizeEvent(mose);
|
|
15
15
|
this.dispatchEvent(e);
|
|
16
16
|
}
|
|
17
17
|
}
|
|
@@ -21,25 +21,35 @@ export class Synthesizer extends HTMLElement {
|
|
|
21
21
|
const init = {
|
|
22
22
|
childList: true
|
|
23
23
|
};
|
|
24
|
-
this
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
this.querySelectorAll('script[type="mountobserver"]').forEach(s => {
|
|
25
|
+
const mose = s;
|
|
26
|
+
this.mountObserverElements.push(mose);
|
|
27
|
+
this.activate(mose);
|
|
28
|
+
});
|
|
29
|
+
this.#mutationObserver = new MutationObserver(this.mutationCallback.bind(this));
|
|
30
|
+
this.#mutationObserver.observe(this, init);
|
|
31
|
+
this.inherit();
|
|
27
32
|
}
|
|
28
|
-
|
|
33
|
+
activate(mose) {
|
|
29
34
|
const { init, do: d, id } = mose;
|
|
30
|
-
const se = document.createElement('script');
|
|
31
|
-
se.init = init;
|
|
32
|
-
se.id = id;
|
|
33
|
-
se.do = d;
|
|
34
35
|
const mi = {
|
|
35
36
|
do: d,
|
|
36
37
|
...init
|
|
37
38
|
};
|
|
38
39
|
const mo = new MountObserver(mi);
|
|
39
|
-
|
|
40
|
+
mose.observer = mo;
|
|
41
|
+
mo.observe(this.getRootNode());
|
|
42
|
+
}
|
|
43
|
+
import(mose) {
|
|
44
|
+
const { init, do: d, id, synConfig } = mose;
|
|
45
|
+
const se = document.createElement('script');
|
|
46
|
+
se.init = { ...init };
|
|
47
|
+
se.id = id;
|
|
48
|
+
se.do = { ...d };
|
|
49
|
+
se.synConfig = { ...synConfig };
|
|
40
50
|
this.appendChild(se);
|
|
41
51
|
}
|
|
42
|
-
|
|
52
|
+
inherit() {
|
|
43
53
|
const rn = this.getRootNode();
|
|
44
54
|
const host = rn.host;
|
|
45
55
|
if (!host)
|
|
@@ -49,11 +59,11 @@ export class Synthesizer extends HTMLElement {
|
|
|
49
59
|
const parentScopeSynthesizer = parentShadowRealm.querySelector(localName);
|
|
50
60
|
const { mountObserverElements } = parentScopeSynthesizer;
|
|
51
61
|
for (const moe of mountObserverElements) {
|
|
52
|
-
this
|
|
62
|
+
this.import(moe);
|
|
53
63
|
}
|
|
54
64
|
if (parentScopeSynthesizer !== null) {
|
|
55
|
-
parentScopeSynthesizer.addEventListener(
|
|
56
|
-
this
|
|
65
|
+
parentScopeSynthesizer.addEventListener(SynthesizeEvent.eventName, e => {
|
|
66
|
+
this.import(e.mountObserverElement);
|
|
57
67
|
});
|
|
58
68
|
}
|
|
59
69
|
}
|
|
@@ -61,6 +71,9 @@ export class Synthesizer extends HTMLElement {
|
|
|
61
71
|
if (this.#mutationObserver !== undefined) {
|
|
62
72
|
this.#mutationObserver.disconnect();
|
|
63
73
|
}
|
|
74
|
+
for (const mose of this.mountObserverElements) {
|
|
75
|
+
mose.observer.disconnect(this.getRootNode());
|
|
76
|
+
}
|
|
64
77
|
}
|
|
65
78
|
}
|
|
66
79
|
// https://github.com/webcomponents-cg/community-protocols/issues/12#issuecomment-872415080
|
|
@@ -68,11 +81,11 @@ export class Synthesizer extends HTMLElement {
|
|
|
68
81
|
* The `mutation-event` event represents something that happened.
|
|
69
82
|
* We can document it here.
|
|
70
83
|
*/
|
|
71
|
-
export class
|
|
84
|
+
export class SynthesizeEvent extends Event {
|
|
72
85
|
mountObserverElement;
|
|
73
86
|
static eventName = 'synthesize';
|
|
74
87
|
constructor(mountObserverElement) {
|
|
75
|
-
super(
|
|
88
|
+
super(SynthesizeEvent.eventName);
|
|
76
89
|
this.mountObserverElement = mountObserverElement;
|
|
77
90
|
}
|
|
78
91
|
}
|
package/package.json
CHANGED
package/types.d.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
export interface JSONSerializableMountInit{
|
|
4
4
|
readonly on?: CSSMatch,
|
|
5
|
+
observedAttrsWhenMounted?: string[],
|
|
5
6
|
readonly whereAttr?: WhereAttr,
|
|
6
7
|
readonly whereElementIntersectsWith?: IntersectionObserverInit,
|
|
7
8
|
readonly whereMediaMatches?: MediaQuery,
|
|
@@ -37,7 +38,7 @@ export interface RootCnfg{
|
|
|
37
38
|
//export type RootAttrOptions = Array<string | RootCnfg>;
|
|
38
39
|
export type delimiter = string;
|
|
39
40
|
export interface WhereAttr{
|
|
40
|
-
|
|
41
|
+
|
|
41
42
|
hasBase?: string | [delimiter, string],
|
|
42
43
|
hasBranchIn?: Array<string> | [delimiter, Array<string>],
|
|
43
44
|
hasRootIn?: Array<RootCnfg>,
|
|
@@ -67,6 +68,7 @@ export interface IMountObserver {
|
|
|
67
68
|
disconnect(within: Node): void;
|
|
68
69
|
module?: any;
|
|
69
70
|
mountedElements: WeakSet<Element>;
|
|
71
|
+
readAttrs(match: Element, branchIndexes?: Set<number>) : AttrChangeInfo[]
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
export interface MountContext{
|
|
@@ -170,11 +172,15 @@ export interface AddLoadEventListener{
|
|
|
170
172
|
//#endregion
|
|
171
173
|
|
|
172
174
|
//#region MountObserver Script Element
|
|
173
|
-
export interface
|
|
175
|
+
export interface MOSEAddedProps<TSynConfig=any>{
|
|
174
176
|
init: JSONSerializableMountInit;
|
|
175
|
-
//mountedElements: Array<WeakRef<Element>>;
|
|
176
177
|
observer: IMountObserver;
|
|
177
178
|
do: MountObserverCallbacks;
|
|
179
|
+
synConfig: TSynConfig;
|
|
180
|
+
}
|
|
181
|
+
export interface MOSE<TSynConfig=any>
|
|
182
|
+
extends HTMLScriptElement, MOSEAddedProps<TSynConfig>{
|
|
183
|
+
|
|
178
184
|
}
|
|
179
185
|
//#endregion
|
|
180
186
|
|