fix(player): the click layer clears only the fullscreen button's column, not a full-width band
Measured: across the full width YouTube needs only its seek bar (up to 96 px from the bottom); the 140 px is the fullscreen button's, at the right end. A full-width 140 px band handed clicks on the picture to the iframe, which then takes the keyboard focus and our shortcuts with it. The layer is now two boxes, hidden from assistive tech (a mouse surface; the keys are its shortcuts), and the tests render the big view too, where the docked bar sits under the iframe.
This commit is contained in:
@@ -444,30 +444,69 @@ describe("PlayerModal — the side arrows", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// YouTube's embed keeps its fullscreen button at a fixed 80–128 px above the iframe's bottom, its gear
|
||||
// and volume along the top. A cutout given only as a share of the height lay over that button on a
|
||||
// short player (a small window, a browser zoomed to 150 %): the click paused instead.
|
||||
// YouTube's embed keeps its controls at fixed px from the iframe's edges: the seek bar across the bottom
|
||||
// 52–96 px up, the fullscreen button above its right end 80–128 px up, the gear and volume along the
|
||||
// top. A cutout given only as a share of the height lay over that button on a short player (a small
|
||||
// window, a browser zoomed to 150 %): the click paused instead.
|
||||
describe("PlayerModal — the click layer over the video", () => {
|
||||
const layer = () => {
|
||||
const layers = () => {
|
||||
vi.spyOn(api, "videoDetail").mockReturnValue(new Promise(() => {}));
|
||||
const A = vid("A", 0);
|
||||
renderModal(
|
||||
<PlayerModal video={A} queue={[A]} startIndex={0} onClose={() => {}} onState={() => {}} />,
|
||||
);
|
||||
return screen.getByTestId("yt-click-layer");
|
||||
return screen.getAllByTestId("yt-click-layer");
|
||||
};
|
||||
const box = (el: HTMLElement) => {
|
||||
const { left, right, width, top, bottom } = el.style;
|
||||
return { left, right, width, top, bottom };
|
||||
};
|
||||
|
||||
it("leaves YouTube's own controls a px floor at the top and the bottom, whatever the height", () => {
|
||||
const el = layer();
|
||||
expect(el.style.top).toBe("max(12%, 56px)");
|
||||
expect(el.style.bottom).toBe("max(22%, 140px)");
|
||||
it("clears the seek bar across the width and the fullscreen button in its right-hand column", () => {
|
||||
const [picture, column] = layers();
|
||||
expect(box(picture!)).toEqual({
|
||||
left: "0px",
|
||||
right: "88px",
|
||||
width: "",
|
||||
top: "max(12%, 56px)",
|
||||
bottom: "max(22%, 104px)",
|
||||
});
|
||||
expect(box(column!)).toEqual({
|
||||
left: "",
|
||||
right: "0px",
|
||||
width: "88px",
|
||||
top: "max(12%, 56px)",
|
||||
bottom: "max(22%, 140px)",
|
||||
});
|
||||
});
|
||||
|
||||
it("measures those edges against the iframe's box, not the stage under our docked bar", () => {
|
||||
const el = layer();
|
||||
expect(el.parentElement!.classList.contains("player-stage")).toBe(false);
|
||||
expect(el.parentElement!.closest(".player-stage")).toBeTruthy();
|
||||
expect(el.parentElement!.className).toMatch(/(^|\s)(relative|absolute)(\s|$)/);
|
||||
// In the big views our docked bar sits under the iframe; YouTube's px are counted from the iframe's
|
||||
// bottom, so a box measured from the stage's would start that bar's height too low again.
|
||||
it.each([
|
||||
["the windowed card", false, /(^|\s)relative(\s|$)/],
|
||||
["true fullscreen", true, /(^|\s)bottom-14(\s|$)/],
|
||||
])("measures those edges against the iframe's own box in %s", (_view, fullscreen, inset) => {
|
||||
const els = layers();
|
||||
const stage = document.querySelector(".player-stage")!;
|
||||
if (fullscreen) {
|
||||
Object.defineProperty(document, "fullscreenElement", {
|
||||
configurable: true,
|
||||
get: () => stage,
|
||||
});
|
||||
act(() => void document.dispatchEvent(new Event("fullscreenchange")));
|
||||
}
|
||||
try {
|
||||
for (const el of screen.getAllByTestId("yt-click-layer")) {
|
||||
const wrapper = el.parentElement!;
|
||||
expect(wrapper).not.toBe(stage);
|
||||
expect(wrapper.parentElement).toBe(stage);
|
||||
expect(wrapper.className).toMatch(inset);
|
||||
expect(wrapper.className).not.toMatch(/(^|\s)p[byt]?-/);
|
||||
}
|
||||
expect(els).toHaveLength(2);
|
||||
} finally {
|
||||
delete (document as { fullscreenElement?: unknown }).fullscreenElement;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user