@dxos/react-ui-form 0.8.4-main.84f28bd → 0.8.4-main.937b3ca

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 (194) hide show
  1. package/dist/lib/browser/index.mjs +2203 -2067
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +2203 -2067
  5. package/dist/lib/node-esm/index.mjs.map +4 -4
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/types/src/components/ControlSection/ControlSection.d.ts +4 -4
  8. package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +1 -1
  9. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts +84 -5
  10. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +1 -1
  11. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +8 -8
  12. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  13. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +84 -7
  14. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
  15. package/dist/types/src/components/Form/Form-old.stories.d.ts +131 -0
  16. package/dist/types/src/components/Form/Form-old.stories.d.ts.map +1 -0
  17. package/dist/types/src/components/Form/Form.d.ts +81 -25
  18. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  19. package/dist/types/src/components/Form/Form.stories.d.ts +127 -55
  20. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  21. package/dist/types/src/components/Form/FormField.d.ts +37 -0
  22. package/dist/types/src/components/Form/FormField.d.ts.map +1 -0
  23. package/dist/types/src/components/Form/FormFieldComponent.d.ts +78 -0
  24. package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -0
  25. package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts +9 -0
  26. package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts.map +1 -0
  27. package/dist/types/src/components/Form/FormFieldSet.d.ts +19 -0
  28. package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -0
  29. package/dist/types/src/components/Form/fields/ArrayField.d.ts +18 -0
  30. package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -0
  31. package/dist/types/src/components/Form/fields/BooleanField.d.ts +4 -0
  32. package/dist/types/src/components/Form/fields/BooleanField.d.ts.map +1 -0
  33. package/dist/types/src/components/Form/fields/GeoPointField.d.ts +5 -0
  34. package/dist/types/src/components/Form/fields/GeoPointField.d.ts.map +1 -0
  35. package/dist/types/src/components/Form/fields/MarkdownField.d.ts +4 -0
  36. package/dist/types/src/components/Form/fields/MarkdownField.d.ts.map +1 -0
  37. package/dist/types/src/components/Form/fields/NumberField.d.ts +4 -0
  38. package/dist/types/src/components/Form/fields/NumberField.d.ts.map +1 -0
  39. package/dist/types/src/components/Form/fields/RefField.d.ts +14 -0
  40. package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -0
  41. package/dist/types/src/components/Form/fields/SelectField.d.ts +10 -0
  42. package/dist/types/src/components/Form/fields/SelectField.d.ts.map +1 -0
  43. package/dist/types/src/components/Form/fields/SelectOptionField.d.ts +5 -0
  44. package/dist/types/src/components/Form/fields/SelectOptionField.d.ts.map +1 -0
  45. package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts +82 -0
  46. package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts.map +1 -0
  47. package/dist/types/src/components/Form/fields/TextAreaField.d.ts +4 -0
  48. package/dist/types/src/components/Form/fields/TextAreaField.d.ts.map +1 -0
  49. package/dist/types/src/components/Form/fields/TextField.d.ts +4 -0
  50. package/dist/types/src/components/Form/fields/TextField.d.ts.map +1 -0
  51. package/dist/types/src/components/Form/fields/TupleField.d.ts +6 -0
  52. package/dist/types/src/components/Form/fields/TupleField.d.ts.map +1 -0
  53. package/dist/types/src/components/Form/fields/index.d.ts +12 -0
  54. package/dist/types/src/components/Form/fields/index.d.ts.map +1 -0
  55. package/dist/types/src/components/Form/index.d.ts +2 -4
  56. package/dist/types/src/components/Form/index.d.ts.map +1 -1
  57. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +46 -0
  58. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -0
  59. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +85 -0
  60. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -0
  61. package/dist/types/src/components/ObjectPicker/index.d.ts +2 -0
  62. package/dist/types/src/components/ObjectPicker/index.d.ts.map +1 -0
  63. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +21 -9
  64. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  65. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +84 -9
  66. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  67. package/dist/types/src/components/index.d.ts +1 -1
  68. package/dist/types/src/components/index.d.ts.map +1 -1
  69. package/dist/types/src/components/testing.d.ts +4 -3
  70. package/dist/types/src/components/testing.d.ts.map +1 -1
  71. package/dist/types/src/hooks/index.d.ts +2 -3
  72. package/dist/types/src/hooks/index.d.ts.map +1 -1
  73. package/dist/types/src/hooks/useFormHandler.d.ts +82 -0
  74. package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -0
  75. package/dist/types/src/hooks/useKeyHandler.d.ts +6 -0
  76. package/dist/types/src/hooks/useKeyHandler.d.ts.map +1 -0
  77. package/dist/types/src/translations.d.ts +19 -10
  78. package/dist/types/src/translations.d.ts.map +1 -1
  79. package/dist/types/src/util/field.d.ts +3 -3
  80. package/dist/types/src/util/field.d.ts.map +1 -1
  81. package/dist/types/src/util/formatting.d.ts +2 -2
  82. package/dist/types/src/util/formatting.d.ts.map +1 -1
  83. package/dist/types/src/util/index.d.ts +1 -1
  84. package/dist/types/src/util/index.d.ts.map +1 -1
  85. package/dist/types/src/util/properties.d.ts +7 -0
  86. package/dist/types/src/util/properties.d.ts.map +1 -0
  87. package/dist/types/src/util/refs.d.ts +2 -3
  88. package/dist/types/src/util/refs.d.ts.map +1 -1
  89. package/dist/types/tsconfig.tsbuildinfo +1 -1
  90. package/package.json +51 -40
  91. package/src/components/ControlSection/ControlSection.stories.tsx +11 -14
  92. package/src/components/ControlSection/ControlSection.tsx +30 -16
  93. package/src/components/FieldEditor/FieldEditor.stories.tsx +53 -30
  94. package/src/components/FieldEditor/FieldEditor.test.tsx +14 -13
  95. package/src/components/FieldEditor/FieldEditor.tsx +87 -92
  96. package/src/components/Form/Form-old.stories.tsx +314 -0
  97. package/src/components/Form/Form.stories.tsx +155 -219
  98. package/src/components/Form/Form.tsx +319 -96
  99. package/src/components/Form/FormField.tsx +286 -0
  100. package/src/components/Form/FormFieldComponent.stories.tsx +65 -0
  101. package/src/components/Form/FormFieldComponent.tsx +174 -0
  102. package/src/components/Form/FormFieldSet.tsx +125 -0
  103. package/src/components/Form/README.md +3 -0
  104. package/src/components/Form/fields/ArrayField.tsx +151 -0
  105. package/src/components/Form/fields/BooleanField.tsx +22 -0
  106. package/src/components/Form/fields/GeoPointField.tsx +120 -0
  107. package/src/components/Form/fields/MarkdownField.tsx +78 -0
  108. package/src/components/Form/fields/NumberField.tsx +39 -0
  109. package/src/components/Form/fields/RefField.tsx +181 -0
  110. package/src/components/Form/fields/SelectField.tsx +65 -0
  111. package/src/components/Form/{custom/SelectOptionsInput.stories.tsx → fields/SelectOptionField.stories.tsx} +25 -22
  112. package/src/components/Form/{custom/SelectOptionsInput.tsx → fields/SelectOptionField.tsx} +47 -42
  113. package/src/components/Form/fields/TextAreaField.tsx +38 -0
  114. package/src/components/Form/fields/TextField.tsx +38 -0
  115. package/src/components/Form/{custom/TupleInput.tsx → fields/TupleField.tsx} +16 -9
  116. package/src/components/Form/fields/index.ts +15 -0
  117. package/src/components/Form/index.ts +3 -4
  118. package/src/components/ObjectPicker/ObjectPicker.stories.tsx +214 -0
  119. package/src/components/ObjectPicker/ObjectPicker.tsx +175 -0
  120. package/src/components/ObjectPicker/index.ts +5 -0
  121. package/src/components/ViewEditor/ViewEditor.stories.tsx +141 -59
  122. package/src/components/ViewEditor/ViewEditor.test.tsx +25 -21
  123. package/src/components/ViewEditor/ViewEditor.tsx +317 -214
  124. package/src/components/index.ts +1 -2
  125. package/src/components/testing.tsx +19 -11
  126. package/src/hooks/index.ts +2 -3
  127. package/src/hooks/useFormHandler.ts +356 -0
  128. package/src/hooks/useKeyHandler.tsx +37 -0
  129. package/src/translations.ts +21 -12
  130. package/src/util/field.ts +33 -43
  131. package/src/util/formatting.ts +22 -22
  132. package/src/util/index.ts +1 -1
  133. package/src/util/properties.ts +18 -0
  134. package/src/util/refs.ts +12 -18
  135. package/dist/types/src/components/Form/ArrayField.d.ts +0 -16
  136. package/dist/types/src/components/Form/ArrayField.d.ts.map +0 -1
  137. package/dist/types/src/components/Form/Defaults.d.ts +0 -14
  138. package/dist/types/src/components/Form/Defaults.d.ts.map +0 -1
  139. package/dist/types/src/components/Form/FormActions.d.ts +0 -9
  140. package/dist/types/src/components/Form/FormActions.d.ts.map +0 -1
  141. package/dist/types/src/components/Form/FormContent.d.ts +0 -57
  142. package/dist/types/src/components/Form/FormContent.d.ts.map +0 -1
  143. package/dist/types/src/components/Form/FormContext.d.ts +0 -23
  144. package/dist/types/src/components/Form/FormContext.d.ts.map +0 -1
  145. package/dist/types/src/components/Form/Input.d.ts +0 -26
  146. package/dist/types/src/components/Form/Input.d.ts.map +0 -1
  147. package/dist/types/src/components/Form/RefField.d.ts +0 -12
  148. package/dist/types/src/components/Form/RefField.d.ts.map +0 -1
  149. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +0 -4
  150. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +0 -1
  151. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +0 -4
  152. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +0 -1
  153. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +0 -7
  154. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +0 -1
  155. package/dist/types/src/components/Form/custom/TupleInput.d.ts +0 -6
  156. package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +0 -1
  157. package/dist/types/src/components/Form/custom/index.d.ts +0 -4
  158. package/dist/types/src/components/Form/custom/index.d.ts.map +0 -1
  159. package/dist/types/src/components/Form/factory.d.ts +0 -8
  160. package/dist/types/src/components/Form/factory.d.ts.map +0 -1
  161. package/dist/types/src/components/deprecated/Form.d.ts +0 -15
  162. package/dist/types/src/components/deprecated/Form.d.ts.map +0 -1
  163. package/dist/types/src/components/deprecated/Form.stories.d.ts +0 -13
  164. package/dist/types/src/components/deprecated/Form.stories.d.ts.map +0 -1
  165. package/dist/types/src/components/deprecated/FormInput.d.ts +0 -16
  166. package/dist/types/src/components/deprecated/FormInput.d.ts.map +0 -1
  167. package/dist/types/src/components/deprecated/index.d.ts +0 -2
  168. package/dist/types/src/components/deprecated/index.d.ts.map +0 -1
  169. package/dist/types/src/hooks/useForm.d.ts +0 -67
  170. package/dist/types/src/hooks/useForm.d.ts.map +0 -1
  171. package/dist/types/src/hooks/useQueryRefOptions.d.ts +0 -23
  172. package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +0 -1
  173. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts +0 -8
  174. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts.map +0 -1
  175. package/dist/types/src/util/schema.d.ts +0 -3
  176. package/dist/types/src/util/schema.d.ts.map +0 -1
  177. package/src/components/Form/ArrayField.tsx +0 -124
  178. package/src/components/Form/Defaults.tsx +0 -203
  179. package/src/components/Form/FormActions.tsx +0 -53
  180. package/src/components/Form/FormContent.tsx +0 -240
  181. package/src/components/Form/FormContext.tsx +0 -110
  182. package/src/components/Form/Input.tsx +0 -51
  183. package/src/components/Form/RefField.tsx +0 -213
  184. package/src/components/Form/custom/GeoPointInput.tsx +0 -72
  185. package/src/components/Form/custom/index.ts +0 -7
  186. package/src/components/Form/factory.tsx +0 -31
  187. package/src/components/deprecated/Form.stories.tsx +0 -62
  188. package/src/components/deprecated/Form.tsx +0 -119
  189. package/src/components/deprecated/FormInput.tsx +0 -71
  190. package/src/components/deprecated/index.ts +0 -5
  191. package/src/hooks/useForm.ts +0 -291
  192. package/src/hooks/useQueryRefOptions.ts +0 -53
  193. package/src/hooks/useRefQueryLookupHandler.ts +0 -39
  194. package/src/util/schema.ts +0 -14
@@ -1,5 +1,5 @@
1
1
  // src/translations.ts
2
- var translationKey = "react-ui-form";
2
+ var translationKey = "@dxos/react-ui-form";
3
3
  var translations = [
4
4
  {
5
5
  "en-US": {
@@ -8,7 +8,7 @@ var translations = [
8
8
  "hidden fields label": "Hidden Fields",
9
9
  "empty readonly ref field label": "(none)",
10
10
  // TODO(burdon): Standardize field/property.
11
- "button add property": "Add property",
11
+ "add property button label": "Add property",
12
12
  "field property label": "Property",
13
13
  "field property placeholder": "Property name",
14
14
  "field format label": "Type",
@@ -18,9 +18,17 @@ var translations = [
18
18
  "field limit reached": "Maximum number of fields reached",
19
19
  "boolean input true value": "Yes",
20
20
  "boolean input false value": "No",
21
- // TODO(burdon): Factor out?
22
- "button cancel": "Cancel",
23
- "button save": "Save",
21
+ "show field label": "Show field",
22
+ "hide field label": "Hide field",
23
+ "delete field label": "Delete field",
24
+ "ref field combobox input placeholder": "Search\u2026",
25
+ "ref field placeholder_one": "Select\u2026",
26
+ "ref field placeholder_other": "Select items\u2026",
27
+ "example placeholder": "Example",
28
+ "latitude placeholder": "Latitude (e.g., 40.7128)",
29
+ "longitude placeholder": "Longitude (e.g., -74.0060)",
30
+ "cancel button label": "Cancel",
31
+ "save button label": "Save",
24
32
  // FormatEnum
25
33
  "format boolean": "Boolean",
26
34
  "format currency": "Currency",
@@ -34,7 +42,7 @@ var translations = [
34
42
  "format hostname": "Hostname",
35
43
  "format integer": "Integer",
36
44
  "format json": "JSON",
37
- "format latlng": "Geopoint",
45
+ "format lnglat": "Geopoint",
38
46
  "format markdown": "Markdown",
39
47
  "format percent": "Percent",
40
48
  "format ref": "Reference",
@@ -50,751 +58,467 @@ var translations = [
50
58
  "format uri": "URI",
51
59
  "format url": "URL",
52
60
  "format uuid": "UUID",
53
- // Placeholders.
54
- "placeholder latitude": "Latitude",
55
- "placeholder longitude": "Longitude",
56
- // SelectOptionsInput.
61
+ // GeoPointField.
62
+ "latitude label": "Latitude",
63
+ "longitude label": "Longitude",
64
+ // SelectOptionsField.
57
65
  "select option label": "Label",
58
66
  "select option label placeholder": "Option label",
59
67
  "select option color": "Color",
60
68
  "select option delete": "Delete",
61
69
  "select option add": "Add option",
62
70
  // System schema message.
63
- "system schema title": "System Schema",
64
- "system schema description": "This is a system schema and cannot be modified."
71
+ "system schema title": "System type",
72
+ "system schema description": "System type: cannot add or remove fields."
65
73
  }
66
74
  }
67
75
  }
68
76
  ];
69
77
 
70
- // src/components/deprecated/FormInput.tsx
71
- import { useSignals as _useSignals } from "@preact-signals/safe-react/tracking";
72
- import React from "react";
73
- import { Input } from "@dxos/react-ui";
74
- import { mx, textBlockWidth } from "@dxos/react-ui-theme";
75
- var DeprecatedFormContainer = ({ children, classNames }) => {
76
- var _effect = _useSignals();
77
- try {
78
- return /* @__PURE__ */ React.createElement("div", {
79
- role: "none",
80
- className: "container-max-width p-4 justify-center overflow-x-hidden overflow-y-auto"
81
- }, /* @__PURE__ */ React.createElement("div", {
82
- role: "form",
83
- className: mx("flex flex-col", textBlockWidth, classNames)
84
- }, children));
85
- } finally {
86
- _effect.f();
87
- }
88
- };
89
- var DeprecatedFormInput = ({ label, description, secondary, children }) => {
90
- var _effect = _useSignals();
91
- try {
92
- const primary = /* @__PURE__ */ React.createElement("div", {
93
- role: "none",
94
- className: "flex w-full gap-4 py-1"
95
- }, /* @__PURE__ */ React.createElement(Input.Root, null, /* @__PURE__ */ React.createElement("div", {
96
- role: "none",
97
- className: "flex flex-col w-full"
98
- }, /* @__PURE__ */ React.createElement(Input.Label, {
99
- classNames: "flex min-bs-[--rail-action] items-center",
100
- style: {
101
- fontSize: "unset"
102
- }
103
- }, label), description && /* @__PURE__ */ React.createElement(Input.DescriptionAndValidation, {
104
- classNames: "mbs-0.5"
105
- }, /* @__PURE__ */ React.createElement(Input.Description, null, description))), /* @__PURE__ */ React.createElement("div", {
106
- role: "none"
107
- }, /* @__PURE__ */ React.createElement("div", {
108
- role: "none",
109
- className: "flex min-bs-[--rail-action] items-center"
110
- }, children))));
111
- if (secondary) {
112
- return /* @__PURE__ */ React.createElement("div", {
113
- role: "none"
114
- }, primary, secondary);
115
- }
116
- return primary;
117
- } finally {
118
- _effect.f();
119
- }
120
- };
121
-
122
78
  // src/components/ControlSection/ControlSection.tsx
123
- import { useSignals as _useSignals2 } from "@preact-signals/safe-react/tracking";
124
- import React2 from "react";
125
- import { Button, Input as Input2, toLocalizedString, useTranslation } from "@dxos/react-ui";
126
- import { mx as mx2 } from "@dxos/react-ui-theme";
79
+ import React from "react";
80
+ import { Button, Input, toLocalizedString, useTranslation } from "@dxos/react-ui";
81
+ import { Layout } from "@dxos/react-ui-mosaic";
82
+ import { mx } from "@dxos/ui-theme";
127
83
  var ControlPage = ({ children, classNames, ...props }) => {
128
- var _effect = _useSignals2();
129
- try {
130
- return /* @__PURE__ */ React2.createElement("div", {
131
- role: "none",
132
- className: mx2("pli-cardSpacingInline", classNames),
133
- ...props
134
- }, children);
135
- } finally {
136
- _effect.f();
137
- }
84
+ return /* @__PURE__ */ React.createElement(Layout.Container, {
85
+ scrollable: true,
86
+ classNames: "[--control-spacing:var(--dx-trimMd)]"
87
+ }, /* @__PURE__ */ React.createElement("div", {
88
+ role: "none",
89
+ className: mx("pli-cardSpacingInline pbe-trimMd", classNames),
90
+ ...props
91
+ }, children));
138
92
  };
139
93
  var ControlSection = ({ title, description, children }) => {
140
- var _effect = _useSignals2();
141
- try {
142
- return /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(ControlSectionHeading, {
143
- title,
144
- description
145
- }), children);
146
- } finally {
147
- _effect.f();
148
- }
94
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(ControlSectionHeading, {
95
+ title,
96
+ description
97
+ }), children);
149
98
  };
150
99
  var ControlSectionHeading = ({ title, description }) => {
151
- var _effect = _useSignals2();
152
- try {
153
- const { t } = useTranslation(translationKey);
154
- return /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement("h2", {
155
- className: "pli-trimMd container-max-width text-xl mbs-trimLg mbe-trimMd"
156
- }, toLocalizedString(title, t)), description && /* @__PURE__ */ React2.createElement("p", {
157
- className: "pli-trimMd mlb-trimMd container-max-width text-description"
158
- }, toLocalizedString(description, t)));
159
- } finally {
160
- _effect.f();
161
- }
100
+ const { t } = useTranslation(translationKey);
101
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("h2", {
102
+ className: "pli-trimMd container-max-width text-xl mbs-trimMd mbe-trimMd"
103
+ }, toLocalizedString(title, t)), description && /* @__PURE__ */ React.createElement("p", {
104
+ className: "pli-trimMd mlb-trimMd container-max-width text-description"
105
+ }, toLocalizedString(description, t)));
162
106
  };
163
107
  var ControlGroupButton = ({ classNames, ...props }) => {
164
- var _effect = _useSignals2();
165
- try {
166
- return /* @__PURE__ */ React2.createElement(Button, {
167
- ...props,
168
- classNames: [
169
- "md:col-span-2",
170
- classNames
171
- ]
172
- });
173
- } finally {
174
- _effect.f();
175
- }
176
- };
177
- var ControlGroup = ({ children, classNames }) => {
178
- var _effect = _useSignals2();
179
- try {
180
- return /* @__PURE__ */ React2.createElement("div", {
181
- role: "none",
182
- className: mx2("group container-max-width grid grid-cols-1 md:grid-cols-[1fr_min-content] gap-trimMd", classNames)
183
- }, children);
184
- } finally {
185
- _effect.f();
186
- }
187
- };
188
- var ControlFrame = ({ children }) => {
189
- var _effect = _useSignals2();
190
- try {
191
- return /* @__PURE__ */ React2.createElement("div", {
192
- role: "none",
193
- className: "p-trimMd container-max-width grid grid-cols-1 md:grid-cols-[1fr_1fr] gap-trimSm md:gap-trimMd border border-separator rounded-md"
194
- }, children);
195
- } finally {
196
- _effect.f();
197
- }
108
+ return /* @__PURE__ */ React.createElement(Button, {
109
+ ...props,
110
+ classNames: [
111
+ "md:col-span-2",
112
+ classNames
113
+ ]
114
+ });
198
115
  };
199
- var controlItemClasses = "pli-trimMd plb-trimMd container-max-width grid md:col-span-2 grid-cols-subgrid items-center gap-trimSm border border-separator rounded-md *:first:!mbs-0 *:last:!mbe-0";
200
- var controlItemTitleClasses = "text-lg font-normal";
201
- var controlItemDescriptionClasses = "text-base mlb-trimSm md:mbe-0 text-description";
116
+ var ControlGroup = ({ children, classNames }) => /* @__PURE__ */ React.createElement("div", {
117
+ role: "none",
118
+ className: mx("group container-max-width grid grid-cols-1 md:grid-cols-[1fr_min-content] gap-trimMd", "[--control-spacing:0px] [&_input]:justify-self-end [&_button]:justify-self-end", classNames)
119
+ }, children);
120
+ var ControlFrame = ({ children }) => /* @__PURE__ */ React.createElement("div", {
121
+ role: "none",
122
+ className: mx("container-max-width grid grid-cols-1 md:grid-cols-[1fr_1fr] gap-trimSm md:gap-trimMd p-trimMd", "border border-separator rounded-md")
123
+ }, children);
124
+ var controlItemClasses = mx([
125
+ "container-max-width grid md:col-span-2 grid-cols-subgrid gap-trimSm items-center",
126
+ // TODO(burdon): Use grid gap consistently or apply margins consistently?
127
+ "mlb-[--control-spacing] *:first:!mbs-0 *:last:!mbe-0 pli-trimMd plb-trimMd",
128
+ "border border-separator rounded-md"
129
+ ]);
130
+ var controlItemTitleClasses = "mbe-0 text-lg text-baseText font-normal";
131
+ var controlItemDescriptionClasses = "mlb-trimSm md:mbe-0 text-base text-description";
202
132
  var ControlItem = ({ title, description, children }) => {
203
- var _effect = _useSignals2();
204
- try {
205
- const { t } = useTranslation(translationKey);
206
- return /* @__PURE__ */ React2.createElement("div", {
207
- className: controlItemClasses
208
- }, /* @__PURE__ */ React2.createElement("div", {
209
- role: "none"
210
- }, /* @__PURE__ */ React2.createElement("h3", {
211
- className: controlItemTitleClasses
212
- }, toLocalizedString(title, t)), description && /* @__PURE__ */ React2.createElement("p", {
213
- className: controlItemDescriptionClasses
214
- }, toLocalizedString(description, t))), children);
215
- } finally {
216
- _effect.f();
217
- }
133
+ const { t } = useTranslation(translationKey);
134
+ return /* @__PURE__ */ React.createElement("div", {
135
+ className: controlItemClasses
136
+ }, /* @__PURE__ */ React.createElement("div", {
137
+ role: "none"
138
+ }, /* @__PURE__ */ React.createElement("h3", {
139
+ className: controlItemTitleClasses
140
+ }, toLocalizedString(title, t)), description && /* @__PURE__ */ React.createElement("p", {
141
+ className: controlItemDescriptionClasses
142
+ }, toLocalizedString(description, t))), children);
218
143
  };
219
144
  var ControlItemInput = ({ title, description, children }) => {
220
- var _effect = _useSignals2();
221
- try {
222
- const { t } = useTranslation(translationKey);
223
- return /* @__PURE__ */ React2.createElement(Input2.Root, null, /* @__PURE__ */ React2.createElement("div", {
224
- className: controlItemClasses
225
- }, /* @__PURE__ */ React2.createElement("div", {
226
- role: "none"
227
- }, /* @__PURE__ */ React2.createElement(Input2.Label, {
228
- classNames: controlItemTitleClasses
229
- }, toLocalizedString(title, t)), description && /* @__PURE__ */ React2.createElement(Input2.DescriptionAndValidation, null, /* @__PURE__ */ React2.createElement(Input2.Description, {
230
- classNames: controlItemDescriptionClasses
231
- }, toLocalizedString(description, t)))), children));
232
- } finally {
233
- _effect.f();
234
- }
145
+ const { t } = useTranslation(translationKey);
146
+ return /* @__PURE__ */ React.createElement(Input.Root, null, /* @__PURE__ */ React.createElement("div", {
147
+ className: controlItemClasses
148
+ }, /* @__PURE__ */ React.createElement("div", {
149
+ role: "none"
150
+ }, /* @__PURE__ */ React.createElement(Input.Label, {
151
+ classNames: controlItemTitleClasses
152
+ }, toLocalizedString(title, t)), description && /* @__PURE__ */ React.createElement(Input.DescriptionAndValidation, null, /* @__PURE__ */ React.createElement(Input.Description, {
153
+ classNames: controlItemDescriptionClasses
154
+ }, toLocalizedString(description, t)))), children));
235
155
  };
236
156
  var ControlFrameItem = ({ title, description, children }) => {
237
- var _effect = _useSignals2();
238
- try {
239
- const { t } = useTranslation(translationKey);
240
- return /* @__PURE__ */ React2.createElement("div", {
241
- role: "group",
242
- className: "min-is-0"
243
- }, /* @__PURE__ */ React2.createElement("h3", {
244
- className: "text-lg mbe-2"
245
- }, toLocalizedString(title, t)), description && /* @__PURE__ */ React2.createElement("p", {
246
- className: "mlb-trimSm md:mbe-0 text-description"
247
- }, toLocalizedString(description, t)), children);
248
- } finally {
249
- _effect.f();
250
- }
157
+ const { t } = useTranslation(translationKey);
158
+ return /* @__PURE__ */ React.createElement("div", {
159
+ role: "group",
160
+ className: "min-is-0"
161
+ }, /* @__PURE__ */ React.createElement("h3", {
162
+ className: "text-lg mbe-2"
163
+ }, toLocalizedString(title, t)), description && /* @__PURE__ */ React.createElement("p", {
164
+ className: "mlb-trimSm md:mbe-0 text-description"
165
+ }, toLocalizedString(description, t)), children);
251
166
  };
252
167
 
253
168
  // src/components/FieldEditor/FieldEditor.tsx
254
- import { useSignals as _useSignals14 } from "@preact-signals/safe-react/tracking";
255
- import React14, { useCallback as useCallback9, useEffect as useEffect8, useMemo as useMemo7, useState as useState5 } from "react";
256
- import { FormatEnum as FormatEnum5, FormatEnums, formatToType } from "@dxos/echo-schema";
257
- import { log as log3 } from "@dxos/log";
258
- import { useTranslation as useTranslation8 } from "@dxos/react-ui";
259
- import { getFormatSchema, getSchemaProperties as getSchemaProperties3, sortProperties } from "@dxos/schema";
260
-
261
- // src/components/Form/custom/GeoPointInput.tsx
262
- import { useSignals as _useSignals4 } from "@preact-signals/safe-react/tracking";
263
- import React4, { useCallback, useState, useEffect, useMemo } from "react";
264
- import { GeoLocation } from "@dxos/echo-schema";
265
- import { Input as Input4, useTranslation as useTranslation2 } from "@dxos/react-ui";
266
- import { safeParseFloat } from "@dxos/util";
169
+ import React18, { useCallback as useCallback14, useEffect as useEffect7, useMemo as useMemo7, useState as useState5 } from "react";
170
+ import { Obj } from "@dxos/echo";
171
+ import { Format as Format4, FormatEnums, formatToType } from "@dxos/echo/internal";
172
+ import { log as log2 } from "@dxos/log";
173
+ import { useAsyncEffect, useTranslation as useTranslation9 } from "@dxos/react-ui";
174
+ import { formatToAdditionalPropertyAttributes, getFormatSchema } from "@dxos/schema";
267
175
 
268
- // src/components/Form/Input.tsx
269
- import { useSignals as _useSignals3 } from "@preact-signals/safe-react/tracking";
270
- import React3 from "react";
271
- import { Icon, Input as Input3, Tooltip } from "@dxos/react-ui";
272
- import { labelSpacing } from "@dxos/react-ui-stack";
273
- import { errorText, mx as mx3 } from "@dxos/react-ui-theme";
274
- var InputHeader = ({ error, label, readonly }) => {
275
- var _effect = _useSignals3();
276
- try {
277
- const Label = readonly ? "span" : Input3.Label;
278
- const labelProps = readonly ? {} : {
279
- classNames: "!mlb-0"
280
- };
281
- return /* @__PURE__ */ React3.createElement("div", {
282
- role: "none",
283
- className: mx3("flex justify-between items-center", labelSpacing)
284
- }, /* @__PURE__ */ React3.createElement(Label, labelProps, label), error && /* @__PURE__ */ React3.createElement(Tooltip.Trigger, {
285
- asChild: true,
286
- content: error,
287
- side: "bottom"
288
- }, /* @__PURE__ */ React3.createElement(Icon, {
289
- icon: "ph--warning--regular",
290
- size: 4,
291
- classNames: errorText
292
- })));
293
- } finally {
294
- _effect.f();
176
+ // src/util/field.ts
177
+ import { Format, GeoLocation, TypeEnum } from "@dxos/echo/internal";
178
+ var parseValue = ({ type, format, value: value3 }) => {
179
+ if (value3 === void 0 || value3 === null || value3 === "") {
180
+ return void 0;
295
181
  }
296
- };
297
-
298
- // src/components/Form/custom/GeoPointInput.tsx
299
- var GeoPointInput = ({ type, label, disabled, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
300
- var _effect = _useSignals4();
301
- try {
302
- const { t } = useTranslation2(translationKey);
303
- const { status, error } = getStatus();
304
- const geoPoint = useMemo(() => getValue3() ?? [
305
- 0,
306
- 0
307
- ], [
308
- getValue3
309
- ]);
310
- const geoLocation = useMemo(() => GeoLocation.fromGeoPoint(geoPoint), [
311
- geoPoint
312
- ]);
313
- const [longitudeText, setLongitudeText] = useState(geoLocation.longitude?.toString());
314
- const [latitudeText, setLatitudeText] = useState(geoLocation.latitude?.toString());
315
- useEffect(() => {
316
- const location = GeoLocation.fromGeoPoint(geoPoint);
317
- setLongitudeText(location.longitude.toString());
318
- setLatitudeText(location.latitude.toString());
319
- }, [
320
- geoPoint
321
- ]);
322
- const handleCoordinateChange = useCallback((coordinateType, setText) => (event) => {
323
- const inputText = event.target.value;
324
- setText(inputText);
325
- if (inputText !== "" && inputText !== "-") {
326
- const coord = safeParseFloat(inputText);
327
- if (coord !== void 0 && !isNaN(coord)) {
328
- const currentLocation = GeoLocation.fromGeoPoint(getValue3() ?? [
329
- 0,
330
- 0
331
- ]);
332
- const newLocation = {
333
- ...currentLocation,
334
- [coordinateType]: coord
335
- };
336
- const newValue = GeoLocation.toGeoPoint(newLocation);
337
- onValueChange(type, newValue);
182
+ const parseScalar = (type2) => {
183
+ switch (type2) {
184
+ case TypeEnum.Boolean: {
185
+ if (typeof value3 === "string") {
186
+ const lowercaseValue = value3.toLowerCase();
187
+ if (lowercaseValue === "0" || lowercaseValue === "false") {
188
+ return false;
189
+ } else if (lowercaseValue === "1" || lowercaseValue === "true") {
190
+ return true;
191
+ }
338
192
  }
193
+ return Boolean(value3);
339
194
  }
340
- }, [
341
- getValue3,
342
- onValueChange,
343
- type
344
- ]);
345
- return /* @__PURE__ */ React4.createElement(Input4.Root, {
346
- validationValence: status
347
- }, /* @__PURE__ */ React4.createElement(InputHeader, {
348
- error,
349
- label
350
- }), /* @__PURE__ */ React4.createElement("div", {
351
- role: "none",
352
- className: "grid grid-cols-2 gap-2"
353
- }, /* @__PURE__ */ React4.createElement(Input4.TextInput, {
354
- type: "text",
355
- pattern: "^-?[0-9]*\\.?[0-9]*$",
356
- disabled,
357
- placeholder: t("placeholder latitude"),
358
- value: latitudeText,
359
- onChange: handleCoordinateChange("latitude", setLatitudeText),
360
- onBlur
361
- }), /* @__PURE__ */ React4.createElement(Input4.TextInput, {
362
- type: "text",
363
- pattern: "^-?[0-9]*\\.?[0-9]*$",
364
- disabled,
365
- placeholder: t("placeholder longitude"),
366
- value: longitudeText,
367
- onChange: handleCoordinateChange("longitude", setLongitudeText),
368
- onBlur
369
- })));
370
- } finally {
371
- _effect.f();
372
- }
373
- };
374
-
375
- // src/components/Form/custom/SelectOptionsInput.tsx
376
- import { useSignals as _useSignals5 } from "@preact-signals/safe-react/tracking";
377
- import React5, { useCallback as useCallback2, useEffect as useEffect2, useState as useState2, useRef } from "react";
378
- import { PublicKey } from "@dxos/keys";
379
- import { Icon as Icon2, IconButton, Input as Input5, Tag, Toolbar, useTranslation as useTranslation3 } from "@dxos/react-ui";
380
- import { List } from "@dxos/react-ui-list";
381
- import { HuePicker } from "@dxos/react-ui-pickers";
382
- import { hues, mx as mx4 } from "@dxos/react-ui-theme";
383
- var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3, onValueChange }) => {
384
- var _effect = _useSignals5();
385
- try {
386
- const [selected, setSelectedId] = useState2(null);
387
- const [isNewOption, setIsNewOption] = useState2(false);
388
- const inputRef = useRef(null);
389
- const { t } = useTranslation3(translationKey);
390
- const { status, error } = getStatus();
391
- const options = getValue3();
392
- useEffect2(() => {
393
- if (options === void 0) {
394
- onValueChange(type, []);
195
+ case TypeEnum.Number: {
196
+ const cleaned = typeof value3 === "string" ? value3.replace(/[^0-9.-]+/g, "") : value3;
197
+ const num = Number(cleaned);
198
+ return Number.isNaN(num) ? null : num;
395
199
  }
396
- }, [
397
- options,
398
- onValueChange,
399
- type
400
- ]);
401
- const randomHue = () => {
402
- const usedHues = new Set(options?.map((option) => option.color) ?? []);
403
- const unusedHues = hues.filter((hue) => !usedHues.has(hue));
404
- if (unusedHues.length > 0) {
405
- return unusedHues[Math.floor(Math.random() * unusedHues.length)];
200
+ case TypeEnum.String:
201
+ case TypeEnum.Ref:
202
+ return String(value3);
203
+ default: {
204
+ return value3;
406
205
  }
407
- const lastThree = new Set(options?.slice(-3).map((option) => option.color) ?? []);
408
- const availableHues = hues.filter((hue) => !lastThree.has(hue));
409
- return availableHues[Math.floor(Math.random() * availableHues.length)] ?? hues[Math.floor(Math.random() * hues.length)];
410
- };
411
- const handleAdd = useCallback2(() => {
412
- const newId = PublicKey.random().truncate();
413
- const newOption = {
414
- id: newId,
415
- title: "",
416
- color: randomHue()
417
- };
418
- onValueChange(type, [
419
- ...options ?? [],
420
- newOption
421
- ]);
422
- setSelectedId(newId);
423
- setIsNewOption(true);
424
- }, [
425
- options,
426
- type,
427
- onValueChange
428
- ]);
429
- const handleDelete = useCallback2((id) => {
430
- const newOptions = options?.filter((option) => option.id !== id) ?? [];
431
- onValueChange(type, newOptions);
432
- setSelectedId(null);
433
- }, [
434
- options,
435
- type,
436
- onValueChange
437
- ]);
438
- const handleMove = useCallback2((from, to) => {
439
- if (!options) {
440
- return;
206
+ }
207
+ };
208
+ if (!format) {
209
+ return parseScalar(type);
210
+ }
211
+ switch (format) {
212
+ case Format.TypeFormat.Boolean:
213
+ return parseScalar(TypeEnum.Boolean);
214
+ case Format.TypeFormat.Number:
215
+ case Format.TypeFormat.Percent:
216
+ case Format.TypeFormat.Currency:
217
+ return parseScalar(TypeEnum.Number);
218
+ case Format.TypeFormat.String:
219
+ case Format.TypeFormat.Markdown: {
220
+ return parseScalar(TypeEnum.String);
221
+ }
222
+ case Format.TypeFormat.Ref:
223
+ throw new Error(`unexpected format: ${Format.TypeFormat.Ref}`);
224
+ case Format.TypeFormat.DateTime:
225
+ case Format.TypeFormat.Date:
226
+ case Format.TypeFormat.Time:
227
+ case Format.TypeFormat.Timestamp: {
228
+ const date = new Date(value3);
229
+ return isNaN(date.getTime()) ? null : date;
230
+ }
231
+ case Format.TypeFormat.GeoPoint: {
232
+ if (typeof value3 === "string") {
233
+ const cleaned = value3.replace(/[^0-9.,-]+/g, "");
234
+ const parts = cleaned.split(",");
235
+ if (parts.length === 2) {
236
+ const lat = parseFloat(parts[0]);
237
+ const long = parseFloat(parts[1]);
238
+ if (!isNaN(lat) && !isNaN(long)) {
239
+ return GeoLocation.toGeoPoint({
240
+ latitude: lat,
241
+ longitude: long
242
+ });
243
+ }
244
+ }
441
245
  }
442
- const newOptions = [
443
- ...options
246
+ return [
247
+ 0,
248
+ 0
444
249
  ];
445
- const [removed] = newOptions.splice(from, 1);
446
- newOptions.splice(to, 0, removed);
447
- onValueChange(type, newOptions);
448
- }, [
449
- options,
450
- type,
451
- onValueChange
452
- ]);
453
- const handleClick = useCallback2((id) => {
454
- setSelectedId((current) => current === id ? null : id);
455
- }, []);
456
- const handleTitleChange = useCallback2((id) => (e) => {
457
- const newOptions = options?.map((o) => o.id === id ? {
458
- ...o,
459
- title: e.target.value
460
- } : o);
461
- onValueChange(type, newOptions ?? []);
462
- }, [
463
- options,
464
- type,
465
- onValueChange
466
- ]);
467
- const handleKeyDown = useCallback2((e) => {
468
- if (e.key === "Enter") {
469
- setSelectedId(null);
470
- }
471
- }, []);
472
- const handleColorChange = useCallback2((id) => (hue) => {
473
- const newOptions = options?.map((o) => o.id === id ? {
474
- ...o,
475
- color: hue
476
- } : o);
477
- onValueChange(type, newOptions ?? []);
478
- }, [
479
- options,
480
- type,
481
- onValueChange
482
- ]);
483
- useEffect2(() => {
484
- if (selected && isNewOption && inputRef.current) {
485
- inputRef.current.focus();
486
- setIsNewOption(false);
487
- }
488
- }, [
489
- selected,
490
- isNewOption
491
- ]);
492
- return /* @__PURE__ */ React5.createElement(Input5.Root, {
493
- validationValence: status
494
- }, /* @__PURE__ */ React5.createElement(InputHeader, {
495
- error,
496
- label
497
- }), /* @__PURE__ */ React5.createElement("div", {
498
- role: "none"
499
- }, options && /* @__PURE__ */ React5.createElement(List.Root, {
500
- items: options,
501
- isItem: (item) => true,
502
- onMove: handleMove
503
- }, ({ items }) => /* @__PURE__ */ React5.createElement("div", {
504
- role: "list",
505
- className: "w-full overflow-auto"
506
- }, items?.map((item) => /* @__PURE__ */ React5.createElement("div", {
507
- key: item.id
508
- }, /* @__PURE__ */ React5.createElement(List.Item, {
509
- role: "button",
510
- item,
511
- classNames: mx4("p-1 plb-2", "flex flex-col", "cursor-pointer", "hover:bg-hoverOverlay"),
512
- "aria-expanded": selected === item.id
513
- }, /* @__PURE__ */ React5.createElement("div", {
514
- className: "flex items-center"
515
- }, /* @__PURE__ */ React5.createElement(List.ItemDragHandle, null), /* @__PURE__ */ React5.createElement(List.ItemTitle, {
516
- onClick: () => handleClick(item.id),
517
- classNames: "flex-1"
518
- }, /* @__PURE__ */ React5.createElement(Tag, {
519
- palette: item.color
520
- }, item.title || "\u200B")), /* @__PURE__ */ React5.createElement(Icon2, {
521
- icon: selected === item.id ? "ph--caret-down--regular" : "ph--caret-right--regular",
522
- onClick: () => handleClick(item.id)
523
- })), selected === item.id && /* @__PURE__ */ React5.createElement("div", {
524
- className: "mis-4 mbs-2 flex flex-col gap-1"
525
- }, /* @__PURE__ */ React5.createElement(Input5.Label, {
526
- classNames: "text-xs"
527
- }, t("select option label")), /* @__PURE__ */ React5.createElement("div", {
528
- className: "flex flex-row items-center gap-1"
529
- }, /* @__PURE__ */ React5.createElement(Input5.TextInput, {
530
- placeholder: t("select option label placeholder"),
531
- ref: selected === item.id ? inputRef : void 0,
532
- value: item.title,
533
- onChange: handleTitleChange(item.id),
534
- onKeyDown: handleKeyDown,
535
- classNames: "flex-1",
536
- "data-no-submit": true
537
- }), /* @__PURE__ */ React5.createElement(Toolbar.Root, {
538
- classNames: "p-0 m-0 !is-auto"
539
- }, /* @__PURE__ */ React5.createElement(HuePicker, {
540
- value: item.color,
541
- onChange: handleColorChange(item.id),
542
- rootVariant: "toolbar-button"
543
- })), /* @__PURE__ */ React5.createElement(IconButton, {
544
- icon: "ph--trash--fill",
545
- iconOnly: true,
546
- label: t("select option delete"),
547
- onClick: () => handleDelete(item.id)
548
- })))))), /* @__PURE__ */ React5.createElement("div", {
549
- role: "listitem"
550
- }, /* @__PURE__ */ React5.createElement("div", {
551
- role: "button",
552
- className: mx4("p-1 plb-2", "flex flex-col", "cursor-pointer", "hover:bg-hoverOverlay"),
553
- onClick: handleAdd
554
- }, /* @__PURE__ */ React5.createElement("div", {
555
- className: "flex items-center gap-1"
556
- }, /* @__PURE__ */ React5.createElement(Icon2, {
557
- icon: "ph--plus--regular"
558
- }), /* @__PURE__ */ React5.createElement("span", {
559
- className: "text-sm"
560
- }, t("select option add")))))))));
561
- } finally {
562
- _effect.f();
250
+ }
251
+ default: {
252
+ return value3;
253
+ }
563
254
  }
564
255
  };
565
-
566
- // src/components/Form/custom/TupleInput.tsx
567
- import { useSignals as _useSignals6 } from "@preact-signals/safe-react/tracking";
568
- import React6 from "react";
569
- import { Input as Input6 } from "@dxos/react-ui";
570
- import { mx as mx5 } from "@dxos/react-ui-theme";
571
- import { safeParseFloat as safeParseFloat2 } from "@dxos/util";
572
- var gridCols = [
573
- "grid-cols-1",
574
- "grid-cols-2",
575
- "grid-cols-3",
576
- "grid-cols-4"
577
- ];
578
- var TupleInput = ({ binding, type, label, disabled, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
579
- var _effect = _useSignals6();
580
- try {
581
- const { status, error } = getStatus();
582
- const values = getValue3() ?? {};
583
- return /* @__PURE__ */ React6.createElement(Input6.Root, {
584
- validationValence: status
585
- }, /* @__PURE__ */ React6.createElement(InputHeader, {
586
- error,
587
- label
588
- }), /* @__PURE__ */ React6.createElement("div", {
589
- className: mx5("grid gap-2", gridCols[binding.length - 1])
590
- }, binding.map((prop) => /* @__PURE__ */ React6.createElement(Input6.TextInput, {
591
- key: prop,
592
- type: "number",
593
- disabled,
594
- value: values[prop],
595
- onChange: (event) => onValueChange(type, {
596
- ...values,
597
- [prop]: safeParseFloat2(event.target.value, 0)
598
- }),
599
- onBlur
600
- }))));
601
- } finally {
602
- _effect.f();
256
+ var cellClassesForFieldType = ({ type, format }) => {
257
+ switch (format || type) {
258
+ case Format.TypeFormat.Markdown:
259
+ return void 0;
260
+ case Format.TypeFormat.Time:
261
+ case Format.TypeFormat.Timestamp:
262
+ case Format.TypeFormat.DateTime:
263
+ case Format.TypeFormat.Date:
264
+ case Format.TypeFormat.Duration:
265
+ return [
266
+ "font-mono",
267
+ "text-right"
268
+ ];
269
+ case Format.TypeFormat.Currency:
270
+ return [
271
+ "text-right"
272
+ ];
273
+ case Format.TypeFormat.JSON:
274
+ case Format.TypeFormat.DID:
275
+ return [
276
+ "font-mono"
277
+ ];
278
+ case TypeEnum.Number:
279
+ case TypeEnum.Boolean:
280
+ return [
281
+ "text-right",
282
+ "font-mono"
283
+ ];
284
+ case TypeEnum.String:
285
+ return void 0;
286
+ case TypeEnum.Ref:
287
+ return void 0;
288
+ default:
289
+ return void 0;
603
290
  }
604
291
  };
292
+ var pathNotUniqueError = (path) => ({
293
+ path: "path",
294
+ message: `'${path}' is already present`
295
+ });
605
296
 
606
- // src/components/Form/Defaults.tsx
607
- import { useSignals as _useSignals7 } from "@preact-signals/safe-react/tracking";
608
- import React7, { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef2 } from "react";
609
- import { Input as Input7, Select, useTranslation as useTranslation4 } from "@dxos/react-ui";
610
- var TextInput = ({ type, label, inputOnly, disabled, placeholder, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
611
- var _effect = _useSignals7();
612
- try {
613
- const { status, error } = getStatus();
614
- return disabled && !getValue3() ? null : disabled && inputOnly ? /* @__PURE__ */ React7.createElement("p", null, getValue3() ?? "") : /* @__PURE__ */ React7.createElement(Input7.Root, {
615
- validationValence: status
616
- }, !inputOnly && /* @__PURE__ */ React7.createElement(InputHeader, {
617
- error,
618
- label
619
- }), disabled ? /* @__PURE__ */ React7.createElement("p", null, getValue3() ?? "") : /* @__PURE__ */ React7.createElement(Input7.TextInput, {
620
- disabled,
621
- placeholder,
622
- value: getValue3() ?? "",
623
- onChange: (event) => onValueChange(type, event.target.value),
624
- onBlur
625
- }), inputOnly && /* @__PURE__ */ React7.createElement(Input7.Validation, null, error));
626
- } finally {
627
- _effect.f();
628
- }
629
- };
630
- var NumberInput = ({ type, label, inputOnly, disabled, placeholder, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
631
- var _effect = _useSignals7();
632
- try {
633
- const { status, error } = getStatus();
634
- return disabled && !getValue3() ? null : disabled && inputOnly ? /* @__PURE__ */ React7.createElement("p", null, getValue3() ?? "") : /* @__PURE__ */ React7.createElement(Input7.Root, {
635
- validationValence: status
636
- }, !inputOnly && /* @__PURE__ */ React7.createElement(InputHeader, {
637
- error,
638
- label
639
- }), disabled ? /* @__PURE__ */ React7.createElement("p", null, getValue3() ?? "") : /* @__PURE__ */ React7.createElement(Input7.TextInput, {
640
- type: "number",
641
- disabled,
642
- placeholder,
643
- value: getValue3() ?? 0,
644
- onChange: (event) => onValueChange(type, event.target.value),
645
- onBlur
646
- }), inputOnly && /* @__PURE__ */ React7.createElement(Input7.DescriptionAndValidation, null, error));
647
- } finally {
648
- _effect.f();
297
+ // src/util/formatting.ts
298
+ import { format as formatDate } from "date-fns/format";
299
+ import { Format as Format2, GeoLocation as GeoLocation2, TypeEnum as TypeEnum2 } from "@dxos/echo/internal";
300
+ var formatForDisplay = ({ type, format, value: value3, locale = void 0 }) => {
301
+ const formatScalar = (type2) => {
302
+ switch (type2) {
303
+ case TypeEnum2.Boolean:
304
+ return value3.toLocaleString().toUpperCase();
305
+ case TypeEnum2.Number:
306
+ return value3.toLocaleString(locale);
307
+ case TypeEnum2.String:
308
+ case TypeEnum2.Ref:
309
+ return String(value3);
310
+ default: {
311
+ return String(value3);
312
+ }
313
+ }
314
+ };
315
+ if (!format) {
316
+ return formatScalar(type);
649
317
  }
650
- };
651
- var BooleanInput = ({ type, label, inputOnly, getStatus, getValue: getValue3, onValueChange, disabled }) => {
652
- var _effect = _useSignals7();
653
- try {
654
- const { status, error } = getStatus();
655
- const checked = Boolean(getValue3());
656
- const { t } = useTranslation4(translationKey);
657
- return /* @__PURE__ */ React7.createElement(Input7.Root, {
658
- validationValence: status
659
- }, !inputOnly && /* @__PURE__ */ React7.createElement(InputHeader, {
660
- error,
661
- label
662
- }), disabled ? /* @__PURE__ */ React7.createElement("p", null, t(checked ? "boolean input true value" : "boolean input false value")) : /* @__PURE__ */ React7.createElement(Input7.Switch, {
663
- checked,
664
- onCheckedChange: (value) => onValueChange(type, value)
665
- }), inputOnly && /* @__PURE__ */ React7.createElement(Input7.DescriptionAndValidation, null, error));
666
- } finally {
667
- _effect.f();
318
+ switch (format) {
319
+ case Format2.TypeFormat.Boolean: {
320
+ return formatScalar(TypeEnum2.Boolean);
321
+ }
322
+ case Format2.TypeFormat.Number: {
323
+ return formatScalar(TypeEnum2.Number);
324
+ }
325
+ case Format2.TypeFormat.String:
326
+ case Format2.TypeFormat.Ref: {
327
+ return formatScalar(TypeEnum2.String);
328
+ }
329
+ case Format2.TypeFormat.Percent: {
330
+ return `${value3 * 100}%`;
331
+ }
332
+ case Format2.TypeFormat.Currency: {
333
+ return value3.toLocaleString(locale, {
334
+ style: "currency",
335
+ currency: "USD",
336
+ minimumFractionDigits: 2,
337
+ maximumFractionDigits: 2
338
+ });
339
+ }
340
+ case Format2.TypeFormat.Date: {
341
+ try {
342
+ return formatDate(new Date(value3), "yyyy-MM-dd");
343
+ } catch (error) {
344
+ return "Invalid Date";
345
+ }
346
+ }
347
+ case Format2.TypeFormat.Time: {
348
+ try {
349
+ return formatDate(new Date(value3), "HH:mm:ss");
350
+ } catch (error) {
351
+ return "Invalid Time";
352
+ }
353
+ }
354
+ case Format2.TypeFormat.DateTime: {
355
+ try {
356
+ return formatDate(new Date(value3), "yyyy-MM-dd HH:mm:ss");
357
+ } catch (error) {
358
+ return "Invalid DateTime";
359
+ }
360
+ }
361
+ case Format2.TypeFormat.GeoPoint: {
362
+ if (value3 === null || value3 === void 0) {
363
+ return "";
364
+ }
365
+ if (Array.isArray(value3) && value3.length >= 2 && value3.every(Number.isFinite)) {
366
+ const { latitude, longitude } = GeoLocation2.fromGeoPoint(value3);
367
+ return `${latitude},${longitude}`;
368
+ }
369
+ return String(value3);
370
+ }
371
+ default: {
372
+ if (value3 === null || value3 === "undefined") {
373
+ return "";
374
+ }
375
+ return String(value3);
376
+ }
668
377
  }
669
378
  };
670
- var SelectInput = ({ type, label, inputOnly, disabled, placeholder, options, getStatus, getValue: getValue3, onValueChange }) => {
671
- var _effect = _useSignals7();
672
- try {
673
- const { status, error } = getStatus();
674
- const value = getValue3();
675
- const handleValueChange = useCallback3((value2) => onValueChange(type, value2), [
676
- type,
677
- onValueChange
678
- ]);
679
- return disabled && !value ? null : /* @__PURE__ */ React7.createElement(Input7.Root, {
680
- validationValence: status
681
- }, !inputOnly && /* @__PURE__ */ React7.createElement(InputHeader, {
682
- error,
683
- label
684
- }), disabled ? /* @__PURE__ */ React7.createElement("p", null, options?.find(({ value: optionValue }) => optionValue === value)?.label ?? String(value)) : /* @__PURE__ */ React7.createElement(Select.Root, {
685
- value,
686
- onValueChange: handleValueChange
687
- }, /* @__PURE__ */ React7.createElement(Select.TriggerButton, {
688
- classNames: "is-full",
689
- disabled,
690
- placeholder
691
- }), /* @__PURE__ */ React7.createElement(Select.Portal, null, /* @__PURE__ */ React7.createElement(Select.Content, null, /* @__PURE__ */ React7.createElement(Select.Viewport, null, options?.map(({ value: value2, label: label2 }) => (
692
- // NOTE: Numeric values are converted to and from strings.
693
- /* @__PURE__ */ React7.createElement(Select.Option, {
694
- key: String(value2),
695
- value: String(value2)
696
- }, label2 ?? String(value2))
697
- )))))), inputOnly && /* @__PURE__ */ React7.createElement(Input7.DescriptionAndValidation, null, error));
698
- } finally {
699
- _effect.f();
379
+ var formatForEditing = ({ type, format, value: value3, locale = void 0 }) => {
380
+ const formatScalar = (type2) => {
381
+ switch (type2) {
382
+ case TypeEnum2.Boolean:
383
+ case TypeEnum2.Number:
384
+ case TypeEnum2.String:
385
+ case TypeEnum2.Ref:
386
+ return String(value3);
387
+ default:
388
+ return String(value3);
389
+ }
390
+ };
391
+ if (!format) {
392
+ return formatScalar(type);
700
393
  }
701
- };
702
- var MarkdownInput = ({ type, label, inputOnly, disabled, placeholder, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
703
- var _effect = _useSignals7();
704
- try {
705
- const { status, error } = getStatus();
706
- const textareaRef = useRef2(null);
707
- const adjustHeight = useCallback3(() => {
708
- const textarea = textareaRef.current;
709
- if (textarea) {
710
- textarea.style.blockSize = "auto";
711
- textarea.style.blockSize = `${textarea.scrollHeight + 2}px`;
394
+ switch (format) {
395
+ case Format2.TypeFormat.Boolean:
396
+ case Format2.TypeFormat.Number:
397
+ case Format2.TypeFormat.String:
398
+ case Format2.TypeFormat.Ref: {
399
+ return formatScalar(type);
400
+ }
401
+ case Format2.TypeFormat.Percent: {
402
+ return String(value3 * 100);
403
+ }
404
+ case Format2.TypeFormat.Currency: {
405
+ return String(value3);
406
+ }
407
+ case Format2.TypeFormat.DateTime: {
408
+ const date = new Date(value3);
409
+ return date.toISOString();
410
+ }
411
+ case Format2.TypeFormat.Date: {
412
+ const date = new Date(value3);
413
+ return date.toISOString().split("T")[0];
414
+ }
415
+ case Format2.TypeFormat.Time: {
416
+ const date = new Date(value3);
417
+ return date.toISOString().split("T")[1].split(".")[0];
418
+ }
419
+ case Format2.TypeFormat.GeoPoint: {
420
+ if (value3 === null || value3 === void 0) {
421
+ return "";
712
422
  }
713
- }, []);
714
- useEffect3(() => {
715
- adjustHeight();
716
- }, [
717
- adjustHeight
718
- ]);
719
- useEffect3(() => {
720
- adjustHeight();
721
- }, [
722
- getValue3(),
723
- adjustHeight
724
- ]);
725
- return /* @__PURE__ */ React7.createElement(Input7.Root, {
726
- validationValence: status
727
- }, !inputOnly && /* @__PURE__ */ React7.createElement(InputHeader, {
728
- error,
729
- label
730
- }), disabled ? /* @__PURE__ */ React7.createElement("p", null, getValue3() ?? "") : /* @__PURE__ */ React7.createElement(Input7.TextArea, {
731
- ref: textareaRef,
732
- disabled,
733
- placeholder,
734
- value: getValue3() ?? "",
735
- classNames: "min-bs-auto max-h-40 overflow-auto",
736
- onChange: (event) => onValueChange(type, event.target.value),
737
- onBlur,
738
- style: {
739
- resize: "none"
740
- },
741
- spellCheck: false
742
- }), inputOnly && /* @__PURE__ */ React7.createElement(Input7.Validation, null, error));
743
- } finally {
744
- _effect.f();
423
+ if (Array.isArray(value3) && value3.length >= 2 && value3.every(Number.isFinite)) {
424
+ const { latitude, longitude } = GeoLocation2.fromGeoPoint(value3);
425
+ return `${latitude},${longitude}`;
426
+ }
427
+ return String(value3);
428
+ }
429
+ default: {
430
+ return String(value3);
431
+ }
745
432
  }
746
433
  };
747
434
 
748
- // src/components/Form/Form.tsx
749
- import { useSignals as _useSignals13 } from "@preact-signals/safe-react/tracking";
750
- import React13, { useEffect as useEffect7, useMemo as useMemo6, useRef as useRef3 } from "react";
751
- import { cardDialogOverflow, cardSpacing as cardSpacing2 } from "@dxos/react-ui-stack";
752
-
753
- // src/components/Form/FormActions.tsx
754
- import { useSignals as _useSignals9 } from "@preact-signals/safe-react/tracking";
755
- import React9 from "react";
756
- import { IconButton as IconButton2, useTranslation as useTranslation5 } from "@dxos/react-ui";
757
- import { cardSpacing } from "@dxos/react-ui-stack";
758
- import { mx as mx6 } from "@dxos/react-ui-theme";
759
-
760
- // src/components/Form/FormContext.tsx
761
- import { useSignals as _useSignals8 } from "@preact-signals/safe-react/tracking";
762
- import React8, { createContext, useContext, useEffect as useEffect6, useMemo as useMemo3, useCallback as useCallback6 } from "react";
763
- import { raise } from "@dxos/debug";
764
- import { getValue as getValue2 } from "@dxos/echo-schema";
765
- import { createJsonPath as createJsonPath2 } from "@dxos/effect";
435
+ // src/util/properties.ts
436
+ import * as Option from "effect/Option";
437
+ import { Annotation } from "@dxos/echo";
438
+ import { getProperties } from "@dxos/effect";
439
+ var getFormProperties = (ast) => {
440
+ return getProperties(ast).filter((prop) => Annotation.FormInputAnnotation.getFromAst(prop.type).pipe(Option.getOrElse(() => true)));
441
+ };
766
442
 
767
- // src/hooks/useForm.ts
768
- import { SchemaAST } from "effect";
769
- import { useCallback as useCallback4, useEffect as useEffect4, useMemo as useMemo2, useState as useState3 } from "react";
770
- import { getValue, setValue } from "@dxos/echo-schema";
771
- import { createJsonPath, fromEffectValidationPath } from "@dxos/effect";
772
- import { invariant } from "@dxos/invariant";
773
- import { log } from "@dxos/log";
774
- import { validateSchema } from "@dxos/schema";
775
- var __dxlog_file = "/__w/dxos/dxos/packages/ui/react-ui-form/src/hooks/useForm.ts";
776
- var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, onSave }) => {
777
- invariant(SchemaAST.isTypeLiteral(schema.ast), void 0, {
778
- F: __dxlog_file,
779
- L: 93,
780
- S: void 0,
781
- A: [
782
- "SchemaAST.isTypeLiteral(schema.ast)",
783
- ""
784
- ]
785
- });
786
- const [values, setValues] = useState3(initialValues);
787
- useEffect4(() => {
788
- setValues(initialValues);
789
- }, [
790
- initialValues
791
- ]);
792
- const [touched, setTouched] = useState3(createKeySet(initialValues, false));
793
- const [changed, setChanged] = useState3(createKeySet(initialValues, false));
794
- const [errors, setErrors] = useState3({});
795
- const [saving, setSaving] = useState3(false);
796
- const validate = useCallback4((values2) => {
443
+ // src/util/refs.ts
444
+ import { Ref } from "@dxos/echo";
445
+ import { getArrayElementType, isArrayType } from "@dxos/effect";
446
+ var getRefProps = (ast) => {
447
+ if (isArrayType(ast)) {
448
+ const elementType = getArrayElementType(ast);
449
+ if (elementType) {
450
+ if (Ref.isRefType(elementType)) {
451
+ return {
452
+ ast: elementType,
453
+ isArray: true
454
+ };
455
+ }
456
+ }
457
+ }
458
+ if (Ref.isRefType(ast)) {
459
+ return {
460
+ ast,
461
+ isArray: false
462
+ };
463
+ }
464
+ return void 0;
465
+ };
466
+
467
+ // src/components/Form/fields/ArrayField.tsx
468
+ import * as Option3 from "effect/Option";
469
+ import * as SchemaAST2 from "effect/SchemaAST";
470
+ import React6, { useCallback as useCallback3 } from "react";
471
+ import { findNode as findNode2, getArrayElementType as getArrayElementType2, getDiscriminatedType as getDiscriminatedType2, isDiscriminatedUnion as isDiscriminatedUnion2, isNestedType as isNestedType2 } from "@dxos/effect";
472
+ import { IconButton as IconButton2, useTranslation as useTranslation4 } from "@dxos/react-ui";
473
+
474
+ // src/components/Form/Form.tsx
475
+ import { createContext } from "@radix-ui/react-context";
476
+ import * as Schema2 from "effect/Schema";
477
+ import React5, { useEffect as useEffect3, useMemo as useMemo4, useRef } from "react";
478
+ import { createJsonPath as createJsonPath3, getValue as getValue$2, setValue as setValue$2 } from "@dxos/effect";
479
+ import { IconButton, ScrollArea, useTranslation as useTranslation3 } from "@dxos/react-ui";
480
+ import { mx as mx4 } from "@dxos/ui-theme";
481
+
482
+ // src/hooks/useFormHandler.ts
483
+ import { useControllableState } from "@radix-ui/react-use-controllable-state";
484
+ import { useCallback, useEffect, useMemo, useState } from "react";
485
+ import { createJsonPath, fromEffectValidationPath, getValue as getValue$, setValue as setValue$ } from "@dxos/effect";
486
+ import { log } from "@dxos/log";
487
+ import { useDefaultValue } from "@dxos/react-ui";
488
+ import { validateSchema } from "@dxos/schema";
489
+ var __dxlog_file = "/__w/dxos/dxos/packages/ui/react-ui-form/src/hooks/useFormHandler.ts";
490
+ var useFormHandler = ({ schema, autoSave, values: valuesProp, defaultValues: defaultValuesProp, onValuesChanged, onValidate, onSave, onCancel, ...props }) => {
491
+ const [changed, setChanged] = useState({});
492
+ const [touched, setTouched] = useState({});
493
+ const [errors, setErrors] = useState({});
494
+ const [saving, setSaving] = useState(false);
495
+ const defaultValues = useDefaultValue(defaultValuesProp, () => ({}));
496
+ const [values$, setValues] = useControllableState({
497
+ prop: valuesProp,
498
+ defaultProp: defaultValues,
499
+ onChange: () => {
500
+ setValues(valuesProp ?? defaultValues);
501
+ setChanged({});
502
+ setTouched({});
503
+ setErrors({});
504
+ setSaving(false);
505
+ }
506
+ });
507
+ const values = values$;
508
+ const validate = useCallback((values2) => {
509
+ if (!schema) {
510
+ return false;
511
+ }
797
512
  let errors2 = validateSchema(schema, values2) ?? [];
513
+ log("validate", {
514
+ values: values2,
515
+ errors: errors2
516
+ }, {
517
+ F: __dxlog_file,
518
+ L: 157,
519
+ S: void 0,
520
+ C: (f, a) => f(...a)
521
+ });
798
522
  if (errors2.length === 0 && onValidate) {
799
523
  const validatedValues = values2;
800
524
  errors2 = onValidate(validatedValues) ?? [];
@@ -806,36 +530,38 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
806
530
  schema,
807
531
  onValidate
808
532
  ]);
809
- const [schemaDirty, setSchemaDirty] = useState3(false);
810
- useEffect4(() => setSchemaDirty(true), [
533
+ const [schemaChanged, setSchemaChanged] = useState(false);
534
+ useEffect(() => setSchemaChanged(true), [
811
535
  schema
812
536
  ]);
813
- useEffect4(() => {
814
- if (schemaDirty) {
537
+ useEffect(() => {
538
+ if (schemaChanged) {
815
539
  validate(values);
816
- setSchemaDirty(false);
540
+ setSchemaChanged(false);
817
541
  }
818
542
  }, [
543
+ validate,
819
544
  values,
820
- schemaDirty
545
+ schemaChanged
821
546
  ]);
822
- const formIsValid = useMemo2(() => {
547
+ const isValid = useMemo(() => {
823
548
  return Object.keys(errors).length === 0;
824
549
  }, [
825
550
  errors
826
551
  ]);
827
- const canSave = useMemo2(() => !saving && // Check if any error paths that are touched have errors.
552
+ const canSave = useMemo(() => !saving && // Check if any error paths that are touched have errors.
828
553
  !Object.entries(touched).some(([path, isTouched]) => isTouched && Object.keys(errors).some((errorPath) => errorPath === path || errorPath.startsWith(`${path}.`) || errorPath.startsWith(`${path}[`))), [
829
554
  touched,
830
555
  errors,
831
556
  saving
832
557
  ]);
833
- const handleSave = useCallback4(async () => {
558
+ const handleSave = useCallback(async () => {
834
559
  if (validate(values)) {
835
560
  setSaving(true);
836
561
  try {
837
562
  await onSave?.(values, {
838
- changed
563
+ changed,
564
+ isValid
839
565
  });
840
566
  } finally {
841
567
  setSaving(false);
@@ -845,11 +571,17 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
845
571
  values,
846
572
  validate,
847
573
  onSave,
848
- changed
574
+ changed,
575
+ isValid
576
+ ]);
577
+ const handleCancel = useCallback(() => {
578
+ onCancel?.();
579
+ }, [
580
+ onCancel
849
581
  ]);
850
- const getStatus = useCallback4((path) => {
582
+ const getStatus = useCallback((path) => {
851
583
  const jsonPath = Array.isArray(path) ? createJsonPath(path) : path;
852
- const matchingError = Object.entries(errors).find(([errorPath]) => errorPath === jsonPath || errorPath.startsWith(`${jsonPath}.`) || errorPath.startsWith(`${jsonPath}[`));
584
+ const [_, error] = Object.entries(errors).find(([errorPath]) => errorPath === jsonPath || errorPath.startsWith(`${jsonPath}.`) || errorPath.startsWith(`${jsonPath}[`)) ?? [];
853
585
  const isTouched = touched[jsonPath];
854
586
  if (!isTouched) {
855
587
  return {
@@ -858,111 +590,120 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
858
590
  };
859
591
  }
860
592
  return {
861
- status: matchingError ? "error" : void 0,
862
- error: matchingError ? matchingError[1] : void 0
593
+ status: error ? "error" : void 0,
594
+ error: error ? error ?? void 0 : void 0
863
595
  };
864
596
  }, [
865
597
  errors,
866
598
  touched
867
599
  ]);
868
- const getFormValue = useCallback4((path) => {
869
- return getValue(values, createJsonPath(path));
600
+ const getValue = useCallback((path) => {
601
+ return getValue$(values, createJsonPath(path));
870
602
  }, [
871
603
  values
872
604
  ]);
873
- const onValueChange = useCallback4((path, type, value) => {
605
+ const onValueChange = useCallback((path, type, value3) => {
606
+ log("onValueChange", {
607
+ path,
608
+ value: value3
609
+ }, {
610
+ F: __dxlog_file,
611
+ L: 256,
612
+ S: void 0,
613
+ C: (f, a) => f(...a)
614
+ });
874
615
  const jsonPath = createJsonPath(path);
875
- let parsedValue = value;
616
+ let parsedValue = value3;
876
617
  try {
877
- if (type === "number") {
878
- parsedValue = parseFloat(value) || 0;
618
+ if (type._tag === "NumberKeyword") {
619
+ parsedValue = parseFloat(value3) || 0;
879
620
  }
880
621
  } catch (err) {
881
622
  log.catch(err, void 0, {
882
623
  F: __dxlog_file,
883
- L: 213,
624
+ L: 265,
884
625
  S: void 0,
885
626
  C: (f, a) => f(...a)
886
627
  });
887
628
  parsedValue = void 0;
888
629
  }
889
630
  const newValues = {
890
- ...setValue(values, jsonPath, parsedValue)
631
+ ...setValue$(values, jsonPath, parsedValue)
891
632
  };
892
633
  setValues(newValues);
893
- setChanged((prev) => ({
894
- ...prev,
634
+ const newChanged = {
895
635
  [jsonPath]: true
896
- }));
897
- onValuesChanged?.(newValues);
898
- const isValid = validate(newValues);
899
- if (isValid && onValid) {
900
- onValid(newValues, {
901
- changed
902
- });
903
- }
636
+ };
637
+ setChanged({
638
+ ...changed,
639
+ ...newChanged
640
+ });
641
+ const isValid2 = validate(newValues);
642
+ onValuesChanged?.(newValues, {
643
+ isValid: isValid2,
644
+ changed: newChanged
645
+ });
904
646
  }, [
905
647
  values,
906
- onValuesChanged,
648
+ changed,
907
649
  validate,
908
- onValid,
909
- changed
650
+ onValuesChanged
910
651
  ]);
911
- const onTouched = useCallback4((path) => {
652
+ const onBlur = useCallback(async (path) => {
912
653
  const jsonPath = createJsonPath(path);
913
654
  setTouched((touched2) => ({
914
655
  ...touched2,
915
656
  [jsonPath]: true
916
657
  }));
917
- validate(values);
658
+ const isValid2 = validate(values);
659
+ if (Object.keys(changed).length > 0 && isValid2 && autoSave) {
660
+ await onSave?.(values, {
661
+ changed,
662
+ isValid: isValid2
663
+ });
664
+ }
918
665
  }, [
919
666
  validate,
920
- values
667
+ values,
668
+ changed,
669
+ autoSave,
670
+ onSave
921
671
  ]);
922
- return useMemo2(() => ({
672
+ return useMemo(() => ({
923
673
  // State.
674
+ schema,
924
675
  values,
925
676
  errors,
926
677
  touched,
927
678
  changed,
679
+ isValid,
928
680
  canSave,
929
- formIsValid,
681
+ autoSave,
930
682
  // Actions.
931
- handleSave,
683
+ onSave: handleSave,
684
+ onCancel: handleCancel,
932
685
  // Field utils.
933
686
  getStatus,
934
- getValue: getFormValue,
687
+ getValue,
688
+ onBlur,
935
689
  onValueChange,
936
- onTouched
690
+ ...props
937
691
  }), [
692
+ schema,
938
693
  values,
939
694
  errors,
940
695
  touched,
941
696
  changed,
942
697
  canSave,
943
- formIsValid,
944
- handleSave,
698
+ isValid,
945
699
  getStatus,
946
- getFormValue,
700
+ getValue,
701
+ onBlur,
947
702
  onValueChange,
948
- onTouched
703
+ onSave,
704
+ onCancel
949
705
  ]);
950
706
  };
951
- var createKeySet = (obj, value) => {
952
- invariant(obj, void 0, {
953
- F: __dxlog_file,
954
- L: 275,
955
- S: void 0,
956
- A: [
957
- "obj",
958
- ""
959
- ]
960
- });
961
- return Object.keys(obj).reduce((acc, key) => ({
962
- ...acc,
963
- [key]: value
964
- }), {});
965
- };
966
707
  var flatMap = (errors) => {
967
708
  return errors.reduce((result, { path, message }) => {
968
709
  const jsonPath = fromEffectValidationPath(path);
@@ -973,1398 +714,1790 @@ var flatMap = (errors) => {
973
714
  }, {});
974
715
  };
975
716
 
976
- // src/hooks/useRefQueryLookupHandler.ts
977
- import { useCallback as useCallback5 } from "react";
978
- import { getObjectDXN } from "@dxos/echo-schema";
979
- import { Filter } from "@dxos/react-client/echo";
980
- import { isNonNullable } from "@dxos/util";
981
- var useRefQueryLookupHandler = ({ space }) => {
982
- return useCallback5(async (typeInfo) => {
983
- if (!space) {
984
- return [];
985
- }
986
- const query = space.db.query(Filter.typename(typeInfo.typename));
987
- const { objects } = await query.run();
988
- return objects.map((object) => {
989
- const dxn = getObjectDXN(object);
990
- if (!dxn) {
991
- return void 0;
717
+ // src/hooks/useKeyHandler.tsx
718
+ import { useCallback as useCallback2, useEffect as useEffect2 } from "react";
719
+ import { addEventListener } from "@dxos/async";
720
+ var useKeyHandler = (formElement, form) => {
721
+ const handleKeyDown = useCallback2((event) => {
722
+ switch (event.key) {
723
+ case "Enter": {
724
+ if (event.metaKey && form.canSave) {
725
+ form.onSave();
726
+ }
727
+ break;
992
728
  }
993
- const item = {
994
- dxn,
995
- label: object?.name ?? object?.id ?? ""
996
- };
997
- return item;
998
- }).filter(isNonNullable);
729
+ }
999
730
  }, [
1000
- space
731
+ form.isValid,
732
+ form.canSave,
733
+ form.onSave,
734
+ form.autoSave
1001
735
  ]);
1002
- };
1003
-
1004
- // src/hooks/useQueryRefOptions.ts
1005
- import { useEffect as useEffect5, useState as useState4 } from "react";
1006
- import { log as log2 } from "@dxos/log";
1007
- var __dxlog_file2 = "/__w/dxos/dxos/packages/ui/react-ui-form/src/hooks/useQueryRefOptions.ts";
1008
- var useQueryRefOptions = ({ refTypeInfo, onQueryRefOptions }) => {
1009
- const [options, setOptions] = useState4([]);
1010
- const [loading, setLoading] = useState4(false);
1011
- useEffect5(() => {
1012
- if (!refTypeInfo || !onQueryRefOptions) {
736
+ useEffect2(() => {
737
+ if (!formElement) {
1013
738
  return;
1014
739
  }
1015
- const fetchOptions = async () => {
1016
- setLoading(true);
1017
- try {
1018
- const fetchedOptions = await onQueryRefOptions(refTypeInfo);
1019
- setOptions(fetchedOptions.map((option) => {
1020
- const dxn = option.dxn.toString();
1021
- return {
1022
- id: dxn,
1023
- label: option.label ?? dxn,
1024
- hue: "neutral"
1025
- };
1026
- }));
1027
- } catch (error) {
1028
- log2.error("Failed to fetch ref options:", error, {
1029
- F: __dxlog_file2,
1030
- L: 42,
1031
- S: void 0,
1032
- C: (f, a) => f(...a)
1033
- });
1034
- setOptions([]);
1035
- } finally {
1036
- setLoading(false);
1037
- }
1038
- };
1039
- void fetchOptions();
740
+ return addEventListener(formElement, "keydown", handleKeyDown);
1040
741
  }, [
1041
- refTypeInfo,
1042
- onQueryRefOptions
742
+ formElement,
743
+ handleKeyDown
1043
744
  ]);
1044
- return {
1045
- options,
1046
- loading
1047
- };
1048
745
  };
1049
746
 
1050
- // src/components/Form/FormContext.tsx
1051
- var FormContext = /* @__PURE__ */ createContext(void 0);
1052
- var useFormContext = () => {
1053
- return useContext(FormContext) ?? raise(new Error("Missing FormContext"));
1054
- };
1055
- var useFormValues = (path = []) => {
1056
- const { values: formValues } = useFormContext();
1057
- const jsonPath = createJsonPath2(path);
1058
- return getValue2(formValues, jsonPath);
1059
- };
1060
- var useInputProps = (path = []) => {
1061
- const { getStatus, getValue: getFormValue, onValueChange, onTouched } = useFormContext();
1062
- const stablePath = useMemo3(() => path, [
1063
- Array.isArray(path) ? path.join(".") : path
1064
- ]);
1065
- return useMemo3(() => ({
1066
- getStatus: () => getStatus(stablePath),
1067
- getValue: () => getFormValue(stablePath),
1068
- onValueChange: (type, value) => onValueChange(stablePath, type, value),
1069
- onBlur: () => onTouched(stablePath)
1070
- }), [
1071
- getStatus,
1072
- getFormValue,
1073
- onValueChange,
1074
- onTouched,
1075
- stablePath
1076
- ]);
1077
- };
1078
- var FormProvider = ({ children, formRef, autoSave, ...formOptions }) => {
1079
- var _effect = _useSignals8();
1080
- try {
1081
- const form = useForm(formOptions);
1082
- const handleKeyDown = useCallback6((event) => {
1083
- const keyIsEnter = event.key === "Enter";
1084
- const modifierUsed = event.ctrlKey || event.altKey || event.metaKey || event.shiftKey;
1085
- const inputIsTextarea = event.target.tagName.toLowerCase() === "textarea";
1086
- const inputOptOut = event.target.hasAttribute("data-no-submit") || event.target.closest("[data-no-submit]") !== null;
1087
- const shouldSubmitRegularInput = !inputIsTextarea && keyIsEnter && !modifierUsed;
1088
- const shouldSubmitTextarea = inputIsTextarea && keyIsEnter && event.metaKey;
1089
- if ((shouldSubmitRegularInput || shouldSubmitTextarea) && !inputOptOut) {
1090
- if (!autoSave && form.canSave) {
1091
- form.handleSave();
1092
- }
1093
- if (autoSave && form.formIsValid) {
1094
- event.target.blur();
1095
- }
1096
- }
1097
- }, [
1098
- form,
1099
- autoSave
1100
- ]);
1101
- useEffect6(() => {
1102
- if (!formRef?.current) {
1103
- return;
1104
- }
1105
- const formElement = formRef.current;
1106
- formElement.addEventListener("keydown", handleKeyDown);
1107
- return () => formElement.removeEventListener("keydown", handleKeyDown);
1108
- }, [
1109
- formRef,
1110
- form.canSave,
1111
- form.formIsValid,
1112
- form.handleSave,
1113
- autoSave
1114
- ]);
1115
- return /* @__PURE__ */ React8.createElement(FormContext.Provider, {
1116
- value: form
1117
- }, children);
1118
- } finally {
1119
- _effect.f();
1120
- }
747
+ // src/components/Form/FormFieldComponent.tsx
748
+ import React2, { Component } from "react";
749
+ import { Icon, Input as Input2, Tooltip } from "@dxos/react-ui";
750
+ import { errorText, mx as mx2 } from "@dxos/ui-theme";
751
+ var labelSpacing = "mbs-inputSpacingBlock mbe-labelSpacingBlock";
752
+ var FormFieldLabel = ({ label, error, readonly, asChild }) => {
753
+ const Label = readonly || asChild ? "span" : Input2.Label;
754
+ const labelProps = readonly || asChild ? {
755
+ className: "text-description text-sm"
756
+ } : {
757
+ classNames: "!mlb-0 text-sm"
758
+ };
759
+ return /* @__PURE__ */ React2.createElement("div", {
760
+ role: "none",
761
+ className: mx2("flex justify-between items-center", labelSpacing)
762
+ }, /* @__PURE__ */ React2.createElement(Label, labelProps, label), error && /* @__PURE__ */ React2.createElement(Tooltip.Trigger, {
763
+ asChild: true,
764
+ content: error,
765
+ side: "bottom"
766
+ }, /* @__PURE__ */ React2.createElement(Icon, {
767
+ icon: "ph--warning--regular",
768
+ size: 4,
769
+ classNames: errorText
770
+ })));
1121
771
  };
1122
-
1123
- // src/components/Form/FormActions.tsx
1124
- var FormActions = ({ readonly, onCancel, outerSpacing = true }) => {
1125
- var _effect = _useSignals9();
1126
- try {
1127
- const { t } = useTranslation5(translationKey);
1128
- const { canSave, handleSave } = useFormContext();
1129
- return /* @__PURE__ */ React9.createElement("div", {
1130
- role: "none",
1131
- className: mx6(outerSpacing === "scroll-fields" ? "pli-cardSpacingInline mbe-cardSpacingBlock" : outerSpacing ? cardSpacing : "mbs-cardSpacingBlock", "flex [&_button]:grow gap-1 first:mbs-0")
1132
- }, onCancel && !readonly && /* @__PURE__ */ React9.createElement(IconButton2, {
1133
- "data-testid": "cancel-button",
1134
- icon: "ph--x--regular",
1135
- label: t("button cancel"),
1136
- onClick: onCancel
1137
- }), handleSave && /* @__PURE__ */ React9.createElement(IconButton2, {
1138
- type: "submit",
1139
- "data-testid": "save-button",
1140
- disabled: !canSave,
1141
- icon: "ph--check--regular",
1142
- label: t("button save"),
1143
- onClick: handleSave
1144
- }));
1145
- } finally {
1146
- _effect.f();
772
+ FormFieldLabel.displayName = "Form.FieldLabel";
773
+ var FormFieldWrapper = (props) => {
774
+ const { children, readonly, layout, label, getStatus, getValue } = props;
775
+ const { status, error } = getStatus();
776
+ const value3 = getValue();
777
+ if (layout === "static" && value3 == null) {
778
+ return null;
1147
779
  }
780
+ const str = String(value3 ?? "");
781
+ return /* @__PURE__ */ React2.createElement(Input2.Root, {
782
+ validationValence: status
783
+ }, layout !== "inline" && /* @__PURE__ */ React2.createElement(FormFieldLabel, {
784
+ error,
785
+ readonly,
786
+ label
787
+ }), layout === "static" ? /* @__PURE__ */ React2.createElement("p", null, str) : children ? children({
788
+ value: value3
789
+ }) : null, layout === "full" && /* @__PURE__ */ React2.createElement(Input2.DescriptionAndValidation, null, /* @__PURE__ */ React2.createElement(Input2.Validation, null, error)));
1148
790
  };
1149
-
1150
- // src/components/Form/FormContent.tsx
1151
- import { useSignals as _useSignals12 } from "@preact-signals/safe-react/tracking";
1152
- import { Schema, SchemaAST as SchemaAST4, pipe as pipe2 } from "effect";
1153
- import { capitalize as capitalize2 } from "effect/String";
1154
- import React12, { forwardRef, useMemo as useMemo5 } from "react";
1155
- import { createJsonPath as createJsonPath3, findNode as findNode3, getDiscriminatedType as getDiscriminatedType2, isDiscriminatedUnion as isDiscriminatedUnion2 } from "@dxos/effect";
1156
- import { mx as mx8 } from "@dxos/react-ui-theme";
1157
- import { getSchemaProperties as getSchemaProperties2 } from "@dxos/schema";
1158
- import { isNotFalsy } from "@dxos/util";
1159
-
1160
- // src/components/Form/ArrayField.tsx
1161
- import { useSignals as _useSignals10 } from "@preact-signals/safe-react/tracking";
1162
- import { SchemaAST as SchemaAST3, pipe } from "effect";
1163
- import { capitalize } from "effect/String";
1164
- import React10, { Fragment, useCallback as useCallback7 } from "react";
1165
- import { findNode as findNode2, getDiscriminatedType, isDiscriminatedUnion, SimpleType } from "@dxos/effect";
1166
- import { invariant as invariant2 } from "@dxos/invariant";
1167
- import { IconButton as IconButton3, useTranslation as useTranslation6 } from "@dxos/react-ui";
1168
- import { getSchemaProperties } from "@dxos/schema";
1169
-
1170
- // src/util/field.ts
1171
- import { FormatEnum, GeoLocation as GeoLocation2, TypeEnum } from "@dxos/echo-schema";
1172
- var parseValue = ({ type, format, value }) => {
1173
- if (value === void 0 || value === null || value === "") {
1174
- return void 0;
791
+ var FormFieldErrorBoundary = class extends Component {
792
+ static getDerivedStateFromError(error) {
793
+ return {
794
+ error
795
+ };
1175
796
  }
1176
- const parseScalar = (type2) => {
1177
- switch (type2) {
1178
- case TypeEnum.Boolean: {
1179
- if (typeof value === "string") {
1180
- const lowercaseValue = value.toLowerCase();
1181
- if (lowercaseValue === "0" || lowercaseValue === "false") {
1182
- return false;
1183
- } else if (lowercaseValue === "1" || lowercaseValue === "true") {
1184
- return true;
1185
- }
1186
- }
1187
- return Boolean(value);
1188
- }
1189
- case TypeEnum.Number: {
1190
- const cleaned = typeof value === "string" ? value.replace(/[^0-9.-]+/g, "") : value;
1191
- const num = Number(cleaned);
1192
- return Number.isNaN(num) ? null : num;
1193
- }
1194
- case TypeEnum.String:
1195
- case TypeEnum.Ref:
1196
- return String(value);
1197
- default: {
1198
- return value;
1199
- }
1200
- }
797
+ state = {
798
+ error: void 0
1201
799
  };
1202
- if (!format) {
1203
- return parseScalar(type);
1204
- }
1205
- switch (format) {
1206
- case FormatEnum.Boolean:
1207
- return parseScalar(TypeEnum.Boolean);
1208
- case FormatEnum.Number:
1209
- case FormatEnum.Percent:
1210
- case FormatEnum.Currency:
1211
- return parseScalar(TypeEnum.Number);
1212
- case FormatEnum.String:
1213
- case FormatEnum.Markdown: {
1214
- return parseScalar(TypeEnum.String);
1215
- }
1216
- case FormatEnum.Ref:
1217
- throw new Error(`unexpected format: ${FormatEnum.Ref}`);
1218
- case FormatEnum.DateTime:
1219
- case FormatEnum.Date:
1220
- case FormatEnum.Time:
1221
- case FormatEnum.Timestamp: {
1222
- const date = new Date(value);
1223
- return isNaN(date.getTime()) ? null : date;
1224
- }
1225
- case FormatEnum.GeoPoint: {
1226
- if (typeof value === "string") {
1227
- const cleaned = value.replace(/[^0-9.,-]+/g, "");
1228
- const parts = cleaned.split(",");
1229
- if (parts.length === 2) {
1230
- const lat = parseFloat(parts[0]);
1231
- const long = parseFloat(parts[1]);
1232
- if (!isNaN(lat) && !isNaN(long)) {
1233
- return GeoLocation2.toGeoPoint({
1234
- latitude: lat,
1235
- longitude: long
1236
- });
1237
- }
1238
- }
1239
- }
1240
- return [
1241
- 0,
1242
- 0
1243
- ];
1244
- }
1245
- default: {
1246
- return value;
800
+ componentDidUpdate(prevProps) {
801
+ if (prevProps.path !== this.props.path) {
802
+ this.resetError();
1247
803
  }
1248
804
  }
1249
- };
1250
- var cellClassesForFieldType = ({ type, format }) => {
1251
- if (!format) {
1252
- switch (type) {
1253
- case TypeEnum.Number:
1254
- return [
1255
- "text-right",
1256
- "font-mono"
1257
- ];
1258
- case TypeEnum.Boolean:
1259
- return [
1260
- "text-right",
1261
- "font-mono"
1262
- ];
1263
- case TypeEnum.String:
1264
- return void 0;
1265
- case TypeEnum.Ref:
1266
- return void 0;
1267
- default: {
1268
- return void 0;
1269
- }
805
+ render() {
806
+ if (this.state.error) {
807
+ return /* @__PURE__ */ React2.createElement("div", {
808
+ className: "flex gap-2 border border-roseFill font-mono text-sm"
809
+ }, /* @__PURE__ */ React2.createElement("span", {
810
+ className: "bg-roseFill text-surfaceText pli-1 font-thin"
811
+ }, "ERROR"), String(this.props.path?.join(".")));
1270
812
  }
813
+ return this.props.children;
1271
814
  }
1272
- switch (format) {
1273
- case FormatEnum.Markdown:
1274
- return void 0;
1275
- case FormatEnum.Time:
1276
- case FormatEnum.Timestamp:
1277
- case FormatEnum.DateTime:
1278
- case FormatEnum.Date:
1279
- case FormatEnum.Duration:
1280
- return [
1281
- "font-mono",
1282
- "text-right"
1283
- ];
1284
- case FormatEnum.Currency:
1285
- return [
1286
- "text-right"
1287
- ];
1288
- case FormatEnum.JSON:
1289
- case FormatEnum.DID:
1290
- return [
1291
- "font-mono"
1292
- ];
1293
- default:
1294
- return void 0;
815
+ resetError() {
816
+ this.setState({
817
+ error: void 0
818
+ });
1295
819
  }
1296
820
  };
1297
- var pathNotUniqueError = (path) => ({
1298
- path: "path",
1299
- message: `'${path}' is already present`
1300
- });
1301
821
 
1302
- // src/util/formatting.ts
1303
- import { format as formatDate } from "date-fns/format";
1304
- import { FormatEnum as FormatEnum2, GeoLocation as GeoLocation3, TypeEnum as TypeEnum2 } from "@dxos/echo-schema";
1305
- var formatForDisplay = ({ type, format, value, locale = void 0 }) => {
1306
- const formatScalar = (type2) => {
1307
- switch (type2) {
1308
- case TypeEnum2.Boolean:
1309
- return value.toLocaleString().toUpperCase();
1310
- case TypeEnum2.Number:
1311
- return value.toLocaleString(locale);
1312
- case TypeEnum2.String:
1313
- case TypeEnum2.Ref:
1314
- return String(value);
1315
- default: {
1316
- return String(value);
1317
- }
1318
- }
822
+ // src/components/Form/FormFieldSet.tsx
823
+ import React4, { forwardRef, useMemo as useMemo3 } from "react";
824
+ import { mx as mx3 } from "@dxos/ui-theme";
825
+ import { isTruthy } from "@dxos/util";
826
+
827
+ // src/components/Form/FormField.tsx
828
+ import * as Match from "effect/Match";
829
+ import * as Option2 from "effect/Option";
830
+ import * as Schema from "effect/Schema";
831
+ import * as SchemaAST from "effect/SchemaAST";
832
+ import * as String2 from "effect/String";
833
+ import React3, { useMemo as useMemo2 } from "react";
834
+ import { Format as Format3 } from "@dxos/echo";
835
+ import { createJsonPath as createJsonPath2, findNode, getAnnotation, getDiscriminatedType, isArrayType as isArrayType2, isDiscriminatedUnion, isLiteralUnion, isNestedType } from "@dxos/effect";
836
+ import { useTranslation as useTranslation2 } from "@dxos/react-ui";
837
+ var FormField = (props) => {
838
+ const {
839
+ type,
840
+ name,
841
+ path,
842
+ projection,
843
+ fieldMap,
844
+ fieldProvider,
845
+ readonly,
846
+ layout,
847
+ // RefFieldProps
848
+ createOptionLabel,
849
+ createOptionIcon,
850
+ createInitialValuePath,
851
+ db,
852
+ schemaHook,
853
+ getOptions: getRefOptions,
854
+ onCreate
855
+ } = props;
856
+ const { t } = useTranslation2(translationKey);
857
+ const title = getAnnotation(SchemaAST.TitleAnnotationId)(type);
858
+ const description = getAnnotation(SchemaAST.DescriptionAnnotationId)(type);
859
+ const examples = getAnnotation(SchemaAST.ExamplesAnnotationId)(type);
860
+ const label = useMemo2(() => title ?? String2.capitalize(name), [
861
+ title,
862
+ name
863
+ ]);
864
+ const placeholder = useMemo2(() => examples?.length ? `${t("example placeholder")}: ${examples[0]}` : description ?? label, [
865
+ examples,
866
+ description,
867
+ label
868
+ ]);
869
+ const fieldState = useFormFieldState(FormField.displayName, path);
870
+ const fieldProps = {
871
+ type,
872
+ format: Format3.FormatAnnotation.getFromAst(type).pipe((annotation) => Option2.getOrUndefined(annotation)),
873
+ readonly,
874
+ label,
875
+ placeholder,
876
+ layout,
877
+ ...fieldState
1319
878
  };
1320
- if (!format) {
1321
- return formatScalar(type);
879
+ const jsonPath = createJsonPath2(path ?? []);
880
+ const CustomField = fieldMap?.[jsonPath];
881
+ if (CustomField) {
882
+ return /* @__PURE__ */ React3.createElement(CustomField, fieldProps);
1322
883
  }
1323
- switch (format) {
1324
- case FormatEnum2.Boolean: {
1325
- return formatScalar(TypeEnum2.Boolean);
1326
- }
1327
- case FormatEnum2.Number: {
1328
- return formatScalar(TypeEnum2.Number);
1329
- }
1330
- case FormatEnum2.String:
1331
- case FormatEnum2.Ref: {
1332
- return formatScalar(TypeEnum2.String);
1333
- }
1334
- case FormatEnum2.Percent: {
1335
- return `${value * 100}%`;
1336
- }
1337
- case FormatEnum2.Currency: {
1338
- return value.toLocaleString(locale, {
1339
- style: "currency",
1340
- currency: "USD",
1341
- minimumFractionDigits: 2,
1342
- maximumFractionDigits: 2
884
+ const component = fieldProvider?.({
885
+ schema: Schema.make(type),
886
+ prop: name,
887
+ fieldProps
888
+ });
889
+ if (component) {
890
+ return component;
891
+ }
892
+ if (isArrayType2(type)) {
893
+ return /* @__PURE__ */ React3.createElement(ArrayField, {
894
+ fieldProps: fieldState,
895
+ label,
896
+ ...props
897
+ });
898
+ }
899
+ const Field = getFormField(fieldProps);
900
+ if (Field) {
901
+ return /* @__PURE__ */ React3.createElement(Field, fieldProps);
902
+ }
903
+ const options = getOptions(type);
904
+ if (options) {
905
+ return /* @__PURE__ */ React3.createElement(SelectField, {
906
+ ...fieldProps,
907
+ options: options.map((option) => ({
908
+ value: option,
909
+ label: option.toString()
910
+ }))
911
+ });
912
+ }
913
+ const refProps = getRefProps(type);
914
+ if (refProps) {
915
+ return /* @__PURE__ */ React3.createElement(RefField, {
916
+ ...fieldProps,
917
+ ...refProps,
918
+ createOptionLabel,
919
+ createOptionIcon,
920
+ createInitialValuePath,
921
+ db,
922
+ schemaHook,
923
+ getOptions: getRefOptions,
924
+ onCreate
925
+ });
926
+ }
927
+ if (isNestedType(type)) {
928
+ const baseNode = findNode(type, isDiscriminatedUnion);
929
+ const typeLiteral = baseNode ? getDiscriminatedType(baseNode, fieldState.getValue()) : findNode(type, SchemaAST.isTypeLiteral);
930
+ if (typeLiteral) {
931
+ const schema = Schema.make(typeLiteral);
932
+ return /* @__PURE__ */ React3.createElement(FormFieldSet, {
933
+ schema,
934
+ path,
935
+ readonly,
936
+ layout,
937
+ label,
938
+ projection,
939
+ fieldMap,
940
+ fieldProvider,
941
+ createOptionLabel,
942
+ createOptionIcon,
943
+ createInitialValuePath,
944
+ db,
945
+ schemaHook,
946
+ getOptions: getRefOptions,
947
+ onCreate
1343
948
  });
1344
949
  }
1345
- case FormatEnum2.Date: {
1346
- try {
1347
- return formatDate(new Date(value), "yyyy-MM-dd");
1348
- } catch (error) {
1349
- return "Invalid Date";
1350
- }
1351
- }
1352
- case FormatEnum2.Time: {
1353
- try {
1354
- return formatDate(new Date(value), "HH:mm:ss");
1355
- } catch (error) {
1356
- return "Invalid Time";
1357
- }
1358
- }
1359
- case FormatEnum2.DateTime: {
1360
- try {
1361
- return formatDate(new Date(value), "yyyy-MM-dd HH:mm:ss");
1362
- } catch (error) {
1363
- return "Invalid DateTime";
1364
- }
950
+ }
951
+ return null;
952
+ };
953
+ FormField.displayName = "Form.FormField";
954
+ var getFormField = ({ type, format }) => {
955
+ const formatField = Match.value(format).pipe(Match.withReturnType(), Match.when(Format3.TypeFormat.GeoPoint, () => GeoPointField), Match.when(Format3.TypeFormat.Markdown, () => MarkdownField), Match.when(Format3.TypeFormat.Text, () => TextAreaField), Match.orElse(() => void 0));
956
+ if (formatField) {
957
+ return formatField;
958
+ }
959
+ switch (type._tag) {
960
+ // TODO(wittjosiah): Schema.Any is currently used to represent template inputs.
961
+ case "AnyKeyword":
962
+ case "StringKeyword":
963
+ return TextField;
964
+ case "NumberKeyword":
965
+ return NumberField;
966
+ case "BooleanKeyword":
967
+ return BooleanField;
968
+ }
969
+ };
970
+ var getOptions = (ast) => {
971
+ if (isLiteralUnion(ast)) {
972
+ return ast.types.map((type) => type.literal).filter((v) => v !== null);
973
+ }
974
+ return Format3.OptionsAnnotation.getFromAst(ast).pipe((annotation) => Option2.getOrUndefined(annotation));
975
+ };
976
+
977
+ // src/components/Form/FormFieldSet.tsx
978
+ var FormFieldSet = /* @__PURE__ */ forwardRef(({ classNames, label, schema, readonly, path, exclude, sort, projection, layout, ...props }, forwardRef4) => {
979
+ const values = useFormValues(FormFieldSet.displayName, path);
980
+ const properties = useMemo3(() => {
981
+ if (!schema) {
982
+ return [];
1365
983
  }
1366
- case FormatEnum2.GeoPoint: {
1367
- if (value === null || value === void 0) {
1368
- return "";
1369
- }
1370
- if (Array.isArray(value) && value.length >= 2 && value.every(Number.isFinite)) {
1371
- const { latitude, longitude } = GeoLocation3.fromGeoPoint(value);
1372
- return `${latitude},${longitude}`;
984
+ const props2 = getFormProperties(schema.ast);
985
+ if (projection) {
986
+ const fieldProjections = projection.getFieldProjections();
987
+ const hiddenProperties = new Set(projection.getHiddenProperties());
988
+ const visibleProps = props2.filter((prop) => !hiddenProperties.has(prop.name.toString()));
989
+ const orderedProps = [];
990
+ for (const fieldProjection of fieldProjections) {
991
+ const fieldPath = String(fieldProjection.field.path);
992
+ const prop = visibleProps.find((p) => p.name === fieldPath);
993
+ if (prop) {
994
+ orderedProps.push(prop);
995
+ }
1373
996
  }
1374
- return String(value);
997
+ const projectionPaths = new Set(fieldProjections.map((projection2) => String(projection2.field.path)));
998
+ const remainingProps = visibleProps.filter((prop) => !projectionPaths.has(prop.name.toString()));
999
+ orderedProps.push(...remainingProps);
1000
+ return orderedProps;
1375
1001
  }
1376
- default: {
1377
- if (value === null || value === "undefined") {
1378
- return "";
1379
- }
1380
- return String(value);
1002
+ const filtered = exclude ? exclude(props2) : props2;
1003
+ return sort ? filtered.sort(({ name: a }, { name: b }) => sort.indexOf(a.toString()) - sort.indexOf(b.toString())) : filtered;
1004
+ }, [
1005
+ schema,
1006
+ values,
1007
+ exclude,
1008
+ sort,
1009
+ projection?.fields
1010
+ ]);
1011
+ if ((readonly || layout === "static") && values == null) {
1012
+ return null;
1013
+ }
1014
+ return /* @__PURE__ */ React4.createElement("div", {
1015
+ role: "form",
1016
+ className: mx3("is-full", layout === "inline" && "flex flex-col gap-2", classNames),
1017
+ ref: forwardRef4
1018
+ }, layout !== "inline" && label && /* @__PURE__ */ React4.createElement(FormFieldLabel, {
1019
+ label,
1020
+ asChild: true
1021
+ }), properties.map((property) => {
1022
+ const name = property.name.toString();
1023
+ return /* @__PURE__ */ React4.createElement(FormFieldErrorBoundary, {
1024
+ key: name,
1025
+ path: [
1026
+ ...path ?? [],
1027
+ name
1028
+ ]
1029
+ }, /* @__PURE__ */ React4.createElement(FormField, {
1030
+ type: property.type,
1031
+ name,
1032
+ path: [
1033
+ ...path ?? [],
1034
+ name
1035
+ ],
1036
+ readonly,
1037
+ layout,
1038
+ projection,
1039
+ ...props
1040
+ }));
1041
+ }).filter(isTruthy));
1042
+ });
1043
+ FormFieldSet.displayName = "Form.Fields";
1044
+
1045
+ // src/components/Form/Form.tsx
1046
+ var omitId = (schema) => schema.pipe(Schema2.omit("id"));
1047
+ var [FormContextProvider, useFormContext] = createContext("Form");
1048
+ var useFormValues = (componentName, path = [], defaultValue) => {
1049
+ const jsonPath = createJsonPath3(path);
1050
+ const { form: { values } } = useFormContext(componentName);
1051
+ const value3 = getValue$2(values, jsonPath);
1052
+ useEffect3(() => {
1053
+ if (!value3 && defaultValue) {
1054
+ setValue$2(values, jsonPath, defaultValue());
1381
1055
  }
1056
+ }, [
1057
+ value3,
1058
+ defaultValue
1059
+ ]);
1060
+ return value3;
1061
+ };
1062
+ var useFormFieldState = (componentName, path = []) => {
1063
+ const stablePath = useMemo4(() => path, [
1064
+ Array.isArray(path) ? path.join(".") : path
1065
+ ]);
1066
+ const { form: { getStatus, getValue, onBlur, onValueChange } } = useFormContext(componentName);
1067
+ return useMemo4(() => ({
1068
+ getStatus: () => getStatus(stablePath),
1069
+ getValue: () => getValue(stablePath),
1070
+ onBlur: () => onBlur(stablePath),
1071
+ onValueChange: (ast, value3) => onValueChange(stablePath, ast, value3)
1072
+ }), [
1073
+ getStatus,
1074
+ getValue,
1075
+ onBlur,
1076
+ onValueChange,
1077
+ stablePath
1078
+ ]);
1079
+ };
1080
+ var FormRoot = ({ children, schema, values, onSave, onCancel, ...props }) => {
1081
+ const form = useFormHandler({
1082
+ schema,
1083
+ values,
1084
+ onSave,
1085
+ onCancel,
1086
+ ...props
1087
+ });
1088
+ return /* @__PURE__ */ React5.createElement(FormContextProvider, {
1089
+ form,
1090
+ ...props
1091
+ }, children);
1092
+ };
1093
+ FormRoot.displayName = "Form.Root";
1094
+ var FormViewport = ({ classNames, children }) => {
1095
+ return /* @__PURE__ */ React5.createElement(ScrollArea.Root, null, /* @__PURE__ */ React5.createElement(ScrollArea.Viewport, {
1096
+ classNames: [
1097
+ "plb-cardSpacingBlock",
1098
+ classNames
1099
+ ]
1100
+ }, children), /* @__PURE__ */ React5.createElement(ScrollArea.Scrollbar, {
1101
+ orientation: "vertical"
1102
+ }, /* @__PURE__ */ React5.createElement(ScrollArea.Thumb, null)));
1103
+ };
1104
+ FormViewport.displayName = "Form.Viewport";
1105
+ var FormContent = ({ classNames, children }) => {
1106
+ const { form, testId } = useFormContext(FormContent.displayName);
1107
+ const ref = useRef(null);
1108
+ useKeyHandler(ref.current, form);
1109
+ return /* @__PURE__ */ React5.createElement("div", {
1110
+ ref,
1111
+ role: "form",
1112
+ className: mx4("flex flex-col is-full pli-cardSpacingInline density-fine", classNames),
1113
+ "data-testid": testId
1114
+ }, children);
1115
+ };
1116
+ FormContent.displayName = "Form.Content";
1117
+ var FormFieldSet2 = ({ classNames }) => {
1118
+ const { form, ...props } = useFormContext(FormFieldSet2.displayName);
1119
+ return /* @__PURE__ */ React5.createElement(FormFieldSet, {
1120
+ classNames,
1121
+ schema: form.schema,
1122
+ ...props
1123
+ });
1124
+ };
1125
+ FormFieldSet2.displayName = "Form.FieldSet";
1126
+ var FormActions = ({ classNames }) => {
1127
+ const { t } = useTranslation3(translationKey);
1128
+ const { form: { canSave, onSave, onCancel }, readonly, layout } = useFormContext(FormActions.displayName);
1129
+ if (readonly || layout === "static") {
1130
+ return null;
1382
1131
  }
1132
+ return /* @__PURE__ */ React5.createElement("div", {
1133
+ role: "none",
1134
+ className: mx4("grid grid-flow-col auto-cols-fr gap-2 pbs-cardSpacingBlock", classNames)
1135
+ }, onCancel && /* @__PURE__ */ React5.createElement(IconButton, {
1136
+ icon: "ph--x--regular",
1137
+ iconEnd: true,
1138
+ label: t("cancel button label"),
1139
+ onClick: onCancel,
1140
+ "data-testid": "cancel-button"
1141
+ }), onSave && /* @__PURE__ */ React5.createElement(IconButton, {
1142
+ type: "submit",
1143
+ variant: "primary",
1144
+ disabled: !canSave,
1145
+ icon: "ph--check--regular",
1146
+ iconEnd: true,
1147
+ label: t("save button label"),
1148
+ onClick: onSave,
1149
+ "data-testid": "save-button"
1150
+ }));
1383
1151
  };
1384
- var formatForEditing = ({ type, format, value, locale = void 0 }) => {
1385
- const formatScalar = (type2) => {
1386
- switch (type2) {
1387
- case TypeEnum2.Boolean:
1388
- case TypeEnum2.Number:
1389
- case TypeEnum2.String:
1390
- case TypeEnum2.Ref:
1391
- return String(value);
1392
- default:
1393
- return String(value);
1152
+ FormActions.displayName = "Form.Actions";
1153
+ var FormSubmit = ({ classNames, label, icon }) => {
1154
+ const { t } = useTranslation3(translationKey);
1155
+ const { form: { canSave, onSave }, readonly, layout } = useFormContext(FormSubmit.displayName);
1156
+ if (readonly || layout === "static") {
1157
+ return null;
1158
+ }
1159
+ return /* @__PURE__ */ React5.createElement("div", {
1160
+ role: "none",
1161
+ className: mx4("flex is-full pbs-cardSpacingBlock", classNames)
1162
+ }, /* @__PURE__ */ React5.createElement(IconButton, {
1163
+ classNames: "is-full",
1164
+ type: "submit",
1165
+ variant: "primary",
1166
+ disabled: !canSave,
1167
+ icon: icon ?? "ph--check--regular",
1168
+ label: label ?? t("save button label"),
1169
+ onClick: onSave,
1170
+ "data-testid": "save-button"
1171
+ }));
1172
+ };
1173
+ FormSubmit.displayName = "Form.Submit";
1174
+ var Form = {
1175
+ Root: FormRoot,
1176
+ Viewport: FormViewport,
1177
+ Content: FormContent,
1178
+ FieldSet: FormFieldSet2,
1179
+ Actions: FormActions,
1180
+ Submit: FormSubmit,
1181
+ Label: FormFieldLabel
1182
+ };
1183
+
1184
+ // src/components/Form/fields/ArrayField.tsx
1185
+ var ArrayField = ({ type, path, label, readonly, layout, fieldProps: inputProps, ...props }) => {
1186
+ const { t } = useTranslation4(translationKey);
1187
+ const elementType = getArrayElementType2(type);
1188
+ const { onValueChange } = inputProps;
1189
+ const values = useFormValues(ArrayField.displayName, path, () => []);
1190
+ const getDefaultObjectValue = (typeNode) => {
1191
+ const baseNode = findNode2(typeNode, isDiscriminatedUnion2);
1192
+ const typeLiteral = baseNode ? getDiscriminatedType2(baseNode, {}) : findNode2(typeNode, SchemaAST2.isTypeLiteral);
1193
+ if (!typeLiteral) {
1194
+ return {};
1394
1195
  }
1196
+ return Object.fromEntries(getFormProperties(typeLiteral).map((prop) => {
1197
+ const defaultValue = SchemaAST2.getDefaultAnnotation(prop.type).pipe((annotation) => Option3.getOrUndefined(annotation));
1198
+ return [
1199
+ prop.name,
1200
+ defaultValue
1201
+ ];
1202
+ }));
1395
1203
  };
1396
- if (!format) {
1397
- return formatScalar(type);
1204
+ const handleAdd = useCallback3(() => {
1205
+ const defaultValue = isNestedType2(type) && elementType ? getDefaultObjectValue(elementType) : getDefaultValue(elementType);
1206
+ values && onValueChange(type, [
1207
+ ...values,
1208
+ defaultValue
1209
+ ]);
1210
+ }, [
1211
+ onValueChange,
1212
+ type,
1213
+ elementType,
1214
+ values
1215
+ ]);
1216
+ const handleDelete = useCallback3((idx) => {
1217
+ onValueChange(type, values?.filter((_, i) => i !== idx));
1218
+ }, [
1219
+ onValueChange,
1220
+ type,
1221
+ values
1222
+ ]);
1223
+ if (!elementType || (readonly || layout === "static") && values && values.length < 1) {
1224
+ return null;
1398
1225
  }
1399
- switch (format) {
1400
- case FormatEnum2.Boolean:
1401
- case FormatEnum2.Number:
1402
- case FormatEnum2.String:
1403
- case FormatEnum2.Ref: {
1404
- return formatScalar(type);
1405
- }
1406
- case FormatEnum2.Percent: {
1407
- return String(value * 100);
1408
- }
1409
- case FormatEnum2.Currency: {
1410
- return String(value);
1411
- }
1412
- case FormatEnum2.DateTime: {
1413
- const date = new Date(value);
1414
- return date.toISOString();
1415
- }
1416
- case FormatEnum2.Date: {
1417
- const date = new Date(value);
1418
- return date.toISOString().split("T")[0];
1226
+ return /* @__PURE__ */ React6.createElement(React6.Fragment, null, (layout !== "static" || values && values.length > 0) && /* @__PURE__ */ React6.createElement(FormFieldLabel, {
1227
+ readonly,
1228
+ label,
1229
+ asChild: true
1230
+ }), /* @__PURE__ */ React6.createElement("div", {
1231
+ role: "none",
1232
+ className: "flex flex-col gap-2"
1233
+ }, values?.map((_, index) => {
1234
+ return /* @__PURE__ */ React6.createElement("div", {
1235
+ role: "none",
1236
+ key: index,
1237
+ className: "grid grid-cols-[1fr_min-content] gap-2 last:mb-3"
1238
+ }, /* @__PURE__ */ React6.createElement(FormField, {
1239
+ autoFocus: index === values.length - 1,
1240
+ type: elementType,
1241
+ path: [
1242
+ ...path ?? [],
1243
+ index
1244
+ ],
1245
+ readonly: readonly || layout === "static",
1246
+ layout: "inline",
1247
+ ...props
1248
+ }), !readonly && layout !== "static" && /* @__PURE__ */ React6.createElement("div", {
1249
+ role: "none",
1250
+ className: "flex flex-col bs-full justify-end"
1251
+ }, /* @__PURE__ */ React6.createElement("div", {
1252
+ role: "none",
1253
+ className: "flex items-center bs-[var(--line-height)]"
1254
+ }, /* @__PURE__ */ React6.createElement(IconButton2, {
1255
+ icon: "ph--x--regular",
1256
+ iconOnly: true,
1257
+ label: t("button remove"),
1258
+ onClick: () => handleDelete(index),
1259
+ classNames: "self-center"
1260
+ }))));
1261
+ })), !readonly && layout !== "static" && /* @__PURE__ */ React6.createElement(IconButton2, {
1262
+ classNames: "flex is-full _mlb-cardSpacingBlock",
1263
+ icon: "ph--plus--regular",
1264
+ label: t("add field"),
1265
+ onClick: handleAdd
1266
+ }));
1267
+ };
1268
+ ArrayField.displayName = "Form.ArrayField";
1269
+ var getDefaultValue = (ast) => {
1270
+ switch (ast?._tag) {
1271
+ case "StringKeyword": {
1272
+ return "";
1419
1273
  }
1420
- case FormatEnum2.Time: {
1421
- const date = new Date(value);
1422
- return date.toISOString().split("T")[1].split(".")[0];
1274
+ case "NumberKeyword": {
1275
+ return 0;
1423
1276
  }
1424
- case FormatEnum2.GeoPoint: {
1425
- if (value === null || value === void 0) {
1426
- return "";
1427
- }
1428
- if (Array.isArray(value) && value.length >= 2 && value.every(Number.isFinite)) {
1429
- const { latitude, longitude } = GeoLocation3.fromGeoPoint(value);
1430
- return `${latitude},${longitude}`;
1431
- }
1432
- return String(value);
1277
+ case "BooleanKeyword": {
1278
+ return false;
1433
1279
  }
1434
1280
  default: {
1435
- return String(value);
1281
+ if (ast && isNestedType2(ast)) {
1282
+ return {};
1283
+ } else {
1284
+ throw new Error(`Unsupported type: ${ast?._tag}`);
1285
+ }
1436
1286
  }
1437
1287
  }
1438
1288
  };
1439
1289
 
1440
- // src/util/refs.ts
1441
- import { FormatEnum as FormatEnum3, ReferenceAnnotationId } from "@dxos/echo-schema";
1442
- import { findAnnotation } from "@dxos/effect";
1443
-
1444
- // src/util/schema.ts
1445
- import { SchemaAST as SchemaAST2 } from "effect";
1446
- import { findNode } from "@dxos/effect";
1447
- var findArrayElementType = (ast) => {
1448
- const tupleType = findNode(ast, SchemaAST2.isTupleType);
1449
- const elementType = tupleType?.rest[0]?.type;
1450
- return elementType;
1290
+ // src/components/Form/fields/BooleanField.tsx
1291
+ import React7, { useCallback as useCallback4 } from "react";
1292
+ import { Input as Input3 } from "@dxos/react-ui";
1293
+ var BooleanField = ({ type, readonly, onValueChange, ...props }) => {
1294
+ const handleChange = useCallback4((value3) => onValueChange?.(type, value3), [
1295
+ type,
1296
+ onValueChange
1297
+ ]);
1298
+ return /* @__PURE__ */ React7.createElement(FormFieldWrapper, {
1299
+ readonly,
1300
+ ...props
1301
+ }, ({ value: value3 }) => /* @__PURE__ */ React7.createElement(Input3.Switch, {
1302
+ disabled: !!readonly,
1303
+ checked: value3,
1304
+ onCheckedChange: handleChange
1305
+ }));
1451
1306
  };
1452
1307
 
1453
- // src/util/refs.ts
1454
- var getRefProps = (property) => {
1455
- const { ast, format, array } = property;
1456
- if (format === FormatEnum3.Ref) {
1457
- return {
1458
- ast,
1459
- isArray: false
1460
- };
1461
- }
1462
- if (array) {
1463
- const elementType = findArrayElementType(ast);
1464
- if (elementType) {
1465
- const annotation = findAnnotation(elementType, ReferenceAnnotationId);
1466
- if (annotation) {
1467
- return {
1468
- ast: elementType,
1469
- isArray: true
1308
+ // src/components/Form/fields/GeoPointField.tsx
1309
+ import React8, { useCallback as useCallback5, useEffect as useEffect4, useMemo as useMemo5, useState as useState2 } from "react";
1310
+ import { GeoLocation as GeoLocation3 } from "@dxos/echo/internal";
1311
+ import { Input as Input4, useTranslation as useTranslation5 } from "@dxos/react-ui";
1312
+ import { safeParseFloat } from "@dxos/util";
1313
+ var GeoPointField = ({ type, label, readonly, layout, getStatus, getValue, onValueChange, onBlur }) => {
1314
+ const { t } = useTranslation5(translationKey);
1315
+ const { status, error } = getStatus();
1316
+ const geoPoint = useMemo5(() => getValue() ?? [
1317
+ 0,
1318
+ 0
1319
+ ], [
1320
+ getValue
1321
+ ]);
1322
+ const value3 = useMemo5(() => GeoLocation3.fromGeoPoint(geoPoint), [
1323
+ geoPoint
1324
+ ]);
1325
+ const [longitudeText, setLongitudeText] = useState2(value3.longitude?.toString());
1326
+ const [latitudeText, setLatitudeText] = useState2(value3.latitude?.toString());
1327
+ useEffect4(() => {
1328
+ const location = GeoLocation3.fromGeoPoint(geoPoint);
1329
+ setLongitudeText(location.longitude?.toString());
1330
+ setLatitudeText(location.latitude?.toString());
1331
+ }, [
1332
+ geoPoint
1333
+ ]);
1334
+ const handleChange = useCallback5((coordinateType, setText) => (event) => {
1335
+ const inputText = event.target.value;
1336
+ setText(inputText);
1337
+ if (inputText !== "" && inputText !== "-") {
1338
+ const coord = safeParseFloat(inputText);
1339
+ if (coord !== void 0 && !isNaN(coord)) {
1340
+ const currentLocation = GeoLocation3.fromGeoPoint(getValue() ?? [
1341
+ 0,
1342
+ 0
1343
+ ]);
1344
+ const newLocation = {
1345
+ ...currentLocation,
1346
+ [coordinateType]: coord
1470
1347
  };
1348
+ const newValue = GeoLocation3.toGeoPoint(newLocation);
1349
+ onValueChange(type, newValue);
1471
1350
  }
1472
1351
  }
1352
+ }, [
1353
+ type,
1354
+ getValue,
1355
+ onValueChange
1356
+ ]);
1357
+ if ((readonly || layout === "static") && !value3?.latitude && !value3?.longitude) {
1358
+ return null;
1473
1359
  }
1474
- return void 0;
1360
+ return /* @__PURE__ */ React8.createElement(Input4.Root, {
1361
+ validationValence: status
1362
+ }, layout !== "inline" && /* @__PURE__ */ React8.createElement(FormFieldLabel, {
1363
+ error,
1364
+ readonly,
1365
+ label,
1366
+ asChild: true
1367
+ }), layout === "static" ? /* @__PURE__ */ React8.createElement(LatLng, value3) : /* @__PURE__ */ React8.createElement("div", {
1368
+ role: "none",
1369
+ className: "grid grid-cols-2 gap-2"
1370
+ }, /* @__PURE__ */ React8.createElement("div", null, /* @__PURE__ */ React8.createElement(Input4.Root, null, layout !== "inline" && /* @__PURE__ */ React8.createElement(Input4.Label, null, t("latitude label")), /* @__PURE__ */ React8.createElement(Input4.TextInput, {
1371
+ type: "number",
1372
+ step: "0.00001",
1373
+ min: "-90",
1374
+ max: "90",
1375
+ disabled: !!readonly,
1376
+ placeholder: t("latitude placeholder"),
1377
+ value: latitudeText ?? "",
1378
+ onChange: handleChange("latitude", setLatitudeText),
1379
+ onBlur
1380
+ }))), /* @__PURE__ */ React8.createElement("div", null, /* @__PURE__ */ React8.createElement(Input4.Root, null, layout !== "inline" && /* @__PURE__ */ React8.createElement(Input4.Label, null, t("longitude label")), /* @__PURE__ */ React8.createElement(Input4.TextInput, {
1381
+ type: "number",
1382
+ step: "0.00001",
1383
+ min: "-180",
1384
+ max: "180",
1385
+ disabled: !!readonly,
1386
+ placeholder: t("longitude placeholder"),
1387
+ value: longitudeText ?? "",
1388
+ onChange: handleChange("longitude", setLongitudeText),
1389
+ onBlur
1390
+ })))));
1391
+ };
1392
+ var LatLng = ({ latitude = 0, longitude = 0 }) => {
1393
+ const latHem = latitude >= 0 ? "N" : "S";
1394
+ const lngHem = longitude >= 0 ? "E" : "W";
1395
+ return /* @__PURE__ */ React8.createElement("span", {
1396
+ className: "inline-flex items-center gap-1"
1397
+ }, /* @__PURE__ */ React8.createElement("span", null, /* @__PURE__ */ React8.createElement("span", null, Math.abs(latitude).toFixed(5)), /* @__PURE__ */ React8.createElement("span", {
1398
+ className: "text-subdued"
1399
+ }, "\xB0", latHem)), /* @__PURE__ */ React8.createElement("span", null, /* @__PURE__ */ React8.createElement("span", null, Math.abs(longitude).toFixed(5)), /* @__PURE__ */ React8.createElement("span", {
1400
+ className: "text-subdued"
1401
+ }, "\xB0", lngHem)));
1475
1402
  };
1476
1403
 
1477
- // src/components/Form/ArrayField.tsx
1478
- var __dxlog_file3 = "/__w/dxos/dxos/packages/ui/react-ui-form/src/components/Form/ArrayField.tsx";
1479
- var ArrayField = ({ property, readonly, path, inputProps, Custom, lookupComponent }) => {
1480
- var _effect = _useSignals10();
1481
- try {
1482
- const { t } = useTranslation6(translationKey);
1483
- const { ast, name, type, title } = property;
1484
- const values = useFormValues(path ?? []) ?? [];
1485
- invariant2(Array.isArray(values), `Values at path ${path?.join(".")} must be an array.`, {
1486
- F: __dxlog_file3,
1487
- L: 36,
1488
- S: void 0,
1489
- A: [
1490
- "Array.isArray(values)",
1491
- "`Values at path ${path?.join('.')} must be an array.`"
1492
- ]
1493
- });
1494
- const label = title ?? pipe(name, capitalize);
1495
- const elementType = findArrayElementType(ast);
1496
- const getDefaultObjectValue = (typeNode) => {
1497
- const baseNode = findNode2(typeNode, isDiscriminatedUnion);
1498
- const typeLiteral = baseNode ? getDiscriminatedType(baseNode, {}) : findNode2(typeNode, SchemaAST3.isTypeLiteral);
1499
- if (!typeLiteral) {
1500
- return {};
1501
- }
1502
- return Object.fromEntries(getSchemaProperties(typeLiteral, {}).map((prop) => [
1503
- prop.name,
1504
- prop.defaultValue
1505
- ]));
1506
- };
1507
- const getDefaultValue = () => type === "object" && elementType ? getDefaultObjectValue(elementType) : SimpleType.getDefaultValue(type);
1508
- const handleAdd = useCallback7(() => {
1509
- inputProps.onValueChange(type, [
1510
- ...values,
1511
- getDefaultValue()
1512
- ]);
1513
- }, [
1514
- type,
1515
- elementType,
1516
- inputProps,
1517
- values
1518
- ]);
1519
- const handleRemove = useCallback7((index) => {
1520
- const newValues = values.filter((_, i) => i !== index);
1521
- inputProps.onValueChange(type, newValues);
1522
- }, [
1523
- type,
1524
- inputProps,
1525
- values
1526
- ]);
1527
- if (!elementType) {
1528
- return null;
1404
+ // src/components/Form/fields/MarkdownField.tsx
1405
+ import React9, { useCallback as useCallback6, useEffect as useEffect5, useRef as useRef2 } from "react";
1406
+ import { Input as Input5 } from "@dxos/react-ui";
1407
+ var MarkdownField = ({ type, readonly, label, placeholder, layout, getStatus, getValue, onValueChange, onBlur }) => {
1408
+ const { status, error } = getStatus();
1409
+ const textareaRef = useRef2(null);
1410
+ const adjustHeight = useCallback6(() => {
1411
+ const textarea = textareaRef.current;
1412
+ if (textarea) {
1413
+ textarea.style.blockSize = "auto";
1414
+ textarea.style.blockSize = `${textarea.scrollHeight + 2}px`;
1529
1415
  }
1530
- return readonly && values.length < 1 ? null : /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement(InputHeader, {
1531
- readonly: true,
1532
- label
1533
- }), /* @__PURE__ */ React10.createElement("div", {
1534
- role: "none",
1535
- className: readonly ? "flex flex-wrap gap-1 mlb-1" : values.length > 0 ? "grid gap-1 grid-cols-[1fr_min-content] mlb-1" : "hidden"
1536
- }, values.map((_value, index) => {
1537
- const field = /* @__PURE__ */ React10.createElement(FormField, {
1538
- property: {
1539
- ...property,
1540
- array: false,
1541
- ast: elementType
1542
- },
1543
- path: [
1544
- ...path ?? [],
1545
- index
1546
- ],
1547
- readonly,
1548
- inline: true,
1549
- Custom,
1550
- lookupComponent
1551
- });
1552
- return readonly ? field : /* @__PURE__ */ React10.createElement(Fragment, {
1553
- key: index
1554
- }, /* @__PURE__ */ React10.createElement("div", {
1555
- role: "none"
1556
- }, field), /* @__PURE__ */ React10.createElement(IconButton3, {
1557
- icon: "ph--trash--regular",
1558
- iconOnly: true,
1559
- label: t("button remove"),
1560
- onClick: () => handleRemove(index),
1561
- classNames: "self-center"
1562
- }));
1563
- })), !readonly && /* @__PURE__ */ React10.createElement(IconButton3, {
1564
- classNames: "is-full mlb-1 flex",
1565
- icon: "ph--plus--regular",
1566
- label: t("add field"),
1567
- onClick: handleAdd
1568
- }));
1569
- } finally {
1570
- _effect.f();
1416
+ }, []);
1417
+ useEffect5(() => {
1418
+ adjustHeight();
1419
+ }, [
1420
+ adjustHeight
1421
+ ]);
1422
+ useEffect5(() => {
1423
+ adjustHeight();
1424
+ }, [
1425
+ getValue(),
1426
+ adjustHeight
1427
+ ]);
1428
+ const handleChange = useCallback6((event) => onValueChange(type, event.target.value), [
1429
+ type,
1430
+ onValueChange
1431
+ ]);
1432
+ const value3 = getValue();
1433
+ if ((readonly || layout === "static") && !value3) {
1434
+ return null;
1571
1435
  }
1436
+ return /* @__PURE__ */ React9.createElement(Input5.Root, {
1437
+ validationValence: status
1438
+ }, layout !== "inline" && /* @__PURE__ */ React9.createElement(FormFieldLabel, {
1439
+ error,
1440
+ readonly,
1441
+ label
1442
+ }), layout === "static" ? /* @__PURE__ */ React9.createElement("p", null, value3) : /* @__PURE__ */ React9.createElement(Input5.TextArea, {
1443
+ ref: textareaRef,
1444
+ disabled: !!readonly,
1445
+ placeholder,
1446
+ value: value3 ?? "",
1447
+ classNames: "min-bs-auto max-bs-40 overflow-auto",
1448
+ onChange: handleChange,
1449
+ onBlur,
1450
+ style: {
1451
+ resize: "none"
1452
+ },
1453
+ spellCheck: false
1454
+ }), layout === "full" && /* @__PURE__ */ React9.createElement(Input5.Validation, null, error));
1572
1455
  };
1573
1456
 
1574
- // src/components/Form/RefField.tsx
1575
- import { useSignals as _useSignals11 } from "@preact-signals/safe-react/tracking";
1576
- import React11, { useCallback as useCallback8, useMemo as useMemo4 } from "react";
1577
- import { Expando, getTypeAnnotation, Ref, ReferenceAnnotationId as ReferenceAnnotationId2 } from "@dxos/echo-schema";
1578
- import { findAnnotation as findAnnotation2 } from "@dxos/effect";
1457
+ // src/components/Form/fields/NumberField.tsx
1458
+ import React10, { useCallback as useCallback7 } from "react";
1459
+ import { Input as Input6 } from "@dxos/react-ui";
1460
+ import { safeParseFloat as safeParseFloat2 } from "@dxos/util";
1461
+ var NumberField = ({ type, readonly, placeholder, onValueChange, onBlur, ...props }) => {
1462
+ const handleChange = useCallback7((event) => onValueChange(type, safeParseFloat2(event.target.value) || 0), [
1463
+ type,
1464
+ onValueChange
1465
+ ]);
1466
+ return /* @__PURE__ */ React10.createElement(FormFieldWrapper, {
1467
+ readonly,
1468
+ ...props
1469
+ }, ({ value: value3 = "" }) => /* @__PURE__ */ React10.createElement(Input6.TextInput, {
1470
+ type: "number",
1471
+ disabled: !!readonly,
1472
+ placeholder,
1473
+ value: value3,
1474
+ onChange: handleChange,
1475
+ onBlur
1476
+ }));
1477
+ };
1478
+
1479
+ // src/components/Form/fields/RefField.tsx
1480
+ import "@dxos/lit-ui/dx-tag-picker.pcss";
1481
+ import React12, { useCallback as useCallback9, useMemo as useMemo6 } from "react";
1482
+ import { Entity, Filter, Ref as Ref2 } from "@dxos/echo";
1483
+ import { ReferenceAnnotationId } from "@dxos/echo/internal";
1484
+ import { useQuery, useSchema as useSchema$ } from "@dxos/echo-react";
1485
+ import { findAnnotation } from "@dxos/effect";
1579
1486
  import { DXN } from "@dxos/keys";
1580
- import { DxRefTag } from "@dxos/lit-ui/react";
1581
- import { Input as Input8, useTranslation as useTranslation7 } from "@dxos/react-ui";
1582
- import { TagPicker } from "@dxos/react-ui-tag-picker";
1583
- import { descriptionText, mx as mx7 } from "@dxos/react-ui-theme";
1584
- import { isNonNullable as isNonNullable2 } from "@dxos/util";
1585
- var RefField = ({ type, label, disabled, placeholder, inputOnly, array, ast, getValue: getValue3, onBlur, onQueryRefOptions, onValueChange, ...restInputProps }) => {
1586
- var _effect = _useSignals11();
1587
- try {
1588
- const { t } = useTranslation7(translationKey);
1589
- const refTypeInfo = useMemo4(() => ast ? findAnnotation2(ast, ReferenceAnnotationId2) : void 0, [
1590
- ast
1591
- ]);
1592
- const { options: availableOptions, loading: _loading } = useQueryRefOptions({
1593
- refTypeInfo,
1594
- onQueryRefOptions
1595
- });
1596
- if (refTypeInfo && refTypeInfo?.typename === getTypeAnnotation(Expando)?.typename || !onQueryRefOptions) {
1597
- return /* @__PURE__ */ React11.createElement(RefFieldFallback, {
1598
- type,
1599
- label,
1600
- placeholder,
1601
- disabled,
1602
- inputOnly,
1603
- getValue: getValue3,
1604
- onBlur,
1605
- onValueChange,
1606
- ...restInputProps
1607
- });
1487
+ import { DxAnchor } from "@dxos/lit-ui/react";
1488
+ import { Button as Button2, Icon as Icon2, Input as Input7, useTranslation as useTranslation7 } from "@dxos/react-ui";
1489
+ import { descriptionText, mx as mx5 } from "@dxos/ui-theme";
1490
+
1491
+ // src/components/ObjectPicker/ObjectPicker.tsx
1492
+ import React11, { forwardRef as forwardRef2, useCallback as useCallback8, useState as useState3 } from "react";
1493
+ import { Popover, useTranslation as useTranslation6 } from "@dxos/react-ui";
1494
+ import { Combobox, useSearchListInput, useSearchListResults } from "@dxos/react-ui-searchlist";
1495
+ var CreateItem = ({ createOptionLabel, createOptionIcon, onCreateItemSelect }) => {
1496
+ const { t } = useTranslation6(translationKey);
1497
+ const { query } = useSearchListInput();
1498
+ return /* @__PURE__ */ React11.createElement(Combobox.Item, {
1499
+ value: "__create__",
1500
+ label: t(createOptionLabel[0], {
1501
+ ns: createOptionLabel[1].ns,
1502
+ text: query
1503
+ }),
1504
+ icon: createOptionIcon,
1505
+ classNames: "flex items-center gap-2",
1506
+ closeOnSelect: false,
1507
+ onSelect: () => {
1508
+ onCreateItemSelect(query);
1608
1509
  }
1609
- const handleGetValue = useCallback8(() => {
1610
- const formValue = getValue3();
1611
- const unknownToRefOption = (val) => {
1612
- if (Ref.isRef(val)) {
1613
- const dxnString = val.dxn.toString();
1614
- const matchingOption = availableOptions.find((option2) => option2.id === dxnString);
1615
- if (matchingOption) {
1616
- return matchingOption;
1617
- }
1618
- }
1619
- return void 0;
1620
- };
1621
- if (array && Array.isArray(formValue)) {
1622
- return formValue.map(unknownToRefOption).filter(isNonNullable2) ?? [];
1623
- }
1624
- const option = unknownToRefOption(formValue);
1625
- if (option) {
1626
- return [
1627
- option
1628
- ];
1629
- }
1630
- return [];
1631
- }, [
1632
- getValue3,
1633
- availableOptions,
1634
- array
1635
- ]);
1636
- const handleSearch = useCallback8((text, ids) => availableOptions.filter((option) => !ids.includes(option.id)).filter((option) => option.label.toLowerCase().includes(text.toLowerCase())).map((option) => ({
1637
- id: option.id,
1638
- label: option.label,
1639
- hue: option.hue
1640
- })), [
1641
- availableOptions
1642
- ]);
1643
- const handleUpdate = useCallback8((ids) => {
1644
- if (ids.length === 0) {
1645
- onValueChange("object", void 0);
1646
- return;
1647
- }
1648
- const refs = ids.map((id) => {
1649
- const item = availableOptions.find((option) => option.id === id);
1650
- if (item) {
1651
- const dxn = DXN.parse(item.id);
1652
- return Ref.fromDXN(dxn);
1653
- }
1654
- return null;
1655
- }).filter(isNonNullable2);
1656
- if (array) {
1657
- onValueChange("object", refs);
1658
- } else {
1659
- onValueChange("object", refs[0]);
1660
- }
1661
- }, [
1662
- availableOptions,
1663
- array,
1664
- onValueChange
1665
- ]);
1666
- const tagPickerMode = useMemo4(() => array ? "multi-select" : "single-select", [
1667
- array
1668
- ]);
1669
- if (!refTypeInfo) {
1670
- return null;
1671
- }
1672
- const { status, error } = restInputProps.getStatus();
1673
- const items = handleGetValue();
1674
- return disabled && items.length < 1 ? null : /* @__PURE__ */ React11.createElement(Input8.Root, {
1675
- validationValence: status
1676
- }, !inputOnly && /* @__PURE__ */ React11.createElement(InputHeader, {
1677
- error,
1678
- label
1679
- }), /* @__PURE__ */ React11.createElement("div", {
1680
- "data-no-submit": true
1681
- }, disabled ? items.length < 1 ? /* @__PURE__ */ React11.createElement("p", {
1682
- className: mx7(descriptionText, "mbe-2")
1683
- }, t("empty readonly ref field label")) : items.map((item) => /* @__PURE__ */ React11.createElement(DxRefTag, {
1684
- key: item.id,
1685
- refid: item.id,
1686
- rootclassname: "mie-1",
1687
- style: {
1688
- marginInlineEnd: ".25rem"
1689
- }
1690
- }, item.label)) : /* @__PURE__ */ React11.createElement(TagPicker, {
1691
- readonly: disabled,
1692
- items,
1693
- mode: tagPickerMode,
1694
- onBlur: (event) => onBlur(event),
1695
- onUpdate: handleUpdate,
1696
- onSearch: handleSearch,
1697
- classNames: "bg-inputSurface p-1.5 rounded-sm"
1698
- })), inputOnly && /* @__PURE__ */ React11.createElement(Input8.DescriptionAndValidation, null, error));
1699
- } finally {
1700
- _effect.f();
1701
- }
1510
+ });
1702
1511
  };
1703
- var RefFieldFallback = ({ type, label, disabled, placeholder, inputOnly, getValue: getValue3, onValueChange, ...restInputProps }) => {
1704
- var _effect = _useSignals11();
1705
- try {
1706
- const handleOnValueChange = (_type, dxnString) => {
1707
- const dxn = DXN.tryParse(dxnString);
1708
- if (dxn) {
1709
- onValueChange?.("object", Ref.fromDXN(dxn));
1710
- } else if (dxnString === "") {
1711
- onValueChange?.("object", void 0);
1712
- } else {
1713
- onValueChange?.("string", dxnString);
1714
- }
1715
- };
1716
- const handleGetValue = () => {
1717
- const formValue = getValue3();
1718
- if (typeof formValue === "string") {
1719
- return formValue;
1720
- }
1721
- if (Ref.isRef(formValue)) {
1722
- return formValue.dxn.toString();
1512
+ var ObjectPickerContent = /* @__PURE__ */ forwardRef2(({ options, selectedIds, createSchema, createOptionLabel, createOptionIcon, createInitialValuePath, onSelect, onCreate, ...props }, ref) => {
1513
+ const { t } = useTranslation6(translationKey);
1514
+ const [showForm, setShowForm] = useState3(false);
1515
+ const [formInitialValue, setFormInitialValue] = useState3("");
1516
+ const { results, handleSearch } = useSearchListResults({
1517
+ items: options
1518
+ });
1519
+ const handleFormSave = useCallback8((values) => {
1520
+ onCreate?.(values);
1521
+ setShowForm(false);
1522
+ setFormInitialValue("");
1523
+ }, [
1524
+ onCreate
1525
+ ]);
1526
+ const handleFormCancel = useCallback8(() => {
1527
+ setShowForm(false);
1528
+ setFormInitialValue("");
1529
+ }, []);
1530
+ const handleKeyDown = useCallback8((event) => {
1531
+ if (event.key === "Enter" && createSchema && event.currentTarget.querySelector('[role="option"][aria-selected="true"][data-value="__create__"]')) {
1532
+ event.stopPropagation();
1533
+ event.preventDefault();
1534
+ const input = event.currentTarget.querySelector('input[type="text"]');
1535
+ const currentQuery = input?.value || "";
1536
+ setFormInitialValue(currentQuery);
1537
+ setShowForm(true);
1538
+ }
1539
+ }, [
1540
+ createSchema
1541
+ ]);
1542
+ return /* @__PURE__ */ React11.createElement(Combobox.Content, {
1543
+ ...props,
1544
+ ref,
1545
+ onSearch: handleSearch,
1546
+ onKeyDownCapture: handleKeyDown
1547
+ }, showForm && createSchema ? /* @__PURE__ */ React11.createElement(Popover.Viewport, null, /* @__PURE__ */ React11.createElement(Form.Root, {
1548
+ testId: "create-referenced-object-form",
1549
+ schema: createSchema,
1550
+ values: createInitialValuePath ? {
1551
+ [createInitialValuePath]: formInitialValue
1552
+ } : {},
1553
+ onSave: handleFormSave,
1554
+ onCancel: handleFormCancel
1555
+ }, /* @__PURE__ */ React11.createElement(Form.Viewport, null, /* @__PURE__ */ React11.createElement(Form.Content, null, /* @__PURE__ */ React11.createElement(Form.FieldSet, null), /* @__PURE__ */ React11.createElement(Form.Actions, null))))) : /* @__PURE__ */ React11.createElement(React11.Fragment, null, /* @__PURE__ */ React11.createElement(Combobox.Input, {
1556
+ placeholder: t("ref field combobox input placeholder"),
1557
+ autoFocus: true
1558
+ }), /* @__PURE__ */ React11.createElement(Combobox.List, null, results.map((option) => /* @__PURE__ */ React11.createElement(Combobox.Item, {
1559
+ key: option.id,
1560
+ value: option.id,
1561
+ label: option.label,
1562
+ checked: selectedIds?.includes(option.id),
1563
+ onSelect: () => onSelect(option.id),
1564
+ classNames: "flex items-center gap-2"
1565
+ })), createOptionLabel && createOptionIcon && createSchema && onCreate && /* @__PURE__ */ React11.createElement(CreateItem, {
1566
+ createOptionLabel,
1567
+ createOptionIcon,
1568
+ onCreateItemSelect: (query) => {
1569
+ setFormInitialValue(query);
1570
+ setShowForm(true);
1571
+ }
1572
+ }))), /* @__PURE__ */ React11.createElement(Combobox.Arrow, null));
1573
+ });
1574
+ ObjectPickerContent.displayName = "ObjectPickerContent";
1575
+ var ObjectPicker = {
1576
+ Root: Combobox.Root,
1577
+ Portal: Combobox.Portal,
1578
+ Trigger: Combobox.Trigger,
1579
+ VirtualTrigger: Combobox.VirtualTrigger,
1580
+ Content: ObjectPickerContent
1581
+ };
1582
+
1583
+ // src/components/Form/fields/RefField.tsx
1584
+ var isRefSnapshot = (val) => {
1585
+ return typeof val === "object" && typeof val?.["/"] === "string";
1586
+ };
1587
+ var defaultGetOptions = (results) => results.map((result) => {
1588
+ const id = Entity.getDXN(result).toString();
1589
+ const label = Entity.getLabel(result);
1590
+ return {
1591
+ id,
1592
+ label: label ?? id
1593
+ };
1594
+ });
1595
+ var defaultResultsHook = (db, typename) => useQuery(db, typename ? Filter.typename(typename) : Filter.nothing());
1596
+ var RefField = (props) => {
1597
+ const { type, readonly, label, placeholder, layout, getStatus, getValue, createOptionLabel, createOptionIcon, createInitialValuePath, db, resultsHook: useResults = defaultResultsHook, schemaHook: useSchema = useSchema$, getOptions: getOptions2 = defaultGetOptions, onCreate, onValueChange } = props;
1598
+ const { t } = useTranslation7(translationKey);
1599
+ const { status, error } = getStatus();
1600
+ const typename = useMemo6(() => type ? findAnnotation(type, ReferenceAnnotationId)?.typename : void 0, [
1601
+ type
1602
+ ]);
1603
+ const results = useResults(db, typename);
1604
+ const options = useMemo6(() => getOptions2(results), [
1605
+ results,
1606
+ getOptions2
1607
+ ]);
1608
+ const handleGetValue = useCallback9(() => {
1609
+ const formValue = getValue();
1610
+ const unknownToRefOption = (value3) => {
1611
+ const isRef = Ref2.isRef(value3);
1612
+ if (isRef || isRefSnapshot(value3)) {
1613
+ const dxnString = isRef ? value3.dxn.toString() : value3["/"];
1614
+ const matchingOption = options.find((option) => option.id === dxnString);
1615
+ if (matchingOption) {
1616
+ return matchingOption;
1617
+ }
1723
1618
  }
1724
1619
  return void 0;
1725
1620
  };
1726
- return /* @__PURE__ */ React11.createElement(TextInput, {
1727
- type,
1728
- label,
1729
- disabled,
1730
- placeholder,
1731
- inputOnly,
1732
- getValue: handleGetValue,
1733
- onValueChange: handleOnValueChange,
1734
- ...restInputProps
1735
- });
1736
- } finally {
1737
- _effect.f();
1621
+ return unknownToRefOption(formValue);
1622
+ }, [
1623
+ options,
1624
+ getValue
1625
+ ]);
1626
+ const handleUpdate = useCallback9((id) => {
1627
+ const item2 = options.find((option) => option.id === id);
1628
+ const ref = item2 ? Ref2.fromDXN(DXN.parse(item2.id)) : void 0;
1629
+ onValueChange(type, ref);
1630
+ }, [
1631
+ options,
1632
+ type,
1633
+ onValueChange
1634
+ ]);
1635
+ const item = handleGetValue();
1636
+ const selectedIds = useMemo6(() => item ? [
1637
+ item.id
1638
+ ] : [], [
1639
+ item
1640
+ ]);
1641
+ const createSchema = useSchema(db, typename);
1642
+ const handleCreate = useCallback9((values) => {
1643
+ if (createSchema && onCreate) {
1644
+ onCreate(createSchema, values);
1645
+ }
1646
+ }, [
1647
+ createSchema,
1648
+ onCreate
1649
+ ]);
1650
+ const handleSelect = useCallback9((id) => {
1651
+ if (item?.id === id) {
1652
+ handleUpdate(void 0);
1653
+ } else {
1654
+ handleUpdate(id);
1655
+ }
1656
+ }, [
1657
+ item,
1658
+ handleUpdate
1659
+ ]);
1660
+ if (!typename || (readonly || layout === "static") && !item) {
1661
+ return null;
1738
1662
  }
1663
+ return /* @__PURE__ */ React12.createElement(Input7.Root, {
1664
+ validationValence: status
1665
+ }, layout !== "inline" && /* @__PURE__ */ React12.createElement(FormFieldLabel, {
1666
+ error,
1667
+ readonly,
1668
+ label
1669
+ }), /* @__PURE__ */ React12.createElement("div", null, readonly ? !item ? /* @__PURE__ */ React12.createElement("p", {
1670
+ className: mx5(descriptionText, "mbe-2")
1671
+ }, t("empty readonly ref field label")) : /* @__PURE__ */ React12.createElement(DxAnchor, {
1672
+ key: item.id,
1673
+ refid: item.id,
1674
+ rootclassname: "mie-1"
1675
+ }, item.label) : /* @__PURE__ */ React12.createElement(ObjectPicker.Root, null, /* @__PURE__ */ React12.createElement(ObjectPicker.Trigger, {
1676
+ asChild: true,
1677
+ classNames: "p-0"
1678
+ }, item ? /* @__PURE__ */ React12.createElement("div", {
1679
+ className: "flex gap-2 is-full"
1680
+ }, /* @__PURE__ */ React12.createElement(Input7.Root, {
1681
+ key: item.id
1682
+ }, /* @__PURE__ */ React12.createElement(Input7.TextInput, {
1683
+ value: item.label,
1684
+ readOnly: true,
1685
+ classNames: "is-full"
1686
+ }))) : /* @__PURE__ */ React12.createElement(Button2, {
1687
+ classNames: "is-full text-start gap-2"
1688
+ }, /* @__PURE__ */ React12.createElement("div", {
1689
+ role: "none",
1690
+ className: "grow overflow-hidden"
1691
+ }, /* @__PURE__ */ React12.createElement("span", {
1692
+ className: "flex truncate text-description"
1693
+ }, placeholder ?? t("ref field placeholder"))), /* @__PURE__ */ React12.createElement(Icon2, {
1694
+ size: 3,
1695
+ icon: "ph--caret-down--bold"
1696
+ }))), /* @__PURE__ */ React12.createElement(ObjectPicker.Portal, null, /* @__PURE__ */ React12.createElement(ObjectPicker.Content, {
1697
+ classNames: "popover-card-width",
1698
+ options,
1699
+ selectedIds,
1700
+ createSchema: createSchema && omitId(createSchema),
1701
+ createOptionLabel,
1702
+ createOptionIcon,
1703
+ createInitialValuePath,
1704
+ onCreate: handleCreate,
1705
+ onSelect: handleSelect
1706
+ })))), layout === "full" && /* @__PURE__ */ React12.createElement(Input7.DescriptionAndValidation, null, error));
1739
1707
  };
1740
1708
 
1741
- // src/components/Form/factory.tsx
1742
- import { FormatEnum as FormatEnum4 } from "@dxos/echo-schema";
1743
- var getInputComponent = (type, format) => {
1744
- switch (format) {
1745
- case FormatEnum4.GeoPoint:
1746
- return GeoPointInput;
1747
- case FormatEnum4.Markdown:
1748
- return MarkdownInput;
1749
- }
1750
- switch (type) {
1751
- case "string":
1752
- return TextInput;
1753
- case "number":
1754
- return NumberInput;
1755
- case "boolean":
1756
- return BooleanInput;
1709
+ // src/components/Form/fields/SelectField.tsx
1710
+ import React13, { useCallback as useCallback10 } from "react";
1711
+ import { Input as Input8, Select } from "@dxos/react-ui";
1712
+ var SelectField = ({ type, readonly, label, layout, placeholder, options, getStatus, getValue, onValueChange }) => {
1713
+ const { status, error } = getStatus();
1714
+ const value3 = getValue();
1715
+ const handleValueChange = useCallback10((value4) => onValueChange(type, value4), [
1716
+ type,
1717
+ onValueChange
1718
+ ]);
1719
+ if ((readonly || layout === "static") && value3 == null) {
1720
+ return null;
1757
1721
  }
1722
+ return /* @__PURE__ */ React13.createElement(Input8.Root, {
1723
+ validationValence: status
1724
+ }, layout !== "inline" && /* @__PURE__ */ React13.createElement(FormFieldLabel, {
1725
+ error,
1726
+ readonly,
1727
+ label
1728
+ }), layout === "static" ? /* @__PURE__ */ React13.createElement("p", null, options?.find(({ value: optionValue }) => optionValue === value3)?.label ?? String(value3)) : /* @__PURE__ */ React13.createElement(Select.Root, {
1729
+ value: value3,
1730
+ onValueChange: handleValueChange
1731
+ }, /* @__PURE__ */ React13.createElement(Select.TriggerButton, {
1732
+ classNames: "is-full",
1733
+ disabled: !!readonly,
1734
+ placeholder
1735
+ }), /* @__PURE__ */ React13.createElement(Select.Portal, null, /* @__PURE__ */ React13.createElement(Select.Content, null, /* @__PURE__ */ React13.createElement(Select.Viewport, null, options?.map(({ value: value4, label: label2 }) => (
1736
+ // NOTE: Numeric values are converted to and from strings.
1737
+ /* @__PURE__ */ React13.createElement(Select.Option, {
1738
+ key: String(value4),
1739
+ value: String(value4)
1740
+ }, label2 ?? String(value4))
1741
+ ))), /* @__PURE__ */ React13.createElement(Select.Arrow, null)))), layout === "full" && /* @__PURE__ */ React13.createElement(Input8.DescriptionAndValidation, null, error));
1758
1742
  };
1759
1743
 
1760
- // src/components/Form/FormContent.tsx
1761
- var FormField = ({ property, path, readonly, inline, onQueryRefOptions, lookupComponent, Custom }) => {
1762
- var _effect = _useSignals12();
1763
- try {
1764
- const { ast, name, type, format, title, description, options, examples, array } = property;
1765
- const inputProps = useInputProps(path);
1766
- const label = useMemo5(() => title ?? pipe2(name, capitalize2), [
1767
- title,
1768
- name
1769
- ]);
1770
- const placeholder = useMemo5(() => examples?.length ? `Example: "${examples[0]}"` : description, [
1771
- examples,
1772
- description
1773
- ]);
1774
- const FoundComponent = lookupComponent?.({
1775
- prop: name,
1776
- schema: Schema.make(ast),
1777
- inputProps: {
1778
- type,
1779
- format,
1780
- label,
1781
- disabled: readonly,
1782
- placeholder,
1783
- ...inputProps
1784
- }
1785
- });
1786
- if (FoundComponent) {
1787
- return FoundComponent;
1788
- }
1789
- const jsonPath = createJsonPath3(path ?? []);
1790
- const CustomComponent = Custom?.[jsonPath];
1791
- if (CustomComponent) {
1792
- return /* @__PURE__ */ React12.createElement(CustomComponent, {
1793
- type,
1794
- format,
1795
- label,
1796
- inputOnly: inline,
1797
- placeholder,
1798
- disabled: readonly,
1799
- ...inputProps
1800
- });
1801
- }
1802
- const refProps = getRefProps(property);
1803
- if (refProps) {
1804
- return /* @__PURE__ */ React12.createElement(RefField, {
1805
- ast: refProps.ast,
1806
- array: refProps.isArray,
1807
- type,
1808
- format,
1809
- label,
1810
- placeholder,
1811
- disabled: readonly,
1812
- inputOnly: inline,
1813
- onQueryRefOptions,
1814
- ...inputProps
1815
- });
1744
+ // src/components/Form/fields/SelectOptionField.tsx
1745
+ import React14, { useCallback as useCallback11, useEffect as useEffect6, useRef as useRef3, useState as useState4 } from "react";
1746
+ import { PublicKey } from "@dxos/keys";
1747
+ import { IconButton as IconButton3, Input as Input9, Tag, useTranslation as useTranslation8 } from "@dxos/react-ui";
1748
+ import { List } from "@dxos/react-ui-list";
1749
+ import { HuePicker } from "@dxos/react-ui-pickers";
1750
+ import { hues, osTranslations, subtleHover } from "@dxos/ui-theme";
1751
+ var SelectOptionField = ({ type, readonly, label, getStatus, getValue, onValueChange }) => {
1752
+ const { t } = useTranslation8(translationKey);
1753
+ const { status, error } = getStatus();
1754
+ const [selected, setSelectedId] = useState4(null);
1755
+ const [isNewOption, setIsNewOption] = useState4(false);
1756
+ const inputRef = useRef3(null);
1757
+ const options = getValue();
1758
+ useEffect6(() => {
1759
+ if (options === void 0) {
1760
+ onValueChange(type, []);
1816
1761
  }
1817
- if (array) {
1818
- return /* @__PURE__ */ React12.createElement(ArrayField, {
1819
- property,
1820
- path,
1821
- inputProps,
1822
- readonly,
1823
- Custom
1824
- });
1762
+ }, [
1763
+ options,
1764
+ type,
1765
+ onValueChange
1766
+ ]);
1767
+ const randomHue = () => {
1768
+ const usedHues = new Set(options?.map((option) => option.color) ?? []);
1769
+ const unusedHues = hues.filter((hue) => !usedHues.has(hue));
1770
+ if (unusedHues.length > 0) {
1771
+ return unusedHues[Math.floor(Math.random() * unusedHues.length)];
1825
1772
  }
1826
- const InputComponent = getInputComponent(type, format);
1827
- if (InputComponent) {
1828
- return /* @__PURE__ */ React12.createElement(InputComponent, {
1829
- type,
1830
- format,
1831
- label,
1832
- inputOnly: inline,
1833
- placeholder,
1834
- disabled: readonly,
1835
- ...inputProps
1836
- });
1773
+ const lastThree = new Set(options?.slice(-3).map((option) => option.color) ?? []);
1774
+ const availableHues = hues.filter((hue) => !lastThree.has(hue));
1775
+ return availableHues[Math.floor(Math.random() * availableHues.length)] ?? hues[Math.floor(Math.random() * hues.length)];
1776
+ };
1777
+ const handleAdd = useCallback11(() => {
1778
+ const newId = PublicKey.random().truncate();
1779
+ const newOption = {
1780
+ id: newId,
1781
+ title: "",
1782
+ color: randomHue()
1783
+ };
1784
+ onValueChange(type, [
1785
+ ...options ?? [],
1786
+ newOption
1787
+ ]);
1788
+ setSelectedId(newId);
1789
+ setIsNewOption(true);
1790
+ }, [
1791
+ options,
1792
+ type,
1793
+ onValueChange
1794
+ ]);
1795
+ const handleDelete = useCallback11((id) => {
1796
+ const newOptions = options?.filter((option) => option.id !== id) ?? [];
1797
+ onValueChange(type, newOptions);
1798
+ setSelectedId(null);
1799
+ }, [
1800
+ options,
1801
+ type,
1802
+ onValueChange
1803
+ ]);
1804
+ const handleMove = useCallback11((from, to) => {
1805
+ if (!options) {
1806
+ return;
1837
1807
  }
1838
- if (options) {
1839
- return /* @__PURE__ */ React12.createElement(SelectInput, {
1840
- type,
1841
- format,
1842
- disabled: readonly,
1843
- inputOnly: inline,
1844
- label,
1845
- options: options.map((option) => ({
1846
- value: option,
1847
- label: String(option)
1848
- })),
1849
- placeholder,
1850
- ...inputProps
1851
- });
1808
+ const newOptions = [
1809
+ ...options
1810
+ ];
1811
+ const [removed] = newOptions.splice(from, 1);
1812
+ newOptions.splice(to, 0, removed);
1813
+ onValueChange(type, newOptions);
1814
+ }, [
1815
+ options,
1816
+ type,
1817
+ onValueChange
1818
+ ]);
1819
+ const handleClick = useCallback11((id) => {
1820
+ setSelectedId((current) => current === id ? null : id);
1821
+ }, []);
1822
+ const handleTitleChange = useCallback11((id) => (e) => {
1823
+ const newOptions = options?.map((o) => o.id === id ? {
1824
+ ...o,
1825
+ title: e.target.value
1826
+ } : o);
1827
+ onValueChange(type, newOptions ?? []);
1828
+ }, [
1829
+ options,
1830
+ type,
1831
+ onValueChange
1832
+ ]);
1833
+ const handleKeyDown = useCallback11((ev) => {
1834
+ if (ev.key === "Enter") {
1835
+ setSelectedId(null);
1852
1836
  }
1853
- if (type === "object") {
1854
- const baseNode = findNode3(ast, isDiscriminatedUnion2);
1855
- const typeLiteral = baseNode ? getDiscriminatedType2(baseNode, inputProps.getValue()) : findNode3(ast, SchemaAST4.isTypeLiteral);
1856
- if (typeLiteral) {
1857
- return /* @__PURE__ */ React12.createElement(React12.Fragment, null, !inline && /* @__PURE__ */ React12.createElement("h3", {
1858
- className: mx8("text-lg mlb-inputSpacingBlock first:mbs-0")
1859
- }, label), /* @__PURE__ */ React12.createElement(FormFields, {
1860
- schema: Schema.make(typeLiteral),
1861
- path,
1862
- readonly,
1863
- onQueryRefOptions,
1864
- Custom,
1865
- lookupComponent
1866
- }));
1867
- }
1837
+ }, []);
1838
+ const handleColorChange = useCallback11((id) => (hue) => {
1839
+ const newOptions = options?.map((o) => o.id === id ? {
1840
+ ...o,
1841
+ color: hue
1842
+ } : o);
1843
+ onValueChange(type, newOptions ?? []);
1844
+ }, [
1845
+ options,
1846
+ type,
1847
+ onValueChange
1848
+ ]);
1849
+ useEffect6(() => {
1850
+ if (selected && isNewOption && inputRef.current) {
1851
+ inputRef.current.focus();
1852
+ setIsNewOption(false);
1868
1853
  }
1869
- return null;
1870
- } finally {
1871
- _effect.f();
1872
- }
1854
+ }, [
1855
+ selected,
1856
+ isNewOption
1857
+ ]);
1858
+ return /* @__PURE__ */ React14.createElement(Input9.Root, {
1859
+ validationValence: status
1860
+ }, /* @__PURE__ */ React14.createElement(FormFieldLabel, {
1861
+ error,
1862
+ readonly,
1863
+ label
1864
+ }), /* @__PURE__ */ React14.createElement("div", {
1865
+ role: "none"
1866
+ }, options && /* @__PURE__ */ React14.createElement(List.Root, {
1867
+ items: options,
1868
+ isItem: (_item) => true,
1869
+ onMove: readonly ? void 0 : handleMove,
1870
+ readonly: !!readonly
1871
+ }, ({ items }) => /* @__PURE__ */ React14.createElement("div", {
1872
+ role: "list",
1873
+ className: "is-full overflow-auto"
1874
+ }, items?.map((item) => /* @__PURE__ */ React14.createElement("div", {
1875
+ key: item.id
1876
+ }, /* @__PURE__ */ React14.createElement(List.Item, {
1877
+ role: "button",
1878
+ item,
1879
+ classNames: [
1880
+ subtleHover,
1881
+ "flex flex-col cursor-pointer rounded-sm"
1882
+ ],
1883
+ "aria-expanded": selected === item.id
1884
+ }, /* @__PURE__ */ React14.createElement("div", {
1885
+ className: "flex items-center"
1886
+ }, /* @__PURE__ */ React14.createElement(List.ItemDragHandle, {
1887
+ disabled: !!readonly
1888
+ }), /* @__PURE__ */ React14.createElement(List.ItemTitle, {
1889
+ onClick: () => handleClick(item.id),
1890
+ classNames: "flex-1"
1891
+ }, /* @__PURE__ */ React14.createElement(Tag, {
1892
+ palette: item.color
1893
+ }, item.title || "\u200B")), /* @__PURE__ */ React14.createElement(IconButton3, {
1894
+ iconOnly: true,
1895
+ variant: "ghost",
1896
+ label: t(selected === item.id ? "collapse label" : "expand label", {
1897
+ ns: osTranslations
1898
+ }),
1899
+ icon: selected === item.id ? "ph--caret-down--regular" : "ph--caret-right--regular",
1900
+ onClick: () => handleClick(item.id)
1901
+ })), selected === item.id && /* @__PURE__ */ React14.createElement("div", {
1902
+ className: "pli-cardSpacingInline plb-cardSpacingBlock flex flex-col gap-1 density-fine"
1903
+ }, /* @__PURE__ */ React14.createElement(Input9.Label, {
1904
+ classNames: "text-xs"
1905
+ }, t("select option label")), /* @__PURE__ */ React14.createElement("div", {
1906
+ className: "grid grid-cols-[1fr_min-content_min-content] gap-1"
1907
+ }, /* @__PURE__ */ React14.createElement(Input9.TextInput, {
1908
+ disabled: !!readonly,
1909
+ placeholder: t("select option label placeholder"),
1910
+ ref: selected === item.id ? inputRef : void 0,
1911
+ value: item.title,
1912
+ onChange: handleTitleChange(item.id),
1913
+ onKeyDown: handleKeyDown,
1914
+ classNames: "flex-1"
1915
+ }), /* @__PURE__ */ React14.createElement(HuePicker, {
1916
+ disabled: !!readonly,
1917
+ value: item.color,
1918
+ onChange: handleColorChange(item.id)
1919
+ }), /* @__PURE__ */ React14.createElement(IconButton3, {
1920
+ disabled: !!readonly,
1921
+ icon: "ph--trash--fill",
1922
+ iconOnly: true,
1923
+ label: t("select option delete"),
1924
+ onClick: () => handleDelete(item.id)
1925
+ })))))), /* @__PURE__ */ React14.createElement(IconButton3, {
1926
+ variant: "ghost",
1927
+ icon: "ph--plus--regular",
1928
+ label: t("select option add"),
1929
+ onClick: handleAdd,
1930
+ disabled: !!readonly,
1931
+ classNames: "is-full"
1932
+ })))));
1873
1933
  };
1874
- var FormFields = /* @__PURE__ */ forwardRef(({ classNames, testId, schema, path, filter, sort, readonly, lookupComponent, Custom, onQueryRefOptions }, forwardRef2) => {
1875
- var _effect = _useSignals12();
1876
- try {
1877
- const values = useFormValues(path);
1878
- const properties = useMemo5(() => {
1879
- const props = getSchemaProperties2(schema.ast, values);
1880
- const filtered = filter ? filter(props) : props;
1881
- return sort ? filtered.sort((a, b) => sort.indexOf(a.name) - sort.indexOf(b.name)) : filtered;
1882
- }, [
1883
- schema,
1884
- values,
1885
- filter,
1886
- sort
1887
- ]);
1888
- return /* @__PURE__ */ React12.createElement("div", {
1889
- role: "form",
1890
- className: mx8("is-full", classNames),
1891
- ref: forwardRef2
1892
- }, properties.map((property) => {
1893
- return /* @__PURE__ */ React12.createElement(FormField, {
1894
- key: property.name,
1895
- property,
1896
- path: [
1897
- ...path ?? [],
1898
- property.name
1899
- ],
1900
- readonly,
1901
- onQueryRefOptions,
1902
- lookupComponent,
1903
- Custom
1904
- });
1905
- }).filter(isNotFalsy));
1906
- } finally {
1907
- _effect.f();
1908
- }
1909
- });
1910
1934
 
1911
- // src/components/Form/Form.tsx
1912
- var Form = ({ classNames, testId, values: initialValues, autoFocus, readonly, autoSave, outerSpacing = true, onCancel, schema, onValuesChanged, onValidate, onSave, ...props }) => {
1913
- var _effect = _useSignals13();
1914
- try {
1915
- const formRef = useRef3(null);
1916
- const handleValid = useMemo6(() => autoSave ? onSave : void 0, [
1917
- autoSave,
1918
- onSave
1919
- ]);
1920
- useEffect7(() => {
1921
- if (autoFocus && formRef.current) {
1922
- const input = formRef.current.querySelector("input");
1923
- if (input) {
1924
- input.focus();
1925
- }
1926
- }
1927
- }, [
1928
- autoFocus
1929
- ]);
1930
- return /* @__PURE__ */ React13.createElement(FormProvider, {
1931
- formRef,
1932
- schema,
1933
- autoSave,
1934
- initialValues,
1935
- onValuesChanged,
1936
- onValidate,
1937
- onValid: handleValid,
1938
- onSave
1939
- }, /* @__PURE__ */ React13.createElement("div", {
1940
- role: "none",
1941
- className: "contents",
1942
- "data-testid": testId
1943
- }, /* @__PURE__ */ React13.createElement(FormFields, {
1944
- ...props,
1945
- ref: formRef,
1946
- classNames: [
1947
- outerSpacing === "blockStart-0" ? "pli-cardSpacingInline mbe-cardSpacingBlock [&>.mbs-inputSpacingBlock:first-child]:!mbs-0" : outerSpacing === "scroll-fields" ? "pli-cardSpacingInline pbe-cardSpacingBlock" : outerSpacing ? cardSpacing2 : false,
1948
- outerSpacing === "scroll-fields" && cardDialogOverflow,
1949
- classNames
1950
- ],
1951
- readonly,
1952
- schema
1953
- }), (onCancel || onSave) && !autoSave && /* @__PURE__ */ React13.createElement(FormActions, {
1954
- readonly,
1955
- onCancel,
1956
- outerSpacing
1957
- })));
1958
- } finally {
1959
- _effect.f();
1960
- }
1935
+ // src/components/Form/fields/TextField.tsx
1936
+ import React15, { useCallback as useCallback12 } from "react";
1937
+ import { Input as Input10 } from "@dxos/react-ui";
1938
+ var TextField = ({ type, readonly, placeholder, onValueChange, onBlur, ...props }) => {
1939
+ const handleChange = useCallback12((event) => onValueChange(type, event.target.value), [
1940
+ type,
1941
+ onValueChange
1942
+ ]);
1943
+ return /* @__PURE__ */ React15.createElement(FormFieldWrapper, {
1944
+ readonly,
1945
+ ...props
1946
+ }, ({ value: value3 = "" }) => /* @__PURE__ */ React15.createElement(Input10.TextInput, {
1947
+ noAutoFill: true,
1948
+ disabled: !!readonly,
1949
+ placeholder,
1950
+ value: value3,
1951
+ onChange: handleChange,
1952
+ onBlur
1953
+ }));
1954
+ };
1955
+
1956
+ // src/components/Form/fields/TextAreaField.tsx
1957
+ import React16, { useCallback as useCallback13 } from "react";
1958
+ import { Input as Input11 } from "@dxos/react-ui";
1959
+ var TextAreaField = ({ type, readonly, placeholder, onValueChange, onBlur, ...props }) => {
1960
+ const handleChange = useCallback13((event) => onValueChange(type, event.target.value), [
1961
+ type,
1962
+ onValueChange
1963
+ ]);
1964
+ return /* @__PURE__ */ React16.createElement(FormFieldWrapper, {
1965
+ readonly,
1966
+ ...props
1967
+ }, ({ value: value3 = "" }) => /* @__PURE__ */ React16.createElement(Input11.TextArea, {
1968
+ rows: 3,
1969
+ disabled: !!readonly,
1970
+ placeholder,
1971
+ value: value3,
1972
+ onChange: handleChange,
1973
+ onBlur
1974
+ }));
1975
+ };
1976
+
1977
+ // src/components/Form/fields/TupleField.tsx
1978
+ import React17 from "react";
1979
+ import { Input as Input12 } from "@dxos/react-ui";
1980
+ import { mx as mx6 } from "@dxos/ui-theme";
1981
+ import { safeParseFloat as safeParseFloat3 } from "@dxos/util";
1982
+ var gridCols = [
1983
+ "grid-cols-1",
1984
+ "grid-cols-2",
1985
+ "grid-cols-3",
1986
+ "grid-cols-4"
1987
+ ];
1988
+ var TupleField = ({ binding, type, readonly, label, getStatus, getValue, onValueChange, onBlur }) => {
1989
+ const { status, error } = getStatus();
1990
+ const values = getValue() ?? {};
1991
+ return /* @__PURE__ */ React17.createElement(Input12.Root, {
1992
+ validationValence: status
1993
+ }, /* @__PURE__ */ React17.createElement(FormFieldLabel, {
1994
+ error,
1995
+ readonly,
1996
+ label
1997
+ }), /* @__PURE__ */ React17.createElement("div", {
1998
+ className: mx6("grid gap-2", gridCols[binding.length - 1])
1999
+ }, binding.map((prop) => /* @__PURE__ */ React17.createElement(Input12.TextInput, {
2000
+ key: prop,
2001
+ type: "number",
2002
+ disabled: !!readonly,
2003
+ value: values[prop],
2004
+ onChange: (event) => {
2005
+ onValueChange(type, {
2006
+ ...values,
2007
+ [prop]: safeParseFloat3(event.target.value, 0)
2008
+ });
2009
+ },
2010
+ onBlur
2011
+ }))));
1961
2012
  };
1962
2013
 
1963
2014
  // src/components/FieldEditor/FieldEditor.tsx
1964
- var __dxlog_file4 = "/__w/dxos/dxos/packages/ui/react-ui-form/src/components/FieldEditor/FieldEditor.tsx";
1965
- var FieldEditor = ({ view, projection, field, registry, onSave, onCancel, outerSpacing }) => {
1966
- var _effect = _useSignals14();
1967
- try {
1968
- const { t } = useTranslation8(translationKey);
1969
- const [props, setProps] = useState5(projection.getFieldProjection(field.id).props);
1970
- useEffect8(() => setProps(projection.getFieldProjection(field.id).props), [
1971
- field,
1972
- projection
1973
- ]);
1974
- const [schemas, setSchemas] = useState5([]);
1975
- useEffect8(() => {
1976
- if (!registry) {
1977
- return;
2015
+ var __dxlog_file2 = "/__w/dxos/dxos/packages/ui/react-ui-form/src/components/FieldEditor/FieldEditor.tsx";
2016
+ var FieldEditor = ({ readonly, projection, field, registry, view, onSave, onCancel }) => {
2017
+ const { t } = useTranslation9(translationKey);
2018
+ const [props, setProps] = useState5(projection.getFieldProjection(field.id).props);
2019
+ useEffect7(() => setProps(projection.getFieldProjection(field.id).props), [
2020
+ field,
2021
+ projection
2022
+ ]);
2023
+ const [schemas, setSchemas] = useState5([]);
2024
+ useAsyncEffect(async () => {
2025
+ if (!registry) {
2026
+ return;
2027
+ }
2028
+ const subscription = registry.query().subscribe((query) => setSchemas(query.results), {
2029
+ fire: true
2030
+ });
2031
+ const schemas2 = await registry.query().run();
2032
+ setSchemas(schemas2);
2033
+ return () => subscription?.();
2034
+ }, [
2035
+ registry
2036
+ ]);
2037
+ const [referenceSchema, setReferenceSchema] = useState5();
2038
+ useEffect7(() => {
2039
+ setReferenceSchema(schemas.find((schema) => schema.typename === props?.referenceSchema));
2040
+ }, [
2041
+ schemas,
2042
+ props?.referenceSchema
2043
+ ]);
2044
+ const [{ fieldSchema }, setFieldSchema] = useState5({
2045
+ fieldSchema: getFormatSchema(props?.format)
2046
+ });
2047
+ const fieldMap = useMemo7(() => ({
2048
+ ["format"]: (props2) => /* @__PURE__ */ React18.createElement(SelectField, {
2049
+ ...props2,
2050
+ options: FormatEnums.filter((value3) => value3 !== Format4.TypeFormat.None).map((value3) => ({
2051
+ value: value3,
2052
+ label: t(`format ${value3}`)
2053
+ }))
2054
+ }),
2055
+ ["referenceSchema"]: (props2) => /* @__PURE__ */ React18.createElement(SelectField, {
2056
+ ...props2,
2057
+ options: schemas.map((schema) => ({
2058
+ value: schema.typename
2059
+ }))
2060
+ }),
2061
+ ["referencePath"]: (props2) => /* @__PURE__ */ React18.createElement(SelectField, {
2062
+ ...props2,
2063
+ options: referenceSchema ? getFormProperties(referenceSchema.ast).sort((a, b) => a.name.toString().localeCompare(b.name.toString())).map((p) => ({
2064
+ value: p.name.toString()
2065
+ })) : []
2066
+ }),
2067
+ ["options"]: (props2) => /* @__PURE__ */ React18.createElement(SelectOptionField, props2)
2068
+ }), [
2069
+ t,
2070
+ schemas,
2071
+ referenceSchema
2072
+ ]);
2073
+ const propIsNotType = useCallback14((props2) => props2.filter((prop) => prop.name !== "type"), []);
2074
+ const handleValuesChanged = useCallback14((_props) => {
2075
+ setFieldSchema((prev) => {
2076
+ const fieldSchema2 = getFormatSchema(_props.format);
2077
+ if (prev.fieldSchema === fieldSchema2) {
2078
+ return prev;
1978
2079
  }
1979
- const subscription = registry.query().subscribe((query) => setSchemas(query.results), {
1980
- fire: true
1981
- });
1982
- const t2 = setTimeout(async () => {
1983
- const schemas2 = await registry.query().run();
1984
- setSchemas(schemas2);
1985
- });
1986
- return () => {
1987
- clearTimeout(t2);
1988
- subscription?.();
2080
+ return {
2081
+ fieldSchema: fieldSchema2
1989
2082
  };
1990
- }, [
1991
- registry
1992
- ]);
1993
- const [referenceSchema, setReferenceSchema] = useState5();
1994
- useEffect8(() => {
1995
- setReferenceSchema(schemas.find((schema) => schema.typename === props?.referenceSchema));
1996
- }, [
1997
- schemas,
1998
- props?.referenceSchema
1999
- ]);
2000
- const [{ fieldSchema }, setFieldSchema] = useState5({
2001
- fieldSchema: getFormatSchema(props?.format)
2002
2083
  });
2003
- const handleValuesChanged = useCallback9((_props) => {
2004
- setFieldSchema((prev) => {
2005
- const fieldSchema2 = getFormatSchema(_props.format);
2006
- if (prev.fieldSchema === fieldSchema2) {
2007
- return prev;
2084
+ setReferenceSchema((prev) => {
2085
+ if (_props.referenceSchema !== prev?.typename) {
2086
+ const newSchema = schemas.find((schema) => schema.typename === _props.referenceSchema);
2087
+ if (newSchema) {
2088
+ return newSchema;
2008
2089
  }
2090
+ }
2091
+ return prev;
2092
+ });
2093
+ setProps((props2) => {
2094
+ const type = formatToType[_props.format];
2095
+ const additionalProps = formatToAdditionalPropertyAttributes[_props.format];
2096
+ if (props2.type !== type) {
2009
2097
  return {
2010
- fieldSchema: fieldSchema2
2098
+ ...props2,
2099
+ ..._props,
2100
+ ...additionalProps,
2101
+ type
2011
2102
  };
2012
- });
2013
- setReferenceSchema((prev) => {
2014
- if (_props.referenceSchema !== prev?.typename) {
2015
- const newSchema = schemas.find((schema) => schema.typename === _props.referenceSchema);
2016
- if (newSchema) {
2017
- return newSchema;
2018
- }
2019
- }
2020
- return prev;
2021
- });
2022
- setProps((props2) => {
2023
- const type = formatToType[_props.format];
2024
- if (props2.type !== type) {
2025
- return {
2026
- ...props2,
2027
- ..._props,
2028
- type
2029
- };
2103
+ }
2104
+ return _props;
2105
+ });
2106
+ }, [
2107
+ schemas,
2108
+ props.format,
2109
+ props.referenceSchema
2110
+ ]);
2111
+ const handleValidate = useCallback14(({ property }) => {
2112
+ if (property && projection.getFields().find((f) => f.path === property && f.path !== field.path)) {
2113
+ return [
2114
+ {
2115
+ path: "property",
2116
+ message: `property is not unique: '${property}'`
2030
2117
  }
2031
- return _props;
2118
+ ];
2119
+ }
2120
+ }, [
2121
+ projection.getFields(),
2122
+ field
2123
+ ]);
2124
+ const handleSave = useCallback14((props2) => {
2125
+ if (view) {
2126
+ Obj.change(view, () => {
2127
+ projection.setFieldProjection({
2128
+ field,
2129
+ props: props2
2130
+ });
2032
2131
  });
2033
- }, [
2034
- schemas,
2035
- props.format,
2036
- props.referenceSchema
2037
- ]);
2038
- const handleValidate = useCallback9(({ property }) => {
2039
- if (property && view.fields.find((f) => f.path === property && f.path !== field.path)) {
2040
- return [
2041
- {
2042
- path: "property",
2043
- message: `property is not unique: '${property}'`
2044
- }
2045
- ];
2046
- }
2047
- }, [
2048
- view.fields,
2049
- field
2050
- ]);
2051
- const handleSave = useCallback9((props2) => {
2132
+ } else {
2052
2133
  projection.setFieldProjection({
2053
2134
  field,
2054
2135
  props: props2
2055
2136
  });
2056
- onSave();
2057
- }, [
2058
- projection,
2059
- field,
2060
- onSave
2061
- ]);
2062
- const handleCancel = useCallback9(() => {
2063
- requestAnimationFrame(() => onCancel?.());
2064
- onSave();
2065
- }, [
2066
- onSave
2067
- ]);
2068
- const custom = useMemo7(() => ({
2069
- ["format"]: (props2) => /* @__PURE__ */ React14.createElement(SelectInput, {
2070
- ...props2,
2071
- options: FormatEnums.filter((value) => value !== FormatEnum5.None).map((value) => ({
2072
- value,
2073
- label: t(`format ${value}`)
2074
- }))
2075
- }),
2076
- ["referenceSchema"]: (props2) => /* @__PURE__ */ React14.createElement(SelectInput, {
2077
- ...props2,
2078
- options: schemas.map((schema) => ({
2079
- value: schema.typename
2080
- }))
2081
- }),
2082
- ["referencePath"]: (props2) => /* @__PURE__ */ React14.createElement(SelectInput, {
2083
- ...props2,
2084
- options: referenceSchema ? getSchemaProperties3(referenceSchema.ast).sort(sortProperties).map((p) => ({
2085
- value: p.name
2086
- })) : []
2087
- }),
2088
- ["options"]: (props2) => /* @__PURE__ */ React14.createElement(SelectOptionInput, props2)
2089
- }), [
2090
- t,
2091
- schemas,
2092
- referenceSchema
2093
- ]);
2094
- const propIsNotType = useCallback9((props2) => props2.filter((p) => p.name !== "type"), []);
2095
- if (!fieldSchema) {
2096
- log3.warn("invalid format", {
2097
- props
2098
- }, {
2099
- F: __dxlog_file4,
2100
- L: 183,
2101
- S: void 0,
2102
- C: (f, a) => f(...a)
2103
- });
2104
- return null;
2105
2137
  }
2106
- return /* @__PURE__ */ React14.createElement(Form, {
2107
- key: field.id,
2108
- autoFocus: true,
2109
- values: props,
2110
- schema: fieldSchema,
2111
- filter: propIsNotType,
2112
- sort: [
2113
- "property",
2114
- "format"
2115
- ],
2116
- onValuesChanged: handleValuesChanged,
2117
- onValidate: handleValidate,
2118
- onSave: handleSave,
2119
- onCancel: handleCancel,
2120
- Custom: custom,
2121
- outerSpacing
2138
+ onSave();
2139
+ }, [
2140
+ projection,
2141
+ field,
2142
+ view,
2143
+ onSave
2144
+ ]);
2145
+ const handleCancel = useCallback14(() => {
2146
+ onSave();
2147
+ requestAnimationFrame(() => onCancel?.());
2148
+ }, [
2149
+ onSave
2150
+ ]);
2151
+ if (!fieldSchema) {
2152
+ log2.warn("invalid format", {
2153
+ props
2154
+ }, {
2155
+ F: __dxlog_file2,
2156
+ L: 173,
2157
+ S: void 0,
2158
+ C: (f, a) => f(...a)
2122
2159
  });
2123
- } finally {
2124
- _effect.f();
2160
+ return null;
2125
2161
  }
2162
+ return /* @__PURE__ */ React18.createElement(Form.Root, {
2163
+ key: field.id,
2164
+ fieldMap,
2165
+ autoFocus: true,
2166
+ readonly,
2167
+ schema: fieldSchema,
2168
+ values: props,
2169
+ exclude: propIsNotType,
2170
+ sort: [
2171
+ "property",
2172
+ "format"
2173
+ ],
2174
+ onValuesChanged: handleValuesChanged,
2175
+ onValidate: handleValidate,
2176
+ onSave: handleSave,
2177
+ onCancel: handleCancel
2178
+ }, /* @__PURE__ */ React18.createElement(Form.Content, null, /* @__PURE__ */ React18.createElement(Form.FieldSet, null), /* @__PURE__ */ React18.createElement(Form.Actions, null)));
2126
2179
  };
2127
2180
 
2128
2181
  // src/components/ViewEditor/ViewEditor.tsx
2129
- import { useSignals as _useSignals15 } from "@preact-signals/safe-react/tracking";
2130
- import { Schema as Schema2 } from "effect";
2131
- import React15, { useCallback as useCallback10, useMemo as useMemo8, useState as useState6 } from "react";
2132
- import { EchoSchema, Format, isMutable, toJsonSchema } from "@dxos/echo-schema";
2133
- import { invariant as invariant3 } from "@dxos/invariant";
2134
- import { Callout, IconButton as IconButton4, useTranslation as useTranslation9 } from "@dxos/react-ui";
2182
+ import { RegistryContext } from "@effect-atom/atom-react";
2183
+ import * as Array2 from "effect/Array";
2184
+ import * as Match2 from "effect/Match";
2185
+ import * as Option4 from "effect/Option";
2186
+ import * as Schema3 from "effect/Schema";
2187
+ import React19, { forwardRef as forwardRef3, useCallback as useCallback15, useContext, useImperativeHandle, useMemo as useMemo8, useState as useState6 } from "react";
2188
+ import { Filter as Filter2, Format as Format5, Obj as Obj2, Query, QueryAST } from "@dxos/echo";
2189
+ import { EchoSchema, isMutable, toJsonSchema } from "@dxos/echo/internal";
2190
+ import { invariant } from "@dxos/invariant";
2191
+ import { Callout, IconButton as IconButton4, Input as Input13, useTranslation as useTranslation10 } from "@dxos/react-ui";
2192
+ import { QueryForm } from "@dxos/react-ui-components";
2135
2193
  import { List as List2 } from "@dxos/react-ui-list";
2136
- import { cardSpacing as cardSpacing3 } from "@dxos/react-ui-stack";
2137
- import { ghostHover, inputTextLabel, mx as mx9 } from "@dxos/react-ui-theme";
2138
- import { FieldSchema, ViewProjection, VIEW_FIELD_LIMIT } from "@dxos/schema";
2139
- var __dxlog_file5 = "/__w/dxos/dxos/packages/ui/react-ui-form/src/components/ViewEditor/ViewEditor.tsx";
2140
- var grid = "grid grid-cols-[32px_1fr_32px_32px] min-bs-[2.5rem]";
2141
- var ViewMetaSchema = Schema2.Struct({
2142
- name: Schema2.String.annotations({
2143
- title: "View"
2144
- }),
2145
- typename: Format.URL.annotations({
2146
- title: "Typename"
2147
- })
2148
- }).pipe(Schema2.mutable);
2149
- var ViewEditor = ({ classNames, schema, view, registry, readonly: _readonly, showHeading = false, onTypenameChanged, onDelete, outerSpacing = true }) => {
2150
- var _effect = _useSignals15();
2151
- try {
2152
- const { t } = useTranslation9(translationKey);
2153
- const projection = useMemo8(() => {
2154
- const jsonSchema = schema instanceof EchoSchema ? schema.jsonSchema : toJsonSchema(schema);
2155
- return new ViewProjection(jsonSchema, view);
2156
- }, [
2157
- schema,
2158
- view
2159
- ]);
2160
- const [field, setField] = useState6();
2161
- const readonly = _readonly || !isMutable(schema);
2162
- const viewValues = useMemo8(() => {
2163
- return {
2164
- name: view.name,
2165
- // TODO(burdon): Need to warn user of possible consequences of editing.
2166
- // TODO(burdon): Settings should have domain name owned by user.
2167
- typename: view.query.typename
2168
- };
2169
- }, [
2170
- view
2171
- ]);
2172
- const handleSelect = useCallback10((field2) => {
2173
- if (readonly) {
2174
- return;
2175
- }
2176
- setField((f) => f === field2 ? void 0 : field2);
2177
- }, [
2178
- readonly
2179
- ]);
2180
- const handleAdd = useCallback10(() => {
2181
- invariant3(!readonly, void 0, {
2182
- F: __dxlog_file5,
2183
- L: 92,
2184
- S: void 0,
2185
- A: [
2186
- "!readonly",
2187
- ""
2188
- ]
2189
- });
2190
- const field2 = projection.createFieldProjection();
2191
- setField(field2);
2192
- }, [
2193
- schema,
2194
+ import { FieldSchema, ProjectionModel, VIEW_FIELD_LIMIT, createDirectChangeCallback, createEchoChangeCallback, getTypenameFromQuery } from "@dxos/schema";
2195
+ import { mx as mx7, osTranslations as osTranslations2, subtleHover as subtleHover2 } from "@dxos/ui-theme";
2196
+ var __dxlog_file3 = "/__w/dxos/dxos/packages/ui/react-ui-form/src/components/ViewEditor/ViewEditor.tsx";
2197
+ var listGrid = "grid grid-cols-[min-content_1fr_min-content_min-content_min-content]";
2198
+ var listItemGrid = "grid grid-cols-subgrid col-span-5";
2199
+ var ViewEditor = /* @__PURE__ */ forwardRef3(({ classNames, schema, view, mode = "schema", registry, readonly, showHeading = false, types, tags, onQueryChanged, onDelete }, forwardedRef) => {
2200
+ const atomRegistry = useContext(RegistryContext);
2201
+ const schemaReadonly = !isMutable(schema);
2202
+ const { t } = useTranslation10(translationKey);
2203
+ const projectionModel = useMemo8(() => {
2204
+ const jsonSchema = schema instanceof EchoSchema ? schema.jsonSchema : toJsonSchema(schema);
2205
+ const change = schema instanceof EchoSchema ? createEchoChangeCallback(view, schema) : createDirectChangeCallback(view.projection, jsonSchema);
2206
+ const model = new ProjectionModel({
2207
+ registry: atomRegistry,
2194
2208
  view,
2195
- readonly
2196
- ]);
2197
- const handleUpdate = useCallback10(({ name, typename }) => {
2198
- invariant3(!readonly, void 0, {
2199
- F: __dxlog_file5,
2200
- L: 99,
2201
- S: void 0,
2202
- A: [
2203
- "!readonly",
2204
- ""
2205
- ]
2206
- });
2207
- requestAnimationFrame(() => {
2208
- if (view.name !== name) {
2209
- view.name = name;
2210
- }
2211
- if (view.query.typename !== typename && !readonly) {
2212
- onTypenameChanged?.(typename);
2213
- }
2214
- });
2215
- }, [
2216
- schema,
2209
+ baseSchema: jsonSchema,
2210
+ change
2211
+ });
2212
+ return model;
2213
+ }, [
2214
+ atomRegistry,
2215
+ schema,
2216
+ view
2217
+ ]);
2218
+ useImperativeHandle(forwardedRef, () => projectionModel, [
2219
+ projectionModel
2220
+ ]);
2221
+ const [expandedField, setExpandedField] = useState6();
2222
+ const queueTarget = Match2.value(view.query.ast).pipe(Match2.when({
2223
+ type: "options"
2224
+ }, ({ options }) => {
2225
+ return Option4.fromNullable(options.queues).pipe(Option4.flatMap((queues) => Array2.head(queues)), Option4.getOrUndefined);
2226
+ }), Match2.orElse(() => void 0));
2227
+ const viewSchema = useMemo8(() => {
2228
+ const base = Schema3.Struct({
2229
+ query: mode === "schema" ? Format5.URL.annotations({
2230
+ title: "Record type"
2231
+ }) : QueryAST.Query.annotations({
2232
+ title: "Query"
2233
+ })
2234
+ });
2235
+ if (mode === "tag") {
2236
+ return Schema3.Struct({
2237
+ ...base.fields,
2238
+ target: Schema3.optional(Schema3.String.annotations({
2239
+ title: "Target Queue"
2240
+ }))
2241
+ }).pipe(Schema3.mutable);
2242
+ }
2243
+ return base.pipe(Schema3.mutable);
2244
+ }, [
2245
+ mode
2246
+ ]);
2247
+ const viewValues = useMemo8(() => ({
2248
+ query: mode === "schema" ? getTypenameFromQuery(view.query.ast) : view.query.ast,
2249
+ target: queueTarget
2250
+ }), [
2251
+ mode,
2252
+ view.query.ast,
2253
+ queueTarget
2254
+ ]);
2255
+ const fieldMap = useMemo8(() => mode === "tag" ? customFields({
2256
+ types,
2257
+ tags
2258
+ }) : void 0, [
2259
+ mode,
2260
+ types,
2261
+ tags
2262
+ ]);
2263
+ const handleAdd = useCallback15(() => {
2264
+ invariant(!readonly, void 0, {
2265
+ F: __dxlog_file3,
2266
+ L: 148,
2267
+ S: void 0,
2268
+ A: [
2269
+ "!readonly",
2270
+ ""
2271
+ ]
2272
+ });
2273
+ const field = projectionModel.createFieldProjection();
2274
+ setExpandedField(field.id);
2275
+ }, [
2276
+ projectionModel,
2277
+ readonly
2278
+ ]);
2279
+ const handleUpdate = useCallback15((values) => {
2280
+ requestAnimationFrame(() => {
2281
+ const query = mode === "schema" ? Query.select(Filter2.typename(values.query)).ast : values.query;
2282
+ onQueryChanged?.(query, values.target);
2283
+ });
2284
+ }, [
2285
+ onQueryChanged,
2286
+ view,
2287
+ queueTarget,
2288
+ mode
2289
+ ]);
2290
+ const handleDelete = useCallback15((fieldId) => {
2291
+ invariant(!readonly, void 0, {
2292
+ F: __dxlog_file3,
2293
+ L: 165,
2294
+ S: void 0,
2295
+ A: [
2296
+ "!readonly",
2297
+ ""
2298
+ ]
2299
+ });
2300
+ if (fieldId === expandedField) {
2301
+ setExpandedField(void 0);
2302
+ }
2303
+ onDelete?.(fieldId);
2304
+ }, [
2305
+ expandedField,
2306
+ onDelete,
2307
+ readonly
2308
+ ]);
2309
+ return /* @__PURE__ */ React19.createElement("div", {
2310
+ role: "none",
2311
+ className: mx7(classNames)
2312
+ }, schemaReadonly && !readonly && /* @__PURE__ */ React19.createElement(Callout.Root, {
2313
+ valence: "info",
2314
+ classNames: "mlb-cardSpacingBlock"
2315
+ }, /* @__PURE__ */ React19.createElement(Callout.Title, null, t("system schema description"))), /* @__PURE__ */ React19.createElement(Form.Root, {
2316
+ schema: viewSchema,
2317
+ values: viewValues,
2318
+ fieldMap,
2319
+ autoSave: true,
2320
+ onSave: handleUpdate
2321
+ }, /* @__PURE__ */ React19.createElement(Form.FieldSet, null), /* @__PURE__ */ React19.createElement(FormFieldLabel, {
2322
+ label: t("fields label"),
2323
+ asChild: true
2324
+ }), /* @__PURE__ */ React19.createElement(FieldList, {
2325
+ schema,
2326
+ view,
2327
+ registry,
2328
+ readonly,
2329
+ showHeading,
2330
+ onDelete: handleDelete
2331
+ })), !readonly && !expandedField && /* @__PURE__ */ React19.createElement("div", {
2332
+ role: "none",
2333
+ className: "mlb-cardSpacingBlock"
2334
+ }, /* @__PURE__ */ React19.createElement(IconButton4, {
2335
+ icon: "ph--plus--regular",
2336
+ label: t("add property button label"),
2337
+ onClick: readonly ? void 0 : handleAdd,
2338
+ // TODO(burdon): Show field limit in ux (not tooltip).
2339
+ disabled: view.projection.fields.length >= VIEW_FIELD_LIMIT,
2340
+ classNames: "is-full"
2341
+ })));
2342
+ });
2343
+ var FieldList = ({ schema, view, registry, readonly, showHeading = false, onDelete }) => {
2344
+ const atomRegistry = useContext(RegistryContext);
2345
+ const schemaReadonly = !isMutable(schema);
2346
+ const { t } = useTranslation10(translationKey);
2347
+ const projectionModel = useMemo8(() => {
2348
+ const jsonSchema = schema instanceof EchoSchema ? schema.jsonSchema : toJsonSchema(schema);
2349
+ const change = schema instanceof EchoSchema ? createEchoChangeCallback(view, schema) : createDirectChangeCallback(view.projection, jsonSchema);
2350
+ const model = new ProjectionModel({
2351
+ registry: atomRegistry,
2217
2352
  view,
2218
- onTypenameChanged,
2219
- readonly
2220
- ]);
2221
- const handleDelete = useCallback10((fieldId) => {
2222
- invariant3(!readonly, void 0, {
2223
- F: __dxlog_file5,
2224
- L: 115,
2225
- S: void 0,
2226
- A: [
2227
- "!readonly",
2228
- ""
2229
- ]
2230
- });
2231
- if (fieldId === field?.id) {
2232
- setField(void 0);
2233
- }
2234
- onDelete?.(fieldId);
2235
- }, [
2236
- schema,
2237
- field,
2238
- onDelete,
2239
- readonly
2240
- ]);
2241
- const handleMove = useCallback10((fromIndex, toIndex) => {
2242
- invariant3(!readonly, void 0, {
2243
- F: __dxlog_file5,
2244
- L: 127,
2245
- S: void 0,
2246
- A: [
2247
- "!readonly",
2248
- ""
2249
- ]
2250
- });
2251
- const fields = [
2252
- ...view.fields
2253
- ];
2254
- const [moved] = fields.splice(fromIndex, 1);
2255
- fields.splice(toIndex, 0, moved);
2256
- view.fields = fields;
2257
- }, [
2258
- schema,
2259
- view.fields,
2260
- readonly
2261
- ]);
2262
- const handleClose = useCallback10(() => setField(void 0), []);
2263
- const hiddenProperties = projection.getHiddenProperties();
2264
- const handleHide = useCallback10((fieldId) => {
2265
- setField(void 0);
2266
- projection.hideFieldProjection(fieldId);
2267
- }, [
2268
- projection
2269
- ]);
2270
- const handleShow = useCallback10((property) => {
2271
- setField(void 0);
2272
- projection.showFieldProjection(property);
2273
- }, [
2274
- projection
2275
- ]);
2276
- return /* @__PURE__ */ React15.createElement("div", {
2277
- role: "none",
2278
- className: mx9(classNames)
2279
- }, readonly && /* @__PURE__ */ React15.createElement("div", {
2353
+ baseSchema: jsonSchema,
2354
+ change
2355
+ });
2356
+ return model;
2357
+ }, [
2358
+ atomRegistry,
2359
+ schema,
2360
+ view
2361
+ ]);
2362
+ const [expandedField, setExpandedField] = useState6();
2363
+ const handleToggleField = useCallback15((field) => {
2364
+ setExpandedField((prevExpandedFieldId) => prevExpandedFieldId === field.id ? void 0 : field.id);
2365
+ }, [
2366
+ readonly
2367
+ ]);
2368
+ const handleDelete = useCallback15((fieldId) => {
2369
+ invariant(!readonly, void 0, {
2370
+ F: __dxlog_file3,
2371
+ L: 254,
2372
+ S: void 0,
2373
+ A: [
2374
+ "!readonly",
2375
+ ""
2376
+ ]
2377
+ });
2378
+ if (fieldId === expandedField) {
2379
+ setExpandedField(void 0);
2380
+ }
2381
+ onDelete?.(fieldId);
2382
+ }, [
2383
+ expandedField,
2384
+ onDelete,
2385
+ readonly
2386
+ ]);
2387
+ const handleMove = useCallback15((fromIndex, toIndex) => {
2388
+ invariant(!readonly, void 0, {
2389
+ F: __dxlog_file3,
2390
+ L: 266,
2391
+ S: void 0,
2392
+ A: [
2393
+ "!readonly",
2394
+ ""
2395
+ ]
2396
+ });
2397
+ const fields = [
2398
+ ...view.projection.fields
2399
+ ];
2400
+ const [moved] = fields.splice(fromIndex, 1);
2401
+ fields.splice(toIndex, 0, moved);
2402
+ Obj2.change(view, (v) => {
2403
+ v.projection.fields = fields;
2404
+ });
2405
+ }, [
2406
+ view.projection.fields,
2407
+ readonly
2408
+ ]);
2409
+ const handleClose = useCallback15(() => setExpandedField(void 0), []);
2410
+ const handleHide = useCallback15((fieldId) => {
2411
+ setExpandedField(void 0);
2412
+ projectionModel.hideFieldProjection(fieldId);
2413
+ }, [
2414
+ projectionModel
2415
+ ]);
2416
+ const handleShow = useCallback15((property) => {
2417
+ setExpandedField(void 0);
2418
+ projectionModel.showFieldProjection(property);
2419
+ }, [
2420
+ projectionModel
2421
+ ]);
2422
+ return /* @__PURE__ */ React19.createElement(List2.Root, {
2423
+ items: view.projection.fields,
2424
+ isItem: Schema3.is(FieldSchema),
2425
+ getId: (field) => field.id,
2426
+ onMove: readonly ? void 0 : handleMove,
2427
+ readonly
2428
+ }, ({ items: fields }) => /* @__PURE__ */ React19.createElement(React19.Fragment, null, showHeading && /* @__PURE__ */ React19.createElement("h3", {
2429
+ className: "text-sm"
2430
+ }, t("field path label")), /* @__PURE__ */ React19.createElement("div", {
2431
+ role: "list",
2432
+ className: listGrid
2433
+ }, fields?.map((field) => {
2434
+ const hidden = field.visible === false;
2435
+ return /* @__PURE__ */ React19.createElement(List2.Item, {
2436
+ key: field.id,
2437
+ item: field,
2438
+ classNames: listItemGrid,
2439
+ "aria-expanded": expandedField === field.id
2440
+ }, /* @__PURE__ */ React19.createElement("div", {
2280
2441
  role: "none",
2281
- className: mx9("is-full", outerSpacing && "plb-cardSpacingBlock pli-cardSpacingInline")
2282
- }, /* @__PURE__ */ React15.createElement(Callout.Root, {
2283
- valence: "info"
2284
- }, /* @__PURE__ */ React15.createElement(Callout.Title, null, t("system schema description")))), /* @__PURE__ */ React15.createElement(Form, {
2285
- autoSave: true,
2286
- schema: ViewMetaSchema,
2287
- values: viewValues,
2288
- readonly,
2289
- onSave: handleUpdate,
2290
- outerSpacing
2291
- }), /* @__PURE__ */ React15.createElement("div", {
2442
+ className: mx7(subtleHover2, listItemGrid, "rounded-sm cursor-pointer min-bs-10")
2443
+ }, /* @__PURE__ */ React19.createElement(List2.ItemDragHandle, {
2444
+ disabled: readonly || schemaReadonly
2445
+ }), /* @__PURE__ */ React19.createElement(List2.ItemTitle, {
2446
+ classNames: hidden && "text-subdued",
2447
+ onClick: () => handleToggleField(field)
2448
+ }, field.path), /* @__PURE__ */ React19.createElement(List2.ItemButton, {
2449
+ label: t(hidden ? "show field label" : "hide field label"),
2450
+ "data-testid": hidden ? "show-field-button" : "hide-field-button",
2451
+ icon: hidden ? "ph--eye-closed--regular" : "ph--eye--regular",
2452
+ autoHide: false,
2453
+ disabled: readonly || !hidden && projectionModel.getFields().length <= 1,
2454
+ onClick: () => hidden ? handleShow(field.path) : handleHide(field.id)
2455
+ }), !readonly && /* @__PURE__ */ React19.createElement(React19.Fragment, null, /* @__PURE__ */ React19.createElement(List2.ItemDeleteButton, {
2456
+ label: t("delete field label"),
2457
+ autoHide: false,
2458
+ disabled: readonly || schemaReadonly || view.projection.fields.length <= 1,
2459
+ onClick: () => handleDelete(field.id),
2460
+ "data-testid": "field.delete"
2461
+ }), /* @__PURE__ */ React19.createElement(IconButton4, {
2462
+ iconOnly: true,
2463
+ variant: "ghost",
2464
+ label: t("toggle expand label", {
2465
+ ns: osTranslations2
2466
+ }),
2467
+ icon: expandedField === field.id ? "ph--caret-down--regular" : "ph--caret-right--regular",
2468
+ onClick: () => handleToggleField(field),
2469
+ "data-testid": "field.toggle"
2470
+ }))), expandedField === field.id && !readonly && /* @__PURE__ */ React19.createElement("div", {
2292
2471
  role: "none",
2293
- className: outerSpacing ? cardSpacing3 : "mlb-cardSpacingBlock"
2294
- }, /* @__PURE__ */ React15.createElement("label", {
2295
- className: mx9(inputTextLabel)
2296
- }, t("fields label")), /* @__PURE__ */ React15.createElement(List2.Root, {
2297
- items: view.fields,
2298
- isItem: Schema2.is(FieldSchema),
2299
- getId: (field2) => field2.id,
2300
- readonly,
2301
- onMove: readonly ? void 0 : handleMove
2302
- }, ({ items: fields }) => /* @__PURE__ */ React15.createElement(React15.Fragment, null, showHeading && /* @__PURE__ */ React15.createElement("div", {
2303
- role: "heading",
2304
- className: grid
2305
- }, /* @__PURE__ */ React15.createElement("div", null), /* @__PURE__ */ React15.createElement("div", {
2306
- className: "flex items-center text-sm"
2307
- }, t("field path label"))), /* @__PURE__ */ React15.createElement("div", {
2308
- role: "list",
2309
- className: "flex flex-col is-full"
2310
- }, fields?.map((field2) => /* @__PURE__ */ React15.createElement(List2.Item, {
2311
- key: field2.id,
2312
- item: field2,
2313
- classNames: mx9(grid, ghostHover, "overflow-hidden", !readonly && "cursor-pointer")
2314
- }, /* @__PURE__ */ React15.createElement(List2.ItemDragHandle, null), /* @__PURE__ */ React15.createElement(List2.ItemTitle, {
2315
- onClick: () => handleSelect(field2)
2316
- }, field2.path), /* @__PURE__ */ React15.createElement(List2.ItemButton, {
2317
- "data-testid": "hide-field-button",
2318
- icon: "ph--eye-slash--regular",
2319
- // TDOO(burdon): Is this the correct test?
2320
- disabled: view.fields.length <= 1,
2321
- onClick: () => handleHide(field2.id)
2322
- }), !readonly && /* @__PURE__ */ React15.createElement(List2.ItemDeleteButton, {
2323
- disabled: view.fields.length <= 1,
2324
- onClick: () => handleDelete(field2.id)
2325
- })))))), hiddenProperties.length > 0 && /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement("label", {
2326
- className: mx9(inputTextLabel)
2327
- }, t("hidden fields label")), /* @__PURE__ */ React15.createElement(List2.Root, {
2328
- items: hiddenProperties,
2329
- isItem: (item) => typeof item === "string",
2330
- getId: (property) => property
2331
- }, ({ items: properties }) => /* @__PURE__ */ React15.createElement("div", {
2332
- role: "list",
2333
- className: "flex flex-col is-full"
2334
- }, properties?.map((property) => /* @__PURE__ */ React15.createElement(List2.Item, {
2335
- key: property,
2336
- item: property,
2337
- classNames: mx9(grid, ghostHover, !readonly && "cursor-pointer")
2338
- }, /* @__PURE__ */ React15.createElement(List2.ItemDragHandle, {
2339
- disabled: true
2340
- }), /* @__PURE__ */ React15.createElement(List2.ItemTitle, null, property), /* @__PURE__ */ React15.createElement(List2.ItemButton, {
2341
- icon: "ph--eye--regular",
2342
- onClick: () => handleShow(property),
2343
- "data-testid": "show-field-button"
2344
- }))))))), field && /* @__PURE__ */ React15.createElement(FieldEditor, {
2345
- key: field.id,
2346
- view,
2347
- projection,
2348
- field,
2472
+ className: "col-span-5 mbs-1 mbe-1 border border-separator rounded-md"
2473
+ }, /* @__PURE__ */ React19.createElement(FieldEditor, {
2474
+ readonly: readonly || schemaReadonly,
2349
2475
  registry,
2476
+ projection: projectionModel,
2477
+ field,
2350
2478
  onSave: handleClose
2351
- }), !readonly && !field && /* @__PURE__ */ React15.createElement("div", {
2352
- role: "none",
2353
- className: outerSpacing ? cardSpacing3 : "mlb-cardSpacingBlock"
2354
- }, /* @__PURE__ */ React15.createElement(IconButton4, {
2355
- icon: "ph--plus--regular",
2356
- label: t("button add property"),
2357
- onClick: readonly ? void 0 : handleAdd,
2358
- // TODO(burdon): Show field limit in ux (not tooltip).
2359
- disabled: view.fields.length >= VIEW_FIELD_LIMIT,
2360
- classNames: "flex is-full"
2361
2479
  })));
2362
- } finally {
2363
- _effect.f();
2364
- }
2480
+ }))));
2365
2481
  };
2482
+ var customFields = ({ types, tags }) => ({
2483
+ query: ({ type, readonly, label, getValue, onValueChange }) => {
2484
+ const handleChange = useCallback15((query) => onValueChange(type, query.ast), [
2485
+ onValueChange
2486
+ ]);
2487
+ return /* @__PURE__ */ React19.createElement(Input13.Root, null, /* @__PURE__ */ React19.createElement(FormFieldLabel, {
2488
+ readonly,
2489
+ label
2490
+ }), /* @__PURE__ */ React19.createElement(QueryForm, {
2491
+ initialQuery: getValue(),
2492
+ types,
2493
+ tags,
2494
+ onChange: handleChange
2495
+ }));
2496
+ }
2497
+ });
2366
2498
  export {
2367
- BooleanInput,
2499
+ ArrayField,
2500
+ BooleanField,
2368
2501
  ControlFrame,
2369
2502
  ControlFrameItem,
2370
2503
  ControlGroup,
@@ -2374,34 +2507,37 @@ export {
2374
2507
  ControlPage,
2375
2508
  ControlSection,
2376
2509
  ControlSectionHeading,
2377
- DeprecatedFormContainer,
2378
- DeprecatedFormInput,
2379
2510
  FieldEditor,
2380
2511
  Form,
2381
- FormProvider,
2382
- GeoPointInput,
2383
- InputHeader,
2384
- MarkdownInput,
2385
- NumberInput,
2386
- SelectInput,
2387
- SelectOptionInput,
2388
- TextInput,
2389
- TupleInput,
2512
+ FormFieldErrorBoundary,
2513
+ FormFieldLabel,
2514
+ FormFieldWrapper,
2515
+ GeoPointField,
2516
+ MarkdownField,
2517
+ NumberField,
2518
+ ObjectPicker,
2519
+ RefField,
2520
+ SelectField,
2521
+ SelectOptionField,
2522
+ TextAreaField,
2523
+ TextField,
2524
+ TupleField,
2390
2525
  ViewEditor,
2391
2526
  cellClassesForFieldType,
2392
2527
  controlItemClasses,
2393
- findArrayElementType,
2394
2528
  formatForDisplay,
2395
2529
  formatForEditing,
2530
+ getDefaultValue,
2531
+ getFormProperties,
2396
2532
  getRefProps,
2533
+ omitId,
2397
2534
  parseValue,
2398
2535
  pathNotUniqueError,
2399
2536
  translations,
2400
- useForm,
2401
2537
  useFormContext,
2538
+ useFormFieldState,
2539
+ useFormHandler,
2402
2540
  useFormValues,
2403
- useInputProps,
2404
- useQueryRefOptions,
2405
- useRefQueryLookupHandler
2541
+ useKeyHandler
2406
2542
  };
2407
2543
  //# sourceMappingURL=index.mjs.map