mount-observer 0.0.24 → 0.0.26

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 CHANGED
@@ -31,7 +31,7 @@ export class MountObserver extends EventTarget {
31
31
  }
32
32
  #calculatedSelector;
33
33
  #attrParts;
34
- #fullListOfAttrs;
34
+ #fullListOfEnhancementAttrs;
35
35
  //get #attrVals
36
36
  async #selector() {
37
37
  if (this.#calculatedSelector !== undefined)
@@ -43,7 +43,7 @@ export class MountObserver extends EventTarget {
43
43
  const { getWhereAttrSelector } = await import('./getWhereAttrSelector.js');
44
44
  const info = await getWhereAttrSelector(whereAttr, withoutAttrs);
45
45
  const { fullListOfAttrs, calculatedSelector, partitionedAttrs } = info;
46
- this.#fullListOfAttrs = fullListOfAttrs;
46
+ this.#fullListOfEnhancementAttrs = fullListOfAttrs;
47
47
  this.#attrParts = partitionedAttrs;
48
48
  this.#calculatedSelector = calculatedSelector;
49
49
  return this.#calculatedSelector;
@@ -119,7 +119,7 @@ export class MountObserver extends EventTarget {
119
119
  }
120
120
  }
121
121
  const rootMutObs = mutationObserverLookup.get(within);
122
- const fullListOfAttrs = this.#fullListOfAttrs;
122
+ const fullListOfAttrs = this.#fullListOfEnhancementAttrs;
123
123
  rootMutObs.addEventListener('mutation-event', async (e) => {
124
124
  //TODO: disconnected
125
125
  if (this.#isComplex) {
@@ -151,6 +151,7 @@ export class MountObserver extends EventTarget {
151
151
  const newValue = target.getAttribute(attributeName);
152
152
  const parts = this.#attrParts[idx];
153
153
  const attrChangeInfo = {
154
+ isSOfTAttr: false,
154
155
  oldValue,
155
156
  name: attributeName,
156
157
  newValue,
@@ -245,8 +246,9 @@ export class MountObserver extends EventTarget {
245
246
  }
246
247
  }
247
248
  readAttrs(match, branchIndexes) {
248
- const fullListOfAttrs = this.#fullListOfAttrs;
249
+ const fullListOfAttrs = this.#fullListOfEnhancementAttrs;
249
250
  const attrChangeInfos = [];
251
+ const oldValue = null;
250
252
  if (fullListOfAttrs !== undefined) {
251
253
  const attrParts = this.#attrParts;
252
254
  for (let idx = 0, ii = fullListOfAttrs.length; idx < ii; idx++) {
@@ -257,17 +259,40 @@ export class MountObserver extends EventTarget {
257
259
  continue;
258
260
  }
259
261
  const name = fullListOfAttrs[idx];
260
- const oldValue = null;
261
262
  const newValue = match.getAttribute(name);
262
263
  attrChangeInfos.push({
263
264
  idx,
265
+ isSOfTAttr: false,
264
266
  newValue,
265
267
  oldValue,
266
268
  name,
267
269
  parts
268
270
  });
269
271
  }
270
- //this.dispatchEvent(new AttrChangeEvent(match, attrChangeInfos));
272
+ }
273
+ const { observedAttrsWhenMounted } = this.#mountInit;
274
+ if (observedAttrsWhenMounted !== undefined) {
275
+ for (const observedAttr of observedAttrsWhenMounted) {
276
+ const attrIsString = typeof observedAttr === 'string';
277
+ const name = attrIsString ? observedAttr : observedAttr.name;
278
+ let mapsTo;
279
+ let newValue = match.getAttribute(name);
280
+ if (!attrIsString) {
281
+ const { customParser, instanceOf, mapsTo: mt, valIfNull } = observedAttr;
282
+ if (instanceOf || customParser)
283
+ throw 'NI';
284
+ if (newValue === null)
285
+ newValue = valIfNull;
286
+ mapsTo = mt;
287
+ }
288
+ attrChangeInfos.push({
289
+ isSOfTAttr: true,
290
+ newValue,
291
+ oldValue,
292
+ name,
293
+ mapsTo
294
+ });
295
+ }
271
296
  }
272
297
  return attrChangeInfos;
273
298
  }
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-22
14
+ Last Update: 2024-5-27
15
15
 
16
16
  ## Benefits of this API
17
17
 
@@ -105,7 +105,7 @@ const observer = new MountObserver({
105
105
  observer.observe(document);
106
106
  ```
107
107
 
108
- Once again, the key can accept either a single import or it can also support multiple imports (via an array).
108
+ Once again, the key can accept either a single import and it can also support multiple imports (via an array).
109
109
 
110
110
  The do event won't be invoked until all the imports have been successfully completed and inserted into the modules array.
111
111
 
@@ -113,7 +113,7 @@ Previously, this proposal called for allowing arrow functions as well, thinking
113
113
 
114
114
  This proposal would also include support for JSON and HTML module imports.
115
115
 
116
- ## MountObserver script elements (MOSEs)
116
+ ## Mount Observer Script Elements (MOSEs)
117
117
 
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
 
@@ -136,7 +136,7 @@ Following an approach similar to the [speculation api](https://developer.chrome.
136
136
  </script>
137
137
  ```
138
138
 
139
- The objects modules, observer, mountedElements (array of weak refs) would be available 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 ith "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,11 +144,11 @@ 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).
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
 
151
- Inside a shadow root, we can plop a script element, also with type mountobserver, optionally giving it the same id as above:
151
+ Inside a shadow root, we can plop a script element, also with type "mountobserver", optionally giving it the same id as above:
152
152
 
153
153
  ```html
154
154
  #shadowRoot
@@ -163,7 +163,7 @@ If no id is found in the parent ShadowRoot (or in the parent window if the shado
163
163
 
164
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.
165
165
 
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.
166
+ We will come back to some important [additional features](#creating-frameworks-that-revolve-around-moses) of using these script elements later, but first we want to cover the highlights of this proposal, in order to give more context as to what kinds of functionality these MOSEs can provide.
167
167
 
168
168
  ## Binding from a distance
169
169
 
@@ -325,9 +325,9 @@ The alternative to providing this feature, which I'm leaning towards, is to just
325
325
 
326
326
  ## A tribute to attributes
327
327
 
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.
328
+ Attributes of DOM elements are tricky. They've been around since the get-go of the Web, and they've survived multiple eras of web development, where different philosophies have prevailed, so prepare yourself for some esoteric discussions in what follows.
329
329
 
330
- Extra support is provided for monitoring attributes. There are two primary reasons for needing to provide special support for attributes with this API:
330
+ The MountObserver API provides explicit support for monitoring attributes. There are two primary reasons for why it is important to provide this as part of the API:
331
331
 
332
332
  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.
333
333
 
@@ -337,9 +337,11 @@ 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 the 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.
341
341
 
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 there are usually 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 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!
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.
343
+
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!
343
345
 
344
346
 
345
347
  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."
@@ -376,6 +378,7 @@ mo.addEventListener('attrChange', e => {
376
378
  // attrChangeInfo:[{
377
379
  // idx: 0,
378
380
  // name: 'lang'
381
+ // isSOfTAttr: true,
379
382
  // oldValue: null,
380
383
  // newValue: 'en-GB',
381
384
  // }]
@@ -722,12 +725,12 @@ Often, we will want to define a large number of "mount observer script elements
722
725
 
723
726
  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
727
 
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.
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.
726
729
 
727
730
  To support this, we propose these highlights:
728
731
 
729
732
  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.
733
+ 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.
731
734
 
732
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.
733
736
 
package/Synthesizer.js CHANGED
@@ -43,6 +43,7 @@ export class Synthesizer extends HTMLElement {
43
43
  import(mose) {
44
44
  const { init, do: d, id, synConfig } = mose;
45
45
  const se = document.createElement('script');
46
+ se.type = 'mountobserver';
46
47
  se.init = { ...init };
47
48
  se.id = id;
48
49
  se.do = { ...d };
@@ -57,15 +58,15 @@ export class Synthesizer extends HTMLElement {
57
58
  const parentShadowRealm = host.getRootNode();
58
59
  const { localName } = this;
59
60
  const parentScopeSynthesizer = parentShadowRealm.querySelector(localName);
61
+ if (parentScopeSynthesizer === null)
62
+ throw 404;
60
63
  const { mountObserverElements } = parentScopeSynthesizer;
61
64
  for (const moe of mountObserverElements) {
62
65
  this.import(moe);
63
66
  }
64
- if (parentScopeSynthesizer !== null) {
65
- parentScopeSynthesizer.addEventListener(SynthesizeEvent.eventName, e => {
66
- this.import(e.mountObserverElement);
67
- });
68
- }
67
+ parentScopeSynthesizer.addEventListener(SynthesizeEvent.eventName, e => {
68
+ this.import(e.mountObserverElement);
69
+ });
69
70
  }
70
71
  disconnectedCallback() {
71
72
  if (this.#mutationObserver !== undefined) {
@@ -1,6 +1,6 @@
1
1
  export async function getWhereAttrSelector(whereAttr, withoutAttrs) {
2
- const { hasBase, hasBranchIn, hasRootIn, isIn } = whereAttr;
3
- const fullListOfAttrs = isIn !== undefined ? [...isIn] : [];
2
+ const { hasBase, hasBranchIn, hasRootIn } = whereAttr;
3
+ const fullListOfAttrs = [];
4
4
  const partitionedAttrs = [];
5
5
  if (hasBase !== undefined) {
6
6
  const hasRootInGuaranteed = hasRootIn || [{
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "mount-observer",
3
- "version": "0.0.24",
3
+ "version": "0.0.26",
4
4
  "description": "Observe and act on css matches.",
5
5
  "main": "MountObserver.js",
6
6
  "module": "MountObserver.js",
7
7
  "devDependencies": {
8
- "@playwright/test": "1.39.0",
8
+ "@playwright/test": "1.44.1",
9
9
  "may-it-serve": "0.0.6"
10
10
  },
11
11
  "exports": {
package/types.d.ts CHANGED
@@ -2,13 +2,23 @@
2
2
 
3
3
  export interface JSONSerializableMountInit{
4
4
  readonly on?: CSSMatch,
5
- observedAttrsWhenMounted?: string[],
5
+ readonly observedAttrsWhenMounted?: (string | ObservedSourceOfTruthAttribute)[],
6
6
  readonly whereAttr?: WhereAttr,
7
7
  readonly whereElementIntersectsWith?: IntersectionObserverInit,
8
8
  readonly whereMediaMatches?: MediaQuery,
9
9
  readonly import?: ImportString | [ImportString, ImportAssertions] | PipelineProcessor,
10
10
 
11
11
  }
12
+
13
+ export interface ObservedSourceOfTruthAttribute {
14
+ name: string,
15
+ mapsTo?: string,
16
+ valIfNull?: any,
17
+ valIfFalsy?: any,
18
+ instanceOf?: any,
19
+ customParser?: (newValue: string | null, oldValue: string | null, instance: Element) => any
20
+ }
21
+
12
22
  export interface MountInit extends JSONSerializableMountInit{
13
23
 
14
24
  readonly withTargetShadowRoot?: ShadowRoot,
@@ -106,9 +116,11 @@ interface AttrParts{
106
116
  interface AttrChangeInfo{
107
117
  oldValue: string | null,
108
118
  newValue: string | null,
109
- idx: number,
119
+ isSOfTAttr: boolean,
120
+ idx?: number,
110
121
  name: string,
111
- parts: AttrParts,
122
+ parts?: AttrParts,
123
+ mapsTo?: string,
112
124
  }
113
125
 
114
126
  //#region mount event