@dxos/react-ui-form 0.8.4-main.3c1ae3b → 0.8.4-main.3eb6e50203

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 (95) hide show
  1. package/dist/lib/browser/index.mjs +1610 -1825
  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 +1610 -1825
  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/FieldEditor/FieldEditor.d.ts +3 -2
  8. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  9. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +2 -2
  10. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
  11. package/dist/types/src/components/Form/Form-old.stories.d.ts +1 -42
  12. package/dist/types/src/components/Form/Form-old.stories.d.ts.map +1 -1
  13. package/dist/types/src/components/Form/Form.d.ts +9 -6
  14. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  15. package/dist/types/src/components/Form/Form.stories.d.ts +32 -32
  16. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  17. package/dist/types/src/components/Form/FormField.d.ts +2 -2
  18. package/dist/types/src/components/Form/FormField.d.ts.map +1 -1
  19. package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -1
  20. package/dist/types/src/components/Form/FormFieldSet.d.ts +3 -3
  21. package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -1
  22. package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -1
  23. package/dist/types/src/components/Form/fields/RefField.d.ts +8 -12
  24. package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -1
  25. package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts +1 -1
  26. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +14 -9
  27. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
  28. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +1 -1
  29. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
  30. package/dist/types/src/components/Settings/Settings.d.ts +56 -0
  31. package/dist/types/src/components/Settings/Settings.d.ts.map +1 -0
  32. package/dist/types/src/components/{ControlSection/ControlSection.stories.d.ts → Settings/Settings.stories.d.ts} +3 -9
  33. package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -0
  34. package/dist/types/src/components/Settings/index.d.ts +2 -0
  35. package/dist/types/src/components/Settings/index.d.ts.map +1 -0
  36. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +1 -1
  37. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  38. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +1 -1
  39. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  40. package/dist/types/src/components/index.d.ts +1 -1
  41. package/dist/types/src/components/index.d.ts.map +1 -1
  42. package/dist/types/src/components/testing.d.ts.map +1 -1
  43. package/dist/types/src/hooks/index.d.ts +0 -2
  44. package/dist/types/src/hooks/index.d.ts.map +1 -1
  45. package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -1
  46. package/dist/types/src/translations.d.ts +2 -2
  47. package/dist/types/src/translations.d.ts.map +1 -1
  48. package/dist/types/src/util/echo.d.ts +6 -0
  49. package/dist/types/src/util/echo.d.ts.map +1 -0
  50. package/dist/types/src/util/index.d.ts +1 -0
  51. package/dist/types/src/util/index.d.ts.map +1 -1
  52. package/dist/types/tsconfig.tsbuildinfo +1 -1
  53. package/package.json +49 -45
  54. package/src/components/FieldEditor/FieldEditor.stories.tsx +39 -18
  55. package/src/components/FieldEditor/FieldEditor.tsx +13 -6
  56. package/src/components/Form/Form-old.stories.tsx +42 -42
  57. package/src/components/Form/Form.stories.tsx +12 -17
  58. package/src/components/Form/Form.tsx +52 -27
  59. package/src/components/Form/FormField.tsx +23 -17
  60. package/src/components/Form/FormFieldComponent.stories.tsx +1 -1
  61. package/src/components/Form/FormFieldComponent.tsx +17 -16
  62. package/src/components/Form/FormFieldSet.tsx +8 -5
  63. package/src/components/Form/fields/ArrayField.tsx +11 -11
  64. package/src/components/Form/fields/RefField.tsx +82 -99
  65. package/src/components/Form/fields/SelectOptionField.stories.tsx +1 -1
  66. package/src/components/Form/fields/SelectOptionField.tsx +2 -2
  67. package/src/components/Form/fields/TupleField.tsx +1 -1
  68. package/src/components/ObjectPicker/ObjectPicker.stories.tsx +12 -5
  69. package/src/components/ObjectPicker/ObjectPicker.tsx +66 -57
  70. package/src/components/Settings/Settings.stories.tsx +56 -0
  71. package/src/components/Settings/Settings.tsx +234 -0
  72. package/src/components/{ControlSection → Settings}/index.ts +1 -1
  73. package/src/components/ViewEditor/ViewEditor.stories.tsx +18 -12
  74. package/src/components/ViewEditor/ViewEditor.test.tsx +1 -5
  75. package/src/components/ViewEditor/ViewEditor.tsx +77 -44
  76. package/src/components/index.ts +1 -1
  77. package/src/components/testing.tsx +3 -5
  78. package/src/hooks/index.ts +0 -2
  79. package/src/hooks/useFormHandler.ts +20 -6
  80. package/src/translations.ts +1 -1
  81. package/src/util/echo.ts +25 -0
  82. package/src/util/index.ts +1 -0
  83. package/dist/types/src/components/ControlSection/ControlSection.d.ts +0 -23
  84. package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +0 -1
  85. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +0 -1
  86. package/dist/types/src/components/ControlSection/index.d.ts +0 -2
  87. package/dist/types/src/components/ControlSection/index.d.ts.map +0 -1
  88. package/dist/types/src/hooks/useQueryRefOptions.d.ts +0 -30
  89. package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +0 -1
  90. package/dist/types/src/hooks/useRefQueryOptions.d.ts +0 -8
  91. package/dist/types/src/hooks/useRefQueryOptions.d.ts.map +0 -1
  92. package/src/components/ControlSection/ControlSection.stories.tsx +0 -38
  93. package/src/components/ControlSection/ControlSection.tsx +0 -153
  94. package/src/hooks/useQueryRefOptions.ts +0 -70
  95. package/src/hooks/useRefQueryOptions.ts +0 -37
@@ -5,12 +5,12 @@
5
5
  import { createContext } from '@radix-ui/react-context';
6
6
  import * as Schema from 'effect/Schema';
7
7
  import type * as SchemaAST from 'effect/SchemaAST';
8
- import React, { type PropsWithChildren, useMemo, useRef } from 'react';
8
+ import React, { type PropsWithChildren, useEffect, useMemo, useRef } from 'react';
9
9
 
10
10
  import { type AnyProperties } from '@dxos/echo/internal';
11
11
  import { createJsonPath, getValue as getValue$ } from '@dxos/effect';
12
12
  import { IconButton, type IconButtonProps, ScrollArea, type ThemedClassName, useTranslation } from '@dxos/react-ui';
13
- import { mx } from '@dxos/react-ui-theme';
13
+ import { mx } from '@dxos/ui-theme';
14
14
 
15
15
  import {
16
16
  type FormHandler,
@@ -20,6 +20,7 @@ import {
20
20
  useKeyHandler,
21
21
  } from '../../hooks';
22
22
  import { translationKey } from '../../translations';
23
+ import { setValueEchoAware } from '../../util';
23
24
 
24
25
  import { FormFieldLabel, type FormFieldLabelProps, type FormFieldStateProps } from './FormFieldComponent';
25
26
  import {
@@ -76,13 +77,28 @@ const [FormContextProvider, useFormContext] = createContext<FormContextValue>('F
76
77
  /**
77
78
  * Get the current form values.
78
79
  */
79
- const useFormValues = <T extends AnyProperties>(componentName: string, path: (string | number)[] = []): T => {
80
+ const useFormValues: {
81
+ <T extends AnyProperties>(componentName: string, path?: (string | number)[]): T;
82
+ <T extends AnyProperties>(
83
+ componentName: string,
84
+ path: (string | number)[] | undefined,
85
+ defaultValue: () => T,
86
+ ): T | undefined;
87
+ } = (componentName: string, path: (string | number)[] = [], defaultValue?: () => any) => {
80
88
  const jsonPath = createJsonPath(path);
81
89
  const {
82
90
  form: { values },
83
91
  } = useFormContext(componentName);
84
92
 
85
- return getValue$(values, jsonPath) as T;
93
+ const value = getValue$(values, jsonPath);
94
+
95
+ useEffect(() => {
96
+ if (!value && defaultValue) {
97
+ setValueEchoAware(values, jsonPath, defaultValue());
98
+ }
99
+ }, [value, defaultValue]);
100
+
101
+ return value;
86
102
  };
87
103
 
88
104
  /**
@@ -148,30 +164,32 @@ FormRoot.displayName = 'Form.Root';
148
164
  // Viewport
149
165
  //
150
166
 
151
- type FormViewportProps = ThemedClassName<PropsWithChildren<{}>>;
167
+ const FORM_VIEWPORT_NAME = 'Form.Viewport';
152
168
 
153
- const FormViewport = ({ classNames, children }: FormViewportProps) => {
169
+ type FormViewportProps = PropsWithChildren<{}>;
170
+
171
+ // TODO(burdon): Ref and props (allow asChild).
172
+ const FormViewport = ({ children }: FormViewportProps) => {
154
173
  return (
155
- <ScrollArea.Root>
156
- <ScrollArea.Viewport classNames={['plb-cardSpacingBlock', classNames]}>{children}</ScrollArea.Viewport>
157
- <ScrollArea.Scrollbar orientation='vertical'>
158
- <ScrollArea.Thumb />
159
- </ScrollArea.Scrollbar>
174
+ <ScrollArea.Root orientation='vertical'>
175
+ <ScrollArea.Viewport>{children}</ScrollArea.Viewport>
160
176
  </ScrollArea.Root>
161
177
  );
162
178
  };
163
179
 
164
- FormViewport.displayName = 'Form.Viewport';
180
+ FormViewport.displayName = FORM_VIEWPORT_NAME;
165
181
 
166
182
  //
167
183
  // Content
168
184
  //
169
185
 
186
+ const FORM_CONTENT_NAME = 'Form.Content';
187
+
170
188
  type FormContentProps = ThemedClassName<PropsWithChildren<{}>>;
171
189
 
172
190
  // TOOD(burdon): Figure out nesting (indent and testId).
173
191
  const FormContent = ({ classNames, children }: FormContentProps) => {
174
- const { form, testId } = useFormContext(FormContent.displayName);
192
+ const { form, testId } = useFormContext(FORM_CONTENT_NAME);
175
193
  const ref = useRef<HTMLDivElement>(null);
176
194
  useKeyHandler(ref.current, form);
177
195
 
@@ -179,7 +197,7 @@ const FormContent = ({ classNames, children }: FormContentProps) => {
179
197
  <div
180
198
  ref={ref}
181
199
  role='form'
182
- className={mx('flex flex-col is-full pli-cardSpacingInline', classNames)}
200
+ className={mx('flex flex-col is-full pli-cardSpacingInline density-fine', classNames)}
183
201
  data-testid={testId}
184
202
  >
185
203
  {children}
@@ -187,35 +205,39 @@ const FormContent = ({ classNames, children }: FormContentProps) => {
187
205
  );
188
206
  };
189
207
 
190
- FormContent.displayName = 'Form.Content';
208
+ FormContent.displayName = FORM_CONTENT_NAME;
191
209
 
192
210
  //
193
211
  // FieldSet
194
212
  //
195
213
 
214
+ const FORM_FIELDSET_NAME = 'Form.FieldSet';
215
+
196
216
  type FormFieldSetProps = ThemedClassName<{}>;
197
217
 
198
218
  const FormFieldSet = ({ classNames }: FormFieldSetProps) => {
199
- const { form, ...props } = useFormContext(FormFieldSet.displayName);
219
+ const { form, ...props } = useFormContext(FORM_FIELDSET_NAME);
200
220
 
201
221
  return <NaturalFormFieldSet classNames={classNames} schema={form.schema} {...props} />;
202
222
  };
203
223
 
204
- FormFieldSet.displayName = 'Form.FieldSet';
224
+ FormFieldSet.displayName = FORM_FIELDSET_NAME;
205
225
 
206
226
  //
207
227
  // Actions
208
228
  //
209
229
 
230
+ const FORM_ACTIONS_NAME = 'Form.Actions';
231
+
210
232
  type FormActionsProps = ThemedClassName<{}>;
211
233
 
212
234
  const FormActions = ({ classNames }: FormActionsProps) => {
213
235
  const { t } = useTranslation(translationKey);
214
236
  const {
215
- form: { isValid, onSave, onCancel },
237
+ form: { canSave, onSave, onCancel },
216
238
  readonly,
217
239
  layout,
218
- } = useFormContext(FormActions.displayName);
240
+ } = useFormContext(FORM_ACTIONS_NAME);
219
241
 
220
242
  if (readonly || layout === 'static') {
221
243
  return null;
@@ -239,7 +261,7 @@ const FormActions = ({ classNames }: FormActionsProps) => {
239
261
  <IconButton
240
262
  type='submit'
241
263
  variant='primary'
242
- disabled={!isValid}
264
+ disabled={!canSave}
243
265
  icon='ph--check--regular'
244
266
  iconEnd
245
267
  label={t('save button label')}
@@ -251,21 +273,23 @@ const FormActions = ({ classNames }: FormActionsProps) => {
251
273
  );
252
274
  };
253
275
 
254
- FormActions.displayName = 'Form.Actions';
276
+ FormActions.displayName = FORM_ACTIONS_NAME;
255
277
 
256
278
  //
257
279
  // Submit
258
280
  //
259
281
 
260
- type FormSubmitProps = ThemedClassName<Partial<Pick<IconButtonProps, 'icon' | 'label'>>>;
282
+ const FORM_SUBMIT_NAME = 'Form.Submit';
283
+
284
+ type FormSubmitProps = ThemedClassName<Partial<Pick<IconButtonProps, 'icon' | 'label' | 'disabled'>>>;
261
285
 
262
- const FormSubmit = ({ classNames, label, icon }: FormSubmitProps) => {
286
+ const FormSubmit = ({ classNames, label, icon, disabled }: FormSubmitProps) => {
263
287
  const { t } = useTranslation(translationKey);
264
288
  const {
265
- form: { isValid, onSave },
289
+ form: { canSave, onSave },
266
290
  readonly,
267
291
  layout,
268
- } = useFormContext(FormSubmit.displayName);
292
+ } = useFormContext(FORM_SUBMIT_NAME);
269
293
 
270
294
  if (readonly || layout === 'static') {
271
295
  return null;
@@ -277,7 +301,7 @@ const FormSubmit = ({ classNames, label, icon }: FormSubmitProps) => {
277
301
  classNames='is-full'
278
302
  type='submit'
279
303
  variant='primary'
280
- disabled={!isValid}
304
+ disabled={disabled ?? !canSave}
281
305
  icon={icon ?? 'ph--check--regular'}
282
306
  label={label ?? t('save button label')}
283
307
  onClick={onSave}
@@ -287,7 +311,7 @@ const FormSubmit = ({ classNames, label, icon }: FormSubmitProps) => {
287
311
  );
288
312
  };
289
313
 
290
- FormSubmit.displayName = 'Form.Submit';
314
+ FormSubmit.displayName = FORM_SUBMIT_NAME;
291
315
 
292
316
  //
293
317
  // Form
@@ -312,5 +336,6 @@ export type {
312
336
  FormContentProps,
313
337
  FormFieldSetProps,
314
338
  FormActionsProps,
339
+ FormSubmitProps,
315
340
  FormFieldLabelProps as LabelProps,
316
341
  };
@@ -78,18 +78,17 @@ export type FormFieldProps = {
78
78
  * Function to lookup custom renderers for specific properties.
79
79
  */
80
80
  fieldProvider?: FormFieldProvider;
81
- } & Pick<
82
- RefFieldProps,
83
- | 'autoFocus'
84
- | 'readonly'
85
- | 'layout'
86
- | 'createSchema'
87
- | 'createOptionLabel'
88
- | 'createOptionIcon'
89
- | 'createInitialValuePath'
90
- | 'onCreate'
91
- | 'onQueryRefOptions'
92
- >;
81
+ } & Pick<FormFieldComponentProps, 'autoFocus' | 'readonly' | 'layout'> &
82
+ Pick<
83
+ RefFieldProps,
84
+ | 'createOptionLabel'
85
+ | 'createOptionIcon'
86
+ | 'createInitialValuePath'
87
+ | 'db'
88
+ | 'schemaHook'
89
+ | 'getOptions'
90
+ | 'onCreate'
91
+ >;
93
92
 
94
93
  export const FormField = (props: FormFieldProps) => {
95
94
  const {
@@ -101,12 +100,15 @@ export const FormField = (props: FormFieldProps) => {
101
100
  fieldProvider,
102
101
  readonly,
103
102
  layout,
104
- createSchema,
103
+
104
+ // RefFieldProps
105
105
  createOptionLabel,
106
106
  createOptionIcon,
107
107
  createInitialValuePath,
108
+ db,
109
+ schemaHook,
110
+ getOptions: getRefOptions,
108
111
  onCreate,
109
- onQueryRefOptions,
110
112
  } = props;
111
113
  const { t } = useTranslation(translationKey);
112
114
  const title = getAnnotation<string>(SchemaAST.TitleAnnotationId)(type);
@@ -190,12 +192,13 @@ export const FormField = (props: FormFieldProps) => {
190
192
  <RefField
191
193
  {...fieldProps}
192
194
  {...refProps}
193
- createSchema={createSchema}
194
195
  createOptionLabel={createOptionLabel}
195
196
  createOptionIcon={createOptionIcon}
196
197
  createInitialValuePath={createInitialValuePath}
198
+ db={db}
199
+ schemaHook={schemaHook}
200
+ getOptions={getRefOptions}
197
201
  onCreate={onCreate}
198
- onQueryRefOptions={onQueryRefOptions}
199
202
  />
200
203
  );
201
204
  }
@@ -224,8 +227,11 @@ export const FormField = (props: FormFieldProps) => {
224
227
  fieldProvider={fieldProvider}
225
228
  createOptionLabel={createOptionLabel}
226
229
  createOptionIcon={createOptionIcon}
230
+ createInitialValuePath={createInitialValuePath}
231
+ db={db}
232
+ schemaHook={schemaHook}
233
+ getOptions={getRefOptions}
227
234
  onCreate={onCreate}
228
- onQueryRefOptions={onQueryRefOptions}
229
235
  />
230
236
  );
231
237
  }
@@ -44,7 +44,7 @@ const Component = ({
44
44
  const meta: Meta<typeof Component> = {
45
45
  title: 'ui/react-ui-form/FormFieldComponent',
46
46
  component: Component,
47
- decorators: [withTheme, withLayout({ container: 'column' })],
47
+ decorators: [withTheme(), withLayout({ layout: 'column' })],
48
48
  parameters: {
49
49
  layout: 'fullscreen',
50
50
  },
@@ -15,11 +15,12 @@ import React, {
15
15
 
16
16
  import { type Format } from '@dxos/echo/internal';
17
17
  import { Icon, Input, Tooltip } from '@dxos/react-ui';
18
- import { labelSpacing } from '@dxos/react-ui-stack';
19
- import { errorText, mx } from '@dxos/react-ui-theme';
18
+ import { errorText } from '@dxos/ui-theme';
20
19
 
21
20
  import { type FormFieldStatus } from '../../hooks';
22
21
 
22
+ const labelSpacing = 'mbs-inputSpacingBlock mbe-labelSpacingBlock';
23
+
23
24
  /**
24
25
  * Dynamic props passed to input components.
25
26
  */
@@ -78,17 +79,15 @@ export type FormFieldLabelProps = {
78
79
 
79
80
  export const FormFieldLabel = ({ label, error, readonly, asChild }: FormFieldLabelProps) => {
80
81
  const Label = readonly || asChild ? 'span' : Input.Label;
81
- const labelProps = readonly || asChild ? { className: 'text-description text-sm' } : { classNames: '!mlb-0 text-sm' };
82
-
83
82
  return (
84
- <div role='none' className={mx('flex justify-between items-center', labelSpacing)}>
85
- <Label {...labelProps}>{label}</Label>
83
+ <>
84
+ <Label>{label}</Label>
86
85
  {error && (
87
86
  <Tooltip.Trigger asChild content={error} side='bottom'>
88
87
  <Icon icon='ph--warning--regular' size={4} classNames={errorText} />
89
88
  </Tooltip.Trigger>
90
89
  )}
91
- </div>
90
+ </>
92
91
  );
93
92
  };
94
93
 
@@ -117,15 +116,17 @@ export const FormFieldWrapper = <T,>(props: FormFieldWrapperProps<T>) => {
117
116
  const str = String(value ?? '');
118
117
 
119
118
  return (
120
- <Input.Root validationValence={status}>
121
- {layout !== 'inline' && <FormFieldLabel error={error} readonly={readonly} label={label} />}
122
- {layout === 'static' ? <p>{str}</p> : children ? children({ value }) : null}
123
- {layout === 'full' && (
124
- <Input.DescriptionAndValidation>
125
- <Input.Validation>{error}</Input.Validation>
126
- </Input.DescriptionAndValidation>
127
- )}
128
- </Input.Root>
119
+ <div role='none' className='contents'>
120
+ <Input.Root validationValence={status}>
121
+ {layout !== 'inline' && <FormFieldLabel error={error} readonly={readonly} label={label} />}
122
+ {layout === 'static' ? <p>{str}</p> : children ? children({ value }) : null}
123
+ {layout === 'full' && (
124
+ <Input.DescriptionAndValidation>
125
+ <Input.Validation>{error}</Input.Validation>
126
+ </Input.DescriptionAndValidation>
127
+ )}
128
+ </Input.Root>
129
+ </div>
129
130
  );
130
131
  };
131
132
 
@@ -7,7 +7,7 @@ import React, { forwardRef, useMemo } from 'react';
7
7
  import { type AnyProperties } from '@dxos/echo/internal';
8
8
  import { type SchemaProperty } from '@dxos/effect';
9
9
  import { type ThemedClassName } from '@dxos/react-ui';
10
- import { mx } from '@dxos/react-ui-theme';
10
+ import { mx } from '@dxos/ui-theme';
11
11
  import { isTruthy } from '@dxos/util';
12
12
 
13
13
  import { type FormHandlerProps } from '../../hooks';
@@ -32,18 +32,21 @@ export type FormFieldSetProps<T extends AnyProperties> = ThemedClassName<
32
32
  | 'projection'
33
33
  | 'fieldMap'
34
34
  | 'fieldProvider'
35
- | 'createSchema'
36
35
  | 'createOptionLabel'
37
36
  | 'createOptionIcon'
38
37
  | 'createInitialValuePath'
38
+ | 'db'
39
+ | 'schemaHook'
40
+ | 'getOptions'
39
41
  | 'onCreate'
40
- | 'onQueryRefOptions'
41
42
  >
42
43
  >;
43
44
 
45
+ const FORM_FIELDSET_NAME = 'Form.FieldSet';
46
+
44
47
  export const FormFieldSet = forwardRef<HTMLDivElement, FormFieldSetProps<any>>(
45
48
  ({ classNames, label, schema, readonly, path, exclude, sort, projection, layout, ...props }, forwardRef) => {
46
- const values = useFormValues(FormFieldSet.displayName!, path);
49
+ const values = useFormValues(FORM_FIELDSET_NAME, path);
47
50
 
48
51
  // TODO(burdon): Updates on every value change.
49
52
  // Remove values dep if can remove from getSchemaProperties.
@@ -121,4 +124,4 @@ export const FormFieldSet = forwardRef<HTMLDivElement, FormFieldSetProps<any>>(
121
124
  },
122
125
  );
123
126
 
124
- FormFieldSet.displayName = 'Form.Fields';
127
+ FormFieldSet.displayName = FORM_FIELDSET_NAME;
@@ -7,7 +7,6 @@ import * as SchemaAST from 'effect/SchemaAST';
7
7
  import React, { Fragment, useCallback } from 'react';
8
8
 
9
9
  import { findNode, getArrayElementType, getDiscriminatedType, isDiscriminatedUnion, isNestedType } from '@dxos/effect';
10
- import { invariant } from '@dxos/invariant';
11
10
  import { IconButton, useTranslation } from '@dxos/react-ui';
12
11
 
13
12
  import { translationKey } from '../../../translations';
@@ -33,11 +32,7 @@ export const ArrayField = ({
33
32
  const { t } = useTranslation(translationKey);
34
33
  const elementType = getArrayElementType(type);
35
34
  const { onValueChange } = inputProps;
36
-
37
- // TODO(wittjosiah): The fallback to an empty array stops the form from crashing but isn't immediately live.
38
- // It doesn't become live until another field is touched, but that's better than the whole form crashing.
39
- const values = useFormValues(ArrayField.displayName, path) ?? [];
40
- invariant(Array.isArray(values), `Expected array at: ${path?.join('.')}`);
35
+ const values = useFormValues(ArrayField.displayName, path, () => []);
41
36
 
42
37
  const getDefaultObjectValue = (typeNode: SchemaAST.AST): any => {
43
38
  const baseNode = findNode(typeNode, isDiscriminatedUnion);
@@ -59,29 +54,31 @@ export const ArrayField = ({
59
54
  const handleAdd = useCallback(() => {
60
55
  const defaultValue =
61
56
  isNestedType(type) && elementType ? getDefaultObjectValue(elementType) : getDefaultValue(elementType);
62
- onValueChange(type, [...values, defaultValue]);
57
+ values && onValueChange(type, [...values, defaultValue]);
63
58
  }, [onValueChange, type, elementType, values]);
64
59
 
65
60
  const handleDelete = useCallback(
66
61
  (idx: number) => {
67
62
  onValueChange(
68
63
  type,
69
- values.filter((_, i) => i !== idx),
64
+ values?.filter((_, i) => i !== idx),
70
65
  );
71
66
  },
72
67
  [onValueChange, type, values],
73
68
  );
74
69
 
75
- if (!elementType || ((readonly || layout === 'static') && values.length < 1)) {
70
+ if (!elementType || ((readonly || layout === 'static') && values && values.length < 1)) {
76
71
  return null;
77
72
  }
78
73
 
79
74
  return (
80
75
  <>
81
- {(layout !== 'static' || values.length > 0) && <FormFieldLabel readonly={readonly} label={label} asChild />}
76
+ {(layout !== 'static' || (values && values.length > 0)) && (
77
+ <FormFieldLabel readonly={readonly} label={label} asChild />
78
+ )}
82
79
 
83
80
  <div role='none' className='flex flex-col gap-2'>
84
- {values.map((_, index) => {
81
+ {values?.map((_, index) => {
85
82
  return (
86
83
  <div role='none' key={index} className='grid grid-cols-[1fr_min-content] gap-2 last:mb-3'>
87
84
  <FormField
@@ -143,6 +140,9 @@ export const getDefaultValue = (ast?: SchemaAST.AST): any => {
143
140
  case 'BooleanKeyword': {
144
141
  return false;
145
142
  }
143
+ case 'Suspend': {
144
+ return getDefaultValue(ast.f());
145
+ }
146
146
  default: {
147
147
  if (ast && isNestedType(ast)) {
148
148
  return {};