feat(ui): refine gem-blue acrylic palette and focus borders
Derive the gem-blue palette with OKLCH tonal and complementary-color rules while preserving acrylic opacity, blur and surface hierarchy. Darker focus tokens exposed stacked field rings, fallback outlines and persistent basemap selection rings. Keep fallback focus styles in the base layer and use one themed field boundary or inset card stroke. Preserve keyboard focus, invalid states and native input semantics. Add browser regression coverage for pointer and keyboard focus, all basemap presentations, narrow layouts and rendered contrast. Validation in an isolated checkout: pnpm lint, pnpm typecheck, pnpm test (393 tests), focused browser suites and pnpm build.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { expectThemeColor, readColorAlpha, readThemeColor } from "./support/theme-colors";
|
||||
import { expect, test, type Locator, type Page } from "@playwright/test";
|
||||
import { mockAgentApi } from "./support/mock-agent-api";
|
||||
import {
|
||||
@@ -114,10 +115,10 @@ test("assigned desktop panels avoid native form controls and horizontal overflow
|
||||
const meterUnit = measurePanel.getByRole("button", { name: "m", exact: true });
|
||||
const kilometerUnit = measurePanel.getByRole("button", { name: "km", exact: true });
|
||||
await expect(kilometerUnit).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(kilometerUnit).toHaveClass(/bg-blue-600/);
|
||||
await expectThemeColor(kilometerUnit, "background-color", "--action-blue");
|
||||
await meterUnit.click();
|
||||
await expect(meterUnit).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(meterUnit).toHaveClass(/bg-blue-600/);
|
||||
await expectThemeColor(meterUnit, "background-color", "--action-blue");
|
||||
await kilometerUnit.click();
|
||||
const distanceMode = measurePanel.getByRole("button", { name: "距离", exact: true });
|
||||
const areaMode = measurePanel.getByRole("button", { name: "面积", exact: true });
|
||||
@@ -136,14 +137,14 @@ test("assigned desktop panels avoid native form controls and horizontal overflow
|
||||
await expect(measurePanel.getByRole("button", { name: "清除测量" })).toBeDisabled();
|
||||
await expect(measurePanel.getByText("等待开始", { exact: true })).toBeVisible();
|
||||
const startMeasureButton = measurePanel.getByRole("button", { name: /开始测量/ });
|
||||
await expect(startMeasureButton).toHaveClass(/bg-slate-50\/90/);
|
||||
await expect(startMeasureButton).not.toHaveClass(/bg-blue-600/);
|
||||
await expect.poll(async () => readColorAlpha(await startMeasureButton.evaluate((element) => getComputedStyle(element).backgroundColor))).toBeCloseTo(0.864, 2);
|
||||
await expect(startMeasureButton).not.toHaveCSS("background-color", await readThemeColor(startMeasureButton, "--action-blue"));
|
||||
await expect(startMeasureButton).toContainText("在地图中连续取点");
|
||||
await startMeasureButton.click();
|
||||
const stopMeasureButton = measurePanel.getByRole("button", { name: /停止测量/ });
|
||||
await expect(stopMeasureButton).toBeVisible();
|
||||
await expect(stopMeasureButton).toHaveAttribute("data-state", "active");
|
||||
await expect(stopMeasureButton).toHaveClass(/border-blue-300/);
|
||||
await expectThemeColor(stopMeasureButton, "border-color", "--palette-brand");
|
||||
await expect(stopMeasureButton).toContainText("暂停并保留当前读数");
|
||||
await expect(stopMeasureButton).toContainText("进行中");
|
||||
await expect(measurePanel.getByText("测量中", { exact: true })).toBeVisible();
|
||||
@@ -152,10 +153,11 @@ test("assigned desktop panels avoid native form controls and horizontal overflow
|
||||
fullPage: true
|
||||
});
|
||||
await stopMeasureButton.click();
|
||||
await page.mouse.move(640, 56);
|
||||
const resumedStartMeasureButton = measurePanel.getByRole("button", { name: /开始测量/ });
|
||||
await expect(resumedStartMeasureButton).toBeEnabled();
|
||||
await expect(resumedStartMeasureButton).toHaveClass(/bg-slate-50\/90/);
|
||||
await expect(resumedStartMeasureButton).not.toHaveClass(/bg-blue-600/);
|
||||
await expect.poll(async () => readColorAlpha(await resumedStartMeasureButton.evaluate((element) => getComputedStyle(element).backgroundColor))).toBeCloseTo(0.864, 2);
|
||||
await expect(resumedStartMeasureButton).not.toHaveCSS("background-color", await readThemeColor(resumedStartMeasureButton, "--action-blue"));
|
||||
await page.screenshot({
|
||||
path: "test-results/non-map-panels/1280-measure-panel.png",
|
||||
fullPage: true
|
||||
@@ -336,6 +338,23 @@ async function expectContained(locator: Locator, viewportWidth: number) {
|
||||
}
|
||||
|
||||
async function prepareWorkbench(page: Page) {
|
||||
// Layout fixtures keep the event stream open, just like the live backend.
|
||||
await page.addInitScript(() => {
|
||||
const fetch = window.fetch.bind(window);
|
||||
window.fetch = (input, init) => {
|
||||
const url = input instanceof Request ? input.url : String(input);
|
||||
if (url.includes("/api/v1/workbench/events")) {
|
||||
const body = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(new TextEncoder().encode(": fixture keepalive\n\n"));
|
||||
init?.signal?.addEventListener("abort", () => controller.close(), { once: true });
|
||||
}
|
||||
});
|
||||
return Promise.resolve(new Response(body, { headers: { "Content-Type": "text/event-stream" } }));
|
||||
}
|
||||
return fetch(input, init);
|
||||
};
|
||||
});
|
||||
await mockAgentApi(page);
|
||||
await page.route("**/runtime-config.js", (route) =>
|
||||
route.fulfill({
|
||||
|
||||
Reference in New Issue
Block a user