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:
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) ?? ""}