mount-observer 0.0.81 → 0.0.82

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/Newish.js CHANGED
@@ -1,6 +1,5 @@
1
1
  export { waitForEvent } from './waitForEvent.js';
2
2
  import { ObsAttr } from './ObsAttr.js';
3
- import { splitRefs } from './refid/splitRefs.js';
4
3
  import { getIsh } from './refid/getIsh.js';
5
4
  import { arr } from './refid/secretKeys.js';
6
5
  export const attached = Symbol.for('xyyspnstnU+CDrNVa0VnxA');
@@ -16,11 +15,11 @@ export class Newish {
16
15
  this.#options = options || { assigner: Object.assign };
17
16
  this.#ref = new WeakRef(enhancedElement);
18
17
  }
19
- handleEvent() {
18
+ async handleEvent() {
20
19
  const enhancedElement = this.#ref.deref();
21
20
  if (!enhancedElement)
22
21
  return;
23
- this.#attachItemrefs(enhancedElement);
22
+ await this.#attachItemrefs(enhancedElement);
24
23
  }
25
24
  async do() {
26
25
  const [enhancedElement, target, itemscope] = this.#args;
@@ -68,31 +67,26 @@ export class Newish {
68
67
  await ce['<mount>'](ce, enhancedElement, this.#options);
69
68
  }
70
69
  //attach any itemref references
71
- this.#attachItemrefs(enhancedElement);
70
+ await this.#attachItemrefs(enhancedElement);
72
71
  const et = ObsAttr(enhancedElement, 'itemref');
73
72
  et.addEventListener('attr-changed', this);
74
73
  this.isResolved = true;
75
74
  return ce;
76
75
  }
77
- #alreadyAttached = new Set();
78
- #attachItemrefs(enhancedElement) {
76
+ #alreadyAttached = new WeakSet;
77
+ async #attachItemrefs(enhancedElement) {
79
78
  //TODO: watch for already attached itemrefs to be removed and remove them from the set
80
79
  // and call outOfScopeCallback on them
81
80
  if ('<inScope>' in this.#ce && enhancedElement.hasAttribute('itemref')) {
82
- const itemref = enhancedElement.getAttribute('itemref');
83
- const itemrefList = splitRefs(itemref); // itemref.split(' ').map((id) => id.trim()).filter((id) => id.length > 0);
84
- if (itemrefList.length === 0)
85
- return;
86
- const rn = enhancedElement.getRootNode();
87
- for (const id of itemrefList) {
88
- if (this.#alreadyAttached.has(id))
81
+ await import('./refid/via.js');
82
+ const itemref = enhancedElement.via.itemref;
83
+ const refs = itemref.children;
84
+ for (const ref of refs) {
85
+ if (this.#alreadyAttached.has(ref))
89
86
  continue;
90
- const itemrefElement = rn.getElementById(id);
91
- if (itemrefElement) {
92
- this.#ce['<inScope>'](this.#ce, itemrefElement, this.#options);
93
- this.#alreadyAttached.add(id);
94
- }
87
+ this.#ce['<inScope>'](this.#ce, ref, this.#options);
95
88
  }
89
+ itemref.addEventListener('change', this);
96
90
  }
97
91
  }
98
92
  async #assignGingerly(fromDo) {
package/Newish.ts CHANGED
@@ -24,10 +24,10 @@ export class Newish implements EventListenerObject {
24
24
  this.#options = options || {assigner: Object.assign};
25
25
  this.#ref = new WeakRef(enhancedElement);
26
26
  }
27
- handleEvent(): void {
27
+ async handleEvent() {
28
28
  const enhancedElement = this.#ref.deref();
29
29
  if(!enhancedElement) return;
30
- this.#attachItemrefs(enhancedElement);
30
+ await this.#attachItemrefs(enhancedElement);
31
31
  }
32
32
 
33
33
  async do(){
@@ -74,7 +74,7 @@ export class Newish implements EventListenerObject {
74
74
  await ce['<mount>'](ce, enhancedElement as HasIsh & Element, this.#options)
75
75
  }
76
76
  //attach any itemref references
77
- this.#attachItemrefs(enhancedElement);
77
+ await this.#attachItemrefs(enhancedElement);
78
78
  const et = ObsAttr(enhancedElement, 'itemref');
79
79
  et.addEventListener('attr-changed', this);
80
80
  this.isResolved = true;
@@ -83,24 +83,21 @@ export class Newish implements EventListenerObject {
83
83
 
84
84
 
85
85
 
86
- #alreadyAttached = new Set<string>();
86
+ #alreadyAttached = new WeakSet<Element>;
87
87
 
88
- #attachItemrefs(enhancedElement: Element){
88
+ async #attachItemrefs(enhancedElement: Element){
89
89
  //TODO: watch for already attached itemrefs to be removed and remove them from the set
90
90
  // and call outOfScopeCallback on them
91
91
  if('<inScope>' in (<any>this.#ce) && enhancedElement.hasAttribute('itemref')){
92
- const itemref = enhancedElement.getAttribute('itemref')!;
93
- const itemrefList = splitRefs(itemref);// itemref.split(' ').map((id) => id.trim()).filter((id) => id.length > 0);
94
- if(itemrefList.length === 0) return;
95
- const rn = enhancedElement.getRootNode() as Document | ShadowRoot;
96
- for(const id of itemrefList){
97
- if(this.#alreadyAttached.has(id)) continue;
98
- const itemrefElement = rn.getElementById(id);
99
- if(itemrefElement){
100
- (<any>this.#ce)['<inScope>'](this.#ce, itemrefElement, this.#options);
101
- this.#alreadyAttached.add(id);
102
- }
92
+ await import('./refid/via.js');
93
+ const itemref = (<any>enhancedElement).via.itemref
94
+ const refs = itemref.children as Element[];
95
+ for(const ref of refs){
96
+ if(this.#alreadyAttached.has(ref)) continue;
97
+ (<any>this.#ce)['<inScope>'](this.#ce, ref, this.#options);
103
98
  }
99
+ itemref.addEventListener('change', this);
100
+
104
101
  }
105
102
 
106
103
  }
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: Dec 11, 2024
14
+ Last Update: Aug 2, 2025
15
15
 
16
16
  ## Benefits of this API
17
17
 
@@ -444,7 +444,7 @@ The alternative to providing this feature, which I'm leaning towards, is to just
444
444
 
445
445
  ## Support for "donut hole scoping"
446
446
 
447
- While browsers are getting support for css based donut hole scoping, such support appears to be elusive for oElement.querySelectorAll(...) and oElement.matches(...). In fact it is unclear how oElement.matches(...) would ever support it. Such support would be quite useful. for microdata-based binding.
447
+ While browsers are getting support for css based donut hole scoping, such support appears to be elusive for oElement.querySelectorAll(...) and oElement.matches(...). In fact it is unclear to me how oElement.matches(...) would ever be able to support it. Such support would be quite useful for microdata-based binding.
448
448
 
449
449
  Ideally, should this proposal be built into the browser, it would as a matter of course support donut hole scoping.
450
450
 
@@ -787,7 +787,7 @@ The thinking here is that longer roots indicate higher "specificity", so it is s
787
787
 
788
788
  ## Intra document html imports
789
789
 
790
- This proposal "sneaks in" one more feature, that perhaps should stand separately as its own proposal. Because the MountObserver api allows us to attach behaviors on the fly based on css matching, and because the MountObserver would provide developers the "first point of contact" for such functionality, the efficiency argument seemingly "screams out" for this feature.
790
+ This proposal "sneaks in" one more expansive feature, that perhaps should stand separately as its own proposal. Because the MountObserver api allows us to attach behaviors on the fly based on css matching, and because the MountObserver would provide developers the "first point of contact" for such functionality, the efficiency argument seemingly "screams out" for this feature.
791
791
 
792
792
  Also, this proposal is partly focused on better management of importing resources "from a distance", in particular via imports carried out via http. Is it such a stretch to look closely at scenarios where that distance happens to be shorter, i.e. found somewhere [in the document tree structure](https://github.com/tc39/proposal-module-expressions)?
793
793
 
@@ -801,7 +801,7 @@ The need for importing templates by id is also demonstrated by Corset's [Todo li
801
801
  }
802
802
  ```
803
803
 
804
- The mount-observer is always on the lookout for template tags with a src attribute starting with #:
804
+ The mount-observer is always on the lookout for template tags with a src attribute starting with # (as well as url patterns):
805
805
 
806
806
  ```html
807
807
  <template src=#id-of-source-template></template>
@@ -840,9 +840,9 @@ Let's say the source template looks as follows:
840
840
  </div>
841
841
  <div>
842
842
  I don't know why you say
843
- <span part=greeting></span>
843
+ <span part=parting></span>
844
844
  I say
845
- <span part=parting></span>
845
+ <span part=greeting></span>
846
846
  </div>
847
847
  </template>
848
848
  ```
@@ -860,9 +860,9 @@ What we end up with is:
860
860
  </div>
861
861
  <div>
862
862
  I don't know why you say
863
- <span part=greeting>goodbye</span>
863
+ <span part=parting>goodbye</span>
864
864
  I say
865
- <span part=parting>hello</span>
865
+ <span part=greeting>hello</span>
866
866
  </div>
867
867
  <?-?>
868
868
  <div>Strawberry Fields Forever</div>
@@ -929,6 +929,84 @@ The discussion there leads to an open question whether a processing instruction
929
929
 
930
930
  The [add src attribute to template to load a template from file](https://github.com/whatwg/html/issues/10571) and an interesting proposal that is [coming from](https://github.com/htmlcomponents/declarative-shadow-imports/blob/main/examples/02-explainer-proposal/02-html.html) the Edge team [seem quite compatible](https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/ShadowDOM/explainer.md#proposal-inline-declarative-css-module-scripts) with this idea.
931
931
 
932
+ ## Applying DRY to templates. [WIP]
933
+
934
+ Recall that with the previous examples, there was an implicit value of the rel attribute:
935
+
936
+ ```html
937
+ <template src=#source-template rel=stream>
938
+ <span slot=slot1>hello</span>
939
+ <span slot=slot2>goodbye<span>
940
+ </template>
941
+ ```
942
+
943
+ Now we provide another scenario where we want to specify a different kind of use of the src attribute adorning the template element -- simply as a way of saying "here is a template to be used within this context as templates are traditionally used (for cloning reusable HTML), but the actual contents for the template is defined remotely (intra document or via http).
944
+
945
+ My timing experiments indicate that it is faster to extract out all the needed template elements defined within a repeating template -- keep the contents that need repeated cloning lighter, and only clone fragments as needed from an external reference.
946
+
947
+ ```html
948
+ <html>
949
+ <head>
950
+ <template id=directory>
951
+ My Shared Content
952
+ </template>
953
+ </head>
954
+ <body>
955
+ <div itemscope>
956
+ <template id=directoryConsumer rel=preload src=#directory></template>
957
+ </div>
958
+ </body>
959
+ <script type=module>
960
+ import {waitForEvent} from 'mount-observer/waitForEvent.js'
961
+ async function getContent(){
962
+ if(directoryConsumer.remoteContent) return directoryConsumer.remoteContents;
963
+ await waitForEvent(directoryConsumer, 'load');
964
+ return directoryConsumer.remoteContents;
965
+ }
966
+ await getContent(directoryConsumer)
967
+ </script>
968
+ </html>
969
+ ```
970
+
971
+ This can allow for elegant "lazy-loaded recursive" patterns:
972
+
973
+ ```html
974
+ <html>
975
+ <head>
976
+ ...
977
+ <template id=dirs-files>
978
+ <ul itemscope=DirList itemprop=subDirs>
979
+ <li per-each="DirInfo of DirList">
980
+ <details>
981
+ <summary itemprop=name></summary>
982
+ <template 🎚️="on when ^{details}."
983
+ rel=preload src=#dirs-files></template>
984
+ </details>
985
+ </li>
986
+ </ul>
987
+ <ul itemscope=DirList itemprop=files>
988
+ <li per-each="FileInfo of DirList">
989
+ File: <span itemprop=name></span>
990
+ <button name=delete>Delete</button>
991
+ </li>
992
+ </ul>
993
+ </template>
994
+ ...
995
+ </head>
996
+ <body>
997
+ ...
998
+ <button name=dirPick disabled>Pick directory</button>
999
+
1000
+ <details itemscope=DirInfo data-kind="directory"
1001
+ when-resolved="@dirPick+📁⛏️ set $0?.ish?.handle to directoryHandle">
1002
+ <summary itemprop=name></summary>
1003
+ <template 🎚️="on when ^{details}." rel=preload src=#dirs-files></template>
1004
+ </details>
1005
+ ...
1006
+ </body>
1007
+ </html>
1008
+ ```
1009
+
932
1010
  ## Lazy Loading / Conditionally loading intra document imports [WIP specification]
933
1011
 
934
1012
  Just as it is useful to be able lazy load external imports when needed, it would also be useful to do the same for intra document HTML imports. The most straightforward way this could be done seems to be as follows, either introducing some attribute like "type=conditional", or defining a new element that inherits from the HTMLTemplateElement, for example:
@@ -970,46 +1048,6 @@ Just as it is useful to be able lazy load external imports when needed, it would
970
1048
  </compose>
971
1049
  ```
972
1050
 
973
- ## Applying DRY to templates. [WIP]
974
-
975
- Recall that with the previous examples, there was an implicit value of the rel attribute:
976
-
977
- ```html
978
- <template src=#source-template rel=stream>
979
- <span slot=slot1>hello</span>
980
- <span slot=slot2>goodbye<span>
981
- </template>
982
- ```
983
-
984
- Now we provide another scenario where we want to specify a different kind of use of the src attribute adorning the template element -- simply as a way of saying "here is a template to be used within this context as templates are traditionally used (for cloning reusable HTML), but the actual contents for the template is defined remotely (intra document or via http).
985
-
986
- My timing experiments indicate that it is faster to extract out all the needed template elements defined within a repeating template -- keep the contents that need repeated cloning lighter, and only clone fragments as needed from an external reference.
987
-
988
- ```html
989
- <html>
990
- <head>
991
- <template id=directory>
992
- My Shared Content
993
- </template>
994
- </head>
995
- <body>
996
- <div itemscope>
997
- <template id=directoryConsumer rel=preload src=#directory></template>
998
- </div>
999
- </body>
1000
- <script type=module>
1001
- import {waitForEvent} from 'mount-observer/waitForEvent.js'
1002
- async function getContent(){
1003
- if(directoryConsumer.remoteContent) return directoryConsumer.remoteContents;
1004
- await waitForEvent(directoryConsumer, 'load');
1005
- return directoryConsumer.remoteContents;
1006
- }
1007
- await getContent(directoryConsumer)
1008
- </script>
1009
- </html>
1010
- ```
1011
-
1012
-
1013
1051
 
1014
1052
  ## Creating "frameworks" that revolve around MOSEs.
1015
1053
 
@@ -1084,5 +1122,112 @@ And we can give each inheriting ShadowRoot a personality of its own by customizi
1084
1122
 
1085
1123
  ## Creating an Element-To-RefID DOM passageway
1086
1124
 
1125
+ The platform provides some nice help with managing forms, including IDREF dependency support:
1126
+
1127
+ ```html
1128
+ <input id=field2 name=field2 form=myForm>
1129
+
1130
+ <form id=myForm>
1131
+ <input name="field1">
1132
+ </form>
1133
+ <script>
1134
+ console.log(myForm.elements);
1135
+ // includes both field1 and field2
1136
+ console.log(field2.form)
1137
+ // form#myForm
1138
+ </script>
1139
+ ```
1140
+
1141
+ This would be useful for other linkages as well, which the platform doesn't support currently.
1142
+
1143
+ Again, because of the mount-observer being the "first point of contact" with the DOM, this is supported by mount-observer as well.
1144
+
1145
+ ```html
1146
+ <section id=section>
1147
+ <div id=myDiv itemscope itemref="myID1 myID2">
1148
+ <span itemprop=greeting></span>
1149
+ </div>
1150
+ </section>
1151
+ ...
1152
+ <div id=myId1>
1153
+ <span itemprop=greeting2>hello</span>
1154
+ </div>
1155
+ <div id=myId2>...</div>
1156
+ <script>
1157
+ console.log(myDiv.via.itemref.children);
1158
+ // [div#myId1, div#myId2]
1159
+ myDiv.via.itemref.addEventListener('change', e => {
1160
+ console.log({e});
1161
+ //{addedChildren, removedChildren}
1162
+ });
1163
+ console.log(myId2.via.itemref.parents); //TODO
1164
+ const mo = new MountObserver({ //TODO
1165
+ via: 'itemref.children',
1166
+ });
1167
+ mo.observe('#section');
1168
+ </script>
1169
+ ```
1170
+
1171
+ ```html
1172
+ <span
1173
+ id=mySpan
1174
+ role="checkbox"
1175
+ aria-checked="false"
1176
+ tabindex="0"
1177
+ aria-labelledby="tac tac2"></span>
1178
+ ...
1179
+ <span id="tac">I agree to the Terms and Conditions.</span>
1180
+
1181
+ <span id="tac2">I agree to the other Terms and Conditions.</span>
1182
+ <script>
1183
+ console.log(mySpan.via.ariaLabelledby.children);
1184
+ //[span#tac, span#tac2]
1185
+
1186
+ console.log(tac2.via.ariaLabelledby.parents);
1187
+ //[span#mySpan]
1188
+
1189
+
1190
+ mySpan.via.ariaLabelledby.addEventListener('change', e => {
1191
+ console.log({e});
1192
+ //{addedChildren, removedChildren}
1193
+ });
1194
+
1195
+
1196
+ </script>
1197
+ ```
1198
+
1199
+
1200
+
1201
+ ```html
1202
+ <table>
1203
+ <thead>
1204
+ ...
1205
+ </thead>
1206
+ <tbody id=myTbody>
1207
+ <tr id=myTR1 data-parent-name=group1>
1208
+ <td>hello</td>
1209
+ <tr id=myTR2 data-parent-name=group1>
1210
+ <td>goodbye</td>
1211
+ </tr>
1212
+ <tr id=myTR3 data-parent-name=group2>
1213
+ <td>good morrow</td>
1214
+ </tr>
1215
+ </tbody>
1216
+ </table>
1217
+ <script>
1218
+ console.log(myTbody.group.dataParentName.by.group1);
1219
+ // [tr.myTR1, tr.myTR2]
1220
+ myTbody.group.dataParentName.by.group1.addEventListener('change', e => {
1221
+ {addedMember, removedMember}
1222
+ });
1223
+
1224
+ console.log(myTR1.joinMatching.dataParentName.fromClosest.tbody);
1225
+ // [tr#myTR1, tr#myTR2]
1226
+ console.log(myTR1.joinMatching.dataParentName.fromParent);
1227
+ // [tr#myTR1, tr#myTR2]
1228
+
1229
+ </script>
1230
+ ```
1231
+
1087
1232
 
1088
1233
 
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "mount-observer",
3
- "version": "0.0.81",
3
+ "version": "0.0.82",
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.54.1",
8
+ "@playwright/test": "1.54.2",
9
9
  "ssi-server": "0.0.1"
10
10
  },
11
11
  "exports": {
@@ -53,14 +53,31 @@
53
53
  "default": "./waitForIsh.js",
54
54
  "types": "./waitForIsh.ts"
55
55
  },
56
+ "./refid/camelToKebab.js": {
57
+ "default": "./refid/camelToKebab.js",
58
+ "types": "./refid/camelToKebab.ts"
59
+ },
56
60
  "./refid/getAdjRefs.js": {
57
61
  "default": "./refid/getAdjRefs.js",
58
62
  "types": "./refid/getAdjRefs.ts"
59
63
  },
64
+ "./refid/joinMatching.js":{
65
+ "default": "./refid/joinMatching.js",
66
+ "types": "./refid/joinMatching.ts"
67
+ },
68
+ "./refid/refs.js": {
69
+ "default": "./refid/refs.js",
70
+ "types": "./refid/refs.ts"
71
+ },
60
72
  "./refid/regIsh.js": {
61
73
  "default": "./refid/regIsh.js",
62
- "types": "./refid/regIsh.js"
74
+ "types": "./refid/regIsh.ts"
75
+ },
76
+ "./refid/via.js": {
77
+ "default": "./refid/via.js",
78
+ "types": "./refid/via.ts"
63
79
  },
80
+
64
81
  "./refid/splitRefs.js": {
65
82
  "default": "./refid/splitRefs.js",
66
83
  "types": "./refid/splitRefs.ts"
@@ -0,0 +1,4 @@
1
+ const ctlRe = /(?=[A-Z])/;
2
+ export function camelToKebab(s) {
3
+ return s.split(ctlRe).join('-').toLowerCase();
4
+ }
@@ -0,0 +1,4 @@
1
+ const ctlRe = /(?=[A-Z])/;
2
+ export function camelToKebab(s: string) {
3
+ return s.split(ctlRe).join('-').toLowerCase();
4
+ }
@@ -0,0 +1,56 @@
1
+ import { camelToKebab } from './camelToKebab.js';
2
+ const proxies = new WeakMap();
3
+ Object.defineProperty(Element.prototype, 'joinMatching', {
4
+ get() {
5
+ if (!proxies.has(this)) {
6
+ const handler = {
7
+ get(target, prop) {
8
+ const jm = new JoinMatching(target, prop);
9
+ return jm;
10
+ },
11
+ };
12
+ proxies.set(this, new Proxy(this, handler));
13
+ }
14
+ return proxies.get(this);
15
+ },
16
+ });
17
+ export class JoinMatching {
18
+ #proxy;
19
+ elRef;
20
+ attr;
21
+ constructor(el, prop) {
22
+ this.elRef = new WeakRef(el);
23
+ this.attr = camelToKebab(prop);
24
+ }
25
+ get fromClosest() {
26
+ const handler = {
27
+ get(self, closestQry) {
28
+ const { elRef, attr } = self;
29
+ const el = elRef.deref();
30
+ if (el === undefined)
31
+ return [];
32
+ const attrVal = el.getAttribute(attr);
33
+ const tryClosestQry = camelToKebab(closestQry);
34
+ let closest = el.closest(tryClosestQry);
35
+ if (closest === null)
36
+ closest = el.closest(closestQry);
37
+ if (closest === null)
38
+ throw 404;
39
+ return Array.from(closest.querySelectorAll(`[${attr}="${attrVal}"]`));
40
+ //console.log({self, closestQry, prop, el});
41
+ }
42
+ };
43
+ return new Proxy(this, handler);
44
+ }
45
+ get fromParent() {
46
+ const { elRef, attr } = this;
47
+ const el = elRef.deref();
48
+ if (el === undefined)
49
+ return [];
50
+ const { parentElement } = el;
51
+ if (parentElement === null)
52
+ return [];
53
+ const attrVal = el.getAttribute(attr);
54
+ return Array.from(parentElement.querySelectorAll(`[${attr}="${attrVal}"]`));
55
+ }
56
+ }
@@ -0,0 +1,54 @@
1
+ import {camelToKebab} from './camelToKebab.js';
2
+ const proxies = new WeakMap<Element, ProxyConstructor>();
3
+ Object.defineProperty(Element.prototype, 'joinMatching', {
4
+ get(){
5
+ if(!proxies.has(this)){
6
+ const handler = {
7
+ get(target: Element, prop: string) {
8
+ const jm = new JoinMatching(target, prop);
9
+ return jm;
10
+ },
11
+ }
12
+ proxies.set(this, new Proxy(this, handler));
13
+ }
14
+ return proxies.get(this);
15
+ },
16
+ });
17
+
18
+ export class JoinMatching {
19
+ #proxy: ProxyConstructor | undefined;
20
+ elRef: WeakRef<Element>;
21
+ attr: string;
22
+ constructor(el: Element, prop: string){
23
+ this.elRef = new WeakRef(el);
24
+ this.attr = camelToKebab(prop);
25
+ }
26
+
27
+ get fromClosest(){
28
+ const handler = {
29
+ get(self: JoinMatching, closestQry: string){
30
+ const {elRef, attr} = self;
31
+ const el = elRef.deref();
32
+ if(el === undefined) return [];
33
+ const attrVal = el.getAttribute(attr);
34
+ const tryClosestQry = camelToKebab(closestQry);
35
+ let closest = el.closest(tryClosestQry);
36
+ if(closest === null) closest = el.closest(closestQry);
37
+ if(closest === null) throw 404;
38
+ return Array.from(closest.querySelectorAll(`[${attr}="${attrVal}"]`))
39
+ //console.log({self, closestQry, prop, el});
40
+ }
41
+ }
42
+ return new Proxy(this, handler);
43
+ }
44
+
45
+ get fromParent(){
46
+ const {elRef, attr} = this;
47
+ const el = elRef.deref();
48
+ if(el === undefined) return [];
49
+ const {parentElement} = el;
50
+ if(parentElement === null) return [];
51
+ const attrVal = el.getAttribute(attr);
52
+ return Array.from(parentElement.querySelectorAll(`[${attr}="${attrVal}"]`));
53
+ }
54
+ }
package/refid/via.js ADDED
@@ -0,0 +1,112 @@
1
+ import { splitRefs } from './splitRefs.js';
2
+ import { MountObserver } from '../MountObserver.js';
3
+ import { camelToKebab } from './camelToKebab.js';
4
+ const proxies = new WeakMap();
5
+ const refLookup = new WeakMap();
6
+ Object.defineProperty(Element.prototype, 'via', {
7
+ get() {
8
+ if (!proxies.has(this)) {
9
+ const handler = {
10
+ get(target, prop) {
11
+ let lookup;
12
+ if (refLookup.has(target)) {
13
+ lookup = refLookup.get(target);
14
+ }
15
+ else {
16
+ lookup = new Map();
17
+ refLookup.set(target, lookup);
18
+ }
19
+ if (lookup.has(prop)) {
20
+ return lookup.get(prop);
21
+ }
22
+ else {
23
+ const refManager = new RefManager(target, prop);
24
+ lookup.set(prop, refManager);
25
+ return refManager;
26
+ }
27
+ //return Reflect.get(target, prop);
28
+ },
29
+ };
30
+ proxies.set(this, new Proxy(this, handler));
31
+ }
32
+ return proxies.get(this);
33
+ }
34
+ });
35
+ class RefManager extends EventTarget {
36
+ #el;
37
+ #children;
38
+ #attr;
39
+ //#parents: Array<WeakRef<Element>> | undefined;
40
+ constructor(el, prop) {
41
+ super();
42
+ this.#attr = camelToKebab(prop);
43
+ this.#el = new WeakRef(el);
44
+ }
45
+ get children() {
46
+ if (this.#children === undefined) {
47
+ const el = this.#el.deref();
48
+ if (el === undefined)
49
+ return [];
50
+ const attr = el.getAttribute(this.#attr);
51
+ if (!attr)
52
+ return [];
53
+ const refIds = splitRefs(attr);
54
+ const qry = refIds.map(id => `#${id}`).join(', ');
55
+ const rn = el.getRootNode();
56
+ const refsArr = Array.from(rn.querySelectorAll(qry));
57
+ const refs = new Map();
58
+ for (const ref of refsArr) {
59
+ refs.set(ref.id, new WeakRef(ref));
60
+ }
61
+ this.#children = refs;
62
+ const mo = new MountObserver({
63
+ on: qry,
64
+ do: {
65
+ mount: (el) => {
66
+ const id = el.id;
67
+ if (id && !this.#children?.has(id)) {
68
+ this.#children?.set(id, new WeakRef(el));
69
+ this.dispatchEvent(new ChangeEvent([el], []));
70
+ }
71
+ },
72
+ dismount: (el) => {
73
+ const id = el.id;
74
+ if (id && this.#children?.has(id)) {
75
+ this.#children?.delete(id);
76
+ this.dispatchEvent(new ChangeEvent([], [el]));
77
+ }
78
+ }
79
+ }
80
+ });
81
+ mo.observe(rn);
82
+ this.dispatchEvent(new ChangeEvent((refsArr), []));
83
+ }
84
+ return Array.from(this.#children?.values().map(ref => ref.deref()).filter(el => el !== undefined)) || [];
85
+ }
86
+ get parents() {
87
+ //for now, hold off on caching parents until a use case arises
88
+ //if(this.#parents === undefined){
89
+ const el = this.#el.deref();
90
+ if (el === undefined)
91
+ return [];
92
+ if (el.id === '')
93
+ return [];
94
+ const rn = el.getRootNode();
95
+ const qry = `[${this.#attr}~="${el.id}"]`;
96
+ const parents = Array.from(rn.querySelectorAll(qry));
97
+ //this.#parents = parents.map(parent => new WeakRef(parent));
98
+ return parents;
99
+ //}
100
+ //return this.#parents.map(ref => ref.deref()).filter(el => el !== undefined) || [];
101
+ }
102
+ }
103
+ export class ChangeEvent extends Event {
104
+ addedChildren;
105
+ removedChildren;
106
+ static eventName = 'change';
107
+ constructor(addedChildren, removedChildren) {
108
+ super(ChangeEvent.eventName);
109
+ this.addedChildren = addedChildren;
110
+ this.removedChildren = removedChildren;
111
+ }
112
+ }
package/refid/via.ts ADDED
@@ -0,0 +1,111 @@
1
+ import {splitRefs} from './splitRefs.js';
2
+ import {MountObserver} from '../MountObserver.js';
3
+ import {camelToKebab} from './camelToKebab.js';
4
+ const proxies = new WeakMap<Element, ProxyConstructor>();
5
+ const refLookup = new WeakMap<Element, RefLookup>();
6
+ Object.defineProperty(Element.prototype, 'via', {
7
+ get(){
8
+ if(!proxies.has(this)){
9
+ const handler = {
10
+ get(target: Element, prop: string) {
11
+ let lookup: RefLookup;
12
+ if(refLookup.has(target)){
13
+ lookup = refLookup.get(target)!;
14
+ }else{
15
+ lookup = new Map<attr, RefManager>();
16
+ refLookup.set(target, lookup);
17
+ }
18
+ if(lookup.has(prop)){
19
+ return lookup.get(prop);
20
+ }else{
21
+ const refManager = new RefManager(target, prop as string);
22
+ lookup.set(prop, refManager);
23
+ return refManager;
24
+ }
25
+ //return Reflect.get(target, prop);
26
+ },
27
+
28
+ };
29
+ proxies.set(this, new Proxy(this, handler));
30
+ }
31
+ return proxies.get(this);
32
+ }
33
+ });
34
+
35
+ type attr = string;
36
+
37
+ type RefLookup = Map<attr, RefManager>;
38
+
39
+ class RefManager extends EventTarget {
40
+ #el: WeakRef<Element>;
41
+ #children: Map<string, WeakRef<Element>> | undefined;
42
+ #attr: string;
43
+ //#parents: Array<WeakRef<Element>> | undefined;
44
+ constructor(el: Element, prop: string){
45
+ super();
46
+ this.#attr = camelToKebab(prop);
47
+ this.#el = new WeakRef(el);
48
+ }
49
+ get children(){
50
+ if(this.#children === undefined){
51
+ const el = this.#el.deref();
52
+ if(el === undefined) return [];
53
+ const attr = el.getAttribute(this.#attr);
54
+ if(!attr) return [];
55
+ const refIds = splitRefs(attr);
56
+ const qry = refIds.map(id => `#${id}`).join(', ');
57
+ const rn = el.getRootNode() as DocumentFragment;
58
+ const refsArr = Array.from(rn.querySelectorAll(qry));
59
+ const refs = new Map<string, WeakRef<Element>>();
60
+ for(const ref of refsArr){
61
+ refs.set(ref.id, new WeakRef(ref));
62
+ }
63
+ this.#children = refs;
64
+ const mo = new MountObserver({
65
+ on: qry,
66
+ do: {
67
+ mount: (el) => {
68
+ const id = el.id;
69
+ if(id && !this.#children?.has(id)){
70
+ this.#children?.set(id, new WeakRef(el));
71
+ this.dispatchEvent(new ChangeEvent([el], []));
72
+ }
73
+ },
74
+ dismount: (el) => {
75
+ const id = el.id;
76
+ if(id && this.#children?.has(id)){
77
+ this.#children?.delete(id);
78
+ this.dispatchEvent(new ChangeEvent([], [el]));
79
+ }
80
+ }
81
+ }
82
+ });
83
+ mo.observe(rn);
84
+ this.dispatchEvent(new ChangeEvent((refsArr), []));
85
+ }
86
+ return Array.from(this.#children?.values().map(ref => ref.deref()).filter(el => el !== undefined)) || [];
87
+ }
88
+
89
+ get parents(){
90
+ //for now, hold off on caching parents until a use case arises
91
+ //if(this.#parents === undefined){
92
+ const el = this.#el.deref();
93
+ if(el === undefined) return [];
94
+ if(el.id === '') return [];
95
+ const rn = el.getRootNode() as DocumentFragment;
96
+ const qry = `[${this.#attr}~="${el.id}"]`;
97
+ const parents = Array.from(rn.querySelectorAll(qry));
98
+ //this.#parents = parents.map(parent => new WeakRef(parent));
99
+ return parents;
100
+ //}
101
+ //return this.#parents.map(ref => ref.deref()).filter(el => el !== undefined) || [];
102
+
103
+ }
104
+ }
105
+
106
+ export class ChangeEvent extends Event {
107
+ static eventName = 'change';
108
+ constructor(public addedChildren: Array<Element>, public removedChildren: Array<Element>){
109
+ super(ChangeEvent.eventName);
110
+ }
111
+ }
@@ -192,7 +192,9 @@ export interface IshConfig<TProps = any, TActions = TProps, ETProps = TProps>{
192
192
  inScopeXForms?: {[key: CSSQuery]: XForm<TProps, TActions>};
193
193
  ishListCountProp?: keyof TProps & string;
194
194
  defaultIshList?: any[];
195
- mapParentScopeRefTo?: keyof TProps & string;
195
+ mapParentScopeRefTo?: keyof TProps & string;
196
+ //mapElTo?: keyof TProps & string; //make sure strong use case before implementing
197
+ ignoreItemProp?: boolean;
196
198
  }
197
199
  export interface OConfig<TProps = any, TActions = TProps, ETProps = TProps> extends IshConfig<TProps, TActions, ETProps>{
198
200
  mainTemplate?: string | HTMLTemplateElement;
@@ -440,7 +442,7 @@ export interface RoundaboutReady{
440
442
  * If truthy, can call await awake() before processing should resume
441
443
  * [TODO]
442
444
  */
443
- readonly sleep?: number,
445
+ readonly sleep?: number | undefined;
444
446
 
445
447
  awake(): Promise<void>;
446
448
 
package/upShadowSearch.ts CHANGED
@@ -4,7 +4,7 @@ export function upShadowSearch(ref: Element, id: string){
4
4
  let test = rn.getElementById(id);
5
5
  if(test) return test;
6
6
  if(rn.host){
7
- test = rn.host[id];
7
+ test = (<any>rn.host)[id];
8
8
  if(test instanceof HTMLElement) return test;
9
9
  rn = rn.host.getRootNode() as (DocumentFragment | ShadowRoot) & { host?: Element };
10
10
  }else if(rn === document){