@dxos/react-ui-form 0.7.5-labs.8a82073 → 0.7.5-labs.a8b535d

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 (64) hide show
  1. package/dist/lib/browser/index.mjs +542 -359
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node/index.cjs +585 -398
  5. package/dist/lib/node/index.cjs.map +4 -4
  6. package/dist/lib/node/meta.json +1 -1
  7. package/dist/lib/node-esm/index.mjs +542 -359
  8. package/dist/lib/node-esm/index.mjs.map +4 -4
  9. package/dist/lib/node-esm/meta.json +1 -1
  10. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +1 -2
  11. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  12. package/dist/types/src/components/Form/ArrayField.d.ts +1 -2
  13. package/dist/types/src/components/Form/ArrayField.d.ts.map +1 -1
  14. package/dist/types/src/components/Form/Defaults.d.ts +5 -5
  15. package/dist/types/src/components/Form/Defaults.d.ts.map +1 -1
  16. package/dist/types/src/components/Form/Form.d.ts +2 -2
  17. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  18. package/dist/types/src/components/Form/FormActions.d.ts +1 -2
  19. package/dist/types/src/components/Form/FormActions.d.ts.map +1 -1
  20. package/dist/types/src/components/Form/FormContent.d.ts +2 -3
  21. package/dist/types/src/components/Form/FormContent.d.ts.map +1 -1
  22. package/dist/types/src/components/Form/FormContext.d.ts +4 -1
  23. package/dist/types/src/components/Form/FormContext.d.ts.map +1 -1
  24. package/dist/types/src/components/Form/Input.d.ts +2 -2
  25. package/dist/types/src/components/Form/Input.d.ts.map +1 -1
  26. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +1 -2
  27. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +1 -1
  28. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +1 -2
  29. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +1 -1
  30. package/dist/types/src/components/Form/custom/TupleInput.d.ts +1 -2
  31. package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +1 -1
  32. package/dist/types/src/components/Form/factory.d.ts.map +1 -1
  33. package/dist/types/src/components/Form/index.d.ts +2 -0
  34. package/dist/types/src/components/Form/index.d.ts.map +1 -1
  35. package/dist/types/src/components/ViewEditor/Test.stories.d.ts +1 -2
  36. package/dist/types/src/components/ViewEditor/Test.stories.d.ts.map +1 -1
  37. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +1 -2
  38. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  39. package/dist/types/src/components/deprecated/Form.d.ts +1 -2
  40. package/dist/types/src/components/deprecated/Form.d.ts.map +1 -1
  41. package/dist/types/src/components/deprecated/Form.stories.d.ts +1 -2
  42. package/dist/types/src/components/deprecated/Form.stories.d.ts.map +1 -1
  43. package/dist/types/src/components/deprecated/FormInput.d.ts +2 -2
  44. package/dist/types/src/components/deprecated/FormInput.d.ts.map +1 -1
  45. package/dist/types/src/components/testing.d.ts +3 -3
  46. package/dist/types/src/components/testing.d.ts.map +1 -1
  47. package/dist/types/src/hooks/useForm.d.ts +1 -0
  48. package/dist/types/src/hooks/useForm.d.ts.map +1 -1
  49. package/dist/types/src/util/field.d.ts.map +1 -1
  50. package/dist/types/src/util/formatting.d.ts.map +1 -1
  51. package/package.json +25 -24
  52. package/src/components/Form/Defaults.tsx +59 -3
  53. package/src/components/Form/Form.tsx +8 -5
  54. package/src/components/Form/FormContent.tsx +9 -12
  55. package/src/components/Form/FormContext.tsx +58 -10
  56. package/src/components/Form/custom/GeoPointInput.tsx +42 -12
  57. package/src/components/Form/custom/SelectOptionsInput.tsx +7 -9
  58. package/src/components/Form/factory.tsx +4 -2
  59. package/src/components/Form/index.ts +3 -0
  60. package/src/components/ViewEditor/ViewEditor.tsx +2 -2
  61. package/src/hooks/useForm.ts +69 -40
  62. package/src/translations.ts +1 -1
  63. package/src/util/field.ts +19 -1
  64. package/src/util/formatting.ts +29 -1
@@ -29,7 +29,7 @@ var translations_default = [
29
29
  "format hostname": "Hostname",
30
30
  "format integer": "Integer",
31
31
  "format json": "JSON",
32
- "format latlng": "LatLng",
32
+ "format latlng": "Geopoint",
33
33
  "format markdown": "Markdown",
34
34
  "format percent": "Percent",
35
35
  "format ref": "Reference",
@@ -99,85 +99,394 @@ var DeprecatedFormInput = ({ label, description, secondary, children }) => {
99
99
  };
100
100
 
101
101
  // packages/ui/react-ui-form/src/components/FieldEditor/FieldEditor.tsx
102
- import React12, { useCallback as useCallback4, useEffect as useEffect4, useMemo as useMemo5, useState as useState3 } from "react";
102
+ import React12, { useCallback as useCallback6, useEffect as useEffect7, useMemo as useMemo7, useState as useState4 } from "react";
103
103
  import { FormatEnum as FormatEnum2, FormatEnums, formatToType } from "@dxos/echo-schema";
104
104
  import { log as log2 } from "@dxos/log";
105
105
  import { useTranslation as useTranslation5 } from "@dxos/react-ui";
106
106
  import { getFormatSchema, getSchemaProperties as getSchemaProperties3, sortProperties } from "@dxos/schema";
107
107
 
108
+ // packages/ui/react-ui-form/src/components/Form/FormContext.tsx
109
+ import React2, { createContext, useContext, useEffect as useEffect2, useMemo as useMemo2 } from "react";
110
+ import { raise } from "@dxos/debug";
111
+ import { getValue as getValue2 } from "@dxos/echo-schema";
112
+ import { createJsonPath as createJsonPath2 } from "@dxos/effect";
113
+
114
+ // packages/ui/react-ui-form/src/hooks/useForm.ts
115
+ import { useCallback, useEffect, useMemo, useState } from "react";
116
+ import { getValue, setValue } from "@dxos/echo-schema";
117
+ import { AST, createJsonPath, fromEffectValidationPath } from "@dxos/effect";
118
+ import { invariant } from "@dxos/invariant";
119
+ import { log } from "@dxos/log";
120
+ import { validateSchema } from "@dxos/schema";
121
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-form/src/hooks/useForm.ts";
122
+ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, onSave }) => {
123
+ invariant(AST.isTypeLiteral(schema.ast), void 0, {
124
+ F: __dxlog_file,
125
+ L: 92,
126
+ S: void 0,
127
+ A: [
128
+ "AST.isTypeLiteral(schema.ast)",
129
+ ""
130
+ ]
131
+ });
132
+ const [values, setValues] = useState(initialValues);
133
+ useEffect(() => {
134
+ setValues(initialValues);
135
+ }, [
136
+ initialValues
137
+ ]);
138
+ const [touched, setTouched] = useState(createKeySet(initialValues, false));
139
+ const [changed, setChanged] = useState(createKeySet(initialValues, false));
140
+ const [errors, setErrors] = useState({});
141
+ const [saving, setSaving] = useState(false);
142
+ const validate = useCallback((values2) => {
143
+ let errors2 = validateSchema(schema, values2) ?? [];
144
+ if (errors2.length === 0 && onValidate) {
145
+ const validatedValues = values2;
146
+ errors2 = onValidate(validatedValues) ?? [];
147
+ }
148
+ setErrors(flatMap(errors2));
149
+ const valid = errors2.length === 0;
150
+ return valid;
151
+ }, [
152
+ schema,
153
+ onValidate
154
+ ]);
155
+ const [schemaDirty, setSchemaDirty] = useState(false);
156
+ useEffect(() => setSchemaDirty(true), [
157
+ schema
158
+ ]);
159
+ useEffect(() => {
160
+ if (schemaDirty) {
161
+ validate(values);
162
+ setSchemaDirty(false);
163
+ }
164
+ }, [
165
+ values,
166
+ schemaDirty
167
+ ]);
168
+ const formIsValid = useMemo(() => {
169
+ return Object.keys(errors).length === 0;
170
+ }, [
171
+ errors
172
+ ]);
173
+ const canSave = useMemo(() => !saving && // Check if any error paths that are touched have errors.
174
+ !Object.entries(touched).some(([path, isTouched]) => isTouched && Object.keys(errors).some((errorPath) => errorPath === path || errorPath.startsWith(`${path}.`) || errorPath.startsWith(`${path}[`))), [
175
+ touched,
176
+ errors,
177
+ saving
178
+ ]);
179
+ const handleSave = useCallback(async () => {
180
+ if (validate(values)) {
181
+ setSaving(true);
182
+ try {
183
+ await onSave?.(values, {
184
+ changed
185
+ });
186
+ } finally {
187
+ setSaving(false);
188
+ }
189
+ }
190
+ }, [
191
+ values,
192
+ validate,
193
+ onSave,
194
+ changed
195
+ ]);
196
+ const getStatus = useCallback((path) => {
197
+ const jsonPath = Array.isArray(path) ? createJsonPath(path) : path;
198
+ const matchingError = Object.entries(errors).find(([errorPath]) => errorPath === jsonPath || errorPath.startsWith(`${jsonPath}.`) || errorPath.startsWith(`${jsonPath}[`));
199
+ return {
200
+ status: matchingError ? "error" : void 0,
201
+ error: matchingError ? matchingError[1] : void 0
202
+ };
203
+ }, [
204
+ errors
205
+ ]);
206
+ const getFormValue = useCallback((path) => {
207
+ return getValue(values, createJsonPath(path));
208
+ }, [
209
+ values
210
+ ]);
211
+ const onValueChange = useCallback((path, type, value) => {
212
+ const jsonPath = createJsonPath(path);
213
+ let parsedValue = value;
214
+ try {
215
+ if (type === "number") {
216
+ parsedValue = parseFloat(value) || 0;
217
+ }
218
+ } catch (err) {
219
+ log.catch(err, void 0, {
220
+ F: __dxlog_file,
221
+ L: 202,
222
+ S: void 0,
223
+ C: (f, a) => f(...a)
224
+ });
225
+ parsedValue = void 0;
226
+ }
227
+ const newValues = {
228
+ ...setValue(values, jsonPath, parsedValue)
229
+ };
230
+ setValues(newValues);
231
+ setChanged((prev) => ({
232
+ ...prev,
233
+ [jsonPath]: true
234
+ }));
235
+ onValuesChanged?.(newValues);
236
+ const isValid = validate(newValues);
237
+ if (isValid && onValid) {
238
+ onValid(newValues, {
239
+ changed
240
+ });
241
+ }
242
+ }, [
243
+ values,
244
+ onValuesChanged,
245
+ validate,
246
+ onValid,
247
+ changed
248
+ ]);
249
+ const onTouched = useCallback((path) => {
250
+ const jsonPath = createJsonPath(path);
251
+ setTouched((touched2) => ({
252
+ ...touched2,
253
+ [jsonPath]: true
254
+ }));
255
+ validate(values);
256
+ }, [
257
+ validate,
258
+ values
259
+ ]);
260
+ return useMemo(() => ({
261
+ // State.
262
+ values,
263
+ errors,
264
+ touched,
265
+ changed,
266
+ canSave,
267
+ formIsValid,
268
+ // Actions.
269
+ handleSave,
270
+ // Field utils.
271
+ getStatus,
272
+ getValue: getFormValue,
273
+ onValueChange,
274
+ onTouched
275
+ }), [
276
+ values,
277
+ errors,
278
+ touched,
279
+ changed,
280
+ canSave,
281
+ formIsValid,
282
+ handleSave,
283
+ getStatus,
284
+ getFormValue,
285
+ onValueChange,
286
+ onTouched
287
+ ]);
288
+ };
289
+ var createKeySet = (obj, value) => {
290
+ invariant(obj, void 0, {
291
+ F: __dxlog_file,
292
+ L: 264,
293
+ S: void 0,
294
+ A: [
295
+ "obj",
296
+ ""
297
+ ]
298
+ });
299
+ return Object.keys(obj).reduce((acc, key) => ({
300
+ ...acc,
301
+ [key]: value
302
+ }), {});
303
+ };
304
+ var flatMap = (errors) => {
305
+ return errors.reduce((result, { path, message }) => {
306
+ const jsonPath = fromEffectValidationPath(path);
307
+ if (!(jsonPath in result)) {
308
+ result[jsonPath] = message;
309
+ }
310
+ return result;
311
+ }, {});
312
+ };
313
+
314
+ // packages/ui/react-ui-form/src/components/Form/FormContext.tsx
315
+ var FormContext = /* @__PURE__ */ createContext(void 0);
316
+ var useFormContext = () => {
317
+ return useContext(FormContext) ?? raise(new Error("Missing FormContext"));
318
+ };
319
+ var useFormValues = (path = []) => {
320
+ const { values: formValues } = useFormContext();
321
+ const jsonPath = createJsonPath2(path);
322
+ return getValue2(formValues, jsonPath);
323
+ };
324
+ var useInputProps = (path = []) => {
325
+ const { getStatus, getValue: getFormValue, onValueChange, onTouched } = useFormContext();
326
+ const stablePath = useMemo2(() => path, [
327
+ Array.isArray(path) ? path.join(".") : path
328
+ ]);
329
+ return useMemo2(() => ({
330
+ getStatus: () => getStatus(stablePath),
331
+ getValue: () => getFormValue(stablePath),
332
+ onValueChange: (type, value) => onValueChange(stablePath, type, value),
333
+ onBlur: () => onTouched(stablePath)
334
+ }), [
335
+ getStatus,
336
+ getFormValue,
337
+ onValueChange,
338
+ onTouched,
339
+ stablePath
340
+ ]);
341
+ };
342
+ var FormProvider = ({ children, formRef, autoSave, ...formOptions }) => {
343
+ const form = useForm(formOptions);
344
+ useEffect2(() => {
345
+ if (!formRef?.current) {
346
+ return;
347
+ }
348
+ const handleKeyDown = (event) => {
349
+ const keyIsEnter = event.key === "Enter";
350
+ const modifierUsed = event.ctrlKey || event.altKey || event.metaKey || event.shiftKey;
351
+ const inputIsTextarea = event.target.tagName.toLowerCase() === "textarea";
352
+ const inputOptOut = event.target.hasAttribute("data-no-submit");
353
+ const shouldSubmitRegularInput = !inputIsTextarea && keyIsEnter && !modifierUsed;
354
+ const shouldSubmitTextarea = inputIsTextarea && keyIsEnter && event.metaKey;
355
+ if ((shouldSubmitRegularInput || shouldSubmitTextarea) && !inputOptOut) {
356
+ if (!autoSave && form.canSave) {
357
+ form.handleSave();
358
+ }
359
+ if (autoSave && form.formIsValid) {
360
+ event.target.blur();
361
+ }
362
+ }
363
+ };
364
+ const formElement = formRef.current;
365
+ formElement.addEventListener("keydown", handleKeyDown);
366
+ return () => formElement.removeEventListener("keydown", handleKeyDown);
367
+ }, [
368
+ formRef,
369
+ form.canSave,
370
+ form.formIsValid,
371
+ form.handleSave,
372
+ autoSave
373
+ ]);
374
+ return /* @__PURE__ */ React2.createElement(FormContext.Provider, {
375
+ value: form
376
+ }, children);
377
+ };
378
+
108
379
  // packages/ui/react-ui-form/src/components/Form/custom/GeoPointInput.tsx
109
- import React3 from "react";
380
+ import React4, { useCallback as useCallback2, useState as useState2, useEffect as useEffect3, useMemo as useMemo3 } from "react";
381
+ import { GeoLocation } from "@dxos/echo-schema";
110
382
  import { Input as Input2, useTranslation } from "@dxos/react-ui";
111
383
  import { safeParseFloat } from "@dxos/util";
112
384
 
113
385
  // packages/ui/react-ui-form/src/components/Form/Input.tsx
114
- import React2 from "react";
386
+ import React3 from "react";
115
387
  import { Icon, Tooltip } from "@dxos/react-ui";
116
388
  import { errorText } from "@dxos/react-ui-theme";
117
389
  var InputHeader = ({ children, error }) => {
118
- return /* @__PURE__ */ React2.createElement("div", {
390
+ return /* @__PURE__ */ React3.createElement("div", {
119
391
  role: "none",
120
392
  className: "flex justify-between items-center mb-1"
121
- }, children, error && /* @__PURE__ */ React2.createElement(Tooltip.Root, null, /* @__PURE__ */ React2.createElement(Tooltip.Trigger, {
393
+ }, children, error && /* @__PURE__ */ React3.createElement(Tooltip.Root, null, /* @__PURE__ */ React3.createElement(Tooltip.Trigger, {
122
394
  asChild: true
123
- }, /* @__PURE__ */ React2.createElement(Icon, {
395
+ }, /* @__PURE__ */ React3.createElement(Icon, {
124
396
  icon: "ph--warning--regular",
125
397
  size: 4,
126
398
  classNames: errorText
127
- })), /* @__PURE__ */ React2.createElement(Tooltip.Content, {
399
+ })), /* @__PURE__ */ React3.createElement(Tooltip.Content, {
128
400
  side: "bottom"
129
- }, /* @__PURE__ */ React2.createElement(Tooltip.Arrow, null), error)));
401
+ }, /* @__PURE__ */ React3.createElement(Tooltip.Arrow, null), error)));
130
402
  };
131
403
 
132
404
  // packages/ui/react-ui-form/src/components/Form/custom/GeoPointInput.tsx
133
405
  var GeoPointInput = ({ type, label, disabled, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
134
406
  const { t } = useTranslation(translationKey);
135
407
  const { status, error } = getStatus();
136
- const [lng = 0, lat = 0] = getValue3() ?? [];
137
- return /* @__PURE__ */ React3.createElement(Input2.Root, {
408
+ const geoPoint = useMemo3(() => getValue3() ?? [
409
+ 0,
410
+ 0
411
+ ], [
412
+ getValue3
413
+ ]);
414
+ const geoLocation = useMemo3(() => GeoLocation.fromGeoPoint(geoPoint), [
415
+ geoPoint
416
+ ]);
417
+ const [longitudeText, setLongitudeText] = useState2(geoLocation.longitude?.toString());
418
+ const [latitudeText, setLatitudeText] = useState2(geoLocation.latitude?.toString());
419
+ useEffect3(() => {
420
+ const location = GeoLocation.fromGeoPoint(geoPoint);
421
+ setLongitudeText(location.longitude.toString());
422
+ setLatitudeText(location.latitude.toString());
423
+ }, [
424
+ geoPoint
425
+ ]);
426
+ const handleCoordinateChange = useCallback2((coordinateType, setText) => (event) => {
427
+ const inputText = event.target.value;
428
+ setText(inputText);
429
+ if (inputText !== "" && inputText !== "-") {
430
+ const coord = safeParseFloat(inputText);
431
+ if (coord !== void 0 && !isNaN(coord)) {
432
+ const currentLocation = GeoLocation.fromGeoPoint(getValue3() ?? [
433
+ 0,
434
+ 0
435
+ ]);
436
+ const newLocation = {
437
+ ...currentLocation,
438
+ [coordinateType]: coord
439
+ };
440
+ const newValue = GeoLocation.toGeoPoint(newLocation);
441
+ onValueChange(type, newValue);
442
+ }
443
+ }
444
+ }, [
445
+ getValue3,
446
+ onValueChange,
447
+ type
448
+ ]);
449
+ return /* @__PURE__ */ React4.createElement(Input2.Root, {
138
450
  validationValence: status
139
- }, /* @__PURE__ */ React3.createElement(InputHeader, {
451
+ }, /* @__PURE__ */ React4.createElement(InputHeader, {
140
452
  error
141
- }, /* @__PURE__ */ React3.createElement(Input2.Label, null, label)), /* @__PURE__ */ React3.createElement("div", {
453
+ }, /* @__PURE__ */ React4.createElement(Input2.Label, null, label)), /* @__PURE__ */ React4.createElement("div", {
454
+ role: "none",
142
455
  className: "grid grid-cols-2 gap-2"
143
- }, /* @__PURE__ */ React3.createElement(Input2.TextInput, {
144
- type: "number",
456
+ }, /* @__PURE__ */ React4.createElement(Input2.TextInput, {
457
+ type: "text",
458
+ pattern: "^-?[0-9]*\\.?[0-9]*$",
145
459
  disabled,
146
- placeholder: t("placeholder longitude"),
147
- value: lng,
148
- onChange: (event) => onValueChange(type, [
149
- lat,
150
- safeParseFloat(event.target.value, 0)
151
- ]),
460
+ placeholder: t("placeholder latitude"),
461
+ value: latitudeText,
462
+ onChange: handleCoordinateChange("latitude", setLatitudeText),
152
463
  onBlur
153
- }), /* @__PURE__ */ React3.createElement(Input2.TextInput, {
154
- type: "number",
464
+ }), /* @__PURE__ */ React4.createElement(Input2.TextInput, {
465
+ type: "text",
466
+ pattern: "^-?[0-9]*\\.?[0-9]*$",
155
467
  disabled,
156
- placeholder: t("placeholder latitude"),
157
- value: lat,
158
- onChange: (event) => onValueChange(type, [
159
- safeParseFloat(event.target.value, 0),
160
- lng
161
- ]),
468
+ placeholder: t("placeholder longitude"),
469
+ value: longitudeText,
470
+ onChange: handleCoordinateChange("longitude", setLongitudeText),
162
471
  onBlur
163
472
  })));
164
473
  };
165
474
 
166
475
  // packages/ui/react-ui-form/src/components/Form/custom/SelectOptionsInput.tsx
167
- import React4, { useCallback, useEffect, useState, useRef } from "react";
476
+ import React5, { useCallback as useCallback3, useEffect as useEffect4, useState as useState3, useRef } from "react";
168
477
  import { PublicKey } from "@dxos/keys";
169
478
  import { Icon as Icon2, IconButton, Input as Input3, Tag, Toolbar, useTranslation as useTranslation2 } from "@dxos/react-ui";
170
479
  import { List } from "@dxos/react-ui-list";
171
- import { HuePickerToolbarButton } from "@dxos/react-ui-pickers";
172
- import { hueTokenThemes, mx as mx2 } from "@dxos/react-ui-theme";
480
+ import { HuePicker } from "@dxos/react-ui-pickers";
481
+ import { hues, mx as mx2 } from "@dxos/react-ui-theme";
173
482
  var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3, onValueChange }) => {
174
- const [selected, setSelectedId] = useState(null);
175
- const [isNewOption, setIsNewOption] = useState(false);
483
+ const [selected, setSelectedId] = useState3(null);
484
+ const [isNewOption, setIsNewOption] = useState3(false);
176
485
  const inputRef = useRef(null);
177
486
  const { t } = useTranslation2(translationKey);
178
487
  const { status, error } = getStatus();
179
488
  const options = getValue3();
180
- useEffect(() => {
489
+ useEffect4(() => {
181
490
  if (options === void 0) {
182
491
  onValueChange(type, []);
183
492
  }
@@ -188,16 +497,15 @@ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3
188
497
  ]);
189
498
  const randomHue = () => {
190
499
  const usedHues = new Set(options?.map((option) => option.color) ?? []);
191
- const hueKeys = Object.keys(hueTokenThemes);
192
- const unusedHues = hueKeys.filter((hue) => !usedHues.has(hue));
500
+ const unusedHues = hues.filter((hue) => !usedHues.has(hue));
193
501
  if (unusedHues.length > 0) {
194
502
  return unusedHues[Math.floor(Math.random() * unusedHues.length)];
195
503
  }
196
504
  const lastThree = new Set(options?.slice(-3).map((option) => option.color) ?? []);
197
- const availableHues = hueKeys.filter((hue) => !lastThree.has(hue));
198
- return availableHues[Math.floor(Math.random() * availableHues.length)] ?? hueKeys[Math.floor(Math.random() * hueKeys.length)];
505
+ const availableHues = hues.filter((hue) => !lastThree.has(hue));
506
+ return availableHues[Math.floor(Math.random() * availableHues.length)] ?? hues[Math.floor(Math.random() * hues.length)];
199
507
  };
200
- const handleAdd = useCallback(() => {
508
+ const handleAdd = useCallback3(() => {
201
509
  const newId = PublicKey.random().truncate();
202
510
  const newOption = {
203
511
  id: newId,
@@ -215,7 +523,7 @@ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3
215
523
  type,
216
524
  onValueChange
217
525
  ]);
218
- const handleDelete = useCallback((id) => {
526
+ const handleDelete = useCallback3((id) => {
219
527
  const newOptions = options?.filter((option) => option.id !== id) ?? [];
220
528
  onValueChange(type, newOptions);
221
529
  setSelectedId(null);
@@ -224,7 +532,7 @@ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3
224
532
  type,
225
533
  onValueChange
226
534
  ]);
227
- const handleMove = useCallback((from, to) => {
535
+ const handleMove = useCallback3((from, to) => {
228
536
  if (!options) {
229
537
  return;
230
538
  }
@@ -239,10 +547,10 @@ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3
239
547
  type,
240
548
  onValueChange
241
549
  ]);
242
- const handleClick = useCallback((id) => {
550
+ const handleClick = useCallback3((id) => {
243
551
  setSelectedId((current) => current === id ? null : id);
244
552
  }, []);
245
- const handleTitleChange = useCallback((id) => (e) => {
553
+ const handleTitleChange = useCallback3((id) => (e) => {
246
554
  const newOptions = options?.map((o) => o.id === id ? {
247
555
  ...o,
248
556
  title: e.target.value
@@ -253,12 +561,12 @@ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3
253
561
  type,
254
562
  onValueChange
255
563
  ]);
256
- const handleKeyDown = useCallback((e) => {
564
+ const handleKeyDown = useCallback3((e) => {
257
565
  if (e.key === "Enter") {
258
566
  setSelectedId(null);
259
567
  }
260
568
  }, []);
261
- const handleColorChange = useCallback((id) => (hue) => {
569
+ const handleColorChange = useCallback3((id) => (hue) => {
262
570
  const newOptions = options?.map((o) => o.id === id ? {
263
571
  ...o,
264
572
  color: hue
@@ -269,7 +577,7 @@ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3
269
577
  type,
270
578
  onValueChange
271
579
  ]);
272
- useEffect(() => {
580
+ useEffect4(() => {
273
581
  if (selected && isNewOption && inputRef.current) {
274
582
  inputRef.current.focus();
275
583
  setIsNewOption(false);
@@ -278,76 +586,77 @@ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3
278
586
  selected,
279
587
  isNewOption
280
588
  ]);
281
- return /* @__PURE__ */ React4.createElement(Input3.Root, {
589
+ return /* @__PURE__ */ React5.createElement(Input3.Root, {
282
590
  validationValence: status
283
- }, /* @__PURE__ */ React4.createElement(InputHeader, {
591
+ }, /* @__PURE__ */ React5.createElement(InputHeader, {
284
592
  error
285
- }, /* @__PURE__ */ React4.createElement(Input3.Label, null, label)), /* @__PURE__ */ React4.createElement("div", {
593
+ }, /* @__PURE__ */ React5.createElement(Input3.Label, null, label)), /* @__PURE__ */ React5.createElement("div", {
286
594
  role: "none"
287
- }, options && /* @__PURE__ */ React4.createElement(List.Root, {
595
+ }, options && /* @__PURE__ */ React5.createElement(List.Root, {
288
596
  items: options,
289
597
  isItem: (item) => true,
290
598
  onMove: handleMove
291
- }, ({ items }) => /* @__PURE__ */ React4.createElement("div", {
599
+ }, ({ items }) => /* @__PURE__ */ React5.createElement("div", {
292
600
  role: "list",
293
601
  className: "w-full overflow-auto"
294
- }, items?.map((item) => /* @__PURE__ */ React4.createElement("div", {
602
+ }, items?.map((item) => /* @__PURE__ */ React5.createElement("div", {
295
603
  key: item.id
296
- }, /* @__PURE__ */ React4.createElement(List.Item, {
604
+ }, /* @__PURE__ */ React5.createElement(List.Item, {
297
605
  role: "button",
298
606
  item,
299
607
  classNames: mx2("p-1 plb-2", "flex flex-col", "cursor-pointer", "hover:bg-hoverOverlay"),
300
608
  "aria-expanded": selected === item.id
301
- }, /* @__PURE__ */ React4.createElement("div", {
609
+ }, /* @__PURE__ */ React5.createElement("div", {
302
610
  className: "flex items-center"
303
- }, /* @__PURE__ */ React4.createElement(List.ItemDragHandle, null), /* @__PURE__ */ React4.createElement(List.ItemTitle, {
611
+ }, /* @__PURE__ */ React5.createElement(List.ItemDragHandle, null), /* @__PURE__ */ React5.createElement(List.ItemTitle, {
304
612
  onClick: () => handleClick(item.id),
305
613
  classNames: "flex-1"
306
- }, /* @__PURE__ */ React4.createElement(Tag, {
614
+ }, /* @__PURE__ */ React5.createElement(Tag, {
307
615
  palette: item.color
308
- }, item.title || "\u200B")), /* @__PURE__ */ React4.createElement(Icon2, {
616
+ }, item.title || "\u200B")), /* @__PURE__ */ React5.createElement(Icon2, {
309
617
  icon: selected === item.id ? "ph--caret-down--regular" : "ph--caret-right--regular",
310
618
  onClick: () => handleClick(item.id)
311
- })), selected === item.id && /* @__PURE__ */ React4.createElement("div", {
619
+ })), selected === item.id && /* @__PURE__ */ React5.createElement("div", {
312
620
  className: "mis-4 mbs-2 flex flex-col gap-1"
313
- }, /* @__PURE__ */ React4.createElement(Input3.Label, {
621
+ }, /* @__PURE__ */ React5.createElement(Input3.Label, {
314
622
  classNames: "text-xs"
315
- }, t("select option label")), /* @__PURE__ */ React4.createElement("div", {
623
+ }, t("select option label")), /* @__PURE__ */ React5.createElement("div", {
316
624
  className: "flex flex-row items-center gap-1"
317
- }, /* @__PURE__ */ React4.createElement(Input3.TextInput, {
625
+ }, /* @__PURE__ */ React5.createElement(Input3.TextInput, {
318
626
  placeholder: t("select option label placeholder"),
319
627
  ref: selected === item.id ? inputRef : void 0,
320
628
  value: item.title,
321
629
  onChange: handleTitleChange(item.id),
322
630
  onKeyDown: handleKeyDown,
323
- classNames: "flex-1"
324
- }), /* @__PURE__ */ React4.createElement(Toolbar.Root, {
631
+ classNames: "flex-1",
632
+ "data-no-submit": true
633
+ }), /* @__PURE__ */ React5.createElement(Toolbar.Root, {
325
634
  classNames: "p-0 m-0 !is-auto"
326
- }, /* @__PURE__ */ React4.createElement(HuePickerToolbarButton, {
327
- hue: item.color,
328
- onChangeHue: handleColorChange(item.id)
329
- })), /* @__PURE__ */ React4.createElement(IconButton, {
635
+ }, /* @__PURE__ */ React5.createElement(HuePicker, {
636
+ value: item.color,
637
+ onChange: handleColorChange(item.id)
638
+ })), /* @__PURE__ */ React5.createElement(IconButton, {
330
639
  icon: "ph--trash--fill",
331
640
  iconOnly: true,
332
641
  label: t("select option delete"),
333
642
  onClick: () => handleDelete(item.id)
334
- })))))), /* @__PURE__ */ React4.createElement("div", {
643
+ })))))), /* @__PURE__ */ React5.createElement("div", {
335
644
  role: "listitem"
336
- }, /* @__PURE__ */ React4.createElement("div", {
645
+ }, /* @__PURE__ */ React5.createElement("div", {
337
646
  role: "button",
338
647
  className: mx2("p-1 plb-2", "flex flex-col", "cursor-pointer", "hover:bg-hoverOverlay"),
339
648
  onClick: handleAdd
340
- }, /* @__PURE__ */ React4.createElement("div", {
649
+ }, /* @__PURE__ */ React5.createElement("div", {
341
650
  className: "flex items-center gap-1"
342
- }, /* @__PURE__ */ React4.createElement(Icon2, {
651
+ }, /* @__PURE__ */ React5.createElement(Icon2, {
343
652
  icon: "ph--plus--regular"
344
- }), /* @__PURE__ */ React4.createElement("span", {
653
+ }), /* @__PURE__ */ React5.createElement("span", {
345
654
  className: "text-sm"
346
655
  }, t("select option add")))))))));
347
656
  };
348
657
 
349
658
  // packages/ui/react-ui-form/src/components/Form/custom/TupleInput.tsx
350
- import React5 from "react";
659
+ import React6 from "react";
351
660
  import { Input as Input4 } from "@dxos/react-ui";
352
661
  import { mx as mx3 } from "@dxos/react-ui-theme";
353
662
  import { safeParseFloat as safeParseFloat2 } from "@dxos/util";
@@ -360,13 +669,13 @@ var gridCols = [
360
669
  var TupleInput = ({ binding, type, label, disabled, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
361
670
  const { status, error } = getStatus();
362
671
  const values = getValue3() ?? {};
363
- return /* @__PURE__ */ React5.createElement(Input4.Root, {
672
+ return /* @__PURE__ */ React6.createElement(Input4.Root, {
364
673
  validationValence: status
365
- }, /* @__PURE__ */ React5.createElement(InputHeader, {
674
+ }, /* @__PURE__ */ React6.createElement(InputHeader, {
366
675
  error
367
- }, /* @__PURE__ */ React5.createElement(Input4.Label, null, label)), /* @__PURE__ */ React5.createElement("div", {
676
+ }, /* @__PURE__ */ React6.createElement(Input4.Label, null, label)), /* @__PURE__ */ React6.createElement("div", {
368
677
  className: mx3("grid gap-2", gridCols[binding.length - 1])
369
- }, binding.map((prop) => /* @__PURE__ */ React5.createElement(Input4.TextInput, {
678
+ }, binding.map((prop) => /* @__PURE__ */ React6.createElement(Input4.TextInput, {
370
679
  key: prop,
371
680
  type: "number",
372
681
  disabled,
@@ -380,286 +689,118 @@ var TupleInput = ({ binding, type, label, disabled, getStatus, getValue: getValu
380
689
  };
381
690
 
382
691
  // packages/ui/react-ui-form/src/components/Form/Defaults.tsx
383
- import React6 from "react";
692
+ import React7, { useCallback as useCallback4, useEffect as useEffect5, useRef as useRef2 } from "react";
384
693
  import { Input as Input5, Select } from "@dxos/react-ui";
385
694
  var TextInput = ({ type, label, inputOnly, disabled, placeholder, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
386
695
  const { status, error } = getStatus();
387
- return /* @__PURE__ */ React6.createElement(Input5.Root, {
696
+ return /* @__PURE__ */ React7.createElement(Input5.Root, {
388
697
  validationValence: status
389
- }, !inputOnly && /* @__PURE__ */ React6.createElement(InputHeader, {
698
+ }, !inputOnly && /* @__PURE__ */ React7.createElement(InputHeader, {
390
699
  error
391
- }, /* @__PURE__ */ React6.createElement(Input5.Label, null, label)), /* @__PURE__ */ React6.createElement(Input5.TextInput, {
700
+ }, /* @__PURE__ */ React7.createElement(Input5.Label, null, label)), /* @__PURE__ */ React7.createElement(Input5.TextInput, {
392
701
  disabled,
393
702
  placeholder,
394
703
  value: getValue3() ?? "",
395
704
  onChange: (event) => onValueChange(type, event.target.value),
396
705
  onBlur
397
- }), inputOnly && /* @__PURE__ */ React6.createElement(Input5.Validation, null, error));
706
+ }), inputOnly && /* @__PURE__ */ React7.createElement(Input5.Validation, null, error));
398
707
  };
399
708
  var NumberInput = ({ type, label, inputOnly, disabled, placeholder, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
400
709
  const { status, error } = getStatus();
401
- return /* @__PURE__ */ React6.createElement(Input5.Root, {
710
+ return /* @__PURE__ */ React7.createElement(Input5.Root, {
402
711
  validationValence: status
403
- }, !inputOnly && /* @__PURE__ */ React6.createElement(InputHeader, {
712
+ }, !inputOnly && /* @__PURE__ */ React7.createElement(InputHeader, {
404
713
  error
405
- }, /* @__PURE__ */ React6.createElement(Input5.Label, null, label)), /* @__PURE__ */ React6.createElement(Input5.TextInput, {
714
+ }, /* @__PURE__ */ React7.createElement(Input5.Label, null, label)), /* @__PURE__ */ React7.createElement(Input5.TextInput, {
406
715
  type: "number",
407
716
  disabled,
408
717
  placeholder,
409
718
  value: getValue3() ?? 0,
410
719
  onChange: (event) => onValueChange(type, event.target.value),
411
720
  onBlur
412
- }), inputOnly && /* @__PURE__ */ React6.createElement(Input5.DescriptionAndValidation, null, error));
721
+ }), inputOnly && /* @__PURE__ */ React7.createElement(Input5.DescriptionAndValidation, null, error));
413
722
  };
414
723
  var BooleanInput = ({ type, label, inputOnly, getStatus, getValue: getValue3, onValueChange }) => {
415
724
  const { status, error } = getStatus();
416
725
  const checked = Boolean(getValue3());
417
- return /* @__PURE__ */ React6.createElement(Input5.Root, {
726
+ return /* @__PURE__ */ React7.createElement(Input5.Root, {
418
727
  validationValence: status
419
- }, !inputOnly && /* @__PURE__ */ React6.createElement(InputHeader, {
728
+ }, !inputOnly && /* @__PURE__ */ React7.createElement(InputHeader, {
420
729
  error
421
- }, /* @__PURE__ */ React6.createElement(Input5.Label, null, label)), /* @__PURE__ */ React6.createElement(Input5.Switch, {
730
+ }, /* @__PURE__ */ React7.createElement(Input5.Label, null, label)), /* @__PURE__ */ React7.createElement(Input5.Switch, {
422
731
  checked,
423
732
  onCheckedChange: (value) => onValueChange(type, value)
424
- }), inputOnly && /* @__PURE__ */ React6.createElement(Input5.DescriptionAndValidation, null, error));
733
+ }), inputOnly && /* @__PURE__ */ React7.createElement(Input5.DescriptionAndValidation, null, error));
425
734
  };
426
735
  var SelectInput = ({ type, label, inputOnly, disabled, placeholder, options, getStatus, getValue: getValue3, onValueChange }) => {
427
736
  const { status, error } = getStatus();
428
- return /* @__PURE__ */ React6.createElement(Input5.Root, {
737
+ return /* @__PURE__ */ React7.createElement(Input5.Root, {
429
738
  validationValence: status
430
- }, !inputOnly && /* @__PURE__ */ React6.createElement(InputHeader, {
739
+ }, !inputOnly && /* @__PURE__ */ React7.createElement(InputHeader, {
431
740
  error
432
- }, /* @__PURE__ */ React6.createElement(Input5.Label, null, label)), /* @__PURE__ */ React6.createElement(Select.Root, {
741
+ }, /* @__PURE__ */ React7.createElement(Input5.Label, null, label)), /* @__PURE__ */ React7.createElement(Select.Root, {
433
742
  value: getValue3(),
434
743
  onValueChange: (value) => onValueChange(type, value)
435
- }, /* @__PURE__ */ React6.createElement(Select.TriggerButton, {
744
+ }, /* @__PURE__ */ React7.createElement(Select.TriggerButton, {
436
745
  classNames: "is-full",
437
746
  disabled,
438
747
  placeholder
439
- }), /* @__PURE__ */ React6.createElement(Select.Portal, null, /* @__PURE__ */ React6.createElement(Select.Content, null, /* @__PURE__ */ React6.createElement(Select.Viewport, null, options?.map(({ value, label: label2 }) => (
748
+ }), /* @__PURE__ */ React7.createElement(Select.Portal, null, /* @__PURE__ */ React7.createElement(Select.Content, null, /* @__PURE__ */ React7.createElement(Select.Viewport, null, options?.map(({ value, label: label2 }) => (
440
749
  // NOTE: Numeric values are converted to and from strings.
441
- /* @__PURE__ */ React6.createElement(Select.Option, {
750
+ /* @__PURE__ */ React7.createElement(Select.Option, {
442
751
  key: String(value),
443
752
  value: String(value)
444
753
  }, label2 ?? String(value))
445
- )))))), inputOnly && /* @__PURE__ */ React6.createElement(Input5.DescriptionAndValidation, null, error));
754
+ )))))), inputOnly && /* @__PURE__ */ React7.createElement(Input5.DescriptionAndValidation, null, error));
446
755
  };
447
-
448
- // packages/ui/react-ui-form/src/components/Form/Form.tsx
449
- import React11, { useEffect as useEffect3, useMemo as useMemo4 } from "react";
450
- import { mx as mx7 } from "@dxos/react-ui-theme";
451
-
452
- // packages/ui/react-ui-form/src/components/Form/FormActions.tsx
453
- import React8 from "react";
454
- import { IconButton as IconButton2, useTranslation as useTranslation3 } from "@dxos/react-ui";
455
- import { mx as mx4 } from "@dxos/react-ui-theme";
456
-
457
- // packages/ui/react-ui-form/src/components/Form/FormContext.tsx
458
- import React7, { createContext, useContext } from "react";
459
- import { raise } from "@dxos/debug";
460
- import { getValue as getValue2 } from "@dxos/echo-schema";
461
- import { createJsonPath as createJsonPath2 } from "@dxos/effect";
462
-
463
- // packages/ui/react-ui-form/src/hooks/useForm.ts
464
- import { useCallback as useCallback2, useEffect as useEffect2, useMemo, useState as useState2 } from "react";
465
- import { getValue, setValue } from "@dxos/echo-schema";
466
- import { AST, createJsonPath, fromEffectValidationPath } from "@dxos/effect";
467
- import { invariant } from "@dxos/invariant";
468
- import { log } from "@dxos/log";
469
- import { validateSchema } from "@dxos/schema";
470
- var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-form/src/hooks/useForm.ts";
471
- var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, onSave }) => {
472
- invariant(AST.isTypeLiteral(schema.ast), void 0, {
473
- F: __dxlog_file,
474
- L: 91,
475
- S: void 0,
476
- A: [
477
- "AST.isTypeLiteral(schema.ast)",
478
- ""
479
- ]
480
- });
481
- const [values, setValues] = useState2(initialValues);
482
- useEffect2(() => {
483
- setValues(initialValues);
484
- }, [
485
- initialValues
486
- ]);
487
- const [touched, setTouched] = useState2(createKeySet(initialValues, false));
488
- const [changed, setChanged] = useState2(createKeySet(initialValues, false));
489
- const [errors, setErrors] = useState2({});
490
- const [saving, setSaving] = useState2(false);
491
- const validate = useCallback2((values2) => {
492
- let errors2 = validateSchema(schema, values2) ?? [];
493
- if (errors2.length === 0 && onValidate) {
494
- const validatedValues = values2;
495
- errors2 = onValidate(validatedValues) ?? [];
496
- }
497
- setErrors(flatMap(errors2));
498
- const valid = errors2.length === 0;
499
- return valid;
500
- }, [
501
- schema,
502
- onValidate
503
- ]);
504
- const [schemaDirty, setSchemaDirty] = useState2(false);
505
- useEffect2(() => setSchemaDirty(true), [
506
- schema
507
- ]);
508
- useEffect2(() => {
509
- if (schemaDirty) {
510
- validate(values);
511
- setSchemaDirty(false);
512
- }
513
- }, [
514
- values,
515
- schemaDirty
516
- ]);
517
- const canSave = useMemo(() => !saving && // Check if any error paths that are touched have errors
518
- !Object.entries(touched).some(([path, isTouched]) => isTouched && Object.keys(errors).some((errorPath) => errorPath === path || errorPath.startsWith(`${path}.`) || errorPath.startsWith(`${path}[`))), [
519
- touched,
520
- errors,
521
- saving
522
- ]);
523
- const handleSave = useCallback2(async () => {
524
- if (validate(values)) {
525
- setSaving(true);
526
- try {
527
- await onSave?.(values, {
528
- changed
529
- });
530
- } finally {
531
- setSaving(false);
532
- }
756
+ var MarkdownInput = ({ type, label, inputOnly, disabled, placeholder, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
757
+ const { status, error } = getStatus();
758
+ const textareaRef = useRef2(null);
759
+ const adjustHeight = useCallback4(() => {
760
+ const textarea = textareaRef.current;
761
+ if (textarea) {
762
+ textarea.style.blockSize = "auto";
763
+ textarea.style.blockSize = `${textarea.scrollHeight + 2}px`;
533
764
  }
765
+ }, []);
766
+ useEffect5(() => {
767
+ adjustHeight();
534
768
  }, [
535
- values,
536
- validate,
537
- onSave,
538
- changed
539
- ]);
540
- const getStatus = useCallback2((path) => {
541
- const jsonPath = Array.isArray(path) ? createJsonPath(path) : path;
542
- const matchingError = Object.entries(errors).find(([errorPath]) => errorPath === jsonPath || errorPath.startsWith(`${jsonPath}.`) || errorPath.startsWith(`${jsonPath}[`));
543
- return {
544
- status: matchingError ? "error" : void 0,
545
- error: matchingError ? matchingError[1] : void 0
546
- };
547
- }, [
548
- errors
769
+ adjustHeight
549
770
  ]);
550
- const getFormValue = (path) => {
551
- return getValue(values, createJsonPath(path));
552
- };
553
- const onValueChange = (path, type, value) => {
554
- const jsonPath = createJsonPath(path);
555
- let parsedValue = value;
556
- try {
557
- if (type === "number") {
558
- parsedValue = parseFloat(value) || 0;
559
- }
560
- } catch (err) {
561
- log.catch(err, void 0, {
562
- F: __dxlog_file,
563
- L: 193,
564
- S: void 0,
565
- C: (f, a) => f(...a)
566
- });
567
- parsedValue = void 0;
568
- }
569
- const newValues = {
570
- ...setValue(values, jsonPath, parsedValue)
571
- };
572
- setValues(newValues);
573
- setChanged((prev) => ({
574
- ...prev,
575
- [jsonPath]: true
576
- }));
577
- onValuesChanged?.(newValues);
578
- const isValid = validate(newValues);
579
- if (isValid && onValid) {
580
- onValid(newValues, {
581
- changed
582
- });
583
- }
584
- };
585
- const onTouched = useCallback2((path) => {
586
- const jsonPath = createJsonPath(path);
587
- setTouched((touched2) => ({
588
- ...touched2,
589
- [jsonPath]: true
590
- }));
591
- validate(values);
771
+ useEffect5(() => {
772
+ adjustHeight();
592
773
  }, [
593
- validate,
594
- values
774
+ getValue3(),
775
+ adjustHeight
595
776
  ]);
596
- return {
597
- // State.
598
- values,
599
- errors,
600
- touched,
601
- changed,
602
- canSave,
603
- handleSave,
604
- // Field utils.
605
- getStatus,
606
- getValue: getFormValue,
607
- onValueChange,
608
- onTouched
609
- };
610
- };
611
- var createKeySet = (obj, value) => {
612
- invariant(obj, void 0, {
613
- F: __dxlog_file,
614
- L: 235,
615
- S: void 0,
616
- A: [
617
- "obj",
618
- ""
619
- ]
620
- });
621
- return Object.keys(obj).reduce((acc, key) => ({
622
- ...acc,
623
- [key]: value
624
- }), {});
625
- };
626
- var flatMap = (errors) => {
627
- return errors.reduce((result, { path, message }) => {
628
- const jsonPath = fromEffectValidationPath(path);
629
- if (!(jsonPath in result)) {
630
- result[jsonPath] = message;
631
- }
632
- return result;
633
- }, {});
777
+ return /* @__PURE__ */ React7.createElement(Input5.Root, {
778
+ validationValence: status
779
+ }, !inputOnly && /* @__PURE__ */ React7.createElement(InputHeader, {
780
+ error
781
+ }, /* @__PURE__ */ React7.createElement(Input5.Label, null, label)), /* @__PURE__ */ React7.createElement(Input5.TextArea, {
782
+ ref: textareaRef,
783
+ disabled,
784
+ placeholder,
785
+ value: getValue3() ?? "",
786
+ classNames: "min-bs-auto max-h-40 overflow-auto",
787
+ onChange: (event) => onValueChange(type, event.target.value),
788
+ onBlur,
789
+ style: {
790
+ resize: "none"
791
+ },
792
+ spellCheck: false
793
+ }), inputOnly && /* @__PURE__ */ React7.createElement(Input5.Validation, null, error));
634
794
  };
635
795
 
636
- // packages/ui/react-ui-form/src/components/Form/FormContext.tsx
637
- var FormContext = /* @__PURE__ */ createContext(void 0);
638
- var useFormContext = () => {
639
- return useContext(FormContext) ?? raise(new Error("Missing FormContext"));
640
- };
641
- var useFormValues = (path = []) => {
642
- const { values: formValues } = useFormContext();
643
- const jsonPath = createJsonPath2(path);
644
- return getValue2(formValues, jsonPath);
645
- };
646
- var useInputProps = (path = []) => {
647
- const { getStatus, getValue: getFormValue, onValueChange, onTouched } = useFormContext();
648
- return {
649
- getStatus: () => getStatus(path),
650
- getValue: () => getFormValue(path),
651
- onValueChange: (type, value) => onValueChange(path, type, value),
652
- onBlur: () => onTouched(path)
653
- };
654
- };
655
- var FormProvider = ({ children, ...formOptions }) => {
656
- const formHandler = useForm(formOptions);
657
- return /* @__PURE__ */ React7.createElement(FormContext.Provider, {
658
- value: formHandler
659
- }, children);
660
- };
796
+ // packages/ui/react-ui-form/src/components/Form/Form.tsx
797
+ import React11, { useEffect as useEffect6, useMemo as useMemo6, useRef as useRef3 } from "react";
798
+ import { mx as mx7 } from "@dxos/react-ui-theme";
661
799
 
662
800
  // packages/ui/react-ui-form/src/components/Form/FormActions.tsx
801
+ import React8 from "react";
802
+ import { IconButton as IconButton2, useTranslation as useTranslation3 } from "@dxos/react-ui";
803
+ import { mx as mx4 } from "@dxos/react-ui-theme";
663
804
  var FormActions = ({ onCancel, readonly }) => {
664
805
  const { t } = useTranslation3(translationKey);
665
806
  const { canSave, handleSave } = useFormContext();
@@ -687,7 +828,7 @@ var FormActions = ({ onCancel, readonly }) => {
687
828
  // packages/ui/react-ui-form/src/components/Form/FormContent.tsx
688
829
  import { pipe as pipe2 } from "effect";
689
830
  import { capitalize as capitalize2 } from "effect/String";
690
- import React10, { useMemo as useMemo3 } from "react";
831
+ import React10, { useMemo as useMemo5 } from "react";
691
832
  import { AST as AST3, S } from "@dxos/echo-schema";
692
833
  import { createJsonPath as createJsonPath3, findNode as findNode2, getDiscriminatedType as getDiscriminatedType2, isDiscriminatedUnion as isDiscriminatedUnion2 } from "@dxos/effect";
693
834
  import { mx as mx6 } from "@dxos/react-ui-theme";
@@ -697,7 +838,7 @@ import { isNotFalsy } from "@dxos/util";
697
838
  // packages/ui/react-ui-form/src/components/Form/ArrayField.tsx
698
839
  import { pipe } from "effect";
699
840
  import { capitalize } from "effect/String";
700
- import React9, { useCallback as useCallback3, useMemo as useMemo2 } from "react";
841
+ import React9, { useCallback as useCallback5, useMemo as useMemo4 } from "react";
701
842
  import { AST as AST2 } from "@dxos/echo-schema";
702
843
  import { findNode, getDiscriminatedType, isDiscriminatedUnion, SimpleType } from "@dxos/effect";
703
844
  import { invariant as invariant2 } from "@dxos/invariant";
@@ -733,7 +874,7 @@ var ArrayField = ({ property, readonly, path, inputProps, Custom, lookupComponen
733
874
  prop.defaultValue
734
875
  ]));
735
876
  };
736
- const newValue = useMemo2(() => {
877
+ const newValue = useMemo4(() => {
737
878
  if (type === "object" && elementType) {
738
879
  return getDefaultObjectValue(elementType);
739
880
  } else {
@@ -743,7 +884,7 @@ var ArrayField = ({ property, readonly, path, inputProps, Custom, lookupComponen
743
884
  type,
744
885
  elementType
745
886
  ]);
746
- const handleAdd = useCallback3(() => {
887
+ const handleAdd = useCallback5(() => {
747
888
  inputProps.onValueChange(type, [
748
889
  ...values,
749
890
  newValue
@@ -755,7 +896,7 @@ var ArrayField = ({ property, readonly, path, inputProps, Custom, lookupComponen
755
896
  newValue,
756
897
  values
757
898
  ]);
758
- const handleRemove = useCallback3((index) => {
899
+ const handleRemove = useCallback5((index) => {
759
900
  const newValues = values.filter((_, i) => i !== index);
760
901
  inputProps.onValueChange(type, newValues);
761
902
  }, [
@@ -813,8 +954,10 @@ var ArrayField = ({ property, readonly, path, inputProps, Custom, lookupComponen
813
954
  import { FormatEnum } from "@dxos/echo-schema";
814
955
  var getInputComponent = (type, format) => {
815
956
  switch (format) {
816
- case FormatEnum.LatLng:
957
+ case FormatEnum.GeoPoint:
817
958
  return GeoPointInput;
959
+ case FormatEnum.Markdown:
960
+ return MarkdownInput;
818
961
  }
819
962
  switch (type) {
820
963
  case "string":
@@ -830,11 +973,11 @@ var getInputComponent = (type, format) => {
830
973
  var FormField = ({ property, path, readonly, inline, lookupComponent, Custom }) => {
831
974
  const inputProps = useInputProps(path);
832
975
  const { ast, name, type, format, title, description, options, examples, array } = property;
833
- const label = useMemo3(() => title ?? pipe2(name, capitalize2), [
976
+ const label = useMemo5(() => title ?? pipe2(name, capitalize2), [
834
977
  title,
835
978
  name
836
979
  ]);
837
- const placeholder = useMemo3(() => examples?.length ? `Example: "${examples[0]}"` : description, [
980
+ const placeholder = useMemo5(() => examples?.length ? `Example: "${examples[0]}"` : description, [
838
981
  examples,
839
982
  description
840
983
  ]);
@@ -858,20 +1001,15 @@ var FormField = ({ property, path, readonly, inline, lookupComponent, Custom })
858
1001
  const jsonPath = createJsonPath3(path ?? []);
859
1002
  const CustomComponent = Custom?.[jsonPath];
860
1003
  if (CustomComponent) {
861
- return (
862
- // TODO(ZaymonFC): We should try to remove this div wrapper.
863
- /* @__PURE__ */ React10.createElement("div", {
864
- role: "none"
865
- }, /* @__PURE__ */ React10.createElement(CustomComponent, {
866
- type,
867
- format,
868
- label,
869
- inputOnly: inline,
870
- placeholder,
871
- disabled: readonly,
872
- ...inputProps
873
- }))
874
- );
1004
+ return /* @__PURE__ */ React10.createElement(CustomComponent, {
1005
+ type,
1006
+ format,
1007
+ label,
1008
+ inputOnly: inline,
1009
+ placeholder,
1010
+ disabled: readonly,
1011
+ ...inputProps
1012
+ });
875
1013
  }
876
1014
  if (array) {
877
1015
  return /* @__PURE__ */ React10.createElement(ArrayField, {
@@ -931,7 +1069,7 @@ var FormField = ({ property, path, readonly, inline, lookupComponent, Custom })
931
1069
  };
932
1070
  var FormFields = ({ schema, path, filter, sort, readonly, lookupComponent, Custom }) => {
933
1071
  const values = useFormValues(path);
934
- const properties = useMemo3(() => {
1072
+ const properties = useMemo5(() => {
935
1073
  const props = getSchemaProperties2(schema.ast, values);
936
1074
  const filtered = filter ? filter(props) : props;
937
1075
  return sort ? filtered.sort((a, b) => sort.indexOf(a.name) - sort.indexOf(b.name)) : filtered;
@@ -961,13 +1099,14 @@ var FormFields = ({ schema, path, filter, sort, readonly, lookupComponent, Custo
961
1099
 
962
1100
  // packages/ui/react-ui-form/src/components/Form/Form.tsx
963
1101
  var Form = ({ classNames, schema, values: initialValues, path = [], autoFocus, readonly, filter, sort, autoSave, testId, onValuesChanged, onValidate, onSave, onCancel, lookupComponent, Custom }) => {
964
- const onValid = useMemo4(() => autoSave ? onSave : void 0, [
1102
+ const formRef = useRef3(null);
1103
+ const onValid = useMemo6(() => autoSave ? onSave : void 0, [
965
1104
  autoSave,
966
1105
  onSave
967
1106
  ]);
968
- useEffect3(() => {
969
- if (autoFocus) {
970
- const input = document.querySelector("input");
1107
+ useEffect6(() => {
1108
+ if (autoFocus && formRef.current) {
1109
+ const input = formRef.current.querySelector("input");
971
1110
  if (input) {
972
1111
  input.focus();
973
1112
  }
@@ -976,13 +1115,16 @@ var Form = ({ classNames, schema, values: initialValues, path = [], autoFocus, r
976
1115
  autoFocus
977
1116
  ]);
978
1117
  return /* @__PURE__ */ React11.createElement(FormProvider, {
1118
+ formRef,
979
1119
  schema,
1120
+ autoSave,
980
1121
  initialValues,
981
1122
  onValuesChanged,
982
1123
  onValidate,
983
1124
  onValid,
984
1125
  onSave
985
1126
  }, /* @__PURE__ */ React11.createElement("div", {
1127
+ ref: formRef,
986
1128
  role: "none",
987
1129
  className: mx7("p-2", classNames),
988
1130
  "data-testid": testId
@@ -1004,13 +1146,13 @@ var Form = ({ classNames, schema, values: initialValues, path = [], autoFocus, r
1004
1146
  var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-form/src/components/FieldEditor/FieldEditor.tsx";
1005
1147
  var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
1006
1148
  const { t } = useTranslation5(translationKey);
1007
- const [props, setProps] = useState3(projection.getFieldProjection(field.id).props);
1008
- useEffect4(() => setProps(projection.getFieldProjection(field.id).props), [
1149
+ const [props, setProps] = useState4(projection.getFieldProjection(field.id).props);
1150
+ useEffect7(() => setProps(projection.getFieldProjection(field.id).props), [
1009
1151
  field,
1010
1152
  projection
1011
1153
  ]);
1012
- const [schemas, setSchemas] = useState3([]);
1013
- useEffect4(() => {
1154
+ const [schemas, setSchemas] = useState4([]);
1155
+ useEffect7(() => {
1014
1156
  if (!registry) {
1015
1157
  return;
1016
1158
  }
@@ -1028,17 +1170,17 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
1028
1170
  }, [
1029
1171
  registry
1030
1172
  ]);
1031
- const [referenceSchema, setReferenceSchema] = useState3();
1032
- useEffect4(() => {
1173
+ const [referenceSchema, setReferenceSchema] = useState4();
1174
+ useEffect7(() => {
1033
1175
  setReferenceSchema(schemas.find((schema) => schema.typename === props?.referenceSchema));
1034
1176
  }, [
1035
1177
  schemas,
1036
1178
  props?.referenceSchema
1037
1179
  ]);
1038
- const [{ fieldSchema }, setFieldSchema] = useState3({
1180
+ const [{ fieldSchema }, setFieldSchema] = useState4({
1039
1181
  fieldSchema: getFormatSchema(props?.format)
1040
1182
  });
1041
- const handleValuesChanged = useCallback4((_props) => {
1183
+ const handleValuesChanged = useCallback6((_props) => {
1042
1184
  setFieldSchema((prev) => {
1043
1185
  const fieldSchema2 = getFormatSchema(_props.format);
1044
1186
  if (prev.fieldSchema === fieldSchema2) {
@@ -1073,7 +1215,7 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
1073
1215
  props.format,
1074
1216
  props.referenceSchema
1075
1217
  ]);
1076
- const handleValidate = useCallback4(({ property }) => {
1218
+ const handleValidate = useCallback6(({ property }) => {
1077
1219
  if (property && view.fields.find((f) => f.path === property && f.path !== field.path)) {
1078
1220
  return [
1079
1221
  {
@@ -1086,7 +1228,7 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
1086
1228
  view.fields,
1087
1229
  field
1088
1230
  ]);
1089
- const handleSave = useCallback4((props2) => {
1231
+ const handleSave = useCallback6((props2) => {
1090
1232
  projection.setFieldProjection({
1091
1233
  field,
1092
1234
  props: props2
@@ -1097,13 +1239,13 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
1097
1239
  field,
1098
1240
  onSave
1099
1241
  ]);
1100
- const handleCancel = useCallback4(() => {
1242
+ const handleCancel = useCallback6(() => {
1101
1243
  requestAnimationFrame(() => onCancel?.());
1102
1244
  onSave();
1103
1245
  }, [
1104
1246
  onSave
1105
1247
  ]);
1106
- const Custom = useMemo5(() => ({
1248
+ const Custom = useMemo7(() => ({
1107
1249
  ["format"]: (props2) => /* @__PURE__ */ React12.createElement(SelectInput, {
1108
1250
  ...props2,
1109
1251
  options: FormatEnums.filter((value) => value !== FormatEnum2.None).map((value) => ({
@@ -1129,7 +1271,7 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
1129
1271
  schemas,
1130
1272
  referenceSchema
1131
1273
  ]);
1132
- const propIsNotType = useCallback4((props2) => props2.filter((p) => p.name !== "type"), []);
1274
+ const propIsNotType = useCallback6((props2) => props2.filter((p) => p.name !== "type"), []);
1133
1275
  if (!fieldSchema) {
1134
1276
  log2.warn("invalid format", {
1135
1277
  props
@@ -1160,7 +1302,7 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
1160
1302
  };
1161
1303
 
1162
1304
  // packages/ui/react-ui-form/src/components/ViewEditor/ViewEditor.tsx
1163
- import React13, { useCallback as useCallback5, useMemo as useMemo6, useState as useState4 } from "react";
1305
+ import React13, { useCallback as useCallback7, useMemo as useMemo8, useState as useState5 } from "react";
1164
1306
  import { AST as AST4, Format, S as S2 } from "@dxos/echo-schema";
1165
1307
  import { IconButton as IconButton4, useTranslation as useTranslation6 } from "@dxos/react-ui";
1166
1308
  import { List as List2 } from "@dxos/react-ui-list";
@@ -1171,18 +1313,18 @@ var ViewMetaSchema = S2.Struct({
1171
1313
  name: S2.String.annotations({
1172
1314
  [AST4.TitleAnnotationId]: "View"
1173
1315
  }),
1174
- typename: Format.DXN.annotations({
1316
+ typename: Format.URL.annotations({
1175
1317
  [AST4.TitleAnnotationId]: "Typename"
1176
1318
  })
1177
1319
  }).pipe(S2.mutable);
1178
1320
  var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading = false, onTypenameChanged, onDelete }) => {
1179
1321
  const { t } = useTranslation6(translationKey);
1180
- const projection = useMemo6(() => new ViewProjection(schema, view), [
1322
+ const projection = useMemo8(() => new ViewProjection(schema, view), [
1181
1323
  schema,
1182
1324
  view
1183
1325
  ]);
1184
- const [field, setField] = useState4();
1185
- const viewValues = useMemo6(() => {
1326
+ const [field, setField] = useState5();
1327
+ const viewValues = useMemo8(() => {
1186
1328
  return {
1187
1329
  name: view.name,
1188
1330
  // TODO(burdon): Need to warn user of possible consequences of editing.
@@ -1192,7 +1334,7 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
1192
1334
  }, [
1193
1335
  view
1194
1336
  ]);
1195
- const handleViewUpdate = useCallback5(({ name, typename }) => {
1337
+ const handleViewUpdate = useCallback7(({ name, typename }) => {
1196
1338
  requestAnimationFrame(() => {
1197
1339
  if (view.name !== name) {
1198
1340
  view.name = name;
@@ -1206,16 +1348,16 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
1206
1348
  schema,
1207
1349
  onTypenameChanged
1208
1350
  ]);
1209
- const handleSelect = useCallback5((field2) => {
1351
+ const handleSelect = useCallback7((field2) => {
1210
1352
  setField((f) => f === field2 ? void 0 : field2);
1211
1353
  }, []);
1212
- const handleAdd = useCallback5(() => {
1354
+ const handleAdd = useCallback7(() => {
1213
1355
  const field2 = projection.createFieldProjection();
1214
1356
  setField(field2);
1215
1357
  }, [
1216
1358
  view
1217
1359
  ]);
1218
- const handleMove = useCallback5((fromIndex, toIndex) => {
1360
+ const handleMove = useCallback7((fromIndex, toIndex) => {
1219
1361
  const fields = [
1220
1362
  ...view.fields
1221
1363
  ];
@@ -1225,12 +1367,11 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
1225
1367
  }, [
1226
1368
  view.fields
1227
1369
  ]);
1228
- const handleClose = useCallback5(() => setField(void 0), []);
1370
+ const handleClose = useCallback7(() => setField(void 0), []);
1229
1371
  return /* @__PURE__ */ React13.createElement("div", {
1230
1372
  role: "none",
1231
1373
  className: mx8("flex flex-col w-full divide-y divide-separator", classNames)
1232
1374
  }, /* @__PURE__ */ React13.createElement(Form, {
1233
- autoFocus: true,
1234
1375
  autoSave: true,
1235
1376
  schema: ViewMetaSchema,
1236
1377
  values: viewValues,
@@ -1281,7 +1422,7 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
1281
1422
  };
1282
1423
 
1283
1424
  // packages/ui/react-ui-form/src/util/field.ts
1284
- import { FormatEnum as FormatEnum3, TypeEnum } from "@dxos/echo-schema";
1425
+ import { FormatEnum as FormatEnum3, GeoLocation as GeoLocation2, TypeEnum } from "@dxos/echo-schema";
1285
1426
  var parseValue = ({ type, format, value }) => {
1286
1427
  if (value === void 0 || value === null || value === "") {
1287
1428
  return void 0;
@@ -1335,6 +1476,26 @@ var parseValue = ({ type, format, value }) => {
1335
1476
  const date = new Date(value);
1336
1477
  return isNaN(date.getTime()) ? null : date;
1337
1478
  }
1479
+ case FormatEnum3.GeoPoint: {
1480
+ if (typeof value === "string") {
1481
+ const cleaned = value.replace(/[^0-9.,-]+/g, "");
1482
+ const parts = cleaned.split(",");
1483
+ if (parts.length === 2) {
1484
+ const lat = parseFloat(parts[0]);
1485
+ const long = parseFloat(parts[1]);
1486
+ if (!isNaN(lat) && !isNaN(long)) {
1487
+ return GeoLocation2.toGeoPoint({
1488
+ latitude: lat,
1489
+ longitude: long
1490
+ });
1491
+ }
1492
+ }
1493
+ }
1494
+ return [
1495
+ 0,
1496
+ 0
1497
+ ];
1498
+ }
1338
1499
  default: {
1339
1500
  return value;
1340
1501
  }
@@ -1393,7 +1554,7 @@ var pathNotUniqueError = (path) => ({
1393
1554
  });
1394
1555
 
1395
1556
  // packages/ui/react-ui-form/src/util/formatting.ts
1396
- import { FormatEnum as FormatEnum4, TypeEnum as TypeEnum2 } from "@dxos/echo-schema";
1557
+ import { FormatEnum as FormatEnum4, GeoLocation as GeoLocation3, TypeEnum as TypeEnum2 } from "@dxos/echo-schema";
1397
1558
  var formatForDisplay = ({ type, format, value, locale = void 0 }) => {
1398
1559
  const formatScalar = (type2) => {
1399
1560
  switch (type2) {
@@ -1446,6 +1607,16 @@ var formatForDisplay = ({ type, format, value, locale = void 0 }) => {
1446
1607
  return date.toLocaleTimeString(locale);
1447
1608
  }
1448
1609
  }
1610
+ case FormatEnum4.GeoPoint: {
1611
+ if (value === null || value === void 0) {
1612
+ return "";
1613
+ }
1614
+ if (Array.isArray(value) && value.length >= 2 && value.every(Number.isFinite)) {
1615
+ const { latitude, longitude } = GeoLocation3.fromGeoPoint(value);
1616
+ return `${latitude},${longitude}`;
1617
+ }
1618
+ return String(value);
1619
+ }
1449
1620
  default: {
1450
1621
  if (value === null || value === "undefined") {
1451
1622
  return "";
@@ -1494,6 +1665,16 @@ var formatForEditing = ({ type, format, value, locale = void 0 }) => {
1494
1665
  const date = new Date(value);
1495
1666
  return date.toISOString().split("T")[1].split(".")[0];
1496
1667
  }
1668
+ case FormatEnum4.GeoPoint: {
1669
+ if (value === null || value === void 0) {
1670
+ return "";
1671
+ }
1672
+ if (Array.isArray(value) && value.length >= 2 && value.every(Number.isFinite)) {
1673
+ const { latitude, longitude } = GeoLocation3.fromGeoPoint(value);
1674
+ return `${latitude},${longitude}`;
1675
+ }
1676
+ return String(value);
1677
+ }
1497
1678
  default: {
1498
1679
  return String(value);
1499
1680
  }
@@ -1507,6 +1688,7 @@ export {
1507
1688
  Form,
1508
1689
  GeoPointInput,
1509
1690
  InputHeader,
1691
+ MarkdownInput,
1510
1692
  NumberInput,
1511
1693
  SelectInput,
1512
1694
  SelectOptionInput,
@@ -1520,6 +1702,7 @@ export {
1520
1702
  parseValue,
1521
1703
  pathNotUniqueError,
1522
1704
  translations_default as translations,
1523
- useForm
1705
+ useForm,
1706
+ useFormValues
1524
1707
  };
1525
1708
  //# sourceMappingURL=index.mjs.map