@engagebay/engagebay-form-module 1.0.7-beta.3 → 1.0.8-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/form/formfields/CheckboxField.tsx +30 -11
- package/src/form/formfields/DatePickerField.tsx +10 -9
- package/src/form/formfields/MultipleSelectField.tsx +17 -5
- package/src/form/schema/FormFieldSchema.ts +2 -0
- package/src/form/util/RenderListOptions.tsx +12 -7
- package/src/form/util/normalizeCustomFieldValues.ts +115 -0
package/package.json
CHANGED
|
@@ -1,32 +1,46 @@
|
|
|
1
1
|
import { RegisterOptions } from "react-hook-form";
|
|
2
2
|
import { useContext, useEffect, useState } from "react";
|
|
3
3
|
import { FormContext } from "../context/FormContext";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
FormFieldComponentPropSchema,
|
|
6
|
+
OutputFormatType,
|
|
7
|
+
} from "../schema/FormFieldSchema";
|
|
5
8
|
import { Checkbox, Field, Input, Label } from "@headlessui/react";
|
|
6
9
|
import React from "react";
|
|
7
10
|
import RenderFormField from "../util/RenderFormField";
|
|
8
11
|
import { handleChange, registerFormField } from "../util";
|
|
12
|
+
import { normalizeCheckboxBooleanValue, normalizeMultiSelectValue } from "../util/normalizeCustomFieldValues";
|
|
9
13
|
const CheckboxField: React.FC<FormFieldComponentPropSchema> = (
|
|
10
14
|
props: FormFieldComponentPropSchema
|
|
11
15
|
) => {
|
|
12
16
|
const formContext = useContext(FormContext);
|
|
13
17
|
const [selectedOption, setSelectedOption] = useState<string[]>(
|
|
14
|
-
formContext.getValues(props.fieldConfig.name)
|
|
15
|
-
? formContext.getValues(props.fieldConfig.name)
|
|
16
|
-
: []
|
|
18
|
+
normalizeMultiSelectValue(formContext.getValues(props.fieldConfig.name))
|
|
17
19
|
);
|
|
18
20
|
const [isChecked, setIsChecked] = useState<boolean>(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
normalizeCheckboxBooleanValue(
|
|
22
|
+
formContext.getValues(props.fieldConfig.name) ?? props.fieldConfig.defaultValue
|
|
23
|
+
)
|
|
22
24
|
);
|
|
23
25
|
useEffect(() => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
const current = formContext.getValues(props.fieldConfig.name);
|
|
27
|
+
if (props.fieldConfig.options) {
|
|
28
|
+
setSelectedOption(normalizeMultiSelectValue(current));
|
|
29
|
+
} else {
|
|
30
|
+
setIsChecked(
|
|
31
|
+
normalizeCheckboxBooleanValue(current ?? props.fieldConfig.defaultValue)
|
|
32
|
+
);
|
|
26
33
|
}
|
|
27
34
|
}, []);
|
|
28
35
|
let registerOptions: RegisterOptions = registerFormField(props.fieldConfig);
|
|
29
36
|
let hookProps = formContext.register(props.fieldConfig.name, registerOptions);
|
|
37
|
+
const getOutputValue = (value: any) => {
|
|
38
|
+
if (props.fieldConfig.outputFormat === OutputFormatType.STRING) {
|
|
39
|
+
if (Array.isArray(value)) return JSON.stringify(value);
|
|
40
|
+
if (typeof value === "boolean") return String(value);
|
|
41
|
+
}
|
|
42
|
+
return value;
|
|
43
|
+
};
|
|
30
44
|
const handleInput = (option: string) => {
|
|
31
45
|
let tempData: string[] = selectedOption;
|
|
32
46
|
if (selectedOption.includes(option)) {
|
|
@@ -36,7 +50,12 @@ const CheckboxField: React.FC<FormFieldComponentPropSchema> = (
|
|
|
36
50
|
tempData.push(option);
|
|
37
51
|
setSelectedOption([...tempData]);
|
|
38
52
|
}
|
|
39
|
-
handleChange(
|
|
53
|
+
handleChange(
|
|
54
|
+
getOutputValue(tempData),
|
|
55
|
+
formContext,
|
|
56
|
+
props.fieldConfig,
|
|
57
|
+
props.onChange
|
|
58
|
+
);
|
|
40
59
|
};
|
|
41
60
|
function getInput() {
|
|
42
61
|
return props.fieldConfig.options ? (
|
|
@@ -87,7 +106,7 @@ const CheckboxField: React.FC<FormFieldComponentPropSchema> = (
|
|
|
87
106
|
checked={isChecked}
|
|
88
107
|
onChange={(checked) => {
|
|
89
108
|
handleChange(
|
|
90
|
-
checked,
|
|
109
|
+
getOutputValue(checked),
|
|
91
110
|
formContext,
|
|
92
111
|
props.fieldConfig,
|
|
93
112
|
props.onChange
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { RegisterOptions } from "react-hook-form";
|
|
2
|
-
import React, { useContext, useEffect
|
|
2
|
+
import React, { useContext, useEffect } from "react";
|
|
3
3
|
import Datepicker from "react-tailwindcss-datepicker";
|
|
4
4
|
import { FormContext } from "../context/FormContext";
|
|
5
5
|
import { FormFieldComponentPropSchema } from "../schema/FormFieldSchema";
|
|
6
6
|
import RenderFormField from "../util/RenderFormField";
|
|
7
7
|
import { handleChange, registerFormField } from "../util";
|
|
8
|
+
import { normalizeDateFormat, normalizeDateInputValue } from "../util/normalizeCustomFieldValues";
|
|
8
9
|
import moment from "moment";
|
|
9
10
|
|
|
11
|
+
|
|
12
|
+
|
|
10
13
|
const DatePicker: React.FC<FormFieldComponentPropSchema> = (
|
|
11
14
|
props: FormFieldComponentPropSchema,
|
|
12
15
|
) => {
|
|
@@ -35,6 +38,7 @@ const DatePicker: React.FC<FormFieldComponentPropSchema> = (
|
|
|
35
38
|
let hookProps = formContext.register(props.fieldConfig.name, registerOptions);
|
|
36
39
|
|
|
37
40
|
function getInput() {
|
|
41
|
+
const displayFormat = normalizeDateFormat(props.fieldConfig.dateDisplayFormat);
|
|
38
42
|
const rawStart =
|
|
39
43
|
initialDate?.startDate ||
|
|
40
44
|
props.fieldConfig.defaultValue ||
|
|
@@ -43,13 +47,9 @@ const DatePicker: React.FC<FormFieldComponentPropSchema> = (
|
|
|
43
47
|
initialDate?.endDate ||
|
|
44
48
|
props.fieldConfig.defaultValue ||
|
|
45
49
|
"";
|
|
46
|
-
const toDate = (v: string | Date | null | undefined): Date | null => {
|
|
47
|
-
if (v == null || v === "") return null;
|
|
48
|
-
return moment(v).isValid() ? moment(v).toDate() : null;
|
|
49
|
-
};
|
|
50
50
|
const value = {
|
|
51
|
-
startDate:
|
|
52
|
-
endDate:
|
|
51
|
+
startDate: normalizeDateInputValue(rawStart, displayFormat),
|
|
52
|
+
endDate: normalizeDateInputValue(rawEnd, displayFormat),
|
|
53
53
|
};
|
|
54
54
|
// Calendar opens on selected date's month/year (must be a Date instance)
|
|
55
55
|
const startFrom = value.startDate instanceof Date ? value.startDate : new Date();
|
|
@@ -58,10 +58,11 @@ const DatePicker: React.FC<FormFieldComponentPropSchema> = (
|
|
|
58
58
|
value={value}
|
|
59
59
|
startFrom={startFrom}
|
|
60
60
|
{...hookProps}
|
|
61
|
+
displayFormat={displayFormat}
|
|
61
62
|
placeholder={
|
|
62
63
|
props.fieldConfig.placeholder
|
|
63
64
|
? props.fieldConfig.placeholder
|
|
64
|
-
:
|
|
65
|
+
: displayFormat
|
|
65
66
|
}
|
|
66
67
|
asSingle={true}
|
|
67
68
|
popoverDirection="down"
|
|
@@ -79,7 +80,7 @@ const DatePicker: React.FC<FormFieldComponentPropSchema> = (
|
|
|
79
80
|
onChange={(dates) => {
|
|
80
81
|
let date = null;
|
|
81
82
|
if (dates?.startDate != null)
|
|
82
|
-
date = moment(dates?.startDate).format(
|
|
83
|
+
date = moment(dates?.startDate).format(displayFormat);
|
|
83
84
|
|
|
84
85
|
handleChange(date, formContext, props.fieldConfig, props.onChange);
|
|
85
86
|
}}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
FieldOptionsSchema,
|
|
14
14
|
FormFieldComponentPropSchema,
|
|
15
15
|
FormFieldType,
|
|
16
|
+
OutputFormatType,
|
|
16
17
|
} from "../schema/FormFieldSchema";
|
|
17
18
|
import { FormContext } from "../context/FormContext";
|
|
18
19
|
import RenderFormField from "../util/RenderFormField";
|
|
@@ -20,6 +21,7 @@ import RenderListOptions, {
|
|
|
20
21
|
renderListBoxValue,
|
|
21
22
|
} from "../util/RenderListOptions";
|
|
22
23
|
import { handleChange, registerFormField } from "../util";
|
|
24
|
+
import { normalizeMultiSelectValue } from "../util/normalizeCustomFieldValues";
|
|
23
25
|
|
|
24
26
|
const MultipleSelectField: React.FC<FormFieldComponentPropSchema> = ({
|
|
25
27
|
fieldConfig,
|
|
@@ -34,9 +36,11 @@ const MultipleSelectField: React.FC<FormFieldComponentPropSchema> = ({
|
|
|
34
36
|
fieldConfig.options ? fieldConfig.options : []
|
|
35
37
|
);
|
|
36
38
|
|
|
37
|
-
const
|
|
38
|
-
formContext.getValues(fieldConfig.name)
|
|
39
|
-
)
|
|
39
|
+
const normalizedSelectedValues = normalizeMultiSelectValue(
|
|
40
|
+
formContext.getValues(fieldConfig.name)
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const validSelectedValues = normalizedSelectedValues.filter(
|
|
40
44
|
(selectedValue: any) =>
|
|
41
45
|
fieldConfig.options &&
|
|
42
46
|
fieldConfig.options.some((option) => option.value === selectedValue)
|
|
@@ -53,14 +57,22 @@ const MultipleSelectField: React.FC<FormFieldComponentPropSchema> = ({
|
|
|
53
57
|
}, [fieldConfig.options]);
|
|
54
58
|
|
|
55
59
|
function getInput() {
|
|
60
|
+
const getOutputValue = (val: any) => {
|
|
61
|
+
if (fieldConfig.outputFormat === OutputFormatType.STRING) {
|
|
62
|
+
const normalized = normalizeMultiSelectValue(val);
|
|
63
|
+
return JSON.stringify(normalized);
|
|
64
|
+
}
|
|
65
|
+
return val;
|
|
66
|
+
};
|
|
67
|
+
|
|
56
68
|
return (
|
|
57
69
|
<Listbox
|
|
58
70
|
as={"div"}
|
|
59
71
|
{...hookProps}
|
|
60
72
|
value={validSelectedValues}
|
|
61
73
|
defaultValue={fieldConfig.defaultValue}
|
|
62
|
-
onChange={(val) =>
|
|
63
|
-
handleChange(val, formContext, fieldConfig, onChange)
|
|
74
|
+
onChange={(val: any) =>
|
|
75
|
+
handleChange(getOutputValue(val), formContext, fieldConfig, onChange)
|
|
64
76
|
}
|
|
65
77
|
disabled={fieldConfig.disabled}
|
|
66
78
|
multiple
|
|
@@ -110,6 +110,8 @@ export type FormFieldSchema = {
|
|
|
110
110
|
options?: FieldOptionsSchema[];
|
|
111
111
|
minDate?: Date | null | undefined;
|
|
112
112
|
maxDate?: Date | null | undefined;
|
|
113
|
+
/** Input display format for date pickers (e.g. 'MMMM D, YYYY'). */
|
|
114
|
+
dateDisplayFormat?: string;
|
|
113
115
|
// ref?:any
|
|
114
116
|
|
|
115
117
|
/**
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
OutputFormatType,
|
|
19
19
|
} from "../schema/FormFieldSchema";
|
|
20
20
|
import Tippy from "@tippyjs/react";
|
|
21
|
-
|
|
21
|
+
import { normalizeMultiSelectValue } from "./normalizeCustomFieldValues";
|
|
22
22
|
type RenderListOptionsProps = {
|
|
23
23
|
formContext: FormContextType;
|
|
24
24
|
fieldConfig: FormFieldSchema;
|
|
@@ -154,12 +154,17 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
154
154
|
|
|
155
155
|
const renderOption = (option: FieldOptionsSchema) => {
|
|
156
156
|
let selected = false;
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
157
|
+
const formValue = formContext.getValues(fieldConfig.name);
|
|
158
|
+
if (formField === FormFieldType.MULTI_SELECT) {
|
|
159
|
+
const normalizedValues = normalizeMultiSelectValue(formValue);
|
|
160
|
+
const normalizedOptionValue =
|
|
161
|
+
typeof option.value === "string"
|
|
162
|
+
? option.value.trim()
|
|
163
|
+
: String(option.value).trim();
|
|
164
|
+
selected = normalizedValues.includes(normalizedOptionValue);
|
|
165
|
+
} else if (Array.isArray(formValue)) {
|
|
166
|
+
selected = formValue.includes(option.value);
|
|
161
167
|
} else {
|
|
162
|
-
const formValue = formContext.getValues(fieldConfig.name);
|
|
163
168
|
// const defaultValue = fieldConfig.defaultValue;
|
|
164
169
|
// selected = formValue ? option.value == formValue : defaultValue == option.value;
|
|
165
170
|
selected = option.value == formValue;
|
|
@@ -368,7 +373,7 @@ export function renderListBoxValue(
|
|
|
368
373
|
listOptions: FieldOptionsSchema[],
|
|
369
374
|
onChange?: (value: any) => void,
|
|
370
375
|
): JSX.Element {
|
|
371
|
-
let value = formContext.getValues(fieldConfig.name);
|
|
376
|
+
let value = normalizeMultiSelectValue(formContext.getValues(fieldConfig.name));
|
|
372
377
|
const renderAsString = () => {
|
|
373
378
|
// if (!listOptions) {
|
|
374
379
|
// return value;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import moment from "moment";
|
|
2
|
+
|
|
3
|
+
const normalizeStringToken = (value: unknown): string => {
|
|
4
|
+
return typeof value === "string" ? value.trim() : String(value).trim();
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
const normalizeStringArray = (values: unknown[]): string[] => {
|
|
8
|
+
return values.map(normalizeStringToken).filter(Boolean);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const parseJsonArrayString = (value: string): string[] | null => {
|
|
12
|
+
if (!value.startsWith("[")) return null;
|
|
13
|
+
try {
|
|
14
|
+
const parsed = JSON.parse(value);
|
|
15
|
+
return Array.isArray(parsed) ? normalizeStringArray(parsed) : null;
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const normalizeCheckboxBooleanValue = (value: any): boolean => {
|
|
22
|
+
if (value === true || value === 1) return true;
|
|
23
|
+
if (value === false || value === 0 || value == null || value === "") {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (typeof value === "string") {
|
|
28
|
+
const s = value.trim().toLowerCase();
|
|
29
|
+
if (s === "true" || s === "1" || s === "yes") return true;
|
|
30
|
+
if (s === "false" || s === "0" || s === "no" || s === "") return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return Boolean(value);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const normalizeMultiSelectValue = (value: any): string[] => {
|
|
37
|
+
if (value == null) return [];
|
|
38
|
+
|
|
39
|
+
if (Array.isArray(value)) return normalizeStringArray(value);
|
|
40
|
+
|
|
41
|
+
if (typeof value === "string") {
|
|
42
|
+
const s = value.trim();
|
|
43
|
+
if (!s) return [];
|
|
44
|
+
|
|
45
|
+
const jsonArray = parseJsonArrayString(s);
|
|
46
|
+
if (jsonArray) return jsonArray;
|
|
47
|
+
|
|
48
|
+
return normalizeStringArray(s.split(","));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return normalizeStringArray([value]);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const normalizeDateFormat = (format?: string): string => {
|
|
55
|
+
if (!format) return "YYYY-MM-DD";
|
|
56
|
+
return format
|
|
57
|
+
.replace(/yyyy/g, "YYYY")
|
|
58
|
+
.replace(/yy/g, "YY")
|
|
59
|
+
.replace(/dd/g, "DD")
|
|
60
|
+
.replace(/d/g, "D")
|
|
61
|
+
.replace(/mm/g, "MM");
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const normalizeDateInputValue = (
|
|
65
|
+
value: any,
|
|
66
|
+
preferredFormat?: string
|
|
67
|
+
): Date | null => {
|
|
68
|
+
if (value == null || value === "") return null;
|
|
69
|
+
|
|
70
|
+
if (value instanceof Date) {
|
|
71
|
+
return Number.isNaN(value.getTime()) ? null : value;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
75
|
+
const ms = value < 1_000_000_000_000 ? value * 1000 : value;
|
|
76
|
+
const date = new Date(ms);
|
|
77
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (typeof value === "string") {
|
|
81
|
+
const s = value.trim();
|
|
82
|
+
if (!s) return null;
|
|
83
|
+
|
|
84
|
+
const numeric = Number(s);
|
|
85
|
+
if (Number.isFinite(numeric)) {
|
|
86
|
+
const ms = numeric < 1_000_000_000_000 ? numeric * 1000 : numeric;
|
|
87
|
+
const date = new Date(ms);
|
|
88
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const preferred = normalizeDateFormat(preferredFormat);
|
|
92
|
+
const formats = Array.from(
|
|
93
|
+
new Set([
|
|
94
|
+
preferred,
|
|
95
|
+
"YYYY-MM-DD",
|
|
96
|
+
"DD/MM/YYYY",
|
|
97
|
+
"MM/DD/YYYY",
|
|
98
|
+
"DD-MM-YYYY",
|
|
99
|
+
"MM-DD-YYYY",
|
|
100
|
+
])
|
|
101
|
+
);
|
|
102
|
+
const parsedWithKnownFormats = moment(s, formats, true);
|
|
103
|
+
if (parsedWithKnownFormats.isValid()) {
|
|
104
|
+
return parsedWithKnownFormats.toDate();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const date = new Date(s);
|
|
108
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const date = new Date(value);
|
|
112
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
|