@dxos/react-ui-form 0.8.4-main.e098934 → 0.8.4-main.ead640a

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 (88) hide show
  1. package/dist/lib/browser/index.mjs +422 -284
  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 +422 -284
  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 +3 -2
  8. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +1 -1
  9. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  10. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +3 -2
  11. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
  12. package/dist/types/src/components/Form/ArrayField.d.ts.map +1 -1
  13. package/dist/types/src/components/Form/Form.d.ts +2 -2
  14. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  15. package/dist/types/src/components/Form/Form.stories.d.ts +12 -11
  16. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  17. package/dist/types/src/components/Form/FormContent.d.ts +5 -5
  18. package/dist/types/src/components/Form/FormContent.d.ts.map +1 -1
  19. package/dist/types/src/components/Form/FormContext.d.ts +2 -2
  20. package/dist/types/src/components/Form/FormContext.d.ts.map +1 -1
  21. package/dist/types/src/components/Form/Input.d.ts +1 -1
  22. package/dist/types/src/components/Form/Input.d.ts.map +1 -1
  23. package/dist/types/src/components/Form/RefField.d.ts +10 -6
  24. package/dist/types/src/components/Form/RefField.d.ts.map +1 -1
  25. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +3 -2
  26. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +1 -1
  27. package/dist/types/src/components/Form/factory.d.ts +1 -1
  28. package/dist/types/src/components/Form/factory.d.ts.map +1 -1
  29. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +41 -0
  30. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -0
  31. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +82 -0
  32. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -0
  33. package/dist/types/src/components/ObjectPicker/index.d.ts +2 -0
  34. package/dist/types/src/components/ObjectPicker/index.d.ts.map +1 -0
  35. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +19 -6
  36. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  37. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +7 -6
  38. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  39. package/dist/types/src/components/index.d.ts +1 -0
  40. package/dist/types/src/components/index.d.ts.map +1 -1
  41. package/dist/types/src/hooks/useForm.d.ts +2 -2
  42. package/dist/types/src/hooks/useForm.d.ts.map +1 -1
  43. package/dist/types/src/hooks/useQueryRefOptions.d.ts +9 -3
  44. package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +1 -1
  45. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts.map +1 -1
  46. package/dist/types/src/translations.d.ts +2 -1
  47. package/dist/types/src/translations.d.ts.map +1 -1
  48. package/dist/types/src/util/field.d.ts +1 -1
  49. package/dist/types/src/util/field.d.ts.map +1 -1
  50. package/dist/types/src/util/formatting.d.ts +1 -1
  51. package/dist/types/src/util/formatting.d.ts.map +1 -1
  52. package/dist/types/src/util/refs.d.ts +1 -1
  53. package/dist/types/src/util/refs.d.ts.map +1 -1
  54. package/dist/types/src/util/schema.d.ts +1 -1
  55. package/dist/types/src/util/schema.d.ts.map +1 -1
  56. package/dist/types/tsconfig.tsbuildinfo +1 -1
  57. package/package.json +39 -39
  58. package/src/components/ControlSection/ControlSection.stories.tsx +3 -4
  59. package/src/components/ControlSection/ControlSection.tsx +1 -1
  60. package/src/components/FieldEditor/FieldEditor.stories.tsx +6 -5
  61. package/src/components/FieldEditor/FieldEditor.tsx +5 -2
  62. package/src/components/Form/ArrayField.tsx +4 -3
  63. package/src/components/Form/Form.stories.tsx +32 -32
  64. package/src/components/Form/Form.tsx +13 -5
  65. package/src/components/Form/FormContent.tsx +30 -10
  66. package/src/components/Form/FormContext.tsx +3 -3
  67. package/src/components/Form/Input.tsx +1 -1
  68. package/src/components/Form/RefField.tsx +48 -64
  69. package/src/components/Form/custom/GeoPointInput.tsx +1 -1
  70. package/src/components/Form/custom/SelectOptionsInput.stories.tsx +5 -6
  71. package/src/components/Form/custom/SelectOptionsInput.tsx +1 -1
  72. package/src/components/Form/factory.tsx +1 -1
  73. package/src/components/ObjectPicker/ObjectPicker.stories.tsx +207 -0
  74. package/src/components/ObjectPicker/ObjectPicker.tsx +161 -0
  75. package/src/components/ObjectPicker/index.ts +5 -0
  76. package/src/components/ViewEditor/ViewEditor.stories.tsx +95 -53
  77. package/src/components/ViewEditor/ViewEditor.test.tsx +4 -3
  78. package/src/components/ViewEditor/ViewEditor.tsx +270 -229
  79. package/src/components/index.ts +1 -0
  80. package/src/components/testing.tsx +2 -2
  81. package/src/hooks/useForm.ts +7 -4
  82. package/src/hooks/useQueryRefOptions.ts +15 -6
  83. package/src/hooks/useRefQueryLookupHandler.ts +3 -3
  84. package/src/translations.ts +2 -1
  85. package/src/util/field.ts +1 -1
  86. package/src/util/formatting.ts +1 -1
  87. package/src/util/refs.ts +2 -2
  88. package/src/util/schema.ts +2 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-form",
3
- "version": "0.8.4-main.e098934",
3
+ "version": "0.8.4-main.ead640a",
4
4
  "description": "Data components.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -28,54 +28,54 @@
28
28
  "@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3",
29
29
  "@fluentui/react-tabster": "^9.24.2",
30
30
  "@preact-signals/safe-react": "^0.9.0",
31
+ "@preact/signals-react": "^3.2.0",
31
32
  "@radix-ui/react-context": "1.1.1",
32
33
  "date-fns": "^3.3.1",
33
34
  "jsonpath": "^1.1.1",
34
- "@dxos/async": "0.8.4-main.e098934",
35
- "@dxos/debug": "0.8.4-main.e098934",
36
- "@dxos/echo": "0.8.4-main.e098934",
37
- "@dxos/echo-schema": "0.8.4-main.e098934",
38
- "@dxos/echo-db": "0.8.4-main.e098934",
39
- "@dxos/effect": "0.8.4-main.e098934",
40
- "@dxos/invariant": "0.8.4-main.e098934",
41
- "@dxos/lit-ui": "0.8.4-main.e098934",
42
- "@dxos/keys": "0.8.4-main.e098934",
43
- "@dxos/live-object": "0.8.4-main.e098934",
44
- "@dxos/log": "0.8.4-main.e098934",
45
- "@dxos/react-client": "0.8.4-main.e098934",
46
- "@dxos/react-ui-components": "0.8.4-main.e098934",
47
- "@dxos/react-ui-editor": "0.8.4-main.e098934",
48
- "@dxos/react-ui-pickers": "0.8.4-main.e098934",
49
- "@dxos/react-ui-list": "0.8.4-main.e098934",
50
- "@dxos/react-ui-searchlist": "0.8.4-main.e098934",
51
- "@dxos/react-ui-stack": "0.8.4-main.e098934",
52
- "@dxos/schema": "0.8.4-main.e098934",
53
- "@dxos/react-ui-types": "0.8.4-main.e098934",
54
- "@dxos/util": "0.8.4-main.e098934",
55
- "@dxos/react-ui-tag-picker": "0.8.4-main.e098934"
35
+ "@dxos/async": "0.8.4-main.ead640a",
36
+ "@dxos/echo": "0.8.4-main.ead640a",
37
+ "@dxos/echo-query": "0.8.4-main.ead640a",
38
+ "@dxos/echo-db": "0.8.4-main.ead640a",
39
+ "@dxos/debug": "0.8.4-main.ead640a",
40
+ "@dxos/effect": "0.8.4-main.ead640a",
41
+ "@dxos/invariant": "0.8.4-main.ead640a",
42
+ "@dxos/live-object": "0.8.4-main.ead640a",
43
+ "@dxos/lit-ui": "0.8.4-main.ead640a",
44
+ "@dxos/log": "0.8.4-main.ead640a",
45
+ "@dxos/react-ui-components": "0.8.4-main.ead640a",
46
+ "@dxos/react-ui-list": "0.8.4-main.ead640a",
47
+ "@dxos/react-ui-editor": "0.8.4-main.ead640a",
48
+ "@dxos/react-client": "0.8.4-main.ead640a",
49
+ "@dxos/react-ui-pickers": "0.8.4-main.ead640a",
50
+ "@dxos/react-ui-searchlist": "0.8.4-main.ead640a",
51
+ "@dxos/keys": "0.8.4-main.ead640a",
52
+ "@dxos/react-ui-types": "0.8.4-main.ead640a",
53
+ "@dxos/schema": "0.8.4-main.ead640a",
54
+ "@dxos/util": "0.8.4-main.ead640a",
55
+ "@dxos/react-ui-stack": "0.8.4-main.ead640a"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@types/jsonpath": "^0.2.4",
59
- "@types/react": "~18.2.0",
60
- "@types/react-dom": "~18.2.0",
61
- "effect": "3.17.7",
59
+ "@types/react": "~19.2.2",
60
+ "@types/react-dom": "~19.2.1",
61
+ "effect": "3.18.3",
62
62
  "fast-check": "^3.19.0",
63
- "react": "~18.2.0",
64
- "react-dom": "~18.2.0",
65
- "vite": "7.1.1",
66
- "@dxos/echo-signals": "0.8.4-main.e098934",
67
- "@dxos/random": "0.8.4-main.e098934",
68
- "@dxos/react-ui": "0.8.4-main.e098934",
69
- "@dxos/react-ui-theme": "0.8.4-main.e098934",
70
- "@dxos/storybook-utils": "0.8.4-main.e098934",
71
- "@dxos/react-ui-syntax-highlighter": "0.8.4-main.e098934"
63
+ "react": "~19.2.0",
64
+ "react-dom": "~19.2.0",
65
+ "vite": "7.1.9",
66
+ "@dxos/echo-signals": "0.8.4-main.ead640a",
67
+ "@dxos/random": "0.8.4-main.ead640a",
68
+ "@dxos/react-ui": "0.8.4-main.ead640a",
69
+ "@dxos/react-ui-syntax-highlighter": "0.8.4-main.ead640a",
70
+ "@dxos/react-ui-theme": "0.8.4-main.ead640a",
71
+ "@dxos/storybook-utils": "0.8.4-main.ead640a"
72
72
  },
73
73
  "peerDependencies": {
74
74
  "effect": "^3.13.3",
75
- "react": "~18.2.0",
76
- "react-dom": "~18.2.0",
77
- "@dxos/react-ui": "0.8.4-main.e098934",
78
- "@dxos/react-ui-theme": "0.8.4-main.e098934"
75
+ "react": "^19.0.0",
76
+ "react-dom": "^19.0.0",
77
+ "@dxos/react-ui": "0.8.4-main.ead640a",
78
+ "@dxos/react-ui-theme": "0.8.4-main.ead640a"
79
79
  },
80
80
  "publishConfig": {
81
81
  "access": "public"
@@ -2,12 +2,10 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
6
  import React from 'react';
9
7
 
10
- import { withLayout, withTheme } from '@dxos/storybook-utils';
8
+ import { withTheme } from '@dxos/react-ui/testing';
11
9
 
12
10
  import { translations } from '../../translations';
13
11
 
@@ -21,8 +19,9 @@ const meta = {
21
19
  <ControlSection {...args} />
22
20
  </ControlPage>
23
21
  ),
24
- decorators: [withLayout({ fullscreen: true, classNames: 'justify-center' }), withTheme],
22
+ decorators: [withTheme],
25
23
  parameters: {
24
+ layout: 'centered',
26
25
  translations,
27
26
  },
28
27
  } satisfies Meta<typeof ControlSection>;
@@ -22,7 +22,7 @@ export type ControlPageProps = ThemedClassName<ComponentPropsWithoutRef<'div'>>;
22
22
 
23
23
  export const ControlPage = ({ children, classNames, ...props }: ControlPageProps) => {
24
24
  return (
25
- <StackItem.Content classNames='block overflow-y-auto [--control-spacing:var(--dx-trimMd)]'>
25
+ <StackItem.Content scrollable classNames='[--control-spacing:var(--dx-trimMd)]'>
26
26
  <div role='none' className={mx('pli-cardSpacingInline pbe-trimLg', classNames)} {...props}>
27
27
  {children}
28
28
  </div>
@@ -2,16 +2,14 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
6
  import React, { useEffect, useMemo } from 'react';
9
7
 
10
- import { createEchoSchema } from '@dxos/live-object/testing';
8
+ import { createEchoSchema } from '@dxos/echo/testing';
11
9
  import { log } from '@dxos/log';
10
+ import { withTheme } from '@dxos/react-ui/testing';
12
11
  import { ProjectionModel } from '@dxos/schema';
13
12
  import { TestSchema, testView } from '@dxos/schema/testing';
14
- import { withLayout, withTheme } from '@dxos/storybook-utils';
15
13
 
16
14
  import { translations } from '../../translations';
17
15
  import { FIELD_EDITOR_DEBUG_SYMBOL, TestLayout, TestPanel } from '../testing';
@@ -31,6 +29,8 @@ const DefaultStory = (props: FieldEditorProps) => {
31
29
 
32
30
  // Expose objects on window for test access.
33
31
  useEffect(() => {
32
+ props.projection.normalizeView();
33
+
34
34
  if (typeof window !== 'undefined') {
35
35
  (window as any)[FIELD_EDITOR_DEBUG_SYMBOL] = {
36
36
  props,
@@ -58,8 +58,9 @@ const meta = {
58
58
  title: 'ui/react-ui-form/FieldEditor',
59
59
  component: FieldEditor as any,
60
60
  render: DefaultStory,
61
- decorators: [withLayout({ fullscreen: true }), withTheme],
61
+ decorators: [withTheme],
62
62
  parameters: {
63
+ layout: 'fullscreen',
63
64
  translations,
64
65
  },
65
66
  } satisfies Meta<typeof DefaultStory>;
@@ -4,8 +4,8 @@
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
8
  import { type SchemaRegistry } from '@dxos/echo-db';
8
- import { type EchoSchema, FormatEnum, FormatEnums, formatToType } from '@dxos/echo-schema';
9
9
  import { log } from '@dxos/log';
10
10
  import { useAsyncEffect, useTranslation } from '@dxos/react-ui';
11
11
  import {
@@ -13,6 +13,7 @@ import {
13
13
  type ProjectionModel,
14
14
  type PropertyType,
15
15
  type SchemaProperty,
16
+ formatToAdditionalPropertyAttributes,
16
17
  getFormatSchema,
17
18
  getSchemaProperties,
18
19
  sortProperties,
@@ -94,8 +95,10 @@ export const FieldEditor = ({
94
95
 
95
96
  setProps((props) => {
96
97
  const type = formatToType[_props.format as keyof typeof formatToType];
98
+ const additionalProps =
99
+ formatToAdditionalPropertyAttributes[_props.format as keyof typeof formatToAdditionalPropertyAttributes];
97
100
  if (props.type !== type) {
98
- return { ...props, ..._props, type };
101
+ return { ...props, ..._props, ...additionalProps, type } as PropertyType;
99
102
  }
100
103
 
101
104
  return _props as PropertyType;
@@ -2,8 +2,9 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { SchemaAST, pipe } from 'effect';
6
- import { capitalize } from 'effect/String';
5
+ import * as Function from 'effect/Function';
6
+ import * as SchemaAST from 'effect/SchemaAST';
7
+ import * as String from 'effect/String';
7
8
  import React, { Fragment, useCallback } from 'react';
8
9
 
9
10
  import { SimpleType, findNode, getDiscriminatedType, isDiscriminatedUnion } from '@dxos/effect';
@@ -34,7 +35,7 @@ export const ArrayField = ({ property, readonly, path, inputProps, Custom, looku
34
35
  // It doesn't become live until another field is touched, but that's better than the whole form crashing.
35
36
  const values = (useFormValues(path ?? []) ?? []) as any[];
36
37
  invariant(Array.isArray(values), `Values at path ${path?.join('.')} must be an array.`);
37
- const label = title ?? pipe(name, capitalize);
38
+ const label = title ?? Function.pipe(name, String.capitalize);
38
39
 
39
40
  const elementType = findArrayElementType(ast);
40
41
 
@@ -2,19 +2,17 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
- import { Schema } from 'effect';
6
+ import * as Schema from 'effect/Schema';
9
7
  import React, { useCallback, useState } from 'react';
10
8
 
11
9
  import { ContactType } from '@dxos/client/testing';
12
10
  import { type Type } from '@dxos/echo';
13
- import { type BaseObject, Expando, Format, Ref, type TypeAnnotation, getObjectDXN } from '@dxos/echo-schema';
14
- import { live } from '@dxos/live-object';
15
- import { withSurfaceVariantsLayout } from '@dxos/react-ui/testing';
11
+ import { type BaseObject, Expando, Format, Ref, type TypeAnnotation, getObjectDXN } from '@dxos/echo/internal';
12
+ import { live } from '@dxos/echo/internal';
13
+ import { Tooltip } from '@dxos/react-ui';
14
+ import { withSurfaceVariantsLayout, withTheme } from '@dxos/react-ui/testing';
16
15
  import { Testing } from '@dxos/schema/testing';
17
- import { withLayout, withTheme } from '@dxos/storybook-utils';
18
16
 
19
17
  import { translations } from '../../translations';
20
18
  import { TestLayout, TestPanel } from '../testing';
@@ -40,11 +38,13 @@ const DefaultStory = <T extends BaseObject = any>({
40
38
 
41
39
  if (debug) {
42
40
  return (
43
- <TestLayout json={{ values, schema: schema.ast }}>
44
- <TestPanel>
45
- <Form<T> schema={schema} values={values} onSave={handleSave} {...props} />
46
- </TestPanel>
47
- </TestLayout>
41
+ <Tooltip.Provider>
42
+ <TestLayout json={{ values, schema: schema.ast }}>
43
+ <TestPanel>
44
+ <Form<T> schema={schema} values={values} onSave={handleSave} {...props} />
45
+ </TestPanel>
46
+ </TestLayout>
47
+ </Tooltip.Provider>
48
48
  );
49
49
  }
50
50
 
@@ -67,24 +67,6 @@ const RefStory = <T extends BaseObject = any>(props: StoryProps<T>) => {
67
67
  return <DefaultStory<T> onQueryRefOptions={onQueryRefOptions} {...props} />;
68
68
  };
69
69
 
70
- const meta = {
71
- title: 'ui/react-ui-form/Form',
72
- component: Form as any,
73
- render: DefaultStory,
74
- decorators: [withLayout({ fullscreen: true }), withTheme],
75
- parameters: {
76
- translations,
77
- },
78
- argTypes: {
79
- readonly: {
80
- control: 'boolean',
81
- description: 'Readonly',
82
- },
83
- },
84
- } satisfies Meta<StoryProps<any>>;
85
-
86
- export default meta;
87
-
88
70
  const AddressSchema = Schema.Struct({
89
71
  street: Schema.optional(Schema.String.annotations({ title: 'Street' })),
90
72
  city: Schema.optional(Schema.String.annotations({ title: 'City' })),
@@ -102,10 +84,28 @@ const ContactSchema = Schema.Struct({
102
84
 
103
85
  type ContactSchema = Schema.Schema.Type<typeof ContactSchema>;
104
86
 
105
- type Story = StoryObj<StoryProps<any>>;
87
+ const meta = {
88
+ title: 'ui/react-ui-form/Form',
89
+ component: Form as any,
90
+ render: DefaultStory,
91
+ decorators: [withTheme, withSurfaceVariantsLayout()],
92
+ parameters: {
93
+ layout: 'fullscreen',
94
+ translations,
95
+ },
96
+ argTypes: {
97
+ readonly: {
98
+ control: 'boolean',
99
+ description: 'Readonly',
100
+ },
101
+ },
102
+ } satisfies Meta<StoryProps<any>>;
103
+
104
+ export default meta;
105
+
106
+ type Story = StoryObj<typeof meta>;
106
107
 
107
108
  export const Default: Story = {
108
- decorators: [withSurfaceVariantsLayout(), withTheme],
109
109
  args: {
110
110
  schema: ContactSchema,
111
111
  values: {
@@ -3,15 +3,15 @@
3
3
  //
4
4
 
5
5
  import { useFocusFinders } from '@fluentui/react-tabster';
6
- import { type Schema } from 'effect';
7
- import React, { type ReactElement, useEffect, useMemo, useRef } from 'react';
6
+ import type * as Schema from 'effect/Schema';
7
+ import React, { type ReactElement, useCallback, useEffect, useRef } from 'react';
8
8
 
9
- import { type BaseObject, type PropertyKey } from '@dxos/echo-schema';
9
+ import { type BaseObject, 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';
13
13
 
14
- import { type FormOptions } from '../../hooks';
14
+ import { type FormHandler, type FormOptions } from '../../hooks';
15
15
 
16
16
  import { FormActions, type FormOuterSpacing } from './FormActions';
17
17
  import { FormFields, type FormFieldsProps } from './FormContent';
@@ -41,6 +41,7 @@ export type FormProps<T extends BaseObject> = ThemedClassName<{
41
41
  onCancel?: () => void;
42
42
  }> &
43
43
  Pick<FormOptions<T>, 'schema' | 'onValuesChanged' | 'onValidate' | 'onSave'> &
44
+ // TODO(wittjosiah): This needs to support different ref field options per field.
44
45
  FormFieldsProps;
45
46
 
46
47
  export const Form = <T extends BaseObject>({
@@ -72,7 +73,14 @@ export const Form = <T extends BaseObject>({
72
73
  }, [autoFocus]);
73
74
 
74
75
  // TODO(burdon): Name?
75
- const handleValid = useMemo(() => (autoSave ? onSave : undefined), [autoSave, onSave]);
76
+ const handleValid = useCallback(
77
+ async (values: any, meta: { changed: FormHandler<T>['changed'] }) => {
78
+ if (autoSave) {
79
+ await onSave?.(values, meta);
80
+ }
81
+ },
82
+ [autoSave, onSave],
83
+ );
76
84
 
77
85
  return (
78
86
  <FormProvider
@@ -2,15 +2,17 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Schema, SchemaAST, pipe } from 'effect';
6
- import { capitalize } from 'effect/String';
5
+ import * as Function from 'effect/Function';
6
+ import * as Schema from 'effect/Schema';
7
+ import * as SchemaAST from 'effect/SchemaAST';
8
+ import * as StringEffect from 'effect/String';
7
9
  import React, { forwardRef, useMemo } from 'react';
8
10
 
9
11
  import { createJsonPath, findNode, getDiscriminatedType, isDiscriminatedUnion } from '@dxos/effect';
10
12
  import { type ThemedClassName } from '@dxos/react-ui';
11
13
  import { mx } from '@dxos/react-ui-theme';
12
14
  import { type ProjectionModel, type SchemaProperty, getSchemaProperties } from '@dxos/schema';
13
- import { isNotFalsy } from '@dxos/util';
15
+ import { isTruthy } from '@dxos/util';
14
16
 
15
17
  import { type QueryRefOptions } from '../../hooks';
16
18
  import { getRefProps } from '../../util';
@@ -33,7 +35,13 @@ export type FormFieldProps = {
33
35
  Custom?: Partial<Record<string, InputComponent>>;
34
36
  } & Pick<
35
37
  RefFieldProps,
36
- 'readonly' | 'onQueryRefOptions' | 'createOptionLabel' | 'createOptionIcon' | 'onCreateFromQuery'
38
+ | 'readonly'
39
+ | 'onQueryRefOptions'
40
+ | 'createOptionLabel'
41
+ | 'createOptionIcon'
42
+ | 'onCreate'
43
+ | 'createSchema'
44
+ | 'createInitialValuePath'
37
45
  >;
38
46
 
39
47
  export const FormField = ({
@@ -45,14 +53,16 @@ export const FormField = ({
45
53
  onQueryRefOptions,
46
54
  createOptionLabel,
47
55
  createOptionIcon,
48
- onCreateFromQuery,
56
+ onCreate,
57
+ createSchema,
58
+ createInitialValuePath,
49
59
  lookupComponent,
50
60
  Custom,
51
61
  }: FormFieldProps) => {
52
62
  const { ast, name, type, format, title, description, options, examples, array } = property;
53
63
  const inputProps = useInputProps(path);
54
64
 
55
- const label = useMemo(() => title ?? pipe(name, capitalize), [title, name]);
65
+ const label = useMemo(() => title ?? Function.pipe(name, StringEffect.capitalize), [title, name]);
56
66
  const placeholder = useMemo(
57
67
  () => (examples?.length ? `Example: "${examples[0]}"` : description),
58
68
  [examples, description],
@@ -114,7 +124,9 @@ export const FormField = ({
114
124
  onQueryRefOptions={onQueryRefOptions}
115
125
  createOptionLabel={createOptionLabel}
116
126
  createOptionIcon={createOptionIcon}
117
- onCreateFromQuery={onCreateFromQuery}
127
+ onCreate={onCreate}
128
+ createSchema={createSchema}
129
+ createInitialValuePath={createInitialValuePath}
118
130
  {...inputProps}
119
131
  />
120
132
  );
@@ -188,7 +200,7 @@ export const FormField = ({
188
200
  onQueryRefOptions={onQueryRefOptions}
189
201
  createOptionLabel={createOptionLabel}
190
202
  createOptionIcon={createOptionIcon}
191
- onCreateFromQuery={onCreateFromQuery}
203
+ onCreate={onCreate}
192
204
  Custom={Custom}
193
205
  lookupComponent={lookupComponent}
194
206
  />
@@ -222,7 +234,15 @@ export type FormFieldsProps = ThemedClassName<
222
234
  Custom?: Partial<Record<string, InputComponent>>;
223
235
  onQueryRefOptions?: QueryRefOptions;
224
236
  } & Pick<FormFieldProps, 'readonly'> &
225
- Pick<RefFieldProps, 'onQueryRefOptions' | 'createOptionLabel' | 'createOptionIcon' | 'onCreateFromQuery'>
237
+ Pick<
238
+ RefFieldProps,
239
+ | 'onQueryRefOptions'
240
+ | 'createOptionLabel'
241
+ | 'createOptionIcon'
242
+ | 'onCreate'
243
+ | 'createSchema'
244
+ | 'createInitialValuePath'
245
+ >
226
246
  >;
227
247
 
228
248
  export const FormFields = forwardRef<HTMLDivElement, FormFieldsProps>(
@@ -295,7 +315,7 @@ export const FormFields = forwardRef<HTMLDivElement, FormFieldsProps>(
295
315
  />
296
316
  );
297
317
  })
298
- .filter(isNotFalsy)}
318
+ .filter(isTruthy)}
299
319
  </div>
300
320
  );
301
321
  },
@@ -14,7 +14,7 @@ import React, {
14
14
  } from 'react';
15
15
 
16
16
  import { raise } from '@dxos/debug';
17
- import { type BaseObject, getValue } from '@dxos/echo-schema';
17
+ import { type BaseObject, 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';
@@ -62,7 +62,7 @@ export const FormProvider = ({
62
62
  ...formOptions
63
63
  }: PropsWithChildren<
64
64
  FormOptions<any> & {
65
- formRef?: RefObject<HTMLDivElement>;
65
+ formRef?: RefObject<HTMLDivElement | null>;
66
66
  autoSave?: boolean;
67
67
  }
68
68
  >) => {
@@ -92,7 +92,7 @@ export const FormProvider = ({
92
92
  }
93
93
  }
94
94
  },
95
- [form, autoSave],
95
+ [form.canSave, form.formIsValid, form.handleSave, autoSave],
96
96
  );
97
97
 
98
98
  useEffect(() => {
@@ -4,7 +4,7 @@
4
4
 
5
5
  import React, { type FC } from 'react';
6
6
 
7
- import { type FormatEnum } from '@dxos/echo-schema';
7
+ import { type FormatEnum } 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';