@dxos/react-ui-form 0.7.5-main.499c70c → 0.7.5-main.5ae2ba8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/dist/lib/browser/index.mjs +1054 -575
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node/index.cjs +1115 -647
  5. package/dist/lib/node/index.cjs.map +4 -4
  6. package/dist/lib/node/meta.json +1 -1
  7. package/dist/lib/node-esm/index.mjs +1054 -575
  8. package/dist/lib/node-esm/index.mjs.map +4 -4
  9. package/dist/lib/node-esm/meta.json +1 -1
  10. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +1 -2
  11. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  12. package/dist/types/src/components/Form/ArrayField.d.ts +15 -0
  13. package/dist/types/src/components/Form/ArrayField.d.ts.map +1 -0
  14. package/dist/types/src/components/Form/Defaults.d.ts +6 -7
  15. package/dist/types/src/components/Form/Defaults.d.ts.map +1 -1
  16. package/dist/types/src/components/Form/Form.d.ts +13 -16
  17. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  18. package/dist/types/src/components/Form/Form.stories.d.ts +2 -1
  19. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  20. package/dist/types/src/components/Form/FormActions.d.ts +6 -0
  21. package/dist/types/src/components/Form/FormActions.d.ts.map +1 -0
  22. package/dist/types/src/components/Form/FormContent.d.ts +25 -0
  23. package/dist/types/src/components/Form/FormContent.d.ts.map +1 -0
  24. package/dist/types/src/components/Form/FormContext.d.ts +23 -0
  25. package/dist/types/src/components/Form/FormContext.d.ts.map +1 -0
  26. package/dist/types/src/components/Form/Input.d.ts +7 -8
  27. package/dist/types/src/components/Form/Input.d.ts.map +1 -1
  28. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +1 -3
  29. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +1 -1
  30. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +3 -0
  31. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +1 -0
  32. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +7 -0
  33. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +1 -0
  34. package/dist/types/src/components/Form/custom/TupleInput.d.ts +2 -4
  35. package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +1 -1
  36. package/dist/types/src/components/Form/custom/index.d.ts +1 -0
  37. package/dist/types/src/components/Form/custom/index.d.ts.map +1 -1
  38. package/dist/types/src/components/Form/factory.d.ts +2 -2
  39. package/dist/types/src/components/Form/factory.d.ts.map +1 -1
  40. package/dist/types/src/components/Form/index.d.ts +2 -0
  41. package/dist/types/src/components/Form/index.d.ts.map +1 -1
  42. package/dist/types/src/components/ViewEditor/Test.stories.d.ts +1 -2
  43. package/dist/types/src/components/ViewEditor/Test.stories.d.ts.map +1 -1
  44. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +2 -2
  45. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  46. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  47. package/dist/types/src/components/deprecated/Form.d.ts +1 -2
  48. package/dist/types/src/components/deprecated/Form.d.ts.map +1 -1
  49. package/dist/types/src/components/deprecated/Form.stories.d.ts +1 -2
  50. package/dist/types/src/components/deprecated/Form.stories.d.ts.map +1 -1
  51. package/dist/types/src/components/deprecated/FormInput.d.ts +6 -1
  52. package/dist/types/src/components/deprecated/FormInput.d.ts.map +1 -1
  53. package/dist/types/src/components/testing.d.ts +3 -3
  54. package/dist/types/src/components/testing.d.ts.map +1 -1
  55. package/dist/types/src/hooks/useForm.d.ts +11 -11
  56. package/dist/types/src/hooks/useForm.d.ts.map +1 -1
  57. package/dist/types/src/translations.d.ts +6 -0
  58. package/dist/types/src/translations.d.ts.map +1 -1
  59. package/dist/types/src/util/field.d.ts.map +1 -1
  60. package/dist/types/src/util/formatting.d.ts.map +1 -1
  61. package/dist/types/tsconfig.tsbuildinfo +1 -1
  62. package/package.json +29 -27
  63. package/src/components/FieldEditor/FieldEditor.stories.tsx +1 -1
  64. package/src/components/FieldEditor/FieldEditor.tsx +58 -40
  65. package/src/components/Form/ArrayField.tsx +111 -0
  66. package/src/components/Form/Defaults.tsx +78 -34
  67. package/src/components/Form/Form.stories.tsx +15 -5
  68. package/src/components/Form/Form.tsx +51 -327
  69. package/src/components/Form/FormActions.tsx +41 -0
  70. package/src/components/Form/FormContent.tsx +170 -0
  71. package/src/components/Form/FormContext.tsx +96 -0
  72. package/src/components/Form/Input.tsx +5 -6
  73. package/src/components/Form/custom/GeoPointInput.tsx +44 -23
  74. package/src/components/Form/custom/SelectOptionsInput.stories.tsx +58 -0
  75. package/src/components/Form/custom/SelectOptionsInput.tsx +190 -0
  76. package/src/components/Form/custom/TupleInput.tsx +5 -9
  77. package/src/components/Form/custom/index.ts +1 -0
  78. package/src/components/Form/factory.tsx +6 -7
  79. package/src/components/Form/index.ts +3 -0
  80. package/src/components/ViewEditor/ViewEditor.stories.tsx +25 -4
  81. package/src/components/ViewEditor/ViewEditor.tsx +16 -8
  82. package/src/components/deprecated/FormInput.tsx +20 -7
  83. package/src/hooks/useForm.ts +116 -79
  84. package/src/translations.ts +9 -1
  85. package/src/util/field.ts +19 -1
  86. package/src/util/formatting.ts +29 -1
@@ -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 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,320 +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, 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: 99,
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
- 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) {
909
+ if (!elementType) {
659
910
  return null;
660
911
  }
661
- return /* @__PURE__ */ React6.createElement("div", {
912
+ return /* @__PURE__ */ React9.createElement("div", {
662
913
  role: "none",
663
- key: "meta-input",
664
- className: mx2(padding)
665
- }, /* @__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", {
666
916
  role: "none",
667
917
  className: "flex flex-col gap-1"
668
- }, arrayValues.map((_, index) => /* @__PURE__ */ React6.createElement("div", {
918
+ }, values.map((_value, index) => /* @__PURE__ */ React9.createElement("div", {
669
919
  key: index,
670
920
  role: "none",
671
- className: "flex items-start gap-1"
672
- }, /* @__PURE__ */ React6.createElement("div", {
921
+ className: "flex items-center gap-1"
922
+ }, /* @__PURE__ */ React9.createElement("div", {
673
923
  role: "none",
674
924
  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, {
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, {
685
940
  icon: "ph--trash--regular",
686
941
  iconOnly: true,
687
942
  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", {
943
+ onClick: () => handleRemove(index)
944
+ })))), /* @__PURE__ */ React9.createElement("div", {
693
945
  role: "none",
694
946
  className: "flex justify-between items-center plb-1"
695
- }, /* @__PURE__ */ React6.createElement(IconButton, {
947
+ }, /* @__PURE__ */ React9.createElement(IconButton3, {
696
948
  icon: "ph--plus--regular",
697
949
  iconOnly: true,
698
950
  label: t("button add"),
699
- onClick: () => {
700
- const newValues = [
701
- ...arrayValues,
702
- SimpleType.getDefaultValue(type)
703
- ];
704
- inputProps.onValueChange(name, type, newValues);
705
- }
951
+ onClick: handleAdd
706
952
  })));
707
953
  };
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", {
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,
714
1130
  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
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
728
1144
  })));
729
1145
  };
730
1146
 
731
1147
  // packages/ui/react-ui-form/src/components/FieldEditor/FieldEditor.tsx
732
1148
  var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-form/src/components/FieldEditor/FieldEditor.tsx";
733
1149
  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), [
1150
+ const { t } = useTranslation5(translationKey);
1151
+ const [props, setProps] = useState4(projection.getFieldProjection(field.id).props);
1152
+ useEffect7(() => setProps(projection.getFieldProjection(field.id).props), [
737
1153
  field,
738
1154
  projection
739
1155
  ]);
740
- const [schemas, setSchemas] = useState2([]);
741
- useEffect3(() => {
1156
+ const [schemas, setSchemas] = useState4([]);
1157
+ useEffect7(() => {
742
1158
  if (!registry) {
743
1159
  return;
744
1160
  }
@@ -756,17 +1172,17 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
756
1172
  }, [
757
1173
  registry
758
1174
  ]);
759
- const [schema, setSchema] = useState2();
760
- useEffect3(() => {
761
- setSchema(schemas.find((schema2) => schema2.typename === props?.referenceSchema));
1175
+ const [referenceSchema, setReferenceSchema] = useState4();
1176
+ useEffect7(() => {
1177
+ setReferenceSchema(schemas.find((schema) => schema.typename === props?.referenceSchema));
762
1178
  }, [
763
1179
  schemas,
764
1180
  props?.referenceSchema
765
1181
  ]);
766
- const [{ fieldSchema }, setFieldSchema] = useState2({
1182
+ const [{ fieldSchema }, setFieldSchema] = useState4({
767
1183
  fieldSchema: getFormatSchema(props?.format)
768
1184
  });
769
- const handleValuesChanged = useCallback2((_props) => {
1185
+ const handleValuesChanged = useCallback6((_props) => {
770
1186
  setFieldSchema((prev) => {
771
1187
  const fieldSchema2 = getFormatSchema(_props.format);
772
1188
  if (prev.fieldSchema === fieldSchema2) {
@@ -776,9 +1192,15 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
776
1192
  fieldSchema: fieldSchema2
777
1193
  };
778
1194
  });
779
- if (_props.referenceSchema !== props.referenceSchema) {
780
- setSchema(schemas.find((schema2) => schema2.typename === _props.referenceSchema));
781
- }
1195
+ setReferenceSchema((prev) => {
1196
+ if (_props.referenceSchema !== prev?.typename) {
1197
+ const newSchema = schemas.find((schema) => schema.typename === _props.referenceSchema);
1198
+ if (newSchema) {
1199
+ return newSchema;
1200
+ }
1201
+ }
1202
+ return prev;
1203
+ });
782
1204
  setProps((props2) => {
783
1205
  const type = formatToType[_props.format];
784
1206
  if (props2.type !== type) {
@@ -788,14 +1210,14 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
788
1210
  type
789
1211
  };
790
1212
  }
791
- return props2;
1213
+ return _props;
792
1214
  });
793
1215
  }, [
794
1216
  schemas,
795
1217
  props.format,
796
1218
  props.referenceSchema
797
1219
  ]);
798
- const handleValidate = useCallback2(({ property }) => {
1220
+ const handleValidate = useCallback6(({ property }) => {
799
1221
  if (property && view.fields.find((f) => f.path === property && f.path !== field.path)) {
800
1222
  return [
801
1223
  {
@@ -808,7 +1230,7 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
808
1230
  view.fields,
809
1231
  field
810
1232
  ]);
811
- const handleSave = useCallback2((props2) => {
1233
+ const handleSave = useCallback6((props2) => {
812
1234
  projection.setFieldProjection({
813
1235
  field,
814
1236
  props: props2
@@ -819,29 +1241,56 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
819
1241
  field,
820
1242
  onSave
821
1243
  ]);
822
- const handleCancel = useCallback2(() => {
1244
+ const handleCancel = useCallback6(() => {
823
1245
  requestAnimationFrame(() => onCancel?.());
824
1246
  onSave();
825
1247
  }, [
826
1248
  onSave
827
1249
  ]);
1250
+ const Custom = useMemo7(() => ({
1251
+ ["format"]: (props2) => /* @__PURE__ */ React12.createElement(SelectInput, {
1252
+ ...props2,
1253
+ options: FormatEnums.filter((value) => value !== FormatEnum2.None).map((value) => ({
1254
+ value,
1255
+ label: t(`format ${value}`)
1256
+ }))
1257
+ }),
1258
+ ["referenceSchema"]: (props2) => /* @__PURE__ */ React12.createElement(SelectInput, {
1259
+ ...props2,
1260
+ options: schemas.map((schema) => ({
1261
+ value: schema.typename
1262
+ }))
1263
+ }),
1264
+ ["referencePath"]: (props2) => /* @__PURE__ */ React12.createElement(SelectInput, {
1265
+ ...props2,
1266
+ options: referenceSchema ? getSchemaProperties3(referenceSchema.ast).sort(sortProperties).map((p) => ({
1267
+ value: p.name
1268
+ })) : []
1269
+ }),
1270
+ ["options"]: (props2) => /* @__PURE__ */ React12.createElement(SelectOptionInput, props2)
1271
+ }), [
1272
+ t,
1273
+ schemas,
1274
+ referenceSchema
1275
+ ]);
1276
+ const propIsNotType = useCallback6((props2) => props2.filter((p) => p.name !== "type"), []);
828
1277
  if (!fieldSchema) {
829
- log3.warn("invalid format", {
1278
+ log2.warn("invalid format", {
830
1279
  props
831
1280
  }, {
832
1281
  F: __dxlog_file3,
833
- L: 126,
1282
+ L: 174,
834
1283
  S: void 0,
835
1284
  C: (f, a) => f(...a)
836
1285
  });
837
1286
  return null;
838
1287
  }
839
- return /* @__PURE__ */ React7.createElement(Form, {
1288
+ return /* @__PURE__ */ React12.createElement(Form, {
840
1289
  key: field.id,
841
1290
  autoFocus: true,
842
1291
  values: props,
843
1292
  schema: fieldSchema,
844
- filter: (props2) => props2.filter((p) => p.name !== "type"),
1293
+ filter: propIsNotType,
845
1294
  sort: [
846
1295
  "property",
847
1296
  "format"
@@ -850,55 +1299,34 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
850
1299
  onValidate: handleValidate,
851
1300
  onSave: handleSave,
852
1301
  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
- }
1302
+ Custom
874
1303
  });
875
1304
  };
876
1305
 
877
1306
  // 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";
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";
883
1312
  import { FieldSchema, ViewProjection, VIEW_FIELD_LIMIT } from "@dxos/schema";
884
- import { arrayMove } from "@dxos/util";
885
1313
  var grid = "grid grid-cols-[32px_1fr_32px] min-bs-[2.5rem]";
886
1314
  var ViewMetaSchema = S2.Struct({
887
1315
  name: S2.String.annotations({
888
- [AST2.TitleAnnotationId]: "View"
1316
+ [AST4.TitleAnnotationId]: "View"
889
1317
  }),
890
- typename: Format.DXN.annotations({
891
- [AST2.TitleAnnotationId]: "Typename"
1318
+ typename: Format.URL.annotations({
1319
+ [AST4.TitleAnnotationId]: "Typename"
892
1320
  })
893
1321
  }).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), [
1322
+ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading = false, onTypenameChanged, onDelete }) => {
1323
+ const { t } = useTranslation6(translationKey);
1324
+ const projection = useMemo8(() => new ViewProjection(schema, view), [
897
1325
  schema,
898
1326
  view
899
1327
  ]);
900
- const [field, setField] = useState3();
901
- const viewValues = useMemo3(() => {
1328
+ const [field, setField] = useState5();
1329
+ const viewValues = useMemo8(() => {
902
1330
  return {
903
1331
  name: view.name,
904
1332
  // TODO(burdon): Need to warn user of possible consequences of editing.
@@ -908,77 +1336,85 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
908
1336
  }, [
909
1337
  view
910
1338
  ]);
911
- const handleViewUpdate = useCallback3(({ name, typename }) => {
1339
+ const handleViewUpdate = useCallback7(({ name, typename }) => {
912
1340
  requestAnimationFrame(() => {
913
- view.name = name;
914
- view.query.type = typename;
915
- schema.updateTypename(typename);
1341
+ if (view.name !== name) {
1342
+ view.name = name;
1343
+ }
1344
+ if (view.query.type !== typename) {
1345
+ onTypenameChanged(typename);
1346
+ }
916
1347
  });
917
1348
  }, [
918
1349
  view,
919
- schema
1350
+ schema,
1351
+ onTypenameChanged
920
1352
  ]);
921
- const handleSelect = useCallback3((field2) => {
1353
+ const handleSelect = useCallback7((field2) => {
922
1354
  setField((f) => f === field2 ? void 0 : field2);
923
1355
  }, []);
924
- const handleAdd = useCallback3(() => {
1356
+ const handleAdd = useCallback7(() => {
925
1357
  const field2 = projection.createFieldProjection();
926
1358
  setField(field2);
927
1359
  }, [
928
1360
  view
929
1361
  ]);
930
- const handleMove = useCallback3((fromIndex, toIndex) => {
931
- 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;
932
1369
  }, [
933
1370
  view.fields
934
1371
  ]);
935
- const handleClose = useCallback3(() => setField(void 0), []);
936
- return /* @__PURE__ */ React8.createElement("div", {
1372
+ const handleClose = useCallback7(() => setField(void 0), []);
1373
+ return /* @__PURE__ */ React13.createElement("div", {
937
1374
  role: "none",
938
- className: mx3("flex flex-col w-full divide-y divide-separator", classNames)
939
- }, /* @__PURE__ */ React8.createElement(Form, {
940
- autoFocus: true,
1375
+ className: mx8("flex flex-col w-full divide-y divide-separator", classNames)
1376
+ }, /* @__PURE__ */ React13.createElement(Form, {
941
1377
  autoSave: true,
942
1378
  schema: ViewMetaSchema,
943
1379
  values: viewValues,
944
1380
  onSave: handleViewUpdate
945
- }), /* @__PURE__ */ React8.createElement("div", null, /* @__PURE__ */ React8.createElement("div", {
1381
+ }), /* @__PURE__ */ React13.createElement("div", null, /* @__PURE__ */ React13.createElement("div", {
946
1382
  role: "none",
947
1383
  className: "p-2"
948
- }, /* @__PURE__ */ React8.createElement("label", {
949
- className: mx3(inputTextLabel)
950
- }, 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, {
951
1387
  items: view.fields,
952
1388
  isItem: S2.is(FieldSchema),
953
1389
  getId: (field2) => field2.id,
954
1390
  onMove: handleMove
955
- }, ({ 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", {
956
1392
  role: "heading",
957
1393
  className: grid
958
- }, /* @__PURE__ */ React8.createElement("div", null), /* @__PURE__ */ React8.createElement("div", {
1394
+ }, /* @__PURE__ */ React13.createElement("div", null), /* @__PURE__ */ React13.createElement("div", {
959
1395
  className: "flex items-center text-sm"
960
- }, t("field path label"))), /* @__PURE__ */ React8.createElement("div", {
1396
+ }, t("field path label"))), /* @__PURE__ */ React13.createElement("div", {
961
1397
  role: "list",
962
1398
  className: "flex flex-col w-full"
963
- }, fields?.map((field2) => /* @__PURE__ */ React8.createElement(List.Item, {
1399
+ }, fields?.map((field2) => /* @__PURE__ */ React13.createElement(List2.Item, {
964
1400
  key: field2.id,
965
1401
  item: field2,
966
- classNames: mx3(grid, ghostHover, "cursor-pointer")
967
- }, /* @__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, {
968
1404
  onClick: () => handleSelect(field2)
969
- }, field2.path), /* @__PURE__ */ React8.createElement(List.ItemDeleteButton, {
1405
+ }, field2.path), /* @__PURE__ */ React13.createElement(List2.ItemDeleteButton, {
970
1406
  disabled: view.fields.length <= 1,
971
1407
  onClick: () => onDelete(field2.id)
972
- }))))))), field && /* @__PURE__ */ React8.createElement(FieldEditor, {
1408
+ }))))))), field && /* @__PURE__ */ React13.createElement(FieldEditor, {
973
1409
  key: field.id,
974
1410
  view,
975
1411
  projection,
976
1412
  field,
977
1413
  registry,
978
1414
  onSave: handleClose
979
- }), !readonly && !field && /* @__PURE__ */ React8.createElement("div", {
1415
+ }), !readonly && !field && /* @__PURE__ */ React13.createElement("div", {
980
1416
  className: "flex p-2 justify-center"
981
- }, /* @__PURE__ */ React8.createElement(IconButton2, {
1417
+ }, /* @__PURE__ */ React13.createElement(IconButton4, {
982
1418
  icon: "ph--plus--regular",
983
1419
  label: t("button add property"),
984
1420
  onClick: handleAdd,
@@ -988,7 +1424,7 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
988
1424
  };
989
1425
 
990
1426
  // packages/ui/react-ui-form/src/util/field.ts
991
- import { FormatEnum as FormatEnum3, TypeEnum } from "@dxos/echo-schema";
1427
+ import { FormatEnum as FormatEnum3, GeoLocation as GeoLocation2, TypeEnum } from "@dxos/echo-schema";
992
1428
  var parseValue = ({ type, format, value }) => {
993
1429
  if (value === void 0 || value === null || value === "") {
994
1430
  return void 0;
@@ -1042,6 +1478,26 @@ var parseValue = ({ type, format, value }) => {
1042
1478
  const date = new Date(value);
1043
1479
  return isNaN(date.getTime()) ? null : date;
1044
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
+ }
1045
1501
  default: {
1046
1502
  return value;
1047
1503
  }
@@ -1100,7 +1556,7 @@ var pathNotUniqueError = (path) => ({
1100
1556
  });
1101
1557
 
1102
1558
  // packages/ui/react-ui-form/src/util/formatting.ts
1103
- 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";
1104
1560
  var formatForDisplay = ({ type, format, value, locale = void 0 }) => {
1105
1561
  const formatScalar = (type2) => {
1106
1562
  switch (type2) {
@@ -1153,6 +1609,16 @@ var formatForDisplay = ({ type, format, value, locale = void 0 }) => {
1153
1609
  return date.toLocaleTimeString(locale);
1154
1610
  }
1155
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
+ }
1156
1622
  default: {
1157
1623
  if (value === null || value === "undefined") {
1158
1624
  return "";
@@ -1201,6 +1667,16 @@ var formatForEditing = ({ type, format, value, locale = void 0 }) => {
1201
1667
  const date = new Date(value);
1202
1668
  return date.toISOString().split("T")[1].split(".")[0];
1203
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
+ }
1204
1680
  default: {
1205
1681
  return String(value);
1206
1682
  }
@@ -1208,14 +1684,16 @@ var formatForEditing = ({ type, format, value, locale = void 0 }) => {
1208
1684
  };
1209
1685
  export {
1210
1686
  BooleanInput,
1687
+ DeprecatedFormContainer,
1211
1688
  DeprecatedFormInput,
1212
1689
  FieldEditor,
1213
1690
  Form,
1214
- FormActions,
1215
1691
  GeoPointInput,
1216
1692
  InputHeader,
1693
+ MarkdownInput,
1217
1694
  NumberInput,
1218
1695
  SelectInput,
1696
+ SelectOptionInput,
1219
1697
  TextInput,
1220
1698
  TupleInput,
1221
1699
  ViewEditor,
@@ -1226,6 +1704,7 @@ export {
1226
1704
  parseValue,
1227
1705
  pathNotUniqueError,
1228
1706
  translations_default as translations,
1229
- useForm
1707
+ useForm,
1708
+ useFormValues
1230
1709
  };
1231
1710
  //# sourceMappingURL=index.mjs.map