@dxos/react-ui-form 0.7.4 → 0.7.5-main.9d26e3a

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 (47) hide show
  1. package/dist/lib/browser/index.mjs +209 -184
  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 +302 -276
  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 +209 -184
  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 +2 -2
  11. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  12. package/dist/types/src/components/Form/Form.d.ts +1 -1
  13. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  14. package/dist/types/src/components/Form/Form.stories.d.ts +5 -0
  15. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  16. package/dist/types/src/components/Form/Input.d.ts +3 -3
  17. package/dist/types/src/components/Form/Input.d.ts.map +1 -1
  18. package/dist/types/src/components/Form/{Custom.d.ts → custom/GeoPointInput.d.ts} +3 -3
  19. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +1 -0
  20. package/dist/types/src/components/Form/custom/TupleInput.d.ts +7 -0
  21. package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +1 -0
  22. package/dist/types/src/components/Form/custom/index.d.ts +3 -0
  23. package/dist/types/src/components/Form/custom/index.d.ts.map +1 -0
  24. package/dist/types/src/components/Form/factory.d.ts.map +1 -1
  25. package/dist/types/src/components/Form/index.d.ts +2 -2
  26. package/dist/types/src/components/Form/index.d.ts.map +1 -1
  27. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +4 -4
  28. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  29. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  30. package/dist/types/src/hooks/useForm.d.ts +9 -10
  31. package/dist/types/src/hooks/useForm.d.ts.map +1 -1
  32. package/dist/types/tsconfig.tsbuildinfo +1 -0
  33. package/package.json +24 -24
  34. package/src/components/FieldEditor/FieldEditor.stories.tsx +2 -2
  35. package/src/components/FieldEditor/FieldEditor.tsx +21 -13
  36. package/src/components/Form/Form.stories.tsx +33 -1
  37. package/src/components/Form/Form.tsx +3 -4
  38. package/src/components/Form/Input.tsx +3 -3
  39. package/src/components/Form/{Custom.tsx → custom/GeoPointInput.tsx} +4 -16
  40. package/src/components/Form/custom/TupleInput.tsx +53 -0
  41. package/src/components/Form/custom/index.ts +6 -0
  42. package/src/components/Form/factory.tsx +2 -2
  43. package/src/components/Form/index.ts +3 -2
  44. package/src/components/ViewEditor/ViewEditor.stories.tsx +6 -5
  45. package/src/components/ViewEditor/ViewEditor.tsx +4 -4
  46. package/src/hooks/useForm.ts +32 -52
  47. package/dist/types/src/components/Form/Custom.d.ts.map +0 -1
@@ -5,11 +5,12 @@
5
5
  import '@dxos-theme';
6
6
 
7
7
  import { type Meta, type StoryObj } from '@storybook/react';
8
- import React, { useCallback, useEffect, useState } from 'react';
8
+ import React, { useCallback, useState } from 'react';
9
9
 
10
- import { Format, type MutableSchema, S, toJsonSchema, TypedObject } from '@dxos/echo-schema';
10
+ import { Format, type EchoSchema, S, toJsonSchema, TypedObject } from '@dxos/echo-schema';
11
11
  import { useSpace } from '@dxos/react-client/echo';
12
12
  import { withClientProvider } from '@dxos/react-client/testing';
13
+ import { useAsyncEffect } from '@dxos/react-ui';
13
14
  import { type ViewType, ViewProjection, createView } from '@dxos/schema';
14
15
  import { withTheme, withLayout } from '@dxos/storybook-utils';
15
16
 
@@ -19,10 +20,10 @@ import { TestLayout, TestPanel } from '../testing';
19
20
 
20
21
  const DefaultStory = () => {
21
22
  const space = useSpace();
22
- const [schema, setSchema] = useState<MutableSchema>();
23
+ const [schema, setSchema] = useState<EchoSchema>();
23
24
  const [view, setView] = useState<ViewType>();
24
25
  const [projection, setProjection] = useState<ViewProjection>();
25
- useEffect(() => {
26
+ useAsyncEffect(async () => {
26
27
  if (space) {
27
28
  class TestSchema extends TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
28
29
  name: S.String,
@@ -30,7 +31,7 @@ const DefaultStory = () => {
30
31
  salary: Format.Currency(),
31
32
  }) {}
32
33
 
33
- const schema = space.db.schemaRegistry.addSchema(TestSchema);
34
+ const [schema] = await space.db.schemaRegistry.register([TestSchema]);
34
35
  const view = createView({ name: 'Test', typename: schema.typename, jsonSchema: toJsonSchema(TestSchema) });
35
36
  const projection = new ViewProjection(schema, view);
36
37
 
@@ -4,8 +4,8 @@
4
4
 
5
5
  import React, { useCallback, useMemo, useState } from 'react';
6
6
 
7
- import { type SchemaResolver } from '@dxos/echo-db';
8
- import { AST, Format, type MutableSchema, S } from '@dxos/echo-schema';
7
+ import { type SchemaRegistry } from '@dxos/echo-db';
8
+ import { AST, Format, type EchoSchema, S } from '@dxos/echo-schema';
9
9
  import { IconButton, type ThemedClassName, useTranslation } from '@dxos/react-ui';
10
10
  import { List } from '@dxos/react-ui-list';
11
11
  import { ghostHover, inputTextLabel, mx } from '@dxos/react-ui-theme';
@@ -30,9 +30,9 @@ const ViewMetaSchema = S.Struct({
30
30
  type ViewMetaType = S.Schema.Type<typeof ViewMetaSchema>;
31
31
 
32
32
  export type ViewEditorProps = ThemedClassName<{
33
- schema: MutableSchema;
33
+ schema: EchoSchema;
34
34
  view: ViewType;
35
- registry?: SchemaResolver;
35
+ registry?: SchemaRegistry;
36
36
  readonly?: boolean;
37
37
  showHeading?: boolean;
38
38
  onDelete: (fieldId: string) => void;
@@ -4,16 +4,13 @@
4
4
 
5
5
  import { type FocusEvent, useCallback, useEffect, useMemo, useState } from 'react';
6
6
 
7
- import { type BaseObject, type PropertyKey } from '@dxos/echo-schema';
8
- import { type SimpleType, type S } from '@dxos/effect';
7
+ import { type BaseObject, type PropertyKey, getValue, setValue } from '@dxos/echo-schema';
8
+ import { type SimpleType, type S, type JsonPath } 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';
12
12
  import { type MaybePromise } from '@dxos/util';
13
13
 
14
- // TODO(ZaymonFC): Where should this live?
15
- export type FormPath<T extends object = {}> = PropertyKey<T> | `${PropertyKey<T>}.${number}`;
16
-
17
14
  /**
18
15
  * Return type from `useForm` hook.
19
16
  */
@@ -22,10 +19,10 @@ export type FormHandler<T extends BaseObject> = {
22
19
  // Form state management.
23
20
  //
24
21
 
25
- values: T;
26
- errors: Record<FormPath<T>, string>;
27
- touched: Record<FormPath<T>, boolean>;
28
- changed: Record<FormPath<T>, boolean>;
22
+ values: Partial<T>;
23
+ errors: Record<PropertyKey<T>, string>;
24
+ touched: Record<PropertyKey<T>, boolean>;
25
+ changed: Record<PropertyKey<T>, boolean>;
29
26
  canSave: boolean;
30
27
  handleSave: () => void;
31
28
 
@@ -33,9 +30,9 @@ export type FormHandler<T extends BaseObject> = {
33
30
  // Form input component helpers.
34
31
  //
35
32
 
36
- getStatus: (property: FormPath<T>) => { status?: 'error'; error?: string };
37
- getValue: <V>(property: FormPath<T>) => V;
38
- onValueChange: <V>(property: FormPath<T>, type: SimpleType, value: V) => void;
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;
39
36
  onBlur: (event: FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
40
37
  };
41
38
 
@@ -52,15 +49,14 @@ export interface FormOptions<T extends BaseObject> {
52
49
  /**
53
50
  * Initial values (which may not pass validation).
54
51
  */
55
- // TODO(burdon): Should be partial?
56
- initialValues: T;
52
+ initialValues: Partial<T>;
57
53
 
58
54
  /**
59
55
  * Callback for value changes. Note: This is called even when values are invalid.
60
56
  * Sometimes the parent component may want to know about changes even if the form is
61
57
  * in an invalid state.
62
58
  */
63
- onValuesChanged?: (values: T) => void;
59
+ onValuesChanged?: (values: Partial<T>) => void;
64
60
 
65
61
  /**
66
62
  * Custom validation function that runs only after schema validation passes.
@@ -93,14 +89,14 @@ export const useForm = <T extends BaseObject>({
93
89
  onValid,
94
90
  onSave,
95
91
  }: FormOptions<T>): FormHandler<T> => {
96
- const [values, setValues] = useState<T>(initialValues);
92
+ const [values, setValues] = useState<Partial<T>>(initialValues);
97
93
  useEffect(() => {
98
94
  setValues(initialValues);
99
95
  }, [initialValues]);
100
96
 
101
- const [touched, setTouched] = useState<Record<FormPath<T>, boolean>>(createKeySet(initialValues, false));
102
- const [changed, setChanged] = useState<Record<FormPath<T>, boolean>>(createKeySet(initialValues, false));
103
- const [errors, setErrors] = useState<Record<FormPath<T>, string>>({} as Record<FormPath<T>, string>);
97
+ const [touched, setTouched] = useState<Record<PropertyKey<T>, boolean>>(createKeySet(initialValues, false));
98
+ const [changed, setChanged] = useState<Record<PropertyKey<T>, boolean>>(createKeySet(initialValues, false));
99
+ const [errors, setErrors] = useState<Record<PropertyKey<T>, string>>({} as Record<PropertyKey<T>, string>);
104
100
  const [saving, setSaving] = useState(false);
105
101
 
106
102
  //
@@ -109,14 +105,16 @@ export const useForm = <T extends BaseObject>({
109
105
 
110
106
  // TODO(burdon): Validate each property separately.
111
107
  const validate = useCallback(
112
- (values: T) => {
108
+ (values: Partial<T>): values is T => {
113
109
  let errors: ValidationError[] = validateSchema(schema, values) ?? [];
114
110
  if (errors.length === 0 && onValidate) {
115
- errors = onValidate(values) ?? [];
111
+ const validatedValues = values as T;
112
+ errors = onValidate(validatedValues) ?? [];
116
113
  }
117
114
 
118
115
  setErrors(flatMap(errors));
119
- return errors.length === 0;
116
+ const valid = errors.length === 0;
117
+ return valid;
120
118
  },
121
119
  [schema, onValidate],
122
120
  );
@@ -138,9 +136,7 @@ export const useForm = <T extends BaseObject>({
138
136
  const canSave = useMemo(
139
137
  () =>
140
138
  !saving &&
141
- Object.keys(values).every(
142
- (property) => touched[property as PropertyKey<T>] === false || !errors[property as PropertyKey<T>],
143
- ),
139
+ Object.keys(values).every((property) => touched[property as JsonPath] === false || !errors[property as JsonPath]),
144
140
  [values, touched, errors, saving],
145
141
  );
146
142
 
@@ -153,14 +149,14 @@ export const useForm = <T extends BaseObject>({
153
149
  setSaving(false);
154
150
  }
155
151
  }
156
- }, [values, validate, onSave]);
152
+ }, [values, validate, onSave, changed]);
157
153
 
158
154
  //
159
155
  // Fields.
160
156
  //
161
157
 
162
158
  const getStatus = useCallback<FormHandler<T>['getStatus']>(
163
- (property: FormPath<T>) => ({
159
+ (property: PropertyKey<T>) => ({
164
160
  status: errors[property] ? 'error' : undefined,
165
161
  error: errors[property] ? errors[property] : undefined,
166
162
  }),
@@ -168,12 +164,11 @@ export const useForm = <T extends BaseObject>({
168
164
  );
169
165
 
170
166
  // TODO(burdon): Use path to extract hierarchical value.
171
- const getValue = <V>(property: FormPath<T>): V => {
172
- return getByPath(values, property);
167
+ const getFormValue = <V>(property: PropertyKey<T>): V | undefined => {
168
+ return getValue(values, property as any as JsonPath);
173
169
  };
174
170
 
175
- // TODO(burdon): Use path to set hierarchical value.
176
- const onValueChange = (property: FormPath<T>, type: SimpleType, value: any) => {
171
+ const onValueChange = (property: PropertyKey<T>, type: SimpleType, value: any) => {
177
172
  let parsedValue = value;
178
173
  try {
179
174
  if (type === 'number') {
@@ -184,7 +179,7 @@ export const useForm = <T extends BaseObject>({
184
179
  parsedValue = undefined;
185
180
  }
186
181
 
187
- const newValues = setByPath(values, property, parsedValue);
182
+ const newValues = { ...setValue(values, property as any as JsonPath, parsedValue) };
188
183
  setValues(newValues);
189
184
  setChanged((prev) => ({ ...prev, [property]: true }));
190
185
  onValuesChanged?.(newValues);
@@ -224,40 +219,25 @@ export const useForm = <T extends BaseObject>({
224
219
 
225
220
  // Field utils.
226
221
  getStatus,
227
- getValue,
222
+ getValue: getFormValue,
228
223
  onValueChange,
229
224
  onBlur,
230
225
  } satisfies FormHandler<T>;
231
226
  };
232
227
 
233
- const createKeySet = <T extends BaseObject, V>(obj: T, value: V): Record<FormPath<T>, V> => {
228
+ const createKeySet = <T extends BaseObject, V>(obj: T, value: V): Record<PropertyKey<T>, V> => {
234
229
  invariant(obj);
235
- return Object.keys(obj).reduce((acc, key) => ({ ...acc, [key]: value }), {} as Record<FormPath<T>, V>);
230
+ return Object.keys(obj).reduce((acc, key) => ({ ...acc, [key]: value }), {} as Record<PropertyKey<T>, V>);
236
231
  };
237
232
 
238
233
  const flatMap = <T extends BaseObject>(errors: ValidationError[]) => {
239
234
  return errors.reduce(
240
235
  (result, { path, message }) => {
241
236
  if (!(path in result)) {
242
- result[path as FormPath<T>] = message;
237
+ result[path as PropertyKey<T>] = message;
243
238
  }
244
239
  return result;
245
240
  },
246
- {} as Record<FormPath<T>, string>,
241
+ {} as Record<PropertyKey<T>, string>,
247
242
  );
248
243
  };
249
-
250
- // TODO(ZaymonFC): Move to util?
251
- const getByPath = <T extends object, V>(obj: T, path: FormPath<T>): V => {
252
- const [key, index] = path.split('.');
253
- return index === undefined ? (obj[key as keyof T] as V) : ((obj[key as keyof T] as any[])[parseInt(index)] as V);
254
- };
255
-
256
- const setByPath = <T extends object>(obj: T, path: FormPath<T>, value: any): T => {
257
- const [key, index] = path.split('.');
258
- const newValue =
259
- index === undefined
260
- ? value
261
- : [...(obj[key as keyof T] as any[])].map((v, i) => (i === parseInt(index) ? value : v));
262
- return { ...obj, [key]: newValue };
263
- };
@@ -1 +0,0 @@
1
- {"version":3,"file":"Custom.d.ts","sourceRoot":"","sources":["../../../../../src/components/Form/Custom.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,KAAK,UAAU,EAAiB,MAAM,mBAAmB,CAAC;AAGnE,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;AAOvD,eAAO,MAAM,aAAa,GAAI,CAAC,SAAS,UAAU,oFAS/C,UAAU,CAAC,CAAC,CAAC,sBA8Bf,CAAC"}