#11: Fix ID input. #12

Merged
manuel merged 1 commit from feature/11-fix-id-input into main 2025-01-11 01:12:29 +01:00
3 changed files with 67 additions and 7 deletions

View file

@ -10,6 +10,7 @@ import {
import styles from "./input-form.module.css"; import styles from "./input-form.module.css";
import "./input-form.css"; import "./input-form.css";
import {State} from "@/app/state/state.ts"; import {State} from "@/app/state/state.ts";
import ValidatingInputBigIntField from "@/app/validating-input-bigint-field.tsx";
export default function InputForm({state, dispatch}: { export default function InputForm({state, dispatch}: {
state: State, state: State,
@ -17,7 +18,7 @@ export default function InputForm({state, dispatch}: {
}) { }) {
const [width, setWidth] = useState(10); const [width, setWidth] = useState(10);
const [height, setHeight] = useState(10); const [height, setHeight] = useState(10);
const [id, setId] = useState<number>(); const [id, setId] = useState<bigint>();
const [algorithm, setAlgorithm] = useState('wilson'); const [algorithm, setAlgorithm] = useState('wilson');
const handleSubmit = (e: FormEvent) => { const handleSubmit = (e: FormEvent) => {
@ -54,15 +55,14 @@ export default function InputForm({state, dispatch}: {
value: numberValue value: numberValue
}; };
}; };
const validateIdInput: ValidatorFunction<string, number> = value => { const validateIdInput: ValidatorFunction<string, bigint> = value => {
if ("" === value) { if ("" === value) {
return { return {
valid: true valid: true
}; };
} }
const numberValue = Number(value); const numberValue = BigInt(value);
// FIXME doesn't handle strings with characters correctly (e.g. "asdf" yields an empty value, due to "type=number"). if (numberValue.toString() !== value.trim()) {
if (isNaN(numberValue) || Math.floor(numberValue) !== numberValue) {
return { return {
valid: false, valid: false,
message: "Must be empty or an integer" message: "Must be empty or an integer"
@ -96,7 +96,7 @@ export default function InputForm({state, dispatch}: {
disabled={state.loading} disabled={state.loading}
onChange={setHeight} onChange={setHeight}
/> />
<ValidatingInputNumberField id={"id"} <ValidatingInputBigIntField id={"id"}
label={"ID (optional)"} label={"ID (optional)"}
value={id} value={id}
validatorFn={validateIdInput} validatorFn={validateIdInput}

View file

@ -0,0 +1,60 @@
import React, {ChangeEventHandler, useState} from 'react';
export default function ValidatingInputBigIntField({
id,
label,
value = 0n,
constraints = undefined,
validatorFn = (value) => {
return {valid: true, value: BigInt(value)};
},
disabled = false,
onChange = () => {
}
}:
{
id: string;
label: string;
value?: bigint;
constraints?: { min?: bigint; max?: bigint; };
validatorFn: ValidatorFunction<string, bigint>;
disabled: boolean;
onChange: (v: bigint) => void;
}) {
const [error, setError] = useState<string>();
const handleValueChange: ChangeEventHandler<HTMLInputElement> = (e) => {
const value = e.target.value;
const validation = validatorFn(value);
if (!validation.valid) {
setError(validation.message);
} else {
setError(undefined);
}
if (validation.value !== undefined) {
onChange(validation.value);
}
};
return (
<>
<label htmlFor={id}>{label}: </label>
<input id={id}
type={"number"}
onChange={handleValueChange}
value={value?.toString() || ""}
min={constraints?.min?.toString()}
max={constraints?.max?.toString()}
disabled={disabled}
/>
<span>{error}</span>
</>
);
}
export interface Validation<T> {
valid: boolean;
message?: string;
value?: T;
}
export type ValidatorFunction<I, T> = (v: I) => Validation<T>;

View file

@ -1,6 +1,6 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2017", "target": "ES2020",
"lib": ["dom", "dom.iterable", "esnext"], "lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true, "allowJs": true,
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,