@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.
- package/dist/lib/browser/SheetContainer-NDNIS44E.mjs +265 -0
- package/dist/lib/browser/SheetContainer-NDNIS44E.mjs.map +7 -0
- package/dist/lib/browser/chunk-AQSGDA4X.mjs +1614 -0
- package/dist/lib/browser/chunk-AQSGDA4X.mjs.map +7 -0
- package/dist/lib/browser/{chunk-QILRZNE5.mjs → chunk-D3QTX46O.mjs} +4 -5
- package/dist/lib/browser/chunk-D3QTX46O.mjs.map +7 -0
- package/dist/lib/browser/{chunk-WZMOZKQZ.mjs → chunk-GKI67SEF.mjs} +19 -25
- package/dist/lib/browser/chunk-GKI67SEF.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +14 -19
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/meta.mjs +1 -1
- package/dist/lib/browser/types.mjs +4 -8
- package/dist/lib/node/SheetContainer-YSQGJD7K.cjs +276 -0
- package/dist/lib/node/SheetContainer-YSQGJD7K.cjs.map +7 -0
- package/dist/lib/node/chunk-6F43RV45.cjs +1610 -0
- package/dist/lib/node/chunk-6F43RV45.cjs.map +7 -0
- package/dist/lib/node/{chunk-AOP42UAA.cjs → chunk-ER3PM7GD.cjs} +25 -33
- package/dist/lib/node/chunk-ER3PM7GD.cjs.map +7 -0
- package/dist/lib/node/{chunk-BNARJ5GM.cjs → chunk-QIFIGEKV.cjs} +6 -7
- package/dist/lib/node/chunk-QIFIGEKV.cjs.map +7 -0
- package/dist/lib/node/index.cjs +36 -40
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.cjs +3 -3
- package/dist/lib/node/meta.cjs.map +1 -1
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/types.cjs +8 -12
- package/dist/lib/node/types.cjs.map +2 -2
- package/dist/lib/node-esm/SheetContainer-M7WRMZDU.mjs +266 -0
- package/dist/lib/node-esm/SheetContainer-M7WRMZDU.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-ELTFPX5B.mjs +1615 -0
- package/dist/lib/node-esm/chunk-ELTFPX5B.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-IU2L277A.mjs → chunk-VCYJWE3O.mjs} +4 -5
- package/dist/lib/node-esm/chunk-VCYJWE3O.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-RR2AO4SM.mjs → chunk-ZVLLQ2PJ.mjs} +19 -25
- package/dist/lib/node-esm/chunk-ZVLLQ2PJ.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +14 -19
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/meta.mjs +1 -1
- package/dist/lib/node-esm/types.mjs +4 -8
- package/dist/types/src/SheetPlugin.d.ts.map +1 -1
- package/dist/types/src/components/FunctionEditor/FunctionEditor.d.ts +3 -0
- package/dist/types/src/components/FunctionEditor/FunctionEditor.d.ts.map +1 -0
- package/dist/types/src/components/FunctionEditor/index.d.ts +2 -0
- package/dist/types/src/components/FunctionEditor/index.d.ts.map +1 -0
- package/dist/types/src/components/GridSheet/GridSheet.d.ts +1 -8
- package/dist/types/src/components/GridSheet/GridSheet.d.ts.map +1 -1
- package/dist/types/src/components/GridSheet/GridSheet.stories.d.ts +1 -1
- package/dist/types/src/components/GridSheet/GridSheet.stories.d.ts.map +1 -1
- package/dist/types/src/components/{CellEditor/CellEditor.stories.d.ts → GridSheet/SheetCellEditor.stories.d.ts} +2 -2
- package/dist/types/src/components/GridSheet/SheetCellEditor.stories.d.ts.map +1 -0
- package/dist/types/src/components/GridSheet/index.d.ts +2 -0
- package/dist/types/src/components/GridSheet/index.d.ts.map +1 -0
- package/dist/types/src/components/GridSheet/util.d.ts +11 -2
- package/dist/types/src/components/GridSheet/util.d.ts.map +1 -1
- package/dist/types/src/components/SheetContainer/SheetContainer.d.ts +6 -0
- package/dist/types/src/components/SheetContainer/SheetContainer.d.ts.map +1 -0
- package/dist/types/src/components/SheetContainer/SheetContainer.stories.d.ts +11 -0
- package/dist/types/src/components/SheetContainer/SheetContainer.stories.d.ts.map +1 -0
- package/dist/types/src/components/SheetContainer/index.d.ts +3 -0
- package/dist/types/src/components/SheetContainer/index.d.ts.map +1 -0
- package/dist/types/src/components/SheetContext/SheetContext.d.ts +27 -0
- package/dist/types/src/components/SheetContext/SheetContext.d.ts.map +1 -0
- package/dist/types/src/components/SheetContext/index.d.ts +2 -0
- package/dist/types/src/components/SheetContext/index.d.ts.map +1 -0
- package/dist/types/src/components/Toolbar/Toolbar.d.ts +31 -17
- package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +1 -1
- package/dist/types/src/components/index.d.ts +3 -2
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/extensions/editor/extension.d.ts.map +1 -0
- package/dist/types/src/extensions/editor/extension.test.d.ts.map +1 -0
- package/dist/types/src/extensions/editor/index.d.ts +2 -0
- package/dist/types/src/extensions/editor/index.d.ts.map +1 -0
- package/dist/types/src/extensions/index.d.ts +1 -0
- package/dist/types/src/extensions/index.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +1 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/threads.d.ts +8 -0
- package/dist/types/src/hooks/threads.d.ts.map +1 -0
- package/dist/types/src/meta.d.ts +3 -6
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/{components/Sheet → model}/decorations.d.ts +1 -0
- package/dist/types/src/model/decorations.d.ts.map +1 -0
- package/dist/types/src/model/formatting-model.d.ts +3 -0
- package/dist/types/src/model/formatting-model.d.ts.map +1 -1
- package/dist/types/src/model/index.d.ts +1 -0
- package/dist/types/src/model/index.d.ts.map +1 -1
- package/dist/types/src/model/sheet-model.d.ts +3 -2
- package/dist/types/src/model/sheet-model.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +13 -28
- package/dist/types/src/types.d.ts.map +1 -1
- package/package.json +36 -39
- package/src/SheetPlugin.tsx +3 -2
- package/src/components/FunctionEditor/FunctionEditor.tsx +45 -0
- package/src/components/FunctionEditor/index.ts +5 -0
- package/src/components/GridSheet/GridSheet.stories.tsx +7 -2
- package/src/components/GridSheet/GridSheet.tsx +77 -69
- package/src/components/{CellEditor/CellEditor.stories.tsx → GridSheet/SheetCellEditor.stories.tsx} +2 -2
- package/src/components/{Sheet → GridSheet}/index.ts +1 -1
- package/src/components/GridSheet/util.ts +63 -27
- package/src/components/SheetContainer/SheetContainer.stories.tsx +40 -0
- package/src/components/SheetContainer/SheetContainer.tsx +52 -0
- package/src/components/SheetContainer/index.ts +7 -0
- package/src/components/{Sheet/sheet-context.tsx → SheetContext/SheetContext.tsx} +47 -27
- package/src/components/SheetContext/index.ts +5 -0
- package/src/components/Toolbar/Toolbar.tsx +127 -86
- package/src/components/index.ts +2 -1
- package/src/defs/util.ts +1 -1
- package/src/extensions/compute.stories.tsx +4 -4
- package/src/{components/CellEditor → extensions/editor}/index.ts +0 -1
- package/src/extensions/index.ts +1 -0
- package/src/hooks/index.ts +1 -0
- package/src/{components/Sheet/threads.tsx → hooks/threads.ts} +26 -84
- package/src/{meta.tsx → meta.ts} +3 -3
- package/src/{components/Sheet → model}/decorations.ts +2 -0
- package/src/model/formatting-model.ts +12 -9
- package/src/model/index.ts +1 -0
- package/src/model/sheet-model.test.ts +1 -3
- package/src/model/sheet-model.ts +13 -11
- package/src/types.ts +9 -35
- package/dist/lib/browser/SheetContainer-LG77O4RM.mjs +0 -262
- package/dist/lib/browser/SheetContainer-LG77O4RM.mjs.map +0 -7
- package/dist/lib/browser/chunk-CHQAW4F4.mjs +0 -2705
- package/dist/lib/browser/chunk-CHQAW4F4.mjs.map +0 -7
- package/dist/lib/browser/chunk-QILRZNE5.mjs.map +0 -7
- package/dist/lib/browser/chunk-WZMOZKQZ.mjs.map +0 -7
- package/dist/lib/node/SheetContainer-OZ7DHH4L.cjs +0 -280
- package/dist/lib/node/SheetContainer-OZ7DHH4L.cjs.map +0 -7
- package/dist/lib/node/chunk-5FTFZL5W.cjs +0 -2690
- package/dist/lib/node/chunk-5FTFZL5W.cjs.map +0 -7
- package/dist/lib/node/chunk-AOP42UAA.cjs.map +0 -7
- package/dist/lib/node/chunk-BNARJ5GM.cjs.map +0 -7
- package/dist/lib/node-esm/SheetContainer-4XS2G25Z.mjs +0 -263
- package/dist/lib/node-esm/SheetContainer-4XS2G25Z.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-IU2L277A.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-KK3XL37M.mjs +0 -2706
- package/dist/lib/node-esm/chunk-KK3XL37M.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-RR2AO4SM.mjs.map +0 -7
- package/dist/types/src/components/CellEditor/CellEditor.d.ts +0 -34
- package/dist/types/src/components/CellEditor/CellEditor.d.ts.map +0 -1
- package/dist/types/src/components/CellEditor/CellEditor.stories.d.ts.map +0 -1
- package/dist/types/src/components/CellEditor/extension.d.ts.map +0 -1
- package/dist/types/src/components/CellEditor/extension.test.d.ts.map +0 -1
- package/dist/types/src/components/CellEditor/index.d.ts +0 -3
- package/dist/types/src/components/CellEditor/index.d.ts.map +0 -1
- package/dist/types/src/components/Sheet/Sheet.d.ts +0 -55
- package/dist/types/src/components/Sheet/Sheet.d.ts.map +0 -1
- package/dist/types/src/components/Sheet/Sheet.stories.d.ts +0 -53
- package/dist/types/src/components/Sheet/Sheet.stories.d.ts.map +0 -1
- package/dist/types/src/components/Sheet/decorations.d.ts.map +0 -1
- package/dist/types/src/components/Sheet/grid.d.ts +0 -52
- package/dist/types/src/components/Sheet/grid.d.ts.map +0 -1
- package/dist/types/src/components/Sheet/index.d.ts +0 -2
- package/dist/types/src/components/Sheet/index.d.ts.map +0 -1
- package/dist/types/src/components/Sheet/nav.d.ts +0 -29
- package/dist/types/src/components/Sheet/nav.d.ts.map +0 -1
- package/dist/types/src/components/Sheet/sheet-context.d.ts +0 -26
- package/dist/types/src/components/Sheet/sheet-context.d.ts.map +0 -1
- package/dist/types/src/components/Sheet/threads.d.ts +0 -2
- package/dist/types/src/components/Sheet/threads.d.ts.map +0 -1
- package/dist/types/src/components/Sheet/util.d.ts +0 -18
- package/dist/types/src/components/Sheet/util.d.ts.map +0 -1
- package/dist/types/src/components/SheetContainer.d.ts +0 -8
- package/dist/types/src/components/SheetContainer.d.ts.map +0 -1
- package/dist/types/src/components/Toolbar/common.d.ts +0 -20
- package/dist/types/src/components/Toolbar/common.d.ts.map +0 -1
- package/src/components/CellEditor/CellEditor.tsx +0 -163
- package/src/components/Sheet/Sheet.stories.tsx +0 -251
- package/src/components/Sheet/Sheet.tsx +0 -1215
- package/src/components/Sheet/grid.ts +0 -191
- package/src/components/Sheet/nav.ts +0 -157
- package/src/components/Sheet/util.ts +0 -56
- package/src/components/SheetContainer.tsx +0 -86
- package/src/components/Toolbar/common.tsx +0 -72
- /package/dist/types/src/{components/CellEditor → extensions/editor}/extension.d.ts +0 -0
- /package/dist/types/src/{components/CellEditor → extensions/editor}/extension.test.d.ts +0 -0
- /package/src/{components/CellEditor → extensions/editor}/extension.test.ts +0 -0
- /package/src/{components/CellEditor → extensions/editor}/extension.ts +0 -0
|
@@ -1,1215 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2024 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
type Active,
|
|
7
|
-
DndContext,
|
|
8
|
-
type DragEndEvent,
|
|
9
|
-
DragOverlay,
|
|
10
|
-
type DragStartEvent,
|
|
11
|
-
KeyboardSensor,
|
|
12
|
-
type Modifier,
|
|
13
|
-
MouseSensor,
|
|
14
|
-
type PointerActivationConstraint,
|
|
15
|
-
TouchSensor,
|
|
16
|
-
useDraggable,
|
|
17
|
-
useDroppable,
|
|
18
|
-
useSensor,
|
|
19
|
-
useSensors,
|
|
20
|
-
} from '@dnd-kit/core';
|
|
21
|
-
import { restrictToHorizontalAxis, restrictToVerticalAxis } from '@dnd-kit/modifiers';
|
|
22
|
-
import { getEventCoordinates, useCombinedRefs } from '@dnd-kit/utilities';
|
|
23
|
-
import { Function as FunctionIcon } from '@phosphor-icons/react';
|
|
24
|
-
import { Resizable, type ResizeCallback, type ResizeStartCallback } from 're-resizable';
|
|
25
|
-
import React, {
|
|
26
|
-
type CSSProperties,
|
|
27
|
-
type DOMAttributes,
|
|
28
|
-
type KeyboardEventHandler,
|
|
29
|
-
type PropsWithChildren,
|
|
30
|
-
forwardRef,
|
|
31
|
-
useEffect,
|
|
32
|
-
useImperativeHandle,
|
|
33
|
-
useMemo,
|
|
34
|
-
useRef,
|
|
35
|
-
useState,
|
|
36
|
-
} from 'react';
|
|
37
|
-
import { createPortal } from 'react-dom';
|
|
38
|
-
import { useResizeDetector } from 'react-resize-detector';
|
|
39
|
-
|
|
40
|
-
import { debounce } from '@dxos/async';
|
|
41
|
-
import { fullyQualifiedId, createDocAccessor } from '@dxos/client/echo';
|
|
42
|
-
import { log } from '@dxos/log';
|
|
43
|
-
import { type ThemedClassName } from '@dxos/react-ui';
|
|
44
|
-
import { ATTENABLE_ATTRIBUTE, useAttendableAttributes, useAttention, useAttentionPath } from '@dxos/react-ui-attention';
|
|
45
|
-
import { mx } from '@dxos/react-ui-theme';
|
|
46
|
-
|
|
47
|
-
import {
|
|
48
|
-
type GridLayoutProps,
|
|
49
|
-
type SizeMap,
|
|
50
|
-
CELL_DATA_KEY,
|
|
51
|
-
axisHeight,
|
|
52
|
-
axisWidth,
|
|
53
|
-
defaultHeight,
|
|
54
|
-
defaultWidth,
|
|
55
|
-
maxWidth,
|
|
56
|
-
maxHeight,
|
|
57
|
-
minWidth,
|
|
58
|
-
minHeight,
|
|
59
|
-
getCellElement,
|
|
60
|
-
useGridLayout,
|
|
61
|
-
} from './grid';
|
|
62
|
-
import { type GridSize, handleArrowNav, handleNav, useRangeSelect } from './nav';
|
|
63
|
-
import { type SheetContextProps, SheetContextProvider, useSheetContext } from './sheet-context';
|
|
64
|
-
import { useThreads } from './threads';
|
|
65
|
-
import { getRectUnion, getRelativeClientRect, scrollIntoView } from './util';
|
|
66
|
-
import {
|
|
67
|
-
type CellIndex,
|
|
68
|
-
type CellAddress,
|
|
69
|
-
addressToA1Notation,
|
|
70
|
-
columnLetter,
|
|
71
|
-
posEquals,
|
|
72
|
-
rangeToA1Notation,
|
|
73
|
-
addressToIndex,
|
|
74
|
-
addressFromIndex,
|
|
75
|
-
} from '../../defs';
|
|
76
|
-
import {
|
|
77
|
-
CellEditor,
|
|
78
|
-
type CellRangeNotifier,
|
|
79
|
-
type EditorKeysProps,
|
|
80
|
-
editorKeys,
|
|
81
|
-
rangeExtension,
|
|
82
|
-
sheetExtension,
|
|
83
|
-
} from '../CellEditor';
|
|
84
|
-
|
|
85
|
-
// TODO(burdon): Virtualization bug.
|
|
86
|
-
// TODO(burdon): Toolbar styles and formatting.
|
|
87
|
-
// TODO(burdon): Insert/delete rows/columns (menu).
|
|
88
|
-
// TODO(burdon): Scroll to position if off screen.
|
|
89
|
-
// TODO(burdon): Don't render until sizes were updated (otherwise, flickers).
|
|
90
|
-
|
|
91
|
-
// TODO(burdon): Model multiple sheets (e.g., documents). And cross sheet references.
|
|
92
|
-
// TODO(burdon): Factor out react-ui-sheet.
|
|
93
|
-
// TODO(burdon): Comments (josiah).
|
|
94
|
-
// TODO(burdon): Realtime long text.
|
|
95
|
-
// TODO(burdon): Search.
|
|
96
|
-
|
|
97
|
-
// TODO(burdon): Virtualization:
|
|
98
|
-
// https://github.com/TanStack/virtual/blob/main/examples/react/dynamic/src/main.tsx#L171
|
|
99
|
-
// https://tanstack.com/virtual/v3/docs/framework/react/examples/variable
|
|
100
|
-
// https://canvas-grid-demo.vercel.app
|
|
101
|
-
// https://sheet.brianhung.me
|
|
102
|
-
// https://github.com/BrianHung
|
|
103
|
-
// https://daybrush.com/moveable
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Features:
|
|
107
|
-
* - Move rows/columns.
|
|
108
|
-
* - Insert/delete rows/columns.
|
|
109
|
-
* - Copy/paste.
|
|
110
|
-
* - Undo/redo.
|
|
111
|
-
* - Comments.
|
|
112
|
-
* - Real time collaborative editing of large text cells.
|
|
113
|
-
* - Select range.
|
|
114
|
-
* - Format cells.
|
|
115
|
-
* - Formulae.
|
|
116
|
-
* - Update formula ranges by selection.
|
|
117
|
-
*/
|
|
118
|
-
|
|
119
|
-
const fragments = {
|
|
120
|
-
axis: 'bg-axisSurface text-axisText text-xs select-none',
|
|
121
|
-
axisSelected: 'bg-attention text-baseText',
|
|
122
|
-
cell: 'bg-gridCell',
|
|
123
|
-
cellSelected: 'bg-gridCellSelected text-baseText border !border-accentSurface',
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
//
|
|
127
|
-
// Root
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
type SheetRootProps = SheetContextProps;
|
|
131
|
-
|
|
132
|
-
const SheetRoot = ({ children, ...props }: PropsWithChildren<SheetContextProps>) => {
|
|
133
|
-
return <SheetContextProvider {...props}>{children}</SheetContextProvider>;
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
//
|
|
137
|
-
// Main
|
|
138
|
-
//
|
|
139
|
-
|
|
140
|
-
type SheetMainProps = ThemedClassName<Partial<GridSize>>;
|
|
141
|
-
|
|
142
|
-
const SheetMain = forwardRef<HTMLDivElement, SheetMainProps>(({ classNames, numRows, numCols }, forwardRef) => {
|
|
143
|
-
const { model, cursor, setCursor, setRange, setEditing } = useSheetContext();
|
|
144
|
-
|
|
145
|
-
// Scrolling.
|
|
146
|
-
const { rowsRef, columnsRef, contentRef } = useScrollHandlers();
|
|
147
|
-
|
|
148
|
-
// Threads.
|
|
149
|
-
// TODO(Zan): Move this to an extension once we have an extension model.
|
|
150
|
-
useThreads();
|
|
151
|
-
|
|
152
|
-
//
|
|
153
|
-
// Order of Row/columns.
|
|
154
|
-
//
|
|
155
|
-
const [rows, setRows] = useState([...model.sheet.rows]);
|
|
156
|
-
const [columns, setColumns] = useState([...model.sheet.columns]);
|
|
157
|
-
useEffect(() => {
|
|
158
|
-
const rowsAccessor = createDocAccessor(model.sheet, ['rows']);
|
|
159
|
-
const columnsAccessor = createDocAccessor(model.sheet, ['columns']);
|
|
160
|
-
const handleUpdate = debounce(() => {
|
|
161
|
-
setRows([...model.sheet.rows]);
|
|
162
|
-
setColumns([...model.sheet.columns]);
|
|
163
|
-
}, 100);
|
|
164
|
-
|
|
165
|
-
rowsAccessor.handle.addListener('change', handleUpdate);
|
|
166
|
-
columnsAccessor.handle.addListener('change', handleUpdate);
|
|
167
|
-
handleUpdate();
|
|
168
|
-
return () => {
|
|
169
|
-
rowsAccessor.handle.removeListener('change', handleUpdate);
|
|
170
|
-
columnsAccessor.handle.removeListener('change', handleUpdate);
|
|
171
|
-
};
|
|
172
|
-
}, [model]);
|
|
173
|
-
|
|
174
|
-
// Refresh the model.
|
|
175
|
-
// TODO(burdon): Breaks undo.
|
|
176
|
-
useEffect(() => {
|
|
177
|
-
model.reset();
|
|
178
|
-
}, [rows, columns]);
|
|
179
|
-
|
|
180
|
-
const handleMoveRows: SheetRowsProps['onMove'] = (from, to, num = 1) => {
|
|
181
|
-
const cursorIdx = cursor ? addressToIndex(model.sheet, cursor) : undefined;
|
|
182
|
-
const [rows] = model.sheet.rows.splice(from, num);
|
|
183
|
-
model.sheet.rows.splice(to, 0, rows);
|
|
184
|
-
if (cursorIdx) {
|
|
185
|
-
setCursor(addressFromIndex(model.sheet, cursorIdx));
|
|
186
|
-
}
|
|
187
|
-
setRows([...model.sheet.rows]);
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
const handleMoveColumns: SheetColumnsProps['onMove'] = (from, to, num = 1) => {
|
|
191
|
-
const cursorIdx = cursor ? addressToIndex(model.sheet, cursor) : undefined;
|
|
192
|
-
const columns = model.sheet.columns.splice(from, num);
|
|
193
|
-
model.sheet.columns.splice(to, 0, ...columns);
|
|
194
|
-
if (cursorIdx) {
|
|
195
|
-
setCursor(addressFromIndex(model.sheet, cursorIdx));
|
|
196
|
-
}
|
|
197
|
-
setColumns([...model.sheet.columns]);
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
//
|
|
201
|
-
// Row/column sizes.
|
|
202
|
-
//
|
|
203
|
-
const [rowSizes, setRowSizes] = useState<SizeMap>();
|
|
204
|
-
const [columnSizes, setColumnSizes] = useState<SizeMap>();
|
|
205
|
-
useEffect(() => {
|
|
206
|
-
const rowAccessor = createDocAccessor(model.sheet, ['rowMeta']);
|
|
207
|
-
const columnAccessor = createDocAccessor(model.sheet, ['columnMeta']);
|
|
208
|
-
const handleUpdate = debounce(() => {
|
|
209
|
-
const mapSizes = (values: [string, { size?: number | undefined }][]) =>
|
|
210
|
-
values.reduce<SizeMap>((map, [idx, meta]) => {
|
|
211
|
-
if (meta.size) {
|
|
212
|
-
map[idx] = meta.size;
|
|
213
|
-
}
|
|
214
|
-
return map;
|
|
215
|
-
}, {});
|
|
216
|
-
|
|
217
|
-
setRowSizes(mapSizes(Object.entries(model.sheet.rowMeta)));
|
|
218
|
-
setColumnSizes(mapSizes(Object.entries(model.sheet.columnMeta)));
|
|
219
|
-
}, 100);
|
|
220
|
-
|
|
221
|
-
rowAccessor.handle.addListener('change', handleUpdate);
|
|
222
|
-
columnAccessor.handle.addListener('change', handleUpdate);
|
|
223
|
-
handleUpdate();
|
|
224
|
-
return () => {
|
|
225
|
-
rowAccessor.handle.removeListener('change', handleUpdate);
|
|
226
|
-
columnAccessor.handle.removeListener('change', handleUpdate);
|
|
227
|
-
};
|
|
228
|
-
}, [model]);
|
|
229
|
-
|
|
230
|
-
const handleResizeRow: SheetRowsProps['onResize'] = (idx, size, save) => {
|
|
231
|
-
if (save) {
|
|
232
|
-
model.sheet.rowMeta[idx] ??= {};
|
|
233
|
-
model.sheet.rowMeta[idx].size = size;
|
|
234
|
-
} else {
|
|
235
|
-
setRowSizes((sizes) => ({ ...sizes, [idx]: size }));
|
|
236
|
-
}
|
|
237
|
-
};
|
|
238
|
-
|
|
239
|
-
const handleResizeColumn: SheetColumnsProps['onResize'] = (idx, size, save) => {
|
|
240
|
-
if (save) {
|
|
241
|
-
model.sheet.columnMeta[idx] ??= {};
|
|
242
|
-
model.sheet.columnMeta[idx].size = size;
|
|
243
|
-
} else {
|
|
244
|
-
setColumnSizes((sizes) => ({ ...sizes, [idx]: size }));
|
|
245
|
-
}
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
return (
|
|
249
|
-
<div
|
|
250
|
-
role='none'
|
|
251
|
-
className={mx(
|
|
252
|
-
'grid grid-cols-[calc(var(--rail-size)-2px)_1fr] grid-rows-[32px_1fr_32px] bs-full is-full overflow-hidden',
|
|
253
|
-
classNames,
|
|
254
|
-
)}
|
|
255
|
-
>
|
|
256
|
-
<GridCorner
|
|
257
|
-
onClick={() => {
|
|
258
|
-
setCursor(undefined);
|
|
259
|
-
setRange(undefined);
|
|
260
|
-
setEditing(false);
|
|
261
|
-
}}
|
|
262
|
-
/>
|
|
263
|
-
<SheetColumns
|
|
264
|
-
ref={columnsRef}
|
|
265
|
-
columns={columns}
|
|
266
|
-
sizes={columnSizes}
|
|
267
|
-
selected={cursor?.col}
|
|
268
|
-
onSelect={(col) => setCursor(cursor?.col === col ? undefined : { row: -1, col })}
|
|
269
|
-
onResize={handleResizeColumn}
|
|
270
|
-
onMove={handleMoveColumns}
|
|
271
|
-
/>
|
|
272
|
-
|
|
273
|
-
<SheetRows
|
|
274
|
-
ref={rowsRef}
|
|
275
|
-
rows={rows}
|
|
276
|
-
sizes={rowSizes}
|
|
277
|
-
selected={cursor?.row}
|
|
278
|
-
onSelect={(row) => setCursor(cursor?.row === row ? undefined : { row, col: -1 })}
|
|
279
|
-
onResize={handleResizeRow}
|
|
280
|
-
onMove={handleMoveRows}
|
|
281
|
-
/>
|
|
282
|
-
<SheetGrid
|
|
283
|
-
ref={contentRef}
|
|
284
|
-
size={{ numRows: numRows ?? rows.length, numCols: numCols ?? columns.length }}
|
|
285
|
-
rows={rows}
|
|
286
|
-
columns={columns}
|
|
287
|
-
rowSizes={rowSizes}
|
|
288
|
-
columnSizes={columnSizes}
|
|
289
|
-
/>
|
|
290
|
-
|
|
291
|
-
<GridCorner />
|
|
292
|
-
<SheetStatusBar />
|
|
293
|
-
</div>
|
|
294
|
-
);
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Coordinate scrolling across components.
|
|
299
|
-
*/
|
|
300
|
-
const useScrollHandlers = () => {
|
|
301
|
-
const rowsRef = useRef<HTMLDivElement>(null);
|
|
302
|
-
const columnsRef = useRef<HTMLDivElement>(null);
|
|
303
|
-
const contentRef = useRef<HTMLDivElement>(null);
|
|
304
|
-
|
|
305
|
-
useEffect(() => {
|
|
306
|
-
const handleRowsScroll = (ev: Event) => {
|
|
307
|
-
const { scrollTop } = ev.target as HTMLDivElement;
|
|
308
|
-
if (!rowsRef.current!.dataset.locked) {
|
|
309
|
-
contentRef.current!.scrollTop = scrollTop;
|
|
310
|
-
}
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
const handleColumnsScroll = (ev: Event) => {
|
|
314
|
-
const { scrollLeft } = ev.target as HTMLDivElement;
|
|
315
|
-
if (!columnsRef.current!.dataset.locked) {
|
|
316
|
-
contentRef.current!.scrollLeft = scrollLeft;
|
|
317
|
-
}
|
|
318
|
-
};
|
|
319
|
-
|
|
320
|
-
const handleContentScroll = (ev: Event) => {
|
|
321
|
-
const { scrollTop, scrollLeft } = ev.target as HTMLDivElement;
|
|
322
|
-
rowsRef.current!.scrollTop = scrollTop;
|
|
323
|
-
columnsRef.current!.scrollLeft = scrollLeft;
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
const rows = rowsRef.current!;
|
|
327
|
-
const columns = columnsRef.current!;
|
|
328
|
-
const content = contentRef.current!;
|
|
329
|
-
|
|
330
|
-
rows.addEventListener('scroll', handleRowsScroll);
|
|
331
|
-
columns.addEventListener('scroll', handleColumnsScroll);
|
|
332
|
-
content.addEventListener('scroll', handleContentScroll);
|
|
333
|
-
return () => {
|
|
334
|
-
rows.removeEventListener('scroll', handleRowsScroll);
|
|
335
|
-
columns.removeEventListener('scroll', handleColumnsScroll);
|
|
336
|
-
content.removeEventListener('scroll', handleContentScroll);
|
|
337
|
-
};
|
|
338
|
-
}, []);
|
|
339
|
-
|
|
340
|
-
return { rowsRef, columnsRef, contentRef };
|
|
341
|
-
};
|
|
342
|
-
|
|
343
|
-
//
|
|
344
|
-
// Row/Column
|
|
345
|
-
//
|
|
346
|
-
|
|
347
|
-
const GridCorner = (props: { className?: string } & Pick<DOMAttributes<HTMLDivElement>, 'onClick'>) => {
|
|
348
|
-
return <div className={fragments.axis} {...props} />;
|
|
349
|
-
};
|
|
350
|
-
|
|
351
|
-
const MovingOverlay = ({ label }: { label: string }) => {
|
|
352
|
-
return (
|
|
353
|
-
<div className='flex w-full h-full justify-center items-center text-sm p-1 bg-gridOverlay cursor-pointer'>
|
|
354
|
-
{label}
|
|
355
|
-
</div>
|
|
356
|
-
);
|
|
357
|
-
};
|
|
358
|
-
|
|
359
|
-
// https://docs.dndkit.com/api-documentation/sensors/pointer#activation-constraints
|
|
360
|
-
const mouseConstraints: PointerActivationConstraint = { distance: 10 };
|
|
361
|
-
const touchConstraints: PointerActivationConstraint = { delay: 250, tolerance: 5 };
|
|
362
|
-
|
|
363
|
-
type ResizeProps = {
|
|
364
|
-
sizes?: SizeMap;
|
|
365
|
-
onResize?: (idx: CellIndex, size: number, save?: boolean) => void;
|
|
366
|
-
};
|
|
367
|
-
|
|
368
|
-
type MoveProps = {
|
|
369
|
-
onMove?: (from: number, to: number) => void;
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
type RowColumnSelection = {
|
|
373
|
-
selected?: number;
|
|
374
|
-
onSelect?: (selected: number) => void;
|
|
375
|
-
};
|
|
376
|
-
|
|
377
|
-
type RowColumnProps = {
|
|
378
|
-
idx: CellIndex;
|
|
379
|
-
index: number;
|
|
380
|
-
label: string;
|
|
381
|
-
size: number;
|
|
382
|
-
resize: boolean;
|
|
383
|
-
selected: boolean;
|
|
384
|
-
} & Pick<ResizeProps, 'onResize'> &
|
|
385
|
-
Pick<RowColumnSelection, 'onSelect'>;
|
|
386
|
-
|
|
387
|
-
//
|
|
388
|
-
// Rows
|
|
389
|
-
//
|
|
390
|
-
|
|
391
|
-
type SheetRowsProps = { rows: CellIndex[] } & RowColumnSelection & ResizeProps & MoveProps;
|
|
392
|
-
|
|
393
|
-
const SheetRows = forwardRef<HTMLDivElement, SheetRowsProps>(
|
|
394
|
-
({ rows, sizes, selected, onSelect, onResize, onMove }, forwardRef) => {
|
|
395
|
-
const mouseSensor = useSensor(MouseSensor, { activationConstraint: mouseConstraints });
|
|
396
|
-
const touchSensor = useSensor(TouchSensor, { activationConstraint: touchConstraints });
|
|
397
|
-
const keyboardSensor = useSensor(KeyboardSensor, {});
|
|
398
|
-
const sensors = useSensors(mouseSensor, touchSensor, keyboardSensor);
|
|
399
|
-
|
|
400
|
-
const [active, setActive] = useState<Active | null>(null);
|
|
401
|
-
const handleDragStart = ({ active }: DragStartEvent) => {
|
|
402
|
-
setActive(active);
|
|
403
|
-
};
|
|
404
|
-
|
|
405
|
-
const handleDragEnd = ({ over, active }: DragEndEvent) => {
|
|
406
|
-
if (over && over.id !== active.id) {
|
|
407
|
-
setActive(null);
|
|
408
|
-
onMove?.(active.data.current!.index, over.data.current!.index);
|
|
409
|
-
}
|
|
410
|
-
};
|
|
411
|
-
|
|
412
|
-
const snapToCenter: Modifier = ({ activatorEvent, draggingNodeRect, transform }) => {
|
|
413
|
-
if (draggingNodeRect && activatorEvent) {
|
|
414
|
-
const activatorCoordinates = getEventCoordinates(activatorEvent);
|
|
415
|
-
if (!activatorCoordinates) {
|
|
416
|
-
return transform;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
const offset = activatorCoordinates.y - draggingNodeRect.top;
|
|
420
|
-
return {
|
|
421
|
-
...transform,
|
|
422
|
-
y: transform.y + offset - draggingNodeRect.height / 2,
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
return transform;
|
|
427
|
-
};
|
|
428
|
-
|
|
429
|
-
return (
|
|
430
|
-
<div className='relative flex grow overflow-hidden'>
|
|
431
|
-
{/* Fixed border. */}
|
|
432
|
-
<div
|
|
433
|
-
className={mx('z-20 absolute inset-0 border-y border-gridLine pointer-events-none')}
|
|
434
|
-
style={{ width: axisWidth }}
|
|
435
|
-
/>
|
|
436
|
-
|
|
437
|
-
{/* Scrollbar. */}
|
|
438
|
-
<div ref={forwardRef} role='rowheader' className='grow overflow-y-auto scrollbar-none'>
|
|
439
|
-
<DndContext
|
|
440
|
-
sensors={sensors}
|
|
441
|
-
modifiers={[restrictToVerticalAxis, snapToCenter]}
|
|
442
|
-
onDragStart={handleDragStart}
|
|
443
|
-
onDragEnd={handleDragEnd}
|
|
444
|
-
>
|
|
445
|
-
<div className='flex flex-col' style={{ width: axisWidth }}>
|
|
446
|
-
{rows.map((idx, index) => (
|
|
447
|
-
<GridRowCell
|
|
448
|
-
key={idx}
|
|
449
|
-
idx={idx}
|
|
450
|
-
index={index}
|
|
451
|
-
label={String(index + 1)}
|
|
452
|
-
size={sizes?.[idx] ?? defaultHeight}
|
|
453
|
-
resize={index < rows.length - 1}
|
|
454
|
-
selected={selected === index}
|
|
455
|
-
onResize={onResize}
|
|
456
|
-
onSelect={onSelect}
|
|
457
|
-
/>
|
|
458
|
-
))}
|
|
459
|
-
</div>
|
|
460
|
-
|
|
461
|
-
{createPortal(
|
|
462
|
-
<DragOverlay>{active && <MovingOverlay label={String(active.data.current!.index + 1)} />}</DragOverlay>,
|
|
463
|
-
document.body,
|
|
464
|
-
)}
|
|
465
|
-
</DndContext>
|
|
466
|
-
</div>
|
|
467
|
-
</div>
|
|
468
|
-
);
|
|
469
|
-
},
|
|
470
|
-
);
|
|
471
|
-
|
|
472
|
-
const GridRowCell = ({ idx, index, label, size, resize, selected, onSelect, onResize }: RowColumnProps) => {
|
|
473
|
-
const { setNodeRef: setDroppableNodeRef } = useDroppable({ id: idx, data: { index } });
|
|
474
|
-
const {
|
|
475
|
-
setNodeRef: setDraggableNodeRef,
|
|
476
|
-
attributes,
|
|
477
|
-
listeners,
|
|
478
|
-
isDragging,
|
|
479
|
-
over,
|
|
480
|
-
} = useDraggable({ id: idx, data: { index } });
|
|
481
|
-
const setNodeRef = useCombinedRefs(setDroppableNodeRef, setDraggableNodeRef);
|
|
482
|
-
const [initialSize, setInitialSize] = useState(size);
|
|
483
|
-
const [resizing, setResizing] = useState(false);
|
|
484
|
-
|
|
485
|
-
// Lock scroll container while resizing (fixes scroll bug).
|
|
486
|
-
// https://github.com/bokuweb/re-resizable/issues/727
|
|
487
|
-
const scrollHandler = useRef<any>();
|
|
488
|
-
const handleResizeStart: ResizeStartCallback = (_ev, _dir, elementRef) => {
|
|
489
|
-
const scrollContainer = elementRef.closest<HTMLDivElement>('[role="rowheader"]')!;
|
|
490
|
-
const scrollTop = scrollContainer.scrollTop;
|
|
491
|
-
scrollHandler.current = (ev: Event) => ((ev.target as HTMLElement).scrollTop = scrollTop);
|
|
492
|
-
scrollContainer.addEventListener('scroll', scrollHandler.current);
|
|
493
|
-
scrollContainer.dataset.locked = 'true';
|
|
494
|
-
setResizing(true);
|
|
495
|
-
};
|
|
496
|
-
|
|
497
|
-
const handleResize: ResizeCallback = (_ev, _dir, _elementRef, { height }) => {
|
|
498
|
-
onResize?.(idx, initialSize + height);
|
|
499
|
-
};
|
|
500
|
-
|
|
501
|
-
const handleResizeStop: ResizeCallback = (_ev, _dir, elementRef, { height }) => {
|
|
502
|
-
const scrollContainer = elementRef.closest<HTMLDivElement>('[role="rowheader"]')!;
|
|
503
|
-
scrollContainer.removeEventListener('scroll', scrollHandler.current!);
|
|
504
|
-
delete scrollContainer.dataset.locked;
|
|
505
|
-
scrollHandler.current = undefined;
|
|
506
|
-
setInitialSize(initialSize + height);
|
|
507
|
-
onResize?.(idx, initialSize + height, true);
|
|
508
|
-
setResizing(false);
|
|
509
|
-
};
|
|
510
|
-
|
|
511
|
-
// Row.
|
|
512
|
-
return (
|
|
513
|
-
<Resizable
|
|
514
|
-
enable={{ bottom: resize }}
|
|
515
|
-
size={{ height: size - 1 }}
|
|
516
|
-
minHeight={minHeight - 1}
|
|
517
|
-
maxHeight={maxHeight}
|
|
518
|
-
onResizeStart={handleResizeStart}
|
|
519
|
-
onResize={handleResize}
|
|
520
|
-
onResizeStop={handleResizeStop}
|
|
521
|
-
>
|
|
522
|
-
<div
|
|
523
|
-
ref={setNodeRef}
|
|
524
|
-
{...attributes}
|
|
525
|
-
{...listeners}
|
|
526
|
-
className={mx(
|
|
527
|
-
'flex h-full items-center justify-center cursor-pointer',
|
|
528
|
-
'border-t border-gridLine focus-visible:outline-none',
|
|
529
|
-
fragments.axis,
|
|
530
|
-
selected && fragments.axisSelected,
|
|
531
|
-
isDragging && fragments.axisSelected,
|
|
532
|
-
)}
|
|
533
|
-
onClick={() => onSelect?.(index)}
|
|
534
|
-
>
|
|
535
|
-
<span className='flex w-full justify-center'>{label}</span>
|
|
536
|
-
|
|
537
|
-
{/* Drop indicator. */}
|
|
538
|
-
{over?.id === idx && !isDragging && (
|
|
539
|
-
<div className='z-20 absolute top-0 w-full min-h-[4px] border-b-4 border-accentSurface' />
|
|
540
|
-
)}
|
|
541
|
-
|
|
542
|
-
{/* Resize indicator. */}
|
|
543
|
-
{resizing && <div className='z-20 absolute bottom-0 w-full min-h-[4px] border-b-4 border-accentSurface' />}
|
|
544
|
-
</div>
|
|
545
|
-
</Resizable>
|
|
546
|
-
);
|
|
547
|
-
};
|
|
548
|
-
|
|
549
|
-
//
|
|
550
|
-
// Columns
|
|
551
|
-
//
|
|
552
|
-
|
|
553
|
-
type SheetColumnsProps = { columns: CellIndex[] } & RowColumnSelection & ResizeProps & MoveProps;
|
|
554
|
-
|
|
555
|
-
const SheetColumns = forwardRef<HTMLDivElement, SheetColumnsProps>(
|
|
556
|
-
({ columns, sizes, selected, onSelect, onResize, onMove }, forwardRef) => {
|
|
557
|
-
const mouseSensor = useSensor(MouseSensor, { activationConstraint: mouseConstraints });
|
|
558
|
-
const touchSensor = useSensor(TouchSensor, { activationConstraint: touchConstraints });
|
|
559
|
-
const keyboardSensor = useSensor(KeyboardSensor, {});
|
|
560
|
-
const sensors = useSensors(mouseSensor, touchSensor, keyboardSensor);
|
|
561
|
-
|
|
562
|
-
const [active, setActive] = useState<Active | null>(null);
|
|
563
|
-
const handleDragStart = ({ active }: DragStartEvent) => {
|
|
564
|
-
setActive(active);
|
|
565
|
-
};
|
|
566
|
-
|
|
567
|
-
const handleDragEnd = ({ active, over }: DragEndEvent) => {
|
|
568
|
-
if (over && over.id !== active.id) {
|
|
569
|
-
setActive(null);
|
|
570
|
-
onMove?.(active.data.current!.index, over.data.current!.index);
|
|
571
|
-
}
|
|
572
|
-
};
|
|
573
|
-
|
|
574
|
-
const snapToCenter: Modifier = ({ activatorEvent, draggingNodeRect, transform }) => {
|
|
575
|
-
if (draggingNodeRect && activatorEvent) {
|
|
576
|
-
const activatorCoordinates = getEventCoordinates(activatorEvent);
|
|
577
|
-
if (!activatorCoordinates) {
|
|
578
|
-
return transform;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
const offset = activatorCoordinates.x - draggingNodeRect.left;
|
|
582
|
-
return {
|
|
583
|
-
...transform,
|
|
584
|
-
x: transform.x + offset - draggingNodeRect.width / 2,
|
|
585
|
-
};
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
return transform;
|
|
589
|
-
};
|
|
590
|
-
|
|
591
|
-
return (
|
|
592
|
-
<div className='relative flex grow overflow-hidden'>
|
|
593
|
-
{/* Fixed border. */}
|
|
594
|
-
<div
|
|
595
|
-
className={mx('z-20 absolute inset-0 border-x border-gridLine pointer-events-none')}
|
|
596
|
-
style={{ height: axisHeight }}
|
|
597
|
-
/>
|
|
598
|
-
|
|
599
|
-
{/* Scrollbar. */}
|
|
600
|
-
<div ref={forwardRef} role='columnheader' className='grow overflow-x-auto scrollbar-none'>
|
|
601
|
-
<DndContext
|
|
602
|
-
autoScroll={{ enabled: true }}
|
|
603
|
-
sensors={sensors}
|
|
604
|
-
modifiers={[restrictToHorizontalAxis, snapToCenter]}
|
|
605
|
-
onDragStart={handleDragStart}
|
|
606
|
-
onDragEnd={handleDragEnd}
|
|
607
|
-
>
|
|
608
|
-
<div className='flex h-full' style={{ height: axisHeight }}>
|
|
609
|
-
{columns.map((idx, index) => (
|
|
610
|
-
<GridColumnCell
|
|
611
|
-
key={idx}
|
|
612
|
-
idx={idx}
|
|
613
|
-
index={index}
|
|
614
|
-
label={columnLetter(index)}
|
|
615
|
-
size={sizes?.[idx] ?? defaultWidth}
|
|
616
|
-
resize={index < columns.length - 1}
|
|
617
|
-
selected={selected === index}
|
|
618
|
-
onResize={onResize}
|
|
619
|
-
onSelect={onSelect}
|
|
620
|
-
/>
|
|
621
|
-
))}
|
|
622
|
-
</div>
|
|
623
|
-
|
|
624
|
-
{createPortal(
|
|
625
|
-
<DragOverlay>{active && <MovingOverlay label={columnLetter(active.data.current!.index)} />}</DragOverlay>,
|
|
626
|
-
document.body,
|
|
627
|
-
)}
|
|
628
|
-
</DndContext>
|
|
629
|
-
</div>
|
|
630
|
-
</div>
|
|
631
|
-
);
|
|
632
|
-
},
|
|
633
|
-
);
|
|
634
|
-
|
|
635
|
-
const GridColumnCell = ({ idx, index, label, size, resize, selected, onSelect, onResize }: RowColumnProps) => {
|
|
636
|
-
const { setNodeRef: setDroppableNodeRef } = useDroppable({ id: idx, data: { index } });
|
|
637
|
-
const {
|
|
638
|
-
setNodeRef: setDraggableNodeRef,
|
|
639
|
-
attributes,
|
|
640
|
-
listeners,
|
|
641
|
-
over,
|
|
642
|
-
isDragging,
|
|
643
|
-
} = useDraggable({ id: idx, data: { index } });
|
|
644
|
-
const setNodeRef = useCombinedRefs(setDroppableNodeRef, setDraggableNodeRef);
|
|
645
|
-
const [initialSize, setInitialSize] = useState(size);
|
|
646
|
-
const [resizing, setResizing] = useState(false);
|
|
647
|
-
|
|
648
|
-
// Lock scroll container while resizing (fixes scroll bug).
|
|
649
|
-
// https://github.com/bokuweb/re-resizable/issues/727
|
|
650
|
-
const scrollHandler = useRef<any>();
|
|
651
|
-
const handleResizeStart: ResizeStartCallback = (_ev, _dir, elementRef) => {
|
|
652
|
-
const scrollContainer = elementRef.closest<HTMLDivElement>('[role="columnheader"]')!;
|
|
653
|
-
const scrollLeft = scrollContainer.scrollLeft;
|
|
654
|
-
scrollHandler.current = (ev: Event) => ((ev.target as HTMLElement).scrollLeft = scrollLeft);
|
|
655
|
-
scrollContainer.addEventListener('scroll', scrollHandler.current);
|
|
656
|
-
scrollContainer.dataset.locked = 'true';
|
|
657
|
-
setResizing(true);
|
|
658
|
-
};
|
|
659
|
-
|
|
660
|
-
const handleResize: ResizeCallback = (_ev, _dir, _elementRef, { width }) => {
|
|
661
|
-
onResize?.(idx, initialSize + width);
|
|
662
|
-
};
|
|
663
|
-
|
|
664
|
-
const handleResizeStop: ResizeCallback = (_ev, _dir, elementRef, { width }) => {
|
|
665
|
-
const scrollContainer = elementRef.closest<HTMLDivElement>('[role="columnheader"]')!;
|
|
666
|
-
scrollContainer.removeEventListener('scroll', scrollHandler.current!);
|
|
667
|
-
delete scrollContainer.dataset.locked;
|
|
668
|
-
scrollHandler.current = undefined;
|
|
669
|
-
setInitialSize(initialSize + width);
|
|
670
|
-
onResize?.(idx, initialSize + width, true);
|
|
671
|
-
setResizing(false);
|
|
672
|
-
};
|
|
673
|
-
|
|
674
|
-
// Column.
|
|
675
|
-
return (
|
|
676
|
-
<Resizable
|
|
677
|
-
enable={{ right: resize }}
|
|
678
|
-
size={{ width: size - 1 }}
|
|
679
|
-
minWidth={minWidth - 1}
|
|
680
|
-
maxWidth={maxWidth}
|
|
681
|
-
onResizeStart={handleResizeStart}
|
|
682
|
-
onResize={handleResize}
|
|
683
|
-
onResizeStop={handleResizeStop}
|
|
684
|
-
>
|
|
685
|
-
<div
|
|
686
|
-
ref={setNodeRef}
|
|
687
|
-
{...attributes}
|
|
688
|
-
{...listeners}
|
|
689
|
-
className={mx(
|
|
690
|
-
'flex h-full items-center justify-center cursor-pointer',
|
|
691
|
-
'border-l border-gridLine focus-visible:outline-none',
|
|
692
|
-
fragments.axis,
|
|
693
|
-
selected && fragments.axisSelected,
|
|
694
|
-
isDragging && fragments.axisSelected,
|
|
695
|
-
)}
|
|
696
|
-
onClick={() => onSelect?.(index)}
|
|
697
|
-
>
|
|
698
|
-
<span className='flex w-full justify-center'>{label}</span>
|
|
699
|
-
|
|
700
|
-
{/* Drop indicator. */}
|
|
701
|
-
{over?.id === idx && !isDragging && (
|
|
702
|
-
<div className='z-20 absolute left-0 h-full min-w-[4px] border-l-4 border-accentSurface' />
|
|
703
|
-
)}
|
|
704
|
-
|
|
705
|
-
{/* Resize indicator. */}
|
|
706
|
-
{resizing && <div className='z-20 absolute right-0 h-full min-h-[4px] border-l-4 border-accentSurface' />}
|
|
707
|
-
</div>
|
|
708
|
-
</Resizable>
|
|
709
|
-
);
|
|
710
|
-
};
|
|
711
|
-
|
|
712
|
-
//
|
|
713
|
-
// Content
|
|
714
|
-
//
|
|
715
|
-
|
|
716
|
-
type SheetGridProps = GridLayoutProps & {
|
|
717
|
-
size: GridSize;
|
|
718
|
-
};
|
|
719
|
-
|
|
720
|
-
const SheetGrid = forwardRef<HTMLDivElement, SheetGridProps>(
|
|
721
|
-
({ size, rows, columns, rowSizes, columnSizes }, forwardRef) => {
|
|
722
|
-
const {
|
|
723
|
-
ref: containerRef,
|
|
724
|
-
width: containerWidth = 0,
|
|
725
|
-
height: containerHeight = 0,
|
|
726
|
-
} = useResizeDetector({ refreshRate: 200 });
|
|
727
|
-
const scrollerRef = useRef<HTMLDivElement>(null);
|
|
728
|
-
useImperativeHandle(forwardRef, () => scrollerRef.current!);
|
|
729
|
-
|
|
730
|
-
const { model, cursor, range, editing, setCursor, setRange, setEditing, onInfo } = useSheetContext();
|
|
731
|
-
const initialText = useRef<string>();
|
|
732
|
-
const quickEdit = useRef(false);
|
|
733
|
-
|
|
734
|
-
// Listen for async calculation updates.
|
|
735
|
-
const [, forceUpdate] = useState({});
|
|
736
|
-
useEffect(() => {
|
|
737
|
-
const unsubscribe = model.update.on(() => {
|
|
738
|
-
log('updated', { id: model.id });
|
|
739
|
-
forceUpdate({});
|
|
740
|
-
});
|
|
741
|
-
|
|
742
|
-
return () => {
|
|
743
|
-
unsubscribe();
|
|
744
|
-
};
|
|
745
|
-
}, [model]);
|
|
746
|
-
|
|
747
|
-
//
|
|
748
|
-
// Event handling.
|
|
749
|
-
//
|
|
750
|
-
|
|
751
|
-
const inputRef = useRef<HTMLInputElement>(null);
|
|
752
|
-
const handleKeyDown: DOMAttributes<HTMLInputElement>['onKeyDown'] = (ev) => {
|
|
753
|
-
// Cut-and-paste.
|
|
754
|
-
const isMacOS = /Mac|iPhone|iPod|iPad/.test(navigator.userAgent);
|
|
755
|
-
if (cursor && ((isMacOS && ev.metaKey) || ev.ctrlKey)) {
|
|
756
|
-
switch (ev.key) {
|
|
757
|
-
case 'x': {
|
|
758
|
-
model.cut(range ?? { from: cursor });
|
|
759
|
-
return;
|
|
760
|
-
}
|
|
761
|
-
case 'c': {
|
|
762
|
-
model.copy(range ?? { from: cursor });
|
|
763
|
-
return;
|
|
764
|
-
}
|
|
765
|
-
case 'v': {
|
|
766
|
-
model.paste(cursor);
|
|
767
|
-
return;
|
|
768
|
-
}
|
|
769
|
-
case 'z': {
|
|
770
|
-
if (ev.shiftKey) {
|
|
771
|
-
model.redo();
|
|
772
|
-
} else {
|
|
773
|
-
model.undo();
|
|
774
|
-
}
|
|
775
|
-
return;
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
switch (ev.key) {
|
|
781
|
-
case 'ArrowUp':
|
|
782
|
-
case 'ArrowDown':
|
|
783
|
-
case 'ArrowLeft':
|
|
784
|
-
case 'ArrowRight':
|
|
785
|
-
case 'Home':
|
|
786
|
-
case 'End': {
|
|
787
|
-
const next = handleNav(ev, cursor, range, size);
|
|
788
|
-
setRange(next.range);
|
|
789
|
-
if (next.cursor) {
|
|
790
|
-
setCursor(next.cursor);
|
|
791
|
-
const element = getCellElement(scrollerRef.current!, next.cursor);
|
|
792
|
-
if (element) {
|
|
793
|
-
scrollIntoView(scrollerRef.current!, element);
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
break;
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
case 'Backspace': {
|
|
800
|
-
if (cursor) {
|
|
801
|
-
if (range) {
|
|
802
|
-
model.clear(range);
|
|
803
|
-
} else {
|
|
804
|
-
model.setValue(cursor, null);
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
break;
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
case 'Escape': {
|
|
811
|
-
setRange(undefined);
|
|
812
|
-
break;
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
case 'Enter': {
|
|
816
|
-
ev.stopPropagation();
|
|
817
|
-
if (cursor) {
|
|
818
|
-
setEditing(true);
|
|
819
|
-
}
|
|
820
|
-
break;
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
case '?': {
|
|
824
|
-
onInfo?.();
|
|
825
|
-
break;
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
default: {
|
|
829
|
-
if (ev.key.length === 1) {
|
|
830
|
-
initialText.current = ev.key;
|
|
831
|
-
quickEdit.current = true;
|
|
832
|
-
setEditing(true);
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
};
|
|
837
|
-
|
|
838
|
-
// Mouse handlers for selection.
|
|
839
|
-
const { handlers } = useRangeSelect((event, range) => {
|
|
840
|
-
switch (event) {
|
|
841
|
-
case 'start': {
|
|
842
|
-
// if (!editing) {
|
|
843
|
-
// setCursor(range?.from);
|
|
844
|
-
// }
|
|
845
|
-
setRange(undefined);
|
|
846
|
-
break;
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
default: {
|
|
850
|
-
setRange(range);
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
});
|
|
854
|
-
|
|
855
|
-
// Calculate visible grid.
|
|
856
|
-
const { width, height, rowRange, columnRange } = useGridLayout({
|
|
857
|
-
scroller: scrollerRef.current,
|
|
858
|
-
size: { width: containerWidth, height: containerHeight },
|
|
859
|
-
rows,
|
|
860
|
-
columns,
|
|
861
|
-
rowSizes,
|
|
862
|
-
columnSizes,
|
|
863
|
-
});
|
|
864
|
-
|
|
865
|
-
const id = fullyQualifiedId(model.sheet);
|
|
866
|
-
const { hasAttention } = useAttention(id);
|
|
867
|
-
|
|
868
|
-
return (
|
|
869
|
-
<div ref={containerRef} role='grid' className='relative flex grow overflow-hidden'>
|
|
870
|
-
{/* Fixed border. */}
|
|
871
|
-
<div className={mx('z-20 absolute inset-0 border border-gridLine pointer-events-none')} />
|
|
872
|
-
|
|
873
|
-
{/* Grid scroll container. */}
|
|
874
|
-
{/* NOTE: Prevents scroll if not attended. */}
|
|
875
|
-
<div ref={scrollerRef} className={mx('grow', hasAttention && 'overflow-auto scrollbar-thin')}>
|
|
876
|
-
{/* Scroll content. */}
|
|
877
|
-
<div
|
|
878
|
-
className='relative select-none'
|
|
879
|
-
style={{ width, height }}
|
|
880
|
-
onClick={() => inputRef.current?.focus()}
|
|
881
|
-
{...handlers}
|
|
882
|
-
>
|
|
883
|
-
{/* Selection. */}
|
|
884
|
-
{scrollerRef.current && <SelectionOverlay root={scrollerRef.current} />}
|
|
885
|
-
|
|
886
|
-
{/* Grid cells. */}
|
|
887
|
-
{rowRange.map(({ row, top, height }) => {
|
|
888
|
-
return columnRange.map(({ col, left, width }) => {
|
|
889
|
-
const style: CSSProperties = { position: 'absolute', top, left, width, height };
|
|
890
|
-
const cell: CellAddress = { row, col };
|
|
891
|
-
const id = addressToA1Notation(cell);
|
|
892
|
-
const idx = addressToIndex(model.sheet, cell);
|
|
893
|
-
const active = posEquals(cursor, cell);
|
|
894
|
-
if (active && editing) {
|
|
895
|
-
const value = initialText.current ?? model.getCellText(cell) ?? '';
|
|
896
|
-
|
|
897
|
-
// TODO(burdon): Validate formula before closing: hf.validateFormula();
|
|
898
|
-
const handleClose: GridCellEditorProps['onClose'] = (value) => {
|
|
899
|
-
initialText.current = undefined;
|
|
900
|
-
quickEdit.current = false;
|
|
901
|
-
if (value !== undefined) {
|
|
902
|
-
model.setValue(cell, value);
|
|
903
|
-
// Auto-advance to next cell.
|
|
904
|
-
const next = handleArrowNav({ key: 'ArrowDown', metaKey: false }, cursor, size);
|
|
905
|
-
if (next) {
|
|
906
|
-
setCursor(next);
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
inputRef.current?.focus();
|
|
910
|
-
setEditing(false);
|
|
911
|
-
};
|
|
912
|
-
|
|
913
|
-
// Quick entry mode: i.e., typing to enter cell.
|
|
914
|
-
const handleNav: GridCellEditorProps['onNav'] = (value, { key }) => {
|
|
915
|
-
initialText.current = undefined;
|
|
916
|
-
model.setValue(cell, value ?? null);
|
|
917
|
-
const next = handleArrowNav({ key, metaKey: false }, cursor, size);
|
|
918
|
-
if (next) {
|
|
919
|
-
setCursor(next);
|
|
920
|
-
}
|
|
921
|
-
inputRef.current?.focus();
|
|
922
|
-
setEditing(false);
|
|
923
|
-
};
|
|
924
|
-
|
|
925
|
-
return (
|
|
926
|
-
<GridCellEditor
|
|
927
|
-
key={idx}
|
|
928
|
-
value={value}
|
|
929
|
-
style={style}
|
|
930
|
-
onNav={quickEdit.current ? handleNav : undefined}
|
|
931
|
-
onClose={handleClose}
|
|
932
|
-
/>
|
|
933
|
-
);
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
return (
|
|
937
|
-
<SheetCell
|
|
938
|
-
key={id}
|
|
939
|
-
id={id}
|
|
940
|
-
cell={cell}
|
|
941
|
-
active={active}
|
|
942
|
-
style={style}
|
|
943
|
-
onSelect={(cell, edit) => {
|
|
944
|
-
setEditing(edit);
|
|
945
|
-
setCursor(cell);
|
|
946
|
-
}}
|
|
947
|
-
/>
|
|
948
|
-
);
|
|
949
|
-
});
|
|
950
|
-
})}
|
|
951
|
-
</div>
|
|
952
|
-
</div>
|
|
953
|
-
|
|
954
|
-
{/* Hidden input for key navigation. */}
|
|
955
|
-
{createPortal(<SheetInput ref={inputRef} id={id} onKeyDown={handleKeyDown} />, document.body)}
|
|
956
|
-
</div>
|
|
957
|
-
);
|
|
958
|
-
},
|
|
959
|
-
);
|
|
960
|
-
|
|
961
|
-
type SheetInputProps = {
|
|
962
|
-
id: string;
|
|
963
|
-
onKeyDown?: KeyboardEventHandler<HTMLInputElement>;
|
|
964
|
-
};
|
|
965
|
-
|
|
966
|
-
const SheetInput = forwardRef<HTMLInputElement, SheetInputProps>(({ id, onKeyDown }, forwardedRef) => {
|
|
967
|
-
const path = useAttentionPath();
|
|
968
|
-
const attendableAttrs = useAttendableAttributes(id);
|
|
969
|
-
|
|
970
|
-
// TODO(wittjosiah): Consider factoring out as an attention util.
|
|
971
|
-
// Wrap input in attendable divs for each part of the path.
|
|
972
|
-
// This ensures that the sheet stays attended when the input is focused.
|
|
973
|
-
return path.toReversed().reduce(
|
|
974
|
-
(acc, part) => {
|
|
975
|
-
return <div {...{ [ATTENABLE_ATTRIBUTE]: part }}>{acc}</div>;
|
|
976
|
-
},
|
|
977
|
-
<input
|
|
978
|
-
ref={forwardedRef}
|
|
979
|
-
className='absolute w-[1px] h-[1px] bg-transparent outline-none border-none caret-transparent'
|
|
980
|
-
onKeyDown={onKeyDown}
|
|
981
|
-
{...attendableAttrs}
|
|
982
|
-
/>,
|
|
983
|
-
);
|
|
984
|
-
});
|
|
985
|
-
|
|
986
|
-
//
|
|
987
|
-
// Selection
|
|
988
|
-
//
|
|
989
|
-
|
|
990
|
-
const SelectionOverlay = ({ root }: { root: HTMLDivElement }) => {
|
|
991
|
-
const { range } = useSheetContext();
|
|
992
|
-
if (!range) {
|
|
993
|
-
return null;
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
const c1 = getCellElement(root, range.from);
|
|
997
|
-
const c2 = range.to ? getCellElement(root, range.to)! : c1;
|
|
998
|
-
if (!c1 || !c2) {
|
|
999
|
-
return null;
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
// TODO(burdon): Instead of measuring cells, get from grid layout?
|
|
1003
|
-
const b1 = getRelativeClientRect(root, c1);
|
|
1004
|
-
const b2 = getRelativeClientRect(root, c2);
|
|
1005
|
-
const bounds = getRectUnion(b1, b2);
|
|
1006
|
-
|
|
1007
|
-
return (
|
|
1008
|
-
<div
|
|
1009
|
-
role='none'
|
|
1010
|
-
style={bounds}
|
|
1011
|
-
className='z-10 absolute pointer-events-none bg-gridSelectionOverlay border border-gridOverlay'
|
|
1012
|
-
/>
|
|
1013
|
-
);
|
|
1014
|
-
};
|
|
1015
|
-
|
|
1016
|
-
//
|
|
1017
|
-
// Cell
|
|
1018
|
-
//
|
|
1019
|
-
|
|
1020
|
-
type SheetCellProps = {
|
|
1021
|
-
id: string; // TODO(burdon): Should this be the index?
|
|
1022
|
-
cell: CellAddress;
|
|
1023
|
-
style: CSSProperties;
|
|
1024
|
-
active: boolean;
|
|
1025
|
-
onSelect?: (selected: CellAddress, edit: boolean) => void;
|
|
1026
|
-
};
|
|
1027
|
-
|
|
1028
|
-
const SheetCell = ({ id, cell, style, active, onSelect }: SheetCellProps) => {
|
|
1029
|
-
const {
|
|
1030
|
-
formatting,
|
|
1031
|
-
editing,
|
|
1032
|
-
setRange,
|
|
1033
|
-
decorations,
|
|
1034
|
-
model: { sheet },
|
|
1035
|
-
} = useSheetContext();
|
|
1036
|
-
const { value, classNames } = formatting.getFormatting(cell);
|
|
1037
|
-
|
|
1038
|
-
const decorationsForCell = decorations.getDecorationsForCell(addressToIndex(sheet, cell)) ?? [];
|
|
1039
|
-
const decorationAddedClasses = useMemo(
|
|
1040
|
-
() => decorationsForCell.flatMap((d) => d.classNames ?? []),
|
|
1041
|
-
[decorationsForCell],
|
|
1042
|
-
);
|
|
1043
|
-
const decoratedContent = decorationsForCell.reduce(
|
|
1044
|
-
(children, { decorate }) => {
|
|
1045
|
-
if (!decorate) {
|
|
1046
|
-
return children;
|
|
1047
|
-
}
|
|
1048
|
-
const DecoratorComponent = decorate;
|
|
1049
|
-
return <DecoratorComponent>{children}</DecoratorComponent>;
|
|
1050
|
-
},
|
|
1051
|
-
<div
|
|
1052
|
-
role='none'
|
|
1053
|
-
className={mx(
|
|
1054
|
-
'flex flex-grow bs-full is-full px-2 items-center truncate cursor-pointer',
|
|
1055
|
-
...decorationAddedClasses,
|
|
1056
|
-
)}
|
|
1057
|
-
>
|
|
1058
|
-
{value}
|
|
1059
|
-
</div>,
|
|
1060
|
-
);
|
|
1061
|
-
|
|
1062
|
-
return (
|
|
1063
|
-
<div
|
|
1064
|
-
{...{ [`data-${CELL_DATA_KEY}`]: id }}
|
|
1065
|
-
role='cell'
|
|
1066
|
-
style={style}
|
|
1067
|
-
className={mx(
|
|
1068
|
-
'border border-gridLine cursor-pointer',
|
|
1069
|
-
fragments.cell,
|
|
1070
|
-
active && ['z-20', fragments.cellSelected],
|
|
1071
|
-
classNames,
|
|
1072
|
-
)}
|
|
1073
|
-
onClick={() => {
|
|
1074
|
-
if (editing) {
|
|
1075
|
-
setRange?.({ from: cell });
|
|
1076
|
-
} else {
|
|
1077
|
-
onSelect?.(cell, false);
|
|
1078
|
-
}
|
|
1079
|
-
}}
|
|
1080
|
-
onDoubleClick={() => onSelect?.(cell, true)}
|
|
1081
|
-
>
|
|
1082
|
-
{decoratedContent}
|
|
1083
|
-
</div>
|
|
1084
|
-
);
|
|
1085
|
-
};
|
|
1086
|
-
|
|
1087
|
-
type GridCellEditorProps = {
|
|
1088
|
-
style: CSSProperties;
|
|
1089
|
-
value: string;
|
|
1090
|
-
} & EditorKeysProps;
|
|
1091
|
-
|
|
1092
|
-
const GridCellEditor = ({ style, value, onNav, onClose }: GridCellEditorProps) => {
|
|
1093
|
-
const { model, range } = useSheetContext();
|
|
1094
|
-
const notifier = useRef<CellRangeNotifier>();
|
|
1095
|
-
useEffect(() => {
|
|
1096
|
-
if (range) {
|
|
1097
|
-
// Update range selection in formula.
|
|
1098
|
-
notifier.current?.(rangeToA1Notation(range));
|
|
1099
|
-
}
|
|
1100
|
-
}, [range]);
|
|
1101
|
-
|
|
1102
|
-
const extension = useMemo(
|
|
1103
|
-
() => [
|
|
1104
|
-
editorKeys({ onNav, onClose }),
|
|
1105
|
-
sheetExtension({ functions: model.graph.getFunctions() }),
|
|
1106
|
-
rangeExtension((fn) => (notifier.current = fn)),
|
|
1107
|
-
],
|
|
1108
|
-
[model],
|
|
1109
|
-
);
|
|
1110
|
-
|
|
1111
|
-
return (
|
|
1112
|
-
<div
|
|
1113
|
-
role='cell'
|
|
1114
|
-
style={style}
|
|
1115
|
-
className={mx('z-20 flex', fragments.cellSelected)}
|
|
1116
|
-
onClick={(ev) => ev.stopPropagation()}
|
|
1117
|
-
>
|
|
1118
|
-
<CellEditor autoFocus value={value} extension={extension} />
|
|
1119
|
-
</div>
|
|
1120
|
-
);
|
|
1121
|
-
};
|
|
1122
|
-
|
|
1123
|
-
//
|
|
1124
|
-
// StatusBar
|
|
1125
|
-
//
|
|
1126
|
-
|
|
1127
|
-
const SheetStatusBar = () => {
|
|
1128
|
-
const { model, cursor, range } = useSheetContext();
|
|
1129
|
-
|
|
1130
|
-
let value;
|
|
1131
|
-
let isFormula = false;
|
|
1132
|
-
if (cursor) {
|
|
1133
|
-
value = model.getCellValue(cursor);
|
|
1134
|
-
if (typeof value === 'string' && value.charAt(0) === '=') {
|
|
1135
|
-
value = model.graph.mapFunctionBindingFromId(model.mapFormulaIndicesToRefs(value));
|
|
1136
|
-
isFormula = true;
|
|
1137
|
-
} else if (value != null) {
|
|
1138
|
-
value = String(value);
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
return (
|
|
1143
|
-
<div className={mx('flex shrink-0 justify-between items-center px-4 py-1 text-sm border-x border-gridLine')}>
|
|
1144
|
-
<div className='flex gap-4 items-center'>
|
|
1145
|
-
<div className='flex w-16 items-center font-mono'>
|
|
1146
|
-
{(range && rangeToA1Notation(range)) || (cursor && addressToA1Notation(cursor))}
|
|
1147
|
-
</div>
|
|
1148
|
-
<div className='flex gap-2 items-center'>
|
|
1149
|
-
<FunctionIcon className={mx('text-greenText', isFormula ? 'visible' : 'invisible')} />
|
|
1150
|
-
<span className='font-mono'>{value}</span>
|
|
1151
|
-
</div>
|
|
1152
|
-
</div>
|
|
1153
|
-
</div>
|
|
1154
|
-
);
|
|
1155
|
-
};
|
|
1156
|
-
|
|
1157
|
-
//
|
|
1158
|
-
// Debug
|
|
1159
|
-
//
|
|
1160
|
-
|
|
1161
|
-
const SheetDebug = () => {
|
|
1162
|
-
const { model, cursor, range } = useSheetContext();
|
|
1163
|
-
const [, forceUpdate] = useState({});
|
|
1164
|
-
useEffect(() => {
|
|
1165
|
-
// TODO(burdon): This is called without registering a listener.
|
|
1166
|
-
const accessor = createDocAccessor(model.sheet, []);
|
|
1167
|
-
const handleUpdate = () => forceUpdate({});
|
|
1168
|
-
accessor.handle.addListener('change', handleUpdate);
|
|
1169
|
-
handleUpdate();
|
|
1170
|
-
return () => {
|
|
1171
|
-
accessor.handle.removeListener('change', handleUpdate);
|
|
1172
|
-
};
|
|
1173
|
-
}, [model]);
|
|
1174
|
-
|
|
1175
|
-
return (
|
|
1176
|
-
<div
|
|
1177
|
-
className={mx(
|
|
1178
|
-
'z-20 absolute right-0 top-20 bottom-20 w-[30rem] overflow-auto scrollbar-thin',
|
|
1179
|
-
'border border-gridLine text-xs bg-neutral-50 dark:bg-black text-cyan-500 font-mono p-1 opacity-80',
|
|
1180
|
-
)}
|
|
1181
|
-
>
|
|
1182
|
-
<pre className='whitespace-pre-wrap'>
|
|
1183
|
-
{JSON.stringify(
|
|
1184
|
-
{
|
|
1185
|
-
cursor,
|
|
1186
|
-
range,
|
|
1187
|
-
cells: model.sheet.cells,
|
|
1188
|
-
rowMeta: model.sheet.rowMeta,
|
|
1189
|
-
columnMeta: model.sheet.columnMeta,
|
|
1190
|
-
formatting: model.sheet.formatting,
|
|
1191
|
-
},
|
|
1192
|
-
undefined,
|
|
1193
|
-
2,
|
|
1194
|
-
)}
|
|
1195
|
-
</pre>
|
|
1196
|
-
</div>
|
|
1197
|
-
);
|
|
1198
|
-
};
|
|
1199
|
-
|
|
1200
|
-
//
|
|
1201
|
-
// Grid
|
|
1202
|
-
//
|
|
1203
|
-
|
|
1204
|
-
export const Sheet = {
|
|
1205
|
-
Root: SheetRoot,
|
|
1206
|
-
Main: SheetMain,
|
|
1207
|
-
Rows: SheetRows,
|
|
1208
|
-
Columns: SheetColumns,
|
|
1209
|
-
Grid: SheetGrid,
|
|
1210
|
-
Cell: SheetCell,
|
|
1211
|
-
StatusBar: SheetStatusBar,
|
|
1212
|
-
Debug: SheetDebug,
|
|
1213
|
-
};
|
|
1214
|
-
|
|
1215
|
-
export type { SheetRootProps, SheetMainProps, SheetRowsProps, SheetColumnsProps, SheetGridProps, SheetCellProps };
|