@dxos/react-ui-form 0.7.5-main.9d2a38b → 0.7.5-main.e94eead

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 (83) hide show
  1. package/dist/lib/browser/index.mjs +1014 -571
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node/index.cjs +1031 -599
  5. package/dist/lib/node/index.cjs.map +4 -4
  6. package/dist/lib/node/meta.json +1 -1
  7. package/dist/lib/node-esm/index.mjs +1014 -571
  8. package/dist/lib/node-esm/index.mjs.map +4 -4
  9. package/dist/lib/node-esm/meta.json +1 -1
  10. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +1 -2
  11. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  12. package/dist/types/src/components/Form/ArrayField.d.ts +15 -0
  13. package/dist/types/src/components/Form/ArrayField.d.ts.map +1 -0
  14. package/dist/types/src/components/Form/Defaults.d.ts +6 -7
  15. package/dist/types/src/components/Form/Defaults.d.ts.map +1 -1
  16. package/dist/types/src/components/Form/Form.d.ts +12 -21
  17. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  18. package/dist/types/src/components/Form/Form.stories.d.ts +2 -1
  19. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  20. package/dist/types/src/components/Form/FormActions.d.ts +6 -0
  21. package/dist/types/src/components/Form/FormActions.d.ts.map +1 -0
  22. package/dist/types/src/components/Form/FormContent.d.ts +25 -0
  23. package/dist/types/src/components/Form/FormContent.d.ts.map +1 -0
  24. package/dist/types/src/components/Form/FormContext.d.ts +23 -0
  25. package/dist/types/src/components/Form/FormContext.d.ts.map +1 -0
  26. package/dist/types/src/components/Form/Input.d.ts +7 -8
  27. package/dist/types/src/components/Form/Input.d.ts.map +1 -1
  28. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +1 -3
  29. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +1 -1
  30. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +3 -0
  31. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +1 -0
  32. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +7 -0
  33. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +1 -0
  34. package/dist/types/src/components/Form/custom/TupleInput.d.ts +2 -4
  35. package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +1 -1
  36. package/dist/types/src/components/Form/custom/index.d.ts +1 -0
  37. package/dist/types/src/components/Form/custom/index.d.ts.map +1 -1
  38. package/dist/types/src/components/Form/factory.d.ts +2 -2
  39. package/dist/types/src/components/Form/factory.d.ts.map +1 -1
  40. package/dist/types/src/components/Form/index.d.ts +2 -0
  41. package/dist/types/src/components/Form/index.d.ts.map +1 -1
  42. package/dist/types/src/components/ViewEditor/Test.stories.d.ts +1 -2
  43. package/dist/types/src/components/ViewEditor/Test.stories.d.ts.map +1 -1
  44. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +1 -2
  45. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  46. package/dist/types/src/components/deprecated/Form.d.ts +1 -2
  47. package/dist/types/src/components/deprecated/Form.d.ts.map +1 -1
  48. package/dist/types/src/components/deprecated/Form.stories.d.ts +1 -2
  49. package/dist/types/src/components/deprecated/Form.stories.d.ts.map +1 -1
  50. package/dist/types/src/components/deprecated/FormInput.d.ts +6 -1
  51. package/dist/types/src/components/deprecated/FormInput.d.ts.map +1 -1
  52. package/dist/types/src/components/testing.d.ts +3 -3
  53. package/dist/types/src/components/testing.d.ts.map +1 -1
  54. package/dist/types/src/hooks/useForm.d.ts +11 -11
  55. package/dist/types/src/hooks/useForm.d.ts.map +1 -1
  56. package/dist/types/src/translations.d.ts +6 -0
  57. package/dist/types/src/translations.d.ts.map +1 -1
  58. package/dist/types/src/util/field.d.ts.map +1 -1
  59. package/dist/types/src/util/formatting.d.ts.map +1 -1
  60. package/package.json +29 -27
  61. package/src/components/FieldEditor/FieldEditor.stories.tsx +1 -1
  62. package/src/components/FieldEditor/FieldEditor.tsx +7 -6
  63. package/src/components/Form/ArrayField.tsx +111 -0
  64. package/src/components/Form/Defaults.tsx +78 -34
  65. package/src/components/Form/Form.stories.tsx +15 -5
  66. package/src/components/Form/Form.tsx +50 -355
  67. package/src/components/Form/FormActions.tsx +41 -0
  68. package/src/components/Form/FormContent.tsx +170 -0
  69. package/src/components/Form/FormContext.tsx +96 -0
  70. package/src/components/Form/Input.tsx +5 -6
  71. package/src/components/Form/custom/GeoPointInput.tsx +44 -23
  72. package/src/components/Form/custom/SelectOptionsInput.stories.tsx +58 -0
  73. package/src/components/Form/custom/SelectOptionsInput.tsx +190 -0
  74. package/src/components/Form/custom/TupleInput.tsx +5 -9
  75. package/src/components/Form/custom/index.ts +1 -0
  76. package/src/components/Form/factory.tsx +6 -7
  77. package/src/components/Form/index.ts +3 -0
  78. package/src/components/ViewEditor/ViewEditor.tsx +7 -4
  79. package/src/components/deprecated/FormInput.tsx +20 -7
  80. package/src/hooks/useForm.ts +116 -79
  81. package/src/translations.ts +9 -1
  82. package/src/util/field.ts +19 -1
  83. package/src/util/formatting.ts +29 -1
@@ -29,12 +29,13 @@ var translations_default = [
29
29
  "format hostname": "Hostname",
30
30
  "format integer": "Integer",
31
31
  "format json": "JSON",
32
- "format latlng": "LatLng",
32
+ "format latlng": "Geopoint",
33
33
  "format markdown": "Markdown",
34
34
  "format percent": "Percent",
35
35
  "format ref": "Reference",
36
36
  "format regex": "RegExp",
37
37
  "format string": "String",
38
+ "format single-select": "Select",
38
39
  "format text": "Long text",
39
40
  "format time": "Time",
40
41
  "format timestamp": "Timestamp",
@@ -44,7 +45,13 @@ var translations_default = [
44
45
  "format uuid": "UUID",
45
46
  // Placeholders
46
47
  "placeholder latitude": "Latitude",
47
- "placeholder longitude": "Longitude"
48
+ "placeholder longitude": "Longitude",
49
+ // SelectOptionsInput
50
+ "select option label": "Label",
51
+ "select option label placeholder": "Option label",
52
+ "select option color": "Color",
53
+ "select option delete": "Delete",
54
+ "select option add": "Add option"
48
55
  }
49
56
  }
50
57
  }
@@ -53,6 +60,16 @@ var translations_default = [
53
60
  // packages/ui/react-ui-form/src/components/deprecated/FormInput.tsx
54
61
  import React from "react";
55
62
  import { Input } from "@dxos/react-ui";
63
+ import { mx, textBlockWidth } from "@dxos/react-ui-theme";
64
+ var DeprecatedFormContainer = ({ children, classNames }) => {
65
+ return /* @__PURE__ */ React.createElement("div", {
66
+ role: "none",
67
+ className: "w-full p-4 justify-center overflow-x-hidden overflow-y-auto"
68
+ }, /* @__PURE__ */ React.createElement("div", {
69
+ role: "form",
70
+ className: mx("flex flex-col", textBlockWidth, classNames)
71
+ }, children));
72
+ };
56
73
  var DeprecatedFormInput = ({ label, description, secondary, children }) => {
57
74
  const primary = /* @__PURE__ */ React.createElement("div", {
58
75
  role: "none",
@@ -61,224 +78,57 @@ var DeprecatedFormInput = ({ label, description, secondary, children }) => {
61
78
  role: "none",
62
79
  className: "flex flex-col w-full"
63
80
  }, /* @__PURE__ */ React.createElement(Input.Label, {
64
- classNames: "flex min-h-[40px] items-center"
81
+ classNames: "flex min-bs-[--rail-action] items-center",
82
+ style: {
83
+ fontSize: "unset"
84
+ }
65
85
  }, label), description && /* @__PURE__ */ React.createElement(Input.DescriptionAndValidation, {
66
86
  classNames: "mbs-0.5"
67
87
  }, /* @__PURE__ */ React.createElement(Input.Description, null, description))), /* @__PURE__ */ React.createElement("div", {
68
88
  role: "none"
69
89
  }, /* @__PURE__ */ React.createElement("div", {
70
90
  role: "none",
71
- className: "flex min-h-[40px] items-center"
91
+ className: "flex min-bs-[--rail-action] items-center"
72
92
  }, children))));
73
93
  if (secondary) {
74
94
  return /* @__PURE__ */ React.createElement("div", {
75
- role: "none",
76
- className: "flex flex-col w-full"
95
+ role: "none"
77
96
  }, primary, secondary);
78
97
  }
79
98
  return primary;
80
99
  };
81
100
 
82
101
  // packages/ui/react-ui-form/src/components/FieldEditor/FieldEditor.tsx
83
- import React7, { useCallback as useCallback2, useEffect as useEffect3, useMemo as useMemo3, useState as useState2 } from "react";
102
+ import React12, { useCallback as useCallback6, useEffect as useEffect7, useMemo as useMemo7, useState as useState4 } from "react";
84
103
  import { FormatEnum as FormatEnum2, FormatEnums, formatToType } from "@dxos/echo-schema";
85
- import { log as log3 } from "@dxos/log";
86
- import { useTranslation as useTranslation3 } from "@dxos/react-ui";
87
- import { getFormatSchema, getSchemaProperties as getSchemaProperties2, sortProperties } from "@dxos/schema";
88
-
89
- // packages/ui/react-ui-form/src/components/Form/custom/GeoPointInput.tsx
90
- import React3 from "react";
91
- import { Input as Input2, useTranslation } from "@dxos/react-ui";
92
- import { safeParseFloat } from "@dxos/util";
93
-
94
- // packages/ui/react-ui-form/src/components/Form/Input.tsx
95
- import React2 from "react";
96
- import { Icon, Tooltip } from "@dxos/react-ui";
97
- import { errorText } from "@dxos/react-ui-theme";
98
- var InputHeader = ({ children, error }) => {
99
- return /* @__PURE__ */ React2.createElement("div", {
100
- role: "none",
101
- className: "flex justify-between items-center mb-1"
102
- }, children, error && /* @__PURE__ */ React2.createElement(Tooltip.Root, null, /* @__PURE__ */ React2.createElement(Tooltip.Trigger, {
103
- asChild: true
104
- }, /* @__PURE__ */ React2.createElement(Icon, {
105
- icon: "ph--warning--regular",
106
- size: 4,
107
- classNames: errorText
108
- })), /* @__PURE__ */ React2.createElement(Tooltip.Content, {
109
- side: "bottom"
110
- }, /* @__PURE__ */ React2.createElement(Tooltip.Arrow, null), error)));
111
- };
112
-
113
- // packages/ui/react-ui-form/src/components/Form/custom/GeoPointInput.tsx
114
- var GeoPointInput = ({ property, type, label, disabled, getStatus, getValue: getValue2, onValueChange, onBlur }) => {
115
- const { t } = useTranslation(translationKey);
116
- const { status, error } = getStatus?.(property);
117
- const [lng = 0, lat = 0] = getValue2(property) ?? [];
118
- return /* @__PURE__ */ React3.createElement(Input2.Root, {
119
- validationValence: status
120
- }, /* @__PURE__ */ React3.createElement(InputHeader, {
121
- error
122
- }, /* @__PURE__ */ React3.createElement(Input2.Label, null, label)), /* @__PURE__ */ React3.createElement("div", {
123
- className: "grid grid-cols-2 gap-2"
124
- }, /* @__PURE__ */ React3.createElement(Input2.TextInput, {
125
- type: "number",
126
- disabled,
127
- placeholder: t("placeholder longitude"),
128
- value: lng,
129
- onChange: (event) => onValueChange(property, type, [
130
- lat,
131
- safeParseFloat(event.target.value, 0)
132
- ]),
133
- onBlur
134
- }), /* @__PURE__ */ React3.createElement(Input2.TextInput, {
135
- type: "number",
136
- disabled,
137
- placeholder: t("placeholder latitude"),
138
- value: lat,
139
- onChange: (event) => onValueChange(property, type, [
140
- safeParseFloat(event.target.value, 0),
141
- lng
142
- ]),
143
- onBlur
144
- })));
145
- };
146
-
147
- // packages/ui/react-ui-form/src/components/Form/custom/TupleInput.tsx
148
- import React4 from "react";
149
- import { Input as Input3 } from "@dxos/react-ui";
150
- import { mx } from "@dxos/react-ui-theme";
151
- import { safeParseFloat as safeParseFloat2 } from "@dxos/util";
152
- var gridCols = [
153
- "grid-cols-1",
154
- "grid-cols-2",
155
- "grid-cols-3",
156
- "grid-cols-4"
157
- ];
158
- var TupleInput = ({ binding, property, type, label, disabled, getStatus, getValue: getValue2, onValueChange, onBlur }) => {
159
- const { status, error } = getStatus?.(property);
160
- const values = getValue2(property) ?? {};
161
- return /* @__PURE__ */ React4.createElement(Input3.Root, {
162
- validationValence: status
163
- }, /* @__PURE__ */ React4.createElement(InputHeader, {
164
- error
165
- }, /* @__PURE__ */ React4.createElement(Input3.Label, null, label)), /* @__PURE__ */ React4.createElement("div", {
166
- className: mx("grid gap-2", gridCols[binding.length - 1])
167
- }, binding.map((prop) => /* @__PURE__ */ React4.createElement(Input3.TextInput, {
168
- key: prop,
169
- type: "number",
170
- disabled,
171
- value: values[prop],
172
- onChange: (event) => onValueChange(property, type, {
173
- ...values,
174
- [prop]: safeParseFloat2(event.target.value, 0)
175
- }),
176
- onBlur
177
- }))));
178
- };
179
-
180
- // packages/ui/react-ui-form/src/components/Form/Defaults.tsx
181
- import React5 from "react";
182
- import { Input as Input4, Select } from "@dxos/react-ui";
183
- var TextInput = ({ property, type, label, inputOnly, disabled, placeholder, getStatus, getValue: getValue2, onValueChange, onBlur }) => {
184
- const { status, error } = getStatus?.(property);
185
- return /* @__PURE__ */ React5.createElement(Input4.Root, {
186
- validationValence: status
187
- }, !inputOnly && /* @__PURE__ */ React5.createElement(InputHeader, {
188
- error
189
- }, /* @__PURE__ */ React5.createElement(Input4.Label, null, label)), /* @__PURE__ */ React5.createElement(Input4.TextInput, {
190
- disabled,
191
- placeholder,
192
- value: getValue2(property) ?? "",
193
- onChange: (event) => onValueChange(property, type, event.target.value),
194
- onBlur
195
- }), inputOnly && /* @__PURE__ */ React5.createElement(Input4.Validation, null, error));
196
- };
197
- var NumberInput = ({ property, type, label, inputOnly, disabled, placeholder, getStatus, getValue: getValue2, onValueChange, onBlur }) => {
198
- const { status, error } = getStatus?.(property);
199
- return /* @__PURE__ */ React5.createElement(Input4.Root, {
200
- validationValence: status
201
- }, !inputOnly && /* @__PURE__ */ React5.createElement(InputHeader, {
202
- error
203
- }, /* @__PURE__ */ React5.createElement(Input4.Label, null, label)), /* @__PURE__ */ React5.createElement(Input4.TextInput, {
204
- type: "number",
205
- disabled,
206
- placeholder,
207
- value: getValue2(property) ?? 0,
208
- onChange: (event) => onValueChange(property, type, event.target.value),
209
- onBlur
210
- }), inputOnly && /* @__PURE__ */ React5.createElement(Input4.DescriptionAndValidation, null, error));
211
- };
212
- var BooleanInput = ({ property, type, label, inputOnly, getStatus, getValue: getValue2, onValueChange }) => {
213
- const { status, error } = getStatus?.(property);
214
- return /* @__PURE__ */ React5.createElement(Input4.Root, {
215
- validationValence: status
216
- }, !inputOnly && /* @__PURE__ */ React5.createElement(InputHeader, {
217
- error
218
- }, /* @__PURE__ */ React5.createElement(Input4.Label, null, label)), /* @__PURE__ */ React5.createElement(Input4.Switch, {
219
- checked: getValue2(property),
220
- onCheckedChange: (value) => onValueChange(property, type, value)
221
- }), inputOnly && /* @__PURE__ */ React5.createElement(Input4.DescriptionAndValidation, null, error));
222
- };
223
- var SelectInput = ({ property, type, label, inputOnly, disabled, placeholder, options, getStatus, getValue: getValue2, onValueChange }) => {
224
- const { status, error } = getStatus?.(property);
225
- return /* @__PURE__ */ React5.createElement(Input4.Root, {
226
- validationValence: status
227
- }, !inputOnly && /* @__PURE__ */ React5.createElement(InputHeader, {
228
- error
229
- }, /* @__PURE__ */ React5.createElement(Input4.Label, null, label)), /* @__PURE__ */ React5.createElement(Select.Root, {
230
- value: getValue2(property),
231
- onValueChange: (value) => onValueChange(property, type, value)
232
- }, /* @__PURE__ */ React5.createElement(Select.TriggerButton, {
233
- classNames: "is-full",
234
- disabled,
235
- placeholder
236
- }), /* @__PURE__ */ React5.createElement(Select.Portal, null, /* @__PURE__ */ React5.createElement(Select.Content, null, /* @__PURE__ */ React5.createElement(Select.Viewport, null, options?.map(({ value, label: label2 }) => (
237
- // NOTE: Numeric values are converted to and from strings.
238
- /* @__PURE__ */ React5.createElement(Select.Option, {
239
- key: String(value),
240
- value: String(value)
241
- }, label2 ?? String(value))
242
- )))))), inputOnly && /* @__PURE__ */ React5.createElement(Input4.DescriptionAndValidation, null, error));
243
- };
244
-
245
- // packages/ui/react-ui-form/src/components/Form/Form.tsx
246
- import { pipe } from "effect";
247
- import { capitalize } from "effect/String";
248
- import React6, { useEffect as useEffect2, useMemo as useMemo2 } from "react";
249
- import { AST, S } from "@dxos/echo-schema";
250
- import { findNode, getDiscriminatedType, isDiscriminatedUnion, SimpleType } from "@dxos/effect";
251
104
  import { log as log2 } from "@dxos/log";
252
- import { IconButton, useTranslation as useTranslation2 } from "@dxos/react-ui";
253
- import { mx as mx2 } from "@dxos/react-ui-theme";
254
- import { getSchemaProperties } from "@dxos/schema";
255
- import { isNotFalsy } from "@dxos/util";
105
+ import { useTranslation as useTranslation5 } from "@dxos/react-ui";
106
+ import { getFormatSchema, getSchemaProperties as getSchemaProperties3, sortProperties } from "@dxos/schema";
256
107
 
257
- // packages/ui/react-ui-form/src/components/Form/factory.tsx
258
- import { FormatEnum } from "@dxos/echo-schema";
259
- var getInputComponent = (type, format) => {
260
- switch (format) {
261
- case FormatEnum.LatLng:
262
- return GeoPointInput;
263
- }
264
- switch (type) {
265
- case "string":
266
- return TextInput;
267
- case "number":
268
- return NumberInput;
269
- case "boolean":
270
- return BooleanInput;
271
- }
272
- };
108
+ // packages/ui/react-ui-form/src/components/Form/FormContext.tsx
109
+ import React2, { createContext, useContext, useEffect as useEffect2, useMemo as useMemo2 } from "react";
110
+ import { raise } from "@dxos/debug";
111
+ import { getValue as getValue2 } from "@dxos/echo-schema";
112
+ import { createJsonPath as createJsonPath2 } from "@dxos/effect";
273
113
 
274
114
  // packages/ui/react-ui-form/src/hooks/useForm.ts
275
115
  import { useCallback, useEffect, useMemo, useState } from "react";
276
116
  import { getValue, setValue } from "@dxos/echo-schema";
117
+ import { AST, createJsonPath, fromEffectValidationPath } from "@dxos/effect";
277
118
  import { invariant } from "@dxos/invariant";
278
119
  import { log } from "@dxos/log";
279
120
  import { validateSchema } from "@dxos/schema";
280
121
  var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-form/src/hooks/useForm.ts";
281
122
  var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, onSave }) => {
123
+ invariant(AST.isTypeLiteral(schema.ast), void 0, {
124
+ F: __dxlog_file,
125
+ L: 92,
126
+ S: void 0,
127
+ A: [
128
+ "AST.isTypeLiteral(schema.ast)",
129
+ ""
130
+ ]
131
+ });
282
132
  const [values, setValues] = useState(initialValues);
283
133
  useEffect(() => {
284
134
  setValues(initialValues);
@@ -315,8 +165,13 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
315
165
  values,
316
166
  schemaDirty
317
167
  ]);
318
- const canSave = useMemo(() => !saving && Object.keys(values).every((property) => touched[property] === false || !errors[property]), [
319
- values,
168
+ const formIsValid = useMemo(() => {
169
+ return Object.keys(errors).length === 0;
170
+ }, [
171
+ errors
172
+ ]);
173
+ const canSave = useMemo(() => !saving && // Check if any error paths that are touched have errors.
174
+ !Object.entries(touched).some(([path, isTouched]) => isTouched && Object.keys(errors).some((errorPath) => errorPath === path || errorPath.startsWith(`${path}.`) || errorPath.startsWith(`${path}[`))), [
320
175
  touched,
321
176
  errors,
322
177
  saving
@@ -338,17 +193,23 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
338
193
  onSave,
339
194
  changed
340
195
  ]);
341
- const getStatus = useCallback((property) => ({
342
- status: errors[property] ? "error" : void 0,
343
- error: errors[property] ? errors[property] : void 0
344
- }), [
345
- touched,
196
+ const getStatus = useCallback((path) => {
197
+ const jsonPath = Array.isArray(path) ? createJsonPath(path) : path;
198
+ const matchingError = Object.entries(errors).find(([errorPath]) => errorPath === jsonPath || errorPath.startsWith(`${jsonPath}.`) || errorPath.startsWith(`${jsonPath}[`));
199
+ return {
200
+ status: matchingError ? "error" : void 0,
201
+ error: matchingError ? matchingError[1] : void 0
202
+ };
203
+ }, [
346
204
  errors
347
205
  ]);
348
- const getFormValue = (property) => {
349
- return getValue(values, property);
350
- };
351
- const onValueChange = (property, type, value) => {
206
+ const getFormValue = useCallback((path) => {
207
+ return getValue(values, createJsonPath(path));
208
+ }, [
209
+ values
210
+ ]);
211
+ const onValueChange = useCallback((path, type, value) => {
212
+ const jsonPath = createJsonPath(path);
352
213
  let parsedValue = value;
353
214
  try {
354
215
  if (type === "number") {
@@ -357,19 +218,19 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
357
218
  } catch (err) {
358
219
  log.catch(err, void 0, {
359
220
  F: __dxlog_file,
360
- L: 178,
221
+ L: 202,
361
222
  S: void 0,
362
223
  C: (f, a) => f(...a)
363
224
  });
364
225
  parsedValue = void 0;
365
226
  }
366
227
  const newValues = {
367
- ...setValue(values, property, parsedValue)
228
+ ...setValue(values, jsonPath, parsedValue)
368
229
  };
369
230
  setValues(newValues);
370
231
  setChanged((prev) => ({
371
232
  ...prev,
372
- [property]: true
233
+ [jsonPath]: true
373
234
  }));
374
235
  onValuesChanged?.(newValues);
375
236
  const isValid = validate(newValues);
@@ -378,40 +239,57 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
378
239
  changed
379
240
  });
380
241
  }
381
- };
382
- const onBlur = useCallback((event) => {
383
- const { name } = event.target;
242
+ }, [
243
+ values,
244
+ onValuesChanged,
245
+ validate,
246
+ onValid,
247
+ changed
248
+ ]);
249
+ const onTouched = useCallback((path) => {
250
+ const jsonPath = createJsonPath(path);
384
251
  setTouched((touched2) => ({
385
252
  ...touched2,
386
- [name]: true
253
+ [jsonPath]: true
387
254
  }));
388
- if (event.relatedTarget?.getAttribute("type") === "submit") {
389
- setTouched(createKeySet(values, true));
390
- }
391
255
  validate(values);
392
256
  }, [
393
257
  validate,
394
258
  values
395
259
  ]);
396
- return {
260
+ return useMemo(() => ({
397
261
  // State.
398
262
  values,
399
263
  errors,
400
264
  touched,
401
265
  changed,
402
266
  canSave,
267
+ formIsValid,
268
+ // Actions.
403
269
  handleSave,
404
270
  // Field utils.
405
271
  getStatus,
406
272
  getValue: getFormValue,
407
273
  onValueChange,
408
- onBlur
409
- };
274
+ onTouched
275
+ }), [
276
+ values,
277
+ errors,
278
+ touched,
279
+ changed,
280
+ canSave,
281
+ formIsValid,
282
+ handleSave,
283
+ getStatus,
284
+ getFormValue,
285
+ onValueChange,
286
+ onTouched
287
+ ]);
410
288
  };
411
289
  var createKeySet = (obj, value) => {
412
290
  invariant(obj, void 0, {
413
291
  F: __dxlog_file,
414
- L: 229,
292
+ L: 264,
415
293
  S: void 0,
416
294
  A: [
417
295
  "obj",
@@ -423,340 +301,858 @@ var createKeySet = (obj, value) => {
423
301
  [key]: value
424
302
  }), {});
425
303
  };
426
- var flatMap = (errors) => {
427
- return errors.reduce((result, { path, message }) => {
428
- if (!(path in result)) {
429
- result[path] = message;
304
+ var flatMap = (errors) => {
305
+ return errors.reduce((result, { path, message }) => {
306
+ const jsonPath = fromEffectValidationPath(path);
307
+ if (!(jsonPath in result)) {
308
+ result[jsonPath] = message;
309
+ }
310
+ return result;
311
+ }, {});
312
+ };
313
+
314
+ // packages/ui/react-ui-form/src/components/Form/FormContext.tsx
315
+ var FormContext = /* @__PURE__ */ createContext(void 0);
316
+ var useFormContext = () => {
317
+ return useContext(FormContext) ?? raise(new Error("Missing FormContext"));
318
+ };
319
+ var useFormValues = (path = []) => {
320
+ const { values: formValues } = useFormContext();
321
+ const jsonPath = createJsonPath2(path);
322
+ return getValue2(formValues, jsonPath);
323
+ };
324
+ var useInputProps = (path = []) => {
325
+ const { getStatus, getValue: getFormValue, onValueChange, onTouched } = useFormContext();
326
+ const stablePath = useMemo2(() => path, [
327
+ Array.isArray(path) ? path.join(".") : path
328
+ ]);
329
+ return useMemo2(() => ({
330
+ getStatus: () => getStatus(stablePath),
331
+ getValue: () => getFormValue(stablePath),
332
+ onValueChange: (type, value) => onValueChange(stablePath, type, value),
333
+ onBlur: () => onTouched(stablePath)
334
+ }), [
335
+ getStatus,
336
+ getFormValue,
337
+ onValueChange,
338
+ onTouched,
339
+ stablePath
340
+ ]);
341
+ };
342
+ var FormProvider = ({ children, formRef, autoSave, ...formOptions }) => {
343
+ const form = useForm(formOptions);
344
+ useEffect2(() => {
345
+ if (!formRef?.current) {
346
+ return;
347
+ }
348
+ const handleKeyDown = (event) => {
349
+ const keyIsEnter = event.key === "Enter";
350
+ const modifierUsed = event.ctrlKey || event.altKey || event.metaKey || event.shiftKey;
351
+ const inputIsTextarea = event.target.tagName.toLowerCase() === "textarea";
352
+ const inputOptOut = event.target.hasAttribute("data-no-submit");
353
+ const shouldSubmitRegularInput = !inputIsTextarea && keyIsEnter && !modifierUsed;
354
+ const shouldSubmitTextarea = inputIsTextarea && keyIsEnter && event.metaKey;
355
+ if ((shouldSubmitRegularInput || shouldSubmitTextarea) && !inputOptOut) {
356
+ if (!autoSave && form.canSave) {
357
+ form.handleSave();
358
+ }
359
+ if (autoSave && form.formIsValid) {
360
+ event.target.blur();
361
+ }
362
+ }
363
+ };
364
+ const formElement = formRef.current;
365
+ formElement.addEventListener("keydown", handleKeyDown);
366
+ return () => formElement.removeEventListener("keydown", handleKeyDown);
367
+ }, [
368
+ formRef,
369
+ form.canSave,
370
+ form.formIsValid,
371
+ form.handleSave,
372
+ autoSave
373
+ ]);
374
+ return /* @__PURE__ */ React2.createElement(FormContext.Provider, {
375
+ value: form
376
+ }, children);
377
+ };
378
+
379
+ // packages/ui/react-ui-form/src/components/Form/custom/GeoPointInput.tsx
380
+ import React4, { useCallback as useCallback2, useState as useState2, useEffect as useEffect3, useMemo as useMemo3 } from "react";
381
+ import { GeoLocation } from "@dxos/echo-schema";
382
+ import { Input as Input2, useTranslation } from "@dxos/react-ui";
383
+ import { safeParseFloat } from "@dxos/util";
384
+
385
+ // packages/ui/react-ui-form/src/components/Form/Input.tsx
386
+ import React3 from "react";
387
+ import { Icon, Tooltip } from "@dxos/react-ui";
388
+ import { errorText } from "@dxos/react-ui-theme";
389
+ var InputHeader = ({ children, error }) => {
390
+ return /* @__PURE__ */ React3.createElement("div", {
391
+ role: "none",
392
+ className: "flex justify-between items-center mb-1"
393
+ }, children, error && /* @__PURE__ */ React3.createElement(Tooltip.Root, null, /* @__PURE__ */ React3.createElement(Tooltip.Trigger, {
394
+ asChild: true
395
+ }, /* @__PURE__ */ React3.createElement(Icon, {
396
+ icon: "ph--warning--regular",
397
+ size: 4,
398
+ classNames: errorText
399
+ })), /* @__PURE__ */ React3.createElement(Tooltip.Content, {
400
+ side: "bottom"
401
+ }, /* @__PURE__ */ React3.createElement(Tooltip.Arrow, null), error)));
402
+ };
403
+
404
+ // packages/ui/react-ui-form/src/components/Form/custom/GeoPointInput.tsx
405
+ var GeoPointInput = ({ type, label, disabled, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
406
+ const { t } = useTranslation(translationKey);
407
+ const { status, error } = getStatus();
408
+ const geoPoint = useMemo3(() => getValue3() ?? [
409
+ 0,
410
+ 0
411
+ ], [
412
+ getValue3
413
+ ]);
414
+ const geoLocation = useMemo3(() => GeoLocation.fromGeoPoint(geoPoint), [
415
+ geoPoint
416
+ ]);
417
+ const [longitudeText, setLongitudeText] = useState2(geoLocation.longitude?.toString());
418
+ const [latitudeText, setLatitudeText] = useState2(geoLocation.latitude?.toString());
419
+ useEffect3(() => {
420
+ const location = GeoLocation.fromGeoPoint(geoPoint);
421
+ setLongitudeText(location.longitude.toString());
422
+ setLatitudeText(location.latitude.toString());
423
+ }, [
424
+ geoPoint
425
+ ]);
426
+ const handleCoordinateChange = useCallback2((coordinateType, setText) => (event) => {
427
+ const inputText = event.target.value;
428
+ setText(inputText);
429
+ if (inputText !== "" && inputText !== "-") {
430
+ const coord = safeParseFloat(inputText);
431
+ if (coord !== void 0 && !isNaN(coord)) {
432
+ const currentLocation = GeoLocation.fromGeoPoint(getValue3() ?? [
433
+ 0,
434
+ 0
435
+ ]);
436
+ const newLocation = {
437
+ ...currentLocation,
438
+ [coordinateType]: coord
439
+ };
440
+ const newValue = GeoLocation.toGeoPoint(newLocation);
441
+ onValueChange(type, newValue);
442
+ }
443
+ }
444
+ }, [
445
+ getValue3,
446
+ onValueChange,
447
+ type
448
+ ]);
449
+ return /* @__PURE__ */ React4.createElement(Input2.Root, {
450
+ validationValence: status
451
+ }, /* @__PURE__ */ React4.createElement(InputHeader, {
452
+ error
453
+ }, /* @__PURE__ */ React4.createElement(Input2.Label, null, label)), /* @__PURE__ */ React4.createElement("div", {
454
+ role: "none",
455
+ className: "grid grid-cols-2 gap-2"
456
+ }, /* @__PURE__ */ React4.createElement(Input2.TextInput, {
457
+ type: "text",
458
+ pattern: "^-?[0-9]*\\.?[0-9]*$",
459
+ disabled,
460
+ placeholder: t("placeholder latitude"),
461
+ value: latitudeText,
462
+ onChange: handleCoordinateChange("latitude", setLatitudeText),
463
+ onBlur
464
+ }), /* @__PURE__ */ React4.createElement(Input2.TextInput, {
465
+ type: "text",
466
+ pattern: "^-?[0-9]*\\.?[0-9]*$",
467
+ disabled,
468
+ placeholder: t("placeholder longitude"),
469
+ value: longitudeText,
470
+ onChange: handleCoordinateChange("longitude", setLongitudeText),
471
+ onBlur
472
+ })));
473
+ };
474
+
475
+ // packages/ui/react-ui-form/src/components/Form/custom/SelectOptionsInput.tsx
476
+ import React5, { useCallback as useCallback3, useEffect as useEffect4, useState as useState3, useRef } from "react";
477
+ import { PublicKey } from "@dxos/keys";
478
+ import { Icon as Icon2, IconButton, Input as Input3, Tag, Toolbar, useTranslation as useTranslation2 } from "@dxos/react-ui";
479
+ import { List } from "@dxos/react-ui-list";
480
+ import { HuePicker } from "@dxos/react-ui-pickers";
481
+ import { hues, mx as mx2 } from "@dxos/react-ui-theme";
482
+ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3, onValueChange }) => {
483
+ const [selected, setSelectedId] = useState3(null);
484
+ const [isNewOption, setIsNewOption] = useState3(false);
485
+ const inputRef = useRef(null);
486
+ const { t } = useTranslation2(translationKey);
487
+ const { status, error } = getStatus();
488
+ const options = getValue3();
489
+ useEffect4(() => {
490
+ if (options === void 0) {
491
+ onValueChange(type, []);
492
+ }
493
+ }, [
494
+ options,
495
+ onValueChange,
496
+ type
497
+ ]);
498
+ const randomHue = () => {
499
+ const usedHues = new Set(options?.map((option) => option.color) ?? []);
500
+ const unusedHues = hues.filter((hue) => !usedHues.has(hue));
501
+ if (unusedHues.length > 0) {
502
+ return unusedHues[Math.floor(Math.random() * unusedHues.length)];
503
+ }
504
+ const lastThree = new Set(options?.slice(-3).map((option) => option.color) ?? []);
505
+ const availableHues = hues.filter((hue) => !lastThree.has(hue));
506
+ return availableHues[Math.floor(Math.random() * availableHues.length)] ?? hues[Math.floor(Math.random() * hues.length)];
507
+ };
508
+ const handleAdd = useCallback3(() => {
509
+ const newId = PublicKey.random().truncate();
510
+ const newOption = {
511
+ id: newId,
512
+ title: "",
513
+ color: randomHue()
514
+ };
515
+ onValueChange(type, [
516
+ ...options ?? [],
517
+ newOption
518
+ ]);
519
+ setSelectedId(newId);
520
+ setIsNewOption(true);
521
+ }, [
522
+ options,
523
+ type,
524
+ onValueChange
525
+ ]);
526
+ const handleDelete = useCallback3((id) => {
527
+ const newOptions = options?.filter((option) => option.id !== id) ?? [];
528
+ onValueChange(type, newOptions);
529
+ setSelectedId(null);
530
+ }, [
531
+ options,
532
+ type,
533
+ onValueChange
534
+ ]);
535
+ const handleMove = useCallback3((from, to) => {
536
+ if (!options) {
537
+ return;
538
+ }
539
+ const newOptions = [
540
+ ...options
541
+ ];
542
+ const [removed] = newOptions.splice(from, 1);
543
+ newOptions.splice(to, 0, removed);
544
+ onValueChange(type, newOptions);
545
+ }, [
546
+ options,
547
+ type,
548
+ onValueChange
549
+ ]);
550
+ const handleClick = useCallback3((id) => {
551
+ setSelectedId((current) => current === id ? null : id);
552
+ }, []);
553
+ const handleTitleChange = useCallback3((id) => (e) => {
554
+ const newOptions = options?.map((o) => o.id === id ? {
555
+ ...o,
556
+ title: e.target.value
557
+ } : o);
558
+ onValueChange(type, newOptions ?? []);
559
+ }, [
560
+ options,
561
+ type,
562
+ onValueChange
563
+ ]);
564
+ const handleKeyDown = useCallback3((e) => {
565
+ if (e.key === "Enter") {
566
+ setSelectedId(null);
567
+ }
568
+ }, []);
569
+ const handleColorChange = useCallback3((id) => (hue) => {
570
+ const newOptions = options?.map((o) => o.id === id ? {
571
+ ...o,
572
+ color: hue
573
+ } : o);
574
+ onValueChange(type, newOptions ?? []);
575
+ }, [
576
+ options,
577
+ type,
578
+ onValueChange
579
+ ]);
580
+ useEffect4(() => {
581
+ if (selected && isNewOption && inputRef.current) {
582
+ inputRef.current.focus();
583
+ setIsNewOption(false);
584
+ }
585
+ }, [
586
+ selected,
587
+ isNewOption
588
+ ]);
589
+ return /* @__PURE__ */ React5.createElement(Input3.Root, {
590
+ validationValence: status
591
+ }, /* @__PURE__ */ React5.createElement(InputHeader, {
592
+ error
593
+ }, /* @__PURE__ */ React5.createElement(Input3.Label, null, label)), /* @__PURE__ */ React5.createElement("div", {
594
+ role: "none"
595
+ }, options && /* @__PURE__ */ React5.createElement(List.Root, {
596
+ items: options,
597
+ isItem: (item) => true,
598
+ onMove: handleMove
599
+ }, ({ items }) => /* @__PURE__ */ React5.createElement("div", {
600
+ role: "list",
601
+ className: "w-full overflow-auto"
602
+ }, items?.map((item) => /* @__PURE__ */ React5.createElement("div", {
603
+ key: item.id
604
+ }, /* @__PURE__ */ React5.createElement(List.Item, {
605
+ role: "button",
606
+ item,
607
+ classNames: mx2("p-1 plb-2", "flex flex-col", "cursor-pointer", "hover:bg-hoverOverlay"),
608
+ "aria-expanded": selected === item.id
609
+ }, /* @__PURE__ */ React5.createElement("div", {
610
+ className: "flex items-center"
611
+ }, /* @__PURE__ */ React5.createElement(List.ItemDragHandle, null), /* @__PURE__ */ React5.createElement(List.ItemTitle, {
612
+ onClick: () => handleClick(item.id),
613
+ classNames: "flex-1"
614
+ }, /* @__PURE__ */ React5.createElement(Tag, {
615
+ palette: item.color
616
+ }, item.title || "\u200B")), /* @__PURE__ */ React5.createElement(Icon2, {
617
+ icon: selected === item.id ? "ph--caret-down--regular" : "ph--caret-right--regular",
618
+ onClick: () => handleClick(item.id)
619
+ })), selected === item.id && /* @__PURE__ */ React5.createElement("div", {
620
+ className: "mis-4 mbs-2 flex flex-col gap-1"
621
+ }, /* @__PURE__ */ React5.createElement(Input3.Label, {
622
+ classNames: "text-xs"
623
+ }, t("select option label")), /* @__PURE__ */ React5.createElement("div", {
624
+ className: "flex flex-row items-center gap-1"
625
+ }, /* @__PURE__ */ React5.createElement(Input3.TextInput, {
626
+ placeholder: t("select option label placeholder"),
627
+ ref: selected === item.id ? inputRef : void 0,
628
+ value: item.title,
629
+ onChange: handleTitleChange(item.id),
630
+ onKeyDown: handleKeyDown,
631
+ classNames: "flex-1",
632
+ "data-no-submit": true
633
+ }), /* @__PURE__ */ React5.createElement(Toolbar.Root, {
634
+ classNames: "p-0 m-0 !is-auto"
635
+ }, /* @__PURE__ */ React5.createElement(HuePicker, {
636
+ value: item.color,
637
+ onChange: handleColorChange(item.id)
638
+ })), /* @__PURE__ */ React5.createElement(IconButton, {
639
+ icon: "ph--trash--fill",
640
+ iconOnly: true,
641
+ label: t("select option delete"),
642
+ onClick: () => handleDelete(item.id)
643
+ })))))), /* @__PURE__ */ React5.createElement("div", {
644
+ role: "listitem"
645
+ }, /* @__PURE__ */ React5.createElement("div", {
646
+ role: "button",
647
+ className: mx2("p-1 plb-2", "flex flex-col", "cursor-pointer", "hover:bg-hoverOverlay"),
648
+ onClick: handleAdd
649
+ }, /* @__PURE__ */ React5.createElement("div", {
650
+ className: "flex items-center gap-1"
651
+ }, /* @__PURE__ */ React5.createElement(Icon2, {
652
+ icon: "ph--plus--regular"
653
+ }), /* @__PURE__ */ React5.createElement("span", {
654
+ className: "text-sm"
655
+ }, t("select option add")))))))));
656
+ };
657
+
658
+ // packages/ui/react-ui-form/src/components/Form/custom/TupleInput.tsx
659
+ import React6 from "react";
660
+ import { Input as Input4 } from "@dxos/react-ui";
661
+ import { mx as mx3 } from "@dxos/react-ui-theme";
662
+ import { safeParseFloat as safeParseFloat2 } from "@dxos/util";
663
+ var gridCols = [
664
+ "grid-cols-1",
665
+ "grid-cols-2",
666
+ "grid-cols-3",
667
+ "grid-cols-4"
668
+ ];
669
+ var TupleInput = ({ binding, type, label, disabled, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
670
+ const { status, error } = getStatus();
671
+ const values = getValue3() ?? {};
672
+ return /* @__PURE__ */ React6.createElement(Input4.Root, {
673
+ validationValence: status
674
+ }, /* @__PURE__ */ React6.createElement(InputHeader, {
675
+ error
676
+ }, /* @__PURE__ */ React6.createElement(Input4.Label, null, label)), /* @__PURE__ */ React6.createElement("div", {
677
+ className: mx3("grid gap-2", gridCols[binding.length - 1])
678
+ }, binding.map((prop) => /* @__PURE__ */ React6.createElement(Input4.TextInput, {
679
+ key: prop,
680
+ type: "number",
681
+ disabled,
682
+ value: values[prop],
683
+ onChange: (event) => onValueChange(type, {
684
+ ...values,
685
+ [prop]: safeParseFloat2(event.target.value, 0)
686
+ }),
687
+ onBlur
688
+ }))));
689
+ };
690
+
691
+ // packages/ui/react-ui-form/src/components/Form/Defaults.tsx
692
+ import React7, { useCallback as useCallback4, useEffect as useEffect5, useRef as useRef2 } from "react";
693
+ import { Input as Input5, Select } from "@dxos/react-ui";
694
+ var TextInput = ({ type, label, inputOnly, disabled, placeholder, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
695
+ const { status, error } = getStatus();
696
+ return /* @__PURE__ */ React7.createElement(Input5.Root, {
697
+ validationValence: status
698
+ }, !inputOnly && /* @__PURE__ */ React7.createElement(InputHeader, {
699
+ error
700
+ }, /* @__PURE__ */ React7.createElement(Input5.Label, null, label)), /* @__PURE__ */ React7.createElement(Input5.TextInput, {
701
+ disabled,
702
+ placeholder,
703
+ value: getValue3() ?? "",
704
+ onChange: (event) => onValueChange(type, event.target.value),
705
+ onBlur
706
+ }), inputOnly && /* @__PURE__ */ React7.createElement(Input5.Validation, null, error));
707
+ };
708
+ var NumberInput = ({ type, label, inputOnly, disabled, placeholder, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
709
+ const { status, error } = getStatus();
710
+ return /* @__PURE__ */ React7.createElement(Input5.Root, {
711
+ validationValence: status
712
+ }, !inputOnly && /* @__PURE__ */ React7.createElement(InputHeader, {
713
+ error
714
+ }, /* @__PURE__ */ React7.createElement(Input5.Label, null, label)), /* @__PURE__ */ React7.createElement(Input5.TextInput, {
715
+ type: "number",
716
+ disabled,
717
+ placeholder,
718
+ value: getValue3() ?? 0,
719
+ onChange: (event) => onValueChange(type, event.target.value),
720
+ onBlur
721
+ }), inputOnly && /* @__PURE__ */ React7.createElement(Input5.DescriptionAndValidation, null, error));
722
+ };
723
+ var BooleanInput = ({ type, label, inputOnly, getStatus, getValue: getValue3, onValueChange }) => {
724
+ const { status, error } = getStatus();
725
+ const checked = Boolean(getValue3());
726
+ return /* @__PURE__ */ React7.createElement(Input5.Root, {
727
+ validationValence: status
728
+ }, !inputOnly && /* @__PURE__ */ React7.createElement(InputHeader, {
729
+ error
730
+ }, /* @__PURE__ */ React7.createElement(Input5.Label, null, label)), /* @__PURE__ */ React7.createElement(Input5.Switch, {
731
+ checked,
732
+ onCheckedChange: (value) => onValueChange(type, value)
733
+ }), inputOnly && /* @__PURE__ */ React7.createElement(Input5.DescriptionAndValidation, null, error));
734
+ };
735
+ var SelectInput = ({ type, label, inputOnly, disabled, placeholder, options, getStatus, getValue: getValue3, onValueChange }) => {
736
+ const { status, error } = getStatus();
737
+ return /* @__PURE__ */ React7.createElement(Input5.Root, {
738
+ validationValence: status
739
+ }, !inputOnly && /* @__PURE__ */ React7.createElement(InputHeader, {
740
+ error
741
+ }, /* @__PURE__ */ React7.createElement(Input5.Label, null, label)), /* @__PURE__ */ React7.createElement(Select.Root, {
742
+ value: getValue3(),
743
+ onValueChange: (value) => onValueChange(type, value)
744
+ }, /* @__PURE__ */ React7.createElement(Select.TriggerButton, {
745
+ classNames: "is-full",
746
+ disabled,
747
+ placeholder
748
+ }), /* @__PURE__ */ React7.createElement(Select.Portal, null, /* @__PURE__ */ React7.createElement(Select.Content, null, /* @__PURE__ */ React7.createElement(Select.Viewport, null, options?.map(({ value, label: label2 }) => (
749
+ // NOTE: Numeric values are converted to and from strings.
750
+ /* @__PURE__ */ React7.createElement(Select.Option, {
751
+ key: String(value),
752
+ value: String(value)
753
+ }, label2 ?? String(value))
754
+ )))))), inputOnly && /* @__PURE__ */ React7.createElement(Input5.DescriptionAndValidation, null, error));
755
+ };
756
+ var MarkdownInput = ({ type, label, inputOnly, disabled, placeholder, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
757
+ const { status, error } = getStatus();
758
+ const textareaRef = useRef2(null);
759
+ const adjustHeight = useCallback4(() => {
760
+ const textarea = textareaRef.current;
761
+ if (textarea) {
762
+ textarea.style.blockSize = "auto";
763
+ textarea.style.blockSize = `${textarea.scrollHeight + 2}px`;
430
764
  }
431
- return result;
432
- }, {});
765
+ }, []);
766
+ useEffect5(() => {
767
+ adjustHeight();
768
+ }, [
769
+ adjustHeight
770
+ ]);
771
+ useEffect5(() => {
772
+ adjustHeight();
773
+ }, [
774
+ getValue3(),
775
+ adjustHeight
776
+ ]);
777
+ return /* @__PURE__ */ React7.createElement(Input5.Root, {
778
+ validationValence: status
779
+ }, !inputOnly && /* @__PURE__ */ React7.createElement(InputHeader, {
780
+ error
781
+ }, /* @__PURE__ */ React7.createElement(Input5.Label, null, label)), /* @__PURE__ */ React7.createElement(Input5.TextArea, {
782
+ ref: textareaRef,
783
+ disabled,
784
+ placeholder,
785
+ value: getValue3() ?? "",
786
+ classNames: "min-bs-auto max-h-40 overflow-auto",
787
+ onChange: (event) => onValueChange(type, event.target.value),
788
+ onBlur,
789
+ style: {
790
+ resize: "none"
791
+ },
792
+ spellCheck: false
793
+ }), inputOnly && /* @__PURE__ */ React7.createElement(Input5.Validation, null, error));
433
794
  };
434
795
 
435
796
  // packages/ui/react-ui-form/src/components/Form/Form.tsx
436
- var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-form/src/components/Form/Form.tsx";
797
+ import React11, { useEffect as useEffect6, useMemo as useMemo6, useRef as useRef3 } from "react";
798
+ import { mx as mx7 } from "@dxos/react-ui-theme";
799
+
800
+ // packages/ui/react-ui-form/src/components/Form/FormActions.tsx
801
+ import React8 from "react";
802
+ import { IconButton as IconButton2, useTranslation as useTranslation3 } from "@dxos/react-ui";
803
+ import { mx as mx4 } from "@dxos/react-ui-theme";
804
+ var FormActions = ({ onCancel, readonly }) => {
805
+ const { t } = useTranslation3(translationKey);
806
+ const { canSave, handleSave } = useFormContext();
807
+ return /* @__PURE__ */ React8.createElement("div", {
808
+ role: "none",
809
+ className: "mt-2 flex justify-center"
810
+ }, /* @__PURE__ */ React8.createElement("div", {
811
+ role: "none",
812
+ className: mx4(onCancel && !readonly && "grid grid-cols-2 gap-2")
813
+ }, onCancel && !readonly && /* @__PURE__ */ React8.createElement(IconButton2, {
814
+ "data-testid": "cancel-button",
815
+ icon: "ph--x--regular",
816
+ label: t("button cancel"),
817
+ onClick: onCancel
818
+ }), handleSave && /* @__PURE__ */ React8.createElement(IconButton2, {
819
+ type: "submit",
820
+ "data-testid": "save-button",
821
+ disabled: !canSave,
822
+ icon: "ph--check--regular",
823
+ label: t("button save"),
824
+ onClick: handleSave
825
+ })));
826
+ };
827
+
828
+ // packages/ui/react-ui-form/src/components/Form/FormContent.tsx
829
+ import { pipe as pipe2 } from "effect";
830
+ import { capitalize as capitalize2 } from "effect/String";
831
+ import React10, { useMemo as useMemo5 } from "react";
832
+ import { AST as AST3, S } from "@dxos/echo-schema";
833
+ import { createJsonPath as createJsonPath3, findNode as findNode2, getDiscriminatedType as getDiscriminatedType2, isDiscriminatedUnion as isDiscriminatedUnion2 } from "@dxos/effect";
834
+ import { mx as mx6 } from "@dxos/react-ui-theme";
835
+ import { getSchemaProperties as getSchemaProperties2 } from "@dxos/schema";
836
+ import { isNotFalsy } from "@dxos/util";
837
+
838
+ // packages/ui/react-ui-form/src/components/Form/ArrayField.tsx
839
+ import { pipe } from "effect";
840
+ import { capitalize } from "effect/String";
841
+ import React9, { useCallback as useCallback5, useMemo as useMemo4 } from "react";
842
+ import { AST as AST2 } from "@dxos/echo-schema";
843
+ import { findNode, getDiscriminatedType, isDiscriminatedUnion, SimpleType } from "@dxos/effect";
844
+ import { invariant as invariant2 } from "@dxos/invariant";
845
+ import { IconButton as IconButton3, useTranslation as useTranslation4 } from "@dxos/react-ui";
846
+ import { mx as mx5 } from "@dxos/react-ui-theme";
847
+ import { getSchemaProperties } from "@dxos/schema";
848
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-form/src/components/Form/ArrayField.tsx";
437
849
  var padding = "px-2";
438
- var Form = ({ classNames, schema, values: initialValues, path = [], readonly, filter, sort, autoSave, testId, onValuesChanged, onValidate, onSave, onCancel, lookupComponent, Custom }) => {
439
- const onValid = useMemo2(() => autoSave ? onSave : void 0, [
440
- autoSave,
441
- onSave
442
- ]);
443
- const { canSave, values, errors, handleSave, ...inputProps } = useForm({
444
- schema,
445
- initialValues,
446
- onValuesChanged,
447
- onValidate,
448
- onValid,
449
- onSave
850
+ var ArrayField = ({ property, readonly, path, inputProps, Custom, lookupComponent }) => {
851
+ const { t } = useTranslation4(translationKey);
852
+ const { ast, name, type, title } = property;
853
+ const values = useFormValues(path ?? []);
854
+ invariant2(Array.isArray(values), `Values at path ${path?.join(".")} must be an array.`, {
855
+ F: __dxlog_file2,
856
+ L: 37,
857
+ S: void 0,
858
+ A: [
859
+ "Array.isArray(values)",
860
+ "`Values at path ${path?.join('.')} must be an array.`"
861
+ ]
450
862
  });
451
- const properties = useMemo2(() => {
452
- const props = getSchemaProperties(schema.ast, values);
453
- const filtered = filter ? filter(props) : props;
454
- const findIndex = (props2, prop) => {
455
- const idx = props2.findIndex((p) => p === prop);
456
- return idx === -1 ? Infinity : idx;
457
- };
458
- return sort ? filtered.sort(({ name: a }, { name: b }) => findIndex(sort, a) - findIndex(sort, b)) : filtered;
863
+ const label = title ?? pipe(name, capitalize);
864
+ const tupleType = findNode(ast, AST2.isTupleType);
865
+ const elementType = tupleType?.rest[0]?.type;
866
+ const getDefaultObjectValue = (typeNode) => {
867
+ const baseNode = findNode(typeNode, isDiscriminatedUnion);
868
+ const typeLiteral = baseNode ? getDiscriminatedType(baseNode, {}) : findNode(typeNode, AST2.isTypeLiteral);
869
+ if (!typeLiteral) {
870
+ return {};
871
+ }
872
+ return Object.fromEntries(getSchemaProperties(typeLiteral, {}).map((prop) => [
873
+ prop.name,
874
+ prop.defaultValue
875
+ ]));
876
+ };
877
+ const newValue = useMemo4(() => {
878
+ if (type === "object" && elementType) {
879
+ return getDefaultObjectValue(elementType);
880
+ } else {
881
+ return SimpleType.getDefaultValue(type);
882
+ }
459
883
  }, [
460
- schema,
461
- filter,
884
+ type,
885
+ elementType
886
+ ]);
887
+ const handleAdd = useCallback5(() => {
888
+ inputProps.onValueChange(type, [
889
+ ...values,
890
+ newValue
891
+ ]);
892
+ }, [
893
+ type,
894
+ elementType,
895
+ inputProps,
896
+ newValue,
462
897
  values
463
898
  ]);
464
- useEffect2(() => {
465
- if (errors && Object.keys(errors).length) {
466
- log2.warn("validation", {
467
- errors
468
- }, {
469
- F: __dxlog_file2,
470
- L: 106,
471
- S: void 0,
472
- C: (f, a) => f(...a)
473
- });
474
- }
899
+ const handleRemove = useCallback5((index) => {
900
+ const newValues = values.filter((_, i) => i !== index);
901
+ inputProps.onValueChange(type, newValues);
475
902
  }, [
476
- errors
903
+ type,
904
+ inputProps,
905
+ values
477
906
  ]);
478
- return /* @__PURE__ */ React6.createElement("div", {
479
- role: "form",
480
- className: mx2("flex flex-col w-full gap-2 py-2", classNames),
481
- "data-testid": testId
482
- }, properties.map((property) => {
483
- const { ast, name, type, format, title, description, examples, options, array } = property;
484
- const key = [
485
- ...path,
486
- name
487
- ];
488
- const label = title ?? pipe(name, capitalize);
489
- const placeholder = examples?.length ? `Example: "${examples[0]}"` : description;
490
- const FoundComponent = lookupComponent?.({
491
- prop: name,
492
- schema: S.make(ast),
493
- inputProps: {
494
- property: name,
495
- type,
496
- format,
497
- label,
498
- disabled: readonly,
499
- placeholder,
500
- ...inputProps
501
- }
502
- });
503
- if (FoundComponent) {
504
- return /* @__PURE__ */ React6.createElement("div", {
505
- key: name,
506
- role: "none",
507
- className: padding
508
- }, FoundComponent);
509
- }
510
- let InputComponent = Custom?.[key.join(".")];
511
- if (!InputComponent) {
512
- if (options) {
513
- return /* @__PURE__ */ React6.createElement("div", {
514
- key: name,
515
- role: "none",
516
- className: padding
517
- }, /* @__PURE__ */ React6.createElement(SelectInput, {
518
- type,
519
- format,
520
- property: name,
521
- disabled: readonly,
522
- label,
523
- options: options.map((option) => ({
524
- value: option,
525
- label: String(option)
526
- })),
527
- placeholder,
528
- ...inputProps
529
- }));
530
- }
531
- InputComponent = getInputComponent(type, format);
532
- if (array) {
533
- return /* @__PURE__ */ React6.createElement(ArrayField, {
534
- key: name,
535
- name,
536
- type,
537
- array: true,
538
- label,
539
- ast,
540
- values,
541
- format,
542
- readonly,
543
- placeholder,
544
- inputProps,
545
- Custom
546
- });
547
- }
548
- }
549
- if (!InputComponent) {
550
- if (type === "object") {
551
- const baseNode = findNode(ast, isDiscriminatedUnion);
552
- const typeLiteral = baseNode ? getDiscriminatedType(baseNode, values[name]) : findNode(ast, AST.isTypeLiteral);
553
- if (typeLiteral) {
554
- const schema2 = S.make(typeLiteral);
555
- return /* @__PURE__ */ React6.createElement("div", {
556
- key: name,
557
- role: "none"
558
- }, /* @__PURE__ */ React6.createElement("div", {
559
- className: padding
560
- }, label), /* @__PURE__ */ React6.createElement(Form, {
561
- schema: schema2,
562
- path: key,
563
- values: values[name] ?? {},
564
- onValuesChanged: (childValues) => {
565
- inputProps.onValueChange(name, "object", childValues);
566
- },
567
- Custom
568
- }));
569
- }
570
- }
571
- log2("no renderer for property", {
572
- name,
573
- type
574
- }, {
575
- F: __dxlog_file2,
576
- L: 213,
577
- S: void 0,
578
- C: (f, a) => f(...a)
579
- });
580
- return null;
581
- }
582
- return /* @__PURE__ */ React6.createElement("div", {
583
- key: name,
584
- role: "none",
585
- className: padding
586
- }, /* @__PURE__ */ React6.createElement(InputComponent, {
587
- type,
588
- format,
589
- property: name,
590
- disabled: readonly,
591
- label,
592
- placeholder,
593
- ...inputProps
594
- }));
595
- }).filter(isNotFalsy), (onCancel || onSave) && !autoSave && /* @__PURE__ */ React6.createElement(FormActions, {
596
- onCancel,
597
- onSubmit: handleSave,
598
- canSubmit: canSave,
599
- readonly
600
- }));
601
- };
602
- var ArrayField = ({ name, type, label, ast, values, format, readonly, placeholder, inputProps, Custom }) => {
603
- const { t } = useTranslation2(translationKey);
604
- const arrayValues = values[name] ?? [];
605
- const key = [
606
- name
607
- ];
608
- const tupleType = findNode(ast, AST.isTupleType);
609
- const elementType = tupleType?.rest[0]?.type;
610
- if (type === "object" && elementType) {
611
- const baseNode = findNode(elementType, isDiscriminatedUnion);
612
- const typeLiteral = baseNode ? getDiscriminatedType(baseNode, values[name]) : findNode(elementType, AST.isTypeLiteral);
613
- if (typeLiteral) {
614
- const schema = S.make(typeLiteral);
615
- return /* @__PURE__ */ React6.createElement("div", {
616
- key: name,
617
- role: "none",
618
- className: mx2(padding)
619
- }, /* @__PURE__ */ React6.createElement(InputHeader, null, label), /* @__PURE__ */ React6.createElement("div", {
620
- role: "none",
621
- className: "flex flex-col gap-1"
622
- }, arrayValues.map((value, index) => {
623
- return /* @__PURE__ */ React6.createElement("div", {
624
- key: index,
625
- role: "none",
626
- className: "flex items-center gap-1"
627
- }, /* @__PURE__ */ React6.createElement("div", {
628
- role: "none",
629
- className: "flex-1"
630
- }, /* @__PURE__ */ React6.createElement(Form, {
631
- schema,
632
- path: [
633
- ...key,
634
- `[${index}]`
635
- ],
636
- values: value,
637
- onValuesChanged: (childValues) => {
638
- const newValues = [
639
- ...arrayValues
640
- ];
641
- newValues[index] = childValues;
642
- inputProps.onValueChange(name, "object", newValues);
643
- },
644
- Custom
645
- })), /* @__PURE__ */ React6.createElement(IconButton, {
646
- icon: "ph--trash--regular",
647
- iconOnly: true,
648
- label: t("button remove"),
649
- onClick: () => {
650
- const newValues = arrayValues.filter((_, i) => i !== index);
651
- inputProps.onValueChange(name, type, newValues);
652
- }
653
- }));
654
- })), /* @__PURE__ */ React6.createElement("div", {
655
- role: "none",
656
- className: "flex justify-between items-center plb-1"
657
- }, /* @__PURE__ */ React6.createElement(IconButton, {
658
- icon: "ph--plus--regular",
659
- iconOnly: true,
660
- label: t("button add"),
661
- onClick: () => {
662
- const newValues = [
663
- ...arrayValues,
664
- {}
665
- ];
666
- inputProps.onValueChange(name, type, newValues);
667
- }
668
- })));
669
- }
670
- return /* @__PURE__ */ React6.createElement("div", {
671
- key: name,
672
- role: "none"
673
- }, "Nested form not supported in arrays, yet.");
674
- }
675
- const InputComponent = getInputComponent(type, format);
676
- if (!InputComponent) {
907
+ if (!elementType) {
677
908
  return null;
678
909
  }
679
- return /* @__PURE__ */ React6.createElement("div", {
910
+ return /* @__PURE__ */ React9.createElement("div", {
680
911
  role: "none",
681
- key: "meta-input",
682
- className: mx2(padding)
683
- }, /* @__PURE__ */ React6.createElement(InputHeader, null, label), /* @__PURE__ */ React6.createElement("div", {
912
+ className: mx5(padding)
913
+ }, /* @__PURE__ */ React9.createElement(InputHeader, null, label), /* @__PURE__ */ React9.createElement("div", {
684
914
  role: "none",
685
915
  className: "flex flex-col gap-1"
686
- }, arrayValues.map((_, index) => /* @__PURE__ */ React6.createElement("div", {
916
+ }, values.map((_value, index) => /* @__PURE__ */ React9.createElement("div", {
687
917
  key: index,
688
918
  role: "none",
689
- className: "flex items-start gap-1"
690
- }, /* @__PURE__ */ React6.createElement("div", {
919
+ className: "flex items-center gap-1"
920
+ }, /* @__PURE__ */ React9.createElement("div", {
691
921
  role: "none",
692
922
  className: "flex-1"
693
- }, /* @__PURE__ */ React6.createElement(InputComponent, {
694
- type,
695
- format,
696
- label,
697
- inputOnly: true,
698
- property: `${name}[${index}]`,
699
- disabled: readonly,
700
- placeholder,
701
- ...inputProps
702
- })), /* @__PURE__ */ React6.createElement(IconButton, {
923
+ }, /* @__PURE__ */ React9.createElement(FormField, {
924
+ property: {
925
+ ...property,
926
+ array: false,
927
+ ast: elementType
928
+ },
929
+ path: [
930
+ ...path ?? [],
931
+ index
932
+ ],
933
+ readonly,
934
+ inline: true,
935
+ Custom,
936
+ lookupComponent
937
+ })), /* @__PURE__ */ React9.createElement(IconButton3, {
703
938
  icon: "ph--trash--regular",
704
939
  iconOnly: true,
705
940
  label: t("button remove"),
706
- onClick: () => {
707
- const newValues = arrayValues.filter((_2, i) => i !== index);
708
- inputProps.onValueChange(name, type, newValues);
709
- }
710
- })))), /* @__PURE__ */ React6.createElement("div", {
941
+ onClick: () => handleRemove(index)
942
+ })))), /* @__PURE__ */ React9.createElement("div", {
711
943
  role: "none",
712
944
  className: "flex justify-between items-center plb-1"
713
- }, /* @__PURE__ */ React6.createElement(IconButton, {
945
+ }, /* @__PURE__ */ React9.createElement(IconButton3, {
714
946
  icon: "ph--plus--regular",
715
947
  iconOnly: true,
716
948
  label: t("button add"),
717
- onClick: () => {
718
- const newValues = [
719
- ...arrayValues,
720
- SimpleType.getDefaultValue(type)
721
- ];
722
- inputProps.onValueChange(name, type, newValues);
723
- }
949
+ onClick: handleAdd
724
950
  })));
725
951
  };
726
- var FormActions = ({ onCancel, onSubmit, canSubmit, readonly }) => {
727
- const { t } = useTranslation2(translationKey);
728
- return /* @__PURE__ */ React6.createElement("div", {
729
- role: "none",
730
- className: "flex justify-center"
731
- }, /* @__PURE__ */ React6.createElement("div", {
952
+
953
+ // packages/ui/react-ui-form/src/components/Form/factory.tsx
954
+ import { FormatEnum } from "@dxos/echo-schema";
955
+ var getInputComponent = (type, format) => {
956
+ switch (format) {
957
+ case FormatEnum.GeoPoint:
958
+ return GeoPointInput;
959
+ case FormatEnum.Markdown:
960
+ return MarkdownInput;
961
+ }
962
+ switch (type) {
963
+ case "string":
964
+ return TextInput;
965
+ case "number":
966
+ return NumberInput;
967
+ case "boolean":
968
+ return BooleanInput;
969
+ }
970
+ };
971
+
972
+ // packages/ui/react-ui-form/src/components/Form/FormContent.tsx
973
+ var FormField = ({ property, path, readonly, inline, lookupComponent, Custom }) => {
974
+ const inputProps = useInputProps(path);
975
+ const { ast, name, type, format, title, description, options, examples, array } = property;
976
+ const label = useMemo5(() => title ?? pipe2(name, capitalize2), [
977
+ title,
978
+ name
979
+ ]);
980
+ const placeholder = useMemo5(() => examples?.length ? `Example: "${examples[0]}"` : description, [
981
+ examples,
982
+ description
983
+ ]);
984
+ const FoundComponent = lookupComponent?.({
985
+ prop: name,
986
+ schema: S.make(ast),
987
+ inputProps: {
988
+ type,
989
+ format,
990
+ label,
991
+ disabled: readonly,
992
+ placeholder,
993
+ ...inputProps
994
+ }
995
+ });
996
+ if (FoundComponent) {
997
+ return /* @__PURE__ */ React10.createElement("div", {
998
+ role: "none"
999
+ }, FoundComponent);
1000
+ }
1001
+ const jsonPath = createJsonPath3(path ?? []);
1002
+ const CustomComponent = Custom?.[jsonPath];
1003
+ if (CustomComponent) {
1004
+ return /* @__PURE__ */ React10.createElement(CustomComponent, {
1005
+ type,
1006
+ format,
1007
+ label,
1008
+ inputOnly: inline,
1009
+ placeholder,
1010
+ disabled: readonly,
1011
+ ...inputProps
1012
+ });
1013
+ }
1014
+ if (array) {
1015
+ return /* @__PURE__ */ React10.createElement(ArrayField, {
1016
+ property,
1017
+ path,
1018
+ inputProps,
1019
+ readonly,
1020
+ Custom
1021
+ });
1022
+ }
1023
+ const InputComponent = getInputComponent(type, format);
1024
+ if (InputComponent) {
1025
+ return /* @__PURE__ */ React10.createElement("div", {
1026
+ role: "none"
1027
+ }, /* @__PURE__ */ React10.createElement(InputComponent, {
1028
+ type,
1029
+ format,
1030
+ label,
1031
+ inputOnly: inline,
1032
+ placeholder,
1033
+ disabled: readonly,
1034
+ ...inputProps
1035
+ }));
1036
+ }
1037
+ if (options) {
1038
+ return /* @__PURE__ */ React10.createElement("div", {
1039
+ role: "none"
1040
+ }, /* @__PURE__ */ React10.createElement(SelectInput, {
1041
+ type,
1042
+ format,
1043
+ disabled: readonly,
1044
+ inputOnly: inline,
1045
+ label,
1046
+ options: options.map((option) => ({
1047
+ value: option,
1048
+ label: String(option)
1049
+ })),
1050
+ placeholder,
1051
+ ...inputProps
1052
+ }));
1053
+ }
1054
+ if (type === "object") {
1055
+ const baseNode = findNode2(ast, isDiscriminatedUnion2);
1056
+ const typeLiteral = baseNode ? getDiscriminatedType2(baseNode, inputProps.getValue()) : findNode2(ast, AST3.isTypeLiteral);
1057
+ if (typeLiteral) {
1058
+ return /* @__PURE__ */ React10.createElement("div", {
1059
+ role: "none"
1060
+ }, !inline && /* @__PURE__ */ React10.createElement("div", null, label), /* @__PURE__ */ React10.createElement(FormFields, {
1061
+ schema: S.make(typeLiteral),
1062
+ path,
1063
+ readonly,
1064
+ Custom
1065
+ }));
1066
+ }
1067
+ }
1068
+ return null;
1069
+ };
1070
+ var FormFields = ({ schema, path, filter, sort, readonly, lookupComponent, Custom }) => {
1071
+ const values = useFormValues(path);
1072
+ const properties = useMemo5(() => {
1073
+ const props = getSchemaProperties2(schema.ast, values);
1074
+ const filtered = filter ? filter(props) : props;
1075
+ return sort ? filtered.sort((a, b) => sort.indexOf(a.name) - sort.indexOf(b.name)) : filtered;
1076
+ }, [
1077
+ schema,
1078
+ values,
1079
+ filter,
1080
+ sort
1081
+ ]);
1082
+ return /* @__PURE__ */ React10.createElement("div", {
1083
+ role: "form",
1084
+ className: mx6("flex flex-col w-full gap-2")
1085
+ }, properties.map((property) => {
1086
+ return /* @__PURE__ */ React10.createElement(FormField, {
1087
+ key: property.name,
1088
+ property,
1089
+ path: [
1090
+ ...path ?? [],
1091
+ property.name
1092
+ ],
1093
+ readonly,
1094
+ lookupComponent,
1095
+ Custom
1096
+ });
1097
+ }).filter(isNotFalsy));
1098
+ };
1099
+
1100
+ // packages/ui/react-ui-form/src/components/Form/Form.tsx
1101
+ var Form = ({ classNames, schema, values: initialValues, path = [], autoFocus, readonly, filter, sort, autoSave, testId, onValuesChanged, onValidate, onSave, onCancel, lookupComponent, Custom }) => {
1102
+ const formRef = useRef3(null);
1103
+ const onValid = useMemo6(() => autoSave ? onSave : void 0, [
1104
+ autoSave,
1105
+ onSave
1106
+ ]);
1107
+ useEffect6(() => {
1108
+ if (autoFocus && formRef.current) {
1109
+ const input = formRef.current.querySelector("input");
1110
+ if (input) {
1111
+ input.focus();
1112
+ }
1113
+ }
1114
+ }, [
1115
+ autoFocus
1116
+ ]);
1117
+ return /* @__PURE__ */ React11.createElement(FormProvider, {
1118
+ formRef,
1119
+ schema,
1120
+ autoSave,
1121
+ initialValues,
1122
+ onValuesChanged,
1123
+ onValidate,
1124
+ onValid,
1125
+ onSave
1126
+ }, /* @__PURE__ */ React11.createElement("div", {
1127
+ ref: formRef,
732
1128
  role: "none",
733
- className: mx2(onCancel && !readonly && "grid grid-cols-2 gap-2")
734
- }, onCancel && !readonly && /* @__PURE__ */ React6.createElement(IconButton, {
735
- "data-testid": "cancel-button",
736
- icon: "ph--x--regular",
737
- label: t("button cancel"),
738
- onClick: onCancel
739
- }), onSubmit && /* @__PURE__ */ React6.createElement(IconButton, {
740
- type: "submit",
741
- "data-testid": "save-button",
742
- disabled: !canSubmit,
743
- icon: "ph--check--regular",
744
- label: t("button save"),
745
- onClick: onSubmit
1129
+ className: mx7("p-2", classNames),
1130
+ "data-testid": testId
1131
+ }, /* @__PURE__ */ React11.createElement(FormFields, {
1132
+ schema,
1133
+ path,
1134
+ readonly,
1135
+ filter,
1136
+ sort,
1137
+ lookupComponent,
1138
+ Custom
1139
+ }), (onCancel || onSave) && !autoSave && /* @__PURE__ */ React11.createElement(FormActions, {
1140
+ onCancel,
1141
+ readonly
746
1142
  })));
747
1143
  };
748
1144
 
749
1145
  // packages/ui/react-ui-form/src/components/FieldEditor/FieldEditor.tsx
750
1146
  var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-form/src/components/FieldEditor/FieldEditor.tsx";
751
1147
  var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
752
- const { t } = useTranslation3(translationKey);
753
- const [props, setProps] = useState2(projection.getFieldProjection(field.id).props);
754
- useEffect3(() => setProps(projection.getFieldProjection(field.id).props), [
1148
+ const { t } = useTranslation5(translationKey);
1149
+ const [props, setProps] = useState4(projection.getFieldProjection(field.id).props);
1150
+ useEffect7(() => setProps(projection.getFieldProjection(field.id).props), [
755
1151
  field,
756
1152
  projection
757
1153
  ]);
758
- const [schemas, setSchemas] = useState2([]);
759
- useEffect3(() => {
1154
+ const [schemas, setSchemas] = useState4([]);
1155
+ useEffect7(() => {
760
1156
  if (!registry) {
761
1157
  return;
762
1158
  }
@@ -774,17 +1170,17 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
774
1170
  }, [
775
1171
  registry
776
1172
  ]);
777
- const [referenceSchema, setReferenceSchema] = useState2();
778
- useEffect3(() => {
1173
+ const [referenceSchema, setReferenceSchema] = useState4();
1174
+ useEffect7(() => {
779
1175
  setReferenceSchema(schemas.find((schema) => schema.typename === props?.referenceSchema));
780
1176
  }, [
781
1177
  schemas,
782
1178
  props?.referenceSchema
783
1179
  ]);
784
- const [{ fieldSchema }, setFieldSchema] = useState2({
1180
+ const [{ fieldSchema }, setFieldSchema] = useState4({
785
1181
  fieldSchema: getFormatSchema(props?.format)
786
1182
  });
787
- const handleValuesChanged = useCallback2((_props) => {
1183
+ const handleValuesChanged = useCallback6((_props) => {
788
1184
  setFieldSchema((prev) => {
789
1185
  const fieldSchema2 = getFormatSchema(_props.format);
790
1186
  if (prev.fieldSchema === fieldSchema2) {
@@ -812,14 +1208,14 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
812
1208
  type
813
1209
  };
814
1210
  }
815
- return props2;
1211
+ return _props;
816
1212
  });
817
1213
  }, [
818
1214
  schemas,
819
1215
  props.format,
820
1216
  props.referenceSchema
821
1217
  ]);
822
- const handleValidate = useCallback2(({ property }) => {
1218
+ const handleValidate = useCallback6(({ property }) => {
823
1219
  if (property && view.fields.find((f) => f.path === property && f.path !== field.path)) {
824
1220
  return [
825
1221
  {
@@ -832,7 +1228,7 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
832
1228
  view.fields,
833
1229
  field
834
1230
  ]);
835
- const handleSave = useCallback2((props2) => {
1231
+ const handleSave = useCallback6((props2) => {
836
1232
  projection.setFieldProjection({
837
1233
  field,
838
1234
  props: props2
@@ -843,50 +1239,51 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
843
1239
  field,
844
1240
  onSave
845
1241
  ]);
846
- const handleCancel = useCallback2(() => {
1242
+ const handleCancel = useCallback6(() => {
847
1243
  requestAnimationFrame(() => onCancel?.());
848
1244
  onSave();
849
1245
  }, [
850
1246
  onSave
851
1247
  ]);
852
- const Custom = useMemo3(() => ({
853
- ["format"]: (props2) => /* @__PURE__ */ React7.createElement(SelectInput, {
1248
+ const Custom = useMemo7(() => ({
1249
+ ["format"]: (props2) => /* @__PURE__ */ React12.createElement(SelectInput, {
854
1250
  ...props2,
855
1251
  options: FormatEnums.filter((value) => value !== FormatEnum2.None).map((value) => ({
856
1252
  value,
857
1253
  label: t(`format ${value}`)
858
1254
  }))
859
1255
  }),
860
- ["referenceSchema"]: (props2) => /* @__PURE__ */ React7.createElement(SelectInput, {
1256
+ ["referenceSchema"]: (props2) => /* @__PURE__ */ React12.createElement(SelectInput, {
861
1257
  ...props2,
862
1258
  options: schemas.map((schema) => ({
863
1259
  value: schema.typename
864
1260
  }))
865
1261
  }),
866
- ["referencePath"]: (props2) => /* @__PURE__ */ React7.createElement(SelectInput, {
1262
+ ["referencePath"]: (props2) => /* @__PURE__ */ React12.createElement(SelectInput, {
867
1263
  ...props2,
868
- options: referenceSchema ? getSchemaProperties2(referenceSchema.ast).sort(sortProperties).map((p) => ({
1264
+ options: referenceSchema ? getSchemaProperties3(referenceSchema.ast).sort(sortProperties).map((p) => ({
869
1265
  value: p.name
870
1266
  })) : []
871
- })
1267
+ }),
1268
+ ["options"]: (props2) => /* @__PURE__ */ React12.createElement(SelectOptionInput, props2)
872
1269
  }), [
873
1270
  t,
874
1271
  schemas,
875
1272
  referenceSchema
876
1273
  ]);
877
- const propIsNotType = useCallback2((props2) => props2.filter((p) => p.name !== "type"), []);
1274
+ const propIsNotType = useCallback6((props2) => props2.filter((p) => p.name !== "type"), []);
878
1275
  if (!fieldSchema) {
879
- log3.warn("invalid format", {
1276
+ log2.warn("invalid format", {
880
1277
  props
881
1278
  }, {
882
1279
  F: __dxlog_file3,
883
- L: 173,
1280
+ L: 174,
884
1281
  S: void 0,
885
1282
  C: (f, a) => f(...a)
886
1283
  });
887
1284
  return null;
888
1285
  }
889
- return /* @__PURE__ */ React7.createElement(Form, {
1286
+ return /* @__PURE__ */ React12.createElement(Form, {
890
1287
  key: field.id,
891
1288
  autoFocus: true,
892
1289
  values: props,
@@ -905,30 +1302,29 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
905
1302
  };
906
1303
 
907
1304
  // packages/ui/react-ui-form/src/components/ViewEditor/ViewEditor.tsx
908
- import React8, { useCallback as useCallback3, useMemo as useMemo4, useState as useState3 } from "react";
909
- import { AST as AST2, Format, S as S2 } from "@dxos/echo-schema";
910
- import { IconButton as IconButton2, useTranslation as useTranslation4 } from "@dxos/react-ui";
911
- import { List } from "@dxos/react-ui-list";
912
- import { ghostHover, inputTextLabel, mx as mx3 } from "@dxos/react-ui-theme";
1305
+ import React13, { useCallback as useCallback7, useMemo as useMemo8, useState as useState5 } from "react";
1306
+ import { AST as AST4, Format, S as S2 } from "@dxos/echo-schema";
1307
+ import { IconButton as IconButton4, useTranslation as useTranslation6 } from "@dxos/react-ui";
1308
+ import { List as List2 } from "@dxos/react-ui-list";
1309
+ import { ghostHover, inputTextLabel, mx as mx8 } from "@dxos/react-ui-theme";
913
1310
  import { FieldSchema, ViewProjection, VIEW_FIELD_LIMIT } from "@dxos/schema";
914
- import { arrayMove } from "@dxos/util";
915
1311
  var grid = "grid grid-cols-[32px_1fr_32px] min-bs-[2.5rem]";
916
1312
  var ViewMetaSchema = S2.Struct({
917
1313
  name: S2.String.annotations({
918
- [AST2.TitleAnnotationId]: "View"
1314
+ [AST4.TitleAnnotationId]: "View"
919
1315
  }),
920
- typename: Format.DXN.annotations({
921
- [AST2.TitleAnnotationId]: "Typename"
1316
+ typename: Format.URL.annotations({
1317
+ [AST4.TitleAnnotationId]: "Typename"
922
1318
  })
923
1319
  }).pipe(S2.mutable);
924
1320
  var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading = false, onTypenameChanged, onDelete }) => {
925
- const { t } = useTranslation4(translationKey);
926
- const projection = useMemo4(() => new ViewProjection(schema, view), [
1321
+ const { t } = useTranslation6(translationKey);
1322
+ const projection = useMemo8(() => new ViewProjection(schema, view), [
927
1323
  schema,
928
1324
  view
929
1325
  ]);
930
- const [field, setField] = useState3();
931
- const viewValues = useMemo4(() => {
1326
+ const [field, setField] = useState5();
1327
+ const viewValues = useMemo8(() => {
932
1328
  return {
933
1329
  name: view.name,
934
1330
  // TODO(burdon): Need to warn user of possible consequences of editing.
@@ -938,7 +1334,7 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
938
1334
  }, [
939
1335
  view
940
1336
  ]);
941
- const handleViewUpdate = useCallback3(({ name, typename }) => {
1337
+ const handleViewUpdate = useCallback7(({ name, typename }) => {
942
1338
  requestAnimationFrame(() => {
943
1339
  if (view.name !== name) {
944
1340
  view.name = name;
@@ -952,67 +1348,71 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
952
1348
  schema,
953
1349
  onTypenameChanged
954
1350
  ]);
955
- const handleSelect = useCallback3((field2) => {
1351
+ const handleSelect = useCallback7((field2) => {
956
1352
  setField((f) => f === field2 ? void 0 : field2);
957
1353
  }, []);
958
- const handleAdd = useCallback3(() => {
1354
+ const handleAdd = useCallback7(() => {
959
1355
  const field2 = projection.createFieldProjection();
960
1356
  setField(field2);
961
1357
  }, [
962
1358
  view
963
1359
  ]);
964
- const handleMove = useCallback3((fromIndex, toIndex) => {
965
- arrayMove(view.fields, fromIndex, toIndex);
1360
+ const handleMove = useCallback7((fromIndex, toIndex) => {
1361
+ const fields = [
1362
+ ...view.fields
1363
+ ];
1364
+ const [moved] = fields.splice(fromIndex, 1);
1365
+ fields.splice(toIndex, 0, moved);
1366
+ view.fields = fields;
966
1367
  }, [
967
1368
  view.fields
968
1369
  ]);
969
- const handleClose = useCallback3(() => setField(void 0), []);
970
- return /* @__PURE__ */ React8.createElement("div", {
1370
+ const handleClose = useCallback7(() => setField(void 0), []);
1371
+ return /* @__PURE__ */ React13.createElement("div", {
971
1372
  role: "none",
972
- className: mx3("flex flex-col w-full divide-y divide-separator", classNames)
973
- }, /* @__PURE__ */ React8.createElement(Form, {
974
- autoFocus: true,
1373
+ className: mx8("flex flex-col w-full divide-y divide-separator", classNames)
1374
+ }, /* @__PURE__ */ React13.createElement(Form, {
975
1375
  autoSave: true,
976
1376
  schema: ViewMetaSchema,
977
1377
  values: viewValues,
978
1378
  onSave: handleViewUpdate
979
- }), /* @__PURE__ */ React8.createElement("div", null, /* @__PURE__ */ React8.createElement("div", {
1379
+ }), /* @__PURE__ */ React13.createElement("div", null, /* @__PURE__ */ React13.createElement("div", {
980
1380
  role: "none",
981
1381
  className: "p-2"
982
- }, /* @__PURE__ */ React8.createElement("label", {
983
- className: mx3(inputTextLabel)
984
- }, t("fields label"))), /* @__PURE__ */ React8.createElement(List.Root, {
1382
+ }, /* @__PURE__ */ React13.createElement("label", {
1383
+ className: mx8(inputTextLabel)
1384
+ }, t("fields label"))), /* @__PURE__ */ React13.createElement(List2.Root, {
985
1385
  items: view.fields,
986
1386
  isItem: S2.is(FieldSchema),
987
1387
  getId: (field2) => field2.id,
988
1388
  onMove: handleMove
989
- }, ({ items: fields }) => /* @__PURE__ */ React8.createElement(React8.Fragment, null, showHeading && /* @__PURE__ */ React8.createElement("div", {
1389
+ }, ({ items: fields }) => /* @__PURE__ */ React13.createElement(React13.Fragment, null, showHeading && /* @__PURE__ */ React13.createElement("div", {
990
1390
  role: "heading",
991
1391
  className: grid
992
- }, /* @__PURE__ */ React8.createElement("div", null), /* @__PURE__ */ React8.createElement("div", {
1392
+ }, /* @__PURE__ */ React13.createElement("div", null), /* @__PURE__ */ React13.createElement("div", {
993
1393
  className: "flex items-center text-sm"
994
- }, t("field path label"))), /* @__PURE__ */ React8.createElement("div", {
1394
+ }, t("field path label"))), /* @__PURE__ */ React13.createElement("div", {
995
1395
  role: "list",
996
1396
  className: "flex flex-col w-full"
997
- }, fields?.map((field2) => /* @__PURE__ */ React8.createElement(List.Item, {
1397
+ }, fields?.map((field2) => /* @__PURE__ */ React13.createElement(List2.Item, {
998
1398
  key: field2.id,
999
1399
  item: field2,
1000
- classNames: mx3(grid, ghostHover, "cursor-pointer")
1001
- }, /* @__PURE__ */ React8.createElement(List.ItemDragHandle, null), /* @__PURE__ */ React8.createElement(List.ItemTitle, {
1400
+ classNames: mx8(grid, ghostHover, "cursor-pointer")
1401
+ }, /* @__PURE__ */ React13.createElement(List2.ItemDragHandle, null), /* @__PURE__ */ React13.createElement(List2.ItemTitle, {
1002
1402
  onClick: () => handleSelect(field2)
1003
- }, field2.path), /* @__PURE__ */ React8.createElement(List.ItemDeleteButton, {
1403
+ }, field2.path), /* @__PURE__ */ React13.createElement(List2.ItemDeleteButton, {
1004
1404
  disabled: view.fields.length <= 1,
1005
1405
  onClick: () => onDelete(field2.id)
1006
- }))))))), field && /* @__PURE__ */ React8.createElement(FieldEditor, {
1406
+ }))))))), field && /* @__PURE__ */ React13.createElement(FieldEditor, {
1007
1407
  key: field.id,
1008
1408
  view,
1009
1409
  projection,
1010
1410
  field,
1011
1411
  registry,
1012
1412
  onSave: handleClose
1013
- }), !readonly && !field && /* @__PURE__ */ React8.createElement("div", {
1413
+ }), !readonly && !field && /* @__PURE__ */ React13.createElement("div", {
1014
1414
  className: "flex p-2 justify-center"
1015
- }, /* @__PURE__ */ React8.createElement(IconButton2, {
1415
+ }, /* @__PURE__ */ React13.createElement(IconButton4, {
1016
1416
  icon: "ph--plus--regular",
1017
1417
  label: t("button add property"),
1018
1418
  onClick: handleAdd,
@@ -1022,7 +1422,7 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
1022
1422
  };
1023
1423
 
1024
1424
  // packages/ui/react-ui-form/src/util/field.ts
1025
- import { FormatEnum as FormatEnum3, TypeEnum } from "@dxos/echo-schema";
1425
+ import { FormatEnum as FormatEnum3, GeoLocation as GeoLocation2, TypeEnum } from "@dxos/echo-schema";
1026
1426
  var parseValue = ({ type, format, value }) => {
1027
1427
  if (value === void 0 || value === null || value === "") {
1028
1428
  return void 0;
@@ -1076,6 +1476,26 @@ var parseValue = ({ type, format, value }) => {
1076
1476
  const date = new Date(value);
1077
1477
  return isNaN(date.getTime()) ? null : date;
1078
1478
  }
1479
+ case FormatEnum3.GeoPoint: {
1480
+ if (typeof value === "string") {
1481
+ const cleaned = value.replace(/[^0-9.,-]+/g, "");
1482
+ const parts = cleaned.split(",");
1483
+ if (parts.length === 2) {
1484
+ const lat = parseFloat(parts[0]);
1485
+ const long = parseFloat(parts[1]);
1486
+ if (!isNaN(lat) && !isNaN(long)) {
1487
+ return GeoLocation2.toGeoPoint({
1488
+ latitude: lat,
1489
+ longitude: long
1490
+ });
1491
+ }
1492
+ }
1493
+ }
1494
+ return [
1495
+ 0,
1496
+ 0
1497
+ ];
1498
+ }
1079
1499
  default: {
1080
1500
  return value;
1081
1501
  }
@@ -1134,7 +1554,7 @@ var pathNotUniqueError = (path) => ({
1134
1554
  });
1135
1555
 
1136
1556
  // packages/ui/react-ui-form/src/util/formatting.ts
1137
- import { FormatEnum as FormatEnum4, TypeEnum as TypeEnum2 } from "@dxos/echo-schema";
1557
+ import { FormatEnum as FormatEnum4, GeoLocation as GeoLocation3, TypeEnum as TypeEnum2 } from "@dxos/echo-schema";
1138
1558
  var formatForDisplay = ({ type, format, value, locale = void 0 }) => {
1139
1559
  const formatScalar = (type2) => {
1140
1560
  switch (type2) {
@@ -1187,6 +1607,16 @@ var formatForDisplay = ({ type, format, value, locale = void 0 }) => {
1187
1607
  return date.toLocaleTimeString(locale);
1188
1608
  }
1189
1609
  }
1610
+ case FormatEnum4.GeoPoint: {
1611
+ if (value === null || value === void 0) {
1612
+ return "";
1613
+ }
1614
+ if (Array.isArray(value) && value.length >= 2 && value.every(Number.isFinite)) {
1615
+ const { latitude, longitude } = GeoLocation3.fromGeoPoint(value);
1616
+ return `${latitude},${longitude}`;
1617
+ }
1618
+ return String(value);
1619
+ }
1190
1620
  default: {
1191
1621
  if (value === null || value === "undefined") {
1192
1622
  return "";
@@ -1235,6 +1665,16 @@ var formatForEditing = ({ type, format, value, locale = void 0 }) => {
1235
1665
  const date = new Date(value);
1236
1666
  return date.toISOString().split("T")[1].split(".")[0];
1237
1667
  }
1668
+ case FormatEnum4.GeoPoint: {
1669
+ if (value === null || value === void 0) {
1670
+ return "";
1671
+ }
1672
+ if (Array.isArray(value) && value.length >= 2 && value.every(Number.isFinite)) {
1673
+ const { latitude, longitude } = GeoLocation3.fromGeoPoint(value);
1674
+ return `${latitude},${longitude}`;
1675
+ }
1676
+ return String(value);
1677
+ }
1238
1678
  default: {
1239
1679
  return String(value);
1240
1680
  }
@@ -1242,14 +1682,16 @@ var formatForEditing = ({ type, format, value, locale = void 0 }) => {
1242
1682
  };
1243
1683
  export {
1244
1684
  BooleanInput,
1685
+ DeprecatedFormContainer,
1245
1686
  DeprecatedFormInput,
1246
1687
  FieldEditor,
1247
1688
  Form,
1248
- FormActions,
1249
1689
  GeoPointInput,
1250
1690
  InputHeader,
1691
+ MarkdownInput,
1251
1692
  NumberInput,
1252
1693
  SelectInput,
1694
+ SelectOptionInput,
1253
1695
  TextInput,
1254
1696
  TupleInput,
1255
1697
  ViewEditor,
@@ -1260,6 +1702,7 @@ export {
1260
1702
  parseValue,
1261
1703
  pathNotUniqueError,
1262
1704
  translations_default as translations,
1263
- useForm
1705
+ useForm,
1706
+ useFormValues
1264
1707
  };
1265
1708
  //# sourceMappingURL=index.mjs.map