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