mount-observer 0.0.71 → 0.0.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/MountObserver.js CHANGED
@@ -67,22 +67,38 @@ export class MountObserver extends EventTarget {
67
67
  }
68
68
  }
69
69
  async #compose(el, level) {
70
- if (el.hasAttribute('src')) {
71
- const { compose } = await import('./compose.js');
72
- await compose(this, el, level);
73
- }
70
+ const src = el.getAttribute('src');
71
+ if (src === null || src.length < 2)
72
+ return;
73
+ const refType = src[0];
74
+ if (!['!', '#'].includes(refType))
75
+ return;
76
+ const { compose } = await import('./compose.js');
77
+ await compose(this, el, level, src.substring(1), refType);
74
78
  }
75
79
  #templLookUp = new Map();
76
- async findByID(id, fragment) {
77
- if (this.#templLookUp.has(id))
78
- return this.#templLookUp.get(id);
79
- let templ = fragment.querySelector(`#${id}`);
80
+ #searchForComment(refName, fragment) {
81
+ const iterator = document.evaluate(`//comment()[.="${refName}"]`, fragment, null, XPathResult.ANY_TYPE, null);
82
+ //console.log({xpathResult})
83
+ try {
84
+ let thisNode = iterator.iterateNext();
85
+ return thisNode;
86
+ }
87
+ catch (e) {
88
+ return null;
89
+ }
90
+ }
91
+ async findByID(refName, fragment, refType) {
92
+ if (this.#templLookUp.has(refName))
93
+ return this.#templLookUp.get(refName);
94
+ let templ = null;
95
+ templ = refType === '#' ? fragment.querySelector(`#${refName}`) : this.#searchForComment(refName, fragment);
80
96
  if (templ === null) {
81
97
  let rootToSearchOutwardFrom = ((fragment.isConnected ? fragment.getRootNode() : this.#mountInit.withTargetShadowRoot) || document);
82
- templ = rootToSearchOutwardFrom.getElementById(id);
98
+ templ = refType === '#' ? rootToSearchOutwardFrom.getElementById(refName) : this.#searchForComment(refName, rootToSearchOutwardFrom);
83
99
  while (templ === null && rootToSearchOutwardFrom !== document) {
84
100
  rootToSearchOutwardFrom = (rootToSearchOutwardFrom.host || rootToSearchOutwardFrom).getRootNode();
85
- templ = rootToSearchOutwardFrom.getElementById(id);
101
+ templ = refType === '#' ? rootToSearchOutwardFrom.getElementById(refName) : this.#searchForComment(refName, rootToSearchOutwardFrom);
86
102
  }
87
103
  }
88
104
  if (templ !== null) {
@@ -98,20 +114,27 @@ export class MountObserver extends EventTarget {
98
114
  let first = true;
99
115
  for (const adjRef of adjRefs) {
100
116
  const clone = adjRef.cloneNode(true);
101
- if (first && adjRefs.length > 1) {
102
- clone.setAttribute('itemref', '<autogen>');
103
- newTempl[wasItemReffed] = true;
104
- first = false;
117
+ if (refType === '#' && clone instanceof Element) {
118
+ if (first && adjRefs.length > 1) {
119
+ clone.setAttribute('itemref', '<autogen>');
120
+ newTempl[wasItemReffed] = true;
121
+ first = false;
122
+ }
123
+ clone.removeAttribute('id');
105
124
  }
106
- clone.removeAttribute('id');
107
125
  fragment.appendChild(clone);
108
126
  }
109
- const { doCleanup } = await import('./doCleanup.js');
110
- doCleanup(templ, fragment);
127
+ if (templ instanceof Element) {
128
+ const { doCleanup } = await import('./doCleanup.js');
129
+ doCleanup(templ, fragment);
130
+ }
131
+ else {
132
+ //TODO: cleanup
133
+ }
111
134
  newTempl.content.appendChild(fragment);
112
135
  templ = newTempl;
113
136
  }
114
- this.#templLookUp.set(id, templ);
137
+ this.#templLookUp.set(refName, templ);
115
138
  }
116
139
  return templ;
117
140
  }
@@ -500,7 +523,7 @@ function areAllIdle(mutObs) {
500
523
  return true;
501
524
  }
502
525
  const refCountErr = 'mount-observer ref count mismatch';
503
- export const inclTemplQry = 'template[src^="#"]:not([hidden])';
526
+ export const inclTemplQry = 'template[src^="#"]:not([hidden]),template[src^="!"]:not([hidden])';
504
527
  // https://github.com/webcomponents-cg/community-protocols/issues/12#issuecomment-872415080
505
528
  /**
506
529
  * The `mutation-event` event represents something that happened.
package/MountObserver.ts CHANGED
@@ -4,7 +4,8 @@ import {MountInit, IMountObserver, AddMutationEventListener,
4
4
  AttrParts,
5
5
  MOSE, WeakDual,
6
6
  MountObserverOptions,
7
- Assigner
7
+ Assigner,
8
+ RefType
8
9
  } from './ts-refs/mount-observer/types';
9
10
  import {RootMutObs} from './RootMutObs.js';
10
11
  import {bindish, bindishIt} from './bindish.js';
@@ -81,24 +82,48 @@ export class MountObserver extends EventTarget implements IMountObserver{
81
82
 
82
83
 
83
84
  async #compose(el: HTMLTemplateElement, level: number){
84
- if(el.hasAttribute('src')){
85
- const {compose} = await import('./compose.js');
86
- await compose(this, el, level);
87
- }
85
+ const src = el.getAttribute('src');
86
+ if(src === null || src.length < 2) return;
87
+ const refType = src[0] as RefType;
88
+ if(!['!', '#'].includes(refType)) return;
89
+ const {compose} = await import('./compose.js');
90
+ await compose(this, el, level, src.substring(1), refType);
91
+
88
92
 
89
93
  }
90
94
  #templLookUp: Map<string, HTMLElement> = new Map();
91
- async findByID(id: string, fragment: DocumentFragment): Promise<HTMLElement | null>{
92
- if(this.#templLookUp.has(id)) return this.#templLookUp.get(id)!;
93
- let templ = fragment.querySelector(`#${id}`);
95
+ #searchForComment(refName: string, fragment: Node){
96
+ const iterator = document.evaluate(
97
+ `//comment()[.="${refName}"]`,
98
+ fragment,
99
+ null,
100
+ XPathResult.ANY_TYPE,
101
+ null
102
+ );
103
+ //console.log({xpathResult})
104
+ try {
105
+ let thisNode = iterator.iterateNext();
106
+ return thisNode;
107
+ }catch(e){
108
+ return null;
109
+ }
110
+ }
111
+ async findByID(
112
+ refName: string, fragment: DocumentFragment,
113
+ refType: RefType): Promise<HTMLElement | DocumentFragment | null>{
114
+ if(this.#templLookUp.has(refName)) return this.#templLookUp.get(refName)!;
115
+ let templ: Node | null = null;
116
+ templ = refType === '#' ? fragment.querySelector(`#${refName}`) : this.#searchForComment(refName, fragment);
94
117
  if(templ === null){
95
118
  let rootToSearchOutwardFrom = ((fragment.isConnected ? fragment.getRootNode() : this.#mountInit.withTargetShadowRoot) || document) as any;
96
- templ = rootToSearchOutwardFrom.getElementById(id);
119
+ templ = refType === '#' ? rootToSearchOutwardFrom.getElementById(refName) : this.#searchForComment(refName, rootToSearchOutwardFrom);
97
120
  while(templ === null && rootToSearchOutwardFrom !== (document as any as DocumentFragment) ){
98
121
  rootToSearchOutwardFrom = (rootToSearchOutwardFrom.host || rootToSearchOutwardFrom).getRootNode() as DocumentFragment;
99
- templ = rootToSearchOutwardFrom.getElementById(id);
122
+ templ = refType === '#' ? rootToSearchOutwardFrom.getElementById(refName) : this.#searchForComment(refName, rootToSearchOutwardFrom);
100
123
  }
101
124
  }
125
+
126
+
102
127
  if(templ !== null) {
103
128
  if(!(templ instanceof HTMLTemplateElement)){
104
129
  const newTempl = document.createElement('template');
@@ -112,20 +137,29 @@ export class MountObserver extends EventTarget implements IMountObserver{
112
137
  let first = true;
113
138
  for(const adjRef of adjRefs){
114
139
  const clone = adjRef.cloneNode(true) as HTMLElement;
115
- if(first && adjRefs.length > 1){
116
- clone.setAttribute('itemref', '<autogen>');
117
- (<any>newTempl)[wasItemReffed] = true;
118
- first = false;
140
+ if(refType === '#' && clone instanceof Element){
141
+ if(first && adjRefs.length > 1){
142
+ clone.setAttribute('itemref', '<autogen>');
143
+ (<any>newTempl)[wasItemReffed] = true;
144
+ first = false;
145
+ }
146
+ clone.removeAttribute('id');
119
147
  }
120
- clone.removeAttribute('id');
148
+
121
149
  fragment.appendChild(clone);
122
150
  }
123
- const {doCleanup} = await import('./doCleanup.js');
124
- doCleanup(templ as HTMLElement, fragment);
151
+ if(templ instanceof Element){
152
+ const {doCleanup} = await import('./doCleanup.js');
153
+ doCleanup(templ as HTMLElement, fragment);
154
+
155
+ }else{
156
+ //TODO: cleanup
157
+ }
125
158
  newTempl.content.appendChild(fragment);
126
159
  templ = newTempl;
160
+
127
161
  }
128
- this.#templLookUp.set(id, templ as HTMLTemplateElement);
162
+ this.#templLookUp.set(refName, templ as HTMLTemplateElement);
129
163
  }
130
164
  return templ as HTMLTemplateElement;
131
165
  }
@@ -523,7 +557,7 @@ function areAllIdle(mutObs: Array<RootMutObs>){
523
557
 
524
558
 
525
559
  const refCountErr = 'mount-observer ref count mismatch';
526
- export const inclTemplQry = 'template[src^="#"]:not([hidden])';
560
+ export const inclTemplQry = 'template[src^="#"]:not([hidden]),template[src^="!"]:not([hidden])';
527
561
 
528
562
  export interface MountObserver extends IMountObserver{}
529
563
 
package/README.md CHANGED
@@ -769,8 +769,8 @@ For example:
769
769
  <div>Your Mother Should Know</div>
770
770
  <div>I Am the Walrus</div>
771
771
  <template src=#id-of-source-template>
772
- <span slot=slot1>hello</span>
773
- <span slot=slot2>goodbye<span>
772
+ <span part=greeting>hello</span>
773
+ <span part=parting>goodbye<span>
774
774
  </template>
775
775
  <div>Strawberry Fields Forever</div>
776
776
  ```
@@ -781,24 +781,45 @@ Let's say the source template looks as follows:
781
781
 
782
782
  ```html
783
783
  <template id=id-of-source-template>
784
- <div>I don't know why you say <slot name=slot2></slot> I say <slot name=slot1></slot></div>
784
+ <div>
785
+ You say, <span part=parting></span> and I say,
786
+ <span part=greeting></span>, <span part=greeting></span>, <span part=greeting></span>
787
+ </div>
788
+ <div>
789
+ I don't know why you say
790
+ <span part=greeting></span>
791
+ I say
792
+ <span part=parting></span>
793
+ </div>
785
794
  </template>
786
795
  ```
787
796
 
788
- What we would end up with is:
797
+ What we end up with is:
789
798
 
790
799
 
791
800
  ```html
792
801
  <div>Your Mother Should Know</div>
793
802
  <div>I Am the Walrus</div>
794
- <div>I don't know why you say <span>goodbye</span> I say <span>hello</span></div>
803
+ <?+?>
804
+ <div>
805
+ You say, <span part=parting>goodbye</span> and I say,
806
+ <span part=greeting>hello</span>, <span part=greeting>hello</span>, <span part=greeting>hello</span>
807
+ </div>
808
+ <div>
809
+ I don't know why you say
810
+ <span part=greeting>goodbye</span>
811
+ I say
812
+ <span part=parting>hello</span>
813
+ </div>
814
+ <?-?>
795
815
  <div>Strawberry Fields Forever</div>
796
816
  ```
797
817
 
798
- Some significant differences with genuine slot support as used with (ShadowDOM'd) custom elements
818
+ Some significant differences with slot support as used with (ShadowDOM'd) custom elements
799
819
 
800
- 1. There is no mechanism for updating the slots. That is something under investigation with this userland [custom enhancement](https://github.com/bahrus/be-inclusive), that could possibly lead to a future implementation request tied to template instantiation. It takes the approach of morphing from slots to a JS host object model that binds to where all the slots were "from a distance".
801
- 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 with a slot identifier can get cloned into multiple slot targets as it weaves itself into the templates as they get merged together.
820
+ 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.
821
+ 2. There is no mechanism for updating the 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.
822
+ 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.
802
823
 
803
824
  ## Intra document html imports with Shadow DOM support
804
825
 
package/compose.js CHANGED
@@ -1,19 +1,26 @@
1
- import { inclTemplQry, wasItemReffed } from './MountObserver.js';
1
+ import { wasItemReffed } from './MountObserver.js';
2
+ //import {prep} from './slotkin/affine.js';
3
+ //goal: deprecate this key, in favor of comments
2
4
  export const childRefsKey = Symbol.for('Wr0WPVh84k+O93miuENdMA');
3
5
  export const cloneKey = Symbol.for('LD97VKZYc02CQv23DT/6fQ');
4
6
  const autogenKey = Symbol.for('YpP5EP0i1UKcBBBH9tsm0w');
5
- export async function compose(self, el, level) {
7
+ //const wrapped = Symbol.for('50tzQZt95ECXUtHF7a40og');
8
+ export async function compose(self, el, level, refName, refType) {
6
9
  const src = el.getAttribute('src');
7
10
  if (src === null)
8
11
  return;
9
12
  el.removeAttribute('src');
10
- const templID = src.substring(1);
13
+ //const templID = src!.substring(1);
14
+ //const refType = src![0];
11
15
  const fragment = self.objNde?.deref();
12
16
  if (fragment === undefined)
13
17
  return;
14
- const templ = await self.findByID(templID, fragment);
18
+ const templ = await self.findByID(refName, fragment, refType);
15
19
  if (!(templ instanceof HTMLTemplateElement))
16
20
  throw 404;
21
+ if (refType === '#') {
22
+ (await import('./slotkin/wrap.js')).wrap(templ, refName);
23
+ }
17
24
  const clone = templ.content.cloneNode(true);
18
25
  const dataLd = el.dataset.ld;
19
26
  const wasReffed = templ[wasItemReffed];
@@ -48,63 +55,55 @@ export async function compose(self, el, level) {
48
55
  delete el.dataset.ld;
49
56
  }
50
57
  }
51
- const slots = el.content.querySelectorAll(`[slot]`);
52
- for (const slot of slots) {
53
- const name = slot.getAttribute('slot');
54
- const slotQry = `slot[name="${name}"]`;
55
- const targets = Array.from(clone.querySelectorAll(slotQry));
56
- const innerTempls = clone.querySelectorAll(inclTemplQry);
57
- for (const innerTempl of innerTempls) {
58
- const innerSlots = innerTempl.content.querySelectorAll(slotQry);
59
- for (const innerSlot of innerSlots) {
60
- targets.push(innerSlot);
61
- }
62
- }
63
- for (const target of targets) {
64
- const slotClone = slot.cloneNode(true);
65
- target.after(slotClone);
66
- target.remove();
58
+ if (el.content.childElementCount > 0) {
59
+ const { affine } = await import('./slotkin/affine.js');
60
+ const children = Array.from(el.content.children);
61
+ for (const child of children) {
62
+ //TODO support clean up
63
+ const mo = affine(clone, child);
67
64
  }
68
65
  }
69
66
  await self.composeFragment(clone, level + 1);
70
- const shadowRootModeOnLoad = el.getAttribute('shadowRootModeOnLoad');
71
- if (shadowRootModeOnLoad === null && level === 0) {
72
- const slotMap = el.getAttribute('slotmap');
73
- let map = slotMap === null ? undefined : JSON.parse(slotMap);
74
- const slots = clone.querySelectorAll('[slot]');
75
- for (const slot of slots) {
76
- if (map !== undefined) {
77
- const slotName = slot.slot;
78
- for (const key in map) {
79
- if (slot.matches(key)) {
80
- const targetAttSymbols = map[key];
81
- for (const sym of targetAttSymbols) {
82
- switch (sym) {
83
- case '|':
84
- slot.setAttribute('itemprop', slotName);
85
- break;
86
- case '$':
87
- slot.setAttribute('itemscope', '');
88
- slot.setAttribute('itemprop', slotName);
89
- break;
90
- case '@':
91
- slot.setAttribute('name', slotName);
92
- break;
93
- case '.':
94
- slot.classList.add(slotName);
95
- break;
96
- case '%':
97
- slot.part.add(slotName);
98
- break;
99
- }
100
- }
101
- }
102
- }
103
- }
104
- slot.removeAttribute('slot');
105
- }
106
- el.dispatchEvent(new LoadEvent(clone));
107
- }
67
+ // if (false) {
68
+ // const shadowRootModeOnLoad = el.getAttribute('shadowRootModeOnLoad') as null | ShadowRootMode;
69
+ // if (shadowRootModeOnLoad === null && level === 0) {
70
+ // const slotMap = el.getAttribute('slotmap');
71
+ // let map = slotMap === null ? undefined : JSON.parse(slotMap);
72
+ // const slots = clone.querySelectorAll('[slot]');
73
+ // for (const slot of slots) {
74
+ // if (map !== undefined) {
75
+ // const slotName = slot.slot;
76
+ // for (const key in map) {
77
+ // if (slot.matches(key)) {
78
+ // const targetAttSymbols = map[key] as string;
79
+ // for (const sym of targetAttSymbols) {
80
+ // switch (sym) {
81
+ // case '|':
82
+ // slot.setAttribute('itemprop', slotName);
83
+ // break;
84
+ // case '$':
85
+ // slot.setAttribute('itemscope', '');
86
+ // slot.setAttribute('itemprop', slotName);
87
+ // break;
88
+ // case '@':
89
+ // slot.setAttribute('name', slotName);
90
+ // break;
91
+ // case '.':
92
+ // slot.classList.add(slotName);
93
+ // break;
94
+ // case '%':
95
+ // slot.part.add(slotName);
96
+ // break;
97
+ // }
98
+ // }
99
+ // }
100
+ // }
101
+ // }
102
+ // slot.removeAttribute('slot');
103
+ // }
104
+ // el.dispatchEvent(new LoadEvent(clone));
105
+ // }
106
+ // }
108
107
  if (level === 0) {
109
108
  const refs = [];
110
109
  for (const child of clone.children) {
@@ -120,17 +119,14 @@ export async function compose(self, el, level) {
120
119
  cloneStashed = true;
121
120
  }
122
121
  else {
123
- if (shadowRootModeOnLoad !== null) {
124
- const parent = el.parentElement;
125
- if (parent === null)
126
- throw 404;
127
- if (parent.shadowRoot === null)
128
- parent.attachShadow({ mode: shadowRootModeOnLoad });
129
- parent.shadowRoot?.append(clone);
130
- }
131
- else {
132
- el.after(clone);
133
- }
122
+ // if (false /*shadowRootModeOnLoad !== null */) {
123
+ // const parent = el.parentElement;
124
+ // if (parent === null) throw 404;
125
+ // if (parent.shadowRoot === null) parent.attachShadow({ mode: shadowRootModeOnLoad });
126
+ // parent.shadowRoot?.append(clone);
127
+ // } else {
128
+ el.after(clone);
129
+ //}
134
130
  }
135
131
  //moving the code down here broke be-inclusive Example2.html (but maybe it caused something else to work, so will need to revisit)
136
132
  //check to make sure the progresive loading of css-charts works as before.
@@ -138,7 +134,7 @@ export async function compose(self, el, level) {
138
134
  // el.dispatchEvent(new LoadEvent(clone));
139
135
  // }
140
136
  if (!cloneStashed) {
141
- if (level !== 0 || (slots.length === 0 && el.attributes.length === 0))
137
+ if (level !== 0 || el.attributes.length === 0)
142
138
  el.remove();
143
139
  }
144
140
  }
package/compose.ts CHANGED
@@ -1,158 +1,164 @@
1
- import { ILoadEvent, loadEventName } from './ts-refs/mount-observer/types';
1
+ import { ILoadEvent, loadEventName, RefType } from './ts-refs/mount-observer/types';
2
2
  import { MountObserver, inclTemplQry, wasItemReffed } from './MountObserver.js';
3
+ //import {prep} from './slotkin/affine.js';
3
4
 
5
+ //goal: deprecate this key, in favor of comments
4
6
  export const childRefsKey = Symbol.for('Wr0WPVh84k+O93miuENdMA');
5
7
  export const cloneKey = Symbol.for('LD97VKZYc02CQv23DT/6fQ');
6
8
  const autogenKey = Symbol.for('YpP5EP0i1UKcBBBH9tsm0w');
9
+ //const wrapped = Symbol.for('50tzQZt95ECXUtHF7a40og');
7
10
  export async function compose(
8
- self: MountObserver,
9
- el: HTMLTemplateElement,
10
- level: number
11
- ){
12
- const src = el.getAttribute('src'); if(src === null) return;
11
+ self: MountObserver,
12
+ el: HTMLTemplateElement,
13
+ level: number,
14
+ refName: string,
15
+ refType: RefType,
16
+ ) {
17
+ const src = el.getAttribute('src'); if (src === null) return;
13
18
  el.removeAttribute('src');
14
- const templID = src!.substring(1);
19
+ //const templID = src!.substring(1);
20
+ //const refType = src![0];
15
21
  const fragment = self.objNde?.deref() as DocumentFragment;
16
- if(fragment === undefined) return;
17
- const templ = await self.findByID(templID, fragment);
18
- if(!(templ instanceof HTMLTemplateElement)) throw 404;
22
+ if (fragment === undefined) return;
23
+ const templ = await self.findByID(refName, fragment, refType);
24
+ if (!(templ instanceof HTMLTemplateElement)) throw 404;
25
+ if (refType === '#') {
26
+ (await import('./slotkin/wrap.js')).wrap(templ, refName);
27
+ }
28
+
19
29
  const clone = templ.content.cloneNode(true) as DocumentFragment;
20
30
  const dataLd = el.dataset.ld;
21
31
  const wasReffed = (<any>templ)[wasItemReffed];
22
- if(wasReffed || dataLd){
32
+ if (wasReffed || dataLd) {
23
33
  const firstElement = clone.firstElementChild!;
24
- if(wasReffed){
34
+ if (wasReffed) {
25
35
  let ns = firstElement.nextElementSibling;
26
36
  const ids = [];
27
37
  let count = (<any>window)[autogenKey];
28
- if(count === undefined){
38
+ if (count === undefined) {
29
39
  count = 0;
30
- }else{
40
+ } else {
31
41
  count++;
32
42
  }
33
43
  (<any>window)[autogenKey] = count;
34
- while(ns !== null){
44
+ while (ns !== null) {
35
45
  const id = ns.id = `mount-observer-${count}`;
36
46
  ids.push(id);
37
47
  ns = ns.nextElementSibling;
38
48
  }
39
49
  firstElement.setAttribute('itemref', ids.join(' '));
40
50
  }
41
-
42
- if(dataLd){
51
+
52
+ if (dataLd) {
43
53
  const parsed = JSON.parse(dataLd);
44
54
  let type = parsed['@type'];
45
55
  const itemscopeAttr = firstElement.getAttribute('itemscope');
46
- if(type && !itemscopeAttr){
56
+ if (type && !itemscopeAttr) {
47
57
  firstElement.setAttribute('itemscope', type);
48
58
  }
49
59
  (<any>firstElement)['ish'] = parsed;
50
60
  delete el.dataset.ld;
51
61
  }
52
-
62
+
53
63
  }
54
- const slots = el.content.querySelectorAll(`[slot]`);
64
+ if (el.content.childElementCount > 0) {
65
+ const { affine } = await import('./slotkin/affine.js');
66
+ const children = Array.from(el.content.children);
67
+ for (const child of children) {
68
+ //TODO support clean up
69
+ const mo = affine(clone, child);
55
70
 
56
- for(const slot of slots){
57
- const name = slot.getAttribute('slot')!;
58
- const slotQry = `slot[name="${name}"]`;
59
- const targets = Array.from(clone.querySelectorAll(slotQry));
60
- const innerTempls = clone.querySelectorAll(inclTemplQry) as NodeListOf<HTMLTemplateElement>;
61
- for(const innerTempl of innerTempls){
62
- const innerSlots = innerTempl.content.querySelectorAll(slotQry);
63
- for(const innerSlot of innerSlots){
64
- targets.push(innerSlot);
65
- }
66
- }
67
- for(const target of targets){
68
- const slotClone = slot.cloneNode(true) as Element;
69
- target.after(slotClone);
70
- target.remove();
71
71
  }
72
+
72
73
  }
74
+
75
+
73
76
  await self.composeFragment(clone, level + 1);
74
- const shadowRootModeOnLoad = el.getAttribute('shadowRootModeOnLoad') as null | ShadowRootMode;
75
- if(shadowRootModeOnLoad === null && level === 0){
76
-
77
- const slotMap = el.getAttribute('slotmap');
78
- let map = slotMap === null ? undefined : JSON.parse(slotMap);
79
- const slots = clone.querySelectorAll('[slot]');
80
- for(const slot of slots){
81
- if(map !== undefined){
82
- const slotName = slot.slot;
83
- for(const key in map){
84
- if(slot.matches(key)){
85
- const targetAttSymbols = map[key] as string;
86
- for(const sym of targetAttSymbols){
87
- switch(sym){
88
- case '|':
89
- slot.setAttribute('itemprop', slotName);
90
- break;
91
- case '$':
92
- slot.setAttribute('itemscope', '');
93
- slot.setAttribute('itemprop', slotName);
94
- break;
95
- case '@':
96
- slot.setAttribute('name', slotName);
97
- break;
98
- case '.':
99
- slot.classList.add(slotName);
100
- break;
101
- case '%':
102
- slot.part.add(slotName);
103
- break;
104
- }
105
- }
106
- }
107
- }
108
- }
109
- slot.removeAttribute('slot');
110
- }
111
- el.dispatchEvent(new LoadEvent(clone));
112
- }
113
- if(level === 0){
77
+ // if (false) {
78
+ // const shadowRootModeOnLoad = el.getAttribute('shadowRootModeOnLoad') as null | ShadowRootMode;
79
+ // if (shadowRootModeOnLoad === null && level === 0) {
80
+
81
+ // const slotMap = el.getAttribute('slotmap');
82
+ // let map = slotMap === null ? undefined : JSON.parse(slotMap);
83
+ // const slots = clone.querySelectorAll('[slot]');
84
+ // for (const slot of slots) {
85
+ // if (map !== undefined) {
86
+ // const slotName = slot.slot;
87
+ // for (const key in map) {
88
+ // if (slot.matches(key)) {
89
+ // const targetAttSymbols = map[key] as string;
90
+ // for (const sym of targetAttSymbols) {
91
+ // switch (sym) {
92
+ // case '|':
93
+ // slot.setAttribute('itemprop', slotName);
94
+ // break;
95
+ // case '$':
96
+ // slot.setAttribute('itemscope', '');
97
+ // slot.setAttribute('itemprop', slotName);
98
+ // break;
99
+ // case '@':
100
+ // slot.setAttribute('name', slotName);
101
+ // break;
102
+ // case '.':
103
+ // slot.classList.add(slotName);
104
+ // break;
105
+ // case '%':
106
+ // slot.part.add(slotName);
107
+ // break;
108
+ // }
109
+ // }
110
+ // }
111
+ // }
112
+ // }
113
+ // slot.removeAttribute('slot');
114
+ // }
115
+ // el.dispatchEvent(new LoadEvent(clone));
116
+ // }
117
+ // }
118
+
119
+ if (level === 0) {
114
120
  const refs: Array<WeakRef<Element>> = [];
115
- for(const child of clone.children){
121
+ for (const child of clone.children) {
116
122
  refs.push(new WeakRef(child));
117
123
  }
118
124
  (<any>el)[childRefsKey] = refs;
119
-
125
+
120
126
  }
121
127
  //if template has itemscope attribute, assume want to do some data binding before instantiating into
122
128
  //DOM fragment.
123
129
  let cloneStashed = false;
124
- if(el.hasAttribute('itemscope')){
130
+ if (el.hasAttribute('itemscope')) {
125
131
  (<any>el)[cloneKey] = clone;
126
132
  cloneStashed = true;
127
- }else{
128
- if(shadowRootModeOnLoad !== null){
129
- const parent = el.parentElement;
130
- if(parent === null) throw 404;
131
- if(parent.shadowRoot === null) parent.attachShadow({mode: shadowRootModeOnLoad});
132
- parent.shadowRoot?.append(clone);
133
- }else{
133
+ } else {
134
+ // if (false /*shadowRootModeOnLoad !== null */) {
135
+ // const parent = el.parentElement;
136
+ // if (parent === null) throw 404;
137
+ // if (parent.shadowRoot === null) parent.attachShadow({ mode: shadowRootModeOnLoad });
138
+ // parent.shadowRoot?.append(clone);
139
+ // } else {
134
140
  el.after(clone);
135
- }
141
+ //}
136
142
  }
137
143
  //moving the code down here broke be-inclusive Example2.html (but maybe it caused something else to work, so will need to revisit)
138
144
  //check to make sure the progresive loading of css-charts works as before.
139
145
  // if(level === 0){
140
146
  // el.dispatchEvent(new LoadEvent(clone));
141
147
  // }
142
-
143
- if(!cloneStashed){
144
- if(level !== 0 || (slots.length === 0 && el.attributes.length === 0)) el.remove();
148
+
149
+ if (!cloneStashed) {
150
+ if (level !== 0 || el.attributes.length === 0) el.remove();
145
151
  }
146
152
 
147
153
  }
148
154
 
149
- export class LoadEvent extends Event implements ILoadEvent{
155
+ export class LoadEvent extends Event implements ILoadEvent {
150
156
  static eventName: loadEventName = 'load';
151
- constructor(public clone: DocumentFragment){
157
+ constructor(public clone: DocumentFragment) {
152
158
  super(LoadEvent.eventName);
153
159
  }
154
160
  }
155
161
 
156
- interface HTMLElementEventMap{
162
+ interface HTMLElementEventMap {
157
163
  'load': LoadEvent,
158
164
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mount-observer",
3
- "version": "0.0.71",
3
+ "version": "0.0.72",
4
4
  "description": "Observe and act on css matches.",
5
5
  "main": "MountObserver.js",
6
6
  "module": "MountObserver.js",
@@ -64,6 +64,10 @@
64
64
  "./slotkin/beKindred.js": {
65
65
  "default": "./slotkin/beKindred.js",
66
66
  "types": "./slotkin/beKindred.ts"
67
+ },
68
+ "./slotkin/wrap.js": {
69
+ "default": "./slotkin/wrap.js",
70
+ "types": "./slotkin/wrap.ts"
67
71
  }
68
72
  },
69
73
  "files": [
@@ -1,16 +1,38 @@
1
1
  import { splitRefs } from './splitRefs.js';
2
- export function getAdjRefs(el) {
3
- const returnArr = [el];
4
- const itemref = el.getAttribute('itemref');
5
- if (itemref === null)
6
- return returnArr;
7
- const itemrefList = splitRefs(itemref); // itemref.split(' ').map((id) => id.trim()).filter((id) => id.length > 0);
8
- if (itemrefList.length === 0)
9
- return returnArr;
10
- let ns = el.nextElementSibling;
11
- while (ns !== null && itemrefList.includes(ns.id)) {
12
- returnArr.push(ns);
13
- ns = ns.nextElementSibling;
2
+ export function getAdjRefs(node) {
3
+ const returnArr = [node];
4
+ if (node.nodeType === node.COMMENT_NODE) {
5
+ const openText = node.data.split(' ')[0];
6
+ const closedText = `/${openText}`;
7
+ let ns = node.nextSibling;
8
+ while (ns) {
9
+ returnArr.push(ns);
10
+ if (node.nodeType === node.COMMENT_NODE && node.data === closedText) {
11
+ return returnArr;
12
+ }
13
+ ns = ns.nextSibling;
14
+ }
15
+ }
16
+ else {
17
+ const el = node;
18
+ const itemref = el.getAttribute('itemref');
19
+ if (itemref === null)
20
+ return returnArr;
21
+ const itemrefList = splitRefs(itemref); // itemref.split(' ').map((id) => id.trim()).filter((id) => id.length > 0);
22
+ if (itemrefList.length === 0)
23
+ return returnArr;
24
+ let ns = el.nextSibling;
25
+ while (ns !== null) {
26
+ if (ns instanceof Element) {
27
+ if (ns.id && itemrefList.includes(ns.id)) {
28
+ returnArr.push(ns);
29
+ }
30
+ }
31
+ else {
32
+ return returnArr;
33
+ }
34
+ ns = ns.nextSibling;
35
+ }
14
36
  }
15
37
  return returnArr;
16
38
  }
@@ -1,15 +1,37 @@
1
1
  import { splitRefs } from './splitRefs.js';
2
- export function getAdjRefs(el: Element): Array<Element>{
3
- const returnArr = [el];
4
- const itemref = el.getAttribute('itemref');
5
- if(itemref === null) return returnArr;
6
- const itemrefList = splitRefs(itemref);// itemref.split(' ').map((id) => id.trim()).filter((id) => id.length > 0);
7
- if(itemrefList.length === 0) return returnArr;
8
- let ns = el.nextElementSibling;
9
- while(ns !== null && itemrefList.includes(ns.id)){
10
- returnArr.push(ns);
11
- ns = ns.nextElementSibling;
2
+ export function getAdjRefs(node: Node){
3
+ const returnArr: Array<Node> = [node];
4
+ if(node.nodeType === node.COMMENT_NODE){
5
+ const openText = (node as Comment).data.split(' ')[0];
6
+ const closedText = `/${openText}`;
7
+ let ns = node.nextSibling;
8
+ while(ns){
9
+ returnArr.push(ns);
10
+ if(node.nodeType === node.COMMENT_NODE && (node as Comment).data === closedText){
11
+ return returnArr;
12
+ }
13
+ ns = ns.nextSibling;
14
+ }
15
+ }else{
16
+ const el = node as Element;
17
+ const itemref = el.getAttribute('itemref');
18
+ if(itemref === null) return returnArr;
19
+ const itemrefList = splitRefs(itemref);// itemref.split(' ').map((id) => id.trim()).filter((id) => id.length > 0);
20
+ if(itemrefList.length === 0) return returnArr;
21
+ let ns = el.nextSibling;
22
+ while(ns !== null){
23
+ if(ns instanceof Element){
24
+ if(ns.id && itemrefList.includes(ns.id)){
25
+ returnArr.push(ns);
26
+ }
27
+ }else{
28
+ return returnArr;
29
+ }
30
+ ns = ns.nextSibling;
31
+ }
32
+
12
33
  }
34
+
13
35
  return returnArr;
14
36
  }
15
37
 
@@ -0,0 +1,39 @@
1
+ import { toQuery } from './toQuery.js';
2
+ import { splitRefs } from '../refid/splitRefs.js';
3
+ export function affine(fragment, el) {
4
+ const qry = toQuery(el);
5
+ const { elFragment, map } = prep(el);
6
+ const matches = Array.from(fragment.querySelectorAll(qry));
7
+ for (const match of matches) {
8
+ clone(match, elFragment, map);
9
+ }
10
+ }
11
+ export function prep(el) {
12
+ const elFragment = new DocumentFragment();
13
+ const clone = el.cloneNode(true);
14
+ for (const child of clone.childNodes) {
15
+ elFragment.appendChild(child);
16
+ }
17
+ const insertAttrs = el.getAttribute('-i');
18
+ let map = null;
19
+ if (insertAttrs !== null) {
20
+ const attrs = splitRefs(insertAttrs);
21
+ map = {};
22
+ for (const attr of attrs) {
23
+ map[attr] = el.getAttribute(attr);
24
+ }
25
+ }
26
+ return {
27
+ elFragment, map
28
+ };
29
+ }
30
+ export function clone(matchingElement, elFragment, map) {
31
+ const fragmentClone = elFragment.cloneNode(true);
32
+ matchingElement.replaceChildren(fragmentClone);
33
+ if (map !== null) {
34
+ for (const key in map) {
35
+ const value = map[key];
36
+ matchingElement.setAttribute(key, value);
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,46 @@
1
+ import {toQuery} from './toQuery.js';
2
+ import {splitRefs} from '../refid/splitRefs.js';
3
+
4
+ export function affine(fragment: DocumentFragment | Element,
5
+ el: Element){
6
+ const qry = toQuery(el);
7
+ const {elFragment, map} = prep(el);
8
+ const matches = Array.from(fragment.querySelectorAll(qry));
9
+ for(const match of matches){
10
+ clone(match, elFragment, map);
11
+ }
12
+ }
13
+
14
+ export function prep(el: Element){
15
+ const elFragment = new DocumentFragment();
16
+ const clone = el.cloneNode(true);
17
+ for(const child of clone.childNodes){
18
+ elFragment.appendChild(child);
19
+ }
20
+ const insertAttrs = el.getAttribute('-i');
21
+ let map: {[key: string]: string} | null = null;
22
+ if(insertAttrs !== null){
23
+ const attrs = splitRefs(insertAttrs);
24
+ map = {};
25
+ for(const attr of attrs){
26
+ map[attr] = el.getAttribute(attr)!;
27
+ }
28
+ }
29
+ return {
30
+ elFragment, map
31
+ }
32
+ }
33
+
34
+ export function clone(
35
+ matchingElement: Element,
36
+ elFragment: DocumentFragment,
37
+ map: {[key: string]: string} | null){
38
+ const fragmentClone = elFragment.cloneNode(true) as DocumentFragment;
39
+ matchingElement.replaceChildren(fragmentClone);
40
+ if(map !== null){
41
+ for(const key in map){
42
+ const value = map[key]!;
43
+ matchingElement.setAttribute(key, value);
44
+ }
45
+ }
46
+ }
@@ -1,37 +1,45 @@
1
1
  import { toQuery } from './toQuery.js';
2
- import { splitRefs } from '../refid/splitRefs.js';
3
2
  import { MountObserver } from '../MountObserver.js';
3
+ import { prep, clone } from './affine.js';
4
+ const previousObservers = new WeakMap();
4
5
  export function beKindred(fragment, el) {
6
+ if (!fragment.isConnected)
7
+ throw 'too soon, use affine';
5
8
  const qry = toQuery(el);
6
- const elFragment = new DocumentFragment();
7
- const clone = el.cloneNode(true);
8
- for (const child of clone.childNodes) {
9
- elFragment.appendChild(child);
10
- }
11
- const insertAttrs = el.getAttribute('-i');
12
- let map = null;
13
- if (insertAttrs !== null) {
14
- const attrs = splitRefs(insertAttrs);
15
- map = {};
16
- for (const attr of attrs) {
17
- map[attr] = el.getAttribute(attr);
9
+ const previousObserversOfFragment = previousObservers.get(fragment);
10
+ if (previousObserversOfFragment !== undefined) {
11
+ const staleObservers = previousObserversOfFragment.filter(x => el.matches(x[0]));
12
+ const nonStaleObservers = previousObserversOfFragment.filter(x => !el.matches(x[0]));
13
+ if (staleObservers !== undefined && staleObservers.length > 0) {
14
+ for (const staleObserver of staleObservers) {
15
+ staleObserver[1].disconnect(fragment);
16
+ }
18
17
  }
18
+ previousObservers.set(fragment, nonStaleObservers);
19
19
  }
20
+ const { elFragment, map } = prep(el);
20
21
  const mo = new MountObserver({
21
22
  on: qry,
22
23
  do: {
23
24
  mount: (matchingElement) => {
24
- const fragmentClone = elFragment.cloneNode(true);
25
- matchingElement.replaceChildren(fragmentClone);
26
- if (map !== null) {
27
- for (const key in map) {
28
- const value = map[key];
29
- matchingElement.setAttribute(key, value);
30
- }
31
- }
25
+ clone(matchingElement, elFragment, map);
26
+ // const fragmentClone = elFragment.cloneNode(true) as DocumentFragment;
27
+ // matchingElement.replaceChildren(fragmentClone);
28
+ // if(map !== null){
29
+ // for(const key in map){
30
+ // const value = map[key]!;
31
+ // matchingElement.setAttribute(key, value);
32
+ // }
33
+ // }
32
34
  }
33
35
  }
34
36
  });
35
37
  mo.observe(fragment);
38
+ if (previousObservers.has(fragment)) {
39
+ previousObservers.get(fragment).push([qry, mo]);
40
+ }
41
+ else {
42
+ previousObservers.set(fragment, [[qry, mo]]);
43
+ }
36
44
  return mo;
37
45
  }
@@ -1,45 +1,55 @@
1
1
  import {toQuery} from './toQuery.js';
2
2
  import {splitRefs} from '../refid/splitRefs.js';
3
3
  import {MountObserver} from '../MountObserver.js';
4
+ import {prep, clone} from './affine.js';
5
+
6
+ type OnMountObserver = [string, MountObserver];
7
+ const previousObservers = new WeakMap<DocumentFragment | Element, Array<OnMountObserver>>();
4
8
 
5
9
  export function beKindred(
6
10
  fragment: DocumentFragment | Element,
7
11
  el: Element,
8
- //beVigilant: boolean = false
9
12
  ){
13
+ if(!fragment.isConnected) throw 'too soon, use affine';
10
14
  const qry = toQuery(el);
11
-
12
- const elFragment = new DocumentFragment();
13
- const clone = el.cloneNode(true);
14
- for(const child of clone.childNodes){
15
- elFragment.appendChild(child);
16
- }
17
- const insertAttrs = el.getAttribute('-i');
18
- let map: {[key: string]: string} | null = null;
19
- if(insertAttrs !== null){
20
- const attrs = splitRefs(insertAttrs);
21
- map = {};
22
- for(const attr of attrs){
23
- map[attr] = el.getAttribute(attr)!;
15
+ const previousObserversOfFragment = previousObservers.get(fragment);
16
+ if(previousObserversOfFragment !== undefined){
17
+ const staleObservers = previousObserversOfFragment.filter(x => el.matches(x[0]));
18
+ const nonStaleObservers = previousObserversOfFragment.filter(x => !el.matches(x[0]));
19
+ if(staleObservers !== undefined && staleObservers.length > 0){
20
+ for(const staleObserver of staleObservers){
21
+ staleObserver[1].disconnect(fragment);
22
+ }
24
23
  }
24
+ previousObservers.set(fragment, nonStaleObservers);
25
25
  }
26
26
 
27
+
28
+
29
+ const {elFragment, map} = prep(el);
30
+
27
31
  const mo = new MountObserver({
28
32
  on: qry,
29
33
  do: {
30
34
  mount: (matchingElement) => {
31
- const fragmentClone = elFragment.cloneNode(true) as DocumentFragment;
32
- matchingElement.replaceChildren(fragmentClone);
33
- if(map !== null){
34
- for(const key in map){
35
- const value = map[key]!;
36
- matchingElement.setAttribute(key, value);
37
- }
38
- }
35
+ clone(matchingElement, elFragment, map);
36
+ // const fragmentClone = elFragment.cloneNode(true) as DocumentFragment;
37
+ // matchingElement.replaceChildren(fragmentClone);
38
+ // if(map !== null){
39
+ // for(const key in map){
40
+ // const value = map[key]!;
41
+ // matchingElement.setAttribute(key, value);
42
+ // }
43
+ // }
39
44
  }
40
45
  }
41
46
  });
42
47
  mo.observe(fragment);
48
+ if(previousObservers.has(fragment)){
49
+ previousObservers.get(fragment)!.push([qry, mo]);
50
+ }else{
51
+ previousObservers.set(fragment, [[qry, mo]]);
52
+ }
43
53
  return mo;
44
54
 
45
55
  }
@@ -0,0 +1,13 @@
1
+ const wrapped = Symbol.for('50tzQZt95ECXUtHF7a40og');
2
+ export function wrap(templ, base) {
3
+ const wasWrapped = templ[wrapped];
4
+ if (!wasWrapped) {
5
+ templ[wrapped] = true;
6
+ if (templ.content.childElementCount > 1) {
7
+ const start = document.createComment(base);
8
+ templ.content.prepend(start);
9
+ const end = document.createComment(`/${base}`);
10
+ templ.content.appendChild(end);
11
+ }
12
+ }
13
+ }
@@ -0,0 +1,17 @@
1
+ const wrapped = Symbol.for('50tzQZt95ECXUtHF7a40og');
2
+
3
+ export function wrap(
4
+ templ: HTMLTemplateElement,
5
+ base: string,
6
+ ){
7
+ const wasWrapped = (<any>templ)[wrapped];
8
+ if (!wasWrapped) {
9
+ (<any>templ)[wrapped] = true;
10
+ if (templ.content.childElementCount > 1) {
11
+ const start = document.createComment(base);
12
+ templ.content.prepend(start);
13
+ const end = document.createComment(`/${base}`);
14
+ templ.content.appendChild(end);
15
+ }
16
+ }
17
+ }
@@ -1,16 +1,13 @@
1
1
  import {IEnhancement, BEAllProps} from '../trans-render/be/types';
2
- import { XForm } from "../trans-render/types";
2
+ import {Specifier} from '../trans-render/dss/types';
3
3
 
4
- export interface EndUserProps<TProps, TMethods, TElement = {}> extends IEnhancement<HTMLTemplateElement>{
5
- of: string,
6
- xform: XForm<TProps, TMethods, TElement>,
7
- initModel?: TProps & TMethods,
8
- slotMap?: any,
4
+ export interface EndUserProps<TProps, TMethods, TElement = {}> extends IEnhancement{
5
+
9
6
  }
10
7
 
11
8
  export interface AllProps<TProps, TMethods, TElement = {}> extends EndUserProps<TProps, TMethods, TElement>{
12
- isParsed?: boolean,
13
- model?: TProps & TMethods,
9
+ includeRules: Array<IncludeRule>;
10
+ nodesToInclude: Array<Element>;
14
11
  }
15
12
 
16
13
  export type AP = AllProps<any, any, any>;
@@ -23,6 +20,11 @@ export type BAP = AP & BEAllProps;
23
20
 
24
21
 
25
22
  export interface Actions{
26
- onInitModel(self: BAP): ProPAP;
27
- startWeaving(self: BAP): ProPAP;
23
+ hydrate(self: BAP): Promise<PAP>;
24
+ include(self: BAP): void;
25
+ }
26
+
27
+ export interface IncludeRule {
28
+ remoteSpecifierString?: string,
29
+ remoteSpecifier: Specifier,
28
30
  }
@@ -237,5 +237,7 @@ export type IshCtr = ({new() : Ishcycle}) | (() => Promise<{new() : Ishcycle}>);
237
237
 
238
238
  //#endregion
239
239
 
240
+ export type RefType = '#' | '!';
241
+
240
242
 
241
243