From 678112492c84af8e1fbf39cb23d893283927538a Mon Sep 17 00:00:00 2001 From: Evan Date: Wed, 17 Jun 2026 08:15:24 -0700 Subject: [PATCH] Fix per-frame layout jank when focusing a toggle-input-card field (#4314) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem Focusing the number field of a `toggle-input-card` (Game Timer / Gold Multiplier / Starting Gold, in both the single-player and host-lobby modals) cost several ms of layout/paint **every tick** for as long as the field stayed focused. ## Root cause The input was rendered **conditionally** — `${this.checked ? html`……` : nothing}`. Enabling a toggle therefore **freshly inserts** the `` into the DOM, and **focusing a just-inserted input** is what forced the per-frame layout/paint. An input that was already present in the DOM doesn't do this. ## Fix Keep the input **permanently mounted** and toggle a `hidden` class when unchecked, instead of conditionally rendering it. Focusing it is then always focusing an element that was already there. Because both modals share ``, this single change fixes both. Also restores the **autofocus + select** of the field on enable (it had been removed earlier while chasing this bug) — safe now that the input isn't freshly inserted. No other UX change: the toggle behavior, checkmark, styling, and all three cards behave identically. ## Testing Hard-reload, then in both the Solo and Host-lobby modals, enable each of Game Timer / Gold Multiplier / Starting Gold, type a value, and keep the field focused — smooth, no per-frame jank, and the field autofocuses on enable. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 --- src/client/components/ToggleInputCard.ts | 62 +++++++++++++++--------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/src/client/components/ToggleInputCard.ts b/src/client/components/ToggleInputCard.ts index 2cceb5296..b9e10bb03 100644 --- a/src/client/components/ToggleInputCard.ts +++ b/src/client/components/ToggleInputCard.ts @@ -1,4 +1,4 @@ -import { LitElement, html, nothing } from "lit"; +import { LitElement, PropertyValues, html, nothing } from "lit"; import { customElement, property } from "lit/decorators.js"; import { translateText } from "../Utils"; import { CARD_LABEL_CLASS, INPUT_CLASS, cardClass } from "./InputCardStyles"; @@ -28,6 +28,18 @@ export class ToggleInputCard extends LitElement { createRenderRoot() { return this; } + + // Autofocus + select the number input when the card is toggled on. Safe now + // that the input is always mounted (focusing a freshly-inserted one janked). + protected updated(changedProperties: PropertyValues) { + if (!changedProperties.has("checked")) return; + if (changedProperties.get("checked") === false && this.checked) { + const input = this.querySelector("input"); + input?.focus(); + input?.select(); + } + } + private toOptionalNumber( value: number | string | undefined, ): number | undefined { @@ -120,28 +132,32 @@ export class ToggleInputCard extends LitElement { - ${this.checked - ? html` -
- -
- ` - : nothing} + +
+ +
`; }