@dxos/react-ui-form 0.8.3-staging.0fa589b → 0.8.4-main.28f8d3d

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 (77) hide show
  1. package/dist/lib/browser/index.mjs +222 -209
  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 +222 -209
  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.stories.d.ts +1 -1
  8. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +1 -1
  9. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +4 -4
  10. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  11. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +3 -3
  12. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
  13. package/dist/types/src/components/Form/ArrayField.d.ts.map +1 -1
  14. package/dist/types/src/components/Form/Defaults.d.ts.map +1 -1
  15. package/dist/types/src/components/Form/Form.d.ts +2 -2
  16. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  17. package/dist/types/src/components/Form/Form.stories.d.ts +1 -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.map +1 -1
  20. package/dist/types/src/components/Form/FormContent.d.ts +1 -1
  21. package/dist/types/src/components/Form/FormContent.d.ts.map +1 -1
  22. package/dist/types/src/components/Form/FormContext.d.ts +1 -1
  23. package/dist/types/src/components/Form/FormContext.d.ts.map +1 -1
  24. package/dist/types/src/components/Form/RefField.d.ts +1 -1
  25. package/dist/types/src/components/Form/RefField.d.ts.map +1 -1
  26. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +1 -1
  27. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +1 -1
  28. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +1 -1
  29. package/dist/types/src/components/Form/factory.d.ts.map +1 -1
  30. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +2 -2
  31. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  32. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +4 -4
  33. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  34. package/dist/types/src/components/deprecated/Form.d.ts +3 -3
  35. package/dist/types/src/components/deprecated/Form.d.ts.map +1 -1
  36. package/dist/types/src/components/deprecated/Form.stories.d.ts +1 -1
  37. package/dist/types/src/components/deprecated/Form.stories.d.ts.map +1 -1
  38. package/dist/types/src/hooks/useForm.d.ts +1 -1
  39. package/dist/types/src/hooks/useForm.d.ts.map +1 -1
  40. package/dist/types/src/index.d.ts +1 -1
  41. package/dist/types/src/index.d.ts.map +1 -1
  42. package/dist/types/src/translations.d.ts +57 -57
  43. package/dist/types/src/translations.d.ts.map +1 -1
  44. package/dist/types/tsconfig.tsbuildinfo +1 -1
  45. package/package.json +31 -32
  46. package/src/components/ControlSection/ControlSection.stories.tsx +3 -2
  47. package/src/components/FieldEditor/FieldEditor.stories.tsx +17 -14
  48. package/src/components/FieldEditor/FieldEditor.test.tsx +14 -13
  49. package/src/components/FieldEditor/FieldEditor.tsx +11 -11
  50. package/src/components/Form/ArrayField.tsx +8 -7
  51. package/src/components/Form/Defaults.tsx +2 -1
  52. package/src/components/Form/Form.stories.tsx +5 -4
  53. package/src/components/Form/Form.tsx +4 -3
  54. package/src/components/Form/FormActions.tsx +2 -1
  55. package/src/components/Form/FormContent.tsx +6 -5
  56. package/src/components/Form/FormContext.tsx +5 -5
  57. package/src/components/Form/RefField.tsx +7 -6
  58. package/src/components/Form/custom/GeoPointInput.tsx +2 -2
  59. package/src/components/Form/custom/SelectOptionsInput.stories.tsx +5 -4
  60. package/src/components/Form/custom/SelectOptionsInput.tsx +28 -29
  61. package/src/components/Form/factory.tsx +1 -1
  62. package/src/components/ViewEditor/ViewEditor.stories.tsx +21 -14
  63. package/src/components/ViewEditor/ViewEditor.test.tsx +41 -38
  64. package/src/components/ViewEditor/ViewEditor.tsx +81 -81
  65. package/src/components/deprecated/Form.stories.tsx +8 -7
  66. package/src/components/deprecated/Form.tsx +6 -6
  67. package/src/components/testing.tsx +1 -1
  68. package/src/hooks/useForm.ts +5 -3
  69. package/src/hooks/useRefQueryLookupHandler.ts +1 -1
  70. package/src/index.ts +1 -1
  71. package/src/translations.ts +7 -4
  72. package/dist/lib/node/index.cjs +0 -2416
  73. package/dist/lib/node/index.cjs.map +0 -7
  74. package/dist/lib/node/meta.json +0 -1
  75. package/dist/types/src/components/ViewEditor/Test.stories.d.ts +0 -10
  76. package/dist/types/src/components/ViewEditor/Test.stories.d.ts.map +0 -1
  77. package/src/components/ViewEditor/Test.stories.tsx +0 -45
@@ -11,21 +11,19 @@ import { invariant } from '@dxos/invariant';
11
11
  import { Callout, IconButton, type ThemedClassName, useTranslation } from '@dxos/react-ui';
12
12
  import { List } from '@dxos/react-ui-list';
13
13
  import { cardSpacing } from '@dxos/react-ui-stack';
14
- import { ghostHover, inputTextLabel, mx } from '@dxos/react-ui-theme';
15
- import { FieldSchema, type FieldType, type ViewType, ViewProjection, VIEW_FIELD_LIMIT } from '@dxos/schema';
14
+ import { inputTextLabel, mx, subtleHover } from '@dxos/react-ui-theme';
15
+ import { type DataType, FieldSchema, type FieldType, ProjectionModel, VIEW_FIELD_LIMIT } from '@dxos/schema';
16
16
 
17
17
  import { translationKey } from '../../translations';
18
18
  import { FieldEditor } from '../FieldEditor';
19
19
  import { Form, type FormProps } from '../Form';
20
20
 
21
- const grid = 'grid grid-cols-[32px_1fr_32px_32px] min-bs-[2.5rem]';
21
+ const listGrid = 'grid grid-cols-[min-content_1fr_min-content_min-content_min-content]';
22
+ const listItemGrid = 'grid grid-cols-subgrid col-span-5';
22
23
 
23
24
  const ViewMetaSchema = Schema.Struct({
24
- name: Schema.String.annotations({
25
- title: 'View',
26
- }),
27
25
  typename: Format.URL.annotations({
28
- title: 'Typename',
26
+ title: 'Record type',
29
27
  }),
30
28
  }).pipe(Schema.mutable);
31
29
 
@@ -34,7 +32,7 @@ type ViewMetaType = Schema.Schema.Type<typeof ViewMetaSchema>;
34
32
  export type ViewEditorProps = ThemedClassName<
35
33
  {
36
34
  schema: Schema.Schema.AnyNoContext;
37
- view: ViewType;
35
+ view: DataType.View;
38
36
  registry?: SchemaRegistry;
39
37
  readonly?: boolean;
40
38
  showHeading?: boolean;
@@ -61,27 +59,23 @@ export const ViewEditor = ({
61
59
  const projection = useMemo(() => {
62
60
  // Use reactive and mutable version of json schema when schema is mutable.
63
61
  const jsonSchema = schema instanceof EchoSchema ? schema.jsonSchema : toJsonSchema(schema);
64
- return new ViewProjection(jsonSchema, view);
65
- }, [schema, view]);
66
- const [field, setField] = useState<FieldType>();
62
+ return new ProjectionModel(jsonSchema, view.projection);
63
+ }, [schema, view.projection]);
64
+ const [expandedField, setExpandedField] = useState<FieldType['id']>();
67
65
  const readonly = _readonly || !isMutable(schema);
68
66
 
69
67
  // TODO(burdon): Should be reactive.
70
68
  const viewValues = useMemo(() => {
71
69
  return {
72
- name: view.name,
73
70
  // TODO(burdon): Need to warn user of possible consequences of editing.
74
71
  // TODO(burdon): Settings should have domain name owned by user.
75
72
  typename: view.query.typename,
76
73
  };
77
- }, [view]);
74
+ }, [view.query.typename]);
78
75
 
79
- const handleSelect = useCallback(
76
+ const handleToggleField = useCallback(
80
77
  (field: FieldType) => {
81
- if (readonly) {
82
- return;
83
- }
84
- setField((f) => (f === field ? undefined : field));
78
+ setExpandedField((prevExpandedFieldId) => (prevExpandedFieldId === field.id ? undefined : field.id));
85
79
  },
86
80
  [readonly],
87
81
  );
@@ -91,56 +85,52 @@ export const ViewEditor = ({
91
85
  const handleAdd = useCallback(() => {
92
86
  invariant(!readonly);
93
87
  const field = projection.createFieldProjection();
94
- setField(field);
95
- }, [schema, view, readonly]);
88
+ setExpandedField(field.id);
89
+ }, [schema, projection, readonly]);
96
90
 
97
91
  const handleUpdate = useCallback(
98
- ({ name, typename }: ViewMetaType) => {
92
+ ({ typename }: ViewMetaType) => {
99
93
  invariant(!readonly);
100
94
  requestAnimationFrame(() => {
101
- if (view.name !== name) {
102
- view.name = name;
103
- }
104
-
105
95
  if (view.query.typename !== typename && !readonly) {
106
96
  onTypenameChanged?.(typename);
107
97
  }
108
98
  });
109
99
  },
110
- [schema, view, onTypenameChanged, readonly],
100
+ [view.query.typename, onTypenameChanged, readonly],
111
101
  );
112
102
 
113
103
  const handleDelete = useCallback(
114
104
  (fieldId: string) => {
115
105
  invariant(!readonly);
116
- if (fieldId === field?.id) {
117
- setField(undefined);
106
+ if (fieldId === expandedField) {
107
+ setExpandedField(undefined);
118
108
  }
119
109
 
120
110
  onDelete?.(fieldId);
121
111
  },
122
- [schema, field, onDelete, readonly],
112
+ [expandedField, onDelete, readonly],
123
113
  );
124
114
 
125
115
  const handleMove = useCallback(
126
116
  (fromIndex: number, toIndex: number) => {
127
117
  invariant(!readonly);
128
118
  // NOTE(ZaymonFC): Using arrayMove here causes a race condition with the kanban model.
129
- const fields = [...view.fields];
119
+ const fields = [...view.projection.fields];
130
120
  const [moved] = fields.splice(fromIndex, 1);
131
121
  fields.splice(toIndex, 0, moved);
132
- view.fields = fields;
122
+ view.projection.fields = fields;
133
123
  },
134
- [schema, view.fields, readonly],
124
+ [view.projection.fields, readonly],
135
125
  );
136
126
 
137
- const handleClose = useCallback(() => setField(undefined), []);
127
+ const handleClose = useCallback(() => setExpandedField(undefined), []);
138
128
 
139
129
  const hiddenProperties = projection.getHiddenProperties();
140
130
 
141
131
  const handleHide = useCallback(
142
132
  (fieldId: string) => {
143
- setField(undefined);
133
+ setExpandedField(undefined);
144
134
  projection.hideFieldProjection(fieldId);
145
135
  },
146
136
  [projection],
@@ -148,7 +138,7 @@ export const ViewEditor = ({
148
138
 
149
139
  const handleShow = useCallback(
150
140
  (property: string) => {
151
- setField(undefined);
141
+ setExpandedField(undefined);
152
142
  projection.showFieldProjection(property as JsonProp);
153
143
  },
154
144
  [projection],
@@ -157,11 +147,12 @@ export const ViewEditor = ({
157
147
  return (
158
148
  <div role='none' className={mx(classNames)}>
159
149
  {readonly && (
160
- <div role='none' className={mx('is-full', outerSpacing && 'plb-cardSpacingBlock pli-cardSpacingInline')}>
161
- <Callout.Root valence='info'>
162
- <Callout.Title>{t('system schema description')}</Callout.Title>
163
- </Callout.Root>
164
- </div>
150
+ <Callout.Root
151
+ valence='info'
152
+ classNames={['is-full mlb-cardSpacingBlock', outerSpacing && 'mli-cardSpacingInline']}
153
+ >
154
+ <Callout.Title>{t('system schema description')}</Callout.Title>
155
+ </Callout.Root>
165
156
  )}
166
157
 
167
158
  {/* TODO(burdon): Is the form read-only or just the schema? */}
@@ -176,46 +167,65 @@ export const ViewEditor = ({
176
167
  />
177
168
 
178
169
  <div role='none' className={outerSpacing ? cardSpacing : 'mlb-cardSpacingBlock'}>
179
- <label className={mx(inputTextLabel)}>{t('fields label')}</label>
170
+ <h2 className={mx(inputTextLabel)}>{t('fields label')}</h2>
180
171
 
181
172
  <List.Root<FieldType>
182
- items={view.fields}
173
+ items={view.projection.fields}
183
174
  isItem={Schema.is(FieldSchema)}
184
175
  getId={(field) => field.id}
185
- readonly={readonly}
186
176
  onMove={readonly ? undefined : handleMove}
177
+ readonly={readonly}
187
178
  >
188
179
  {({ items: fields }) => (
189
180
  <>
190
- {showHeading && (
191
- <div role='heading' className={grid}>
192
- <div />
193
- <div className='flex items-center text-sm'>{t('field path label')}</div>
194
- </div>
195
- )}
181
+ {showHeading && <h3 className='text-sm'>{t('field path label')}</h3>}
196
182
 
197
- <div role='list' className='flex flex-col is-full'>
183
+ <div role='list' className={listGrid}>
198
184
  {fields?.map((field) => (
199
185
  <List.Item<FieldType>
200
186
  key={field.id}
201
187
  item={field}
202
- classNames={mx(grid, ghostHover, 'overflow-hidden', !readonly && 'cursor-pointer')}
188
+ classNames={listItemGrid}
189
+ aria-expanded={expandedField === field.id}
203
190
  >
204
- <List.ItemDragHandle />
205
- <List.ItemTitle onClick={() => handleSelect(field)}>{field.path}</List.ItemTitle>
206
- <List.ItemButton
207
- data-testid='hide-field-button'
208
- icon='ph--eye-slash--regular'
209
- // TDOO(burdon): Is this the correct test?
210
- disabled={view.fields.length <= 1}
211
- onClick={() => handleHide(field.id)}
212
- />
213
- {/* TODO(burdon): Remove unless implement undo. */}
214
- {!readonly && (
191
+ <div role='none' className={mx(subtleHover, listItemGrid, 'rounded-sm cursor-pointer min-bs-10')}>
192
+ <List.ItemDragHandle disabled={readonly} />
193
+ <List.ItemTitle onClick={() => handleToggleField(field)}>{field.path}</List.ItemTitle>
194
+ <List.ItemButton
195
+ label={t('hide field label')}
196
+ data-testid='hide-field-button'
197
+ icon='ph--eye-slash--regular'
198
+ autoHide={false}
199
+ // TDOO(burdon): Is this the correct test?
200
+ disabled={readonly || view.projection.fields.length <= 1}
201
+ onClick={() => handleHide(field.id)}
202
+ />
203
+ {/* TODO(burdon): Remove unless implement undo. */}
215
204
  <List.ItemDeleteButton
216
- disabled={view.fields.length <= 1}
205
+ label={t('delete field label')}
206
+ autoHide={false}
207
+ disabled={readonly || view.projection.fields.length <= 1}
217
208
  onClick={() => handleDelete(field.id)}
209
+ data-testid='field.delete'
218
210
  />
211
+ <IconButton
212
+ iconOnly
213
+ variant='ghost'
214
+ label={t('toggle expand label', { ns: 'os' })}
215
+ icon={expandedField === field.id ? 'ph--caret-down--regular' : 'ph--caret-right--regular'}
216
+ onClick={() => handleToggleField(field)}
217
+ />
218
+ </div>
219
+ {expandedField === field.id && (
220
+ <div role='none' className='col-span-5'>
221
+ <FieldEditor
222
+ readonly={readonly}
223
+ projection={projection}
224
+ field={field}
225
+ registry={registry}
226
+ onSave={handleClose}
227
+ />
228
+ </div>
219
229
  )}
220
230
  </List.Item>
221
231
  ))}
@@ -226,7 +236,7 @@ export const ViewEditor = ({
226
236
 
227
237
  {hiddenProperties.length > 0 && (
228
238
  <>
229
- <label className={mx(inputTextLabel)}>{t('hidden fields label')}</label>
239
+ <h2 className={mx(inputTextLabel)}>{t('hidden fields label')}</h2>
230
240
 
231
241
  <List.Root<string>
232
242
  items={hiddenProperties}
@@ -234,16 +244,17 @@ export const ViewEditor = ({
234
244
  getId={(property) => property}
235
245
  >
236
246
  {({ items: properties }) => (
237
- <div role='list' className='flex flex-col is-full'>
247
+ <div role='list' className='grid grid-cols-[1fr_min-content]'>
238
248
  {properties?.map((property) => (
239
249
  <List.Item<string>
240
250
  key={property}
241
251
  item={property}
242
- classNames={mx(grid, ghostHover, !readonly && 'cursor-pointer')}
252
+ classNames={mx('grid grid-cols-subgrid col-span-2 rounded-sm', subtleHover)}
243
253
  >
244
- <List.ItemDragHandle disabled />
245
254
  <List.ItemTitle>{property}</List.ItemTitle>
246
255
  <List.ItemButton
256
+ label={t('show field label')}
257
+ disabled={readonly}
247
258
  icon='ph--eye--regular'
248
259
  onClick={() => handleShow(property)}
249
260
  data-testid='show-field-button'
@@ -257,26 +268,15 @@ export const ViewEditor = ({
257
268
  )}
258
269
  </div>
259
270
 
260
- {field && (
261
- <FieldEditor
262
- key={field.id}
263
- view={view}
264
- projection={projection}
265
- field={field}
266
- registry={registry}
267
- onSave={handleClose}
268
- />
269
- )}
270
-
271
- {!readonly && !field && (
271
+ {!readonly && !expandedField && (
272
272
  <div role='none' className={outerSpacing ? cardSpacing : 'mlb-cardSpacingBlock'}>
273
273
  <IconButton
274
274
  icon='ph--plus--regular'
275
275
  label={t('button add property')}
276
276
  onClick={readonly ? undefined : handleAdd}
277
277
  // TODO(burdon): Show field limit in ux (not tooltip).
278
- disabled={view.fields.length >= VIEW_FIELD_LIMIT}
279
- classNames='flex is-full'
278
+ disabled={view.projection.fields.length >= VIEW_FIELD_LIMIT}
279
+ classNames='is-full'
280
280
  />
281
281
  </div>
282
282
  )}
@@ -4,17 +4,18 @@
4
4
 
5
5
  import '@dxos-theme';
6
6
 
7
- import { type Meta, type StoryObj } from '@storybook/react';
7
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
8
8
  import React from 'react';
9
9
 
10
10
  import { live } from '@dxos/live-object';
11
- import { testData, TestSchema, testView, type TestType } from '@dxos/schema/testing';
11
+ import { TestSchema, type TestType, testData, testView } from '@dxos/schema/testing';
12
12
  import { withLayout, withTheme } from '@dxos/storybook-utils';
13
13
 
14
- import { DeprecatedForm, type DeprecatedFormProps } from './Form';
15
- import translations from '../../translations';
14
+ import { translations } from '../../translations';
16
15
  import { TestLayout, TestPanel } from '../testing';
17
16
 
17
+ import { DeprecatedForm, type DeprecatedFormProps } from './Form';
18
+
18
19
  const DefaultStory = (props: DeprecatedFormProps) => (
19
20
  <TestLayout json={{ props }}>
20
21
  <TestPanel>
@@ -40,14 +41,14 @@ type Story = StoryObj<DeprecatedFormProps<TestType>>;
40
41
  export const Default: Story = {
41
42
  args: {
42
43
  object: live(testData),
43
- view: live(testView),
44
+ projection: live(testView).projection,
44
45
  schema: TestSchema,
45
46
  },
46
47
  };
47
48
 
48
49
  export const Empty: Story = {
49
50
  args: {
50
- view: live(testView),
51
+ projection: live(testView).projection,
51
52
  schema: TestSchema,
52
53
  },
53
54
  };
@@ -55,7 +56,7 @@ export const Empty: Story = {
55
56
  export const Readonly: Story = {
56
57
  args: {
57
58
  object: live(testData),
58
- view: live(testView),
59
+ projection: live(testView).projection,
59
60
  schema: TestSchema,
60
61
  readonly: true,
61
62
  },
@@ -2,16 +2,16 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { SchemaAST, type Schema, Option, pipe } from 'effect';
5
+ import { Option, type Schema, SchemaAST, pipe } from 'effect';
6
6
  import React, { useState } from 'react';
7
7
 
8
8
  import { findProperty } from '@dxos/effect';
9
- import { Input, type ThemedClassName, type TextInputProps as NativeTextInputProps } from '@dxos/react-ui';
9
+ import { Input, type TextInputProps as NativeTextInputProps, type ThemedClassName } from '@dxos/react-ui';
10
10
  import { mx } from '@dxos/react-ui-theme';
11
- import { type ViewType, getFieldValue, setFieldValue } from '@dxos/schema';
11
+ import { type Projection, getFieldValue, setFieldValue } from '@dxos/schema';
12
12
 
13
13
  export type DeprecatedFormProps<T extends {} = {}> = ThemedClassName<{
14
- view: ViewType;
14
+ projection: Projection;
15
15
  object: T;
16
16
  schema?: Schema.Schema<T>;
17
17
  readonly?: boolean;
@@ -22,14 +22,14 @@ export type DeprecatedFormProps<T extends {} = {}> = ThemedClassName<{
22
22
  */
23
23
  export const DeprecatedForm = <T extends {} = {}>({
24
24
  classNames,
25
- view,
25
+ projection,
26
26
  object,
27
27
  schema,
28
28
  readonly,
29
29
  }: DeprecatedFormProps<T>) => {
30
30
  return (
31
31
  <div role='none' className={mx('flex flex-col w-full gap-2 p-2', classNames)}>
32
- {view.fields.map((field) => {
32
+ {projection.fields.map((field) => {
33
33
  const prop = schema && findProperty(schema, field.path);
34
34
  const title = (prop && pipe(SchemaAST.getTitleAnnotation(prop), Option.getOrUndefined)) ?? '';
35
35
  const description = (prop && pipe(SchemaAST.getDescriptionAnnotation(prop), Option.getOrUndefined)) ?? title;
@@ -17,7 +17,7 @@ export const TestLayout = ({ classNames, json, children }: ThemedClassName<Props
17
17
  );
18
18
 
19
19
  export const TestPanel = ({ classNames, children }: ThemedClassName<PropsWithChildren>) => (
20
- <Card.Content classNames={[textBlockWidth, classNames]}>{children}</Card.Content>
20
+ <Card.StaticRoot classNames={[textBlockWidth, classNames]}>{children}</Card.StaticRoot>
21
21
  );
22
22
 
23
23
  // Symbol for accessing debug objects in tests.
@@ -2,14 +2,14 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { SchemaAST, type Schema } from 'effect';
5
+ import { type Schema, SchemaAST } from 'effect';
6
6
  import { useCallback, useEffect, useMemo, useState } from 'react';
7
7
 
8
8
  import { type BaseObject, getValue, setValue } from '@dxos/echo-schema';
9
- import { type SimpleType, type JsonPath, createJsonPath, fromEffectValidationPath } from '@dxos/effect';
9
+ import { type JsonPath, type SimpleType, createJsonPath, fromEffectValidationPath } from '@dxos/effect';
10
10
  import { invariant } from '@dxos/invariant';
11
11
  import { log } from '@dxos/log';
12
- import { validateSchema, type ValidationError } from '@dxos/schema';
12
+ import { type ValidationError, validateSchema } from '@dxos/schema';
13
13
  import { type MaybePromise } from '@dxos/util';
14
14
 
15
15
  /**
@@ -26,6 +26,8 @@ export type FormHandler<T extends BaseObject> = {
26
26
  changed: Record<JsonPath, boolean>;
27
27
  canSave: boolean;
28
28
  formIsValid: boolean;
29
+
30
+ // TODO(burdon): Return onSave.
29
31
  handleSave: () => void;
30
32
 
31
33
  //
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { useCallback } from 'react';
6
6
 
7
- import { getObjectDXN, type TypeAnnotation } from '@dxos/echo-schema';
7
+ import { type TypeAnnotation, getObjectDXN } from '@dxos/echo-schema';
8
8
  import { Filter, type Space } from '@dxos/react-client/echo';
9
9
  import { isNonNullable } from '@dxos/util';
10
10
 
package/src/index.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import translations from './translations';
5
+ import { translations } from './translations';
6
6
 
7
7
  export * from './components';
8
8
  export * from './hooks';
@@ -2,9 +2,11 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
+ import { type Resource } from '@dxos/react-ui';
6
+
5
7
  export const translationKey = 'react-ui-form';
6
8
 
7
- export default [
9
+ export const translations = [
8
10
  {
9
11
  'en-US': {
10
12
  [translationKey]: {
@@ -23,6 +25,7 @@ export default [
23
25
  'field limit reached': 'Maximum number of fields reached',
24
26
  'boolean input true value': 'Yes',
25
27
  'boolean input false value': 'No',
28
+ 'delete field label': 'Delete field',
26
29
 
27
30
  // TODO(burdon): Factor out?
28
31
  'button cancel': 'Cancel',
@@ -70,9 +73,9 @@ export default [
70
73
  'select option add': 'Add option',
71
74
 
72
75
  // System schema message.
73
- 'system schema title': 'System Schema',
74
- 'system schema description': 'This is a system schema and cannot be modified.',
76
+ 'system schema title': 'System record type',
77
+ 'system schema description': 'This is a system record type and cannot be modified.',
75
78
  },
76
79
  },
77
80
  },
78
- ];
81
+ ] as const satisfies Resource[];