@react-fabric/form 1.0.0-beta.9 → 1.0.2

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 (215) hide show
  1. package/{src/input/ArrayInput.module.css → css/_arrayInput.css} +8 -8
  2. package/css/_colorPicker.css +95 -0
  3. package/{src/internal/Field.module.css → css/_field.css} +94 -33
  4. package/{src/input/File.module.css → css/_file.css} +21 -6
  5. package/css/styles.css +27 -0
  6. package/dist/cjs/builder/FileField.js +107 -78
  7. package/dist/cjs/builder/FormField.js +60 -39
  8. package/dist/cjs/builder/SchemaEditor.js +72 -0
  9. package/dist/cjs/builder/components/AddSchemaFields.js +64 -0
  10. package/dist/cjs/builder/components/SchemaArrayForm.js +146 -0
  11. package/dist/cjs/builder/components/SchemaField.js +423 -0
  12. package/dist/cjs/form/Controller.js +66 -19
  13. package/dist/cjs/form/Form.js +97 -39
  14. package/dist/cjs/form/useForm.js +32 -0
  15. package/dist/cjs/hooks/useFileUploader.js +74 -45
  16. package/dist/cjs/hooks/useFormBuilder.js +85 -31
  17. package/dist/cjs/i18n/{ar.json.js → ar/form.json.js} +5 -4
  18. package/dist/cjs/i18n/{en.json.js → en/form.json.js} +5 -5
  19. package/dist/cjs/i18n/index.js +4 -4
  20. package/dist/cjs/index.js +41 -2
  21. package/dist/cjs/input/ArrayInput.js +259 -135
  22. package/dist/cjs/input/AvatarInput.js +99 -63
  23. package/dist/cjs/input/Checkbox.js +24 -25
  24. package/dist/cjs/input/ColorInput.js +138 -81
  25. package/dist/cjs/input/ColorPicker.js +78 -95
  26. package/dist/cjs/input/DateInput.js +192 -0
  27. package/dist/cjs/input/Field.js +21 -20
  28. package/dist/cjs/input/File.js +85 -80
  29. package/dist/cjs/input/Hidden.js +27 -13
  30. package/dist/cjs/input/Input.js +49 -48
  31. package/dist/cjs/input/Number.js +46 -44
  32. package/dist/cjs/input/Password.js +51 -49
  33. package/dist/cjs/input/Radio.js +26 -16
  34. package/dist/cjs/input/RadioGroup.js +26 -11
  35. package/dist/cjs/input/{Range.js → RangeSlider.js} +61 -66
  36. package/dist/cjs/input/Search.js +58 -64
  37. package/dist/cjs/input/Slider.js +57 -66
  38. package/dist/cjs/input/Switch.js +29 -27
  39. package/dist/cjs/input/Textarea.js +65 -53
  40. package/dist/cjs/internal/ErrorIcon.js +9 -9
  41. package/dist/cjs/internal/FieldWrapper.js +23 -13
  42. package/dist/cjs/internal/InputWrapper.js +85 -87
  43. package/dist/cjs/ranger/index.js +7 -7
  44. package/dist/cjs/ranger/utils.js +9 -4
  45. package/dist/cjs/select/AutoComplete.js +93 -76
  46. package/dist/cjs/select/DualList.js +235 -154
  47. package/dist/cjs/select/List.js +122 -89
  48. package/dist/cjs/select/Options.js +58 -54
  49. package/dist/cjs/select/Select.js +119 -89
  50. package/dist/cjs/select/helpers.js +5 -5
  51. package/dist/cjs/select/useSelect.js +83 -58
  52. package/dist/cjs/types/schema.js +43 -0
  53. package/dist/cjs/utils/eventHandlers.js +16 -6
  54. package/dist/cjs/utils/getColor.js +6 -6
  55. package/dist/cjs/utils/getProps.js +49 -0
  56. package/dist/esm/builder/FileField.js +109 -80
  57. package/dist/esm/builder/FormField.js +60 -39
  58. package/dist/esm/builder/SchemaEditor.js +72 -0
  59. package/dist/esm/builder/components/AddSchemaFields.js +64 -0
  60. package/dist/esm/builder/components/SchemaArrayForm.js +146 -0
  61. package/dist/esm/builder/components/SchemaField.js +423 -0
  62. package/dist/esm/form/Controller.js +66 -19
  63. package/dist/esm/form/Form.js +97 -39
  64. package/dist/esm/form/useForm.js +32 -0
  65. package/dist/esm/hooks/useFileUploader.js +75 -46
  66. package/dist/esm/hooks/useFormBuilder.js +84 -30
  67. package/dist/esm/i18n/{ar.json.js → ar/form.json.js} +5 -4
  68. package/dist/esm/i18n/{en.json.js → en/form.json.js} +5 -5
  69. package/dist/esm/i18n/index.js +2 -2
  70. package/dist/esm/index.js +36 -1
  71. package/dist/esm/input/ArrayInput.js +260 -136
  72. package/dist/esm/input/AvatarInput.js +99 -63
  73. package/dist/esm/input/Checkbox.js +26 -27
  74. package/dist/esm/input/ColorInput.js +141 -84
  75. package/dist/esm/input/ColorPicker.js +80 -97
  76. package/dist/esm/input/DateInput.js +192 -0
  77. package/dist/esm/input/Field.js +21 -20
  78. package/dist/esm/input/File.js +89 -84
  79. package/dist/esm/input/Hidden.js +28 -14
  80. package/dist/esm/input/Input.js +51 -50
  81. package/dist/esm/input/Number.js +48 -46
  82. package/dist/esm/input/Password.js +54 -52
  83. package/dist/esm/input/Radio.js +27 -17
  84. package/dist/esm/input/RadioGroup.js +27 -12
  85. package/dist/esm/input/{Range.js → RangeSlider.js} +62 -67
  86. package/dist/esm/input/Search.js +61 -67
  87. package/dist/esm/input/Slider.js +58 -67
  88. package/dist/esm/input/Switch.js +31 -29
  89. package/dist/esm/input/Textarea.js +67 -55
  90. package/dist/esm/internal/ErrorIcon.js +11 -11
  91. package/dist/esm/internal/FieldWrapper.js +23 -13
  92. package/dist/esm/internal/InputWrapper.js +88 -90
  93. package/dist/esm/ranger/index.js +7 -7
  94. package/dist/esm/ranger/utils.js +9 -4
  95. package/dist/esm/select/AutoComplete.js +95 -78
  96. package/dist/esm/select/DualList.js +233 -152
  97. package/dist/esm/select/List.js +125 -92
  98. package/dist/esm/select/Options.js +58 -54
  99. package/dist/esm/select/Select.js +122 -92
  100. package/dist/esm/select/helpers.js +5 -5
  101. package/dist/esm/select/useSelect.js +84 -59
  102. package/{src/input/ColorPicker.module.css → dist/esm/types/schema.js} +18 -27
  103. package/dist/esm/utils/eventHandlers.js +16 -6
  104. package/dist/esm/utils/getColor.js +6 -6
  105. package/dist/esm/utils/getProps.js +49 -0
  106. package/dist/types/builder/FileField.d.ts +7 -4
  107. package/dist/types/builder/FormField.d.ts +4 -2
  108. package/dist/types/builder/SchemaEditor.d.ts +6 -0
  109. package/dist/types/builder/components/AddSchemaFields.d.ts +2 -0
  110. package/dist/types/builder/components/SchemaArrayForm.d.ts +2 -0
  111. package/dist/types/builder/components/SchemaField.d.ts +12 -0
  112. package/dist/types/form/Controller.d.ts +45 -3
  113. package/dist/types/form/Form.d.ts +38 -7
  114. package/dist/types/form/useForm.d.ts +6 -0
  115. package/dist/types/hooks/useFileUploader.d.ts +24 -3
  116. package/dist/types/hooks/useFormBuilder.d.ts +41 -3
  117. package/dist/types/index.d.ts +32 -2
  118. package/dist/types/input/ArrayInput.d.ts +68 -13
  119. package/dist/types/input/AvatarInput.d.ts +23 -6
  120. package/dist/types/input/Checkbox.d.ts +12 -10
  121. package/dist/types/input/ColorInput.d.ts +15 -5
  122. package/dist/types/input/ColorPicker.d.ts +10 -2
  123. package/dist/types/input/DateInput.d.ts +21 -0
  124. package/dist/types/input/Field.d.ts +9 -36
  125. package/dist/types/input/File.d.ts +16 -4
  126. package/dist/types/input/Hidden.d.ts +7 -6
  127. package/dist/types/input/Input.d.ts +11 -4
  128. package/dist/types/input/Number.d.ts +9 -4
  129. package/dist/types/input/Password.d.ts +9 -4
  130. package/dist/types/input/Radio.d.ts +9 -4
  131. package/dist/types/input/RadioGroup.d.ts +14 -6
  132. package/dist/types/input/{Range.d.ts → RangeSlider.d.ts} +11 -6
  133. package/dist/types/input/Search.d.ts +14 -4
  134. package/dist/types/input/Slider.d.ts +9 -6
  135. package/dist/types/input/Switch.d.ts +13 -8
  136. package/dist/types/input/Textarea.d.ts +15 -4
  137. package/dist/types/internal/ErrorIcon.d.ts +4 -1
  138. package/dist/types/internal/FieldWrapper.d.ts +10 -1
  139. package/dist/types/internal/InputWrapper.d.ts +19 -1
  140. package/dist/types/ranger/index.d.ts +7 -3
  141. package/dist/types/ranger/utils.d.ts +2 -2
  142. package/dist/types/select/AutoComplete.d.ts +7 -2
  143. package/dist/types/select/DualList.d.ts +11 -37
  144. package/dist/types/select/List.d.ts +7 -2
  145. package/dist/types/select/Options.d.ts +8 -7
  146. package/dist/types/select/Select.d.ts +7 -2
  147. package/dist/types/select/helpers.d.ts +2 -2
  148. package/dist/types/select/useSelect.d.ts +10 -28
  149. package/dist/types/types/index.d.ts +73 -39
  150. package/dist/types/types/schema.d.ts +78 -32
  151. package/dist/types/utils/eventHandlers.d.ts +4 -4
  152. package/dist/types/utils/getColor.d.ts +4 -4
  153. package/dist/types/utils/getProps.d.ts +26 -0
  154. package/dist/types/utils/index.d.ts +1 -0
  155. package/package.json +25 -25
  156. package/src/builder/FileField.tsx +73 -56
  157. package/src/builder/FormField.tsx +66 -58
  158. package/src/builder/SchemaEditor.tsx +78 -0
  159. package/src/builder/components/AddSchemaFields.tsx +64 -0
  160. package/src/builder/components/SchemaArrayForm.tsx +146 -0
  161. package/src/builder/components/SchemaField.tsx +405 -0
  162. package/src/form/Controller.tsx +61 -24
  163. package/src/form/Form.tsx +74 -50
  164. package/src/form/useForm.ts +53 -0
  165. package/src/hooks/useFileUploader.ts +115 -84
  166. package/src/hooks/useFormBuilder.tsx +80 -41
  167. package/src/i18n/{ar.json → ar/form.json} +5 -4
  168. package/src/i18n/{en.json → en/form.json} +5 -5
  169. package/src/i18n/index.ts +2 -2
  170. package/src/index.ts +34 -20
  171. package/src/input/ArrayInput.tsx +255 -173
  172. package/src/input/AvatarInput.tsx +82 -39
  173. package/src/input/Checkbox.tsx +30 -54
  174. package/src/input/ColorInput.tsx +146 -105
  175. package/src/input/ColorPicker.tsx +76 -102
  176. package/src/input/DateInput.tsx +225 -0
  177. package/src/input/Field.tsx +23 -65
  178. package/src/input/File.tsx +76 -98
  179. package/src/input/Hidden.tsx +36 -24
  180. package/src/input/Input.tsx +55 -53
  181. package/src/input/Number.tsx +49 -54
  182. package/src/input/Password.tsx +61 -66
  183. package/src/input/Radio.tsx +28 -37
  184. package/src/input/RadioGroup.tsx +30 -25
  185. package/src/input/{Range.tsx → RangeSlider.tsx} +81 -109
  186. package/src/input/Search.tsx +64 -77
  187. package/src/input/Slider.tsx +80 -109
  188. package/src/input/Switch.tsx +39 -53
  189. package/src/input/Textarea.tsx +72 -59
  190. package/src/internal/ErrorIcon.tsx +22 -21
  191. package/src/internal/FieldWrapper.tsx +33 -23
  192. package/src/internal/InputWrapper.tsx +132 -143
  193. package/src/ranger/index.tsx +37 -117
  194. package/src/ranger/utils.ts +12 -11
  195. package/src/select/AutoComplete.tsx +113 -123
  196. package/src/select/DualList.tsx +218 -204
  197. package/src/select/List.tsx +130 -97
  198. package/src/select/Options.tsx +59 -57
  199. package/src/select/Select.tsx +148 -122
  200. package/src/select/helpers.ts +13 -8
  201. package/src/select/useSelect.tsx +110 -127
  202. package/src/types/index.ts +76 -45
  203. package/src/types/schema.ts +97 -40
  204. package/src/utils/eventHandlers.ts +24 -26
  205. package/src/utils/getColor.ts +9 -8
  206. package/src/utils/getProps.ts +47 -0
  207. package/src/utils/index.ts +1 -0
  208. package/dist/cjs/input/ArrayInput.module.css +0 -1
  209. package/dist/cjs/input/ColorPicker.module.css +0 -1
  210. package/dist/cjs/input/File.module.css +0 -1
  211. package/dist/cjs/internal/Field.module.css +0 -1
  212. package/dist/esm/input/ArrayInput.module.css +0 -1
  213. package/dist/esm/input/ColorPicker.module.css +0 -1
  214. package/dist/esm/input/File.module.css +0 -1
  215. package/dist/esm/internal/Field.module.css +0 -1
@@ -21,23 +21,16 @@
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
+ /* eslint-disable @typescript-eslint/no-unsafe-return */
25
+ /* eslint-disable @typescript-eslint/no-unsafe-call */
26
+ /* eslint-disable @typescript-eslint/no-unsafe-argument */
27
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
28
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
29
+
24
30
  import { useDebounce } from "@react-fabric/core";
25
- import {
26
- dedupe,
27
- EMPTY_ARRAY,
28
- groupBy,
29
- isArray,
30
- isString,
31
- matchString,
32
- } from "@react-fabric/utilities";
33
- import {
34
- useCallback,
35
- useDeferredValue,
36
- useEffect,
37
- useMemo,
38
- useReducer,
39
- useRef,
40
- } from "react";
31
+ import { compareValues, dedupe, EMPTY_ARRAY, groupBy, isArray, isString, matchString } from "@react-fabric/utilities";
32
+ import { useCallback, useDeferredValue, useEffect, useMemo, useReducer, useRef } from "react";
33
+ import type { SelectProps } from "../types";
41
34
  import { getSelectedValue, makeOption } from "./helpers";
42
35
 
43
36
  type StateActions =
@@ -64,6 +57,7 @@ interface State {
64
57
  open: boolean;
65
58
  loading: boolean;
66
59
  activeIndex: number | null;
60
+ activeItem: AnyObject;
67
61
  selectedIndex: number | null;
68
62
  value: AnyObject;
69
63
  // filter
@@ -72,37 +66,30 @@ interface State {
72
66
  items: AnyObject[];
73
67
  }
74
68
 
75
- export const useSelect = ({
76
- options = EMPTY_ARRAY,
77
- value,
78
- autoComplete,
79
- multiple = false,
80
- allowCreate = false,
81
- groupProperty = "",
82
- valueProperty = "id",
83
- labelProperty = "label",
84
- createOption = makeOption,
85
- matcher,
86
- onQuery,
87
- onChange,
88
- onSelect,
89
- }: {
90
- value?: AnyObject;
91
- options?: AnyObject[];
92
- multiple?: boolean;
93
- allowCreate?: boolean;
94
- groupProperty?: AnyObject;
95
- valueProperty?: AnyObject;
96
- labelProperty?: AnyObject;
97
- autoComplete?: boolean;
98
- matcher?: (item: AnyObject, query: string) => boolean;
99
- createOption?: (query: string) => AnyObject;
100
- onQuery?: (query: string) => AnyObject;
101
- onSelect?: (item: AnyObject) => void;
102
- onChange?: (value: AnyObject) => void;
103
- }) => {
69
+ export function useSelect(
70
+ props: Omit<SelectProps<AnyObject>, "multiple"> & { multiple?: boolean; autoComplete?: boolean; alwaysOpen?: boolean },
71
+ ) {
72
+ const {
73
+ options = EMPTY_ARRAY,
74
+ value,
75
+ autoComplete,
76
+ alwaysOpen = false,
77
+ defaultOpen = false,
78
+ multiple = false,
79
+ allowCreate = false,
80
+ groupProperty = "",
81
+ valueProperty = "value",
82
+ labelProperty = "label",
83
+ sortProperty,
84
+ createOption = makeOption,
85
+ matcher,
86
+ onQuery,
87
+ onChange,
88
+ onSelect,
89
+ } = props;
90
+
104
91
  // value state
105
- const emptyValue = useMemo(() => (multiple ? [] : ""), [multiple]);
92
+ const emptyValue = useMemo(() => (multiple ? ([] as string[]) : ""), [multiple]);
106
93
  const deferred = useDeferredValue(value ?? emptyValue);
107
94
 
108
95
  // list items
@@ -111,32 +98,28 @@ export const useSelect = ({
111
98
  const listContentRef = useRef<AnyObject[]>([]);
112
99
  const optionsRef = useRef<AnyObject[]>([]);
113
100
 
114
- const macthOption = useCallback(
101
+ const matchOption = useCallback(
115
102
  (item: AnyObject, query: string) => {
116
- return (
117
- matcher?.(item, query) ??
118
- matchString(item[labelProperty] ?? item, query)
119
- );
103
+ return matcher?.(item, query) ?? matchString(item[labelProperty] ?? item, query);
120
104
  },
121
- [matcher],
105
+ [labelProperty, matcher],
122
106
  );
123
107
 
124
108
  const fireChange = useCallback(
125
109
  (value: AnyObject) => {
126
110
  setTimeout(() => {
127
111
  onSelect?.(value ?? emptyValue);
112
+ // @ts-expect-error ignore
128
113
  onChange?.(getSelectedValue(value, emptyValue, valueProperty));
129
114
  }, 50);
130
115
  },
131
- [emptyValue, onSelect, onChange],
116
+ [onSelect, emptyValue, onChange, valueProperty],
132
117
  );
133
118
 
134
119
  const [state, dispatch] = useReducer(
135
120
  (state: State, action: StateActions) => {
136
121
  if (action.type === "open") {
137
- const index = state.items.indexOf(
138
- state.multiple ? state.value[0] : state.value,
139
- );
122
+ const index = state.items.indexOf(state.multiple ? state.value[0] : state.value);
140
123
  return {
141
124
  ...state,
142
125
  open: true,
@@ -144,20 +127,21 @@ export const useSelect = ({
144
127
  };
145
128
  }
146
129
  if (action.type === "close") {
147
- return { ...state, activeIndex: null, open: false };
130
+ return { ...state, activeIndex: null, activeItem: null, open: false };
148
131
  }
149
132
  if (action.type === "active" && state.open) {
150
- if (autoComplete && action.index !== null)
151
- state.query = state.items[action.index];
152
- return { ...state, activeIndex: action.index };
133
+ if (autoComplete && action.index !== null) state.query = state.items[action.index];
134
+ return {
135
+ ...state,
136
+ activeIndex: action.index,
137
+ activeItem: action.index !== null ? state.items[action.index] : null,
138
+ };
153
139
  }
154
140
  if (action.type === "setValue") {
155
141
  return {
156
142
  ...state,
157
143
  value: action.value,
158
- selectedIndex: state.items.indexOf(
159
- state.multiple ? action.value[0] : action.value,
160
- ),
144
+ selectedIndex: state.items.indexOf(state.multiple ? action.value[0] : action.value),
161
145
  };
162
146
  }
163
147
  if (action.type === "changeValue") {
@@ -165,17 +149,17 @@ export const useSelect = ({
165
149
  ...state,
166
150
  value: state.emptyValue,
167
151
  activeIndex: null,
152
+ activeItem: null,
168
153
  selectedIndex: autoComplete
169
154
  ? state.multiple
170
- ? action.value?.length
155
+ ? (action.value?.length as number)
171
156
  : 0
172
157
  : state.items.indexOf(action.value),
173
158
  };
174
159
  if (action.value !== undefined) {
175
160
  if (multiple) {
176
161
  const values = [];
177
- if (isString(action.value))
178
- values.push(...action.value.split(/,\s?/));
162
+ if (isString(action.value)) values.push(...action.value.split(/,\s?/));
179
163
  else values.push(action.value);
180
164
  newState.value = [...state.value];
181
165
  values.forEach((value: AnyObject) => {
@@ -187,7 +171,7 @@ export const useSelect = ({
187
171
  newState.value = action.value;
188
172
  }
189
173
  }
190
- if (!state.multiple) newState.open = false;
174
+ if (!state.multiple) newState.open = alwaysOpen;
191
175
  fireChange(newState.value);
192
176
  return newState;
193
177
  }
@@ -223,6 +207,7 @@ export const useSelect = ({
223
207
  if (autoComplete && action.query !== undefined) {
224
208
  optionList = dedupe([action.query, ...optionList]);
225
209
  }
210
+ if (sortProperty) optionList = optionList.sort(compareValues("asc", sortProperty !== true ? sortProperty : undefined));
226
211
  const grouped = groupBy(optionList, groupProperty, "");
227
212
  if (action.open) newState.open = true;
228
213
  newState.items = [];
@@ -230,7 +215,7 @@ export const useSelect = ({
230
215
  if (allowCreate && action.query)
231
216
  newState.items.push({
232
217
  ___create___: true,
233
- value: createOption(action.query),
218
+ value: action.query,
234
219
  });
235
220
  Object.entries(grouped).forEach(([key, list]) => {
236
221
  if (key) {
@@ -239,42 +224,40 @@ export const useSelect = ({
239
224
  newState.items.push(...list);
240
225
  });
241
226
  if (action.setActive) {
242
- const index = newState.items.findIndex(
243
- (opt: AnyObject) => !opt.___group___,
244
- );
227
+ const index = newState.items.findIndex((opt: AnyObject) => !opt.___group___);
245
228
  newState.activeIndex = index >= 0 ? index : null;
229
+ newState.activeItem = index >= 0 ? newState.items[index] : null;
246
230
  } else {
247
231
  newState.activeIndex = null;
232
+ newState.activeItem = null;
248
233
  }
249
234
  listContentRef.current = newState.items;
250
235
  return newState;
251
236
  }
252
237
  return state;
253
238
  },
254
- {
239
+ {},
240
+ () => ({
255
241
  multiple,
256
242
  emptyValue,
257
- open: false,
243
+ open: alwaysOpen || defaultOpen,
258
244
  loading: false,
259
245
  activeIndex: null,
246
+ activeItem: null,
260
247
  selectedIndex: null,
261
248
  value: emptyValue,
262
249
  // filter
263
250
  query: undefined,
264
251
  // dropdown items
265
252
  items: [],
266
- },
253
+ }),
267
254
  );
268
255
 
269
256
  useEffect(() => {
270
257
  if (!multiple) {
271
- const newOption =
272
- (allowCreate || autoComplete) && deferred
273
- ? createOption(deferred)
274
- : undefined;
275
- const hasValue = options.find(
276
- (opt: AnyObject) => (opt[valueProperty] ?? opt) === deferred,
277
- );
258
+ // @ts-expect-error ignore
259
+ const newOption = (allowCreate || autoComplete) && deferred ? createOption(deferred) : undefined;
260
+ const hasValue = options.find((opt: AnyObject) => (opt[valueProperty] ?? opt) === deferred);
278
261
  optionsRef.current = options;
279
262
  // if current value missing in list inject it
280
263
  if (!hasValue && newOption) {
@@ -283,26 +266,19 @@ export const useSelect = ({
283
266
  dispatch({ type: "options", options: optionsRef.current });
284
267
  dispatch({
285
268
  type: "setValue",
286
- value: hasValue ?? newOption ?? "",
269
+ value: hasValue ?? newOption ?? deferred,
287
270
  });
288
271
  }
289
272
  if (multiple) {
290
273
  optionsRef.current = [];
291
- const value = (isArray(deferred) ? deferred : []).map(
292
- (item: AnyObject) => {
293
- const newOption =
294
- (allowCreate || autoComplete) && item
295
- ? createOption(item)
296
- : undefined;
297
- const hasValue = options.find(
298
- (opt: AnyObject) => (opt[valueProperty] ?? opt) === deferred,
299
- );
300
- if (!hasValue && newOption) {
301
- optionsRef.current.push(newOption);
302
- }
303
- return hasValue ?? newOption ?? "";
304
- },
305
- );
274
+ const value = (isArray(deferred) ? deferred : []).map((item: AnyObject) => {
275
+ const newOption = (allowCreate || autoComplete) && item ? createOption(item) : undefined;
276
+ const hasValue = options.find((opt: AnyObject) => (opt[valueProperty] ?? opt) === item);
277
+ if (!hasValue && newOption) {
278
+ optionsRef.current.push(newOption);
279
+ }
280
+ return hasValue ?? newOption ?? item;
281
+ });
306
282
  optionsRef.current = [...optionsRef.current, ...(options ?? [])];
307
283
  dispatch({ type: "options", options: optionsRef.current });
308
284
  dispatch({
@@ -310,17 +286,18 @@ export const useSelect = ({
310
286
  value,
311
287
  });
312
288
  }
313
- }, [deferred, options, multiple, allowCreate, autoComplete, createOption]);
289
+ }, [deferred, options, multiple, allowCreate, autoComplete, createOption, valueProperty]);
314
290
 
315
291
  const fireQuery = useDebounce(
316
292
  (query: string) => {
317
293
  void Promise.resolve(onQuery?.(query)).then((options) => {
294
+ // @ts-expect-error ignore
318
295
  const newOptions = [...options];
319
296
  // if autocomplete inject other filtered options from history
320
297
  if (autoComplete) {
321
298
  newOptions.push(
322
299
  ...optionsRef.current.filter((opt: AnyObject) => {
323
- return macthOption?.(opt, query);
300
+ return matchOption?.(opt, query);
324
301
  }),
325
302
  );
326
303
  }
@@ -334,6 +311,7 @@ export const useSelect = ({
334
311
  });
335
312
  },
336
313
  [onQuery],
314
+ 300,
337
315
  );
338
316
 
339
317
  // handlers
@@ -352,38 +330,38 @@ export const useSelect = ({
352
330
  open: true,
353
331
  setActive: true,
354
332
  options: optionsRef.current.filter((opt: AnyObject) => {
355
- return macthOption?.(opt, query);
333
+ return matchOption?.(opt, query);
356
334
  }),
357
335
  });
358
336
  }
359
337
  },
360
- [macthOption, onQuery],
338
+ [fireQuery, matchOption, onQuery],
361
339
  );
362
340
 
363
- const handleChange = useCallback((value?: AnyObject) => {
364
- let values = value;
365
- if (autoComplete && multiple && value) {
366
- values = value.split(/,\s?/);
367
- }
368
- dispatch({
369
- type: "changeValue",
370
- value: value?.___create___ ? value.value : value,
371
- });
372
- if (value?.___create___) {
373
- let val = [value.value];
374
- if (multiple) {
375
- val = value.value.split(/,\s?/);
341
+ const handleChange = useCallback(
342
+ (value?: AnyObject) => {
343
+ let values = value;
344
+ if (autoComplete && multiple && value) {
345
+ values = value.split(/,\s?/);
376
346
  }
377
- optionsRef.current = [...optionsRef.current, ...val.map(createOption)];
378
- }
379
- if (autoComplete) {
380
- optionsRef.current = dedupe([
381
- ...(isArray(values) ? values : [values]),
382
- ...optionsRef.current,
383
- ]);
384
- }
385
- dispatch({ type: "options", options: optionsRef.current });
386
- }, []);
347
+ dispatch({
348
+ type: "changeValue",
349
+ value: value?.___create___ ? value.value : value,
350
+ });
351
+ if (value?.___create___) {
352
+ let val = [value.value];
353
+ if (multiple) {
354
+ val = value.value.split(/,\s?/);
355
+ }
356
+ optionsRef.current = [...optionsRef.current, ...val.map(createOption)];
357
+ }
358
+ if (autoComplete) {
359
+ optionsRef.current = dedupe([...(isArray(values) ? values : [values]), ...optionsRef.current]);
360
+ }
361
+ dispatch({ type: "options", options: optionsRef.current });
362
+ },
363
+ [autoComplete, createOption, multiple],
364
+ );
387
365
 
388
366
  const handleRemove = useCallback((index?: number) => {
389
367
  dispatch({ type: "removeItem", index });
@@ -399,7 +377,11 @@ export const useSelect = ({
399
377
  }, []);
400
378
 
401
379
  const setActiveIndex = useCallback((index: number | null) => {
402
- dispatch({ type: "active", index });
380
+ if (index && index > -1) dispatch({ type: "active", index });
381
+ }, []);
382
+
383
+ const clearActiveIndex = useCallback(() => {
384
+ dispatch({ type: "active", index: null });
403
385
  }, []);
404
386
 
405
387
  return {
@@ -410,11 +392,12 @@ export const useSelect = ({
410
392
  handleChange,
411
393
  handleRemove,
412
394
  handleQuery,
413
- macthOption,
395
+ clearActiveIndex,
396
+ macthOption: matchOption,
414
397
  setActiveIndex,
415
398
  setOpen,
416
399
  setItemRef: (index: number, node: HTMLElement) => {
417
400
  listRef.current[index] = node;
418
401
  },
419
402
  };
420
- };
403
+ }
@@ -21,16 +21,19 @@
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 {
25
- type RefProp,
26
- type TestProps,
27
- } from "@react-fabric/core/dist/types/types";
24
+ import type { ChildNode, NestedKeys, RefProp, TestProps } from "@react-fabric/core/dist/types/types";
28
25
 
29
26
  export interface InputProps<V = string> extends TestProps {
27
+ name?: string;
28
+ autoFocus?: boolean;
30
29
  /**
31
30
  * value
32
31
  */
33
32
  value?: V;
33
+ /**
34
+ * default value
35
+ */
36
+ defaultValue?: V;
34
37
  /**
35
38
  * change handler
36
39
  */
@@ -46,57 +49,47 @@ export interface InputProps<V = string> extends TestProps {
46
49
  /**
47
50
  * on enter press callback
48
51
  */
49
- onEnterPressed?: React.KeyboardEventHandler;
52
+ onEnterPressed?: React.KeyboardEventHandler<HTMLInputElement>;
50
53
 
54
+ placeholder?: string;
55
+
56
+ disabled?: boolean;
57
+ readOnly?: boolean;
51
58
  /**
52
- * input name
59
+ * show clear button
53
60
  */
61
+ allowClear?: boolean;
62
+ }
63
+
64
+ export interface WrapperProps {
54
65
  name?: string;
55
66
  /**
56
67
  * input label
57
68
  */
58
69
  label?: string;
59
70
  /**
60
- * input placeholder
71
+ * required
61
72
  */
62
- placeholder?: string;
73
+ required?: boolean;
74
+ disabled?: boolean;
63
75
  /**
64
76
  * append to label end
65
77
  */
66
- appendLabel?: JSX.Element | string | number | boolean;
78
+ appendLabel?: React.ReactNode;
67
79
  /**
68
80
  *
69
81
  */
70
- decorateStart?: JSX.Element | string | number | boolean;
82
+ decorateStart?: React.ReactNode;
71
83
  /**
72
84
  *
73
85
  */
74
- decorateEnd?: JSX.Element | string | number | boolean;
86
+ decorateEnd?: React.ReactNode;
87
+ decorateEndShowWhenEmpty?: boolean;
88
+ decorateEndHideWhenEmpty?: boolean;
75
89
  /**
76
90
  * info label at bottom
77
91
  */
78
- info?: string;
79
-
80
- /**
81
- * required
82
- */
83
- required?: boolean;
84
- /**
85
- * disabled input
86
- */
87
- disabled?: boolean;
88
- /**
89
- * read-only input
90
- */
91
- readOnly?: boolean;
92
- /**
93
- * auto focus when rendered
94
- */
95
- autoFocus?: boolean;
96
- /**
97
- * show clear button
98
- */
99
- allowClear?: boolean;
92
+ info?: React.ReactNode;
100
93
  /**
101
94
  * invalid value input
102
95
  */
@@ -105,7 +98,6 @@ export interface InputProps<V = string> extends TestProps {
105
98
  * error message
106
99
  */
107
100
  error?: string;
108
-
109
101
  /**
110
102
  * field width
111
103
  */
@@ -117,28 +109,46 @@ export interface InputProps<V = string> extends TestProps {
117
109
  /**
118
110
  * label width for inline
119
111
  */
120
- labelWidth?: string;
112
+ labelWidth?: string | number;
113
+ /**
114
+ * remove the outline borders
115
+ */
116
+ borderless?: boolean;
121
117
  }
122
118
 
123
- export interface BaseSelectProps<T>
124
- extends RefProp<HTMLInputElement>,
125
- Omit<InputProps, "value" | "onChange"> {
119
+ export interface BaseSelectProps<T extends AnyObject = string> {
120
+ /**
121
+ * open dropdown by default
122
+ */
123
+ defaultOpen?: boolean;
124
+ /**
125
+ * hide dropdown handle
126
+ */
127
+ hideHandle?: boolean;
126
128
  /**
127
129
  * list options
128
130
  */
129
- options?: T[];
131
+ options: T[];
130
132
  /**
131
133
  * group by property name
132
134
  */
133
- groupProperty?: keyof T;
135
+ groupProperty?: NestedKeys<T>;
134
136
  /**
135
137
  * label property name
136
138
  */
137
- labelProperty?: keyof T;
139
+ labelProperty?: NestedKeys<T>;
140
+ /**
141
+ * value property name
142
+ */
143
+ valueProperty?: NestedKeys<T>;
138
144
  /**
139
145
  * value property name
140
146
  */
141
- valueProperty?: keyof T;
147
+ infoProperty?: NestedKeys<T>;
148
+ /**
149
+ * value property name
150
+ */
151
+ sortProperty?: NestedKeys<T> | true;
142
152
  /**
143
153
  * searchable list
144
154
  */
@@ -147,6 +157,10 @@ export interface BaseSelectProps<T>
147
157
  * allow create item
148
158
  */
149
159
  allowCreate?: boolean;
160
+ /**
161
+ * empty list display
162
+ */
163
+ emptyDisplay?: ChildNode;
150
164
  /**
151
165
  * create new item object
152
166
  */
@@ -158,20 +172,22 @@ export interface BaseSelectProps<T>
158
172
  /**
159
173
  * item renderer
160
174
  */
161
- renderer?: (option: T) => React.ReactNode;
175
+ renderer?: (option: T) => ChildNode;
162
176
  /**
163
177
  * item matcher
164
178
  */
165
179
  matcher?: (option: T, query: string) => boolean;
166
180
  }
167
181
 
168
- export type SelectProps<T> = BaseSelectProps<T> &
182
+ type BaseSelectType<T> = BaseSelectProps<T> & Omit<InputProps, "value" | "onChange"> & WrapperProps & RefProp<HTMLInputElement>;
183
+
184
+ export type SelectProps<T> = BaseSelectType<T> &
169
185
  (
170
186
  | {
171
187
  /**
172
188
  * is multiple selection
173
189
  */
174
- multiple?: never | false;
190
+ multiple?: false;
175
191
  /**
176
192
  * selected value
177
193
  */
@@ -204,3 +220,18 @@ export type SelectProps<T> = BaseSelectProps<T> &
204
220
  onChange?: (value?: string[]) => void;
205
221
  }
206
222
  );
223
+
224
+ export type DualSelectProps<T> = BaseSelectType<T> & {
225
+ /**
226
+ * selected value
227
+ */
228
+ value?: string[];
229
+ /**
230
+ * select handler
231
+ */
232
+ onSelect?: (option: T[]) => void;
233
+ /**
234
+ * change handler
235
+ */
236
+ onChange?: (value?: string[]) => void;
237
+ };