@web-ppt/viewer-core 0.4.5 → 0.5.0-beta.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/CHANGELOG.md CHANGED
@@ -2,6 +2,100 @@
2
2
 
3
3
  本文件记录对使用者可见的变化。版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
4
4
 
5
+ ## 未发布
6
+
7
+ ## 0.5.0-beta.1 - 2026-08-27
8
+
9
+ ### 新增
10
+
11
+ - `@web-ppt/edit-core`、`@web-ppt/editor` 与框架适配层新增元素动画编辑闭环:稳定
12
+ `SlideId` / `ElementId` 时间线支持来源恢复、显式清空、撤销恢复、删除/复制页联动,以及
13
+ view/edit 共用的无副作用即时预览。首个安全目录覆盖入场/退场的 appear、fade、fly、wipe、zoom、
14
+ dissolve,强调 spin/grow 和 2–256 顶点运动路径;复杂 MCE、build、扩展或条件时间树会标记
15
+ `sourceReadonly`,只有调用方明确整条替换才写回;公开 `MAX_ANIMATION_STEPS=128`,保证允许写入的
16
+ 最重时间线仍能在有界解析预算内完整回读。
17
+ - 元素动画保存按 OOXML 官方 preset/filter 重建规范 `p:tnLst`,保留 timing 根属性、`bldLst`、
18
+ `extLst` 与无关 MCE,并精确回读 trigger、delay、duration、方向和路径。真实 Chrome 实测 60 元素
19
+ 启动、200 页批量提交、单页反馈 p95 为 0.8/3.5/0.4ms;确定性 5 页固件及 LibreOffice 打开验证通过。
20
+
21
+ - `@web-ppt/editor` 新增显式启用的纯浏览器自动保存:完整源字节 SHA-256 把同内容输入绑定到同一日志,
22
+ IndexedDB 以串行批次原子追加恢复帧,并按分块数、总估算字节、记录数和保留期压缩清理。`openEditor`
23
+ 在解析和挂载前完成 restore/discard/cancel 决策,clean 尾帧不提示;adapter 与 React/Vue 共用
24
+ `recovering + onRecovery` seam;原子代际占位拒绝旧页面在 discard 后复活日志。真实 Chrome 中 50MB
25
+ 指纹为 42.2ms,1,000 帧写入/关闭重开恢复为 131.5/14.9ms,压缩后 10 个分块,同步提交增量 0.000ms。
26
+
27
+ - `@web-ppt/edit-core` 新增版本化 `RecoveryFrame`、`Editor.subscribeRecovery()` 与
28
+ `restoreRecoveryFrames()`:事务、撤销/重做、非历史写入、选区、保存点、图片资源和身份水位都能按
29
+ 严格序号写成纯 JSON。会话图片 URL 在 patch 中先变成保留 token,资源闭包缺失会原子拒绝;回放先在
30
+ 写时复制模型上完整验真,并拒绝身份水位倒退,坏尾帧不会污染目标;恢复保留选区与脏状态,
31
+ 但不伪造旧历史。未订阅时普通编辑零日志深拷贝。210 页 / 50MB 基准中,1,000 帧为 316.3KB,
32
+ JSON 往返 1.6ms、回放 269.9ms;`@web-ppt/editor.openEditor()` 可在挂载前透传同一日志。
33
+
34
+ - `renderSlideToSvg` 新增 `RenderOptions.idPrefix`。指定后,同一页与同一前缀会生成
35
+ 逐字节相同的 SVG,供编辑器增量更新和确定性比较使用;省略时仍维持全局唯一 id,
36
+ 主视图与缩略图同时挂载不会发生 defs 冲突。
37
+ - 新增 `renderElementToSvg(element, options) → { markup, defs }`。编辑器可用每元素唯一的
38
+ 稳定 `idPrefix` 只重渲脏元素,并在同一次 DOM 更新中替换它自己的节点与 defs;
39
+ 整页与元素入口复用同一分发和两条文本路径,不存在第二套近似渲染器。
40
+ - 新增无 DOM 的 `renderTextBodyToHtml(text, width, height, options)`。屏幕预览和后续
41
+ contenteditable 覆盖层共用同一份段落、run、CJK 标点、分栏与 autofit 排版;默认输出
42
+ 可反解的编辑标记,并隔离危险链接协议,原生 SVG 文本导出路径保持独立。
43
+ - 新增纯函数 `layoutText(text, width, height, options)`。它与原生 `<text>` 输出共用断行、
44
+ CJK 挤压、分栏、行距及 autofit,并公开段落/run 身份、UTF-16 字符区间与光标停靠点,
45
+ 供 Safari engine 模式命中选区;竖排返回仿射变换,公式保持原子,测量器可注入。
46
+ - `renderTextBodyToHtml` 新增 `{ layout: 'engine' }`:直接把 `layoutText()` 的视觉行与源 UTF-16
47
+ 半开区间序列化为绝对定位、禁止二次断行的可编辑 HTML。`@web-ppt/editor` 的 `textMode: 'svg'`
48
+ 以及 `auto` Safari 降级现在让静态 SVG 与 `contenteditable` 共用这组行盒;硬换行、空 run/段、
49
+ 项目符号、RTL、竖排、分栏、公式和裸 autofit 在进入、输入、提交与退出时保持同一布局。真实
50
+ Chrome 的 2,000 字符完整上屏多轮 p95 为 8.3–17.8ms、行盒最大偏差 0.009px;editor 入口为
51
+ 27.46KB gzip。
52
+ - `parse` 新增完全可选的 `edit` / `keepPackage`:前者保留页与元素的 OOXML 回写锚点、
53
+ 占位符身份,后者通过 `Presentation.package` 保留原始 ZIP 与解压 part。默认解析的
54
+ 对象形状和资源生命周期不变;`dispose()` 会同时释放保留的原包。
55
+ - `.pptx` 与 `.ppt` 的编辑解析会额外保留预设形状的 `preset + adj` 语义(OOXML 含
56
+ 版式/母版继承),并新增纯函数 `resolveGeomPath`;编辑投影改变宽高后可重新求值路径,
57
+ 不再复用解析期烘焙的几何。
58
+ - 新增无 DOM、无框架的 `@web-ppt/edit-core`:把解析结果转换为带稳定身份与分数 z 序的
59
+ 扁平 `EditDoc`,以 `src` / `ovr` 分离源值和用户改动,并通过 `toSlide` 投影回现有
60
+ 渲染 Schema。元素、组祖先和页面缓存按修改路径精确失效;图表、SmartArt、OLE、
61
+ 墨迹与媒体只开放框架级变换,内部派生节点不可误编辑。
62
+ - `@web-ppt/edit-core` 新增无 DOM 的 `Editor`、可 JSON 序列化的 `SetXfrm` 与双向 patch。
63
+ 事务失败整体回滚;撤销/重做恢复选区,同路径改动按 500ms 分组,远端 origin 不进入本地历史,
64
+ 默认保留 200 组 / 8MB。固定种子 200 条命令通过撤销、重做与 JSON 回放全等,500 条非法命令
65
+ 不污染文档;210 页实测 200 组撤销/重做含脏页重渲均约 0.5ms/次,历史约 64KB。
66
+ - 新增按需入口 `@web-ppt/edit-core/xml`:无 DOM 的保留型 XML 树支持 UTF-8 / UTF-16
67
+ 字节回环、限定名与 namespace URI 查询、属性最小改写和 OOXML sequence 有序插入;声明、
68
+ 注释、PI、前缀、属性顺序、自闭合形态及 `AlternateContent` 不被重建。默认编辑模型入口为
69
+ 8.28KB gzip,保存期 XML 入口独立为 7.14KB gzip。
70
+ - 新增按需入口 `@web-ppt/edit-core/opc`:无修改保存复用原始字节;脏 part 定点重压,净条目的
71
+ 本地头、extra field 与压缩流逐字直通。新增/删除 part、重复保存和确定性输出均有契约;zip64、
72
+ 数据描述符、存档注释、加密条目、未知压缩与多磁盘格式会给出原因并整包重压。入口为 4.27KB gzip,
73
+ 210 页 / 50.6MB 修改并序列化 3 页后的实测保存为 84.0ms;保存结果可随文档或独立释放。
74
+ - 新增 M0 自动门禁:29 份固件(含吸附、加密 OOXML、RC4 `.ppt` 与 hardcases)的只读链路和
75
+ 编辑投影分别在独立进程渲染,两条文本路径共 232 对原始 SVG 指纹必须完全一致;
76
+ 210 页基准同时强制检查默认路径零编辑状态、编辑常驻内存增量、文本行盒与提交重渲预算。
77
+ - `@web-ppt/editor` 在编辑模式直接消费浏览器 SVG 命中:组默认选外层,双击 / Escape
78
+ 逐层进退,Alt+点击按 `elementsFromPoint` z 序循环;选择只更新交互层,多视图各自拥有监听器。
79
+ - `@web-ppt/editor` 公开元素本地 / 幻灯片 / 屏幕坐标变换,并为普通、旋转翻转和嵌套组元素绘制
80
+ OBB、8 个缩放柄与旋转柄。手柄尺寸不随 zoom 变化;真实 Chrome 三档缩放的最大矩阵偏差为
81
+ 0.000px,9 个手柄的位置与尺寸最大偏差同为 0.000px,60 元素页完整选择框上屏 p95 为 0.100ms。
82
+ - `@web-ppt/editor` 支持主指针拖动单选与多选:3px 阈值后以 pointer capture 和 rAF 合并幽灵帧,
83
+ 松手只提交一个可撤销 `SetXfrm` 事务;嵌套旋转翻转组按各自父空间换算。取消、丢失捕获、切页、
84
+ view 模式和销毁均不写模型。真实 Chrome 嵌套幽灵偏差 0.000px,60 元素拖动帧 p95 0.100ms。
85
+ - `@web-ppt/editor` 绑定 8 个恒屏幕尺寸缩放柄,支持单选/多选、旋转翻转与嵌套组、Shift 等比、
86
+ Alt 中心缩放及过锚翻面;移动与缩放共用可回收的 pointer capture/rAF 状态机,松手一次提交并可
87
+ 精确保存。真实 Chrome 三档 zoom 的手柄/锚点最大偏差 0.009px,缩放帧 p95 0.100ms;60 个
88
+ 45° 元素的近奇异多选缩放 p95 0.600ms。
89
+ - `@web-ppt/editor` 绑定旋转柄:单选按元素父空间处理自身翻转与多层旋转/翻转组,多选围绕共同 AABB
90
+ 中心同步旋转;角度跨 ±180° 连续累计,Shift 可动态吸附 15°,松手以一个事务精确保存。真实 Chrome
91
+ 三档 zoom 的嵌套/多选幽灵最大偏差为 0.000/0.009px,60 元素旋转帧 p95 0.300ms,可信 pointer
92
+ capture 与撤销门禁通过。
93
+ - `@web-ppt/editor` 的移动手势新增屏幕 6px 吸附:画布中线/边缘、显式页边距、同组兄弟边缘/中线与
94
+ 横纵等距按稳定优先级逐轴裁决;`Ctrl` 可动态关闭,视图也可用 `snapping` / `setSnapping()` 全局切换。
95
+ 对齐线和双向等距箭头只进入 interaction SVG,取消零提交。真实 Chrome 三档 zoom、缩放组和等距线
96
+ 最大偏差均为 0.000px,60 元素吸附帧 p95 0.200ms,可信 pointer capture 与撤销门禁通过;editor
97
+ 发布入口实测为 15.87KB gzip。
98
+
5
99
  ## 0.4.5
6
100
 
7
101
  ### 修复
package/README.md CHANGED
@@ -26,6 +26,9 @@ v.search('keyword'); // → array of matching slide indices
26
26
  await v.exportPng(2); // → Blob
27
27
  ```
28
28
 
29
+ Internal slide links support both click and `Tab`/`Enter`; external links can be intercepted with `v.onLinkClick`.
30
+ `destroy()` removes both navigation listeners.
31
+
29
32
  ## Bring your own UI
30
33
 
31
34
  ```ts
package/dist/index.d.ts CHANGED
@@ -10,6 +10,6 @@ export type { PresentationStateOptions, StateChange } from './state';
10
10
  export { Viewer } from './viewer';
11
11
  export { foreignObjectScalesCorrectly, resetForeignObjectProbe } from './foreign-object';
12
12
  export type { ViewerOptions } from './viewer';
13
- export { autoAdvanceMs, framesFor, morphPairs, playGroup, playTransition, transitionFrames } from './playback';
13
+ export { autoAdvanceMs, framesFor, morphPairs, playGroup, playTransition, playTransitionControlled, transitionFrames, } from './playback';
14
14
  export { groupSteps, hiddenBefore, staticHidden } from '@web-ppt/core';
15
- export type { PlayHandle } from './playback';
15
+ export type { PlayHandle, TransitionPlayHandle } from './playback';
@@ -1,10 +1,12 @@
1
- import type { AnimStep, Slide, Transition } from '@web-ppt/core';
1
+ import { type AnimStep, type Slide, type Transition } from '@web-ppt/core';
2
2
  interface Keyframes {
3
3
  from: Keyframe;
4
4
  to: Keyframe;
5
5
  }
6
6
  export declare function framesFor(step: AnimStep): Keyframes;
7
7
  export interface PlayHandle {
8
+ /** 由本句柄创建的动画;宿主需要精确回收时无需扫描整棵 DOM。 */
9
+ readonly animations: readonly Animation[];
8
10
  cancel(): void;
9
11
  finished: Promise<void>;
10
12
  }
@@ -24,10 +26,16 @@ export declare function morphPairs(outgoing: Element, incoming: Element): {
24
26
  from: DOMRect;
25
27
  to: DOMRect;
26
28
  }[];
29
+ export interface TransitionPlayHandle {
30
+ readonly animations: readonly Animation[];
31
+ readonly finished: Promise<void>;
32
+ cancel(): void;
33
+ }
27
34
  /**
28
35
  * 播放切换:把旧内容截图式地留在下层,新内容在上层按效果进场。
29
36
  * outgoing 为旧内容节点(可为 null),incoming 为新内容节点。
30
37
  */
38
+ export declare function playTransitionControlled(outgoing: HTMLElement | null, incoming: HTMLElement, t: Transition | undefined): TransitionPlayHandle;
31
39
  export declare function playTransition(outgoing: HTMLElement | null, incoming: HTMLElement, t: Transition | undefined): Promise<void>;
32
40
  /** 幻灯片是否配置了自动换片 */
33
41
  export declare function autoAdvanceMs(slide: Slide): number | null;
package/dist/state.d.ts CHANGED
@@ -92,7 +92,7 @@ export declare class PresentationState {
92
92
  text(i: number): string;
93
93
  /** 全文搜索,返回命中的页索引 */
94
94
  search(query: string): number[];
95
- /** 解析内部跳转目标(`slide:3` / `slide:last`)为页索引;外部链接返回 null */
95
+ /** 解析绝对或相对内部跳转;相对动作保留到点击时,页序变化后仍指向正确目标。 */
96
96
  resolveLink(href: string): number | null;
97
97
  destroy(): void;
98
98
  }
@@ -1,59 +1,59 @@
1
- import { hiddenBefore as C, staticHidden as w, groupSteps as M, slideText as S, renderSlideToSvg as v, slideToPng as T } from "@web-ppt/core";
2
- import { groupSteps as R, hiddenBefore as W, staticHidden as G } from "@web-ppt/core";
3
- const h = 1.15;
4
- function $(s) {
5
- switch (s) {
1
+ import { transitionPreferredDirection as E, hiddenBefore as Y, staticHidden as k, groupSteps as z, slideText as L, renderSlideToSvg as S, slideToPng as X } from "@web-ppt/core";
2
+ import { groupSteps as et, hiddenBefore as st, staticHidden as it } from "@web-ppt/core";
3
+ const p = 1.15;
4
+ function D(i) {
5
+ switch (i) {
6
6
  case "l":
7
- return [-100 * h, 0];
7
+ return [-100 * p, 0];
8
8
  case "r":
9
- return [100 * h, 0];
9
+ return [100 * p, 0];
10
10
  case "u":
11
- return [0, -100 * h];
11
+ return [0, -100 * p];
12
12
  case "d":
13
- return [0, 100 * h];
13
+ return [0, 100 * p];
14
14
  case "lu":
15
- return [-100 * h, -100 * h];
15
+ return [-100 * p, -100 * p];
16
16
  case "ru":
17
- return [100 * h, -100 * h];
17
+ return [100 * p, -100 * p];
18
18
  case "ld":
19
- return [-100 * h, 100 * h];
19
+ return [-100 * p, 100 * p];
20
20
  case "rd":
21
- return [100 * h, 100 * h];
21
+ return [100 * p, 100 * p];
22
22
  default:
23
- return [0, 100 * h];
23
+ return [0, 100 * p];
24
24
  }
25
25
  }
26
- function d(s, t) {
27
- const e = t ? 100 : 0;
28
- switch (s) {
26
+ function d(i, e) {
27
+ const t = e ? 100 : 0;
28
+ switch (i) {
29
29
  case "l":
30
- return `inset(0 ${e}% 0 0)`;
30
+ return `inset(0 ${t}% 0 0)`;
31
31
  case "r":
32
- return `inset(0 0 0 ${e}%)`;
32
+ return `inset(0 0 0 ${t}%)`;
33
33
  case "u":
34
- return `inset(0 0 ${e}% 0)`;
34
+ return `inset(0 0 ${t}% 0)`;
35
35
  case "horz":
36
- return `inset(0 ${e / 2}% 0 ${e / 2}%)`;
36
+ return `inset(0 ${t / 2}% 0 ${t / 2}%)`;
37
37
  case "vert":
38
- return `inset(${e / 2}% 0 ${e / 2}% 0)`;
38
+ return `inset(${t / 2}% 0 ${t / 2}% 0)`;
39
39
  default:
40
- return `inset(${e}% 0 0 0)`;
40
+ return `inset(${t}% 0 0 0)`;
41
41
  }
42
42
  }
43
- function A(s) {
44
- switch (s.effect) {
43
+ function T(i) {
44
+ switch (i.effect) {
45
45
  case "appear":
46
46
  return { from: { opacity: 0 }, to: { opacity: 1 } };
47
47
  case "fly": {
48
- const [t, e] = $(s.dir);
48
+ const [e, t] = D(i.dir);
49
49
  return {
50
- from: { opacity: 0, transform: `translate(${t}%, ${e}%)` },
50
+ from: { opacity: 0, transform: `translate(${e}%, ${t}%)` },
51
51
  to: { opacity: 1, transform: "translate(0, 0)" }
52
52
  };
53
53
  }
54
54
  case "zoom":
55
55
  return {
56
- from: { opacity: 0, transform: s.dir === "out" ? "scale(1.6)" : "scale(0.1)" },
56
+ from: { opacity: 0, transform: i.dir === "out" ? "scale(1.6)" : "scale(0.1)" },
57
57
  to: { opacity: 1, transform: "scale(1)" }
58
58
  };
59
59
  case "grow":
@@ -73,8 +73,8 @@ function A(s) {
73
73
  case "split":
74
74
  case "wheel":
75
75
  return {
76
- from: { opacity: 1, clipPath: d(s.dir, !0) },
77
- to: { opacity: 1, clipPath: d(s.dir, !1) }
76
+ from: { opacity: 1, clipPath: d(i.dir, !0) },
77
+ to: { opacity: 1, clipPath: d(i.dir, !1) }
78
78
  };
79
79
  case "dissolve":
80
80
  case "fade":
@@ -83,12 +83,12 @@ function A(s) {
83
83
  return { from: { opacity: 0 }, to: { opacity: 1 } };
84
84
  }
85
85
  }
86
- function k(s) {
87
- const t = A({ ...s });
88
- return { from: t.to, to: t.from };
86
+ function N(i) {
87
+ const e = T({ ...i });
88
+ return { from: e.to, to: e.from };
89
89
  }
90
- function E(s) {
91
- switch (s.effect) {
90
+ function V(i) {
91
+ switch (i.effect) {
92
92
  case "spin":
93
93
  return { from: { transform: "rotate(0)" }, to: { transform: "rotate(360deg)" } };
94
94
  case "grow":
@@ -97,219 +97,300 @@ function E(s) {
97
97
  return { from: { opacity: 1 }, to: { opacity: 0.35 } };
98
98
  }
99
99
  }
100
- function z(s) {
101
- return s.kind === "exit" ? k(s) : s.kind === "emphasis" ? E(s) : s.kind === "motion" ? { from: { opacity: 1 }, to: { opacity: 1 } } : A(s);
100
+ function F(i) {
101
+ return i.kind === "exit" ? N(i) : i.kind === "emphasis" ? V(i) : i.kind === "motion" ? { from: { opacity: 1 }, to: { opacity: 1 } } : T(i);
102
102
  }
103
- function Y(s, t) {
104
- var n, r;
105
- const e = [];
106
- let a = 0;
107
- for (const i of t) {
108
- const c = s.querySelector(`[data-el="${i.target}"]`);
109
- if (!c) continue;
110
- const o = i.trigger === "afterPrev" ? a + i.delayMs : i.delayMs;
111
- i.trigger === "afterPrev" ? a = o + i.durationMs : a = Math.max(a, o + i.durationMs);
112
- const { from: l, to: p } = z(i);
113
- c.style.visibility = "visible", i.kind === "entrance" && (c.style.opacity = "");
114
- const f = (n = i.motionPath) != null && n.length ? i.motionPath.map(([u, y]) => ({ transform: `translate(${u}px, ${y}px)` })) : [l, p];
103
+ function q(i, e) {
104
+ var s, r, c;
105
+ const t = [];
106
+ let n = 0, o = 0;
107
+ for (const a of e) {
108
+ const l = a.trigger === "afterPrev" ? o + a.delayMs : a.trigger === "withPrev" ? n + a.delayMs : a.delayMs;
109
+ n = l, o = l + a.durationMs;
110
+ const h = i.querySelector(`[data-el="${a.target}"]`);
111
+ if (!h) continue;
112
+ const { from: b, to: g } = F(a);
113
+ h.style.visibility = "visible", a.kind === "entrance" && (h.style.opacity = "");
114
+ const f = (s = a.motionPath) == null ? void 0 : s.reduce((u, [m, w], A, P) => (A === 0 ? u.push(0) : u.push(u[A - 1] + Math.hypot(m - P[A - 1][0], w - P[A - 1][1])), u), []), x = (f == null ? void 0 : f[f.length - 1]) ?? 0, $ = (r = a.motionPath) != null && r.length ? a.motionPath.map(([u, m], w) => ({
115
+ transform: `translate(${u}px, ${m}px)`,
116
+ offset: x > 0 ? f[w] / x : w / (a.motionPath.length - 1)
117
+ })) : [b, g];
115
118
  try {
116
- const u = c.animate(f, {
117
- duration: i.durationMs,
118
- delay: o,
119
+ const u = (c = a.motionPath) != null && c.length ? "linear" : a.effect === "appear" && a.kind === "entrance" ? "steps(1, start)" : a.effect === "appear" && a.kind === "exit" ? "steps(1, end)" : "cubic-bezier(0.25, 0.46, 0.45, 0.94)", m = h.animate($, {
120
+ duration: a.durationMs,
121
+ delay: l,
119
122
  // 路径动画在 PowerPoint 里是匀速,不能套入场用的缓动
120
- easing: (r = i.motionPath) != null && r.length ? "linear" : "cubic-bezier(0.25, 0.46, 0.45, 0.94)",
123
+ easing: u,
121
124
  fill: "both"
122
125
  });
123
- i.kind === "exit" && u.finished.then(() => {
124
- c.style.visibility = "hidden";
126
+ a.kind === "exit" && m.finished.then(() => {
127
+ h.style.visibility = "hidden";
125
128
  }).catch(() => {
126
- }), e.push(u);
129
+ }), t.push(m);
127
130
  } catch {
128
- Object.assign(c.style, f[f.length - 1] ?? p);
131
+ Object.assign(h.style, $[$.length - 1] ?? g);
129
132
  }
130
133
  }
131
134
  return {
132
- cancel: () => e.forEach((i) => {
135
+ animations: t,
136
+ cancel: () => t.forEach((a) => {
133
137
  try {
134
- i.finish();
138
+ a.finish();
135
139
  } catch {
136
140
  }
137
141
  }),
138
- finished: Promise.all(e.map((i) => i.finished.catch(() => {
142
+ finished: Promise.all(t.map((a) => a.finished.catch(() => {
139
143
  }))).then(() => {
140
144
  })
141
145
  };
142
146
  }
143
- function x(s, t) {
144
- const e = s.dir ?? "", a = (r, i) => `translate(${r}, ${i})`, n = () => e.includes("l") ? ["-100%", "0"] : e.includes("r") ? ["100%", "0"] : e.includes("u") ? ["0", "-100%"] : ["0", "100%"];
145
- switch (s.type) {
147
+ function M(i, e) {
148
+ const t = i.dir ?? E(i.type) ?? "", n = (s, r) => `translate(${s}, ${r})`, o = () => {
149
+ const s = {
150
+ l: ["-100%", "0"],
151
+ r: ["100%", "0"],
152
+ u: ["0", "-100%"],
153
+ d: ["0", "100%"],
154
+ lu: ["-100%", "-100%"],
155
+ ld: ["-100%", "100%"],
156
+ ru: ["100%", "-100%"],
157
+ rd: ["100%", "100%"]
158
+ };
159
+ return s[t] ?? s.d;
160
+ };
161
+ switch (i.type) {
146
162
  case "cut":
147
- return t ? [{ opacity: 1 }, { opacity: 1 }] : [{ opacity: 0 }, { opacity: 0 }];
163
+ return e ? [{ opacity: 1 }, { opacity: 1 }] : [
164
+ { opacity: 1, offset: 0 },
165
+ { opacity: 1, offset: 0.49 },
166
+ { opacity: 0, offset: 0.5 },
167
+ { opacity: 0, offset: 1 }
168
+ ];
148
169
  case "push":
149
170
  case "pull": {
150
- const [r, i] = n(), c = s.type === "pull" ? -1 : 1, o = (l) => l === "0" ? "0" : `${-parseFloat(l) * c}%`;
151
- return t ? [{ transform: a(c > 0 ? r : o(r), c > 0 ? i : o(i)) }, { transform: "translate(0,0)" }] : [{ transform: "translate(0,0)" }, { transform: a(o(r), o(i)) }];
171
+ const [s, r] = o(), c = i.type === "pull" ? -1 : 1, a = (l) => l === "0" ? "0" : `${-parseFloat(l) * c}%`;
172
+ return e ? [{ transform: n(c > 0 ? s : a(s), c > 0 ? r : a(r)) }, { transform: "translate(0,0)" }] : [{ transform: "translate(0,0)" }, { transform: n(a(s), a(r)) }];
152
173
  }
153
174
  case "cover": {
154
- const [r, i] = n();
155
- return t ? [{ transform: a(r, i) }, { transform: "translate(0,0)" }] : [{ opacity: 1 }, { opacity: 1 }];
175
+ const [s, r] = o();
176
+ return e ? [{ transform: n(s, r) }, { transform: "translate(0,0)" }] : [{ opacity: 1 }, { opacity: 1 }];
156
177
  }
157
178
  case "wipe": {
158
- const r = d(e || "d", !0), i = d(e || "d", !1);
159
- return t ? [{ clipPath: r }, { clipPath: i }] : [{ opacity: 1 }, { opacity: 1 }];
179
+ const s = d(t || "d", !0), r = d(t || "d", !1);
180
+ return e ? [{ clipPath: s }, { clipPath: r }] : [{ opacity: 1 }, { opacity: 1 }];
160
181
  }
161
182
  case "split": {
162
- const r = e.startsWith("vert"), i = e.endsWith("out"), c = r ? "inset(0 50% 0 50%)" : "inset(50% 0 50% 0)", o = "inset(0 0 0 0)";
163
- return t ? i ? [{ clipPath: c }, { clipPath: o }] : [{ clipPath: o }, { clipPath: o }] : [{ clipPath: o }, { clipPath: c }];
183
+ const s = t.startsWith("vert"), r = t.endsWith("out"), c = s ? "inset(0 50% 0 50%)" : "inset(50% 0 50% 0)", a = "inset(0 0 0 0)";
184
+ return e ? r ? [{ clipPath: c }, { clipPath: a }] : [{ clipPath: a }, { clipPath: a }] : [{ clipPath: a }, { clipPath: c }];
164
185
  }
165
186
  case "zoom":
166
- return t ? [{ opacity: 0, transform: e === "out" ? "scale(1.4)" : "scale(0.6)" }, { opacity: 1, transform: "scale(1)" }] : [{ opacity: 1 }, { opacity: 0 }];
187
+ return e ? [{ opacity: 0, transform: t === "out" ? "scale(1.4)" : "scale(0.6)" }, { opacity: 1, transform: "scale(1)" }] : [{ opacity: 1 }, { opacity: 0 }];
167
188
  case "wheel":
168
189
  case "circle":
169
190
  case "diamond":
170
191
  case "plus":
171
192
  case "wedge":
172
- return t ? [{ opacity: 0, transform: "scale(0.7)" }, { opacity: 1, transform: "scale(1)" }] : [{ opacity: 1 }, { opacity: 0 }];
173
- case "ripple":
174
- return t ? [{ clipPath: "circle(0% at 50% 50%)" }, { clipPath: "circle(75% at 50% 50%)" }] : [{ opacity: 1 }, { opacity: 1 }];
175
- case "vortex":
176
- return t ? [{ opacity: 0, transform: "scale(0.2) rotate(-180deg)" }, { opacity: 1, transform: "none" }] : [{ opacity: 1 }, { opacity: 0 }];
193
+ return e ? [{ opacity: 0, transform: "scale(0.7)" }, { opacity: 1, transform: "scale(1)" }] : [{ opacity: 1 }, { opacity: 0 }];
194
+ case "ripple": {
195
+ const s = {
196
+ lu: "0% 0%",
197
+ ld: "0% 100%",
198
+ ru: "100% 0%",
199
+ rd: "100% 100%",
200
+ center: "50% 50%"
201
+ }, r = s[t] ?? s.center;
202
+ return e ? [{ clipPath: `circle(0% at ${r})` }, { clipPath: `circle(145% at ${r})` }] : [{ opacity: 1 }, { opacity: 1 }];
203
+ }
204
+ case "vortex": {
205
+ const [s, r] = o(), c = t === "r" || t === "d" ? 180 : -180;
206
+ return e ? [{ opacity: 0, transformOrigin: `${s === "0" ? "50%" : s.startsWith("-") ? "0%" : "100%"} ${r === "0" ? "50%" : r.startsWith("-") ? "0%" : "100%"}`, transform: `scale(0.2) rotate(${c}deg)` }, { opacity: 1, transform: "none" }] : [{ opacity: 1 }, { opacity: 0 }];
207
+ }
177
208
  case "honeycomb":
178
- return t ? [{ opacity: 0, transform: "scale(0.85) rotate(-6deg)" }, { opacity: 1, transform: "none" }] : [{ opacity: 1, transform: "none" }, { opacity: 0, transform: "scale(1.1)" }];
209
+ return e ? [{ opacity: 0, transform: "scale(0.85) rotate(-6deg)" }, { opacity: 1, transform: "none" }] : [{ opacity: 1, transform: "none" }, { opacity: 0, transform: "scale(1.1)" }];
179
210
  case "glitter":
180
- return t ? [{ opacity: 0, clipPath: d(e || "l", !0) }, { opacity: 1, clipPath: d(e || "l", !1) }] : [{ opacity: 1 }, { opacity: 0 }];
181
- case "shred":
182
- return t ? [{ opacity: 0, transform: "scale(1.06)" }, { opacity: 1, transform: "none" }] : [{ opacity: 1, transform: "none" }, { opacity: 0, transform: "scaleY(0.9)" }];
211
+ return e ? [{ opacity: 0, clipPath: d(t || "l", !0) }, { opacity: 1, clipPath: d(t || "l", !1) }] : [{ opacity: 1 }, { opacity: 0 }];
212
+ case "shred": {
213
+ const s = t === "out";
214
+ return e ? [{ opacity: 0, transform: s ? "scale(1.35) rotate(5deg)" : "scale(0.72) rotate(-5deg)" }, { opacity: 1, transform: "none" }] : [{ opacity: 1, transform: "none" }, { opacity: 0, transform: s ? "scaleY(0.72)" : "scaleY(1.2)" }];
215
+ }
183
216
  case "flash":
184
- return t ? [{ opacity: 0, filter: "brightness(3)" }, { opacity: 1, filter: "brightness(1)" }] : [{ opacity: 1, filter: "brightness(1)" }, { opacity: 0, filter: "brightness(3)" }];
217
+ return e ? [{ opacity: 0, filter: "brightness(3)" }, { opacity: 1, filter: "brightness(1)" }] : [{ opacity: 1, filter: "brightness(1)" }, { opacity: 0, filter: "brightness(3)" }];
185
218
  case "reveal": {
186
- const [r, i] = n();
187
- return t ? [{ opacity: 0, transform: a(r, i) }, { opacity: 1, transform: "translate(0,0)" }] : [{ opacity: 1 }, { opacity: 0 }];
219
+ const [s, r] = o();
220
+ return e ? [{ opacity: 0, transform: n(s, r) }, { opacity: 1, transform: "translate(0,0)" }] : [{ opacity: 1 }, { opacity: 0 }];
188
221
  }
189
222
  case "wheelReverse":
190
- return t ? [{ opacity: 0, transform: "scale(1.3) rotate(20deg)" }, { opacity: 1, transform: "none" }] : [{ opacity: 1 }, { opacity: 0 }];
223
+ return e ? [{ opacity: 0, transform: "scale(1.3) rotate(20deg)" }, { opacity: 1, transform: "none" }] : [{ opacity: 1 }, { opacity: 0 }];
191
224
  case "doors":
192
- return t ? [{ clipPath: "inset(0 50% 0 50%)" }, { clipPath: "inset(0 0 0 0)" }] : [{ opacity: 1 }, { opacity: 1 }];
225
+ return e ? [{ clipPath: t === "horz" ? "inset(50% 0 50% 0)" : "inset(0 50% 0 50%)" }, { clipPath: "inset(0 0 0 0)" }] : [{ opacity: 1 }, { opacity: 1 }];
193
226
  case "window":
194
- return t ? [{ clipPath: "inset(50% 0 50% 0)" }, { clipPath: "inset(0 0 0 0)" }] : [{ opacity: 1 }, { opacity: 1 }];
227
+ return e ? [{ clipPath: t === "horz" ? "inset(0 50% 0 50%)" : "inset(50% 0 50% 0)" }, { clipPath: "inset(0 0 0 0)" }] : [{ opacity: 1 }, { opacity: 1 }];
195
228
  case "switch":
196
- case "flip":
197
- case "prism": {
198
- const r = s.type === "flip" ? -1 : 1;
199
- return t ? [
200
- { opacity: 0, transform: `perspective(1400px) rotateY(${r * -80}deg)` },
229
+ case "flip": {
230
+ const s = (i.type === "flip" ? -1 : 1) * (t === "r" ? -1 : 1);
231
+ return e ? [
232
+ { opacity: 0, transform: `perspective(1400px) rotateY(${s * -80}deg)` },
201
233
  { opacity: 1, transform: "perspective(1400px) rotateY(0)" }
202
234
  ] : [
203
235
  { opacity: 1, transform: "perspective(1400px) rotateY(0)" },
204
- { opacity: 0, transform: `perspective(1400px) rotateY(${r * 80}deg)` }
236
+ { opacity: 0, transform: `perspective(1400px) rotateY(${s * 80}deg)` }
205
237
  ];
206
238
  }
207
- case "ferris":
208
- return t ? [
209
- { opacity: 0, transform: "perspective(1400px) rotateX(70deg)" },
239
+ case "prism": {
240
+ const s = t === "u" || t === "d", r = t === "r" || t === "d" ? -1 : 1, c = (a) => s ? `rotateX(${a}deg)` : `rotateY(${a}deg)`;
241
+ return e ? [
242
+ { opacity: 0, transform: `perspective(1400px) ${c(r * -70)}` },
243
+ { opacity: 1, transform: `perspective(1400px) ${c(0)}` }
244
+ ] : [
245
+ { opacity: 1, transform: `perspective(1400px) ${c(0)}` },
246
+ { opacity: 0, transform: `perspective(1400px) ${c(r * 70)}` }
247
+ ];
248
+ }
249
+ case "ferris": {
250
+ const s = t === "r" ? -1 : 1;
251
+ return e ? [
252
+ { opacity: 0, transform: `perspective(1400px) rotateX(${s * 70}deg)` },
210
253
  { opacity: 1, transform: "perspective(1400px) rotateX(0)" }
211
254
  ] : [
212
255
  { opacity: 1, transform: "perspective(1400px) rotateX(0)" },
213
- { opacity: 0, transform: "perspective(1400px) rotateX(-70deg)" }
256
+ { opacity: 0, transform: `perspective(1400px) rotateX(${s * -70}deg)` }
214
257
  ];
258
+ }
215
259
  case "gallery":
216
260
  case "conveyor": {
217
- const r = s.type === "conveyor" ? 25 : 12, i = e.includes("l") ? "-100%" : "100%", c = e.includes("l") ? "100%" : "-100%";
218
- return t ? [
219
- { transform: `perspective(1600px) translateX(${i}) rotateY(${r}deg)` },
261
+ const s = i.type === "conveyor" ? 25 : 12, r = t.includes("l") ? "-100%" : "100%", c = t.includes("l") ? "100%" : "-100%";
262
+ return e ? [
263
+ { transform: `perspective(1600px) translateX(${r}) rotateY(${s}deg)` },
220
264
  { transform: "perspective(1600px) translateX(0) rotateY(0)" }
221
265
  ] : [
222
266
  { transform: "perspective(1600px) translateX(0) rotateY(0)" },
223
- { transform: `perspective(1600px) translateX(${c}) rotateY(${-r}deg)` }
267
+ { transform: `perspective(1600px) translateX(${c}) rotateY(${-s}deg)` }
224
268
  ];
225
269
  }
226
270
  case "pan": {
227
- const [r, i] = n(), c = (o) => o === "0" ? "0" : `${-parseFloat(o)}%`;
228
- return t ? [{ transform: a(r, i) }, { transform: "translate(0,0)" }] : [{ transform: "translate(0,0)" }, { transform: a(c(r), c(i)) }];
271
+ const [s, r] = o(), c = (a) => a === "0" ? "0" : `${-parseFloat(a)}%`;
272
+ return e ? [{ transform: n(s, r) }, { transform: "translate(0,0)" }] : [{ transform: "translate(0,0)" }, { transform: n(c(s), c(r)) }];
229
273
  }
230
274
  case "flythrough":
231
- return t ? [{ opacity: 0, transform: "perspective(1000px) scale(0.15)" }, { opacity: 1, transform: "none" }] : [{ opacity: 1, transform: "none" }, { opacity: 0, transform: "perspective(1000px) scale(2.2)" }];
232
- case "warp":
233
- return t ? [{ opacity: 0, transform: "skewX(-18deg) scale(1.15)" }, { opacity: 1, transform: "none" }] : [{ opacity: 1, transform: "none" }, { opacity: 0, transform: "skewX(18deg) scale(0.9)" }];
275
+ return e ? [{ opacity: 0, transform: `perspective(1000px) scale(${t === "out" ? 2.2 : 0.15})` }, { opacity: 1, transform: "none" }] : [{ opacity: 1, transform: "none" }, { opacity: 0, transform: `perspective(1000px) scale(${t === "out" ? 0.15 : 2.2})` }];
276
+ case "warp": {
277
+ const s = t === "out";
278
+ return e ? [{ opacity: 0, transform: s ? "skewX(-18deg) scale(1.35)" : "skewX(18deg) scale(0.65)" }, { opacity: 1, transform: "none" }] : [{ opacity: 1, transform: "none" }, { opacity: 0, transform: s ? "skewX(18deg) scale(0.65)" : "skewX(-18deg) scale(1.35)" }];
279
+ }
234
280
  case "blinds":
235
281
  case "checker":
282
+ case "randomBar": {
283
+ const s = d(t, !0);
284
+ return e ? [{ opacity: 0.2, clipPath: s }, { opacity: 1, clipPath: d(t, !1) }] : [{ opacity: 1 }, { opacity: 0 }];
285
+ }
236
286
  case "comb":
237
- case "randomBar":
238
- case "strips":
287
+ return e ? [{ opacity: 0, transform: t === "vert" ? "translateY(-100%)" : "translateX(-100%)" }, { opacity: 1, transform: "translate(0,0)" }] : [{ opacity: 1 }, { opacity: 0 }];
288
+ case "strips": {
289
+ const s = {
290
+ lu: "0% 0%",
291
+ ld: "0% 100%",
292
+ ru: "100% 0%",
293
+ rd: "100% 100%"
294
+ }, r = s[t] ?? s.rd;
295
+ return e ? [{ opacity: 0, clipPath: `circle(0% at ${r})` }, { opacity: 1, clipPath: `circle(145% at ${r})` }] : [{ opacity: 1 }, { opacity: 0 }];
296
+ }
239
297
  case "newsflash":
240
298
  case "dissolve":
241
299
  case "fade":
242
300
  case "morph":
243
301
  default:
244
- return t ? [{ opacity: 0 }, { opacity: 1 }] : [{ opacity: 1 }, { opacity: 0 }];
302
+ return e ? [{ opacity: 0 }, { opacity: 1 }] : [{ opacity: 1 }, { opacity: 0 }];
245
303
  }
246
304
  }
247
- function b(s, t) {
248
- const e = Promise.all(s.map((a) => a.catch(() => {
305
+ function H(i, e) {
306
+ const t = Promise.all(i.map((n) => n.catch(() => {
249
307
  }))).then(() => {
250
308
  });
251
- return typeof setTimeout != "function" ? e : Promise.race([e, new Promise((a) => setTimeout(a, t + 250))]);
309
+ return typeof setTimeout != "function" ? t : Promise.race([t, new Promise((n) => setTimeout(n, e + 250))]);
252
310
  }
253
- function L(s, t) {
254
- const e = /* @__PURE__ */ new Map();
255
- s.querySelectorAll("[data-el]").forEach((n) => {
256
- const r = n.getBoundingClientRect();
257
- (r.width > 0 || r.height > 0) && e.set(n.getAttribute("data-el") ?? "", r);
311
+ function O(i, e) {
312
+ const t = /* @__PURE__ */ new Map();
313
+ i.querySelectorAll("[data-el]").forEach((o) => {
314
+ const s = o.getBoundingClientRect();
315
+ (s.width > 0 || s.height > 0) && t.set(o.getAttribute("data-el") ?? "", s);
258
316
  });
259
- const a = [];
260
- return t.querySelectorAll("[data-el]").forEach((n) => {
261
- const r = e.get(n.getAttribute("data-el") ?? "");
262
- if (!r) return;
263
- const i = n.getBoundingClientRect();
264
- i.width <= 0 && i.height <= 0 || a.push({ node: n, from: r, to: i });
265
- }), a;
317
+ const n = [];
318
+ return e.querySelectorAll("[data-el]").forEach((o) => {
319
+ const s = t.get(o.getAttribute("data-el") ?? "");
320
+ if (!s) return;
321
+ const r = o.getBoundingClientRect();
322
+ r.width <= 0 && r.height <= 0 || n.push({ node: o, from: s, to: r });
323
+ }), n;
324
+ }
325
+ function v(i, e, t) {
326
+ let n = !1;
327
+ const o = () => {
328
+ if (!n) {
329
+ n = !0;
330
+ for (const r of i)
331
+ try {
332
+ r.cancel();
333
+ } catch {
334
+ }
335
+ e == null || e.remove();
336
+ }
337
+ }, s = H(i.map((r) => r.finished), t).then(o);
338
+ return { animations: i, finished: s, cancel: o };
266
339
  }
267
- function X(s, t, e) {
268
- if (!s) return Promise.resolve();
269
- const a = { duration: e.durationMs, easing: "ease-in-out", fill: "both" }, n = L(s, t), r = new Set(n.map((o) => o.node.getAttribute("data-el"))), i = [], c = (o, l) => {
340
+ function j(i, e, t) {
341
+ if (!i) return v([], null, t.durationMs);
342
+ const n = { duration: t.durationMs, easing: "ease-in-out", fill: "both" }, o = O(i, e), s = new Set(o.map((a) => a.node.getAttribute("data-el"))), r = [], c = (a, l) => {
270
343
  try {
271
- i.push(o.animate(l, a).finished);
344
+ r.push(a.animate(l, n));
272
345
  } catch {
273
346
  }
274
347
  };
275
- for (const { node: o, from: l, to: p } of n) {
276
- const f = l.left - p.left, u = l.top - p.top, y = p.width > 0 ? l.width / p.width : 1, P = p.height > 0 ? l.height / p.height : 1;
277
- c(o, [
278
- { transformOrigin: "left top", transform: `translate(${f}px, ${u}px) scale(${y}, ${P})` },
348
+ for (const { node: a, from: l, to: h } of o) {
349
+ const b = l.left - h.left, g = l.top - h.top, f = h.width > 0 ? l.width / h.width : 1, x = h.height > 0 ? l.height / h.height : 1;
350
+ c(a, [
351
+ { transformOrigin: "left top", transform: `translate(${b}px, ${g}px) scale(${f}, ${x})` },
279
352
  { transformOrigin: "left top", transform: "none" }
280
353
  ]);
281
354
  }
282
- return s.querySelectorAll("[data-el]").forEach((o) => {
283
- const l = r.has(o.getAttribute("data-el"));
284
- c(o, l ? [{ opacity: 0 }, { opacity: 0 }] : [{ opacity: 1 }, { opacity: 0 }]);
285
- }), t.querySelectorAll("[data-el]").forEach((o) => {
286
- r.has(o.getAttribute("data-el")) || c(o, [{ opacity: 0 }, { opacity: 1 }]);
287
- }), b(i, e.durationMs).then(() => {
288
- s.remove();
289
- });
355
+ return i.querySelectorAll("[data-el]").forEach((a) => {
356
+ const l = s.has(a.getAttribute("data-el"));
357
+ c(a, l ? [{ opacity: 0 }, { opacity: 0 }] : [{ opacity: 1 }, { opacity: 0 }]);
358
+ }), e.querySelectorAll("[data-el]").forEach((a) => {
359
+ s.has(a.getAttribute("data-el")) || c(a, [{ opacity: 0 }, { opacity: 1 }]);
360
+ }), v(r, i, t.durationMs);
290
361
  }
291
- function N(s, t, e) {
292
- if (!e || e.type === "none")
293
- return s == null || s.remove(), Promise.resolve();
294
- if (e.type === "morph" && s) return X(s, t, e);
295
- const a = { duration: e.durationMs, easing: "ease-in-out", fill: "both" }, n = [];
362
+ function B(i, e, t) {
363
+ if (!t || t.type === "none")
364
+ return i == null || i.remove(), v([], null, 0);
365
+ if (t.type === "morph" && i) return j(i, e, t);
366
+ const n = { duration: t.durationMs, easing: "ease-in-out", fill: "both" }, o = [];
296
367
  try {
297
- n.push(t.animate(x(e, !0), a).finished), s && n.push(s.animate(x(e, !1), a).finished);
368
+ o.push(e.animate(M(t, !0), n)), i && o.push(i.animate(M(t, !1), n));
298
369
  } catch {
299
- return s == null || s.remove(), Promise.resolve();
370
+ for (const s of o) {
371
+ s.finished.catch(() => {
372
+ });
373
+ try {
374
+ s.cancel();
375
+ } catch {
376
+ }
377
+ }
378
+ return i == null || i.remove(), v([], null, 0);
300
379
  }
301
- return b(n, e.durationMs).then(() => {
302
- s == null || s.remove();
303
- });
380
+ return v(o, i, t.durationMs);
381
+ }
382
+ function W(i, e, t) {
383
+ return B(i, e, t).finished;
304
384
  }
305
- function V(s) {
306
- var e;
307
- const t = (e = s.transition) == null ? void 0 : e.advanceAfterMs;
308
- return t !== void 0 && t > 0 ? t : null;
385
+ function Z(i) {
386
+ var t;
387
+ const e = (t = i.transition) == null ? void 0 : t.advanceAfterMs;
388
+ return e !== void 0 && e >= 0 ? e : null;
309
389
  }
310
- class F {
311
- constructor(t, e = {}) {
312
- this.presentation = t, this.options = e, this.idx = 0, this.zoomValue = 1, this.groups = [], this.cursor = 0, this.listeners = /* @__PURE__ */ new Set(), this.autoTimer = null, this.textCache = /* @__PURE__ */ new Map(), this.idx = this.clamp(e.index ?? 0), this.loadAnimations();
390
+ const R = 2147483647;
391
+ class _ {
392
+ constructor(e, t = {}) {
393
+ this.presentation = e, this.options = t, this.idx = 0, this.zoomValue = 1, this.groups = [], this.cursor = 0, this.listeners = /* @__PURE__ */ new Set(), this.autoTimer = null, this.textCache = /* @__PURE__ */ new Map(), this.idx = this.clamp(t.index ?? 0), this.loadAnimations();
313
394
  }
314
395
  // ---------------- 只读状态 ----------------
315
396
  get index() {
@@ -343,64 +424,64 @@ class F {
343
424
  * 一起画出来就是几帧叠在一起(见 core 的 staticHidden)。
344
425
  */
345
426
  get hiddenElementIds() {
346
- return this.animate ? C(this.groups, this.cursor) : w(this.slide);
427
+ return this.animate ? Y(this.groups, this.cursor) : k(this.slide);
347
428
  }
348
429
  /** 本页的自动换片延迟(毫秒),未配置则为 null */
349
430
  get autoAdvanceDelay() {
350
- return this.options.autoAdvance ? V(this.slide) : null;
431
+ return this.options.autoAdvance ? Z(this.slide) : null;
351
432
  }
352
433
  // ---------------- 订阅 ----------------
353
- subscribe(t) {
354
- return this.listeners.add(t), () => this.listeners.delete(t);
434
+ subscribe(e) {
435
+ return this.listeners.add(e), () => this.listeners.delete(e);
355
436
  }
356
- emit(t) {
357
- for (const e of this.listeners) e(t);
437
+ emit(e) {
438
+ for (const t of this.listeners) t(e);
358
439
  }
359
440
  // ---------------- 导航 ----------------
360
- clamp(t) {
361
- return Math.max(0, Math.min(this.count - 1, t));
441
+ clamp(e) {
442
+ return Math.max(0, Math.min(this.count - 1, e));
362
443
  }
363
- visible(t) {
364
- var e;
365
- return !this.options.skipHidden || !((e = this.presentation.slides[t]) != null && e.hidden);
444
+ visible(e) {
445
+ var t;
446
+ return !this.options.skipHidden || !((t = this.presentation.slides[e]) != null && t.hidden);
366
447
  }
367
448
  /**
368
449
  * 跳转到指定页。返回是否真的换了页。
369
450
  * 前进时带上目标页的切换效果;回退不播切换,避免观感错乱。
370
451
  */
371
- goTo(t, e = "forward") {
372
- const a = this.clamp(t);
373
- if (a === this.idx) return !1;
374
- const n = this.idx;
375
- return this.idx = a, this.loadAnimations(), this.emit({
452
+ goTo(e, t = "forward") {
453
+ const n = this.clamp(e);
454
+ if (n === this.idx) return !1;
455
+ const o = this.idx;
456
+ return this.idx = n, this.loadAnimations(), this.emit({
376
457
  type: "slide",
377
- index: a,
378
- previous: n,
379
- transition: e === "forward" && this.animate ? this.slide.transition : void 0
458
+ index: n,
459
+ previous: o,
460
+ transition: t === "forward" && this.animate ? this.slide.transition : void 0
380
461
  }), !0;
381
462
  }
382
463
  /** 有待播动画时先播动画,否则翻页 */
383
464
  next() {
384
465
  if (this.playNextAnimation()) return;
385
- let t = this.idx + 1;
386
- for (; t < this.count && !this.visible(t); ) t++;
387
- t < this.count && this.goTo(t);
466
+ let e = this.idx + 1;
467
+ for (; e < this.count && !this.visible(e); ) e++;
468
+ e < this.count && this.goTo(e);
388
469
  }
389
470
  prev() {
390
- let t = this.idx - 1;
391
- for (; t >= 0 && !this.visible(t); ) t--;
392
- t >= 0 && this.goTo(t, "backward");
471
+ let e = this.idx - 1;
472
+ for (; e >= 0 && !this.visible(e); ) e--;
473
+ e >= 0 && this.goTo(e, "backward");
393
474
  }
394
- setZoom(t) {
395
- const e = Math.max(0.1, Math.min(8, t));
396
- e !== this.zoomValue && (this.zoomValue = e, this.emit({ type: "zoom", zoom: e }));
475
+ setZoom(e) {
476
+ const t = Math.max(0.1, Math.min(8, e));
477
+ t !== this.zoomValue && (this.zoomValue = t, this.emit({ type: "zoom", zoom: t }));
397
478
  }
398
- setAnimate(t) {
399
- this.options.animate !== t && (this.options = { ...this.options, animate: t }, this.loadAnimations(), this.emit({ type: "animation", done: this.cursor, total: this.groups.length, group: null }));
479
+ setAnimate(e) {
480
+ this.options.animate !== e && (this.options = { ...this.options, animate: e }, this.loadAnimations(), this.emit({ type: "animation", done: this.cursor, total: this.groups.length, group: null }));
400
481
  }
401
482
  // ---------------- 动画 ----------------
402
483
  loadAnimations() {
403
- this.groups = this.animate ? M(this.slide.animations) : [], this.cursor = 0;
484
+ this.groups = this.animate ? z(this.slide.animations) : [], this.cursor = 0;
404
485
  }
405
486
  /**
406
487
  * 推进一批动画。返回应播放的这一批;没有待播批次时返回 null。
@@ -408,8 +489,8 @@ class F {
408
489
  */
409
490
  playNextAnimation() {
410
491
  if (!this.hasPendingAnimation) return null;
411
- const t = this.groups[this.cursor];
412
- return this.cursor++, this.emit({ type: "animation", done: this.cursor, total: this.groups.length, group: t }), t;
492
+ const e = this.groups[this.cursor];
493
+ return this.cursor++, this.emit({ type: "animation", done: this.cursor, total: this.groups.length, group: e }), e;
413
494
  }
414
495
  /** 直接跳到本页动画终态 */
415
496
  finishAnimations() {
@@ -419,75 +500,87 @@ class F {
419
500
  /** 启动自动换片计时;返回取消函数。由 UI 层在每次换页后调用。 */
420
501
  scheduleAutoAdvance() {
421
502
  this.cancelAutoAdvance();
422
- const t = this.autoAdvanceDelay;
423
- return t === null ? () => {
424
- } : (this.autoTimer = setTimeout(() => {
425
- this.autoTimer = null, this.next();
426
- }, t), () => this.cancelAutoAdvance());
503
+ const e = this.autoAdvanceDelay;
504
+ if (e === null) return () => {
505
+ };
506
+ let t = e;
507
+ const n = () => {
508
+ const o = Math.min(t, R);
509
+ this.autoTimer = setTimeout(() => {
510
+ this.autoTimer = null, t -= o, t > 0 ? n() : this.next();
511
+ }, o);
512
+ };
513
+ return n(), () => this.cancelAutoAdvance();
427
514
  }
428
515
  cancelAutoAdvance() {
429
516
  this.autoTimer !== null && (clearTimeout(this.autoTimer), this.autoTimer = null);
430
517
  }
431
518
  // ---------------- 文本 / 搜索 ----------------
432
- text(t) {
433
- let e = this.textCache.get(t);
434
- return e === void 0 && (e = S(this.presentation.slides[t]), this.textCache.set(t, e)), e;
519
+ text(e) {
520
+ let t = this.textCache.get(e);
521
+ return t === void 0 && (t = L(this.presentation.slides[e]), this.textCache.set(e, t)), t;
435
522
  }
436
523
  /** 全文搜索,返回命中的页索引 */
437
- search(t) {
438
- const e = t.trim().toLowerCase();
439
- if (!e) return [];
440
- const a = [];
441
- for (let n = 0; n < this.count; n++)
442
- this.text(n).toLowerCase().includes(e) && a.push(n);
443
- return a;
444
- }
445
- /** 解析内部跳转目标(`slide:3` / `slide:last`)为页索引;外部链接返回 null */
446
- resolveLink(t) {
447
- if (!t.startsWith("slide:")) return null;
448
- const e = t.slice(6), a = e === "last" ? this.count : Number(e);
449
- return Number.isFinite(a) ? this.clamp(a - 1) : null;
524
+ search(e) {
525
+ const t = e.trim().toLowerCase();
526
+ if (!t) return [];
527
+ const n = [];
528
+ for (let o = 0; o < this.count; o++)
529
+ this.text(o).toLowerCase().includes(t) && n.push(o);
530
+ return n;
531
+ }
532
+ /** 解析绝对或相对内部跳转;相对动作保留到点击时,页序变化后仍指向正确目标。 */
533
+ resolveLink(e) {
534
+ if (!e.startsWith("slide:")) return null;
535
+ const t = e.slice(6), n = t === "next" ? this.idx + 2 : t === "previous" ? this.idx : t === "first" ? 1 : t === "last" ? this.count : Number(t);
536
+ return Number.isFinite(n) ? this.clamp(n - 1) : null;
450
537
  }
451
538
  destroy() {
452
539
  this.cancelAutoAdvance(), this.listeners.clear(), this.textCache.clear();
453
540
  }
454
541
  }
455
- const g = "http://www.w3.org/2000/svg", D = "http://www.w3.org/1999/xhtml";
456
- let m = null;
457
- function H() {
458
- m = null;
542
+ const C = "http://www.w3.org/2000/svg", I = "http://www.w3.org/1999/xhtml";
543
+ let y = null;
544
+ function J() {
545
+ y = null;
459
546
  }
460
- function q(s) {
461
- if (m !== null) return m;
462
- const t = s.body ?? s.documentElement;
463
- if (!t) return !0;
464
- const e = s.createElementNS(g, "svg");
465
- e.setAttribute("viewBox", "0 0 100 100"), e.setAttribute("width", "200"), e.setAttribute("height", "200"), e.setAttribute("style", "position:absolute;left:-9999px;top:-9999px;pointer-events:none");
466
- const a = s.createElementNS(g, "foreignObject");
467
- a.setAttribute("width", "100"), a.setAttribute("height", "100");
468
- const n = s.createElementNS(D, "div");
469
- n.setAttribute("style", "width:100px;height:100px"), a.appendChild(n), e.appendChild(a), t.appendChild(e);
470
- const r = n.getBoundingClientRect().width;
471
- return e.remove(), m = r === 0 || r > 150, m;
547
+ function K(i) {
548
+ if (y !== null) return y;
549
+ const e = i.body ?? i.documentElement;
550
+ if (!e) return !0;
551
+ const t = i.createElementNS(C, "svg");
552
+ t.setAttribute("viewBox", "0 0 100 100"), t.setAttribute("width", "200"), t.setAttribute("height", "200"), t.setAttribute("style", "position:absolute;left:-9999px;top:-9999px;pointer-events:none");
553
+ const n = i.createElementNS(C, "foreignObject");
554
+ n.setAttribute("width", "100"), n.setAttribute("height", "100");
555
+ const o = i.createElementNS(I, "div");
556
+ o.setAttribute("style", "width:100px;height:100px"), n.appendChild(o), t.appendChild(n), e.appendChild(t);
557
+ const s = o.getBoundingClientRect().width;
558
+ return t.remove(), y = s === 0 || s > 150, y;
472
559
  }
473
- class O {
474
- constructor(t, e, a = {}) {
475
- this.container = t, this.presentation = e, this.svgCache = /* @__PURE__ */ new Map(), this.playing = null, this.cancelAuto = null, this.onChange = null, this.onAnimStep = null, this.onLinkClick = null, this.handleClick = (r) => {
476
- const i = r.target, c = i == null ? void 0 : i.closest("[data-slide]");
560
+ class Q {
561
+ constructor(e, t, n = {}) {
562
+ this.container = e, this.presentation = t, this.svgCache = /* @__PURE__ */ new Map(), this.playing = null, this.cancelAuto = null, this.onChange = null, this.onAnimStep = null, this.onLinkClick = null, this.handleClick = (s) => {
563
+ const r = s.target, c = r == null ? void 0 : r.closest("[data-slide]");
477
564
  if (c) {
478
- r.preventDefault();
479
- const p = this.state.resolveLink(`slide:${c.getAttribute("data-slide") ?? ""}`);
480
- p !== null && this.goTo(p);
565
+ s.preventDefault();
566
+ const h = this.state.resolveLink(`slide:${c.getAttribute("data-slide") ?? ""}`);
567
+ h !== null && this.goTo(h);
481
568
  return;
482
569
  }
483
- const o = i == null ? void 0 : i.closest("a[href]"), l = o == null ? void 0 : o.getAttribute("href");
484
- l && this.onLinkClick && this.onLinkClick(l) === !0 && r.preventDefault();
485
- }, this.media = a.media ?? "badge";
486
- const n = a.textMode ?? "auto";
487
- this.textMode = n !== "auto" ? n : q(t.ownerDocument) ? "html" : "svg", this.state = new F(e, a), this.unsubscribe = this.state.subscribe((r) => {
488
- var i, c;
489
- r.type === "slide" ? (this.paint(r.transition), (i = this.onChange) == null || i.call(this, r.index)) : r.type === "animation" ? (r.group ? this.play(r.group) : this.applyVisibility(), (c = this.onAnimStep) == null || c.call(this, r.done, r.total)) : r.type === "zoom" && this.applyZoom();
490
- }), this.container.addEventListener("click", this.handleClick), this.paint();
570
+ const a = r == null ? void 0 : r.closest("a[href]"), l = a == null ? void 0 : a.getAttribute("href");
571
+ l && this.onLinkClick && this.onLinkClick(l) === !0 && s.preventDefault();
572
+ }, this.handleKeyDown = (s) => {
573
+ if (s.key !== "Enter") return;
574
+ const r = s.target, c = r == null ? void 0 : r.closest("[data-slide]");
575
+ if (!c) return;
576
+ const a = this.state.resolveLink(`slide:${c.getAttribute("data-slide") ?? ""}`);
577
+ a !== null && (s.preventDefault(), this.goTo(a));
578
+ }, this.media = n.media ?? "badge";
579
+ const o = n.textMode ?? "auto";
580
+ this.textMode = o !== "auto" ? o : K(e.ownerDocument) ? "html" : "svg", this.state = new _(t, n), this.unsubscribe = this.state.subscribe((s) => {
581
+ var r, c;
582
+ s.type === "slide" ? (this.paint(s.transition), (r = this.onChange) == null || r.call(this, s.index)) : s.type === "animation" ? (s.group ? this.play(s.group) : this.applyVisibility(), (c = this.onAnimStep) == null || c.call(this, s.done, s.total)) : s.type === "zoom" && this.applyZoom();
583
+ }), this.container.addEventListener("click", this.handleClick), this.container.addEventListener("keydown", this.handleKeyDown), this.paint();
491
584
  }
492
585
  // ---------------- 转发给状态机 ----------------
493
586
  get index() {
@@ -511,8 +604,8 @@ class O {
511
604
  get hasPendingAnimation() {
512
605
  return this.state.hasPendingAnimation;
513
606
  }
514
- goTo(t, e = "forward") {
515
- this.state.goTo(t, e);
607
+ goTo(e, t = "forward") {
608
+ this.state.goTo(e, t);
516
609
  }
517
610
  next() {
518
611
  this.state.next();
@@ -520,41 +613,41 @@ class O {
520
613
  prev() {
521
614
  this.state.prev();
522
615
  }
523
- setZoom(t) {
524
- this.state.setZoom(t);
616
+ setZoom(e) {
617
+ this.state.setZoom(e);
525
618
  }
526
619
  finishAnimations() {
527
620
  this.state.finishAnimations();
528
621
  }
529
- search(t) {
530
- return this.state.search(t);
622
+ search(e) {
623
+ return this.state.search(e);
531
624
  }
532
- text(t) {
533
- return this.state.text(t);
625
+ text(e) {
626
+ return this.state.text(e);
534
627
  }
535
- setAnimate(t) {
536
- this.state.setAnimate(t), this.paint();
628
+ setAnimate(e) {
629
+ this.state.setAnimate(e), this.paint();
537
630
  }
538
631
  playNextAnimation() {
539
632
  return this.state.playNextAnimation() !== null;
540
633
  }
541
634
  // ---------------- 渲染 ----------------
542
635
  /** 带缓存的单页 SVG(主视图用) */
543
- slideSvg(t) {
544
- let e = this.svgCache.get(t);
545
- return e === void 0 && (e = v(this.presentation, this.presentation.slides[t], {
636
+ slideSvg(e) {
637
+ let t = this.svgCache.get(e);
638
+ return t === void 0 && (t = S(this.presentation, this.presentation.slides[e], {
546
639
  media: this.media,
547
640
  textMode: this.textMode
548
- }), this.svgCache.set(t, e)), e;
641
+ }), this.svgCache.set(e, t)), t;
549
642
  }
550
643
  /** 不走缓存,用于缩略图等需要独立 defs id 的场景 */
551
- renderSlide(t) {
552
- const e = this.presentation.slides[t];
553
- return v(this.presentation, e, {
644
+ renderSlide(e) {
645
+ const t = this.presentation.slides[e];
646
+ return S(this.presentation, t, {
554
647
  // 缩略图不嵌播放器:既没意义,还会为每个缩略图各建一个媒体元素
555
648
  textMode: this.textMode,
556
649
  // 缩略图是静态产物,没有后续的 applyVisibility,隐藏状态只能烘进 SVG
557
- hiddenElements: [...w(e)]
650
+ hiddenElements: [...k(t)]
558
651
  });
559
652
  }
560
653
  /**
@@ -567,22 +660,22 @@ class O {
567
660
  refresh() {
568
661
  this.svgCache.delete(this.index), this.paint();
569
662
  }
570
- exportPng(t = 2, e = this.index) {
571
- return T(this.presentation, this.presentation.slides[e], t);
663
+ exportPng(e = 2, t = this.index) {
664
+ return X(this.presentation, this.presentation.slides[t], e);
572
665
  }
573
- paint(t) {
574
- var i, c, o;
575
- (i = this.playing) == null || i.cancel(), this.playing = null, (c = this.cancelAuto) == null || c.call(this);
576
- const e = document.createElement("div");
577
- e.style.cssText = "position:absolute;inset:0;display:flex;align-items:center;justify-content:center", e.innerHTML = this.slideSvg(this.index);
578
- const a = this.container.firstElementChild, n = this.container.ownerDocument.defaultView;
579
- (!n || n.getComputedStyle(this.container).position === "static") && (this.container.style.position = "relative"), t && a ? (this.container.appendChild(e), N(a, e, t).then(() => this.startAutoAdvance())) : (this.container.innerHTML = "", this.container.appendChild(e), this.startAutoAdvance());
580
- const r = e.querySelector("svg");
581
- r && (r.style.display = "block", r.style.maxWidth = "none"), this.applyZoom(), this.applyVisibility(), (o = this.onAnimStep) == null || o.call(this, this.state.animationDone, this.state.animationTotal);
666
+ paint(e) {
667
+ var r, c, a;
668
+ (r = this.playing) == null || r.cancel(), this.playing = null, (c = this.cancelAuto) == null || c.call(this);
669
+ const t = document.createElement("div");
670
+ t.style.cssText = "position:absolute;inset:0;display:flex;align-items:center;justify-content:center", t.innerHTML = this.slideSvg(this.index);
671
+ const n = this.container.firstElementChild, o = this.container.ownerDocument.defaultView;
672
+ (!o || o.getComputedStyle(this.container).position === "static") && (this.container.style.position = "relative"), e && n ? (this.container.appendChild(t), W(n, t, e).then(() => this.startAutoAdvance())) : (this.container.innerHTML = "", this.container.appendChild(t), this.startAutoAdvance());
673
+ const s = t.querySelector("svg");
674
+ s && (s.style.display = "block", s.style.maxWidth = "none"), this.applyZoom(), this.applyVisibility(), (a = this.onAnimStep) == null || a.call(this, this.state.animationDone, this.state.animationTotal);
582
675
  }
583
- play(t) {
584
- var e;
585
- (e = this.playing) == null || e.cancel(), this.playing = Y(this.container, t), this.applyVisibility();
676
+ play(e) {
677
+ var t;
678
+ (t = this.playing) == null || t.cancel(), this.playing = q(this.container, e), this.applyVisibility();
586
679
  }
587
680
  /**
588
681
  * 按状态机给出的隐藏集合设置元素可见性。
@@ -590,38 +683,39 @@ class O {
590
683
  * 不播动画时也要跑:静态画面取的是动画终态,退场元素同样得藏起来。
591
684
  */
592
685
  applyVisibility() {
593
- const t = this.state.hiddenElementIds;
594
- this.container.querySelectorAll("[data-el]").forEach((e) => {
595
- const a = Number(e.getAttribute("data-el"));
596
- e.style.visibility = t.has(a) ? "hidden" : "";
686
+ const e = this.state.hiddenElementIds;
687
+ this.container.querySelectorAll("[data-el]").forEach((t) => {
688
+ const n = Number(t.getAttribute("data-el"));
689
+ t.style.visibility = e.has(n) ? "hidden" : "";
597
690
  });
598
691
  }
599
692
  applyZoom() {
600
- const t = this.container.querySelector("svg");
601
- if (!t) return;
602
- const e = this.state.zoom;
603
- e === 1 ? (t.style.width = "100%", t.style.height = "100%") : (t.style.width = `${this.presentation.width * e}px`, t.style.height = `${this.presentation.height * e}px`);
693
+ const e = this.container.querySelector("svg");
694
+ if (!e) return;
695
+ const t = this.state.zoom;
696
+ t === 1 ? (e.style.width = "100%", e.style.height = "100%") : (e.style.width = `${this.presentation.width * t}px`, e.style.height = `${this.presentation.height * t}px`);
604
697
  }
605
698
  startAutoAdvance() {
606
699
  this.cancelAuto = this.state.scheduleAutoAdvance();
607
700
  }
608
701
  destroy() {
609
- var t, e;
610
- this.container.removeEventListener("click", this.handleClick), this.unsubscribe(), (t = this.playing) == null || t.cancel(), (e = this.cancelAuto) == null || e.call(this), this.state.destroy(), this.container.innerHTML = "", this.svgCache.clear();
702
+ var e, t;
703
+ this.container.removeEventListener("click", this.handleClick), this.container.removeEventListener("keydown", this.handleKeyDown), this.unsubscribe(), (e = this.playing) == null || e.cancel(), (t = this.cancelAuto) == null || t.call(this), this.state.destroy(), this.container.innerHTML = "", this.svgCache.clear();
611
704
  }
612
705
  }
613
706
  export {
614
- F as PresentationState,
615
- O as Viewer,
616
- V as autoAdvanceMs,
617
- q as foreignObjectScalesCorrectly,
618
- z as framesFor,
619
- R as groupSteps,
620
- W as hiddenBefore,
621
- L as morphPairs,
622
- Y as playGroup,
623
- N as playTransition,
624
- H as resetForeignObjectProbe,
625
- G as staticHidden,
626
- x as transitionFrames
707
+ _ as PresentationState,
708
+ Q as Viewer,
709
+ Z as autoAdvanceMs,
710
+ K as foreignObjectScalesCorrectly,
711
+ F as framesFor,
712
+ et as groupSteps,
713
+ st as hiddenBefore,
714
+ O as morphPairs,
715
+ q as playGroup,
716
+ W as playTransition,
717
+ B as playTransitionControlled,
718
+ J as resetForeignObjectProbe,
719
+ it as staticHidden,
720
+ M as transitionFrames
627
721
  };
package/dist/viewer.d.ts CHANGED
@@ -75,5 +75,6 @@ export declare class Viewer {
75
75
  private applyZoom;
76
76
  private startAutoAdvance;
77
77
  private handleClick;
78
+ private handleKeyDown;
78
79
  destroy(): void;
79
80
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@web-ppt/viewer-core",
3
- "version": "0.4.5",
3
+ "version": "0.5.0-beta.1",
4
4
  "description": "Headless presentation state machine for Web-PPT: navigation, zoom, search and click-batched animation playback, drivable from any UI framework.",
5
5
  "keywords": [
6
6
  "pptx",
@@ -50,7 +50,7 @@
50
50
  "prepack": "node ../../tooling/sync-package-docs.mjs"
51
51
  },
52
52
  "peerDependencies": {
53
- "@web-ppt/core": "^0.4.0"
53
+ "@web-ppt/core": "^0.5.0-beta.1"
54
54
  },
55
55
  "publishConfig": {
56
56
  "access": "public"