@react-fabric/form 1.0.0-beta.61 → 1.0.0-beta.62

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.
Files changed (49) hide show
  1. package/dist/cjs/builder/FileField.js +8 -5
  2. package/dist/cjs/builder/components/SchemaArrayForm.js +1 -1
  3. package/dist/cjs/builder/components/SchemaField.js +2 -2
  4. package/dist/cjs/form/Form.js +40 -15
  5. package/dist/cjs/hooks/useFileUploader.js +40 -28
  6. package/dist/cjs/i18n/index.js +4 -4
  7. package/dist/cjs/input/AvatarInput.js +14 -9
  8. package/dist/cjs/input/Hidden.js +1 -10
  9. package/dist/cjs/input/RadioGroup.js +3 -1
  10. package/dist/cjs/select/DualList.js +1 -1
  11. package/dist/cjs/select/List.js +4 -2
  12. package/dist/cjs/select/Select.js +1 -1
  13. package/dist/esm/builder/FileField.js +8 -5
  14. package/dist/esm/builder/components/SchemaArrayForm.js +1 -1
  15. package/dist/esm/builder/components/SchemaField.js +2 -2
  16. package/dist/esm/form/Form.js +40 -15
  17. package/dist/esm/hooks/useFileUploader.js +41 -29
  18. package/dist/esm/i18n/index.js +2 -2
  19. package/dist/esm/input/AvatarInput.js +14 -9
  20. package/dist/esm/input/Hidden.js +2 -11
  21. package/dist/esm/input/RadioGroup.js +3 -1
  22. package/dist/esm/select/DualList.js +1 -1
  23. package/dist/esm/select/List.js +4 -2
  24. package/dist/esm/select/Select.js +1 -1
  25. package/dist/types/builder/FileField.d.ts +2 -1
  26. package/dist/types/form/Form.d.ts +6 -2
  27. package/dist/types/hooks/useFileUploader.d.ts +4 -2
  28. package/dist/types/input/AvatarInput.d.ts +1 -1
  29. package/dist/types/input/Hidden.d.ts +3 -11
  30. package/dist/types/input/RadioGroup.d.ts +5 -1
  31. package/package.json +5 -5
  32. package/src/builder/FileField.tsx +5 -3
  33. package/src/builder/components/SchemaArrayForm.tsx +1 -1
  34. package/src/builder/components/SchemaField.tsx +2 -2
  35. package/src/form/Form.tsx +20 -13
  36. package/src/hooks/useFileUploader.ts +41 -29
  37. package/src/i18n/index.ts +2 -2
  38. package/src/input/AvatarInput.tsx +11 -11
  39. package/src/input/Hidden.tsx +4 -22
  40. package/src/input/RadioGroup.tsx +6 -0
  41. package/src/select/DualList.tsx +1 -1
  42. package/src/select/List.tsx +2 -2
  43. package/src/select/Select.tsx +1 -1
  44. /package/dist/cjs/i18n/{ar.json.js → ar/form.json.js} +0 -0
  45. /package/dist/cjs/i18n/{en.json.js → en/form.json.js} +0 -0
  46. /package/dist/esm/i18n/{ar.json.js → ar/form.json.js} +0 -0
  47. /package/dist/esm/i18n/{en.json.js → en/form.json.js} +0 -0
  48. /package/src/i18n/{ar.json → ar/form.json} +0 -0
  49. /package/src/i18n/{en.json → en/form.json} +0 -0
package/src/form/Form.tsx CHANGED
@@ -37,7 +37,6 @@ import {
37
37
  useFormContext,
38
38
  useForm as useFormHook,
39
39
  useWatch,
40
- type DefaultValues,
41
40
  type FormState,
42
41
  type Path,
43
42
  type Resolver,
@@ -70,7 +69,11 @@ export interface FormProps<K extends KeyValue = KeyValue> {
70
69
  /**
71
70
  * default data values
72
71
  */
73
- defaultValues?: K;
72
+ defaultValues?: K | Promise<K>;
73
+ /**
74
+ * reset values on defaultValues change (workaround for using fetch result)
75
+ */
76
+ resetOnChange?: boolean;
74
77
  /**
75
78
  * change callback
76
79
  */
@@ -115,6 +118,7 @@ export const Form = <K extends KeyValue>({
115
118
  formRef,
116
119
  resolver,
117
120
  children,
121
+ resetOnChange,
118
122
  defaultValues = EMPTY_OBJECT as K,
119
123
  onSubmit = DEFAULT_SUBMIT,
120
124
  onChange,
@@ -124,7 +128,7 @@ export const Form = <K extends KeyValue>({
124
128
  const form = useFormHook<K>({
125
129
  shouldFocusError: true,
126
130
  resolver,
127
- defaultValues: defaultValues as DefaultValues<K>,
131
+ defaultValues: async () => await Promise.resolve(defaultValues),
128
132
  });
129
133
 
130
134
  const [, startTransition] = useTransition();
@@ -136,16 +140,19 @@ export const Form = <K extends KeyValue>({
136
140
  return () => subscription.unsubscribe();
137
141
  }, [form.watch, changeHandler]);
138
142
 
139
- const handleReset = useCallback(
140
- (reset: AnyObject = {}) => {
141
- form.reset(reset);
142
- form.clearErrors();
143
- ref.current
144
- ?.querySelector<HTMLElement>("[auto-focus], input, textarea")
145
- ?.focus();
146
- },
147
- [defaultValues],
148
- );
143
+ const handleReset = useCallback((reset: AnyObject = {}) => {
144
+ form.reset(reset);
145
+ form.clearErrors();
146
+ ref.current
147
+ ?.querySelector<HTMLElement>("[auto-focus], input, textarea")
148
+ ?.focus();
149
+ }, []);
150
+
151
+ useEffect(() => {
152
+ if (resetOnChange) {
153
+ handleReset(defaultValues);
154
+ }
155
+ }, [defaultValues, resetOnChange]);
149
156
 
150
157
  useImperativeHandle(
151
158
  formRef,
@@ -23,7 +23,7 @@
23
23
 
24
24
  import { isArray, isObject, isString, uuid } from "@react-fabric/utilities";
25
25
  import { type AxiosRequestConfig } from "axios";
26
- import { useCallback, useReducer } from "react";
26
+ import { useCallback, useEffect, useReducer } from "react";
27
27
 
28
28
  export type UploadHandler = (
29
29
  data: FormData,
@@ -54,6 +54,7 @@ type UploaderActions =
54
54
  | { type: "stop"; key: string; path: string }
55
55
  | { type: "error"; key: string; error: string }
56
56
  | { type: "progress"; key: string; progress: number }
57
+ | { type: "init"; initialList?: string | FileInfo | FileInfo[] }
57
58
  | { type: "remove"; key: string }
58
59
  | { type: "abort"; key: string };
59
60
 
@@ -76,7 +77,13 @@ type UploaderActions =
76
77
  export const useFileUploader = (
77
78
  uploader: UploadHandler,
78
79
  initialList?: string | FileInfo | FileInfo[],
79
- multiple = true,
80
+ {
81
+ multiple = true,
82
+ onChange,
83
+ }: {
84
+ multiple?: boolean;
85
+ onChange?: (files: FileInfo[] | FileInfo | null) => void;
86
+ } = {},
80
87
  ) => {
81
88
  const [state, dispatch] = useReducer(
82
89
  (state: UploaderState, action: UploaderActions) => {
@@ -119,6 +126,29 @@ export const useFileUploader = (
119
126
  if (action.type === "remove") {
120
127
  state.files.delete(action.key);
121
128
  }
129
+ if (action.type === "init") {
130
+ state.files = new Map();
131
+ if (isString(action.initialList)) {
132
+ const key = uuid();
133
+ state.files.set(key, {
134
+ key,
135
+ path: action.initialList,
136
+ filename: "",
137
+ size: 0,
138
+ mime: "",
139
+ });
140
+ }
141
+ if (isObject(action.initialList)) {
142
+ const key = uuid();
143
+ state.files.set(key, { ...(action.initialList as AnyObject), key });
144
+ }
145
+ if (isArray(action.initialList)) {
146
+ action.initialList.forEach((file) => {
147
+ const key = uuid();
148
+ state.files.set(key, { ...file, key });
149
+ });
150
+ }
151
+ }
122
152
  state.list = Array.from(state.files.values())
123
153
  .map((file) => {
124
154
  return (
@@ -134,40 +164,23 @@ export const useFileUploader = (
134
164
  );
135
165
  })
136
166
  .filter(Boolean) as FileInfo[];
167
+ if (action.type === "remove" || action.type === "stop") {
168
+ setTimeout(() => {
169
+ onChange?.(multiple ? state.list : state.list[0] ?? null);
170
+ }, 100);
171
+ }
137
172
  return { ...state };
138
173
  },
139
174
  {
140
175
  files: new Map(),
141
176
  count: 0,
142
177
  } as UploaderState,
143
- (state: UploaderState) => {
144
- if (initialList) {
145
- if (isString(initialList)) {
146
- const key = uuid();
147
- state.files.set(key, {
148
- key,
149
- path: initialList,
150
- filename: "",
151
- size: 0,
152
- mime: "",
153
- });
154
- }
155
- if (isObject(initialList)) {
156
- const key = uuid();
157
- state.files.set(key, { ...(initialList as AnyObject), key });
158
- }
159
- if (isArray(initialList)) {
160
- initialList.forEach((file) => {
161
- const key = uuid();
162
- state.files.set(key, { ...file, key });
163
- });
164
- }
165
- }
166
- state.list = Array.from(state.files.values());
167
- return state;
168
- },
169
178
  );
170
179
 
180
+ useEffect(() => {
181
+ dispatch({ type: "init", initialList });
182
+ }, [initialList]);
183
+
171
184
  const doUpload = useCallback(async (key: string, file: File) => {
172
185
  const data = new FormData();
173
186
  data.append("file", file);
@@ -244,7 +257,6 @@ export const useFileUploader = (
244
257
  remove,
245
258
  retry,
246
259
  pending: state.count,
247
- list: state.list,
248
260
  files: Array.from(state.files.values()),
249
261
  };
250
262
  };
package/src/i18n/index.ts CHANGED
@@ -22,7 +22,7 @@
22
22
  */
23
23
 
24
24
  import { addTranslationBundle } from "@react-fabric/core";
25
- import ar from "./ar.json";
26
- import en from "./en.json";
25
+ import ar from "./ar/form.json";
26
+ import en from "./en/form.json";
27
27
 
28
28
  addTranslationBundle("form", { en, ar });
@@ -88,17 +88,21 @@ export const AvatarInput = ({
88
88
  variant,
89
89
  fileUrl,
90
90
  uploadHandler,
91
+ onChange,
91
92
  ...rest
92
93
  }: Props) => {
93
- const { files, list, upload, remove } = useFileUploader(
94
+ const { files, upload, remove } = useFileUploader(
94
95
  async (data, config) => await uploadHandler?.(data, config),
95
- value,
96
- false,
96
+ value ?? defaultValue,
97
+ {
98
+ multiple: false,
99
+ onChange: (files: AnyObject) => onChange?.(files?.path ?? null),
100
+ },
97
101
  );
98
102
 
99
103
  const [base64, setBase64] = useState("");
100
104
  useEffect(() => {
101
- if (list?.length) {
105
+ if (files?.length) {
102
106
  const file = files?.[0]?.file;
103
107
  void (
104
108
  file &&
@@ -116,7 +120,7 @@ export const AvatarInput = ({
116
120
  } else {
117
121
  setBase64(fileUrl?.(defaultValue ?? "") ?? defaultValue ?? "");
118
122
  }
119
- }, [files, list, defaultValue]);
123
+ }, [files, defaultValue]);
120
124
 
121
125
  useEffect(() => {
122
126
  !value &&
@@ -126,7 +130,7 @@ export const AvatarInput = ({
126
130
  }, [value, defaultValue]);
127
131
 
128
132
  return (
129
- <div className="flex flex-nowrap items-end overflow-hidden">
133
+ <div className="flex flex-nowrap items-end overflow-hidden p-px">
130
134
  <div
131
135
  className={classNames(
132
136
  "inline-block leading-none relative outline overflow-hidden",
@@ -153,11 +157,7 @@ export const AvatarInput = ({
153
157
  />
154
158
  </div>
155
159
  </div>
156
- <HiddenInput
157
- hiddenValue={list?.[0]?.path}
158
- defaultValue={defaultValue}
159
- {...rest}
160
- />
160
+ <HiddenInput value={files?.[0]?.path} {...rest} />
161
161
  {files.map((file, idx) => (
162
162
  <div key={idx} className="px-2 py-1 flex-1 overflow-hidden">
163
163
  <div className="flex flex-nowrap gap-1 items-center">
@@ -21,7 +21,7 @@
21
21
  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  */
23
23
 
24
- import { Fragment, useEffect } from "react";
24
+ import { Fragment } from "react";
25
25
  import { ErrorIcon } from "../internal/ErrorIcon";
26
26
 
27
27
  interface Props {
@@ -30,17 +30,9 @@ interface Props {
30
30
  */
31
31
  name?: string;
32
32
  /**
33
- * hidden value will fire change for form update
33
+ * hidden input value
34
34
  */
35
- hiddenValue?: AnyObject;
36
- /**
37
- * default avatar image
38
- */
39
- defaultValue?: string;
40
- /**
41
- * form onChange handler
42
- */
43
- onChange?: (value: AnyObject) => void;
35
+ value?: AnyObject;
44
36
  }
45
37
 
46
38
  /**
@@ -52,28 +44,18 @@ interface Props {
52
44
  * @returns {JSX.Element} The rendered hidden input field and error icon.
53
45
  */
54
46
  export const HiddenInput = ({
55
- hiddenValue,
56
- defaultValue,
57
- onChange,
58
47
  // @ts-expect-error ignore
59
48
  // eslint-disable-next-line react/prop-types
60
49
  invalid,
61
50
  // @ts-expect-error ignore
62
51
  // eslint-disable-next-line react/prop-types
63
52
  error,
64
- // @ts-expect-error ignore
65
- // eslint-disable-next-line react/prop-types
66
53
  value = "",
67
54
  name,
68
55
  }: Props) => {
69
- useEffect(() => {
70
- setTimeout(() => {
71
- onChange?.(hiddenValue ?? defaultValue);
72
- }, 50);
73
- }, [hiddenValue, defaultValue]);
74
56
  return (
75
57
  <Fragment>
76
- <input type="hidden" onChange={onChange} value={value} name={name} />
58
+ <input type="hidden" value={value} name={name} />
77
59
  <ErrorIcon invalid={invalid} error={error} />
78
60
  </Fragment>
79
61
  );
@@ -68,6 +68,10 @@ export interface RadioGroupProps extends ChildrenProp<typeof Radio>, CssProp {
68
68
  * vertical orientation
69
69
  */
70
70
  vertical?: boolean;
71
+ /**
72
+ * value
73
+ */
74
+ value?: string | number;
71
75
  /**
72
76
  * change handler
73
77
  */
@@ -87,6 +91,7 @@ export const RadioGroup = ({
87
91
  name,
88
92
  className,
89
93
  optionWidth,
94
+ value,
90
95
  onChange,
91
96
  ...rest
92
97
  }: RadioGroupProps) => {
@@ -96,6 +101,7 @@ export const RadioGroup = ({
96
101
  {cloneChildren(children, (child: AnyObject) =>
97
102
  cloneElement(child, {
98
103
  name,
104
+ checked: child.props.value === value,
99
105
  onChange,
100
106
  width: optionWidth,
101
107
  }),
@@ -105,7 +105,7 @@ export const DualList = <T extends AnyObject = string>({
105
105
  groupProperty,
106
106
  height = "18rem",
107
107
  labelProperty = "label" as AnyObject,
108
- valueProperty = "id" as AnyObject,
108
+ valueProperty = "value" as AnyObject,
109
109
  renderer,
110
110
  onChange,
111
111
  // @ts-expect-error ignore
@@ -59,8 +59,8 @@ export const List = <T extends AnyObject = string>({
59
59
  options,
60
60
  height,
61
61
  groupProperty,
62
- labelProperty,
63
- valueProperty,
62
+ labelProperty = "label" as AnyObject,
63
+ valueProperty = "value" as AnyObject,
64
64
  infoProperty,
65
65
  sortProperty,
66
66
  multiple,
@@ -68,7 +68,7 @@ export const Select = <T extends AnyObject = string>({
68
68
  hideHandle,
69
69
  groupProperty,
70
70
  labelProperty = "label" as AnyObject,
71
- valueProperty = "id" as AnyObject,
71
+ valueProperty = "value" as AnyObject,
72
72
  sortProperty,
73
73
  infoProperty,
74
74
  multiple,
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes