@dxos/plugin-sheet 0.6.12-main.c974201 → 0.6.12-main.f9d0246

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 (122) hide show
  1. package/dist/lib/browser/{SheetContainer-V4GCCZTX.mjs → SheetContainer-VISF3VUB.mjs} +6 -6
  2. package/dist/lib/browser/{SheetContainer-V4GCCZTX.mjs.map → SheetContainer-VISF3VUB.mjs.map} +3 -3
  3. package/dist/lib/browser/{chunk-T3NJFTD4.mjs → chunk-WZMOZKQZ.mjs} +2 -2
  4. package/dist/lib/browser/{chunk-T3NJFTD4.mjs.map → chunk-WZMOZKQZ.mjs.map} +3 -3
  5. package/dist/lib/browser/{chunk-6ZMQVB4Z.mjs → chunk-Z2XOOC2R.mjs} +81 -62
  6. package/dist/lib/browser/chunk-Z2XOOC2R.mjs.map +7 -0
  7. package/dist/lib/browser/{chunk-U2JHW3L6.mjs → chunk-ZLJ2GRE2.mjs} +173 -42
  8. package/dist/lib/browser/chunk-ZLJ2GRE2.mjs.map +7 -0
  9. package/dist/lib/browser/{graph-T27BOBOV.mjs → graph-4XFKIHRL.mjs} +4 -4
  10. package/dist/lib/browser/index.mjs +15 -13
  11. package/dist/lib/browser/index.mjs.map +3 -3
  12. package/dist/lib/browser/meta.json +1 -1
  13. package/dist/lib/browser/types.mjs +1 -1
  14. package/dist/lib/node/{SheetContainer-3ZY7MPWJ.cjs → SheetContainer-2MEALQWW.cjs} +14 -14
  15. package/dist/lib/node/{SheetContainer-3ZY7MPWJ.cjs.map → SheetContainer-2MEALQWW.cjs.map} +3 -3
  16. package/dist/lib/node/{chunk-OTTD7FBK.cjs → chunk-6DQABRGJ.cjs} +192 -60
  17. package/dist/lib/node/chunk-6DQABRGJ.cjs.map +7 -0
  18. package/dist/lib/node/{chunk-Q3HBHPRL.cjs → chunk-AOP42UAA.cjs} +5 -5
  19. package/dist/lib/node/{chunk-Q3HBHPRL.cjs.map → chunk-AOP42UAA.cjs.map} +3 -3
  20. package/dist/lib/node/{chunk-DD6FIXWC.cjs → chunk-P5QYYEHQ.cjs} +86 -67
  21. package/dist/lib/node/chunk-P5QYYEHQ.cjs.map +7 -0
  22. package/dist/lib/node/{graph-SPKGX7W4.cjs → graph-2LRDUXBZ.cjs} +14 -14
  23. package/dist/lib/node/graph-2LRDUXBZ.cjs.map +7 -0
  24. package/dist/lib/node/index.cjs +25 -24
  25. package/dist/lib/node/index.cjs.map +3 -3
  26. package/dist/lib/node/meta.json +1 -1
  27. package/dist/lib/node/types.cjs +8 -8
  28. package/dist/lib/node/types.cjs.map +1 -1
  29. package/dist/lib/node-esm/{SheetContainer-PXSJX6XK.mjs → SheetContainer-RPSUSXWS.mjs} +6 -6
  30. package/dist/lib/node-esm/{SheetContainer-PXSJX6XK.mjs.map → SheetContainer-RPSUSXWS.mjs.map} +3 -3
  31. package/dist/lib/node-esm/{chunk-D6KU5MI7.mjs → chunk-4MM7THJW.mjs} +81 -62
  32. package/dist/lib/node-esm/chunk-4MM7THJW.mjs.map +7 -0
  33. package/dist/lib/node-esm/{chunk-7HVSOTGA.mjs → chunk-5RLTCIE2.mjs} +173 -42
  34. package/dist/lib/node-esm/chunk-5RLTCIE2.mjs.map +7 -0
  35. package/dist/lib/node-esm/{chunk-BMNA27EX.mjs → chunk-RR2AO4SM.mjs} +2 -2
  36. package/dist/lib/node-esm/{chunk-BMNA27EX.mjs.map → chunk-RR2AO4SM.mjs.map} +3 -3
  37. package/dist/lib/node-esm/{graph-U67IO4UC.mjs → graph-WG5EKOMO.mjs} +4 -4
  38. package/dist/lib/node-esm/index.mjs +15 -13
  39. package/dist/lib/node-esm/index.mjs.map +3 -3
  40. package/dist/lib/node-esm/meta.json +1 -1
  41. package/dist/lib/node-esm/types.mjs +1 -1
  42. package/dist/types/src/SheetPlugin.d.ts.map +1 -1
  43. package/dist/types/src/components/GridSheet/GridSheet.d.ts +3 -3
  44. package/dist/types/src/components/GridSheet/GridSheet.d.ts.map +1 -1
  45. package/dist/types/src/components/GridSheet/GridSheet.stories.d.ts +1 -1
  46. package/dist/types/src/components/GridSheet/GridSheet.stories.d.ts.map +1 -1
  47. package/dist/types/src/components/GridSheet/util.d.ts +3 -2
  48. package/dist/types/src/components/GridSheet/util.d.ts.map +1 -1
  49. package/dist/types/src/components/Sheet/Sheet.stories.d.ts.map +1 -1
  50. package/dist/types/src/components/Sheet/sheet-context.d.ts +3 -3
  51. package/dist/types/src/components/Sheet/sheet-context.d.ts.map +1 -1
  52. package/dist/types/src/components/SheetContainer.d.ts +1 -1
  53. package/dist/types/src/components/index.d.ts +1 -1
  54. package/dist/types/src/defs/types.d.ts.map +1 -1
  55. package/dist/types/src/defs/util.d.ts +1 -1
  56. package/dist/types/src/defs/util.d.ts.map +1 -1
  57. package/dist/types/src/extensions/compute.d.ts +5 -1
  58. package/dist/types/src/extensions/compute.d.ts.map +1 -1
  59. package/dist/types/src/extensions/compute.stories.d.ts.map +1 -1
  60. package/dist/types/src/graph/async-function.d.ts +7 -1
  61. package/dist/types/src/graph/async-function.d.ts.map +1 -1
  62. package/dist/types/src/graph/compute-graph.d.ts +12 -9
  63. package/dist/types/src/graph/compute-graph.d.ts.map +1 -1
  64. package/dist/types/src/graph/compute-graph.stories.d.ts.map +1 -1
  65. package/dist/types/src/graph/compute-graph.test.d.ts +2 -0
  66. package/dist/types/src/graph/compute-graph.test.d.ts.map +1 -0
  67. package/dist/types/src/graph/compute-node.d.ts +9 -2
  68. package/dist/types/src/graph/compute-node.d.ts.map +1 -1
  69. package/dist/types/src/graph/edge-function.d.ts.map +1 -1
  70. package/dist/types/src/graph/{custom-function.d.ts → testing/custom-function.d.ts} +3 -1
  71. package/dist/types/src/graph/testing/custom-function.d.ts.map +1 -0
  72. package/dist/types/src/graph/testing/index.d.ts +2 -0
  73. package/dist/types/src/graph/testing/index.d.ts.map +1 -0
  74. package/dist/types/src/hooks/useComputeGraph.d.ts.map +1 -1
  75. package/dist/types/src/hooks/useSheetModel.d.ts +2 -2
  76. package/dist/types/src/hooks/useSheetModel.d.ts.map +1 -1
  77. package/dist/types/src/model/sheet-model.d.ts +3 -3
  78. package/dist/types/src/model/sheet-model.d.ts.map +1 -1
  79. package/dist/types/src/testing/testing.d.ts +4 -5
  80. package/dist/types/src/testing/testing.d.ts.map +1 -1
  81. package/dist/types/src/types.d.ts +4 -3
  82. package/dist/types/src/types.d.ts.map +1 -1
  83. package/package.json +33 -33
  84. package/src/SheetPlugin.tsx +9 -7
  85. package/src/components/CellEditor/CellEditor.stories.tsx +1 -1
  86. package/src/components/GridSheet/GridSheet.stories.tsx +5 -4
  87. package/src/components/GridSheet/GridSheet.tsx +6 -6
  88. package/src/components/GridSheet/util.ts +46 -27
  89. package/src/components/Sheet/Sheet.stories.tsx +21 -20
  90. package/src/components/Sheet/sheet-context.tsx +4 -4
  91. package/src/components/SheetContainer.tsx +2 -2
  92. package/src/defs/types.ts +1 -0
  93. package/src/defs/util.ts +19 -3
  94. package/src/extensions/compute.stories.tsx +18 -16
  95. package/src/extensions/compute.ts +72 -39
  96. package/src/graph/async-function.ts +13 -6
  97. package/src/graph/compute-graph.stories.tsx +4 -3
  98. package/src/graph/compute-graph.test.ts +127 -0
  99. package/src/graph/compute-graph.ts +64 -41
  100. package/src/graph/compute-node.ts +16 -5
  101. package/src/graph/edge-function.ts +1 -2
  102. package/src/graph/{custom-function.ts → testing/custom-function.ts} +10 -2
  103. package/src/graph/testing/index.ts +5 -0
  104. package/src/hooks/hooks.stories.tsx +3 -3
  105. package/src/hooks/useComputeGraph.ts +2 -1
  106. package/src/hooks/useSheetModel.ts +4 -7
  107. package/src/model/sheet-model.ts +44 -29
  108. package/src/testing/testing.tsx +17 -15
  109. package/src/types.ts +3 -3
  110. package/dist/lib/browser/chunk-6ZMQVB4Z.mjs.map +0 -7
  111. package/dist/lib/browser/chunk-U2JHW3L6.mjs.map +0 -7
  112. package/dist/lib/node/chunk-DD6FIXWC.cjs.map +0 -7
  113. package/dist/lib/node/chunk-OTTD7FBK.cjs.map +0 -7
  114. package/dist/lib/node/graph-SPKGX7W4.cjs.map +0 -7
  115. package/dist/lib/node-esm/chunk-7HVSOTGA.mjs.map +0 -7
  116. package/dist/lib/node-esm/chunk-D6KU5MI7.mjs.map +0 -7
  117. package/dist/types/src/graph/compute-graph.browser.test.d.ts +0 -2
  118. package/dist/types/src/graph/compute-graph.browser.test.d.ts.map +0 -1
  119. package/dist/types/src/graph/custom-function.d.ts.map +0 -1
  120. package/src/graph/compute-graph.browser.test.ts +0 -104
  121. /package/dist/lib/browser/{graph-T27BOBOV.mjs.map → graph-4XFKIHRL.mjs.map} +0 -0
  122. /package/dist/lib/node-esm/{graph-U67IO4UC.mjs.map → graph-WG5EKOMO.mjs.map} +0 -0
@@ -2,13 +2,20 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { useEffect, useState } from 'react';
5
+ import { type MutableRefObject, useEffect, useLayoutEffect, useState } from 'react';
6
6
 
7
7
  import { createDocAccessor } from '@dxos/react-client/echo';
8
- import { type GridEditing, type GridContentProps } from '@dxos/react-ui-grid';
8
+ import {
9
+ type GridEditing,
10
+ type GridContentProps,
11
+ type DxGridElement,
12
+ type DxGridCells,
13
+ type DxGridAxisMeta,
14
+ type DxGridRange,
15
+ } from '@dxos/react-ui-grid';
9
16
  import { mx } from '@dxos/react-ui-theme';
10
17
 
11
- import { addressFromIndex, type CellAddress } from '../../defs';
18
+ import { type CellAddress } from '../../defs';
12
19
  import { type SheetModel, type FormattingModel } from '../../model';
13
20
 
14
21
  export const dxGridCellIndexToSheetCellAddress = (gridEditing: GridEditing): CellAddress | null => {
@@ -22,19 +29,8 @@ export const dxGridCellIndexToSheetCellAddress = (gridEditing: GridEditing): Cel
22
29
  };
23
30
  };
24
31
 
25
- const createDxGridCells = (model: SheetModel, formatting: FormattingModel) => {
26
- return Object.keys(model.sheet.cells).reduce((acc: NonNullable<GridContentProps['cells']>, sheetCellIndex) => {
27
- const address = addressFromIndex(model.sheet, sheetCellIndex);
28
- const cell = formatting.getFormatting(address);
29
- if (cell.value) {
30
- acc[`${address.col},${address.row}`] = { value: cell.value, className: mx(cell.classNames) };
31
- }
32
- return acc;
33
- }, {});
34
- };
35
-
36
- const createDxGridColumns = (model: SheetModel): GridContentProps['columns'] => {
37
- return model.sheet.columns.reduce((acc: NonNullable<GridContentProps['columns']>, columnId, numericIndex) => {
32
+ const createDxGridColumns = (model: SheetModel): DxGridAxisMeta => {
33
+ return model.sheet.columns.reduce((acc: DxGridAxisMeta, columnId, numericIndex) => {
38
34
  if (model.sheet.columnMeta[columnId] && model.sheet.columnMeta[columnId].size) {
39
35
  acc[numericIndex] = { size: model.sheet.columnMeta[columnId].size, resizeable: true };
40
36
  }
@@ -42,8 +38,8 @@ const createDxGridColumns = (model: SheetModel): GridContentProps['columns'] =>
42
38
  }, {});
43
39
  };
44
40
 
45
- const createDxGridRows = (model: SheetModel): GridContentProps['rows'] => {
46
- return model.sheet.rows.reduce((acc: NonNullable<GridContentProps['rows']>, rowId, numericIndex) => {
41
+ const createDxGridRows = (model: SheetModel): DxGridAxisMeta => {
42
+ return model.sheet.rows.reduce((acc: DxGridAxisMeta, rowId, numericIndex) => {
47
43
  if (model.sheet.rowMeta[rowId] && model.sheet.rowMeta[rowId].size) {
48
44
  acc[numericIndex] = { size: model.sheet.rowMeta[rowId].size, resizeable: true };
49
45
  }
@@ -51,18 +47,41 @@ const createDxGridRows = (model: SheetModel): GridContentProps['rows'] => {
51
47
  }, {});
52
48
  };
53
49
 
50
+ const cellGetter = (model: SheetModel, formatting: FormattingModel) => {
51
+ // TODO(thure): Actually use the cache.
52
+ let _cachedBounds: DxGridRange | null = null;
53
+ const cachedCells: DxGridCells = {};
54
+ return (nextBounds: DxGridRange): DxGridCells => {
55
+ [...Array(nextBounds.end.col - nextBounds.start.col)].forEach((_, c0) => {
56
+ return [...Array(nextBounds.end.row - nextBounds.start.row)].forEach((_, r0) => {
57
+ const col = nextBounds.start.col + c0;
58
+ const row = nextBounds.start.row + r0;
59
+ const cell = formatting.getFormatting({ col, row });
60
+ if (cell.value) {
61
+ cachedCells[`${col},${row}`] = { value: cell.value, className: mx(cell.classNames) };
62
+ }
63
+ });
64
+ });
65
+ _cachedBounds = nextBounds;
66
+ return cachedCells;
67
+ };
68
+ };
69
+
54
70
  export const useSheetModelDxGridProps = (
71
+ dxGridRef: MutableRefObject<DxGridElement | null>,
55
72
  model: SheetModel,
56
73
  formatting: FormattingModel,
57
- ): Pick<GridContentProps, 'cells' | 'columns' | 'rows'> => {
58
- const [dxGridCells, setDxGridCells] = useState<GridContentProps['cells']>(createDxGridCells(model, formatting));
59
- const [dxGridColumns, setDxGridColumns] = useState<GridContentProps['columns']>(createDxGridColumns(model));
60
- const [dxGridRows, setDxGridRows] = useState<GridContentProps['rows']>(createDxGridColumns(model));
74
+ ): Pick<GridContentProps, 'columns' | 'rows'> => {
75
+ const [columns, setColumns] = useState<DxGridAxisMeta>(createDxGridColumns(model));
76
+ const [rows, setRows] = useState<DxGridAxisMeta>(createDxGridColumns(model));
61
77
 
62
- useEffect(() => {
78
+ useLayoutEffect(() => {
63
79
  const cellsAccessor = createDocAccessor(model.sheet, ['cells']);
80
+ if (dxGridRef.current) {
81
+ dxGridRef.current.getCells = cellGetter(model, formatting);
82
+ }
64
83
  const handleCellsUpdate = () => {
65
- setDxGridCells(createDxGridCells(model, formatting));
84
+ dxGridRef.current?.requestUpdate('initialCells');
66
85
  };
67
86
  cellsAccessor.handle.addListener('change', handleCellsUpdate);
68
87
  return () => cellsAccessor.handle.removeListener('change', handleCellsUpdate);
@@ -72,10 +91,10 @@ export const useSheetModelDxGridProps = (
72
91
  const columnMetaAccessor = createDocAccessor(model.sheet, ['columnMeta']);
73
92
  const rowMetaAccessor = createDocAccessor(model.sheet, ['rowMeta']);
74
93
  const handleColumnMetaUpdate = () => {
75
- setDxGridColumns(createDxGridColumns(model));
94
+ setColumns(createDxGridColumns(model));
76
95
  };
77
96
  const handleRowMetaUpdate = () => {
78
- setDxGridRows(createDxGridRows(model));
97
+ setRows(createDxGridRows(model));
79
98
  };
80
99
  columnMetaAccessor.handle.addListener('change', handleColumnMetaUpdate);
81
100
  rowMetaAccessor.handle.addListener('change', handleRowMetaUpdate);
@@ -85,5 +104,5 @@ export const useSheetModelDxGridProps = (
85
104
  };
86
105
  }, [model]);
87
106
 
88
- return { cells: dxGridCells, columns: dxGridColumns, rows: dxGridRows };
107
+ return { columns, rows };
89
108
  };
@@ -7,7 +7,7 @@ import '@dxos-theme';
7
7
  import React, { useState } from 'react';
8
8
 
9
9
  import { log } from '@dxos/log';
10
- import { type Space, useSpace } from '@dxos/react-client/echo';
10
+ import { useSpace } from '@dxos/react-client/echo';
11
11
  import { withClientProvider } from '@dxos/react-client/testing';
12
12
  import { Button } from '@dxos/react-ui';
13
13
  import { mx } from '@dxos/react-ui-theme';
@@ -17,18 +17,19 @@ import { Sheet } from './Sheet';
17
17
  import { type SizeMap } from './grid';
18
18
  import { useSheetContext } from './sheet-context';
19
19
  import { addressToIndex, rangeToIndex } from '../../defs';
20
+ import { type ComputeGraph } from '../../graph';
21
+ import { testPlugins } from '../../graph/testing';
20
22
  import { useComputeGraph } from '../../hooks';
21
- import { useTestSheet, withGraphDecorator } from '../../testing';
23
+ import { createTestCells, useTestSheet, withComputeGraphDecorator } from '../../testing';
22
24
  import { SheetType, ValueTypeEnum } from '../../types';
23
25
  import { Toolbar, type ToolbarActionHandler } from '../Toolbar';
24
26
 
25
27
  // TODO(burdon): Allow toolbar to access sheet context; provide state for current cursor/range.
26
- const SheetWithToolbar = ({ debug, space }: { debug?: boolean; space: Space }) => {
28
+ const SheetWithToolbar = ({ graph, debug }: { graph: ComputeGraph; debug?: boolean }) => {
27
29
  const { model, cursor, range } = useSheetContext();
28
30
 
29
- const graph = useComputeGraph(space);
30
31
  const handleRefresh = () => {
31
- graph?.refresh();
32
+ // graph?.refresh(); // TODO(burdon): ???
32
33
  };
33
34
 
34
35
  // TODO(burdon): Factor out.
@@ -104,7 +105,7 @@ export default {
104
105
  component: Sheet,
105
106
  decorators: [
106
107
  withClientProvider({ types: [SheetType], createIdentity: true }),
107
- withGraphDecorator,
108
+ withComputeGraphDecorator({ plugins: testPlugins }),
108
109
  withTheme,
109
110
  withLayout({ fullscreen: true, tooltips: true, classNames: 'inset-4' }),
110
111
  ],
@@ -114,14 +115,14 @@ export const Default = () => {
114
115
  const [debug, setDebug] = useState(false);
115
116
  const space = useSpace();
116
117
  const graph = useComputeGraph(space);
117
- const sheet = useTestSheet(space, graph);
118
- if (!space || !sheet) {
118
+ const sheet = useTestSheet(space, graph, { cells: createTestCells() });
119
+ if (!graph || !sheet) {
119
120
  return null;
120
121
  }
121
122
 
122
123
  return (
123
- <Sheet.Root space={space} sheet={sheet} onInfo={() => setDebug((debug) => !debug)}>
124
- <SheetWithToolbar debug={debug} space={space} />
124
+ <Sheet.Root graph={graph} sheet={sheet} onInfo={() => setDebug((debug) => !debug)}>
125
+ <SheetWithToolbar graph={graph} debug={debug} />
125
126
  </Sheet.Root>
126
127
  );
127
128
  };
@@ -129,13 +130,13 @@ export const Default = () => {
129
130
  export const Debug = () => {
130
131
  const space = useSpace();
131
132
  const graph = useComputeGraph(space);
132
- const sheet = useTestSheet(space, graph);
133
- if (!sheet || !space) {
133
+ const sheet = useTestSheet(space, graph, { cells: createTestCells() });
134
+ if (!graph || !sheet) {
134
135
  return null;
135
136
  }
136
137
 
137
138
  return (
138
- <Sheet.Root space={space} sheet={sheet}>
139
+ <Sheet.Root graph={graph} sheet={sheet}>
139
140
  <Sheet.Main />
140
141
  <Sheet.Debug />
141
142
  </Sheet.Root>
@@ -147,12 +148,12 @@ export const Rows = () => {
147
148
  const space = useSpace();
148
149
  const graph = useComputeGraph(space);
149
150
  const sheet = useTestSheet(space, graph);
150
- if (!sheet || !space) {
151
+ if (!graph || !sheet) {
151
152
  return null;
152
153
  }
153
154
 
154
155
  return (
155
- <Sheet.Root space={space} sheet={sheet}>
156
+ <Sheet.Root graph={graph} sheet={sheet}>
156
157
  <Sheet.Rows
157
158
  rows={sheet.rows}
158
159
  sizes={rowSizes}
@@ -167,12 +168,12 @@ export const Columns = () => {
167
168
  const space = useSpace();
168
169
  const graph = useComputeGraph(space);
169
170
  const sheet = useTestSheet(space, graph);
170
- if (!sheet || !space) {
171
+ if (!graph || !sheet) {
171
172
  return null;
172
173
  }
173
174
 
174
175
  return (
175
- <Sheet.Root space={space} sheet={sheet}>
176
+ <Sheet.Root graph={graph} sheet={sheet}>
176
177
  <Sheet.Columns
177
178
  columns={sheet.columns}
178
179
  sizes={columnSizes}
@@ -185,13 +186,13 @@ export const Columns = () => {
185
186
  export const Main = () => {
186
187
  const space = useSpace();
187
188
  const graph = useComputeGraph(space);
188
- const sheet = useTestSheet(space, graph);
189
- if (!sheet || !space) {
189
+ const sheet = useTestSheet(space, graph, { cells: createTestCells() });
190
+ if (!graph || !sheet) {
190
191
  return null;
191
192
  }
192
193
 
193
194
  return (
194
- <Sheet.Root space={space} sheet={sheet}>
195
+ <Sheet.Root graph={graph} sheet={sheet}>
195
196
  <Sheet.Grid
196
197
  size={{
197
198
  numRows: 50,
@@ -5,10 +5,10 @@
5
5
  import React, { type PropsWithChildren, createContext, useContext, useMemo, useState } from 'react';
6
6
 
7
7
  import { invariant } from '@dxos/invariant';
8
- import { type Space } from '@dxos/react-client/echo';
9
8
 
10
9
  import { createDecorations } from './decorations';
11
10
  import { type CellAddress, type CellRange } from '../../defs';
11
+ import { type ComputeGraph } from '../../graph';
12
12
  import { useSheetModel, useFormattingModel } from '../../hooks';
13
13
  import { type FormattingModel, type SheetModel } from '../../model';
14
14
  import { type SheetType } from '../../types';
@@ -45,19 +45,19 @@ export const useSheetContext = (): SheetContextType => {
45
45
  };
46
46
 
47
47
  export type SheetContextProps = {
48
+ graph: ComputeGraph;
48
49
  sheet: SheetType;
49
- space: Space;
50
50
  readonly?: boolean;
51
51
  } & Pick<SheetContextType, 'onInfo'>;
52
52
 
53
53
  export const SheetContextProvider = ({
54
54
  children,
55
+ graph,
55
56
  sheet,
56
- space,
57
57
  readonly,
58
58
  onInfo,
59
59
  }: PropsWithChildren<SheetContextProps>) => {
60
- const model = useSheetModel(space, sheet, { readonly });
60
+ const model = useSheetModel(graph, sheet, { readonly });
61
61
  const formatting = useFormattingModel(model);
62
62
 
63
63
  // TODO(Zan): Impl. set range and set cursor that scrolls to that cell or range if it is not visible.
@@ -22,7 +22,7 @@ const attentionFragment = mx(
22
22
  export const sectionToolbarLayout =
23
23
  'bs-[--rail-action] bg-[--sticky-bg] sticky block-start-0 __-block-start-px transition-opacity';
24
24
 
25
- const SheetContainer = ({ sheet, space, role }: SheetRootProps & { role?: string }) => {
25
+ const SheetContainer = ({ graph, sheet, role }: SheetRootProps & { role?: string }) => {
26
26
  const dispatch = useIntentDispatcher();
27
27
 
28
28
  const id = fullyQualifiedId(sheet);
@@ -50,7 +50,7 @@ const SheetContainer = ({ sheet, space, role }: SheetRootProps & { role?: string
50
50
 
51
51
  return (
52
52
  <div role='none' className={role === 'article' ? 'row-span-2 grid grid-rows-subgrid' : undefined}>
53
- <Sheet.Root space={space} sheet={sheet}>
53
+ <Sheet.Root graph={graph} sheet={sheet}>
54
54
  <div role='none' className={mx('flex flex-0 justify-center overflow-x-auto')}>
55
55
  <Toolbar.Root
56
56
  onAction={handleAction}
package/src/defs/types.ts CHANGED
@@ -34,6 +34,7 @@ export const addressToA1Notation = ({ col, row }: CellAddress): string => {
34
34
  return `${columnLetter(col)}${row + 1}`;
35
35
  };
36
36
 
37
+ // TODO(burdon): See simpleCellAddressFromString
37
38
  export const addressFromA1Notation = (ref: string): CellAddress => {
38
39
  const match = ref.match(/([A-Z]+)(\d+)/);
39
40
  invariant(match, `Invalid notation: ${ref}`);
package/src/defs/util.ts CHANGED
@@ -5,7 +5,15 @@
5
5
  import { randomBytes } from '@dxos/crypto';
6
6
  import { create } from '@dxos/echo-schema';
7
7
 
8
- import { type CellAddress, type CellRange, DEFAULT_COLUMNS, DEFAULT_ROWS, MAX_COLUMNS, MAX_ROWS } from './types';
8
+ import {
9
+ addressFromA1Notation,
10
+ type CellAddress,
11
+ type CellRange,
12
+ DEFAULT_COLUMNS,
13
+ DEFAULT_ROWS,
14
+ MAX_COLUMNS,
15
+ MAX_ROWS,
16
+ } from './types';
9
17
  import { type CreateSheetOptions, type SheetSize, SheetType } from '../types';
10
18
 
11
19
  // TODO(burdon): Factor out from dxos/protocols to new common package.
@@ -52,9 +60,9 @@ export const initialize = (
52
60
  }
53
61
  };
54
62
 
55
- export const createSheet = ({ title, ...size }: CreateSheetOptions = {}): SheetType => {
63
+ export const createSheet = ({ name, cells, ...size }: CreateSheetOptions = {}): SheetType => {
56
64
  const sheet = create(SheetType, {
57
- title,
65
+ name,
58
66
  cells: {},
59
67
  rows: [],
60
68
  columns: [],
@@ -64,6 +72,14 @@ export const createSheet = ({ title, ...size }: CreateSheetOptions = {}): SheetT
64
72
  });
65
73
 
66
74
  initialize(sheet, size);
75
+
76
+ if (cells) {
77
+ Object.entries(cells).forEach(([key, { value }]) => {
78
+ const idx = addressToIndex(sheet, addressFromA1Notation(key));
79
+ sheet.cells[idx] = { value };
80
+ });
81
+ }
82
+
67
83
  return sheet;
68
84
  };
69
85
 
@@ -3,10 +3,11 @@
3
3
  //
4
4
 
5
5
  import '@dxos-theme';
6
- import React, { useEffect, useState } from 'react';
6
+ import React, { useEffect } from 'react';
7
7
 
8
8
  import { useSpace } from '@dxos/react-client/echo';
9
9
  import { withClientProvider } from '@dxos/react-client/testing';
10
+ import { useAsyncState } from '@dxos/react-hooks';
10
11
  import { useThemeContext } from '@dxos/react-ui';
11
12
  import {
12
13
  createBasicExtensions,
@@ -18,11 +19,11 @@ import {
18
19
  import { withTheme, withLayout } from '@dxos/storybook-utils';
19
20
  import { nonNullable } from '@dxos/util';
20
21
 
21
- import { compute } from './compute';
22
+ import { compute, computeNodeFacet } from './compute';
22
23
  import { Sheet } from '../components';
23
24
  import { type ComputeNode } from '../graph';
24
25
  import { useComputeGraph, useSheetModel } from '../hooks';
25
- import { useTestSheet, withGraphDecorator } from '../testing';
26
+ import { useTestSheet, withComputeGraphDecorator } from '../testing';
26
27
  import { SheetType } from '../types';
27
28
 
28
29
  const str = (...lines: string[]) => lines.join('\n');
@@ -34,6 +35,8 @@ type EditorProps = {
34
35
  // TODO(burdon): Implement named expressions.
35
36
  // https://hyperformula.handsontable.com/guide/cell-references.html
36
37
 
38
+ // TODO(burdon): Inline Adobe eCharts.
39
+
37
40
  const DOC_NAME = 'Test Doc';
38
41
  const SHEET_NAME = 'Test Sheet';
39
42
 
@@ -41,12 +44,8 @@ const Editor = ({ text }: EditorProps) => {
41
44
  const { themeMode } = useThemeContext();
42
45
  const space = useSpace();
43
46
  const graph = useComputeGraph(space);
44
- const [node, setNode] = useState<ComputeNode>();
45
- // TODO(burdon): Virtualize SheetModel.
46
- useEffect(() => {
47
- if (graph) {
48
- setNode(graph.getOrCreateNode(DOC_NAME));
49
- }
47
+ const [computeNode] = useAsyncState<ComputeNode>(async () => {
48
+ return graph ? await graph.getOrCreateNode(DOC_NAME) : undefined;
50
49
  }, [graph]);
51
50
  const { parentRef, focusAttributes } = useTextEditor(
52
51
  () => ({
@@ -55,11 +54,12 @@ const Editor = ({ text }: EditorProps) => {
55
54
  createBasicExtensions(),
56
55
  createMarkdownExtensions({ themeMode }),
57
56
  createThemeExtensions({ themeMode, syntaxHighlighting: true }),
58
- node && compute(node),
57
+ computeNode && computeNodeFacet.of(computeNode),
58
+ compute(),
59
59
  decorateMarkdown(),
60
60
  ].filter(nonNullable),
61
61
  }),
62
- [node, themeMode],
62
+ [computeNode, themeMode],
63
63
  );
64
64
 
65
65
  return <div className='w-[40rem] overflow-hidden' ref={parentRef} {...focusAttributes} />;
@@ -68,21 +68,21 @@ const Editor = ({ text }: EditorProps) => {
68
68
  const Grid = () => {
69
69
  const space = useSpace();
70
70
  const graph = useComputeGraph(space);
71
- const sheet = useTestSheet(space, graph, { title: SHEET_NAME });
72
- const model = useSheetModel(space, sheet);
71
+ const sheet = useTestSheet(space, graph, { name: SHEET_NAME });
72
+ const model = useSheetModel(graph, sheet);
73
73
  useEffect(() => {
74
74
  if (model) {
75
75
  model.setValues({ A1: { value: 100 }, A2: { value: 200 }, A3: { value: 300 }, A5: { value: '=SUM(A1:A3)' } });
76
76
  }
77
77
  }, [model]);
78
78
 
79
- if (!space || !sheet) {
79
+ if (!graph || !sheet) {
80
80
  return null;
81
81
  }
82
82
 
83
83
  return (
84
84
  <div className='flex w-[40rem] overflow-hidden'>
85
- <Sheet.Root space={space} sheet={sheet}>
85
+ <Sheet.Root graph={graph} sheet={sheet}>
86
86
  <Sheet.Main classNames='border border-separator' />
87
87
  </Sheet.Root>
88
88
  </div>
@@ -102,13 +102,14 @@ export default {
102
102
  title: 'plugin-sheet/extensions',
103
103
  decorators: [
104
104
  withClientProvider({ types: [SheetType], createIdentity: true, createSpace: true }),
105
- withGraphDecorator,
105
+ withComputeGraphDecorator(),
106
106
  withTheme,
107
107
  withLayout({ fullscreen: true, classNames: 'justify-center' }),
108
108
  ],
109
109
  parameters: { layout: 'fullscreen' },
110
110
  };
111
111
 
112
+ // TODO(burdon): Inline formulae.
112
113
  export const Default = {
113
114
  render: Editor,
114
115
  args: {
@@ -146,6 +147,7 @@ export const Graph = {
146
147
  `="${SHEET_NAME}"!A5`,
147
148
  '```',
148
149
  '',
150
+ '',
149
151
  ),
150
152
  },
151
153
  };
@@ -3,6 +3,7 @@
3
3
  //
4
4
 
5
5
  import { syntaxTree } from '@codemirror/language';
6
+ import { Facet } from '@codemirror/state';
6
7
  import {
7
8
  type EditorState,
8
9
  type Extension,
@@ -14,7 +15,21 @@ import {
14
15
  } from '@codemirror/state';
15
16
  import { Decoration, EditorView, ViewPlugin, WidgetType } from '@codemirror/view';
16
17
 
18
+ import { type UnsubscribeCallback } from '@dxos/async';
19
+ import { type Space } from '@dxos/client/echo';
20
+
21
+ import { type CellAddress } from '../defs';
17
22
  import { type ComputeNode } from '../graph';
23
+ import { type CellScalarValue } from '../types';
24
+
25
+ export const spaceFacet = Facet.define<Space, Space>({
26
+ combine: (values) => values[0],
27
+ });
28
+
29
+ // TODO(burdon): Create on demand?
30
+ export const computeNodeFacet = Facet.define<ComputeNode, ComputeNode>({
31
+ combine: (values) => values[0],
32
+ });
18
33
 
19
34
  const LANGUAGE_TAG = 'dx';
20
35
 
@@ -23,36 +38,46 @@ const updateAllDecorations = StateEffect.define<void>();
23
38
 
24
39
  export type ComputeOptions = {};
25
40
 
26
- export const compute = (computeNode: ComputeNode, options: ComputeOptions = {}): Extension => {
27
- const update = (state: EditorState) => {
28
- const builder = new RangeSetBuilder();
29
- syntaxTree(state).iterate({
30
- enter: (node) => {
31
- if (node.name === 'FencedCode') {
32
- const cursor = state.selection.main.head;
33
- if (state.readOnly || cursor < node.from || cursor > node.to) {
34
- const info = node.node.getChild('CodeInfo');
35
- if (info) {
36
- const type = state.sliceDoc(info.from, info.to);
37
- const text = node.node.getChild('CodeText');
38
- if (type === LANGUAGE_TAG && text) {
39
- const content = state.sliceDoc(text.from, text.to);
40
- // TODO(burdon): Map unique reference onto cell; e.g., track ordered list?
41
- computeNode.setValue({ col: 0, row: 0 }, content);
42
- const value = computeNode.getValue({ col: 0, row: 0 });
43
- builder.add(
44
- node.from,
45
- node.to,
46
- Decoration.replace({
47
- widget: new DxWidget(String(value)),
48
- }),
49
- );
41
+ export const compute = (options: ComputeOptions = {}): Extension => {
42
+ const update = (state: EditorState, rangeSet?: RangeSet<Decoration>) => {
43
+ const builder = new RangeSetBuilder<Decoration>();
44
+ const computeNode = state.facet(computeNodeFacet);
45
+ if (computeNode) {
46
+ computeNode.clear();
47
+ syntaxTree(state).iterate({
48
+ enter: (node) => {
49
+ if (node.name === 'FencedCode') {
50
+ const cursor = state.selection.main.head;
51
+ if (state.readOnly || cursor < node.from || cursor > node.to) {
52
+ const info = node.node.getChild('CodeInfo');
53
+ if (info) {
54
+ const type = state.sliceDoc(info.from, info.to);
55
+ const text = node.node.getChild('CodeText');
56
+ if (type === LANGUAGE_TAG && text) {
57
+ const formula = state.sliceDoc(text.from, text.to);
58
+ const iter = rangeSet?.iter(node.node.from);
59
+ if (iter?.value && iter?.value.spec.formula === formula) {
60
+ builder.add(node.from, node.to, iter.value);
61
+ } else {
62
+ const cell: CellAddress = { col: node.node.from, row: 0 };
63
+ computeNode.setValue(cell, formula);
64
+ const value = computeNode.getValue(cell);
65
+ builder.add(
66
+ node.from,
67
+ node.to,
68
+ Decoration.replace({
69
+ widget: new DxWidget(formula, value),
70
+ formula,
71
+ }),
72
+ );
73
+ }
74
+ }
50
75
  }
51
76
  }
52
77
  }
53
- }
54
- },
55
- });
78
+ },
79
+ });
80
+ }
56
81
 
57
82
  return builder.finish();
58
83
  };
@@ -61,38 +86,46 @@ export const compute = (computeNode: ComputeNode, options: ComputeOptions = {}):
61
86
  // Graph subscription.
62
87
  ViewPlugin.fromClass(
63
88
  class {
64
- private readonly _subscription: any;
89
+ private readonly _subscription?: UnsubscribeCallback;
65
90
  constructor(view: EditorView) {
66
- this._subscription = computeNode.graph.update.on(() => {
67
- view.dispatch({
68
- effects: updateAllDecorations.of(),
91
+ const computeNode = view.state.facet(computeNodeFacet);
92
+ if (computeNode) {
93
+ this._subscription = computeNode.update.on(({ type }) => {
94
+ if (type === 'valuesUpdated') {
95
+ view.dispatch({
96
+ effects: updateAllDecorations.of(),
97
+ });
98
+ }
69
99
  });
70
- });
100
+ }
71
101
  }
72
102
 
73
103
  destroy() {
74
- this._subscription();
104
+ this._subscription?.();
75
105
  }
76
106
  },
77
107
  ),
78
108
 
79
- // Decorations.
80
- StateField.define<RangeSet<any>>({
109
+ StateField.define<RangeSet<Decoration>>({
81
110
  create: (state) => update(state),
82
- update: (_: RangeSet<any>, tr: Transaction) => update(tr.state),
111
+ update: (rangeSet: RangeSet<Decoration>, tr: Transaction) => update(tr.state, rangeSet),
83
112
  provide: (field) => EditorView.decorations.from(field),
84
113
  }),
85
114
  ];
86
115
  };
87
116
 
88
117
  class DxWidget extends WidgetType {
89
- constructor(private readonly value: string) {
118
+ constructor(
119
+ private readonly formula: string,
120
+ private readonly value: CellScalarValue,
121
+ ) {
90
122
  super();
91
123
  }
92
124
 
93
- override toDOM(view: EditorView) {
125
+ override toDOM(_view: EditorView) {
94
126
  const div = document.createElement('div');
95
- div.innerText = this.value;
127
+ div.setAttribute('title', this.formula);
128
+ div.innerText = String(this.value);
96
129
  return div;
97
130
  }
98
131
  }