gnunet-svn
[Top][All Lists]
Advanced

[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.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]