Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
117 lines
4.0 KiB
TypeScript
117 lines
4.0 KiB
TypeScript
/* =============================================================================
|
|
* device_context.ts
|
|
* 기기 상태 한 곳(요구 193~195 · PLAN_기기대응 2-1) — `<html>` 에 지금 상태를 늘 적음.
|
|
*
|
|
* - data-size = compact(<600) · medium(<840) · expanded(<1200) · large ← 배치는 이것만 봄
|
|
* - data-pointer = 주 입력 coarse · fine(미디어 `pointer`)
|
|
* - data-input = 마지막 누름의 pointerType(mouse · touch · pen)
|
|
* - data-touch-mode = 사용자 스위치(auto · on · off · 이 기기 브라우저에만 저장)
|
|
* - data-touch = on · off — 누를 자리 크기를 키울지(auto 면 주 입력 coarse 일 때)
|
|
* 바뀔 때만 window 에 `app:context` 이벤트(detail = 지금 상태).
|
|
* 경계 표는 여기 한 벌 — CSS 는 위 표시(data-size)로 가르고, 숫자가 필요하면 --bp-* 변수.
|
|
* 첫 화면 깜빡임 — 화면은 모두 JS 가 그리므로 main.ts 가 맨 먼저 import 하면 그리기 전에 적힘.
|
|
* ========================================================================== */
|
|
|
|
import "./device_context.css";
|
|
|
|
export const SIZE_BREAKS = { medium: 600, expanded: 840, large: 1200 } as const;
|
|
export type SizeClass = "compact" | "medium" | "expanded" | "large";
|
|
export type TouchMode = "auto" | "on" | "off";
|
|
|
|
export interface DeviceContext {
|
|
size: SizeClass;
|
|
pointer: "coarse" | "fine";
|
|
input: string;
|
|
touchMode: TouchMode;
|
|
touch: boolean;
|
|
}
|
|
|
|
const TOUCH_MODE_KEY = "frd_touch_mode";
|
|
|
|
export function sizeClassOf(width: number): SizeClass {
|
|
if (width < SIZE_BREAKS.medium) return "compact";
|
|
if (width < SIZE_BREAKS.expanded) return "medium";
|
|
if (width < SIZE_BREAKS.large) return "expanded";
|
|
return "large";
|
|
}
|
|
|
|
let modeInMemory: TouchMode | null = null; // 저장이 막힌 브라우저용
|
|
|
|
export function readTouchMode(): TouchMode {
|
|
if (modeInMemory) return modeInMemory;
|
|
try {
|
|
const v = localStorage.getItem(TOUCH_MODE_KEY);
|
|
return v === "on" || v === "off" ? v : "auto";
|
|
} catch {
|
|
return "auto";
|
|
}
|
|
}
|
|
|
|
const coarseQuery = window.matchMedia("(pointer: coarse)");
|
|
let current: DeviceContext | null = null;
|
|
let lastInput = "";
|
|
|
|
function read(): DeviceContext {
|
|
const pointer = coarseQuery.matches ? "coarse" : "fine";
|
|
const touchMode = readTouchMode();
|
|
return {
|
|
size: sizeClassOf(window.innerWidth),
|
|
pointer,
|
|
input: lastInput || (pointer === "coarse" ? "touch" : "mouse"),
|
|
touchMode,
|
|
touch: touchMode === "on" || (touchMode === "auto" && pointer === "coarse"),
|
|
};
|
|
}
|
|
|
|
function apply(): void {
|
|
const next = read();
|
|
if (current && JSON.stringify(current) === JSON.stringify(next)) return;
|
|
const html = document.documentElement;
|
|
html.dataset.size = next.size;
|
|
html.dataset.pointer = next.pointer;
|
|
html.dataset.input = next.input;
|
|
html.dataset.touchMode = next.touchMode;
|
|
html.dataset.touch = next.touch ? "on" : "off";
|
|
const first = !current;
|
|
current = next;
|
|
if (!first) window.dispatchEvent(new CustomEvent("app:context", { detail: next }));
|
|
}
|
|
|
|
export function deviceContext(): DeviceContext {
|
|
return current ?? read();
|
|
}
|
|
|
|
/** 터치 모드 스위치(☰ 서랍) — 이 기기 브라우저에만 저장 */
|
|
export function setTouchMode(mode: TouchMode): void {
|
|
modeInMemory = mode;
|
|
try {
|
|
if (mode === "auto") localStorage.removeItem(TOUCH_MODE_KEY);
|
|
else localStorage.setItem(TOUCH_MODE_KEY, mode);
|
|
} catch {
|
|
/* 저장 막힌 브라우저 — 이번 화면만 */
|
|
}
|
|
apply();
|
|
}
|
|
|
|
function init(): void {
|
|
const html = document.documentElement;
|
|
for (const [name, px] of Object.entries(SIZE_BREAKS))
|
|
html.style.setProperty(`--bp-${name}`, `${px}px`);
|
|
apply();
|
|
coarseQuery.addEventListener("change", apply);
|
|
window.addEventListener("resize", apply);
|
|
window.visualViewport?.addEventListener("resize", apply);
|
|
screen.orientation?.addEventListener("change", apply);
|
|
window.addEventListener(
|
|
"pointerdown",
|
|
(e) => {
|
|
if (e.pointerType === lastInput) return;
|
|
lastInput = e.pointerType;
|
|
apply();
|
|
},
|
|
{ capture: true, passive: true },
|
|
);
|
|
}
|
|
|
|
init();
|