Hold'em: every seat on the ring is the same seat, and the ring is measured before it is believed #122
@@ -549,8 +549,12 @@
|
||||
|
||||
<div id="rulingsOverlay" hidden>
|
||||
<div id="rulingsSheet" role="dialog" aria-labelledby="rulingsTitle">
|
||||
<div class="seatSheetGrip" aria-hidden="true"></div>
|
||||
@* The grip belongs inside the header, not above it, because the header is stuck
|
||||
to the top of the scrollport — anything above a sticky thing is something the
|
||||
list scrolls out through. The seat menu's header is built the same way, and
|
||||
for the same reason. *@
|
||||
<div class="rulingsHeader">
|
||||
<div class="seatSheetGrip" aria-hidden="true"></div>
|
||||
<h2 id="rulingsTitle" tabindex="-1" data-modal-focus>Common rulings</h2>
|
||||
<button id="closeRulingsBtn" data-modal-close class="holdemBtn holdemBtnSmall holdemBtnGhost" type="button">Done</button>
|
||||
</div>
|
||||
@@ -579,6 +583,14 @@
|
||||
Not exclusive (no `name` attribute): closing one because another opened moves
|
||||
everything under the dealer's finger, which is the disorientation the
|
||||
accordion is here to avoid. *@
|
||||
|
||||
@* Six and six, down one column and then the next. Two boxes rather than one
|
||||
list so that landscape can lay them side by side without the columns
|
||||
sharing rows: opening a ruling in one of them moves what is under it in
|
||||
that column and nothing else. Everywhere but landscape the wrappers are
|
||||
`display: contents` and the twelve are one column, exactly as before. *@
|
||||
<div class="rulingsList">
|
||||
<div class="rulingsColumn">
|
||||
<details class="ruling">
|
||||
<summary><h3>Redeal it — misdeal</h3></summary>
|
||||
<ul>
|
||||
@@ -654,7 +666,9 @@
|
||||
off the short one. The action bar follows this for you.
|
||||
</p>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<div class="rulingsColumn">
|
||||
<details class="ruling">
|
||||
<summary><h3>Chips nobody called come back</h3></summary>
|
||||
<p>
|
||||
@@ -733,6 +747,8 @@
|
||||
decide how much to bet. Say it, or push it all at once.
|
||||
</p>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="rulingsSource">
|
||||
Follows the Tournament Directors Association rules, which most rooms and
|
||||
|
||||
@@ -2179,29 +2179,60 @@ input[type="checkbox"] {
|
||||
max-width: 460px;
|
||||
max-height: 86vh;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
background: linear-gradient(180deg, var(--surface-2), var(--surface-1));
|
||||
border: 1px solid var(--line);
|
||||
border-bottom: none;
|
||||
border-radius: var(--r-lg) var(--r-lg) 0 0;
|
||||
padding: var(--sp-1) var(--sp-4) calc(var(--sp-5) + env(safe-area-inset-bottom, 0px));
|
||||
/* No padding at the top: the header owns it, because the header is sticky and a
|
||||
gap above a sticky thing is a gap the list scrolls through. The seat menu's
|
||||
sheet is built the same way, one section down. */
|
||||
padding: 0 var(--sp-4) calc(var(--sp-5) + env(safe-area-inset-bottom, 0px));
|
||||
box-shadow: 0 -14px 40px rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
|
||||
/* Stuck to the top of the sheet's own scrollport, for the reason the seat menu's header
|
||||
is: the way out should not scroll away. This sheet is the longer of the two and never
|
||||
had it. Measured before: on a 360x640 phone the twelve shut rulings are 791px of a
|
||||
549px sheet, so at the bottom of that scroll Done sits 221px above the top edge — and
|
||||
on a 390x844 phone with one ruling opened, which is the only reason anybody is in
|
||||
here, 262px. A dealer who has read the answer has to scroll back past it to leave. */
|
||||
.rulingsHeader {
|
||||
display: flex;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: var(--sp-2);
|
||||
margin-bottom: var(--sp-2);
|
||||
column-gap: var(--sp-2);
|
||||
/* Out to the sheet's edges and back again, so nothing shows through either side of
|
||||
a header sitting over a scrolling list. */
|
||||
margin: 0 calc(var(--sp-4) * -1) var(--sp-2);
|
||||
padding: var(--sp-1) var(--sp-4) var(--sp-2);
|
||||
background: var(--surface-2);
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
/* The grip is inside the header now rather than above it — above a sticky element is
|
||||
the one place on this sheet that scrolls out of sight. */
|
||||
.rulingsHeader .seatSheetGrip {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.rulingsHeader h2 {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
font-size: var(--fs-lg);
|
||||
font-weight: 500;
|
||||
color: var(--text-bright);
|
||||
}
|
||||
|
||||
/* Two boxes of six that are not boxes at all: `display: contents` hands the twelve
|
||||
rulings straight to the list, so every layout but landscape sees the single column it
|
||||
always had. Landscape turns them back into boxes and stands them side by side. */
|
||||
.rulingsColumn {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.rulingsIntro {
|
||||
margin: 0 0 var(--sp-4);
|
||||
color: var(--text-muted);
|
||||
@@ -5853,6 +5884,116 @@ body.hasActionBar #toastHost {
|
||||
inset: max(24px, 12%) 44px min(26px, 12%);
|
||||
}
|
||||
|
||||
/* ---------- Common rulings: the sheet becomes the screen ----------
|
||||
|
||||
Everywhere else this is a bottom sheet a dealer scrolls, and that works because
|
||||
upright there is height to scroll through. Sideways there is not. Measured at
|
||||
844x390 before this: a 460px-wide sheet standing in the middle of an 844px window
|
||||
with 384px of that window unused either side of it, 754px of rulings in 334px of
|
||||
scrollport, 111px of it spent on a title and a caveat that stand still — four of
|
||||
the twelve names on screen, and the dealer is looking one up with an argument
|
||||
going on at the table.
|
||||
|
||||
So the sheet takes the window, and the width pays for the height. Material's rule
|
||||
for a bottom sheet on a landscape screen is to stop being one — a side sheet on a
|
||||
wide screen, a full-screen dialog when the content needs real scrolling — and this
|
||||
is the tool's own move as well: the action bar becomes a right-hand rail under
|
||||
exactly this query, for exactly this reason.
|
||||
|
||||
The twelve go into two columns of six. Measured after: all twelve names on screen
|
||||
at 844x390 with nothing to scroll at all, and the columns are 400px wide, which is
|
||||
a better measure for the prose inside an open ruling than the 460 it had.
|
||||
|
||||
Capped rather than unbounded, because this query also matches a short desktop
|
||||
window, and two columns of a 1400px window would be 680px lines of body text. */
|
||||
#rulingsOverlay {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
#rulingsSheet {
|
||||
max-width: 62rem;
|
||||
max-height: none;
|
||||
height: 100%;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
/* The list inside does the scrolling now, so the sheet itself does none. */
|
||||
overflow: hidden;
|
||||
padding:
|
||||
var(--sp-2)
|
||||
calc(var(--sp-4) + env(safe-area-inset-right, 0px))
|
||||
calc(var(--sp-2) + env(safe-area-inset-bottom, 0px))
|
||||
calc(var(--sp-4) + env(safe-area-inset-left, 0px));
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
grid-template-rows: auto minmax(0, 1fr) auto;
|
||||
grid-template-areas:
|
||||
"title intro done"
|
||||
"list list list"
|
||||
"src src src";
|
||||
column-gap: var(--sp-3);
|
||||
row-gap: var(--sp-2);
|
||||
}
|
||||
|
||||
/* The title, the caveat and the way out share one line, which is the whole of the
|
||||
111px this screen was spending on them. `display: contents` because the three sit
|
||||
in two different boxes in the markup and the sheet's own grid is where they need
|
||||
to be. The sticky positioning goes with the box it is dropping, and is not wanted
|
||||
here: nothing above the list scrolls, so nothing has to stick. */
|
||||
.rulingsHeader {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
/* A grab handle for a sheet that is not a sheet any more. */
|
||||
.rulingsHeader .seatSheetGrip {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rulingsHeader h2 {
|
||||
grid-area: title;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.rulingsIntro {
|
||||
grid-area: intro;
|
||||
align-self: center;
|
||||
margin: 0;
|
||||
font-size: var(--fs-sm);
|
||||
}
|
||||
|
||||
#closeRulingsBtn {
|
||||
grid-area: done;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
/* Two tracks holding two independent flows, not one flow wrapped into two. That is
|
||||
what the pair of wrappers in the markup buys: a grid with twelve children of its
|
||||
own would pair them into six shared rows, and opening the third ruling on the left
|
||||
would push the ninth down the right-hand side to keep the row square. Here a
|
||||
column only ever moves itself.
|
||||
|
||||
`scrollbar-gutter` because the gutter arrives the moment a ruling is opened, and
|
||||
without the reservation it takes 8px off the tracks and slides the right-hand
|
||||
column sideways while the dealer is reading it. */
|
||||
.rulingsList {
|
||||
grid-area: list;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
scrollbar-gutter: stable;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
column-gap: var(--sp-5);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.rulingsColumn {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.rulingsSource {
|
||||
grid-area: src;
|
||||
padding-top: var(--sp-1);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Short phones, upright ----------
|
||||
|
||||
@@ -40,6 +40,35 @@ const ruleFor = selector => {
|
||||
return CSS.slice(at, CSS.indexOf("}", at));
|
||||
};
|
||||
|
||||
// Every @media block written at exactly this condition, concatenated — the same helper
|
||||
// theHeader.test.js uses, and for the same reason: this condition is opened more than
|
||||
// once in the file, and a helper that stops at the first finds a block with none of
|
||||
// these rules in it and reports them missing.
|
||||
const mediaBlock = condition => {
|
||||
const found = [];
|
||||
const needle = "@media ";
|
||||
for (let at = CSS.indexOf(needle); at >= 0; at = CSS.indexOf(needle, at + 1)) {
|
||||
const open = CSS.indexOf("{", at);
|
||||
if (CSS.slice(at + needle.length, open).trim() !== condition) continue;
|
||||
let depth = 0;
|
||||
for (let i = open; i < CSS.length; i++) {
|
||||
if (CSS[i] === "{") depth++;
|
||||
else if (CSS[i] === "}" && --depth === 0) { found.push(CSS.slice(open + 1, i)); break; }
|
||||
}
|
||||
}
|
||||
assert.ok(found.length > 0, `no @media block written as "${condition}"`);
|
||||
return found.join("\n");
|
||||
};
|
||||
|
||||
const ruleIn = (block, selector) => {
|
||||
const re = new RegExp(`(^|[},])\\s*${selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*\\{([^}]*)\\}`, "m");
|
||||
const found = block.match(re);
|
||||
assert.ok(found, `there is no \`${selector}\` rule in that block`);
|
||||
return found[2];
|
||||
};
|
||||
|
||||
const SIDEWAYS = () => mediaBlock("(orientation: landscape) and (max-height: 560px)");
|
||||
|
||||
// Each ruling as its own slice of markup.
|
||||
const rulings = () => {
|
||||
const parts = sheet().split('<details class="ruling"');
|
||||
@@ -140,3 +169,106 @@ describe("reading one of them", () => {
|
||||
assert.match(MAIN, /getElementById\("openRulingsBtn"\)\.addEventListener\("click", openRulings\)/);
|
||||
});
|
||||
});
|
||||
|
||||
// The dealer who has read the answer has to be able to leave without scrolling back
|
||||
// past it. Measured before this: on a 360x640 phone the twelve shut rulings are 791px of
|
||||
// a 549px sheet, so at the bottom of that scroll Done is 221px above the sheet's top
|
||||
// edge; on a 390x844 phone with one ruling opened — the only reason anybody is in here —
|
||||
// it is 262px. The seat menu's sheet had already been given the fix and this one, the
|
||||
// longer of the two, had not.
|
||||
describe("the way out does not scroll away", () => {
|
||||
test("the header is stuck to the top of the sheet's own scrollport", () => {
|
||||
const rule = ruleFor(".rulingsHeader");
|
||||
assert.match(rule, /position:\s*sticky/);
|
||||
assert.match(rule, /top:\s*0/);
|
||||
// Out to the sheet's edges and back, or the list shows through either side of it.
|
||||
assert.match(rule, /margin:\s*0 calc\(var\(--sp-4\) \* -1\)/);
|
||||
assert.match(rule, /background:\s*var\(--surface-2\)/);
|
||||
});
|
||||
|
||||
test("and nothing is left above it for the list to scroll out through", () => {
|
||||
assert.match(ruleFor("#rulingsSheet"), /padding:\s*0 var\(--sp-4\)/,
|
||||
"the sheet keeps a top padding, which is a gap above a sticky header");
|
||||
// The grip was the one thing up there. It belongs inside the header now.
|
||||
const header = sheet().match(/<div class="rulingsHeader">[\s\S]*?<\/div>\s*<\/div>/);
|
||||
assert.ok(header, "the header is not in the markup where it was");
|
||||
assert.match(header[0], /seatSheetGrip/,
|
||||
"the grip is still outside the header, so it scrolls away above it");
|
||||
assert.ok(ruleFor(".rulingsHeader .seatSheetGrip"),
|
||||
"the grip has no rule placing it inside a two-column header");
|
||||
});
|
||||
|
||||
test("the scroll inside the sheet stays inside it", () => {
|
||||
assert.match(ruleFor("#rulingsSheet"), /overscroll-behavior:\s*contain/);
|
||||
});
|
||||
});
|
||||
|
||||
// A phone held sideways is 844x390: no height and swimming in width. Measured before
|
||||
// this, the bottom sheet was 460px wide in the middle of that window — 384px of it
|
||||
// unused either side — holding 754px of rulings in a 334px scrollport, 111px of which
|
||||
// went on a title and a caveat that stand still. Four of the twelve names on screen.
|
||||
//
|
||||
// Material's rule for a bottom sheet in landscape is to stop being one, and this tool
|
||||
// already does exactly that under this query for the action bar. So the sheet takes the
|
||||
// window and the width pays for the height: two columns of six, all twelve names on
|
||||
// screen at 844x390 with nothing to scroll at all.
|
||||
describe("a phone on its side gets the whole window", () => {
|
||||
test("the twelve are six and six in the markup", () => {
|
||||
const columns = sheet().split('<div class="rulingsColumn">').slice(1);
|
||||
assert.equal(columns.length, 2, "there are not exactly two columns to stand side by side");
|
||||
columns.forEach((c, i) => {
|
||||
const count = (c.slice(0, c.indexOf("\n </div>")).match(/<details class="ruling"/g) || []).length;
|
||||
assert.equal(count, 6, `column ${i + 1} holds ${count} rulings rather than six`);
|
||||
});
|
||||
});
|
||||
|
||||
test("but everywhere else they are still one column", () => {
|
||||
// `display: contents` hands the rulings straight to the list, so every layout
|
||||
// that isn't landscape sees exactly the markup it saw before the wrappers.
|
||||
assert.match(ruleFor(".rulingsColumn"), /display:\s*contents/);
|
||||
assert.match(ruleIn(SIDEWAYS(), ".rulingsColumn"), /display:\s*block/);
|
||||
});
|
||||
|
||||
test("sideways the sheet stops being a sheet and becomes the screen", () => {
|
||||
const block = SIDEWAYS();
|
||||
assert.match(ruleIn(block, "#rulingsOverlay"), /align-items:\s*stretch/,
|
||||
"the sheet is still anchored to the bottom edge, so it is still short");
|
||||
const sheetRule = ruleIn(block, "#rulingsSheet");
|
||||
assert.match(sheetRule, /height:\s*100%/);
|
||||
assert.match(sheetRule, /max-height:\s*none/);
|
||||
// Capped rather than unbounded: this query also matches a short desktop window,
|
||||
// and two columns of a 1400px one would be 680px lines of body text.
|
||||
assert.match(sheetRule, /max-width:\s*62rem/);
|
||||
assert.match(sheetRule, /overflow:\s*hidden/,
|
||||
"the sheet scrolls as well as the list, which is two scrollbars for one list");
|
||||
});
|
||||
|
||||
test("the title, the caveat and the way out share one line", () => {
|
||||
// That line is the whole of the 111px this screen was spending on them, and the
|
||||
// sticky positioning goes with the box being dropped: nothing above the list
|
||||
// scrolls here, so nothing has to stick.
|
||||
const block = SIDEWAYS();
|
||||
assert.match(ruleIn(block, ".rulingsHeader"), /display:\s*contents/);
|
||||
assert.match(ruleIn(block, ".rulingsHeader .seatSheetGrip"), /display:\s*none/,
|
||||
"a grab handle on a sheet that is not a sheet any more");
|
||||
assert.match(ruleIn(block, "#rulingsSheet"), /grid-template-areas:\s*\n?\s*"title intro done"/);
|
||||
});
|
||||
|
||||
test("the two columns are two flows, not one flow wrapped", () => {
|
||||
// A grid with twelve children of its own pairs them into six shared rows, and
|
||||
// opening the third ruling on the left pushes the ninth down the right-hand side
|
||||
// to keep the row square. Two wrappers means a column only ever moves itself.
|
||||
const rule = ruleIn(SIDEWAYS(), ".rulingsList");
|
||||
assert.match(rule, /grid-template-columns:\s*1fr 1fr/);
|
||||
assert.match(rule, /overflow-y:\s*auto/);
|
||||
assert.match(rule, /min-height:\s*0/,
|
||||
"a grid item's floor is its content, so the list would push the sheet open");
|
||||
});
|
||||
|
||||
test("and the gutter is reserved before it is needed", () => {
|
||||
// It arrives the moment a ruling is opened. Without the reservation it takes 8px
|
||||
// off the tracks and slides the right-hand column sideways under the eye that is
|
||||
// reading it.
|
||||
assert.match(ruleIn(SIDEWAYS(), ".rulingsList"), /scrollbar-gutter:\s*stable/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -957,7 +957,14 @@ const LayoutAudit = {
|
||||
// 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");
|
||||
// `.rulingsList` before `#rulingsSheet`, and `closest` takes the nearer
|
||||
// of the two: on a phone held sideways the sheet fills the window and
|
||||
// stops scrolling, and the twelve rulings scroll inside the list instead.
|
||||
// Naming only the sheet there finds a box whose scrollHeight equals its
|
||||
// clientHeight, decides nothing is out of view, and puts the check back
|
||||
// in the silence this guard exists to break.
|
||||
const scroller = el.closest("[style*='overflow'], .modalBody, #potWizardBody, " +
|
||||
".rulingsList, #rulingsSheet, #seatEditorPanel");
|
||||
if (scroller && scroller.scrollHeight > scroller.clientHeight) {
|
||||
const box = scroller.getBoundingClientRect();
|
||||
const own = el.getBoundingClientRect();
|
||||
|
||||
Reference in New Issue
Block a user