@dxos/react-ui-form 0.8.4-main.b97322e → 0.8.4-main.f5c0578

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 (59) hide show
  1. package/dist/lib/browser/index.mjs +168 -149
  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 +168 -149
  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.map +1 -1
  8. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +2 -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.map +1 -1
  11. package/dist/types/src/components/Form/ArrayField.d.ts.map +1 -1
  12. package/dist/types/src/components/Form/Defaults.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.map +1 -1
  16. package/dist/types/src/components/Form/FormActions.d.ts.map +1 -1
  17. package/dist/types/src/components/Form/FormContent.d.ts +1 -1
  18. package/dist/types/src/components/Form/FormContent.d.ts.map +1 -1
  19. package/dist/types/src/components/Form/FormContext.d.ts +1 -1
  20. package/dist/types/src/components/Form/FormContext.d.ts.map +1 -1
  21. package/dist/types/src/components/Form/RefField.d.ts +1 -1
  22. package/dist/types/src/components/Form/RefField.d.ts.map +1 -1
  23. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +1 -1
  24. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +1 -1
  25. package/dist/types/src/components/Form/factory.d.ts.map +1 -1
  26. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  27. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  28. package/dist/types/src/components/deprecated/Form.d.ts.map +1 -1
  29. package/dist/types/src/components/deprecated/Form.stories.d.ts.map +1 -1
  30. package/dist/types/src/hooks/useForm.d.ts +1 -1
  31. package/dist/types/src/hooks/useForm.d.ts.map +1 -1
  32. package/dist/types/src/translations.d.ts +3 -2
  33. package/dist/types/src/translations.d.ts.map +1 -1
  34. package/dist/types/tsconfig.tsbuildinfo +1 -1
  35. package/package.json +30 -29
  36. package/src/components/ControlSection/ControlSection.stories.tsx +2 -1
  37. package/src/components/FieldEditor/FieldEditor.stories.tsx +4 -3
  38. package/src/components/FieldEditor/FieldEditor.test.tsx +3 -2
  39. package/src/components/FieldEditor/FieldEditor.tsx +16 -6
  40. package/src/components/Form/ArrayField.tsx +7 -6
  41. package/src/components/Form/Defaults.tsx +2 -1
  42. package/src/components/Form/Form.stories.tsx +4 -3
  43. package/src/components/Form/Form.tsx +4 -3
  44. package/src/components/Form/FormActions.tsx +2 -1
  45. package/src/components/Form/FormContent.tsx +6 -5
  46. package/src/components/Form/FormContext.tsx +5 -5
  47. package/src/components/Form/RefField.tsx +6 -5
  48. package/src/components/Form/custom/GeoPointInput.tsx +2 -2
  49. package/src/components/Form/custom/SelectOptionsInput.stories.tsx +3 -2
  50. package/src/components/Form/custom/SelectOptionsInput.tsx +28 -29
  51. package/src/components/Form/factory.tsx +1 -1
  52. package/src/components/ViewEditor/ViewEditor.stories.tsx +5 -4
  53. package/src/components/ViewEditor/ViewEditor.test.tsx +5 -4
  54. package/src/components/ViewEditor/ViewEditor.tsx +69 -54
  55. package/src/components/deprecated/Form.stories.tsx +3 -2
  56. package/src/components/deprecated/Form.tsx +3 -3
  57. package/src/hooks/useForm.ts +5 -3
  58. package/src/hooks/useRefQueryLookupHandler.ts +1 -1
  59. package/src/translations.ts +3 -2
@@ -11,18 +11,19 @@ import { invariant } from '@dxos/invariant';
11
11
  import { Callout, IconButton, type ThemedClassName, useTranslation } from '@dxos/react-ui';
12
12
  import { List } from '@dxos/react-ui-list';
13
13
  import { cardSpacing } from '@dxos/react-ui-stack';
14
- import { ghostHover, inputTextLabel, mx } from '@dxos/react-ui-theme';
15
- import { FieldSchema, type FieldType, VIEW_FIELD_LIMIT, type DataType, ProjectionModel } from '@dxos/schema';
14
+ import { inputTextLabel, mx, subtleHover } from '@dxos/react-ui-theme';
15
+ import { type DataType, FieldSchema, type FieldType, ProjectionModel, VIEW_FIELD_LIMIT } from '@dxos/schema';
16
16
 
17
17
  import { translationKey } from '../../translations';
18
18
  import { FieldEditor } from '../FieldEditor';
19
19
  import { Form, type FormProps } from '../Form';
20
20
 
21
- const grid = 'grid grid-cols-[32px_1fr_32px_32px] min-bs-[2.5rem]';
21
+ const listGrid = 'grid grid-cols-[min-content_1fr_min-content_min-content_min-content]';
22
+ const listItemGrid = 'grid grid-cols-subgrid col-span-5';
22
23
 
23
24
  const ViewMetaSchema = Schema.Struct({
24
25
  typename: Format.URL.annotations({
25
- title: 'Typename',
26
+ title: 'Record type',
26
27
  }),
27
28
  }).pipe(Schema.mutable);
28
29
 
@@ -60,7 +61,7 @@ export const ViewEditor = ({
60
61
  const jsonSchema = schema instanceof EchoSchema ? schema.jsonSchema : toJsonSchema(schema);
61
62
  return new ProjectionModel(jsonSchema, view.projection);
62
63
  }, [schema, view.projection]);
63
- const [field, setField] = useState<FieldType>();
64
+ const [expandedField, setExpandedField] = useState<FieldType['id']>();
64
65
  const readonly = _readonly || !isMutable(schema);
65
66
 
66
67
  // TODO(burdon): Should be reactive.
@@ -72,12 +73,9 @@ export const ViewEditor = ({
72
73
  };
73
74
  }, [view.query.typename]);
74
75
 
75
- const handleSelect = useCallback(
76
+ const handleToggleField = useCallback(
76
77
  (field: FieldType) => {
77
- if (readonly) {
78
- return;
79
- }
80
- setField((f) => (f === field ? undefined : field));
78
+ setExpandedField((prevExpandedFieldId) => (prevExpandedFieldId === field.id ? undefined : field.id));
81
79
  },
82
80
  [readonly],
83
81
  );
@@ -87,7 +85,7 @@ export const ViewEditor = ({
87
85
  const handleAdd = useCallback(() => {
88
86
  invariant(!readonly);
89
87
  const field = projection.createFieldProjection();
90
- setField(field);
88
+ setExpandedField(field.id);
91
89
  }, [schema, projection, readonly]);
92
90
 
93
91
  const handleUpdate = useCallback(
@@ -105,13 +103,13 @@ export const ViewEditor = ({
105
103
  const handleDelete = useCallback(
106
104
  (fieldId: string) => {
107
105
  invariant(!readonly);
108
- if (fieldId === field?.id) {
109
- setField(undefined);
106
+ if (fieldId === expandedField) {
107
+ setExpandedField(undefined);
110
108
  }
111
109
 
112
110
  onDelete?.(fieldId);
113
111
  },
114
- [field, onDelete, readonly],
112
+ [expandedField, onDelete, readonly],
115
113
  );
116
114
 
117
115
  const handleMove = useCallback(
@@ -126,13 +124,13 @@ export const ViewEditor = ({
126
124
  [view.projection.fields, readonly],
127
125
  );
128
126
 
129
- const handleClose = useCallback(() => setField(undefined), []);
127
+ const handleClose = useCallback(() => setExpandedField(undefined), []);
130
128
 
131
129
  const hiddenProperties = projection.getHiddenProperties();
132
130
 
133
131
  const handleHide = useCallback(
134
132
  (fieldId: string) => {
135
- setField(undefined);
133
+ setExpandedField(undefined);
136
134
  projection.hideFieldProjection(fieldId);
137
135
  },
138
136
  [projection],
@@ -140,7 +138,7 @@ export const ViewEditor = ({
140
138
 
141
139
  const handleShow = useCallback(
142
140
  (property: string) => {
143
- setField(undefined);
141
+ setExpandedField(undefined);
144
142
  projection.showFieldProjection(property as JsonProp);
145
143
  },
146
144
  [projection],
@@ -149,11 +147,12 @@ export const ViewEditor = ({
149
147
  return (
150
148
  <div role='none' className={mx(classNames)}>
151
149
  {readonly && (
152
- <div role='none' className={mx('is-full', outerSpacing && 'plb-cardSpacingBlock pli-cardSpacingInline')}>
153
- <Callout.Root valence='info'>
154
- <Callout.Title>{t('system schema description')}</Callout.Title>
155
- </Callout.Root>
156
- </div>
150
+ <Callout.Root
151
+ valence='info'
152
+ classNames={['is-full mlb-cardSpacingBlock', outerSpacing && 'mli-cardSpacingInline']}
153
+ >
154
+ <Callout.Title>{t('system schema description')}</Callout.Title>
155
+ </Callout.Root>
157
156
  )}
158
157
 
159
158
  {/* TODO(burdon): Is the form read-only or just the schema? */}
@@ -168,46 +167,65 @@ export const ViewEditor = ({
168
167
  />
169
168
 
170
169
  <div role='none' className={outerSpacing ? cardSpacing : 'mlb-cardSpacingBlock'}>
171
- <label className={mx(inputTextLabel)}>{t('fields label')}</label>
170
+ <h2 className={mx(inputTextLabel)}>{t('fields label')}</h2>
172
171
 
173
172
  <List.Root<FieldType>
174
173
  items={view.projection.fields}
175
174
  isItem={Schema.is(FieldSchema)}
176
175
  getId={(field) => field.id}
177
- readonly={readonly}
178
176
  onMove={readonly ? undefined : handleMove}
177
+ readonly={readonly}
179
178
  >
180
179
  {({ items: fields }) => (
181
180
  <>
182
- {showHeading && (
183
- <div role='heading' className={grid}>
184
- <div />
185
- <div className='flex items-center text-sm'>{t('field path label')}</div>
186
- </div>
187
- )}
181
+ {showHeading && <h3 className='text-sm'>{t('field path label')}</h3>}
188
182
 
189
- <div role='list' className='flex flex-col is-full'>
183
+ <div role='list' className={listGrid}>
190
184
  {fields?.map((field) => (
191
185
  <List.Item<FieldType>
192
186
  key={field.id}
193
187
  item={field}
194
- classNames={mx(grid, ghostHover, 'overflow-hidden', !readonly && 'cursor-pointer')}
188
+ classNames={listItemGrid}
189
+ aria-expanded={expandedField === field.id}
195
190
  >
196
- <List.ItemDragHandle />
197
- <List.ItemTitle onClick={() => handleSelect(field)}>{field.path}</List.ItemTitle>
198
- <List.ItemButton
199
- data-testid='hide-field-button'
200
- icon='ph--eye-slash--regular'
201
- // TDOO(burdon): Is this the correct test?
202
- disabled={view.projection.fields.length <= 1}
203
- onClick={() => handleHide(field.id)}
204
- />
205
- {/* TODO(burdon): Remove unless implement undo. */}
206
- {!readonly && (
191
+ <div role='none' className={mx(subtleHover, listItemGrid, 'rounded-sm cursor-pointer min-bs-10')}>
192
+ <List.ItemDragHandle disabled={readonly} />
193
+ <List.ItemTitle onClick={() => handleToggleField(field)}>{field.path}</List.ItemTitle>
194
+ <List.ItemButton
195
+ label={t('hide field label')}
196
+ data-testid='hide-field-button'
197
+ icon='ph--eye-slash--regular'
198
+ autoHide={false}
199
+ // TDOO(burdon): Is this the correct test?
200
+ disabled={readonly || view.projection.fields.length <= 1}
201
+ onClick={() => handleHide(field.id)}
202
+ />
203
+ {/* TODO(burdon): Remove unless implement undo. */}
207
204
  <List.ItemDeleteButton
208
- disabled={view.projection.fields.length <= 1}
205
+ label={t('delete field label')}
206
+ autoHide={false}
207
+ disabled={readonly || view.projection.fields.length <= 1}
209
208
  onClick={() => handleDelete(field.id)}
209
+ data-testid='field.delete'
210
210
  />
211
+ <IconButton
212
+ iconOnly
213
+ variant='ghost'
214
+ label={t('toggle expand label', { ns: 'os' })}
215
+ icon={expandedField === field.id ? 'ph--caret-down--regular' : 'ph--caret-right--regular'}
216
+ onClick={() => handleToggleField(field)}
217
+ />
218
+ </div>
219
+ {expandedField === field.id && (
220
+ <div role='none' className='col-span-5'>
221
+ <FieldEditor
222
+ readonly={readonly}
223
+ projection={projection}
224
+ field={field}
225
+ registry={registry}
226
+ onSave={handleClose}
227
+ />
228
+ </div>
211
229
  )}
212
230
  </List.Item>
213
231
  ))}
@@ -218,7 +236,7 @@ export const ViewEditor = ({
218
236
 
219
237
  {hiddenProperties.length > 0 && (
220
238
  <>
221
- <label className={mx(inputTextLabel)}>{t('hidden fields label')}</label>
239
+ <h2 className={mx(inputTextLabel)}>{t('hidden fields label')}</h2>
222
240
 
223
241
  <List.Root<string>
224
242
  items={hiddenProperties}
@@ -226,16 +244,17 @@ export const ViewEditor = ({
226
244
  getId={(property) => property}
227
245
  >
228
246
  {({ items: properties }) => (
229
- <div role='list' className='flex flex-col is-full'>
247
+ <div role='list' className='grid grid-cols-[1fr_min-content]'>
230
248
  {properties?.map((property) => (
231
249
  <List.Item<string>
232
250
  key={property}
233
251
  item={property}
234
- classNames={mx(grid, ghostHover, !readonly && 'cursor-pointer')}
252
+ classNames={mx('grid grid-cols-subgrid col-span-2 rounded-sm', subtleHover)}
235
253
  >
236
- <List.ItemDragHandle disabled />
237
254
  <List.ItemTitle>{property}</List.ItemTitle>
238
255
  <List.ItemButton
256
+ label={t('show field label')}
257
+ disabled={readonly}
239
258
  icon='ph--eye--regular'
240
259
  onClick={() => handleShow(property)}
241
260
  data-testid='show-field-button'
@@ -249,11 +268,7 @@ export const ViewEditor = ({
249
268
  )}
250
269
  </div>
251
270
 
252
- {field && (
253
- <FieldEditor key={field.id} projection={projection} field={field} registry={registry} onSave={handleClose} />
254
- )}
255
-
256
- {!readonly && !field && (
271
+ {!readonly && !expandedField && (
257
272
  <div role='none' className={outerSpacing ? cardSpacing : 'mlb-cardSpacingBlock'}>
258
273
  <IconButton
259
274
  icon='ph--plus--regular'
@@ -261,7 +276,7 @@ export const ViewEditor = ({
261
276
  onClick={readonly ? undefined : handleAdd}
262
277
  // TODO(burdon): Show field limit in ux (not tooltip).
263
278
  disabled={view.projection.fields.length >= VIEW_FIELD_LIMIT}
264
- classNames='flex is-full'
279
+ classNames='is-full'
265
280
  />
266
281
  </div>
267
282
  )}
@@ -8,13 +8,14 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
8
8
  import React from 'react';
9
9
 
10
10
  import { live } from '@dxos/live-object';
11
- import { testData, TestSchema, testView, type TestType } from '@dxos/schema/testing';
11
+ import { TestSchema, type TestType, testData, testView } from '@dxos/schema/testing';
12
12
  import { withLayout, withTheme } from '@dxos/storybook-utils';
13
13
 
14
- import { DeprecatedForm, type DeprecatedFormProps } from './Form';
15
14
  import { translations } from '../../translations';
16
15
  import { TestLayout, TestPanel } from '../testing';
17
16
 
17
+ import { DeprecatedForm, type DeprecatedFormProps } from './Form';
18
+
18
19
  const DefaultStory = (props: DeprecatedFormProps) => (
19
20
  <TestLayout json={{ props }}>
20
21
  <TestPanel>
@@ -2,13 +2,13 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { SchemaAST, type Schema, Option, pipe } from 'effect';
5
+ import { Option, type Schema, SchemaAST, pipe } from 'effect';
6
6
  import React, { useState } from 'react';
7
7
 
8
8
  import { findProperty } from '@dxos/effect';
9
- import { Input, type ThemedClassName, type TextInputProps as NativeTextInputProps } from '@dxos/react-ui';
9
+ import { Input, type TextInputProps as NativeTextInputProps, type ThemedClassName } from '@dxos/react-ui';
10
10
  import { mx } from '@dxos/react-ui-theme';
11
- import { getFieldValue, setFieldValue, type Projection } from '@dxos/schema';
11
+ import { type Projection, getFieldValue, setFieldValue } from '@dxos/schema';
12
12
 
13
13
  export type DeprecatedFormProps<T extends {} = {}> = ThemedClassName<{
14
14
  projection: Projection;
@@ -2,14 +2,14 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { SchemaAST, type Schema } from 'effect';
5
+ import { type Schema, SchemaAST } from 'effect';
6
6
  import { useCallback, useEffect, useMemo, useState } from 'react';
7
7
 
8
8
  import { type BaseObject, getValue, setValue } from '@dxos/echo-schema';
9
- import { type SimpleType, type JsonPath, createJsonPath, fromEffectValidationPath } from '@dxos/effect';
9
+ import { type JsonPath, type SimpleType, createJsonPath, fromEffectValidationPath } from '@dxos/effect';
10
10
  import { invariant } from '@dxos/invariant';
11
11
  import { log } from '@dxos/log';
12
- import { validateSchema, type ValidationError } from '@dxos/schema';
12
+ import { type ValidationError, validateSchema } from '@dxos/schema';
13
13
  import { type MaybePromise } from '@dxos/util';
14
14
 
15
15
  /**
@@ -26,6 +26,8 @@ export type FormHandler<T extends BaseObject> = {
26
26
  changed: Record<JsonPath, boolean>;
27
27
  canSave: boolean;
28
28
  formIsValid: boolean;
29
+
30
+ // TODO(burdon): Return onSave.
29
31
  handleSave: () => void;
30
32
 
31
33
  //
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { useCallback } from 'react';
6
6
 
7
- import { getObjectDXN, type TypeAnnotation } from '@dxos/echo-schema';
7
+ import { type TypeAnnotation, getObjectDXN } from '@dxos/echo-schema';
8
8
  import { Filter, type Space } from '@dxos/react-client/echo';
9
9
  import { isNonNullable } from '@dxos/util';
10
10
 
@@ -25,6 +25,7 @@ export const translations = [
25
25
  'field limit reached': 'Maximum number of fields reached',
26
26
  'boolean input true value': 'Yes',
27
27
  'boolean input false value': 'No',
28
+ 'delete field label': 'Delete field',
28
29
 
29
30
  // TODO(burdon): Factor out?
30
31
  'button cancel': 'Cancel',
@@ -72,8 +73,8 @@ export const translations = [
72
73
  'select option add': 'Add option',
73
74
 
74
75
  // System schema message.
75
- 'system schema title': 'System Schema',
76
- 'system schema description': 'This is a system schema and cannot be modified.',
76
+ 'system schema title': 'System record type',
77
+ 'system schema description': 'This is a system record type and cannot be modified.',
77
78
  },
78
79
  },
79
80
  },