aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2024-04-29 14:40:10 -0300
committerSebastian <sebasjm@gmail.com>2024-04-29 14:40:10 -0300
commit50442d335e1332bbb2457bd02b973edff7845a31 (patch)
treeec5b9754473ccb54adff088b5159b69ecd9b8a15
parent4ed3a05fdd97b08085e5a390963f9810b93a75fd (diff)
downloadwallet-core-50442d335e1332bbb2457bd02b973edff7845a31.tar.gz
wallet-core-50442d335e1332bbb2457bd02b973edff7845a31.tar.bz2
wallet-core-50442d335e1332bbb2457bd02b973edff7845a31.zip
allow field by props
-rw-r--r--packages/web-util/src/forms/FormProvider.tsx45
-rw-r--r--packages/web-util/src/forms/InputAbsoluteTime.tsx55
-rw-r--r--packages/web-util/src/forms/InputAmount.tsx12
-rw-r--r--packages/web-util/src/forms/InputArray.tsx29
-rw-r--r--packages/web-util/src/forms/InputChoiceHorizontal.tsx22
-rw-r--r--packages/web-util/src/forms/InputChoiceStacked.tsx14
-rw-r--r--packages/web-util/src/forms/InputFile.tsx21
-rw-r--r--packages/web-util/src/forms/InputLine.tsx30
-rw-r--r--packages/web-util/src/forms/InputSelectMultiple.tsx174
-rw-r--r--packages/web-util/src/forms/InputSelectOne.tsx27
-rw-r--r--packages/web-util/src/forms/InputToggle.tsx50
-rw-r--r--packages/web-util/src/forms/forms.ts34
-rw-r--r--packages/web-util/src/forms/useField.ts30
13 files changed, 321 insertions, 222 deletions
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<T extends object> {
value: MutableRef<Partial<T>>;
@@ -17,8 +14,7 @@ export interface FormType<T extends object> {
computeFormState?: (v: Partial<T>) => FormState<T>;
}
-//@ts-ignore
-export const FormContext = createContext<FormType<any>>({});
+export const FormContext = createContext<FormType<any>| undefined>(undefined);
/**
* Map of {[field]:FieldUIOptions}
@@ -26,21 +22,21 @@ export const FormContext = createContext<FormType<any>>({});
* - any native (string, number, etc...)
* - absoluteTime
* - amountJson
- *
- * except for:
+ *
+ * except for:
* - object => recurse into
* - array => behavior result and element field
*/
export type FormState<T extends object | undefined> = {
[field in keyof T]?: T[field] extends AbsoluteTime
- ? FieldUIOptions
- : T[field] extends AmountJson
- ? FieldUIOptions
- : T[field] extends Array<infer P extends object>
- ? InputArrayFieldState<P>
- : T[field] extends (object | undefined)
- ? FormState<T[field]>
- : FieldUIOptions;
+ ? FieldUIOptions
+ : T[field] extends AmountJson
+ ? FieldUIOptions
+ : T[field] extends Array<infer P extends object>
+ ? InputArrayFieldState<P>
+ : T[field] extends object | undefined
+ ? FormState<T[field]>
+ : 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<T extends object, K extends keyof T> extends FieldUIOptions {
-
+export interface UIFormProps<T extends object, K extends keyof T>
+ extends FieldUIOptions {
// property name of the object
name: K;
@@ -80,8 +76,16 @@ export interface UIFormProps<T extends object, K extends keyof T> extends FieldU
// converter to string and back
converter?: StringConverter<T[K]>;
+
+ 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<P extends object> extends FieldUIOptions {
export type FormProviderProps<T extends object> = Omit<FormType<T>, "value"> & {
onSubmit?: (v: Partial<T>, s: FormState<T> | undefined) => void;
children?: ComponentChildren;
-}
+};
export function FormProvider<T extends object>({
children,
@@ -119,7 +123,6 @@ export function FormProvider<T extends object>({
computeFormState,
readOnly,
}: FormProviderProps<T>): VNode {
-
const [state, setState] = useState<Partial<T>>(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<T extends object, K extends keyof T>(
props: { pattern?: string } & UIFormProps<T, K>,
): VNode {
const pattern = props.pattern ?? "dd/MM/yyyy";
- const [open, setOpen] = useState(false)
- const { value, onChange } = useField<T, K>(props.name);
+ const [open, setOpen] = useState(false);
+
+ //FIXME: remove deprecated
+ const fieldCtx = useField<T, K>(props.name);
+ const { value, onChange } =
+ props.handler ?? fieldCtx ?? noHandlerPropsAndNoContextForField(props.name);
return (
<Fragment>
-
<InputLine<T, K>
type="text"
after={{
type: "button",
onClick: () => {
- setOpen(true)
+ setOpen(true);
},
// icon: <CalendarIcon class="h-6 w-6" />,
children: (
- <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
- <path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5" />
- </svg>)
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ viewBox="0 0 24 24"
+ stroke-width="1.5"
+ stroke="currentColor"
+ class="w-6 h-6"
+ >
+ <path
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5"
+ />
+ </svg>
+ ),
}}
converter={{
//@ts-ignore
@@ -53,15 +68,17 @@ export function InputAbsoluteTime<T extends object, K extends keyof T>(
}}
{...props}
/>
- {open &&
+ {open && (
<Dialog onClose={() => setOpen(false)}>
- <Calendar value={value as AbsoluteTime ?? AbsoluteTime.now()}
+ <Calendar
+ value={(value as AbsoluteTime) ?? AbsoluteTime.now()}
onChange={(v) => {
- onChange(v as any)
- setOpen(false)
- }} />
+ onChange(v as any);
+ setOpen(false);
+ }}
+ />
</Dialog>
- }
+ )}
{/* {open &&
<Dialog onClose={() => setOpen(false)} >
<TimePicker value={value as AbsoluteTime ?? AbsoluteTime.now()}
diff --git a/packages/web-util/src/forms/InputAmount.tsx b/packages/web-util/src/forms/InputAmount.tsx
index 7a8c08f76..31e83350e 100644
--- a/packages/web-util/src/forms/InputAmount.tsx
+++ b/packages/web-util/src/forms/InputAmount.tsx
@@ -3,11 +3,15 @@ import { VNode, h } from "preact";
import { UIFormProps } from "./FormProvider.js";
import { InputLine } from "./InputLine.js";
import { useField } from "./useField.js";
+import { noHandlerPropsAndNoContextForField } from "./InputArray.js";
export function InputAmount<T extends object, K extends keyof T>(
props: { currency?: string } & UIFormProps<T, K>,
): VNode {
- const { value } = useField<T, K>(props.name);
+ //FIXME: remove deprecated
+ const fieldCtx = useField<T, K>(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<T extends object, K extends keyof T>(
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<T extends object, K extends keyof T>(
props: {
fields: UIFormField[];
@@ -78,7 +86,15 @@ export function InputArray<T extends object, K extends keyof T>(
} & UIFormProps<T, K>,
): VNode {
const { fields, labelField, name, label, required, tooltip } = props;
- const { value, onChange, state } = useField<T, K>(name);
+ // const { value, onChange, state } = useField<T, K>(name);
+ //FIXME: remove deprecated
+ const fieldCtx = useField<T, K>(props.name);
+ if (!props.handler && !fieldCtx) {
+ throw Error("");
+ }
+ const { value, onChange, state } =
+ props.handler ?? fieldCtx ?? noHandlerPropsAndNoContextForField(props.name);
+
const list = (value ?? []) as Array<Record<string, string | undefined>>;
const [selectedIndex, setSelected] = useState<number | undefined>(undefined);
const selected =
@@ -97,6 +113,7 @@ export function InputArray<T extends object, K extends keyof T>(
return (
<Option
label={v[labelField] as TranslatedString}
+ key={idx}
isSelected={selectedIndex === idx}
isLast={idx === list.length - 1}
disabled={selectedIndex !== undefined && selectedIndex !== idx}
@@ -107,7 +124,7 @@ export function InputArray<T extends object, K extends keyof T>(
/>
);
})}
- {!state.disabled &&
+ {!state.disabled && (
<div class="pt-2">
<Option
label={"Add..." as TranslatedString}
@@ -124,7 +141,7 @@ export function InputArray<T extends object, K extends keyof T>(
}}
/>
</div>
- }
+ )}
</div>
{selectedIndex !== undefined && (
/**
@@ -145,13 +162,13 @@ export function InputArray<T extends object, K extends keyof T>(
onSubmit={(v) => {
const newValue = [...list];
newValue.splice(selectedIndex, 1, v);
- onChange(newValue as T[K]);
+ onChange(newValue as any);
setSelected(undefined);
}}
onUpdate={(v) => {
const newValue = [...list];
newValue.splice(selectedIndex, 1, v);
- onChange(newValue as T[K]);
+ onChange(newValue as any);
}}
>
<div class="px-4 py-6">
@@ -170,7 +187,7 @@ export function InputArray<T extends object, K extends keyof T>(
onClick={() => {
const newValue = [...list];
newValue.splice(selectedIndex, 1);
- onChange(newValue as T[K]);
+ onChange(newValue as any);
setSelected(undefined);
}}
class="block rounded-md bg-red-600 px-3 py-2 text-center text-sm text-white shadow-sm hover:bg-red-500 "
diff --git a/packages/web-util/src/forms/InputChoiceHorizontal.tsx b/packages/web-util/src/forms/InputChoiceHorizontal.tsx
index 778b73c75..82a7c3115 100644
--- a/packages/web-util/src/forms/InputChoiceHorizontal.tsx
+++ b/packages/web-util/src/forms/InputChoiceHorizontal.tsx
@@ -3,6 +3,7 @@ import { Fragment, VNode, h } from "preact";
import { UIFormProps } from "./FormProvider.js";
import { LabelWithTooltipMaybeRequired } from "./InputLine.js";
import { useField } from "./useField.js";
+import { noHandlerPropsAndNoContextForField } from "./InputArray.js";
export interface ChoiceH<V> {
label: TranslatedString;
@@ -14,19 +15,11 @@ export function InputChoiceHorizontal<T extends object, K extends keyof T>(
choices: ChoiceH<T[K]>[];
} & UIFormProps<T, K>,
): VNode {
- const {
- choices,
- name,
- label,
- tooltip,
- help,
- placeholder,
- required,
- before,
- after,
- converter,
- } = props;
- const { value, onChange, state, isDirty } = useField<T, K>(name);
+ const { choices, label, tooltip, help, required } = props;
+ //FIXME: remove deprecated
+ const fieldCtx = useField<T, K>(props.name);
+ const { value, onChange, state } =
+ props.handler ?? fieldCtx ?? noHandlerPropsAndNoContextForField(props.name);
if (state.hidden) {
return <Fragment />;
}
@@ -62,12 +55,13 @@ export function InputChoiceHorizontal<T extends object, K extends keyof T>(
return (
<button
type="button"
+ key={idx}
disabled={state.disabled}
label={choice.label}
class={clazz}
onClick={(e) => {
onChange(
- (value === choice.value ? undefined : choice.value) as T[K],
+ (value === choice.value ? undefined : choice.value) as any,
);
}}
>
diff --git a/packages/web-util/src/forms/InputChoiceStacked.tsx b/packages/web-util/src/forms/InputChoiceStacked.tsx
index 234bb2255..1928f4365 100644
--- a/packages/web-util/src/forms/InputChoiceStacked.tsx
+++ b/packages/web-util/src/forms/InputChoiceStacked.tsx
@@ -3,6 +3,7 @@ import { Fragment, VNode, h } from "preact";
import { UIFormProps } from "./FormProvider.js";
import { LabelWithTooltipMaybeRequired } from "./InputLine.js";
import { useField } from "./useField.js";
+import { noHandlerPropsAndNoContextForField } from "./InputArray.js";
export interface ChoiceS<V> {
label: TranslatedString;
@@ -27,7 +28,12 @@ export function InputChoiceStacked<T extends object, K extends keyof T>(
after,
converter,
} = props;
- const { value, onChange, state, isDirty } = useField<T, K>(name);
+
+ //FIXME: remove deprecated
+ const fieldCtx = useField<T, K>(props.name);
+ const { value, onChange, state } =
+ props.handler ?? fieldCtx ?? noHandlerPropsAndNoContextForField(props.name);
+
if (state.hidden) {
return <Fragment />;
}
@@ -41,7 +47,7 @@ export function InputChoiceStacked<T extends object, K extends keyof T>(
/>
<fieldset class="mt-2">
<div class="space-y-4">
- {choices.map((choice) => {
+ {choices.map((choice, idx) => {
// const currentValue = !converter
// ? choice.value
// : converter.fromStringUI(choice.value) ?? "";
@@ -56,7 +62,7 @@ export function InputChoiceStacked<T extends object, K extends keyof T>(
}
return (
- <label class={clazz}>
+ <label key={idx} class={clazz}>
<input
type="radio"
name="server-size"
@@ -71,7 +77,7 @@ export function InputChoiceStacked<T extends object, K extends keyof T>(
onChange(
(value === choice.value
? undefined
- : choice.value) as T[K],
+ : choice.value) as any,
);
}}
class="sr-only"
diff --git a/packages/web-util/src/forms/InputFile.tsx b/packages/web-util/src/forms/InputFile.tsx
index 6337d0902..6147eae59 100644
--- a/packages/web-util/src/forms/InputFile.tsx
+++ b/packages/web-util/src/forms/InputFile.tsx
@@ -2,6 +2,7 @@ import { Fragment, VNode, h } from "preact";
import { UIFormProps } from "./FormProvider.js";
import { LabelWithTooltipMaybeRequired } from "./InputLine.js";
import { useField } from "./useField.js";
+import { noHandlerPropsAndNoContextForField } from "./InputArray.js";
export function InputFile<T extends object, K extends keyof T>(
props: { maxBites: number; accept?: string } & UIFormProps<T, K>,
@@ -16,8 +17,12 @@ export function InputFile<T extends object, K extends keyof T>(
maxBites,
accept,
} = props;
- const { value, onChange, state } = useField<T, K>(name);
- const help = propsHelp ?? state.help
+ //FIXME: remove deprecated
+ const fieldCtx = useField<T, K>(props.name);
+ const { value, onChange, state } =
+ props.handler ?? fieldCtx ?? noHandlerPropsAndNoContextForField(props.name);
+
+ const help = propsHelp ?? state.help;
if (state.hidden) {
return <div />;
}
@@ -43,7 +48,7 @@ export function InputFile<T extends object, K extends keyof T>(
clip-rule="evenodd"
/>
</svg>
- {!state.disabled &&
+ {!state.disabled && (
<div class="my-2 flex text-sm leading-6 text-gray-600">
<label
for="file-upload"
@@ -71,14 +76,16 @@ export function InputFile<T extends object, K extends keyof T>(
"",
),
);
- return onChange(`data:${f[0].type};base64,${b64}` as any);
+ return onChange(
+ `data:${f[0].type};base64,${b64}` as any,
+ );
});
}}
/>
</label>
{/* <p class="pl-1">or drag and drop</p> */}
</div>
- }
+ )}
</div>
</div>
) : (
@@ -88,7 +95,7 @@ export function InputFile<T extends object, K extends keyof T>(
class=" h-24 w-full object-cover relative"
/>
- {!state.disabled &&
+ {!state.disabled && (
<div
class="opacity-0 hover:opacity-70 duration-300 absolute rounded-lg border inset-0 z-10 flex justify-center text-xl items-center bg-black text-white cursor-pointer "
onClick={() => {
@@ -97,7 +104,7 @@ export function InputFile<T extends object, K extends keyof T>(
>
Clear
</div>
- }
+ )}
</div>
)}
{help && <p class="text-xs leading-5 text-gray-600 mt-2">{help}</p>}
diff --git a/packages/web-util/src/forms/InputLine.tsx b/packages/web-util/src/forms/InputLine.tsx
index b8879f9ec..c7f69dd8a 100644
--- a/packages/web-util/src/forms/InputLine.tsx
+++ b/packages/web-util/src/forms/InputLine.tsx
@@ -3,6 +3,7 @@ import { ComponentChildren, Fragment, VNode, h } from "preact";
import { useEffect, useState } from "preact/hooks";
import { UIFormProps } from "./FormProvider.js";
import { useField } from "./useField.js";
+import { noHandlerPropsAndNoContextForField } from "./InputArray.js";
//@ts-ignore
const TooltipIcon = (
@@ -78,7 +79,11 @@ function InputWrapper<T extends object, K extends keyof T>({
error,
disabled,
required,
-}: { error?: string; disabled: boolean, children: ComponentChildren } & UIFormProps<T, K>): VNode {
+}: {
+ error?: string;
+ disabled: boolean;
+ children: ComponentChildren;
+} & UIFormProps<T, K>): VNode {
return (
<div class="sm:col-span-6">
<LabelWithTooltipMaybeRequired
@@ -156,19 +161,22 @@ export function InputLine<T extends object, K extends keyof T>(
props: { type: InputType } & UIFormProps<T, K>,
): VNode {
const { name, placeholder, before, after, converter, type } = props;
- const { value, onChange, state, isDirty } = useField<T, K>(name);
+ //FIXME: remove deprecated
+ const fieldCtx = useField<T, K>(props.name);
+ const { value, onChange, state } =
+ props.handler ?? fieldCtx ?? noHandlerPropsAndNoContextForField(props.name);
- const [text, setText] = useState("")
+ const [text, setText] = useState("");
const fromString: (s: string) => any =
converter?.fromStringUI ?? defaultFromString;
const toString: (s: any) => string = converter?.toStringUI ?? defaultToString;
useEffect(() => {
- const newValue = toString(value)
+ const newValue = toString(value);
if (newValue) {
- setText(newValue)
+ setText(newValue);
}
- }, [value])
+ }, [value]);
if (state.hidden) return <div />;
@@ -206,7 +214,7 @@ export function InputLine<T extends object, K extends keyof T>(
}
}
}
- const showError = isDirty && state.error;
+ const showError = value !== undefined && state.error;
if (showError) {
clazz +=
" text-red-900 ring-red-300 placeholder:text-red-300 focus:ring-red-500";
@@ -242,15 +250,17 @@ export function InputLine<T extends object, K extends keyof T>(
}
return (
- <InputWrapper<T, K> {...props}
+ <InputWrapper<T, K>
+ {...props}
help={props.help ?? state.help}
- disabled={state.disabled ?? false} error={showError ? state.error : undefined}
+ disabled={state.disabled ?? false}
+ error={showError ? state.error : undefined}
>
<input
name={String(name)}
type={type}
onChange={(e) => {
- setText(e.currentTarget.value)
+ setText(e.currentTarget.value);
}}
placeholder={placeholder ? placeholder : undefined}
value={text}
diff --git a/packages/web-util/src/forms/InputSelectMultiple.tsx b/packages/web-util/src/forms/InputSelectMultiple.tsx
index a67eb23b7..972389cb3 100644
--- a/packages/web-util/src/forms/InputSelectMultiple.tsx
+++ b/packages/web-util/src/forms/InputSelectMultiple.tsx
@@ -4,6 +4,7 @@ import { UIFormProps } from "./FormProvider.js";
import { ChoiceS } from "./InputChoiceStacked.js";
import { LabelWithTooltipMaybeRequired } from "./InputLine.js";
import { useField } from "./useField.js";
+import { noHandlerPropsAndNoContextForField } from "./InputArray.js";
export function InputSelectMultiple<T extends object, K extends keyof T>(
props: {
@@ -12,23 +13,28 @@ export function InputSelectMultiple<T extends object, K extends keyof T>(
max?: number;
} & UIFormProps<T, K>,
): VNode {
- const { name, label, choices, placeholder, tooltip, required, unique, max } =
- props;
- const { value, onChange, state } = useField<T, K>(name);
+ const { label, choices, placeholder, tooltip, required, unique, max } = props;
+ //FIXME: remove deprecated
+ const fieldCtx = useField<T, K>(props.name);
+ const { value, onChange, state } =
+ props.handler ?? fieldCtx ?? noHandlerPropsAndNoContextForField(props.name);
const [filter, setFilter] = useState<string | undefined>(undefined);
const regex = new RegExp(`.*${filter}.*`, "i");
- const choiceMap = choices.reduce((prev, curr) => {
- return { ...prev, [curr.value as string]: curr.label };
- }, {} as Record<string, string>);
+ const choiceMap = choices.reduce(
+ (prev, curr) => {
+ return { ...prev, [curr.value as string]: curr.label };
+ },
+ {} as Record<string, string>,
+ );
const list = (value ?? []) as string[];
const filteredChoices =
filter === undefined
? undefined
: choices.filter((v) => {
- return regex.test(v.label);
- });
+ return regex.test(v.label);
+ });
return (
<div class="sm:col-span-6">
<LabelWithTooltipMaybeRequired
@@ -38,7 +44,10 @@ export function InputSelectMultiple<T extends object, K extends keyof T>(
/>
{list.map((v, idx) => {
return (
- <span class="inline-flex items-center gap-x-0.5 rounded-md bg-gray-100 p-1 mr-2 text-xs font-medium text-gray-600">
+ <span
+ key={idx}
+ class="inline-flex items-center gap-x-0.5 rounded-md bg-gray-100 p-1 mr-2 text-xs font-medium text-gray-600"
+ >
{choiceMap[v]}
<button
type="button"
@@ -46,7 +55,7 @@ export function InputSelectMultiple<T extends object, K extends keyof T>(
onClick={() => {
const newValue = [...list];
newValue.splice(idx, 1);
- onChange(newValue as T[K]);
+ onChange(newValue as any);
setFilter(undefined);
}}
class="group relative h-5 w-5 rounded-sm hover:bg-gray-500/20"
@@ -64,91 +73,94 @@ export function InputSelectMultiple<T extends object, K extends keyof T>(
);
})}
- {!state.disabled && <div class="relative mt-2">
- <input
- id="combobox"
- type="text"
- value={filter ?? ""}
- onChange={(e) => {
- setFilter(e.currentTarget.value);
- }}
- placeholder={placeholder}
- class="w-full rounded-md border-0 bg-white py-1.5 pl-3 pr-12 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
- role="combobox"
- aria-controls="options"
- aria-expanded="false"
- />
- <button
- type="button"
- disabled={state.disabled}
- onClick={() => {
- setFilter(filter === undefined ? "" : undefined);
- }}
- class="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none"
- >
- <svg
- class="h-5 w-5 text-gray-400"
- viewBox="0 0 20 20"
- fill="currentColor"
- aria-hidden="true"
+ {!state.disabled && (
+ <div class="relative mt-2">
+ <input
+ id="combobox"
+ type="text"
+ value={filter ?? ""}
+ onChange={(e) => {
+ setFilter(e.currentTarget.value);
+ }}
+ placeholder={placeholder}
+ class="w-full rounded-md border-0 bg-white py-1.5 pl-3 pr-12 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
+ role="combobox"
+ aria-controls="options"
+ aria-expanded="false"
+ />
+ <button
+ type="button"
+ disabled={state.disabled}
+ onClick={() => {
+ setFilter(filter === undefined ? "" : undefined);
+ }}
+ class="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none"
>
- <path
- fill-rule="evenodd"
- d="M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z"
- clip-rule="evenodd"
- />
- </svg>
- </button>
+ <svg
+ class="h-5 w-5 text-gray-400"
+ viewBox="0 0 20 20"
+ fill="currentColor"
+ aria-hidden="true"
+ >
+ <path
+ fill-rule="evenodd"
+ d="M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z"
+ clip-rule="evenodd"
+ />
+ </svg>
+ </button>
- {filteredChoices !== undefined && (
- <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"
- >
- {filteredChoices.map((v, idx) => {
- return (
- <li
- class="relative cursor-pointer select-none py-2 pl-3 pr-9 text-gray-900 hover:text-white hover:bg-indigo-600"
- id="option-0"
- role="option"
- onClick={() => {
- setFilter(undefined);
- if (unique && list.indexOf(v.value as string) !== -1) {
- return;
- }
- if (max !== undefined && list.length >= max) {
- return;
- }
- const newValue = [...list];
- newValue.splice(0, 0, v.value as string);
- onChange(newValue as T[K]);
- }}
+ {filteredChoices !== undefined && (
+ <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"
+ >
+ {filteredChoices.map((v, idx) => {
+ return (
+ <li
+ key={idx}
+ class="relative cursor-pointer select-none py-2 pl-3 pr-9 text-gray-900 hover:text-white hover:bg-indigo-600"
+ id="option-0"
+ role="option"
+ onClick={() => {
+ setFilter(undefined);
+ if (unique && list.indexOf(v.value as string) !== -1) {
+ return;
+ }
+ if (max !== undefined && list.length >= max) {
+ return;
+ }
+ const newValue = [...list];
+ newValue.splice(0, 0, v.value as string);
+ onChange(newValue as any);
+ }}
- // tabindex="-1"
- >
- {/* <!-- Selected: "font-semibold" --> */}
- <span class="block truncate">{v.label}</span>
+ // 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>
- );
- })}
+ </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>}
+ {/* <!-- More items... --> */}
+ </ul>
+ )}
+ </div>
+ )}
</div>
);
}
diff --git a/packages/web-util/src/forms/InputSelectOne.tsx b/packages/web-util/src/forms/InputSelectOne.tsx
index d100b079d..26f887b08 100644
--- a/packages/web-util/src/forms/InputSelectOne.tsx
+++ b/packages/web-util/src/forms/InputSelectOne.tsx
@@ -4,27 +4,35 @@ import { UIFormProps } from "./FormProvider.js";
import { ChoiceS } from "./InputChoiceStacked.js";
import { LabelWithTooltipMaybeRequired } from "./InputLine.js";
import { useField } from "./useField.js";
+import { noHandlerPropsAndNoContextForField } from "./InputArray.js";
export function InputSelectOne<T extends object, K extends keyof T>(
props: {
choices: ChoiceS<T[K]>[];
} & UIFormProps<T, K>,
): VNode {
- const { name, label, choices, placeholder, tooltip, required } = props;
- const { value, onChange } = useField<T, K>(name);
+ const { label, choices, placeholder, tooltip, required } = props;
+ //FIXME: remove deprecated
+ const fieldCtx = useField<T, K>(props.name);
+ const { value, onChange } =
+ props.handler ?? fieldCtx ?? noHandlerPropsAndNoContextForField(props.name);
+
const [filter, setFilter] = useState<string | undefined>(undefined);
const regex = new RegExp(`.*${filter}.*`, "i");
- const choiceMap = choices.reduce((prev, curr) => {
- return { ...prev, [curr.value as string]: curr.label };
- }, {} as Record<string, string>);
+ const choiceMap = choices.reduce(
+ (prev, curr) => {
+ return { ...prev, [curr.value as string]: curr.label };
+ },
+ {} as Record<string, string>,
+ );
const filteredChoices =
filter === undefined
? undefined
: choices.filter((v) => {
- return regex.test(v.label);
- });
+ return regex.test(v.label);
+ });
return (
<div class="sm:col-span-6">
<LabelWithTooltipMaybeRequired
@@ -97,15 +105,16 @@ export function InputSelectOne<T extends object, K extends keyof T>(
{filteredChoices.map((v, idx) => {
return (
<li
+ key={idx}
class="relative cursor-pointer select-none py-2 pl-3 pr-9 text-gray-900 hover:text-white hover:bg-indigo-600"
id="option-0"
role="option"
onClick={() => {
setFilter(undefined);
- onChange(v.value as T[K]);
+ onChange(v.value as any);
}}
- // tabindex="-1"
+ // tabindex="-1"
>
{/* <!-- Selected: "font-semibold" --> */}
<span class="block truncate">{v.label}</span>
diff --git a/packages/web-util/src/forms/InputToggle.tsx b/packages/web-util/src/forms/InputToggle.tsx
index 56b29c502..c56efec3a 100644
--- a/packages/web-util/src/forms/InputToggle.tsx
+++ b/packages/web-util/src/forms/InputToggle.tsx
@@ -2,6 +2,7 @@ import { VNode, h } from "preact";
import { UIFormProps } from "./FormProvider.js";
import { LabelWithTooltipMaybeRequired } from "./InputLine.js";
import { useField } from "./useField.js";
+import { noHandlerPropsAndNoContextForField } from "./InputArray.js";
export function InputToggle<T extends object, K extends keyof T>(
props: UIFormProps<T, K>,
@@ -17,22 +18,39 @@ export function InputToggle<T extends object, K extends keyof T>(
after,
converter,
} = props;
- const { value, onChange, state, isDirty } = useField<T, K>(name);
+ //FIXME: remove deprecated
+ const fieldCtx = useField<T, K>(props.name);
+ const { value, onChange } =
+ props.handler ?? fieldCtx ?? noHandlerPropsAndNoContextForField(props.name);
- const isOn = !!value
- return <div class="sm:col-span-6">
- <div class="flex items-center justify-between">
- <LabelWithTooltipMaybeRequired
- label={label}
- required={required}
- tooltip={tooltip}
- />
- <button type="button" data-enabled={isOn}
- class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2"
- role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description"
- onClick={() => { onChange(!isOn as any); }}>
- <span aria-hidden="true" data-enabled={isOn} class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
- </button>
+ const isOn = !!value;
+ return (
+ <div class="sm:col-span-6">
+ <div class="flex items-center justify-between">
+ <LabelWithTooltipMaybeRequired
+ label={label}
+ required={required}
+ tooltip={tooltip}
+ />
+ <button
+ type="button"
+ data-enabled={isOn}
+ class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2"
+ role="switch"
+ aria-checked="false"
+ aria-labelledby="availability-label"
+ aria-describedby="availability-description"
+ onClick={() => {
+ onChange(!isOn as any);
+ }}
+ >
+ <span
+ aria-hidden="true"
+ data-enabled={isOn}
+ class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
+ ></span>
+ </button>
+ </div>
</div>
- </div>
+ );
}
diff --git a/packages/web-util/src/forms/forms.ts b/packages/web-util/src/forms/forms.ts
index 3b8620bfb..1ad3508ae 100644
--- a/packages/web-util/src/forms/forms.ts
+++ b/packages/web-util/src/forms/forms.ts
@@ -109,26 +109,26 @@ export function RenderAllFieldsByUiConfig({
);
}
-type FormSet<T extends object> = {
- Provider: typeof FormProvider<T>;
- InputLine: <K extends keyof T>() => typeof InputLine<T, K>;
- InputChoiceHorizontal: <K extends keyof T>() => typeof InputChoiceHorizontal<T, K>;
-};
+// type FormSet<T extends object> = {
+// Provider: typeof FormProvider<T>;
+// InputLine: <K extends keyof T>() => typeof InputLine<T, K>;
+// InputChoiceHorizontal: <K extends keyof T>() => typeof InputChoiceHorizontal<T, K>;
+// };
/**
* Helper function that created a typed object.
*
* @returns
*/
-export function createNewForm<T extends object>() {
- const res: FormSet<T> = {
- Provider: FormProvider,
- InputLine: () => InputLine,
- InputChoiceHorizontal: () => InputChoiceHorizontal,
- };
- return {
- Provider: res.Provider,
- InputLine: res.InputLine(),
- InputChoiceHorizontal: res.InputChoiceHorizontal(),
- };
-}
+// export function createNewForm<T extends object>() {
+// const res: FormSet<T> = {
+// Provider: FormProvider,
+// InputLine: () => InputLine,
+// InputChoiceHorizontal: () => InputChoiceHorizontal,
+// };
+// return {
+// Provider: res.Provider,
+// InputLine: res.InputLine(),
+// InputChoiceHorizontal: res.InputChoiceHorizontal(),
+// };
+// }
diff --git a/packages/web-util/src/forms/useField.ts b/packages/web-util/src/forms/useField.ts
index eed8cebea..fad53ebac 100644
--- a/packages/web-util/src/forms/useField.ts
+++ b/packages/web-util/src/forms/useField.ts
@@ -8,15 +8,25 @@ export interface InputFieldHandler<Type> {
isDirty: boolean;
}
+/**
+ * @depreacted removing this so we don't depend on context to create a form
+ * @param name
+ * @returns
+ */
export function useField<T extends object, K extends keyof T>(
name: K,
-): InputFieldHandler<T[K]> {
+): InputFieldHandler<T[K]> | undefined {
+ const ctx = useContext(FormContext);
+ if (!ctx) {
+ //no context, can't be used
+ return undefined;
+ }
const {
value: formValue,
computeFormState,
onUpdate: notifyUpdate,
readOnly: readOnlyForm,
- } = useContext(FormContext);
+ } = ctx
type P = typeof name;
type V = T[P];
@@ -24,7 +34,7 @@ export function useField<T extends object, K extends keyof T>(
const fieldValue = readField(formValue.current, String(name)) as V;
// console.log("USE FIELD", String(name), formValue.current, fieldValue);
- const [currentValue, setCurrentValue] = useState<any | undefined>(fieldValue);
+ // const [currentValue, setCurrentValue] = useState<any | undefined>(fieldValue);
const fieldState =
readField<Partial<FieldUIOptions>>(formState, String(name)) ?? {};
@@ -38,7 +48,7 @@ export function useField<T extends object, K extends keyof T>(
};
function onChange(value: V): void {
- setCurrentValue(value);
+ // setCurrentValue(value);
formValue.current = setValueDeeper(
formValue.current,
String(name).split("."),
@@ -52,7 +62,7 @@ export function useField<T extends object, K extends keyof T>(
return {
value: fieldValue,
onChange,
- isDirty: currentValue !== undefined,
+ isDirty: fieldValue !== undefined,
state,
};
}
@@ -67,18 +77,8 @@ export function useField<T extends object, K extends keyof T>(
function readField<T>(
object: any,
name: string,
- debug?: boolean,
): T | undefined {
return name.split(".").reduce((prev, current) => {
- if (debug) {
- console.log(
- "READ",
- name,
- prev,
- current,
- prev ? prev[current] : undefined,
- );
- }
return prev ? prev[current] : undefined;
}, object);
}