diff --git a/src/app/input-form.tsx b/src/app/input-form.tsx index bed9b76..ef2729e 100644 --- a/src/app/input-form.tsx +++ b/src/app/input-form.tsx @@ -10,6 +10,7 @@ import { import styles from "./input-form.module.css"; import "./input-form.css"; import {State} from "@/app/state/state.ts"; +import ValidatingInputBigIntField from "@/app/validating-input-bigint-field.tsx"; export default function InputForm({state, dispatch}: { state: State, @@ -17,7 +18,7 @@ export default function InputForm({state, dispatch}: { }) { const [width, setWidth] = useState(10); const [height, setHeight] = useState(10); - const [id, setId] = useState<number>(); + const [id, setId] = useState<bigint>(); const [algorithm, setAlgorithm] = useState('wilson'); const handleSubmit = (e: FormEvent) => { @@ -54,15 +55,14 @@ export default function InputForm({state, dispatch}: { value: numberValue }; }; - const validateIdInput: ValidatorFunction<string, number> = value => { + const validateIdInput: ValidatorFunction<string, bigint> = value => { if ("" === value) { return { valid: true }; } - const numberValue = Number(value); - // FIXME doesn't handle strings with characters correctly (e.g. "asdf" yields an empty value, due to "type=number"). - if (isNaN(numberValue) || Math.floor(numberValue) !== numberValue) { + const numberValue = BigInt(value); + if (numberValue.toString() !== value.trim()) { return { valid: false, message: "Must be empty or an integer" @@ -96,7 +96,7 @@ export default function InputForm({state, dispatch}: { disabled={state.loading} onChange={setHeight} /> - <ValidatingInputNumberField id={"id"} + <ValidatingInputBigIntField id={"id"} label={"ID (optional)"} value={id} validatorFn={validateIdInput} diff --git a/src/app/validating-input-bigint-field.tsx b/src/app/validating-input-bigint-field.tsx new file mode 100644 index 0000000..572f481 --- /dev/null +++ b/src/app/validating-input-bigint-field.tsx @@ -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>; diff --git a/tsconfig.json b/tsconfig.json index ef36eec..b953079 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "ES2017", + "target": "ES2020", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "allowImportingTsExtensions": true,