gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-typescript-core] 01/04: input duration


From: gnunet
Subject: [taler-typescript-core] 01/04: input duration
Date: Sun, 26 Jan 2025 15:33:56 +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 f586af103f68c67f763c01c105fe640ce0d34aae
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Fri Jan 24 12:18:46 2025 -0300

    input duration
---
 packages/aml-backoffice-ui/src/pages/Dashboard.tsx |   8 +-
 packages/taler-util/src/time.ts                    |  29 ++--
 packages/web-util/src/components/index.ts          |   2 +-
 packages/web-util/src/components/utils.ts          |  24 +++
 packages/web-util/src/forms/field-types.ts         |   7 +
 .../src/forms/fields/InputDuration.stories.tsx     |   8 +-
 .../web-util/src/forms/fields/InputDuration.tsx    | 176 ++++++++++++++++++---
 ...r.stories.tsx => InputDurationText.stories.tsx} |  10 +-
 .../src/forms/fields/InputDurationText.tsx         | 105 ++++++++++++
 .../src/forms/fields/InputInteger.stories.tsx      |  27 ++++
 packages/web-util/src/forms/fields/InputLine.tsx   |  16 +-
 packages/web-util/src/forms/forms-types.ts         |  11 ++
 packages/web-util/src/forms/forms-utils.ts         |  13 ++
 packages/web-util/src/forms/index.stories.ts       |   1 +
 14 files changed, 382 insertions(+), 55 deletions(-)

diff --git a/packages/aml-backoffice-ui/src/pages/Dashboard.tsx 
b/packages/aml-backoffice-ui/src/pages/Dashboard.tsx
index e87236cfc..e43934420 100644
--- a/packages/aml-backoffice-ui/src/pages/Dashboard.tsx
+++ b/packages/aml-backoffice-ui/src/pages/Dashboard.tsx
@@ -95,7 +95,7 @@ function EventMetrics({
       <div class="sm:flex sm:items-center mb-4">
         <div class="sm:flex-auto">
           <h1 class="text-base font-semibold leading-6 text-gray-900">
-            <i18n.Translate>Events</i18n.Translate>
+            <i18n.Translate>Statistics</i18n.Translate>
           </h1>
         </div>
       </div>
@@ -104,7 +104,7 @@ function EventMetrics({
 
       <div class="w-full flex justify-between">
         <h1 class="text-base text-gray-900 mt-5">
-          {i18n.str`Trading volume from ${getDateStringForTimeframe(
+          {i18n.str`Events from ${getDateStringForTimeframe(
             params.current.start,
             metricType,
             dateLocale,
@@ -258,13 +258,13 @@ function MetricValueNumber({
     <Fragment>
       <dd class="mt-1 block ">
         <div class="flex justify-start text-2xl items-baseline font-semibold 
text-indigo-600">
-          {!current ? "-" : current}
+          {!current ? 0 : current}
         </div>
         <div class="flex flex-col">
           <div class="flex justify-end items-baseline text-2xl font-semibold 
text-indigo-600">
             <small class="ml-2 text-sm font-medium text-gray-500">
               <i18n.Translate>previous</i18n.Translate>{" "}
-              {!previous ? "-" : previous}
+              {!previous ? 0 : previous}
             </small>
           </div>
           {!!rate && (
diff --git a/packages/taler-util/src/time.ts b/packages/taler-util/src/time.ts
index 93e34bbaf..81774455c 100644
--- a/packages/taler-util/src/time.ts
+++ b/packages/taler-util/src/time.ts
@@ -304,25 +304,26 @@ export namespace Duration {
         minutes: number;
         hours: number;
         days: number;
+        month: number;
+        years: number;
       }
     | undefined {
     if (d_ms === "forever") return undefined;
-    let time = d_ms;
-    const millis = d_ms % SECONDS;
-    time -= millis;
-    const s = time % MINUTES;
-    time -= s;
-    const m = time % HOURS;
-    time -= m;
-    const h = time % DAYS;
-    time -= h;
-    const d = time;
+    const ms = d_ms > 0 ? d_ms : 0;
+    const Y_rest = ms % YEARS;
+    const M_rest = Y_rest % MONTHS;
+    const D_rest = M_rest % DAYS;
+    const h_rest = D_rest % HOURS;
+    const m_rest = h_rest % MINUTES;
+    const millis = m_rest % SECONDS;
 
     return {
-      seconds: s / SECONDS,
-      minutes: m / MINUTES,
-      hours: h / HOURS,
-      days: d / DAYS,
+      years: (ms - Y_rest) / YEARS,
+      month: (Y_rest - M_rest) / MONTHS,
+      days: (M_rest - D_rest) / DAYS,
+      hours: (D_rest - h_rest) / HOURS,
+      minutes: (h_rest - m_rest) / MINUTES,
+      seconds: (m_rest - millis) / SECONDS,
     };
   }
 
diff --git a/packages/web-util/src/components/index.ts 
b/packages/web-util/src/components/index.ts
index 944bed269..17d89ad1f 100644
--- a/packages/web-util/src/components/index.ts
+++ b/packages/web-util/src/components/index.ts
@@ -1,5 +1,5 @@
 export * as utils from "./utils.js";
-export { onComponentUnload } from "./utils.js";
+export { onComponentUnload, preconnectAs, Preconnect } from "./utils.js";
 export * from "./Attention.js";
 export * from "./CopyButton.js";
 export * from "./ErrorLoading.js";
diff --git a/packages/web-util/src/components/utils.ts 
b/packages/web-util/src/components/utils.ts
index a9871ce85..9cbc1bbba 100644
--- a/packages/web-util/src/components/utils.ts
+++ b/packages/web-util/src/components/utils.ts
@@ -83,6 +83,30 @@ export function onComponentUnload(callback: () => void) {
   }, []);
 }
 
+const ownerDocument = typeof document === "undefined" ? null : document;
+const preconnectsSet: Set<string> = new Set();
+
+export type Preconnect = {
+  rel: "preconnect" | "dns-prefetch";
+  href: string;
+  crossOrigin: string;
+};
+
+export function preconnectAs(pre: Preconnect[]) {
+  if (ownerDocument) {
+    pre.forEach(({ rel, href, crossOrigin }) => {
+      const key = `${rel}${href}${crossOrigin}`;
+      if (preconnectsSet.has(key)) return;
+      preconnectsSet.add(key);
+      const instance = ownerDocument.createElement("link");
+      instance.setAttribute("rel", rel);
+      instance.setAttribute("crossOrigin", crossOrigin);
+      instance.setAttribute("href", href);
+      ownerDocument.head.appendChild(instance);
+    });
+  }
+}
+
 /**
  *
  * @param obj VNode
diff --git a/packages/web-util/src/forms/field-types.ts 
b/packages/web-util/src/forms/field-types.ts
index 34696b430..e65f44b1c 100644
--- a/packages/web-util/src/forms/field-types.ts
+++ b/packages/web-util/src/forms/field-types.ts
@@ -17,6 +17,7 @@ import { InputTextArea } from "./fields/InputTextArea.js";
 import { InputToggle } from "./fields/InputToggle.js";
 import { Group } from "./Group.js";
 import { HtmlIframe } from "./HtmlIframe.js";
+import { InputDurationText } from "./fields/InputDurationText.js";
 /**
  * Constrain the type with the ui props
  */
@@ -39,6 +40,7 @@ type FieldType<T extends object = any, K extends keyof T = 
any> = {
   toggle: Parameters<typeof InputToggle<T, K>>[0];
   amount: Parameters<typeof InputAmount<T, K>>[0];
   duration: Parameters<typeof InputDuration<T, K>>[0];
+  durationText: Parameters<typeof InputDurationText<T, K>>[0];
 };
 
 /**
@@ -77,6 +79,10 @@ export type UIFormField =
   | {
       type: "duration";
       properties: FieldType["duration"];
+    }
+  | {
+      type: "durationText";
+      properties: FieldType["durationText"];
     };
 
 export type FieldComponentFunction<key extends keyof FieldType> = (
@@ -118,4 +124,5 @@ export const UIFormConfiguration: UIFormFieldMap = {
   //@ts-ignore
   amount: InputAmount,
   duration: InputDuration,
+  durationText: InputDurationText,
 };
diff --git a/packages/web-util/src/forms/fields/InputDuration.stories.tsx 
b/packages/web-util/src/forms/fields/InputDuration.stories.tsx
index 8c0983287..d73e5c535 100644
--- a/packages/web-util/src/forms/fields/InputDuration.stories.tsx
+++ b/packages/web-util/src/forms/fields/InputDuration.stories.tsx
@@ -33,10 +33,10 @@ type TargetObject = {
 };
 const initial: TargetObject = {
   time: Duration.fromSpec({
-    days: 1,
-    hours: 2,
-    minutes: 3,
-    seconds: 4,
+    days: 29,
+    hours: 23,
+    minutes: 59,
+    seconds: 59,
   }),
 };
 
diff --git a/packages/web-util/src/forms/fields/InputDuration.tsx 
b/packages/web-util/src/forms/fields/InputDuration.tsx
index 9c368bdf3..08364694b 100644
--- a/packages/web-util/src/forms/fields/InputDuration.tsx
+++ b/packages/web-util/src/forms/fields/InputDuration.tsx
@@ -4,7 +4,7 @@ import { UIFormProps } from "../FormProvider.js";
 import { noHandlerPropsAndNoContextForField } from "./InputArray.js";
 import { InputWrapper } from "./InputLine.js";
 import { Duration } from "@gnu-taler/taler-util";
-import { useEffect, useState } from "preact/hooks";
+import { useEffect, useRef, useState } from "preact/hooks";
 
 export function InputDuration<T extends object, K extends keyof T>(
   props: UIFormProps<T, K>,
@@ -14,26 +14,75 @@ export function InputDuration<T extends object, K extends 
keyof T>(
   const { value, onChange, state, error } =
     props.handler ?? noHandlerPropsAndNoContextForField(props.name);
 
-  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);
-  const [seconds, setSeconds] = useState(sd?.seconds ?? 0);
+  const specDuration = !value ? undefined : Duration.toSpec(value as Duration);
+  // const [seconds, setSeconds] = useState(sd?.seconds ?? 0);
+  // const [hours, setHours] = useState(sd?.hours ?? 0);
+  // const [minutes, setMinutes] = useState(sd?.minutes ?? 0);
+  // const [days, setDays] = useState(sd?.days ?? 0);
+  // const [months, setMonths] = useState(sd?.month ?? 0);
+  // const [years, setYears] = useState(sd?.years ?? 0);
 
-  useEffect(() => {
-    onChange(
-      Duration.fromSpec({
-        days,
-        hours,
-        minutes,
-        seconds,
-      }),
-    );
-  }, [days, hours, minutes, seconds]);
+  const secondsRef = useRef<HTMLInputElement>(null);
+  const hoursRef = useRef<HTMLInputElement>(null);
+  const minutesRef = useRef<HTMLInputElement>(null);
+  const daysRef = useRef<HTMLInputElement>(null);
+  const monthsRef = useRef<HTMLInputElement>(null);
+  const yearsRef = useRef<HTMLInputElement>(null);
+
+  // useEffect(() => {
+  //   onChange(
+  //     Duration.fromSpec({
+  //       days,
+  //       hours,
+  //       minutes,
+  //       seconds,
+  //       months,
+  //       years,
+  //     }),
+  //   );
+  // }, [days, hours, minutes, seconds, months, years]);
   const fromString: (s: string) => any =
     converter?.fromStringUI ?? defaultFromString;
   const toString: (s: any) => string = converter?.toStringUI ?? 
defaultToString;
 
+  const strSeconds = toString(specDuration?.seconds ?? 0) ?? "";
+  const strHours = toString(specDuration?.hours ?? 0) ?? "";
+  const strMinutes = toString(specDuration?.minutes ?? 0) ?? "";
+  const strDays = toString(specDuration?.days ?? 0) ?? "";
+  const strMonths = toString(specDuration?.month ?? 0) ?? "";
+  const strYears = toString(specDuration?.years ?? 0) ?? "";
+
+  useEffect(() => {
+    if (!secondsRef.current) return;
+    if (secondsRef.current === document.activeElement) return;
+    secondsRef.current.value = strSeconds;
+  }, [strSeconds]);
+  useEffect(() => {
+    if (!minutesRef.current) return;
+    if (minutesRef.current === document.activeElement) return;
+    minutesRef.current.value = strMinutes;
+  }, [strMinutes]);
+  useEffect(() => {
+    if (!hoursRef.current) return;
+    if (hoursRef.current === document.activeElement) return;
+    hoursRef.current.value = strHours;
+  }, [strHours]);
+  useEffect(() => {
+    if (!daysRef.current) return;
+    if (daysRef.current === document.activeElement) return;
+    daysRef.current.value = strDays;
+  }, [strDays]);
+  useEffect(() => {
+    if (!monthsRef.current) return;
+    if (monthsRef.current === document.activeElement) return;
+    monthsRef.current.value = strMonths;
+  }, [strMonths]);
+  useEffect(() => {
+    if (!yearsRef.current) return;
+    if (yearsRef.current === document.activeElement) return;
+    yearsRef.current.value = strYears;
+  }, [strYears]);
+
   if (state.hidden) return <div />;
 
   let clazz =
@@ -86,6 +135,60 @@ export function InputDuration<T extends object, K extends 
keyof T>(
       error={showError ? error : undefined}
     >
       <div class="flex flex-col gap-1">
+        <div class="flex">
+          <span class="ml-2 inline-flex items-center rounded-l-md border 
border-r-0 border-gray-300 px-3 text-gray-500 sm:text-sm">
+            <i18n.Translate>years</i18n.Translate>
+          </span>
+          <input
+            name={String(name)}
+            type="number"
+            onChange={(e) => {
+              onChange(
+                Duration.fromSpec({
+                  ...specDuration,
+                  years: fromString(e.currentTarget.value),
+                }),
+              );
+            }}
+            placeholder={placeholder ? placeholder : undefined}
+            ref={yearsRef}
+            // value={toString(sd?.years) ?? ""}
+            // onBlur={() => {
+            //   onChange(fromString(value as any));
+            // }}
+            // defaultValue={toString(value)}
+            disabled={disabled ?? false}
+            aria-invalid={showError}
+            // aria-describedby="email-error"
+            class={clazz}
+          />
+          <span class="ml-2 inline-flex items-center rounded-l-md border 
border-r-0 border-gray-300 px-3 text-gray-500 sm:text-sm">
+            <i18n.Translate>months</i18n.Translate>
+          </span>
+          <input
+            name={String(name)}
+            type="number"
+            ref={monthsRef}
+            onChange={(e) => {
+              onChange(
+                Duration.fromSpec({
+                  ...specDuration,
+                  months: fromString(e.currentTarget.value),
+                }),
+              );
+            }}
+            placeholder={placeholder ? placeholder : undefined}
+            // value={toString(specDuration?.month) ?? ""}
+            // onBlur={() => {
+            //   onChange(fromString(value as any));
+            // }}
+            // defaultValue={toString(value)}
+            disabled={disabled ?? false}
+            aria-invalid={showError}
+            // aria-describedby="email-error"
+            class={clazz}
+          />
+        </div>
         <div class="flex">
           <span class="ml-2 inline-flex items-center rounded-l-md border 
border-r-0 border-gray-300 px-3 text-gray-500 sm:text-sm">
             <i18n.Translate>days</i18n.Translate>
@@ -93,11 +196,17 @@ export function InputDuration<T extends object, K extends 
keyof T>(
           <input
             name={String(name)}
             type="number"
+            ref={daysRef}
             onChange={(e) => {
-              setDays(fromString(e.currentTarget.value));
+              onChange(
+                Duration.fromSpec({
+                  ...specDuration,
+                  days: fromString(e.currentTarget.value),
+                }),
+              );
             }}
             placeholder={placeholder ? placeholder : undefined}
-            value={toString(sd?.days) ?? ""}
+            // value={toString(specDuration?.days) ?? ""}
             // onBlur={() => {
             //   onChange(fromString(value as any));
             // }}
@@ -113,11 +222,17 @@ export function InputDuration<T extends object, K extends 
keyof T>(
           <input
             name={String(name)}
             type="number"
+            ref={hoursRef}
             onChange={(e) => {
-              setHours(fromString(e.currentTarget.value));
+              onChange(
+                Duration.fromSpec({
+                  ...specDuration,
+                  hours: fromString(e.currentTarget.value),
+                }),
+              );
             }}
             placeholder={placeholder ? placeholder : undefined}
-            value={toString(sd?.hours) ?? ""}
+            // value={toString(specDuration?.hours) ?? ""}
             // onBlur={() => {
             //   onChange(fromString(value as any));
             // }}
@@ -135,11 +250,17 @@ export function InputDuration<T extends object, K extends 
keyof T>(
           <input
             name={String(name)}
             type="number"
+            ref={minutesRef}
             onChange={(e) => {
-              setMinutes(fromString(e.currentTarget.value));
+              onChange(
+                Duration.fromSpec({
+                  ...specDuration,
+                  minutes: fromString(e.currentTarget.value),
+                }),
+              );
             }}
             placeholder={placeholder ? placeholder : undefined}
-            value={toString(sd?.minutes) ?? ""}
+            // value={toString(specDuration?.minutes) ?? ""}
             // onBlur={() => {
             //   onChange(fromString(value as any));
             // }}
@@ -155,11 +276,18 @@ export function InputDuration<T extends object, K extends 
keyof T>(
           <input
             name={String(name)}
             type="number"
+            ref={secondsRef}
             onChange={(e) => {
-              setSeconds(fromString(e.currentTarget.value));
+              // setSeconds(fromString(e.currentTarget.value));
+              onChange(
+                Duration.fromSpec({
+                  ...specDuration,
+                  seconds: fromString(e.currentTarget.value),
+                }),
+              );
             }}
             placeholder={placeholder ? placeholder : undefined}
-            value={toString(sd?.seconds) ?? ""}
+            // value={toString(specDuration?.seconds) ?? ""}
             // onBlur={() => {
             //   onChange(fromString(value as any));
             // }}
diff --git a/packages/web-util/src/forms/fields/InputInteger.stories.tsx 
b/packages/web-util/src/forms/fields/InputDurationText.stories.tsx
similarity index 88%
copy from packages/web-util/src/forms/fields/InputInteger.stories.tsx
copy to packages/web-util/src/forms/fields/InputDurationText.stories.tsx
index 0a2bcaca0..44801b0a0 100644
--- a/packages/web-util/src/forms/fields/InputInteger.stories.tsx
+++ b/packages/web-util/src/forms/fields/InputDurationText.stories.tsx
@@ -19,20 +19,20 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { TranslatedString } from "@gnu-taler/taler-util";
+import { Duration, TranslatedString } from "@gnu-taler/taler-util";
 import * as tests from "../../tests/hook.js";
 import { DefaultForm as TestedComponent } from "../forms-ui.js";
 import { FormDesign, UIHandlerId } from "../forms-types.js";
 
 export default {
-  title: "Input Integer",
+  title: "Input Duration Text",
 };
 
 type TargetObject = {
-  age: number;
+  age: Duration;
 };
 const initial: TargetObject = {
-  age: 5,
+  age: { d_ms: 10000000 },
 };
 
 const design: FormDesign = {
@@ -42,7 +42,7 @@ const design: FormDesign = {
       title: "this is a simple form" as TranslatedString,
       fields: [
         {
-          type: "integer",
+          type: "durationText",
           label: "Age" as TranslatedString,
           id: "age" as UIHandlerId,
           tooltip: "just numbers" as TranslatedString,
diff --git a/packages/web-util/src/forms/fields/InputDurationText.tsx 
b/packages/web-util/src/forms/fields/InputDurationText.tsx
new file mode 100644
index 000000000..3a4316117
--- /dev/null
+++ b/packages/web-util/src/forms/fields/InputDurationText.tsx
@@ -0,0 +1,105 @@
+import { VNode, h } from "preact";
+import { InputLine } from "./InputLine.js";
+import { UIFormProps } from "../FormProvider.js";
+import { Duration } from "@gnu-taler/taler-util";
+
+const PATTERN = /^(?<value>[0-9]+)(?<unit>[smhDMY])$/;
+const UNIT_GROUP = "unit";
+const VALUE_GROUP = "value";
+
+type DurationUnit = "s" | "m" | "h" | "D" | "M" | "Y";
+type DurationSpec = Parameters<typeof Duration.fromSpec>[0];
+
+type DurationValue = {
+  unit: DurationUnit;
+  value: number;
+};
+
+function updateSpec(spec: DurationSpec, value: DurationValue): void {
+  switch (value.unit) {
+    case "s": {
+      spec.seconds = value.value;
+      break;
+    }
+    case "m": {
+      spec.minutes = value.value;
+      break;
+    }
+    case "h": {
+      spec.hours = value.value;
+      break;
+    }
+    case "D": {
+      spec.days = value.value;
+      break;
+    }
+    case "M": {
+      spec.months = value.value;
+      break;
+    }
+    case "Y": {
+      spec.years = value.value;
+      break;
+    }
+  }
+}
+
+function parseDurationValue(str: string): DurationValue | undefined {
+  const r = PATTERN.exec(str);
+  if (!r) return undefined;
+  const value = Number.parseInt(r.groups![VALUE_GROUP], 10);
+  const unit = r.groups![UNIT_GROUP] as DurationUnit;
+  return { value, unit };
+}
+
+export function InputDurationText<T extends object, K extends keyof T>(
+  props: UIFormProps<T, K>,
+): VNode {
+  return (
+    // <div>s</div>
+    <InputLine
+      type="text"
+      {...props}
+      converter={{
+        //@ts-ignore
+        fromStringUI: (v): Duration => {
+          if (!v) return Duration.getForever();
+          const spec = v.split(" ").reduce((prev, cur) => {
+            const v = parseDurationValue(cur);
+            if (v) {
+              updateSpec(prev, v);
+            }
+            return prev;
+          }, {} as DurationSpec);
+          return Duration.fromSpec(spec);
+        },
+        //@ts-ignore
+        toStringUI: (v?: Duration): string => {
+          if (v === undefined) return "";
+          // return v! as any;
+          const spec = Duration.toSpec(v);
+          let result = "";
+          if (spec?.years) {
+            result += `${spec.years}Y `;
+          }
+          if (spec?.month) {
+            result += `${spec.month}M `;
+          }
+          if (spec?.days) {
+            result += `${spec.days}D `;
+          }
+          if (spec?.hours) {
+            result += `${spec.hours}h `;
+          }
+          if (spec?.minutes) {
+            result += `${spec.minutes}m `;
+          }
+          if (spec?.seconds) {
+            result += `${spec.seconds}s `;
+          }
+          return result.trimEnd();
+        },
+      }}
+    />
+  );
+}
diff --git a/packages/web-util/src/forms/fields/InputInteger.stories.tsx 
b/packages/web-util/src/forms/fields/InputInteger.stories.tsx
index 0a2bcaca0..b20048f70 100644
--- a/packages/web-util/src/forms/fields/InputInteger.stories.tsx
+++ b/packages/web-util/src/forms/fields/InputInteger.stories.tsx
@@ -52,7 +52,34 @@ const design: FormDesign = {
   ],
 };
 
+const design2: FormDesign = {
+  type: "double-column",
+  sections: [
+    {
+      title: "this is a simple form" as TranslatedString,
+      fields: [
+        {
+          type: "integer",
+          label: "Age" as TranslatedString,
+          id: "age" as UIHandlerId,
+          tooltip: "just numbers" as TranslatedString,
+        },
+        {
+          type: "integer",
+          label: "Age" as TranslatedString,
+          id: "age" as UIHandlerId,
+          tooltip: "just numbers" as TranslatedString,
+        },
+      ],
+    },
+  ],
+};
 export const SimpleComment = tests.createExample(TestedComponent, {
   initial,
   design,
 });
+
+export const DoubleInput = tests.createExample(TestedComponent, {
+  initial,
+  design: design2,
+});
diff --git a/packages/web-util/src/forms/fields/InputLine.tsx 
b/packages/web-util/src/forms/fields/InputLine.tsx
index bbbc871e0..0f981f93a 100644
--- a/packages/web-util/src/forms/fields/InputLine.tsx
+++ b/packages/web-util/src/forms/fields/InputLine.tsx
@@ -2,6 +2,7 @@ import { TranslatedString } from "@gnu-taler/taler-util";
 import { ComponentChildren, Fragment, VNode, h } from "preact";
 import { Addon, UIFormProps } from "../FormProvider.js";
 import { noHandlerPropsAndNoContextForField } from "./InputArray.js";
+import { useEffect, useRef } from "preact/hooks";
 
 //@ts-ignore
 const TooltipIcon = (
@@ -160,6 +161,7 @@ export function InputLine<T extends object, K extends keyof 
T>(
   props: { type: InputType } & UIFormProps<T, K>,
 ): VNode {
   const { name, placeholder, before, after, converter, type, disabled } = 
props;
+  const input = useRef<HTMLTextAreaElement & HTMLInputElement>(null);
 
   const { value, onChange, state, error } =
     props.handler ?? noHandlerPropsAndNoContextForField(props.name);
@@ -168,6 +170,12 @@ export function InputLine<T extends object, K extends 
keyof T>(
     converter?.fromStringUI ?? defaultFromString;
   const toString: (s: any) => string = converter?.toStringUI ?? 
defaultToString;
 
+  useEffect(() => {
+    if (!input.current) return;
+    if (input.current === document.activeElement) return;
+    input.current.value = String(value);
+  }, [value]);
+
   if (state.hidden) return <div />;
 
   let clazz =
@@ -223,12 +231,13 @@ export function InputLine<T extends object, K extends 
keyof T>(
       >
         <textarea
           rows={4}
+          ref={input}
           name={String(name)}
           onChange={(e) => {
             onChange(fromString(e.currentTarget.value));
           }}
           placeholder={placeholder ? placeholder : undefined}
-          value={toString(value) ?? ""}
+          // value={toString(value) ?? ""}
           // defaultValue={toString(value)}
           disabled={disabled ?? false}
           aria-invalid={showError}
@@ -248,16 +257,17 @@ export function InputLine<T extends object, K extends 
keyof T>(
     >
       <input
         name={String(name)}
+        ref={input}
         type={type}
         onChange={(e) => {
           onChange(fromString(e.currentTarget.value));
         }}
         placeholder={placeholder ? placeholder : undefined}
-        value={toString(value) ?? ""}
+        // value={toString(value) ?? ""}
         // onBlur={() => {
         //   onChange(fromString(value as any));
         // }}
-        // defaultValue={toString(value)}
+        defaultValue={toString(value)}
         disabled={disabled ?? false}
         aria-invalid={showError}
         // aria-describedby="email-error"
diff --git a/packages/web-util/src/forms/forms-types.ts 
b/packages/web-util/src/forms/forms-types.ts
index 0da107e34..7509a7cc8 100644
--- a/packages/web-util/src/forms/forms-types.ts
+++ b/packages/web-util/src/forms/forms-types.ts
@@ -59,6 +59,7 @@ export type UIFormElementConfig =
   | UIFormFieldSecret
   | UIFormFieldSelectMultiple
   | UIFormFieldDuration
+  | UIFormFieldDurationText
   | UIFormFieldSelectOne
   | UIFormFieldText
   | UIFormFieldTextArea
@@ -151,6 +152,10 @@ type UIFormFieldDuration = {
   type: "duration";
 } & UIFormFieldBaseConfig;
 
+type UIFormFieldDurationText = {
+  type: "durationText";
+} & UIFormFieldBaseConfig;
+
 type UIFormFieldSelectOne = {
   type: "selectOne";
   choices: Array<SelectUiChoice>;
@@ -332,6 +337,11 @@ const codecForUiFormFieldDuration = (): 
Codec<UIFormFieldDuration> =>
     .property("type", codecForConstString("duration"))
     .build("UiFormFieldDuration");
 
+const codecForUiFormFieldDurationText = (): Codec<UIFormFieldDurationText> =>
+  codecForUIFormFieldBaseConfigTemplate<UIFormFieldDurationText>()
+    .property("type", codecForConstString("durationText"))
+    .build("UiFormFieldDuration");
+
 const codecForUiFormFieldSelectMultiple =
   (): Codec<UIFormFieldSelectMultiple> =>
     codecForUIFormFieldBaseConfigTemplate<UIFormFieldSelectMultiple>()
@@ -383,6 +393,7 @@ const codecForUiFormField = (): Codec<UIFormElementConfig> 
=>
     .alternative("secret", codecForUiFormFieldSecret())
     .alternative("selectMultiple", codecForUiFormFieldSelectMultiple())
     .alternative("duration", codecForUiFormFieldDuration())
+    .alternative("durationText", codecForUiFormFieldDurationText())
     .alternative("selectOne", codecForUiFormFieldSelectOne())
     .alternative("text", codecForUiFormFieldText())
     .alternative("textArea", codecForUiFormFieldTextArea())
diff --git a/packages/web-util/src/forms/forms-utils.ts 
b/packages/web-util/src/forms/forms-utils.ts
index dc7d7d22f..ebf349de0 100644
--- a/packages/web-util/src/forms/forms-utils.ts
+++ b/packages/web-util/src/forms/forms-utils.ts
@@ -258,6 +258,19 @@ export function convertFormConfigToUiField(
           },
         } as UIFormField;
       }
+      case "durationText": {
+        return {
+          type: "durationText",
+          properties: {
+            ...converBaseFieldsProps(i18n_, config),
+            ...converInputFieldsProps(
+              form,
+              config,
+              getConverterByFieldType(config.type, config),
+            ),
+          },
+        } as UIFormField;
+      }
       case "toggle": {
         return {
           type: "toggle",
diff --git a/packages/web-util/src/forms/index.stories.ts 
b/packages/web-util/src/forms/index.stories.ts
index 5b62c512a..823e71fac 100644
--- a/packages/web-util/src/forms/index.stories.ts
+++ b/packages/web-util/src/forms/index.stories.ts
@@ -12,3 +12,4 @@ export * as a12 from "./fields/InputTextArea.stories.js";
 export * as a13 from "./fields/InputToggle.stories.js";
 export * as a14 from "./fields/InputSecret.stories.js";
 export * as a15 from "./fields/InputDuration.stories.js";
+export * as a16 from "./fields/InputDurationText.stories.js";

-- 
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]