@dxos/react-ui-form 0.7.5-main.499c70c → 0.7.5-main.6a330ac

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