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