@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.
Files changed (174) 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} +64 -29
  4. package/{src/input/File.module.css → css/_file.css} +21 -6
  5. package/css/styles.css +27 -0
  6. package/dist/cjs/builder/FileField.js +37 -25
  7. package/dist/cjs/builder/FormField.js +50 -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 +414 -0
  12. package/dist/cjs/form/Controller.js +1 -1
  13. package/dist/cjs/form/Form.js +11 -6
  14. package/dist/cjs/hooks/useFormBuilder.js +27 -15
  15. package/dist/cjs/index.js +11 -2
  16. package/dist/cjs/input/ArrayInput.js +61 -33
  17. package/dist/cjs/input/AvatarInput.js +9 -8
  18. package/dist/cjs/input/Checkbox.js +6 -5
  19. package/dist/cjs/input/ColorInput.js +95 -56
  20. package/dist/cjs/input/ColorPicker.js +1 -35
  21. package/dist/cjs/input/DateInput.js +186 -0
  22. package/dist/cjs/input/Field.js +5 -4
  23. package/dist/cjs/input/File.js +4 -7
  24. package/dist/cjs/input/Hidden.js +3 -1
  25. package/dist/cjs/input/Input.js +11 -7
  26. package/dist/cjs/input/Number.js +9 -7
  27. package/dist/cjs/input/Password.js +10 -8
  28. package/dist/cjs/input/Radio.js +2 -2
  29. package/dist/cjs/input/RadioGroup.js +6 -3
  30. package/dist/cjs/input/{Range.js → RangeSlider.js} +5 -5
  31. package/dist/cjs/input/Search.js +16 -16
  32. package/dist/cjs/input/Slider.js +3 -3
  33. package/dist/cjs/input/Switch.js +30 -7
  34. package/dist/cjs/input/Textarea.js +13 -4
  35. package/dist/cjs/internal/ErrorIcon.js +1 -1
  36. package/dist/cjs/internal/FieldWrapper.js +11 -4
  37. package/dist/cjs/internal/InputWrapper.js +19 -16
  38. package/dist/cjs/select/AutoComplete.js +12 -8
  39. package/dist/cjs/select/DualList.js +4 -1
  40. package/dist/cjs/select/List.js +45 -20
  41. package/dist/cjs/select/Options.js +2 -2
  42. package/dist/cjs/select/Select.js +14 -4
  43. package/dist/cjs/select/useSelect.js +10 -6
  44. package/dist/cjs/types/schema.js +43 -0
  45. package/dist/cjs/utils/eventHandlers.js +11 -5
  46. package/dist/cjs/utils/getColor.js +6 -0
  47. package/dist/esm/builder/FileField.js +39 -27
  48. package/dist/esm/builder/FormField.js +50 -34
  49. package/dist/esm/builder/SchemaEditor.js +72 -0
  50. package/dist/esm/builder/components/AddSchemaFields.js +62 -0
  51. package/dist/esm/builder/components/SchemaArrayForm.js +143 -0
  52. package/dist/esm/builder/components/SchemaField.js +414 -0
  53. package/dist/esm/form/Controller.js +1 -1
  54. package/dist/esm/form/Form.js +11 -6
  55. package/dist/esm/hooks/useFormBuilder.js +25 -13
  56. package/dist/esm/index.js +5 -1
  57. package/dist/esm/input/ArrayInput.js +61 -33
  58. package/dist/esm/input/AvatarInput.js +9 -8
  59. package/dist/esm/input/Checkbox.js +6 -5
  60. package/dist/esm/input/ColorInput.js +97 -58
  61. package/dist/esm/input/ColorPicker.js +1 -35
  62. package/dist/esm/input/DateInput.js +186 -0
  63. package/dist/esm/input/Field.js +6 -5
  64. package/dist/esm/input/File.js +6 -9
  65. package/dist/esm/input/Hidden.js +3 -1
  66. package/dist/esm/input/Input.js +13 -9
  67. package/dist/esm/input/Number.js +11 -9
  68. package/dist/esm/input/Password.js +12 -10
  69. package/dist/esm/input/Radio.js +2 -2
  70. package/dist/esm/input/RadioGroup.js +7 -4
  71. package/dist/esm/input/{Range.js → RangeSlider.js} +5 -5
  72. package/dist/esm/input/Search.js +18 -18
  73. package/dist/esm/input/Slider.js +3 -3
  74. package/dist/esm/input/Switch.js +32 -9
  75. package/dist/esm/input/Textarea.js +14 -5
  76. package/dist/esm/internal/ErrorIcon.js +1 -1
  77. package/dist/esm/internal/FieldWrapper.js +11 -4
  78. package/dist/esm/internal/InputWrapper.js +19 -16
  79. package/dist/esm/select/AutoComplete.js +12 -8
  80. package/dist/esm/select/DualList.js +4 -1
  81. package/dist/esm/select/List.js +46 -21
  82. package/dist/esm/select/Options.js +2 -2
  83. package/dist/esm/select/Select.js +14 -4
  84. package/dist/esm/select/useSelect.js +11 -7
  85. package/{src/input/ColorPicker.module.css → dist/esm/types/schema.js} +18 -27
  86. package/dist/esm/utils/eventHandlers.js +11 -5
  87. package/dist/esm/utils/getColor.js +6 -1
  88. package/dist/types/builder/FileField.d.ts +4 -2
  89. package/dist/types/builder/FormField.d.ts +3 -1
  90. package/dist/types/builder/SchemaEditor.d.ts +2 -0
  91. package/dist/types/builder/components/AddSchemaFields.d.ts +2 -0
  92. package/dist/types/builder/components/SchemaArrayForm.d.ts +2 -0
  93. package/dist/types/builder/components/SchemaField.d.ts +10 -0
  94. package/dist/types/form/Controller.d.ts +2 -2
  95. package/dist/types/form/Form.d.ts +5 -1
  96. package/dist/types/hooks/useFormBuilder.d.ts +3 -1
  97. package/dist/types/index.d.ts +5 -2
  98. package/dist/types/input/ArrayInput.d.ts +39 -6
  99. package/dist/types/input/AvatarInput.d.ts +6 -3
  100. package/dist/types/input/Checkbox.d.ts +4 -4
  101. package/dist/types/input/ColorInput.d.ts +2 -2
  102. package/dist/types/input/ColorPicker.d.ts +1 -1
  103. package/dist/types/input/DateInput.d.ts +19 -0
  104. package/dist/types/input/Field.d.ts +0 -1
  105. package/dist/types/input/Input.d.ts +1 -1
  106. package/dist/types/input/Number.d.ts +1 -1
  107. package/dist/types/input/Password.d.ts +1 -1
  108. package/dist/types/input/Radio.d.ts +0 -1
  109. package/dist/types/input/RadioGroup.d.ts +18 -3
  110. package/dist/types/input/{Range.d.ts → RangeSlider.d.ts} +1 -1
  111. package/dist/types/input/Search.d.ts +5 -1
  112. package/dist/types/input/Switch.d.ts +7 -4
  113. package/dist/types/input/Textarea.d.ts +5 -1
  114. package/dist/types/internal/FieldWrapper.d.ts +1 -1
  115. package/dist/types/internal/InputWrapper.d.ts +1 -1
  116. package/dist/types/ranger/index.d.ts +2 -2
  117. package/dist/types/select/AutoComplete.d.ts +1 -1
  118. package/dist/types/select/DualList.d.ts +6 -2
  119. package/dist/types/select/List.d.ts +1 -1
  120. package/dist/types/select/Select.d.ts +1 -1
  121. package/dist/types/select/useSelect.d.ts +7 -15
  122. package/dist/types/types/index.d.ts +28 -7
  123. package/dist/types/types/schema.d.ts +78 -33
  124. package/dist/types/utils/eventHandlers.d.ts +1 -1
  125. package/package.json +15 -12
  126. package/src/builder/FileField.tsx +53 -27
  127. package/src/builder/FormField.tsx +52 -32
  128. package/src/builder/SchemaEditor.tsx +72 -0
  129. package/src/builder/components/AddSchemaFields.tsx +74 -0
  130. package/src/builder/components/SchemaArrayForm.tsx +158 -0
  131. package/src/builder/components/SchemaField.tsx +436 -0
  132. package/src/form/Controller.tsx +3 -3
  133. package/src/form/Form.tsx +14 -5
  134. package/src/hooks/useFormBuilder.tsx +29 -15
  135. package/src/index.ts +11 -2
  136. package/src/input/ArrayInput.tsx +130 -54
  137. package/src/input/AvatarInput.tsx +12 -8
  138. package/src/input/Checkbox.tsx +6 -5
  139. package/src/input/ColorInput.tsx +113 -69
  140. package/src/input/ColorPicker.tsx +2 -36
  141. package/src/input/DateInput.tsx +250 -0
  142. package/src/input/Field.tsx +10 -11
  143. package/src/input/File.tsx +7 -14
  144. package/src/input/Hidden.tsx +4 -1
  145. package/src/input/Input.tsx +15 -10
  146. package/src/input/Number.tsx +10 -10
  147. package/src/input/Password.tsx +13 -12
  148. package/src/input/Radio.tsx +2 -2
  149. package/src/input/RadioGroup.tsx +27 -4
  150. package/src/input/{Range.tsx → RangeSlider.tsx} +4 -4
  151. package/src/input/Search.tsx +20 -19
  152. package/src/input/Slider.tsx +3 -3
  153. package/src/input/Switch.tsx +43 -7
  154. package/src/input/Textarea.tsx +19 -5
  155. package/src/internal/ErrorIcon.tsx +1 -1
  156. package/src/internal/FieldWrapper.tsx +8 -4
  157. package/src/internal/InputWrapper.tsx +36 -20
  158. package/src/select/AutoComplete.tsx +5 -1
  159. package/src/select/DualList.tsx +8 -0
  160. package/src/select/List.tsx +41 -8
  161. package/src/select/Options.tsx +1 -1
  162. package/src/select/Select.tsx +13 -3
  163. package/src/select/useSelect.tsx +13 -5
  164. package/src/types/index.ts +34 -8
  165. package/src/types/schema.ts +96 -41
  166. package/src/utils/eventHandlers.ts +9 -3
  167. package/dist/cjs/input/ArrayInput.module.css +0 -1
  168. package/dist/cjs/input/ColorPicker.module.css +0 -1
  169. package/dist/cjs/input/File.module.css +0 -1
  170. package/dist/cjs/internal/Field.module.css +0 -1
  171. package/dist/esm/input/ArrayInput.module.css +0 -1
  172. package/dist/esm/input/ColorPicker.module.css +0 -1
  173. package/dist/esm/input/File.module.css +0 -1
  174. package/dist/esm/internal/Field.module.css +0 -1
@@ -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> & { height?: string | number }) => {
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 (state.activeIndex === null) {
171
- setActiveIndex(state.selectedIndex);
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="group flex-1 py-1 px-2 truncate text-start flex gap-1 flex-wrap"
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) ?? item[labelProperty] ?? item}
316
+ {(item, label) => renderer?.(item) ?? label}
284
317
  </Options>
285
318
  </FloatingFocusManager>
286
319
  </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,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-[var(--z-popover)]"
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>
@@ -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: createOption(action.query),
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: false,
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) === deferred,
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
@@ -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 { 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";
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?: V) => void;
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
- extends RefProp<HTMLInputElement>,
122
- Omit<InputProps, "value" | "onChange"> {
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
- export type SelectProps<T> = BaseSelectProps<T> &
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
  /**
@@ -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
- interface BaseSchemaDef {
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 SchemaDef extends BaseSchemaDef {
34
- datatype: "text" | "color";
64
+ export interface DefaultSchema extends BaseSchema {
65
+ datatype: DATA_TYPES.TEXT | DATA_TYPES.COLOR;
35
66
  }
36
67
 
37
- interface BooleanSchema extends BaseSchemaDef {
38
- datatype: "boolean";
39
- defaultValue?: boolean;
68
+ export interface BooleanSchema extends BaseSchema {
69
+ datatype: DATA_TYPES.BOOL;
40
70
  }
41
71
 
42
- interface StringSchema extends BaseSchemaDef {
43
- datatype: "string";
44
- options?: string[];
72
+ export interface StringSchema extends BaseSchema {
73
+ datatype: DATA_TYPES.STRING;
45
74
  regex?: string;
46
- identifierType?: string;
75
+ options?: string[];
76
+ optionList?: string;
77
+ allowCustom?: boolean;
47
78
  }
48
79
 
49
- interface RangeSchema extends BaseSchemaDef {
50
- datatype: "range";
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 NumberSchema extends BaseSchemaDef {
58
- datatype: "number";
59
- min?: number;
60
- max?: number;
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 DecimalSchema extends BaseSchemaDef {
66
- datatype: "decimal";
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 BaseSchemaDef {
74
- datatype: "file";
100
+ export interface FileSchema extends BaseSchema {
101
+ datatype: DATA_TYPES.FILE;
75
102
  accept?: string;
76
103
  }
77
104
 
78
- export interface AvatarSchema extends BaseSchemaDef {
79
- datatype: "avatar";
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
- | SchemaDef
85
- | BooleanSchema
86
- | StringSchema
87
- | RangeSchema
88
- | NumberSchema
89
- | DecimalSchema
90
- | FileSchema
91
- | AvatarSchema;
92
-
93
- export interface ChildSchema extends BaseSchemaDef {
94
- datatype: "schema";
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 FormSchema = SchemaType | ChildSchema;
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 { debounce } from "@react-fabric/utilities";
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 handleEnter = (
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 = debounce(callback ?? (() => undefined), 100);
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}}}}