@dxos/plugin-sheet 0.6.14-main.7bd9c89 → 0.6.14-staging.54a8bab

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 (231) hide show
  1. package/dist/lib/browser/{SheetContainer-AKWROARP.mjs → SheetContainer-JZEKRM4Z.mjs} +83 -76
  2. package/dist/lib/browser/SheetContainer-JZEKRM4Z.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-BWN5DZWZ.mjs → chunk-BVUN7SHF.mjs} +6 -13
  4. package/dist/lib/browser/chunk-BVUN7SHF.mjs.map +7 -0
  5. package/dist/lib/browser/{chunk-FGMFOW6U.mjs → chunk-G2FUL6PK.mjs} +791 -690
  6. package/dist/lib/browser/chunk-G2FUL6PK.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-PJE74VO4.cjs} +81 -81
  18. package/dist/lib/node/SheetContainer-PJE74VO4.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-STZ7S7RF.cjs} +755 -668
  26. package/dist/lib/node/chunk-STZ7S7RF.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-R73XEXHU.mjs} +83 -76
  37. package/dist/lib/node-esm/SheetContainer-R73XEXHU.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-5WPZCXNS.mjs → chunk-CR4K75EL.mjs} +296 -44
  41. package/dist/lib/node-esm/chunk-CR4K75EL.mjs.map +7 -0
  42. package/dist/lib/node-esm/{chunk-WFDTY3IC.mjs → chunk-UIBWRHW7.mjs} +6 -13
  43. package/dist/lib/node-esm/chunk-UIBWRHW7.mjs.map +7 -0
  44. package/dist/lib/node-esm/{chunk-T3PRH7QS.mjs → chunk-V5N3Y2O5.mjs} +791 -690
  45. package/dist/lib/node-esm/chunk-V5N3Y2O5.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/GridSheet/GridSheet.d.ts.map +1 -1
  56. package/dist/types/src/components/GridSheet/GridSheet.stories.d.ts.map +1 -1
  57. package/dist/types/src/components/GridSheet/util.d.ts +1 -2
  58. package/dist/types/src/components/GridSheet/util.d.ts.map +1 -1
  59. package/dist/types/src/components/RangeList/RangeList.d.ts +7 -0
  60. package/dist/types/src/components/RangeList/RangeList.d.ts.map +1 -0
  61. package/dist/types/src/components/RangeList/index.d.ts +2 -0
  62. package/dist/types/src/components/RangeList/index.d.ts.map +1 -0
  63. package/dist/types/src/components/SheetContainer/SheetContainer.d.ts +6 -3
  64. package/dist/types/src/components/SheetContainer/SheetContainer.d.ts.map +1 -1
  65. package/dist/types/src/components/SheetContainer/SheetContainer.stories.d.ts.map +1 -1
  66. package/dist/types/src/components/SheetContext/SheetContext.d.ts +7 -5
  67. package/dist/types/src/components/SheetContext/SheetContext.d.ts.map +1 -1
  68. package/dist/types/src/components/Toolbar/Toolbar.d.ts +6 -3
  69. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  70. package/dist/types/src/components/index.d.ts +5 -2
  71. package/dist/types/src/components/index.d.ts.map +1 -1
  72. package/dist/types/src/compute-graph/compute-graph-registry.d.ts.map +1 -0
  73. package/dist/types/src/{graph → compute-graph}/compute-graph.d.ts +3 -3
  74. package/dist/types/src/compute-graph/compute-graph.d.ts.map +1 -0
  75. package/dist/types/src/compute-graph/compute-graph.stories.d.ts.map +1 -0
  76. package/dist/types/src/compute-graph/compute-graph.test.d.ts.map +1 -0
  77. package/dist/types/src/compute-graph/compute-node.d.ts.map +1 -0
  78. package/dist/types/src/compute-graph/functions/async-function.d.ts.map +1 -0
  79. package/dist/types/src/compute-graph/functions/edge-function.d.ts.map +1 -0
  80. package/dist/types/src/compute-graph/functions/function-defs.d.ts.map +1 -0
  81. package/dist/types/src/compute-graph/functions/index.d.ts.map +1 -0
  82. package/dist/types/src/compute-graph/hyperformula.test.d.ts.map +1 -0
  83. package/dist/types/src/compute-graph/index.d.ts.map +1 -0
  84. package/dist/types/src/compute-graph/testing/index.d.ts.map +1 -0
  85. package/dist/types/src/compute-graph/testing/test-builder.d.ts.map +1 -0
  86. package/dist/types/src/compute-graph/testing/test-plugin.d.ts.map +1 -0
  87. package/dist/types/src/compute-graph/util.d.ts.map +1 -0
  88. package/dist/types/src/defs/index.d.ts +1 -1
  89. package/dist/types/src/defs/index.d.ts.map +1 -1
  90. package/dist/types/src/defs/sheet-range-types.d.ts +2 -2
  91. package/dist/types/src/defs/sheet-range-types.d.ts.map +1 -1
  92. package/dist/types/src/defs/types.d.ts +6 -0
  93. package/dist/types/src/defs/types.d.ts.map +1 -1
  94. package/dist/types/src/defs/util.d.ts +3 -7
  95. package/dist/types/src/defs/util.d.ts.map +1 -1
  96. package/dist/types/src/extensions/compute.d.ts +1 -1
  97. package/dist/types/src/extensions/compute.d.ts.map +1 -1
  98. package/dist/types/src/extensions/editor/extension.d.ts +23 -3
  99. package/dist/types/src/extensions/editor/extension.d.ts.map +1 -1
  100. package/dist/types/src/index.d.ts +1 -1
  101. package/dist/types/src/index.d.ts.map +1 -1
  102. package/dist/types/src/integrations/index.d.ts +2 -0
  103. package/dist/types/src/integrations/index.d.ts.map +1 -0
  104. package/dist/types/src/integrations/thread-ranges.d.ts +7 -0
  105. package/dist/types/src/integrations/thread-ranges.d.ts.map +1 -0
  106. package/dist/types/src/meta.d.ts +1 -0
  107. package/dist/types/src/meta.d.ts.map +1 -1
  108. package/dist/types/src/model/index.d.ts +1 -1
  109. package/dist/types/src/model/index.d.ts.map +1 -1
  110. package/dist/types/src/model/sheet-model.d.ts +13 -6
  111. package/dist/types/src/model/sheet-model.d.ts.map +1 -1
  112. package/dist/types/src/{hooks → model}/useSheetModel.d.ts +1 -1
  113. package/dist/types/src/model/useSheetModel.d.ts.map +1 -0
  114. package/dist/types/src/serializer.d.ts +4 -0
  115. package/dist/types/src/serializer.d.ts.map +1 -0
  116. package/dist/types/src/testing/testing.d.ts +1 -1
  117. package/dist/types/src/testing/testing.d.ts.map +1 -1
  118. package/dist/types/src/translations.d.ts +18 -3
  119. package/dist/types/src/translations.d.ts.map +1 -1
  120. package/dist/types/src/types.d.ts +37 -33
  121. package/dist/types/src/types.d.ts.map +1 -1
  122. package/package.json +43 -50
  123. package/src/SheetPlugin.tsx +45 -12
  124. package/src/components/ComputeGraph/ComputeGraphContextProvider.tsx +19 -2
  125. package/src/components/FunctionEditor/FunctionEditor.tsx +5 -5
  126. package/src/components/GridSheet/GridSheet.stories.tsx +3 -1
  127. package/src/components/GridSheet/GridSheet.tsx +181 -24
  128. package/src/components/GridSheet/SheetCellEditor.stories.tsx +1 -1
  129. package/src/components/GridSheet/util.ts +34 -18
  130. package/src/components/RangeList/RangeList.tsx +53 -0
  131. package/src/components/RangeList/index.ts +5 -0
  132. package/src/components/SheetContainer/SheetContainer.stories.tsx +5 -3
  133. package/src/components/SheetContainer/SheetContainer.tsx +10 -4
  134. package/src/components/SheetContext/SheetContext.tsx +43 -20
  135. package/src/components/Toolbar/Toolbar.tsx +98 -74
  136. package/src/components/index.ts +1 -0
  137. package/src/{graph → compute-graph}/compute-graph.stories.tsx +2 -1
  138. package/src/{graph → compute-graph}/compute-graph.ts +27 -9
  139. package/src/{graph → compute-graph}/compute-node.ts +2 -3
  140. package/src/{graph → compute-graph}/functions/async-function.ts +1 -0
  141. package/src/{graph → compute-graph}/functions/edge-function.ts +5 -3
  142. package/src/defs/index.ts +1 -1
  143. package/src/defs/sheet-range-types.ts +9 -6
  144. package/src/defs/types.ts +6 -1
  145. package/src/defs/util.ts +4 -19
  146. package/src/extensions/compute.stories.tsx +2 -2
  147. package/src/extensions/compute.ts +2 -2
  148. package/src/extensions/editor/extension.test.ts +1 -1
  149. package/src/extensions/editor/extension.ts +48 -23
  150. package/src/index.ts +2 -2
  151. package/src/integrations/index.ts +5 -0
  152. package/src/integrations/thread-ranges.ts +101 -0
  153. package/src/meta.ts +1 -0
  154. package/src/model/index.ts +1 -1
  155. package/src/model/sheet-model.test.ts +6 -4
  156. package/src/model/sheet-model.ts +103 -26
  157. package/src/{hooks → model}/useSheetModel.ts +1 -1
  158. package/src/serializer.ts +27 -0
  159. package/src/testing/testing.tsx +1 -1
  160. package/src/translations.ts +18 -3
  161. package/src/types.ts +24 -2
  162. package/dist/lib/browser/SheetContainer-AKWROARP.mjs.map +0 -7
  163. package/dist/lib/browser/chunk-BWN5DZWZ.mjs.map +0 -7
  164. package/dist/lib/browser/chunk-FGMFOW6U.mjs.map +0 -7
  165. package/dist/lib/browser/chunk-GSV5QNLD.mjs.map +0 -7
  166. package/dist/lib/node/SheetContainer-N5IQGEFL.cjs.map +0 -7
  167. package/dist/lib/node/chunk-53BMSUIK.cjs.map +0 -7
  168. package/dist/lib/node/chunk-5XPK2V4A.cjs.map +0 -7
  169. package/dist/lib/node/chunk-NZARD7UP.cjs.map +0 -7
  170. package/dist/lib/node/graph-Q3N2X26H.cjs.map +0 -7
  171. package/dist/lib/node-esm/SheetContainer-46PBMF2E.mjs.map +0 -7
  172. package/dist/lib/node-esm/chunk-5WPZCXNS.mjs.map +0 -7
  173. package/dist/lib/node-esm/chunk-T3PRH7QS.mjs.map +0 -7
  174. package/dist/lib/node-esm/chunk-WFDTY3IC.mjs.map +0 -7
  175. package/dist/types/src/graph/compute-graph-registry.d.ts.map +0 -1
  176. package/dist/types/src/graph/compute-graph.d.ts.map +0 -1
  177. package/dist/types/src/graph/compute-graph.stories.d.ts.map +0 -1
  178. package/dist/types/src/graph/compute-graph.test.d.ts.map +0 -1
  179. package/dist/types/src/graph/compute-node.d.ts.map +0 -1
  180. package/dist/types/src/graph/functions/async-function.d.ts.map +0 -1
  181. package/dist/types/src/graph/functions/edge-function.d.ts.map +0 -1
  182. package/dist/types/src/graph/functions/function-defs.d.ts.map +0 -1
  183. package/dist/types/src/graph/functions/index.d.ts.map +0 -1
  184. package/dist/types/src/graph/hyperformula.test.d.ts.map +0 -1
  185. package/dist/types/src/graph/index.d.ts.map +0 -1
  186. package/dist/types/src/graph/testing/index.d.ts.map +0 -1
  187. package/dist/types/src/graph/testing/test-builder.d.ts.map +0 -1
  188. package/dist/types/src/graph/testing/test-plugin.d.ts.map +0 -1
  189. package/dist/types/src/graph/util.d.ts.map +0 -1
  190. package/dist/types/src/hooks/hooks.stories.d.ts +0 -6
  191. package/dist/types/src/hooks/hooks.stories.d.ts.map +0 -1
  192. package/dist/types/src/hooks/index.d.ts +0 -4
  193. package/dist/types/src/hooks/index.d.ts.map +0 -1
  194. package/dist/types/src/hooks/threads.d.ts +0 -8
  195. package/dist/types/src/hooks/threads.d.ts.map +0 -1
  196. package/dist/types/src/hooks/useComputeGraph.d.ts +0 -7
  197. package/dist/types/src/hooks/useComputeGraph.d.ts.map +0 -1
  198. package/dist/types/src/hooks/useSheetModel.d.ts.map +0 -1
  199. package/dist/types/src/model/decorations.d.ts +0 -26
  200. package/dist/types/src/model/decorations.d.ts.map +0 -1
  201. package/src/hooks/hooks.stories.tsx +0 -53
  202. package/src/hooks/index.ts +0 -7
  203. package/src/hooks/threads.ts +0 -147
  204. package/src/hooks/useComputeGraph.ts +0 -28
  205. package/src/model/decorations.ts +0 -66
  206. /package/dist/lib/browser/{graph-M4IQ76QX.mjs.map → compute-graph-GGWUX644.mjs.map} +0 -0
  207. /package/dist/lib/node-esm/{graph-SMPUMOV2.mjs.map → compute-graph-2SCZT7N5.mjs.map} +0 -0
  208. /package/dist/types/src/{graph → compute-graph}/compute-graph-registry.d.ts +0 -0
  209. /package/dist/types/src/{graph → compute-graph}/compute-graph.stories.d.ts +0 -0
  210. /package/dist/types/src/{graph → compute-graph}/compute-graph.test.d.ts +0 -0
  211. /package/dist/types/src/{graph → compute-graph}/compute-node.d.ts +0 -0
  212. /package/dist/types/src/{graph → compute-graph}/functions/async-function.d.ts +0 -0
  213. /package/dist/types/src/{graph → compute-graph}/functions/edge-function.d.ts +0 -0
  214. /package/dist/types/src/{graph → compute-graph}/functions/function-defs.d.ts +0 -0
  215. /package/dist/types/src/{graph → compute-graph}/functions/index.d.ts +0 -0
  216. /package/dist/types/src/{graph → compute-graph}/hyperformula.test.d.ts +0 -0
  217. /package/dist/types/src/{graph → compute-graph}/index.d.ts +0 -0
  218. /package/dist/types/src/{graph → compute-graph}/testing/index.d.ts +0 -0
  219. /package/dist/types/src/{graph → compute-graph}/testing/test-builder.d.ts +0 -0
  220. /package/dist/types/src/{graph → compute-graph}/testing/test-plugin.d.ts +0 -0
  221. /package/dist/types/src/{graph → compute-graph}/util.d.ts +0 -0
  222. /package/src/{graph → compute-graph}/compute-graph-registry.ts +0 -0
  223. /package/src/{graph → compute-graph}/compute-graph.test.ts +0 -0
  224. /package/src/{graph → compute-graph}/functions/function-defs.ts +0 -0
  225. /package/src/{graph → compute-graph}/functions/index.ts +0 -0
  226. /package/src/{graph → compute-graph}/hyperformula.test.ts +0 -0
  227. /package/src/{graph → compute-graph}/index.ts +0 -0
  228. /package/src/{graph → compute-graph}/testing/index.ts +0 -0
  229. /package/src/{graph → compute-graph}/testing/test-builder.ts +0 -0
  230. /package/src/{graph → compute-graph}/testing/test-plugin.ts +0 -0
  231. /package/src/{graph → compute-graph}/util.ts +0 -0
@@ -2,9 +2,9 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { type MutableRefObject, useEffect, useLayoutEffect, useState } from 'react';
5
+ import { useEffect, useState } from 'react';
6
6
 
7
- import { createDocAccessor } from '@dxos/react-client/echo';
7
+ import { createDocAccessor, fullyQualifiedId } from '@dxos/react-client/echo';
8
8
  import { parseValue, cellClassesForFieldType } from '@dxos/react-ui-data';
9
9
  import {
10
10
  type GridContentProps,
@@ -16,10 +16,12 @@ import {
16
16
  type DxGridCellValue,
17
17
  colToA1Notation,
18
18
  rowToA1Notation,
19
+ commentedClassName,
19
20
  } from '@dxos/react-ui-grid';
20
21
  import { mx } from '@dxos/react-ui-theme';
21
22
 
22
- import { type CellAddress, inRange, cellClassNameForRange } from '../../defs';
23
+ import { type CellAddress, inRange, cellClassNameForRange, rangeFromIndex } from '../../defs';
24
+ import { parseThreadAnchorAsCellRange } from '../../integrations';
23
25
  import { type SheetModel } from '../../model';
24
26
 
25
27
  export const dxGridCellIndexToSheetCellAddress = (index: string): CellAddress => {
@@ -57,16 +59,26 @@ const createDxGridRows = (model: SheetModel): DxGridAxisMeta => {
57
59
  const projectCellProps = (model: SheetModel, col: number, row: number): DxGridCellValue => {
58
60
  const address = { col, row };
59
61
  const rawValue = model.getValue(address);
60
- if (rawValue === undefined || rawValue === null) {
61
- return { value: '' };
62
- }
63
- const ranges = model.sheet.ranges?.filter(({ range }) => inRange(range, address));
64
- const type = model.getValueType(address);
62
+ const ranges = model.sheet.ranges?.filter(({ range }) => inRange(rangeFromIndex(model.sheet, range), address));
63
+ const threadRefs = model.sheet.threads
64
+ ?.filter((thread) => {
65
+ const range = thread?.anchor && parseThreadAnchorAsCellRange(thread!.anchor);
66
+ return thread && range ? inRange(range, address) : false;
67
+ })
68
+ .map((thread) => fullyQualifiedId(thread!))
69
+ .join(' ');
70
+
71
+ const description = model.getValueDescription(address);
72
+ const type = description?.type;
73
+ const format = description?.format;
65
74
  const classNames = ranges?.map(cellClassNameForRange).reverse();
66
75
 
67
- return { value: parseValue(type, rawValue), className: mx(cellClassesForFieldType(type), classNames) };
76
+ return {
77
+ value: parseValue({ type, format, value: rawValue }),
78
+ className: mx(cellClassesForFieldType({ type, format }), threadRefs && commentedClassName, classNames),
79
+ dataRefs: threadRefs,
80
+ };
68
81
  };
69
-
70
82
  const gridCellGetter = (model: SheetModel) => {
71
83
  // TODO(thure): Actually use the cache.
72
84
  const cachedGridCells: DxGridPlaneCells = {};
@@ -115,23 +127,27 @@ const cellGetter = (model: SheetModel) => {
115
127
  };
116
128
 
117
129
  export const useSheetModelDxGridProps = (
118
- dxGridRef: MutableRefObject<DxGridElement | null>,
130
+ dxGrid: DxGridElement | null,
119
131
  model: SheetModel,
120
132
  ): Pick<GridContentProps, 'columns' | 'rows'> => {
121
133
  const [columns, setColumns] = useState<DxGridAxisMeta>(createDxGridColumns(model));
122
134
  const [rows, setRows] = useState<DxGridAxisMeta>(createDxGridColumns(model));
123
135
 
124
- useLayoutEffect(() => {
136
+ useEffect(() => {
125
137
  const cellsAccessor = createDocAccessor(model.sheet, ['cells']);
126
- if (dxGridRef.current) {
127
- dxGridRef.current.getCells = cellGetter(model);
138
+ if (dxGrid) {
139
+ dxGrid.getCells = cellGetter(model);
128
140
  }
129
141
  const handleCellsUpdate = () => {
130
- dxGridRef.current?.requestUpdate('initialCells');
142
+ dxGrid?.requestUpdate('initialCells');
131
143
  };
132
144
  cellsAccessor.handle.addListener('change', handleCellsUpdate);
133
- return () => cellsAccessor.handle.removeListener('change', handleCellsUpdate);
134
- }, [model]);
145
+ const unsubscribe = model.graph.update.on(handleCellsUpdate);
146
+ return () => {
147
+ cellsAccessor.handle.removeListener('change', handleCellsUpdate);
148
+ unsubscribe();
149
+ };
150
+ }, [model, dxGrid]);
135
151
 
136
152
  useEffect(() => {
137
153
  const columnMetaAccessor = createDocAccessor(model.sheet, ['columnMeta']);
@@ -148,7 +164,7 @@ export const useSheetModelDxGridProps = (
148
164
  columnMetaAccessor.handle.removeListener('change', handleColumnMetaUpdate);
149
165
  rowMetaAccessor.handle.removeListener('change', handleRowMetaUpdate);
150
166
  };
151
- }, [model]);
167
+ }, [model, dxGrid]);
152
168
 
153
169
  return { columns, rows };
154
170
  };
@@ -0,0 +1,53 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React, { useCallback } from 'react';
6
+
7
+ import { S } from '@dxos/echo-schema';
8
+ import { useTranslation } from '@dxos/react-ui';
9
+ import { List } from '@dxos/react-ui-list';
10
+ import { ghostHover } from '@dxos/react-ui-theme';
11
+
12
+ import { rangeFromIndex, rangeToA1Notation } from '../../defs';
13
+ import { SHEET_PLUGIN } from '../../meta';
14
+ import { Range, type SheetType } from '../../types';
15
+
16
+ export type RangeListProps = {
17
+ sheet: SheetType;
18
+ };
19
+
20
+ export const RangeList = ({ sheet }: RangeListProps) => {
21
+ const { t } = useTranslation(SHEET_PLUGIN);
22
+ // TODO(thure): Implement similar to comments, #8121
23
+ const handleSelectRange = (range: Range) => {};
24
+ const handleDeleteRange = useCallback(
25
+ (range: Range) => {
26
+ const index = sheet.ranges.findIndex((sheetRange) => sheetRange === range);
27
+ sheet.ranges.splice(index, 1);
28
+ },
29
+ [sheet],
30
+ );
31
+ return (
32
+ <>
33
+ <h2 className='p-2 text-sm font-semibold'>{t('range list heading')}</h2>
34
+ <List.Root<Range> items={sheet.ranges} isItem={S.is(Range)}>
35
+ {({ items: ranges }) =>
36
+ ranges.map((range, i) => (
37
+ <List.Item key={i} item={range} classNames={['p-2', ghostHover]}>
38
+ <List.ItemDragHandle />
39
+ <List.ItemTitle onClick={() => handleSelectRange(range)}>
40
+ {t('range title', {
41
+ position: rangeToA1Notation(rangeFromIndex(sheet, range.range)),
42
+ key: t(`range key ${range.key} label`),
43
+ value: t(`range value ${range.value} label`),
44
+ })}
45
+ </List.ItemTitle>
46
+ <List.ItemDeleteButton onClick={() => handleDeleteRange(range)} />
47
+ </List.Item>
48
+ ))
49
+ }
50
+ </List.Root>
51
+ </>
52
+ );
53
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './RangeList';
@@ -10,19 +10,20 @@ import { withClientProvider } from '@dxos/react-client/testing';
10
10
  import { withTheme, withLayout } from '@dxos/storybook-utils';
11
11
 
12
12
  import { SheetContainer } from './SheetContainer';
13
- import { useComputeGraph } from '../../hooks';
14
13
  import { createTestCells, useTestSheet, withComputeGraphDecorator } from '../../testing';
14
+ import translations from '../../translations';
15
15
  import { SheetType } from '../../types';
16
+ import { useComputeGraph } from '../ComputeGraph';
16
17
 
17
18
  export const Basic = () => {
18
19
  const space = useSpace();
19
20
  const graph = useComputeGraph(space);
20
21
  const sheet = useTestSheet(space, graph, { cells: createTestCells() });
21
- if (!sheet || !graph) {
22
+ if (!sheet || !space) {
22
23
  return null;
23
24
  }
24
25
 
25
- return <SheetContainer graph={graph} sheet={sheet} role='article' />;
26
+ return <SheetContainer space={space} sheet={sheet} role='article' />;
26
27
  };
27
28
 
28
29
  const meta: Meta = {
@@ -38,6 +39,7 @@ const meta: Meta = {
38
39
  classNames: 'grid grid-cols-1 grid-rows-[min-content_minmax(0,1fr)_min-content]',
39
40
  }),
40
41
  ],
42
+ parameters: { translations },
41
43
  };
42
44
 
43
45
  export default meta;
@@ -4,13 +4,19 @@
4
4
 
5
5
  import React from 'react';
6
6
 
7
+ import { type Space } from '@dxos/react-client/echo';
8
+
9
+ import { type SheetType } from '../../types';
10
+ import { useComputeGraph } from '../ComputeGraph';
7
11
  import { FunctionEditor } from '../FunctionEditor';
8
12
  import { GridSheet } from '../GridSheet';
9
- import { SheetProvider, type SheetProviderProps } from '../SheetContext';
13
+ import { SheetProvider } from '../SheetContext';
10
14
  import { Toolbar } from '../Toolbar';
11
15
 
12
- export const SheetContainer = ({ graph, sheet, role }: SheetProviderProps & { role?: string }) => {
13
- return (
16
+ export const SheetContainer = ({ space, sheet, role }: { space: Space; sheet: SheetType; role?: string }) => {
17
+ const graph = useComputeGraph(space);
18
+
19
+ return graph ? (
14
20
  <SheetProvider sheet={sheet} graph={graph}>
15
21
  <Toolbar.Root role={role}>
16
22
  <Toolbar.Styles />
@@ -23,5 +29,5 @@ export const SheetContainer = ({ graph, sheet, role }: SheetProviderProps & { ro
23
29
  <FunctionEditor />
24
30
  </div>
25
31
  </SheetProvider>
26
- );
32
+ ) : null;
27
33
  };
@@ -2,16 +2,21 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import React, { type PropsWithChildren, createContext, useContext, useMemo, useState } from 'react';
5
+ import React, { type PropsWithChildren, createContext, useCallback, useContext, useState } from 'react';
6
6
 
7
7
  import { invariant } from '@dxos/invariant';
8
8
  import { fullyQualifiedId } from '@dxos/react-client/echo';
9
- import { Grid, useGridContext, type GridScopedProps, type GridEditing } from '@dxos/react-ui-grid';
10
-
11
- import { type CellAddress, type CellRange } from '../../defs';
12
- import { type ComputeGraph } from '../../graph';
13
- import { useSheetModel, useSelectThreadOnCellFocus, useThreadDecorations } from '../../hooks';
14
- import { type SheetModel, createDecorations } from '../../model';
9
+ import {
10
+ Grid,
11
+ type GridContentProps,
12
+ type GridEditing,
13
+ type GridScopedProps,
14
+ useGridContext,
15
+ } from '@dxos/react-ui-grid';
16
+
17
+ import { type ComputeGraph } from '../../compute-graph';
18
+ import { type CellAddress, type CellRange, type CompleteCellRange } from '../../defs';
19
+ import { type SheetModel, useSheetModel } from '../../model';
15
20
  import { type SheetType } from '../../types';
16
21
 
17
22
  export type SheetContextValue = {
@@ -25,17 +30,19 @@ export type SheetContextValue = {
25
30
  setCursor: (cell: CellAddress | undefined) => void;
26
31
  range?: CellRange;
27
32
  setRange: (range: CellRange | undefined) => void;
33
+ cursorFallbackRange?: CompleteCellRange;
28
34
 
29
35
  // Editing state (undefined if not editing).
30
36
  editing: GridEditing;
31
37
  setEditing: (editing: GridEditing) => void;
32
38
 
39
+ // Active refs
40
+ activeRefs: GridContentProps['activeRefs'];
41
+ setActiveRefs: (activeRefs: GridContentProps['activeRefs']) => void;
42
+
33
43
  // Events.
34
44
  // TODO(burdon): Generalize.
35
45
  onInfo?: () => void;
36
-
37
- // Decorations.
38
- decorations: ReturnType<typeof createDecorations>;
39
46
  };
40
47
 
41
48
  const SheetContext = createContext<SheetContextValue | undefined>(undefined);
@@ -54,15 +61,29 @@ const SheetProviderImpl = ({
54
61
  }: GridScopedProps<PropsWithChildren<Pick<SheetContextValue, 'onInfo' | 'model'>>>) => {
55
62
  const { id, editing, setEditing } = useGridContext('SheetProvider', __gridScope);
56
63
 
57
- // TODO(Zan): Impl. set range and set cursor that scrolls to that cell or range if it is not visible.
58
- const decorations = useMemo(() => createDecorations(), []);
59
-
60
- // TODO(thure): Reconnect these.
61
- const [cursor, setCursor] = useState<CellAddress>();
62
- const [range, setRange] = useState<CellRange>();
63
-
64
- useSelectThreadOnCellFocus(model, cursor);
65
- useThreadDecorations(model, decorations);
64
+ const [cursor, setCursorInternal] = useState<CellAddress>();
65
+ const [range, setRangeInternal] = useState<CellRange>();
66
+ const [cursorFallbackRange, setCursorFallbackRange] = useState<CompleteCellRange>();
67
+ const [activeRefs, setActiveRefs] = useState<GridContentProps['activeRefs']>('');
68
+
69
+ const setCursor = useCallback(
70
+ (nextCursor?: CellAddress) => {
71
+ setCursorInternal(nextCursor);
72
+ setCursorFallbackRange(
73
+ range?.to ? (range as CompleteCellRange) : nextCursor ? { from: nextCursor!, to: nextCursor! } : undefined,
74
+ );
75
+ },
76
+ [range],
77
+ );
78
+ const setRange = useCallback(
79
+ (nextRange?: CellRange) => {
80
+ setRangeInternal(nextRange);
81
+ setCursorFallbackRange(
82
+ nextRange?.to ? (nextRange as CompleteCellRange) : cursor ? { from: cursor!, to: cursor! } : undefined,
83
+ );
84
+ },
85
+ [cursor],
86
+ );
66
87
 
67
88
  return (
68
89
  <SheetContext.Provider
@@ -75,9 +96,11 @@ const SheetProviderImpl = ({
75
96
  setCursor,
76
97
  range,
77
98
  setRange,
99
+ cursorFallbackRange,
100
+ activeRefs,
101
+ setActiveRefs,
78
102
  // TODO(burdon): Change to event.
79
103
  onInfo,
80
- decorations,
81
104
  }}
82
105
  >
83
106
  {children}
@@ -3,32 +3,36 @@
3
3
  //
4
4
 
5
5
  import { createContext } from '@radix-ui/react-context';
6
- import React, { type PropsWithChildren, useCallback, useMemo } from 'react';
6
+ import React, { type PropsWithChildren, useCallback } from 'react';
7
7
 
8
8
  import { useIntentDispatcher } from '@dxos/app-framework';
9
9
  import {
10
10
  Icon,
11
+ type ThemedClassName,
11
12
  Toolbar as NaturalToolbar,
12
- useTranslation,
13
- Tooltip,
14
- type ToolbarToggleGroupItemProps as NaturalToolbarToggleGroupItemProps,
15
13
  type ToolbarButtonProps as NaturalToolbarButtonProps,
14
+ type ToolbarToggleGroupItemProps as NaturalToolbarToggleGroupItemProps,
16
15
  type ToolbarToggleProps as NaturalToolbarToggleProps,
17
- type ThemedClassName,
16
+ Tooltip,
17
+ useTranslation,
18
18
  } from '@dxos/react-ui';
19
19
  import { useAttention } from '@dxos/react-ui-attention';
20
20
  import { nonNullable } from '@dxos/util';
21
21
 
22
22
  import {
23
- addressToIndex,
23
+ alignKey,
24
24
  type AlignKey,
25
25
  type AlignValue,
26
26
  type CommentKey,
27
27
  type CommentValue,
28
28
  inRange,
29
+ rangeFromIndex,
30
+ rangeToIndex,
31
+ styleKey,
29
32
  type StyleKey,
30
33
  type StyleValue,
31
34
  } from '../../defs';
35
+ import { completeCellRangeToThreadCursor } from '../../integrations';
32
36
  import { SHEET_PLUGIN } from '../../meta';
33
37
  import { type SheetType } from '../../types';
34
38
  import { useSheetContext } from '../SheetContext';
@@ -86,10 +90,11 @@ type CommentAction = { key: CommentKey; value: CommentValue; cellContent?: strin
86
90
  type StyleAction = { key: StyleKey; value: StyleValue };
87
91
 
88
92
  export type ToolbarAction = StyleAction | AlignAction | CommentAction;
93
+ export type ToolbarActionAnnotated = ToolbarAction & { unset?: boolean };
89
94
 
90
95
  export type ToolbarActionType = ToolbarAction['key'];
91
96
 
92
- export type ToolbarActionHandler = (action: ToolbarAction) => void;
97
+ export type ToolbarActionHandler = (action: ToolbarActionAnnotated) => void;
93
98
 
94
99
  export type ToolbarProps = ThemedClassName<
95
100
  PropsWithChildren<{
@@ -97,9 +102,9 @@ export type ToolbarProps = ThemedClassName<
97
102
  }>
98
103
  >;
99
104
 
100
- const [ToolbarContextProvider, useToolbarContext] = createContext<{ onAction: (action: ToolbarAction) => void }>(
101
- 'Toolbar',
102
- );
105
+ const [ToolbarContextProvider, useToolbarContext] = createContext<{
106
+ onAction: (action: ToolbarActionAnnotated) => void;
107
+ }>('Toolbar');
103
108
 
104
109
  // TODO(Zan): Factor out, copied this from MarkdownPlugin.
105
110
  const sectionToolbarLayout =
@@ -108,42 +113,50 @@ const sectionToolbarLayout =
108
113
  type Range = SheetType['ranges'][number];
109
114
 
110
115
  const ToolbarRoot = ({ children, role, classNames }: ToolbarProps) => {
111
- const { id, model, range, cursor } = useSheetContext();
116
+ const { id, model, cursorFallbackRange, cursor } = useSheetContext();
112
117
  const { hasAttention } = useAttention(id);
113
118
  const dispatch = useIntentDispatcher();
114
119
 
115
- // TODO(Zan): Centralise the toolbar action handler. Current implementation in stories.
120
+ // TODO(Zan): Externalize the toolbar action handler. E.g., Toolbar/keys should both fire events.
116
121
  const handleAction = useCallback(
117
- (action: ToolbarAction) => {
122
+ (action: ToolbarActionAnnotated) => {
118
123
  switch (action.key) {
119
- case 'align':
120
- if (cursor) {
121
- const index = model.sheet.ranges?.findIndex(
122
- (range) => range.key === action.key && inRange(range.range, cursor),
123
- );
124
- const nextRange = range ? { from: range.from, to: range.to ?? range.from } : { from: cursor, to: cursor };
124
+ case 'alignment':
125
+ if (cursorFallbackRange) {
126
+ const index =
127
+ model.sheet.ranges?.findIndex(
128
+ (range) =>
129
+ range.key === action.key &&
130
+ inRange(rangeFromIndex(model.sheet, range.range), cursorFallbackRange.from),
131
+ ) ?? -1;
125
132
  const nextRangeEntity = {
126
- range: nextRange as Range['range'],
133
+ range: rangeToIndex(model.sheet, cursorFallbackRange),
127
134
  key: action.key,
128
135
  value: action.value,
129
136
  };
130
137
  if (index < 0) {
131
138
  model.sheet.ranges?.push(nextRangeEntity);
139
+ } else if (model.sheet.ranges![index].value === action.value) {
140
+ model.sheet.ranges?.splice(index, 1);
132
141
  } else {
133
142
  model.sheet.ranges?.splice(index, 1, nextRangeEntity);
134
143
  }
135
144
  }
136
145
  break;
137
146
  case 'style':
138
- if (action.value === 'unset') {
139
- const index = model.sheet.ranges?.findIndex((range) => range.key === action.key);
147
+ if (action.unset) {
148
+ const index = model.sheet.ranges?.findIndex(
149
+ (range) =>
150
+ range.key === action.key &&
151
+ cursorFallbackRange &&
152
+ inRange(rangeFromIndex(model.sheet, range.range), cursorFallbackRange.from),
153
+ );
140
154
  if (index >= 0) {
141
155
  model.sheet.ranges?.splice(index, 1);
142
156
  }
143
- } else if (range || cursor) {
144
- const nextRange = range ? { from: range.from, to: range.to ?? range.from } : { from: cursor, to: cursor };
157
+ } else if (cursorFallbackRange) {
145
158
  model.sheet.ranges?.push({
146
- range: nextRange as Range['range'],
159
+ range: rangeToIndex(model.sheet, cursorFallbackRange),
147
160
  key: action.key,
148
161
  value: action.value,
149
162
  });
@@ -151,24 +164,27 @@ const ToolbarRoot = ({ children, role, classNames }: ToolbarProps) => {
151
164
  break;
152
165
  case 'comment': {
153
166
  // TODO(Zan): We shouldn't hardcode the action ID.
154
- void dispatch({
155
- action: 'dxos.org/plugin/thread/action/create',
156
- data: {
157
- cursor: action.value,
158
- name: action.cellContent,
159
- subject: model.sheet,
160
- },
161
- });
167
+ if (cursorFallbackRange) {
168
+ void dispatch({
169
+ action: 'dxos.org/plugin/thread/action/create',
170
+ data: {
171
+ cursor: completeCellRangeToThreadCursor(cursorFallbackRange),
172
+ name: action.cellContent,
173
+ subject: model.sheet,
174
+ },
175
+ });
176
+ }
162
177
  }
163
178
  }
164
179
  },
165
- [model.sheet, range, cursor, dispatch],
180
+ [model.sheet, cursorFallbackRange, cursor, dispatch],
166
181
  );
167
182
 
168
183
  return (
169
184
  <ToolbarContextProvider onAction={handleAction}>
170
185
  <NaturalToolbar.Root
171
186
  classNames={[
187
+ 'pli-0.5',
172
188
  ...(role === 'section'
173
189
  ? ['z-[2] group-focus-within/section:visible', !hasAttention && 'invisible', sectionToolbarLayout]
174
190
  : ['attention-surface']),
@@ -204,48 +220,55 @@ const Alignment = () => {
204
220
  const { onAction } = useToolbarContext('Alignment');
205
221
  const { t } = useTranslation(SHEET_PLUGIN);
206
222
 
207
- const value = useMemo(
208
- () =>
209
- cursor
210
- ? model.sheet.ranges?.find(({ range, key }) => key === 'alignment' && inRange(range, cursor))?.value
211
- : undefined,
212
- [cursor, model.sheet.ranges],
213
- );
223
+ // TODO(thure): Can this O(n) call be memoized?
224
+ const value = cursor
225
+ ? model.sheet.ranges?.findLast(
226
+ ({ range, key }) => key === alignKey && inRange(rangeFromIndex(model.sheet, range), cursor),
227
+ )?.value
228
+ : undefined;
214
229
 
215
230
  return (
216
231
  <NaturalToolbar.ToggleGroup
217
232
  type='single'
218
- value={value}
219
- onValueChange={(value: AlignValue) => onAction?.({ key: 'align', value })}
233
+ value={
234
+ // TODO(thure): providing `undefined` leaves the last item active which was active rather than showing none.
235
+ value ?? 'never'
236
+ }
237
+ onValueChange={(value: AlignValue) => onAction?.({ key: alignKey, value })}
220
238
  >
221
239
  {alignmentOptions.map(({ value, icon }) => (
222
240
  <ToolbarItem itemType='toggleGroupItem' key={value} value={value} icon={icon}>
223
- {t(`toolbar ${value} label`)}
241
+ {t('toolbar action label', {
242
+ key: t(`range key ${alignKey} label`),
243
+ value: t(`range value ${value} label`),
244
+ })}
224
245
  </ToolbarItem>
225
246
  ))}
226
247
  </NaturalToolbar.ToggleGroup>
227
248
  );
228
249
  };
229
250
 
230
- const styleOptions: ButtonProps<StyleValue>[] = [{ value: 'highlight', icon: 'ph--highlighter--regular' }];
251
+ const styleOptions: ButtonProps<StyleValue>[] = [
252
+ { value: 'highlight', icon: 'ph--highlighter--regular' },
253
+ { value: 'softwrap', icon: 'ph--paragraph--regular' },
254
+ ];
231
255
 
232
256
  const Styles = () => {
233
- const { cursor, model } = useSheetContext();
257
+ const { cursorFallbackRange, model } = useSheetContext();
234
258
  const { onAction } = useToolbarContext('Styles');
235
259
  const { t } = useTranslation(SHEET_PLUGIN);
236
260
 
237
- const activeValues = useMemo(
238
- () =>
239
- cursor
240
- ? model.sheet.ranges
241
- ?.filter(({ range, key }) => key === 'style' && inRange(range, cursor))
242
- .reduce((acc, { value }) => {
243
- acc.add(value);
244
- return acc;
245
- }, new Set())
246
- : undefined,
247
- [cursor, model.sheet.ranges],
248
- );
261
+ // TODO(thure): Can this O(n) call be memoized?
262
+ const activeValues = cursorFallbackRange
263
+ ? model.sheet.ranges
264
+ ?.filter(
265
+ ({ range, key }) => key === 'style' && inRange(rangeFromIndex(model.sheet, range), cursorFallbackRange.from),
266
+ )
267
+ .reduce((acc, { value }) => {
268
+ acc.add(value);
269
+ return acc;
270
+ }, new Set())
271
+ : undefined;
249
272
 
250
273
  return (
251
274
  <>
@@ -254,10 +277,15 @@ const Styles = () => {
254
277
  itemType='toggle'
255
278
  key={value}
256
279
  pressed={activeValues?.has(value)}
257
- onPressedChange={(nextPressed: boolean) => onAction?.({ key: 'style', value: nextPressed ? value : 'unset' })}
280
+ onPressedChange={(nextPressed: boolean) => {
281
+ onAction?.({ key: 'style', value, unset: !nextPressed });
282
+ }}
258
283
  icon={icon}
259
284
  >
260
- {t(`toolbar ${value} label`)}
285
+ {t('toolbar action label', {
286
+ key: t(`range key ${styleKey} label`),
287
+ value: t(`range value ${value} label`),
288
+ })}
261
289
  </ToolbarItem>
262
290
  ))}
263
291
  </>
@@ -270,29 +298,25 @@ const Styles = () => {
270
298
 
271
299
  const Actions = () => {
272
300
  const { onAction } = useToolbarContext('Actions');
273
- const { cursor, range, model } = useSheetContext();
301
+ const { cursorFallbackRange, cursor, model } = useSheetContext();
274
302
  const { t } = useTranslation(SHEET_PLUGIN);
275
303
 
304
+ // TODO(thure): Can this O(n) call be memoized?
276
305
  const overlapsCommentAnchor = (model.sheet.threads ?? [])
277
306
  .filter(nonNullable)
278
307
  .filter((thread) => thread.status !== 'resolved')
279
308
  .some((thread) => {
280
- if (!cursor) {
309
+ if (!cursorFallbackRange) {
281
310
  return false;
282
311
  }
283
- return addressToIndex(model.sheet, cursor) === thread.anchor;
312
+ return rangeToIndex(model.sheet, cursorFallbackRange) === thread.anchor;
284
313
  });
285
314
 
286
- const hasCursor = !!cursor;
287
- const cursorOnly = hasCursor && !range && !overlapsCommentAnchor;
288
-
289
- const tooltipLabelKey = !hasCursor
315
+ const tooltipLabelKey = !cursor
290
316
  ? 'no cursor label'
291
317
  : overlapsCommentAnchor
292
318
  ? 'selection overlaps existing comment label'
293
- : range
294
- ? 'comment ranges not supported label'
295
- : 'comment label';
319
+ : 'comment label';
296
320
 
297
321
  return (
298
322
  <ToolbarItem
@@ -301,16 +325,16 @@ const Actions = () => {
301
325
  icon='ph--chat-text--regular'
302
326
  data-testid='editor.toolbar.comment'
303
327
  onClick={() => {
304
- if (!cursor) {
328
+ if (!cursorFallbackRange) {
305
329
  return;
306
330
  }
307
331
  return onAction?.({
308
332
  key: 'comment',
309
- value: addressToIndex(model.sheet, cursor),
310
- cellContent: model.getCellText(cursor),
333
+ value: rangeToIndex(model.sheet, cursorFallbackRange),
334
+ cellContent: model.getCellText(cursorFallbackRange.from),
311
335
  });
312
336
  }}
313
- disabled={!cursorOnly || overlapsCommentAnchor}
337
+ disabled={!cursorFallbackRange || overlapsCommentAnchor}
314
338
  >
315
339
  {t(tooltipLabelKey)}
316
340
  </ToolbarItem>
@@ -6,6 +6,7 @@ import { lazy } from 'react';
6
6
 
7
7
  export * from './ComputeGraph';
8
8
  export * from './GridSheet';
9
+ export * from './RangeList';
9
10
  export * from './SheetContext';
10
11
 
11
12
  export const SheetContainer = lazy(() => import('./SheetContainer'));