@dxos/plugin-sheet 0.6.14-staging.9e90729 → 0.6.14-staging.a52de51

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 (117) hide show
  1. package/dist/lib/browser/{SheetContainer-JBB7W52Y.mjs → SheetContainer-P3NF5KEI.mjs} +60 -48
  2. package/dist/lib/browser/SheetContainer-P3NF5KEI.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-XXDHBYZQ.mjs → chunk-4LKIURJA.mjs} +337 -122
  4. package/dist/lib/browser/chunk-4LKIURJA.mjs.map +7 -0
  5. package/dist/lib/browser/{chunk-3QWIMZZJ.mjs → chunk-BVUN7SHF.mjs} +5 -3
  6. package/dist/lib/browser/chunk-BVUN7SHF.mjs.map +7 -0
  7. package/dist/lib/browser/{chunk-D3QTX46O.mjs → chunk-RABELMEQ.mjs} +3 -2
  8. package/dist/lib/browser/{chunk-D3QTX46O.mjs.map → chunk-RABELMEQ.mjs.map} +3 -3
  9. package/dist/lib/browser/{chunk-2ZK3YMZG.mjs → chunk-VMSX6Z4X.mjs} +58 -20
  10. package/dist/lib/browser/chunk-VMSX6Z4X.mjs.map +7 -0
  11. package/dist/lib/browser/{compute-graph-ZQKB6QVP.mjs → compute-graph-GGWUX644.mjs} +4 -4
  12. package/dist/lib/browser/index.mjs +48 -21
  13. package/dist/lib/browser/index.mjs.map +3 -3
  14. package/dist/lib/browser/meta.json +1 -1
  15. package/dist/lib/browser/meta.mjs +1 -1
  16. package/dist/lib/browser/types.mjs +2 -2
  17. package/dist/lib/node/{SheetContainer-L37HUFCF.cjs → SheetContainer-MPFKXY26.cjs} +72 -63
  18. package/dist/lib/node/SheetContainer-MPFKXY26.cjs.map +7 -0
  19. package/dist/lib/node/{chunk-QIFIGEKV.cjs → chunk-2ZVZI2KJ.cjs} +6 -5
  20. package/dist/lib/node/{chunk-QIFIGEKV.cjs.map → chunk-2ZVZI2KJ.cjs.map} +3 -3
  21. package/dist/lib/node/{chunk-V7E5JZBD.cjs → chunk-AWKOWDMI.cjs} +9 -7
  22. package/dist/lib/node/chunk-AWKOWDMI.cjs.map +7 -0
  23. package/dist/lib/node/{chunk-QNFMTK3X.cjs → chunk-O7XR4R7Y.cjs} +69 -29
  24. package/dist/lib/node/chunk-O7XR4R7Y.cjs.map +7 -0
  25. package/dist/lib/node/{chunk-7AWAC3R3.cjs → chunk-OO24XJBV.cjs} +353 -139
  26. package/dist/lib/node/chunk-OO24XJBV.cjs.map +7 -0
  27. package/dist/lib/node/{compute-graph-RJB6OVW6.cjs → compute-graph-KGWA2QLE.cjs} +21 -21
  28. package/dist/lib/node/{compute-graph-RJB6OVW6.cjs.map → compute-graph-KGWA2QLE.cjs.map} +2 -2
  29. package/dist/lib/node/index.cjs +74 -46
  30. package/dist/lib/node/index.cjs.map +3 -3
  31. package/dist/lib/node/meta.cjs +3 -3
  32. package/dist/lib/node/meta.cjs.map +1 -1
  33. package/dist/lib/node/meta.json +1 -1
  34. package/dist/lib/node/types.cjs +7 -7
  35. package/dist/lib/node/types.cjs.map +1 -1
  36. package/dist/lib/node-esm/{SheetContainer-CLKFEM35.mjs → SheetContainer-22IOAW3B.mjs} +60 -48
  37. package/dist/lib/node-esm/SheetContainer-22IOAW3B.mjs.map +7 -0
  38. package/dist/lib/node-esm/{chunk-VCYJWE3O.mjs → chunk-BM2Q3FFC.mjs} +3 -2
  39. package/dist/lib/node-esm/{chunk-VCYJWE3O.mjs.map → chunk-BM2Q3FFC.mjs.map} +3 -3
  40. package/dist/lib/node-esm/{chunk-3JYJEOCF.mjs → chunk-BW36PM2Y.mjs} +337 -122
  41. package/dist/lib/node-esm/chunk-BW36PM2Y.mjs.map +7 -0
  42. package/dist/lib/node-esm/{chunk-JBTORSST.mjs → chunk-CR4K75EL.mjs} +58 -20
  43. package/dist/lib/node-esm/chunk-CR4K75EL.mjs.map +7 -0
  44. package/dist/lib/node-esm/{chunk-SPQFLYC2.mjs → chunk-UIBWRHW7.mjs} +5 -3
  45. package/dist/lib/node-esm/chunk-UIBWRHW7.mjs.map +7 -0
  46. package/dist/lib/node-esm/{compute-graph-VKRG526N.mjs → compute-graph-2SCZT7N5.mjs} +4 -4
  47. package/dist/lib/node-esm/index.mjs +48 -21
  48. package/dist/lib/node-esm/index.mjs.map +3 -3
  49. package/dist/lib/node-esm/meta.json +1 -1
  50. package/dist/lib/node-esm/meta.mjs +1 -1
  51. package/dist/lib/node-esm/types.mjs +2 -2
  52. package/dist/types/src/SheetPlugin.d.ts.map +1 -1
  53. package/dist/types/src/components/FunctionEditor/FunctionEditor.d.ts.map +1 -1
  54. package/dist/types/src/components/GridSheet/GridSheet.d.ts.map +1 -1
  55. package/dist/types/src/components/GridSheet/util.d.ts +2 -2
  56. package/dist/types/src/components/GridSheet/util.d.ts.map +1 -1
  57. package/dist/types/src/components/RangeList/RangeList.d.ts +2 -4
  58. package/dist/types/src/components/RangeList/RangeList.d.ts.map +1 -1
  59. package/dist/types/src/components/SheetContainer/SheetContainer.d.ts +6 -3
  60. package/dist/types/src/components/SheetContainer/SheetContainer.d.ts.map +1 -1
  61. package/dist/types/src/components/SheetContainer/SheetContainer.stories.d.ts.map +1 -1
  62. package/dist/types/src/components/Toolbar/Toolbar.d.ts +6 -3
  63. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  64. package/dist/types/src/components/index.d.ts +4 -2
  65. package/dist/types/src/components/index.d.ts.map +1 -1
  66. package/dist/types/src/compute-graph/compute-graph.d.ts +2 -2
  67. package/dist/types/src/compute-graph/compute-graph.d.ts.map +1 -1
  68. package/dist/types/src/compute-graph/functions/async-function.d.ts.map +1 -1
  69. package/dist/types/src/compute-graph/functions/edge-function.d.ts.map +1 -1
  70. package/dist/types/src/defs/sheet-range-types.d.ts +2 -2
  71. package/dist/types/src/defs/sheet-range-types.d.ts.map +1 -1
  72. package/dist/types/src/defs/util.d.ts +1 -1
  73. package/dist/types/src/defs/util.d.ts.map +1 -1
  74. package/dist/types/src/integrations/thread-ranges.d.ts +1 -2
  75. package/dist/types/src/integrations/thread-ranges.d.ts.map +1 -1
  76. package/dist/types/src/meta.d.ts +1 -0
  77. package/dist/types/src/meta.d.ts.map +1 -1
  78. package/dist/types/src/model/sheet-model.d.ts +12 -5
  79. package/dist/types/src/model/sheet-model.d.ts.map +1 -1
  80. package/dist/types/src/translations.d.ts +12 -3
  81. package/dist/types/src/translations.d.ts.map +1 -1
  82. package/dist/types/src/types.d.ts +33 -3
  83. package/dist/types/src/types.d.ts.map +1 -1
  84. package/package.json +37 -36
  85. package/src/SheetPlugin.tsx +27 -7
  86. package/src/components/FunctionEditor/FunctionEditor.tsx +1 -5
  87. package/src/components/GridSheet/GridSheet.tsx +107 -42
  88. package/src/components/GridSheet/util.ts +26 -16
  89. package/src/components/RangeList/RangeList.tsx +27 -12
  90. package/src/components/SheetContainer/SheetContainer.stories.tsx +4 -2
  91. package/src/components/SheetContainer/SheetContainer.tsx +19 -12
  92. package/src/components/Toolbar/Toolbar.tsx +72 -59
  93. package/src/compute-graph/compute-graph.ts +22 -7
  94. package/src/compute-graph/functions/async-function.ts +1 -0
  95. package/src/compute-graph/functions/edge-function.ts +5 -3
  96. package/src/defs/sheet-range-types.ts +8 -6
  97. package/src/defs/util.ts +1 -0
  98. package/src/integrations/thread-ranges.ts +3 -3
  99. package/src/meta.ts +1 -0
  100. package/src/model/sheet-model.test.ts +5 -3
  101. package/src/model/sheet-model.ts +93 -21
  102. package/src/translations.ts +12 -3
  103. package/src/types.ts +21 -1
  104. package/dist/lib/browser/SheetContainer-JBB7W52Y.mjs.map +0 -7
  105. package/dist/lib/browser/chunk-2ZK3YMZG.mjs.map +0 -7
  106. package/dist/lib/browser/chunk-3QWIMZZJ.mjs.map +0 -7
  107. package/dist/lib/browser/chunk-XXDHBYZQ.mjs.map +0 -7
  108. package/dist/lib/node/SheetContainer-L37HUFCF.cjs.map +0 -7
  109. package/dist/lib/node/chunk-7AWAC3R3.cjs.map +0 -7
  110. package/dist/lib/node/chunk-QNFMTK3X.cjs.map +0 -7
  111. package/dist/lib/node/chunk-V7E5JZBD.cjs.map +0 -7
  112. package/dist/lib/node-esm/SheetContainer-CLKFEM35.mjs.map +0 -7
  113. package/dist/lib/node-esm/chunk-3JYJEOCF.mjs.map +0 -7
  114. package/dist/lib/node-esm/chunk-JBTORSST.mjs.map +0 -7
  115. package/dist/lib/node-esm/chunk-SPQFLYC2.mjs.map +0 -7
  116. /package/dist/lib/browser/{compute-graph-ZQKB6QVP.mjs.map → compute-graph-GGWUX644.mjs.map} +0 -0
  117. /package/dist/lib/node-esm/{compute-graph-VKRG526N.mjs.map → compute-graph-2SCZT7N5.mjs.map} +0 -0
@@ -13,15 +13,18 @@ import React, {
13
13
  useState,
14
14
  } from 'react';
15
15
 
16
+ import { useIntentDispatcher } from '@dxos/app-framework';
16
17
  import { DropdownMenu, Icon, useTranslation } from '@dxos/react-ui';
17
18
  import { useAttention } from '@dxos/react-ui-attention';
18
19
  import {
19
20
  closestCell,
21
+ defaultSizeRow,
20
22
  editorKeys,
21
23
  Grid,
22
24
  GridCellEditor,
23
25
  type DxGridElement,
24
- type EditorKeysProps,
26
+ type EditorKeyHandler,
27
+ type EditorBlurHandler,
25
28
  type GridContentProps,
26
29
  type DxGridPosition,
27
30
  } from '@dxos/react-ui-grid';
@@ -31,6 +34,7 @@ import { DEFAULT_COLUMNS, DEFAULT_ROWS, rangeToA1Notation, type CellRange } from
31
34
  import { rangeExtension, sheetExtension, type RangeController } from '../../extensions';
32
35
  import { useSelectThreadOnCellFocus, useUpdateFocusedCellOnThreadSelection } from '../../integrations';
33
36
  import { SHEET_PLUGIN } from '../../meta';
37
+ import { SheetAction } from '../../types';
34
38
  import { useSheetContext } from '../SheetContext';
35
39
 
36
40
  const inertPosition: DxGridPosition = { plane: 'grid', col: 0, row: 0 };
@@ -52,14 +56,20 @@ const frozen = {
52
56
  frozenRowsStart: 1,
53
57
  };
54
58
 
55
- const sheetRowDefault = { frozenRowsStart: { size: 32, readonly: true }, grid: { size: 32, resizeable: true } };
59
+ const sheetRowDefault = {
60
+ frozenRowsStart: { size: defaultSizeRow, readonly: true },
61
+ grid: { size: defaultSizeRow, resizeable: true },
62
+ };
56
63
  const sheetColDefault = { frozenColsStart: { size: 48, readonly: true }, grid: { size: 180, resizeable: true } };
57
64
 
58
65
  export const GridSheet = () => {
59
66
  const { t } = useTranslation(SHEET_PLUGIN);
60
- const { id, model, editing, setEditing, setCursor, setRange, cursor, cursorFallbackRange, activeRefs } =
61
- useSheetContext();
62
- const dxGrid = useRef<DxGridElement | null>(null);
67
+ const { id, model, editing, setCursor, setRange, cursor, cursorFallbackRange, activeRefs } = useSheetContext();
68
+ // NOTE(thure): using `useState` instead of `useRef` works with refs provided by `@lit/react` and gives us
69
+ // a reliable dependency for `useEffect` whereas `useLayoutEffect` does not guarantee the element will be defined.
70
+ const [dxGrid, setDxGrid] = useState<DxGridElement | null>(null);
71
+ const [extraplanarFocus, setExtraplanarFocus] = useState<DxGridPosition | null>(null);
72
+ const dispatch = useIntentDispatcher();
63
73
  const rangeController = useRef<RangeController>();
64
74
  const { hasAttention } = useAttention(id);
65
75
 
@@ -67,8 +77,15 @@ export const GridSheet = () => {
67
77
  (event: FocusEvent) => {
68
78
  if (!editing) {
69
79
  const cell = closestCell(event.target);
70
- if (cell && cell.plane === 'grid') {
71
- setCursor({ col: cell.col, row: cell.row });
80
+ if (cell) {
81
+ if (cell.plane === 'grid') {
82
+ setCursor({ col: cell.col, row: cell.row });
83
+ setExtraplanarFocus(null);
84
+ } else {
85
+ setExtraplanarFocus(cell);
86
+ }
87
+ } else {
88
+ setExtraplanarFocus(null);
72
89
  }
73
90
  }
74
91
  },
@@ -76,29 +93,27 @@ export const GridSheet = () => {
76
93
  );
77
94
 
78
95
  // TODO(burdon): Validate formula before closing: hf.validateFormula();
79
- const handleClose = useCallback<NonNullable<EditorKeysProps['onClose']> | NonNullable<EditorKeysProps['onNav']>>(
80
- (value, { key, shift }) => {
81
- if (value !== undefined) {
82
- model.setValue(dxGridCellIndexToSheetCellAddress(editing!.index), value);
96
+ const handleClose = useCallback<EditorKeyHandler>(
97
+ (_value, event) => {
98
+ if (event) {
99
+ const { key, shift } = event;
100
+ const axis = ['Enter', 'ArrowUp', 'ArrowDown'].includes(key)
101
+ ? 'row'
102
+ : ['Tab', 'ArrowLeft', 'ArrowRight'].includes(key)
103
+ ? 'col'
104
+ : undefined;
105
+ const delta = key.startsWith('Arrow') ? (['ArrowUp', 'ArrowLeft'].includes(key) ? -1 : 1) : shift ? -1 : 1;
106
+ dxGrid?.refocus(axis, delta);
83
107
  }
84
- setEditing(null);
85
- const axis = ['Enter', 'ArrowUp', 'ArrowDown'].includes(key)
86
- ? 'row'
87
- : ['Tab', 'ArrowLeft', 'ArrowRight'].includes(key)
88
- ? 'col'
89
- : undefined;
90
- const delta = key.startsWith('Arrow') ? (['ArrowUp', 'ArrowLeft'].includes(key) ? -1 : 1) : shift ? -1 : 1;
91
- dxGrid.current?.refocus(axis, delta);
92
108
  },
93
- [model, editing],
109
+ [model, editing, dxGrid],
94
110
  );
95
111
 
96
- const handleBlur = useCallback(
97
- (value?: string) => {
112
+ const handleBlur = useCallback<EditorBlurHandler>(
113
+ (value) => {
98
114
  if (value !== undefined) {
99
115
  model.setValue(dxGridCellIndexToSheetCellAddress(editing!.index), value);
100
116
  }
101
- setEditing(null);
102
117
  },
103
118
  [model, editing],
104
119
  );
@@ -143,6 +158,34 @@ export const GridSheet = () => {
143
158
  [hasAttention],
144
159
  );
145
160
 
161
+ const selectEntireAxis = useCallback(
162
+ (pos: DxGridPosition) => {
163
+ switch (pos.plane) {
164
+ case 'frozenRowsStart':
165
+ return dxGrid?.setSelection({
166
+ start: { col: pos.col, row: 0, plane: 'grid' },
167
+ end: { col: pos.col, row: model.sheet.rows.length - 1, plane: 'grid' },
168
+ });
169
+ case 'frozenColsStart':
170
+ return dxGrid?.setSelection({
171
+ start: { row: pos.row, col: 0, plane: 'grid' },
172
+ end: { row: pos.row, col: model.sheet.columns.length - 1, plane: 'grid' },
173
+ });
174
+ }
175
+ },
176
+ [dxGrid, model.sheet],
177
+ );
178
+
179
+ const handleClick = useCallback(
180
+ (event: MouseEvent) => {
181
+ const cell = closestCell(event.target);
182
+ if (cell) {
183
+ selectEntireAxis(cell);
184
+ }
185
+ },
186
+ [selectEntireAxis],
187
+ );
188
+
146
189
  const handleKeyDown = useCallback(
147
190
  (event: KeyboardEvent) => {
148
191
  switch (event.key) {
@@ -150,6 +193,16 @@ export const GridSheet = () => {
150
193
  case 'Delete':
151
194
  event.preventDefault();
152
195
  return cursorFallbackRange && model.clear(cursorFallbackRange);
196
+ case 'Enter':
197
+ case 'Space':
198
+ if (dxGrid && extraplanarFocus) {
199
+ switch (extraplanarFocus.plane) {
200
+ case 'frozenRowsStart':
201
+ case 'frozenColsStart':
202
+ event.preventDefault();
203
+ return selectEntireAxis(extraplanarFocus);
204
+ }
205
+ }
153
206
  }
154
207
  if (event.metaKey || event.ctrlKey) {
155
208
  switch (event.key) {
@@ -175,7 +228,7 @@ export const GridSheet = () => {
175
228
  }
176
229
  }
177
230
  },
178
- [cursorFallbackRange, model, cursor],
231
+ [cursorFallbackRange, model, cursor, extraplanarFocus, selectEntireAxis],
179
232
  );
180
233
 
181
234
  const contextMenuAnchorRef = useRef<HTMLButtonElement | null>(null);
@@ -192,20 +245,31 @@ export const GridSheet = () => {
192
245
  }, []);
193
246
 
194
247
  const handleAxisMenuAction = useCallback(
195
- (operation: 'add-before' | 'add-after' | 'remove') => {
248
+ (operation: 'insert-before' | 'insert-after' | 'drop') => {
196
249
  switch (operation) {
197
- case 'add-before':
198
- case 'add-after':
199
- model[contextMenuAxis === 'col' ? 'insertColumns' : 'insertRows'](
200
- contextMenuOpen![contextMenuAxis] + (operation === 'add-before' ? 0 : 1),
201
- 1,
202
- );
250
+ case 'insert-before':
251
+ case 'insert-after':
252
+ return dispatch({
253
+ action: SheetAction.INSERT_AXIS,
254
+ data: {
255
+ model,
256
+ axis: contextMenuAxis,
257
+ index: contextMenuOpen![contextMenuAxis] + (operation === 'insert-before' ? 0 : 1),
258
+ } satisfies SheetAction.InsertAxis,
259
+ });
203
260
  break;
204
- case 'remove':
205
- // console.warn('[model does not implement]');
261
+ case 'drop':
262
+ return dispatch({
263
+ action: SheetAction.DROP_AXIS,
264
+ data: {
265
+ model,
266
+ axis: contextMenuAxis,
267
+ axisIndex: model.sheet[contextMenuAxis === 'row' ? 'rows' : 'columns'][contextMenuOpen![contextMenuAxis]],
268
+ } satisfies SheetAction.DropAxis,
269
+ });
206
270
  }
207
271
  },
208
- [contextMenuAxis, contextMenuOpen, model],
272
+ [contextMenuAxis, contextMenuOpen, model, dispatch],
209
273
  );
210
274
 
211
275
  const { columns, rows } = useSheetModelDxGridProps(dxGrid, model);
@@ -217,9 +281,9 @@ export const GridSheet = () => {
217
281
  rangeExtension({
218
282
  onInit: (fn) => (rangeController.current = fn),
219
283
  onStateChange: (state) => {
220
- if (dxGrid.current) {
284
+ if (dxGrid) {
221
285
  // This can’t dispatch a setState in this component, otherwise the cell editor remounts and loses focus.
222
- dxGrid.current.mode = typeof state.activeRange === 'undefined' ? 'edit' : 'edit-select';
286
+ dxGrid.mode = typeof state.activeRange === 'undefined' ? 'edit' : 'edit-select';
223
287
  }
224
288
  },
225
289
  }),
@@ -256,10 +320,11 @@ export const GridSheet = () => {
256
320
  onWheelCapture={handleWheel}
257
321
  onKeyDown={handleKeyDown}
258
322
  onContextMenu={handleContextMenu}
259
- overscroll='inline'
260
- className='[--dx-grid-base:var(--surface-bg)]'
323
+ onClick={handleClick}
324
+ overscroll='trap'
325
+ className='[--dx-grid-base:var(--surface-bg)] [&_.dx-grid]:border-bs [&_.dx-grid]:border-separator'
261
326
  activeRefs={activeRefs}
262
- ref={dxGrid}
327
+ ref={setDxGrid}
263
328
  />
264
329
  <DropdownMenu.Root
265
330
  modal={false}
@@ -269,21 +334,21 @@ export const GridSheet = () => {
269
334
  <DropdownMenu.VirtualTrigger virtualRef={contextMenuAnchorRef} />
270
335
  <DropdownMenu.Content side={contextMenuAxis === 'col' ? 'bottom' : 'right'} sideOffset={4} collisionPadding={8}>
271
336
  <DropdownMenu.Viewport>
272
- <DropdownMenu.Item onClick={() => handleAxisMenuAction('add-before')}>
337
+ <DropdownMenu.Item onClick={() => handleAxisMenuAction('insert-before')}>
273
338
  <Icon
274
339
  size={5}
275
340
  icon={contextMenuAxis === 'col' ? 'ph--columns-plus-left--regular' : 'ph--rows-plus-top--regular'}
276
341
  />
277
342
  <span>{t(`add ${contextMenuAxis} before label`)}</span>
278
343
  </DropdownMenu.Item>
279
- <DropdownMenu.Item onClick={() => handleAxisMenuAction('add-after')}>
344
+ <DropdownMenu.Item onClick={() => handleAxisMenuAction('insert-after')}>
280
345
  <Icon
281
346
  size={5}
282
347
  icon={contextMenuAxis === 'col' ? 'ph--columns-plus-right--regular' : 'ph--rows-plus-bottom--regular'}
283
348
  />
284
349
  <span>{t(`add ${contextMenuAxis} after label`)}</span>
285
350
  </DropdownMenu.Item>
286
- <DropdownMenu.Item disabled onClick={() => handleAxisMenuAction('remove')}>
351
+ <DropdownMenu.Item onClick={() => handleAxisMenuAction('drop')}>
287
352
  <Icon size={5} icon='ph--backspace--regular' />
288
353
  <span>{t(`delete ${contextMenuAxis} label`)}</span>
289
354
  </DropdownMenu.Item>
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { type MutableRefObject, useEffect, useLayoutEffect, useState } from 'react';
5
+ import { useEffect, useState } from 'react';
6
6
 
7
7
  import { createDocAccessor, fullyQualifiedId } from '@dxos/react-client/echo';
8
8
  import { parseValue, cellClassesForFieldType } from '@dxos/react-ui-data';
@@ -67,16 +67,18 @@ const projectCellProps = (model: SheetModel, col: number, row: number): DxGridCe
67
67
  })
68
68
  .map((thread) => fullyQualifiedId(thread!))
69
69
  .join(' ');
70
- const type = model.getValueType(address);
70
+
71
+ const description = model.getValueDescription(address);
72
+ const type = description?.type;
73
+ const format = description?.format;
71
74
  const classNames = ranges?.map(cellClassNameForRange).reverse();
72
75
 
73
76
  return {
74
- value: parseValue(type, rawValue),
75
- className: mx(cellClassesForFieldType(type), threadRefs && commentedClassName, classNames),
77
+ value: parseValue({ type, format, value: rawValue }),
78
+ className: mx(cellClassesForFieldType({ type, format }), threadRefs && commentedClassName, classNames),
76
79
  dataRefs: threadRefs,
77
80
  };
78
81
  };
79
-
80
82
  const gridCellGetter = (model: SheetModel) => {
81
83
  // TODO(thure): Actually use the cache.
82
84
  const cachedGridCells: DxGridPlaneCells = {};
@@ -94,11 +96,15 @@ const gridCellGetter = (model: SheetModel) => {
94
96
 
95
97
  export const rowLabelCell = (row: number) => ({
96
98
  value: rowToA1Notation(row),
97
- className: 'text-end !pie-1',
99
+ className: 'text-end !pie-1 text-subdued',
98
100
  resizeHandle: 'row',
99
101
  });
100
102
 
101
- export const colLabelCell = (col: number) => ({ value: colToA1Notation(col), resizeHandle: 'col' });
103
+ export const colLabelCell = (col: number) => ({
104
+ value: colToA1Notation(col),
105
+ className: 'text-subdued',
106
+ resizeHandle: 'col',
107
+ });
102
108
 
103
109
  const cellGetter = (model: SheetModel) => {
104
110
  const getGridCells = gridCellGetter(model);
@@ -125,23 +131,27 @@ const cellGetter = (model: SheetModel) => {
125
131
  };
126
132
 
127
133
  export const useSheetModelDxGridProps = (
128
- dxGridRef: MutableRefObject<DxGridElement | null>,
134
+ dxGrid: DxGridElement | null,
129
135
  model: SheetModel,
130
136
  ): Pick<GridContentProps, 'columns' | 'rows'> => {
131
137
  const [columns, setColumns] = useState<DxGridAxisMeta>(createDxGridColumns(model));
132
- const [rows, setRows] = useState<DxGridAxisMeta>(createDxGridColumns(model));
138
+ const [rows, setRows] = useState<DxGridAxisMeta>(createDxGridRows(model));
133
139
 
134
- useLayoutEffect(() => {
140
+ useEffect(() => {
135
141
  const cellsAccessor = createDocAccessor(model.sheet, ['cells']);
136
- if (dxGridRef.current) {
137
- dxGridRef.current.getCells = cellGetter(model);
142
+ if (dxGrid) {
143
+ dxGrid.getCells = cellGetter(model);
138
144
  }
139
145
  const handleCellsUpdate = () => {
140
- dxGridRef.current?.requestUpdate('initialCells');
146
+ dxGrid?.requestUpdate('initialCells');
141
147
  };
142
148
  cellsAccessor.handle.addListener('change', handleCellsUpdate);
143
- return () => cellsAccessor.handle.removeListener('change', handleCellsUpdate);
144
- }, [model]);
149
+ const unsubscribe = model.graph.update.on(handleCellsUpdate);
150
+ return () => {
151
+ cellsAccessor.handle.removeListener('change', handleCellsUpdate);
152
+ unsubscribe();
153
+ };
154
+ }, [model, dxGrid]);
145
155
 
146
156
  useEffect(() => {
147
157
  const columnMetaAccessor = createDocAccessor(model.sheet, ['columnMeta']);
@@ -158,7 +168,7 @@ export const useSheetModelDxGridProps = (
158
168
  columnMetaAccessor.handle.removeListener('change', handleColumnMetaUpdate);
159
169
  rowMetaAccessor.handle.removeListener('change', handleRowMetaUpdate);
160
170
  };
161
- }, [model]);
171
+ }, [model, dxGrid]);
162
172
 
163
173
  return { columns, rows };
164
174
  };
@@ -2,37 +2,52 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import React from 'react';
5
+ import React, { useCallback } from 'react';
6
6
 
7
7
  import { S } from '@dxos/echo-schema';
8
+ import { useTranslation } from '@dxos/react-ui';
8
9
  import { List } from '@dxos/react-ui-list';
9
10
  import { ghostHover } from '@dxos/react-ui-theme';
10
11
 
11
12
  import { rangeFromIndex, rangeToA1Notation } from '../../defs';
13
+ import { SHEET_PLUGIN } from '../../meta';
12
14
  import { Range, type SheetType } from '../../types';
13
15
 
14
16
  export type RangeListProps = {
15
17
  sheet: SheetType;
16
- onSelect?: (range: Range) => void;
17
- onDelete?: (range: Range) => void;
18
18
  };
19
19
 
20
- export const RangeList = ({ sheet, onSelect, onDelete }: RangeListProps) => {
20
+ export const RangeList = ({ sheet }: RangeListProps) => {
21
+ const { t } = useTranslation(SHEET_PLUGIN);
22
+ // TODO(thure): Implement similar to comments, #8121
23
+ const handleSelectRange = (range: Range) => {};
24
+ const handleDeleteRange = useCallback(
25
+ (range: Range) => {
26
+ const index = sheet.ranges.findIndex((sheetRange) => sheetRange === range);
27
+ sheet.ranges.splice(index, 1);
28
+ },
29
+ [sheet],
30
+ );
21
31
  return (
22
- <div className='flex flex-col'>
32
+ <>
33
+ <h2 className='p-2 text-sm font-semibold'>{t('range list heading')}</h2>
23
34
  <List.Root<Range> items={sheet.ranges} isItem={S.is(Range)}>
24
- {({ items }) =>
25
- items.map((item, i) => (
26
- <List.Item key={i} item={item} classNames={['p-2', ghostHover]}>
35
+ {({ items: ranges }) =>
36
+ ranges.map((range, i) => (
37
+ <List.Item key={i} item={range} classNames={['p-2', ghostHover]}>
27
38
  <List.ItemDragHandle />
28
- <List.ItemTitle onClick={() => onSelect?.(item)}>
29
- {rangeToA1Notation(rangeFromIndex(sheet, item.range))}
39
+ <List.ItemTitle onClick={() => handleSelectRange(range)}>
40
+ {t('range title', {
41
+ position: rangeToA1Notation(rangeFromIndex(sheet, range.range)),
42
+ key: t(`range key ${range.key} label`),
43
+ value: t(`range value ${range.value} label`),
44
+ })}
30
45
  </List.ItemTitle>
31
- {onDelete && <List.ItemDeleteButton onClick={() => onDelete(item)} />}
46
+ <List.ItemDeleteButton onClick={() => handleDeleteRange(range)} />
32
47
  </List.Item>
33
48
  ))
34
49
  }
35
50
  </List.Root>
36
- </div>
51
+ </>
37
52
  );
38
53
  };
@@ -11,6 +11,7 @@ import { withTheme, withLayout } from '@dxos/storybook-utils';
11
11
 
12
12
  import { SheetContainer } from './SheetContainer';
13
13
  import { createTestCells, useTestSheet, withComputeGraphDecorator } from '../../testing';
14
+ import translations from '../../translations';
14
15
  import { SheetType } from '../../types';
15
16
  import { useComputeGraph } from '../ComputeGraph';
16
17
 
@@ -18,11 +19,11 @@ export const Basic = () => {
18
19
  const space = useSpace();
19
20
  const graph = useComputeGraph(space);
20
21
  const sheet = useTestSheet(space, graph, { cells: createTestCells() });
21
- if (!sheet || !graph) {
22
+ if (!sheet || !space) {
22
23
  return null;
23
24
  }
24
25
 
25
- return <SheetContainer graph={graph} sheet={sheet} role='article' />;
26
+ return <SheetContainer space={space} sheet={sheet} role='article' />;
26
27
  };
27
28
 
28
29
  const meta: Meta = {
@@ -38,6 +39,7 @@ const meta: Meta = {
38
39
  classNames: 'grid grid-cols-1 grid-rows-[min-content_minmax(0,1fr)_min-content]',
39
40
  }),
40
41
  ],
42
+ parameters: { translations },
41
43
  };
42
44
 
43
45
  export default meta;
@@ -4,24 +4,31 @@
4
4
 
5
5
  import React from 'react';
6
6
 
7
+ import { type Space } from '@dxos/react-client/echo';
8
+ import { StackItem } from '@dxos/react-ui-stack';
9
+
10
+ import { type SheetType } from '../../types';
11
+ import { useComputeGraph } from '../ComputeGraph';
7
12
  import { FunctionEditor } from '../FunctionEditor';
8
13
  import { GridSheet } from '../GridSheet';
9
- import { SheetProvider, type SheetProviderProps } from '../SheetContext';
14
+ import { SheetProvider } from '../SheetContext';
10
15
  import { Toolbar } from '../Toolbar';
11
16
 
12
- export const SheetContainer = ({ graph, sheet, role }: SheetProviderProps & { role?: string }) => {
13
- return (
17
+ export const SheetContainer = ({ space, sheet, role }: { space: Space; sheet: SheetType; role?: string }) => {
18
+ const graph = useComputeGraph(space);
19
+
20
+ return graph ? (
14
21
  <SheetProvider sheet={sheet} graph={graph}>
15
- <Toolbar.Root role={role}>
16
- <Toolbar.Styles />
17
- <Toolbar.Alignment />
18
- <Toolbar.Separator />
19
- <Toolbar.Actions />
20
- </Toolbar.Root>
21
- <div role='none' className='border-bs border-separator grid cols-1 rows-[1fr_min-content] min-bs-0'>
22
+ <StackItem.Content toolbar statusbar classNames='border-bs border-separator'>
23
+ <Toolbar.Root role={role}>
24
+ <Toolbar.Styles />
25
+ <Toolbar.Alignment />
26
+ <Toolbar.Separator />
27
+ <Toolbar.Actions />
28
+ </Toolbar.Root>
22
29
  <GridSheet />
23
30
  <FunctionEditor />
24
- </div>
31
+ </StackItem.Content>
25
32
  </SheetProvider>
26
- );
33
+ ) : null;
27
34
  };