tarinai/js/input_mode_manager.js
2026-06-30 10:57:06 +09:00

73 lines
2.5 KiB
JavaScript

"use strict";
(function (global) {
const MODES = Object.freeze(["auto", "camera", "tool"]);
const STORAGE_KEY = "tarinai_mobile_input_mode_v1";
class InputModeManager {
constructor() {
this.currentMode = this.loadMode();
this.classification = this.classify();
}
loadMode() {
try {
const mode = global.localStorage?.getItem(STORAGE_KEY) || "auto";
return MODES.includes(mode) ? mode : "auto";
} catch (_) {
return "auto";
}
}
setMode(mode = "auto") {
this.currentMode = MODES.includes(mode) ? mode : "auto";
try { global.localStorage?.setItem(STORAGE_KEY, this.currentMode); } catch (_) {}
return this.currentMode;
}
classify() {
const nav = global.navigator || {};
const ua = String(nav.userAgent || "");
const mm = typeof global.matchMedia === "function" ? global.matchMedia.bind(global) : null;
const width = Number(global.innerWidth || 9999);
const height = Number(global.innerHeight || 9999);
const shortSide = Math.min(width, height);
const longSide = Math.max(width, height);
const pointerCoarse = Boolean(mm?.("(pointer: coarse)")?.matches);
const hasHover = Boolean(mm?.("(hover: hover)")?.matches);
const noHover = Boolean(mm?.("(hover: none)")?.matches);
const isTouchDevice = Number(nav.maxTouchPoints || 0) > 0 || "ontouchstart" in global;
const isCompactViewport = shortSide <= 760 && longSide <= 1200;
const userAgentMobile = /Android|iPhone|iPod|iPad|Mobile|Silk|Tablet/i.test(ua);
// v39.15.35: Some mobile browsers report hover support or a desktop-like
// viewport. Treat compact touch devices as touch-first so the on-canvas
// mobile mode UI is reliably available.
const touchFirst = Boolean(isTouchDevice && (isCompactViewport || userAgentMobile) && (pointerCoarse || noHover || userAgentMobile || shortSide <= 820));
this.classification = Object.freeze({
isTouchDevice,
isCompactViewport,
hasHover,
pointerCoarse,
userAgentMobile,
touchFirst,
width,
height,
});
return this.classification;
}
refresh() {
const next = this.classify();
return next;
}
snapshot() {
return Object.freeze({
...this.classification,
currentMode: this.currentMode,
});
}
}
global.TarinaiInputMode = global.TarinaiInputMode || new InputModeManager();
})(typeof window !== "undefined" ? window : globalThis);