@dxos/plugin-sheet 0.6.11-staging.e6894a4 → 0.6.12-main.5cc132e

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 (107) hide show
  1. package/dist/lib/browser/{SheetContainer-4XOKHKKZ.mjs → SheetContainer-Y7ZMFBAP.mjs} +582 -121
  2. package/dist/lib/browser/SheetContainer-Y7ZMFBAP.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-P7SSL3EG.mjs → chunk-GNNVBNCX.mjs} +61 -53
  4. package/dist/lib/browser/chunk-GNNVBNCX.mjs.map +7 -0
  5. package/dist/lib/browser/{chunk-FWGRE3EG.mjs → chunk-PGKZPKUD.mjs} +2 -2
  6. package/dist/lib/browser/chunk-VBF7YENS.mjs +8 -0
  7. package/dist/lib/browser/{chunk-FUAGSXA4.mjs → chunk-WUPTZUTX.mjs} +6 -3
  8. package/dist/lib/browser/chunk-WUPTZUTX.mjs.map +7 -0
  9. package/dist/lib/browser/index.mjs +29 -18
  10. package/dist/lib/browser/index.mjs.map +3 -3
  11. package/dist/lib/browser/meta.json +1 -1
  12. package/dist/lib/browser/testing.mjs +3 -3
  13. package/dist/lib/browser/types.mjs +1 -1
  14. package/dist/lib/node/{SheetContainer-IQT6TR4Z.cjs → SheetContainer-KEOKUKAQ.cjs} +528 -79
  15. package/dist/lib/node/SheetContainer-KEOKUKAQ.cjs.map +7 -0
  16. package/dist/lib/node/{chunk-5EPCDAZC.cjs → chunk-57PB2HPY.cjs} +5 -5
  17. package/dist/lib/node/{chunk-727C6YNP.cjs → chunk-6LWBQAQZ.cjs} +9 -9
  18. package/dist/lib/node/{chunk-DSYKOI4E.cjs → chunk-VJU3NPUJ.cjs} +8 -5
  19. package/dist/lib/node/chunk-VJU3NPUJ.cjs.map +7 -0
  20. package/dist/lib/node/{chunk-SVAIIXWQ.cjs → chunk-ZRQZFV5T.cjs} +76 -63
  21. package/dist/lib/node/chunk-ZRQZFV5T.cjs.map +7 -0
  22. package/dist/lib/node/index.cjs +43 -33
  23. package/dist/lib/node/index.cjs.map +3 -3
  24. package/dist/lib/node/meta.json +1 -1
  25. package/dist/lib/node/testing.cjs +6 -6
  26. package/dist/lib/node/types.cjs +9 -9
  27. package/dist/lib/node/types.cjs.map +1 -1
  28. package/dist/lib/node-esm/SheetContainer-Y7ZMFBAP.mjs +2231 -0
  29. package/dist/lib/node-esm/SheetContainer-Y7ZMFBAP.mjs.map +7 -0
  30. package/dist/lib/node-esm/chunk-GNNVBNCX.mjs +3243 -0
  31. package/dist/lib/node-esm/chunk-GNNVBNCX.mjs.map +7 -0
  32. package/dist/lib/node-esm/chunk-JRL5LGCE.mjs +18 -0
  33. package/dist/lib/node-esm/chunk-JRL5LGCE.mjs.map +7 -0
  34. package/dist/lib/node-esm/chunk-PGKZPKUD.mjs +175 -0
  35. package/dist/lib/node-esm/chunk-PGKZPKUD.mjs.map +7 -0
  36. package/dist/lib/node-esm/chunk-VBF7YENS.mjs +8 -0
  37. package/dist/lib/node-esm/chunk-VBF7YENS.mjs.map +7 -0
  38. package/dist/lib/node-esm/chunk-WUPTZUTX.mjs +85 -0
  39. package/dist/lib/node-esm/chunk-WUPTZUTX.mjs.map +7 -0
  40. package/dist/lib/node-esm/index.mjs +257 -0
  41. package/dist/lib/node-esm/index.mjs.map +7 -0
  42. package/dist/lib/node-esm/meta.json +1 -0
  43. package/dist/lib/node-esm/meta.mjs +9 -0
  44. package/dist/lib/node-esm/meta.mjs.map +7 -0
  45. package/dist/lib/node-esm/testing.mjs +92 -0
  46. package/dist/lib/node-esm/testing.mjs.map +7 -0
  47. package/dist/lib/node-esm/types.mjs +22 -0
  48. package/dist/lib/node-esm/types.mjs.map +7 -0
  49. package/dist/types/src/SheetPlugin.d.ts.map +1 -1
  50. package/dist/types/src/components/Sheet/Sheet.d.ts.map +1 -1
  51. package/dist/types/src/components/Sheet/Sheet.stories.d.ts.map +1 -1
  52. package/dist/types/src/components/Sheet/decorations.d.ts +24 -0
  53. package/dist/types/src/components/Sheet/decorations.d.ts.map +1 -0
  54. package/dist/types/src/components/Sheet/formatting.d.ts.map +1 -1
  55. package/dist/types/src/components/Sheet/sheet-context.d.ts +2 -0
  56. package/dist/types/src/components/Sheet/sheet-context.d.ts.map +1 -1
  57. package/dist/types/src/components/Sheet/threads.d.ts +2 -0
  58. package/dist/types/src/components/Sheet/threads.d.ts.map +1 -0
  59. package/dist/types/src/components/SheetContainer.d.ts +2 -3
  60. package/dist/types/src/components/SheetContainer.d.ts.map +1 -1
  61. package/dist/types/src/components/Toolbar/Toolbar.d.ts +19 -3
  62. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  63. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +17 -12
  64. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
  65. package/dist/types/src/components/index.d.ts +1 -2
  66. package/dist/types/src/components/index.d.ts.map +1 -1
  67. package/dist/types/src/model/index.d.ts +1 -0
  68. package/dist/types/src/model/index.d.ts.map +1 -1
  69. package/dist/types/src/model/model.d.ts +0 -16
  70. package/dist/types/src/model/model.d.ts.map +1 -1
  71. package/dist/types/src/model/util.d.ts +24 -0
  72. package/dist/types/src/model/util.d.ts.map +1 -1
  73. package/dist/types/src/translations.d.ts +17 -12
  74. package/dist/types/src/translations.d.ts.map +1 -1
  75. package/dist/types/src/types.d.ts +72 -2
  76. package/dist/types/src/types.d.ts.map +1 -1
  77. package/package.json +36 -32
  78. package/src/SheetPlugin.tsx +19 -20
  79. package/src/components/CellEditor/extension.test.ts +1 -2
  80. package/src/components/ComputeGraph/graph.browser.test.ts +1 -2
  81. package/src/components/Sheet/Sheet.stories.tsx +11 -9
  82. package/src/components/Sheet/Sheet.tsx +57 -29
  83. package/src/components/Sheet/decorations.ts +62 -0
  84. package/src/components/Sheet/formatting.ts +3 -3
  85. package/src/components/Sheet/sheet-context.tsx +9 -1
  86. package/src/components/Sheet/threads.tsx +201 -0
  87. package/src/components/SheetContainer.tsx +72 -20
  88. package/src/components/Toolbar/Toolbar.stories.tsx +5 -10
  89. package/src/components/Toolbar/Toolbar.tsx +54 -12
  90. package/src/model/index.ts +1 -0
  91. package/src/model/model.browser.test.ts +1 -2
  92. package/src/model/model.ts +11 -46
  93. package/src/model/types.test.ts +1 -2
  94. package/src/model/util.ts +67 -0
  95. package/src/translations.ts +6 -1
  96. package/src/types.ts +26 -3
  97. package/dist/lib/browser/SheetContainer-4XOKHKKZ.mjs.map +0 -7
  98. package/dist/lib/browser/chunk-FUAGSXA4.mjs.map +0 -7
  99. package/dist/lib/browser/chunk-P7SSL3EG.mjs.map +0 -7
  100. package/dist/lib/browser/chunk-YPU3R7FA.mjs +0 -8
  101. package/dist/lib/node/SheetContainer-IQT6TR4Z.cjs.map +0 -7
  102. package/dist/lib/node/chunk-DSYKOI4E.cjs.map +0 -7
  103. package/dist/lib/node/chunk-SVAIIXWQ.cjs.map +0 -7
  104. /package/dist/lib/browser/{chunk-FWGRE3EG.mjs.map → chunk-PGKZPKUD.mjs.map} +0 -0
  105. /package/dist/lib/browser/{chunk-YPU3R7FA.mjs.map → chunk-VBF7YENS.mjs.map} +0 -0
  106. /package/dist/lib/node/{chunk-5EPCDAZC.cjs.map → chunk-57PB2HPY.cjs.map} +0 -0
  107. /package/dist/lib/node/{chunk-727C6YNP.cjs.map → chunk-6LWBQAQZ.cjs.map} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/plugin-sheet",
3
- "version": "0.6.11-staging.e6894a4",
3
+ "version": "0.6.12-main.5cc132e",
4
4
  "description": "Braneframe sketch plugin",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -10,14 +10,16 @@
10
10
  ".": {
11
11
  "browser": "./dist/lib/browser/index.mjs",
12
12
  "node": {
13
- "default": "./dist/lib/node/index.cjs"
13
+ "require": "./dist/lib/node/index.cjs",
14
+ "default": "./dist/lib/node-esm/index.mjs"
14
15
  },
15
16
  "types": "./dist/types/src/index.d.ts"
16
17
  },
17
18
  "./meta": {
18
19
  "browser": "./dist/lib/browser/meta.mjs",
19
20
  "node": {
20
- "default": "./dist/lib/node/meta.cjs"
21
+ "require": "./dist/lib/node/meta.cjs",
22
+ "default": "./dist/lib/node-esm/meta.mjs"
21
23
  },
22
24
  "types": "./dist/types/src/meta.d.ts"
23
25
  },
@@ -31,7 +33,8 @@
31
33
  "./types": {
32
34
  "browser": "./dist/lib/browser/types.mjs",
33
35
  "node": {
34
- "default": "./dist/lib/node/types.cjs"
36
+ "require": "./dist/lib/node/types.cjs",
37
+ "default": "./dist/lib/node-esm/types.mjs"
35
38
  },
36
39
  "types": "./dist/types/src/types.d.ts"
37
40
  }
@@ -76,25 +79,26 @@
76
79
  "re-resizable": "^6.9.17",
77
80
  "react-markdown": "^8.0.5",
78
81
  "react-resize-detector": "^11.0.1",
79
- "@dxos/async": "0.6.11-staging.e6894a4",
80
- "@dxos/client": "0.6.11-staging.e6894a4",
81
- "@dxos/context": "0.6.11-staging.e6894a4",
82
- "@dxos/crypto": "0.6.11-staging.e6894a4",
83
- "@dxos/app-framework": "0.6.11-staging.e6894a4",
84
- "@dxos/debug": "0.6.11-staging.e6894a4",
85
- "@dxos/echo-schema": "0.6.11-staging.e6894a4",
86
- "@dxos/keys": "0.6.11-staging.e6894a4",
87
- "@dxos/log": "0.6.11-staging.e6894a4",
88
- "@dxos/invariant": "0.6.11-staging.e6894a4",
89
- "@dxos/plugin-client": "0.6.11-staging.e6894a4",
90
- "@dxos/plugin-graph": "0.6.11-staging.e6894a4",
91
- "@dxos/plugin-script": "0.6.11-staging.e6894a4",
92
- "@dxos/plugin-space": "0.6.11-staging.e6894a4",
93
- "@dxos/react-client": "0.6.11-staging.e6894a4",
94
- "@dxos/react-ui-attention": "0.6.11-staging.e6894a4",
95
- "@dxos/plugin-stack": "0.6.11-staging.e6894a4",
96
- "@dxos/util": "0.6.11-staging.e6894a4",
97
- "@dxos/react-ui-editor": "0.6.11-staging.e6894a4"
82
+ "@dxos/app-framework": "0.6.12-main.5cc132e",
83
+ "@dxos/async": "0.6.12-main.5cc132e",
84
+ "@dxos/client": "0.6.12-main.5cc132e",
85
+ "@dxos/crypto": "0.6.12-main.5cc132e",
86
+ "@dxos/context": "0.6.12-main.5cc132e",
87
+ "@dxos/echo-schema": "0.6.12-main.5cc132e",
88
+ "@dxos/debug": "0.6.12-main.5cc132e",
89
+ "@dxos/invariant": "0.6.12-main.5cc132e",
90
+ "@dxos/keys": "0.6.12-main.5cc132e",
91
+ "@dxos/log": "0.6.12-main.5cc132e",
92
+ "@dxos/plugin-attention": "0.6.12-main.5cc132e",
93
+ "@dxos/plugin-client": "0.6.12-main.5cc132e",
94
+ "@dxos/plugin-graph": "0.6.12-main.5cc132e",
95
+ "@dxos/plugin-script": "0.6.12-main.5cc132e",
96
+ "@dxos/plugin-space": "0.6.12-main.5cc132e",
97
+ "@dxos/react-client": "0.6.12-main.5cc132e",
98
+ "@dxos/react-ui-attention": "0.6.12-main.5cc132e",
99
+ "@dxos/plugin-stack": "0.6.12-main.5cc132e",
100
+ "@dxos/react-ui-editor": "0.6.12-main.5cc132e",
101
+ "@dxos/util": "0.6.12-main.5cc132e"
98
102
  },
99
103
  "devDependencies": {
100
104
  "@lezer/generator": "^1.7.1",
@@ -106,20 +110,20 @@
106
110
  "@types/react-window": "^1.8.8",
107
111
  "react": "~18.2.0",
108
112
  "react-dom": "~18.2.0",
109
- "vite": "^5.3.4",
110
- "@dxos/echo-generator": "0.6.11-staging.e6894a4",
111
- "@dxos/random": "0.6.11-staging.e6894a4",
112
- "@dxos/react-ui": "0.6.11-staging.e6894a4",
113
- "@dxos/storybook-utils": "0.6.11-staging.e6894a4",
114
- "@dxos/react-ui-theme": "0.6.11-staging.e6894a4",
115
- "@dxos/react-ui-types": "0.6.11-staging.e6894a4"
113
+ "vite": "5.4.7",
114
+ "@dxos/echo-generator": "0.6.12-main.5cc132e",
115
+ "@dxos/random": "0.6.12-main.5cc132e",
116
+ "@dxos/react-ui": "0.6.12-main.5cc132e",
117
+ "@dxos/react-ui-types": "0.6.12-main.5cc132e",
118
+ "@dxos/react-ui-theme": "0.6.12-main.5cc132e",
119
+ "@dxos/storybook-utils": "0.6.12-main.5cc132e"
116
120
  },
117
121
  "optionalDependencies": {
118
122
  "@phosphor-icons/react": "^2.1.5",
119
123
  "react": "^18.0.0",
120
124
  "react-dom": "^18.0.0",
121
- "@dxos/react-ui": "0.6.11-staging.e6894a4",
122
- "@dxos/react-ui-theme": "0.6.11-staging.e6894a4"
125
+ "@dxos/react-ui": "0.6.12-main.5cc132e",
126
+ "@dxos/react-ui-theme": "0.6.12-main.5cc132e"
123
127
  },
124
128
  "publishConfig": {
125
129
  "access": "public"
@@ -5,13 +5,7 @@
5
5
  import { type IconProps, GridNine } from '@phosphor-icons/react';
6
6
  import React from 'react';
7
7
 
8
- import {
9
- NavigationAction,
10
- parseIntentPlugin,
11
- resolvePlugin,
12
- type PluginDefinition,
13
- type LayoutCoordinate,
14
- } from '@dxos/app-framework';
8
+ import { NavigationAction, parseIntentPlugin, resolvePlugin, type PluginDefinition } from '@dxos/app-framework';
15
9
  import { create } from '@dxos/echo-schema';
16
10
  import { parseClientPlugin } from '@dxos/plugin-client';
17
11
  import { type ActionGroup, createExtension, isActionGroup } from '@dxos/plugin-graph';
@@ -30,7 +24,7 @@ import {
30
24
  import { EdgeFunctionPlugin, EdgeFunctionPluginTranslations } from './components/ComputeGraph/edge-function';
31
25
  import { ComputeGraphContextProvider } from './components/ComputeGraph/graph-context';
32
26
  import meta, { SHEET_PLUGIN } from './meta';
33
- import { SheetModel } from './model';
27
+ import { compareIndexPositions, SheetModel } from './model';
34
28
  import translations from './translations';
35
29
  import { createSheet, SheetAction, type SheetPluginProvides, SheetType } from './types';
36
30
 
@@ -138,22 +132,27 @@ export const SheetPlugin = (): PluginDefinition<SheetPluginProvides> => {
138
132
  },
139
133
  ],
140
134
  },
135
+ thread: {
136
+ predicate: (data) => data instanceof SheetType,
137
+ createSort: (sheet) => (anchorA, anchorB) =>
138
+ !anchorA || !anchorB ? 0 : compareIndexPositions(sheet, anchorA, anchorB),
139
+ },
141
140
  surface: {
142
141
  component: ({ data, role = 'never' }) => {
143
- if (!['article', 'section'].includes(role) || !isEchoObject(data.object)) {
144
- return null;
142
+ // TODO(burdon): Standardize wrapper (with room for toolbar).
143
+ const space = isEchoObject(data.object) && getSpace(data.object);
144
+ if (space && data.object instanceof SheetType) {
145
+ switch (role) {
146
+ case 'article':
147
+ case 'section': {
148
+ return (
149
+ <SheetContainer sheet={data.object} space={space} role={role} remoteFunctionUrl={remoteFunctionUrl} />
150
+ );
151
+ }
152
+ }
145
153
  }
146
154
 
147
- const space = getSpace(data.object);
148
- return space && data.object instanceof SheetType ? (
149
- <SheetContainer
150
- sheet={data.object}
151
- space={space}
152
- role={role}
153
- coordinate={data.coordinate as LayoutCoordinate}
154
- remoteFunctionUrl={remoteFunctionUrl}
155
- />
156
- ) : null;
155
+ return null;
157
156
  },
158
157
  },
159
158
  intent: {
@@ -6,9 +6,8 @@ import { CompletionContext, type CompletionSource } from '@codemirror/autocomple
6
6
  import { EditorState } from '@codemirror/state';
7
7
  // @ts-ignore
8
8
  import { testTree } from '@lezer/generator/test';
9
- import { expect } from 'chai';
10
9
  import { spreadsheet } from 'codemirror-lang-spreadsheet';
11
- import { describe, test } from 'vitest';
10
+ import { describe, expect, test } from 'vitest';
12
11
 
13
12
  import { sheetExtension } from './extension';
14
13
  import { defaultFunctions } from '../../model/functions';
@@ -2,8 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { expect } from 'chai';
6
- import { describe, test } from 'vitest';
5
+ import { describe, expect, test } from 'vitest';
7
6
 
8
7
  import { Trigger } from '@dxos/async';
9
8
 
@@ -11,13 +11,14 @@ import { Client } from '@dxos/client';
11
11
  import { type EchoReactiveObject } from '@dxos/echo-schema';
12
12
  import { log } from '@dxos/log';
13
13
  import { getSpace, type Space } from '@dxos/react-client/echo';
14
- import { Button, Tooltip } from '@dxos/react-ui';
14
+ import { Button } from '@dxos/react-ui';
15
15
  import { mx } from '@dxos/react-ui-theme';
16
- import { withTheme, withFullscreen } from '@dxos/storybook-utils';
16
+ import { withTheme, withLayout } from '@dxos/storybook-utils';
17
17
 
18
18
  import { Sheet } from './Sheet';
19
19
  import { type SizeMap } from './grid';
20
20
  import { useSheetContext } from './sheet-context';
21
+ import { addressToIndex, rangeToIndex } from '../../model';
21
22
  import { createTestSheet, testSheetName } from '../../testing';
22
23
  import { ValueTypeEnum, SheetType } from '../../types';
23
24
  import { type ComputeGraph, createComputeGraph } from '../ComputeGraph';
@@ -36,7 +37,7 @@ const SheetWithToolbar = ({ debug, space }: { debug?: boolean; space: Space }) =
36
37
  return;
37
38
  }
38
39
 
39
- const idx = range ? model.rangeToIndex(range) : model.addressToIndex(cursor);
40
+ const idx = range ? rangeToIndex(model.sheet, range) : addressToIndex(model.sheet, cursor);
40
41
  model.sheet.formatting[idx] ??= {};
41
42
  const format = model.sheet.formatting[idx];
42
43
 
@@ -75,6 +76,9 @@ const SheetWithToolbar = ({ debug, space }: { debug?: boolean; space: Space }) =
75
76
  format.precision = 2;
76
77
  break;
77
78
  }
79
+ case 'comment': {
80
+ break;
81
+ }
78
82
  }
79
83
  };
80
84
 
@@ -122,7 +126,7 @@ const withGraphDecorator: Decorator = (Story) => {
122
126
  export default {
123
127
  title: 'plugin-sheet/Sheet',
124
128
  component: Sheet,
125
- decorators: [withGraphDecorator, withTheme, withFullscreen({ classNames: 'inset-4' })],
129
+ decorators: [withTheme, withLayout({ fullscreen: true, tooltips: true, classNames: 'inset-4' }), withGraphDecorator],
126
130
  };
127
131
 
128
132
  export const Default = () => {
@@ -134,11 +138,9 @@ export const Default = () => {
134
138
  }
135
139
 
136
140
  return (
137
- <Tooltip.Provider>
138
- <Sheet.Root sheet={sheet} space={space} onInfo={() => setDebug((debug) => !debug)}>
139
- <SheetWithToolbar debug={debug} space={space} />
140
- </Sheet.Root>
141
- </Tooltip.Provider>
141
+ <Sheet.Root sheet={sheet} space={space} onInfo={() => setDebug((debug) => !debug)}>
142
+ <SheetWithToolbar debug={debug} space={space} />
143
+ </Sheet.Root>
142
144
  );
143
145
  };
144
146
 
@@ -40,7 +40,7 @@ import { debounce } from '@dxos/async';
40
40
  import { fullyQualifiedId, createDocAccessor } from '@dxos/client/echo';
41
41
  import { log } from '@dxos/log';
42
42
  import { type ThemedClassName } from '@dxos/react-ui';
43
- import { createAttendableAttributes } from '@dxos/react-ui-attention';
43
+ import { createAttendableAttributes, useHasAttention } from '@dxos/react-ui-attention';
44
44
  import { mx } from '@dxos/react-ui-theme';
45
45
 
46
46
  import {
@@ -60,6 +60,7 @@ import {
60
60
  } from './grid';
61
61
  import { type GridSize, handleArrowNav, handleNav, useRangeSelect } from './nav';
62
62
  import { type SheetContextProps, SheetContextProvider, useSheetContext } from './sheet-context';
63
+ import { useThreads } from './threads';
63
64
  import { getRectUnion, getRelativeClientRect, scrollIntoView } from './util';
64
65
  import {
65
66
  type CellIndex,
@@ -68,6 +69,8 @@ import {
68
69
  columnLetter,
69
70
  posEquals,
70
71
  rangeToA1Notation,
72
+ addressToIndex,
73
+ addressFromIndex,
71
74
  } from '../../model';
72
75
  import {
73
76
  CellEditor,
@@ -112,13 +115,11 @@ import {
112
115
  * - Update formula ranges by selection.
113
116
  */
114
117
 
115
- // TODO(burdon): Factor out fragments.
116
118
  const fragments = {
117
119
  axis: 'bg-axisSurface text-axisText text-xs select-none',
118
120
  axisSelected: 'bg-attention text-baseText',
119
121
  cell: 'bg-gridCell',
120
122
  cellSelected: 'bg-gridCellSelected text-baseText border !border-accentSurface',
121
- border: 'border-gridLine',
122
123
  };
123
124
 
124
125
  //
@@ -143,6 +144,10 @@ const SheetMain = forwardRef<HTMLDivElement, SheetMainProps>(({ classNames, numR
143
144
  // Scrolling.
144
145
  const { rowsRef, columnsRef, contentRef } = useScrollHandlers();
145
146
 
147
+ // Threads.
148
+ // TODO(Zan): Move this to an extension once we have an extension model.
149
+ useThreads();
150
+
146
151
  //
147
152
  // Order of Row/columns.
148
153
  //
@@ -172,21 +177,21 @@ const SheetMain = forwardRef<HTMLDivElement, SheetMainProps>(({ classNames, numR
172
177
  }, [rows, columns]);
173
178
 
174
179
  const handleMoveRows: SheetRowsProps['onMove'] = (from, to, num = 1) => {
175
- const cursorIdx = cursor ? model.addressToIndex(cursor) : undefined;
180
+ const cursorIdx = cursor ? addressToIndex(model.sheet, cursor) : undefined;
176
181
  const [rows] = model.sheet.rows.splice(from, num);
177
182
  model.sheet.rows.splice(to, 0, rows);
178
183
  if (cursorIdx) {
179
- setCursor(model.addressFromIndex(cursorIdx));
184
+ setCursor(addressFromIndex(model.sheet, cursorIdx));
180
185
  }
181
186
  setRows([...model.sheet.rows]);
182
187
  };
183
188
 
184
189
  const handleMoveColumns: SheetColumnsProps['onMove'] = (from, to, num = 1) => {
185
- const cursorIdx = cursor ? model.addressToIndex(cursor) : undefined;
190
+ const cursorIdx = cursor ? addressToIndex(model.sheet, cursor) : undefined;
186
191
  const columns = model.sheet.columns.splice(from, num);
187
192
  model.sheet.columns.splice(to, 0, ...columns);
188
193
  if (cursorIdx) {
189
- setCursor(model.addressFromIndex(cursorIdx));
194
+ setCursor(addressFromIndex(model.sheet, cursorIdx));
190
195
  }
191
196
  setColumns([...model.sheet.columns]);
192
197
  };
@@ -244,7 +249,6 @@ const SheetMain = forwardRef<HTMLDivElement, SheetMainProps>(({ classNames, numR
244
249
  role='none'
245
250
  className={mx(
246
251
  'grid grid-cols-[calc(var(--rail-size)-2px)_1fr] grid-rows-[32px_1fr_32px] bs-full is-full overflow-hidden',
247
- fragments.border,
248
252
  classNames,
249
253
  )}
250
254
  >
@@ -425,7 +429,7 @@ const SheetRows = forwardRef<HTMLDivElement, SheetRowsProps>(
425
429
  <div className='relative flex grow overflow-hidden'>
426
430
  {/* Fixed border. */}
427
431
  <div
428
- className={mx('z-20 absolute inset-0 border-y pointer-events-none', fragments.border)}
432
+ className={mx('z-20 absolute inset-0 border-y border-gridLine pointer-events-none')}
429
433
  style={{ width: axisWidth }}
430
434
  />
431
435
 
@@ -520,8 +524,7 @@ const GridRowCell = ({ idx, index, label, size, resize, selected, onSelect, onRe
520
524
  {...listeners}
521
525
  className={mx(
522
526
  'flex h-full items-center justify-center cursor-pointer',
523
- 'border-t focus-visible:outline-none',
524
- fragments.border,
527
+ 'border-t border-gridLine focus-visible:outline-none',
525
528
  fragments.axis,
526
529
  selected && fragments.axisSelected,
527
530
  isDragging && fragments.axisSelected,
@@ -588,7 +591,7 @@ const SheetColumns = forwardRef<HTMLDivElement, SheetColumnsProps>(
588
591
  <div className='relative flex grow overflow-hidden'>
589
592
  {/* Fixed border. */}
590
593
  <div
591
- className={mx('z-20 absolute inset-0 border-x pointer-events-none', fragments.border)}
594
+ className={mx('z-20 absolute inset-0 border-x border-gridLine pointer-events-none')}
592
595
  style={{ height: axisHeight }}
593
596
  />
594
597
 
@@ -684,8 +687,7 @@ const GridColumnCell = ({ idx, index, label, size, resize, selected, onSelect, o
684
687
  {...listeners}
685
688
  className={mx(
686
689
  'flex h-full items-center justify-center cursor-pointer',
687
- 'border-l focus-visible:outline-none',
688
- fragments.border,
690
+ 'border-l border-gridLine focus-visible:outline-none',
689
691
  fragments.axis,
690
692
  selected && fragments.axisSelected,
691
693
  isDragging && fragments.axisSelected,
@@ -860,18 +862,17 @@ const SheetGrid = forwardRef<HTMLDivElement, SheetGridProps>(
860
862
  });
861
863
 
862
864
  // TODO(burdon): Prevent scroll if not attended.
863
- const qualifiedSubjectId = fullyQualifiedId(model.sheet);
864
- const attendableAttrs = createAttendableAttributes(qualifiedSubjectId);
865
- // const attended = useHasAttention(qualifiedSubjectId);
866
- const attended = true;
865
+ const id = fullyQualifiedId(model.sheet);
866
+ const attendableAttrs = createAttendableAttributes(id);
867
+ const hasAttention = useHasAttention(id);
867
868
 
868
869
  return (
869
870
  <div ref={containerRef} role='grid' className='relative flex grow overflow-hidden'>
870
871
  {/* Fixed border. */}
871
- <div className={mx('z-20 absolute inset-0 border pointer-events-none', fragments.border)} />
872
+ <div className={mx('z-20 absolute inset-0 border border-gridLine pointer-events-none')} />
872
873
 
873
874
  {/* Grid scroll container. */}
874
- <div ref={scrollerRef} className={mx('grow', attended && 'overflow-auto scrollbar-thin')}>
875
+ <div ref={scrollerRef} className={mx('grow', hasAttention && 'overflow-auto scrollbar-thin')}>
875
876
  {/* Scroll content. */}
876
877
  <div
877
878
  className='relative select-none'
@@ -888,7 +889,7 @@ const SheetGrid = forwardRef<HTMLDivElement, SheetGridProps>(
888
889
  const style: CSSProperties = { position: 'absolute', top, left, width, height };
889
890
  const cell = { row, column };
890
891
  const id = addressToA1Notation(cell);
891
- const idx = model.addressToIndex(cell);
892
+ const idx = addressToIndex(model.sheet, cell);
892
893
  const active = posEquals(cursor, cell);
893
894
  if (active && editing) {
894
895
  const value = initialText.current ?? model.getCellText(cell) ?? '';
@@ -1009,19 +1010,47 @@ type SheetCellProps = {
1009
1010
  };
1010
1011
 
1011
1012
  const SheetCell = ({ id, cell, style, active, onSelect }: SheetCellProps) => {
1012
- const { formatting, editing, setRange } = useSheetContext();
1013
+ const {
1014
+ formatting,
1015
+ editing,
1016
+ setRange,
1017
+ decorations,
1018
+ model: { sheet },
1019
+ } = useSheetContext();
1013
1020
  const { value, classNames } = formatting.getFormatting(cell);
1014
1021
 
1022
+ const decorationsForCell = decorations.getDecorationsForCell(addressToIndex(sheet, cell)) ?? [];
1023
+ const decorationAddedClasses = useMemo(
1024
+ () => decorationsForCell.flatMap((d) => d.classNames ?? []),
1025
+ [decorationsForCell],
1026
+ );
1027
+ const decoratedContent = decorationsForCell.reduce(
1028
+ (children, { decorate }) => {
1029
+ if (!decorate) {
1030
+ return children;
1031
+ }
1032
+ const DecoratorComponent = decorate;
1033
+ return <DecoratorComponent>{children}</DecoratorComponent>;
1034
+ },
1035
+ <div
1036
+ role='none'
1037
+ className={mx(
1038
+ 'flex flex-grow bs-full is-full px-2 items-center truncate cursor-pointer',
1039
+ ...decorationAddedClasses,
1040
+ )}
1041
+ >
1042
+ {value}
1043
+ </div>,
1044
+ );
1045
+
1015
1046
  return (
1016
1047
  <div
1017
1048
  {...{ [`data-${CELL_DATA_KEY}`]: id }}
1018
1049
  role='cell'
1019
1050
  style={style}
1020
1051
  className={mx(
1021
- 'flex w-full h-full truncate items-center border cursor-pointer',
1022
- 'px-2 py-1',
1052
+ 'border border-gridLine cursor-pointer',
1023
1053
  fragments.cell,
1024
- fragments.border,
1025
1054
  active && ['z-20', fragments.cellSelected],
1026
1055
  classNames,
1027
1056
  )}
@@ -1034,7 +1063,7 @@ const SheetCell = ({ id, cell, style, active, onSelect }: SheetCellProps) => {
1034
1063
  }}
1035
1064
  onDoubleClick={() => onSelect?.(cell, true)}
1036
1065
  >
1037
- {value}
1066
+ {decoratedContent}
1038
1067
  </div>
1039
1068
  );
1040
1069
  };
@@ -1093,7 +1122,7 @@ const SheetStatusBar = () => {
1093
1122
  }
1094
1123
 
1095
1124
  return (
1096
- <div className={mx('flex shrink-0 justify-between items-center px-4 py-1 text-sm border-x', fragments.border)}>
1125
+ <div className={mx('flex shrink-0 justify-between items-center px-4 py-1 text-sm border-x border-gridLine')}>
1097
1126
  <div className='flex gap-4 items-center'>
1098
1127
  <div className='flex w-16 items-center font-mono'>
1099
1128
  {(range && rangeToA1Notation(range)) || (cursor && addressToA1Notation(cursor))}
@@ -1129,8 +1158,7 @@ const SheetDebug = () => {
1129
1158
  <div
1130
1159
  className={mx(
1131
1160
  'z-20 absolute right-0 top-20 bottom-20 w-[30rem] overflow-auto scrollbar-thin',
1132
- 'border text-xs bg-neutral-50 dark:bg-black text-cyan-500 font-mono p-1 opacity-80',
1133
- fragments.border,
1161
+ 'border border-gridLine text-xs bg-neutral-50 dark:bg-black text-cyan-500 font-mono p-1 opacity-80',
1134
1162
  )}
1135
1163
  >
1136
1164
  <pre className='whitespace-pre-wrap'>
@@ -0,0 +1,62 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { create } from '@dxos/echo-schema';
6
+
7
+ export type Decoration = {
8
+ type: string;
9
+ /**
10
+ * A wrapping render function to encapsulate cell content. This function is applied between
11
+ * the cell's border and its padding/layout/content, allowing for custom rendering or
12
+ * additional elements to be inserted around the cell's main content.
13
+ */
14
+ decorate?: (props: { children: React.ReactNode }) => React.ReactNode;
15
+ /**
16
+ * An array of CSS class names to be applied to the content of the SheetCell.
17
+ * These classes can be used to style the cell's content independently of its structure.
18
+ */
19
+ classNames?: string[];
20
+ cellIndex: string;
21
+ };
22
+
23
+ export const createDecorations = () => {
24
+ // Reactive object to hold decorations
25
+ // TODO(Zan): Use CELL ID's to key the decoration map.
26
+ // TODO(Zan): Consider maintaining an index of decorations by type.
27
+ const { decorations } = create<{ decorations: Record<string, Decoration[]> }>({ decorations: {} });
28
+
29
+ const addDecoration = (cellIndex: string, decorator: Decoration) => {
30
+ decorations[cellIndex] = [...(decorations[cellIndex] || []), decorator];
31
+ };
32
+
33
+ const removeDecoration = (cellIndex: string, type?: string) => {
34
+ if (type) {
35
+ decorations[cellIndex] = (decorations[cellIndex] || []).filter((d) => d.type !== type);
36
+ } else {
37
+ delete decorations[cellIndex];
38
+ }
39
+ };
40
+
41
+ // TODO(Zan): I should check if returning the a value from a map in a deep signal is a reactive slice.
42
+ const getDecorationsForCell = (cellIndex: string): Decoration[] | undefined => {
43
+ return decorations[cellIndex];
44
+ };
45
+
46
+ const getAllDecorations = (): Decoration[] => {
47
+ const result: Decoration[] = [];
48
+ for (const decoratorArray of Object.values(decorations)) {
49
+ for (const decorator of decoratorArray) {
50
+ result.push(decorator);
51
+ }
52
+ }
53
+ return result;
54
+ };
55
+
56
+ return {
57
+ addDecoration,
58
+ removeDecoration,
59
+ getDecorationsForCell,
60
+ getAllDecorations,
61
+ } as const;
62
+ };
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { type ClassNameValue } from '@dxos/react-ui-types';
6
6
 
7
- import { type SheetModel, type CellAddress, inRange } from '../../model';
7
+ import { type SheetModel, type CellAddress, inRange, addressToIndex, rangeFromIndex } from '../../model';
8
8
  import { ValueTypeEnum } from '../../types';
9
9
 
10
10
  export class FormattingModel {
@@ -23,7 +23,7 @@ export class FormattingModel {
23
23
  const locales = undefined;
24
24
 
25
25
  // Cell-specific formatting.
26
- const idx = this.model.addressToIndex(cell);
26
+ const idx = addressToIndex(this.model.sheet, cell);
27
27
  let formatting = this.model.sheet.formatting?.[idx] ?? {};
28
28
  const classNames = [...(formatting?.classNames ?? [])];
29
29
 
@@ -31,7 +31,7 @@ export class FormattingModel {
31
31
  // TODO(burdon): NOTE: D0 means the D column.
32
32
  // TODO(burdon): Cache model formatting (e.g., for ranges). Create class out of this function.
33
33
  for (const [idx, _formatting] of Object.entries(this.model.sheet.formatting)) {
34
- const range = this.model.rangeFromIndex(idx);
34
+ const range = rangeFromIndex(this.model.sheet, idx);
35
35
  if (inRange(range, cell)) {
36
36
  if (_formatting.classNames) {
37
37
  classNames.push(..._formatting.classNames);
@@ -2,12 +2,13 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import React, { type PropsWithChildren, createContext, useContext, useState, useEffect } from 'react';
5
+ import React, { type PropsWithChildren, createContext, useContext, useState, useEffect, useMemo } from 'react';
6
6
 
7
7
  import { invariant } from '@dxos/invariant';
8
8
  import { type FunctionType } from '@dxos/plugin-script';
9
9
  import { fullyQualifiedId, type Space } from '@dxos/react-client/echo';
10
10
 
11
+ import { createDecorations } from './decorations';
11
12
  import { FormattingModel } from './formatting';
12
13
  import { type CellAddress, type CellRange, defaultFunctions, SheetModel } from '../../model';
13
14
  import { type SheetType } from '../../types';
@@ -36,6 +37,9 @@ export type SheetContextType = {
36
37
  // Events.
37
38
  // TODO(burdon): Generalize.
38
39
  onInfo?: () => void;
40
+
41
+ // Decorations.
42
+ decorations: ReturnType<typeof createDecorations>;
39
43
  };
40
44
 
41
45
  const SheetContext = createContext<SheetContextType | null>(null);
@@ -104,9 +108,12 @@ export const SheetContextProvider = ({
104
108
  }: PropsWithChildren<SheetContextProps>) => {
105
109
  const graph = useComputeGraph(space, options);
106
110
 
111
+ // TODO(Zan): We should offer a version of set range and set cursor that scrolls to
112
+ // that cell or range if it is not visible.
107
113
  const [cursor, setCursor] = useState<CellAddress>();
108
114
  const [range, setRange] = useState<CellRange>();
109
115
  const [editing, setEditing] = useState<boolean>(false);
116
+ const decorations = useMemo(() => createDecorations(), []);
110
117
 
111
118
  const [[model, formatting] = [], setModels] = useState<[SheetModel, FormattingModel] | undefined>(undefined);
112
119
  useEffect(() => {
@@ -142,6 +149,7 @@ export const SheetContextProvider = ({
142
149
  setEditing,
143
150
  // TODO(burdon): Change to event.
144
151
  onInfo,
152
+ decorations,
145
153
  }}
146
154
  >
147
155
  {children}