@dxos/react-ui-form 0.7.3-main.2dd075e → 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.
@@ -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
@@ -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,16 +289,22 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
288
289
  values,
289
290
  schemaDirty
290
291
  ]);
291
- const canSave = 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 handleSave = useCallback(() => {
298
+ const handleSave = useCallback(async () => {
297
299
  if (validate(values)) {
298
- onSave?.(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,
@@ -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: 172,
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,
@@ -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: 223,
385
+ L: 234,
382
386
  S: void 0,
383
387
  A: [
384
388
  "obj",
@@ -398,12 +402,25 @@ 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
423
  var Form = ({ classNames, schema, values: initialValues, path = [], readonly, filter, sort, autoSave, testId, onValuesChanged, onValidate, onSave, onCancel, Custom }) => {
406
- const { t } = useTranslation2(translationKey);
407
424
  const onValid = useMemo2(() => autoSave ? onSave : void 0, [
408
425
  autoSave,
409
426
  onSave
@@ -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: 101,
455
+ L: 100,
439
456
  S: void 0,
440
457
  C: (f, a) => f(...a)
441
458
  });
@@ -448,7 +465,7 @@ var Form = ({ classNames, schema, values: initialValues, path = [], readonly, fi
448
465
  className: mx("flex flex-col w-full gap-2 py-2", classNames),
449
466
  "data-testid": testId
450
467
  }, properties.map((property) => {
451
- const { ast, name, type, format, title, description, examples, options } = property;
468
+ const { ast, name, type, format, title, description, examples, options, array } = property;
452
469
  const key = [
453
470
  ...path,
454
471
  name
@@ -477,6 +494,22 @@ var Form = ({ classNames, schema, values: initialValues, path = [], readonly, fi
477
494
  }));
478
495
  }
479
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
+ }
480
513
  }
481
514
  if (!InputComponent) {
482
515
  if (type === "object") {
@@ -505,7 +538,7 @@ var Form = ({ classNames, schema, values: initialValues, path = [], readonly, fi
505
538
  type
506
539
  }, {
507
540
  F: __dxlog_file2,
508
- L: 165,
541
+ L: 185,
509
542
  S: void 0,
510
543
  C: (f, a) => f(...a)
511
544
  });
@@ -524,7 +557,140 @@ var Form = ({ classNames, schema, values: initialValues, path = [], readonly, fi
524
557
  placeholder,
525
558
  ...inputProps
526
559
  }));
527
- }).filter(isNotFalsy), (onCancel || onSave) && !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", {
528
694
  role: "none",
529
695
  className: "flex justify-center"
530
696
  }, /* @__PURE__ */ React5.createElement("div", {
@@ -535,14 +701,14 @@ var Form = ({ classNames, schema, values: initialValues, path = [], readonly, fi
535
701
  icon: "ph--x--regular",
536
702
  label: t("button cancel"),
537
703
  onClick: onCancel
538
- }), onSave && /* @__PURE__ */ React5.createElement(IconButton, {
704
+ }), onSubmit && /* @__PURE__ */ React5.createElement(IconButton, {
539
705
  type: "submit",
540
706
  "data-testid": "save-button",
541
- disabled: !canSave,
707
+ disabled: !canSubmit,
542
708
  icon: "ph--check--regular",
543
709
  label: t("button save"),
544
- onClick: handleSave
545
- }))));
710
+ onClick: onSubmit
711
+ })));
546
712
  };
547
713
 
548
714
  // packages/ui/react-ui-form/src/components/FieldEditor/FieldEditor.tsx
@@ -713,7 +879,7 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
713
879
  name: view.name,
714
880
  // TODO(burdon): Need to warn user of possible consequences of editing.
715
881
  // TODO(burdon): Settings should have domain name owned by user.
716
- typename: view.query.typename
882
+ typename: view.query.type
717
883
  };
718
884
  }, [
719
885
  view
@@ -721,7 +887,7 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
721
887
  const handleViewUpdate = useCallback3(({ name, typename }) => {
722
888
  requestAnimationFrame(() => {
723
889
  view.name = name;
724
- view.query.typename = typename;
890
+ view.query.type = typename;
725
891
  schema.updateTypename(typename);
726
892
  });
727
893
  }, [
@@ -1021,6 +1187,7 @@ export {
1021
1187
  DeprecatedFormInput,
1022
1188
  FieldEditor,
1023
1189
  Form,
1190
+ FormActions,
1024
1191
  GeiPointInput,
1025
1192
  InputHeader,
1026
1193
  NumberInput,