@dxos/react-ui-form 0.8.4-main.5ea62a8 → 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 (91) hide show
  1. package/dist/lib/browser/index.mjs +534 -348
  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 +534 -348
  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 +8 -7
  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 +8 -7
  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 +6 -5
  14. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  15. package/dist/types/src/components/Form/Form.stories.d.ts +52 -37
  16. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  17. package/dist/types/src/components/Form/FormActions.d.ts.map +1 -1
  18. package/dist/types/src/components/Form/FormContent.d.ts +5 -5
  19. package/dist/types/src/components/Form/FormContent.d.ts.map +1 -1
  20. package/dist/types/src/components/Form/FormContext.d.ts +4 -4
  21. package/dist/types/src/components/Form/FormContext.d.ts.map +1 -1
  22. package/dist/types/src/components/Form/Input.d.ts +2 -2
  23. package/dist/types/src/components/Form/Input.d.ts.map +1 -1
  24. package/dist/types/src/components/Form/RefField.d.ts +10 -6
  25. package/dist/types/src/components/Form/RefField.d.ts.map +1 -1
  26. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +8 -7
  27. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +1 -1
  28. package/dist/types/src/components/Form/factory.d.ts +2 -2
  29. package/dist/types/src/components/Form/factory.d.ts.map +1 -1
  30. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +41 -0
  31. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -0
  32. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +82 -0
  33. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -0
  34. package/dist/types/src/components/ObjectPicker/index.d.ts +2 -0
  35. package/dist/types/src/components/ObjectPicker/index.d.ts.map +1 -0
  36. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +20 -6
  37. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  38. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +13 -11
  39. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  40. package/dist/types/src/components/index.d.ts +1 -0
  41. package/dist/types/src/components/index.d.ts.map +1 -1
  42. package/dist/types/src/hooks/useForm.d.ts +5 -5
  43. package/dist/types/src/hooks/useForm.d.ts.map +1 -1
  44. package/dist/types/src/hooks/useQueryRefOptions.d.ts +9 -3
  45. package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +1 -1
  46. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts.map +1 -1
  47. package/dist/types/src/translations.d.ts +7 -6
  48. package/dist/types/src/translations.d.ts.map +1 -1
  49. package/dist/types/src/util/field.d.ts +3 -3
  50. package/dist/types/src/util/field.d.ts.map +1 -1
  51. package/dist/types/src/util/formatting.d.ts +2 -2
  52. package/dist/types/src/util/formatting.d.ts.map +1 -1
  53. package/dist/types/src/util/refs.d.ts +1 -1
  54. package/dist/types/src/util/refs.d.ts.map +1 -1
  55. package/dist/types/src/util/schema.d.ts +1 -1
  56. package/dist/types/src/util/schema.d.ts.map +1 -1
  57. package/dist/types/tsconfig.tsbuildinfo +1 -1
  58. package/package.json +40 -37
  59. package/src/components/ControlSection/ControlSection.stories.tsx +4 -5
  60. package/src/components/ControlSection/ControlSection.tsx +1 -1
  61. package/src/components/FieldEditor/FieldEditor.stories.tsx +8 -7
  62. package/src/components/FieldEditor/FieldEditor.tsx +7 -4
  63. package/src/components/Form/ArrayField.tsx +7 -4
  64. package/src/components/Form/Defaults.tsx +1 -1
  65. package/src/components/Form/Form.stories.tsx +52 -52
  66. package/src/components/Form/Form.tsx +19 -9
  67. package/src/components/Form/FormActions.tsx +7 -2
  68. package/src/components/Form/FormContent.tsx +83 -23
  69. package/src/components/Form/FormContext.tsx +6 -6
  70. package/src/components/Form/Input.tsx +2 -2
  71. package/src/components/Form/RefField.tsx +50 -66
  72. package/src/components/Form/custom/GeoPointInput.tsx +1 -1
  73. package/src/components/Form/custom/SelectOptionsInput.stories.tsx +5 -6
  74. package/src/components/Form/custom/SelectOptionsInput.tsx +2 -2
  75. package/src/components/Form/factory.tsx +7 -4
  76. package/src/components/ObjectPicker/ObjectPicker.stories.tsx +207 -0
  77. package/src/components/ObjectPicker/ObjectPicker.tsx +161 -0
  78. package/src/components/ObjectPicker/index.ts +5 -0
  79. package/src/components/ViewEditor/ViewEditor.stories.tsx +112 -49
  80. package/src/components/ViewEditor/ViewEditor.test.tsx +6 -5
  81. package/src/components/ViewEditor/ViewEditor.tsx +277 -208
  82. package/src/components/index.ts +1 -0
  83. package/src/components/testing.tsx +2 -2
  84. package/src/hooks/useForm.ts +11 -8
  85. package/src/hooks/useQueryRefOptions.ts +15 -6
  86. package/src/hooks/useRefQueryLookupHandler.ts +5 -5
  87. package/src/translations.ts +7 -6
  88. package/src/util/field.ts +25 -25
  89. package/src/util/formatting.ts +22 -22
  90. package/src/util/refs.ts +7 -7
  91. 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.5ea62a8",
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",
@@ -28,52 +28,55 @@
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.5ea62a8",
35
- "@dxos/echo": "0.8.4-main.5ea62a8",
36
- "@dxos/debug": "0.8.4-main.5ea62a8",
37
- "@dxos/echo-db": "0.8.4-main.5ea62a8",
38
- "@dxos/echo-schema": "0.8.4-main.5ea62a8",
39
- "@dxos/effect": "0.8.4-main.5ea62a8",
40
- "@dxos/invariant": "0.8.4-main.5ea62a8",
41
- "@dxos/lit-ui": "0.8.4-main.5ea62a8",
42
- "@dxos/live-object": "0.8.4-main.5ea62a8",
43
- "@dxos/keys": "0.8.4-main.5ea62a8",
44
- "@dxos/log": "0.8.4-main.5ea62a8",
45
- "@dxos/react-client": "0.8.4-main.5ea62a8",
46
- "@dxos/react-ui-list": "0.8.4-main.5ea62a8",
47
- "@dxos/react-ui-pickers": "0.8.4-main.5ea62a8",
48
- "@dxos/react-ui-tag-picker": "0.8.4-main.5ea62a8",
49
- "@dxos/schema": "0.8.4-main.5ea62a8",
50
- "@dxos/react-ui-types": "0.8.4-main.5ea62a8",
51
- "@dxos/util": "0.8.4-main.5ea62a8",
52
- "@dxos/react-ui-stack": "0.8.4-main.5ea62a8",
53
- "@dxos/react-ui-searchlist": "0.8.4-main.5ea62a8"
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"
54
57
  },
55
58
  "devDependencies": {
56
59
  "@types/jsonpath": "^0.2.4",
57
- "@types/react": "~18.2.0",
58
- "@types/react-dom": "~18.2.0",
59
- "effect": "3.17.7",
60
+ "@types/react": "~19.2.2",
61
+ "@types/react-dom": "~19.2.2",
62
+ "effect": "3.18.3",
60
63
  "fast-check": "^3.19.0",
61
- "react": "~18.2.0",
62
- "react-dom": "~18.2.0",
63
- "vite": "7.1.1",
64
- "@dxos/echo-signals": "0.8.4-main.5ea62a8",
65
- "@dxos/random": "0.8.4-main.5ea62a8",
66
- "@dxos/react-ui": "0.8.4-main.5ea62a8",
67
- "@dxos/react-ui-syntax-highlighter": "0.8.4-main.5ea62a8",
68
- "@dxos/react-ui-theme": "0.8.4-main.5ea62a8",
69
- "@dxos/storybook-utils": "0.8.4-main.5ea62a8"
64
+ "react": "~19.2.0",
65
+ "react-dom": "~19.2.0",
66
+ "vite": "7.1.9",
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"
70
73
  },
71
74
  "peerDependencies": {
72
75
  "effect": "^3.13.3",
73
- "react": "~18.2.0",
74
- "react-dom": "~18.2.0",
75
- "@dxos/react-ui": "0.8.4-main.5ea62a8",
76
- "@dxos/react-ui-theme": "0.8.4-main.5ea62a8"
76
+ "react": "^19.0.0",
77
+ "react-dom": "^19.0.0",
78
+ "@dxos/react-ui": "0.8.4-main.66e292d",
79
+ "@dxos/react-ui-theme": "0.8.4-main.66e292d"
77
80
  },
78
81
  "publishConfig": {
79
82
  "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
 
@@ -17,12 +15,13 @@ const meta = {
17
15
  title: 'ui/react-ui-form/ControlSection',
18
16
  component: ControlSection,
19
17
  render: (args) => (
20
- <ControlPage classNames='w-[40rem]'>
18
+ <ControlPage classNames='is-[40rem]'>
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
- import { TestSchema, testView } from '@dxos/schema/testing';
14
- import { withLayout, withTheme } from '@dxos/storybook-utils';
12
+ import { Example, testView } from '@dxos/schema/testing';
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>;
@@ -70,7 +71,7 @@ type Story = StoryObj<typeof meta>;
70
71
 
71
72
  export const Default: Story = {
72
73
  args: {
73
- projection: new ProjectionModel(createEchoSchema(TestSchema).jsonSchema, testView.projection),
74
+ projection: new ProjectionModel(createEchoSchema(Example).jsonSchema, testView.projection),
74
75
  field: testView.projection.fields[0],
75
76
  },
76
77
  parameters: { controls: { disabled: true } },
@@ -4,8 +4,8 @@
4
4
 
5
5
  import React, { useCallback, useEffect, useMemo, useState } from 'react';
6
6
 
7
+ import { type EchoSchema, Format, 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;
@@ -137,7 +140,7 @@ export const FieldEditor = ({
137
140
  ['format' satisfies keyof PropertyType]: (props) => (
138
141
  <SelectInput
139
142
  {...props}
140
- options={FormatEnums.filter((value) => value !== FormatEnum.None).map((value) => ({
143
+ options={FormatEnums.filter((value) => value !== Format.TypeFormat.None).map((value) => ({
141
144
  value,
142
145
  label: t(`format ${value}`),
143
146
  }))}
@@ -156,7 +159,7 @@ export const FieldEditor = ({
156
159
  {...props}
157
160
  options={
158
161
  referenceSchema
159
- ? getSchemaProperties(referenceSchema.ast)
162
+ ? getSchemaProperties(referenceSchema.ast, {}, { form: true })
160
163
  .sort(sortProperties)
161
164
  .map((p) => ({ value: p.name }))
162
165
  : []
@@ -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
 
@@ -45,7 +46,9 @@ export const ArrayField = ({ property, readonly, path, inputProps, Custom, looku
45
46
  return {};
46
47
  }
47
48
 
48
- 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
+ );
49
52
  };
50
53
 
51
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' }}
@@ -2,19 +2,15 @@
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
- import { ContactType } from '@dxos/client/testing';
12
- 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';
16
- import { Testing } from '@dxos/schema/testing';
17
- import { withLayout, withTheme } from '@dxos/storybook-utils';
9
+ import { Format, Obj, Ref, Type } from '@dxos/echo';
10
+ import { type AnyProperties, type TypeAnnotation } from '@dxos/echo/internal';
11
+ import { Tooltip } from '@dxos/react-ui';
12
+ import { withLayoutVariants, withTheme } from '@dxos/react-ui/testing';
13
+ import { TestSchema } from '@dxos/schema/testing';
18
14
 
19
15
  import { translations } from '../../translations';
20
16
  import { TestLayout, TestPanel } from '../testing';
@@ -22,12 +18,12 @@ import { TestLayout, TestPanel } from '../testing';
22
18
  import { SelectInput } from './Defaults';
23
19
  import { Form, type FormProps } from './Form';
24
20
 
25
- type StoryProps<T extends BaseObject> = {
21
+ type StoryProps<T extends AnyProperties> = {
26
22
  debug?: boolean;
27
- schema: Type.Obj.Any;
23
+ schema: Schema.Schema.AnyNoContext;
28
24
  } & FormProps<T>;
29
25
 
30
- const DefaultStory = <T extends BaseObject = any>({
26
+ const DefaultStory = <T extends AnyProperties = any>({
31
27
  debug,
32
28
  schema,
33
29
  values: initialValues,
@@ -40,24 +36,26 @@ const DefaultStory = <T extends BaseObject = any>({
40
36
 
41
37
  if (debug) {
42
38
  return (
43
- <TestLayout json={{ values, schema: schema.ast }}>
44
- <TestPanel>
45
- <Form<T> schema={schema} values={values} onSave={handleSave} {...props} />
46
- </TestPanel>
47
- </TestLayout>
39
+ <Tooltip.Provider>
40
+ <TestLayout json={{ values, schema: schema.ast }}>
41
+ <TestPanel>
42
+ <Form<T> schema={schema} values={values} onSave={handleSave} {...props} />
43
+ </TestPanel>
44
+ </TestLayout>
45
+ </Tooltip.Provider>
48
46
  );
49
47
  }
50
48
 
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 [];
@@ -67,24 +65,6 @@ const RefStory = <T extends BaseObject = any>(props: StoryProps<T>) => {
67
65
  return <DefaultStory<T> onQueryRefOptions={onQueryRefOptions} {...props} />;
68
66
  };
69
67
 
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
68
  const AddressSchema = Schema.Struct({
89
69
  street: Schema.optional(Schema.String.annotations({ title: 'Street' })),
90
70
  city: Schema.optional(Schema.String.annotations({ title: 'City' })),
@@ -102,10 +82,28 @@ const ContactSchema = Schema.Struct({
102
82
 
103
83
  type ContactSchema = Schema.Schema.Type<typeof ContactSchema>;
104
84
 
105
- type Story = StoryObj<StoryProps<any>>;
85
+ const meta = {
86
+ title: 'ui/react-ui-form/Form',
87
+ component: Form as any,
88
+ render: DefaultStory,
89
+ decorators: [withTheme, withLayoutVariants()],
90
+ parameters: {
91
+ layout: 'fullscreen',
92
+ translations,
93
+ },
94
+ argTypes: {
95
+ readonly: {
96
+ control: 'boolean',
97
+ description: 'Readonly',
98
+ },
99
+ },
100
+ } satisfies Meta<StoryProps<any>>;
101
+
102
+ export default meta;
103
+
104
+ type Story = StoryObj<typeof meta>;
106
105
 
107
106
  export const Default: Story = {
108
- decorators: [withSurfaceVariantsLayout(), withTheme],
109
107
  args: {
110
108
  schema: ContactSchema,
111
109
  values: {
@@ -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,
@@ -3,22 +3,22 @@
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 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';
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';
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,
@@ -72,7 +75,14 @@ export const Form = <T extends BaseObject>({
72
75
  }, [autoFocus]);
73
76
 
74
77
  // TODO(burdon): Name?
75
- const handleValid = useMemo(() => (autoSave ? onSave : undefined), [autoSave, onSave]);
78
+ const handleValid = useCallback(
79
+ async (values: any, meta: { changed: FormHandler<T>['changed'] }) => {
80
+ if (autoSave) {
81
+ await onSave?.(values, meta);
82
+ }
83
+ },
84
+ [autoSave, onSave],
85
+ );
76
86
 
77
87
  return (
78
88
  <FormProvider
@@ -85,7 +95,7 @@ export const Form = <T extends BaseObject>({
85
95
  onValid={handleValid}
86
96
  onSave={onSave}
87
97
  >
88
- <div role='none' className='contents' data-testid={testId}>
98
+ <div role='none' className='contents' {...(id && { 'data-object-id': id })} data-testid={testId}>
89
99
  <FormFields
90
100
  {...props}
91
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
  )}