feat(ui-calculator): input validation, load caps, ceil display, modernization layout
- custom load capped at cargo capacity (error when exceeded); full load shows the cargo capacity; zero cargo pins load to empty and disables the toggle - per-input red border + tooltip for every invalid value (blocks, techs, load, MAT, modernization target); no value may be negative; locking a speed is disabled when drive is zero - display every computed number (results + goal-seek back-solved input) rounded up to 3 decimals via a shared pkg/calc Ceil3 bridged to wasm; engine keeps its own round-to-nearest util.Fixed* - modernization total upgrade cost spans two columns (single line) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,11 @@ The component is presentational — the parent owns the state and the
|
||||
calculator math — so the ship-group upgrade flow can reuse it later.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { i18n } from "$lib/i18n/index.svelte";
|
||||
import { i18n, type TranslationKey } from "$lib/i18n/index.svelte";
|
||||
import {
|
||||
shipClassFieldErrors,
|
||||
type ShipClassValueInvalidReason,
|
||||
} from "$lib/util/ship-class-validation";
|
||||
import type { ClaimedInput } from "./calc-model";
|
||||
|
||||
export interface DesignBlocksState {
|
||||
@@ -49,6 +53,29 @@ calculator math — so the ship-group upgrade flow can reuse it later.
|
||||
onResetTech,
|
||||
}: Props = $props();
|
||||
|
||||
const VALUE_REASON_KEY: Record<ShipClassValueInvalidReason, TranslationKey> = {
|
||||
drive_value: "game.calculator.invalid.drive_value",
|
||||
armament_value: "game.calculator.invalid.armament_value",
|
||||
armament_not_integer: "game.calculator.invalid.armament_not_integer",
|
||||
weapons_value: "game.calculator.invalid.weapons_value",
|
||||
shields_value: "game.calculator.invalid.shields_value",
|
||||
cargo_value: "game.calculator.invalid.cargo_value",
|
||||
armament_weapons_pair: "game.calculator.invalid.armament_weapons_pair",
|
||||
all_zero: "game.calculator.invalid.all_zero",
|
||||
};
|
||||
|
||||
// Per-block validity (independent of which one failed first) so every
|
||||
// invalid input is highlighted, not only the first.
|
||||
const blockErrors = $derived(shipClassFieldErrors(blocks));
|
||||
|
||||
function blockError(key: keyof DesignBlocksState): string {
|
||||
const reason = blockErrors[key];
|
||||
return reason === undefined ? "" : i18n.t(VALUE_REASON_KEY[reason]);
|
||||
}
|
||||
function techError(key: TechKey): string {
|
||||
return techs[key] < 0 ? i18n.t("game.calculator.invalid.tech_value") : "";
|
||||
}
|
||||
|
||||
const BLOCK_ROWS: {
|
||||
key: keyof DesignBlocksState;
|
||||
label: () => string;
|
||||
@@ -92,6 +119,8 @@ calculator math — so the ship-group upgrade flow can reuse it later.
|
||||
min="0"
|
||||
bind:value={blocks[row.key]}
|
||||
readonly={blocksReadonly}
|
||||
aria-invalid={blockError(row.key) !== "" ? "true" : "false"}
|
||||
title={blockError(row.key)}
|
||||
data-testid={`calculator-block-${row.key}`}
|
||||
/>
|
||||
{/if}
|
||||
@@ -105,6 +134,8 @@ calculator math — so the ship-group upgrade flow can reuse it later.
|
||||
min="0"
|
||||
bind:value={techs[techKey]}
|
||||
oninput={() => onTechInput(techKey)}
|
||||
aria-invalid={techError(techKey) !== "" ? "true" : "false"}
|
||||
title={techError(techKey)}
|
||||
data-testid={`calculator-tech-${techKey}`}
|
||||
/>
|
||||
{#if techOverridden[techKey]}
|
||||
@@ -167,6 +198,9 @@ calculator math — so the ship-group upgrade flow can reuse it later.
|
||||
color: #9fb0ff;
|
||||
background: #11162a;
|
||||
}
|
||||
input[aria-invalid="true"] {
|
||||
border-color: #d97a7a;
|
||||
}
|
||||
.tech-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in New Issue
Block a user