diff --git a/web/src/app/pattern-lab/LabPanels.module.css b/web/src/app/pattern-lab/LabPanels.module.css index f1ceff358..21d6bccb1 100644 --- a/web/src/app/pattern-lab/LabPanels.module.css +++ b/web/src/app/pattern-lab/LabPanels.module.css @@ -499,6 +499,14 @@ .layerControlRow select { min-height: 44px; } + + /* A finger has no Alt key, right button or middle button. On a 390×844 + phone this line wrapped the Pixel status bar to 51px, which left the + picture a 372×151 viewport and fitted it at 1×. Without it the viewport + is 372×177, and 2× needs 168. */ + .pixelMouseHint { + display: none; + } } /* ── Pixel editor panel ── */ diff --git a/web/src/app/pattern-lab/labCss.test.ts b/web/src/app/pattern-lab/labCss.test.ts index de61262e5..9accfc178 100644 --- a/web/src/app/pattern-lab/labCss.test.ts +++ b/web/src/app/pattern-lab/labCss.test.ts @@ -6,7 +6,8 @@ // and 44×44px after, in a header 59px tall both times. On a 390×844 touch // screen the tab strip under Preview was 736px for 333px of room, and every // tab carried a 27×27px ×. Without the × it is 520px and Knobs shows whole. -// On the same screen Del was 37×28px, the layer eye 20×17px and Push 40×22px. +// On the same screen Del was 37×28px, the layer eye 20×17px and Push 40×22px, +// and the Pixel status bar was 51px, two lines with the mouse gestures. import { readFileSync } from "node:fs"; import { createRequire } from "node:module"; @@ -168,3 +169,14 @@ describe("Layers and Knobs on a touch screen", () => { expect(knobs.lastIndexOf("@media (hover: none) {")).toBeGreaterThan(knobs.indexOf("@media (max-width: 980px) {")); }); }); + +describe("Pixel status line on a touch screen", () => { + const dock = css("LabPanels.module.css"); + const pixel = css("panels/PixelPanel.tsx"); + + it("drops the mouse gestures, and nothing else", () => { + expect(mediaRule(dock, "(hover: none)", ".pixelMouseHint")).toMatch(/display: none;/); + expect(pixel).toContain("alt-click picks · right-drag erases · middle-drag pans"); + expect(mediaRule(dock, "(hover: none)", ".pixelStatus")).toBe(""); + }); +}); diff --git a/web/src/app/pattern-lab/panels/PixelPanel.test.tsx b/web/src/app/pattern-lab/panels/PixelPanel.test.tsx index e6514806a..dedbb95f3 100644 --- a/web/src/app/pattern-lab/panels/PixelPanel.test.tsx +++ b/web/src/app/pattern-lab/panels/PixelPanel.test.tsx @@ -5,6 +5,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { act, createEvent, fireEvent, render, screen } from "@testing-library/react"; import PixelPanel from "./PixelPanel"; +import { zoomToFit } from "./pixel/usePixelViewport"; import { useLabStore } from "@/lib/lab/store"; import type { PixelLayer } from "@/lib/lab/types"; @@ -285,13 +286,20 @@ describe("PixelPanel", () => { }; it("on a phone the picture opens fitted, all of it in view", () => { - // the Pixel viewport on a 390×844 phone, measured in Chromium - panelSize(372, 151); + // the Pixel viewport on a 390×844 touch phone, measured in Chromium + panelSize(372, 177); mount(); - expect(screen.getByText("zoom 1×")).toBeTruthy(); + expect(screen.getByText("zoom 2×")).toBeTruthy(); // and the zoom stays the user's to change fireEvent.click(screen.getByLabelText("Zoom in")); - expect(screen.getByText("zoom 2×")).toBeTruthy(); + expect(screen.getByText("zoom 3×")).toBeTruthy(); + }); + + it("the mouse gestures sit apart in the status line, where a touch screen can drop them", () => { + mount(); + const hint = screen.getByText("alt-click picks · right-drag erases · middle-drag pans"); + expect(hint.className).toBe("pixelMouseHint"); + expect(hint.parentElement?.textContent).toContain("zoom 6×"); }); it("a desktop panel narrower than the picture at 6× opens fitted too", () => { @@ -440,3 +448,13 @@ describe("PixelPanel", () => { expect(screen.queryByTitle(/Stamp the selection down/)).toBeNull(); }); }); + +describe("zoomToFit", () => { + it("fits 128×64 at 2× from a 168px-tall viewport, 64 × 2 and 40 of padding", () => { + expect(zoomToFit(372, 167, 128, 64)).toBe(1); + expect(zoomToFit(372, 168, 128, 64)).toBe(2); + // a 390×844 phone, with and without the mouse gestures under the picture + expect(zoomToFit(372, 151, 128, 64)).toBe(1); + expect(zoomToFit(372, 177, 128, 64)).toBe(2); + }); +}); diff --git a/web/src/app/pattern-lab/panels/PixelPanel.tsx b/web/src/app/pattern-lab/panels/PixelPanel.tsx index ec6f8708b..9a2430d29 100644 --- a/web/src/app/pattern-lab/panels/PixelPanel.tsx +++ b/web/src/app/pattern-lab/panels/PixelPanel.tsx @@ -222,7 +222,7 @@ export default function PixelPanel() { )} - alt-click picks · right-drag erases · middle-drag pans + alt-click picks · right-drag erases · middle-drag pans {importOpen && ( diff --git a/web/src/app/pattern-lab/panels/pixel/usePixelViewport.ts b/web/src/app/pattern-lab/panels/pixel/usePixelViewport.ts index f3d882e44..9c9bacff3 100644 --- a/web/src/app/pattern-lab/panels/pixel/usePixelViewport.ts +++ b/web/src/app/pattern-lab/panels/pixel/usePixelViewport.ts @@ -15,7 +15,7 @@ export const ZOOM_MAX = 32; /** * The largest whole zoom that shows the picture entire inside a viewport of * this size: 16 px of padding a side and 4 px of air. Never below 1×. For - * 128×64 that is 1× in a 390×844 phone's 372×151 viewport, and 4× in the + * 128×64 that is 2× in a 390×844 phone's 372×177 viewport, and 4× in the * 649×648 one at 1440×900. */ export function zoomToFit(viewportWidth: number, viewportHeight: number, width: number, height: number) {