@dxos/plugin-sheet 0.6.14-main.7bd9c89 → 0.6.14-staging.9e90729

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 (208) hide show
  1. package/dist/lib/browser/{SheetContainer-AKWROARP.mjs → SheetContainer-JBB7W52Y.mjs} +42 -52
  2. package/dist/lib/browser/SheetContainer-JBB7W52Y.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-GSV5QNLD.mjs → chunk-2ZK3YMZG.mjs} +248 -33
  4. package/dist/lib/browser/{chunk-GSV5QNLD.mjs.map → chunk-2ZK3YMZG.mjs.map} +4 -4
  5. package/dist/lib/browser/{chunk-BWN5DZWZ.mjs → chunk-3QWIMZZJ.mjs} +2 -11
  6. package/dist/lib/browser/chunk-3QWIMZZJ.mjs.map +7 -0
  7. package/dist/lib/browser/{chunk-FGMFOW6U.mjs → chunk-XXDHBYZQ.mjs} +553 -668
  8. package/dist/lib/browser/chunk-XXDHBYZQ.mjs.map +7 -0
  9. package/dist/lib/browser/{graph-M4IQ76QX.mjs → compute-graph-ZQKB6QVP.mjs} +4 -2
  10. package/dist/lib/browser/index.mjs +57 -14
  11. package/dist/lib/browser/index.mjs.map +4 -4
  12. package/dist/lib/browser/meta.json +1 -1
  13. package/dist/lib/browser/types.mjs +1 -1
  14. package/dist/lib/node/{SheetContainer-N5IQGEFL.cjs → SheetContainer-L37HUFCF.cjs} +44 -58
  15. package/dist/lib/node/SheetContainer-L37HUFCF.cjs.map +7 -0
  16. package/dist/lib/node/{chunk-53BMSUIK.cjs → chunk-7AWAC3R3.cjs} +517 -645
  17. package/dist/lib/node/chunk-7AWAC3R3.cjs.map +7 -0
  18. package/dist/lib/node/{chunk-5XPK2V4A.cjs → chunk-QNFMTK3X.cjs} +252 -28
  19. package/dist/lib/node/{chunk-5XPK2V4A.cjs.map → chunk-QNFMTK3X.cjs.map} +4 -4
  20. package/dist/lib/node/{chunk-NZARD7UP.cjs → chunk-V7E5JZBD.cjs} +5 -14
  21. package/dist/lib/node/chunk-V7E5JZBD.cjs.map +7 -0
  22. package/dist/lib/node/{graph-Q3N2X26H.cjs → compute-graph-RJB6OVW6.cjs} +21 -19
  23. package/dist/lib/node/compute-graph-RJB6OVW6.cjs.map +7 -0
  24. package/dist/lib/node/index.cjs +66 -27
  25. package/dist/lib/node/index.cjs.map +4 -4
  26. package/dist/lib/node/meta.json +1 -1
  27. package/dist/lib/node/types.cjs +6 -6
  28. package/dist/lib/node/types.cjs.map +1 -1
  29. package/dist/lib/node-esm/{SheetContainer-46PBMF2E.mjs → SheetContainer-CLKFEM35.mjs} +42 -52
  30. package/dist/lib/node-esm/SheetContainer-CLKFEM35.mjs.map +7 -0
  31. package/dist/lib/node-esm/{chunk-T3PRH7QS.mjs → chunk-3JYJEOCF.mjs} +553 -668
  32. package/dist/lib/node-esm/chunk-3JYJEOCF.mjs.map +7 -0
  33. package/dist/lib/node-esm/{chunk-5WPZCXNS.mjs → chunk-JBTORSST.mjs} +247 -33
  34. package/dist/lib/node-esm/{chunk-5WPZCXNS.mjs.map → chunk-JBTORSST.mjs.map} +4 -4
  35. package/dist/lib/node-esm/{chunk-WFDTY3IC.mjs → chunk-SPQFLYC2.mjs} +2 -11
  36. package/dist/lib/node-esm/chunk-SPQFLYC2.mjs.map +7 -0
  37. package/dist/lib/node-esm/{graph-SMPUMOV2.mjs → compute-graph-VKRG526N.mjs} +4 -2
  38. package/dist/lib/node-esm/index.mjs +57 -14
  39. package/dist/lib/node-esm/index.mjs.map +4 -4
  40. package/dist/lib/node-esm/meta.json +1 -1
  41. package/dist/lib/node-esm/types.mjs +1 -1
  42. package/dist/types/src/SheetPlugin.d.ts.map +1 -1
  43. package/dist/types/src/components/ComputeGraph/ComputeGraphContextProvider.d.ts +3 -1
  44. package/dist/types/src/components/ComputeGraph/ComputeGraphContextProvider.d.ts.map +1 -1
  45. package/dist/types/src/components/GridSheet/GridSheet.d.ts.map +1 -1
  46. package/dist/types/src/components/GridSheet/GridSheet.stories.d.ts.map +1 -1
  47. package/dist/types/src/components/GridSheet/util.d.ts.map +1 -1
  48. package/dist/types/src/components/RangeList/RangeList.d.ts +9 -0
  49. package/dist/types/src/components/RangeList/RangeList.d.ts.map +1 -0
  50. package/dist/types/src/components/RangeList/index.d.ts +2 -0
  51. package/dist/types/src/components/RangeList/index.d.ts.map +1 -0
  52. package/dist/types/src/components/SheetContext/SheetContext.d.ts +7 -5
  53. package/dist/types/src/components/SheetContext/SheetContext.d.ts.map +1 -1
  54. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  55. package/dist/types/src/components/index.d.ts +1 -0
  56. package/dist/types/src/components/index.d.ts.map +1 -1
  57. package/dist/types/src/compute-graph/compute-graph-registry.d.ts.map +1 -0
  58. package/dist/types/src/{graph → compute-graph}/compute-graph.d.ts +1 -1
  59. package/dist/types/src/compute-graph/compute-graph.d.ts.map +1 -0
  60. package/dist/types/src/compute-graph/compute-graph.stories.d.ts.map +1 -0
  61. package/dist/types/src/compute-graph/compute-graph.test.d.ts.map +1 -0
  62. package/dist/types/src/compute-graph/compute-node.d.ts.map +1 -0
  63. package/dist/types/src/compute-graph/functions/async-function.d.ts.map +1 -0
  64. package/dist/types/src/compute-graph/functions/edge-function.d.ts.map +1 -0
  65. package/dist/types/src/compute-graph/functions/function-defs.d.ts.map +1 -0
  66. package/dist/types/src/compute-graph/functions/index.d.ts.map +1 -0
  67. package/dist/types/src/compute-graph/hyperformula.test.d.ts.map +1 -0
  68. package/dist/types/src/compute-graph/index.d.ts.map +1 -0
  69. package/dist/types/src/compute-graph/testing/index.d.ts.map +1 -0
  70. package/dist/types/src/compute-graph/testing/test-builder.d.ts.map +1 -0
  71. package/dist/types/src/compute-graph/testing/test-plugin.d.ts.map +1 -0
  72. package/dist/types/src/compute-graph/util.d.ts.map +1 -0
  73. package/dist/types/src/defs/index.d.ts +1 -1
  74. package/dist/types/src/defs/index.d.ts.map +1 -1
  75. package/dist/types/src/defs/sheet-range-types.d.ts.map +1 -1
  76. package/dist/types/src/defs/types.d.ts +6 -0
  77. package/dist/types/src/defs/types.d.ts.map +1 -1
  78. package/dist/types/src/defs/util.d.ts +2 -6
  79. package/dist/types/src/defs/util.d.ts.map +1 -1
  80. package/dist/types/src/extensions/compute.d.ts +1 -1
  81. package/dist/types/src/extensions/compute.d.ts.map +1 -1
  82. package/dist/types/src/extensions/editor/extension.d.ts +23 -3
  83. package/dist/types/src/extensions/editor/extension.d.ts.map +1 -1
  84. package/dist/types/src/index.d.ts +1 -1
  85. package/dist/types/src/index.d.ts.map +1 -1
  86. package/dist/types/src/integrations/index.d.ts +2 -0
  87. package/dist/types/src/integrations/index.d.ts.map +1 -0
  88. package/dist/types/src/integrations/thread-ranges.d.ts +8 -0
  89. package/dist/types/src/integrations/thread-ranges.d.ts.map +1 -0
  90. package/dist/types/src/model/index.d.ts +1 -1
  91. package/dist/types/src/model/index.d.ts.map +1 -1
  92. package/dist/types/src/model/sheet-model.d.ts +1 -1
  93. package/dist/types/src/model/sheet-model.d.ts.map +1 -1
  94. package/dist/types/src/{hooks → model}/useSheetModel.d.ts +1 -1
  95. package/dist/types/src/model/useSheetModel.d.ts.map +1 -0
  96. package/dist/types/src/serializer.d.ts +4 -0
  97. package/dist/types/src/serializer.d.ts.map +1 -0
  98. package/dist/types/src/testing/testing.d.ts +1 -1
  99. package/dist/types/src/testing/testing.d.ts.map +1 -1
  100. package/dist/types/src/translations.d.ts +6 -0
  101. package/dist/types/src/translations.d.ts.map +1 -1
  102. package/dist/types/src/types.d.ts +4 -30
  103. package/dist/types/src/types.d.ts.map +1 -1
  104. package/package.json +43 -50
  105. package/src/SheetPlugin.tsx +23 -10
  106. package/src/components/ComputeGraph/ComputeGraphContextProvider.tsx +19 -2
  107. package/src/components/FunctionEditor/FunctionEditor.tsx +5 -5
  108. package/src/components/GridSheet/GridSheet.stories.tsx +3 -1
  109. package/src/components/GridSheet/GridSheet.tsx +114 -19
  110. package/src/components/GridSheet/SheetCellEditor.stories.tsx +1 -1
  111. package/src/components/GridSheet/util.ts +17 -7
  112. package/src/components/RangeList/RangeList.tsx +38 -0
  113. package/src/components/RangeList/index.ts +5 -0
  114. package/src/components/SheetContainer/SheetContainer.stories.tsx +1 -1
  115. package/src/components/SheetContext/SheetContext.tsx +43 -20
  116. package/src/components/Toolbar/Toolbar.tsx +34 -35
  117. package/src/components/index.ts +1 -0
  118. package/src/{graph → compute-graph}/compute-graph.stories.tsx +2 -1
  119. package/src/{graph → compute-graph}/compute-graph.ts +5 -2
  120. package/src/{graph → compute-graph}/compute-node.ts +2 -3
  121. package/src/defs/index.ts +1 -1
  122. package/src/defs/sheet-range-types.ts +1 -0
  123. package/src/defs/types.ts +6 -1
  124. package/src/defs/util.ts +3 -19
  125. package/src/extensions/compute.stories.tsx +2 -2
  126. package/src/extensions/compute.ts +2 -2
  127. package/src/extensions/editor/extension.test.ts +1 -1
  128. package/src/extensions/editor/extension.ts +48 -23
  129. package/src/index.ts +2 -2
  130. package/src/integrations/index.ts +5 -0
  131. package/src/integrations/thread-ranges.ts +101 -0
  132. package/src/model/index.ts +1 -1
  133. package/src/model/sheet-model.test.ts +1 -1
  134. package/src/model/sheet-model.ts +13 -8
  135. package/src/{hooks → model}/useSheetModel.ts +1 -1
  136. package/src/serializer.ts +27 -0
  137. package/src/testing/testing.tsx +1 -1
  138. package/src/translations.ts +6 -0
  139. package/src/types.ts +3 -1
  140. package/dist/lib/browser/SheetContainer-AKWROARP.mjs.map +0 -7
  141. package/dist/lib/browser/chunk-BWN5DZWZ.mjs.map +0 -7
  142. package/dist/lib/browser/chunk-FGMFOW6U.mjs.map +0 -7
  143. package/dist/lib/node/SheetContainer-N5IQGEFL.cjs.map +0 -7
  144. package/dist/lib/node/chunk-53BMSUIK.cjs.map +0 -7
  145. package/dist/lib/node/chunk-NZARD7UP.cjs.map +0 -7
  146. package/dist/lib/node/graph-Q3N2X26H.cjs.map +0 -7
  147. package/dist/lib/node-esm/SheetContainer-46PBMF2E.mjs.map +0 -7
  148. package/dist/lib/node-esm/chunk-T3PRH7QS.mjs.map +0 -7
  149. package/dist/lib/node-esm/chunk-WFDTY3IC.mjs.map +0 -7
  150. package/dist/types/src/graph/compute-graph-registry.d.ts.map +0 -1
  151. package/dist/types/src/graph/compute-graph.d.ts.map +0 -1
  152. package/dist/types/src/graph/compute-graph.stories.d.ts.map +0 -1
  153. package/dist/types/src/graph/compute-graph.test.d.ts.map +0 -1
  154. package/dist/types/src/graph/compute-node.d.ts.map +0 -1
  155. package/dist/types/src/graph/functions/async-function.d.ts.map +0 -1
  156. package/dist/types/src/graph/functions/edge-function.d.ts.map +0 -1
  157. package/dist/types/src/graph/functions/function-defs.d.ts.map +0 -1
  158. package/dist/types/src/graph/functions/index.d.ts.map +0 -1
  159. package/dist/types/src/graph/hyperformula.test.d.ts.map +0 -1
  160. package/dist/types/src/graph/index.d.ts.map +0 -1
  161. package/dist/types/src/graph/testing/index.d.ts.map +0 -1
  162. package/dist/types/src/graph/testing/test-builder.d.ts.map +0 -1
  163. package/dist/types/src/graph/testing/test-plugin.d.ts.map +0 -1
  164. package/dist/types/src/graph/util.d.ts.map +0 -1
  165. package/dist/types/src/hooks/hooks.stories.d.ts +0 -6
  166. package/dist/types/src/hooks/hooks.stories.d.ts.map +0 -1
  167. package/dist/types/src/hooks/index.d.ts +0 -4
  168. package/dist/types/src/hooks/index.d.ts.map +0 -1
  169. package/dist/types/src/hooks/threads.d.ts +0 -8
  170. package/dist/types/src/hooks/threads.d.ts.map +0 -1
  171. package/dist/types/src/hooks/useComputeGraph.d.ts +0 -7
  172. package/dist/types/src/hooks/useComputeGraph.d.ts.map +0 -1
  173. package/dist/types/src/hooks/useSheetModel.d.ts.map +0 -1
  174. package/dist/types/src/model/decorations.d.ts +0 -26
  175. package/dist/types/src/model/decorations.d.ts.map +0 -1
  176. package/src/hooks/hooks.stories.tsx +0 -53
  177. package/src/hooks/index.ts +0 -7
  178. package/src/hooks/threads.ts +0 -147
  179. package/src/hooks/useComputeGraph.ts +0 -28
  180. package/src/model/decorations.ts +0 -66
  181. /package/dist/lib/browser/{graph-M4IQ76QX.mjs.map → compute-graph-ZQKB6QVP.mjs.map} +0 -0
  182. /package/dist/lib/node-esm/{graph-SMPUMOV2.mjs.map → compute-graph-VKRG526N.mjs.map} +0 -0
  183. /package/dist/types/src/{graph → compute-graph}/compute-graph-registry.d.ts +0 -0
  184. /package/dist/types/src/{graph → compute-graph}/compute-graph.stories.d.ts +0 -0
  185. /package/dist/types/src/{graph → compute-graph}/compute-graph.test.d.ts +0 -0
  186. /package/dist/types/src/{graph → compute-graph}/compute-node.d.ts +0 -0
  187. /package/dist/types/src/{graph → compute-graph}/functions/async-function.d.ts +0 -0
  188. /package/dist/types/src/{graph → compute-graph}/functions/edge-function.d.ts +0 -0
  189. /package/dist/types/src/{graph → compute-graph}/functions/function-defs.d.ts +0 -0
  190. /package/dist/types/src/{graph → compute-graph}/functions/index.d.ts +0 -0
  191. /package/dist/types/src/{graph → compute-graph}/hyperformula.test.d.ts +0 -0
  192. /package/dist/types/src/{graph → compute-graph}/index.d.ts +0 -0
  193. /package/dist/types/src/{graph → compute-graph}/testing/index.d.ts +0 -0
  194. /package/dist/types/src/{graph → compute-graph}/testing/test-builder.d.ts +0 -0
  195. /package/dist/types/src/{graph → compute-graph}/testing/test-plugin.d.ts +0 -0
  196. /package/dist/types/src/{graph → compute-graph}/util.d.ts +0 -0
  197. /package/src/{graph → compute-graph}/compute-graph-registry.ts +0 -0
  198. /package/src/{graph → compute-graph}/compute-graph.test.ts +0 -0
  199. /package/src/{graph → compute-graph}/functions/async-function.ts +0 -0
  200. /package/src/{graph → compute-graph}/functions/edge-function.ts +0 -0
  201. /package/src/{graph → compute-graph}/functions/function-defs.ts +0 -0
  202. /package/src/{graph → compute-graph}/functions/index.ts +0 -0
  203. /package/src/{graph → compute-graph}/hyperformula.test.ts +0 -0
  204. /package/src/{graph → compute-graph}/index.ts +0 -0
  205. /package/src/{graph → compute-graph}/testing/index.ts +0 -0
  206. /package/src/{graph → compute-graph}/testing/test-builder.ts +0 -0
  207. /package/src/{graph → compute-graph}/testing/test-plugin.ts +0 -0
  208. /package/src/{graph → compute-graph}/util.ts +0 -0
@@ -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}
@@ -9,26 +9,28 @@ import { useIntentDispatcher } from '@dxos/app-framework';
9
9
  import {
10
10
  Icon,
11
11
  Toolbar as NaturalToolbar,
12
- useTranslation,
13
12
  Tooltip,
14
13
  type ToolbarToggleGroupItemProps as NaturalToolbarToggleGroupItemProps,
15
14
  type ToolbarButtonProps as NaturalToolbarButtonProps,
16
15
  type ToolbarToggleProps as NaturalToolbarToggleProps,
17
16
  type ThemedClassName,
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,
24
23
  type AlignKey,
25
24
  type AlignValue,
26
25
  type CommentKey,
27
26
  type CommentValue,
28
27
  inRange,
28
+ rangeFromIndex,
29
+ rangeToIndex,
29
30
  type StyleKey,
30
31
  type StyleValue,
31
32
  } from '../../defs';
33
+ import { completeCellRangeToThreadCursor } from '../../integrations';
32
34
  import { SHEET_PLUGIN } from '../../meta';
33
35
  import { type SheetType } from '../../types';
34
36
  import { useSheetContext } from '../SheetContext';
@@ -108,22 +110,21 @@ const sectionToolbarLayout =
108
110
  type Range = SheetType['ranges'][number];
109
111
 
110
112
  const ToolbarRoot = ({ children, role, classNames }: ToolbarProps) => {
111
- const { id, model, range, cursor } = useSheetContext();
113
+ const { id, model, cursorFallbackRange, cursor } = useSheetContext();
112
114
  const { hasAttention } = useAttention(id);
113
115
  const dispatch = useIntentDispatcher();
114
116
 
115
- // TODO(Zan): Centralise the toolbar action handler. Current implementation in stories.
117
+ // TODO(Zan): Externalize the toolbar action handler. E.g., Toolbar/keys should both fire events.
116
118
  const handleAction = useCallback(
117
119
  (action: ToolbarAction) => {
118
120
  switch (action.key) {
119
121
  case 'align':
120
- if (cursor) {
122
+ if (cursor && cursorFallbackRange) {
121
123
  const index = model.sheet.ranges?.findIndex(
122
- (range) => range.key === action.key && inRange(range.range, cursor),
124
+ (range) => range.key === action.key && inRange(rangeFromIndex(model.sheet, range.range), cursor),
123
125
  );
124
- const nextRange = range ? { from: range.from, to: range.to ?? range.from } : { from: cursor, to: cursor };
125
126
  const nextRangeEntity = {
126
- range: nextRange as Range['range'],
127
+ range: rangeToIndex(model.sheet, cursorFallbackRange),
127
128
  key: action.key,
128
129
  value: action.value,
129
130
  };
@@ -140,10 +141,9 @@ const ToolbarRoot = ({ children, role, classNames }: ToolbarProps) => {
140
141
  if (index >= 0) {
141
142
  model.sheet.ranges?.splice(index, 1);
142
143
  }
143
- } else if (range || cursor) {
144
- const nextRange = range ? { from: range.from, to: range.to ?? range.from } : { from: cursor, to: cursor };
144
+ } else if (cursorFallbackRange) {
145
145
  model.sheet.ranges?.push({
146
- range: nextRange as Range['range'],
146
+ range: rangeToIndex(model.sheet, cursorFallbackRange),
147
147
  key: action.key,
148
148
  value: action.value,
149
149
  });
@@ -151,18 +151,20 @@ const ToolbarRoot = ({ children, role, classNames }: ToolbarProps) => {
151
151
  break;
152
152
  case 'comment': {
153
153
  // 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
- });
154
+ if (cursorFallbackRange) {
155
+ void dispatch({
156
+ action: 'dxos.org/plugin/thread/action/create',
157
+ data: {
158
+ cursor: completeCellRangeToThreadCursor(cursorFallbackRange),
159
+ name: action.cellContent,
160
+ subject: model.sheet,
161
+ },
162
+ });
163
+ }
162
164
  }
163
165
  }
164
166
  },
165
- [model.sheet, range, cursor, dispatch],
167
+ [model.sheet, cursorFallbackRange, cursor, dispatch],
166
168
  );
167
169
 
168
170
  return (
@@ -207,7 +209,9 @@ const Alignment = () => {
207
209
  const value = useMemo(
208
210
  () =>
209
211
  cursor
210
- ? model.sheet.ranges?.find(({ range, key }) => key === 'alignment' && inRange(range, cursor))?.value
212
+ ? model.sheet.ranges?.find(
213
+ ({ range, key }) => key === 'alignment' && inRange(rangeFromIndex(model.sheet, range), cursor),
214
+ )?.value
211
215
  : undefined,
212
216
  [cursor, model.sheet.ranges],
213
217
  );
@@ -238,7 +242,7 @@ const Styles = () => {
238
242
  () =>
239
243
  cursor
240
244
  ? model.sheet.ranges
241
- ?.filter(({ range, key }) => key === 'style' && inRange(range, cursor))
245
+ ?.filter(({ range, key }) => key === 'style' && inRange(rangeFromIndex(model.sheet, range), cursor))
242
246
  .reduce((acc, { value }) => {
243
247
  acc.add(value);
244
248
  return acc;
@@ -270,29 +274,24 @@ const Styles = () => {
270
274
 
271
275
  const Actions = () => {
272
276
  const { onAction } = useToolbarContext('Actions');
273
- const { cursor, range, model } = useSheetContext();
277
+ const { cursorFallbackRange, cursor, model } = useSheetContext();
274
278
  const { t } = useTranslation(SHEET_PLUGIN);
275
279
 
276
280
  const overlapsCommentAnchor = (model.sheet.threads ?? [])
277
281
  .filter(nonNullable)
278
282
  .filter((thread) => thread.status !== 'resolved')
279
283
  .some((thread) => {
280
- if (!cursor) {
284
+ if (!cursorFallbackRange) {
281
285
  return false;
282
286
  }
283
- return addressToIndex(model.sheet, cursor) === thread.anchor;
287
+ return rangeToIndex(model.sheet, cursorFallbackRange) === thread.anchor;
284
288
  });
285
289
 
286
- const hasCursor = !!cursor;
287
- const cursorOnly = hasCursor && !range && !overlapsCommentAnchor;
288
-
289
- const tooltipLabelKey = !hasCursor
290
+ const tooltipLabelKey = !cursor
290
291
  ? 'no cursor label'
291
292
  : overlapsCommentAnchor
292
293
  ? 'selection overlaps existing comment label'
293
- : range
294
- ? 'comment ranges not supported label'
295
- : 'comment label';
294
+ : 'comment label';
296
295
 
297
296
  return (
298
297
  <ToolbarItem
@@ -301,16 +300,16 @@ const Actions = () => {
301
300
  icon='ph--chat-text--regular'
302
301
  data-testid='editor.toolbar.comment'
303
302
  onClick={() => {
304
- if (!cursor) {
303
+ if (!(cursorFallbackRange && cursor)) {
305
304
  return;
306
305
  }
307
306
  return onAction?.({
308
307
  key: 'comment',
309
- value: addressToIndex(model.sheet, cursor),
308
+ value: rangeToIndex(model.sheet, cursorFallbackRange),
310
309
  cellContent: model.getCellText(cursor),
311
310
  });
312
311
  }}
313
- disabled={!cursorOnly || overlapsCommentAnchor}
312
+ disabled={!cursor || overlapsCommentAnchor}
314
313
  >
315
314
  {t(tooltipLabelKey)}
316
315
  </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'));
@@ -15,8 +15,9 @@ import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
15
15
  import { withTheme } from '@dxos/storybook-utils';
16
16
 
17
17
  import { testFunctionPlugins } from './testing';
18
+ import { useComputeGraph } from '../components';
18
19
  import { createSheet } from '../defs';
19
- import { useComputeGraph, useSheetModel } from '../hooks';
20
+ import { useSheetModel } from '../model';
20
21
  import { withComputeGraphDecorator } from '../testing';
21
22
  import { SheetType } from '../types';
22
23
 
@@ -24,6 +24,8 @@ import {
24
24
  EDGE_FUNCTION_NAME,
25
25
  } from './functions';
26
26
 
27
+ // TODO(burdon): Factor out compute-graph.
28
+
27
29
  // TODO(wittjosiah): Factor out.
28
30
  const OBJECT_ID_LENGTH = 60; // 33 (space id) + 1 (separator) + 26 (object id).
29
31
 
@@ -71,6 +73,7 @@ export class ComputeGraph extends Resource {
71
73
  ) {
72
74
  super();
73
75
  this._hf.updateConfig({ context: this.context });
76
+
74
77
  // TODO(burdon): If debounce then aggregate changes.
75
78
  const onValuesUpdate: Listeners['valuesUpdated'] = (changes) => {
76
79
  for (const change of changes) {
@@ -116,7 +119,7 @@ export class ComputeGraph extends Resource {
116
119
  getOrCreateNode(name: string): ComputeNode {
117
120
  invariant(name.length);
118
121
  if (!this._hf.doesSheetExist(name)) {
119
- log.info('created node', { space: this._space?.id, sheet: name });
122
+ log('created node', { space: this._space?.id, sheet: name });
120
123
  this._hf.addSheet(name);
121
124
  }
122
125
 
@@ -130,7 +133,7 @@ export class ComputeGraph extends Resource {
130
133
 
131
134
  /**
132
135
  * Map bound value to custom function invocation.
133
- * E.g., "HELLO(...args)" => "EDGE("HELLO", ...args)".
136
+ * E.g., "HELLO(...args)" => "DX("HELLO", ...args)".
134
137
  */
135
138
  mapFormulaToNative(formula: string): string {
136
139
  return (
@@ -10,7 +10,7 @@ import { Resource } from '@dxos/context';
10
10
 
11
11
  import { DetailedCellError } from '#hyperformula';
12
12
  import { type ComputeGraph } from './compute-graph';
13
- import { type CellAddress } from '../defs';
13
+ import { type CellAddress, isFormula } from '../defs';
14
14
  import { type CellScalarValue } from '../types';
15
15
 
16
16
  export type ComputeNodeEvent = {
@@ -50,8 +50,7 @@ export class ComputeNode extends Resource {
50
50
  }
51
51
 
52
52
  setValue(cell: CellAddress, value: CellScalarValue) {
53
- const mappedValue =
54
- typeof value === 'string' && value.charAt(0) === '=' ? this._graph.mapFormulaToNative(value) : value;
53
+ const mappedValue = isFormula(value) ? this._graph.mapFormulaToNative(value) : value;
55
54
  this._graph.hf.setCellContents({ sheet: this.sheetId, row: cell.row, col: cell.col }, [[mappedValue]]);
56
55
  }
57
56
 
package/src/defs/index.ts CHANGED
@@ -2,6 +2,6 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ export * from './sheet-range-types';
5
6
  export * from './types';
6
7
  export * from './util';
7
- export * from './sheet-range-types';
@@ -18,6 +18,7 @@ export const styleKey = 'style';
18
18
  export type StyleKey = typeof styleKey;
19
19
  export type StyleValue = 'highlight' | 'unset';
20
20
 
21
+ // TODO(burdon): Reconcile with plugin-table.
21
22
  export const cellClassNameForRange = ({ key, value }: SheetType['ranges'][number]): ClassNameValue => {
22
23
  switch (key) {
23
24
  case 'align':
package/src/defs/types.ts CHANGED
@@ -13,11 +13,16 @@ export const MAX_COLUMNS = 26 * 2;
13
13
  export type CellAddress = { col: number; row: number };
14
14
 
15
15
  export type CellRange = { from: CellAddress; to?: CellAddress };
16
+ export type CompleteCellRange = { from: CellAddress; to: CellAddress };
16
17
 
17
18
  export type CellIndex = string;
18
19
 
19
20
  export type CellContentValue = number | string | boolean | null;
20
21
 
22
+ export const RANGE_NOTATION = /^[A-Z]+[0-9]+(:[A-Z]+[0-9]+)?$/;
23
+
24
+ export const isFormula = (value: any): value is string => typeof value === 'string' && value.charAt(0) === '=';
25
+
21
26
  export const posEquals = (a: CellAddress | undefined, b: CellAddress | undefined) => {
22
27
  return a?.col === b?.col && a?.row === b?.row;
23
28
  };
@@ -34,7 +39,7 @@ export const addressToA1Notation = ({ col, row }: CellAddress): string => {
34
39
  return `${columnLetter(col)}${row + 1}`;
35
40
  };
36
41
 
37
- // TODO(burdon): See simpleCellAddressFromString
42
+ // TODO(burdon): See (HF) simpleCellAddressFromString.
38
43
  export const addressFromA1Notation = (ref: string): CellAddress => {
39
44
  const match = ref.match(/([A-Z]+)(\d+)/);
40
45
  invariant(match, `Invalid notation: ${ref}`);
package/src/defs/util.ts CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  addressFromA1Notation,
10
10
  type CellAddress,
11
11
  type CellRange,
12
+ type CompleteCellRange,
12
13
  DEFAULT_COLUMNS,
13
14
  DEFAULT_ROWS,
14
15
  MAX_COLUMNS,
@@ -69,6 +70,7 @@ export const createSheet = ({ name, cells, ...size }: CreateSheetOptions = {}):
69
70
  rowMeta: {},
70
71
  columnMeta: {},
71
72
  ranges: [],
73
+ threads: [],
72
74
  });
73
75
 
74
76
  initialize(sheet, size);
@@ -111,29 +113,11 @@ export const rangeToIndex = (sheet: SheetType, range: CellRange): string => {
111
113
  /**
112
114
  * E.g., "CA2@CB3:CC4@CD5" => "A1:B2".
113
115
  */
114
- export const rangeFromIndex = (sheet: SheetType, idx: string): CellRange => {
116
+ export const rangeFromIndex = (sheet: SheetType, idx: string): CompleteCellRange => {
115
117
  const [from, to] = idx.split(':').map((index) => addressFromIndex(sheet, index));
116
118
  return { from, to };
117
119
  };
118
120
 
119
- /**
120
- * Find closest cell to cursor.
121
- */
122
- export const closest = (cursor: CellAddress, cells: CellAddress[]): CellAddress | undefined => {
123
- let closestCell: CellAddress | undefined;
124
- let closestDistance = Number.MAX_SAFE_INTEGER;
125
-
126
- for (const cell of cells) {
127
- const distance = Math.abs(cell.row - cursor.row) + Math.abs(cell.col - cursor.col);
128
- if (distance < closestDistance) {
129
- closestCell = cell;
130
- closestDistance = distance;
131
- }
132
- }
133
-
134
- return closestCell;
135
- };
136
-
137
121
  /**
138
122
  * Compares the positions of two cell indexes in a sheet.
139
123
  * Sorts primarily by row, then by column if rows are equal.
@@ -23,8 +23,8 @@ import { withTheme, withLayout } from '@dxos/storybook-utils';
23
23
  import { nonNullable } from '@dxos/util';
24
24
 
25
25
  import { compute, computeGraphFacet } from './compute';
26
- import { GridSheet, SheetProvider } from '../components';
27
- import { useComputeGraph, useSheetModel } from '../hooks';
26
+ import { GridSheet, SheetProvider, useComputeGraph } from '../components';
27
+ import { useSheetModel } from '../model';
28
28
  import { useTestSheet, withComputeGraphDecorator } from '../testing';
29
29
  import { SheetType } from '../types';
30
30
 
@@ -16,10 +16,10 @@ import { Decoration, EditorView, ViewPlugin, WidgetType } from '@codemirror/view
16
16
 
17
17
  import { type UnsubscribeCallback, debounce } from '@dxos/async';
18
18
  import { invariant } from '@dxos/invariant';
19
- import { documentId, singleValueFacet } from '@dxos/react-ui-editor/state';
19
+ import { documentId, singleValueFacet } from '@dxos/react-ui-editor';
20
20
 
21
+ import { type ComputeGraph, type ComputeNode, createSheetName } from '../compute-graph';
21
22
  import { type CellAddress } from '../defs';
22
- import { type ComputeGraph, type ComputeNode, createSheetName } from '../graph';
23
23
  import { type CellScalarValue } from '../types';
24
24
 
25
25
  const LANGUAGE_TAG = 'dx';
@@ -9,7 +9,7 @@ import { spreadsheet } from 'codemirror-lang-spreadsheet';
9
9
  import { describe, expect, test } from 'vitest';
10
10
 
11
11
  import { sheetExtension } from './extension';
12
- import { defaultFunctions } from '../../graph';
12
+ import { defaultFunctions } from '../../compute-graph';
13
13
 
14
14
  describe('formula parser', () => {
15
15
  const {
@@ -18,10 +18,11 @@ import { type SyntaxNode } from '@lezer/common';
18
18
  import { tags } from '@lezer/highlight';
19
19
  import { spreadsheet } from 'codemirror-lang-spreadsheet';
20
20
 
21
- import { singleValueFacet } from '@dxos/react-ui-editor/state';
21
+ import { singleValueFacet } from '@dxos/react-ui-editor';
22
22
  import { mx } from '@dxos/react-ui-theme';
23
23
 
24
- import { type FunctionDefinition } from '../../graph';
24
+ import { type FunctionDefinition } from '../../compute-graph';
25
+ import { RANGE_NOTATION } from '../../defs';
25
26
 
26
27
  /**
27
28
  * https://codemirror.net/examples/styling
@@ -195,34 +196,53 @@ export const sheetExtension = ({ functions = [] }: SheetExtensionOptions): Exten
195
196
  ];
196
197
  };
197
198
 
198
- export type CellRangeNotifier = (range: string) => void;
199
-
200
- type Range = { from: number; to: number };
199
+ export type SelectionRange = { from: number; to: number };
200
+
201
+ export interface RangeController {
202
+ setRange(range: string): void;
203
+ }
204
+
205
+ export type RangeExtensionOptions = {
206
+ /**
207
+ * Provides controller callback when extension is initialized.
208
+ */
209
+ onInit?: (controller: RangeController) => void;
210
+ /**
211
+ * Called when the active range changes.
212
+ * @param state The current state.
213
+ * @param state.activeRange undefined if no range is active, otherwise a possibly partially defined range.
214
+ */
215
+ onStateChange?: (state: { activeRange: string | undefined }) => void;
216
+ };
201
217
 
202
218
  /**
203
219
  * Tracks the currently active cell within a formula and provides a callback to modify it.
204
220
  */
205
- export const rangeExtension = (onInit: (notifier: CellRangeNotifier) => void): Extension => {
221
+ export const rangeExtension = ({ onInit, onStateChange }: RangeExtensionOptions): Extension => {
206
222
  let view: EditorView;
207
- let activeRange: Range | undefined;
208
- const provider: CellRangeNotifier = (range: string) => {
209
- if (activeRange) {
210
- view.dispatch(
211
- view.state.update({
212
- changes: { ...activeRange, insert: range.toString() },
213
- selection: { anchor: activeRange.from + range.length },
214
- }),
215
- );
216
- }
223
+ let activeRange: SelectionRange | undefined;
217
224
 
218
- view.focus();
225
+ // Called externally to provide current range.
226
+ const notifier: RangeController = {
227
+ setRange: (range: string) => {
228
+ if (activeRange) {
229
+ view.dispatch(
230
+ view.state.update({
231
+ changes: { ...activeRange, insert: range.toString() },
232
+ selection: { anchor: activeRange.from + range.length },
233
+ }),
234
+ );
235
+ }
236
+
237
+ view.focus();
238
+ },
219
239
  };
220
240
 
221
241
  return ViewPlugin.fromClass(
222
242
  class {
223
243
  constructor(_view: EditorView) {
224
244
  view = _view;
225
- onInit(provider);
245
+ onInit?.(notifier);
226
246
  }
227
247
 
228
248
  update(view: ViewUpdate) {
@@ -235,17 +255,15 @@ export const rangeExtension = (onInit: (notifier: CellRangeNotifier) => void): E
235
255
  visitTree(topNode, ({ type, from, to }) => {
236
256
  if (from <= anchor && to >= anchor) {
237
257
  switch (type.name) {
238
- case 'Function': {
258
+ case 'Function':
239
259
  // Mark but keep looking.
240
260
  activeRange = { from: to, to };
241
261
  break;
242
- }
243
262
 
244
- case 'CloseParen': {
263
+ case 'CloseParen':
245
264
  // Mark but keep looking.
246
265
  activeRange = { from, to: from };
247
266
  break;
248
- }
249
267
 
250
268
  case 'RangeToken':
251
269
  case 'CellToken':
@@ -259,8 +277,15 @@ export const rangeExtension = (onInit: (notifier: CellRangeNotifier) => void): E
259
277
 
260
278
  // Allow start of formula.
261
279
  if (!activeRange && view.state.doc.toString()[0] === '=') {
262
- activeRange = { from: 1, to: view.state.doc.toString().length };
280
+ const str = view.state.doc.sliceString(1);
281
+ if (RANGE_NOTATION.test(str)) {
282
+ activeRange = { from: 1, to: str.length + 1 };
283
+ }
263
284
  }
285
+
286
+ onStateChange?.({
287
+ activeRange: activeRange ? view.state.doc.sliceString(activeRange.from, activeRange.to) : undefined,
288
+ });
264
289
  }
265
290
  },
266
291
  );
package/src/index.ts CHANGED
@@ -4,6 +4,6 @@
4
4
 
5
5
  import { SheetPlugin } from './SheetPlugin';
6
6
 
7
- export default SheetPlugin;
8
-
9
7
  export * from './SheetPlugin';
8
+
9
+ export default SheetPlugin;
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './thread-ranges';