@dxos/react-ui-form 0.8.4-main.3eb6e50203 → 0.8.4-main.3fbcb4aa9b
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.
- package/README.md +0 -1
- package/dist/lib/browser/index.mjs +1134 -682
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/translations.mjs +82 -0
- package/dist/lib/browser/translations.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1134 -682
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/translations.mjs +84 -0
- package/dist/lib/node-esm/translations.mjs.map +7 -0
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +3 -3
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +63 -61
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +44 -16
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +75 -73
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField.d.ts +11 -5
- package/dist/types/src/components/Form/FormField.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormFieldComponent.d.ts +8 -7
- package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts +2 -2
- package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormFieldSet.d.ts +6 -10
- package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/ArrayField.d.ts +4 -4
- package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/BooleanField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/GeoPointField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/MarkdownField.d.ts +8 -1
- package/dist/types/src/components/Form/fields/MarkdownField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/MarkdownField.stories.d.ts +84 -0
- package/dist/types/src/components/Form/fields/MarkdownField.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/NumberField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/RefField.d.ts +15 -7
- package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/SelectField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/SelectOptionField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts +63 -61
- package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/TextAreaField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/TextField.d.ts +1 -1
- package/dist/types/src/components/Form/fields/TextField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/TupleField.d.ts.map +1 -1
- package/dist/types/src/components/ObjectForm/ObjectForm.d.ts +9 -0
- package/dist/types/src/components/ObjectForm/ObjectForm.d.ts.map +1 -0
- package/dist/types/src/components/ObjectForm/index.d.ts +2 -0
- package/dist/types/src/components/ObjectForm/index.d.ts.map +1 -0
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +26 -4
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +63 -61
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
- package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts +9 -0
- package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts.map +1 -0
- package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts +127 -0
- package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts.map +1 -0
- package/dist/types/src/components/ObjectProperties/ObjectProperties.test.d.ts +2 -0
- package/dist/types/src/components/ObjectProperties/ObjectProperties.test.d.ts.map +1 -0
- package/dist/types/src/components/ObjectProperties/index.d.ts +2 -0
- package/dist/types/src/components/ObjectProperties/index.d.ts.map +1 -0
- package/dist/types/src/components/Settings/Settings.d.ts +28 -33
- package/dist/types/src/components/Settings/Settings.d.ts.map +1 -1
- package/dist/types/src/components/Settings/Settings.stories.d.ts +64 -61
- package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -1
- package/dist/types/src/components/Settings/SettingsFieldSet.d.ts +43 -0
- package/dist/types/src/components/Settings/SettingsFieldSet.d.ts.map +1 -0
- package/dist/types/src/components/Settings/SettingsItem.d.ts +16 -0
- package/dist/types/src/components/Settings/SettingsItem.d.ts.map +1 -0
- package/dist/types/src/components/Settings/SettingsSelectField.d.ts +18 -0
- package/dist/types/src/components/Settings/SettingsSelectField.d.ts.map +1 -0
- package/dist/types/src/components/Settings/index.d.ts +2 -0
- package/dist/types/src/components/Settings/index.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +6 -6
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +67 -64
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +2 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +4 -4
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -1
- package/dist/types/src/hooks/useKeyHandler.d.ts +2 -1
- package/dist/types/src/hooks/useKeyHandler.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +0 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +63 -61
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/util/field-type.d.ts +19 -0
- package/dist/types/src/util/field-type.d.ts.map +1 -0
- package/dist/types/src/util/field.d.ts.map +1 -1
- package/dist/types/src/util/formatting.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +1 -1
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/properties.d.ts.map +1 -1
- package/dist/types/src/util/refs.d.ts +1 -0
- package/dist/types/src/util/refs.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +42 -42
- package/src/components/FieldEditor/FieldEditor.stories.tsx +6 -8
- package/src/components/FieldEditor/FieldEditor.test.tsx +0 -1
- package/src/components/FieldEditor/FieldEditor.tsx +6 -6
- package/src/components/Form/AUDIT.md +17 -0
- package/src/components/Form/Form.stories.tsx +29 -44
- package/src/components/Form/Form.tsx +101 -54
- package/src/components/Form/FormField.tsx +39 -19
- package/src/components/Form/FormFieldComponent.stories.tsx +4 -4
- package/src/components/Form/FormFieldComponent.tsx +11 -11
- package/src/components/Form/FormFieldSet.tsx +121 -104
- package/src/components/Form/fields/ArrayField.tsx +10 -9
- package/src/components/Form/fields/GeoPointField.tsx +8 -7
- package/src/components/Form/fields/MarkdownField.stories.tsx +206 -0
- package/src/components/Form/fields/MarkdownField.tsx +135 -45
- package/src/components/Form/fields/RefField.tsx +85 -45
- package/src/components/Form/fields/SelectField.tsx +1 -1
- package/src/components/Form/fields/SelectOptionField.stories.tsx +10 -8
- package/src/components/Form/fields/SelectOptionField.tsx +13 -13
- package/src/components/Form/fields/TextField.tsx +2 -2
- package/src/components/Form/fields/TupleField.tsx +1 -1
- package/src/components/ObjectForm/ObjectForm.tsx +114 -0
- package/src/components/ObjectForm/index.ts +5 -0
- package/src/components/ObjectPicker/ObjectPicker.stories.tsx +16 -18
- package/src/components/ObjectPicker/ObjectPicker.tsx +92 -65
- package/src/components/ObjectProperties/ObjectProperties.stories.tsx +362 -0
- package/src/components/ObjectProperties/ObjectProperties.test.tsx +101 -0
- package/src/components/ObjectProperties/ObjectProperties.tsx +141 -0
- package/src/components/ObjectProperties/index.ts +5 -0
- package/src/components/Settings/AUDIT.md +52 -0
- package/src/components/Settings/Settings.stories.tsx +69 -21
- package/src/components/Settings/Settings.tsx +33 -187
- package/src/components/Settings/SettingsFieldSet.tsx +257 -0
- package/src/components/Settings/SettingsItem.tsx +58 -0
- package/src/components/Settings/SettingsSelectField.tsx +59 -0
- package/src/components/Settings/index.ts +2 -0
- package/src/components/ViewEditor/ViewEditor.stories.tsx +21 -19
- package/src/components/ViewEditor/ViewEditor.test.tsx +2 -2
- package/src/components/ViewEditor/ViewEditor.tsx +87 -50
- package/src/components/index.ts +2 -0
- package/src/components/testing.tsx +23 -12
- package/src/hooks/useFormHandler.ts +42 -24
- package/src/hooks/useKeyHandler.tsx +7 -7
- package/src/index.ts +0 -4
- package/src/translations.ts +66 -63
- package/src/util/field-type.ts +60 -0
- package/src/util/index.ts +1 -1
- package/src/util/refs.ts +7 -3
- package/dist/types/src/components/Form/Form-old.stories.d.ts +0 -131
- package/dist/types/src/components/Form/Form-old.stories.d.ts.map +0 -1
- package/dist/types/src/util/echo.d.ts +0 -6
- package/dist/types/src/util/echo.d.ts.map +0 -1
- package/src/components/Form/Form-old.stories.tsx +0 -314
- package/src/util/echo.ts +0 -25
|
@@ -7,22 +7,13 @@ import type * as Schema from 'effect/Schema';
|
|
|
7
7
|
import type * as SchemaAST from 'effect/SchemaAST';
|
|
8
8
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
9
9
|
|
|
10
|
-
import { Obj } from '@dxos/echo';
|
|
11
10
|
import { type AnyProperties } from '@dxos/echo/internal';
|
|
12
|
-
import {
|
|
13
|
-
type JsonPath,
|
|
14
|
-
createJsonPath,
|
|
15
|
-
fromEffectValidationPath,
|
|
16
|
-
getValue as getValue$,
|
|
17
|
-
setValue as setValue$,
|
|
18
|
-
} from '@dxos/effect';
|
|
11
|
+
import { type JsonPath, createJsonPath, fromEffectValidationPath, getValue as getValue$ } from '@dxos/effect';
|
|
19
12
|
import { log } from '@dxos/log';
|
|
20
13
|
import { useDefaultValue } from '@dxos/react-ui';
|
|
21
14
|
import { type ValidationError, validateSchema } from '@dxos/schema';
|
|
22
15
|
import { type MaybePromise } from '@dxos/util';
|
|
23
16
|
|
|
24
|
-
import { setValueEchoAware } from '../util';
|
|
25
|
-
|
|
26
17
|
export type FormFieldStatus = {
|
|
27
18
|
status?: 'error';
|
|
28
19
|
error?: string;
|
|
@@ -139,11 +130,15 @@ export const useFormHandler = <T extends AnyProperties>({
|
|
|
139
130
|
prop: valuesProp,
|
|
140
131
|
defaultProp: defaultValues,
|
|
141
132
|
onChange: () => {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
133
|
+
// Only reset when controlled: parent passed new values.
|
|
134
|
+
// Uncontrolled updates come from setValues in onValueChange.
|
|
135
|
+
if (valuesProp !== undefined) {
|
|
136
|
+
setValues(valuesProp ?? defaultValues);
|
|
137
|
+
setChanged({});
|
|
138
|
+
setTouched({});
|
|
139
|
+
setErrors({});
|
|
140
|
+
setSaving(false);
|
|
141
|
+
}
|
|
147
142
|
},
|
|
148
143
|
});
|
|
149
144
|
// TODO(wittjosiah): Upgrade @radix-ui/react-use-controllable-state.
|
|
@@ -259,6 +254,7 @@ export const useFormHandler = <T extends AnyProperties>({
|
|
|
259
254
|
log('onValueChange', { path, value });
|
|
260
255
|
|
|
261
256
|
const jsonPath = createJsonPath(path);
|
|
257
|
+
const pathArray = path;
|
|
262
258
|
let parsedValue = value as any;
|
|
263
259
|
try {
|
|
264
260
|
if (type._tag === 'NumberKeyword') {
|
|
@@ -269,24 +265,25 @@ export const useFormHandler = <T extends AnyProperties>({
|
|
|
269
265
|
parsedValue = undefined;
|
|
270
266
|
}
|
|
271
267
|
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
-
const newValues = Obj.isObject(values)
|
|
275
|
-
? (setValueEchoAware(values, jsonPath, parsedValue), values)
|
|
276
|
-
: { ...setValue$(values, jsonPath, parsedValue) };
|
|
277
|
-
setValues(newValues);
|
|
268
|
+
// Create merged values for validation and callback. Form never mutates; parent applies via onValuesChanged.
|
|
269
|
+
const newValues = mergeAtPath(values, pathArray as (string | number)[], parsedValue) as Partial<T>;
|
|
278
270
|
|
|
279
271
|
// TODO(burdon): Check value has changed from original.
|
|
280
272
|
const newChanged = { [jsonPath]: true };
|
|
281
|
-
setChanged({ ...
|
|
273
|
+
setChanged((prev) => ({ ...prev, ...newChanged }));
|
|
282
274
|
|
|
283
275
|
// Validate.
|
|
284
276
|
const isValid = validate(newValues);
|
|
285
277
|
|
|
286
|
-
//
|
|
278
|
+
// Notify parent; parent is responsible for applying the change.
|
|
287
279
|
onValuesChanged?.(newValues, { isValid, changed: newChanged });
|
|
280
|
+
|
|
281
|
+
// For uncontrolled mode, update internal state. Controlled mode relies on parent to pass new values.
|
|
282
|
+
if (valuesProp === undefined) {
|
|
283
|
+
setValues(newValues);
|
|
284
|
+
}
|
|
288
285
|
},
|
|
289
|
-
[values,
|
|
286
|
+
[values, validate, onValuesChanged, valuesProp],
|
|
290
287
|
);
|
|
291
288
|
|
|
292
289
|
const onBlur = useCallback(
|
|
@@ -347,6 +344,27 @@ export const useFormHandler = <T extends AnyProperties>({
|
|
|
347
344
|
);
|
|
348
345
|
};
|
|
349
346
|
|
|
347
|
+
/**
|
|
348
|
+
* Creates a new object with value at path. Does not mutate source.
|
|
349
|
+
* Preserves arrays when path segments are numeric indices.
|
|
350
|
+
*/
|
|
351
|
+
const mergeAtPath = (obj: any, path: readonly (string | number)[], value: any): any => {
|
|
352
|
+
if (path.length === 0) {
|
|
353
|
+
return value;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const [head, ...rest] = path;
|
|
357
|
+
const child = rest.length === 0 ? value : mergeAtPath(obj?.[head], rest, value);
|
|
358
|
+
|
|
359
|
+
if (Array.isArray(obj)) {
|
|
360
|
+
const copy = [...obj];
|
|
361
|
+
copy[head as number] = child;
|
|
362
|
+
return copy;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return { ...obj, [head]: child };
|
|
366
|
+
};
|
|
367
|
+
|
|
350
368
|
const flatMap = (errors: ValidationError[]) => {
|
|
351
369
|
return errors.reduce(
|
|
352
370
|
(result, { path, message }) => {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { useCallback,
|
|
5
|
+
import { type RefObject, useCallback, useLayoutEffect } from 'react';
|
|
6
6
|
|
|
7
7
|
import { addEventListener } from '@dxos/async';
|
|
8
8
|
|
|
@@ -11,7 +11,7 @@ import { type FormHandler } from './useFormHandler';
|
|
|
11
11
|
/**
|
|
12
12
|
* Key handler.
|
|
13
13
|
*/
|
|
14
|
-
export const useKeyHandler = (
|
|
14
|
+
export const useKeyHandler = (elRef: RefObject<HTMLDivElement | null>, form: FormHandler<any>) => {
|
|
15
15
|
const handleKeyDown = useCallback(
|
|
16
16
|
(event: KeyboardEvent) => {
|
|
17
17
|
switch (event.key) {
|
|
@@ -26,12 +26,12 @@ export const useKeyHandler = (formElement: HTMLDivElement | null, form: FormHand
|
|
|
26
26
|
[form.isValid, form.canSave, form.onSave, form.autoSave],
|
|
27
27
|
);
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
useLayoutEffect(() => {
|
|
30
|
+
const el = elRef.current;
|
|
31
|
+
if (!el) {
|
|
31
32
|
return;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}, [formElement, handleKeyDown]);
|
|
35
|
+
return addEventListener(el, 'keydown', handleKeyDown);
|
|
36
|
+
}, [elRef, handleKeyDown]);
|
|
37
37
|
};
|
package/src/index.ts
CHANGED
package/src/translations.ts
CHANGED
|
@@ -10,79 +10,82 @@ export const translations = [
|
|
|
10
10
|
{
|
|
11
11
|
'en-US': {
|
|
12
12
|
[translationKey]: {
|
|
13
|
-
'fields
|
|
14
|
-
'hidden
|
|
15
|
-
'empty
|
|
13
|
+
'fields.label': 'Fields',
|
|
14
|
+
'hidden-fields.label': 'Hidden Fields',
|
|
15
|
+
'empty-readonly-ref-field.label': '(none)',
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
'
|
|
19
|
-
'field
|
|
20
|
-
'field
|
|
21
|
-
'field format label': 'Type',
|
|
22
|
-
'field path label': 'Field path',
|
|
23
|
-
'add field': 'Add field',
|
|
24
|
-
'delete field': 'Delete field',
|
|
25
|
-
'field limit reached': 'Maximum number of fields reached',
|
|
26
|
-
'boolean input true value': 'Yes',
|
|
27
|
-
'boolean input false value': 'No',
|
|
28
|
-
'show field label': 'Show field',
|
|
29
|
-
'hide field label': 'Hide field',
|
|
30
|
-
'delete field label': 'Delete field',
|
|
31
|
-
'ref field combobox input placeholder': 'Search…',
|
|
32
|
-
'ref field placeholder_one': 'Select…',
|
|
33
|
-
'ref field placeholder_other': 'Select items…',
|
|
17
|
+
'field-property.label': 'Property',
|
|
18
|
+
'field-property.placeholder': 'Property name',
|
|
19
|
+
'field-format.label': 'Type',
|
|
20
|
+
'field-path.label': 'Field path',
|
|
34
21
|
|
|
35
|
-
'
|
|
36
|
-
'
|
|
37
|
-
'
|
|
22
|
+
'add-item.button': 'Add item',
|
|
23
|
+
'add-property-button.label': 'Add property',
|
|
24
|
+
'boolean-input-true.value': 'Yes',
|
|
25
|
+
'boolean-input-false.value': 'No',
|
|
26
|
+
'show-field.label': 'Show field',
|
|
27
|
+
'hide-field.label': 'Hide field',
|
|
28
|
+
'delete-field.label': 'Delete field',
|
|
29
|
+
'create-option.label': 'Create',
|
|
30
|
+
'add-tag.label': 'Add tag',
|
|
38
31
|
|
|
39
|
-
'
|
|
40
|
-
'
|
|
32
|
+
'ref-field-combobox-input.placeholder': 'Search…',
|
|
33
|
+
'ref-field.placeholder': 'Select…',
|
|
34
|
+
'ref-field.placeholder_one': 'Select…',
|
|
35
|
+
'ref-field.placeholder_other': 'Select items…',
|
|
41
36
|
|
|
42
|
-
|
|
43
|
-
'
|
|
44
|
-
'
|
|
45
|
-
|
|
46
|
-
'
|
|
47
|
-
'
|
|
48
|
-
'
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
'format
|
|
52
|
-
'format
|
|
53
|
-
'format
|
|
54
|
-
'format
|
|
55
|
-
'format
|
|
56
|
-
'format
|
|
57
|
-
'format
|
|
58
|
-
'format
|
|
59
|
-
'format
|
|
60
|
-
'format
|
|
61
|
-
'format
|
|
62
|
-
'format
|
|
63
|
-
'format
|
|
64
|
-
'format
|
|
65
|
-
'format
|
|
66
|
-
'format
|
|
67
|
-
'format
|
|
68
|
-
'format
|
|
69
|
-
'format
|
|
70
|
-
'format
|
|
37
|
+
'example.placeholder': 'Example',
|
|
38
|
+
'latitude.placeholder': 'Latitude (e.g., 40.7128)',
|
|
39
|
+
'longitude.placeholder': 'Longitude (e.g., -74.0060)',
|
|
40
|
+
|
|
41
|
+
'cancel-button.label': 'Cancel',
|
|
42
|
+
'save-button.label': 'Save',
|
|
43
|
+
'create-text.label': 'Create text',
|
|
44
|
+
|
|
45
|
+
// TypeFormat
|
|
46
|
+
'format.boolean.label': 'Boolean',
|
|
47
|
+
'format.currency.label': 'Currency',
|
|
48
|
+
'format.date.label': 'Date',
|
|
49
|
+
'format.date-time.label': 'DateTime',
|
|
50
|
+
'format.did.label': 'DID',
|
|
51
|
+
'format.duration.label': 'Duration',
|
|
52
|
+
'format.dxn.label': 'DXN',
|
|
53
|
+
'format.email.label': 'Email',
|
|
54
|
+
'format.formula.label': 'Formula',
|
|
55
|
+
'format.hostname.label': 'Hostname',
|
|
56
|
+
'format.integer.label': 'Integer',
|
|
57
|
+
'format.json.label': 'JSON',
|
|
58
|
+
'format.lnglat.label': 'Geopoint',
|
|
59
|
+
'format.markdown.label': 'Markdown',
|
|
60
|
+
'format.percent.label': 'Percent',
|
|
61
|
+
'format.ref.label': 'Reference',
|
|
62
|
+
'format.regex.label': 'RegExp',
|
|
63
|
+
'format.string.label': 'String',
|
|
64
|
+
'format.single-select.label': 'Select',
|
|
65
|
+
'format.multi-select.label': 'Multi-select',
|
|
66
|
+
'format.text.label': 'Long text',
|
|
67
|
+
'format.time.label': 'Time',
|
|
68
|
+
'format.timestamp.label': 'Timestamp',
|
|
69
|
+
'format.user.label': 'User',
|
|
70
|
+
'format.number.label': 'Number',
|
|
71
|
+
'format.uri.label': 'URI',
|
|
72
|
+
'format.url.label': 'URL',
|
|
73
|
+
'format.uuid.label': 'UUID',
|
|
71
74
|
|
|
72
75
|
// GeoPointField.
|
|
73
|
-
'latitude
|
|
74
|
-
'longitude
|
|
76
|
+
'latitude.label': 'Latitude',
|
|
77
|
+
'longitude.label': 'Longitude',
|
|
75
78
|
|
|
76
79
|
// SelectOptionsField.
|
|
77
|
-
'select
|
|
78
|
-
'select
|
|
79
|
-
'select
|
|
80
|
-
'select
|
|
81
|
-
'select option add': 'Add option',
|
|
80
|
+
'select-option.label': 'Label',
|
|
81
|
+
'select-option-label.placeholder': 'Option label',
|
|
82
|
+
'select-option-delete.button': 'Delete',
|
|
83
|
+
'select-option-add.button': 'Add option',
|
|
82
84
|
|
|
83
85
|
// System schema message.
|
|
84
|
-
'system
|
|
85
|
-
'system
|
|
86
|
+
'system-schema.title': 'System type',
|
|
87
|
+
'system-schema.description': 'System type: cannot add or remove fields.',
|
|
88
|
+
'remove.button': 'Remove',
|
|
86
89
|
},
|
|
87
90
|
},
|
|
88
91
|
},
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as SchemaAST from 'effect/SchemaAST';
|
|
6
|
+
|
|
7
|
+
import { getAnnotation, isLiteralUnion } from '@dxos/effect';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Detected field type for a schema property.
|
|
11
|
+
*/
|
|
12
|
+
export type SettingsFieldType = 'boolean' | 'string' | 'number' | 'select' | 'struct';
|
|
13
|
+
|
|
14
|
+
export type SelectOption = {
|
|
15
|
+
value: string | number;
|
|
16
|
+
label?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Detect the field type from an Effect Schema AST node.
|
|
21
|
+
*/
|
|
22
|
+
export const detectFieldType = (ast: SchemaAST.AST): SettingsFieldType | undefined => {
|
|
23
|
+
if (isLiteralUnion(ast)) {
|
|
24
|
+
return 'select';
|
|
25
|
+
}
|
|
26
|
+
if (SchemaAST.isTypeLiteral(ast)) {
|
|
27
|
+
return 'struct';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
switch (ast._tag) {
|
|
31
|
+
case 'BooleanKeyword':
|
|
32
|
+
return 'boolean';
|
|
33
|
+
case 'StringKeyword':
|
|
34
|
+
return 'string';
|
|
35
|
+
case 'NumberKeyword':
|
|
36
|
+
return 'number';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return undefined;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Extract select options from a literal union AST node.
|
|
44
|
+
* Reads title annotations from individual literals for labels.
|
|
45
|
+
*/
|
|
46
|
+
export const getSelectOptionsFromAst = (ast: SchemaAST.AST): SelectOption[] | undefined => {
|
|
47
|
+
if (!isLiteralUnion(ast)) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (ast as SchemaAST.Union<SchemaAST.Literal>).types.flatMap((literalNode) => {
|
|
52
|
+
const value = literalNode.literal;
|
|
53
|
+
if (typeof value !== 'string' && typeof value !== 'number') {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
const title = getAnnotation<string>(SchemaAST.TitleAnnotationId, false)(literalNode);
|
|
57
|
+
const label = title ?? (typeof value === 'string' ? value.charAt(0).toUpperCase() + value.slice(1) : String(value));
|
|
58
|
+
return [{ value, label }];
|
|
59
|
+
});
|
|
60
|
+
};
|
package/src/util/index.ts
CHANGED
package/src/util/refs.ts
CHANGED
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
import type * as SchemaAST from 'effect/SchemaAST';
|
|
6
6
|
|
|
7
7
|
import { Ref } from '@dxos/echo';
|
|
8
|
-
import {
|
|
8
|
+
import { type ReferenceAnnotationValue, ReferenceAnnotationId } from '@dxos/echo/internal';
|
|
9
|
+
import { findAnnotation, getArrayElementType, isArrayType } from '@dxos/effect';
|
|
9
10
|
|
|
10
11
|
type RefProps = {
|
|
11
12
|
ast: SchemaAST.AST;
|
|
12
13
|
isArray: boolean;
|
|
14
|
+
typename?: string;
|
|
13
15
|
};
|
|
14
16
|
|
|
15
17
|
export const getRefProps = (ast: SchemaAST.AST): RefProps | undefined => {
|
|
@@ -18,14 +20,16 @@ export const getRefProps = (ast: SchemaAST.AST): RefProps | undefined => {
|
|
|
18
20
|
const elementType = getArrayElementType(ast);
|
|
19
21
|
if (elementType) {
|
|
20
22
|
if (Ref.isRefType(elementType)) {
|
|
21
|
-
|
|
23
|
+
const typename = findAnnotation<ReferenceAnnotationValue>(elementType, ReferenceAnnotationId)?.typename;
|
|
24
|
+
return { ast: elementType, isArray: true, typename };
|
|
22
25
|
}
|
|
23
26
|
}
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
// Direct reference.
|
|
27
30
|
if (Ref.isRefType(ast)) {
|
|
28
|
-
|
|
31
|
+
const typename = findAnnotation<ReferenceAnnotationValue>(ast, ReferenceAnnotationId)?.typename;
|
|
32
|
+
return { ast, isArray: false, typename };
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
return undefined;
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { type StoryObj } from '@storybook/react-vite';
|
|
2
|
-
import * as Schema from 'effect/Schema';
|
|
3
|
-
import React from 'react';
|
|
4
|
-
import { type AnyProperties } from '@dxos/echo/internal';
|
|
5
|
-
import { type FormRootProps } from './Form';
|
|
6
|
-
type StoryProps<T extends AnyProperties> = {
|
|
7
|
-
debug?: boolean;
|
|
8
|
-
schema: Schema.Schema.AnyNoContext;
|
|
9
|
-
} & FormRootProps<T>;
|
|
10
|
-
declare const meta: {
|
|
11
|
-
title: string;
|
|
12
|
-
component: any;
|
|
13
|
-
render: <T extends AnyProperties = any>({ debug, schema, values: valuesProp, ...props }: StoryProps<T>) => React.JSX.Element;
|
|
14
|
-
decorators: import("@storybook/react").Decorator[];
|
|
15
|
-
parameters: {
|
|
16
|
-
layout: string;
|
|
17
|
-
translations: [{
|
|
18
|
-
readonly 'en-US': {
|
|
19
|
-
readonly "@dxos/react-ui-form": {
|
|
20
|
-
readonly 'fields label': "Fields";
|
|
21
|
-
readonly 'hidden fields label': "Hidden Fields";
|
|
22
|
-
readonly 'empty readonly ref field label': "(none)";
|
|
23
|
-
readonly 'add property button label': "Add property";
|
|
24
|
-
readonly 'field property label': "Property";
|
|
25
|
-
readonly 'field property placeholder': "Property name";
|
|
26
|
-
readonly 'field format label': "Type";
|
|
27
|
-
readonly 'field path label': "Field path";
|
|
28
|
-
readonly 'add field': "Add field";
|
|
29
|
-
readonly 'delete field': "Delete field";
|
|
30
|
-
readonly 'field limit reached': "Maximum number of fields reached";
|
|
31
|
-
readonly 'boolean input true value': "Yes";
|
|
32
|
-
readonly 'boolean input false value': "No";
|
|
33
|
-
readonly 'show field label': "Show field";
|
|
34
|
-
readonly 'hide field label': "Hide field";
|
|
35
|
-
readonly 'delete field label': "Delete field";
|
|
36
|
-
readonly 'ref field combobox input placeholder': "Search…";
|
|
37
|
-
readonly 'ref field placeholder_one': "Select…";
|
|
38
|
-
readonly 'ref field placeholder_other': "Select items…";
|
|
39
|
-
readonly 'example placeholder': "Example";
|
|
40
|
-
readonly 'latitude placeholder': "Latitude (e.g., 40.7128)";
|
|
41
|
-
readonly 'longitude placeholder': "Longitude (e.g., -74.0060)";
|
|
42
|
-
readonly 'cancel button label': "Cancel";
|
|
43
|
-
readonly 'save button label': "Save";
|
|
44
|
-
readonly 'format boolean': "Boolean";
|
|
45
|
-
readonly 'format currency': "Currency";
|
|
46
|
-
readonly 'format date': "Date";
|
|
47
|
-
readonly 'format date-time': "DateTime";
|
|
48
|
-
readonly 'format did': "DID";
|
|
49
|
-
readonly 'format duration': "Duration";
|
|
50
|
-
readonly 'format dxn': "DXN";
|
|
51
|
-
readonly 'format email': "Email";
|
|
52
|
-
readonly 'format formula': "Formula";
|
|
53
|
-
readonly 'format hostname': "Hostname";
|
|
54
|
-
readonly 'format integer': "Integer";
|
|
55
|
-
readonly 'format json': "JSON";
|
|
56
|
-
readonly 'format lnglat': "Geopoint";
|
|
57
|
-
readonly 'format markdown': "Markdown";
|
|
58
|
-
readonly 'format percent': "Percent";
|
|
59
|
-
readonly 'format ref': "Reference";
|
|
60
|
-
readonly 'format regex': "RegExp";
|
|
61
|
-
readonly 'format string': "String";
|
|
62
|
-
readonly 'format single-select': "Select";
|
|
63
|
-
readonly 'format multi-select': "Multi-select";
|
|
64
|
-
readonly 'format text': "Long text";
|
|
65
|
-
readonly 'format time': "Time";
|
|
66
|
-
readonly 'format timestamp': "Timestamp";
|
|
67
|
-
readonly 'format user': "User";
|
|
68
|
-
readonly 'format number': "Number";
|
|
69
|
-
readonly 'format uri': "URI";
|
|
70
|
-
readonly 'format url': "URL";
|
|
71
|
-
readonly 'format uuid': "UUID";
|
|
72
|
-
readonly 'latitude label': "Latitude";
|
|
73
|
-
readonly 'longitude label': "Longitude";
|
|
74
|
-
readonly 'select option label': "Label";
|
|
75
|
-
readonly 'select option label placeholder': "Option label";
|
|
76
|
-
readonly 'select option color': "Color";
|
|
77
|
-
readonly 'select option delete': "Delete";
|
|
78
|
-
readonly 'select option add': "Add option";
|
|
79
|
-
readonly 'system schema title': "System type";
|
|
80
|
-
readonly 'system schema description': "System type: cannot add or remove fields.";
|
|
81
|
-
};
|
|
82
|
-
};
|
|
83
|
-
}];
|
|
84
|
-
};
|
|
85
|
-
argTypes: {
|
|
86
|
-
readonly: {
|
|
87
|
-
control: "boolean";
|
|
88
|
-
description: string;
|
|
89
|
-
};
|
|
90
|
-
};
|
|
91
|
-
};
|
|
92
|
-
export default meta;
|
|
93
|
-
type Story = StoryObj<typeof meta>;
|
|
94
|
-
export declare const Default: Story;
|
|
95
|
-
export declare const Organization: Story;
|
|
96
|
-
export declare const OrganizationAutoSave: Story;
|
|
97
|
-
export declare const Person: Story;
|
|
98
|
-
declare const ShapeSchema: Schema.mutable<Schema.Struct<{
|
|
99
|
-
shape: Schema.optional<Schema.Union<[Schema.Struct<{
|
|
100
|
-
type: Schema.Literal<["circle"]>;
|
|
101
|
-
radius: Schema.optional<Schema.SchemaClass<number, number, never>>;
|
|
102
|
-
}>, Schema.Struct<{
|
|
103
|
-
type: Schema.Literal<["square"]>;
|
|
104
|
-
size: Schema.optional<Schema.refine<number, typeof Schema.Number>>;
|
|
105
|
-
}>]>>;
|
|
106
|
-
}>>;
|
|
107
|
-
type ShapeSchema = Schema.Schema.Type<typeof ShapeSchema>;
|
|
108
|
-
export declare const DiscriminatedShape: StoryObj<StoryProps<ShapeSchema>>;
|
|
109
|
-
declare const ArraysSchema: Schema.mutable<Schema.Struct<{
|
|
110
|
-
names: Schema.Array$<Schema.filter<typeof Schema.String>>;
|
|
111
|
-
addresses: Schema.Array$<Schema.Struct<{
|
|
112
|
-
street: Schema.optional<Schema.SchemaClass<string, string, never>>;
|
|
113
|
-
city: Schema.optional<Schema.SchemaClass<string, string, never>>;
|
|
114
|
-
zip: Schema.optional<Schema.refine<string, typeof Schema.String>>;
|
|
115
|
-
location: Schema.optional<Schema.Tuple<[Schema.filter<Schema.transform<Schema.SchemaClass<number, number, never>, Schema.filter<Schema.SchemaClass<number, number, never>>>>, Schema.filter<Schema.transform<Schema.SchemaClass<number, number, never>, Schema.filter<Schema.SchemaClass<number, number, never>>>>, Schema.Element<typeof Schema.Number, "?">]>>;
|
|
116
|
-
}>>;
|
|
117
|
-
}>>;
|
|
118
|
-
type ArraysSchema = Schema.Schema.Type<typeof ArraysSchema>;
|
|
119
|
-
export declare const Arrays: StoryObj<StoryProps<ArraysSchema>>;
|
|
120
|
-
declare const TupleSchema: Schema.mutable<Schema.Struct<{
|
|
121
|
-
tuple: Schema.Tuple<[typeof Schema.Number, typeof Schema.String, typeof Schema.Boolean]>;
|
|
122
|
-
geopoint: Schema.Tuple<[Schema.filter<Schema.transform<Schema.SchemaClass<number, number, never>, Schema.filter<Schema.SchemaClass<number, number, never>>>>, Schema.filter<Schema.transform<Schema.SchemaClass<number, number, never>, Schema.filter<Schema.SchemaClass<number, number, never>>>>, Schema.Element<typeof Schema.Number, "?">]>;
|
|
123
|
-
}>>;
|
|
124
|
-
type TupleSchema = Schema.Schema.Type<typeof TupleSchema>;
|
|
125
|
-
export declare const Tuple: StoryObj<StoryProps<TupleSchema>>;
|
|
126
|
-
declare const ColorSchema: Schema.mutable<Schema.Struct<{
|
|
127
|
-
color: Schema.Union<[Schema.Literal<["red"]>, Schema.Literal<["green"]>, Schema.Literal<["blue"]>]>;
|
|
128
|
-
}>>;
|
|
129
|
-
type ColorType = Schema.Schema.Type<typeof ColorSchema>;
|
|
130
|
-
export declare const Enum: StoryObj<StoryProps<ColorType>>;
|
|
131
|
-
//# sourceMappingURL=Form-old.stories.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Form-old.stories.d.ts","sourceRoot":"","sources":["../../../../../src/components/Form/Form-old.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAa,KAAK,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAgC,MAAM,OAAO,CAAC;AAGrD,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AASzD,OAAO,EAAQ,KAAK,aAAa,EAAE,MAAM,QAAQ,CAAC;AAIlD,KAAK,UAAU,CAAC,CAAC,SAAS,aAAa,IAAI;IACzC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC;CACpC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;AAuErB,QAAA,MAAM,IAAI;;eAEgB,GAAG;aAvEP,CAAC,SAAS,aAAa,yDAK1C,UAAU,CAAC,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+EiB,CAAC;AAElC,eAAe,IAAI,CAAC;AAEpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAEnC,eAAO,MAAM,OAAO,EAAE,KAYrB,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,KAU1B,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,KAWlC,CAAC;AAKF,eAAO,MAAM,MAAM,EAAE,KAQpB,CAAC;AAMF,QAAA,MAAM,WAAW;;;;;;;;GAaM,CAAC;AAExB,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,WAAW,CAAC,CAAC;AAE1D,eAAO,MAAM,kBAAkB,EAAE,QAAQ,CAAC,UAAU,CAAC,WAAW,CAAC,CAuBhE,CAAC;AAMF,QAAA,MAAM,YAAY;;;;;;;;GAGK,CAAC;AAExB,KAAK,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,YAAY,CAAC,CAAC;AAE5D,eAAO,MAAM,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,YAAY,CAAC,CAUrD,CAAC;AAOF,QAAA,MAAM,WAAW;;;GAGM,CAAC;AAExB,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,WAAW,CAAC,CAAC;AAE1D,eAAO,MAAM,KAAK,EAAE,QAAQ,CAAC,UAAU,CAAC,WAAW,CAAC,CAUnD,CAAC;AAMF,QAAA,MAAM,WAAW;;GAIM,CAAC;AAExB,KAAK,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,WAAW,CAAC,CAAC;AAExD,eAAO,MAAM,IAAI,EAAE,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC,CAShD,CAAC"}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { type JsonPath } from '@dxos/effect';
|
|
2
|
-
/**
|
|
3
|
-
* Sets a value on an object, using Obj.change() if it's an ECHO object.
|
|
4
|
-
*/
|
|
5
|
-
export declare const setValueEchoAware: <T extends object>(obj: T, path: JsonPath, value: any) => T;
|
|
6
|
-
//# sourceMappingURL=echo.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"echo.d.ts","sourceRoot":"","sources":["../../../../src/util/echo.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,QAAQ,EAA4C,MAAM,cAAc,CAAC;AAEvF;;GAEG;AAOH,eAAO,MAAM,iBAAiB,GAAI,CAAC,SAAS,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,QAAQ,EAAE,OAAO,GAAG,KAAG,CAQxF,CAAC"}
|