// Toasts and confirmation dialogs, so the table never has to fall back on the // browser's own alert boxes. A system confirm() steals focus, looks nothing like // the rest of the page, and on a phone it's a full-screen interruption — all of // which is a bad trade for a dealer trying to keep a game moving. // A short-lived message at the bottom of the screen, optionally with one action // (in practice: Undo). Only one is ever on screen; a second replaces the first. const Toast = { DEFAULT_DURATION_MS: 8000, timer: null, show(message, options = {}) { const host = document.getElementById("toastHost"); if (!host) return; this.dismiss(); const toast = document.createElement("div"); toast.className = "toast"; toast.setAttribute("role", "status"); const text = document.createElement("span"); text.className = "toastText"; text.textContent = message; toast.appendChild(text); if (options.actionLabel && options.onAction) { const action = document.createElement("button"); action.type = "button"; action.className = "toastAction"; action.textContent = options.actionLabel; action.addEventListener("click", () => { this.dismiss(); options.onAction(); }); toast.appendChild(action); } const close = document.createElement("button"); close.type = "button"; close.className = "toastClose"; close.setAttribute("aria-label", "Dismiss"); close.textContent = "✕"; close.addEventListener("click", () => this.dismiss()); toast.appendChild(close); host.appendChild(toast); // Next frame, so the entrance transition actually runs. requestAnimationFrame(() => toast.classList.add("toastVisible")); this.timer = setTimeout( () => this.dismiss(), options.duration || this.DEFAULT_DURATION_MS ); }, dismiss() { if (this.timer) { clearTimeout(this.timer); this.timer = null; } const host = document.getElementById("toastHost"); if (host) host.innerHTML = ""; }, }; // Styled stand-in for confirm(). Resolves true if the dealer goes through with it. const Dialog = { confirm({ title, message, confirmLabel = "Confirm", cancelLabel = "Cancel", danger = false }) { return new Promise(resolve => { const overlay = document.getElementById("confirmOverlay"); const modal = overlay.querySelector("#confirmModal"); overlay.querySelector("#confirmTitle").textContent = title; overlay.querySelector("#confirmMessage").textContent = message; const confirmBtn = overlay.querySelector("#confirmAcceptBtn"); const cancelBtn = overlay.querySelector("#confirmCancelBtn"); confirmBtn.textContent = confirmLabel; cancelBtn.textContent = cancelLabel; confirmBtn.classList.toggle("holdemBtnDanger", danger); confirmBtn.classList.toggle("holdemBtnAccent", !danger); const finish = result => { overlay.hidden = true; confirmBtn.removeEventListener("click", onAccept); cancelBtn.removeEventListener("click", onCancel); overlay.removeEventListener("click", onBackdrop); document.removeEventListener("keydown", onKey); resolve(result); }; const onAccept = () => finish(true); const onCancel = () => finish(false); const onBackdrop = e => { if (e.target === overlay) finish(false); }; const onKey = e => { if (e.key === "Escape") finish(false); if (e.key === "Enter") finish(true); }; confirmBtn.addEventListener("click", onAccept); cancelBtn.addEventListener("click", onCancel); overlay.addEventListener("click", onBackdrop); document.addEventListener("keydown", onKey); overlay.hidden = false; modal.focus(); confirmBtn.focus(); }); }, }; if (typeof module !== "undefined" && module.exports) { module.exports = { Toast, Dialog }; }