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

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 +971 -558
  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 +998 -596
  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 +971 -558
  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 +92 -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 +63 -49
  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 useMemo6, 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 } 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,21 @@ 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);
206
+ const getFormValue = (path) => {
207
+ return getValue(values, createJsonPath(path));
350
208
  };
351
- const onValueChange = (property, type, value) => {
209
+ const onValueChange = (path, type, value) => {
210
+ const jsonPath = createJsonPath(path);
352
211
  let parsedValue = value;
353
212
  try {
354
213
  if (type === "number") {
@@ -357,19 +216,19 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
357
216
  } catch (err) {
358
217
  log.catch(err, void 0, {
359
218
  F: __dxlog_file,
360
- L: 178,
219
+ L: 198,
361
220
  S: void 0,
362
221
  C: (f, a) => f(...a)
363
222
  });
364
223
  parsedValue = void 0;
365
224
  }
366
225
  const newValues = {
367
- ...setValue(values, property, parsedValue)
226
+ ...setValue(values, jsonPath, parsedValue)
368
227
  };
369
228
  setValues(newValues);
370
229
  setChanged((prev) => ({
371
230
  ...prev,
372
- [property]: true
231
+ [jsonPath]: true
373
232
  }));
374
233
  onValuesChanged?.(newValues);
375
234
  const isValid = validate(newValues);
@@ -379,15 +238,12 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
379
238
  });
380
239
  }
381
240
  };
382
- const onBlur = useCallback((event) => {
383
- const { name } = event.target;
241
+ const onTouched = useCallback((path) => {
242
+ const jsonPath = createJsonPath(path);
384
243
  setTouched((touched2) => ({
385
244
  ...touched2,
386
- [name]: true
245
+ [jsonPath]: true
387
246
  }));
388
- if (event.relatedTarget?.getAttribute("type") === "submit") {
389
- setTouched(createKeySet(values, true));
390
- }
391
247
  validate(values);
392
248
  }, [
393
249
  validate,
@@ -401,17 +257,18 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
401
257
  changed,
402
258
  canSave,
403
259
  handleSave,
260
+ formIsValid,
404
261
  // Field utils.
405
262
  getStatus,
406
263
  getValue: getFormValue,
407
264
  onValueChange,
408
- onBlur
265
+ onTouched
409
266
  };
410
267
  };
411
268
  var createKeySet = (obj, value) => {
412
269
  invariant(obj, void 0, {
413
270
  F: __dxlog_file,
414
- L: 229,
271
+ L: 241,
415
272
  S: void 0,
416
273
  A: [
417
274
  "obj",
@@ -425,338 +282,847 @@ var createKeySet = (obj, value) => {
425
282
  };
426
283
  var flatMap = (errors) => {
427
284
  return errors.reduce((result, { path, message }) => {
428
- if (!(path in result)) {
429
- result[path] = message;
285
+ const jsonPath = fromEffectValidationPath(path);
286
+ if (!(jsonPath in result)) {
287
+ result[jsonPath] = message;
430
288
  }
431
289
  return result;
432
290
  }, {});
433
291
  };
434
292
 
435
- // 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";
437
- 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
450
- });
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;
293
+ // packages/ui/react-ui-form/src/components/Form/FormContext.tsx
294
+ var FormContext = /* @__PURE__ */ createContext(void 0);
295
+ var useFormContext = () => {
296
+ return useContext(FormContext) ?? raise(new Error("Missing FormContext"));
297
+ };
298
+ var useFormValues = (path = []) => {
299
+ const { values: formValues } = useFormContext();
300
+ const jsonPath = createJsonPath2(path);
301
+ return getValue2(formValues, jsonPath);
302
+ };
303
+ var useInputProps = (path = []) => {
304
+ const { getStatus, getValue: getFormValue, onValueChange, onTouched } = useFormContext();
305
+ return {
306
+ getStatus: () => getStatus(path),
307
+ getValue: () => getFormValue(path),
308
+ onValueChange: (type, value) => onValueChange(path, type, value),
309
+ onBlur: () => onTouched(path)
310
+ };
311
+ };
312
+ var FormProvider = ({ children, formRef, autoSave, ...formOptions }) => {
313
+ const form = useForm(formOptions);
314
+ useEffect2(() => {
315
+ if (!formRef?.current) {
316
+ return;
317
+ }
318
+ const handleKeyDown = (event) => {
319
+ const keyIsEnter = event.key === "Enter";
320
+ const modifierUsed = event.ctrlKey || event.altKey || event.metaKey || event.shiftKey;
321
+ const inputIsTextarea = event.target.tagName.toLowerCase() === "textarea";
322
+ const inputOptOut = event.target.hasAttribute("data-no-submit");
323
+ const shouldSubmitRegularInput = !inputIsTextarea && keyIsEnter && !modifierUsed;
324
+ const shouldSubmitTextarea = inputIsTextarea && keyIsEnter && event.metaKey;
325
+ if ((shouldSubmitRegularInput || shouldSubmitTextarea) && !inputOptOut) {
326
+ if (!autoSave && form.canSave) {
327
+ form.handleSave();
328
+ }
329
+ if (autoSave && form.formIsValid) {
330
+ event.target.blur();
331
+ }
332
+ }
457
333
  };
458
- return sort ? filtered.sort(({ name: a }, { name: b }) => findIndex(sort, a) - findIndex(sort, b)) : filtered;
334
+ const formElement = formRef.current;
335
+ formElement.addEventListener("keydown", handleKeyDown);
336
+ return () => formElement.removeEventListener("keydown", handleKeyDown);
459
337
  }, [
460
- schema,
461
- filter,
462
- values
338
+ formRef,
339
+ form.canSave,
340
+ form.formIsValid,
341
+ form.handleSave,
342
+ autoSave
463
343
  ]);
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
- });
344
+ return /* @__PURE__ */ React2.createElement(FormContext.Provider, {
345
+ value: form
346
+ }, children);
347
+ };
348
+
349
+ // packages/ui/react-ui-form/src/components/Form/custom/GeoPointInput.tsx
350
+ import React4, { useCallback as useCallback2, useState as useState2, useEffect as useEffect3, useMemo as useMemo2 } from "react";
351
+ import { GeoLocation } from "@dxos/echo-schema";
352
+ import { Input as Input2, useTranslation } from "@dxos/react-ui";
353
+ import { safeParseFloat } from "@dxos/util";
354
+
355
+ // packages/ui/react-ui-form/src/components/Form/Input.tsx
356
+ import React3 from "react";
357
+ import { Icon, Tooltip } from "@dxos/react-ui";
358
+ import { errorText } from "@dxos/react-ui-theme";
359
+ var InputHeader = ({ children, error }) => {
360
+ return /* @__PURE__ */ React3.createElement("div", {
361
+ role: "none",
362
+ className: "flex justify-between items-center mb-1"
363
+ }, children, error && /* @__PURE__ */ React3.createElement(Tooltip.Root, null, /* @__PURE__ */ React3.createElement(Tooltip.Trigger, {
364
+ asChild: true
365
+ }, /* @__PURE__ */ React3.createElement(Icon, {
366
+ icon: "ph--warning--regular",
367
+ size: 4,
368
+ classNames: errorText
369
+ })), /* @__PURE__ */ React3.createElement(Tooltip.Content, {
370
+ side: "bottom"
371
+ }, /* @__PURE__ */ React3.createElement(Tooltip.Arrow, null), error)));
372
+ };
373
+
374
+ // packages/ui/react-ui-form/src/components/Form/custom/GeoPointInput.tsx
375
+ var GeoPointInput = ({ type, label, disabled, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
376
+ const { t } = useTranslation(translationKey);
377
+ const { status, error } = getStatus();
378
+ const geoPoint = useMemo2(() => getValue3() ?? [
379
+ 0,
380
+ 0
381
+ ], [
382
+ getValue3
383
+ ]);
384
+ const geoLocation = useMemo2(() => GeoLocation.fromGeoPoint(geoPoint), [
385
+ geoPoint
386
+ ]);
387
+ const [longitudeText, setLongitudeText] = useState2(geoLocation.longitude?.toString());
388
+ const [latitudeText, setLatitudeText] = useState2(geoLocation.latitude?.toString());
389
+ useEffect3(() => {
390
+ const location = GeoLocation.fromGeoPoint(geoPoint);
391
+ setLongitudeText(location.longitude.toString());
392
+ setLatitudeText(location.latitude.toString());
393
+ }, [
394
+ geoPoint
395
+ ]);
396
+ const handleCoordinateChange = useCallback2((coordinateType, setText) => (event) => {
397
+ const inputText = event.target.value;
398
+ setText(inputText);
399
+ if (inputText !== "" && inputText !== "-") {
400
+ const coord = safeParseFloat(inputText);
401
+ if (coord !== void 0 && !isNaN(coord)) {
402
+ const currentLocation = GeoLocation.fromGeoPoint(getValue3() ?? [
403
+ 0,
404
+ 0
405
+ ]);
406
+ const newLocation = {
407
+ ...currentLocation,
408
+ [coordinateType]: coord
409
+ };
410
+ const newValue = GeoLocation.toGeoPoint(newLocation);
411
+ onValueChange(type, newValue);
412
+ }
474
413
  }
475
414
  }, [
476
- errors
415
+ getValue3,
416
+ onValueChange,
417
+ type
477
418
  ]);
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
419
+ return /* @__PURE__ */ React4.createElement(Input2.Root, {
420
+ validationValence: status
421
+ }, /* @__PURE__ */ React4.createElement(InputHeader, {
422
+ error
423
+ }, /* @__PURE__ */ React4.createElement(Input2.Label, null, label)), /* @__PURE__ */ React4.createElement("div", {
424
+ role: "none",
425
+ className: "grid grid-cols-2 gap-2"
426
+ }, /* @__PURE__ */ React4.createElement(Input2.TextInput, {
427
+ type: "text",
428
+ pattern: "^-?[0-9]*\\.?[0-9]*$",
429
+ disabled,
430
+ placeholder: t("placeholder latitude"),
431
+ value: latitudeText,
432
+ onChange: handleCoordinateChange("latitude", setLatitudeText),
433
+ onBlur
434
+ }), /* @__PURE__ */ React4.createElement(Input2.TextInput, {
435
+ type: "text",
436
+ pattern: "^-?[0-9]*\\.?[0-9]*$",
437
+ disabled,
438
+ placeholder: t("placeholder longitude"),
439
+ value: longitudeText,
440
+ onChange: handleCoordinateChange("longitude", setLongitudeText),
441
+ onBlur
442
+ })));
443
+ };
444
+
445
+ // packages/ui/react-ui-form/src/components/Form/custom/SelectOptionsInput.tsx
446
+ import React5, { useCallback as useCallback3, useEffect as useEffect4, useState as useState3, useRef } from "react";
447
+ import { PublicKey } from "@dxos/keys";
448
+ import { Icon as Icon2, IconButton, Input as Input3, Tag, Toolbar, useTranslation as useTranslation2 } from "@dxos/react-ui";
449
+ import { List } from "@dxos/react-ui-list";
450
+ import { HuePicker } from "@dxos/react-ui-pickers";
451
+ import { hues, mx as mx2 } from "@dxos/react-ui-theme";
452
+ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3, onValueChange }) => {
453
+ const [selected, setSelectedId] = useState3(null);
454
+ const [isNewOption, setIsNewOption] = useState3(false);
455
+ const inputRef = useRef(null);
456
+ const { t } = useTranslation2(translationKey);
457
+ const { status, error } = getStatus();
458
+ const options = getValue3();
459
+ useEffect4(() => {
460
+ if (options === void 0) {
461
+ onValueChange(type, []);
462
+ }
463
+ }, [
464
+ options,
465
+ onValueChange,
466
+ type
467
+ ]);
468
+ const randomHue = () => {
469
+ const usedHues = new Set(options?.map((option) => option.color) ?? []);
470
+ const unusedHues = hues.filter((hue) => !usedHues.has(hue));
471
+ if (unusedHues.length > 0) {
472
+ return unusedHues[Math.floor(Math.random() * unusedHues.length)];
473
+ }
474
+ const lastThree = new Set(options?.slice(-3).map((option) => option.color) ?? []);
475
+ const availableHues = hues.filter((hue) => !lastThree.has(hue));
476
+ return availableHues[Math.floor(Math.random() * availableHues.length)] ?? hues[Math.floor(Math.random() * hues.length)];
477
+ };
478
+ const handleAdd = useCallback3(() => {
479
+ const newId = PublicKey.random().truncate();
480
+ const newOption = {
481
+ id: newId,
482
+ title: "",
483
+ color: randomHue()
484
+ };
485
+ onValueChange(type, [
486
+ ...options ?? [],
487
+ newOption
488
+ ]);
489
+ setSelectedId(newId);
490
+ setIsNewOption(true);
491
+ }, [
492
+ options,
493
+ type,
494
+ onValueChange
495
+ ]);
496
+ const handleDelete = useCallback3((id) => {
497
+ const newOptions = options?.filter((option) => option.id !== id) ?? [];
498
+ onValueChange(type, newOptions);
499
+ setSelectedId(null);
500
+ }, [
501
+ options,
502
+ type,
503
+ onValueChange
504
+ ]);
505
+ const handleMove = useCallback3((from, to) => {
506
+ if (!options) {
507
+ return;
508
+ }
509
+ const newOptions = [
510
+ ...options
487
511
  ];
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);
512
+ const [removed] = newOptions.splice(from, 1);
513
+ newOptions.splice(to, 0, removed);
514
+ onValueChange(type, newOptions);
515
+ }, [
516
+ options,
517
+ type,
518
+ onValueChange
519
+ ]);
520
+ const handleClick = useCallback3((id) => {
521
+ setSelectedId((current) => current === id ? null : id);
522
+ }, []);
523
+ const handleTitleChange = useCallback3((id) => (e) => {
524
+ const newOptions = options?.map((o) => o.id === id ? {
525
+ ...o,
526
+ title: e.target.value
527
+ } : o);
528
+ onValueChange(type, newOptions ?? []);
529
+ }, [
530
+ options,
531
+ type,
532
+ onValueChange
533
+ ]);
534
+ const handleKeyDown = useCallback3((e) => {
535
+ if (e.key === "Enter") {
536
+ setSelectedId(null);
509
537
  }
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
- }
538
+ }, []);
539
+ const handleColorChange = useCallback3((id) => (hue) => {
540
+ const newOptions = options?.map((o) => o.id === id ? {
541
+ ...o,
542
+ color: hue
543
+ } : o);
544
+ onValueChange(type, newOptions ?? []);
545
+ }, [
546
+ options,
547
+ type,
548
+ onValueChange
549
+ ]);
550
+ useEffect4(() => {
551
+ if (selected && isNewOption && inputRef.current) {
552
+ inputRef.current.focus();
553
+ setIsNewOption(false);
548
554
  }
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;
555
+ }, [
556
+ selected,
557
+ isNewOption
558
+ ]);
559
+ return /* @__PURE__ */ React5.createElement(Input3.Root, {
560
+ validationValence: status
561
+ }, /* @__PURE__ */ React5.createElement(InputHeader, {
562
+ error
563
+ }, /* @__PURE__ */ React5.createElement(Input3.Label, null, label)), /* @__PURE__ */ React5.createElement("div", {
564
+ role: "none"
565
+ }, options && /* @__PURE__ */ React5.createElement(List.Root, {
566
+ items: options,
567
+ isItem: (item) => true,
568
+ onMove: handleMove
569
+ }, ({ items }) => /* @__PURE__ */ React5.createElement("div", {
570
+ role: "list",
571
+ className: "w-full overflow-auto"
572
+ }, items?.map((item) => /* @__PURE__ */ React5.createElement("div", {
573
+ key: item.id
574
+ }, /* @__PURE__ */ React5.createElement(List.Item, {
575
+ role: "button",
576
+ item,
577
+ classNames: mx2("p-1 plb-2", "flex flex-col", "cursor-pointer", "hover:bg-hoverOverlay"),
578
+ "aria-expanded": selected === item.id
579
+ }, /* @__PURE__ */ React5.createElement("div", {
580
+ className: "flex items-center"
581
+ }, /* @__PURE__ */ React5.createElement(List.ItemDragHandle, null), /* @__PURE__ */ React5.createElement(List.ItemTitle, {
582
+ onClick: () => handleClick(item.id),
583
+ classNames: "flex-1"
584
+ }, /* @__PURE__ */ React5.createElement(Tag, {
585
+ palette: item.color
586
+ }, item.title || "\u200B")), /* @__PURE__ */ React5.createElement(Icon2, {
587
+ icon: selected === item.id ? "ph--caret-down--regular" : "ph--caret-right--regular",
588
+ onClick: () => handleClick(item.id)
589
+ })), selected === item.id && /* @__PURE__ */ React5.createElement("div", {
590
+ className: "mis-4 mbs-2 flex flex-col gap-1"
591
+ }, /* @__PURE__ */ React5.createElement(Input3.Label, {
592
+ classNames: "text-xs"
593
+ }, t("select option label")), /* @__PURE__ */ React5.createElement("div", {
594
+ className: "flex flex-row items-center gap-1"
595
+ }, /* @__PURE__ */ React5.createElement(Input3.TextInput, {
596
+ placeholder: t("select option label placeholder"),
597
+ ref: selected === item.id ? inputRef : void 0,
598
+ value: item.title,
599
+ onChange: handleTitleChange(item.id),
600
+ onKeyDown: handleKeyDown,
601
+ classNames: "flex-1",
602
+ "data-no-submit": true
603
+ }), /* @__PURE__ */ React5.createElement(Toolbar.Root, {
604
+ classNames: "p-0 m-0 !is-auto"
605
+ }, /* @__PURE__ */ React5.createElement(HuePicker, {
606
+ value: item.color,
607
+ onChange: handleColorChange(item.id)
608
+ })), /* @__PURE__ */ React5.createElement(IconButton, {
609
+ icon: "ph--trash--fill",
610
+ iconOnly: true,
611
+ label: t("select option delete"),
612
+ onClick: () => handleDelete(item.id)
613
+ })))))), /* @__PURE__ */ React5.createElement("div", {
614
+ role: "listitem"
615
+ }, /* @__PURE__ */ React5.createElement("div", {
616
+ role: "button",
617
+ className: mx2("p-1 plb-2", "flex flex-col", "cursor-pointer", "hover:bg-hoverOverlay"),
618
+ onClick: handleAdd
619
+ }, /* @__PURE__ */ React5.createElement("div", {
620
+ className: "flex items-center gap-1"
621
+ }, /* @__PURE__ */ React5.createElement(Icon2, {
622
+ icon: "ph--plus--regular"
623
+ }), /* @__PURE__ */ React5.createElement("span", {
624
+ className: "text-sm"
625
+ }, t("select option add")))))))));
626
+ };
627
+
628
+ // packages/ui/react-ui-form/src/components/Form/custom/TupleInput.tsx
629
+ import React6 from "react";
630
+ import { Input as Input4 } from "@dxos/react-ui";
631
+ import { mx as mx3 } from "@dxos/react-ui-theme";
632
+ import { safeParseFloat as safeParseFloat2 } from "@dxos/util";
633
+ var gridCols = [
634
+ "grid-cols-1",
635
+ "grid-cols-2",
636
+ "grid-cols-3",
637
+ "grid-cols-4"
638
+ ];
639
+ var TupleInput = ({ binding, type, label, disabled, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
640
+ const { status, error } = getStatus();
641
+ const values = getValue3() ?? {};
642
+ return /* @__PURE__ */ React6.createElement(Input4.Root, {
643
+ validationValence: status
644
+ }, /* @__PURE__ */ React6.createElement(InputHeader, {
645
+ error
646
+ }, /* @__PURE__ */ React6.createElement(Input4.Label, null, label)), /* @__PURE__ */ React6.createElement("div", {
647
+ className: mx3("grid gap-2", gridCols[binding.length - 1])
648
+ }, binding.map((prop) => /* @__PURE__ */ React6.createElement(Input4.TextInput, {
649
+ key: prop,
650
+ type: "number",
651
+ disabled,
652
+ value: values[prop],
653
+ onChange: (event) => onValueChange(type, {
654
+ ...values,
655
+ [prop]: safeParseFloat2(event.target.value, 0)
656
+ }),
657
+ onBlur
658
+ }))));
659
+ };
660
+
661
+ // packages/ui/react-ui-form/src/components/Form/Defaults.tsx
662
+ import React7, { useCallback as useCallback4, useEffect as useEffect5, useRef as useRef2 } from "react";
663
+ import { Input as Input5, Select } from "@dxos/react-ui";
664
+ var TextInput = ({ type, label, inputOnly, disabled, placeholder, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
665
+ const { status, error } = getStatus();
666
+ return /* @__PURE__ */ React7.createElement(Input5.Root, {
667
+ validationValence: status
668
+ }, !inputOnly && /* @__PURE__ */ React7.createElement(InputHeader, {
669
+ error
670
+ }, /* @__PURE__ */ React7.createElement(Input5.Label, null, label)), /* @__PURE__ */ React7.createElement(Input5.TextInput, {
671
+ disabled,
672
+ placeholder,
673
+ value: getValue3() ?? "",
674
+ onChange: (event) => onValueChange(type, event.target.value),
675
+ onBlur
676
+ }), inputOnly && /* @__PURE__ */ React7.createElement(Input5.Validation, null, error));
677
+ };
678
+ var NumberInput = ({ type, label, inputOnly, disabled, placeholder, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
679
+ const { status, error } = getStatus();
680
+ return /* @__PURE__ */ React7.createElement(Input5.Root, {
681
+ validationValence: status
682
+ }, !inputOnly && /* @__PURE__ */ React7.createElement(InputHeader, {
683
+ error
684
+ }, /* @__PURE__ */ React7.createElement(Input5.Label, null, label)), /* @__PURE__ */ React7.createElement(Input5.TextInput, {
685
+ type: "number",
686
+ disabled,
687
+ placeholder,
688
+ value: getValue3() ?? 0,
689
+ onChange: (event) => onValueChange(type, event.target.value),
690
+ onBlur
691
+ }), inputOnly && /* @__PURE__ */ React7.createElement(Input5.DescriptionAndValidation, null, error));
692
+ };
693
+ var BooleanInput = ({ type, label, inputOnly, getStatus, getValue: getValue3, onValueChange }) => {
694
+ const { status, error } = getStatus();
695
+ const checked = Boolean(getValue3());
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.Switch, {
701
+ checked,
702
+ onCheckedChange: (value) => onValueChange(type, value)
703
+ }), inputOnly && /* @__PURE__ */ React7.createElement(Input5.DescriptionAndValidation, null, error));
704
+ };
705
+ var SelectInput = ({ type, label, inputOnly, disabled, placeholder, options, getStatus, getValue: getValue3, onValueChange }) => {
706
+ const { status, error } = getStatus();
707
+ return /* @__PURE__ */ React7.createElement(Input5.Root, {
708
+ validationValence: status
709
+ }, !inputOnly && /* @__PURE__ */ React7.createElement(InputHeader, {
710
+ error
711
+ }, /* @__PURE__ */ React7.createElement(Input5.Label, null, label)), /* @__PURE__ */ React7.createElement(Select.Root, {
712
+ value: getValue3(),
713
+ onValueChange: (value) => onValueChange(type, value)
714
+ }, /* @__PURE__ */ React7.createElement(Select.TriggerButton, {
715
+ classNames: "is-full",
716
+ disabled,
717
+ placeholder
718
+ }), /* @__PURE__ */ React7.createElement(Select.Portal, null, /* @__PURE__ */ React7.createElement(Select.Content, null, /* @__PURE__ */ React7.createElement(Select.Viewport, null, options?.map(({ value, label: label2 }) => (
719
+ // NOTE: Numeric values are converted to and from strings.
720
+ /* @__PURE__ */ React7.createElement(Select.Option, {
721
+ key: String(value),
722
+ value: String(value)
723
+ }, label2 ?? String(value))
724
+ )))))), inputOnly && /* @__PURE__ */ React7.createElement(Input5.DescriptionAndValidation, null, error));
725
+ };
726
+ var MarkdownInput = ({ type, label, inputOnly, disabled, placeholder, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
727
+ const { status, error } = getStatus();
728
+ const textareaRef = useRef2(null);
729
+ const adjustHeight = useCallback4(() => {
730
+ const textarea = textareaRef.current;
731
+ if (textarea) {
732
+ textarea.style.blockSize = "auto";
733
+ textarea.style.blockSize = `${textarea.scrollHeight + 2}px`;
581
734
  }
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
- }));
735
+ }, []);
736
+ useEffect5(() => {
737
+ adjustHeight();
738
+ }, [
739
+ adjustHeight
740
+ ]);
741
+ useEffect5(() => {
742
+ adjustHeight();
743
+ }, [
744
+ getValue3(),
745
+ adjustHeight
746
+ ]);
747
+ return /* @__PURE__ */ React7.createElement(Input5.Root, {
748
+ validationValence: status
749
+ }, !inputOnly && /* @__PURE__ */ React7.createElement(InputHeader, {
750
+ error
751
+ }, /* @__PURE__ */ React7.createElement(Input5.Label, null, label)), /* @__PURE__ */ React7.createElement(Input5.TextArea, {
752
+ ref: textareaRef,
753
+ disabled,
754
+ placeholder,
755
+ value: getValue3() ?? "",
756
+ classNames: "min-bs-auto max-h-40 overflow-auto",
757
+ onChange: (event) => onValueChange(type, event.target.value),
758
+ onBlur,
759
+ style: {
760
+ resize: "none"
761
+ },
762
+ spellCheck: false
763
+ }), inputOnly && /* @__PURE__ */ React7.createElement(Input5.Validation, null, error));
601
764
  };
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);
765
+
766
+ // packages/ui/react-ui-form/src/components/Form/Form.tsx
767
+ import React11, { useEffect as useEffect6, useMemo as useMemo5, useRef as useRef3 } from "react";
768
+ import { mx as mx7 } from "@dxos/react-ui-theme";
769
+
770
+ // packages/ui/react-ui-form/src/components/Form/FormActions.tsx
771
+ import React8 from "react";
772
+ import { IconButton as IconButton2, useTranslation as useTranslation3 } from "@dxos/react-ui";
773
+ import { mx as mx4 } from "@dxos/react-ui-theme";
774
+ var FormActions = ({ onCancel, readonly }) => {
775
+ const { t } = useTranslation3(translationKey);
776
+ const { canSave, handleSave } = useFormContext();
777
+ return /* @__PURE__ */ React8.createElement("div", {
778
+ role: "none",
779
+ className: "mt-2 flex justify-center"
780
+ }, /* @__PURE__ */ React8.createElement("div", {
781
+ role: "none",
782
+ className: mx4(onCancel && !readonly && "grid grid-cols-2 gap-2")
783
+ }, onCancel && !readonly && /* @__PURE__ */ React8.createElement(IconButton2, {
784
+ "data-testid": "cancel-button",
785
+ icon: "ph--x--regular",
786
+ label: t("button cancel"),
787
+ onClick: onCancel
788
+ }), handleSave && /* @__PURE__ */ React8.createElement(IconButton2, {
789
+ type: "submit",
790
+ "data-testid": "save-button",
791
+ disabled: !canSave,
792
+ icon: "ph--check--regular",
793
+ label: t("button save"),
794
+ onClick: handleSave
795
+ })));
796
+ };
797
+
798
+ // packages/ui/react-ui-form/src/components/Form/FormContent.tsx
799
+ import { pipe as pipe2 } from "effect";
800
+ import { capitalize as capitalize2 } from "effect/String";
801
+ import React10, { useMemo as useMemo4 } from "react";
802
+ import { AST as AST3, S } from "@dxos/echo-schema";
803
+ import { createJsonPath as createJsonPath3, findNode as findNode2, getDiscriminatedType as getDiscriminatedType2, isDiscriminatedUnion as isDiscriminatedUnion2 } from "@dxos/effect";
804
+ import { mx as mx6 } from "@dxos/react-ui-theme";
805
+ import { getSchemaProperties as getSchemaProperties2 } from "@dxos/schema";
806
+ import { isNotFalsy } from "@dxos/util";
807
+
808
+ // packages/ui/react-ui-form/src/components/Form/ArrayField.tsx
809
+ import { pipe } from "effect";
810
+ import { capitalize } from "effect/String";
811
+ import React9, { useCallback as useCallback5, useMemo as useMemo3 } from "react";
812
+ import { AST as AST2 } from "@dxos/echo-schema";
813
+ import { findNode, getDiscriminatedType, isDiscriminatedUnion, SimpleType } from "@dxos/effect";
814
+ import { invariant as invariant2 } from "@dxos/invariant";
815
+ import { IconButton as IconButton3, useTranslation as useTranslation4 } from "@dxos/react-ui";
816
+ import { mx as mx5 } from "@dxos/react-ui-theme";
817
+ import { getSchemaProperties } from "@dxos/schema";
818
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-form/src/components/Form/ArrayField.tsx";
819
+ var padding = "px-2";
820
+ var ArrayField = ({ property, readonly, path, inputProps, Custom, lookupComponent }) => {
821
+ const { t } = useTranslation4(translationKey);
822
+ const { ast, name, type, title } = property;
823
+ const values = useFormValues(path ?? []);
824
+ invariant2(Array.isArray(values), `Values at path ${path?.join(".")} must be an array.`, {
825
+ F: __dxlog_file2,
826
+ L: 37,
827
+ S: void 0,
828
+ A: [
829
+ "Array.isArray(values)",
830
+ "`Values at path ${path?.join('.')} must be an array.`"
831
+ ]
832
+ });
833
+ const label = title ?? pipe(name, capitalize);
834
+ const tupleType = findNode(ast, AST2.isTupleType);
609
835
  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
- })));
836
+ const getDefaultObjectValue = (typeNode) => {
837
+ const baseNode = findNode(typeNode, isDiscriminatedUnion);
838
+ const typeLiteral = baseNode ? getDiscriminatedType(baseNode, {}) : findNode(typeNode, AST2.isTypeLiteral);
839
+ if (!typeLiteral) {
840
+ return {};
669
841
  }
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) {
842
+ return Object.fromEntries(getSchemaProperties(typeLiteral, {}).map((prop) => [
843
+ prop.name,
844
+ prop.defaultValue
845
+ ]));
846
+ };
847
+ const newValue = useMemo3(() => {
848
+ if (type === "object" && elementType) {
849
+ return getDefaultObjectValue(elementType);
850
+ } else {
851
+ return SimpleType.getDefaultValue(type);
852
+ }
853
+ }, [
854
+ type,
855
+ elementType
856
+ ]);
857
+ const handleAdd = useCallback5(() => {
858
+ inputProps.onValueChange(type, [
859
+ ...values,
860
+ newValue
861
+ ]);
862
+ }, [
863
+ type,
864
+ elementType,
865
+ inputProps,
866
+ newValue,
867
+ values
868
+ ]);
869
+ const handleRemove = useCallback5((index) => {
870
+ const newValues = values.filter((_, i) => i !== index);
871
+ inputProps.onValueChange(type, newValues);
872
+ }, [
873
+ type,
874
+ inputProps,
875
+ values
876
+ ]);
877
+ if (!elementType) {
677
878
  return null;
678
879
  }
679
- return /* @__PURE__ */ React6.createElement("div", {
880
+ return /* @__PURE__ */ React9.createElement("div", {
680
881
  role: "none",
681
- key: "meta-input",
682
- className: mx2(padding)
683
- }, /* @__PURE__ */ React6.createElement(InputHeader, null, label), /* @__PURE__ */ React6.createElement("div", {
882
+ className: mx5(padding)
883
+ }, /* @__PURE__ */ React9.createElement(InputHeader, null, label), /* @__PURE__ */ React9.createElement("div", {
684
884
  role: "none",
685
885
  className: "flex flex-col gap-1"
686
- }, arrayValues.map((_, index) => /* @__PURE__ */ React6.createElement("div", {
886
+ }, values.map((_value, index) => /* @__PURE__ */ React9.createElement("div", {
687
887
  key: index,
688
888
  role: "none",
689
- className: "flex items-start gap-1"
690
- }, /* @__PURE__ */ React6.createElement("div", {
889
+ className: "flex items-center gap-1"
890
+ }, /* @__PURE__ */ React9.createElement("div", {
691
891
  role: "none",
692
892
  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, {
893
+ }, /* @__PURE__ */ React9.createElement(FormField, {
894
+ property: {
895
+ ...property,
896
+ array: false,
897
+ ast: elementType
898
+ },
899
+ path: [
900
+ ...path ?? [],
901
+ index
902
+ ],
903
+ readonly,
904
+ inline: true,
905
+ Custom,
906
+ lookupComponent
907
+ })), /* @__PURE__ */ React9.createElement(IconButton3, {
703
908
  icon: "ph--trash--regular",
704
909
  iconOnly: true,
705
910
  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", {
911
+ onClick: () => handleRemove(index)
912
+ })))), /* @__PURE__ */ React9.createElement("div", {
711
913
  role: "none",
712
914
  className: "flex justify-between items-center plb-1"
713
- }, /* @__PURE__ */ React6.createElement(IconButton, {
915
+ }, /* @__PURE__ */ React9.createElement(IconButton3, {
714
916
  icon: "ph--plus--regular",
715
917
  iconOnly: true,
716
918
  label: t("button add"),
717
- onClick: () => {
718
- const newValues = [
719
- ...arrayValues,
720
- SimpleType.getDefaultValue(type)
721
- ];
722
- inputProps.onValueChange(name, type, newValues);
723
- }
919
+ onClick: handleAdd
724
920
  })));
725
921
  };
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", {
922
+
923
+ // packages/ui/react-ui-form/src/components/Form/factory.tsx
924
+ import { FormatEnum } from "@dxos/echo-schema";
925
+ var getInputComponent = (type, format) => {
926
+ switch (format) {
927
+ case FormatEnum.GeoPoint:
928
+ return GeoPointInput;
929
+ case FormatEnum.Markdown:
930
+ return MarkdownInput;
931
+ }
932
+ switch (type) {
933
+ case "string":
934
+ return TextInput;
935
+ case "number":
936
+ return NumberInput;
937
+ case "boolean":
938
+ return BooleanInput;
939
+ }
940
+ };
941
+
942
+ // packages/ui/react-ui-form/src/components/Form/FormContent.tsx
943
+ var FormField = ({ property, path, readonly, inline, lookupComponent, Custom }) => {
944
+ const inputProps = useInputProps(path);
945
+ const { ast, name, type, format, title, description, options, examples, array } = property;
946
+ const label = useMemo4(() => title ?? pipe2(name, capitalize2), [
947
+ title,
948
+ name
949
+ ]);
950
+ const placeholder = useMemo4(() => examples?.length ? `Example: "${examples[0]}"` : description, [
951
+ examples,
952
+ description
953
+ ]);
954
+ const FoundComponent = lookupComponent?.({
955
+ prop: name,
956
+ schema: S.make(ast),
957
+ inputProps: {
958
+ type,
959
+ format,
960
+ label,
961
+ disabled: readonly,
962
+ placeholder,
963
+ ...inputProps
964
+ }
965
+ });
966
+ if (FoundComponent) {
967
+ return /* @__PURE__ */ React10.createElement("div", {
968
+ role: "none"
969
+ }, FoundComponent);
970
+ }
971
+ const jsonPath = createJsonPath3(path ?? []);
972
+ const CustomComponent = Custom?.[jsonPath];
973
+ if (CustomComponent) {
974
+ return /* @__PURE__ */ React10.createElement(CustomComponent, {
975
+ type,
976
+ format,
977
+ label,
978
+ inputOnly: inline,
979
+ placeholder,
980
+ disabled: readonly,
981
+ ...inputProps
982
+ });
983
+ }
984
+ if (array) {
985
+ return /* @__PURE__ */ React10.createElement(ArrayField, {
986
+ property,
987
+ path,
988
+ inputProps,
989
+ readonly,
990
+ Custom
991
+ });
992
+ }
993
+ const InputComponent = getInputComponent(type, format);
994
+ if (InputComponent) {
995
+ return /* @__PURE__ */ React10.createElement("div", {
996
+ role: "none"
997
+ }, /* @__PURE__ */ React10.createElement(InputComponent, {
998
+ type,
999
+ format,
1000
+ label,
1001
+ inputOnly: inline,
1002
+ placeholder,
1003
+ disabled: readonly,
1004
+ ...inputProps
1005
+ }));
1006
+ }
1007
+ if (options) {
1008
+ return /* @__PURE__ */ React10.createElement("div", {
1009
+ role: "none"
1010
+ }, /* @__PURE__ */ React10.createElement(SelectInput, {
1011
+ type,
1012
+ format,
1013
+ disabled: readonly,
1014
+ inputOnly: inline,
1015
+ label,
1016
+ options: options.map((option) => ({
1017
+ value: option,
1018
+ label: String(option)
1019
+ })),
1020
+ placeholder,
1021
+ ...inputProps
1022
+ }));
1023
+ }
1024
+ if (type === "object") {
1025
+ const baseNode = findNode2(ast, isDiscriminatedUnion2);
1026
+ const typeLiteral = baseNode ? getDiscriminatedType2(baseNode, inputProps.getValue()) : findNode2(ast, AST3.isTypeLiteral);
1027
+ if (typeLiteral) {
1028
+ return /* @__PURE__ */ React10.createElement("div", {
1029
+ role: "none"
1030
+ }, !inline && /* @__PURE__ */ React10.createElement("div", null, label), /* @__PURE__ */ React10.createElement(FormFields, {
1031
+ schema: S.make(typeLiteral),
1032
+ path,
1033
+ readonly,
1034
+ Custom
1035
+ }));
1036
+ }
1037
+ }
1038
+ return null;
1039
+ };
1040
+ var FormFields = ({ schema, path, filter, sort, readonly, lookupComponent, Custom }) => {
1041
+ const values = useFormValues(path);
1042
+ const properties = useMemo4(() => {
1043
+ const props = getSchemaProperties2(schema.ast, values);
1044
+ const filtered = filter ? filter(props) : props;
1045
+ return sort ? filtered.sort((a, b) => sort.indexOf(a.name) - sort.indexOf(b.name)) : filtered;
1046
+ }, [
1047
+ schema,
1048
+ values,
1049
+ filter,
1050
+ sort
1051
+ ]);
1052
+ return /* @__PURE__ */ React10.createElement("div", {
1053
+ role: "form",
1054
+ className: mx6("flex flex-col w-full gap-2")
1055
+ }, properties.map((property) => {
1056
+ return /* @__PURE__ */ React10.createElement(FormField, {
1057
+ key: property.name,
1058
+ property,
1059
+ path: [
1060
+ ...path ?? [],
1061
+ property.name
1062
+ ],
1063
+ readonly,
1064
+ lookupComponent,
1065
+ Custom
1066
+ });
1067
+ }).filter(isNotFalsy));
1068
+ };
1069
+
1070
+ // packages/ui/react-ui-form/src/components/Form/Form.tsx
1071
+ var Form = ({ classNames, schema, values: initialValues, path = [], autoFocus, readonly, filter, sort, autoSave, testId, onValuesChanged, onValidate, onSave, onCancel, lookupComponent, Custom }) => {
1072
+ const formRef = useRef3(null);
1073
+ const onValid = useMemo5(() => autoSave ? onSave : void 0, [
1074
+ autoSave,
1075
+ onSave
1076
+ ]);
1077
+ useEffect6(() => {
1078
+ if (autoFocus && formRef.current) {
1079
+ const input = formRef.current.querySelector("input");
1080
+ if (input) {
1081
+ input.focus();
1082
+ }
1083
+ }
1084
+ }, [
1085
+ autoFocus
1086
+ ]);
1087
+ return /* @__PURE__ */ React11.createElement(FormProvider, {
1088
+ formRef,
1089
+ schema,
1090
+ autoSave,
1091
+ initialValues,
1092
+ onValuesChanged,
1093
+ onValidate,
1094
+ onValid,
1095
+ onSave
1096
+ }, /* @__PURE__ */ React11.createElement("div", {
1097
+ ref: formRef,
732
1098
  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
1099
+ className: mx7("p-2", classNames),
1100
+ "data-testid": testId
1101
+ }, /* @__PURE__ */ React11.createElement(FormFields, {
1102
+ schema,
1103
+ path,
1104
+ readonly,
1105
+ filter,
1106
+ sort,
1107
+ lookupComponent,
1108
+ Custom
1109
+ }), (onCancel || onSave) && !autoSave && /* @__PURE__ */ React11.createElement(FormActions, {
1110
+ onCancel,
1111
+ readonly
746
1112
  })));
747
1113
  };
748
1114
 
749
1115
  // packages/ui/react-ui-form/src/components/FieldEditor/FieldEditor.tsx
750
1116
  var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-form/src/components/FieldEditor/FieldEditor.tsx";
751
1117
  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), [
1118
+ const { t } = useTranslation5(translationKey);
1119
+ const [props, setProps] = useState4(projection.getFieldProjection(field.id).props);
1120
+ useEffect7(() => setProps(projection.getFieldProjection(field.id).props), [
755
1121
  field,
756
1122
  projection
757
1123
  ]);
758
- const [schemas, setSchemas] = useState2([]);
759
- useEffect3(() => {
1124
+ const [schemas, setSchemas] = useState4([]);
1125
+ useEffect7(() => {
760
1126
  if (!registry) {
761
1127
  return;
762
1128
  }
@@ -774,17 +1140,17 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
774
1140
  }, [
775
1141
  registry
776
1142
  ]);
777
- const [referenceSchema, setReferenceSchema] = useState2();
778
- useEffect3(() => {
1143
+ const [referenceSchema, setReferenceSchema] = useState4();
1144
+ useEffect7(() => {
779
1145
  setReferenceSchema(schemas.find((schema) => schema.typename === props?.referenceSchema));
780
1146
  }, [
781
1147
  schemas,
782
1148
  props?.referenceSchema
783
1149
  ]);
784
- const [{ fieldSchema }, setFieldSchema] = useState2({
1150
+ const [{ fieldSchema }, setFieldSchema] = useState4({
785
1151
  fieldSchema: getFormatSchema(props?.format)
786
1152
  });
787
- const handleValuesChanged = useCallback2((_props) => {
1153
+ const handleValuesChanged = useCallback6((_props) => {
788
1154
  setFieldSchema((prev) => {
789
1155
  const fieldSchema2 = getFormatSchema(_props.format);
790
1156
  if (prev.fieldSchema === fieldSchema2) {
@@ -812,14 +1178,14 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
812
1178
  type
813
1179
  };
814
1180
  }
815
- return props2;
1181
+ return _props;
816
1182
  });
817
1183
  }, [
818
1184
  schemas,
819
1185
  props.format,
820
1186
  props.referenceSchema
821
1187
  ]);
822
- const handleValidate = useCallback2(({ property }) => {
1188
+ const handleValidate = useCallback6(({ property }) => {
823
1189
  if (property && view.fields.find((f) => f.path === property && f.path !== field.path)) {
824
1190
  return [
825
1191
  {
@@ -832,7 +1198,7 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
832
1198
  view.fields,
833
1199
  field
834
1200
  ]);
835
- const handleSave = useCallback2((props2) => {
1201
+ const handleSave = useCallback6((props2) => {
836
1202
  projection.setFieldProjection({
837
1203
  field,
838
1204
  props: props2
@@ -843,50 +1209,51 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
843
1209
  field,
844
1210
  onSave
845
1211
  ]);
846
- const handleCancel = useCallback2(() => {
1212
+ const handleCancel = useCallback6(() => {
847
1213
  requestAnimationFrame(() => onCancel?.());
848
1214
  onSave();
849
1215
  }, [
850
1216
  onSave
851
1217
  ]);
852
- const Custom = useMemo3(() => ({
853
- ["format"]: (props2) => /* @__PURE__ */ React7.createElement(SelectInput, {
1218
+ const Custom = useMemo6(() => ({
1219
+ ["format"]: (props2) => /* @__PURE__ */ React12.createElement(SelectInput, {
854
1220
  ...props2,
855
1221
  options: FormatEnums.filter((value) => value !== FormatEnum2.None).map((value) => ({
856
1222
  value,
857
1223
  label: t(`format ${value}`)
858
1224
  }))
859
1225
  }),
860
- ["referenceSchema"]: (props2) => /* @__PURE__ */ React7.createElement(SelectInput, {
1226
+ ["referenceSchema"]: (props2) => /* @__PURE__ */ React12.createElement(SelectInput, {
861
1227
  ...props2,
862
1228
  options: schemas.map((schema) => ({
863
1229
  value: schema.typename
864
1230
  }))
865
1231
  }),
866
- ["referencePath"]: (props2) => /* @__PURE__ */ React7.createElement(SelectInput, {
1232
+ ["referencePath"]: (props2) => /* @__PURE__ */ React12.createElement(SelectInput, {
867
1233
  ...props2,
868
- options: referenceSchema ? getSchemaProperties2(referenceSchema.ast).sort(sortProperties).map((p) => ({
1234
+ options: referenceSchema ? getSchemaProperties3(referenceSchema.ast).sort(sortProperties).map((p) => ({
869
1235
  value: p.name
870
1236
  })) : []
871
- })
1237
+ }),
1238
+ ["options"]: (props2) => /* @__PURE__ */ React12.createElement(SelectOptionInput, props2)
872
1239
  }), [
873
1240
  t,
874
1241
  schemas,
875
1242
  referenceSchema
876
1243
  ]);
877
- const propIsNotType = useCallback2((props2) => props2.filter((p) => p.name !== "type"), []);
1244
+ const propIsNotType = useCallback6((props2) => props2.filter((p) => p.name !== "type"), []);
878
1245
  if (!fieldSchema) {
879
- log3.warn("invalid format", {
1246
+ log2.warn("invalid format", {
880
1247
  props
881
1248
  }, {
882
1249
  F: __dxlog_file3,
883
- L: 173,
1250
+ L: 174,
884
1251
  S: void 0,
885
1252
  C: (f, a) => f(...a)
886
1253
  });
887
1254
  return null;
888
1255
  }
889
- return /* @__PURE__ */ React7.createElement(Form, {
1256
+ return /* @__PURE__ */ React12.createElement(Form, {
890
1257
  key: field.id,
891
1258
  autoFocus: true,
892
1259
  values: props,
@@ -905,30 +1272,29 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
905
1272
  };
906
1273
 
907
1274
  // 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";
1275
+ import React13, { useCallback as useCallback7, useMemo as useMemo7, useState as useState5 } from "react";
1276
+ import { AST as AST4, Format, S as S2 } from "@dxos/echo-schema";
1277
+ import { IconButton as IconButton4, useTranslation as useTranslation6 } from "@dxos/react-ui";
1278
+ import { List as List2 } from "@dxos/react-ui-list";
1279
+ import { ghostHover, inputTextLabel, mx as mx8 } from "@dxos/react-ui-theme";
913
1280
  import { FieldSchema, ViewProjection, VIEW_FIELD_LIMIT } from "@dxos/schema";
914
- import { arrayMove } from "@dxos/util";
915
1281
  var grid = "grid grid-cols-[32px_1fr_32px] min-bs-[2.5rem]";
916
1282
  var ViewMetaSchema = S2.Struct({
917
1283
  name: S2.String.annotations({
918
- [AST2.TitleAnnotationId]: "View"
1284
+ [AST4.TitleAnnotationId]: "View"
919
1285
  }),
920
- typename: Format.DXN.annotations({
921
- [AST2.TitleAnnotationId]: "Typename"
1286
+ typename: Format.URL.annotations({
1287
+ [AST4.TitleAnnotationId]: "Typename"
922
1288
  })
923
1289
  }).pipe(S2.mutable);
924
1290
  var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading = false, onTypenameChanged, onDelete }) => {
925
- const { t } = useTranslation4(translationKey);
926
- const projection = useMemo4(() => new ViewProjection(schema, view), [
1291
+ const { t } = useTranslation6(translationKey);
1292
+ const projection = useMemo7(() => new ViewProjection(schema, view), [
927
1293
  schema,
928
1294
  view
929
1295
  ]);
930
- const [field, setField] = useState3();
931
- const viewValues = useMemo4(() => {
1296
+ const [field, setField] = useState5();
1297
+ const viewValues = useMemo7(() => {
932
1298
  return {
933
1299
  name: view.name,
934
1300
  // TODO(burdon): Need to warn user of possible consequences of editing.
@@ -938,7 +1304,7 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
938
1304
  }, [
939
1305
  view
940
1306
  ]);
941
- const handleViewUpdate = useCallback3(({ name, typename }) => {
1307
+ const handleViewUpdate = useCallback7(({ name, typename }) => {
942
1308
  requestAnimationFrame(() => {
943
1309
  if (view.name !== name) {
944
1310
  view.name = name;
@@ -952,67 +1318,71 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
952
1318
  schema,
953
1319
  onTypenameChanged
954
1320
  ]);
955
- const handleSelect = useCallback3((field2) => {
1321
+ const handleSelect = useCallback7((field2) => {
956
1322
  setField((f) => f === field2 ? void 0 : field2);
957
1323
  }, []);
958
- const handleAdd = useCallback3(() => {
1324
+ const handleAdd = useCallback7(() => {
959
1325
  const field2 = projection.createFieldProjection();
960
1326
  setField(field2);
961
1327
  }, [
962
1328
  view
963
1329
  ]);
964
- const handleMove = useCallback3((fromIndex, toIndex) => {
965
- arrayMove(view.fields, fromIndex, toIndex);
1330
+ const handleMove = useCallback7((fromIndex, toIndex) => {
1331
+ const fields = [
1332
+ ...view.fields
1333
+ ];
1334
+ const [moved] = fields.splice(fromIndex, 1);
1335
+ fields.splice(toIndex, 0, moved);
1336
+ view.fields = fields;
966
1337
  }, [
967
1338
  view.fields
968
1339
  ]);
969
- const handleClose = useCallback3(() => setField(void 0), []);
970
- return /* @__PURE__ */ React8.createElement("div", {
1340
+ const handleClose = useCallback7(() => setField(void 0), []);
1341
+ return /* @__PURE__ */ React13.createElement("div", {
971
1342
  role: "none",
972
- className: mx3("flex flex-col w-full divide-y divide-separator", classNames)
973
- }, /* @__PURE__ */ React8.createElement(Form, {
974
- autoFocus: true,
1343
+ className: mx8("flex flex-col w-full divide-y divide-separator", classNames)
1344
+ }, /* @__PURE__ */ React13.createElement(Form, {
975
1345
  autoSave: true,
976
1346
  schema: ViewMetaSchema,
977
1347
  values: viewValues,
978
1348
  onSave: handleViewUpdate
979
- }), /* @__PURE__ */ React8.createElement("div", null, /* @__PURE__ */ React8.createElement("div", {
1349
+ }), /* @__PURE__ */ React13.createElement("div", null, /* @__PURE__ */ React13.createElement("div", {
980
1350
  role: "none",
981
1351
  className: "p-2"
982
- }, /* @__PURE__ */ React8.createElement("label", {
983
- className: mx3(inputTextLabel)
984
- }, t("fields label"))), /* @__PURE__ */ React8.createElement(List.Root, {
1352
+ }, /* @__PURE__ */ React13.createElement("label", {
1353
+ className: mx8(inputTextLabel)
1354
+ }, t("fields label"))), /* @__PURE__ */ React13.createElement(List2.Root, {
985
1355
  items: view.fields,
986
1356
  isItem: S2.is(FieldSchema),
987
1357
  getId: (field2) => field2.id,
988
1358
  onMove: handleMove
989
- }, ({ items: fields }) => /* @__PURE__ */ React8.createElement(React8.Fragment, null, showHeading && /* @__PURE__ */ React8.createElement("div", {
1359
+ }, ({ items: fields }) => /* @__PURE__ */ React13.createElement(React13.Fragment, null, showHeading && /* @__PURE__ */ React13.createElement("div", {
990
1360
  role: "heading",
991
1361
  className: grid
992
- }, /* @__PURE__ */ React8.createElement("div", null), /* @__PURE__ */ React8.createElement("div", {
1362
+ }, /* @__PURE__ */ React13.createElement("div", null), /* @__PURE__ */ React13.createElement("div", {
993
1363
  className: "flex items-center text-sm"
994
- }, t("field path label"))), /* @__PURE__ */ React8.createElement("div", {
1364
+ }, t("field path label"))), /* @__PURE__ */ React13.createElement("div", {
995
1365
  role: "list",
996
1366
  className: "flex flex-col w-full"
997
- }, fields?.map((field2) => /* @__PURE__ */ React8.createElement(List.Item, {
1367
+ }, fields?.map((field2) => /* @__PURE__ */ React13.createElement(List2.Item, {
998
1368
  key: field2.id,
999
1369
  item: field2,
1000
- classNames: mx3(grid, ghostHover, "cursor-pointer")
1001
- }, /* @__PURE__ */ React8.createElement(List.ItemDragHandle, null), /* @__PURE__ */ React8.createElement(List.ItemTitle, {
1370
+ classNames: mx8(grid, ghostHover, "cursor-pointer")
1371
+ }, /* @__PURE__ */ React13.createElement(List2.ItemDragHandle, null), /* @__PURE__ */ React13.createElement(List2.ItemTitle, {
1002
1372
  onClick: () => handleSelect(field2)
1003
- }, field2.path), /* @__PURE__ */ React8.createElement(List.ItemDeleteButton, {
1373
+ }, field2.path), /* @__PURE__ */ React13.createElement(List2.ItemDeleteButton, {
1004
1374
  disabled: view.fields.length <= 1,
1005
1375
  onClick: () => onDelete(field2.id)
1006
- }))))))), field && /* @__PURE__ */ React8.createElement(FieldEditor, {
1376
+ }))))))), field && /* @__PURE__ */ React13.createElement(FieldEditor, {
1007
1377
  key: field.id,
1008
1378
  view,
1009
1379
  projection,
1010
1380
  field,
1011
1381
  registry,
1012
1382
  onSave: handleClose
1013
- }), !readonly && !field && /* @__PURE__ */ React8.createElement("div", {
1383
+ }), !readonly && !field && /* @__PURE__ */ React13.createElement("div", {
1014
1384
  className: "flex p-2 justify-center"
1015
- }, /* @__PURE__ */ React8.createElement(IconButton2, {
1385
+ }, /* @__PURE__ */ React13.createElement(IconButton4, {
1016
1386
  icon: "ph--plus--regular",
1017
1387
  label: t("button add property"),
1018
1388
  onClick: handleAdd,
@@ -1022,7 +1392,7 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
1022
1392
  };
1023
1393
 
1024
1394
  // packages/ui/react-ui-form/src/util/field.ts
1025
- import { FormatEnum as FormatEnum3, TypeEnum } from "@dxos/echo-schema";
1395
+ import { FormatEnum as FormatEnum3, GeoLocation as GeoLocation2, TypeEnum } from "@dxos/echo-schema";
1026
1396
  var parseValue = ({ type, format, value }) => {
1027
1397
  if (value === void 0 || value === null || value === "") {
1028
1398
  return void 0;
@@ -1076,6 +1446,26 @@ var parseValue = ({ type, format, value }) => {
1076
1446
  const date = new Date(value);
1077
1447
  return isNaN(date.getTime()) ? null : date;
1078
1448
  }
1449
+ case FormatEnum3.GeoPoint: {
1450
+ if (typeof value === "string") {
1451
+ const cleaned = value.replace(/[^0-9.,-]+/g, "");
1452
+ const parts = cleaned.split(",");
1453
+ if (parts.length === 2) {
1454
+ const lat = parseFloat(parts[0]);
1455
+ const long = parseFloat(parts[1]);
1456
+ if (!isNaN(lat) && !isNaN(long)) {
1457
+ return GeoLocation2.toGeoPoint({
1458
+ latitude: lat,
1459
+ longitude: long
1460
+ });
1461
+ }
1462
+ }
1463
+ }
1464
+ return [
1465
+ 0,
1466
+ 0
1467
+ ];
1468
+ }
1079
1469
  default: {
1080
1470
  return value;
1081
1471
  }
@@ -1134,7 +1524,7 @@ var pathNotUniqueError = (path) => ({
1134
1524
  });
1135
1525
 
1136
1526
  // packages/ui/react-ui-form/src/util/formatting.ts
1137
- import { FormatEnum as FormatEnum4, TypeEnum as TypeEnum2 } from "@dxos/echo-schema";
1527
+ import { FormatEnum as FormatEnum4, GeoLocation as GeoLocation3, TypeEnum as TypeEnum2 } from "@dxos/echo-schema";
1138
1528
  var formatForDisplay = ({ type, format, value, locale = void 0 }) => {
1139
1529
  const formatScalar = (type2) => {
1140
1530
  switch (type2) {
@@ -1187,6 +1577,16 @@ var formatForDisplay = ({ type, format, value, locale = void 0 }) => {
1187
1577
  return date.toLocaleTimeString(locale);
1188
1578
  }
1189
1579
  }
1580
+ case FormatEnum4.GeoPoint: {
1581
+ if (value === null || value === void 0) {
1582
+ return "";
1583
+ }
1584
+ if (Array.isArray(value) && value.length >= 2 && value.every(Number.isFinite)) {
1585
+ const { latitude, longitude } = GeoLocation3.fromGeoPoint(value);
1586
+ return `${latitude},${longitude}`;
1587
+ }
1588
+ return String(value);
1589
+ }
1190
1590
  default: {
1191
1591
  if (value === null || value === "undefined") {
1192
1592
  return "";
@@ -1235,6 +1635,16 @@ var formatForEditing = ({ type, format, value, locale = void 0 }) => {
1235
1635
  const date = new Date(value);
1236
1636
  return date.toISOString().split("T")[1].split(".")[0];
1237
1637
  }
1638
+ case FormatEnum4.GeoPoint: {
1639
+ if (value === null || value === void 0) {
1640
+ return "";
1641
+ }
1642
+ if (Array.isArray(value) && value.length >= 2 && value.every(Number.isFinite)) {
1643
+ const { latitude, longitude } = GeoLocation3.fromGeoPoint(value);
1644
+ return `${latitude},${longitude}`;
1645
+ }
1646
+ return String(value);
1647
+ }
1238
1648
  default: {
1239
1649
  return String(value);
1240
1650
  }
@@ -1242,14 +1652,16 @@ var formatForEditing = ({ type, format, value, locale = void 0 }) => {
1242
1652
  };
1243
1653
  export {
1244
1654
  BooleanInput,
1655
+ DeprecatedFormContainer,
1245
1656
  DeprecatedFormInput,
1246
1657
  FieldEditor,
1247
1658
  Form,
1248
- FormActions,
1249
1659
  GeoPointInput,
1250
1660
  InputHeader,
1661
+ MarkdownInput,
1251
1662
  NumberInput,
1252
1663
  SelectInput,
1664
+ SelectOptionInput,
1253
1665
  TextInput,
1254
1666
  TupleInput,
1255
1667
  ViewEditor,
@@ -1260,6 +1672,7 @@ export {
1260
1672
  parseValue,
1261
1673
  pathNotUniqueError,
1262
1674
  translations_default as translations,
1263
- useForm
1675
+ useForm,
1676
+ useFormValues
1264
1677
  };
1265
1678
  //# sourceMappingURL=index.mjs.map