mount-observer 0.1.11 → 0.1.12

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.
@@ -0,0 +1,67 @@
1
+ import { DismountEvent } from './Events.js';
2
+ export function setupElementIntersection(init, rootNodeRef, mountedElements, modules, observer, matchesSelector, handleMatch) {
3
+ const { whereElementIntersectsWith } = init;
4
+ if (!whereElementIntersectsWith) {
5
+ throw new Error('whereElementIntersectsWith is required');
6
+ }
7
+ // Track which elements are currently intersecting
8
+ const intersectingElements = new WeakSet();
9
+ // Create IntersectionObserver with the provided options
10
+ const intersectionObserver = new IntersectionObserver((entries) => {
11
+ for (const entry of entries) {
12
+ const element = entry.target;
13
+ if (entry.isIntersecting) {
14
+ // Element is now intersecting
15
+ intersectingElements.add(element);
16
+ // Check if element matches all other conditions and mount if so
17
+ if (matchesSelector(element)) {
18
+ handleMatch(element);
19
+ }
20
+ }
21
+ else {
22
+ // Element is no longer intersecting
23
+ intersectingElements.delete(element);
24
+ // Dismount if it was mounted
25
+ if (mountedElements.weakSet.has(element)) {
26
+ dismountElement(element);
27
+ }
28
+ }
29
+ }
30
+ }, whereElementIntersectsWith);
31
+ function dismountElement(element) {
32
+ // Remove from mounted elements
33
+ mountedElements.weakSet.delete(element);
34
+ for (const ref of mountedElements.setWeak) {
35
+ if (ref.deref() === element) {
36
+ mountedElements.setWeak.delete(ref);
37
+ break;
38
+ }
39
+ }
40
+ // Dispatch dismount event
41
+ observer.dispatchEvent(new DismountEvent(element, 'intersection-failed', init));
42
+ }
43
+ function observeElement(element) {
44
+ intersectionObserver.observe(element);
45
+ }
46
+ return {
47
+ intersectionObserver,
48
+ observeElement,
49
+ cleanup: () => {
50
+ intersectionObserver.disconnect();
51
+ }
52
+ };
53
+ }
54
+ /**
55
+ * Check if an element is currently intersecting
56
+ * This is called from #matchesSelector to determine if intersection condition is met
57
+ */
58
+ export function isElementIntersecting(element, intersectionObserver) {
59
+ // If no intersection observer is set up, consider all elements as intersecting
60
+ if (!intersectionObserver) {
61
+ return true;
62
+ }
63
+ // When intersection observer is active, we can't synchronously determine intersection state
64
+ // The element will be observed and the callback will handle mounting when it intersects
65
+ // Return false here to prevent immediate mounting
66
+ return false;
67
+ }
@@ -0,0 +1,96 @@
1
+ // Element intersection observation for MountObserver
2
+ import type { MountConfig, WeakDual } from './types/mount-observer/types.js';
3
+ import { DismountEvent } from './Events.js';
4
+
5
+ export function setupElementIntersection(
6
+ init: MountConfig,
7
+ rootNodeRef: WeakRef<Node>,
8
+ mountedElements: WeakDual<Element>,
9
+ modules: any[],
10
+ observer: EventTarget,
11
+ matchesSelector: (element: Element) => boolean,
12
+ handleMatch: (element: Element) => void
13
+ ): {
14
+ intersectionObserver: IntersectionObserver;
15
+ observeElement: (element: Element) => void;
16
+ cleanup: () => void;
17
+ } {
18
+ const { whereElementIntersectsWith } = init;
19
+
20
+ if (!whereElementIntersectsWith) {
21
+ throw new Error('whereElementIntersectsWith is required');
22
+ }
23
+
24
+ // Track which elements are currently intersecting
25
+ const intersectingElements = new WeakSet<Element>();
26
+
27
+ // Create IntersectionObserver with the provided options
28
+ const intersectionObserver = new IntersectionObserver((entries) => {
29
+ for (const entry of entries) {
30
+ const element = entry.target as Element;
31
+
32
+ if (entry.isIntersecting) {
33
+ // Element is now intersecting
34
+ intersectingElements.add(element);
35
+
36
+ // Check if element matches all other conditions and mount if so
37
+ if (matchesSelector(element)) {
38
+ handleMatch(element);
39
+ }
40
+ } else {
41
+ // Element is no longer intersecting
42
+ intersectingElements.delete(element);
43
+
44
+ // Dismount if it was mounted
45
+ if (mountedElements.weakSet.has(element)) {
46
+ dismountElement(element);
47
+ }
48
+ }
49
+ }
50
+ }, whereElementIntersectsWith);
51
+
52
+ function dismountElement(element: Element): void {
53
+ // Remove from mounted elements
54
+ mountedElements.weakSet.delete(element);
55
+ for (const ref of mountedElements.setWeak) {
56
+ if (ref.deref() === element) {
57
+ mountedElements.setWeak.delete(ref);
58
+ break;
59
+ }
60
+ }
61
+
62
+ // Dispatch dismount event
63
+ observer.dispatchEvent(new DismountEvent(element, 'intersection-failed', init));
64
+ }
65
+
66
+ function observeElement(element: Element): void {
67
+ intersectionObserver.observe(element);
68
+ }
69
+
70
+ return {
71
+ intersectionObserver,
72
+ observeElement,
73
+ cleanup: () => {
74
+ intersectionObserver.disconnect();
75
+ }
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Check if an element is currently intersecting
81
+ * This is called from #matchesSelector to determine if intersection condition is met
82
+ */
83
+ export function isElementIntersecting(
84
+ element: Element,
85
+ intersectionObserver: IntersectionObserver | undefined
86
+ ): boolean {
87
+ // If no intersection observer is set up, consider all elements as intersecting
88
+ if (!intersectionObserver) {
89
+ return true;
90
+ }
91
+
92
+ // When intersection observer is active, we can't synchronously determine intersection state
93
+ // The element will be observed and the callback will handle mounting when it intersects
94
+ // Return false here to prevent immediate mounting
95
+ return false;
96
+ }
@@ -0,0 +1,87 @@
1
+ import { DismountEvent } from './Events.js';
2
+ export function setupObservedRootHas(init, rootNodeRef, mountedElements, modules, observer, processNode) {
3
+ const { whereObservedRootHas } = init;
4
+ if (!whereObservedRootHas) {
5
+ throw new Error('whereObservedRootHas is required');
6
+ }
7
+ const rootNode = rootNodeRef.deref();
8
+ if (!rootNode) {
9
+ throw new Error('Root node has been garbage collected');
10
+ }
11
+ // Get the element to query against
12
+ const rootElement = rootNode instanceof Element
13
+ ? rootNode
14
+ : rootNode.documentElement || rootNode.host;
15
+ if (!rootElement) {
16
+ throw new Error('Could not determine root element for whereObservedRootHas');
17
+ }
18
+ // Track current state
19
+ let conditionMatches = !!rootElement.querySelector(whereObservedRootHas);
20
+ // Set up mutation observer to watch for changes
21
+ const mutationObserver = new MutationObserver(() => {
22
+ const previousMatches = conditionMatches;
23
+ conditionMatches = !!rootElement.querySelector(whereObservedRootHas);
24
+ if (conditionMatches && !previousMatches) {
25
+ // Condition now matches - process elements
26
+ handleConditionMatch();
27
+ }
28
+ else if (!conditionMatches && previousMatches) {
29
+ // Condition no longer matches - dismount all elements
30
+ handleConditionUnmatch();
31
+ }
32
+ });
33
+ function handleConditionMatch() {
34
+ // Process all elements in the observed node
35
+ const rootNode = rootNodeRef.deref();
36
+ if (rootNode) {
37
+ processNode(rootNode);
38
+ }
39
+ }
40
+ function handleConditionUnmatch() {
41
+ // Dismount all currently mounted elements
42
+ const rootNode = rootNodeRef.deref();
43
+ if (!rootNode) {
44
+ return;
45
+ }
46
+ const context = {
47
+ modules,
48
+ observer: observer,
49
+ rootNode,
50
+ MountConfig: init
51
+ };
52
+ // Get all mounted elements from the WeakDual setWeak
53
+ const mountedElementsList = [];
54
+ for (const ref of mountedElements.setWeak) {
55
+ const element = ref.deref();
56
+ if (element) {
57
+ mountedElementsList.push(element);
58
+ }
59
+ }
60
+ // Dismount each element
61
+ for (const element of mountedElementsList) {
62
+ // Remove from both structures
63
+ mountedElements.weakSet.delete(element);
64
+ for (const ref of mountedElements.setWeak) {
65
+ if (ref.deref() === element) {
66
+ mountedElements.setWeak.delete(ref);
67
+ break;
68
+ }
69
+ }
70
+ // Dispatch dismount event with reason
71
+ observer.dispatchEvent(new DismountEvent(element, 'observed-root-has-failed', init));
72
+ }
73
+ }
74
+ // Observe the root element for changes
75
+ mutationObserver.observe(rootElement, {
76
+ childList: true,
77
+ subtree: true,
78
+ attributes: true,
79
+ attributeOldValue: false
80
+ });
81
+ return {
82
+ conditionMatches,
83
+ cleanup: () => {
84
+ mutationObserver.disconnect();
85
+ }
86
+ };
87
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mount-observer",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "Observe and act on css matches.",
5
5
  "main": "MountObserver.js",
6
6
  "module": "MountObserver.js",
@@ -0,0 +1,124 @@
1
+ import { DismountEvent } from './Events.js';
2
+ export function setupRootSizeObserver(init, rootNodeRef, mountedElements, modules, observer, processNode) {
3
+ const { whereObservedRootSizeMatches } = init;
4
+ if (!whereObservedRootSizeMatches) {
5
+ throw new Error('whereObservedRootSizeMatches is required');
6
+ }
7
+ const rootNode = rootNodeRef.deref();
8
+ if (!rootNode) {
9
+ throw new Error('Root node has been garbage collected');
10
+ }
11
+ // Get the element to observe
12
+ const rootElement = rootNode instanceof Element
13
+ ? rootNode
14
+ : rootNode.documentElement;
15
+ if (!rootElement) {
16
+ throw new Error('Could not determine root element for whereObservedRootSizeMatches');
17
+ }
18
+ // Parse the container query condition
19
+ // Container queries use the same syntax as media queries: (min-width: 700px)
20
+ const containerQuery = whereObservedRootSizeMatches;
21
+ // Check if condition currently matches
22
+ let conditionMatches = evaluateContainerQuery(rootElement, containerQuery);
23
+ // Set up ResizeObserver to watch for size changes
24
+ const resizeObserver = new ResizeObserver((entries) => {
25
+ for (const entry of entries) {
26
+ const previousMatches = conditionMatches;
27
+ conditionMatches = evaluateContainerQuery(entry.target, containerQuery);
28
+ if (conditionMatches && !previousMatches) {
29
+ // Condition now matches - process elements
30
+ handleConditionMatch();
31
+ }
32
+ else if (!conditionMatches && previousMatches) {
33
+ // Condition no longer matches - dismount all elements
34
+ handleConditionUnmatch();
35
+ }
36
+ }
37
+ });
38
+ function handleConditionMatch() {
39
+ // Process all elements in the observed node
40
+ const rootNode = rootNodeRef.deref();
41
+ if (rootNode) {
42
+ processNode(rootNode);
43
+ }
44
+ }
45
+ function handleConditionUnmatch() {
46
+ // Dismount all currently mounted elements
47
+ const rootNode = rootNodeRef.deref();
48
+ if (!rootNode) {
49
+ return;
50
+ }
51
+ const context = {
52
+ modules,
53
+ observer: observer,
54
+ rootNode,
55
+ MountConfig: init
56
+ };
57
+ // Get all mounted elements from the WeakDual setWeak
58
+ const mountedElementsList = [];
59
+ for (const ref of mountedElements.setWeak) {
60
+ const element = ref.deref();
61
+ if (element) {
62
+ mountedElementsList.push(element);
63
+ }
64
+ }
65
+ // Dismount each element
66
+ for (const element of mountedElementsList) {
67
+ // Remove from both structures
68
+ mountedElements.weakSet.delete(element);
69
+ for (const ref of mountedElements.setWeak) {
70
+ if (ref.deref() === element) {
71
+ mountedElements.setWeak.delete(ref);
72
+ break;
73
+ }
74
+ }
75
+ // Dispatch dismount event with reason
76
+ observer.dispatchEvent(new DismountEvent(element, 'root-size-failed', init));
77
+ }
78
+ }
79
+ // Start observing the root element
80
+ resizeObserver.observe(rootElement);
81
+ return {
82
+ conditionMatches,
83
+ cleanup: () => {
84
+ resizeObserver.disconnect();
85
+ }
86
+ };
87
+ }
88
+ /**
89
+ * Evaluate a container query condition against an element
90
+ * Supports: min-width, max-width, min-height, max-height
91
+ */
92
+ function evaluateContainerQuery(element, query) {
93
+ // Parse container query: (min-width: 700px) or (max-height: 500px)
94
+ const match = query.match(/\(([^:]+):\s*([^)]+)\)/);
95
+ if (!match) {
96
+ console.warn(`Invalid container query format: ${query}`);
97
+ return false;
98
+ }
99
+ const [, property, valueStr] = match;
100
+ const prop = property.trim();
101
+ const value = parseFloat(valueStr);
102
+ if (isNaN(value)) {
103
+ console.warn(`Invalid container query value: ${valueStr}`);
104
+ return false;
105
+ }
106
+ // Get element dimensions
107
+ const rect = element.getBoundingClientRect();
108
+ const width = rect.width;
109
+ const height = rect.height;
110
+ // Evaluate condition
111
+ switch (prop) {
112
+ case 'min-width':
113
+ return width >= value;
114
+ case 'max-width':
115
+ return width <= value;
116
+ case 'min-height':
117
+ return height >= value;
118
+ case 'max-height':
119
+ return height <= value;
120
+ default:
121
+ console.warn(`Unsupported container query property: ${prop}`);
122
+ return false;
123
+ }
124
+ }
@@ -0,0 +1,157 @@
1
+ // Root size observation with container query matching for MountObserver
2
+ import type { MountConfig, MountContext, WeakDual } from './types/mount-observer/types.js';
3
+ import { DismountEvent } from './Events.js';
4
+
5
+ export function setupRootSizeObserver(
6
+ init: MountConfig,
7
+ rootNodeRef: WeakRef<Node>,
8
+ mountedElements: WeakDual<Element>,
9
+ modules: any[],
10
+ observer: EventTarget,
11
+ processNode: (node: Node) => void
12
+ ): {
13
+ conditionMatches: boolean;
14
+ cleanup: () => void;
15
+ } {
16
+ const { whereObservedRootSizeMatches } = init;
17
+
18
+ if (!whereObservedRootSizeMatches) {
19
+ throw new Error('whereObservedRootSizeMatches is required');
20
+ }
21
+
22
+ const rootNode = rootNodeRef.deref();
23
+ if (!rootNode) {
24
+ throw new Error('Root node has been garbage collected');
25
+ }
26
+
27
+ // Get the element to observe
28
+ const rootElement = rootNode instanceof Element
29
+ ? rootNode
30
+ : (rootNode as Document).documentElement;
31
+
32
+ if (!rootElement) {
33
+ throw new Error('Could not determine root element for whereObservedRootSizeMatches');
34
+ }
35
+
36
+ // Parse the container query condition
37
+ // Container queries use the same syntax as media queries: (min-width: 700px)
38
+ const containerQuery = whereObservedRootSizeMatches;
39
+
40
+ // Check if condition currently matches
41
+ let conditionMatches = evaluateContainerQuery(rootElement, containerQuery);
42
+
43
+ // Set up ResizeObserver to watch for size changes
44
+ const resizeObserver = new ResizeObserver((entries) => {
45
+ for (const entry of entries) {
46
+ const previousMatches = conditionMatches;
47
+ conditionMatches = evaluateContainerQuery(entry.target as Element, containerQuery);
48
+
49
+ if (conditionMatches && !previousMatches) {
50
+ // Condition now matches - process elements
51
+ handleConditionMatch();
52
+ } else if (!conditionMatches && previousMatches) {
53
+ // Condition no longer matches - dismount all elements
54
+ handleConditionUnmatch();
55
+ }
56
+ }
57
+ });
58
+
59
+ function handleConditionMatch(): void {
60
+ // Process all elements in the observed node
61
+ const rootNode = rootNodeRef.deref();
62
+ if (rootNode) {
63
+ processNode(rootNode);
64
+ }
65
+ }
66
+
67
+ function handleConditionUnmatch(): void {
68
+ // Dismount all currently mounted elements
69
+ const rootNode = rootNodeRef.deref();
70
+ if (!rootNode) {
71
+ return;
72
+ }
73
+
74
+ const context: MountContext = {
75
+ modules,
76
+ observer: observer as any,
77
+ rootNode,
78
+ MountConfig: init
79
+ };
80
+
81
+ // Get all mounted elements from the WeakDual setWeak
82
+ const mountedElementsList: Element[] = [];
83
+ for (const ref of mountedElements.setWeak) {
84
+ const element = ref.deref();
85
+ if (element) {
86
+ mountedElementsList.push(element);
87
+ }
88
+ }
89
+
90
+ // Dismount each element
91
+ for (const element of mountedElementsList) {
92
+ // Remove from both structures
93
+ mountedElements.weakSet.delete(element);
94
+ for (const ref of mountedElements.setWeak) {
95
+ if (ref.deref() === element) {
96
+ mountedElements.setWeak.delete(ref);
97
+ break;
98
+ }
99
+ }
100
+
101
+ // Dispatch dismount event with reason
102
+ observer.dispatchEvent(new DismountEvent(element, 'root-size-failed', init));
103
+ }
104
+ }
105
+
106
+ // Start observing the root element
107
+ resizeObserver.observe(rootElement);
108
+
109
+ return {
110
+ conditionMatches,
111
+ cleanup: () => {
112
+ resizeObserver.disconnect();
113
+ }
114
+ };
115
+ }
116
+
117
+ /**
118
+ * Evaluate a container query condition against an element
119
+ * Supports: min-width, max-width, min-height, max-height
120
+ */
121
+ function evaluateContainerQuery(element: Element, query: string): boolean {
122
+ // Parse container query: (min-width: 700px) or (max-height: 500px)
123
+ const match = query.match(/\(([^:]+):\s*([^)]+)\)/);
124
+ if (!match) {
125
+ console.warn(`Invalid container query format: ${query}`);
126
+ return false;
127
+ }
128
+
129
+ const [, property, valueStr] = match;
130
+ const prop = property.trim();
131
+ const value = parseFloat(valueStr);
132
+
133
+ if (isNaN(value)) {
134
+ console.warn(`Invalid container query value: ${valueStr}`);
135
+ return false;
136
+ }
137
+
138
+ // Get element dimensions
139
+ const rect = element.getBoundingClientRect();
140
+ const width = rect.width;
141
+ const height = rect.height;
142
+
143
+ // Evaluate condition
144
+ switch (prop) {
145
+ case 'min-width':
146
+ return width >= value;
147
+ case 'max-width':
148
+ return width <= value;
149
+ case 'min-height':
150
+ return height >= value;
151
+ case 'max-height':
152
+ return height <= value;
153
+ default:
154
+ console.warn(`Unsupported container query property: ${prop}`);
155
+ return false;
156
+ }
157
+ }