@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@engagebay/engagebay-form-module",
3
- "version": "1.0.7-beta.3",
3
+ "version": "1.0.8-beta.1",
4
4
  "description": "Provide base form components to reacho",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -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 { FormFieldComponentPropSchema } from "../schema/FormFieldSchema";
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
- formContext.getValues(props.fieldConfig.name)
20
- ? formContext.getValues(props.fieldConfig.name)
21
- : props.fieldConfig.defaultValue
21
+ normalizeCheckboxBooleanValue(
22
+ formContext.getValues(props.fieldConfig.name) ?? props.fieldConfig.defaultValue
23
+ )
22
24
  );
23
25
  useEffect(() => {
24
- if (formContext.getValues(props.fieldConfig.name)) {
25
- setSelectedOption(formContext.getValues(props.fieldConfig.name));
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(tempData, formContext, props.fieldConfig, props.onChange);
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, useRef, useState } from "react";
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: toDate(rawStart),
52
- endDate: toDate(rawEnd),
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
- : "YYYY-MM-DD"
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("YYYY-MM-DD");
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 validSelectedValues = (
38
- formContext.getValues(fieldConfig.name) || []
39
- ).filter(
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
- if (Array.isArray(formContext.getValues(fieldConfig.name))) {
158
- selected = formContext
159
- .getValues(fieldConfig.name)
160
- .includes(option.value);
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
+