@file-viewer/pptx 2.1.16 → 2.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { PptxViewer } from './viewer';
2
2
  export { createPptxWorker } from './worker';
3
3
  export { RECOMMENDED_ZIP_LIMITS, createDefaultPptxOptions, resolvePptxEngineOptions, } from './options';
4
- export type { NativePptxEngineOptions, PptxFitMode, PptxListOptions, PptxSlideSize, PptxViewerOptions, PptxWorkerFactoryOptions, PptxWorkerMessage, PptxZipLimits, } from './types';
4
+ export type { NativePptxEngineOptions, PptxDiagnosticError, PptxDiagnosticErrorCode, PptxFitMode, PptxListOptions, PptxSlideSize, PptxViewerOptions, PptxWorkerFactoryOptions, PptxWorkerMessage, PptxZipLimits, } from './types';
package/dist/styles.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const pptxViewerCss = "\n.flyfish-pptx-scale-box{position:relative;box-sizing:border-box;max-width:100%;margin:0 auto;min-width:0}\n.flyfish-pptx-content{position:absolute;top:0;left:0;box-sizing:border-box;max-width:none;min-width:0;transform-origin:top left;will-change:transform}\n.flyfish-pptx-content[data-render-state=\"loading\"]{opacity:.82}\n.flyfish-pptx-content .slide{background:#fff;box-shadow:0 18px 36px rgba(18,35,52,.12)}\n.flyfish-pptx-thumbnail{display:block;box-sizing:border-box;width:min(960px,100%);height:auto;margin:0 auto 28px;border-radius:12px;background:#fff;box-shadow:0 18px 36px rgba(18,35,52,.14)}\n\n.slide{position:relative;border:0;border-radius:0;overflow:hidden;margin-bottom:50px;margin-left:auto;margin-right:auto;z-index:100}\n.slide div.block{position:absolute;top:0;left:0;width:100%;line-height:1}\n.slide div.content{display:flex;flex-direction:column}\n.slide div.diagram-content{display:flex;flex-direction:column}\n.slide div.content-rtl{display:flex;flex-direction:column;direction:rtl}\n.slide .pregraph-rtl{direction:rtl}\n.slide .pregraph-ltr{direction:ltr}\n.slide .pregraph-inherit{direction:inherit}\n.slide .slide-prgrph{width:100%}\n.slide .line-break-br::before{content:\"\\A\";white-space:pre}\n.slide div.v-up{justify-content:flex-start}\n.slide div.v-mid{justify-content:center}\n.slide div.v-down{justify-content:flex-end}\n.slide div.h-left{justify-content:flex-start;align-items:flex-start;text-align:left}\n.slide div.h-left-rtl{justify-content:flex-end;align-items:flex-end;text-align:left}\n.slide div.h-mid{justify-content:center;align-items:center;text-align:center}\n.slide div.h-right{justify-content:flex-end;align-items:flex-end;text-align:right}\n.slide div.h-right-rtl{justify-content:flex-start;align-items:flex-start;text-align:right}\n.slide div.h-just,.slide div.h-dist{text-align:justify}\n.slide div.up-left{justify-content:flex-start;align-items:flex-start;text-align:left}\n.slide div.up-center{justify-content:flex-start;align-items:center}\n.slide div.up-right{justify-content:flex-start;align-items:flex-end}\n.slide div.center-left{justify-content:center;align-items:flex-start;text-align:left}\n.slide div.center-center{justify-content:center;align-items:center}\n.slide div.center-right{justify-content:center;align-items:flex-end}\n.slide div.down-left{justify-content:flex-end;align-items:flex-start;text-align:left}\n.slide div.down-center{justify-content:flex-end;align-items:center}\n.slide div.down-right{justify-content:flex-end;align-items:flex-end}\n.slide li.slide{margin:10px 0;font-size:18px}\n.slide table{position:absolute}\n.slide svg.drawing{position:absolute;overflow:visible}\n\n.flyfish-pptx-content > .slide:last-of-type{margin-bottom:0}\n";
2
- export declare const ensurePptxViewerStyles: (documentRef: Document) => void;
1
+ export declare const pptxViewerCss = "\n.flyfish-pptx-scale-box{position:relative;box-sizing:border-box;max-width:100%;margin:0 auto;min-width:0}\n.flyfish-pptx-content{position:absolute;top:0;left:0;box-sizing:border-box;max-width:none;min-width:0;transform-origin:top left;will-change:transform}\n.flyfish-pptx-content[data-render-state=\"loading\"]{opacity:.82}\n.flyfish-pptx-slide-slot{display:flow-root;box-sizing:border-box;width:100%;min-width:0}\n.flyfish-pptx-content .slide{background:#fff;box-shadow:0 18px 36px rgba(18,35,52,.12)}\n.flyfish-pptx-thumbnail{display:block;box-sizing:border-box;width:min(960px,100%);height:auto;margin:0 auto 28px;border-radius:12px;background:#fff;box-shadow:0 18px 36px rgba(18,35,52,.14)}\n\n.slide{position:relative;border:0;border-radius:0;overflow:hidden;margin-bottom:50px;margin-left:auto;margin-right:auto;z-index:100}\n.slide div.block{position:absolute;top:0;left:0;width:100%;line-height:1}\n.slide div.content{display:flex;flex-direction:column}\n.slide div.diagram-content{display:flex;flex-direction:column}\n.slide div.content-rtl{display:flex;flex-direction:column;direction:rtl}\n.slide .pregraph-rtl{direction:rtl}\n.slide .pregraph-ltr{direction:ltr}\n.slide .pregraph-inherit{direction:inherit}\n.slide .slide-prgrph{width:100%}\n.slide .line-break-br::before{content:\"\\A\";white-space:pre}\n.slide div.v-up{justify-content:flex-start}\n.slide div.v-mid{justify-content:center}\n.slide div.v-down{justify-content:flex-end}\n.slide div.h-left{justify-content:flex-start;align-items:flex-start;text-align:left}\n.slide div.h-left-rtl{justify-content:flex-end;align-items:flex-end;text-align:left}\n.slide div.h-mid{justify-content:center;align-items:center;text-align:center}\n.slide div.h-right{justify-content:flex-end;align-items:flex-end;text-align:right}\n.slide div.h-right-rtl{justify-content:flex-start;align-items:flex-start;text-align:right}\n.slide div.h-just,.slide div.h-dist{text-align:justify}\n.slide div.up-left{justify-content:flex-start;align-items:flex-start;text-align:left}\n.slide div.up-center{justify-content:flex-start;align-items:center}\n.slide div.up-right{justify-content:flex-start;align-items:flex-end}\n.slide div.center-left{justify-content:center;align-items:flex-start;text-align:left}\n.slide div.center-center{justify-content:center;align-items:center}\n.slide div.center-right{justify-content:center;align-items:flex-end}\n.slide div.down-left{justify-content:flex-end;align-items:flex-start;text-align:left}\n.slide div.down-center{justify-content:flex-end;align-items:center}\n.slide div.down-right{justify-content:flex-end;align-items:flex-end}\n.slide li.slide{margin:10px 0;font-size:18px}\n.slide table{position:absolute}\n.slide svg.drawing{position:absolute;overflow:visible}\n\n.flyfish-pptx-content > .slide:last-of-type{margin-bottom:0}\n.flyfish-pptx-slide-slot:last-of-type > .slide{margin-bottom:0}\n";
2
+ export declare const ensurePptxViewerStyles: (documentRef: Document, root?: Document | ShadowRoot) => void;
package/dist/styles.js CHANGED
@@ -35,18 +35,27 @@ export const pptxViewerCss = `
35
35
  .flyfish-pptx-scale-box{position:relative;box-sizing:border-box;max-width:100%;margin:0 auto;min-width:0}
36
36
  .flyfish-pptx-content{position:absolute;top:0;left:0;box-sizing:border-box;max-width:none;min-width:0;transform-origin:top left;will-change:transform}
37
37
  .flyfish-pptx-content[data-render-state="loading"]{opacity:.82}
38
+ .flyfish-pptx-slide-slot{display:flow-root;box-sizing:border-box;width:100%;min-width:0}
38
39
  .flyfish-pptx-content .slide{background:#fff;box-shadow:0 18px 36px rgba(18,35,52,.12)}
39
40
  .flyfish-pptx-thumbnail{display:block;box-sizing:border-box;width:min(960px,100%);height:auto;margin:0 auto 28px;border-radius:12px;background:#fff;box-shadow:0 18px 36px rgba(18,35,52,.14)}
40
41
  ${legacyPptxCss}
41
42
  .flyfish-pptx-content > .slide:last-of-type{margin-bottom:0}
43
+ .flyfish-pptx-slide-slot:last-of-type > .slide{margin-bottom:0}
42
44
  `;
43
45
  const PPTX_STYLE_ID = 'flyfish-pptx-native-style';
44
- export const ensurePptxViewerStyles = (documentRef) => {
45
- if (documentRef.getElementById(PPTX_STYLE_ID)) {
46
+ export const ensurePptxViewerStyles = (documentRef, root = documentRef) => {
47
+ if (root instanceof Document && root.getElementById(PPTX_STYLE_ID)) {
48
+ return;
49
+ }
50
+ if (!(root instanceof Document) && root.querySelector(`#${PPTX_STYLE_ID}`)) {
46
51
  return;
47
52
  }
48
53
  const style = documentRef.createElement('style');
49
54
  style.id = PPTX_STYLE_ID;
50
55
  style.textContent = pptxViewerCss;
51
- (documentRef.head || documentRef.documentElement).appendChild(style);
56
+ if (root instanceof Document) {
57
+ (root.head || root.documentElement).appendChild(style);
58
+ return;
59
+ }
60
+ root.appendChild(style);
52
61
  };
package/dist/types.d.ts CHANGED
@@ -34,7 +34,17 @@ export interface PptxSlideSize {
34
34
  height?: number;
35
35
  [key: string]: unknown;
36
36
  }
37
+ export type PptxDiagnosticErrorCode = 'PPTX_FILE_EMPTY' | 'PPTX_FILE_TOO_LARGE' | 'PPTX_INVALID_ZIP' | 'PPTX_MISSING_CONTENT_TYPES' | 'PPTX_MISSING_PRESENTATION' | 'PPTX_NO_SLIDES' | 'PPTX_MISSING_SLIDE' | 'PPTX_SLIDE_RENDER_FAILED' | 'PPTX_WORKER_FAILED' | 'PPTX_PARSE_FAILED' | (string & {});
38
+ export interface PptxDiagnosticError {
39
+ name: 'PptxDiagnosticError';
40
+ code: PptxDiagnosticErrorCode;
41
+ stage?: string;
42
+ message: string;
43
+ detail?: string;
44
+ hint?: string;
45
+ }
37
46
  export interface PptxViewerOptions extends PptxWorkerFactoryOptions {
47
+ styleRoot?: ShadowRoot;
38
48
  fitMode?: PptxFitMode;
39
49
  zoomPercent?: number;
40
50
  zipLimits?: PptxZipLimits;
package/dist/viewer.d.ts CHANGED
@@ -13,8 +13,15 @@ export declare class PptxViewer {
13
13
  private userZoomPercent;
14
14
  private currentZoomPercent;
15
15
  private thumbnailElement;
16
+ private slideSize;
17
+ private slideRecords;
18
+ private slideWindowTarget;
19
+ private slideWindowListeners;
20
+ private slideWindowFrame;
21
+ private charts;
16
22
  private disposed;
17
23
  private completed;
24
+ private readonly handleSlideWindowChange;
18
25
  private constructor();
19
26
  get zoomPercent(): number;
20
27
  open(): Promise<void>;
@@ -25,9 +32,31 @@ export declare class PptxViewer {
25
32
  private appendGlobalCss;
26
33
  private showThumbnail;
27
34
  private clearThumbnail;
35
+ private shouldWindowSlides;
36
+ private getSlideWindowOptions;
37
+ private getEstimatedSlideHeight;
38
+ private appendWindowedSlide;
39
+ private createWindowedSlideRecord;
40
+ private renderSlideRecord;
41
+ private unmountSlideRecord;
42
+ private updateMeasuredSlideHeight;
43
+ private syncWindowedPlaceholderHeights;
44
+ private scheduleSlideWindowUpdate;
45
+ private updateSlideWindow;
46
+ private getWindowedSlideIndexes;
47
+ private getDistanceFromViewport;
48
+ private getSlideViewport;
49
+ private attachSlideWindowListeners;
50
+ private addSlideWindowListener;
51
+ private detachSlideWindowListeners;
52
+ private findSlideWindowTarget;
53
+ private postProcessRenderedContent;
54
+ private postProcessSlideRecord;
28
55
  private complete;
29
56
  private fail;
30
57
  private attachResizeObserver;
31
58
  private scheduleResize;
32
59
  private resize;
60
+ private getMountedSlideElements;
61
+ private isSlideElement;
33
62
  }
package/dist/viewer.js CHANGED
@@ -9,13 +9,39 @@ const toPercent = (value) => {
9
9
  const numeric = typeof value === 'number' && Number.isFinite(value) ? value : 100;
10
10
  return clamp(numeric, 25, 300);
11
11
  };
12
+ const stringifyErrorDetail = (error) => {
13
+ if (error === undefined || error === null) {
14
+ return '';
15
+ }
16
+ if (typeof error === 'string') {
17
+ return error;
18
+ }
19
+ if (error instanceof Error && error.message) {
20
+ return error.message;
21
+ }
22
+ try {
23
+ const serialized = JSON.stringify(error);
24
+ return serialized && serialized !== '{}' ? serialized : String(error);
25
+ }
26
+ catch {
27
+ return String(error);
28
+ }
29
+ };
30
+ const createPptxDiagnosticError = (code, stage, message, detailOrError, hint) => ({
31
+ name: 'PptxDiagnosticError',
32
+ code,
33
+ stage,
34
+ message,
35
+ detail: stringifyErrorDetail(detailOrError),
36
+ hint,
37
+ });
12
38
  const ensureZipWithinLimits = (buffer, options) => {
13
39
  const limits = {
14
40
  ...RECOMMENDED_ZIP_LIMITS,
15
41
  ...options.zipLimits,
16
42
  };
17
43
  if (limits.maxFileBytes && buffer.byteLength > limits.maxFileBytes) {
18
- throw new Error(`PPTX file is too large to preview safely (${buffer.byteLength} bytes).`);
44
+ throw createPptxDiagnosticError('PPTX_FILE_TOO_LARGE', 'preflight', 'PPTX 文件超过浏览器安全预览体积限制。', `${buffer.byteLength} bytes > ${limits.maxFileBytes} bytes`, '请压缩图片、拆分演示文稿,或在业务侧提高 zipLimits.maxFileBytes 后再预览。');
19
45
  }
20
46
  };
21
47
  const appendHtml = (container, html) => {
@@ -25,6 +51,21 @@ const appendHtml = (container, html) => {
25
51
  container.append(template.content);
26
52
  return nodes[0] || null;
27
53
  };
54
+ const DEFAULT_INITIAL_SLIDES = 3;
55
+ const DEFAULT_BATCH_SIZE = 4;
56
+ const DEFAULT_OVERSCAN_VIEWPORTS = 1.5;
57
+ const DEFAULT_SLIDE_HEIGHT = 540;
58
+ const DEFAULT_SLIDE_GAP = 50;
59
+ const toPositiveInteger = (value, fallback, min = 1, max = Number.MAX_SAFE_INTEGER) => {
60
+ const numeric = typeof value === 'number' && Number.isFinite(value)
61
+ ? Math.floor(value)
62
+ : fallback;
63
+ return clamp(numeric, min, max);
64
+ };
65
+ const toPositiveNumber = (value, fallback, min = 0, max = Number.MAX_SAFE_INTEGER) => {
66
+ const numeric = typeof value === 'number' && Number.isFinite(value) ? value : fallback;
67
+ return clamp(numeric, min, max);
68
+ };
28
69
  export class PptxViewer {
29
70
  static async open(buffer, target, options = {}) {
30
71
  const viewer = new PptxViewer(buffer, target, options);
@@ -39,8 +80,15 @@ export class PptxViewer {
39
80
  this.userZoomPercent = 100;
40
81
  this.currentZoomPercent = 100;
41
82
  this.thumbnailElement = null;
83
+ this.slideSize = null;
84
+ this.slideRecords = [];
85
+ this.slideWindowTarget = null;
86
+ this.slideWindowListeners = [];
87
+ this.slideWindowFrame = 0;
88
+ this.charts = null;
42
89
  this.disposed = false;
43
90
  this.completed = false;
91
+ this.handleSlideWindowChange = () => this.scheduleSlideWindowUpdate();
44
92
  this.buffer = buffer;
45
93
  this.target = target;
46
94
  this.options = options;
@@ -59,9 +107,10 @@ export class PptxViewer {
59
107
  }
60
108
  async open() {
61
109
  ensureZipWithinLimits(this.buffer, this.options);
62
- ensurePptxViewerStyles(this.target.ownerDocument || document);
110
+ ensurePptxViewerStyles(this.target.ownerDocument || document, this.options.styleRoot);
63
111
  this.target.replaceChildren(this.scaleBox);
64
112
  this.attachResizeObserver();
113
+ this.attachSlideWindowListeners();
65
114
  this.startWorker();
66
115
  }
67
116
  async setZoom(percent) {
@@ -70,7 +119,7 @@ export class PptxViewer {
70
119
  this.scheduleResize();
71
120
  }
72
121
  destroy() {
73
- var _a, _b, _c;
122
+ var _a, _b, _c, _d;
74
123
  this.disposed = true;
75
124
  (_a = this.worker) === null || _a === void 0 ? void 0 : _a.terminate();
76
125
  this.worker = null;
@@ -79,22 +128,40 @@ export class PptxViewer {
79
128
  if (this.resizeFrame) {
80
129
  (_c = this.target.ownerDocument.defaultView) === null || _c === void 0 ? void 0 : _c.cancelAnimationFrame(this.resizeFrame);
81
130
  }
131
+ if (this.slideWindowFrame) {
132
+ (_d = this.target.ownerDocument.defaultView) === null || _d === void 0 ? void 0 : _d.cancelAnimationFrame(this.slideWindowFrame);
133
+ }
134
+ this.detachSlideWindowListeners();
82
135
  this.target.replaceChildren();
83
136
  }
84
137
  startWorker() {
85
138
  var _a;
86
139
  (_a = this.worker) === null || _a === void 0 ? void 0 : _a.terminate();
87
140
  this.completed = false;
141
+ this.charts = null;
142
+ this.slideSize = null;
143
+ this.slideRecords = [];
144
+ this.content.replaceChildren();
88
145
  this.content.dataset.renderState = 'loading';
89
- this.worker = createPptxWorker(this.options);
146
+ try {
147
+ this.worker = createPptxWorker(this.options);
148
+ }
149
+ catch (error) {
150
+ throw createPptxDiagnosticError('PPTX_WORKER_FAILED', 'start-worker', 'PPTX Worker 启动失败。', error, '请检查 presentation.workerUrl、Worker 文件是否 200 返回、MIME/CSP/跨域策略是否允许加载。');
151
+ }
90
152
  this.worker.addEventListener('message', event => this.processMessage(event.data));
91
- this.worker.addEventListener('error', event => this.fail(event.error || event.message));
92
- this.worker.postMessage({
93
- type: 'processPPTX',
94
- data: this.buffer,
95
- IE11: false,
96
- options: resolvePptxEngineOptions(this.options.engineOptions),
97
- });
153
+ this.worker.addEventListener('error', event => this.fail(createPptxDiagnosticError('PPTX_WORKER_FAILED', 'worker-runtime', 'PPTX Worker 运行失败。', event.error || event.message, '请检查 Worker 脚本是否完整、浏览器控制台是否有 CSP/MIME/跨域或语法错误。')));
154
+ try {
155
+ this.worker.postMessage({
156
+ type: 'processPPTX',
157
+ data: this.buffer,
158
+ IE11: false,
159
+ options: resolvePptxEngineOptions(this.options.engineOptions),
160
+ });
161
+ }
162
+ catch (error) {
163
+ throw createPptxDiagnosticError('PPTX_WORKER_FAILED', 'post-worker-message', 'PPTX 数据发送到 Worker 失败。', error, '请确认浏览器支持 Worker 消息传递,并避免传入已被释放或不可克隆的数据。');
164
+ }
98
165
  }
99
166
  processMessage(message) {
100
167
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
@@ -104,9 +171,14 @@ export class PptxViewer {
104
171
  switch (message.type) {
105
172
  case 'slide': {
106
173
  this.clearThumbnail();
107
- const element = appendHtml(this.content, String(message.data || ''));
108
- this.scheduleResize();
109
- (_b = (_a = this.options).onSlideRendered) === null || _b === void 0 ? void 0 : _b.call(_a, Number(message.slide_num || 0), element);
174
+ if (this.shouldWindowSlides()) {
175
+ this.appendWindowedSlide(String(message.data || ''), Number(message.slide_num || 0));
176
+ }
177
+ else {
178
+ const element = appendHtml(this.content, String(message.data || ''));
179
+ this.scheduleResize();
180
+ (_b = (_a = this.options).onSlideRendered) === null || _b === void 0 ? void 0 : _b.call(_a, Number(message.slide_num || 0), element);
181
+ }
110
182
  break;
111
183
  }
112
184
  case 'pptx-thumb':
@@ -114,6 +186,8 @@ export class PptxViewer {
114
186
  (_d = (_c = this.options).onThumbnail) === null || _d === void 0 ? void 0 : _d.call(_c, String(message.data || ''));
115
187
  break;
116
188
  case 'slideSize':
189
+ this.slideSize = (message.data || {});
190
+ this.syncWindowedPlaceholderHeights();
117
191
  (_f = (_e = this.options).onSlideSize) === null || _f === void 0 ? void 0 : _f.call(_e, (message.data || {}));
118
192
  break;
119
193
  case 'globalCSS':
@@ -160,17 +234,283 @@ export class PptxViewer {
160
234
  (_a = this.thumbnailElement) === null || _a === void 0 ? void 0 : _a.remove();
161
235
  this.thumbnailElement = null;
162
236
  }
237
+ shouldWindowSlides() {
238
+ var _a;
239
+ return Boolean(this.options.lazySlides || ((_a = this.options.listOptions) === null || _a === void 0 ? void 0 : _a.windowed));
240
+ }
241
+ getSlideWindowOptions() {
242
+ const listOptions = this.options.listOptions || {};
243
+ return {
244
+ initialSlides: toPositiveInteger(listOptions.initialSlides, DEFAULT_INITIAL_SLIDES, 1, 20),
245
+ batchSize: toPositiveInteger(listOptions.batchSize, DEFAULT_BATCH_SIZE, 1, 20),
246
+ overscanViewport: toPositiveNumber(listOptions.overscanViewport, DEFAULT_OVERSCAN_VIEWPORTS, 0.25, 6),
247
+ };
248
+ }
249
+ getEstimatedSlideHeight() {
250
+ var _a, _b;
251
+ const sizeHeight = Number((_a = this.slideSize) === null || _a === void 0 ? void 0 : _a.height);
252
+ const measuredHeight = (_b = this.slideRecords.find(record => record.estimatedHeight > 0)) === null || _b === void 0 ? void 0 : _b.estimatedHeight;
253
+ return Math.ceil((Number.isFinite(sizeHeight) && sizeHeight > 0
254
+ ? sizeHeight + DEFAULT_SLIDE_GAP
255
+ : measuredHeight || DEFAULT_SLIDE_HEIGHT + DEFAULT_SLIDE_GAP));
256
+ }
257
+ appendWindowedSlide(html, slideNumber) {
258
+ const record = this.createWindowedSlideRecord(html, slideNumber || this.slideRecords.length + 1);
259
+ this.slideRecords.push(record);
260
+ this.content.append(record.slot);
261
+ if (this.slideRecords.length <= this.getSlideWindowOptions().initialSlides) {
262
+ this.renderSlideRecord(record);
263
+ }
264
+ this.scheduleResize();
265
+ this.scheduleSlideWindowUpdate();
266
+ }
267
+ createWindowedSlideRecord(html, slideNumber) {
268
+ const slot = this.target.ownerDocument.createElement('div');
269
+ const estimatedHeight = this.getEstimatedSlideHeight();
270
+ slot.className = 'flyfish-pptx-slide-slot';
271
+ slot.dataset.slideNumber = String(slideNumber);
272
+ slot.style.minHeight = `${estimatedHeight}px`;
273
+ return {
274
+ slideNumber,
275
+ html,
276
+ slot,
277
+ element: null,
278
+ estimatedHeight,
279
+ rendered: false,
280
+ notified: false,
281
+ postProcessed: false,
282
+ };
283
+ }
284
+ renderSlideRecord(record) {
285
+ var _a, _b;
286
+ if (record.rendered || this.disposed) {
287
+ return;
288
+ }
289
+ record.slot.replaceChildren();
290
+ record.element = appendHtml(record.slot, record.html);
291
+ record.rendered = true;
292
+ record.postProcessed = false;
293
+ this.updateMeasuredSlideHeight(record);
294
+ if (!record.notified) {
295
+ record.notified = true;
296
+ (_b = (_a = this.options).onSlideRendered) === null || _b === void 0 ? void 0 : _b.call(_a, record.slideNumber, record.element);
297
+ }
298
+ if (this.completed) {
299
+ void this.postProcessSlideRecord(record).finally(() => this.scheduleResize());
300
+ }
301
+ this.scheduleResize();
302
+ }
303
+ unmountSlideRecord(record) {
304
+ if (!record.rendered) {
305
+ return;
306
+ }
307
+ this.updateMeasuredSlideHeight(record);
308
+ record.slot.replaceChildren();
309
+ record.element = null;
310
+ record.rendered = false;
311
+ record.postProcessed = false;
312
+ }
313
+ updateMeasuredSlideHeight(record) {
314
+ var _a;
315
+ const HTMLElementCtor = ((_a = this.target.ownerDocument.defaultView) === null || _a === void 0 ? void 0 : _a.HTMLElement) || HTMLElement;
316
+ if (!(record.element instanceof HTMLElementCtor)) {
317
+ return;
318
+ }
319
+ const view = this.target.ownerDocument.defaultView || window;
320
+ const computed = view.getComputedStyle(record.element);
321
+ const marginTop = parseFloat(computed.marginTop) || 0;
322
+ const marginBottom = parseFloat(computed.marginBottom) || 0;
323
+ const measuredHeight = Math.ceil(record.element.offsetHeight + marginTop + marginBottom);
324
+ const nextHeight = Math.max(measuredHeight, record.estimatedHeight, 1);
325
+ record.estimatedHeight = nextHeight;
326
+ record.slot.style.minHeight = `${nextHeight}px`;
327
+ }
328
+ syncWindowedPlaceholderHeights() {
329
+ if (!this.shouldWindowSlides() || !this.slideRecords.length) {
330
+ return;
331
+ }
332
+ const estimatedHeight = this.getEstimatedSlideHeight();
333
+ for (const record of this.slideRecords) {
334
+ if (!record.rendered && record.estimatedHeight < estimatedHeight) {
335
+ record.estimatedHeight = estimatedHeight;
336
+ record.slot.style.minHeight = `${estimatedHeight}px`;
337
+ }
338
+ }
339
+ this.scheduleResize();
340
+ }
341
+ scheduleSlideWindowUpdate() {
342
+ if (!this.shouldWindowSlides() || this.disposed) {
343
+ return;
344
+ }
345
+ const view = this.target.ownerDocument.defaultView || window;
346
+ if (this.slideWindowFrame) {
347
+ view.cancelAnimationFrame(this.slideWindowFrame);
348
+ }
349
+ this.slideWindowFrame = view.requestAnimationFrame(() => this.updateSlideWindow());
350
+ }
351
+ updateSlideWindow() {
352
+ this.slideWindowFrame = 0;
353
+ if (!this.shouldWindowSlides() || !this.slideRecords.length || this.disposed) {
354
+ return;
355
+ }
356
+ const windowOptions = this.getSlideWindowOptions();
357
+ const indexesToRender = this.getWindowedSlideIndexes(windowOptions);
358
+ let changed = false;
359
+ this.slideRecords.forEach((record, index) => {
360
+ if (indexesToRender.has(index)) {
361
+ const wasRendered = record.rendered;
362
+ this.renderSlideRecord(record);
363
+ changed || (changed = !wasRendered);
364
+ }
365
+ else if (record.rendered) {
366
+ this.unmountSlideRecord(record);
367
+ changed = true;
368
+ }
369
+ });
370
+ if (changed) {
371
+ this.scheduleResize();
372
+ }
373
+ }
374
+ getWindowedSlideIndexes(windowOptions) {
375
+ const viewport = this.getSlideViewport();
376
+ const maxMountedSlides = Math.max(windowOptions.initialSlides, windowOptions.initialSlides + windowOptions.batchSize * 4, 12);
377
+ const selected = new Set();
378
+ for (let index = 0; index < Math.min(windowOptions.initialSlides, this.slideRecords.length); index += 1) {
379
+ selected.add(index);
380
+ }
381
+ const candidates = this.slideRecords
382
+ .map((record, index) => ({
383
+ index,
384
+ distance: this.getDistanceFromViewport(record, viewport, windowOptions.overscanViewport),
385
+ }))
386
+ .filter(candidate => candidate.distance !== Number.POSITIVE_INFINITY)
387
+ .sort((a, b) => a.distance - b.distance || a.index - b.index);
388
+ const closestFallback = candidates[0];
389
+ for (const candidate of candidates) {
390
+ if (candidate.distance > 0 && selected.size >= windowOptions.initialSlides) {
391
+ continue;
392
+ }
393
+ selected.add(candidate.index);
394
+ }
395
+ if (closestFallback) {
396
+ selected.add(closestFallback.index);
397
+ }
398
+ if (selected.size <= maxMountedSlides) {
399
+ return selected;
400
+ }
401
+ const capped = new Set();
402
+ for (let index = 0; index < Math.min(windowOptions.initialSlides, this.slideRecords.length); index += 1) {
403
+ capped.add(index);
404
+ }
405
+ for (const candidate of candidates) {
406
+ if (capped.size >= maxMountedSlides) {
407
+ break;
408
+ }
409
+ capped.add(candidate.index);
410
+ }
411
+ return capped;
412
+ }
413
+ getDistanceFromViewport(record, viewport, overscanViewport) {
414
+ const rect = record.slot.getBoundingClientRect();
415
+ const overscan = viewport.height * overscanViewport;
416
+ const top = viewport.top - overscan;
417
+ const bottom = viewport.bottom + overscan;
418
+ if (rect.bottom >= top && rect.top <= bottom) {
419
+ return 0;
420
+ }
421
+ if (rect.bottom < top) {
422
+ return top - rect.bottom;
423
+ }
424
+ if (rect.top > bottom) {
425
+ return rect.top - bottom;
426
+ }
427
+ return Number.POSITIVE_INFINITY;
428
+ }
429
+ getSlideViewport() {
430
+ const view = this.target.ownerDocument.defaultView || window;
431
+ const HTMLElementCtor = view.HTMLElement;
432
+ if (this.slideWindowTarget instanceof HTMLElementCtor) {
433
+ const rect = this.slideWindowTarget.getBoundingClientRect();
434
+ const height = Math.max(1, rect.height || this.slideWindowTarget.clientHeight || view.innerHeight || 1);
435
+ return {
436
+ top: rect.top,
437
+ bottom: rect.top + height,
438
+ height,
439
+ };
440
+ }
441
+ const height = Math.max(1, view.innerHeight || this.target.ownerDocument.documentElement.clientHeight || DEFAULT_SLIDE_HEIGHT);
442
+ return {
443
+ top: 0,
444
+ bottom: height,
445
+ height,
446
+ };
447
+ }
448
+ attachSlideWindowListeners() {
449
+ if (!this.shouldWindowSlides()) {
450
+ return;
451
+ }
452
+ this.detachSlideWindowListeners();
453
+ const view = this.target.ownerDocument.defaultView || window;
454
+ this.slideWindowTarget = this.findSlideWindowTarget();
455
+ this.addSlideWindowListener(view, 'scroll');
456
+ this.addSlideWindowListener(view, 'resize');
457
+ if (this.slideWindowTarget && this.slideWindowTarget !== view) {
458
+ this.addSlideWindowListener(this.slideWindowTarget, 'scroll');
459
+ }
460
+ }
461
+ addSlideWindowListener(target, type) {
462
+ target.addEventListener(type, this.handleSlideWindowChange, { passive: true });
463
+ this.slideWindowListeners.push({ target, type, listener: this.handleSlideWindowChange });
464
+ }
465
+ detachSlideWindowListeners() {
466
+ for (const { target, type, listener } of this.slideWindowListeners) {
467
+ target.removeEventListener(type, listener);
468
+ }
469
+ this.slideWindowListeners = [];
470
+ this.slideWindowTarget = null;
471
+ }
472
+ findSlideWindowTarget() {
473
+ const view = this.target.ownerDocument.defaultView || window;
474
+ let element = this.target.parentElement;
475
+ while (element && element !== this.target.ownerDocument.body && element !== this.target.ownerDocument.documentElement) {
476
+ const style = view.getComputedStyle(element);
477
+ if (/(auto|scroll|overlay)/.test(`${style.overflowY} ${style.overflow}`)) {
478
+ return element;
479
+ }
480
+ element = element.parentElement;
481
+ }
482
+ return view;
483
+ }
484
+ async postProcessRenderedContent(charts) {
485
+ if (!this.shouldWindowSlides()) {
486
+ await renderPptxPostProcessing(charts, this.content);
487
+ return;
488
+ }
489
+ await Promise.all(this.slideRecords
490
+ .filter(record => record.rendered)
491
+ .map(record => this.postProcessSlideRecord(record)));
492
+ }
493
+ async postProcessSlideRecord(record) {
494
+ if (!record.rendered || record.postProcessed || this.disposed) {
495
+ return;
496
+ }
497
+ await renderPptxPostProcessing(this.charts, record.slot);
498
+ record.postProcessed = true;
499
+ this.updateMeasuredSlideHeight(record);
500
+ }
163
501
  async complete(charts) {
164
502
  var _a, _b, _c;
165
503
  if (this.disposed || this.completed) {
166
504
  return;
167
505
  }
168
506
  this.completed = true;
507
+ this.charts = charts;
169
508
  this.content.dataset.renderState = 'ready';
170
509
  (_a = this.worker) === null || _a === void 0 ? void 0 : _a.terminate();
171
510
  this.worker = null;
511
+ this.scheduleSlideWindowUpdate();
172
512
  this.scheduleResize();
173
- await renderPptxPostProcessing(charts, this.content);
513
+ await this.postProcessRenderedContent(charts);
174
514
  this.scheduleResize();
175
515
  (_c = (_b = this.options).onRenderComplete) === null || _c === void 0 ? void 0 : _c.call(_b);
176
516
  }
@@ -189,7 +529,10 @@ export class PptxViewer {
189
529
  if (typeof ResizeObserver === 'undefined') {
190
530
  return;
191
531
  }
192
- this.resizeObserver = new ResizeObserver(() => this.scheduleResize());
532
+ this.resizeObserver = new ResizeObserver(() => {
533
+ this.scheduleResize();
534
+ this.scheduleSlideWindowUpdate();
535
+ });
193
536
  this.resizeObserver.observe(this.target);
194
537
  if (this.target.parentElement) {
195
538
  this.resizeObserver.observe(this.target.parentElement);
@@ -204,14 +547,10 @@ export class PptxViewer {
204
547
  }
205
548
  resize() {
206
549
  var _a, _b;
207
- const HTMLElementCtor = ((_a = this.target.ownerDocument.defaultView) === null || _a === void 0 ? void 0 : _a.HTMLElement) || HTMLElement;
208
- const slides = Array.from(this.content.children).filter((child) => {
209
- return child instanceof HTMLElementCtor && (child.classList.contains('slide') || child.tagName === 'SECTION');
210
- });
211
- if (!slides.length) {
212
- return;
213
- }
214
- const slideWidth = Math.max(...slides.map(slide => slide.offsetWidth), 0);
550
+ const slides = this.getMountedSlideElements();
551
+ const sizeWidth = Number((_a = this.slideSize) === null || _a === void 0 ? void 0 : _a.width);
552
+ const fallbackWidth = Number.isFinite(sizeWidth) && sizeWidth > 0 ? sizeWidth : 0;
553
+ const slideWidth = Math.max(...slides.map(slide => slide.offsetWidth), fallbackWidth);
215
554
  if (!slideWidth) {
216
555
  return;
217
556
  }
@@ -227,4 +566,29 @@ export class PptxViewer {
227
566
  this.scaleBox.style.height = `${Math.ceil(this.content.scrollHeight * effectiveScale)}px`;
228
567
  this.scaleBox.style.minHeight = '';
229
568
  }
569
+ getMountedSlideElements() {
570
+ var _a;
571
+ const HTMLElementCtor = ((_a = this.target.ownerDocument.defaultView) === null || _a === void 0 ? void 0 : _a.HTMLElement) || HTMLElement;
572
+ const slides = [];
573
+ for (const child of Array.from(this.content.children)) {
574
+ if (!(child instanceof HTMLElementCtor)) {
575
+ continue;
576
+ }
577
+ if (this.isSlideElement(child)) {
578
+ slides.push(child);
579
+ continue;
580
+ }
581
+ if (!child.classList.contains('flyfish-pptx-slide-slot')) {
582
+ continue;
583
+ }
584
+ const firstChild = child.firstElementChild;
585
+ if (firstChild instanceof HTMLElementCtor && this.isSlideElement(firstChild)) {
586
+ slides.push(firstChild);
587
+ }
588
+ }
589
+ return slides;
590
+ }
591
+ isSlideElement(element) {
592
+ return element.classList.contains('slide') || element.tagName === 'SECTION';
593
+ }
230
594
  }