mount-observer 0.0.112 → 0.1.1

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 (181) hide show
  1. package/Events.js +62 -26
  2. package/Events.ts +52 -30
  3. package/MountObserver.js +285 -514
  4. package/MountObserver.ts +362 -538
  5. package/README.md +149 -56
  6. package/SharedMutationObserver.js +70 -0
  7. package/SharedMutationObserver.ts +96 -0
  8. package/attrCoordinates.js +93 -0
  9. package/attrCoordinates.ts +122 -0
  10. package/index.js +3 -0
  11. package/index.ts +22 -0
  12. package/loadImports.js +47 -0
  13. package/loadImports.ts +56 -0
  14. package/mediaQuery.js +86 -0
  15. package/mediaQuery.ts +113 -0
  16. package/package.json +11 -119
  17. package/playwright.config.ts +0 -1
  18. package/types.d.ts +104 -0
  19. package/whereAttr.js +174 -0
  20. package/whereAttr.ts +221 -0
  21. package/LICENSE +0 -21
  22. package/Newish.js +0 -145
  23. package/Newish.ts +0 -169
  24. package/ObsAttr.js +0 -18
  25. package/ObsAttr.ts +0 -18
  26. package/RootMutObs.js +0 -49
  27. package/RootMutObs.ts +0 -58
  28. package/Synthesizer.js +0 -125
  29. package/Synthesizer.ts +0 -130
  30. package/bindish.js +0 -15
  31. package/bindish.ts +0 -22
  32. package/compose.js +0 -148
  33. package/compose.ts +0 -164
  34. package/doCleanup.js +0 -31
  35. package/doCleanup.ts +0 -34
  36. package/getWhereAttrSelector.js +0 -83
  37. package/getWhereAttrSelector.ts +0 -92
  38. package/preloadContent.js +0 -44
  39. package/preloadContent.ts +0 -47
  40. package/readAttrs.ts +0 -60
  41. package/refid/README.md +0 -259
  42. package/refid/arr.js +0 -4
  43. package/refid/arr.ts +0 -4
  44. package/refid/camelToKebab.js +0 -4
  45. package/refid/camelToKebab.ts +0 -4
  46. package/refid/genIds.js +0 -190
  47. package/refid/genIds.ts +0 -177
  48. package/refid/getAdjRefs.js +0 -38
  49. package/refid/getAdjRefs.ts +0 -38
  50. package/refid/getContext.js +0 -13
  51. package/refid/getContext.ts +0 -14
  52. package/refid/getCount.js +0 -8
  53. package/refid/getCount.ts +0 -8
  54. package/refid/getIsh.js +0 -35
  55. package/refid/getIsh.ts +0 -37
  56. package/refid/hostish.js +0 -18
  57. package/refid/hostish.ts +0 -20
  58. package/refid/ism.js +0 -78
  59. package/refid/ism.ts +0 -81
  60. package/refid/itemprops.js +0 -60
  61. package/refid/itemprops.ts +0 -67
  62. package/refid/joinMatching.js +0 -56
  63. package/refid/joinMatching.ts +0 -54
  64. package/refid/nudge.js +0 -23
  65. package/refid/nudge.ts +0 -23
  66. package/refid/regIsh.js +0 -27
  67. package/refid/regIsh.ts +0 -31
  68. package/refid/secretKeys.js +0 -5
  69. package/refid/secretKeys.ts +0 -5
  70. package/refid/splitRefs.js +0 -6
  71. package/refid/splitRefs.ts +0 -6
  72. package/refid/stdVal.js +0 -15
  73. package/refid/stdVal.ts +0 -15
  74. package/refid/via.js +0 -114
  75. package/refid/via.ts +0 -113
  76. package/slotkin/affine.js +0 -39
  77. package/slotkin/affine.ts +0 -46
  78. package/slotkin/beKindred.js +0 -45
  79. package/slotkin/beKindred.ts +0 -55
  80. package/slotkin/getBreadth.js +0 -19
  81. package/slotkin/getBreadth.ts +0 -21
  82. package/slotkin/getFrag.js +0 -22
  83. package/slotkin/getFrag.ts +0 -21
  84. package/slotkin/toQuery.js +0 -12
  85. package/slotkin/toQuery.ts +0 -13
  86. package/slotkin/wrap.js +0 -13
  87. package/slotkin/wrap.ts +0 -18
  88. package/ts-refs/LICENSE +0 -21
  89. package/ts-refs/README.md +0 -18
  90. package/ts-refs/be-a-beacon/types.d.ts +0 -22
  91. package/ts-refs/be-alit/types.d.ts +0 -1
  92. package/ts-refs/be-based/types.d.ts +0 -32
  93. package/ts-refs/be-bound/types.d.ts +0 -65
  94. package/ts-refs/be-buttoned-up/types.d.ts +0 -21
  95. package/ts-refs/be-calculating/types.d.ts +0 -57
  96. package/ts-refs/be-clonable/types.d.ts +0 -28
  97. package/ts-refs/be-committed/types.d.ts +0 -26
  98. package/ts-refs/be-consoling/types.d.ts +0 -25
  99. package/ts-refs/be-counted/types.d.ts +0 -88
  100. package/ts-refs/be-delible/types.d.ts +0 -26
  101. package/ts-refs/be-directive/types.d.ts +0 -43
  102. package/ts-refs/be-dispatching/types.d.ts +0 -41
  103. package/ts-refs/be-elevating/types.d.ts +0 -55
  104. package/ts-refs/be-enhanced/types.d.ts +0 -32
  105. package/ts-refs/be-enhancing/types.d.ts +0 -31
  106. package/ts-refs/be-evanescent/types.d.ts +0 -20
  107. package/ts-refs/be-eventing/types.d.ts +0 -27
  108. package/ts-refs/be-exportable/types.d.ts +0 -26
  109. package/ts-refs/be-fetching/types.d.ts +0 -73
  110. package/ts-refs/be-flashy/types.d.ts +0 -27
  111. package/ts-refs/be-formalizing/types.d.ts +0 -29
  112. package/ts-refs/be-formidable/types.d.ts +0 -64
  113. package/ts-refs/be-giddy/types.d.ts +0 -26
  114. package/ts-refs/be-gingerly/types.d.ts +0 -19
  115. package/ts-refs/be-gone/types.d.ts +0 -24
  116. package/ts-refs/be-hashing-out/types.d.ts +0 -22
  117. package/ts-refs/be-hive/types.d.ts +0 -18
  118. package/ts-refs/be-imbued/types.d.ts +0 -30
  119. package/ts-refs/be-included/types.d.ts +0 -20
  120. package/ts-refs/be-inclusive/types.d.ts +0 -30
  121. package/ts-refs/be-intersectional/types.d.ts +0 -37
  122. package/ts-refs/be-intl/types.d.ts +0 -28
  123. package/ts-refs/be-invoking/types.d.ts +0 -28
  124. package/ts-refs/be-joining/types.d.ts +0 -26
  125. package/ts-refs/be-kvetching/types.d.ts +0 -24
  126. package/ts-refs/be-lazy/types.d.ts +0 -29
  127. package/ts-refs/be-literate/types.d.ts +0 -29
  128. package/ts-refs/be-mediating/types.d.ts +0 -34
  129. package/ts-refs/be-methodical/types.d.ts +0 -20
  130. package/ts-refs/be-modding/types.d.ts +0 -18
  131. package/ts-refs/be-observant/types.d.ts +0 -27
  132. package/ts-refs/be-observing/types.d.ts +0 -84
  133. package/ts-refs/be-parsed/types.d.ts +0 -19
  134. package/ts-refs/be-parsing/types.d.ts +0 -37
  135. package/ts-refs/be-persistent/types.d.ts +0 -66
  136. package/ts-refs/be-propagating/types.d.ts +0 -26
  137. package/ts-refs/be-reformable/types.d.ts +0 -48
  138. package/ts-refs/be-render-neutral/types.d.ts +0 -31
  139. package/ts-refs/be-scoped/types.d.ts +0 -24
  140. package/ts-refs/be-sharing/types.d.ts +0 -17
  141. package/ts-refs/be-switched/types.d.ts +0 -155
  142. package/ts-refs/be-typed/types.d.ts +0 -36
  143. package/ts-refs/be-value-added/types.d.ts +0 -34
  144. package/ts-refs/be-valued/types.d.ts +0 -22
  145. package/ts-refs/be-written/types.d.ts +0 -59
  146. package/ts-refs/css-charts/types.d.ts +0 -38
  147. package/ts-refs/css-echarts/types.d.ts +0 -13
  148. package/ts-refs/data-props/types.d.ts +0 -27
  149. package/ts-refs/do-inc/types.d.ts +0 -28
  150. package/ts-refs/do-invoke/types.d.ts +0 -28
  151. package/ts-refs/do-toggle/types.d.ts +0 -27
  152. package/ts-refs/em-bower/types.d.ts +0 -24
  153. package/ts-refs/fetch-for/types.d.ts +0 -37
  154. package/ts-refs/folder-picker/types.d.ts +0 -43
  155. package/ts-refs/for-fetch/doc.d.ts +0 -98
  156. package/ts-refs/for-fetch/types.d.ts +0 -83
  157. package/ts-refs/mount-observer/types.d.ts +0 -248
  158. package/ts-refs/mt-si/types.d.ts +0 -21
  159. package/ts-refs/per-each/types.d.ts +0 -51
  160. package/ts-refs/soak-up/types.d.ts +0 -36
  161. package/ts-refs/trans-render/XV/types.d.ts +0 -69
  162. package/ts-refs/trans-render/asmr/types.d.ts +0 -138
  163. package/ts-refs/trans-render/be/types.d.ts +0 -198
  164. package/ts-refs/trans-render/dss/types.d.ts +0 -57
  165. package/ts-refs/trans-render/froop/types.d.ts +0 -416
  166. package/ts-refs/trans-render/funions/types.d.ts +0 -12
  167. package/ts-refs/trans-render/lib/mixins/types.d.ts +0 -42
  168. package/ts-refs/trans-render/lib/prs/types.d.ts +0 -40
  169. package/ts-refs/trans-render/lib/types.d.ts +0 -489
  170. package/ts-refs/trans-render/types.d.ts +0 -583
  171. package/ts-refs/wc-info/SimpleWCInfo.d.ts +0 -15
  172. package/ts-refs/when-resolved/types.d.ts +0 -30
  173. package/ts-refs/xp-as/types.d.ts +0 -20
  174. package/ts-refs/xtal-element/types.d.ts +0 -43
  175. package/ts-refs/xtal-frappe-chart/types.d.ts +0 -193
  176. package/upShadowSearch.js +0 -25
  177. package/upShadowSearch.ts +0 -23
  178. package/waitForEvent.js +0 -12
  179. package/waitForEvent.ts +0 -13
  180. package/waitForIsh.js +0 -21
  181. package/waitForIsh.ts +0 -20
package/MountObserver.js CHANGED
@@ -1,574 +1,345 @@
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';
2
+ import { registerSharedObserver, unregisterSharedObserver } from './SharedMutationObserver.js';
8
3
  export class MountObserver extends EventTarget {
9
- #mountInit;
4
+ #init;
10
5
  #options;
11
- //#rootMutObs: RootMutObs | undefined;
12
6
  #abortController;
13
- mountedElements;
14
- #mountedList;
15
- #disconnected;
16
- //#unmounted: WeakSet<Element>;
17
- #isComplex;
18
- objNde;
19
- constructor(init) {
7
+ #modules = [];
8
+ #mountedElements = new WeakSet();
9
+ #processedElements = new WeakSet();
10
+ #mutationCallback;
11
+ #rootNode;
12
+ #importsLoaded = false;
13
+ #elementAttrStates = new WeakMap();
14
+ #elementOnceAttrs = new WeakMap();
15
+ #matchesWhereAttrFn = null;
16
+ #buildAttrCoordinateMapFn = null;
17
+ #mediaQueryCleanup;
18
+ #mediaMatches = true;
19
+ constructor(init, options = {}) {
20
20
  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;
21
+ this.#init = init;
22
+ this.#options = options;
32
23
  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
- }
24
+ if (options.disconnectedSignal) {
25
+ options.disconnectedSignal.addEventListener('abort', () => {
26
+ this.disconnect();
27
+ });
73
28
  }
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;
29
+ // Preload whereAttr utilities if needed
30
+ if (init.whereAttr) {
31
+ this.#preloadWhereAttrUtilities();
94
32
  }
95
- catch (e) {
96
- return null;
33
+ // Start loading imports if eager
34
+ if (init.loadingEagerness === 'eager' && init.import) {
35
+ this.#loadImports();
97
36
  }
98
37
  }
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
- }
38
+ async #preloadWhereAttrUtilities() {
39
+ if (!this.#matchesWhereAttrFn) {
40
+ const { matchesWhereAttr } = await import('./whereAttr.js');
41
+ this.#matchesWhereAttrFn = matchesWhereAttr;
113
42
  }
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);
43
+ if (!this.#buildAttrCoordinateMapFn) {
44
+ const { buildAttrCoordinateMap } = await import('./attrCoordinates.js');
45
+ this.#buildAttrCoordinateMapFn = buildAttrCoordinateMap;
148
46
  }
149
- return templ;
150
47
  }
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
- }
48
+ async #setupMediaQuery() {
49
+ if (!this.#rootNode) {
50
+ throw new Error('Cannot setup media query before observe() is called');
174
51
  }
175
- this.dispatchEvent(new Event('disconnectedCallback'));
52
+ const { setupMediaQuery } = await import('./mediaQuery.js');
53
+ const result = setupMediaQuery(this.#init, this.#rootNode, this.#mountedElements, this.#modules, this, (node) => this.#processNode(node));
54
+ this.#mediaMatches = result.mediaMatches;
55
+ this.#mediaQueryCleanup = result.cleanup;
176
56
  }
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;
57
+ get disconnectedSignal() {
58
+ return this.#abortController.signal;
59
+ }
60
+ async observe(rootNode) {
61
+ if (this.#rootNode) {
62
+ throw new Error('Already observing');
184
63
  }
185
- const mql = window.matchMedia(whereMediaMatches);
186
- if (mql.matches) {
187
- await this.#observe2(within);
64
+ this.#rootNode = new WeakRef(rootNode);
65
+ // Set up media query if specified (needs rootNode to be set first)
66
+ if (this.#init.whereMediaMatches) {
67
+ await this.#setupMediaQuery();
188
68
  }
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
- });
204
- }
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);
69
+ // Wait for whereAttr utilities to load if needed
70
+ if (this.#init.whereAttr && !this.#matchesWhereAttrFn) {
71
+ await this.#preloadWhereAttrUtilities();
212
72
  }
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
- }
73
+ // Process existing elements only if media matches
74
+ if (this.#mediaMatches) {
75
+ this.#processNode(rootNode);
221
76
  }
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);
77
+ // Create mutation callback
78
+ this.#mutationCallback = (mutations) => {
79
+ // Skip processing if media doesn't match
80
+ if (!this.#mediaMatches) {
228
81
  return;
229
82
  }
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
- }
83
+ const attrChanges = [];
84
+ for (const mutation of mutations) {
85
+ if (mutation.type === 'childList') {
86
+ for (const node of mutation.addedNodes) {
87
+ if (node.nodeType === Node.ELEMENT_NODE) {
88
+ this.#processNode(node);
264
89
  }
265
90
  }
266
- elsToInspect.push(target);
91
+ mutation.removedNodes.forEach(node => {
92
+ if (node.nodeType === Node.ELEMENT_NODE) {
93
+ this.#handleRemoval(node);
94
+ }
95
+ });
267
96
  }
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, {});
97
+ else if (mutation.type === 'attributes' && mutation.target.nodeType === Node.ELEMENT_NODE) {
98
+ // Handle attribute changes for mounted elements
99
+ const element = mutation.target;
100
+ if (this.#mountedElements.has(element) && this.#init.whereAttr) {
101
+ const changes = this.#checkAttrChanges(element);
102
+ attrChanges.push(...changes);
274
103
  }
275
- this.dispatchEvent(new DisconnectEvent(deletedElement));
276
104
  }
277
105
  }
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
- }
283
- }
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);
290
- }
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);
106
+ // Batch and dispatch attribute changes
107
+ if (attrChanges.length > 0) {
108
+ this.dispatchEvent(new AttrChangeEvent(attrChanges, this.#init));
305
109
  }
110
+ };
111
+ const observerConfig = {
112
+ childList: true,
113
+ subtree: true
114
+ };
115
+ // Add attribute observation if whereAttr is configured
116
+ if (this.#init.whereAttr) {
117
+ observerConfig.attributes = true;
118
+ observerConfig.attributeOldValue = true;
306
119
  }
307
- instance.appendChild(mose);
120
+ // Register with shared mutation observer
121
+ registerSharedObserver(rootNode, this.#mutationCallback, observerConfig);
308
122
  }
309
- #confirmInstanceOf(el, whereInstanceOf) {
310
- for (const test of whereInstanceOf) {
311
- if (el instanceof test)
312
- return true;
123
+ disconnect() {
124
+ const rootNode = this.#rootNode?.deref();
125
+ // Unregister from shared mutation observer
126
+ if (rootNode && this.#mutationCallback) {
127
+ unregisterSharedObserver(rootNode, this.#mutationCallback);
128
+ this.#mutationCallback = undefined;
313
129
  }
314
- return false;
130
+ // Remove media query listener
131
+ if (this.#mediaQueryCleanup) {
132
+ this.#mediaQueryCleanup();
133
+ this.#mediaQueryCleanup = undefined;
134
+ }
135
+ this.#abortController.abort();
136
+ this.#rootNode = undefined;
315
137
  }
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
- });
353
- }
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));
138
+ async #loadImports() {
139
+ if (this.#importsLoaded || !this.#init.import) {
140
+ return;
365
141
  }
142
+ // Dynamically load the import utilities only when needed
143
+ const { loadImports } = await import('./loadImports.js');
144
+ this.#modules = await loadImports(this.#init.import);
145
+ this.#importsLoaded = true;
146
+ this.dispatchEvent(new LoadEvent(this.#modules, this.#init));
366
147
  }
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
390
- });
148
+ #processNode(node) {
149
+ // If it's an element node, check if it matches
150
+ if (node.nodeType === Node.ELEMENT_NODE) {
151
+ const element = node;
152
+ if (this.#matchesSelector(element)) {
153
+ this.#handleMatch(element);
391
154
  }
392
155
  }
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;
156
+ // Process children
157
+ if (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.DOCUMENT_NODE) {
158
+ const root = node;
159
+ // Get all elements matching the CSS selector first
160
+ root.querySelectorAll(this.#init.whereElementMatches).forEach(child => {
161
+ if (this.#matchesSelector(child)) {
162
+ this.#handleMatch(child);
407
163
  }
408
- attrChangeInfos.push({
409
- isSOfTAttr: true,
410
- newValue,
411
- oldValue,
412
- name,
413
- mapsTo
414
- });
415
- }
164
+ });
416
165
  }
417
- return attrChangeInfos;
418
166
  }
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));
167
+ #matchesSelector(element) {
168
+ //TODO: reduce redundncy with this.#init?
169
+ // Check whereElementMatches condition
170
+ const matchesElement = element.matches(this.#init.whereElementMatches);
171
+ if (!matchesElement) {
172
+ return false;
427
173
  }
428
- }
429
- async #dismountAll() {
430
- const mounted = this.#mountedList;
431
- if (mounted === undefined)
432
- 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);
174
+ // Check whereAttr condition if specified
175
+ if (this.#init.whereAttr) {
176
+ // Use cached function (should be loaded by now from constructor)
177
+ if (!this.#matchesWhereAttrFn) {
178
+ console.warn('whereAttr utilities not loaded yet');
459
179
  return false;
460
- });
461
- this.#dismount(elsToUnMount);
180
+ }
181
+ if (!this.#matchesWhereAttrFn(element, this.#init.whereAttr)) {
182
+ return false;
183
+ }
462
184
  }
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))
185
+ // Check whereInstanceOf condition if specified
186
+ if (this.#init.whereInstanceOf) {
187
+ const constructors = Array.isArray(this.#init.whereInstanceOf)
188
+ ? this.#init.whereInstanceOf
189
+ : [this.#init.whereInstanceOf];
190
+ // Element must be an instance of at least one constructor (OR logic for array)
191
+ const matchesInstanceOf = constructors.some(constructor => element instanceof constructor);
192
+ if (!matchesInstanceOf) {
470
193
  return false;
194
+ }
471
195
  }
196
+ // All conditions passed
472
197
  return true;
473
198
  }
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
199
+ async #handleMatch(element) {
200
+ if (this.#processedElements.has(element)) {
201
+ return;
202
+ }
203
+ // Load imports if not already loaded
204
+ if (!this.#importsLoaded && this.#init.import) {
205
+ await this.#loadImports();
206
+ }
207
+ this.#processedElements.add(element);
208
+ this.#mountedElements.add(element);
209
+ const rootNode = this.#rootNode?.deref();
210
+ if (!rootNode) {
211
+ // Root node was garbage collected
212
+ return;
213
+ }
214
+ const context = {
215
+ modules: this.#modules,
216
+ observer: this,
217
+ observeInfo: {
218
+ rootNode
482
219
  }
483
- if (outside !== undefined) {
484
- if (!this.#outsideCheck(this.objNde.deref(), x, outside))
485
- return false;
220
+ };
221
+ // Apply assignGingerly if specified
222
+ if (this.#init.assignGingerly) {
223
+ const { assignGingerly } = await import('assign-gingerly/index.js');
224
+ assignGingerly(element, this.#init.assignGingerly);
225
+ }
226
+ // Call do callback
227
+ if (this.#init.do) {
228
+ if (typeof this.#init.do === 'function') {
229
+ this.#init.do(element, context);
486
230
  }
487
- if (whereSatisfies !== undefined) {
488
- if (!whereSatisfies(x, this, { stage: 'Inspecting', initializing }))
489
- return false;
231
+ else if (this.#init.do.mount) {
232
+ this.#init.do.mount(element, context);
490
233
  }
491
- if (whereInstanceOf !== undefined) {
492
- if (!this.#confirmInstanceOf(x, whereInstanceOf))
493
- return false;
234
+ }
235
+ // Dispatch mount event
236
+ this.dispatchEvent(new MountEvent(element, this.#modules, this.#init));
237
+ // Check for initial attribute changes if whereAttr is configured
238
+ if (this.#init.whereAttr) {
239
+ const changes = this.#checkAttrChanges(element);
240
+ if (changes.length > 0) {
241
+ this.dispatchEvent(new AttrChangeEvent(changes, this.#init));
494
242
  }
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*/);
243
+ }
244
+ }
245
+ #checkAttrChanges(element) {
246
+ if (!this.#init.whereAttr || !this.#buildAttrCoordinateMapFn) {
247
+ return [];
248
+ }
249
+ const isCustomElement = element.tagName.toLowerCase().includes('-');
250
+ const attrCoordMap = this.#buildAttrCoordinateMapFn(this.#init.whereAttr, isCustomElement);
251
+ // Get or create the attribute state for this element
252
+ let attrState = this.#elementAttrStates.get(element);
253
+ if (!attrState) {
254
+ attrState = new Map();
255
+ this.#elementAttrStates.set(element, attrState);
256
+ }
257
+ const changes = [];
258
+ const currentAttrs = new Set();
259
+ // Check all possible attributes from the coordinate map
260
+ for (const attrName of Object.keys(attrCoordMap)) {
261
+ const coordinate = attrCoordMap[attrName];
262
+ const currentValue = element.getAttribute(attrName);
263
+ const previousValue = attrState.get(attrName);
264
+ if (currentValue !== null) {
265
+ currentAttrs.add(attrName);
266
+ }
267
+ // Check if this attribute has "once: true" in its map entry
268
+ const mapEntry = this.#init.map?.[coordinate] || null;
269
+ const isOnce = mapEntry?.once === true;
270
+ // If "once" is true, check if we've already seen this attribute
271
+ if (isOnce) {
272
+ let onceAttrs = this.#elementOnceAttrs.get(element);
273
+ if (!onceAttrs) {
274
+ onceAttrs = new Set();
275
+ this.#elementOnceAttrs.set(element, onceAttrs);
276
+ }
277
+ // If we've already seen this attribute, skip it
278
+ if (onceAttrs.has(attrName)) {
279
+ continue;
501
280
  }
502
- else {
503
- await this.#compose(elToMount, 0);
281
+ // Mark this attribute as seen if it currently has a value
282
+ if (currentValue !== null) {
283
+ onceAttrs.add(attrName);
504
284
  }
505
285
  }
506
- }
507
- await bindishIt(els, target, { assigner });
508
- if (elsToMount.length === 0)
509
- return;
510
- this.#mount(elsToMount, initializing);
511
- }
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');
286
+ // Include if: currently has value OR previously had value but now removed
287
+ if (currentValue !== null || (previousValue !== undefined && currentValue === null)) {
288
+ // Check if value changed
289
+ if (currentValue !== previousValue) {
290
+ const attrNode = currentValue !== null ? element.getAttributeNode(attrName) : null;
291
+ changes.push({
292
+ value: currentValue,
293
+ attrNode,
294
+ mapEntry,
295
+ attrName,
296
+ coordinate,
297
+ element
298
+ });
299
+ // Update state
300
+ if (currentValue !== null) {
301
+ attrState.set(attrName, currentValue);
302
+ }
303
+ else {
304
+ attrState.delete(attrName);
305
+ }
306
+ }
523
307
  }
524
308
  }
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);
309
+ return changes;
530
310
  }
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
- }
311
+ #handleRemoval(element) {
312
+ if (!this.#mountedElements.has(element)) {
313
+ return;
551
314
  }
552
- if (areAllIdle(mutObservers)) {
553
- resolve();
315
+ this.#mountedElements.delete(element);
316
+ const rootNode = this.#rootNode?.deref();
317
+ if (!rootNode) {
318
+ // Root node was garbage collected
319
+ return;
554
320
  }
555
- for (const obs of mutObservers) {
556
- obs.addEventListener('is-idle', () => {
557
- if (areAllIdle(mutObservers)) {
558
- resolve();
559
- }
560
- });
321
+ const context = {
322
+ modules: this.#modules,
323
+ observer: this,
324
+ observeInfo: {
325
+ rootNode
326
+ }
327
+ };
328
+ // Call dismount callback
329
+ if (this.#init.do && typeof this.#init.do !== 'function' && this.#init.do.dismount) {
330
+ this.#init.do.dismount(element, context);
561
331
  }
562
- });
563
- }
564
- //make external
565
- function areAllIdle(mutObs) {
566
- for (const mo of mutObs) {
567
- if (!mo.isIdle)
568
- return false;
332
+ // Dispatch dismount event
333
+ this.dispatchEvent(new DismountEvent(element, 'where-element-matches-failed', this.#init));
334
+ // Check if element is being moved within the same root
335
+ // If it's truly disconnected, dispatch disconnect event
336
+ setTimeout(() => {
337
+ if (!rootNode.contains(element)) {
338
+ if (this.#init.do && typeof this.#init.do !== 'function' && this.#init.do.disconnect) {
339
+ this.#init.do.disconnect(element, context);
340
+ }
341
+ this.dispatchEvent(new DisconnectEvent(element, this.#init));
342
+ }
343
+ }, 0);
569
344
  }
570
- return true;
571
345
  }
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']