@dxos/react-ui-form 0.8.3 → 0.8.4-main.1f223c7

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.
Files changed (103) hide show
  1. package/dist/lib/browser/index.mjs +648 -581
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +648 -581
  5. package/dist/lib/node-esm/index.mjs.map +4 -4
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/types/src/components/ControlSection/ControlSection.d.ts +2 -2
  8. package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +1 -1
  9. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts +79 -4
  10. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +1 -1
  11. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +4 -5
  12. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  13. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +76 -6
  14. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
  15. package/dist/types/src/components/Form/ArrayField.d.ts +2 -2
  16. package/dist/types/src/components/Form/ArrayField.d.ts.map +1 -1
  17. package/dist/types/src/components/Form/Defaults.d.ts +5 -5
  18. package/dist/types/src/components/Form/Defaults.d.ts.map +1 -1
  19. package/dist/types/src/components/Form/Form.d.ts +5 -4
  20. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  21. package/dist/types/src/components/Form/Form.stories.d.ts +126 -30
  22. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  23. package/dist/types/src/components/Form/FormActions.d.ts.map +1 -1
  24. package/dist/types/src/components/Form/FormContent.d.ts +16 -10
  25. package/dist/types/src/components/Form/FormContent.d.ts.map +1 -1
  26. package/dist/types/src/components/Form/FormContext.d.ts +1 -1
  27. package/dist/types/src/components/Form/FormContext.d.ts.map +1 -1
  28. package/dist/types/src/components/Form/Input.d.ts +4 -1
  29. package/dist/types/src/components/Form/Input.d.ts.map +1 -1
  30. package/dist/types/src/components/Form/RefField.d.ts +10 -4
  31. package/dist/types/src/components/Form/RefField.d.ts.map +1 -1
  32. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +1 -1
  33. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +1 -1
  34. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +1 -1
  35. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +1 -1
  36. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +74 -3
  37. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +1 -1
  38. package/dist/types/src/components/Form/custom/TupleInput.d.ts +1 -1
  39. package/dist/types/src/components/Form/factory.d.ts.map +1 -1
  40. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +5 -4
  41. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  42. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +78 -7
  43. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  44. package/dist/types/src/components/index.d.ts +0 -1
  45. package/dist/types/src/components/index.d.ts.map +1 -1
  46. package/dist/types/src/components/testing.d.ts +6 -3
  47. package/dist/types/src/components/testing.d.ts.map +1 -1
  48. package/dist/types/src/hooks/useForm.d.ts +1 -1
  49. package/dist/types/src/hooks/useForm.d.ts.map +1 -1
  50. package/dist/types/src/index.d.ts +1 -1
  51. package/dist/types/src/index.d.ts.map +1 -1
  52. package/dist/types/src/translations.d.ts +61 -57
  53. package/dist/types/src/translations.d.ts.map +1 -1
  54. package/dist/types/src/util/field.d.ts.map +1 -1
  55. package/dist/types/tsconfig.tsbuildinfo +1 -1
  56. package/package.json +36 -33
  57. package/src/components/ControlSection/ControlSection.stories.tsx +10 -12
  58. package/src/components/ControlSection/ControlSection.tsx +26 -12
  59. package/src/components/FieldEditor/FieldEditor.stories.tsx +21 -20
  60. package/src/components/FieldEditor/FieldEditor.test.tsx +14 -13
  61. package/src/components/FieldEditor/FieldEditor.tsx +17 -22
  62. package/src/components/Form/ArrayField.tsx +9 -9
  63. package/src/components/Form/Defaults.tsx +22 -20
  64. package/src/components/Form/Form.stories.tsx +131 -95
  65. package/src/components/Form/Form.tsx +16 -12
  66. package/src/components/Form/FormActions.tsx +2 -1
  67. package/src/components/Form/FormContent.tsx +96 -34
  68. package/src/components/Form/FormContext.tsx +5 -5
  69. package/src/components/Form/Input.tsx +4 -1
  70. package/src/components/Form/RefField.tsx +126 -40
  71. package/src/components/Form/custom/GeoPointInput.tsx +29 -21
  72. package/src/components/Form/custom/SelectOptionsInput.stories.tsx +9 -8
  73. package/src/components/Form/custom/SelectOptionsInput.tsx +29 -30
  74. package/src/components/Form/custom/TupleInput.tsx +2 -2
  75. package/src/components/Form/factory.tsx +1 -1
  76. package/src/components/ViewEditor/ViewEditor.stories.tsx +70 -32
  77. package/src/components/ViewEditor/ViewEditor.test.tsx +38 -39
  78. package/src/components/ViewEditor/ViewEditor.tsx +159 -155
  79. package/src/components/index.ts +0 -2
  80. package/src/components/testing.tsx +15 -9
  81. package/src/hooks/useForm.ts +5 -3
  82. package/src/hooks/useRefQueryLookupHandler.ts +1 -1
  83. package/src/index.ts +1 -1
  84. package/src/translations.ts +15 -7
  85. package/src/util/field.ts +8 -18
  86. package/dist/lib/node/index.cjs +0 -2416
  87. package/dist/lib/node/index.cjs.map +0 -7
  88. package/dist/lib/node/meta.json +0 -1
  89. package/dist/types/src/components/ViewEditor/Test.stories.d.ts +0 -10
  90. package/dist/types/src/components/ViewEditor/Test.stories.d.ts.map +0 -1
  91. package/dist/types/src/components/deprecated/Form.d.ts +0 -15
  92. package/dist/types/src/components/deprecated/Form.d.ts.map +0 -1
  93. package/dist/types/src/components/deprecated/Form.stories.d.ts +0 -13
  94. package/dist/types/src/components/deprecated/Form.stories.d.ts.map +0 -1
  95. package/dist/types/src/components/deprecated/FormInput.d.ts +0 -16
  96. package/dist/types/src/components/deprecated/FormInput.d.ts.map +0 -1
  97. package/dist/types/src/components/deprecated/index.d.ts +0 -2
  98. package/dist/types/src/components/deprecated/index.d.ts.map +0 -1
  99. package/src/components/ViewEditor/Test.stories.tsx +0 -45
  100. package/src/components/deprecated/Form.stories.tsx +0 -62
  101. package/src/components/deprecated/Form.tsx +0 -119
  102. package/src/components/deprecated/FormInput.tsx +0 -71
  103. package/src/components/deprecated/index.ts +0 -5
@@ -2,20 +2,19 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import React, { type ChangeEvent, type KeyboardEvent, useCallback, useEffect, useState, useRef } from 'react';
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, Icon, IconButton, Input, Tag, Toolbar, useTranslation } from '@dxos/react-ui';
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, mx } from '@dxos/react-ui-theme';
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
- // TODO(ZaymonFC): Handle disabled.
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 items={options} isItem={(item) => true} onMove={handleMove}>
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={mx('p-1 plb-2', 'flex flex-col', 'cursor-pointer', 'hover:bg-hoverOverlay')}
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
- <Icon
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='mis-4 mbs-2 flex flex-col gap-1'>
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='flex flex-row items-center gap-1'>
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
- <Toolbar.Root classNames='p-0 m-0 !is-auto'>
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
- <div role='listitem'>
174
- <div
175
- role='button'
176
- className={mx('p-1 plb-2', 'flex flex-col', 'cursor-pointer', 'hover:bg-hoverOverlay')}
177
- onClick={handleAdd}
178
- >
179
- <div className='flex items-center gap-1'>
180
- <Icon icon='ph--plus--regular' />
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
- disabled,
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={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.
@@ -4,46 +4,59 @@
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 { Schema } from 'effect';
9
- import React, { useCallback, useMemo, useState, useEffect } from 'react';
9
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
10
10
 
11
- import { Format, type EchoSchema, toJsonSchema, TypedObject } from '@dxos/echo-schema';
12
- import { Filter, useQuery, useSpace } from '@dxos/react-client/echo';
11
+ import { Filter, Obj, Query, Type } from '@dxos/echo';
12
+ import { type EchoSchema, Format, toJsonSchema } from '@dxos/echo-schema';
13
+ import { useSpace } from '@dxos/react-client/echo';
13
14
  import { withClientProvider } from '@dxos/react-client/testing';
14
15
  import { useAsyncEffect } from '@dxos/react-ui';
15
- import { ViewProjection, ViewType, createView } from '@dxos/schema';
16
- import { withTheme, withLayout } from '@dxos/storybook-utils';
16
+ import { QueryParser, createFilter } from '@dxos/react-ui-components';
17
+ import { type DataType, ProjectionModel, createView, typenameFromQuery } from '@dxos/schema';
18
+ import { withLayout, withTheme } from '@dxos/storybook-utils';
17
19
 
18
- import { ViewEditor, type ViewEditorProps } from './ViewEditor';
19
- import translations from '../../translations';
20
+ import { translations } from '../../translations';
20
21
  import { TestLayout, TestPanel, VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
21
22
 
23
+ import { ViewEditor, type ViewEditorProps } from './ViewEditor';
24
+
22
25
  // Type definition for debug objects exposed to tests.
23
26
  export type ViewEditorDebugObjects = {
24
27
  schema: EchoSchema;
25
- view: ViewType;
26
- projection: ViewProjection;
28
+ view: DataType.View;
29
+ projection: ProjectionModel;
27
30
  };
28
31
 
29
- type StoryProps = Pick<ViewEditorProps, 'readonly'>;
32
+ type StoryProps = Pick<ViewEditorProps, 'readonly' | 'kind'>;
30
33
 
31
34
  const DefaultStory = (props: StoryProps) => {
32
35
  const space = useSpace();
33
36
  const [schema, setSchema] = useState<EchoSchema>();
34
- const [view, setView] = useState<ViewType>();
35
- const [projection, setProjection] = useState<ViewProjection>();
37
+ const [view, setView] = useState<DataType.View>();
38
+ const [projection, setProjection] = useState<ProjectionModel>();
36
39
  useAsyncEffect(async () => {
37
40
  if (space) {
38
- class TestSchema extends TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
41
+ const TestSchema = Schema.Struct({
39
42
  name: Schema.String,
40
43
  email: Format.Email,
41
44
  salary: Format.Currency(),
42
- }) {}
45
+ }).pipe(
46
+ Type.Obj({
47
+ typename: 'example.com/type/Test',
48
+ version: '0.1.0',
49
+ }),
50
+ );
43
51
 
44
52
  const [schema] = await space.db.schemaRegistry.register([TestSchema]);
45
- const view = createView({ name: 'Test', typename: schema.typename, jsonSchema: toJsonSchema(TestSchema) });
46
- const projection = new ViewProjection(schema.jsonSchema, view);
53
+ const view = createView({
54
+ name: 'Test',
55
+ query: Query.select(Filter.type(TestSchema)),
56
+ jsonSchema: toJsonSchema(TestSchema),
57
+ presentation: Obj.make(Type.Expando, {}),
58
+ });
59
+ const projection = new ProjectionModel(schema.jsonSchema, view.projection);
47
60
 
48
61
  setSchema(schema);
49
62
  setView(view);
@@ -51,20 +64,32 @@ const DefaultStory = (props: StoryProps) => {
51
64
  }
52
65
  }, [space]);
53
66
 
54
- const views = useQuery(space, Filter.type(ViewType));
55
- const currentTypename = useMemo(() => view?.query?.typename, [view]);
56
- const updateViewTypename = useCallback(
57
- (newTypename: string) => {
58
- if (!schema) {
67
+ const updateViewQuery = useCallback(
68
+ (newQueryString: string) => {
69
+ if (!schema || !view) {
59
70
  return;
60
71
  }
61
- const matchingViews = views.filter((view) => view.query.typename === currentTypename);
62
- for (const view of matchingViews) {
63
- view.query.typename = newTypename;
72
+
73
+ if (props.kind === 'advanced') {
74
+ try {
75
+ const parser = new QueryParser(newQueryString);
76
+ // TODO(wittjosiah): When this fails it should show validation errors in the UI.
77
+ const newQuery = Query.select(createFilter(parser.parse()));
78
+ view.query = newQuery.ast;
79
+
80
+ const typename = typenameFromQuery(newQuery.ast);
81
+ if (typename) {
82
+ schema.updateTypename(typename);
83
+ }
84
+ } catch {}
85
+ } else {
86
+ const typename = newQueryString;
87
+ const newQuery = Query.select(Filter.typename(typename));
88
+ view.query = newQuery.ast;
89
+ schema.updateTypename(typename);
64
90
  }
65
- schema.updateTypename(newTypename);
66
91
  },
67
- [views, schema],
92
+ [view, schema],
68
93
  );
69
94
 
70
95
  const handleDelete = useCallback((property: string) => projection?.deleteFieldProjection(property), [projection]);
@@ -93,8 +118,9 @@ const DefaultStory = (props: StoryProps) => {
93
118
  schema={schema}
94
119
  view={view}
95
120
  registry={space?.db.schemaRegistry}
121
+ kind={props.kind}
96
122
  readonly={props.readonly}
97
- onTypenameChanged={updateViewTypename}
123
+ onQueryChanged={updateViewQuery}
98
124
  onDelete={handleDelete}
99
125
  />
100
126
  </TestPanel>
@@ -102,19 +128,31 @@ const DefaultStory = (props: StoryProps) => {
102
128
  );
103
129
  };
104
130
 
105
- const meta: Meta<StoryProps> = {
131
+ const meta = {
106
132
  title: 'ui/react-ui-form/ViewEditor',
107
133
  render: DefaultStory,
108
- decorators: [withClientProvider({ createSpace: true }), withLayout({ fullscreen: true }), withTheme],
134
+ decorators: [
135
+ withClientProvider({
136
+ createSpace: true,
137
+ }),
138
+ withLayout({
139
+ fullscreen: true,
140
+ }),
141
+ withTheme,
142
+ ],
109
143
  parameters: { translations },
110
- };
144
+ } satisfies Meta<typeof DefaultStory>;
111
145
 
112
146
  export default meta;
113
147
 
114
- type Story = StoryObj<StoryProps>;
148
+ type Story = StoryObj<typeof meta>;
115
149
 
116
150
  export const Default: Story = {};
117
151
 
118
152
  export const Readonly: Story = {
119
153
  args: { readonly: true },
120
154
  };
155
+
156
+ export const Advanced: Story = {
157
+ args: { kind: 'advanced' },
158
+ };
@@ -3,15 +3,16 @@
3
3
  //
4
4
 
5
5
  import { composeStories } from '@storybook/react';
6
- import { screen, cleanup, fireEvent, waitFor } from '@testing-library/react';
6
+ import { cleanup, fireEvent, screen, waitFor } from '@testing-library/react';
7
7
  import { afterEach, describe, expect, test } from 'vitest';
8
8
 
9
- import { EchoSchema } from '@dxos/echo-schema';
10
- import { ViewProjection, ViewType } from '@dxos/schema';
9
+ import { EchoSchema, isInstanceOf } from '@dxos/echo-schema';
10
+ import { DataType, ProjectionModel } from '@dxos/schema';
11
+
12
+ import { VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
11
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
 
@@ -19,8 +20,8 @@ const getViewEditorDebugObjects = (): ViewEditorDebugObjects => {
19
20
  const debugObjects = (window as any)[VIEW_EDITOR_DEBUG_SYMBOL] as ViewEditorDebugObjects;
20
21
  expect(debugObjects).toBeDefined();
21
22
  expect(debugObjects.schema).toBeInstanceOf(EchoSchema);
22
- expect(debugObjects.view).toBeInstanceOf(ViewType);
23
- expect(debugObjects.projection).toBeInstanceOf(ViewProjection);
23
+ expect(isInstanceOf(DataType.View, debugObjects.view)).toBeTruthy();
24
+ expect(debugObjects.projection).toBeInstanceOf(ProjectionModel);
24
25
  return debugObjects;
25
26
  };
26
27
 
@@ -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('View')).toBeInTheDocument();
45
+ expect(screen.getByText('Record type')).toBeInTheDocument();
45
46
  });
46
47
 
47
48
  test('change property name', async () => {
@@ -58,7 +59,7 @@ describe('ViewEditor', () => {
58
59
 
59
60
  fireEvent.click(screen.getByTestId('save-button'));
60
61
 
61
- // Wait for the changes to propagate to both UI and schema
62
+ // Wait for the changes to propagate to both UI and schema.
62
63
  await waitFor(() => {
63
64
  expect(screen.getByText('new_property')).toBeInTheDocument();
64
65
  });
@@ -66,18 +67,18 @@ describe('ViewEditor', () => {
66
67
  // Access the live objects directly from window using symbol.
67
68
  const debugObjects = getViewEditorDebugObjects();
68
69
 
69
- // Check schema contains new_property
70
+ // Check schema contains new_property.
70
71
  const schemaProperties = debugObjects.schema.jsonSchema.properties;
71
72
  expect(schemaProperties).toBeDefined();
72
73
  expect(schemaProperties!.new_property).toBeDefined();
73
74
  expect(schemaProperties!.name).toBeUndefined();
74
75
 
75
- // Check view contains new_property field
76
- const newPropertyField = debugObjects.view.fields.find((field: any) => field.path === 'new_property');
76
+ // Check view contains new_property field.
77
+ const newPropertyField = debugObjects.view.projection.fields.find((field: any) => field.path === 'new_property');
77
78
  expect(newPropertyField).toBeDefined();
78
79
  expect(newPropertyField!.path).toBe('new_property');
79
80
 
80
- // Check projection contains new_property
81
+ // Check projection contains new_property.
81
82
  const fieldProjections = debugObjects.projection.getFieldProjections();
82
83
  const newPropertyProjection = fieldProjections.find((proj: any) => proj.field.path === 'new_property');
83
84
  expect(newPropertyProjection).toBeDefined();
@@ -88,25 +89,25 @@ describe('ViewEditor', () => {
88
89
  await Default.run();
89
90
  await waitForViewEditor();
90
91
 
91
- // Click the add property button
92
+ // Click the add property button.
92
93
  const addButton = screen.getByText('Add property');
93
94
  fireEvent.click(addButton);
94
95
 
95
- // Fill out the property field
96
+ // Fill out the property field.
96
97
  const fieldInput = screen.getByPlaceholderText('Field name.');
97
98
  fireEvent.change(fieldInput, { target: { value: 'added_property' } });
98
99
 
99
- // Click the format combo box and select the first option
100
+ // Click the format combo box and select the first option.
100
101
  const formatCombo = screen.getByRole('combobox');
101
102
  fireEvent.click(formatCombo);
102
103
 
103
104
  const firstOption = screen.getAllByRole('option')[0];
104
105
  fireEvent.click(firstOption);
105
106
 
106
- // Save the changes
107
+ // Save the changes.
107
108
  fireEvent.click(screen.getByTestId('save-button'));
108
109
 
109
- // Wait for the changes to propagate
110
+ // Wait for the changes to propagate.
110
111
  await waitFor(() => {
111
112
  expect(screen.getByText('added_property')).toBeInTheDocument();
112
113
  });
@@ -114,17 +115,19 @@ describe('ViewEditor', () => {
114
115
  // Access the live objects directly from window using symbol.
115
116
  const debugObjects = getViewEditorDebugObjects();
116
117
 
117
- // Check schema contains added_property
118
+ // Check schema contains added_property.
118
119
  const schemaProperties = debugObjects.schema.jsonSchema.properties;
119
120
  expect(schemaProperties).toBeDefined();
120
121
  expect(schemaProperties!.added_property).toBeDefined();
121
122
 
122
- // Check view contains added_property field
123
- const addedPropertyField = debugObjects.view.fields.find((field: any) => field.path === 'added_property');
123
+ // Check view contains added_property field.
124
+ const addedPropertyField = debugObjects.view.projection.fields.find(
125
+ (field: any) => field.path === 'added_property',
126
+ );
124
127
  expect(addedPropertyField).toBeDefined();
125
128
  expect(addedPropertyField!.path).toBe('added_property');
126
129
 
127
- // Check projection contains added_property
130
+ // Check projection contains added_property.
128
131
  const fieldProjections = debugObjects.projection.getFieldProjections();
129
132
  const addedPropertyProjection = fieldProjections.find((proj: any) => proj.field.path === 'added_property');
130
133
  expect(addedPropertyProjection).toBeDefined();
@@ -135,19 +138,19 @@ describe('ViewEditor', () => {
135
138
  await Default.run();
136
139
  await waitForViewEditor();
137
140
 
138
- // Find the delete button for the 'name' property
141
+ // Find the delete button for the 'name' property.
139
142
  const nameProperty = screen.getByText('name');
140
143
  const propertyRow = nameProperty.closest('[role="listitem"]');
141
- const deleteButton = propertyRow?.querySelector('button:last-child');
144
+ const deleteButton = propertyRow?.querySelector('[data-testid="field.delete"]');
142
145
 
143
146
  if (!deleteButton) {
144
147
  throw new Error('Delete button not found');
145
148
  }
146
149
 
147
- // Click the delete button
150
+ // Click the delete button.
148
151
  fireEvent.click(deleteButton);
149
152
 
150
- // Wait for the changes to propagate
153
+ // Wait for the changes to propagate.
151
154
  await waitFor(() => {
152
155
  expect(screen.queryByText('name')).not.toBeInTheDocument();
153
156
  });
@@ -155,16 +158,16 @@ describe('ViewEditor', () => {
155
158
  // Access the live objects directly from window using symbol.
156
159
  const debugObjects = getViewEditorDebugObjects();
157
160
 
158
- // Check schema no longer contains the name property
161
+ // Check schema no longer contains the name property.
159
162
  const schemaProperties = debugObjects.schema.jsonSchema.properties;
160
163
  expect(schemaProperties).toBeDefined();
161
164
  expect(schemaProperties!.name).toBeUndefined();
162
165
 
163
- // Check view no longer contains the name field
164
- const nameField = debugObjects.view.fields.find((field: any) => field.path === 'name');
166
+ // Check view no longer contains the name field.
167
+ const nameField = debugObjects.view.projection.fields.find((field: any) => field.path === 'name');
165
168
  expect(nameField).toBeUndefined();
166
169
 
167
- // Check projection no longer contains the name property
170
+ // Check projection no longer contains the name property.
168
171
  const fieldProjections = debugObjects.projection.getFieldProjections();
169
172
  const nameProjection = fieldProjections.find((proj: any) => proj.field.path === 'name');
170
173
  expect(nameProjection).toBeUndefined();
@@ -174,11 +177,11 @@ describe('ViewEditor', () => {
174
177
  await Default.run();
175
178
  await waitForViewEditor();
176
179
 
177
- // Click the first hide button
180
+ // Click the first hide button.
178
181
  const hideButtons = screen.getAllByTestId('hide-field-button');
179
182
  fireEvent.click(hideButtons[0]);
180
183
 
181
- // Check that hiddenFields is non-empty
184
+ // Check that hiddenFields is non-empty.
182
185
  await waitFor(() => {
183
186
  expect(screen.getByTestId('show-field-button')).toBeInTheDocument();
184
187
  });
@@ -186,19 +189,15 @@ describe('ViewEditor', () => {
186
189
  // Access the live objects directly from window using symbol.
187
190
  const debugObjects = getViewEditorDebugObjects();
188
191
 
189
- // Check that hiddenFields is non-empty
190
- expect(debugObjects.view.hiddenFields).toBeDefined();
191
- expect(debugObjects.view.hiddenFields!.length).toBeGreaterThan(0);
192
-
193
192
  // Click the show button
194
193
  fireEvent.click(screen.getByTestId('show-field-button'));
195
194
 
196
- // Wait for the data to update and check that hiddenFields is empty
195
+ // Wait for the data to update and check that hiddenFields is empty.
197
196
  await waitFor(() => {
198
- expect(debugObjects.view.hiddenFields!.length).toBe(0);
197
+ expect(debugObjects.view.projection.fields.filter(({ visible }) => visible === false)!.length).toBe(0);
199
198
  });
200
199
 
201
- // Also verify that the field is back in the visible fields list
202
- expect(debugObjects.view.fields.length).toBeGreaterThan(0);
200
+ // Also verify that the field is back in the visible fields list.
201
+ expect(debugObjects.view.projection.fields.filter(({ visible }) => visible !== false).length).toBeGreaterThan(0);
203
202
  });
204
203
  });