mount-observer 0.0.112 → 0.1.0

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.
Files changed (179) hide show
  1. package/Events.js +28 -26
  2. package/Events.ts +34 -30
  3. package/MountObserver.js +235 -520
  4. package/MountObserver.ts +281 -542
  5. package/README.md +149 -56
  6. package/attrCoordinates.js +93 -0
  7. package/attrCoordinates.ts +122 -0
  8. package/constants.js +6 -0
  9. package/constants.ts +7 -0
  10. package/index.js +3 -0
  11. package/index.ts +19 -0
  12. package/loadImports.js +47 -0
  13. package/loadImports.ts +56 -0
  14. package/package.json +8 -119
  15. package/playwright.config.ts +0 -1
  16. package/types.d.ts +86 -0
  17. package/whereAttr.js +174 -0
  18. package/whereAttr.ts +221 -0
  19. package/LICENSE +0 -21
  20. package/Newish.js +0 -145
  21. package/Newish.ts +0 -169
  22. package/ObsAttr.js +0 -18
  23. package/ObsAttr.ts +0 -18
  24. package/RootMutObs.js +0 -49
  25. package/RootMutObs.ts +0 -58
  26. package/Synthesizer.js +0 -125
  27. package/Synthesizer.ts +0 -130
  28. package/bindish.js +0 -15
  29. package/bindish.ts +0 -22
  30. package/compose.js +0 -148
  31. package/compose.ts +0 -164
  32. package/doCleanup.js +0 -31
  33. package/doCleanup.ts +0 -34
  34. package/getWhereAttrSelector.js +0 -83
  35. package/getWhereAttrSelector.ts +0 -92
  36. package/preloadContent.js +0 -44
  37. package/preloadContent.ts +0 -47
  38. package/readAttrs.ts +0 -60
  39. package/refid/README.md +0 -259
  40. package/refid/arr.js +0 -4
  41. package/refid/arr.ts +0 -4
  42. package/refid/camelToKebab.js +0 -4
  43. package/refid/camelToKebab.ts +0 -4
  44. package/refid/genIds.js +0 -190
  45. package/refid/genIds.ts +0 -177
  46. package/refid/getAdjRefs.js +0 -38
  47. package/refid/getAdjRefs.ts +0 -38
  48. package/refid/getContext.js +0 -13
  49. package/refid/getContext.ts +0 -14
  50. package/refid/getCount.js +0 -8
  51. package/refid/getCount.ts +0 -8
  52. package/refid/getIsh.js +0 -35
  53. package/refid/getIsh.ts +0 -37
  54. package/refid/hostish.js +0 -18
  55. package/refid/hostish.ts +0 -20
  56. package/refid/ism.js +0 -78
  57. package/refid/ism.ts +0 -81
  58. package/refid/itemprops.js +0 -60
  59. package/refid/itemprops.ts +0 -67
  60. package/refid/joinMatching.js +0 -56
  61. package/refid/joinMatching.ts +0 -54
  62. package/refid/nudge.js +0 -23
  63. package/refid/nudge.ts +0 -23
  64. package/refid/regIsh.js +0 -27
  65. package/refid/regIsh.ts +0 -31
  66. package/refid/secretKeys.js +0 -5
  67. package/refid/secretKeys.ts +0 -5
  68. package/refid/splitRefs.js +0 -6
  69. package/refid/splitRefs.ts +0 -6
  70. package/refid/stdVal.js +0 -15
  71. package/refid/stdVal.ts +0 -15
  72. package/refid/via.js +0 -114
  73. package/refid/via.ts +0 -113
  74. package/slotkin/affine.js +0 -39
  75. package/slotkin/affine.ts +0 -46
  76. package/slotkin/beKindred.js +0 -45
  77. package/slotkin/beKindred.ts +0 -55
  78. package/slotkin/getBreadth.js +0 -19
  79. package/slotkin/getBreadth.ts +0 -21
  80. package/slotkin/getFrag.js +0 -22
  81. package/slotkin/getFrag.ts +0 -21
  82. package/slotkin/toQuery.js +0 -12
  83. package/slotkin/toQuery.ts +0 -13
  84. package/slotkin/wrap.js +0 -13
  85. package/slotkin/wrap.ts +0 -18
  86. package/ts-refs/LICENSE +0 -21
  87. package/ts-refs/README.md +0 -18
  88. package/ts-refs/be-a-beacon/types.d.ts +0 -22
  89. package/ts-refs/be-alit/types.d.ts +0 -1
  90. package/ts-refs/be-based/types.d.ts +0 -32
  91. package/ts-refs/be-bound/types.d.ts +0 -65
  92. package/ts-refs/be-buttoned-up/types.d.ts +0 -21
  93. package/ts-refs/be-calculating/types.d.ts +0 -57
  94. package/ts-refs/be-clonable/types.d.ts +0 -28
  95. package/ts-refs/be-committed/types.d.ts +0 -26
  96. package/ts-refs/be-consoling/types.d.ts +0 -25
  97. package/ts-refs/be-counted/types.d.ts +0 -88
  98. package/ts-refs/be-delible/types.d.ts +0 -26
  99. package/ts-refs/be-directive/types.d.ts +0 -43
  100. package/ts-refs/be-dispatching/types.d.ts +0 -41
  101. package/ts-refs/be-elevating/types.d.ts +0 -55
  102. package/ts-refs/be-enhanced/types.d.ts +0 -32
  103. package/ts-refs/be-enhancing/types.d.ts +0 -31
  104. package/ts-refs/be-evanescent/types.d.ts +0 -20
  105. package/ts-refs/be-eventing/types.d.ts +0 -27
  106. package/ts-refs/be-exportable/types.d.ts +0 -26
  107. package/ts-refs/be-fetching/types.d.ts +0 -73
  108. package/ts-refs/be-flashy/types.d.ts +0 -27
  109. package/ts-refs/be-formalizing/types.d.ts +0 -29
  110. package/ts-refs/be-formidable/types.d.ts +0 -64
  111. package/ts-refs/be-giddy/types.d.ts +0 -26
  112. package/ts-refs/be-gingerly/types.d.ts +0 -19
  113. package/ts-refs/be-gone/types.d.ts +0 -24
  114. package/ts-refs/be-hashing-out/types.d.ts +0 -22
  115. package/ts-refs/be-hive/types.d.ts +0 -18
  116. package/ts-refs/be-imbued/types.d.ts +0 -30
  117. package/ts-refs/be-included/types.d.ts +0 -20
  118. package/ts-refs/be-inclusive/types.d.ts +0 -30
  119. package/ts-refs/be-intersectional/types.d.ts +0 -37
  120. package/ts-refs/be-intl/types.d.ts +0 -28
  121. package/ts-refs/be-invoking/types.d.ts +0 -28
  122. package/ts-refs/be-joining/types.d.ts +0 -26
  123. package/ts-refs/be-kvetching/types.d.ts +0 -24
  124. package/ts-refs/be-lazy/types.d.ts +0 -29
  125. package/ts-refs/be-literate/types.d.ts +0 -29
  126. package/ts-refs/be-mediating/types.d.ts +0 -34
  127. package/ts-refs/be-methodical/types.d.ts +0 -20
  128. package/ts-refs/be-modding/types.d.ts +0 -18
  129. package/ts-refs/be-observant/types.d.ts +0 -27
  130. package/ts-refs/be-observing/types.d.ts +0 -84
  131. package/ts-refs/be-parsed/types.d.ts +0 -19
  132. package/ts-refs/be-parsing/types.d.ts +0 -37
  133. package/ts-refs/be-persistent/types.d.ts +0 -66
  134. package/ts-refs/be-propagating/types.d.ts +0 -26
  135. package/ts-refs/be-reformable/types.d.ts +0 -48
  136. package/ts-refs/be-render-neutral/types.d.ts +0 -31
  137. package/ts-refs/be-scoped/types.d.ts +0 -24
  138. package/ts-refs/be-sharing/types.d.ts +0 -17
  139. package/ts-refs/be-switched/types.d.ts +0 -155
  140. package/ts-refs/be-typed/types.d.ts +0 -36
  141. package/ts-refs/be-value-added/types.d.ts +0 -34
  142. package/ts-refs/be-valued/types.d.ts +0 -22
  143. package/ts-refs/be-written/types.d.ts +0 -59
  144. package/ts-refs/css-charts/types.d.ts +0 -38
  145. package/ts-refs/css-echarts/types.d.ts +0 -13
  146. package/ts-refs/data-props/types.d.ts +0 -27
  147. package/ts-refs/do-inc/types.d.ts +0 -28
  148. package/ts-refs/do-invoke/types.d.ts +0 -28
  149. package/ts-refs/do-toggle/types.d.ts +0 -27
  150. package/ts-refs/em-bower/types.d.ts +0 -24
  151. package/ts-refs/fetch-for/types.d.ts +0 -37
  152. package/ts-refs/folder-picker/types.d.ts +0 -43
  153. package/ts-refs/for-fetch/doc.d.ts +0 -98
  154. package/ts-refs/for-fetch/types.d.ts +0 -83
  155. package/ts-refs/mount-observer/types.d.ts +0 -248
  156. package/ts-refs/mt-si/types.d.ts +0 -21
  157. package/ts-refs/per-each/types.d.ts +0 -51
  158. package/ts-refs/soak-up/types.d.ts +0 -36
  159. package/ts-refs/trans-render/XV/types.d.ts +0 -69
  160. package/ts-refs/trans-render/asmr/types.d.ts +0 -138
  161. package/ts-refs/trans-render/be/types.d.ts +0 -198
  162. package/ts-refs/trans-render/dss/types.d.ts +0 -57
  163. package/ts-refs/trans-render/froop/types.d.ts +0 -416
  164. package/ts-refs/trans-render/funions/types.d.ts +0 -12
  165. package/ts-refs/trans-render/lib/mixins/types.d.ts +0 -42
  166. package/ts-refs/trans-render/lib/prs/types.d.ts +0 -40
  167. package/ts-refs/trans-render/lib/types.d.ts +0 -489
  168. package/ts-refs/trans-render/types.d.ts +0 -583
  169. package/ts-refs/wc-info/SimpleWCInfo.d.ts +0 -15
  170. package/ts-refs/when-resolved/types.d.ts +0 -30
  171. package/ts-refs/xp-as/types.d.ts +0 -20
  172. package/ts-refs/xtal-element/types.d.ts +0 -43
  173. package/ts-refs/xtal-frappe-chart/types.d.ts +0 -193
  174. package/upShadowSearch.js +0 -25
  175. package/upShadowSearch.ts +0 -23
  176. package/waitForEvent.js +0 -12
  177. package/waitForEvent.ts +0 -13
  178. package/waitForIsh.js +0 -21
  179. package/waitForIsh.ts +0 -20
package/README.md CHANGED
@@ -62,9 +62,7 @@ The amount of code necessary to accomplish these common tasks designed to improv
62
62
  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?
63
63
 
64
64
  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.
65
-
66
- > [!Note]
67
- > Reading through the historical links tied to the selector-observer proposal this proposal helped spawn, I may have painted an overly optimistic picture of [what the platform is capable of](https://github.com/whatwg/dom/issues/398). It does leave me a little puzzled why this isn't an issue when it comes to styling, and also if some of the advances that were utilized to support :has could be applied to this problem space, so that maybe the arguments raised there have weakened. Even if the concerns raised are as relevant today, I think considering the use cases this proposal envisions, that the objections could be overcome, for the following reasons: 1. For scenarios where lazy loading is the primary objective, "bunching" multiple DOM mutations together and only reevaluating when things are quite idle is perfectly reasonable. Also, for binding from a distance, most of the mutations that need responding to quickly will be when the *state of the host* changes, so DOM mutations play a somewhat muted role in that regard. Again, bunching multiple DOM mutations together, even if adds a bit of a delay, also seems reasonable. I also think the platform could add an "analysis" step to look at the query and categorize it as "simple" queries vs complex. Selector queries that are driven by the characteristics of the element itself (localName, attributes, etc) could be handled in a more expedited fashion. Those that the platform does expect to require more babysitting could be monitored for less vigilantly. Maybe in the latter case, a console.warning could be emitted during initialization. The other use case, for lazy loading custom elements and custom enhancements based on attributes, I think most of the time this would fit the "simple" scenario, so again there wouldn't be much of an issue.
65
+
68
66
 
69
67
  ## First use case -- lazy loading custom elements
70
68
 
@@ -72,26 +70,26 @@ To specify the equivalent of what the alternative proposal linked to above would
72
70
 
73
71
  ```JavaScript
74
72
  const observer = new MountObserver({
75
- on:'my-element',
73
+ select:'my-element',
76
74
  import: './my-element.js',
77
- do: {
78
- mount: ({localName}, {modules, observer}) => {
79
- if(!customElements.get(localName)) {
80
- customElements.define(localName, modules[0].MyElement);
81
- }
82
- observer.disconnectedSignal.abort();
83
- },
84
- },
85
- disconnectedSignal: new AbortController().signal
86
- });
75
+ do: ({localName}, {modules, observer, observeInfo}) => {
76
+ if(!customElements.get(localName)) {
77
+ customElements.define(localName, modules[0].MyElement);
78
+ }
79
+ observer.disconnectedSignal.abort();
80
+ }
81
+
82
+ }, {disconnectedSignal: new AbortController().signal});
87
83
  observer.observe(document);
88
84
  ```
89
85
 
86
+ The do function will *only be called once per matching element* -- i.e. if the element stops matching the "select" criteria, then matches again, the do function won't be called again. It will be called for all elements when they match within the scope passed in to the observe method. However, the events discussed below, as well as more structured inline functions also as discussed below, will continue to be called repeatedly.
87
+
90
88
  The constructor argument can also be an array of objects that fit the pattern shown above.
91
89
 
92
- 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.
90
+ 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 "select" 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.
93
91
 
94
- 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.
92
+ 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.
95
93
 
96
94
  This only searches for elements matching 'my-element' outside any shadow DOM.
97
95
 
@@ -99,24 +97,52 @@ But the observe method can accept a node within the document, or a shadowRoot, o
99
97
 
100
98
  The "observer" constant above is a class instance that inherits from EventTarget, which means it can be subscribed to by outside interests.
101
99
 
100
+ > [!Note]
101
+ > Reading through the historical links tied to the selector-observer proposal this proposal helped spawn, I may have painted an overly optimistic picture of [what the platform is capable of](https://github.com/whatwg/dom/issues/398). It does leave me a little puzzled why this isn't an issue when it comes to styling, and also if some of the advances that were utilized to support :has could be applied to this problem space, so that maybe the arguments raised there have weakened. Even if the concerns raised are as relevant today, I think considering the use cases this proposal envisions, that the objections could be overcome, for the following reasons: 1. For scenarios where lazy loading is the primary objective, "bunching" multiple DOM mutations together and only reevaluating when things are quite idle is perfectly reasonable. Also, for binding from a distance, most of the mutations that need responding to quickly will be when the *state of the host* changes, so DOM mutations play a somewhat muted role in that regard. Again, bunching multiple DOM mutations together, even if adds a bit of a delay, also seems reasonable. I also think the platform could add an "analysis" step to look at the query and categorize it as "simple" queries vs complex. Selector queries that are driven by the characteristics of the element itself (localName, attributes, etc) could be handled in a more expedited fashion. Those that the platform does expect to require more babysitting could be monitored for less vigilantly. Maybe in the latter case, a console.warning could be emitted during initialization. The other use case, for lazy loading custom elements and custom enhancements based on attributes, I think most of the time this would fit the "simple" scenario, so again there wouldn't be much of an issue.
102
+
103
+ In fact, I have encountered statements made by the browser vendors that some queries supported by css can't be evaluated simply by looking at the layout of the HTML, but has to be made after rendering and performing style calculations. This necessitates having to delay the notification, which would be unacceptable.
104
+
105
+ If the developer has a simple query in mind that needs no such nuance, I'm thinking it might be helpful to provide an alternative key to "on" that is used specifically for (a subset?) of queries supported by the existing "matches" method that elements support.
106
+
107
+ So the developer could use:
108
+
109
+ ## Polyfill Supported Scenario I
110
+
111
+ ```JavaScript
112
+ const observer = new MountObserver({
113
+ import: './my-element.js',
114
+ whereElementMatches:'my-element',
115
+ do: ({localName}, {modules, observer, observeInfo}) => {
116
+ if(!customElements.get(localName)) {
117
+ customElements.define(localName, modules[0].MyElement);
118
+ }
119
+ observer.disconnectedSignal.abort();
120
+ }
121
+
122
+ }, {disconnectedSignal: new AbortController().signal});
123
+ observer.observe(document);
124
+ ```
125
+
126
+ and could perhaps expect faster binding as a result of the more limited supported expressions. Since "select" is not specified, it is assumed to be "*"
127
+
128
+ This polyfill in fact only supports this latter option ("whreElementMatches"), and leaves "select" for such a time as when a selector observer is available in the platform.
129
+
102
130
  ## The import key
103
131
 
104
132
  This proposal has been amended to support multiple imports, including of different types:
105
133
 
106
134
  ```JavaScript
107
135
  const observer = new MountObserver({
108
- on:'my-element',
136
+ select:'my-element',
109
137
  import: [
110
138
  ['./my-element-small.css', {type: 'css'}],
111
139
  './my-element.js',
112
140
  ],
113
- do: {
114
- mount: ({localName}, {modules, observer}) => {
115
- if(!customElements.get(localName)) {
116
- customElements.define(localName, modules[1].MyElement);
117
- }
118
- observer.disconnectedSignal.abort();
141
+ do: ({localName}, {modules, observer}) => {
142
+ if(!customElements.get(localName)) {
143
+ customElements.define(localName, modules[1].MyElement);
119
144
  }
145
+ observer.disconnectedSignal.abort();
120
146
  }
121
147
  });
122
148
  observer.observe(document);
@@ -124,11 +150,11 @@ observer.observe(document);
124
150
 
125
151
  Once again, the key can accept either a single import, but alternatively it can also support multiple imports (via an array).
126
152
 
127
- The do event won't be invoked until all the imports have been successfully completed and inserted into the modules array.
153
+ The do function won't be invoked until all the imports have been successfully completed and inserted into the modules array.
128
154
 
129
155
  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.
130
156
 
131
- This proposal would also include support for JSON and HTML module imports.
157
+ This proposal would also include support for JSON and HTML module imports (really, all types).
132
158
 
133
159
  ## Preemptive downloading
134
160
 
@@ -137,21 +163,19 @@ There are two significant steps to imports, each of which imposes a cost:
137
163
  1. Downloading the resource.
138
164
  2. Loading the resource into memory.
139
165
 
140
- What if we want to download the resource ahead of time, but only load into memory when needed?
166
+ What if we want to *download* the resource ahead of time, but only load into memory when needed?
141
167
 
142
168
  The link rel=modulepreload option provides an already existing platform support for this, but the browser complains when no use of the resource is used within a short time span of page load. That doesn't really fit the bill for lazy loading custom elements and other resources.
143
169
 
144
- So for this we add option:
170
+ So for this we add loadingEagerness:
145
171
 
146
172
  ```JavaScript
147
173
  const observer = new MountObserver({
148
- on: 'my-element',
174
+ select: 'my-element',
149
175
  loadingEagerness: 'eager',
150
176
  import: './my-element.js',
151
- do:{
152
- mount: (matchingElement, {modules}) => customElements.define(modules[0].MyElement)
153
- }
154
- })
177
+ do: ({localName}, {modules}) => customElements.define(localName, modules[0].MyElement),
178
+ });
155
179
  ```
156
180
 
157
181
  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.
@@ -175,7 +199,7 @@ Following an approach similar to the [speculation api](https://developer.chrome.
175
199
  observer.disconnectedSignal.abort();
176
200
  }">
177
201
  {
178
- "on":"my-element",
202
+ "select":"my-element",
179
203
  "import": [
180
204
  ["./my-element-small.css", {type: "css"}],
181
205
  "./my-element.js",
@@ -206,7 +230,7 @@ The syntax below is just one, "spit-balling" way this could be done, as an examp
206
230
  ```html
207
231
  <script type="mountobserver">
208
232
  {
209
- "on":"my-element",
233
+ "select":"my-element",
210
234
  "import": [
211
235
  ["./my-element-small.css", {type: "css"}],
212
236
  "./my-element.js",
@@ -233,7 +257,7 @@ Inside a shadow root, we can plop a script element, also with type "mountobserve
233
257
  #shadowRoot
234
258
  <script id=myMountObserver type=mountobserver>
235
259
  {
236
- "on":"your-element"
260
+ "select":"your-element"
237
261
  }
238
262
  </script>
239
263
  ```
@@ -252,15 +276,18 @@ We will come back to some important [additional features](#creating-frameworks-t
252
276
 
253
277
  ## Binding from a distance
254
278
 
255
- It is important to note that "on" is a css query with no restrictions. So something like:
279
+ It is important to note that "select" is a css query with no restrictions. So something like:
256
280
 
257
281
  ```JavaScript
258
282
  const observer = new MountObserver({
259
- on:'div > p + p ~ span[class$="name"]',
283
+ select:'div > p + p ~ span[class$="name"]',
260
284
  do:{
261
285
  mount: (matchingElement) => {
262
286
  //attach some behavior or set some property value or add an event listener, etc.
263
287
  matchingElement.textContent = 'hello';
288
+ },
289
+ dismount: (matchingElement) => {
290
+ matchingElement.textContent = 'bye';
264
291
  }
265
292
  }
266
293
  })
@@ -268,8 +295,78 @@ const observer = new MountObserver({
268
295
 
269
296
  ... would work.
270
297
 
298
+ Note that in this example, "do" no longer points to a function. When it did (above), we mentioned this would only be called once per element. **Now it will be called every the conditions flip from not all satisfied to satisfied"**.
299
+
271
300
  This would allow developers to create "stylesheet" like capabilities.
272
301
 
302
+ ## Applying properties with assignGingerly
303
+
304
+ For the common use case of setting properties on matching elements, MountObserver provides built-in support for the [assignGingerly](https://github.com/bahrus/assign-gingerly) library. This allows you to declaratively specify properties to apply to elements without writing custom mount callbacks:
305
+
306
+ ```JavaScript
307
+ const observer = new MountObserver({
308
+ whereElementMatches: 'input',
309
+ assignGingerly: {
310
+ disabled: true,
311
+ value: 'Default value',
312
+ title: 'This is a tooltip'
313
+ }
314
+ });
315
+ observer.observe(document);
316
+ ```
317
+
318
+ This will automatically apply the specified properties to all matching input elements, both existing ones and those added dynamically.
319
+
320
+ ### Nested properties with dataset
321
+
322
+ The `assignGingerly` library supports nested property assignment using the `?.` notation. This is particularly useful for setting data attributes:
323
+
324
+ ```JavaScript
325
+ const observer = new MountObserver({
326
+ whereElementMatches: 'button',
327
+ assignGingerly: {
328
+ disabled: false,
329
+ '?.dataset.action': 'submit',
330
+ '?.dataset.trackingId': '12345'
331
+ }
332
+ });
333
+ observer.observe(document);
334
+ ```
335
+
336
+ The `?.` prefix tells assignGingerly to create nested properties if they don't exist. In this example, `?.dataset.action` will set the `data-action` attribute on the button elements.
337
+
338
+ ### Combining with imports
339
+
340
+ You can combine `assignGingerly` with lazy loading to both import resources and set properties:
341
+
342
+ ```JavaScript
343
+ const observer = new MountObserver({
344
+ whereElementMatches: 'my-element',
345
+ import: './my-element.js',
346
+ assignGingerly: {
347
+ theme: 'dark',
348
+ '?.dataset.initialized': 'true'
349
+ },
350
+ do: ({localName}, {modules}) => {
351
+ if(!customElements.get(localName)) {
352
+ customElements.define(localName, modules[0].MyElement);
353
+ }
354
+ }
355
+ });
356
+ observer.observe(document);
357
+ ```
358
+
359
+ The `assignGingerly` properties are applied after imports are loaded but before the `do` callback is invoked, ensuring that elements are properly configured before any custom initialization logic runs.
360
+
361
+ ### Performance benefits
362
+
363
+ Using `assignGingerly` provides several benefits:
364
+
365
+ 1. **Lazy loading**: The assign-gingerly library is only loaded when needed (when the `assignGingerly` property is specified)
366
+ 2. **Bulk operations**: Properties are applied efficiently to all matching elements
367
+ 3. **Declarative**: No need to write custom mount callbacks for simple property assignments
368
+ 4. **Consistent**: The same property values are applied uniformly across all matching elements
369
+
273
370
 
274
371
  ## Extra lazy loading
275
372
 
@@ -279,7 +376,7 @@ However, we could make the loading even more lazy by specifying intersection opt
279
376
 
280
377
  ```JavaScript
281
378
  const observer = new MountObserver({
282
- on: 'my-element',
379
+ select: 'my-element',
283
380
  whereElementIntersectsWith:{
284
381
  rootMargin: "0px",
285
382
  threshold: 1.0,
@@ -294,13 +391,13 @@ Unlike traditional CSS @import, CSS Modules don't support specifying different i
294
391
 
295
392
  ```JavaScript
296
393
  const observer = new MountObserver({
297
- on: 'div > p + p ~ span[class$="name"]',
394
+ select: 'div > p + p ~ span[class$="name"]',
298
395
  whereMediaMatches: '(max-width: 1250px)',
299
396
  whereSizeOfContainerMatches: '(min-width: 700px)',
300
397
  whereContainerHas: '[itemprop=isActive][value="true"]',
301
398
  whereInstanceOf: [HTMLMarqueeElement], //or ['HTMLMarqueeElement']
302
399
  whereLangIn: ['en-GB'],
303
- whereConnection:{
400
+ whereConnectiselect:{
304
401
  effectiveTypeIn: ["slow-2g"],
305
402
  },
306
403
  import: ['./my-element-small.css', {type: 'css'}],
@@ -416,9 +513,9 @@ So the dismount event should provide a "checklist" of all the conditions, and th
416
513
  ```JavaScript
417
514
  mediaMatches: true,
418
515
  containerMatches: true,
419
- satisfiesCustomCondition: true,
516
+ satisfiesCustomConditiselect: true,
420
517
  whereLangIn: ['en-GB'],
421
- whereConnection:{
518
+ whereConnectiselect:{
422
519
  effectiveTypeMatches: true
423
520
  },
424
521
  isIntersecting: false,
@@ -459,7 +556,7 @@ For the polyfill, we need to support it as follows:
459
556
  ```JavaScript
460
557
  const oElement = document.getElementById('myTest');
461
558
  const observer = new MountObserver({
462
- on:'[itemprop]',
559
+ select:'[itemprop]',
463
560
  outside: '[itemscope]'
464
561
  do: {
465
562
  mount: ({localName}, {modules, observer}) => {
@@ -531,7 +628,7 @@ Let's focus on the first scenario. It doesn't make sense to use the word "where
531
628
  ```JavaScript
532
629
  import {MountObserver} from 'mount-observer/MountObserver.js';
533
630
  const mo = new MountObserver({
534
- on: '*',
631
+ select: '*',
535
632
  observedAttrsWhenMounted: ['lang', 'contenteditable']
536
633
  });
537
634
 
@@ -613,7 +710,7 @@ Using the same expression structure as above, we would end up with this avalanch
613
710
  ```JavaScript
614
711
  import {MountObserver} from '../MountObserver.js';
615
712
  const mo = new MountObserver({
616
- on: '*',
713
+ select: '*',
617
714
  whereAttr:{
618
715
  isIn: [
619
716
  'data-my-enhancement',
@@ -649,7 +746,6 @@ This seems like a much better approach, and is supported by this proposal:
649
746
  ```JavaScript
650
747
  import {MountObserver} from '../MountObserver.js';
651
748
  const mo = new MountObserver({
652
- on: '*',
653
749
  whereAttr:{
654
750
  hasRootIn: ['data', 'enh', 'data-enh'],
655
751
  hasBase: 'my-enhancement',
@@ -670,7 +766,7 @@ MountObserver provides a breakdown of the matching attribute when encountered:
670
766
  <script type=module>
671
767
  import {MountObserver} from '../MountObserver.js';
672
768
  const mo = new MountObserver({
673
- on: '*',
769
+ select: '*',
674
770
  whereAttr:{
675
771
  hasRootIn: ['data', 'enh', 'data-enh'],
676
772
  hasBase: 'my-enhancement',
@@ -727,7 +823,7 @@ An example of this in the real world can be found with [HTMX](https://htmx.org/d
727
823
 
728
824
  ```html
729
825
  <button hx-post="/example"
730
- hx-on:htmx:config-request="event.detail.parameters.example = 'Hello Scripting!'">
826
+ hx-select:htmx:config-request="event.detail.parameters.example = 'Hello Scripting!'">
731
827
  Post Me!
732
828
  </button>
733
829
  ```
@@ -736,7 +832,7 @@ To support such syntax, specify the delimiters thusly:
736
832
 
737
833
  ```JavaScript
738
834
  const mo = new MountObserver({
739
- on: '*',
835
+ select: '*',
740
836
  whereAttr:{
741
837
  hasRootIn: ['data', 'enh', 'data-enh'],
742
838
  hasBase: ['-', 'my-enhancement'],
@@ -758,7 +854,7 @@ Thus the mountObserver does provide that information to the consumer as well:
758
854
 
759
855
  ```JavaScript
760
856
  const mo = new MountObserver({
761
- on: '*',
857
+ select: '*',
762
858
  whereAttr:{
763
859
  hasRootIn: ['data', 'enh', 'data-enh'],
764
860
  hasBase: ['-', 'my-enhancement'],
@@ -1015,7 +1111,7 @@ Just as it is useful to be able lazy load external imports when needed, it would
1015
1111
  <template id=source-template rel=conditional-stream>
1016
1112
 
1017
1113
  <template mount='{
1018
- "on": ":not([defer-loading])",
1114
+ "select": ":not([defer-loading])",
1019
1115
  "loadingEagerness": "eager",
1020
1116
  "whereMediaMatches": "(min-width: 700px)",
1021
1117
  "whereLangIn": ["en-GB"],
@@ -1024,7 +1120,7 @@ Just as it is useful to be able lazy load external imports when needed, it would
1024
1120
  </template>
1025
1121
 
1026
1122
  <template mount='{
1027
- "on": ":not([defer-loading])",
1123
+ "select": ":not([defer-loading])",
1028
1124
  "loadingEagerness": "lazy",
1029
1125
  "whereMediaMatches": "(max-width: 700px)",
1030
1126
  "whereLangIn": ["fr"],
@@ -1236,7 +1332,4 @@ To keep the api uniform, we hide this discrepancy by pretending the form element
1236
1332
  // includes both field1 and field2
1237
1333
 
1238
1334
  </script>
1239
- ```
1240
-
1241
-
1242
-
1335
+ ```
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Builds a map of attribute names to their coordinates based on whereAttr config
3
+ */
4
+ export function buildAttrCoordinateMap(whereAttr, isCustomElement) {
5
+ const map = {};
6
+ const rootPrefixes = isCustomElement
7
+ ? (whereAttr.hasCERootIn || [])
8
+ : (whereAttr.hasBuiltInRootIn || []);
9
+ // Parse base attribute for custom delimiter
10
+ const { delimiter: baseDelimiter, name: baseName } = parseDelimiter(whereAttr.hasBase);
11
+ // Build attribute names for each prefix
12
+ for (const prefix of rootPrefixes) {
13
+ const baseAttrName = buildAttributeName(prefix, baseName, baseDelimiter);
14
+ // If no branches specified, just the base attribute
15
+ if (!whereAttr.hasBranchIn || whereAttr.hasBranchIn.length === 0) {
16
+ map[baseAttrName] = '0';
17
+ continue;
18
+ }
19
+ // Process each branch
20
+ for (let i = 0; i < whereAttr.hasBranchIn.length; i++) {
21
+ const branch = whereAttr.hasBranchIn[i];
22
+ if (branch === '') {
23
+ // Empty string means base attribute alone is valid
24
+ map[baseAttrName] = '0';
25
+ continue;
26
+ }
27
+ if (typeof branch === 'object') {
28
+ // Process branch object
29
+ processBranch(branch, baseAttrName, String(i), map);
30
+ }
31
+ }
32
+ }
33
+ return map;
34
+ }
35
+ /**
36
+ * Recursively processes a branch object to build attribute-coordinate mappings
37
+ */
38
+ function processBranch(branch, parentAttrName, parentCoordinate, map) {
39
+ for (const [key, subBranches] of Object.entries(branch)) {
40
+ const { delimiter, name } = parseDelimiter(key);
41
+ const attrName = parentAttrName + delimiter + name;
42
+ // Process sub-branches
43
+ if (!subBranches || subBranches.length === 0) {
44
+ map[attrName] = parentCoordinate;
45
+ continue;
46
+ }
47
+ for (let i = 0; i < subBranches.length; i++) {
48
+ const subBranch = subBranches[i];
49
+ const coordinate = `${parentCoordinate}.${i}`;
50
+ if (subBranch === '') {
51
+ // Empty string means this level alone is valid
52
+ map[attrName] = parentCoordinate;
53
+ continue;
54
+ }
55
+ if (typeof subBranch === 'string') {
56
+ // Simple string sub-branch
57
+ const { delimiter: subDelimiter, name: subName } = parseDelimiter(subBranch);
58
+ const subAttrName = attrName + subDelimiter + subName;
59
+ map[subAttrName] = coordinate;
60
+ continue;
61
+ }
62
+ if (typeof subBranch === 'object') {
63
+ // Nested object - recursively process
64
+ processBranch(subBranch, attrName, coordinate, map);
65
+ }
66
+ }
67
+ }
68
+ }
69
+ /**
70
+ * Parses a key to extract custom delimiter and name
71
+ */
72
+ function parseDelimiter(key) {
73
+ const match = key.match(/^\[(.+?)\](.+)$/);
74
+ if (match) {
75
+ return {
76
+ delimiter: match[1],
77
+ name: match[2]
78
+ };
79
+ }
80
+ return {
81
+ delimiter: '-',
82
+ name: key
83
+ };
84
+ }
85
+ /**
86
+ * Builds the full attribute name from prefix, base name, and delimiter
87
+ */
88
+ function buildAttributeName(prefix, baseName, delimiter) {
89
+ if (prefix === '') {
90
+ return baseName;
91
+ }
92
+ return prefix + delimiter + baseName;
93
+ }
@@ -0,0 +1,122 @@
1
+ import { WhereAttr, BranchValue } from './types.js';
2
+
3
+ /**
4
+ * Represents a mapping from attribute name to coordinate
5
+ */
6
+ export interface AttrCoordinateMap {
7
+ [attrName: string]: string;
8
+ }
9
+
10
+ /**
11
+ * Builds a map of attribute names to their coordinates based on whereAttr config
12
+ */
13
+ export function buildAttrCoordinateMap(whereAttr: WhereAttr, isCustomElement: boolean): AttrCoordinateMap {
14
+ const map: AttrCoordinateMap = {};
15
+ const rootPrefixes = isCustomElement
16
+ ? (whereAttr.hasCERootIn || [])
17
+ : (whereAttr.hasBuiltInRootIn || []);
18
+
19
+ // Parse base attribute for custom delimiter
20
+ const { delimiter: baseDelimiter, name: baseName } = parseDelimiter(whereAttr.hasBase);
21
+
22
+ // Build attribute names for each prefix
23
+ for (const prefix of rootPrefixes) {
24
+ const baseAttrName = buildAttributeName(prefix, baseName, baseDelimiter);
25
+
26
+ // If no branches specified, just the base attribute
27
+ if (!whereAttr.hasBranchIn || whereAttr.hasBranchIn.length === 0) {
28
+ map[baseAttrName] = '0';
29
+ continue;
30
+ }
31
+
32
+ // Process each branch
33
+ for (let i = 0; i < whereAttr.hasBranchIn.length; i++) {
34
+ const branch = whereAttr.hasBranchIn[i];
35
+
36
+ if (branch === '') {
37
+ // Empty string means base attribute alone is valid
38
+ map[baseAttrName] = '0';
39
+ continue;
40
+ }
41
+
42
+ if (typeof branch === 'object') {
43
+ // Process branch object
44
+ processBranch(branch, baseAttrName, String(i), map);
45
+ }
46
+ }
47
+ }
48
+
49
+ return map;
50
+ }
51
+
52
+ /**
53
+ * Recursively processes a branch object to build attribute-coordinate mappings
54
+ */
55
+ function processBranch(
56
+ branch: { [key: string]: BranchValue[] },
57
+ parentAttrName: string,
58
+ parentCoordinate: string,
59
+ map: AttrCoordinateMap
60
+ ): void {
61
+ for (const [key, subBranches] of Object.entries(branch)) {
62
+ const { delimiter, name } = parseDelimiter(key);
63
+ const attrName = parentAttrName + delimiter + name;
64
+
65
+ // Process sub-branches
66
+ if (!subBranches || subBranches.length === 0) {
67
+ map[attrName] = parentCoordinate;
68
+ continue;
69
+ }
70
+
71
+ for (let i = 0; i < subBranches.length; i++) {
72
+ const subBranch = subBranches[i];
73
+ const coordinate = `${parentCoordinate}.${i}`;
74
+
75
+ if (subBranch === '') {
76
+ // Empty string means this level alone is valid
77
+ map[attrName] = parentCoordinate;
78
+ continue;
79
+ }
80
+
81
+ if (typeof subBranch === 'string') {
82
+ // Simple string sub-branch
83
+ const { delimiter: subDelimiter, name: subName } = parseDelimiter(subBranch);
84
+ const subAttrName = attrName + subDelimiter + subName;
85
+ map[subAttrName] = coordinate;
86
+ continue;
87
+ }
88
+
89
+ if (typeof subBranch === 'object') {
90
+ // Nested object - recursively process
91
+ processBranch(subBranch, attrName, coordinate, map);
92
+ }
93
+ }
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Parses a key to extract custom delimiter and name
99
+ */
100
+ function parseDelimiter(key: string): { delimiter: string; name: string } {
101
+ const match = key.match(/^\[(.+?)\](.+)$/);
102
+ if (match) {
103
+ return {
104
+ delimiter: match[1],
105
+ name: match[2]
106
+ };
107
+ }
108
+ return {
109
+ delimiter: '-',
110
+ name: key
111
+ };
112
+ }
113
+
114
+ /**
115
+ * Builds the full attribute name from prefix, base name, and delimiter
116
+ */
117
+ function buildAttributeName(prefix: string, baseName: string, delimiter: string): string {
118
+ if (prefix === '') {
119
+ return baseName;
120
+ }
121
+ return prefix + delimiter + baseName;
122
+ }
package/constants.js ADDED
@@ -0,0 +1,6 @@
1
+ // Constants for MountObserver
2
+ export const loadEventName = 'load';
3
+ export const mountEventName = 'mount';
4
+ export const dismountEventName = 'dismount';
5
+ export const disconnectEventName = 'disconnect';
6
+ export const attrchangeEventName = 'attrchange';
package/constants.ts ADDED
@@ -0,0 +1,7 @@
1
+ // Constants for MountObserver
2
+
3
+ export const loadEventName = 'load';
4
+ export const mountEventName = 'mount';
5
+ export const dismountEventName = 'dismount';
6
+ export const disconnectEventName = 'disconnect';
7
+ export const attrchangeEventName = 'attrchange';
package/index.js ADDED
@@ -0,0 +1,3 @@
1
+ // Main entry point for MountObserver v2
2
+ export { MountObserver } from './MountObserver.js';
3
+ export { mountEventName, dismountEventName, disconnectEventName, loadEventName } from './constants.js';
package/index.ts ADDED
@@ -0,0 +1,19 @@
1
+ // Main entry point for MountObserver v2
2
+ export { MountObserver } from './MountObserver.js';
3
+ export type {
4
+ MountInit,
5
+ MountObserverOptions,
6
+ IMountObserver,
7
+ MountContext,
8
+ DoCallback,
9
+ DoCallbacks,
10
+ ImportSpec,
11
+ IMountEvent,
12
+ IDismountEvent
13
+ } from './types.js';
14
+ export {
15
+ mountEventName,
16
+ dismountEventName,
17
+ disconnectEventName,
18
+ loadEventName
19
+ } from './constants.js';