@dxos/react-ui-form 0.7.5-labs.ea4b4c2 → 0.7.5-labs.f400bbc

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 (61) hide show
  1. package/dist/lib/browser/index.mjs +299 -115
  2. package/dist/lib/browser/index.mjs.map +3 -3
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node/index.cjs +324 -139
  5. package/dist/lib/node/index.cjs.map +3 -3
  6. package/dist/lib/node/meta.json +1 -1
  7. package/dist/lib/node-esm/index.mjs +299 -115
  8. package/dist/lib/node-esm/index.mjs.map +3 -3
  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/ViewEditor/Test.stories.d.ts +1 -2
  34. package/dist/types/src/components/ViewEditor/Test.stories.d.ts.map +1 -1
  35. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +1 -2
  36. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  37. package/dist/types/src/components/deprecated/Form.d.ts +1 -2
  38. package/dist/types/src/components/deprecated/Form.d.ts.map +1 -1
  39. package/dist/types/src/components/deprecated/Form.stories.d.ts +1 -2
  40. package/dist/types/src/components/deprecated/Form.stories.d.ts.map +1 -1
  41. package/dist/types/src/components/deprecated/FormInput.d.ts +2 -2
  42. package/dist/types/src/components/deprecated/FormInput.d.ts.map +1 -1
  43. package/dist/types/src/components/testing.d.ts +3 -3
  44. package/dist/types/src/components/testing.d.ts.map +1 -1
  45. package/dist/types/src/hooks/useForm.d.ts +1 -0
  46. package/dist/types/src/hooks/useForm.d.ts.map +1 -1
  47. package/dist/types/src/util/field.d.ts.map +1 -1
  48. package/dist/types/src/util/formatting.d.ts.map +1 -1
  49. package/package.json +25 -24
  50. package/src/components/Form/Defaults.tsx +59 -3
  51. package/src/components/Form/Form.tsx +8 -5
  52. package/src/components/Form/FormContent.tsx +9 -12
  53. package/src/components/Form/FormContext.tsx +58 -10
  54. package/src/components/Form/custom/GeoPointInput.tsx +42 -12
  55. package/src/components/Form/custom/SelectOptionsInput.tsx +7 -9
  56. package/src/components/Form/factory.tsx +4 -2
  57. package/src/components/ViewEditor/ViewEditor.tsx +2 -2
  58. package/src/hooks/useForm.ts +69 -40
  59. package/src/translations.ts +1 -1
  60. package/src/util/field.ts +19 -1
  61. package/src/util/formatting.ts +29 -1
@@ -31,7 +31,7 @@ var translations_default = [
31
31
  "format hostname": "Hostname",
32
32
  "format integer": "Integer",
33
33
  "format json": "JSON",
34
- "format latlng": "LatLng",
34
+ "format latlng": "Geopoint",
35
35
  "format markdown": "Markdown",
36
36
  "format percent": "Percent",
37
37
  "format ref": "Reference",
@@ -101,14 +101,14 @@ var DeprecatedFormInput = ({ label, description, secondary, children }) => {
101
101
  };
102
102
 
103
103
  // packages/ui/react-ui-form/src/components/FieldEditor/FieldEditor.tsx
104
- import React12, { useCallback as useCallback4, useEffect as useEffect4, useMemo as useMemo5, useState as useState3 } from "react";
104
+ import React12, { useCallback as useCallback6, useEffect as useEffect7, useMemo as useMemo7, useState as useState4 } from "react";
105
105
  import { FormatEnum as FormatEnum2, FormatEnums, formatToType } from "@dxos/echo-schema";
106
106
  import { log as log2 } from "@dxos/log";
107
107
  import { useTranslation as useTranslation5 } from "@dxos/react-ui";
108
108
  import { getFormatSchema, getSchemaProperties as getSchemaProperties3, sortProperties } from "@dxos/schema";
109
109
 
110
110
  // packages/ui/react-ui-form/src/components/Form/FormContext.tsx
111
- import React2, { createContext, useContext } from "react";
111
+ import React2, { createContext, useContext, useEffect as useEffect2, useMemo as useMemo2 } from "react";
112
112
  import { raise } from "@dxos/debug";
113
113
  import { getValue as getValue2 } from "@dxos/echo-schema";
114
114
  import { createJsonPath as createJsonPath2 } from "@dxos/effect";
@@ -124,7 +124,7 @@ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-form/src/ho
124
124
  var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, onSave }) => {
125
125
  invariant(AST.isTypeLiteral(schema.ast), void 0, {
126
126
  F: __dxlog_file,
127
- L: 91,
127
+ L: 92,
128
128
  S: void 0,
129
129
  A: [
130
130
  "AST.isTypeLiteral(schema.ast)",
@@ -167,7 +167,12 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
167
167
  values,
168
168
  schemaDirty
169
169
  ]);
170
- const canSave = useMemo(() => !saving && // Check if any error paths that are touched have errors
170
+ const formIsValid = useMemo(() => {
171
+ return Object.keys(errors).length === 0;
172
+ }, [
173
+ errors
174
+ ]);
175
+ const canSave = useMemo(() => !saving && // Check if any error paths that are touched have errors.
171
176
  !Object.entries(touched).some(([path, isTouched]) => isTouched && Object.keys(errors).some((errorPath) => errorPath === path || errorPath.startsWith(`${path}.`) || errorPath.startsWith(`${path}[`))), [
172
177
  touched,
173
178
  errors,
@@ -200,10 +205,12 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
200
205
  }, [
201
206
  errors
202
207
  ]);
203
- const getFormValue = (path) => {
208
+ const getFormValue = useCallback((path) => {
204
209
  return getValue(values, createJsonPath(path));
205
- };
206
- const onValueChange = (path, type, value) => {
210
+ }, [
211
+ values
212
+ ]);
213
+ const onValueChange = useCallback((path, type, value) => {
207
214
  const jsonPath = createJsonPath(path);
208
215
  let parsedValue = value;
209
216
  try {
@@ -213,7 +220,7 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
213
220
  } catch (err) {
214
221
  log.catch(err, void 0, {
215
222
  F: __dxlog_file,
216
- L: 193,
223
+ L: 202,
217
224
  S: void 0,
218
225
  C: (f, a) => f(...a)
219
226
  });
@@ -234,7 +241,13 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
234
241
  changed
235
242
  });
236
243
  }
237
- };
244
+ }, [
245
+ values,
246
+ onValuesChanged,
247
+ validate,
248
+ onValid,
249
+ changed
250
+ ]);
238
251
  const onTouched = useCallback((path) => {
239
252
  const jsonPath = createJsonPath(path);
240
253
  setTouched((touched2) => ({
@@ -246,25 +259,39 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
246
259
  validate,
247
260
  values
248
261
  ]);
249
- return {
262
+ return useMemo(() => ({
250
263
  // State.
251
264
  values,
252
265
  errors,
253
266
  touched,
254
267
  changed,
255
268
  canSave,
269
+ formIsValid,
270
+ // Actions.
256
271
  handleSave,
257
272
  // Field utils.
258
273
  getStatus,
259
274
  getValue: getFormValue,
260
275
  onValueChange,
261
276
  onTouched
262
- };
277
+ }), [
278
+ values,
279
+ errors,
280
+ touched,
281
+ changed,
282
+ canSave,
283
+ formIsValid,
284
+ handleSave,
285
+ getStatus,
286
+ getFormValue,
287
+ onValueChange,
288
+ onTouched
289
+ ]);
263
290
  };
264
291
  var createKeySet = (obj, value) => {
265
292
  invariant(obj, void 0, {
266
293
  F: __dxlog_file,
267
- L: 235,
294
+ L: 264,
268
295
  S: void 0,
269
296
  A: [
270
297
  "obj",
@@ -298,22 +325,62 @@ var useFormValues = (path = []) => {
298
325
  };
299
326
  var useInputProps = (path = []) => {
300
327
  const { getStatus, getValue: getFormValue, onValueChange, onTouched } = useFormContext();
301
- return {
302
- getStatus: () => getStatus(path),
303
- getValue: () => getFormValue(path),
304
- onValueChange: (type, value) => onValueChange(path, type, value),
305
- onBlur: () => onTouched(path)
306
- };
328
+ const stablePath = useMemo2(() => path, [
329
+ Array.isArray(path) ? path.join(".") : path
330
+ ]);
331
+ return useMemo2(() => ({
332
+ getStatus: () => getStatus(stablePath),
333
+ getValue: () => getFormValue(stablePath),
334
+ onValueChange: (type, value) => onValueChange(stablePath, type, value),
335
+ onBlur: () => onTouched(stablePath)
336
+ }), [
337
+ getStatus,
338
+ getFormValue,
339
+ onValueChange,
340
+ onTouched,
341
+ stablePath
342
+ ]);
307
343
  };
308
- var FormProvider = ({ children, ...formOptions }) => {
309
- const formHandler = useForm(formOptions);
344
+ var FormProvider = ({ children, formRef, autoSave, ...formOptions }) => {
345
+ const form = useForm(formOptions);
346
+ useEffect2(() => {
347
+ if (!formRef?.current) {
348
+ return;
349
+ }
350
+ const handleKeyDown = (event) => {
351
+ const keyIsEnter = event.key === "Enter";
352
+ const modifierUsed = event.ctrlKey || event.altKey || event.metaKey || event.shiftKey;
353
+ const inputIsTextarea = event.target.tagName.toLowerCase() === "textarea";
354
+ const inputOptOut = event.target.hasAttribute("data-no-submit");
355
+ const shouldSubmitRegularInput = !inputIsTextarea && keyIsEnter && !modifierUsed;
356
+ const shouldSubmitTextarea = inputIsTextarea && keyIsEnter && event.metaKey;
357
+ if ((shouldSubmitRegularInput || shouldSubmitTextarea) && !inputOptOut) {
358
+ if (!autoSave && form.canSave) {
359
+ form.handleSave();
360
+ }
361
+ if (autoSave && form.formIsValid) {
362
+ event.target.blur();
363
+ }
364
+ }
365
+ };
366
+ const formElement = formRef.current;
367
+ formElement.addEventListener("keydown", handleKeyDown);
368
+ return () => formElement.removeEventListener("keydown", handleKeyDown);
369
+ }, [
370
+ formRef,
371
+ form.canSave,
372
+ form.formIsValid,
373
+ form.handleSave,
374
+ autoSave
375
+ ]);
310
376
  return /* @__PURE__ */ React2.createElement(FormContext.Provider, {
311
- value: formHandler
377
+ value: form
312
378
  }, children);
313
379
  };
314
380
 
315
381
  // packages/ui/react-ui-form/src/components/Form/custom/GeoPointInput.tsx
316
- import React4 from "react";
382
+ import React4, { useCallback as useCallback2, useState as useState2, useEffect as useEffect3, useMemo as useMemo3 } from "react";
383
+ import { GeoLocation } from "@dxos/echo-schema";
317
384
  import { Input as Input2, useTranslation } from "@dxos/react-ui";
318
385
  import { safeParseFloat } from "@dxos/util";
319
386
 
@@ -340,51 +407,88 @@ var InputHeader = ({ children, error }) => {
340
407
  var GeoPointInput = ({ type, label, disabled, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
341
408
  const { t } = useTranslation(translationKey);
342
409
  const { status, error } = getStatus();
343
- const [lng = 0, lat = 0] = getValue3() ?? [];
410
+ const geoPoint = useMemo3(() => getValue3() ?? [
411
+ 0,
412
+ 0
413
+ ], [
414
+ getValue3
415
+ ]);
416
+ const geoLocation = useMemo3(() => GeoLocation.fromGeoPoint(geoPoint), [
417
+ geoPoint
418
+ ]);
419
+ const [longitudeText, setLongitudeText] = useState2(geoLocation.longitude?.toString());
420
+ const [latitudeText, setLatitudeText] = useState2(geoLocation.latitude?.toString());
421
+ useEffect3(() => {
422
+ const location = GeoLocation.fromGeoPoint(geoPoint);
423
+ setLongitudeText(location.longitude.toString());
424
+ setLatitudeText(location.latitude.toString());
425
+ }, [
426
+ geoPoint
427
+ ]);
428
+ const handleCoordinateChange = useCallback2((coordinateType, setText) => (event) => {
429
+ const inputText = event.target.value;
430
+ setText(inputText);
431
+ if (inputText !== "" && inputText !== "-") {
432
+ const coord = safeParseFloat(inputText);
433
+ if (coord !== void 0 && !isNaN(coord)) {
434
+ const currentLocation = GeoLocation.fromGeoPoint(getValue3() ?? [
435
+ 0,
436
+ 0
437
+ ]);
438
+ const newLocation = {
439
+ ...currentLocation,
440
+ [coordinateType]: coord
441
+ };
442
+ const newValue = GeoLocation.toGeoPoint(newLocation);
443
+ onValueChange(type, newValue);
444
+ }
445
+ }
446
+ }, [
447
+ getValue3,
448
+ onValueChange,
449
+ type
450
+ ]);
344
451
  return /* @__PURE__ */ React4.createElement(Input2.Root, {
345
452
  validationValence: status
346
453
  }, /* @__PURE__ */ React4.createElement(InputHeader, {
347
454
  error
348
455
  }, /* @__PURE__ */ React4.createElement(Input2.Label, null, label)), /* @__PURE__ */ React4.createElement("div", {
456
+ role: "none",
349
457
  className: "grid grid-cols-2 gap-2"
350
458
  }, /* @__PURE__ */ React4.createElement(Input2.TextInput, {
351
- type: "number",
459
+ type: "text",
460
+ pattern: "^-?[0-9]*\\.?[0-9]*$",
352
461
  disabled,
353
- placeholder: t("placeholder longitude"),
354
- value: lng,
355
- onChange: (event) => onValueChange(type, [
356
- lat,
357
- safeParseFloat(event.target.value, 0)
358
- ]),
462
+ placeholder: t("placeholder latitude"),
463
+ value: latitudeText,
464
+ onChange: handleCoordinateChange("latitude", setLatitudeText),
359
465
  onBlur
360
466
  }), /* @__PURE__ */ React4.createElement(Input2.TextInput, {
361
- type: "number",
467
+ type: "text",
468
+ pattern: "^-?[0-9]*\\.?[0-9]*$",
362
469
  disabled,
363
- placeholder: t("placeholder latitude"),
364
- value: lat,
365
- onChange: (event) => onValueChange(type, [
366
- safeParseFloat(event.target.value, 0),
367
- lng
368
- ]),
470
+ placeholder: t("placeholder longitude"),
471
+ value: longitudeText,
472
+ onChange: handleCoordinateChange("longitude", setLongitudeText),
369
473
  onBlur
370
474
  })));
371
475
  };
372
476
 
373
477
  // packages/ui/react-ui-form/src/components/Form/custom/SelectOptionsInput.tsx
374
- import React5, { useCallback as useCallback2, useEffect as useEffect2, useState as useState2, useRef } from "react";
478
+ import React5, { useCallback as useCallback3, useEffect as useEffect4, useState as useState3, useRef } from "react";
375
479
  import { PublicKey } from "@dxos/keys";
376
480
  import { Icon as Icon2, IconButton, Input as Input3, Tag, Toolbar, useTranslation as useTranslation2 } from "@dxos/react-ui";
377
481
  import { List } from "@dxos/react-ui-list";
378
- import { HuePickerToolbarButton } from "@dxos/react-ui-pickers";
379
- import { hueTokenThemes, mx as mx2 } from "@dxos/react-ui-theme";
482
+ import { HuePicker } from "@dxos/react-ui-pickers";
483
+ import { hues, mx as mx2 } from "@dxos/react-ui-theme";
380
484
  var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3, onValueChange }) => {
381
- const [selected, setSelectedId] = useState2(null);
382
- const [isNewOption, setIsNewOption] = useState2(false);
485
+ const [selected, setSelectedId] = useState3(null);
486
+ const [isNewOption, setIsNewOption] = useState3(false);
383
487
  const inputRef = useRef(null);
384
488
  const { t } = useTranslation2(translationKey);
385
489
  const { status, error } = getStatus();
386
490
  const options = getValue3();
387
- useEffect2(() => {
491
+ useEffect4(() => {
388
492
  if (options === void 0) {
389
493
  onValueChange(type, []);
390
494
  }
@@ -395,16 +499,15 @@ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3
395
499
  ]);
396
500
  const randomHue = () => {
397
501
  const usedHues = new Set(options?.map((option) => option.color) ?? []);
398
- const hueKeys = Object.keys(hueTokenThemes);
399
- const unusedHues = hueKeys.filter((hue) => !usedHues.has(hue));
502
+ const unusedHues = hues.filter((hue) => !usedHues.has(hue));
400
503
  if (unusedHues.length > 0) {
401
504
  return unusedHues[Math.floor(Math.random() * unusedHues.length)];
402
505
  }
403
506
  const lastThree = new Set(options?.slice(-3).map((option) => option.color) ?? []);
404
- const availableHues = hueKeys.filter((hue) => !lastThree.has(hue));
405
- return availableHues[Math.floor(Math.random() * availableHues.length)] ?? hueKeys[Math.floor(Math.random() * hueKeys.length)];
507
+ const availableHues = hues.filter((hue) => !lastThree.has(hue));
508
+ return availableHues[Math.floor(Math.random() * availableHues.length)] ?? hues[Math.floor(Math.random() * hues.length)];
406
509
  };
407
- const handleAdd = useCallback2(() => {
510
+ const handleAdd = useCallback3(() => {
408
511
  const newId = PublicKey.random().truncate();
409
512
  const newOption = {
410
513
  id: newId,
@@ -422,7 +525,7 @@ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3
422
525
  type,
423
526
  onValueChange
424
527
  ]);
425
- const handleDelete = useCallback2((id) => {
528
+ const handleDelete = useCallback3((id) => {
426
529
  const newOptions = options?.filter((option) => option.id !== id) ?? [];
427
530
  onValueChange(type, newOptions);
428
531
  setSelectedId(null);
@@ -431,7 +534,7 @@ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3
431
534
  type,
432
535
  onValueChange
433
536
  ]);
434
- const handleMove = useCallback2((from, to) => {
537
+ const handleMove = useCallback3((from, to) => {
435
538
  if (!options) {
436
539
  return;
437
540
  }
@@ -446,10 +549,10 @@ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3
446
549
  type,
447
550
  onValueChange
448
551
  ]);
449
- const handleClick = useCallback2((id) => {
552
+ const handleClick = useCallback3((id) => {
450
553
  setSelectedId((current) => current === id ? null : id);
451
554
  }, []);
452
- const handleTitleChange = useCallback2((id) => (e) => {
555
+ const handleTitleChange = useCallback3((id) => (e) => {
453
556
  const newOptions = options?.map((o) => o.id === id ? {
454
557
  ...o,
455
558
  title: e.target.value
@@ -460,12 +563,12 @@ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3
460
563
  type,
461
564
  onValueChange
462
565
  ]);
463
- const handleKeyDown = useCallback2((e) => {
566
+ const handleKeyDown = useCallback3((e) => {
464
567
  if (e.key === "Enter") {
465
568
  setSelectedId(null);
466
569
  }
467
570
  }, []);
468
- const handleColorChange = useCallback2((id) => (hue) => {
571
+ const handleColorChange = useCallback3((id) => (hue) => {
469
572
  const newOptions = options?.map((o) => o.id === id ? {
470
573
  ...o,
471
574
  color: hue
@@ -476,7 +579,7 @@ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3
476
579
  type,
477
580
  onValueChange
478
581
  ]);
479
- useEffect2(() => {
582
+ useEffect4(() => {
480
583
  if (selected && isNewOption && inputRef.current) {
481
584
  inputRef.current.focus();
482
585
  setIsNewOption(false);
@@ -527,12 +630,13 @@ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3
527
630
  value: item.title,
528
631
  onChange: handleTitleChange(item.id),
529
632
  onKeyDown: handleKeyDown,
530
- classNames: "flex-1"
633
+ classNames: "flex-1",
634
+ "data-no-submit": true
531
635
  }), /* @__PURE__ */ React5.createElement(Toolbar.Root, {
532
636
  classNames: "p-0 m-0 !is-auto"
533
- }, /* @__PURE__ */ React5.createElement(HuePickerToolbarButton, {
534
- hue: item.color,
535
- onChangeHue: handleColorChange(item.id)
637
+ }, /* @__PURE__ */ React5.createElement(HuePicker, {
638
+ value: item.color,
639
+ onChange: handleColorChange(item.id)
536
640
  })), /* @__PURE__ */ React5.createElement(IconButton, {
537
641
  icon: "ph--trash--fill",
538
642
  iconOnly: true,
@@ -587,7 +691,7 @@ var TupleInput = ({ binding, type, label, disabled, getStatus, getValue: getValu
587
691
  };
588
692
 
589
693
  // packages/ui/react-ui-form/src/components/Form/Defaults.tsx
590
- import React7 from "react";
694
+ import React7, { useCallback as useCallback4, useEffect as useEffect5, useRef as useRef2 } from "react";
591
695
  import { Input as Input5, Select } from "@dxos/react-ui";
592
696
  var TextInput = ({ type, label, inputOnly, disabled, placeholder, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
593
697
  const { status, error } = getStatus();
@@ -651,9 +755,48 @@ var SelectInput = ({ type, label, inputOnly, disabled, placeholder, options, get
651
755
  }, label2 ?? String(value))
652
756
  )))))), inputOnly && /* @__PURE__ */ React7.createElement(Input5.DescriptionAndValidation, null, error));
653
757
  };
758
+ var MarkdownInput = ({ type, label, inputOnly, disabled, placeholder, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
759
+ const { status, error } = getStatus();
760
+ const textareaRef = useRef2(null);
761
+ const adjustHeight = useCallback4(() => {
762
+ const textarea = textareaRef.current;
763
+ if (textarea) {
764
+ textarea.style.blockSize = "auto";
765
+ textarea.style.blockSize = `${textarea.scrollHeight + 2}px`;
766
+ }
767
+ }, []);
768
+ useEffect5(() => {
769
+ adjustHeight();
770
+ }, [
771
+ adjustHeight
772
+ ]);
773
+ useEffect5(() => {
774
+ adjustHeight();
775
+ }, [
776
+ getValue3(),
777
+ adjustHeight
778
+ ]);
779
+ return /* @__PURE__ */ React7.createElement(Input5.Root, {
780
+ validationValence: status
781
+ }, !inputOnly && /* @__PURE__ */ React7.createElement(InputHeader, {
782
+ error
783
+ }, /* @__PURE__ */ React7.createElement(Input5.Label, null, label)), /* @__PURE__ */ React7.createElement(Input5.TextArea, {
784
+ ref: textareaRef,
785
+ disabled,
786
+ placeholder,
787
+ value: getValue3() ?? "",
788
+ classNames: "min-bs-auto max-h-40 overflow-auto",
789
+ onChange: (event) => onValueChange(type, event.target.value),
790
+ onBlur,
791
+ style: {
792
+ resize: "none"
793
+ },
794
+ spellCheck: false
795
+ }), inputOnly && /* @__PURE__ */ React7.createElement(Input5.Validation, null, error));
796
+ };
654
797
 
655
798
  // packages/ui/react-ui-form/src/components/Form/Form.tsx
656
- import React11, { useEffect as useEffect3, useMemo as useMemo4 } from "react";
799
+ import React11, { useEffect as useEffect6, useMemo as useMemo6, useRef as useRef3 } from "react";
657
800
  import { mx as mx7 } from "@dxos/react-ui-theme";
658
801
 
659
802
  // packages/ui/react-ui-form/src/components/Form/FormActions.tsx
@@ -687,7 +830,7 @@ var FormActions = ({ onCancel, readonly }) => {
687
830
  // packages/ui/react-ui-form/src/components/Form/FormContent.tsx
688
831
  import { pipe as pipe2 } from "effect";
689
832
  import { capitalize as capitalize2 } from "effect/String";
690
- import React10, { useMemo as useMemo3 } from "react";
833
+ import React10, { useMemo as useMemo5 } from "react";
691
834
  import { AST as AST3, S } from "@dxos/echo-schema";
692
835
  import { createJsonPath as createJsonPath3, findNode as findNode2, getDiscriminatedType as getDiscriminatedType2, isDiscriminatedUnion as isDiscriminatedUnion2 } from "@dxos/effect";
693
836
  import { mx as mx6 } from "@dxos/react-ui-theme";
@@ -697,7 +840,7 @@ import { isNotFalsy } from "@dxos/util";
697
840
  // packages/ui/react-ui-form/src/components/Form/ArrayField.tsx
698
841
  import { pipe } from "effect";
699
842
  import { capitalize } from "effect/String";
700
- import React9, { useCallback as useCallback3, useMemo as useMemo2 } from "react";
843
+ import React9, { useCallback as useCallback5, useMemo as useMemo4 } from "react";
701
844
  import { AST as AST2 } from "@dxos/echo-schema";
702
845
  import { findNode, getDiscriminatedType, isDiscriminatedUnion, SimpleType } from "@dxos/effect";
703
846
  import { invariant as invariant2 } from "@dxos/invariant";
@@ -733,7 +876,7 @@ var ArrayField = ({ property, readonly, path, inputProps, Custom, lookupComponen
733
876
  prop.defaultValue
734
877
  ]));
735
878
  };
736
- const newValue = useMemo2(() => {
879
+ const newValue = useMemo4(() => {
737
880
  if (type === "object" && elementType) {
738
881
  return getDefaultObjectValue(elementType);
739
882
  } else {
@@ -743,7 +886,7 @@ var ArrayField = ({ property, readonly, path, inputProps, Custom, lookupComponen
743
886
  type,
744
887
  elementType
745
888
  ]);
746
- const handleAdd = useCallback3(() => {
889
+ const handleAdd = useCallback5(() => {
747
890
  inputProps.onValueChange(type, [
748
891
  ...values,
749
892
  newValue
@@ -755,7 +898,7 @@ var ArrayField = ({ property, readonly, path, inputProps, Custom, lookupComponen
755
898
  newValue,
756
899
  values
757
900
  ]);
758
- const handleRemove = useCallback3((index) => {
901
+ const handleRemove = useCallback5((index) => {
759
902
  const newValues = values.filter((_, i) => i !== index);
760
903
  inputProps.onValueChange(type, newValues);
761
904
  }, [
@@ -813,8 +956,10 @@ var ArrayField = ({ property, readonly, path, inputProps, Custom, lookupComponen
813
956
  import { FormatEnum } from "@dxos/echo-schema";
814
957
  var getInputComponent = (type, format) => {
815
958
  switch (format) {
816
- case FormatEnum.LatLng:
959
+ case FormatEnum.GeoPoint:
817
960
  return GeoPointInput;
961
+ case FormatEnum.Markdown:
962
+ return MarkdownInput;
818
963
  }
819
964
  switch (type) {
820
965
  case "string":
@@ -830,11 +975,11 @@ var getInputComponent = (type, format) => {
830
975
  var FormField = ({ property, path, readonly, inline, lookupComponent, Custom }) => {
831
976
  const inputProps = useInputProps(path);
832
977
  const { ast, name, type, format, title, description, options, examples, array } = property;
833
- const label = useMemo3(() => title ?? pipe2(name, capitalize2), [
978
+ const label = useMemo5(() => title ?? pipe2(name, capitalize2), [
834
979
  title,
835
980
  name
836
981
  ]);
837
- const placeholder = useMemo3(() => examples?.length ? `Example: "${examples[0]}"` : description, [
982
+ const placeholder = useMemo5(() => examples?.length ? `Example: "${examples[0]}"` : description, [
838
983
  examples,
839
984
  description
840
985
  ]);
@@ -858,20 +1003,15 @@ var FormField = ({ property, path, readonly, inline, lookupComponent, Custom })
858
1003
  const jsonPath = createJsonPath3(path ?? []);
859
1004
  const CustomComponent = Custom?.[jsonPath];
860
1005
  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
- );
1006
+ return /* @__PURE__ */ React10.createElement(CustomComponent, {
1007
+ type,
1008
+ format,
1009
+ label,
1010
+ inputOnly: inline,
1011
+ placeholder,
1012
+ disabled: readonly,
1013
+ ...inputProps
1014
+ });
875
1015
  }
876
1016
  if (array) {
877
1017
  return /* @__PURE__ */ React10.createElement(ArrayField, {
@@ -931,7 +1071,7 @@ var FormField = ({ property, path, readonly, inline, lookupComponent, Custom })
931
1071
  };
932
1072
  var FormFields = ({ schema, path, filter, sort, readonly, lookupComponent, Custom }) => {
933
1073
  const values = useFormValues(path);
934
- const properties = useMemo3(() => {
1074
+ const properties = useMemo5(() => {
935
1075
  const props = getSchemaProperties2(schema.ast, values);
936
1076
  const filtered = filter ? filter(props) : props;
937
1077
  return sort ? filtered.sort((a, b) => sort.indexOf(a.name) - sort.indexOf(b.name)) : filtered;
@@ -961,13 +1101,14 @@ var FormFields = ({ schema, path, filter, sort, readonly, lookupComponent, Custo
961
1101
 
962
1102
  // packages/ui/react-ui-form/src/components/Form/Form.tsx
963
1103
  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, [
1104
+ const formRef = useRef3(null);
1105
+ const onValid = useMemo6(() => autoSave ? onSave : void 0, [
965
1106
  autoSave,
966
1107
  onSave
967
1108
  ]);
968
- useEffect3(() => {
969
- if (autoFocus) {
970
- const input = document.querySelector("input");
1109
+ useEffect6(() => {
1110
+ if (autoFocus && formRef.current) {
1111
+ const input = formRef.current.querySelector("input");
971
1112
  if (input) {
972
1113
  input.focus();
973
1114
  }
@@ -976,13 +1117,16 @@ var Form = ({ classNames, schema, values: initialValues, path = [], autoFocus, r
976
1117
  autoFocus
977
1118
  ]);
978
1119
  return /* @__PURE__ */ React11.createElement(FormProvider, {
1120
+ formRef,
979
1121
  schema,
1122
+ autoSave,
980
1123
  initialValues,
981
1124
  onValuesChanged,
982
1125
  onValidate,
983
1126
  onValid,
984
1127
  onSave
985
1128
  }, /* @__PURE__ */ React11.createElement("div", {
1129
+ ref: formRef,
986
1130
  role: "none",
987
1131
  className: mx7("p-2", classNames),
988
1132
  "data-testid": testId
@@ -1004,13 +1148,13 @@ var Form = ({ classNames, schema, values: initialValues, path = [], autoFocus, r
1004
1148
  var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-form/src/components/FieldEditor/FieldEditor.tsx";
1005
1149
  var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
1006
1150
  const { t } = useTranslation5(translationKey);
1007
- const [props, setProps] = useState3(projection.getFieldProjection(field.id).props);
1008
- useEffect4(() => setProps(projection.getFieldProjection(field.id).props), [
1151
+ const [props, setProps] = useState4(projection.getFieldProjection(field.id).props);
1152
+ useEffect7(() => setProps(projection.getFieldProjection(field.id).props), [
1009
1153
  field,
1010
1154
  projection
1011
1155
  ]);
1012
- const [schemas, setSchemas] = useState3([]);
1013
- useEffect4(() => {
1156
+ const [schemas, setSchemas] = useState4([]);
1157
+ useEffect7(() => {
1014
1158
  if (!registry) {
1015
1159
  return;
1016
1160
  }
@@ -1028,17 +1172,17 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
1028
1172
  }, [
1029
1173
  registry
1030
1174
  ]);
1031
- const [referenceSchema, setReferenceSchema] = useState3();
1032
- useEffect4(() => {
1175
+ const [referenceSchema, setReferenceSchema] = useState4();
1176
+ useEffect7(() => {
1033
1177
  setReferenceSchema(schemas.find((schema) => schema.typename === props?.referenceSchema));
1034
1178
  }, [
1035
1179
  schemas,
1036
1180
  props?.referenceSchema
1037
1181
  ]);
1038
- const [{ fieldSchema }, setFieldSchema] = useState3({
1182
+ const [{ fieldSchema }, setFieldSchema] = useState4({
1039
1183
  fieldSchema: getFormatSchema(props?.format)
1040
1184
  });
1041
- const handleValuesChanged = useCallback4((_props) => {
1185
+ const handleValuesChanged = useCallback6((_props) => {
1042
1186
  setFieldSchema((prev) => {
1043
1187
  const fieldSchema2 = getFormatSchema(_props.format);
1044
1188
  if (prev.fieldSchema === fieldSchema2) {
@@ -1073,7 +1217,7 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
1073
1217
  props.format,
1074
1218
  props.referenceSchema
1075
1219
  ]);
1076
- const handleValidate = useCallback4(({ property }) => {
1220
+ const handleValidate = useCallback6(({ property }) => {
1077
1221
  if (property && view.fields.find((f) => f.path === property && f.path !== field.path)) {
1078
1222
  return [
1079
1223
  {
@@ -1086,7 +1230,7 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
1086
1230
  view.fields,
1087
1231
  field
1088
1232
  ]);
1089
- const handleSave = useCallback4((props2) => {
1233
+ const handleSave = useCallback6((props2) => {
1090
1234
  projection.setFieldProjection({
1091
1235
  field,
1092
1236
  props: props2
@@ -1097,13 +1241,13 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
1097
1241
  field,
1098
1242
  onSave
1099
1243
  ]);
1100
- const handleCancel = useCallback4(() => {
1244
+ const handleCancel = useCallback6(() => {
1101
1245
  requestAnimationFrame(() => onCancel?.());
1102
1246
  onSave();
1103
1247
  }, [
1104
1248
  onSave
1105
1249
  ]);
1106
- const Custom = useMemo5(() => ({
1250
+ const Custom = useMemo7(() => ({
1107
1251
  ["format"]: (props2) => /* @__PURE__ */ React12.createElement(SelectInput, {
1108
1252
  ...props2,
1109
1253
  options: FormatEnums.filter((value) => value !== FormatEnum2.None).map((value) => ({
@@ -1129,7 +1273,7 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
1129
1273
  schemas,
1130
1274
  referenceSchema
1131
1275
  ]);
1132
- const propIsNotType = useCallback4((props2) => props2.filter((p) => p.name !== "type"), []);
1276
+ const propIsNotType = useCallback6((props2) => props2.filter((p) => p.name !== "type"), []);
1133
1277
  if (!fieldSchema) {
1134
1278
  log2.warn("invalid format", {
1135
1279
  props
@@ -1160,7 +1304,7 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
1160
1304
  };
1161
1305
 
1162
1306
  // packages/ui/react-ui-form/src/components/ViewEditor/ViewEditor.tsx
1163
- import React13, { useCallback as useCallback5, useMemo as useMemo6, useState as useState4 } from "react";
1307
+ import React13, { useCallback as useCallback7, useMemo as useMemo8, useState as useState5 } from "react";
1164
1308
  import { AST as AST4, Format, S as S2 } from "@dxos/echo-schema";
1165
1309
  import { IconButton as IconButton4, useTranslation as useTranslation6 } from "@dxos/react-ui";
1166
1310
  import { List as List2 } from "@dxos/react-ui-list";
@@ -1171,18 +1315,18 @@ var ViewMetaSchema = S2.Struct({
1171
1315
  name: S2.String.annotations({
1172
1316
  [AST4.TitleAnnotationId]: "View"
1173
1317
  }),
1174
- typename: Format.DXN.annotations({
1318
+ typename: Format.URL.annotations({
1175
1319
  [AST4.TitleAnnotationId]: "Typename"
1176
1320
  })
1177
1321
  }).pipe(S2.mutable);
1178
1322
  var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading = false, onTypenameChanged, onDelete }) => {
1179
1323
  const { t } = useTranslation6(translationKey);
1180
- const projection = useMemo6(() => new ViewProjection(schema, view), [
1324
+ const projection = useMemo8(() => new ViewProjection(schema, view), [
1181
1325
  schema,
1182
1326
  view
1183
1327
  ]);
1184
- const [field, setField] = useState4();
1185
- const viewValues = useMemo6(() => {
1328
+ const [field, setField] = useState5();
1329
+ const viewValues = useMemo8(() => {
1186
1330
  return {
1187
1331
  name: view.name,
1188
1332
  // TODO(burdon): Need to warn user of possible consequences of editing.
@@ -1192,7 +1336,7 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
1192
1336
  }, [
1193
1337
  view
1194
1338
  ]);
1195
- const handleViewUpdate = useCallback5(({ name, typename }) => {
1339
+ const handleViewUpdate = useCallback7(({ name, typename }) => {
1196
1340
  requestAnimationFrame(() => {
1197
1341
  if (view.name !== name) {
1198
1342
  view.name = name;
@@ -1206,16 +1350,16 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
1206
1350
  schema,
1207
1351
  onTypenameChanged
1208
1352
  ]);
1209
- const handleSelect = useCallback5((field2) => {
1353
+ const handleSelect = useCallback7((field2) => {
1210
1354
  setField((f) => f === field2 ? void 0 : field2);
1211
1355
  }, []);
1212
- const handleAdd = useCallback5(() => {
1356
+ const handleAdd = useCallback7(() => {
1213
1357
  const field2 = projection.createFieldProjection();
1214
1358
  setField(field2);
1215
1359
  }, [
1216
1360
  view
1217
1361
  ]);
1218
- const handleMove = useCallback5((fromIndex, toIndex) => {
1362
+ const handleMove = useCallback7((fromIndex, toIndex) => {
1219
1363
  const fields = [
1220
1364
  ...view.fields
1221
1365
  ];
@@ -1225,12 +1369,11 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
1225
1369
  }, [
1226
1370
  view.fields
1227
1371
  ]);
1228
- const handleClose = useCallback5(() => setField(void 0), []);
1372
+ const handleClose = useCallback7(() => setField(void 0), []);
1229
1373
  return /* @__PURE__ */ React13.createElement("div", {
1230
1374
  role: "none",
1231
1375
  className: mx8("flex flex-col w-full divide-y divide-separator", classNames)
1232
1376
  }, /* @__PURE__ */ React13.createElement(Form, {
1233
- autoFocus: true,
1234
1377
  autoSave: true,
1235
1378
  schema: ViewMetaSchema,
1236
1379
  values: viewValues,
@@ -1281,7 +1424,7 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
1281
1424
  };
1282
1425
 
1283
1426
  // packages/ui/react-ui-form/src/util/field.ts
1284
- import { FormatEnum as FormatEnum3, TypeEnum } from "@dxos/echo-schema";
1427
+ import { FormatEnum as FormatEnum3, GeoLocation as GeoLocation2, TypeEnum } from "@dxos/echo-schema";
1285
1428
  var parseValue = ({ type, format, value }) => {
1286
1429
  if (value === void 0 || value === null || value === "") {
1287
1430
  return void 0;
@@ -1335,6 +1478,26 @@ var parseValue = ({ type, format, value }) => {
1335
1478
  const date = new Date(value);
1336
1479
  return isNaN(date.getTime()) ? null : date;
1337
1480
  }
1481
+ case FormatEnum3.GeoPoint: {
1482
+ if (typeof value === "string") {
1483
+ const cleaned = value.replace(/[^0-9.,-]+/g, "");
1484
+ const parts = cleaned.split(",");
1485
+ if (parts.length === 2) {
1486
+ const lat = parseFloat(parts[0]);
1487
+ const long = parseFloat(parts[1]);
1488
+ if (!isNaN(lat) && !isNaN(long)) {
1489
+ return GeoLocation2.toGeoPoint({
1490
+ latitude: lat,
1491
+ longitude: long
1492
+ });
1493
+ }
1494
+ }
1495
+ }
1496
+ return [
1497
+ 0,
1498
+ 0
1499
+ ];
1500
+ }
1338
1501
  default: {
1339
1502
  return value;
1340
1503
  }
@@ -1393,7 +1556,7 @@ var pathNotUniqueError = (path) => ({
1393
1556
  });
1394
1557
 
1395
1558
  // packages/ui/react-ui-form/src/util/formatting.ts
1396
- import { FormatEnum as FormatEnum4, TypeEnum as TypeEnum2 } from "@dxos/echo-schema";
1559
+ import { FormatEnum as FormatEnum4, GeoLocation as GeoLocation3, TypeEnum as TypeEnum2 } from "@dxos/echo-schema";
1397
1560
  var formatForDisplay = ({ type, format, value, locale = void 0 }) => {
1398
1561
  const formatScalar = (type2) => {
1399
1562
  switch (type2) {
@@ -1446,6 +1609,16 @@ var formatForDisplay = ({ type, format, value, locale = void 0 }) => {
1446
1609
  return date.toLocaleTimeString(locale);
1447
1610
  }
1448
1611
  }
1612
+ case FormatEnum4.GeoPoint: {
1613
+ if (value === null || value === void 0) {
1614
+ return "";
1615
+ }
1616
+ if (Array.isArray(value) && value.length >= 2 && value.every(Number.isFinite)) {
1617
+ const { latitude, longitude } = GeoLocation3.fromGeoPoint(value);
1618
+ return `${latitude},${longitude}`;
1619
+ }
1620
+ return String(value);
1621
+ }
1449
1622
  default: {
1450
1623
  if (value === null || value === "undefined") {
1451
1624
  return "";
@@ -1494,6 +1667,16 @@ var formatForEditing = ({ type, format, value, locale = void 0 }) => {
1494
1667
  const date = new Date(value);
1495
1668
  return date.toISOString().split("T")[1].split(".")[0];
1496
1669
  }
1670
+ case FormatEnum4.GeoPoint: {
1671
+ if (value === null || value === void 0) {
1672
+ return "";
1673
+ }
1674
+ if (Array.isArray(value) && value.length >= 2 && value.every(Number.isFinite)) {
1675
+ const { latitude, longitude } = GeoLocation3.fromGeoPoint(value);
1676
+ return `${latitude},${longitude}`;
1677
+ }
1678
+ return String(value);
1679
+ }
1497
1680
  default: {
1498
1681
  return String(value);
1499
1682
  }
@@ -1507,6 +1690,7 @@ export {
1507
1690
  Form,
1508
1691
  GeoPointInput,
1509
1692
  InputHeader,
1693
+ MarkdownInput,
1510
1694
  NumberInput,
1511
1695
  SelectInput,
1512
1696
  SelectOptionInput,