@react-fabric/form 1.0.0-beta.46 → 1.0.0-beta.48

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 (69) hide show
  1. package/dist/cjs/builder/FormField.js +7 -1
  2. package/dist/cjs/builder/components/SchemaField.js +2 -1
  3. package/dist/cjs/input/ArrayInput.js +27 -10
  4. package/dist/cjs/input/AvatarInput.js +8 -7
  5. package/dist/cjs/input/DateInput.js +2 -4
  6. package/dist/cjs/input/File.js +1 -3
  7. package/dist/cjs/input/Input.js +2 -4
  8. package/dist/cjs/input/Number.js +1 -3
  9. package/dist/cjs/input/Password.js +1 -3
  10. package/dist/cjs/input/Search.js +3 -5
  11. package/dist/cjs/input/Switch.js +2 -1
  12. package/dist/cjs/input/Textarea.js +4 -3
  13. package/dist/cjs/internal/FieldWrapper.js +6 -0
  14. package/dist/cjs/select/AutoComplete.js +3 -2
  15. package/dist/cjs/select/List.js +3 -1
  16. package/dist/cjs/select/Select.js +3 -1
  17. package/dist/cjs/select/useSelect.js +2 -1
  18. package/dist/cjs/types/schema.js +1 -0
  19. package/dist/cjs/utils/eventHandlers.js +11 -5
  20. package/dist/esm/builder/FormField.js +7 -1
  21. package/dist/esm/builder/components/SchemaField.js +2 -1
  22. package/dist/esm/input/ArrayInput.js +24 -7
  23. package/dist/esm/input/AvatarInput.js +8 -7
  24. package/dist/esm/input/DateInput.js +3 -5
  25. package/dist/esm/input/File.js +3 -5
  26. package/dist/esm/input/Input.js +4 -6
  27. package/dist/esm/input/Number.js +3 -5
  28. package/dist/esm/input/Password.js +3 -5
  29. package/dist/esm/input/Search.js +5 -7
  30. package/dist/esm/input/Switch.js +2 -1
  31. package/dist/esm/input/Textarea.js +5 -4
  32. package/dist/esm/internal/FieldWrapper.js +6 -0
  33. package/dist/esm/select/AutoComplete.js +3 -2
  34. package/dist/esm/select/List.js +3 -1
  35. package/dist/esm/select/Select.js +3 -1
  36. package/dist/esm/select/useSelect.js +2 -1
  37. package/dist/esm/types/schema.js +1 -0
  38. package/dist/esm/utils/eventHandlers.js +11 -5
  39. package/dist/types/form/Form.d.ts +5 -1
  40. package/dist/types/input/ArrayInput.d.ts +20 -4
  41. package/dist/types/input/AvatarInput.d.ts +6 -3
  42. package/dist/types/internal/FieldWrapper.d.ts +1 -1
  43. package/dist/types/select/List.d.ts +1 -1
  44. package/dist/types/select/Select.d.ts +1 -1
  45. package/dist/types/select/useSelect.d.ts +2 -1
  46. package/dist/types/types/index.d.ts +4 -0
  47. package/dist/types/types/schema.d.ts +2 -2
  48. package/dist/types/utils/eventHandlers.d.ts +1 -1
  49. package/package.json +9 -9
  50. package/src/builder/FormField.tsx +8 -1
  51. package/src/builder/components/SchemaField.tsx +2 -1
  52. package/src/form/Form.tsx +7 -1
  53. package/src/input/ArrayInput.tsx +50 -12
  54. package/src/input/AvatarInput.tsx +11 -7
  55. package/src/input/DateInput.tsx +3 -6
  56. package/src/input/File.tsx +2 -6
  57. package/src/input/Input.tsx +3 -7
  58. package/src/input/Number.tsx +2 -6
  59. package/src/input/Password.tsx +2 -6
  60. package/src/input/Search.tsx +4 -8
  61. package/src/input/Textarea.tsx +6 -4
  62. package/src/internal/FieldWrapper.tsx +4 -0
  63. package/src/select/AutoComplete.tsx +1 -0
  64. package/src/select/List.tsx +2 -0
  65. package/src/select/Select.tsx +2 -0
  66. package/src/select/useSelect.tsx +4 -1
  67. package/src/types/index.ts +4 -0
  68. package/src/types/schema.ts +2 -2
  69. package/src/utils/eventHandlers.ts +9 -3
@@ -79,6 +79,7 @@ export const FormField = ({
79
79
  if (rest.datatype === DATA_TYPES.BOOL) return Switch;
80
80
  if (rest.datatype === DATA_TYPES.TEXT) return Textarea;
81
81
  if (rest.datatype === DATA_TYPES.DATE) return DateInput;
82
+ if (rest.datatype === DATA_TYPES.DATETIME) return DateInput;
82
83
  if (rest.datatype === DATA_TYPES.COLOR) return ColorInput;
83
84
  if (hasOptions) return Select;
84
85
 
@@ -104,7 +105,13 @@ export const FormField = ({
104
105
  return {
105
106
  min: rest.min,
106
107
  max: rest.max,
107
- type: rest.type,
108
+ type: "date",
109
+ };
110
+ if (rest.datatype === DATA_TYPES.DATETIME)
111
+ return {
112
+ min: rest.min,
113
+ max: rest.max,
114
+ type: "datetime",
108
115
  };
109
116
  if (rest.datatype === DATA_TYPES.TEXT) return { rows: 3 };
110
117
  if (rest.datatype === DATA_TYPES.COLOR) return { showPicker: true };
@@ -190,7 +190,8 @@ export const SchemaField = ({
190
190
  } as StringSchema);
191
191
  } else if (type === DATA_TYPES.DATE) {
192
192
  reset = Object.assign(reset, {
193
- type: "date",
193
+ min: modelRef.min,
194
+ max: modelRef.max,
194
195
  } as DateSchema);
195
196
  } else if (type === DATA_TYPES.NUMBER || type === DATA_TYPES.RANGE) {
196
197
  reset = Object.assign(reset, {
package/src/form/Form.tsx CHANGED
@@ -40,6 +40,12 @@ import {
40
40
  type FormState,
41
41
  } from "react-hook-form";
42
42
 
43
+ type NestedKeyOf<T extends object> = {
44
+ [K in keyof T & (string | number)]: T[K] extends object
45
+ ? `${K}` | `${K}.${NestedKeyOf<T[K]>}`
46
+ : K;
47
+ }[keyof T & (string | number)];
48
+
43
49
  export interface FormRef<K extends KeyValue> {
44
50
  state: FormState<K>;
45
51
  reset: () => void;
@@ -47,7 +53,7 @@ export interface FormRef<K extends KeyValue> {
47
53
  submit: () => void;
48
54
  validate: () => Promise<boolean>;
49
55
  getValues: () => K;
50
- setValue: (key: keyof K, value: AnyObject) => void;
56
+ setValue: (key: NestedKeyOf<K>, value: AnyObject) => void;
51
57
  setValues: (values: K) => void;
52
58
  }
53
59
 
@@ -30,7 +30,7 @@ import {
30
30
  Icon,
31
31
  Tooltip,
32
32
  } from "@react-fabric/core";
33
- import { isString } from "@react-fabric/utilities";
33
+ import { isFalse, isString } from "@react-fabric/utilities";
34
34
  import classNames from "classnames";
35
35
  import {
36
36
  cloneElement,
@@ -48,7 +48,7 @@ import { FieldWrapper } from "../internal/FieldWrapper";
48
48
  import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
49
49
  import { CSS } from "@dnd-kit/utilities";
50
50
 
51
- export interface ArrayInputProps {
51
+ export interface ArrayInputProps<T extends AnyObject = string> {
52
52
  /**
53
53
  * input label
54
54
  */
@@ -107,13 +107,25 @@ export interface ArrayInputProps {
107
107
  */
108
108
  fixedList?: boolean;
109
109
  arrayRef?: Ref<{
110
- addItem: (item: AnyObject) => void;
110
+ addItem: (item: T) => void;
111
111
  removeItem: (idx: number) => void;
112
112
  }>;
113
113
  /**
114
- * Add item
114
+ * add new item
115
115
  */
116
116
  onAdd?: () => AnyObject;
117
+ /**
118
+ * on item remove
119
+ */
120
+ onRemove?: (item: T, idx: number) => void;
121
+ /**
122
+ * before item remove
123
+ */
124
+ onBeforeRemove?: (item: T, idx: number) => Promise<boolean> | boolean;
125
+ /**
126
+ * can remove item
127
+ */
128
+ canRemove?: (props: { item: T; index: number; lastItem: boolean }) => boolean;
117
129
 
118
130
  /**
119
131
  * field width
@@ -129,7 +141,7 @@ export interface ArrayInputProps {
129
141
  labelWidth?: string;
130
142
  }
131
143
 
132
- export const ArrayInput = ({
144
+ export const ArrayInput = <T extends AnyObject = string>({
133
145
  name,
134
146
  children,
135
147
  addLabel,
@@ -137,6 +149,9 @@ export const ArrayInput = ({
137
149
  focusName = "",
138
150
  buttonPosition = "bottom",
139
151
  onAdd,
152
+ onRemove,
153
+ onBeforeRemove,
154
+ canRemove,
140
155
  arrayRef,
141
156
  enableSorting,
142
157
  disabled = false,
@@ -144,7 +159,7 @@ export const ArrayInput = ({
144
159
  minItems = 0,
145
160
  maxItems = Number.MAX_SAFE_INTEGER,
146
161
  ...rest
147
- }: ArrayInputProps) => {
162
+ }: ArrayInputProps<T>) => {
148
163
  const { t } = useTranslation("form");
149
164
  const form = useFormContext();
150
165
 
@@ -153,6 +168,7 @@ export const ArrayInput = ({
153
168
 
154
169
  const { fields, append, remove, move } = useFieldArray({
155
170
  name,
171
+ keyName: "__ID__",
156
172
  control: form.control,
157
173
  });
158
174
 
@@ -164,7 +180,7 @@ export const ArrayInput = ({
164
180
  );
165
181
 
166
182
  const handleAdd = useCallback(
167
- (item: AnyObject) => {
183
+ (item: T) => {
168
184
  append(item);
169
185
  setTimeout(() => {
170
186
  form.setFocus(
@@ -175,6 +191,19 @@ export const ArrayInput = ({
175
191
  [name, fields, focusName],
176
192
  );
177
193
 
194
+ const handleRemove = useCallback(
195
+ (item: T, index: number) => {
196
+ const ret = onBeforeRemove?.(item, index);
197
+ void Promise.resolve(ret).then((b) => {
198
+ if (!isFalse(b)) {
199
+ remove(index);
200
+ onRemove?.(item, index);
201
+ }
202
+ });
203
+ },
204
+ [remove, onRemove, onBeforeRemove],
205
+ );
206
+
178
207
  useImperativeHandle(arrayRef, () => ({
179
208
  addItem: handleAdd,
180
209
  removeItem: remove,
@@ -184,7 +213,7 @@ export const ArrayInput = ({
184
213
  ({ children }: PropsWithChildren) => {
185
214
  if (enableSorting) {
186
215
  // pass id list to dnd context
187
- const idMap = fields.map((item) => item.id);
216
+ const idMap = fields.map((item) => item.__ID__);
188
217
  const handleDragEnd = (e: DragEndEvent) => {
189
218
  if (e.over && e.active.id !== e.over.id) {
190
219
  // find index of item and drop over
@@ -304,8 +333,8 @@ export const ArrayInput = ({
304
333
  <Wrapper>
305
334
  {fields.map((item, index) => (
306
335
  <SortableItem
307
- key={item.id}
308
- id={item.id}
336
+ key={item.__ID__}
337
+ id={item.__ID__}
309
338
  className="flex items-center flex-nowrap gap-2 mb-1"
310
339
  >
311
340
  {typeof children === "function"
@@ -342,8 +371,17 @@ export const ArrayInput = ({
342
371
  color="danger"
343
372
  variant="link"
344
373
  className="self-end"
345
- disabled={disabled || readOnly || fields.length <= minItems}
346
- onClick={() => remove(index)}
374
+ disabled={
375
+ disabled ||
376
+ readOnly ||
377
+ fields.length <= minItems ||
378
+ canRemove?.({
379
+ item: item as T,
380
+ index,
381
+ lastItem: index + 1 === fields.length,
382
+ }) !== false
383
+ }
384
+ onClick={() => handleRemove(item as T, index)}
347
385
  />
348
386
  )}
349
387
  </SortableItem>
@@ -37,14 +37,17 @@ export interface Props extends RefProp {
37
37
  */
38
38
  value?: string;
39
39
  /**
40
- * avatar stream src
40
+ * avatar name
41
41
  */
42
- src?: string;
42
+ avatarName?: string;
43
43
  size?: string | number;
44
44
  /**
45
45
  * upload handler to store file in temp storage
46
46
  */
47
47
  uploadHandler: UploadHandler;
48
+ /**
49
+ * file stream path
50
+ */
48
51
  fileUrl?: (path: string) => string;
49
52
  /**
50
53
  * change handler
@@ -57,15 +60,16 @@ export interface Props extends RefProp {
57
60
  }
58
61
 
59
62
  export const AvatarInput = ({
60
- src,
61
63
  size = "6rem",
64
+ avatarName,
65
+ value,
62
66
  fileUrl,
63
67
  uploadHandler,
64
68
  ...rest
65
69
  }: Props) => {
66
70
  const { files, list, upload, remove } = useFileUploader(
67
71
  async (data, config) => await uploadHandler?.(data, config),
68
- rest.value,
72
+ value,
69
73
  false,
70
74
  );
71
75
 
@@ -92,13 +96,13 @@ export const AvatarInput = ({
92
96
  }, [files, list]);
93
97
 
94
98
  useEffect(() => {
95
- src && setBase64(fileUrl?.(src) ?? src);
96
- }, [src]);
99
+ value && setBase64(fileUrl?.(value) ?? value);
100
+ }, [value]);
97
101
 
98
102
  return (
99
103
  <div className="flex flex-nowrap items-end">
100
104
  <div className="inline-block leading-none rounded-full relative outline overflow-hidden">
101
- <Avatar name="temp" size={size} avatar={base64} />
105
+ <Avatar name={avatarName ?? "temp"} size={size} avatar={base64} />
102
106
  <div className="absolute inset-x-0 bottom-0 cursor-pointer bg-black/20 hover:bg-black/50 text-white text-xs text-center py-1">
103
107
  <span>Edit</span>
104
108
  <input
@@ -37,6 +37,7 @@ import {
37
37
  import { type RefProp } from "@react-fabric/core/dist/types/types";
38
38
  import { type DateLike, DatePanel, DateUtil } from "@react-fabric/date";
39
39
  import { mergeRefs } from "@react-fabric/utilities";
40
+ import classNames from "classnames";
40
41
  import {
41
42
  type ChangeEvent,
42
43
  startTransition,
@@ -49,8 +50,7 @@ import {
49
50
  } from "react";
50
51
  import { InputWrapper } from "../internal/InputWrapper";
51
52
  import { type InputProps } from "../types";
52
- import { handleEnter } from "../utils";
53
- import classNames from "classnames";
53
+ import { useHandleEnter } from "../utils";
54
54
 
55
55
  export interface DateProps
56
56
  extends InputProps<string>,
@@ -105,10 +105,7 @@ export const DateInput = ({
105
105
  setInputValue(deferred ? DateUtil.format(deferred, inputFormat) : "");
106
106
  }, [deferred, inputFormat]);
107
107
 
108
- const handleEnterPressed = useMemo(
109
- () => handleEnter(onEnterPressed),
110
- [onEnterPressed],
111
- );
108
+ const handleEnterPressed = useHandleEnter(onEnterPressed);
112
109
 
113
110
  const handleChange = useCallback(
114
111
  (e?: ChangeEvent<HTMLInputElement>) => {
@@ -36,14 +36,13 @@ import {
36
36
  useCallback,
37
37
  useDeferredValue,
38
38
  useEffect,
39
- useMemo,
40
39
  useRef,
41
40
  useState,
42
41
  type ChangeEvent,
43
42
  } from "react";
44
43
  import { InputWrapper } from "../internal/InputWrapper";
45
44
  import { type InputProps } from "../types";
46
- import { handleEnter } from "../utils";
45
+ import { useHandleEnter } from "../utils";
47
46
 
48
47
  export interface FileInputProps
49
48
  extends InputProps<AnyObject>,
@@ -100,10 +99,7 @@ export const FileInput = ({
100
99
  const [actualValue, setActualValue] = useState<File | File[] | null>();
101
100
  const deferred = useDeferredValue(value);
102
101
 
103
- const handleEnterPressed = useMemo(
104
- () => handleEnter(onEnterPressed),
105
- [onEnterPressed],
106
- );
102
+ const handleEnterPressed = useHandleEnter(onEnterPressed);
107
103
 
108
104
  useEffect(() => {
109
105
  setActualValue(deferred);
@@ -22,18 +22,17 @@
22
22
  */
23
23
 
24
24
  import { type RefProp } from "@react-fabric/core/dist/types/types";
25
+ import classNames from "classnames";
25
26
  import {
26
27
  useCallback,
27
28
  useDeferredValue,
28
29
  useEffect,
29
- useMemo,
30
30
  useState,
31
31
  type ChangeEvent,
32
32
  } from "react";
33
33
  import { InputWrapper } from "../internal/InputWrapper";
34
34
  import { type InputProps } from "../types";
35
- import { handleEnter } from "../utils";
36
- import classNames from "classnames";
35
+ import { useHandleEnter } from "../utils";
37
36
 
38
37
  export interface TextInputProps extends InputProps, RefProp<HTMLInputElement> {
39
38
  /**
@@ -64,10 +63,7 @@ export const Input = ({
64
63
  const [actualValue, setActualValue] = useState("");
65
64
  const deferred = useDeferredValue(value);
66
65
 
67
- const handleEnterPressed = useMemo(
68
- () => handleEnter(onEnterPressed),
69
- [onEnterPressed],
70
- );
66
+ const handleEnterPressed = useHandleEnter(onEnterPressed);
71
67
 
72
68
  useEffect(() => {
73
69
  setActualValue(deferred ?? "");
@@ -26,13 +26,12 @@ import {
26
26
  useCallback,
27
27
  useDeferredValue,
28
28
  useEffect,
29
- useMemo,
30
29
  useState,
31
30
  type ChangeEvent,
32
31
  } from "react";
33
32
  import { InputWrapper } from "../internal/InputWrapper";
34
33
  import { type InputProps } from "../types";
35
- import { handleEnter } from "../utils";
34
+ import { useHandleEnter } from "../utils";
36
35
 
37
36
  export interface NumberProps
38
37
  extends InputProps<number>,
@@ -73,10 +72,7 @@ export const Number = ({
73
72
  const [actualValue, setActualValue] = useState<"" | number>("");
74
73
  const deferred = useDeferredValue(value);
75
74
 
76
- const handleEnterPressed = useMemo(
77
- () => handleEnter(onEnterPressed),
78
- [onEnterPressed],
79
- );
75
+ const handleEnterPressed = useHandleEnter(onEnterPressed);
80
76
 
81
77
  useEffect(() => {
82
78
  setActualValue(deferred ?? "");
@@ -28,14 +28,13 @@ import {
28
28
  useCallback,
29
29
  useDeferredValue,
30
30
  useEffect,
31
- useMemo,
32
31
  useState,
33
32
  type ChangeEvent,
34
33
  } from "react";
35
34
  import { useTranslation } from "react-i18next";
36
35
  import { InputWrapper } from "../internal/InputWrapper";
37
36
  import { type InputProps } from "../types";
38
- import { handleEnter } from "../utils";
37
+ import { useHandleEnter } from "../utils";
39
38
 
40
39
  export interface PasswordProps extends InputProps, RefProp<HTMLInputElement> {
41
40
  /**
@@ -72,10 +71,7 @@ export const Password = ({
72
71
  const [actualValue, setActualValue] = useState("");
73
72
  const deferred = useDeferredValue(value);
74
73
 
75
- const handleEnterPressed = useMemo(
76
- () => handleEnter(onEnterPressed),
77
- [onEnterPressed],
78
- );
74
+ const handleEnterPressed = useHandleEnter(onEnterPressed);
79
75
 
80
76
  useEffect(() => {
81
77
  setActualValue(deferred ?? "");
@@ -28,14 +28,13 @@ import {
28
28
  useCallback,
29
29
  useDeferredValue,
30
30
  useEffect,
31
- useMemo,
32
31
  useRef,
33
32
  useState,
34
33
  type ChangeEvent,
35
34
  } from "react";
36
35
  import { InputWrapper } from "../internal/InputWrapper";
37
36
  import { type InputProps } from "../types";
38
- import { handleEnter } from "../utils";
37
+ import { useHandleEnter } from "../utils";
39
38
 
40
39
  export interface SearchProps extends InputProps, RefProp<HTMLInputElement> {
41
40
  /**
@@ -87,10 +86,7 @@ export const Search = ({
87
86
  250,
88
87
  );
89
88
 
90
- const handleEnterPressed = useMemo(
91
- () => handleEnter(handleSearch),
92
- [handleSearch],
93
- );
89
+ const handleEnterPressed = useHandleEnter(handleSearch);
94
90
 
95
91
  useEffect(() => {
96
92
  setActualValue(deferred ?? "");
@@ -103,8 +99,8 @@ export const Search = ({
103
99
  setActualValue(value ?? "");
104
100
  onChange?.(value ?? null);
105
101
 
106
- // value was cleared so force fire onSearch
107
- if (searchOnChange) {
102
+ // when e is undefined value was cleared so force fire onSearch
103
+ if (!e || searchOnChange) {
108
104
  handleSearch();
109
105
  }
110
106
  },
@@ -32,7 +32,7 @@ import {
32
32
  } from "react";
33
33
  import { InputWrapper } from "../internal/InputWrapper";
34
34
  import { type InputProps } from "../types";
35
- import { handleEnter } from "../utils";
35
+ import { useHandleEnter } from "../utils";
36
36
 
37
37
  export interface TextareaProps
38
38
  extends InputProps,
@@ -102,6 +102,10 @@ export const Textarea = ({
102
102
  }
103
103
  }, [actualValue, variableRows, rows, maxRows]);
104
104
 
105
+ const handleEnterPressed = useHandleEnter(onEnterPressed, {
106
+ preventDefault: !!onEnterPressed,
107
+ });
108
+
105
109
  return (
106
110
  <InputWrapper
107
111
  showClear={!!actualValue && !disabled && !readOnly}
@@ -133,9 +137,7 @@ export const Textarea = ({
133
137
  onBlur={onBlur}
134
138
  onFocus={onFocus}
135
139
  onChange={handleChange}
136
- onKeyDown={handleEnter(onEnterPressed, {
137
- preventDefault: !!onEnterPressed,
138
- })}
140
+ onKeyDown={handleEnterPressed}
139
141
  />
140
142
  </InputWrapper>
141
143
  );
@@ -22,6 +22,7 @@
22
22
  */
23
23
 
24
24
  import classNames from "classnames";
25
+ import { ErrorIcon } from "./ErrorIcon";
25
26
 
26
27
  export const FieldWrapper = ({
27
28
  label,
@@ -35,6 +36,8 @@ export const FieldWrapper = ({
35
36
  children,
36
37
  listing,
37
38
  className,
39
+ invalid,
40
+ error,
38
41
  "data-inner": isInner,
39
42
  }: KeyValue) => {
40
43
  return (
@@ -72,6 +75,7 @@ export const FieldWrapper = ({
72
75
  ✽
73
76
  </span>
74
77
  )}
78
+ <ErrorIcon invalid={invalid} error={error} />
75
79
  <div className="flex-1" />
76
80
  {appendLabel && <span className="flex-content">{appendLabel}</span>}
77
81
  </div>
@@ -293,6 +293,7 @@ export const AutoComplete = ({
293
293
  data-testid={name}
294
294
  name={name}
295
295
  size={1}
296
+ ref={ref}
296
297
  onInput={onInput}
297
298
  autoFocus={autoFocus}
298
299
  rows={!multiple && expandOnEdit && state.open ? 5 : 1}
@@ -53,6 +53,7 @@ export const List = <T extends AnyObject = string>({
53
53
  groupProperty,
54
54
  labelProperty,
55
55
  valueProperty,
56
+ sortProperty,
56
57
  multiple,
57
58
  allowCreate,
58
59
  searchable,
@@ -100,6 +101,7 @@ export const List = <T extends AnyObject = string>({
100
101
  groupProperty,
101
102
  labelProperty,
102
103
  valueProperty,
104
+ sortProperty,
103
105
  allowCreate,
104
106
  createOption,
105
107
  matcher,
@@ -61,6 +61,7 @@ export const Select = <T extends AnyObject = string>({
61
61
  groupProperty,
62
62
  labelProperty = "label" as AnyObject,
63
63
  valueProperty = "id" as AnyObject,
64
+ sortProperty,
64
65
  multiple,
65
66
  searchable,
66
67
  allowCreate,
@@ -93,6 +94,7 @@ export const Select = <T extends AnyObject = string>({
93
94
  groupProperty,
94
95
  labelProperty,
95
96
  valueProperty,
97
+ sortProperty,
96
98
  allowCreate,
97
99
  createOption,
98
100
  matcher,
@@ -83,6 +83,7 @@ export const useSelect = ({
83
83
  groupProperty = "",
84
84
  valueProperty = "id",
85
85
  labelProperty = "label",
86
+ sortProperty,
86
87
  createOption = makeOption,
87
88
  matcher,
88
89
  onQuery,
@@ -97,6 +98,7 @@ export const useSelect = ({
97
98
  groupProperty?: AnyObject;
98
99
  valueProperty?: AnyObject;
99
100
  labelProperty?: AnyObject;
101
+ sortProperty?: AnyObject;
100
102
  autoComplete?: boolean;
101
103
  matcher?: (item: AnyObject, query: string) => boolean;
102
104
  createOption?: (query: string) => AnyObject;
@@ -226,7 +228,8 @@ export const useSelect = ({
226
228
  if (autoComplete && action.query !== undefined) {
227
229
  optionList = dedupe([action.query, ...optionList]);
228
230
  }
229
- optionList = optionList.sort(compareValues("asc", labelProperty));
231
+ if (sortProperty)
232
+ optionList = optionList.sort(compareValues("asc", sortProperty));
230
233
  const grouped = groupBy(optionList, groupProperty, "");
231
234
  if (action.open) newState.open = true;
232
235
  newState.items = [];
@@ -150,6 +150,10 @@ export interface BaseSelectProps<T extends AnyObject = string> {
150
150
  * value property name
151
151
  */
152
152
  valueProperty?: keyof T;
153
+ /**
154
+ * value property name
155
+ */
156
+ sortProperty?: keyof T | true;
153
157
  /**
154
158
  * searchable list
155
159
  */
@@ -29,6 +29,7 @@ export enum DATA_TYPES {
29
29
  COLOR = "color",
30
30
  BOOL = "bool",
31
31
  DATE = "date",
32
+ DATETIME = "datetime",
32
33
  STRING = "string",
33
34
  NUMBER = "number",
34
35
  RANGE = "range",
@@ -84,8 +85,7 @@ export interface RangeSchema extends BaseSchema {
84
85
  }
85
86
 
86
87
  export interface DateSchema extends BaseSchema {
87
- datatype: DATA_TYPES.DATE;
88
- type?: "date" | "datetime";
88
+ datatype: DATA_TYPES.DATE | DATA_TYPES.DATETIME;
89
89
  min?: DateLike;
90
90
  max?: DateLike;
91
91
  }
@@ -23,19 +23,25 @@
23
23
 
24
24
  /* istanbul ignore file */
25
25
 
26
- import { debounce } from "@react-fabric/utilities";
26
+ import { useFloatingTree } from "@floating-ui/react";
27
+ import { useDebounce } from "@react-fabric/core";
27
28
  import { type KeyboardEvent, type MouseEvent } from "react";
28
29
 
29
30
  /** ***************** common handler for enter press *******************/
30
- export const handleEnter = (
31
+ export const useHandleEnter = (
31
32
  callback?: (e: KeyboardEvent) => void,
32
33
  {
33
34
  stopPropagation = false,
34
35
  preventDefault = false,
35
36
  }: { stopPropagation?: boolean; preventDefault?: boolean } = {},
36
37
  ) => {
37
- const cbHandle = debounce(callback ?? (() => undefined), 100);
38
+ const cbHandle = useDebounce(callback ?? (() => undefined), [callback], 100);
39
+ const tree = useFloatingTree();
38
40
  return (e: KeyboardEvent) => {
41
+ // prevent propagation of event if input within dropdown (work around for floating-ui typeahead)
42
+ if (tree && e.key !== "Escape") {
43
+ e?.stopPropagation();
44
+ }
39
45
  if (
40
46
  !e.shiftKey &&
41
47
  !e.altKey &&