@dxos/react-ui-form 0.8.3 → 0.8.4-main.28f8d3d
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/browser/index.mjs +222 -209
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +222 -209
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts +1 -1
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +4 -4
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +3 -3
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/ArrayField.d.ts.map +1 -1
- package/dist/types/src/components/Form/Defaults.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +2 -2
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormActions.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormContent.d.ts +1 -1
- package/dist/types/src/components/Form/FormContent.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormContext.d.ts +1 -1
- package/dist/types/src/components/Form/FormContext.d.ts.map +1 -1
- package/dist/types/src/components/Form/RefField.d.ts +1 -1
- package/dist/types/src/components/Form/RefField.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +1 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/factory.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +2 -2
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +4 -4
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/deprecated/Form.d.ts +3 -3
- package/dist/types/src/components/deprecated/Form.d.ts.map +1 -1
- package/dist/types/src/components/deprecated/Form.stories.d.ts +1 -1
- package/dist/types/src/components/deprecated/Form.stories.d.ts.map +1 -1
- package/dist/types/src/hooks/useForm.d.ts +1 -1
- package/dist/types/src/hooks/useForm.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +57 -57
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +31 -32
- package/src/components/ControlSection/ControlSection.stories.tsx +3 -2
- package/src/components/FieldEditor/FieldEditor.stories.tsx +17 -14
- package/src/components/FieldEditor/FieldEditor.test.tsx +14 -13
- package/src/components/FieldEditor/FieldEditor.tsx +11 -11
- package/src/components/Form/ArrayField.tsx +8 -7
- package/src/components/Form/Defaults.tsx +2 -1
- package/src/components/Form/Form.stories.tsx +5 -4
- package/src/components/Form/Form.tsx +4 -3
- package/src/components/Form/FormActions.tsx +2 -1
- package/src/components/Form/FormContent.tsx +6 -5
- package/src/components/Form/FormContext.tsx +5 -5
- package/src/components/Form/RefField.tsx +7 -6
- package/src/components/Form/custom/GeoPointInput.tsx +2 -2
- package/src/components/Form/custom/SelectOptionsInput.stories.tsx +5 -4
- package/src/components/Form/custom/SelectOptionsInput.tsx +28 -29
- package/src/components/Form/factory.tsx +1 -1
- package/src/components/ViewEditor/ViewEditor.stories.tsx +21 -14
- package/src/components/ViewEditor/ViewEditor.test.tsx +41 -38
- package/src/components/ViewEditor/ViewEditor.tsx +81 -81
- package/src/components/deprecated/Form.stories.tsx +8 -7
- package/src/components/deprecated/Form.tsx +6 -6
- package/src/components/testing.tsx +1 -1
- package/src/hooks/useForm.ts +5 -3
- package/src/hooks/useRefQueryLookupHandler.ts +1 -1
- package/src/index.ts +1 -1
- package/src/translations.ts +7 -4
- package/dist/lib/node/index.cjs +0 -2416
- package/dist/lib/node/index.cjs.map +0 -7
- package/dist/lib/node/meta.json +0 -1
- package/dist/types/src/components/ViewEditor/Test.stories.d.ts +0 -10
- package/dist/types/src/components/ViewEditor/Test.stories.d.ts.map +0 -1
- package/src/components/ViewEditor/Test.stories.tsx +0 -45
|
@@ -11,21 +11,19 @@ import { invariant } from '@dxos/invariant';
|
|
|
11
11
|
import { Callout, IconButton, type ThemedClassName, useTranslation } from '@dxos/react-ui';
|
|
12
12
|
import { List } from '@dxos/react-ui-list';
|
|
13
13
|
import { cardSpacing } from '@dxos/react-ui-stack';
|
|
14
|
-
import {
|
|
15
|
-
import { FieldSchema, type FieldType,
|
|
14
|
+
import { inputTextLabel, mx, subtleHover } from '@dxos/react-ui-theme';
|
|
15
|
+
import { type DataType, FieldSchema, type FieldType, ProjectionModel, VIEW_FIELD_LIMIT } from '@dxos/schema';
|
|
16
16
|
|
|
17
17
|
import { translationKey } from '../../translations';
|
|
18
18
|
import { FieldEditor } from '../FieldEditor';
|
|
19
19
|
import { Form, type FormProps } from '../Form';
|
|
20
20
|
|
|
21
|
-
const
|
|
21
|
+
const listGrid = 'grid grid-cols-[min-content_1fr_min-content_min-content_min-content]';
|
|
22
|
+
const listItemGrid = 'grid grid-cols-subgrid col-span-5';
|
|
22
23
|
|
|
23
24
|
const ViewMetaSchema = Schema.Struct({
|
|
24
|
-
name: Schema.String.annotations({
|
|
25
|
-
title: 'View',
|
|
26
|
-
}),
|
|
27
25
|
typename: Format.URL.annotations({
|
|
28
|
-
title: '
|
|
26
|
+
title: 'Record type',
|
|
29
27
|
}),
|
|
30
28
|
}).pipe(Schema.mutable);
|
|
31
29
|
|
|
@@ -34,7 +32,7 @@ type ViewMetaType = Schema.Schema.Type<typeof ViewMetaSchema>;
|
|
|
34
32
|
export type ViewEditorProps = ThemedClassName<
|
|
35
33
|
{
|
|
36
34
|
schema: Schema.Schema.AnyNoContext;
|
|
37
|
-
view:
|
|
35
|
+
view: DataType.View;
|
|
38
36
|
registry?: SchemaRegistry;
|
|
39
37
|
readonly?: boolean;
|
|
40
38
|
showHeading?: boolean;
|
|
@@ -61,27 +59,23 @@ export const ViewEditor = ({
|
|
|
61
59
|
const projection = useMemo(() => {
|
|
62
60
|
// Use reactive and mutable version of json schema when schema is mutable.
|
|
63
61
|
const jsonSchema = schema instanceof EchoSchema ? schema.jsonSchema : toJsonSchema(schema);
|
|
64
|
-
return new
|
|
65
|
-
}, [schema, view]);
|
|
66
|
-
const [
|
|
62
|
+
return new ProjectionModel(jsonSchema, view.projection);
|
|
63
|
+
}, [schema, view.projection]);
|
|
64
|
+
const [expandedField, setExpandedField] = useState<FieldType['id']>();
|
|
67
65
|
const readonly = _readonly || !isMutable(schema);
|
|
68
66
|
|
|
69
67
|
// TODO(burdon): Should be reactive.
|
|
70
68
|
const viewValues = useMemo(() => {
|
|
71
69
|
return {
|
|
72
|
-
name: view.name,
|
|
73
70
|
// TODO(burdon): Need to warn user of possible consequences of editing.
|
|
74
71
|
// TODO(burdon): Settings should have domain name owned by user.
|
|
75
72
|
typename: view.query.typename,
|
|
76
73
|
};
|
|
77
|
-
}, [view]);
|
|
74
|
+
}, [view.query.typename]);
|
|
78
75
|
|
|
79
|
-
const
|
|
76
|
+
const handleToggleField = useCallback(
|
|
80
77
|
(field: FieldType) => {
|
|
81
|
-
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
setField((f) => (f === field ? undefined : field));
|
|
78
|
+
setExpandedField((prevExpandedFieldId) => (prevExpandedFieldId === field.id ? undefined : field.id));
|
|
85
79
|
},
|
|
86
80
|
[readonly],
|
|
87
81
|
);
|
|
@@ -91,56 +85,52 @@ export const ViewEditor = ({
|
|
|
91
85
|
const handleAdd = useCallback(() => {
|
|
92
86
|
invariant(!readonly);
|
|
93
87
|
const field = projection.createFieldProjection();
|
|
94
|
-
|
|
95
|
-
}, [schema,
|
|
88
|
+
setExpandedField(field.id);
|
|
89
|
+
}, [schema, projection, readonly]);
|
|
96
90
|
|
|
97
91
|
const handleUpdate = useCallback(
|
|
98
|
-
({
|
|
92
|
+
({ typename }: ViewMetaType) => {
|
|
99
93
|
invariant(!readonly);
|
|
100
94
|
requestAnimationFrame(() => {
|
|
101
|
-
if (view.name !== name) {
|
|
102
|
-
view.name = name;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
95
|
if (view.query.typename !== typename && !readonly) {
|
|
106
96
|
onTypenameChanged?.(typename);
|
|
107
97
|
}
|
|
108
98
|
});
|
|
109
99
|
},
|
|
110
|
-
[
|
|
100
|
+
[view.query.typename, onTypenameChanged, readonly],
|
|
111
101
|
);
|
|
112
102
|
|
|
113
103
|
const handleDelete = useCallback(
|
|
114
104
|
(fieldId: string) => {
|
|
115
105
|
invariant(!readonly);
|
|
116
|
-
if (fieldId ===
|
|
117
|
-
|
|
106
|
+
if (fieldId === expandedField) {
|
|
107
|
+
setExpandedField(undefined);
|
|
118
108
|
}
|
|
119
109
|
|
|
120
110
|
onDelete?.(fieldId);
|
|
121
111
|
},
|
|
122
|
-
[
|
|
112
|
+
[expandedField, onDelete, readonly],
|
|
123
113
|
);
|
|
124
114
|
|
|
125
115
|
const handleMove = useCallback(
|
|
126
116
|
(fromIndex: number, toIndex: number) => {
|
|
127
117
|
invariant(!readonly);
|
|
128
118
|
// NOTE(ZaymonFC): Using arrayMove here causes a race condition with the kanban model.
|
|
129
|
-
const fields = [...view.fields];
|
|
119
|
+
const fields = [...view.projection.fields];
|
|
130
120
|
const [moved] = fields.splice(fromIndex, 1);
|
|
131
121
|
fields.splice(toIndex, 0, moved);
|
|
132
|
-
view.fields = fields;
|
|
122
|
+
view.projection.fields = fields;
|
|
133
123
|
},
|
|
134
|
-
[
|
|
124
|
+
[view.projection.fields, readonly],
|
|
135
125
|
);
|
|
136
126
|
|
|
137
|
-
const handleClose = useCallback(() =>
|
|
127
|
+
const handleClose = useCallback(() => setExpandedField(undefined), []);
|
|
138
128
|
|
|
139
129
|
const hiddenProperties = projection.getHiddenProperties();
|
|
140
130
|
|
|
141
131
|
const handleHide = useCallback(
|
|
142
132
|
(fieldId: string) => {
|
|
143
|
-
|
|
133
|
+
setExpandedField(undefined);
|
|
144
134
|
projection.hideFieldProjection(fieldId);
|
|
145
135
|
},
|
|
146
136
|
[projection],
|
|
@@ -148,7 +138,7 @@ export const ViewEditor = ({
|
|
|
148
138
|
|
|
149
139
|
const handleShow = useCallback(
|
|
150
140
|
(property: string) => {
|
|
151
|
-
|
|
141
|
+
setExpandedField(undefined);
|
|
152
142
|
projection.showFieldProjection(property as JsonProp);
|
|
153
143
|
},
|
|
154
144
|
[projection],
|
|
@@ -157,11 +147,12 @@ export const ViewEditor = ({
|
|
|
157
147
|
return (
|
|
158
148
|
<div role='none' className={mx(classNames)}>
|
|
159
149
|
{readonly && (
|
|
160
|
-
<
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
150
|
+
<Callout.Root
|
|
151
|
+
valence='info'
|
|
152
|
+
classNames={['is-full mlb-cardSpacingBlock', outerSpacing && 'mli-cardSpacingInline']}
|
|
153
|
+
>
|
|
154
|
+
<Callout.Title>{t('system schema description')}</Callout.Title>
|
|
155
|
+
</Callout.Root>
|
|
165
156
|
)}
|
|
166
157
|
|
|
167
158
|
{/* TODO(burdon): Is the form read-only or just the schema? */}
|
|
@@ -176,46 +167,65 @@ export const ViewEditor = ({
|
|
|
176
167
|
/>
|
|
177
168
|
|
|
178
169
|
<div role='none' className={outerSpacing ? cardSpacing : 'mlb-cardSpacingBlock'}>
|
|
179
|
-
<
|
|
170
|
+
<h2 className={mx(inputTextLabel)}>{t('fields label')}</h2>
|
|
180
171
|
|
|
181
172
|
<List.Root<FieldType>
|
|
182
|
-
items={view.fields}
|
|
173
|
+
items={view.projection.fields}
|
|
183
174
|
isItem={Schema.is(FieldSchema)}
|
|
184
175
|
getId={(field) => field.id}
|
|
185
|
-
readonly={readonly}
|
|
186
176
|
onMove={readonly ? undefined : handleMove}
|
|
177
|
+
readonly={readonly}
|
|
187
178
|
>
|
|
188
179
|
{({ items: fields }) => (
|
|
189
180
|
<>
|
|
190
|
-
{showHeading && (
|
|
191
|
-
<div role='heading' className={grid}>
|
|
192
|
-
<div />
|
|
193
|
-
<div className='flex items-center text-sm'>{t('field path label')}</div>
|
|
194
|
-
</div>
|
|
195
|
-
)}
|
|
181
|
+
{showHeading && <h3 className='text-sm'>{t('field path label')}</h3>}
|
|
196
182
|
|
|
197
|
-
<div role='list' className=
|
|
183
|
+
<div role='list' className={listGrid}>
|
|
198
184
|
{fields?.map((field) => (
|
|
199
185
|
<List.Item<FieldType>
|
|
200
186
|
key={field.id}
|
|
201
187
|
item={field}
|
|
202
|
-
classNames={
|
|
188
|
+
classNames={listItemGrid}
|
|
189
|
+
aria-expanded={expandedField === field.id}
|
|
203
190
|
>
|
|
204
|
-
<
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
191
|
+
<div role='none' className={mx(subtleHover, listItemGrid, 'rounded-sm cursor-pointer min-bs-10')}>
|
|
192
|
+
<List.ItemDragHandle disabled={readonly} />
|
|
193
|
+
<List.ItemTitle onClick={() => handleToggleField(field)}>{field.path}</List.ItemTitle>
|
|
194
|
+
<List.ItemButton
|
|
195
|
+
label={t('hide field label')}
|
|
196
|
+
data-testid='hide-field-button'
|
|
197
|
+
icon='ph--eye-slash--regular'
|
|
198
|
+
autoHide={false}
|
|
199
|
+
// TDOO(burdon): Is this the correct test?
|
|
200
|
+
disabled={readonly || view.projection.fields.length <= 1}
|
|
201
|
+
onClick={() => handleHide(field.id)}
|
|
202
|
+
/>
|
|
203
|
+
{/* TODO(burdon): Remove unless implement undo. */}
|
|
215
204
|
<List.ItemDeleteButton
|
|
216
|
-
|
|
205
|
+
label={t('delete field label')}
|
|
206
|
+
autoHide={false}
|
|
207
|
+
disabled={readonly || view.projection.fields.length <= 1}
|
|
217
208
|
onClick={() => handleDelete(field.id)}
|
|
209
|
+
data-testid='field.delete'
|
|
218
210
|
/>
|
|
211
|
+
<IconButton
|
|
212
|
+
iconOnly
|
|
213
|
+
variant='ghost'
|
|
214
|
+
label={t('toggle expand label', { ns: 'os' })}
|
|
215
|
+
icon={expandedField === field.id ? 'ph--caret-down--regular' : 'ph--caret-right--regular'}
|
|
216
|
+
onClick={() => handleToggleField(field)}
|
|
217
|
+
/>
|
|
218
|
+
</div>
|
|
219
|
+
{expandedField === field.id && (
|
|
220
|
+
<div role='none' className='col-span-5'>
|
|
221
|
+
<FieldEditor
|
|
222
|
+
readonly={readonly}
|
|
223
|
+
projection={projection}
|
|
224
|
+
field={field}
|
|
225
|
+
registry={registry}
|
|
226
|
+
onSave={handleClose}
|
|
227
|
+
/>
|
|
228
|
+
</div>
|
|
219
229
|
)}
|
|
220
230
|
</List.Item>
|
|
221
231
|
))}
|
|
@@ -226,7 +236,7 @@ export const ViewEditor = ({
|
|
|
226
236
|
|
|
227
237
|
{hiddenProperties.length > 0 && (
|
|
228
238
|
<>
|
|
229
|
-
<
|
|
239
|
+
<h2 className={mx(inputTextLabel)}>{t('hidden fields label')}</h2>
|
|
230
240
|
|
|
231
241
|
<List.Root<string>
|
|
232
242
|
items={hiddenProperties}
|
|
@@ -234,16 +244,17 @@ export const ViewEditor = ({
|
|
|
234
244
|
getId={(property) => property}
|
|
235
245
|
>
|
|
236
246
|
{({ items: properties }) => (
|
|
237
|
-
<div role='list' className='
|
|
247
|
+
<div role='list' className='grid grid-cols-[1fr_min-content]'>
|
|
238
248
|
{properties?.map((property) => (
|
|
239
249
|
<List.Item<string>
|
|
240
250
|
key={property}
|
|
241
251
|
item={property}
|
|
242
|
-
classNames={mx(grid
|
|
252
|
+
classNames={mx('grid grid-cols-subgrid col-span-2 rounded-sm', subtleHover)}
|
|
243
253
|
>
|
|
244
|
-
<List.ItemDragHandle disabled />
|
|
245
254
|
<List.ItemTitle>{property}</List.ItemTitle>
|
|
246
255
|
<List.ItemButton
|
|
256
|
+
label={t('show field label')}
|
|
257
|
+
disabled={readonly}
|
|
247
258
|
icon='ph--eye--regular'
|
|
248
259
|
onClick={() => handleShow(property)}
|
|
249
260
|
data-testid='show-field-button'
|
|
@@ -257,26 +268,15 @@ export const ViewEditor = ({
|
|
|
257
268
|
)}
|
|
258
269
|
</div>
|
|
259
270
|
|
|
260
|
-
{
|
|
261
|
-
<FieldEditor
|
|
262
|
-
key={field.id}
|
|
263
|
-
view={view}
|
|
264
|
-
projection={projection}
|
|
265
|
-
field={field}
|
|
266
|
-
registry={registry}
|
|
267
|
-
onSave={handleClose}
|
|
268
|
-
/>
|
|
269
|
-
)}
|
|
270
|
-
|
|
271
|
-
{!readonly && !field && (
|
|
271
|
+
{!readonly && !expandedField && (
|
|
272
272
|
<div role='none' className={outerSpacing ? cardSpacing : 'mlb-cardSpacingBlock'}>
|
|
273
273
|
<IconButton
|
|
274
274
|
icon='ph--plus--regular'
|
|
275
275
|
label={t('button add property')}
|
|
276
276
|
onClick={readonly ? undefined : handleAdd}
|
|
277
277
|
// TODO(burdon): Show field limit in ux (not tooltip).
|
|
278
|
-
disabled={view.fields.length >= VIEW_FIELD_LIMIT}
|
|
279
|
-
classNames='
|
|
278
|
+
disabled={view.projection.fields.length >= VIEW_FIELD_LIMIT}
|
|
279
|
+
classNames='is-full'
|
|
280
280
|
/>
|
|
281
281
|
</div>
|
|
282
282
|
)}
|
|
@@ -4,17 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
import '@dxos-theme';
|
|
6
6
|
|
|
7
|
-
import { type Meta, type StoryObj } from '@storybook/react';
|
|
7
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
8
8
|
import React from 'react';
|
|
9
9
|
|
|
10
10
|
import { live } from '@dxos/live-object';
|
|
11
|
-
import {
|
|
11
|
+
import { TestSchema, type TestType, testData, testView } from '@dxos/schema/testing';
|
|
12
12
|
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
13
13
|
|
|
14
|
-
import {
|
|
15
|
-
import translations from '../../translations';
|
|
14
|
+
import { translations } from '../../translations';
|
|
16
15
|
import { TestLayout, TestPanel } from '../testing';
|
|
17
16
|
|
|
17
|
+
import { DeprecatedForm, type DeprecatedFormProps } from './Form';
|
|
18
|
+
|
|
18
19
|
const DefaultStory = (props: DeprecatedFormProps) => (
|
|
19
20
|
<TestLayout json={{ props }}>
|
|
20
21
|
<TestPanel>
|
|
@@ -40,14 +41,14 @@ type Story = StoryObj<DeprecatedFormProps<TestType>>;
|
|
|
40
41
|
export const Default: Story = {
|
|
41
42
|
args: {
|
|
42
43
|
object: live(testData),
|
|
43
|
-
|
|
44
|
+
projection: live(testView).projection,
|
|
44
45
|
schema: TestSchema,
|
|
45
46
|
},
|
|
46
47
|
};
|
|
47
48
|
|
|
48
49
|
export const Empty: Story = {
|
|
49
50
|
args: {
|
|
50
|
-
|
|
51
|
+
projection: live(testView).projection,
|
|
51
52
|
schema: TestSchema,
|
|
52
53
|
},
|
|
53
54
|
};
|
|
@@ -55,7 +56,7 @@ export const Empty: Story = {
|
|
|
55
56
|
export const Readonly: Story = {
|
|
56
57
|
args: {
|
|
57
58
|
object: live(testData),
|
|
58
|
-
|
|
59
|
+
projection: live(testView).projection,
|
|
59
60
|
schema: TestSchema,
|
|
60
61
|
readonly: true,
|
|
61
62
|
},
|
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Option, type Schema, SchemaAST, pipe } from 'effect';
|
|
6
6
|
import React, { useState } from 'react';
|
|
7
7
|
|
|
8
8
|
import { findProperty } from '@dxos/effect';
|
|
9
|
-
import { Input, type
|
|
9
|
+
import { Input, type TextInputProps as NativeTextInputProps, type ThemedClassName } from '@dxos/react-ui';
|
|
10
10
|
import { mx } from '@dxos/react-ui-theme';
|
|
11
|
-
import { type
|
|
11
|
+
import { type Projection, getFieldValue, setFieldValue } from '@dxos/schema';
|
|
12
12
|
|
|
13
13
|
export type DeprecatedFormProps<T extends {} = {}> = ThemedClassName<{
|
|
14
|
-
|
|
14
|
+
projection: Projection;
|
|
15
15
|
object: T;
|
|
16
16
|
schema?: Schema.Schema<T>;
|
|
17
17
|
readonly?: boolean;
|
|
@@ -22,14 +22,14 @@ export type DeprecatedFormProps<T extends {} = {}> = ThemedClassName<{
|
|
|
22
22
|
*/
|
|
23
23
|
export const DeprecatedForm = <T extends {} = {}>({
|
|
24
24
|
classNames,
|
|
25
|
-
|
|
25
|
+
projection,
|
|
26
26
|
object,
|
|
27
27
|
schema,
|
|
28
28
|
readonly,
|
|
29
29
|
}: DeprecatedFormProps<T>) => {
|
|
30
30
|
return (
|
|
31
31
|
<div role='none' className={mx('flex flex-col w-full gap-2 p-2', classNames)}>
|
|
32
|
-
{
|
|
32
|
+
{projection.fields.map((field) => {
|
|
33
33
|
const prop = schema && findProperty(schema, field.path);
|
|
34
34
|
const title = (prop && pipe(SchemaAST.getTitleAnnotation(prop), Option.getOrUndefined)) ?? '';
|
|
35
35
|
const description = (prop && pipe(SchemaAST.getDescriptionAnnotation(prop), Option.getOrUndefined)) ?? title;
|
|
@@ -17,7 +17,7 @@ export const TestLayout = ({ classNames, json, children }: ThemedClassName<Props
|
|
|
17
17
|
);
|
|
18
18
|
|
|
19
19
|
export const TestPanel = ({ classNames, children }: ThemedClassName<PropsWithChildren>) => (
|
|
20
|
-
<Card.
|
|
20
|
+
<Card.StaticRoot classNames={[textBlockWidth, classNames]}>{children}</Card.StaticRoot>
|
|
21
21
|
);
|
|
22
22
|
|
|
23
23
|
// Symbol for accessing debug objects in tests.
|
package/src/hooks/useForm.ts
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { type Schema, SchemaAST } from 'effect';
|
|
6
6
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
7
7
|
|
|
8
8
|
import { type BaseObject, getValue, setValue } from '@dxos/echo-schema';
|
|
9
|
-
import { type
|
|
9
|
+
import { type JsonPath, type SimpleType, createJsonPath, fromEffectValidationPath } from '@dxos/effect';
|
|
10
10
|
import { invariant } from '@dxos/invariant';
|
|
11
11
|
import { log } from '@dxos/log';
|
|
12
|
-
import {
|
|
12
|
+
import { type ValidationError, validateSchema } from '@dxos/schema';
|
|
13
13
|
import { type MaybePromise } from '@dxos/util';
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -26,6 +26,8 @@ export type FormHandler<T extends BaseObject> = {
|
|
|
26
26
|
changed: Record<JsonPath, boolean>;
|
|
27
27
|
canSave: boolean;
|
|
28
28
|
formIsValid: boolean;
|
|
29
|
+
|
|
30
|
+
// TODO(burdon): Return onSave.
|
|
29
31
|
handleSave: () => void;
|
|
30
32
|
|
|
31
33
|
//
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { useCallback } from 'react';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { type TypeAnnotation, getObjectDXN } from '@dxos/echo-schema';
|
|
8
8
|
import { Filter, type Space } from '@dxos/react-client/echo';
|
|
9
9
|
import { isNonNullable } from '@dxos/util';
|
|
10
10
|
|
package/src/index.ts
CHANGED
package/src/translations.ts
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { type Resource } from '@dxos/react-ui';
|
|
6
|
+
|
|
5
7
|
export const translationKey = 'react-ui-form';
|
|
6
8
|
|
|
7
|
-
export
|
|
9
|
+
export const translations = [
|
|
8
10
|
{
|
|
9
11
|
'en-US': {
|
|
10
12
|
[translationKey]: {
|
|
@@ -23,6 +25,7 @@ export default [
|
|
|
23
25
|
'field limit reached': 'Maximum number of fields reached',
|
|
24
26
|
'boolean input true value': 'Yes',
|
|
25
27
|
'boolean input false value': 'No',
|
|
28
|
+
'delete field label': 'Delete field',
|
|
26
29
|
|
|
27
30
|
// TODO(burdon): Factor out?
|
|
28
31
|
'button cancel': 'Cancel',
|
|
@@ -70,9 +73,9 @@ export default [
|
|
|
70
73
|
'select option add': 'Add option',
|
|
71
74
|
|
|
72
75
|
// System schema message.
|
|
73
|
-
'system schema title': 'System
|
|
74
|
-
'system schema description': 'This is a system
|
|
76
|
+
'system schema title': 'System record type',
|
|
77
|
+
'system schema description': 'This is a system record type and cannot be modified.',
|
|
75
78
|
},
|
|
76
79
|
},
|
|
77
80
|
},
|
|
78
|
-
];
|
|
81
|
+
] as const satisfies Resource[];
|