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 +21 -0
- package/README.md +91 -0
- package/dist/style.css +1 -0
- package/dist/types/components/spreader/constants.d.ts +15 -0
- package/dist/types/components/spreader/formula.d.ts +36 -0
- package/dist/types/components/spreader/spreader.vue.d.ts +28 -0
- package/dist/types/components/spreader/theme.d.ts +4 -0
- package/dist/types/components/spreader/types.d.ts +115 -0
- package/dist/types/components/spreader/utils.d.ts +26 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/xiaodao-spreader.es.js +1632 -0
- package/dist/xiaodao-spreader.umd.js +4 -0
- package/package.json +52 -0
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
|
+

|
|
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';
|