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