@dxos/react-ui-form 0.7.4 → 0.7.5-labs.5f04cf6

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 (65) hide show
  1. package/dist/lib/browser/index.mjs +574 -496
  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 +629 -565
  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 +574 -496
  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 +2 -2
  11. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  12. package/dist/types/src/components/Form/ArrayField.d.ts +16 -0
  13. package/dist/types/src/components/Form/ArrayField.d.ts.map +1 -0
  14. package/dist/types/src/components/Form/Defaults.d.ts +5 -6
  15. package/dist/types/src/components/Form/Defaults.d.ts.map +1 -1
  16. package/dist/types/src/components/Form/Form.d.ts +13 -17
  17. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  18. package/dist/types/src/components/Form/Form.stories.d.ts +7 -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 +7 -0
  21. package/dist/types/src/components/Form/FormActions.d.ts.map +1 -0
  22. package/dist/types/src/components/Form/FormContent.d.ts +26 -0
  23. package/dist/types/src/components/Form/FormContent.d.ts.map +1 -0
  24. package/dist/types/src/components/Form/FormContext.d.ts +20 -0
  25. package/dist/types/src/components/Form/FormContext.d.ts.map +1 -0
  26. package/dist/types/src/components/Form/Input.d.ts +5 -6
  27. package/dist/types/src/components/Form/Input.d.ts.map +1 -1
  28. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +4 -0
  29. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +1 -0
  30. package/dist/types/src/components/Form/custom/TupleInput.d.ts +6 -0
  31. package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +1 -0
  32. package/dist/types/src/components/Form/custom/index.d.ts +3 -0
  33. package/dist/types/src/components/Form/custom/index.d.ts.map +1 -0
  34. package/dist/types/src/components/Form/factory.d.ts +2 -2
  35. package/dist/types/src/components/Form/factory.d.ts.map +1 -1
  36. package/dist/types/src/components/Form/index.d.ts +2 -2
  37. package/dist/types/src/components/Form/index.d.ts.map +1 -1
  38. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +6 -5
  39. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  40. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  41. package/dist/types/src/hooks/useForm.d.ts +13 -15
  42. package/dist/types/src/hooks/useForm.d.ts.map +1 -1
  43. package/dist/types/tsconfig.tsbuildinfo +1 -0
  44. package/package.json +26 -26
  45. package/src/components/FieldEditor/FieldEditor.stories.tsx +2 -2
  46. package/src/components/FieldEditor/FieldEditor.tsx +75 -50
  47. package/src/components/Form/ArrayField.tsx +111 -0
  48. package/src/components/Form/Defaults.tsx +20 -33
  49. package/src/components/Form/Form.stories.tsx +48 -6
  50. package/src/components/Form/Form.tsx +39 -333
  51. package/src/components/Form/FormActions.tsx +41 -0
  52. package/src/components/Form/FormContent.tsx +155 -0
  53. package/src/components/Form/FormContext.tsx +48 -0
  54. package/src/components/Form/Input.tsx +5 -6
  55. package/src/components/Form/custom/GeoPointInput.tsx +44 -0
  56. package/src/components/Form/custom/TupleInput.tsx +49 -0
  57. package/src/components/Form/custom/index.ts +6 -0
  58. package/src/components/Form/factory.tsx +4 -7
  59. package/src/components/Form/index.ts +3 -2
  60. package/src/components/ViewEditor/ViewEditor.stories.tsx +30 -8
  61. package/src/components/ViewEditor/ViewEditor.tsx +13 -8
  62. package/src/hooks/useForm.ts +67 -78
  63. package/dist/types/src/components/Form/Custom.d.ts +0 -5
  64. package/dist/types/src/components/Form/Custom.d.ts.map +0 -1
  65. package/src/components/Form/Custom.tsx +0 -65
@@ -80,27 +80,16 @@ var DeprecatedFormInput = ({ label, description, secondary, children }) => {
80
80
  };
81
81
 
82
82
  // packages/ui/react-ui-form/src/components/FieldEditor/FieldEditor.tsx
83
- import React6, { useCallback as useCallback2, useEffect as useEffect3, useState as useState2 } from "react";
83
+ import React11, { useCallback as useCallback3, useEffect as useEffect2, useMemo as useMemo5, useState as useState2 } from "react";
84
84
  import { FormatEnum as FormatEnum2, FormatEnums, formatToType } from "@dxos/echo-schema";
85
- import { log as log3 } from "@dxos/log";
86
- import { useTranslation as useTranslation3 } from "@dxos/react-ui";
87
- import { getFormatSchema, getSchemaProperties as getSchemaProperties2, sortProperties } from "@dxos/schema";
88
-
89
- // packages/ui/react-ui-form/src/components/Form/Form.tsx
90
- import { pipe } from "effect";
91
- import { capitalize } from "effect/String";
92
- import React5, { useEffect as useEffect2, useMemo as useMemo2 } from "react";
93
- import { AST, S } from "@dxos/echo-schema";
94
- import { findNode, getDiscriminatedType, isDiscriminatedUnion, SimpleType } from "@dxos/effect";
95
85
  import { log as log2 } from "@dxos/log";
96
- import { IconButton, useTranslation as useTranslation2 } from "@dxos/react-ui";
97
- import { mx } from "@dxos/react-ui-theme";
98
- import { getSchemaProperties } from "@dxos/schema";
99
- import { isNotFalsy } from "@dxos/util";
86
+ import { useTranslation as useTranslation4 } from "@dxos/react-ui";
87
+ import { getFormatSchema, getSchemaProperties as getSchemaProperties3, sortProperties } from "@dxos/schema";
100
88
 
101
- // packages/ui/react-ui-form/src/components/Form/Defaults.tsx
89
+ // packages/ui/react-ui-form/src/components/Form/custom/GeoPointInput.tsx
102
90
  import React3 from "react";
103
- import { Input as Input2, Select } from "@dxos/react-ui";
91
+ import { Input as Input2, useTranslation } from "@dxos/react-ui";
92
+ import { safeParseFloat } from "@dxos/util";
104
93
 
105
94
  // packages/ui/react-ui-form/src/components/Form/Input.tsx
106
95
  import React2 from "react";
@@ -121,138 +110,171 @@ var InputHeader = ({ children, error }) => {
121
110
  }, /* @__PURE__ */ React2.createElement(Tooltip.Arrow, null), error)));
122
111
  };
123
112
 
124
- // packages/ui/react-ui-form/src/components/Form/Defaults.tsx
125
- var TextInput = ({ property, type, label, inputOnly, disabled, placeholder, getStatus, getValue, onValueChange, onBlur }) => {
126
- const { status, error } = getStatus?.(property);
113
+ // packages/ui/react-ui-form/src/components/Form/custom/GeoPointInput.tsx
114
+ var GeoPointInput = ({ type, label, disabled, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
115
+ const { t } = useTranslation(translationKey);
116
+ const { status, error } = getStatus();
117
+ const [lng = 0, lat = 0] = getValue3() ?? [];
127
118
  return /* @__PURE__ */ React3.createElement(Input2.Root, {
128
119
  validationValence: status
129
- }, !inputOnly && /* @__PURE__ */ React3.createElement(InputHeader, {
120
+ }, /* @__PURE__ */ React3.createElement(InputHeader, {
130
121
  error
131
- }, /* @__PURE__ */ React3.createElement(Input2.Label, null, label)), /* @__PURE__ */ React3.createElement(Input2.TextInput, {
122
+ }, /* @__PURE__ */ React3.createElement(Input2.Label, null, label)), /* @__PURE__ */ React3.createElement("div", {
123
+ className: "grid grid-cols-2 gap-2"
124
+ }, /* @__PURE__ */ React3.createElement(Input2.TextInput, {
125
+ type: "number",
126
+ disabled,
127
+ placeholder: t("placeholder longitude"),
128
+ value: lng,
129
+ onChange: (event) => onValueChange(type, [
130
+ lat,
131
+ safeParseFloat(event.target.value, 0)
132
+ ]),
133
+ onBlur
134
+ }), /* @__PURE__ */ React3.createElement(Input2.TextInput, {
135
+ type: "number",
136
+ disabled,
137
+ placeholder: t("placeholder latitude"),
138
+ value: lat,
139
+ onChange: (event) => onValueChange(type, [
140
+ safeParseFloat(event.target.value, 0),
141
+ lng
142
+ ]),
143
+ onBlur
144
+ })));
145
+ };
146
+
147
+ // packages/ui/react-ui-form/src/components/Form/custom/TupleInput.tsx
148
+ import React4 from "react";
149
+ import { Input as Input3 } from "@dxos/react-ui";
150
+ import { mx } from "@dxos/react-ui-theme";
151
+ import { safeParseFloat as safeParseFloat2 } from "@dxos/util";
152
+ var gridCols = [
153
+ "grid-cols-1",
154
+ "grid-cols-2",
155
+ "grid-cols-3",
156
+ "grid-cols-4"
157
+ ];
158
+ var TupleInput = ({ binding, type, label, disabled, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
159
+ const { status, error } = getStatus();
160
+ const values = getValue3() ?? {};
161
+ return /* @__PURE__ */ React4.createElement(Input3.Root, {
162
+ validationValence: status
163
+ }, /* @__PURE__ */ React4.createElement(InputHeader, {
164
+ error
165
+ }, /* @__PURE__ */ React4.createElement(Input3.Label, null, label)), /* @__PURE__ */ React4.createElement("div", {
166
+ className: mx("grid gap-2", gridCols[binding.length - 1])
167
+ }, binding.map((prop) => /* @__PURE__ */ React4.createElement(Input3.TextInput, {
168
+ key: prop,
169
+ type: "number",
170
+ disabled,
171
+ value: values[prop],
172
+ onChange: (event) => onValueChange(type, {
173
+ ...values,
174
+ [prop]: safeParseFloat2(event.target.value, 0)
175
+ }),
176
+ onBlur
177
+ }))));
178
+ };
179
+
180
+ // packages/ui/react-ui-form/src/components/Form/Defaults.tsx
181
+ import React5 from "react";
182
+ import { Input as Input4, Select } from "@dxos/react-ui";
183
+ var TextInput = ({ type, label, inputOnly, disabled, placeholder, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
184
+ const { status, error } = getStatus();
185
+ return /* @__PURE__ */ React5.createElement(Input4.Root, {
186
+ validationValence: status
187
+ }, !inputOnly && /* @__PURE__ */ React5.createElement(InputHeader, {
188
+ error
189
+ }, /* @__PURE__ */ React5.createElement(Input4.Label, null, label)), /* @__PURE__ */ React5.createElement(Input4.TextInput, {
132
190
  disabled,
133
191
  placeholder,
134
- value: getValue(property) ?? "",
135
- onChange: (event) => onValueChange(property, type, event.target.value),
192
+ value: getValue3() ?? "",
193
+ onChange: (event) => onValueChange(type, event.target.value),
136
194
  onBlur
137
- }), inputOnly && /* @__PURE__ */ React3.createElement(Input2.Validation, null, error));
195
+ }), inputOnly && /* @__PURE__ */ React5.createElement(Input4.Validation, null, error));
138
196
  };
139
- var NumberInput = ({ property, type, label, inputOnly, disabled, placeholder, getStatus, getValue, onValueChange, onBlur }) => {
140
- const { status, error } = getStatus?.(property);
141
- return /* @__PURE__ */ React3.createElement(Input2.Root, {
197
+ var NumberInput = ({ type, label, inputOnly, disabled, placeholder, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
198
+ const { status, error } = getStatus();
199
+ return /* @__PURE__ */ React5.createElement(Input4.Root, {
142
200
  validationValence: status
143
- }, !inputOnly && /* @__PURE__ */ React3.createElement(InputHeader, {
201
+ }, !inputOnly && /* @__PURE__ */ React5.createElement(InputHeader, {
144
202
  error
145
- }, /* @__PURE__ */ React3.createElement(Input2.Label, null, label)), /* @__PURE__ */ React3.createElement(Input2.TextInput, {
203
+ }, /* @__PURE__ */ React5.createElement(Input4.Label, null, label)), /* @__PURE__ */ React5.createElement(Input4.TextInput, {
146
204
  type: "number",
147
205
  disabled,
148
206
  placeholder,
149
- value: getValue(property) ?? 0,
150
- onChange: (event) => onValueChange(property, type, event.target.value),
207
+ value: getValue3() ?? 0,
208
+ onChange: (event) => onValueChange(type, event.target.value),
151
209
  onBlur
152
- }), inputOnly && /* @__PURE__ */ React3.createElement(Input2.DescriptionAndValidation, null, error));
210
+ }), inputOnly && /* @__PURE__ */ React5.createElement(Input4.DescriptionAndValidation, null, error));
153
211
  };
154
- var BooleanInput = ({ property, type, label, inputOnly, getStatus, getValue, onValueChange }) => {
155
- const { status, error } = getStatus?.(property);
156
- return /* @__PURE__ */ React3.createElement(Input2.Root, {
212
+ var BooleanInput = ({ type, label, inputOnly, getStatus, getValue: getValue3, onValueChange }) => {
213
+ const { status, error } = getStatus();
214
+ return /* @__PURE__ */ React5.createElement(Input4.Root, {
157
215
  validationValence: status
158
- }, !inputOnly && /* @__PURE__ */ React3.createElement(InputHeader, {
216
+ }, !inputOnly && /* @__PURE__ */ React5.createElement(InputHeader, {
159
217
  error
160
- }, /* @__PURE__ */ React3.createElement(Input2.Label, null, label)), /* @__PURE__ */ React3.createElement(Input2.Switch, {
161
- checked: getValue(property),
162
- onCheckedChange: (value) => onValueChange(property, type, value)
163
- }), inputOnly && /* @__PURE__ */ React3.createElement(Input2.DescriptionAndValidation, null, error));
218
+ }, /* @__PURE__ */ React5.createElement(Input4.Label, null, label)), /* @__PURE__ */ React5.createElement(Input4.Switch, {
219
+ checked: getValue3(),
220
+ onCheckedChange: (value) => onValueChange(type, value)
221
+ }), inputOnly && /* @__PURE__ */ React5.createElement(Input4.DescriptionAndValidation, null, error));
164
222
  };
165
- var SelectInput = ({ property, type, label, inputOnly, disabled, placeholder, options, getStatus, getValue, onValueChange }) => {
166
- const { status, error } = getStatus?.(property);
167
- return /* @__PURE__ */ React3.createElement(Input2.Root, {
223
+ var SelectInput = ({ type, label, inputOnly, disabled, placeholder, options, getStatus, getValue: getValue3, onValueChange }) => {
224
+ const { status, error } = getStatus();
225
+ return /* @__PURE__ */ React5.createElement(Input4.Root, {
168
226
  validationValence: status
169
- }, !inputOnly && /* @__PURE__ */ React3.createElement(InputHeader, {
227
+ }, !inputOnly && /* @__PURE__ */ React5.createElement(InputHeader, {
170
228
  error
171
- }, /* @__PURE__ */ React3.createElement(Input2.Label, null, label)), /* @__PURE__ */ React3.createElement(Select.Root, {
172
- value: getValue(property),
173
- onValueChange: (value) => onValueChange(property, type, value)
174
- }, /* @__PURE__ */ React3.createElement(Select.TriggerButton, {
229
+ }, /* @__PURE__ */ React5.createElement(Input4.Label, null, label)), /* @__PURE__ */ React5.createElement(Select.Root, {
230
+ value: getValue3(),
231
+ onValueChange: (value) => onValueChange(type, value)
232
+ }, /* @__PURE__ */ React5.createElement(Select.TriggerButton, {
175
233
  classNames: "is-full",
176
234
  disabled,
177
235
  placeholder
178
- }), /* @__PURE__ */ React3.createElement(Select.Portal, null, /* @__PURE__ */ React3.createElement(Select.Content, null, /* @__PURE__ */ React3.createElement(Select.Viewport, null, options?.map(({ value, label: label2 }) => (
236
+ }), /* @__PURE__ */ React5.createElement(Select.Portal, null, /* @__PURE__ */ React5.createElement(Select.Content, null, /* @__PURE__ */ React5.createElement(Select.Viewport, null, options?.map(({ value, label: label2 }) => (
179
237
  // NOTE: Numeric values are converted to and from strings.
180
- /* @__PURE__ */ React3.createElement(Select.Option, {
238
+ /* @__PURE__ */ React5.createElement(Select.Option, {
181
239
  key: String(value),
182
240
  value: String(value)
183
241
  }, label2 ?? String(value))
184
- )))))), inputOnly && /* @__PURE__ */ React3.createElement(Input2.DescriptionAndValidation, null, error));
242
+ )))))), inputOnly && /* @__PURE__ */ React5.createElement(Input4.DescriptionAndValidation, null, error));
185
243
  };
186
244
 
187
- // packages/ui/react-ui-form/src/components/Form/factory.tsx
188
- import { FormatEnum } from "@dxos/echo-schema";
245
+ // packages/ui/react-ui-form/src/components/Form/Form.tsx
246
+ import React10, { useMemo as useMemo4 } from "react";
247
+ import { mx as mx5 } from "@dxos/react-ui-theme";
189
248
 
190
- // packages/ui/react-ui-form/src/components/Form/Custom.tsx
191
- import React4 from "react";
192
- import { Input as Input3, useTranslation } from "@dxos/react-ui";
193
- var GeiPointInput = ({ property, type, label, disabled, getStatus, getValue, onValueChange, onBlur }) => {
194
- const { t } = useTranslation(translationKey);
195
- const { status, error } = getStatus?.(property);
196
- const [lng = 0, lat = 0] = getValue(property) ?? [];
197
- return /* @__PURE__ */ React4.createElement(Input3.Root, {
198
- validationValence: status
199
- }, /* @__PURE__ */ React4.createElement(InputHeader, {
200
- error
201
- }, /* @__PURE__ */ React4.createElement(Input3.Label, null, label)), /* @__PURE__ */ React4.createElement("div", {
202
- className: "grid grid-cols-2 gap-2"
203
- }, /* @__PURE__ */ React4.createElement(Input3.TextInput, {
204
- type: "number",
205
- disabled,
206
- placeholder: t("placeholder longitude"),
207
- value: lng,
208
- onChange: (event) => onValueChange(property, type, [
209
- lat,
210
- safeParseFloat(event.target.value, 0)
211
- ]),
212
- onBlur
213
- }), /* @__PURE__ */ React4.createElement(Input3.TextInput, {
214
- type: "number",
215
- disabled,
216
- placeholder: t("placeholder latitude"),
217
- value: lat,
218
- onChange: (event) => onValueChange(property, type, [
219
- safeParseFloat(event.target.value, 0),
220
- lng
221
- ]),
222
- onBlur
223
- })));
224
- };
225
- var safeParseFloat = (str, defaultValue) => {
226
- try {
227
- return parseFloat(str);
228
- } catch {
229
- return defaultValue ?? 0;
230
- }
231
- };
249
+ // packages/ui/react-ui-form/src/components/Form/FormActions.tsx
250
+ import React7 from "react";
251
+ import { IconButton, useTranslation as useTranslation2 } from "@dxos/react-ui";
252
+ import { mx as mx2 } from "@dxos/react-ui-theme";
232
253
 
233
- // packages/ui/react-ui-form/src/components/Form/factory.tsx
234
- var getInputComponent = (type, format) => {
235
- switch (format) {
236
- case FormatEnum.LatLng:
237
- return GeiPointInput;
238
- }
239
- switch (type) {
240
- case "string":
241
- return TextInput;
242
- case "number":
243
- return NumberInput;
244
- case "boolean":
245
- return BooleanInput;
246
- }
247
- };
254
+ // packages/ui/react-ui-form/src/components/Form/FormContext.tsx
255
+ import React6, { createContext, useContext } from "react";
256
+ import { raise } from "@dxos/debug";
257
+ import { getValue as getValue2 } from "@dxos/echo-schema";
258
+ import { createJsonPath as createJsonPath2 } from "@dxos/effect";
248
259
 
249
260
  // packages/ui/react-ui-form/src/hooks/useForm.ts
250
261
  import { useCallback, useEffect, useMemo, useState } from "react";
262
+ import { getValue, setValue } from "@dxos/echo-schema";
263
+ import { AST, createJsonPath, fromEffectValidationPath } from "@dxos/effect";
251
264
  import { invariant } from "@dxos/invariant";
252
265
  import { log } from "@dxos/log";
253
266
  import { validateSchema } from "@dxos/schema";
254
267
  var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-form/src/hooks/useForm.ts";
255
268
  var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, onSave }) => {
269
+ invariant(AST.isTypeLiteral(schema.ast), void 0, {
270
+ F: __dxlog_file,
271
+ L: 91,
272
+ S: void 0,
273
+ A: [
274
+ "AST.isTypeLiteral(schema.ast)",
275
+ ""
276
+ ]
277
+ });
256
278
  const [values, setValues] = useState(initialValues);
257
279
  useEffect(() => {
258
280
  setValues(initialValues);
@@ -266,10 +288,12 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
266
288
  const validate = useCallback((values2) => {
267
289
  let errors2 = validateSchema(schema, values2) ?? [];
268
290
  if (errors2.length === 0 && onValidate) {
269
- errors2 = onValidate(values2) ?? [];
291
+ const validatedValues = values2;
292
+ errors2 = onValidate(validatedValues) ?? [];
270
293
  }
271
294
  setErrors(flatMap(errors2));
272
- return errors2.length === 0;
295
+ const valid = errors2.length === 0;
296
+ return valid;
273
297
  }, [
274
298
  schema,
275
299
  onValidate
@@ -287,8 +311,8 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
287
311
  values,
288
312
  schemaDirty
289
313
  ]);
290
- const canSave = useMemo(() => !saving && Object.keys(values).every((property) => touched[property] === false || !errors[property]), [
291
- values,
314
+ const canSave = useMemo(() => !saving && // Check if any error paths that are touched have errors
315
+ !Object.entries(touched).some(([path, isTouched]) => isTouched && Object.keys(errors).some((errorPath) => errorPath === path || errorPath.startsWith(`${path}.`) || errorPath.startsWith(`${path}[`))), [
292
316
  touched,
293
317
  errors,
294
318
  saving
@@ -307,19 +331,24 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
307
331
  }, [
308
332
  values,
309
333
  validate,
310
- onSave
334
+ onSave,
335
+ changed
311
336
  ]);
312
- const getStatus = useCallback((property) => ({
313
- status: errors[property] ? "error" : void 0,
314
- error: errors[property] ? errors[property] : void 0
315
- }), [
316
- touched,
337
+ const getStatus = useCallback((path) => {
338
+ const jsonPath = Array.isArray(path) ? createJsonPath(path) : path;
339
+ const matchingError = Object.entries(errors).find(([errorPath]) => errorPath === jsonPath || errorPath.startsWith(`${jsonPath}.`) || errorPath.startsWith(`${jsonPath}[`));
340
+ return {
341
+ status: matchingError ? "error" : void 0,
342
+ error: matchingError ? matchingError[1] : void 0
343
+ };
344
+ }, [
317
345
  errors
318
346
  ]);
319
- const getValue = (property) => {
320
- return getByPath(values, property);
347
+ const getFormValue = (path) => {
348
+ return getValue(values, createJsonPath(path));
321
349
  };
322
- const onValueChange = (property, type, value) => {
350
+ const onValueChange = (path, type, value) => {
351
+ const jsonPath = createJsonPath(path);
323
352
  let parsedValue = value;
324
353
  try {
325
354
  if (type === "number") {
@@ -328,17 +357,19 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
328
357
  } catch (err) {
329
358
  log.catch(err, void 0, {
330
359
  F: __dxlog_file,
331
- L: 183,
360
+ L: 194,
332
361
  S: void 0,
333
362
  C: (f, a) => f(...a)
334
363
  });
335
364
  parsedValue = void 0;
336
365
  }
337
- const newValues = setByPath(values, property, parsedValue);
366
+ const newValues = {
367
+ ...setValue(values, jsonPath, parsedValue)
368
+ };
338
369
  setValues(newValues);
339
370
  setChanged((prev) => ({
340
371
  ...prev,
341
- [property]: true
372
+ [jsonPath]: true
342
373
  }));
343
374
  onValuesChanged?.(newValues);
344
375
  const isValid = validate(newValues);
@@ -348,15 +379,12 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
348
379
  });
349
380
  }
350
381
  };
351
- const onBlur = useCallback((event) => {
352
- const { name } = event.target;
382
+ const onTouched = useCallback((path) => {
383
+ const jsonPath = createJsonPath(path);
353
384
  setTouched((touched2) => ({
354
385
  ...touched2,
355
- [name]: true
386
+ [jsonPath]: true
356
387
  }));
357
- if (event.relatedTarget?.getAttribute("type") === "submit") {
358
- setTouched(createKeySet(values, true));
359
- }
360
388
  validate(values);
361
389
  }, [
362
390
  validate,
@@ -372,15 +400,15 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
372
400
  handleSave,
373
401
  // Field utils.
374
402
  getStatus,
375
- getValue,
403
+ getValue: getFormValue,
376
404
  onValueChange,
377
- onBlur
405
+ onTouched
378
406
  };
379
407
  };
380
408
  var createKeySet = (obj, value) => {
381
409
  invariant(obj, void 0, {
382
410
  F: __dxlog_file,
383
- L: 234,
411
+ L: 236,
384
412
  S: void 0,
385
413
  A: [
386
414
  "obj",
@@ -394,338 +422,367 @@ var createKeySet = (obj, value) => {
394
422
  };
395
423
  var flatMap = (errors) => {
396
424
  return errors.reduce((result, { path, message }) => {
397
- if (!(path in result)) {
398
- result[path] = message;
425
+ const jsonPath = fromEffectValidationPath(path);
426
+ if (!(jsonPath in result)) {
427
+ result[jsonPath] = message;
399
428
  }
400
429
  return result;
401
430
  }, {});
402
431
  };
403
- var getByPath = (obj, path) => {
404
- const [key, index] = path.split(".");
405
- return index === void 0 ? obj[key] : obj[key][parseInt(index)];
432
+
433
+ // packages/ui/react-ui-form/src/components/Form/FormContext.tsx
434
+ var FormContext = /* @__PURE__ */ createContext(void 0);
435
+ var useFormContext = () => {
436
+ return useContext(FormContext) ?? raise(new Error("Missing FormContext"));
437
+ };
438
+ var useFormValues = (path = []) => {
439
+ const { values: formValues } = useFormContext();
440
+ const jsonPath = createJsonPath2(path);
441
+ return getValue2(formValues, jsonPath);
406
442
  };
407
- var setByPath = (obj, path, value) => {
408
- const [key, index] = path.split(".");
409
- const newValue = index === void 0 ? value : [
410
- ...obj[key]
411
- ].map((v, i) => i === parseInt(index) ? value : v);
443
+ var useInputProps = (path = []) => {
444
+ const { getStatus, getValue: getFormValue, onValueChange, onTouched } = useFormContext();
412
445
  return {
413
- ...obj,
414
- [key]: newValue
446
+ getStatus: () => getStatus(path),
447
+ getValue: () => getFormValue(path),
448
+ onValueChange: (type, value) => onValueChange(path, type, value),
449
+ onBlur: () => onTouched(path)
415
450
  };
416
451
  };
452
+ var FormProvider = ({ children, ...formOptions }) => {
453
+ const formHandler = useForm(formOptions);
454
+ return /* @__PURE__ */ React6.createElement(FormContext.Provider, {
455
+ value: formHandler
456
+ }, children);
457
+ };
417
458
 
418
- // packages/ui/react-ui-form/src/components/Form/Form.tsx
419
- var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-form/src/components/Form/Form.tsx";
459
+ // packages/ui/react-ui-form/src/components/Form/FormActions.tsx
460
+ var FormActions = ({ onCancel, readonly }) => {
461
+ const { t } = useTranslation2(translationKey);
462
+ const { canSave, handleSave } = useFormContext();
463
+ return /* @__PURE__ */ React7.createElement("div", {
464
+ role: "none",
465
+ className: "flex justify-center"
466
+ }, /* @__PURE__ */ React7.createElement("div", {
467
+ role: "none",
468
+ className: mx2(onCancel && !readonly && "grid grid-cols-2 gap-2")
469
+ }, onCancel && !readonly && /* @__PURE__ */ React7.createElement(IconButton, {
470
+ "data-testid": "cancel-button",
471
+ icon: "ph--x--regular",
472
+ label: t("button cancel"),
473
+ onClick: onCancel
474
+ }), handleSave && /* @__PURE__ */ React7.createElement(IconButton, {
475
+ type: "submit",
476
+ "data-testid": "save-button",
477
+ disabled: !canSave,
478
+ icon: "ph--check--regular",
479
+ label: t("button save"),
480
+ onClick: handleSave
481
+ })));
482
+ };
483
+
484
+ // packages/ui/react-ui-form/src/components/Form/FormContent.tsx
485
+ import { pipe as pipe2 } from "effect";
486
+ import { capitalize as capitalize2 } from "effect/String";
487
+ import React9, { useMemo as useMemo3 } from "react";
488
+ import { AST as AST3, S } from "@dxos/echo-schema";
489
+ import { createJsonPath as createJsonPath3, findNode as findNode2, getDiscriminatedType as getDiscriminatedType2, isDiscriminatedUnion as isDiscriminatedUnion2 } from "@dxos/effect";
490
+ import { mx as mx4 } from "@dxos/react-ui-theme";
491
+ import { getSchemaProperties as getSchemaProperties2 } from "@dxos/schema";
492
+ import { isNotFalsy } from "@dxos/util";
493
+
494
+ // packages/ui/react-ui-form/src/components/Form/ArrayField.tsx
495
+ import { pipe } from "effect";
496
+ import { capitalize } from "effect/String";
497
+ import React8, { useCallback as useCallback2, useMemo as useMemo2 } from "react";
498
+ import { AST as AST2 } from "@dxos/echo-schema";
499
+ import { findNode, getDiscriminatedType, isDiscriminatedUnion, SimpleType } from "@dxos/effect";
500
+ import { invariant as invariant2 } from "@dxos/invariant";
501
+ import { IconButton as IconButton2, useTranslation as useTranslation3 } from "@dxos/react-ui";
502
+ import { mx as mx3 } from "@dxos/react-ui-theme";
503
+ import { getSchemaProperties } from "@dxos/schema";
504
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-form/src/components/Form/ArrayField.tsx";
420
505
  var padding = "px-2";
421
- var Form = ({ classNames, schema, values: initialValues, path = [], readonly, filter, sort, autoSave, testId, onValuesChanged, onValidate, onSave, onCancel, Custom }) => {
422
- const onValid = useMemo2(() => autoSave ? onSave : void 0, [
423
- autoSave,
424
- onSave
425
- ]);
426
- const { canSave, values, errors, handleSave, ...inputProps } = useForm({
427
- schema,
428
- initialValues,
429
- onValuesChanged,
430
- onValidate,
431
- onValid,
432
- onSave
506
+ var ArrayField = ({ property, readonly, path, inputProps, Custom, lookupComponent }) => {
507
+ const { t } = useTranslation3(translationKey);
508
+ const { ast, name, type, title } = property;
509
+ const values = useFormValues(path ?? []);
510
+ invariant2(Array.isArray(values), void 0, {
511
+ F: __dxlog_file2,
512
+ L: 37,
513
+ S: void 0,
514
+ A: [
515
+ "Array.isArray(values)",
516
+ ""
517
+ ]
433
518
  });
434
- const properties = useMemo2(() => {
435
- const props = getSchemaProperties(schema.ast, values);
436
- const filtered = filter ? filter(props) : props;
437
- const findIndex = (props2, prop) => {
438
- const idx = props2.findIndex((p) => p === prop);
439
- return idx === -1 ? Infinity : idx;
440
- };
441
- return sort ? filtered.sort(({ name: a }, { name: b }) => findIndex(sort, a) - findIndex(sort, b)) : filtered;
519
+ const label = title ?? pipe(name, capitalize);
520
+ const tupleType = findNode(ast, AST2.isTupleType);
521
+ const elementType = tupleType?.rest[0]?.type;
522
+ const getDefaultObjectValue = (typeNode) => {
523
+ const baseNode = findNode(typeNode, isDiscriminatedUnion);
524
+ const typeLiteral = baseNode ? getDiscriminatedType(baseNode, {}) : findNode(typeNode, AST2.isTypeLiteral);
525
+ if (!typeLiteral) {
526
+ return {};
527
+ }
528
+ return Object.fromEntries(getSchemaProperties(typeLiteral, {}).map((prop) => [
529
+ prop.name,
530
+ prop.defaultValue
531
+ ]));
532
+ };
533
+ const newValue = useMemo2(() => {
534
+ if (type === "object" && elementType) {
535
+ return getDefaultObjectValue(elementType);
536
+ } else {
537
+ return SimpleType.getDefaultValue(type);
538
+ }
442
539
  }, [
443
- schema,
444
- filter,
540
+ type,
541
+ elementType
542
+ ]);
543
+ const handleAdd = useCallback2(() => {
544
+ inputProps.onValueChange(type, [
545
+ ...values,
546
+ newValue
547
+ ]);
548
+ }, [
549
+ type,
550
+ elementType,
551
+ inputProps,
552
+ newValue,
445
553
  values
446
554
  ]);
447
- useEffect2(() => {
448
- if (errors && Object.keys(errors).length) {
449
- log2.warn("validation", {
450
- errors
451
- }, {
452
- F: __dxlog_file2,
453
- L: 100,
454
- S: void 0,
455
- C: (f, a) => f(...a)
456
- });
457
- }
555
+ const handleRemove = useCallback2((index) => {
556
+ const newValues = values.filter((_, i) => i !== index);
557
+ inputProps.onValueChange(type, newValues);
458
558
  }, [
459
- errors
559
+ type,
560
+ inputProps,
561
+ values
460
562
  ]);
461
- return /* @__PURE__ */ React5.createElement("div", {
462
- role: "form",
463
- className: mx("flex flex-col w-full gap-2 py-2", classNames),
464
- "data-testid": testId
465
- }, properties.map((property) => {
466
- const { ast, name, type, format, title, description, examples, options, array } = property;
467
- const key = [
468
- ...path,
469
- name
470
- ];
471
- const label = title ?? pipe(name, capitalize);
472
- const placeholder = examples?.length ? `Example: "${examples[0]}"` : description;
473
- let InputComponent = Custom?.[key.join(".")];
474
- if (!InputComponent) {
475
- if (options) {
476
- return /* @__PURE__ */ React5.createElement("div", {
477
- key: name,
478
- role: "none",
479
- className: padding
480
- }, /* @__PURE__ */ React5.createElement(SelectInput, {
481
- type,
482
- format,
483
- property: name,
484
- disabled: readonly,
485
- label,
486
- options: options.map((option) => ({
487
- value: option,
488
- label: String(option)
489
- })),
490
- placeholder,
491
- ...inputProps
492
- }));
493
- }
494
- InputComponent = getInputComponent(type, format);
495
- if (array) {
496
- return /* @__PURE__ */ React5.createElement(ArrayField, {
497
- key: name,
498
- name,
499
- type,
500
- array: true,
501
- label,
502
- ast,
503
- values,
504
- format,
505
- readonly,
506
- placeholder,
507
- inputProps,
508
- Custom
509
- });
510
- }
511
- }
512
- if (!InputComponent) {
513
- if (type === "object") {
514
- const baseNode = findNode(ast, isDiscriminatedUnion);
515
- const typeLiteral = baseNode ? getDiscriminatedType(baseNode, values[name]) : findNode(ast, AST.isTypeLiteral);
516
- if (typeLiteral) {
517
- const schema2 = S.make(typeLiteral);
518
- return /* @__PURE__ */ React5.createElement("div", {
519
- key: name,
520
- role: "none"
521
- }, /* @__PURE__ */ React5.createElement("div", {
522
- className: padding
523
- }, label), /* @__PURE__ */ React5.createElement(Form, {
524
- schema: schema2,
525
- path: key,
526
- values: values[name] ?? {},
527
- onValuesChanged: (childValues) => {
528
- inputProps.onValueChange(name, "object", childValues);
529
- },
530
- Custom
531
- }));
532
- }
533
- }
534
- log2("no renderer for property", {
535
- name,
536
- type
537
- }, {
538
- F: __dxlog_file2,
539
- L: 185,
540
- S: void 0,
541
- C: (f, a) => f(...a)
542
- });
543
- return null;
544
- }
545
- return /* @__PURE__ */ React5.createElement("div", {
546
- key: name,
547
- role: "none",
548
- className: padding
549
- }, /* @__PURE__ */ React5.createElement(InputComponent, {
550
- type,
551
- format,
552
- property: name,
553
- disabled: readonly,
554
- label,
555
- placeholder,
556
- ...inputProps
557
- }));
558
- }).filter(isNotFalsy), (onCancel || onSave) && !autoSave && /* @__PURE__ */ React5.createElement(FormActions, {
559
- onCancel,
560
- onSubmit: handleSave,
561
- canSubmit: canSave,
562
- readonly
563
- }));
564
- };
565
- var ArrayField = ({ name, type, label, ast, values, format, readonly, placeholder, inputProps, Custom }) => {
566
- const { t } = useTranslation2(translationKey);
567
- const arrayValues = values[name] ?? [];
568
- const key = [
569
- name
570
- ];
571
- const tupleType = findNode(ast, AST.isTupleType);
572
- const elementType = tupleType?.rest[0]?.type;
573
- if (type === "object" && elementType) {
574
- const baseNode = findNode(elementType, isDiscriminatedUnion);
575
- const typeLiteral = baseNode ? getDiscriminatedType(baseNode, values[name]) : findNode(elementType, AST.isTypeLiteral);
576
- if (typeLiteral) {
577
- const schema = S.make(typeLiteral);
578
- return /* @__PURE__ */ React5.createElement("div", {
579
- key: name,
580
- role: "none",
581
- className: mx(padding)
582
- }, /* @__PURE__ */ React5.createElement(InputHeader, null, label), /* @__PURE__ */ React5.createElement("div", {
583
- role: "none",
584
- className: "flex flex-col gap-1"
585
- }, arrayValues.map((value, index) => {
586
- return /* @__PURE__ */ React5.createElement("div", {
587
- key: index,
588
- role: "none",
589
- className: "flex items-center gap-1"
590
- }, /* @__PURE__ */ React5.createElement("div", {
591
- role: "none",
592
- className: "flex-1"
593
- }, /* @__PURE__ */ React5.createElement(Form, {
594
- schema,
595
- path: [
596
- ...key,
597
- index.toString()
598
- ],
599
- values: value,
600
- onValuesChanged: (childValues) => {
601
- const newValues = [
602
- ...arrayValues
603
- ];
604
- newValues[index] = childValues;
605
- inputProps.onValueChange(name, "object", newValues);
606
- },
607
- Custom
608
- })), /* @__PURE__ */ React5.createElement(IconButton, {
609
- icon: "ph--trash--regular",
610
- iconOnly: true,
611
- label: t("button remove"),
612
- onClick: () => {
613
- const newValues = arrayValues.filter((_, i) => i !== index);
614
- inputProps.onValueChange(name, type, newValues);
615
- }
616
- }));
617
- })), /* @__PURE__ */ React5.createElement("div", {
618
- role: "none",
619
- className: "flex justify-between items-center plb-1"
620
- }, /* @__PURE__ */ React5.createElement(IconButton, {
621
- icon: "ph--plus--regular",
622
- iconOnly: true,
623
- label: t("button add"),
624
- onClick: () => {
625
- const newValues = [
626
- ...arrayValues,
627
- {}
628
- ];
629
- inputProps.onValueChange(name, type, newValues);
630
- }
631
- })));
632
- }
633
- return /* @__PURE__ */ React5.createElement("div", {
634
- key: name,
635
- role: "none"
636
- }, "Nested form not supported in arrays, yet.");
637
- }
638
- const InputComponent = getInputComponent(type, format);
639
- if (!InputComponent) {
563
+ if (!elementType) {
640
564
  return null;
641
565
  }
642
- return /* @__PURE__ */ React5.createElement("div", {
566
+ return /* @__PURE__ */ React8.createElement("div", {
643
567
  role: "none",
644
- key: "meta-input",
645
- className: mx(padding)
646
- }, /* @__PURE__ */ React5.createElement(InputHeader, null, label), /* @__PURE__ */ React5.createElement("div", {
568
+ className: mx3(padding)
569
+ }, /* @__PURE__ */ React8.createElement(InputHeader, null, label), /* @__PURE__ */ React8.createElement("div", {
647
570
  role: "none",
648
571
  className: "flex flex-col gap-1"
649
- }, arrayValues.map((_, index) => /* @__PURE__ */ React5.createElement("div", {
572
+ }, values.map((_value, index) => /* @__PURE__ */ React8.createElement("div", {
650
573
  key: index,
651
574
  role: "none",
652
- className: "flex items-start gap-1"
653
- }, /* @__PURE__ */ React5.createElement("div", {
575
+ className: "flex items-center gap-1"
576
+ }, /* @__PURE__ */ React8.createElement("div", {
654
577
  role: "none",
655
578
  className: "flex-1"
656
- }, /* @__PURE__ */ React5.createElement(InputComponent, {
657
- type,
658
- format,
659
- label,
660
- inputOnly: true,
661
- property: `${name}.${index}`,
662
- disabled: readonly,
663
- placeholder,
664
- ...inputProps
665
- })), /* @__PURE__ */ React5.createElement(IconButton, {
579
+ }, /* @__PURE__ */ React8.createElement(FormField, {
580
+ property: {
581
+ ...property,
582
+ array: false,
583
+ ast: elementType
584
+ },
585
+ path: [
586
+ ...path ?? [],
587
+ index
588
+ ],
589
+ readonly,
590
+ inline: true,
591
+ Custom,
592
+ lookupComponent
593
+ })), /* @__PURE__ */ React8.createElement(IconButton2, {
666
594
  icon: "ph--trash--regular",
667
595
  iconOnly: true,
668
596
  label: t("button remove"),
669
- onClick: () => {
670
- const newValues = arrayValues.filter((_2, i) => i !== index);
671
- inputProps.onValueChange(name, type, newValues);
672
- }
673
- })))), /* @__PURE__ */ React5.createElement("div", {
597
+ onClick: () => handleRemove(index)
598
+ })))), /* @__PURE__ */ React8.createElement("div", {
674
599
  role: "none",
675
600
  className: "flex justify-between items-center plb-1"
676
- }, /* @__PURE__ */ React5.createElement(IconButton, {
601
+ }, /* @__PURE__ */ React8.createElement(IconButton2, {
677
602
  icon: "ph--plus--regular",
678
603
  iconOnly: true,
679
604
  label: t("button add"),
680
- onClick: () => {
681
- const newValues = [
682
- ...arrayValues,
683
- SimpleType.getDefaultValue(type)
684
- ];
685
- inputProps.onValueChange(name, type, newValues);
686
- }
605
+ onClick: handleAdd
687
606
  })));
688
607
  };
689
- var FormActions = ({ onCancel, onSubmit, canSubmit, readonly }) => {
690
- const { t } = useTranslation2(translationKey);
691
- return /* @__PURE__ */ React5.createElement("div", {
692
- role: "none",
693
- className: "flex justify-center"
694
- }, /* @__PURE__ */ React5.createElement("div", {
608
+
609
+ // packages/ui/react-ui-form/src/components/Form/factory.tsx
610
+ import { FormatEnum } from "@dxos/echo-schema";
611
+ var getInputComponent = (type, format) => {
612
+ switch (format) {
613
+ case FormatEnum.LatLng:
614
+ return GeoPointInput;
615
+ }
616
+ switch (type) {
617
+ case "string":
618
+ return TextInput;
619
+ case "number":
620
+ return NumberInput;
621
+ case "boolean":
622
+ return BooleanInput;
623
+ }
624
+ };
625
+
626
+ // packages/ui/react-ui-form/src/components/Form/FormContent.tsx
627
+ var FormField = ({ property, path, readonly, inline, lookupComponent, Custom }) => {
628
+ const inputProps = useInputProps(path);
629
+ const { ast, name, type, format, title, description, options, examples, array } = property;
630
+ const label = useMemo3(() => title ?? pipe2(name, capitalize2), [
631
+ title,
632
+ name
633
+ ]);
634
+ const placeholder = useMemo3(() => examples?.length ? `Example: "${examples[0]}"` : description, [
635
+ examples,
636
+ description
637
+ ]);
638
+ if (array) {
639
+ return /* @__PURE__ */ React9.createElement(ArrayField, {
640
+ property,
641
+ path,
642
+ inputProps,
643
+ readonly,
644
+ Custom
645
+ });
646
+ }
647
+ const FoundComponent = lookupComponent?.({
648
+ prop: name,
649
+ schema: S.make(ast),
650
+ inputProps: {
651
+ type,
652
+ format,
653
+ label,
654
+ disabled: readonly,
655
+ placeholder,
656
+ ...inputProps
657
+ }
658
+ });
659
+ if (FoundComponent) {
660
+ return /* @__PURE__ */ React9.createElement("div", null, FoundComponent);
661
+ }
662
+ const jsonPath = createJsonPath3(path ?? []);
663
+ const InputComponent = Custom?.[jsonPath] || getInputComponent(type, format);
664
+ if (InputComponent) {
665
+ return /* @__PURE__ */ React9.createElement("div", null, /* @__PURE__ */ React9.createElement(InputComponent, {
666
+ type,
667
+ format,
668
+ label,
669
+ inputOnly: inline,
670
+ placeholder,
671
+ disabled: readonly,
672
+ ...inputProps
673
+ }));
674
+ }
675
+ if (options) {
676
+ return /* @__PURE__ */ React9.createElement("div", {
677
+ role: "none"
678
+ }, /* @__PURE__ */ React9.createElement(SelectInput, {
679
+ type,
680
+ format,
681
+ disabled: readonly,
682
+ inputOnly: inline,
683
+ label,
684
+ options: options.map((option) => ({
685
+ value: option,
686
+ label: String(option)
687
+ })),
688
+ placeholder,
689
+ ...inputProps
690
+ }));
691
+ }
692
+ if (type === "object") {
693
+ const baseNode = findNode2(ast, isDiscriminatedUnion2);
694
+ const typeLiteral = baseNode ? getDiscriminatedType2(baseNode, inputProps.getValue()) : findNode2(ast, AST3.isTypeLiteral);
695
+ if (typeLiteral) {
696
+ return /* @__PURE__ */ React9.createElement("div", null, !inline && /* @__PURE__ */ React9.createElement("div", null, label), /* @__PURE__ */ React9.createElement(FormFields, {
697
+ schema: S.make(typeLiteral),
698
+ path,
699
+ readonly,
700
+ Custom
701
+ }));
702
+ }
703
+ }
704
+ return null;
705
+ };
706
+ var FormFields = ({ schema, path, filter, sort, readonly, lookupComponent, Custom }) => {
707
+ const values = useFormValues(path);
708
+ const properties = useMemo3(() => {
709
+ const props = getSchemaProperties2(schema.ast, values);
710
+ const filtered = filter ? filter(props) : props;
711
+ return sort ? filtered.sort((a, b) => sort.indexOf(a.name) - sort.indexOf(b.name)) : filtered;
712
+ }, [
713
+ schema,
714
+ values,
715
+ filter,
716
+ sort
717
+ ]);
718
+ return /* @__PURE__ */ React9.createElement("div", {
719
+ role: "form",
720
+ className: mx4("flex flex-col w-full gap-2 py-2")
721
+ }, properties.map((property) => {
722
+ return /* @__PURE__ */ React9.createElement(FormField, {
723
+ key: property.name,
724
+ property,
725
+ path: [
726
+ ...path ?? [],
727
+ property.name
728
+ ],
729
+ readonly,
730
+ lookupComponent,
731
+ Custom
732
+ });
733
+ }).filter(isNotFalsy));
734
+ };
735
+
736
+ // packages/ui/react-ui-form/src/components/Form/Form.tsx
737
+ var Form = ({ classNames, schema, values: initialValues, path = [], readonly, filter, sort, autoSave, testId, onValuesChanged, onValidate, onSave, onCancel, lookupComponent, Custom }) => {
738
+ const onValid = useMemo4(() => autoSave ? onSave : void 0, [
739
+ autoSave,
740
+ onSave
741
+ ]);
742
+ return /* @__PURE__ */ React10.createElement(FormProvider, {
743
+ schema,
744
+ initialValues,
745
+ onValuesChanged,
746
+ onValidate,
747
+ onValid,
748
+ onSave
749
+ }, /* @__PURE__ */ React10.createElement("div", {
695
750
  role: "none",
696
- className: mx(onCancel && !readonly && "grid grid-cols-2 gap-2")
697
- }, onCancel && !readonly && /* @__PURE__ */ React5.createElement(IconButton, {
698
- "data-testid": "cancel-button",
699
- icon: "ph--x--regular",
700
- label: t("button cancel"),
701
- onClick: onCancel
702
- }), onSubmit && /* @__PURE__ */ React5.createElement(IconButton, {
703
- type: "submit",
704
- "data-testid": "save-button",
705
- disabled: !canSubmit,
706
- icon: "ph--check--regular",
707
- label: t("button save"),
708
- onClick: onSubmit
751
+ className: mx5("p-2", classNames),
752
+ "data-testid": testId
753
+ }, /* @__PURE__ */ React10.createElement(FormFields, {
754
+ schema,
755
+ path,
756
+ readonly,
757
+ filter,
758
+ sort,
759
+ lookupComponent,
760
+ Custom
761
+ }), (onCancel || onSave) && !autoSave && /* @__PURE__ */ React10.createElement(FormActions, {
762
+ onCancel,
763
+ readonly
709
764
  })));
710
765
  };
711
766
 
712
767
  // packages/ui/react-ui-form/src/components/FieldEditor/FieldEditor.tsx
713
768
  var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-form/src/components/FieldEditor/FieldEditor.tsx";
714
769
  var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
715
- const { t } = useTranslation3(translationKey);
770
+ const { t } = useTranslation4(translationKey);
716
771
  const [props, setProps] = useState2(projection.getFieldProjection(field.id).props);
717
- useEffect3(() => setProps(projection.getFieldProjection(field.id).props), [
772
+ useEffect2(() => setProps(projection.getFieldProjection(field.id).props), [
718
773
  field,
719
774
  projection
720
775
  ]);
721
776
  const [schemas, setSchemas] = useState2([]);
722
- useEffect3(() => {
777
+ useEffect2(() => {
723
778
  if (!registry) {
724
779
  return;
725
780
  }
726
- const subscription = registry.subscribe(setSchemas);
781
+ const subscription = registry.query().subscribe((query) => setSchemas(query.results), {
782
+ fire: true
783
+ });
727
784
  const t2 = setTimeout(async () => {
728
- const schemas2 = await registry.query();
785
+ const schemas2 = await registry.query().run();
729
786
  setSchemas(schemas2);
730
787
  });
731
788
  return () => {
@@ -735,9 +792,9 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
735
792
  }, [
736
793
  registry
737
794
  ]);
738
- const [schema, setSchema] = useState2();
739
- useEffect3(() => {
740
- setSchema(schemas.find((schema2) => schema2.typename === props?.referenceSchema));
795
+ const [referenceSchema, setReferenceSchema] = useState2();
796
+ useEffect2(() => {
797
+ setReferenceSchema(schemas.find((schema) => schema.typename === props?.referenceSchema));
741
798
  }, [
742
799
  schemas,
743
800
  props?.referenceSchema
@@ -745,15 +802,25 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
745
802
  const [{ fieldSchema }, setFieldSchema] = useState2({
746
803
  fieldSchema: getFormatSchema(props?.format)
747
804
  });
748
- const handleValuesChanged = useCallback2((_props) => {
749
- if (_props.format !== props.format) {
750
- setFieldSchema({
751
- fieldSchema: getFormatSchema(_props.format)
752
- });
753
- }
754
- if (_props.referenceSchema !== props.referenceSchema) {
755
- setSchema(schemas.find((schema2) => schema2.typename === _props.referenceSchema));
756
- }
805
+ const handleValuesChanged = useCallback3((_props) => {
806
+ setFieldSchema((prev) => {
807
+ const fieldSchema2 = getFormatSchema(_props.format);
808
+ if (prev.fieldSchema === fieldSchema2) {
809
+ return prev;
810
+ }
811
+ return {
812
+ fieldSchema: fieldSchema2
813
+ };
814
+ });
815
+ setReferenceSchema((prev) => {
816
+ if (_props.referenceSchema !== prev?.typename) {
817
+ const newSchema = schemas.find((schema) => schema.typename === _props.referenceSchema);
818
+ if (newSchema) {
819
+ return newSchema;
820
+ }
821
+ }
822
+ return prev;
823
+ });
757
824
  setProps((props2) => {
758
825
  const type = formatToType[_props.format];
759
826
  if (props2.type !== type) {
@@ -767,9 +834,10 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
767
834
  });
768
835
  }, [
769
836
  schemas,
770
- props
837
+ props.format,
838
+ props.referenceSchema
771
839
  ]);
772
- const handleValidate = useCallback2(({ property }) => {
840
+ const handleValidate = useCallback3(({ property }) => {
773
841
  if (property && view.fields.find((f) => f.path === property && f.path !== field.path)) {
774
842
  return [
775
843
  {
@@ -782,7 +850,7 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
782
850
  view.fields,
783
851
  field
784
852
  ]);
785
- const handleSave = useCallback2((props2) => {
853
+ const handleSave = useCallback3((props2) => {
786
854
  projection.setFieldProjection({
787
855
  field,
788
856
  props: props2
@@ -793,29 +861,55 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
793
861
  field,
794
862
  onSave
795
863
  ]);
796
- const handleCancel = useCallback2(() => {
864
+ const handleCancel = useCallback3(() => {
797
865
  requestAnimationFrame(() => onCancel?.());
798
866
  onSave();
799
867
  }, [
800
868
  onSave
801
869
  ]);
870
+ const Custom = useMemo5(() => ({
871
+ ["format"]: (props2) => /* @__PURE__ */ React11.createElement(SelectInput, {
872
+ ...props2,
873
+ options: FormatEnums.filter((value) => value !== FormatEnum2.None).map((value) => ({
874
+ value,
875
+ label: t(`format ${value}`)
876
+ }))
877
+ }),
878
+ ["referenceSchema"]: (props2) => /* @__PURE__ */ React11.createElement(SelectInput, {
879
+ ...props2,
880
+ options: schemas.map((schema) => ({
881
+ value: schema.typename
882
+ }))
883
+ }),
884
+ ["referencePath"]: (props2) => /* @__PURE__ */ React11.createElement(SelectInput, {
885
+ ...props2,
886
+ options: referenceSchema ? getSchemaProperties3(referenceSchema.ast).sort(sortProperties).map((p) => ({
887
+ value: p.name
888
+ })) : []
889
+ })
890
+ }), [
891
+ t,
892
+ schemas,
893
+ referenceSchema
894
+ ]);
895
+ const propIsNotType = useCallback3((props2) => props2.filter((p) => p.name !== "type"), []);
802
896
  if (!fieldSchema) {
803
- log3.warn("invalid format", {
897
+ log2.warn("invalid format", {
804
898
  props
805
899
  }, {
806
900
  F: __dxlog_file3,
807
- L: 118,
901
+ L: 173,
808
902
  S: void 0,
809
903
  C: (f, a) => f(...a)
810
904
  });
811
905
  return null;
812
906
  }
813
- return /* @__PURE__ */ React6.createElement(Form, {
907
+ return /* @__PURE__ */ React11.createElement(Form, {
814
908
  key: field.id,
815
909
  autoFocus: true,
816
910
  values: props,
817
911
  schema: fieldSchema,
818
- filter: (props2) => props2.filter((p) => p.name !== "type"),
912
+ filter: propIsNotType,
819
913
  sort: [
820
914
  "property",
821
915
  "format"
@@ -824,55 +918,35 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
824
918
  onValidate: handleValidate,
825
919
  onSave: handleSave,
826
920
  onCancel: handleCancel,
827
- Custom: {
828
- ["format"]: (props2) => /* @__PURE__ */ React6.createElement(SelectInput, {
829
- ...props2,
830
- options: FormatEnums.filter((value) => value !== FormatEnum2.None).map((value) => ({
831
- value,
832
- label: t(`format ${value}`)
833
- }))
834
- }),
835
- ["referenceSchema"]: (props2) => /* @__PURE__ */ React6.createElement(SelectInput, {
836
- ...props2,
837
- options: schemas.map((schema2) => ({
838
- value: schema2.typename
839
- }))
840
- }),
841
- ["referencePath"]: (props2) => /* @__PURE__ */ React6.createElement(SelectInput, {
842
- ...props2,
843
- options: schema ? getSchemaProperties2(schema.schema.ast).sort(sortProperties).map((p) => ({
844
- value: p.name
845
- })) : []
846
- })
847
- }
921
+ Custom
848
922
  });
849
923
  };
850
924
 
851
925
  // packages/ui/react-ui-form/src/components/ViewEditor/ViewEditor.tsx
852
- import React7, { useCallback as useCallback3, useMemo as useMemo3, useState as useState3 } from "react";
853
- import { AST as AST2, Format, S as S2 } from "@dxos/echo-schema";
854
- import { IconButton as IconButton2, useTranslation as useTranslation4 } from "@dxos/react-ui";
926
+ import React12, { useCallback as useCallback4, useMemo as useMemo6, useState as useState3 } from "react";
927
+ import { AST as AST4, Format, S as S2 } from "@dxos/echo-schema";
928
+ import { IconButton as IconButton3, useTranslation as useTranslation5 } from "@dxos/react-ui";
855
929
  import { List } from "@dxos/react-ui-list";
856
- import { ghostHover, inputTextLabel, mx as mx2 } from "@dxos/react-ui-theme";
930
+ import { ghostHover, inputTextLabel, mx as mx6 } from "@dxos/react-ui-theme";
857
931
  import { FieldSchema, ViewProjection, VIEW_FIELD_LIMIT } from "@dxos/schema";
858
932
  import { arrayMove } from "@dxos/util";
859
933
  var grid = "grid grid-cols-[32px_1fr_32px] min-bs-[2.5rem]";
860
934
  var ViewMetaSchema = S2.Struct({
861
935
  name: S2.String.annotations({
862
- [AST2.TitleAnnotationId]: "View"
936
+ [AST4.TitleAnnotationId]: "View"
863
937
  }),
864
938
  typename: Format.DXN.annotations({
865
- [AST2.TitleAnnotationId]: "Typename"
939
+ [AST4.TitleAnnotationId]: "Typename"
866
940
  })
867
941
  }).pipe(S2.mutable);
868
- var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading = false, onDelete }) => {
869
- const { t } = useTranslation4(translationKey);
870
- const projection = useMemo3(() => new ViewProjection(schema, view), [
942
+ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading = false, onTypenameChanged, onDelete }) => {
943
+ const { t } = useTranslation5(translationKey);
944
+ const projection = useMemo6(() => new ViewProjection(schema, view), [
871
945
  schema,
872
946
  view
873
947
  ]);
874
948
  const [field, setField] = useState3();
875
- const viewValues = useMemo3(() => {
949
+ const viewValues = useMemo6(() => {
876
950
  return {
877
951
  name: view.name,
878
952
  // TODO(burdon): Need to warn user of possible consequences of editing.
@@ -882,77 +956,81 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
882
956
  }, [
883
957
  view
884
958
  ]);
885
- const handleViewUpdate = useCallback3(({ name, typename }) => {
959
+ const handleViewUpdate = useCallback4(({ name, typename }) => {
886
960
  requestAnimationFrame(() => {
887
- view.name = name;
888
- view.query.type = typename;
889
- schema.updateTypename(typename);
961
+ if (view.name !== name) {
962
+ view.name = name;
963
+ }
964
+ if (view.query.type !== typename) {
965
+ onTypenameChanged(typename);
966
+ }
890
967
  });
891
968
  }, [
892
969
  view,
893
- schema
970
+ schema,
971
+ onTypenameChanged
894
972
  ]);
895
- const handleSelect = useCallback3((field2) => {
973
+ const handleSelect = useCallback4((field2) => {
896
974
  setField((f) => f === field2 ? void 0 : field2);
897
975
  }, []);
898
- const handleAdd = useCallback3(() => {
976
+ const handleAdd = useCallback4(() => {
899
977
  const field2 = projection.createFieldProjection();
900
978
  setField(field2);
901
979
  }, [
902
980
  view
903
981
  ]);
904
- const handleMove = useCallback3((fromIndex, toIndex) => {
982
+ const handleMove = useCallback4((fromIndex, toIndex) => {
905
983
  arrayMove(view.fields, fromIndex, toIndex);
906
984
  }, [
907
985
  view.fields
908
986
  ]);
909
- const handleClose = useCallback3(() => setField(void 0), []);
910
- return /* @__PURE__ */ React7.createElement("div", {
987
+ const handleClose = useCallback4(() => setField(void 0), []);
988
+ return /* @__PURE__ */ React12.createElement("div", {
911
989
  role: "none",
912
- className: mx2("flex flex-col w-full divide-y divide-separator", classNames)
913
- }, /* @__PURE__ */ React7.createElement(Form, {
990
+ className: mx6("flex flex-col w-full divide-y divide-separator", classNames)
991
+ }, /* @__PURE__ */ React12.createElement(Form, {
914
992
  autoFocus: true,
915
993
  autoSave: true,
916
994
  schema: ViewMetaSchema,
917
995
  values: viewValues,
918
996
  onSave: handleViewUpdate
919
- }), /* @__PURE__ */ React7.createElement("div", null, /* @__PURE__ */ React7.createElement("div", {
997
+ }), /* @__PURE__ */ React12.createElement("div", null, /* @__PURE__ */ React12.createElement("div", {
920
998
  role: "none",
921
999
  className: "p-2"
922
- }, /* @__PURE__ */ React7.createElement("label", {
923
- className: mx2(inputTextLabel)
924
- }, t("fields label"))), /* @__PURE__ */ React7.createElement(List.Root, {
1000
+ }, /* @__PURE__ */ React12.createElement("label", {
1001
+ className: mx6(inputTextLabel)
1002
+ }, t("fields label"))), /* @__PURE__ */ React12.createElement(List.Root, {
925
1003
  items: view.fields,
926
1004
  isItem: S2.is(FieldSchema),
927
1005
  getId: (field2) => field2.id,
928
1006
  onMove: handleMove
929
- }, ({ items: fields }) => /* @__PURE__ */ React7.createElement(React7.Fragment, null, showHeading && /* @__PURE__ */ React7.createElement("div", {
1007
+ }, ({ items: fields }) => /* @__PURE__ */ React12.createElement(React12.Fragment, null, showHeading && /* @__PURE__ */ React12.createElement("div", {
930
1008
  role: "heading",
931
1009
  className: grid
932
- }, /* @__PURE__ */ React7.createElement("div", null), /* @__PURE__ */ React7.createElement("div", {
1010
+ }, /* @__PURE__ */ React12.createElement("div", null), /* @__PURE__ */ React12.createElement("div", {
933
1011
  className: "flex items-center text-sm"
934
- }, t("field path label"))), /* @__PURE__ */ React7.createElement("div", {
1012
+ }, t("field path label"))), /* @__PURE__ */ React12.createElement("div", {
935
1013
  role: "list",
936
1014
  className: "flex flex-col w-full"
937
- }, fields?.map((field2) => /* @__PURE__ */ React7.createElement(List.Item, {
1015
+ }, fields?.map((field2) => /* @__PURE__ */ React12.createElement(List.Item, {
938
1016
  key: field2.id,
939
1017
  item: field2,
940
- classNames: mx2(grid, ghostHover, "cursor-pointer")
941
- }, /* @__PURE__ */ React7.createElement(List.ItemDragHandle, null), /* @__PURE__ */ React7.createElement(List.ItemTitle, {
1018
+ classNames: mx6(grid, ghostHover, "cursor-pointer")
1019
+ }, /* @__PURE__ */ React12.createElement(List.ItemDragHandle, null), /* @__PURE__ */ React12.createElement(List.ItemTitle, {
942
1020
  onClick: () => handleSelect(field2)
943
- }, field2.path), /* @__PURE__ */ React7.createElement(List.ItemDeleteButton, {
1021
+ }, field2.path), /* @__PURE__ */ React12.createElement(List.ItemDeleteButton, {
944
1022
  disabled: view.fields.length <= 1,
945
1023
  onClick: () => onDelete(field2.id)
946
- }))))))), field && /* @__PURE__ */ React7.createElement(FieldEditor, {
1024
+ }))))))), field && /* @__PURE__ */ React12.createElement(FieldEditor, {
947
1025
  key: field.id,
948
1026
  view,
949
1027
  projection,
950
1028
  field,
951
1029
  registry,
952
1030
  onSave: handleClose
953
- }), !readonly && !field && /* @__PURE__ */ React7.createElement("div", {
1031
+ }), !readonly && !field && /* @__PURE__ */ React12.createElement("div", {
954
1032
  className: "flex p-2 justify-center"
955
- }, /* @__PURE__ */ React7.createElement(IconButton2, {
1033
+ }, /* @__PURE__ */ React12.createElement(IconButton3, {
956
1034
  icon: "ph--plus--regular",
957
1035
  label: t("button add property"),
958
1036
  onClick: handleAdd,
@@ -1185,12 +1263,12 @@ export {
1185
1263
  DeprecatedFormInput,
1186
1264
  FieldEditor,
1187
1265
  Form,
1188
- FormActions,
1189
- GeiPointInput,
1266
+ GeoPointInput,
1190
1267
  InputHeader,
1191
1268
  NumberInput,
1192
1269
  SelectInput,
1193
1270
  TextInput,
1271
+ TupleInput,
1194
1272
  ViewEditor,
1195
1273
  cellClassesForFieldType,
1196
1274
  cellClassesForRowSelection,