mount-observer 0.0.27 → 0.0.29

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/README.md CHANGED
@@ -11,7 +11,7 @@ Author: Bruce B. Anderson (with valuable feedback from @doeixd )
11
11
 
12
12
  Issues / pr's / polyfill: [mount-observer](https://github.com/bahrus/mount-observer)
13
13
 
14
- Last Update: 2024-5-27
14
+ Last Update: 2024-6-11
15
15
 
16
16
  ## Benefits of this API
17
17
 
@@ -19,7 +19,7 @@ What follows is a far more ambitious alternative to the [lazy custom element pro
19
19
 
20
20
  ["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](https://www.xanthir.com/blog/b4K_0) voices in the community.
21
21
 
22
- 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, including, potentially, [this one](https://github.com/whatwg/dom/issues/1285).
22
+ 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, including, potentially, [this one](https://github.com/whatwg/dom/issues/1285), which may be a similar duplicate to [that one](https://github.com/whatwg/dom/issues/1225).
23
23
 
24
24
 
25
25
  ### Does this api make the impossible possible?
@@ -72,7 +72,7 @@ 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.
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. If multiple "on" selectors are provided, and multiple ones match, I think it makes sense to indicate the one with the highest specifier that matches. It would probably be helpful in this case to provide a special event that allows for knowing when the matching selector with the highest specificity changes for mounted elements.
76
76
 
77
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.
78
78
 
@@ -92,7 +92,7 @@ const observer = new MountObserver({
92
92
  import: [
93
93
  ['./my-element-small.css', {type: 'css'}],
94
94
  './my-element.js',
95
- ]
95
+ ],
96
96
  do: {
97
97
  mount: ({localName}, {modules, observer}) => {
98
98
  if(!customElements.get(localName)) {
@@ -118,14 +118,14 @@ This proposal would also include support for JSON and HTML module imports.
118
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:
119
119
 
120
120
  ```html
121
- <script type="mountobserver" id=myMountObserver onmount="
121
+ <script type="mountobserver" id=myMountObserver onmount="{
122
122
  const {matchingElement} = event;
123
123
  const {localName} = matchingElement;
124
124
  if(!customElements.get(localName)) {
125
125
  customElements.define(localName, modules[1].MyElement);
126
126
  }
127
127
  observer.disconnect();
128
- ">
128
+ }">
129
129
  {
130
130
  "on":"my-element",
131
131
  "import": [
@@ -136,7 +136,7 @@ Following an approach similar to the [speculation api](https://developer.chrome.
136
136
  </script>
137
137
  ```
138
138
 
139
- The things that make this API work together, namely the "modules", "observer", and "mountedElements" (an array of an array of weak refs to elements that match all the criteria for the ith "on" selector) would be accessible as properties of the script element:
139
+ The things that make this API work together, namely the "modules", "observer", and "mountedElements" (an array of an array of weak refs to elements that match all the criteria for the i<sup>th</sup> "on" selector) would be accessible as properties of the script element:
140
140
 
141
141
  ```JavaScript
142
142
  const {modules, observer, mountedElements, mountInit} = myMountObserver;
@@ -144,7 +144,7 @@ const {modules, observer, mountedElements, mountInit} = myMountObserver;
144
144
 
145
145
  The "scope" of the observer would be the ShadowRoot containing the script element (or the document outside Shadow if placed outside any shadow DOM, like in the head element).
146
146
 
147
- No arrays of settings would be supported within a single tag (as this causes issues as far as supporting a single onmount, ondismount, etc event attributes), but remember that the on criteria can be an array of selectors.
147
+ No arrays of settings would be supported within a single tag (as this causes issues as far as supporting a single onmount, ondismount, etc event attributes), but remember that the "on" criteria can be an array of selectors.
148
148
 
149
149
  ## Shadow Root inheritance
150
150
 
@@ -230,7 +230,7 @@ const observer = new MountObserver({
230
230
  exit: ...,
231
231
  forget: ...,
232
232
  }
233
- })
233
+ });
234
234
  ```
235
235
 
236
236
  Callbacks like we see above are useful for tight coupling, and probably are unmatched in terms of performance. The expression that the "do" field points to could also be a (stateful) user defined class instance.
@@ -337,11 +337,15 @@ We want to be alerted by the discovery of elements adorned by these attributes,
337
337
 
338
338
  I think it is useful to divide [attributes](https://jakearchibald.com/2024/attributes-vs-properties/) that we would want to observe into two categories:
339
339
 
340
- 1. Invariably named, prefix-less, "top-level" attributes that serve as the "source of truth" for key features of the DOM element itself. We will refer to these attributes as "Source of Truth" attributes.
340
+ 1. Invariably named, prefix-less, "top-level" attributes that serve as the "source of truth" for key features of the DOM element itself. We will refer to these attributes as "Source of Truth" attributes. Please don't read too much into the name. Whether the platform or custom element author developer chooses to make properties reflect to attributes, or attributes reflect to the properties, or some hybrid of some sort, is immaterial here.
341
+
342
+ By invariably named, I mean the name will be the same in all Shadow DOM realms.
341
343
 
342
- Examples are many built-in global attributes, like lang, or contenteditable, or more specialized examples such as "content" for the meta tag. I think in the vast majority of cases, setting the property values corresponding to these attributes results in directly reflecting those property values to the attributes. There are exceptions, especially for non-string attributes like the checked property of the input element / type=checkbox, and JSON based attributes for custom elements.
344
+ Examples are many built-in global attributes, like lang, or contenteditable, or more specialized examples such as "content" for the meta tag. It could also include attributes of third party custom elements we want to enhance in a cross-cutting way.
343
345
 
344
- Usually, that are no events we can subscribe to in order to know when the property changes. Hijacking the property setter in order to observe changes may not always work or feel very resilient. So monitoring the attribute value associated with the property is often the most effective way of observing when the property/attribute state for these elements change. And some attributes (like the microdata attributes such as itemprop) don't even have properties that they pair with!
346
+ I think in the vast majority of cases, setting the property values corresponding to these attributes results in directly reflecting those property values to the attributes (and vice versa). There are exceptions, especially for non-string attributes like the checked property of the input element / type=checkbox, and JSON based attributes for custom elements.
347
+
348
+ Usually, there are no events we can subscribe to in order to know when the property changes. Hijacking the property setter in order to observe changes may not always work or feel very resilient. So monitoring the attribute value associated with the property is often the most effective way of observing when the property/attribute state for these elements change. And some attributes (like the microdata attributes such as itemprop) don't even have properties that they pair with!
345
349
 
346
350
 
347
351
  2. In contrast, there are scenarios where we want to support somewhat fluid, renamable attributes within different Shadow DOM scopes, which add behavior/enhancement capabilities on top of built-in or third party custom elements. We'll refer to these attributes as "Enhancement Attributes."
@@ -717,7 +721,7 @@ This proposal (and polyfill) also supports the option to utilize ShadowDOM / slo
717
721
  <compose src="#productCard"></compose>
718
722
  ```
719
723
 
720
- 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.
724
+ 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. 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.
721
725
 
722
726
  ## Creating "frameworks" that revolve around MOSEs.
723
727
 
@@ -725,28 +729,34 @@ Often, we will want to define a large number of "mount observer script elements
725
729
 
726
730
  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.
727
731
 
728
- 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 MOSEs 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.
732
+ 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 MOSEs 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 realm.
729
733
 
730
734
  To support this, we propose these highlights:
731
735
 
732
736
  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.
733
737
  2. As the *synthesize* method is called repeatedly from different packages that work within that framework, it creates a cluster of MOSEs wrapped inside the "synthesizing" 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.
734
738
 
735
- 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.
739
+ So framework developers can develop a bespoke custom element that inherits from the "abstract" class "*Synthesizer*" that is part of this package / proposal, that is used to group families of MountObserver's together.
740
+
741
+ Some attributes that the base "Synthesizer" supports are listed below. They are all related to allowing individual ShadowDOM realms to be able to easily opt in or opt out, depending on the level of control/trust that is exerted by a web component / Shadow Root, as far as the HTML it imports in.
742
+
743
+ 1. passthrough. Allows for the inheritance of behaviors to flow through from above (or from the root document), while not actually activating any of them within the Shadow DOM realm itself.
744
+ 2. exclude. List of specific MOSE id's to block. Allows them to flow through to child Shadow Roots.
745
+ 3. include. List of specific MOSE id's to allow.
736
746
 
737
747
  What functionality do these "synthesizing" custom elements provide, what value-add proposition do they fulfill over what is built into the MountObserver polyfill / package?
738
748
 
739
749
  The sky is the limit, but focusing on the first example, be-hive, they are:
740
750
 
741
751
  1. Managing, interpreting and parsing the attributes that add semantic enhancement vocabularies onto exiting elements.
742
- 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.
752
+ 2. Establishing the "handshake" that imports the enhancement package, instantiates the enhancement, and passes properties that were previously assigned to the pre-enhanced element to the attached enhancement/behavior.
743
753
 
744
754
  If one inspects the DOM, one will see grouped (already "parsed") MOSEs, like so:
745
755
 
746
756
  ```html
747
757
  <be-hive>
748
- <script type=mountobserver id=be-searching></script>
749
- <script type=mountobserver id=be-counted></script>
758
+ <script type=mountobserver id=be-hive.be-searching></script>
759
+ <script type=mountobserver id=be-hive.be-counted></script>
750
760
  </be-hive>
751
761
  ```
752
762
 
@@ -755,7 +765,7 @@ Without the help of the synthesize method / Synthesizer base class, the develope
755
765
  The developer of each package defines their MOSE "template", and then syndicates it via the synthesize method:
756
766
 
757
767
  ```JavaScript
758
- mountObserver.synthesize(rootNode, BeHive, mose)
768
+ mountObserver.synthesize(root: document | shadowRootNode, ctr: ({new() => Synthesizer}), mose: MOSE)
759
769
  ```
760
770
 
761
771
  What this method does is it:
@@ -775,7 +785,7 @@ And we can give each inheriting ShadowRoot a personality of its own by customizi
775
785
 
776
786
  ```html
777
787
  <be-hive>
778
- <script type=mountobserver id=be-searching>
788
+ <script type=mountobserver id=be-hive.be-searching>
779
789
  {
780
790
  ...my custom settings
781
791
  }
package/Synthesizer.js CHANGED
@@ -30,8 +30,26 @@ export class Synthesizer extends HTMLElement {
30
30
  this.#mutationObserver.observe(this, init);
31
31
  this.inherit();
32
32
  }
33
+ checkIfAllowed(mose) {
34
+ if (this.hasAttribute('passthrough'))
35
+ return false;
36
+ const { id } = mose;
37
+ if (this.hasAttribute('include')) {
38
+ const split = this.getAttribute('include').split(' ');
39
+ if (!split.includes(id))
40
+ return false;
41
+ }
42
+ if (this.hasAttribute('exclude')) {
43
+ const split = this.getAttribute('exclude').split(' ');
44
+ if (split.includes(id))
45
+ return false;
46
+ }
47
+ return true;
48
+ }
33
49
  activate(mose) {
34
- const { init, do: d, id } = mose;
50
+ if (!this.checkIfAllowed(mose))
51
+ return;
52
+ const { init, do: d } = mose;
35
53
  const mi = {
36
54
  do: d,
37
55
  ...init
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mount-observer",
3
- "version": "0.0.27",
3
+ "version": "0.0.29",
4
4
  "description": "Observe and act on css matches.",
5
5
  "main": "MountObserver.js",
6
6
  "module": "MountObserver.js",
package/types.d.ts CHANGED
@@ -194,6 +194,7 @@ export interface MOSE<TSynConfig=any>
194
194
  extends HTMLScriptElement, MOSEAddedProps<TSynConfig>{
195
195
 
196
196
  }
197
+
197
198
  //#endregion
198
199
 
199
200