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

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