@dxos/react-ui-table 0.8.4-main.bc674ce → 0.8.4-main.c351d160a8

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 (90) hide show
  1. package/dist/lib/browser/{chunk-OZBLY7XM.mjs → chunk-BF54SSNL.mjs} +12 -18
  2. package/dist/lib/browser/chunk-BF54SSNL.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-I5IFW7UF.mjs → chunk-PTHMYAAM.mjs} +33 -28
  4. package/dist/lib/browser/chunk-PTHMYAAM.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +178 -176
  6. package/dist/lib/browser/index.mjs.map +4 -4
  7. package/dist/lib/browser/meta.json +1 -1
  8. package/dist/lib/browser/testing/index.mjs +3 -3
  9. package/dist/lib/browser/testing/index.mjs.map +2 -2
  10. package/dist/lib/browser/types/index.mjs +1 -1
  11. package/dist/lib/node-esm/{chunk-DDJKYJFY.mjs → chunk-GI72MQSG.mjs} +33 -28
  12. package/dist/lib/node-esm/chunk-GI72MQSG.mjs.map +7 -0
  13. package/dist/lib/node-esm/{chunk-WNZSKIRF.mjs → chunk-SIRDIPHZ.mjs} +12 -18
  14. package/dist/lib/node-esm/chunk-SIRDIPHZ.mjs.map +7 -0
  15. package/dist/lib/node-esm/index.mjs +178 -176
  16. package/dist/lib/node-esm/index.mjs.map +4 -4
  17. package/dist/lib/node-esm/meta.json +1 -1
  18. package/dist/lib/node-esm/testing/index.mjs +3 -3
  19. package/dist/lib/node-esm/testing/index.mjs.map +2 -2
  20. package/dist/lib/node-esm/types/index.mjs +1 -1
  21. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
  22. package/dist/types/src/components/Table/DynamicTable.d.ts +5 -4
  23. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
  24. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +1 -1
  25. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  26. package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
  27. package/dist/types/src/components/Table/Table.d.ts +6 -6
  28. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  29. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  30. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +3 -3
  31. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  32. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +2 -2
  33. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  34. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  35. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +1 -1
  36. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
  37. package/dist/types/src/hooks/useAddRow.d.ts +1 -1
  38. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
  39. package/dist/types/src/hooks/useProjectionModel.d.ts +1 -1
  40. package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -1
  41. package/dist/types/src/hooks/useTableModel.d.ts +1 -1
  42. package/dist/types/src/model/table-model.d.ts +5 -4
  43. package/dist/types/src/model/table-model.d.ts.map +1 -1
  44. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  45. package/dist/types/src/playwright/TableManager.d.ts +2 -1
  46. package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
  47. package/dist/types/src/testing/useTestTableModel.d.ts +2 -2
  48. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
  49. package/dist/types/src/types/Table.d.ts +1 -6
  50. package/dist/types/src/types/Table.d.ts.map +1 -1
  51. package/dist/types/src/util/dynamic-table.d.ts +6 -5
  52. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  53. package/dist/types/src/util/dynamic-table.test.d.ts +2 -0
  54. package/dist/types/src/util/dynamic-table.test.d.ts.map +1 -0
  55. package/dist/types/src/util/query-ast.d.ts.map +1 -1
  56. package/dist/types/tsconfig.tsbuildinfo +1 -1
  57. package/package.json +42 -42
  58. package/src/components/Table/ColumnSettings.tsx +3 -4
  59. package/src/components/Table/DynamicTable.stories.tsx +6 -5
  60. package/src/components/Table/DynamicTable.tsx +11 -10
  61. package/src/components/Table/Relations.stories.tsx +13 -10
  62. package/src/components/Table/Table.stories.tsx +54 -32
  63. package/src/components/Table/Table.tsx +51 -56
  64. package/src/components/TableCellEditor/CellValidationMessage.tsx +4 -4
  65. package/src/components/TableCellEditor/FormCellEditor.tsx +11 -5
  66. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +4 -3
  67. package/src/components/TableCellEditor/TableCellEditor.tsx +3 -3
  68. package/src/components/TableToolbar/TableToolbar.tsx +6 -6
  69. package/src/components/testing.ts +1 -1
  70. package/src/hooks/useAddRow.ts +1 -1
  71. package/src/hooks/useProjectionModel.ts +3 -3
  72. package/src/hooks/useTableModel.ts +1 -1
  73. package/src/model/table-model.test.ts +5 -5
  74. package/src/model/table-model.ts +9 -8
  75. package/src/model/table-presentation.test.ts +5 -5
  76. package/src/model/table-presentation.ts +12 -11
  77. package/src/playwright/TableManager.ts +10 -3
  78. package/src/playwright/playwright.config.ts +1 -1
  79. package/src/playwright/smoke.spec.ts +45 -2
  80. package/src/testing/useTestTableModel.ts +3 -3
  81. package/src/types/Table.ts +11 -22
  82. package/src/util/dynamic-table.test.ts +70 -0
  83. package/src/util/dynamic-table.ts +13 -12
  84. package/src/util/query-ast.ts +3 -0
  85. package/src/util/table-buttons.ts +1 -1
  86. package/src/util/table-controls.ts +2 -2
  87. package/dist/lib/browser/chunk-I5IFW7UF.mjs.map +0 -7
  88. package/dist/lib/browser/chunk-OZBLY7XM.mjs.map +0 -7
  89. package/dist/lib/node-esm/chunk-DDJKYJFY.mjs.map +0 -7
  90. package/dist/lib/node-esm/chunk-WNZSKIRF.mjs.map +0 -7
@@ -17,6 +17,7 @@ const TABLE_SELECTORS = {
17
17
  },
18
18
  columnDelete: 'column-delete',
19
19
  rowDelete: 'row-menu-delete',
20
+ addRow: 'table.toolbar.add-row',
20
21
  } as const;
21
22
 
22
23
  export class TableManager {
@@ -53,10 +54,16 @@ export class TableManager {
53
54
  await this.page.getByTestId(TABLE_SELECTORS.columnSort[direction]).click();
54
55
  }
55
56
 
56
- public async addColumn({ label, format }: { label: string; format: string }): Promise<void> {
57
+ public async addRow(): Promise<void> {
58
+ await this.page.getByTestId(TABLE_SELECTORS.addRow).click();
59
+ }
60
+
61
+ public async addColumn({ label, format }: { label: string; format?: string }): Promise<void> {
57
62
  await this.page.getByTestId(TABLE_SELECTORS.newColumnButton).click();
58
- await this.page.getByRole('combobox').click();
59
- await this.page.getByLabel(format).click();
63
+ if (format) {
64
+ await this.page.getByRole('combobox').click();
65
+ await this.page.getByLabel(format).click();
66
+ }
60
67
  await this.page.getByPlaceholder('Property label').click();
61
68
  await this.page.getByPlaceholder('Property label').fill(label);
62
69
  await this.page.getByRole('button', { name: 'Save' }).click();
@@ -10,7 +10,7 @@ export default defineConfig({
10
10
  ...e2ePreset(import.meta.dirname),
11
11
  // TODO(wittjosiah): Avoid hard-coding ports.
12
12
  webServer: {
13
- command: 'moon run storybook-react:serve-e2e -- --port=9004',
13
+ command: 'pnpm storybook dev --ci --quiet --port=9004 --config-dir=.storybook',
14
14
  port: 9004,
15
15
  reuseExistingServer: false,
16
16
  },
@@ -121,8 +121,22 @@ test.describe('Table', () => {
121
121
  await page.close();
122
122
  });
123
123
 
124
- // Rest of add column test remains the same as it's a more complex flow.
125
- test('add column', async ({ browser, browserName }) => {
124
+ test('add column without changing format', async ({ browser, browserName }) => {
125
+ test.skip(browserName === 'webkit');
126
+ test.skip(browserName === 'firefox');
127
+ const { page } = await setupPage(browser, { url: storyUrl });
128
+ const table = new TableManager(page);
129
+
130
+ await table.grid.ready();
131
+ const newColumnLabel = 'DEFAULT FORMAT COL';
132
+
133
+ await table.addColumn({ label: newColumnLabel });
134
+
135
+ await expect(page.getByRole('gridcell', { name: newColumnLabel })).toBeVisible();
136
+ await page.close();
137
+ });
138
+
139
+ test('add column with format', async ({ browser, browserName }) => {
126
140
  test.skip(browserName === 'webkit');
127
141
  test.skip(browserName === 'firefox');
128
142
  const { page } = await setupPage(browser, { url: storyUrl });
@@ -160,6 +174,35 @@ test.describe('Table', () => {
160
174
  await page.close();
161
175
  });
162
176
 
177
+ test('add row and edit cell', async ({ browser, browserName }) => {
178
+ test.skip(browserName === 'webkit');
179
+ test.skip(browserName === 'firefox');
180
+ const { page } = await setupPage(browser, { url: storyUrl });
181
+ const table = new TableManager(page);
182
+
183
+ await table.grid.ready();
184
+
185
+ // Click the CTA row at the bottom to add a new row.
186
+ await table.grid.cell(0, 0, 'frozenRowsEnd').click();
187
+
188
+ // All schema fields are optional so the row is added directly to the grid (not as a draft).
189
+ const newCell = table.grid.cell(0, 10, 'grid');
190
+ await newCell.waitFor({ state: 'visible' });
191
+
192
+ // Focus should already be on the new row; engage edit mode.
193
+ await page.keyboard.press('Enter');
194
+ await page.getByTestId('grid.cell-editor').waitFor({ state: 'visible' });
195
+
196
+ // Type text and confirm.
197
+ const cellContent = 'New row content';
198
+ await page.keyboard.type(cellContent);
199
+ await page.keyboard.press('Enter');
200
+
201
+ // Assert the cell content is saved.
202
+ await expect(newCell).toHaveText(cellContent);
203
+ await page.close();
204
+ });
205
+
163
206
  // TODO(wittjosiah): Remove? Conflicts with story play function which is run as a unit test anyways.
164
207
  test.skip('extant relations work as expected', async ({ browser, browserName }) => {
165
208
  test.skip(browserName === 'webkit');
@@ -20,7 +20,7 @@ import { Table } from '../types';
20
20
 
21
21
  faker.seed(0); // NOTE(ZaymonFC): Required for smoke tests.
22
22
 
23
- export type TestTableModel<T extends Type.Obj.Any = Type.Obj.Any> = {
23
+ export type TestTableModel<T extends Type.AnyObj = Type.AnyObj> = {
24
24
  schema: T | undefined;
25
25
  table: Table.Table | undefined;
26
26
  projection: ProjectionModel | undefined;
@@ -38,7 +38,7 @@ export type TestTableModel<T extends Type.Obj.Any = Type.Obj.Any> = {
38
38
  * Custom hook to create and manage a test table model for storybook demonstrations.
39
39
  * Provides table data, schema, and handlers for table operations.
40
40
  */
41
- export const useTestTableModel = <T extends Type.Obj.Any = Type.Obj.Any>(): TestTableModel<T> => {
41
+ export const useTestTableModel = <T extends Type.AnyObj = Type.AnyObj>(): TestTableModel<T> => {
42
42
  const registry = useContext(RegistryContext);
43
43
  const { space } = useClientStory();
44
44
  const db = space?.db;
@@ -97,7 +97,7 @@ export const useTestTableModel = <T extends Type.Obj.Any = Type.Obj.Any>(): Test
97
97
  rows: filteredObjects,
98
98
  onInsertRow: addRow,
99
99
  onDeleteRows: handleDeleteRows,
100
- onDeleteColumn: handleDeleteColumn,
100
+ onColumnDelete: handleDeleteColumn,
101
101
  onCellUpdate: handleCellUpdate,
102
102
  onRowOrderChange: handleRowOrderChange,
103
103
  });
@@ -6,14 +6,15 @@ import * as Match from 'effect/Match';
6
6
  import * as Schema from 'effect/Schema';
7
7
  import * as SchemaAST from 'effect/SchemaAST';
8
8
 
9
- import { Obj, Ref, Type } from '@dxos/echo';
9
+ import { Annotation, JsonSchema, Obj, Ref, Type } from '@dxos/echo';
10
+ import { View } from '@dxos/echo';
10
11
  import { FormInputAnnotation, type JsonPath, type JsonSchemaType, LabelAnnotation } from '@dxos/echo/internal';
11
- import { View, ViewAnnotation } from '@dxos/schema';
12
+ import { ViewAnnotation } from '@dxos/schema';
12
13
 
13
14
  export const Table = Schema.Struct({
14
15
  name: Schema.String.pipe(Schema.optional),
15
16
 
16
- view: Type.Ref(View.View).pipe(FormInputAnnotation.set(false)),
17
+ view: Ref.Ref(View.View).pipe(FormInputAnnotation.set(false)),
17
18
 
18
19
  sizes: Schema.Record({
19
20
  // TODO(wittjosiah): Should be JsonPath.
@@ -22,11 +23,15 @@ export const Table = Schema.Struct({
22
23
  }).pipe(Schema.mutable, FormInputAnnotation.set(false)),
23
24
  }).pipe(
24
25
  Type.object({
25
- typename: 'dxos.org/type/Table',
26
- version: '0.2.0',
26
+ typename: 'org.dxos.type.table',
27
+ version: '0.1.0',
27
28
  }),
28
29
  LabelAnnotation.set(['name']),
29
30
  ViewAnnotation.set(true),
31
+ Annotation.IconAnnotation.set({
32
+ icon: 'ph--table--regular',
33
+ hue: 'green',
34
+ }),
30
35
  );
31
36
 
32
37
  export interface Table extends Schema.Schema.Type<typeof Table> {}
@@ -47,7 +52,7 @@ export const make = ({ name, sizes = {}, view, jsonSchema }: MakeProps): Table =
47
52
 
48
53
  // Preset sizes.
49
54
  if (jsonSchema) {
50
- const schema = Type.toEffectSchema(jsonSchema);
55
+ const schema = JsonSchema.toEffectSchema(jsonSchema);
51
56
  const properties = SchemaAST.getPropertySignatures(schema.ast);
52
57
  for (const property of properties) {
53
58
  const name = property.name.toString() as JsonPath;
@@ -72,19 +77,3 @@ export const make = ({ name, sizes = {}, view, jsonSchema }: MakeProps): Table =
72
77
 
73
78
  return table;
74
79
  };
75
-
76
- //
77
- // V1
78
- //
79
-
80
- export const TableV1 = Schema.Struct({
81
- sizes: Schema.Record({
82
- key: Schema.String,
83
- value: Schema.Number,
84
- }).pipe(Schema.mutable),
85
- }).pipe(
86
- Type.object({
87
- typename: 'dxos.org/type/Table',
88
- version: '0.1.0',
89
- }),
90
- );
@@ -0,0 +1,70 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Registry } from '@effect-atom/atom-react';
6
+ import { describe, expect, test } from 'vitest';
7
+
8
+ import { Format } from '@dxos/echo';
9
+
10
+ import { type TablePropertyDefinition, getBaseSchema, makeDynamicTable } from './dynamic-table';
11
+
12
+ describe('makeDynamicTable', () => {
13
+ /**
14
+ * Base case: plain jsonSchema (not from Echo / JsonSchema.toJsonSchema). Does not exercise the path
15
+ * where projection or schema are reactive, so this does not reproduce the Obj.change regression.
16
+ * Confirms makeDynamicTable + setProperties (title) works with plain objects.
17
+ */
18
+ test('makeDynamicTable with plain jsonSchema and properties with title does not throw', () => {
19
+ const registry = Registry.make();
20
+ const jsonSchema = {
21
+ type: 'object' as const,
22
+ properties: {
23
+ id: { type: 'string' as const },
24
+ name: { type: 'string' as const },
25
+ },
26
+ required: ['id'],
27
+ };
28
+ const properties: TablePropertyDefinition[] = [
29
+ { name: 'id', format: Format.TypeFormat.String },
30
+ { name: 'name', format: Format.TypeFormat.String, title: 'Name' },
31
+ ];
32
+
33
+ expect(() => {
34
+ const { projection, object } = makeDynamicTable({
35
+ registry,
36
+ jsonSchema,
37
+ properties,
38
+ });
39
+ expect(projection).toBeDefined();
40
+ expect(object).toBeDefined();
41
+ expect(projection.getFields().length).toBeGreaterThan(0);
42
+ }).not.toThrow();
43
+ });
44
+
45
+ /**
46
+ * Regression test for the path where jsonSchema comes from getBaseSchema(typename, properties).
47
+ * That path uses Echo (ViewModel.make, JsonSchema.toJsonSchema); mutations must run inside Obj.change and on a
48
+ * cloned schema. This test ensures that flow does not throw.
49
+ */
50
+ test('makeDynamicTable with jsonSchema from getBaseSchema(typename, properties) and properties with title does not throw', () => {
51
+ const registry = Registry.make();
52
+ const typename = 'com.example.type.dynamic-table-test';
53
+ const properties: TablePropertyDefinition[] = [
54
+ { name: 'id', format: Format.TypeFormat.String },
55
+ { name: 'name', format: Format.TypeFormat.String, title: 'Name' },
56
+ ];
57
+ const { jsonSchema } = getBaseSchema({ typename, properties });
58
+
59
+ expect(() => {
60
+ const { projection, object } = makeDynamicTable({
61
+ registry,
62
+ jsonSchema,
63
+ properties,
64
+ });
65
+ expect(projection).toBeDefined();
66
+ expect(object).toBeDefined();
67
+ expect(projection.getFields().length).toBeGreaterThan(0);
68
+ }).not.toThrow();
69
+ });
70
+ });
@@ -3,13 +3,15 @@
3
3
  //
4
4
 
5
5
  import { type Registry } from '@effect-atom/atom-react';
6
+ import type * as Types from 'effect/Types';
6
7
 
7
- import { Filter, type JsonSchema, Obj, Order, Query, type QueryAST, Ref, Type } from '@dxos/echo';
8
+ import { Filter, JsonSchema, Obj, Order, Query, type QueryAST, Ref, Type } from '@dxos/echo';
9
+ import { type View } from '@dxos/echo';
8
10
  import {
9
11
  ProjectionModel,
10
12
  type SchemaPropertyDefinition,
11
- View,
12
- createDirectChangeCallback,
13
+ ViewModel,
14
+ createEchoChangeCallback,
13
15
  getSchemaFromPropertyDefinitions,
14
16
  } from '@dxos/schema';
15
17
 
@@ -34,16 +36,16 @@ export const getBaseSchema = ({
34
36
  properties,
35
37
  jsonSchema,
36
38
  }: {
37
- schema?: Type.Entity.Any;
39
+ schema?: Type.AnyEntity;
38
40
  typename?: string;
39
41
  properties?: TablePropertyDefinition[];
40
- jsonSchema?: JsonSchema.JsonSchema;
41
- }): { typename: string; jsonSchema: JsonSchema.JsonSchema } => {
42
+ jsonSchema?: Types.DeepMutable<JsonSchema.JsonSchema>;
43
+ }): { typename: string; jsonSchema: Types.DeepMutable<JsonSchema.JsonSchema> } => {
42
44
  if (typename && properties) {
43
45
  const schema = getSchemaFromPropertyDefinitions(typename, properties);
44
- return { typename: schema.typename, jsonSchema: schema.jsonSchema };
46
+ return { typename: schema.typename, jsonSchema: JsonSchema.toJsonSchema(schema) };
45
47
  } else if (schema) {
46
- return { typename: Type.getTypename(schema)!, jsonSchema: Type.toJsonSchema(schema) };
48
+ return { typename: Type.getTypename(schema)!, jsonSchema: JsonSchema.toJsonSchema(schema) };
47
49
  } else if (typename && jsonSchema) {
48
50
  return { typename, jsonSchema };
49
51
  } else {
@@ -57,22 +59,21 @@ export const makeDynamicTable = ({
57
59
  properties,
58
60
  }: {
59
61
  registry: Registry.Registry;
60
- jsonSchema: JsonSchema.JsonSchema;
62
+ jsonSchema: Types.DeepMutable<JsonSchema.JsonSchema>;
61
63
  properties?: TablePropertyDefinition[];
62
64
  }): { projection: ProjectionModel; object: Table.Table } => {
63
- const view = View.make({
65
+ const view = ViewModel.make({
64
66
  query: Query.select(Filter.everything()),
65
67
  jsonSchema,
66
68
  ...(properties && { fields: properties.map((property) => property.name) }),
67
69
  });
68
70
  const object = Obj.make(Table.Table, { view: Ref.make(view), sizes: {} });
69
71
 
70
- // Use direct change callback for in-memory objects (non-ECHO backed).
71
72
  const projection = new ProjectionModel({
72
73
  registry,
73
74
  view,
74
75
  baseSchema: jsonSchema,
75
- change: createDirectChangeCallback(view.projection, jsonSchema),
76
+ change: createEchoChangeCallback(view, jsonSchema),
76
77
  });
77
78
  projection.normalizeView();
78
79
  if (properties && projection.getFields()) {
@@ -15,5 +15,8 @@ export const extractOrder = (queryAst: QueryAST.Query): readonly QueryAST.Order[
15
15
  if (queryAst.type === 'options') {
16
16
  return extractOrder(queryAst.query);
17
17
  }
18
+ if (queryAst.type === 'from') {
19
+ return extractOrder(queryAst.query);
20
+ }
18
21
  return undefined;
19
22
  };
@@ -39,7 +39,7 @@ const createButton = ({
39
39
  .map(([k, v]) => `${k}="${v}"`)
40
40
  .join(' ');
41
41
 
42
- return `<button ${attr} data-testid="${testId}" class="dx-button is-6 aspect-square pli-0.5 min-bs-0" ${dataAttrs} ${disabled ? 'disabled' : ''} data-dx-grid-action="accessory"><svg data-size="4"><use href="/icons.svg#${icon}"/></svg></button>`;
42
+ return `<button ${attr} data-testid="${testId}" class="dx-button w-6 aspect-square px-0.5 min-h-0" ${dataAttrs} ${disabled ? 'disabled' : ''} data-dx-grid-action="accessory"><svg data-size="4"><use href="/icons.svg#${icon}"/></svg></button>`;
43
43
  };
44
44
 
45
45
  const addColumnButton = {
@@ -25,8 +25,8 @@ export const CONTROL_IDENTIFIERS = {
25
25
  } as const;
26
26
 
27
27
  const BASE_CLASSES = {
28
- checkbox: 'absolute inset-block-[.375rem] inline-end-[.375rem] dx-checkbox',
29
- switch: 'absolute inset-block-[.25rem] inline-end-[.25rem] dx-checkbox--switch',
28
+ checkbox: 'absolute inset-y-[.375rem] end-[.375rem] dx-checkbox',
29
+ switch: 'absolute inset-y-[.25rem] end-[.25rem] dx-checkbox--switch',
30
30
  } as const;
31
31
 
32
32
  const renderAttributes = (data: Record<string, string>) => {