@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.
@@ -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
@@ -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,16 +287,22 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
286
287
  values,
287
288
  schemaDirty
288
289
  ]);
289
- const canSave = 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 handleSave = useCallback(() => {
296
+ const handleSave = useCallback(async () => {
295
297
  if (validate(values)) {
296
- onSave?.(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,
@@ -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: 172,
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,
@@ -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: 223,
383
+ L: 234,
380
384
  S: void 0,
381
385
  A: [
382
386
  "obj",
@@ -396,12 +400,25 @@ 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
421
  var Form = ({ classNames, schema, values: initialValues, path = [], readonly, filter, sort, autoSave, testId, onValuesChanged, onValidate, onSave, onCancel, Custom }) => {
404
- const { t } = useTranslation2(translationKey);
405
422
  const onValid = useMemo2(() => autoSave ? onSave : void 0, [
406
423
  autoSave,
407
424
  onSave
@@ -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: 101,
453
+ L: 100,
437
454
  S: void 0,
438
455
  C: (f, a) => f(...a)
439
456
  });
@@ -446,7 +463,7 @@ var Form = ({ classNames, schema, values: initialValues, path = [], readonly, fi
446
463
  className: mx("flex flex-col w-full gap-2 py-2", classNames),
447
464
  "data-testid": testId
448
465
  }, properties.map((property) => {
449
- const { ast, name, type, format, title, description, examples, options } = property;
466
+ const { ast, name, type, format, title, description, examples, options, array } = property;
450
467
  const key = [
451
468
  ...path,
452
469
  name
@@ -475,6 +492,22 @@ var Form = ({ classNames, schema, values: initialValues, path = [], readonly, fi
475
492
  }));
476
493
  }
477
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
+ }
478
511
  }
479
512
  if (!InputComponent) {
480
513
  if (type === "object") {
@@ -503,7 +536,7 @@ var Form = ({ classNames, schema, values: initialValues, path = [], readonly, fi
503
536
  type
504
537
  }, {
505
538
  F: __dxlog_file2,
506
- L: 165,
539
+ L: 185,
507
540
  S: void 0,
508
541
  C: (f, a) => f(...a)
509
542
  });
@@ -522,7 +555,140 @@ var Form = ({ classNames, schema, values: initialValues, path = [], readonly, fi
522
555
  placeholder,
523
556
  ...inputProps
524
557
  }));
525
- }).filter(isNotFalsy), (onCancel || onSave) && !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", {
526
692
  role: "none",
527
693
  className: "flex justify-center"
528
694
  }, /* @__PURE__ */ React5.createElement("div", {
@@ -533,14 +699,14 @@ var Form = ({ classNames, schema, values: initialValues, path = [], readonly, fi
533
699
  icon: "ph--x--regular",
534
700
  label: t("button cancel"),
535
701
  onClick: onCancel
536
- }), onSave && /* @__PURE__ */ React5.createElement(IconButton, {
702
+ }), onSubmit && /* @__PURE__ */ React5.createElement(IconButton, {
537
703
  type: "submit",
538
704
  "data-testid": "save-button",
539
- disabled: !canSave,
705
+ disabled: !canSubmit,
540
706
  icon: "ph--check--regular",
541
707
  label: t("button save"),
542
- onClick: handleSave
543
- }))));
708
+ onClick: onSubmit
709
+ })));
544
710
  };
545
711
 
546
712
  // packages/ui/react-ui-form/src/components/FieldEditor/FieldEditor.tsx
@@ -711,7 +877,7 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
711
877
  name: view.name,
712
878
  // TODO(burdon): Need to warn user of possible consequences of editing.
713
879
  // TODO(burdon): Settings should have domain name owned by user.
714
- typename: view.query.typename
880
+ typename: view.query.type
715
881
  };
716
882
  }, [
717
883
  view
@@ -719,7 +885,7 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
719
885
  const handleViewUpdate = useCallback3(({ name, typename }) => {
720
886
  requestAnimationFrame(() => {
721
887
  view.name = name;
722
- view.query.typename = typename;
888
+ view.query.type = typename;
723
889
  schema.updateTypename(typename);
724
890
  });
725
891
  }, [
@@ -1019,6 +1185,7 @@ export {
1019
1185
  DeprecatedFormInput,
1020
1186
  FieldEditor,
1021
1187
  Form,
1188
+ FormActions,
1022
1189
  GeiPointInput,
1023
1190
  InputHeader,
1024
1191
  NumberInput,