@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
@@ -2,25 +2,43 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import React, { useCallback, useMemo, useRef, type FocusEvent, type WheelEvent, type KeyboardEvent } from 'react';
5
+ import React, {
6
+ useCallback,
7
+ useMemo,
8
+ useRef,
9
+ type FocusEvent,
10
+ type KeyboardEvent,
11
+ type WheelEvent,
12
+ type MouseEvent,
13
+ useState,
14
+ } from 'react';
6
15
 
16
+ import { useIntentDispatcher } from '@dxos/app-framework';
17
+ import { DropdownMenu, Icon, useTranslation } from '@dxos/react-ui';
7
18
  import { useAttention } from '@dxos/react-ui-attention';
8
19
  import {
9
- type DxGridElement,
10
- Grid,
11
- type GridContentProps,
20
+ closestCell,
21
+ defaultSizeRow,
12
22
  editorKeys,
13
- type EditorKeysProps,
23
+ Grid,
14
24
  GridCellEditor,
15
- closestCell,
25
+ type DxGridElement,
26
+ type EditorKeyHandler,
27
+ type EditorBlurHandler,
28
+ type GridContentProps,
29
+ type DxGridPosition,
16
30
  } from '@dxos/react-ui-grid';
17
31
 
18
32
  import { colLabelCell, dxGridCellIndexToSheetCellAddress, rowLabelCell, useSheetModelDxGridProps } from './util';
19
- import { rangeToA1Notation, type CellRange, DEFAULT_COLUMNS, DEFAULT_ROWS } from '../../defs';
20
- import { rangeExtension, sheetExtension, type CellRangeNotifier } from '../../extensions';
21
- import { useUpdateFocusedCellOnThreadSelection } from '../../hooks';
33
+ import { DEFAULT_COLUMNS, DEFAULT_ROWS, rangeToA1Notation, type CellRange } from '../../defs';
34
+ import { rangeExtension, sheetExtension, type RangeController } from '../../extensions';
35
+ import { useSelectThreadOnCellFocus, useUpdateFocusedCellOnThreadSelection } from '../../integrations';
36
+ import { SHEET_PLUGIN } from '../../meta';
37
+ import { SheetAction } from '../../types';
22
38
  import { useSheetContext } from '../SheetContext';
23
39
 
40
+ const inertPosition: DxGridPosition = { plane: 'grid', col: 0, row: 0 };
41
+
24
42
  const initialCells = {
25
43
  grid: {},
26
44
  frozenColsStart: [...Array(64)].reduce((acc, _, i) => {
@@ -38,21 +56,36 @@ const frozen = {
38
56
  frozenRowsStart: 1,
39
57
  };
40
58
 
41
- const sheetRowDefault = { frozenRowsStart: { size: 32, readonly: true }, grid: { size: 32, resizeable: true } };
59
+ const sheetRowDefault = {
60
+ frozenRowsStart: { size: defaultSizeRow, readonly: true },
61
+ grid: { size: defaultSizeRow, resizeable: true },
62
+ };
42
63
  const sheetColDefault = { frozenColsStart: { size: 48, readonly: true }, grid: { size: 180, resizeable: true } };
43
64
 
44
65
  export const GridSheet = () => {
45
- const { id, model, editing, setEditing, setCursor, setRange, range, cursor } = useSheetContext();
46
- const dxGrid = useRef<DxGridElement | null>(null);
47
- const rangeNotifier = useRef<CellRangeNotifier>();
66
+ const { t } = useTranslation(SHEET_PLUGIN);
67
+ const { id, model, editing, setCursor, setRange, cursor, cursorFallbackRange, activeRefs } = useSheetContext();
68
+ // NOTE(thure): using `useState` instead of `useRef` works with refs provided by `@lit/react` and gives us
69
+ // a reliable dependency for `useEffect` whereas `useLayoutEffect` does not guarantee the element will be defined.
70
+ const [dxGrid, setDxGrid] = useState<DxGridElement | null>(null);
71
+ const [extraplanarFocus, setExtraplanarFocus] = useState<DxGridPosition | null>(null);
72
+ const dispatch = useIntentDispatcher();
73
+ const rangeController = useRef<RangeController>();
48
74
  const { hasAttention } = useAttention(id);
49
75
 
50
76
  const handleFocus = useCallback(
51
77
  (event: FocusEvent) => {
52
78
  if (!editing) {
53
79
  const cell = closestCell(event.target);
54
- if (cell && cell.plane === 'grid') {
55
- setCursor({ col: cell.col, row: cell.row });
80
+ if (cell) {
81
+ if (cell.plane === 'grid') {
82
+ setCursor({ col: cell.col, row: cell.row });
83
+ setExtraplanarFocus(null);
84
+ } else {
85
+ setExtraplanarFocus(cell);
86
+ }
87
+ } else {
88
+ setExtraplanarFocus(null);
56
89
  }
57
90
  }
58
91
  },
@@ -60,21 +93,29 @@ export const GridSheet = () => {
60
93
  );
61
94
 
62
95
  // TODO(burdon): Validate formula before closing: hf.validateFormula();
63
- const handleClose = useCallback<NonNullable<EditorKeysProps['onClose']> | NonNullable<EditorKeysProps['onNav']>>(
64
- (value, { key, shift }) => {
96
+ const handleClose = useCallback<EditorKeyHandler>(
97
+ (_value, event) => {
98
+ if (event) {
99
+ const { key, shift } = event;
100
+ const axis = ['Enter', 'ArrowUp', 'ArrowDown'].includes(key)
101
+ ? 'row'
102
+ : ['Tab', 'ArrowLeft', 'ArrowRight'].includes(key)
103
+ ? 'col'
104
+ : undefined;
105
+ const delta = key.startsWith('Arrow') ? (['ArrowUp', 'ArrowLeft'].includes(key) ? -1 : 1) : shift ? -1 : 1;
106
+ dxGrid?.refocus(axis, delta);
107
+ }
108
+ },
109
+ [model, editing, dxGrid],
110
+ );
111
+
112
+ const handleBlur = useCallback<EditorBlurHandler>(
113
+ (value) => {
65
114
  if (value !== undefined) {
66
115
  model.setValue(dxGridCellIndexToSheetCellAddress(editing!.index), value);
67
116
  }
68
- setEditing(null);
69
- const axis = ['Enter', 'ArrowUp', 'ArrowDown'].includes(key)
70
- ? 'row'
71
- : ['Tab', 'ArrowLeft', 'ArrowRight'].includes(key)
72
- ? 'col'
73
- : undefined;
74
- const delta = key.startsWith('Arrow') ? (['ArrowUp', 'ArrowLeft'].includes(key) ? -1 : 1) : shift ? -1 : 1;
75
- dxGrid.current?.refocus(axis, delta);
76
117
  },
77
- [model, editing, setEditing],
118
+ [model, editing],
78
119
  );
79
120
 
80
121
  const handleAxisResize = useCallback<NonNullable<GridContentProps['onAxisResize']>>(
@@ -100,7 +141,7 @@ export const GridSheet = () => {
100
141
  }
101
142
  if (editing) {
102
143
  // Update range selection in formula.
103
- rangeNotifier.current?.(rangeToA1Notation(range));
144
+ rangeController.current?.setRange(rangeToA1Notation(range));
104
145
  } else {
105
146
  // Setting range while editing causes focus to move to null, avoid doing so.
106
147
  setRange(range.to ? range : undefined);
@@ -108,7 +149,6 @@ export const GridSheet = () => {
108
149
  },
109
150
  [editing],
110
151
  );
111
-
112
152
  const handleWheel = useCallback(
113
153
  (event: WheelEvent) => {
114
154
  if (!hasAttention) {
@@ -118,14 +158,51 @@ export const GridSheet = () => {
118
158
  [hasAttention],
119
159
  );
120
160
 
161
+ const selectEntireAxis = useCallback(
162
+ (pos: DxGridPosition) => {
163
+ switch (pos.plane) {
164
+ case 'frozenRowsStart':
165
+ return dxGrid?.setSelection({
166
+ start: { col: pos.col, row: 0, plane: 'grid' },
167
+ end: { col: pos.col, row: model.sheet.rows.length - 1, plane: 'grid' },
168
+ });
169
+ case 'frozenColsStart':
170
+ return dxGrid?.setSelection({
171
+ start: { row: pos.row, col: 0, plane: 'grid' },
172
+ end: { row: pos.row, col: model.sheet.columns.length - 1, plane: 'grid' },
173
+ });
174
+ }
175
+ },
176
+ [dxGrid, model.sheet],
177
+ );
178
+
179
+ const handleClick = useCallback(
180
+ (event: MouseEvent) => {
181
+ const cell = closestCell(event.target);
182
+ if (cell) {
183
+ selectEntireAxis(cell);
184
+ }
185
+ },
186
+ [selectEntireAxis],
187
+ );
188
+
121
189
  const handleKeyDown = useCallback(
122
190
  (event: KeyboardEvent) => {
123
- const cursorFallbackRange: CellRange | null = range ?? cursor ? { from: cursor!, to: cursor! } : null;
124
191
  switch (event.key) {
125
192
  case 'Backspace':
126
193
  case 'Delete':
127
194
  event.preventDefault();
128
195
  return cursorFallbackRange && model.clear(cursorFallbackRange);
196
+ case 'Enter':
197
+ case 'Space':
198
+ if (dxGrid && extraplanarFocus) {
199
+ switch (extraplanarFocus.plane) {
200
+ case 'frozenRowsStart':
201
+ case 'frozenColsStart':
202
+ event.preventDefault();
203
+ return selectEntireAxis(extraplanarFocus);
204
+ }
205
+ }
129
206
  }
130
207
  if (event.metaKey || event.ctrlKey) {
131
208
  switch (event.key) {
@@ -151,7 +228,48 @@ export const GridSheet = () => {
151
228
  }
152
229
  }
153
230
  },
154
- [range, model, cursor],
231
+ [cursorFallbackRange, model, cursor, extraplanarFocus, selectEntireAxis],
232
+ );
233
+
234
+ const contextMenuAnchorRef = useRef<HTMLButtonElement | null>(null);
235
+ const [contextMenuOpen, setContextMenuOpen] = useState<DxGridPosition | null>(null);
236
+ const contextMenuAxis = contextMenuOpen?.plane.startsWith('frozenRows') ? 'col' : 'row';
237
+
238
+ const handleContextMenu = useCallback((event: MouseEvent) => {
239
+ const cell = closestCell(event.target);
240
+ if (cell && cell.plane.startsWith('frozen')) {
241
+ event.preventDefault();
242
+ contextMenuAnchorRef.current = event.target as HTMLButtonElement;
243
+ setContextMenuOpen(cell);
244
+ }
245
+ }, []);
246
+
247
+ const handleAxisMenuAction = useCallback(
248
+ (operation: 'insert-before' | 'insert-after' | 'drop') => {
249
+ switch (operation) {
250
+ case 'insert-before':
251
+ case 'insert-after':
252
+ return dispatch({
253
+ action: SheetAction.INSERT_AXIS,
254
+ data: {
255
+ model,
256
+ axis: contextMenuAxis,
257
+ index: contextMenuOpen![contextMenuAxis] + (operation === 'insert-before' ? 0 : 1),
258
+ } satisfies SheetAction.InsertAxis,
259
+ });
260
+ break;
261
+ case 'drop':
262
+ return dispatch({
263
+ action: SheetAction.DROP_AXIS,
264
+ data: {
265
+ model,
266
+ axis: contextMenuAxis,
267
+ axisIndex: model.sheet[contextMenuAxis === 'row' ? 'rows' : 'columns'][contextMenuOpen![contextMenuAxis]],
268
+ } satisfies SheetAction.DropAxis,
269
+ });
270
+ }
271
+ },
272
+ [contextMenuAxis, contextMenuOpen, model, dispatch],
155
273
  );
156
274
 
157
275
  const { columns, rows } = useSheetModelDxGridProps(dxGrid, model);
@@ -160,7 +278,15 @@ export const GridSheet = () => {
160
278
  () => [
161
279
  editorKeys({ onClose: handleClose, ...(editing?.initialContent && { onNav: handleClose }) }),
162
280
  sheetExtension({ functions: model.graph.getFunctions() }),
163
- rangeExtension((fn) => (rangeNotifier.current = fn)),
281
+ rangeExtension({
282
+ onInit: (fn) => (rangeController.current = fn),
283
+ onStateChange: (state) => {
284
+ if (dxGrid) {
285
+ // This can’t dispatch a setState in this component, otherwise the cell editor remounts and loses focus.
286
+ dxGrid.mode = typeof state.activeRange === 'undefined' ? 'edit' : 'edit-select';
287
+ }
288
+ },
289
+ }),
164
290
  ],
165
291
  [model, handleClose, editing],
166
292
  );
@@ -173,11 +299,12 @@ export const GridSheet = () => {
173
299
  [model],
174
300
  );
175
301
 
176
- useUpdateFocusedCellOnThreadSelection(model, dxGrid);
302
+ useUpdateFocusedCellOnThreadSelection(dxGrid);
303
+ useSelectThreadOnCellFocus();
177
304
 
178
305
  return (
179
306
  <>
180
- <GridCellEditor getCellContent={getCellContent} extension={extension} />
307
+ <GridCellEditor getCellContent={getCellContent} extension={extension} onBlur={handleBlur} />
181
308
  <Grid.Content
182
309
  initialCells={initialCells}
183
310
  limitColumns={DEFAULT_COLUMNS}
@@ -192,10 +319,43 @@ export const GridSheet = () => {
192
319
  onFocus={handleFocus}
193
320
  onWheelCapture={handleWheel}
194
321
  onKeyDown={handleKeyDown}
195
- overscroll='inline'
196
- className='[--dx-grid-base:var(--surface-bg)]'
197
- ref={dxGrid}
322
+ onContextMenu={handleContextMenu}
323
+ onClick={handleClick}
324
+ overscroll='trap'
325
+ className='[--dx-grid-base:var(--surface-bg)] [&_.dx-grid]:border-bs [&_.dx-grid]:border-separator'
326
+ activeRefs={activeRefs}
327
+ ref={setDxGrid}
198
328
  />
329
+ <DropdownMenu.Root
330
+ modal={false}
331
+ open={!!contextMenuOpen}
332
+ onOpenChange={(nextOpen) => setContextMenuOpen(nextOpen ? inertPosition : null)}
333
+ >
334
+ <DropdownMenu.VirtualTrigger virtualRef={contextMenuAnchorRef} />
335
+ <DropdownMenu.Content side={contextMenuAxis === 'col' ? 'bottom' : 'right'} sideOffset={4} collisionPadding={8}>
336
+ <DropdownMenu.Viewport>
337
+ <DropdownMenu.Item onClick={() => handleAxisMenuAction('insert-before')}>
338
+ <Icon
339
+ size={5}
340
+ icon={contextMenuAxis === 'col' ? 'ph--columns-plus-left--regular' : 'ph--rows-plus-top--regular'}
341
+ />
342
+ <span>{t(`add ${contextMenuAxis} before label`)}</span>
343
+ </DropdownMenu.Item>
344
+ <DropdownMenu.Item onClick={() => handleAxisMenuAction('insert-after')}>
345
+ <Icon
346
+ size={5}
347
+ icon={contextMenuAxis === 'col' ? 'ph--columns-plus-right--regular' : 'ph--rows-plus-bottom--regular'}
348
+ />
349
+ <span>{t(`add ${contextMenuAxis} after label`)}</span>
350
+ </DropdownMenu.Item>
351
+ <DropdownMenu.Item onClick={() => handleAxisMenuAction('drop')}>
352
+ <Icon size={5} icon='ph--backspace--regular' />
353
+ <span>{t(`delete ${contextMenuAxis} label`)}</span>
354
+ </DropdownMenu.Item>
355
+ </DropdownMenu.Viewport>
356
+ <DropdownMenu.Arrow />
357
+ </DropdownMenu.Content>
358
+ </DropdownMenu.Root>
199
359
  </>
200
360
  );
201
361
  };
@@ -14,9 +14,9 @@ import { CellEditor, type CellEditorProps } from '@dxos/react-ui-grid';
14
14
  import { withTheme } from '@dxos/storybook-utils';
15
15
 
16
16
  import { HyperFormula } from '#hyperformula';
17
+ import { defaultFunctions } from '../../compute-graph';
17
18
  import { createSheet } from '../../defs';
18
19
  import { sheetExtension } from '../../extensions';
19
- import { defaultFunctions } from '../../graph';
20
20
  import { SheetType } from '../../types';
21
21
 
22
22
  type StoryProps = CellEditorProps;
@@ -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 = {};
@@ -84,11 +96,15 @@ const gridCellGetter = (model: SheetModel) => {
84
96
 
85
97
  export const rowLabelCell = (row: number) => ({
86
98
  value: rowToA1Notation(row),
87
- className: 'text-end !pie-1',
99
+ className: 'text-end !pie-1 text-subdued',
88
100
  resizeHandle: 'row',
89
101
  });
90
102
 
91
- export const colLabelCell = (col: number) => ({ value: colToA1Notation(col), resizeHandle: 'col' });
103
+ export const colLabelCell = (col: number) => ({
104
+ value: colToA1Notation(col),
105
+ className: 'text-subdued',
106
+ resizeHandle: 'col',
107
+ });
92
108
 
93
109
  const cellGetter = (model: SheetModel) => {
94
110
  const getGridCells = gridCellGetter(model);
@@ -115,23 +131,27 @@ const cellGetter = (model: SheetModel) => {
115
131
  };
116
132
 
117
133
  export const useSheetModelDxGridProps = (
118
- dxGridRef: MutableRefObject<DxGridElement | null>,
134
+ dxGrid: DxGridElement | null,
119
135
  model: SheetModel,
120
136
  ): Pick<GridContentProps, 'columns' | 'rows'> => {
121
137
  const [columns, setColumns] = useState<DxGridAxisMeta>(createDxGridColumns(model));
122
- const [rows, setRows] = useState<DxGridAxisMeta>(createDxGridColumns(model));
138
+ const [rows, setRows] = useState<DxGridAxisMeta>(createDxGridRows(model));
123
139
 
124
- useLayoutEffect(() => {
140
+ useEffect(() => {
125
141
  const cellsAccessor = createDocAccessor(model.sheet, ['cells']);
126
- if (dxGridRef.current) {
127
- dxGridRef.current.getCells = cellGetter(model);
142
+ if (dxGrid) {
143
+ dxGrid.getCells = cellGetter(model);
128
144
  }
129
145
  const handleCellsUpdate = () => {
130
- dxGridRef.current?.requestUpdate('initialCells');
146
+ dxGrid?.requestUpdate('initialCells');
131
147
  };
132
148
  cellsAccessor.handle.addListener('change', handleCellsUpdate);
133
- return () => cellsAccessor.handle.removeListener('change', handleCellsUpdate);
134
- }, [model]);
149
+ const unsubscribe = model.graph.update.on(handleCellsUpdate);
150
+ return () => {
151
+ cellsAccessor.handle.removeListener('change', handleCellsUpdate);
152
+ unsubscribe();
153
+ };
154
+ }, [model, dxGrid]);
135
155
 
136
156
  useEffect(() => {
137
157
  const columnMetaAccessor = createDocAccessor(model.sheet, ['columnMeta']);
@@ -148,7 +168,7 @@ export const useSheetModelDxGridProps = (
148
168
  columnMetaAccessor.handle.removeListener('change', handleColumnMetaUpdate);
149
169
  rowMetaAccessor.handle.removeListener('change', handleRowMetaUpdate);
150
170
  };
151
- }, [model]);
171
+ }, [model, dxGrid]);
152
172
 
153
173
  return { columns, rows };
154
174
  };
@@ -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,24 +4,31 @@
4
4
 
5
5
  import React from 'react';
6
6
 
7
+ import { type Space } from '@dxos/react-client/echo';
8
+ import { StackItem } from '@dxos/react-ui-stack';
9
+
10
+ import { type SheetType } from '../../types';
11
+ import { useComputeGraph } from '../ComputeGraph';
7
12
  import { FunctionEditor } from '../FunctionEditor';
8
13
  import { GridSheet } from '../GridSheet';
9
- import { SheetProvider, type SheetProviderProps } from '../SheetContext';
14
+ import { SheetProvider } from '../SheetContext';
10
15
  import { Toolbar } from '../Toolbar';
11
16
 
12
- export const SheetContainer = ({ graph, sheet, role }: SheetProviderProps & { role?: string }) => {
13
- return (
17
+ export const SheetContainer = ({ space, sheet, role }: { space: Space; sheet: SheetType; role?: string }) => {
18
+ const graph = useComputeGraph(space);
19
+
20
+ return graph ? (
14
21
  <SheetProvider sheet={sheet} graph={graph}>
15
- <Toolbar.Root role={role}>
16
- <Toolbar.Styles />
17
- <Toolbar.Alignment />
18
- <Toolbar.Separator />
19
- <Toolbar.Actions />
20
- </Toolbar.Root>
21
- <div role='none' className='border-bs border-separator grid cols-1 rows-[1fr_min-content] min-bs-0'>
22
+ <StackItem.Content toolbar statusbar classNames='border-bs border-separator'>
23
+ <Toolbar.Root role={role}>
24
+ <Toolbar.Styles />
25
+ <Toolbar.Alignment />
26
+ <Toolbar.Separator />
27
+ <Toolbar.Actions />
28
+ </Toolbar.Root>
22
29
  <GridSheet />
23
30
  <FunctionEditor />
24
- </div>
31
+ </StackItem.Content>
25
32
  </SheetProvider>
26
- );
33
+ ) : null;
27
34
  };