73 lines
2.5 KiB
JavaScript
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);
|