mount-observer 0.0.77 → 0.0.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MountObserver.js +15 -3
- package/MountObserver.ts +14 -3
- package/README.md +51 -3
- package/getContent.ts +10 -0
- package/package.json +6 -2
- package/preloadContent.js +42 -0
- package/preloadContent.ts +40 -0
- package/ts-refs/folder-picker/types.d.ts +43 -0
- package/ts-refs/mount-observer/types.d.ts +4 -0
- package/ts-refs/trans-render/types.d.ts +2 -2
- package/upShadowSearch.js +18 -0
- package/upShadowSearch.ts +16 -0
package/MountObserver.js
CHANGED
|
@@ -63,7 +63,12 @@ export class MountObserver extends EventTarget {
|
|
|
63
63
|
async composeFragment(fragment, level) {
|
|
64
64
|
const bis = fragment.querySelectorAll(`${inclTemplQry}`);
|
|
65
65
|
for (const bi of bis) {
|
|
66
|
-
|
|
66
|
+
if (bi.getAttribute('rel') === 'preload') {
|
|
67
|
+
(await import('./preloadContent.js')).preloadContent(bi, this.#mountInit.withTargetShadowRoot);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
await this.#compose(bi, level);
|
|
71
|
+
}
|
|
67
72
|
}
|
|
68
73
|
}
|
|
69
74
|
async #compose(el, level) {
|
|
@@ -465,7 +470,7 @@ export class MountObserver extends EventTarget {
|
|
|
465
470
|
//TODO: check for outside
|
|
466
471
|
}
|
|
467
472
|
if (outside !== undefined) {
|
|
468
|
-
if (!this.#outsideCheck(
|
|
473
|
+
if (!this.#outsideCheck(this.objNde.deref(), x, outside))
|
|
469
474
|
return false;
|
|
470
475
|
}
|
|
471
476
|
if (whereSatisfies !== undefined) {
|
|
@@ -480,10 +485,17 @@ export class MountObserver extends EventTarget {
|
|
|
480
485
|
});
|
|
481
486
|
for (const elToMount of elsToMount) {
|
|
482
487
|
if (elToMount.matches(inclTemplQry)) {
|
|
483
|
-
|
|
488
|
+
if (elToMount instanceof HTMLTemplateElement && elToMount.getAttribute('rel') === 'preload') {
|
|
489
|
+
(await import('./preloadContent.js')).preloadContent(elToMount, this.#mountInit.withTargetShadowRoot);
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
await this.#compose(elToMount, 0);
|
|
493
|
+
}
|
|
484
494
|
}
|
|
485
495
|
}
|
|
486
496
|
await bindishIt(els, target, { assigner });
|
|
497
|
+
if (elsToMount.length === 0)
|
|
498
|
+
return;
|
|
487
499
|
this.#mount(elsToMount, initializing);
|
|
488
500
|
}
|
|
489
501
|
async #inspectWithin(within, initializing) {
|
package/MountObserver.ts
CHANGED
|
@@ -76,7 +76,12 @@ export class MountObserver extends EventTarget implements IMountObserver{
|
|
|
76
76
|
async composeFragment(fragment: DocumentFragment, level: number){
|
|
77
77
|
const bis = fragment.querySelectorAll(`${inclTemplQry}`) as NodeListOf<HTMLTemplateElement>;
|
|
78
78
|
for(const bi of bis){
|
|
79
|
-
|
|
79
|
+
if(bi.getAttribute('rel') === 'preload'){
|
|
80
|
+
(await import('./preloadContent.js')).preloadContent(bi, this.#mountInit.withTargetShadowRoot);
|
|
81
|
+
}else{
|
|
82
|
+
await this.#compose(bi, level);
|
|
83
|
+
}
|
|
84
|
+
|
|
80
85
|
}
|
|
81
86
|
}
|
|
82
87
|
|
|
@@ -497,7 +502,7 @@ export class MountObserver extends EventTarget implements IMountObserver{
|
|
|
497
502
|
//TODO: check for outside
|
|
498
503
|
}
|
|
499
504
|
if(outside !== undefined){
|
|
500
|
-
if(!this.#outsideCheck(
|
|
505
|
+
if(!this.#outsideCheck(this.objNde!.deref() as Element, x, outside)) return false;
|
|
501
506
|
}
|
|
502
507
|
if(whereSatisfies !== undefined){
|
|
503
508
|
if(!whereSatisfies(x, this, {stage: 'Inspecting', initializing})) return false;
|
|
@@ -509,11 +514,17 @@ export class MountObserver extends EventTarget implements IMountObserver{
|
|
|
509
514
|
});
|
|
510
515
|
for(const elToMount of elsToMount){
|
|
511
516
|
if(elToMount.matches(inclTemplQry)){
|
|
512
|
-
|
|
517
|
+
if(elToMount instanceof HTMLTemplateElement && elToMount.getAttribute('rel') === 'preload'){
|
|
518
|
+
(await import('./preloadContent.js')).preloadContent(elToMount, this.#mountInit.withTargetShadowRoot);
|
|
519
|
+
}else{
|
|
520
|
+
await this.#compose(elToMount as HTMLTemplateElement, 0);
|
|
521
|
+
}
|
|
522
|
+
|
|
513
523
|
}
|
|
514
524
|
|
|
515
525
|
}
|
|
516
526
|
await bindishIt(els, target, {assigner});
|
|
527
|
+
if(elsToMount.length === 0) return;
|
|
517
528
|
this.#mount(elsToMount, initializing);
|
|
518
529
|
}
|
|
519
530
|
|
package/README.md
CHANGED
|
@@ -819,6 +819,15 @@ For example:
|
|
|
819
819
|
<div>Strawberry Fields Forever</div>
|
|
820
820
|
```
|
|
821
821
|
|
|
822
|
+
Optionally, a rel=stream attribute can be specified. Other values of the attribute will result in different behavior from what is described below:
|
|
823
|
+
|
|
824
|
+
```html
|
|
825
|
+
<template rel=stream src=#id-of-source-template>
|
|
826
|
+
<span part=greeting>hello</span>
|
|
827
|
+
<span part=parting>goodbye<span>
|
|
828
|
+
</template>
|
|
829
|
+
```
|
|
830
|
+
|
|
822
831
|
When it encounters such a thing, it searches "upwardly" through the chain of ShadowRoots for a template with id=id-of-source-template (in this case), and caches them as it finds them.
|
|
823
832
|
|
|
824
833
|
Let's say the source template looks as follows:
|
|
@@ -862,8 +871,8 @@ What we end up with is:
|
|
|
862
871
|
Some significant differences with slot support as used with (ShadowDOM'd) custom elements
|
|
863
872
|
|
|
864
873
|
1. The mechanism to weave DOM together is more flexible here: We are searching for DOM elements that match all the attributes of the children of the *target* template, that template that is pulling in the intra document source template. The "part" attribute was used just as an example.
|
|
865
|
-
2. There is no mechanism for updating
|
|
866
|
-
2. ShadowDOM's slots act on a "many to one" basis. Multiple light children with identical slot identifiers all get merged into a single (first?) matching slot within the Shadow DOM. These "birtual" (birth-only, virtual) inclusions, instead, follow the opposite approach -- a single element can get cloned into multiple slot targets as it weaves itself into the templates as they get merged together.
|
|
874
|
+
2. There is no mechanism for updating slots. That is something under investigation with this userland [custom enhancement](https://github.com/bahrus/be-inclusive) that allows for updating the existing DOM tree based on identical syntax.
|
|
875
|
+
2. ShadowDOM's slots act on a "many to one" basis. Multiple light children with identical slot identifiers all get merged into a single (first?) matching slot within the Shadow DOM. These "birtual" (birth-only, virtual) streaming inclusions, instead, follow the opposite approach -- a single element can get cloned into multiple slot targets as it weaves itself into the templates as they get merged together.
|
|
867
876
|
|
|
868
877
|
## Intra document html imports with Shadow DOM support
|
|
869
878
|
|
|
@@ -925,7 +934,7 @@ The [add src attribute to template to load a template from file](https://github.
|
|
|
925
934
|
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:
|
|
926
935
|
|
|
927
936
|
```html
|
|
928
|
-
<template id=source-template
|
|
937
|
+
<template id=source-template rel=conditional-stream>
|
|
929
938
|
|
|
930
939
|
<template mount='{
|
|
931
940
|
"on": ":not([defer-loading])",
|
|
@@ -961,6 +970,45 @@ Just as it is useful to be able lazy load external imports when needed, it would
|
|
|
961
970
|
</compose>
|
|
962
971
|
```
|
|
963
972
|
|
|
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
|
+
|
|
964
1012
|
|
|
965
1013
|
|
|
966
1014
|
## Creating "frameworks" that revolve around MOSEs.
|
package/getContent.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import {waitForEvent} from './waitForEvent.js';
|
|
2
|
+
import {TemplateWithRemoteContent} from './ts-refs/mount-observer/types.js';
|
|
3
|
+
|
|
4
|
+
export async function getContent(templ: HTMLTemplateElement, target?: DocumentFragment | ShadowRoot | Document | Element) {
|
|
5
|
+
const templWithRemoteContent = templ as TemplateWithRemoteContent;
|
|
6
|
+
if(templWithRemoteContent.remoteContent) return templWithRemoteContent.remoteContent;
|
|
7
|
+
await waitForEvent(templ, 'load');
|
|
8
|
+
if(templWithRemoteContent.remoteContent) return templWithRemoteContent.remoteContent;
|
|
9
|
+
throw 500; //remote content not loaded
|
|
10
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mount-observer",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.79",
|
|
4
4
|
"description": "Observe and act on css matches.",
|
|
5
5
|
"main": "MountObserver.js",
|
|
6
6
|
"module": "MountObserver.js",
|
|
@@ -41,6 +41,10 @@
|
|
|
41
41
|
"default": "./doCleanup.js",
|
|
42
42
|
"types": "./doCleanup.ts"
|
|
43
43
|
},
|
|
44
|
+
"./getContent.js": {
|
|
45
|
+
"default": "./getContent.js",
|
|
46
|
+
"types": "./getContent.ts"
|
|
47
|
+
},
|
|
44
48
|
"./waitForEvent.js": {
|
|
45
49
|
"default": "./waitForEvent.js",
|
|
46
50
|
"types": "./waitForEvent.ts"
|
|
@@ -87,7 +91,7 @@
|
|
|
87
91
|
],
|
|
88
92
|
"types": "./ts-refs/mount-observer/types.d.ts",
|
|
89
93
|
"scripts": {
|
|
90
|
-
"serve": "
|
|
94
|
+
"serve": "python ./node_modules/ssi-server/ssi_server.py",
|
|
91
95
|
"test": "playwright test",
|
|
92
96
|
"safari": "npx playwright wk http://localhost:8000",
|
|
93
97
|
"update": "ncu -u && npm install"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const remoteTemplElSym = Symbol.for('du3y+tfsAUGFHMG/iHZiMQ');
|
|
2
|
+
export async function preloadContent(templ, target) {
|
|
3
|
+
const templWithRemoteContent = templ;
|
|
4
|
+
if (templWithRemoteContent.remoteContent)
|
|
5
|
+
return templWithRemoteContent.remoteContent;
|
|
6
|
+
const src = templ.getAttribute('src');
|
|
7
|
+
if (!src)
|
|
8
|
+
throw 300; //no src attribute
|
|
9
|
+
const isIntraDoc = src[0] === '#';
|
|
10
|
+
if (!('remoteContent' in templWithRemoteContent)) {
|
|
11
|
+
//define a property on the template instance
|
|
12
|
+
Object.defineProperty(templWithRemoteContent, 'remoteContent', {
|
|
13
|
+
get() {
|
|
14
|
+
if (isIntraDoc) {
|
|
15
|
+
const ref = this[remoteTemplElSym]?.deref();
|
|
16
|
+
if (ref)
|
|
17
|
+
return ref.content;
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
throw 'NI'; //not implemented
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
enumerable: true,
|
|
24
|
+
configurable: true,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (isIntraDoc) {
|
|
31
|
+
const id = src.substring(1);
|
|
32
|
+
const { upShadowSearch } = await import('./upShadowSearch.js');
|
|
33
|
+
const remoteTempl = upShadowSearch(templ, id) || upShadowSearch((target || document), id);
|
|
34
|
+
if (!(remoteTempl instanceof HTMLTemplateElement))
|
|
35
|
+
throw 404; //not found
|
|
36
|
+
templWithRemoteContent[remoteTemplElSym] = new WeakRef(remoteTempl);
|
|
37
|
+
templWithRemoteContent.dispatchEvent(new Event('load'));
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
throw 'NI'; //not implemented
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {TemplateWithRemoteContent} from './ts-refs/mount-observer/types.js';
|
|
2
|
+
const remoteTemplElSym = Symbol.for('du3y+tfsAUGFHMG/iHZiMQ');
|
|
3
|
+
|
|
4
|
+
export async function preloadContent(templ: HTMLTemplateElement, target?: DocumentFragment | ShadowRoot | Document | Element) {
|
|
5
|
+
const templWithRemoteContent = templ as TemplateWithRemoteContent & {
|
|
6
|
+
[remoteTemplElSym]?: WeakRef<HTMLTemplateElement>
|
|
7
|
+
};
|
|
8
|
+
if(templWithRemoteContent.remoteContent) return templWithRemoteContent.remoteContent;
|
|
9
|
+
const src = templ.getAttribute('src');
|
|
10
|
+
if(!src) throw 300; //no src attribute
|
|
11
|
+
const isIntraDoc = src[0] === '#';
|
|
12
|
+
if(!('remoteContent' in templWithRemoteContent)) {
|
|
13
|
+
//define a property on the template instance
|
|
14
|
+
Object.defineProperty(templWithRemoteContent, 'remoteContent', {
|
|
15
|
+
get(){
|
|
16
|
+
if(isIntraDoc){
|
|
17
|
+
const ref = this[remoteTemplElSym]?.deref();
|
|
18
|
+
if(ref) return ref.content;
|
|
19
|
+
}else{
|
|
20
|
+
throw 'NI'; //not implemented
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
enumerable: true,
|
|
24
|
+
configurable: true,
|
|
25
|
+
|
|
26
|
+
});
|
|
27
|
+
}else{
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if(isIntraDoc){
|
|
31
|
+
const id = src.substring(1);
|
|
32
|
+
const {upShadowSearch} = await import('./upShadowSearch.js');
|
|
33
|
+
const remoteTempl = upShadowSearch(templ, id) || upShadowSearch((target || document) as Element, id);
|
|
34
|
+
if(!(remoteTempl instanceof HTMLTemplateElement)) throw 404; //not found
|
|
35
|
+
templWithRemoteContent[remoteTemplElSym] = new WeakRef(remoteTempl);
|
|
36
|
+
templWithRemoteContent.dispatchEvent(new Event('load'));
|
|
37
|
+
}else{
|
|
38
|
+
throw 'NI'; //not implemented
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {IEnhancement, BEAllProps} from '../trans-render/be/types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
export interface DirectoryPickerOptions {
|
|
7
|
+
/**
|
|
8
|
+
* By specifying an ID, the browser can remember different directories for different IDs. If the same ID is used for another picker, the picker opens in the same directory.
|
|
9
|
+
*/
|
|
10
|
+
id?: string;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A string that defaults to "read" for read-only access or "readwrite" for read and write access to the directory.
|
|
14
|
+
*/
|
|
15
|
+
mode?: 'read' | 'readwrite';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A FileSystemHandle or a well known directory ("desktop", "documents", "downloads", "music", "pictures", or "videos") to open the dialog in.
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
startIn?: 'documents' | 'downloads' | 'music' | 'pictures' | 'videos' | 'home' | 'desktop' | File;
|
|
22
|
+
}
|
|
23
|
+
export interface EndUserProps extends IEnhancement{
|
|
24
|
+
options: DirectoryPickerOptions;
|
|
25
|
+
noNudge: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface AllProps extends EndUserProps{
|
|
29
|
+
directoryHandle?: FileSystemDirectoryHandle;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type AP = AllProps;
|
|
33
|
+
|
|
34
|
+
export type PAP = Partial<AP>;
|
|
35
|
+
|
|
36
|
+
export type ProPAP = Promise<PAP>;
|
|
37
|
+
|
|
38
|
+
export type BAP = AP & BEAllProps;
|
|
39
|
+
|
|
40
|
+
export interface Actions{
|
|
41
|
+
hydrate(self: BAP): ProPAP;
|
|
42
|
+
}
|
|
43
|
+
|
|
@@ -240,5 +240,9 @@ export type IshCtr = ({new() : Ishcycle}) | (() => Promise<{new() : Ishcycle}>);
|
|
|
240
240
|
|
|
241
241
|
export type RefType = '#' | '!';
|
|
242
242
|
|
|
243
|
+
export interface TemplateWithRemoteContent extends HTMLTemplateElement {
|
|
244
|
+
remoteContent?: DocumentFragment,
|
|
245
|
+
}
|
|
246
|
+
|
|
243
247
|
|
|
244
248
|
|
|
@@ -219,7 +219,7 @@ export interface UnitOfWork<TProps, TMethods = TProps, TElement = {}>{
|
|
|
219
219
|
/**
|
|
220
220
|
* abbrev. for addEventListener
|
|
221
221
|
*/
|
|
222
|
-
a?: AddEventListenerType<TProps, TMethods> | Array<AddEventListenerType<TProps, TMethods>>,
|
|
222
|
+
a?: 0 | AddEventListenerType<TProps, TMethods> | Array<AddEventListenerType<TProps, TMethods>>,
|
|
223
223
|
|
|
224
224
|
/**
|
|
225
225
|
* Specify how the value we want to apply to the target element should be derived from the observed props.
|
|
@@ -434,7 +434,7 @@ export interface AddEventListener<TProps, TMethods>{
|
|
|
434
434
|
}
|
|
435
435
|
|
|
436
436
|
export type XForm<TProps, TMethods, TElement = {}> = Partial<{
|
|
437
|
-
[key in LHS<TProps, TElement>]: RHS<TProps, TMethods, TElement>;
|
|
437
|
+
[key in LHS<TProps & TMethods, TElement>]: RHS<TProps, TMethods, TElement>;
|
|
438
438
|
}>;
|
|
439
439
|
|
|
440
440
|
export interface Info {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function upShadowSearch(ref, id) {
|
|
2
|
+
let rn = ref.getRootNode();
|
|
3
|
+
while (rn) {
|
|
4
|
+
let test = rn.getElementById(id);
|
|
5
|
+
if (test)
|
|
6
|
+
return test;
|
|
7
|
+
if (rn.host) {
|
|
8
|
+
test = rn.host[id];
|
|
9
|
+
if (test instanceof HTMLElement)
|
|
10
|
+
return test;
|
|
11
|
+
rn = rn.host.getRootNode();
|
|
12
|
+
}
|
|
13
|
+
else if (rn === document) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
throw 'NI';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function upShadowSearch(ref: Element, id: string){
|
|
2
|
+
let rn = ref.getRootNode() as (Document | DocumentFragment | ShadowRoot) & { host?: Element };
|
|
3
|
+
while(rn){
|
|
4
|
+
let test = rn.getElementById(id);
|
|
5
|
+
if(test) return test;
|
|
6
|
+
if(rn.host){
|
|
7
|
+
test = rn.host[id];
|
|
8
|
+
if(test instanceof HTMLElement) return test;
|
|
9
|
+
rn = rn.host.getRootNode() as (DocumentFragment | ShadowRoot) & { host?: Element };
|
|
10
|
+
}else if(rn === document){
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
throw 'NI';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|