@dxos/react-ui-form 0.7.5-feature-compute.4d9d99a → 0.7.5-labs.071a3e2

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 (67) hide show
  1. package/dist/lib/browser/index.mjs +717 -468
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node/index.cjs +742 -508
  5. package/dist/lib/node/index.cjs.map +4 -4
  6. package/dist/lib/node/meta.json +1 -1
  7. package/dist/lib/node-esm/index.mjs +717 -468
  8. package/dist/lib/node-esm/index.mjs.map +4 -4
  9. package/dist/lib/node-esm/meta.json +1 -1
  10. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  11. package/dist/types/src/components/Form/ArrayField.d.ts +16 -0
  12. package/dist/types/src/components/Form/ArrayField.d.ts.map +1 -0
  13. package/dist/types/src/components/Form/Defaults.d.ts +5 -6
  14. package/dist/types/src/components/Form/Defaults.d.ts.map +1 -1
  15. package/dist/types/src/components/Form/Form.d.ts +12 -21
  16. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  17. package/dist/types/src/components/Form/Form.stories.d.ts +2 -1
  18. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  19. package/dist/types/src/components/Form/FormActions.d.ts +7 -0
  20. package/dist/types/src/components/Form/FormActions.d.ts.map +1 -0
  21. package/dist/types/src/components/Form/FormContent.d.ts +26 -0
  22. package/dist/types/src/components/Form/FormContent.d.ts.map +1 -0
  23. package/dist/types/src/components/Form/FormContext.d.ts +20 -0
  24. package/dist/types/src/components/Form/FormContext.d.ts.map +1 -0
  25. package/dist/types/src/components/Form/Input.d.ts +5 -6
  26. package/dist/types/src/components/Form/Input.d.ts.map +1 -1
  27. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +1 -2
  28. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +1 -1
  29. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +4 -0
  30. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +1 -0
  31. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +7 -0
  32. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +1 -0
  33. package/dist/types/src/components/Form/custom/TupleInput.d.ts +1 -2
  34. package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +1 -1
  35. package/dist/types/src/components/Form/custom/index.d.ts +1 -0
  36. package/dist/types/src/components/Form/custom/index.d.ts.map +1 -1
  37. package/dist/types/src/components/Form/factory.d.ts +2 -2
  38. package/dist/types/src/components/Form/factory.d.ts.map +1 -1
  39. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  40. package/dist/types/src/components/deprecated/FormInput.d.ts +5 -0
  41. package/dist/types/src/components/deprecated/FormInput.d.ts.map +1 -1
  42. package/dist/types/src/hooks/useForm.d.ts +9 -10
  43. package/dist/types/src/hooks/useForm.d.ts.map +1 -1
  44. package/dist/types/src/translations.d.ts +6 -0
  45. package/dist/types/src/translations.d.ts.map +1 -1
  46. package/dist/types/tsconfig.tsbuildinfo +1 -1
  47. package/package.json +25 -24
  48. package/src/components/FieldEditor/FieldEditor.stories.tsx +1 -1
  49. package/src/components/FieldEditor/FieldEditor.tsx +7 -6
  50. package/src/components/Form/ArrayField.tsx +111 -0
  51. package/src/components/Form/Defaults.tsx +18 -33
  52. package/src/components/Form/Form.stories.tsx +15 -5
  53. package/src/components/Form/Form.tsx +47 -355
  54. package/src/components/Form/FormActions.tsx +41 -0
  55. package/src/components/Form/FormContent.tsx +173 -0
  56. package/src/components/Form/FormContext.tsx +48 -0
  57. package/src/components/Form/Input.tsx +5 -6
  58. package/src/components/Form/custom/GeoPointInput.tsx +6 -15
  59. package/src/components/Form/custom/SelectOptionsInput.stories.tsx +58 -0
  60. package/src/components/Form/custom/SelectOptionsInput.tsx +192 -0
  61. package/src/components/Form/custom/TupleInput.tsx +5 -9
  62. package/src/components/Form/custom/index.ts +1 -0
  63. package/src/components/Form/factory.tsx +2 -5
  64. package/src/components/ViewEditor/ViewEditor.tsx +5 -2
  65. package/src/components/deprecated/FormInput.tsx +20 -7
  66. package/src/hooks/useForm.ts +54 -47
  67. package/src/translations.ts +8 -0
@@ -2,10 +2,10 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { type FocusEvent, useCallback, useEffect, useMemo, useState } from 'react';
5
+ import { useCallback, useEffect, useMemo, useState } from 'react';
6
6
 
7
- import { type BaseObject, type PropertyKey, getValue, setValue } from '@dxos/echo-schema';
8
- import { type SimpleType, type S, type JsonPath, AST } from '@dxos/effect';
7
+ import { type BaseObject, getValue, setValue } from '@dxos/echo-schema';
8
+ import { AST, type S, type SimpleType, type JsonPath, createJsonPath, fromEffectValidationPath } from '@dxos/effect';
9
9
  import { invariant } from '@dxos/invariant';
10
10
  import { log } from '@dxos/log';
11
11
  import { validateSchema, type ValidationError } from '@dxos/schema';
@@ -20,9 +20,9 @@ export type FormHandler<T extends BaseObject> = {
20
20
  //
21
21
 
22
22
  values: Partial<T>;
23
- errors: Record<PropertyKey<T>, string>;
24
- touched: Record<PropertyKey<T>, boolean>;
25
- changed: Record<PropertyKey<T>, boolean>;
23
+ errors: Record<JsonPath, string>;
24
+ touched: Record<JsonPath, boolean>;
25
+ changed: Record<JsonPath, boolean>;
26
26
  canSave: boolean;
27
27
  handleSave: () => void;
28
28
 
@@ -30,10 +30,10 @@ export type FormHandler<T extends BaseObject> = {
30
30
  // Form input component helpers.
31
31
  //
32
32
 
33
- getStatus: (property: PropertyKey<T>) => { status?: 'error'; error?: string };
34
- getValue: <V>(property: PropertyKey<T>) => V | undefined;
35
- onValueChange: <V>(property: PropertyKey<T>, type: SimpleType, value: V) => void;
36
- onBlur: (event: FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
33
+ getStatus: (path: string | (string | number)[]) => { status?: 'error'; error?: string };
34
+ getValue: <V>(path: (string | number)[]) => V | undefined;
35
+ onValueChange: <V>(path: (string | number)[], type: SimpleType, value: V) => void;
36
+ onTouched: (path: (string | number)[]) => void;
37
37
  };
38
38
 
39
39
  /**
@@ -95,16 +95,15 @@ export const useForm = <T extends BaseObject>({
95
95
  setValues(initialValues);
96
96
  }, [initialValues]);
97
97
 
98
- const [touched, setTouched] = useState<Record<PropertyKey<T>, boolean>>(createKeySet(initialValues, false));
99
- const [changed, setChanged] = useState<Record<PropertyKey<T>, boolean>>(createKeySet(initialValues, false));
100
- const [errors, setErrors] = useState<Record<PropertyKey<T>, string>>({} as Record<PropertyKey<T>, string>);
98
+ const [touched, setTouched] = useState<Record<JsonPath, boolean>>(createKeySet(initialValues, false));
99
+ const [changed, setChanged] = useState<Record<JsonPath, boolean>>(createKeySet(initialValues, false));
100
+ const [errors, setErrors] = useState<Record<JsonPath, string>>({});
101
101
  const [saving, setSaving] = useState(false);
102
102
 
103
103
  //
104
104
  // Validation.
105
105
  //
106
106
 
107
- // TODO(burdon): Validate each property separately.
108
107
  const validate = useCallback(
109
108
  (values: Partial<T>): values is T => {
110
109
  let errors: ValidationError[] = validateSchema(schema, values) ?? [];
@@ -137,8 +136,15 @@ export const useForm = <T extends BaseObject>({
137
136
  const canSave = useMemo(
138
137
  () =>
139
138
  !saving &&
140
- Object.keys(values).every((property) => touched[property as JsonPath] === false || !errors[property as JsonPath]),
141
- [values, touched, errors, saving],
139
+ // Check if any error paths that are touched have errors
140
+ !Object.entries(touched).some(
141
+ ([path, isTouched]) =>
142
+ isTouched &&
143
+ Object.keys(errors).some(
144
+ (errorPath) => errorPath === path || errorPath.startsWith(`${path}.`) || errorPath.startsWith(`${path}[`),
145
+ ),
146
+ ),
147
+ [touched, errors, saving],
142
148
  );
143
149
 
144
150
  const handleSave = useCallback(async () => {
@@ -157,19 +163,27 @@ export const useForm = <T extends BaseObject>({
157
163
  //
158
164
 
159
165
  const getStatus = useCallback<FormHandler<T>['getStatus']>(
160
- (property: PropertyKey<T>) => ({
161
- status: errors[property] ? 'error' : undefined,
162
- error: errors[property] ? errors[property] : undefined,
163
- }),
164
- [touched, errors],
166
+ (path) => {
167
+ const jsonPath = Array.isArray(path) ? createJsonPath(path) : path;
168
+ const matchingError = Object.entries(errors).find(
169
+ ([errorPath]) =>
170
+ errorPath === jsonPath || errorPath.startsWith(`${jsonPath}.`) || errorPath.startsWith(`${jsonPath}[`),
171
+ );
172
+
173
+ return {
174
+ status: matchingError ? 'error' : undefined,
175
+ error: matchingError ? matchingError[1] : undefined,
176
+ };
177
+ },
178
+ [errors],
165
179
  );
166
180
 
167
- // TODO(burdon): Use path to extract hierarchical value.
168
- const getFormValue = <V>(property: PropertyKey<T>): V | undefined => {
169
- return getValue(values, property as any as JsonPath);
181
+ const getFormValue = <V>(path: (string | number)[]): V | undefined => {
182
+ return getValue(values, createJsonPath(path));
170
183
  };
171
184
 
172
- const onValueChange = (property: PropertyKey<T>, type: SimpleType, value: any) => {
185
+ const onValueChange = (path: (string | number)[], type: SimpleType, value: any) => {
186
+ const jsonPath = createJsonPath(path);
173
187
  let parsedValue = value;
174
188
  try {
175
189
  if (type === 'number') {
@@ -180,9 +194,9 @@ export const useForm = <T extends BaseObject>({
180
194
  parsedValue = undefined;
181
195
  }
182
196
 
183
- const newValues = { ...setValue(values, property as any as JsonPath, parsedValue) };
197
+ const newValues = { ...setValue(values, jsonPath, parsedValue) };
184
198
  setValues(newValues);
185
- setChanged((prev) => ({ ...prev, [property]: true }));
199
+ setChanged((prev) => ({ ...prev, [jsonPath]: true }));
186
200
  onValuesChanged?.(newValues);
187
201
 
188
202
  const isValid = validate(newValues);
@@ -191,19 +205,10 @@ export const useForm = <T extends BaseObject>({
191
205
  }
192
206
  };
193
207
 
194
- // TODO(burdon): This is a leaky abstraction: the hook ideally shouldn't get involved in UX.
195
- const onBlur = useCallback(
196
- (event: FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => {
197
- const { name } = event.target;
198
- setTouched((touched) => ({ ...touched, [name]: true }));
199
-
200
- // TODO(Zan): This should be configurable behavior.
201
- if (event.relatedTarget?.getAttribute('type') === 'submit') {
202
- // NOTE: We do this here instead of onSave because the blur event is triggered before the submit event
203
- // and results in the submit button being disabled when the form is invalid.
204
- setTouched(createKeySet(values, true));
205
- }
206
-
208
+ const onTouched = useCallback(
209
+ (path: (string | number)[]) => {
210
+ const jsonPath = createJsonPath(path);
211
+ setTouched((touched) => ({ ...touched, [jsonPath]: true }));
207
212
  validate(values);
208
213
  },
209
214
  [validate, values],
@@ -222,23 +227,25 @@ export const useForm = <T extends BaseObject>({
222
227
  getStatus,
223
228
  getValue: getFormValue,
224
229
  onValueChange,
225
- onBlur,
230
+ onTouched,
226
231
  } satisfies FormHandler<T>;
227
232
  };
228
233
 
229
- const createKeySet = <T extends BaseObject, V>(obj: T, value: V): Record<PropertyKey<T>, V> => {
234
+ const createKeySet = <T extends BaseObject, V>(obj: T, value: V): Record<JsonPath, V> => {
230
235
  invariant(obj);
231
- return Object.keys(obj).reduce((acc, key) => ({ ...acc, [key]: value }), {} as Record<PropertyKey<T>, V>);
236
+ return Object.keys(obj).reduce((acc, key) => ({ ...acc, [key]: value }), {} as Record<JsonPath, V>);
232
237
  };
233
238
 
234
- const flatMap = <T extends BaseObject>(errors: ValidationError[]) => {
239
+ const flatMap = (errors: ValidationError[]) => {
235
240
  return errors.reduce(
236
241
  (result, { path, message }) => {
237
- if (!(path in result)) {
238
- result[path as PropertyKey<T>] = message;
242
+ // Convert the validation error path format to our JsonPath format
243
+ const jsonPath = fromEffectValidationPath(path);
244
+ if (!(jsonPath in result)) {
245
+ result[jsonPath] = message;
239
246
  }
240
247
  return result;
241
248
  },
242
- {} as Record<PropertyKey<T>, string>,
249
+ {} as Record<JsonPath, string>,
243
250
  );
244
251
  };
@@ -42,6 +42,7 @@ export default [
42
42
  'format ref': 'Reference',
43
43
  'format regex': 'RegExp',
44
44
  'format string': 'String',
45
+ 'format single-select': 'Select',
45
46
  'format text': 'Long text',
46
47
  'format time': 'Time',
47
48
  'format timestamp': 'Timestamp',
@@ -53,6 +54,13 @@ export default [
53
54
  // Placeholders
54
55
  'placeholder latitude': 'Latitude',
55
56
  'placeholder longitude': 'Longitude',
57
+
58
+ // SelectOptionsInput
59
+ 'select option label': 'Label',
60
+ 'select option label placeholder': 'Option label',
61
+ 'select option color': 'Color',
62
+ 'select option delete': 'Delete',
63
+ 'select option add': 'Add option',
56
64
  },
57
65
  },
58
66
  },