@react-fabric/form 1.0.0-beta.4 → 1.0.0-beta.41
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} +26 -15
- package/{src/input/File.module.css → css/_file.css} +20 -6
- package/css/styles.css +27 -0
- package/dist/cjs/builder/FileField.js +37 -25
- package/dist/cjs/builder/FormField.js +44 -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 +143 -0
- package/dist/cjs/builder/components/SchemaField.js +413 -0
- package/dist/cjs/form/Controller.js +1 -1
- package/dist/cjs/form/Form.js +19 -10
- package/dist/cjs/hooks/useFormBuilder.js +27 -15
- package/dist/cjs/index.js +9 -2
- package/dist/cjs/input/ArrayInput.js +36 -25
- package/dist/cjs/input/AvatarInput.js +1 -1
- package/dist/cjs/input/Checkbox.js +5 -4
- package/dist/cjs/input/ColorInput.js +70 -28
- package/dist/cjs/input/ColorPicker.js +1 -35
- package/dist/cjs/input/DateInput.js +183 -0
- package/dist/cjs/input/Field.js +4 -4
- package/dist/cjs/input/File.js +2 -3
- package/dist/cjs/input/Hidden.js +3 -1
- package/dist/cjs/input/Input.js +1 -1
- package/dist/cjs/input/Number.js +1 -1
- package/dist/cjs/input/Password.js +1 -1
- package/dist/cjs/input/Radio.js +1 -1
- package/dist/cjs/input/RadioGroup.js +6 -3
- package/dist/cjs/input/{Range.js → RangeSlider.js} +3 -3
- package/dist/cjs/input/Search.js +1 -1
- package/dist/cjs/input/Slider.js +1 -1
- package/dist/cjs/input/Switch.js +27 -5
- package/dist/cjs/input/Textarea.js +7 -3
- package/dist/cjs/internal/ErrorIcon.js +1 -1
- package/dist/cjs/internal/FieldWrapper.js +4 -3
- package/dist/cjs/internal/InputWrapper.js +14 -16
- package/dist/cjs/select/AutoComplete.js +9 -6
- package/dist/cjs/select/List.js +41 -20
- package/dist/cjs/select/Options.js +2 -2
- package/dist/cjs/select/Select.js +15 -9
- package/dist/cjs/select/useSelect.js +15 -8
- package/{src/input/ColorPicker.module.css → dist/cjs/types/schema.js} +18 -27
- package/dist/cjs/utils/getColor.js +6 -0
- package/dist/esm/builder/FileField.js +39 -27
- package/dist/esm/builder/FormField.js +44 -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 +143 -0
- package/dist/esm/builder/components/SchemaField.js +413 -0
- package/dist/esm/form/Controller.js +1 -1
- package/dist/esm/form/Form.js +19 -10
- package/dist/esm/hooks/useFormBuilder.js +25 -13
- package/dist/esm/index.js +4 -1
- package/dist/esm/input/ArrayInput.js +38 -27
- package/dist/esm/input/AvatarInput.js +1 -1
- package/dist/esm/input/Checkbox.js +5 -4
- package/dist/esm/input/ColorInput.js +72 -30
- package/dist/esm/input/ColorPicker.js +1 -35
- package/dist/esm/input/DateInput.js +183 -0
- package/dist/esm/input/Field.js +5 -5
- package/dist/esm/input/File.js +2 -3
- package/dist/esm/input/Hidden.js +3 -1
- package/dist/esm/input/Input.js +1 -1
- package/dist/esm/input/Number.js +1 -1
- package/dist/esm/input/Password.js +1 -1
- package/dist/esm/input/Radio.js +1 -1
- package/dist/esm/input/RadioGroup.js +7 -4
- package/dist/esm/input/{Range.js → RangeSlider.js} +3 -3
- package/dist/esm/input/Search.js +1 -1
- package/dist/esm/input/Slider.js +1 -1
- package/dist/esm/input/Switch.js +29 -7
- package/dist/esm/input/Textarea.js +7 -3
- package/dist/esm/internal/ErrorIcon.js +1 -1
- package/dist/esm/internal/FieldWrapper.js +4 -3
- package/dist/esm/internal/InputWrapper.js +14 -16
- package/dist/esm/select/AutoComplete.js +9 -6
- package/dist/esm/select/List.js +42 -21
- package/dist/esm/select/Options.js +2 -2
- package/dist/esm/select/Select.js +15 -9
- package/dist/esm/select/useSelect.js +16 -9
- package/dist/esm/types/schema.js +41 -0
- package/dist/esm/utils/getColor.js +6 -1
- 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 +2 -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 +1 -1
- package/dist/types/hooks/useFormBuilder.d.ts +3 -1
- package/dist/types/index.d.ts +4 -2
- package/dist/types/input/ArrayInput.d.ts +20 -3
- package/dist/types/input/Checkbox.d.ts +4 -3
- package/dist/types/input/ColorInput.d.ts +1 -1
- package/dist/types/input/DateInput.d.ts +19 -0
- package/dist/types/input/RadioGroup.d.ts +18 -2
- package/dist/types/input/{Range.d.ts → RangeSlider.d.ts} +1 -1
- package/dist/types/input/Switch.d.ts +7 -3
- package/dist/types/input/Textarea.d.ts +5 -1
- package/dist/types/internal/InputWrapper.d.ts +1 -1
- package/dist/types/select/AutoComplete.d.ts +1 -1
- package/dist/types/select/List.d.ts +1 -1
- package/dist/types/select/Select.d.ts +1 -1
- package/dist/types/select/useSelect.d.ts +2 -1
- package/dist/types/types/index.d.ts +17 -5
- package/dist/types/types/schema.d.ts +79 -33
- package/package.json +10 -7
- package/src/builder/FileField.tsx +53 -27
- package/src/builder/FormField.tsx +45 -32
- package/src/builder/SchemaEditor.tsx +72 -0
- package/src/builder/components/AddSchemaFields.tsx +74 -0
- package/src/builder/components/SchemaArrayForm.tsx +158 -0
- package/src/builder/components/SchemaField.tsx +435 -0
- package/src/form/Controller.tsx +2 -2
- package/src/form/Form.tsx +15 -8
- package/src/hooks/useFormBuilder.tsx +29 -15
- package/src/index.ts +10 -2
- package/src/input/ArrayInput.tsx +80 -42
- package/src/input/AvatarInput.tsx +1 -1
- package/src/input/Checkbox.tsx +5 -4
- package/src/input/ColorInput.tsx +102 -34
- package/src/input/ColorPicker.tsx +1 -35
- package/src/input/DateInput.tsx +248 -0
- package/src/input/Field.tsx +9 -11
- package/src/input/File.tsx +3 -6
- package/src/input/Hidden.tsx +4 -1
- package/src/input/Input.tsx +2 -2
- package/src/input/Number.tsx +2 -2
- package/src/input/Password.tsx +2 -2
- package/src/input/Radio.tsx +1 -1
- package/src/input/RadioGroup.tsx +27 -4
- package/src/input/{Range.tsx → RangeSlider.tsx} +2 -2
- package/src/input/Search.tsx +2 -2
- package/src/input/Slider.tsx +1 -1
- package/src/input/Switch.tsx +42 -6
- package/src/input/Textarea.tsx +11 -3
- package/src/internal/ErrorIcon.tsx +1 -1
- package/src/internal/FieldWrapper.tsx +3 -3
- package/src/internal/InputWrapper.tsx +21 -20
- package/src/select/AutoComplete.tsx +4 -1
- package/src/select/List.tsx +37 -8
- package/src/select/Options.tsx +1 -1
- package/src/select/Select.tsx +9 -3
- package/src/select/useSelect.tsx +30 -19
- package/src/types/index.ts +22 -7
- package/src/types/schema.ts +96 -41
- 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
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import classNames from "classnames";
|
|
25
|
-
import classes from "./Field.module.css";
|
|
26
25
|
|
|
27
26
|
export const FieldWrapper = ({
|
|
28
27
|
label,
|
|
@@ -41,7 +40,7 @@ export const FieldWrapper = ({
|
|
|
41
40
|
return (
|
|
42
41
|
<div
|
|
43
42
|
className={classNames(
|
|
44
|
-
|
|
43
|
+
"fabric-fieldWrapper",
|
|
45
44
|
className,
|
|
46
45
|
inline ? "flex flex-nowrap gap-1" : "block",
|
|
47
46
|
)}
|
|
@@ -50,7 +49,7 @@ export const FieldWrapper = ({
|
|
|
50
49
|
>
|
|
51
50
|
{!isInner && (
|
|
52
51
|
<div
|
|
53
|
-
className="flex flex-nowrap flex-content gap-1 py-0.5 px-2"
|
|
52
|
+
className="flex flex-nowrap flex-content items-end gap-1 py-0.5 px-2"
|
|
54
53
|
style={{ width: inline ? labelWidth : undefined }}
|
|
55
54
|
>
|
|
56
55
|
<label
|
|
@@ -73,6 +72,7 @@ export const FieldWrapper = ({
|
|
|
73
72
|
✽
|
|
74
73
|
</span>
|
|
75
74
|
)}
|
|
75
|
+
<div className="flex-1" />
|
|
76
76
|
{appendLabel && <span className="flex-content">{appendLabel}</span>}
|
|
77
77
|
</div>
|
|
78
78
|
)}
|
|
@@ -21,7 +21,6 @@
|
|
|
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 { FloatingPortal } from "@floating-ui/react";
|
|
25
24
|
import { CoreIcons, Icon } from "@react-fabric/core";
|
|
26
25
|
import { isString } from "@react-fabric/utilities";
|
|
27
26
|
import classNames from "classnames";
|
|
@@ -35,7 +34,6 @@ import {
|
|
|
35
34
|
} from "react";
|
|
36
35
|
import { Fragment } from "react/jsx-runtime";
|
|
37
36
|
import { ErrorIcon } from "./ErrorIcon";
|
|
38
|
-
import classes from "./Field.module.css";
|
|
39
37
|
import { FieldWrapper } from "./FieldWrapper";
|
|
40
38
|
|
|
41
39
|
export const InputWrapper = ({
|
|
@@ -45,11 +43,14 @@ export const InputWrapper = ({
|
|
|
45
43
|
showClear,
|
|
46
44
|
decorateStart,
|
|
47
45
|
decorateEnd,
|
|
46
|
+
decorateEndShowWhenEmpty,
|
|
47
|
+
decorateEndHideWhenEmpty,
|
|
48
48
|
children,
|
|
49
49
|
onClear,
|
|
50
50
|
noOutline,
|
|
51
51
|
noBorder,
|
|
52
52
|
textarea = false,
|
|
53
|
+
textareaExpandable = false,
|
|
53
54
|
"data-inner": isInner,
|
|
54
55
|
"data-open": isOpen,
|
|
55
56
|
// expand autocomplete
|
|
@@ -102,12 +103,12 @@ export const InputWrapper = ({
|
|
|
102
103
|
role="none"
|
|
103
104
|
ref={wrapperRef}
|
|
104
105
|
className={classNames(
|
|
105
|
-
|
|
106
|
-
invalid &&
|
|
106
|
+
"fabric-inputWrapper",
|
|
107
|
+
invalid && "fabric-invalidWrapper",
|
|
107
108
|
!noOutline &&
|
|
108
109
|
!noBorder &&
|
|
109
|
-
"bg-
|
|
110
|
-
"flex flex-nowrap items-center",
|
|
110
|
+
"bg-(--fabric-input) outline overflow-hidden",
|
|
111
|
+
"flex flex-nowrap items-center flex-1",
|
|
111
112
|
textarea ? "rounded-capped group" : "rounded",
|
|
112
113
|
isExpanded
|
|
113
114
|
? "fixed inset-4 z-[99]"
|
|
@@ -117,13 +118,17 @@ export const InputWrapper = ({
|
|
|
117
118
|
!noOutline &&
|
|
118
119
|
"after:absolute after:bottom-0 after:h-px after:bg-primary-500 after:mx-auto",
|
|
119
120
|
)}
|
|
120
|
-
onKeyDown={(e) =>
|
|
121
|
+
onKeyDown={(e) =>
|
|
122
|
+
e.key === "Escape" &&
|
|
123
|
+
isExpanded &&
|
|
124
|
+
(setExpanded(false), e.stopPropagation())
|
|
125
|
+
}
|
|
121
126
|
>
|
|
122
127
|
{decorateStart && (
|
|
123
128
|
<div
|
|
124
129
|
onClickCapture={doFocus}
|
|
125
130
|
className={classNames(
|
|
126
|
-
|
|
131
|
+
"fabric-decorate",
|
|
127
132
|
"text-muted leading-none flex-content",
|
|
128
133
|
isString(decorateStart) && "px-1",
|
|
129
134
|
)}
|
|
@@ -139,7 +144,7 @@ export const InputWrapper = ({
|
|
|
139
144
|
onClick={handleClear}
|
|
140
145
|
className={classNames(
|
|
141
146
|
"text-tint-500 hover:text-tint-700 p-1 z-1 flex-content",
|
|
142
|
-
|
|
147
|
+
"fabric-inputClear",
|
|
143
148
|
textarea && "self-start mt-1 me-1",
|
|
144
149
|
)}
|
|
145
150
|
/>
|
|
@@ -148,15 +153,18 @@ export const InputWrapper = ({
|
|
|
148
153
|
<div
|
|
149
154
|
onClickCapture={doFocus}
|
|
150
155
|
className={classNames(
|
|
151
|
-
|
|
156
|
+
"fabric-decorate",
|
|
152
157
|
"text-muted leading-none flex-content",
|
|
153
158
|
isString(decorateEnd) && "px-1",
|
|
159
|
+
decorateEndHideWhenEmpty && "peer-placeholder-shown:hidden",
|
|
160
|
+
decorateEndShowWhenEmpty &&
|
|
161
|
+
"hidden peer-placeholder-shown:inline-block",
|
|
154
162
|
)}
|
|
155
163
|
>
|
|
156
164
|
{decorateEnd}
|
|
157
165
|
</div>
|
|
158
166
|
)}
|
|
159
|
-
{
|
|
167
|
+
{textareaExpandable && (
|
|
160
168
|
<Icon
|
|
161
169
|
onClick={() => setExpanded(!isExpanded)}
|
|
162
170
|
icon={isExpanded ? CoreIcons.arrowCollapse : CoreIcons.arrowExpand}
|
|
@@ -170,17 +178,10 @@ export const InputWrapper = ({
|
|
|
170
178
|
<Wrapper {...wrapperProps}>
|
|
171
179
|
<div
|
|
172
180
|
ref={elRef}
|
|
173
|
-
className="flex-1 relative min-h-8"
|
|
181
|
+
className="flex-1 relative min-h-8 flex flex-col flex-nowrap"
|
|
174
182
|
style={{ width, flexBasis: width }}
|
|
175
183
|
>
|
|
176
|
-
{
|
|
177
|
-
{isExpanded && (
|
|
178
|
-
<FloatingPortal
|
|
179
|
-
root={elRef.current?.closest<HTMLElement>(".theme-base")}
|
|
180
|
-
>
|
|
181
|
-
{el}
|
|
182
|
-
</FloatingPortal>
|
|
183
|
-
)}
|
|
184
|
+
{el}
|
|
184
185
|
</div>
|
|
185
186
|
</Wrapper>
|
|
186
187
|
);
|
|
@@ -80,6 +80,7 @@ export const AutoComplete = ({
|
|
|
80
80
|
onInput,
|
|
81
81
|
onFocus,
|
|
82
82
|
onBlur,
|
|
83
|
+
onQuery,
|
|
83
84
|
onEnterPressed,
|
|
84
85
|
...rest
|
|
85
86
|
}: AutoCompleteProps) => {
|
|
@@ -104,6 +105,7 @@ export const AutoComplete = ({
|
|
|
104
105
|
multiple,
|
|
105
106
|
onChange,
|
|
106
107
|
onSelect,
|
|
108
|
+
onQuery,
|
|
107
109
|
});
|
|
108
110
|
|
|
109
111
|
const dropdownKey = useId();
|
|
@@ -292,6 +294,7 @@ export const AutoComplete = ({
|
|
|
292
294
|
name={name}
|
|
293
295
|
size={1}
|
|
294
296
|
onInput={onInput}
|
|
297
|
+
autoFocus={autoFocus}
|
|
295
298
|
rows={!multiple && expandOnEdit && state.open ? 5 : 1}
|
|
296
299
|
className={classNames(
|
|
297
300
|
"appearance-none bg-transparent outline-none border-none ring-0 flex-1 resize-none",
|
|
@@ -328,7 +331,7 @@ export const AutoComplete = ({
|
|
|
328
331
|
>
|
|
329
332
|
<Options
|
|
330
333
|
ref={refs.setFloating}
|
|
331
|
-
className="outline shadow-lg max-h-[40vh] z-
|
|
334
|
+
className="outline shadow-lg max-h-[40vh] z-(--z-popover)"
|
|
332
335
|
style={{
|
|
333
336
|
width: refs.reference.current?.getBoundingClientRect().width,
|
|
334
337
|
...floatingStyles,
|
package/src/select/List.tsx
CHANGED
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
import { Chip } from "@react-fabric/core";
|
|
33
33
|
import { isArray, isEmpty } from "@react-fabric/utilities";
|
|
34
34
|
import classNames from "classnames";
|
|
35
|
-
import { Fragment, useCallback, useId, useMemo } from "react";
|
|
35
|
+
import { Fragment, useCallback, useId, useLayoutEffect, useMemo } from "react";
|
|
36
36
|
import { FieldWrapper } from "../internal/FieldWrapper";
|
|
37
37
|
import { InputWrapper } from "../internal/InputWrapper";
|
|
38
38
|
import { type SelectProps } from "../types";
|
|
@@ -55,6 +55,7 @@ export const List = <T extends AnyObject = string>({
|
|
|
55
55
|
valueProperty,
|
|
56
56
|
multiple,
|
|
57
57
|
allowCreate,
|
|
58
|
+
searchable,
|
|
58
59
|
createOption,
|
|
59
60
|
matcher,
|
|
60
61
|
renderer,
|
|
@@ -72,7 +73,9 @@ export const List = <T extends AnyObject = string>({
|
|
|
72
73
|
// @ts-expect-error ignore
|
|
73
74
|
"data-hide-selected": hideSelected,
|
|
74
75
|
...rest
|
|
75
|
-
}: SelectProps<T
|
|
76
|
+
}: Omit<SelectProps<T>, "defaultOpen" | "hideHandle"> & {
|
|
77
|
+
height?: string | number;
|
|
78
|
+
}) => {
|
|
76
79
|
const Wrapper = useMemo(() => (isInner ? Fragment : FieldWrapper), [isInner]);
|
|
77
80
|
const wrapperProps = useMemo(
|
|
78
81
|
() => (isInner ? {} : { ...rest }),
|
|
@@ -166,9 +169,12 @@ export const List = <T extends AnyObject = string>({
|
|
|
166
169
|
}
|
|
167
170
|
} else if (evt.key === "Backspace") {
|
|
168
171
|
!state.query && handleRemove();
|
|
169
|
-
} else if (["ArrowUp", "ArrowDown"].includes(evt.key)) {
|
|
170
|
-
if (
|
|
171
|
-
|
|
172
|
+
} else if (!searchable || ["ArrowUp", "ArrowDown"].includes(evt.key)) {
|
|
173
|
+
if (
|
|
174
|
+
["ArrowUp", "ArrowDown"].includes(evt.key) &&
|
|
175
|
+
state.activeIndex === null
|
|
176
|
+
) {
|
|
177
|
+
setActiveIndex(state.selectedIndex ?? 0);
|
|
172
178
|
}
|
|
173
179
|
setTimeout(() => {
|
|
174
180
|
props.onKeyDown?.(evt);
|
|
@@ -225,6 +231,15 @@ export const List = <T extends AnyObject = string>({
|
|
|
225
231
|
}
|
|
226
232
|
}, [state.value, state.query, placeholder, renderer]);
|
|
227
233
|
|
|
234
|
+
useLayoutEffect(() => {
|
|
235
|
+
setTimeout(() => {
|
|
236
|
+
state.selectedIndex &&
|
|
237
|
+
listRef.current[state.selectedIndex]?.scrollIntoView({
|
|
238
|
+
block: "center",
|
|
239
|
+
});
|
|
240
|
+
}, 100);
|
|
241
|
+
}, [state.selectedIndex]);
|
|
242
|
+
|
|
228
243
|
return (
|
|
229
244
|
<Wrapper {...wrapperProps}>
|
|
230
245
|
<InputWrapper
|
|
@@ -240,22 +255,34 @@ export const List = <T extends AnyObject = string>({
|
|
|
240
255
|
wrapperRef={refs.setReference}
|
|
241
256
|
>
|
|
242
257
|
<div
|
|
258
|
+
role="none"
|
|
243
259
|
data-select-display="true"
|
|
244
|
-
className=
|
|
260
|
+
className={classNames(
|
|
261
|
+
"group flex-1 py-1 px-2 truncate text-start flex gap-1 relative min-h-5 justify-start",
|
|
262
|
+
multiple ? "flex-wrap" : "flex-nowrap overflow-hidden",
|
|
263
|
+
)}
|
|
264
|
+
{...referenceProps}
|
|
265
|
+
onMouseUp={(e) =>
|
|
266
|
+
e.currentTarget.querySelector<HTMLElement>("input")?.focus()
|
|
267
|
+
}
|
|
245
268
|
>
|
|
246
269
|
{!hideSelected && displayValue}
|
|
247
270
|
<input
|
|
271
|
+
readOnly={!searchable || readOnly}
|
|
248
272
|
disabled={disabled}
|
|
249
273
|
value={state.query ?? ""}
|
|
250
274
|
aria-invalid={invalid}
|
|
251
275
|
data-testid={name}
|
|
252
276
|
name={name}
|
|
277
|
+
size={1}
|
|
278
|
+
ref={ref}
|
|
279
|
+
autoFocus={autoFocus}
|
|
253
280
|
className={classNames(
|
|
254
281
|
"appearance-none bg-transparent outline-none border-none ring-0 flex-1 min-w-24",
|
|
255
282
|
disabled && "cursor-not-allowed pointer-events-none",
|
|
283
|
+
multiple && "min-w-24",
|
|
256
284
|
)}
|
|
257
285
|
placeholder={isEmpty(state.value) ? placeholder : ""}
|
|
258
|
-
{...referenceProps}
|
|
259
286
|
onChange={(evt) => handleQuery(evt.target.value)}
|
|
260
287
|
/>
|
|
261
288
|
</div>
|
|
@@ -277,10 +304,12 @@ export const List = <T extends AnyObject = string>({
|
|
|
277
304
|
{...floatingProps}
|
|
278
305
|
items={state.items}
|
|
279
306
|
active={state.activeIndex}
|
|
307
|
+
valueProperty={valueProperty}
|
|
308
|
+
labelProperty={labelProperty}
|
|
280
309
|
itemRef={setItemRef}
|
|
281
310
|
itemProps={makeItemProps}
|
|
282
311
|
>
|
|
283
|
-
{(item) => renderer?.(item) ??
|
|
312
|
+
{(item, label) => renderer?.(item) ?? label}
|
|
284
313
|
</Options>
|
|
285
314
|
</FloatingFocusManager>
|
|
286
315
|
</Wrapper>
|
package/src/select/Options.tsx
CHANGED
package/src/select/Select.tsx
CHANGED
|
@@ -56,6 +56,8 @@ export const Select = <T extends AnyObject = string>({
|
|
|
56
56
|
autoFocus,
|
|
57
57
|
error,
|
|
58
58
|
options,
|
|
59
|
+
defaultOpen,
|
|
60
|
+
hideHandle,
|
|
59
61
|
groupProperty,
|
|
60
62
|
labelProperty = "label" as AnyObject,
|
|
61
63
|
valueProperty = "id" as AnyObject,
|
|
@@ -86,6 +88,7 @@ export const Select = <T extends AnyObject = string>({
|
|
|
86
88
|
value,
|
|
87
89
|
options,
|
|
88
90
|
multiple,
|
|
91
|
+
defaultOpen,
|
|
89
92
|
groupProperty,
|
|
90
93
|
labelProperty,
|
|
91
94
|
valueProperty,
|
|
@@ -172,6 +175,9 @@ export const Select = <T extends AnyObject = string>({
|
|
|
172
175
|
return false;
|
|
173
176
|
} else if (state.query) {
|
|
174
177
|
handleQuery("");
|
|
178
|
+
evt.preventDefault();
|
|
179
|
+
evt.stopPropagation();
|
|
180
|
+
return false;
|
|
175
181
|
} else {
|
|
176
182
|
onEnterPressed?.(evt);
|
|
177
183
|
}
|
|
@@ -273,6 +279,7 @@ export const Select = <T extends AnyObject = string>({
|
|
|
273
279
|
"group flex-1 py-1 px-2 truncate text-start flex gap-1 relative min-h-5 justify-start",
|
|
274
280
|
multiple ? "flex-wrap" : "flex-nowrap overflow-hidden",
|
|
275
281
|
)}
|
|
282
|
+
{...referenceProps}
|
|
276
283
|
onMouseUp={(e) =>
|
|
277
284
|
e.currentTarget.querySelector<HTMLElement>("input")?.focus()
|
|
278
285
|
}
|
|
@@ -294,11 +301,10 @@ export const Select = <T extends AnyObject = string>({
|
|
|
294
301
|
multiple && "min-w-24",
|
|
295
302
|
)}
|
|
296
303
|
placeholder={isEmpty(state.value) ? placeholder : ""}
|
|
297
|
-
{...referenceProps}
|
|
298
304
|
onChange={(evt) => handleQuery(evt.target.value)}
|
|
299
305
|
/>
|
|
300
306
|
</div>
|
|
301
|
-
{!state.loading && (
|
|
307
|
+
{!state.loading && !hideHandle && (
|
|
302
308
|
<Icon
|
|
303
309
|
className={classNames(
|
|
304
310
|
"flex-content p-2 z-0 order-9 text-muted cursor-pointer self-stretch flex items-center h-auto",
|
|
@@ -327,7 +333,7 @@ export const Select = <T extends AnyObject = string>({
|
|
|
327
333
|
>
|
|
328
334
|
<Options
|
|
329
335
|
ref={refs.setFloating}
|
|
330
|
-
className="outline shadow-lg max-h-[40vh] z-
|
|
336
|
+
className="outline shadow-lg max-h-[40vh] z-(--z-popover)"
|
|
331
337
|
style={{
|
|
332
338
|
width: refs.reference.current?.getBoundingClientRect().width,
|
|
333
339
|
...floatingStyles,
|
package/src/select/useSelect.tsx
CHANGED
|
@@ -23,10 +23,12 @@
|
|
|
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,
|
|
29
30
|
isArray,
|
|
31
|
+
isString,
|
|
30
32
|
matchString,
|
|
31
33
|
} from "@react-fabric/utilities";
|
|
32
34
|
import {
|
|
@@ -74,6 +76,7 @@ interface State {
|
|
|
74
76
|
export const useSelect = ({
|
|
75
77
|
options = EMPTY_ARRAY,
|
|
76
78
|
value,
|
|
79
|
+
defaultOpen = false,
|
|
77
80
|
autoComplete,
|
|
78
81
|
multiple = false,
|
|
79
82
|
allowCreate = false,
|
|
@@ -89,6 +92,7 @@ export const useSelect = ({
|
|
|
89
92
|
value?: AnyObject;
|
|
90
93
|
options?: AnyObject[];
|
|
91
94
|
multiple?: boolean;
|
|
95
|
+
defaultOpen?: boolean;
|
|
92
96
|
allowCreate?: boolean;
|
|
93
97
|
groupProperty?: AnyObject;
|
|
94
98
|
valueProperty?: AnyObject;
|
|
@@ -173,9 +177,9 @@ export const useSelect = ({
|
|
|
173
177
|
if (action.value !== undefined) {
|
|
174
178
|
if (multiple) {
|
|
175
179
|
const values = [];
|
|
176
|
-
if (action.value
|
|
180
|
+
if (isString(action.value))
|
|
177
181
|
values.push(...action.value.split(/,\s?/));
|
|
178
|
-
else
|
|
182
|
+
else values.push(action.value);
|
|
179
183
|
newState.value = [...state.value];
|
|
180
184
|
values.forEach((value: AnyObject) => {
|
|
181
185
|
newState.value = newState.value.includes?.(value)
|
|
@@ -222,6 +226,7 @@ export const useSelect = ({
|
|
|
222
226
|
if (autoComplete && action.query !== undefined) {
|
|
223
227
|
optionList = dedupe([action.query, ...optionList]);
|
|
224
228
|
}
|
|
229
|
+
optionList = optionList.sort(compareValues("asc", labelProperty));
|
|
225
230
|
const grouped = groupBy(optionList, groupProperty, "");
|
|
226
231
|
if (action.open) newState.open = true;
|
|
227
232
|
newState.items = [];
|
|
@@ -229,7 +234,7 @@ export const useSelect = ({
|
|
|
229
234
|
if (allowCreate && action.query)
|
|
230
235
|
newState.items.push({
|
|
231
236
|
___create___: true,
|
|
232
|
-
value:
|
|
237
|
+
value: action.query,
|
|
233
238
|
});
|
|
234
239
|
Object.entries(grouped).forEach(([key, list]) => {
|
|
235
240
|
if (key) {
|
|
@@ -253,7 +258,7 @@ export const useSelect = ({
|
|
|
253
258
|
{
|
|
254
259
|
multiple,
|
|
255
260
|
emptyValue,
|
|
256
|
-
open:
|
|
261
|
+
open: defaultOpen,
|
|
257
262
|
loading: false,
|
|
258
263
|
activeIndex: null,
|
|
259
264
|
selectedIndex: null,
|
|
@@ -282,24 +287,26 @@ export const useSelect = ({
|
|
|
282
287
|
dispatch({ type: "options", options: optionsRef.current });
|
|
283
288
|
dispatch({
|
|
284
289
|
type: "setValue",
|
|
285
|
-
value: hasValue ?? newOption ??
|
|
290
|
+
value: hasValue ?? newOption ?? deferred,
|
|
286
291
|
});
|
|
287
292
|
}
|
|
288
293
|
if (multiple) {
|
|
289
294
|
optionsRef.current = [];
|
|
290
|
-
const value = (deferred
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
295
|
+
const value = (isArray(deferred) ? deferred : []).map(
|
|
296
|
+
(item: AnyObject) => {
|
|
297
|
+
const newOption =
|
|
298
|
+
(allowCreate || autoComplete) && item
|
|
299
|
+
? createOption(item)
|
|
300
|
+
: undefined;
|
|
301
|
+
const hasValue = options.find(
|
|
302
|
+
(opt: AnyObject) => (opt[valueProperty] ?? opt) === item,
|
|
303
|
+
);
|
|
304
|
+
if (!hasValue && newOption) {
|
|
305
|
+
optionsRef.current.push(newOption);
|
|
306
|
+
}
|
|
307
|
+
return hasValue ?? newOption ?? item;
|
|
308
|
+
},
|
|
309
|
+
);
|
|
303
310
|
optionsRef.current = [...optionsRef.current, ...(options ?? [])];
|
|
304
311
|
dispatch({ type: "options", options: optionsRef.current });
|
|
305
312
|
dispatch({
|
|
@@ -367,7 +374,11 @@ export const useSelect = ({
|
|
|
367
374
|
value: value?.___create___ ? value.value : value,
|
|
368
375
|
});
|
|
369
376
|
if (value?.___create___) {
|
|
370
|
-
|
|
377
|
+
let val = [value.value];
|
|
378
|
+
if (multiple) {
|
|
379
|
+
val = value.value.split(/,\s?/);
|
|
380
|
+
}
|
|
381
|
+
optionsRef.current = [...optionsRef.current, ...val.map(createOption)];
|
|
371
382
|
}
|
|
372
383
|
if (autoComplete) {
|
|
373
384
|
optionsRef.current = dedupe([
|
package/src/types/index.ts
CHANGED
|
@@ -21,9 +21,12 @@
|
|
|
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 {
|
|
24
|
+
import {
|
|
25
|
+
type RefProp,
|
|
26
|
+
type TestProps,
|
|
27
|
+
} from "@react-fabric/core/dist/types/types";
|
|
25
28
|
|
|
26
|
-
export interface InputProps<V = string> {
|
|
29
|
+
export interface InputProps<V = string> extends TestProps {
|
|
27
30
|
/**
|
|
28
31
|
* value
|
|
29
32
|
*/
|
|
@@ -31,7 +34,7 @@ export interface InputProps<V = string> {
|
|
|
31
34
|
/**
|
|
32
35
|
* change handler
|
|
33
36
|
*/
|
|
34
|
-
onChange?: (value
|
|
37
|
+
onChange?: (value: V | null) => void;
|
|
35
38
|
/**
|
|
36
39
|
* on focus event handler
|
|
37
40
|
*/
|
|
@@ -69,6 +72,8 @@ export interface InputProps<V = string> {
|
|
|
69
72
|
*
|
|
70
73
|
*/
|
|
71
74
|
decorateEnd?: JSX.Element | string | number | boolean;
|
|
75
|
+
decorateEndShowWhenEmpty?: boolean;
|
|
76
|
+
decorateEndHideWhenEmpty?: boolean;
|
|
72
77
|
/**
|
|
73
78
|
* info label at bottom
|
|
74
79
|
*/
|
|
@@ -117,9 +122,15 @@ export interface InputProps<V = string> {
|
|
|
117
122
|
labelWidth?: string;
|
|
118
123
|
}
|
|
119
124
|
|
|
120
|
-
export interface BaseSelectProps<T>
|
|
121
|
-
|
|
122
|
-
|
|
125
|
+
export interface BaseSelectProps<T extends AnyObject = string> {
|
|
126
|
+
/**
|
|
127
|
+
* open dropdown by default
|
|
128
|
+
*/
|
|
129
|
+
defaultOpen?: boolean;
|
|
130
|
+
/**
|
|
131
|
+
* hide dropdown handle
|
|
132
|
+
*/
|
|
133
|
+
hideHandle?: boolean;
|
|
123
134
|
/**
|
|
124
135
|
* list options
|
|
125
136
|
*/
|
|
@@ -162,7 +173,11 @@ export interface BaseSelectProps<T>
|
|
|
162
173
|
matcher?: (option: T, query: string) => boolean;
|
|
163
174
|
}
|
|
164
175
|
|
|
165
|
-
|
|
176
|
+
type BaseSelectType<T> = BaseSelectProps<T> &
|
|
177
|
+
RefProp<HTMLInputElement> &
|
|
178
|
+
Omit<InputProps, "value" | "onChange">;
|
|
179
|
+
|
|
180
|
+
export type SelectProps<T> = BaseSelectType<T> &
|
|
166
181
|
(
|
|
167
182
|
| {
|
|
168
183
|
/**
|