@comty.io/core 0.1.9 → 0.1.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@comty.io/core",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "Comty visual comments, inspector and local agent tools",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -39,7 +39,8 @@
39
39
  "test:anchors": "node --test tests/anchors.test.mjs tests/anchor-reload.browser.test.mjs",
40
40
  "test:auth": "node --test tests/auth-return.browser.test.mjs",
41
41
  "test:vite": "node --test tests/vite.test.mjs",
42
- "prepack": "npm run build"
42
+ "prepack": "npm run build",
43
+ "test:isolation": "node --test tests/style-isolation.browser.test.mjs"
43
44
  },
44
45
  "devDependencies": {
45
46
  "esbuild": "0.28.2",
package/src/anchors.js CHANGED
@@ -1,437 +1,192 @@
1
- export function createAnchorMatcher({
2
- getElementTextSignature,
3
- getElementOwnTextSignature,
4
- getAttributeSignature,
5
- getSiblingFingerprint,
6
- getPresentationLayer,
7
- isComtyNode,
8
- }) {
9
- function getStableAnchorId(id) {
10
- // React useId values change with mount order, including Base UI and Radix IDs.
11
- return /^(?:(?:radix|base-ui)-)?(?:_r_[\w-]+_|:r[\w-]+:)$/i.test(id || "") ? "" : (id || "");
1
+ import { createResilientAnchors, readAnchor } from "./resilient-anchor.js";
2
+
3
+ export function createAnchorMatcher({ getPresentationLayer, isComtyNode }) {
4
+ const resilient = createResilientAnchors(isComtyNode, getPresentationLayer);
5
+
6
+ function getStableAnchorId(id) {
7
+ // React useId values change with mount order, including Base UI and Radix IDs.
8
+ return /^(?:(?:radix|base-ui)-)?(?:_r_[\w-]+_|:r[\w-]+:)$/i.test(id || "")
9
+ ? ""
10
+ : id || "";
11
+ }
12
+
13
+ function resolveAnchor(context, exact = null, previous = null) {
14
+ const anchor = readAnchor(context);
15
+ return anchor
16
+ ? resilient.resolve(anchor, previous, exact)
17
+ : { element: null, status: "uncertain" };
18
+ }
19
+
20
+ function getViewportBounds() {
21
+ const viewport = window.visualViewport;
22
+ if (!viewport) {
23
+ return {
24
+ left: 0,
25
+ top: 0,
26
+ right: window.innerWidth,
27
+ bottom: window.innerHeight,
28
+ };
12
29
  }
13
30
 
14
- function includesText(haystack, needle) {
15
- if (!haystack || !needle) return false;
16
- return haystack.toLowerCase().includes(needle.toLowerCase());
17
- }
31
+ return {
32
+ left: viewport.offsetLeft,
33
+ top: viewport.offsetTop,
34
+ right: viewport.offsetLeft + viewport.width,
35
+ bottom: viewport.offsetTop + viewport.height,
36
+ };
37
+ }
18
38
 
19
- function similarText(a, b) {
20
- if (!a || !b) return false;
21
- const words = value => new Set(value.toLowerCase().split(/\s+/).filter(Boolean));
22
- const left = words(a), right = words(b);
23
- const common = [...left].filter(word => right.has(word)).length;
24
- return common >= 2 && common / Math.max(left.size, right.size) >= 0.7;
25
- }
26
-
27
- function scoreFingerprint(el, fingerprint) {
28
- if (!el || !fingerprint) return 0;
29
- if (
30
- fingerprint.tag &&
31
- el.tagName.toLowerCase() !== fingerprint.tag.toLowerCase()
32
- ) {
33
- return -1000;
39
+ function isElementTreeHidden(el) {
40
+ let current = el;
41
+ while (current instanceof Element) {
42
+ if (!current.isConnected) return true;
43
+ if (current.hasAttribute("hidden")) return true;
44
+ if (current.hasAttribute("inert")) return true;
45
+ if (current.getAttribute("aria-hidden") === "true") {
46
+ return true;
34
47
  }
35
48
 
36
- let score = 0;
37
- const text = getElementTextSignature(el, fingerprint.textMode === "static");
38
- const ownText = getElementOwnTextSignature(el, fingerprint.textMode === "static");
39
-
40
- score += 20;
41
- const stableId = getStableAnchorId(fingerprint.id);
42
- if (stableId) {
43
- score += el.id.slice(0, 160) === stableId ? 70 : -30;
44
- }
45
- const exactAttributes = [
46
- ["role", "role", 24],
47
- ["ariaLabel", "aria-label", 34],
48
- ["name", "name", 30],
49
- ["type", "type", 12],
50
- ["placeholder", "placeholder", 24],
51
- ["alt", "alt", 28],
52
- ["href", "href", 30],
53
- ["src", "src", 24],
54
- ["title", "title", 20],
55
- ];
56
- for (const [key, attribute, weight] of exactAttributes) {
57
- if (!fingerprint[key]) continue;
58
- score +=
59
- getAttributeSignature(el, attribute) === fingerprint[key]
60
- ? weight
61
- : -Math.ceil(weight / 3);
62
- }
63
-
64
- const testId =
65
- getAttributeSignature(el, "data-testid") ||
66
- getAttributeSignature(el, "data-test") ||
67
- getAttributeSignature(el, "data-cy");
68
- if (fingerprint.testId) {
69
- score += testId === fingerprint.testId ? 55 : -20;
49
+ const style = getComputedStyle(current);
50
+ if (style.display === "none") return true;
51
+ if (style.visibility === "hidden" || style.visibility === "collapse") {
52
+ return true;
70
53
  }
71
-
72
- const classes = Array.isArray(fingerprint.classes)
73
- ? fingerprint.classes
74
- : [];
75
- const matchingClasses = classes.filter((className) =>
76
- el.classList.contains(className),
77
- );
78
- score += matchingClasses.length * 7;
79
- if (classes.length > 0 && matchingClasses.length === 0) score -= 8;
80
-
81
- if (fingerprint.ownText && ownText === fingerprint.ownText) score += 42;
82
- else if (
83
- fingerprint.ownText &&
84
- includesText(ownText, fingerprint.ownText.slice(0, 80))
85
- ) {
86
- score += 20;
87
- }
88
-
89
- if (fingerprint.text && text === fingerprint.text) score += 32;
90
- else if (
91
- fingerprint.text &&
92
- includesText(text, fingerprint.text.slice(0, 80))
93
- ) {
94
- score += 15;
95
- }
96
-
97
- if (text !== fingerprint.text && similarText(text, fingerprint.text)) score += 24;
98
-
99
- if (typeof fingerprint.siblingIndex === "number" && el.parentElement) {
100
- const siblings = [...el.parentElement.children].filter(
101
- (child) => child.tagName === el.tagName,
102
- );
103
- if (siblings.indexOf(el) === fingerprint.siblingIndex) score += 8;
104
- }
105
-
106
- if (
107
- typeof fingerprint.childCount === "number" &&
108
- el.children.length === fingerprint.childCount
109
- ) {
110
- score += 4;
54
+ if (Number.parseFloat(style.opacity) <= 0.01) return true;
55
+ if (style.contentVisibility === "hidden") {
56
+ return true;
111
57
  }
112
58
 
113
- const rect = el.getBoundingClientRect();
114
- if (
115
- fingerprint.width > 0 &&
116
- rect.width > 0 &&
117
- Math.abs(rect.width - fingerprint.width) /
118
- Math.max(rect.width, fingerprint.width) <
119
- 0.2
120
- ) {
121
- score += 4;
122
- }
123
- if (
124
- fingerprint.height > 0 &&
125
- rect.height > 0 &&
126
- Math.abs(rect.height - fingerprint.height) /
127
- Math.max(rect.height, fingerprint.height) <
128
- 0.2
129
- ) {
130
- score += 4;
59
+ if (current.tagName === "DETAILS" && !current.hasAttribute("open")) {
60
+ const summary = current.querySelector(":scope > summary");
61
+ if (!summary || !summary.contains(el)) return true;
131
62
  }
132
63
 
133
- return score;
64
+ current = current.parentElement;
134
65
  }
66
+ return false;
67
+ }
135
68
 
136
- function scoreSibling(el, fingerprint) {
137
- if (!fingerprint) return 0;
138
- const sibling = getSiblingFingerprint(el, fingerprint.textMode === "static");
139
- if (!sibling || sibling.tag !== fingerprint.tag) return -4;
140
- let score = 4;
141
- if (fingerprint.ariaLabel && sibling.ariaLabel === fingerprint.ariaLabel) {
142
- score += 6;
143
- }
144
- if (fingerprint.role && sibling.role === fingerprint.role) score += 4;
145
- if (fingerprint.text && sibling.text === fingerprint.text) score += 8;
146
- return score;
147
- }
69
+ function pointIsInsideRect(x, y, rect) {
70
+ return x >= rect.left && x < rect.right && y >= rect.top && y < rect.bottom;
71
+ }
148
72
 
149
- function scoreAnchorCandidate(el, context) {
150
- if (!el || !context?.target) return 0;
151
- let score = scoreFingerprint(el, context.target);
152
- if (score < 0) return score;
153
-
154
- score += scoreSibling(
155
- el.previousElementSibling,
156
- context.target.previousSibling,
73
+ function isPointClipped(el, x, y) {
74
+ let ancestor = el.parentElement;
75
+ while (
76
+ ancestor &&
77
+ ancestor !== document.body &&
78
+ ancestor !== document.documentElement
79
+ ) {
80
+ const style = getComputedStyle(ancestor);
81
+ const clipsX = ["auto", "scroll", "hidden", "clip"].includes(
82
+ style.overflowX,
157
83
  );
158
- score += scoreSibling(el.nextElementSibling, context.target.nextSibling);
159
-
160
- const ancestors = Array.isArray(context.ancestors)
161
- ? context.ancestors
162
- : [];
163
- let parent = el.parentElement;
164
- for (
165
- let index = 0;
166
- parent && index < ancestors.length;
167
- index += 1
168
- ) {
169
- const ancestorScore = scoreFingerprint(
170
- parent,
171
- ancestors[index],
172
- );
173
- if (ancestorScore > 0) {
174
- score += Math.min(ancestorScore, 40) * (index === 0 ? 0.5 : 0.2);
175
- }
176
- // Distinguish repeated cards by their section, even when dimensions match.
177
- if (ancestors[index].text && getElementTextSignature(parent, ancestors[index].textMode === "static") === ancestors[index].text) score += 18;
178
- parent = parent.parentElement;
179
- }
180
-
181
- if (context.layer) {
182
- const layer = getPresentationLayer(el);
183
- if (!layer) return -1000;
184
- const layerScore = scoreFingerprint(layer, context.layer);
185
- if (layerScore < 30) return -1000;
186
- score += Math.min(layerScore, 60) * 0.5;
187
- }
188
-
189
- return score;
190
- }
191
-
192
- function matchesAnchorIdentity(el, fingerprint, ignoreDescendantText = false, allowTextChanges = true) {
193
- if (!el || !fingerprint || el.tagName.toLowerCase() !== fingerprint.tag) return false;
194
- const attributes = { ariaLabel: "aria-label", name: "name", href: "href", src: "src", alt: "alt", title: "title", role: "role" };
195
- for (const [key, attribute] of Object.entries(attributes)) {
196
- if (fingerprint[key] && getAttributeSignature(el, attribute) !== fingerprint[key]) return false;
197
- }
198
- const stableId = getStableAnchorId(fingerprint.id);
199
- if (stableId && el.id !== stableId) return false;
200
- if (fingerprint.testId && !["data-testid", "data-test", "data-cy"].some(name => getAttributeSignature(el, name) === fingerprint.testId)) return false;
201
- const stableIdentity = (stableId && el.id === stableId) ||
202
- (fingerprint.testId && ["data-testid", "data-test", "data-cy"].some(name => getAttributeSignature(el, name) === fingerprint.testId));
203
- const staticText = fingerprint.textMode === "static";
204
- const text = getElementTextSignature(el, staticText);
205
- if (fingerprint.ownText && getElementOwnTextSignature(el, staticText) !== fingerprint.ownText && !(allowTextChanges && stableIdentity)) return false;
206
- // Container text is useful for matching remounted drawers and dialogs.
207
- if (!ignoreDescendantText && !fingerprint.ownText && fingerprint.text && text !== fingerprint.text && !(allowTextChanges && stableIdentity)) {
208
- if (!allowTextChanges || !el.children.length || !similarText(text, fingerprint.text)) return false;
209
- }
210
- return true;
211
- }
212
-
213
- function matchesAnchorContext(el, context) {
214
- if (!context) return true;
215
- // A unique page landmark keeps its identity when a child is edited.
216
- const pageContainer = !context.layer && (context.target?.tag === "main" || context.target?.role === "main") &&
217
- [...document.querySelectorAll('main, [role="main"]')].filter(node => !isComtyNode(node)).length === 1;
218
- if (!matchesAnchorIdentity(el, context.target, pageContainer)) return false;
219
- const parentFingerprint = context.ancestors?.[0];
220
- if (parentFingerprint?.text && parentFingerprint.text !== context.target.text &&
221
- !matchesAnchorIdentity(el.parentElement, parentFingerprint, pageContainer)) return false;
222
- const layer = getPresentationLayer(el);
223
- if (context.layer && (!layer || !matchesAnchorIdentity(layer, context.layer, false, false))) return false;
224
- if (context.version >= 2 && !context.layer && layer) return false;
225
- return scoreAnchorCandidate(el, context) >= 50;
226
- }
227
-
228
- function resolveAnchor(context, exact = null) {
229
- if (!context?.target) return { element: exact, status: exact ? "matched" : "missing" };
230
- let candidates;
231
- try {
232
- candidates = [...document.body.querySelectorAll(context.target.tag || "*")].filter(el => !isComtyNode(el));
233
- } catch {
234
- return { element: null, status: "missing" };
235
- }
236
- const matches = candidates
237
- .filter(el => matchesAnchorContext(el, context))
238
- .map(el => ({ el, score: scoreAnchorCandidate(el, context) + (context.version === 1 && el === exact ? 16 : 0) }))
239
- .sort((a, b) => b.score - a.score);
240
- if (context.candidateCount > 1) {
241
- // Creation counts full context matches, not every element with the same text.
242
- const peers = candidates.filter(el => matchesAnchorIdentity(el, context.target));
243
- if (matches.length !== context.candidateCount && peers.length !== context.candidateCount) {
244
- // A removed identical sibling must not move its comment to the survivor.
245
- const distinctive = matches[0] && (getStableAnchorId(context.target.id) || context.target.testId);
246
- if (!distinctive) return { element: null, status: "uncertain" };
247
- }
248
- }
249
- if (matches[0]?.score >= 58 && (!matches[1] || matches[0].score - matches[1].score >= 12)) {
250
- return { element: matches[0].el, status: "matched" };
251
- }
252
- // A closed/unmounted overlay is not evidence that its contents were deleted.
253
- if (context.layer && !candidates.some(el => {
254
- const layer = getPresentationLayer(el);
255
- return layer && matchesAnchorIdentity(layer, context.layer, false, false);
256
- })) return { element: null, status: "hidden" };
257
- const plausible = matches.length || candidates.some(el =>
258
- (getStableAnchorId(context.target.id) && el.id === context.target.id) ||
259
- (context.target.text && (getElementTextSignature(el, context.target.textMode === "static") === context.target.text || similarText(getElementTextSignature(el, context.target.textMode === "static"), context.target.text))));
260
- return { element: null, status: plausible ? "uncertain" : "missing" };
261
- }
262
-
263
- function resolveByAnchorContext(context) {
264
- return resolveAnchor(context).element;
265
- }
266
-
267
- function getViewportBounds() {
268
- const viewport = window.visualViewport;
269
- if (!viewport) {
270
- return {
271
- left: 0,
272
- top: 0,
273
- right: window.innerWidth,
274
- bottom: window.innerHeight,
275
- };
276
- }
277
-
278
- return {
279
- left: viewport.offsetLeft,
280
- top: viewport.offsetTop,
281
- right: viewport.offsetLeft + viewport.width,
282
- bottom: viewport.offsetTop + viewport.height,
283
- };
284
- }
285
-
286
- function isElementTreeHidden(el) {
287
- let current = el;
288
- while (current instanceof Element) {
289
- if (!current.isConnected) return true;
290
- if (current.hasAttribute("hidden")) return true;
291
- if (current.hasAttribute("inert")) return true;
292
- if (current.getAttribute("aria-hidden") === "true") {
293
- return true;
294
- }
295
-
296
- const style = getComputedStyle(current);
297
- if (style.display === "none") return true;
298
- if (style.visibility === "hidden" || style.visibility === "collapse") {
84
+ const clipsY = ["auto", "scroll", "hidden", "clip"].includes(
85
+ style.overflowY,
86
+ );
87
+ if (clipsX || clipsY) {
88
+ const rect = ancestor.getBoundingClientRect();
89
+ if (clipsX && (x < rect.left || x > rect.right)) {
299
90
  return true;
300
91
  }
301
- if (Number.parseFloat(style.opacity) <= 0.01) return true;
302
- if (style.contentVisibility === "hidden") {
92
+ if (clipsY && (y < rect.top || y > rect.bottom)) {
303
93
  return true;
304
94
  }
305
-
306
- if (
307
- current.tagName === "DETAILS" &&
308
- !current.hasAttribute("open")
309
- ) {
310
- const summary = current.querySelector(":scope > summary");
311
- if (!summary || !summary.contains(el)) return true;
312
- }
313
-
314
- current = current.parentElement;
315
95
  }
316
- return false;
96
+ ancestor = ancestor.parentElement;
317
97
  }
98
+ return false;
99
+ }
318
100
 
319
- function pointIsInsideRect(x, y, rect) {
320
- return (
321
- x >= rect.left &&
322
- x < rect.right &&
323
- y >= rect.top &&
324
- y < rect.bottom
325
- );
326
- }
327
-
328
- function isPointClipped(el, x, y) {
329
- let ancestor = el.parentElement;
330
- while (
331
- ancestor &&
332
- ancestor !== document.body &&
333
- ancestor !== document.documentElement
334
- ) {
335
- const style = getComputedStyle(ancestor);
336
- const clipsX = ["auto", "scroll", "hidden", "clip"].includes(
337
- style.overflowX,
338
- );
339
- const clipsY = ["auto", "scroll", "hidden", "clip"].includes(
340
- style.overflowY,
341
- );
342
- if (clipsX || clipsY) {
343
- const rect = ancestor.getBoundingClientRect();
344
- if (clipsX && (x < rect.left || x > rect.right)) {
345
- return true;
346
- }
347
- if (clipsY && (y < rect.top || y > rect.bottom)) {
348
- return true;
349
- }
350
- }
351
- ancestor = ancestor.parentElement;
352
- }
353
- return false;
101
+ function getTopActiveModal() {
102
+ const modals = [...document.querySelectorAll('[aria-modal="true"]')];
103
+ try {
104
+ modals.push(...document.querySelectorAll("dialog:modal"));
105
+ } catch {
106
+ modals.push(...document.querySelectorAll("dialog[open]"));
354
107
  }
355
-
356
- function getTopActiveModal() {
357
- const modals = [...document.querySelectorAll('[aria-modal="true"]')];
358
- try {
359
- modals.push(...document.querySelectorAll("dialog:modal"));
360
- } catch {
361
- modals.push(...document.querySelectorAll("dialog[open]"));
362
- }
363
- const visibleModals = [...new Set(modals)].filter(
364
- (modal) => {
365
- if (isComtyNode(modal)) return false;
366
- const rect = modal.getBoundingClientRect();
367
- if (rect.width < 0.1 || rect.height < 0.1) return false;
368
- const viewport = getViewportBounds();
369
- if (
370
- rect.right <= viewport.left ||
371
- rect.left >= viewport.right ||
372
- rect.bottom <= viewport.top ||
373
- rect.top >= viewport.bottom
374
- ) {
375
- return false;
376
- }
377
- return !isElementTreeHidden(modal);
378
- },
379
- );
380
-
381
- return (
382
- visibleModals.sort((a, b) => {
383
- const aZIndex = Number.parseFloat(getComputedStyle(a).zIndex) || 0;
384
- const bZIndex = Number.parseFloat(getComputedStyle(b).zIndex) || 0;
385
- if (aZIndex !== bZIndex) return aZIndex - bZIndex;
386
- return a.compareDocumentPosition(b) &
387
- Node.DOCUMENT_POSITION_FOLLOWING
388
- ? -1
389
- : 1;
390
- })[visibleModals.length - 1] || null
391
- );
392
- }
393
-
394
- function getOccludingElement(el, x, y) {
395
- const stack = document
396
- .elementsFromPoint(x, y)
397
- .filter((candidate) => !isComtyNode(candidate));
398
- const top = stack[0];
399
- if (!top) return null;
400
- if (top === el || el.contains(top) || top.contains(el)) return null;
401
- return top;
402
- }
403
-
404
- function getVisibleAnchorPoint(el, relX, relY, activeModal) {
405
- if (!el || !Number.isFinite(relX) || !Number.isFinite(relY) || isElementTreeHidden(el)) return null;
406
-
407
- const rect = el.getBoundingClientRect();
408
- if (rect.width < 0.1 || rect.height < 0.1) {
409
- return null;
410
- }
411
-
412
- const clientX = Math.min(
413
- Math.max(rect.left + relX * rect.width, rect.left + 0.01),
414
- rect.right - 0.01,
415
- );
416
- const clientY = Math.min(
417
- Math.max(rect.top + relY * rect.height, rect.top + 0.01),
418
- rect.bottom - 0.01,
419
- );
108
+ const visibleModals = [...new Set(modals)].filter((modal) => {
109
+ if (isComtyNode(modal)) return false;
110
+ const rect = modal.getBoundingClientRect();
111
+ if (rect.width < 0.1 || rect.height < 0.1) return false;
420
112
  const viewport = getViewportBounds();
421
- if (!pointIsInsideRect(clientX, clientY, viewport)) {
422
- return null;
423
- }
424
-
425
- if (isPointClipped(el, clientX, clientY)) return null;
426
-
427
- if (activeModal && !activeModal.contains(el)) {
428
- return null;
429
- }
430
-
431
- if (getOccludingElement(el, clientX, clientY)) return null;
432
-
433
- return { clientX, clientY };
434
- }
435
-
436
- return { getStableAnchorId, matchesAnchorContext, resolveAnchor, resolveByAnchorContext, isElementTreeHidden, getTopActiveModal, getVisibleAnchorPoint };
113
+ if (
114
+ rect.right <= viewport.left ||
115
+ rect.left >= viewport.right ||
116
+ rect.bottom <= viewport.top ||
117
+ rect.top >= viewport.bottom
118
+ ) {
119
+ return false;
120
+ }
121
+ return !isElementTreeHidden(modal);
122
+ });
123
+
124
+ return (
125
+ visibleModals.sort((a, b) => {
126
+ const aZIndex = Number.parseFloat(getComputedStyle(a).zIndex) || 0;
127
+ const bZIndex = Number.parseFloat(getComputedStyle(b).zIndex) || 0;
128
+ if (aZIndex !== bZIndex) return aZIndex - bZIndex;
129
+ return a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING
130
+ ? -1
131
+ : 1;
132
+ })[visibleModals.length - 1] || null
133
+ );
134
+ }
135
+
136
+ function getOccludingElement(el, x, y) {
137
+ const stack = document
138
+ .elementsFromPoint(x, y)
139
+ .filter((candidate) => !isComtyNode(candidate));
140
+ const top = stack[0];
141
+ if (!top) return null;
142
+ if (top === el || el.contains(top) || top.contains(el)) return null;
143
+ return top;
144
+ }
145
+
146
+ function getVisibleAnchorPoint(el, relX, relY, activeModal) {
147
+ if (
148
+ !el ||
149
+ !Number.isFinite(relX) ||
150
+ !Number.isFinite(relY) ||
151
+ isElementTreeHidden(el)
152
+ )
153
+ return null;
154
+
155
+ const rect = el.getBoundingClientRect();
156
+ if (rect.width < 0.1 || rect.height < 0.1) {
157
+ return null;
158
+ }
159
+
160
+ const clientX = Math.min(
161
+ Math.max(rect.left + relX * rect.width, rect.left + 0.01),
162
+ rect.right - 0.01,
163
+ );
164
+ const clientY = Math.min(
165
+ Math.max(rect.top + relY * rect.height, rect.top + 0.01),
166
+ rect.bottom - 0.01,
167
+ );
168
+ const viewport = getViewportBounds();
169
+ if (!pointIsInsideRect(clientX, clientY, viewport)) {
170
+ return null;
171
+ }
172
+
173
+ if (isPointClipped(el, clientX, clientY)) return null;
174
+
175
+ if (activeModal && !activeModal.contains(el)) {
176
+ return null;
177
+ }
178
+
179
+ if (getOccludingElement(el, clientX, clientY)) return null;
180
+
181
+ return { clientX, clientY };
182
+ }
183
+
184
+ return {
185
+ captureResilientAnchor: resilient.capture,
186
+ getStableAnchorId,
187
+ resolveAnchor,
188
+ isElementTreeHidden,
189
+ getTopActiveModal,
190
+ getVisibleAnchorPoint,
191
+ };
437
192
  }