@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
@@ -6,32 +6,23 @@ import * as Array from 'effect/Array';
6
6
  import * as Match from 'effect/Match';
7
7
  import * as Option from 'effect/Option';
8
8
  import * as Schema from 'effect/Schema';
9
- import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
9
+ import React, { forwardRef, useCallback, useImperativeHandle, useMemo, useState } from 'react';
10
10
 
11
+ import { QueryAST } from '@dxos/echo';
11
12
  import { EchoSchema, Format, type JsonProp, isMutable, toJsonSchema } from '@dxos/echo/internal';
12
- import { type SchemaRegistry } from '@dxos/echo-db';
13
+ import { Filter, Query, type SchemaRegistry } from '@dxos/echo-db';
13
14
  import { invariant } from '@dxos/invariant';
14
- import {
15
- Callout,
16
- IconButton,
17
- Input,
18
- type ThemedClassName,
19
- useDensityContext,
20
- useElevationContext,
21
- useThemeContext,
22
- useTranslation,
23
- } from '@dxos/react-ui';
24
- import { Editor, createBasicExtensions, createThemeExtensions } from '@dxos/react-ui-editor';
15
+ import { Callout, IconButton, Input, type ThemedClassName, useTranslation } from '@dxos/react-ui';
16
+ import { QueryForm, type QueryFormProps } from '@dxos/react-ui-components';
25
17
  import { List } from '@dxos/react-ui-list';
26
18
  import { cardSpacing } from '@dxos/react-ui-stack';
27
- import { inputTheme } from '@dxos/react-ui-theme';
28
19
  import { inputTextLabel, mx, subtleHover } from '@dxos/react-ui-theme';
29
20
  import {
30
- type DataType,
31
21
  FieldSchema,
32
22
  type FieldType,
33
23
  ProjectionModel,
34
24
  VIEW_FIELD_LIMIT,
25
+ type View,
35
26
  getTypenameFromQuery,
36
27
  } from '@dxos/schema';
37
28
 
@@ -45,14 +36,15 @@ const listItemGrid = 'grid grid-cols-subgrid col-span-5';
45
36
  export type ViewEditorProps = ThemedClassName<
46
37
  {
47
38
  schema: Schema.Schema.AnyNoContext;
48
- view: DataType.View;
49
- mode?: 'schema' | 'query';
39
+ view: View.View;
40
+ mode?: 'schema' | 'tag';
50
41
  registry?: SchemaRegistry;
51
42
  readonly?: boolean;
52
43
  showHeading?: boolean;
53
- onQueryChanged?: (query: string, target?: string) => void;
44
+ onQueryChanged?: (query: QueryAST.Query, target?: string) => void;
54
45
  onDelete?: (fieldId: string) => void;
55
- } & Pick<FormProps<any>, 'outerSpacing'>
46
+ } & Pick<FormProps<any>, 'outerSpacing'> &
47
+ Pick<QueryFormProps, 'types' | 'tags'>
56
48
  >;
57
49
 
58
50
  /**
@@ -68,6 +60,8 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
68
60
  registry,
69
61
  readonly,
70
62
  showHeading = false,
63
+ types,
64
+ tags,
71
65
  onQueryChanged,
72
66
  onDelete,
73
67
  outerSpacing = true,
@@ -84,18 +78,6 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
84
78
  useImperativeHandle(forwardedRef, () => projectionModel, [projectionModel]);
85
79
  const [expandedField, setExpandedField] = useState<FieldType['id']>();
86
80
 
87
- const serializedQuery = Match.value(mode).pipe(
88
- Match.when('schema', () => getTypenameFromQuery(view.query.ast)),
89
- Match.when('query', () => {
90
- if (view.query.string) {
91
- return view.query.string;
92
- } else {
93
- return 'Serializing query AST is not currently supported.';
94
- }
95
- }),
96
- Match.exhaustive,
97
- );
98
-
99
81
  const queueTarget = Match.value(view.query.ast).pipe(
100
82
  Match.when({ type: 'options' }, ({ options }) => {
101
83
  return Option.fromNullable(options.queues).pipe(
@@ -111,12 +93,11 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
111
93
  query:
112
94
  mode === 'schema'
113
95
  ? Format.URL.annotations({ title: 'Record type' })
114
- : Schema.String.annotations({ title: 'Query' }),
96
+ : QueryAST.Query.annotations({ title: 'Query' }),
115
97
  });
116
98
 
117
- if (mode === 'query') {
99
+ if (mode === 'tag') {
118
100
  return Schema.Struct({
119
- name: Schema.optional(Schema.String.annotations({ title: 'Name' })),
120
101
  ...base.fields,
121
102
  target: Schema.optional(Schema.String.annotations({ title: 'Target Queue' })),
122
103
  }).pipe(Schema.mutable);
@@ -124,11 +105,15 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
124
105
 
125
106
  return base.pipe(Schema.mutable);
126
107
  }, [mode]);
108
+
127
109
  // TODO(burdon): Need to warn user of possible consequences of editing.
128
110
  // TODO(burdon): Settings should have domain name owned by user.
129
111
  const viewValues = useMemo(
130
- () => ({ name: view.name, query: serializedQuery, target: queueTarget }),
131
- [view.name, serializedQuery, queueTarget],
112
+ () => ({
113
+ query: mode === 'schema' ? getTypenameFromQuery(view.query.ast) : view.query.ast,
114
+ target: queueTarget,
115
+ }),
116
+ [mode, view.query.ast, queueTarget],
132
117
  );
133
118
 
134
119
  const handleToggleField = useCallback(
@@ -147,23 +132,13 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
147
132
  }, [schema, projectionModel, readonly]);
148
133
 
149
134
  const handleUpdate = useCallback(
150
- (values: Schema.Schema.Type<typeof viewSchema>) => {
151
- invariant(!readonly);
135
+ (values: any) => {
152
136
  requestAnimationFrame(() => {
153
- if ('name' in values && view.name !== values.name) {
154
- view.name = values.name;
155
- }
156
-
157
- const queryChanged = serializedQuery !== values.query;
158
- const targetValue = 'target' in values ? values.target : undefined;
159
- const targetChanged = 'target' in values && values.target && queueTarget !== targetValue;
160
-
161
- if ((queryChanged || targetChanged) && !readonly) {
162
- onQueryChanged?.(values.query, targetValue);
163
- }
137
+ const query = mode === 'schema' ? Query.select(Filter.typename(values.query)).ast : values.query;
138
+ onQueryChanged?.(query, values.target);
164
139
  });
165
140
  },
166
- [serializedQuery, onQueryChanged, readonly, view, queueTarget],
141
+ [onQueryChanged, view, queueTarget, mode],
167
142
  );
168
143
 
169
144
  const handleDelete = useCallback(
@@ -208,9 +183,12 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
208
183
  [projectionModel],
209
184
  );
210
185
 
186
+ const custom = useMemo(() => (mode === 'tag' ? customFields({ types, tags }) : undefined), [types, tags, mode]);
187
+
211
188
  return (
212
189
  <div role='none' className={mx(classNames)}>
213
- {schemaReadonly && mode === 'schema' && (
190
+ {/* If readonlyProp is set, then the callout is not needed. */}
191
+ {schemaReadonly && !readonly && (
214
192
  <Callout.Root valence='info' classNames={['mlb-cardSpacingBlock', outerSpacing && 'mli-cardSpacingInline']}>
215
193
  <Callout.Title>{t('system schema description')}</Callout.Title>
216
194
  </Callout.Root>
@@ -222,10 +200,9 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
222
200
  autoSave
223
201
  schema={viewSchema}
224
202
  values={viewValues}
225
- readonly={readonly ? 'disabled-input' : false}
226
203
  onSave={handleUpdate}
227
204
  outerSpacing={outerSpacing}
228
- Custom={mode === 'query' ? customFields : undefined}
205
+ Custom={custom}
229
206
  />
230
207
 
231
208
  <div role='none' className={outerSpacing ? cardSpacing : 'mlb-cardSpacingBlock'}>
@@ -255,7 +232,7 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
255
232
  role='none'
256
233
  className={mx(subtleHover, listItemGrid, 'rounded-sm cursor-pointer min-bs-10')}
257
234
  >
258
- <List.ItemDragHandle disabled={readonly} />
235
+ <List.ItemDragHandle disabled={readonly || schemaReadonly} />
259
236
  <List.ItemTitle
260
237
  classNames={hidden && 'text-subdued'}
261
238
  onClick={() => handleToggleField(field)}
@@ -270,7 +247,7 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
270
247
  disabled={readonly || (!hidden && projectionModel.fields.length <= 1)}
271
248
  onClick={() => (hidden ? handleShow(field.path) : handleHide(field.id))}
272
249
  />
273
- {mode === 'schema' && (
250
+ {!readonly && (
274
251
  <>
275
252
  <List.ItemDeleteButton
276
253
  label={t('delete field label')}
@@ -291,7 +268,7 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
291
268
  </>
292
269
  )}
293
270
  </div>
294
- {expandedField === field.id && mode === 'schema' && (
271
+ {expandedField === field.id && !readonly && (
295
272
  <div role='none' className='col-span-5 mbs-1 mbe-1 border border-separator rounded-md'>
296
273
  <FieldEditor
297
274
  readonly={readonly || schemaReadonly ? 'disabled-input' : false}
@@ -311,11 +288,11 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
311
288
  </List.Root>
312
289
  </div>
313
290
 
314
- {!readonly && !expandedField && mode === 'schema' && (
291
+ {!readonly && !expandedField && (
315
292
  <div role='none' className={outerSpacing ? cardSpacing : 'mlb-cardSpacingBlock'}>
316
293
  <IconButton
317
294
  icon='ph--plus--regular'
318
- label={t('button add property')}
295
+ label={t('add property button label')}
319
296
  onClick={readonly ? undefined : handleAdd}
320
297
  // TODO(burdon): Show field limit in ux (not tooltip).
321
298
  disabled={view.projection.fields.length >= VIEW_FIELD_LIMIT}
@@ -328,51 +305,18 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
328
305
  },
329
306
  );
330
307
 
331
- const customFields: Record<string, InputComponent> = {
308
+ const customFields = ({ types, tags }: Pick<ViewEditorProps, 'types' | 'tags'>): Record<string, InputComponent> => ({
332
309
  query: (props: InputProps) => {
333
- const { themeMode } = useThemeContext();
334
- const { t } = useTranslation(translationKey);
335
- const density = useDensityContext();
336
- const elevation = useElevationContext();
337
-
338
- const onValueChange = useRef(props.onValueChange);
339
- useEffect(() => {
340
- onValueChange.current = props.onValueChange;
341
- }, [props.onValueChange]);
342
- // NOTE: Including props.onValueChange in deps causes infinite loop with Editor.
343
- const handleChange = useCallback((text: string) => onValueChange.current('string', text), []);
344
-
345
- const extensions = useMemo(
346
- () => [createBasicExtensions({ placeholder: t('query placeholder') }), createThemeExtensions({ themeMode })],
347
- [],
310
+ const handleChange = useCallback(
311
+ (query: Query.Any) => props.onValueChange('object', query.ast),
312
+ [props.onValueChange],
348
313
  );
349
314
 
350
- // TODO(wittjosiah): This is probably not the right way to do these styles.
351
315
  return (
352
316
  <Input.Root>
353
317
  <InputHeader label={props.label} />
354
- <Editor
355
- classNames={mx(
356
- inputTheme.input({ density, elevation }),
357
- 'flex items-center',
358
- 'focus-within:bg-focusSurface focus-within:border-separator focus-within:hover:bg-focusSurface',
359
- )}
360
- extensions={extensions}
361
- value={props.getValue()}
362
- onChange={handleChange}
363
- />
364
- {/* TODO(wittjosiah): Support query editor.
365
- <QueryEditor
366
- classNames={mx(
367
- inputTheme.input({ density, elevation }),
368
- 'flex items-center',
369
- 'focus-within:bg-focusSurface focus-within:border-separator focus-within:hover:bg-focusSurface',
370
- )}
371
- space={space}
372
- value={props.getValue()}
373
- onChange={handleChange}
374
- /> */}
318
+ <QueryForm initialQuery={props.getValue()} types={types} tags={tags} onChange={handleChange} />
375
319
  </Input.Root>
376
320
  );
377
321
  },
378
- };
322
+ });
@@ -6,7 +6,7 @@ import type * as Schema from 'effect/Schema';
6
6
  import * as SchemaAST from 'effect/SchemaAST';
7
7
  import { useCallback, useEffect, useMemo, useState } from 'react';
8
8
 
9
- import { type BaseObject, getValue, setValue } from '@dxos/echo/internal';
9
+ import { type AnyProperties, getValue, setValue } from '@dxos/echo/internal';
10
10
  import { type JsonPath, type SimpleType, createJsonPath, fromEffectValidationPath } from '@dxos/effect';
11
11
  import { invariant } from '@dxos/invariant';
12
12
  import { log } from '@dxos/log';
@@ -16,7 +16,7 @@ import { type MaybePromise } from '@dxos/util';
16
16
  /**
17
17
  * Return type from `useForm` hook.
18
18
  */
19
- export type FormHandler<T extends BaseObject> = {
19
+ export type FormHandler<T extends AnyProperties> = {
20
20
  //
21
21
  // Form state management.
22
22
  //
@@ -44,7 +44,7 @@ export type FormHandler<T extends BaseObject> = {
44
44
  /**
45
45
  * Hook options.
46
46
  */
47
- export interface FormOptions<T extends BaseObject> {
47
+ export interface FormOptions<T extends AnyProperties> {
48
48
  /**
49
49
  * Effect schema (Type literal).
50
50
  */
@@ -85,7 +85,7 @@ export interface FormOptions<T extends BaseObject> {
85
85
  * Creates a hook for managing form state, including values, validation, and submission.
86
86
  * Deeply integrated with `@dxos/schema` for schema-based validation.
87
87
  */
88
- export const useForm = <T extends BaseObject>({
88
+ export const useForm = <T extends AnyProperties>({
89
89
  schema,
90
90
  initialValues,
91
91
  onValuesChanged,
@@ -220,12 +220,13 @@ export const useForm = <T extends BaseObject>({
220
220
 
221
221
  const newValues = { ...setValue(values, jsonPath, parsedValue) };
222
222
  setValues(newValues);
223
- setChanged((prev) => ({ ...prev, [jsonPath]: true }));
223
+ const newChanged = { ...changed, [jsonPath]: true };
224
+ setChanged(newChanged);
224
225
  onValuesChanged?.(newValues);
225
226
 
226
227
  const isValid = validate(newValues);
227
228
  if (isValid && onValid) {
228
- onValid(newValues, { changed });
229
+ onValid(newValues, { changed: newChanged });
229
230
  }
230
231
  },
231
232
  [values, onValuesChanged, validate, onValid, changed],
@@ -275,7 +276,7 @@ export const useForm = <T extends BaseObject>({
275
276
  );
276
277
  };
277
278
 
278
- const createKeySet = <T extends BaseObject, V>(obj: T, value: V): Record<JsonPath, V> => {
279
+ const createKeySet = <T extends AnyProperties, V>(obj: T, value: V): Record<JsonPath, V> => {
279
280
  invariant(obj);
280
281
  return Object.keys(obj).reduce((acc, key) => ({ ...acc, [key]: value }), {} as Record<JsonPath, V>);
281
282
  };
@@ -2,7 +2,7 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { useEffect, useState } from 'react';
5
+ import { useCallback, useEffect, useState } from 'react';
6
6
 
7
7
  import { type TypeAnnotation } from '@dxos/echo/internal';
8
8
  import { type DXN } from '@dxos/keys';
@@ -25,9 +25,12 @@ type UseQueryRefOptionsProps = { refTypeInfo: TypeAnnotation | undefined; onQuer
25
25
  * Hook to query reference options based on type information.
26
26
  * Used internally within forms to fetch and format reference options for reference fields.
27
27
  */
28
+ // TODO(wittjosiah): This should be a reactive query so that the options are always up to date.
28
29
  export const useQueryRefOptions = ({ refTypeInfo, onQueryRefOptions }: UseQueryRefOptionsProps) => {
29
30
  const [options, setOptions] = useState<QueryTag[]>([]);
30
31
  const [loading, setLoading] = useState(false);
32
+ const [state, setState] = useState({});
33
+ const update = useCallback(() => setState({}), []);
31
34
 
32
35
  useEffect(() => {
33
36
  if (!refTypeInfo || !onQueryRefOptions) {
@@ -53,7 +56,7 @@ export const useQueryRefOptions = ({ refTypeInfo, onQueryRefOptions }: UseQueryR
53
56
  };
54
57
 
55
58
  void fetchOptions();
56
- }, [refTypeInfo, onQueryRefOptions]);
59
+ }, [refTypeInfo, onQueryRefOptions, state]);
57
60
 
58
- return { options, loading };
61
+ return { options, update, loading };
59
62
  };
@@ -4,7 +4,8 @@
4
4
 
5
5
  import { useCallback } from 'react';
6
6
 
7
- import { type TypeAnnotation, getObjectDXN } from '@dxos/echo/internal';
7
+ import { Obj } from '@dxos/echo';
8
+ import { type TypeAnnotation } from '@dxos/echo/internal';
8
9
  import { Filter, type Space } from '@dxos/react-client/echo';
9
10
  import { isNonNullable } from '@dxos/util';
10
11
 
@@ -18,18 +19,17 @@ export const useRefQueryLookupHandler = ({ space }: UseRefQueryLookupProps): Que
18
19
  if (!space) {
19
20
  return [];
20
21
  }
22
+
21
23
  const query = space.db.query(Filter.typename(typeInfo.typename));
22
24
  const { objects } = await query.run();
23
-
24
25
  return objects
25
26
  .map((object) => {
26
- const dxn = getObjectDXN(object);
27
+ const dxn = Obj.getDXN(object);
27
28
  if (!dxn) {
28
29
  return undefined;
29
30
  }
30
31
 
31
- // TODO(Zaymon): Better fallback object names?
32
- const item = { dxn, label: object?.name ?? object?.id ?? '' };
32
+ const item = { dxn, label: Obj.getLabel(object) ?? object?.id ?? '' };
33
33
  return item;
34
34
  })
35
35
  .filter(isNonNullable);
@@ -15,7 +15,7 @@ export const translations = [
15
15
  'empty readonly ref field label': '(none)',
16
16
 
17
17
  // TODO(burdon): Standardize field/property.
18
- 'button add property': 'Add property',
18
+ 'add property button label': 'Add property',
19
19
  'field property label': 'Property',
20
20
  'field property placeholder': 'Property name',
21
21
  'field format label': 'Type',
@@ -33,8 +33,8 @@ export const translations = [
33
33
  'ref field placeholder_other': 'Select items…',
34
34
 
35
35
  // TODO(burdon): Factor out?
36
- 'button cancel': 'Cancel',
37
- 'button save': 'Save',
36
+ 'cancel button label': 'Cancel',
37
+ 'save button label': 'Save',
38
38
 
39
39
  // FormatEnum
40
40
  'format boolean': 'Boolean',
@@ -78,9 +78,9 @@ export const translations = [
78
78
  'select option add': 'Add option',
79
79
 
80
80
  // System schema message.
81
- 'system schema title': 'System record type',
81
+ 'system schema title': 'System type',
82
82
  'system schema description':
83
- 'This is a system record type and cannot be modified, though the view can still be configured.',
83
+ 'This is a system type and cannot be modified, though the view can still be configured.',
84
84
  },
85
85
  },
86
86
  },
package/src/util/field.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { FormatEnum, GeoLocation, TypeEnum } from '@dxos/echo/internal';
5
+ import { Format, GeoLocation, TypeEnum } from '@dxos/echo/internal';
6
6
  import { type ValidationError } from '@dxos/schema';
7
7
 
8
8
  /**
@@ -13,7 +13,7 @@ import { type ValidationError } from '@dxos/schema';
13
13
  */
14
14
  export type ParseProps = {
15
15
  type?: TypeEnum;
16
- format?: FormatEnum;
16
+ format?: Format.TypeFormat;
17
17
  value: any;
18
18
  };
19
19
 
@@ -57,31 +57,31 @@ export const parseValue = ({ type, format, value }: ParseProps) => {
57
57
  }
58
58
 
59
59
  switch (format) {
60
- case FormatEnum.Boolean:
60
+ case Format.TypeFormat.Boolean:
61
61
  return parseScalar(TypeEnum.Boolean);
62
62
 
63
- case FormatEnum.Number:
64
- case FormatEnum.Percent:
65
- case FormatEnum.Currency:
63
+ case Format.TypeFormat.Number:
64
+ case Format.TypeFormat.Percent:
65
+ case Format.TypeFormat.Currency:
66
66
  return parseScalar(TypeEnum.Number);
67
67
 
68
- case FormatEnum.String:
69
- case FormatEnum.Markdown: {
68
+ case Format.TypeFormat.String:
69
+ case Format.TypeFormat.Markdown: {
70
70
  return parseScalar(TypeEnum.String);
71
71
  }
72
72
 
73
- case FormatEnum.Ref:
74
- throw new Error(`unexpected format: ${FormatEnum.Ref}`);
73
+ case Format.TypeFormat.Ref:
74
+ throw new Error(`unexpected format: ${Format.TypeFormat.Ref}`);
75
75
 
76
- case FormatEnum.DateTime:
77
- case FormatEnum.Date:
78
- case FormatEnum.Time:
79
- case FormatEnum.Timestamp: {
76
+ case Format.TypeFormat.DateTime:
77
+ case Format.TypeFormat.Date:
78
+ case Format.TypeFormat.Time:
79
+ case Format.TypeFormat.Timestamp: {
80
80
  const date = new Date(value as string | number);
81
81
  return isNaN(date.getTime()) ? null : date;
82
82
  }
83
83
 
84
- case FormatEnum.GeoPoint: {
84
+ case Format.TypeFormat.GeoPoint: {
85
85
  // Parse string in format "lat,long" to GeoPoint [longitude, latitude].
86
86
  if (typeof value === 'string') {
87
87
  // Only keep digits, decimal points, minus signs, and commas.
@@ -108,23 +108,23 @@ export const parseValue = ({ type, format, value }: ParseProps) => {
108
108
  // TODO(burdon): Type and format.
109
109
  export type CellClassesForFieldTypeProps = {
110
110
  type?: TypeEnum;
111
- format?: FormatEnum;
111
+ format?: Format.TypeFormat;
112
112
  };
113
113
 
114
114
  export const cellClassesForFieldType = ({ type, format }: CellClassesForFieldTypeProps): string[] | undefined => {
115
115
  switch (format || type) {
116
- case FormatEnum.Markdown:
116
+ case Format.TypeFormat.Markdown:
117
117
  return undefined;
118
- case FormatEnum.Time:
119
- case FormatEnum.Timestamp:
120
- case FormatEnum.DateTime:
121
- case FormatEnum.Date:
122
- case FormatEnum.Duration:
118
+ case Format.TypeFormat.Time:
119
+ case Format.TypeFormat.Timestamp:
120
+ case Format.TypeFormat.DateTime:
121
+ case Format.TypeFormat.Date:
122
+ case Format.TypeFormat.Duration:
123
123
  return ['font-mono', 'text-right'];
124
- case FormatEnum.Currency:
124
+ case Format.TypeFormat.Currency:
125
125
  return ['text-right'];
126
- case FormatEnum.JSON:
127
- case FormatEnum.DID:
126
+ case Format.TypeFormat.JSON:
127
+ case Format.TypeFormat.DID:
128
128
  return ['font-mono'];
129
129
  case TypeEnum.Number:
130
130
  case TypeEnum.Boolean:
@@ -4,11 +4,11 @@
4
4
 
5
5
  import { format as formatDate } from 'date-fns/format';
6
6
 
7
- import { FormatEnum, GeoLocation, TypeEnum } from '@dxos/echo/internal';
7
+ import { Format, GeoLocation, TypeEnum } from '@dxos/echo/internal';
8
8
 
9
9
  type ValueFormatProps = {
10
10
  type: TypeEnum;
11
- format?: FormatEnum | undefined;
11
+ format?: Format.TypeFormat | undefined;
12
12
  value: any;
13
13
  locale?: string | undefined;
14
14
  };
@@ -41,20 +41,20 @@ export const formatForDisplay = ({ type, format, value, locale = undefined }: Va
41
41
  }
42
42
 
43
43
  switch (format) {
44
- case FormatEnum.Boolean: {
44
+ case Format.TypeFormat.Boolean: {
45
45
  return formatScalar(TypeEnum.Boolean);
46
46
  }
47
- case FormatEnum.Number: {
47
+ case Format.TypeFormat.Number: {
48
48
  return formatScalar(TypeEnum.Number);
49
49
  }
50
- case FormatEnum.String:
51
- case FormatEnum.Ref: {
50
+ case Format.TypeFormat.String:
51
+ case Format.TypeFormat.Ref: {
52
52
  return formatScalar(TypeEnum.String);
53
53
  }
54
- case FormatEnum.Percent: {
54
+ case Format.TypeFormat.Percent: {
55
55
  return `${(value as number) * 100}%`;
56
56
  }
57
- case FormatEnum.Currency: {
57
+ case Format.TypeFormat.Currency: {
58
58
  // TODO(burdon): Get from property annotation.
59
59
  return (value as number).toLocaleString(locale, {
60
60
  style: 'currency',
@@ -63,28 +63,28 @@ export const formatForDisplay = ({ type, format, value, locale = undefined }: Va
63
63
  maximumFractionDigits: 2,
64
64
  });
65
65
  }
66
- case FormatEnum.Date: {
66
+ case Format.TypeFormat.Date: {
67
67
  try {
68
68
  return formatDate(new Date(value as number), 'yyyy-MM-dd');
69
69
  } catch (error) {
70
70
  return 'Invalid Date';
71
71
  }
72
72
  }
73
- case FormatEnum.Time: {
73
+ case Format.TypeFormat.Time: {
74
74
  try {
75
75
  return formatDate(new Date(value as number), 'HH:mm:ss');
76
76
  } catch (error) {
77
77
  return 'Invalid Time';
78
78
  }
79
79
  }
80
- case FormatEnum.DateTime: {
80
+ case Format.TypeFormat.DateTime: {
81
81
  try {
82
82
  return formatDate(new Date(value as number), 'yyyy-MM-dd HH:mm:ss');
83
83
  } catch (error) {
84
84
  return 'Invalid DateTime';
85
85
  }
86
86
  }
87
- case FormatEnum.GeoPoint: {
87
+ case Format.TypeFormat.GeoPoint: {
88
88
  if (value === null || value === undefined) {
89
89
  return '';
90
90
  }
@@ -125,31 +125,31 @@ export const formatForEditing = ({ type, format, value, locale = undefined }: Va
125
125
  }
126
126
 
127
127
  switch (format) {
128
- case FormatEnum.Boolean:
129
- case FormatEnum.Number:
130
- case FormatEnum.String:
131
- case FormatEnum.Ref: {
128
+ case Format.TypeFormat.Boolean:
129
+ case Format.TypeFormat.Number:
130
+ case Format.TypeFormat.String:
131
+ case Format.TypeFormat.Ref: {
132
132
  return formatScalar(type);
133
133
  }
134
- case FormatEnum.Percent: {
134
+ case Format.TypeFormat.Percent: {
135
135
  return String(value * 100);
136
136
  }
137
- case FormatEnum.Currency: {
137
+ case Format.TypeFormat.Currency: {
138
138
  return String(value);
139
139
  }
140
- case FormatEnum.DateTime: {
140
+ case Format.TypeFormat.DateTime: {
141
141
  const date = new Date(value as number);
142
142
  return date.toISOString();
143
143
  }
144
- case FormatEnum.Date: {
144
+ case Format.TypeFormat.Date: {
145
145
  const date = new Date(value as number);
146
146
  return date.toISOString().split('T')[0];
147
147
  }
148
- case FormatEnum.Time: {
148
+ case Format.TypeFormat.Time: {
149
149
  const date = new Date(value as number);
150
150
  return date.toISOString().split('T')[1].split('.')[0];
151
151
  }
152
- case FormatEnum.GeoPoint: {
152
+ case Format.TypeFormat.GeoPoint: {
153
153
  // Handle null or undefined
154
154
  if (value === null || value === undefined) {
155
155
  return '';
package/src/util/refs.ts CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  import type * as SchemaAST from 'effect/SchemaAST';
6
6
 
7
- import { FormatEnum, ReferenceAnnotationId } from '@dxos/echo/internal';
7
+ import { Format, ReferenceAnnotationId } from '@dxos/echo/internal';
8
8
  import { findAnnotation } from '@dxos/effect';
9
9
  import { type SchemaProperty } from '@dxos/schema';
10
10
 
@@ -18,11 +18,6 @@ type RefProps = {
18
18
  export const getRefProps = (property: SchemaProperty<any>): RefProps | undefined => {
19
19
  const { ast, format, array } = property;
20
20
 
21
- // Direct reference.
22
- if (format === FormatEnum.Ref) {
23
- return { ast, isArray: false };
24
- }
25
-
26
21
  // Array of references.
27
22
  if (array) {
28
23
  const elementType = findArrayElementType(ast);
@@ -34,5 +29,10 @@ export const getRefProps = (property: SchemaProperty<any>): RefProps | undefined
34
29
  }
35
30
  }
36
31
 
32
+ // Direct reference.
33
+ if (format === Format.TypeFormat.Ref) {
34
+ return { ast, isArray: false };
35
+ }
36
+
37
37
  return undefined;
38
38
  };