@dxos/plugin-sheet 0.6.14-main.7bd9c89 → 0.6.14-staging.3e2eaca

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 (232) hide show
  1. package/dist/lib/browser/{SheetContainer-AKWROARP.mjs → SheetContainer-P3NF5KEI.mjs} +90 -88
  2. package/dist/lib/browser/SheetContainer-P3NF5KEI.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-FGMFOW6U.mjs → chunk-4LKIURJA.mjs} +917 -817
  4. package/dist/lib/browser/chunk-4LKIURJA.mjs.map +7 -0
  5. package/dist/lib/browser/{chunk-BWN5DZWZ.mjs → chunk-BVUN7SHF.mjs} +6 -13
  6. package/dist/lib/browser/chunk-BVUN7SHF.mjs.map +7 -0
  7. package/dist/lib/browser/{chunk-D3QTX46O.mjs → chunk-RABELMEQ.mjs} +3 -2
  8. package/dist/lib/browser/{chunk-D3QTX46O.mjs.map → chunk-RABELMEQ.mjs.map} +3 -3
  9. package/dist/lib/browser/{chunk-GSV5QNLD.mjs → chunk-VMSX6Z4X.mjs} +297 -44
  10. package/dist/lib/browser/chunk-VMSX6Z4X.mjs.map +7 -0
  11. package/dist/lib/browser/{graph-M4IQ76QX.mjs → compute-graph-GGWUX644.mjs} +4 -2
  12. package/dist/lib/browser/index.mjs +91 -21
  13. package/dist/lib/browser/index.mjs.map +4 -4
  14. package/dist/lib/browser/meta.json +1 -1
  15. package/dist/lib/browser/meta.mjs +1 -1
  16. package/dist/lib/browser/types.mjs +2 -2
  17. package/dist/lib/node/{SheetContainer-N5IQGEFL.cjs → SheetContainer-MPFKXY26.cjs} +88 -93
  18. package/dist/lib/node/SheetContainer-MPFKXY26.cjs.map +7 -0
  19. package/dist/lib/node/{chunk-QIFIGEKV.cjs → chunk-2ZVZI2KJ.cjs} +6 -5
  20. package/dist/lib/node/{chunk-QIFIGEKV.cjs.map → chunk-2ZVZI2KJ.cjs.map} +3 -3
  21. package/dist/lib/node/{chunk-NZARD7UP.cjs → chunk-AWKOWDMI.cjs} +10 -17
  22. package/dist/lib/node/chunk-AWKOWDMI.cjs.map +7 -0
  23. package/dist/lib/node/{chunk-5XPK2V4A.cjs → chunk-O7XR4R7Y.cjs} +306 -42
  24. package/dist/lib/node/chunk-O7XR4R7Y.cjs.map +7 -0
  25. package/dist/lib/node/{chunk-53BMSUIK.cjs → chunk-OO24XJBV.cjs} +853 -767
  26. package/dist/lib/node/chunk-OO24XJBV.cjs.map +7 -0
  27. package/dist/lib/node/{graph-Q3N2X26H.cjs → compute-graph-KGWA2QLE.cjs} +21 -19
  28. package/dist/lib/node/compute-graph-KGWA2QLE.cjs.map +7 -0
  29. package/dist/lib/node/index.cjs +109 -42
  30. package/dist/lib/node/index.cjs.map +4 -4
  31. package/dist/lib/node/meta.cjs +3 -3
  32. package/dist/lib/node/meta.cjs.map +1 -1
  33. package/dist/lib/node/meta.json +1 -1
  34. package/dist/lib/node/types.cjs +7 -7
  35. package/dist/lib/node/types.cjs.map +1 -1
  36. package/dist/lib/node-esm/{SheetContainer-46PBMF2E.mjs → SheetContainer-22IOAW3B.mjs} +90 -88
  37. package/dist/lib/node-esm/SheetContainer-22IOAW3B.mjs.map +7 -0
  38. package/dist/lib/node-esm/{chunk-VCYJWE3O.mjs → chunk-BM2Q3FFC.mjs} +3 -2
  39. package/dist/lib/node-esm/{chunk-VCYJWE3O.mjs.map → chunk-BM2Q3FFC.mjs.map} +3 -3
  40. package/dist/lib/node-esm/{chunk-T3PRH7QS.mjs → chunk-BW36PM2Y.mjs} +917 -817
  41. package/dist/lib/node-esm/chunk-BW36PM2Y.mjs.map +7 -0
  42. package/dist/lib/node-esm/{chunk-5WPZCXNS.mjs → chunk-CR4K75EL.mjs} +296 -44
  43. package/dist/lib/node-esm/chunk-CR4K75EL.mjs.map +7 -0
  44. package/dist/lib/node-esm/{chunk-WFDTY3IC.mjs → chunk-UIBWRHW7.mjs} +6 -13
  45. package/dist/lib/node-esm/chunk-UIBWRHW7.mjs.map +7 -0
  46. package/dist/lib/node-esm/{graph-SMPUMOV2.mjs → compute-graph-2SCZT7N5.mjs} +4 -2
  47. package/dist/lib/node-esm/index.mjs +91 -21
  48. package/dist/lib/node-esm/index.mjs.map +4 -4
  49. package/dist/lib/node-esm/meta.json +1 -1
  50. package/dist/lib/node-esm/meta.mjs +1 -1
  51. package/dist/lib/node-esm/types.mjs +2 -2
  52. package/dist/types/src/SheetPlugin.d.ts.map +1 -1
  53. package/dist/types/src/components/ComputeGraph/ComputeGraphContextProvider.d.ts +3 -1
  54. package/dist/types/src/components/ComputeGraph/ComputeGraphContextProvider.d.ts.map +1 -1
  55. package/dist/types/src/components/FunctionEditor/FunctionEditor.d.ts.map +1 -1
  56. package/dist/types/src/components/GridSheet/GridSheet.d.ts.map +1 -1
  57. package/dist/types/src/components/GridSheet/GridSheet.stories.d.ts.map +1 -1
  58. package/dist/types/src/components/GridSheet/util.d.ts +2 -2
  59. package/dist/types/src/components/GridSheet/util.d.ts.map +1 -1
  60. package/dist/types/src/components/RangeList/RangeList.d.ts +7 -0
  61. package/dist/types/src/components/RangeList/RangeList.d.ts.map +1 -0
  62. package/dist/types/src/components/RangeList/index.d.ts +2 -0
  63. package/dist/types/src/components/RangeList/index.d.ts.map +1 -0
  64. package/dist/types/src/components/SheetContainer/SheetContainer.d.ts +6 -3
  65. package/dist/types/src/components/SheetContainer/SheetContainer.d.ts.map +1 -1
  66. package/dist/types/src/components/SheetContainer/SheetContainer.stories.d.ts.map +1 -1
  67. package/dist/types/src/components/SheetContext/SheetContext.d.ts +7 -5
  68. package/dist/types/src/components/SheetContext/SheetContext.d.ts.map +1 -1
  69. package/dist/types/src/components/Toolbar/Toolbar.d.ts +6 -3
  70. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  71. package/dist/types/src/components/index.d.ts +5 -2
  72. package/dist/types/src/components/index.d.ts.map +1 -1
  73. package/dist/types/src/compute-graph/compute-graph-registry.d.ts.map +1 -0
  74. package/dist/types/src/{graph → compute-graph}/compute-graph.d.ts +3 -3
  75. package/dist/types/src/compute-graph/compute-graph.d.ts.map +1 -0
  76. package/dist/types/src/compute-graph/compute-graph.stories.d.ts.map +1 -0
  77. package/dist/types/src/compute-graph/compute-graph.test.d.ts.map +1 -0
  78. package/dist/types/src/compute-graph/compute-node.d.ts.map +1 -0
  79. package/dist/types/src/compute-graph/functions/async-function.d.ts.map +1 -0
  80. package/dist/types/src/compute-graph/functions/edge-function.d.ts.map +1 -0
  81. package/dist/types/src/compute-graph/functions/function-defs.d.ts.map +1 -0
  82. package/dist/types/src/compute-graph/functions/index.d.ts.map +1 -0
  83. package/dist/types/src/compute-graph/hyperformula.test.d.ts.map +1 -0
  84. package/dist/types/src/compute-graph/index.d.ts.map +1 -0
  85. package/dist/types/src/compute-graph/testing/index.d.ts.map +1 -0
  86. package/dist/types/src/compute-graph/testing/test-builder.d.ts.map +1 -0
  87. package/dist/types/src/compute-graph/testing/test-plugin.d.ts.map +1 -0
  88. package/dist/types/src/compute-graph/util.d.ts.map +1 -0
  89. package/dist/types/src/defs/index.d.ts +1 -1
  90. package/dist/types/src/defs/index.d.ts.map +1 -1
  91. package/dist/types/src/defs/sheet-range-types.d.ts +2 -2
  92. package/dist/types/src/defs/sheet-range-types.d.ts.map +1 -1
  93. package/dist/types/src/defs/types.d.ts +6 -0
  94. package/dist/types/src/defs/types.d.ts.map +1 -1
  95. package/dist/types/src/defs/util.d.ts +3 -7
  96. package/dist/types/src/defs/util.d.ts.map +1 -1
  97. package/dist/types/src/extensions/compute.d.ts +1 -1
  98. package/dist/types/src/extensions/compute.d.ts.map +1 -1
  99. package/dist/types/src/extensions/editor/extension.d.ts +23 -3
  100. package/dist/types/src/extensions/editor/extension.d.ts.map +1 -1
  101. package/dist/types/src/index.d.ts +1 -1
  102. package/dist/types/src/index.d.ts.map +1 -1
  103. package/dist/types/src/integrations/index.d.ts +2 -0
  104. package/dist/types/src/integrations/index.d.ts.map +1 -0
  105. package/dist/types/src/integrations/thread-ranges.d.ts +7 -0
  106. package/dist/types/src/integrations/thread-ranges.d.ts.map +1 -0
  107. package/dist/types/src/meta.d.ts +1 -0
  108. package/dist/types/src/meta.d.ts.map +1 -1
  109. package/dist/types/src/model/index.d.ts +1 -1
  110. package/dist/types/src/model/index.d.ts.map +1 -1
  111. package/dist/types/src/model/sheet-model.d.ts +13 -6
  112. package/dist/types/src/model/sheet-model.d.ts.map +1 -1
  113. package/dist/types/src/{hooks → model}/useSheetModel.d.ts +1 -1
  114. package/dist/types/src/model/useSheetModel.d.ts.map +1 -0
  115. package/dist/types/src/serializer.d.ts +4 -0
  116. package/dist/types/src/serializer.d.ts.map +1 -0
  117. package/dist/types/src/testing/testing.d.ts +1 -1
  118. package/dist/types/src/testing/testing.d.ts.map +1 -1
  119. package/dist/types/src/translations.d.ts +18 -3
  120. package/dist/types/src/translations.d.ts.map +1 -1
  121. package/dist/types/src/types.d.ts +37 -33
  122. package/dist/types/src/types.d.ts.map +1 -1
  123. package/package.json +44 -50
  124. package/src/SheetPlugin.tsx +45 -12
  125. package/src/components/ComputeGraph/ComputeGraphContextProvider.tsx +19 -2
  126. package/src/components/FunctionEditor/FunctionEditor.tsx +6 -10
  127. package/src/components/GridSheet/GridSheet.stories.tsx +3 -1
  128. package/src/components/GridSheet/GridSheet.tsx +196 -36
  129. package/src/components/GridSheet/SheetCellEditor.stories.tsx +1 -1
  130. package/src/components/GridSheet/util.ts +41 -21
  131. package/src/components/RangeList/RangeList.tsx +53 -0
  132. package/src/components/RangeList/index.ts +5 -0
  133. package/src/components/SheetContainer/SheetContainer.stories.tsx +5 -3
  134. package/src/components/SheetContainer/SheetContainer.tsx +19 -12
  135. package/src/components/SheetContext/SheetContext.tsx +43 -20
  136. package/src/components/Toolbar/Toolbar.tsx +98 -86
  137. package/src/components/index.ts +1 -0
  138. package/src/{graph → compute-graph}/compute-graph.stories.tsx +2 -1
  139. package/src/{graph → compute-graph}/compute-graph.ts +27 -9
  140. package/src/{graph → compute-graph}/compute-node.ts +2 -3
  141. package/src/{graph → compute-graph}/functions/async-function.ts +1 -0
  142. package/src/{graph → compute-graph}/functions/edge-function.ts +5 -3
  143. package/src/defs/index.ts +1 -1
  144. package/src/defs/sheet-range-types.ts +9 -6
  145. package/src/defs/types.ts +6 -1
  146. package/src/defs/util.ts +4 -19
  147. package/src/extensions/compute.stories.tsx +2 -2
  148. package/src/extensions/compute.ts +2 -2
  149. package/src/extensions/editor/extension.test.ts +1 -1
  150. package/src/extensions/editor/extension.ts +48 -23
  151. package/src/index.ts +2 -2
  152. package/src/integrations/index.ts +5 -0
  153. package/src/integrations/thread-ranges.ts +101 -0
  154. package/src/meta.ts +1 -0
  155. package/src/model/index.ts +1 -1
  156. package/src/model/sheet-model.test.ts +6 -4
  157. package/src/model/sheet-model.ts +103 -26
  158. package/src/{hooks → model}/useSheetModel.ts +1 -1
  159. package/src/serializer.ts +27 -0
  160. package/src/testing/testing.tsx +1 -1
  161. package/src/translations.ts +18 -3
  162. package/src/types.ts +24 -2
  163. package/dist/lib/browser/SheetContainer-AKWROARP.mjs.map +0 -7
  164. package/dist/lib/browser/chunk-BWN5DZWZ.mjs.map +0 -7
  165. package/dist/lib/browser/chunk-FGMFOW6U.mjs.map +0 -7
  166. package/dist/lib/browser/chunk-GSV5QNLD.mjs.map +0 -7
  167. package/dist/lib/node/SheetContainer-N5IQGEFL.cjs.map +0 -7
  168. package/dist/lib/node/chunk-53BMSUIK.cjs.map +0 -7
  169. package/dist/lib/node/chunk-5XPK2V4A.cjs.map +0 -7
  170. package/dist/lib/node/chunk-NZARD7UP.cjs.map +0 -7
  171. package/dist/lib/node/graph-Q3N2X26H.cjs.map +0 -7
  172. package/dist/lib/node-esm/SheetContainer-46PBMF2E.mjs.map +0 -7
  173. package/dist/lib/node-esm/chunk-5WPZCXNS.mjs.map +0 -7
  174. package/dist/lib/node-esm/chunk-T3PRH7QS.mjs.map +0 -7
  175. package/dist/lib/node-esm/chunk-WFDTY3IC.mjs.map +0 -7
  176. package/dist/types/src/graph/compute-graph-registry.d.ts.map +0 -1
  177. package/dist/types/src/graph/compute-graph.d.ts.map +0 -1
  178. package/dist/types/src/graph/compute-graph.stories.d.ts.map +0 -1
  179. package/dist/types/src/graph/compute-graph.test.d.ts.map +0 -1
  180. package/dist/types/src/graph/compute-node.d.ts.map +0 -1
  181. package/dist/types/src/graph/functions/async-function.d.ts.map +0 -1
  182. package/dist/types/src/graph/functions/edge-function.d.ts.map +0 -1
  183. package/dist/types/src/graph/functions/function-defs.d.ts.map +0 -1
  184. package/dist/types/src/graph/functions/index.d.ts.map +0 -1
  185. package/dist/types/src/graph/hyperformula.test.d.ts.map +0 -1
  186. package/dist/types/src/graph/index.d.ts.map +0 -1
  187. package/dist/types/src/graph/testing/index.d.ts.map +0 -1
  188. package/dist/types/src/graph/testing/test-builder.d.ts.map +0 -1
  189. package/dist/types/src/graph/testing/test-plugin.d.ts.map +0 -1
  190. package/dist/types/src/graph/util.d.ts.map +0 -1
  191. package/dist/types/src/hooks/hooks.stories.d.ts +0 -6
  192. package/dist/types/src/hooks/hooks.stories.d.ts.map +0 -1
  193. package/dist/types/src/hooks/index.d.ts +0 -4
  194. package/dist/types/src/hooks/index.d.ts.map +0 -1
  195. package/dist/types/src/hooks/threads.d.ts +0 -8
  196. package/dist/types/src/hooks/threads.d.ts.map +0 -1
  197. package/dist/types/src/hooks/useComputeGraph.d.ts +0 -7
  198. package/dist/types/src/hooks/useComputeGraph.d.ts.map +0 -1
  199. package/dist/types/src/hooks/useSheetModel.d.ts.map +0 -1
  200. package/dist/types/src/model/decorations.d.ts +0 -26
  201. package/dist/types/src/model/decorations.d.ts.map +0 -1
  202. package/src/hooks/hooks.stories.tsx +0 -53
  203. package/src/hooks/index.ts +0 -7
  204. package/src/hooks/threads.ts +0 -147
  205. package/src/hooks/useComputeGraph.ts +0 -28
  206. package/src/model/decorations.ts +0 -66
  207. /package/dist/lib/browser/{graph-M4IQ76QX.mjs.map → compute-graph-GGWUX644.mjs.map} +0 -0
  208. /package/dist/lib/node-esm/{graph-SMPUMOV2.mjs.map → compute-graph-2SCZT7N5.mjs.map} +0 -0
  209. /package/dist/types/src/{graph → compute-graph}/compute-graph-registry.d.ts +0 -0
  210. /package/dist/types/src/{graph → compute-graph}/compute-graph.stories.d.ts +0 -0
  211. /package/dist/types/src/{graph → compute-graph}/compute-graph.test.d.ts +0 -0
  212. /package/dist/types/src/{graph → compute-graph}/compute-node.d.ts +0 -0
  213. /package/dist/types/src/{graph → compute-graph}/functions/async-function.d.ts +0 -0
  214. /package/dist/types/src/{graph → compute-graph}/functions/edge-function.d.ts +0 -0
  215. /package/dist/types/src/{graph → compute-graph}/functions/function-defs.d.ts +0 -0
  216. /package/dist/types/src/{graph → compute-graph}/functions/index.d.ts +0 -0
  217. /package/dist/types/src/{graph → compute-graph}/hyperformula.test.d.ts +0 -0
  218. /package/dist/types/src/{graph → compute-graph}/index.d.ts +0 -0
  219. /package/dist/types/src/{graph → compute-graph}/testing/index.d.ts +0 -0
  220. /package/dist/types/src/{graph → compute-graph}/testing/test-builder.d.ts +0 -0
  221. /package/dist/types/src/{graph → compute-graph}/testing/test-plugin.d.ts +0 -0
  222. /package/dist/types/src/{graph → compute-graph}/util.d.ts +0 -0
  223. /package/src/{graph → compute-graph}/compute-graph-registry.ts +0 -0
  224. /package/src/{graph → compute-graph}/compute-graph.test.ts +0 -0
  225. /package/src/{graph → compute-graph}/functions/function-defs.ts +0 -0
  226. /package/src/{graph → compute-graph}/functions/index.ts +0 -0
  227. /package/src/{graph → compute-graph}/hyperformula.test.ts +0 -0
  228. /package/src/{graph → compute-graph}/index.ts +0 -0
  229. /package/src/{graph → compute-graph}/testing/index.ts +0 -0
  230. /package/src/{graph → compute-graph}/testing/test-builder.ts +0 -0
  231. /package/src/{graph → compute-graph}/testing/test-plugin.ts +0 -0
  232. /package/src/{graph → compute-graph}/util.ts +0 -0
package/src/defs/types.ts CHANGED
@@ -13,11 +13,16 @@ export const MAX_COLUMNS = 26 * 2;
13
13
  export type CellAddress = { col: number; row: number };
14
14
 
15
15
  export type CellRange = { from: CellAddress; to?: CellAddress };
16
+ export type CompleteCellRange = { from: CellAddress; to: CellAddress };
16
17
 
17
18
  export type CellIndex = string;
18
19
 
19
20
  export type CellContentValue = number | string | boolean | null;
20
21
 
22
+ export const RANGE_NOTATION = /^[A-Z]+[0-9]+(:[A-Z]+[0-9]+)?$/;
23
+
24
+ export const isFormula = (value: any): value is string => typeof value === 'string' && value.charAt(0) === '=';
25
+
21
26
  export const posEquals = (a: CellAddress | undefined, b: CellAddress | undefined) => {
22
27
  return a?.col === b?.col && a?.row === b?.row;
23
28
  };
@@ -34,7 +39,7 @@ export const addressToA1Notation = ({ col, row }: CellAddress): string => {
34
39
  return `${columnLetter(col)}${row + 1}`;
35
40
  };
36
41
 
37
- // TODO(burdon): See simpleCellAddressFromString
42
+ // TODO(burdon): See (HF) simpleCellAddressFromString.
38
43
  export const addressFromA1Notation = (ref: string): CellAddress => {
39
44
  const match = ref.match(/([A-Z]+)(\d+)/);
40
45
  invariant(match, `Invalid notation: ${ref}`);
package/src/defs/util.ts CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  addressFromA1Notation,
10
10
  type CellAddress,
11
11
  type CellRange,
12
+ type CompleteCellRange,
12
13
  DEFAULT_COLUMNS,
13
14
  DEFAULT_ROWS,
14
15
  MAX_COLUMNS,
@@ -46,6 +47,7 @@ export const insertIndices = (indices: string[], i: number, n: number, max: numb
46
47
 
47
48
  const idx = createIndices(n);
48
49
  indices.splice(i, 0, ...idx);
50
+ return idx;
49
51
  };
50
52
 
51
53
  export const initialize = (
@@ -69,6 +71,7 @@ export const createSheet = ({ name, cells, ...size }: CreateSheetOptions = {}):
69
71
  rowMeta: {},
70
72
  columnMeta: {},
71
73
  ranges: [],
74
+ threads: [],
72
75
  });
73
76
 
74
77
  initialize(sheet, size);
@@ -111,29 +114,11 @@ export const rangeToIndex = (sheet: SheetType, range: CellRange): string => {
111
114
  /**
112
115
  * E.g., "CA2@CB3:CC4@CD5" => "A1:B2".
113
116
  */
114
- export const rangeFromIndex = (sheet: SheetType, idx: string): CellRange => {
117
+ export const rangeFromIndex = (sheet: SheetType, idx: string): CompleteCellRange => {
115
118
  const [from, to] = idx.split(':').map((index) => addressFromIndex(sheet, index));
116
119
  return { from, to };
117
120
  };
118
121
 
119
- /**
120
- * Find closest cell to cursor.
121
- */
122
- export const closest = (cursor: CellAddress, cells: CellAddress[]): CellAddress | undefined => {
123
- let closestCell: CellAddress | undefined;
124
- let closestDistance = Number.MAX_SAFE_INTEGER;
125
-
126
- for (const cell of cells) {
127
- const distance = Math.abs(cell.row - cursor.row) + Math.abs(cell.col - cursor.col);
128
- if (distance < closestDistance) {
129
- closestCell = cell;
130
- closestDistance = distance;
131
- }
132
- }
133
-
134
- return closestCell;
135
- };
136
-
137
122
  /**
138
123
  * Compares the positions of two cell indexes in a sheet.
139
124
  * Sorts primarily by row, then by column if rows are equal.
@@ -23,8 +23,8 @@ import { withTheme, withLayout } from '@dxos/storybook-utils';
23
23
  import { nonNullable } from '@dxos/util';
24
24
 
25
25
  import { compute, computeGraphFacet } from './compute';
26
- import { GridSheet, SheetProvider } from '../components';
27
- import { useComputeGraph, useSheetModel } from '../hooks';
26
+ import { GridSheet, SheetProvider, useComputeGraph } from '../components';
27
+ import { useSheetModel } from '../model';
28
28
  import { useTestSheet, withComputeGraphDecorator } from '../testing';
29
29
  import { SheetType } from '../types';
30
30
 
@@ -16,10 +16,10 @@ import { Decoration, EditorView, ViewPlugin, WidgetType } from '@codemirror/view
16
16
 
17
17
  import { type UnsubscribeCallback, debounce } from '@dxos/async';
18
18
  import { invariant } from '@dxos/invariant';
19
- import { documentId, singleValueFacet } from '@dxos/react-ui-editor/state';
19
+ import { documentId, singleValueFacet } from '@dxos/react-ui-editor';
20
20
 
21
+ import { type ComputeGraph, type ComputeNode, createSheetName } from '../compute-graph';
21
22
  import { type CellAddress } from '../defs';
22
- import { type ComputeGraph, type ComputeNode, createSheetName } from '../graph';
23
23
  import { type CellScalarValue } from '../types';
24
24
 
25
25
  const LANGUAGE_TAG = 'dx';
@@ -9,7 +9,7 @@ import { spreadsheet } from 'codemirror-lang-spreadsheet';
9
9
  import { describe, expect, test } from 'vitest';
10
10
 
11
11
  import { sheetExtension } from './extension';
12
- import { defaultFunctions } from '../../graph';
12
+ import { defaultFunctions } from '../../compute-graph';
13
13
 
14
14
  describe('formula parser', () => {
15
15
  const {
@@ -18,10 +18,11 @@ import { type SyntaxNode } from '@lezer/common';
18
18
  import { tags } from '@lezer/highlight';
19
19
  import { spreadsheet } from 'codemirror-lang-spreadsheet';
20
20
 
21
- import { singleValueFacet } from '@dxos/react-ui-editor/state';
21
+ import { singleValueFacet } from '@dxos/react-ui-editor';
22
22
  import { mx } from '@dxos/react-ui-theme';
23
23
 
24
- import { type FunctionDefinition } from '../../graph';
24
+ import { type FunctionDefinition } from '../../compute-graph';
25
+ import { RANGE_NOTATION } from '../../defs';
25
26
 
26
27
  /**
27
28
  * https://codemirror.net/examples/styling
@@ -195,34 +196,53 @@ export const sheetExtension = ({ functions = [] }: SheetExtensionOptions): Exten
195
196
  ];
196
197
  };
197
198
 
198
- export type CellRangeNotifier = (range: string) => void;
199
-
200
- type Range = { from: number; to: number };
199
+ export type SelectionRange = { from: number; to: number };
200
+
201
+ export interface RangeController {
202
+ setRange(range: string): void;
203
+ }
204
+
205
+ export type RangeExtensionOptions = {
206
+ /**
207
+ * Provides controller callback when extension is initialized.
208
+ */
209
+ onInit?: (controller: RangeController) => void;
210
+ /**
211
+ * Called when the active range changes.
212
+ * @param state The current state.
213
+ * @param state.activeRange undefined if no range is active, otherwise a possibly partially defined range.
214
+ */
215
+ onStateChange?: (state: { activeRange: string | undefined }) => void;
216
+ };
201
217
 
202
218
  /**
203
219
  * Tracks the currently active cell within a formula and provides a callback to modify it.
204
220
  */
205
- export const rangeExtension = (onInit: (notifier: CellRangeNotifier) => void): Extension => {
221
+ export const rangeExtension = ({ onInit, onStateChange }: RangeExtensionOptions): Extension => {
206
222
  let view: EditorView;
207
- let activeRange: Range | undefined;
208
- const provider: CellRangeNotifier = (range: string) => {
209
- if (activeRange) {
210
- view.dispatch(
211
- view.state.update({
212
- changes: { ...activeRange, insert: range.toString() },
213
- selection: { anchor: activeRange.from + range.length },
214
- }),
215
- );
216
- }
223
+ let activeRange: SelectionRange | undefined;
217
224
 
218
- view.focus();
225
+ // Called externally to provide current range.
226
+ const notifier: RangeController = {
227
+ setRange: (range: string) => {
228
+ if (activeRange) {
229
+ view.dispatch(
230
+ view.state.update({
231
+ changes: { ...activeRange, insert: range.toString() },
232
+ selection: { anchor: activeRange.from + range.length },
233
+ }),
234
+ );
235
+ }
236
+
237
+ view.focus();
238
+ },
219
239
  };
220
240
 
221
241
  return ViewPlugin.fromClass(
222
242
  class {
223
243
  constructor(_view: EditorView) {
224
244
  view = _view;
225
- onInit(provider);
245
+ onInit?.(notifier);
226
246
  }
227
247
 
228
248
  update(view: ViewUpdate) {
@@ -235,17 +255,15 @@ export const rangeExtension = (onInit: (notifier: CellRangeNotifier) => void): E
235
255
  visitTree(topNode, ({ type, from, to }) => {
236
256
  if (from <= anchor && to >= anchor) {
237
257
  switch (type.name) {
238
- case 'Function': {
258
+ case 'Function':
239
259
  // Mark but keep looking.
240
260
  activeRange = { from: to, to };
241
261
  break;
242
- }
243
262
 
244
- case 'CloseParen': {
263
+ case 'CloseParen':
245
264
  // Mark but keep looking.
246
265
  activeRange = { from, to: from };
247
266
  break;
248
- }
249
267
 
250
268
  case 'RangeToken':
251
269
  case 'CellToken':
@@ -259,8 +277,15 @@ export const rangeExtension = (onInit: (notifier: CellRangeNotifier) => void): E
259
277
 
260
278
  // Allow start of formula.
261
279
  if (!activeRange && view.state.doc.toString()[0] === '=') {
262
- activeRange = { from: 1, to: view.state.doc.toString().length };
280
+ const str = view.state.doc.sliceString(1);
281
+ if (RANGE_NOTATION.test(str)) {
282
+ activeRange = { from: 1, to: str.length + 1 };
283
+ }
263
284
  }
285
+
286
+ onStateChange?.({
287
+ activeRange: activeRange ? view.state.doc.sliceString(activeRange.from, activeRange.to) : undefined,
288
+ });
264
289
  }
265
290
  },
266
291
  );
package/src/index.ts CHANGED
@@ -4,6 +4,6 @@
4
4
 
5
5
  import { SheetPlugin } from './SheetPlugin';
6
6
 
7
- export default SheetPlugin;
8
-
9
7
  export * from './SheetPlugin';
8
+
9
+ export default SheetPlugin;
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './thread-ranges';
@@ -0,0 +1,101 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { useCallback, useEffect, useMemo } from 'react';
6
+
7
+ import { type IntentResolver, LayoutAction, useIntentDispatcher, useIntentResolver } from '@dxos/app-framework';
8
+ import { debounce } from '@dxos/async';
9
+ import { fullyQualifiedId } from '@dxos/react-client/echo';
10
+ import { type DxGridElement, type DxGridPosition } from '@dxos/react-ui-grid';
11
+
12
+ import { useSheetContext } from '../components';
13
+ import { type CellAddress, type CompleteCellRange, inRange } from '../defs';
14
+ import { SHEET_PLUGIN } from '../meta';
15
+
16
+ export const completeCellRangeToThreadCursor = (range: CompleteCellRange): string => {
17
+ return `${range.from.col},${range.from.row},${range.to.col},${range.to.row}`;
18
+ };
19
+
20
+ export const parseThreadAnchorAsCellRange = (cursor: string): CompleteCellRange | null => {
21
+ const coords = cursor.split(',');
22
+ if (coords.length !== 4) {
23
+ return null;
24
+ } else {
25
+ const [fromCol, fromRow, toCol, toRow] = coords;
26
+ return {
27
+ from: { col: parseInt(fromCol), row: parseInt(fromRow) },
28
+ to: { col: parseInt(toCol), row: parseInt(toRow) },
29
+ };
30
+ }
31
+ };
32
+
33
+ export const useUpdateFocusedCellOnThreadSelection = (grid: DxGridElement | null) => {
34
+ const { model, setActiveRefs } = useSheetContext();
35
+ const handleScrollIntoView: IntentResolver = useCallback(
36
+ ({ action, data }) => {
37
+ switch (action) {
38
+ case LayoutAction.SCROLL_INTO_VIEW: {
39
+ if (!data?.id || data?.cursor === undefined || data?.id !== fullyQualifiedId(model.sheet)) {
40
+ return;
41
+ }
42
+ setActiveRefs(data.thread);
43
+ // TODO(Zan): Everywhere we refer to the cursor in a thread context should change to `anchor`.
44
+ const range = parseThreadAnchorAsCellRange(data.cursor);
45
+ range && grid?.setFocus({ ...range.to, plane: 'grid' }, true);
46
+ }
47
+ }
48
+ },
49
+ [model.sheet, setActiveRefs],
50
+ );
51
+
52
+ useIntentResolver(SHEET_PLUGIN, handleScrollIntoView);
53
+ };
54
+
55
+ export const useSelectThreadOnCellFocus = () => {
56
+ const { model, cursor } = useSheetContext();
57
+ const dispatch = useIntentDispatcher();
58
+
59
+ const threads = useMemo(
60
+ () => model.sheet.threads?.filter((thread): thread is NonNullable<typeof thread> => !!thread) ?? [],
61
+ [
62
+ // TODO(thure): Surely we can find a better dependency for this…
63
+ JSON.stringify(model.sheet.threads),
64
+ ],
65
+ );
66
+
67
+ const selectClosestThread = useCallback(
68
+ (cellAddress: CellAddress) => {
69
+ if (!cellAddress || !threads) {
70
+ return;
71
+ }
72
+
73
+ const closestThread = threads?.find(({ anchor }) => {
74
+ if (anchor) {
75
+ const range = parseThreadAnchorAsCellRange(anchor);
76
+ return range ? inRange(range, cellAddress) : false;
77
+ } else {
78
+ return false;
79
+ }
80
+ });
81
+
82
+ if (closestThread) {
83
+ void dispatch([
84
+ { action: 'dxos.org/plugin/thread/action/select', data: { current: fullyQualifiedId(closestThread) } },
85
+ ]);
86
+ }
87
+ },
88
+ [dispatch, threads],
89
+ );
90
+
91
+ const debounced = useMemo(() => {
92
+ return debounce((cellCoords: DxGridPosition) => requestAnimationFrame(() => selectClosestThread(cellCoords)), 50);
93
+ }, [selectClosestThread]);
94
+
95
+ useEffect(() => {
96
+ if (!cursor) {
97
+ return;
98
+ }
99
+ debounced(cursor);
100
+ }, [cursor, debounced]);
101
+ };
package/src/meta.ts CHANGED
@@ -11,4 +11,5 @@ export default {
11
11
  name: 'Sheet',
12
12
  description: 'A simple spreadsheet plugin.',
13
13
  icon: 'ph--grid-nine--regular',
14
+ source: 'https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-sheet',
14
15
  } satisfies PluginMeta;
@@ -2,5 +2,5 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- export * from './decorations';
6
5
  export * from './sheet-model';
6
+ export * from './useSheetModel';
@@ -2,14 +2,14 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { afterEach, beforeEach, describe, expect, test } from 'vitest';
5
+ import { afterEach, beforeEach, describe, expect, onTestFinished, test } from 'vitest';
6
6
 
7
7
  import { Trigger } from '@dxos/async';
8
8
  import { FunctionType } from '@dxos/plugin-script/types';
9
9
 
10
10
  import { SheetModel } from './sheet-model';
11
+ import { TestBuilder, testFunctionPlugins } from '../compute-graph/testing';
11
12
  import { addressFromA1Notation, createSheet } from '../defs';
12
- import { TestBuilder, testFunctionPlugins } from '../graph/testing';
13
13
  import { type CellScalarValue } from '../types';
14
14
 
15
15
  describe('SheetModel', () => {
@@ -36,13 +36,15 @@ describe('SheetModel', () => {
36
36
  // Trigger waits for function invocation.
37
37
  const trigger = new Trigger<CellScalarValue>();
38
38
  model.setValue(addressFromA1Notation('A1'), '=TEST(100)');
39
- model.update.once((update) => {
39
+ // TODO(wittjosiah): Currently this fires twice, once for the binding loading & once for the function invocation.
40
+ const unsubscribe = model.update.on((update) => {
40
41
  const { type } = update;
41
42
  if (type === 'valuesUpdated') {
42
43
  const value = model.getValue(addressFromA1Notation('A1'));
43
- trigger.wake(value);
44
+ value && trigger.wake(value);
44
45
  }
45
46
  });
47
+ onTestFinished(() => unsubscribe());
46
48
 
47
49
  // Initial value will be null.
48
50
  const v1 = model.getValue(addressFromA1Notation('A1'));
@@ -8,34 +8,41 @@ import { type SimpleDate, type SimpleDateTime } from 'hyperformula/typings/DateT
8
8
 
9
9
  import { Event } from '@dxos/async';
10
10
  import { Resource } from '@dxos/context';
11
- import { getTypename } from '@dxos/echo-schema';
11
+ import { getTypename, FormatEnum, TypeEnum } from '@dxos/echo-schema';
12
12
  import { invariant } from '@dxos/invariant';
13
13
  import { PublicKey } from '@dxos/keys';
14
14
  import { log } from '@dxos/log';
15
- import { FieldValueType } from '@dxos/schema';
16
15
 
17
16
  import { DetailedCellError, ExportedCellChange } from '#hyperformula';
17
+ import { type ComputeGraph, type ComputeNode, type ComputeNodeEvent, createSheetName } from '../compute-graph';
18
18
  import {
19
19
  addressFromA1Notation,
20
+ addressFromIndex,
20
21
  addressToA1Notation,
22
+ addressToIndex,
23
+ initialize,
24
+ insertIndices,
25
+ isFormula,
21
26
  type CellAddress,
22
27
  type CellRange,
28
+ ReadonlyException,
23
29
  MAX_COLUMNS,
24
30
  MAX_ROWS,
25
31
  } from '../defs';
26
- import { addressFromIndex, addressToIndex, initialize, insertIndices, ReadonlyException } from '../defs';
27
- import { type ComputeNode, type ComputeGraph, createSheetName, type ComputeNodeEvent } from '../graph';
28
- import { type CellScalarValue, type CellValue, type SheetType } from '../types';
32
+ import { type CellScalarValue, type CellValue, type SheetType, type RestoreAxis } from '../types';
29
33
 
30
34
  // Map sheet types to system types.
31
- const typeMap: Record<string, FieldValueType> = {
32
- BOOLEAN: FieldValueType.Boolean,
33
- NUMBER_RAW: FieldValueType.Number,
34
- NUMBER_PERCENT: FieldValueType.Percent,
35
- NUMBER_CURRENCY: FieldValueType.Currency,
36
- NUMBER_DATETIME: FieldValueType.DateTime,
37
- NUMBER_DATE: FieldValueType.Date,
38
- NUMBER_TIME: FieldValueType.Time,
35
+ // https://hyperformula.handsontable.com/guide/types-of-values.html
36
+ // - https://github.com/handsontable/hyperformula/blob/master/src/Cell.ts (CellValueType)
37
+ // - https://github.com/handsontable/hyperformula/blob/master/src/interpreter/InterpreterValue.ts (NumberType)
38
+ const typeMap: Record<string, { type: TypeEnum; format?: FormatEnum }> = {
39
+ BOOLEAN: { type: TypeEnum.Boolean },
40
+ NUMBER_RAW: { type: TypeEnum.Number },
41
+ NUMBER_PERCENT: { type: TypeEnum.Number, format: FormatEnum.Percent },
42
+ NUMBER_CURRENCY: { type: TypeEnum.Number, format: FormatEnum.Currency },
43
+ NUMBER_DATETIME: { type: TypeEnum.String, format: FormatEnum.DateTime },
44
+ NUMBER_DATE: { type: TypeEnum.String, format: FormatEnum.Date },
45
+ NUMBER_TIME: { type: TypeEnum.String, format: FormatEnum.Time },
39
46
  };
40
47
 
41
48
  const getTopLeft = (range: CellRange): CellAddress => {
@@ -106,6 +113,12 @@ export class SheetModel extends Resource {
106
113
  log('initialize', { id: this.id });
107
114
  initialize(this._sheet);
108
115
 
116
+ this._graph.update.on((event) => {
117
+ if (event.type === 'functionsUpdated') {
118
+ this.reset();
119
+ }
120
+ });
121
+
109
122
  // TODO(burdon): SheetModel should extend ComputeNode and be constructed via the graph.
110
123
  this._node = this._graph.getOrCreateNode(createSheetName({ type: getTypename(this._sheet)!, id: this._sheet.id }));
111
124
  await this._node.open();
@@ -128,10 +141,15 @@ export class SheetModel extends Resource {
128
141
  Object.entries(this._sheet.cells).forEach(([key, { value }]) => {
129
142
  invariant(this._node);
130
143
  const { col, row } = addressFromIndex(this._sheet, key);
131
- if (typeof value === 'string' && value.charAt(0) === '=') {
132
- value = this._graph.mapFormulaToNative(
133
- this._graph.mapFunctionBindingFromId(this.mapFormulaIndicesToRefs(value)),
134
- );
144
+ if (isFormula(value)) {
145
+ const binding = this._graph.mapFunctionBindingFromId(this.mapFormulaIndicesToRefs(value));
146
+ if (binding) {
147
+ value = this._graph.mapFormulaToNative(binding);
148
+ } else {
149
+ // If binding is not found, render the cell as empty.
150
+ // This prevents the cell from momentarily rendering an error while the binding is being loaded.
151
+ value = '';
152
+ }
135
153
  }
136
154
 
137
155
  this._node.graph.hf.setCellContents({ sheet: this._node.sheetId, row, col }, value);
@@ -150,12 +168,68 @@ export class SheetModel extends Resource {
150
168
  }
151
169
 
152
170
  insertRows(i: number, n = 1) {
153
- insertIndices(this._sheet.rows, i, n, MAX_ROWS);
171
+ const idx = insertIndices(this._sheet.rows, i, n, MAX_ROWS);
154
172
  this.reset();
173
+ return idx;
155
174
  }
156
175
 
157
176
  insertColumns(i: number, n = 1) {
158
- insertIndices(this._sheet.columns, i, n, MAX_COLUMNS);
177
+ const idx = insertIndices(this._sheet.columns, i, n, MAX_COLUMNS);
178
+ this.reset();
179
+ return idx;
180
+ }
181
+
182
+ dropRow(rowIndex: string): RestoreAxis {
183
+ const range = {
184
+ from: addressFromIndex(this._sheet, `${this._sheet.columns[0]}@${rowIndex}`),
185
+ to: addressFromIndex(this._sheet, `${this._sheet.columns[this._sheet.columns.length - 1]}@${rowIndex}`),
186
+ };
187
+ const values = this.getCellValues(range).flat();
188
+ const index = this._sheet.rows.indexOf(rowIndex);
189
+ this.clear(range);
190
+ this._sheet.rows.splice(index, 1);
191
+ delete this._sheet.rowMeta[rowIndex];
192
+ this.reset();
193
+ return { axis: 'row', index, axisIndex: rowIndex, axisMeta: this._sheet.rowMeta[rowIndex], values };
194
+ }
195
+
196
+ dropColumn(colIndex: string): RestoreAxis {
197
+ const range = {
198
+ from: addressFromIndex(this._sheet, `${colIndex}@${this._sheet.rows[0]}`),
199
+ to: addressFromIndex(this._sheet, `${colIndex}@${this._sheet.rows[this._sheet.rows.length - 1]}`),
200
+ };
201
+ const values = this.getCellValues(range).flat();
202
+ const index = this._sheet.columns.indexOf(colIndex);
203
+ this.clear(range);
204
+ this._sheet.columns.splice(index, 1);
205
+ delete this._sheet.columnMeta[colIndex];
206
+ this.reset();
207
+ return { axis: 'col', index, axisIndex: colIndex, axisMeta: this._sheet.rowMeta[colIndex], values };
208
+ }
209
+
210
+ restoreRow({ index, axisIndex, axisMeta, values }: RestoreAxis) {
211
+ this._sheet.rows.splice(index, 0, axisIndex);
212
+ values.forEach((value, col) => {
213
+ if (value) {
214
+ this._sheet.cells[`${this._sheet.columns[col]}@${axisIndex}`] = { value };
215
+ }
216
+ });
217
+ if (axisMeta) {
218
+ this._sheet.rowMeta[axisIndex] = axisMeta;
219
+ }
220
+ this.reset();
221
+ }
222
+
223
+ restoreColumn({ index, axisIndex, axisMeta, values }: RestoreAxis) {
224
+ this._sheet.columns.splice(index, 0, axisIndex);
225
+ values.forEach((value, row) => {
226
+ if (value) {
227
+ this._sheet.cells[`${axisIndex}@${this._sheet.rows[row]}`] = { value };
228
+ }
229
+ });
230
+ if (axisMeta) {
231
+ this._sheet.columnMeta[axisIndex] = axisMeta;
232
+ }
159
233
  this.reset();
160
234
  }
161
235
 
@@ -240,7 +314,7 @@ export class SheetModel extends Resource {
240
314
  return undefined;
241
315
  }
242
316
 
243
- if (typeof value === 'string' && value.charAt(0) === '=') {
317
+ if (isFormula(value)) {
244
318
  return this._graph.mapFunctionBindingFromId(this.mapFormulaIndicesToRefs(value));
245
319
  } else {
246
320
  return String(value);
@@ -260,8 +334,11 @@ export class SheetModel extends Resource {
260
334
  getValue(cell: CellAddress): CellScalarValue {
261
335
  // Applies rounding and post-processing.
262
336
  invariant(this._node);
263
- const value = this._node.graph.hf.getCellValue(toSimpleCellAddress(this._node.sheetId, cell));
337
+ const address = toSimpleCellAddress(this._node.sheetId, cell);
338
+ const value = this._node.graph.hf.getCellValue(address);
264
339
  if (value instanceof DetailedCellError) {
340
+ // TODO(wittjosiah): Error details should be shown in cell `title`.
341
+ log.info('cell error', { cell, error: value });
265
342
  return value.toString();
266
343
  }
267
344
 
@@ -271,7 +348,7 @@ export class SheetModel extends Resource {
271
348
  /**
272
349
  * Get value type.
273
350
  */
274
- getValueType(cell: CellAddress): FieldValueType {
351
+ getValueDescription(cell: CellAddress): { type: TypeEnum; format?: FormatEnum } | undefined {
275
352
  invariant(this._node);
276
353
  const addr = toSimpleCellAddress(this._node.sheetId, cell);
277
354
  const type = this._node.graph.hf.getCellValueDetailedType(addr);
@@ -305,7 +382,7 @@ export class SheetModel extends Resource {
305
382
 
306
383
  // Insert into engine.
307
384
  this._node.graph.hf.setCellContents({ sheet: this._node.sheetId, row: cell.row, col: cell.col }, [
308
- [typeof value === 'string' && value.charAt(0) === '=' ? this._graph.mapFormulaToNative(value) : value],
385
+ [isFormula(value) ? this._graph.mapFormulaToNative(value) : value],
309
386
  ]);
310
387
 
311
388
  // Insert into sheet.
@@ -313,7 +390,7 @@ export class SheetModel extends Resource {
313
390
  if (value === undefined || value === null) {
314
391
  delete this._sheet.cells[idx];
315
392
  } else {
316
- if (typeof value === 'string' && value.charAt(0) === '=') {
393
+ if (isFormula(value)) {
317
394
  value = this._graph.mapFunctionBindingToId(this.mapFormulaRefsToIndices(value));
318
395
  }
319
396
 
@@ -371,7 +448,7 @@ export class SheetModel extends Resource {
371
448
  * Map from A1 notation to indices.
372
449
  */
373
450
  mapFormulaRefsToIndices(formula: string): string {
374
- invariant(formula.charAt(0) === '=');
451
+ invariant(isFormula(formula));
375
452
  return formula.replace(/([a-zA-Z]+)([0-9]+)/g, (match) => {
376
453
  return addressToIndex(this._sheet, addressFromA1Notation(match));
377
454
  });
@@ -381,7 +458,7 @@ export class SheetModel extends Resource {
381
458
  * Map from indices to A1 notation.
382
459
  */
383
460
  mapFormulaIndicesToRefs(formula: string): string {
384
- invariant(formula.charAt(0) === '=');
461
+ invariant(isFormula(formula));
385
462
  return formula.replace(/([a-zA-Z0-9]+)@([a-zA-Z0-9]+)/g, (idx) => {
386
463
  return addressToA1Notation(addressFromIndex(this._sheet, idx));
387
464
  });
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { useEffect, useState } from 'react';
6
6
 
7
- import { type ComputeGraph } from '../graph';
7
+ import { type ComputeGraph } from '../compute-graph';
8
8
  import { SheetModel } from '../model';
9
9
  import { type SheetType } from '../types';
10
10
 
@@ -0,0 +1,27 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { type TypedObjectSerializer } from '@dxos/plugin-space/types';
6
+ import { create, getObjectCore } from '@dxos/react-client/echo';
7
+
8
+ import { SheetType } from './types';
9
+
10
+ export const serializer: TypedObjectSerializer<SheetType> = {
11
+ serialize: async ({ object }): Promise<string> => {
12
+ const { threads: _threads, ...sheet } = object;
13
+ return JSON.stringify(sheet, null, 2);
14
+ },
15
+
16
+ deserialize: async ({ content, newId }) => {
17
+ const { id, ...parsed } = JSON.parse(content);
18
+ const sheet = create(SheetType, parsed);
19
+
20
+ if (!newId) {
21
+ const core = getObjectCore(sheet);
22
+ core.id = id;
23
+ }
24
+
25
+ return sheet;
26
+ },
27
+ };