@dxos/react-ui-form 0.7.5-labs.f5080a1 → 0.7.5-labs.ff2ff30

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.
@@ -105,28 +105,233 @@ import { log as log2 } from "@dxos/log";
105
105
  import { useTranslation as useTranslation5 } from "@dxos/react-ui";
106
106
  import { getFormatSchema, getSchemaProperties as getSchemaProperties3, sortProperties } from "@dxos/schema";
107
107
 
108
+ // packages/ui/react-ui-form/src/components/Form/FormContext.tsx
109
+ import React2, { createContext, useContext } from "react";
110
+ import { raise } from "@dxos/debug";
111
+ import { getValue as getValue2 } from "@dxos/echo-schema";
112
+ import { createJsonPath as createJsonPath2 } from "@dxos/effect";
113
+
114
+ // packages/ui/react-ui-form/src/hooks/useForm.ts
115
+ import { useCallback, useEffect, useMemo, useState } from "react";
116
+ import { getValue, setValue } from "@dxos/echo-schema";
117
+ import { AST, createJsonPath, fromEffectValidationPath } from "@dxos/effect";
118
+ import { invariant } from "@dxos/invariant";
119
+ import { log } from "@dxos/log";
120
+ import { validateSchema } from "@dxos/schema";
121
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-form/src/hooks/useForm.ts";
122
+ var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, onSave }) => {
123
+ invariant(AST.isTypeLiteral(schema.ast), void 0, {
124
+ F: __dxlog_file,
125
+ L: 91,
126
+ S: void 0,
127
+ A: [
128
+ "AST.isTypeLiteral(schema.ast)",
129
+ ""
130
+ ]
131
+ });
132
+ const [values, setValues] = useState(initialValues);
133
+ useEffect(() => {
134
+ setValues(initialValues);
135
+ }, [
136
+ initialValues
137
+ ]);
138
+ const [touched, setTouched] = useState(createKeySet(initialValues, false));
139
+ const [changed, setChanged] = useState(createKeySet(initialValues, false));
140
+ const [errors, setErrors] = useState({});
141
+ const [saving, setSaving] = useState(false);
142
+ const validate = useCallback((values2) => {
143
+ let errors2 = validateSchema(schema, values2) ?? [];
144
+ if (errors2.length === 0 && onValidate) {
145
+ const validatedValues = values2;
146
+ errors2 = onValidate(validatedValues) ?? [];
147
+ }
148
+ setErrors(flatMap(errors2));
149
+ const valid = errors2.length === 0;
150
+ return valid;
151
+ }, [
152
+ schema,
153
+ onValidate
154
+ ]);
155
+ const [schemaDirty, setSchemaDirty] = useState(false);
156
+ useEffect(() => setSchemaDirty(true), [
157
+ schema
158
+ ]);
159
+ useEffect(() => {
160
+ if (schemaDirty) {
161
+ validate(values);
162
+ setSchemaDirty(false);
163
+ }
164
+ }, [
165
+ values,
166
+ schemaDirty
167
+ ]);
168
+ const canSave = useMemo(() => !saving && // Check if any error paths that are touched have errors
169
+ !Object.entries(touched).some(([path, isTouched]) => isTouched && Object.keys(errors).some((errorPath) => errorPath === path || errorPath.startsWith(`${path}.`) || errorPath.startsWith(`${path}[`))), [
170
+ touched,
171
+ errors,
172
+ saving
173
+ ]);
174
+ const handleSave = useCallback(async () => {
175
+ if (validate(values)) {
176
+ setSaving(true);
177
+ try {
178
+ await onSave?.(values, {
179
+ changed
180
+ });
181
+ } finally {
182
+ setSaving(false);
183
+ }
184
+ }
185
+ }, [
186
+ values,
187
+ validate,
188
+ onSave,
189
+ changed
190
+ ]);
191
+ const getStatus = useCallback((path) => {
192
+ const jsonPath = Array.isArray(path) ? createJsonPath(path) : path;
193
+ const matchingError = Object.entries(errors).find(([errorPath]) => errorPath === jsonPath || errorPath.startsWith(`${jsonPath}.`) || errorPath.startsWith(`${jsonPath}[`));
194
+ return {
195
+ status: matchingError ? "error" : void 0,
196
+ error: matchingError ? matchingError[1] : void 0
197
+ };
198
+ }, [
199
+ errors
200
+ ]);
201
+ const getFormValue = (path) => {
202
+ return getValue(values, createJsonPath(path));
203
+ };
204
+ const onValueChange = (path, type, value) => {
205
+ const jsonPath = createJsonPath(path);
206
+ let parsedValue = value;
207
+ try {
208
+ if (type === "number") {
209
+ parsedValue = parseFloat(value) || 0;
210
+ }
211
+ } catch (err) {
212
+ log.catch(err, void 0, {
213
+ F: __dxlog_file,
214
+ L: 193,
215
+ S: void 0,
216
+ C: (f, a) => f(...a)
217
+ });
218
+ parsedValue = void 0;
219
+ }
220
+ const newValues = {
221
+ ...setValue(values, jsonPath, parsedValue)
222
+ };
223
+ setValues(newValues);
224
+ setChanged((prev) => ({
225
+ ...prev,
226
+ [jsonPath]: true
227
+ }));
228
+ onValuesChanged?.(newValues);
229
+ const isValid = validate(newValues);
230
+ if (isValid && onValid) {
231
+ onValid(newValues, {
232
+ changed
233
+ });
234
+ }
235
+ };
236
+ const onTouched = useCallback((path) => {
237
+ const jsonPath = createJsonPath(path);
238
+ setTouched((touched2) => ({
239
+ ...touched2,
240
+ [jsonPath]: true
241
+ }));
242
+ validate(values);
243
+ }, [
244
+ validate,
245
+ values
246
+ ]);
247
+ return {
248
+ // State.
249
+ values,
250
+ errors,
251
+ touched,
252
+ changed,
253
+ canSave,
254
+ handleSave,
255
+ // Field utils.
256
+ getStatus,
257
+ getValue: getFormValue,
258
+ onValueChange,
259
+ onTouched
260
+ };
261
+ };
262
+ var createKeySet = (obj, value) => {
263
+ invariant(obj, void 0, {
264
+ F: __dxlog_file,
265
+ L: 235,
266
+ S: void 0,
267
+ A: [
268
+ "obj",
269
+ ""
270
+ ]
271
+ });
272
+ return Object.keys(obj).reduce((acc, key) => ({
273
+ ...acc,
274
+ [key]: value
275
+ }), {});
276
+ };
277
+ var flatMap = (errors) => {
278
+ return errors.reduce((result, { path, message }) => {
279
+ const jsonPath = fromEffectValidationPath(path);
280
+ if (!(jsonPath in result)) {
281
+ result[jsonPath] = message;
282
+ }
283
+ return result;
284
+ }, {});
285
+ };
286
+
287
+ // packages/ui/react-ui-form/src/components/Form/FormContext.tsx
288
+ var FormContext = /* @__PURE__ */ createContext(void 0);
289
+ var useFormContext = () => {
290
+ return useContext(FormContext) ?? raise(new Error("Missing FormContext"));
291
+ };
292
+ var useFormValues = (path = []) => {
293
+ const { values: formValues } = useFormContext();
294
+ const jsonPath = createJsonPath2(path);
295
+ return getValue2(formValues, jsonPath);
296
+ };
297
+ var useInputProps = (path = []) => {
298
+ 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
+ };
305
+ };
306
+ var FormProvider = ({ children, ...formOptions }) => {
307
+ const formHandler = useForm(formOptions);
308
+ return /* @__PURE__ */ React2.createElement(FormContext.Provider, {
309
+ value: formHandler
310
+ }, children);
311
+ };
312
+
108
313
  // packages/ui/react-ui-form/src/components/Form/custom/GeoPointInput.tsx
109
- import React3 from "react";
314
+ import React4 from "react";
110
315
  import { Input as Input2, useTranslation } from "@dxos/react-ui";
111
316
  import { safeParseFloat } from "@dxos/util";
112
317
 
113
318
  // packages/ui/react-ui-form/src/components/Form/Input.tsx
114
- import React2 from "react";
319
+ import React3 from "react";
115
320
  import { Icon, Tooltip } from "@dxos/react-ui";
116
321
  import { errorText } from "@dxos/react-ui-theme";
117
322
  var InputHeader = ({ children, error }) => {
118
- return /* @__PURE__ */ React2.createElement("div", {
323
+ return /* @__PURE__ */ React3.createElement("div", {
119
324
  role: "none",
120
325
  className: "flex justify-between items-center mb-1"
121
- }, children, error && /* @__PURE__ */ React2.createElement(Tooltip.Root, null, /* @__PURE__ */ React2.createElement(Tooltip.Trigger, {
326
+ }, children, error && /* @__PURE__ */ React3.createElement(Tooltip.Root, null, /* @__PURE__ */ React3.createElement(Tooltip.Trigger, {
122
327
  asChild: true
123
- }, /* @__PURE__ */ React2.createElement(Icon, {
328
+ }, /* @__PURE__ */ React3.createElement(Icon, {
124
329
  icon: "ph--warning--regular",
125
330
  size: 4,
126
331
  classNames: errorText
127
- })), /* @__PURE__ */ React2.createElement(Tooltip.Content, {
332
+ })), /* @__PURE__ */ React3.createElement(Tooltip.Content, {
128
333
  side: "bottom"
129
- }, /* @__PURE__ */ React2.createElement(Tooltip.Arrow, null), error)));
334
+ }, /* @__PURE__ */ React3.createElement(Tooltip.Arrow, null), error)));
130
335
  };
131
336
 
132
337
  // packages/ui/react-ui-form/src/components/Form/custom/GeoPointInput.tsx
@@ -134,13 +339,13 @@ var GeoPointInput = ({ type, label, disabled, getStatus, getValue: getValue3, on
134
339
  const { t } = useTranslation(translationKey);
135
340
  const { status, error } = getStatus();
136
341
  const [lng = 0, lat = 0] = getValue3() ?? [];
137
- return /* @__PURE__ */ React3.createElement(Input2.Root, {
342
+ return /* @__PURE__ */ React4.createElement(Input2.Root, {
138
343
  validationValence: status
139
- }, /* @__PURE__ */ React3.createElement(InputHeader, {
344
+ }, /* @__PURE__ */ React4.createElement(InputHeader, {
140
345
  error
141
- }, /* @__PURE__ */ React3.createElement(Input2.Label, null, label)), /* @__PURE__ */ React3.createElement("div", {
346
+ }, /* @__PURE__ */ React4.createElement(Input2.Label, null, label)), /* @__PURE__ */ React4.createElement("div", {
142
347
  className: "grid grid-cols-2 gap-2"
143
- }, /* @__PURE__ */ React3.createElement(Input2.TextInput, {
348
+ }, /* @__PURE__ */ React4.createElement(Input2.TextInput, {
144
349
  type: "number",
145
350
  disabled,
146
351
  placeholder: t("placeholder longitude"),
@@ -150,7 +355,7 @@ var GeoPointInput = ({ type, label, disabled, getStatus, getValue: getValue3, on
150
355
  safeParseFloat(event.target.value, 0)
151
356
  ]),
152
357
  onBlur
153
- }), /* @__PURE__ */ React3.createElement(Input2.TextInput, {
358
+ }), /* @__PURE__ */ React4.createElement(Input2.TextInput, {
154
359
  type: "number",
155
360
  disabled,
156
361
  placeholder: t("placeholder latitude"),
@@ -164,20 +369,20 @@ var GeoPointInput = ({ type, label, disabled, getStatus, getValue: getValue3, on
164
369
  };
165
370
 
166
371
  // packages/ui/react-ui-form/src/components/Form/custom/SelectOptionsInput.tsx
167
- import React4, { useCallback, useEffect, useState, useRef } from "react";
372
+ import React5, { useCallback as useCallback2, useEffect as useEffect2, useState as useState2, useRef } from "react";
168
373
  import { PublicKey } from "@dxos/keys";
169
374
  import { Icon as Icon2, IconButton, Input as Input3, Tag, Toolbar, useTranslation as useTranslation2 } from "@dxos/react-ui";
170
375
  import { List } from "@dxos/react-ui-list";
171
376
  import { HuePickerToolbarButton } from "@dxos/react-ui-pickers";
172
377
  import { hueTokenThemes, mx as mx2 } from "@dxos/react-ui-theme";
173
378
  var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3, onValueChange }) => {
174
- const [selected, setSelectedId] = useState(null);
175
- const [isNewOption, setIsNewOption] = useState(false);
379
+ const [selected, setSelectedId] = useState2(null);
380
+ const [isNewOption, setIsNewOption] = useState2(false);
176
381
  const inputRef = useRef(null);
177
382
  const { t } = useTranslation2(translationKey);
178
383
  const { status, error } = getStatus();
179
384
  const options = getValue3();
180
- useEffect(() => {
385
+ useEffect2(() => {
181
386
  if (options === void 0) {
182
387
  onValueChange(type, []);
183
388
  }
@@ -197,7 +402,7 @@ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3
197
402
  const availableHues = hueKeys.filter((hue) => !lastThree.has(hue));
198
403
  return availableHues[Math.floor(Math.random() * availableHues.length)] ?? hueKeys[Math.floor(Math.random() * hueKeys.length)];
199
404
  };
200
- const handleAdd = useCallback(() => {
405
+ const handleAdd = useCallback2(() => {
201
406
  const newId = PublicKey.random().truncate();
202
407
  const newOption = {
203
408
  id: newId,
@@ -215,7 +420,7 @@ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3
215
420
  type,
216
421
  onValueChange
217
422
  ]);
218
- const handleDelete = useCallback((id) => {
423
+ const handleDelete = useCallback2((id) => {
219
424
  const newOptions = options?.filter((option) => option.id !== id) ?? [];
220
425
  onValueChange(type, newOptions);
221
426
  setSelectedId(null);
@@ -224,7 +429,7 @@ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3
224
429
  type,
225
430
  onValueChange
226
431
  ]);
227
- const handleMove = useCallback((from, to) => {
432
+ const handleMove = useCallback2((from, to) => {
228
433
  if (!options) {
229
434
  return;
230
435
  }
@@ -239,10 +444,10 @@ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3
239
444
  type,
240
445
  onValueChange
241
446
  ]);
242
- const handleClick = useCallback((id) => {
447
+ const handleClick = useCallback2((id) => {
243
448
  setSelectedId((current) => current === id ? null : id);
244
449
  }, []);
245
- const handleTitleChange = useCallback((id) => (e) => {
450
+ const handleTitleChange = useCallback2((id) => (e) => {
246
451
  const newOptions = options?.map((o) => o.id === id ? {
247
452
  ...o,
248
453
  title: e.target.value
@@ -253,12 +458,12 @@ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3
253
458
  type,
254
459
  onValueChange
255
460
  ]);
256
- const handleKeyDown = useCallback((e) => {
461
+ const handleKeyDown = useCallback2((e) => {
257
462
  if (e.key === "Enter") {
258
463
  setSelectedId(null);
259
464
  }
260
465
  }, []);
261
- const handleColorChange = useCallback((id) => (hue) => {
466
+ const handleColorChange = useCallback2((id) => (hue) => {
262
467
  const newOptions = options?.map((o) => o.id === id ? {
263
468
  ...o,
264
469
  color: hue
@@ -269,7 +474,7 @@ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3
269
474
  type,
270
475
  onValueChange
271
476
  ]);
272
- useEffect(() => {
477
+ useEffect2(() => {
273
478
  if (selected && isNewOption && inputRef.current) {
274
479
  inputRef.current.focus();
275
480
  setIsNewOption(false);
@@ -278,76 +483,76 @@ var SelectOptionInput = ({ type, label, disabled, getStatus, getValue: getValue3
278
483
  selected,
279
484
  isNewOption
280
485
  ]);
281
- return /* @__PURE__ */ React4.createElement(Input3.Root, {
486
+ return /* @__PURE__ */ React5.createElement(Input3.Root, {
282
487
  validationValence: status
283
- }, /* @__PURE__ */ React4.createElement(InputHeader, {
488
+ }, /* @__PURE__ */ React5.createElement(InputHeader, {
284
489
  error
285
- }, /* @__PURE__ */ React4.createElement(Input3.Label, null, label)), /* @__PURE__ */ React4.createElement("div", {
490
+ }, /* @__PURE__ */ React5.createElement(Input3.Label, null, label)), /* @__PURE__ */ React5.createElement("div", {
286
491
  role: "none"
287
- }, options && /* @__PURE__ */ React4.createElement(List.Root, {
492
+ }, options && /* @__PURE__ */ React5.createElement(List.Root, {
288
493
  items: options,
289
494
  isItem: (item) => true,
290
495
  onMove: handleMove
291
- }, ({ items }) => /* @__PURE__ */ React4.createElement("div", {
496
+ }, ({ items }) => /* @__PURE__ */ React5.createElement("div", {
292
497
  role: "list",
293
498
  className: "w-full overflow-auto"
294
- }, items?.map((item) => /* @__PURE__ */ React4.createElement("div", {
499
+ }, items?.map((item) => /* @__PURE__ */ React5.createElement("div", {
295
500
  key: item.id
296
- }, /* @__PURE__ */ React4.createElement(List.Item, {
501
+ }, /* @__PURE__ */ React5.createElement(List.Item, {
297
502
  role: "button",
298
503
  item,
299
504
  classNames: mx2("p-1 plb-2", "flex flex-col", "cursor-pointer", "hover:bg-hoverOverlay"),
300
505
  "aria-expanded": selected === item.id
301
- }, /* @__PURE__ */ React4.createElement("div", {
506
+ }, /* @__PURE__ */ React5.createElement("div", {
302
507
  className: "flex items-center"
303
- }, /* @__PURE__ */ React4.createElement(List.ItemDragHandle, null), /* @__PURE__ */ React4.createElement(List.ItemTitle, {
508
+ }, /* @__PURE__ */ React5.createElement(List.ItemDragHandle, null), /* @__PURE__ */ React5.createElement(List.ItemTitle, {
304
509
  onClick: () => handleClick(item.id),
305
510
  classNames: "flex-1"
306
- }, /* @__PURE__ */ React4.createElement(Tag, {
511
+ }, /* @__PURE__ */ React5.createElement(Tag, {
307
512
  palette: item.color
308
- }, item.title || "\u200B")), /* @__PURE__ */ React4.createElement(Icon2, {
513
+ }, item.title || "\u200B")), /* @__PURE__ */ React5.createElement(Icon2, {
309
514
  icon: selected === item.id ? "ph--caret-down--regular" : "ph--caret-right--regular",
310
515
  onClick: () => handleClick(item.id)
311
- })), selected === item.id && /* @__PURE__ */ React4.createElement("div", {
516
+ })), selected === item.id && /* @__PURE__ */ React5.createElement("div", {
312
517
  className: "mis-4 mbs-2 flex flex-col gap-1"
313
- }, /* @__PURE__ */ React4.createElement(Input3.Label, {
518
+ }, /* @__PURE__ */ React5.createElement(Input3.Label, {
314
519
  classNames: "text-xs"
315
- }, t("select option label")), /* @__PURE__ */ React4.createElement("div", {
520
+ }, t("select option label")), /* @__PURE__ */ React5.createElement("div", {
316
521
  className: "flex flex-row items-center gap-1"
317
- }, /* @__PURE__ */ React4.createElement(Input3.TextInput, {
522
+ }, /* @__PURE__ */ React5.createElement(Input3.TextInput, {
318
523
  placeholder: t("select option label placeholder"),
319
524
  ref: selected === item.id ? inputRef : void 0,
320
525
  value: item.title,
321
526
  onChange: handleTitleChange(item.id),
322
527
  onKeyDown: handleKeyDown,
323
528
  classNames: "flex-1"
324
- }), /* @__PURE__ */ React4.createElement(Toolbar.Root, {
529
+ }), /* @__PURE__ */ React5.createElement(Toolbar.Root, {
325
530
  classNames: "p-0 m-0 !is-auto"
326
- }, /* @__PURE__ */ React4.createElement(HuePickerToolbarButton, {
531
+ }, /* @__PURE__ */ React5.createElement(HuePickerToolbarButton, {
327
532
  hue: item.color,
328
533
  onChangeHue: handleColorChange(item.id)
329
- })), /* @__PURE__ */ React4.createElement(IconButton, {
534
+ })), /* @__PURE__ */ React5.createElement(IconButton, {
330
535
  icon: "ph--trash--fill",
331
536
  iconOnly: true,
332
537
  label: t("select option delete"),
333
538
  onClick: () => handleDelete(item.id)
334
- })))))), /* @__PURE__ */ React4.createElement("div", {
539
+ })))))), /* @__PURE__ */ React5.createElement("div", {
335
540
  role: "listitem"
336
- }, /* @__PURE__ */ React4.createElement("div", {
541
+ }, /* @__PURE__ */ React5.createElement("div", {
337
542
  role: "button",
338
543
  className: mx2("p-1 plb-2", "flex flex-col", "cursor-pointer", "hover:bg-hoverOverlay"),
339
544
  onClick: handleAdd
340
- }, /* @__PURE__ */ React4.createElement("div", {
545
+ }, /* @__PURE__ */ React5.createElement("div", {
341
546
  className: "flex items-center gap-1"
342
- }, /* @__PURE__ */ React4.createElement(Icon2, {
547
+ }, /* @__PURE__ */ React5.createElement(Icon2, {
343
548
  icon: "ph--plus--regular"
344
- }), /* @__PURE__ */ React4.createElement("span", {
549
+ }), /* @__PURE__ */ React5.createElement("span", {
345
550
  className: "text-sm"
346
551
  }, t("select option add")))))))));
347
552
  };
348
553
 
349
554
  // packages/ui/react-ui-form/src/components/Form/custom/TupleInput.tsx
350
- import React5 from "react";
555
+ import React6 from "react";
351
556
  import { Input as Input4 } from "@dxos/react-ui";
352
557
  import { mx as mx3 } from "@dxos/react-ui-theme";
353
558
  import { safeParseFloat as safeParseFloat2 } from "@dxos/util";
@@ -360,13 +565,13 @@ var gridCols = [
360
565
  var TupleInput = ({ binding, type, label, disabled, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
361
566
  const { status, error } = getStatus();
362
567
  const values = getValue3() ?? {};
363
- return /* @__PURE__ */ React5.createElement(Input4.Root, {
568
+ return /* @__PURE__ */ React6.createElement(Input4.Root, {
364
569
  validationValence: status
365
- }, /* @__PURE__ */ React5.createElement(InputHeader, {
570
+ }, /* @__PURE__ */ React6.createElement(InputHeader, {
366
571
  error
367
- }, /* @__PURE__ */ React5.createElement(Input4.Label, null, label)), /* @__PURE__ */ React5.createElement("div", {
572
+ }, /* @__PURE__ */ React6.createElement(Input4.Label, null, label)), /* @__PURE__ */ React6.createElement("div", {
368
573
  className: mx3("grid gap-2", gridCols[binding.length - 1])
369
- }, binding.map((prop) => /* @__PURE__ */ React5.createElement(Input4.TextInput, {
574
+ }, binding.map((prop) => /* @__PURE__ */ React6.createElement(Input4.TextInput, {
370
575
  key: prop,
371
576
  type: "number",
372
577
  disabled,
@@ -380,68 +585,69 @@ var TupleInput = ({ binding, type, label, disabled, getStatus, getValue: getValu
380
585
  };
381
586
 
382
587
  // packages/ui/react-ui-form/src/components/Form/Defaults.tsx
383
- import React6 from "react";
588
+ import React7 from "react";
384
589
  import { Input as Input5, Select } from "@dxos/react-ui";
385
590
  var TextInput = ({ type, label, inputOnly, disabled, placeholder, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
386
591
  const { status, error } = getStatus();
387
- return /* @__PURE__ */ React6.createElement(Input5.Root, {
592
+ return /* @__PURE__ */ React7.createElement(Input5.Root, {
388
593
  validationValence: status
389
- }, !inputOnly && /* @__PURE__ */ React6.createElement(InputHeader, {
594
+ }, !inputOnly && /* @__PURE__ */ React7.createElement(InputHeader, {
390
595
  error
391
- }, /* @__PURE__ */ React6.createElement(Input5.Label, null, label)), /* @__PURE__ */ React6.createElement(Input5.TextInput, {
596
+ }, /* @__PURE__ */ React7.createElement(Input5.Label, null, label)), /* @__PURE__ */ React7.createElement(Input5.TextInput, {
392
597
  disabled,
393
598
  placeholder,
394
599
  value: getValue3() ?? "",
395
600
  onChange: (event) => onValueChange(type, event.target.value),
396
601
  onBlur
397
- }), inputOnly && /* @__PURE__ */ React6.createElement(Input5.Validation, null, error));
602
+ }), inputOnly && /* @__PURE__ */ React7.createElement(Input5.Validation, null, error));
398
603
  };
399
604
  var NumberInput = ({ type, label, inputOnly, disabled, placeholder, getStatus, getValue: getValue3, onValueChange, onBlur }) => {
400
605
  const { status, error } = getStatus();
401
- return /* @__PURE__ */ React6.createElement(Input5.Root, {
606
+ return /* @__PURE__ */ React7.createElement(Input5.Root, {
402
607
  validationValence: status
403
- }, !inputOnly && /* @__PURE__ */ React6.createElement(InputHeader, {
608
+ }, !inputOnly && /* @__PURE__ */ React7.createElement(InputHeader, {
404
609
  error
405
- }, /* @__PURE__ */ React6.createElement(Input5.Label, null, label)), /* @__PURE__ */ React6.createElement(Input5.TextInput, {
610
+ }, /* @__PURE__ */ React7.createElement(Input5.Label, null, label)), /* @__PURE__ */ React7.createElement(Input5.TextInput, {
406
611
  type: "number",
407
612
  disabled,
408
613
  placeholder,
409
614
  value: getValue3() ?? 0,
410
615
  onChange: (event) => onValueChange(type, event.target.value),
411
616
  onBlur
412
- }), inputOnly && /* @__PURE__ */ React6.createElement(Input5.DescriptionAndValidation, null, error));
617
+ }), inputOnly && /* @__PURE__ */ React7.createElement(Input5.DescriptionAndValidation, null, error));
413
618
  };
414
619
  var BooleanInput = ({ type, label, inputOnly, getStatus, getValue: getValue3, onValueChange }) => {
415
620
  const { status, error } = getStatus();
416
- return /* @__PURE__ */ React6.createElement(Input5.Root, {
621
+ const checked = Boolean(getValue3());
622
+ return /* @__PURE__ */ React7.createElement(Input5.Root, {
417
623
  validationValence: status
418
- }, !inputOnly && /* @__PURE__ */ React6.createElement(InputHeader, {
624
+ }, !inputOnly && /* @__PURE__ */ React7.createElement(InputHeader, {
419
625
  error
420
- }, /* @__PURE__ */ React6.createElement(Input5.Label, null, label)), /* @__PURE__ */ React6.createElement(Input5.Switch, {
421
- checked: getValue3(),
626
+ }, /* @__PURE__ */ React7.createElement(Input5.Label, null, label)), /* @__PURE__ */ React7.createElement(Input5.Switch, {
627
+ checked,
422
628
  onCheckedChange: (value) => onValueChange(type, value)
423
- }), inputOnly && /* @__PURE__ */ React6.createElement(Input5.DescriptionAndValidation, null, error));
629
+ }), inputOnly && /* @__PURE__ */ React7.createElement(Input5.DescriptionAndValidation, null, error));
424
630
  };
425
631
  var SelectInput = ({ type, label, inputOnly, disabled, placeholder, options, getStatus, getValue: getValue3, onValueChange }) => {
426
632
  const { status, error } = getStatus();
427
- return /* @__PURE__ */ React6.createElement(Input5.Root, {
633
+ return /* @__PURE__ */ React7.createElement(Input5.Root, {
428
634
  validationValence: status
429
- }, !inputOnly && /* @__PURE__ */ React6.createElement(InputHeader, {
635
+ }, !inputOnly && /* @__PURE__ */ React7.createElement(InputHeader, {
430
636
  error
431
- }, /* @__PURE__ */ React6.createElement(Input5.Label, null, label)), /* @__PURE__ */ React6.createElement(Select.Root, {
637
+ }, /* @__PURE__ */ React7.createElement(Input5.Label, null, label)), /* @__PURE__ */ React7.createElement(Select.Root, {
432
638
  value: getValue3(),
433
639
  onValueChange: (value) => onValueChange(type, value)
434
- }, /* @__PURE__ */ React6.createElement(Select.TriggerButton, {
640
+ }, /* @__PURE__ */ React7.createElement(Select.TriggerButton, {
435
641
  classNames: "is-full",
436
642
  disabled,
437
643
  placeholder
438
- }), /* @__PURE__ */ React6.createElement(Select.Portal, null, /* @__PURE__ */ React6.createElement(Select.Content, null, /* @__PURE__ */ React6.createElement(Select.Viewport, null, options?.map(({ value, label: label2 }) => (
644
+ }), /* @__PURE__ */ React7.createElement(Select.Portal, null, /* @__PURE__ */ React7.createElement(Select.Content, null, /* @__PURE__ */ React7.createElement(Select.Viewport, null, options?.map(({ value, label: label2 }) => (
439
645
  // NOTE: Numeric values are converted to and from strings.
440
- /* @__PURE__ */ React6.createElement(Select.Option, {
646
+ /* @__PURE__ */ React7.createElement(Select.Option, {
441
647
  key: String(value),
442
648
  value: String(value)
443
649
  }, label2 ?? String(value))
444
- )))))), inputOnly && /* @__PURE__ */ React6.createElement(Input5.DescriptionAndValidation, null, error));
650
+ )))))), inputOnly && /* @__PURE__ */ React7.createElement(Input5.DescriptionAndValidation, null, error));
445
651
  };
446
652
 
447
653
  // packages/ui/react-ui-form/src/components/Form/Form.tsx
@@ -452,219 +658,12 @@ import { mx as mx7 } from "@dxos/react-ui-theme";
452
658
  import React8 from "react";
453
659
  import { IconButton as IconButton2, useTranslation as useTranslation3 } from "@dxos/react-ui";
454
660
  import { mx as mx4 } from "@dxos/react-ui-theme";
455
-
456
- // packages/ui/react-ui-form/src/components/Form/FormContext.tsx
457
- import React7, { createContext, useContext } from "react";
458
- import { raise } from "@dxos/debug";
459
- import { getValue as getValue2 } from "@dxos/echo-schema";
460
- import { createJsonPath as createJsonPath2 } from "@dxos/effect";
461
-
462
- // packages/ui/react-ui-form/src/hooks/useForm.ts
463
- import { useCallback as useCallback2, useEffect as useEffect2, useMemo, useState as useState2 } from "react";
464
- import { getValue, setValue } from "@dxos/echo-schema";
465
- import { AST, createJsonPath, fromEffectValidationPath } from "@dxos/effect";
466
- import { invariant } from "@dxos/invariant";
467
- import { log } from "@dxos/log";
468
- import { validateSchema } from "@dxos/schema";
469
- var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-form/src/hooks/useForm.ts";
470
- var useForm = ({ schema, initialValues, onValuesChanged, onValidate, onValid, onSave }) => {
471
- invariant(AST.isTypeLiteral(schema.ast), void 0, {
472
- F: __dxlog_file,
473
- L: 91,
474
- S: void 0,
475
- A: [
476
- "AST.isTypeLiteral(schema.ast)",
477
- ""
478
- ]
479
- });
480
- const [values, setValues] = useState2(initialValues);
481
- useEffect2(() => {
482
- setValues(initialValues);
483
- }, [
484
- initialValues
485
- ]);
486
- const [touched, setTouched] = useState2(createKeySet(initialValues, false));
487
- const [changed, setChanged] = useState2(createKeySet(initialValues, false));
488
- const [errors, setErrors] = useState2({});
489
- const [saving, setSaving] = useState2(false);
490
- const validate = useCallback2((values2) => {
491
- let errors2 = validateSchema(schema, values2) ?? [];
492
- if (errors2.length === 0 && onValidate) {
493
- const validatedValues = values2;
494
- errors2 = onValidate(validatedValues) ?? [];
495
- }
496
- setErrors(flatMap(errors2));
497
- const valid = errors2.length === 0;
498
- return valid;
499
- }, [
500
- schema,
501
- onValidate
502
- ]);
503
- const [schemaDirty, setSchemaDirty] = useState2(false);
504
- useEffect2(() => setSchemaDirty(true), [
505
- schema
506
- ]);
507
- useEffect2(() => {
508
- if (schemaDirty) {
509
- validate(values);
510
- setSchemaDirty(false);
511
- }
512
- }, [
513
- values,
514
- schemaDirty
515
- ]);
516
- const canSave = useMemo(() => !saving && // Check if any error paths that are touched have errors
517
- !Object.entries(touched).some(([path, isTouched]) => isTouched && Object.keys(errors).some((errorPath) => errorPath === path || errorPath.startsWith(`${path}.`) || errorPath.startsWith(`${path}[`))), [
518
- touched,
519
- errors,
520
- saving
521
- ]);
522
- const handleSave = useCallback2(async () => {
523
- if (validate(values)) {
524
- setSaving(true);
525
- try {
526
- await onSave?.(values, {
527
- changed
528
- });
529
- } finally {
530
- setSaving(false);
531
- }
532
- }
533
- }, [
534
- values,
535
- validate,
536
- onSave,
537
- changed
538
- ]);
539
- const getStatus = useCallback2((path) => {
540
- const jsonPath = Array.isArray(path) ? createJsonPath(path) : path;
541
- const matchingError = Object.entries(errors).find(([errorPath]) => errorPath === jsonPath || errorPath.startsWith(`${jsonPath}.`) || errorPath.startsWith(`${jsonPath}[`));
542
- return {
543
- status: matchingError ? "error" : void 0,
544
- error: matchingError ? matchingError[1] : void 0
545
- };
546
- }, [
547
- errors
548
- ]);
549
- const getFormValue = (path) => {
550
- return getValue(values, createJsonPath(path));
551
- };
552
- const onValueChange = (path, type, value) => {
553
- const jsonPath = createJsonPath(path);
554
- let parsedValue = value;
555
- try {
556
- if (type === "number") {
557
- parsedValue = parseFloat(value) || 0;
558
- }
559
- } catch (err) {
560
- log.catch(err, void 0, {
561
- F: __dxlog_file,
562
- L: 193,
563
- S: void 0,
564
- C: (f, a) => f(...a)
565
- });
566
- parsedValue = void 0;
567
- }
568
- const newValues = {
569
- ...setValue(values, jsonPath, parsedValue)
570
- };
571
- setValues(newValues);
572
- setChanged((prev) => ({
573
- ...prev,
574
- [jsonPath]: true
575
- }));
576
- onValuesChanged?.(newValues);
577
- const isValid = validate(newValues);
578
- if (isValid && onValid) {
579
- onValid(newValues, {
580
- changed
581
- });
582
- }
583
- };
584
- const onTouched = useCallback2((path) => {
585
- const jsonPath = createJsonPath(path);
586
- setTouched((touched2) => ({
587
- ...touched2,
588
- [jsonPath]: true
589
- }));
590
- validate(values);
591
- }, [
592
- validate,
593
- values
594
- ]);
595
- return {
596
- // State.
597
- values,
598
- errors,
599
- touched,
600
- changed,
601
- canSave,
602
- handleSave,
603
- // Field utils.
604
- getStatus,
605
- getValue: getFormValue,
606
- onValueChange,
607
- onTouched
608
- };
609
- };
610
- var createKeySet = (obj, value) => {
611
- invariant(obj, void 0, {
612
- F: __dxlog_file,
613
- L: 235,
614
- S: void 0,
615
- A: [
616
- "obj",
617
- ""
618
- ]
619
- });
620
- return Object.keys(obj).reduce((acc, key) => ({
621
- ...acc,
622
- [key]: value
623
- }), {});
624
- };
625
- var flatMap = (errors) => {
626
- return errors.reduce((result, { path, message }) => {
627
- const jsonPath = fromEffectValidationPath(path);
628
- if (!(jsonPath in result)) {
629
- result[jsonPath] = message;
630
- }
631
- return result;
632
- }, {});
633
- };
634
-
635
- // packages/ui/react-ui-form/src/components/Form/FormContext.tsx
636
- var FormContext = /* @__PURE__ */ createContext(void 0);
637
- var useFormContext = () => {
638
- return useContext(FormContext) ?? raise(new Error("Missing FormContext"));
639
- };
640
- var useFormValues = (path = []) => {
641
- const { values: formValues } = useFormContext();
642
- const jsonPath = createJsonPath2(path);
643
- return getValue2(formValues, jsonPath);
644
- };
645
- var useInputProps = (path = []) => {
646
- const { getStatus, getValue: getFormValue, onValueChange, onTouched } = useFormContext();
647
- return {
648
- getStatus: () => getStatus(path),
649
- getValue: () => getFormValue(path),
650
- onValueChange: (type, value) => onValueChange(path, type, value),
651
- onBlur: () => onTouched(path)
652
- };
653
- };
654
- var FormProvider = ({ children, ...formOptions }) => {
655
- const formHandler = useForm(formOptions);
656
- return /* @__PURE__ */ React7.createElement(FormContext.Provider, {
657
- value: formHandler
658
- }, children);
659
- };
660
-
661
- // packages/ui/react-ui-form/src/components/Form/FormActions.tsx
662
661
  var FormActions = ({ onCancel, readonly }) => {
663
662
  const { t } = useTranslation3(translationKey);
664
663
  const { canSave, handleSave } = useFormContext();
665
664
  return /* @__PURE__ */ React8.createElement("div", {
666
665
  role: "none",
667
- className: "flex justify-center"
666
+ className: "mt-2 flex justify-center"
668
667
  }, /* @__PURE__ */ React8.createElement("div", {
669
668
  role: "none",
670
669
  className: mx4(onCancel && !readonly && "grid grid-cols-2 gap-2")
@@ -942,7 +941,7 @@ var FormFields = ({ schema, path, filter, sort, readonly, lookupComponent, Custo
942
941
  ]);
943
942
  return /* @__PURE__ */ React10.createElement("div", {
944
943
  role: "form",
945
- className: mx6("flex flex-col w-full gap-2 py-2")
944
+ className: mx6("flex flex-col w-full gap-2")
946
945
  }, properties.map((property) => {
947
946
  return /* @__PURE__ */ React10.createElement(FormField, {
948
947
  key: property.name,
@@ -1519,6 +1518,7 @@ export {
1519
1518
  parseValue,
1520
1519
  pathNotUniqueError,
1521
1520
  translations_default as translations,
1522
- useForm
1521
+ useForm,
1522
+ useFormValues
1523
1523
  };
1524
1524
  //# sourceMappingURL=index.mjs.map