@react-fabric/form 1.0.0-beta.9 → 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/select/List.tsx
CHANGED
|
@@ -30,54 +30,57 @@ import {
|
|
|
30
30
|
useTypeahead,
|
|
31
31
|
} from "@floating-ui/react";
|
|
32
32
|
import { Chip } from "@react-fabric/core";
|
|
33
|
-
import { isArray, isEmpty } from "@react-fabric/utilities";
|
|
34
|
-
import
|
|
35
|
-
import { Fragment, useCallback, useId, useMemo } from "react";
|
|
36
|
-
import { FieldWrapper } from "../internal/FieldWrapper";
|
|
33
|
+
import { cn, getByPath, isArray, isEmpty } from "@react-fabric/utilities";
|
|
34
|
+
import { useCallback, useId, useLayoutEffect, useMemo } from "react";
|
|
37
35
|
import { InputWrapper } from "../internal/InputWrapper";
|
|
38
36
|
import { type SelectProps } from "../types";
|
|
37
|
+
import { getWrapperProps } from "../utils";
|
|
39
38
|
import { Options } from "./Options";
|
|
40
39
|
import { useSelect } from "./useSelect";
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
41
|
+
/**
|
|
42
|
+
* List component for selecting items from a list.
|
|
43
|
+
* It supports single and multiple selections, searching, creating new options,
|
|
44
|
+
* and custom rendering of options.
|
|
45
|
+
*/
|
|
46
|
+
export function List<T extends AnyObject = string>(
|
|
47
|
+
props: Omit<SelectProps<T>, "defaultOpen" | "hideHandle"> & {
|
|
48
|
+
height?: string | number;
|
|
49
|
+
},
|
|
50
|
+
) {
|
|
51
|
+
const {
|
|
52
|
+
ref,
|
|
53
|
+
name,
|
|
54
|
+
value,
|
|
55
|
+
readOnly,
|
|
56
|
+
disabled,
|
|
57
|
+
placeholder,
|
|
58
|
+
autoFocus,
|
|
59
|
+
options,
|
|
60
|
+
height,
|
|
61
|
+
groupProperty,
|
|
62
|
+
labelProperty = "label",
|
|
63
|
+
valueProperty = "value",
|
|
64
|
+
infoProperty,
|
|
65
|
+
sortProperty,
|
|
66
|
+
multiple,
|
|
67
|
+
allowCreate,
|
|
68
|
+
allowClear,
|
|
69
|
+
searchable,
|
|
70
|
+
createOption,
|
|
71
|
+
matcher,
|
|
72
|
+
renderer,
|
|
73
|
+
onQuery,
|
|
74
|
+
onChange,
|
|
75
|
+
onSelect,
|
|
76
|
+
onEnterPressed,
|
|
77
|
+
emptyDisplay,
|
|
78
|
+
invalid,
|
|
79
|
+
// @ts-expect-error ignore
|
|
80
|
+
"data-inner": isInner,
|
|
81
|
+
// @ts-expect-error ignore
|
|
82
|
+
"data-hide-selected": hideSelected,
|
|
83
|
+
} = props;
|
|
81
84
|
|
|
82
85
|
const {
|
|
83
86
|
state,
|
|
@@ -88,14 +91,17 @@ export const List = <T extends AnyObject = string>({
|
|
|
88
91
|
handleRemove,
|
|
89
92
|
handleQuery,
|
|
90
93
|
setItemRef,
|
|
94
|
+
clearActiveIndex,
|
|
91
95
|
setActiveIndex,
|
|
92
96
|
} = useSelect({
|
|
93
97
|
value,
|
|
94
98
|
options,
|
|
95
99
|
multiple,
|
|
100
|
+
alwaysOpen: true,
|
|
96
101
|
groupProperty,
|
|
97
102
|
labelProperty,
|
|
98
103
|
valueProperty,
|
|
104
|
+
sortProperty,
|
|
99
105
|
allowCreate,
|
|
100
106
|
createOption,
|
|
101
107
|
matcher,
|
|
@@ -104,7 +110,7 @@ export const List = <T extends AnyObject = string>({
|
|
|
104
110
|
onQuery,
|
|
105
111
|
});
|
|
106
112
|
|
|
107
|
-
const
|
|
113
|
+
const dropdownKeyId = useId();
|
|
108
114
|
|
|
109
115
|
const { context, refs } = useFloating({
|
|
110
116
|
open: true,
|
|
@@ -132,29 +138,25 @@ export const List = <T extends AnyObject = string>({
|
|
|
132
138
|
return list.find((item) => macthOption(item, typedString));
|
|
133
139
|
},
|
|
134
140
|
onMatch(index) {
|
|
135
|
-
state.open
|
|
141
|
+
if (state.open) setActiveIndex(index);
|
|
136
142
|
},
|
|
137
143
|
});
|
|
138
144
|
|
|
139
|
-
const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions(
|
|
140
|
-
[navigation, typeahead],
|
|
141
|
-
);
|
|
145
|
+
const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([navigation, typeahead]);
|
|
142
146
|
|
|
143
147
|
const referenceProps = useMemo(() => {
|
|
144
|
-
const props
|
|
148
|
+
const props = getReferenceProps();
|
|
145
149
|
|
|
146
150
|
return {
|
|
147
151
|
...props,
|
|
148
152
|
onBlur(evt: React.FocusEvent) {
|
|
149
|
-
if (
|
|
150
|
-
evt.relatedTarget?.closest(`[data-select-dropdown="${dropdownKey}"]`)
|
|
151
|
-
) {
|
|
153
|
+
if (evt.relatedTarget?.closest(`[data-select-dropdown="${dropdownKeyId}"]`)) {
|
|
152
154
|
refs.domReference.current?.querySelector("input")?.focus();
|
|
153
155
|
return;
|
|
154
156
|
}
|
|
155
157
|
handleQuery("");
|
|
156
158
|
},
|
|
157
|
-
onKeyDown(evt: React.KeyboardEvent) {
|
|
159
|
+
onKeyDown(evt: React.KeyboardEvent<HTMLInputElement>) {
|
|
158
160
|
if (evt.key === "Enter") {
|
|
159
161
|
if (state.activeIndex !== null) {
|
|
160
162
|
handleChange(listContentRef.current[state.activeIndex]);
|
|
@@ -165,44 +167,50 @@ export const List = <T extends AnyObject = string>({
|
|
|
165
167
|
onEnterPressed?.(evt);
|
|
166
168
|
}
|
|
167
169
|
} else if (evt.key === "Backspace") {
|
|
168
|
-
!state.query
|
|
169
|
-
} else if (["ArrowUp", "ArrowDown"].includes(evt.key)) {
|
|
170
|
-
if (state.activeIndex === null) {
|
|
171
|
-
setActiveIndex(state.selectedIndex);
|
|
170
|
+
if (!state.query) handleRemove();
|
|
171
|
+
} else if (!searchable || ["ArrowUp", "ArrowDown"].includes(evt.key)) {
|
|
172
|
+
if (["ArrowUp", "ArrowDown"].includes(evt.key) && state.activeIndex === null) {
|
|
173
|
+
setActiveIndex(state.selectedIndex ?? 0);
|
|
172
174
|
}
|
|
173
175
|
setTimeout(() => {
|
|
176
|
+
// @ts-expect-error ignore
|
|
174
177
|
props.onKeyDown?.(evt);
|
|
175
178
|
}, 50);
|
|
176
179
|
}
|
|
177
180
|
},
|
|
178
|
-
};
|
|
181
|
+
} as KeyValue;
|
|
179
182
|
}, [
|
|
180
183
|
getReferenceProps,
|
|
181
|
-
|
|
184
|
+
dropdownKeyId,
|
|
182
185
|
handleQuery,
|
|
183
|
-
refs,
|
|
184
|
-
|
|
186
|
+
refs.domReference,
|
|
187
|
+
searchable,
|
|
185
188
|
state.activeIndex,
|
|
189
|
+
state.query,
|
|
190
|
+
state.selectedIndex,
|
|
191
|
+
handleChange,
|
|
192
|
+
listContentRef,
|
|
193
|
+
onEnterPressed,
|
|
194
|
+
handleRemove,
|
|
195
|
+
setActiveIndex,
|
|
186
196
|
]);
|
|
187
197
|
|
|
188
198
|
const floatingProps = useMemo(() => {
|
|
189
199
|
return getFloatingProps({
|
|
190
|
-
"data-select-dropdown":
|
|
191
|
-
} as
|
|
192
|
-
}, [getFloatingProps]);
|
|
200
|
+
"data-select-dropdown": dropdownKeyId,
|
|
201
|
+
} as KeyValue);
|
|
202
|
+
}, [dropdownKeyId, getFloatingProps]);
|
|
193
203
|
|
|
194
204
|
const makeItemProps = useCallback(
|
|
195
205
|
(item: AnyObject) => {
|
|
196
|
-
const selected = isArray(state.value)
|
|
197
|
-
? state.value?.includes?.(item)
|
|
198
|
-
: state.value === item;
|
|
206
|
+
const selected = isArray(state.value) ? state.value?.includes?.(item) : state.value === item;
|
|
199
207
|
return {
|
|
200
208
|
...getItemProps({
|
|
201
209
|
"data-selected": selected ? true : undefined,
|
|
202
210
|
onClick() {
|
|
203
211
|
handleChange(item);
|
|
204
212
|
},
|
|
205
|
-
} as
|
|
213
|
+
} as KeyValue),
|
|
206
214
|
"aria-hidden": undefined,
|
|
207
215
|
};
|
|
208
216
|
},
|
|
@@ -211,64 +219,85 @@ export const List = <T extends AnyObject = string>({
|
|
|
211
219
|
|
|
212
220
|
const displayValue = useMemo(() => {
|
|
213
221
|
if (isArray(state.value)) {
|
|
214
|
-
return state.value.map((item:
|
|
222
|
+
return state.value.map((item: T, index) => (
|
|
223
|
+
// eslint-disable-next-line @eslint-react/no-array-index-key
|
|
215
224
|
<Chip key={index} onRemove={() => handleRemove(index)}>
|
|
225
|
+
{/* @ts-expect-error ignore */}
|
|
216
226
|
{renderer?.(item) ?? item[labelProperty] ?? item}
|
|
217
227
|
</Chip>
|
|
218
228
|
));
|
|
219
229
|
} else if (!isEmpty(state.value) && !state.query) {
|
|
220
230
|
return (
|
|
221
|
-
<span>
|
|
222
|
-
{
|
|
231
|
+
<span className="self-center pointer-events-none select-none truncate leading-tight">
|
|
232
|
+
{/* eslint-disable-next-line @typescript-eslint/no-unsafe-member-access */}
|
|
233
|
+
{renderer?.(state.value as T) ?? state.value[labelProperty] ?? state.value}
|
|
223
234
|
</span>
|
|
224
235
|
);
|
|
225
236
|
}
|
|
226
|
-
}, [state.value, state.query,
|
|
237
|
+
}, [state.value, state.query, renderer, labelProperty, handleRemove]);
|
|
238
|
+
|
|
239
|
+
useLayoutEffect(() => {
|
|
240
|
+
if (state.open) {
|
|
241
|
+
const tmr = setTimeout(() => {
|
|
242
|
+
if (state.selectedIndex)
|
|
243
|
+
listRef.current[state.selectedIndex]?.scrollIntoView({
|
|
244
|
+
block: "nearest",
|
|
245
|
+
});
|
|
246
|
+
}, 100);
|
|
247
|
+
return () => {
|
|
248
|
+
clearTimeout(tmr);
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
}, [listRef, state.open, state.selectedIndex]);
|
|
227
252
|
|
|
228
253
|
return (
|
|
229
|
-
|
|
254
|
+
<>
|
|
230
255
|
<InputWrapper
|
|
231
|
-
showClear={!isEmpty(state.value)}
|
|
256
|
+
showClear={allowClear && !isEmpty(state.value)}
|
|
232
257
|
onClear={handleChange}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
error={error}
|
|
238
|
-
className="z-1 rounded-b-none"
|
|
239
|
-
data-inner={true}
|
|
240
|
-
wrapperRef={refs.setReference}
|
|
258
|
+
ref={(el) => refs.setReference(el)}
|
|
259
|
+
className="z-1 rounded-capped rounded-b-none outline"
|
|
260
|
+
{...getWrapperProps(props)}
|
|
261
|
+
borderless={!!isInner}
|
|
241
262
|
>
|
|
242
263
|
<div
|
|
264
|
+
role="none"
|
|
243
265
|
data-select-display="true"
|
|
244
|
-
className=
|
|
266
|
+
className={cn(
|
|
267
|
+
"group flex-1 py-1 px-2 truncate text-start flex gap-1 relative min-h-5 justify-start",
|
|
268
|
+
multiple ? "flex-wrap" : "flex-nowrap overflow-hidden",
|
|
269
|
+
)}
|
|
270
|
+
{...referenceProps}
|
|
271
|
+
onMouseUp={(e) => e.currentTarget.querySelector<HTMLElement>("input")?.focus()}
|
|
245
272
|
>
|
|
246
273
|
{!hideSelected && displayValue}
|
|
247
274
|
<input
|
|
275
|
+
readOnly={!searchable || readOnly}
|
|
248
276
|
disabled={disabled}
|
|
249
277
|
value={state.query ?? ""}
|
|
250
278
|
aria-invalid={invalid}
|
|
251
279
|
data-testid={name}
|
|
252
280
|
name={name}
|
|
253
|
-
|
|
281
|
+
size={1}
|
|
282
|
+
ref={ref}
|
|
283
|
+
autoComplete="off"
|
|
284
|
+
{...{ autoFocus }}
|
|
285
|
+
className={cn(
|
|
254
286
|
"appearance-none bg-transparent outline-none border-none ring-0 flex-1 min-w-24",
|
|
255
287
|
disabled && "cursor-not-allowed pointer-events-none",
|
|
288
|
+
multiple && "min-w-24",
|
|
256
289
|
)}
|
|
257
|
-
placeholder={isEmpty(state.value) ?
|
|
258
|
-
{...referenceProps}
|
|
290
|
+
placeholder={!hideSelected && !isEmpty(state.value) ? "" : placeholder}
|
|
259
291
|
onChange={(evt) => handleQuery(evt.target.value)}
|
|
260
292
|
/>
|
|
261
293
|
</div>
|
|
262
294
|
</InputWrapper>
|
|
263
|
-
<FloatingFocusManager
|
|
264
|
-
context={context}
|
|
265
|
-
initialFocus={-1}
|
|
266
|
-
visuallyHiddenDismiss
|
|
267
|
-
>
|
|
295
|
+
<FloatingFocusManager context={context} initialFocus={-1} visuallyHiddenDismiss closeOnFocusOut>
|
|
268
296
|
<Options
|
|
269
|
-
ref={refs.setFloating}
|
|
270
|
-
|
|
271
|
-
|
|
297
|
+
ref={(el) => refs.setFloating(el)}
|
|
298
|
+
onMouseOut={() => clearActiveIndex()}
|
|
299
|
+
className={cn(
|
|
300
|
+
"max-h-96 z-0 rounded-capped! rounded-t-none! outline peer-focus-within:outline-primary-500",
|
|
272
301
|
disabled && "pointer-events-none opacity-65",
|
|
273
302
|
)}
|
|
274
303
|
style={{
|
|
@@ -277,12 +306,16 @@ export const List = <T extends AnyObject = string>({
|
|
|
277
306
|
{...floatingProps}
|
|
278
307
|
items={state.items}
|
|
279
308
|
active={state.activeIndex}
|
|
309
|
+
valueProperty={valueProperty}
|
|
310
|
+
labelProperty={labelProperty}
|
|
280
311
|
itemRef={setItemRef}
|
|
281
312
|
itemProps={makeItemProps}
|
|
313
|
+
empty={emptyDisplay}
|
|
314
|
+
info={getByPath(state.activeItem as KeyValue, infoProperty ?? "info")}
|
|
282
315
|
>
|
|
283
|
-
{(item) => renderer?.(item
|
|
316
|
+
{(item, label) => renderer?.(item as T) ?? label}
|
|
284
317
|
</Options>
|
|
285
318
|
</FloatingFocusManager>
|
|
286
|
-
|
|
319
|
+
</>
|
|
287
320
|
);
|
|
288
|
-
}
|
|
321
|
+
}
|
package/src/select/Options.tsx
CHANGED
|
@@ -21,12 +21,18 @@
|
|
|
21
21
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
25
|
+
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
26
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
27
|
+
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
28
|
+
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
29
|
+
/* eslint-disable @eslint-react/no-array-index-key */
|
|
30
|
+
|
|
24
31
|
import { EmptyContent } from "@react-fabric/core";
|
|
25
|
-
import
|
|
26
|
-
import { type ReactElement, type ReactNode } from "react";
|
|
32
|
+
import { cn, getByPath } from "@react-fabric/utilities";
|
|
27
33
|
import { Trans } from "react-i18next";
|
|
28
34
|
|
|
29
|
-
export
|
|
35
|
+
export function Options({
|
|
30
36
|
ref,
|
|
31
37
|
style,
|
|
32
38
|
items,
|
|
@@ -34,50 +40,65 @@ export const Options = ({
|
|
|
34
40
|
className,
|
|
35
41
|
itemRef,
|
|
36
42
|
empty,
|
|
37
|
-
|
|
43
|
+
info,
|
|
38
44
|
labelProperty = "label",
|
|
39
45
|
itemProps,
|
|
40
46
|
children,
|
|
41
|
-
|
|
47
|
+
onMouseOut,
|
|
42
48
|
}: {
|
|
43
|
-
ref:
|
|
49
|
+
ref: React.Ref<HTMLDivElement | null>;
|
|
50
|
+
info?: AnyObject;
|
|
44
51
|
itemRef: AnyObject;
|
|
45
|
-
style?:
|
|
52
|
+
style?: React.CSSProperties;
|
|
46
53
|
items: AnyObject[];
|
|
47
54
|
active: number | null;
|
|
48
55
|
className?: string;
|
|
49
|
-
empty?:
|
|
56
|
+
empty?: React.ReactNode;
|
|
50
57
|
valueProperty?: AnyObject;
|
|
51
58
|
labelProperty?: AnyObject;
|
|
59
|
+
onMouseOut?: () => void;
|
|
52
60
|
itemProps: (item: AnyObject) => AnyObject;
|
|
53
|
-
children: (item: AnyObject, label: string) => ReactNode;
|
|
54
|
-
}
|
|
61
|
+
children: (item: AnyObject, label: string) => React.ReactNode;
|
|
62
|
+
}) {
|
|
55
63
|
return (
|
|
56
64
|
<div
|
|
57
65
|
ref={ref}
|
|
58
|
-
className={
|
|
59
|
-
"bg-base select-none overflow-x-hidden overflow-y-auto",
|
|
60
|
-
className,
|
|
61
|
-
)}
|
|
66
|
+
className={cn("bg-default select-none flex flex-col overflow-hidden", className)}
|
|
62
67
|
style={style}
|
|
63
|
-
{
|
|
68
|
+
onMouseLeave={onMouseOut}
|
|
64
69
|
>
|
|
65
|
-
{
|
|
66
|
-
(empty ??
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
70
|
+
<div className={cn("overflow-x-hidden overflow-y-auto flex-1")}>
|
|
71
|
+
{items.length === 0 && (empty ?? <EmptyContent message={<Trans i18nKey="form:select.emptyList" />} />)}
|
|
72
|
+
{items.map((item, index) => {
|
|
73
|
+
if (item.___group___)
|
|
74
|
+
return (
|
|
75
|
+
<p key={index} className="px-2 py-1 text-muted text-xs sticky -top-px z-2 bg-default border-t">
|
|
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={cn(
|
|
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"
|
|
@@ -85,37 +106,18 @@ export const Options = ({
|
|
|
85
106
|
tabIndex={active === index ? 0 : -1}
|
|
86
107
|
ref={(el: AnyObject) => itemRef(index, el)}
|
|
87
108
|
data-active={active === index ? true : undefined}
|
|
88
|
-
className={
|
|
89
|
-
"block text-start truncate px-2 py-1 leading-normal cursor-pointer
|
|
90
|
-
"data-
|
|
109
|
+
className={cn(
|
|
110
|
+
"block text-start truncate px-2 py-1 leading-normal cursor-pointer",
|
|
111
|
+
"active:bg-primary-200 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, getByPath(item, labelProperty) ?? `${item}`)}
|
|
99
116
|
</div>
|
|
100
117
|
);
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
role="none"
|
|
105
|
-
key={index}
|
|
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 && <div className="px-2 py-1 text-sm bg-dimmed text-muted">{info}</div>}
|
|
119
121
|
</div>
|
|
120
122
|
);
|
|
121
|
-
}
|
|
123
|
+
}
|