mount-observer 0.0.111 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/Events.js +28 -26
  2. package/Events.ts +34 -30
  3. package/MountObserver.js +235 -520
  4. package/MountObserver.ts +281 -542
  5. package/README.md +149 -56
  6. package/attrCoordinates.js +93 -0
  7. package/attrCoordinates.ts +122 -0
  8. package/constants.js +6 -0
  9. package/constants.ts +7 -0
  10. package/index.js +3 -0
  11. package/index.ts +19 -0
  12. package/loadImports.js +47 -0
  13. package/loadImports.ts +56 -0
  14. package/package.json +8 -115
  15. package/playwright.config.ts +0 -1
  16. package/types.d.ts +86 -0
  17. package/whereAttr.js +174 -0
  18. package/whereAttr.ts +221 -0
  19. package/LICENSE +0 -21
  20. package/Newish.js +0 -145
  21. package/Newish.ts +0 -169
  22. package/ObsAttr.js +0 -18
  23. package/ObsAttr.ts +0 -18
  24. package/RootMutObs.js +0 -49
  25. package/RootMutObs.ts +0 -58
  26. package/Synthesizer.js +0 -125
  27. package/Synthesizer.ts +0 -130
  28. package/bindish.js +0 -15
  29. package/bindish.ts +0 -22
  30. package/compose.js +0 -148
  31. package/compose.ts +0 -164
  32. package/doCleanup.js +0 -31
  33. package/doCleanup.ts +0 -34
  34. package/getWhereAttrSelector.js +0 -83
  35. package/getWhereAttrSelector.ts +0 -92
  36. package/preloadContent.js +0 -44
  37. package/preloadContent.ts +0 -47
  38. package/readAttrs.ts +0 -60
  39. package/refid/README.md +0 -259
  40. package/refid/arr.js +0 -4
  41. package/refid/arr.ts +0 -4
  42. package/refid/camelToKebab.js +0 -4
  43. package/refid/camelToKebab.ts +0 -4
  44. package/refid/genIds.js +0 -190
  45. package/refid/genIds.ts +0 -177
  46. package/refid/getAdjRefs.js +0 -38
  47. package/refid/getAdjRefs.ts +0 -38
  48. package/refid/getContext.js +0 -13
  49. package/refid/getContext.ts +0 -14
  50. package/refid/getCount.js +0 -8
  51. package/refid/getCount.ts +0 -8
  52. package/refid/getIsh.js +0 -35
  53. package/refid/getIsh.ts +0 -37
  54. package/refid/hostish.js +0 -18
  55. package/refid/hostish.ts +0 -20
  56. package/refid/ism.js +0 -78
  57. package/refid/ism.ts +0 -81
  58. package/refid/itemprops.js +0 -60
  59. package/refid/itemprops.ts +0 -67
  60. package/refid/joinMatching.js +0 -56
  61. package/refid/joinMatching.ts +0 -54
  62. package/refid/nudge.js +0 -23
  63. package/refid/nudge.ts +0 -23
  64. package/refid/regIsh.js +0 -27
  65. package/refid/regIsh.ts +0 -31
  66. package/refid/secretKeys.js +0 -5
  67. package/refid/secretKeys.ts +0 -5
  68. package/refid/splitRefs.js +0 -6
  69. package/refid/splitRefs.ts +0 -6
  70. package/refid/stdVal.js +0 -15
  71. package/refid/stdVal.ts +0 -15
  72. package/refid/via.js +0 -114
  73. package/refid/via.ts +0 -113
  74. package/slotkin/affine.js +0 -39
  75. package/slotkin/affine.ts +0 -46
  76. package/slotkin/beKindred.js +0 -45
  77. package/slotkin/beKindred.ts +0 -55
  78. package/slotkin/getBreadth.js +0 -19
  79. package/slotkin/getBreadth.ts +0 -21
  80. package/slotkin/getFrag.js +0 -22
  81. package/slotkin/getFrag.ts +0 -21
  82. package/slotkin/toQuery.js +0 -12
  83. package/slotkin/toQuery.ts +0 -13
  84. package/slotkin/wrap.js +0 -13
  85. package/slotkin/wrap.ts +0 -18
  86. package/ts-refs/LICENSE +0 -21
  87. package/ts-refs/README.md +0 -18
  88. package/ts-refs/be-a-beacon/types.d.ts +0 -22
  89. package/ts-refs/be-alit/types.d.ts +0 -1
  90. package/ts-refs/be-based/types.d.ts +0 -32
  91. package/ts-refs/be-bound/types.d.ts +0 -65
  92. package/ts-refs/be-buttoned-up/types.d.ts +0 -21
  93. package/ts-refs/be-calculating/types.d.ts +0 -57
  94. package/ts-refs/be-clonable/types.d.ts +0 -28
  95. package/ts-refs/be-committed/types.d.ts +0 -26
  96. package/ts-refs/be-consoling/types.d.ts +0 -25
  97. package/ts-refs/be-counted/types.d.ts +0 -88
  98. package/ts-refs/be-delible/types.d.ts +0 -26
  99. package/ts-refs/be-directive/types.d.ts +0 -43
  100. package/ts-refs/be-dispatching/types.d.ts +0 -41
  101. package/ts-refs/be-elevating/types.d.ts +0 -55
  102. package/ts-refs/be-enhanced/types.d.ts +0 -32
  103. package/ts-refs/be-enhancing/types.d.ts +0 -31
  104. package/ts-refs/be-evanescent/types.d.ts +0 -20
  105. package/ts-refs/be-eventing/types.d.ts +0 -27
  106. package/ts-refs/be-exportable/types.d.ts +0 -26
  107. package/ts-refs/be-fetching/types.d.ts +0 -73
  108. package/ts-refs/be-flashy/types.d.ts +0 -27
  109. package/ts-refs/be-formalizing/types.d.ts +0 -29
  110. package/ts-refs/be-formidable/types.d.ts +0 -64
  111. package/ts-refs/be-giddy/types.d.ts +0 -26
  112. package/ts-refs/be-gingerly/types.d.ts +0 -19
  113. package/ts-refs/be-gone/types.d.ts +0 -24
  114. package/ts-refs/be-hashing-out/types.d.ts +0 -22
  115. package/ts-refs/be-hive/types.d.ts +0 -18
  116. package/ts-refs/be-imbued/types.d.ts +0 -30
  117. package/ts-refs/be-included/types.d.ts +0 -20
  118. package/ts-refs/be-inclusive/types.d.ts +0 -30
  119. package/ts-refs/be-intersectional/types.d.ts +0 -37
  120. package/ts-refs/be-intl/types.d.ts +0 -28
  121. package/ts-refs/be-invoking/types.d.ts +0 -28
  122. package/ts-refs/be-joining/types.d.ts +0 -26
  123. package/ts-refs/be-kvetching/types.d.ts +0 -24
  124. package/ts-refs/be-lazy/types.d.ts +0 -29
  125. package/ts-refs/be-literate/types.d.ts +0 -29
  126. package/ts-refs/be-mediating/types.d.ts +0 -34
  127. package/ts-refs/be-methodical/types.d.ts +0 -20
  128. package/ts-refs/be-modding/types.d.ts +0 -18
  129. package/ts-refs/be-observant/types.d.ts +0 -27
  130. package/ts-refs/be-observing/types.d.ts +0 -84
  131. package/ts-refs/be-parsed/types.d.ts +0 -19
  132. package/ts-refs/be-parsing/types.d.ts +0 -37
  133. package/ts-refs/be-persistent/types.d.ts +0 -66
  134. package/ts-refs/be-propagating/types.d.ts +0 -26
  135. package/ts-refs/be-reformable/types.d.ts +0 -48
  136. package/ts-refs/be-render-neutral/types.d.ts +0 -31
  137. package/ts-refs/be-scoped/types.d.ts +0 -24
  138. package/ts-refs/be-sharing/types.d.ts +0 -17
  139. package/ts-refs/be-switched/types.d.ts +0 -155
  140. package/ts-refs/be-typed/types.d.ts +0 -36
  141. package/ts-refs/be-value-added/types.d.ts +0 -34
  142. package/ts-refs/be-valued/types.d.ts +0 -22
  143. package/ts-refs/be-written/types.d.ts +0 -59
  144. package/ts-refs/css-charts/types.d.ts +0 -38
  145. package/ts-refs/css-echarts/types.d.ts +0 -13
  146. package/ts-refs/data-props/types.d.ts +0 -27
  147. package/ts-refs/do-inc/types.d.ts +0 -28
  148. package/ts-refs/do-invoke/types.d.ts +0 -28
  149. package/ts-refs/do-toggle/types.d.ts +0 -27
  150. package/ts-refs/em-bower/types.d.ts +0 -18
  151. package/ts-refs/fetch-for/types.d.ts +0 -37
  152. package/ts-refs/folder-picker/types.d.ts +0 -43
  153. package/ts-refs/for-fetch/doc.d.ts +0 -98
  154. package/ts-refs/for-fetch/types.d.ts +0 -83
  155. package/ts-refs/mount-observer/types.d.ts +0 -248
  156. package/ts-refs/mt-si/types.d.ts +0 -21
  157. package/ts-refs/per-each/types.d.ts +0 -51
  158. package/ts-refs/soak-up/types.d.ts +0 -36
  159. package/ts-refs/trans-render/XV/types.d.ts +0 -69
  160. package/ts-refs/trans-render/asmr/types.d.ts +0 -138
  161. package/ts-refs/trans-render/be/types.d.ts +0 -190
  162. package/ts-refs/trans-render/dss/types.d.ts +0 -57
  163. package/ts-refs/trans-render/froop/types.d.ts +0 -416
  164. package/ts-refs/trans-render/funions/types.d.ts +0 -12
  165. package/ts-refs/trans-render/lib/mixins/types.d.ts +0 -42
  166. package/ts-refs/trans-render/lib/prs/types.d.ts +0 -40
  167. package/ts-refs/trans-render/lib/types.d.ts +0 -489
  168. package/ts-refs/trans-render/types.d.ts +0 -583
  169. package/ts-refs/wc-info/SimpleWCInfo.d.ts +0 -15
  170. package/ts-refs/when-resolved/types.d.ts +0 -30
  171. package/ts-refs/xp-as/types.d.ts +0 -20
  172. package/ts-refs/xtal-element/types.d.ts +0 -43
  173. package/ts-refs/xtal-frappe-chart/types.d.ts +0 -193
  174. package/upShadowSearch.js +0 -25
  175. package/upShadowSearch.ts +0 -23
  176. package/waitForEvent.js +0 -12
  177. package/waitForEvent.ts +0 -13
  178. package/waitForIsh.js +0 -21
  179. package/waitForIsh.ts +0 -20
package/MountObserver.js CHANGED
@@ -1,574 +1,289 @@
1
- import { RootMutObs } from './RootMutObs.js';
2
- import { bindish, bindishIt } from './bindish.js';
3
- import './refid/hostish.js'; // gets embedded even if not used
4
- export const guid = '5Pv6bHOVH0ae07opRZ8N/g';
5
- export const wasItemReffed = Symbol.for('8aA6xB8+PkScmivaslBk5Q');
6
- export const mutationObserverLookup = new WeakMap();
7
- const refCount = new WeakMap();
1
+ import { MountEvent, DismountEvent, DisconnectEvent, LoadEvent, AttrChangeEvent } from './Events.js';
8
2
  export class MountObserver extends EventTarget {
9
- #mountInit;
3
+ #init;
10
4
  #options;
11
- //#rootMutObs: RootMutObs | undefined;
12
5
  #abortController;
13
- mountedElements;
14
- #mountedList;
15
- #disconnected;
16
- //#unmounted: WeakSet<Element>;
17
- #isComplex;
18
- objNde;
19
- constructor(init) {
6
+ #modules = [];
7
+ #mountedElements = new WeakSet();
8
+ #processedElements = new WeakSet();
9
+ #mutationObserver;
10
+ #rootNode;
11
+ #importsLoaded = false;
12
+ #elementAttrStates = new WeakMap();
13
+ #matchesWhereAttrFn = null;
14
+ #buildAttrCoordinateMapFn = null;
15
+ constructor(init, options = {}) {
20
16
  super();
21
- const { on, whereElementIntersectsWith, whereMediaMatches } = init;
22
- let isComplex = false;
23
- //TODO: study this problem further. Starting to think this is basically not polyfillable
24
- if (on !== undefined) {
25
- const reducedMatch = on.replaceAll(':not(', '');
26
- isComplex = reducedMatch.includes(' ') || (reducedMatch.includes(':') && reducedMatch.includes('('));
27
- }
28
- this.#isComplex = isComplex;
29
- if (whereElementIntersectsWith)
30
- throw 'NI'; //not implemented
31
- this.#mountInit = init;
17
+ this.#init = init;
18
+ this.#options = options;
32
19
  this.#abortController = new AbortController();
33
- this.mountedElements = {
34
- weakSet: new WeakSet(),
35
- setWeak: new Set(),
36
- };
37
- this.#disconnected = new WeakSet();
38
- //this.#unmounted = new WeakSet();
39
- }
40
- #calculatedSelector;
41
- #attrParts;
42
- #fullListOfEnhancementAttrs;
43
- async observedAttrs() {
44
- await this.#selector();
45
- return this.#fullListOfEnhancementAttrs;
46
- }
47
- //get #attrVals
48
- async #selector() {
49
- if (this.#calculatedSelector !== undefined)
50
- return this.#calculatedSelector;
51
- const { on, whereAttr } = this.#mountInit;
52
- const withoutAttrs = on || '*';
53
- if (whereAttr === undefined)
54
- return withoutAttrs;
55
- const { getWhereAttrSelector } = await import('./getWhereAttrSelector.js');
56
- const info = await getWhereAttrSelector(whereAttr, withoutAttrs);
57
- const { fullListOfAttrs, calculatedSelector, partitionedAttrs } = info;
58
- this.#fullListOfEnhancementAttrs = fullListOfAttrs;
59
- this.#attrParts = partitionedAttrs;
60
- this.#calculatedSelector = calculatedSelector;
61
- return this.#calculatedSelector;
62
- }
63
- //This method is called publicly from outside mount-observer -- keep it public
64
- async composeFragment(fragment, level) {
65
- const bis = fragment.querySelectorAll(`${inclTemplQry}`);
66
- for (const bi of bis) {
67
- if (bi.getAttribute('rel') === 'preload') {
68
- (await import('./preloadContent.js')).preloadContent(bi);
69
- }
70
- else {
71
- await this.#compose(bi, level);
72
- }
20
+ if (options.disconnectedSignal) {
21
+ options.disconnectedSignal.addEventListener('abort', () => {
22
+ this.disconnect();
23
+ });
73
24
  }
74
- }
75
- async #compose(el, level) {
76
- //[TODO]: load async, not used often
77
- const src = el.getAttribute('src');
78
- if (src === null || src.length < 2)
79
- return;
80
- const refType = src[0];
81
- if (!['!', '#'].includes(refType))
82
- return;
83
- const { compose } = await import('./compose.js');
84
- await compose(this, el, level, src.substring(1), refType);
85
- }
86
- #templLookUp = new Map();
87
- #searchForComment(refName, fragment) {
88
- //get rid of
89
- const iterator = document.evaluate(`//comment()[.="${refName}"]`, fragment, null, XPathResult.ANY_TYPE, null);
90
- //console.log({xpathResult})
91
- try {
92
- let thisNode = iterator.iterateNext();
93
- return thisNode;
25
+ // Preload whereAttr utilities if needed
26
+ if (init.whereAttr) {
27
+ this.#preloadWhereAttrUtilities();
94
28
  }
95
- catch (e) {
96
- return null;
29
+ // Start loading imports if eager
30
+ if (init.loadingEagerness === 'eager' && init.import) {
31
+ this.#loadImports();
97
32
  }
98
33
  }
99
- async findByID(
100
- //[TODO]: make external, not always used
101
- refName, fragment, refType) {
102
- if (this.#templLookUp.has(refName))
103
- return this.#templLookUp.get(refName);
104
- let templ = null;
105
- templ = refType === '#' ? fragment.querySelector(`#${refName}`) : this.#searchForComment(refName, fragment);
106
- if (templ === null) {
107
- let rootToSearchOutwardFrom = ((fragment.isConnected ? fragment.getRootNode() : this.#mountInit.withTargetShadowRoot) || document);
108
- templ = refType === '#' ? rootToSearchOutwardFrom.getElementById(refName) : this.#searchForComment(refName, rootToSearchOutwardFrom);
109
- while (templ === null && rootToSearchOutwardFrom !== document) {
110
- rootToSearchOutwardFrom = (rootToSearchOutwardFrom.host || rootToSearchOutwardFrom).getRootNode();
111
- templ = refType === '#' ? rootToSearchOutwardFrom.getElementById(refName) : this.#searchForComment(refName, rootToSearchOutwardFrom);
112
- }
34
+ async #preloadWhereAttrUtilities() {
35
+ if (!this.#matchesWhereAttrFn) {
36
+ const { matchesWhereAttr } = await import('./whereAttr.js');
37
+ this.#matchesWhereAttrFn = matchesWhereAttr;
113
38
  }
114
- if (templ !== null) {
115
- if (!(templ instanceof HTMLTemplateElement)) {
116
- const newTempl = document.createElement('template');
117
- const { getAdjRefs } = await import('./refid/getAdjRefs.js');
118
- const adjRefs = getAdjRefs(templ);
119
- // if(adjRefs.length > 1){
120
- // (<any>newTempl)[wasItemReffed] = true;
121
- // adjRefs[0].setAttribute('itemref', '<autogen>');
122
- // }
123
- const fragment = document.createDocumentFragment();
124
- let first = true;
125
- for (const adjRef of adjRefs) {
126
- const clone = adjRef.cloneNode(true);
127
- if (refType === '#' && clone instanceof Element) {
128
- if (first && adjRefs.length > 1) {
129
- clone.setAttribute('itemref', '<autogen>');
130
- newTempl[wasItemReffed] = true;
131
- first = false;
132
- }
133
- clone.removeAttribute('id');
134
- }
135
- fragment.appendChild(clone);
136
- }
137
- if (templ instanceof Element) {
138
- const { doCleanup } = await import('./doCleanup.js');
139
- doCleanup(templ, fragment);
140
- }
141
- else {
142
- //TODO: cleanup
143
- }
144
- newTempl.content.appendChild(fragment);
145
- templ = newTempl;
146
- }
147
- this.#templLookUp.set(refName, templ);
39
+ if (!this.#buildAttrCoordinateMapFn) {
40
+ const { buildAttrCoordinateMap } = await import('./attrCoordinates.js');
41
+ this.#buildAttrCoordinateMapFn = buildAttrCoordinateMap;
148
42
  }
149
- return templ;
150
43
  }
151
- disconnect(within) {
152
- const nodeToMonitor = this.#isComplex ? (within instanceof ShadowRoot ? within : within.getRootNode()) : within;
153
- const currentCount = refCount.get(nodeToMonitor);
154
- if (currentCount !== undefined) {
155
- if (currentCount <= 1) {
156
- const observer = mutationObserverLookup.get(nodeToMonitor);
157
- if (observer === undefined) {
158
- console.warn(refCountErr);
159
- }
160
- else {
161
- observer.disconnect();
162
- mutationObserverLookup.delete(nodeToMonitor);
163
- refCount.delete(nodeToMonitor);
164
- }
165
- }
166
- else {
167
- refCount.set(nodeToMonitor, currentCount + 1);
168
- }
169
- }
170
- else {
171
- if (mutationObserverLookup.has(nodeToMonitor)) {
172
- console.warn(refCountErr);
173
- }
174
- }
175
- this.dispatchEvent(new Event('disconnectedCallback'));
176
- }
177
- async observe(within, options) {
178
- this.#options = options;
179
- const init = this.#mountInit;
180
- const { whereMediaMatches } = init;
181
- if (whereMediaMatches === undefined) {
182
- await this.#observe2(within);
183
- return;
184
- }
185
- const mql = window.matchMedia(whereMediaMatches);
186
- if (mql.matches) {
187
- await this.#observe2(within);
188
- }
189
- mql.addEventListener('change', async (e) => {
190
- if (e.matches) {
191
- if (this.objNde === undefined) {
192
- await this.#observe2(within);
193
- }
194
- else {
195
- await this.#mountAll();
196
- }
197
- }
198
- else {
199
- if (this.objNde !== undefined) {
200
- await this.#dismountAll();
201
- }
202
- }
203
- });
44
+ get disconnectedSignal() {
45
+ return this.#abortController.signal;
204
46
  }
205
- async #observe2(within) {
206
- await this.#selector();
207
- this.objNde = new WeakRef(within);
208
- const nodeToMonitor = this.#isComplex ? (within instanceof ShadowRoot ? within : within.getRootNode()) : within;
209
- if (!mutationObserverLookup.has(nodeToMonitor)) {
210
- mutationObserverLookup.set(nodeToMonitor, new RootMutObs(nodeToMonitor, this.#mountInit));
211
- refCount.set(nodeToMonitor, 1);
47
+ async observe(rootNode) {
48
+ if (this.#rootNode) {
49
+ throw new Error('Already observing');
212
50
  }
213
- else {
214
- const currentCount = refCount.get(nodeToMonitor);
215
- if (currentCount === undefined) {
216
- console.warn(refCountErr);
217
- }
218
- else {
219
- refCount.set(nodeToMonitor, currentCount + 1);
220
- }
51
+ this.#rootNode = new WeakRef(rootNode);
52
+ // Wait for whereAttr utilities to load if needed
53
+ if (this.#init.whereAttr && !this.#matchesWhereAttrFn) {
54
+ await this.#preloadWhereAttrUtilities();
221
55
  }
222
- const rootMutObs = mutationObserverLookup.get(within);
223
- const fullListOfAttrs = this.#fullListOfEnhancementAttrs;
224
- rootMutObs.addEventListener('mutation-event', async (e) => {
225
- //TODO: disconnected
226
- if (this.#isComplex) {
227
- this.#inspectWithin(within, false);
228
- return;
229
- }
230
- const { mutationRecords } = e;
231
- const elsToInspect = [];
232
- //const elsToDisconnect: Array<Element> = [];
233
- const doDisconnect = this.#mountInit.do?.disconnect;
234
- let attrChangeInfosMap;
235
- for (const mutationRecord of mutationRecords) {
236
- const { addedNodes, type, removedNodes } = mutationRecord;
237
- const addedElements = Array.from(addedNodes).filter(x => x instanceof Element);
238
- addedElements.forEach(x => elsToInspect.push(x));
239
- if (type === 'attributes') {
240
- const { target, attributeName, oldValue } = mutationRecord;
241
- if (target instanceof Element && attributeName !== null /*&& this.#mounted.has(target)*/) {
242
- if (fullListOfAttrs !== undefined) {
243
- const idx = fullListOfAttrs.indexOf(attributeName);
244
- if (idx !== -1) {
245
- if (attrChangeInfosMap === undefined)
246
- attrChangeInfosMap = new Map();
247
- let attrChangeInfos = attrChangeInfosMap.get(target);
248
- if (attrChangeInfos === undefined) {
249
- attrChangeInfos = [];
250
- attrChangeInfosMap.set(target, attrChangeInfos);
251
- }
252
- const newValue = target.getAttribute(attributeName);
253
- const parts = this.#attrParts[idx];
254
- const attrChangeInfo = {
255
- isSOfTAttr: false,
256
- oldValue,
257
- name: attributeName,
258
- newValue,
259
- idx,
260
- parts
261
- };
262
- attrChangeInfos.push(attrChangeInfo);
263
- }
56
+ // Process existing elements
57
+ this.#processNode(rootNode);
58
+ // Set up mutation observer
59
+ this.#mutationObserver = new MutationObserver((mutations) => {
60
+ const attrChanges = [];
61
+ for (const mutation of mutations) {
62
+ if (mutation.type === 'childList') {
63
+ for (const node of mutation.addedNodes) {
64
+ if (node.nodeType === Node.ELEMENT_NODE) {
65
+ this.#processNode(node);
264
66
  }
265
67
  }
266
- elsToInspect.push(target);
68
+ mutation.removedNodes.forEach(node => {
69
+ if (node.nodeType === Node.ELEMENT_NODE) {
70
+ this.#handleRemoval(node);
71
+ }
72
+ });
267
73
  }
268
- const deletedElements = Array.from(removedNodes).filter(x => x instanceof Element);
269
- const { DisconnectEvent } = await import('./Events.js');
270
- for (const deletedElement of deletedElements) {
271
- this.#disconnected.add(deletedElement);
272
- if (doDisconnect !== undefined) {
273
- doDisconnect(deletedElement, this, {});
74
+ else if (mutation.type === 'attributes' && mutation.target.nodeType === Node.ELEMENT_NODE) {
75
+ // Handle attribute changes for mounted elements
76
+ const element = mutation.target;
77
+ if (this.#mountedElements.has(element) && this.#init.whereAttr) {
78
+ const changes = this.#checkAttrChanges(element);
79
+ attrChanges.push(...changes);
274
80
  }
275
- this.dispatchEvent(new DisconnectEvent(deletedElement));
276
81
  }
277
82
  }
278
- if (attrChangeInfosMap !== undefined) {
279
- const { AttrChangeEvent } = await import('./Events.js');
280
- for (const [key, value] of attrChangeInfosMap) {
281
- this.dispatchEvent(new AttrChangeEvent(key, value));
282
- }
83
+ // Batch and dispatch attribute changes
84
+ if (attrChanges.length > 0) {
85
+ this.dispatchEvent(new AttrChangeEvent(attrChanges));
283
86
  }
284
- this.#filterAndMount(elsToInspect, within, true, false);
285
- for (const el of elsToInspect) {
286
- await this.#inspectWithin(el, false);
287
- }
288
- }, { signal: this.#abortController.signal });
289
- await this.#inspectWithin(within, true);
87
+ });
88
+ const observerConfig = {
89
+ childList: true,
90
+ subtree: true
91
+ };
92
+ // Add attribute observation if whereAttr is configured
93
+ if (this.#init.whereAttr) {
94
+ observerConfig.attributes = true;
95
+ observerConfig.attributeOldValue = true;
96
+ }
97
+ this.#mutationObserver.observe(rootNode, observerConfig);
290
98
  }
291
- static synthesize(within, customElement, mose) {
292
- //TODO: make external
293
- mose.type = 'mountobserver';
294
- const name = customElements.getName(customElement);
295
- if (name === null)
296
- throw 400;
297
- let instance = within.querySelector(name);
298
- if (instance === null) {
299
- instance = new customElement();
300
- if (within === document) {
301
- within.head.appendChild(instance);
302
- }
303
- else {
304
- within.appendChild(instance);
305
- }
99
+ disconnect() {
100
+ if (this.#mutationObserver) {
101
+ this.#mutationObserver.disconnect();
102
+ this.#mutationObserver = undefined;
306
103
  }
307
- instance.appendChild(mose);
104
+ this.#abortController.abort();
105
+ this.#rootNode = undefined;
308
106
  }
309
- #confirmInstanceOf(el, whereInstanceOf) {
310
- for (const test of whereInstanceOf) {
311
- if (el instanceof test)
312
- return true;
107
+ async #loadImports() {
108
+ if (this.#importsLoaded || !this.#init.import) {
109
+ return;
313
110
  }
314
- return false;
111
+ // Dynamically load the import utilities only when needed
112
+ const { loadImports } = await import('./loadImports.js');
113
+ this.#modules = await loadImports(this.#init.import);
114
+ this.#importsLoaded = true;
115
+ this.dispatchEvent(new LoadEvent(this.#modules));
315
116
  }
316
- async #mount(matching, initializing) {
317
- //first unmount non matching
318
- const alreadyMounted = await this.#filterAndDismount();
319
- const mount = this.#mountInit.do?.mount;
320
- const { import: imp } = this.#mountInit;
321
- const me = this.mountedElements;
322
- const options = this.#options;
323
- for (const match of matching) {
324
- if (alreadyMounted.has(match))
325
- continue;
326
- if (!me.weakSet.has(match)) {
327
- me.setWeak.add(new WeakRef(match));
328
- me.weakSet.add(match);
329
- }
330
- if (imp !== undefined) {
331
- switch (typeof imp) {
332
- case 'string':
333
- this.module = await import(imp);
334
- break;
335
- case 'object':
336
- if (Array.isArray(imp)) {
337
- throw 'NI: Firefox';
338
- }
339
- break;
340
- case 'function':
341
- this.module = await imp(match, this, {
342
- stage: 'Import',
343
- initializing
344
- });
345
- break;
346
- }
347
- }
348
- if (mount !== undefined) {
349
- mount(match, this, {
350
- stage: 'PostImport',
351
- initializing
352
- });
117
+ #processNode(node) {
118
+ // If it's an element node, check if it matches
119
+ if (node.nodeType === Node.ELEMENT_NODE) {
120
+ const element = node;
121
+ if (this.#matchesSelector(element)) {
122
+ this.#handleMatch(element);
353
123
  }
354
- if (options?.leaveBreadcrumb) {
355
- if (match[guid] === undefined) {
356
- match[guid] = new Set();
357
- }
358
- match[guid].add(this);
359
- }
360
- const { MountEvent } = await import('./Events.js');
361
- this.dispatchEvent(new MountEvent(match, initializing));
362
- //should we automatically call readAttrs?
363
- //the thinking is it might make more sense to call that after mounting
364
- this.#mountedList?.push(new WeakRef(match));
365
124
  }
366
- }
367
- readAttrs(match, branchIndexes) {
368
- //TODO: externalize
369
- const fullListOfAttrs = this.#fullListOfEnhancementAttrs;
370
- const attrChangeInfos = [];
371
- const oldValue = null;
372
- if (fullListOfAttrs !== undefined) {
373
- const attrParts = this.#attrParts;
374
- for (let idx = 0, ii = fullListOfAttrs.length; idx < ii; idx++) {
375
- const parts = attrParts[idx];
376
- const { branchIdx } = parts;
377
- if (branchIndexes !== undefined) {
378
- if (!branchIndexes.has(branchIdx))
379
- continue;
380
- }
381
- const name = fullListOfAttrs[idx];
382
- const newValue = match.getAttribute(name);
383
- attrChangeInfos.push({
384
- idx,
385
- isSOfTAttr: false,
386
- newValue,
387
- oldValue,
388
- name,
389
- parts
125
+ // Process children
126
+ if (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.DOCUMENT_NODE) {
127
+ const root = node;
128
+ // If whereAttr is specified, we need to check all elements
129
+ // since we can't use querySelectorAll for complex attribute matching
130
+ if (this.#init.whereAttr) {
131
+ // Get all elements matching the CSS selector first
132
+ root.querySelectorAll(this.#init.whereElementMatches).forEach(child => {
133
+ if (this.#matchesSelector(child)) {
134
+ this.#handleMatch(child);
135
+ }
390
136
  });
391
137
  }
392
- }
393
- const { observedAttrsWhenMounted } = this.#mountInit;
394
- if (observedAttrsWhenMounted !== undefined) {
395
- for (const observedAttr of observedAttrsWhenMounted) {
396
- const attrIsString = typeof observedAttr === 'string';
397
- const name = attrIsString ? observedAttr : observedAttr.name;
398
- let mapsTo;
399
- let newValue = match.getAttribute(name);
400
- if (!attrIsString) {
401
- const { customParser, instanceOf, mapsTo: mt, valIfNull } = observedAttr;
402
- if (instanceOf || customParser)
403
- throw 'NI';
404
- if (newValue === null)
405
- newValue = valIfNull;
406
- mapsTo = mt;
407
- }
408
- attrChangeInfos.push({
409
- isSOfTAttr: true,
410
- newValue,
411
- oldValue,
412
- name,
413
- mapsTo
138
+ else {
139
+ // Optimize: use querySelectorAll directly when no whereAttr
140
+ root.querySelectorAll(this.#init.whereElementMatches).forEach(child => {
141
+ this.#handleMatch(child);
414
142
  });
415
143
  }
416
144
  }
417
- return attrChangeInfos;
418
145
  }
419
- async #dismount(unmatching) {
420
- const onDismount = this.#mountInit.do?.dismount;
421
- const { DismountEvent } = await import('./Events.js');
422
- for (const unmatch of unmatching) {
423
- if (onDismount !== undefined) {
424
- onDismount(unmatch, this, {});
425
- }
426
- this.dispatchEvent(new DismountEvent(unmatch));
146
+ #matchesSelector(element) {
147
+ // Check whereElementMatches condition
148
+ const matchesElement = element.matches(this.#init.whereElementMatches);
149
+ // If whereAttr is not specified, only check whereElementMatches
150
+ if (!this.#init.whereAttr) {
151
+ return matchesElement;
152
+ }
153
+ // Use cached function (should be loaded by now from constructor)
154
+ if (!this.#matchesWhereAttrFn) {
155
+ console.warn('whereAttr utilities not loaded yet');
156
+ return false;
427
157
  }
158
+ // Both conditions must be true (AND logic)
159
+ return matchesElement && this.#matchesWhereAttrFn(element, this.#init.whereAttr);
428
160
  }
429
- async #dismountAll() {
430
- const mounted = this.#mountedList;
431
- if (mounted === undefined)
161
+ async #handleMatch(element) {
162
+ if (this.#processedElements.has(element)) {
432
163
  return;
433
- this.#dismount(mounted.map(x => x.deref()).filter(x => x !== undefined));
434
- }
435
- async #mountAll() {
436
- //TODO: copilot created, check if needed
437
- const { whereSatisfies, whereInstanceOf } = this.#mountInit;
438
- const match = await this.#selector();
439
- const els = Array.from(document.querySelectorAll(match));
440
- this.#filterAndMount(els, document.body, false, true);
441
- }
442
- async #filterAndDismount() {
443
- const returnSet = new Set();
444
- if (this.#mountedList !== undefined) {
445
- const previouslyMounted = this.#mountedList.map(x => x.deref());
446
- const { whereSatisfies, whereInstanceOf } = this.#mountInit;
447
- const match = await this.#selector();
448
- const elsToUnMount = previouslyMounted.filter(x => {
449
- if (x === undefined)
450
- return false;
451
- if (!x.matches(match))
452
- return true;
453
- //TODO: add check for outside
454
- if (whereSatisfies !== undefined) {
455
- if (!whereSatisfies(x, this, { stage: 'Inspecting', initializing: false }))
456
- return true;
457
- }
458
- returnSet.add(x);
459
- return false;
460
- });
461
- this.#dismount(elsToUnMount);
462
164
  }
463
- this.#mountedList = Array.from(returnSet).map(x => new WeakRef(x));
464
- return returnSet;
465
- }
466
- #outsideCheck(oElement, matchCandidate, outside) {
467
- const elementsToExclude = Array.from(oElement.querySelectorAll(outside));
468
- for (const elementToExclude of elementsToExclude) {
469
- if (elementToExclude === matchCandidate || elementToExclude.contains(matchCandidate))
470
- return false;
165
+ // Load imports if not already loaded
166
+ if (!this.#importsLoaded && this.#init.import) {
167
+ await this.#loadImports();
471
168
  }
472
- return true;
473
- }
474
- async #filterAndMount(els, target, checkMatch, initializing) {
475
- const { whereSatisfies, whereInstanceOf, assigner, outside } = this.#mountInit;
476
- const match = await this.#selector();
477
- const elsToMount = els.filter(x => {
478
- if (checkMatch) {
479
- if (!x.matches(match))
480
- return false;
481
- //TODO: check for outside
482
- }
483
- if (outside !== undefined) {
484
- if (!this.#outsideCheck(this.objNde.deref(), x, outside))
485
- return false;
169
+ this.#processedElements.add(element);
170
+ this.#mountedElements.add(element);
171
+ const rootNode = this.#rootNode?.deref();
172
+ if (!rootNode) {
173
+ // Root node was garbage collected
174
+ return;
175
+ }
176
+ const context = {
177
+ modules: this.#modules,
178
+ observer: this,
179
+ observeInfo: {
180
+ rootNode
486
181
  }
487
- if (whereSatisfies !== undefined) {
488
- if (!whereSatisfies(x, this, { stage: 'Inspecting', initializing }))
489
- return false;
182
+ };
183
+ // Apply assignGingerly if specified
184
+ if (this.#init.assignGingerly) {
185
+ const { assignGingerly } = await import('assign-gingerly/index.js');
186
+ assignGingerly(element, this.#init.assignGingerly);
187
+ }
188
+ // Call do callback
189
+ if (this.#init.do) {
190
+ if (typeof this.#init.do === 'function') {
191
+ this.#init.do(element, context);
490
192
  }
491
- if (whereInstanceOf !== undefined) {
492
- if (!this.#confirmInstanceOf(x, whereInstanceOf))
493
- return false;
193
+ else if (this.#init.do.mount) {
194
+ this.#init.do.mount(element, context);
494
195
  }
495
- return true;
496
- });
497
- for (const elToMount of elsToMount) {
498
- if (elToMount.matches(inclTemplQry)) {
499
- if (elToMount instanceof HTMLTemplateElement && elToMount.getAttribute('rel') === 'preload') {
500
- (await import('./preloadContent.js')).preloadContent(elToMount /*, this.#mountInit.withTargetShadowRoot*/);
501
- }
502
- else {
503
- await this.#compose(elToMount, 0);
504
- }
196
+ }
197
+ // Dispatch mount event
198
+ this.dispatchEvent(new MountEvent(element, this.#modules));
199
+ // Check for initial attribute changes if whereAttr is configured
200
+ if (this.#init.whereAttr) {
201
+ const changes = this.#checkAttrChanges(element);
202
+ if (changes.length > 0) {
203
+ this.dispatchEvent(new AttrChangeEvent(changes));
505
204
  }
506
205
  }
507
- await bindishIt(els, target, { assigner });
508
- if (elsToMount.length === 0)
509
- return;
510
- this.#mount(elsToMount, initializing);
511
206
  }
512
- async #inspectWithin(within, initializing) {
513
- //the line below had an await for bindish, consistent with the rest of the code, but it was
514
- //getting into a catch-22 scenario frequently, blocking the code for resuming.
515
- //This was observed with per-each package, demo/ScopeScript.html, clicking refresh a few times
516
- //one will see the inconsistent behavior if await is added below.
517
- const idGenerators = Array.from(within.querySelectorAll('[-id]'));
518
- if (idGenerators[0]) {
519
- const { genIds } = await import('./refid/genIds.js');
520
- for (const el of idGenerators) {
521
- genIds(el);
522
- el.removeAttribute('-id');
207
+ #checkAttrChanges(element) {
208
+ if (!this.#init.whereAttr || !this.#buildAttrCoordinateMapFn) {
209
+ return [];
210
+ }
211
+ const isCustomElement = element.tagName.toLowerCase().includes('-');
212
+ const attrCoordMap = this.#buildAttrCoordinateMapFn(this.#init.whereAttr, isCustomElement);
213
+ // Get or create the attribute state for this element
214
+ let attrState = this.#elementAttrStates.get(element);
215
+ if (!attrState) {
216
+ attrState = new Map();
217
+ this.#elementAttrStates.set(element, attrState);
218
+ }
219
+ const changes = [];
220
+ const currentAttrs = new Set();
221
+ // Check all possible attributes from the coordinate map
222
+ for (const attrName of Object.keys(attrCoordMap)) {
223
+ const coordinate = attrCoordMap[attrName];
224
+ const currentValue = element.getAttribute(attrName);
225
+ const previousValue = attrState.get(attrName);
226
+ if (currentValue !== null) {
227
+ currentAttrs.add(attrName);
228
+ }
229
+ // Include if: currently has value OR previously had value but now removed
230
+ if (currentValue !== null || (previousValue !== undefined && currentValue === null)) {
231
+ // Check if value changed
232
+ if (currentValue !== previousValue) {
233
+ const attrNode = currentValue !== null ? element.getAttributeNode(attrName) : null;
234
+ const mapEntry = this.#init.map?.[coordinate] || null;
235
+ changes.push({
236
+ value: currentValue,
237
+ attrNode,
238
+ mapEntry,
239
+ attrName,
240
+ coordinate,
241
+ element
242
+ });
243
+ // Update state
244
+ if (currentValue !== null) {
245
+ attrState.set(attrName, currentValue);
246
+ }
247
+ else {
248
+ attrState.delete(attrName);
249
+ }
250
+ }
523
251
  }
524
252
  }
525
- bindish(within, within, { assigner: this.#mountInit.assigner });
526
- await this.composeFragment(within, 0);
527
- const match = await this.#selector();
528
- const els = Array.from(within.querySelectorAll(match));
529
- this.#filterAndMount(els, within, false, initializing);
253
+ return changes;
530
254
  }
531
- }
532
- //ToDO: make external
533
- export function waitForIdleNodes(nodes, idleTimeout) {
534
- const mountInit = {
535
- idleTimeout
536
- };
537
- return new Promise((resolve) => {
538
- const mutObservers = [];
539
- for (const node of nodes) {
540
- const mutObs = mutationObserverLookup.get(node);
541
- if (mutObs !== undefined) {
542
- mutObservers.push(mutObs);
543
- }
544
- else {
545
- const currentCount = refCount.get(node) || 0;
546
- const newMutObs = new RootMutObs(node, mountInit);
547
- mutationObserverLookup.set(node, newMutObs);
548
- refCount.set(node, currentCount + 1);
549
- mutObservers.push(newMutObs);
550
- }
255
+ #handleRemoval(element) {
256
+ if (!this.#mountedElements.has(element)) {
257
+ return;
551
258
  }
552
- if (areAllIdle(mutObservers)) {
553
- resolve();
259
+ this.#mountedElements.delete(element);
260
+ const rootNode = this.#rootNode?.deref();
261
+ if (!rootNode) {
262
+ // Root node was garbage collected
263
+ return;
554
264
  }
555
- for (const obs of mutObservers) {
556
- obs.addEventListener('is-idle', () => {
557
- if (areAllIdle(mutObservers)) {
558
- resolve();
559
- }
560
- });
265
+ const context = {
266
+ modules: this.#modules,
267
+ observer: this,
268
+ observeInfo: {
269
+ rootNode
270
+ }
271
+ };
272
+ // Call dismount callback
273
+ if (this.#init.do && typeof this.#init.do !== 'function' && this.#init.do.dismount) {
274
+ this.#init.do.dismount(element, context);
561
275
  }
562
- });
563
- }
564
- //make external
565
- function areAllIdle(mutObs) {
566
- for (const mo of mutObs) {
567
- if (!mo.isIdle)
568
- return false;
276
+ // Dispatch dismount event
277
+ this.dispatchEvent(new DismountEvent(element));
278
+ // Check if element is being moved within the same root
279
+ // If it's truly disconnected, dispatch disconnect event
280
+ setTimeout(() => {
281
+ if (!rootNode.contains(element)) {
282
+ if (this.#init.do && typeof this.#init.do !== 'function' && this.#init.do.disconnect) {
283
+ this.#init.do.disconnect(element, context);
284
+ }
285
+ this.dispatchEvent(new DisconnectEvent(element));
286
+ }
287
+ }, 0);
569
288
  }
570
- return true;
571
289
  }
572
- const refCountErr = 'mount-observer ref count mismatch';
573
- export const inclTemplQry = 'template[src^="#"]:not([hidden]),template[src^="!"]:not([hidden])';
574
- //const hasRootInDefault = ['data', 'enh', 'data-enh']