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