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

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.
@@ -1,8 +1,8 @@
1
1
  import React from 'react';
2
- import { type BaseObject, type PropertyKey } from '@dxos/echo-schema';
2
+ import { type BaseObject, S, type PropertyKey } from '@dxos/echo-schema';
3
3
  import { type ThemedClassName } from '@dxos/react-ui';
4
4
  import { type SchemaProperty } from '@dxos/schema';
5
- import { type InputComponent } from './Input';
5
+ import { type InputProps, type InputComponent } from './Input';
6
6
  import { type FormOptions } from '../../hooks';
7
7
  export type PropsFilter<T extends BaseObject> = (props: SchemaProperty<T>[]) => SchemaProperty<T>[];
8
8
  export type FormProps<T extends BaseObject> = ThemedClassName<{
@@ -16,15 +16,21 @@ export type FormProps<T extends BaseObject> = ThemedClassName<{
16
16
  autoSave?: boolean;
17
17
  testId?: string;
18
18
  onCancel?: () => void;
19
+ lookupComponent?: (args: {
20
+ prop: string;
21
+ schema: S.Schema<any>;
22
+ inputProps: InputProps<T>;
23
+ }) => React.ReactElement | undefined;
19
24
  /**
20
25
  * Map of custom renderers for specific properties.
26
+ * @deprecated Use lookupComponent instead.
21
27
  */
22
28
  Custom?: Partial<Record<string, InputComponent<T>>>;
23
29
  } & Pick<FormOptions<T>, 'schema' | 'onValuesChanged' | 'onValidate' | 'onSave'>>;
24
30
  /**
25
31
  * General purpose form component that displays field controls based on the given schema.
26
32
  */
27
- export declare const Form: <T extends BaseObject>({ classNames, schema, values: initialValues, path, readonly, filter, sort, autoSave, testId, onValuesChanged, onValidate, onSave, onCancel, Custom, }: FormProps<T>) => React.JSX.Element;
33
+ export declare const Form: <T extends BaseObject>({ classNames, schema, values: initialValues, path, readonly, filter, sort, autoSave, testId, onValuesChanged, onValidate, onSave, onCancel, lookupComponent, Custom, }: FormProps<T>) => React.JSX.Element;
28
34
  type FormActionsProps = {
29
35
  readonly?: boolean;
30
36
  canSubmit: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"Form.d.ts","sourceRoot":"","sources":["../../../../../src/components/Form/Form.tsx"],"names":[],"mappings":"AAMA,OAAO,KAA6B,MAAM,OAAO,CAAC;AAElD,OAAO,EAAO,KAAK,UAAU,EAAK,KAAK,WAAW,EAAmB,MAAM,mBAAmB,CAAC;AAG/F,OAAO,EAAc,KAAK,eAAe,EAAkB,MAAM,gBAAgB,CAAC;AAElF,OAAO,EAAuB,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAIxE,OAAO,EAAe,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AAE3D,OAAO,EAAoB,KAAK,WAAW,EAAW,MAAM,aAAa,CAAC;AAO1E,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,UAAU,IAAI,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC;AAEpG,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,UAAU,IAAI,eAAe,CAC3D;IACE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAEnB,wEAAwE;IACxE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAGhB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,MAAM,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;IACxB,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IAEtB;;OAEG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CACrD,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,QAAQ,GAAG,iBAAiB,GAAG,YAAY,GAAG,QAAQ,CAAC,CACjF,CAAC;AAEF;;GAEG;AAEH,eAAO,MAAM,IAAI,GAAI,CAAC,SAAS,UAAU,yJAetC,SAAS,CAAC,CAAC,CAAC,sBAwId,CAAC;AAqJF,KAAK,gBAAgB,GAAG;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB,CAAC;AAEF,eAAO,MAAM,WAAW,gDAAiD,gBAAgB,sBAsBxF,CAAC"}
1
+ {"version":3,"file":"Form.d.ts","sourceRoot":"","sources":["../../../../../src/components/Form/Form.tsx"],"names":[],"mappings":"AAMA,OAAO,KAA6B,MAAM,OAAO,CAAC;AAElD,OAAO,EAAO,KAAK,UAAU,EAAE,CAAC,EAAE,KAAK,WAAW,EAAmB,MAAM,mBAAmB,CAAC;AAG/F,OAAO,EAAc,KAAK,eAAe,EAAkB,MAAM,gBAAgB,CAAC;AAElF,OAAO,EAAuB,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAIxE,OAAO,EAAe,KAAK,UAAU,EAAE,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AAE5E,OAAO,EAAoB,KAAK,WAAW,EAAW,MAAM,aAAa,CAAC;AAO1E,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,UAAU,IAAI,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC;AAEpG,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,UAAU,IAAI,eAAe,CAC3D;IACE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAEnB,wEAAwE;IACxE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAGhB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,MAAM,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;IACxB,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IAEtB,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE;QACvB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACtB,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;KAC3B,KAAK,KAAK,CAAC,YAAY,GAAG,SAAS,CAAC;IACrC;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CACrD,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,QAAQ,GAAG,iBAAiB,GAAG,YAAY,GAAG,QAAQ,CAAC,CACjF,CAAC;AAEF;;GAEG;AAEH,eAAO,MAAM,IAAI,GAAI,CAAC,SAAS,UAAU,0KAgBtC,SAAS,CAAC,CAAC,CAAC,sBA8Jd,CAAC;AAqJF,KAAK,gBAAgB,GAAG;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB,CAAC;AAEF,eAAO,MAAM,WAAW,gDAAiD,gBAAgB,sBAsBxF,CAAC"}
@@ -9,10 +9,11 @@ export type ViewEditorProps = ThemedClassName<{
9
9
  registry?: SchemaRegistry;
10
10
  readonly?: boolean;
11
11
  showHeading?: boolean;
12
+ onTypenameChanged: (typename: string) => void;
12
13
  onDelete: (fieldId: string) => void;
13
14
  }>;
14
15
  /**
15
16
  * Schema-based object form.
16
17
  */
17
- export declare const ViewEditor: ({ classNames, schema, view, registry, readonly, showHeading, onDelete, }: ViewEditorProps) => React.JSX.Element;
18
+ export declare const ViewEditor: ({ classNames, schema, view, registry, readonly, showHeading, onTypenameChanged, onDelete, }: ViewEditorProps) => React.JSX.Element;
18
19
  //# sourceMappingURL=ViewEditor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ViewEditor.d.ts","sourceRoot":"","sources":["../../../../../src/components/ViewEditor/ViewEditor.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAyC,MAAM,OAAO,CAAC;AAE9D,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAe,KAAK,UAAU,EAAK,MAAM,mBAAmB,CAAC;AACpE,OAAO,EAAc,KAAK,eAAe,EAAkB,MAAM,gBAAgB,CAAC;AAGlF,OAAO,EAA+B,KAAK,QAAQ,EAAoC,MAAM,cAAc,CAAC;AAoB5G,MAAM,MAAM,eAAe,GAAG,eAAe,CAAC;IAC5C,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC,CAAC,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,UAAU,6EAQpB,eAAe,sBA2GjB,CAAC"}
1
+ {"version":3,"file":"ViewEditor.d.ts","sourceRoot":"","sources":["../../../../../src/components/ViewEditor/ViewEditor.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAyC,MAAM,OAAO,CAAC;AAE9D,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAe,KAAK,UAAU,EAAK,MAAM,mBAAmB,CAAC;AACpE,OAAO,EAAc,KAAK,eAAe,EAAkB,MAAM,gBAAgB,CAAC;AAGlF,OAAO,EAA+B,KAAK,QAAQ,EAAoC,MAAM,cAAc,CAAC;AAoB5G,MAAM,MAAM,eAAe,GAAG,eAAe,CAAC;IAC5C,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,iBAAiB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC,CAAC,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,UAAU,gGASpB,eAAe,sBA8GjB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ViewEditor.stories.d.ts","sourceRoot":"","sources":["../../../../../src/components/ViewEditor/ViewEditor.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,aAAa,CAAC;AAErB,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAU5D,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AA0C1C,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,UAAU,CAQjC,CAAC;AAEF,eAAe,IAAI,CAAC;AAEpB,KAAK,KAAK,GAAG,QAAQ,CAAC;AAEtB,eAAO,MAAM,OAAO,EAAE,KAAU,CAAC"}
1
+ {"version":3,"file":"ViewEditor.stories.d.ts","sourceRoot":"","sources":["../../../../../src/components/ViewEditor/ViewEditor.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,aAAa,CAAC;AAErB,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAU5D,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AA+D1C,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,UAAU,CAQjC,CAAC;AAEF,eAAe,IAAI,CAAC;AAEpB,KAAK,KAAK,GAAG,QAAQ,CAAC;AAEtB,eAAO,MAAM,OAAO,EAAE,KAAU,CAAC"}
@@ -1 +1 @@
1
- {"version":"5.7.2"}
1
+ {"version":"5.7.3"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-form",
3
- "version": "0.7.5-main.9d26e3a",
3
+ "version": "0.7.5-main.9d2a38b",
4
4
  "description": "Data components.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -29,20 +29,20 @@
29
29
  "date-fns": "^3.3.1",
30
30
  "effect": "^3.12.1",
31
31
  "jsonpath": "^1.1.1",
32
- "@dxos/debug": "0.7.5-main.9d26e3a",
33
- "@dxos/echo-db": "0.7.5-main.9d26e3a",
34
- "@dxos/echo-schema": "0.7.5-main.9d26e3a",
35
- "@dxos/effect": "0.7.5-main.9d26e3a",
36
- "@dxos/async": "0.7.5-main.9d26e3a",
37
- "@dxos/keys": "0.7.5-main.9d26e3a",
38
- "@dxos/invariant": "0.7.5-main.9d26e3a",
39
- "@dxos/log": "0.7.5-main.9d26e3a",
40
- "@dxos/live-object": "0.7.5-main.9d26e3a",
41
- "@dxos/react-client": "0.7.5-main.9d26e3a",
42
- "@dxos/react-ui-list": "0.7.5-main.9d26e3a",
43
- "@dxos/react-ui-types": "0.7.5-main.9d26e3a",
44
- "@dxos/schema": "0.7.5-main.9d26e3a",
45
- "@dxos/util": "0.7.5-main.9d26e3a"
32
+ "@dxos/async": "0.7.5-main.9d2a38b",
33
+ "@dxos/debug": "0.7.5-main.9d2a38b",
34
+ "@dxos/echo-db": "0.7.5-main.9d2a38b",
35
+ "@dxos/echo-schema": "0.7.5-main.9d2a38b",
36
+ "@dxos/effect": "0.7.5-main.9d2a38b",
37
+ "@dxos/keys": "0.7.5-main.9d2a38b",
38
+ "@dxos/invariant": "0.7.5-main.9d2a38b",
39
+ "@dxos/live-object": "0.7.5-main.9d2a38b",
40
+ "@dxos/log": "0.7.5-main.9d2a38b",
41
+ "@dxos/react-client": "0.7.5-main.9d2a38b",
42
+ "@dxos/react-ui-list": "0.7.5-main.9d2a38b",
43
+ "@dxos/schema": "0.7.5-main.9d2a38b",
44
+ "@dxos/react-ui-types": "0.7.5-main.9d2a38b",
45
+ "@dxos/util": "0.7.5-main.9d2a38b"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@phosphor-icons/react": "^2.1.5",
@@ -53,12 +53,12 @@
53
53
  "react": "~18.2.0",
54
54
  "react-dom": "~18.2.0",
55
55
  "vite": "5.4.7",
56
- "@dxos/react-ui": "0.7.5-main.9d26e3a",
57
- "@dxos/echo-signals": "0.7.5-main.9d26e3a",
58
- "@dxos/random": "0.7.5-main.9d26e3a",
59
- "@dxos/react-ui-syntax-highlighter": "0.7.5-main.9d26e3a",
60
- "@dxos/storybook-utils": "0.7.5-main.9d26e3a",
61
- "@dxos/react-ui-theme": "0.7.5-main.9d26e3a"
56
+ "@dxos/echo-signals": "0.7.5-main.9d2a38b",
57
+ "@dxos/react-ui": "0.7.5-main.9d2a38b",
58
+ "@dxos/random": "0.7.5-main.9d2a38b",
59
+ "@dxos/react-ui-syntax-highlighter": "0.7.5-main.9d2a38b",
60
+ "@dxos/react-ui-theme": "0.7.5-main.9d2a38b",
61
+ "@dxos/storybook-utils": "0.7.5-main.9d2a38b"
62
62
  },
63
63
  "peerDependencies": {
64
64
  "@effect/schema": "^0.75.5",
@@ -66,8 +66,8 @@
66
66
  "effect": "^3.9.2",
67
67
  "react": "~18.2.0",
68
68
  "react-dom": "~18.2.0",
69
- "@dxos/react-ui": "0.7.5-main.9d26e3a",
70
- "@dxos/react-ui-theme": "0.7.5-main.9d26e3a"
69
+ "@dxos/react-ui": "0.7.5-main.9d2a38b",
70
+ "@dxos/react-ui-theme": "0.7.5-main.9d2a38b"
71
71
  },
72
72
  "publishConfig": {
73
73
  "access": "public"
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import React, { useCallback, useEffect, useState } from 'react';
5
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
6
6
 
7
7
  import { type SchemaRegistry } from '@dxos/echo-db';
8
8
  import { FormatEnum, FormatEnums, formatToType, type EchoSchema } from '@dxos/echo-schema';
@@ -16,10 +16,11 @@ import {
16
16
  type PropertyType,
17
17
  type ViewType,
18
18
  type ViewProjection,
19
+ type SchemaProperty,
19
20
  } from '@dxos/schema';
20
21
 
21
22
  import { translationKey } from '../../translations';
22
- import { Form, type FormProps, SelectInput } from '../Form';
23
+ import { Form, type FormProps, type InputComponent, SelectInput } from '../Form';
23
24
 
24
25
  export type FieldEditorProps = {
25
26
  view: ViewType;
@@ -57,9 +58,9 @@ export const FieldEditor = ({ view, projection, field, registry, onSave, onCance
57
58
  };
58
59
  }, [registry]);
59
60
 
60
- const [schema, setSchema] = useState<EchoSchema>();
61
+ const [referenceSchema, setReferenceSchema] = useState<EchoSchema>();
61
62
  useEffect(() => {
62
- setSchema(schemas.find((schema) => schema.typename === props?.referenceSchema));
63
+ setReferenceSchema(schemas.find((schema) => schema.typename === props?.referenceSchema));
63
64
  }, [schemas, props?.referenceSchema]);
64
65
 
65
66
  // TODO(burdon): Need to wrap otherwise throws error:
@@ -78,9 +79,15 @@ export const FieldEditor = ({ view, projection, field, registry, onSave, onCance
78
79
  }
79
80
  return { fieldSchema };
80
81
  });
81
- if (_props.referenceSchema !== props.referenceSchema) {
82
- setSchema(schemas.find((schema) => schema.typename === _props.referenceSchema));
83
- }
82
+ setReferenceSchema((prev) => {
83
+ if (_props.referenceSchema !== prev?.typename) {
84
+ const newSchema = schemas.find((schema) => schema.typename === _props.referenceSchema);
85
+ if (newSchema) {
86
+ return newSchema;
87
+ }
88
+ }
89
+ return prev;
90
+ });
84
91
 
85
92
  setProps((props) => {
86
93
  const type = formatToType[_props.format as keyof typeof formatToType];
@@ -122,6 +129,46 @@ export const FieldEditor = ({ view, projection, field, registry, onSave, onCance
122
129
  onSave();
123
130
  }, [onSave]);
124
131
 
132
+ const Custom: Partial<Record<string, InputComponent<PropertyType>>> = useMemo(
133
+ () => ({
134
+ ['format' satisfies keyof PropertyType]: (props) => (
135
+ <SelectInput<PropertyType>
136
+ {...props}
137
+ options={FormatEnums.filter((value) => value !== FormatEnum.None).map((value) => ({
138
+ value,
139
+ label: t(`format ${value}`),
140
+ }))}
141
+ />
142
+ ),
143
+ ['referenceSchema' satisfies keyof PropertyType]: (props) => (
144
+ <SelectInput<PropertyType>
145
+ {...props}
146
+ options={schemas.map((schema) => ({
147
+ value: schema.typename,
148
+ }))}
149
+ />
150
+ ),
151
+ ['referencePath' satisfies keyof PropertyType]: (props) => (
152
+ <SelectInput<PropertyType>
153
+ {...props}
154
+ options={
155
+ referenceSchema
156
+ ? getSchemaProperties(referenceSchema.ast)
157
+ .sort(sortProperties)
158
+ .map((p) => ({ value: p.name }))
159
+ : []
160
+ }
161
+ />
162
+ ),
163
+ }),
164
+ [t, schemas, referenceSchema],
165
+ );
166
+
167
+ const propIsNotType = useCallback(
168
+ (props: SchemaProperty<PropertyType>[]) => props.filter((p) => p.name !== 'type'),
169
+ [],
170
+ );
171
+
125
172
  if (!fieldSchema) {
126
173
  log.warn('invalid format', { props });
127
174
  return null;
@@ -133,43 +180,13 @@ export const FieldEditor = ({ view, projection, field, registry, onSave, onCance
133
180
  autoFocus
134
181
  values={props}
135
182
  schema={fieldSchema}
136
- filter={(props) => props.filter((p) => p.name !== 'type')}
183
+ filter={propIsNotType}
137
184
  sort={['property', 'format']}
138
185
  onValuesChanged={handleValuesChanged}
139
186
  onValidate={handleValidate}
140
187
  onSave={handleSave}
141
188
  onCancel={handleCancel}
142
- Custom={{
143
- ['format' satisfies keyof PropertyType]: (props) => (
144
- <SelectInput<PropertyType>
145
- {...props}
146
- options={FormatEnums.filter((value) => value !== FormatEnum.None).map((value) => ({
147
- value,
148
- label: t(`format ${value}`),
149
- }))}
150
- />
151
- ),
152
- ['referenceSchema' satisfies keyof PropertyType]: (props) => (
153
- <SelectInput<PropertyType>
154
- {...props}
155
- options={schemas.map((schema) => ({
156
- value: schema.typename,
157
- }))}
158
- />
159
- ),
160
- ['referencePath' satisfies keyof PropertyType]: (props) => (
161
- <SelectInput<PropertyType>
162
- {...props}
163
- options={
164
- schema
165
- ? getSchemaProperties(schema.ast)
166
- .sort(sortProperties)
167
- .map((p) => ({ value: p.name }))
168
- : []
169
- }
170
- />
171
- ),
172
- }}
189
+ Custom={Custom}
173
190
  />
174
191
  );
175
192
  };
@@ -15,7 +15,7 @@ import { getSchemaProperties, type SchemaProperty } from '@dxos/schema';
15
15
  import { isNotFalsy } from '@dxos/util';
16
16
 
17
17
  import { SelectInput } from './Defaults';
18
- import { InputHeader, type InputComponent } from './Input';
18
+ import { InputHeader, type InputProps, type InputComponent } from './Input';
19
19
  import { getInputComponent } from './factory';
20
20
  import { type FormHandler, type FormOptions, useForm } from '../../hooks';
21
21
  import { translationKey } from '../../translations';
@@ -43,8 +43,14 @@ export type FormProps<T extends BaseObject> = ThemedClassName<
43
43
  testId?: string;
44
44
  onCancel?: () => void;
45
45
 
46
+ lookupComponent?: (args: {
47
+ prop: string;
48
+ schema: S.Schema<any>;
49
+ inputProps: InputProps<T>;
50
+ }) => React.ReactElement | undefined;
46
51
  /**
47
52
  * Map of custom renderers for specific properties.
53
+ * @deprecated Use lookupComponent instead.
48
54
  */
49
55
  Custom?: Partial<Record<string, InputComponent<T>>>;
50
56
  } & Pick<FormOptions<T>, 'schema' | 'onValuesChanged' | 'onValidate' | 'onSave'>
@@ -68,6 +74,7 @@ export const Form = <T extends BaseObject>({
68
74
  onValidate,
69
75
  onSave,
70
76
  onCancel,
77
+ lookupComponent,
71
78
  Custom,
72
79
  }: FormProps<T>) => {
73
80
  const onValid = useMemo(() => (autoSave ? onSave : undefined), [autoSave, onSave]);
@@ -109,6 +116,28 @@ export const Form = <T extends BaseObject>({
109
116
  const label = title ?? pipe(name, capitalize);
110
117
  const placeholder = examples?.length ? `Example: "${examples[0]}"` : description;
111
118
 
119
+ const FoundComponent = lookupComponent?.({
120
+ prop: name,
121
+ schema: S.make(ast),
122
+ inputProps: {
123
+ property: name,
124
+ type,
125
+ format,
126
+ label,
127
+ disabled: readonly,
128
+ placeholder,
129
+ ...inputProps,
130
+ },
131
+ });
132
+
133
+ if (FoundComponent) {
134
+ return (
135
+ <div key={name} role='none' className={padding}>
136
+ {FoundComponent}
137
+ </div>
138
+ );
139
+ }
140
+
112
141
  // Get generic input.
113
142
  // TODO(ZaymonFC): We might need to switch this to using globs since we're now indexing into arrays.
114
143
  let InputComponent = Custom?.[key.join('.')];
@@ -5,13 +5,13 @@
5
5
  import '@dxos-theme';
6
6
 
7
7
  import { type Meta, type StoryObj } from '@storybook/react';
8
- import React, { useCallback, useState } from 'react';
8
+ import React, { useCallback, useMemo, useState } from 'react';
9
9
 
10
10
  import { Format, type EchoSchema, S, toJsonSchema, TypedObject } from '@dxos/echo-schema';
11
- import { useSpace } from '@dxos/react-client/echo';
11
+ import { Filter, useQuery, useSpace } from '@dxos/react-client/echo';
12
12
  import { withClientProvider } from '@dxos/react-client/testing';
13
13
  import { useAsyncEffect } from '@dxos/react-ui';
14
- import { type ViewType, ViewProjection, createView } from '@dxos/schema';
14
+ import { ViewProjection, ViewType, createView } from '@dxos/schema';
15
15
  import { withTheme, withLayout } from '@dxos/storybook-utils';
16
16
 
17
17
  import { ViewEditor } from './ViewEditor';
@@ -41,6 +41,21 @@ const DefaultStory = () => {
41
41
  }
42
42
  }, [space]);
43
43
 
44
+ const views = useQuery(space, Filter.schema(ViewType));
45
+ const currentTypename = useMemo(() => view?.query?.type, [view]);
46
+ const updateViewTypename = useCallback(
47
+ (newTypename: string) => {
48
+ if (!schema) {
49
+ return;
50
+ }
51
+ const matchingViews = views.filter((view) => view.query.type === currentTypename);
52
+ for (const view of matchingViews) {
53
+ view.query.type = newTypename;
54
+ }
55
+ schema.updateTypename(newTypename);
56
+ },
57
+ [views, schema],
58
+ );
44
59
  const handleDelete = useCallback((property: string) => projection?.deleteFieldProjection(property), [projection]);
45
60
 
46
61
  if (!schema || !view || !projection) {
@@ -50,7 +65,13 @@ const DefaultStory = () => {
50
65
  return (
51
66
  <TestLayout json={{ schema, view, projection }}>
52
67
  <TestPanel>
53
- <ViewEditor schema={schema} view={view} registry={space?.db.schemaRegistry} onDelete={handleDelete} />
68
+ <ViewEditor
69
+ schema={schema}
70
+ view={view}
71
+ registry={space?.db.schemaRegistry}
72
+ onTypenameChanged={updateViewTypename}
73
+ onDelete={handleDelete}
74
+ />
54
75
  </TestPanel>
55
76
  </TestLayout>
56
77
  );
@@ -35,6 +35,7 @@ export type ViewEditorProps = ThemedClassName<{
35
35
  registry?: SchemaRegistry;
36
36
  readonly?: boolean;
37
37
  showHeading?: boolean;
38
+ onTypenameChanged: (typename: string) => void;
38
39
  onDelete: (fieldId: string) => void;
39
40
  }>;
40
41
 
@@ -48,6 +49,7 @@ export const ViewEditor = ({
48
49
  registry,
49
50
  readonly,
50
51
  showHeading = false,
52
+ onTypenameChanged,
51
53
  onDelete,
52
54
  }: ViewEditorProps) => {
53
55
  const { t } = useTranslation(translationKey);
@@ -67,12 +69,15 @@ export const ViewEditor = ({
67
69
  const handleViewUpdate = useCallback(
68
70
  ({ name, typename }: ViewMetaType) => {
69
71
  requestAnimationFrame(() => {
70
- view.name = name;
71
- view.query.type = typename;
72
- schema.updateTypename(typename);
72
+ if (view.name !== name) {
73
+ view.name = name;
74
+ }
75
+ if (view.query.type !== typename) {
76
+ onTypenameChanged(typename);
77
+ }
73
78
  });
74
79
  },
75
- [view, schema],
80
+ [view, schema, onTypenameChanged],
76
81
  );
77
82
 
78
83
  const handleSelect = useCallback((field: FieldType) => {