@dxos/react-ui-form 0.8.4-main.67995b8 → 0.8.4-main.a4bbb77
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 +701 -538
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +701 -538
- 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.d.ts +2 -2
- package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +1 -1
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts +81 -5
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +2 -2
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +76 -5
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/ArrayField.d.ts +2 -2
- package/dist/types/src/components/Form/ArrayField.d.ts.map +1 -1
- package/dist/types/src/components/Form/Defaults.d.ts +5 -5
- package/dist/types/src/components/Form/Defaults.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +5 -4
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +128 -31
- 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 +16 -10
- package/dist/types/src/components/Form/FormContent.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormContext.d.ts +2 -2
- package/dist/types/src/components/Form/FormContext.d.ts.map +1 -1
- package/dist/types/src/components/Form/Input.d.ts +4 -1
- package/dist/types/src/components/Form/Input.d.ts.map +1 -1
- package/dist/types/src/components/Form/RefField.d.ts +13 -5
- package/dist/types/src/components/Form/RefField.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +1 -1
- package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +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 +76 -4
- package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/TupleInput.d.ts +1 -1
- package/dist/types/src/components/Form/factory.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +15 -3
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +78 -6
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +0 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +6 -3
- package/dist/types/src/components/testing.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/hooks/useQueryRefOptions.d.ts +7 -2
- package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +11 -5
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/util/field.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +43 -38
- package/src/components/ControlSection/ControlSection.stories.tsx +11 -14
- package/src/components/ControlSection/ControlSection.tsx +26 -12
- package/src/components/FieldEditor/FieldEditor.stories.tsx +13 -13
- package/src/components/FieldEditor/FieldEditor.test.tsx +3 -2
- package/src/components/FieldEditor/FieldEditor.tsx +26 -18
- package/src/components/Form/ArrayField.tsx +8 -8
- package/src/components/Form/Defaults.tsx +22 -20
- package/src/components/Form/Form.stories.tsx +133 -97
- package/src/components/Form/Form.tsx +16 -12
- package/src/components/Form/FormActions.tsx +2 -1
- package/src/components/Form/FormContent.tsx +116 -36
- package/src/components/Form/FormContext.tsx +6 -6
- package/src/components/Form/Input.tsx +4 -1
- package/src/components/Form/RefField.tsx +190 -40
- package/src/components/Form/custom/GeoPointInput.tsx +29 -21
- package/src/components/Form/custom/SelectOptionsInput.stories.tsx +10 -10
- package/src/components/Form/custom/SelectOptionsInput.tsx +29 -30
- package/src/components/Form/custom/TupleInput.tsx +2 -2
- package/src/components/Form/factory.tsx +1 -1
- package/src/components/ViewEditor/ViewEditor.stories.tsx +101 -43
- package/src/components/ViewEditor/ViewEditor.test.tsx +8 -11
- package/src/components/ViewEditor/ViewEditor.tsx +329 -220
- package/src/components/index.ts +0 -2
- package/src/components/testing.tsx +15 -9
- package/src/hooks/useForm.ts +6 -3
- package/src/hooks/useQueryRefOptions.ts +8 -2
- package/src/hooks/useRefQueryLookupHandler.ts +1 -1
- package/src/translations.ts +13 -6
- package/src/util/field.ts +8 -18
- package/dist/types/src/components/deprecated/Form.d.ts +0 -15
- package/dist/types/src/components/deprecated/Form.d.ts.map +0 -1
- package/dist/types/src/components/deprecated/Form.stories.d.ts +0 -13
- package/dist/types/src/components/deprecated/Form.stories.d.ts.map +0 -1
- package/dist/types/src/components/deprecated/FormInput.d.ts +0 -16
- package/dist/types/src/components/deprecated/FormInput.d.ts.map +0 -1
- package/dist/types/src/components/deprecated/index.d.ts +0 -2
- package/dist/types/src/components/deprecated/index.d.ts.map +0 -1
- package/src/components/deprecated/Form.stories.tsx +0 -62
- package/src/components/deprecated/Form.tsx +0 -119
- package/src/components/deprecated/FormInput.tsx +0 -71
- package/src/components/deprecated/index.ts +0 -5
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, {
|
|
5
|
+
import React, { type ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react';
|
|
6
6
|
|
|
7
|
-
import { type GeoPoint
|
|
7
|
+
import { GeoLocation, type GeoPoint } from '@dxos/echo-schema';
|
|
8
8
|
import { Input, useTranslation } from '@dxos/react-ui';
|
|
9
9
|
import { safeParseFloat } from '@dxos/util';
|
|
10
10
|
|
|
11
11
|
import { translationKey } from '../../../translations';
|
|
12
12
|
import { InputHeader, type InputProps } from '../Input';
|
|
13
13
|
|
|
14
|
-
export const GeoPointInput = ({ type, label,
|
|
14
|
+
export const GeoPointInput = ({ type, label, readonly, getStatus, getValue, onValueChange, onBlur }: InputProps) => {
|
|
15
15
|
const { t } = useTranslation(translationKey);
|
|
16
16
|
const { status, error } = getStatus();
|
|
17
17
|
const geoPoint = useMemo<GeoPoint>(() => getValue<GeoPoint>() ?? [0, 0], [getValue]);
|
|
@@ -48,24 +48,32 @@ export const GeoPointInput = ({ type, label, disabled, getStatus, getValue, onVa
|
|
|
48
48
|
<Input.Root validationValence={status}>
|
|
49
49
|
<InputHeader error={error} label={label} />
|
|
50
50
|
<div role='none' className='grid grid-cols-2 gap-2'>
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
51
|
+
<div role='none'>
|
|
52
|
+
<Input.Label>{t('latitude label')}</Input.Label>
|
|
53
|
+
<Input.TextInput
|
|
54
|
+
type='number'
|
|
55
|
+
step='0.00001'
|
|
56
|
+
min='-90'
|
|
57
|
+
max='90'
|
|
58
|
+
disabled={!!readonly}
|
|
59
|
+
value={latitudeText}
|
|
60
|
+
onChange={handleCoordinateChange('latitude', setLatitudeText)}
|
|
61
|
+
onBlur={onBlur}
|
|
62
|
+
/>
|
|
63
|
+
</div>
|
|
64
|
+
<div role='none'>
|
|
65
|
+
<Input.Label>{t('longitude label')}</Input.Label>
|
|
66
|
+
<Input.TextInput
|
|
67
|
+
type='number'
|
|
68
|
+
step='0.00001'
|
|
69
|
+
min='-180'
|
|
70
|
+
max='180'
|
|
71
|
+
disabled={!!readonly}
|
|
72
|
+
value={longitudeText}
|
|
73
|
+
onChange={handleCoordinateChange('longitude', setLongitudeText)}
|
|
74
|
+
onBlur={onBlur}
|
|
75
|
+
/>
|
|
76
|
+
</div>
|
|
69
77
|
</div>
|
|
70
78
|
</Input.Root>
|
|
71
79
|
);
|
|
@@ -2,20 +2,19 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@dxos-theme';
|
|
6
|
-
|
|
7
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
8
6
|
import { Schema } from 'effect';
|
|
9
7
|
import React, { useState } from 'react';
|
|
10
8
|
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
9
|
+
import { type SelectOption, SelectOptionSchema } from '@dxos/echo-schema';
|
|
10
|
+
import { withTheme } from '@dxos/react-ui/testing';
|
|
13
11
|
|
|
14
|
-
import { SelectOptionInput } from './SelectOptionsInput';
|
|
15
12
|
import { translations } from '../../../translations';
|
|
16
13
|
import { TestLayout, TestPanel } from '../../testing';
|
|
17
14
|
import { Form } from '../Form';
|
|
18
15
|
|
|
16
|
+
import { SelectOptionInput } from './SelectOptionsInput';
|
|
17
|
+
|
|
19
18
|
const schema = Schema.Struct({
|
|
20
19
|
options: Schema.Array(SelectOptionSchema).pipe(Schema.mutable),
|
|
21
20
|
}).pipe(Schema.mutable);
|
|
@@ -44,18 +43,19 @@ const DefaultStory = () => {
|
|
|
44
43
|
);
|
|
45
44
|
};
|
|
46
45
|
|
|
47
|
-
const meta
|
|
46
|
+
const meta = {
|
|
48
47
|
title: 'ui/react-ui-form/SelectOptionsInput',
|
|
49
|
-
component: SelectOptionInput,
|
|
48
|
+
component: SelectOptionInput as any,
|
|
50
49
|
render: DefaultStory,
|
|
51
|
-
decorators: [withTheme
|
|
50
|
+
decorators: [withTheme],
|
|
52
51
|
parameters: {
|
|
52
|
+
layout: 'fullscreen',
|
|
53
53
|
translations,
|
|
54
54
|
},
|
|
55
|
-
}
|
|
55
|
+
} satisfies Meta<typeof DefaultStory>;
|
|
56
56
|
|
|
57
57
|
export default meta;
|
|
58
58
|
|
|
59
|
-
type Story = StoryObj
|
|
59
|
+
type Story = StoryObj<typeof meta>;
|
|
60
60
|
|
|
61
61
|
export const Default: Story = {};
|
|
@@ -2,20 +2,19 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, { type ChangeEvent, type KeyboardEvent, useCallback, useEffect,
|
|
5
|
+
import React, { type ChangeEvent, type KeyboardEvent, useCallback, useEffect, useRef, useState } from 'react';
|
|
6
6
|
|
|
7
7
|
import { type SelectOption } from '@dxos/echo-schema';
|
|
8
8
|
import { PublicKey } from '@dxos/keys';
|
|
9
|
-
import { type ChromaticPalette,
|
|
9
|
+
import { type ChromaticPalette, IconButton, Input, Tag, useTranslation } from '@dxos/react-ui';
|
|
10
10
|
import { List } from '@dxos/react-ui-list';
|
|
11
11
|
import { HuePicker } from '@dxos/react-ui-pickers';
|
|
12
|
-
import { hues,
|
|
12
|
+
import { hues, subtleHover } from '@dxos/react-ui-theme';
|
|
13
13
|
|
|
14
14
|
import { translationKey } from '../../../translations';
|
|
15
15
|
import { InputHeader, type InputProps } from '../Input';
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
export const SelectOptionInput = ({ type, label, disabled, getStatus, getValue, onValueChange }: InputProps) => {
|
|
17
|
+
export const SelectOptionInput = ({ type, label, readonly, getStatus, getValue, onValueChange }: InputProps) => {
|
|
19
18
|
const [selected, setSelectedId] = useState<string | null>(null);
|
|
20
19
|
const [isNewOption, setIsNewOption] = useState(false);
|
|
21
20
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
@@ -115,7 +114,12 @@ export const SelectOptionInput = ({ type, label, disabled, getStatus, getValue,
|
|
|
115
114
|
<InputHeader error={error} label={label} />
|
|
116
115
|
<div role='none'>
|
|
117
116
|
{options && (
|
|
118
|
-
<List.Root
|
|
117
|
+
<List.Root
|
|
118
|
+
items={options}
|
|
119
|
+
isItem={(item) => true}
|
|
120
|
+
onMove={readonly ? undefined : handleMove}
|
|
121
|
+
readonly={!!readonly}
|
|
122
|
+
>
|
|
119
123
|
{({ items }) => (
|
|
120
124
|
<div role='list' className='w-full overflow-auto'>
|
|
121
125
|
{items?.map((item) => (
|
|
@@ -123,26 +127,30 @@ export const SelectOptionInput = ({ type, label, disabled, getStatus, getValue,
|
|
|
123
127
|
<List.Item
|
|
124
128
|
role='button'
|
|
125
129
|
item={item}
|
|
126
|
-
classNames={[
|
|
130
|
+
classNames={[subtleHover, 'flex flex-col cursor-pointer rounded-sm']}
|
|
127
131
|
aria-expanded={selected === item.id}
|
|
128
132
|
>
|
|
129
133
|
<div className='flex items-center'>
|
|
130
|
-
<List.ItemDragHandle />
|
|
134
|
+
<List.ItemDragHandle disabled={!!readonly} />
|
|
131
135
|
<List.ItemTitle onClick={() => handleClick(item.id)} classNames='flex-1'>
|
|
132
136
|
{/* TODO(ZaymonFC): Move spacer into Tag component. */}
|
|
133
137
|
<Tag palette={item.color as ChromaticPalette}>{item.title || '\u200b'}</Tag>
|
|
134
138
|
</List.ItemTitle>
|
|
135
|
-
<
|
|
139
|
+
<IconButton
|
|
140
|
+
iconOnly
|
|
141
|
+
variant='ghost'
|
|
142
|
+
label={t(selected === item.id ? 'collapse label' : 'expand label', { ns: 'os' })}
|
|
136
143
|
icon={selected === item.id ? 'ph--caret-down--regular' : 'ph--caret-right--regular'}
|
|
137
144
|
onClick={() => handleClick(item.id)}
|
|
138
145
|
/>
|
|
139
146
|
</div>
|
|
140
147
|
{selected === item.id && (
|
|
141
|
-
<div className='
|
|
148
|
+
<div className='pli-cardSpacingInline plb-cardSpacingBlock flex flex-col gap-1 density-fine'>
|
|
142
149
|
{/* 16px to match drag handle width. */}
|
|
143
150
|
<Input.Label classNames='text-xs'>{t('select option label')}</Input.Label>
|
|
144
|
-
<div className='
|
|
151
|
+
<div className='grid grid-cols-[1fr_min-content_min-content] gap-1'>
|
|
145
152
|
<Input.TextInput
|
|
153
|
+
disabled={!!readonly}
|
|
146
154
|
placeholder={t('select option label placeholder')}
|
|
147
155
|
ref={selected === item.id ? inputRef : undefined}
|
|
148
156
|
value={item.title}
|
|
@@ -151,14 +159,9 @@ export const SelectOptionInput = ({ type, label, disabled, getStatus, getValue,
|
|
|
151
159
|
classNames='flex-1'
|
|
152
160
|
data-no-submit
|
|
153
161
|
/>
|
|
154
|
-
<
|
|
155
|
-
<HuePicker
|
|
156
|
-
value={item.color}
|
|
157
|
-
onChange={handleColorChange(item.id)}
|
|
158
|
-
rootVariant='toolbar-button'
|
|
159
|
-
/>
|
|
160
|
-
</Toolbar.Root>
|
|
162
|
+
<HuePicker disabled={!!readonly} value={item.color} onChange={handleColorChange(item.id)} />
|
|
161
163
|
<IconButton
|
|
164
|
+
disabled={!!readonly}
|
|
162
165
|
icon='ph--trash--fill'
|
|
163
166
|
iconOnly
|
|
164
167
|
label={t('select option delete')}
|
|
@@ -170,18 +173,14 @@ export const SelectOptionInput = ({ type, label, disabled, getStatus, getValue,
|
|
|
170
173
|
</List.Item>
|
|
171
174
|
</div>
|
|
172
175
|
))}
|
|
173
|
-
<
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
<span className='text-sm'>{t('select option add')}</span>
|
|
182
|
-
</div>
|
|
183
|
-
</div>
|
|
184
|
-
</div>
|
|
176
|
+
<IconButton
|
|
177
|
+
variant='ghost'
|
|
178
|
+
icon='ph--plus--regular'
|
|
179
|
+
label={t('select option add')}
|
|
180
|
+
onClick={handleAdd}
|
|
181
|
+
disabled={!!readonly}
|
|
182
|
+
classNames='is-full'
|
|
183
|
+
/>
|
|
185
184
|
</div>
|
|
186
185
|
)}
|
|
187
186
|
</List.Root>
|
|
@@ -16,7 +16,7 @@ export const TupleInput = ({
|
|
|
16
16
|
binding,
|
|
17
17
|
type,
|
|
18
18
|
label,
|
|
19
|
-
|
|
19
|
+
readonly,
|
|
20
20
|
getStatus,
|
|
21
21
|
getValue,
|
|
22
22
|
onValueChange,
|
|
@@ -35,7 +35,7 @@ export const TupleInput = ({
|
|
|
35
35
|
<Input.TextInput
|
|
36
36
|
key={prop}
|
|
37
37
|
type='number'
|
|
38
|
-
disabled={
|
|
38
|
+
disabled={!!readonly}
|
|
39
39
|
value={values[prop]}
|
|
40
40
|
onChange={(event) => onValueChange(type, { ...values, [prop]: safeParseFloat(event.target.value, 0) })}
|
|
41
41
|
onBlur={onBlur}
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
import { FormatEnum } from '@dxos/echo-schema';
|
|
6
6
|
import { type SimpleType } from '@dxos/effect';
|
|
7
7
|
|
|
8
|
+
import { GeoPointInput } from './custom';
|
|
8
9
|
import { BooleanInput, MarkdownInput, NumberInput, TextInput } from './Defaults';
|
|
9
10
|
import { type InputComponent } from './Input';
|
|
10
|
-
import { GeoPointInput } from './custom';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Get property input component.
|
|
@@ -2,24 +2,24 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@dxos-theme';
|
|
6
|
-
|
|
7
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
8
6
|
import { Schema } from 'effect';
|
|
9
|
-
import React, { useCallback, useMemo,
|
|
7
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
10
8
|
|
|
11
|
-
import { Obj, Type } from '@dxos/echo';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
9
|
+
import { DXN, Filter, Obj, Query, Type } from '@dxos/echo';
|
|
10
|
+
import { QueryBuilder } from '@dxos/echo-query';
|
|
11
|
+
import { type EchoSchema, Format, toJsonSchema } from '@dxos/echo-schema';
|
|
12
|
+
import { useSpace } from '@dxos/react-client/echo';
|
|
14
13
|
import { withClientProvider } from '@dxos/react-client/testing';
|
|
15
14
|
import { useAsyncEffect } from '@dxos/react-ui';
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
15
|
+
import { withTheme } from '@dxos/react-ui/testing';
|
|
16
|
+
import { type DataType, type ProjectionModel, createView, typenameFromQuery } from '@dxos/schema';
|
|
18
17
|
|
|
19
|
-
import { ViewEditor, type ViewEditorProps } from './ViewEditor';
|
|
20
18
|
import { translations } from '../../translations';
|
|
21
19
|
import { TestLayout, TestPanel, VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
|
|
22
20
|
|
|
21
|
+
import { ViewEditor, type ViewEditorProps } from './ViewEditor';
|
|
22
|
+
|
|
23
23
|
// Type definition for debug objects exposed to tests.
|
|
24
24
|
export type ViewEditorDebugObjects = {
|
|
25
25
|
schema: EchoSchema;
|
|
@@ -27,68 +27,113 @@ export type ViewEditorDebugObjects = {
|
|
|
27
27
|
projection: ProjectionModel;
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
type StoryProps = Pick<ViewEditorProps, 'readonly'>;
|
|
30
|
+
type StoryProps = Pick<ViewEditorProps, 'readonly' | 'mode'>;
|
|
31
31
|
|
|
32
32
|
const DefaultStory = (props: StoryProps) => {
|
|
33
33
|
const space = useSpace();
|
|
34
34
|
const [schema, setSchema] = useState<EchoSchema>();
|
|
35
35
|
const [view, setView] = useState<DataType.View>();
|
|
36
|
-
const
|
|
36
|
+
const projectionRef = useRef<ProjectionModel>(null);
|
|
37
|
+
|
|
37
38
|
useAsyncEffect(async () => {
|
|
38
39
|
if (space) {
|
|
39
|
-
|
|
40
|
+
const TestSchema = Schema.Struct({
|
|
40
41
|
name: Schema.String,
|
|
41
42
|
email: Format.Email,
|
|
42
43
|
salary: Format.Currency(),
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
}).pipe(
|
|
45
|
+
Type.Obj({
|
|
46
|
+
typename: 'example.com/type/Test',
|
|
47
|
+
version: '0.1.0',
|
|
48
|
+
}),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const AlternateSchema = Schema.Struct({
|
|
52
|
+
title: Schema.String,
|
|
53
|
+
description: Schema.String,
|
|
54
|
+
completed: Schema.Boolean,
|
|
55
|
+
}).pipe(
|
|
56
|
+
Type.Obj({
|
|
57
|
+
typename: 'example.com/type/Alternate',
|
|
58
|
+
version: '0.1.0',
|
|
59
|
+
}),
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const [testSchema] = await space.db.schemaRegistry.register([TestSchema, AlternateSchema]);
|
|
46
63
|
const view = createView({
|
|
47
64
|
name: 'Test',
|
|
48
|
-
|
|
65
|
+
query: Query.select(Filter.type(TestSchema)),
|
|
66
|
+
queryString: 'Query.select(Filter.type(TestSchema))',
|
|
49
67
|
jsonSchema: toJsonSchema(TestSchema),
|
|
50
68
|
presentation: Obj.make(Type.Expando, {}),
|
|
51
69
|
});
|
|
52
|
-
const projection = new ProjectionModel(schema.jsonSchema, view.projection);
|
|
53
70
|
|
|
54
|
-
setSchema(
|
|
71
|
+
setSchema(testSchema);
|
|
55
72
|
setView(view);
|
|
56
|
-
setProjection(projection);
|
|
57
73
|
}
|
|
58
74
|
}, [space]);
|
|
59
75
|
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
(newTypename: string) => {
|
|
64
|
-
if (!schema) {
|
|
76
|
+
const updateViewQuery = useCallback(
|
|
77
|
+
async (queryString: string, target?: string) => {
|
|
78
|
+
if (!schema || !view || !space) {
|
|
65
79
|
return;
|
|
66
80
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
view.query.
|
|
81
|
+
|
|
82
|
+
if (props.mode === 'query') {
|
|
83
|
+
view.query.string = queryString;
|
|
84
|
+
|
|
85
|
+
const builder = new QueryBuilder();
|
|
86
|
+
const filter = builder.build(queryString) ?? Filter.nothing();
|
|
87
|
+
const queue = target && DXN.tryParse(target) ? target : undefined;
|
|
88
|
+
const newQuery = queue ? Query.select(filter).options({ queues: [queue] }) : Query.select(filter);
|
|
89
|
+
view.query.ast = newQuery.ast;
|
|
90
|
+
|
|
91
|
+
const typename = typenameFromQuery(newQuery.ast);
|
|
92
|
+
const [newSchema] = await space.db.schemaRegistry.query({ typename }).run();
|
|
93
|
+
if (!newSchema) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const newView = createView({
|
|
98
|
+
query: newQuery,
|
|
99
|
+
jsonSchema: newSchema.jsonSchema,
|
|
100
|
+
presentation: Obj.make(Type.Expando, {}),
|
|
101
|
+
});
|
|
102
|
+
view.projection = Obj.getSnapshot(newView).projection;
|
|
103
|
+
setSchema(() => newSchema);
|
|
104
|
+
} else {
|
|
105
|
+
const typename = queryString;
|
|
106
|
+
const newQuery = Query.select(Filter.typename(typename));
|
|
107
|
+
view.query.ast = newQuery.ast;
|
|
108
|
+
schema.updateTypename(typename);
|
|
70
109
|
}
|
|
71
|
-
schema.updateTypename(newTypename);
|
|
72
110
|
},
|
|
73
|
-
[
|
|
111
|
+
[view, schema],
|
|
74
112
|
);
|
|
75
113
|
|
|
76
|
-
const handleDelete = useCallback(
|
|
114
|
+
const handleDelete = useCallback(
|
|
115
|
+
(property: string) => projectionRef.current?.deleteFieldProjection(property),
|
|
116
|
+
[projectionRef],
|
|
117
|
+
);
|
|
77
118
|
|
|
78
119
|
// Expose objects on window for test access.
|
|
79
120
|
useEffect(() => {
|
|
80
|
-
if (typeof window !== 'undefined' && schema && view &&
|
|
81
|
-
(window as any)[VIEW_EDITOR_DEBUG_SYMBOL] = {
|
|
121
|
+
if (typeof window !== 'undefined' && schema && view && projectionRef.current) {
|
|
122
|
+
(window as any)[VIEW_EDITOR_DEBUG_SYMBOL] = {
|
|
123
|
+
schema,
|
|
124
|
+
view,
|
|
125
|
+
projection: projectionRef.current,
|
|
126
|
+
} satisfies ViewEditorDebugObjects;
|
|
82
127
|
}
|
|
83
|
-
}, [schema, view
|
|
128
|
+
}, [schema, view]);
|
|
84
129
|
|
|
85
130
|
// NOTE(ZaymonFC): This looks awkward but it resolves an infinite parsing issue with sb.
|
|
86
131
|
const json = useMemo(
|
|
87
|
-
() => JSON.parse(JSON.stringify({ schema, view, projection })),
|
|
88
|
-
[JSON.stringify(schema), JSON.stringify(view)
|
|
132
|
+
() => JSON.parse(JSON.stringify({ schema, view, projection: projectionRef.current })),
|
|
133
|
+
[JSON.stringify(schema), JSON.stringify(view)],
|
|
89
134
|
);
|
|
90
135
|
|
|
91
|
-
if (!schema || !view
|
|
136
|
+
if (!schema || !view) {
|
|
92
137
|
return <div />;
|
|
93
138
|
}
|
|
94
139
|
|
|
@@ -96,11 +141,13 @@ const DefaultStory = (props: StoryProps) => {
|
|
|
96
141
|
<TestLayout json={json}>
|
|
97
142
|
<TestPanel>
|
|
98
143
|
<ViewEditor
|
|
144
|
+
ref={projectionRef}
|
|
99
145
|
schema={schema}
|
|
100
146
|
view={view}
|
|
101
147
|
registry={space?.db.schemaRegistry}
|
|
148
|
+
mode={props.mode}
|
|
102
149
|
readonly={props.readonly}
|
|
103
|
-
|
|
150
|
+
onQueryChanged={updateViewQuery}
|
|
104
151
|
onDelete={handleDelete}
|
|
105
152
|
/>
|
|
106
153
|
</TestPanel>
|
|
@@ -108,19 +155,30 @@ const DefaultStory = (props: StoryProps) => {
|
|
|
108
155
|
);
|
|
109
156
|
};
|
|
110
157
|
|
|
111
|
-
const meta
|
|
158
|
+
const meta = {
|
|
112
159
|
title: 'ui/react-ui-form/ViewEditor',
|
|
113
160
|
render: DefaultStory,
|
|
114
|
-
decorators: [withClientProvider({ createSpace: true }),
|
|
115
|
-
parameters: {
|
|
116
|
-
|
|
161
|
+
decorators: [withClientProvider({ createSpace: true }), withTheme],
|
|
162
|
+
parameters: {
|
|
163
|
+
layout: 'fullscreen',
|
|
164
|
+
translations,
|
|
165
|
+
},
|
|
166
|
+
} satisfies Meta<typeof DefaultStory>;
|
|
117
167
|
|
|
118
168
|
export default meta;
|
|
119
169
|
|
|
120
|
-
type Story = StoryObj<
|
|
170
|
+
type Story = StoryObj<typeof meta>;
|
|
121
171
|
|
|
122
172
|
export const Default: Story = {};
|
|
123
173
|
|
|
124
174
|
export const Readonly: Story = {
|
|
125
|
-
args: {
|
|
175
|
+
args: {
|
|
176
|
+
readonly: true,
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
export const QueryMode: Story = {
|
|
181
|
+
args: {
|
|
182
|
+
mode: 'query',
|
|
183
|
+
},
|
|
126
184
|
};
|
|
@@ -3,15 +3,16 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { composeStories } from '@storybook/react';
|
|
6
|
-
import {
|
|
6
|
+
import { cleanup, fireEvent, screen, waitFor } from '@testing-library/react';
|
|
7
7
|
import { afterEach, describe, expect, test } from 'vitest';
|
|
8
8
|
|
|
9
9
|
import { EchoSchema, isInstanceOf } from '@dxos/echo-schema';
|
|
10
10
|
import { DataType, ProjectionModel } from '@dxos/schema';
|
|
11
11
|
|
|
12
|
+
import { VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
|
|
13
|
+
|
|
12
14
|
import * as stories from './ViewEditor.stories';
|
|
13
15
|
import { type ViewEditorDebugObjects } from './ViewEditor.stories';
|
|
14
|
-
import { VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
|
|
15
16
|
|
|
16
17
|
const { Default } = composeStories(stories);
|
|
17
18
|
|
|
@@ -41,7 +42,7 @@ describe('ViewEditor', () => {
|
|
|
41
42
|
test('renders view editor', async () => {
|
|
42
43
|
await Default.run();
|
|
43
44
|
await waitForViewEditor();
|
|
44
|
-
expect(screen.getByText('
|
|
45
|
+
expect(screen.getByText('Record type')).toBeInTheDocument();
|
|
45
46
|
});
|
|
46
47
|
|
|
47
48
|
test('change property name', async () => {
|
|
@@ -140,7 +141,7 @@ describe('ViewEditor', () => {
|
|
|
140
141
|
// Find the delete button for the 'name' property.
|
|
141
142
|
const nameProperty = screen.getByText('name');
|
|
142
143
|
const propertyRow = nameProperty.closest('[role="listitem"]');
|
|
143
|
-
const deleteButton = propertyRow?.querySelector('
|
|
144
|
+
const deleteButton = propertyRow?.querySelector('[data-testid="field.delete"]');
|
|
144
145
|
|
|
145
146
|
if (!deleteButton) {
|
|
146
147
|
throw new Error('Delete button not found');
|
|
@@ -188,19 +189,15 @@ describe('ViewEditor', () => {
|
|
|
188
189
|
// Access the live objects directly from window using symbol.
|
|
189
190
|
const debugObjects = getViewEditorDebugObjects();
|
|
190
191
|
|
|
191
|
-
// Check that hiddenFields is non-empty.
|
|
192
|
-
expect(debugObjects.view.projection.hiddenFields).toBeDefined();
|
|
193
|
-
expect(debugObjects.view.projection.hiddenFields!.length).toBeGreaterThan(0);
|
|
194
|
-
|
|
195
192
|
// Click the show button
|
|
196
193
|
fireEvent.click(screen.getByTestId('show-field-button'));
|
|
197
194
|
|
|
198
195
|
// Wait for the data to update and check that hiddenFields is empty.
|
|
199
196
|
await waitFor(() => {
|
|
200
|
-
expect(debugObjects.view.projection.
|
|
197
|
+
expect(debugObjects.view.projection.fields.filter(({ visible }) => visible === false)!.length).toBe(0);
|
|
201
198
|
});
|
|
202
199
|
|
|
203
200
|
// Also verify that the field is back in the visible fields list.
|
|
204
|
-
expect(debugObjects.view.projection.fields.length).toBeGreaterThan(0);
|
|
201
|
+
expect(debugObjects.view.projection.fields.filter(({ visible }) => visible !== false).length).toBeGreaterThan(0);
|
|
205
202
|
});
|
|
206
|
-
});
|
|
203
|
+
}, 30_000);
|