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