@react-fabric/form 1.0.0-beta.6 → 1.0.0-beta.60
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 +65 -0
- package/{src/internal/Field.module.css → css/_field.css} +66 -30
- package/{src/input/File.module.css → css/_file.css} +21 -6
- package/css/styles.css +27 -0
- package/dist/cjs/builder/FileField.js +40 -28
- package/dist/cjs/builder/FormField.js +50 -34
- package/dist/cjs/builder/SchemaEditor.js +72 -0
- package/dist/cjs/builder/components/AddSchemaFields.js +62 -0
- package/dist/cjs/builder/components/SchemaArrayForm.js +145 -0
- package/dist/cjs/builder/components/SchemaField.js +414 -0
- package/dist/cjs/form/Controller.js +46 -1
- package/dist/cjs/form/Form.js +70 -9
- package/dist/cjs/hooks/useFileUploader.js +16 -0
- package/dist/cjs/hooks/useFormBuilder.js +57 -15
- package/dist/cjs/i18n/ar.json.js +5 -4
- package/dist/cjs/i18n/en.json.js +5 -5
- package/dist/cjs/index.js +12 -2
- package/dist/cjs/input/ArrayInput.js +126 -36
- package/dist/cjs/input/AvatarInput.js +43 -23
- package/dist/cjs/input/Checkbox.js +16 -6
- package/dist/cjs/input/ColorInput.js +113 -58
- package/dist/cjs/input/ColorPicker.js +60 -76
- package/dist/cjs/input/DateInput.js +197 -0
- package/dist/cjs/input/Field.js +10 -4
- package/dist/cjs/input/File.js +14 -7
- package/dist/cjs/input/Hidden.js +13 -2
- package/dist/cjs/input/Input.js +24 -10
- package/dist/cjs/input/Number.js +22 -10
- package/dist/cjs/input/Password.js +22 -12
- package/dist/cjs/input/Radio.js +12 -3
- package/dist/cjs/input/RadioGroup.js +10 -1
- package/dist/cjs/input/{Range.js → RangeSlider.js} +16 -8
- package/dist/cjs/input/Search.js +30 -19
- package/dist/cjs/input/Slider.js +12 -6
- package/dist/cjs/input/Switch.js +20 -9
- package/dist/cjs/input/Textarea.js +29 -8
- package/dist/cjs/internal/ErrorIcon.js +1 -1
- package/dist/cjs/internal/FieldWrapper.js +11 -4
- package/dist/cjs/internal/InputWrapper.js +27 -20
- package/dist/cjs/select/AutoComplete.js +21 -11
- package/dist/cjs/select/DualList.js +34 -12
- package/dist/cjs/select/List.js +68 -22
- package/dist/cjs/select/Options.js +49 -42
- package/dist/cjs/select/Select.js +40 -10
- package/dist/cjs/select/useSelect.js +22 -11
- package/dist/cjs/types/schema.js +43 -0
- package/dist/cjs/utils/eventHandlers.js +15 -5
- package/dist/esm/builder/FileField.js +42 -30
- package/dist/esm/builder/FormField.js +50 -34
- package/dist/esm/builder/SchemaEditor.js +72 -0
- package/dist/esm/builder/components/AddSchemaFields.js +62 -0
- package/dist/esm/builder/components/SchemaArrayForm.js +145 -0
- package/dist/esm/builder/components/SchemaField.js +414 -0
- package/dist/esm/form/Controller.js +46 -1
- package/dist/esm/form/Form.js +72 -12
- package/dist/esm/hooks/useFileUploader.js +16 -0
- package/dist/esm/hooks/useFormBuilder.js +55 -13
- package/dist/esm/i18n/ar.json.js +5 -4
- package/dist/esm/i18n/en.json.js +5 -5
- package/dist/esm/index.js +6 -2
- package/dist/esm/input/ArrayInput.js +126 -36
- package/dist/esm/input/AvatarInput.js +44 -24
- package/dist/esm/input/Checkbox.js +16 -6
- package/dist/esm/input/ColorInput.js +115 -60
- package/dist/esm/input/ColorPicker.js +60 -76
- package/dist/esm/input/DateInput.js +197 -0
- package/dist/esm/input/Field.js +11 -5
- package/dist/esm/input/File.js +16 -9
- package/dist/esm/input/Hidden.js +13 -2
- package/dist/esm/input/Input.js +26 -12
- package/dist/esm/input/Number.js +24 -12
- package/dist/esm/input/Password.js +24 -14
- package/dist/esm/input/Radio.js +12 -3
- package/dist/esm/input/RadioGroup.js +11 -2
- package/dist/esm/input/{Range.js → RangeSlider.js} +16 -8
- package/dist/esm/input/Search.js +32 -21
- package/dist/esm/input/Slider.js +12 -6
- package/dist/esm/input/Switch.js +20 -9
- package/dist/esm/input/Textarea.js +30 -9
- package/dist/esm/internal/ErrorIcon.js +1 -1
- package/dist/esm/internal/FieldWrapper.js +11 -4
- package/dist/esm/internal/InputWrapper.js +27 -20
- package/dist/esm/select/AutoComplete.js +21 -11
- package/dist/esm/select/DualList.js +34 -12
- package/dist/esm/select/List.js +69 -23
- package/dist/esm/select/Options.js +49 -42
- package/dist/esm/select/Select.js +40 -10
- package/dist/esm/select/useSelect.js +23 -12
- package/{src/input/ColorPicker.module.css → dist/esm/types/schema.js} +18 -27
- package/dist/esm/utils/eventHandlers.js +15 -5
- package/dist/types/builder/FileField.d.ts +4 -2
- package/dist/types/builder/FormField.d.ts +3 -1
- package/dist/types/builder/SchemaEditor.d.ts +9 -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 +10 -0
- package/dist/types/form/Controller.d.ts +47 -2
- package/dist/types/form/Form.d.ts +48 -2
- package/dist/types/hooks/useFileUploader.d.ts +16 -0
- package/dist/types/hooks/useFormBuilder.d.ts +32 -1
- package/dist/types/index.d.ts +6 -3
- package/dist/types/input/ArrayInput.d.ts +69 -12
- package/dist/types/input/AvatarInput.d.ts +23 -3
- package/dist/types/input/Checkbox.d.ts +13 -4
- package/dist/types/input/ColorInput.d.ts +15 -2
- package/dist/types/input/ColorPicker.d.ts +11 -1
- package/dist/types/input/DateInput.d.ts +28 -0
- package/dist/types/input/Field.d.ts +5 -1
- package/dist/types/input/File.d.ts +9 -0
- package/dist/types/input/Hidden.d.ts +13 -1
- package/dist/types/input/Input.d.ts +9 -1
- package/dist/types/input/Number.d.ts +9 -1
- package/dist/types/input/Password.d.ts +7 -1
- package/dist/types/input/Radio.d.ts +9 -1
- package/dist/types/input/RadioGroup.d.ts +8 -1
- package/dist/types/input/RangeSlider.d.ts +67 -0
- package/dist/types/input/Search.d.ts +14 -1
- package/dist/types/input/Slider.d.ts +8 -2
- package/dist/types/input/Switch.d.ts +10 -2
- package/dist/types/input/Textarea.d.ts +15 -1
- package/dist/types/internal/FieldWrapper.d.ts +1 -1
- package/dist/types/internal/InputWrapper.d.ts +1 -1
- package/dist/types/ranger/index.d.ts +2 -2
- package/dist/types/select/AutoComplete.d.ts +8 -1
- package/dist/types/select/DualList.d.ts +17 -2
- package/dist/types/select/List.d.ts +9 -1
- package/dist/types/select/Options.d.ts +4 -3
- package/dist/types/select/Select.d.ts +9 -1
- package/dist/types/select/useSelect.d.ts +10 -6
- package/dist/types/types/index.d.ts +34 -5
- package/dist/types/types/schema.d.ts +78 -33
- package/dist/types/utils/eventHandlers.d.ts +2 -2
- package/package.json +22 -20
- package/src/builder/FileField.tsx +62 -30
- package/src/builder/FormField.tsx +52 -32
- package/src/builder/SchemaEditor.tsx +79 -0
- package/src/builder/components/AddSchemaFields.tsx +74 -0
- package/src/builder/components/SchemaArrayForm.tsx +161 -0
- package/src/builder/components/SchemaField.tsx +436 -0
- package/src/form/Controller.tsx +48 -3
- package/src/form/Form.tsx +80 -8
- package/src/hooks/useFileUploader.ts +16 -0
- package/src/hooks/useFormBuilder.tsx +58 -15
- package/src/i18n/ar.json +5 -4
- package/src/i18n/en.json +5 -5
- package/src/index.ts +12 -3
- package/src/input/ArrayInput.tsx +199 -64
- package/src/input/AvatarInput.tsx +67 -19
- package/src/input/Checkbox.tsx +16 -6
- package/src/input/ColorInput.tsx +131 -71
- package/src/input/ColorPicker.tsx +54 -72
- package/src/input/DateInput.tsx +260 -0
- package/src/input/Field.tsx +15 -11
- package/src/input/File.tsx +16 -14
- package/src/input/Hidden.tsx +17 -2
- package/src/input/Input.tsx +27 -13
- package/src/input/Number.tsx +22 -13
- package/src/input/Password.tsx +24 -16
- package/src/input/Radio.tsx +12 -3
- package/src/input/RadioGroup.tsx +12 -3
- package/src/input/{Range.tsx → RangeSlider.tsx} +17 -9
- package/src/input/Search.tsx +33 -22
- package/src/input/Slider.tsx +20 -11
- package/src/input/Switch.tsx +17 -6
- package/src/input/Textarea.tsx +34 -9
- package/src/internal/ErrorIcon.tsx +1 -1
- package/src/internal/FieldWrapper.tsx +8 -4
- package/src/internal/InputWrapper.tsx +49 -27
- package/src/select/AutoComplete.tsx +14 -3
- package/src/select/DualList.tsx +31 -16
- package/src/select/List.tsx +52 -8
- package/src/select/Options.tsx +50 -46
- package/src/select/Select.tsx +25 -5
- package/src/select/useSelect.tsx +26 -9
- package/src/types/index.ts +37 -6
- package/src/types/schema.ts +96 -41
- package/src/utils/eventHandlers.ts +14 -4
- 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/dist/types/input/Range.d.ts +0 -59
package/src/select/Options.tsx
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
import { EmptyContent } from "@react-fabric/core";
|
|
25
25
|
import classNames from "classnames";
|
|
26
|
-
import { type ReactElement
|
|
26
|
+
import { type ReactElement } from "react";
|
|
27
27
|
import { Trans } from "react-i18next";
|
|
28
28
|
|
|
29
29
|
export const Options = ({
|
|
@@ -34,6 +34,7 @@ export const Options = ({
|
|
|
34
34
|
className,
|
|
35
35
|
itemRef,
|
|
36
36
|
empty,
|
|
37
|
+
info,
|
|
37
38
|
valueProperty = "id",
|
|
38
39
|
labelProperty = "label",
|
|
39
40
|
itemProps,
|
|
@@ -41,6 +42,7 @@ export const Options = ({
|
|
|
41
42
|
...rest
|
|
42
43
|
}: {
|
|
43
44
|
ref: AnyObject;
|
|
45
|
+
info?: AnyObject;
|
|
44
46
|
itemRef: AnyObject;
|
|
45
47
|
style?: AnyObject;
|
|
46
48
|
items: AnyObject[];
|
|
@@ -50,34 +52,53 @@ export const Options = ({
|
|
|
50
52
|
valueProperty?: AnyObject;
|
|
51
53
|
labelProperty?: AnyObject;
|
|
52
54
|
itemProps: (item: AnyObject) => AnyObject;
|
|
53
|
-
children: (item: AnyObject, label: string) => ReactNode;
|
|
55
|
+
children: (item: AnyObject, label: string) => React.ReactNode;
|
|
54
56
|
} & KeyValue) => {
|
|
55
57
|
return (
|
|
56
58
|
<div
|
|
57
59
|
ref={ref}
|
|
58
|
-
className={classNames(
|
|
59
|
-
"bg-base select-none overflow-x-hidden overflow-y-auto",
|
|
60
|
-
className,
|
|
61
|
-
)}
|
|
60
|
+
className={classNames("bg-base select-none flex flex-col", className)}
|
|
62
61
|
style={style}
|
|
63
62
|
{...rest}
|
|
64
63
|
>
|
|
65
|
-
{
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
64
|
+
<div className={classNames("overflow-x-hidden overflow-y-auto flex-1")}>
|
|
65
|
+
{items.length === 0 &&
|
|
66
|
+
(empty ?? (
|
|
67
|
+
<EmptyContent message={<Trans i18nKey="form:select.emptyList" />} />
|
|
68
|
+
))}
|
|
69
|
+
{items.map((item, index) => {
|
|
70
|
+
if (item.___group___)
|
|
71
|
+
return (
|
|
72
|
+
<p
|
|
73
|
+
key={index}
|
|
74
|
+
className="px-2 py-1 text-muted text-xs sticky -top-px z-2 bg-base border-t"
|
|
75
|
+
>
|
|
76
|
+
{item.label}
|
|
77
|
+
</p>
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
if (item.___create___)
|
|
81
|
+
return (
|
|
82
|
+
<div
|
|
83
|
+
role="none"
|
|
84
|
+
key={index}
|
|
85
|
+
tabIndex={active === index ? 0 : -1}
|
|
86
|
+
ref={(el: AnyObject) => itemRef(index, el)}
|
|
87
|
+
data-active={active === index ? true : undefined}
|
|
88
|
+
className={classNames(
|
|
89
|
+
"block text-start truncate px-2 py-1 leading-normal cursor-pointer select-none",
|
|
90
|
+
"data-active:bg-primary-200 data-selected:bg-primary-600 data-selected:text-white data-active:data-selected:underline",
|
|
91
|
+
)}
|
|
92
|
+
{...itemProps(item)}
|
|
93
|
+
>
|
|
94
|
+
<Trans
|
|
95
|
+
i18nKey="form:select.createOption"
|
|
96
|
+
values={{ query: item.value ?? item }}
|
|
97
|
+
components={[<strong key="t0">query</strong>]}
|
|
98
|
+
/>
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
79
101
|
|
|
80
|
-
if (item.___create___)
|
|
81
102
|
return (
|
|
82
103
|
<div
|
|
83
104
|
role="none"
|
|
@@ -86,36 +107,19 @@ export const Options = ({
|
|
|
86
107
|
ref={(el: AnyObject) => itemRef(index, el)}
|
|
87
108
|
data-active={active === index ? true : undefined}
|
|
88
109
|
className={classNames(
|
|
89
|
-
"block text-start truncate px-2 py-1 leading-normal cursor-pointer
|
|
90
|
-
"data-
|
|
110
|
+
"block text-start truncate px-2 py-1 leading-normal cursor-pointer",
|
|
111
|
+
"data-active:bg-primary-200 data-selected:bg-primary-600 data-selected:text-white data-active:data-selected:underline",
|
|
91
112
|
)}
|
|
92
113
|
{...itemProps(item)}
|
|
93
114
|
>
|
|
94
|
-
|
|
95
|
-
i18nKey="form:select.createOption"
|
|
96
|
-
values={{ query: item.value }}
|
|
97
|
-
components={[<strong key="t0">query</strong>]}
|
|
98
|
-
/>
|
|
115
|
+
{children(item, item[labelProperty] ?? item)}
|
|
99
116
|
</div>
|
|
100
117
|
);
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
tabIndex={active === index ? 0 : -1}
|
|
107
|
-
ref={(el: AnyObject) => itemRef(index, el)}
|
|
108
|
-
data-active={active === index ? true : undefined}
|
|
109
|
-
className={classNames(
|
|
110
|
-
"block text-start truncate px-2 py-1 leading-normal cursor-pointer",
|
|
111
|
-
"data-[active]:bg-primary-200 data-[selected]:bg-primary-600 data-[selected]:text-white data-[active]:data-[selected]:underline",
|
|
112
|
-
)}
|
|
113
|
-
{...itemProps(item)}
|
|
114
|
-
>
|
|
115
|
-
{children(item, item[labelProperty] ?? item)}
|
|
116
|
-
</div>
|
|
117
|
-
);
|
|
118
|
-
})}
|
|
118
|
+
})}
|
|
119
|
+
</div>
|
|
120
|
+
{info && (
|
|
121
|
+
<div className="px-2 py-1 text-sm bg-dimmed text-muted">{info}</div>
|
|
122
|
+
)}
|
|
119
123
|
</div>
|
|
120
124
|
);
|
|
121
125
|
};
|
package/src/select/Select.tsx
CHANGED
|
@@ -44,6 +44,14 @@ import { type SelectProps } from "../types";
|
|
|
44
44
|
import { Options } from "./Options";
|
|
45
45
|
import { useSelect } from "./useSelect";
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Select component for selecting items from a dropdown list.
|
|
49
|
+
* It supports single and multiple selections, searching, creating new options,
|
|
50
|
+
* and custom rendering of options.
|
|
51
|
+
*
|
|
52
|
+
* @param {SelectProps<T>} props - The properties for the Select component.
|
|
53
|
+
* @returns {JSX.Element} The rendered Select component.
|
|
54
|
+
*/
|
|
47
55
|
export const Select = <T extends AnyObject = string>({
|
|
48
56
|
ref,
|
|
49
57
|
name,
|
|
@@ -56,12 +64,17 @@ export const Select = <T extends AnyObject = string>({
|
|
|
56
64
|
autoFocus,
|
|
57
65
|
error,
|
|
58
66
|
options,
|
|
67
|
+
defaultOpen,
|
|
68
|
+
hideHandle,
|
|
59
69
|
groupProperty,
|
|
60
70
|
labelProperty = "label" as AnyObject,
|
|
61
71
|
valueProperty = "id" as AnyObject,
|
|
72
|
+
sortProperty,
|
|
73
|
+
infoProperty,
|
|
62
74
|
multiple,
|
|
63
75
|
searchable,
|
|
64
76
|
allowCreate,
|
|
77
|
+
emptyDisplay,
|
|
65
78
|
createOption,
|
|
66
79
|
matcher,
|
|
67
80
|
renderer,
|
|
@@ -86,9 +99,11 @@ export const Select = <T extends AnyObject = string>({
|
|
|
86
99
|
value,
|
|
87
100
|
options,
|
|
88
101
|
multiple,
|
|
102
|
+
defaultOpen,
|
|
89
103
|
groupProperty,
|
|
90
104
|
labelProperty,
|
|
91
105
|
valueProperty,
|
|
106
|
+
sortProperty,
|
|
92
107
|
allowCreate,
|
|
93
108
|
createOption,
|
|
94
109
|
matcher,
|
|
@@ -172,6 +187,9 @@ export const Select = <T extends AnyObject = string>({
|
|
|
172
187
|
return false;
|
|
173
188
|
} else if (state.query) {
|
|
174
189
|
handleQuery("");
|
|
190
|
+
evt.preventDefault();
|
|
191
|
+
evt.stopPropagation();
|
|
192
|
+
return false;
|
|
175
193
|
} else {
|
|
176
194
|
onEnterPressed?.(evt);
|
|
177
195
|
}
|
|
@@ -263,7 +281,7 @@ export const Select = <T extends AnyObject = string>({
|
|
|
263
281
|
required={required}
|
|
264
282
|
error={error}
|
|
265
283
|
wrapperRef={refs.setReference}
|
|
266
|
-
className={state.open ? "
|
|
284
|
+
className={state.open ? "z-99!" : ""}
|
|
267
285
|
{...rest}
|
|
268
286
|
>
|
|
269
287
|
<div
|
|
@@ -290,7 +308,7 @@ export const Select = <T extends AnyObject = string>({
|
|
|
290
308
|
ref={ref}
|
|
291
309
|
autoFocus={autoFocus}
|
|
292
310
|
className={classNames(
|
|
293
|
-
"appearance-none bg-transparent outline-none border-none ring-0 flex-1",
|
|
311
|
+
"appearance-none bg-transparent outline-none border-none ring-0 flex-1 peer",
|
|
294
312
|
disabled && "cursor-not-allowed pointer-events-none",
|
|
295
313
|
multiple && "min-w-24",
|
|
296
314
|
)}
|
|
@@ -298,7 +316,7 @@ export const Select = <T extends AnyObject = string>({
|
|
|
298
316
|
onChange={(evt) => handleQuery(evt.target.value)}
|
|
299
317
|
/>
|
|
300
318
|
</div>
|
|
301
|
-
{!state.loading && (
|
|
319
|
+
{!state.loading && !hideHandle && (
|
|
302
320
|
<Icon
|
|
303
321
|
className={classNames(
|
|
304
322
|
"flex-content p-2 z-0 order-9 text-muted cursor-pointer self-stretch flex items-center h-auto",
|
|
@@ -327,7 +345,7 @@ export const Select = <T extends AnyObject = string>({
|
|
|
327
345
|
>
|
|
328
346
|
<Options
|
|
329
347
|
ref={refs.setFloating}
|
|
330
|
-
className="outline shadow-lg max-h-[40vh] z-
|
|
348
|
+
className="outline shadow-lg max-h-[40vh] z-(--z-popover)"
|
|
331
349
|
style={{
|
|
332
350
|
width: refs.reference.current?.getBoundingClientRect().width,
|
|
333
351
|
...floatingStyles,
|
|
@@ -339,8 +357,10 @@ export const Select = <T extends AnyObject = string>({
|
|
|
339
357
|
labelProperty={labelProperty}
|
|
340
358
|
itemRef={setItemRef}
|
|
341
359
|
itemProps={makeItemProps}
|
|
360
|
+
empty={emptyDisplay}
|
|
361
|
+
info={state.activeItem?.[infoProperty ?? "info"]}
|
|
342
362
|
>
|
|
343
|
-
{(item, label) => renderer?.(item) ?? label}
|
|
363
|
+
{async (item, label) => await (renderer?.(item) ?? label)}
|
|
344
364
|
</Options>
|
|
345
365
|
</FloatingFocusManager>
|
|
346
366
|
</FloatingPortal>
|
package/src/select/useSelect.tsx
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
import { useDebounce } from "@react-fabric/core";
|
|
25
25
|
import {
|
|
26
|
+
compareValues,
|
|
26
27
|
dedupe,
|
|
27
28
|
EMPTY_ARRAY,
|
|
28
29
|
groupBy,
|
|
@@ -64,6 +65,7 @@ interface State {
|
|
|
64
65
|
open: boolean;
|
|
65
66
|
loading: boolean;
|
|
66
67
|
activeIndex: number | null;
|
|
68
|
+
activeItem: AnyObject;
|
|
67
69
|
selectedIndex: number | null;
|
|
68
70
|
value: AnyObject;
|
|
69
71
|
// filter
|
|
@@ -76,11 +78,14 @@ export const useSelect = ({
|
|
|
76
78
|
options = EMPTY_ARRAY,
|
|
77
79
|
value,
|
|
78
80
|
autoComplete,
|
|
81
|
+
alwaysOpen = false,
|
|
82
|
+
defaultOpen = false,
|
|
79
83
|
multiple = false,
|
|
80
84
|
allowCreate = false,
|
|
81
85
|
groupProperty = "",
|
|
82
86
|
valueProperty = "id",
|
|
83
87
|
labelProperty = "label",
|
|
88
|
+
sortProperty,
|
|
84
89
|
createOption = makeOption,
|
|
85
90
|
matcher,
|
|
86
91
|
onQuery,
|
|
@@ -90,10 +95,13 @@ export const useSelect = ({
|
|
|
90
95
|
value?: AnyObject;
|
|
91
96
|
options?: AnyObject[];
|
|
92
97
|
multiple?: boolean;
|
|
98
|
+
alwaysOpen?: boolean;
|
|
99
|
+
defaultOpen?: boolean;
|
|
93
100
|
allowCreate?: boolean;
|
|
94
101
|
groupProperty?: AnyObject;
|
|
95
102
|
valueProperty?: AnyObject;
|
|
96
103
|
labelProperty?: AnyObject;
|
|
104
|
+
sortProperty?: AnyObject;
|
|
97
105
|
autoComplete?: boolean;
|
|
98
106
|
matcher?: (item: AnyObject, query: string) => boolean;
|
|
99
107
|
createOption?: (query: string) => AnyObject;
|
|
@@ -144,12 +152,16 @@ export const useSelect = ({
|
|
|
144
152
|
};
|
|
145
153
|
}
|
|
146
154
|
if (action.type === "close") {
|
|
147
|
-
return { ...state, activeIndex: null, open: false };
|
|
155
|
+
return { ...state, activeIndex: null, activeItem: null, open: false };
|
|
148
156
|
}
|
|
149
157
|
if (action.type === "active" && state.open) {
|
|
150
158
|
if (autoComplete && action.index !== null)
|
|
151
159
|
state.query = state.items[action.index];
|
|
152
|
-
return {
|
|
160
|
+
return {
|
|
161
|
+
...state,
|
|
162
|
+
activeIndex: action.index,
|
|
163
|
+
activeItem: state.items[action.index ?? -1],
|
|
164
|
+
};
|
|
153
165
|
}
|
|
154
166
|
if (action.type === "setValue") {
|
|
155
167
|
return {
|
|
@@ -165,6 +177,7 @@ export const useSelect = ({
|
|
|
165
177
|
...state,
|
|
166
178
|
value: state.emptyValue,
|
|
167
179
|
activeIndex: null,
|
|
180
|
+
activeItem: null,
|
|
168
181
|
selectedIndex: autoComplete
|
|
169
182
|
? state.multiple
|
|
170
183
|
? action.value?.length
|
|
@@ -187,7 +200,7 @@ export const useSelect = ({
|
|
|
187
200
|
newState.value = action.value;
|
|
188
201
|
}
|
|
189
202
|
}
|
|
190
|
-
if (!state.multiple) newState.open =
|
|
203
|
+
if (!state.multiple) newState.open = alwaysOpen;
|
|
191
204
|
fireChange(newState.value);
|
|
192
205
|
return newState;
|
|
193
206
|
}
|
|
@@ -223,6 +236,8 @@ export const useSelect = ({
|
|
|
223
236
|
if (autoComplete && action.query !== undefined) {
|
|
224
237
|
optionList = dedupe([action.query, ...optionList]);
|
|
225
238
|
}
|
|
239
|
+
if (sortProperty)
|
|
240
|
+
optionList = optionList.sort(compareValues("asc", sortProperty));
|
|
226
241
|
const grouped = groupBy(optionList, groupProperty, "");
|
|
227
242
|
if (action.open) newState.open = true;
|
|
228
243
|
newState.items = [];
|
|
@@ -230,7 +245,7 @@ export const useSelect = ({
|
|
|
230
245
|
if (allowCreate && action.query)
|
|
231
246
|
newState.items.push({
|
|
232
247
|
___create___: true,
|
|
233
|
-
value:
|
|
248
|
+
value: action.query,
|
|
234
249
|
});
|
|
235
250
|
Object.entries(grouped).forEach(([key, list]) => {
|
|
236
251
|
if (key) {
|
|
@@ -254,9 +269,10 @@ export const useSelect = ({
|
|
|
254
269
|
{
|
|
255
270
|
multiple,
|
|
256
271
|
emptyValue,
|
|
257
|
-
open:
|
|
272
|
+
open: alwaysOpen || defaultOpen,
|
|
258
273
|
loading: false,
|
|
259
274
|
activeIndex: null,
|
|
275
|
+
activeItem: null,
|
|
260
276
|
selectedIndex: null,
|
|
261
277
|
value: emptyValue,
|
|
262
278
|
// filter
|
|
@@ -283,7 +299,7 @@ export const useSelect = ({
|
|
|
283
299
|
dispatch({ type: "options", options: optionsRef.current });
|
|
284
300
|
dispatch({
|
|
285
301
|
type: "setValue",
|
|
286
|
-
value: hasValue ?? newOption ??
|
|
302
|
+
value: hasValue ?? newOption ?? deferred,
|
|
287
303
|
});
|
|
288
304
|
}
|
|
289
305
|
if (multiple) {
|
|
@@ -295,12 +311,12 @@ export const useSelect = ({
|
|
|
295
311
|
? createOption(item)
|
|
296
312
|
: undefined;
|
|
297
313
|
const hasValue = options.find(
|
|
298
|
-
(opt: AnyObject) => (opt[valueProperty] ?? opt) ===
|
|
314
|
+
(opt: AnyObject) => (opt[valueProperty] ?? opt) === item,
|
|
299
315
|
);
|
|
300
316
|
if (!hasValue && newOption) {
|
|
301
317
|
optionsRef.current.push(newOption);
|
|
302
318
|
}
|
|
303
|
-
return hasValue ?? newOption ??
|
|
319
|
+
return hasValue ?? newOption ?? item;
|
|
304
320
|
},
|
|
305
321
|
);
|
|
306
322
|
optionsRef.current = [...optionsRef.current, ...(options ?? [])];
|
|
@@ -334,6 +350,7 @@ export const useSelect = ({
|
|
|
334
350
|
});
|
|
335
351
|
},
|
|
336
352
|
[onQuery],
|
|
353
|
+
300,
|
|
337
354
|
);
|
|
338
355
|
|
|
339
356
|
// handlers
|
|
@@ -399,7 +416,7 @@ export const useSelect = ({
|
|
|
399
416
|
}, []);
|
|
400
417
|
|
|
401
418
|
const setActiveIndex = useCallback((index: number | null) => {
|
|
402
|
-
dispatch({ type: "active", index });
|
|
419
|
+
index && index > -1 && dispatch({ type: "active", index });
|
|
403
420
|
}, []);
|
|
404
421
|
|
|
405
422
|
return {
|
package/src/types/index.ts
CHANGED
|
@@ -25,16 +25,21 @@ import {
|
|
|
25
25
|
type RefProp,
|
|
26
26
|
type TestProps,
|
|
27
27
|
} from "@react-fabric/core/dist/types/types";
|
|
28
|
+
import { type ReactElement } from "react";
|
|
28
29
|
|
|
29
30
|
export interface InputProps<V = string> extends TestProps {
|
|
30
31
|
/**
|
|
31
32
|
* value
|
|
32
33
|
*/
|
|
33
|
-
value?: V;
|
|
34
|
+
value?: V | null;
|
|
35
|
+
/**
|
|
36
|
+
* default value
|
|
37
|
+
*/
|
|
38
|
+
defaultValue?: V | null;
|
|
34
39
|
/**
|
|
35
40
|
* change handler
|
|
36
41
|
*/
|
|
37
|
-
onChange?: (value
|
|
42
|
+
onChange?: (value: V | null) => void;
|
|
38
43
|
/**
|
|
39
44
|
* on focus event handler
|
|
40
45
|
*/
|
|
@@ -72,11 +77,15 @@ export interface InputProps<V = string> extends TestProps {
|
|
|
72
77
|
*
|
|
73
78
|
*/
|
|
74
79
|
decorateEnd?: JSX.Element | string | number | boolean;
|
|
80
|
+
decorateEndShowWhenEmpty?: boolean;
|
|
81
|
+
decorateEndHideWhenEmpty?: boolean;
|
|
75
82
|
/**
|
|
76
83
|
* info label at bottom
|
|
77
84
|
*/
|
|
78
85
|
info?: string;
|
|
79
86
|
|
|
87
|
+
monospace?: boolean;
|
|
88
|
+
|
|
80
89
|
/**
|
|
81
90
|
* required
|
|
82
91
|
*/
|
|
@@ -120,9 +129,15 @@ export interface InputProps<V = string> extends TestProps {
|
|
|
120
129
|
labelWidth?: string;
|
|
121
130
|
}
|
|
122
131
|
|
|
123
|
-
export interface BaseSelectProps<T>
|
|
124
|
-
|
|
125
|
-
|
|
132
|
+
export interface BaseSelectProps<T extends AnyObject = string> {
|
|
133
|
+
/**
|
|
134
|
+
* open dropdown by default
|
|
135
|
+
*/
|
|
136
|
+
defaultOpen?: boolean;
|
|
137
|
+
/**
|
|
138
|
+
* hide dropdown handle
|
|
139
|
+
*/
|
|
140
|
+
hideHandle?: boolean;
|
|
126
141
|
/**
|
|
127
142
|
* list options
|
|
128
143
|
*/
|
|
@@ -139,6 +154,14 @@ export interface BaseSelectProps<T>
|
|
|
139
154
|
* value property name
|
|
140
155
|
*/
|
|
141
156
|
valueProperty?: keyof T;
|
|
157
|
+
/**
|
|
158
|
+
* value property name
|
|
159
|
+
*/
|
|
160
|
+
infoProperty?: keyof T;
|
|
161
|
+
/**
|
|
162
|
+
* value property name
|
|
163
|
+
*/
|
|
164
|
+
sortProperty?: keyof T | true;
|
|
142
165
|
/**
|
|
143
166
|
* searchable list
|
|
144
167
|
*/
|
|
@@ -147,6 +170,10 @@ export interface BaseSelectProps<T>
|
|
|
147
170
|
* allow create item
|
|
148
171
|
*/
|
|
149
172
|
allowCreate?: boolean;
|
|
173
|
+
/**
|
|
174
|
+
* empty list display
|
|
175
|
+
*/
|
|
176
|
+
emptyDisplay?: ReactElement;
|
|
150
177
|
/**
|
|
151
178
|
* create new item object
|
|
152
179
|
*/
|
|
@@ -165,7 +192,11 @@ export interface BaseSelectProps<T>
|
|
|
165
192
|
matcher?: (option: T, query: string) => boolean;
|
|
166
193
|
}
|
|
167
194
|
|
|
168
|
-
|
|
195
|
+
type BaseSelectType<T> = BaseSelectProps<T> &
|
|
196
|
+
RefProp<HTMLInputElement> &
|
|
197
|
+
Omit<InputProps, "value" | "onChange">;
|
|
198
|
+
|
|
199
|
+
export type SelectProps<T> = BaseSelectType<T> &
|
|
169
200
|
(
|
|
170
201
|
| {
|
|
171
202
|
/**
|
package/src/types/schema.ts
CHANGED
|
@@ -21,80 +21,135 @@
|
|
|
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
|
+
import { type Elements } from "@react-fabric/core/dist/types/types";
|
|
25
|
+
import { type DateLike } from "@react-fabric/date";
|
|
26
|
+
|
|
27
|
+
export enum DATA_TYPES {
|
|
28
|
+
TEXT = "text",
|
|
29
|
+
COLOR = "color",
|
|
30
|
+
BOOL = "bool",
|
|
31
|
+
DATE = "date",
|
|
32
|
+
DATETIME = "datetime",
|
|
33
|
+
STRING = "string",
|
|
34
|
+
NUMBER = "number",
|
|
35
|
+
RANGE = "range",
|
|
36
|
+
FILE = "file",
|
|
37
|
+
AVATAR = "avatar",
|
|
38
|
+
SCHEMA = "schema",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const SCHEMA_CUSTOM_LIST = "--CUSTOM--";
|
|
42
|
+
|
|
43
|
+
interface SingleValue {
|
|
44
|
+
multiple?: false;
|
|
45
|
+
minItems?: never;
|
|
46
|
+
maxItems?: never;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface MultipleValue {
|
|
50
|
+
multiple: true;
|
|
51
|
+
minItems?: number;
|
|
52
|
+
maxItems?: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type ValueType = SingleValue | MultipleValue;
|
|
56
|
+
|
|
57
|
+
interface BaseSchema {
|
|
25
58
|
id: string;
|
|
26
59
|
label: string;
|
|
27
60
|
datatype: string;
|
|
28
61
|
required?: boolean;
|
|
29
|
-
multiple?: boolean;
|
|
30
|
-
defaultValue?: AnyObject;
|
|
31
62
|
}
|
|
32
63
|
|
|
33
|
-
interface
|
|
34
|
-
datatype:
|
|
64
|
+
export interface DefaultSchema extends BaseSchema {
|
|
65
|
+
datatype: DATA_TYPES.TEXT | DATA_TYPES.COLOR;
|
|
35
66
|
}
|
|
36
67
|
|
|
37
|
-
interface BooleanSchema extends
|
|
38
|
-
datatype:
|
|
39
|
-
defaultValue?: boolean;
|
|
68
|
+
export interface BooleanSchema extends BaseSchema {
|
|
69
|
+
datatype: DATA_TYPES.BOOL;
|
|
40
70
|
}
|
|
41
71
|
|
|
42
|
-
interface StringSchema extends
|
|
43
|
-
datatype:
|
|
44
|
-
options?: string[];
|
|
72
|
+
export interface StringSchema extends BaseSchema {
|
|
73
|
+
datatype: DATA_TYPES.STRING;
|
|
45
74
|
regex?: string;
|
|
46
|
-
|
|
75
|
+
options?: string[];
|
|
76
|
+
optionList?: string;
|
|
77
|
+
allowCustom?: boolean;
|
|
47
78
|
}
|
|
48
79
|
|
|
49
|
-
interface RangeSchema extends
|
|
50
|
-
datatype:
|
|
80
|
+
export interface RangeSchema extends BaseSchema {
|
|
81
|
+
datatype: DATA_TYPES.RANGE;
|
|
51
82
|
min?: number;
|
|
52
83
|
max?: number;
|
|
53
84
|
step?: number;
|
|
54
|
-
defaultValue?: [number, number];
|
|
55
85
|
}
|
|
56
86
|
|
|
57
|
-
interface
|
|
58
|
-
datatype:
|
|
59
|
-
min?:
|
|
60
|
-
max?:
|
|
61
|
-
step?: number;
|
|
62
|
-
defaultValue?: number;
|
|
87
|
+
export interface DateSchema extends BaseSchema {
|
|
88
|
+
datatype: DATA_TYPES.DATE | DATA_TYPES.DATETIME;
|
|
89
|
+
min?: DateLike;
|
|
90
|
+
max?: DateLike;
|
|
63
91
|
}
|
|
64
92
|
|
|
65
|
-
interface
|
|
66
|
-
datatype:
|
|
93
|
+
export interface NumberSchema extends BaseSchema {
|
|
94
|
+
datatype: DATA_TYPES.NUMBER;
|
|
67
95
|
min?: number;
|
|
68
96
|
max?: number;
|
|
69
97
|
step?: number;
|
|
70
|
-
defaultValue?: number;
|
|
71
98
|
}
|
|
72
99
|
|
|
73
|
-
export interface FileSchema extends
|
|
74
|
-
datatype:
|
|
100
|
+
export interface FileSchema extends BaseSchema {
|
|
101
|
+
datatype: DATA_TYPES.FILE;
|
|
75
102
|
accept?: string;
|
|
76
103
|
}
|
|
77
104
|
|
|
78
|
-
export interface AvatarSchema extends
|
|
79
|
-
datatype:
|
|
105
|
+
export interface AvatarSchema extends BaseSchema {
|
|
106
|
+
datatype: DATA_TYPES.AVATAR;
|
|
80
107
|
multiple?: false;
|
|
81
108
|
}
|
|
82
109
|
|
|
83
110
|
export type SchemaType =
|
|
84
|
-
|
|
|
85
|
-
| BooleanSchema
|
|
86
|
-
| StringSchema
|
|
87
|
-
| RangeSchema
|
|
88
|
-
|
|
|
89
|
-
|
|
|
90
|
-
| FileSchema
|
|
91
|
-
| AvatarSchema;
|
|
92
|
-
|
|
93
|
-
export interface ChildSchema extends
|
|
94
|
-
datatype:
|
|
111
|
+
| (DefaultSchema & ValueType)
|
|
112
|
+
| (BooleanSchema & ValueType)
|
|
113
|
+
| (StringSchema & ValueType)
|
|
114
|
+
| (RangeSchema & ValueType)
|
|
115
|
+
| (DateSchema & ValueType)
|
|
116
|
+
| (NumberSchema & ValueType)
|
|
117
|
+
| (FileSchema & ValueType)
|
|
118
|
+
| (AvatarSchema & ValueType);
|
|
119
|
+
|
|
120
|
+
export interface ChildSchema extends BaseSchema {
|
|
121
|
+
datatype: DATA_TYPES.SCHEMA;
|
|
95
122
|
inline?: boolean;
|
|
96
123
|
schema: SchemaType[];
|
|
97
|
-
defaultValue?: never;
|
|
98
124
|
}
|
|
99
125
|
|
|
100
|
-
export type
|
|
126
|
+
export type SchemaDef = SchemaType | (ChildSchema & ValueType);
|
|
127
|
+
export type FormSchema = SchemaDef[];
|
|
128
|
+
|
|
129
|
+
export interface SchemaEditorProps {
|
|
130
|
+
/**
|
|
131
|
+
* default form schema
|
|
132
|
+
*/
|
|
133
|
+
schemaDef: FormSchema;
|
|
134
|
+
/**
|
|
135
|
+
* is dynamic schema (allow add/remove)
|
|
136
|
+
*/
|
|
137
|
+
dynamic?: boolean;
|
|
138
|
+
/**
|
|
139
|
+
* acceptable list of data types
|
|
140
|
+
*/
|
|
141
|
+
acceptableTypes?: DATA_TYPES[];
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* available option lists in application
|
|
145
|
+
*/
|
|
146
|
+
optionLists?: string[];
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* footer actions
|
|
150
|
+
*/
|
|
151
|
+
children?: Elements<JSX.Element> | Array<Elements<JSX.Element>>;
|
|
152
|
+
|
|
153
|
+
onChange?: (schema: FormSchema) => void;
|
|
154
|
+
onSubmit?: (schema: FormSchema) => void;
|
|
155
|
+
}
|
|
@@ -23,19 +23,29 @@
|
|
|
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
|
-
|
|
38
|
+
const cbHandle = useDebounce(callback ?? (() => undefined), [callback], 100);
|
|
39
|
+
const tree = useFloatingTree();
|
|
40
|
+
return (e: KeyboardEvent<HTMLInputElement>) => {
|
|
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
|
+
}
|
|
45
|
+
// blur input on escape key press
|
|
46
|
+
if (e.key === "Escape") {
|
|
47
|
+
e.currentTarget.blur();
|
|
48
|
+
}
|
|
39
49
|
if (
|
|
40
50
|
!e.shiftKey &&
|
|
41
51
|
!e.altKey &&
|