taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit c901ff42295811d3b5a91cf5cea1a870db7007b5
parent 9144a04eebdcca28b217706d1c8918d8aa7257a7
Author: Florian Dold <dold@taler.net>
Date:   Sun, 23 Aug 2026 23:36:20 +0200

web-util: validate text fields while typing

Diffstat:
Mpackages/web-util/src/forms/fields/InputArray.test.tsx | 10++++++----
Mpackages/web-util/src/forms/fields/InputLine.tsx | 13+++++++++++--
2 files changed, 17 insertions(+), 6 deletions(-)

diff --git a/packages/web-util/src/forms/fields/InputArray.test.tsx b/packages/web-util/src/forms/fields/InputArray.test.tsx @@ -111,14 +111,16 @@ test("array items can be added, edited, cancelled, and deleted", async () => { ); nameInput.value = "Alex Example"; - nameInput.dispatchEvent(new window.Event("change", { bubbles: true })); + nameInput.dispatchEvent(new window.Event("input", { bubbles: true })); await new Promise((resolve) => setTimeout(resolve, 0)); + addressInput.focus(); addressInput.value = "Main Street 12, 8000 Zürich"; - addressInput.dispatchEvent(new window.Event("change", { bubbles: true })); + addressInput.dispatchEvent(new window.Event("input", { bubbles: true })); await new Promise((resolve) => setTimeout(resolve, 0)); const confirmAddButton = view.getByRole("button", { name: "Add item" }); await eventually(() => { assert.equal((confirmAddButton as HTMLButtonElement).disabled, false); + assert.equal(window.document.activeElement === addressInput, true); }); confirmAddButton.click(); @@ -143,7 +145,7 @@ test("array items can be added, edited, cancelled, and deleted", async () => { view.container.querySelector<HTMLInputElement>('input[name="name"]'); assert.ok(secondNameInput); secondNameInput.value = "Discarded"; - secondNameInput.dispatchEvent(new window.Event("change", { bubbles: true })); + secondNameInput.dispatchEvent(new window.Event("input", { bubbles: true })); view.getByRole("button", { name: "Cancel" }).click(); await eventually(() => assert.equal(result.people.length, 1)); await eventually(() => { @@ -171,7 +173,7 @@ test("array items can be added, edited, cancelled, and deleted", async () => { assert.ok(editNameInput); assert.equal(editNameInput.value, "Alex Example"); editNameInput.value = "Alex Updated"; - editNameInput.dispatchEvent(new window.Event("change", { bubbles: true })); + editNameInput.dispatchEvent(new window.Event("input", { bubbles: true })); await new Promise((resolve) => setTimeout(resolve, 0)); view.getByRole("button", { name: "Save changes" }).click(); await eventually(() => assert.equal(result.people[0].name, "Alex Updated")); diff --git a/packages/web-util/src/forms/fields/InputLine.tsx b/packages/web-util/src/forms/fields/InputLine.tsx @@ -192,6 +192,9 @@ export function InputLine<Value = string>( const fromString: (s: string) => any = converter?.fromStringUI ?? defaultFromString; const toString: (s: any) => string = converter?.toStringUI ?? defaultToString; + const updateFromString = (newValue: string): void => { + onChange(fromString(newValue)); + }; useEffect(() => { if (!input.current) return; @@ -263,8 +266,11 @@ export function InputLine<Value = string>( ref={composeRef(saveRef(input))} // ref={composeRef(saveRef(input), doAutoFocus)} FIXME: enable autofocus when form has focus name={String(name)} + onInput={(e) => { + updateFromString(e.currentTarget.value); + }} onChange={(e) => { - onChange(fromString(e.currentTarget.value)); + updateFromString(e.currentTarget.value); }} defaultValue={props.defaultValue} placeholder={placeholder ? placeholder : undefined} @@ -293,8 +299,11 @@ export function InputLine<Value = string>( ref={composeRef(saveRef(input))} // ref={composeRef(saveRef(input), doAutoFocus)} FIXME: enable autofocus when form has focus type={type} + onInput={(e) => { + updateFromString(e.currentTarget.value); + }} onChange={(e) => { - onChange(fromString(e.currentTarget.value)); + updateFromString(e.currentTarget.value); }} placeholder={placeholder ? placeholder : undefined} // value={toString(value) ?? ""}