fix(ui): calculator polish — smart input steps, unified tech/MAT lock idiom, tech floor, speed-lock ceiling fix
- pkg/calc: DriveForSpeed treats restMass==0 as a valid ceiling-only case (every positive drive solves it), so locking the displayed speed of a D=1, W=A=S=C=0 ship is no longer a phantom "infeasible". - ship-design-area: drive/weapons/shields/cargo inputs use a JS-driven smart step on ArrowUp/ArrowDown (0↔1 jump, otherwise ±0.1) and hide the native spinner so it cannot produce invalid (0, 1) values; armament keeps its native step 1. - Tech and planet MAT cells follow the same lock idiom as goal-seek locks: open padlock (🔓) over the inherited value → click to open an input with a closed padlock (🔒). The padlock slot is always reserved, so the column width is stable. - Tech overrides (design area and modernization target) are floored at the player's current tech on this turn — a lower value is flagged as invalid.
This commit is contained in:
@@ -14,7 +14,7 @@ switch (the inspector auto-opens on a planet click) — the calculator is a
|
||||
long-lived planning tool. `ensureGame` resets it when the game changes.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { getContext } from "svelte";
|
||||
import { getContext, tick } from "svelte";
|
||||
import { appScreen } from "$lib/app-nav.svelte";
|
||||
|
||||
import { i18n, type TranslationKey } from "$lib/i18n/index.svelte";
|
||||
@@ -273,6 +273,18 @@ long-lived planning tool. `ensureGame` resets it when the game changes.
|
||||
cs.matValue < 0 ? i18n.t("game.calculator.invalid.negative") : "",
|
||||
);
|
||||
|
||||
// Modernization target tech mirrors the design-area floor: a target
|
||||
// below the player's current tech on this turn is meaningless (no
|
||||
// upgrade), so flag it the same way.
|
||||
function targetTechError(key: TechKey): string {
|
||||
const value = cs.targetTech[key];
|
||||
if (value < 0) return i18n.t("game.calculator.invalid.negative");
|
||||
if (value < playerTech[key]) {
|
||||
return i18n.t("game.calculator.invalid.tech_below_current");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// Locking a speed back-solves the drive block; with a zero drive the
|
||||
// ship is deliberately immobile, so disallow it.
|
||||
function lockDisabledReason(output: LockableOutputId): string {
|
||||
@@ -291,8 +303,12 @@ long-lived planning tool. `ensureGame` resets it when the game changes.
|
||||
function onResetTech(key: TechKey): void {
|
||||
cs.techOverridden[key] = false;
|
||||
}
|
||||
function onMatInput(): void {
|
||||
const matInputRef: { el?: HTMLInputElement } = {};
|
||||
async function activateMatOverride(): Promise<void> {
|
||||
cs.matOverridden = true;
|
||||
await tick();
|
||||
matInputRef.el?.focus();
|
||||
matInputRef.el?.select();
|
||||
}
|
||||
function resetMat(): void {
|
||||
cs.matOverridden = false;
|
||||
@@ -485,6 +501,7 @@ long-lived planning tool. `ensureGame` resets it when the game changes.
|
||||
resolved={resolvedCeil}
|
||||
bind:techs={cs.techValues}
|
||||
techOverridden={cs.techOverridden}
|
||||
techFloor={playerTech}
|
||||
computedInput={result.computedInput}
|
||||
{onTechInput}
|
||||
{onResetTech}
|
||||
@@ -589,17 +606,17 @@ long-lived planning tool. `ensureGame` resets it when the game changes.
|
||||
<div class="rrow">
|
||||
<span class="label">{i18n.t("game.calculator.planet.mat")}</span>
|
||||
<span class="cell">
|
||||
<input
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0"
|
||||
bind:value={cs.matValue}
|
||||
oninput={onMatInput}
|
||||
aria-invalid={matError !== "" ? "true" : "false"}
|
||||
title={matError}
|
||||
data-testid="calculator-planet-mat"
|
||||
/>
|
||||
{#if cs.matOverridden}
|
||||
<input
|
||||
bind:this={matInputRef.el}
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0"
|
||||
bind:value={cs.matValue}
|
||||
aria-invalid={matError !== "" ? "true" : "false"}
|
||||
title={matError}
|
||||
data-testid="calculator-planet-mat"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="lock active"
|
||||
@@ -610,6 +627,23 @@ long-lived planning tool. `ensureGame` resets it when the game changes.
|
||||
>
|
||||
🔒
|
||||
</button>
|
||||
{:else}
|
||||
<span
|
||||
class="mat-val"
|
||||
data-testid="calculator-planet-mat-value"
|
||||
>
|
||||
{cs.matValue}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="lock"
|
||||
title={i18n.t("game.calculator.mat.override")}
|
||||
aria-label={i18n.t("game.calculator.mat.override")}
|
||||
data-testid="calculator-mat-override"
|
||||
onclick={() => void activateMatOverride()}
|
||||
>
|
||||
🔓
|
||||
</button>
|
||||
{/if}
|
||||
</span>
|
||||
<span></span>
|
||||
@@ -638,18 +672,17 @@ long-lived planning tool. `ensureGame` resets it when the game changes.
|
||||
<span class="col-head">{i18n.t("game.calculator.modern.cost")}</span>
|
||||
</div>
|
||||
{#each modernCosts?.perBlock ?? [] as row (row.key)}
|
||||
{@const targetError = targetTechError(row.key)}
|
||||
<div class="rrow">
|
||||
<span class="label">{i18n.t(`game.calculator.field.${row.key}` as TranslationKey)}</span>
|
||||
<span class="cell">
|
||||
<input
|
||||
type="number"
|
||||
step="0.001"
|
||||
min="0"
|
||||
min={playerTech[row.key]}
|
||||
bind:value={cs.targetTech[row.key]}
|
||||
aria-invalid={cs.targetTech[row.key] < 0 ? "true" : "false"}
|
||||
title={cs.targetTech[row.key] < 0
|
||||
? i18n.t("game.calculator.invalid.negative")
|
||||
: ""}
|
||||
aria-invalid={targetError !== "" ? "true" : "false"}
|
||||
title={targetError}
|
||||
data-testid={`calculator-target-${row.key}`}
|
||||
/>
|
||||
</span>
|
||||
@@ -899,4 +932,15 @@ long-lived planning tool. `ensureGame` resets it when the game changes.
|
||||
font-size: 0.8rem;
|
||||
color: var(--color-accent);
|
||||
}
|
||||
/* Plain-text view of the planet MAT (mirrors `.tech-val` in the
|
||||
design area) so the cell width stays the same whether the value is
|
||||
the inherited planet number or the player's override. */
|
||||
.mat-val {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 0.85rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
text-align: right;
|
||||
padding: 0.15rem 0.3rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user