@dxos/react-ui-form 0.7.1-staging.599df14

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 (93) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +4 -0
  3. package/dist/lib/browser/index.mjs +1008 -0
  4. package/dist/lib/browser/index.mjs.map +7 -0
  5. package/dist/lib/browser/meta.json +1 -0
  6. package/dist/lib/node/index.cjs +1028 -0
  7. package/dist/lib/node/index.cjs.map +7 -0
  8. package/dist/lib/node/meta.json +1 -0
  9. package/dist/lib/node-esm/index.mjs +1010 -0
  10. package/dist/lib/node-esm/index.mjs.map +7 -0
  11. package/dist/lib/node-esm/meta.json +1 -0
  12. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +16 -0
  13. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -0
  14. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +9 -0
  15. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -0
  16. package/dist/types/src/components/FieldEditor/index.d.ts +2 -0
  17. package/dist/types/src/components/FieldEditor/index.d.ts.map +1 -0
  18. package/dist/types/src/components/Form/Custom.d.ts +4 -0
  19. package/dist/types/src/components/Form/Custom.d.ts.map +1 -0
  20. package/dist/types/src/components/Form/Defaults.d.ts +7 -0
  21. package/dist/types/src/components/Form/Defaults.d.ts.map +1 -0
  22. package/dist/types/src/components/Form/Form.d.ts +26 -0
  23. package/dist/types/src/components/Form/Form.d.ts.map +1 -0
  24. package/dist/types/src/components/Form/Form.stories.d.ts +38 -0
  25. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -0
  26. package/dist/types/src/components/Form/Input.d.ts +29 -0
  27. package/dist/types/src/components/Form/Input.d.ts.map +1 -0
  28. package/dist/types/src/components/Form/factory.d.ts +8 -0
  29. package/dist/types/src/components/Form/factory.d.ts.map +1 -0
  30. package/dist/types/src/components/Form/index.d.ts +5 -0
  31. package/dist/types/src/components/Form/index.d.ts.map +1 -0
  32. package/dist/types/src/components/ViewEditor/Test.stories.d.ts +10 -0
  33. package/dist/types/src/components/ViewEditor/Test.stories.d.ts.map +1 -0
  34. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +18 -0
  35. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -0
  36. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +8 -0
  37. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -0
  38. package/dist/types/src/components/ViewEditor/index.d.ts +2 -0
  39. package/dist/types/src/components/ViewEditor/index.d.ts.map +1 -0
  40. package/dist/types/src/components/deprecated/Form.d.ts +15 -0
  41. package/dist/types/src/components/deprecated/Form.d.ts.map +1 -0
  42. package/dist/types/src/components/deprecated/Form.stories.d.ts +13 -0
  43. package/dist/types/src/components/deprecated/Form.stories.d.ts.map +1 -0
  44. package/dist/types/src/components/deprecated/FormInput.d.ts +11 -0
  45. package/dist/types/src/components/deprecated/FormInput.d.ts.map +1 -0
  46. package/dist/types/src/components/deprecated/index.d.ts +2 -0
  47. package/dist/types/src/components/deprecated/index.d.ts.map +1 -0
  48. package/dist/types/src/components/index.d.ts +5 -0
  49. package/dist/types/src/components/index.d.ts.map +1 -0
  50. package/dist/types/src/components/testing.d.ts +7 -0
  51. package/dist/types/src/components/testing.d.ts.map +1 -0
  52. package/dist/types/src/hooks/index.d.ts +2 -0
  53. package/dist/types/src/hooks/index.d.ts.map +1 -0
  54. package/dist/types/src/hooks/useForm.d.ts +58 -0
  55. package/dist/types/src/hooks/useForm.d.ts.map +1 -0
  56. package/dist/types/src/index.d.ts +6 -0
  57. package/dist/types/src/index.d.ts.map +1 -0
  58. package/dist/types/src/translations.d.ts +46 -0
  59. package/dist/types/src/translations.d.ts.map +1 -0
  60. package/dist/types/src/util/field.d.ts +22 -0
  61. package/dist/types/src/util/field.d.ts.map +1 -0
  62. package/dist/types/src/util/formatting.d.ts +15 -0
  63. package/dist/types/src/util/formatting.d.ts.map +1 -0
  64. package/dist/types/src/util/index.d.ts +3 -0
  65. package/dist/types/src/util/index.d.ts.map +1 -0
  66. package/package.json +76 -0
  67. package/src/components/FieldEditor/FieldEditor.stories.tsx +56 -0
  68. package/src/components/FieldEditor/FieldEditor.tsx +167 -0
  69. package/src/components/FieldEditor/index.ts +5 -0
  70. package/src/components/Form/Custom.tsx +65 -0
  71. package/src/components/Form/Defaults.tsx +130 -0
  72. package/src/components/Form/Form.stories.tsx +137 -0
  73. package/src/components/Form/Form.tsx +176 -0
  74. package/src/components/Form/Input.tsx +56 -0
  75. package/src/components/Form/factory.tsx +32 -0
  76. package/src/components/Form/index.ts +8 -0
  77. package/src/components/ViewEditor/Test.stories.tsx +45 -0
  78. package/src/components/ViewEditor/ViewEditor.stories.tsx +73 -0
  79. package/src/components/ViewEditor/ViewEditor.tsx +158 -0
  80. package/src/components/ViewEditor/index.ts +5 -0
  81. package/src/components/deprecated/Form.stories.tsx +62 -0
  82. package/src/components/deprecated/Form.tsx +119 -0
  83. package/src/components/deprecated/FormInput.tsx +58 -0
  84. package/src/components/deprecated/index.ts +5 -0
  85. package/src/components/index.ts +9 -0
  86. package/src/components/testing.tsx +24 -0
  87. package/src/hooks/index.ts +5 -0
  88. package/src/hooks/useForm.ts +229 -0
  89. package/src/index.ts +11 -0
  90. package/src/translations.ts +59 -0
  91. package/src/util/field.ts +137 -0
  92. package/src/util/formatting.ts +132 -0
  93. package/src/util/index.ts +6 -0
@@ -0,0 +1,1008 @@
1
+ // packages/ui/react-ui-form/src/translations.ts
2
+ var translationKey = "react-ui-form";
3
+ var translations_default = [
4
+ {
5
+ "en-US": {
6
+ [translationKey]: {
7
+ "fields label": "Fields",
8
+ // TODO(burdon): Standardize field/property.
9
+ "button add property": "Add property",
10
+ "field property label": "Property",
11
+ "field property placeholder": "Property name",
12
+ "field format label": "Type",
13
+ "field path label": "Field path",
14
+ "add field": "Add field",
15
+ "delete field": "Delete field",
16
+ "field limit reached": "Maximum number of fields reached",
17
+ // TODO(burdon): Factor out?
18
+ "button cancel": "Cancel",
19
+ "button save": "Save",
20
+ // FormatEnum
21
+ "format boolean": "Boolean",
22
+ "format currency": "Currency",
23
+ "format date": "Date",
24
+ "format date-time": "DateTime",
25
+ "format did": "DID",
26
+ "format duration": "Duration",
27
+ "format email": "Email",
28
+ "format formula": "Formula",
29
+ "format hostname": "Hostname",
30
+ "format integer": "Integer",
31
+ "format json": "JSON",
32
+ "format latlng": "LatLng",
33
+ "format markdown": "Markdown",
34
+ "format percent": "Percent",
35
+ "format ref": "Reference",
36
+ "format regex": "RegExp",
37
+ "format string": "String",
38
+ "format text": "Long text",
39
+ "format time": "Time",
40
+ "format timestamp": "Timestamp",
41
+ "format user": "User",
42
+ "format number": "Number",
43
+ "format uri": "URI",
44
+ "format uuid": "UUID",
45
+ // Placeholders
46
+ "placeholder latitude": "Latitude",
47
+ "placeholder longitude": "Longitude"
48
+ }
49
+ }
50
+ }
51
+ ];
52
+
53
+ // packages/ui/react-ui-form/src/components/deprecated/FormInput.tsx
54
+ import React from "react";
55
+ import { Input } from "@dxos/react-ui";
56
+ var DeprecatedFormInput = ({ label, description, secondary, children }) => {
57
+ const primary = /* @__PURE__ */ React.createElement("div", {
58
+ role: "none",
59
+ className: "flex w-full gap-4 py-1"
60
+ }, /* @__PURE__ */ React.createElement(Input.Root, null, /* @__PURE__ */ React.createElement("div", {
61
+ role: "none",
62
+ className: "flex flex-col w-full"
63
+ }, /* @__PURE__ */ React.createElement(Input.Label, {
64
+ classNames: "flex min-h-[40px] items-center"
65
+ }, label), description && /* @__PURE__ */ React.createElement(Input.DescriptionAndValidation, {
66
+ classNames: "mbs-0.5"
67
+ }, /* @__PURE__ */ React.createElement(Input.Description, null, description))), /* @__PURE__ */ React.createElement("div", {
68
+ role: "none"
69
+ }, /* @__PURE__ */ React.createElement("div", {
70
+ role: "none",
71
+ className: "flex min-h-[40px] items-center"
72
+ }, children))));
73
+ if (secondary) {
74
+ return /* @__PURE__ */ React.createElement("div", {
75
+ role: "none",
76
+ className: "flex flex-col w-full"
77
+ }, primary, secondary);
78
+ }
79
+ return primary;
80
+ };
81
+
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";
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 React5, { useEffect as useEffect2, useMemo as useMemo2 } from "react";
91
+ import { AST, S } from "@dxos/echo-schema";
92
+ import { findNode, getDiscriminatedType, isDiscriminatedUnion } from "@dxos/effect";
93
+ import { log as log2 } from "@dxos/log";
94
+ import { IconButton, useTranslation as useTranslation2 } from "@dxos/react-ui";
95
+ import { mx } from "@dxos/react-ui-theme";
96
+ import { getSchemaProperties } from "@dxos/schema";
97
+ import { isNotFalsy } from "@dxos/util";
98
+
99
+ // packages/ui/react-ui-form/src/components/Form/factory.tsx
100
+ import { FormatEnum } from "@dxos/echo-schema";
101
+
102
+ // packages/ui/react-ui-form/src/components/Form/Custom.tsx
103
+ import React3 from "react";
104
+ import { Input as Input2, useTranslation } from "@dxos/react-ui";
105
+
106
+ // packages/ui/react-ui-form/src/components/Form/Input.tsx
107
+ import React2 from "react";
108
+ import { Icon, Tooltip } from "@dxos/react-ui";
109
+ import { errorText } from "@dxos/react-ui-theme";
110
+ var InputHeader = ({ children, error }) => {
111
+ return /* @__PURE__ */ React2.createElement("div", {
112
+ role: "none",
113
+ className: "flex justify-between items-center mb-1"
114
+ }, children, error && /* @__PURE__ */ React2.createElement(Tooltip.Root, null, /* @__PURE__ */ React2.createElement(Tooltip.Trigger, {
115
+ asChild: true
116
+ }, /* @__PURE__ */ React2.createElement(Icon, {
117
+ icon: "ph--warning--regular",
118
+ size: 4,
119
+ classNames: errorText
120
+ })), /* @__PURE__ */ React2.createElement(Tooltip.Content, {
121
+ side: "bottom"
122
+ }, /* @__PURE__ */ React2.createElement(Tooltip.Arrow, null), error)));
123
+ };
124
+
125
+ // packages/ui/react-ui-form/src/components/Form/Custom.tsx
126
+ var LatLngInput = ({ property, type, label, disabled, getStatus, getValue, onValueChange, onBlur }) => {
127
+ const { t } = useTranslation(translationKey);
128
+ const { status, error } = getStatus?.(property);
129
+ const { lat = 0, lng = 0 } = getValue(property) ?? {};
130
+ return /* @__PURE__ */ React3.createElement(Input2.Root, {
131
+ validationValence: status
132
+ }, /* @__PURE__ */ React3.createElement(InputHeader, {
133
+ error
134
+ }, /* @__PURE__ */ React3.createElement(Input2.Label, null, label)), /* @__PURE__ */ React3.createElement("div", {
135
+ className: "grid grid-cols-2 gap-2"
136
+ }, /* @__PURE__ */ React3.createElement(Input2.TextInput, {
137
+ type: "number",
138
+ disabled,
139
+ placeholder: t("placeholder latitude"),
140
+ value: lat,
141
+ onChange: (event) => onValueChange(property, type, {
142
+ lat: safeParseFloat(event.target.value, 0),
143
+ lng
144
+ }),
145
+ onBlur
146
+ }), /* @__PURE__ */ React3.createElement(Input2.TextInput, {
147
+ type: "number",
148
+ disabled,
149
+ placeholder: t("placeholder longitude"),
150
+ value: lng,
151
+ onChange: (event) => onValueChange(property, type, {
152
+ lat,
153
+ lng: safeParseFloat(event.target.value, 0)
154
+ }),
155
+ onBlur
156
+ })));
157
+ };
158
+ var safeParseFloat = (str, defaultValue) => {
159
+ try {
160
+ return parseFloat(str);
161
+ } catch {
162
+ return defaultValue ?? 0;
163
+ }
164
+ };
165
+
166
+ // packages/ui/react-ui-form/src/components/Form/Defaults.tsx
167
+ import React4 from "react";
168
+ import { Input as Input3, Select } from "@dxos/react-ui";
169
+ var TextInput = ({ property, type, label, disabled, placeholder, getStatus, getValue, onValueChange, onBlur }) => {
170
+ const { status, error } = getStatus?.(property);
171
+ return /* @__PURE__ */ React4.createElement(Input3.Root, {
172
+ validationValence: status
173
+ }, /* @__PURE__ */ React4.createElement(InputHeader, {
174
+ error
175
+ }, /* @__PURE__ */ React4.createElement(Input3.Label, null, label)), /* @__PURE__ */ React4.createElement(Input3.TextInput, {
176
+ disabled,
177
+ placeholder,
178
+ value: getValue(property) ?? "",
179
+ onChange: (event) => onValueChange(property, type, event.target.value),
180
+ onBlur
181
+ }));
182
+ };
183
+ var NumberInput = ({ property, type, label, disabled, placeholder, getStatus, getValue, onValueChange, onBlur }) => {
184
+ const { status, error } = getStatus?.(property);
185
+ return /* @__PURE__ */ React4.createElement(Input3.Root, {
186
+ validationValence: status
187
+ }, /* @__PURE__ */ React4.createElement(InputHeader, {
188
+ error
189
+ }, /* @__PURE__ */ React4.createElement(Input3.Label, null, label)), /* @__PURE__ */ React4.createElement(Input3.TextInput, {
190
+ type: "number",
191
+ disabled,
192
+ placeholder,
193
+ value: getValue(property) ?? 0,
194
+ onChange: (event) => onValueChange(property, type, event.target.value),
195
+ onBlur
196
+ }));
197
+ };
198
+ var BooleanInput = ({ property, type, label, getStatus, getValue, onValueChange }) => {
199
+ const { status, error } = getStatus?.(property);
200
+ return /* @__PURE__ */ React4.createElement(Input3.Root, {
201
+ validationValence: status
202
+ }, /* @__PURE__ */ React4.createElement(InputHeader, {
203
+ error
204
+ }, /* @__PURE__ */ React4.createElement(Input3.Label, null, label)), /* @__PURE__ */ React4.createElement(Input3.Switch, {
205
+ checked: getValue(property),
206
+ onCheckedChange: (value) => onValueChange(property, type, value)
207
+ }));
208
+ };
209
+ var SelectInput = ({ property, type, label, disabled, placeholder, options, getStatus, getValue, onValueChange }) => {
210
+ const { status, error } = getStatus?.(property);
211
+ return /* @__PURE__ */ React4.createElement(Input3.Root, {
212
+ validationValence: status
213
+ }, /* @__PURE__ */ React4.createElement(InputHeader, {
214
+ error
215
+ }, /* @__PURE__ */ React4.createElement(Input3.Label, null, label)), /* @__PURE__ */ React4.createElement(Select.Root, {
216
+ value: getValue(property),
217
+ onValueChange: (value) => onValueChange(property, type, value)
218
+ }, /* @__PURE__ */ React4.createElement(Select.TriggerButton, {
219
+ classNames: "is-full",
220
+ disabled,
221
+ placeholder
222
+ }), /* @__PURE__ */ React4.createElement(Select.Portal, null, /* @__PURE__ */ React4.createElement(Select.Content, null, /* @__PURE__ */ React4.createElement(Select.Viewport, null, options?.map(({ value, label: label2 }) => (
223
+ // NOTE: Numeric values are converted to and from strings.
224
+ /* @__PURE__ */ React4.createElement(Select.Option, {
225
+ key: String(value),
226
+ value: String(value)
227
+ }, label2 ?? String(value))
228
+ )))))));
229
+ };
230
+
231
+ // packages/ui/react-ui-form/src/components/Form/factory.tsx
232
+ var getInputComponent = (type, format) => {
233
+ switch (format) {
234
+ case FormatEnum.LatLng:
235
+ return LatLngInput;
236
+ }
237
+ switch (type) {
238
+ case "string":
239
+ return TextInput;
240
+ case "number":
241
+ return NumberInput;
242
+ case "boolean":
243
+ return BooleanInput;
244
+ }
245
+ };
246
+
247
+ // packages/ui/react-ui-form/src/hooks/useForm.ts
248
+ import { useCallback, useEffect, useMemo, useState } from "react";
249
+ import { invariant } from "@dxos/invariant";
250
+ import { log } from "@dxos/log";
251
+ import { validateSchema } from "@dxos/schema";
252
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-form/src/hooks/useForm.ts";
253
+ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, onSubmit }) => {
254
+ const [values, setValues] = useState(initialValues);
255
+ useEffect(() => {
256
+ setValues(initialValues);
257
+ }, [
258
+ initialValues
259
+ ]);
260
+ const [touched, setTouched] = useState(createKeySet(initialValues, false));
261
+ const [changed, setChanged] = useState(createKeySet(initialValues, false));
262
+ const [errors, setErrors] = useState({});
263
+ const validate = useCallback((values2) => {
264
+ let errors2 = validateSchema(schema, values2) ?? [];
265
+ if (errors2.length === 0 && onValidate) {
266
+ errors2 = onValidate(values2) ?? [];
267
+ }
268
+ setErrors(flatMap(errors2));
269
+ return errors2.length === 0;
270
+ }, [
271
+ schema,
272
+ onValidate
273
+ ]);
274
+ const [schemaDirty, setSchemaDirty] = useState(false);
275
+ useEffect(() => setSchemaDirty(true), [
276
+ schema
277
+ ]);
278
+ useEffect(() => {
279
+ if (schemaDirty) {
280
+ validate(values);
281
+ setSchemaDirty(false);
282
+ }
283
+ }, [
284
+ values,
285
+ schemaDirty
286
+ ]);
287
+ const canSubmit = useMemo(() => Object.keys(values).every((property) => touched[property] === false || !errors[property]), [
288
+ values,
289
+ touched,
290
+ errors
291
+ ]);
292
+ const handleSubmit = useCallback(() => {
293
+ if (validate(values)) {
294
+ onSubmit?.(values, {
295
+ changed
296
+ });
297
+ }
298
+ }, [
299
+ values,
300
+ validate,
301
+ onSubmit
302
+ ]);
303
+ const getStatus = useCallback((property) => ({
304
+ status: errors[property] ? "error" : void 0,
305
+ error: errors[property] ? errors[property] : void 0
306
+ }), [
307
+ touched,
308
+ errors
309
+ ]);
310
+ const getValue = (property) => {
311
+ return values[property];
312
+ };
313
+ const onValueChange = (property, type, value) => {
314
+ let parsedValue = value;
315
+ try {
316
+ if (type === "number") {
317
+ parsedValue = parseFloat(value) || 0;
318
+ }
319
+ } catch (err) {
320
+ log.catch(err, void 0, {
321
+ F: __dxlog_file,
322
+ L: 164,
323
+ S: void 0,
324
+ C: (f, a) => f(...a)
325
+ });
326
+ parsedValue = void 0;
327
+ }
328
+ const newValues = {
329
+ ...values,
330
+ [property]: parsedValue
331
+ };
332
+ setValues(newValues);
333
+ setChanged((prev) => ({
334
+ ...prev,
335
+ [property]: true
336
+ }));
337
+ onValuesChanged?.(newValues);
338
+ const isValid = validate(newValues);
339
+ if (isValid && onValid) {
340
+ onValid(newValues, {
341
+ changed
342
+ });
343
+ }
344
+ };
345
+ const onBlur = useCallback((event) => {
346
+ const { name } = event.target;
347
+ setTouched((touched2) => ({
348
+ ...touched2,
349
+ [name]: true
350
+ }));
351
+ if (event.relatedTarget?.getAttribute("type") === "submit") {
352
+ setTouched(createKeySet(values, true));
353
+ }
354
+ validate(values);
355
+ }, [
356
+ validate,
357
+ values
358
+ ]);
359
+ return {
360
+ // State.
361
+ values,
362
+ errors,
363
+ touched,
364
+ changed,
365
+ canSubmit,
366
+ handleSubmit,
367
+ // Field utils.
368
+ getStatus,
369
+ getValue,
370
+ onValueChange,
371
+ onBlur
372
+ };
373
+ };
374
+ var createKeySet = (obj, value) => {
375
+ invariant(obj, void 0, {
376
+ F: __dxlog_file,
377
+ L: 215,
378
+ S: void 0,
379
+ A: [
380
+ "obj",
381
+ ""
382
+ ]
383
+ });
384
+ return Object.keys(obj).reduce((acc, key) => ({
385
+ ...acc,
386
+ [key]: value
387
+ }), {});
388
+ };
389
+ var flatMap = (errors) => {
390
+ return errors.reduce((result, { path, message }) => {
391
+ if (!(path in result)) {
392
+ result[path] = message;
393
+ }
394
+ return result;
395
+ }, {});
396
+ };
397
+
398
+ // packages/ui/react-ui-form/src/components/Form/Form.tsx
399
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-form/src/components/Form/Form.tsx";
400
+ var padding = "px-2";
401
+ var Form = ({ classNames, schema, values: initialValues, path = [], readonly, filter, sort, autoSave, onValuesChanged, onValidate, onSubmit, onCancel, Custom }) => {
402
+ const { t } = useTranslation2(translationKey);
403
+ const onValid = useMemo2(() => autoSave ? onSubmit : void 0, [
404
+ autoSave,
405
+ onSubmit
406
+ ]);
407
+ const { canSubmit, values, errors, handleSubmit, ...inputProps } = useForm({
408
+ schema,
409
+ initialValues,
410
+ onValuesChanged,
411
+ onValidate,
412
+ onValid,
413
+ onSubmit
414
+ });
415
+ const props = useMemo2(() => {
416
+ const props2 = getSchemaProperties(schema.ast, values);
417
+ const filtered = filter ? filter(props2) : props2;
418
+ const findIndex = (props3, prop) => {
419
+ const idx = props3.findIndex((p) => p === prop);
420
+ return idx === -1 ? Infinity : idx;
421
+ };
422
+ return sort ? filtered.sort(({ name: a }, { name: b }) => findIndex(sort, a) - findIndex(sort, b)) : filtered;
423
+ }, [
424
+ schema,
425
+ filter,
426
+ values
427
+ ]);
428
+ useEffect2(() => {
429
+ if (errors && Object.keys(errors).length) {
430
+ log2.warn("validation", {
431
+ errors
432
+ }, {
433
+ F: __dxlog_file2,
434
+ L: 93,
435
+ S: void 0,
436
+ C: (f, a) => f(...a)
437
+ });
438
+ }
439
+ }, [
440
+ errors
441
+ ]);
442
+ return /* @__PURE__ */ React5.createElement("div", {
443
+ role: "form",
444
+ className: mx("flex flex-col w-full gap-2 py-2", classNames)
445
+ }, props.map(({ prop, name, type, format, title, description, examples }) => {
446
+ const key = [
447
+ ...path,
448
+ name
449
+ ];
450
+ const InputComponent = Custom?.[key.join(".")] ?? getInputComponent(type, format);
451
+ if (!InputComponent) {
452
+ if (type === "object") {
453
+ const baseNode = findNode(prop.type, isDiscriminatedUnion);
454
+ const typeLiteral = baseNode ? getDiscriminatedType(baseNode, values[name]) : findNode(prop.type, AST.isTypeLiteral);
455
+ if (typeLiteral) {
456
+ const schema2 = S.make(typeLiteral);
457
+ return /* @__PURE__ */ React5.createElement("div", {
458
+ key: name,
459
+ role: "none"
460
+ }, /* @__PURE__ */ React5.createElement("div", {
461
+ className: padding
462
+ }, title ?? name), /* @__PURE__ */ React5.createElement(Form, {
463
+ schema: schema2,
464
+ path: key,
465
+ values: values[name] ?? {},
466
+ onValuesChanged: (childValues) => {
467
+ inputProps.onValueChange(name, "object", childValues);
468
+ },
469
+ Custom
470
+ }));
471
+ }
472
+ }
473
+ log2("no renderer for property", {
474
+ name,
475
+ type
476
+ }, {
477
+ F: __dxlog_file2,
478
+ L: 132,
479
+ S: void 0,
480
+ C: (f, a) => f(...a)
481
+ });
482
+ return null;
483
+ }
484
+ const placeholder = examples?.length ? `Example: ${examples[0]}` : description;
485
+ return /* @__PURE__ */ React5.createElement("div", {
486
+ key: name,
487
+ role: "none",
488
+ className: padding
489
+ }, /* @__PURE__ */ React5.createElement(InputComponent, {
490
+ type,
491
+ format,
492
+ property: name,
493
+ disabled: readonly,
494
+ label: title ?? name,
495
+ placeholder,
496
+ ...inputProps
497
+ }));
498
+ }).filter(isNotFalsy), (onCancel || onSubmit) && !autoSave && /* @__PURE__ */ React5.createElement("div", {
499
+ role: "none",
500
+ className: "flex justify-center"
501
+ }, /* @__PURE__ */ React5.createElement("div", {
502
+ role: "none",
503
+ className: mx(onCancel && !readonly && "grid grid-cols-2 gap-2")
504
+ }, onCancel && !readonly && /* @__PURE__ */ React5.createElement(IconButton, {
505
+ icon: "ph--x--regular",
506
+ label: t("button cancel"),
507
+ onClick: onCancel
508
+ }), onSubmit && /* @__PURE__ */ React5.createElement(IconButton, {
509
+ type: "submit",
510
+ disabled: !canSubmit,
511
+ icon: "ph--check--regular",
512
+ label: t("button save"),
513
+ onClick: handleSubmit
514
+ }))));
515
+ };
516
+
517
+ // packages/ui/react-ui-form/src/components/FieldEditor/FieldEditor.tsx
518
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-form/src/components/FieldEditor/FieldEditor.tsx";
519
+ var FieldEditor = ({ view, projection, field, registry, onClose, onCancel }) => {
520
+ const { t } = useTranslation3(translationKey);
521
+ const [props, setProps] = useState2(projection.getFieldProjection(field.id).props);
522
+ useEffect3(() => setProps(projection.getFieldProjection(field.id).props), [
523
+ field,
524
+ projection
525
+ ]);
526
+ const [schemas, setSchemas] = useState2([]);
527
+ useEffect3(() => {
528
+ if (!registry) {
529
+ return;
530
+ }
531
+ const subscription = registry.subscribe(setSchemas);
532
+ const t2 = setTimeout(async () => {
533
+ const schemas2 = await registry.query();
534
+ setSchemas(schemas2);
535
+ });
536
+ return () => {
537
+ clearTimeout(t2);
538
+ subscription?.();
539
+ };
540
+ }, [
541
+ registry
542
+ ]);
543
+ const [schema, setSchema] = useState2();
544
+ useEffect3(() => {
545
+ setSchema(schemas.find((schema2) => schema2.typename === props?.referenceSchema));
546
+ }, [
547
+ schemas,
548
+ props?.referenceSchema
549
+ ]);
550
+ const [{ fieldSchema }, setFieldSchema] = useState2({
551
+ fieldSchema: getFormatSchema(props?.format)
552
+ });
553
+ const handleValuesChanged = useCallback2((_props) => {
554
+ if (_props.format !== props.format) {
555
+ setFieldSchema({
556
+ fieldSchema: getFormatSchema(_props.format)
557
+ });
558
+ }
559
+ if (_props.referenceSchema !== props.referenceSchema) {
560
+ setSchema(schemas.find((schema2) => schema2.typename === _props.referenceSchema));
561
+ }
562
+ setProps((props2) => {
563
+ const type = formatToType[_props.format];
564
+ if (props2.type !== type) {
565
+ return {
566
+ ...props2,
567
+ ..._props,
568
+ type
569
+ };
570
+ }
571
+ return props2;
572
+ });
573
+ }, [
574
+ schemas,
575
+ props
576
+ ]);
577
+ const handleValidate = useCallback2(({ property }) => {
578
+ if (property && view.fields.find((f) => f.path === property && f.path !== field.path)) {
579
+ return [
580
+ {
581
+ path: "property",
582
+ message: `property is not unique: '${property}'`
583
+ }
584
+ ];
585
+ }
586
+ }, [
587
+ view.fields,
588
+ field
589
+ ]);
590
+ const handleSubmit = useCallback2((props2) => {
591
+ projection.setFieldProjection({
592
+ field,
593
+ props: props2
594
+ });
595
+ onClose();
596
+ }, [
597
+ projection,
598
+ field,
599
+ onClose
600
+ ]);
601
+ const handleCancel = useCallback2(() => {
602
+ requestAnimationFrame(() => onCancel?.());
603
+ onClose();
604
+ }, [
605
+ onClose
606
+ ]);
607
+ if (!fieldSchema) {
608
+ log3.warn("invalid format", {
609
+ props
610
+ }, {
611
+ F: __dxlog_file3,
612
+ L: 118,
613
+ S: void 0,
614
+ C: (f, a) => f(...a)
615
+ });
616
+ return null;
617
+ }
618
+ return /* @__PURE__ */ React6.createElement(Form, {
619
+ key: field.id,
620
+ autoFocus: true,
621
+ values: props,
622
+ schema: fieldSchema,
623
+ filter: (props2) => props2.filter((p) => p.name !== "type"),
624
+ sort: [
625
+ "property",
626
+ "format"
627
+ ],
628
+ onValuesChanged: handleValuesChanged,
629
+ onValidate: handleValidate,
630
+ onSubmit: handleSubmit,
631
+ onCancel: handleCancel,
632
+ Custom: {
633
+ ["format"]: (props2) => /* @__PURE__ */ React6.createElement(SelectInput, {
634
+ ...props2,
635
+ options: FormatEnums.filter((value) => value !== FormatEnum2.None).map((value) => ({
636
+ value,
637
+ label: t(`format ${value}`)
638
+ }))
639
+ }),
640
+ ["referenceSchema"]: (props2) => /* @__PURE__ */ React6.createElement(SelectInput, {
641
+ ...props2,
642
+ options: schemas.map((schema2) => ({
643
+ value: schema2.typename
644
+ }))
645
+ }),
646
+ ["referencePath"]: (props2) => /* @__PURE__ */ React6.createElement(SelectInput, {
647
+ ...props2,
648
+ options: schema ? getSchemaProperties2(schema.schema.ast).sort(sortProperties).map((p) => ({
649
+ value: p.name
650
+ })) : []
651
+ })
652
+ }
653
+ });
654
+ };
655
+
656
+ // packages/ui/react-ui-form/src/components/ViewEditor/ViewEditor.tsx
657
+ import React7, { useCallback as useCallback3, useMemo as useMemo3, useState as useState3 } from "react";
658
+ import { AST as AST2, Format, S as S2 } from "@dxos/echo-schema";
659
+ import { IconButton as IconButton2, useTranslation as useTranslation4 } from "@dxos/react-ui";
660
+ import { List } from "@dxos/react-ui-list";
661
+ import { ghostHover, inputTextLabel, mx as mx2 } from "@dxos/react-ui-theme";
662
+ import { FieldSchema, ViewProjection, VIEW_FIELD_LIMIT } from "@dxos/schema";
663
+ import { arrayMove } from "@dxos/util";
664
+ var grid = "grid grid-cols-[32px_1fr_32px] min-bs-[2.5rem]";
665
+ var ViewMetaSchema = S2.Struct({
666
+ name: S2.String.annotations({
667
+ [AST2.TitleAnnotationId]: "View"
668
+ }),
669
+ type: Format.DXN.annotations({
670
+ [AST2.TitleAnnotationId]: "Typename"
671
+ })
672
+ }).pipe(S2.mutable);
673
+ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading = false, onDelete }) => {
674
+ const { t } = useTranslation4(translationKey);
675
+ const projection = useMemo3(() => new ViewProjection(schema, view), [
676
+ schema,
677
+ view
678
+ ]);
679
+ const [field, setField] = useState3();
680
+ const viewValues = useMemo3(() => {
681
+ return {
682
+ name: view.name,
683
+ // TODO(burdon): Need to warn user of possible consequences of editing.
684
+ // TODO(burdon): Settings should have domain name owned by user.
685
+ type: view.query.type
686
+ };
687
+ }, [
688
+ view
689
+ ]);
690
+ const handleViewUpdate = useCallback3(({ name, type }) => {
691
+ requestAnimationFrame(() => {
692
+ view.name = name;
693
+ view.query.type = type;
694
+ schema.updateTypename(type);
695
+ });
696
+ }, [
697
+ view,
698
+ schema
699
+ ]);
700
+ const handleSelect = useCallback3((field2) => {
701
+ setField((f) => f === field2 ? void 0 : field2);
702
+ }, []);
703
+ const handleAdd = useCallback3(() => {
704
+ const field2 = projection.createFieldProjection();
705
+ setField(field2);
706
+ }, [
707
+ view
708
+ ]);
709
+ const handleMove = useCallback3((fromIndex, toIndex) => {
710
+ arrayMove(view.fields, fromIndex, toIndex);
711
+ }, [
712
+ view.fields
713
+ ]);
714
+ const handleClose = useCallback3(() => setField(void 0), []);
715
+ return /* @__PURE__ */ React7.createElement("div", {
716
+ role: "none",
717
+ className: mx2("flex flex-col w-full divide-y divide-separator", classNames)
718
+ }, /* @__PURE__ */ React7.createElement(Form, {
719
+ autoFocus: true,
720
+ autoSave: true,
721
+ schema: ViewMetaSchema,
722
+ values: viewValues,
723
+ onSubmit: handleViewUpdate
724
+ }), /* @__PURE__ */ React7.createElement("div", null, /* @__PURE__ */ React7.createElement("div", {
725
+ role: "none",
726
+ className: "p-2"
727
+ }, /* @__PURE__ */ React7.createElement("label", {
728
+ className: mx2(inputTextLabel)
729
+ }, t("fields label"))), /* @__PURE__ */ React7.createElement(List.Root, {
730
+ items: view.fields,
731
+ isItem: S2.is(FieldSchema),
732
+ getId: (field2) => field2.id,
733
+ onMove: handleMove
734
+ }, ({ items: fields }) => /* @__PURE__ */ React7.createElement(React7.Fragment, null, showHeading && /* @__PURE__ */ React7.createElement("div", {
735
+ role: "heading",
736
+ className: grid
737
+ }, /* @__PURE__ */ React7.createElement("div", null), /* @__PURE__ */ React7.createElement("div", {
738
+ className: "flex items-center text-sm"
739
+ }, t("field path label"))), /* @__PURE__ */ React7.createElement("div", {
740
+ role: "list",
741
+ className: "flex flex-col w-full"
742
+ }, fields?.map((field2) => /* @__PURE__ */ React7.createElement(List.Item, {
743
+ key: field2.id,
744
+ item: field2,
745
+ classNames: mx2(grid, ghostHover, "cursor-pointer")
746
+ }, /* @__PURE__ */ React7.createElement(List.ItemDragHandle, null), /* @__PURE__ */ React7.createElement(List.ItemTitle, {
747
+ onClick: () => handleSelect(field2)
748
+ }, field2.path), /* @__PURE__ */ React7.createElement(List.ItemDeleteButton, {
749
+ disabled: view.fields.length <= 1,
750
+ onClick: () => onDelete(field2.id)
751
+ }))))))), field && /* @__PURE__ */ React7.createElement(FieldEditor, {
752
+ key: field.id,
753
+ view,
754
+ projection,
755
+ field,
756
+ registry,
757
+ onClose: handleClose
758
+ }), !readonly && !field && /* @__PURE__ */ React7.createElement("div", {
759
+ className: "flex p-2 justify-center"
760
+ }, /* @__PURE__ */ React7.createElement(IconButton2, {
761
+ icon: "ph--plus--regular",
762
+ label: t("button add property"),
763
+ onClick: handleAdd,
764
+ // TODO(burdon): Show field limit in ux (not tooltip).
765
+ disabled: view.fields.length >= VIEW_FIELD_LIMIT
766
+ })));
767
+ };
768
+
769
+ // packages/ui/react-ui-form/src/util/field.ts
770
+ import { FormatEnum as FormatEnum3, TypeEnum } from "@dxos/echo-schema";
771
+ var parseValue = ({ type, format, value }) => {
772
+ if (value === void 0 || value === null || value === "") {
773
+ return void 0;
774
+ }
775
+ const parseScalar = (type2) => {
776
+ switch (type2) {
777
+ case TypeEnum.Boolean: {
778
+ if (typeof value === "string") {
779
+ const lowercaseValue = value.toLowerCase();
780
+ if (lowercaseValue === "0" || lowercaseValue === "false") {
781
+ return false;
782
+ } else if (lowercaseValue === "1" || lowercaseValue === "true") {
783
+ return true;
784
+ }
785
+ }
786
+ return Boolean(value);
787
+ }
788
+ case TypeEnum.Number: {
789
+ const cleaned = typeof value === "string" ? value.replace(/[^0-9.-]+/g, "") : value;
790
+ const num = Number(cleaned);
791
+ return Number.isNaN(num) ? null : num;
792
+ }
793
+ case TypeEnum.String:
794
+ case TypeEnum.Ref:
795
+ return String(value);
796
+ default: {
797
+ return value;
798
+ }
799
+ }
800
+ };
801
+ if (!format) {
802
+ return parseScalar(type);
803
+ }
804
+ switch (format) {
805
+ case FormatEnum3.Boolean:
806
+ return parseScalar(TypeEnum.Boolean);
807
+ case FormatEnum3.Number:
808
+ case FormatEnum3.Percent:
809
+ case FormatEnum3.Currency:
810
+ return parseScalar(TypeEnum.Number);
811
+ case FormatEnum3.String:
812
+ case FormatEnum3.Markdown: {
813
+ return parseScalar(TypeEnum.String);
814
+ }
815
+ case FormatEnum3.Ref:
816
+ throw new Error(`unexpected format: ${FormatEnum3.Ref}`);
817
+ case FormatEnum3.DateTime:
818
+ case FormatEnum3.Date:
819
+ case FormatEnum3.Time:
820
+ case FormatEnum3.Timestamp: {
821
+ const date = new Date(value);
822
+ return isNaN(date.getTime()) ? null : date;
823
+ }
824
+ default: {
825
+ return value;
826
+ }
827
+ }
828
+ };
829
+ var cellClassesForFieldType = ({ type, format }) => {
830
+ if (!format) {
831
+ switch (type) {
832
+ case TypeEnum.Number:
833
+ return [
834
+ "text-right",
835
+ "font-mono"
836
+ ];
837
+ case TypeEnum.Boolean:
838
+ return [
839
+ "text-right",
840
+ "font-mono"
841
+ ];
842
+ case TypeEnum.String:
843
+ return void 0;
844
+ case TypeEnum.Ref:
845
+ return void 0;
846
+ default: {
847
+ return void 0;
848
+ }
849
+ }
850
+ }
851
+ switch (format) {
852
+ case FormatEnum3.Markdown:
853
+ return void 0;
854
+ case FormatEnum3.Timestamp:
855
+ case FormatEnum3.DateTime:
856
+ case FormatEnum3.Date:
857
+ case FormatEnum3.Time:
858
+ return [
859
+ "font-mono"
860
+ ];
861
+ case FormatEnum3.Currency:
862
+ return [
863
+ "text-right"
864
+ ];
865
+ case FormatEnum3.JSON:
866
+ return [
867
+ "font-mono"
868
+ ];
869
+ default:
870
+ return void 0;
871
+ }
872
+ };
873
+ var cellClassesForRowSelection = (selected) => selected ? [
874
+ "!bg-gridCellSelected"
875
+ ] : void 0;
876
+ var pathNotUniqueError = (path) => ({
877
+ path: "path",
878
+ message: `'${path}' is already present`
879
+ });
880
+
881
+ // packages/ui/react-ui-form/src/util/formatting.ts
882
+ import { FormatEnum as FormatEnum4, TypeEnum as TypeEnum2 } from "@dxos/echo-schema";
883
+ var formatForDisplay = ({ type, format, value, locale = void 0 }) => {
884
+ const formatScalar = (type2) => {
885
+ switch (type2) {
886
+ case TypeEnum2.Boolean:
887
+ return value.toLocaleString().toUpperCase();
888
+ case TypeEnum2.Number:
889
+ return value.toLocaleString(locale);
890
+ case TypeEnum2.String:
891
+ case TypeEnum2.Ref:
892
+ return String(value);
893
+ default: {
894
+ return String(value);
895
+ }
896
+ }
897
+ };
898
+ if (!format) {
899
+ return formatScalar(type);
900
+ }
901
+ switch (format) {
902
+ case FormatEnum4.Boolean: {
903
+ return formatScalar(TypeEnum2.Boolean);
904
+ }
905
+ case FormatEnum4.Number: {
906
+ return formatScalar(TypeEnum2.Number);
907
+ }
908
+ case FormatEnum4.String:
909
+ case FormatEnum4.Ref: {
910
+ return formatScalar(TypeEnum2.String);
911
+ }
912
+ case FormatEnum4.Percent: {
913
+ return `${value * 100}%`;
914
+ }
915
+ case FormatEnum4.Currency: {
916
+ return value.toLocaleString(locale, {
917
+ style: "currency",
918
+ currency: "USD",
919
+ minimumFractionDigits: 2,
920
+ maximumFractionDigits: 2
921
+ });
922
+ }
923
+ case FormatEnum4.DateTime:
924
+ case FormatEnum4.Date:
925
+ case FormatEnum4.Time: {
926
+ const date = new Date(value);
927
+ if (format === FormatEnum4.DateTime) {
928
+ return date.toLocaleString(locale);
929
+ } else if (format === FormatEnum4.Date) {
930
+ return date.toLocaleDateString(locale);
931
+ } else {
932
+ return date.toLocaleTimeString(locale);
933
+ }
934
+ }
935
+ default: {
936
+ if (value === null || value === "undefined") {
937
+ return "";
938
+ }
939
+ return String(value);
940
+ }
941
+ }
942
+ };
943
+ var formatForEditing = ({ type, format, value, locale = void 0 }) => {
944
+ const formatScalar = (type2) => {
945
+ switch (type2) {
946
+ case TypeEnum2.Boolean:
947
+ case TypeEnum2.Number:
948
+ case TypeEnum2.String:
949
+ case TypeEnum2.Ref:
950
+ return String(value);
951
+ default:
952
+ return String(value);
953
+ }
954
+ };
955
+ if (!format) {
956
+ return formatScalar(type);
957
+ }
958
+ switch (format) {
959
+ case FormatEnum4.Boolean:
960
+ case FormatEnum4.Number:
961
+ case FormatEnum4.String:
962
+ case FormatEnum4.Ref: {
963
+ return formatScalar(type);
964
+ }
965
+ case FormatEnum4.Percent: {
966
+ return String(value * 100);
967
+ }
968
+ case FormatEnum4.Currency: {
969
+ return String(value);
970
+ }
971
+ case FormatEnum4.DateTime: {
972
+ const date = new Date(value);
973
+ return date.toISOString();
974
+ }
975
+ case FormatEnum4.Date: {
976
+ const date = new Date(value);
977
+ return date.toISOString().split("T")[0];
978
+ }
979
+ case FormatEnum4.Time: {
980
+ const date = new Date(value);
981
+ return date.toISOString().split("T")[1].split(".")[0];
982
+ }
983
+ default: {
984
+ return String(value);
985
+ }
986
+ }
987
+ };
988
+ export {
989
+ BooleanInput,
990
+ DeprecatedFormInput,
991
+ FieldEditor,
992
+ Form,
993
+ InputHeader,
994
+ LatLngInput,
995
+ NumberInput,
996
+ SelectInput,
997
+ TextInput,
998
+ ViewEditor,
999
+ cellClassesForFieldType,
1000
+ cellClassesForRowSelection,
1001
+ formatForDisplay,
1002
+ formatForEditing,
1003
+ parseValue,
1004
+ pathNotUniqueError,
1005
+ translations_default as translations,
1006
+ useForm
1007
+ };
1008
+ //# sourceMappingURL=index.mjs.map