fix: 用 Pointer Events 替代 touch+mouse 事件,修复移动端双触发导致按钮卡死

- pointerdown/pointerup/pointerleave/pointercancel 统一处理所有输入
- 移除分离的 touchstart/touchend/mousedown/mouseup 事件绑定
- WS 断连时清理 pendingStart 状态,防止按钮永久卡死
- 添加 contextmenu 阻止长按弹出菜单
This commit is contained in:
2026-03-01 05:49:07 +08:00
parent 30e3271146
commit ce1ff2d04d

View File

@@ -133,6 +133,12 @@ function connectWS(): void {
state.ws = null; state.ws = null;
micBtn.disabled = true; micBtn.disabled = true;
if (state.recording) stopRecording(); if (state.recording) stopRecording();
// Clean up pending async start on disconnect
if (state.pendingStart) {
state.pendingStart = false;
state.startCancelled = true;
micBtn.classList.remove("recording");
}
setStatus("disconnected", "已断开"); setStatus("disconnected", "已断开");
scheduleReconnect(); scheduleReconnect();
}; };
@@ -357,40 +363,24 @@ function escapeHtml(s: string): string {
} }
// ── Event bindings ── // ── Event bindings ──
function bindMicButton(): void { function bindMicButton(): void {
// Touch events (mobile primary) // Pointer Events: unified touch + mouse, no double-trigger
micBtn.addEventListener( micBtn.addEventListener("pointerdown", (e: PointerEvent) => {
"touchstart",
(e: TouchEvent) => {
e.preventDefault();
startRecording();
},
{ passive: false },
);
micBtn.addEventListener(
"touchend",
(e: TouchEvent) => {
e.preventDefault();
stopRecording();
},
{ passive: false },
);
micBtn.addEventListener(
"touchcancel",
(e: TouchEvent) => {
e.preventDefault();
stopRecording();
},
{ passive: false },
);
// Mouse fallback (desktop testing)
micBtn.addEventListener("mousedown", (e: MouseEvent) => {
if (e.button !== 0) return; if (e.button !== 0) return;
e.preventDefault();
startRecording(); startRecording();
}); });
micBtn.addEventListener("mouseup", () => stopRecording()); micBtn.addEventListener("pointerup", (e: PointerEvent) => {
micBtn.addEventListener("mouseleave", () => { e.preventDefault();
if (state.recording) stopRecording(); stopRecording();
}); });
micBtn.addEventListener("pointerleave", () => {
if (state.recording || state.pendingStart) stopRecording();
});
micBtn.addEventListener("pointercancel", () => {
if (state.recording || state.pendingStart) stopRecording();
});
// Prevent context menu on long press
micBtn.addEventListener("contextmenu", (e) => e.preventDefault());
} }
// ── Init ── // ── Init ──
function init(): void { function init(): void {