@dxos/react-ui-form 0.13.0 → 0.14.0
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/dist/lib/index.mjs +21 -15
- package/dist/lib/index.mjs.map +1 -1
- package/dist/types/src/components/Form.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker.d.ts.map +1 -1
- package/dist/types/src/components/fields/RefField.d.ts +1 -1
- package/dist/types/src/components/fields/RefField.d.ts.map +1 -1
- package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +26 -26
- package/src/components/Form.tsx +8 -1
- package/src/components/ObjectPicker.tsx +2 -0
- package/src/components/fields/RefField.tsx +7 -1
- package/src/hooks/useFormHandler.test.ts +27 -1
- package/src/hooks/useFormHandler.ts +3 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-form",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Data components.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -51,24 +51,24 @@
|
|
|
51
51
|
"@codemirror/view": "^6.43.13",
|
|
52
52
|
"@effect/atom-react": "4.0.0",
|
|
53
53
|
"date-fns": "^3.6.0",
|
|
54
|
-
"@dxos/async": "0.
|
|
55
|
-
"@dxos/echo
|
|
56
|
-
"@dxos/echo": "0.
|
|
57
|
-
"@dxos/echo-
|
|
58
|
-
"@dxos/effect": "0.
|
|
59
|
-
"@dxos/invariant": "0.
|
|
60
|
-
"@dxos/keys": "0.
|
|
61
|
-
"@dxos/
|
|
62
|
-
"@dxos/
|
|
63
|
-
"@dxos/react-
|
|
64
|
-
"@dxos/react-ui-
|
|
65
|
-
"@dxos/react-ui-
|
|
66
|
-
"@dxos/
|
|
67
|
-
"@dxos/
|
|
68
|
-
"@dxos/ui": "0.
|
|
69
|
-
"@dxos/ui
|
|
70
|
-
"@dxos/
|
|
71
|
-
"@dxos/
|
|
54
|
+
"@dxos/async": "0.14.0",
|
|
55
|
+
"@dxos/echo": "0.14.0",
|
|
56
|
+
"@dxos/echo-react": "0.14.0",
|
|
57
|
+
"@dxos/echo-doc": "0.14.0",
|
|
58
|
+
"@dxos/effect": "0.14.0",
|
|
59
|
+
"@dxos/invariant": "0.14.0",
|
|
60
|
+
"@dxos/keys": "0.14.0",
|
|
61
|
+
"@dxos/log": "0.14.0",
|
|
62
|
+
"@dxos/react-hooks": "0.14.0",
|
|
63
|
+
"@dxos/react-ui-editor": "0.14.0",
|
|
64
|
+
"@dxos/react-ui-list": "0.14.0",
|
|
65
|
+
"@dxos/react-ui-query": "0.14.0",
|
|
66
|
+
"@dxos/schema": "0.14.0",
|
|
67
|
+
"@dxos/lit-ui": "0.14.0",
|
|
68
|
+
"@dxos/ui-editor": "0.14.0",
|
|
69
|
+
"@dxos/ui": "0.14.0",
|
|
70
|
+
"@dxos/util": "0.14.0",
|
|
71
|
+
"@dxos/ui-types": "0.14.0"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
74
|
"@storybook/react-vite": "^10.6.0",
|
|
@@ -78,18 +78,18 @@
|
|
|
78
78
|
"react": "~19.2.7",
|
|
79
79
|
"react-dom": "~19.2.7",
|
|
80
80
|
"vite": "^8.2.2",
|
|
81
|
-
"@dxos/react-client": "0.
|
|
82
|
-
"@dxos/react-ui": "0.
|
|
83
|
-
"@dxos/react-ui-syntax-highlighter": "0.
|
|
84
|
-
"@dxos/types": "0.
|
|
85
|
-
"@dxos/ui-theme": "0.
|
|
81
|
+
"@dxos/react-client": "0.14.0",
|
|
82
|
+
"@dxos/react-ui": "0.14.0",
|
|
83
|
+
"@dxos/react-ui-syntax-highlighter": "0.14.0",
|
|
84
|
+
"@dxos/types": "0.14.0",
|
|
85
|
+
"@dxos/ui-theme": "0.14.0"
|
|
86
86
|
},
|
|
87
87
|
"peerDependencies": {
|
|
88
88
|
"effect": "4.0.0",
|
|
89
89
|
"react": "~19.2.7",
|
|
90
90
|
"react-dom": "~19.2.7",
|
|
91
|
-
"@dxos/react-ui": "^0.
|
|
92
|
-
"@dxos/ui-theme": "^0.
|
|
91
|
+
"@dxos/react-ui": "^0.14.0",
|
|
92
|
+
"@dxos/ui-theme": "^0.14.0"
|
|
93
93
|
},
|
|
94
94
|
"publishConfig": {
|
|
95
95
|
"access": "public"
|
package/src/components/Form.tsx
CHANGED
|
@@ -72,7 +72,14 @@ export const FormViewport = Util.composable<HTMLDivElement, FormViewportProps>(
|
|
|
72
72
|
</Panel.Body>
|
|
73
73
|
</Panel.Root>
|
|
74
74
|
) : (
|
|
75
|
-
|
|
75
|
+
// Padded on the block axis as the scrolling form is, so a form in a host that scrolls it still ends a gutter in.
|
|
76
|
+
<Layout.Container
|
|
77
|
+
{...props}
|
|
78
|
+
gutter={gutter ?? defaultGutter ?? 'sm'}
|
|
79
|
+
width={documentWidth}
|
|
80
|
+
padBlock
|
|
81
|
+
ref={forwardedRef}
|
|
82
|
+
>
|
|
76
83
|
{children}
|
|
77
84
|
</Layout.Container>
|
|
78
85
|
);
|
|
@@ -144,6 +144,8 @@ export const ObjectPicker = ({
|
|
|
144
144
|
<Combobox.Root
|
|
145
145
|
items={items}
|
|
146
146
|
loading={loading}
|
|
147
|
+
// Pinned to the field's width: object labels (or URIs) can be long enough to widen the popup past the form.
|
|
148
|
+
positioning={{ sameWidth: true }}
|
|
147
149
|
open={open}
|
|
148
150
|
onOpenChange={handleOpenChange}
|
|
149
151
|
value={value ? [value] : []}
|
|
@@ -32,6 +32,7 @@ export const RefField = ({
|
|
|
32
32
|
presentation,
|
|
33
33
|
getValue,
|
|
34
34
|
onValueChange,
|
|
35
|
+
onBlur,
|
|
35
36
|
createInitialValuePath,
|
|
36
37
|
createFieldMap,
|
|
37
38
|
createOptionIcon,
|
|
@@ -67,10 +68,15 @@ export const RefField = ({
|
|
|
67
68
|
const created = await create(values);
|
|
68
69
|
if (created) {
|
|
69
70
|
onValueChange(type, Ref.make(created));
|
|
71
|
+
onBlur();
|
|
70
72
|
}
|
|
71
73
|
})
|
|
72
74
|
}
|
|
73
|
-
|
|
75
|
+
// A pick is the whole edit, so it commits as a select does; without the blur an auto-saving form never saves it.
|
|
76
|
+
onSelect={(id) => {
|
|
77
|
+
onValueChange(type, id ? Ref.fromURI(URI.make(id)) : undefined);
|
|
78
|
+
onBlur();
|
|
79
|
+
}}
|
|
74
80
|
/>
|
|
75
81
|
);
|
|
76
82
|
};
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
import { act, renderHook } from '@testing-library/react';
|
|
6
6
|
import * as Schema from 'effect/Schema';
|
|
7
7
|
import * as Struct from 'effect/Struct';
|
|
8
|
-
import { describe, test } from 'vitest';
|
|
8
|
+
import { describe, onTestFinished, test } from 'vitest';
|
|
9
|
+
|
|
10
|
+
import { type LogEntry, type LogProcessor, log } from '@dxos/log';
|
|
9
11
|
|
|
10
12
|
import { useFormHandler } from './useFormHandler.ts';
|
|
11
13
|
|
|
@@ -101,3 +103,27 @@ describe('useFormHandler field overrides', () => {
|
|
|
101
103
|
expect(result.current.isValid).toBe(true);
|
|
102
104
|
});
|
|
103
105
|
});
|
|
106
|
+
|
|
107
|
+
describe('useFormHandler logging', () => {
|
|
108
|
+
test('never writes field values to the log', ({ expect }) => {
|
|
109
|
+
const entries: LogEntry[] = [];
|
|
110
|
+
const capture: LogProcessor = (_config, entry) => {
|
|
111
|
+
entries.push(entry);
|
|
112
|
+
};
|
|
113
|
+
onTestFinished(log.addProcessor(capture));
|
|
114
|
+
|
|
115
|
+
// Stands in for a pasted credential; any field may hold one, so no field's value may be logged.
|
|
116
|
+
const secret = 'placeholder-secret-0123456789';
|
|
117
|
+
const { result } = renderHook(() => useFormHandler<Values>({ schema, values: { name: 'Alice', city: 'NYC' } }));
|
|
118
|
+
act(() => result.current.onValueChange(['city'], stringAst, secret));
|
|
119
|
+
act(() => result.current.onValueChange(['name'], stringAst, ''));
|
|
120
|
+
expect(result.current.getValue(['city'])).toBe(secret);
|
|
121
|
+
|
|
122
|
+
const messages = entries.map((entry) => entry.message);
|
|
123
|
+
expect(messages).toContain('onValueChange');
|
|
124
|
+
expect(messages).toContain('validate');
|
|
125
|
+
for (const entry of entries) {
|
|
126
|
+
expect(JSON.stringify({ message: entry.message, context: entry.computedContext })).not.toContain(secret);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -170,7 +170,6 @@ export const useFormHandler = <T extends AnyProperties>({
|
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
let errors: ValidationError[] = validateSchema(schema, values) ?? [];
|
|
173
|
-
log('validate', { values, errors });
|
|
174
173
|
if (errors.length === 0 && onValidate) {
|
|
175
174
|
const validatedValues = values as T;
|
|
176
175
|
errors = onValidate(validatedValues) ?? [];
|
|
@@ -178,6 +177,8 @@ export const useFormHandler = <T extends AnyProperties>({
|
|
|
178
177
|
|
|
179
178
|
setErrors(flatMap(errors));
|
|
180
179
|
const valid = errors.length === 0;
|
|
180
|
+
// Never log values or error messages: fields may hold secrets (passwords, API keys) and messages can echo them.
|
|
181
|
+
log('validate', { valid, errorPaths: errors.map((error) => error.path) });
|
|
181
182
|
return valid;
|
|
182
183
|
},
|
|
183
184
|
[schema, onValidate],
|
|
@@ -303,7 +304,7 @@ export const useFormHandler = <T extends AnyProperties>({
|
|
|
303
304
|
|
|
304
305
|
const onValueChange = useCallback<FormHandler<T>['onValueChange']>(
|
|
305
306
|
(path, type, value) => {
|
|
306
|
-
log('onValueChange', { path
|
|
307
|
+
log('onValueChange', { path });
|
|
307
308
|
|
|
308
309
|
const jsonPath = SchemaEx.createJsonPath(path);
|
|
309
310
|
const pathArray = path;
|