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