@canvas-components/xlsx-table
v0.1.7
Published
Framework-agnostic canvas spreadsheet core for xlsx-table.
Readme
@canvas-components/xlsx-table
@canvas-components/xlsx-table 是面向在线表格 / 类 Excel 编辑场景的纯原生 Canvas 核心包。
它与 list-table 职责分离:
| 包 | 场景 |
| ------------ | -------------------------------- |
| list-table | 业务数据表、查询、汇总、列配置 |
| xlsx-table | workbook / sheet / cell 网格编辑 |
破坏性数据契约变更
只接收 SheetJS WorkBook / WorkSheet / CellObject,工作表名称为标识,地址使用 A1。旧逻辑 JSON、旧存档及旧协同载荷明确拒绝,不提供映射、类型别名或自动迁移。业务单元格直接读写 t/v/f/F/w/z/s/l/c,f 不带前导等号。
SheetJS 0.18.5 不保证复杂文件无损。普通保存默认阻止有损/未知内容;界面列出摘要后,用户明确确认才允许降级。失效公式缓存和损坏结构始终禁止保存。含未知/外部/数组公式的工作簿保留缓存并阻止值、公式与结构修改。
安装
pnpm add @canvas-components/xlsx-table快速使用
import { XlsxTable } from "@canvas-components/xlsx-table";
import { parseXlsx, writeXlsx } from "@canvas-components/xlsx-table/file";
const workbook = await parseXlsx(await file.arrayBuffer());
const table = new XlsxTable(container, {
workbook,
width: "100%",
height: 480,
});
table.mount();
// 当前完整原生工作簿直接写出;不会从展示文本重建表格。
const bytes = await writeXlsx(table.getWorkbook());当前能力
- 单 sheet 网格渲染、行列头、虚拟滚动
- 多 sheet:切换 / 新增 / 重命名 / 复制 / 左右移动 / 删除,按 sheet 记忆选区与滚动
- 活动格 / 选区:点击、拖拽、Shift 扩展、Cmd/Ctrl+点击多选区、行头/列头整行整列、Cmd/Ctrl+A(used→整表)、方向键导航
- 单元格编辑:双击 / Enter / F2 / 直接输入,
onEditorRequest驱动宿主编辑器 - 公式栏状态:
onFormulaBarChange/commitFormulaBarText - 复制粘贴:TSV 与 Excel/飞书互通;Delete/Backspace 清空;
clipboard.expandBounds预留扩展 - 填充柄:copy 策略拖拽填充
- 填充柄:auto 策略(数字/日期等差序列,文本回退 copy)
- 列宽 / 行高拖拽:表头边界拖拽调整,支持撤销
- 单元格样式:选区文字颜色、填充色、字号、字重、斜体、下划线、水平对齐,支持撤销
- 单元格数字类型:常规 / 数字 / 金额 / 百分比
- 行列折叠:行头 / 列头右键折叠,支持撤销
- 冻结窗格:
sheet.frozen.rows/columns,滚动时冻结区固定 - 公式求值:
=A1+B1、比较/拼接、IF/AND/OR、文本与数学函数、Sheet1!A1跨 sheet、区间与循环检测 - 公式依赖图:增量拓扑重算、跨 sheet 下游传播、大区间依赖边、命名区域依赖
- 公式依赖图调试快照:
getFormulaDependencyDebugSnapshot/createFormulaDependencyDebugSnapshot - 易失公式:
TODAY()/NOW()自动纳入增量重算种子 - 数组公式 MVP:数组常量
{1,2;3,4}、SEQUENCE/TRANSPOSEspill、#SPILL!阻塞提示 - 命名区域:workbook 级
namedRanges,支持=TaxRate/=SUM(Sales) - 公式自动补全:函数名与命名区域候选(可复用
getFormulaAutocompleteSuggestions) - 高频函数:
COUNTBLANK/SUMIF(S)/COUNTIF(S)/VLOOKUP/HLOOKUP/INDEX/MATCH/TEXT/VALUE/ 日期函数 - 撤销重做:Cmd/Ctrl+Z / Shift+Z / Y,统一 patch history
- 多人协同:
collaborationadapter、远端 patch apply、presence 选区渲染、mock 双实例同步 - WebSocket 协同适配器:
WebSocketXlsxCollaborationAdapter(JSON 协议、重连、出站队列、patch-ack) - 服务端权威源:
XlsxAuthoritativeCollaborationRoom、单元格级 OT 裁剪、鉴权 hook、公式权威重算
与 list-table 的边界
- 不把 Excel 编辑能力塞回 list-table。
- xlsx-table 不实现业务查询、汇总行、行选择等数据表语义。
- 核心包不依赖 React;React 适配见
@canvas-components/react-xlsx-table。 - 多人协同提供协议、Mock 与 WebSocket adapter,不绑定具体业务服务端;见
docs/collaboration-protocol.md。
主要 API
const table = new XlsxTable(container, options);
table.mount();
table.updateOptions(options);
table.getSelection();
table.getWorkbook();
table.getFormulaDependencyDebugSnapshot();
table.getFormulaBarState();
table.getActiveSheetId();
table.getNamedRanges();
table.setActiveSheet(sheetId);
table.addSheet({ name?: string, activate?: boolean });
table.duplicateSheet(sheetId, { name?: string, activate?: boolean });
table.moveSheet(sheetId, targetIndex);
table.renameSheet(sheetId, name);
table.removeSheet(sheetId);
table.upsertNamedRange(named);
table.removeNamedRange(name);
table.startEditing("edit");
table.commitFormulaBarText("=A1");
table.undo();
table.redo();
table.getHistoryState();
table.getRemoteSelections();
table.getPresenceRecord();
table.acknowledgePatches(["patch_id"]);
table.hideRows([1, 2]);
table.hideColumns([3]);
table.clearSelectionValues();
table.setSelectionCellStyle({ fontWeight: "600", color: "#1456f0" });
table.toggleSelectionCellStyle({ fontWeight: "600" }, { fontWeight: undefined });
table.clearSelectionCellStyle();
table.clearSelectionCellStyleKeys(["color", "backgroundColor"]);
table.setSelectionCellType("money");
table.setFrozenPane({ rows: 1, columns: 1 });
table.freezeToSelection();
table.clearFreeze();
table.insertFormulaTemplate("=SUM()");
table.insertRows(2);
table.insertColumns(3);
table.deleteRows(2);
table.deleteColumns(3);
table.destroy();命名区域:
import {
upsertNamedRange,
removeNamedRange,
} from "@canvas-components/xlsx-table";
table.upsertNamedRange({
name: "Sales",
sheetId: "sheet1",
range: {
startRowIndex: 0,
startColumnIndex: 1,
endRowIndex: 9,
endColumnIndex: 1,
},
});常用 options:
workbook/theme/readonlyonWorkbookChange/onSelectionChangeonEditorRequest/onFormulaBarChangeonActiveSheetChangeclipboard.expandBoundscollaboration/onLocalPatches
文档站
本地文档:
/components/xlsx-table/basic/components/xlsx-table/editing/components/xlsx-table/collab/components/xlsx-table/multi-sheet
交互补充:拖动列头右边界改列宽,拖动行头下边界改行高。
协同协议:docs/collaboration-protocol.md。
WebSocket 协同接入
import {
XlsxTable,
WebSocketXlsxCollaborationAdapter,
} from "@canvas-components/xlsx-table";
const adapter = new WebSocketXlsxCollaborationAdapter({
roomId: "workbook-1",
user: { id: "user-1", name: "Alice" },
url: "wss://example.com/xlsx?room=workbook-1&token=...",
});
const table = new XlsxTable(container, {
workbook,
collaboration: {
self: { id: "user-1", name: "Alice" },
adapter,
},
});
adapter.onPatchAck(({ patchIds }) => table.acknowledgePatches(patchIds));服务端权威源
import { XlsxAuthoritativeCollaborationRoom } from "@canvas-components/xlsx-table";
const room = new XlsxAuthoritativeCollaborationRoom({
workbook,
conflictPolicy: "server",
authorizePatch: ({ user, patch }) => {
return user.id === patch.authorId || "无权写入该 patch";
},
});
const result = room.submitPatches({
user,
patches,
});
// result.acceptedPatches 广播给房间;result.ackPatchIds 回发给提交者。引擎维护
控制器划分、状态归属、数据提交与生命周期约定见 引擎维护说明。
文件、事务和视图边界
parseXlsx/writeXlsx位于@canvas-components/xlsx-table/file,输入输出直接使用 SheetJS 原生对象。- Core 的
getDocumentStamp()与commitNativeCellTransaction({stamp,changes:[{sheetName,address,after}]})支持多表原子提交。文档替换重置历史和 session;迟到 revision 拒绝。一次编辑只增加一个撤销条目。 - React ref 提供
openXlsxFile(file)、exportWorkbookXlsx(fileName?)、cancelFileTask()。菜单“完整工作簿 (.xlsx)”保存所有表与被筛选行;CSV/TXT/JSON 属展示值导出。 - React
fileWorkerFactory可注入模块 Worker。默认动态加载@canvas-components/xlsx-table/file/worker-loader(浏览器 ESM);CJS 主入口不会直接求值import.meta.url。worker 是自包含产物,取消会 terminate。 - 筛选条件、冻结 pane、排序指示器留在运行时;手动隐藏使用
!rows/!cols。排序移动原生 cell,格式、链接和备注随单元格保留。默认 legacy writer 不能完整写回字体、填充、边框、冻结和规则;显式 native writer 支持保留原包并写入值/普通公式,暂拒绝格式和结构编辑;请阅读 能力矩阵。 - 协同线协议为
sheetjs-v1,原生快照与 A1 变更支持可逆日期/二进制编码。原子批次失败不部分提交,重复 ID 不重复记账。结构快照要求基版本一致,不宣称任意并发结构 OT。 - React 表单复用 react-components;唯一直接原生表单是隐藏文件选择 input。核心包不依赖 React。
真实 Excel/WPS/LibreOffice 往返互通尚未执行,不宣称兼容任意 XLSX。
命令事实与 A0 文件门禁
核心导出 XlsxCommandId、xlsxCommandDefinitions、getXlsxCommandDefinition(id) 和 inspectXlsxCommandSafety(book,id),不依赖 React。结构/排序控制器在执行前检查原生部件及公式依赖;不安全操作返回原有 false/null,保持内容、选区、revision 与历史不变。清除排序也属于记录重排,使用相同门禁。
普通 SheetJS bookFiles 的根条目/内部标记不是文件部件。未被 cell cm/vm 引用且已识别的 XLDAPR 定义不阻止普通文件操作;未知或被引用的元数据、高级规则与图形仍保守保护。该预检不实现 OOXML 引用更新或高级 writer,不代表完整 XLSX 互通能力。
数据清洗与本地打印
previewCleaning(request) 预检去首尾空白、矩形范围去重、文本分列和明确格式的类型转换;applyCleaning(plan, { allowOverwrite }) 验证文档版本并重建候选,一次操作一次撤销。默认排除标题、隐藏及被筛选行,去重只压缩范围内的参与行,拒绝不安全的公式依赖/数组/合并;非空分列目标必须明确确认覆盖。原生单元格和样式、链接、备注直接参与操作,没有旧数据格式映射。单次最多 200,000 个源/目标单元格。
createPrintPlan(options) 从完整原生范围生成基础单元格分页,renderXlsxPrintDocument(plan) 输出同一版式的独立 HTML。支持 A4/Letter、横竖向、边距、缩放、适应宽度、网格线、行列标题、可见性和重复首行;最多 200 页。预览不改工作簿或历史,不依赖屏幕 Canvas/缩放;复杂对象和规则不在本地打印还原范围内。已有原生源包支持通过 updateSheetSettings 显式保存基本页面设置与受支持的打印名称;预览不自动保存。
B0.1:原生包基础工具
新增独立入口 @canvas-components/xlsx-table/file/ooxml,提供异步 readXlsxPackage / writeXlsxPackage、命名空间 XML 原文编辑及标准关系/内容类型索引:
import {
readXlsxPackage,
inspectOoxmlPackage,
writeXlsxPackage,
} from "@canvas-components/xlsx-table/file/ooxml";
const parts = await readXlsxPackage(bytes, { signal });
const index = inspectOoxmlPackage(parts);
console.log(index.workbookPath, index.sheets);
const repacked = await writeXlsxPackage(parts, { signal });Node 条件入口使用内置 zlib,最低 Node 18.12 已验证发布 ESM/CJS;浏览器/Worker 使用原生 DecompressionStream('deflate-raw'),不支持时明确拒绝,不回退 CFB 解压。CFB 仅封装可信部件,写出后按真实路径逐字节核对;没有新增依赖。默认限制为压缩50MiB、总解压200MiB、单XML32MiB和1万条目,options.limits 可调整正整数限制,options.signal 支持取消。ZIP64、加密、分卷、非规范编码别名拒绝;非ASCII物理路径可读,但当前CFB写出明确拒绝,不偷偷改名。
浏览器限制的是应用接受/保留的输出,不能宣称底层解压器具备硬内存上限;业务应在Worker调用并控制并发。XML编辑器不依赖DOM、拒绝DTD/未知实体,保留未改原文及未知扩展;节点范围按本次原文解析,子内容片段先在目标命名空间内验证。索引目前面向标准XLSX工作簿/worksheet,按真实关系定位,不按sheet编号猜路径;外部关系只记录,不访问。
这些工具处理原生文件部件和可丢弃索引,不引入旧数据映射。readNativeXlsxParts(book) 从现有 files 复制原生字节,但不能证明旧CFB导入流的完整性。本轮没有接管既有 parseXlsx / writeXlsx;部件事务、引用更新、协同能力、样式/冻结/页面设置持久化仍待B0.2/B0.3。不能把独立包重封装验证推导为现有编辑/保存已无损。
B0.2:单元格与部件原子事务
commitNativeTransaction({ stamp, changes, parts }) 将原生cells与标准files部件作为一次操作提交;每个part为 { path, expectedHash, after },原先不存在时hash为null,删除时after为undefined。getDocumentStamp()、fingerprintNativePart(book,path) 和 getNativePartsState() 提供操作前置条件。成功仅增加一个revision/历史条目;undo/redo同时恢复触及的cells、范围、部件及来源证据,未触及大部件不复制进每条历史。
core先检查只读/文档版本、旧部件指纹、路径/重复、XML与关系闭包以及当前工作表对应关系。B0.3前禁止raw parts直接覆盖sheetData、尺寸、合并、链接等cells拥有的节点,或绕过稳定索引修改workbook/styles/sharedStrings。原始包为空时,可先通过file/ooxml的 createXlsxPackageParts(book) 生成最小源包,再以同一次原子操作注册;注册会验证包中单元格事实与当前工作簿一致,撤销恢复原本没有files的状态。
prepareNativeTransaction 是不依赖DOM的本地候选预检;XlsxNativePartsClient.preview(book,stamp,tx,{signal}) 使用独立发布Worker预检,携带原生files/bytes与来源诊断,取消或换任务会terminate。Worker只返回结论,不能代替公共commit的当前版本与权限复核。Node没有Worker时可显式使用本地预检,客户端不会悄悄改用同步大批处理。
协同新增 sheetjs-parts-v2 和整体 native-parts patch。WebSocket adapter须设置 nativeParts:true,发送支持版本;只有当前连接收到v2快照、服务接受该基线且没有待确认批次,部件编辑才开启。断线、重连、换文档或v1基线会锁定。服务端gateway必须向room的 setNativePartsPeers 提供完整成员能力,成员变化后广播新snapshot;仅配置客户端布尔值不能代替握手。老端仍可查看;v1与普通workbook patch都不能偷改当前files,部件不降级成cell patches。服务端要求原子部件批次基于当前版本,冲突整批拒绝。
本轮仍未接管文件合成。旧writer保存时会列明尚未合成的部件路径并要求明确有损确认;不能声称part编辑已完整写回XLSX。B0.3继续实现稳定样式索引、结构引用与冻结/页面设置持久化。注入自定义Worker factory时,预检响应的计数/状态仍按可信factory契约使用;核心提交不依赖这些计数。
B0.3a:安全读取与显式原生文件合成
parseXlsx 已使用平台原生 ZIP 验证路径、实际解压长度、CRC、关系和 XML,再交 SheetJS 解析可信 stored 包;files 保留原部件字节。非 ASCII 物理路径当前因可信封装限制明确拒绝,工作表显示名称支持中文。
import { parseXlsx, writeNativeXlsx, writeXlsx } from "@canvas-components/xlsx-table/file";
const book = await parseXlsx(bytes);
book.Sheets[book.SheetNames[0]].A1.v = 31;
const result = await writeNativeXlsx(book, { signal });
// 等价:await writeXlsx(book, { writer: "native", signal });原生模式以原 XML 为底稿更新值、普通公式和缓存、稀疏单元格及 dimension;保留旧样式/共享字符串索引、未知属性/扩展和其他部件。新文本使用 inlineStr,公式文本缓存使用 str。输入必须含可验证的原生 files;不修改当前工作簿或历史。共享/数组/未知依赖公式、图形/透视/外部对象缓存涉及值变化时保守拒绝。
本阶段拒绝格式、数字格式、行列配置、合并、链接、备注和工作簿元数据编辑,allowLossy 不能让原生模式偷偷降级。默认 writeXlsx 与现有工具栏仍使用 legacy 并保留有损提示;B0.3b/B0.3c 继续完成样式和结构/冻结/页面设置持久化,B0整体未完成。文件 Worker 支持显式 writer,并在成功响应中确认模式;取消终止独立 Worker。
B0.3b:稳定样式与仅格式粘贴
原生读取从标准 styles.xml 补齐 Cell.s 的字体、填充、边框和对齐,并解析行列默认样式;原始部件不变。显式 native writer 支持基础样式/数字格式编辑,按差异追加并复用 font/fill/border/xf/numFmt,旧记录及索引不重排。只改字体不会用不完整投影覆写旧保护或扩展;无法安全清除的未知字段明确拒绝。
pasteSpecial(snapshot,{mode:"formats"}) 和选择性粘贴面板的“仅格式”复制支持的 s/z,不改变目标值、公式、链接、备注;同文档快照、转置/跳空及一次撤销重做沿用现有门禁。空白格的行列默认格式也被捕获,无法完整表示的源样式通过 Tooltip 说明并拒绝复制格式。格式意图保存在标准 cells,原文件仍是底稿,导出时合成;不新增私有工作簿格式。
工具栏金额格式现使用 "¥"#,##0.00,只改变 z,文本表头不被改成数字。默认数字采用 General,整数不出现尾部小数点,小数值保持原精度。默认保存仍为 legacy 并保留有损提示;结构引用和冻结/页面设置持久化继续 B0.3c,真实 Excel/WPS/LibreOffice 软件互通仍未验收。
B0.3c1:冻结与基本页面设置
已有原生源包的表格通过 getSheetSettings(sheetName?) 读取标准设置,updateSheetSettings({frozen,print},{sheetName?,stamp?}) 原子应用,支持一次撤销/重做。print 本阶段支持 A4/Letter、方向、统一边距mm、比例10%–200%、适应一页宽、打印网格线/行列标题。既有 setFrozenPane 在有源文件时改为部件事务,克隆和worker重开从标准pane恢复;没有files时仍为原有本地冻结,持久化接口明确拒绝,不有损初始化源包。
打印面板可单独“保存页面设置”,只提交实际编辑的支持字段;预览和打印不自动改文档。普通比例和适应宽度按有效模式处理,未知高度/多页模式、自定义纸张和非统一边距提示限制并保留原文件设置。显式选择预设纸张才清理覆盖它的自定义尺寸属性。源保护、只读、旧版本与协同部件能力门禁有效;未知视图扩展不能安全移除时拒绝。
打印范围/重复标题的标准命名区域接续见 B0.3c2a。默认文件writer仍legacy并保留兼容性提示,原生导出才能保留这些设置;真实外部Excel/WPS/LibreOffice互通尚未验收。
B0.3c2a:标准引用与打印命名区域
updateSheetSettings({printArea,repeatRows},{sheetName?,stamp?}) 将打印范围和重复行写入标准 _xlnm.Print_Area / _xlnm.Print_Titles,null 明确清除。引用使用真实工作表顺序的 localSheetId,原文保留一般名称、其他作用域、未知属性和其余workbook节点;未知字段无法安全清除时拒绝。只读、保护、迟到版本和公式依赖继续阻止不安全修改。Workbook.Names 中的打印项来自files只读投影,一般名称编辑意图不会被投影覆盖。
createPrintPlan() 默认采用已保存范围和受支持的首行标题;显式 {range:undefined} 使用实际内容范围。多区域、跨表/外部/动态范围、列标题、多行标题不能完整预览时显示限制并保留原文件。源名称的中文、实体、注释和Hidden从标准XML读取,不复用SheetJS历史UTF-8属性问题。标准定义依据 Microsoft DefinedName。
打印名称保存暂限本地;parts-v2未约定新workbook节点所有权,协同客户端和房间均拒绝这类增量。既有冻结与基本页面设置仍遵循已支持的parts-v2门禁。无源初始化、一般结构引用、默认native切换与外部软件互通继续后续阶段。
B0.3d:无源工作簿的安全原生初始化
file 和 file/ooxml 导出 createNativeSourceWorkbook(book),只为可证明保留的程序工作簿生成第一个标准源包;已有files保持原对象。当前值/公式缓存、基础s/z、格式空白格、合并、支持的行列尺寸/隐藏、链接备注、日期基准和静态Names等与原始标准字段核验;未知字段、丢失源痕迹、无定义数字样式索引、数组/富文本/Date单元格等明确拒绝。最多200000个格和矩形范围,不通过有损roundtrip过滤未知内容。
表格和React ref提供 getNativeSourceInitializationState() / initializeNativeSource({stamp?})。本地成功只增加一次revision和一次历史,通过onWorkbookChange发布完整标准WorkBook;undo恢复原来的无源对象事实,redo完整恢复。宿主通知在内部提交后执行,异常报告控制台,不能导致已发布却谎报rejected或丢失撤销。只读、旧stamp和协同配置拒绝;新完整初始化快照不发旧协议workbook增量。
源包含真实标准默认元数据,structuredClone和Worker后原生导出仍可用,不依赖WeakMap/sourceHash找回内容。固定SheetJS版本的中文Custprops及备注作者从实际XML修正。初始化不会恢复已遗失的高级源部件,不对已有来源整本重写。默认writer仍legacy,显式native接口可使用已支持能力;标准元数据差异、结构引用和默认模式收口继续下一阶段。真实Excel/WPS/LibreOffice未验收。
B0.3e:标准名称与行列元数据合成
显式 native writer 现在支持全局单区域静态 Workbook.Names 的新增、更新和注释字段,以及标准 !rows / !cols 的尺寸、隐藏和分组层级。名称保留原顺序、局部/打印/动态定义和未知属性;名称引用变化经过公式依赖门禁并刷新支持公式缓存,未知或动态依赖会拒绝。行高按OOXML ht点值与96dpi像素互转,列宽按标准MDW和1/256字符宽量化;列区间变更会拆分已知区间并保留邻列属性,未知扩展无法安全拆分时拒绝。
普通 setRowHeights / setColumnWidths 的标准字段可随 native 导出保存;cells、样式、公式缓存和未知worksheet节点保持原有合成规则。局部、内建、外部、3D、多区域和动态名称继续只读,通用结构插入/删除/改名、外部Office互通和默认native模式继续后续阶段。单位依据 Microsoft Column 与 Excel 行列尺寸说明。
阶段 B0.3e 已补齐标准名称和行列元数据 native 合成。名称编辑保留未知节点和其他作用域,行列尺寸按标准单位写回,UTF-16 XML 编码保持不变。Tooltip 仅用于工具栏,其他面板和控件不显示 Tooltip。协同 Worker 的打印名称预检能力参数仍待后续协议能力阶段统一。
B0.4:通用结构引用与原子事务(当前范围)
单表行列插入/删除通过结构证明器统一更新单元格、公式、静态名称、合并、行列元数据和范围;动态/外部/3D/未知依赖直接拒绝。原生导出保留 sheetData、mergeCells、rows、cols、名称和未知 XML,简单原生源可本地插入、导出、撤销和重做。含未支持对象或协同旧协议的结构入口提前禁用,避免发送旧 workbook patch。工作表新增/删除、完整结构协同 wire 和默认 native 模式留在后续阶段。
