ooxml-excel-editor 1.1.0 → 1.2.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/README.md +148 -4
  3. package/dist/chunks/plugin-overlay-BBrNby8v.js +8965 -0
  4. package/dist/chunks/worker-client.stub-CJlmpAgJ.js +190 -0
  5. package/dist/components/ExcelViewer.vue.d.ts +170 -19
  6. package/dist/components/ExportProgressOverlay.vue.d.ts +11 -0
  7. package/dist/components/FilterPopup.vue.d.ts +4 -4
  8. package/dist/components/ViewerToolbar.vue.d.ts +2 -0
  9. package/dist/composables/useExcelDocument.d.ts +1 -0
  10. package/dist/core/edit/clipboard-html.d.ts +24 -0
  11. package/dist/core/edit/commands.d.ts +45 -1
  12. package/dist/core/edit/context-menu.d.ts +19 -0
  13. package/dist/core/edit/edit-controller.d.ts +70 -2
  14. package/dist/core/edit/permissions.d.ts +41 -2
  15. package/dist/core/edit/types.d.ts +62 -0
  16. package/dist/core/export/abort.d.ts +21 -0
  17. package/dist/core/export/exporter.d.ts +2 -1
  18. package/dist/core/export/types.d.ts +8 -0
  19. package/dist/core/export/wps-cellimages.d.ts +6 -0
  20. package/dist/core/export/xlsx-writer.d.ts +9 -0
  21. package/dist/core/format/color.d.ts +5 -0
  22. package/dist/core/format/number-format.d.ts +3 -0
  23. package/dist/core/layout/autofit.d.ts +3 -0
  24. package/dist/core/layout/grid-metrics.d.ts +14 -2
  25. package/dist/core/loader-json.d.ts +23 -0
  26. package/dist/core/model/clone.d.ts +3 -4
  27. package/dist/core/model/inspect.d.ts +43 -0
  28. package/dist/core/model/mutations.d.ts +16 -1
  29. package/dist/core/model/types.d.ts +44 -2
  30. package/dist/core/parser/cell-image-parser.d.ts +9 -0
  31. package/dist/core/parser/row-meta-parser.d.ts +3 -0
  32. package/dist/core/plugin.d.ts +144 -6
  33. package/dist/core/progress.d.ts +23 -0
  34. package/dist/core/render/canvas-renderer.d.ts +56 -2
  35. package/dist/core/render/conditional.d.ts +7 -0
  36. package/dist/core/template/style-overlay.d.ts +9 -0
  37. package/dist/core/viewer/controller.d.ts +209 -6
  38. package/dist/core/viewer/lightbox-host.d.ts +16 -0
  39. package/dist/core.js +1 -1
  40. package/dist/index.js +1169 -821
  41. package/dist/react/ExcelViewer.d.ts +134 -3
  42. package/dist/react/ExportProgressOverlay.d.ts +6 -0
  43. package/dist/react/use-excel-document.d.ts +2 -0
  44. package/dist/react.js +718 -281
  45. package/dist/style.css +1 -1
  46. package/package.json +1 -1
  47. package/dist/chunks/plugin-overlay-Cfnn9EOi.js +0 -7144
  48. package/dist/chunks/worker-client.stub-BQVZfaLd.js +0 -7
@@ -1,10 +1,11 @@
1
1
  import { CellStyleOverride, ColumnInfo, ImageAnchor, MergeRange, RowInfo, SheetModel, WorkbookModel } from '../model/types';
2
2
  import { CellValue } from '../model/data-access';
3
3
  import { CellSnapshot } from '../model/snapshot';
4
- import { CellPos, DimAxis } from './commands';
4
+ import { ParsedClipboard } from './clipboard-html';
5
+ import { CellPos, DimAxis, EditCommand } from './commands';
5
6
  import { StructOp } from '../model/structure';
6
7
  import { FormulaEngineFactory } from '../formula/engine';
7
- export type EditEventName = 'cell-change' | 'edit-start' | 'edit-commit' | 'dim-change' | 'dirty-change' | 'image-change' | 'struct-change';
8
+ export type EditEventName = 'cell-change' | 'edit-start' | 'edit-commit' | 'dim-change' | 'dirty-change' | 'image-change' | 'struct-change' | 'permission-denied';
8
9
  export type EditSource = 'api' | 'ui' | 'undo' | 'redo';
9
10
  /** 结构变更事件载荷(增删行列;restore = 撤销/重做的整体还原) */
10
11
  export interface StructChangePayload {
@@ -93,6 +94,14 @@ export declare class EditController {
93
94
  editCell(row: number, col: number, value: CellValue): boolean;
94
95
  /** 区域批量设值(2D,左上对齐 range.top/left);跳过只读格,返回是否有改动 */
95
96
  editRange(range: MergeRange, values: CellValue[][]): boolean;
97
+ /**
98
+ * 富粘贴(Excel/WPS 复制的 HTML 解析后):值 + 样式 + 合并 + 图片,**整体单次撤销**。
99
+ * start = 落点左上角。跳过只读格。一次 cloneWorkbook 快照 → 应用全部 → 压一条 restore-wb 逆。
100
+ */
101
+ pasteRich(start: {
102
+ row: number;
103
+ col: number;
104
+ }, parsed: ParsedClipboard): boolean;
96
105
  /** 清空区域(跳过只读) */
97
106
  clearRange(range: MergeRange): boolean;
98
107
  /** 给区域套样式覆盖(E5;跳过只读格)。返回是否有改动。前后 style 不同 → 发 cell-change。 */
@@ -102,6 +111,12 @@ export declare class EditController {
102
111
  insertCols(at: number, count?: number): boolean;
103
112
  deleteCols(at: number, count?: number): boolean;
104
113
  private structEdit;
114
+ /** 合并区域(吸收相交旧合并,清空被覆盖格只留左上锚点)。单格不合并。
115
+ * Phase A 补漏 (2026-06-08): 区域内任一格只读 → 拒绝整次合并 + emit permission-denied. */
116
+ mergeCells(range: MergeRange): boolean;
117
+ /** 拆分:移除与区域相交的所有合并。
118
+ * Phase A 补漏 (2026-06-08): 区域内任一格只读 → 拒绝整次拆分 + emit permission-denied. */
119
+ unmergeCells(range: MergeRange): boolean;
105
120
  /** 读当前表全部图片锚点(克隆,防外部改)。 */
106
121
  getImages(): ImageAnchor[];
107
122
  /** 加一张图,返回插入索引(失败 -1)。 */
@@ -113,8 +128,57 @@ export declare class EditController {
113
128
  * baseline 须在变更前由调用方 ensureBaseline() 捕获。
114
129
  */
115
130
  recordImageEdit(index: number, before: ImageAnchor, after: ImageAnchor): void;
131
+ /**
132
+ * 浮动图 → 单元格内嵌图(DISPIMG):把第 imageIndex 张浮动图塞进 (row,col) 格。
133
+ * 图缺字节不可转 → 返 false。入命令栈(undo 走整簿快照还原)。
134
+ */
135
+ convertImageToCell(imageIndex: number, row: number, col: number): boolean;
136
+ /**
137
+ * 批量把多张浮动图嵌入各自的目标格(整表/整列);一次进撤销栈。返回成功嵌入的张数。
138
+ * targets 的 imageIndex 是调用前的浮动图索引(内部按降序 splice,索引互不干扰)。
139
+ * Phase A 补漏: 过滤掉目标格只读的 targets, denied 一次性 emit.
140
+ */
141
+ convertImagesToCells(targets: {
142
+ imageIndex: number;
143
+ row: number;
144
+ col: number;
145
+ }[]): number;
146
+ /**
147
+ * 单元格内嵌图 → 浮动图:把 (row,col) 的 DISPIMG 拎出来变成浮动图(默认 96×96px)。
148
+ * 非内嵌图格 → 返 false。入命令栈。
149
+ * Phase A 补漏: 源格只读 → 拒绝 (拎出来意味着该格内容变了,属于修改).
150
+ */
151
+ convertCellImageToFloat(row: number, col: number, size?: {
152
+ width: number;
153
+ height: number;
154
+ }): boolean;
155
+ /**
156
+ * 批量把多格 DISPIMG 拎成浮动图(选区批量反向);非内嵌图格自动跳过;一次进撤销栈。
157
+ * 返回成功转换的张数。
158
+ * Phase A 补漏: 过滤掉源格只读的, denied 一次性 emit.
159
+ */
160
+ convertCellImagesToFloats(cells: {
161
+ row: number;
162
+ col: number;
163
+ size?: {
164
+ width: number;
165
+ height: number;
166
+ };
167
+ }[]): number;
116
168
  /** 程序化设列宽/行高(API 路径:apply-via-command)。返回是否生效。 */
117
169
  setDimension(axis: DimAxis, index: number, size: number): boolean;
170
+ /**
171
+ * 批量设维度尺寸 (Phase B, 2026-06-08). target 接 number | number[] | {from,to}.
172
+ * 多 index 时聚合成单次 restore-wb undo (跟 convertImagesToCells 范式一致).
173
+ * strictDimensions=true + 白名单未覆盖某 index → skip 那个 + 累计 deniedDims; 整次结束 emit 一次 permission-denied.
174
+ * 返回成功条数 (0 = 全 skip / editable=false).
175
+ */
176
+ setDimensions(axis: DimAxis, indices: number[], size: number, canDim: (i: number) => boolean): number;
177
+ /**
178
+ * 重置某些列宽/行高到默认 (Phase B, 2026-06-08) — 移除 columns/rows Map 条目, 渲染回落 defaultColWidth/Height.
179
+ * 多 index 时单次 restore-wb undo.
180
+ */
181
+ resetDimensions(axis: DimAxis, indices: number[], canDim: (i: number) => boolean): number;
118
182
  /**
119
183
  * 补登一次维度变更(拖拽/autofit 路径:模型已被 renderer 改完,这里只补 undo 项 + 发事件)。
120
184
  * baseline 须在变更前(拖拽起始/autofit 前)由调用方 ensureBaseline() 捕获。
@@ -132,6 +196,10 @@ export declare class EditController {
132
196
  redo(): void;
133
197
  private collectEditable;
134
198
  private pushUndo;
199
+ /** 公开版 pushUndo (Phase B 2026-06-08): 给 controller 的批量 autoFit 用. 一般业务不要直调. */
200
+ pushUndoExternal(inverse: EditCommand): void;
201
+ /** 公开版 markDirty: 给 controller 的批量 autoFit 用. 一般业务不要直调. */
202
+ markDirtyExternal(): void;
135
203
  /** 应用一条命令:建前快照 → apply → 重绘 → 发事件(cell 族 cell-change / dim 族 dim-change)。返回逆命令。 */
136
204
  private exec;
137
205
  }
@@ -1,3 +1,42 @@
1
- import { SheetModel } from '../model/types';
2
- import { EditConfig } from './types';
1
+ import { MergeRange, SheetModel } from '../model/types';
2
+ import { DimTarget, EditableTarget, EditConfig } from './types';
3
+ /** 把 DimTarget 3 形态规范化成 index 数组. 重复 / 越界由调用方各自处理. */
4
+ export declare function normalizeDimTarget(target: DimTarget): number[];
5
+ /**
6
+ * 该列/行能否改尺寸 (Phase B, 2026-06-08).
7
+ * - editable=false → 一律 false
8
+ * - strictDimensions=false (默认) → 仅受全局 editable 控制, 一律 true
9
+ * - strictDimensions=true + 没设白名单 → true (没白名单 = 默认全可编辑)
10
+ * - strictDimensions=true + 设了白名单 → 该轴 index 上至少 1 格在白名单内才 true
11
+ */
12
+ export declare function canEditDimension(sheet: SheetModel, axis: 'col' | 'row', index: number, cfg: EditConfig): boolean;
13
+ /** 单格 / 行 / 列 / 区域 四态 target 命中判定. 形状由有哪些字段自动识别. */
14
+ export declare function matchesEditableTarget(row: number, col: number, t: EditableTarget): boolean;
15
+ /** 把一组格按"是否可编辑"二分. 用于粘贴 / 图片转换 等"批量目标 + 部分跳过"场景. */
16
+ export declare function partitionByEditable(sheet: SheetModel, cells: Array<{
17
+ row: number;
18
+ col: number;
19
+ }>, cfg: EditConfig): {
20
+ allowed: Array<{
21
+ row: number;
22
+ col: number;
23
+ }>;
24
+ denied: Array<{
25
+ row: number;
26
+ col: number;
27
+ }>;
28
+ };
29
+ /** 区域是否**全可编辑**(任一格只读即返 ok=false + firstDenied). 用于 mergeCells / unmergeCells. */
30
+ export declare function rangeAllEditable(sheet: SheetModel, range: MergeRange, cfg: EditConfig): {
31
+ ok: boolean;
32
+ firstDenied?: {
33
+ row: number;
34
+ col: number;
35
+ };
36
+ };
37
+ /** 收集区域里全部不可编辑的格(用于 emit permission-denied 时填 cells 列表). */
38
+ export declare function collectDeniedInRange(sheet: SheetModel, range: MergeRange, cfg: EditConfig): Array<{
39
+ row: number;
40
+ col: number;
41
+ }>;
3
42
  export declare function resolveEditable(sheet: SheetModel, row: number, col: number, cfg: EditConfig): boolean;
@@ -1,5 +1,46 @@
1
1
  import { CellModel, MergeRange } from '../model/types';
2
2
  import { FormulaEngineFactory } from '../formula/engine';
3
+ /**
4
+ * 行/列维度目标 (Phase B, 2026-06-08) —— 用于尺寸 API (setColumnWidth / setRowHeight /
5
+ * autoFitColumns / resetColumnWidth ...) 的参数. 3 种形状自动识别:
6
+ *
7
+ * ┌────────────────────┬──────────────────────────┐
8
+ * │ 形状 │ 含义 │
9
+ * ├────────────────────┼──────────────────────────┤
10
+ * │ `number` │ 单个 index │
11
+ * │ `number[]` │ 多 index (允许不相邻) │
12
+ * │ `{ from, to }` │ 闭区间范围 │
13
+ * └────────────────────┴──────────────────────────┘
14
+ */
15
+ export type DimTarget = number | number[] | {
16
+ from: number;
17
+ to: number;
18
+ };
19
+ /**
20
+ * 可编辑目标 —— 用于 `EditConfig.editableTargets` 的白名单元素;接受 4 种形状,
21
+ * 自动识别(看带哪些字段):
22
+ *
23
+ * ┌──────────────────────────────────────┬─────────────────────────────┐
24
+ * │ 形状 │ 含义 │
25
+ * ├──────────────────────────────────────┼─────────────────────────────┤
26
+ * │ `{ row, col }` │ 单格(命中精确这一格) │
27
+ * │ `{ row }` │ 整行(该行所有列) │
28
+ * │ `{ col }` │ 整列(该列所有行) │
29
+ * │ `{ top, left, bottom, right }` │ 矩形区域(0-based 闭区间) │
30
+ * └──────────────────────────────────────┴─────────────────────────────┘
31
+ *
32
+ * 多个 target 可以**重叠 / 不相邻**;命中**任一**就算可编辑。
33
+ */
34
+ export type EditableTarget = {
35
+ row: number;
36
+ col: number;
37
+ } | {
38
+ row: number;
39
+ col?: undefined;
40
+ } | {
41
+ col: number;
42
+ row?: undefined;
43
+ } | MergeRange;
3
44
  export interface EditConfig {
4
45
  /** 总开关:默认 false = 只读(行为与历史完全一致) */
5
46
  editable?: boolean;
@@ -10,6 +51,27 @@ export interface EditConfig {
10
51
  }) => boolean | void;
11
52
  /** 只读区域(0-based 闭区间);命中即只读 */
12
53
  readOnlyRanges?: MergeRange[];
54
+ /**
55
+ * **可编辑白名单**(2026-06-08 新增) —— 设了就是"白名单模式":默认只读,
56
+ * 只有命中**任一** target 的格才可编辑。**未设(undefined)= 默认全可编辑**
57
+ * (老行为)。**显式传空数组 `[]`** = 全只读(没格在白名单)。
58
+ *
59
+ * 用例: 协同编辑、点检表单只填几格、模板里只让用户改"金额"列等.
60
+ *
61
+ * 与黑名单组合(优先级): `editable=false` → 全只读 ► 不在白名单 → 只读 ►
62
+ * 命中 `readOnlyRanges` → 只读 ► `cellReadOnly` 返 true → 只读 ► 否则可编辑.
63
+ */
64
+ editableTargets?: EditableTarget | EditableTarget[];
65
+ /**
66
+ * **严格尺寸闸门**(Phase B, 2026-06-08) —— 默认 `false`:setColumnWidth / setRowHeight /
67
+ * autoFit / resetDimensions 等尺寸 API 仅受 `editable` 全局闸门控制 (老行为, 简单).
68
+ *
69
+ * 设 `true` + 启用了 `editableTargets` 白名单 → 升级语义:**该列/行至少有 1 格在白名单内**
70
+ * 才能改它的宽高; 否则拒绝 + emit permission-denied (reason='dimension').
71
+ *
72
+ * 跟"白名单未覆盖 = 完全只读"的严格语义一致, 防"用户改不了数据但能改列宽行高"。
73
+ */
74
+ strictDimensions?: boolean;
13
75
  /**
14
76
  * 公式重算(E4):默认 false = 沿用 Excel 缓存值(只读/无公式路径零成本)。
15
77
  * 开启后,编辑公式格或被公式引用的格 → 依赖格自动重算并逐个发 cell-change。
@@ -0,0 +1,21 @@
1
+ /**
2
+ * 长任务防假死 + 取消的两件小工具(框架无关)。
3
+ *
4
+ * 长导出(PDF / PNG / XLSX、批量图片转换)必须在循环中:
5
+ * 1. checkAborted(signal) — 用户调 abortController.abort() 立刻中断
6
+ * 2. await yieldToEvent() — 让出主线程跑一次绘制 / 事件,避免 UI 假死
7
+ * 配合 ExportProgress 的 onProgress 回调,使用者可显示进度条 + 取消按钮。
8
+ */
9
+ /**
10
+ * 让出主线程跑一帧。优先 requestAnimationFrame(浏览器),其次 setTimeout(node / worker 无 rAF)。
11
+ * 用 await yieldToEvent() 包在循环里防 UI 假死。返回 0 让调用方可选取用时间戳。
12
+ */
13
+ export declare function yieldToEvent(): Promise<number>;
14
+ /**
15
+ * 抛 AbortError 让上层 try/catch 区分"取消"与"真出错"。
16
+ * AbortSignal 标准:`signal.aborted === true` 时调用方应 throw `DOMException('Aborted', 'AbortError')`。
17
+ * 跨环境兜底:Node 18+ / 现代浏览器 / Worker 均有 DOMException;无 DOMException 时回退普通 Error 但保留 name。
18
+ */
19
+ export declare function checkAborted(signal?: AbortSignal): void;
20
+ /** 判断是否 AbortError(便于上层在 catch 里区分,避免吞掉真异常) */
21
+ export declare function isAbortError(e: unknown): boolean;
@@ -41,7 +41,8 @@ export declare class WorkbookExporter {
41
41
  /** 导出当前/指定表为图片 Blob(图片为单表;多表请用 PDF) */
42
42
  exportImage(opts?: ImageExportOptions): Promise<Blob>;
43
43
  downloadImage(opts?: ImageExportOptions): Promise<void>;
44
- /** 导出为 PDF Blob(每个目标表分页;需可选依赖 jspdf)。未显式指定的页面参数取自工作表 pageSetup。 */
44
+ /** 导出为 PDF Blob(每个目标表分页;需可选依赖 jspdf)。未显式指定的页面参数取自工作表 pageSetup。
45
+ * 支持 `onProgress` 分阶段(render/compose/paginate/write)报进度 + `signal` 取消(标准 AbortSignal)。 */
45
46
  exportPdf(opts?: PdfExportOptions): Promise<Blob>;
46
47
  downloadPdf(opts?: PdfExportOptions): Promise<void>;
47
48
  /** 打开系统打印(可在对话框另存为 PDF)。页面参数同样默认取自 pageSetup。 */
@@ -1,4 +1,5 @@
1
1
  import { MergeRange } from '../model/types';
2
+ import { ExportProgressFn } from '../progress';
2
3
  /** 选哪些工作表导出 */
3
4
  export type ExportTarget = 'active' | 'all' | number | number[];
4
5
  /** 公共渲染选项(决定底图怎么画) */
@@ -13,6 +14,13 @@ export interface RenderExportOptions {
13
14
  gridlines?: boolean;
14
15
  /** 背景色(默认白) */
15
16
  background?: string;
17
+ /**
18
+ * 长任务进度回调。每个调度阶段(render/compose/paginate/write/zip)各 emit。
19
+ * 大表 / 多表 / 多页时穿插 `await yieldToEvent()` 避免 UI 假死。
20
+ */
21
+ onProgress?: ExportProgressFn;
22
+ /** 取消信号。`abortController.abort()` 后下一个调度点抛 AbortError(标准语义) */
23
+ signal?: AbortSignal;
16
24
  }
17
25
  /** 图片导出选项 */
18
26
  export interface ImageExportOptions extends RenderExportOptions {
@@ -0,0 +1,6 @@
1
+ import { WorkbookModel } from '../model/types';
2
+ /**
3
+ * 把 WorkbookModel.cellImages 回注进 ExcelJS 写出的 zip 字节,返回新 zip 字节。
4
+ * 无内嵌图(或全缺字节)→ 原样返回(零开销)。
5
+ */
6
+ export declare function injectCellImagesIntoZip(zipBytes: Uint8Array, workbook: WorkbookModel): Uint8Array;
@@ -1,4 +1,5 @@
1
1
  import { WorkbookModel } from '../model/types';
2
+ import { ExportProgressFn } from '../progress';
2
3
  export interface XlsxExportOptions {
3
4
  /**
4
5
  * 保真模式:
@@ -6,10 +7,18 @@ export interface XlsxExportOptions {
6
7
  * - `'overlay'`:重载原始 .xlsx,只把编辑后的 值/样式/合并/行高列宽/冻结 叠加上去,**保留** ExcelJS
7
8
  * 能往返的其余部分(条件格式/数据验证/打印设置/定义名/图表等)。需 `sourceBuffer`(壳自动注入);
8
9
  * 缺原件时自动回退 rebuild。注:overlay 不反映 结构增删行列 / 图片 编辑(那类用 rebuild)。
10
+ *
11
+ * WPS 单元格内嵌图(DISPIMG):两种模式都会在 ExcelJS 写出后**于 zip 层回注** `xl/cellimages.xml`
12
+ * + rels + media + Content_Types/workbook-rels(见 wps-cellimages.ts),从模型重建 → 原有的 + App 内
13
+ * 新转的内嵌图导出后都在(blob-only 无字节的图除外)。导出 → 用 WPS 打开,内嵌图正常显示。
9
14
  */
10
15
  fidelity?: 'rebuild' | 'overlay';
11
16
  /** 原始 .xlsx 字节(overlay 模式用;由 exporter 从 host 注入,用方一般不直接传) */
12
17
  sourceBuffer?: ArrayBuffer;
18
+ /** 长任务进度回调(zip 写出前/后 emit `{stage:'zip'}`;exceljs writeBuffer 黑盒) */
19
+ onProgress?: ExportProgressFn;
20
+ /** 取消信号(zip 阶段前后检查) */
21
+ signal?: AbortSignal;
13
22
  }
14
23
  /** WorkbookModel → .xlsx Blob(懒加载 exceljs)。overlay 模式重载原件叠加编辑,否则从模型重建。 */
15
24
  export declare function workbookToXlsxBlob(workbook: WorkbookModel, opts?: XlsxExportOptions): Promise<Blob>;
@@ -1,5 +1,10 @@
1
1
  import { CssColor } from '../model/types';
2
2
  export declare function argbToCss(argb: string | undefined): CssColor | undefined;
3
+ /**
4
+ * 任意 css 颜色 → `#RRGGBB`(大写);供 <input type=color> 回显 / 粘贴样式解析。
5
+ * 支持 #RGB / #RRGGBB / #RRGGBBAA / rgb()/rgba();识别不了返 ''。
6
+ */
7
+ export declare function toHex6(css: string | undefined): string;
3
8
  export declare function indexedToCss(idx: number): CssColor | undefined;
4
9
  /** 主题色 + tint → css。tint > 0 变亮,< 0 变暗(按 OOXML 规范的 HSL Luminance 调整)。 */
5
10
  export declare function themeToCss(themeColors: CssColor[], themeIdx: number, tint?: number): CssColor | undefined;
@@ -3,3 +3,6 @@ export interface FormatResult {
3
3
  color?: string;
4
4
  }
5
5
  export declare function formatValue(value: number | string | boolean | Date | null, code: string | undefined, date1904?: boolean): FormatResult;
6
+ /** Date → Excel 序列号(导出/往返用). date1904=true 时 epoch=1904-01-01;
7
+ * 1900 系统时跳过 phantom 1900-02-29(序号 ≥ 60 加 1 补回 bug 偏移). */
8
+ export declare function dateToSerial(date: Date, date1904: boolean): number;
@@ -1,2 +1,5 @@
1
1
  import { SheetModel, WorkbookModel } from '../model/types';
2
+ /** 失效某 sheet 的"已 fit"标记;下次 autoFitRowHeights 会重新测量。
3
+ * 用法:模型有结构性变化(wrapText 切换 / 富文本编辑 / 列宽改) → 行高需按新内容重撑 → 调一次。 */
4
+ export declare function invalidateAutofit(sheet: SheetModel): void;
2
5
  export declare function autoFitRowHeights(sheet: SheetModel, workbook: WorkbookModel, ctx?: CanvasRenderingContext2D): void;
@@ -1,8 +1,17 @@
1
1
  import { SheetModel } from '../model/types';
2
+ /** Excel/WPS 网格上限:行 1,048,576 / 列 16,384(虚拟外推封顶,防失控) */
3
+ export declare const MAX_GRID_ROWS = 1048576;
4
+ export declare const MAX_GRID_COLS = 16384;
2
5
  export declare class GridMetrics {
3
6
  private sheet;
4
7
  readonly cols: number;
5
8
  readonly rows: number;
9
+ /**
10
+ * 虚拟行/列上限(≥ dimension,≤ Excel 上限)。**仅**用于"滚动出空行"的 spacer 尺寸 /
11
+ * 可视区范围 / 命中夹取 / 表头;**不**改 totalWidth/Height(导出与 data-access 仍按 dimension)。
12
+ */
13
+ readonly vRows: number;
14
+ readonly vCols: number;
6
15
  /** 行表头(显示行号)宽度 px */
7
16
  readonly rowHeaderWidth: number;
8
17
  /** 列表头(显示列字母)高度 px */
@@ -13,7 +22,7 @@ export declare class GridMetrics {
13
22
  private rowHeights;
14
23
  /** 当前缩放比例(几何与字体同步按它缩放,保证缩放后排版一致) */
15
24
  readonly zoom: number;
16
- constructor(sheet: SheetModel, zoom?: number);
25
+ constructor(sheet: SheetModel, zoom?: number, virtualRows?: number, virtualCols?: number);
17
26
  colWidth(c: number): number;
18
27
  rowHeight(r: number): number;
19
28
  private get dcw();
@@ -23,10 +32,13 @@ export declare class GridMetrics {
23
32
  rowTop(r: number): number;
24
33
  get totalWidth(): number;
25
34
  get totalHeight(): number;
35
+ /** 含虚拟外推的总宽/高(spacer 尺寸用;= totalWidth + 虚拟列外推) */
36
+ get virtualWidth(): number;
37
+ get virtualHeight(): number;
26
38
  /** 给定网格 x 坐标,返回所在列。超出数据范围按默认列宽外推。 */
27
39
  colAt(x: number): number;
28
40
  rowAt(y: number): number;
29
- /** 数据单元格的可视区列区间(end 限制在数据范围内,避免画空 cell) */
41
+ /** 数据单元格的可视区列区间(end 夹到虚拟范围 vCols-1,允许滚动出空列;空格 paint 为 no-op) */
30
42
  visibleColRange(scrollX: number, viewW: number): [number, number];
31
43
  visibleRowRange(scrollY: number, viewH: number): [number, number];
32
44
  /** 网格线/表头的可视区列区间(可超出数据范围,铺满视口,模拟 Excel) */
@@ -0,0 +1,23 @@
1
+ import { WorkbookModel } from './model/types';
2
+ export type JsonRow = unknown[] | Record<string, unknown>;
3
+ export type JsonSheetInput = {
4
+ name?: string;
5
+ rows: JsonRow[];
6
+ };
7
+ export type JsonInput = unknown[][] | Record<string, unknown>[] | {
8
+ sheets: JsonSheetInput[];
9
+ };
10
+ export interface JsonLoadOptions {
11
+ /** 对象数组:首行写表头(键名);默认 true。`false` 直接从首行起当数据(用 columns 控制顺序) */
12
+ headerRow?: boolean;
13
+ /** 单表 / 默认表名,缺省 'Sheet1' */
14
+ sheetName?: string;
15
+ /** 主题色 17 色调色板;不给用全黑兜底 */
16
+ themeColors?: string[];
17
+ /** 数字串 → 数字、'TRUE'/'FALSE' → boolean、ISO 日期串 → Date(默认 true) */
18
+ autoInfer?: boolean;
19
+ }
20
+ /** JSON → WorkbookModel。input shape 自动识别。 */
21
+ export declare function jsonToWorkbook(input: JsonInput, opts?: JsonLoadOptions): WorkbookModel;
22
+ /** 用方传 WorkbookModel-shape 时的浅校验(壳分支用) */
23
+ export declare function isWorkbookModel(v: unknown): v is WorkbookModel;
@@ -1,9 +1,8 @@
1
1
  import { WorkbookModel } from './types';
2
- /** 深克隆整个工作簿(供 editable 时懒捕获 baseline)。 */
2
+ /** 轻量深克隆整个工作簿(供 editable 懒捕获 baseline + 结构编辑 undo 快照)。 */
3
3
  export declare function cloneWorkbook(wb: WorkbookModel): WorkbookModel;
4
4
  /**
5
- * 把 baseline(snap)的内容就地还原进 live(不换 live / live.sheets / 各 sheet 的对象身份)。
6
- * 每次都对 snap 取一份新克隆,故 baseline 自身保持原始、可重复还原。
7
- * 约定:editing 期间不增删 sheet,故按 index 对齐;多出的 live sheet 不动。
5
+ * 把 baseline/快照(snap)的内容就地还原进 live(不换 live / live.sheets / 各 sheet 的对象身份)。
6
+ * 每次对 snap 取一份新轻量克隆,故 snap 自身保持可重复还原(redo / 多次 reset)。
8
7
  */
9
8
  export declare function restoreWorkbookInto(live: WorkbookModel, snap: WorkbookModel): void;
@@ -0,0 +1,43 @@
1
+ import { CellStyleOverride, ImageAnchor, MergeRange, SheetModel, WorkbookModel } from './types';
2
+ import { CellSnapshot } from './snapshot';
3
+ export interface CellInspection extends CellSnapshot {
4
+ /** 该格所属合并区(锚点格 = 自身;被覆盖格 = 覆盖它的;否则 null) */
5
+ merge: MergeRange | null;
6
+ /** 是否合并区的左上锚点 */
7
+ isMergeAnchor: boolean;
8
+ /** 覆盖到该格的浮动图(按 sheet.images 索引 + 锚点克隆) */
9
+ floatingImages: Array<{
10
+ index: number;
11
+ anchor: ImageAnchor;
12
+ }>;
13
+ /** WPS 单元格内嵌图(DISPIMG) */
14
+ cellImage: {
15
+ id: string;
16
+ src: string;
17
+ mime?: string;
18
+ } | null;
19
+ /** 命中的数据验证范围(无则 null;详细规则字段当前模型未建模) */
20
+ dataValidation: MergeRange | null;
21
+ /** 命中的条件格式规则索引 + 该规则计算后的等效样式 */
22
+ conditional: Array<{
23
+ ruleIndex: number;
24
+ style: CellStyleOverride;
25
+ }>;
26
+ /** 直读 cell.hyperlink(便于不深入 cell 字段) */
27
+ hyperlink: string | null;
28
+ /** 直读 cell.comment */
29
+ comment: string | null;
30
+ }
31
+ /** 该格落在哪个合并区(锚点 / 被覆盖均算);否则 null */
32
+ export declare function findMergeAt(sheet: SheetModel, row: number, col: number): MergeRange | null;
33
+ /** 一张浮动图的锚点是否覆盖到 (row,col)。twoCellAnchor 用 from..to 矩形;oneCellAnchor 仅 from 格。 */
34
+ export declare function imageAnchorContains(anchor: ImageAnchor, row: number, col: number): boolean;
35
+ /**
36
+ * 聚合查询。
37
+ * @param sheet 工作表
38
+ * @param workbook 工作簿(用于 WPS 内嵌图登记表 cellImages)
39
+ * @param row 0-based
40
+ * @param col 0-based
41
+ * @param date1904 仅作 buildCellSnapshot 文本格式化用
42
+ */
43
+ export declare function inspectCell(sheet: SheetModel, workbook: WorkbookModel, row: number, col: number, date1904: boolean): CellInspection;
@@ -1,4 +1,4 @@
1
- import { CellModel, CellStyle, CellStyleOverride, ColumnInfo, ImageAnchor, MergeRange, RowInfo, SheetModel } from './types';
1
+ import { CellModel, CellStyle, CellStyleOverride, ColumnInfo, ImageAnchor, MergeRange, RowInfo, SheetModel, WorkbookModel } from './types';
2
2
  import { CellValue } from './data-access';
3
3
  /** 设单元格值。返回旧 CellModel(克隆,供 undo);新值为空则删除该格。 */
4
4
  export declare function setCellValue(sheet: SheetModel, row: number, col: number, value: CellValue): void;
@@ -23,6 +23,21 @@ export declare function cloneImageAnchor(a: ImageAnchor): ImageAnchor;
23
23
  export declare function addImage(sheet: SheetModel, anchor: ImageAnchor, index?: number): number;
24
24
  /** 删一张图(调用方负责为 undo 捕获前态)。 */
25
25
  export declare function removeImage(sheet: SheetModel, index: number): void;
26
+ /**
27
+ * 浮动图 → WPS 单元格内嵌图(DISPIMG)。
28
+ * 取 sheet.images[imageIndex] 的字节登记到 wb.cellImages(新 id),目标格设 =DISPIMG 公式 + dispImgId,
29
+ * 移除该浮动图。返回新 id;图缺 bytes/mime 不可转 → 返 null(不动模型)。
30
+ */
31
+ export declare function convertFloatToCellImage(wb: WorkbookModel, sheet: SheetModel, imageIndex: number, row: number, col: number): string | null;
32
+ /**
33
+ * WPS 单元格内嵌图 → 浮动图。
34
+ * 取 (row,col) 的 dispImgId 对应登记图,造一张 oneCellAnchor 浮动图锚在该格(默认尺寸 sizePx),
35
+ * 清空该格。登记表里若无其它格再引用该 id,一并回收。返回浮动图索引;非内嵌图格返 -1。
36
+ */
37
+ export declare function convertCellImageToFloat(wb: WorkbookModel, sheet: SheetModel, row: number, col: number, size?: {
38
+ width: number;
39
+ height: number;
40
+ }): number;
26
41
  /**
27
42
  * 设图片的内容矩形(zoom 后像素,content 坐标)→ 规整为原点相对 oneCellAnchor。
28
43
  * 像素↔EMU:left_px = emuToPx(colOffEmu)*zoom ⇒ colOffEmu = pxToEmu(left/zoom)。不依赖列/行几何。
@@ -85,6 +85,12 @@ export interface CellModel {
85
85
  comment?: string;
86
86
  /** 样式索引,指向 SheetModel.styles */
87
87
  styleId: number;
88
+ /**
89
+ * WPS DISPIMG 单元格内嵌图的 id(指向 WorkbookModel.cellImages)。
90
+ * 有值时渲染器把图画进格内(随行高列宽缩放、随网格滚动裁剪),而非画 formula 文本。
91
+ * 由 cell-image-parser 从 `=DISPIMG("id",n)` 公式解析填充。
92
+ */
93
+ dispImgId?: string;
88
94
  }
89
95
  /** 合并区域(0-based,闭区间) */
90
96
  export interface MergeRange {
@@ -102,6 +108,12 @@ export interface RowInfo {
102
108
  /** 行高,单位 = px(已从 pt 换算) */
103
109
  height: number;
104
110
  hidden: boolean;
111
+ /**
112
+ * 是否"手动设定行高"(OOXML `<row customHeight="1">`)。
113
+ * true 时渲染层**不做自动行高**(与 Excel/WPS 一致:手动高度的行只裁切/溢出,不撑大)。
114
+ * 由 row-meta-parser 从原始 XML 读出(ExcelJS 不暴露此标记)。
115
+ */
116
+ customHeight?: boolean;
105
117
  }
106
118
  /** 冻结窗格: 冻结前 frozenRows 行 / frozenCols 列 */
107
119
  export interface FreezeInfo {
@@ -155,6 +167,19 @@ export interface AnchorCell {
155
167
  row: number;
156
168
  rowOffEmu: number;
157
169
  }
170
+ /**
171
+ * WPS DISPIMG 单元格内嵌图(workbook 级登记表 xl/cellimages.xml 的一条)。
172
+ * 单元格通过 `=DISPIMG("id",n)` 公式按 id 引用;不同于浮动图(ImageAnchor),它"属于"单元格。
173
+ */
174
+ export interface CellImage {
175
+ /** DISPIMG id(= cellimages.xml 里 cNvPr@name,如 "ID_5db4b3...") */
176
+ id: string;
177
+ /** 原始图片字节 + mime(解析层产出,可跨 Worker 传输) */
178
+ bytes?: Uint8Array;
179
+ mime?: string;
180
+ /** blob url(主线程 finalize 从 bytes 生成;解析阶段为空) */
181
+ src: string;
182
+ }
158
183
  /** 图表规格(从 chartN.xml 抽出,交给 ECharts 映射) */
159
184
  export interface ChartSpec {
160
185
  type: 'bar' | 'line' | 'pie' | 'area' | 'scatter' | 'doughnut' | 'radar' | 'unsupported';
@@ -265,6 +290,11 @@ export interface WorkbookModel {
265
290
  /** 主题色调色板(theme1.xml 解析所得,索引同 ECMA dk1/lt1/dk2/lt2/accent1..6/hlink/folHlink) */
266
291
  themeColors: CssColor[];
267
292
  date1904: boolean;
293
+ /**
294
+ * WPS DISPIMG 单元格内嵌图登记表(id → 图)。对应 xl/cellimages.xml,workbook 级共享。
295
+ * 单元格 CellModel.dispImgId 指向此表;无 WPS 内嵌图时为 undefined。
296
+ */
297
+ cellImages?: Map<string, CellImage>;
268
298
  }
269
299
  export declare const cellKey: (row: number, col: number) => string;
270
300
  /** 数据钩子: 解析后、渲染前改模型(返回新模型或就地改) */
@@ -282,8 +312,20 @@ export interface CellStyleOverride {
282
312
  indent?: number;
283
313
  numFmt?: string;
284
314
  }
285
- /** 渲染钩子: 按单元格覆盖样式(返回部分样式,与解析样式合并) */
315
+ /**
316
+ * 渲染钩子上下文 (Phase C, 2026-06-08).
317
+ * 给 `cellStyle` 钩子的第 3 入参, 让插件能感知该格当前是否可编辑 →
318
+ * 给只读格定制样式不再需要在 setup 里间接调 viewer.isCellEditable.
319
+ */
320
+ export interface CellStyleCtx {
321
+ /** 该格此刻是否可编辑(综合 editable + editableTargets + readOnlyRanges + cellReadOnly) */
322
+ editable: boolean;
323
+ }
324
+ /**
325
+ * 渲染钩子: 按单元格覆盖样式(返回部分样式,与解析样式合并).
326
+ * Phase C 2026-06-08: 加可选第 3 入参 `ctx: CellStyleCtx`, 含 `editable`. 旧 `(cell, pos) => ...` 签名兼容.
327
+ */
286
328
  export type CellStyleFn = (cell: CellModel, pos: {
287
329
  row: number;
288
330
  col: number;
289
- }) => CellStyleOverride | void;
331
+ }, ctx?: CellStyleCtx) => CellStyleOverride | void;
@@ -0,0 +1,9 @@
1
+ import { RawPackage } from './raw-xml';
2
+ import { CellImage, SheetModel } from '../model/types';
3
+ /** 从单元格公式里抽 DISPIMG id;非 DISPIMG 返 undefined */
4
+ export declare function dispImgIdOf(formula: string | undefined): string | undefined;
5
+ /**
6
+ * 解析 xl/cellimages.xml → id→CellImage 登记表,并回填各表单元格的 dispImgId。
7
+ * 无该私有件(非 WPS 文件)→ 返 undefined,不动任何单元格。
8
+ */
9
+ export declare function attachCellImages(pkg: RawPackage, sheets: SheetModel[]): Map<string, CellImage> | undefined;
@@ -0,0 +1,3 @@
1
+ import { RawPackage } from './raw-xml';
2
+ import { SheetModel } from '../model/types';
3
+ export declare function attachRowMeta(pkg: RawPackage, sheets: SheetModel[]): void;