mount-observer 0.1.1 → 0.1.3
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/DefineCustomElementHandler.js +64 -0
- package/DefineCustomElementHandler.ts +77 -0
- package/Events.js +11 -9
- package/Events.ts +9 -4
- package/EvtRt.js +34 -0
- package/EvtRt.ts +42 -0
- package/MountObserver.js +275 -106
- package/MountObserver.ts +328 -118
- package/README.md +857 -203
- package/SharedMutationObserver.js +9 -6
- package/SharedMutationObserver.ts +11 -8
- package/arr.js +13 -0
- package/arr.ts +13 -0
- package/attrChanges.js +70 -0
- package/attrChanges.ts +90 -0
- package/emitEvents.js +103 -0
- package/emitEvents.ts +126 -0
- package/index.js +13 -1
- package/index.ts +14 -1
- package/loadImports.js +2 -1
- package/loadImports.ts +2 -1
- package/mediaQuery.js +15 -17
- package/mediaQuery.ts +18 -20
- package/package.json +27 -3
- package/types.d.ts +38 -15
- package/whereOutside.js +19 -0
- package/whereOutside.ts +25 -0
|
@@ -14,6 +14,12 @@ const sharedObservers = new WeakMap();
|
|
|
14
14
|
export function registerSharedObserver(rootNode, callback, config) {
|
|
15
15
|
let sharedData = sharedObservers.get(rootNode);
|
|
16
16
|
if (!sharedData) {
|
|
17
|
+
// Create shared data structure first
|
|
18
|
+
sharedData = {
|
|
19
|
+
observer: null, // Will be set immediately below
|
|
20
|
+
callbacks: new Set(),
|
|
21
|
+
config
|
|
22
|
+
};
|
|
17
23
|
// Create new shared observer for this root node
|
|
18
24
|
const observer = new MutationObserver((mutations) => {
|
|
19
25
|
// Distribute mutations to all registered callbacks
|
|
@@ -22,13 +28,10 @@ export function registerSharedObserver(rootNode, callback, config) {
|
|
|
22
28
|
cb(mutations);
|
|
23
29
|
}
|
|
24
30
|
});
|
|
25
|
-
observer
|
|
26
|
-
sharedData = {
|
|
27
|
-
observer,
|
|
28
|
-
callbacks: new Set(),
|
|
29
|
-
config
|
|
30
|
-
};
|
|
31
|
+
sharedData.observer = observer;
|
|
31
32
|
sharedObservers.set(rootNode, sharedData);
|
|
33
|
+
// Start observing after everything is set up
|
|
34
|
+
observer.observe(rootNode, config);
|
|
32
35
|
}
|
|
33
36
|
else {
|
|
34
37
|
// Verify config matches (for safety)
|
|
@@ -29,6 +29,13 @@ export function registerSharedObserver(
|
|
|
29
29
|
let sharedData = sharedObservers.get(rootNode);
|
|
30
30
|
|
|
31
31
|
if (!sharedData) {
|
|
32
|
+
// Create shared data structure first
|
|
33
|
+
sharedData = {
|
|
34
|
+
observer: null as any, // Will be set immediately below
|
|
35
|
+
callbacks: new Set(),
|
|
36
|
+
config
|
|
37
|
+
};
|
|
38
|
+
|
|
32
39
|
// Create new shared observer for this root node
|
|
33
40
|
const observer = new MutationObserver((mutations) => {
|
|
34
41
|
// Distribute mutations to all registered callbacks
|
|
@@ -38,15 +45,11 @@ export function registerSharedObserver(
|
|
|
38
45
|
}
|
|
39
46
|
});
|
|
40
47
|
|
|
41
|
-
observer
|
|
42
|
-
|
|
43
|
-
sharedData = {
|
|
44
|
-
observer,
|
|
45
|
-
callbacks: new Set(),
|
|
46
|
-
config
|
|
47
|
-
};
|
|
48
|
-
|
|
48
|
+
sharedData.observer = observer;
|
|
49
49
|
sharedObservers.set(rootNode, sharedData);
|
|
50
|
+
|
|
51
|
+
// Start observing after everything is set up
|
|
52
|
+
observer.observe(rootNode, config);
|
|
50
53
|
} else {
|
|
51
54
|
// Verify config matches (for safety)
|
|
52
55
|
// In practice, all MountObservers should use the same config
|
package/arr.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility function to normalize a value to an array.
|
|
3
|
+
* - If undefined, returns empty array
|
|
4
|
+
* - If already an array, returns as-is
|
|
5
|
+
* - Otherwise, wraps the value in an array
|
|
6
|
+
*
|
|
7
|
+
* @param inp - Value to normalize to array
|
|
8
|
+
* @returns Array containing the value(s)
|
|
9
|
+
*/
|
|
10
|
+
export function arr(inp) {
|
|
11
|
+
return inp === undefined ? []
|
|
12
|
+
: Array.isArray(inp) ? inp : [inp];
|
|
13
|
+
}
|
package/arr.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility function to normalize a value to an array.
|
|
3
|
+
* - If undefined, returns empty array
|
|
4
|
+
* - If already an array, returns as-is
|
|
5
|
+
* - Otherwise, wraps the value in an array
|
|
6
|
+
*
|
|
7
|
+
* @param inp - Value to normalize to array
|
|
8
|
+
* @returns Array containing the value(s)
|
|
9
|
+
*/
|
|
10
|
+
export function arr<T = any>(inp: T | T[] | undefined): T[] {
|
|
11
|
+
return inp === undefined ? []
|
|
12
|
+
: Array.isArray(inp) ? inp : [inp];
|
|
13
|
+
}
|
package/attrChanges.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks for attribute changes on a mounted element.
|
|
3
|
+
* This module is dynamically loaded only when whereAttr is configured.
|
|
4
|
+
*/
|
|
5
|
+
export function checkAttrChanges(element, mountInit, buildAttrCoordinateMapFn, elementAttrStates, elementOnceAttrs) {
|
|
6
|
+
if (!mountInit.whereAttr || !buildAttrCoordinateMapFn) {
|
|
7
|
+
return [];
|
|
8
|
+
}
|
|
9
|
+
const isCustomElement = element.tagName.toLowerCase().includes('-');
|
|
10
|
+
const attrCoordMap = buildAttrCoordinateMapFn(mountInit.whereAttr, isCustomElement);
|
|
11
|
+
// Get or create the attribute state for this element
|
|
12
|
+
let attrState = elementAttrStates.get(element);
|
|
13
|
+
if (!attrState) {
|
|
14
|
+
attrState = new Map();
|
|
15
|
+
elementAttrStates.set(element, attrState);
|
|
16
|
+
}
|
|
17
|
+
const changes = [];
|
|
18
|
+
const currentAttrs = new Set();
|
|
19
|
+
// Check all possible attributes from the coordinate map
|
|
20
|
+
for (const attrName of Object.keys(attrCoordMap)) {
|
|
21
|
+
const coordinate = attrCoordMap[attrName];
|
|
22
|
+
const currentValue = element.getAttribute(attrName);
|
|
23
|
+
const previousValue = attrState.get(attrName);
|
|
24
|
+
if (currentValue !== null) {
|
|
25
|
+
currentAttrs.add(attrName);
|
|
26
|
+
}
|
|
27
|
+
// Check if this attribute has "once: true" in its map entry
|
|
28
|
+
const mapEntry = mountInit.map?.[coordinate] || null;
|
|
29
|
+
const isOnce = mapEntry?.once === true;
|
|
30
|
+
// If "once" is true, check if we've already seen this attribute
|
|
31
|
+
if (isOnce) {
|
|
32
|
+
let onceAttrs = elementOnceAttrs.get(element);
|
|
33
|
+
if (!onceAttrs) {
|
|
34
|
+
onceAttrs = new Set();
|
|
35
|
+
elementOnceAttrs.set(element, onceAttrs);
|
|
36
|
+
}
|
|
37
|
+
// If we've already seen this attribute, skip it
|
|
38
|
+
if (onceAttrs.has(attrName)) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
// Mark this attribute as seen if it currently has a value
|
|
42
|
+
if (currentValue !== null) {
|
|
43
|
+
onceAttrs.add(attrName);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Include if: currently has value OR previously had value but now removed
|
|
47
|
+
if (currentValue !== null || (previousValue !== undefined && currentValue === null)) {
|
|
48
|
+
// Check if value changed
|
|
49
|
+
if (currentValue !== previousValue) {
|
|
50
|
+
const attrNode = currentValue !== null ? element.getAttributeNode(attrName) : null;
|
|
51
|
+
changes.push({
|
|
52
|
+
value: currentValue,
|
|
53
|
+
attrNode,
|
|
54
|
+
mapEntry,
|
|
55
|
+
attrName,
|
|
56
|
+
coordinate,
|
|
57
|
+
element
|
|
58
|
+
});
|
|
59
|
+
// Update state
|
|
60
|
+
if (currentValue !== null) {
|
|
61
|
+
attrState.set(attrName, currentValue);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
attrState.delete(attrName);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return changes;
|
|
70
|
+
}
|
package/attrChanges.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { AttrChange, MountInit } from './types.d.ts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Checks for attribute changes on a mounted element.
|
|
5
|
+
* This module is dynamically loaded only when whereAttr is configured.
|
|
6
|
+
*/
|
|
7
|
+
export function checkAttrChanges(
|
|
8
|
+
element: Element,
|
|
9
|
+
mountInit: MountInit,
|
|
10
|
+
buildAttrCoordinateMapFn: (whereAttr: any, isCustomElement: boolean) => any,
|
|
11
|
+
elementAttrStates: WeakMap<Element, Map<string, string | null>>,
|
|
12
|
+
elementOnceAttrs: WeakMap<Element, Set<string>>
|
|
13
|
+
): AttrChange[] {
|
|
14
|
+
if (!mountInit.whereAttr || !buildAttrCoordinateMapFn) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const isCustomElement = element.tagName.toLowerCase().includes('-');
|
|
19
|
+
const attrCoordMap = buildAttrCoordinateMapFn(mountInit.whereAttr, isCustomElement);
|
|
20
|
+
|
|
21
|
+
// Get or create the attribute state for this element
|
|
22
|
+
let attrState = elementAttrStates.get(element);
|
|
23
|
+
if (!attrState) {
|
|
24
|
+
attrState = new Map<string, string | null>();
|
|
25
|
+
elementAttrStates.set(element, attrState);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const changes: AttrChange[] = [];
|
|
29
|
+
const currentAttrs = new Set<string>();
|
|
30
|
+
|
|
31
|
+
// Check all possible attributes from the coordinate map
|
|
32
|
+
for (const attrName of Object.keys(attrCoordMap)) {
|
|
33
|
+
const coordinate = attrCoordMap[attrName];
|
|
34
|
+
const currentValue = element.getAttribute(attrName);
|
|
35
|
+
const previousValue = attrState.get(attrName);
|
|
36
|
+
|
|
37
|
+
if (currentValue !== null) {
|
|
38
|
+
currentAttrs.add(attrName);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Check if this attribute has "once: true" in its map entry
|
|
42
|
+
const mapEntry = mountInit.map?.[coordinate] || null;
|
|
43
|
+
const isOnce = mapEntry?.once === true;
|
|
44
|
+
|
|
45
|
+
// If "once" is true, check if we've already seen this attribute
|
|
46
|
+
if (isOnce) {
|
|
47
|
+
let onceAttrs = elementOnceAttrs.get(element);
|
|
48
|
+
if (!onceAttrs) {
|
|
49
|
+
onceAttrs = new Set<string>();
|
|
50
|
+
elementOnceAttrs.set(element, onceAttrs);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// If we've already seen this attribute, skip it
|
|
54
|
+
if (onceAttrs.has(attrName)) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Mark this attribute as seen if it currently has a value
|
|
59
|
+
if (currentValue !== null) {
|
|
60
|
+
onceAttrs.add(attrName);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Include if: currently has value OR previously had value but now removed
|
|
65
|
+
if (currentValue !== null || (previousValue !== undefined && currentValue === null)) {
|
|
66
|
+
// Check if value changed
|
|
67
|
+
if (currentValue !== previousValue) {
|
|
68
|
+
const attrNode = currentValue !== null ? element.getAttributeNode(attrName) : null;
|
|
69
|
+
|
|
70
|
+
changes.push({
|
|
71
|
+
value: currentValue,
|
|
72
|
+
attrNode,
|
|
73
|
+
mapEntry,
|
|
74
|
+
attrName,
|
|
75
|
+
coordinate,
|
|
76
|
+
element
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Update state
|
|
80
|
+
if (currentValue !== null) {
|
|
81
|
+
attrState.set(attrName, currentValue);
|
|
82
|
+
} else {
|
|
83
|
+
attrState.delete(attrName);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return changes;
|
|
90
|
+
}
|
package/emitEvents.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emits events from a mounted element based on the mountedElemEmits configuration.
|
|
3
|
+
* This module is dynamically loaded only when mountedElemEmits is configured.
|
|
4
|
+
*/
|
|
5
|
+
export async function emitMountedElementEvents(element, mountInit, processedEventsForElement) {
|
|
6
|
+
const configs = Array.isArray(mountInit.mountedElemEmits)
|
|
7
|
+
? mountInit.mountedElemEmits
|
|
8
|
+
: [mountInit.mountedElemEmits];
|
|
9
|
+
for (const config of configs) {
|
|
10
|
+
await emitSingleEvent(element, mountInit, config, processedEventsForElement);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
async function emitSingleEvent(element, mountInit, config, processedEventsForElement) {
|
|
14
|
+
// Check if this event should only fire once per element
|
|
15
|
+
if (config.oncePerMountedElement) {
|
|
16
|
+
const eventId = getEventId(config);
|
|
17
|
+
let processedEvents = processedEventsForElement.get(element);
|
|
18
|
+
if (!processedEvents) {
|
|
19
|
+
processedEvents = new Set();
|
|
20
|
+
processedEventsForElement.set(element, processedEvents);
|
|
21
|
+
}
|
|
22
|
+
if (processedEvents.has(eventId)) {
|
|
23
|
+
return; // Already emitted for this element
|
|
24
|
+
}
|
|
25
|
+
processedEvents.add(eventId);
|
|
26
|
+
}
|
|
27
|
+
// Resolve event constructor
|
|
28
|
+
const EventCtor = resolveEventConstructor(config.event);
|
|
29
|
+
// Process args with magic string substitution
|
|
30
|
+
const processedArgs = config.args !== undefined
|
|
31
|
+
? processMagicStrings(config.args, element, mountInit)
|
|
32
|
+
: undefined;
|
|
33
|
+
// Construct the event
|
|
34
|
+
let event;
|
|
35
|
+
if (processedArgs === undefined) {
|
|
36
|
+
event = new EventCtor();
|
|
37
|
+
}
|
|
38
|
+
else if (Array.isArray(processedArgs)) {
|
|
39
|
+
// For array args, ensure bubbles is set if second arg is an options object
|
|
40
|
+
if (processedArgs.length === 2 && typeof processedArgs[0] === 'string' && typeof processedArgs[1] === 'object' && processedArgs[1] !== null) {
|
|
41
|
+
// Merge bubbles: true into the options object if not already set
|
|
42
|
+
const options = { bubbles: true, ...processedArgs[1] };
|
|
43
|
+
event = new EventCtor(processedArgs[0], options);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
event = new EventCtor(...processedArgs);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
// Single arg - if it's a string (event name), add bubbles: true by default
|
|
51
|
+
if (typeof processedArgs === 'string') {
|
|
52
|
+
event = new EventCtor(processedArgs, { bubbles: true });
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
event = new EventCtor(processedArgs);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Apply eventProps if specified
|
|
59
|
+
if (config.eventProps) {
|
|
60
|
+
const { assignGingerly } = await import('assign-gingerly/assignGingerly.js');
|
|
61
|
+
const processedProps = processMagicStrings(config.eventProps, element, mountInit);
|
|
62
|
+
assignGingerly(event, processedProps);
|
|
63
|
+
}
|
|
64
|
+
// Dispatch the event from the mounted element
|
|
65
|
+
element.dispatchEvent(event);
|
|
66
|
+
}
|
|
67
|
+
function resolveEventConstructor(event) {
|
|
68
|
+
if (typeof event === 'string') {
|
|
69
|
+
const EventCtor = globalThis[event];
|
|
70
|
+
if (!EventCtor || typeof EventCtor !== 'function') {
|
|
71
|
+
throw new Error(`Event constructor "${event}" not found in globalThis`);
|
|
72
|
+
}
|
|
73
|
+
return EventCtor;
|
|
74
|
+
}
|
|
75
|
+
return event;
|
|
76
|
+
}
|
|
77
|
+
function getEventId(config) {
|
|
78
|
+
const eventName = typeof config.event === 'string' ? config.event : config.event.name;
|
|
79
|
+
const argsStr = JSON.stringify(config.args || '');
|
|
80
|
+
return `${eventName}:${argsStr}`;
|
|
81
|
+
}
|
|
82
|
+
function processMagicStrings(value, element, mountInit) {
|
|
83
|
+
if (typeof value === 'string') {
|
|
84
|
+
if (value === '{{mountedElement}}') {
|
|
85
|
+
return element;
|
|
86
|
+
}
|
|
87
|
+
if (value === '{{mountInit}}') {
|
|
88
|
+
return mountInit;
|
|
89
|
+
}
|
|
90
|
+
return value;
|
|
91
|
+
}
|
|
92
|
+
if (Array.isArray(value)) {
|
|
93
|
+
return value.map(item => processMagicStrings(item, element, mountInit));
|
|
94
|
+
}
|
|
95
|
+
if (value && typeof value === 'object') {
|
|
96
|
+
const processed = {};
|
|
97
|
+
for (const [key, val] of Object.entries(value)) {
|
|
98
|
+
processed[key] = processMagicStrings(val, element, mountInit);
|
|
99
|
+
}
|
|
100
|
+
return processed;
|
|
101
|
+
}
|
|
102
|
+
return value;
|
|
103
|
+
}
|
package/emitEvents.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { EventConfig, EventConstructor, MountInit } from './types.d.ts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Emits events from a mounted element based on the mountedElemEmits configuration.
|
|
5
|
+
* This module is dynamically loaded only when mountedElemEmits is configured.
|
|
6
|
+
*/
|
|
7
|
+
export async function emitMountedElementEvents(
|
|
8
|
+
element: Element,
|
|
9
|
+
mountInit: MountInit,
|
|
10
|
+
processedEventsForElement: WeakMap<Element, Set<string>>
|
|
11
|
+
): Promise<void> {
|
|
12
|
+
const configs = Array.isArray(mountInit.mountedElemEmits)
|
|
13
|
+
? mountInit.mountedElemEmits
|
|
14
|
+
: [mountInit.mountedElemEmits!];
|
|
15
|
+
|
|
16
|
+
for (const config of configs) {
|
|
17
|
+
await emitSingleEvent(element, mountInit, config, processedEventsForElement);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function emitSingleEvent(
|
|
22
|
+
element: Element,
|
|
23
|
+
mountInit: MountInit,
|
|
24
|
+
config: EventConfig,
|
|
25
|
+
processedEventsForElement: WeakMap<Element, Set<string>>
|
|
26
|
+
): Promise<void> {
|
|
27
|
+
// Check if this event should only fire once per element
|
|
28
|
+
if (config.oncePerMountedElement) {
|
|
29
|
+
const eventId = getEventId(config);
|
|
30
|
+
let processedEvents = processedEventsForElement.get(element);
|
|
31
|
+
|
|
32
|
+
if (!processedEvents) {
|
|
33
|
+
processedEvents = new Set<string>();
|
|
34
|
+
processedEventsForElement.set(element, processedEvents);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (processedEvents.has(eventId)) {
|
|
38
|
+
return; // Already emitted for this element
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
processedEvents.add(eventId);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Resolve event constructor
|
|
45
|
+
const EventCtor = resolveEventConstructor(config.event);
|
|
46
|
+
|
|
47
|
+
// Process args with magic string substitution
|
|
48
|
+
const processedArgs = config.args !== undefined
|
|
49
|
+
? processMagicStrings(config.args, element, mountInit)
|
|
50
|
+
: undefined;
|
|
51
|
+
|
|
52
|
+
// Construct the event
|
|
53
|
+
let event: Event;
|
|
54
|
+
if (processedArgs === undefined) {
|
|
55
|
+
event = new EventCtor();
|
|
56
|
+
} else if (Array.isArray(processedArgs)) {
|
|
57
|
+
// For array args, ensure bubbles is set if second arg is an options object
|
|
58
|
+
if (processedArgs.length === 2 && typeof processedArgs[0] === 'string' && typeof processedArgs[1] === 'object' && processedArgs[1] !== null) {
|
|
59
|
+
// Merge bubbles: true into the options object if not already set
|
|
60
|
+
const options = { bubbles: true, ...processedArgs[1] };
|
|
61
|
+
event = new EventCtor(processedArgs[0], options);
|
|
62
|
+
} else {
|
|
63
|
+
event = new EventCtor(...processedArgs);
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
// Single arg - if it's a string (event name), add bubbles: true by default
|
|
67
|
+
if (typeof processedArgs === 'string') {
|
|
68
|
+
event = new EventCtor(processedArgs, { bubbles: true });
|
|
69
|
+
} else {
|
|
70
|
+
event = new EventCtor(processedArgs);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Apply eventProps if specified
|
|
75
|
+
if (config.eventProps) {
|
|
76
|
+
const { assignGingerly } = await import('assign-gingerly/assignGingerly.js');
|
|
77
|
+
const processedProps = processMagicStrings(config.eventProps, element, mountInit);
|
|
78
|
+
assignGingerly(event, processedProps);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Dispatch the event from the mounted element
|
|
82
|
+
element.dispatchEvent(event);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function resolveEventConstructor(event: string | EventConstructor): EventConstructor {
|
|
86
|
+
if (typeof event === 'string') {
|
|
87
|
+
const EventCtor = (globalThis as any)[event];
|
|
88
|
+
if (!EventCtor || typeof EventCtor !== 'function') {
|
|
89
|
+
throw new Error(`Event constructor "${event}" not found in globalThis`);
|
|
90
|
+
}
|
|
91
|
+
return EventCtor;
|
|
92
|
+
}
|
|
93
|
+
return event;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function getEventId(config: EventConfig): string {
|
|
97
|
+
const eventName = typeof config.event === 'string' ? config.event : config.event.name;
|
|
98
|
+
const argsStr = JSON.stringify(config.args || '');
|
|
99
|
+
return `${eventName}:${argsStr}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function processMagicStrings(value: any, element: Element, mountInit: MountInit): any {
|
|
103
|
+
if (typeof value === 'string') {
|
|
104
|
+
if (value === '{{mountedElement}}') {
|
|
105
|
+
return element;
|
|
106
|
+
}
|
|
107
|
+
if (value === '{{mountInit}}') {
|
|
108
|
+
return mountInit;
|
|
109
|
+
}
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (Array.isArray(value)) {
|
|
114
|
+
return value.map(item => processMagicStrings(item, element, mountInit));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (value && typeof value === 'object') {
|
|
118
|
+
const processed: any = {};
|
|
119
|
+
for (const [key, val] of Object.entries(value)) {
|
|
120
|
+
processed[key] = processMagicStrings(val, element, mountInit);
|
|
121
|
+
}
|
|
122
|
+
return processed;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return value;
|
|
126
|
+
}
|
package/index.js
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
1
|
// Main entry point for MountObserver v2
|
|
2
2
|
export { MountObserver } from './MountObserver.js';
|
|
3
|
-
export {
|
|
3
|
+
export { whereOutside } from './whereOutside.js';
|
|
4
|
+
export { emitMountedElementEvents } from './emitEvents.js';
|
|
5
|
+
export { checkAttrChanges } from './attrChanges.js';
|
|
6
|
+
export { arr } from './arr.js';
|
|
7
|
+
export { EvtRt } from './EvtRt.js';
|
|
8
|
+
export { DefineCustomElementHandler } from './DefineCustomElementHandler.js';
|
|
9
|
+
export { mountEventName, dismountEventName, disconnectEventName, loadEventName, attrchangeEventName, mediamatchEventName, mediaunmatchEventName } from './Events.js';
|
|
10
|
+
// Register built-in handlers
|
|
11
|
+
import { MountObserver } from './MountObserver.js';
|
|
12
|
+
import { EvtRt } from './EvtRt.js';
|
|
13
|
+
import { DefineCustomElementHandler } from './DefineCustomElementHandler.js';
|
|
14
|
+
MountObserver.define('builtIns.logToConsole', EvtRt);
|
|
15
|
+
MountObserver.define('builtIns.defineCustomElement', DefineCustomElementHandler);
|
package/index.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
// Main entry point for MountObserver v2
|
|
2
2
|
export { MountObserver } from './MountObserver.js';
|
|
3
|
+
export { whereOutside } from './whereOutside.js';
|
|
4
|
+
export { emitMountedElementEvents } from './emitEvents.js';
|
|
5
|
+
export { checkAttrChanges } from './attrChanges.js';
|
|
6
|
+
export { arr } from './arr.js';
|
|
7
|
+
export { EvtRt } from './EvtRt.js';
|
|
8
|
+
export { DefineCustomElementHandler } from './DefineCustomElementHandler.js';
|
|
3
9
|
export type {
|
|
4
10
|
MountInit,
|
|
5
11
|
MountObserverOptions,
|
|
6
12
|
IMountObserver,
|
|
7
13
|
MountContext,
|
|
8
14
|
DoCallback,
|
|
9
|
-
DoCallbacks,
|
|
10
15
|
ImportSpec,
|
|
11
16
|
IMountEvent,
|
|
12
17
|
IDismountEvent
|
|
@@ -20,3 +25,11 @@ export {
|
|
|
20
25
|
mediamatchEventName,
|
|
21
26
|
mediaunmatchEventName
|
|
22
27
|
} from './Events.js';
|
|
28
|
+
|
|
29
|
+
// Register built-in handlers
|
|
30
|
+
import { MountObserver } from './MountObserver.js';
|
|
31
|
+
import { EvtRt } from './EvtRt.js';
|
|
32
|
+
import { DefineCustomElementHandler } from './DefineCustomElementHandler.js';
|
|
33
|
+
|
|
34
|
+
MountObserver.define('builtIns.logToConsole', EvtRt);
|
|
35
|
+
MountObserver.define('builtIns.defineCustomElement', DefineCustomElementHandler);
|
package/loadImports.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Dynamic import loading utilities
|
|
2
2
|
// Only loaded when MountInit.import is specified
|
|
3
|
+
import { arr } from './arr.js';
|
|
3
4
|
export async function loadImports(imports) {
|
|
4
|
-
const importArray =
|
|
5
|
+
const importArray = arr(imports);
|
|
5
6
|
const promises = importArray.map(imp => loadSingleImport(imp));
|
|
6
7
|
return Promise.all(promises);
|
|
7
8
|
}
|
package/loadImports.ts
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
// Only loaded when MountInit.import is specified
|
|
3
3
|
|
|
4
4
|
import { ImportSpec } from './types.js';
|
|
5
|
+
import { arr } from './arr.js';
|
|
5
6
|
|
|
6
7
|
export async function loadImports(
|
|
7
8
|
imports: string | ImportSpec | Array<string | ImportSpec | [string, any]>
|
|
8
9
|
): Promise<any[]> {
|
|
9
|
-
const importArray =
|
|
10
|
+
const importArray = arr(imports);
|
|
10
11
|
const promises = importArray.map(imp => loadSingleImport(imp));
|
|
11
12
|
return Promise.all(promises);
|
|
12
13
|
}
|
package/mediaQuery.js
CHANGED
|
@@ -48,28 +48,26 @@ export function setupMediaQuery(init, rootNodeRef, mountedElements, modules, obs
|
|
|
48
48
|
const context = {
|
|
49
49
|
modules,
|
|
50
50
|
observer: observer,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
51
|
+
rootNode,
|
|
52
|
+
mountInit: init
|
|
54
53
|
};
|
|
55
|
-
// Get all mounted elements
|
|
54
|
+
// Get all mounted elements from the WeakDual setWeak
|
|
56
55
|
const mountedElementsList = [];
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
mountedElementsList.push(element);
|
|
62
|
-
}
|
|
56
|
+
for (const ref of mountedElements.setWeak) {
|
|
57
|
+
const element = ref.deref();
|
|
58
|
+
if (element) {
|
|
59
|
+
mountedElementsList.push(element);
|
|
63
60
|
}
|
|
64
|
-
|
|
65
|
-
};
|
|
66
|
-
collectMountedElements(rootNode);
|
|
61
|
+
}
|
|
67
62
|
// Dismount each element
|
|
68
63
|
for (const element of mountedElementsList) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
64
|
+
// Remove from both structures
|
|
65
|
+
mountedElements.weakSet.delete(element);
|
|
66
|
+
for (const ref of mountedElements.setWeak) {
|
|
67
|
+
if (ref.deref() === element) {
|
|
68
|
+
mountedElements.setWeak.delete(ref);
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
73
71
|
}
|
|
74
72
|
// Dispatch dismount event with reason
|
|
75
73
|
observer.dispatchEvent(new DismountEvent(element, 'media-query-failed', init));
|
package/mediaQuery.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// Media query handling for MountObserver
|
|
2
|
-
import type { MountInit, MountContext } from './types.js';
|
|
2
|
+
import type { MountInit, MountContext, WeakDual } from './types.js';
|
|
3
3
|
import { MediaMatchEvent, MediaUnmatchEvent, DismountEvent } from './Events.js';
|
|
4
4
|
|
|
5
5
|
export function setupMediaQuery(
|
|
6
6
|
init: MountInit,
|
|
7
7
|
rootNodeRef: WeakRef<Node>,
|
|
8
|
-
mountedElements:
|
|
8
|
+
mountedElements: WeakDual<Element>,
|
|
9
9
|
modules: any[],
|
|
10
10
|
observer: EventTarget,
|
|
11
11
|
processNode: (node: Node) => void
|
|
@@ -69,33 +69,31 @@ export function setupMediaQuery(
|
|
|
69
69
|
const context: MountContext = {
|
|
70
70
|
modules,
|
|
71
71
|
observer: observer as any,
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
72
|
+
rootNode,
|
|
73
|
+
mountInit: init
|
|
75
74
|
};
|
|
76
75
|
|
|
77
|
-
// Get all mounted elements
|
|
76
|
+
// Get all mounted elements from the WeakDual setWeak
|
|
78
77
|
const mountedElementsList: Element[] = [];
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
mountedElementsList.push(element);
|
|
84
|
-
}
|
|
78
|
+
for (const ref of mountedElements.setWeak) {
|
|
79
|
+
const element = ref.deref();
|
|
80
|
+
if (element) {
|
|
81
|
+
mountedElementsList.push(element);
|
|
85
82
|
}
|
|
86
|
-
|
|
87
|
-
};
|
|
88
|
-
collectMountedElements(rootNode);
|
|
83
|
+
}
|
|
89
84
|
|
|
90
85
|
// Dismount each element
|
|
91
86
|
for (const element of mountedElementsList) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
87
|
+
// Remove from both structures
|
|
88
|
+
mountedElements.weakSet.delete(element);
|
|
89
|
+
for (const ref of mountedElements.setWeak) {
|
|
90
|
+
if (ref.deref() === element) {
|
|
91
|
+
mountedElements.setWeak.delete(ref);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
97
94
|
}
|
|
98
95
|
|
|
96
|
+
|
|
99
97
|
// Dispatch dismount event with reason
|
|
100
98
|
observer.dispatchEvent(new DismountEvent(element, 'media-query-failed', init));
|
|
101
99
|
}
|