@dxos/plugin-sheet 0.6.11 → 0.6.12-main.15a606f

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 (260) hide show
  1. package/dist/lib/browser/SheetContainer-V4GCCZTX.mjs +261 -0
  2. package/dist/lib/browser/SheetContainer-V4GCCZTX.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-D5AGLXJP.mjs → chunk-6ZMQVB4Z.mjs} +359 -671
  4. package/dist/lib/browser/chunk-6ZMQVB4Z.mjs.map +7 -0
  5. package/dist/lib/browser/{chunk-JRL5LGCE.mjs → chunk-QILRZNE5.mjs} +2 -5
  6. package/dist/lib/browser/chunk-QILRZNE5.mjs.map +7 -0
  7. package/dist/lib/browser/{chunk-FUAGSXA4.mjs → chunk-T3NJFTD4.mjs} +8 -15
  8. package/dist/lib/browser/chunk-T3NJFTD4.mjs.map +7 -0
  9. package/dist/lib/browser/{SheetContainer-U4H5D34A.mjs → chunk-U2JHW3L6.mjs} +1020 -240
  10. package/dist/lib/browser/chunk-U2JHW3L6.mjs.map +7 -0
  11. package/dist/lib/browser/graph-T27BOBOV.mjs +21 -0
  12. package/dist/lib/browser/graph-T27BOBOV.mjs.map +7 -0
  13. package/dist/lib/browser/index.mjs +68 -56
  14. package/dist/lib/browser/index.mjs.map +3 -3
  15. package/dist/lib/browser/meta.json +1 -1
  16. package/dist/lib/browser/meta.mjs +1 -1
  17. package/dist/lib/browser/types.mjs +4 -6
  18. package/dist/lib/node/SheetContainer-3ZY7MPWJ.cjs +279 -0
  19. package/dist/lib/node/SheetContainer-3ZY7MPWJ.cjs.map +7 -0
  20. package/dist/lib/node/{chunk-BJ6ZD7MN.cjs → chunk-BNARJ5GM.cjs} +5 -18
  21. package/dist/lib/node/chunk-BNARJ5GM.cjs.map +7 -0
  22. package/dist/lib/node/{chunk-5KKJ4NPP.cjs → chunk-DD6FIXWC.cjs} +360 -667
  23. package/dist/lib/node/chunk-DD6FIXWC.cjs.map +7 -0
  24. package/dist/lib/node/{SheetContainer-AXQV3ZT5.cjs → chunk-OTTD7FBK.cjs} +1050 -279
  25. package/dist/lib/node/chunk-OTTD7FBK.cjs.map +7 -0
  26. package/dist/lib/node/{chunk-DSYKOI4E.cjs → chunk-Q3HBHPRL.cjs} +12 -20
  27. package/dist/lib/node/chunk-Q3HBHPRL.cjs.map +7 -0
  28. package/dist/lib/node/graph-SPKGX7W4.cjs +43 -0
  29. package/dist/lib/node/graph-SPKGX7W4.cjs.map +7 -0
  30. package/dist/lib/node/index.cjs +83 -64
  31. package/dist/lib/node/index.cjs.map +3 -3
  32. package/dist/lib/node/meta.cjs +3 -3
  33. package/dist/lib/node/meta.cjs.map +1 -1
  34. package/dist/lib/node/meta.json +1 -1
  35. package/dist/lib/node/types.cjs +10 -12
  36. package/dist/lib/node/types.cjs.map +2 -2
  37. package/dist/lib/node-esm/SheetContainer-PXSJX6XK.mjs +262 -0
  38. package/dist/lib/node-esm/SheetContainer-PXSJX6XK.mjs.map +7 -0
  39. package/dist/lib/node-esm/chunk-7HVSOTGA.mjs +2553 -0
  40. package/dist/lib/node-esm/chunk-7HVSOTGA.mjs.map +7 -0
  41. package/dist/lib/node-esm/chunk-BMNA27EX.mjs +76 -0
  42. package/dist/lib/node-esm/chunk-BMNA27EX.mjs.map +7 -0
  43. package/dist/lib/node-esm/chunk-D6KU5MI7.mjs +2925 -0
  44. package/dist/lib/node-esm/chunk-D6KU5MI7.mjs.map +7 -0
  45. package/dist/lib/node-esm/chunk-IU2L277A.mjs +17 -0
  46. package/dist/lib/node-esm/chunk-IU2L277A.mjs.map +7 -0
  47. package/dist/lib/node-esm/graph-U67IO4UC.mjs +22 -0
  48. package/dist/lib/node-esm/graph-U67IO4UC.mjs.map +7 -0
  49. package/dist/lib/node-esm/index.mjs +261 -0
  50. package/dist/lib/node-esm/index.mjs.map +7 -0
  51. package/dist/lib/node-esm/meta.json +1 -0
  52. package/dist/lib/node-esm/meta.mjs +10 -0
  53. package/dist/lib/node-esm/meta.mjs.map +7 -0
  54. package/dist/lib/node-esm/types.mjs +21 -0
  55. package/dist/lib/node-esm/types.mjs.map +7 -0
  56. package/dist/types/src/SheetPlugin.d.ts.map +1 -1
  57. package/dist/types/src/components/CellEditor/CellEditor.d.ts +23 -3
  58. package/dist/types/src/components/CellEditor/CellEditor.d.ts.map +1 -1
  59. package/dist/types/src/components/CellEditor/CellEditor.stories.d.ts +2 -2
  60. package/dist/types/src/components/CellEditor/CellEditor.stories.d.ts.map +1 -1
  61. package/dist/types/src/components/CellEditor/extension.d.ts +1 -1
  62. package/dist/types/src/components/CellEditor/extension.d.ts.map +1 -1
  63. package/dist/types/src/components/ComputeGraph/ComputeGraphContextProvider.d.ts +11 -0
  64. package/dist/types/src/components/ComputeGraph/ComputeGraphContextProvider.d.ts.map +1 -0
  65. package/dist/types/src/components/ComputeGraph/index.d.ts +1 -3
  66. package/dist/types/src/components/ComputeGraph/index.d.ts.map +1 -1
  67. package/dist/types/src/components/GridSheet/GridSheet.d.ts +10 -0
  68. package/dist/types/src/components/GridSheet/GridSheet.d.ts.map +1 -0
  69. package/dist/types/src/components/GridSheet/GridSheet.stories.d.ts +9 -0
  70. package/dist/types/src/components/GridSheet/GridSheet.stories.d.ts.map +1 -0
  71. package/dist/types/src/components/GridSheet/util.d.ts +6 -0
  72. package/dist/types/src/components/GridSheet/util.d.ts.map +1 -0
  73. package/dist/types/src/components/Sheet/Sheet.d.ts +1 -1
  74. package/dist/types/src/components/Sheet/Sheet.d.ts.map +1 -1
  75. package/dist/types/src/components/Sheet/Sheet.stories.d.ts +5 -6
  76. package/dist/types/src/components/Sheet/Sheet.stories.d.ts.map +1 -1
  77. package/dist/types/src/components/Sheet/decorations.d.ts +24 -0
  78. package/dist/types/src/components/Sheet/decorations.d.ts.map +1 -0
  79. package/dist/types/src/components/Sheet/grid.d.ts +2 -2
  80. package/dist/types/src/components/Sheet/grid.d.ts.map +1 -1
  81. package/dist/types/src/components/Sheet/nav.d.ts +3 -3
  82. package/dist/types/src/components/Sheet/nav.d.ts.map +1 -1
  83. package/dist/types/src/components/Sheet/sheet-context.d.ts +6 -5
  84. package/dist/types/src/components/Sheet/sheet-context.d.ts.map +1 -1
  85. package/dist/types/src/components/Sheet/threads.d.ts +2 -0
  86. package/dist/types/src/components/Sheet/threads.d.ts.map +1 -0
  87. package/dist/types/src/components/SheetContainer.d.ts +2 -3
  88. package/dist/types/src/components/SheetContainer.d.ts.map +1 -1
  89. package/dist/types/src/components/Toolbar/Toolbar.d.ts +19 -3
  90. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  91. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +18 -13
  92. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
  93. package/dist/types/src/components/index.d.ts +2 -2
  94. package/dist/types/src/components/index.d.ts.map +1 -1
  95. package/dist/types/src/defs/index.d.ts +3 -0
  96. package/dist/types/src/defs/index.d.ts.map +1 -0
  97. package/dist/types/src/{model → defs}/types.d.ts +8 -3
  98. package/dist/types/src/defs/types.d.ts.map +1 -0
  99. package/dist/types/src/defs/types.test.d.ts.map +1 -0
  100. package/dist/types/src/defs/util.d.ts +43 -0
  101. package/dist/types/src/defs/util.d.ts.map +1 -0
  102. package/dist/types/src/extensions/compute.d.ts +5 -0
  103. package/dist/types/src/extensions/compute.d.ts.map +1 -0
  104. package/dist/types/src/extensions/compute.stories.d.ts +26 -0
  105. package/dist/types/src/extensions/compute.stories.d.ts.map +1 -0
  106. package/dist/types/src/extensions/index.d.ts +2 -0
  107. package/dist/types/src/extensions/index.d.ts.map +1 -0
  108. package/dist/types/src/{components/ComputeGraph → graph}/async-function.d.ts +1 -1
  109. package/dist/types/src/graph/async-function.d.ts.map +1 -0
  110. package/dist/types/src/graph/compute-graph.browser.test.d.ts +2 -0
  111. package/dist/types/src/graph/compute-graph.browser.test.d.ts.map +1 -0
  112. package/dist/types/src/graph/compute-graph.d.ts +81 -0
  113. package/dist/types/src/graph/compute-graph.d.ts.map +1 -0
  114. package/dist/types/src/graph/compute-graph.stories.d.ts +10 -0
  115. package/dist/types/src/graph/compute-graph.stories.d.ts.map +1 -0
  116. package/dist/types/src/graph/compute-node.d.ts +19 -0
  117. package/dist/types/src/graph/compute-node.d.ts.map +1 -0
  118. package/dist/types/src/{components/ComputeGraph/custom.d.ts → graph/custom-function.d.ts} +1 -1
  119. package/dist/types/src/graph/custom-function.d.ts.map +1 -0
  120. package/dist/types/src/graph/edge-function.d.ts.map +1 -0
  121. package/dist/types/src/{model/functions.d.ts → graph/function-defs.d.ts} +1 -1
  122. package/dist/types/src/graph/function-defs.d.ts.map +1 -0
  123. package/dist/types/src/graph/hyperformula.test.d.ts +2 -0
  124. package/dist/types/src/graph/hyperformula.test.d.ts.map +1 -0
  125. package/dist/types/src/graph/index.d.ts +4 -0
  126. package/dist/types/src/graph/index.d.ts.map +1 -0
  127. package/dist/types/src/graph/util.d.ts +2 -0
  128. package/dist/types/src/graph/util.d.ts.map +1 -0
  129. package/dist/types/src/hooks/hooks.stories.d.ts +11 -0
  130. package/dist/types/src/hooks/hooks.stories.d.ts.map +1 -0
  131. package/dist/types/src/hooks/index.d.ts +4 -0
  132. package/dist/types/src/hooks/index.d.ts.map +1 -0
  133. package/dist/types/src/hooks/useComputeGraph.d.ts +7 -0
  134. package/dist/types/src/hooks/useComputeGraph.d.ts.map +1 -0
  135. package/dist/types/src/hooks/useFormattingModel.d.ts +3 -0
  136. package/dist/types/src/hooks/useFormattingModel.d.ts.map +1 -0
  137. package/dist/types/src/hooks/useSheetModel.d.ts +8 -0
  138. package/dist/types/src/hooks/useSheetModel.d.ts.map +1 -0
  139. package/dist/types/src/meta.d.ts +1 -4
  140. package/dist/types/src/meta.d.ts.map +1 -1
  141. package/dist/types/src/model/formatting-model.d.ts +16 -0
  142. package/dist/types/src/model/formatting-model.d.ts.map +1 -0
  143. package/dist/types/src/model/index.d.ts +2 -3
  144. package/dist/types/src/model/index.d.ts.map +1 -1
  145. package/dist/types/src/model/{model.d.ts → sheet-model.d.ts} +9 -64
  146. package/dist/types/src/model/sheet-model.d.ts.map +1 -0
  147. package/dist/types/src/sanity.test.d.ts +2 -0
  148. package/dist/types/src/sanity.test.d.ts.map +1 -0
  149. package/dist/types/src/testing/index.d.ts +2 -0
  150. package/dist/types/src/testing/index.d.ts.map +1 -0
  151. package/dist/types/src/testing/testing.d.ts +9 -0
  152. package/dist/types/src/testing/testing.d.ts.map +1 -0
  153. package/dist/types/src/translations.d.ts +17 -12
  154. package/dist/types/src/translations.d.ts.map +1 -1
  155. package/dist/types/src/types.d.ts +83 -3
  156. package/dist/types/src/types.d.ts.map +1 -1
  157. package/dist/vendor/hyperformula.mjs +37145 -0
  158. package/package.json +48 -41
  159. package/src/SheetPlugin.tsx +43 -70
  160. package/src/components/CellEditor/CellEditor.stories.tsx +4 -3
  161. package/src/components/CellEditor/CellEditor.tsx +59 -9
  162. package/src/components/CellEditor/extension.test.ts +4 -5
  163. package/src/components/CellEditor/extension.ts +1 -3
  164. package/src/components/ComputeGraph/ComputeGraphContextProvider.tsx +20 -0
  165. package/src/components/ComputeGraph/index.ts +1 -3
  166. package/src/components/GridSheet/GridSheet.stories.tsx +35 -0
  167. package/src/components/GridSheet/GridSheet.tsx +153 -0
  168. package/src/components/GridSheet/util.ts +89 -0
  169. package/src/components/Sheet/Sheet.stories.tsx +45 -82
  170. package/src/components/Sheet/Sheet.tsx +57 -18
  171. package/src/components/Sheet/decorations.ts +62 -0
  172. package/src/components/Sheet/grid.ts +3 -3
  173. package/src/components/Sheet/nav.ts +19 -19
  174. package/src/components/Sheet/sheet-context.tsx +16 -78
  175. package/src/components/Sheet/threads.tsx +205 -0
  176. package/src/components/SheetContainer.tsx +73 -19
  177. package/src/components/Toolbar/Toolbar.tsx +53 -12
  178. package/src/components/index.ts +1 -0
  179. package/src/defs/index.ts +6 -0
  180. package/src/{model → defs}/types.test.ts +8 -9
  181. package/src/{model → defs}/types.ts +23 -14
  182. package/src/defs/util.ts +135 -0
  183. package/src/extensions/compute.stories.tsx +151 -0
  184. package/src/extensions/compute.ts +98 -0
  185. package/src/extensions/index.ts +5 -0
  186. package/src/{components/ComputeGraph → graph}/async-function.ts +3 -1
  187. package/src/graph/compute-graph.browser.test.ts +104 -0
  188. package/src/graph/compute-graph.stories.tsx +92 -0
  189. package/src/graph/compute-graph.ts +290 -0
  190. package/src/graph/compute-node.ts +51 -0
  191. package/src/{components/ComputeGraph/custom.ts → graph/custom-function.ts} +2 -6
  192. package/src/{components/ComputeGraph → graph}/edge-function.ts +2 -1
  193. package/src/graph/hyperformula.test.ts +15 -0
  194. package/src/graph/index.ts +7 -0
  195. package/src/graph/util.ts +8 -0
  196. package/src/hooks/hooks.stories.tsx +50 -0
  197. package/src/hooks/index.ts +7 -0
  198. package/src/hooks/useComputeGraph.ts +20 -0
  199. package/src/hooks/useFormattingModel.ts +11 -0
  200. package/src/hooks/useSheetModel.ts +43 -0
  201. package/src/meta.tsx +1 -5
  202. package/src/{components/Sheet/formatting.ts → model/formatting-model.ts} +20 -13
  203. package/src/model/index.ts +2 -3
  204. package/src/model/sheet-model.ts +399 -0
  205. package/src/sanity.test.ts +40 -0
  206. package/src/testing/index.ts +5 -0
  207. package/src/testing/testing.tsx +66 -0
  208. package/src/translations.ts +6 -1
  209. package/src/types.ts +30 -5
  210. package/dist/lib/browser/SheetContainer-U4H5D34A.mjs.map +0 -7
  211. package/dist/lib/browser/chunk-APHOLYUB.mjs +0 -175
  212. package/dist/lib/browser/chunk-APHOLYUB.mjs.map +0 -7
  213. package/dist/lib/browser/chunk-D5AGLXJP.mjs.map +0 -7
  214. package/dist/lib/browser/chunk-FUAGSXA4.mjs.map +0 -7
  215. package/dist/lib/browser/chunk-JRL5LGCE.mjs.map +0 -7
  216. package/dist/lib/browser/chunk-NU4PBN33.mjs +0 -8
  217. package/dist/lib/browser/chunk-NU4PBN33.mjs.map +0 -7
  218. package/dist/lib/browser/testing.mjs +0 -92
  219. package/dist/lib/browser/testing.mjs.map +0 -7
  220. package/dist/lib/node/SheetContainer-AXQV3ZT5.cjs.map +0 -7
  221. package/dist/lib/node/chunk-5KKJ4NPP.cjs.map +0 -7
  222. package/dist/lib/node/chunk-BJ6ZD7MN.cjs.map +0 -7
  223. package/dist/lib/node/chunk-CN3RPESU.cjs +0 -202
  224. package/dist/lib/node/chunk-CN3RPESU.cjs.map +0 -7
  225. package/dist/lib/node/chunk-DSYKOI4E.cjs.map +0 -7
  226. package/dist/lib/node/chunk-PYXHNAAK.cjs +0 -40
  227. package/dist/lib/node/chunk-PYXHNAAK.cjs.map +0 -7
  228. package/dist/lib/node/testing.cjs +0 -111
  229. package/dist/lib/node/testing.cjs.map +0 -7
  230. package/dist/types/src/components/ComputeGraph/async-function.d.ts.map +0 -1
  231. package/dist/types/src/components/ComputeGraph/custom.d.ts.map +0 -1
  232. package/dist/types/src/components/ComputeGraph/edge-function.d.ts.map +0 -1
  233. package/dist/types/src/components/ComputeGraph/graph-context.d.ts +0 -12
  234. package/dist/types/src/components/ComputeGraph/graph-context.d.ts.map +0 -1
  235. package/dist/types/src/components/ComputeGraph/graph.browser.test.d.ts +0 -2
  236. package/dist/types/src/components/ComputeGraph/graph.browser.test.d.ts.map +0 -1
  237. package/dist/types/src/components/ComputeGraph/graph.d.ts +0 -26
  238. package/dist/types/src/components/ComputeGraph/graph.d.ts.map +0 -1
  239. package/dist/types/src/components/Sheet/formatting.d.ts +0 -14
  240. package/dist/types/src/components/Sheet/formatting.d.ts.map +0 -1
  241. package/dist/types/src/model/functions.d.ts.map +0 -1
  242. package/dist/types/src/model/model.browser.test.d.ts +0 -2
  243. package/dist/types/src/model/model.browser.test.d.ts.map +0 -1
  244. package/dist/types/src/model/model.d.ts.map +0 -1
  245. package/dist/types/src/model/types.d.ts.map +0 -1
  246. package/dist/types/src/model/types.test.d.ts.map +0 -1
  247. package/dist/types/src/model/util.d.ts +0 -15
  248. package/dist/types/src/model/util.d.ts.map +0 -1
  249. package/dist/types/src/testing.d.ts +0 -9
  250. package/dist/types/src/testing.d.ts.map +0 -1
  251. package/src/components/ComputeGraph/graph-context.tsx +0 -50
  252. package/src/components/ComputeGraph/graph.browser.test.ts +0 -50
  253. package/src/components/ComputeGraph/graph.ts +0 -62
  254. package/src/model/model.browser.test.ts +0 -100
  255. package/src/model/model.ts +0 -550
  256. package/src/model/util.ts +0 -36
  257. package/src/testing.ts +0 -50
  258. /package/dist/types/src/{model → defs}/types.test.d.ts +0 -0
  259. /package/dist/types/src/{components/ComputeGraph → graph}/edge-function.d.ts +0 -0
  260. /package/src/{model/functions.ts → graph/function-defs.ts} +0 -0
@@ -0,0 +1,62 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { create } from '@dxos/echo-schema';
6
+
7
+ export type Decoration = {
8
+ type: string;
9
+ /**
10
+ * A wrapping render function to encapsulate cell content. This function is applied between
11
+ * the cell's border and its padding/layout/content, allowing for custom rendering or
12
+ * additional elements to be inserted around the cell's main content.
13
+ */
14
+ decorate?: (props: { children: React.ReactNode }) => React.ReactNode;
15
+ /**
16
+ * An array of CSS class names to be applied to the content of the SheetCell.
17
+ * These classes can be used to style the cell's content independently of its structure.
18
+ */
19
+ classNames?: string[];
20
+ cellIndex: string;
21
+ };
22
+
23
+ export const createDecorations = () => {
24
+ // Reactive object to hold decorations
25
+ // TODO(Zan): Use CELL ID's to key the decoration map.
26
+ // TODO(Zan): Consider maintaining an index of decorations by type.
27
+ const { decorations } = create<{ decorations: Record<string, Decoration[]> }>({ decorations: {} });
28
+
29
+ const addDecoration = (cellIndex: string, decorator: Decoration) => {
30
+ decorations[cellIndex] = [...(decorations[cellIndex] || []), decorator];
31
+ };
32
+
33
+ const removeDecoration = (cellIndex: string, type?: string) => {
34
+ if (type) {
35
+ decorations[cellIndex] = (decorations[cellIndex] || []).filter((d) => d.type !== type);
36
+ } else {
37
+ delete decorations[cellIndex];
38
+ }
39
+ };
40
+
41
+ // TODO(Zan): I should check if returning the a value from a map in a deep signal is a reactive slice.
42
+ const getDecorationsForCell = (cellIndex: string): Decoration[] | undefined => {
43
+ return decorations[cellIndex];
44
+ };
45
+
46
+ const getAllDecorations = (): Decoration[] => {
47
+ const result: Decoration[] = [];
48
+ for (const decoratorArray of Object.values(decorations)) {
49
+ for (const decorator of decoratorArray) {
50
+ result.push(decorator);
51
+ }
52
+ }
53
+ return result;
54
+ };
55
+
56
+ return {
57
+ addDecoration,
58
+ removeDecoration,
59
+ getDecorationsForCell,
60
+ getAllDecorations,
61
+ } as const;
62
+ };
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { type MouseEvent, useEffect, useState } from 'react';
6
6
 
7
- import { type CellAddress, type CellIndex, addressFromA1Notation, addressToA1Notation } from '../../model';
7
+ import { type CellAddress, type CellIndex, addressFromA1Notation, addressToA1Notation } from '../../defs';
8
8
 
9
9
  // export type Bounds = Pick<DOMRect, 'left' | 'top' | 'width' | 'height'>;
10
10
  // export type Dimension = Pick<DOMRect, 'width' | 'height'>;
@@ -12,7 +12,7 @@ import { type CellAddress, type CellIndex, addressFromA1Notation, addressToA1Not
12
12
  export type SizeMap = Record<string, number>;
13
13
 
14
14
  export type RowPosition = { row: number } & Pick<DOMRect, 'top' | 'height'>;
15
- export type ColumnPosition = { column: number } & Pick<DOMRect, 'left' | 'width'>;
15
+ export type ColumnPosition = { col: number } & Pick<DOMRect, 'left' | 'width'>;
16
16
 
17
17
  export const axisWidth = 'calc(var(--rail-size)-2px)';
18
18
  export const axisHeight = 34;
@@ -88,7 +88,7 @@ export const useGridLayout = ({
88
88
  const width = columnSizes?.[idx] ?? defaultWidth;
89
89
  const left = x;
90
90
  x += width - 1;
91
- return { column: i, left, width };
91
+ return { col: i, left, width };
92
92
  }),
93
93
  );
94
94
  }, [columns, columnSizes]);
@@ -5,11 +5,11 @@
5
5
  import { type KeyboardEvent, type MouseEventHandler, useState } from 'react';
6
6
 
7
7
  import { getCellAtPointer } from './grid';
8
- import { type CellAddress, type CellRange, posEquals } from '../../model';
8
+ import { type CellAddress, type CellRange, posEquals } from '../../defs';
9
9
 
10
10
  export type GridSize = {
11
11
  numRows: number;
12
- numColumns: number;
12
+ numCols: number;
13
13
  };
14
14
 
15
15
  /**
@@ -38,14 +38,14 @@ export const handleNav = (
38
38
  break;
39
39
  }
40
40
  case 'ArrowLeft': {
41
- if (opposite.column > 0) {
42
- opposite.column -= 1;
41
+ if (opposite.col > 0) {
42
+ opposite.col -= 1;
43
43
  }
44
44
  break;
45
45
  }
46
46
  case 'ArrowRight': {
47
- if (opposite.column < size.numColumns - 1) {
48
- opposite.column += 1;
47
+ if (opposite.col < size.numCols - 1) {
48
+ opposite.col += 1;
49
49
  }
50
50
  break;
51
51
  }
@@ -64,41 +64,41 @@ export const handleNav = (
64
64
  export const handleArrowNav = (
65
65
  ev: Pick<KeyboardEvent<HTMLInputElement>, 'key' | 'metaKey'>,
66
66
  cursor: CellAddress | undefined,
67
- { numRows, numColumns }: GridSize,
67
+ { numRows, numCols }: GridSize,
68
68
  ): CellAddress | undefined => {
69
69
  switch (ev.key) {
70
70
  case 'ArrowUp':
71
71
  if (cursor === undefined) {
72
- return { row: 0, column: 0 };
72
+ return { row: 0, col: 0 };
73
73
  } else if (cursor.row > 0) {
74
- return { row: ev.metaKey ? 0 : cursor.row - 1, column: cursor.column };
74
+ return { row: ev.metaKey ? 0 : cursor.row - 1, col: cursor.col };
75
75
  }
76
76
  break;
77
77
  case 'ArrowDown':
78
78
  if (cursor === undefined) {
79
- return { row: 0, column: 0 };
79
+ return { row: 0, col: 0 };
80
80
  } else if (cursor.row < numRows - 1) {
81
- return { row: ev.metaKey ? numRows - 1 : cursor.row + 1, column: cursor.column };
81
+ return { row: ev.metaKey ? numRows - 1 : cursor.row + 1, col: cursor.col };
82
82
  }
83
83
  break;
84
84
  case 'ArrowLeft':
85
85
  if (cursor === undefined) {
86
- return { row: 0, column: 0 };
87
- } else if (cursor.column > 0) {
88
- return { row: cursor.row, column: ev.metaKey ? 0 : cursor.column - 1 };
86
+ return { row: 0, col: 0 };
87
+ } else if (cursor.col > 0) {
88
+ return { row: cursor.row, col: ev.metaKey ? 0 : cursor.col - 1 };
89
89
  }
90
90
  break;
91
91
  case 'ArrowRight':
92
92
  if (cursor === undefined) {
93
- return { row: 0, column: 0 };
94
- } else if (cursor.column < numColumns - 1) {
95
- return { row: cursor.row, column: ev.metaKey ? numColumns - 1 : cursor.column + 1 };
93
+ return { row: 0, col: 0 };
94
+ } else if (cursor.col < numCols - 1) {
95
+ return { row: cursor.row, col: ev.metaKey ? numCols - 1 : cursor.col + 1 };
96
96
  }
97
97
  break;
98
98
  case 'Home':
99
- return { row: 0, column: 0 };
99
+ return { row: 0, col: 0 };
100
100
  case 'End':
101
- return { row: numRows - 1, column: numColumns - 1 };
101
+ return { row: numRows - 1, col: numCols - 1 };
102
102
  }
103
103
  };
104
104
 
@@ -2,21 +2,16 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import React, { type PropsWithChildren, createContext, useContext, useState, useEffect } from 'react';
5
+ import React, { type PropsWithChildren, createContext, useContext, useMemo, useState } from 'react';
6
6
 
7
7
  import { invariant } from '@dxos/invariant';
8
- import { type FunctionType } from '@dxos/plugin-script';
9
- import { fullyQualifiedId, type Space } from '@dxos/react-client/echo';
8
+ import { type Space } from '@dxos/react-client/echo';
10
9
 
11
- import { FormattingModel } from './formatting';
12
- import { type CellAddress, type CellRange, defaultFunctions, SheetModel } from '../../model';
10
+ import { createDecorations } from './decorations';
11
+ import { type CellAddress, type CellRange } from '../../defs';
12
+ import { useSheetModel, useFormattingModel } from '../../hooks';
13
+ import { type FormattingModel, type SheetModel } from '../../model';
13
14
  import { type SheetType } from '../../types';
14
- import { type FunctionContextOptions } from '../ComputeGraph';
15
- // TODO(wittjosiah): Refactor. This is not exported from ./components due to depending on ECHO.
16
- import { useComputeGraph } from '../ComputeGraph/graph-context';
17
-
18
- // TODO(wittjosiah): Factor out.
19
- const OBJECT_ID_LENGTH = 60; // 33 (space id) + 26 (object id) + 1 (separator).
20
15
 
21
16
  export type SheetContextType = {
22
17
  model: SheetModel;
@@ -36,6 +31,9 @@ export type SheetContextType = {
36
31
  // Events.
37
32
  // TODO(burdon): Generalize.
38
33
  onInfo?: () => void;
34
+
35
+ // Decorations.
36
+ decorations: ReturnType<typeof createDecorations>;
39
37
  };
40
38
 
41
39
  const SheetContext = createContext<SheetContextType | null>(null);
@@ -50,49 +48,7 @@ export type SheetContextProps = {
50
48
  sheet: SheetType;
51
49
  space: Space;
52
50
  readonly?: boolean;
53
- } & Pick<SheetContextType, 'onInfo'> &
54
- Partial<FunctionContextOptions>;
55
-
56
- /**
57
- * Map from binding to fully qualified ECHO ID.
58
- */
59
- const mapFormulaBindingToId =
60
- (functions: FunctionType[]) =>
61
- (formula: string): string => {
62
- return formula.replace(/([a-zA-Z0-9]+)\((.*)\)/g, (match, binding, args) => {
63
- if (defaultFunctions.find((fn) => fn.name === binding) || binding === 'EDGE') {
64
- return match;
65
- }
66
-
67
- const fn = functions.find((fn) => fn.binding === binding);
68
- if (fn) {
69
- return `${fullyQualifiedId(fn)}(${args})`;
70
- } else {
71
- return match;
72
- }
73
- });
74
- };
75
-
76
- /**
77
- * Map from fully qualified ECHO ID to binding.
78
- */
79
- const mapFormulaBindingFromId =
80
- (functions: FunctionType[]) =>
81
- (formula: string): string => {
82
- return formula.replace(/([a-zA-Z0-9]+):([a-zA-Z0-9]+)\((.*)\)/g, (match, spaceId, objectId, args) => {
83
- const id = `${spaceId}:${objectId}`;
84
- if (id.length !== OBJECT_ID_LENGTH) {
85
- return match;
86
- }
87
-
88
- const fn = functions.find((fn) => fullyQualifiedId(fn) === id);
89
- if (fn?.binding) {
90
- return `${fn.binding}(${args})`;
91
- } else {
92
- return match;
93
- }
94
- });
95
- };
51
+ } & Pick<SheetContextType, 'onInfo'>;
96
52
 
97
53
  export const SheetContextProvider = ({
98
54
  children,
@@ -100,36 +56,17 @@ export const SheetContextProvider = ({
100
56
  space,
101
57
  readonly,
102
58
  onInfo,
103
- ...options
104
59
  }: PropsWithChildren<SheetContextProps>) => {
105
- const graph = useComputeGraph(space, options);
60
+ const model = useSheetModel(space, sheet, { readonly });
61
+ const formatting = useFormattingModel(model);
106
62
 
63
+ // TODO(Zan): Impl. set range and set cursor that scrolls to that cell or range if it is not visible.
107
64
  const [cursor, setCursor] = useState<CellAddress>();
108
65
  const [range, setRange] = useState<CellRange>();
109
66
  const [editing, setEditing] = useState<boolean>(false);
67
+ const decorations = useMemo(() => createDecorations(), []);
110
68
 
111
- const [[model, formatting] = [], setModels] = useState<[SheetModel, FormattingModel] | undefined>(undefined);
112
- useEffect(() => {
113
- let model: SheetModel | undefined;
114
- let formatting;
115
- const t = setTimeout(async () => {
116
- model = new SheetModel(graph, sheet, space, { readonly, mapFormulaBindingToId, mapFormulaBindingFromId });
117
- await model.initialize();
118
- formatting = new FormattingModel(model);
119
- setModels([model, formatting]);
120
- });
121
-
122
- return () => {
123
- clearTimeout(t);
124
- void model?.destroy();
125
- };
126
- }, [graph, readonly]);
127
-
128
- if (!model || !formatting) {
129
- return null;
130
- }
131
-
132
- return (
69
+ return !model || !formatting ? null : (
133
70
  <SheetContext.Provider
134
71
  value={{
135
72
  model,
@@ -142,6 +79,7 @@ export const SheetContextProvider = ({
142
79
  setEditing,
143
80
  // TODO(burdon): Change to event.
144
81
  onInfo,
82
+ decorations,
145
83
  }}
146
84
  >
147
85
  {children}
@@ -0,0 +1,205 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { effect } from '@preact/signals-core';
6
+ import React, { type PropsWithChildren, useCallback, useEffect, useMemo } from 'react';
7
+
8
+ import { type IntentResolver, LayoutAction, useIntentDispatcher, useIntentResolver } from '@dxos/app-framework';
9
+ import { debounce } from '@dxos/async';
10
+ import { fullyQualifiedId } from '@dxos/react-client/echo';
11
+ import { Icon, useTranslation } from '@dxos/react-ui';
12
+
13
+ import { type Decoration } from './decorations';
14
+ import { useSheetContext } from './sheet-context';
15
+ import { addressFromIndex, addressToIndex, type CellAddress, closest } from '../../defs';
16
+ import { SHEET_PLUGIN } from '../../meta';
17
+
18
+ // TODO(burdon): Move into folder; split hooks.
19
+
20
+ const CommentIndicator = () => {
21
+ return (
22
+ <div
23
+ role='none'
24
+ className='absolute top-0 right-0 w-0 h-0 border-t-8 border-l-8 border-t-cmCommentSurface border-l-transparent'
25
+ />
26
+ );
27
+ };
28
+
29
+ const ThreadedCellWrapper = ({ children }: PropsWithChildren) => {
30
+ const dispatch = useIntentDispatcher();
31
+ const [isHovered, setIsHovered] = React.useState(false);
32
+ const { t } = useTranslation(SHEET_PLUGIN);
33
+
34
+ const handleClick = React.useCallback(
35
+ (_event: React.MouseEvent) => {
36
+ void dispatch({ action: LayoutAction.SET_LAYOUT, data: { element: 'complementary', state: true } });
37
+ },
38
+ [dispatch],
39
+ );
40
+
41
+ return (
42
+ <div
43
+ role='none'
44
+ className='relative h-full is-full'
45
+ onMouseEnter={() => {
46
+ setIsHovered(true);
47
+ }}
48
+ onMouseLeave={() => {
49
+ setIsHovered(false);
50
+ }}
51
+ >
52
+ <CommentIndicator />
53
+ {isHovered && (
54
+ <div className='absolute inset-0 flex items-center justify-end pr-1'>
55
+ <button
56
+ className='ch-button text-xs min-bs-0 p-1'
57
+ onClick={handleClick}
58
+ aria-label={t('open comment for sheet cell')}
59
+ >
60
+ <Icon icon='ph--chat--regular' aria-hidden={true} />
61
+ </button>
62
+ </div>
63
+ )}
64
+ {children}
65
+ </div>
66
+ );
67
+ };
68
+
69
+ const createThreadDecoration = (cellIndex: string, threadId: string, sheetId: string): Decoration => {
70
+ return {
71
+ type: 'comment',
72
+ cellIndex,
73
+ decorate: (props) => <ThreadedCellWrapper {...props} />,
74
+ };
75
+ };
76
+
77
+ // TODO(burdon): Factor out hooks.
78
+
79
+ const useUpdateCursorOnThreadSelection = () => {
80
+ const { setCursor, model } = useSheetContext();
81
+
82
+ const handleScrollIntoView: IntentResolver = useCallback(
83
+ ({ action, data }) => {
84
+ switch (action) {
85
+ case LayoutAction.SCROLL_INTO_VIEW: {
86
+ if (!data?.id || data?.cursor === undefined || data?.id !== fullyQualifiedId(model.sheet)) {
87
+ return;
88
+ }
89
+
90
+ // TODO(Zan): Everywhere we refer to the cursor in a thread context should change to `anchor`.
91
+ const cellAddress = addressFromIndex(model.sheet, data.cursor);
92
+ setCursor(cellAddress);
93
+ }
94
+ }
95
+ },
96
+ [model.sheet, setCursor],
97
+ );
98
+
99
+ useIntentResolver(SHEET_PLUGIN, handleScrollIntoView);
100
+ };
101
+
102
+ const useSelectThreadOnCursorChange = () => {
103
+ const { cursor, model } = useSheetContext();
104
+ const dispatch = useIntentDispatcher();
105
+
106
+ const activeThreads = useMemo(
107
+ () =>
108
+ model.sheet.threads?.filter(
109
+ (thread): thread is NonNullable<typeof thread> => !!thread && thread.status === 'active',
110
+ ) ?? [],
111
+ [JSON.stringify(model.sheet.threads)],
112
+ );
113
+
114
+ const activeThreadAddresses = useMemo(
115
+ () =>
116
+ activeThreads
117
+ .map((thread) => thread.anchor)
118
+ .filter((anchor): anchor is NonNullable<typeof anchor> => anchor !== undefined)
119
+ .map((anchor) => addressFromIndex(model.sheet, anchor)),
120
+ [activeThreads, model.sheet],
121
+ );
122
+
123
+ const selectClosestThread = useCallback(
124
+ (cellAddress: CellAddress) => {
125
+ if (!cellAddress || !activeThreads) {
126
+ return;
127
+ }
128
+
129
+ const closestThreadAnchor = closest(cellAddress, activeThreadAddresses);
130
+ if (closestThreadAnchor) {
131
+ const closestThread = activeThreads.find(
132
+ (thread) => thread && thread.anchor === addressToIndex(model.sheet, closestThreadAnchor),
133
+ );
134
+
135
+ if (closestThread) {
136
+ void dispatch([
137
+ { action: 'dxos.org/plugin/thread/action/select', data: { current: fullyQualifiedId(closestThread) } },
138
+ ]);
139
+ }
140
+ }
141
+ },
142
+ [dispatch, activeThreads, activeThreadAddresses, model.sheet],
143
+ );
144
+
145
+ const debounced = useMemo(() => {
146
+ return debounce((cursor: CellAddress) => requestAnimationFrame(() => selectClosestThread(cursor)), 50);
147
+ }, [selectClosestThread]);
148
+
149
+ useEffect(() => {
150
+ if (!cursor) {
151
+ return;
152
+ }
153
+ debounced(cursor);
154
+ }, [cursor, selectClosestThread]);
155
+ };
156
+
157
+ const useThreadDecorations = () => {
158
+ const { decorations, model } = useSheetContext();
159
+ const sheet = useMemo(() => model.sheet, [model.sheet]);
160
+ const sheetId = useMemo(() => fullyQualifiedId(sheet), [sheet]);
161
+
162
+ useEffect(() => {
163
+ const unsubscribe = effect(() => {
164
+ const activeThreadAnchors = new Set<string>();
165
+ if (!sheet.threads) {
166
+ return;
167
+ }
168
+
169
+ // Process active threads
170
+ for (const thread of sheet.threads) {
171
+ if (!thread || thread.anchor === undefined || thread.status === 'resolved') {
172
+ continue;
173
+ }
174
+
175
+ activeThreadAnchors.add(thread.anchor);
176
+ const index = thread.anchor;
177
+
178
+ // Add decoration only if it doesn't already exist
179
+ const existingDecorations = decorations.getDecorationsForCell(index);
180
+ if (!existingDecorations || !existingDecorations.some((d) => d.type === 'comment')) {
181
+ decorations.addDecoration(index, createThreadDecoration(index, thread.id, sheetId));
182
+ }
183
+ }
184
+
185
+ // Remove decorations for resolved or deleted threads
186
+ for (const decoration of decorations.getAllDecorations()) {
187
+ if (decoration.type !== 'comment') {
188
+ continue;
189
+ }
190
+
191
+ if (!activeThreadAnchors.has(decoration.cellIndex)) {
192
+ decorations.removeDecoration(decoration.cellIndex, 'comment');
193
+ }
194
+ }
195
+ });
196
+
197
+ return () => unsubscribe();
198
+ });
199
+ };
200
+
201
+ export const useThreads = () => {
202
+ useUpdateCursorOnThreadSelection();
203
+ useSelectThreadOnCursorChange();
204
+ useThreadDecorations();
205
+ };
@@ -2,30 +2,84 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import React from 'react';
5
+ import React, { useCallback } from 'react';
6
6
 
7
- import { type LayoutCoordinate } from '@dxos/app-framework';
8
- import { mx } from '@dxos/react-ui-theme';
7
+ import { useIntentDispatcher } from '@dxos/app-framework';
8
+ import { fullyQualifiedId } from '@dxos/react-client/echo';
9
+ import { useIsDirectlyAttended } from '@dxos/react-ui-attention';
10
+ import { focusRing, mx } from '@dxos/react-ui-theme';
9
11
 
10
12
  import { Sheet, type SheetRootProps } from './Sheet';
13
+ import { Toolbar, type ToolbarAction } from './Toolbar';
14
+
15
+ // TODO(Zan): Factor out, copied this from MarkdownPlugin.
16
+ const attentionFragment = mx(
17
+ 'group-focus-within/editor:attention-surface group-[[aria-current]]/editor:attention-surface',
18
+ 'group-focus-within/editor:border-separator',
19
+ );
20
+
21
+ // TODO(Zan): Factor out, copied this from MarkdownPlugin.
22
+ export const sectionToolbarLayout =
23
+ 'bs-[--rail-action] bg-[--sticky-bg] sticky block-start-0 __-block-start-px transition-opacity';
24
+
25
+ const SheetContainer = ({ sheet, space, role }: SheetRootProps & { role?: string }) => {
26
+ const dispatch = useIntentDispatcher();
27
+
28
+ const id = fullyQualifiedId(sheet);
29
+ const isDirectlyAttended = useIsDirectlyAttended(id);
30
+
31
+ // TODO(Zan): Centralise the toolbar action handler. Current implementation in stories.
32
+ const handleAction = useCallback(
33
+ (action: ToolbarAction) => {
34
+ switch (action.type) {
35
+ case 'comment': {
36
+ // TODO(Zan): We shouldn't hardcode the action ID.
37
+ void dispatch({
38
+ action: 'dxos.org/plugin/thread/action/create',
39
+ data: {
40
+ cursor: action.anchor,
41
+ name: action.cellContent,
42
+ subject: sheet,
43
+ },
44
+ });
45
+ }
46
+ }
47
+ },
48
+ [sheet, dispatch],
49
+ );
11
50
 
12
- const SheetContainer = ({
13
- sheet,
14
- space,
15
- role,
16
- remoteFunctionUrl,
17
- }: SheetRootProps & { role?: string; coordinate?: LayoutCoordinate }) => {
18
51
  return (
19
- <div
20
- role='none'
21
- className={mx(
22
- 'flex',
23
- role === 'article' && 'row-span-2',
24
- role === 'section' && 'aspect-square border-y border-is border-separator',
25
- )}
26
- >
27
- <Sheet.Root sheet={sheet} space={space} remoteFunctionUrl={remoteFunctionUrl}>
28
- <Sheet.Main />
52
+ <div role='none' className={role === 'article' ? 'row-span-2 grid grid-rows-subgrid' : undefined}>
53
+ <Sheet.Root space={space} sheet={sheet}>
54
+ <div role='none' className={mx('flex flex-0 justify-center overflow-x-auto')}>
55
+ <Toolbar.Root
56
+ onAction={handleAction}
57
+ classNames={mx(
58
+ role === 'section'
59
+ ? ['z-[2] group-focus-within/section:visible', !isDirectlyAttended && 'invisible', sectionToolbarLayout]
60
+ : 'group-focus-within/editor:border-separator group-[[aria-current]]/editor:border-separator',
61
+ )}
62
+ >
63
+ {/* TODO(Zan): Restore some of this functionality */}
64
+ {/* <Toolbar.Styles /> */}
65
+ {/* <Toolbar.Format /> */}
66
+ {/* <Toolbar.Alignment /> */}
67
+ <Toolbar.Separator />
68
+ <Toolbar.Actions />
69
+ </Toolbar.Root>
70
+ </div>
71
+ <div
72
+ role='none'
73
+ className={mx(
74
+ role === 'section' && 'aspect-square border-is border-bs border-be border-separator',
75
+ role === 'article' &&
76
+ 'flex is-full overflow-hidden focus-visible:ring-inset row-span-1 data-[toolbar=disabled]:pbs-2 data-[toolbar=disabled]:row-span-2 border-bs border-separator',
77
+ focusRing,
78
+ attentionFragment,
79
+ )}
80
+ >
81
+ <Sheet.Main />
82
+ </div>
29
83
  </Sheet.Root>
30
84
  </div>
31
85
  );