@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
@@ -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,14 +99,14 @@ 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
108
  // packages/ui/react-ui-form/src/components/Form/FormContext.tsx
109
- import React2, { createContext, useContext } from "react";
109
+ import React2, { createContext, useContext, useEffect as useEffect2, useMemo as useMemo2 } from "react";
110
110
  import { raise } from "@dxos/debug";
111
111
  import { getValue as getValue2 } from "@dxos/echo-schema";
112
112
  import { createJsonPath as createJsonPath2 } from "@dxos/effect";
@@ -122,7 +122,7 @@ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-form/src/ho
122
122
  var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, onSave }) => {
123
123
  invariant(AST.isTypeLiteral(schema.ast), void 0, {
124
124
  F: __dxlog_file,
125
- L: 91,
125
+ L: 92,
126
126
  S: void 0,
127
127
  A: [
128
128
  "AST.isTypeLiteral(schema.ast)",
@@ -165,7 +165,12 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
165
165
  values,
166
166
  schemaDirty
167
167
  ]);
168
- const canSave = useMemo(() => !saving && // Check if any error paths that are touched have errors
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.
169
174
  !Object.entries(touched).some(([path, isTouched]) => isTouched && Object.keys(errors).some((errorPath) => errorPath === path || errorPath.startsWith(`${path}.`) || errorPath.startsWith(`${path}[`))), [
170
175
  touched,
171
176
  errors,
@@ -198,10 +203,12 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
198
203
  }, [
199
204
  errors
200
205
  ]);
201
- const getFormValue = (path) => {
206
+ const getFormValue = useCallback((path) => {
202
207
  return getValue(values, createJsonPath(path));
203
- };
204
- const onValueChange = (path, type, value) => {
208
+ }, [
209
+ values
210
+ ]);
211
+ const onValueChange = useCallback((path, type, value) => {
205
212
  const jsonPath = createJsonPath(path);
206
213
  let parsedValue = value;
207
214
  try {
@@ -211,7 +218,7 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
211
218
  } catch (err) {
212
219
  log.catch(err, void 0, {
213
220
  F: __dxlog_file,
214
- L: 193,
221
+ L: 202,
215
222
  S: void 0,
216
223
  C: (f, a) => f(...a)
217
224
  });
@@ -232,7 +239,13 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
232
239
  changed
233
240
  });
234
241
  }
235
- };
242
+ }, [
243
+ values,
244
+ onValuesChanged,
245
+ validate,
246
+ onValid,
247
+ changed
248
+ ]);
236
249
  const onTouched = useCallback((path) => {
237
250
  const jsonPath = createJsonPath(path);
238
251
  setTouched((touched2) => ({
@@ -244,25 +257,39 @@ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, on
244
257
  validate,
245
258
  values
246
259
  ]);
247
- return {
260
+ return useMemo(() => ({
248
261
  // State.
249
262
  values,
250
263
  errors,
251
264
  touched,
252
265
  changed,
253
266
  canSave,
267
+ formIsValid,
268
+ // Actions.
254
269
  handleSave,
255
270
  // Field utils.
256
271
  getStatus,
257
272
  getValue: getFormValue,
258
273
  onValueChange,
259
274
  onTouched
260
- };
275
+ }), [
276
+ values,
277
+ errors,
278
+ touched,
279
+ changed,
280
+ canSave,
281
+ formIsValid,
282
+ handleSave,
283
+ getStatus,
284
+ getFormValue,
285
+ onValueChange,
286
+ onTouched
287
+ ]);
261
288
  };
262
289
  var createKeySet = (obj, value) => {
263
290
  invariant(obj, void 0, {
264
291
  F: __dxlog_file,
265
- L: 235,
292
+ L: 264,
266
293
  S: void 0,
267
294
  A: [
268
295
  "obj",
@@ -296,22 +323,62 @@ var useFormValues = (path = []) => {
296
323
  };
297
324
  var useInputProps = (path = []) => {
298
325
  const { getStatus, getValue: getFormValue, onValueChange, onTouched } = useFormContext();
299
- return {
300
- getStatus: () => getStatus(path),
301
- getValue: () => getFormValue(path),
302
- onValueChange: (type, value) => onValueChange(path, type, value),
303
- onBlur: () => onTouched(path)
304
- };
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
+ ]);
305
341
  };
306
- var FormProvider = ({ children, ...formOptions }) => {
307
- const formHandler = useForm(formOptions);
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
+ ]);
308
374
  return /* @__PURE__ */ React2.createElement(FormContext.Provider, {
309
- value: formHandler
375
+ value: form
310
376
  }, children);
311
377
  };
312
378
 
313
379
  // packages/ui/react-ui-form/src/components/Form/custom/GeoPointInput.tsx
314
- import React4 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";
315
382
  import { Input as Input2, useTranslation } from "@dxos/react-ui";
316
383
  import { safeParseFloat } from "@dxos/util";
317
384
 
@@ -338,51 +405,88 @@ var InputHeader = ({ children, error }) => {
338
405
  var GeoPointInput = ({ type, label, disabled, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
339
406
  const { t } = useTranslation(translationKey);
340
407
  const { status, error } = getStatus();
341
- const [lng = 0, lat = 0] = getValue3() ?? [];
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
+ ]);
342
449
  return /* @__PURE__ */ React4.createElement(Input2.Root, {
343
450
  validationValence: status
344
451
  }, /* @__PURE__ */ React4.createElement(InputHeader, {
345
452
  error
346
453
  }, /* @__PURE__ */ React4.createElement(Input2.Label, null, label)), /* @__PURE__ */ React4.createElement("div", {
454
+ role: "none",
347
455
  className: "grid grid-cols-2 gap-2"
348
456
  }, /* @__PURE__ */ React4.createElement(Input2.TextInput, {
349
- type: "number",
457
+ type: "text",
458
+ pattern: "^-?[0-9]*\\.?[0-9]*$",
350
459
  disabled,
351
- placeholder: t("placeholder longitude"),
352
- value: lng,
353
- onChange: (event) => onValueChange(type, [
354
- lat,
355
- safeParseFloat(event.target.value, 0)
356
- ]),
460
+ placeholder: t("placeholder latitude"),
461
+ value: latitudeText,
462
+ onChange: handleCoordinateChange("latitude", setLatitudeText),
357
463
  onBlur
358
464
  }), /* @__PURE__ */ React4.createElement(Input2.TextInput, {
359
- type: "number",
465
+ type: "text",
466
+ pattern: "^-?[0-9]*\\.?[0-9]*$",
360
467
  disabled,
361
- placeholder: t("placeholder latitude"),
362
- value: lat,
363
- onChange: (event) => onValueChange(type, [
364
- safeParseFloat(event.target.value, 0),
365
- lng
366
- ]),
468
+ placeholder: t("placeholder longitude"),
469
+ value: longitudeText,
470
+ onChange: handleCoordinateChange("longitude", setLongitudeText),
367
471
  onBlur
368
472
  })));
369
473
  };
370
474
 
371
475
  // packages/ui/react-ui-form/src/components/Form/custom/SelectOptionsInput.tsx
372
- import React5, { useCallback as useCallback2, useEffect as useEffect2, useState as useState2, useRef } from "react";
476
+ import React5, { useCallback as useCallback3, useEffect as useEffect4, useState as useState3, useRef } from "react";
373
477
  import { PublicKey } from "@dxos/keys";
374
478
  import { Icon as Icon2, IconButton, Input as Input3, Tag, Toolbar, useTranslation as useTranslation2 } from "@dxos/react-ui";
375
479
  import { List } from "@dxos/react-ui-list";
376
- import { HuePickerToolbarButton } from "@dxos/react-ui-pickers";
377
- 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";
378
482
  var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3, onValueChange }) => {
379
- const [selected, setSelectedId] = useState2(null);
380
- const [isNewOption, setIsNewOption] = useState2(false);
483
+ const [selected, setSelectedId] = useState3(null);
484
+ const [isNewOption, setIsNewOption] = useState3(false);
381
485
  const inputRef = useRef(null);
382
486
  const { t } = useTranslation2(translationKey);
383
487
  const { status, error } = getStatus();
384
488
  const options = getValue3();
385
- useEffect2(() => {
489
+ useEffect4(() => {
386
490
  if (options === void 0) {
387
491
  onValueChange(type, []);
388
492
  }
@@ -393,16 +497,15 @@ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3
393
497
  ]);
394
498
  const randomHue = () => {
395
499
  const usedHues = new Set(options?.map((option) => option.color) ?? []);
396
- const hueKeys = Object.keys(hueTokenThemes);
397
- const unusedHues = hueKeys.filter((hue) => !usedHues.has(hue));
500
+ const unusedHues = hues.filter((hue) => !usedHues.has(hue));
398
501
  if (unusedHues.length > 0) {
399
502
  return unusedHues[Math.floor(Math.random() * unusedHues.length)];
400
503
  }
401
504
  const lastThree = new Set(options?.slice(-3).map((option) => option.color) ?? []);
402
- const availableHues = hueKeys.filter((hue) => !lastThree.has(hue));
403
- 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)];
404
507
  };
405
- const handleAdd = useCallback2(() => {
508
+ const handleAdd = useCallback3(() => {
406
509
  const newId = PublicKey.random().truncate();
407
510
  const newOption = {
408
511
  id: newId,
@@ -420,7 +523,7 @@ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3
420
523
  type,
421
524
  onValueChange
422
525
  ]);
423
- const handleDelete = useCallback2((id) => {
526
+ const handleDelete = useCallback3((id) => {
424
527
  const newOptions = options?.filter((option) => option.id !== id) ?? [];
425
528
  onValueChange(type, newOptions);
426
529
  setSelectedId(null);
@@ -429,7 +532,7 @@ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3
429
532
  type,
430
533
  onValueChange
431
534
  ]);
432
- const handleMove = useCallback2((from, to) => {
535
+ const handleMove = useCallback3((from, to) => {
433
536
  if (!options) {
434
537
  return;
435
538
  }
@@ -444,10 +547,10 @@ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3
444
547
  type,
445
548
  onValueChange
446
549
  ]);
447
- const handleClick = useCallback2((id) => {
550
+ const handleClick = useCallback3((id) => {
448
551
  setSelectedId((current) => current === id ? null : id);
449
552
  }, []);
450
- const handleTitleChange = useCallback2((id) => (e) => {
553
+ const handleTitleChange = useCallback3((id) => (e) => {
451
554
  const newOptions = options?.map((o) => o.id === id ? {
452
555
  ...o,
453
556
  title: e.target.value
@@ -458,12 +561,12 @@ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3
458
561
  type,
459
562
  onValueChange
460
563
  ]);
461
- const handleKeyDown = useCallback2((e) => {
564
+ const handleKeyDown = useCallback3((e) => {
462
565
  if (e.key === "Enter") {
463
566
  setSelectedId(null);
464
567
  }
465
568
  }, []);
466
- const handleColorChange = useCallback2((id) => (hue) => {
569
+ const handleColorChange = useCallback3((id) => (hue) => {
467
570
  const newOptions = options?.map((o) => o.id === id ? {
468
571
  ...o,
469
572
  color: hue
@@ -474,7 +577,7 @@ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3
474
577
  type,
475
578
  onValueChange
476
579
  ]);
477
- useEffect2(() => {
580
+ useEffect4(() => {
478
581
  if (selected && isNewOption && inputRef.current) {
479
582
  inputRef.current.focus();
480
583
  setIsNewOption(false);
@@ -525,12 +628,13 @@ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3
525
628
  value: item.title,
526
629
  onChange: handleTitleChange(item.id),
527
630
  onKeyDown: handleKeyDown,
528
- classNames: "flex-1"
631
+ classNames: "flex-1",
632
+ "data-no-submit": true
529
633
  }), /* @__PURE__ */ React5.createElement(Toolbar.Root, {
530
634
  classNames: "p-0 m-0 !is-auto"
531
- }, /* @__PURE__ */ React5.createElement(HuePickerToolbarButton, {
532
- hue: item.color,
533
- onChangeHue: handleColorChange(item.id)
635
+ }, /* @__PURE__ */ React5.createElement(HuePicker, {
636
+ value: item.color,
637
+ onChange: handleColorChange(item.id)
534
638
  })), /* @__PURE__ */ React5.createElement(IconButton, {
535
639
  icon: "ph--trash--fill",
536
640
  iconOnly: true,
@@ -585,7 +689,7 @@ var TupleInput = ({ binding, type, label, disabled, getStatus, getValue: getValu
585
689
  };
586
690
 
587
691
  // packages/ui/react-ui-form/src/components/Form/Defaults.tsx
588
- import React7 from "react";
692
+ import React7, { useCallback as useCallback4, useEffect as useEffect5, useRef as useRef2 } from "react";
589
693
  import { Input as Input5, Select } from "@dxos/react-ui";
590
694
  var TextInput = ({ type, label, inputOnly, disabled, placeholder, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
591
695
  const { status, error } = getStatus();
@@ -649,9 +753,48 @@ var SelectInput = ({ type, label, inputOnly, disabled, placeholder, options, get
649
753
  }, label2 ?? String(value))
650
754
  )))))), inputOnly && /* @__PURE__ */ React7.createElement(Input5.DescriptionAndValidation, null, error));
651
755
  };
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`;
764
+ }
765
+ }, []);
766
+ useEffect5(() => {
767
+ adjustHeight();
768
+ }, [
769
+ adjustHeight
770
+ ]);
771
+ useEffect5(() => {
772
+ adjustHeight();
773
+ }, [
774
+ getValue3(),
775
+ adjustHeight
776
+ ]);
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));
794
+ };
652
795
 
653
796
  // packages/ui/react-ui-form/src/components/Form/Form.tsx
654
- import React11, { useEffect as useEffect3, useMemo as useMemo4 } from "react";
797
+ import React11, { useEffect as useEffect6, useMemo as useMemo6, useRef as useRef3 } from "react";
655
798
  import { mx as mx7 } from "@dxos/react-ui-theme";
656
799
 
657
800
  // packages/ui/react-ui-form/src/components/Form/FormActions.tsx
@@ -685,7 +828,7 @@ var FormActions = ({ onCancel, readonly }) => {
685
828
  // packages/ui/react-ui-form/src/components/Form/FormContent.tsx
686
829
  import { pipe as pipe2 } from "effect";
687
830
  import { capitalize as capitalize2 } from "effect/String";
688
- import React10, { useMemo as useMemo3 } from "react";
831
+ import React10, { useMemo as useMemo5 } from "react";
689
832
  import { AST as AST3, S } from "@dxos/echo-schema";
690
833
  import { createJsonPath as createJsonPath3, findNode as findNode2, getDiscriminatedType as getDiscriminatedType2, isDiscriminatedUnion as isDiscriminatedUnion2 } from "@dxos/effect";
691
834
  import { mx as mx6 } from "@dxos/react-ui-theme";
@@ -695,7 +838,7 @@ import { isNotFalsy } from "@dxos/util";
695
838
  // packages/ui/react-ui-form/src/components/Form/ArrayField.tsx
696
839
  import { pipe } from "effect";
697
840
  import { capitalize } from "effect/String";
698
- import React9, { useCallback as useCallback3, useMemo as useMemo2 } from "react";
841
+ import React9, { useCallback as useCallback5, useMemo as useMemo4 } from "react";
699
842
  import { AST as AST2 } from "@dxos/echo-schema";
700
843
  import { findNode, getDiscriminatedType, isDiscriminatedUnion, SimpleType } from "@dxos/effect";
701
844
  import { invariant as invariant2 } from "@dxos/invariant";
@@ -731,7 +874,7 @@ var ArrayField = ({ property, readonly, path, inputProps, Custom, lookupComponen
731
874
  prop.defaultValue
732
875
  ]));
733
876
  };
734
- const newValue = useMemo2(() => {
877
+ const newValue = useMemo4(() => {
735
878
  if (type === "object" && elementType) {
736
879
  return getDefaultObjectValue(elementType);
737
880
  } else {
@@ -741,7 +884,7 @@ var ArrayField = ({ property, readonly, path, inputProps, Custom, lookupComponen
741
884
  type,
742
885
  elementType
743
886
  ]);
744
- const handleAdd = useCallback3(() => {
887
+ const handleAdd = useCallback5(() => {
745
888
  inputProps.onValueChange(type, [
746
889
  ...values,
747
890
  newValue
@@ -753,7 +896,7 @@ var ArrayField = ({ property, readonly, path, inputProps, Custom, lookupComponen
753
896
  newValue,
754
897
  values
755
898
  ]);
756
- const handleRemove = useCallback3((index) => {
899
+ const handleRemove = useCallback5((index) => {
757
900
  const newValues = values.filter((_, i) => i !== index);
758
901
  inputProps.onValueChange(type, newValues);
759
902
  }, [
@@ -811,8 +954,10 @@ var ArrayField = ({ property, readonly, path, inputProps, Custom, lookupComponen
811
954
  import { FormatEnum } from "@dxos/echo-schema";
812
955
  var getInputComponent = (type, format) => {
813
956
  switch (format) {
814
- case FormatEnum.LatLng:
957
+ case FormatEnum.GeoPoint:
815
958
  return GeoPointInput;
959
+ case FormatEnum.Markdown:
960
+ return MarkdownInput;
816
961
  }
817
962
  switch (type) {
818
963
  case "string":
@@ -828,11 +973,11 @@ var getInputComponent = (type, format) => {
828
973
  var FormField = ({ property, path, readonly, inline, lookupComponent, Custom }) => {
829
974
  const inputProps = useInputProps(path);
830
975
  const { ast, name, type, format, title, description, options, examples, array } = property;
831
- const label = useMemo3(() => title ?? pipe2(name, capitalize2), [
976
+ const label = useMemo5(() => title ?? pipe2(name, capitalize2), [
832
977
  title,
833
978
  name
834
979
  ]);
835
- const placeholder = useMemo3(() => examples?.length ? `Example: "${examples[0]}"` : description, [
980
+ const placeholder = useMemo5(() => examples?.length ? `Example: "${examples[0]}"` : description, [
836
981
  examples,
837
982
  description
838
983
  ]);
@@ -856,20 +1001,15 @@ var FormField = ({ property, path, readonly, inline, lookupComponent, Custom })
856
1001
  const jsonPath = createJsonPath3(path ?? []);
857
1002
  const CustomComponent = Custom?.[jsonPath];
858
1003
  if (CustomComponent) {
859
- return (
860
- // TODO(ZaymonFC): We should try to remove this div wrapper.
861
- /* @__PURE__ */ React10.createElement("div", {
862
- role: "none"
863
- }, /* @__PURE__ */ React10.createElement(CustomComponent, {
864
- type,
865
- format,
866
- label,
867
- inputOnly: inline,
868
- placeholder,
869
- disabled: readonly,
870
- ...inputProps
871
- }))
872
- );
1004
+ return /* @__PURE__ */ React10.createElement(CustomComponent, {
1005
+ type,
1006
+ format,
1007
+ label,
1008
+ inputOnly: inline,
1009
+ placeholder,
1010
+ disabled: readonly,
1011
+ ...inputProps
1012
+ });
873
1013
  }
874
1014
  if (array) {
875
1015
  return /* @__PURE__ */ React10.createElement(ArrayField, {
@@ -929,7 +1069,7 @@ var FormField = ({ property, path, readonly, inline, lookupComponent, Custom })
929
1069
  };
930
1070
  var FormFields = ({ schema, path, filter, sort, readonly, lookupComponent, Custom }) => {
931
1071
  const values = useFormValues(path);
932
- const properties = useMemo3(() => {
1072
+ const properties = useMemo5(() => {
933
1073
  const props = getSchemaProperties2(schema.ast, values);
934
1074
  const filtered = filter ? filter(props) : props;
935
1075
  return sort ? filtered.sort((a, b) => sort.indexOf(a.name) - sort.indexOf(b.name)) : filtered;
@@ -959,13 +1099,14 @@ var FormFields = ({ schema, path, filter, sort, readonly, lookupComponent, Custo
959
1099
 
960
1100
  // packages/ui/react-ui-form/src/components/Form/Form.tsx
961
1101
  var Form = ({ classNames, schema, values: initialValues, path = [], autoFocus, readonly, filter, sort, autoSave, testId, onValuesChanged, onValidate, onSave, onCancel, lookupComponent, Custom }) => {
962
- const onValid = useMemo4(() => autoSave ? onSave : void 0, [
1102
+ const formRef = useRef3(null);
1103
+ const onValid = useMemo6(() => autoSave ? onSave : void 0, [
963
1104
  autoSave,
964
1105
  onSave
965
1106
  ]);
966
- useEffect3(() => {
967
- if (autoFocus) {
968
- const input = document.querySelector("input");
1107
+ useEffect6(() => {
1108
+ if (autoFocus && formRef.current) {
1109
+ const input = formRef.current.querySelector("input");
969
1110
  if (input) {
970
1111
  input.focus();
971
1112
  }
@@ -974,13 +1115,16 @@ var Form = ({ classNames, schema, values: initialValues, path = [], autoFocus, r
974
1115
  autoFocus
975
1116
  ]);
976
1117
  return /* @__PURE__ */ React11.createElement(FormProvider, {
1118
+ formRef,
977
1119
  schema,
1120
+ autoSave,
978
1121
  initialValues,
979
1122
  onValuesChanged,
980
1123
  onValidate,
981
1124
  onValid,
982
1125
  onSave
983
1126
  }, /* @__PURE__ */ React11.createElement("div", {
1127
+ ref: formRef,
984
1128
  role: "none",
985
1129
  className: mx7("p-2", classNames),
986
1130
  "data-testid": testId
@@ -1002,13 +1146,13 @@ var Form = ({ classNames, schema, values: initialValues, path = [], autoFocus, r
1002
1146
  var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-form/src/components/FieldEditor/FieldEditor.tsx";
1003
1147
  var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
1004
1148
  const { t } = useTranslation5(translationKey);
1005
- const [props, setProps] = useState3(projection.getFieldProjection(field.id).props);
1006
- 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), [
1007
1151
  field,
1008
1152
  projection
1009
1153
  ]);
1010
- const [schemas, setSchemas] = useState3([]);
1011
- useEffect4(() => {
1154
+ const [schemas, setSchemas] = useState4([]);
1155
+ useEffect7(() => {
1012
1156
  if (!registry) {
1013
1157
  return;
1014
1158
  }
@@ -1026,17 +1170,17 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
1026
1170
  }, [
1027
1171
  registry
1028
1172
  ]);
1029
- const [referenceSchema, setReferenceSchema] = useState3();
1030
- useEffect4(() => {
1173
+ const [referenceSchema, setReferenceSchema] = useState4();
1174
+ useEffect7(() => {
1031
1175
  setReferenceSchema(schemas.find((schema) => schema.typename === props?.referenceSchema));
1032
1176
  }, [
1033
1177
  schemas,
1034
1178
  props?.referenceSchema
1035
1179
  ]);
1036
- const [{ fieldSchema }, setFieldSchema] = useState3({
1180
+ const [{ fieldSchema }, setFieldSchema] = useState4({
1037
1181
  fieldSchema: getFormatSchema(props?.format)
1038
1182
  });
1039
- const handleValuesChanged = useCallback4((_props) => {
1183
+ const handleValuesChanged = useCallback6((_props) => {
1040
1184
  setFieldSchema((prev) => {
1041
1185
  const fieldSchema2 = getFormatSchema(_props.format);
1042
1186
  if (prev.fieldSchema === fieldSchema2) {
@@ -1071,7 +1215,7 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
1071
1215
  props.format,
1072
1216
  props.referenceSchema
1073
1217
  ]);
1074
- const handleValidate = useCallback4(({ property }) => {
1218
+ const handleValidate = useCallback6(({ property }) => {
1075
1219
  if (property && view.fields.find((f) => f.path === property && f.path !== field.path)) {
1076
1220
  return [
1077
1221
  {
@@ -1084,7 +1228,7 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
1084
1228
  view.fields,
1085
1229
  field
1086
1230
  ]);
1087
- const handleSave = useCallback4((props2) => {
1231
+ const handleSave = useCallback6((props2) => {
1088
1232
  projection.setFieldProjection({
1089
1233
  field,
1090
1234
  props: props2
@@ -1095,13 +1239,13 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
1095
1239
  field,
1096
1240
  onSave
1097
1241
  ]);
1098
- const handleCancel = useCallback4(() => {
1242
+ const handleCancel = useCallback6(() => {
1099
1243
  requestAnimationFrame(() => onCancel?.());
1100
1244
  onSave();
1101
1245
  }, [
1102
1246
  onSave
1103
1247
  ]);
1104
- const Custom = useMemo5(() => ({
1248
+ const Custom = useMemo7(() => ({
1105
1249
  ["format"]: (props2) => /* @__PURE__ */ React12.createElement(SelectInput, {
1106
1250
  ...props2,
1107
1251
  options: FormatEnums.filter((value) => value !== FormatEnum2.None).map((value) => ({
@@ -1127,7 +1271,7 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
1127
1271
  schemas,
1128
1272
  referenceSchema
1129
1273
  ]);
1130
- const propIsNotType = useCallback4((props2) => props2.filter((p) => p.name !== "type"), []);
1274
+ const propIsNotType = useCallback6((props2) => props2.filter((p) => p.name !== "type"), []);
1131
1275
  if (!fieldSchema) {
1132
1276
  log2.warn("invalid format", {
1133
1277
  props
@@ -1158,7 +1302,7 @@ var FieldEditor = ({ view, projection, field, registry, onSave, onCancel }) => {
1158
1302
  };
1159
1303
 
1160
1304
  // packages/ui/react-ui-form/src/components/ViewEditor/ViewEditor.tsx
1161
- 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";
1162
1306
  import { AST as AST4, Format, S as S2 } from "@dxos/echo-schema";
1163
1307
  import { IconButton as IconButton4, useTranslation as useTranslation6 } from "@dxos/react-ui";
1164
1308
  import { List as List2 } from "@dxos/react-ui-list";
@@ -1169,18 +1313,18 @@ var ViewMetaSchema = S2.Struct({
1169
1313
  name: S2.String.annotations({
1170
1314
  [AST4.TitleAnnotationId]: "View"
1171
1315
  }),
1172
- typename: Format.DXN.annotations({
1316
+ typename: Format.URL.annotations({
1173
1317
  [AST4.TitleAnnotationId]: "Typename"
1174
1318
  })
1175
1319
  }).pipe(S2.mutable);
1176
1320
  var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading = false, onTypenameChanged, onDelete }) => {
1177
1321
  const { t } = useTranslation6(translationKey);
1178
- const projection = useMemo6(() => new ViewProjection(schema, view), [
1322
+ const projection = useMemo8(() => new ViewProjection(schema, view), [
1179
1323
  schema,
1180
1324
  view
1181
1325
  ]);
1182
- const [field, setField] = useState4();
1183
- const viewValues = useMemo6(() => {
1326
+ const [field, setField] = useState5();
1327
+ const viewValues = useMemo8(() => {
1184
1328
  return {
1185
1329
  name: view.name,
1186
1330
  // TODO(burdon): Need to warn user of possible consequences of editing.
@@ -1190,7 +1334,7 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
1190
1334
  }, [
1191
1335
  view
1192
1336
  ]);
1193
- const handleViewUpdate = useCallback5(({ name, typename }) => {
1337
+ const handleViewUpdate = useCallback7(({ name, typename }) => {
1194
1338
  requestAnimationFrame(() => {
1195
1339
  if (view.name !== name) {
1196
1340
  view.name = name;
@@ -1204,16 +1348,16 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
1204
1348
  schema,
1205
1349
  onTypenameChanged
1206
1350
  ]);
1207
- const handleSelect = useCallback5((field2) => {
1351
+ const handleSelect = useCallback7((field2) => {
1208
1352
  setField((f) => f === field2 ? void 0 : field2);
1209
1353
  }, []);
1210
- const handleAdd = useCallback5(() => {
1354
+ const handleAdd = useCallback7(() => {
1211
1355
  const field2 = projection.createFieldProjection();
1212
1356
  setField(field2);
1213
1357
  }, [
1214
1358
  view
1215
1359
  ]);
1216
- const handleMove = useCallback5((fromIndex, toIndex) => {
1360
+ const handleMove = useCallback7((fromIndex, toIndex) => {
1217
1361
  const fields = [
1218
1362
  ...view.fields
1219
1363
  ];
@@ -1223,12 +1367,11 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
1223
1367
  }, [
1224
1368
  view.fields
1225
1369
  ]);
1226
- const handleClose = useCallback5(() => setField(void 0), []);
1370
+ const handleClose = useCallback7(() => setField(void 0), []);
1227
1371
  return /* @__PURE__ */ React13.createElement("div", {
1228
1372
  role: "none",
1229
1373
  className: mx8("flex flex-col w-full divide-y divide-separator", classNames)
1230
1374
  }, /* @__PURE__ */ React13.createElement(Form, {
1231
- autoFocus: true,
1232
1375
  autoSave: true,
1233
1376
  schema: ViewMetaSchema,
1234
1377
  values: viewValues,
@@ -1279,7 +1422,7 @@ var ViewEditor = ({ classNames, schema, view, registry, readonly, showHeading =
1279
1422
  };
1280
1423
 
1281
1424
  // packages/ui/react-ui-form/src/util/field.ts
1282
- import { FormatEnum as FormatEnum3, TypeEnum } from "@dxos/echo-schema";
1425
+ import { FormatEnum as FormatEnum3, GeoLocation as GeoLocation2, TypeEnum } from "@dxos/echo-schema";
1283
1426
  var parseValue = ({ type, format, value }) => {
1284
1427
  if (value === void 0 || value === null || value === "") {
1285
1428
  return void 0;
@@ -1333,6 +1476,26 @@ var parseValue = ({ type, format, value }) => {
1333
1476
  const date = new Date(value);
1334
1477
  return isNaN(date.getTime()) ? null : date;
1335
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
+ }
1336
1499
  default: {
1337
1500
  return value;
1338
1501
  }
@@ -1391,7 +1554,7 @@ var pathNotUniqueError = (path) => ({
1391
1554
  });
1392
1555
 
1393
1556
  // packages/ui/react-ui-form/src/util/formatting.ts
1394
- 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";
1395
1558
  var formatForDisplay = ({ type, format, value, locale = void 0 }) => {
1396
1559
  const formatScalar = (type2) => {
1397
1560
  switch (type2) {
@@ -1444,6 +1607,16 @@ var formatForDisplay = ({ type, format, value, locale = void 0 }) => {
1444
1607
  return date.toLocaleTimeString(locale);
1445
1608
  }
1446
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
+ }
1447
1620
  default: {
1448
1621
  if (value === null || value === "undefined") {
1449
1622
  return "";
@@ -1492,6 +1665,16 @@ var formatForEditing = ({ type, format, value, locale = void 0 }) => {
1492
1665
  const date = new Date(value);
1493
1666
  return date.toISOString().split("T")[1].split(".")[0];
1494
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
+ }
1495
1678
  default: {
1496
1679
  return String(value);
1497
1680
  }
@@ -1505,6 +1688,7 @@ export {
1505
1688
  Form,
1506
1689
  GeoPointInput,
1507
1690
  InputHeader,
1691
+ MarkdownInput,
1508
1692
  NumberInput,
1509
1693
  SelectInput,
1510
1694
  SelectOptionInput,