@react-fabric/form 1.0.0-beta.8 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{src/input/ArrayInput.module.css → css/_arrayInput.css} +8 -8
- package/css/_colorPicker.css +95 -0
- package/{src/internal/Field.module.css → css/_field.css} +94 -33
- package/{src/input/File.module.css → css/_file.css} +21 -6
- package/css/styles.css +27 -0
- package/dist/cjs/builder/FileField.js +107 -78
- package/dist/cjs/builder/FormField.js +60 -39
- package/dist/cjs/builder/SchemaEditor.js +72 -0
- package/dist/cjs/builder/components/AddSchemaFields.js +64 -0
- package/dist/cjs/builder/components/SchemaArrayForm.js +146 -0
- package/dist/cjs/builder/components/SchemaField.js +423 -0
- package/dist/cjs/form/Controller.js +66 -19
- package/dist/cjs/form/Form.js +97 -39
- package/dist/cjs/form/useForm.js +32 -0
- package/dist/cjs/hooks/useFileUploader.js +74 -45
- package/dist/cjs/hooks/useFormBuilder.js +85 -31
- package/dist/cjs/i18n/{ar.json.js → ar/form.json.js} +5 -4
- package/dist/cjs/i18n/{en.json.js → en/form.json.js} +5 -5
- package/dist/cjs/i18n/index.js +4 -4
- package/dist/cjs/index.js +41 -2
- package/dist/cjs/input/ArrayInput.js +259 -135
- package/dist/cjs/input/AvatarInput.js +99 -63
- package/dist/cjs/input/Checkbox.js +24 -25
- package/dist/cjs/input/ColorInput.js +138 -81
- package/dist/cjs/input/ColorPicker.js +78 -95
- package/dist/cjs/input/DateInput.js +192 -0
- package/dist/cjs/input/Field.js +21 -20
- package/dist/cjs/input/File.js +85 -80
- package/dist/cjs/input/Hidden.js +27 -13
- package/dist/cjs/input/Input.js +49 -48
- package/dist/cjs/input/Number.js +46 -44
- package/dist/cjs/input/Password.js +51 -49
- package/dist/cjs/input/Radio.js +26 -16
- package/dist/cjs/input/RadioGroup.js +26 -11
- package/dist/cjs/input/{Range.js → RangeSlider.js} +61 -66
- package/dist/cjs/input/Search.js +58 -64
- package/dist/cjs/input/Slider.js +57 -66
- package/dist/cjs/input/Switch.js +29 -27
- package/dist/cjs/input/Textarea.js +65 -53
- package/dist/cjs/internal/ErrorIcon.js +9 -9
- package/dist/cjs/internal/FieldWrapper.js +23 -13
- package/dist/cjs/internal/InputWrapper.js +85 -87
- package/dist/cjs/ranger/index.js +7 -7
- package/dist/cjs/ranger/utils.js +9 -4
- package/dist/cjs/select/AutoComplete.js +93 -76
- package/dist/cjs/select/DualList.js +235 -154
- package/dist/cjs/select/List.js +122 -89
- package/dist/cjs/select/Options.js +58 -54
- package/dist/cjs/select/Select.js +119 -89
- package/dist/cjs/select/helpers.js +5 -5
- package/dist/cjs/select/useSelect.js +83 -58
- package/dist/cjs/types/schema.js +43 -0
- package/dist/cjs/utils/eventHandlers.js +16 -6
- package/dist/cjs/utils/getColor.js +6 -6
- package/dist/cjs/utils/getProps.js +49 -0
- package/dist/esm/builder/FileField.js +109 -80
- package/dist/esm/builder/FormField.js +60 -39
- package/dist/esm/builder/SchemaEditor.js +72 -0
- package/dist/esm/builder/components/AddSchemaFields.js +64 -0
- package/dist/esm/builder/components/SchemaArrayForm.js +146 -0
- package/dist/esm/builder/components/SchemaField.js +423 -0
- package/dist/esm/form/Controller.js +66 -19
- package/dist/esm/form/Form.js +97 -39
- package/dist/esm/form/useForm.js +32 -0
- package/dist/esm/hooks/useFileUploader.js +75 -46
- package/dist/esm/hooks/useFormBuilder.js +84 -30
- package/dist/esm/i18n/{ar.json.js → ar/form.json.js} +5 -4
- package/dist/esm/i18n/{en.json.js → en/form.json.js} +5 -5
- package/dist/esm/i18n/index.js +2 -2
- package/dist/esm/index.js +36 -1
- package/dist/esm/input/ArrayInput.js +260 -136
- package/dist/esm/input/AvatarInput.js +99 -63
- package/dist/esm/input/Checkbox.js +26 -27
- package/dist/esm/input/ColorInput.js +141 -84
- package/dist/esm/input/ColorPicker.js +80 -97
- package/dist/esm/input/DateInput.js +192 -0
- package/dist/esm/input/Field.js +21 -20
- package/dist/esm/input/File.js +89 -84
- package/dist/esm/input/Hidden.js +28 -14
- package/dist/esm/input/Input.js +51 -50
- package/dist/esm/input/Number.js +48 -46
- package/dist/esm/input/Password.js +54 -52
- package/dist/esm/input/Radio.js +27 -17
- package/dist/esm/input/RadioGroup.js +27 -12
- package/dist/esm/input/{Range.js → RangeSlider.js} +62 -67
- package/dist/esm/input/Search.js +61 -67
- package/dist/esm/input/Slider.js +58 -67
- package/dist/esm/input/Switch.js +31 -29
- package/dist/esm/input/Textarea.js +67 -55
- package/dist/esm/internal/ErrorIcon.js +11 -11
- package/dist/esm/internal/FieldWrapper.js +23 -13
- package/dist/esm/internal/InputWrapper.js +88 -90
- package/dist/esm/ranger/index.js +7 -7
- package/dist/esm/ranger/utils.js +9 -4
- package/dist/esm/select/AutoComplete.js +95 -78
- package/dist/esm/select/DualList.js +233 -152
- package/dist/esm/select/List.js +125 -92
- package/dist/esm/select/Options.js +58 -54
- package/dist/esm/select/Select.js +122 -92
- package/dist/esm/select/helpers.js +5 -5
- package/dist/esm/select/useSelect.js +84 -59
- package/{src/input/ColorPicker.module.css → dist/esm/types/schema.js} +18 -27
- package/dist/esm/utils/eventHandlers.js +16 -6
- package/dist/esm/utils/getColor.js +6 -6
- package/dist/esm/utils/getProps.js +49 -0
- package/dist/types/builder/FileField.d.ts +7 -4
- package/dist/types/builder/FormField.d.ts +4 -2
- package/dist/types/builder/SchemaEditor.d.ts +6 -0
- package/dist/types/builder/components/AddSchemaFields.d.ts +2 -0
- package/dist/types/builder/components/SchemaArrayForm.d.ts +2 -0
- package/dist/types/builder/components/SchemaField.d.ts +12 -0
- package/dist/types/form/Controller.d.ts +45 -3
- package/dist/types/form/Form.d.ts +38 -7
- package/dist/types/form/useForm.d.ts +6 -0
- package/dist/types/hooks/useFileUploader.d.ts +24 -3
- package/dist/types/hooks/useFormBuilder.d.ts +41 -3
- package/dist/types/index.d.ts +32 -2
- package/dist/types/input/ArrayInput.d.ts +68 -13
- package/dist/types/input/AvatarInput.d.ts +23 -6
- package/dist/types/input/Checkbox.d.ts +12 -10
- package/dist/types/input/ColorInput.d.ts +15 -5
- package/dist/types/input/ColorPicker.d.ts +10 -2
- package/dist/types/input/DateInput.d.ts +21 -0
- package/dist/types/input/Field.d.ts +9 -36
- package/dist/types/input/File.d.ts +16 -4
- package/dist/types/input/Hidden.d.ts +7 -6
- package/dist/types/input/Input.d.ts +11 -4
- package/dist/types/input/Number.d.ts +9 -4
- package/dist/types/input/Password.d.ts +9 -4
- package/dist/types/input/Radio.d.ts +9 -4
- package/dist/types/input/RadioGroup.d.ts +14 -6
- package/dist/types/input/{Range.d.ts → RangeSlider.d.ts} +11 -6
- package/dist/types/input/Search.d.ts +14 -4
- package/dist/types/input/Slider.d.ts +9 -6
- package/dist/types/input/Switch.d.ts +13 -8
- package/dist/types/input/Textarea.d.ts +15 -4
- package/dist/types/internal/ErrorIcon.d.ts +4 -1
- package/dist/types/internal/FieldWrapper.d.ts +10 -1
- package/dist/types/internal/InputWrapper.d.ts +19 -1
- package/dist/types/ranger/index.d.ts +7 -3
- package/dist/types/ranger/utils.d.ts +2 -2
- package/dist/types/select/AutoComplete.d.ts +7 -2
- package/dist/types/select/DualList.d.ts +11 -37
- package/dist/types/select/List.d.ts +7 -2
- package/dist/types/select/Options.d.ts +8 -7
- package/dist/types/select/Select.d.ts +7 -2
- package/dist/types/select/helpers.d.ts +2 -2
- package/dist/types/select/useSelect.d.ts +10 -28
- package/dist/types/types/index.d.ts +73 -39
- package/dist/types/types/schema.d.ts +78 -32
- package/dist/types/utils/eventHandlers.d.ts +4 -4
- package/dist/types/utils/getColor.d.ts +4 -4
- package/dist/types/utils/getProps.d.ts +26 -0
- package/dist/types/utils/index.d.ts +1 -0
- package/package.json +25 -25
- package/src/builder/FileField.tsx +73 -56
- package/src/builder/FormField.tsx +66 -58
- package/src/builder/SchemaEditor.tsx +78 -0
- package/src/builder/components/AddSchemaFields.tsx +64 -0
- package/src/builder/components/SchemaArrayForm.tsx +146 -0
- package/src/builder/components/SchemaField.tsx +405 -0
- package/src/form/Controller.tsx +61 -24
- package/src/form/Form.tsx +74 -50
- package/src/form/useForm.ts +53 -0
- package/src/hooks/useFileUploader.ts +115 -84
- package/src/hooks/useFormBuilder.tsx +80 -41
- package/src/i18n/{ar.json → ar/form.json} +5 -4
- package/src/i18n/{en.json → en/form.json} +5 -5
- package/src/i18n/index.ts +2 -2
- package/src/index.ts +34 -20
- package/src/input/ArrayInput.tsx +255 -173
- package/src/input/AvatarInput.tsx +82 -39
- package/src/input/Checkbox.tsx +30 -54
- package/src/input/ColorInput.tsx +146 -105
- package/src/input/ColorPicker.tsx +76 -102
- package/src/input/DateInput.tsx +225 -0
- package/src/input/Field.tsx +23 -65
- package/src/input/File.tsx +76 -98
- package/src/input/Hidden.tsx +36 -24
- package/src/input/Input.tsx +55 -53
- package/src/input/Number.tsx +49 -54
- package/src/input/Password.tsx +61 -66
- package/src/input/Radio.tsx +28 -37
- package/src/input/RadioGroup.tsx +30 -25
- package/src/input/{Range.tsx → RangeSlider.tsx} +81 -109
- package/src/input/Search.tsx +64 -77
- package/src/input/Slider.tsx +80 -109
- package/src/input/Switch.tsx +39 -53
- package/src/input/Textarea.tsx +72 -59
- package/src/internal/ErrorIcon.tsx +22 -21
- package/src/internal/FieldWrapper.tsx +33 -23
- package/src/internal/InputWrapper.tsx +132 -143
- package/src/ranger/index.tsx +37 -117
- package/src/ranger/utils.ts +12 -11
- package/src/select/AutoComplete.tsx +113 -123
- package/src/select/DualList.tsx +218 -204
- package/src/select/List.tsx +130 -97
- package/src/select/Options.tsx +59 -57
- package/src/select/Select.tsx +148 -122
- package/src/select/helpers.ts +13 -8
- package/src/select/useSelect.tsx +110 -127
- package/src/types/index.ts +76 -45
- package/src/types/schema.ts +97 -40
- package/src/utils/eventHandlers.ts +24 -26
- package/src/utils/getColor.ts +9 -8
- package/src/utils/getProps.ts +47 -0
- package/src/utils/index.ts +1 -0
- package/dist/cjs/input/ArrayInput.module.css +0 -1
- package/dist/cjs/input/ColorPicker.module.css +0 -1
- package/dist/cjs/input/File.module.css +0 -1
- package/dist/cjs/internal/Field.module.css +0 -1
- package/dist/esm/input/ArrayInput.module.css +0 -1
- package/dist/esm/input/ColorPicker.module.css +0 -1
- package/dist/esm/input/File.module.css +0 -1
- package/dist/esm/internal/Field.module.css +0 -1
package/src/input/File.tsx
CHANGED
|
@@ -21,34 +21,21 @@
|
|
|
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
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
Format,
|
|
29
|
-
isArray,
|
|
30
|
-
isEmpty,
|
|
31
|
-
mergeRefs,
|
|
32
|
-
} from "@react-fabric/utilities";
|
|
33
|
-
import classNames from "classnames";
|
|
34
|
-
import {
|
|
35
|
-
Fragment,
|
|
36
|
-
useCallback,
|
|
37
|
-
useDeferredValue,
|
|
38
|
-
useEffect,
|
|
39
|
-
useMemo,
|
|
40
|
-
useRef,
|
|
41
|
-
useState,
|
|
42
|
-
type ChangeEvent,
|
|
43
|
-
} from "react";
|
|
24
|
+
import { Icon, useControlledValue } from "@react-fabric/core";
|
|
25
|
+
import type { RefProp } from "@react-fabric/core/dist/types/types";
|
|
26
|
+
import { cn, FileUtil, Format, isArray, isEmpty, mergeRefs } from "@react-fabric/utilities";
|
|
27
|
+
import { Fragment, useCallback, useEffect, useId, useRef, useState } from "react";
|
|
44
28
|
import { InputWrapper } from "../internal/InputWrapper";
|
|
45
|
-
import {
|
|
46
|
-
import {
|
|
47
|
-
import classes from "./File.module.css";
|
|
29
|
+
import type { InputProps, WrapperProps } from "../types";
|
|
30
|
+
import { getWrapperProps, useHandleEnter } from "../utils";
|
|
48
31
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
32
|
+
interface FileType {
|
|
33
|
+
name: string;
|
|
34
|
+
type?: string;
|
|
35
|
+
size?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface FileInputProps extends InputProps<FileType | FileType[]>, WrapperProps, RefProp<HTMLInputElement> {
|
|
52
39
|
/**
|
|
53
40
|
* allow selecting mutiple files
|
|
54
41
|
*/
|
|
@@ -63,125 +50,119 @@ export interface FileInputProps
|
|
|
63
50
|
accept?: string;
|
|
64
51
|
}
|
|
65
52
|
|
|
66
|
-
|
|
53
|
+
function FileExists(prop: { name: string }) {
|
|
67
54
|
const [kill, setKill] = useState(false);
|
|
68
55
|
useEffect(() => {
|
|
69
|
-
setTimeout(() => {
|
|
56
|
+
const tmr = setTimeout(() => {
|
|
70
57
|
setKill(true);
|
|
71
58
|
}, 2000);
|
|
72
|
-
|
|
59
|
+
return () => {
|
|
60
|
+
clearTimeout(tmr);
|
|
61
|
+
};
|
|
62
|
+
}, []);
|
|
73
63
|
return kill ? null : (
|
|
74
|
-
<div className="bg-danger-500 text-white px-2 rounded text-xs">
|
|
75
|
-
File [{prop.name}] already added to list
|
|
76
|
-
</div>
|
|
64
|
+
<div className="bg-danger-500 text-white px-2 rounded text-xs">File [{prop.name}] already added to list</div>
|
|
77
65
|
);
|
|
78
|
-
}
|
|
66
|
+
}
|
|
79
67
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
68
|
+
/**
|
|
69
|
+
* File input component that allows users to select files from their system.
|
|
70
|
+
* It supports multiple file selection and displays a list of selected files.
|
|
71
|
+
* The component also provides functionality to remove files from the list.
|
|
72
|
+
* It handles file changes, invalid states, and read-only or disabled states.
|
|
73
|
+
*/
|
|
74
|
+
export function FileInput(props: FileInputProps) {
|
|
75
|
+
const {
|
|
76
|
+
ref,
|
|
77
|
+
value,
|
|
78
|
+
invalid,
|
|
79
|
+
readOnly,
|
|
80
|
+
disabled,
|
|
81
|
+
placeholder,
|
|
82
|
+
autoFocus,
|
|
83
|
+
error,
|
|
84
|
+
name,
|
|
85
|
+
accept,
|
|
86
|
+
showList = true,
|
|
87
|
+
multiple,
|
|
88
|
+
onChange,
|
|
89
|
+
onEnterPressed,
|
|
90
|
+
} = props;
|
|
98
91
|
const listRef = useRef<File[]>([]);
|
|
99
92
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
100
|
-
const [exists, setExists] = useState<string[]>([]);
|
|
101
|
-
const [actualValue, setActualValue] = useState<File | File[] | null>();
|
|
102
|
-
const deferred = useDeferredValue(value);
|
|
93
|
+
const [exists, setExists] = useState<string[]>(() => []);
|
|
103
94
|
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
[onEnterPressed],
|
|
107
|
-
);
|
|
95
|
+
const id = useId();
|
|
96
|
+
const { currentValue, updateValue } = useControlledValue(value, null);
|
|
108
97
|
|
|
109
|
-
|
|
110
|
-
setActualValue(deferred);
|
|
111
|
-
}, [deferred]);
|
|
98
|
+
const handleEnterPressed = useHandleEnter(onEnterPressed);
|
|
112
99
|
|
|
113
100
|
const handleChange = useCallback(
|
|
114
|
-
(e?: ChangeEvent<HTMLInputElement>) => {
|
|
101
|
+
(e?: React.ChangeEvent<HTMLInputElement>) => {
|
|
115
102
|
setExists([]);
|
|
116
103
|
if (multiple) {
|
|
117
104
|
if (e?.target.files) {
|
|
118
105
|
if (showList) {
|
|
119
106
|
const exists: string[] = [];
|
|
120
107
|
Array.from(e.target.files).forEach((file) => {
|
|
121
|
-
listRef.current.find((ef) => ef.name === file.name)
|
|
122
|
-
|
|
123
|
-
: listRef.current.push(file);
|
|
108
|
+
if (listRef.current.find((ef) => ef.name === file.name)) exists.push(file.name);
|
|
109
|
+
else listRef.current.push(file);
|
|
124
110
|
});
|
|
125
|
-
|
|
111
|
+
updateValue(listRef.current.slice());
|
|
126
112
|
setExists(exists);
|
|
127
113
|
}
|
|
128
114
|
e.target.value = "";
|
|
129
115
|
onChange?.(listRef.current);
|
|
130
116
|
} else {
|
|
131
117
|
listRef.current = [];
|
|
132
|
-
|
|
118
|
+
updateValue(listRef.current);
|
|
133
119
|
onChange?.(listRef.current);
|
|
134
120
|
}
|
|
135
121
|
} else {
|
|
136
|
-
|
|
122
|
+
updateValue(e?.target.files?.item(0));
|
|
137
123
|
onChange?.(e?.target.files?.item(0) ?? undefined);
|
|
138
124
|
if (!e && inputRef.current) inputRef.current.value = "";
|
|
139
125
|
}
|
|
140
126
|
},
|
|
141
|
-
[
|
|
127
|
+
[multiple, showList, onChange, updateValue],
|
|
142
128
|
);
|
|
143
129
|
|
|
144
130
|
const removeFile = useCallback(
|
|
145
131
|
(idx: number) => {
|
|
146
132
|
listRef.current.splice(idx, 1);
|
|
147
|
-
|
|
133
|
+
updateValue(listRef.current);
|
|
148
134
|
onChange?.(listRef.current);
|
|
149
135
|
},
|
|
150
|
-
[onChange],
|
|
136
|
+
[onChange, updateValue],
|
|
151
137
|
);
|
|
152
138
|
|
|
153
139
|
return (
|
|
154
140
|
<InputWrapper
|
|
155
|
-
|
|
141
|
+
id={id}
|
|
142
|
+
showClear={!isEmpty(currentValue) && !disabled && !readOnly}
|
|
156
143
|
onClear={handleChange}
|
|
157
|
-
|
|
158
|
-
readOnly={readOnly}
|
|
159
|
-
disabled={disabled}
|
|
160
|
-
required={required}
|
|
161
|
-
error={error}
|
|
144
|
+
{...getWrapperProps(props)}
|
|
162
145
|
listing={
|
|
163
146
|
showList && (
|
|
164
147
|
<Fragment>
|
|
165
|
-
{isArray(
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
className="flex gap-1 items-center text-sm text-dimmed py-1 px-2"
|
|
170
|
-
>
|
|
148
|
+
{isArray(currentValue) &&
|
|
149
|
+
currentValue?.map?.((file, idx: number) => (
|
|
150
|
+
// eslint-disable-next-line @eslint-react/no-array-index-key
|
|
151
|
+
<div key={idx} className="flex gap-1 items-center text-sm text-dimmed py-1 px-2">
|
|
171
152
|
<div className="flex-1 truncate">
|
|
172
153
|
<div className="font-medium">{file.name}</div>
|
|
173
154
|
<span className="text-xs">{FileUtil.mime(file.type)}</span>
|
|
174
155
|
</div>
|
|
175
|
-
<span className="flex-content text-end w-24">
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
className="cursor-pointer opacity-30 hover:opacity-80"
|
|
180
|
-
icon={CoreIcons.close}
|
|
156
|
+
<span className="flex-content text-end w-24">{Format.bytes(file.size)}</span>
|
|
157
|
+
<button
|
|
158
|
+
type="button"
|
|
159
|
+
className={cn("size-fit leading-0 cursor-pointer")}
|
|
181
160
|
onClick={() => {
|
|
182
161
|
removeFile(idx);
|
|
183
162
|
}}
|
|
184
|
-
|
|
163
|
+
>
|
|
164
|
+
<Icon className="cursor-pointer opacity-30 hover:opacity-80" icon="icon-[mdi--close]" />
|
|
165
|
+
</button>
|
|
185
166
|
</div>
|
|
186
167
|
))}
|
|
187
168
|
{exists.map((name) => (
|
|
@@ -190,15 +171,12 @@ export const FileInput = ({
|
|
|
190
171
|
</Fragment>
|
|
191
172
|
)
|
|
192
173
|
}
|
|
193
|
-
{...rest}
|
|
194
174
|
>
|
|
195
175
|
<input
|
|
196
|
-
className={
|
|
197
|
-
"
|
|
198
|
-
|
|
199
|
-
isArray(
|
|
200
|
-
actualValue?.length &&
|
|
201
|
-
"text-[var(--fabric-bg)]",
|
|
176
|
+
className={cn(
|
|
177
|
+
"fabric-fileInput",
|
|
178
|
+
"appearance-none bg-transparent py-1 px-2 flex-1 border-none outline-none ring-0 peer",
|
|
179
|
+
isArray(currentValue) && currentValue?.length && "text-(--fabric-bg)",
|
|
202
180
|
)}
|
|
203
181
|
aria-invalid={invalid}
|
|
204
182
|
aria-disabled={disabled}
|
|
@@ -221,4 +199,4 @@ export const FileInput = ({
|
|
|
221
199
|
/>
|
|
222
200
|
</InputWrapper>
|
|
223
201
|
);
|
|
224
|
-
}
|
|
202
|
+
}
|
package/src/input/Hidden.tsx
CHANGED
|
@@ -21,7 +21,10 @@
|
|
|
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
|
-
|
|
24
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
25
|
+
|
|
26
|
+
import { isEqual } from "@react-fabric/utilities";
|
|
27
|
+
import { Fragment, useDeferredValue, useEffect, useRef } from "react";
|
|
25
28
|
import { ErrorIcon } from "../internal/ErrorIcon";
|
|
26
29
|
|
|
27
30
|
interface Props {
|
|
@@ -30,36 +33,45 @@ interface Props {
|
|
|
30
33
|
*/
|
|
31
34
|
name?: string;
|
|
32
35
|
/**
|
|
33
|
-
* hidden value
|
|
36
|
+
* hidden input value
|
|
34
37
|
*/
|
|
35
38
|
hiddenValue?: AnyObject;
|
|
36
|
-
/**
|
|
37
|
-
* form onChange handler
|
|
38
|
-
*/
|
|
39
|
-
onChange?: (value: AnyObject) => void;
|
|
40
39
|
}
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Hidden input component to handle hidden values in forms.
|
|
43
|
+
* It will trigger the onChange handler with the hidden value after a short delay.
|
|
44
|
+
* This is useful for cases where you need to update the form state without user interaction.
|
|
45
|
+
*/
|
|
46
|
+
export function HiddenInput(props: Props) {
|
|
47
|
+
const {
|
|
48
|
+
// @ts-expect-error ignore
|
|
49
|
+
invalid = false,
|
|
50
|
+
// @ts-expect-error ignore
|
|
51
|
+
error = "",
|
|
52
|
+
// @ts-expect-error ignore
|
|
53
|
+
onChange,
|
|
54
|
+
// @ts-expect-error ignore
|
|
55
|
+
value,
|
|
56
|
+
hiddenValue,
|
|
57
|
+
name,
|
|
58
|
+
} = props;
|
|
59
|
+
|
|
60
|
+
const valueRef = useRef(value);
|
|
61
|
+
const deferred = useDeferredValue(hiddenValue);
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
valueRef.current = value;
|
|
64
|
+
}, [value]);
|
|
56
65
|
useEffect(() => {
|
|
57
|
-
|
|
58
|
-
|
|
66
|
+
if (!isEqual(valueRef.current, deferred)) {
|
|
67
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
68
|
+
onChange?.(deferred);
|
|
69
|
+
}
|
|
70
|
+
}, [deferred, onChange]);
|
|
59
71
|
return (
|
|
60
72
|
<Fragment>
|
|
61
|
-
<input type="hidden"
|
|
73
|
+
<input type="hidden" value={value ?? ""} name={name} />
|
|
62
74
|
<ErrorIcon invalid={invalid} error={error} />
|
|
63
75
|
</Fragment>
|
|
64
76
|
);
|
|
65
|
-
}
|
|
77
|
+
}
|
package/src/input/Input.tsx
CHANGED
|
@@ -21,76 +21,76 @@
|
|
|
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
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
useMemo,
|
|
30
|
-
useState,
|
|
31
|
-
type ChangeEvent,
|
|
32
|
-
} from "react";
|
|
24
|
+
import { useControlledValue } from "@react-fabric/core";
|
|
25
|
+
import type { RefProp } from "@react-fabric/core/dist/types/types";
|
|
26
|
+
import { cn } from "@react-fabric/utilities";
|
|
27
|
+
import type { InputHTMLAttributes } from "react";
|
|
28
|
+
import { useCallback, useId } from "react";
|
|
33
29
|
import { InputWrapper } from "../internal/InputWrapper";
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
30
|
+
import type { InputProps, WrapperProps } from "../types";
|
|
31
|
+
import { getWrapperProps, useHandleEnter } from "../utils";
|
|
36
32
|
|
|
37
|
-
export interface TextInputProps extends InputProps, RefProp<HTMLInputElement> {
|
|
33
|
+
export interface TextInputProps extends InputProps, WrapperProps, RefProp<HTMLInputElement> {
|
|
38
34
|
/**
|
|
39
35
|
* input type
|
|
40
36
|
*/
|
|
41
37
|
type?: "email" | "file" | "search" | "tel" | "text" | "url";
|
|
38
|
+
|
|
39
|
+
autoComplete?: InputHTMLAttributes<HTMLInputElement>["autoComplete"];
|
|
42
40
|
}
|
|
43
41
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
42
|
+
/**
|
|
43
|
+
* This component is used to render a text input field with various properties such as name, value, invalid state, read-only, disabled, required, placeholder, autoFocus, error message, monospace font option, and event handlers for blur, focus, change, and enter key press.
|
|
44
|
+
* It also includes a clear button that appears when there is a value in the input field, allowing users to clear the input easily.
|
|
45
|
+
* The component uses a wrapper to handle the clear button and styling, and it supports deferred value updates to optimize performance.
|
|
46
|
+
*/
|
|
47
|
+
export function Input(props: TextInputProps) {
|
|
48
|
+
const {
|
|
49
|
+
value,
|
|
50
|
+
defaultValue,
|
|
51
|
+
onEnterPressed,
|
|
52
|
+
onChange,
|
|
53
|
+
onBlur,
|
|
54
|
+
onFocus,
|
|
55
|
+
disabled,
|
|
56
|
+
readOnly,
|
|
57
|
+
invalid,
|
|
58
|
+
error,
|
|
59
|
+
placeholder,
|
|
60
|
+
name,
|
|
61
|
+
type,
|
|
62
|
+
autoFocus,
|
|
63
|
+
allowClear,
|
|
64
|
+
autoComplete = "off",
|
|
65
|
+
ref,
|
|
66
|
+
} = props;
|
|
62
67
|
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
[onEnterPressed],
|
|
66
|
-
);
|
|
68
|
+
const id = useId();
|
|
69
|
+
const { currentValue, updateValue } = useControlledValue(value, defaultValue);
|
|
67
70
|
|
|
68
|
-
|
|
69
|
-
setActualValue(deferred ?? "");
|
|
70
|
-
}, [deferred]);
|
|
71
|
+
const handleEnterPressed = useHandleEnter(onEnterPressed);
|
|
71
72
|
|
|
72
73
|
const handleChange = useCallback(
|
|
73
|
-
(e?: ChangeEvent<HTMLInputElement>) => {
|
|
74
|
-
const value = e?.target.value;
|
|
75
|
-
|
|
76
|
-
onChange?.(value
|
|
74
|
+
(e?: React.ChangeEvent<HTMLInputElement>) => {
|
|
75
|
+
const value = e?.target.value ?? defaultValue;
|
|
76
|
+
updateValue(value);
|
|
77
|
+
onChange?.(value);
|
|
77
78
|
},
|
|
78
|
-
[
|
|
79
|
+
[defaultValue, updateValue, onChange],
|
|
79
80
|
);
|
|
80
81
|
|
|
81
82
|
return (
|
|
82
83
|
<InputWrapper
|
|
83
|
-
|
|
84
|
+
id={id}
|
|
85
|
+
showClear={allowClear && (currentValue ?? "") !== (defaultValue ?? "") && !disabled && !readOnly}
|
|
84
86
|
onClear={handleChange}
|
|
85
|
-
|
|
86
|
-
readOnly={readOnly}
|
|
87
|
-
disabled={disabled}
|
|
88
|
-
required={required}
|
|
89
|
-
error={error}
|
|
90
|
-
{...rest}
|
|
87
|
+
{...getWrapperProps(props)}
|
|
91
88
|
>
|
|
92
89
|
<input
|
|
93
|
-
|
|
90
|
+
id={id}
|
|
91
|
+
className={cn(
|
|
92
|
+
"appearance-none bg-transparent py-1 px-4 not-first:ps-2 not-last:pe-2 flex-1 border-none outline-none ring-0 peer",
|
|
93
|
+
)}
|
|
94
94
|
aria-invalid={invalid}
|
|
95
95
|
aria-disabled={disabled}
|
|
96
96
|
aria-readonly={readOnly}
|
|
@@ -103,12 +103,14 @@ export const Input = ({
|
|
|
103
103
|
size={1}
|
|
104
104
|
type={type}
|
|
105
105
|
ref={ref}
|
|
106
|
-
autoComplete=
|
|
106
|
+
autoComplete={autoComplete}
|
|
107
107
|
{...{ autoFocus }}
|
|
108
|
-
value={
|
|
108
|
+
value={currentValue ?? ""}
|
|
109
|
+
onBlur={onBlur}
|
|
110
|
+
onFocus={onFocus}
|
|
109
111
|
onChange={handleChange}
|
|
110
112
|
onKeyDown={handleEnterPressed}
|
|
111
113
|
/>
|
|
112
114
|
</InputWrapper>
|
|
113
115
|
);
|
|
114
|
-
}
|
|
116
|
+
}
|
package/src/input/Number.tsx
CHANGED
|
@@ -21,22 +21,14 @@
|
|
|
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
|
-
import {
|
|
26
|
-
|
|
27
|
-
useDeferredValue,
|
|
28
|
-
useEffect,
|
|
29
|
-
useMemo,
|
|
30
|
-
useState,
|
|
31
|
-
type ChangeEvent,
|
|
32
|
-
} from "react";
|
|
24
|
+
import { useControlledValue } from "@react-fabric/core";
|
|
25
|
+
import type { RefProp } from "@react-fabric/core/dist/types/types";
|
|
26
|
+
import { useCallback, useId } from "react";
|
|
33
27
|
import { InputWrapper } from "../internal/InputWrapper";
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
28
|
+
import type { InputProps, WrapperProps } from "../types";
|
|
29
|
+
import { getWrapperProps, useHandleEnter } from "../utils";
|
|
36
30
|
|
|
37
|
-
export interface NumberProps
|
|
38
|
-
extends InputProps<number>,
|
|
39
|
-
RefProp<HTMLInputElement> {
|
|
31
|
+
export interface NumberProps extends InputProps<number>, WrapperProps, RefProp<HTMLInputElement> {
|
|
40
32
|
/**
|
|
41
33
|
* minimum value
|
|
42
34
|
*/
|
|
@@ -51,56 +43,57 @@ export interface NumberProps
|
|
|
51
43
|
step?: number;
|
|
52
44
|
}
|
|
53
45
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
46
|
+
/**
|
|
47
|
+
* This component allows users to input numeric values with optional minimum, maximum, and step constraints.
|
|
48
|
+
* It provides a clear interface for entering numbers, with support for validation and accessibility features.
|
|
49
|
+
* It also includes a clear button to reset the input value.
|
|
50
|
+
*/
|
|
51
|
+
export function Number(props: NumberProps) {
|
|
52
|
+
const {
|
|
53
|
+
value,
|
|
54
|
+
defaultValue,
|
|
55
|
+
onEnterPressed,
|
|
56
|
+
onChange,
|
|
57
|
+
onBlur,
|
|
58
|
+
onFocus,
|
|
59
|
+
disabled,
|
|
60
|
+
readOnly,
|
|
61
|
+
invalid,
|
|
62
|
+
min,
|
|
63
|
+
max,
|
|
64
|
+
step,
|
|
65
|
+
placeholder,
|
|
66
|
+
name,
|
|
67
|
+
autoFocus,
|
|
68
|
+
allowClear,
|
|
69
|
+
ref,
|
|
70
|
+
} = props;
|
|
73
71
|
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
[onEnterPressed],
|
|
77
|
-
);
|
|
72
|
+
const id = useId();
|
|
73
|
+
const { currentValue, updateValue } = useControlledValue(value, defaultValue);
|
|
78
74
|
|
|
79
|
-
|
|
80
|
-
setActualValue(deferred ?? "");
|
|
81
|
-
}, [deferred]);
|
|
75
|
+
const handleEnterPressed = useHandleEnter(onEnterPressed);
|
|
82
76
|
|
|
83
77
|
const handleChange = useCallback(
|
|
84
|
-
(e?: ChangeEvent<HTMLInputElement>) => {
|
|
85
|
-
const number = e?.target.valueAsNumber;
|
|
86
|
-
|
|
87
|
-
onChange?.(number
|
|
78
|
+
(e?: React.ChangeEvent<HTMLInputElement>) => {
|
|
79
|
+
const number = e?.target.valueAsNumber ?? defaultValue;
|
|
80
|
+
updateValue(number);
|
|
81
|
+
onChange?.(number);
|
|
88
82
|
},
|
|
89
|
-
[onChange],
|
|
83
|
+
[defaultValue, updateValue, onChange],
|
|
90
84
|
);
|
|
91
85
|
|
|
92
86
|
return (
|
|
93
87
|
<InputWrapper
|
|
94
|
-
|
|
88
|
+
id={id}
|
|
89
|
+
showClear={allowClear && (currentValue ?? "") !== (defaultValue ?? "") && !disabled && !readOnly}
|
|
95
90
|
onClear={handleChange}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
disabled={disabled}
|
|
99
|
-
required={required}
|
|
100
|
-
{...rest}
|
|
91
|
+
hasStepper
|
|
92
|
+
{...getWrapperProps(props)}
|
|
101
93
|
>
|
|
102
94
|
<input
|
|
103
|
-
|
|
95
|
+
id={id}
|
|
96
|
+
className="appearance-none bg-transparent py-1 ps-4 pe-2 not-first:ps-2 flex-1 border-none outline-none ring-0 peer"
|
|
104
97
|
aria-invalid={invalid}
|
|
105
98
|
aria-disabled={disabled}
|
|
106
99
|
aria-readonly={readOnly}
|
|
@@ -117,10 +110,12 @@ export const Number = ({
|
|
|
117
110
|
ref={ref}
|
|
118
111
|
autoComplete="off"
|
|
119
112
|
{...{ autoFocus }}
|
|
120
|
-
value={
|
|
113
|
+
value={currentValue ?? ""}
|
|
114
|
+
onBlur={onBlur}
|
|
115
|
+
onFocus={onFocus}
|
|
121
116
|
onChange={handleChange}
|
|
122
117
|
onKeyDown={handleEnterPressed}
|
|
123
118
|
/>
|
|
124
119
|
</InputWrapper>
|
|
125
120
|
);
|
|
126
|
-
}
|
|
121
|
+
}
|