@react-fabric/form 1.0.0-beta.5 → 1.0.0-beta.51
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} +64 -29
- package/{src/input/File.module.css → css/_file.css} +21 -6
- package/css/styles.css +27 -0
- package/dist/cjs/builder/FileField.js +37 -25
- 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 +143 -0
- package/dist/cjs/builder/components/SchemaField.js +414 -0
- package/dist/cjs/form/Controller.js +1 -1
- package/dist/cjs/form/Form.js +11 -6
- package/dist/cjs/hooks/useFormBuilder.js +27 -15
- package/dist/cjs/index.js +11 -2
- package/dist/cjs/input/ArrayInput.js +61 -33
- package/dist/cjs/input/AvatarInput.js +9 -8
- package/dist/cjs/input/Checkbox.js +6 -5
- package/dist/cjs/input/ColorInput.js +95 -56
- package/dist/cjs/input/ColorPicker.js +1 -35
- package/dist/cjs/input/DateInput.js +186 -0
- package/dist/cjs/input/Field.js +5 -4
- package/dist/cjs/input/File.js +4 -7
- package/dist/cjs/input/Hidden.js +3 -1
- package/dist/cjs/input/Input.js +11 -7
- package/dist/cjs/input/Number.js +9 -7
- package/dist/cjs/input/Password.js +10 -8
- package/dist/cjs/input/Radio.js +2 -2
- package/dist/cjs/input/RadioGroup.js +6 -3
- package/dist/cjs/input/{Range.js → RangeSlider.js} +5 -5
- package/dist/cjs/input/Search.js +16 -16
- package/dist/cjs/input/Slider.js +3 -3
- package/dist/cjs/input/Switch.js +30 -7
- package/dist/cjs/input/Textarea.js +13 -4
- package/dist/cjs/internal/ErrorIcon.js +1 -1
- package/dist/cjs/internal/FieldWrapper.js +11 -4
- package/dist/cjs/internal/InputWrapper.js +19 -16
- package/dist/cjs/select/AutoComplete.js +12 -8
- package/dist/cjs/select/DualList.js +4 -1
- package/dist/cjs/select/List.js +45 -20
- package/dist/cjs/select/Options.js +2 -2
- package/dist/cjs/select/Select.js +14 -4
- package/dist/cjs/select/useSelect.js +10 -6
- package/dist/cjs/types/schema.js +43 -0
- package/dist/cjs/utils/eventHandlers.js +11 -5
- package/dist/cjs/utils/getColor.js +6 -0
- package/dist/esm/builder/FileField.js +39 -27
- 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 +143 -0
- package/dist/esm/builder/components/SchemaField.js +414 -0
- package/dist/esm/form/Controller.js +1 -1
- package/dist/esm/form/Form.js +11 -6
- package/dist/esm/hooks/useFormBuilder.js +25 -13
- package/dist/esm/index.js +5 -1
- package/dist/esm/input/ArrayInput.js +61 -33
- package/dist/esm/input/AvatarInput.js +9 -8
- package/dist/esm/input/Checkbox.js +6 -5
- package/dist/esm/input/ColorInput.js +97 -58
- package/dist/esm/input/ColorPicker.js +1 -35
- package/dist/esm/input/DateInput.js +186 -0
- package/dist/esm/input/Field.js +6 -5
- package/dist/esm/input/File.js +6 -9
- package/dist/esm/input/Hidden.js +3 -1
- package/dist/esm/input/Input.js +13 -9
- package/dist/esm/input/Number.js +11 -9
- package/dist/esm/input/Password.js +12 -10
- package/dist/esm/input/Radio.js +2 -2
- package/dist/esm/input/RadioGroup.js +7 -4
- package/dist/esm/input/{Range.js → RangeSlider.js} +5 -5
- package/dist/esm/input/Search.js +18 -18
- package/dist/esm/input/Slider.js +3 -3
- package/dist/esm/input/Switch.js +32 -9
- package/dist/esm/input/Textarea.js +14 -5
- package/dist/esm/internal/ErrorIcon.js +1 -1
- package/dist/esm/internal/FieldWrapper.js +11 -4
- package/dist/esm/internal/InputWrapper.js +19 -16
- package/dist/esm/select/AutoComplete.js +12 -8
- package/dist/esm/select/DualList.js +4 -1
- package/dist/esm/select/List.js +46 -21
- package/dist/esm/select/Options.js +2 -2
- package/dist/esm/select/Select.js +14 -4
- package/dist/esm/select/useSelect.js +11 -7
- package/{src/input/ColorPicker.module.css → dist/esm/types/schema.js} +18 -27
- package/dist/esm/utils/eventHandlers.js +11 -5
- 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 +2 -2
- package/dist/types/form/Form.d.ts +5 -1
- package/dist/types/hooks/useFormBuilder.d.ts +3 -1
- package/dist/types/index.d.ts +5 -2
- package/dist/types/input/ArrayInput.d.ts +39 -6
- package/dist/types/input/AvatarInput.d.ts +6 -3
- package/dist/types/input/Checkbox.d.ts +4 -4
- package/dist/types/input/ColorInput.d.ts +2 -2
- package/dist/types/input/ColorPicker.d.ts +1 -1
- package/dist/types/input/DateInput.d.ts +19 -0
- package/dist/types/input/Field.d.ts +0 -1
- package/dist/types/input/Input.d.ts +1 -1
- package/dist/types/input/Number.d.ts +1 -1
- package/dist/types/input/Password.d.ts +1 -1
- package/dist/types/input/Radio.d.ts +0 -1
- package/dist/types/input/RadioGroup.d.ts +18 -3
- package/dist/types/input/{Range.d.ts → RangeSlider.d.ts} +1 -1
- package/dist/types/input/Search.d.ts +5 -1
- package/dist/types/input/Switch.d.ts +7 -4
- package/dist/types/input/Textarea.d.ts +5 -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 +1 -1
- package/dist/types/select/DualList.d.ts +6 -2
- 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 +7 -15
- package/dist/types/types/index.d.ts +28 -7
- package/dist/types/types/schema.d.ts +78 -33
- package/dist/types/utils/eventHandlers.d.ts +1 -1
- package/package.json +15 -12
- package/src/builder/FileField.tsx +53 -27
- package/src/builder/FormField.tsx +52 -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 +436 -0
- package/src/form/Controller.tsx +3 -3
- package/src/form/Form.tsx +14 -5
- package/src/hooks/useFormBuilder.tsx +29 -15
- package/src/index.ts +11 -2
- package/src/input/ArrayInput.tsx +130 -54
- package/src/input/AvatarInput.tsx +12 -8
- package/src/input/Checkbox.tsx +6 -5
- package/src/input/ColorInput.tsx +113 -69
- package/src/input/ColorPicker.tsx +2 -36
- package/src/input/DateInput.tsx +250 -0
- package/src/input/Field.tsx +10 -11
- package/src/input/File.tsx +7 -14
- package/src/input/Hidden.tsx +4 -1
- package/src/input/Input.tsx +15 -10
- package/src/input/Number.tsx +10 -10
- package/src/input/Password.tsx +13 -12
- package/src/input/Radio.tsx +2 -2
- package/src/input/RadioGroup.tsx +27 -4
- package/src/input/{Range.tsx → RangeSlider.tsx} +4 -4
- package/src/input/Search.tsx +20 -19
- package/src/input/Slider.tsx +3 -3
- package/src/input/Switch.tsx +43 -7
- package/src/input/Textarea.tsx +19 -5
- package/src/internal/ErrorIcon.tsx +1 -1
- package/src/internal/FieldWrapper.tsx +8 -4
- package/src/internal/InputWrapper.tsx +36 -20
- package/src/select/AutoComplete.tsx +5 -1
- package/src/select/DualList.tsx +8 -0
- package/src/select/List.tsx +41 -8
- package/src/select/Options.tsx +1 -1
- package/src/select/Select.tsx +13 -3
- package/src/select/useSelect.tsx +13 -5
- package/src/types/index.ts +34 -8
- package/src/types/schema.ts +96 -41
- package/src/utils/eventHandlers.ts +9 -3
- 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
|
@@ -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";
|
|
@@ -53,8 +53,10 @@ export const List = <T extends AnyObject = string>({
|
|
|
53
53
|
groupProperty,
|
|
54
54
|
labelProperty,
|
|
55
55
|
valueProperty,
|
|
56
|
+
sortProperty,
|
|
56
57
|
multiple,
|
|
57
58
|
allowCreate,
|
|
59
|
+
searchable,
|
|
58
60
|
createOption,
|
|
59
61
|
matcher,
|
|
60
62
|
renderer,
|
|
@@ -62,6 +64,7 @@ export const List = <T extends AnyObject = string>({
|
|
|
62
64
|
onChange,
|
|
63
65
|
onSelect,
|
|
64
66
|
onEnterPressed,
|
|
67
|
+
emptyDisplay,
|
|
65
68
|
invalid,
|
|
66
69
|
error,
|
|
67
70
|
allowClear,
|
|
@@ -72,7 +75,9 @@ export const List = <T extends AnyObject = string>({
|
|
|
72
75
|
// @ts-expect-error ignore
|
|
73
76
|
"data-hide-selected": hideSelected,
|
|
74
77
|
...rest
|
|
75
|
-
}: SelectProps<T
|
|
78
|
+
}: Omit<SelectProps<T>, "defaultOpen" | "hideHandle"> & {
|
|
79
|
+
height?: string | number;
|
|
80
|
+
}) => {
|
|
76
81
|
const Wrapper = useMemo(() => (isInner ? Fragment : FieldWrapper), [isInner]);
|
|
77
82
|
const wrapperProps = useMemo(
|
|
78
83
|
() => (isInner ? {} : { ...rest }),
|
|
@@ -96,6 +101,7 @@ export const List = <T extends AnyObject = string>({
|
|
|
96
101
|
groupProperty,
|
|
97
102
|
labelProperty,
|
|
98
103
|
valueProperty,
|
|
104
|
+
sortProperty,
|
|
99
105
|
allowCreate,
|
|
100
106
|
createOption,
|
|
101
107
|
matcher,
|
|
@@ -166,9 +172,12 @@ export const List = <T extends AnyObject = string>({
|
|
|
166
172
|
}
|
|
167
173
|
} else if (evt.key === "Backspace") {
|
|
168
174
|
!state.query && handleRemove();
|
|
169
|
-
} else if (["ArrowUp", "ArrowDown"].includes(evt.key)) {
|
|
170
|
-
if (
|
|
171
|
-
|
|
175
|
+
} else if (!searchable || ["ArrowUp", "ArrowDown"].includes(evt.key)) {
|
|
176
|
+
if (
|
|
177
|
+
["ArrowUp", "ArrowDown"].includes(evt.key) &&
|
|
178
|
+
state.activeIndex === null
|
|
179
|
+
) {
|
|
180
|
+
setActiveIndex(state.selectedIndex ?? 0);
|
|
172
181
|
}
|
|
173
182
|
setTimeout(() => {
|
|
174
183
|
props.onKeyDown?.(evt);
|
|
@@ -225,6 +234,15 @@ export const List = <T extends AnyObject = string>({
|
|
|
225
234
|
}
|
|
226
235
|
}, [state.value, state.query, placeholder, renderer]);
|
|
227
236
|
|
|
237
|
+
useLayoutEffect(() => {
|
|
238
|
+
setTimeout(() => {
|
|
239
|
+
state.selectedIndex &&
|
|
240
|
+
listRef.current[state.selectedIndex]?.scrollIntoView({
|
|
241
|
+
block: "center",
|
|
242
|
+
});
|
|
243
|
+
}, 100);
|
|
244
|
+
}, [state.selectedIndex]);
|
|
245
|
+
|
|
228
246
|
return (
|
|
229
247
|
<Wrapper {...wrapperProps}>
|
|
230
248
|
<InputWrapper
|
|
@@ -240,22 +258,34 @@ export const List = <T extends AnyObject = string>({
|
|
|
240
258
|
wrapperRef={refs.setReference}
|
|
241
259
|
>
|
|
242
260
|
<div
|
|
261
|
+
role="none"
|
|
243
262
|
data-select-display="true"
|
|
244
|
-
className=
|
|
263
|
+
className={classNames(
|
|
264
|
+
"group flex-1 py-1 px-2 truncate text-start flex gap-1 relative min-h-5 justify-start",
|
|
265
|
+
multiple ? "flex-wrap" : "flex-nowrap overflow-hidden",
|
|
266
|
+
)}
|
|
267
|
+
{...referenceProps}
|
|
268
|
+
onMouseUp={(e) =>
|
|
269
|
+
e.currentTarget.querySelector<HTMLElement>("input")?.focus()
|
|
270
|
+
}
|
|
245
271
|
>
|
|
246
272
|
{!hideSelected && displayValue}
|
|
247
273
|
<input
|
|
274
|
+
readOnly={!searchable || readOnly}
|
|
248
275
|
disabled={disabled}
|
|
249
276
|
value={state.query ?? ""}
|
|
250
277
|
aria-invalid={invalid}
|
|
251
278
|
data-testid={name}
|
|
252
279
|
name={name}
|
|
280
|
+
size={1}
|
|
281
|
+
ref={ref}
|
|
282
|
+
autoFocus={autoFocus}
|
|
253
283
|
className={classNames(
|
|
254
284
|
"appearance-none bg-transparent outline-none border-none ring-0 flex-1 min-w-24",
|
|
255
285
|
disabled && "cursor-not-allowed pointer-events-none",
|
|
286
|
+
multiple && "min-w-24",
|
|
256
287
|
)}
|
|
257
288
|
placeholder={isEmpty(state.value) ? placeholder : ""}
|
|
258
|
-
{...referenceProps}
|
|
259
289
|
onChange={(evt) => handleQuery(evt.target.value)}
|
|
260
290
|
/>
|
|
261
291
|
</div>
|
|
@@ -277,10 +307,13 @@ export const List = <T extends AnyObject = string>({
|
|
|
277
307
|
{...floatingProps}
|
|
278
308
|
items={state.items}
|
|
279
309
|
active={state.activeIndex}
|
|
310
|
+
valueProperty={valueProperty}
|
|
311
|
+
labelProperty={labelProperty}
|
|
280
312
|
itemRef={setItemRef}
|
|
281
313
|
itemProps={makeItemProps}
|
|
314
|
+
empty={emptyDisplay}
|
|
282
315
|
>
|
|
283
|
-
{(item) => renderer?.(item) ??
|
|
316
|
+
{(item, label) => renderer?.(item) ?? label}
|
|
284
317
|
</Options>
|
|
285
318
|
</FloatingFocusManager>
|
|
286
319
|
</Wrapper>
|
package/src/select/Options.tsx
CHANGED
package/src/select/Select.tsx
CHANGED
|
@@ -56,12 +56,16 @@ 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,
|
|
64
|
+
sortProperty,
|
|
62
65
|
multiple,
|
|
63
66
|
searchable,
|
|
64
67
|
allowCreate,
|
|
68
|
+
emptyDisplay,
|
|
65
69
|
createOption,
|
|
66
70
|
matcher,
|
|
67
71
|
renderer,
|
|
@@ -86,9 +90,11 @@ export const Select = <T extends AnyObject = string>({
|
|
|
86
90
|
value,
|
|
87
91
|
options,
|
|
88
92
|
multiple,
|
|
93
|
+
defaultOpen,
|
|
89
94
|
groupProperty,
|
|
90
95
|
labelProperty,
|
|
91
96
|
valueProperty,
|
|
97
|
+
sortProperty,
|
|
92
98
|
allowCreate,
|
|
93
99
|
createOption,
|
|
94
100
|
matcher,
|
|
@@ -172,6 +178,9 @@ export const Select = <T extends AnyObject = string>({
|
|
|
172
178
|
return false;
|
|
173
179
|
} else if (state.query) {
|
|
174
180
|
handleQuery("");
|
|
181
|
+
evt.preventDefault();
|
|
182
|
+
evt.stopPropagation();
|
|
183
|
+
return false;
|
|
175
184
|
} else {
|
|
176
185
|
onEnterPressed?.(evt);
|
|
177
186
|
}
|
|
@@ -290,7 +299,7 @@ export const Select = <T extends AnyObject = string>({
|
|
|
290
299
|
ref={ref}
|
|
291
300
|
autoFocus={autoFocus}
|
|
292
301
|
className={classNames(
|
|
293
|
-
"appearance-none bg-transparent outline-none border-none ring-0 flex-1",
|
|
302
|
+
"appearance-none bg-transparent outline-none border-none ring-0 flex-1 peer",
|
|
294
303
|
disabled && "cursor-not-allowed pointer-events-none",
|
|
295
304
|
multiple && "min-w-24",
|
|
296
305
|
)}
|
|
@@ -298,7 +307,7 @@ export const Select = <T extends AnyObject = string>({
|
|
|
298
307
|
onChange={(evt) => handleQuery(evt.target.value)}
|
|
299
308
|
/>
|
|
300
309
|
</div>
|
|
301
|
-
{!state.loading && (
|
|
310
|
+
{!state.loading && !hideHandle && (
|
|
302
311
|
<Icon
|
|
303
312
|
className={classNames(
|
|
304
313
|
"flex-content p-2 z-0 order-9 text-muted cursor-pointer self-stretch flex items-center h-auto",
|
|
@@ -327,7 +336,7 @@ export const Select = <T extends AnyObject = string>({
|
|
|
327
336
|
>
|
|
328
337
|
<Options
|
|
329
338
|
ref={refs.setFloating}
|
|
330
|
-
className="outline shadow-lg max-h-[40vh] z-
|
|
339
|
+
className="outline shadow-lg max-h-[40vh] z-(--z-popover)"
|
|
331
340
|
style={{
|
|
332
341
|
width: refs.reference.current?.getBoundingClientRect().width,
|
|
333
342
|
...floatingStyles,
|
|
@@ -339,6 +348,7 @@ export const Select = <T extends AnyObject = string>({
|
|
|
339
348
|
labelProperty={labelProperty}
|
|
340
349
|
itemRef={setItemRef}
|
|
341
350
|
itemProps={makeItemProps}
|
|
351
|
+
empty={emptyDisplay}
|
|
342
352
|
>
|
|
343
353
|
{(item, label) => renderer?.(item) ?? label}
|
|
344
354
|
</Options>
|
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,
|
|
@@ -75,12 +76,14 @@ interface State {
|
|
|
75
76
|
export const useSelect = ({
|
|
76
77
|
options = EMPTY_ARRAY,
|
|
77
78
|
value,
|
|
79
|
+
defaultOpen = false,
|
|
78
80
|
autoComplete,
|
|
79
81
|
multiple = false,
|
|
80
82
|
allowCreate = false,
|
|
81
83
|
groupProperty = "",
|
|
82
84
|
valueProperty = "id",
|
|
83
85
|
labelProperty = "label",
|
|
86
|
+
sortProperty,
|
|
84
87
|
createOption = makeOption,
|
|
85
88
|
matcher,
|
|
86
89
|
onQuery,
|
|
@@ -90,10 +93,12 @@ export const useSelect = ({
|
|
|
90
93
|
value?: AnyObject;
|
|
91
94
|
options?: AnyObject[];
|
|
92
95
|
multiple?: boolean;
|
|
96
|
+
defaultOpen?: boolean;
|
|
93
97
|
allowCreate?: boolean;
|
|
94
98
|
groupProperty?: AnyObject;
|
|
95
99
|
valueProperty?: AnyObject;
|
|
96
100
|
labelProperty?: AnyObject;
|
|
101
|
+
sortProperty?: AnyObject;
|
|
97
102
|
autoComplete?: boolean;
|
|
98
103
|
matcher?: (item: AnyObject, query: string) => boolean;
|
|
99
104
|
createOption?: (query: string) => AnyObject;
|
|
@@ -223,6 +228,8 @@ export const useSelect = ({
|
|
|
223
228
|
if (autoComplete && action.query !== undefined) {
|
|
224
229
|
optionList = dedupe([action.query, ...optionList]);
|
|
225
230
|
}
|
|
231
|
+
if (sortProperty)
|
|
232
|
+
optionList = optionList.sort(compareValues("asc", sortProperty));
|
|
226
233
|
const grouped = groupBy(optionList, groupProperty, "");
|
|
227
234
|
if (action.open) newState.open = true;
|
|
228
235
|
newState.items = [];
|
|
@@ -230,7 +237,7 @@ export const useSelect = ({
|
|
|
230
237
|
if (allowCreate && action.query)
|
|
231
238
|
newState.items.push({
|
|
232
239
|
___create___: true,
|
|
233
|
-
value:
|
|
240
|
+
value: action.query,
|
|
234
241
|
});
|
|
235
242
|
Object.entries(grouped).forEach(([key, list]) => {
|
|
236
243
|
if (key) {
|
|
@@ -254,7 +261,7 @@ export const useSelect = ({
|
|
|
254
261
|
{
|
|
255
262
|
multiple,
|
|
256
263
|
emptyValue,
|
|
257
|
-
open:
|
|
264
|
+
open: defaultOpen,
|
|
258
265
|
loading: false,
|
|
259
266
|
activeIndex: null,
|
|
260
267
|
selectedIndex: null,
|
|
@@ -283,7 +290,7 @@ export const useSelect = ({
|
|
|
283
290
|
dispatch({ type: "options", options: optionsRef.current });
|
|
284
291
|
dispatch({
|
|
285
292
|
type: "setValue",
|
|
286
|
-
value: hasValue ?? newOption ??
|
|
293
|
+
value: hasValue ?? newOption ?? deferred,
|
|
287
294
|
});
|
|
288
295
|
}
|
|
289
296
|
if (multiple) {
|
|
@@ -295,12 +302,12 @@ export const useSelect = ({
|
|
|
295
302
|
? createOption(item)
|
|
296
303
|
: undefined;
|
|
297
304
|
const hasValue = options.find(
|
|
298
|
-
(opt: AnyObject) => (opt[valueProperty] ?? opt) ===
|
|
305
|
+
(opt: AnyObject) => (opt[valueProperty] ?? opt) === item,
|
|
299
306
|
);
|
|
300
307
|
if (!hasValue && newOption) {
|
|
301
308
|
optionsRef.current.push(newOption);
|
|
302
309
|
}
|
|
303
|
-
return hasValue ?? newOption ??
|
|
310
|
+
return hasValue ?? newOption ?? item;
|
|
304
311
|
},
|
|
305
312
|
);
|
|
306
313
|
optionsRef.current = [...optionsRef.current, ...(options ?? [])];
|
|
@@ -334,6 +341,7 @@ export const useSelect = ({
|
|
|
334
341
|
});
|
|
335
342
|
},
|
|
336
343
|
[onQuery],
|
|
344
|
+
300,
|
|
337
345
|
);
|
|
338
346
|
|
|
339
347
|
// handlers
|
package/src/types/index.ts
CHANGED
|
@@ -21,17 +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 {
|
|
24
|
+
import {
|
|
25
|
+
type RefProp,
|
|
26
|
+
type TestProps,
|
|
27
|
+
} from "@react-fabric/core/dist/types/types";
|
|
28
|
+
import { type ReactElement } from "react";
|
|
25
29
|
|
|
26
|
-
export interface InputProps<V = string> {
|
|
30
|
+
export interface InputProps<V = string> extends TestProps {
|
|
27
31
|
/**
|
|
28
32
|
* value
|
|
29
33
|
*/
|
|
30
|
-
value?: V;
|
|
34
|
+
value?: V | null;
|
|
31
35
|
/**
|
|
32
36
|
* change handler
|
|
33
37
|
*/
|
|
34
|
-
onChange?: (value
|
|
38
|
+
onChange?: (value: V | null) => void;
|
|
35
39
|
/**
|
|
36
40
|
* on focus event handler
|
|
37
41
|
*/
|
|
@@ -69,11 +73,15 @@ export interface InputProps<V = string> {
|
|
|
69
73
|
*
|
|
70
74
|
*/
|
|
71
75
|
decorateEnd?: JSX.Element | string | number | boolean;
|
|
76
|
+
decorateEndShowWhenEmpty?: boolean;
|
|
77
|
+
decorateEndHideWhenEmpty?: boolean;
|
|
72
78
|
/**
|
|
73
79
|
* info label at bottom
|
|
74
80
|
*/
|
|
75
81
|
info?: string;
|
|
76
82
|
|
|
83
|
+
monospace?: boolean;
|
|
84
|
+
|
|
77
85
|
/**
|
|
78
86
|
* required
|
|
79
87
|
*/
|
|
@@ -117,9 +125,15 @@ export interface InputProps<V = string> {
|
|
|
117
125
|
labelWidth?: string;
|
|
118
126
|
}
|
|
119
127
|
|
|
120
|
-
export interface BaseSelectProps<T>
|
|
121
|
-
|
|
122
|
-
|
|
128
|
+
export interface BaseSelectProps<T extends AnyObject = string> {
|
|
129
|
+
/**
|
|
130
|
+
* open dropdown by default
|
|
131
|
+
*/
|
|
132
|
+
defaultOpen?: boolean;
|
|
133
|
+
/**
|
|
134
|
+
* hide dropdown handle
|
|
135
|
+
*/
|
|
136
|
+
hideHandle?: boolean;
|
|
123
137
|
/**
|
|
124
138
|
* list options
|
|
125
139
|
*/
|
|
@@ -136,6 +150,10 @@ export interface BaseSelectProps<T>
|
|
|
136
150
|
* value property name
|
|
137
151
|
*/
|
|
138
152
|
valueProperty?: keyof T;
|
|
153
|
+
/**
|
|
154
|
+
* value property name
|
|
155
|
+
*/
|
|
156
|
+
sortProperty?: keyof T | true;
|
|
139
157
|
/**
|
|
140
158
|
* searchable list
|
|
141
159
|
*/
|
|
@@ -144,6 +162,10 @@ export interface BaseSelectProps<T>
|
|
|
144
162
|
* allow create item
|
|
145
163
|
*/
|
|
146
164
|
allowCreate?: boolean;
|
|
165
|
+
/**
|
|
166
|
+
* empty list display
|
|
167
|
+
*/
|
|
168
|
+
emptyDisplay?: ReactElement;
|
|
147
169
|
/**
|
|
148
170
|
* create new item object
|
|
149
171
|
*/
|
|
@@ -162,7 +184,11 @@ export interface BaseSelectProps<T>
|
|
|
162
184
|
matcher?: (option: T, query: string) => boolean;
|
|
163
185
|
}
|
|
164
186
|
|
|
165
|
-
|
|
187
|
+
type BaseSelectType<T> = BaseSelectProps<T> &
|
|
188
|
+
RefProp<HTMLInputElement> &
|
|
189
|
+
Omit<InputProps, "value" | "onChange">;
|
|
190
|
+
|
|
191
|
+
export type SelectProps<T> = BaseSelectType<T> &
|
|
166
192
|
(
|
|
167
193
|
| {
|
|
168
194
|
/**
|
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,25 @@
|
|
|
23
23
|
|
|
24
24
|
/* istanbul ignore file */
|
|
25
25
|
|
|
26
|
-
import {
|
|
26
|
+
import { useFloatingTree } from "@floating-ui/react";
|
|
27
|
+
import { useDebounce } from "@react-fabric/core";
|
|
27
28
|
import { type KeyboardEvent, type MouseEvent } from "react";
|
|
28
29
|
|
|
29
30
|
/** ***************** common handler for enter press *******************/
|
|
30
|
-
export const
|
|
31
|
+
export const useHandleEnter = (
|
|
31
32
|
callback?: (e: KeyboardEvent) => void,
|
|
32
33
|
{
|
|
33
34
|
stopPropagation = false,
|
|
34
35
|
preventDefault = false,
|
|
35
36
|
}: { stopPropagation?: boolean; preventDefault?: boolean } = {},
|
|
36
37
|
) => {
|
|
37
|
-
const cbHandle =
|
|
38
|
+
const cbHandle = useDebounce(callback ?? (() => undefined), [callback], 100);
|
|
39
|
+
const tree = useFloatingTree();
|
|
38
40
|
return (e: KeyboardEvent) => {
|
|
41
|
+
// prevent propagation of event if input within dropdown (work around for floating-ui typeahead)
|
|
42
|
+
if (tree && e.key !== "Escape") {
|
|
43
|
+
e?.stopPropagation();
|
|
44
|
+
}
|
|
39
45
|
if (
|
|
40
46
|
!e.shiftKey &&
|
|
41
47
|
!e.altKey &&
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@layer var{.arrayInput{& [data-ref=fieldWrapper]{flex:1 1 0%}& .addButton :global(.buttonInner){color:theme(colors.tint.600);outline:1px dashed theme(colors.tint.600);&[data-invalid=true]{background-color:theme(colors.danger.100);color:theme(colors.danger.600);outline:1px dashed theme(colors.danger.600)}}}}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@layer comp{.colorPicker{--color-shadow:theme(borderColor.base);& input{@apply bg-base text-base !border !border-solid !rounded-sm !border-base focus:!border-primary !shadow-none;line-height:2;padding:0 4px!important}& label{@apply !text-base}& :global(.flexbox-fix){@apply !border-t-muted justify-center;&>div>div{@apply text-center}& span>div{box-shadow:inset 0 0 1px 1px theme(borderColor.base)!important}}}}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@layer comp{.fileInput{&::-webkit-file-upload-button{@apply bg-primary-500 opacity-80 hover:opacity-90 text-white rounded border-none py-0 cursor-pointer}&[aria-invalid=true]::-webkit-file-upload-button{@apply bg-danger-500}&:disabled::-webkit-file-upload-button{@apply bg-muted pointer-events-none}&[readOnly]{@apply pointer-events-none}}}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@layer comp{}@layer var{.inputWrapper{&:after{inset-inline:50%;transition:all .3s;transition-delay:.1s}& .decorate:has([data-ref=button]){align-self:stretch;display:flex}&:has(input[disabled],textarea[disabled]){background-color:theme(colors.tint.50);cursor:not-allowed;& input,& textarea{cursor:not-allowed}}&:focus-within,&:has([data-open=true]){background-color:var(--fabric-input-focus);outline-color:var(--primary);z-index:1;&:not(:has([aria-readonly=true]:not([hidden]))):after{inset-inline:0}}&:global(.fixed){box-shadow:0 0 2rem .5rem var(--fabric-shadow)}& input,& textarea{max-width:100%}&>input[type=number]+.inputClear{margin-inline:-3rem 2rem}& input::-ms-clear,& select::-ms-clear,& textarea::-ms-clear{display:none}& input[type=search]::-webkit-search-cancel-button{display:none}& input[type=password]:not(:placeholder-shown){font-family:caption;letter-spacing:2px}& input:-webkit-autofill{box-shadow:0 0 0 999em var(--fabric-input) inset;-webkit-text-fill-color:theme(colors.accent.700)}& input:-webkit-autofill:focus{box-shadow:0 0 0 999em var(--fabric-input-focus) inset;-webkit-text-fill-color:theme(colors.accent.700)}&.invalidWrapper{outline-color:theme(colors.danger.500);&:after{background-color:theme(colors.danger.500)}}}.fieldGroup{>*{line-height:1}>:not(:first-child){&,&>[data-ref=button]>[role=button],&>[role=button],&>div{border-end-start-radius:0;border-start-start-radius:0}}>:not(:last-child){&,&>[data-ref=button]>[role=button],&>[role=button],&>div{border-end-end-radius:0;border-start-end-radius:0}}}}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@layer var{.arrayInput{& [data-ref=fieldWrapper]{flex:1 1 0%}& .addButton :global(.buttonInner){color:theme(colors.tint.600);outline:1px dashed theme(colors.tint.600);&[data-invalid=true]{background-color:theme(colors.danger.100);color:theme(colors.danger.600);outline:1px dashed theme(colors.danger.600)}}}}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@layer comp{.colorPicker{--color-shadow:theme(borderColor.base);& input{@apply bg-base text-base !border !border-solid !rounded-sm !border-base focus:!border-primary !shadow-none;line-height:2;padding:0 4px!important}& label{@apply !text-base}& :global(.flexbox-fix){@apply !border-t-muted justify-center;&>div>div{@apply text-center}& span>div{box-shadow:inset 0 0 1px 1px theme(borderColor.base)!important}}}}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@layer comp{.fileInput{&::-webkit-file-upload-button{@apply bg-primary-500 opacity-80 hover:opacity-90 text-white rounded border-none py-0 cursor-pointer}&[aria-invalid=true]::-webkit-file-upload-button{@apply bg-danger-500}&:disabled::-webkit-file-upload-button{@apply bg-muted pointer-events-none}&[readOnly]{@apply pointer-events-none}}}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@layer comp{}@layer var{.inputWrapper{&:after{inset-inline:50%;transition:all .3s;transition-delay:.1s}& .decorate:has([data-ref=button]){align-self:stretch;display:flex}&:has(input[disabled],textarea[disabled]){background-color:theme(colors.tint.50);cursor:not-allowed;& input,& textarea{cursor:not-allowed}}&:focus-within,&:has([data-open=true]){background-color:var(--fabric-input-focus);outline-color:var(--primary);z-index:1;&:not(:has([aria-readonly=true]:not([hidden]))):after{inset-inline:0}}&:global(.fixed){box-shadow:0 0 2rem .5rem var(--fabric-shadow)}& input,& textarea{max-width:100%}&>input[type=number]+.inputClear{margin-inline:-3rem 2rem}& input::-ms-clear,& select::-ms-clear,& textarea::-ms-clear{display:none}& input[type=search]::-webkit-search-cancel-button{display:none}& input[type=password]:not(:placeholder-shown){font-family:caption;letter-spacing:2px}& input:-webkit-autofill{box-shadow:0 0 0 999em var(--fabric-input) inset;-webkit-text-fill-color:theme(colors.accent.700)}& input:-webkit-autofill:focus{box-shadow:0 0 0 999em var(--fabric-input-focus) inset;-webkit-text-fill-color:theme(colors.accent.700)}&.invalidWrapper{outline-color:theme(colors.danger.500);&:after{background-color:theme(colors.danger.500)}}}.fieldGroup{>*{line-height:1}>:not(:first-child){&,&>[data-ref=button]>[role=button],&>[role=button],&>div{border-end-start-radius:0;border-start-start-radius:0}}>:not(:last-child){&,&>[data-ref=button]>[role=button],&>[role=button],&>div{border-end-end-radius:0;border-start-end-radius:0}}}}
|