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,7 +1,10 @@
1
1
  import { CellStyleFn, CellStyleOverride, ImageAnchor, MergeRange, TransformModelFn, WorkbookModel } from './model/types';
2
2
  import { CellValue, ReadOptions, SheetToJSONOptions } from './model/data-access';
3
3
  import { CellSnapshot } from './model/snapshot';
4
+ import { CellInspection } from './model/inspect';
5
+ import { MenuItem } from './edit/context-menu';
4
6
  import { EditorResolver } from './edit/editor-context';
7
+ import { EditableTarget } from './edit/types';
5
8
  import { ViewerTheme } from './render/theme';
6
9
  import { ExcelSource } from './loader';
7
10
  import { ImageExportOptions, PdfExportOptions, PrintOptions } from './export/types';
@@ -12,7 +15,30 @@ export interface Rect {
12
15
  w: number;
13
16
  h: number;
14
17
  }
15
- export type PluginEvent = 'cell-click' | 'cell-dblclick' | 'selection-change' | 'sheet-change' | 'hyperlink-click' | 'cell-change' | 'edit-start' | 'edit-commit' | 'dim-change' | 'dirty-change' | 'image-change' | 'struct-change';
18
+ export type PluginEvent = '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';
19
+ /**
20
+ * 权限拒绝事件 payload (Phase A, 2026-06-08):mutation 因 editable / editableTargets /
21
+ * readOnlyRanges / cellReadOnly 被阻挡时, 一次操作结束**统一**发一次. 默认行为仍是
22
+ * **静默跳过**(跟 editRange 一致), 此事件**只通知**消费方做 toast / 高亮, 不阻断流程.
23
+ *
24
+ * 一次操作只 emit 一次 (避免 N 张图 spam N 次).
25
+ */
26
+ export interface PermissionDeniedPayload {
27
+ /** 触发的操作类型 */
28
+ reason: 'paste' | 'merge' | 'unmerge' | 'image-place' | 'image-convert' | 'dimension' | 'other';
29
+ /** 被拒的目标格 (粘贴 / 合并 / 图片转换 等场景下的具体位置;'dimension' 时可空) */
30
+ cells: Array<{
31
+ row: number;
32
+ col: number;
33
+ }>;
34
+ /** 'dimension' 场景下被拒的列 / 行 index 列表 */
35
+ dims?: {
36
+ axis: 'col' | 'row';
37
+ indices: number[];
38
+ };
39
+ /** 给消费方的可读说明 */
40
+ message?: string;
41
+ }
16
42
  /** 命令式 API(组件 ref 与插件 ctx 共用) */
17
43
  export interface ViewerApi {
18
44
  load(src: ExcelSource): void;
@@ -24,8 +50,17 @@ export interface ViewerApi {
24
50
  rectOf(row: number, col: number): Rect | null;
25
51
  rectOfRange(range: MergeRange): Rect | null;
26
52
  redraw(): void;
27
- /** 该格当前是否可编辑(综合 editable + readOnlyRanges + cellReadOnly) */
53
+ /** 该格当前是否可编辑(综合 editable + editableTargets 白名单 + readOnlyRanges + cellReadOnly) */
28
54
  isCellEditable(row: number, col: number): boolean;
55
+ /**
56
+ * **运行时**改可编辑白名单 (2026-06-08 新增). 接受 4 种 target 形状:
57
+ * `{row,col}` 单格 / `{row}` 整行 / `{col}` 整列 / `MergeRange` 矩形;单值或数组都支持,允许**不相邻**.
58
+ * 传 `undefined` = 关闭白名单(默认全可编辑);`[]` = 全只读;命中**任一** target → 可编辑.
59
+ * 立即生效, 不动 `:editableTargets` prop.
60
+ */
61
+ setEditableTargets(targets: EditableTarget | EditableTarget[] | undefined): void;
62
+ /** 当前生效的可编辑白名单. `undefined` 表示未启用白名单. */
63
+ getEditableTargets(): EditableTarget | EditableTarget[] | undefined;
29
64
  /** 导出当前/指定表为图片 Blob(默认 png) */
30
65
  exportImage(opts?: ImageExportOptions): Promise<Blob>;
31
66
  /** 导出为图片并触发下载 */
@@ -81,6 +116,8 @@ export interface ViewerApi {
81
116
  } | null;
82
117
  /** 查询任意格的完整快照(底层结构 + raw/computed/text/style) */
83
118
  getCellSnapshot(row: number, col: number): CellSnapshot | null;
119
+ /** 单元格"全息体检":snapshot + 合并区 + 浮动图覆盖 + WPS 内嵌图 + 数据验证 + 条件格式命中 + 链接/批注 */
120
+ inspectCell(row: number, col: number): CellInspection | null;
84
121
  /** 进入编辑(需有 editor 工厂 + 可编辑);返回是否进入 */
85
122
  beginEdit(row: number, col: number): boolean;
86
123
  /** 取消当前编辑(不改模型) */
@@ -89,6 +126,37 @@ export interface ViewerApi {
89
126
  isEditing(): boolean;
90
127
  /** 给区域套样式覆盖(E5;粗体/对齐/填充等);editable 时入命令栈(可撤销 + 发 cell-change + 记脏) */
91
128
  setStyle(range: MergeRange, patch: CellStyleOverride): boolean;
129
+ /** 活动格当前背景填充色(#RRGGBB;无填充→白) —— 工具栏色板回显用 */
130
+ getActiveFillColor(): string;
131
+ /** 活动格当前字体色(#RRGGBB;缺省黑) */
132
+ getActiveFontColor(): string;
133
+ /** 给当前选区设背景填充色(null=清除填充);editable 时入命令栈 */
134
+ setSelectionFill(color: string | null): boolean;
135
+ /** 给当前选区设字体色;editable 时入命令栈 */
136
+ setSelectionFontColor(color: string): boolean;
137
+ /** 当前选区里 wrapText 的整体态:'all' 全开 / 'none' 全关 / 'mixed' 混合(工具栏 active/右键勾选用) */
138
+ getSelectionWrapState(): 'all' | 'none' | 'mixed';
139
+ /** 切换当前选区的"自动换行"(WPS 风格 toggle);editable 时入命令栈,行高按内容重撑(只扩不缩) */
140
+ toggleWrapTextOnSelection(): boolean;
141
+ /** 合并区域(G1;清空被覆盖格,只留左上锚点);editable 时入命令栈 */
142
+ mergeCells(range: MergeRange): boolean;
143
+ /** 拆分区域内的合并(G1);editable 时入命令栈 */
144
+ unmergeCells(range: MergeRange): boolean;
145
+ /** 把 TSV 文本粘到选区左上角(G2;类型自动推断、跳过只读、入命令栈);at 缺省用活动格 */
146
+ pasteText(text: string, at?: {
147
+ row: number;
148
+ col: number;
149
+ }): boolean;
150
+ /** 解析 Excel/WPS 复制的剪贴板 HTML → 富粘贴(值+字体/颜色/填充/边框/对齐+合并+data-uri图),整体单次撤销 */
151
+ pasteRichHtml(html: string, at?: {
152
+ row: number;
153
+ col: number;
154
+ }): boolean;
155
+ /** 把一张图片 blob 落到活动格(转内嵌图);剪贴板单图 / 拖文件进网格用 */
156
+ pasteImageBlob(blob: Blob, at?: {
157
+ row: number;
158
+ col: number;
159
+ }): Promise<boolean>;
92
160
  /** 读当前表全部图片锚点(克隆;E6) */
93
161
  getImages(): ImageAnchor[];
94
162
  /** 加一张图(无 src 但有 bytes+mime 时自动生成 blob url);返回插入索引 */
@@ -99,6 +167,51 @@ export interface ViewerApi {
99
167
  moveImage(index: number, dxPx: number, dyPx: number): boolean;
100
168
  /** 缩放图片(目标屏幕像素宽高);editable 时入命令栈 + 发 image-change */
101
169
  resizeImage(index: number, widthPx: number, heightPx: number): boolean;
170
+ /** 活动格在公式栏里的可编辑字符串(公式→`=...`;数值→原始数字串;布尔→TRUE/FALSE;其余→显示文本) */
171
+ getCellEditString(): string;
172
+ /** 活动格此刻是否可经公式栏/命令式编辑(editable 开 + 该格非只读) */
173
+ canEditActiveCell(): boolean;
174
+ /** 经公式栏提交活动格的值(同 editCell 输入语义);move='down' 提交后活动格下移。返回是否提交 */
175
+ commitActiveCellValue(value: string, move?: 'down'): boolean;
176
+ /** 读 WPS 单元格内嵌图(DISPIMG)登记表(id→{id,src,mime});非 WPS 文件返空数组 */
177
+ getCellImages(): {
178
+ id: string;
179
+ src: string;
180
+ mime?: string;
181
+ }[];
182
+ /** 某格是否内嵌图 → {id,src,mime} 否则 null(供图片放大判定) */
183
+ getCellImageAt(row: number, col: number): {
184
+ id: string;
185
+ src: string;
186
+ mime?: string;
187
+ } | null;
188
+ /** 打开图片放大灯箱(命令式;src = blob/data/http url) */
189
+ openImageLightbox(src: string, fileName?: string, mime?: string): void;
190
+ /** 设 WPS 单元格内嵌图贴合方式(fill 拉伸铺满 / contain 等比留白 / cover 等比裁剪);即时重绘 */
191
+ setCellImageFit(fit: 'fill' | 'contain' | 'cover'): void;
192
+ /** 浮动图 → 单元格内嵌图(显式目标格);editable 时入命令栈 */
193
+ convertImageToCell(imageIndex: number, row: number, col: number): boolean;
194
+ /** 浮动图 → 内嵌图(**就近**:图在哪格就嵌哪格,几何反推目标);editable 时入命令栈 */
195
+ convertImageToCellAuto(imageIndex: number): boolean;
196
+ /** 批量把浮动图就近嵌入各自单元格(整表;`col` 给定则仅该列);一次进撤销栈,返回嵌入张数 */
197
+ convertAllImagesToCells(col?: number): number;
198
+ /** 选区批量:把中心落在 range 内的浮动图全部就近嵌入,单次撤销;返回嵌入张数。
199
+ * 壳侧 1.2.0 起返 `Promise<number>`(为接内置 ExportProgressOverlay;关闭 `:exportProgress` 也仍是 Promise) */
200
+ convertImagesInRangeToCell(range: MergeRange): Promise<number>;
201
+ /** 选区批量(反向):range 内所有 DISPIMG 格拎成浮动图,单次撤销;返回转换张数(壳侧返 Promise,见上) */
202
+ convertCellImagesInRangeToFloat(range: MergeRange, size?: {
203
+ width: number;
204
+ height: number;
205
+ }): Promise<number>;
206
+ /** 程序化打开右键菜单(Plan C;键盘 Shift+F10 / 工具栏触发等);items 不给则按当前选区算内置 */
207
+ openContextMenu(x: number, y: number, items?: MenuItem[]): void;
208
+ /** 关闭当前打开的右键菜单 */
209
+ closeContextMenu(): void;
210
+ /** 单元格内嵌图 → 浮动图(把 row,col 的 DISPIMG 拎成浮动图,默认 96×96px);editable 时入命令栈 */
211
+ convertCellImageToFloat(row: number, col: number, size?: {
212
+ width: number;
213
+ height: number;
214
+ }): boolean;
102
215
  /** 在 at 处插入 count 行(E7);editable 时入命令栈 + 发 struct-change */
103
216
  insertRows(at: number, count?: number): boolean;
104
217
  /** 删除 [at, at+count) 行(与合并相交则相交合并被移除) */
@@ -107,12 +220,29 @@ export interface ViewerApi {
107
220
  insertCols(at: number, count?: number): boolean;
108
221
  /** 删除 [at, at+count) 列 */
109
222
  deleteCols(at: number, count?: number): boolean;
110
- /** 程序化设列宽(px,模型单位);editable 时入命令栈(可撤销 + 发 dim-change + 记脏) */
111
- setColumnWidth(col: number, width: number): boolean;
112
- /** 程序化设行高(px,模型单位);editable 时入命令栈 */
113
- setRowHeight(row: number, height: number): boolean;
223
+ /**
224
+ * 程序化设列宽 (px, 模型单位). Phase B 2026-06-08:
225
+ * target 接 `number | number[] | {from,to}` (DimTarget). index 时聚合成单次 undo.
226
+ * 返回**成功条数** (0 = skip / editable=false). 老 `setColumnWidth(5, 100)` 单值用法兼容.
227
+ */
228
+ setColumnWidth(target: import('./edit/types').DimTarget, width: number): number;
229
+ /** 程序化设行高. 同 setColumnWidth, 维度 = 'row'. */
230
+ setRowHeight(target: import('./edit/types').DimTarget, height: number): number;
231
+ /** 批量 autoFit 列宽 (Phase B). target 不传 = 整表; 传 DimTarget = 选定列. 返成功条数. */
232
+ autoFitColumns(target?: import('./edit/types').DimTarget): number;
233
+ /** 批量 autoFit 行高 (Phase B). 同上, 维度 = 'row'. */
234
+ autoFitRows(target?: import('./edit/types').DimTarget): number;
235
+ /** 重置列宽到默认 (Phase B) — 移除 columns Map 条目, 回落 defaultColWidth. 返成功条数. */
236
+ resetColumnWidth(target: import('./edit/types').DimTarget): number;
237
+ /** 重置行高到默认. 同上, 维度 = 'row'. */
238
+ resetRowHeight(target: import('./edit/types').DimTarget): number;
114
239
  /** 公式引擎是否已就绪(recalc 开启 + 异步 warm 完成);未开重算恒 false */
115
240
  isRecalcReady(): boolean;
241
+ /** 当前虚拟范围(滚动出空行/列的外推上限,含 dimension 兜底);不动 dimension/文件 */
242
+ getVirtualExtent(): {
243
+ rows: number;
244
+ cols: number;
245
+ };
116
246
  /** 当前是否有未保存修改(自加载/还原以来发生过编辑或 resize) */
117
247
  isDirty(): boolean;
118
248
  /** 放弃全部修改,还原到刚加载的原件;返回是否还原 */
@@ -173,8 +303,16 @@ export interface ExcelPlugin {
173
303
  overlay?: (ctx: OverlayContext) => OverlayNode;
174
304
  /** 按格自定义编辑控件(返回工厂;多插件数组序首个非空胜,组件 editor prop 覆盖);需 editable 开启。 */
175
305
  editor?: EditorResolver;
306
+ /** 右键菜单 transform(Plan C):`(ctx, items) => items[]`,多插件按数组顺序串行,组件 :contextMenu prop 最后覆盖 */
307
+ contextMenu?: import('./viewer/controller').ContextMenuTransform;
176
308
  /** 高级: 拿命令式 API、订阅事件;返回可选清理函数 */
177
309
  setup?: (ctx: ExcelPluginContext) => void | (() => void);
178
310
  }
179
311
  /** 定义插件(仅作类型推断,原样返回) */
180
312
  export declare function definePlugin(plugin: ExcelPlugin): ExcelPlugin;
313
+ export { jsonToWorkbook, isWorkbookModel, type JsonInput, type JsonLoadOptions, type JsonRow, type JsonSheetInput } from './loader-json';
314
+ export { applyStyleTemplate } from './template/style-overlay';
315
+ export type { CellInspection } from './model/inspect';
316
+ export type { EditableTarget, EditConfig } from './edit/types';
317
+ export type { MenuItem } from './edit/context-menu';
318
+ export type { ContextMenuCtx, ContextMenuTransform, ContextMenuBeforePayload, ContextMenuShowPayload, } from './viewer/controller';
@@ -5,3 +5,26 @@ export interface ParseProgress {
5
5
  ratio?: number;
6
6
  }
7
7
  export type ProgressFn = (p: ParseProgress) => void;
8
+ /**
9
+ * 导出 / 批量转换进度。所有耗时操作(PDF / PNG / XLSX / 批量浮动↔嵌入)统一报这套。
10
+ * stage 语义:
11
+ * - 'render':canvas 渲染一张表(大表分块时按 ratio 报子进度)
12
+ * - 'compose':多表合成一张大图 / 合并几何
13
+ * - 'paginate':PDF 分页 / 每页布局
14
+ * - 'write':PDF / 图片 blob 编码写出
15
+ * - 'zip':XLSX zip 压缩(exceljs 黑盒,只前/后两次)
16
+ * - 'convert':批量图片浮动↔嵌入(P2)
17
+ */
18
+ export type ExportStage = 'render' | 'compose' | 'paginate' | 'write' | 'zip' | 'convert';
19
+ export interface ExportProgress {
20
+ stage: ExportStage;
21
+ /** 当前正在处理的表(可选) */
22
+ sheetIndex?: number;
23
+ /** PDF 分页阶段:当前页 */
24
+ pageIndex?: number;
25
+ /** 0..1;黑盒阶段省略 */
26
+ ratio?: number;
27
+ /** 给 UI 显示的标签(可选,如"渲染 Sheet1 (3/5)") */
28
+ label?: string;
29
+ }
30
+ export type ExportProgressFn = (p: ExportProgress) => void;
@@ -1,10 +1,37 @@
1
- import { CellStyle, CellStyleFn, MergeRange, SheetModel, WorkbookModel } from '../model/types';
1
+ import { CellStyle, CellStyleFn, CellStyleOverride, MergeRange, SheetModel, WorkbookModel } from '../model/types';
2
2
  import { ViewerTheme } from './theme';
3
3
  import { GridMetrics } from '../layout/grid-metrics';
4
4
  import { FreezeGeometry } from '../layout/freeze';
5
+ /**
6
+ * WPS 单元格内嵌图(DISPIMG)在格内的贴合方式:
7
+ * - `contain`(默认,与 WPS 渲染一致):等比缩放完整显示,周围留白(不裁剪、不变形)。
8
+ * WPS 打开导出文件时 DISPIMG 固定按 contain 渲染,故默认 contain 让预览所见即所得。
9
+ * - `fill`:拉伸铺满整格,随宽高变形(预览铺满,但导出后 WPS 仍按 contain 显示,二者不一致);
10
+ * - `cover`:等比放大铺满,超出部分裁掉(不变形、不留白)。
11
+ */
12
+ export type CellImageFit = 'fill' | 'contain' | 'cover';
5
13
  export interface RendererOptions {
6
14
  theme?: Partial<ViewerTheme>;
7
15
  cellStyle?: CellStyleFn;
16
+ /** 异步内容(WPS 单元格内嵌图)解码完成后请求重绘;壳/控制器接到 scheduleRender */
17
+ onNeedsRedraw?: () => void;
18
+ /** WPS 单元格内嵌图贴合方式(默认 contain,与 WPS 渲染一致) */
19
+ cellImageFit?: CellImageFit;
20
+ /**
21
+ * 只读单元格视觉钩子 (Phase C, 2026-06-08):
22
+ * - false (默认) = 无视觉差异 (老行为不变)
23
+ * - true = 套内置默认 (灰底 #f5f7fa)
24
+ * - CellStyleOverride 对象 = 固定样式给所有只读格
25
+ * - CellStyleFn 函数 = 按格自定义
26
+ * 仅在 cellStyle 钩子之后, 该格 editable=false 时套用.
27
+ */
28
+ readOnlyCellStyle?: boolean | CellStyleOverride | CellStyleFn;
29
+ /**
30
+ * 查询某格是否可编辑 (Phase C, 2026-06-08):
31
+ * 让渲染器把 ctx.editable 喂给 cellStyle 钩子 + 决定是否套 readOnlyCellStyle.
32
+ * controller 注入, 默认 () => true (不知道权限 → 当全可编辑, 老行为不变).
33
+ */
34
+ isEditable?: (row: number, col: number) => boolean;
8
35
  }
9
36
  /** 导出为离屏 canvas 的选项 */
10
37
  export interface ExportToCanvasOptions {
@@ -84,10 +111,28 @@ export declare class CanvasRenderer {
84
111
  private sparklineIndex;
85
112
  private theme;
86
113
  private cellStyleHook?;
114
+ private onNeedsRedraw?;
115
+ private cellImageFit;
116
+ /** Phase C 2026-06-08: 只读视觉钩子, 渲染时按格套用 */
117
+ private readOnlyStyleHook?;
118
+ /** Phase C 2026-06-08: 查询该格是否可编辑 (controller 注入). 默认全可编辑 (老行为) */
119
+ private isEditableFn;
120
+ /** 虚拟外推行/列数(滚动出空行用;0 = 仅按 dimension)。透传给 GridMetrics,不影响导出。 */
121
+ private virtualRows;
122
+ private virtualCols;
123
+ /** WPS 单元格内嵌图解码缓存: blob src → 已加载的 HTMLImageElement(complete 才画) */
124
+ private cellImageCache;
87
125
  constructor(canvas: HTMLCanvasElement, sheet: SheetModel, workbook: WorkbookModel, zoom?: number, opts?: RendererOptions);
126
+ /** 改 WPS 单元格内嵌图贴合方式(fill/contain/cover);返回是否有变化(变了需重绘)。 */
127
+ setCellImageFit(fit: CellImageFit): boolean;
88
128
  /** 改变缩放: 重建几何(列宽行高表头按 zoom 缩放)。合并/条件格式无需重建。 */
89
129
  setZoom(zoom: number): void;
90
- /** 内容总尺寸(含表头),给外层滚动容器用 */
130
+ /**
131
+ * 设虚拟外推行/列数(滚动出空行/空列);仅当变化时重建 GridMetrics,返回是否变了(变了需重绘 + 刷 spacer)。
132
+ * 不影响 dimension / 导出 / data-access。
133
+ */
134
+ setVirtualExtent(rows: number, cols: number): boolean;
135
+ /** 内容总尺寸(含表头),给外层滚动容器用(用虚拟范围 → 可滚动出空行/列) */
91
136
  get contentWidth(): number;
92
137
  get contentHeight(): number;
93
138
  get freezeGeometry(): FreezeGeometry;
@@ -202,6 +247,15 @@ export declare class CanvasRenderer {
202
247
  private borderEdgeOf;
203
248
  /** 画单个 cell(或合并区)的: 填充 → 条件背景 → 数据条 → 边框 → 内容 → 图标 → 筛选按钮 */
204
249
  private paintCellBox;
250
+ /**
251
+ * 把 WPS 单元格内嵌图(DISPIMG)画进格内,贴合方式由 `cellImageFit` 决定(fill/contain/cover);
252
+ * 始终裁剪到格内、随行高列宽变化。未解码完成/缺图时画淡占位。
253
+ */
254
+ private drawCellImage;
255
+ /** 取/起一张内嵌图的解码;未缓存则起加载,onload 请求重绘(无 DOM 环境返 null) */
256
+ private getCellImageEl;
257
+ /** 缺图提示(仅画淡图标,**不盖底色** — 让单元格自身填充色透出来) */
258
+ private drawImagePlaceholder;
205
259
  private drawCellContent;
206
260
  /**
207
261
  * 文本溢出可用的裁剪范围: 从本格向对齐方向延伸,吞掉相邻"空白"格(无值且无填充)。
@@ -21,5 +21,12 @@ export declare class ConditionalEngine {
21
21
  hasRules(): boolean;
22
22
  private numericValuesIn;
23
23
  private prepare;
24
+ /** 返回所有命中该格的规则索引 + 各自计算出的 effect(供 Cell Inspector 查询;不做"第一条赢"短路) */
25
+ inspectHits(row: number, col: number, value: number | string | boolean | Date | null): Array<{
26
+ ruleIndex: number;
27
+ effect: CellEffect;
28
+ }>;
29
+ /** 单条规则在某格上的 effect(命中返 patch,不命中返 null);effectsFor 与 inspectHits 共用 */
30
+ private evalRule;
24
31
  effectsFor(row: number, col: number, value: number | string | boolean | Date | null): CellEffect | null;
25
32
  }
@@ -0,0 +1,9 @@
1
+ import { WorkbookModel } from '../model/types';
2
+ /**
3
+ * 把模板的样式套到数据 workbook 上,产出新 workbook(不修改入参).
4
+ *
5
+ * @param dataWb 数据源(JSON / CSV 加载得到的 WorkbookModel),raw 值的来源
6
+ * @param templateWb 模板源(.xlsx 解析得到的 WorkbookModel),styling 的来源
7
+ * @returns 合成 workbook —— 用模板的 sheet 形态 + 数据的 raw 内容
8
+ */
9
+ export declare function applyStyleTemplate(dataWb: WorkbookModel, templateWb: WorkbookModel): WorkbookModel;
@@ -3,8 +3,10 @@ import { EditConfig } from '../edit/types';
3
3
  import { EditController, EditEventName } from '../edit/edit-controller';
4
4
  import { CellValue, SheetToJSONOptions } from '../model/data-access';
5
5
  import { CellSnapshot } from '../model/snapshot';
6
+ import { CellInspection } from '../model/inspect';
7
+ import { MenuItem } from '../edit/context-menu';
6
8
  import { EditorCommitValue, EditorResolver } from '../edit/editor-context';
7
- import { CanvasRenderer, RendererOptions, ViewState } from '../render/canvas-renderer';
9
+ import { CanvasRenderer, CellImageFit, RendererOptions, ViewState } from '../render/canvas-renderer';
8
10
  import { OverlayQuads } from './overlay-manager';
9
11
  import { ImageExportOptions, PdfExportOptions, PrintOptions } from '../export/types';
10
12
  import { XlsxExportOptions } from '../export/xlsx-writer';
@@ -70,6 +72,33 @@ export interface ViewerControllerHooks {
70
72
  onFilterChange: () => void;
71
73
  /** 编辑事件(cell-change/edit-start/edit-commit;壳转 emit + 插件派发) */
72
74
  onEditEvent: (event: EditEventName, payload: unknown) => void;
75
+ /** 右键菜单触发前(Plan C):用户可调 `preventDefault()` 阻止内置菜单弹出(然后自渲染) */
76
+ onContextMenuBefore?: (payload: ContextMenuBeforePayload) => void;
77
+ /** 右键菜单"展示"通知(Plan C):无论内置是否弹都触发,供壳自渲染或事件流串到业务 */
78
+ onContextMenuShow?: (payload: ContextMenuShowPayload) => void;
79
+ }
80
+ /** 右键菜单上下文(单格/选区 + 活动格 + 当前表/簿 + editable 闸门态) */
81
+ export interface ContextMenuCtx {
82
+ range: MergeRange;
83
+ single: boolean;
84
+ activeCell: Cell;
85
+ sheet: SheetModel;
86
+ workbook: WorkbookModel;
87
+ editable: boolean;
88
+ }
89
+ /** 用户 transform:在内置 items 上做加 / 减 / 重排;返新数组生效,返 undefined 用原样 */
90
+ export type ContextMenuTransform = (ctx: ContextMenuCtx, items: MenuItem[]) => MenuItem[] | undefined | void;
91
+ export interface ContextMenuBeforePayload {
92
+ event: MouseEvent;
93
+ ctx: ContextMenuCtx;
94
+ items: MenuItem[];
95
+ preventDefault: () => void;
96
+ }
97
+ export interface ContextMenuShowPayload {
98
+ x: number;
99
+ y: number;
100
+ ctx: ContextMenuCtx;
101
+ items: MenuItem[];
73
102
  }
74
103
  export declare class ViewerController {
75
104
  private els;
@@ -83,11 +112,20 @@ export declare class ViewerController {
83
112
  private rafId;
84
113
  private contentW;
85
114
  private contentH;
115
+ /** 虚拟外推行/列数(滚动出空行/空列;只增不减,封顶 Excel 上限)。不动 dimension/文件。 */
116
+ private virtualRows;
117
+ private virtualCols;
86
118
  private workbook;
87
119
  private activeIndex;
88
120
  private rendererOpts;
89
121
  /** 下载默认文件名(壳可随 props 更新) */
90
122
  fileName: string | undefined;
123
+ /** 右键上下文菜单宿主(G3;body 级 DOM,框架无关) */
124
+ private menuHost;
125
+ private lightbox;
126
+ private lightboxEnabled;
127
+ /** 用户的右键菜单 transform 回调(Plan C):`(ctx, items) => MenuItem[] | undefined` */
128
+ private ctxMenuTransform;
91
129
  /** 原始 .xlsx 字节(壳加载时注入;供高保真 overlay 导出重载原件) */
92
130
  private sourceBuffer;
93
131
  /** 壳在加载后注入原始字节(供 exportXlsx({fidelity:'overlay'}) 重载原件叠加编辑) */
@@ -107,6 +145,7 @@ export declare class ViewerController {
107
145
  private resizeStartInfo;
108
146
  private resizeStartModelSize;
109
147
  private dragMoved;
148
+ private dragStartXY;
110
149
  private findQuery;
111
150
  private findMatchCase;
112
151
  private findWholeCell;
@@ -145,10 +184,30 @@ export declare class ViewerController {
145
184
  autoFitColumn(col: number): void;
146
185
  /** 双击行边界: 自适应行高(editable 时入命令栈/发事件/记脏) */
147
186
  autoFitRow(row: number): void;
148
- /** 程序化设列宽(px,模型单位/非缩放);editable 时走命令栈(可撤销+发 dim-change+记脏)。 */
149
- setColumnWidth(col: number, width: number): boolean;
150
- /** 程序化设行高(px,模型单位/非缩放);editable 时走命令栈。 */
151
- setRowHeight(row: number, height: number): boolean;
187
+ /**
188
+ * 程序化设列宽 (px, 模型单位/非缩放). Phase B 2026-06-08:
189
+ * target 接 `number | number[] | {from,to}` (DimTarget). index 时聚合成单次 undo.
190
+ * editable 时走命令栈;strictDimensions=true 时该列至少 1 格在白名单内才生效.
191
+ * 返回**成功条数** (0 = 全部 skip / editable=false).
192
+ */
193
+ setColumnWidth(target: import('../edit/types').DimTarget, width: number): number;
194
+ /** 程序化设行高 (px, 模型单位/非缩放). 同 setColumnWidth, 维度 = 'row'. */
195
+ setRowHeight(target: import('../edit/types').DimTarget, height: number): number;
196
+ /**
197
+ * 批量 autoFit 列宽 (Phase B 2026-06-08). target 不传 = 整表; 传 DimTarget = 选定列.
198
+ * 单 index 走 autoFitColumn (含 resize-record); 多 index 单次 restore-wb undo + 循环 autofit.
199
+ * 返回成功条数.
200
+ */
201
+ autoFitColumns(target?: import('../edit/types').DimTarget): number;
202
+ /** 批量 autoFit 行高 (Phase B 2026-06-08). 同 autoFitColumns, 维度 = 'row'. */
203
+ autoFitRows(target?: import('../edit/types').DimTarget): number;
204
+ /**
205
+ * 重置列宽到默认 (Phase B 2026-06-08) — 移除 sheet.columns Map 条目, 回落到 defaultColWidth.
206
+ * 多 index 单次 undo. 返回成功条数.
207
+ */
208
+ resetColumnWidth(target: import('../edit/types').DimTarget): number;
209
+ /** 重置行高到默认. 同 resetColumnWidth, 维度 = 'row'. */
210
+ resetRowHeight(target: import('../edit/types').DimTarget): number;
152
211
  /** 公式引擎是否已就绪(recalc 开启 + 异步 warm 完成);未开重算恒 false。 */
153
212
  isRecalcReady(): boolean;
154
213
  /** 当前是否有未保存修改(自加载/还原以来发生过编辑或 resize)。 */
@@ -172,10 +231,34 @@ export declare class ViewerController {
172
231
  dispose(): void;
173
232
  /** 内容总尺寸变化 → 量 + 直接撑 spacer(滚动范围由它决定) */
174
233
  refreshContentSize(): void;
234
+ /**
235
+ * 据当前滚动+视口算"虚拟范围"(滚到数据下方仍有空行/空列可滚动/选中/编辑)。
236
+ * 只增不减(防 spacer 抖动)、封顶 Excel 上限。**不动 dimension/文件**;编辑虚拟格才靠 growDimension 变实。
237
+ * 仅当范围真变化时重建 GridMetrics + 刷 spacer。
238
+ */
239
+ recomputeVirtualExtent(): void;
240
+ /** 当前虚拟范围(含 dimension 兜底);供调试/e2e。 */
241
+ getVirtualExtent(): {
242
+ rows: number;
243
+ cols: number;
244
+ };
175
245
  /** 当前选区(随 mode 解析为单元格/整行/整列范围,含合并单元格扩展) */
176
246
  getSelection(): MergeRange | null;
177
247
  /** 活动单元格(选区的"焦点"角) */
178
248
  getActiveCell(): Cell | null;
249
+ /**
250
+ * 活动格(或指定格)在公式栏里**可编辑的字符串**:
251
+ * 公式 → `=...`;数值 → 原始数字串(非格式化,避免编辑货币/千分位被当文本);布尔 → TRUE/FALSE;
252
+ * 日期/字符串/富文本 → 显示文本。空格返 ''。供公式栏 / 命令式取活动格编辑值。
253
+ */
254
+ getCellEditString(row?: number, col?: number): string;
255
+ /** 活动格此刻是否可经公式栏/命令式编辑(editable 开 + 该格非只读) */
256
+ canEditActiveCell(): boolean;
257
+ /**
258
+ * 经公式栏提交活动格的值(value 同 editCell 的输入语义:`=`→公式、数字串→数字…)。
259
+ * 仅在 editable + 该格可编辑时生效;move='down' 时提交后活动格下移(像 Excel 回车)。返回是否提交。
260
+ */
261
+ commitActiveCellValue(value: string, move?: 'down'): boolean;
179
262
  /** 清空选区 */
180
263
  clearSelection(): void;
181
264
  /** 全选 */
@@ -201,12 +284,59 @@ export declare class ViewerController {
201
284
  /** 拖拽结束:模型已被 renderer 改完,补登一条 undo 项 + 发 dim-change。 */
202
285
  private endResizeRecord;
203
286
  onMouseLeave(): void;
287
+ /** 右键上下文菜单(G3;仅 editable;只读用浏览器默认菜单)。壳把 contextmenu 事件转给它。 */
288
+ /**
289
+ * 右键事件入口(Plan C 全面开放):
290
+ * 1. 算 ctx(选区 + 单/多格 + 活动格);右键不在当前选区 → 先选中(仿 Excel)
291
+ * 2. 算内置 items(`buildBuiltinContextMenuItems`,editable 时才有,否则空)
292
+ * 3. 跑用户 `setContextMenuTransform` 回调(可加/减/重排)
293
+ * 4. 触发 `onContextMenuBefore` 钩子(可 preventDefault)
294
+ * 5. 没被 prevent + items 非空 → 弹内置浮层;无论是否弹,都触发 `onContextMenuShow`(让用户也能自渲染)
295
+ */
296
+ onContextMenu(e: MouseEvent): void;
297
+ /** 算右键 ctx:命中格 + 选区调整;非内容区 / 无 renderer 返 null */
298
+ private buildContextMenuCtx;
299
+ /** 编辑模式下的内置菜单项(独立提取,便于 transform 回调拿到再二次加工) */
300
+ buildBuiltinContextMenuItems(ctx: ContextMenuCtx): MenuItem[];
301
+ /** 设置用户 transform 回调(`(ctx, items) => MenuItem[] | undefined`);壳侧调用 */
302
+ setContextMenuTransform(fn: ContextMenuTransform | null | undefined): void;
303
+ /** 程序化打开菜单(键盘 Shift+F10 / 自定义触发);items 不给则按当前选区算内置 */
304
+ openContextMenu(x: number, y: number, items?: MenuItem[]): void;
305
+ /** 关闭当前菜单(无打开则 no-op) */
306
+ closeContextMenu(): void;
204
307
  private updateHover;
205
308
  onDblClick(e: MouseEvent): void;
206
309
  private pageRows;
207
310
  onKeyDown(e: KeyboardEvent): void;
208
311
  private scrollActiveIntoView;
209
312
  copySelection(): Promise<void>;
313
+ /**
314
+ * 从系统剪贴板粘贴。优先 `clipboard.read()` 拿 **text/html**(Excel/WPS 复制 → 富粘贴:值+字体+颜色+
315
+ * 填充+边框+合并),否则单张图片 → 落格,再否则回退 **text/plain** TSV(`pasteText`)。需读权限/安全上下文,
316
+ * 受限时回退 readText;都失败返 false。
317
+ */
318
+ pasteFromClipboard(): Promise<boolean>;
319
+ /**
320
+ * 解析 Excel/WPS 复制的剪贴板 HTML(text/html)→ 富粘贴:值 + 字体/颜色/填充/边框/对齐 + 合并 + data-uri 图,
321
+ * **整体单次撤销**。无 `<table>` 返 false(调用方回退 TSV)。at 缺省用活动格。
322
+ */
323
+ pasteRichHtml(html: string, at?: {
324
+ row: number;
325
+ col: number;
326
+ }): boolean;
327
+ /** 把一张图片 blob 落到活动格(转内嵌图);剪贴板单图粘贴 / 拖文件进网格用。 */
328
+ pasteImageBlob(blob: Blob, at?: {
329
+ row: number;
330
+ col: number;
331
+ }): Promise<boolean>;
332
+ /**
333
+ * 把 TSV(Excel/表格复制的制表符分隔文本)粘到选区左上角(无 at 时用活动格)。
334
+ * 值类型自动推断(纯数字串→数字、`=`→公式)、跳过只读格;入命令栈可撤销。返回是否有改动。
335
+ */
336
+ pasteText(text: string, at?: {
337
+ row: number;
338
+ col: number;
339
+ }): boolean;
210
340
  /** 查找状态快照(壳渲染 FindBar) */
211
341
  getFindState(): FindState;
212
342
  setFindQuery(q: string): void;
@@ -260,13 +390,39 @@ export declare class ViewerController {
260
390
  sortColumn(col: number, dir: 'asc' | 'desc'): void;
261
391
  /** 设置编辑配置(默认只读;壳在挂载 + props 变化时调) */
262
392
  setEditConfig(cfg: EditConfig): void;
263
- /** 该格当前是否可编辑(综合 editable + readOnlyRanges + cellReadOnly) */
393
+ /** 该格当前是否可编辑(综合 editable + editableTargets 白名单 + readOnlyRanges + cellReadOnly) */
264
394
  isCellEditable(row: number, col: number): boolean;
395
+ /**
396
+ * **运行时**改可编辑白名单(2026-06-08 新增) —— 不动 `:editableTargets` prop,
397
+ * 直接覆盖 `editCfg.editableTargets`. 立即重绘以反映只读光标变化.
398
+ * 传 `undefined` = 关闭白名单(默认全可编辑);`[]` = 全只读;单值或数组 = 白名单.
399
+ */
400
+ setEditableTargets(targets: EditConfig['editableTargets']): void;
401
+ /** 当前生效的可编辑白名单(运行时 setEditableTargets 或初始 prop). 用 `undefined` 表示未启用白名单 */
402
+ getEditableTargets(): EditConfig['editableTargets'];
265
403
  editCell(row: number, col: number, value: CellValue): boolean;
266
404
  editRange(range: MergeRange, values: CellValue[][]): boolean;
267
405
  clearRange(range: MergeRange): boolean;
268
406
  /** 给区域套样式覆盖(E5;粗体/对齐/填充等);editable 时走命令栈(可撤销 + 发 cell-change + 记脏) */
269
407
  setStyle(range: MergeRange, patch: CellStyleOverride): boolean;
408
+ /** 活动格当前背景填充色(#RRGGBB);无填充/非纯色 → 默认白 #FFFFFF。 */
409
+ getActiveFillColor(): string;
410
+ /** 活动格当前字体色(#RRGGBB);缺省黑 #000000。 */
411
+ getActiveFontColor(): string;
412
+ /** 给当前选区设背景填充色(null = 清除填充);editable 时入命令栈。 */
413
+ setSelectionFill(color: string | null): boolean;
414
+ /** 给当前选区设字体色;editable 时入命令栈。 */
415
+ setSelectionFontColor(color: string): boolean;
416
+ /** 当前选区里 wrapText 的整体态:'all' 全开 / 'none' 全关 / 'mixed' 混合。空选区→'none'。 */
417
+ getSelectionWrapState(): 'all' | 'none' | 'mixed';
418
+ /** 切换当前选区的"自动换行"(WPS 风格):全 wrap → 全关;否则 → 全开。
419
+ * 失效行高缓存让 autofit 按新 wrap 重撑(只扩不缩);editable 时入命令栈(单次撤销 style)。
420
+ * 注:undo 回滚 style 但不回滚行高,与现有 setStyle/autofit "只扩不缩"语义一致。 */
421
+ toggleWrapTextOnSelection(): boolean;
422
+ /** 合并区域(G1;清空被覆盖格,只留左上锚点);editable 时入命令栈 */
423
+ mergeCells(range: MergeRange): boolean;
424
+ /** 拆分区域内的合并(G1);editable 时入命令栈 */
425
+ unmergeCells(range: MergeRange): boolean;
270
426
  /** 读当前表全部图片锚点(克隆)。 */
271
427
  getImages(): ImageAnchor[];
272
428
  /** 加一张图(无 src 但有 bytes+mime 时自动生成 blob url);返回插入索引(失败 -1)。 */
@@ -277,6 +433,50 @@ export declare class ViewerController {
277
433
  moveImage(index: number, dxPx: number, dyPx: number): boolean;
278
434
  /** 缩放图片(目标屏幕像素宽高);editable 时入命令栈 + 发 image-change。 */
279
435
  resizeImage(index: number, widthPx: number, heightPx: number): boolean;
436
+ /** 改 WPS 单元格内嵌图贴合方式(fill/contain/cover);即时重绘。 */
437
+ setCellImageFit(fit: CellImageFit): void;
438
+ /** 读 WPS 单元格内嵌图登记表(克隆,id→{id,src,mime});无则空数组。 */
439
+ getCellImages(): {
440
+ id: string;
441
+ src: string;
442
+ mime?: string;
443
+ }[];
444
+ /** 某格是否内嵌图(DISPIMG)→ 返 {id,src,mime},否则 null(供点击放大判定)。 */
445
+ getCellImageAt(row: number, col: number): {
446
+ id: string;
447
+ src: string;
448
+ mime?: string;
449
+ } | null;
450
+ /** 开/关图片点击放大灯箱(默认开;只读模式单击图、编辑模式右键菜单触发)。 */
451
+ setLightboxEnabled(b: boolean): void;
452
+ /** 打开图片放大灯箱(命令式;src = blob/data/http url)。 */
453
+ openImageLightbox(src: string, fileName?: string, mime?: string): void;
454
+ /** 一张浮动图视觉中心落在哪个单元格(用几何反推;无渲染器时回落锚点 from 格)。 */
455
+ imageCellOf(index: number): {
456
+ row: number;
457
+ col: number;
458
+ } | null;
459
+ /** 浮动图 → 单元格内嵌图(显式指定目标格);失败返 false。 */
460
+ convertImageToCell(imageIndex: number, row: number, col: number): boolean;
461
+ /** 选区批量:把"中心格落在 range 内"的所有浮动图就近嵌入,聚合成单次 undo;返回成功嵌入张数。 */
462
+ convertImagesInRangeToCell(range: MergeRange): number;
463
+ /** 选区批量:把 range 内所有 DISPIMG 格拎成浮动图;聚合成单次 undo;返回成功转换张数。 */
464
+ convertCellImagesInRangeToFloat(range: MergeRange, size?: {
465
+ width: number;
466
+ height: number;
467
+ }): number;
468
+ /** 浮动图 → 内嵌图(**就近**:图在哪格就嵌哪格,目标由几何反推);失败返 false。 */
469
+ convertImageToCellAuto(imageIndex: number): boolean;
470
+ /**
471
+ * 批量把浮动图按"所在单元格"就近嵌入(整表 / 整列)。`col` 给定则只嵌中心落在该列的图。
472
+ * 一次进撤销栈(单次 Ctrl+Z 全撤)。返回成功嵌入的张数。
473
+ */
474
+ convertAllImagesToCells(col?: number): number;
475
+ /** 单元格内嵌图 → 浮动图(把 row,col 的 DISPIMG 拎成浮动图);非内嵌图格返 false。 */
476
+ convertCellImageToFloat(row: number, col: number, size?: {
477
+ width: number;
478
+ height: number;
479
+ }): boolean;
280
480
  /** 在 at 处插入 count 行(原 at 行及之后下移)。 */
281
481
  insertRows(at: number, count?: number): boolean;
282
482
  /** 删除 [at, at+count) 行(与合并相交则警告,相交合并被移除)。 */
@@ -296,6 +496,9 @@ export declare class ViewerController {
296
496
  col: number;
297
497
  } | null;
298
498
  getCellSnapshot(row: number, col: number): CellSnapshot | null;
499
+ /** 单元格"全息体检":snapshot + 合并区 + 浮动图覆盖 + WPS 内嵌图 + 数据验证 + 条件格式命中 + 链接/批注。
500
+ * 无 workbook / sheet / 越界返 null。详见 [src/core/model/inspect.ts](src/core/model/inspect.ts)。 */
501
+ inspectCell(row: number, col: number): CellInspection | null;
299
502
  /** 壳注入合并后的编辑器解析器(plugin.editor + prop.editor) */
300
503
  setEditorResolver(fn?: EditorResolver): void;
301
504
  /**