@file-viewer/pptx 3.3.0 → 3.3.1
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/README.en.md +1 -0
- package/README.md +1 -0
- package/README.zh-CN.md +1 -0
- package/dist/chart-config.d.ts +16 -1
- package/dist/chart-config.js +18 -8
- package/dist/chart-legend.d.ts +11 -0
- package/dist/chart-legend.js +103 -0
- package/dist/chart-text.d.ts +19 -0
- package/dist/chart-text.js +164 -0
- package/dist/chart-types.d.ts +17 -8
- package/dist/chart.d.ts +3 -1
- package/dist/chart.js +48 -2
- package/dist/index.d.ts +9 -9
- package/dist/index.js +5 -5
- package/dist/options.d.ts +1 -1
- package/dist/presentation.d.ts +20 -1
- package/dist/presentation.js +39 -0
- package/dist/styles.d.ts +5 -0
- package/dist/styles.js +5 -0
- package/dist/types.d.ts +2 -2
- package/dist/viewer.d.ts +13 -2
- package/dist/viewer.js +27 -6
- package/dist/worker/pptx.worker.js +9 -9
- package/dist/worker-location.d.ts +1 -0
- package/dist/worker-location.js +3 -0
- package/dist/worker.d.ts +2 -1
- package/dist/worker.js +13 -1
- package/package.json +5 -1
package/dist/chart.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { createPptxCartesianChart } from
|
|
1
|
+
import { createPptxCartesianChart } from './chart-config.js';
|
|
2
|
+
import { createPptxChartTextPlugin, mergePptxChartFonts, stylePptxChartText } from './chart-text.js';
|
|
3
|
+
import { createPptxChartLegend } from './chart-legend.js';
|
|
2
4
|
let chartLibraryLoader = null;
|
|
3
5
|
let chartRenderer = null;
|
|
6
|
+
/** Register a renderer receiving the preserved Worker model, without reopening the ZIP. */
|
|
4
7
|
export const registerPptxChartRenderer = (renderer) => {
|
|
5
8
|
chartRenderer = renderer;
|
|
6
9
|
};
|
|
@@ -44,6 +47,7 @@ const formatEastAsianNumber = (index) => {
|
|
|
44
47
|
return digits[Math.floor(index / 10000)] + '万' +
|
|
45
48
|
(remainder ? (remainder < 1000 ? digits[0] : '') + group(remainder, false) : '');
|
|
46
49
|
};
|
|
50
|
+
/** DrawingML numbering, including the authored suffix punctuation. */
|
|
47
51
|
export const getNumericBulletText = (type, index) => {
|
|
48
52
|
if (!Number.isInteger(index) || index < 1 || index > 32767) {
|
|
49
53
|
return String(index);
|
|
@@ -175,6 +179,8 @@ const applyTextFitScale = (block, scale) => {
|
|
|
175
179
|
const hasTextOverflow = (block, fitWidth = true) => block.scrollHeight > block.clientHeight + TEXT_FIT_TOLERANCE ||
|
|
176
180
|
(fitWidth && block.scrollWidth > block.clientWidth + TEXT_FIT_TOLERANCE);
|
|
177
181
|
const fitOverflowingTextBlock = (block, fitWidth = true) => {
|
|
182
|
+
// noAutofit/spAutoFit preserve authored typography. Only normal AutoFit may
|
|
183
|
+
// shrink text; unmarked legacy/table content retains its existing policy.
|
|
178
184
|
const mode = block.dataset.pptxAutofit;
|
|
179
185
|
if (mode && mode !== 'normal')
|
|
180
186
|
return;
|
|
@@ -217,7 +223,7 @@ const fitOverflowingTextBlocks = (root) => {
|
|
|
217
223
|
.forEach(block => fitOverflowingTextBlock(block, false));
|
|
218
224
|
};
|
|
219
225
|
const renderChart = async (message, root) => {
|
|
220
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
|
|
226
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
|
|
221
227
|
const payload = message.data;
|
|
222
228
|
if (!(payload === null || payload === void 0 ? void 0 : payload.chartID) || !payload.chartType || !payload.chartData) {
|
|
223
229
|
return;
|
|
@@ -238,10 +244,14 @@ const renderChart = async (message, root) => {
|
|
|
238
244
|
const { billboard, d3Format } = await chartLibraryLoader();
|
|
239
245
|
const bb = billboard.default || billboard;
|
|
240
246
|
const { area, bar, line, pie, donut, scatter } = billboard;
|
|
247
|
+
// Billboard 4 exposes grid focus as a separately registered ESM module.
|
|
248
|
+
// Bundled or custom loaders may already provide it without this export.
|
|
241
249
|
if (typeof billboard.grid === 'function') {
|
|
242
250
|
billboard.grid();
|
|
243
251
|
}
|
|
244
252
|
const chart = {
|
|
253
|
+
// A selector makes Billboard query the main document. That misses chart
|
|
254
|
+
// placeholders inside the viewer Shadow DOM and makes it fall back to body.
|
|
245
255
|
bindto: chartTarget,
|
|
246
256
|
};
|
|
247
257
|
const chartData = payload.chartData;
|
|
@@ -266,6 +276,8 @@ const renderChart = async (message, root) => {
|
|
|
266
276
|
const names = {};
|
|
267
277
|
const colors = {};
|
|
268
278
|
for (const [index, point] of ((series === null || series === void 0 ? void 0 : series.values) || []).entries()) {
|
|
279
|
+
// Keep one library id per indexed point, not per label. Duplicate/empty
|
|
280
|
+
// labels are valid and must never merge sectors. Null is not zero.
|
|
269
281
|
if (typeof point.y !== 'number' || !Number.isFinite(point.y) || point.y === 0)
|
|
270
282
|
continue;
|
|
271
283
|
const id = `point-${index}`;
|
|
@@ -288,6 +300,11 @@ const renderChart = async (message, root) => {
|
|
|
288
300
|
},
|
|
289
301
|
transition: { duration: 0 },
|
|
290
302
|
onrendered() {
|
|
303
|
+
// Billboard accepts an absolute ring width, while DrawingML specifies
|
|
304
|
+
// a ratio. Read its finished SVG geometry and update through the public
|
|
305
|
+
// config API. This also tracks library resize without private state or
|
|
306
|
+
// assumptions about legend/font layout. The re-entrancy guard bounds
|
|
307
|
+
// the corrective redraw to one pass per changed radius.
|
|
291
308
|
if (adjustingWidth || typeof this.config !== 'function')
|
|
292
309
|
return;
|
|
293
310
|
let radius = 0;
|
|
@@ -401,10 +418,15 @@ const renderChart = async (message, root) => {
|
|
|
401
418
|
chart.legend = {
|
|
402
419
|
show: options.legend.show !== false,
|
|
403
420
|
position: options.legend.position === 'b' ? 'bottom' : 'right',
|
|
421
|
+
// The chart library cannot measure an empty SVG text node. Use an
|
|
422
|
+
// invisible measuring character only in legend presentation; names and
|
|
423
|
+
// tooltip data still retain the document's actual empty string.
|
|
404
424
|
format: (label) => label === '' ? '\u200b' : label,
|
|
405
425
|
};
|
|
406
426
|
}
|
|
407
427
|
if (!cartesian && ['barChart', 'areaChart', 'lineChart'].includes(payload.chartType)) {
|
|
428
|
+
// Distinct series can legitimately share a display name. Billboard groups
|
|
429
|
+
// by id, so disambiguate only duplicate ids and preserve authored labels.
|
|
408
430
|
const used = new Set();
|
|
409
431
|
const reserved = new Set(chart.data.columns.map((column) => String(column[0])));
|
|
410
432
|
const names = {};
|
|
@@ -437,27 +459,50 @@ const renderChart = async (message, root) => {
|
|
|
437
459
|
if (Object.keys(colors).length)
|
|
438
460
|
chart.data.colors = colors;
|
|
439
461
|
}
|
|
462
|
+
// Billboard resets the bind target's position to relative. Keep DrawingML
|
|
463
|
+
// placement on the outer frame; only let the library own its inner surface.
|
|
440
464
|
const surface = chartTarget.ownerDocument.createElement('div');
|
|
441
465
|
surface.className = 'pptx-chart-surface';
|
|
442
466
|
surface.style.cssText = 'width:100%;height:100%;min-width:0;min-height:0;flex:1 1 auto';
|
|
443
467
|
chartTarget.replaceChildren(surface);
|
|
444
468
|
chart.bindto = surface;
|
|
469
|
+
const legend = createPptxChartLegend(surface, chart, options === null || options === void 0 ? void 0 : options.legend, mergePptxChartFonts((_q = payload.chartModel) === null || _q === void 0 ? void 0 : _q.font, (_r = options === null || options === void 0 ? void 0 : options.legend) === null || _r === void 0 ? void 0 : _r.font));
|
|
470
|
+
const fontStyle = stylePptxChartText(surface, payload.chartModel, chart, (_t = (_s = payload.chartOptions) === null || _s === void 0 ? void 0 : _s.legend) === null || _t === void 0 ? void 0 : _t.font);
|
|
471
|
+
const textPlugin = createPptxChartTextPlugin(surface);
|
|
472
|
+
if (textPlugin)
|
|
473
|
+
chart.plugins = [...(chart.plugins || []), textPlugin];
|
|
474
|
+
// DrawingML fixes the chart's size in slide coordinates. Billboard's
|
|
475
|
+
// automatic measurement uses transformed screen rectangles (or a visible
|
|
476
|
+
// ancestor for hidden slides), which applies presentation scaling twice.
|
|
477
|
+
// Keep fractional authored dimensions; the viewer scales the whole slide.
|
|
478
|
+
const logicalSize = [((_u = chartTarget.style) === null || _u === void 0 ? void 0 : _u.width) || '', ((_v = chartTarget.style) === null || _v === void 0 ? void 0 : _v.height) || '']
|
|
479
|
+
.map(value => /^\d+(?:\.\d+)?px$/.test(value) ? Number.parseFloat(value) : NaN);
|
|
480
|
+
if (logicalSize.every(value => Number.isFinite(value) && value > 0)) {
|
|
481
|
+
const plotSize = legend === null || legend === void 0 ? void 0 : legend.size();
|
|
482
|
+
chart.size = plotSize && plotSize.width > 0 && plotSize.height > 0 ? plotSize : { width: logicalSize[0], height: logicalSize[1] };
|
|
483
|
+
chart.resize = { auto: false };
|
|
484
|
+
}
|
|
445
485
|
try {
|
|
446
486
|
const instance = bb.generate(chart);
|
|
487
|
+
legend === null || legend === void 0 ? void 0 : legend.bind(instance);
|
|
447
488
|
return {
|
|
448
489
|
destroy() {
|
|
449
490
|
var _a;
|
|
450
491
|
try {
|
|
492
|
+
legend === null || legend === void 0 ? void 0 : legend.destroy();
|
|
451
493
|
(_a = instance.destroy) === null || _a === void 0 ? void 0 : _a.call(instance);
|
|
452
494
|
}
|
|
453
495
|
finally {
|
|
454
496
|
surface.remove();
|
|
497
|
+
fontStyle === null || fontStyle === void 0 ? void 0 : fontStyle.remove();
|
|
455
498
|
}
|
|
456
499
|
},
|
|
457
500
|
};
|
|
458
501
|
}
|
|
459
502
|
catch (error) {
|
|
460
503
|
surface.remove();
|
|
504
|
+
fontStyle === null || fontStyle === void 0 ? void 0 : fontStyle.remove();
|
|
505
|
+
legend === null || legend === void 0 ? void 0 : legend.destroy();
|
|
461
506
|
throw error;
|
|
462
507
|
}
|
|
463
508
|
}
|
|
@@ -493,6 +538,7 @@ export const renderPptxPostProcessing = async (charts, root) => {
|
|
|
493
538
|
(_a = chart.destroy) === null || _a === void 0 ? void 0 : _a.call(chart);
|
|
494
539
|
}
|
|
495
540
|
catch {
|
|
541
|
+
// The DOM may already be detached during framework unmount.
|
|
496
542
|
}
|
|
497
543
|
}
|
|
498
544
|
chartInstances.length = 0;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export { PptxViewer } from
|
|
2
|
-
export { PptxPresentation } from
|
|
3
|
-
export type { PptxPresentationLabels, PptxPresentationState } from
|
|
4
|
-
export { createPptxWorker, resolvePptxPackageWorkerUrl } from
|
|
5
|
-
export { registerPptxChartLibraryLoader, registerPptxChartRenderer } from
|
|
6
|
-
export type { PptxChartLibraries, PptxChartLibraryLoader } from
|
|
7
|
-
export type { PptxChartType, PptxChartLabels, PptxChartOptions, PptxChartSeries, PptxChartAxis, PptxChartGroup, PptxChartModel, PptxChartMessage, PptxChartCollection, PptxChartRenderer, } from
|
|
8
|
-
export { RECOMMENDED_ZIP_LIMITS, createDefaultPptxOptions, resolvePptxEngineOptions, } from
|
|
9
|
-
export type { NativePptxEngineOptions, PptxDiagnosticError, PptxDiagnosticErrorCode, PptxFitMode, PptxListOptions, PptxSlideSize, PptxViewerOptions, PptxWorkerFactoryOptions, PptxWorkerMessage, PptxZipLimits, } from
|
|
1
|
+
export { PptxViewer } from './viewer.js';
|
|
2
|
+
export { PptxPresentation } from './presentation.js';
|
|
3
|
+
export type { PptxPresentationLabels, PptxPresentationState } from './presentation.js';
|
|
4
|
+
export { createPptxWorker, resolvePptxPackageWorkerUrl } from './worker.js';
|
|
5
|
+
export { registerPptxChartLibraryLoader, registerPptxChartRenderer } from './chart.js';
|
|
6
|
+
export type { PptxChartLibraries, PptxChartLibraryLoader } from './chart.js';
|
|
7
|
+
export type { PptxChartType, PptxChartFont, PptxChartLabels, PptxChartOptions, PptxChartSeries, PptxChartAxis, PptxChartGroup, PptxChartModel, PptxChartMessage, PptxChartCollection, PptxChartRenderer, } from './chart-types.js';
|
|
8
|
+
export { RECOMMENDED_ZIP_LIMITS, createDefaultPptxOptions, resolvePptxEngineOptions, } from './options.js';
|
|
9
|
+
export type { NativePptxEngineOptions, PptxDiagnosticError, PptxDiagnosticErrorCode, PptxFitMode, PptxListOptions, PptxSlideSize, PptxViewerOptions, PptxWorkerFactoryOptions, PptxWorkerMessage, PptxZipLimits, } from './types.js';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { PptxViewer } from
|
|
2
|
-
export { PptxPresentation } from
|
|
3
|
-
export { createPptxWorker, resolvePptxPackageWorkerUrl } from
|
|
4
|
-
export { registerPptxChartLibraryLoader, registerPptxChartRenderer } from
|
|
5
|
-
export { RECOMMENDED_ZIP_LIMITS, createDefaultPptxOptions, resolvePptxEngineOptions, } from
|
|
1
|
+
export { PptxViewer } from './viewer.js';
|
|
2
|
+
export { PptxPresentation } from './presentation.js';
|
|
3
|
+
export { createPptxWorker, resolvePptxPackageWorkerUrl } from './worker.js';
|
|
4
|
+
export { registerPptxChartLibraryLoader, registerPptxChartRenderer } from './chart.js';
|
|
5
|
+
export { RECOMMENDED_ZIP_LIMITS, createDefaultPptxOptions, resolvePptxEngineOptions, } from './options.js';
|
package/dist/options.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { NativePptxEngineOptions, PptxZipLimits } from
|
|
1
|
+
import type { NativePptxEngineOptions, PptxZipLimits } from './types.js';
|
|
2
2
|
export declare const RECOMMENDED_ZIP_LIMITS: Required<PptxZipLimits>;
|
|
3
3
|
export declare const createDefaultPptxOptions: () => NativePptxEngineOptions;
|
|
4
4
|
export declare const resolvePptxEngineOptions: (options?: Partial<NativePptxEngineOptions>) => NativePptxEngineOptions;
|
package/dist/presentation.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PptxViewer } from
|
|
1
|
+
import type { PptxViewer } from './viewer.js';
|
|
2
2
|
export interface PptxPresentationState {
|
|
3
3
|
active: boolean;
|
|
4
4
|
slideNumber: number;
|
|
@@ -10,6 +10,14 @@ export interface PptxPresentationLabels {
|
|
|
10
10
|
next?: string;
|
|
11
11
|
previous?: string;
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Full-screen slideshow for a rendered PPTX deck.
|
|
15
|
+
*
|
|
16
|
+
* The slides stay where the viewer put them: the whole scale box is moved into an overlay and the
|
|
17
|
+
* inactive slots are hidden with CSS, so the engine's scoped `.flyfish-pptx-content .slide` rules
|
|
18
|
+
* keep applying and no node is cloned. A placeholder marks the original position so exiting puts
|
|
19
|
+
* everything back exactly where it was.
|
|
20
|
+
*/
|
|
13
21
|
export declare class PptxPresentation {
|
|
14
22
|
private readonly viewer;
|
|
15
23
|
private readonly labels;
|
|
@@ -32,10 +40,21 @@ export declare class PptxPresentation {
|
|
|
32
40
|
goTo(slideNumber: number): void;
|
|
33
41
|
next(): void;
|
|
34
42
|
previous(): void;
|
|
43
|
+
/** Scale the active slide to fit the overlay, letterboxing whichever axis has slack. */
|
|
35
44
|
layout(): void;
|
|
36
45
|
destroy(): void;
|
|
37
46
|
private slots;
|
|
47
|
+
/**
|
|
48
|
+
* The nodes the slideshow toggles between. Windowed decks wrap each slide in a
|
|
49
|
+
* slot; the default (non-windowed) deck appends slides directly to the content
|
|
50
|
+
* node, so those are the containers there.
|
|
51
|
+
*/
|
|
38
52
|
private slideContainers;
|
|
53
|
+
/**
|
|
54
|
+
* Chromium reports the fullscreen element of a shadow root on the shadow root
|
|
55
|
+
* itself while document.fullscreenElement stays on the host, so both have to
|
|
56
|
+
* be checked before deciding who owns fullscreen.
|
|
57
|
+
*/
|
|
39
58
|
private isFullscreenElement;
|
|
40
59
|
private scheduleLayout;
|
|
41
60
|
private notify;
|
package/dist/presentation.js
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|
|
2
|
+
/**
|
|
3
|
+
* Full-screen slideshow for a rendered PPTX deck.
|
|
4
|
+
*
|
|
5
|
+
* The slides stay where the viewer put them: the whole scale box is moved into an overlay and the
|
|
6
|
+
* inactive slots are hidden with CSS, so the engine's scoped `.flyfish-pptx-content .slide` rules
|
|
7
|
+
* keep applying and no node is cloned. A placeholder marks the original position so exiting puts
|
|
8
|
+
* everything back exactly where it was.
|
|
9
|
+
*/
|
|
2
10
|
export class PptxPresentation {
|
|
3
11
|
constructor(viewer, labels = {}, fullscreen = true) {
|
|
4
12
|
this.overlay = null;
|
|
@@ -54,8 +62,11 @@ export class PptxPresentation {
|
|
|
54
62
|
this.overlay = overlay;
|
|
55
63
|
this.stage = stage;
|
|
56
64
|
this.counter = counter;
|
|
65
|
+
// Keep the overlay inside whichever root holds the slide styles, or the engine's scoped CSS
|
|
66
|
+
// would not reach the slides once they move.
|
|
57
67
|
const styleRoot = this.viewer.presentationRoot;
|
|
58
68
|
styleRoot.appendChild(overlay);
|
|
69
|
+
// Park a comment where the scale box lived so exit() can restore the exact position.
|
|
59
70
|
this.placeholder = documentRef.createComment('flyfish-pptx-presentation');
|
|
60
71
|
this.viewer.saveScrollPosition();
|
|
61
72
|
this.viewer.scaleBox.replaceWith(this.placeholder);
|
|
@@ -69,9 +80,13 @@ export class PptxPresentation {
|
|
|
69
80
|
this.ownsFullscreen = this.isFullscreenElement(overlay);
|
|
70
81
|
}
|
|
71
82
|
catch {
|
|
83
|
+
// Fullscreen needs a user gesture and can be blocked by permissions policy. The overlay is
|
|
84
|
+
// position:fixed, so the slideshow still fills the viewport without it.
|
|
72
85
|
this.ownsFullscreen = false;
|
|
73
86
|
}
|
|
74
87
|
}
|
|
88
|
+
// exit() may have run while fullscreen was being requested. Leave the
|
|
89
|
+
// browser out of fullscreen rather than stranded with no overlay.
|
|
75
90
|
if (this.overlay !== overlay) {
|
|
76
91
|
if (this.ownsFullscreen) {
|
|
77
92
|
void ((_b = documentRef.exitFullscreen) === null || _b === void 0 ? void 0 : _b.call(documentRef).catch(() => undefined));
|
|
@@ -90,6 +105,8 @@ export class PptxPresentation {
|
|
|
90
105
|
return;
|
|
91
106
|
}
|
|
92
107
|
const documentRef = this.viewer.target.ownerDocument || document;
|
|
108
|
+
// Capture before the overlay leaves the DOM: a detached node's getRootNode()
|
|
109
|
+
// is the document, so the fullscreen check would fail after removal.
|
|
93
110
|
const shouldExitFullscreen = this.ownsFullscreen && this.isFullscreenElement(overlay);
|
|
94
111
|
this.detach();
|
|
95
112
|
if (this.layoutFrame) {
|
|
@@ -132,8 +149,11 @@ export class PptxPresentation {
|
|
|
132
149
|
}
|
|
133
150
|
this.current = clamp(Math.round(slideNumber) || 1, 1, total);
|
|
134
151
|
this.viewer.ensureSlideRendered(this.current);
|
|
152
|
+
// Rendering the neighbour keeps the next step instant without defeating virtualization.
|
|
135
153
|
this.viewer.ensureSlideRendered(this.current + 1);
|
|
136
154
|
const containers = this.slideContainers();
|
|
155
|
+
// Windowed decks tag each slot with its slide number; non-windowed decks
|
|
156
|
+
// append slides in order, so the active one is found by index.
|
|
137
157
|
const byNumber = containers.some(container => Boolean(container.dataset.slideNumber));
|
|
138
158
|
containers.forEach((container, index) => {
|
|
139
159
|
const isActive = byNumber
|
|
@@ -159,6 +179,7 @@ export class PptxPresentation {
|
|
|
159
179
|
this.goTo(this.current - 1);
|
|
160
180
|
}
|
|
161
181
|
}
|
|
182
|
+
/** Scale the active slide to fit the overlay, letterboxing whichever axis has slack. */
|
|
162
183
|
layout() {
|
|
163
184
|
const overlay = this.overlay;
|
|
164
185
|
if (!overlay) {
|
|
@@ -170,6 +191,7 @@ export class PptxPresentation {
|
|
|
170
191
|
}
|
|
171
192
|
return index === this.current - 1;
|
|
172
193
|
});
|
|
194
|
+
// Windowed decks wrap the slide in a slot; non-windowed decks are the slide.
|
|
173
195
|
const slide = container && container.classList.contains('flyfish-pptx-slide-slot')
|
|
174
196
|
? container.firstElementChild
|
|
175
197
|
: container;
|
|
@@ -198,6 +220,11 @@ export class PptxPresentation {
|
|
|
198
220
|
slots() {
|
|
199
221
|
return Array.from(this.viewer.content.querySelectorAll(':scope > .flyfish-pptx-slide-slot'));
|
|
200
222
|
}
|
|
223
|
+
/**
|
|
224
|
+
* The nodes the slideshow toggles between. Windowed decks wrap each slide in a
|
|
225
|
+
* slot; the default (non-windowed) deck appends slides directly to the content
|
|
226
|
+
* node, so those are the containers there.
|
|
227
|
+
*/
|
|
201
228
|
slideContainers() {
|
|
202
229
|
const slots = this.slots();
|
|
203
230
|
if (slots.length > 0) {
|
|
@@ -205,6 +232,11 @@ export class PptxPresentation {
|
|
|
205
232
|
}
|
|
206
233
|
return Array.from(this.viewer.content.querySelectorAll(':scope > .slide'));
|
|
207
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* Chromium reports the fullscreen element of a shadow root on the shadow root
|
|
237
|
+
* itself while document.fullscreenElement stays on the host, so both have to
|
|
238
|
+
* be checked before deciding who owns fullscreen.
|
|
239
|
+
*/
|
|
208
240
|
isFullscreenElement(overlay) {
|
|
209
241
|
const documentRef = this.viewer.target.ownerDocument || document;
|
|
210
242
|
if (documentRef.fullscreenElement === overlay) {
|
|
@@ -243,6 +275,8 @@ export class PptxPresentation {
|
|
|
243
275
|
});
|
|
244
276
|
this.on(view, 'resize', () => this.scheduleLayout());
|
|
245
277
|
this.on(documentRef, 'fullscreenchange', () => {
|
|
278
|
+
// Leaving fullscreen with the browser's own Esc must close the slideshow too, otherwise the
|
|
279
|
+
// overlay would stay up as a plain fixed layer.
|
|
246
280
|
if (this.ownsFullscreen && !this.isFullscreenElement(overlay)) {
|
|
247
281
|
this.ownsFullscreen = false;
|
|
248
282
|
this.exit();
|
|
@@ -261,6 +295,7 @@ export class PptxPresentation {
|
|
|
261
295
|
if (!overlay || ((_a = event.target) === null || _a === void 0 ? void 0 : _a.closest('.flyfish-pptx-presentation-exit'))) {
|
|
262
296
|
return;
|
|
263
297
|
}
|
|
298
|
+
// PowerPoint advances on click; the left edge is the only place that goes back.
|
|
264
299
|
const bounds = overlay.getBoundingClientRect();
|
|
265
300
|
const ratio = (event.clientX - bounds.left) / (bounds.width || 1);
|
|
266
301
|
if (ratio < 0.2) {
|
|
@@ -274,12 +309,16 @@ export class PptxPresentation {
|
|
|
274
309
|
if (!this.active || event.altKey || event.metaKey || event.ctrlKey) {
|
|
275
310
|
return;
|
|
276
311
|
}
|
|
312
|
+
// Enter/Space on a focused control (the exit button) must activate that
|
|
313
|
+
// control, not advance the slide. Let the browser's default run instead.
|
|
277
314
|
const target = event.target;
|
|
278
315
|
if (target &&
|
|
279
316
|
typeof target.closest === 'function' &&
|
|
280
317
|
target.closest('button, a, input, textarea, select, [contenteditable="true"], [contenteditable=""]')) {
|
|
281
318
|
return;
|
|
282
319
|
}
|
|
320
|
+
// With two slideshows open, a key pressed inside the other overlay belongs
|
|
321
|
+
// to that overlay; only the focused one advances.
|
|
283
322
|
if (target && typeof target.closest === 'function') {
|
|
284
323
|
const otherOverlay = target.closest('.flyfish-pptx-presentation');
|
|
285
324
|
if (otherOverlay && otherOverlay !== this.overlay) {
|
package/dist/styles.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The legacy engine emits class-only selector lists. Prefix each top-level
|
|
3
|
+
* class rule so light-DOM consumers do not inherit generic `.slide` or
|
|
4
|
+
* generated `._css_*` rules from a renderer-local style element.
|
|
5
|
+
*/
|
|
1
6
|
export declare const scopePptxContentStyleText: (cssText: string) => string;
|
|
2
7
|
export declare const pptxViewerCss: string;
|
|
3
8
|
export declare const ensurePptxViewerStyles: (documentRef: Document, root?: Document | ShadowRoot) => void;
|
package/dist/styles.js
CHANGED
|
@@ -51,6 +51,11 @@ const legacyPptxCss = `
|
|
|
51
51
|
|
|
52
52
|
`;
|
|
53
53
|
const PPTX_CONTENT_STYLE_SCOPE = '.flyfish-pptx-content';
|
|
54
|
+
/**
|
|
55
|
+
* The legacy engine emits class-only selector lists. Prefix each top-level
|
|
56
|
+
* class rule so light-DOM consumers do not inherit generic `.slide` or
|
|
57
|
+
* generated `._css_*` rules from a renderer-local style element.
|
|
58
|
+
*/
|
|
54
59
|
export const scopePptxContentStyleText = (cssText) => cssText.replace(/(^|})\s*(\.[^{}]+)\{/g, (_match, boundary, selectorList) => {
|
|
55
60
|
const scopedSelectors = selectorList
|
|
56
61
|
.split(',')
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { PptxPresentationLabels, PptxPresentationState } from
|
|
2
|
-
import type { PptxChartCollection } from
|
|
1
|
+
import type { PptxPresentationLabels, PptxPresentationState } from './presentation.js';
|
|
2
|
+
import type { PptxChartCollection } from './chart-types.js';
|
|
3
3
|
export type PptxFitMode = 'contain' | 'none';
|
|
4
4
|
export interface PptxZipLimits {
|
|
5
5
|
maxFileBytes?: number;
|
package/dist/viewer.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { PptxPresentationState } from
|
|
2
|
-
import type { PptxViewerOptions } from
|
|
1
|
+
import type { PptxPresentationState } from './presentation.js';
|
|
2
|
+
import type { PptxViewerOptions } from './types.js';
|
|
3
3
|
export declare class PptxViewer {
|
|
4
4
|
static open(buffer: ArrayBuffer, target: HTMLElement, options?: PptxViewerOptions): Promise<PptxViewer>;
|
|
5
5
|
readonly target: HTMLElement;
|
|
@@ -41,9 +41,19 @@ export declare class PptxViewer {
|
|
|
41
41
|
get presenting(): boolean;
|
|
42
42
|
get presentationSlideNumber(): number;
|
|
43
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
|
+
*/
|
|
44
48
|
get presentationRoot(): ShadowRoot | HTMLElement;
|
|
49
|
+
/** Force a slide out of the virtualized window so the slideshow can show it immediately. */
|
|
45
50
|
ensureSlideRendered(slideNumber: number): Element | null;
|
|
46
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
|
+
*/
|
|
47
57
|
saveScrollPosition(): void;
|
|
48
58
|
restoreScrollPosition(): void;
|
|
49
59
|
emitPresentationChange(state: PptxPresentationState): void;
|
|
@@ -79,6 +89,7 @@ export declare class PptxViewer {
|
|
|
79
89
|
private findSlideWindowTarget;
|
|
80
90
|
private postProcessRenderedContent;
|
|
81
91
|
private postProcessSlideRecord;
|
|
92
|
+
/** Materializes every lazy slide only for the duration of an export snapshot. */
|
|
82
93
|
cloneForExport(): Promise<HTMLElement>;
|
|
83
94
|
private complete;
|
|
84
95
|
private fail;
|
package/dist/viewer.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { renderPptxPostProcessing } from
|
|
2
|
-
import { resolvePptxEngineOptions, RECOMMENDED_ZIP_LIMITS } from
|
|
3
|
-
import { PptxPresentation } from
|
|
4
|
-
import { sanitizePptxCss, sanitizePptxMarkup } from
|
|
5
|
-
import { ensurePptxViewerStyles } from
|
|
6
|
-
import { createPptxWorker } from
|
|
1
|
+
import { renderPptxPostProcessing } from './chart.js';
|
|
2
|
+
import { resolvePptxEngineOptions, RECOMMENDED_ZIP_LIMITS } from './options.js';
|
|
3
|
+
import { PptxPresentation } from './presentation.js';
|
|
4
|
+
import { sanitizePptxCss, sanitizePptxMarkup } from './sanitize.js';
|
|
5
|
+
import { ensurePptxViewerStyles } from './styles.js';
|
|
6
|
+
import { createPptxWorker } from './worker.js';
|
|
7
7
|
const clamp = (value, min, max) => {
|
|
8
8
|
return Math.min(max, Math.max(min, value));
|
|
9
9
|
};
|
|
@@ -97,6 +97,10 @@ export class PptxViewer {
|
|
|
97
97
|
this.previewThumbnailDataUrl = null;
|
|
98
98
|
this.slideSize = null;
|
|
99
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.
|
|
100
104
|
this.totalSlides = 0;
|
|
101
105
|
this.savedScroll = null;
|
|
102
106
|
this.pendingScrollRestore = null;
|
|
@@ -160,6 +164,10 @@ export class PptxViewer {
|
|
|
160
164
|
? root
|
|
161
165
|
: documentRef;
|
|
162
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
|
+
*/
|
|
163
171
|
get presentationRoot() {
|
|
164
172
|
const documentRef = this.target.ownerDocument || document;
|
|
165
173
|
const styleRoot = this.styleRoot;
|
|
@@ -168,6 +176,7 @@ export class PptxViewer {
|
|
|
168
176
|
}
|
|
169
177
|
return documentRef.body || documentRef.documentElement;
|
|
170
178
|
}
|
|
179
|
+
/** Force a slide out of the virtualized window so the slideshow can show it immediately. */
|
|
171
180
|
ensureSlideRendered(slideNumber) {
|
|
172
181
|
const record = this.slideRecords.find(item => item.slideNumber === slideNumber);
|
|
173
182
|
if (!record) {
|
|
@@ -181,6 +190,11 @@ export class PptxViewer {
|
|
|
181
190
|
refreshLayout() {
|
|
182
191
|
this.scheduleResize();
|
|
183
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
|
+
*/
|
|
184
198
|
saveScrollPosition() {
|
|
185
199
|
const view = this.target.ownerDocument.defaultView || window;
|
|
186
200
|
const HTMLElementCtor = view.HTMLElement;
|
|
@@ -522,6 +536,9 @@ export class PptxViewer {
|
|
|
522
536
|
}
|
|
523
537
|
const windowOptions = this.getSlideWindowOptions();
|
|
524
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.
|
|
525
542
|
const presentation = this.presentation;
|
|
526
543
|
if (presentation === null || presentation === void 0 ? void 0 : presentation.active) {
|
|
527
544
|
const activeIndex = this.slideRecords.findIndex(record => record.slideNumber === presentation.slideNumber);
|
|
@@ -694,6 +711,7 @@ export class PptxViewer {
|
|
|
694
711
|
}
|
|
695
712
|
await record.postProcessPromise;
|
|
696
713
|
}
|
|
714
|
+
/** Materializes every lazy slide only for the duration of an export snapshot. */
|
|
697
715
|
async cloneForExport() {
|
|
698
716
|
if (!this.shouldWindowSlides()) {
|
|
699
717
|
return this.target.cloneNode(true);
|
|
@@ -860,6 +878,7 @@ export class PptxViewer {
|
|
|
860
878
|
}
|
|
861
879
|
resize() {
|
|
862
880
|
var _a, _b, _c;
|
|
881
|
+
// While presenting, the slideshow owns the transform of the same nodes.
|
|
863
882
|
if ((_a = this.presentation) === null || _a === void 0 ? void 0 : _a.active) {
|
|
864
883
|
this.presentation.layout();
|
|
865
884
|
return;
|
|
@@ -882,6 +901,8 @@ export class PptxViewer {
|
|
|
882
901
|
this.scaleBox.style.width = `${Math.ceil(slideWidth * effectiveScale)}px`;
|
|
883
902
|
this.scaleBox.style.height = `${Math.ceil(this.content.scrollHeight * effectiveScale)}px`;
|
|
884
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.
|
|
885
906
|
if (this.pendingScrollRestore) {
|
|
886
907
|
const { top, left } = this.pendingScrollRestore;
|
|
887
908
|
this.pendingScrollRestore = null;
|