@react-fabric/form 1.0.0-beta.4 → 1.0.0-beta.40
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 +1 -1
- package/dist/cjs/select/List.js +41 -20
- package/dist/cjs/select/Options.js +2 -2
- package/dist/cjs/select/Select.js +11 -8
- package/dist/cjs/select/useSelect.js +12 -7
- 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 +1 -1
- package/dist/esm/select/List.js +42 -21
- package/dist/esm/select/Options.js +2 -2
- package/dist/esm/select/Select.js +11 -8
- package/dist/esm/select/useSelect.js +13 -8
- 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/List.d.ts +1 -1
- package/dist/types/types/index.d.ts +9 -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 +1 -1
- package/src/select/List.tsx +34 -7
- package/src/select/Options.tsx +1 -1
- package/src/select/Select.tsx +5 -2
- package/src/select/useSelect.tsx +27 -18
- package/src/types/index.ts +14 -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
|
);
|
|
@@ -328,7 +328,7 @@ export const AutoComplete = ({
|
|
|
328
328
|
>
|
|
329
329
|
<Options
|
|
330
330
|
ref={refs.setFloating}
|
|
331
|
-
className="outline shadow-lg max-h-[40vh] z-
|
|
331
|
+
className="outline shadow-lg max-h-[40vh] z-(--z-popover)"
|
|
332
332
|
style={{
|
|
333
333
|
width: refs.reference.current?.getBoundingClientRect().width,
|
|
334
334
|
...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,
|
|
@@ -166,9 +167,12 @@ export const List = <T extends AnyObject = string>({
|
|
|
166
167
|
}
|
|
167
168
|
} else if (evt.key === "Backspace") {
|
|
168
169
|
!state.query && handleRemove();
|
|
169
|
-
} else if (["ArrowUp", "ArrowDown"].includes(evt.key)) {
|
|
170
|
-
if (
|
|
171
|
-
|
|
170
|
+
} else if (!searchable || ["ArrowUp", "ArrowDown"].includes(evt.key)) {
|
|
171
|
+
if (
|
|
172
|
+
["ArrowUp", "ArrowDown"].includes(evt.key) &&
|
|
173
|
+
state.activeIndex === null
|
|
174
|
+
) {
|
|
175
|
+
setActiveIndex(state.selectedIndex ?? 0);
|
|
172
176
|
}
|
|
173
177
|
setTimeout(() => {
|
|
174
178
|
props.onKeyDown?.(evt);
|
|
@@ -225,6 +229,15 @@ export const List = <T extends AnyObject = string>({
|
|
|
225
229
|
}
|
|
226
230
|
}, [state.value, state.query, placeholder, renderer]);
|
|
227
231
|
|
|
232
|
+
useLayoutEffect(() => {
|
|
233
|
+
setTimeout(() => {
|
|
234
|
+
state.selectedIndex &&
|
|
235
|
+
listRef.current[state.selectedIndex]?.scrollIntoView({
|
|
236
|
+
block: "center",
|
|
237
|
+
});
|
|
238
|
+
}, 100);
|
|
239
|
+
}, [state.selectedIndex]);
|
|
240
|
+
|
|
228
241
|
return (
|
|
229
242
|
<Wrapper {...wrapperProps}>
|
|
230
243
|
<InputWrapper
|
|
@@ -240,22 +253,34 @@ export const List = <T extends AnyObject = string>({
|
|
|
240
253
|
wrapperRef={refs.setReference}
|
|
241
254
|
>
|
|
242
255
|
<div
|
|
256
|
+
role="none"
|
|
243
257
|
data-select-display="true"
|
|
244
|
-
className=
|
|
258
|
+
className={classNames(
|
|
259
|
+
"group flex-1 py-1 px-2 truncate text-start flex gap-1 relative min-h-5 justify-start",
|
|
260
|
+
multiple ? "flex-wrap" : "flex-nowrap overflow-hidden",
|
|
261
|
+
)}
|
|
262
|
+
{...referenceProps}
|
|
263
|
+
onMouseUp={(e) =>
|
|
264
|
+
e.currentTarget.querySelector<HTMLElement>("input")?.focus()
|
|
265
|
+
}
|
|
245
266
|
>
|
|
246
267
|
{!hideSelected && displayValue}
|
|
247
268
|
<input
|
|
269
|
+
readOnly={!searchable || readOnly}
|
|
248
270
|
disabled={disabled}
|
|
249
271
|
value={state.query ?? ""}
|
|
250
272
|
aria-invalid={invalid}
|
|
251
273
|
data-testid={name}
|
|
252
274
|
name={name}
|
|
275
|
+
size={1}
|
|
276
|
+
ref={ref}
|
|
277
|
+
autoFocus={autoFocus}
|
|
253
278
|
className={classNames(
|
|
254
279
|
"appearance-none bg-transparent outline-none border-none ring-0 flex-1 min-w-24",
|
|
255
280
|
disabled && "cursor-not-allowed pointer-events-none",
|
|
281
|
+
multiple && "min-w-24",
|
|
256
282
|
)}
|
|
257
283
|
placeholder={isEmpty(state.value) ? placeholder : ""}
|
|
258
|
-
{...referenceProps}
|
|
259
284
|
onChange={(evt) => handleQuery(evt.target.value)}
|
|
260
285
|
/>
|
|
261
286
|
</div>
|
|
@@ -277,10 +302,12 @@ export const List = <T extends AnyObject = string>({
|
|
|
277
302
|
{...floatingProps}
|
|
278
303
|
items={state.items}
|
|
279
304
|
active={state.activeIndex}
|
|
305
|
+
valueProperty={valueProperty}
|
|
306
|
+
labelProperty={labelProperty}
|
|
280
307
|
itemRef={setItemRef}
|
|
281
308
|
itemProps={makeItemProps}
|
|
282
309
|
>
|
|
283
|
-
{(item) => renderer?.(item) ??
|
|
310
|
+
{(item, label) => renderer?.(item) ?? label}
|
|
284
311
|
</Options>
|
|
285
312
|
</FloatingFocusManager>
|
|
286
313
|
</Wrapper>
|
package/src/select/Options.tsx
CHANGED
package/src/select/Select.tsx
CHANGED
|
@@ -172,6 +172,9 @@ export const Select = <T extends AnyObject = string>({
|
|
|
172
172
|
return false;
|
|
173
173
|
} else if (state.query) {
|
|
174
174
|
handleQuery("");
|
|
175
|
+
evt.preventDefault();
|
|
176
|
+
evt.stopPropagation();
|
|
177
|
+
return false;
|
|
175
178
|
} else {
|
|
176
179
|
onEnterPressed?.(evt);
|
|
177
180
|
}
|
|
@@ -273,6 +276,7 @@ export const Select = <T extends AnyObject = string>({
|
|
|
273
276
|
"group flex-1 py-1 px-2 truncate text-start flex gap-1 relative min-h-5 justify-start",
|
|
274
277
|
multiple ? "flex-wrap" : "flex-nowrap overflow-hidden",
|
|
275
278
|
)}
|
|
279
|
+
{...referenceProps}
|
|
276
280
|
onMouseUp={(e) =>
|
|
277
281
|
e.currentTarget.querySelector<HTMLElement>("input")?.focus()
|
|
278
282
|
}
|
|
@@ -294,7 +298,6 @@ export const Select = <T extends AnyObject = string>({
|
|
|
294
298
|
multiple && "min-w-24",
|
|
295
299
|
)}
|
|
296
300
|
placeholder={isEmpty(state.value) ? placeholder : ""}
|
|
297
|
-
{...referenceProps}
|
|
298
301
|
onChange={(evt) => handleQuery(evt.target.value)}
|
|
299
302
|
/>
|
|
300
303
|
</div>
|
|
@@ -327,7 +330,7 @@ export const Select = <T extends AnyObject = string>({
|
|
|
327
330
|
>
|
|
328
331
|
<Options
|
|
329
332
|
ref={refs.setFloating}
|
|
330
|
-
className="outline shadow-lg max-h-[40vh] z-
|
|
333
|
+
className="outline shadow-lg max-h-[40vh] z-(--z-popover)"
|
|
331
334
|
style={{
|
|
332
335
|
width: refs.reference.current?.getBoundingClientRect().width,
|
|
333
336
|
...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 {
|
|
@@ -173,9 +175,9 @@ export const useSelect = ({
|
|
|
173
175
|
if (action.value !== undefined) {
|
|
174
176
|
if (multiple) {
|
|
175
177
|
const values = [];
|
|
176
|
-
if (action.value
|
|
178
|
+
if (isString(action.value))
|
|
177
179
|
values.push(...action.value.split(/,\s?/));
|
|
178
|
-
else
|
|
180
|
+
else values.push(action.value);
|
|
179
181
|
newState.value = [...state.value];
|
|
180
182
|
values.forEach((value: AnyObject) => {
|
|
181
183
|
newState.value = newState.value.includes?.(value)
|
|
@@ -222,6 +224,7 @@ export const useSelect = ({
|
|
|
222
224
|
if (autoComplete && action.query !== undefined) {
|
|
223
225
|
optionList = dedupe([action.query, ...optionList]);
|
|
224
226
|
}
|
|
227
|
+
optionList = optionList.sort(compareValues("asc", labelProperty));
|
|
225
228
|
const grouped = groupBy(optionList, groupProperty, "");
|
|
226
229
|
if (action.open) newState.open = true;
|
|
227
230
|
newState.items = [];
|
|
@@ -229,7 +232,7 @@ export const useSelect = ({
|
|
|
229
232
|
if (allowCreate && action.query)
|
|
230
233
|
newState.items.push({
|
|
231
234
|
___create___: true,
|
|
232
|
-
value:
|
|
235
|
+
value: action.query,
|
|
233
236
|
});
|
|
234
237
|
Object.entries(grouped).forEach(([key, list]) => {
|
|
235
238
|
if (key) {
|
|
@@ -282,24 +285,26 @@ export const useSelect = ({
|
|
|
282
285
|
dispatch({ type: "options", options: optionsRef.current });
|
|
283
286
|
dispatch({
|
|
284
287
|
type: "setValue",
|
|
285
|
-
value: hasValue ?? newOption ??
|
|
288
|
+
value: hasValue ?? newOption ?? deferred,
|
|
286
289
|
});
|
|
287
290
|
}
|
|
288
291
|
if (multiple) {
|
|
289
292
|
optionsRef.current = [];
|
|
290
|
-
const value = (deferred
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
293
|
+
const value = (isArray(deferred) ? deferred : []).map(
|
|
294
|
+
(item: AnyObject) => {
|
|
295
|
+
const newOption =
|
|
296
|
+
(allowCreate || autoComplete) && item
|
|
297
|
+
? createOption(item)
|
|
298
|
+
: undefined;
|
|
299
|
+
const hasValue = options.find(
|
|
300
|
+
(opt: AnyObject) => (opt[valueProperty] ?? opt) === item,
|
|
301
|
+
);
|
|
302
|
+
if (!hasValue && newOption) {
|
|
303
|
+
optionsRef.current.push(newOption);
|
|
304
|
+
}
|
|
305
|
+
return hasValue ?? newOption ?? item;
|
|
306
|
+
},
|
|
307
|
+
);
|
|
303
308
|
optionsRef.current = [...optionsRef.current, ...(options ?? [])];
|
|
304
309
|
dispatch({ type: "options", options: optionsRef.current });
|
|
305
310
|
dispatch({
|
|
@@ -367,7 +372,11 @@ export const useSelect = ({
|
|
|
367
372
|
value: value?.___create___ ? value.value : value,
|
|
368
373
|
});
|
|
369
374
|
if (value?.___create___) {
|
|
370
|
-
|
|
375
|
+
let val = [value.value];
|
|
376
|
+
if (multiple) {
|
|
377
|
+
val = value.value.split(/,\s?/);
|
|
378
|
+
}
|
|
379
|
+
optionsRef.current = [...optionsRef.current, ...val.map(createOption)];
|
|
371
380
|
}
|
|
372
381
|
if (autoComplete) {
|
|
373
382
|
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,7 @@ export interface InputProps<V = string> {
|
|
|
117
122
|
labelWidth?: string;
|
|
118
123
|
}
|
|
119
124
|
|
|
120
|
-
export interface BaseSelectProps<T>
|
|
121
|
-
extends RefProp<HTMLInputElement>,
|
|
122
|
-
Omit<InputProps, "value" | "onChange"> {
|
|
125
|
+
export interface BaseSelectProps<T extends AnyObject = string> {
|
|
123
126
|
/**
|
|
124
127
|
* list options
|
|
125
128
|
*/
|
|
@@ -162,7 +165,11 @@ export interface BaseSelectProps<T>
|
|
|
162
165
|
matcher?: (option: T, query: string) => boolean;
|
|
163
166
|
}
|
|
164
167
|
|
|
165
|
-
|
|
168
|
+
type BaseSelectType<T> = BaseSelectProps<T> &
|
|
169
|
+
RefProp<HTMLInputElement> &
|
|
170
|
+
Omit<InputProps, "value" | "onChange">;
|
|
171
|
+
|
|
172
|
+
export type SelectProps<T> = BaseSelectType<T> &
|
|
166
173
|
(
|
|
167
174
|
| {
|
|
168
175
|
/**
|
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
|
+
STRING = "string",
|
|
33
|
+
NUMBER = "number",
|
|
34
|
+
RANGE = "range",
|
|
35
|
+
FILE = "file",
|
|
36
|
+
AVATAR = "avatar",
|
|
37
|
+
SCHEMA = "schema",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const SCHEMA_CUSTOM_LIST = "--CUSTOM--";
|
|
41
|
+
|
|
42
|
+
interface SingleValue {
|
|
43
|
+
multiple?: false;
|
|
44
|
+
minItems?: never;
|
|
45
|
+
maxItems?: never;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface MultipleValue {
|
|
49
|
+
multiple: true;
|
|
50
|
+
minItems?: number;
|
|
51
|
+
maxItems?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type ValueType = SingleValue | MultipleValue;
|
|
55
|
+
|
|
56
|
+
interface BaseSchema {
|
|
25
57
|
id: string;
|
|
26
58
|
label: string;
|
|
27
59
|
datatype: string;
|
|
28
60
|
required?: boolean;
|
|
29
|
-
multiple?: boolean;
|
|
30
|
-
defaultValue?: AnyObject;
|
|
31
61
|
}
|
|
32
62
|
|
|
33
|
-
interface
|
|
34
|
-
datatype:
|
|
63
|
+
export interface DefaultSchema extends BaseSchema {
|
|
64
|
+
datatype: DATA_TYPES.TEXT | DATA_TYPES.COLOR;
|
|
35
65
|
}
|
|
36
66
|
|
|
37
|
-
interface BooleanSchema extends
|
|
38
|
-
datatype:
|
|
39
|
-
defaultValue?: boolean;
|
|
67
|
+
export interface BooleanSchema extends BaseSchema {
|
|
68
|
+
datatype: DATA_TYPES.BOOL;
|
|
40
69
|
}
|
|
41
70
|
|
|
42
|
-
interface StringSchema extends
|
|
43
|
-
datatype:
|
|
44
|
-
options?: string[];
|
|
71
|
+
export interface StringSchema extends BaseSchema {
|
|
72
|
+
datatype: DATA_TYPES.STRING;
|
|
45
73
|
regex?: string;
|
|
46
|
-
|
|
74
|
+
options?: string[];
|
|
75
|
+
optionList?: string;
|
|
76
|
+
allowCustom?: boolean;
|
|
47
77
|
}
|
|
48
78
|
|
|
49
|
-
interface RangeSchema extends
|
|
50
|
-
datatype:
|
|
79
|
+
export interface RangeSchema extends BaseSchema {
|
|
80
|
+
datatype: DATA_TYPES.RANGE;
|
|
51
81
|
min?: number;
|
|
52
82
|
max?: number;
|
|
53
83
|
step?: number;
|
|
54
|
-
defaultValue?: [number, number];
|
|
55
84
|
}
|
|
56
85
|
|
|
57
|
-
interface
|
|
58
|
-
datatype:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
defaultValue?: number;
|
|
86
|
+
export interface DateSchema extends BaseSchema {
|
|
87
|
+
datatype: DATA_TYPES.DATE;
|
|
88
|
+
type?: "date" | "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
|
+
}
|
|
@@ -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}}}}
|