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