ooxml-excel-editor 1.2.0 → 1.3.0

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/CHANGELOG.md CHANGED
@@ -2,6 +2,85 @@
2
2
 
3
3
  本项目遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/) 与 [语义化版本](https://semver.org/lang/zh-CN/)。
4
4
 
5
+ ## [1.3.0] - 2026-06-08
6
+
7
+ **Vue 2 兼容子入口 + 三壳 UI 1:1 复刻 + 独立 dev scripts** — Vue 2.7+ / Vue 3 / React 三个壳视觉与交互完全一致 (Vue 3 SFC 是参考实现 Standard, Vue 2 / React 1:1 复刻).
8
+
9
+ ### 新增
10
+
11
+ - **`ooxml-excel-editor/vue2` 子入口** (`src/vue2/ExcelViewer.ts`, ~1000 行 render function 版): Vue 2.7+ 兼容壳, 跟 Vue 3 壳 1:1 功能对齐
12
+ - 全部 28 项 props (src / workbook / jsonOptions / templateFile / fileName / theme / cellStyle / cellImageFit / imageLightbox / openLinks / plugins / toolbar / editable / cellReadOnly / readOnlyRanges / editableTargets / strictDimensions / readOnlyCellStyle / editor / recalc / formulaEngine / contextMenu / exportProgress / transformModel / templateName)
13
+ - 全部 15+ events 跟 Vue 3 同名 (rendered / error / progress / cell-click / cell-dblclick / selection-change / sheet-change / hyperlink-click / cell-change / edit-start / edit-commit / dim-change / dirty-change / image-change / struct-change / permission-denied / before-context-menu / context-menu)
14
+ - 完整命令式 API (80+ 方法) 跟 Vue 3 `viewerApi` / React `ExcelViewerHandle` 对齐
15
+ - 插件系统完整 (events / cellStyle / theme / overlay / toolbar / setup / contextMenu / editor)
16
+ - **独立 dev scripts**: `npm run dev:vue3` (port 5300, 默认) / `npm run dev:react` (port 5301) / `npm run dev:vue2` (port 5302), 三个 demo 进程隔离 + 独立端口
17
+
18
+ ### UI 1:1 复刻 (★ 新中心原则)
19
+
20
+ **Vue 3 SFC 是参考实现 (Standard)**, 任何 UI 变动先 Vue 3 落地再复刻到 Vue 2 / React. 三壳 + 三 demo 视觉/交互对齐:
21
+
22
+ - **顶部 ViewerToolbar**: 文件名 + " · 模板: <name>" + 灰色 "N 个工作表" + "导出 ▾" 下拉 (PNG / PDF 位图 / PDF 矢量 / 打印 / 导出设置…) + 缩放 [− / select / +]
23
+ - **Action ToolBar**: 完整 9 项内置工具 (find / filter / clear-filter / copy / wrap-text / template / image-tools / freeze / export / zoom) + SVG 图标 (`src/components/toolbar-icons.ts`) + 下拉子菜单 + separator 分组
24
+ - **状态栏**: 选区范围 (A1:E5) + 计数 / 求和 / 平均 / 最大 / 最小 (调 `renderer.selectionStats`)
25
+ - **cell tooltip**: `onTooltip` hook → ref<TooltipState>, 支持 default / comment 黄底批注样式
26
+ - **ExportProgressOverlay**: 居中模态 + stage 标签 + 进度条 + 取消按钮 + `chain()` 包装所有 export API + `:export-progress` prop
27
+ - **ExportDialog**: 高级导出配置 (范围 / 清晰度 / 内容 / PDF 类型 / 纸张) — 工具栏 "导出设置…" 弹出
28
+ - **overlay scoped slot**: `<template v-slot:overlay="{ rectOf, rectOfRange, tick }">` 跨壳同 API
29
+ - **loading / error / empty 三态浮层**: progress bar + 错误提示 + 空状态
30
+ - **三 demo 入口** (App.vue / vue2-demo / react-demo) **绿色头共享** `src/demo-shared/demo-bar.css`:
31
+ - 共同按钮: 选择 .xlsx / 加载示例 / JSON 示例 / 编辑模式 / PDF(页码+水印) / 数据→JSON / 贴合 select / ↓XLSX / ↓CSV / ↓JSON
32
+ - 编辑模式按钮组: 设置可编辑 (EditTargets 白名单 dialog) / 高亮只读 / B 加粗选区 / 合并 / 拆分 / 背景 / 字体 / 清除填充 / 整表嵌入 / 格→图 / +行 / -行
33
+ - "⋯ 更多" 溢出折叠 (ResizeObserver + 测量行)
34
+
35
+ ### 修复
36
+
37
+ - **Vue 2 patch 复用 DOM 致 controller stale 引用 (致命空白渲染 bug)**: Vue 2 patch children 算法没 key 时按 tag 匹配, 把 `.ov-render-area` div 复用成 `.ov-toolbar` (改 className + 替换内容), controller.els.renderArea 变 stale (指向 toolbar 元素), 内部 canvas/overlays 都被 Vue 一并清掉. **修复**: 所有 chrome 子节点加唯一 `key` + canvas/overlays/scroller/editor-slot 改 imperative DOM (onMounted createElement + appendChild, Vue 完全不碰)
38
+ - **chrome computed 读 renderTick 导致滚动卡顿**: `fbCanEdit` / `formulaBarEditString` / `renderActionToolbar` 读了 `renderTick.value`, 每帧 scroll → root render function 重跑 → 整个 chrome DOM patch. **修复**: 只依赖低频 selVersion/findVersion/filterVersion
39
+ - **`updated()` 钩子内改 reactive → 死循环**: `demoRemeasure()` 改 `demoItemWidths` 触发 re-render → 又 `updated()` → CPU 100% 卡死. **修复**: 改用 `watch` 监听具体根本依赖
40
+ - **rAF 等浏览器 layout**: chrome 刚加上时 `renderArea.clientHeight` 是中间态 36px (不是最终 537px), `await nextTick()` 后加 `await rAF` 保证 layout 完成再 measure
41
+
42
+ ### 已知限制
43
+
44
+ - `dist/vue2.js` 424 KB (含内嵌 core) — Vue 2 build pass 独立, 不共享 `chunks/`。后续探索 rollup 多入口共享 chunk
45
+ - `dist/vue2.d.ts` 类型声明暂未生成 (vue-tsc 不认 Vue 2 SFC; 本入口是 .ts 后续可补)
46
+ - Vue 2 e2e 覆盖待补 (Playwright 多 entry 配置)
47
+
48
+ ### 文档
49
+
50
+ - 新 [docs/Vue2.md](docs/Vue2.md) — Vue 2 子入口完整使用文档 + 跟 Vue 3 差异表 + Vue 2 壳特殊坑(函数 ref / ResizeObserver / updated 死循环 / template v-for key)
51
+ - [README.md](README.md) 加 Vue 2 入口章节 + 四个子入口对照表 + 三壳 UI 1:1 说明
52
+ - [ARCHITECTURE.md](ARCHITECTURE.md) 加 demo-shared / 三壳对齐 / 第 7 中心原则段
53
+ - [CLAUDE.md](CLAUDE.md) 加第 7 中心原则 (UI 1:1 复刻) + UI 1:1 工作流 + Vue 2 壳特殊坑 + 路线图更新
54
+
55
+ ### Commits (累计 9 个)
56
+
57
+ `ba6470c` 修空白渲染 + dev scripts → `0f9718a` statusbar/tooltip/ExportProgressOverlay → `d74c9f1` ExportDialog/overlay slot → `e735e8f` Vue 2 1:1 工具栏 → `b075e05` React 1:1 工具栏 → `5462f5e` demo 绿色头 + 演示按钮 → `ee199c3` EditTargets dialog 迁移 → `411e328` Vue 2 ⋯ 更多溢出折叠 → `a312222` 修死循环
58
+
59
+ ---
60
+
61
+ ## [1.2.1] - 2026-06-08
62
+
63
+ **WPS 风格长文本编辑** — 默认编辑器从 `<input>` 升级 `<textarea>`, 输入长文本自动换行 + 向下撑高, 跟 WPS / Excel 用户习惯一致。
64
+
65
+ ### 修复 + 改进
66
+ - **★ 默认编辑器换 textarea + 动态撑高(WPS 风格, 2026-06-08)** — 用户反馈 / 截图: 编辑长文本时, 旧 `<input>` 单行水平溢出被裁切, 看不到全文; WPS 同场景**编辑框向下浮起 + 自动换行**显示完整内容。修复:
67
+ - [default-editor.ts](src/core/edit/default-editor.ts) `<input>` → `<textarea>`, `white-space:pre-wrap` + 行高同步渲染层 `LINE_HEIGHT_FACTOR=1.18`. 短文本 (`rows=1`) 视觉跟之前 `<input>` 一致, 不破坏短输入体验. **Shift+Enter** 插入换行, 普通 **Enter** 提交 (跟 Excel/WPS 一致).
68
+ - 新钩子 `CellEditorReturn.getDesiredHeight(widthPx) → number` + `CellEditorContext.reposition()` — 编辑器 input 事件触发 reposition, host 重测撑高. 用 [text.ts 的 `wrapLines`](src/core/render/text.ts) 复用渲染层换行算法, 保证视觉一致.
69
+ - [editor-host.ts](src/core/edit/editor-host.ts) `position()` 新逻辑: 宽度仍 = 列宽 (跟 WPS 一致, **仅向下溢出**); 高度 = `max(单元格原高, getDesiredHeight(列宽))`, 上限 viewport 一半 (防一格输入 10000 字撑爆屏).
70
+ - [`.editor-slot`](src/components/ExcelViewer.vue) + [`.rxl-editor-slot`](src/react/excel-viewer.css) CSS `overflow: hidden → visible`, 让编辑器可溢出原格. z-index:6 仍最上层, 不影响下方网格 / 冻结窗格 / 滚动交互.
71
+ - **提交后行高保持原样** (跟 WPS 一致, 不持久化撑高): 短期视觉浮起仅在编辑期, 提交后单元格还原. 如需永久撑高, 用户单元格设 `wrapText=true` 走已有 autofit (历史行为不变).
72
+ - **★ 公式栏 (fx 内容条) 长文本自动撑高 (2026-06-08)** — 用户反馈: 公式栏内容过长被 `text-overflow:ellipsis` 截断, 看不全公式 / 长文本. 修: Vue + React 壳公式栏 `<input>` → `<textarea>` + `auto-resize` (内容变化时 height = scrollHeight). 跟单元格编辑器一样, 普通 Enter 提交、Shift+Enter 插换行、上限 ~6 行 (max-height: 108px) 超过内部滚动. CSS 同步 — formula-bar 高度自动撑, addr / fx 区垂直居中.
73
+ - **★ vAlign 溢出 fallback 顶对齐 (跟 WPS 一致, 2026-06-08)** — 用户反馈: 输入长文本提交后, 单元格"显示文末而不是开头". 根因: [canvas-renderer.ts:1090](src/core/render/canvas-renderer.ts#L1090) 默认 vAlign='bottom' 时, 文本总高超过单元格高 → `startY = y + h - pad - totalH` 变负, 首行画到格外, 用户看到的是最后几行. 修: 当 `totalH > availH` 时, 强制走 `'top'` 分支 (显示文头, 末尾裁切). 跟 WPS / Excel 行为一致.
74
+ - **`CellEditorFactory` 返回类型扩展** — 旧 `HTMLElement | { el, destroy? }` → 新增可选 `{ el, destroy?, getDesiredHeight?(w) }`. 旧自定义编辑器 100% 向后兼容 (不返 `getDesiredHeight` = 高度锁单元格高, 老行为).
75
+ - **`CellEditorContext.reposition()`** — 给自定义编辑器: 主动通知 host 重测撑高. 内容变化后调一次. 不调 = 不撑 (老行为).
76
+
77
+ ### 测试
78
+ - 新单测 [default-editor.test.ts](src/core/edit/__tests__/default-editor.test.ts) 10 项: textarea 类型 / commit-cancel 行为 / Enter 移动 / Shift+Enter 换行 / 失焦提交 / 样式贴合 / `getDesiredHeight` 钩子存在 / 兜底 (依赖 canvas measureText 的具体撑高数测试走 e2e 覆盖)
79
+ - 新 e2e [edit-long-text.e2e.ts](e2e/edit-long-text.e2e.ts) Vue + React 双覆盖 12 项: 编辑器 textarea / 短文本不撑大 / 长文本撑高 / 动态变化 / Esc 取消 / Shift+Enter 换行 + Enter 提交
80
+ - devDependency 加 `jsdom` (单元格编辑器单测依赖 DOM 环境;**仅 dev**, 不影响 dist)
81
+
82
+ ---
83
+
5
84
  ## [1.2.0] - 2026-06-08
6
85
 
7
86
  **主线**: 只读边界三件套(白名单 / 尺寸多形态 / 视觉钩子 + permission-denied 事件)+
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ooxml-excel-editor
2
2
 
3
- > Vue 3 + React 高保真 **.xlsx 预览 / 编辑组件** —— Canvas 渲染,**默认只读预览,可选开启编辑**。从零实现解析与渲染,尽量还原微软 Excel 打开工作簿的观感。
3
+ > Vue 3 + **Vue 2** + React 高保真 **.xlsx 预览 / 编辑组件** —— Canvas 渲染,**默认只读预览,可选开启编辑**。从零实现解析与渲染,尽量还原微软 Excel 打开工作簿的观感。**三个壳 UI 1:1 对齐**(Vue 3 SFC 是标准,Vue 2 / React 复刻)。
4
4
 
5
5
  [English](#english) · 中文
6
6
 
@@ -9,8 +9,9 @@
9
9
  **装**(按框架二选一):
10
10
 
11
11
  ```bash
12
- npm i ooxml-excel-editor vue exceljs # Vue
13
- npm i ooxml-excel-editor react react-dom exceljs # React
12
+ npm i ooxml-excel-editor vue exceljs # Vue 3 (默认入口)
13
+ npm i ooxml-excel-editor react react-dom exceljs # React 壳 (/react 子入口)
14
+ npm i ooxml-excel-editor vue@2.7 exceljs # Vue 2.7+ (/vue2 子入口, 1.3.0+)
14
15
  ```
15
16
 
16
17
  **用**(Vue,容器要给高度;`src` 可传 `File` / `Blob` / `ArrayBuffer` / `Uint8Array` / URL 字符串):
@@ -28,7 +29,7 @@ const src = ref<File>() // 绑个 <input type="file" @change> 给它即可
28
29
  </template>
29
30
  ```
30
31
 
31
- 默认**只读预览**;想编辑加 `:editable="true"`(React 同名 `editable`)。React 写法、props/事件表、编辑 / 导出 API 见下文对应章节。
32
+ 默认**只读预览**;想编辑加 `:editable="true"`(React / Vue 2 同名 `editable`)。React / Vue 2 写法、props/事件表、编辑 / 导出 API 见下文对应章节。Vue 2 用法详见 [docs/Vue2.md](./docs/Vue2.md)。
32
33
 
33
34
  > 纯使用者只需读 **安装 / 使用 / API / 编辑 / 导出** 几节即可接入,无需看源码;类型随包发 `.d.ts`(IDE 自动补全)。「扩展 API / 插件 / 开发」是进阶,可跳过。
34
35
 
@@ -55,15 +56,18 @@ const src = ref<File>() // 绑个 <input type="file" @change> 给它即可
55
56
 
56
57
  ## 安装
57
58
 
58
- 一个包,三个子入口 —— **框架无关的 core 引擎被 Vue / React 两个薄壳共享**(`dist/core.js` 只打一份)。按你的框架装对应 peer:
59
+ 一个包,**四个子入口** —— 框架无关的 core 引擎被 Vue 3 / React 两个壳共享(`dist/core.js` 只打一份),Vue 2 因 SFC 编译器跟 Vue 3 冲突独立打包(内嵌 core)。按你的框架装对应 peer:
59
60
 
60
61
  ```bash
61
- # Vue 项目
62
+ # Vue 3 项目
62
63
  npm i ooxml-excel-editor vue exceljs
63
64
 
64
65
  # React 项目
65
66
  npm i ooxml-excel-editor react react-dom exceljs
66
67
 
68
+ # Vue 2.7+ 项目 (1.3.0+)
69
+ npm i ooxml-excel-editor vue@2.7 exceljs
70
+
67
71
  # 只解析 / 读数据 / 导出(不渲染 UI)
68
72
  npm i ooxml-excel-editor exceljs
69
73
 
@@ -73,15 +77,18 @@ npm i echarts jspdf
73
77
  npm i hyperformula
74
78
  ```
75
79
 
76
- 三个入口:
80
+ 四个入口:
77
81
 
78
- | import | 内容 | 需要的 peer |
79
- |---|---|---|
80
- | `ooxml-excel-editor` | Vue 3 组件 `<ExcelViewer>` | `vue` + `exceljs` |
81
- | `ooxml-excel-editor/react` | React 组件 `<ExcelViewer>` | `react` + `react-dom` + `exceljs` |
82
- | `ooxml-excel-editor/core` | 框架无关引擎(解析/渲染/控制器/导出/读数据) | `exceljs` |
82
+ | import | 内容 | 需要的 peer | 体积 (gzip) |
83
+ |---|---|---|---|
84
+ | `ooxml-excel-editor` | **Vue 3** 组件 `<ExcelViewer>` (参考实现 Standard) | `vue@3` + `exceljs` | ~19 KB + 共享 chunks |
85
+ | `ooxml-excel-editor/react` | **React** 组件 `<ExcelViewer>` (1:1 复刻 Vue 3) | `react` + `react-dom` + `exceljs` | ~11 KB + 共享 chunks |
86
+ | `ooxml-excel-editor/vue2` | **Vue 2.7+** 组件 `<ExcelViewer>` (1:1 复刻 Vue 3) | `vue@2.7` + `exceljs` | ~124 KB (内嵌 core) |
87
+ | `ooxml-excel-editor/core` | 框架无关引擎(解析/渲染/控制器/导出/读数据) | `exceljs` | ~1 KB + 共享 chunks |
83
88
 
84
- `exceljs` 必需;`vue` / `react` / `react-dom` 按框架二选一(均为可选 peer);`echarts` / `jspdf` / `hyperformula` 为**可选** peer —— 未装分别只影响"图表渲染""PDF 导出""公式重算",其余正常,且**绝不打包进你的产物**(运行时才动态加载)。
89
+ `exceljs` 必需;`vue` / `react` / `vue@2` 按框架三选一(均为可选 peer);`echarts` / `jspdf` / `hyperformula` 为**可选** peer —— 未装分别只影响"图表渲染""PDF 导出""公式重算",其余正常,且**绝不打包进你的产物**(运行时才动态加载)。
90
+
91
+ > **三壳 UI 1:1**: Vue 3 SFC 是参考实现 (Standard), Vue 2 / React 1:1 复刻视觉与交互 (工具栏 SVG 图标 / 下拉子菜单 / 公式栏 / 状态栏 / dialog / 浮层 / 演示 demo 全部对齐). 详见 [docs/Vue2.md](./docs/Vue2.md) 跟 Vue 3 的差异速查 + [CLAUDE.md](./CLAUDE.md) 第 7 中心原则。
85
92
 
86
93
  > ⚠️ **公式重算的许可证**:默认公式引擎是 [HyperFormula](https://hyperformula.handsontable.com/),**GPL-3.0 / 商业 双授权**。本组件以 `licenseKey: 'gpl-v3'` 调用(适合开源/GPL 场景)。**商业闭源项目**请改用 `formulaEngine` prop 注入你自己持有商业 license 的引擎(或自研引擎),只需实现 `FormulaEngine` 接口即可。不开启 `recalc` 时完全不加载 hyperformula,无许可证负担。
87
94
 
@@ -136,6 +143,40 @@ export function Preview() {
136
143
  }
137
144
  ```
138
145
 
146
+ ### Vue 2 (1.3.0+)
147
+
148
+ 跟 Vue 3 1:1 复刻 (工具栏 / 公式栏 / dialog / 浮层 / events / API 全对齐). Vue 2 用 Options API + Composition API 都行:
149
+
150
+ ```html
151
+ <template>
152
+ <ExcelViewer
153
+ ref="viewer"
154
+ :src="src"
155
+ :file-name="fileName"
156
+ :editable="editMode"
157
+ style="height: 100vh"
158
+ @rendered="(wb) => console.log('已渲染', wb.sheets.length, '个工作表')"
159
+ @cell-change="(p) => console.log(p.before.text, '→', p.after.text)"
160
+ />
161
+ </template>
162
+
163
+ <script>
164
+ import ExcelViewer from 'ooxml-excel-editor/vue2'
165
+ import 'ooxml-excel-editor/style.css'
166
+ import 'ooxml-excel-editor/vue2.css'
167
+
168
+ export default {
169
+ components: { ExcelViewer },
170
+ data: () => ({ src: null, fileName: '', editMode: false }),
171
+ methods: {
172
+ download() { this.$refs.viewer.downloadXlsx() },
173
+ },
174
+ }
175
+ </script>
176
+ ```
177
+
178
+ 完整 Vue 2 用法 + 跟 Vue 3 的差异表 见 [docs/Vue2.md](./docs/Vue2.md)。
179
+
139
180
  ### 仅引擎(不渲染 UI)
140
181
 
141
182
  ```ts
@@ -300,7 +341,9 @@ viewer.value.getRangeData(viewer.value.getSelection()) // 取"我选中的"区
300
341
  />
301
342
  ```
302
343
 
303
- 开编辑后:**双击 / F2 / 直接打字**进格编辑(Enter 提交下移、Tab 右移、Esc 取消),**Ctrl+Z / Ctrl+Y** 撤销重做,拖列头/行头边界改宽高,拖浮动图片移动。只读格 / 区域不进编辑。
344
+ 开编辑后:**双击 / F2 / 直接打字**进格编辑(Enter 提交下移、Tab 右移、Esc 取消、Shift+Enter 插换行),**Ctrl+Z / Ctrl+Y** 撤销重做,拖列头/行头边界改宽高,拖浮动图片移动。只读格 / 区域不进编辑。
345
+
346
+ **长文本编辑(WPS 风格, 1.2.1)**:默认编辑器是 `<textarea>`, 输入长文本自动**换行 + 向下浮起撑高**, 编辑期显示完整内容;提交后单元格行高保持原样(跟 WPS 一致, 不持久化撑高;如需永久保持多行, 单元格设 `wrapText=true`)。自定义编辑器可通过 `CellEditorReturn.getDesiredHeight(width)` 复用此撑高机制 + `ctx.reposition()` 主动触发重撑。
304
347
 
305
348
  ### 命令式编辑 API(模板 ref / 插件 `viewer`)
306
349
 
@@ -634,6 +677,7 @@ npm run build:demo # 构建 demo 站点
634
677
  - [CONTRIBUTING.md](./CONTRIBUTING.md) —— 本地跑通、改动流程、不可破坏的硬约束
635
678
  - [CHANGELOG.md](./CHANGELOG.md) / [RELEASING.md](./RELEASING.md) —— 变更记录 / 发布清单
636
679
  - [docs/编辑权限与只读边界.md](./docs/编辑权限与只读边界.md) —— **EditableTarget 白名单 / DimTarget 尺寸多形态 / readOnlyCellStyle 视觉钩子 / permission-denied 事件** 体系化说明(1.2.0)
680
+ - [docs/Vue2.md](./docs/Vue2.md) —— **Vue 2 兼容子入口**完整文档(1.3.0;`ooxml-excel-editor/vue2` 跟 Vue 3 / React 壳 ~100% 功能对齐)
637
681
 
638
682
  > **React props/events** 与 Vue 对齐(事件用 camelCase 回调:`onRendered`/`onError`/`onCellClick`/`onSelectionChange`/`onSheetChange`/`onHyperlinkClick`),命令式句柄 `ExcelViewerHandle` 与 Vue 组件 ref 同名方法一致。上面「扩展 API」中的**插件 `definePlugin`** 目前服务 Vue 壳;React 壳已可用全部 props/命令式 API/事件,插件 overlay 跨框架化在路线图中。
639
683