[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-typescript-core] 03/08: fix some input array problems
From: |
gnunet |
Subject: |
[taler-typescript-core] 03/08: fix some input array problems |
Date: |
Wed, 15 Jan 2025 22:32:49 +0100 |
This is an automated email from the git hooks/post-receive script.
sebasjm pushed a commit to branch master
in repository taler-typescript-core.
commit e1aae7f0f0f0c30d91f1f2ea3bcefeb1e4eb7cf3
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Wed Jan 15 11:38:19 2025 -0300
fix some input array problems
---
.../src/forms/fields/InputArray.stories.tsx | 36 ++++
packages/web-util/src/forms/fields/InputArray.tsx | 191 ++++++---------------
.../web-util/src/forms/fields/InputDuration.tsx | 2 +-
.../src/forms/fields/InputSelectMultiple.tsx | 41 ++---
.../src/forms/fields/InputSelectOne.stories.tsx | 37 ++++
.../src/forms/fields/InputText.stories.tsx | 1 +
.../src/forms/fields/InputToggle.stories.tsx | 1 +
packages/web-util/src/forms/fields/InputToggle.tsx | 1 -
packages/web-util/src/forms/forms-ui.tsx | 15 +-
packages/web-util/src/hooks/useForm.ts | 2 +-
10 files changed, 163 insertions(+), 164 deletions(-)
diff --git a/packages/web-util/src/forms/fields/InputArray.stories.tsx
b/packages/web-util/src/forms/fields/InputArray.stories.tsx
index a20c98756..a4dfaf417 100644
--- a/packages/web-util/src/forms/fields/InputArray.stories.tsx
+++ b/packages/web-util/src/forms/fields/InputArray.stories.tsx
@@ -135,3 +135,39 @@ export const NonMixingProperties =
tests.createExample(TestedComponent, {
initial: initial2,
design: design2,
});
+
+const initial3: any = {
+ list: [{ steps: ["asd"] }],
+};
+
+const design3: FormDesign = {
+ type: "single-column",
+ fields: [
+ {
+ type: "array",
+ id: "list" as UIHandlerId,
+ label: `Paths`,
+ help: `For every entry the customer will have a different path to satify
checks.`,
+ labelFieldId: "steps" as UIHandlerId,
+ fields: [
+ {
+ type: "selectMultiple",
+ choices: ["asd", "qwe", "zxc"].map((m) => {
+ return {
+ value: m,
+ label: m,
+ };
+ }),
+ id: "steps" as UIHandlerId,
+ label: `Steps`,
+ help: `The checks that the customer will need to satisfy for this
path.`,
+ },
+ ],
+ },
+ ],
+};
+
+export const ArrayOfSelect = tests.createExample(TestedComponent, {
+ initial: initial3,
+ design: design3,
+});
diff --git a/packages/web-util/src/forms/fields/InputArray.tsx
b/packages/web-util/src/forms/fields/InputArray.tsx
index b0cfd80bc..b2c5d6e3d 100644
--- a/packages/web-util/src/forms/fields/InputArray.tsx
+++ b/packages/web-util/src/forms/fields/InputArray.tsx
@@ -1,7 +1,11 @@
import { TranslatedString } from "@gnu-taler/taler-util";
import { Fragment, h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks";
-import { getValueFromPath, useForm } from "../../hooks/useForm.js";
+import {
+ getValueFromPath,
+ RecursivePartial,
+ useForm,
+} from "../../hooks/useForm.js";
import {
SingleColumnFormSectionUI,
useTranslationContext,
@@ -83,88 +87,38 @@ export function noHandlerPropsAndNoContextForField(
);
}
-// function getRequiredFields(fields: UIFormField[]): Array<UIHandlerId> {
-// const shape: Array<UIHandlerId> = [];
-// fields.forEach((field) => {
-// if ("name" in field.properties) {
-// // FIXME: this should be a validation when loading the form
-// // consistency check
-// if (shape.indexOf(field.properties.name) !== -1) {
-// throw Error(`already present: ${field.properties.name}`);
-// }
-// if (!field.properties.required) {
-// return;
-// }
-// shape.push(field.properties.name);
-// } else if (field.type === "group") {
-// Array.prototype.push.apply(
-// shape,
-// getRequiredFields(field.properties.fields),
-// );
-// }
-// });
-// return shape;
-// }
+type FormType = {};
-function getRequiredFields(fields: UIFormElementConfig[]): Array<UIHandlerId> {
- const shape: Array<UIHandlerId> = [];
- fields.forEach((field) => {
- if ("id" in field) {
- // FIXME: this should be a validation when loading the form
- // consistency check
- if (shape.indexOf(field.id) !== -1) {
- throw Error(`already present: ${field.id}`);
- }
- if (!field.required) {
- return;
- }
- shape.push(field.id);
- } else if (field.type === "group") {
- Array.prototype.push.apply(shape, getRequiredFields(field.fields));
- }
- });
- return shape;
-}
+function ArrayForm({
+ fields,
+ selected,
+ onChange,
+}: {
+ fields: UIFormElementConfig[];
+ selected: Record<string, string | undefined> | undefined;
+ onChange: (r: RecursivePartial<FormType>) => void;
+}): VNode {
+ const form = useForm<FormType>(
+ {
+ type: "single-column",
+ fields,
+ },
+ selected ?? {},
+ );
-// function getShapeFromFields(fields: UIFormField[]): Array<UIHandlerId> {
-// const shape: Array<UIHandlerId> = [];
-// fields.forEach((field) => {
-// if ("name" in field.properties) {
-// // FIXME: this should be a validation when loading the form
-// // consistency check
-// if (shape.indexOf(field.properties.name) !== -1) {
-// throw Error(`already present: ${field.properties.name}`);
-// }
-// shape.push(field.properties.name);
-// } else if (field.type === "group") {
-// Array.prototype.push.apply(
-// shape,
-// getShapeFromFields(field.properties.fields),
-// );
-// }
-// });
-// return shape;
-// }
+ useEffect(() => {
+ onChange(form.status.result);
+ }, [form.status.result]);
-function getShapeFromFields(fields: UIFormElementConfig[]): Array<UIHandlerId>
{
- const shape: Array<UIHandlerId> = [];
- fields.forEach((field) => {
- if ("id" in field) {
- // FIXME: this should be a validation when loading the form
- // consistency check
- if (shape.indexOf(field.id) !== -1) {
- throw Error(`already present: ${field.id}`);
- }
- shape.push(field.id);
- } else if (field.type === "group") {
- Array.prototype.push.apply(shape, getShapeFromFields(field.fields));
- }
- });
- return shape;
+ return (
+ <div class="px-4 py-6">
+ <div class="grid grid-cols-1 gap-y-8 ">
+ <SingleColumnFormSectionUI fields={fields} handler={form.handler} />
+ </div>
+ </div>
+ );
}
-type FormType = {};
-
export function InputArray<T extends object, K extends keyof T>(
props: {
fields: UIFormElementConfig[];
@@ -183,20 +137,6 @@ export function InputArray<T extends object, K extends
keyof T>(
const selected =
selectedIndex === undefined ? undefined : list[selectedIndex];
- const form = useForm<FormType>(
- {
- type: "single-column",
- fields,
- },
- selected ?? {},
- );
-
- useEffect(() => {
- if (selectedIndex === undefined) return;
- const newValue = [...list];
- newValue.splice(selectedIndex, 1, form.status.result);
- onChange(newValue as any);
- }, [form.status.result, selectedIndex]);
const { i18n } = useTranslationContext();
return (
@@ -210,8 +150,11 @@ export function InputArray<T extends object, K extends
keyof T>(
<div class="overflow-hidden ring-1 ring-gray-900/5 rounded-xl p-4">
<div class="-space-y-px rounded-md bg-white ">
{list.map((v, idx) => {
- const label =
+ const labelValue =
getValueFromPath(v, labelField.split(".")) ?? "<<incomplete>>";
+ const label = Array.isArray(labelValue)
+ ? labelValue.join(", ")
+ : labelValue;
return (
<Option
label={label as TranslatedString}
@@ -246,51 +189,23 @@ export function InputArray<T extends object, K extends
keyof T>(
)}
</div>
{selectedIndex !== undefined && (
- /**
- * This form provider act as a substate of the parent form
- * Consider creating an InnerFormProvider since not every feature is
expected
- */
- // <FormProvider
- // initial={selected ?? {}}
- // readOnly={state.disabled}
- // computeFormState={(v) => {
- // // current state is ignored
- // // the state is defined by the parent form
-
- // // elements should be present in the state object since this
is expected to be an array
- // //@ts-ignore
- // // return state.elements[selectedIndex];
- // return {};
- // }}
- // onSubmit={(v) => {
- // const newValue = [...list];
- // newValue.splice(selectedIndex, 1, v);
- // onChange(newValue as any);
- // setSelectedIndex(undefined);
- // }}
- // onUpdate={(v) => {
- // const newValue = [...list];
- // newValue.splice(selectedIndex, 1, v);
- // onChange(newValue as any);
- // }}
- // >
- <div class="px-4 py-6">
- <div class="grid grid-cols-1 gap-y-8 ">
- <SingleColumnFormSectionUI
- fields={fields}
- handler={form.handler}
- />
- {/* <RenderAllFieldsByUiConfig
- fields={convertUiField(
- i18n,
- fields,
- form.handler,
- getConverterById,
- )}
- /> */}
- </div>
- </div>
- // </FormProvider>
+ <ArrayForm
+ fields={fields}
+ onChange={(result) => {
+ const newValue = [...list];
+ newValue.splice(selectedIndex, 1, result);
+ onChange(newValue as any);
+ }}
+ selected={selected}
+ />
+ // <div class="px-4 py-6">
+ // <div class="grid grid-cols-1 gap-y-8 ">
+ // <SingleColumnFormSectionUI
+ // fields={fields}
+ // handler={form.handler}
+ // />
+ // </div>
+ // </div>
)}
{selectedIndex !== undefined && (
<div class="flex items-center justify-end gap-x-6">
diff --git a/packages/web-util/src/forms/fields/InputDuration.tsx
b/packages/web-util/src/forms/fields/InputDuration.tsx
index 94cd6d6aa..9c368bdf3 100644
--- a/packages/web-util/src/forms/fields/InputDuration.tsx
+++ b/packages/web-util/src/forms/fields/InputDuration.tsx
@@ -14,7 +14,7 @@ export function InputDuration<T extends object, K extends
keyof T>(
const { value, onChange, state, error } =
props.handler ?? noHandlerPropsAndNoContextForField(props.name);
- const sd = Duration.toSpec(value as Duration);
+ const sd = !value ? undefined : Duration.toSpec(value as Duration);
const [days, setDays] = useState(sd?.days ?? 0);
const [hours, setHours] = useState(sd?.hours ?? 0);
const [minutes, setMinutes] = useState(sd?.minutes ?? 0);
diff --git a/packages/web-util/src/forms/fields/InputSelectMultiple.tsx
b/packages/web-util/src/forms/fields/InputSelectMultiple.tsx
index ef74e28c2..d7e8f9032 100644
--- a/packages/web-util/src/forms/fields/InputSelectMultiple.tsx
+++ b/packages/web-util/src/forms/fields/InputSelectMultiple.tsx
@@ -4,6 +4,7 @@ import { UIFormProps } from "../FormProvider.js";
import { noHandlerPropsAndNoContextForField } from "./InputArray.js";
import { ChoiceS } from "./InputChoiceStacked.js";
import { LabelWithTooltipMaybeRequired } from "./InputLine.js";
+import { useTranslationContext } from "../../index.browser.js";
export function InputSelectMultiple<T extends object, K extends keyof T>(
props: {
@@ -22,6 +23,7 @@ export function InputSelectMultiple<T extends object, K
extends keyof T>(
unique,
max,
} = props;
+ const { i18n } = useTranslationContext();
const { value, onChange, state } =
props.handler ?? noHandlerPropsAndNoContextForField(props.name);
@@ -39,7 +41,9 @@ export function InputSelectMultiple<T extends object, K
extends keyof T>(
filter === undefined
? undefined
: choices.filter((v) => {
- return regex.test(v.label);
+ const match = regex.test(v.label);
+ if (!unique) return match;
+ return match && list.indexOf(v.value as string) === -1;
});
return (
<div class="sm:col-span-6">
@@ -116,7 +120,20 @@ export function InputSelectMultiple<T extends object, K
extends keyof T>(
</svg>
</button>
- {filteredChoices !== undefined && (
+ {!filter ? undefined : filteredChoices === undefined ||
+ !filteredChoices.length ? (
+ <ul
+ class="absolute z-10 mt-1 max-h-60 w-full overflow-auto
rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5
focus:outline-none sm:text-sm"
+ id="options"
+ role="listbox"
+ >
+ <li class="relative cursor-pointer select-none py-2 pl-3 pr-9
text-gray-900 hover:text-white hover:bg-indigo-600">
+ <span class="block truncate">
+ <i18n.Translate>No element found</i18n.Translate>
+ </span>
+ </li>
+ </ul>
+ ) : (
<ul
class="absolute z-10 mt-1 max-h-60 w-full overflow-auto
rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5
focus:outline-none sm:text-sm"
id="options"
@@ -138,31 +155,15 @@ export function InputSelectMultiple<T extends object, K
extends keyof T>(
return;
}
const newValue = [...list];
- newValue.splice(0, 0, v.value as string);
+ newValue.push(v.value as string);
+ // newValue.splice(0, 0, v.value as string);
onChange(newValue as any);
}}
-
- // tabindex="-1"
>
- {/* <!-- Selected: "font-semibold" --> */}
<span class="block truncate">{v.label}</span>
-
- {/* <!--
- Checkmark, only display for selected option.
-
- Active: "text-white", Not Active: "text-indigo-600"
- --> */}
</li>
);
})}
-
- {/* <!--
- Combobox option, manage highlight styles based on
mouseenter/mouseleave and keyboard navigation.
-
- Active: "text-white bg-indigo-600", Not Active: "text-gray-900"
- --> */}
-
- {/* <!-- More items... --> */}
</ul>
)}
</div>
diff --git a/packages/web-util/src/forms/fields/InputSelectOne.stories.tsx
b/packages/web-util/src/forms/fields/InputSelectOne.stories.tsx
index 5c9dfe04f..b36889d2f 100644
--- a/packages/web-util/src/forms/fields/InputSelectOne.stories.tsx
+++ b/packages/web-util/src/forms/fields/InputSelectOne.stories.tsx
@@ -76,3 +76,40 @@ export const SimpleComment =
tests.createExample(TestedComponent, {
initial,
design,
});
+
+const design2: FormDesign = {
+ type: "double-column",
+ sections: [
+ {
+ title: "this is a simple form" as TranslatedString,
+ fields: [
+ {
+ type: "selectOne",
+ label: "label of the field" as TranslatedString,
+ id: "things" as UIHandlerId,
+ required: true,
+ placeholder: "search..." as TranslatedString,
+ choices: [
+ {
+ label: "one label" as TranslatedString,
+ value: "one",
+ },
+ {
+ label: "two label" as TranslatedString,
+ value: "two",
+ },
+ {
+ label: "five label" as TranslatedString,
+ value: "five",
+ },
+ ],
+ },
+ ],
+ },
+ ],
+};
+
+export const SimpleRequired = tests.createExample(TestedComponent, {
+ initial,
+ design: design2,
+});
diff --git a/packages/web-util/src/forms/fields/InputText.stories.tsx
b/packages/web-util/src/forms/fields/InputText.stories.tsx
index 092e8fa81..1eb7275e7 100644
--- a/packages/web-util/src/forms/fields/InputText.stories.tsx
+++ b/packages/web-util/src/forms/fields/InputText.stories.tsx
@@ -51,6 +51,7 @@ const design: FormDesign = {
type: "text",
label: "label of the field" as TranslatedString,
id: "comment" as UIHandlerId,
+ required: true,
},
],
},
diff --git a/packages/web-util/src/forms/fields/InputToggle.stories.tsx
b/packages/web-util/src/forms/fields/InputToggle.stories.tsx
index 6a2f30c80..7e856e583 100644
--- a/packages/web-util/src/forms/fields/InputToggle.stories.tsx
+++ b/packages/web-util/src/forms/fields/InputToggle.stories.tsx
@@ -64,6 +64,7 @@ export const WithThreeState =
tests.createExample(TestedComponent, {
type: "toggle",
label: "do you accept?" as TranslatedString,
threeState: true,
+ required: true,
id: "accept" as UIHandlerId,
},
],
diff --git a/packages/web-util/src/forms/fields/InputToggle.tsx
b/packages/web-util/src/forms/fields/InputToggle.tsx
index f22ffe1e7..ed126fe42 100644
--- a/packages/web-util/src/forms/fields/InputToggle.tsx
+++ b/packages/web-util/src/forms/fields/InputToggle.tsx
@@ -24,7 +24,6 @@ export function InputToggle<T extends object, K extends keyof
T>(
const isOn = !!value;
return (
<div class="sm:col-span-6">
- v = {JSON.stringify({ value, isOn })}
<div class="flex items-center justify-between">
<LabelWithTooltipMaybeRequired
label={label}
diff --git a/packages/web-util/src/forms/forms-ui.tsx
b/packages/web-util/src/forms/forms-ui.tsx
index 55ee29cb3..3db07ea92 100644
--- a/packages/web-util/src/forms/forms-ui.tsx
+++ b/packages/web-util/src/forms/forms-ui.tsx
@@ -26,9 +26,18 @@ export function DefaultForm<T>({
return (
<div>
<FormUI design={design} handler={handler} />
- <pre class="break-all whitespace-pre-wrap">
- {JSON.stringify({ status }, undefined, 2)}
- </pre>
+ {status.status === "ok" ? (
+ <pre class="break-all whitespace-pre-wrap">
+ {JSON.stringify(status.result ?? {}, undefined, 2)}
+ </pre>
+ ) : (
+ <Fragment>
+ <h1>form validation </h1>
+ <pre class="break-all whitespace-pre-wrap bg-red-200 border
border-red-500 w-max p-4">
+ {JSON.stringify(status.errors, undefined, 2)}
+ </pre>
+ </Fragment>
+ )}
</div>
);
}
diff --git a/packages/web-util/src/hooks/useForm.ts
b/packages/web-util/src/hooks/useForm.ts
index 54cbc06e5..b96387b6b 100644
--- a/packages/web-util/src/hooks/useForm.ts
+++ b/packages/web-util/src/hooks/useForm.ts
@@ -27,7 +27,7 @@ import {
UIFormElementConfig,
UIHandlerId,
} from "@gnu-taler/web-util/browser";
-import { useState } from "preact/hooks";
+import { useEffect, useState } from "preact/hooks";
export type FormHandler<T> = {
[k in keyof T]?: T[k] extends string
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
- [taler-typescript-core] branch master updated (f119f4aae -> 35b6e5931), gnunet, 2025/01/15
- [taler-typescript-core] 02/08: input duration, gnunet, 2025/01/15
- [taler-typescript-core] 01/08: new input type: secret, gnunet, 2025/01/15
- [taler-typescript-core] 03/08: fix some input array problems,
gnunet <=
- [taler-typescript-core] 06/08: sync types with docs, gnunet, 2025/01/15
- [taler-typescript-core] 07/08: use gana for account properties, gnunet, 2025/01/15
- [taler-typescript-core] 05/08: update measures, gnunet, 2025/01/15
- [taler-typescript-core] 08/08: properties form, gnunet, 2025/01/15
- [taler-typescript-core] 04/08: tospec, gnunet, 2025/01/15