@dxos/react-ui-form 0.7.2 → 0.7.3-staging.0905f03

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 (33) hide show
  1. package/dist/lib/browser/index.mjs +228 -58
  2. package/dist/lib/browser/index.mjs.map +3 -3
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node/index.cjs +228 -57
  5. package/dist/lib/node/index.cjs.map +3 -3
  6. package/dist/lib/node/meta.json +1 -1
  7. package/dist/lib/node-esm/index.mjs +228 -58
  8. package/dist/lib/node-esm/index.mjs.map +3 -3
  9. package/dist/lib/node-esm/meta.json +1 -1
  10. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +2 -2
  11. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  12. package/dist/types/src/components/Form/Defaults.d.ts +4 -4
  13. package/dist/types/src/components/Form/Defaults.d.ts.map +1 -1
  14. package/dist/types/src/components/Form/Form.d.ts +11 -2
  15. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  16. package/dist/types/src/components/Form/Form.stories.d.ts +20 -5
  17. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  18. package/dist/types/src/components/Form/Input.d.ts +5 -4
  19. package/dist/types/src/components/Form/Input.d.ts.map +1 -1
  20. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  21. package/dist/types/src/components/deprecated/Form.stories.d.ts.map +1 -1
  22. package/dist/types/src/hooks/useForm.d.ts +19 -11
  23. package/dist/types/src/hooks/useForm.d.ts.map +1 -1
  24. package/package.json +23 -22
  25. package/src/components/FieldEditor/FieldEditor.stories.tsx +3 -3
  26. package/src/components/FieldEditor/FieldEditor.tsx +8 -8
  27. package/src/components/Form/Defaults.tsx +28 -13
  28. package/src/components/Form/Form.stories.tsx +81 -19
  29. package/src/components/Form/Form.tsx +216 -29
  30. package/src/components/Form/Input.tsx +5 -4
  31. package/src/components/ViewEditor/ViewEditor.tsx +8 -7
  32. package/src/components/deprecated/Form.stories.tsx +4 -4
  33. package/src/hooks/useForm.ts +65 -32
@@ -91,7 +91,7 @@ import { pipe } from "effect";
91
91
  import { capitalize } from "effect/String";
92
92
  import React5, { useEffect as useEffect2, useMemo as useMemo2 } from "react";
93
93
  import { AST, S } from "@dxos/echo-schema";
94
- import { findNode, getDiscriminatedType, isDiscriminatedUnion } from "@dxos/effect";
94
+ import { findNode, getDiscriminatedType, isDiscriminatedUnion, SimpleType } from "@dxos/effect";
95
95
  import { log as log2 } from "@dxos/log";
96
96
  import { IconButton, useTranslation as useTranslation2 } from "@dxos/react-ui";
97
97
  import { mx } from "@dxos/react-ui-theme";
@@ -122,11 +122,11 @@ var InputHeader = ({ children, error }) => {
122
122
  };
123
123
 
124
124
  // packages/ui/react-ui-form/src/components/Form/Defaults.tsx
125
- var TextInput = ({ property, type, label, disabled, placeholder, getStatus, getValue, onValueChange, onBlur }) => {
125
+ var TextInput = ({ property, type, label, inputOnly, disabled, placeholder, getStatus, getValue, onValueChange, onBlur }) => {
126
126
  const { status, error } = getStatus?.(property);
127
127
  return /* @__PURE__ */ React3.createElement(Input2.Root, {
128
128
  validationValence: status
129
- }, /* @__PURE__ */ React3.createElement(InputHeader, {
129
+ }, !inputOnly && /* @__PURE__ */ React3.createElement(InputHeader, {
130
130
  error
131
131
  }, /* @__PURE__ */ React3.createElement(Input2.Label, null, label)), /* @__PURE__ */ React3.createElement(Input2.TextInput, {
132
132
  disabled,
@@ -134,13 +134,13 @@ var TextInput = ({ property, type, label, disabled, placeholder, getStatus, getV
134
134
  value: getValue(property) ?? "",
135
135
  onChange: (event) => onValueChange(property, type, event.target.value),
136
136
  onBlur
137
- }));
137
+ }), inputOnly && /* @__PURE__ */ React3.createElement(Input2.Validation, null, error));
138
138
  };
139
- var NumberInput = ({ property, type, label, disabled, placeholder, getStatus, getValue, onValueChange, onBlur }) => {
139
+ var NumberInput = ({ property, type, label, inputOnly, disabled, placeholder, getStatus, getValue, onValueChange, onBlur }) => {
140
140
  const { status, error } = getStatus?.(property);
141
141
  return /* @__PURE__ */ React3.createElement(Input2.Root, {
142
142
  validationValence: status
143
- }, /* @__PURE__ */ React3.createElement(InputHeader, {
143
+ }, !inputOnly && /* @__PURE__ */ React3.createElement(InputHeader, {
144
144
  error
145
145
  }, /* @__PURE__ */ React3.createElement(Input2.Label, null, label)), /* @__PURE__ */ React3.createElement(Input2.TextInput, {
146
146
  type: "number",
@@ -149,24 +149,24 @@ var NumberInput = ({ property, type, label, disabled, placeholder, getStatus, ge
149
149
  value: getValue(property) ?? 0,
150
150
  onChange: (event) => onValueChange(property, type, event.target.value),
151
151
  onBlur
152
- }));
152
+ }), inputOnly && /* @__PURE__ */ React3.createElement(Input2.DescriptionAndValidation, null, error));
153
153
  };
154
- var BooleanInput = ({ property, type, label, getStatus, getValue, onValueChange }) => {
154
+ var BooleanInput = ({ property, type, label, inputOnly, getStatus, getValue, onValueChange }) => {
155
155
  const { status, error } = getStatus?.(property);
156
156
  return /* @__PURE__ */ React3.createElement(Input2.Root, {
157
157
  validationValence: status
158
- }, /* @__PURE__ */ React3.createElement(InputHeader, {
158
+ }, !inputOnly && /* @__PURE__ */ React3.createElement(InputHeader, {
159
159
  error
160
160
  }, /* @__PURE__ */ React3.createElement(Input2.Label, null, label)), /* @__PURE__ */ React3.createElement(Input2.Switch, {
161
161
  checked: getValue(property),
162
162
  onCheckedChange: (value) => onValueChange(property, type, value)
163
- }));
163
+ }), inputOnly && /* @__PURE__ */ React3.createElement(Input2.DescriptionAndValidation, null, error));
164
164
  };
165
- var SelectInput = ({ property, type, label, disabled, placeholder, options, getStatus, getValue, onValueChange }) => {
165
+ var SelectInput = ({ property, type, label, inputOnly, disabled, placeholder, options, getStatus, getValue, onValueChange }) => {
166
166
  const { status, error } = getStatus?.(property);
167
167
  return /* @__PURE__ */ React3.createElement(Input2.Root, {
168
168
  validationValence: status
169
- }, /* @__PURE__ */ React3.createElement(InputHeader, {
169
+ }, !inputOnly && /* @__PURE__ */ React3.createElement(InputHeader, {
170
170
  error
171
171
  }, /* @__PURE__ */ React3.createElement(Input2.Label, null, label)), /* @__PURE__ */ React3.createElement(Select.Root, {
172
172
  value: getValue(property),
@@ -181,7 +181,7 @@ var SelectInput = ({ property, type, label, disabled, placeholder, options, getS
181
181
  key: String(value),
182
182
  value: String(value)
183
183
  }, label2 ?? String(value))
184
- )))))));
184
+ )))))), inputOnly && /* @__PURE__ */ React3.createElement(Input2.DescriptionAndValidation, null, error));
185
185
  };
186
186
 
187
187
  // packages/ui/react-ui-form/src/components/Form/factory.tsx
@@ -252,7 +252,7 @@ import { invariant } from "@dxos/invariant";
252
252
  import { log } from "@dxos/log";
253
253
  import { validateSchema } from "@dxos/schema";
254
254
  var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-form/src/hooks/useForm.ts";
255
- var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, onSubmit }) => {
255
+ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, onSave }) => {
256
256
  const [values, setValues] = useState(initialValues);
257
257
  useEffect(() => {
258
258
  setValues(initialValues);
@@ -262,6 +262,7 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
262
262
  const [touched, setTouched] = useState(createKeySet(initialValues, false));
263
263
  const [changed, setChanged] = useState(createKeySet(initialValues, false));
264
264
  const [errors, setErrors] = useState({});
265
+ const [saving, setSaving] = useState(false);
265
266
  const validate = useCallback((values2) => {
266
267
  let errors2 = validateSchema(schema, values2) ?? [];
267
268
  if (errors2.length === 0 && onValidate) {
@@ -286,21 +287,27 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
286
287
  values,
287
288
  schemaDirty
288
289
  ]);
289
- const canSubmit = useMemo(() => Object.keys(values).every((property) => touched[property] === false || !errors[property]), [
290
+ const canSave = useMemo(() => !saving && Object.keys(values).every((property) => touched[property] === false || !errors[property]), [
290
291
  values,
291
292
  touched,
292
- errors
293
+ errors,
294
+ saving
293
295
  ]);
294
- const handleSubmit = useCallback(() => {
296
+ const handleSave = useCallback(async () => {
295
297
  if (validate(values)) {
296
- onSubmit?.(values, {
297
- changed
298
- });
298
+ setSaving(true);
299
+ try {
300
+ await onSave?.(values, {
301
+ changed
302
+ });
303
+ } finally {
304
+ setSaving(false);
305
+ }
299
306
  }
300
307
  }, [
301
308
  values,
302
309
  validate,
303
- onSubmit
310
+ onSave
304
311
  ]);
305
312
  const getStatus = useCallback((property) => ({
306
313
  status: errors[property] ? "error" : void 0,
@@ -310,7 +317,7 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
310
317
  errors
311
318
  ]);
312
319
  const getValue = (property) => {
313
- return values[property];
320
+ return getByPath(values, property);
314
321
  };
315
322
  const onValueChange = (property, type, value) => {
316
323
  let parsedValue = value;
@@ -321,16 +328,13 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
321
328
  } catch (err) {
322
329
  log.catch(err, void 0, {
323
330
  F: __dxlog_file,
324
- L: 165,
331
+ L: 183,
325
332
  S: void 0,
326
333
  C: (f, a) => f(...a)
327
334
  });
328
335
  parsedValue = void 0;
329
336
  }
330
- const newValues = {
331
- ...values,
332
- [property]: parsedValue
333
- };
337
+ const newValues = setByPath(values, property, parsedValue);
334
338
  setValues(newValues);
335
339
  setChanged((prev) => ({
336
340
  ...prev,
@@ -364,8 +368,8 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
364
368
  errors,
365
369
  touched,
366
370
  changed,
367
- canSubmit,
368
- handleSubmit,
371
+ canSave,
372
+ handleSave,
369
373
  // Field utils.
370
374
  getStatus,
371
375
  getValue,
@@ -376,7 +380,7 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
376
380
  var createKeySet = (obj, value) => {
377
381
  invariant(obj, void 0, {
378
382
  F: __dxlog_file,
379
- L: 216,
383
+ L: 234,
380
384
  S: void 0,
381
385
  A: [
382
386
  "obj",
@@ -396,23 +400,36 @@ var flatMap = (errors) => {
396
400
  return result;
397
401
  }, {});
398
402
  };
403
+ var getByPath = (obj, path) => {
404
+ const [key, index] = path.split(".");
405
+ return index === void 0 ? obj[key] : obj[key][parseInt(index)];
406
+ };
407
+ var setByPath = (obj, path, value) => {
408
+ const [key, index] = path.split(".");
409
+ const newValue = index === void 0 ? value : [
410
+ ...obj[key]
411
+ ].map((v, i) => i === parseInt(index) ? value : v);
412
+ return {
413
+ ...obj,
414
+ [key]: newValue
415
+ };
416
+ };
399
417
 
400
418
  // packages/ui/react-ui-form/src/components/Form/Form.tsx
401
419
  var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-form/src/components/Form/Form.tsx";
402
420
  var padding = "px-2";
403
- var Form = ({ classNames, schema, values: initialValues, path = [], readonly, filter, sort, autoSave, onValuesChanged, onValidate, onSubmit, onCancel, Custom }) => {
404
- const { t } = useTranslation2(translationKey);
405
- const onValid = useMemo2(() => autoSave ? onSubmit : void 0, [
421
+ var Form = ({ classNames, schema, values: initialValues, path = [], readonly, filter, sort, autoSave, testId, onValuesChanged, onValidate, onSave, onCancel, Custom }) => {
422
+ const onValid = useMemo2(() => autoSave ? onSave : void 0, [
406
423
  autoSave,
407
- onSubmit
424
+ onSave
408
425
  ]);
409
- const { canSubmit, values, errors, handleSubmit, ...inputProps } = useForm({
426
+ const { canSave, values, errors, handleSave, ...inputProps } = useForm({
410
427
  schema,
411
428
  initialValues,
412
429
  onValuesChanged,
413
430
  onValidate,
414
431
  onValid,
415
- onSubmit
432
+ onSave
416
433
  });
417
434
  const properties = useMemo2(() => {
418
435
  const props = getSchemaProperties(schema.ast, values);
@@ -433,7 +450,7 @@ var Form = ({ classNames, schema, values: initialValues, path = [], readonly, fi
433
450
  errors
434
451
  }, {
435
452
  F: __dxlog_file2,
436
- L: 96,
453
+ L: 100,
437
454
  S: void 0,
438
455
  C: (f, a) => f(...a)
439
456
  });
@@ -443,14 +460,15 @@ var Form = ({ classNames, schema, values: initialValues, path = [], readonly, fi
443
460
  ]);
444
461
  return /* @__PURE__ */ React5.createElement("div", {
445
462
  role: "form",
446
- className: mx("flex flex-col w-full gap-2 py-2", classNames)
463
+ className: mx("flex flex-col w-full gap-2 py-2", classNames),
464
+ "data-testid": testId
447
465
  }, properties.map((property) => {
448
- const { ast, name, type, format, title, description, examples, options } = property;
466
+ const { ast, name, type, format, title, description, examples, options, array } = property;
449
467
  const key = [
450
468
  ...path,
451
469
  name
452
470
  ];
453
- const label = pipe(title ?? name, capitalize);
471
+ const label = title ?? pipe(name, capitalize);
454
472
  const placeholder = examples?.length ? `Example: "${examples[0]}"` : description;
455
473
  let InputComponent = Custom?.[key.join(".")];
456
474
  if (!InputComponent) {
@@ -474,6 +492,22 @@ var Form = ({ classNames, schema, values: initialValues, path = [], readonly, fi
474
492
  }));
475
493
  }
476
494
  InputComponent = getInputComponent(type, format);
495
+ if (array) {
496
+ return /* @__PURE__ */ React5.createElement(ArrayField, {
497
+ key: name,
498
+ name,
499
+ type,
500
+ array: true,
501
+ label,
502
+ ast,
503
+ values,
504
+ format,
505
+ readonly,
506
+ placeholder,
507
+ inputProps,
508
+ Custom
509
+ });
510
+ }
477
511
  }
478
512
  if (!InputComponent) {
479
513
  if (type === "object") {
@@ -502,7 +536,7 @@ var Form = ({ classNames, schema, values: initialValues, path = [], readonly, fi
502
536
  type
503
537
  }, {
504
538
  F: __dxlog_file2,
505
- L: 160,
539
+ L: 185,
506
540
  S: void 0,
507
541
  C: (f, a) => f(...a)
508
542
  });
@@ -521,28 +555,163 @@ var Form = ({ classNames, schema, values: initialValues, path = [], readonly, fi
521
555
  placeholder,
522
556
  ...inputProps
523
557
  }));
524
- }).filter(isNotFalsy), (onCancel || onSubmit) && !autoSave && /* @__PURE__ */ React5.createElement("div", {
558
+ }).filter(isNotFalsy), (onCancel || onSave) && !autoSave && /* @__PURE__ */ React5.createElement(FormActions, {
559
+ onCancel,
560
+ onSubmit: handleSave,
561
+ canSubmit: canSave,
562
+ readonly
563
+ }));
564
+ };
565
+ var ArrayField = ({ name, type, label, ast, values, format, readonly, placeholder, inputProps, Custom }) => {
566
+ const { t } = useTranslation2(translationKey);
567
+ const arrayValues = values[name] ?? [];
568
+ const key = [
569
+ name
570
+ ];
571
+ const tupleType = findNode(ast, AST.isTupleType);
572
+ const elementType = tupleType?.rest[0]?.type;
573
+ if (type === "object" && elementType) {
574
+ const baseNode = findNode(elementType, isDiscriminatedUnion);
575
+ const typeLiteral = baseNode ? getDiscriminatedType(baseNode, values[name]) : findNode(elementType, AST.isTypeLiteral);
576
+ if (typeLiteral) {
577
+ const schema = S.make(typeLiteral);
578
+ return /* @__PURE__ */ React5.createElement("div", {
579
+ key: name,
580
+ role: "none",
581
+ className: mx(padding)
582
+ }, /* @__PURE__ */ React5.createElement(InputHeader, null, label), /* @__PURE__ */ React5.createElement("div", {
583
+ role: "none",
584
+ className: "flex flex-col gap-1"
585
+ }, arrayValues.map((value, index) => {
586
+ return /* @__PURE__ */ React5.createElement("div", {
587
+ key: index,
588
+ role: "none",
589
+ className: "flex items-center gap-1"
590
+ }, /* @__PURE__ */ React5.createElement("div", {
591
+ role: "none",
592
+ className: "flex-1"
593
+ }, /* @__PURE__ */ React5.createElement(Form, {
594
+ schema,
595
+ path: [
596
+ ...key,
597
+ index.toString()
598
+ ],
599
+ values: value,
600
+ onValuesChanged: (childValues) => {
601
+ const newValues = [
602
+ ...arrayValues
603
+ ];
604
+ newValues[index] = childValues;
605
+ inputProps.onValueChange(name, "object", newValues);
606
+ },
607
+ Custom
608
+ })), /* @__PURE__ */ React5.createElement(IconButton, {
609
+ icon: "ph--trash--regular",
610
+ iconOnly: true,
611
+ label: t("button remove"),
612
+ onClick: () => {
613
+ const newValues = arrayValues.filter((_, i) => i !== index);
614
+ inputProps.onValueChange(name, type, newValues);
615
+ }
616
+ }));
617
+ })), /* @__PURE__ */ React5.createElement("div", {
618
+ role: "none",
619
+ className: "flex justify-between items-center plb-1"
620
+ }, /* @__PURE__ */ React5.createElement(IconButton, {
621
+ icon: "ph--plus--regular",
622
+ iconOnly: true,
623
+ label: t("button add"),
624
+ onClick: () => {
625
+ const newValues = [
626
+ ...arrayValues,
627
+ {}
628
+ ];
629
+ inputProps.onValueChange(name, type, newValues);
630
+ }
631
+ })));
632
+ }
633
+ return /* @__PURE__ */ React5.createElement("div", {
634
+ key: name,
635
+ role: "none"
636
+ }, "Nested form not supported in arrays, yet.");
637
+ }
638
+ const InputComponent = getInputComponent(type, format);
639
+ if (!InputComponent) {
640
+ return null;
641
+ }
642
+ return /* @__PURE__ */ React5.createElement("div", {
643
+ role: "none",
644
+ key: "meta-input",
645
+ className: mx(padding)
646
+ }, /* @__PURE__ */ React5.createElement(InputHeader, null, label), /* @__PURE__ */ React5.createElement("div", {
647
+ role: "none",
648
+ className: "flex flex-col gap-1"
649
+ }, arrayValues.map((_, index) => /* @__PURE__ */ React5.createElement("div", {
650
+ key: index,
651
+ role: "none",
652
+ className: "flex items-start gap-1"
653
+ }, /* @__PURE__ */ React5.createElement("div", {
654
+ role: "none",
655
+ className: "flex-1"
656
+ }, /* @__PURE__ */ React5.createElement(InputComponent, {
657
+ type,
658
+ format,
659
+ label,
660
+ inputOnly: true,
661
+ property: `${name}.${index}`,
662
+ disabled: readonly,
663
+ placeholder,
664
+ ...inputProps
665
+ })), /* @__PURE__ */ React5.createElement(IconButton, {
666
+ icon: "ph--trash--regular",
667
+ iconOnly: true,
668
+ label: t("button remove"),
669
+ onClick: () => {
670
+ const newValues = arrayValues.filter((_2, i) => i !== index);
671
+ inputProps.onValueChange(name, type, newValues);
672
+ }
673
+ })))), /* @__PURE__ */ React5.createElement("div", {
674
+ role: "none",
675
+ className: "flex justify-between items-center plb-1"
676
+ }, /* @__PURE__ */ React5.createElement(IconButton, {
677
+ icon: "ph--plus--regular",
678
+ iconOnly: true,
679
+ label: t("button add"),
680
+ onClick: () => {
681
+ const newValues = [
682
+ ...arrayValues,
683
+ SimpleType.getDefaultValue(type)
684
+ ];
685
+ inputProps.onValueChange(name, type, newValues);
686
+ }
687
+ })));
688
+ };
689
+ var FormActions = ({ onCancel, onSubmit, canSubmit, readonly }) => {
690
+ const { t } = useTranslation2(translationKey);
691
+ return /* @__PURE__ */ React5.createElement("div", {
525
692
  role: "none",
526
693
  className: "flex justify-center"
527
694
  }, /* @__PURE__ */ React5.createElement("div", {
528
695
  role: "none",
529
696
  className: mx(onCancel && !readonly && "grid grid-cols-2 gap-2")
530
697
  }, onCancel && !readonly && /* @__PURE__ */ React5.createElement(IconButton, {
698
+ "data-testid": "cancel-button",
531
699
  icon: "ph--x--regular",
532
700
  label: t("button cancel"),
533
701
  onClick: onCancel
534
702
  }), onSubmit && /* @__PURE__ */ React5.createElement(IconButton, {
535
703
  type: "submit",
704
+ "data-testid": "save-button",
536
705
  disabled: !canSubmit,
537
706
  icon: "ph--check--regular",
538
707
  label: t("button save"),
539
- onClick: handleSubmit
540
- }))));
708
+ onClick: onSubmit
709
+ })));
541
710
  };
542
711
 
543
712
  // packages/ui/react-ui-form/src/components/FieldEditor/FieldEditor.tsx
544
713
  var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-form/src/components/FieldEditor/FieldEditor.tsx";
545
- var FieldEditor = ({ view, projection, field, registry, onClose, onCancel }) => {
714
+ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
546
715
  const { t } = useTranslation3(translationKey);
547
716
  const [props, setProps] = useState2(projection.getFieldProjection(field.id).props);
548
717
  useEffect3(() => setProps(projection.getFieldProjection(field.id).props), [
@@ -613,22 +782,22 @@ var FieldEditor = ({ view, projection, field, registry, onClose, onCancel }) =>
613
782
  view.fields,
614
783
  field
615
784
  ]);
616
- const handleSubmit = useCallback2((props2) => {
785
+ const handleSave = useCallback2((props2) => {
617
786
  projection.setFieldProjection({
618
787
  field,
619
788
  props: props2
620
789
  });
621
- onClose();
790
+ onSave();
622
791
  }, [
623
792
  projection,
624
793
  field,
625
- onClose
794
+ onSave
626
795
  ]);
627
796
  const handleCancel = useCallback2(() => {
628
797
  requestAnimationFrame(() => onCancel?.());
629
- onClose();
798
+ onSave();
630
799
  }, [
631
- onClose
800
+ onSave
632
801
  ]);
633
802
  if (!fieldSchema) {
634
803
  log3.warn("invalid format", {
@@ -653,7 +822,7 @@ var FieldEditor = ({ view, projection, field, registry, onClose, onCancel }) =>
653
822
  ],
654
823
  onValuesChanged: handleValuesChanged,
655
824
  onValidate: handleValidate,
656
- onSubmit: handleSubmit,
825
+ onSave: handleSave,
657
826
  onCancel: handleCancel,
658
827
  Custom: {
659
828
  ["format"]: (props2) => /* @__PURE__ */ React6.createElement(SelectInput, {
@@ -692,7 +861,7 @@ var ViewMetaSchema = S2.Struct({
692
861
  name: S2.String.annotations({
693
862
  [AST2.TitleAnnotationId]: "View"
694
863
  }),
695
- type: Format.DXN.annotations({
864
+ typename: Format.DXN.annotations({
696
865
  [AST2.TitleAnnotationId]: "Typename"
697
866
  })
698
867
  }).pipe(S2.mutable);
@@ -708,16 +877,16 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
708
877
  name: view.name,
709
878
  // TODO(burdon): Need to warn user of possible consequences of editing.
710
879
  // TODO(burdon): Settings should have domain name owned by user.
711
- type: view.query.type
880
+ typename: view.query.type
712
881
  };
713
882
  }, [
714
883
  view
715
884
  ]);
716
- const handleViewUpdate = useCallback3(({ name, type }) => {
885
+ const handleViewUpdate = useCallback3(({ name, typename }) => {
717
886
  requestAnimationFrame(() => {
718
887
  view.name = name;
719
- view.query.type = type;
720
- schema.updateTypename(type);
888
+ view.query.type = typename;
889
+ schema.updateTypename(typename);
721
890
  });
722
891
  }, [
723
892
  view,
@@ -746,7 +915,7 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
746
915
  autoSave: true,
747
916
  schema: ViewMetaSchema,
748
917
  values: viewValues,
749
- onSubmit: handleViewUpdate
918
+ onSave: handleViewUpdate
750
919
  }), /* @__PURE__ */ React7.createElement("div", null, /* @__PURE__ */ React7.createElement("div", {
751
920
  role: "none",
752
921
  className: "p-2"
@@ -780,7 +949,7 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
780
949
  projection,
781
950
  field,
782
951
  registry,
783
- onClose: handleClose
952
+ onSave: handleClose
784
953
  }), !readonly && !field && /* @__PURE__ */ React7.createElement("div", {
785
954
  className: "flex p-2 justify-center"
786
955
  }, /* @__PURE__ */ React7.createElement(IconButton2, {
@@ -1016,6 +1185,7 @@ export {
1016
1185
  DeprecatedFormInput,
1017
1186
  FieldEditor,
1018
1187
  Form,
1188
+ FormActions,
1019
1189
  GeiPointInput,
1020
1190
  InputHeader,
1021
1191
  NumberInput,