@file-viewer/pptx 2.2.8 → 2.3.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.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/presentation.d.ts +66 -0
- package/dist/presentation.js +360 -0
- package/dist/sanitize.d.ts +2 -0
- package/dist/sanitize.js +167 -0
- package/dist/styles.js +18 -0
- package/dist/types.d.ts +4 -0
- package/dist/viewer.d.ts +32 -0
- package/dist/viewer.js +167 -16
- package/dist/worker/pptx.worker.js +6 -6
- package/package.json +3 -2
package/dist/viewer.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PptxPresentationState } from './presentation';
|
|
1
2
|
import type { PptxViewerOptions } from './types';
|
|
2
3
|
export declare class PptxViewer {
|
|
3
4
|
static open(buffer: ArrayBuffer, target: HTMLElement, options?: PptxViewerOptions): Promise<PptxViewer>;
|
|
@@ -16,6 +17,9 @@ export declare class PptxViewer {
|
|
|
16
17
|
private previewThumbnailDataUrl;
|
|
17
18
|
private slideSize;
|
|
18
19
|
private slideRecords;
|
|
20
|
+
private totalSlides;
|
|
21
|
+
private savedScroll;
|
|
22
|
+
private pendingScrollRestore;
|
|
19
23
|
private slideWindowTarget;
|
|
20
24
|
private slideWindowListeners;
|
|
21
25
|
private slideWindowFrame;
|
|
@@ -24,10 +28,38 @@ export declare class PptxViewer {
|
|
|
24
28
|
private readonly mediaRecords;
|
|
25
29
|
private disposed;
|
|
26
30
|
private completed;
|
|
31
|
+
private presentation;
|
|
27
32
|
private readonly handleSlideWindowChange;
|
|
28
33
|
private constructor();
|
|
29
34
|
get zoomPercent(): number;
|
|
30
35
|
get thumbnailDataUrl(): string | null;
|
|
36
|
+
get slideCount(): number;
|
|
37
|
+
get slideDimensions(): {
|
|
38
|
+
width: number;
|
|
39
|
+
height: number;
|
|
40
|
+
} | null;
|
|
41
|
+
get presenting(): boolean;
|
|
42
|
+
get presentationSlideNumber(): number;
|
|
43
|
+
private get styleRoot();
|
|
44
|
+
/**
|
|
45
|
+
* Where the slideshow overlay is mounted. It has to share a root with the injected slide styles,
|
|
46
|
+
* or the engine's scoped CSS stops applying once the slides move into the overlay.
|
|
47
|
+
*/
|
|
48
|
+
get presentationRoot(): ShadowRoot | HTMLElement;
|
|
49
|
+
/** Force a slide out of the virtualized window so the slideshow can show it immediately. */
|
|
50
|
+
ensureSlideRendered(slideNumber: number): Element | null;
|
|
51
|
+
refreshLayout(): void;
|
|
52
|
+
/**
|
|
53
|
+
* Remember where the deck was scrolled before the slideshow moves the scale
|
|
54
|
+
* box into the overlay. Moving it collapses the scroller and clamps scrollTop
|
|
55
|
+
* to zero, so the position has to be captured up front and restored on exit.
|
|
56
|
+
*/
|
|
57
|
+
saveScrollPosition(): void;
|
|
58
|
+
restoreScrollPosition(): void;
|
|
59
|
+
emitPresentationChange(state: PptxPresentationState): void;
|
|
60
|
+
enterPresentation(slideNumber?: number): Promise<void>;
|
|
61
|
+
exitPresentation(): void;
|
|
62
|
+
togglePresentation(slideNumber?: number): Promise<void>;
|
|
31
63
|
open(): Promise<void>;
|
|
32
64
|
setZoom(percent: number): Promise<void>;
|
|
33
65
|
destroy(): void;
|
package/dist/viewer.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { renderPptxPostProcessing } from './chart.js';
|
|
2
2
|
import { resolvePptxEngineOptions, RECOMMENDED_ZIP_LIMITS } from './options.js';
|
|
3
|
-
import {
|
|
3
|
+
import { PptxPresentation } from './presentation.js';
|
|
4
|
+
import { sanitizePptxCss, sanitizePptxMarkup } from './sanitize.js';
|
|
5
|
+
import { ensurePptxViewerStyles } from './styles.js';
|
|
4
6
|
import { createPptxWorker } from './worker.js';
|
|
5
7
|
const clamp = (value, min, max) => {
|
|
6
8
|
return Math.min(max, Math.max(min, value));
|
|
@@ -45,10 +47,9 @@ const ensureZipWithinLimits = (buffer, options) => {
|
|
|
45
47
|
}
|
|
46
48
|
};
|
|
47
49
|
const appendHtml = (container, html) => {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
container.append(template.content);
|
|
50
|
+
const fragment = sanitizePptxMarkup(container.ownerDocument, html);
|
|
51
|
+
const nodes = Array.from(fragment.children);
|
|
52
|
+
container.append(fragment);
|
|
52
53
|
return nodes[0] || null;
|
|
53
54
|
};
|
|
54
55
|
const escapeHtml = (value) => String(value !== null && value !== void 0 ? value : '')
|
|
@@ -96,6 +97,13 @@ export class PptxViewer {
|
|
|
96
97
|
this.previewThumbnailDataUrl = null;
|
|
97
98
|
this.slideSize = null;
|
|
98
99
|
this.slideRecords = [];
|
|
100
|
+
// Total slides received from the worker. The windowed path mirrors this in
|
|
101
|
+
// slideRecords, but the default (non-windowed) path appends slides straight
|
|
102
|
+
// into the content node without creating records, so the deck size has to be
|
|
103
|
+
// counted independently of the virtualization bookkeeping.
|
|
104
|
+
this.totalSlides = 0;
|
|
105
|
+
this.savedScroll = null;
|
|
106
|
+
this.pendingScrollRestore = null;
|
|
99
107
|
this.slideWindowTarget = null;
|
|
100
108
|
this.slideWindowListeners = [];
|
|
101
109
|
this.slideWindowFrame = 0;
|
|
@@ -104,6 +112,7 @@ export class PptxViewer {
|
|
|
104
112
|
this.mediaRecords = new Map();
|
|
105
113
|
this.disposed = false;
|
|
106
114
|
this.completed = false;
|
|
115
|
+
this.presentation = null;
|
|
107
116
|
this.handleSlideWindowChange = () => this.scheduleSlideWindowUpdate();
|
|
108
117
|
this.buffer = buffer;
|
|
109
118
|
this.target = target;
|
|
@@ -124,9 +133,111 @@ export class PptxViewer {
|
|
|
124
133
|
get thumbnailDataUrl() {
|
|
125
134
|
return this.previewThumbnailDataUrl;
|
|
126
135
|
}
|
|
136
|
+
get slideCount() {
|
|
137
|
+
return this.totalSlides;
|
|
138
|
+
}
|
|
139
|
+
get slideDimensions() {
|
|
140
|
+
var _a, _b;
|
|
141
|
+
const width = Number((_a = this.slideSize) === null || _a === void 0 ? void 0 : _a.width);
|
|
142
|
+
const height = Number((_b = this.slideSize) === null || _b === void 0 ? void 0 : _b.height);
|
|
143
|
+
return Number.isFinite(width) && width > 0 && Number.isFinite(height) && height > 0
|
|
144
|
+
? { width, height }
|
|
145
|
+
: null;
|
|
146
|
+
}
|
|
147
|
+
get presenting() {
|
|
148
|
+
var _a;
|
|
149
|
+
return Boolean((_a = this.presentation) === null || _a === void 0 ? void 0 : _a.active);
|
|
150
|
+
}
|
|
151
|
+
get presentationSlideNumber() {
|
|
152
|
+
var _a, _b;
|
|
153
|
+
return (_b = (_a = this.presentation) === null || _a === void 0 ? void 0 : _a.slideNumber) !== null && _b !== void 0 ? _b : 1;
|
|
154
|
+
}
|
|
155
|
+
get styleRoot() {
|
|
156
|
+
var _a;
|
|
157
|
+
if (this.options.styleRoot) {
|
|
158
|
+
return this.options.styleRoot;
|
|
159
|
+
}
|
|
160
|
+
const documentRef = this.target.ownerDocument || document;
|
|
161
|
+
const root = this.target.getRootNode();
|
|
162
|
+
const ShadowRootCtor = (_a = documentRef.defaultView) === null || _a === void 0 ? void 0 : _a.ShadowRoot;
|
|
163
|
+
return ShadowRootCtor && root instanceof ShadowRootCtor
|
|
164
|
+
? root
|
|
165
|
+
: documentRef;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Where the slideshow overlay is mounted. It has to share a root with the injected slide styles,
|
|
169
|
+
* or the engine's scoped CSS stops applying once the slides move into the overlay.
|
|
170
|
+
*/
|
|
171
|
+
get presentationRoot() {
|
|
172
|
+
const documentRef = this.target.ownerDocument || document;
|
|
173
|
+
const styleRoot = this.styleRoot;
|
|
174
|
+
if (styleRoot !== documentRef) {
|
|
175
|
+
return styleRoot;
|
|
176
|
+
}
|
|
177
|
+
return documentRef.body || documentRef.documentElement;
|
|
178
|
+
}
|
|
179
|
+
/** Force a slide out of the virtualized window so the slideshow can show it immediately. */
|
|
180
|
+
ensureSlideRendered(slideNumber) {
|
|
181
|
+
const record = this.slideRecords.find(item => item.slideNumber === slideNumber);
|
|
182
|
+
if (!record) {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
if (!record.rendered) {
|
|
186
|
+
this.renderSlideRecord(record);
|
|
187
|
+
}
|
|
188
|
+
return record.element;
|
|
189
|
+
}
|
|
190
|
+
refreshLayout() {
|
|
191
|
+
this.scheduleResize();
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Remember where the deck was scrolled before the slideshow moves the scale
|
|
195
|
+
* box into the overlay. Moving it collapses the scroller and clamps scrollTop
|
|
196
|
+
* to zero, so the position has to be captured up front and restored on exit.
|
|
197
|
+
*/
|
|
198
|
+
saveScrollPosition() {
|
|
199
|
+
const view = this.target.ownerDocument.defaultView || window;
|
|
200
|
+
const HTMLElementCtor = view.HTMLElement;
|
|
201
|
+
const scroller = this.slideWindowTarget || this.findSlideWindowTarget();
|
|
202
|
+
if (scroller instanceof HTMLElementCtor) {
|
|
203
|
+
this.savedScroll = { top: scroller.scrollTop, left: scroller.scrollLeft };
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
this.savedScroll = { top: view.scrollY, left: view.scrollX };
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
restoreScrollPosition() {
|
|
210
|
+
if (this.savedScroll) {
|
|
211
|
+
this.pendingScrollRestore = this.savedScroll;
|
|
212
|
+
this.savedScroll = null;
|
|
213
|
+
}
|
|
214
|
+
this.scheduleResize();
|
|
215
|
+
}
|
|
216
|
+
emitPresentationChange(state) {
|
|
217
|
+
var _a, _b;
|
|
218
|
+
(_b = (_a = this.options).onPresentationChange) === null || _b === void 0 ? void 0 : _b.call(_a, state);
|
|
219
|
+
}
|
|
220
|
+
async enterPresentation(slideNumber) {
|
|
221
|
+
if (this.disposed || this.totalSlides === 0) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
this.presentation || (this.presentation = new PptxPresentation(this, this.options.presentationLabels, this.options.presentationFullscreen));
|
|
225
|
+
await this.presentation.enter(slideNumber);
|
|
226
|
+
}
|
|
227
|
+
exitPresentation() {
|
|
228
|
+
var _a;
|
|
229
|
+
(_a = this.presentation) === null || _a === void 0 ? void 0 : _a.exit();
|
|
230
|
+
}
|
|
231
|
+
async togglePresentation(slideNumber) {
|
|
232
|
+
if (this.presenting) {
|
|
233
|
+
this.exitPresentation();
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
await this.enterPresentation(slideNumber);
|
|
237
|
+
}
|
|
127
238
|
async open() {
|
|
128
239
|
ensureZipWithinLimits(this.buffer, this.options);
|
|
129
|
-
ensurePptxViewerStyles(this.target.ownerDocument || document, this.
|
|
240
|
+
ensurePptxViewerStyles(this.target.ownerDocument || document, this.styleRoot);
|
|
130
241
|
this.target.replaceChildren(this.scaleBox);
|
|
131
242
|
this.attachResizeObserver();
|
|
132
243
|
this.attachSlideWindowListeners();
|
|
@@ -138,20 +249,22 @@ export class PptxViewer {
|
|
|
138
249
|
this.scheduleResize();
|
|
139
250
|
}
|
|
140
251
|
destroy() {
|
|
141
|
-
var _a, _b, _c, _d;
|
|
252
|
+
var _a, _b, _c, _d, _e;
|
|
142
253
|
this.disposed = true;
|
|
254
|
+
(_a = this.presentation) === null || _a === void 0 ? void 0 : _a.destroy();
|
|
255
|
+
this.presentation = null;
|
|
143
256
|
this.previewThumbnailDataUrl = null;
|
|
144
257
|
this.releaseCharts();
|
|
145
258
|
this.releaseMedia();
|
|
146
|
-
(
|
|
259
|
+
(_b = this.worker) === null || _b === void 0 ? void 0 : _b.terminate();
|
|
147
260
|
this.worker = null;
|
|
148
|
-
(
|
|
261
|
+
(_c = this.resizeObserver) === null || _c === void 0 ? void 0 : _c.disconnect();
|
|
149
262
|
this.resizeObserver = null;
|
|
150
263
|
if (this.resizeFrame) {
|
|
151
|
-
(
|
|
264
|
+
(_d = this.target.ownerDocument.defaultView) === null || _d === void 0 ? void 0 : _d.cancelAnimationFrame(this.resizeFrame);
|
|
152
265
|
}
|
|
153
266
|
if (this.slideWindowFrame) {
|
|
154
|
-
(
|
|
267
|
+
(_e = this.target.ownerDocument.defaultView) === null || _e === void 0 ? void 0 : _e.cancelAnimationFrame(this.slideWindowFrame);
|
|
155
268
|
}
|
|
156
269
|
this.detachSlideWindowListeners();
|
|
157
270
|
this.target.replaceChildren();
|
|
@@ -166,6 +279,7 @@ export class PptxViewer {
|
|
|
166
279
|
this.previewThumbnailDataUrl = null;
|
|
167
280
|
this.slideSize = null;
|
|
168
281
|
this.slideRecords = [];
|
|
282
|
+
this.totalSlides = 0;
|
|
169
283
|
this.content.replaceChildren();
|
|
170
284
|
this.content.dataset.renderState = 'loading';
|
|
171
285
|
try {
|
|
@@ -196,6 +310,7 @@ export class PptxViewer {
|
|
|
196
310
|
switch (message.type) {
|
|
197
311
|
case 'slide': {
|
|
198
312
|
this.clearThumbnail();
|
|
313
|
+
this.totalSlides += 1;
|
|
199
314
|
if (this.shouldWindowSlides()) {
|
|
200
315
|
this.appendWindowedSlide(String(message.data || ''), Number(message.slide_num || 0));
|
|
201
316
|
}
|
|
@@ -215,6 +330,7 @@ export class PptxViewer {
|
|
|
215
330
|
const error = message.data;
|
|
216
331
|
const html = buildSlideErrorHtml(slideNumber, error);
|
|
217
332
|
this.clearThumbnail();
|
|
333
|
+
this.totalSlides += 1;
|
|
218
334
|
if (this.shouldWindowSlides()) {
|
|
219
335
|
this.appendWindowedSlide(html, slideNumber);
|
|
220
336
|
}
|
|
@@ -257,11 +373,12 @@ export class PptxViewer {
|
|
|
257
373
|
}
|
|
258
374
|
}
|
|
259
375
|
appendGlobalCss(css) {
|
|
260
|
-
|
|
376
|
+
const sanitized = sanitizePptxCss(this.target.ownerDocument, css);
|
|
377
|
+
if (!sanitized) {
|
|
261
378
|
return;
|
|
262
379
|
}
|
|
263
380
|
const style = this.target.ownerDocument.createElement('style');
|
|
264
|
-
style.textContent =
|
|
381
|
+
style.textContent = sanitized;
|
|
265
382
|
this.content.append(style);
|
|
266
383
|
}
|
|
267
384
|
showThumbnail(base64Jpeg) {
|
|
@@ -419,6 +536,19 @@ export class PptxViewer {
|
|
|
419
536
|
}
|
|
420
537
|
const windowOptions = this.getSlideWindowOptions();
|
|
421
538
|
const indexesToRender = this.getWindowedSlideIndexes(windowOptions);
|
|
539
|
+
// While presenting, the active slide and its neighbour live inside the fixed
|
|
540
|
+
// overlay, outside the scroll-view geometry the windowing code measures. Keep
|
|
541
|
+
// them mounted so a resize cannot unmount the slide that is on screen.
|
|
542
|
+
const presentation = this.presentation;
|
|
543
|
+
if (presentation === null || presentation === void 0 ? void 0 : presentation.active) {
|
|
544
|
+
const activeIndex = this.slideRecords.findIndex(record => record.slideNumber === presentation.slideNumber);
|
|
545
|
+
if (activeIndex >= 0) {
|
|
546
|
+
indexesToRender.add(activeIndex);
|
|
547
|
+
if (activeIndex + 1 < this.slideRecords.length) {
|
|
548
|
+
indexesToRender.add(activeIndex + 1);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
422
552
|
let changed = false;
|
|
423
553
|
this.slideRecords.forEach((record, index) => {
|
|
424
554
|
if (indexesToRender.has(index)) {
|
|
@@ -747,15 +877,20 @@ export class PptxViewer {
|
|
|
747
877
|
this.resizeFrame = view.requestAnimationFrame(() => this.resize());
|
|
748
878
|
}
|
|
749
879
|
resize() {
|
|
750
|
-
var _a, _b;
|
|
880
|
+
var _a, _b, _c;
|
|
881
|
+
// While presenting, the slideshow owns the transform of the same nodes.
|
|
882
|
+
if ((_a = this.presentation) === null || _a === void 0 ? void 0 : _a.active) {
|
|
883
|
+
this.presentation.layout();
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
751
886
|
const slides = this.getMountedSlideElements();
|
|
752
|
-
const sizeWidth = Number((
|
|
887
|
+
const sizeWidth = Number((_b = this.slideSize) === null || _b === void 0 ? void 0 : _b.width);
|
|
753
888
|
const fallbackWidth = Number.isFinite(sizeWidth) && sizeWidth > 0 ? sizeWidth : 0;
|
|
754
889
|
const slideWidth = Math.max(...slides.map(slide => slide.offsetWidth), fallbackWidth);
|
|
755
890
|
if (!slideWidth) {
|
|
756
891
|
return;
|
|
757
892
|
}
|
|
758
|
-
const viewportWidth = this.target.clientWidth || ((
|
|
893
|
+
const viewportWidth = this.target.clientWidth || ((_c = this.target.parentElement) === null || _c === void 0 ? void 0 : _c.clientWidth) || slideWidth;
|
|
759
894
|
this.fitScale = this.options.fitMode === 'none'
|
|
760
895
|
? 1
|
|
761
896
|
: Math.min(1, viewportWidth / slideWidth);
|
|
@@ -766,6 +901,22 @@ export class PptxViewer {
|
|
|
766
901
|
this.scaleBox.style.width = `${Math.ceil(slideWidth * effectiveScale)}px`;
|
|
767
902
|
this.scaleBox.style.height = `${Math.ceil(this.content.scrollHeight * effectiveScale)}px`;
|
|
768
903
|
this.scaleBox.style.minHeight = '';
|
|
904
|
+
// The scale box height is recomputed above; only now can the saved scroll
|
|
905
|
+
// position be applied without the scroller clamping it back to zero.
|
|
906
|
+
if (this.pendingScrollRestore) {
|
|
907
|
+
const { top, left } = this.pendingScrollRestore;
|
|
908
|
+
this.pendingScrollRestore = null;
|
|
909
|
+
const view = this.target.ownerDocument.defaultView || window;
|
|
910
|
+
const HTMLElementCtor = view.HTMLElement;
|
|
911
|
+
const scroller = this.slideWindowTarget || this.findSlideWindowTarget();
|
|
912
|
+
if (scroller instanceof HTMLElementCtor) {
|
|
913
|
+
scroller.scrollTop = top;
|
|
914
|
+
scroller.scrollLeft = left;
|
|
915
|
+
}
|
|
916
|
+
else {
|
|
917
|
+
view.scrollTo(left, top);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
769
920
|
}
|
|
770
921
|
getMountedSlideElements() {
|
|
771
922
|
var _a;
|