@dxos/react-ui-form 0.8.4-main.5ad4a44 → 0.8.4-main.66e292d

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 (70) hide show
  1. package/dist/lib/browser/index.mjs +223 -205
  2. package/dist/lib/browser/index.mjs.map +3 -3
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +223 -205
  5. package/dist/lib/node-esm/index.mjs.map +3 -3
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts +5 -5
  8. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +5 -5
  9. package/dist/types/src/components/Form/ArrayField.d.ts.map +1 -1
  10. package/dist/types/src/components/Form/Form.d.ts +5 -4
  11. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  12. package/dist/types/src/components/Form/Form.stories.d.ts +47 -33
  13. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  14. package/dist/types/src/components/Form/FormActions.d.ts.map +1 -1
  15. package/dist/types/src/components/Form/FormContent.d.ts.map +1 -1
  16. package/dist/types/src/components/Form/FormContext.d.ts +3 -3
  17. package/dist/types/src/components/Form/FormContext.d.ts.map +1 -1
  18. package/dist/types/src/components/Form/Input.d.ts +2 -2
  19. package/dist/types/src/components/Form/Input.d.ts.map +1 -1
  20. package/dist/types/src/components/Form/RefField.d.ts +1 -0
  21. package/dist/types/src/components/Form/RefField.d.ts.map +1 -1
  22. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +5 -5
  23. package/dist/types/src/components/Form/factory.d.ts +2 -2
  24. package/dist/types/src/components/Form/factory.d.ts.map +1 -1
  25. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +1 -1
  26. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
  27. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +6 -5
  28. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
  29. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +11 -9
  30. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  31. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +8 -8
  32. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  33. package/dist/types/src/hooks/useForm.d.ts +4 -4
  34. package/dist/types/src/hooks/useForm.d.ts.map +1 -1
  35. package/dist/types/src/hooks/useQueryRefOptions.d.ts +1 -0
  36. package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +1 -1
  37. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts.map +1 -1
  38. package/dist/types/src/translations.d.ts +5 -5
  39. package/dist/types/src/util/field.d.ts +3 -3
  40. package/dist/types/src/util/field.d.ts.map +1 -1
  41. package/dist/types/src/util/formatting.d.ts +2 -2
  42. package/dist/types/src/util/formatting.d.ts.map +1 -1
  43. package/dist/types/tsconfig.tsbuildinfo +1 -1
  44. package/package.json +32 -31
  45. package/src/components/ControlSection/ControlSection.stories.tsx +1 -1
  46. package/src/components/FieldEditor/FieldEditor.stories.tsx +2 -2
  47. package/src/components/FieldEditor/FieldEditor.tsx +3 -3
  48. package/src/components/Form/ArrayField.tsx +3 -1
  49. package/src/components/Form/Defaults.tsx +1 -1
  50. package/src/components/Form/Form.stories.tsx +24 -24
  51. package/src/components/Form/Form.tsx +8 -5
  52. package/src/components/Form/FormActions.tsx +7 -2
  53. package/src/components/Form/FormContent.tsx +53 -13
  54. package/src/components/Form/FormContext.tsx +4 -4
  55. package/src/components/Form/Input.tsx +2 -2
  56. package/src/components/Form/RefField.tsx +16 -2
  57. package/src/components/Form/custom/SelectOptionsInput.tsx +1 -1
  58. package/src/components/Form/factory.tsx +7 -4
  59. package/src/components/ObjectPicker/ObjectPicker.stories.tsx +25 -25
  60. package/src/components/ObjectPicker/ObjectPicker.tsx +14 -14
  61. package/src/components/ViewEditor/ViewEditor.stories.tsx +46 -32
  62. package/src/components/ViewEditor/ViewEditor.test.tsx +4 -3
  63. package/src/components/ViewEditor/ViewEditor.tsx +41 -97
  64. package/src/hooks/useForm.ts +8 -7
  65. package/src/hooks/useQueryRefOptions.ts +6 -3
  66. package/src/hooks/useRefQueryLookupHandler.ts +5 -5
  67. package/src/translations.ts +5 -5
  68. package/src/util/field.ts +25 -25
  69. package/src/util/formatting.ts +22 -22
  70. package/src/util/refs.ts +6 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-form",
3
- "version": "0.8.4-main.5ad4a44",
3
+ "version": "0.8.4-main.66e292d",
4
4
  "description": "Data components.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -32,50 +32,51 @@
32
32
  "@radix-ui/react-context": "1.1.1",
33
33
  "date-fns": "^3.3.1",
34
34
  "jsonpath": "^1.1.1",
35
- "@dxos/async": "0.8.4-main.5ad4a44",
36
- "@dxos/debug": "0.8.4-main.5ad4a44",
37
- "@dxos/echo": "0.8.4-main.5ad4a44",
38
- "@dxos/echo-db": "0.8.4-main.5ad4a44",
39
- "@dxos/echo-query": "0.8.4-main.5ad4a44",
40
- "@dxos/effect": "0.8.4-main.5ad4a44",
41
- "@dxos/invariant": "0.8.4-main.5ad4a44",
42
- "@dxos/keys": "0.8.4-main.5ad4a44",
43
- "@dxos/lit-ui": "0.8.4-main.5ad4a44",
44
- "@dxos/react-client": "0.8.4-main.5ad4a44",
45
- "@dxos/react-ui-components": "0.8.4-main.5ad4a44",
46
- "@dxos/log": "0.8.4-main.5ad4a44",
47
- "@dxos/react-ui-editor": "0.8.4-main.5ad4a44",
48
- "@dxos/react-ui-list": "0.8.4-main.5ad4a44",
49
- "@dxos/react-ui-pickers": "0.8.4-main.5ad4a44",
50
- "@dxos/react-ui-searchlist": "0.8.4-main.5ad4a44",
51
- "@dxos/react-ui-stack": "0.8.4-main.5ad4a44",
52
- "@dxos/live-object": "0.8.4-main.5ad4a44",
53
- "@dxos/schema": "0.8.4-main.5ad4a44",
54
- "@dxos/util": "0.8.4-main.5ad4a44",
55
- "@dxos/react-ui-types": "0.8.4-main.5ad4a44"
35
+ "@dxos/async": "0.8.4-main.66e292d",
36
+ "@dxos/debug": "0.8.4-main.66e292d",
37
+ "@dxos/echo": "0.8.4-main.66e292d",
38
+ "@dxos/echo-db": "0.8.4-main.66e292d",
39
+ "@dxos/echo-query": "0.8.4-main.66e292d",
40
+ "@dxos/effect": "0.8.4-main.66e292d",
41
+ "@dxos/invariant": "0.8.4-main.66e292d",
42
+ "@dxos/keys": "0.8.4-main.66e292d",
43
+ "@dxos/lit-ui": "0.8.4-main.66e292d",
44
+ "@dxos/log": "0.8.4-main.66e292d",
45
+ "@dxos/live-object": "0.8.4-main.66e292d",
46
+ "@dxos/react-client": "0.8.4-main.66e292d",
47
+ "@dxos/react-ui-components": "0.8.4-main.66e292d",
48
+ "@dxos/react-ui-editor": "0.8.4-main.66e292d",
49
+ "@dxos/react-ui-list": "0.8.4-main.66e292d",
50
+ "@dxos/react-ui-pickers": "0.8.4-main.66e292d",
51
+ "@dxos/react-ui-searchlist": "0.8.4-main.66e292d",
52
+ "@dxos/react-ui-stack": "0.8.4-main.66e292d",
53
+ "@dxos/react-ui-types": "0.8.4-main.66e292d",
54
+ "@dxos/schema": "0.8.4-main.66e292d",
55
+ "@dxos/util": "0.8.4-main.66e292d",
56
+ "@dxos/types": "0.8.4-main.66e292d"
56
57
  },
57
58
  "devDependencies": {
58
59
  "@types/jsonpath": "^0.2.4",
59
60
  "@types/react": "~19.2.2",
60
- "@types/react-dom": "~19.2.1",
61
+ "@types/react-dom": "~19.2.2",
61
62
  "effect": "3.18.3",
62
63
  "fast-check": "^3.19.0",
63
64
  "react": "~19.2.0",
64
65
  "react-dom": "~19.2.0",
65
66
  "vite": "7.1.9",
66
- "@dxos/echo-signals": "0.8.4-main.5ad4a44",
67
- "@dxos/random": "0.8.4-main.5ad4a44",
68
- "@dxos/react-ui": "0.8.4-main.5ad4a44",
69
- "@dxos/react-ui-syntax-highlighter": "0.8.4-main.5ad4a44",
70
- "@dxos/storybook-utils": "0.8.4-main.5ad4a44",
71
- "@dxos/react-ui-theme": "0.8.4-main.5ad4a44"
67
+ "@dxos/echo-signals": "0.8.4-main.66e292d",
68
+ "@dxos/random": "0.8.4-main.66e292d",
69
+ "@dxos/react-ui-syntax-highlighter": "0.8.4-main.66e292d",
70
+ "@dxos/storybook-utils": "0.8.4-main.66e292d",
71
+ "@dxos/react-ui-theme": "0.8.4-main.66e292d",
72
+ "@dxos/react-ui": "0.8.4-main.66e292d"
72
73
  },
73
74
  "peerDependencies": {
74
75
  "effect": "^3.13.3",
75
76
  "react": "^19.0.0",
76
77
  "react-dom": "^19.0.0",
77
- "@dxos/react-ui": "0.8.4-main.5ad4a44",
78
- "@dxos/react-ui-theme": "0.8.4-main.5ad4a44"
78
+ "@dxos/react-ui": "0.8.4-main.66e292d",
79
+ "@dxos/react-ui-theme": "0.8.4-main.66e292d"
79
80
  },
80
81
  "publishConfig": {
81
82
  "access": "public"
@@ -15,7 +15,7 @@ const meta = {
15
15
  title: 'ui/react-ui-form/ControlSection',
16
16
  component: ControlSection,
17
17
  render: (args) => (
18
- <ControlPage classNames='w-[40rem]'>
18
+ <ControlPage classNames='is-[40rem]'>
19
19
  <ControlSection {...args} />
20
20
  </ControlPage>
21
21
  ),
@@ -9,7 +9,7 @@ import { createEchoSchema } from '@dxos/echo/testing';
9
9
  import { log } from '@dxos/log';
10
10
  import { withTheme } from '@dxos/react-ui/testing';
11
11
  import { ProjectionModel } from '@dxos/schema';
12
- import { TestSchema, testView } from '@dxos/schema/testing';
12
+ import { Example, testView } from '@dxos/schema/testing';
13
13
 
14
14
  import { translations } from '../../translations';
15
15
  import { FIELD_EDITOR_DEBUG_SYMBOL, TestLayout, TestPanel } from '../testing';
@@ -71,7 +71,7 @@ type Story = StoryObj<typeof meta>;
71
71
 
72
72
  export const Default: Story = {
73
73
  args: {
74
- projection: new ProjectionModel(createEchoSchema(TestSchema).jsonSchema, testView.projection),
74
+ projection: new ProjectionModel(createEchoSchema(Example).jsonSchema, testView.projection),
75
75
  field: testView.projection.fields[0],
76
76
  },
77
77
  parameters: { controls: { disabled: true } },
@@ -4,7 +4,7 @@
4
4
 
5
5
  import React, { useCallback, useEffect, useMemo, useState } from 'react';
6
6
 
7
- import { type EchoSchema, FormatEnum, FormatEnums, formatToType } from '@dxos/echo/internal';
7
+ import { type EchoSchema, Format, FormatEnums, formatToType } from '@dxos/echo/internal';
8
8
  import { type SchemaRegistry } from '@dxos/echo-db';
9
9
  import { log } from '@dxos/log';
10
10
  import { useAsyncEffect, useTranslation } from '@dxos/react-ui';
@@ -140,7 +140,7 @@ export const FieldEditor = ({
140
140
  ['format' satisfies keyof PropertyType]: (props) => (
141
141
  <SelectInput
142
142
  {...props}
143
- options={FormatEnums.filter((value) => value !== FormatEnum.None).map((value) => ({
143
+ options={FormatEnums.filter((value) => value !== Format.TypeFormat.None).map((value) => ({
144
144
  value,
145
145
  label: t(`format ${value}`),
146
146
  }))}
@@ -159,7 +159,7 @@ export const FieldEditor = ({
159
159
  {...props}
160
160
  options={
161
161
  referenceSchema
162
- ? getSchemaProperties(referenceSchema.ast)
162
+ ? getSchemaProperties(referenceSchema.ast, {}, { form: true })
163
163
  .sort(sortProperties)
164
164
  .map((p) => ({ value: p.name }))
165
165
  : []
@@ -46,7 +46,9 @@ export const ArrayField = ({ property, readonly, path, inputProps, Custom, looku
46
46
  return {};
47
47
  }
48
48
 
49
- return Object.fromEntries(getSchemaProperties(typeLiteral, {}).map((prop) => [prop.name, prop.defaultValue]));
49
+ return Object.fromEntries(
50
+ getSchemaProperties(typeLiteral, {}, { form: true }).map((prop) => [prop.name, prop.defaultValue]),
51
+ );
50
52
  };
51
53
 
52
54
  const getDefaultValue = () =>
@@ -192,7 +192,7 @@ export const MarkdownInput = ({
192
192
  disabled={!!readonly}
193
193
  placeholder={placeholder}
194
194
  value={getValue() ?? ''}
195
- classNames={'min-bs-auto max-h-40 overflow-auto'}
195
+ classNames={'min-bs-auto max-bs-40 overflow-auto'}
196
196
  onChange={(event) => onValueChange(type, event.target.value)}
197
197
  onBlur={onBlur}
198
198
  style={{ resize: 'none' }}
@@ -6,13 +6,11 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
  import * as Schema from 'effect/Schema';
7
7
  import React, { useCallback, useState } from 'react';
8
8
 
9
- import { ContactType } from '@dxos/client/testing';
10
- import { type Type } from '@dxos/echo';
11
- import { type BaseObject, Expando, Format, Ref, type TypeAnnotation, getObjectDXN } from '@dxos/echo/internal';
12
- import { live } from '@dxos/echo/internal';
9
+ import { Format, Obj, Ref, Type } from '@dxos/echo';
10
+ import { type AnyProperties, type TypeAnnotation } from '@dxos/echo/internal';
13
11
  import { Tooltip } from '@dxos/react-ui';
14
- import { withSurfaceVariantsLayout, withTheme } from '@dxos/react-ui/testing';
15
- import { Testing } from '@dxos/schema/testing';
12
+ import { withLayoutVariants, withTheme } from '@dxos/react-ui/testing';
13
+ import { TestSchema } from '@dxos/schema/testing';
16
14
 
17
15
  import { translations } from '../../translations';
18
16
  import { TestLayout, TestPanel } from '../testing';
@@ -20,12 +18,12 @@ import { TestLayout, TestPanel } from '../testing';
20
18
  import { SelectInput } from './Defaults';
21
19
  import { Form, type FormProps } from './Form';
22
20
 
23
- type StoryProps<T extends BaseObject> = {
21
+ type StoryProps<T extends AnyProperties> = {
24
22
  debug?: boolean;
25
- schema: Type.Obj.Any;
23
+ schema: Schema.Schema.AnyNoContext;
26
24
  } & FormProps<T>;
27
25
 
28
- const DefaultStory = <T extends BaseObject = any>({
26
+ const DefaultStory = <T extends AnyProperties = any>({
29
27
  debug,
30
28
  schema,
31
29
  values: initialValues,
@@ -51,13 +49,13 @@ const DefaultStory = <T extends BaseObject = any>({
51
49
  return <Form<T> schema={schema} values={values} onSave={handleSave} {...props} />;
52
50
  };
53
51
 
54
- const RefStory = <T extends BaseObject = any>(props: StoryProps<T>) => {
52
+ const RefStory = <T extends AnyProperties = any>(props: StoryProps<T>) => {
55
53
  const onQueryRefOptions = useCallback((typeInfo: TypeAnnotation) => {
56
54
  switch (typeInfo.typename) {
57
- case ContactType.typename:
55
+ case TestSchema.Person.typename:
58
56
  return [
59
- { dxn: getObjectDXN(contact1)!, label: 'John Coltraine' },
60
- { dxn: getObjectDXN(contact2)!, label: 'Erykah Badu' },
57
+ { dxn: Obj.getDXN(contact1), label: 'Alice' },
58
+ { dxn: Obj.getDXN(contact2), label: 'Bob' },
61
59
  ];
62
60
  default:
63
61
  return [];
@@ -88,7 +86,7 @@ const meta = {
88
86
  title: 'ui/react-ui-form/Form',
89
87
  component: Form as any,
90
88
  render: DefaultStory,
91
- decorators: [withTheme, withSurfaceVariantsLayout()],
89
+ decorators: [withTheme, withLayoutVariants()],
92
90
  parameters: {
93
91
  layout: 'fullscreen',
94
92
  translations,
@@ -122,7 +120,7 @@ export const Default: Story = {
122
120
  export const Organization: Story = {
123
121
  args: {
124
122
  debug: true,
125
- schema: Testing.OrganizationSchema,
123
+ schema: TestSchema.OrganizationSchema,
126
124
  values: {
127
125
  name: 'DXOS',
128
126
  website: 'https://dxos.org',
@@ -134,7 +132,7 @@ export const Organization: Story = {
134
132
  export const OrganizationAutoSave: Story = {
135
133
  args: {
136
134
  debug: true,
137
- schema: Testing.OrganizationSchema,
135
+ schema: TestSchema.OrganizationSchema,
138
136
  values: {
139
137
  name: 'DXOS',
140
138
  website: 'https://dxos.org',
@@ -147,10 +145,10 @@ export const OrganizationAutoSave: Story = {
147
145
  // TODO(burdon): Type issue with employer reference.
148
146
  // TODO(burdon): Test table/form with compound values (e.g., address).
149
147
  // Property name is missing in type EncodedReference but required in type
150
- export const Contact: Story = {
148
+ export const Person: Story = {
151
149
  args: {
152
150
  debug: true,
153
- schema: Testing.ContactSchema,
151
+ schema: TestSchema.Person,
154
152
  values: {
155
153
  name: 'Bot',
156
154
  },
@@ -278,16 +276,18 @@ export const Enum: StoryObj<StoryProps<ColorType>> = {
278
276
  //
279
277
 
280
278
  const RefSchema = Schema.Struct({
281
- contact: Ref(ContactType).annotations({ title: 'Contact Reference' }),
282
- optionalContact: Schema.optional(Ref(ContactType).annotations({ title: 'Optional Contact Reference' })),
283
- refArray: Schema.optional(Schema.Array(Ref(ContactType))),
284
- unknownExpando: Schema.optional(Ref(Expando).annotations({ title: 'Optional Ref to an Expando (DXN Input)' })),
279
+ contact: Type.Ref(TestSchema.Person).annotations({ title: 'Contact Reference' }),
280
+ optionalContact: Schema.optional(Type.Ref(TestSchema.Person).annotations({ title: 'Optional Contact Reference' })),
281
+ refArray: Schema.optional(Schema.Array(Type.Ref(TestSchema.Person))),
282
+ unknownExpando: Schema.optional(
283
+ Type.Ref(Type.Expando).annotations({ title: 'Optional Ref to an Expando (DXN Input)' }),
284
+ ),
285
285
  });
286
286
 
287
287
  type RefSchema = Schema.Schema.Type<typeof RefSchema>;
288
288
 
289
- const contact1 = live(ContactType, { identifiers: [] });
290
- const contact2 = live(ContactType, { identifiers: [] });
289
+ const contact1 = Obj.make(TestSchema.Person, { name: 'Alice' });
290
+ const contact2 = Obj.make(TestSchema.Person, { name: 'Bob' });
291
291
 
292
292
  export const Refs: StoryObj<StoryProps<RefSchema>> = {
293
293
  render: RefStory,
@@ -6,7 +6,7 @@ import { useFocusFinders } from '@fluentui/react-tabster';
6
6
  import type * as Schema from 'effect/Schema';
7
7
  import React, { type ReactElement, useCallback, useEffect, useRef } from 'react';
8
8
 
9
- import { type BaseObject, type PropertyKey } from '@dxos/echo/internal';
9
+ import { type AnyProperties, type PropertyKey } from '@dxos/echo/internal';
10
10
  import { type ThemedClassName } from '@dxos/react-ui';
11
11
  import { cardDialogOverflow, cardSpacing } from '@dxos/react-ui-stack';
12
12
  import { type ProjectionModel, type SchemaProperty } from '@dxos/schema';
@@ -18,7 +18,7 @@ import { FormFields, type FormFieldsProps } from './FormContent';
18
18
  import { FormProvider } from './FormContext';
19
19
  import { type InputComponent, type InputProps } from './Input';
20
20
 
21
- export type PropsFilter<T extends BaseObject> = (props: SchemaProperty<T>[]) => SchemaProperty<T>[];
21
+ export type PropsFilter<T extends AnyProperties> = (props: SchemaProperty<T>[]) => SchemaProperty<T>[];
22
22
 
23
23
  export type ComponentLookup = (args: {
24
24
  prop: string;
@@ -28,7 +28,8 @@ export type ComponentLookup = (args: {
28
28
 
29
29
  export type CustomInputMap = Partial<Record<string, InputComponent>>;
30
30
 
31
- export type FormProps<T extends BaseObject> = ThemedClassName<{
31
+ export type FormProps<T extends AnyProperties> = ThemedClassName<{
32
+ id?: string;
32
33
  values: Partial<T>;
33
34
  // TODO(burdon): Autofocus first input.
34
35
  autoFocus?: boolean;
@@ -41,10 +42,12 @@ export type FormProps<T extends BaseObject> = ThemedClassName<{
41
42
  onCancel?: () => void;
42
43
  }> &
43
44
  Pick<FormOptions<T>, 'schema' | 'onValuesChanged' | 'onValidate' | 'onSave'> &
45
+ // TODO(wittjosiah): This needs to support different ref field options per field.
44
46
  FormFieldsProps;
45
47
 
46
- export const Form = <T extends BaseObject>({
48
+ export const Form = <T extends AnyProperties>({
47
49
  classNames,
50
+ id,
48
51
  testId,
49
52
  values: initialValues,
50
53
  autoFocus,
@@ -92,7 +95,7 @@ export const Form = <T extends BaseObject>({
92
95
  onValid={handleValid}
93
96
  onSave={onSave}
94
97
  >
95
- <div role='none' className='contents' data-testid={testId}>
98
+ <div role='none' className='contents' {...(id && { 'data-object-id': id })} data-testid={testId}>
96
99
  <FormFields
97
100
  {...props}
98
101
  ref={formRef}
@@ -37,7 +37,12 @@ export const FormActions = ({ readonly, onCancel, outerSpacing = true }: FormAct
37
37
  )}
38
38
  >
39
39
  {onCancel && !readonly && (
40
- <IconButton data-testid='cancel-button' icon='ph--x--regular' label={t('button cancel')} onClick={onCancel} />
40
+ <IconButton
41
+ data-testid='cancel-button'
42
+ icon='ph--x--regular'
43
+ label={t('cancel button label')}
44
+ onClick={onCancel}
45
+ />
41
46
  )}
42
47
  {handleSave && (
43
48
  <IconButton
@@ -45,7 +50,7 @@ export const FormActions = ({ readonly, onCancel, outerSpacing = true }: FormAct
45
50
  data-testid='save-button'
46
51
  disabled={!canSave}
47
52
  icon='ph--check--regular'
48
- label={t('button save')}
53
+ label={t('save button label')}
49
54
  onClick={handleSave}
50
55
  />
51
56
  )}
@@ -6,7 +6,7 @@ import * as Function from 'effect/Function';
6
6
  import * as Schema from 'effect/Schema';
7
7
  import * as SchemaAST from 'effect/SchemaAST';
8
8
  import * as StringEffect from 'effect/String';
9
- import React, { forwardRef, useMemo } from 'react';
9
+ import React, { Component, type PropsWithChildren, type ReactNode, forwardRef, useMemo } from 'react';
10
10
 
11
11
  import { createJsonPath, findNode, getDiscriminatedType, isDiscriminatedUnion } from '@dxos/effect';
12
12
  import { type ThemedClassName } from '@dxos/react-ui';
@@ -264,7 +264,7 @@ export const FormFields = forwardRef<HTMLDivElement, FormFieldsProps>(
264
264
  ) => {
265
265
  const values = useFormValues(path);
266
266
  const properties = useMemo(() => {
267
- const props = getSchemaProperties(schema.ast, values);
267
+ const props = getSchemaProperties(schema.ast, values, { form: true });
268
268
 
269
269
  // Use projection-based field management when view and projection are available
270
270
  if (projection) {
@@ -302,17 +302,18 @@ export const FormFields = forwardRef<HTMLDivElement, FormFieldsProps>(
302
302
  {properties
303
303
  .map((property) => {
304
304
  return (
305
- <FormField
306
- key={property.name}
307
- property={property}
308
- path={[...(path ?? []), property.name]}
309
- readonly={readonly}
310
- projection={projection}
311
- onQueryRefOptions={onQueryRefOptions}
312
- lookupComponent={lookupComponent}
313
- Custom={Custom}
314
- {...props}
315
- />
305
+ <ErrorBoundary key={property.name} path={[...(path ?? []), property.name]}>
306
+ <FormField
307
+ property={property}
308
+ path={[...(path ?? []), property.name]}
309
+ readonly={readonly}
310
+ projection={projection}
311
+ onQueryRefOptions={onQueryRefOptions}
312
+ lookupComponent={lookupComponent}
313
+ Custom={Custom}
314
+ {...props}
315
+ />
316
+ </ErrorBoundary>
316
317
  );
317
318
  })
318
319
  .filter(isTruthy)}
@@ -320,3 +321,42 @@ export const FormFields = forwardRef<HTMLDivElement, FormFieldsProps>(
320
321
  );
321
322
  },
322
323
  );
324
+
325
+ type ErrorState = {
326
+ error: Error | undefined;
327
+ };
328
+
329
+ type ErrorBoundaryProps = PropsWithChildren<{
330
+ path?: (string | number)[];
331
+ }>;
332
+
333
+ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorState> {
334
+ static getDerivedStateFromError(error: Error): { error: Error } {
335
+ return { error };
336
+ }
337
+
338
+ override state = { error: undefined };
339
+
340
+ override componentDidUpdate(prevProps: ErrorBoundaryProps): void {
341
+ if (prevProps.path !== this.props.path) {
342
+ this.resetError();
343
+ }
344
+ }
345
+
346
+ override render(): ReactNode {
347
+ if (this.state.error) {
348
+ return (
349
+ <div className='flex gap-2 border border-roseFill font-mono text-sm'>
350
+ <span className='bg-roseFill text-surfaceText pli-1 font-thin'>ERROR</span>
351
+ {String(this.props.path?.join('.'))}
352
+ </div>
353
+ );
354
+ }
355
+
356
+ return this.props.children;
357
+ }
358
+
359
+ private resetError(): void {
360
+ this.setState({ error: undefined });
361
+ }
362
+ }
@@ -14,23 +14,23 @@ import React, {
14
14
  } from 'react';
15
15
 
16
16
  import { raise } from '@dxos/debug';
17
- import { type BaseObject, getValue } from '@dxos/echo/internal';
17
+ import { type AnyProperties, getValue } from '@dxos/echo/internal';
18
18
  import { type SimpleType, createJsonPath } from '@dxos/effect';
19
19
 
20
20
  import { type FormHandler, type FormOptions, useForm } from '../../hooks';
21
21
 
22
- type FormContextValue<T extends BaseObject> = FormHandler<T>;
22
+ type FormContextValue<T extends AnyProperties> = FormHandler<T>;
23
23
 
24
24
  const FormContext = createContext<FormContextValue<any> | undefined>(undefined);
25
25
 
26
- export const useFormContext = <T extends BaseObject>(): FormContextValue<T> => {
26
+ export const useFormContext = <T extends AnyProperties>(): FormContextValue<T> => {
27
27
  return useContext(FormContext) ?? raise(new Error('Missing FormContext'));
28
28
  };
29
29
 
30
30
  export const useFormValues = (path: (string | number)[] = []): any => {
31
31
  const { values: formValues } = useFormContext();
32
32
  const jsonPath = createJsonPath(path);
33
- return getValue(formValues, jsonPath) as BaseObject;
33
+ return getValue(formValues, jsonPath) as AnyProperties;
34
34
  };
35
35
 
36
36
  export type FormInputStateProps = {
@@ -4,7 +4,7 @@
4
4
 
5
5
  import React, { type FC } from 'react';
6
6
 
7
- import { type FormatEnum } from '@dxos/echo/internal';
7
+ import { type Format } from '@dxos/echo/internal';
8
8
  import { type SimpleType } from '@dxos/effect';
9
9
  import { Icon, Input, Tooltip } from '@dxos/react-ui';
10
10
  import { labelSpacing } from '@dxos/react-ui-stack';
@@ -17,7 +17,7 @@ import { type FormInputStateProps } from './FormContext';
17
17
  */
18
18
  export type InputProps = {
19
19
  type: SimpleType;
20
- format?: FormatEnum;
20
+ format?: Format.TypeFormat;
21
21
  label: string;
22
22
  placeholder?: string;
23
23
  inputOnly?: boolean;
@@ -2,6 +2,8 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
+ import '@dxos/lit-ui/dx-tag-picker.pcss';
6
+
5
7
  import type * as Schema from 'effect/Schema';
6
8
  import type * as SchemaAST from 'effect/SchemaAST';
7
9
  import React, { useCallback, useMemo } from 'react';
@@ -70,7 +72,11 @@ export const RefField = ({
70
72
  () => (ast ? findAnnotation<ReferenceAnnotationValue>(ast, ReferenceAnnotationId) : undefined),
71
73
  [ast],
72
74
  );
73
- const { options: availableOptions, loading: _loading } = useQueryRefOptions({ refTypeInfo, onQueryRefOptions });
75
+ const {
76
+ options: availableOptions,
77
+ update: updateOptions,
78
+ loading: _loading,
79
+ } = useQueryRefOptions({ refTypeInfo, onQueryRefOptions });
74
80
 
75
81
  if ((refTypeInfo && refTypeInfo?.typename === getTypeAnnotation(Expando)?.typename) || !onQueryRefOptions) {
76
82
  // If ref type is expando, fall back to taking a DXN in string format.
@@ -135,6 +141,14 @@ export const RefField = ({
135
141
  [availableOptions, array, onValueChange],
136
142
  );
137
143
 
144
+ const handleCreate = useCallback(
145
+ (values: any) => {
146
+ onCreate?.(values);
147
+ updateOptions();
148
+ },
149
+ [onCreate, updateOptions],
150
+ );
151
+
138
152
  if (!refTypeInfo) {
139
153
  return null;
140
154
  }
@@ -211,7 +225,7 @@ export const RefField = ({
211
225
  createOptionLabel={createOptionLabel}
212
226
  createOptionIcon={createOptionIcon}
213
227
  createInitialValuePath={createInitialValuePath}
214
- onCreate={onCreate}
228
+ onCreate={handleCreate}
215
229
  />
216
230
  </ObjectPicker.Root>
217
231
  )}
@@ -121,7 +121,7 @@ export const SelectOptionInput = ({ type, label, readonly, getStatus, getValue,
121
121
  readonly={!!readonly}
122
122
  >
123
123
  {({ items }) => (
124
- <div role='list' className='w-full overflow-auto'>
124
+ <div role='list' className='is-full overflow-auto'>
125
125
  {items?.map((item) => (
126
126
  <div key={item.id}>
127
127
  <List.Item
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { FormatEnum } from '@dxos/echo/internal';
5
+ import { Format } from '@dxos/echo/internal';
6
6
  import { type SimpleType } from '@dxos/effect';
7
7
 
8
8
  import { GeoPointInput } from './custom';
@@ -12,11 +12,14 @@ import { type InputComponent } from './Input';
12
12
  /**
13
13
  * Get property input component.
14
14
  */
15
- export const getInputComponent = (type: SimpleType, format?: FormatEnum | string): InputComponent | undefined => {
15
+ export const getInputComponent = (
16
+ type: SimpleType,
17
+ format?: Format.TypeFormat | string,
18
+ ): InputComponent | undefined => {
16
19
  switch (format) {
17
- case FormatEnum.GeoPoint:
20
+ case Format.TypeFormat.GeoPoint:
18
21
  return GeoPointInput;
19
- case FormatEnum.Markdown:
22
+ case Format.TypeFormat.Markdown:
20
23
  return MarkdownInput;
21
24
  }
22
25