@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/dist/chart.js CHANGED
@@ -1,6 +1,9 @@
1
- import { createPptxCartesianChart } from "./chart-config.js";
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 "./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, 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";
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 "./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";
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 "./types.js";
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;
@@ -1,4 +1,4 @@
1
- import type { PptxViewer } from "./viewer.js";
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;
@@ -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 "./presentation.js";
2
- import type { PptxChartCollection } from "./chart-types.js";
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 "./presentation.js";
2
- import type { PptxViewerOptions } from "./types.js";
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 "./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";
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;