返回 oh-my-ppt
ppt-runtime.js
根目录 / resources / ppt-runtime.js
1 (function initPptRuntime(global) {
2 if (!global || typeof global !== "object") return;
3 // @ohmyppt-ppt-runtime:arcsin1:v2.0.21
4
5 var ppt = global.PPT && typeof global.PPT === "object" ? global.PPT : (global.PPT = {});
6 if (ppt.__runtimeVersion === "2.0.21") return;
7 ppt.__runtimeVersion = "2.0.21";
8
9 function resolveSearchParams() {
10 try {
11 return new URLSearchParams(global.location ? global.location.search : "");
12 } catch (_err) {
13 return new URLSearchParams();
14 }
15 }
16
17 var search = resolveSearchParams();
18 var isPrintMode = search.get("print") === "1";
19 var expectsMasterElements = search.get("_pptMasterElementsExpected") === "1";
20 var clickAnimationsEnabled = search.get("pptPlayback") === "1";
21 if (!clickAnimationsEnabled) {
22 global.__ohmypptPlaybackBridgeInstallToken =
23 (global.__ohmypptPlaybackBridgeInstallToken || 0) + 1;
24 global.__ohmypptPlaybackBridgeInstalled = false;
25 }
26 var printTimeoutMs = Math.max(1000, Number(search.get("printTimeoutMs")) || 40000);
27 var printReadyEmitted = false;
28 var pendingPrintTasks = [];
29 var masterElementsTask = Promise.resolve();
30
31 function trackPrintTask(promise) {
32 if (!promise || typeof promise.then !== "function") return promise;
33 var wrapped = Promise.resolve(promise).catch(function () { return null; });
34 pendingPrintTasks.push(wrapped);
35 wrapped.finally(function () {
36 var idx = pendingPrintTasks.indexOf(wrapped);
37 if (idx >= 0) pendingPrintTasks.splice(idx, 1);
38 });
39 return wrapped;
40 }
41
42 function waitFrames(frames) {
43 var count = Math.max(1, Number(frames) || 1);
44 return new Promise(function (resolve) {
45 function next(remaining) {
46 if (remaining <= 0) {
47 resolve();
48 return;
49 }
50 if (typeof global.requestAnimationFrame === "function") {
51 global.requestAnimationFrame(function () {
52 next(remaining - 1);
53 });
54 return;
55 }
56 setTimeout(function () {
57 next(remaining - 1);
58 }, 16);
59 }
60 next(count);
61 });
62 }
63
64 function resolvePageIdForPrint() {
65 var explicit = search.get("pageId");
66 if (explicit && explicit.trim()) return explicit.trim();
67 try {
68 var bodyId = global.document && document.body ? document.body.getAttribute("data-page-id") : "";
69 if (bodyId && bodyId.trim()) return bodyId.trim();
70 var pathname = global.location ? String(global.location.pathname || "") : "";
71 var match = pathname.match(/(page-\d+)\.html?$/i);
72 if (match && match[1]) return match[1];
73 } catch (_err) {}
74 return "page-unknown";
75 }
76
77 function emitPrintReadyOnce() {
78 if (!isPrintMode || printReadyEmitted) return;
79 printReadyEmitted = true;
80 var pageId = resolvePageIdForPrint();
81 try {
82 console.log("__PPT_PRINT_READY__dfaarcsin1_:" + pageId);
83 } catch (_err) {}
84 }
85
86 function parseStyleNumber(value, withPx) {
87 if (typeof value === "number") {
88 return withPx ? String(value) + "px" : String(value);
89 }
90 return String(value);
91 }
92
93 function toKebabCase(input) {
94 return String(input).replace(/[A-Z]/g, function (m) { return "-" + m.toLowerCase(); });
95 }
96
97 function resolveFinalValue(rawValue, el, index, total) {
98 var value = rawValue;
99 if (typeof value === "function") {
100 try {
101 value = value(el, index, total);
102 } catch (_err) {
103 value = null;
104 }
105 }
106 if (Array.isArray(value) && value.length > 0) {
107 value = value[value.length - 1];
108 }
109 if (value && typeof value === "object") {
110 if (Object.prototype.hasOwnProperty.call(value, "to")) value = value.to;
111 else if (Object.prototype.hasOwnProperty.call(value, "value")) value = value.value;
112 else if (Object.prototype.hasOwnProperty.call(value, "end")) value = value.end;
113 }
114 return value;
115 }
116
117 function normalizeTargets(rawTargets) {
118 if (!rawTargets) return [];
119 if (typeof rawTargets === "string") {
120 return global.document ? Array.prototype.slice.call(document.querySelectorAll(rawTargets)) : [];
121 }
122 if (rawTargets instanceof Element) return [rawTargets];
123 if (rawTargets && typeof rawTargets.length === "number") {
124 return Array.prototype.slice.call(rawTargets).filter(function (item) {
125 return item instanceof Element;
126 });
127 }
128 return [];
129 }
130
131 function applyPrintAnimationEndState(rawTargets, params) {
132 var targets = normalizeTargets(rawTargets);
133 if (!targets.length || !params || typeof params !== "object") return;
134 // Mark animated targets so the PPTX export can keep them in the background screenshot
135 targets.forEach(function (el) {
136 if (el && el.nodeType === 1) el.setAttribute("data-pptx-animated", "1");
137 });
138 var transformKeys = {
139 x: true,
140 y: true,
141 translateX: true,
142 translateY: true,
143 translateZ: true,
144 scale: true,
145 scaleX: true,
146 scaleY: true,
147 rotate: true,
148 rotateX: true,
149 rotateY: true,
150 rotateZ: true,
151 skewX: true,
152 skewY: true,
153 };
154 var skipKeys = {
155 targets: true,
156 delay: true,
157 duration: true,
158 easing: true,
159 autoplay: true,
160 loop: true,
161 complete: true,
162 begin: true,
163 update: true,
164 keyframes: true,
165 direction: true,
166 alternate: true,
167 reversed: true,
168 };
169
170 targets.forEach(function (el, index) {
171 var transformParts = [];
172 Object.keys(params).forEach(function (key) {
173 if (skipKeys[key]) return;
174 var finalValue = resolveFinalValue(params[key], el, index, targets.length);
175 if (finalValue === undefined || finalValue === null) return;
176
177 if (key === "opacity") {
178 el.style.opacity = String(finalValue);
179 return;
180 }
181 if (key === "x") {
182 transformParts.push("translateX(" + parseStyleNumber(finalValue, true) + ")");
183 return;
184 }
185 if (key === "y") {
186 transformParts.push("translateY(" + parseStyleNumber(finalValue, true) + ")");
187 return;
188 }
189 if (transformKeys[key]) {
190 if (key.indexOf("scale") === 0) {
191 transformParts.push(key + "(" + String(finalValue) + ")");
192 } else if (key.indexOf("rotate") === 0 || key.indexOf("skew") === 0) {
193 var rotateValue = typeof finalValue === "number" ? String(finalValue) + "deg" : String(finalValue);
194 transformParts.push(key + "(" + rotateValue + ")");
195 } else {
196 transformParts.push(key + "(" + parseStyleNumber(finalValue, true) + ")");
197 }
198 return;
199 }
200
201 var unitless = key === "zIndex" || key === "fontWeight" || key === "lineHeight" || key === "order";
202 if (key in el.style) {
203 el.style[key] = unitless ? String(finalValue) : parseStyleNumber(finalValue, typeof finalValue === "number");
204 return;
205 }
206 el.style.setProperty(toKebabCase(key), unitless ? String(finalValue) : parseStyleNumber(finalValue, typeof finalValue === "number"));
207 });
208
209 if (transformParts.length > 0) {
210 el.style.transform = transformParts.join(" ");
211 }
212 });
213 }
214
215 function buildStagger(step, options) {
216 var start = Number((options && options.start) || 0);
217 var gap = Number(step || 0);
218 return function (_el, i) {
219 return start + i * gap;
220 };
221 }
222
223 function buildTimeline(animeApi) {
224 return function (_options) {
225 return {
226 add: function (params) {
227 if (!params || typeof params !== "object") return this;
228 var run = animeApi && typeof animeApi.animate === "function"
229 ? animeApi.animate.bind(animeApi)
230 : (typeof animeApi === "function" ? animeApi : null);
231 if (run) run(params);
232 return this;
233 },
234 };
235 };
236 }
237
238 function resolveAnime() {
239 return global.anime;
240 }
241
242 function getChartRegistry() {
243 if (!(global.__PPT_CHART_REGISTRY__ instanceof Map)) {
244 global.__PPT_CHART_REGISTRY__ = new Map();
245 }
246 return global.__PPT_CHART_REGISTRY__;
247 }
248
249 function resolveChartTarget(target) {
250 var resolved = target;
251 if (typeof target === "string") {
252 resolved = global.document ? (document.querySelector(target) || document.getElementById(target)) : null;
253 }
254 if (resolved && resolved.canvas) {
255 resolved = resolved.canvas;
256 }
257 if (resolved && typeof resolved.getContext === "function") {
258 return { canvas: resolved, chartTarget: resolved };
259 }
260 if (resolved && typeof resolved.querySelector === "function") {
261 var nestedCanvas = resolved.querySelector("canvas");
262 if (nestedCanvas && typeof nestedCanvas.getContext === "function") {
263 return { canvas: nestedCanvas, chartTarget: nestedCanvas };
264 }
265 }
266 return { canvas: resolved || null, chartTarget: resolved || null };
267 }
268
269 function withPrintChartConfig(config) {
270 if (!config || typeof config !== "object") return config;
271 var safe = Object.assign({}, config);
272 normalizeChartConfig(safe);
273 if (!isPrintMode) return safe;
274 var options = Object.assign({}, (safe.options && typeof safe.options === "object") ? safe.options : {});
275 options.animation = false;
276 options.responsive = false;
277 options.maintainAspectRatio = false;
278 safe.options = options;
279 ensureChartNumberFormatters(safe);
280 return safe;
281 }
282
283 function trimFloatingNoise(value, maxDecimals) {
284 if (typeof value !== "number" || !Number.isFinite(value)) return value;
285 var decimals = Math.max(0, Math.min(12, Number(maxDecimals) || 6));
286 var factor = Math.pow(10, decimals);
287 var rounded = Math.round((value + Number.EPSILON) * factor) / factor;
288 return Object.is(rounded, -0) ? 0 : rounded;
289 }
290
291 function formatChartNumber(value, maxDecimals) {
292 if (typeof value !== "number" || !Number.isFinite(value)) return String(value == null ? "" : value);
293 return String(trimFloatingNoise(value, maxDecimals));
294 }
295
296 function normalizeChartScalar(value) {
297 if (typeof value === "number") return trimFloatingNoise(value, 6);
298 if (typeof value === "string" && /^-?\d+\.\d{7,}$/.test(value.trim())) {
299 var parsed = Number(value);
300 if (Number.isFinite(parsed)) return formatChartNumber(parsed, 6);
301 }
302 return value;
303 }
304
305 function normalizeChartData(value) {
306 if (Array.isArray(value)) return value.map(normalizeChartData);
307 if (value && typeof value === "object") {
308 Object.keys(value).forEach(function (key) {
309 value[key] = normalizeChartData(value[key]);
310 });
311 return value;
312 }
313 return normalizeChartScalar(value);
314 }
315
316 function isLikelyCategoryScale(config, scaleKey, scale) {
317 if (scale && scale.type === "category") return true;
318 if (scale && scale.type) return false;
319 var data = config && config.data && typeof config.data === "object" ? config.data : null;
320 if (!data || !Array.isArray(data.labels)) return false;
321 var options = config && config.options && typeof config.options === "object" ? config.options : null;
322 var defaultCategoryAxis = options && options.indexAxis === "y" ? "y" : "x";
323 return scaleKey === defaultCategoryAxis;
324 }
325
326 function resolveChartTooltipValue(context) {
327 if (!context) return "";
328 var chartOptions = context.chart && context.chart.options && typeof context.chart.options === "object"
329 ? context.chart.options
330 : null;
331 var indexAxis = chartOptions && chartOptions.indexAxis === "y" ? "y" : "x";
332 var parsed = context.parsed && typeof context.parsed === "object" ? context.parsed : null;
333 if (parsed) {
334 var valueAxis = indexAxis === "y" ? "x" : "y";
335 if (parsed[valueAxis] !== undefined) return parsed[valueAxis];
336 if (parsed.y !== undefined) return parsed.y;
337 if (parsed.x !== undefined) return parsed.x;
338 }
339 if (context.formattedValue !== undefined) return context.formattedValue;
340 return context.raw;
341 }
342
343 function ensureChartNumberFormatters(config) {
344 if (!config || typeof config !== "object") return;
345 var options = config.options && typeof config.options === "object" ? config.options : (config.options = {});
346 var scales = options.scales && typeof options.scales === "object" ? options.scales : null;
347 if (scales) {
348 Object.keys(scales).forEach(function (scaleKey) {
349 var scale = scales[scaleKey];
350 if (!scale || typeof scale !== "object") return;
351 var ticks = scale.ticks && typeof scale.ticks === "object" ? scale.ticks : (scale.ticks = {});
352 if (typeof ticks.callback !== "function") {
353 if (isLikelyCategoryScale(config, scaleKey, scale)) {
354 ticks.callback = function (value) {
355 if (this && typeof this.getLabelForValue === "function") {
356 return this.getLabelForValue(value);
357 }
358 return String(value == null ? "" : value);
359 };
360 } else {
361 ticks.callback = function (value) {
362 return typeof value === "number" ? formatChartNumber(value, 6) : String(value);
363 };
364 }
365 }
366 });
367 }
368 var plugins = options.plugins && typeof options.plugins === "object" ? options.plugins : (options.plugins = {});
369 var tooltip = plugins.tooltip && typeof plugins.tooltip === "object" ? plugins.tooltip : (plugins.tooltip = {});
370 var callbacks = tooltip.callbacks && typeof tooltip.callbacks === "object" ? tooltip.callbacks : (tooltip.callbacks = {});
371 if (typeof callbacks.label !== "function") {
372 callbacks.label = function (context) {
373 var label = context && context.dataset && context.dataset.label ? String(context.dataset.label) + ": " : "";
374 var value = resolveChartTooltipValue(context);
375 return label + (typeof value === "number" ? formatChartNumber(value, 6) : String(value == null ? "" : value));
376 };
377 }
378 }
379
380 function normalizeChartConfig(config) {
381 if (config && config.data) {
382 config.data = normalizeChartData(config.data);
383 }
384 ensureChartNumberFormatters(config);
385 return config;
386 }
387
388 function resolveChartInstance(ChartCtor, target, canvas) {
389 var chart = null;
390 if (ChartCtor && typeof ChartCtor.getChart === "function") {
391 try { chart = ChartCtor.getChart(target) || null; } catch (_err) {}
392 if (!chart && canvas) {
393 try { chart = ChartCtor.getChart(canvas) || null; } catch (_err) {}
394 }
395 }
396 if (!chart && canvas) {
397 chart = getChartRegistry().get(canvas) || null;
398 }
399 return chart;
400 }
401
402 function collectChartInstances(ChartCtor) {
403 var set = new Set();
404 var registry = getChartRegistry();
405 registry.forEach(function (chart) {
406 if (chart) set.add(chart);
407 });
408 if (ChartCtor && ChartCtor.instances) {
409 var values = Array.isArray(ChartCtor.instances)
410 ? ChartCtor.instances
411 : Object.values(ChartCtor.instances);
412 values.forEach(function (chart) {
413 if (chart) set.add(chart);
414 });
415 }
416 return Array.from(set);
417 }
418
419 function renderMath(root) {
420 var target = root || (global.document ? document.body : null);
421 if (!target || typeof global.renderMathInElement !== "function") {
422 return Promise.resolve(false);
423 }
424 try {
425 global.renderMathInElement(target, {
426 delimiters: [
427 { left: "$$", right: "$$", display: true },
428 { left: "\\[", right: "\\]", display: true },
429 { left: "\\(", right: "\\)", display: false },
430 ],
431 ignoredTags: ["script", "noscript", "style", "textarea", "pre", "code", "option"],
432 ignoredClasses: ["katex", "katex-display", "katex-html", "katex-mathml"],
433 throwOnError: false,
434 strict: "ignore",
435 trust: false,
436 });
437 } catch (err) {
438 try { console.warn("[PPT.renderMath] arcsin1 failed", err); } catch (_err) {}
439 return Promise.resolve(false);
440 }
441 return waitFrames(2).then(function () { return true; });
442 }
443
444 function markChartReady(chart) {
445 if (!chart || typeof chart !== "object") return Promise.resolve();
446 var readyTask = waitFrames(2).then(function () {
447 if (typeof chart.resize === "function") {
448 try { chart.resize(); } catch (_err) {}
449 }
450 if (typeof chart.update === "function") {
451 try { chart.update("none"); } catch (_err) {}
452 }
453 });
454 return trackPrintTask(readyTask);
455 }
456
457 function createCompletedAnimationStub() {
458 var done = Promise.resolve();
459 return {
460 finished: done,
461 play: function () {},
462 pause: function () {},
463 restart: function () {},
464 seek: function () {},
465 remove: function () {},
466 cancel: function () {},
467 };
468 }
469
470 var _activeAnimations = new Set();
471
472 ppt.animate = function () {
473 var args = Array.prototype.slice.call(arguments);
474 if (isPrintMode) {
475 var rawTargets = args[0];
476 var rawParams = args[1];
477 if (args.length === 1 && rawTargets && typeof rawTargets === "object" && rawTargets.targets) {
478 rawParams = rawTargets;
479 rawTargets = rawTargets.targets;
480 }
481 applyPrintAnimationEndState(rawTargets, rawParams);
482 return createCompletedAnimationStub();
483 }
484
485 var runtimeAnime = resolveAnime();
486 var animation = null;
487 if (runtimeAnime && typeof runtimeAnime.animate === "function") {
488 animation = runtimeAnime.animate.apply(runtimeAnime, args);
489 } else if (typeof runtimeAnime === "function") {
490 animation = runtimeAnime.apply(null, args);
491 } else {
492 throw new Error("anime.js v4 未就绪,无法执行 PPT.animate");
493 }
494 if (animation && animation.finished && typeof animation.finished.then === "function") {
495 trackPrintTask(animation.finished);
496 }
497 if (animation && typeof animation.pause === "function") {
498 _activeAnimations.add(animation);
499 var origFinished = animation.finished;
500 if (origFinished && typeof origFinished.then === "function") {
501 origFinished.then(
502 function () { _activeAnimations.delete(animation); },
503 function () { _activeAnimations.delete(animation); }
504 );
505 }
506 }
507 return animation;
508 };
509
510 ppt.stagger = function () {
511 var runtimeAnime = resolveAnime();
512 var args = Array.prototype.slice.call(arguments);
513 if (runtimeAnime && typeof runtimeAnime.stagger === "function") {
514 return runtimeAnime.stagger.apply(runtimeAnime, args);
515 }
516 return buildStagger.apply(null, args);
517 };
518
519 ppt.createTimeline = function () {
520 var runtimeAnime = resolveAnime();
521 var args = Array.prototype.slice.call(arguments);
522 if (runtimeAnime && typeof runtimeAnime.createTimeline === "function") {
523 return runtimeAnime.createTimeline.apply(runtimeAnime, args);
524 }
525 if (runtimeAnime && typeof runtimeAnime.timeline === "function") {
526 return runtimeAnime.timeline.apply(runtimeAnime, args);
527 }
528 return buildTimeline(runtimeAnime).apply(null, args);
529 };
530
531 ppt.stopAnimations = function () {
532 _activeAnimations.forEach(function (anim) {
533 try { if (typeof anim.pause === "function") anim.pause(); } catch (_err) {}
534 });
535 };
536
537 ppt.finishAnimations = function () {
538 _activeAnimations.forEach(function (anim) {
539 try {
540 if (typeof anim.complete === "function") {
541 anim.complete();
542 return;
543 }
544 if (typeof anim.seek === "function" && Number.isFinite(Number(anim.duration))) {
545 anim.seek(Number(anim.duration));
546 }
547 if (typeof anim.pause === "function") anim.pause();
548 } catch (_err) {}
549 });
550 };
551
552 ppt.resumeAnimations = function () {
553 _activeAnimations.forEach(function (anim) {
554 try { if (typeof anim.play === "function") anim.play(); } catch (_err) {}
555 });
556 };
557
558 ppt.clicks = {
559 enabled: clickAnimationsEnabled,
560 current: 0,
561 total: 0,
562 _listeners: [],
563 _advanceListeners: [],
564 reset: function () {
565 this.current = 0;
566 },
567 _clearListeners: function () {
568 this._listeners = [];
569 this._advanceListeners = [];
570 },
571 setTotal: function (n) {
572 this.total = this.enabled ? Math.max(0, Number(n) || 0) : 0;
573 if (this.current > this.total) this.current = this.total;
574 },
575 advance: function () {
576 if (!this.enabled) return false;
577 if (this.total > 0 && this.current >= this.total) return false;
578 this.current += 1;
579 this._dispatch(this.current);
580 return true;
581 },
582 on: function (clickNum, fn) {
583 if (!this.enabled) return;
584 this._listeners.push({ clickNum: clickNum, fn: fn });
585 if (this.current >= clickNum) {
586 try { fn(); } catch (_err) {}
587 }
588 },
589 onAdvance: function (fn) {
590 this._advanceListeners.push(fn);
591 },
592 _dispatch: function (click) {
593 var self = this;
594 this._listeners.forEach(function (entry) {
595 if (entry.clickNum === click) {
596 try { entry.fn(); } catch (_err) {}
597 }
598 });
599 this._advanceListeners.forEach(function (fn) {
600 try { fn(click, self.current, self.total); } catch (_err) {}
601 });
602 }
603 };
604
605 function isPlaybackBridgeEnabled() {
606 try {
607 var search = new URLSearchParams(global.location && global.location.search || "");
608 return search.get("pptPlayback") === "1";
609 } catch (_err) {
610 return false;
611 }
612 }
613
614 function isEditablePlaybackTarget(target) {
615 if (!target || target.nodeType !== 1) return false;
616 return Boolean(target.closest("input, textarea, select, button, [contenteditable='true'], [contenteditable='']"));
617 }
618
619 function postPlaybackHandled(requestId) {
620 if (!requestId) return;
621 try {
622 if (!global.parent || global.parent === global) return;
623 global.parent.postMessage({
624 type: "ohmyppt:playback:handled",
625 requestId: requestId
626 }, "*");
627 } catch (_err) {}
628 }
629
630 function postPlaybackNavigation(offset, requestId) {
631 try {
632 if (!global.parent || global.parent === global) return;
633 global.parent.postMessage({
634 type: "ohmyppt:playback:goto",
635 offset: offset || 1,
636 requestId: requestId || null
637 }, "*");
638 } catch (_err) {}
639 }
640
641 function consumePlaybackStepOrNavigate(offset, requestId) {
642 if (ppt.clicks && ppt.clicks.total > 0 && typeof ppt.clicks.advance === "function") {
643 if (ppt.clicks.advance()) {
644 postPlaybackHandled(requestId);
645 return true;
646 }
647 }
648 postPlaybackNavigation(offset || 1, requestId);
649 return true;
650 }
651
652 function stopPlaybackEvent(event) {
653 if (!event) return;
654 if (typeof event.preventDefault === "function") event.preventDefault();
655 }
656
657 function normalizePlaybackWheelDelta(event) {
658 var delta = Math.abs(event.deltaY) >= Math.abs(event.deltaX) ? event.deltaY : event.deltaX;
659 if (event.deltaMode === 1) delta *= 16;
660 else if (event.deltaMode === 2) delta *= 900;
661 return delta;
662 }
663
664 function installPlaybackBridge() {
665 if (!isPlaybackBridgeEnabled() || global.__ohmypptPlaybackBridgeInstalled) return;
666 var doc = global.document;
667 if (!doc || typeof doc.addEventListener !== "function") return;
668 global.__ohmypptPlaybackBridgeInstalled = true;
669 var playbackBridgeInstallToken = (global.__ohmypptPlaybackBridgeInstallToken || 0) + 1;
670 global.__ohmypptPlaybackBridgeInstallToken = playbackBridgeInstallToken;
671 var wheelDeltaBuffer = 0;
672 var wheelGestureLocked = false;
673 var wheelGestureLockDirection = 0;
674 var lastWheelNavigateAt = 0;
675 var wheelGestureUnlockTimer = 0;
676 var wheelNavigationRequestSeq = 0;
677 var pendingWheelNavigationRequests = {};
678 var WHEEL_NAV_THRESHOLD = 80;
679 var WHEEL_NAV_COOLDOWN = 520;
680 var WHEEL_GESTURE_IDLE = 260;
681
682 function resetWheelGestureState() {
683 wheelDeltaBuffer = 0;
684 wheelGestureLocked = false;
685 wheelGestureLockDirection = 0;
686 lastWheelNavigateAt = 0;
687 }
688
689 function resetWheelGestureSoon() {
690 if (wheelGestureUnlockTimer) global.clearTimeout(wheelGestureUnlockTimer);
691 wheelGestureUnlockTimer = global.setTimeout(function () {
692 wheelGestureUnlockTimer = 0;
693 resetWheelGestureState();
694 }, WHEEL_GESTURE_IDLE);
695 }
696
697 function nextWheelNavigationRequestId() {
698 var requestId = "wheel-" + (++wheelNavigationRequestSeq);
699 pendingWheelNavigationRequests[requestId] = true;
700 return requestId;
701 }
702
703 function postWheelNavigation(offset) {
704 postPlaybackNavigation(offset, nextWheelNavigationRequestId());
705 }
706
707 doc.addEventListener("click", function (event) {
708 if (global.__ohmypptPlaybackBridgeInstallToken !== playbackBridgeInstallToken) return;
709 if (isEditablePlaybackTarget(event.target)) return;
710 stopPlaybackEvent(event);
711 consumePlaybackStepOrNavigate(1);
712 }, true);
713
714 doc.addEventListener("keydown", function (event) {
715 if (global.__ohmypptPlaybackBridgeInstallToken !== playbackBridgeInstallToken) return;
716 if (isEditablePlaybackTarget(event.target)) return;
717 var forwardKeys = ["ArrowRight", "ArrowDown", "PageDown", " "];
718 var backKeys = ["ArrowLeft", "ArrowUp", "PageUp"];
719 if (forwardKeys.indexOf(event.key) >= 0) {
720 stopPlaybackEvent(event);
721 consumePlaybackStepOrNavigate(1);
722 } else if (backKeys.indexOf(event.key) >= 0) {
723 stopPlaybackEvent(event);
724 postPlaybackNavigation(-1);
725 }
726 }, true);
727
728 doc.addEventListener("wheel", function (event) {
729 if (global.__ohmypptPlaybackBridgeInstallToken !== playbackBridgeInstallToken) return;
730 if (event.ctrlKey || event.metaKey) return;
731 if (isEditablePlaybackTarget(event.target)) return;
732
733 var delta = normalizePlaybackWheelDelta(event);
734 if (!Number.isFinite(delta) || Math.abs(delta) < 1) return;
735 var direction = delta > 0 ? 1 : -1;
736
737 if (Math.sign(delta) !== Math.sign(wheelDeltaBuffer)) wheelDeltaBuffer = 0;
738 wheelDeltaBuffer += delta;
739 resetWheelGestureSoon();
740
741 if (Math.abs(wheelDeltaBuffer) < WHEEL_NAV_THRESHOLD) return;
742
743 if (wheelGestureLocked && wheelGestureLockDirection && direction !== wheelGestureLockDirection) {
744 wheelGestureLocked = false;
745 wheelGestureLockDirection = 0;
746 lastWheelNavigateAt = 0;
747 }
748
749 if (wheelGestureLocked) {
750 wheelDeltaBuffer = 0;
751 stopPlaybackEvent(event);
752 return;
753 }
754
755 var now = Date.now();
756 if (now - lastWheelNavigateAt < WHEEL_NAV_COOLDOWN) {
757 wheelDeltaBuffer = 0;
758 wheelGestureLocked = true;
759 wheelGestureLockDirection = direction;
760 stopPlaybackEvent(event);
761 return;
762 }
763
764 var offset = wheelDeltaBuffer > 0 ? 1 : -1;
765 wheelDeltaBuffer = 0;
766 wheelGestureLocked = true;
767 wheelGestureLockDirection = offset;
768 lastWheelNavigateAt = now;
769 stopPlaybackEvent(event);
770 if (offset > 0) {
771 if (ppt.clicks && ppt.clicks.total > 0 && typeof ppt.clicks.advance === "function" && ppt.clicks.advance()) {
772 return;
773 }
774 postWheelNavigation(1);
775 } else {
776 postWheelNavigation(-1);
777 }
778 }, { capture: true, passive: false });
779
780 global.addEventListener("message", function (event) {
781 if (global.__ohmypptPlaybackBridgeInstallToken !== playbackBridgeInstallToken) return;
782 if (event.source && event.source !== global.parent) return;
783 var data = event && event.data;
784 if (!data) return;
785 if (data.type === "ohmyppt:playback:navigation-result") {
786 if (!data.requestId || !pendingWheelNavigationRequests[data.requestId]) return;
787 delete pendingWheelNavigationRequests[data.requestId];
788 if (!data.navigated) resetWheelGestureState();
789 return;
790 }
791 if (data.type !== "ohmyppt:playback:advance") return;
792 var offset = Number(data.offset);
793 consumePlaybackStepOrNavigate(
794 Number.isFinite(offset) && offset !== 0 ? offset : 1,
795 data.requestId || null
796 );
797 });
798 }
799
800 installPlaybackBridge();
801
802 var DATA_ANIM_TYPES = {
803 "fade": true,
804 "fade-up": true,
805 "fade-down": true,
806 "fade-left": true,
807 "fade-right": true,
808 "scale-in": true,
809 "slide-up": true,
810 "slide-down": true,
811 "slide-left": true,
812 "slide-right": true,
813 "fly-in": true,
814 "wipe": true,
815 "zoom-in": true,
816 "spin-in": true,
817 "grow-shrink-soft": true,
818 "grow-shrink": true,
819 "grow-shrink-strong": true,
820 "pulse-soft": true,
821 "pulse": true,
822 "pulse-strong": true,
823 "exit-fade": true,
824 "exit-scale": true,
825 "exit-zoom": true,
826 "exit-wipe": true,
827 "exit-fly": true,
828 "path": true,
829 "lottie": true
830 };
831
832 var DATA_ANIM_ENTRANCE_TYPES = {
833 "fade": true,
834 "fade-up": true,
835 "fade-down": true,
836 "fade-left": true,
837 "fade-right": true,
838 "scale-in": true,
839 "slide-up": true,
840 "slide-down": true,
841 "slide-left": true,
842 "slide-right": true,
843 "fly-in": true,
844 "wipe": true,
845 "zoom-in": true,
846 "spin-in": true
847 };
848
849 var DATA_ANIM_INITIAL_STYLES = {
850 "fade": { opacity: "0" },
851 "fade-up": { opacity: "0", transform: "translateY(20px)" },
852 "fade-down": { opacity: "0", transform: "translateY(-20px)" },
853 "fade-left": { opacity: "0", transform: "translateX(20px)" },
854 "fade-right": { opacity: "0", transform: "translateX(-20px)" },
855 "scale-in": { opacity: "0", transform: "scale(0.85)" },
856 "slide-up": { opacity: "0", transform: "translateY(64px)" },
857 "slide-down": { opacity: "0", transform: "translateY(-64px)" },
858 "slide-left": { opacity: "0", transform: "translateX(72px)" },
859 "slide-right":{ opacity: "0", transform: "translateX(-72px)" },
860 "zoom-in": { opacity: "0", transform: "scale(0.75)" },
861 "spin-in": { opacity: "0", transform: "rotate(-12deg) scale(0.92)" }
862 };
863
864 function normalizeDataAnimType(type) {
865 var normalized = String(type || "fade-up").trim().toLowerCase();
866 if (normalized === "none") return "none";
867 if (normalized === "fly" || normalized === "flyin") return "fly-in";
868 if (normalized === "zoom" || normalized === "zoomin") return "zoom-in";
869 if (normalized === "spin" || normalized === "spinin") return "spin-in";
870 if (normalized === "grow" || normalized === "growshrink") return "grow-shrink";
871 if (normalized === "growsoft" || normalized === "growshrinksoft") return "grow-shrink-soft";
872 if (normalized === "growstrong" || normalized === "growshrinkstrong") return "grow-shrink-strong";
873 if (normalized === "emphasis") return "pulse";
874 if (normalized === "pulsesoft") return "pulse-soft";
875 if (normalized === "pulsestrong") return "pulse-strong";
876 if (normalized === "exitscale") return "exit-scale";
877 if (normalized === "exitzoom") return "exit-zoom";
878 if (DATA_ANIM_TYPES[normalized]) return normalized;
879 return "fade-up";
880 }
881
882 function normalizeAnimTrigger(trigger) {
883 var normalized = String(trigger || "load").trim().toLowerCase();
884 if (normalized === "on-click") return "click";
885 if (normalized === "after-previous") return "after";
886 if (normalized === "with-previous") return "with";
887 if (normalized === "click" || normalized === "load" || normalized === "with" || normalized === "after") {
888 return normalized;
889 }
890 return "load";
891 }
892
893 function normalizeAnimSequence(sequence) {
894 var normalized = String(sequence || "").trim().toLowerCase();
895 if (normalized === "after-previous") return "after";
896 if (normalized === "with-previous") return "with";
897 if (normalized === "with" || normalized === "after") return normalized;
898 return "";
899 }
900
901 function normalizeAnimClickGroup(group) {
902 var normalized = String(group || "").trim();
903 return normalized || "";
904 }
905
906 function isContiguousClickGroupValue(group) {
907 return /^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(String(group || ""));
908 }
909
910 function normalizeAnimSide(side, fallback) {
911 var normalized = String(side || fallback || "bottom").trim().toLowerCase();
912 if (normalized === "up" || normalized === "top") return "top";
913 if (normalized === "down" || normalized === "bottom") return "bottom";
914 if (normalized === "start") return "left";
915 if (normalized === "end") return "right";
916 if (normalized === "left" || normalized === "right" || normalized === "center") return normalized;
917 return fallback || "bottom";
918 }
919
920 function defaultAnimSideForType(type) {
921 switch (type) {
922 case "fade-up":
923 case "slide-up":
924 return "bottom";
925 case "fade-down":
926 case "slide-down":
927 return "top";
928 case "fade-left":
929 case "slide-left":
930 return "right";
931 case "fade-right":
932 case "slide-right":
933 return "left";
934 case "wipe":
935 case "exit-wipe":
936 return "left";
937 default:
938 return "bottom";
939 }
940 }
941
942 function getSideOffset(side, distance) {
943 switch (normalizeAnimSide(side, "bottom")) {
944 case "left":
945 return { x: -distance, y: 0 };
946 case "right":
947 return { x: distance, y: 0 };
948 case "top":
949 return { x: 0, y: -distance };
950 case "center":
951 return { x: 0, y: 0, scale: 0.9 };
952 case "bottom":
953 default:
954 return { x: 0, y: distance };
955 }
956 }
957
958 function getWipeClipPath(side, visible) {
959 if (visible) return "inset(0% 0% 0% 0%)";
960 switch (normalizeAnimSide(side, "left")) {
961 case "right":
962 return "inset(0% 0% 0% 100%)";
963 case "top":
964 return "inset(0% 0% 100% 0%)";
965 case "bottom":
966 return "inset(100% 0% 0% 0%)";
967 case "center":
968 return "inset(20% 20% 20% 20%)";
969 case "left":
970 default:
971 return "inset(0% 100% 0% 0%)";
972 }
973 }
974
975 function getInitialDataAnimStyle(type, from) {
976 if (!DATA_ANIM_ENTRANCE_TYPES[type]) return null;
977 if (type === "fly-in") {
978 var fly = getSideOffset(from, 40);
979 if (fly.scale) return { opacity: "0", transform: "scale(" + fly.scale + ")" };
980 if (fly.x) return { opacity: "0", transform: "translateX(" + fly.x + "px)" };
981 return { opacity: "0", transform: "translateY(" + fly.y + "px)" };
982 }
983 if (type === "wipe") {
984 return { opacity: "0", clipPath: getWipeClipPath(from, false) };
985 }
986 return DATA_ANIM_INITIAL_STYLES[type] || DATA_ANIM_INITIAL_STYLES["fade-up"];
987 }
988
989 function applyInitialHiddenState(el, type, from) {
990 var initial = getInitialDataAnimStyle(type, from);
991 if (!initial) return false;
992 if (initial.opacity !== undefined) el.style.opacity = initial.opacity;
993 if (initial.clipPath) el.style.clipPath = initial.clipPath;
994 // Compose with existing transform so Tailwind classes survive
995 if (initial.transform) {
996 var existing = (el.style.transform || "").trim();
997 el.style.transform = existing ? existing + " " + initial.transform : initial.transform;
998 }
999 return true;
1000 }
1001
1002 function normalizeAnimRepeat(repeat) {
1003 var raw = String(repeat || "").trim().toLowerCase();
1004 if (!raw || raw === "1" || raw === "false") return null;
1005 if (raw === "infinite" || raw === "loop" || raw === "true") return true;
1006 var count = Number(raw);
1007 if (!Number.isFinite(count) || count <= 1) return null;
1008 return Math.min(20, Math.floor(count));
1009 }
1010
1011 function applyRepeatDirectionParams(params, animDef) {
1012 if (animDef.repeat === true) {
1013 params.loop = true;
1014 } else if (Number.isFinite(animDef.repeat) && animDef.repeat > 1) {
1015 params.loop = animDef.repeat - 1;
1016 }
1017
1018 if (animDef.direction === "alternate") {
1019 params.alternate = true;
1020 } else if (animDef.direction === "reverse") {
1021 params.reversed = true;
1022 }
1023 }
1024
1025 function parseMotionPathDelta(path) {
1026 var raw = String(path || "").trim();
1027 if (!raw) return null;
1028 var coords = raw.match(/-?\d+(?:\.\d+)?/g);
1029 if (!coords || coords.length < 4) return null;
1030 var startX = Number(coords[0]);
1031 var startY = Number(coords[1]);
1032 var endX = Number(coords[coords.length - 2]);
1033 var endY = Number(coords[coords.length - 1]);
1034 if (![startX, startY, endX, endY].every(Number.isFinite)) return null;
1035 return { x: endX - startX, y: endY - startY };
1036 }
1037
1038 function scanDataAnimElements(root) {
1039 ppt.clicks.reset();
1040 ppt.clicks._clearListeners();
1041
1042 var elements = Array.from((root || document).querySelectorAll("[data-anim]"));
1043 if (elements.length === 0) {
1044 ppt.clicks.setTotal(0);
1045 return null;
1046 }
1047
1048 var animConfigs = [];
1049 // Per-trigger-group counters for stagger(N) → numeric delay
1050 var staggerCounters = {};
1051 var lastSequenceStart = 0;
1052 var lastSequenceEnd = 0;
1053
1054 elements.forEach(function (el, index) {
1055 var type = normalizeDataAnimType(el.getAttribute("data-anim") || "fade-up");
1056 if (type === "none") return;
1057
1058 var trigger = normalizeAnimTrigger(el.getAttribute("data-anim-trigger") || "load");
1059 if (trigger === "click" && !clickAnimationsEnabled) return;
1060 var effectiveTrigger = trigger === "click" ? "click" : "load";
1061 var sequence = normalizeAnimSequence(el.getAttribute("data-anim-sequence") || "");
1062 var clickGroup = normalizeAnimClickGroup(el.getAttribute("data-anim-click-group") || "");
1063 if (clickGroup && (!isContiguousClickGroupValue(clickGroup) || effectiveTrigger !== "click")) {
1064 clickGroup = "";
1065 }
1066 var from = normalizeAnimSide(el.getAttribute("data-anim-from"), defaultAnimSideForType(type));
1067 var duration = Number(el.getAttribute("data-anim-duration")) || 500;
1068 var easing = (el.getAttribute("data-anim-easing") || "easeOutCubic").trim();
1069 var delayRaw = (el.getAttribute("data-anim-delay") || "0").trim();
1070 var staggerRaw = (el.getAttribute("data-anim-stagger") || "").trim();
1071 var delay = 0;
1072 var boundedDuration = Math.max(100, Math.min(5000, duration));
1073
1074 if (staggerRaw) {
1075 var staggerGap = Number(staggerRaw);
1076 var normalizedGap = Number.isFinite(staggerGap) ? Math.max(0, staggerGap) : 0;
1077 var staggerGroupKey = effectiveTrigger;
1078 if (staggerCounters[staggerGroupKey] === undefined) staggerCounters[staggerGroupKey] = 0;
1079 delay = staggerCounters[staggerGroupKey] * normalizedGap;
1080 staggerCounters[staggerGroupKey] += 1;
1081 } else if (delayRaw.indexOf("stagger") === 0) {
1082 var match = delayRaw.match(/stagger\s*\(\s*(\d+)\s*\)/);
1083 var gap = match ? Number(match[1]) : 50;
1084 var groupKey = effectiveTrigger;
1085 if (staggerCounters[groupKey] === undefined) staggerCounters[groupKey] = 0;
1086 delay = staggerCounters[groupKey] * gap;
1087 staggerCounters[groupKey] += 1;
1088 } else {
1089 delay = Number(delayRaw) || 0;
1090 }
1091
1092 if (effectiveTrigger === "load") {
1093 var sequencingMode = sequence || trigger;
1094 if (sequencingMode === "after") {
1095 delay += lastSequenceEnd;
1096 lastSequenceStart = delay;
1097 lastSequenceEnd = Math.max(lastSequenceEnd, delay + boundedDuration);
1098 } else if (sequencingMode === "with") {
1099 delay += lastSequenceStart;
1100 lastSequenceEnd = Math.max(lastSequenceEnd, delay + boundedDuration);
1101 } else {
1102 lastSequenceStart = delay;
1103 lastSequenceEnd = Math.max(lastSequenceEnd, delay + boundedDuration);
1104 }
1105 }
1106
1107 if (trigger === "click" && type !== "lottie") {
1108 if (applyInitialHiddenState(el, type, from)) {
1109 el.setAttribute("data-ppt-anim-initialized", "1");
1110 }
1111 }
1112
1113 var animDef = {
1114 targets: el,
1115 type: type,
1116 trigger: trigger,
1117 effectiveTrigger: effectiveTrigger,
1118 sequence: sequence || undefined,
1119 clickGroup: clickGroup || undefined,
1120 from: from,
1121 duration: boundedDuration,
1122 easing: easing,
1123 delay: delay,
1124 stagger: staggerRaw ? Math.max(0, Number(staggerRaw) || 0) : undefined,
1125 repeat: normalizeAnimRepeat(el.getAttribute("data-anim-repeat")),
1126 direction: (el.getAttribute("data-anim-direction") || "normal").trim().toLowerCase(),
1127 path: (el.getAttribute("data-anim-path") || "").trim(),
1128 order: index
1129 };
1130
1131 // Lottie hook — parse additional attributes, store in config.
1132 if (type === "lottie") {
1133 animDef.lottieSrc = (el.getAttribute("data-anim-lottie-src") || "").trim();
1134 animDef.lottieLoop = el.getAttribute("data-anim-lottie-loop") !== "false";
1135 animDef.lottieAutoplay = el.getAttribute("data-anim-lottie-autoplay") !== "false";
1136 }
1137
1138 animConfigs.push(animDef);
1139 });
1140
1141 var loadAnims = animConfigs.filter(function (a) { return a.effectiveTrigger === "load"; });
1142 var clickAnims = animConfigs.filter(function (a) { return a.effectiveTrigger === "click"; });
1143
1144 var clickSteps = [];
1145 clickAnims.forEach(function (animDef) {
1146 var previousStep = clickSteps.length > 0 ? clickSteps[clickSteps.length - 1] : null;
1147 var previousGroup =
1148 previousStep && previousStep.length > 0 ? previousStep[0].clickGroup || "" : "";
1149 if (animDef.clickGroup && previousStep && previousGroup === animDef.clickGroup) {
1150 previousStep.push(animDef);
1151 return;
1152 }
1153 clickSteps.push([animDef]);
1154 });
1155
1156 ppt.clicks.setTotal(clickSteps.length);
1157
1158 return { load: loadAnims, click: clickAnims, clickSteps: clickSteps, all: animConfigs };
1159 }
1160
1161 function executeDataAnimConfig(config) {
1162 if (!config || config.length === 0) return;
1163
1164 config.forEach(function (animDef) {
1165 // Lottie hook — delegate to dedicated player when available.
1166 // Falls through to no-op until lottie runtime is injected.
1167 if (animDef.type === "lottie") {
1168 if (typeof ppt.playLottie === "function") {
1169 ppt.playLottie(animDef.targets, animDef);
1170 }
1171 return;
1172 }
1173
1174 var params = {
1175 duration: animDef.duration,
1176 easing: animDef.easing,
1177 delay: animDef.delay
1178 };
1179 applyRepeatDirectionParams(params, animDef);
1180
1181 var motionPathDelta = parseMotionPathDelta(animDef.path);
1182
1183 switch (animDef.type) {
1184 case "fade":
1185 params.opacity = [0, 1];
1186 break;
1187 case "fade-up":
1188 params.opacity = [0, 1];
1189 params.translateY = [20, 0];
1190 break;
1191 case "fade-down":
1192 params.opacity = [0, 1];
1193 params.translateY = [-20, 0];
1194 break;
1195 case "fade-left":
1196 params.opacity = [0, 1];
1197 params.translateX = [20, 0];
1198 break;
1199 case "fade-right":
1200 params.opacity = [0, 1];
1201 params.translateX = [-20, 0];
1202 break;
1203 case "scale-in":
1204 params.opacity = [0, 1];
1205 params.scale = [0.85, 1];
1206 break;
1207 case "slide-up":
1208 params.opacity = [0, 1];
1209 params.translateY = [64, 0];
1210 break;
1211 case "slide-down":
1212 params.opacity = [0, 1];
1213 params.translateY = [-64, 0];
1214 break;
1215 case "slide-left":
1216 params.opacity = [0, 1];
1217 params.translateX = [72, 0];
1218 break;
1219 case "slide-right":
1220 params.opacity = [0, 1];
1221 params.translateX = [-72, 0];
1222 break;
1223 case "fly-in": {
1224 var fly = getSideOffset(animDef.from, 72);
1225 params.opacity = [0, 1];
1226 if (fly.scale) {
1227 params.scale = [fly.scale, 1];
1228 } else if (fly.x) {
1229 params.translateX = [fly.x, 0];
1230 } else {
1231 params.translateY = [fly.y, 0];
1232 }
1233 break;
1234 }
1235 case "wipe":
1236 params.opacity = [0, 1];
1237 params.clipPath = [getWipeClipPath(animDef.from, false), getWipeClipPath(animDef.from, true)];
1238 break;
1239 case "zoom-in":
1240 params.opacity = [0, 1];
1241 params.scale = [0.75, 1];
1242 break;
1243 case "spin-in":
1244 params.opacity = [0, 1];
1245 params.rotate = [-12, 0];
1246 params.scale = [0.92, 1];
1247 break;
1248 case "grow-shrink-soft":
1249 params.scale = [0.95, 1.04, 1];
1250 break;
1251 case "grow-shrink":
1252 params.scale = [0.9, 1.08, 1];
1253 break;
1254 case "grow-shrink-strong":
1255 params.scale = [0.85, 1.12, 1];
1256 break;
1257 case "pulse-soft":
1258 params.scale = [1, 1.03, 1];
1259 break;
1260 case "pulse":
1261 params.scale = [1, 1.06, 1];
1262 break;
1263 case "pulse-strong":
1264 params.scale = [1, 1.1, 1];
1265 break;
1266 case "exit-fade":
1267 params.opacity = [1, 0];
1268 break;
1269 case "exit-scale":
1270 params.opacity = [1, 0];
1271 params.scale = [1, 0.85];
1272 break;
1273 case "exit-zoom":
1274 params.opacity = [1, 0];
1275 params.scale = [1, 0.75];
1276 break;
1277 case "exit-wipe":
1278 params.opacity = [1, 0];
1279 params.clipPath = [getWipeClipPath(animDef.from, true), getWipeClipPath(animDef.from, false)];
1280 break;
1281 case "exit-fly": {
1282 var exitFly = getSideOffset(animDef.from, 40);
1283 params.opacity = [1, 0];
1284 if (exitFly.x) {
1285 params.translateX = [0, exitFly.x];
1286 } else if (exitFly.y) {
1287 params.translateY = [0, exitFly.y];
1288 } else {
1289 params.scale = [1, 0.9];
1290 }
1291 break;
1292 }
1293 default:
1294 if (motionPathDelta) {
1295 params.translateX = [0, motionPathDelta.x];
1296 params.translateY = [0, motionPathDelta.y];
1297 } else {
1298 params.opacity = [0, 1];
1299 params.translateY = [20, 0];
1300 }
1301 }
1302
1303 if (motionPathDelta && !params.translateX && !params.translateY) {
1304 params.translateX = [0, motionPathDelta.x];
1305 params.translateY = [0, motionPathDelta.y];
1306 }
1307
1308 // Unified path: print-mode, task tracking, stopAnimations()
1309 ppt.animate(animDef.targets, params);
1310 });
1311 }
1312
1313 // no-op until lottie-web injected
1314 ppt.playLottie = function (_el, _animDef) {};
1315
1316 ppt.scanDataAnim = function (root) {
1317 return scanDataAnimElements(root);
1318 };
1319
1320 ppt.executeDataAnim = function (config) {
1321 return executeDataAnimConfig(config);
1322 };
1323
1324 ppt.createChart = function (target, config) {
1325 var ChartCtor = global.Chart;
1326 if (typeof ChartCtor !== "function") {
1327 throw new Error("Chart.js v4 未就绪,无法执行 PPT.createChart");
1328 }
1329 var targetInfo = resolveChartTarget(target);
1330 var canvas = targetInfo.canvas;
1331 var chartTarget = targetInfo.chartTarget;
1332 if (!chartTarget) {
1333 throw new Error("PPT.createChart 目标无效,未找到 canvas");
1334 }
1335 var existing = resolveChartInstance(ChartCtor, chartTarget, canvas);
1336 if (existing && typeof existing.destroy === "function") {
1337 try { existing.destroy(); } catch (_err) {}
1338 }
1339 var chart = new ChartCtor(chartTarget, withPrintChartConfig(config));
1340 var key = canvas || (chart && chart.canvas) || null;
1341 if (key) getChartRegistry().set(key, chart);
1342 markChartReady(chart);
1343 return chart;
1344 };
1345
1346 ppt.updateChart = function (target, patch) {
1347 var ChartCtor = global.Chart;
1348 if (typeof ChartCtor !== "function") {
1349 throw new Error("Chart.js v4 未就绪,无法执行1nicscra PPT.updateChart");
1350 }
1351 var targetInfo = resolveChartTarget(target);
1352 var canvas = targetInfo.canvas;
1353 var chartTarget = targetInfo.chartTarget;
1354 var chart = resolveChartInstance(ChartCtor, chartTarget, canvas);
1355 if (!chart) {
1356 throw new Error("PPT.updateChart 未找到对应图表实例arcsin1");
1357 }
1358 if (typeof patch === "function") {
1359 patch(chart);
1360 } else if (patch && typeof patch === "object") {
1361 if (Object.prototype.hasOwnProperty.call(patch, "data")) chart.data = normalizeChartData(patch.data);
1362 if (Object.prototype.hasOwnProperty.call(patch, "options")) {
1363 var patchedConfig = { data: chart.data, options: patch.options };
1364 ensureChartNumberFormatters(patchedConfig);
1365 chart.options = patchedConfig.options;
1366 }
1367 }
1368 if (typeof chart.update === "function") {
1369 var mode = patch && typeof patch === "object" ? patch.mode : undefined;
1370 chart.update(mode);
1371 }
1372 markChartReady(chart);
1373 return chart;
1374 };
1375
1376 ppt.destroyChart = function (target) {
1377 var ChartCtor = global.Chart;
1378 if (typeof ChartCtor !== "function") {
1379 throw new Error("Chart.js v4 未就绪,无法执行 PPT.destroyChart-1nicscra");
1380 }
1381 var targetInfo = resolveChartTarget(target);
1382 var canvas = targetInfo.canvas;
1383 var chartTarget = targetInfo.chartTarget;
1384 var chart = resolveChartInstance(ChartCtor, chartTarget, canvas);
1385 if (!chart) return false;
1386 try { chart.destroy(); } catch (_err) {}
1387 if (canvas) getChartRegistry().delete(canvas);
1388 return true;
1389 };
1390
1391 ppt.resizeCharts = function (target) {
1392 var ChartCtor = global.Chart;
1393 if (typeof ChartCtor !== "function") {
1394 throw new Error("Chart.js v4 未就绪,无法执行arcsin1 PPT.resizeCharts-1nicscra");
1395 }
1396 if (target !== undefined && target !== null) {
1397 var targetInfo = resolveChartTarget(target);
1398 var chart = resolveChartInstance(ChartCtor, targetInfo.chartTarget, targetInfo.canvas);
1399 if (!chart || typeof chart.resize !== "function") return 0;
1400 chart.resize();
1401 return 1;
1402 }
1403 var charts = collectChartInstances(ChartCtor);
1404 var count = 0;
1405 charts.forEach(function (chart) {
1406 if (chart && typeof chart.resize === "function") {
1407 chart.resize();
1408 count += 1;
1409 }
1410 });
1411 return count;
1412 };
1413
1414 ppt.renderMath = function (root) {
1415 return trackPrintTask(renderMath(root));
1416 };
1417
1418 function autoRenderMathWhenReady() {
1419 if (!global.document) return;
1420 var run = function () {
1421 ppt.renderMath(document.querySelector(".ppt-page-content") || document.body);
1422 };
1423 if (document.readyState === "loading") {
1424 document.addEventListener("DOMContentLoaded", run, { once: true });
1425 } else {
1426 run();
1427 }
1428 }
1429
1430 function isMasterElementsDisabled(root) {
1431 try {
1432 return !!(
1433 (global.document && document.body && document.body.getAttribute("data-ppt-master-off") === "1") ||
1434 (root && root.getAttribute("data-ppt-master-off") === "1")
1435 );
1436 } catch (_err) {
1437 return false;
1438 }
1439 }
1440
1441 function resolveMasterPageNumber(root) {
1442 var raw = "";
1443 try {
1444 raw =
1445 (root && root.getAttribute("data-ppt-page-number")) ||
1446 (document.body && document.body.getAttribute("data-ppt-page-number")) ||
1447 "";
1448 if (!raw) {
1449 var scaffold = document.querySelector("[data-page-number]");
1450 raw = scaffold ? scaffold.getAttribute("data-page-number") || "" : "";
1451 }
1452 } catch (_err) {}
1453 var pageNumber = Number(raw);
1454 if (Number.isFinite(pageNumber) && pageNumber >= 1) return String(Math.floor(pageNumber));
1455 try {
1456 var pathname = global.location ? String(global.location.pathname || "") : "";
1457 var match = pathname.match(/page-(\d+)\.html?$/i);
1458 if (match && match[1]) return String(Math.max(1, Number(match[1])));
1459 } catch (_err2) {}
1460 return "";
1461 }
1462
1463 function removeUnsafeMasterElementNodes(layer) {
1464 if (!layer || typeof layer.querySelectorAll !== "function") return;
1465 Array.prototype.slice.call(layer.querySelectorAll("script, iframe, object, embed, link")).forEach(function (node) {
1466 node.remove();
1467 });
1468 Array.prototype.slice.call(layer.querySelectorAll("*")).forEach(function (node) {
1469 Array.prototype.slice.call(node.attributes || []).forEach(function (attr) {
1470 if (/^on/i.test(attr.name)) node.removeAttribute(attr.name);
1471 });
1472 });
1473 }
1474
1475 function syncMasterWatermarkFontSize(root, layer) {
1476 if (!root || !layer || typeof layer.querySelectorAll !== "function") return;
1477 var slideHeight = Number(root.getAttribute("data-ppt-height"));
1478 if (!Number.isFinite(slideHeight) || slideHeight <= 0) {
1479 slideHeight = Number(root.clientHeight) || 0;
1480 }
1481 if (!Number.isFinite(slideHeight) || slideHeight <= 0) return;
1482 Array.prototype.slice.call(layer.querySelectorAll("[data-ppt-master-watermark-height]")).forEach(function (node) {
1483 var relativeHeight = Number(node.getAttribute("data-ppt-master-watermark-height"));
1484 if (!Number.isFinite(relativeHeight) || relativeHeight <= 0) return;
1485 var fontSize = Math.max(8, Math.min(160, Math.round(slideHeight * (relativeHeight / 100) * 0.45)));
1486 node.style.fontSize = String(fontSize) + "px";
1487 });
1488 }
1489
1490 function injectMasterElements() {
1491 if (!global.document || typeof global.fetch !== "function") {
1492 return expectsMasterElements
1493 ? Promise.reject(new Error("母版全局元素运行时不可用"))
1494 : Promise.resolve();
1495 }
1496 var root = document.querySelector(".ppt-page-root[data-ppt-guard-root=\"1\"]");
1497 if (!root) {
1498 return expectsMasterElements
1499 ? Promise.reject(new Error("母版全局元素缺少页面根节点"))
1500 : Promise.resolve();
1501 }
1502 if (isMasterElementsDisabled(root)) return Promise.resolve();
1503 var existing = root.querySelector(":scope > [data-ppt-master-elements-layer=\"1\"]");
1504 if (existing) return Promise.resolve();
1505 var masterUrl = "";
1506 try {
1507 masterUrl = new URL("./master/master.html", global.location.href).toString();
1508 } catch (_err) {
1509 return expectsMasterElements
1510 ? Promise.reject(new Error("母版全局元素地址无效"))
1511 : Promise.resolve();
1512 }
1513 return global
1514 .fetch(masterUrl, { cache: "no-store" })
1515 .then(function (response) {
1516 if (!response || !response.ok) {
1517 if (expectsMasterElements) throw new Error("母版全局元素加载失败");
1518 return "";
1519 }
1520 return response.text();
1521 })
1522 .then(function (source) {
1523 if (!source) {
1524 if (expectsMasterElements) throw new Error("母版全局元素内容为空");
1525 return;
1526 }
1527 var host = document.createElement("template");
1528 host.innerHTML = source;
1529 var configNode = host.content.querySelector("script[data-ppt-master-elements-config=\"1\"]");
1530 if (configNode) {
1531 try {
1532 JSON.parse(configNode.textContent || "{}");
1533 } catch (_err2) {
1534 if (expectsMasterElements) throw new Error("母版全局元素配置无效");
1535 return;
1536 }
1537 } else if (expectsMasterElements) {
1538 throw new Error("母版全局元素配置缺失");
1539 }
1540 var masterTemplate = host.content.querySelector("template[data-ppt-master-elements=\"1\"]");
1541 if (!masterTemplate) {
1542 if (expectsMasterElements) throw new Error("母版全局元素内容无效");
1543 return;
1544 }
1545 var sourceLayer = masterTemplate.content.querySelector("[data-ppt-master-elements-layer=\"1\"]");
1546 if (!sourceLayer) {
1547 if (expectsMasterElements) throw new Error("母版全局元素层缺失");
1548 return;
1549 }
1550 var layer = sourceLayer.cloneNode(true);
1551 removeUnsafeMasterElementNodes(layer);
1552 var pageNumber = resolveMasterPageNumber(root);
1553 Array.prototype.slice.call(layer.querySelectorAll("[data-ppt-master-page-number]")).forEach(function (node) {
1554 node.textContent = pageNumber;
1555 });
1556 syncMasterWatermarkFontSize(root, layer);
1557 root.appendChild(layer);
1558 })
1559 .catch(function (error) {
1560 if (expectsMasterElements) {
1561 throw error instanceof Error ? error : new Error("母版全局元素加载失败");
1562 }
1563 // Historical sessions without a fragment remain a safe no-op.
1564 });
1565 }
1566
1567 function loadMasterElementsWhenReady() {
1568 if (!global.document) return;
1569 masterElementsTask = new Promise(function (resolve, reject) {
1570 function run() {
1571 Promise.resolve(injectMasterElements()).then(resolve, reject);
1572 }
1573 if (document.readyState === "loading") {
1574 document.addEventListener("DOMContentLoaded", run, { once: true });
1575 } else {
1576 run();
1577 }
1578 });
1579 trackPrintTask(masterElementsTask);
1580 }
1581
1582 ppt.assertMasterElementsReady = function (timeoutMs) {
1583 if (!expectsMasterElements) return Promise.resolve();
1584 var timeout = Math.max(1000, Number(timeoutMs) || 5000);
1585 return Promise.race([
1586 masterElementsTask,
1587 new Promise(function (_resolve, reject) {
1588 setTimeout(function () {
1589 reject(new Error("母版全局元素加载超时"));
1590 }, timeout);
1591 }),
1592 ]);
1593 };
1594
1595 ppt.whenReadyForPrint = function (timeoutMs) {
1596 var timeout = Math.max(0, Number(timeoutMs) || 5000);
1597 var startAt = Date.now();
1598
1599 function waitDomReady() {
1600 if (!global.document || document.readyState === "complete" || document.readyState === "interactive") {
1601 return Promise.resolve();
1602 }
1603 return new Promise(function (resolve) {
1604 document.addEventListener("DOMContentLoaded", function onReady() {
1605 document.removeEventListener("DOMContentLoaded", onReady);
1606 resolve();
1607 });
1608 });
1609 }
1610
1611 function drainPending() {
1612 var snapshot = pendingPrintTasks.slice();
1613 if (snapshot.length === 0) return Promise.resolve();
1614 return Promise.allSettled(snapshot).then(function () {
1615 if (pendingPrintTasks.length > 0 && (Date.now() - startAt) < timeout) {
1616 return drainPending();
1617 }
1618 });
1619 }
1620
1621 return Promise.race([
1622 waitDomReady().then(function () {
1623 return drainPending().then(function () {
1624 return waitFrames(2);
1625 });
1626 }),
1627 new Promise(function (resolve) {
1628 setTimeout(function () { resolve(); }, timeout);
1629 }),
1630 ]);
1631 };
1632
1633 autoRenderMathWhenReady();
1634 loadMasterElementsWhenReady();
1635
1636 if (isPrintMode) {
1637 ppt.whenReadyForPrint(printTimeoutMs).then(function () {
1638 emitPrintReadyOnce();
1639 });
1640 }
1641 })(typeof globalThis !== "undefined" ? globalThis : window);
1642
1642 lines JAVASCRIPT