ooxml-excel-editor 1.3.3 → 1.7.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 (42) hide show
  1. package/CHANGELOG.md +218 -0
  2. package/README.md +27 -8
  3. package/dist/chunks/index-BNQIWClg.js +12532 -0
  4. package/dist/chunks/{index.es-D9BGYyEt.js → index.es-BVXJfTmn.js} +1 -1
  5. package/dist/chunks/{index.es-n6H_ncuE.js → index.es-gb2_kTSZ.js} +1 -1
  6. package/dist/chunks/{jspdf.es.min-Dbn0akWf.js → jspdf.es.min-C7JL2eZm.js} +2 -2
  7. package/dist/chunks/{jspdf.es.min-B6-ocR7J.js → jspdf.es.min-CBWDsR7H.js} +2 -2
  8. package/dist/chunks/plugin-overlay-C_qauTcv.js +11057 -0
  9. package/dist/chunks/{toolbar-icons-fOm95ASq.js → toolbar-icons-CD7G5Aof.js} +62 -71
  10. package/dist/components/ExcelViewer.vue.d.ts +29 -1
  11. package/dist/core/edit/clipboard-html.d.ts +12 -0
  12. package/dist/core/edit/clipboard-snapshot.d.ts +76 -0
  13. package/dist/core/edit/context-menu.d.ts +14 -1
  14. package/dist/core/edit/edit-controller.d.ts +22 -1
  15. package/dist/core/edit/paste-behavior.d.ts +33 -0
  16. package/dist/core/edit/types.d.ts +20 -0
  17. package/dist/core/export/exporter.d.ts +2 -0
  18. package/dist/core/export/pivot-tables.d.ts +17 -0
  19. package/dist/core/export/xlsx-writer.d.ts +6 -0
  20. package/dist/core/index.d.ts +4 -2
  21. package/dist/core/model/mutations.d.ts +2 -0
  22. package/dist/core/model/types.d.ts +61 -0
  23. package/dist/core/parser/pivot-parser.d.ts +3 -0
  24. package/dist/core/plugin.d.ts +47 -5
  25. package/dist/core/render/canvas-renderer.d.ts +12 -0
  26. package/dist/core/render/pivot-toggle.d.ts +13 -0
  27. package/dist/core/viewer/controller.d.ts +82 -6
  28. package/dist/core/viewer/overlay-manager.d.ts +1 -0
  29. package/dist/core/viewer/paste-config-host.d.ts +12 -0
  30. package/dist/core/viewer/pivot-dialog-host.d.ts +48 -0
  31. package/dist/core/viewer/readonly-prompt-host.d.ts +23 -0
  32. package/dist/core.js +67 -64
  33. package/dist/index.d.ts +2 -2
  34. package/dist/index.js +948 -873
  35. package/dist/react/ExcelViewer.d.ts +25 -4
  36. package/dist/react.js +621 -535
  37. package/dist/style.css +1 -1
  38. package/dist/vue2.css +1 -1
  39. package/dist/vue2.js +1 -1
  40. package/package.json +1 -1
  41. package/dist/chunks/index-6q8kSGQg.js +0 -10575
  42. package/dist/chunks/plugin-overlay-BUrPrpT2.js +0 -9146
@@ -1,30 +1,16 @@
1
1
  var S = Object.defineProperty;
2
- var T = Object.getOwnPropertySymbols;
2
+ var z = Object.getOwnPropertySymbols;
3
3
  var E = Object.prototype.hasOwnProperty, H = Object.prototype.propertyIsEnumerable;
4
- var j = (e, r, t) => r in e ? S(e, r, { enumerable: !0, configurable: !0, writable: !0, value: t }) : e[r] = t, f = (e, r) => {
4
+ var j = (e, r, t) => r in e ? S(e, r, { enumerable: !0, configurable: !0, writable: !0, value: t }) : e[r] = t, w = (e, r) => {
5
5
  for (var t in r || (r = {}))
6
6
  E.call(r, t) && j(e, t, r[t]);
7
- if (T)
8
- for (var t of T(r))
7
+ if (z)
8
+ for (var t of z(r))
9
9
  H.call(r, t) && j(e, t, r[t]);
10
10
  return e;
11
11
  };
12
- import { i as R, p as G } from "./plugin-overlay-BUrPrpT2.js";
13
- const N = Array(17).fill("#000000");
14
- function F() {
15
- return {
16
- font: { name: "Calibri", size: 11, bold: !1, italic: !1, underline: !1, strike: !1, color: "#000000" },
17
- fill: { type: "none" },
18
- borders: {},
19
- hAlign: "general",
20
- vAlign: "bottom",
21
- wrapText: !1,
22
- shrinkToFit: !1,
23
- textRotation: 0,
24
- indent: 0,
25
- numFmt: "General"
26
- };
27
- }
12
+ import { m as N, i as k, p as G } from "./plugin-overlay-C_qauTcv.js";
13
+ const O = Array(17).fill("#000000");
28
14
  function V(e, r) {
29
15
  return {
30
16
  name: e,
@@ -32,7 +18,7 @@ function V(e, r) {
32
18
  state: "visible",
33
19
  dimension: { rows: 0, cols: 0 },
34
20
  cells: /* @__PURE__ */ new Map(),
35
- styles: [F()],
21
+ styles: [N()],
36
22
  merges: [],
37
23
  columns: /* @__PURE__ */ new Map(),
38
24
  rows: /* @__PURE__ */ new Map(),
@@ -45,10 +31,11 @@ function V(e, r) {
45
31
  charts: [],
46
32
  shapes: [],
47
33
  sparklines: [],
34
+ pivotTables: [],
48
35
  showGridLines: !0
49
36
  };
50
37
  }
51
- function O(e, r) {
38
+ function $(e, r) {
52
39
  if (e == null || e === "") return { raw: null, type: "empty" };
53
40
  if (typeof e == "number") return { raw: e, type: "number" };
54
41
  if (typeof e == "boolean") return { raw: e, type: "boolean" };
@@ -67,54 +54,54 @@ function O(e, r) {
67
54
  }
68
55
  return { raw: e, type: "string" };
69
56
  }
70
- function $(e, r) {
57
+ function D(e, r) {
71
58
  return r.map((t) => e[t]);
72
59
  }
73
60
  function L(e, r, t = {}) {
74
- var A, x, p, w, C, M;
75
- const s = (A = t.startRow) != null ? A : 0, c = (x = t.startCol) != null ? x : 0, y = (p = t.autoInfer) != null ? p : !0;
76
- let n = 0, h = 0, d = !1;
61
+ var I, x, p, f, M, C;
62
+ const s = (I = t.startRow) != null ? I : 0, c = (x = t.startCol) != null ? x : 0, y = (p = t.autoInfer) != null ? p : !0;
63
+ let l = 0, h = 0, m = !1;
77
64
  if (!r.length) return { lastRow: 0, lastCol: 0, wrote: !1 };
78
- const m = r[0], o = m !== null && typeof m == "object" && !Array.isArray(m), a = o ? (w = t.columns) != null ? w : Object.keys(m) : [];
65
+ const d = r[0], o = d !== null && typeof d == "object" && !Array.isArray(d), a = o ? (f = t.columns) != null ? f : Object.keys(d) : [];
79
66
  let g = s;
80
- if (o && ((C = t.headerRow) == null || C)) {
81
- for (let l = 0; l < a.length; l++) {
82
- const i = g, u = c + l;
83
- e.cells.set(R(i, u), { row: i, col: u, type: "string", raw: a[l], styleId: 0 }), n = Math.max(n, i), h = Math.max(h, u), d = !0;
67
+ if (o && ((M = t.headerRow) == null || M)) {
68
+ for (let n = 0; n < a.length; n++) {
69
+ const i = g, u = c + n;
70
+ e.cells.set(k(i, u), { row: i, col: u, type: "string", raw: a[n], styleId: 0 }), l = Math.max(l, i), h = Math.max(h, u), m = !0;
84
71
  }
85
72
  g++;
86
73
  }
87
- for (let l = 0; l < r.length; l++) {
88
- const i = r[l], u = Array.isArray(i) ? i : $(i, a);
89
- for (let k = 0; k < u.length; k++) {
90
- const I = g + l - (o && ((M = t.headerRow) == null || M), 0), b = c + k, z = O(u[k], y);
91
- z.type !== "empty" && (e.cells.set(R(I, b), { row: I, col: b, type: z.type, raw: z.raw, styleId: 0 }), n = Math.max(n, I), h = Math.max(h, b), d = !0);
74
+ for (let n = 0; n < r.length; n++) {
75
+ const i = r[n], u = Array.isArray(i) ? i : D(i, a);
76
+ for (let R = 0; R < u.length; R++) {
77
+ const b = g + n - (o && ((C = t.headerRow) == null || C), 0), A = c + R, T = $(u[R], y);
78
+ T.type !== "empty" && (e.cells.set(k(b, A), { row: b, col: A, type: T.type, raw: T.raw, styleId: 0 }), l = Math.max(l, b), h = Math.max(h, A), m = !0);
92
79
  }
93
80
  }
94
- return { lastRow: n, lastCol: h, wrote: d };
81
+ return { lastRow: l, lastCol: h, wrote: m };
95
82
  }
96
- function B(e, r = {}) {
83
+ function v(e, r = {}) {
97
84
  var o, a;
98
- const t = r.themeColors && r.themeColors.length === 17 ? r.themeColors : N, s = (o = r.autoInfer) != null ? o : !0, c = (a = r.headerRow) != null ? a : !0;
85
+ const t = r.themeColors && r.themeColors.length === 17 ? r.themeColors : O, s = (o = r.autoInfer) != null ? o : !0, c = (a = r.headerRow) != null ? a : !0;
99
86
  if (e && typeof e == "object" && !Array.isArray(e) && Array.isArray(e.sheets))
100
87
  return { sheets: e.sheets.map((x, p) => {
101
88
  var i;
102
- const w = V(x.name || `Sheet${p + 1}`, p), { lastRow: C, lastCol: M, wrote: l } = L(w, (i = x.rows) != null ? i : [], { headerRow: c, autoInfer: s });
103
- return w.dimension = l ? { rows: C + 1, cols: M + 1 } : { rows: 0, cols: 0 }, w;
89
+ const f = V(x.name || `Sheet${p + 1}`, p), { lastRow: M, lastCol: C, wrote: n } = L(f, (i = x.rows) != null ? i : [], { headerRow: c, autoInfer: s });
90
+ return f.dimension = n ? { rows: M + 1, cols: C + 1 } : { rows: 0, cols: 0 }, f;
104
91
  }), activeSheet: 0, themeColors: t, date1904: !1 };
105
- const y = V(r.sheetName || "Sheet1", 0), n = e != null ? e : [], { lastRow: h, lastCol: d, wrote: m } = L(y, n, { headerRow: c, autoInfer: s });
106
- return y.dimension = m ? { rows: h + 1, cols: d + 1 } : { rows: 0, cols: 0 }, { sheets: [y], activeSheet: 0, themeColors: t, date1904: !1 };
92
+ const y = V(r.sheetName || "Sheet1", 0), l = e != null ? e : [], { lastRow: h, lastCol: m, wrote: d } = L(y, l, { headerRow: c, autoInfer: s });
93
+ return y.dimension = d ? { rows: h + 1, cols: m + 1 } : { rows: 0, cols: 0 }, { sheets: [y], activeSheet: 0, themeColors: t, date1904: !1 };
107
94
  }
108
- function _(e) {
95
+ function B(e) {
109
96
  return !!e && typeof e == "object" && Array.isArray(e.sheets) && typeof e.activeSheet == "number" && Array.isArray(e.themeColors);
110
97
  }
111
- function K(e, r) {
112
- var m;
98
+ function _(e, r) {
99
+ var d;
113
100
  const t = r.sheets[0], s = e.sheets[0];
114
101
  if (!t) return e;
115
102
  if (!s)
116
103
  return {
117
- sheets: [D(t, t.name)],
104
+ sheets: [F(t, t.name)],
118
105
  activeSheet: 0,
119
106
  themeColors: r.themeColors,
120
107
  date1904: e.date1904,
@@ -126,13 +113,13 @@ function K(e, r) {
126
113
  state: "visible",
127
114
  dimension: { rows: 0, cols: 0 },
128
115
  cells: /* @__PURE__ */ new Map(),
129
- styles: t.styles.map((o) => f({}, o)),
130
- merges: t.merges.map((o) => f({}, o)),
116
+ styles: t.styles.map((o) => w({}, o)),
117
+ merges: t.merges.map((o) => w({}, o)),
131
118
  columns: new Map(t.columns),
132
119
  rows: new Map(t.rows),
133
120
  defaultColWidth: t.defaultColWidth,
134
121
  defaultRowHeight: t.defaultRowHeight,
135
- freeze: f({}, t.freeze),
122
+ freeze: w({}, t.freeze),
136
123
  // 条件格式 / 数据验证 不带过来 —— 数据可能不在模板的目标列上, 套规则会误命中
137
124
  conditional: [],
138
125
  dataValidations: [],
@@ -141,12 +128,13 @@ function K(e, r) {
141
128
  charts: [],
142
129
  shapes: [],
143
130
  sparklines: [],
131
+ pivotTables: [],
144
132
  showGridLines: t.showGridLines
145
133
  };
146
- let y = 0, n = 0;
134
+ let y = 0, l = 0;
147
135
  for (const o of s.cells.values()) {
148
- const a = t.cells.get(R(o.row, o.col));
149
- c.cells.set(R(o.row, o.col), {
136
+ const a = t.cells.get(k(o.row, o.col));
137
+ c.cells.set(k(o.row, o.col), {
150
138
  row: o.row,
151
139
  col: o.col,
152
140
  type: o.type,
@@ -155,14 +143,14 @@ function K(e, r) {
155
143
  formula: o.formula,
156
144
  hyperlink: o.hyperlink,
157
145
  comment: o.comment,
158
- styleId: (m = a == null ? void 0 : a.styleId) != null ? m : 0,
146
+ styleId: (d = a == null ? void 0 : a.styleId) != null ? d : 0,
159
147
  dispImgId: o.dispImgId
160
- }), o.row > y && (y = o.row), o.col > n && (n = o.col);
148
+ }), o.row > y && (y = o.row), o.col > l && (l = o.col);
161
149
  }
162
- const h = t.columns.size ? Math.max(...t.columns.keys()) + 1 : 0, d = t.rows.size ? Math.max(...t.rows.keys()) + 1 : 0;
150
+ const h = t.columns.size ? Math.max(...t.columns.keys()) + 1 : 0, m = t.rows.size ? Math.max(...t.rows.keys()) + 1 : 0;
163
151
  return c.dimension = {
164
- rows: Math.max(s.dimension.rows, y + 1, d),
165
- cols: Math.max(s.dimension.cols, n + 1, h)
152
+ rows: Math.max(s.dimension.rows, y + 1, m),
153
+ cols: Math.max(s.dimension.cols, l + 1, h)
166
154
  }, {
167
155
  sheets: [c],
168
156
  activeSheet: 0,
@@ -171,33 +159,34 @@ function K(e, r) {
171
159
  cellImages: e.cellImages
172
160
  };
173
161
  }
174
- function D(e, r) {
162
+ function F(e, r) {
175
163
  return {
176
164
  name: r,
177
165
  index: 0,
178
166
  state: "visible",
179
167
  dimension: { rows: 0, cols: 0 },
180
168
  cells: /* @__PURE__ */ new Map(),
181
- styles: e.styles.map((t) => f({}, t)),
182
- merges: e.merges.map((t) => f({}, t)),
169
+ styles: e.styles.map((t) => w({}, t)),
170
+ merges: e.merges.map((t) => w({}, t)),
183
171
  columns: new Map(e.columns),
184
172
  rows: new Map(e.rows),
185
173
  defaultColWidth: e.defaultColWidth,
186
174
  defaultRowHeight: e.defaultRowHeight,
187
- freeze: f({}, e.freeze),
175
+ freeze: w({}, e.freeze),
188
176
  conditional: [],
189
177
  dataValidations: [],
190
178
  images: [],
191
179
  charts: [],
192
180
  shapes: [],
193
181
  sparklines: [],
182
+ pivotTables: [],
194
183
  showGridLines: e.showGridLines
195
184
  };
196
185
  }
197
- function Z(e, r) {
186
+ function K(e, r) {
198
187
  return G(e, r);
199
188
  }
200
- const q = {
189
+ const Z = {
201
190
  // 放大镜
202
191
  find: '<circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>',
203
192
  // 漏斗
@@ -206,6 +195,8 @@ const q = {
206
195
  "clear-filter": '<path d="M3 5h18l-7 8.5V20l-4-2.2V13.5z"/><line x1="3" y1="3" x2="21" y2="21"/>',
207
196
  // 上下箭头(排序)
208
197
  sort: '<path d="M7 16l3 3 3-3"/><line x1="10" y1="19" x2="10" y2="5"/><path d="M17 8l-3-3-3 3"/><line x1="14" y1="5" x2="14" y2="19"/>',
198
+ // 透视表: 小表格 + 汇总箭头
199
+ "pivot-table": '<rect x="3" y="4" width="13" height="13" rx="1.5"/><line x1="3" y1="8" x2="16" y2="8"/><line x1="7" y1="4" x2="7" y2="17"/><path d="M14 14h7"/><path d="M18 11l3 3-3 3"/>',
209
200
  // 下载(导出)
210
201
  export: '<line x1="12" y1="3" x2="12" y2="15"/><path d="M8 11l4 4 4-4"/><path d="M4 19h16"/>',
211
202
  // 放大镜带 +
@@ -225,14 +216,14 @@ const q = {
225
216
  // 下拉小箭头
226
217
  caret: '<path d="M6 9l6 6 6-6"/>'
227
218
  };
228
- function J(e) {
219
+ function q(e) {
229
220
  return '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' + e + "</svg>";
230
221
  }
231
222
  export {
232
- q as T,
233
- K as a,
234
- _ as i,
235
- B as j,
236
- Z as p,
237
- J as s
223
+ Z as T,
224
+ _ as a,
225
+ B as i,
226
+ v as j,
227
+ K as p,
228
+ q as s
238
229
  };
@@ -7,6 +7,7 @@ import { ReadOptions } from '../core/model/data-access';
7
7
  import { ViewerTheme } from '../core/render/theme';
8
8
  import { ContextMenuBeforePayload, ContextMenuShowPayload, ContextMenuTransform } from '../core/viewer/controller';
9
9
  import { EditableTarget } from '../core/edit/types';
10
+ import { PasteBehavior } from '../core/edit/paste-behavior';
10
11
  import { FormulaEngineFactory } from '../core/formula/engine';
11
12
  import { CellChangePayload, DimChangePayload, DirtyChangePayload, ImageChangePayload, StructChangePayload } from '../core/edit/edit-controller';
12
13
  import { EditorResolver } from '../core/edit/editor-context';
@@ -58,6 +59,12 @@ type __VLS_Props = {
58
59
  toolbar?: boolean | Array<string | ToolbarItem>;
59
60
  /** 编辑总开关:默认 false = 只读(行为不变)。开启后才能进入编辑(E0:闸门) */
60
61
  editable?: boolean;
62
+ /**
63
+ * 透视表功能开关:默认 false = 关闭。开启后(还需 `editable`)工具栏 `pivot-table` 入口可见、
64
+ * `createPivotTable`/`openPivotTableDialog` 等 API 生效、导出 .xlsx 回注真实 OOXML 透视表零件
65
+ * (overlay 模式同时保留原文件透视表)。
66
+ */
67
+ pivotTable?: boolean;
61
68
  /** 按格只读判定:返回 true = 只读(cell 为空格时传 null) */
62
69
  cellReadOnly?: (cell: CellModel | null, pos: {
63
70
  row: number;
@@ -95,6 +102,13 @@ type __VLS_Props = {
95
102
  recalc?: boolean;
96
103
  /** 自定义/自研公式引擎工厂(可换引擎);不给则用默认 HyperFormula(需 npm i hyperformula) */
97
104
  formulaEngine?: FormulaEngineFactory;
105
+ /**
106
+ * 粘贴行为(默认 = 覆盖式 1:1)。控制 Ctrl+V / 右键粘贴时源各方面如何落目标(覆盖/合并/仅值)。
107
+ * 不传 = 默认;也可 `viewer.setPasteBehavior(cfg)` 运行时改、右键「选择性粘贴」逐次选预设。
108
+ */
109
+ pasteBehavior?: Partial<PasteBehavior>;
110
+ /** 粘贴撞只读格的内置提醒:'dialog'(默认,弹窗列出哪些格只读)/ 'toast'(气泡)/ 'none'(只发事件) */
111
+ readOnlyPrompt?: 'dialog' | 'toast' | 'none';
98
112
  /**
99
113
  * 内置导出进度遮罩(P1.5):默认 `true` —— 调 `viewer.downloadPdf` / `downloadImage` / `downloadXlsx` /
100
114
  * `print` / 选区图片批量转换 时,壳自动建 `AbortController` + 接 `onProgress` →
@@ -206,12 +220,23 @@ declare const __VLS_component: import('vue').DefineComponent<__VLS_Props, {
206
220
  setActiveSheet(index: number): void;
207
221
  getSelection(): MergeRange | null;
208
222
  setSelection(range: MergeRange): void;
223
+ scrollToCell(row: number, col: number, opts?: {
224
+ select?: boolean;
225
+ }): boolean;
209
226
  rectOf(row: number, col: number): import('../core/plugin').Rect | null;
210
227
  rectOfRange(range: MergeRange): import('../core/plugin').Rect | null;
211
228
  redraw(): void;
212
229
  isCellEditable(row: number, col: number): boolean;
213
230
  setEditableTargets(targets: EditableTarget | EditableTarget[] | undefined): void;
214
231
  getEditableTargets(): EditableTarget | EditableTarget[] | undefined;
232
+ sortActiveColumn(dir: "asc" | "desc"): boolean;
233
+ createPivotTable(opts: import('../core/plugin').CreatePivotTableOptions): boolean;
234
+ createPivotTableFromSelection(opts?: {
235
+ rowFieldIndex?: number;
236
+ valueFieldIndex?: number;
237
+ output?: import('../core/plugin').PivotOutput;
238
+ }): boolean;
239
+ openPivotTableDialog(): boolean;
215
240
  exportImage(opts?: ImageExportOptions): Promise<Blob>;
216
241
  downloadImage(opts?: ImageExportOptions): Promise<void>;
217
242
  exportPdf(opts?: PdfExportOptions): Promise<Blob>;
@@ -266,7 +291,10 @@ declare const __VLS_component: import('vue').DefineComponent<__VLS_Props, {
266
291
  pasteRichHtml(html: string, at?: {
267
292
  row: number;
268
293
  col: number;
269
- }): boolean;
294
+ }, behaviorOverride?: Partial<PasteBehavior> | null): boolean;
295
+ getPasteBehavior(): PasteBehavior;
296
+ setPasteBehavior(cfg: Partial<PasteBehavior> | null): void;
297
+ openPasteConfigDialog(): boolean;
270
298
  pasteImageBlob(blob: Blob, at?: {
271
299
  row: number;
272
300
  col: number;
@@ -17,7 +17,19 @@ export interface ParsedClipboard {
17
17
  col: number;
18
18
  dataUrl: string;
19
19
  }[];
20
+ /** 列宽(相对列 index → px;来自 <col width>);稀疏 */
21
+ colWidths: number[];
22
+ /** 行高(相对行 index → px;来自 <tr height>);稀疏 */
23
+ rowHeights: number[];
20
24
  }
25
+ /** mso-number-format 的值是 CSS 转义的 Excel 格式码(\0022→" \#→# \;→; \\(→\( …),解回真实格式码。 */
26
+ export declare function unescapeMsoNumFmt(v: string): string;
27
+ /**
28
+ * 从合并 CSS 串里解析 mso-number-format → Excel 格式码(值可能含转义的 \; ,故按引号串/到分号匹配)。
29
+ * **取最后一条**:合并串是 `td默认;类;内联`,按 CSS 层叠后写的覆盖——裸 `td` 默认常带 `mso-number-format:General`,
30
+ * 若取第一条会被它的 General 顶掉、丢掉后面类里的真实日期/货币格式码。
31
+ */
32
+ export declare function parseMsoNumberFormat(css: string): string | undefined;
21
33
  /**
22
34
  * 解析剪贴板 HTML → {values, styles, merges, images}。非浏览器环境 / 无 <table> 返 null(调用方回退 TSV)。
23
35
  */
@@ -0,0 +1,76 @@
1
+ import { AnchorCell, CellModel, CellStyle, CellValueType, MergeRange, RichTextRun, SheetModel, WorkbookModel } from '../model/types';
2
+ /** raw 是 Date 时序列化为 {__d: epochMs},反序列化还原。其余原样 JSON。 */
3
+ type ClipRaw = number | string | boolean | null | {
4
+ __d: number;
5
+ };
6
+ export interface ClipCell {
7
+ r: number;
8
+ c: number;
9
+ type: CellValueType;
10
+ raw: ClipRaw;
11
+ formula?: string;
12
+ hyperlink?: string;
13
+ comment?: string;
14
+ rich?: RichTextRun[];
15
+ dispImgId?: string;
16
+ style: CellStyle;
17
+ }
18
+ export interface ClipImage {
19
+ from: AnchorCell;
20
+ to?: AnchorCell;
21
+ extWidthEmu?: number;
22
+ extHeightEmu?: number;
23
+ editAs?: string;
24
+ mime: string;
25
+ b64: string;
26
+ }
27
+ export interface ClipCellImage {
28
+ id: string;
29
+ mime: string;
30
+ b64: string;
31
+ }
32
+ export interface ClipDim {
33
+ i: number;
34
+ height?: number;
35
+ width?: number;
36
+ hidden?: boolean;
37
+ custom?: boolean;
38
+ }
39
+ export interface ClipSnapshot {
40
+ v: 1;
41
+ rows: number;
42
+ cols: number;
43
+ cells: ClipCell[];
44
+ merges: MergeRange[];
45
+ images: ClipImage[];
46
+ cellImages: ClipCellImage[];
47
+ rowHeights: ClipDim[];
48
+ colWidths: ClipDim[];
49
+ }
50
+ /** 图片字节预算(原始字节):复制区图片总字节超此值 → 降级为"无图 1:1 复制",避免剪贴板超限/卡顿。 */
51
+ export declare const CLIP_IMAGE_BUDGET_BYTES: number;
52
+ /**
53
+ * 抓一段区域的完整模型快照(相对坐标)。
54
+ * `withImageBytes=false`:图片只记引用(id / 浮动序号),b64 留空 —— 给"图片字节走可见 `<img>`、
55
+ * 快照只引用"的瘦身传输用(避免图片被双重 base64),粘贴时由 parseSnapshotHtml 从 `<img>` 回填字节。
56
+ */
57
+ export declare function serializeSnapshot(sheet: SheetModel, wb: WorkbookModel, range: MergeRange, opts?: {
58
+ withImageBytes?: boolean;
59
+ }): ClipSnapshot;
60
+ /** ClipRaw → 运行时 raw(还原 Date)。 */
61
+ export declare function reviveClipRaw(raw: ClipRaw): CellModel['raw'];
62
+ export declare function encodeSnapshot(snap: ClipSnapshot): string;
63
+ export declare function decodeSnapshot(s: string | null | undefined): ClipSnapshot | null;
64
+ /**
65
+ * 把图片字节从可见 `<img data-clip-img="key">` 回填进瘦身快照(传输优化:图片只在 `<img>` 存一份)。
66
+ * key 约定:DISPIMG 单元格图 = `c:${id}`,浮动图 = `f:${序号}`(序号同 serializeSnapshot 的 images 顺序)。
67
+ * 已带 b64(完整快照路径)则原样保留。
68
+ */
69
+ export declare function reattachImages(snap: ClipSnapshot | null, imgB64: Map<string, string>): ClipSnapshot | null;
70
+ /** 降级:去掉所有图片(超字节预算时用)。DISPIMG 格中性化为空格(保样式),避免粘出引用不到的破图。 */
71
+ export declare function withoutImages(snap: ClipSnapshot): ClipSnapshot;
72
+ /** 从剪贴板 HTML 抽出本组件的 1:1 快照(`<table data-ooxml-clip="...">` + 回填 `<img>` 字节);非本组件复制返 null。 */
73
+ export declare function parseSnapshotHtml(html: string): ClipSnapshot | null;
74
+ export declare function bytesToB64(bytes: Uint8Array): string;
75
+ export declare function b64ToBytes(b64: string): Uint8Array;
76
+ export {};
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * 右键上下文菜单宿主(框架无关 DOM)—— 挂到 document.body 的 fixed 菜单,点项执行 action + 关闭,
3
- * 点外部 / Esc 关闭,贴边自动翻转。Vue / React 壳只把 contextmenu 事件转给 controller,菜单逻辑全在这。
3
+ * 点外部 / Esc 关闭,贴边自动翻转。支持一级子菜单(`children`,悬停展开右侧 flyout)。
4
+ * Vue / React 壳只把 contextmenu 事件转给 controller,菜单逻辑全在这。
4
5
  */
5
6
  export interface MenuItem {
6
7
  label?: string;
@@ -8,12 +9,24 @@ export interface MenuItem {
8
9
  disabled?: boolean;
9
10
  /** true = 分隔线 */
10
11
  separator?: boolean;
12
+ /** 子菜单项(有则该行不执行 action,悬停展开右侧 flyout) */
13
+ children?: MenuItem[];
11
14
  }
12
15
  export declare class ContextMenuHost {
13
16
  private el;
17
+ private submenus;
18
+ private openSubRow;
19
+ private subCloseTimer;
14
20
  private cleanup;
15
21
  isOpen(): boolean;
16
22
  show(x: number, y: number, items: MenuItem[]): void;
23
+ /** 建一级菜单 DOM(不挂载、不定位);子菜单悬停时再建 flyout。 */
24
+ private buildMenu;
25
+ /** 在 row 右侧展开子菜单 flyout(贴边翻转到左侧);悬停 flyout 取消关闭,离开 flyout 延时关。 */
26
+ private openSubmenu;
27
+ private scheduleSubClose;
28
+ private cancelSubClose;
29
+ private closeAllSubmenus;
17
30
  close(): void;
18
31
  dispose(): void;
19
32
  }
@@ -1,7 +1,9 @@
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 { PasteBehavior } from './paste-behavior';
4
5
  import { ParsedClipboard } from './clipboard-html';
6
+ import { ClipSnapshot } from './clipboard-snapshot';
5
7
  import { CellPos, DimAxis, EditCommand } from './commands';
6
8
  import { StructOp } from '../model/structure';
7
9
  import { FormulaEngineFactory } from '../formula/engine';
@@ -72,7 +74,12 @@ export declare class EditController {
72
74
  private engine;
73
75
  private engineFor;
74
76
  private warming;
77
+ private pasteBehavior;
75
78
  constructor(host: EditControllerHost);
79
+ /** 读当前粘贴行为(完整配置)。 */
80
+ getPasteBehavior(): PasteBehavior;
81
+ /** 设粘贴行为默认(缺项回落默认);影响 Ctrl+V / 右键「粘贴」。 */
82
+ setPasteBehavior(cfg: Partial<PasteBehavior> | null): void;
76
83
  /** 异步懒初始化引擎(开重算 + 有工厂 + 尚未为当前簿建好)。返回的 Promise 供测试 await;生产 fire-and-forget。 */
77
84
  warmEngine(): Promise<void>;
78
85
  /** 释放引擎(切簿/关重算/dispose)。 */
@@ -94,14 +101,28 @@ export declare class EditController {
94
101
  editCell(row: number, col: number, value: CellValue): boolean;
95
102
  /** 区域批量设值(2D,左上对齐 range.top/left);跳过只读格,返回是否有改动 */
96
103
  editRange(range: MergeRange, values: CellValue[][]): boolean;
104
+ /** 合并区 m 是否与矩形 [top,left]-[bottom,right] 有交叠(用于清粘贴区内的目标原有合并)。 */
105
+ private _mergeOverlaps;
97
106
  /**
98
107
  * 富粘贴(Excel/WPS 复制的 HTML 解析后):值 + 样式 + 合并 + 图片,**整体单次撤销**。
99
108
  * start = 落点左上角。跳过只读格。一次 cloneWorkbook 快照 → 应用全部 → 压一条 restore-wb 逆。
109
+ * behaviorOverride = 右键「选择性粘贴」逐次预设(覆盖 this.pasteBehavior;缺省走默认配置)。
100
110
  */
101
111
  pasteRich(start: {
102
112
  row: number;
103
113
  col: number;
104
- }, parsed: ParsedClipboard): boolean;
114
+ }, parsed: ParsedClipboard, behaviorOverride?: Partial<PasteBehavior> | null): boolean;
115
+ /** 去重后发一次 paste 的 permission-denied(没只读格则不发)。 */
116
+ private _emitPasteDenied;
117
+ /**
118
+ * 1:1 保真粘贴:用复制时序列化进剪贴板的完整模型快照(本组件自己复制的,跨实例 Vue3/Vue2/React 通用)
119
+ * 覆盖式落到目标区 —— 完整还原 值/原始类型/数字格式/全部样式(含边框)/合并/图片(浮动 + DISPIMG)/行高列宽。
120
+ * 跟 pasteRich(外部 HTML 近似)同走命令栈(整体单次撤销)+ 前后快照 cell-change + 只读 permission-denied。
121
+ */
122
+ pasteSnapshot(start: {
123
+ row: number;
124
+ col: number;
125
+ }, snap: ClipSnapshot, behaviorOverride?: Partial<PasteBehavior> | null): boolean;
105
126
  /** 清空区域(跳过只读) */
106
127
  clearRange(range: MergeRange): boolean;
107
128
  /** 给区域套样式覆盖(E5;跳过只读格)。返回是否有改动。前后 style 不同 → 发 cell-change。 */
@@ -0,0 +1,33 @@
1
+ import { CellStyle, CellStyleOverride } from '../model/types';
2
+ export interface PasteBehavior {
3
+ /** 字体/对齐/换行/边框/数字格式: 'overwrite' 覆盖式(只留源) | 'merge' 合并式(源没写的留目标) | 'skip' 不动 */
4
+ cellStyle: 'overwrite' | 'merge' | 'skip';
5
+ /** 填充底色: 'overwrite' 覆盖式(源没写→无填充) | 'merge' 合并式(源没写→留目标底色) | 'skip' 不动 */
6
+ fill: 'overwrite' | 'merge' | 'skip';
7
+ /** 行高: 'source' 搬源行高 | 'keep' 不动 */
8
+ rowHeight: 'source' | 'keep';
9
+ /** 列宽: 'source' 总搬源 | 'keep' 不动 | 'firstRowOnly' 仅粘到首行(start.row===0)搬源 */
10
+ colWidth: 'source' | 'keep' | 'firstRowOnly';
11
+ /** 源自带的合并区: 'apply' 应用 | 'skip' 不应用 */
12
+ sourceMerges: 'apply' | 'skip';
13
+ /** 目标原有、落在粘贴区内的合并: 'clear' 清掉(否则旧合并会吞列致数据错位) | 'keep' 保留 */
14
+ targetMerges: 'clear' | 'keep';
15
+ /** 图片(内嵌/浮动): 'apply' 落格 | 'skip' 不粘 */
16
+ images: 'apply' | 'skip';
17
+ }
18
+ /** 默认粘贴行为 = 覆盖式 1:1(贴近源)。不配置即此。 */
19
+ export declare const DEFAULT_PASTE_BEHAVIOR: PasteBehavior;
20
+ /** 右键预设:保留原来的(仅值)—— 只落值,样式/底色/行高列宽/合并/图片全不动,目标结构完全保留。 */
21
+ export declare const PASTE_PRESET_VALUES_ONLY: PasteBehavior;
22
+ /** 补全部分配置为完整 PasteBehavior(缺项回落默认)。 */
23
+ export declare function resolvePasteBehavior(partial?: Partial<PasteBehavior> | null): PasteBehavior;
24
+ /** 列宽该不该搬:source 总搬;firstRowOnly 仅 start.row===0 搬;keep 不搬。 */
25
+ export declare function shouldApplyColWidth(b: PasteBehavior, startRow: number): boolean;
26
+ /**
27
+ * 按 cellStyle / fill 两档模式,从「目标现有样式 target + 源 patch」算出粘贴后该格的完整 CellStyle。
28
+ * 返回 null = 两档都 skip,样式整个不动(调用方跳过)。neutral = 表的中性默认(styles[0])。
29
+ *
30
+ * 非填充部分(font/对齐/换行/边框/数字格式):overwrite 以 neutral 为基套源(只留源);merge 以 target 为基套源
31
+ * (源没写的留目标);skip 保留 target。填充部分(fill):overwrite 源没写→无填充;merge 源没写→留目标;skip 留目标。
32
+ */
33
+ export declare function resolvePastedCellStyle(target: CellStyle, neutral: CellStyle, patch: CellStyleOverride, styleMode: PasteBehavior['cellStyle'], fillMode: PasteBehavior['fill']): CellStyle | null;
@@ -1,5 +1,6 @@
1
1
  import { CellModel, MergeRange } from '../model/types';
2
2
  import { FormulaEngineFactory } from '../formula/engine';
3
+ import { PasteBehavior } from './paste-behavior';
3
4
  /**
4
5
  * 行/列维度目标 (Phase B, 2026-06-08) —— 用于尺寸 API (setColumnWidth / setRowHeight /
5
6
  * autoFitColumns / resetColumnWidth ...) 的参数. 3 种形状自动识别:
@@ -44,6 +45,12 @@ export type EditableTarget = {
44
45
  export interface EditConfig {
45
46
  /** 总开关:默认 false = 只读(行为与历史完全一致) */
46
47
  editable?: boolean;
48
+ /**
49
+ * 透视表功能开关(默认 false = 关闭)。开启后(还需 `editable`):工具栏 `pivot-table` 入口可见、
50
+ * `createPivotTable` / `openPivotTableDialog` 等 API 生效、导出 .xlsx 回注真实 OOXML 透视表零件
51
+ * (含 overlay 模式保留原文件透视表)。关闭时上述行为全部不生效,与历史版本一致。
52
+ */
53
+ pivotTable?: boolean;
47
54
  /** 按格只读判定:返回 true = 该格只读。cell 为空格时传 null。pos 为 0-based 行列。 */
48
55
  cellReadOnly?: (cell: CellModel | null, pos: {
49
56
  row: number;
@@ -80,6 +87,19 @@ export interface EditConfig {
80
87
  recalc?: boolean;
81
88
  /** 自定义/自研公式引擎工厂(可换引擎);不给则用默认 HyperFormula 适配器。 */
82
89
  formulaEngine?: FormulaEngineFactory;
90
+ /**
91
+ * 粘贴行为配置(默认 = 覆盖式 1:1,见 {@link PasteBehavior})。控制 Ctrl+V / 右键粘贴时
92
+ * 源内容各方面如何落到目标(覆盖 / 合并 / 仅值)。不传 = 默认。也可运行时 `viewer.setPasteBehavior(cfg)`,
93
+ * 或右键「选择性粘贴」逐次选预设。缺项回落默认。
94
+ */
95
+ pasteBehavior?: Partial<PasteBehavior>;
96
+ /**
97
+ * 粘贴撞只读格时的**内置提醒**方式(逐格精确,即便编辑模式下也可能有只读格):
98
+ * - `'dialog'`(默认):弹窗列出**具体哪些格**只读,让用户明确知道哪部分没粘上;
99
+ * - `'toast'`:顶部气泡简短提示 + 自动消失;
100
+ * - `'none'`:不弹内置 UI(仍发 `permission-denied` 事件,消费方自处理)。
101
+ */
102
+ readOnlyPrompt?: 'dialog' | 'toast' | 'none';
83
103
  }
84
104
  /** 单元格编辑权限 */
85
105
  export type EditPermission = 'editable' | 'readonly';
@@ -16,6 +16,8 @@ export interface ExporterHost {
16
16
  getFileName(): string | undefined;
17
17
  /** 原始 .xlsx 字节(高保真 overlay 导出重载原件用;无则返回 null,overlay 回退 rebuild) */
18
18
  getSourceBuffer?(): ArrayBuffer | null;
19
+ /** 透视表功能是否开启(EditConfig.pivotTable;决定 xlsx 导出是否回注/保留 pivot 零件) */
20
+ isPivotEnabled?(): boolean;
19
21
  }
20
22
  export declare class WorkbookExporter {
21
23
  private host;
@@ -0,0 +1,17 @@
1
+ import { WorkbookModel } from '../model/types';
2
+ /**
3
+ * 把 App 内创建的透视表回注进 ExcelJS 写出的 zip 字节,返回新 zip 字节。
4
+ * 无可回注透视表 → 原样返回(零开销)。单个透视表失败不影响其余(整体 try/catch 由调用方兜底)。
5
+ */
6
+ export declare function injectPivotTablesIntoZip(zipBytes: Uint8Array, workbook: WorkbookModel): Uint8Array;
7
+ /**
8
+ * overlay 导出:把**原文件**的透视表零件原样搬运进 ExcelJS 写出的 zip(ExcelJS 不建模 pivot,
9
+ * load→write 会整套丢掉)。与 `injectPivotTablesIntoZip`(重建 App 内新建的)互补,先搬运后重建,
10
+ * 重建侧的零件编号/cacheId 扫描会自动避开搬运进来的。
11
+ *
12
+ * 搬运内容:xl/pivotCache/** + xl/pivotTables/**(零件 + _rels 整目录)、workbook `<pivotCaches>`
13
+ * 注册(cacheId 不变,r:id 在新 rels 里重新分配)、所在 worksheet 的隐式关系(按表名匹配新旧
14
+ * worksheet)、`[Content_Types].xml` Override。源数据被编辑过时,打开后由宿主刷新重算(透视表
15
+ * 自身带 cacheSource 范围)。原件无透视表 → 原样返回(零开销)。
16
+ */
17
+ export declare function restoreOriginalPivotPartsIntoZip(zipBytes: Uint8Array, sourceBytes: Uint8Array): Uint8Array;
@@ -15,6 +15,12 @@ export interface XlsxExportOptions {
15
15
  fidelity?: 'rebuild' | 'overlay';
16
16
  /** 原始 .xlsx 字节(overlay 模式用;由 exporter 从 host 注入,用方一般不直接传) */
17
17
  sourceBuffer?: ArrayBuffer;
18
+ /**
19
+ * 透视表零件回注开关(默认 false,经 viewer 导出时随 `EditConfig.pivotTable` 自动注入)。
20
+ * 开启时:① App 内创建的透视表重建成真实 OOXML 零件(pivot-tables.ts);② overlay 模式
21
+ * 从 `sourceBuffer` 原样搬运原文件的透视表零件(ExcelJS 不建模 pivot,不搬运就丢)。
22
+ */
23
+ pivotTables?: boolean;
18
24
  /** 长任务进度回调(zip 写出前/后 emit `{stage:'zip'}`;exceljs writeBuffer 黑盒) */
19
25
  onProgress?: ExportProgressFn;
20
26
  /** 取消信号(zip 阶段前后检查) */