@dxos/react-ui-form 0.8.4-main.b97322e → 0.8.4-main.bc674ce

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 (197) hide show
  1. package/dist/lib/browser/index.mjs +2220 -2058
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +2220 -2058
  5. package/dist/lib/node-esm/index.mjs.map +4 -4
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/types/src/components/ControlSection/ControlSection.d.ts +4 -4
  8. package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +1 -1
  9. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts +84 -5
  10. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +1 -1
  11. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +6 -5
  12. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  13. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +82 -5
  14. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
  15. package/dist/types/src/components/Form/Form-old.stories.d.ts +131 -0
  16. package/dist/types/src/components/Form/Form-old.stories.d.ts.map +1 -0
  17. package/dist/types/src/components/Form/Form.d.ts +81 -25
  18. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  19. package/dist/types/src/components/Form/Form.stories.d.ts +127 -55
  20. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  21. package/dist/types/src/components/Form/FormField.d.ts +37 -0
  22. package/dist/types/src/components/Form/FormField.d.ts.map +1 -0
  23. package/dist/types/src/components/Form/FormFieldComponent.d.ts +78 -0
  24. package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -0
  25. package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts +9 -0
  26. package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts.map +1 -0
  27. package/dist/types/src/components/Form/FormFieldSet.d.ts +19 -0
  28. package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -0
  29. package/dist/types/src/components/Form/fields/ArrayField.d.ts +18 -0
  30. package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -0
  31. package/dist/types/src/components/Form/fields/BooleanField.d.ts +4 -0
  32. package/dist/types/src/components/Form/fields/BooleanField.d.ts.map +1 -0
  33. package/dist/types/src/components/Form/fields/GeoPointField.d.ts +5 -0
  34. package/dist/types/src/components/Form/fields/GeoPointField.d.ts.map +1 -0
  35. package/dist/types/src/components/Form/fields/MarkdownField.d.ts +4 -0
  36. package/dist/types/src/components/Form/fields/MarkdownField.d.ts.map +1 -0
  37. package/dist/types/src/components/Form/fields/NumberField.d.ts +4 -0
  38. package/dist/types/src/components/Form/fields/NumberField.d.ts.map +1 -0
  39. package/dist/types/src/components/Form/fields/RefField.d.ts +14 -0
  40. package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -0
  41. package/dist/types/src/components/Form/fields/SelectField.d.ts +10 -0
  42. package/dist/types/src/components/Form/fields/SelectField.d.ts.map +1 -0
  43. package/dist/types/src/components/Form/fields/SelectOptionField.d.ts +5 -0
  44. package/dist/types/src/components/Form/fields/SelectOptionField.d.ts.map +1 -0
  45. package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts +82 -0
  46. package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts.map +1 -0
  47. package/dist/types/src/components/Form/fields/TextAreaField.d.ts +4 -0
  48. package/dist/types/src/components/Form/fields/TextAreaField.d.ts.map +1 -0
  49. package/dist/types/src/components/Form/fields/TextField.d.ts +4 -0
  50. package/dist/types/src/components/Form/fields/TextField.d.ts.map +1 -0
  51. package/dist/types/src/components/Form/fields/TupleField.d.ts +6 -0
  52. package/dist/types/src/components/Form/fields/TupleField.d.ts.map +1 -0
  53. package/dist/types/src/components/Form/fields/index.d.ts +12 -0
  54. package/dist/types/src/components/Form/fields/index.d.ts.map +1 -0
  55. package/dist/types/src/components/Form/index.d.ts +2 -4
  56. package/dist/types/src/components/Form/index.d.ts.map +1 -1
  57. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +46 -0
  58. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -0
  59. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +85 -0
  60. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -0
  61. package/dist/types/src/components/ObjectPicker/index.d.ts +2 -0
  62. package/dist/types/src/components/ObjectPicker/index.d.ts.map +1 -0
  63. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +21 -9
  64. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  65. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +83 -8
  66. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  67. package/dist/types/src/components/index.d.ts +1 -1
  68. package/dist/types/src/components/index.d.ts.map +1 -1
  69. package/dist/types/src/components/testing.d.ts +4 -3
  70. package/dist/types/src/components/testing.d.ts.map +1 -1
  71. package/dist/types/src/hooks/index.d.ts +2 -3
  72. package/dist/types/src/hooks/index.d.ts.map +1 -1
  73. package/dist/types/src/hooks/useFormHandler.d.ts +82 -0
  74. package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -0
  75. package/dist/types/src/hooks/useKeyHandler.d.ts +6 -0
  76. package/dist/types/src/hooks/useKeyHandler.d.ts.map +1 -0
  77. package/dist/types/src/translations.d.ts +19 -10
  78. package/dist/types/src/translations.d.ts.map +1 -1
  79. package/dist/types/src/util/echo.d.ts +6 -0
  80. package/dist/types/src/util/echo.d.ts.map +1 -0
  81. package/dist/types/src/util/field.d.ts +3 -3
  82. package/dist/types/src/util/field.d.ts.map +1 -1
  83. package/dist/types/src/util/formatting.d.ts +2 -2
  84. package/dist/types/src/util/formatting.d.ts.map +1 -1
  85. package/dist/types/src/util/index.d.ts +2 -1
  86. package/dist/types/src/util/index.d.ts.map +1 -1
  87. package/dist/types/src/util/properties.d.ts +7 -0
  88. package/dist/types/src/util/properties.d.ts.map +1 -0
  89. package/dist/types/src/util/refs.d.ts +2 -3
  90. package/dist/types/src/util/refs.d.ts.map +1 -1
  91. package/dist/types/tsconfig.tsbuildinfo +1 -1
  92. package/package.json +51 -40
  93. package/src/components/ControlSection/ControlSection.stories.tsx +11 -14
  94. package/src/components/ControlSection/ControlSection.tsx +30 -16
  95. package/src/components/FieldEditor/FieldEditor.stories.tsx +52 -31
  96. package/src/components/FieldEditor/FieldEditor.test.tsx +3 -2
  97. package/src/components/FieldEditor/FieldEditor.tsx +86 -81
  98. package/src/components/Form/Form-old.stories.tsx +314 -0
  99. package/src/components/Form/Form.stories.tsx +155 -219
  100. package/src/components/Form/Form.tsx +320 -96
  101. package/src/components/Form/FormField.tsx +286 -0
  102. package/src/components/Form/FormFieldComponent.stories.tsx +65 -0
  103. package/src/components/Form/FormFieldComponent.tsx +174 -0
  104. package/src/components/Form/FormFieldSet.tsx +125 -0
  105. package/src/components/Form/README.md +3 -0
  106. package/src/components/Form/fields/ArrayField.tsx +151 -0
  107. package/src/components/Form/fields/BooleanField.tsx +22 -0
  108. package/src/components/Form/fields/GeoPointField.tsx +120 -0
  109. package/src/components/Form/fields/MarkdownField.tsx +78 -0
  110. package/src/components/Form/fields/NumberField.tsx +39 -0
  111. package/src/components/Form/fields/RefField.tsx +181 -0
  112. package/src/components/Form/fields/SelectField.tsx +65 -0
  113. package/src/components/Form/{custom/SelectOptionsInput.stories.tsx → fields/SelectOptionField.stories.tsx} +25 -22
  114. package/src/components/Form/{custom/SelectOptionsInput.tsx → fields/SelectOptionField.tsx} +47 -42
  115. package/src/components/Form/fields/TextAreaField.tsx +38 -0
  116. package/src/components/Form/fields/TextField.tsx +38 -0
  117. package/src/components/Form/{custom/TupleInput.tsx → fields/TupleField.tsx} +16 -9
  118. package/src/components/Form/fields/index.ts +15 -0
  119. package/src/components/Form/index.ts +3 -4
  120. package/src/components/ObjectPicker/ObjectPicker.stories.tsx +214 -0
  121. package/src/components/ObjectPicker/ObjectPicker.tsx +175 -0
  122. package/src/components/ObjectPicker/index.ts +5 -0
  123. package/src/components/ViewEditor/ViewEditor.stories.tsx +137 -61
  124. package/src/components/ViewEditor/ViewEditor.test.tsx +19 -17
  125. package/src/components/ViewEditor/ViewEditor.tsx +324 -203
  126. package/src/components/index.ts +1 -2
  127. package/src/components/testing.tsx +19 -11
  128. package/src/hooks/index.ts +2 -3
  129. package/src/hooks/useFormHandler.ts +362 -0
  130. package/src/hooks/useKeyHandler.tsx +37 -0
  131. package/src/translations.ts +21 -12
  132. package/src/util/echo.ts +25 -0
  133. package/src/util/field.ts +33 -43
  134. package/src/util/formatting.ts +22 -22
  135. package/src/util/index.ts +2 -1
  136. package/src/util/properties.ts +18 -0
  137. package/src/util/refs.ts +12 -18
  138. package/dist/types/src/components/Form/ArrayField.d.ts +0 -16
  139. package/dist/types/src/components/Form/ArrayField.d.ts.map +0 -1
  140. package/dist/types/src/components/Form/Defaults.d.ts +0 -14
  141. package/dist/types/src/components/Form/Defaults.d.ts.map +0 -1
  142. package/dist/types/src/components/Form/FormActions.d.ts +0 -9
  143. package/dist/types/src/components/Form/FormActions.d.ts.map +0 -1
  144. package/dist/types/src/components/Form/FormContent.d.ts +0 -57
  145. package/dist/types/src/components/Form/FormContent.d.ts.map +0 -1
  146. package/dist/types/src/components/Form/FormContext.d.ts +0 -23
  147. package/dist/types/src/components/Form/FormContext.d.ts.map +0 -1
  148. package/dist/types/src/components/Form/Input.d.ts +0 -26
  149. package/dist/types/src/components/Form/Input.d.ts.map +0 -1
  150. package/dist/types/src/components/Form/RefField.d.ts +0 -12
  151. package/dist/types/src/components/Form/RefField.d.ts.map +0 -1
  152. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +0 -4
  153. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +0 -1
  154. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +0 -4
  155. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +0 -1
  156. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +0 -7
  157. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +0 -1
  158. package/dist/types/src/components/Form/custom/TupleInput.d.ts +0 -6
  159. package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +0 -1
  160. package/dist/types/src/components/Form/custom/index.d.ts +0 -4
  161. package/dist/types/src/components/Form/custom/index.d.ts.map +0 -1
  162. package/dist/types/src/components/Form/factory.d.ts +0 -8
  163. package/dist/types/src/components/Form/factory.d.ts.map +0 -1
  164. package/dist/types/src/components/deprecated/Form.d.ts +0 -15
  165. package/dist/types/src/components/deprecated/Form.d.ts.map +0 -1
  166. package/dist/types/src/components/deprecated/Form.stories.d.ts +0 -13
  167. package/dist/types/src/components/deprecated/Form.stories.d.ts.map +0 -1
  168. package/dist/types/src/components/deprecated/FormInput.d.ts +0 -16
  169. package/dist/types/src/components/deprecated/FormInput.d.ts.map +0 -1
  170. package/dist/types/src/components/deprecated/index.d.ts +0 -2
  171. package/dist/types/src/components/deprecated/index.d.ts.map +0 -1
  172. package/dist/types/src/hooks/useForm.d.ts +0 -67
  173. package/dist/types/src/hooks/useForm.d.ts.map +0 -1
  174. package/dist/types/src/hooks/useQueryRefOptions.d.ts +0 -23
  175. package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +0 -1
  176. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts +0 -8
  177. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts.map +0 -1
  178. package/dist/types/src/util/schema.d.ts +0 -3
  179. package/dist/types/src/util/schema.d.ts.map +0 -1
  180. package/src/components/Form/ArrayField.tsx +0 -124
  181. package/src/components/Form/Defaults.tsx +0 -203
  182. package/src/components/Form/FormActions.tsx +0 -53
  183. package/src/components/Form/FormContent.tsx +0 -240
  184. package/src/components/Form/FormContext.tsx +0 -110
  185. package/src/components/Form/Input.tsx +0 -51
  186. package/src/components/Form/RefField.tsx +0 -213
  187. package/src/components/Form/custom/GeoPointInput.tsx +0 -72
  188. package/src/components/Form/custom/index.ts +0 -7
  189. package/src/components/Form/factory.tsx +0 -31
  190. package/src/components/deprecated/Form.stories.tsx +0 -62
  191. package/src/components/deprecated/Form.tsx +0 -119
  192. package/src/components/deprecated/FormInput.tsx +0 -71
  193. package/src/components/deprecated/index.ts +0 -5
  194. package/src/hooks/useForm.ts +0 -291
  195. package/src/hooks/useQueryRefOptions.ts +0 -53
  196. package/src/hooks/useRefQueryLookupHandler.ts +0 -39
  197. package/src/util/schema.ts +0 -14
@@ -1,110 +0,0 @@
1
- //
2
- // Copyright 2025 DXOS.org
3
- //
4
-
5
- import React, {
6
- createContext,
7
- type RefObject,
8
- useContext,
9
- useEffect,
10
- useMemo,
11
- type FocusEvent,
12
- type PropsWithChildren,
13
- useCallback,
14
- } from 'react';
15
-
16
- import { raise } from '@dxos/debug';
17
- import { type BaseObject, getValue } from '@dxos/echo-schema';
18
- import { createJsonPath, type SimpleType } from '@dxos/effect';
19
-
20
- import { type FormHandler, type FormOptions, useForm } from '../../hooks';
21
-
22
- type FormContextValue<T extends BaseObject> = FormHandler<T>;
23
-
24
- const FormContext = createContext<FormContextValue<any> | undefined>(undefined);
25
-
26
- export const useFormContext = <T extends BaseObject>(): FormContextValue<T> => {
27
- return useContext(FormContext) ?? raise(new Error('Missing FormContext'));
28
- };
29
-
30
- export const useFormValues = (path: (string | number)[] = []): any => {
31
- const { values: formValues } = useFormContext();
32
- const jsonPath = createJsonPath(path);
33
- return getValue(formValues, jsonPath) as BaseObject;
34
- };
35
-
36
- export type FormInputStateProps = {
37
- getStatus: () => { status?: 'error'; error?: string };
38
- getValue: <V>() => V | undefined;
39
- onValueChange: (type: SimpleType, value: any) => void;
40
- onBlur: (event: FocusEvent<HTMLElement>) => void;
41
- };
42
-
43
- export const useInputProps = (path: (string | number)[] = []): FormInputStateProps => {
44
- const { getStatus, getValue: getFormValue, onValueChange, onTouched } = useFormContext();
45
-
46
- const stablePath = useMemo(() => path, [Array.isArray(path) ? path.join('.') : path]);
47
- return useMemo(
48
- () => ({
49
- getStatus: () => getStatus(stablePath),
50
- getValue: () => getFormValue(stablePath),
51
- onValueChange: (type: SimpleType, value: any) => onValueChange(stablePath, type, value),
52
- onBlur: () => onTouched(stablePath),
53
- }),
54
- [getStatus, getFormValue, onValueChange, onTouched, stablePath],
55
- );
56
- };
57
-
58
- export const FormProvider = ({
59
- children,
60
- formRef,
61
- autoSave,
62
- ...formOptions
63
- }: PropsWithChildren<
64
- FormOptions<any> & {
65
- formRef?: RefObject<HTMLDivElement>;
66
- autoSave?: boolean;
67
- }
68
- >) => {
69
- const form = useForm(formOptions);
70
-
71
- const handleKeyDown = useCallback(
72
- (event: KeyboardEvent) => {
73
- const keyIsEnter = event.key === 'Enter';
74
- const modifierUsed = event.ctrlKey || event.altKey || event.metaKey || event.shiftKey;
75
- const inputIsTextarea = (event.target as HTMLElement).tagName.toLowerCase() === 'textarea';
76
- const inputOptOut =
77
- (event.target as HTMLElement).hasAttribute('data-no-submit') ||
78
- (event.target as HTMLElement).closest('[data-no-submit]') !== null;
79
-
80
- // Regular inputs: Submit on Enter (no modifiers).
81
- const shouldSubmitRegularInput = !inputIsTextarea && keyIsEnter && !modifierUsed;
82
-
83
- // Textareas: Submit only on Meta+Enter.
84
- const shouldSubmitTextarea = inputIsTextarea && keyIsEnter && event.metaKey;
85
-
86
- if ((shouldSubmitRegularInput || shouldSubmitTextarea) && !inputOptOut) {
87
- if (!autoSave && form.canSave) {
88
- form.handleSave();
89
- }
90
- if (autoSave && form.formIsValid) {
91
- (event.target as HTMLElement).blur();
92
- }
93
- }
94
- },
95
- [form, autoSave],
96
- );
97
-
98
- useEffect(() => {
99
- if (!formRef?.current) {
100
- return;
101
- }
102
-
103
- const formElement = formRef.current;
104
-
105
- formElement.addEventListener('keydown', handleKeyDown);
106
- return () => formElement.removeEventListener('keydown', handleKeyDown);
107
- }, [formRef, form.canSave, form.formIsValid, form.handleSave, autoSave]);
108
-
109
- return <FormContext.Provider value={form}>{children}</FormContext.Provider>;
110
- };
@@ -1,51 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import React, { type FC } from 'react';
6
-
7
- import { type FormatEnum } from '@dxos/echo-schema';
8
- import { type SimpleType } from '@dxos/effect';
9
- import { Icon, Input, Tooltip } from '@dxos/react-ui';
10
- import { labelSpacing } from '@dxos/react-ui-stack';
11
- import { errorText, mx } from '@dxos/react-ui-theme';
12
-
13
- import { type FormInputStateProps } from './FormContext';
14
-
15
- /**
16
- * Props passed to input components.
17
- */
18
- export type InputProps = {
19
- type: SimpleType;
20
- format?: FormatEnum;
21
- label: string;
22
- disabled?: boolean;
23
- placeholder?: string;
24
- inputOnly?: boolean;
25
- } & FormInputStateProps;
26
-
27
- /**
28
- * Form input component.
29
- */
30
- export type InputComponent = FC<InputProps>;
31
-
32
- export type InputHeaderProps = {
33
- error?: string;
34
- label: string;
35
- readonly?: boolean;
36
- };
37
-
38
- export const InputHeader = ({ error, label, readonly }: InputHeaderProps) => {
39
- const Label = readonly ? 'span' : Input.Label;
40
- const labelProps = readonly ? {} : { classNames: '!mlb-0' };
41
- return (
42
- <div role='none' className={mx('flex justify-between items-center', labelSpacing)}>
43
- <Label {...labelProps}>{label}</Label>
44
- {error && (
45
- <Tooltip.Trigger asChild content={error} side='bottom'>
46
- <Icon icon='ph--warning--regular' size={4} classNames={errorText} />
47
- </Tooltip.Trigger>
48
- )}
49
- </div>
50
- );
51
- };
@@ -1,213 +0,0 @@
1
- //
2
- // Copyright 2025 DXOS.org
3
- //
4
-
5
- import { type SchemaAST } from 'effect';
6
- import React, { useCallback, useMemo, type FocusEvent } from 'react';
7
-
8
- import {
9
- Expando,
10
- getTypeAnnotation,
11
- Ref,
12
- ReferenceAnnotationId,
13
- type ReferenceAnnotationValue,
14
- } from '@dxos/echo-schema';
15
- import { findAnnotation } from '@dxos/effect';
16
- import { DXN } from '@dxos/keys';
17
- import { DxRefTag } from '@dxos/lit-ui/react';
18
- import { Input, useTranslation } from '@dxos/react-ui';
19
- import { TagPicker, type TagPickerMode, type TagPickerItemData } from '@dxos/react-ui-tag-picker';
20
- import { descriptionText, mx } from '@dxos/react-ui-theme';
21
- import { isNonNullable } from '@dxos/util';
22
-
23
- import { TextInput } from './Defaults';
24
- import { InputHeader, type InputProps } from './Input';
25
- import { type QueryRefOptions, useQueryRefOptions } from '../../hooks';
26
- import { translationKey } from '../../translations';
27
-
28
- type RefFieldProps = InputProps & {
29
- ast?: SchemaAST.AST;
30
- array?: boolean;
31
- onQueryRefOptions?: QueryRefOptions;
32
- };
33
-
34
- export const RefField = ({
35
- type,
36
- label,
37
- disabled,
38
- placeholder,
39
- inputOnly,
40
- array,
41
- ast,
42
- getValue,
43
- onBlur,
44
- onQueryRefOptions,
45
- onValueChange,
46
- ...restInputProps
47
- }: RefFieldProps) => {
48
- const { t } = useTranslation(translationKey);
49
- const refTypeInfo = useMemo(
50
- () => (ast ? findAnnotation<ReferenceAnnotationValue>(ast, ReferenceAnnotationId) : undefined),
51
- [ast],
52
- );
53
- const { options: availableOptions, loading: _loading } = useQueryRefOptions({ refTypeInfo, onQueryRefOptions });
54
-
55
- if ((refTypeInfo && refTypeInfo?.typename === getTypeAnnotation(Expando)?.typename) || !onQueryRefOptions) {
56
- // If ref type is expando, fall back to taking a DXN in string format.
57
- return (
58
- <RefFieldFallback
59
- {...{ type, label, placeholder, disabled, inputOnly, getValue, onBlur, onValueChange, ...restInputProps }}
60
- />
61
- );
62
- }
63
-
64
- const handleGetValue = useCallback((): TagPickerItemData[] => {
65
- const formValue = getValue();
66
-
67
- const unknownToRefOption = (val: unknown) => {
68
- if (Ref.isRef(val)) {
69
- const dxnString = val.dxn.toString();
70
- const matchingOption = availableOptions.find((option) => option.id === dxnString);
71
- if (matchingOption) {
72
- return matchingOption;
73
- }
74
- }
75
- return undefined;
76
- };
77
-
78
- if (array && Array.isArray(formValue)) {
79
- return formValue.map(unknownToRefOption).filter(isNonNullable) ?? [];
80
- }
81
-
82
- const option = unknownToRefOption(formValue);
83
- if (option) {
84
- return [option];
85
- }
86
-
87
- return [];
88
- }, [getValue, availableOptions, array]);
89
-
90
- const handleSearch = useCallback(
91
- (text: string, ids: string[]): TagPickerItemData[] =>
92
- availableOptions
93
- .filter((option) => !ids.includes(option.id))
94
- .filter((option) => option.label.toLowerCase().includes(text.toLowerCase()))
95
- .map((option) => ({ id: option.id, label: option.label, hue: option.hue as any })),
96
- [availableOptions],
97
- );
98
-
99
- const handleUpdate = useCallback(
100
- (ids: string[]) => {
101
- if (ids.length === 0) {
102
- onValueChange('object', undefined);
103
- return;
104
- }
105
-
106
- const refs = ids
107
- .map((id) => {
108
- const item = availableOptions.find((option) => option.id === id);
109
- if (item) {
110
- const dxn = DXN.parse(item.id);
111
- return Ref.fromDXN(dxn);
112
- }
113
- return null;
114
- })
115
- .filter(isNonNullable);
116
-
117
- if (array) {
118
- onValueChange('object', refs);
119
- } else {
120
- onValueChange('object', refs[0]);
121
- }
122
- },
123
- [availableOptions, array, onValueChange],
124
- );
125
-
126
- const tagPickerMode: TagPickerMode = useMemo(() => (array ? 'multi-select' : 'single-select'), [array]);
127
-
128
- if (!refTypeInfo) {
129
- return null;
130
- }
131
-
132
- const { status, error } = restInputProps.getStatus();
133
-
134
- const items = handleGetValue();
135
-
136
- // NOTE(thure): I left both predicates in-place in case we decide to add variants which do render readonly but empty values.
137
- return disabled && items.length < 1 ? null : (
138
- <Input.Root validationValence={status}>
139
- {!inputOnly && <InputHeader error={error} label={label} />}
140
- <div data-no-submit>
141
- {disabled ? (
142
- items.length < 1 ? (
143
- <p className={mx(descriptionText, 'mbe-2')}>{t('empty readonly ref field label')}</p>
144
- ) : (
145
- items.map((item) => (
146
- <DxRefTag key={item.id} refid={item.id} rootclassname='mie-1' style={{ marginInlineEnd: '.25rem' }}>
147
- {item.label}
148
- </DxRefTag>
149
- ))
150
- )
151
- ) : (
152
- <TagPicker
153
- readonly={disabled}
154
- items={items}
155
- mode={tagPickerMode}
156
- onBlur={(event) => onBlur(event as unknown as FocusEvent<HTMLElement>)}
157
- onUpdate={handleUpdate}
158
- onSearch={handleSearch}
159
- classNames='bg-inputSurface p-1.5 rounded-sm'
160
- />
161
- )}
162
- </div>
163
- {inputOnly && <Input.DescriptionAndValidation>{error}</Input.DescriptionAndValidation>}
164
- </Input.Root>
165
- );
166
- };
167
-
168
- const RefFieldFallback = ({
169
- type,
170
- label,
171
- disabled,
172
- placeholder,
173
- inputOnly,
174
- getValue,
175
- onValueChange,
176
- ...restInputProps
177
- }: InputProps) => {
178
- const handleOnValueChange = (_type: any, dxnString: string) => {
179
- const dxn = DXN.tryParse(dxnString);
180
- if (dxn) {
181
- onValueChange?.('object', Ref.fromDXN(dxn));
182
- } else if (dxnString === '') {
183
- onValueChange?.('object', undefined);
184
- } else {
185
- onValueChange?.('string', dxnString);
186
- }
187
- };
188
-
189
- const handleGetValue = () => {
190
- const formValue = getValue();
191
- if (typeof formValue === 'string') {
192
- return formValue;
193
- }
194
- if (Ref.isRef(formValue)) {
195
- return formValue.dxn.toString();
196
- }
197
-
198
- return undefined;
199
- };
200
-
201
- return (
202
- <TextInput
203
- type={type}
204
- label={label}
205
- disabled={disabled}
206
- placeholder={placeholder}
207
- inputOnly={inputOnly}
208
- getValue={handleGetValue as <V>() => V | undefined}
209
- onValueChange={handleOnValueChange}
210
- {...restInputProps}
211
- />
212
- );
213
- };
@@ -1,72 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import React, { useCallback, useState, useEffect, useMemo, type ChangeEvent } from 'react';
6
-
7
- import { type GeoPoint, GeoLocation } from '@dxos/echo-schema';
8
- import { Input, useTranslation } from '@dxos/react-ui';
9
- import { safeParseFloat } from '@dxos/util';
10
-
11
- import { translationKey } from '../../../translations';
12
- import { InputHeader, type InputProps } from '../Input';
13
-
14
- export const GeoPointInput = ({ type, label, disabled, getStatus, getValue, onValueChange, onBlur }: InputProps) => {
15
- const { t } = useTranslation(translationKey);
16
- const { status, error } = getStatus();
17
- const geoPoint = useMemo<GeoPoint>(() => getValue<GeoPoint>() ?? [0, 0], [getValue]);
18
- const geoLocation = useMemo(() => GeoLocation.fromGeoPoint(geoPoint), [geoPoint]);
19
-
20
- const [longitudeText, setLongitudeText] = useState(geoLocation.longitude?.toString());
21
- const [latitudeText, setLatitudeText] = useState(geoLocation.latitude?.toString());
22
-
23
- useEffect(() => {
24
- const location = GeoLocation.fromGeoPoint(geoPoint);
25
- setLongitudeText(location.longitude.toString());
26
- setLatitudeText(location.latitude.toString());
27
- }, [geoPoint]);
28
-
29
- const handleCoordinateChange = useCallback(
30
- (coordinateType: keyof Pick<GeoLocation, 'longitude' | 'latitude'>, setText: (text: string) => void) =>
31
- (event: ChangeEvent<HTMLInputElement>) => {
32
- const inputText = event.target.value;
33
- setText(inputText);
34
- if (inputText !== '' && inputText !== '-') {
35
- const coord = safeParseFloat(inputText);
36
- if (coord !== undefined && !isNaN(coord)) {
37
- const currentLocation = GeoLocation.fromGeoPoint(getValue<GeoPoint>() ?? [0, 0]);
38
- const newLocation = { ...currentLocation, [coordinateType]: coord };
39
- const newValue = GeoLocation.toGeoPoint(newLocation);
40
- onValueChange(type, newValue);
41
- }
42
- }
43
- },
44
- [getValue, onValueChange, type],
45
- );
46
-
47
- return (
48
- <Input.Root validationValence={status}>
49
- <InputHeader error={error} label={label} />
50
- <div role='none' className='grid grid-cols-2 gap-2'>
51
- <Input.TextInput
52
- type='text'
53
- pattern='^-?[0-9]*\.?[0-9]*$'
54
- disabled={disabled}
55
- placeholder={t('placeholder latitude')}
56
- value={latitudeText}
57
- onChange={handleCoordinateChange('latitude', setLatitudeText)}
58
- onBlur={onBlur}
59
- />
60
- <Input.TextInput
61
- type='text'
62
- pattern='^-?[0-9]*\.?[0-9]*$'
63
- disabled={disabled}
64
- placeholder={t('placeholder longitude')}
65
- value={longitudeText}
66
- onChange={handleCoordinateChange('longitude', setLongitudeText)}
67
- onBlur={onBlur}
68
- />
69
- </div>
70
- </Input.Root>
71
- );
72
- };
@@ -1,7 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- export * from './GeoPointInput';
6
- export * from './SelectOptionsInput';
7
- export * from './TupleInput';
@@ -1,31 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { FormatEnum } from '@dxos/echo-schema';
6
- import { type SimpleType } from '@dxos/effect';
7
-
8
- import { BooleanInput, MarkdownInput, NumberInput, TextInput } from './Defaults';
9
- import { type InputComponent } from './Input';
10
- import { GeoPointInput } from './custom';
11
-
12
- /**
13
- * Get property input component.
14
- */
15
- export const getInputComponent = (type: SimpleType, format?: FormatEnum | string): InputComponent | undefined => {
16
- switch (format) {
17
- case FormatEnum.GeoPoint:
18
- return GeoPointInput;
19
- case FormatEnum.Markdown:
20
- return MarkdownInput;
21
- }
22
-
23
- switch (type) {
24
- case 'string':
25
- return TextInput;
26
- case 'number':
27
- return NumberInput;
28
- case 'boolean':
29
- return BooleanInput;
30
- }
31
- };
@@ -1,62 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import '@dxos-theme';
6
-
7
- import { type Meta, type StoryObj } from '@storybook/react-vite';
8
- import React from 'react';
9
-
10
- import { live } from '@dxos/live-object';
11
- import { testData, TestSchema, testView, type TestType } from '@dxos/schema/testing';
12
- import { withLayout, withTheme } from '@dxos/storybook-utils';
13
-
14
- import { DeprecatedForm, type DeprecatedFormProps } from './Form';
15
- import { translations } from '../../translations';
16
- import { TestLayout, TestPanel } from '../testing';
17
-
18
- const DefaultStory = (props: DeprecatedFormProps) => (
19
- <TestLayout json={{ props }}>
20
- <TestPanel>
21
- <DeprecatedForm {...props} />
22
- </TestPanel>
23
- </TestLayout>
24
- );
25
-
26
- const meta: Meta<typeof DefaultStory> = {
27
- title: 'ui/react-ui-form/DeprecatedForm',
28
- component: DeprecatedForm,
29
- render: DefaultStory,
30
- decorators: [withLayout({ fullscreen: true }), withTheme],
31
- parameters: {
32
- translations,
33
- },
34
- };
35
-
36
- export default meta;
37
-
38
- type Story = StoryObj<DeprecatedFormProps<TestType>>;
39
-
40
- export const Default: Story = {
41
- args: {
42
- object: live(testData),
43
- projection: live(testView).projection,
44
- schema: TestSchema,
45
- },
46
- };
47
-
48
- export const Empty: Story = {
49
- args: {
50
- projection: live(testView).projection,
51
- schema: TestSchema,
52
- },
53
- };
54
-
55
- export const Readonly: Story = {
56
- args: {
57
- object: live(testData),
58
- projection: live(testView).projection,
59
- schema: TestSchema,
60
- readonly: true,
61
- },
62
- };
@@ -1,119 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { SchemaAST, type Schema, Option, pipe } from 'effect';
6
- import React, { useState } from 'react';
7
-
8
- import { findProperty } from '@dxos/effect';
9
- import { Input, type ThemedClassName, type TextInputProps as NativeTextInputProps } from '@dxos/react-ui';
10
- import { mx } from '@dxos/react-ui-theme';
11
- import { getFieldValue, setFieldValue, type Projection } from '@dxos/schema';
12
-
13
- export type DeprecatedFormProps<T extends {} = {}> = ThemedClassName<{
14
- projection: Projection;
15
- object: T;
16
- schema?: Schema.Schema<T>;
17
- readonly?: boolean;
18
- }>;
19
-
20
- /**
21
- * Schema-based object form.
22
- */
23
- export const DeprecatedForm = <T extends {} = {}>({
24
- classNames,
25
- projection,
26
- object,
27
- schema,
28
- readonly,
29
- }: DeprecatedFormProps<T>) => {
30
- return (
31
- <div role='none' className={mx('flex flex-col w-full gap-2 p-2', classNames)}>
32
- {projection.fields.map((field) => {
33
- const prop = schema && findProperty(schema, field.path);
34
- const title = (prop && pipe(SchemaAST.getTitleAnnotation(prop), Option.getOrUndefined)) ?? '';
35
- const description = (prop && pipe(SchemaAST.getDescriptionAnnotation(prop), Option.getOrUndefined)) ?? title;
36
- // const format =
37
- // (prop && (getPatternAnnotation(prop) ?? (SchemaAST.isNumberKeyword(prop) && RealNumberFormat))) || undefined;
38
-
39
- //
40
- // Boolean
41
- //
42
-
43
- if (prop && SchemaAST.isBooleanKeyword(prop)) {
44
- const value = object && getFieldValue(object, field);
45
- const onChange = (checked: boolean) => {
46
- setFieldValue(object, field, checked);
47
- };
48
-
49
- return (
50
- <div key={field.path} className='flex flex-col w-full gap-1'>
51
- <Input.Root>
52
- <Input.Label classNames='px-1'>{title}</Input.Label>
53
- <Input.Switch disabled={readonly} checked={!!value} onCheckedChange={(checked) => onChange(checked)} />
54
- </Input.Root>
55
- </div>
56
- );
57
- }
58
-
59
- //
60
- // Strings
61
- // TODO(burdon): Other types:
62
- // - RichText editor
63
- // - Numbers (with formatting: commas, currency, etc.)
64
- // - Date (https://ark-ui.com/react/docs/components/date-picker)
65
- // - Enum (single/multi-select)
66
- //
67
-
68
- const value = object ? getFieldValue(object, field) : undefined;
69
-
70
- return (
71
- <div key={field.path} className='flex flex-col w-full gap-1'>
72
- <Input.Root>
73
- <Input.Label classNames='px-1'>{title}</Input.Label>
74
- <TextInput
75
- disabled={readonly}
76
- placeholder={description}
77
- // format={format}
78
- value={value ? String(value) : ''}
79
- onChange={(event) => setFieldValue(object, field, event.target.value)}
80
- />
81
- </Input.Root>
82
- </div>
83
- );
84
- })}
85
- </div>
86
- );
87
- };
88
-
89
- type TextInputProps = {
90
- // format?: PatternAnnotation;
91
- } & Omit<NativeTextInputProps, 'style'>;
92
-
93
- /**
94
- * @deprecated
95
- */
96
- // TODO(burdon): Remove.
97
- // TODO(burdon): Use schema validation directly.
98
- const TextInput = ({ onChange, ...props }: TextInputProps) => {
99
- const [valid, _setValid] = useState(true);
100
-
101
- const handleChange: NativeTextInputProps['onChange'] = (ev) => {
102
- // const text = ev.target.value;
103
- // if (!format?.filter || text.match(format.filter)) {
104
- onChange?.(ev);
105
- // if (format?.valid) {
106
- // setValid(format.valid.test(text));
107
- // }
108
- // }
109
- };
110
-
111
- return (
112
- <Input.TextInput
113
- {...props}
114
- // TODO(burdon): validationValence on Input.
115
- style={valid ? {} : ({ '--tw-ring-color': 'red' } as any)}
116
- onChange={handleChange}
117
- />
118
- );
119
- };