mount-observer 0.1.14 → 0.1.15
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 → handlers/DefineCustomElement.js} +103 -99
- package/handlers/DefineCustomElement.ts +123 -0
- package/handlers/EnhanceMountedElement.js +99 -0
- package/handlers/EnhanceMountedElement.ts +116 -0
- package/handlers/Events.js +110 -0
- package/handlers/EvtRt.js +59 -0
- package/handlers/GenIds.js +37 -0
- package/handlers/GenIds.ts +45 -0
- package/handlers/HTMLInclude.js +393 -0
- package/handlers/HTMLInclude.ts +453 -0
- package/handlers/HoistTemplate.js +77 -0
- package/handlers/HoistTemplate.ts +89 -0
- package/handlers/MountObserver.js +941 -0
- package/handlers/MountObserverScript.js +78 -0
- package/handlers/MountObserverScript.ts +89 -0
- package/handlers/ScriptExport.js +83 -0
- package/handlers/ScriptExport.ts +97 -0
- package/handlers/SharedMutationObserver.js +78 -0
- package/handlers/arr.js +16 -0
- package/handlers/connectionMonitor.js +122 -0
- package/handlers/elementIntersection.js +73 -0
- package/handlers/emitEvents.js +187 -0
- package/handlers/getRegistryRoot.js +52 -0
- package/handlers/loadImports.js +129 -0
- package/handlers/mediaQuery.js +90 -0
- package/handlers/rootSizeObserver.js +131 -0
- package/handlers/upShadowSearch.js +70 -0
- package/handlers/withScopePerimeter.js +22 -0
- package/package.json +12 -2
- package/types/assign-gingerly/types.d.ts +244 -0
- package/types/be-a-beacon/types.d.ts +3 -0
- package/types/global.d.ts +29 -0
- package/types/id-generation/types.d.ts +26 -0
- package/types/mount-observer/types.d.ts +330 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { EvtRt } from '../EvtRt.js';
|
|
2
|
+
import '../ElementMountExtension.js';
|
|
3
|
+
/**
|
|
4
|
+
* Handler for Mount Observer Script Elements (MOSEs).
|
|
5
|
+
* Processes script[type="mountobserver"] elements to declaratively configure mount observers.
|
|
6
|
+
*
|
|
7
|
+
* Supports two modes:
|
|
8
|
+
* 1. External JSON: <script type="mountobserver" src="./config.json"></script>
|
|
9
|
+
* 2. Inline JSON: <script type="mountobserver">{ "matching": "div" }</script>
|
|
10
|
+
*
|
|
11
|
+
* Supports multiple configs in one script element:
|
|
12
|
+
* - Single config: { "do": "builtIns.hoistTemplate" }
|
|
13
|
+
* - Multiple configs: [{ "do": "builtIns.hoistTemplate" }, { "do": "builtIns.HTMLInclude" }]
|
|
14
|
+
*/
|
|
15
|
+
export class MountObserverScriptHandler extends EvtRt {
|
|
16
|
+
// Static properties define default MountConfig constraints
|
|
17
|
+
static matching = 'script[type="mountobserver"]';
|
|
18
|
+
static whereInstanceOf = HTMLScriptElement;
|
|
19
|
+
async mount(mountedElement, MountConfig, context) {
|
|
20
|
+
this.abort(); // Clean up event listeners (one-time operation)
|
|
21
|
+
const scriptElement = mountedElement;
|
|
22
|
+
let config = scriptElement.export;
|
|
23
|
+
if (!config) {
|
|
24
|
+
// Check if script has src attribute
|
|
25
|
+
const srcAttr = scriptElement.getAttribute('src');
|
|
26
|
+
if (srcAttr) {
|
|
27
|
+
// External JSON mode: import from src
|
|
28
|
+
const resolvedUrl = new URL(srcAttr, document.baseURI).href;
|
|
29
|
+
try {
|
|
30
|
+
const module = await import(resolvedUrl, { with: { type: 'json' } });
|
|
31
|
+
config = module.default;
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
throw new Error(`Failed to import JSON from '${srcAttr}': ${error instanceof Error ? error.message : String(error)}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
// Inline JSON mode: parse textContent
|
|
39
|
+
const jsonText = scriptElement.textContent?.trim();
|
|
40
|
+
if (!jsonText) {
|
|
41
|
+
throw new Error('Script element must have either src attribute or JSON content');
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
config = JSON.parse(jsonText);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
throw new Error(`Failed to parse JSON content: ${error instanceof Error ? error.message : String(error)}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Validate that config is an object or array
|
|
51
|
+
if (typeof config !== 'object' || config === null) {
|
|
52
|
+
throw new Error('Mount observer config must be an object or array');
|
|
53
|
+
}
|
|
54
|
+
// Store the parsed config on the script element's export property
|
|
55
|
+
scriptElement.export = config;
|
|
56
|
+
// Dispatch resolved event
|
|
57
|
+
const { ResolvedEvent } = await import('../Events.js');
|
|
58
|
+
scriptElement.dispatchEvent(new ResolvedEvent(config));
|
|
59
|
+
}
|
|
60
|
+
// Handle array of configs
|
|
61
|
+
if (Array.isArray(config)) {
|
|
62
|
+
// Mount each config in the array
|
|
63
|
+
for (const singleConfig of config) {
|
|
64
|
+
if (typeof singleConfig !== 'object' || singleConfig === null) {
|
|
65
|
+
throw new Error('Each config in array must be an object');
|
|
66
|
+
}
|
|
67
|
+
await scriptElement.mount(singleConfig);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// Single config object - mount it
|
|
72
|
+
await scriptElement.mount(config);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Register built-in handler
|
|
77
|
+
import { MountObserver } from '../MountObserver.js';
|
|
78
|
+
MountObserver.define('builtIns.mountObserverScript', MountObserverScriptHandler);
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { EvtRt } from '../EvtRt.js';
|
|
2
|
+
import { MountConfig, MountContext } from '../types/mount-observer/types.js';
|
|
3
|
+
import '../ElementMountExtension.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Handler for Mount Observer Script Elements (MOSEs).
|
|
7
|
+
* Processes script[type="mountobserver"] elements to declaratively configure mount observers.
|
|
8
|
+
*
|
|
9
|
+
* Supports two modes:
|
|
10
|
+
* 1. External JSON: <script type="mountobserver" src="./config.json"></script>
|
|
11
|
+
* 2. Inline JSON: <script type="mountobserver">{ "matching": "div" }</script>
|
|
12
|
+
*
|
|
13
|
+
* Supports multiple configs in one script element:
|
|
14
|
+
* - Single config: { "do": "builtIns.hoistTemplate" }
|
|
15
|
+
* - Multiple configs: [{ "do": "builtIns.hoistTemplate" }, { "do": "builtIns.HTMLInclude" }]
|
|
16
|
+
*/
|
|
17
|
+
export class MountObserverScriptHandler extends EvtRt {
|
|
18
|
+
// Static properties define default MountConfig constraints
|
|
19
|
+
static matching = 'script[type="mountobserver"]';
|
|
20
|
+
static whereInstanceOf = HTMLScriptElement;
|
|
21
|
+
|
|
22
|
+
async mount(mountedElement: Element, MountConfig: MountConfig, context: MountContext): Promise<void> {
|
|
23
|
+
this.abort(); // Clean up event listeners (one-time operation)
|
|
24
|
+
|
|
25
|
+
const scriptElement = mountedElement as HTMLScriptElement;
|
|
26
|
+
|
|
27
|
+
let config = (scriptElement as any).export;
|
|
28
|
+
if (!config) {
|
|
29
|
+
// Check if script has src attribute
|
|
30
|
+
const srcAttr = scriptElement.getAttribute('src');
|
|
31
|
+
|
|
32
|
+
if (srcAttr) {
|
|
33
|
+
// External JSON mode: import from src
|
|
34
|
+
const resolvedUrl = new URL(srcAttr, document.baseURI).href;
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const module = await import(resolvedUrl, { with: { type: 'json' } } as any);
|
|
38
|
+
config = module.default;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
throw new Error(`Failed to import JSON from '${srcAttr}': ${error instanceof Error ? error.message : String(error)}`);
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
// Inline JSON mode: parse textContent
|
|
44
|
+
const jsonText = scriptElement.textContent?.trim();
|
|
45
|
+
|
|
46
|
+
if (!jsonText) {
|
|
47
|
+
throw new Error('Script element must have either src attribute or JSON content');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
config = JSON.parse(jsonText);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
throw new Error(`Failed to parse JSON content: ${error instanceof Error ? error.message : String(error)}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Validate that config is an object or array
|
|
58
|
+
if (typeof config !== 'object' || config === null) {
|
|
59
|
+
throw new Error('Mount observer config must be an object or array');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Store the parsed config on the script element's export property
|
|
63
|
+
(scriptElement as any).export = config;
|
|
64
|
+
|
|
65
|
+
// Dispatch resolved event
|
|
66
|
+
const { ResolvedEvent } = await import('../Events.js');
|
|
67
|
+
scriptElement.dispatchEvent(new ResolvedEvent(config));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Handle array of configs
|
|
71
|
+
if (Array.isArray(config)) {
|
|
72
|
+
// Mount each config in the array
|
|
73
|
+
for (const singleConfig of config) {
|
|
74
|
+
if (typeof singleConfig !== 'object' || singleConfig === null) {
|
|
75
|
+
throw new Error('Each config in array must be an object');
|
|
76
|
+
}
|
|
77
|
+
await scriptElement.mount(singleConfig);
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
// Single config object - mount it
|
|
81
|
+
await scriptElement.mount(config);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Register built-in handler
|
|
87
|
+
import { MountObserver } from '../MountObserver.js';
|
|
88
|
+
|
|
89
|
+
MountObserver.define('builtIns.mountObserverScript', MountObserverScriptHandler);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { EvtRt } from '../EvtRt.js';
|
|
2
|
+
/**
|
|
3
|
+
* Handler for exposing module exports from script elements via element.export.
|
|
4
|
+
*
|
|
5
|
+
* Solves two key problems:
|
|
6
|
+
*
|
|
7
|
+
* 1. ES Module Export Access:
|
|
8
|
+
* - Use nomodule attribute to prevent browser from loading the module separately
|
|
9
|
+
* - Handler imports it and exposes exports via element.export
|
|
10
|
+
* - Avoids double-loading the module in memory
|
|
11
|
+
* - Example: <script nomodule src="./config.js" id="cfg"></script>
|
|
12
|
+
* - Access: document.getElementById('cfg').export.myValue
|
|
13
|
+
*
|
|
14
|
+
* 2. JSON/Data Import:
|
|
15
|
+
* - Use type attribute for JSON and other data formats
|
|
16
|
+
* - Browser ignores non-standard script types, so no double-loading issue
|
|
17
|
+
* - Handler imports with appropriate assertion and exposes via element.export
|
|
18
|
+
* - Example: <script src="./data.json" type="json" id="data"></script>
|
|
19
|
+
* - Access: document.getElementById('data').export.default
|
|
20
|
+
*
|
|
21
|
+
* Supported type values:
|
|
22
|
+
* - type="json" - JSON data
|
|
23
|
+
* - type="application/json" - JSON with full MIME type
|
|
24
|
+
* - type="application/ld+json" - JSON-LD linked data
|
|
25
|
+
* - Any type containing "json" triggers JSON import assertion
|
|
26
|
+
*/
|
|
27
|
+
export class ScriptExportHandler extends EvtRt {
|
|
28
|
+
// Match script elements with src attribute
|
|
29
|
+
static matching = 'script[src]';
|
|
30
|
+
static whereInstanceOf = HTMLScriptElement;
|
|
31
|
+
async mount(mountedElement, MountConfig, context) {
|
|
32
|
+
this.abort(); // Clean up event listeners (one-time operation)
|
|
33
|
+
const scriptElement = mountedElement;
|
|
34
|
+
// Optimization 3: Skip if already processed (export property exists)
|
|
35
|
+
if (scriptElement.export !== undefined) {
|
|
36
|
+
console.log('ScriptExport: Skipping already processed script element');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
// Skip if this is a module script (browser handles these)
|
|
40
|
+
const typeAttr = scriptElement.getAttribute('type');
|
|
41
|
+
if (typeAttr === 'module') {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// Only process if:
|
|
45
|
+
// 1. Has nomodule attribute (for ES modules), OR
|
|
46
|
+
// 2. Has type attribute containing "json" (for JSON data)
|
|
47
|
+
const hasNoModule = scriptElement.hasAttribute('nomodule');
|
|
48
|
+
const isJsonType = typeAttr && typeAttr.toLowerCase().includes('json');
|
|
49
|
+
if (!hasNoModule && !isJsonType) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
// Read src attribute
|
|
53
|
+
const srcAttr = scriptElement.getAttribute('src');
|
|
54
|
+
if (!srcAttr) {
|
|
55
|
+
throw new Error('Script element must have a src attribute');
|
|
56
|
+
}
|
|
57
|
+
// Resolve the src relative to the document's base URL
|
|
58
|
+
const resolvedUrl = new URL(srcAttr, document.baseURI).href;
|
|
59
|
+
// Determine import assertion type
|
|
60
|
+
const importType = isJsonType ? 'json' : null;
|
|
61
|
+
// Perform import
|
|
62
|
+
let module;
|
|
63
|
+
try {
|
|
64
|
+
if (importType) {
|
|
65
|
+
module = await import(resolvedUrl, { with: { type: importType } });
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
module = await import(resolvedUrl);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
throw new Error(`Failed to import module from '${srcAttr}': ${error instanceof Error ? error.message : String(error)}`);
|
|
73
|
+
}
|
|
74
|
+
// Store result on element
|
|
75
|
+
scriptElement.export = module;
|
|
76
|
+
// Dispatch resolved event
|
|
77
|
+
const { ResolvedEvent } = await import('../Events.js');
|
|
78
|
+
scriptElement.dispatchEvent(new ResolvedEvent(module));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Register built-in handler
|
|
82
|
+
import { MountObserver } from '../MountObserver.js';
|
|
83
|
+
MountObserver.define('builtIns.scriptExport', ScriptExportHandler);
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { EvtRt } from '../EvtRt.js';
|
|
2
|
+
import { MountConfig, MountContext } from '../types/mount-observer/types.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Handler for exposing module exports from script elements via element.export.
|
|
6
|
+
*
|
|
7
|
+
* Solves two key problems:
|
|
8
|
+
*
|
|
9
|
+
* 1. ES Module Export Access:
|
|
10
|
+
* - Use nomodule attribute to prevent browser from loading the module separately
|
|
11
|
+
* - Handler imports it and exposes exports via element.export
|
|
12
|
+
* - Avoids double-loading the module in memory
|
|
13
|
+
* - Example: <script nomodule src="./config.js" id="cfg"></script>
|
|
14
|
+
* - Access: document.getElementById('cfg').export.myValue
|
|
15
|
+
*
|
|
16
|
+
* 2. JSON/Data Import:
|
|
17
|
+
* - Use type attribute for JSON and other data formats
|
|
18
|
+
* - Browser ignores non-standard script types, so no double-loading issue
|
|
19
|
+
* - Handler imports with appropriate assertion and exposes via element.export
|
|
20
|
+
* - Example: <script src="./data.json" type="json" id="data"></script>
|
|
21
|
+
* - Access: document.getElementById('data').export.default
|
|
22
|
+
*
|
|
23
|
+
* Supported type values:
|
|
24
|
+
* - type="json" - JSON data
|
|
25
|
+
* - type="application/json" - JSON with full MIME type
|
|
26
|
+
* - type="application/ld+json" - JSON-LD linked data
|
|
27
|
+
* - Any type containing "json" triggers JSON import assertion
|
|
28
|
+
*/
|
|
29
|
+
export class ScriptExportHandler extends EvtRt {
|
|
30
|
+
// Match script elements with src attribute
|
|
31
|
+
static matching = 'script[src]';
|
|
32
|
+
static whereInstanceOf = HTMLScriptElement;
|
|
33
|
+
|
|
34
|
+
async mount(mountedElement: Element, MountConfig: MountConfig, context: MountContext): Promise<void> {
|
|
35
|
+
this.abort(); // Clean up event listeners (one-time operation)
|
|
36
|
+
|
|
37
|
+
const scriptElement = mountedElement as HTMLScriptElement;
|
|
38
|
+
|
|
39
|
+
// Optimization 3: Skip if already processed (export property exists)
|
|
40
|
+
if ((scriptElement as any).export !== undefined) {
|
|
41
|
+
console.log('ScriptExport: Skipping already processed script element');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Skip if this is a module script (browser handles these)
|
|
46
|
+
const typeAttr = scriptElement.getAttribute('type');
|
|
47
|
+
if (typeAttr === 'module') {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Only process if:
|
|
52
|
+
// 1. Has nomodule attribute (for ES modules), OR
|
|
53
|
+
// 2. Has type attribute containing "json" (for JSON data)
|
|
54
|
+
const hasNoModule = scriptElement.hasAttribute('nomodule');
|
|
55
|
+
const isJsonType = typeAttr && typeAttr.toLowerCase().includes('json');
|
|
56
|
+
|
|
57
|
+
if (!hasNoModule && !isJsonType) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Read src attribute
|
|
62
|
+
const srcAttr = scriptElement.getAttribute('src');
|
|
63
|
+
if (!srcAttr) {
|
|
64
|
+
throw new Error('Script element must have a src attribute');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Resolve the src relative to the document's base URL
|
|
68
|
+
const resolvedUrl = new URL(srcAttr, document.baseURI).href;
|
|
69
|
+
|
|
70
|
+
// Determine import assertion type
|
|
71
|
+
const importType = isJsonType ? 'json' : null;
|
|
72
|
+
|
|
73
|
+
// Perform import
|
|
74
|
+
let module;
|
|
75
|
+
try {
|
|
76
|
+
if (importType) {
|
|
77
|
+
module = await import(resolvedUrl, { with: { type: importType } } as any);
|
|
78
|
+
} else {
|
|
79
|
+
module = await import(resolvedUrl);
|
|
80
|
+
}
|
|
81
|
+
} catch (error) {
|
|
82
|
+
throw new Error(`Failed to import module from '${srcAttr}': ${error instanceof Error ? error.message : String(error)}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Store result on element
|
|
86
|
+
(scriptElement as any).export = module;
|
|
87
|
+
|
|
88
|
+
// Dispatch resolved event
|
|
89
|
+
const { ResolvedEvent } = await import('../Events.js');
|
|
90
|
+
scriptElement.dispatchEvent(new ResolvedEvent(module));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Register built-in handler
|
|
95
|
+
import { MountObserver } from '../MountObserver.js';
|
|
96
|
+
|
|
97
|
+
MountObserver.define('builtIns.scriptExport', ScriptExportHandler);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Manages shared MutationObserver instances for multiple MountObserver instances
|
|
4
|
+
* observing the same root node. This reduces overhead when multiple observers
|
|
5
|
+
* are watching the same DOM fragment.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.registerSharedObserver = registerSharedObserver;
|
|
9
|
+
exports.unregisterSharedObserver = unregisterSharedObserver;
|
|
10
|
+
/**
|
|
11
|
+
* Global registry of shared observers, keyed by root node
|
|
12
|
+
*/
|
|
13
|
+
var sharedObservers = new WeakMap();
|
|
14
|
+
/**
|
|
15
|
+
* Registers a callback with the shared MutationObserver for the given root node.
|
|
16
|
+
* Creates a new shared observer if one doesn't exist for this root.
|
|
17
|
+
*/
|
|
18
|
+
function registerSharedObserver(rootNode, callback, config) {
|
|
19
|
+
var sharedData = sharedObservers.get(rootNode);
|
|
20
|
+
if (!sharedData) {
|
|
21
|
+
// Create shared data structure first
|
|
22
|
+
sharedData = {
|
|
23
|
+
observer: null, // Will be set immediately below
|
|
24
|
+
callbacks: new Set(),
|
|
25
|
+
config: config
|
|
26
|
+
};
|
|
27
|
+
// Create new shared observer for this root node
|
|
28
|
+
var observer = new MutationObserver(function (mutations) {
|
|
29
|
+
// Distribute mutations to all registered callbacks
|
|
30
|
+
var callbacks = sharedData.callbacks;
|
|
31
|
+
for (var _i = 0, callbacks_1 = callbacks; _i < callbacks_1.length; _i++) {
|
|
32
|
+
var cb = callbacks_1[_i];
|
|
33
|
+
cb(mutations);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
sharedData.observer = observer;
|
|
37
|
+
sharedObservers.set(rootNode, sharedData);
|
|
38
|
+
// Start observing after everything is set up
|
|
39
|
+
observer.observe(rootNode, config);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
// Verify config matches (for safety)
|
|
43
|
+
// In practice, all MountObservers should use the same config
|
|
44
|
+
if (!configsMatch(sharedData.config, config)) {
|
|
45
|
+
console.warn('MutationObserver config mismatch detected. Using existing config.');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Register the callback
|
|
49
|
+
sharedData.callbacks.add(callback);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Unregisters a callback from the shared MutationObserver.
|
|
53
|
+
* If this was the last callback, disconnects and removes the shared observer.
|
|
54
|
+
*/
|
|
55
|
+
function unregisterSharedObserver(rootNode, callback) {
|
|
56
|
+
var sharedData = sharedObservers.get(rootNode);
|
|
57
|
+
if (!sharedData) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// Remove the callback
|
|
61
|
+
sharedData.callbacks.delete(callback);
|
|
62
|
+
// If no more callbacks, disconnect and cleanup
|
|
63
|
+
if (sharedData.callbacks.size === 0) {
|
|
64
|
+
sharedData.observer.disconnect();
|
|
65
|
+
sharedObservers.delete(rootNode);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Checks if two MutationObserverInit configs are equivalent
|
|
70
|
+
*/
|
|
71
|
+
function configsMatch(a, b) {
|
|
72
|
+
return a.childList === b.childList &&
|
|
73
|
+
a.subtree === b.subtree &&
|
|
74
|
+
a.attributes === b.attributes &&
|
|
75
|
+
a.attributeOldValue === b.attributeOldValue &&
|
|
76
|
+
a.characterData === b.characterData &&
|
|
77
|
+
a.characterDataOldValue === b.characterDataOldValue;
|
|
78
|
+
}
|
package/handlers/arr.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.arr = arr;
|
|
4
|
+
/**
|
|
5
|
+
* Utility function to normalize a value to an array.
|
|
6
|
+
* - If undefined, returns empty array
|
|
7
|
+
* - If already an array, returns as-is
|
|
8
|
+
* - Otherwise, wraps the value in an array
|
|
9
|
+
*
|
|
10
|
+
* @param inp - Value to normalize to array
|
|
11
|
+
* @returns Array containing the value(s)
|
|
12
|
+
*/
|
|
13
|
+
function arr(inp) {
|
|
14
|
+
return inp === undefined ? []
|
|
15
|
+
: Array.isArray(inp) ? inp : [inp];
|
|
16
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setupConnectionMonitor = setupConnectionMonitor;
|
|
4
|
+
var Events_js_1 = require("./Events.js");
|
|
5
|
+
function setupConnectionMonitor(init, rootNodeRef, mountedElements, modules, observer, processNode) {
|
|
6
|
+
var whereConnectionHas = init.whereConnectionHas;
|
|
7
|
+
if (!whereConnectionHas) {
|
|
8
|
+
throw new Error('whereConnectionHas is required');
|
|
9
|
+
}
|
|
10
|
+
// Get connection object with vendor prefixes
|
|
11
|
+
var nav = navigator;
|
|
12
|
+
var connection = nav.connection || nav.mozConnection || nav.webkitConnection;
|
|
13
|
+
// If Network Information API is not supported, warn and pass the condition
|
|
14
|
+
if (!connection) {
|
|
15
|
+
console.warn('Network Information API is not supported in this browser. whereConnectionHas condition will be ignored.');
|
|
16
|
+
return {
|
|
17
|
+
conditionMatches: true,
|
|
18
|
+
cleanup: function () { }
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
// Check initial condition
|
|
22
|
+
var conditionMatches = evaluateConnectionCondition(connection, whereConnectionHas);
|
|
23
|
+
// Set up change listener
|
|
24
|
+
var changeHandler = function () {
|
|
25
|
+
var previousMatches = conditionMatches;
|
|
26
|
+
conditionMatches = evaluateConnectionCondition(connection, whereConnectionHas);
|
|
27
|
+
if (conditionMatches && !previousMatches) {
|
|
28
|
+
// Connection now matches - process elements
|
|
29
|
+
handleConditionMatch();
|
|
30
|
+
}
|
|
31
|
+
else if (!conditionMatches && previousMatches) {
|
|
32
|
+
// Connection no longer matches - dismount all elements
|
|
33
|
+
handleConditionUnmatch();
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
function handleConditionMatch() {
|
|
37
|
+
// Process all elements in the observed node
|
|
38
|
+
var rootNode = rootNodeRef.deref();
|
|
39
|
+
if (rootNode) {
|
|
40
|
+
processNode(rootNode);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function handleConditionUnmatch() {
|
|
44
|
+
// Dismount all currently mounted elements
|
|
45
|
+
var rootNode = rootNodeRef.deref();
|
|
46
|
+
if (!rootNode) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
var context = {
|
|
50
|
+
modules: modules,
|
|
51
|
+
observer: observer,
|
|
52
|
+
rootNode: rootNode,
|
|
53
|
+
mountConfig: init
|
|
54
|
+
};
|
|
55
|
+
// Get all mounted elements from the WeakDual setWeak
|
|
56
|
+
var mountedElementsList = [];
|
|
57
|
+
for (var _i = 0, _a = mountedElements.setWeak; _i < _a.length; _i++) {
|
|
58
|
+
var ref = _a[_i];
|
|
59
|
+
var element = ref.deref();
|
|
60
|
+
if (element) {
|
|
61
|
+
mountedElementsList.push(element);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Dismount each element
|
|
65
|
+
for (var _b = 0, mountedElementsList_1 = mountedElementsList; _b < mountedElementsList_1.length; _b++) {
|
|
66
|
+
var element = mountedElementsList_1[_b];
|
|
67
|
+
// Remove from both structures
|
|
68
|
+
mountedElements.weakSet.delete(element);
|
|
69
|
+
for (var _c = 0, _d = mountedElements.setWeak; _c < _d.length; _c++) {
|
|
70
|
+
var ref = _d[_c];
|
|
71
|
+
if (ref.deref() === element) {
|
|
72
|
+
mountedElements.setWeak.delete(ref);
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Dispatch dismount event with reason
|
|
77
|
+
observer.dispatchEvent(new Events_js_1.DismountEvent(element, 'connection-failed', init));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Listen for connection changes
|
|
81
|
+
connection.addEventListener('change', changeHandler);
|
|
82
|
+
return {
|
|
83
|
+
conditionMatches: conditionMatches,
|
|
84
|
+
cleanup: function () {
|
|
85
|
+
connection.removeEventListener('change', changeHandler);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Evaluate if the current connection meets the specified conditions
|
|
91
|
+
*/
|
|
92
|
+
function evaluateConnectionCondition(connection, condition) {
|
|
93
|
+
// Check effectiveType (e.g., 'slow-2g', '2g', '3g', '4g')
|
|
94
|
+
if (condition.effectiveTypeIn && condition.effectiveTypeIn.length > 0) {
|
|
95
|
+
var effectiveType = connection.effectiveType;
|
|
96
|
+
if (!effectiveType || !condition.effectiveTypeIn.includes(effectiveType)) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Check downlink (bandwidth in Mbps)
|
|
101
|
+
if (condition.downlinkMin !== undefined) {
|
|
102
|
+
var downlink = connection.downlink;
|
|
103
|
+
if (downlink === undefined || downlink < condition.downlinkMin) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (condition.downlinkMax !== undefined) {
|
|
108
|
+
var downlink = connection.downlink;
|
|
109
|
+
if (downlink === undefined || downlink > condition.downlinkMax) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Check RTT (round-trip time in ms)
|
|
114
|
+
if (condition.rttMax !== undefined) {
|
|
115
|
+
var rtt = connection.rtt;
|
|
116
|
+
if (rtt === undefined || rtt > condition.rttMax) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// All conditions passed
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setupElementIntersection = setupElementIntersection;
|
|
4
|
+
exports.isElementIntersecting = isElementIntersecting;
|
|
5
|
+
var Events_js_1 = require("./Events.js");
|
|
6
|
+
function setupElementIntersection(init, rootNodeRef, mountedElements, modules, observer, matchesSelector, handleMatch) {
|
|
7
|
+
var whereElementIntersectsWith = init.whereElementIntersectsWith;
|
|
8
|
+
if (!whereElementIntersectsWith) {
|
|
9
|
+
throw new Error('whereElementIntersectsWith is required');
|
|
10
|
+
}
|
|
11
|
+
// Track which elements are currently intersecting
|
|
12
|
+
var intersectingElements = new WeakSet();
|
|
13
|
+
// Create IntersectionObserver with the provided options
|
|
14
|
+
var intersectionObserver = new IntersectionObserver(function (entries) {
|
|
15
|
+
for (var _i = 0, entries_1 = entries; _i < entries_1.length; _i++) {
|
|
16
|
+
var entry = entries_1[_i];
|
|
17
|
+
var element = entry.target;
|
|
18
|
+
if (entry.isIntersecting) {
|
|
19
|
+
// Element is now intersecting
|
|
20
|
+
intersectingElements.add(element);
|
|
21
|
+
// Check if element matches all other conditions and mount if so
|
|
22
|
+
if (matchesSelector(element)) {
|
|
23
|
+
handleMatch(element);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
// Element is no longer intersecting
|
|
28
|
+
intersectingElements.delete(element);
|
|
29
|
+
// Dismount if it was mounted
|
|
30
|
+
if (mountedElements.weakSet.has(element)) {
|
|
31
|
+
dismountElement(element);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}, whereElementIntersectsWith);
|
|
36
|
+
function dismountElement(element) {
|
|
37
|
+
// Remove from mounted elements
|
|
38
|
+
mountedElements.weakSet.delete(element);
|
|
39
|
+
for (var _i = 0, _a = mountedElements.setWeak; _i < _a.length; _i++) {
|
|
40
|
+
var ref = _a[_i];
|
|
41
|
+
if (ref.deref() === element) {
|
|
42
|
+
mountedElements.setWeak.delete(ref);
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Dispatch dismount event
|
|
47
|
+
observer.dispatchEvent(new Events_js_1.DismountEvent(element, 'intersection-failed', init));
|
|
48
|
+
}
|
|
49
|
+
function observeElement(element) {
|
|
50
|
+
intersectionObserver.observe(element);
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
intersectionObserver: intersectionObserver,
|
|
54
|
+
observeElement: observeElement,
|
|
55
|
+
cleanup: function () {
|
|
56
|
+
intersectionObserver.disconnect();
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Check if an element is currently intersecting
|
|
62
|
+
* This is called from #matchesSelector to determine if intersection condition is met
|
|
63
|
+
*/
|
|
64
|
+
function isElementIntersecting(element, intersectionObserver) {
|
|
65
|
+
// If no intersection observer is set up, consider all elements as intersecting
|
|
66
|
+
if (!intersectionObserver) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
// When intersection observer is active, we can't synchronously determine intersection state
|
|
70
|
+
// The element will be observed and the callback will handle mounting when it intersects
|
|
71
|
+
// Return false here to prevent immediate mounting
|
|
72
|
+
return false;
|
|
73
|
+
}
|