- theme.ts: colorScheme → data-tg-theme on <html>; header_bg_color 0-1 luminance fallback (T-03-11 — Telegram hex is a signal, never a consumed CSS value); applyTheme's ONLY statement is the attribute set (Pitfall 7 / D-10, greppable invariant) - backbutton.ts: hide() at init (one shell state), onClick is total — close() at the root, no dead-ends (UI-SPEC §BackButton) - safe-area.ts: env(safe-area-inset-*) probe ∪ WebApp.safeAreaInset, per-edge max, version-gated at 8.0, applied as --safe-* custom properties on :root - webapp.ts + backbutton/safe-area never read node_modules types; @types/telegram-web-app stays dropped (web/package.json untouched)
47 lines
2.0 KiB
TypeScript
47 lines
2.0 KiB
TypeScript
/*
|
||
* theme.ts — PX-02: the Telegram theme signal → data-tg-theme slot switch.
|
||
* ---------------------------------------------------------------------
|
||
* Light/dark is a SLOT SWITCH, not a color import: Telegram's signals
|
||
* (`colorScheme`, or the luminance of `themeParams.header_bg_color` as
|
||
* the old-client fallback) select between two Rams token pairs defined
|
||
* in theme.css (`--shell-bg` / `--shell-on-surface`). Telegram's own hex
|
||
* values are read ONLY as a 0–1 luminance signal (T-03-11) — no
|
||
* Telegram color ever reaches a CSS custom property.
|
||
*
|
||
* Pitfall 7 (D-10): applyTheme's ONLY statement is the data-attribute
|
||
* set. MapLibre is deliberately absent — the map style is never re-set
|
||
* on themeChanged; CSS custom properties re-slot and the map stays
|
||
* Rams-constant. The invariant is greppable (theme.test.mjs asserts no
|
||
* setStyle call exists in this source).
|
||
*/
|
||
import type { TelegramWebAppLike } from "./webapp.ts";
|
||
|
||
type ThemeBranch = "light" | "dark";
|
||
|
||
export function resolveBranch(wa: TelegramWebAppLike): ThemeBranch {
|
||
// Primary signal — present on all clients this phase targets.
|
||
if (wa.colorScheme === "dark" || wa.colorScheme === "light") return wa.colorScheme;
|
||
|
||
// Fallback (UI-SPEC §Theme mapping): luminance of header_bg_color ≥ 0.5 → light.
|
||
const hex = wa.themeParams?.header_bg_color;
|
||
if (typeof hex === "string" && /^#[0-9a-f]{6}$/i.test(hex)) {
|
||
const r = parseInt(hex.slice(1, 3), 16);
|
||
const g = parseInt(hex.slice(3, 5), 16);
|
||
const b = parseInt(hex.slice(5, 7), 16);
|
||
const lum = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255;
|
||
return lum >= 0.5 ? "light" : "dark";
|
||
}
|
||
return "light";
|
||
}
|
||
|
||
export function applyTheme(wa: TelegramWebAppLike): void {
|
||
// The ONLY statement a theme change is allowed to touch. MapLibre is
|
||
// deliberately absent from this function (D-10 / RESEARCH Pitfall 7).
|
||
document.documentElement.dataset.tgTheme = resolveBranch(wa);
|
||
}
|
||
|
||
export function watchTheme(wa: TelegramWebAppLike): void {
|
||
applyTheme(wa);
|
||
wa.onEvent?.("themeChanged", () => applyTheme(wa));
|
||
}
|