@file-viewer/pptx 3.1.1 → 3.2.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/README.en.md CHANGED
@@ -1,34 +1,7 @@
1
1
  # @file-viewer/pptx
2
2
 
3
- Native PPTX rendering engine extracted from Flyfish File Viewer. It is maintained as a standalone package so presentation rendering can evolve independently from framework adapters.
3
+ Runtime package version 3.2.0.
4
4
 
5
- ## Highlights
5
+ Usage and API documentation: https://doc.file-viewer.app/
6
6
 
7
- - Pure TypeScript/DOM API with no Vue, React, or iframe dependency.
8
- - Web Worker based PPTX parsing with progressive slide output.
9
- - Preserves the latest native Flyfish rendering path for themes, images, shapes, tables, text, and basic charts.
10
- - Supports `fitMode`, `zoomPercent`, custom Worker URLs, and Worker factories for private deployment and strict CSP environments.
11
-
12
- `resolvePptxPackageWorkerUrl()` lets integrations identify an emitted or directly served package Worker URL. It returns `undefined` when the location is not recognized, not as proof that a file is missing; integrations can then use their asset-copy strategy or explicit Worker configuration.
13
-
14
- When `PptxViewer` is used directly, Vite or Webpack can serve a recognized package Worker; an unrecognized build never guesses an application-relative path. Angular, IIFE, offline static directories, and strict CSP deployments should use `file-viewer-copy-assets` or an explicit `workerUrl` so the final static origin serves the Worker reliably.
15
-
16
- ## Usage
17
-
18
- ```ts
19
- import { PptxViewer } from '@file-viewer/pptx'
20
-
21
- const buffer = await file.arrayBuffer()
22
- const viewer = await PptxViewer.open(buffer, document.querySelector('#app')!, {
23
- fitMode: 'contain',
24
- zoomPercent: 100,
25
- onSlideRendered(index) {
26
- console.log('slide rendered', index)
27
- },
28
- })
29
-
30
- await viewer.setZoom(125)
31
- viewer.destroy()
32
- ```
33
-
34
- Most users should consume PPTX through `@file-viewer/core` or a standard framework package. The core package lazy-loads this renderer only when a presentation is opened.
7
+ The package's own license is Apache-2.0. Included third-party notices apply to their respective components.
package/README.md CHANGED
@@ -1,34 +1,7 @@
1
1
  # @file-viewer/pptx
2
2
 
3
- Flyfish File Viewer 的原生 PPTX 渲染引擎包,从历史稳定实现中独立拆分,便于持续维护和单独发布。
3
+ Runtime package version 3.2.0.
4
4
 
5
- ## 特性
5
+ Usage and API documentation: https://doc.file-viewer.app/
6
6
 
7
- - 纯 TypeScript/DOM 入口,不依赖 Vue、React 或 iframe。
8
- - 使用 Web Worker 解析 PPTX,按页渐进输出,避免阻塞主线程。
9
- - 保留历史原生解析链路中的主题、图片、形状、表格、文本和基础图表处理。
10
- - 支持 `fitMode`、`zoomPercent`、自定义 Worker URL / Worker factory,方便内网、离线和 CSP 场景。
11
-
12
- `resolvePptxPackageWorkerUrl()` 可供集成层识别打包器已输出或包目录直接提供的 Worker URL。未识别时返回 `undefined`,不代表文件不存在;集成层仍可使用自己的资产复制策略或显式 Worker 配置。
13
-
14
- 直接使用 `PptxViewer` 时,Vite 或 Webpack 可直接提供已识别的包内 Worker;无法识别的构建不会猜测应用相对路径。Angular、IIFE、离线静态目录或严格 CSP 部署应使用 `file-viewer-copy-assets` 或显式 `workerUrl`,让 Worker 由最终静态域名稳定提供。
15
-
16
- ## 使用
17
-
18
- ```ts
19
- import { PptxViewer } from '@file-viewer/pptx'
20
-
21
- const buffer = await file.arrayBuffer()
22
- const viewer = await PptxViewer.open(buffer, document.querySelector('#app')!, {
23
- fitMode: 'contain',
24
- zoomPercent: 100,
25
- onSlideRendered(index) {
26
- console.log('slide rendered', index)
27
- },
28
- })
29
-
30
- await viewer.setZoom(125)
31
- viewer.destroy()
32
- ```
33
-
34
- 在 Flyfish File Viewer 中通常无需直接使用本包,`@file-viewer/core` 会按 PPTX 类型自动按需加载。
7
+ The package's own license is Apache-2.0. Included third-party notices apply to their respective components.
package/dist/chart.d.ts CHANGED
@@ -8,4 +8,5 @@ export type PptxPostProcessingHandle = {
8
8
  destroy: () => void;
9
9
  };
10
10
  export declare const findPptxChartTarget: (root: ParentNode, chartID: string) => HTMLElement | null;
11
+ export declare const getNumericBulletText: (type: string, index: number) => string;
11
12
  export declare const renderPptxPostProcessing: (charts: unknown, root: ParentNode) => Promise<PptxPostProcessingHandle>;
package/dist/chart.js CHANGED
@@ -13,22 +13,69 @@ export const findPptxChartTarget = (root, chartID) => {
13
13
  return Array.from(root.querySelectorAll('[id]'))
14
14
  .find(element => element.id === chartID) || null;
15
15
  };
16
- const getNumericBulletText = (type, index) => {
17
- switch (type) {
18
- case 'arabicPeriod':
19
- return `${index}. `;
20
- case 'arabicParenR':
21
- return `${index}) `;
22
- case 'alphaLcParenR':
23
- return `${String.fromCharCode(index + 96)}) `;
24
- case 'alphaLcPeriod':
25
- return `${String.fromCharCode(index + 96)}. `;
26
- case 'alphaUcParenR':
27
- return `${String.fromCharCode(index + 64)}) `;
28
- case 'alphaUcPeriod':
29
- return `${String.fromCharCode(index + 64)}. `;
30
- default:
31
- return String(index);
16
+ const formatEastAsianNumber = (index) => {
17
+ const digits = '零一二三四五六七八九';
18
+ const units = [[1000, '千'], [100, '百'], [10, '十'], [1, '']];
19
+ const group = (value, omitLeadingOne = true) => {
20
+ let label = '';
21
+ let gap = false;
22
+ for (const [place, unit] of units) {
23
+ const digit = Math.floor(value / place);
24
+ value %= place;
25
+ if (digit) {
26
+ if (gap)
27
+ label += digits[0];
28
+ label += (place === 10 && digit === 1 && !label && omitLeadingOne ? '' : digits[digit]) + unit;
29
+ gap = false;
30
+ }
31
+ else if (label && value)
32
+ gap = true;
33
+ }
34
+ return label;
35
+ };
36
+ if (index < 10000)
37
+ return group(index);
38
+ const remainder = index % 10000;
39
+ return digits[Math.floor(index / 10000)] + '万' +
40
+ (remainder ? (remainder < 1000 ? digits[0] : '') + group(remainder, false) : '');
41
+ };
42
+ export const getNumericBulletText = (type, index) => {
43
+ if (!Number.isInteger(index) || index < 1 || index > 32767) {
44
+ return String(index);
45
+ }
46
+ if (type === 'ea1JpnChsDbPeriod') {
47
+ return `${formatEastAsianNumber(index)}. `;
48
+ }
49
+ const match = /^(arabic|alphaLc|alphaUc|romanLc|romanUc)(Period|ParenR|ParenBoth|Plain)$/.exec(type);
50
+ if (!match) {
51
+ return String(index);
52
+ }
53
+ let label = String(index);
54
+ if (match[1].startsWith('alpha')) {
55
+ label = '';
56
+ for (let remaining = index; remaining > 0; remaining = Math.floor((remaining - 1) / 26)) {
57
+ label = String.fromCharCode(65 + (remaining - 1) % 26) + label;
58
+ }
59
+ }
60
+ else if (match[1].startsWith('roman')) {
61
+ label = '';
62
+ let remaining = index;
63
+ const digits = [
64
+ [1000, 'M'], [900, 'CM'], [500, 'D'], [400, 'CD'], [100, 'C'], [90, 'XC'],
65
+ [50, 'L'], [40, 'XL'], [10, 'X'], [9, 'IX'], [5, 'V'], [4, 'IV'], [1, 'I'],
66
+ ];
67
+ for (const [value, symbol] of digits) {
68
+ label += symbol.repeat(Math.floor(remaining / value));
69
+ remaining %= value;
70
+ }
71
+ }
72
+ if (match[1].endsWith('Lc'))
73
+ label = label.toLowerCase();
74
+ switch (match[2]) {
75
+ case 'Period': return `${label}. `;
76
+ case 'ParenR': return `${label}) `;
77
+ case 'ParenBoth': return `(${label}) `;
78
+ default: return label;
32
79
  }
33
80
  };
34
81
  const restoreNumericBullets = (root) => {
@@ -43,7 +90,10 @@ const restoreNumericBullets = (root) => {
43
90
  const type = String(bullet.dataset.bulltname || 'arabicPeriod');
44
91
  const level = String(bullet.dataset.bulltlvl || '0');
45
92
  const key = `${level}:${type}`;
46
- const nextIndex = (counters.get(key) || 0) + 1;
93
+ const startAt = Number(bullet.dataset.bulltstartat);
94
+ const nextIndex = Number.isInteger(startAt) && startAt >= 1 && startAt <= 32767
95
+ ? startAt
96
+ : (counters.get(key) || 0) + 1;
47
97
  counters.set(key, nextIndex);
48
98
  bullet.textContent = getNumericBulletText(type, nextIndex);
49
99
  }
@@ -120,6 +170,11 @@ const applyTextFitScale = (block, scale) => {
120
170
  const hasTextOverflow = (block, fitWidth = true) => block.scrollHeight > block.clientHeight + TEXT_FIT_TOLERANCE ||
121
171
  (fitWidth && block.scrollWidth > block.clientWidth + TEXT_FIT_TOLERANCE);
122
172
  const fitOverflowingTextBlock = (block, fitWidth = true) => {
173
+ const mode = block.dataset.pptxAutofit;
174
+ if (mode && mode !== 'normal')
175
+ return;
176
+ if (block.dataset.pptxWrap === 'none')
177
+ fitWidth = false;
123
178
  if (!block.querySelector('.text-block') || block.clientWidth <= 0 || block.clientHeight <= 0) {
124
179
  return;
125
180
  }
@@ -157,7 +212,7 @@ const fitOverflowingTextBlocks = (root) => {
157
212
  .forEach(block => fitOverflowingTextBlock(block, false));
158
213
  };
159
214
  const renderChart = async (message, root) => {
160
- var _a;
215
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
161
216
  const payload = message.data;
162
217
  if (!(payload === null || payload === void 0 ? void 0 : payload.chartID) || !payload.chartType || !payload.chartData) {
163
218
  return;
@@ -174,10 +229,11 @@ const renderChart = async (message, root) => {
174
229
  }
175
230
  const { billboard, d3Format } = await chartLibraryLoader();
176
231
  const bb = billboard.default || billboard;
177
- const { area, bar, line, pie, scatter } = billboard;
232
+ const { area, bar, line, pie, donut, scatter } = billboard;
233
+ if (typeof billboard.grid === 'function') {
234
+ billboard.grid();
235
+ }
178
236
  const chart = {
179
- // A selector makes Billboard query the main document. That misses chart
180
- // placeholders inside the viewer Shadow DOM and makes it fall back to body.
181
237
  bindto: chartTarget,
182
238
  };
183
239
  const chartData = payload.chartData;
@@ -192,6 +248,58 @@ const renderChart = async (message, root) => {
192
248
  },
193
249
  };
194
250
  switch (payload.chartType) {
251
+ case 'doughnutChart': {
252
+ const series = chartData[0];
253
+ const columns = [];
254
+ const names = {};
255
+ const colors = {};
256
+ for (const [index, point] of ((series === null || series === void 0 ? void 0 : series.values) || []).entries()) {
257
+ if (typeof point.y !== 'number' || !Number.isFinite(point.y) || point.y === 0)
258
+ continue;
259
+ const id = `point-${index}`;
260
+ columns.push([id, Math.abs(point.y)]);
261
+ names[id] = String((_c = (_b = (_a = series.xlabels) === null || _a === void 0 ? void 0 : _a[index]) !== null && _b !== void 0 ? _b : point.x) !== null && _c !== void 0 ? _c : index);
262
+ const fill = (_h = (_g = (_f = (_e = (_d = payload.chartOptions) === null || _d === void 0 ? void 0 : _d.series) === null || _e === void 0 ? void 0 : _e[0]) === null || _f === void 0 ? void 0 : _f.points) === null || _g === void 0 ? void 0 : _g[point.x]) !== null && _h !== void 0 ? _h : (_l = (_k = (_j = payload.chartOptions) === null || _j === void 0 ? void 0 : _j.series) === null || _k === void 0 ? void 0 : _k[0]) === null || _l === void 0 ? void 0 : _l.color;
263
+ if (fill && /^(?:#[\da-f]{6}(?:[\da-f]{2})?|transparent)$/i.test(fill))
264
+ colors[id] = fill;
265
+ }
266
+ const hole = (_m = payload.chartOptions) === null || _m === void 0 ? void 0 : _m.holeSize;
267
+ const holeRatio = (typeof hole === 'number' && Number.isFinite(hole) && hole >= 10 && hole <= 90 ? hole : 50) / 100;
268
+ const angle = (_o = payload.chartOptions) === null || _o === void 0 ? void 0 : _o.firstSliceAngle;
269
+ let adjustingWidth = false;
270
+ Object.assign(chart, {
271
+ data: { columns, names, colors, type: donut(), order: null },
272
+ donut: {
273
+ startingAngle: typeof angle === 'number' && Number.isFinite(angle) ? angle * Math.PI / 180 : 0,
274
+ expand: false,
275
+ label: { show: false },
276
+ },
277
+ transition: { duration: 0 },
278
+ onrendered() {
279
+ if (adjustingWidth || typeof this.config !== 'function')
280
+ return;
281
+ let radius = 0;
282
+ for (const arc of chartTarget.querySelectorAll('.bb-arc')) {
283
+ const match = /[Aa]([\d.+eE-]+)[ ,]+([\d.+eE-]+)/.exec(arc.getAttribute('d') || '');
284
+ if (match)
285
+ radius = Math.max(radius, Number(match[1]));
286
+ }
287
+ if (!(radius > 0) || !Number.isFinite(radius))
288
+ return;
289
+ const width = radius * (1 - holeRatio);
290
+ if (Math.abs(Number(this.config('donut.width')) - width) <= 0.01)
291
+ return;
292
+ adjustingWidth = true;
293
+ try {
294
+ this.config('donut.width', width, true);
295
+ }
296
+ finally {
297
+ adjustingWidth = false;
298
+ }
299
+ },
300
+ });
301
+ break;
302
+ }
195
303
  case 'lineChart':
196
304
  Object.assign(chart, {
197
305
  data: {
@@ -226,7 +334,7 @@ const renderChart = async (message, root) => {
226
334
  case 'pie3DChart':
227
335
  Object.assign(chart, {
228
336
  data: {
229
- columns: Object.values(((_a = chartData[0]) === null || _a === void 0 ? void 0 : _a.xlabels) || {}).map((value, index) => {
337
+ columns: Object.values(((_p = chartData[0]) === null || _p === void 0 ? void 0 : _p.xlabels) || {}).map((value, index) => {
230
338
  var _a, _b, _c;
231
339
  return [
232
340
  value,
@@ -276,7 +384,70 @@ const renderChart = async (message, root) => {
276
384
  return;
277
385
  }
278
386
  if (chart.data) {
279
- return bb.generate(chart);
387
+ const options = payload.chartOptions;
388
+ if (options === null || options === void 0 ? void 0 : options.legend) {
389
+ chart.legend = {
390
+ show: options.legend.show !== false,
391
+ position: options.legend.position === 'b' ? 'bottom' : 'right',
392
+ format: (label) => label === '' ? '\u200b' : label,
393
+ };
394
+ }
395
+ if (['barChart', 'areaChart', 'lineChart'].includes(payload.chartType)) {
396
+ const used = new Set();
397
+ const reserved = new Set(chart.data.columns.map((column) => String(column[0])));
398
+ const names = {};
399
+ chart.data.columns.forEach((column, index) => {
400
+ const label = String(column[0]);
401
+ let id = label;
402
+ if (used.has(id)) {
403
+ id = `pptx-series-${index}`;
404
+ while (reserved.has(id) || used.has(id))
405
+ id += '-';
406
+ column[0] = id;
407
+ }
408
+ used.add(id);
409
+ Object.defineProperty(names, id, { value: label, enumerable: true });
410
+ });
411
+ chart.data.names = names;
412
+ if ((options === null || options === void 0 ? void 0 : options.grouping) === 'stacked' || (options === null || options === void 0 ? void 0 : options.grouping) === 'percentStacked') {
413
+ chart.data.groups = [chart.data.columns.map((column) => column[0])];
414
+ if (options.grouping === 'percentStacked')
415
+ chart.data.stack = { normalize: true };
416
+ chart.data.order = null;
417
+ }
418
+ const colors = {};
419
+ chartData.forEach((item, index) => {
420
+ var _a, _b;
421
+ const fill = (_b = (_a = options === null || options === void 0 ? void 0 : options.series) === null || _a === void 0 ? void 0 : _a[index]) === null || _b === void 0 ? void 0 : _b.color;
422
+ if (fill && /^(?:#[\da-f]{6}(?:[\da-f]{2})?|transparent)$/i.test(fill))
423
+ Object.defineProperty(colors, String(chart.data.columns[index][0]), { value: fill, enumerable: true });
424
+ });
425
+ if (Object.keys(colors).length)
426
+ chart.data.colors = colors;
427
+ }
428
+ const surface = chartTarget.ownerDocument.createElement('div');
429
+ surface.className = 'pptx-chart-surface';
430
+ surface.style.cssText = 'width:100%;height:100%;min-width:0;min-height:0;flex:1 1 auto';
431
+ chartTarget.replaceChildren(surface);
432
+ chart.bindto = surface;
433
+ try {
434
+ const instance = bb.generate(chart);
435
+ return {
436
+ destroy() {
437
+ var _a;
438
+ try {
439
+ (_a = instance.destroy) === null || _a === void 0 ? void 0 : _a.call(instance);
440
+ }
441
+ finally {
442
+ surface.remove();
443
+ }
444
+ },
445
+ };
446
+ }
447
+ catch (error) {
448
+ surface.remove();
449
+ throw error;
450
+ }
280
451
  }
281
452
  };
282
453
  export const renderPptxPostProcessing = async (charts, root) => {
@@ -310,7 +481,6 @@ export const renderPptxPostProcessing = async (charts, root) => {
310
481
  (_a = chart.destroy) === null || _a === void 0 ? void 0 : _a.call(chart);
311
482
  }
312
483
  catch {
313
- // The DOM may already be detached during framework unmount.
314
484
  }
315
485
  }
316
486
  chartInstances.length = 0;
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- export { PptxViewer } from './viewer';
2
- export { PptxPresentation } from './presentation';
3
- export type { PptxPresentationLabels, PptxPresentationState } from './presentation';
4
- export { createPptxWorker, resolvePptxPackageWorkerUrl } from './worker';
5
- export { registerPptxChartLibraryLoader } from './chart';
6
- export type { PptxChartLibraries, PptxChartLibraryLoader } from './chart';
7
- export { RECOMMENDED_ZIP_LIMITS, createDefaultPptxOptions, resolvePptxEngineOptions, } from './options';
8
- export type { NativePptxEngineOptions, PptxDiagnosticError, PptxDiagnosticErrorCode, PptxFitMode, PptxListOptions, PptxSlideSize, PptxViewerOptions, PptxWorkerFactoryOptions, PptxWorkerMessage, PptxZipLimits, } from './types';
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 } from "./chart.js";
6
+ export type { PptxChartLibraries, PptxChartLibraryLoader } from "./chart.js";
7
+ export { RECOMMENDED_ZIP_LIMITS, createDefaultPptxOptions, resolvePptxEngineOptions, } from "./options.js";
8
+ 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 } 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 } 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';
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';
1
+ import type { PptxViewer } from "./viewer.js";
2
2
  export interface PptxPresentationState {
3
3
  active: boolean;
4
4
  slideNumber: number;
@@ -10,14 +10,6 @@ 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
- */
21
13
  export declare class PptxPresentation {
22
14
  private readonly viewer;
23
15
  private readonly labels;
@@ -40,21 +32,10 @@ export declare class PptxPresentation {
40
32
  goTo(slideNumber: number): void;
41
33
  next(): void;
42
34
  previous(): void;
43
- /** Scale the active slide to fit the overlay, letterboxing whichever axis has slack. */
44
35
  layout(): void;
45
36
  destroy(): void;
46
37
  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
- */
52
38
  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
- */
58
39
  private isFullscreenElement;
59
40
  private scheduleLayout;
60
41
  private notify;
@@ -1,12 +1,4 @@
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
- */
10
2
  export class PptxPresentation {
11
3
  constructor(viewer, labels = {}, fullscreen = true) {
12
4
  this.overlay = null;
@@ -62,11 +54,8 @@ export class PptxPresentation {
62
54
  this.overlay = overlay;
63
55
  this.stage = stage;
64
56
  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.
67
57
  const styleRoot = this.viewer.presentationRoot;
68
58
  styleRoot.appendChild(overlay);
69
- // Park a comment where the scale box lived so exit() can restore the exact position.
70
59
  this.placeholder = documentRef.createComment('flyfish-pptx-presentation');
71
60
  this.viewer.saveScrollPosition();
72
61
  this.viewer.scaleBox.replaceWith(this.placeholder);
@@ -80,13 +69,9 @@ export class PptxPresentation {
80
69
  this.ownsFullscreen = this.isFullscreenElement(overlay);
81
70
  }
82
71
  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.
85
72
  this.ownsFullscreen = false;
86
73
  }
87
74
  }
88
- // exit() may have run while fullscreen was being requested. Leave the
89
- // browser out of fullscreen rather than stranded with no overlay.
90
75
  if (this.overlay !== overlay) {
91
76
  if (this.ownsFullscreen) {
92
77
  void ((_b = documentRef.exitFullscreen) === null || _b === void 0 ? void 0 : _b.call(documentRef).catch(() => undefined));
@@ -105,8 +90,6 @@ export class PptxPresentation {
105
90
  return;
106
91
  }
107
92
  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.
110
93
  const shouldExitFullscreen = this.ownsFullscreen && this.isFullscreenElement(overlay);
111
94
  this.detach();
112
95
  if (this.layoutFrame) {
@@ -149,11 +132,8 @@ export class PptxPresentation {
149
132
  }
150
133
  this.current = clamp(Math.round(slideNumber) || 1, 1, total);
151
134
  this.viewer.ensureSlideRendered(this.current);
152
- // Rendering the neighbour keeps the next step instant without defeating virtualization.
153
135
  this.viewer.ensureSlideRendered(this.current + 1);
154
136
  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.
157
137
  const byNumber = containers.some(container => Boolean(container.dataset.slideNumber));
158
138
  containers.forEach((container, index) => {
159
139
  const isActive = byNumber
@@ -179,7 +159,6 @@ export class PptxPresentation {
179
159
  this.goTo(this.current - 1);
180
160
  }
181
161
  }
182
- /** Scale the active slide to fit the overlay, letterboxing whichever axis has slack. */
183
162
  layout() {
184
163
  const overlay = this.overlay;
185
164
  if (!overlay) {
@@ -191,7 +170,6 @@ export class PptxPresentation {
191
170
  }
192
171
  return index === this.current - 1;
193
172
  });
194
- // Windowed decks wrap the slide in a slot; non-windowed decks are the slide.
195
173
  const slide = container && container.classList.contains('flyfish-pptx-slide-slot')
196
174
  ? container.firstElementChild
197
175
  : container;
@@ -220,11 +198,6 @@ export class PptxPresentation {
220
198
  slots() {
221
199
  return Array.from(this.viewer.content.querySelectorAll(':scope > .flyfish-pptx-slide-slot'));
222
200
  }
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
- */
228
201
  slideContainers() {
229
202
  const slots = this.slots();
230
203
  if (slots.length > 0) {
@@ -232,11 +205,6 @@ export class PptxPresentation {
232
205
  }
233
206
  return Array.from(this.viewer.content.querySelectorAll(':scope > .slide'));
234
207
  }
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
- */
240
208
  isFullscreenElement(overlay) {
241
209
  const documentRef = this.viewer.target.ownerDocument || document;
242
210
  if (documentRef.fullscreenElement === overlay) {
@@ -275,8 +243,6 @@ export class PptxPresentation {
275
243
  });
276
244
  this.on(view, 'resize', () => this.scheduleLayout());
277
245
  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.
280
246
  if (this.ownsFullscreen && !this.isFullscreenElement(overlay)) {
281
247
  this.ownsFullscreen = false;
282
248
  this.exit();
@@ -295,7 +261,6 @@ export class PptxPresentation {
295
261
  if (!overlay || ((_a = event.target) === null || _a === void 0 ? void 0 : _a.closest('.flyfish-pptx-presentation-exit'))) {
296
262
  return;
297
263
  }
298
- // PowerPoint advances on click; the left edge is the only place that goes back.
299
264
  const bounds = overlay.getBoundingClientRect();
300
265
  const ratio = (event.clientX - bounds.left) / (bounds.width || 1);
301
266
  if (ratio < 0.2) {
@@ -309,16 +274,12 @@ export class PptxPresentation {
309
274
  if (!this.active || event.altKey || event.metaKey || event.ctrlKey) {
310
275
  return;
311
276
  }
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.
314
277
  const target = event.target;
315
278
  if (target &&
316
279
  typeof target.closest === 'function' &&
317
280
  target.closest('button, a, input, textarea, select, [contenteditable="true"], [contenteditable=""]')) {
318
281
  return;
319
282
  }
320
- // With two slideshows open, a key pressed inside the other overlay belongs
321
- // to that overlay; only the focused one advances.
322
283
  if (target && typeof target.closest === 'function') {
323
284
  const otherOverlay = target.closest('.flyfish-pptx-presentation');
324
285
  if (otherOverlay && otherOverlay !== this.overlay) {
package/dist/styles.d.ts CHANGED
@@ -1,8 +1,3 @@
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
- */
6
1
  export declare const scopePptxContentStyleText: (cssText: string) => string;
7
2
  export declare const pptxViewerCss: string;
8
3
  export declare const ensurePptxViewerStyles: (documentRef: Document, root?: Document | ShadowRoot) => void;
package/dist/styles.js CHANGED
@@ -32,13 +32,25 @@ const legacyPptxCss = `
32
32
  .slide table tr,.slide table td{box-sizing:border-box;min-width:0;overflow:hidden}
33
33
  .slide .pptx-table-cell-content{box-sizing:border-box;min-height:0;overflow:hidden}
34
34
  .slide svg.drawing{position:absolute;overflow:visible}
35
+ /* Chart shapes need renderer-owned styling even in offline / Shadow DOM hosts.
36
+ Without it SVG's default black fill closes line and axis paths into polygons. */
37
+ .slide .pptx-chart-surface svg{font:10px sans-serif;overflow:hidden}
38
+ .slide .pptx-chart-surface .bb-axis path,.slide .pptx-chart-surface .bb-axis line{fill:none;stroke:currentColor}
39
+ .slide .pptx-chart-surface .bb-line{fill:none;stroke-width:1px}
40
+ .slide .pptx-chart-surface .bb-bar{stroke-width:0}
41
+ .slide .pptx-chart-surface .bb-area{stroke-width:0;opacity:.2}
42
+ .slide .pptx-chart-surface .bb-chart-arc path{stroke:#fff}
43
+ .slide .pptx-chart-surface .bb-chart-arc text{fill:#fff;font-size:13px}
44
+ .slide .pptx-chart-surface .bb-grid line{fill:none;stroke:#aaa}
45
+ .slide .pptx-chart-surface .bb-xgrid,.slide .pptx-chart-surface .bb-ygrid{stroke-dasharray:3 3}
46
+ .slide .pptx-chart-surface .bb-xgrid-focus,.slide .pptx-chart-surface .bb-ygrid-focus{pointer-events:none}
47
+ .slide .pptx-chart-surface .bb-legend-item{font-size:12px}
48
+ .slide .pptx-chart-surface .bb-tooltip-container{z-index:10;pointer-events:none}
49
+ .slide .pptx-chart-surface table.bb-tooltip{position:static;width:auto;table-layout:auto;background:#fff;color:#172033;border:1px solid #ccc;opacity:.95;font-size:12px}
50
+ .slide .pptx-chart-surface .bb-tooltip td,.slide .pptx-chart-surface .bb-tooltip th{padding:4px 6px}
51
+
35
52
  `;
36
53
  const PPTX_CONTENT_STYLE_SCOPE = '.flyfish-pptx-content';
37
- /**
38
- * The legacy engine emits class-only selector lists. Prefix each top-level
39
- * class rule so light-DOM consumers do not inherit generic `.slide` or
40
- * generated `._css_*` rules from a renderer-local style element.
41
- */
42
54
  export const scopePptxContentStyleText = (cssText) => cssText.replace(/(^|})\s*(\.[^{}]+)\{/g, (_match, boundary, selectorList) => {
43
55
  const scopedSelectors = selectorList
44
56
  .split(',')
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { PptxPresentationLabels, PptxPresentationState } from './presentation';
1
+ import type { PptxPresentationLabels, PptxPresentationState } from "./presentation.js";
2
2
  export type PptxFitMode = 'contain' | 'none';
3
3
  export interface PptxZipLimits {
4
4
  maxFileBytes?: number;