feat: annotate inside/contains overflow metrics with clipping awareness

- insidePredicate: add hasClippedOverflow=1 when reference element
  clips its content (contain:paint or overflow:hidden) and the
  subject overflows beyond the reference bounds
- containsPredicate: same, checking the subject (container) clips
- Reads world.clipping table to determine if the container clips
- Safe when clipping data is absent (unit test fixtures)
This commit is contained in:
John Dvorak
2026-05-21 14:11:47 -07:00
parent d23d2a431e
commit 8dae898304
+26 -2
View File
@@ -459,7 +459,7 @@ export const insidePredicate: PredicateEvaluator = {
sRect.top >= rRect.top - effectiveTolerance && sRect.top >= rRect.top - effectiveTolerance &&
sRect.right <= rRect.right + effectiveTolerance && sRect.right <= rRect.right + effectiveTolerance &&
sRect.bottom <= rRect.bottom + effectiveTolerance; sRect.bottom <= rRect.bottom + effectiveTolerance;
const metrics = { const metrics: Record<string, number> = {
overflowLeft: Math.max(0, rRect.left - sRect.left), overflowLeft: Math.max(0, rRect.left - sRect.left),
overflowTop: Math.max(0, rRect.top - sRect.top), overflowTop: Math.max(0, rRect.top - sRect.top),
overflowRight: Math.max(0, sRect.right - rRect.right), overflowRight: Math.max(0, sRect.right - rRect.right),
@@ -476,6 +476,16 @@ export const insidePredicate: PredicateEvaluator = {
refRight: rRect.right, refRight: rRect.right,
refBottom: rRect.bottom, refBottom: rRect.bottom,
}; };
const hasOverflow = metrics.overflowLeft > 0 || metrics.overflowTop > 0
|| metrics.overflowRight > 0 || metrics.overflowBottom > 0;
if (hasOverflow && world.clipping?.subjectId) {
for (let i = 0; i < world.clipping.subjectId.length; i++) {
if (world.clipping.subjectId[i] === referenceId) {
metrics.hasClippedOverflow = 1;
break;
}
}
}
const diagnostics = pass ? undefined : makePredicateDiagnostic('inside', metrics, [subjectId, referenceId]); const diagnostics = pass ? undefined : makePredicateDiagnostic('inside', metrics, [subjectId, referenceId]);
return makePredicateResult(pass ? 'true' : 'false', metrics, [subjectId, referenceId], diagnostics); return makePredicateResult(pass ? 'true' : 'false', metrics, [subjectId, referenceId], diagnostics);
}, },
@@ -499,7 +509,7 @@ export const containsPredicate: PredicateEvaluator = {
rRect.top >= sRect.top - tolerance && rRect.top >= sRect.top - tolerance &&
rRect.right <= sRect.right + tolerance && rRect.right <= sRect.right + tolerance &&
rRect.bottom <= sRect.bottom + tolerance; rRect.bottom <= sRect.bottom + tolerance;
const metrics = { const metrics: Record<string, number> = {
tolerance, tolerance,
subjectLeft: sRect.left, subjectLeft: sRect.left,
subjectTop: sRect.top, subjectTop: sRect.top,
@@ -510,6 +520,20 @@ export const containsPredicate: PredicateEvaluator = {
refRight: rRect.right, refRight: rRect.right,
refBottom: rRect.bottom, refBottom: rRect.bottom,
}; };
const overflowLeft = Math.max(0, sRect.left - rRect.left);
const overflowTop = Math.max(0, sRect.top - rRect.top);
const overflowRight = Math.max(0, rRect.right - sRect.right);
const overflowBottom = Math.max(0, rRect.bottom - sRect.bottom);
if (overflowLeft > 0 || overflowTop > 0 || overflowRight > 0 || overflowBottom > 0) {
if (world.clipping?.subjectId) {
for (let i = 0; i < world.clipping.subjectId.length; i++) {
if (world.clipping.subjectId[i] === subjectId) {
metrics.hasClippedOverflow = 1;
break;
}
}
}
}
const diagnostics = pass ? undefined : makePredicateDiagnostic('contains', metrics, [subjectId, referenceId]); const diagnostics = pass ? undefined : makePredicateDiagnostic('contains', metrics, [subjectId, referenceId]);
return makePredicateResult(pass ? 'true' : 'false', metrics, [subjectId, referenceId], diagnostics); return makePredicateResult(pass ? 'true' : 'false', metrics, [subjectId, referenceId], diagnostics);
}, },