mount-observer 0.0.80 → 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 +12 -18
- package/Newish.ts +13 -16
- package/README.md +193 -48
- package/package.json +20 -3
- package/preloadContent.ts +4 -1
- package/refid/camelToKebab.js +4 -0
- package/refid/camelToKebab.ts +4 -0
- package/refid/joinMatching.js +56 -0
- package/refid/joinMatching.ts +54 -0
- package/refid/via.js +112 -0
- package/refid/via.ts +111 -0
- package/ts-refs/trans-render/froop/types.d.ts +4 -1
- package/ts-refs/when-resolved/types.d.ts +1 -0
- package/upShadowSearch.js +1 -1
- package/upShadowSearch.ts +2 -2
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
|
|
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
|
-
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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()
|
|
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
|
|
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
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
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:
|
|
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
|
|
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=
|
|
843
|
+
<span part=parting></span>
|
|
844
844
|
I say
|
|
845
|
-
<span part=
|
|
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=
|
|
863
|
+
<span part=parting>goodbye</span>
|
|
864
864
|
I say
|
|
865
|
-
<span part=
|
|
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.
|
|
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.
|
|
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.
|
|
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"
|
package/preloadContent.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import {TemplateWithRemoteContent} from './ts-refs/mount-observer/types.js';
|
|
2
2
|
const remoteTemplElSym = Symbol.for('du3y+tfsAUGFHMG/iHZiMQ');
|
|
3
3
|
|
|
4
|
-
export async function preloadContent(
|
|
4
|
+
export async function preloadContent(
|
|
5
|
+
templ: HTMLTemplateElement,
|
|
6
|
+
target?: DocumentFragment | ShadowRoot | Document | Element
|
|
7
|
+
) {
|
|
5
8
|
const templWithRemoteContent = templ as TemplateWithRemoteContent & {
|
|
6
9
|
[remoteTemplElSym]?: WeakRef<HTMLTemplateElement>
|
|
7
10
|
};
|
|
@@ -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,6 +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;
|
|
196
|
+
//mapElTo?: keyof TProps & string; //make sure strong use case before implementing
|
|
197
|
+
ignoreItemProp?: boolean;
|
|
195
198
|
}
|
|
196
199
|
export interface OConfig<TProps = any, TActions = TProps, ETProps = TProps> extends IshConfig<TProps, TActions, ETProps>{
|
|
197
200
|
mainTemplate?: string | HTMLTemplateElement;
|
|
@@ -439,7 +442,7 @@ export interface RoundaboutReady{
|
|
|
439
442
|
* If truthy, can call await awake() before processing should resume
|
|
440
443
|
* [TODO]
|
|
441
444
|
*/
|
|
442
|
-
readonly sleep?: number
|
|
445
|
+
readonly sleep?: number | undefined;
|
|
443
446
|
|
|
444
447
|
awake(): Promise<void>;
|
|
445
448
|
|
package/upShadowSearch.js
CHANGED
package/upShadowSearch.ts
CHANGED
|
@@ -4,13 +4,13 @@ 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){
|
|
11
11
|
return null;
|
|
12
12
|
}
|
|
13
|
-
|
|
13
|
+
return null;
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
|