Keep the keyboard open: no redraws under focused fields

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-23 23:02:33 +01:00
parent 71dd99795a
commit 7150b2f89c

View file

@ -205,20 +205,41 @@ function renderMain() {
}
const el = $("#page");
if (!S.me) {
el.innerHTML = registerPage();
// Only redraw when the form itself changes: replacing the name field under
// a phone's keyboard closes the keyboard.
const html = registerPage();
if (el.dataset.html !== html) {
el.innerHTML = html;
el.dataset.html = html;
$("#name").value = remember("draftName") || "";
}
return;
}
if (page === "chat") {
seenChat = Math.max(seenChat, ...S.chat.map((m) => m.t), 0);
remember("seenChat", seenChat);
}
// Never redraw under a focused field (the chat box aside): on a phone that
// closes the keyboard or the open dropdown. The next render catches up.
const focused = document.activeElement;
const typing = focused && el.contains(focused) && focused.matches("input, select, textarea");
if (page === "chat" && el.querySelector("#say")) {
// The chat box stays put (and keeps the keyboard); only the lists refresh.
const holder = document.createElement("div");
holder.innerHTML = chatPage();
for (const id of ["chatlog", "worldlog"]) {
const fresh = holder.querySelector("#" + id).innerHTML;
const old = el.querySelector("#" + id);
if (old.innerHTML !== fresh) old.innerHTML = fresh;
}
return;
}
if (typing) return;
const html = { adventure: adventurePage, quests: questsPage, character: characterPage, group: groupPage, chat: chatPage }[page]();
// Keep a half-typed chat message across re-renders.
const typing = document.activeElement?.id === "say" ? document.activeElement.value : null;
if (typing !== null && page === "chat") return;
if (el.dataset.html !== html) {
el.innerHTML = html;
el.dataset.html = html;
animate();
}
}
@ -230,7 +251,7 @@ function registerPage() {
return `<div class="card">
<h2>Create your character</h2>
<p class="muted small">You wake in Northshire Abbey. The Defias Brotherhood stirs in Westfall.</p>
<div class="row"><input id="name" placeholder="Name" maxlength="16" class="grow" value="${esc(remember("draftName") || "")}"></div>
<div class="row"><input id="name" placeholder="Name" maxlength="16" class="grow" autocomplete="off" autocapitalize="words"></div>
<h3>Class</h3>
<div class="classes">${Object.entries(C.classes).map(([id, c]) => `
<div class="cls ${regCls === id ? "on" : ""}" data-cls="${id}">
@ -284,14 +305,13 @@ function statusCard() {
const act = me.activity;
const t = now();
if (me.dead_until > t) {
return `<div class="card"><b>💀 You died.</b> <span class="muted">Running back · ${clock(me.dead_until - t)}</span></div>`;
return `<div class="card"><b>💀 You died.</b> <span class="muted">Running back · <span data-until="${me.dead_until}"></span></span></div>`;
}
if (!act) return "";
if (act.kind === "travel") {
const done = (t - act.start) / (act.end - act.start);
return `<div class="card">
<div class="row"><b>🐎 On the road to ${esc(place(act.to).name)}</b><span class="right muted small" data-until="${act.end}">${clock(act.end - t)}</span></div>
<div class="bar road" style="margin-top:6px" data-from="${act.start}" data-to="${act.end}"><i style="width:${done * 100}%"></i><span></span></div>
<div class="row"><b>🐎 On the road to ${esc(place(act.to).name)}</b><span class="right muted small" data-until="${act.end}"></span></div>
<div class="bar road" style="margin-top:6px" data-from="${act.start}" data-to="${act.end}"><i></i><span></span></div>
<div class="small muted" style="margin-top:4px">${act.path.map((p) => esc(place(p).name)).join(" → ")}</div>
</div>`;
}
@ -300,7 +320,7 @@ function statusCard() {
const waiting = !S.fight && me.next_pull > t;
return `<div class="card"><div class="row">
<b>🎯 Hunting ${esc(mob.name)}</b>
<span class="muted small">${waiting ? `resting · ${clock(me.next_pull - t)}` : ""}</span>
<span class="muted small">${waiting ? `resting · <span data-until="${me.next_pull}"></span>` : ""}</span>
<button class="btn small right" data-act="stop">Stop</button></div>
</div>`;
}
@ -322,7 +342,7 @@ function dungeonCard() {
}).join("");
let status = "";
if (d.state === "rest") {
status = `<div class="row"><span>${d.last && !d.last.won ? "💀 Wiped. " : ""}Next pull in <b data-until="${d.next}">${clock(d.next - t)}</b></span>
status = `<div class="row"><span>${d.last && !d.last.won ? "💀 Wiped. " : ""}Next pull in <b data-until="${d.next}"></b></span>
${leader ? `<button class="btn primary small right" data-act="pull">Pull now</button>` : `<span class="right small muted">the leader can pull early</span>`}</div>`;
} else if (d.state === "done") {
status = `<div><b>🏆 ${esc(spec.name)} cleared!</b></div>`;
@ -603,8 +623,8 @@ function chatPage() {
const events = S.log.slice().reverse().map((l) => `<div class="${l.kind}">${esc(l.text)} <span class="small">· ${ago(l.t)}</span></div>`).join("");
return `<div class="card"><h2>Guild chat</h2>
<div class="row"><input id="say" class="grow" placeholder="Say something" maxlength="300"><button class="btn primary small" data-act="say">Send</button></div>
<div class="chat" style="margin-top:8px">${msgs || '<span class="muted">Quiet in here.</span>'}</div></div>
<div class="card"><h2>World</h2><div class="log" style="max-height:none; flex-direction:column">${events}</div></div>`;
<div class="chat" id="chatlog" style="margin-top:8px">${msgs || '<span class="muted">Quiet in here.</span>'}</div></div>
<div class="card"><h2>World</h2><div class="log" id="worldlog" style="max-height:none; flex-direction:column">${events}</div></div>`;
}
// --- the fight ----------------------------------------------------------
@ -830,6 +850,7 @@ document.addEventListener("click", async (ev) => {
if (cls) {
remember("draftName", $("#name")?.value || "");
regCls = cls.dataset.cls;
document.activeElement?.blur();
render();
return;
}
@ -881,6 +902,10 @@ document.addEventListener("keydown", (ev) => {
}
});
document.addEventListener("input", (ev) => {
if (ev.target.id === "name") remember("draftName", ev.target.value);
});
document.addEventListener("change", (ev) => {
if (ev.target.id === "dev-user") {
document.cookie = `wog_user=${encodeURIComponent(ev.target.value)}; path=/; max-age=31536000`;