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.ts CHANGED
@@ -1,615 +1,439 @@
1
- import {MountInit, IMountObserver, AddMutationEventListener,
2
- MutationEvent, dismountEventName, mountEventName, IMountEvent, IDismountEvent,
3
- disconnectedEventName, IDisconnectEvent, IAttrChangeEvent, attrChangeEventName, AttrChangeInfo, loadEventName, ILoadEvent,
4
- AttrParts,
5
- MOSE, WeakDual,
1
+ import {
2
+ MountInit,
6
3
  MountObserverOptions,
7
- Assigner,
8
- RefType
9
- } from './ts-refs/mount-observer/types';
10
- import {RootMutObs} from './RootMutObs.js';
11
- import {bindish, bindishIt} from './bindish.js';
12
- import './refid/hostish.js'; // gets embedded even if not used
13
- export {MOSE} from './ts-refs/mount-observer/types';
14
- export const guid = '5Pv6bHOVH0ae07opRZ8N/g';
15
- export const wasItemReffed = Symbol.for('8aA6xB8+PkScmivaslBk5Q');
16
-
17
- export const mutationObserverLookup = new WeakMap<Node, RootMutObs>();
18
- const refCount = new WeakMap<Node, number>();
19
- export class MountObserver extends EventTarget implements IMountObserver{
20
-
21
- #mountInit: MountInit;
22
- #options: MountObserverOptions | undefined;
23
- //#rootMutObs: RootMutObs | undefined;
4
+ IMountObserver,
5
+ MountContext,
6
+ AttrChange
7
+ } from './types.js';
8
+ import {
9
+ MountEvent,
10
+ DismountEvent,
11
+ DisconnectEvent,
12
+ LoadEvent,
13
+ AttrChangeEvent,
14
+ MediaMatchEvent,
15
+ MediaUnmatchEvent
16
+ } from './Events.js';
17
+ import {
18
+ registerSharedObserver,
19
+ unregisterSharedObserver,
20
+ type MutationCallback
21
+ } from './SharedMutationObserver.js';
22
+
23
+ export class MountObserver extends EventTarget implements IMountObserver {
24
+ #init: MountInit;
25
+ #options: MountObserverOptions;
24
26
  #abortController: AbortController;
25
- mountedElements: WeakDual<Element>;
26
- #mountedList: Array<WeakRef<Element>> | undefined;
27
- #disconnected: WeakSet<Element>;
28
- //#unmounted: WeakSet<Element>;
29
- #isComplex: boolean;
30
- objNde: WeakRef<Node> | undefined;
31
-
32
- constructor(init: MountInit){
27
+ #modules: any[] = [];
28
+ #mountedElements = new WeakSet<Element>();
29
+ #processedElements = new WeakSet<Element>();
30
+ #mutationCallback: MutationCallback | undefined;
31
+ #rootNode: WeakRef<Node> | undefined;
32
+ #importsLoaded = false;
33
+ #elementAttrStates = new WeakMap<Element, Map<string, string | null>>();
34
+ #elementOnceAttrs = new WeakMap<Element, Set<string>>();
35
+ #matchesWhereAttrFn: ((element: Element, whereAttr: any) => boolean) | null = null;
36
+ #buildAttrCoordinateMapFn: ((whereAttr: any, isCustomElement: boolean) => any) | null = null;
37
+ #mediaQueryCleanup?: () => void;
38
+ #mediaMatches: boolean = true;
39
+
40
+ constructor(init: MountInit, options: MountObserverOptions = {}) {
33
41
  super();
34
- const {on, whereElementIntersectsWith, whereMediaMatches} = init;
35
- let isComplex = false;
36
- //TODO: study this problem further. Starting to think this is basically not polyfillable
37
- if(on !== undefined){
38
- const reducedMatch = on.replaceAll(':not(', '');
39
- isComplex = reducedMatch.includes(' ') || (reducedMatch.includes(':') && reducedMatch.includes('('));
40
- }
41
- this.#isComplex = isComplex;
42
- if(whereElementIntersectsWith) throw 'NI'; //not implemented
43
- this.#mountInit = init;
42
+ this.#init = init;
43
+ this.#options = options;
44
44
  this.#abortController = new AbortController();
45
- this.mountedElements = {
46
- weakSet: new WeakSet(),
47
- setWeak: new Set(),
48
- };
49
- this.#disconnected = new WeakSet();
50
- //this.#unmounted = new WeakSet();
51
- }
52
45
 
53
- #calculatedSelector: string | undefined;
54
- #attrParts: Array<AttrParts> | undefined;
55
-
56
- #fullListOfEnhancementAttrs: Array<string> | undefined;
57
- async observedAttrs(){
58
- await this.#selector();
59
- return this.#fullListOfEnhancementAttrs;
60
- }
61
- //get #attrVals
62
- async #selector() : Promise<string>{
63
- if(this.#calculatedSelector !== undefined) return this.#calculatedSelector;
64
- const {on, whereAttr} = this.#mountInit;
65
- const withoutAttrs = on || '*';
66
- if(whereAttr === undefined) return withoutAttrs;
67
- const {getWhereAttrSelector} = await import('./getWhereAttrSelector.js');
68
- const info = await getWhereAttrSelector(whereAttr, withoutAttrs);
69
- const {fullListOfAttrs, calculatedSelector, partitionedAttrs} = info;
70
- this.#fullListOfEnhancementAttrs = fullListOfAttrs;
71
- this.#attrParts = partitionedAttrs;
72
- this.#calculatedSelector = calculatedSelector
73
- return this.#calculatedSelector;
74
- }
75
-
76
- //This method is called publicly from outside mount-observer -- keep it public
77
- async composeFragment(fragment: DocumentFragment, level: number){
78
- const bis = fragment.querySelectorAll(`${inclTemplQry}`) as NodeListOf<HTMLTemplateElement>;
79
- for(const bi of bis){
80
- if(bi.getAttribute('rel') === 'preload'){
81
- (await import('./preloadContent.js')).preloadContent(bi);
82
- }else{
83
- await this.#compose(bi, level);
84
- }
85
-
46
+ if (options.disconnectedSignal) {
47
+ options.disconnectedSignal.addEventListener('abort', () => {
48
+ this.disconnect();
49
+ });
86
50
  }
87
- }
88
-
89
51
 
90
- async #compose(el: HTMLTemplateElement, level: number){
91
- //[TODO]: load async, not used often
92
- const src = el.getAttribute('src');
93
- if(src === null || src.length < 2) return;
94
- const refType = src[0] as RefType;
95
- if(!['!', '#'].includes(refType)) return;
96
- const {compose} = await import('./compose.js');
97
- await compose(this, el, level, src.substring(1), refType);
98
-
52
+ // Preload whereAttr utilities if needed
53
+ if (init.whereAttr) {
54
+ this.#preloadWhereAttrUtilities();
55
+ }
99
56
 
57
+ // Start loading imports if eager
58
+ if (init.loadingEagerness === 'eager' && init.import) {
59
+ this.#loadImports();
60
+ }
100
61
  }
101
- #templLookUp: Map<string, HTMLElement> = new Map();
102
- #searchForComment(refName: string, fragment: Node){
103
- //get rid of
104
- const iterator = document.evaluate(
105
- `//comment()[.="${refName}"]`,
106
- fragment,
107
- null,
108
- XPathResult.ANY_TYPE,
109
- null
110
- );
111
- //console.log({xpathResult})
112
- try {
113
- let thisNode = iterator.iterateNext();
114
- return thisNode;
115
- }catch(e){
116
- return null;
62
+
63
+ async #preloadWhereAttrUtilities(): Promise<void> {
64
+ if (!this.#matchesWhereAttrFn) {
65
+ const { matchesWhereAttr } = await import('./whereAttr.js');
66
+ this.#matchesWhereAttrFn = matchesWhereAttr;
67
+ }
68
+ if (!this.#buildAttrCoordinateMapFn) {
69
+ const { buildAttrCoordinateMap } = await import('./attrCoordinates.js');
70
+ this.#buildAttrCoordinateMapFn = buildAttrCoordinateMap;
117
71
  }
118
72
  }
119
- async findByID(
120
- //[TODO]: make external, not always used
121
- refName: string, fragment: DocumentFragment,
122
- refType: RefType): Promise<HTMLElement | DocumentFragment | null>{
123
- if(this.#templLookUp.has(refName)) return this.#templLookUp.get(refName)!;
124
- let templ: Node | null = null;
125
- templ = refType === '#' ? fragment.querySelector(`#${refName}`) : this.#searchForComment(refName, fragment);
126
- if(templ === null){
127
- let rootToSearchOutwardFrom = ((fragment.isConnected ? fragment.getRootNode() : this.#mountInit.withTargetShadowRoot) || document) as any;
128
- templ = refType === '#' ? rootToSearchOutwardFrom.getElementById(refName) : this.#searchForComment(refName, rootToSearchOutwardFrom);
129
- while(templ === null && rootToSearchOutwardFrom !== (document as any as DocumentFragment) ){
130
- rootToSearchOutwardFrom = (rootToSearchOutwardFrom.host || rootToSearchOutwardFrom).getRootNode() as DocumentFragment;
131
- templ = refType === '#' ? rootToSearchOutwardFrom.getElementById(refName) : this.#searchForComment(refName, rootToSearchOutwardFrom);
132
- }
73
+
74
+ async #setupMediaQuery(): Promise<void> {
75
+ if (!this.#rootNode) {
76
+ throw new Error('Cannot setup media query before observe() is called');
133
77
  }
78
+
79
+ const { setupMediaQuery } = await import('./mediaQuery.js');
80
+ const result = setupMediaQuery(
81
+ this.#init,
82
+ this.#rootNode,
83
+ this.#mountedElements,
84
+ this.#modules,
85
+ this,
86
+ (node) => this.#processNode(node)
87
+ );
88
+
89
+ this.#mediaMatches = result.mediaMatches;
90
+ this.#mediaQueryCleanup = result.cleanup;
91
+ }
134
92
 
93
+ get disconnectedSignal(): AbortSignal {
94
+ return this.#abortController.signal;
95
+ }
135
96
 
136
- if(templ !== null) {
137
- if(!(templ instanceof HTMLTemplateElement)){
138
- const newTempl = document.createElement('template');
139
- const {getAdjRefs} = await import('./refid/getAdjRefs.js');
140
- const adjRefs = getAdjRefs(templ);
141
- // if(adjRefs.length > 1){
142
- // (<any>newTempl)[wasItemReffed] = true;
143
- // adjRefs[0].setAttribute('itemref', '<autogen>');
144
- // }
145
- const fragment = document.createDocumentFragment();
146
- let first = true;
147
- for(const adjRef of adjRefs){
148
- const clone = adjRef.cloneNode(true) as HTMLElement;
149
- if(refType === '#' && clone instanceof Element){
150
- if(first && adjRefs.length > 1){
151
- clone.setAttribute('itemref', '<autogen>');
152
- (<any>newTempl)[wasItemReffed] = true;
153
- first = false;
154
- }
155
- clone.removeAttribute('id');
156
- }
157
-
158
- fragment.appendChild(clone);
159
- }
160
- if(templ instanceof Element){
161
- const {doCleanup} = await import('./doCleanup.js');
162
- doCleanup(templ as HTMLElement, fragment);
163
-
164
- }else{
165
- //TODO: cleanup
166
- }
167
- newTempl.content.appendChild(fragment);
168
- templ = newTempl;
169
-
170
- }
171
- this.#templLookUp.set(refName, templ as HTMLTemplateElement);
97
+ async observe(rootNode: Node): Promise<void> {
98
+ if (this.#rootNode) {
99
+ throw new Error('Already observing');
172
100
  }
173
- return templ as HTMLTemplateElement;
174
- }
175
101
 
176
- disconnect(within: Node){
177
- const nodeToMonitor = this.#isComplex ? (within instanceof ShadowRoot ? within : within.getRootNode()) : within;
178
- const currentCount = refCount.get(nodeToMonitor);
179
- if(currentCount !== undefined){
180
- if(currentCount <= 1){
181
- const observer = mutationObserverLookup.get(nodeToMonitor);
182
- if(observer === undefined){
183
- console.warn(refCountErr);
184
- }else{
185
- observer.disconnect();
186
- mutationObserverLookup.delete(nodeToMonitor);
187
- refCount.delete(nodeToMonitor);
188
- }
189
- }else{
190
- refCount.set(nodeToMonitor, currentCount + 1);
191
- }
192
- }else{
193
- if(mutationObserverLookup.has(nodeToMonitor)){
194
- console.warn(refCountErr);
195
- }
102
+ this.#rootNode = new WeakRef(rootNode);
103
+
104
+ // Set up media query if specified (needs rootNode to be set first)
105
+ if (this.#init.whereMediaMatches) {
106
+ await this.#setupMediaQuery();
196
107
  }
197
- this.dispatchEvent(new Event('disconnectedCallback'));
198
-
199
- }
200
108
 
201
- async observe(within: Node, options?: MountObserverOptions){
202
- this.#options = options;
203
- const init = this.#mountInit;
204
- const {whereMediaMatches} = init;
205
- if(whereMediaMatches === undefined){
206
- await this.#observe2(within);
207
- return;
109
+ // Wait for whereAttr utilities to load if needed
110
+ if (this.#init.whereAttr && !this.#matchesWhereAttrFn) {
111
+ await this.#preloadWhereAttrUtilities();
208
112
  }
209
- const mql = window.matchMedia(whereMediaMatches);
210
- if(mql.matches){
211
- await this.#observe2(within);
113
+
114
+ // Process existing elements only if media matches
115
+ if (this.#mediaMatches) {
116
+ this.#processNode(rootNode);
212
117
  }
213
- mql.addEventListener('change', async (e) => {
214
- if(e.matches){
215
- if(this.objNde === undefined){
216
- await this.#observe2(within);
217
- }else{
218
- await this.#mountAll();
219
- }
220
- }else{
221
- if(this.objNde !== undefined){
222
- await this.#dismountAll();
223
- }
224
- }
225
- });
226
- }
227
118
 
228
- async #observe2(within: Node){
229
- await this.#selector();
230
- this.objNde = new WeakRef(within);
231
- const nodeToMonitor = this.#isComplex ? (within instanceof ShadowRoot ? within : within.getRootNode()) : within;
232
- if(!mutationObserverLookup.has(nodeToMonitor)){
233
- mutationObserverLookup.set(nodeToMonitor, new RootMutObs(nodeToMonitor, this.#mountInit));
234
- refCount.set(nodeToMonitor, 1);
235
- }else{
236
- const currentCount = refCount.get(nodeToMonitor);
237
- if(currentCount === undefined){
238
- console.warn(refCountErr);
239
- }else{
240
- refCount.set(nodeToMonitor, currentCount + 1);
241
- }
242
- }
243
- const rootMutObs = mutationObserverLookup.get(within)!;
244
- const fullListOfAttrs = this.#fullListOfEnhancementAttrs;
245
- (rootMutObs as any as AddMutationEventListener).addEventListener('mutation-event', async (e: MutationEvent) => {
246
- //TODO: disconnected
247
- if(this.#isComplex){
248
- this.#inspectWithin(within, false);
119
+ // Create mutation callback
120
+ this.#mutationCallback = (mutations) => {
121
+ // Skip processing if media doesn't match
122
+ if (!this.#mediaMatches) {
249
123
  return;
250
124
  }
251
- const {mutationRecords} = e;
252
- const elsToInspect: Array<Element> = [];
253
- //const elsToDisconnect: Array<Element> = [];
254
- const doDisconnect = this.#mountInit.do?.disconnect;
255
- let attrChangeInfosMap: Map<Element, Array<AttrChangeInfo>> | undefined;
256
- for(const mutationRecord of mutationRecords){
257
- const {addedNodes, type, removedNodes} = mutationRecord;
258
- const addedElements = Array.from(addedNodes).filter(x => x instanceof Element) as Array<Element>;
259
- addedElements.forEach(x => elsToInspect.push(x));
260
- if(type === 'attributes'){
261
- const {target, attributeName, oldValue} = mutationRecord;
262
- if(target instanceof Element && attributeName !== null /*&& this.#mounted.has(target)*/){
263
-
264
- if(fullListOfAttrs !== undefined){
265
- const idx = fullListOfAttrs.indexOf(attributeName);
266
- if(idx !== -1){
267
- if(attrChangeInfosMap === undefined) attrChangeInfosMap = new Map();
268
- let attrChangeInfos = attrChangeInfosMap.get(target);
269
- if(attrChangeInfos === undefined){
270
- attrChangeInfos = [];
271
- attrChangeInfosMap.set(target, attrChangeInfos);
272
- }
273
- const newValue = target.getAttribute(attributeName);
274
- const parts = this.#attrParts![idx];
275
- const attrChangeInfo: AttrChangeInfo = {
276
- isSOfTAttr: false,
277
- oldValue,
278
- name: attributeName,
279
- newValue,
280
- idx,
281
- parts
282
- }
283
- attrChangeInfos.push(attrChangeInfo)
284
- }
285
-
286
-
125
+
126
+ const attrChanges: AttrChange[] = [];
127
+
128
+ for (const mutation of mutations) {
129
+ if (mutation.type === 'childList') {
130
+ for (const node of mutation.addedNodes) {
131
+ if (node.nodeType === Node.ELEMENT_NODE) {
132
+ this.#processNode(node);
287
133
  }
288
-
289
134
  }
290
- elsToInspect.push(target as Element);
291
- }
292
-
293
- const deletedElements = Array.from(removedNodes).filter(x => x instanceof Element) as Array<Element>;
294
- const {DisconnectEvent} = await import('./Events.js');
295
- for(const deletedElement of deletedElements){
296
- this.#disconnected.add(deletedElement);
297
- if(doDisconnect !== undefined){
298
- doDisconnect(deletedElement, this, {});
135
+ mutation.removedNodes.forEach(node => {
136
+ if (node.nodeType === Node.ELEMENT_NODE) {
137
+ this.#handleRemoval(node as Element);
138
+ }
139
+ });
140
+ } else if (mutation.type === 'attributes' && mutation.target.nodeType === Node.ELEMENT_NODE) {
141
+ // Handle attribute changes for mounted elements
142
+ const element = mutation.target as Element;
143
+ if (this.#mountedElements.has(element) && this.#init.whereAttr) {
144
+ const changes = this.#checkAttrChanges(element);
145
+ attrChanges.push(...changes);
299
146
  }
300
- this.dispatchEvent(new DisconnectEvent(deletedElement));
301
- }
302
-
303
- }
304
- if(attrChangeInfosMap !== undefined){
305
- const {AttrChangeEvent} = await import('./Events.js');
306
- for(const [key, value] of attrChangeInfosMap){
307
- this.dispatchEvent(new AttrChangeEvent(key, value))
308
147
  }
309
148
  }
310
- this.#filterAndMount(elsToInspect, within, true, false);
311
- for(const el of elsToInspect){
312
- await this.#inspectWithin(el, false);
149
+
150
+ // Batch and dispatch attribute changes
151
+ if (attrChanges.length > 0) {
152
+ this.dispatchEvent(new AttrChangeEvent(attrChanges, this.#init));
313
153
  }
314
- }, {signal: this.#abortController.signal});
154
+ };
155
+
156
+ const observerConfig: MutationObserverInit = {
157
+ childList: true,
158
+ subtree: true
159
+ };
315
160
 
316
- await this.#inspectWithin(within, true);
161
+ // Add attribute observation if whereAttr is configured
162
+ if (this.#init.whereAttr) {
163
+ observerConfig.attributes = true;
164
+ observerConfig.attributeOldValue = true;
165
+ }
166
+
167
+ // Register with shared mutation observer
168
+ registerSharedObserver(rootNode, this.#mutationCallback, observerConfig);
317
169
  }
318
170
 
319
- static synthesize(within: Document | ShadowRoot, customElement: {new(): HTMLElement}, mose: MOSE){
320
- //TODO: make external
321
- mose.type = 'mountobserver';
322
- const name = customElements.getName(customElement);
323
- if(name === null) throw 400;
324
- let instance = within.querySelector(name);
325
- if(instance === null){
326
- instance = new customElement();
327
- if(within === document){
328
- within.head.appendChild(instance);
329
- }else{
330
- within.appendChild(instance);
331
- }
171
+ disconnect(): void {
172
+ const rootNode = this.#rootNode?.deref();
173
+
174
+ // Unregister from shared mutation observer
175
+ if (rootNode && this.#mutationCallback) {
176
+ unregisterSharedObserver(rootNode, this.#mutationCallback);
177
+ this.#mutationCallback = undefined;
332
178
  }
333
- instance.appendChild(mose);
179
+
180
+ // Remove media query listener
181
+ if (this.#mediaQueryCleanup) {
182
+ this.#mediaQueryCleanup();
183
+ this.#mediaQueryCleanup = undefined;
184
+ }
185
+
186
+ this.#abortController.abort();
187
+ this.#rootNode = undefined;
334
188
  }
335
189
 
336
- #confirmInstanceOf(el: Element, whereInstanceOf: Array<{new(): Element}>){
337
- for(const test of whereInstanceOf){
338
- if(el instanceof test) return true;
190
+ async #loadImports(): Promise<void> {
191
+ if (this.#importsLoaded || !this.#init.import) {
192
+ return;
339
193
  }
340
- return false;
194
+
195
+ // Dynamically load the import utilities only when needed
196
+ const { loadImports } = await import('./loadImports.js');
197
+ this.#modules = await loadImports(this.#init.import);
198
+ this.#importsLoaded = true;
199
+
200
+ this.dispatchEvent(new LoadEvent(this.#modules, this.#init));
341
201
  }
342
202
 
343
- async #mount(matching: Array<Element>, initializing: boolean){
344
- //first unmount non matching
345
- const alreadyMounted = await this.#filterAndDismount();
346
- const mount = this.#mountInit.do?.mount;
347
- const {import: imp} = this.#mountInit;
348
- const me = this.mountedElements;
349
- const options = this.#options;
350
- for(const match of matching){
351
- if(alreadyMounted.has(match)) continue;
352
- if(!me.weakSet.has(match)){
353
- me.setWeak.add(new WeakRef(match));
354
- me.weakSet.add(match);
355
- }
356
- if(imp !== undefined){
357
- switch(typeof imp){
358
- case 'string':
359
- this.module = await import(imp);
360
- break;
361
- case 'object':
362
- if(Array.isArray(imp)){
363
- throw 'NI: Firefox'
364
- }
365
- break;
366
- case 'function':
367
- this.module = await imp(match, this, {
368
- stage: 'Import',
369
- initializing
370
- });
371
- break;
372
- }
373
- }
374
- if(mount !== undefined) {
375
- mount(match, this, {
376
- stage: 'PostImport',
377
- initializing
378
- })
379
- }
380
- if(options?.leaveBreadcrumb){
381
- if((<any>match)[guid] === undefined){
382
- (<any>match)[guid] = new Set();
383
- }
384
- (<any>match)[guid].add(this);
203
+ #processNode(node: Node): void {
204
+ // If it's an element node, check if it matches
205
+ if (node.nodeType === Node.ELEMENT_NODE) {
206
+ const element = node as Element;
207
+
208
+ if (this.#matchesSelector(element)) {
209
+ this.#handleMatch(element);
385
210
  }
386
- const {MountEvent} = await import('./Events.js');
387
- this.dispatchEvent(new MountEvent(match, initializing));
388
- //should we automatically call readAttrs?
389
- //the thinking is it might make more sense to call that after mounting
211
+ }
212
+
213
+ // Process children
214
+ if (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.DOCUMENT_NODE) {
215
+ const root = node as Element | Document;
390
216
 
391
- this.#mountedList?.push(new WeakRef(match));
217
+ // Get all elements matching the CSS selector first
218
+ root.querySelectorAll(this.#init.whereElementMatches).forEach(child => {
219
+ if (this.#matchesSelector(child)) {
220
+ this.#handleMatch(child);
221
+ }
222
+ });
392
223
  }
393
224
  }
394
225
 
395
- readAttrs(match: Element, branchIndexes?: Set<number>){
396
- //TODO: externalize
397
- const fullListOfAttrs = this.#fullListOfEnhancementAttrs;
398
- const attrChangeInfos: Array<AttrChangeInfo> = [];
399
- const oldValue = null;
400
- if(fullListOfAttrs !== undefined){
401
- const attrParts = this.#attrParts
226
+ #matchesSelector(element: Element): boolean {
227
+ //TODO: reduce redundncy with this.#init?
228
+ // Check whereElementMatches condition
229
+ const matchesElement = element.matches(this.#init.whereElementMatches);
230
+ if (!matchesElement) {
231
+ return false;
232
+ }
233
+
234
+ // Check whereAttr condition if specified
235
+ if (this.#init.whereAttr) {
236
+ // Use cached function (should be loaded by now from constructor)
237
+ if (!this.#matchesWhereAttrFn) {
238
+ console.warn('whereAttr utilities not loaded yet');
239
+ return false;
240
+ }
402
241
 
403
- for(let idx = 0, ii = fullListOfAttrs.length; idx < ii; idx++){
404
- const parts = attrParts![idx];
405
- const {branchIdx} = parts;
406
- if(branchIndexes !== undefined){
407
- if(!branchIndexes.has(branchIdx)) continue;
408
- }
409
- const name = fullListOfAttrs[idx];
410
-
411
- const newValue = match.getAttribute(name);
412
-
413
- attrChangeInfos.push({
414
- idx,
415
- isSOfTAttr: false,
416
- newValue,
417
- oldValue,
418
- name,
419
- parts
420
- });
242
+ if (!this.#matchesWhereAttrFn(element, this.#init.whereAttr)) {
243
+ return false;
421
244
  }
422
-
423
245
  }
424
- const {observedAttrsWhenMounted} = this.#mountInit;
425
- if(observedAttrsWhenMounted !== undefined){
426
- for(const observedAttr of observedAttrsWhenMounted){
427
- const attrIsString = typeof observedAttr === 'string';
428
- const name = attrIsString ? observedAttr : observedAttr.name;
429
- let mapsTo: string | undefined;
430
- let newValue = match.getAttribute(name);
431
- if(!attrIsString){
432
- const {customParser, instanceOf, mapsTo: mt, valIfNull} = observedAttr;
433
- if(instanceOf || customParser) throw 'NI';
434
- if(newValue === null) newValue = valIfNull;
435
- mapsTo = mt;
436
- }
437
- attrChangeInfos.push({
438
- isSOfTAttr: true,
439
- newValue,
440
- oldValue,
441
- name,
442
- mapsTo
443
- });
246
+
247
+ // Check whereInstanceOf condition if specified
248
+ if (this.#init.whereInstanceOf) {
249
+ const constructors = Array.isArray(this.#init.whereInstanceOf)
250
+ ? this.#init.whereInstanceOf
251
+ : [this.#init.whereInstanceOf];
252
+
253
+ // Element must be an instance of at least one constructor (OR logic for array)
254
+ const matchesInstanceOf = constructors.some(constructor => element instanceof constructor);
255
+
256
+ if (!matchesInstanceOf) {
257
+ return false;
444
258
  }
445
259
  }
446
-
447
- return attrChangeInfos;
260
+
261
+ // All conditions passed
262
+ return true;
448
263
  }
449
264
 
450
- async #dismount(unmatching: Array<Element>){
451
- const onDismount = this.#mountInit.do?.dismount;
452
- const {DismountEvent} = await import('./Events.js');
453
- for(const unmatch of unmatching){
454
- if(onDismount !== undefined){
455
- onDismount(unmatch, this, {});
456
- }
457
- this.dispatchEvent(new DismountEvent(unmatch));
265
+ async #handleMatch(element: Element): Promise<void> {
266
+ if (this.#processedElements.has(element)) {
267
+ return;
458
268
  }
459
- }
460
269
 
461
- async #dismountAll(){
462
- const mounted = this.#mountedList;
463
- if(mounted === undefined) return;
464
- this.#dismount(mounted.map(x => x.deref()).filter(x => x !== undefined) as Array<Element>);
465
- }
270
+ // Load imports if not already loaded
271
+ if (!this.#importsLoaded && this.#init.import) {
272
+ await this.#loadImports();
273
+ }
466
274
 
467
- async #mountAll(){
468
- //TODO: copilot created, check if needed
469
- const {whereSatisfies, whereInstanceOf} = this.#mountInit;
470
- const match = await this.#selector();
471
- const els = Array.from(document.querySelectorAll(match));
472
- this.#filterAndMount(els, document.body, false, true);
473
- }
275
+ this.#processedElements.add(element);
276
+ this.#mountedElements.add(element);
474
277
 
475
- async #filterAndDismount(): Promise<Set<Element>>{
476
- const returnSet = new Set<Element>();
477
- if(this.#mountedList !== undefined){
478
- const previouslyMounted = this.#mountedList.map(x => x.deref());
479
- const {whereSatisfies, whereInstanceOf} = this.#mountInit;
480
- const match = await this.#selector();
481
- const elsToUnMount = previouslyMounted.filter(x => {
482
- if(x === undefined) return false;
483
- if(!x.matches(match)) return true;
484
- //TODO: add check for outside
485
- if(whereSatisfies !== undefined){
486
- if(!whereSatisfies(x, this, {stage: 'Inspecting', initializing: false})) return true;
487
- }
488
- returnSet.add(x);
489
- return false;
490
- }) as Array<Element>;
491
- this.#dismount(elsToUnMount);
278
+ const rootNode = this.#rootNode?.deref();
279
+ if (!rootNode) {
280
+ // Root node was garbage collected
281
+ return;
492
282
  }
493
- this.#mountedList = Array.from(returnSet).map(x => new WeakRef(x));
494
- return returnSet;
495
- }
496
283
 
497
- #outsideCheck(oElement: Element, matchCandidate: Element, outside: string){
498
- const elementsToExclude = Array.from(oElement.querySelectorAll(outside));
499
- for(const elementToExclude of elementsToExclude){
500
- if(elementToExclude === matchCandidate || elementToExclude.contains(matchCandidate)) return false;
501
- }
502
- return true;
503
- }
284
+ const context: MountContext = {
285
+ modules: this.#modules,
286
+ observer: this,
287
+ observeInfo: {
288
+ rootNode
289
+ }
290
+ };
504
291
 
505
- async #filterAndMount(els: Array<Element>, target: Node, checkMatch: boolean, initializing: boolean){
506
- const {whereSatisfies, whereInstanceOf, assigner, outside} = this.#mountInit;
507
- const match = await this.#selector();
508
- const elsToMount = els.filter(x => {
509
- if(checkMatch){
510
- if(!x.matches(match)) return false;
292
+ // Apply assignGingerly if specified
293
+ if (this.#init.assignGingerly) {
294
+ const { assignGingerly } = await import('assign-gingerly/index.js');
295
+ assignGingerly(element, this.#init.assignGingerly);
296
+ }
511
297
 
512
- //TODO: check for outside
513
- }
514
- if(outside !== undefined){
515
- if(!this.#outsideCheck(this.objNde!.deref() as Element, x, outside)) return false;
516
- }
517
- if(whereSatisfies !== undefined){
518
- if(!whereSatisfies(x, this, {stage: 'Inspecting', initializing})) return false;
519
- }
520
- if(whereInstanceOf !== undefined){
521
- if(!this.#confirmInstanceOf(x, whereInstanceOf)) return false;
298
+ // Call do callback
299
+ if (this.#init.do) {
300
+ if (typeof this.#init.do === 'function') {
301
+ this.#init.do(element, context);
302
+ } else if (this.#init.do.mount) {
303
+ this.#init.do.mount(element, context);
522
304
  }
523
- return true;
524
- });
525
- for(const elToMount of elsToMount){
526
- if(elToMount.matches(inclTemplQry)){
527
- if(elToMount instanceof HTMLTemplateElement && elToMount.getAttribute('rel') === 'preload'){
528
- (await import('./preloadContent.js')).preloadContent(elToMount/*, this.#mountInit.withTargetShadowRoot*/);
529
- }else{
530
- await this.#compose(elToMount as HTMLTemplateElement, 0);
531
- }
532
-
533
- }
534
-
535
305
  }
536
- await bindishIt(els, target, {assigner});
537
- if(elsToMount.length === 0) return;
538
- this.#mount(elsToMount, initializing);
539
- }
540
306
 
541
- async #inspectWithin(within: Node, initializing: boolean){
542
- //the line below had an await for bindish, consistent with the rest of the code, but it was
543
- //getting into a catch-22 scenario frequently, blocking the code for resuming.
544
- //This was observed with per-each package, demo/ScopeScript.html, clicking refresh a few times
545
- //one will see the inconsistent behavior if await is added below.
546
- const idGenerators = Array.from((within as DocumentFragment).querySelectorAll('[-id]'))
547
- if(idGenerators[0]){
548
- const {genIds} = await import('./refid/genIds.js');
549
- for(const el of idGenerators){
550
- genIds(el);
551
- el.removeAttribute('-id');
307
+ // Dispatch mount event
308
+ this.dispatchEvent(new MountEvent(element, this.#modules, this.#init));
309
+
310
+ // Check for initial attribute changes if whereAttr is configured
311
+ if (this.#init.whereAttr) {
312
+ const changes = this.#checkAttrChanges(element);
313
+ if (changes.length > 0) {
314
+ this.dispatchEvent(new AttrChangeEvent(changes, this.#init));
552
315
  }
553
316
  }
554
- bindish(within as DocumentFragment, within, {assigner: this.#mountInit.assigner});
555
- await this.composeFragment(within as DocumentFragment, 0);
556
- const match = await this.#selector();
557
- const els = Array.from((within as Element).querySelectorAll(match));
558
- this.#filterAndMount(els, within, false, initializing);
559
-
560
317
  }
561
-
562
- }
563
-
564
- //ToDO: make external
565
- export function waitForIdleNodes(nodes: Array<Node>, idleTimeout?: number): Promise<void>{
566
- const mountInit: MountInit = {
567
- idleTimeout
568
- };
569
- return new Promise((resolve) => {
570
- const mutObservers: Array<RootMutObs> = [];
571
- for(const node of nodes){
572
- const mutObs = mutationObserverLookup.get(node);
573
- if(mutObs !== undefined){
574
- mutObservers.push(mutObs);
575
- }else{
576
- const currentCount = refCount.get(node) || 0;
577
- const newMutObs = new RootMutObs(node, mountInit);
578
- mutationObserverLookup.set(node, newMutObs);
579
- refCount.set(node, currentCount + 1);
580
- mutObservers.push(newMutObs);
581
- }
318
+
319
+ #checkAttrChanges(element: Element): AttrChange[] {
320
+ if (!this.#init.whereAttr || !this.#buildAttrCoordinateMapFn) {
321
+ return [];
582
322
  }
583
- if(areAllIdle(mutObservers)){
584
- resolve();
323
+
324
+ const isCustomElement = element.tagName.toLowerCase().includes('-');
325
+ const attrCoordMap = this.#buildAttrCoordinateMapFn(this.#init.whereAttr, isCustomElement);
326
+
327
+ // Get or create the attribute state for this element
328
+ let attrState = this.#elementAttrStates.get(element);
329
+ if (!attrState) {
330
+ attrState = new Map<string, string | null>();
331
+ this.#elementAttrStates.set(element, attrState);
585
332
  }
586
- for(const obs of mutObservers){
587
- obs.addEventListener('is-idle', () => {
588
- if(areAllIdle(mutObservers)){
589
- resolve();
333
+
334
+ const changes: AttrChange[] = [];
335
+ const currentAttrs = new Set<string>();
336
+
337
+ // Check all possible attributes from the coordinate map
338
+ for (const attrName of Object.keys(attrCoordMap)) {
339
+ const coordinate = attrCoordMap[attrName];
340
+ const currentValue = element.getAttribute(attrName);
341
+ const previousValue = attrState.get(attrName);
342
+
343
+ if (currentValue !== null) {
344
+ currentAttrs.add(attrName);
345
+ }
346
+
347
+ // Check if this attribute has "once: true" in its map entry
348
+ const mapEntry = this.#init.map?.[coordinate] || null;
349
+ const isOnce = mapEntry?.once === true;
350
+
351
+ // If "once" is true, check if we've already seen this attribute
352
+ if (isOnce) {
353
+ let onceAttrs = this.#elementOnceAttrs.get(element);
354
+ if (!onceAttrs) {
355
+ onceAttrs = new Set<string>();
356
+ this.#elementOnceAttrs.set(element, onceAttrs);
590
357
  }
591
- });
358
+
359
+ // If we've already seen this attribute, skip it
360
+ if (onceAttrs.has(attrName)) {
361
+ continue;
362
+ }
363
+
364
+ // Mark this attribute as seen if it currently has a value
365
+ if (currentValue !== null) {
366
+ onceAttrs.add(attrName);
367
+ }
368
+ }
369
+
370
+ // Include if: currently has value OR previously had value but now removed
371
+ if (currentValue !== null || (previousValue !== undefined && currentValue === null)) {
372
+ // Check if value changed
373
+ if (currentValue !== previousValue) {
374
+ const attrNode = currentValue !== null ? element.getAttributeNode(attrName) : null;
375
+
376
+ changes.push({
377
+ value: currentValue,
378
+ attrNode,
379
+ mapEntry,
380
+ attrName,
381
+ coordinate,
382
+ element
383
+ });
384
+
385
+ // Update state
386
+ if (currentValue !== null) {
387
+ attrState.set(attrName, currentValue);
388
+ } else {
389
+ attrState.delete(attrName);
390
+ }
391
+ }
392
+ }
592
393
  }
593
- });
594
- }
595
-
596
- //make external
597
- function areAllIdle(mutObs: Array<RootMutObs>){
598
- for(const mo of mutObs){
599
- if(!mo.isIdle) return false;
394
+
395
+ return changes;
600
396
  }
601
- return true;
602
- }
603
397
 
398
+ #handleRemoval(element: Element): void {
399
+ if (!this.#mountedElements.has(element)) {
400
+ return;
401
+ }
604
402
 
605
- const refCountErr = 'mount-observer ref count mismatch';
606
- export const inclTemplQry = 'template[src^="#"]:not([hidden]),template[src^="!"]:not([hidden])';
607
-
608
- export interface MountObserver extends IMountObserver{}
403
+ this.#mountedElements.delete(element);
609
404
 
405
+ const rootNode = this.#rootNode?.deref();
406
+ if (!rootNode) {
407
+ // Root node was garbage collected
408
+ return;
409
+ }
610
410
 
411
+ const context: MountContext = {
412
+ modules: this.#modules,
413
+ observer: this,
414
+ observeInfo: {
415
+ rootNode
416
+ }
417
+ };
611
418
 
419
+ // Call dismount callback
420
+ if (this.#init.do && typeof this.#init.do !== 'function' && this.#init.do.dismount) {
421
+ this.#init.do.dismount(element, context);
422
+ }
612
423
 
424
+ // Dispatch dismount event
425
+ this.dispatchEvent(new DismountEvent(element, 'where-element-matches-failed', this.#init));
613
426
 
614
- //const hasRootInDefault = ['data', 'enh', 'data-enh']
427
+ // Check if element is being moved within the same root
428
+ // If it's truly disconnected, dispatch disconnect event
429
+ setTimeout(() => {
430
+ if (!rootNode.contains(element)) {
431
+ if (this.#init.do && typeof this.#init.do !== 'function' && this.#init.do.disconnect) {
432
+ this.#init.do.disconnect(element, context);
433
+ }
615
434
 
435
+ this.dispatchEvent(new DisconnectEvent(element, this.#init));
436
+ }
437
+ }, 0);
438
+ }
439
+ }