Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b64da7a787 |
+82
-4
@@ -93,10 +93,44 @@ const LayoutAudit = {
|
||||
return false;
|
||||
},
|
||||
|
||||
// The modal that is currently on top, if any.
|
||||
//
|
||||
// Every check below asks "is this thing reachable", and while a sheet or dialog is
|
||||
// open the answer for everything behind it is legitimately no — that is what a
|
||||
// modal is for. Without knowing that, the audit reported the pot wizard covering
|
||||
// the seats as ten faults and the rulings sheet as seven, which is the noise that
|
||||
// made it useless in exactly the states nobody had audited. Now it scopes to what
|
||||
// is actually on top and says so.
|
||||
MODAL_SELECTOR: "#potWizardOverlay, #rulingsOverlay, #seatMenuOverlay, #cardPickerOverlay, " +
|
||||
"#confirmOverlay, #addChipsOverlay, #seatEditorOverlay",
|
||||
|
||||
openModal() {
|
||||
const open = [...document.querySelectorAll(this.MODAL_SELECTOR)]
|
||||
.filter(el => !el.hidden && getComputedStyle(el).display !== "none");
|
||||
// Last in the DOM wins, which is how they stack.
|
||||
return open.length > 0 ? open[open.length - 1] : null;
|
||||
},
|
||||
|
||||
// A seat sheet on a phone isn't an overlay element, it's a body class — but it
|
||||
// covers the felt just the same.
|
||||
modalScope() {
|
||||
const overlay = this.openModal();
|
||||
if (overlay) return overlay;
|
||||
if (document.body.classList.contains("seatSheetOpen")) {
|
||||
return document.getElementById("seatEditorPanel") || null;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
occlusion(selector = "#actionBar button, #holdemHeader button, .seat") {
|
||||
const offenders = [];
|
||||
const warnings = [];
|
||||
[...document.querySelectorAll(selector)].forEach(el => {
|
||||
// While something is open on top, only what is inside it can be judged. The
|
||||
// rest of the page is meant to be unreachable.
|
||||
const scope = this.modalScope();
|
||||
const candidates = [...document.querySelectorAll(selector)]
|
||||
.filter(el => !scope || scope.contains(el));
|
||||
candidates.forEach(el => {
|
||||
const rect = el.getBoundingClientRect();
|
||||
if (rect.width === 0 || rect.height === 0) return;
|
||||
|
||||
@@ -504,10 +538,25 @@ const LayoutAudit = {
|
||||
if (!enforceOnPointerDevices && matchMedia("(hover: hover)").matches) {
|
||||
return { name: `touch targets at least ${minimum}px`, offenders, skipped: "pointer device" };
|
||||
}
|
||||
const unmeasured = [];
|
||||
this.visibleControls()
|
||||
.filter(el => el.type !== "color")
|
||||
.filter(el => !(exempt && el.matches(exempt)))
|
||||
.forEach(el => {
|
||||
// A control inside a scrolling sheet can sit below its container's
|
||||
// visible area while still having a viewport rect. elementFromPoint at
|
||||
// its centre then returns the sheet, effectiveTarget gives up, and the
|
||||
// check used to pass it in silence — which is how "Confirm Winner(s)",
|
||||
// the button that pays the pot out, went unmeasured for 36 rounds.
|
||||
// Bring it into view before probing.
|
||||
const scroller = el.closest("[style*='overflow'], .modalBody, #potWizardBody, #rulingsSheet, #seatEditorPanel");
|
||||
if (scroller && scroller.scrollHeight > scroller.clientHeight) {
|
||||
const box = scroller.getBoundingClientRect();
|
||||
const own = el.getBoundingClientRect();
|
||||
if (own.bottom > box.bottom || own.top < box.top) {
|
||||
el.scrollIntoView({ block: "center" });
|
||||
}
|
||||
}
|
||||
const rect = el.getBoundingClientRect();
|
||||
// Drawn big enough is big enough — no need to probe, and probing wide
|
||||
// elements is where the cap above used to produce nonsense. Seats sit
|
||||
@@ -515,15 +564,35 @@ const LayoutAudit = {
|
||||
// measuring their reach would report the crowding as a fault.
|
||||
if (rect.width >= minimum - 1 && rect.height >= minimum - 1) return;
|
||||
|
||||
// A checkbox inside a label is tapped by tapping the label, so the
|
||||
// label is the target. Measured rather than probed, because probing
|
||||
// fails on anything scrolled off the viewport — which is how a 24px
|
||||
// checkbox with a 335x44 label around it came up as unprobeable
|
||||
// instead of as the perfectly good target it is.
|
||||
const label = el.closest("label");
|
||||
if (label) {
|
||||
const lr = label.getBoundingClientRect();
|
||||
if (lr.width >= minimum - 1 && lr.height >= minimum - 1) return;
|
||||
}
|
||||
|
||||
const size = this.effectiveTarget(el);
|
||||
if (!size) return;
|
||||
// Still can't measure it. That is not a pass — it is the check failing
|
||||
// to run, and quietly returning here is exactly the kind of exemption
|
||||
// that hides real faults. Say so instead.
|
||||
if (!size) {
|
||||
unmeasured.push(
|
||||
`${this.describe(el)} :: could not be probed` +
|
||||
` (drawn ${Math.round(rect.width)}x${Math.round(rect.height)})`
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (size.width >= minimum - 1 && size.height >= minimum - 1) return;
|
||||
offenders.push(
|
||||
`${this.describe(el)} :: hittable ${size.width}x${size.height}` +
|
||||
` (drawn ${Math.round(rect.width)}x${Math.round(rect.height)})`
|
||||
);
|
||||
});
|
||||
return { name: `touch targets at least ${minimum}px`, offenders };
|
||||
return { name: `touch targets at least ${minimum}px`, offenders, unmeasured };
|
||||
},
|
||||
|
||||
// Tapping the middle of a seat must give you that seat.
|
||||
@@ -537,6 +606,13 @@ const LayoutAudit = {
|
||||
// Reported separately from occlusion because the cause is different: not an
|
||||
// overlay on top of the table, but two pieces of the table fighting over a tap.
|
||||
seatOwnership() {
|
||||
// Seats behind an open sheet aren't fighting each other for taps — nothing is
|
||||
// meant to reach them at all. Asking the question there produced six offenders
|
||||
// per modal, every one of them the modal doing its job.
|
||||
if (this.modalScope()) {
|
||||
return { name: "a seat's centre belongs to that seat", offenders: [],
|
||||
skipped: "a sheet is open over the table" };
|
||||
}
|
||||
const seats = [...document.querySelectorAll(".seat")];
|
||||
const offenders = [];
|
||||
|
||||
@@ -617,7 +693,9 @@ const LayoutAudit = {
|
||||
viewport: `${innerWidth}x${innerHeight}`,
|
||||
failed: checks.filter(c => c.offenders.length > 0),
|
||||
// Things worth knowing that aren't faults — see occlusion() on toasts.
|
||||
warnings: checks.flatMap(c => c.warnings || []),
|
||||
// Controls a check couldn't probe go here too: not a fault, but not a pass
|
||||
// either, and a silent skip is how faults hide.
|
||||
warnings: checks.flatMap(c => [...(c.warnings || []), ...(c.unmeasured || [])]),
|
||||
passed: checks.filter(c => c.offenders.length === 0).map(c => c.name),
|
||||
clean: checks.every(c => c.offenders.length === 0),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user