@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.
Files changed (155) hide show
  1. package/{src/input/ArrayInput.module.css → css/_arrayInput.css} +8 -8
  2. package/css/_colorPicker.css +65 -0
  3. package/{src/internal/Field.module.css → css/_field.css} +26 -15
  4. package/{src/input/File.module.css → css/_file.css} +20 -6
  5. package/css/styles.css +27 -0
  6. package/dist/cjs/builder/FileField.js +37 -25
  7. package/dist/cjs/builder/FormField.js +44 -34
  8. package/dist/cjs/builder/SchemaEditor.js +72 -0
  9. package/dist/cjs/builder/components/AddSchemaFields.js +62 -0
  10. package/dist/cjs/builder/components/SchemaArrayForm.js +143 -0
  11. package/dist/cjs/builder/components/SchemaField.js +413 -0
  12. package/dist/cjs/form/Controller.js +1 -1
  13. package/dist/cjs/form/Form.js +19 -10
  14. package/dist/cjs/hooks/useFormBuilder.js +27 -15
  15. package/dist/cjs/index.js +9 -2
  16. package/dist/cjs/input/ArrayInput.js +36 -25
  17. package/dist/cjs/input/AvatarInput.js +1 -1
  18. package/dist/cjs/input/Checkbox.js +5 -4
  19. package/dist/cjs/input/ColorInput.js +70 -28
  20. package/dist/cjs/input/ColorPicker.js +1 -35
  21. package/dist/cjs/input/DateInput.js +183 -0
  22. package/dist/cjs/input/Field.js +4 -4
  23. package/dist/cjs/input/File.js +2 -3
  24. package/dist/cjs/input/Hidden.js +3 -1
  25. package/dist/cjs/input/Input.js +1 -1
  26. package/dist/cjs/input/Number.js +1 -1
  27. package/dist/cjs/input/Password.js +1 -1
  28. package/dist/cjs/input/Radio.js +1 -1
  29. package/dist/cjs/input/RadioGroup.js +6 -3
  30. package/dist/cjs/input/{Range.js → RangeSlider.js} +3 -3
  31. package/dist/cjs/input/Search.js +1 -1
  32. package/dist/cjs/input/Slider.js +1 -1
  33. package/dist/cjs/input/Switch.js +27 -5
  34. package/dist/cjs/input/Textarea.js +7 -3
  35. package/dist/cjs/internal/ErrorIcon.js +1 -1
  36. package/dist/cjs/internal/FieldWrapper.js +4 -3
  37. package/dist/cjs/internal/InputWrapper.js +14 -16
  38. package/dist/cjs/select/AutoComplete.js +9 -6
  39. package/dist/cjs/select/List.js +41 -20
  40. package/dist/cjs/select/Options.js +2 -2
  41. package/dist/cjs/select/Select.js +15 -9
  42. package/dist/cjs/select/useSelect.js +15 -8
  43. package/{src/input/ColorPicker.module.css → dist/cjs/types/schema.js} +18 -27
  44. package/dist/cjs/utils/getColor.js +6 -0
  45. package/dist/esm/builder/FileField.js +39 -27
  46. package/dist/esm/builder/FormField.js +44 -34
  47. package/dist/esm/builder/SchemaEditor.js +72 -0
  48. package/dist/esm/builder/components/AddSchemaFields.js +62 -0
  49. package/dist/esm/builder/components/SchemaArrayForm.js +143 -0
  50. package/dist/esm/builder/components/SchemaField.js +413 -0
  51. package/dist/esm/form/Controller.js +1 -1
  52. package/dist/esm/form/Form.js +19 -10
  53. package/dist/esm/hooks/useFormBuilder.js +25 -13
  54. package/dist/esm/index.js +4 -1
  55. package/dist/esm/input/ArrayInput.js +38 -27
  56. package/dist/esm/input/AvatarInput.js +1 -1
  57. package/dist/esm/input/Checkbox.js +5 -4
  58. package/dist/esm/input/ColorInput.js +72 -30
  59. package/dist/esm/input/ColorPicker.js +1 -35
  60. package/dist/esm/input/DateInput.js +183 -0
  61. package/dist/esm/input/Field.js +5 -5
  62. package/dist/esm/input/File.js +2 -3
  63. package/dist/esm/input/Hidden.js +3 -1
  64. package/dist/esm/input/Input.js +1 -1
  65. package/dist/esm/input/Number.js +1 -1
  66. package/dist/esm/input/Password.js +1 -1
  67. package/dist/esm/input/Radio.js +1 -1
  68. package/dist/esm/input/RadioGroup.js +7 -4
  69. package/dist/esm/input/{Range.js → RangeSlider.js} +3 -3
  70. package/dist/esm/input/Search.js +1 -1
  71. package/dist/esm/input/Slider.js +1 -1
  72. package/dist/esm/input/Switch.js +29 -7
  73. package/dist/esm/input/Textarea.js +7 -3
  74. package/dist/esm/internal/ErrorIcon.js +1 -1
  75. package/dist/esm/internal/FieldWrapper.js +4 -3
  76. package/dist/esm/internal/InputWrapper.js +14 -16
  77. package/dist/esm/select/AutoComplete.js +9 -6
  78. package/dist/esm/select/List.js +42 -21
  79. package/dist/esm/select/Options.js +2 -2
  80. package/dist/esm/select/Select.js +15 -9
  81. package/dist/esm/select/useSelect.js +16 -9
  82. package/dist/esm/types/schema.js +41 -0
  83. package/dist/esm/utils/getColor.js +6 -1
  84. package/dist/types/builder/FileField.d.ts +4 -2
  85. package/dist/types/builder/FormField.d.ts +3 -1
  86. package/dist/types/builder/SchemaEditor.d.ts +2 -0
  87. package/dist/types/builder/components/AddSchemaFields.d.ts +2 -0
  88. package/dist/types/builder/components/SchemaArrayForm.d.ts +2 -0
  89. package/dist/types/builder/components/SchemaField.d.ts +10 -0
  90. package/dist/types/form/Controller.d.ts +1 -1
  91. package/dist/types/hooks/useFormBuilder.d.ts +3 -1
  92. package/dist/types/index.d.ts +4 -2
  93. package/dist/types/input/ArrayInput.d.ts +20 -3
  94. package/dist/types/input/Checkbox.d.ts +4 -3
  95. package/dist/types/input/ColorInput.d.ts +1 -1
  96. package/dist/types/input/DateInput.d.ts +19 -0
  97. package/dist/types/input/RadioGroup.d.ts +18 -2
  98. package/dist/types/input/{Range.d.ts → RangeSlider.d.ts} +1 -1
  99. package/dist/types/input/Switch.d.ts +7 -3
  100. package/dist/types/input/Textarea.d.ts +5 -1
  101. package/dist/types/internal/InputWrapper.d.ts +1 -1
  102. package/dist/types/select/AutoComplete.d.ts +1 -1
  103. package/dist/types/select/List.d.ts +1 -1
  104. package/dist/types/select/Select.d.ts +1 -1
  105. package/dist/types/select/useSelect.d.ts +2 -1
  106. package/dist/types/types/index.d.ts +17 -5
  107. package/dist/types/types/schema.d.ts +79 -33
  108. package/package.json +10 -7
  109. package/src/builder/FileField.tsx +53 -27
  110. package/src/builder/FormField.tsx +45 -32
  111. package/src/builder/SchemaEditor.tsx +72 -0
  112. package/src/builder/components/AddSchemaFields.tsx +74 -0
  113. package/src/builder/components/SchemaArrayForm.tsx +158 -0
  114. package/src/builder/components/SchemaField.tsx +435 -0
  115. package/src/form/Controller.tsx +2 -2
  116. package/src/form/Form.tsx +15 -8
  117. package/src/hooks/useFormBuilder.tsx +29 -15
  118. package/src/index.ts +10 -2
  119. package/src/input/ArrayInput.tsx +80 -42
  120. package/src/input/AvatarInput.tsx +1 -1
  121. package/src/input/Checkbox.tsx +5 -4
  122. package/src/input/ColorInput.tsx +102 -34
  123. package/src/input/ColorPicker.tsx +1 -35
  124. package/src/input/DateInput.tsx +248 -0
  125. package/src/input/Field.tsx +9 -11
  126. package/src/input/File.tsx +3 -6
  127. package/src/input/Hidden.tsx +4 -1
  128. package/src/input/Input.tsx +2 -2
  129. package/src/input/Number.tsx +2 -2
  130. package/src/input/Password.tsx +2 -2
  131. package/src/input/Radio.tsx +1 -1
  132. package/src/input/RadioGroup.tsx +27 -4
  133. package/src/input/{Range.tsx → RangeSlider.tsx} +2 -2
  134. package/src/input/Search.tsx +2 -2
  135. package/src/input/Slider.tsx +1 -1
  136. package/src/input/Switch.tsx +42 -6
  137. package/src/input/Textarea.tsx +11 -3
  138. package/src/internal/ErrorIcon.tsx +1 -1
  139. package/src/internal/FieldWrapper.tsx +3 -3
  140. package/src/internal/InputWrapper.tsx +21 -20
  141. package/src/select/AutoComplete.tsx +4 -1
  142. package/src/select/List.tsx +37 -8
  143. package/src/select/Options.tsx +1 -1
  144. package/src/select/Select.tsx +9 -3
  145. package/src/select/useSelect.tsx +30 -19
  146. package/src/types/index.ts +22 -7
  147. package/src/types/schema.ts +96 -41
  148. package/dist/cjs/input/ArrayInput.module.css +0 -1
  149. package/dist/cjs/input/ColorPicker.module.css +0 -1
  150. package/dist/cjs/input/File.module.css +0 -1
  151. package/dist/cjs/internal/Field.module.css +0 -1
  152. package/dist/esm/input/ArrayInput.module.css +0 -1
  153. package/dist/esm/input/ColorPicker.module.css +0 -1
  154. package/dist/esm/input/File.module.css +0 -1
  155. 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
- classes.fieldWrapper,
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
- classes.inputWrapper,
106
- invalid && classes.invalidWrapper,
106
+ "fabric-inputWrapper",
107
+ invalid && "fabric-invalidWrapper",
107
108
  !noOutline &&
108
109
  !noBorder &&
109
- "bg-[var(--fabric-input)] outline overflow-hidden",
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) => e.key === "Escape" && setExpanded(false)}
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
- classes.decorate,
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
- classes.inputClear,
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
- classes.decorate,
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
- {textarea && (
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
- {!isExpanded && el}
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-[var(--z-popover)]"
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,
@@ -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> & { height?: string | number }) => {
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 (state.activeIndex === null) {
171
- setActiveIndex(state.selectedIndex);
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="group flex-1 py-1 px-2 truncate text-start flex gap-1 flex-wrap"
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) ?? item[labelProperty] ?? item}
312
+ {(item, label) => renderer?.(item) ?? label}
284
313
  </Options>
285
314
  </FloatingFocusManager>
286
315
  </Wrapper>
@@ -93,7 +93,7 @@ export const Options = ({
93
93
  >
94
94
  <Trans
95
95
  i18nKey="form:select.createOption"
96
- values={{ query: item.value }}
96
+ values={{ query: item.value ?? item }}
97
97
  components={[<strong key="t0">query</strong>]}
98
98
  />
99
99
  </div>
@@ -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-[var(--z-popover)]"
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,
@@ -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 && autoComplete)
180
+ if (isString(action.value))
177
181
  values.push(...action.value.split(/,\s?/));
178
- else if (action.value) values.push(action.value);
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: createOption(action.query),
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: false,
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 ?? []).map((item: AnyObject) => {
291
- const newOption =
292
- (allowCreate || autoComplete) && item
293
- ? createOption(item)
294
- : undefined;
295
- const hasValue = options.find(
296
- (opt: AnyObject) => (opt[valueProperty] ?? opt) === deferred,
297
- );
298
- if (!hasValue && newOption) {
299
- optionsRef.current.push(newOption);
300
- }
301
- return hasValue ?? newOption ?? "";
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
- optionsRef.current = [...optionsRef.current, createOption(value.value)];
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([
@@ -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 { type RefProp } from "@react-fabric/core/dist/types/types";
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?: V) => void;
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
- extends RefProp<HTMLInputElement>,
122
- Omit<InputProps, "value" | "onChange"> {
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
- export type SelectProps<T> = BaseSelectProps<T> &
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
  /**