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