@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.
- package/dist/cjs/builder/FormField.js +7 -1
- package/dist/cjs/builder/components/SchemaField.js +2 -1
- package/dist/cjs/input/ArrayInput.js +27 -10
- package/dist/cjs/input/AvatarInput.js +8 -7
- package/dist/cjs/input/DateInput.js +2 -4
- package/dist/cjs/input/File.js +1 -3
- package/dist/cjs/input/Input.js +2 -4
- package/dist/cjs/input/Number.js +1 -3
- package/dist/cjs/input/Password.js +1 -3
- package/dist/cjs/input/Search.js +3 -5
- package/dist/cjs/input/Switch.js +2 -1
- package/dist/cjs/input/Textarea.js +4 -3
- package/dist/cjs/internal/FieldWrapper.js +6 -0
- package/dist/cjs/select/AutoComplete.js +3 -2
- package/dist/cjs/select/List.js +3 -1
- package/dist/cjs/select/Select.js +3 -1
- package/dist/cjs/select/useSelect.js +2 -1
- package/dist/cjs/types/schema.js +1 -0
- package/dist/cjs/utils/eventHandlers.js +11 -5
- package/dist/esm/builder/FormField.js +7 -1
- package/dist/esm/builder/components/SchemaField.js +2 -1
- package/dist/esm/input/ArrayInput.js +24 -7
- package/dist/esm/input/AvatarInput.js +8 -7
- package/dist/esm/input/DateInput.js +3 -5
- package/dist/esm/input/File.js +3 -5
- package/dist/esm/input/Input.js +4 -6
- package/dist/esm/input/Number.js +3 -5
- package/dist/esm/input/Password.js +3 -5
- package/dist/esm/input/Search.js +5 -7
- package/dist/esm/input/Switch.js +2 -1
- package/dist/esm/input/Textarea.js +5 -4
- package/dist/esm/internal/FieldWrapper.js +6 -0
- package/dist/esm/select/AutoComplete.js +3 -2
- package/dist/esm/select/List.js +3 -1
- package/dist/esm/select/Select.js +3 -1
- package/dist/esm/select/useSelect.js +2 -1
- package/dist/esm/types/schema.js +1 -0
- package/dist/esm/utils/eventHandlers.js +11 -5
- package/dist/types/form/Form.d.ts +5 -1
- package/dist/types/input/ArrayInput.d.ts +20 -4
- package/dist/types/input/AvatarInput.d.ts +6 -3
- package/dist/types/internal/FieldWrapper.d.ts +1 -1
- package/dist/types/select/List.d.ts +1 -1
- package/dist/types/select/Select.d.ts +1 -1
- package/dist/types/select/useSelect.d.ts +2 -1
- package/dist/types/types/index.d.ts +4 -0
- package/dist/types/types/schema.d.ts +2 -2
- package/dist/types/utils/eventHandlers.d.ts +1 -1
- package/package.json +9 -9
- package/src/builder/FormField.tsx +8 -1
- package/src/builder/components/SchemaField.tsx +2 -1
- package/src/form/Form.tsx +7 -1
- package/src/input/ArrayInput.tsx +50 -12
- package/src/input/AvatarInput.tsx +11 -7
- package/src/input/DateInput.tsx +3 -6
- package/src/input/File.tsx +2 -6
- package/src/input/Input.tsx +3 -7
- package/src/input/Number.tsx +2 -6
- package/src/input/Password.tsx +2 -6
- package/src/input/Search.tsx +4 -8
- package/src/input/Textarea.tsx +6 -4
- package/src/internal/FieldWrapper.tsx +4 -0
- package/src/select/AutoComplete.tsx +1 -0
- package/src/select/List.tsx +2 -0
- package/src/select/Select.tsx +2 -0
- package/src/select/useSelect.tsx +4 -1
- package/src/types/index.ts +4 -0
- package/src/types/schema.ts +2 -2
- 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:
|
|
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
|
-
|
|
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:
|
|
56
|
+
setValue: (key: NestedKeyOf<K>, value: AnyObject) => void;
|
|
51
57
|
setValues: (values: K) => void;
|
|
52
58
|
}
|
|
53
59
|
|
package/src/input/ArrayInput.tsx
CHANGED
|
@@ -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:
|
|
110
|
+
addItem: (item: T) => void;
|
|
111
111
|
removeItem: (idx: number) => void;
|
|
112
112
|
}>;
|
|
113
113
|
/**
|
|
114
|
-
*
|
|
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:
|
|
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.
|
|
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.
|
|
308
|
-
id={item.
|
|
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={
|
|
346
|
-
|
|
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
|
|
40
|
+
* avatar name
|
|
41
41
|
*/
|
|
42
|
-
|
|
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
|
-
|
|
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
|
-
|
|
96
|
-
}, [
|
|
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
|
package/src/input/DateInput.tsx
CHANGED
|
@@ -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 {
|
|
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 =
|
|
109
|
-
() => handleEnter(onEnterPressed),
|
|
110
|
-
[onEnterPressed],
|
|
111
|
-
);
|
|
108
|
+
const handleEnterPressed = useHandleEnter(onEnterPressed);
|
|
112
109
|
|
|
113
110
|
const handleChange = useCallback(
|
|
114
111
|
(e?: ChangeEvent<HTMLInputElement>) => {
|
package/src/input/File.tsx
CHANGED
|
@@ -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 {
|
|
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 =
|
|
104
|
-
() => handleEnter(onEnterPressed),
|
|
105
|
-
[onEnterPressed],
|
|
106
|
-
);
|
|
102
|
+
const handleEnterPressed = useHandleEnter(onEnterPressed);
|
|
107
103
|
|
|
108
104
|
useEffect(() => {
|
|
109
105
|
setActualValue(deferred);
|
package/src/input/Input.tsx
CHANGED
|
@@ -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 {
|
|
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 =
|
|
68
|
-
() => handleEnter(onEnterPressed),
|
|
69
|
-
[onEnterPressed],
|
|
70
|
-
);
|
|
66
|
+
const handleEnterPressed = useHandleEnter(onEnterPressed);
|
|
71
67
|
|
|
72
68
|
useEffect(() => {
|
|
73
69
|
setActualValue(deferred ?? "");
|
package/src/input/Number.tsx
CHANGED
|
@@ -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 {
|
|
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 =
|
|
77
|
-
() => handleEnter(onEnterPressed),
|
|
78
|
-
[onEnterPressed],
|
|
79
|
-
);
|
|
75
|
+
const handleEnterPressed = useHandleEnter(onEnterPressed);
|
|
80
76
|
|
|
81
77
|
useEffect(() => {
|
|
82
78
|
setActualValue(deferred ?? "");
|
package/src/input/Password.tsx
CHANGED
|
@@ -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 {
|
|
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 =
|
|
76
|
-
() => handleEnter(onEnterPressed),
|
|
77
|
-
[onEnterPressed],
|
|
78
|
-
);
|
|
74
|
+
const handleEnterPressed = useHandleEnter(onEnterPressed);
|
|
79
75
|
|
|
80
76
|
useEffect(() => {
|
|
81
77
|
setActualValue(deferred ?? "");
|
package/src/input/Search.tsx
CHANGED
|
@@ -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 {
|
|
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 =
|
|
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
|
},
|
package/src/input/Textarea.tsx
CHANGED
|
@@ -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 {
|
|
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={
|
|
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>
|
package/src/select/List.tsx
CHANGED
|
@@ -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,
|
package/src/select/Select.tsx
CHANGED
|
@@ -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,
|
package/src/select/useSelect.tsx
CHANGED
|
@@ -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
|
-
|
|
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 = [];
|
package/src/types/index.ts
CHANGED
package/src/types/schema.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
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 =
|
|
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 &&
|