From 50442d335e1332bbb2457bd02b973edff7845a31 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 29 Apr 2024 14:40:10 -0300 Subject: allow field by props --- packages/web-util/src/forms/FormProvider.tsx | 45 +++--- packages/web-util/src/forms/InputAbsoluteTime.tsx | 55 ++++--- packages/web-util/src/forms/InputAmount.tsx | 12 +- packages/web-util/src/forms/InputArray.tsx | 29 +++- .../web-util/src/forms/InputChoiceHorizontal.tsx | 22 +-- packages/web-util/src/forms/InputChoiceStacked.tsx | 14 +- packages/web-util/src/forms/InputFile.tsx | 21 ++- packages/web-util/src/forms/InputLine.tsx | 30 ++-- .../web-util/src/forms/InputSelectMultiple.tsx | 174 +++++++++++---------- packages/web-util/src/forms/InputSelectOne.tsx | 27 ++-- packages/web-util/src/forms/InputToggle.tsx | 50 ++++-- packages/web-util/src/forms/forms.ts | 34 ++-- packages/web-util/src/forms/useField.ts | 30 ++-- 13 files changed, 321 insertions(+), 222 deletions(-) (limited to 'packages/web-util') diff --git a/packages/web-util/src/forms/FormProvider.tsx b/packages/web-util/src/forms/FormProvider.tsx index f4616525b..de19a6315 100644 --- a/packages/web-util/src/forms/FormProvider.tsx +++ b/packages/web-util/src/forms/FormProvider.tsx @@ -4,10 +4,7 @@ import { TranslatedString, } from "@gnu-taler/taler-util"; import { ComponentChildren, VNode, createContext, h } from "preact"; -import { - MutableRef, - useState -} from "preact/hooks"; +import { MutableRef, useState } from "preact/hooks"; export interface FormType { value: MutableRef>; @@ -17,8 +14,7 @@ export interface FormType { computeFormState?: (v: Partial) => FormState; } -//@ts-ignore -export const FormContext = createContext>({}); +export const FormContext = createContext| undefined>(undefined); /** * Map of {[field]:FieldUIOptions} @@ -26,21 +22,21 @@ export const FormContext = createContext>({}); * - any native (string, number, etc...) * - absoluteTime * - amountJson - * - * except for: + * + * except for: * - object => recurse into * - array => behavior result and element field */ export type FormState = { [field in keyof T]?: T[field] extends AbsoluteTime - ? FieldUIOptions - : T[field] extends AmountJson - ? FieldUIOptions - : T[field] extends Array - ? InputArrayFieldState

- : T[field] extends (object | undefined) - ? FormState - : FieldUIOptions; + ? FieldUIOptions + : T[field] extends AmountJson + ? FieldUIOptions + : T[field] extends Array + ? InputArrayFieldState

+ : T[field] extends object | undefined + ? FormState + : FieldUIOptions; }; /** @@ -63,13 +59,13 @@ export type FieldUIOptions = { /* show a mark as required*/ required?: boolean; -} +}; /** * properties only to be defined on design time */ -export interface UIFormProps extends FieldUIOptions { - +export interface UIFormProps + extends FieldUIOptions { // property name of the object name: K; @@ -80,8 +76,16 @@ export interface UIFormProps extends FieldU // converter to string and back converter?: StringConverter; + + handler?: UIField; } +export type UIField = { + value: string | undefined; + onChange: (s: string) => void; + state: FieldUIOptions; +}; + export interface IconAddon { type: "icon"; icon: VNode; @@ -109,7 +113,7 @@ export interface InputArrayFieldState

extends FieldUIOptions { export type FormProviderProps = Omit, "value"> & { onSubmit?: (v: Partial, s: FormState | undefined) => void; children?: ComponentChildren; -} +}; export function FormProvider({ children, @@ -119,7 +123,6 @@ export function FormProvider({ computeFormState, readOnly, }: FormProviderProps): VNode { - const [state, setState] = useState>(initial ?? {}); const value = { current: state }; const onUpdate = (v: typeof state) => { diff --git a/packages/web-util/src/forms/InputAbsoluteTime.tsx b/packages/web-util/src/forms/InputAbsoluteTime.tsx index ee18e5592..772ab1813 100644 --- a/packages/web-util/src/forms/InputAbsoluteTime.tsx +++ b/packages/web-util/src/forms/InputAbsoluteTime.tsx @@ -1,35 +1,50 @@ import { AbsoluteTime } from "@gnu-taler/taler-util"; -import { InputLine } from "./InputLine.js"; -import { Fragment, VNode, h } from "preact"; import { format, parse } from "date-fns"; -import { Dialog } from "./Dialog.js"; -import { Calendar } from "./Calendar.js"; +import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; -import { useField } from "./useField.js"; +import { Calendar } from "./Calendar.js"; +import { Dialog } from "./Dialog.js"; import { UIFormProps } from "./FormProvider.js"; -import { TimePicker } from "./TimePicker.js"; +import { InputLine } from "./InputLine.js"; +import { useField } from "./useField.js"; +import { noHandlerPropsAndNoContextForField } from "./InputArray.js"; export function InputAbsoluteTime( props: { pattern?: string } & UIFormProps, ): VNode { const pattern = props.pattern ?? "dd/MM/yyyy"; - const [open, setOpen] = useState(false) - const { value, onChange } = useField(props.name); + const [open, setOpen] = useState(false); + + //FIXME: remove deprecated + const fieldCtx = useField(props.name); + const { value, onChange } = + props.handler ?? fieldCtx ?? noHandlerPropsAndNoContextForField(props.name); return ( - type="text" after={{ type: "button", onClick: () => { - setOpen(true) + setOpen(true); }, // icon: , children: ( - - - ) + + + + ), }} converter={{ //@ts-ignore @@ -53,15 +68,17 @@ export function InputAbsoluteTime( }} {...props} /> - {open && + {open && (

setOpen(false)}> - { - onChange(v as any) - setOpen(false) - }} /> + onChange(v as any); + setOpen(false); + }} + /> - } + )} {/* {open && setOpen(false)} > ( props: { currency?: string } & UIFormProps, ): VNode { - const { value } = useField(props.name); + //FIXME: remove deprecated + const fieldCtx = useField(props.name); + const { value } = + props.handler ?? fieldCtx ?? noHandlerPropsAndNoContextForField(props.name); const currency = !value || !(value as any).currency ? props.currency @@ -22,8 +26,10 @@ export function InputAmount( converter={{ //@ts-ignore fromStringUI: (v): AmountJson => { - - return Amounts.parse(`${currency}:${v}`) ?? Amounts.zeroOfCurrency(currency); + return ( + Amounts.parse(`${currency}:${v}`) ?? + Amounts.zeroOfCurrency(currency) + ); }, //@ts-ignore toStringUI: (v: AmountJson) => { diff --git a/packages/web-util/src/forms/InputArray.tsx b/packages/web-util/src/forms/InputArray.tsx index 7d9a1b378..ac4617c8c 100644 --- a/packages/web-util/src/forms/InputArray.tsx +++ b/packages/web-util/src/forms/InputArray.tsx @@ -71,6 +71,14 @@ function Option({ ); } +export function noHandlerPropsAndNoContextForField( + field: string | number | symbol, +): never { + throw Error( + `Field ${field.toString()} doesn't have handler and is not in a form provider context.`, + ); +} + export function InputArray( props: { fields: UIFormField[]; @@ -78,7 +86,15 @@ export function InputArray( } & UIFormProps, ): VNode { const { fields, labelField, name, label, required, tooltip } = props; - const { value, onChange, state } = useField(name); + // const { value, onChange, state } = useField(name); + //FIXME: remove deprecated + const fieldCtx = useField(props.name); + if (!props.handler && !fieldCtx) { + throw Error(""); + } + const { value, onChange, state } = + props.handler ?? fieldCtx ?? noHandlerPropsAndNoContextForField(props.name); + const list = (value ?? []) as Array>; const [selectedIndex, setSelected] = useState(undefined); const selected = @@ -97,6 +113,7 @@ export function InputArray( return (