xiaodao-spreader 0.1.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Leeqwind
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,91 @@
1
+ # xiaodao-spreader
2
+
3
+ A Vue 3 spreadsheet component based on Canvas 2D, providing an Excel-like editing experience.
4
+
5
+ ![Preview](./img/preview.png)
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ pnpm install
11
+ pnpm dev
12
+ ```
13
+
14
+ ## Basic Usage
15
+
16
+ ```vue
17
+ <template>
18
+ <Spreadsheet v-model:data="myData" />
19
+ </template>
20
+
21
+ <script setup lang="ts">
22
+ import { ref } from 'vue'
23
+ import Spreadsheet from './components/spreader/spreader.vue'
24
+
25
+ const myData = ref([])
26
+ </script>
27
+ ```
28
+
29
+ ## Props
30
+
31
+ | Prop | Type | Default | Description |
32
+ |------|------|---------|-------------|
33
+ | `data` (v-model) | `SheetModelData[]` | `[]` | Multi-sheet data (two-way binding) |
34
+ | `rowCount` | `number` | `200` | Total rows per sheet |
35
+ | `colCount` | `number` | `26` | Total columns per sheet |
36
+ | `width` | `number \| string` | — | Component width (pixel value, percentages not supported) |
37
+ | `height` | `number \| string` | — | Component height (pixel value, percentages not supported) |
38
+ | `theme` | `'light' \| 'dark'` | `'light'` | Theme mode |
39
+ | `locale` | `string` | `'zh-CN'` | Language (`'zh-CN'` / `'en-US'`) |
40
+
41
+ ### SheetModelData Type
42
+
43
+ ```typescript
44
+ interface SheetModelData {
45
+ name: string
46
+ cells: Record<string, { value: string; style?: Record<string, unknown> }>
47
+ colWidths?: Record<number, number> // Column index → width (px), only stores non-default values
48
+ rowHeights?: Record<number, number> // Row index → height (px), only stores non-default values
49
+ }
50
+ ```
51
+
52
+ The key of `cells` is a `"col,row"` string format (e.g., `"0,0"` represents A1).
53
+
54
+ ## Features
55
+
56
+ - **Canvas Rendering**: High DPI support, virtual rendering only draws cells in the visible area
57
+ - **Multiple Sheets**: Tab bar switching, create, rename, duplicate, delete, drag-to-reorder
58
+ - **Formula Engine**: Supports `=SUM(A1:B5)` range summation with dependency tracking and caching
59
+ - **Undo/Redo**: Ctrl+Z / Ctrl+Y, up to 50 steps
60
+ - **Selection**: Click / drag / Shift+Click selection, row/column headers and corner cell for one-click select-all
61
+ - **Editing**: Double-click / F2 / direct input to enter edit mode, formula bar supports formula input
62
+ - **Keyboard Navigation**: Arrow keys / Tab / Enter / Home / End / Ctrl+Home / Ctrl+End
63
+ - **Clipboard**: Ctrl+C/V/X copy, paste, cut (TSV format), formula references automatically offset
64
+ - **Row/Column Operations**: Right-click menu to insert, delete, cut, copy, paste entire rows/columns
65
+ - **Column Width/Row Height Dragging**: Drag header edges to resize
66
+ - **Context Menu**: Independent context menus for cells, row/column headers, corner cell, and tab bar; nested submenu support (e.g., Calculate → Sum)
67
+ - **Theme**: Light / Dark dual theme
68
+ - **Internationalization**: Chinese / English right-click menus
69
+ - **Touch**: Swipe scrolling + double-tap to enter edit mode
70
+ - **Custom Scrollbar**: Physical scrollbar with arrow buttons and draggable thumb
71
+ - **Responsive**: ResizeObserver monitors container size changes
72
+
73
+ ## Tech Stack
74
+
75
+ - **Framework**: Vue 3 (Composition API + `<script setup>`)
76
+ - **Build**: Vite 5
77
+ - **Language**: TypeScript (strict)
78
+ - **Rendering**: Canvas 2D
79
+ - **Package Manager**: pnpm
80
+
81
+ ## Build
82
+
83
+ ```bash
84
+ pnpm build # Type check + production build
85
+ pnpm preview # Preview build output
86
+ pnpm type-check # Type check only
87
+ ```
88
+
89
+ ## Design Document
90
+
91
+ See [DOC.md](./DOC.md) for details on architecture, file structure, data model, rendering pipeline, interaction model, and extension points.
package/dist/style.css ADDED
@@ -0,0 +1 @@
1
+ .spreadsheet-outer[data-v-5100e6d6]{flex:1;display:flex;flex-direction:column;overflow:hidden}.formula-bar[data-v-5100e6d6]{display:flex;align-items:start;height:32px;min-height:32px;padding:0;gap:0}.formula-bar__cell-label[data-v-5100e6d6]{width:48px;min-width:48px;height:28px;line-height:28px;text-align:center;font-size:12px;font-weight:600;color:var(--sp-formula-bar-label-color);background:var(--sp-formula-bar-label-bg);border:1px solid var(--sp-formula-bar-label-border);border-radius:2px;-webkit-user-select:none;user-select:none}.formula-bar__input[data-v-5100e6d6]{flex:1;height:28px;border:1px solid var(--sp-formula-bar-input-border);border-radius:2px;outline:none;padding:0 6px;margin-left:4px;font-size:13px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Microsoft YaHei,sans-serif;color:var(--sp-formula-bar-input-color);background:var(--sp-formula-bar-input-bg)}.formula-bar__input[data-v-5100e6d6]:focus{border-color:var(--sp-formula-bar-input-focus-border);box-shadow:0 0 0 1px var(--sp-formula-bar-input-focus-shadow)}.tab-bar[data-v-5100e6d6]{display:flex;align-items:stretch;height:30px;min-height:30px;background:var(--sp-tab-bar-bg);border-bottom:1px solid var(--sp-tab-bar-border);-webkit-user-select:none;user-select:none;margin-bottom:4px}.tab-list[data-v-5100e6d6]{display:flex;align-items:flex-end;flex:1;overflow:hidden;gap:1px;padding:0 1px}.tab-item[data-v-5100e6d6]{display:flex;align-items:center;height:28px;min-width:0;max-width:120px;padding:0 10px;cursor:pointer;border:1px solid var(--sp-tab-inactive-border);background:var(--sp-tab-inactive-bg);color:var(--sp-tab-inactive-color);font-size:14px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Microsoft YaHei,sans-serif;white-space:nowrap;transition:background .1s}.tab-item[data-v-5100e6d6]:hover{background:var(--sp-tab-hover-bg)}.tab-item--active[data-v-5100e6d6]{height:28px;background:var(--sp-tab-active-bg);color:var(--sp-tab-active-color);border-color:var(--sp-tab-bar-border) var(--sp-tab-bar-border) var(--sp-tab-active-bg);border-bottom:2px solid var(--sp-tab-active-border);font-size:16px}.tab-item--active[data-v-5100e6d6]:hover{background:var(--sp-tab-active-bg)}.tab-item__name[data-v-5100e6d6]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tab-rename-input[data-v-5100e6d6]{width:100%;height:18px;border:none;border-radius:0;outline:none;padding:0;font-size:16px;font-family:inherit;background:var(--sp-tab-active-bg);color:var(--sp-tab-active-color);box-sizing:border-box}.tab-bar__add-btn[data-v-5100e6d6]{width:24px;min-width:24px;height:24px;margin:3px 4px 0 3px;border:none;background:transparent;color:var(--sp-tab-add-btn-color);font-size:16px;line-height:22px;text-align:center;cursor:pointer;padding:0}.tab-bar__add-btn[data-v-5100e6d6]:hover{background:var(--sp-tab-add-btn-hover-bg)}.spreadsheet-wrapper[data-v-5100e6d6]{flex:1;position:relative;overflow:hidden;background:var(--sp-wrapper-bg)}.grid-canvas[data-v-5100e6d6]{position:absolute;top:0;left:0;display:block;outline:none;cursor:cell}.grid-canvas[data-v-5100e6d6]:focus{outline:none}.v-scrollbar[data-v-5100e6d6]{position:absolute;right:0;top:0;width:11px;height:calc(100% - 11px);display:flex;flex-direction:column;background:var(--sp-wrapper-bg)}.h-scrollbar[data-v-5100e6d6]{position:absolute;left:0;bottom:0;height:11px;width:calc(100% - 11px);display:flex;background:var(--sp-wrapper-bg)}.sb-corner[data-v-5100e6d6]{position:absolute;right:0;bottom:0;width:11px;height:11px;background:var(--sp-wrapper-bg)}.sb-btn[data-v-5100e6d6]{width:11px;height:11px;min-width:11px;min-height:11px;border:none;background:var(--sp-scroll-btn-bg, #e8e8e8);display:flex;align-items:center;justify-content:center;cursor:pointer;padding:0;-webkit-user-select:none;user-select:none}.sb-btn[data-v-5100e6d6]:hover{background:var(--sp-scroll-btn-hover-bg, #d0d0d0)}.sb-btn[data-v-5100e6d6]:active{background:var(--sp-scroll-btn-active-bg, #c0c0c0)}.sb-arrow[data-v-5100e6d6]{display:block;width:0;height:0}.sb-arrow--up[data-v-5100e6d6]{border-left:3px solid transparent;border-right:3px solid transparent;border-bottom:4px solid var(--sp-scroll-btn-color, #666)}.sb-arrow--down[data-v-5100e6d6]{border-left:3px solid transparent;border-right:3px solid transparent;border-top:4px solid var(--sp-scroll-btn-color, #666)}.sb-arrow--left[data-v-5100e6d6]{border-top:3px solid transparent;border-bottom:3px solid transparent;border-right:4px solid var(--sp-scroll-btn-color, #666)}.sb-arrow--right[data-v-5100e6d6]{border-top:3px solid transparent;border-bottom:3px solid transparent;border-left:4px solid var(--sp-scroll-btn-color, #666)}.sb-track[data-v-5100e6d6]{flex:1;position:relative;background:var(--sp-scroll-track-bg, rgba(0,0,0,.06))}.sb-track--v[data-v-5100e6d6]{width:11px}.sb-track--h[data-v-5100e6d6]{height:11px}.sb-thumb[data-v-5100e6d6]{position:absolute;border-radius:3px;background:var(--sp-scroll-thumb);cursor:default}.sb-thumb[data-v-5100e6d6]:hover{background:var(--sp-scroll-thumb-hover)}.sb-thumb--v[data-v-5100e6d6]{left:1px;right:1px;min-height:16px}.sb-thumb--h[data-v-5100e6d6]{top:1px;bottom:1px;min-width:16px}.cell-editor[data-v-5100e6d6]{position:absolute;border:2px solid var(--sp-cell-editor-border);outline:none;padding:0 4px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Microsoft YaHei,sans-serif;font-size:13px;color:var(--sp-cell-editor-color);background:var(--sp-cell-editor-bg);box-shadow:0 0 0 1px var(--sp-cell-editor-shadow);z-index:10;box-sizing:border-box;min-width:0}.context-menu{position:fixed;z-index:10000;background:#fff;border:1px solid #ccc;border-radius:4px;box-shadow:0 2px 8px #00000026;padding:4px 0;min-width:120px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Microsoft YaHei,sans-serif;font-size:13px}.context-menu__item{padding:6px 20px;cursor:pointer;color:#333;white-space:nowrap;position:relative;display:flex;align-items:center;justify-content:space-between}.context-menu__item:hover{background:#e8f0fe}.context-menu__item--disabled{color:#bbb;cursor:default}.context-menu__item--disabled:hover{background:transparent}.context-menu__arrow{margin-left:16px;margin-right:-5px;width:0;height:0;border-top:3px solid transparent;border-bottom:3px solid transparent;border-left:4px solid #888}.context-submenu{display:none;position:absolute;left:100%;top:-4px;background:#fff;border:1px solid #ccc;border-radius:4px;box-shadow:0 2px 8px #00000026;padding:4px 0;min-width:100px;z-index:10001}.context-submenu--left{left:auto;right:100%}.context-menu__item:hover>.context-submenu{display:block}.context-submenu .context-menu__item{justify-content:flex-start}
@@ -0,0 +1,15 @@
1
+ import type { ThemeColors } from './types';
2
+ export declare const HEADER_WIDTH = 52;
3
+ export declare const HEADER_HEIGHT = 24;
4
+ export declare const DEFAULT_COL_WIDTH = 100;
5
+ export declare const DEFAULT_ROW_HEIGHT = 24;
6
+ export declare const MIN_COL_WIDTH = 30;
7
+ export declare const MIN_ROW_HEIGHT = 24;
8
+ export declare const SB_SIZE = 11;
9
+ export declare const ARROW_SIZE = 11;
10
+ export declare const SCROLL_STEP = 50;
11
+ export declare const UNDO_MAX = 50;
12
+ export declare const i18n: Record<string, Record<string, string>>;
13
+ export declare function t(locale: string, key: string): string;
14
+ export declare const lightTheme: ThemeColors;
15
+ export declare const darkTheme: ThemeColors;
@@ -0,0 +1,36 @@
1
+ import type { CellData } from './types';
2
+ /** 解析区域引用(如 "A1:B5" → {sc, sr, ec, er}) */
3
+ export declare function parseRangeRef(rangeRef: string, colCount: number, rowCount: number): {
4
+ sc: number;
5
+ sr: number;
6
+ ec: number;
7
+ er: number;
8
+ } | null;
9
+ /** 获取单元格数值(用于公式参数求值),返回 0 表示空或非数值 */
10
+ export declare function getNumericValue(col: number, row: number, cells: Record<string, CellData>, colCount: number, rowCount: number): number;
11
+ export declare function clearEvalCache(): void;
12
+ export declare function getEvalCache(): Map<string, number>;
13
+ /** 对指定单元格公式求值,返回数值结果;若为普通值则尝试解析为数字 */
14
+ export declare function evalFormula(key: string, cells: Record<string, CellData>, colCount: number, rowCount: number): number | null;
15
+ /** 解析公式中引用的单元格列表(用于依赖追踪) */
16
+ export declare function parseFormulaRefs(formula: string, colCount: number, rowCount: number): string[];
17
+ /** 计算单元格显示值(公式则求值,否则返回原值) */
18
+ export declare function computeCellValue(col: number, row: number, cells: Record<string, CellData>, colCount: number, rowCount: number): string;
19
+ /** 将公式中所有单元格引用偏移 dCol, dRow(支持 $ 绝对引用) */
20
+ export declare function shiftFormulaRefs(formula: string, dCol: number, dRow: number, colCount: number, rowCount: number, colToLabelFn: (col: number) => string): string;
21
+ /**
22
+ * 公式依赖追踪管理器
23
+ * 正向:formulaKey → [depKey, ...]
24
+ * 反向:depKey → Set<formulaKey>
25
+ */
26
+ export declare class FormulaDeps {
27
+ private deps;
28
+ private rev;
29
+ private dirty;
30
+ clear(key: string): void;
31
+ set(key: string, newDeps: string[]): void;
32
+ markDirty(key: string): void;
33
+ getDirtyAndClear(): Set<string>;
34
+ /** 从 cells 快照重建所有依赖关系 */
35
+ rebuild(cells: Record<string, CellData>, colCount: number, rowCount: number): void;
36
+ }
@@ -0,0 +1,28 @@
1
+ import type { SheetModelData } from './types';
2
+ type __VLS_Props = {
3
+ rowCount?: number;
4
+ colCount?: number;
5
+ width?: number | string;
6
+ height?: number | string;
7
+ theme?: 'light' | 'dark';
8
+ locale?: string;
9
+ };
10
+ type __VLS_PublicProps = {
11
+ 'data'?: SheetModelData[];
12
+ } & __VLS_Props;
13
+ declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
14
+ "update:data": (value: SheetModelData[]) => any;
15
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
16
+ "onUpdate:data"?: ((value: SheetModelData[]) => any) | undefined;
17
+ }>, {
18
+ rowCount: number;
19
+ colCount: number;
20
+ theme: "light" | "dark";
21
+ locale: string;
22
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {
23
+ formulaBarRef: HTMLInputElement;
24
+ wrapperRef: HTMLDivElement;
25
+ canvasRef: HTMLCanvasElement;
26
+ editInputRef: HTMLInputElement;
27
+ }, HTMLDivElement>;
28
+ export default _default;
@@ -0,0 +1,4 @@
1
+ import { type ComputedRef } from 'vue';
2
+ import type { ThemeColors } from './types';
3
+ export declare function useTheme(theme: ComputedRef<'light' | 'dark'>): ComputedRef<ThemeColors>;
4
+ export declare function buildOuterStyle(colors: ThemeColors, width: number | string | undefined, height: number | string | undefined, resolveSize: (val: number | string | undefined) => number | null): Record<string, string>;
@@ -0,0 +1,115 @@
1
+ export interface CellCoord {
2
+ col: number;
3
+ row: number;
4
+ }
5
+ export interface SelectionRange {
6
+ startCol: number;
7
+ startRow: number;
8
+ endCol: number;
9
+ endRow: number;
10
+ }
11
+ export interface CellData {
12
+ value: string;
13
+ style: Record<string, unknown> | null;
14
+ }
15
+ export interface Range {
16
+ start: number;
17
+ end: number;
18
+ }
19
+ export interface SpreadsheetOptions {
20
+ rowCount?: number;
21
+ colCount?: number;
22
+ colWidths?: Record<number, number>;
23
+ rowHeights?: Record<number, number>;
24
+ }
25
+ export interface SpreadsheetData {
26
+ [cellRef: string]: {
27
+ value: string;
28
+ style?: Record<string, unknown>;
29
+ };
30
+ }
31
+ export interface SheetModelData {
32
+ name: string;
33
+ cells: Record<string, {
34
+ value: string;
35
+ style?: Record<string, unknown>;
36
+ }>;
37
+ colWidths?: Record<number, number>;
38
+ rowHeights?: Record<number, number>;
39
+ }
40
+ export interface SheetState {
41
+ id: string;
42
+ name: string;
43
+ cells: Record<string, CellData>;
44
+ selection: SelectionRange | null;
45
+ activeCell: CellCoord;
46
+ scrollX: number;
47
+ scrollY: number;
48
+ colWidths: number[];
49
+ rowHeights: number[];
50
+ }
51
+ export interface UndoSnapshot {
52
+ cells: Record<string, CellData>;
53
+ colWidths: number[];
54
+ rowHeights: number[];
55
+ }
56
+ export interface ContextMenuItem {
57
+ label: string;
58
+ action?: () => void;
59
+ disabled?: boolean;
60
+ children?: ContextMenuItem[];
61
+ }
62
+ export interface ThemeColors {
63
+ bg: string;
64
+ gridBg: string;
65
+ headerBg: string;
66
+ headerBorder: string;
67
+ headerText: string;
68
+ headerSep: string;
69
+ cornerBg: string;
70
+ gridLine: string;
71
+ selectionBg: string;
72
+ activeCellBorder: string;
73
+ cellText: string;
74
+ scrollTrack: string;
75
+ scrollThumb: string;
76
+ formulaBarBg: string;
77
+ formulaBarBorder: string;
78
+ formulaBarLabelText: string;
79
+ formulaBarLabelBg: string;
80
+ formulaBarLabelBorder: string;
81
+ formulaBarInputBg: string;
82
+ formulaBarInputBorder: string;
83
+ formulaBarInputText: string;
84
+ formulaBarInputFocusBorder: string;
85
+ formulaBarInputFocusShadow: string;
86
+ wrapperBg: string;
87
+ cellEditorBorder: string;
88
+ cellEditorText: string;
89
+ cellEditorBg: string;
90
+ cellEditorShadow: string;
91
+ tabBarBg: string;
92
+ tabBarBorder: string;
93
+ tabActiveBg: string;
94
+ tabActiveText: string;
95
+ tabActiveBorder: string;
96
+ tabInactiveBg: string;
97
+ tabInactiveText: string;
98
+ tabInactiveBorder: string;
99
+ tabHoverBg: string;
100
+ tabAddBtnColor: string;
101
+ tabAddBtnHoverBg: string;
102
+ tabScrollBtnColor: string;
103
+ tabScrollBtnHoverBg: string;
104
+ scrollbarThumb: string;
105
+ scrollbarThumbHover: string;
106
+ scrollBtnBg: string;
107
+ scrollBtnColor: string;
108
+ scrollBtnHoverBg: string;
109
+ scrollBtnActiveBg: string;
110
+ scrollTrackBg: string;
111
+ }
112
+ export interface Point {
113
+ x: number;
114
+ y: number;
115
+ }
@@ -0,0 +1,26 @@
1
+ import type { CellCoord } from './types';
2
+ /** 列号 → Excel 风格列标签 (0→A, 25→Z, 26→AA) */
3
+ export declare function colToLabel(col: number): string;
4
+ /** Excel 风格列标签 → 列号 (A→0, Z→25, AA→26) */
5
+ export declare function labelToCol(label: string): number;
6
+ /** 解析 CSS 尺寸值,返回像素数值或 null */
7
+ export declare function resolveSize(val: number | string | undefined): number | null;
8
+ /** 写入剪贴板(兼容旧浏览器) */
9
+ export declare function writeClipboardText(text: string): void;
10
+ /** 根据行列索引生成单元格标志符 */
11
+ export declare function cellKey(col: number, row: number): string;
12
+ /** 将单元格引用字符串转为坐标(如 "A1" → {col:0, row:0}) */
13
+ export declare function parseCellRef(ref: string, colCount: number, rowCount: number): CellCoord | null;
14
+ /** 命中测试:根据 X 坐标二分查找所在列 */
15
+ export declare function hitTestCol(x: number, colPositions: number[], colCount: number): number;
16
+ /** 命中测试:根据 Y 坐标二分查找所在行 */
17
+ export declare function hitTestRow(y: number, rowPositions: number[], rowCount: number): number;
18
+ /** 获取 canvas 相对坐标 */
19
+ export declare function getCanvasXY(e: MouseEvent, canvas: HTMLCanvasElement | null): {
20
+ x: number;
21
+ y: number;
22
+ };
23
+ /** 网格可视区域宽度(减去列头和滚动条) */
24
+ export declare function gridViewW(viewW: number, sbSize: number): number;
25
+ /** 网格可视区域高度(减去行头和滚动条) */
26
+ export declare function gridViewH(viewH: number, sbSize: number): number;
@@ -0,0 +1,4 @@
1
+ import Spreader from './components/spreader/spreader.vue';
2
+ export default Spreader;
3
+ export { Spreader };
4
+ export type { CellCoord, SelectionRange, CellData, Range, SpreadsheetOptions, SpreadsheetData, SheetModelData, SheetState, UndoSnapshot, ContextMenuItem, ThemeColors, Point, } from './components/spreader/types';