@dxos/react-ui-form 0.7.5-main.9d26e3a → 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 (86) hide show
  1. package/dist/lib/browser/index.mjs +1020 -571
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node/index.cjs +1046 -608
  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 +1020 -571
  8. package/dist/lib/node-esm/index.mjs.map +4 -4
  9. package/dist/lib/node-esm/meta.json +1 -1
  10. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +1 -2
  11. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  12. package/dist/types/src/components/Form/ArrayField.d.ts +15 -0
  13. package/dist/types/src/components/Form/ArrayField.d.ts.map +1 -0
  14. package/dist/types/src/components/Form/Defaults.d.ts +6 -7
  15. package/dist/types/src/components/Form/Defaults.d.ts.map +1 -1
  16. package/dist/types/src/components/Form/Form.d.ts +13 -16
  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 +2 -2
  45. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  46. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  47. package/dist/types/src/components/deprecated/Form.d.ts +1 -2
  48. package/dist/types/src/components/deprecated/Form.d.ts.map +1 -1
  49. package/dist/types/src/components/deprecated/Form.stories.d.ts +1 -2
  50. package/dist/types/src/components/deprecated/Form.stories.d.ts.map +1 -1
  51. package/dist/types/src/components/deprecated/FormInput.d.ts +6 -1
  52. package/dist/types/src/components/deprecated/FormInput.d.ts.map +1 -1
  53. package/dist/types/src/components/testing.d.ts +3 -3
  54. package/dist/types/src/components/testing.d.ts.map +1 -1
  55. package/dist/types/src/hooks/useForm.d.ts +11 -11
  56. package/dist/types/src/hooks/useForm.d.ts.map +1 -1
  57. package/dist/types/src/translations.d.ts +6 -0
  58. package/dist/types/src/translations.d.ts.map +1 -1
  59. package/dist/types/src/util/field.d.ts.map +1 -1
  60. package/dist/types/src/util/formatting.d.ts.map +1 -1
  61. package/dist/types/tsconfig.tsbuildinfo +1 -1
  62. package/package.json +29 -27
  63. package/src/components/FieldEditor/FieldEditor.stories.tsx +1 -1
  64. package/src/components/FieldEditor/FieldEditor.tsx +58 -40
  65. package/src/components/Form/ArrayField.tsx +111 -0
  66. package/src/components/Form/Defaults.tsx +78 -34
  67. package/src/components/Form/Form.stories.tsx +15 -5
  68. package/src/components/Form/Form.tsx +51 -327
  69. package/src/components/Form/FormActions.tsx +41 -0
  70. package/src/components/Form/FormContent.tsx +170 -0
  71. package/src/components/Form/FormContext.tsx +92 -0
  72. package/src/components/Form/Input.tsx +5 -6
  73. package/src/components/Form/custom/GeoPointInput.tsx +44 -23
  74. package/src/components/Form/custom/SelectOptionsInput.stories.tsx +58 -0
  75. package/src/components/Form/custom/SelectOptionsInput.tsx +190 -0
  76. package/src/components/Form/custom/TupleInput.tsx +5 -9
  77. package/src/components/Form/custom/index.ts +1 -0
  78. package/src/components/Form/factory.tsx +6 -7
  79. package/src/components/Form/index.ts +3 -0
  80. package/src/components/ViewEditor/ViewEditor.stories.tsx +25 -4
  81. package/src/components/ViewEditor/ViewEditor.tsx +16 -8
  82. package/src/components/deprecated/FormInput.tsx +20 -7
  83. package/src/hooks/useForm.ts +63 -49
  84. package/src/translations.ts +9 -1
  85. package/src/util/field.ts +19 -1
  86. 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, 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",
@@ -423,320 +280,849 @@ var createKeySet = (obj, value) => {
423
280
  [key]: value
424
281
  }), {});
425
282
  };
426
- var flatMap = (errors) => {
427
- return errors.reduce((result, { path, message }) => {
428
- if (!(path in result)) {
429
- result[path] = message;
283
+ var flatMap = (errors) => {
284
+ return errors.reduce((result, { path, message }) => {
285
+ const jsonPath = fromEffectValidationPath(path);
286
+ if (!(jsonPath in result)) {
287
+ result[jsonPath] = message;
288
+ }
289
+ return result;
290
+ }, {});
291
+ };
292
+
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
+ }
333
+ };
334
+ const formElement = formRef.current;
335
+ formElement.addEventListener("keydown", handleKeyDown);
336
+ return () => formElement.removeEventListener("keydown", handleKeyDown);
337
+ }, [
338
+ formRef,
339
+ form.canSave,
340
+ form.formIsValid,
341
+ form.handleSave,
342
+ autoSave
343
+ ]);
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
+ }
413
+ }
414
+ }, [
415
+ getValue3,
416
+ onValueChange,
417
+ type
418
+ ]);
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
511
+ ];
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);
537
+ }
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);
554
+ }
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`;
430
734
  }
431
- return result;
432
- }, {});
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));
433
764
  };
434
765
 
435
766
  // 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";
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";
437
819
  var padding = "px-2";
438
- var Form = ({ classNames, schema, values: initialValues, path = [], readonly, filter, sort, autoSave, testId, onValuesChanged, onValidate, onSave, onCancel, 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
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
+ ]
450
832
  });
451
- const properties = useMemo2(() => {
452
- const props = getSchemaProperties(schema.ast, values);
453
- const filtered = filter ? filter(props) : props;
454
- const findIndex = (props2, prop) => {
455
- const idx = props2.findIndex((p) => p === prop);
456
- return idx === -1 ? Infinity : idx;
457
- };
458
- return sort ? filtered.sort(({ name: a }, { name: b }) => findIndex(sort, a) - findIndex(sort, b)) : filtered;
833
+ const label = title ?? pipe(name, capitalize);
834
+ const tupleType = findNode(ast, AST2.isTupleType);
835
+ const elementType = tupleType?.rest[0]?.type;
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 {};
841
+ }
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
+ }
459
853
  }, [
460
- schema,
461
- filter,
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,
462
867
  values
463
868
  ]);
464
- useEffect2(() => {
465
- if (errors && Object.keys(errors).length) {
466
- log2.warn("validation", {
467
- errors
468
- }, {
469
- F: __dxlog_file2,
470
- L: 99,
471
- S: void 0,
472
- C: (f, a) => f(...a)
473
- });
474
- }
869
+ const handleRemove = useCallback5((index) => {
870
+ const newValues = values.filter((_, i) => i !== index);
871
+ inputProps.onValueChange(type, newValues);
475
872
  }, [
476
- errors
873
+ type,
874
+ inputProps,
875
+ values
477
876
  ]);
478
- return /* @__PURE__ */ React6.createElement("div", {
479
- role: "form",
480
- className: mx2("flex flex-col w-full gap-2 py-2", classNames),
481
- "data-testid": testId
482
- }, properties.map((property) => {
483
- const { ast, name, type, format, title, description, examples, options, array } = property;
484
- const key = [
485
- ...path,
486
- name
487
- ];
488
- const label = title ?? pipe(name, capitalize);
489
- const placeholder = examples?.length ? `Example: "${examples[0]}"` : description;
490
- let InputComponent = Custom?.[key.join(".")];
491
- if (!InputComponent) {
492
- if (options) {
493
- return /* @__PURE__ */ React6.createElement("div", {
494
- key: name,
495
- role: "none",
496
- className: padding
497
- }, /* @__PURE__ */ React6.createElement(SelectInput, {
498
- type,
499
- format,
500
- property: name,
501
- disabled: readonly,
502
- label,
503
- options: options.map((option) => ({
504
- value: option,
505
- label: String(option)
506
- })),
507
- placeholder,
508
- ...inputProps
509
- }));
510
- }
511
- InputComponent = getInputComponent(type, format);
512
- if (array) {
513
- return /* @__PURE__ */ React6.createElement(ArrayField, {
514
- key: name,
515
- name,
516
- type,
517
- array: true,
518
- label,
519
- ast,
520
- values,
521
- format,
522
- readonly,
523
- placeholder,
524
- inputProps,
525
- Custom
526
- });
527
- }
528
- }
529
- if (!InputComponent) {
530
- if (type === "object") {
531
- const baseNode = findNode(ast, isDiscriminatedUnion);
532
- const typeLiteral = baseNode ? getDiscriminatedType(baseNode, values[name]) : findNode(ast, AST.isTypeLiteral);
533
- if (typeLiteral) {
534
- const schema2 = S.make(typeLiteral);
535
- return /* @__PURE__ */ React6.createElement("div", {
536
- key: name,
537
- role: "none"
538
- }, /* @__PURE__ */ React6.createElement("div", {
539
- className: padding
540
- }, label), /* @__PURE__ */ React6.createElement(Form, {
541
- schema: schema2,
542
- path: key,
543
- values: values[name] ?? {},
544
- onValuesChanged: (childValues) => {
545
- inputProps.onValueChange(name, "object", childValues);
546
- },
547
- Custom
548
- }));
549
- }
550
- }
551
- log2("no renderer for property", {
552
- name,
553
- type
554
- }, {
555
- F: __dxlog_file2,
556
- L: 184,
557
- S: void 0,
558
- C: (f, a) => f(...a)
559
- });
560
- return null;
561
- }
562
- return /* @__PURE__ */ React6.createElement("div", {
563
- key: name,
564
- role: "none",
565
- className: padding
566
- }, /* @__PURE__ */ React6.createElement(InputComponent, {
567
- type,
568
- format,
569
- property: name,
570
- disabled: readonly,
571
- label,
572
- placeholder,
573
- ...inputProps
574
- }));
575
- }).filter(isNotFalsy), (onCancel || onSave) && !autoSave && /* @__PURE__ */ React6.createElement(FormActions, {
576
- onCancel,
577
- onSubmit: handleSave,
578
- canSubmit: canSave,
579
- readonly
580
- }));
581
- };
582
- var ArrayField = ({ name, type, label, ast, values, format, readonly, placeholder, inputProps, Custom }) => {
583
- const { t } = useTranslation2(translationKey);
584
- const arrayValues = values[name] ?? [];
585
- const key = [
586
- name
587
- ];
588
- const tupleType = findNode(ast, AST.isTupleType);
589
- const elementType = tupleType?.rest[0]?.type;
590
- if (type === "object" && elementType) {
591
- const baseNode = findNode(elementType, isDiscriminatedUnion);
592
- const typeLiteral = baseNode ? getDiscriminatedType(baseNode, values[name]) : findNode(elementType, AST.isTypeLiteral);
593
- if (typeLiteral) {
594
- const schema = S.make(typeLiteral);
595
- return /* @__PURE__ */ React6.createElement("div", {
596
- key: name,
597
- role: "none",
598
- className: mx2(padding)
599
- }, /* @__PURE__ */ React6.createElement(InputHeader, null, label), /* @__PURE__ */ React6.createElement("div", {
600
- role: "none",
601
- className: "flex flex-col gap-1"
602
- }, arrayValues.map((value, index) => {
603
- return /* @__PURE__ */ React6.createElement("div", {
604
- key: index,
605
- role: "none",
606
- className: "flex items-center gap-1"
607
- }, /* @__PURE__ */ React6.createElement("div", {
608
- role: "none",
609
- className: "flex-1"
610
- }, /* @__PURE__ */ React6.createElement(Form, {
611
- schema,
612
- path: [
613
- ...key,
614
- `[${index}]`
615
- ],
616
- values: value,
617
- onValuesChanged: (childValues) => {
618
- const newValues = [
619
- ...arrayValues
620
- ];
621
- newValues[index] = childValues;
622
- inputProps.onValueChange(name, "object", newValues);
623
- },
624
- Custom
625
- })), /* @__PURE__ */ React6.createElement(IconButton, {
626
- icon: "ph--trash--regular",
627
- iconOnly: true,
628
- label: t("button remove"),
629
- onClick: () => {
630
- const newValues = arrayValues.filter((_, i) => i !== index);
631
- inputProps.onValueChange(name, type, newValues);
632
- }
633
- }));
634
- })), /* @__PURE__ */ React6.createElement("div", {
635
- role: "none",
636
- className: "flex justify-between items-center plb-1"
637
- }, /* @__PURE__ */ React6.createElement(IconButton, {
638
- icon: "ph--plus--regular",
639
- iconOnly: true,
640
- label: t("button add"),
641
- onClick: () => {
642
- const newValues = [
643
- ...arrayValues,
644
- {}
645
- ];
646
- inputProps.onValueChange(name, type, newValues);
647
- }
648
- })));
649
- }
650
- return /* @__PURE__ */ React6.createElement("div", {
651
- key: name,
652
- role: "none"
653
- }, "Nested form not supported in arrays, yet.");
654
- }
655
- const InputComponent = getInputComponent(type, format);
656
- if (!InputComponent) {
877
+ if (!elementType) {
657
878
  return null;
658
879
  }
659
- return /* @__PURE__ */ React6.createElement("div", {
880
+ return /* @__PURE__ */ React9.createElement("div", {
660
881
  role: "none",
661
- key: "meta-input",
662
- className: mx2(padding)
663
- }, /* @__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", {
664
884
  role: "none",
665
885
  className: "flex flex-col gap-1"
666
- }, arrayValues.map((_, index) => /* @__PURE__ */ React6.createElement("div", {
886
+ }, values.map((_value, index) => /* @__PURE__ */ React9.createElement("div", {
667
887
  key: index,
668
888
  role: "none",
669
- className: "flex items-start gap-1"
670
- }, /* @__PURE__ */ React6.createElement("div", {
889
+ className: "flex items-center gap-1"
890
+ }, /* @__PURE__ */ React9.createElement("div", {
671
891
  role: "none",
672
892
  className: "flex-1"
673
- }, /* @__PURE__ */ React6.createElement(InputComponent, {
674
- type,
675
- format,
676
- label,
677
- inputOnly: true,
678
- property: `${name}[${index}]`,
679
- disabled: readonly,
680
- placeholder,
681
- ...inputProps
682
- })), /* @__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, {
683
908
  icon: "ph--trash--regular",
684
909
  iconOnly: true,
685
910
  label: t("button remove"),
686
- onClick: () => {
687
- const newValues = arrayValues.filter((_2, i) => i !== index);
688
- inputProps.onValueChange(name, type, newValues);
689
- }
690
- })))), /* @__PURE__ */ React6.createElement("div", {
911
+ onClick: () => handleRemove(index)
912
+ })))), /* @__PURE__ */ React9.createElement("div", {
691
913
  role: "none",
692
914
  className: "flex justify-between items-center plb-1"
693
- }, /* @__PURE__ */ React6.createElement(IconButton, {
915
+ }, /* @__PURE__ */ React9.createElement(IconButton3, {
694
916
  icon: "ph--plus--regular",
695
917
  iconOnly: true,
696
918
  label: t("button add"),
697
- onClick: () => {
698
- const newValues = [
699
- ...arrayValues,
700
- SimpleType.getDefaultValue(type)
701
- ];
702
- inputProps.onValueChange(name, type, newValues);
703
- }
919
+ onClick: handleAdd
704
920
  })));
705
921
  };
706
- var FormActions = ({ onCancel, onSubmit, canSubmit, readonly }) => {
707
- const { t } = useTranslation2(translationKey);
708
- return /* @__PURE__ */ React6.createElement("div", {
709
- role: "none",
710
- className: "flex justify-center"
711
- }, /* @__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,
712
1098
  role: "none",
713
- className: mx2(onCancel && !readonly && "grid grid-cols-2 gap-2")
714
- }, onCancel && !readonly && /* @__PURE__ */ React6.createElement(IconButton, {
715
- "data-testid": "cancel-button",
716
- icon: "ph--x--regular",
717
- label: t("button cancel"),
718
- onClick: onCancel
719
- }), onSubmit && /* @__PURE__ */ React6.createElement(IconButton, {
720
- type: "submit",
721
- "data-testid": "save-button",
722
- disabled: !canSubmit,
723
- icon: "ph--check--regular",
724
- label: t("button save"),
725
- 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
726
1112
  })));
727
1113
  };
728
1114
 
729
1115
  // packages/ui/react-ui-form/src/components/FieldEditor/FieldEditor.tsx
730
1116
  var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-form/src/components/FieldEditor/FieldEditor.tsx";
731
1117
  var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
732
- const { t } = useTranslation3(translationKey);
733
- const [props, setProps] = useState2(projection.getFieldProjection(field.id).props);
734
- 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), [
735
1121
  field,
736
1122
  projection
737
1123
  ]);
738
- const [schemas, setSchemas] = useState2([]);
739
- useEffect3(() => {
1124
+ const [schemas, setSchemas] = useState4([]);
1125
+ useEffect7(() => {
740
1126
  if (!registry) {
741
1127
  return;
742
1128
  }
@@ -754,17 +1140,17 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
754
1140
  }, [
755
1141
  registry
756
1142
  ]);
757
- const [schema, setSchema] = useState2();
758
- useEffect3(() => {
759
- setSchema(schemas.find((schema2) => schema2.typename === props?.referenceSchema));
1143
+ const [referenceSchema, setReferenceSchema] = useState4();
1144
+ useEffect7(() => {
1145
+ setReferenceSchema(schemas.find((schema) => schema.typename === props?.referenceSchema));
760
1146
  }, [
761
1147
  schemas,
762
1148
  props?.referenceSchema
763
1149
  ]);
764
- const [{ fieldSchema }, setFieldSchema] = useState2({
1150
+ const [{ fieldSchema }, setFieldSchema] = useState4({
765
1151
  fieldSchema: getFormatSchema(props?.format)
766
1152
  });
767
- const handleValuesChanged = useCallback2((_props) => {
1153
+ const handleValuesChanged = useCallback6((_props) => {
768
1154
  setFieldSchema((prev) => {
769
1155
  const fieldSchema2 = getFormatSchema(_props.format);
770
1156
  if (prev.fieldSchema === fieldSchema2) {
@@ -774,9 +1160,15 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
774
1160
  fieldSchema: fieldSchema2
775
1161
  };
776
1162
  });
777
- if (_props.referenceSchema !== props.referenceSchema) {
778
- setSchema(schemas.find((schema2) => schema2.typename === _props.referenceSchema));
779
- }
1163
+ setReferenceSchema((prev) => {
1164
+ if (_props.referenceSchema !== prev?.typename) {
1165
+ const newSchema = schemas.find((schema) => schema.typename === _props.referenceSchema);
1166
+ if (newSchema) {
1167
+ return newSchema;
1168
+ }
1169
+ }
1170
+ return prev;
1171
+ });
780
1172
  setProps((props2) => {
781
1173
  const type = formatToType[_props.format];
782
1174
  if (props2.type !== type) {
@@ -786,14 +1178,14 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
786
1178
  type
787
1179
  };
788
1180
  }
789
- return props2;
1181
+ return _props;
790
1182
  });
791
1183
  }, [
792
1184
  schemas,
793
1185
  props.format,
794
1186
  props.referenceSchema
795
1187
  ]);
796
- const handleValidate = useCallback2(({ property }) => {
1188
+ const handleValidate = useCallback6(({ property }) => {
797
1189
  if (property && view.fields.find((f) => f.path === property && f.path !== field.path)) {
798
1190
  return [
799
1191
  {
@@ -806,7 +1198,7 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
806
1198
  view.fields,
807
1199
  field
808
1200
  ]);
809
- const handleSave = useCallback2((props2) => {
1201
+ const handleSave = useCallback6((props2) => {
810
1202
  projection.setFieldProjection({
811
1203
  field,
812
1204
  props: props2
@@ -817,29 +1209,56 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
817
1209
  field,
818
1210
  onSave
819
1211
  ]);
820
- const handleCancel = useCallback2(() => {
1212
+ const handleCancel = useCallback6(() => {
821
1213
  requestAnimationFrame(() => onCancel?.());
822
1214
  onSave();
823
1215
  }, [
824
1216
  onSave
825
1217
  ]);
1218
+ const Custom = useMemo6(() => ({
1219
+ ["format"]: (props2) => /* @__PURE__ */ React12.createElement(SelectInput, {
1220
+ ...props2,
1221
+ options: FormatEnums.filter((value) => value !== FormatEnum2.None).map((value) => ({
1222
+ value,
1223
+ label: t(`format ${value}`)
1224
+ }))
1225
+ }),
1226
+ ["referenceSchema"]: (props2) => /* @__PURE__ */ React12.createElement(SelectInput, {
1227
+ ...props2,
1228
+ options: schemas.map((schema) => ({
1229
+ value: schema.typename
1230
+ }))
1231
+ }),
1232
+ ["referencePath"]: (props2) => /* @__PURE__ */ React12.createElement(SelectInput, {
1233
+ ...props2,
1234
+ options: referenceSchema ? getSchemaProperties3(referenceSchema.ast).sort(sortProperties).map((p) => ({
1235
+ value: p.name
1236
+ })) : []
1237
+ }),
1238
+ ["options"]: (props2) => /* @__PURE__ */ React12.createElement(SelectOptionInput, props2)
1239
+ }), [
1240
+ t,
1241
+ schemas,
1242
+ referenceSchema
1243
+ ]);
1244
+ const propIsNotType = useCallback6((props2) => props2.filter((p) => p.name !== "type"), []);
826
1245
  if (!fieldSchema) {
827
- log3.warn("invalid format", {
1246
+ log2.warn("invalid format", {
828
1247
  props
829
1248
  }, {
830
1249
  F: __dxlog_file3,
831
- L: 126,
1250
+ L: 174,
832
1251
  S: void 0,
833
1252
  C: (f, a) => f(...a)
834
1253
  });
835
1254
  return null;
836
1255
  }
837
- return /* @__PURE__ */ React7.createElement(Form, {
1256
+ return /* @__PURE__ */ React12.createElement(Form, {
838
1257
  key: field.id,
839
1258
  autoFocus: true,
840
1259
  values: props,
841
1260
  schema: fieldSchema,
842
- filter: (props2) => props2.filter((p) => p.name !== "type"),
1261
+ filter: propIsNotType,
843
1262
  sort: [
844
1263
  "property",
845
1264
  "format"
@@ -848,55 +1267,34 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
848
1267
  onValidate: handleValidate,
849
1268
  onSave: handleSave,
850
1269
  onCancel: handleCancel,
851
- Custom: {
852
- ["format"]: (props2) => /* @__PURE__ */ React7.createElement(SelectInput, {
853
- ...props2,
854
- options: FormatEnums.filter((value) => value !== FormatEnum2.None).map((value) => ({
855
- value,
856
- label: t(`format ${value}`)
857
- }))
858
- }),
859
- ["referenceSchema"]: (props2) => /* @__PURE__ */ React7.createElement(SelectInput, {
860
- ...props2,
861
- options: schemas.map((schema2) => ({
862
- value: schema2.typename
863
- }))
864
- }),
865
- ["referencePath"]: (props2) => /* @__PURE__ */ React7.createElement(SelectInput, {
866
- ...props2,
867
- options: schema ? getSchemaProperties2(schema.ast).sort(sortProperties).map((p) => ({
868
- value: p.name
869
- })) : []
870
- })
871
- }
1270
+ Custom
872
1271
  });
873
1272
  };
874
1273
 
875
1274
  // packages/ui/react-ui-form/src/components/ViewEditor/ViewEditor.tsx
876
- import React8, { useCallback as useCallback3, useMemo as useMemo3, useState as useState3 } from "react";
877
- import { AST as AST2, Format, S as S2 } from "@dxos/echo-schema";
878
- import { IconButton as IconButton2, useTranslation as useTranslation4 } from "@dxos/react-ui";
879
- import { List } from "@dxos/react-ui-list";
880
- 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";
881
1280
  import { FieldSchema, ViewProjection, VIEW_FIELD_LIMIT } from "@dxos/schema";
882
- import { arrayMove } from "@dxos/util";
883
1281
  var grid = "grid grid-cols-[32px_1fr_32px] min-bs-[2.5rem]";
884
1282
  var ViewMetaSchema = S2.Struct({
885
1283
  name: S2.String.annotations({
886
- [AST2.TitleAnnotationId]: "View"
1284
+ [AST4.TitleAnnotationId]: "View"
887
1285
  }),
888
- typename: Format.DXN.annotations({
889
- [AST2.TitleAnnotationId]: "Typename"
1286
+ typename: Format.URL.annotations({
1287
+ [AST4.TitleAnnotationId]: "Typename"
890
1288
  })
891
1289
  }).pipe(S2.mutable);
892
- var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading = false, onDelete }) => {
893
- const { t } = useTranslation4(translationKey);
894
- const projection = useMemo3(() => new ViewProjection(schema, view), [
1290
+ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading = false, onTypenameChanged, onDelete }) => {
1291
+ const { t } = useTranslation6(translationKey);
1292
+ const projection = useMemo7(() => new ViewProjection(schema, view), [
895
1293
  schema,
896
1294
  view
897
1295
  ]);
898
- const [field, setField] = useState3();
899
- const viewValues = useMemo3(() => {
1296
+ const [field, setField] = useState5();
1297
+ const viewValues = useMemo7(() => {
900
1298
  return {
901
1299
  name: view.name,
902
1300
  // TODO(burdon): Need to warn user of possible consequences of editing.
@@ -906,77 +1304,85 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
906
1304
  }, [
907
1305
  view
908
1306
  ]);
909
- const handleViewUpdate = useCallback3(({ name, typename }) => {
1307
+ const handleViewUpdate = useCallback7(({ name, typename }) => {
910
1308
  requestAnimationFrame(() => {
911
- view.name = name;
912
- view.query.type = typename;
913
- schema.updateTypename(typename);
1309
+ if (view.name !== name) {
1310
+ view.name = name;
1311
+ }
1312
+ if (view.query.type !== typename) {
1313
+ onTypenameChanged(typename);
1314
+ }
914
1315
  });
915
1316
  }, [
916
1317
  view,
917
- schema
1318
+ schema,
1319
+ onTypenameChanged
918
1320
  ]);
919
- const handleSelect = useCallback3((field2) => {
1321
+ const handleSelect = useCallback7((field2) => {
920
1322
  setField((f) => f === field2 ? void 0 : field2);
921
1323
  }, []);
922
- const handleAdd = useCallback3(() => {
1324
+ const handleAdd = useCallback7(() => {
923
1325
  const field2 = projection.createFieldProjection();
924
1326
  setField(field2);
925
1327
  }, [
926
1328
  view
927
1329
  ]);
928
- const handleMove = useCallback3((fromIndex, toIndex) => {
929
- 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;
930
1337
  }, [
931
1338
  view.fields
932
1339
  ]);
933
- const handleClose = useCallback3(() => setField(void 0), []);
934
- return /* @__PURE__ */ React8.createElement("div", {
1340
+ const handleClose = useCallback7(() => setField(void 0), []);
1341
+ return /* @__PURE__ */ React13.createElement("div", {
935
1342
  role: "none",
936
- className: mx3("flex flex-col w-full divide-y divide-separator", classNames)
937
- }, /* @__PURE__ */ React8.createElement(Form, {
938
- autoFocus: true,
1343
+ className: mx8("flex flex-col w-full divide-y divide-separator", classNames)
1344
+ }, /* @__PURE__ */ React13.createElement(Form, {
939
1345
  autoSave: true,
940
1346
  schema: ViewMetaSchema,
941
1347
  values: viewValues,
942
1348
  onSave: handleViewUpdate
943
- }), /* @__PURE__ */ React8.createElement("div", null, /* @__PURE__ */ React8.createElement("div", {
1349
+ }), /* @__PURE__ */ React13.createElement("div", null, /* @__PURE__ */ React13.createElement("div", {
944
1350
  role: "none",
945
1351
  className: "p-2"
946
- }, /* @__PURE__ */ React8.createElement("label", {
947
- className: mx3(inputTextLabel)
948
- }, 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, {
949
1355
  items: view.fields,
950
1356
  isItem: S2.is(FieldSchema),
951
1357
  getId: (field2) => field2.id,
952
1358
  onMove: handleMove
953
- }, ({ 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", {
954
1360
  role: "heading",
955
1361
  className: grid
956
- }, /* @__PURE__ */ React8.createElement("div", null), /* @__PURE__ */ React8.createElement("div", {
1362
+ }, /* @__PURE__ */ React13.createElement("div", null), /* @__PURE__ */ React13.createElement("div", {
957
1363
  className: "flex items-center text-sm"
958
- }, t("field path label"))), /* @__PURE__ */ React8.createElement("div", {
1364
+ }, t("field path label"))), /* @__PURE__ */ React13.createElement("div", {
959
1365
  role: "list",
960
1366
  className: "flex flex-col w-full"
961
- }, fields?.map((field2) => /* @__PURE__ */ React8.createElement(List.Item, {
1367
+ }, fields?.map((field2) => /* @__PURE__ */ React13.createElement(List2.Item, {
962
1368
  key: field2.id,
963
1369
  item: field2,
964
- classNames: mx3(grid, ghostHover, "cursor-pointer")
965
- }, /* @__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, {
966
1372
  onClick: () => handleSelect(field2)
967
- }, field2.path), /* @__PURE__ */ React8.createElement(List.ItemDeleteButton, {
1373
+ }, field2.path), /* @__PURE__ */ React13.createElement(List2.ItemDeleteButton, {
968
1374
  disabled: view.fields.length <= 1,
969
1375
  onClick: () => onDelete(field2.id)
970
- }))))))), field && /* @__PURE__ */ React8.createElement(FieldEditor, {
1376
+ }))))))), field && /* @__PURE__ */ React13.createElement(FieldEditor, {
971
1377
  key: field.id,
972
1378
  view,
973
1379
  projection,
974
1380
  field,
975
1381
  registry,
976
1382
  onSave: handleClose
977
- }), !readonly && !field && /* @__PURE__ */ React8.createElement("div", {
1383
+ }), !readonly && !field && /* @__PURE__ */ React13.createElement("div", {
978
1384
  className: "flex p-2 justify-center"
979
- }, /* @__PURE__ */ React8.createElement(IconButton2, {
1385
+ }, /* @__PURE__ */ React13.createElement(IconButton4, {
980
1386
  icon: "ph--plus--regular",
981
1387
  label: t("button add property"),
982
1388
  onClick: handleAdd,
@@ -986,7 +1392,7 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
986
1392
  };
987
1393
 
988
1394
  // packages/ui/react-ui-form/src/util/field.ts
989
- import { FormatEnum as FormatEnum3, TypeEnum } from "@dxos/echo-schema";
1395
+ import { FormatEnum as FormatEnum3, GeoLocation as GeoLocation2, TypeEnum } from "@dxos/echo-schema";
990
1396
  var parseValue = ({ type, format, value }) => {
991
1397
  if (value === void 0 || value === null || value === "") {
992
1398
  return void 0;
@@ -1040,6 +1446,26 @@ var parseValue = ({ type, format, value }) => {
1040
1446
  const date = new Date(value);
1041
1447
  return isNaN(date.getTime()) ? null : date;
1042
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
+ }
1043
1469
  default: {
1044
1470
  return value;
1045
1471
  }
@@ -1098,7 +1524,7 @@ var pathNotUniqueError = (path) => ({
1098
1524
  });
1099
1525
 
1100
1526
  // packages/ui/react-ui-form/src/util/formatting.ts
1101
- 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";
1102
1528
  var formatForDisplay = ({ type, format, value, locale = void 0 }) => {
1103
1529
  const formatScalar = (type2) => {
1104
1530
  switch (type2) {
@@ -1151,6 +1577,16 @@ var formatForDisplay = ({ type, format, value, locale = void 0 }) => {
1151
1577
  return date.toLocaleTimeString(locale);
1152
1578
  }
1153
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
+ }
1154
1590
  default: {
1155
1591
  if (value === null || value === "undefined") {
1156
1592
  return "";
@@ -1199,6 +1635,16 @@ var formatForEditing = ({ type, format, value, locale = void 0 }) => {
1199
1635
  const date = new Date(value);
1200
1636
  return date.toISOString().split("T")[1].split(".")[0];
1201
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
+ }
1202
1648
  default: {
1203
1649
  return String(value);
1204
1650
  }
@@ -1206,14 +1652,16 @@ var formatForEditing = ({ type, format, value, locale = void 0 }) => {
1206
1652
  };
1207
1653
  export {
1208
1654
  BooleanInput,
1655
+ DeprecatedFormContainer,
1209
1656
  DeprecatedFormInput,
1210
1657
  FieldEditor,
1211
1658
  Form,
1212
- FormActions,
1213
1659
  GeoPointInput,
1214
1660
  InputHeader,
1661
+ MarkdownInput,
1215
1662
  NumberInput,
1216
1663
  SelectInput,
1664
+ SelectOptionInput,
1217
1665
  TextInput,
1218
1666
  TupleInput,
1219
1667
  ViewEditor,
@@ -1224,6 +1672,7 @@ export {
1224
1672
  parseValue,
1225
1673
  pathNotUniqueError,
1226
1674
  translations_default as translations,
1227
- useForm
1675
+ useForm,
1676
+ useFormValues
1228
1677
  };
1229
1678
  //# sourceMappingURL=index.mjs.map