mount-observer 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Events.js +41 -7
- package/Events.ts +31 -13
- package/MountObserver.js +154 -99
- package/MountObserver.ts +190 -105
- package/README.md +344 -24
- package/SharedMutationObserver.js +70 -0
- package/SharedMutationObserver.ts +96 -0
- package/attrChanges.js +70 -0
- package/attrChanges.ts +90 -0
- package/emitEvents.js +103 -0
- package/emitEvents.ts +126 -0
- package/index.ts +9 -2
- package/mediaQuery.js +89 -0
- package/mediaQuery.ts +116 -0
- package/package.json +19 -4
- package/types.d.ts +35 -0
- package/whereOutside.js +19 -0
- package/whereOutside.ts +25 -0
- package/constants.js +0 -6
- package/constants.ts +0 -7
package/Events.js
CHANGED
|
@@ -1,44 +1,78 @@
|
|
|
1
|
-
// Event
|
|
2
|
-
|
|
1
|
+
// Event name constants
|
|
2
|
+
export const loadEventName = 'load';
|
|
3
|
+
export const mountEventName = 'mount';
|
|
4
|
+
export const dismountEventName = 'dismount';
|
|
5
|
+
export const disconnectEventName = 'disconnect';
|
|
6
|
+
export const attrchangeEventName = 'attrchange';
|
|
7
|
+
export const mediamatchEventName = 'mediamatch';
|
|
8
|
+
export const mediaunmatchEventName = 'mediaunmatch';
|
|
3
9
|
export class MountEvent extends Event {
|
|
4
10
|
matchingElement;
|
|
5
11
|
modules;
|
|
12
|
+
mountInit;
|
|
6
13
|
static eventName = mountEventName;
|
|
7
|
-
constructor(matchingElement, modules) {
|
|
14
|
+
constructor(matchingElement, modules, mountInit) {
|
|
8
15
|
super(MountEvent.eventName);
|
|
9
16
|
this.matchingElement = matchingElement;
|
|
10
17
|
this.modules = modules;
|
|
18
|
+
this.mountInit = mountInit;
|
|
11
19
|
}
|
|
12
20
|
}
|
|
13
21
|
export class DismountEvent extends Event {
|
|
14
22
|
matchingElement;
|
|
23
|
+
reason;
|
|
24
|
+
mountInit;
|
|
15
25
|
static eventName = dismountEventName;
|
|
16
|
-
constructor(matchingElement) {
|
|
26
|
+
constructor(matchingElement, reason, mountInit) {
|
|
17
27
|
super(DismountEvent.eventName);
|
|
18
28
|
this.matchingElement = matchingElement;
|
|
29
|
+
this.reason = reason;
|
|
30
|
+
this.mountInit = mountInit;
|
|
19
31
|
}
|
|
20
32
|
}
|
|
21
33
|
export class DisconnectEvent extends Event {
|
|
22
34
|
matchingElement;
|
|
35
|
+
mountInit;
|
|
23
36
|
static eventName = disconnectEventName;
|
|
24
|
-
constructor(matchingElement) {
|
|
37
|
+
constructor(matchingElement, mountInit) {
|
|
25
38
|
super(DisconnectEvent.eventName);
|
|
26
39
|
this.matchingElement = matchingElement;
|
|
40
|
+
this.mountInit = mountInit;
|
|
27
41
|
}
|
|
28
42
|
}
|
|
29
43
|
export class LoadEvent extends Event {
|
|
30
44
|
modules;
|
|
45
|
+
mountInit;
|
|
31
46
|
static eventName = loadEventName;
|
|
32
|
-
constructor(modules) {
|
|
47
|
+
constructor(modules, mountInit) {
|
|
33
48
|
super(LoadEvent.eventName);
|
|
34
49
|
this.modules = modules;
|
|
50
|
+
this.mountInit = mountInit;
|
|
35
51
|
}
|
|
36
52
|
}
|
|
37
53
|
export class AttrChangeEvent extends Event {
|
|
38
54
|
changes;
|
|
55
|
+
mountInit;
|
|
39
56
|
static eventName = attrchangeEventName;
|
|
40
|
-
constructor(changes) {
|
|
57
|
+
constructor(changes, mountInit) {
|
|
41
58
|
super(AttrChangeEvent.eventName);
|
|
42
59
|
this.changes = changes;
|
|
60
|
+
this.mountInit = mountInit;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export class MediaMatchEvent extends Event {
|
|
64
|
+
mountInit;
|
|
65
|
+
static eventName = mediamatchEventName;
|
|
66
|
+
constructor(mountInit) {
|
|
67
|
+
super(MediaMatchEvent.eventName);
|
|
68
|
+
this.mountInit = mountInit;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export class MediaUnmatchEvent extends Event {
|
|
72
|
+
mountInit;
|
|
73
|
+
static eventName = mediaunmatchEventName;
|
|
74
|
+
constructor(mountInit) {
|
|
75
|
+
super(MediaUnmatchEvent.eventName);
|
|
76
|
+
this.mountInit = mountInit;
|
|
43
77
|
}
|
|
44
78
|
}
|
package/Events.ts
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
// Event classes for MountObserver
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
import type { IMountEvent, IDismountEvent, IAttrChangeEvent, AttrChange, MountInit, DismountReason } from './types.js';
|
|
3
|
+
|
|
4
|
+
// Event name constants
|
|
5
|
+
export const loadEventName = 'load';
|
|
6
|
+
export const mountEventName = 'mount';
|
|
7
|
+
export const dismountEventName = 'dismount';
|
|
8
|
+
export const disconnectEventName = 'disconnect';
|
|
9
|
+
export const attrchangeEventName = 'attrchange';
|
|
10
|
+
export const mediamatchEventName = 'mediamatch';
|
|
11
|
+
export const mediaunmatchEventName = 'mediaunmatch';
|
|
10
12
|
|
|
11
13
|
export class MountEvent extends Event implements IMountEvent {
|
|
12
14
|
static eventName: typeof mountEventName = mountEventName;
|
|
13
15
|
|
|
14
|
-
constructor(public matchingElement: Element, public modules: any[]) {
|
|
16
|
+
constructor(public matchingElement: Element, public modules: any[], public mountInit: MountInit) {
|
|
15
17
|
super(MountEvent.eventName);
|
|
16
18
|
}
|
|
17
19
|
}
|
|
@@ -19,7 +21,7 @@ export class MountEvent extends Event implements IMountEvent {
|
|
|
19
21
|
export class DismountEvent extends Event implements IDismountEvent {
|
|
20
22
|
static eventName: typeof dismountEventName = dismountEventName;
|
|
21
23
|
|
|
22
|
-
constructor(public matchingElement: Element) {
|
|
24
|
+
constructor(public matchingElement: Element, public reason: DismountReason, public mountInit: MountInit) {
|
|
23
25
|
super(DismountEvent.eventName);
|
|
24
26
|
}
|
|
25
27
|
}
|
|
@@ -27,7 +29,7 @@ export class DismountEvent extends Event implements IDismountEvent {
|
|
|
27
29
|
export class DisconnectEvent extends Event {
|
|
28
30
|
static eventName: typeof disconnectEventName = disconnectEventName;
|
|
29
31
|
|
|
30
|
-
constructor(public matchingElement: Element) {
|
|
32
|
+
constructor(public matchingElement: Element, public mountInit: MountInit) {
|
|
31
33
|
super(DisconnectEvent.eventName);
|
|
32
34
|
}
|
|
33
35
|
}
|
|
@@ -35,7 +37,7 @@ export class DisconnectEvent extends Event {
|
|
|
35
37
|
export class LoadEvent extends Event {
|
|
36
38
|
static eventName: typeof loadEventName = loadEventName;
|
|
37
39
|
|
|
38
|
-
constructor(public modules: any[]) {
|
|
40
|
+
constructor(public modules: any[], public mountInit: MountInit) {
|
|
39
41
|
super(LoadEvent.eventName);
|
|
40
42
|
}
|
|
41
43
|
}
|
|
@@ -43,7 +45,23 @@ export class LoadEvent extends Event {
|
|
|
43
45
|
export class AttrChangeEvent extends Event implements IAttrChangeEvent {
|
|
44
46
|
static eventName: typeof attrchangeEventName = attrchangeEventName;
|
|
45
47
|
|
|
46
|
-
constructor(public changes: AttrChange[]) {
|
|
48
|
+
constructor(public changes: AttrChange[], public mountInit: MountInit) {
|
|
47
49
|
super(AttrChangeEvent.eventName);
|
|
48
50
|
}
|
|
49
51
|
}
|
|
52
|
+
|
|
53
|
+
export class MediaMatchEvent extends Event {
|
|
54
|
+
static eventName: typeof mediamatchEventName = mediamatchEventName;
|
|
55
|
+
|
|
56
|
+
constructor(public mountInit: MountInit) {
|
|
57
|
+
super(MediaMatchEvent.eventName);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export class MediaUnmatchEvent extends Event {
|
|
62
|
+
static eventName: typeof mediaunmatchEventName = mediaunmatchEventName;
|
|
63
|
+
|
|
64
|
+
constructor(public mountInit: MountInit) {
|
|
65
|
+
super(MediaUnmatchEvent.eventName);
|
|
66
|
+
}
|
|
67
|
+
}
|
package/MountObserver.js
CHANGED
|
@@ -1,22 +1,37 @@
|
|
|
1
|
-
import { MountEvent, DismountEvent, DisconnectEvent, LoadEvent, AttrChangeEvent } from './Events.js';
|
|
1
|
+
import { MountEvent, DismountEvent, DisconnectEvent, LoadEvent, AttrChangeEvent, } from './Events.js';
|
|
2
|
+
import { registerSharedObserver, unregisterSharedObserver } from './SharedMutationObserver.js';
|
|
3
|
+
import { whereOutside } from './whereOutside.js';
|
|
2
4
|
export class MountObserver extends EventTarget {
|
|
3
5
|
#init;
|
|
4
6
|
#options;
|
|
5
7
|
#abortController;
|
|
6
8
|
#modules = [];
|
|
7
|
-
#mountedElements =
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
#mountedElements = {
|
|
10
|
+
weakSet: new WeakSet(),
|
|
11
|
+
setWeak: new Set()
|
|
12
|
+
};
|
|
13
|
+
#processedDoForElement = new WeakSet();
|
|
14
|
+
#processedEventsForElement = new WeakMap();
|
|
15
|
+
#mutationCallback;
|
|
10
16
|
#rootNode;
|
|
11
17
|
#importsLoaded = false;
|
|
12
18
|
#elementAttrStates = new WeakMap();
|
|
19
|
+
#elementOnceAttrs = new WeakMap();
|
|
13
20
|
#matchesWhereAttrFn = null;
|
|
14
21
|
#buildAttrCoordinateMapFn = null;
|
|
22
|
+
#checkAttrChangesFn = null;
|
|
23
|
+
#mediaQueryCleanup;
|
|
24
|
+
#mediaMatches = true;
|
|
25
|
+
#assignGingerlySource;
|
|
15
26
|
constructor(init, options = {}) {
|
|
16
27
|
super();
|
|
17
28
|
this.#init = init;
|
|
18
29
|
this.#options = options;
|
|
19
30
|
this.#abortController = new AbortController();
|
|
31
|
+
// Make a copy of assignGingerly config using structuredClone
|
|
32
|
+
if (init.assignGingerly !== undefined) {
|
|
33
|
+
this.#assignGingerlySource = structuredClone(init.assignGingerly);
|
|
34
|
+
}
|
|
20
35
|
if (options.disconnectedSignal) {
|
|
21
36
|
options.disconnectedSignal.addEventListener('abort', () => {
|
|
22
37
|
this.disconnect();
|
|
@@ -40,6 +55,22 @@ export class MountObserver extends EventTarget {
|
|
|
40
55
|
const { buildAttrCoordinateMap } = await import('./attrCoordinates.js');
|
|
41
56
|
this.#buildAttrCoordinateMapFn = buildAttrCoordinateMap;
|
|
42
57
|
}
|
|
58
|
+
if (!this.#checkAttrChangesFn) {
|
|
59
|
+
const { checkAttrChanges } = await import('./attrChanges.js');
|
|
60
|
+
// Create a bound function that passes the required parameters
|
|
61
|
+
this.#checkAttrChangesFn = (element) => {
|
|
62
|
+
return checkAttrChanges(element, this.#init, this.#buildAttrCoordinateMapFn, this.#elementAttrStates, this.#elementOnceAttrs);
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async #setupMediaQuery() {
|
|
67
|
+
if (!this.#rootNode) {
|
|
68
|
+
throw new Error('Cannot setup media query before observe() is called');
|
|
69
|
+
}
|
|
70
|
+
const { setupMediaQuery } = await import('./mediaQuery.js');
|
|
71
|
+
const result = setupMediaQuery(this.#init, this.#rootNode, this.#mountedElements, this.#modules, this, (node) => this.#processNode(node));
|
|
72
|
+
this.#mediaMatches = result.mediaMatches;
|
|
73
|
+
this.#mediaQueryCleanup = result.cleanup;
|
|
43
74
|
}
|
|
44
75
|
get disconnectedSignal() {
|
|
45
76
|
return this.#abortController.signal;
|
|
@@ -49,14 +80,24 @@ export class MountObserver extends EventTarget {
|
|
|
49
80
|
throw new Error('Already observing');
|
|
50
81
|
}
|
|
51
82
|
this.#rootNode = new WeakRef(rootNode);
|
|
83
|
+
// Set up media query if specified (needs rootNode to be set first)
|
|
84
|
+
if (this.#init.whereMediaMatches) {
|
|
85
|
+
await this.#setupMediaQuery();
|
|
86
|
+
}
|
|
52
87
|
// Wait for whereAttr utilities to load if needed
|
|
53
88
|
if (this.#init.whereAttr && !this.#matchesWhereAttrFn) {
|
|
54
89
|
await this.#preloadWhereAttrUtilities();
|
|
55
90
|
}
|
|
56
|
-
// Process existing elements
|
|
57
|
-
this.#
|
|
58
|
-
|
|
59
|
-
|
|
91
|
+
// Process existing elements only if media matches
|
|
92
|
+
if (this.#mediaMatches) {
|
|
93
|
+
this.#processNode(rootNode);
|
|
94
|
+
}
|
|
95
|
+
// Create mutation callback
|
|
96
|
+
this.#mutationCallback = (mutations) => {
|
|
97
|
+
// Skip processing if media doesn't match
|
|
98
|
+
if (!this.#mediaMatches) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
60
101
|
const attrChanges = [];
|
|
61
102
|
for (const mutation of mutations) {
|
|
62
103
|
if (mutation.type === 'childList') {
|
|
@@ -74,17 +115,17 @@ export class MountObserver extends EventTarget {
|
|
|
74
115
|
else if (mutation.type === 'attributes' && mutation.target.nodeType === Node.ELEMENT_NODE) {
|
|
75
116
|
// Handle attribute changes for mounted elements
|
|
76
117
|
const element = mutation.target;
|
|
77
|
-
if (this.#mountedElements.has(element) && this.#
|
|
78
|
-
const changes = this.#
|
|
118
|
+
if (this.#mountedElements.weakSet.has(element) && this.#checkAttrChangesFn) {
|
|
119
|
+
const changes = this.#checkAttrChangesFn(element);
|
|
79
120
|
attrChanges.push(...changes);
|
|
80
121
|
}
|
|
81
122
|
}
|
|
82
123
|
}
|
|
83
124
|
// Batch and dispatch attribute changes
|
|
84
125
|
if (attrChanges.length > 0) {
|
|
85
|
-
this.dispatchEvent(new AttrChangeEvent(attrChanges));
|
|
126
|
+
this.dispatchEvent(new AttrChangeEvent(attrChanges, this.#init));
|
|
86
127
|
}
|
|
87
|
-
}
|
|
128
|
+
};
|
|
88
129
|
const observerConfig = {
|
|
89
130
|
childList: true,
|
|
90
131
|
subtree: true
|
|
@@ -94,12 +135,20 @@ export class MountObserver extends EventTarget {
|
|
|
94
135
|
observerConfig.attributes = true;
|
|
95
136
|
observerConfig.attributeOldValue = true;
|
|
96
137
|
}
|
|
97
|
-
|
|
138
|
+
// Register with shared mutation observer
|
|
139
|
+
registerSharedObserver(rootNode, this.#mutationCallback, observerConfig);
|
|
98
140
|
}
|
|
99
141
|
disconnect() {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
142
|
+
const rootNode = this.#rootNode?.deref();
|
|
143
|
+
// Unregister from shared mutation observer
|
|
144
|
+
if (rootNode && this.#mutationCallback) {
|
|
145
|
+
unregisterSharedObserver(rootNode, this.#mutationCallback);
|
|
146
|
+
this.#mutationCallback = undefined;
|
|
147
|
+
}
|
|
148
|
+
// Remove media query listener
|
|
149
|
+
if (this.#mediaQueryCleanup) {
|
|
150
|
+
this.#mediaQueryCleanup();
|
|
151
|
+
this.#mediaQueryCleanup = undefined;
|
|
103
152
|
}
|
|
104
153
|
this.#abortController.abort();
|
|
105
154
|
this.#rootNode = undefined;
|
|
@@ -112,7 +161,7 @@ export class MountObserver extends EventTarget {
|
|
|
112
161
|
const { loadImports } = await import('./loadImports.js');
|
|
113
162
|
this.#modules = await loadImports(this.#init.import);
|
|
114
163
|
this.#importsLoaded = true;
|
|
115
|
-
this.dispatchEvent(new LoadEvent(this.#modules));
|
|
164
|
+
this.dispatchEvent(new LoadEvent(this.#modules, this.#init));
|
|
116
165
|
}
|
|
117
166
|
#processNode(node) {
|
|
118
167
|
// If it's an element node, check if it matches
|
|
@@ -125,49 +174,67 @@ export class MountObserver extends EventTarget {
|
|
|
125
174
|
// Process children
|
|
126
175
|
if (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.DOCUMENT_NODE) {
|
|
127
176
|
const root = node;
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
}
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
else {
|
|
139
|
-
// Optimize: use querySelectorAll directly when no whereAttr
|
|
140
|
-
root.querySelectorAll(this.#init.whereElementMatches).forEach(child => {
|
|
177
|
+
// Get all elements matching the CSS selector first
|
|
178
|
+
root.querySelectorAll(this.#init.whereElementMatches).forEach(child => {
|
|
179
|
+
if (this.#matchesSelector(child)) {
|
|
141
180
|
this.#handleMatch(child);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
181
|
+
}
|
|
182
|
+
});
|
|
144
183
|
}
|
|
145
184
|
}
|
|
146
185
|
#matchesSelector(element) {
|
|
186
|
+
//TODO: reduce redundncy with this.#init?
|
|
147
187
|
// Check whereElementMatches condition
|
|
148
188
|
const matchesElement = element.matches(this.#init.whereElementMatches);
|
|
149
|
-
|
|
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');
|
|
189
|
+
if (!matchesElement) {
|
|
156
190
|
return false;
|
|
157
191
|
}
|
|
158
|
-
//
|
|
159
|
-
|
|
192
|
+
// Check whereOutside condition if specified (donut hole scoping)
|
|
193
|
+
if (this.#init.whereOutside) {
|
|
194
|
+
const rootNode = this.#rootNode?.deref();
|
|
195
|
+
if (!rootNode || !whereOutside(rootNode, element, this.#init.whereOutside)) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Check whereAttr condition if specified
|
|
200
|
+
if (this.#init.whereAttr) {
|
|
201
|
+
// Use cached function (should be loaded by now from constructor)
|
|
202
|
+
if (!this.#matchesWhereAttrFn) {
|
|
203
|
+
console.warn('whereAttr utilities not loaded yet');
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
if (!this.#matchesWhereAttrFn(element, this.#init.whereAttr)) {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// Check whereInstanceOf condition if specified
|
|
211
|
+
if (this.#init.whereInstanceOf) {
|
|
212
|
+
const constructors = Array.isArray(this.#init.whereInstanceOf)
|
|
213
|
+
? this.#init.whereInstanceOf
|
|
214
|
+
: [this.#init.whereInstanceOf];
|
|
215
|
+
// Element must be an instance of at least one constructor (OR logic for array)
|
|
216
|
+
const matchesInstanceOf = constructors.some(constructor => element instanceof constructor);
|
|
217
|
+
if (!matchesInstanceOf) {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// All conditions passed
|
|
222
|
+
return true;
|
|
160
223
|
}
|
|
161
224
|
async #handleMatch(element) {
|
|
162
|
-
if (this.#
|
|
225
|
+
if (this.#processedDoForElement.has(element)) {
|
|
163
226
|
return;
|
|
164
227
|
}
|
|
165
228
|
// Load imports if not already loaded
|
|
166
229
|
if (!this.#importsLoaded && this.#init.import) {
|
|
167
230
|
await this.#loadImports();
|
|
168
231
|
}
|
|
169
|
-
this.#
|
|
170
|
-
|
|
232
|
+
this.#processedDoForElement.add(element);
|
|
233
|
+
// Add to both WeakSet and Set<WeakRef> for efficient operations
|
|
234
|
+
if (!this.#mountedElements.weakSet.has(element)) {
|
|
235
|
+
this.#mountedElements.weakSet.add(element);
|
|
236
|
+
this.#mountedElements.setWeak.add(new WeakRef(element));
|
|
237
|
+
}
|
|
171
238
|
const rootNode = this.#rootNode?.deref();
|
|
172
239
|
if (!rootNode) {
|
|
173
240
|
// Root node was garbage collected
|
|
@@ -181,9 +248,9 @@ export class MountObserver extends EventTarget {
|
|
|
181
248
|
}
|
|
182
249
|
};
|
|
183
250
|
// Apply assignGingerly if specified
|
|
184
|
-
if (this.#
|
|
251
|
+
if (this.#assignGingerlySource) {
|
|
185
252
|
const { assignGingerly } = await import('assign-gingerly/index.js');
|
|
186
|
-
assignGingerly(element, this.#
|
|
253
|
+
assignGingerly(element, this.#assignGingerlySource);
|
|
187
254
|
}
|
|
188
255
|
// Call do callback
|
|
189
256
|
if (this.#init.do) {
|
|
@@ -195,68 +262,56 @@ export class MountObserver extends EventTarget {
|
|
|
195
262
|
}
|
|
196
263
|
}
|
|
197
264
|
// Dispatch mount event
|
|
198
|
-
this.dispatchEvent(new MountEvent(element, this.#modules));
|
|
265
|
+
this.dispatchEvent(new MountEvent(element, this.#modules, this.#init));
|
|
266
|
+
// Emit events from mounted element if configured
|
|
267
|
+
if (this.#init.mountedElemEmits) {
|
|
268
|
+
const { emitMountedElementEvents } = await import('./emitEvents.js');
|
|
269
|
+
await emitMountedElementEvents(element, this.#init, this.#processedEventsForElement);
|
|
270
|
+
}
|
|
199
271
|
// Check for initial attribute changes if whereAttr is configured
|
|
200
|
-
if (this.#
|
|
201
|
-
const changes = this.#
|
|
272
|
+
if (this.#checkAttrChangesFn) {
|
|
273
|
+
const changes = this.#checkAttrChangesFn(element);
|
|
202
274
|
if (changes.length > 0) {
|
|
203
|
-
this.dispatchEvent(new AttrChangeEvent(changes));
|
|
275
|
+
this.dispatchEvent(new AttrChangeEvent(changes, this.#init));
|
|
204
276
|
}
|
|
205
277
|
}
|
|
206
278
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
if (
|
|
216
|
-
|
|
217
|
-
this.#
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const
|
|
226
|
-
if (
|
|
227
|
-
|
|
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
|
-
}
|
|
279
|
+
async assignGingerly(config) {
|
|
280
|
+
// Handle undefined case
|
|
281
|
+
if (config === undefined) {
|
|
282
|
+
this.#assignGingerlySource = undefined;
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const { assignGingerly } = await import('assign-gingerly/index.js');
|
|
286
|
+
// Update the source config for future mounted elements
|
|
287
|
+
if (this.#assignGingerlySource === undefined) {
|
|
288
|
+
// No existing config, just clone the passed in object
|
|
289
|
+
this.#assignGingerlySource = structuredClone(config);
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
// Merge into existing config using assignGingerly
|
|
293
|
+
assignGingerly(this.#assignGingerlySource, config);
|
|
294
|
+
}
|
|
295
|
+
// Apply to already mounted elements using setWeak for iteration
|
|
296
|
+
for (const ref of this.#mountedElements.setWeak) {
|
|
297
|
+
const element = ref.deref();
|
|
298
|
+
if (element) {
|
|
299
|
+
assignGingerly(element, config);
|
|
251
300
|
}
|
|
252
301
|
}
|
|
253
|
-
return changes;
|
|
254
302
|
}
|
|
255
303
|
#handleRemoval(element) {
|
|
256
|
-
if (!this.#mountedElements.has(element)) {
|
|
304
|
+
if (!this.#mountedElements.weakSet.has(element)) {
|
|
257
305
|
return;
|
|
258
306
|
}
|
|
259
|
-
|
|
307
|
+
// Remove from both structures
|
|
308
|
+
this.#mountedElements.weakSet.delete(element);
|
|
309
|
+
for (const ref of this.#mountedElements.setWeak) {
|
|
310
|
+
if (ref.deref() === element) {
|
|
311
|
+
this.#mountedElements.setWeak.delete(ref);
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
260
315
|
const rootNode = this.#rootNode?.deref();
|
|
261
316
|
if (!rootNode) {
|
|
262
317
|
// Root node was garbage collected
|
|
@@ -274,7 +329,7 @@ export class MountObserver extends EventTarget {
|
|
|
274
329
|
this.#init.do.dismount(element, context);
|
|
275
330
|
}
|
|
276
331
|
// Dispatch dismount event
|
|
277
|
-
this.dispatchEvent(new DismountEvent(element));
|
|
332
|
+
this.dispatchEvent(new DismountEvent(element, 'where-element-matches-failed', this.#init));
|
|
278
333
|
// Check if element is being moved within the same root
|
|
279
334
|
// If it's truly disconnected, dispatch disconnect event
|
|
280
335
|
setTimeout(() => {
|
|
@@ -282,7 +337,7 @@ export class MountObserver extends EventTarget {
|
|
|
282
337
|
if (this.#init.do && typeof this.#init.do !== 'function' && this.#init.do.disconnect) {
|
|
283
338
|
this.#init.do.disconnect(element, context);
|
|
284
339
|
}
|
|
285
|
-
this.dispatchEvent(new DisconnectEvent(element));
|
|
340
|
+
this.dispatchEvent(new DisconnectEvent(element, this.#init));
|
|
286
341
|
}
|
|
287
342
|
}, 0);
|
|
288
343
|
}
|