@dxos/plugin-sheet 0.6.12-staging.e11e696 → 0.6.13-main.041e8aa

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 (180) hide show
  1. package/dist/lib/browser/SheetContainer-NDNIS44E.mjs +265 -0
  2. package/dist/lib/browser/SheetContainer-NDNIS44E.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-AQSGDA4X.mjs +1614 -0
  4. package/dist/lib/browser/chunk-AQSGDA4X.mjs.map +7 -0
  5. package/dist/lib/browser/{chunk-QILRZNE5.mjs → chunk-D3QTX46O.mjs} +4 -5
  6. package/dist/lib/browser/chunk-D3QTX46O.mjs.map +7 -0
  7. package/dist/lib/browser/{chunk-WZMOZKQZ.mjs → chunk-GKI67SEF.mjs} +19 -25
  8. package/dist/lib/browser/chunk-GKI67SEF.mjs.map +7 -0
  9. package/dist/lib/browser/index.mjs +14 -19
  10. package/dist/lib/browser/index.mjs.map +4 -4
  11. package/dist/lib/browser/meta.json +1 -1
  12. package/dist/lib/browser/meta.mjs +1 -1
  13. package/dist/lib/browser/types.mjs +4 -8
  14. package/dist/lib/node/SheetContainer-YSQGJD7K.cjs +276 -0
  15. package/dist/lib/node/SheetContainer-YSQGJD7K.cjs.map +7 -0
  16. package/dist/lib/node/chunk-6F43RV45.cjs +1610 -0
  17. package/dist/lib/node/chunk-6F43RV45.cjs.map +7 -0
  18. package/dist/lib/node/{chunk-AOP42UAA.cjs → chunk-ER3PM7GD.cjs} +25 -33
  19. package/dist/lib/node/chunk-ER3PM7GD.cjs.map +7 -0
  20. package/dist/lib/node/{chunk-BNARJ5GM.cjs → chunk-QIFIGEKV.cjs} +6 -7
  21. package/dist/lib/node/chunk-QIFIGEKV.cjs.map +7 -0
  22. package/dist/lib/node/index.cjs +36 -40
  23. package/dist/lib/node/index.cjs.map +4 -4
  24. package/dist/lib/node/meta.cjs +3 -3
  25. package/dist/lib/node/meta.cjs.map +1 -1
  26. package/dist/lib/node/meta.json +1 -1
  27. package/dist/lib/node/types.cjs +8 -12
  28. package/dist/lib/node/types.cjs.map +2 -2
  29. package/dist/lib/node-esm/SheetContainer-M7WRMZDU.mjs +266 -0
  30. package/dist/lib/node-esm/SheetContainer-M7WRMZDU.mjs.map +7 -0
  31. package/dist/lib/node-esm/chunk-ELTFPX5B.mjs +1615 -0
  32. package/dist/lib/node-esm/chunk-ELTFPX5B.mjs.map +7 -0
  33. package/dist/lib/node-esm/{chunk-IU2L277A.mjs → chunk-VCYJWE3O.mjs} +4 -5
  34. package/dist/lib/node-esm/chunk-VCYJWE3O.mjs.map +7 -0
  35. package/dist/lib/node-esm/{chunk-RR2AO4SM.mjs → chunk-ZVLLQ2PJ.mjs} +19 -25
  36. package/dist/lib/node-esm/chunk-ZVLLQ2PJ.mjs.map +7 -0
  37. package/dist/lib/node-esm/index.mjs +14 -19
  38. package/dist/lib/node-esm/index.mjs.map +4 -4
  39. package/dist/lib/node-esm/meta.json +1 -1
  40. package/dist/lib/node-esm/meta.mjs +1 -1
  41. package/dist/lib/node-esm/types.mjs +4 -8
  42. package/dist/types/src/SheetPlugin.d.ts.map +1 -1
  43. package/dist/types/src/components/FunctionEditor/FunctionEditor.d.ts +3 -0
  44. package/dist/types/src/components/FunctionEditor/FunctionEditor.d.ts.map +1 -0
  45. package/dist/types/src/components/FunctionEditor/index.d.ts +2 -0
  46. package/dist/types/src/components/FunctionEditor/index.d.ts.map +1 -0
  47. package/dist/types/src/components/GridSheet/GridSheet.d.ts +1 -8
  48. package/dist/types/src/components/GridSheet/GridSheet.d.ts.map +1 -1
  49. package/dist/types/src/components/GridSheet/GridSheet.stories.d.ts +1 -1
  50. package/dist/types/src/components/GridSheet/GridSheet.stories.d.ts.map +1 -1
  51. package/dist/types/src/components/{CellEditor/CellEditor.stories.d.ts → GridSheet/SheetCellEditor.stories.d.ts} +2 -2
  52. package/dist/types/src/components/GridSheet/SheetCellEditor.stories.d.ts.map +1 -0
  53. package/dist/types/src/components/GridSheet/index.d.ts +2 -0
  54. package/dist/types/src/components/GridSheet/index.d.ts.map +1 -0
  55. package/dist/types/src/components/GridSheet/util.d.ts +11 -2
  56. package/dist/types/src/components/GridSheet/util.d.ts.map +1 -1
  57. package/dist/types/src/components/SheetContainer/SheetContainer.d.ts +6 -0
  58. package/dist/types/src/components/SheetContainer/SheetContainer.d.ts.map +1 -0
  59. package/dist/types/src/components/SheetContainer/SheetContainer.stories.d.ts +11 -0
  60. package/dist/types/src/components/SheetContainer/SheetContainer.stories.d.ts.map +1 -0
  61. package/dist/types/src/components/SheetContainer/index.d.ts +3 -0
  62. package/dist/types/src/components/SheetContainer/index.d.ts.map +1 -0
  63. package/dist/types/src/components/SheetContext/SheetContext.d.ts +27 -0
  64. package/dist/types/src/components/SheetContext/SheetContext.d.ts.map +1 -0
  65. package/dist/types/src/components/SheetContext/index.d.ts +2 -0
  66. package/dist/types/src/components/SheetContext/index.d.ts.map +1 -0
  67. package/dist/types/src/components/Toolbar/Toolbar.d.ts +31 -17
  68. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  69. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +1 -1
  70. package/dist/types/src/components/index.d.ts +3 -2
  71. package/dist/types/src/components/index.d.ts.map +1 -1
  72. package/dist/types/src/extensions/editor/extension.d.ts.map +1 -0
  73. package/dist/types/src/extensions/editor/extension.test.d.ts.map +1 -0
  74. package/dist/types/src/extensions/editor/index.d.ts +2 -0
  75. package/dist/types/src/extensions/editor/index.d.ts.map +1 -0
  76. package/dist/types/src/extensions/index.d.ts +1 -0
  77. package/dist/types/src/extensions/index.d.ts.map +1 -1
  78. package/dist/types/src/hooks/index.d.ts +1 -0
  79. package/dist/types/src/hooks/index.d.ts.map +1 -1
  80. package/dist/types/src/hooks/threads.d.ts +8 -0
  81. package/dist/types/src/hooks/threads.d.ts.map +1 -0
  82. package/dist/types/src/meta.d.ts +3 -6
  83. package/dist/types/src/meta.d.ts.map +1 -1
  84. package/dist/types/src/{components/Sheet → model}/decorations.d.ts +1 -0
  85. package/dist/types/src/model/decorations.d.ts.map +1 -0
  86. package/dist/types/src/model/formatting-model.d.ts +3 -0
  87. package/dist/types/src/model/formatting-model.d.ts.map +1 -1
  88. package/dist/types/src/model/index.d.ts +1 -0
  89. package/dist/types/src/model/index.d.ts.map +1 -1
  90. package/dist/types/src/model/sheet-model.d.ts +3 -2
  91. package/dist/types/src/model/sheet-model.d.ts.map +1 -1
  92. package/dist/types/src/types.d.ts +13 -28
  93. package/dist/types/src/types.d.ts.map +1 -1
  94. package/package.json +36 -39
  95. package/src/SheetPlugin.tsx +3 -2
  96. package/src/components/FunctionEditor/FunctionEditor.tsx +45 -0
  97. package/src/components/FunctionEditor/index.ts +5 -0
  98. package/src/components/GridSheet/GridSheet.stories.tsx +7 -2
  99. package/src/components/GridSheet/GridSheet.tsx +77 -69
  100. package/src/components/{CellEditor/CellEditor.stories.tsx → GridSheet/SheetCellEditor.stories.tsx} +2 -2
  101. package/src/components/{Sheet → GridSheet}/index.ts +1 -1
  102. package/src/components/GridSheet/util.ts +63 -27
  103. package/src/components/SheetContainer/SheetContainer.stories.tsx +40 -0
  104. package/src/components/SheetContainer/SheetContainer.tsx +52 -0
  105. package/src/components/SheetContainer/index.ts +7 -0
  106. package/src/components/{Sheet/sheet-context.tsx → SheetContext/SheetContext.tsx} +47 -27
  107. package/src/components/SheetContext/index.ts +5 -0
  108. package/src/components/Toolbar/Toolbar.tsx +127 -86
  109. package/src/components/index.ts +2 -1
  110. package/src/defs/util.ts +1 -1
  111. package/src/extensions/compute.stories.tsx +4 -4
  112. package/src/{components/CellEditor → extensions/editor}/index.ts +0 -1
  113. package/src/extensions/index.ts +1 -0
  114. package/src/hooks/index.ts +1 -0
  115. package/src/{components/Sheet/threads.tsx → hooks/threads.ts} +26 -84
  116. package/src/{meta.tsx → meta.ts} +3 -3
  117. package/src/{components/Sheet → model}/decorations.ts +2 -0
  118. package/src/model/formatting-model.ts +12 -9
  119. package/src/model/index.ts +1 -0
  120. package/src/model/sheet-model.test.ts +1 -3
  121. package/src/model/sheet-model.ts +13 -11
  122. package/src/types.ts +9 -35
  123. package/dist/lib/browser/SheetContainer-LG77O4RM.mjs +0 -262
  124. package/dist/lib/browser/SheetContainer-LG77O4RM.mjs.map +0 -7
  125. package/dist/lib/browser/chunk-CHQAW4F4.mjs +0 -2705
  126. package/dist/lib/browser/chunk-CHQAW4F4.mjs.map +0 -7
  127. package/dist/lib/browser/chunk-QILRZNE5.mjs.map +0 -7
  128. package/dist/lib/browser/chunk-WZMOZKQZ.mjs.map +0 -7
  129. package/dist/lib/node/SheetContainer-OZ7DHH4L.cjs +0 -280
  130. package/dist/lib/node/SheetContainer-OZ7DHH4L.cjs.map +0 -7
  131. package/dist/lib/node/chunk-5FTFZL5W.cjs +0 -2690
  132. package/dist/lib/node/chunk-5FTFZL5W.cjs.map +0 -7
  133. package/dist/lib/node/chunk-AOP42UAA.cjs.map +0 -7
  134. package/dist/lib/node/chunk-BNARJ5GM.cjs.map +0 -7
  135. package/dist/lib/node-esm/SheetContainer-4XS2G25Z.mjs +0 -263
  136. package/dist/lib/node-esm/SheetContainer-4XS2G25Z.mjs.map +0 -7
  137. package/dist/lib/node-esm/chunk-IU2L277A.mjs.map +0 -7
  138. package/dist/lib/node-esm/chunk-KK3XL37M.mjs +0 -2706
  139. package/dist/lib/node-esm/chunk-KK3XL37M.mjs.map +0 -7
  140. package/dist/lib/node-esm/chunk-RR2AO4SM.mjs.map +0 -7
  141. package/dist/types/src/components/CellEditor/CellEditor.d.ts +0 -34
  142. package/dist/types/src/components/CellEditor/CellEditor.d.ts.map +0 -1
  143. package/dist/types/src/components/CellEditor/CellEditor.stories.d.ts.map +0 -1
  144. package/dist/types/src/components/CellEditor/extension.d.ts.map +0 -1
  145. package/dist/types/src/components/CellEditor/extension.test.d.ts.map +0 -1
  146. package/dist/types/src/components/CellEditor/index.d.ts +0 -3
  147. package/dist/types/src/components/CellEditor/index.d.ts.map +0 -1
  148. package/dist/types/src/components/Sheet/Sheet.d.ts +0 -55
  149. package/dist/types/src/components/Sheet/Sheet.d.ts.map +0 -1
  150. package/dist/types/src/components/Sheet/Sheet.stories.d.ts +0 -53
  151. package/dist/types/src/components/Sheet/Sheet.stories.d.ts.map +0 -1
  152. package/dist/types/src/components/Sheet/decorations.d.ts.map +0 -1
  153. package/dist/types/src/components/Sheet/grid.d.ts +0 -52
  154. package/dist/types/src/components/Sheet/grid.d.ts.map +0 -1
  155. package/dist/types/src/components/Sheet/index.d.ts +0 -2
  156. package/dist/types/src/components/Sheet/index.d.ts.map +0 -1
  157. package/dist/types/src/components/Sheet/nav.d.ts +0 -29
  158. package/dist/types/src/components/Sheet/nav.d.ts.map +0 -1
  159. package/dist/types/src/components/Sheet/sheet-context.d.ts +0 -26
  160. package/dist/types/src/components/Sheet/sheet-context.d.ts.map +0 -1
  161. package/dist/types/src/components/Sheet/threads.d.ts +0 -2
  162. package/dist/types/src/components/Sheet/threads.d.ts.map +0 -1
  163. package/dist/types/src/components/Sheet/util.d.ts +0 -18
  164. package/dist/types/src/components/Sheet/util.d.ts.map +0 -1
  165. package/dist/types/src/components/SheetContainer.d.ts +0 -8
  166. package/dist/types/src/components/SheetContainer.d.ts.map +0 -1
  167. package/dist/types/src/components/Toolbar/common.d.ts +0 -20
  168. package/dist/types/src/components/Toolbar/common.d.ts.map +0 -1
  169. package/src/components/CellEditor/CellEditor.tsx +0 -163
  170. package/src/components/Sheet/Sheet.stories.tsx +0 -251
  171. package/src/components/Sheet/Sheet.tsx +0 -1215
  172. package/src/components/Sheet/grid.ts +0 -191
  173. package/src/components/Sheet/nav.ts +0 -157
  174. package/src/components/Sheet/util.ts +0 -56
  175. package/src/components/SheetContainer.tsx +0 -86
  176. package/src/components/Toolbar/common.tsx +0 -72
  177. /package/dist/types/src/{components/CellEditor → extensions/editor}/extension.d.ts +0 -0
  178. /package/dist/types/src/{components/CellEditor → extensions/editor}/extension.test.d.ts +0 -0
  179. /package/src/{components/CellEditor → extensions/editor}/extension.test.ts +0 -0
  180. /package/src/{components/CellEditor → extensions/editor}/extension.ts +0 -0
@@ -2,48 +2,87 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import {
6
- type Icon,
7
- Calendar,
8
- ChatText,
9
- CurrencyDollar,
10
- Eraser,
11
- HighlighterCircle,
12
- TextAlignCenter,
13
- TextAlignLeft,
14
- TextAlignRight,
15
- } from '@phosphor-icons/react';
16
5
  import { createContext } from '@radix-ui/react-context';
17
6
  import React, { type PropsWithChildren } from 'react';
18
7
 
19
8
  import {
20
- DensityProvider,
21
- ElevationProvider,
9
+ Icon,
22
10
  Toolbar as NaturalToolbar,
23
- type ThemedClassName,
24
11
  useTranslation,
12
+ Tooltip,
13
+ type ToolbarToggleGroupItemProps as NaturalToolbarToggleGroupItemProps,
14
+ type ToolbarButtonProps as NaturalToolbarButtonProps,
15
+ type ToolbarToggleProps as NaturalToolbarToggleProps,
16
+ type ThemedClassName,
25
17
  } from '@dxos/react-ui';
18
+ import { useAttention } from '@dxos/react-ui-attention';
26
19
  import { nonNullable } from '@dxos/util';
27
20
 
28
- import { ToolbarButton, ToolbarSeparator, ToolbarToggleButton } from './common';
29
21
  import { addressToIndex } from '../../defs';
30
22
  import { SHEET_PLUGIN } from '../../meta';
31
23
  import { type Formatting } from '../../types';
32
- import { useSheetContext } from '../Sheet/sheet-context';
24
+ import { useSheetContext } from '../SheetContext';
25
+
26
+ //
27
+ // Buttons
28
+ //
29
+
30
+ const buttonStyles = 'min-bs-0 p-2';
31
+ const tooltipProps = { side: 'bottom' as const, classNames: 'z-10' };
32
+
33
+ const ToolbarSeparator = () => <div role='separator' className='grow' />;
34
+
35
+ //
36
+ // ToolbarItem
37
+ //
38
+
39
+ type ToolbarItemProps =
40
+ | (NaturalToolbarButtonProps & { itemType: 'button'; icon: string })
41
+ | (NaturalToolbarToggleGroupItemProps & { itemType: 'toggleGroupItem'; icon: string })
42
+ | (NaturalToolbarToggleProps & { itemType: 'toggle'; icon: string });
43
+
44
+ export const ToolbarItem = ({ itemType, icon, children, ...props }: ToolbarItemProps) => {
45
+ const Invoker =
46
+ itemType === 'toggleGroupItem'
47
+ ? NaturalToolbar.ToggleGroupItem
48
+ : itemType === 'toggle'
49
+ ? NaturalToolbar.Toggle
50
+ : NaturalToolbar.Button;
51
+ return (
52
+ <Tooltip.Root>
53
+ <Tooltip.Trigger asChild>
54
+ {/* TODO(thure): type the props spread better. */}
55
+ <Invoker variant='ghost' {...(props as any)} classNames={buttonStyles}>
56
+ <Icon icon={icon} size={5} />
57
+ <span className='sr-only'>{children}</span>
58
+ </Invoker>
59
+ </Tooltip.Trigger>
60
+ <Tooltip.Portal>
61
+ <Tooltip.Content {...tooltipProps}>
62
+ {children}
63
+ <Tooltip.Arrow />
64
+ </Tooltip.Content>
65
+ </Tooltip.Portal>
66
+ </Tooltip.Root>
67
+ );
68
+ };
33
69
 
34
70
  //
35
71
  // Root
36
72
  //
37
73
 
38
- export type ToolbarAction =
39
- | { type: 'clear' }
40
- | { type: 'highlight' }
41
- | { type: 'left' }
42
- | { type: 'center' }
43
- | { type: 'right' }
44
- | { type: 'date' }
45
- | { type: 'currency' }
46
- | { type: 'comment'; anchor: string; cellContent?: string };
74
+ type AlignValue = 'left' | 'center' | 'right' | 'unset';
75
+ type AlignAction = { type: 'align'; value: AlignValue };
76
+
77
+ type CommentAction = { type: 'comment'; anchor: string; cellContent?: string };
78
+
79
+ type FormatValue = 'date' | 'currency' | 'unset';
80
+ type FormatAction = { type: 'format'; value: FormatValue };
81
+
82
+ type StyleValue = 'highlight' | 'unset';
83
+ type StyleAction = { type: 'style'; value: StyleValue };
84
+
85
+ export type ToolbarAction = StyleAction | AlignAction | FormatAction | CommentAction;
47
86
 
48
87
  export type ToolbarActionType = ToolbarAction['type'];
49
88
 
@@ -52,30 +91,41 @@ export type ToolbarActionHandler = (action: ToolbarAction) => void;
52
91
  export type ToolbarProps = ThemedClassName<
53
92
  PropsWithChildren<{
54
93
  onAction?: ToolbarActionHandler;
94
+ role?: string;
55
95
  }>
56
96
  >;
57
97
 
58
98
  const [ToolbarContextProvider, useToolbarContext] = createContext<ToolbarProps>('Toolbar');
59
99
 
60
- const ToolbarRoot = ({ children, onAction, classNames }: ToolbarProps) => {
100
+ // TODO(Zan): Factor out, copied this from MarkdownPlugin.
101
+ const sectionToolbarLayout =
102
+ 'bs-[--rail-action] bg-[--sticky-bg] sticky block-start-0 __-block-start-px transition-opacity';
103
+
104
+ const ToolbarRoot = ({ children, onAction, role, classNames }: ToolbarProps) => {
105
+ const { id } = useSheetContext();
106
+ const { hasAttention } = useAttention(id);
107
+
61
108
  return (
62
109
  <ToolbarContextProvider onAction={onAction}>
63
- <DensityProvider density='fine'>
64
- <ElevationProvider elevation='chrome'>
65
- <NaturalToolbar.Root classNames={['is-full shrink-0 overflow-x-auto overflow-y-hidden p-1', classNames]}>
66
- {children}
67
- </NaturalToolbar.Root>
68
- </ElevationProvider>
69
- </DensityProvider>
110
+ <NaturalToolbar.Root
111
+ classNames={[
112
+ ...(role === 'section'
113
+ ? ['z-[2] group-focus-within/section:visible', !hasAttention && 'invisible', sectionToolbarLayout]
114
+ : ['attention-surface']),
115
+ classNames,
116
+ ]}
117
+ >
118
+ {children}
119
+ </NaturalToolbar.Root>
70
120
  </ToolbarContextProvider>
71
121
  );
72
122
  };
73
123
 
74
124
  // TODO(burdon): Generalize.
75
125
  // TODO(burdon): Detect and display current state.
76
- type ButtonProps = {
77
- type: ToolbarActionType;
78
- Icon: Icon;
126
+ type ButtonProps<T> = {
127
+ value: T;
128
+ icon: string;
79
129
  getState: (state: Formatting) => boolean;
80
130
  disabled?: (state: Formatting) => boolean;
81
131
  };
@@ -84,9 +134,9 @@ type ButtonProps = {
84
134
  // Alignment
85
135
  //
86
136
 
87
- const formatOptions: ButtonProps[] = [
88
- { type: 'date', Icon: Calendar, getState: (state) => false },
89
- { type: 'currency', Icon: CurrencyDollar, getState: (state) => false },
137
+ const formatOptions: ButtonProps<FormatValue>[] = [
138
+ { value: 'date', icon: 'ph--calendar--regular', getState: (state) => false },
139
+ { value: 'currency', icon: 'ph--currency-dollar--regular', getState: (state) => false },
90
140
  ];
91
141
 
92
142
  const Format = () => {
@@ -98,26 +148,25 @@ const Format = () => {
98
148
  type='single'
99
149
  // value={cellStyles.filter(({ getState }) => state && getState(state)).map(({ type }) => type)}
100
150
  >
101
- {formatOptions.map(({ type, getState, Icon }) => (
102
- <ToolbarToggleButton
103
- key={type}
104
- value={type}
105
- Icon={Icon}
106
- // disabled={state?.blockType === 'codeblock'}
107
- // onClick={state ? () => onAction?.({ type, data: !getState(state) }) : undefined}
108
- onClick={() => onAction?.({ type: type as Exclude<typeof type, 'comment'> })}
151
+ {formatOptions.map(({ value, getState, icon }) => (
152
+ <ToolbarItem
153
+ itemType='toggleGroupItem'
154
+ key={value}
155
+ value={value}
156
+ icon={icon}
157
+ onClick={() => onAction?.({ type: 'format', value })}
109
158
  >
110
- {t(`toolbar ${type} label`)}
111
- </ToolbarToggleButton>
159
+ {t(`toolbar ${value} label`)}
160
+ </ToolbarItem>
112
161
  ))}
113
162
  </NaturalToolbar.ToggleGroup>
114
163
  );
115
164
  };
116
165
 
117
- const alignmentOptions: ButtonProps[] = [
118
- { type: 'left', Icon: TextAlignLeft, getState: (state) => false },
119
- { type: 'center', Icon: TextAlignCenter, getState: (state) => false },
120
- { type: 'right', Icon: TextAlignRight, getState: (state) => false },
166
+ const alignmentOptions: ButtonProps<AlignValue>[] = [
167
+ { value: 'left', icon: 'ph--text-align-left--regular', getState: (state) => false },
168
+ { value: 'center', icon: 'ph--text-align-center--regular', getState: (state) => false },
169
+ { value: 'right', icon: 'ph--text-align-right--regular', getState: (state) => false },
121
170
  ];
122
171
 
123
172
  const Alignment = () => {
@@ -128,50 +177,41 @@ const Alignment = () => {
128
177
  <NaturalToolbar.ToggleGroup
129
178
  type='single'
130
179
  // value={cellStyles.filter(({ getState }) => state && getState(state)).map(({ type }) => type)}
180
+ // disabled={state?.blockType === 'codeblock'}
181
+ onValueChange={(value: AlignValue) => onAction?.({ type: 'align', value })}
131
182
  >
132
- {alignmentOptions.map(({ type, getState, Icon }) => (
133
- <ToolbarToggleButton
134
- key={type}
135
- value={type}
136
- Icon={Icon}
137
- // disabled={state?.blockType === 'codeblock'}
138
- // onClick={state ? () => onAction?.({ type, data: !getState(state) }) : undefined}
139
- onClick={() => onAction?.({ type: type as Exclude<typeof type, 'comment'> })}
140
- >
141
- {t(`toolbar ${type} label`)}
142
- </ToolbarToggleButton>
183
+ {alignmentOptions.map(({ value, getState, icon }) => (
184
+ <ToolbarItem itemType='toggleGroupItem' key={value} value={value} icon={icon}>
185
+ {t(`toolbar ${value} label`)}
186
+ </ToolbarItem>
143
187
  ))}
144
188
  </NaturalToolbar.ToggleGroup>
145
189
  );
146
190
  };
147
191
 
148
- const styleOptions: ButtonProps[] = [
149
- { type: 'clear', Icon: Eraser, getState: (state) => false },
150
- { type: 'highlight', Icon: HighlighterCircle, getState: (state) => false },
192
+ const styleOptions: ButtonProps<StyleValue>[] = [
193
+ { value: 'highlight', icon: 'ph--highlighter--regular', getState: (state) => false },
151
194
  ];
152
195
 
153
196
  const Styles = () => {
154
- const { onAction } = useToolbarContext('Alignment');
197
+ const { onAction } = useToolbarContext('Styles');
155
198
  const { t } = useTranslation(SHEET_PLUGIN);
156
199
 
157
200
  return (
158
- <NaturalToolbar.ToggleGroup
159
- type='single'
160
- // value={cellStyles.filter(({ getState }) => state && getState(state)).map(({ type }) => type)}
161
- >
162
- {styleOptions.map(({ type, getState, Icon }) => (
163
- <ToolbarToggleButton
164
- key={type}
165
- value={type}
166
- Icon={Icon}
167
- // disabled={state?.blockType === 'codeblock'}
168
- // onClick={state ? () => onAction?.({ type, data: !getState(state) }) : undefined}
169
- onClick={() => onAction?.({ type: type as Exclude<typeof type, 'comment'> })}
201
+ <>
202
+ {styleOptions.map(({ value, getState, icon }) => (
203
+ <ToolbarItem
204
+ itemType='toggle'
205
+ key={value}
206
+ onPressedChange={(nextPressed: boolean) =>
207
+ onAction?.({ type: 'style', value: nextPressed ? 'highlight' : 'unset' })
208
+ }
209
+ icon={icon}
170
210
  >
171
- {t(`toolbar ${type} label`)}
172
- </ToolbarToggleButton>
211
+ {t(`toolbar ${value} label`)}
212
+ </ToolbarItem>
173
213
  ))}
174
- </NaturalToolbar.ToggleGroup>
214
+ </>
175
215
  );
176
216
  };
177
217
 
@@ -206,9 +246,10 @@ const Actions = () => {
206
246
  : 'comment label';
207
247
 
208
248
  return (
209
- <ToolbarButton
249
+ <ToolbarItem
250
+ itemType='button'
210
251
  value='comment'
211
- Icon={ChatText}
252
+ icon='ph--chat-text--regular'
212
253
  data-testid='editor.toolbar.comment'
213
254
  onClick={() => {
214
255
  if (!cursor) {
@@ -223,7 +264,7 @@ const Actions = () => {
223
264
  disabled={!cursorOnly || overlapsCommentAnchor}
224
265
  >
225
266
  {t(tooltipLabelKey)}
226
- </ToolbarButton>
267
+ </ToolbarItem>
227
268
  );
228
269
  };
229
270
 
@@ -5,7 +5,8 @@
5
5
  import React from 'react';
6
6
 
7
7
  export * from './ComputeGraph';
8
- export * from './Sheet';
8
+ export * from './GridSheet';
9
+ export * from './SheetContext';
9
10
 
10
11
  // Lazily load components for content surfaces.
11
12
  export const SheetContainer = React.lazy(() => import('./SheetContainer'));
package/src/defs/util.ts CHANGED
@@ -68,7 +68,7 @@ export const createSheet = ({ name, cells, ...size }: CreateSheetOptions = {}):
68
68
  columns: [],
69
69
  rowMeta: {},
70
70
  columnMeta: {},
71
- formatting: {},
71
+ formatting: [],
72
72
  });
73
73
 
74
74
  initialize(sheet, size);
@@ -21,7 +21,7 @@ import { withTheme, withLayout } from '@dxos/storybook-utils';
21
21
  import { nonNullable } from '@dxos/util';
22
22
 
23
23
  import { compute, computeGraphFacet } from './compute';
24
- import { Sheet } from '../components';
24
+ import { GridSheet, SheetProvider } from '../components';
25
25
  import { useComputeGraph, useSheetModel } from '../hooks';
26
26
  import { useTestSheet, withComputeGraphDecorator } from '../testing';
27
27
  import { SheetType } from '../types';
@@ -80,9 +80,9 @@ const Grid = () => {
80
80
 
81
81
  return (
82
82
  <div className='flex w-[40rem] overflow-hidden'>
83
- <Sheet.Root graph={graph} sheet={sheet}>
84
- <Sheet.Main classNames='border border-separator' />
85
- </Sheet.Root>
83
+ <SheetProvider graph={graph} sheet={sheet}>
84
+ <GridSheet />
85
+ </SheetProvider>
86
86
  </div>
87
87
  );
88
88
  };
@@ -2,5 +2,4 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- export * from './CellEditor';
6
5
  export * from './extension';
@@ -3,3 +3,4 @@
3
3
  //
4
4
 
5
5
  export * from './compute';
6
+ export * from './editor';
@@ -5,3 +5,4 @@
5
5
  export * from './useComputeGraph';
6
6
  export * from './useFormattingModel';
7
7
  export * from './useSheetModel';
8
+ export * from './threads';
@@ -3,82 +3,21 @@
3
3
  //
4
4
 
5
5
  import { effect } from '@preact/signals-core';
6
- import React, { type PropsWithChildren, useCallback, useEffect, useMemo } from 'react';
6
+ import { type MutableRefObject, useCallback, useEffect, useMemo } from 'react';
7
7
 
8
8
  import { type IntentResolver, LayoutAction, useIntentDispatcher, useIntentResolver } from '@dxos/app-framework';
9
9
  import { debounce } from '@dxos/async';
10
10
  import { fullyQualifiedId } from '@dxos/react-client/echo';
11
- import { Icon, useTranslation } from '@dxos/react-ui';
11
+ import { type DxGridElement, type DxGridPosition } from '@dxos/react-ui-grid';
12
12
 
13
- import { type Decoration } from './decorations';
14
- import { useSheetContext } from './sheet-context';
15
- import { addressFromIndex, addressToIndex, type CellAddress, closest } from '../../defs';
16
- import { SHEET_PLUGIN } from '../../meta';
17
-
18
- // TODO(burdon): Move into folder; split hooks.
19
-
20
- const CommentIndicator = () => {
21
- return (
22
- <div
23
- role='none'
24
- className='absolute top-0 right-0 w-0 h-0 border-t-8 border-l-8 border-t-cmCommentSurface border-l-transparent'
25
- />
26
- );
27
- };
28
-
29
- const ThreadedCellWrapper = ({ children }: PropsWithChildren) => {
30
- const dispatch = useIntentDispatcher();
31
- const [isHovered, setIsHovered] = React.useState(false);
32
- const { t } = useTranslation(SHEET_PLUGIN);
33
-
34
- const handleClick = React.useCallback(
35
- (_event: React.MouseEvent) => {
36
- void dispatch({ action: LayoutAction.SET_LAYOUT, data: { element: 'complementary', state: true } });
37
- },
38
- [dispatch],
39
- );
40
-
41
- return (
42
- <div
43
- role='none'
44
- className='relative h-full is-full'
45
- onMouseEnter={() => {
46
- setIsHovered(true);
47
- }}
48
- onMouseLeave={() => {
49
- setIsHovered(false);
50
- }}
51
- >
52
- <CommentIndicator />
53
- {isHovered && (
54
- <div className='absolute inset-0 flex items-center justify-end pr-1'>
55
- <button
56
- className='ch-button text-xs min-bs-0 p-1'
57
- onClick={handleClick}
58
- aria-label={t('open comment for sheet cell')}
59
- >
60
- <Icon icon='ph--chat--regular' aria-hidden={true} />
61
- </button>
62
- </div>
63
- )}
64
- {children}
65
- </div>
66
- );
67
- };
68
-
69
- const createThreadDecoration = (cellIndex: string, threadId: string, sheetId: string): Decoration => {
70
- return {
71
- type: 'comment',
72
- cellIndex,
73
- decorate: (props) => <ThreadedCellWrapper {...props} />,
74
- };
75
- };
76
-
77
- // TODO(burdon): Factor out hooks.
78
-
79
- const useUpdateCursorOnThreadSelection = () => {
80
- const { setCursor, model } = useSheetContext();
13
+ import { addressFromIndex, addressToIndex, type CellAddress, closest } from '../defs';
14
+ import { SHEET_PLUGIN } from '../meta';
15
+ import { type SheetModel, type Decoration, type Decorations } from '../model';
81
16
 
17
+ export const useUpdateFocusedCellOnThreadSelection = (
18
+ model: SheetModel,
19
+ grid: MutableRefObject<DxGridElement | null>,
20
+ ) => {
82
21
  const handleScrollIntoView: IntentResolver = useCallback(
83
22
  ({ action, data }) => {
84
23
  switch (action) {
@@ -89,18 +28,17 @@ const useUpdateCursorOnThreadSelection = () => {
89
28
 
90
29
  // TODO(Zan): Everywhere we refer to the cursor in a thread context should change to `anchor`.
91
30
  const cellAddress = addressFromIndex(model.sheet, data.cursor);
92
- setCursor(cellAddress);
31
+ grid.current?.setFocus({ ...cellAddress, plane: 'grid' }, true);
93
32
  }
94
33
  }
95
34
  },
96
- [model.sheet, setCursor],
35
+ [model.sheet],
97
36
  );
98
37
 
99
38
  useIntentResolver(SHEET_PLUGIN, handleScrollIntoView);
100
39
  };
101
40
 
102
- const useSelectThreadOnCursorChange = () => {
103
- const { cursor, model } = useSheetContext();
41
+ export const useSelectThreadOnCellFocus = (model: SheetModel, cursor?: CellAddress) => {
104
42
  const dispatch = useIntentDispatcher();
105
43
 
106
44
  const activeThreads = useMemo(
@@ -108,7 +46,10 @@ const useSelectThreadOnCursorChange = () => {
108
46
  model.sheet.threads?.filter(
109
47
  (thread): thread is NonNullable<typeof thread> => !!thread && thread.status === 'active',
110
48
  ) ?? [],
111
- [JSON.stringify(model.sheet.threads)],
49
+ [
50
+ // TODO(thure): Surely we can find a better dependency for this…
51
+ JSON.stringify(model.sheet.threads),
52
+ ],
112
53
  );
113
54
 
114
55
  const activeThreadAddresses = useMemo(
@@ -143,7 +84,7 @@ const useSelectThreadOnCursorChange = () => {
143
84
  );
144
85
 
145
86
  const debounced = useMemo(() => {
146
- return debounce((cursor: CellAddress) => requestAnimationFrame(() => selectClosestThread(cursor)), 50);
87
+ return debounce((cellCoords: DxGridPosition) => requestAnimationFrame(() => selectClosestThread(cellCoords)), 50);
147
88
  }, [selectClosestThread]);
148
89
 
149
90
  useEffect(() => {
@@ -154,8 +95,15 @@ const useSelectThreadOnCursorChange = () => {
154
95
  }, [cursor, selectClosestThread]);
155
96
  };
156
97
 
157
- const useThreadDecorations = () => {
158
- const { decorations, model } = useSheetContext();
98
+ const createThreadDecoration = (cellIndex: string, threadId: string, sheetId: string): Decoration => {
99
+ return {
100
+ type: 'comment',
101
+ classNames: ['bg-greenFill'],
102
+ cellIndex,
103
+ };
104
+ };
105
+
106
+ export const useThreadDecorations = (model: SheetModel, decorations: Decorations) => {
159
107
  const sheet = useMemo(() => model.sheet, [model.sheet]);
160
108
  const sheetId = useMemo(() => fullyQualifiedId(sheet), [sheet]);
161
109
 
@@ -197,9 +145,3 @@ const useThreadDecorations = () => {
197
145
  return () => unsubscribe();
198
146
  });
199
147
  };
200
-
201
- export const useThreads = () => {
202
- useUpdateCursorOnThreadSelection();
203
- useSelectThreadOnCursorChange();
204
- useThreadDecorations();
205
- };
@@ -2,13 +2,13 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { pluginMeta } from '@dxos/app-framework';
5
+ import { type PluginMeta } from '@dxos/app-framework';
6
6
 
7
7
  export const SHEET_PLUGIN = 'dxos.org/plugin/sheet';
8
8
 
9
- export default pluginMeta({
9
+ export default {
10
10
  id: SHEET_PLUGIN,
11
11
  name: 'Sheet',
12
12
  description: 'A simple spreadsheet plugin.',
13
13
  icon: 'ph--grid-nine--regular',
14
- });
14
+ } satisfies PluginMeta;
@@ -60,3 +60,5 @@ export const createDecorations = () => {
60
60
  getAllDecorations,
61
61
  } as const;
62
62
  };
63
+
64
+ export type Decorations = ReturnType<typeof createDecorations>;
@@ -3,17 +3,20 @@
3
3
  //
4
4
 
5
5
  import { type ClassNameValue } from '@dxos/react-ui-types';
6
+ import { FieldValueType } from '@dxos/schema';
6
7
 
7
8
  import { type SheetModel } from './sheet-model';
8
9
  import { type CellAddress, inRange } from '../defs';
9
10
  import { addressToIndex, rangeFromIndex } from '../defs';
10
- import { ValueTypeEnum } from '../types';
11
11
 
12
12
  export type CellFormat = {
13
13
  value?: string;
14
14
  classNames?: ClassNameValue;
15
15
  };
16
16
 
17
+ /**
18
+ * @deprecated See react-ui-data.
19
+ */
17
20
  export class FormattingModel {
18
21
  constructor(private readonly _model: SheetModel) {}
19
22
 
@@ -31,7 +34,7 @@ export class FormattingModel {
31
34
 
32
35
  // Cell-specific formatting.
33
36
  const idx = addressToIndex(this._model.sheet, cell);
34
- let formatting = this._model.sheet.formatting?.[idx] ?? {};
37
+ let formatting = this._model.sheet.formatting.find?.(({ range }) => range === idx);
35
38
  const classNames = [...(formatting?.classNames ?? [])];
36
39
 
37
40
  // Range formatting.
@@ -55,7 +58,7 @@ export class FormattingModel {
55
58
 
56
59
  const type = formatting?.type ?? this._model.getValueType(cell);
57
60
  switch (type) {
58
- case ValueTypeEnum.Boolean: {
61
+ case FieldValueType.Boolean: {
59
62
  return {
60
63
  value: (value as boolean).toLocaleString().toUpperCase(),
61
64
  classNames: [...classNames, value ? '!text-greenText' : '!text-orangeText'],
@@ -66,15 +69,15 @@ export class FormattingModel {
66
69
  // Numbers.
67
70
  //
68
71
 
69
- case ValueTypeEnum.Number: {
72
+ case FieldValueType.Number: {
70
73
  return { value: value.toLocaleString(locales), classNames: [...classNames, defaultNumber] };
71
74
  }
72
75
 
73
- case ValueTypeEnum.Percent: {
76
+ case FieldValueType.Percent: {
74
77
  return { value: (value as number) * 100 + '%', classNames: [...classNames, defaultNumber] };
75
78
  }
76
79
 
77
- case ValueTypeEnum.Currency: {
80
+ case FieldValueType.Currency: {
78
81
  return {
79
82
  value: (value as number).toLocaleString(locales, {
80
83
  style: 'currency',
@@ -90,17 +93,17 @@ export class FormattingModel {
90
93
  // Dates.
91
94
  //
92
95
 
93
- case ValueTypeEnum.DateTime: {
96
+ case FieldValueType.DateTime: {
94
97
  const date = this._model.toLocalDate(value as number);
95
98
  return { value: date.toLocaleString(locales), classNames };
96
99
  }
97
100
 
98
- case ValueTypeEnum.Date: {
101
+ case FieldValueType.Date: {
99
102
  const date = this._model.toLocalDate(value as number);
100
103
  return { value: date.toLocaleDateString(locales), classNames };
101
104
  }
102
105
 
103
- case ValueTypeEnum.Time: {
106
+ case FieldValueType.Time: {
104
107
  const date = this._model.toLocalDate(value as number);
105
108
  return { value: date.toLocaleTimeString(locales), classNames };
106
109
  }
@@ -2,5 +2,6 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ export * from './decorations';
5
6
  export * from './formatting-model';
6
7
  export * from './sheet-model';
@@ -8,12 +8,10 @@ import { Trigger } from '@dxos/async';
8
8
  import { FunctionType } from '@dxos/plugin-script/types';
9
9
 
10
10
  import { SheetModel } from './sheet-model';
11
- import { createSheet, addressFromA1Notation } from '../defs';
11
+ import { addressFromA1Notation, createSheet } from '../defs';
12
12
  import { TestBuilder, testFunctionPlugins } from '../graph/testing';
13
13
  import { type CellScalarValue } from '../types';
14
14
 
15
- // TODO(burdon): GPT("prompt", inputs); e.g., with large text cells.
16
-
17
15
  describe('SheetModel', () => {
18
16
  let testBuilder: TestBuilder;
19
17
  beforeEach(async () => {