hero-animate 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +219 -2
- package/dist/heroTransition-CND4UQtc.js +330 -0
- package/dist/heroTransition.d.ts +44 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1 -0
- package/dist/modalSource.d.ts +19 -0
- package/dist/motion.d.ts +47 -0
- package/dist/react.d.ts +23 -0
- package/dist/react.js +64 -0
- package/dist/vue.d.ts +84 -0
- package/dist/vue.js +74 -0
- package/package.json +92 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SinJayXie
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,220 @@
|
|
|
1
|
-
#
|
|
1
|
+
# hero-animate
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
iOS 风格「图标 ⇄ 窗口」形变转场:窗口从触发元素(按钮 / 图标)的矩形展开,关闭时沿同一条路径收回原处。框架无关核心 + Vue 3 / React 开箱即用组件。
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/hero-animate)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
[](https://sinjayxie.github.io/hero-animate/)
|
|
8
|
+
|
|
9
|
+
- **形变转场**:遮罩 + 面板 + 图标克隆三层交叉,视觉上「图标长成窗口 / 窗口塌回图标」
|
|
10
|
+
- **零配置**:自动记录 pointerdown 触发源;无触发源时回落到本体中心缩放
|
|
11
|
+
- **Vue / React 组件**:`open` 状态驱动,内置挂载 / 卸载与离场动画管理
|
|
12
|
+
- **弹簧可调**:`mass / stiffness / damping / velocity` 任意字段覆盖,其余用默认值
|
|
13
|
+
- **无障碍**:`prefers-reduced-motion` 下自动跳过动画
|
|
14
|
+
- **ESM + 类型**:核心入口零框架依赖,`vue` / `react` 为可选 peer
|
|
15
|
+
|
|
16
|
+
## 安装
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pnpm add hero-animate animejs
|
|
20
|
+
# Vue 项目额外安装 vue(已有可跳过)
|
|
21
|
+
# React 项目额外安装 react(已有可跳过)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`animejs@^4` 为必装 peer 依赖;`vue` / `react` 仅在引入对应子入口时需要。
|
|
25
|
+
|
|
26
|
+
## 快速开始
|
|
27
|
+
|
|
28
|
+
### Vue 3
|
|
29
|
+
|
|
30
|
+
```vue
|
|
31
|
+
<script setup lang="ts">
|
|
32
|
+
import { ref } from "vue";
|
|
33
|
+
import { HeroTransition } from "hero-animate/vue";
|
|
34
|
+
|
|
35
|
+
const open = ref(false);
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<template>
|
|
39
|
+
<button class="icon-button" @click="open = true">📁</button>
|
|
40
|
+
|
|
41
|
+
<HeroTransition :open="open" class="modal-mask" @exited="open = false">
|
|
42
|
+
<div class="modal-panel">
|
|
43
|
+
<h3>从图标长出来的窗口</h3>
|
|
44
|
+
<button @click="open = false">关闭</button>
|
|
45
|
+
</div>
|
|
46
|
+
</HeroTransition>
|
|
47
|
+
</template>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### React
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
import { useState } from "react";
|
|
54
|
+
import { HeroTransition } from "hero-animate/react";
|
|
55
|
+
|
|
56
|
+
export function Demo() {
|
|
57
|
+
const [open, setOpen] = useState(false);
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<>
|
|
61
|
+
<button className="icon-button" onClick={() => setOpen(true)}>📁</button>
|
|
62
|
+
|
|
63
|
+
<HeroTransition open={open} className="modal-mask" onExited={() => {}}>
|
|
64
|
+
<div className="modal-panel">
|
|
65
|
+
<h3>从图标长出来的窗口</h3>
|
|
66
|
+
<button onClick={() => setOpen(false)}>关闭</button>
|
|
67
|
+
</div>
|
|
68
|
+
</HeroTransition>
|
|
69
|
+
</>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 锚定浮层(下拉 / 菜单)
|
|
75
|
+
|
|
76
|
+
`masked={false}` 时根元素本身就是面板;配合 `ghostOnly`,浮层只动克隆体,真实浮层在飞行期间让位,适合 JS 定位的小浮层。
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
<HeroTransition
|
|
80
|
+
open={open}
|
|
81
|
+
masked={false}
|
|
82
|
+
ghostOnly
|
|
83
|
+
anchor={() => buttonRef.current}
|
|
84
|
+
className="popover-panel"
|
|
85
|
+
style={{ position: "fixed", left: x, top: y }}
|
|
86
|
+
>
|
|
87
|
+
…
|
|
88
|
+
</HeroTransition>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 核心 API(任意框架 / 原生)
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import { createHeroTransition } from "hero-animate";
|
|
95
|
+
|
|
96
|
+
const hero = createHeroTransition(
|
|
97
|
+
(root) => root.querySelector(".modal-panel"),
|
|
98
|
+
{ masked: true }
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// CSS hooks 语义:动画结束调用 done
|
|
102
|
+
hero.onEnter(maskEl, () => {});
|
|
103
|
+
hero.onLeave(maskEl, () => {});
|
|
104
|
+
hero.cancel(); // 中断并清理
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`createDialogHero(selector = ".modal-panel", options?)` 是外层 `<Transition :css="false">` 场景的语法糖。
|
|
108
|
+
|
|
109
|
+
## CSS 约定
|
|
110
|
+
|
|
111
|
+
转场不注入任何样式,但会读取元素的自然样式来对齐:
|
|
112
|
+
|
|
113
|
+
```css
|
|
114
|
+
.modal-mask {
|
|
115
|
+
position: fixed;
|
|
116
|
+
inset: 0;
|
|
117
|
+
z-index: 50;
|
|
118
|
+
display: flex;
|
|
119
|
+
align-items: center;
|
|
120
|
+
justify-content: center;
|
|
121
|
+
background: rgba(0, 0, 0, 0.4);
|
|
122
|
+
backdrop-filter: blur(4px); /* 会被读取为遮罩动画的终点模糊值 */
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.modal-panel {
|
|
126
|
+
width: 420px;
|
|
127
|
+
border-radius: 20px;
|
|
128
|
+
background: #fff;
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
- 面板选择器默认 `.modal-panel`,可用 `panelSelector` 覆盖
|
|
133
|
+
- 组件在原地渲染:若祖先节点带 `transform` / `filter` / `backdrop-filter`,`position: fixed` 会以该祖先为包含块。建议将弹窗挂到 `body`(Vue `Teleport` / React Portal)
|
|
134
|
+
|
|
135
|
+
## API
|
|
136
|
+
|
|
137
|
+
### HeroOptions
|
|
138
|
+
|
|
139
|
+
| 选项 | 类型 | 默认值 | 说明 |
|
|
140
|
+
| --- | --- | --- | --- |
|
|
141
|
+
| `masked` | `boolean` | `true` | 根元素是遮罩(内部找面板);`false` 时根元素本身就是面板 |
|
|
142
|
+
| `panelSelector` | `string` | `".modal-panel"` | 面板选择器,用于在根元素内定位面板 |
|
|
143
|
+
| `anchor` | `() => HTMLElement \| null` | — | 无指针来源时的锚点(快捷键打开等场景) |
|
|
144
|
+
| `ghostOnly` | `boolean` | `false` | 只动画克隆体,不写真实浮层 transform |
|
|
145
|
+
| `spring` | `HeroSpringParams` | 见下 | 打开曲线弹簧参数 |
|
|
146
|
+
| `springAccel` | `HeroSpringParams` | 见下 | 关闭曲线弹簧参数 |
|
|
147
|
+
|
|
148
|
+
### HeroHandlers
|
|
149
|
+
|
|
150
|
+
| 成员 | 类型 | 说明 |
|
|
151
|
+
| --- | --- | --- |
|
|
152
|
+
| `onEnter` | `(el: Element, done: () => void) => void` | 打开转场,结束调用 `done` |
|
|
153
|
+
| `onLeave` | `(el: Element, done: () => void) => void` | 关闭转场,结束调用 `done` |
|
|
154
|
+
| `cancel` | `() => void` | 中断时间线、移除克隆层、还原被隐藏的触发元素 |
|
|
155
|
+
|
|
156
|
+
### 组件 Props
|
|
157
|
+
|
|
158
|
+
| Prop | 类型 | 默认值 | 说明 |
|
|
159
|
+
| --- | --- | --- | --- |
|
|
160
|
+
| `open` | `boolean` | — | 必填,驱动打开 / 关闭 |
|
|
161
|
+
| `masked` | `boolean` | `true` | 同 `HeroOptions.masked` |
|
|
162
|
+
| `panelSelector` | `string` | `".modal-panel"` | 同 `HeroOptions.panelSelector` |
|
|
163
|
+
| `anchor` | `() => HTMLElement \| null` | — | 同 `HeroOptions.anchor` |
|
|
164
|
+
| `ghostOnly` | `boolean` | `false` | 同 `HeroOptions.ghostOnly` |
|
|
165
|
+
| `spring` / `springAccel` | `HeroSpringParams` | 见下 | 弹簧参数 |
|
|
166
|
+
|
|
167
|
+
- Vue:`entered` / `exited` 事件,其余 attrs 透传根元素
|
|
168
|
+
- React:`onEntered` / `onExited` 回调,额外支持 `className` / `style` / `children`
|
|
169
|
+
|
|
170
|
+
## 弹簧参数
|
|
171
|
+
|
|
172
|
+
默认值与 `SPRING_PARAMS` 一致:
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
import { SPRING_PARAMS } from "hero-animate";
|
|
176
|
+
|
|
177
|
+
// { mass: 1, stiffness: 250, damping: 35, velocity: 20 }
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
只传想覆盖的字段,其余自动回落:
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
createHeroTransition(getPanel, {
|
|
184
|
+
spring: { stiffness: 300, damping: 32 }, // 打开
|
|
185
|
+
springAccel: { stiffness: 280, damping: 30 }, // 关闭
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
| 字段 | 含义 | 默认值 |
|
|
190
|
+
| --- | --- | --- |
|
|
191
|
+
| `mass` | 质量,越大越迟滞 | `1` |
|
|
192
|
+
| `stiffness` | 刚度,越大越快 | `250` |
|
|
193
|
+
| `damping` | 阻尼,越小回弹越明显 | `35` |
|
|
194
|
+
| `velocity` | 初速度 | `20` |
|
|
195
|
+
|
|
196
|
+
## 触发源追踪
|
|
197
|
+
|
|
198
|
+
库在创建转场时自动在 window 捕获阶段安装一次监听,记录最近一次 pointerdown 命中的 `button / [role="button"] / a / label` 及其矩形(800ms 内有效)。如需提前安装:
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
import { installModalSourceTracking } from "hero-animate";
|
|
202
|
+
installModalSourceTracking();
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## 开发与发布
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
pnpm install
|
|
209
|
+
pnpm dev # 本地文档 + 演示(http://localhost:5173)
|
|
210
|
+
pnpm typecheck # 类型检查
|
|
211
|
+
pnpm build # 构建 dist/(ESM + d.ts)
|
|
212
|
+
pnpm docs:build # 构建文档站到 docs-dist/
|
|
213
|
+
npm publish # 发布(prepublishOnly 会自动构建)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
文档站通过 [deploy-docs.yml](./.github/workflows/deploy-docs.yml) 在 push 到 `main` 时自动发布到 GitHub Pages。首次部署前需在仓库 **Settings → Pages → Build and deployment** 将 Source 设为 **GitHub Actions**(`GITHUB_TOKEN` 无权自动创建 Pages 站点)。
|
|
217
|
+
|
|
218
|
+
## License
|
|
219
|
+
|
|
220
|
+
[MIT](./LICENSE) © SinJayXie
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import { createTimeline as e, spring as t } from "animejs";
|
|
2
|
+
// lib/motion.ts
|
|
3
|
+
var n = {
|
|
4
|
+
micro: 100,
|
|
5
|
+
press: 130,
|
|
6
|
+
hover: 150,
|
|
7
|
+
short: 180,
|
|
8
|
+
base: 420,
|
|
9
|
+
panel: 420,
|
|
10
|
+
page: 350,
|
|
11
|
+
pop: 420
|
|
12
|
+
}, r = {
|
|
13
|
+
mass: 1,
|
|
14
|
+
stiffness: 250,
|
|
15
|
+
damping: 35,
|
|
16
|
+
velocity: 20
|
|
17
|
+
};
|
|
18
|
+
function i(e) {
|
|
19
|
+
return t({
|
|
20
|
+
...r,
|
|
21
|
+
...e
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
var a = {
|
|
25
|
+
hover: 1.04,
|
|
26
|
+
press: .96,
|
|
27
|
+
dismiss: .9
|
|
28
|
+
}, o = null;
|
|
29
|
+
function s() {
|
|
30
|
+
return typeof window > "u" || !window.matchMedia ? null : (o ||= window.matchMedia("(prefers-reduced-motion: reduce)"), o);
|
|
31
|
+
}
|
|
32
|
+
function c() {
|
|
33
|
+
return s()?.matches ?? !1;
|
|
34
|
+
}
|
|
35
|
+
// lib/modalSource.ts
|
|
36
|
+
var l = "button, [role='button'], a, label", u = null, d = !1;
|
|
37
|
+
function f(e) {
|
|
38
|
+
let t = e.target;
|
|
39
|
+
if (!t || !t.closest) return;
|
|
40
|
+
let n = t.closest(l);
|
|
41
|
+
if (!n) return;
|
|
42
|
+
let r = n.getBoundingClientRect();
|
|
43
|
+
r.width < 2 || r.height < 2 || (u = {
|
|
44
|
+
source: {
|
|
45
|
+
el: n,
|
|
46
|
+
rect: {
|
|
47
|
+
left: r.left,
|
|
48
|
+
top: r.top,
|
|
49
|
+
width: r.width,
|
|
50
|
+
height: r.height
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
at: performance.now()
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function p() {
|
|
57
|
+
d || typeof window > "u" || (d = !0, window.addEventListener("pointerdown", f, { capture: !0 }));
|
|
58
|
+
}
|
|
59
|
+
function m(e = 800) {
|
|
60
|
+
return !u || performance.now() - u.at > e ? null : u.source;
|
|
61
|
+
}
|
|
62
|
+
function h(e) {
|
|
63
|
+
return e.left + e.width > 0 && e.top + e.height > 0 && e.left < window.innerWidth && e.top < window.innerHeight;
|
|
64
|
+
}
|
|
65
|
+
// lib/heroTransition.ts
|
|
66
|
+
var g = "rgba(0, 0, 0, 0)", _ = "rgba(0, 0, 0, 0.4)", v = "blur(0px)";
|
|
67
|
+
function y(e) {
|
|
68
|
+
let t = getComputedStyle(e), n = t.backdropFilter || t.webkitBackdropFilter;
|
|
69
|
+
return n && n !== "none" ? n : v;
|
|
70
|
+
}
|
|
71
|
+
function b(t, r = {}) {
|
|
72
|
+
p();
|
|
73
|
+
let o = r.masked ?? !0, s = r.anchor, l = r.ghostOnly ?? !1, u = r.panelSelector ?? ".modal-panel", d = i(r.spring), f = i(r.springAccel), b = 0, x = null, S = null, C = null, w = null, T = null, E = null, D = null, O = null, k = null;
|
|
74
|
+
function A(e) {
|
|
75
|
+
E = e, D = e.style.opacity === "" ? null : e.style.opacity, e.style.opacity = "0";
|
|
76
|
+
}
|
|
77
|
+
function j() {
|
|
78
|
+
E && (D === null ? E.style.removeProperty("opacity") : E.style.opacity = D, E = null, D = null);
|
|
79
|
+
}
|
|
80
|
+
function M(e) {
|
|
81
|
+
return parseFloat(getComputedStyle(e).borderTopLeftRadius) || 0;
|
|
82
|
+
}
|
|
83
|
+
function N(e) {
|
|
84
|
+
return t(e) || (e instanceof HTMLElement ? (e.matches(u) ? e : e.querySelector(u)) ?? (o ? null : e) : null);
|
|
85
|
+
}
|
|
86
|
+
function P() {
|
|
87
|
+
S?.remove(), S = null, C?.remove(), C = null;
|
|
88
|
+
}
|
|
89
|
+
function F() {
|
|
90
|
+
if (w?.isConnected) {
|
|
91
|
+
let e = w.getBoundingClientRect();
|
|
92
|
+
if (e.width > 0 && e.height > 0) return {
|
|
93
|
+
left: e.left,
|
|
94
|
+
top: e.top,
|
|
95
|
+
width: e.width,
|
|
96
|
+
height: e.height
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return T && T.width > 0 && h(T) ? T : null;
|
|
100
|
+
}
|
|
101
|
+
function I() {
|
|
102
|
+
let e = s?.();
|
|
103
|
+
if (!e?.isConnected) return null;
|
|
104
|
+
let t = e.getBoundingClientRect();
|
|
105
|
+
return t.width <= 0 || t.height <= 0 ? null : {
|
|
106
|
+
el: e,
|
|
107
|
+
rect: {
|
|
108
|
+
left: t.left,
|
|
109
|
+
top: t.top,
|
|
110
|
+
width: t.width,
|
|
111
|
+
height: t.height
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function L(e, t, n, r) {
|
|
116
|
+
let i = t.cloneNode(!0);
|
|
117
|
+
i.style.removeProperty("transform"), i.style.removeProperty("transform-origin"), i.style.removeProperty("opacity"), i.style.removeProperty("right"), i.style.removeProperty("bottom"), i.style.position = "fixed", i.style.left = `${n.left}px`, i.style.top = `${n.top}px`, i.style.width = `${n.width}px`, i.style.height = `${n.height}px`, i.style.margin = "0", i.style.pointerEvents = "none", i.style.transformOrigin = "0 0";
|
|
118
|
+
let a = e instanceof HTMLElement && Number(getComputedStyle(e).zIndex) || 0;
|
|
119
|
+
return i.style.zIndex = String(a + r), document.body.appendChild(i), i;
|
|
120
|
+
}
|
|
121
|
+
function R(e, t) {
|
|
122
|
+
let n = e.querySelectorAll("input, textarea, select"), r = t.querySelectorAll("input, textarea, select");
|
|
123
|
+
n.length === r.length && n.forEach((e, t) => {
|
|
124
|
+
let n = r[t];
|
|
125
|
+
e instanceof HTMLInputElement && n instanceof HTMLInputElement && (n.checked = e.checked, n.indeterminate = e.indeterminate), n.value !== e.value && (n.value = e.value);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
function z(e) {
|
|
129
|
+
e.style.removeProperty("transform"), e.style.removeProperty("transform-origin"), e.style.removeProperty("border-radius"), e.style.removeProperty("opacity");
|
|
130
|
+
}
|
|
131
|
+
function B(t, r) {
|
|
132
|
+
let i = t, s = N(t);
|
|
133
|
+
O = i, k = s, x?.cancel(), P(), j();
|
|
134
|
+
let u = o ? y(i) : v;
|
|
135
|
+
if (o && (i.style.removeProperty("background-color"), i.style.removeProperty("backdrop-filter"), i.style.removeProperty("opacity")), c() || !s) {
|
|
136
|
+
s?.style.removeProperty("opacity"), r();
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
z(s);
|
|
140
|
+
let f = m() ?? I();
|
|
141
|
+
if (!f) {
|
|
142
|
+
s.style.opacity = "0", o && (i.style.backgroundColor = g, i.style.backdropFilter = v), x = e({ onComplete: () => {
|
|
143
|
+
z(s), o && (i.style.removeProperty("background-color"), i.style.removeProperty("backdrop-filter")), r();
|
|
144
|
+
} }), o && x.add(i, {
|
|
145
|
+
backgroundColor: [g, _],
|
|
146
|
+
backdropFilter: [v, u],
|
|
147
|
+
duration: n.base,
|
|
148
|
+
ease: d
|
|
149
|
+
}, 0), x.add(s, {
|
|
150
|
+
opacity: [0, 1],
|
|
151
|
+
scaleX: [a.dismiss, 1],
|
|
152
|
+
scaleY: [a.dismiss, 1],
|
|
153
|
+
duration: n.base,
|
|
154
|
+
ease: d
|
|
155
|
+
}, 0);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
w = f.el, T = f.rect;
|
|
159
|
+
let p = f.rect, h = s.getBoundingClientRect(), E = p.width / h.width, D = p.height / h.height, F = h.left - p.left, R = h.top - p.top, B = M(f.el), V = M(s);
|
|
160
|
+
if (l) {
|
|
161
|
+
s.style.opacity = "0";
|
|
162
|
+
let a = ++b;
|
|
163
|
+
requestAnimationFrame(() => {
|
|
164
|
+
if (a !== b || !s.isConnected) return;
|
|
165
|
+
let c = s.getBoundingClientRect();
|
|
166
|
+
if (c.width <= 0 || c.height <= 0) {
|
|
167
|
+
z(s), r();
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
let l = p.width / c.width, f = p.height / c.height, m = c.left - p.left, h = c.top - p.top;
|
|
171
|
+
S = L(t, s, {
|
|
172
|
+
left: c.left,
|
|
173
|
+
top: c.top,
|
|
174
|
+
width: c.width,
|
|
175
|
+
height: c.height
|
|
176
|
+
}, 1), S.style.transform = `translate(${-m}px, ${-h}px) scale(${l}, ${f})`, S.style.borderRadius = `${B}px`, o && (i.style.backgroundColor = g, i.style.backdropFilter = v), x = e({ onComplete: () => {
|
|
177
|
+
P(), z(s), o && (i.style.removeProperty("background-color"), i.style.removeProperty("backdrop-filter")), r();
|
|
178
|
+
} }), o && x.add(i, {
|
|
179
|
+
backgroundColor: [g, _],
|
|
180
|
+
backdropFilter: [v, u],
|
|
181
|
+
duration: n.base,
|
|
182
|
+
ease: d
|
|
183
|
+
}, 0), x.add(S, {
|
|
184
|
+
translateX: [-m, 0],
|
|
185
|
+
translateY: [-h, 0],
|
|
186
|
+
scaleX: [l, 1],
|
|
187
|
+
scaleY: [f, 1],
|
|
188
|
+
borderRadius: [B, M(s)],
|
|
189
|
+
duration: n.pop,
|
|
190
|
+
ease: d
|
|
191
|
+
}, 0);
|
|
192
|
+
});
|
|
193
|
+
} else s.style.transformOrigin = "0 0", s.style.transform = `translate(${-F}px, ${-R}px) scale(${E}, ${D})`, s.style.borderRadius = `${B}px`, C = L(t, f.el, p, 2), A(f.el), o && (i.style.backgroundColor = g, i.style.backdropFilter = v), x = e({ onComplete: () => {
|
|
194
|
+
P(), z(s), o && (i.style.removeProperty("background-color"), i.style.removeProperty("backdrop-filter")), r();
|
|
195
|
+
} }), o && x.add(i, {
|
|
196
|
+
backgroundColor: [g, _],
|
|
197
|
+
backdropFilter: [v, u],
|
|
198
|
+
duration: n.base,
|
|
199
|
+
ease: d
|
|
200
|
+
}, 0), x.add(s, {
|
|
201
|
+
translateX: [-F, 0],
|
|
202
|
+
translateY: [-R, 0],
|
|
203
|
+
scaleX: [E, 1],
|
|
204
|
+
scaleY: [D, 1],
|
|
205
|
+
borderRadius: [B, V],
|
|
206
|
+
duration: n.pop,
|
|
207
|
+
ease: d
|
|
208
|
+
}, 0).add(C, {
|
|
209
|
+
translateX: [0, F],
|
|
210
|
+
translateY: [0, R],
|
|
211
|
+
scaleX: [1, 1 / E],
|
|
212
|
+
scaleY: [1, 1 / D],
|
|
213
|
+
duration: n.pop,
|
|
214
|
+
ease: d
|
|
215
|
+
}, 0).add(C, {
|
|
216
|
+
opacity: [
|
|
217
|
+
1,
|
|
218
|
+
0,
|
|
219
|
+
0
|
|
220
|
+
],
|
|
221
|
+
duration: n.pop,
|
|
222
|
+
ease: d
|
|
223
|
+
}, 0);
|
|
224
|
+
}
|
|
225
|
+
function V(t, r) {
|
|
226
|
+
let i = t, s = N(t);
|
|
227
|
+
O = i, k = s;
|
|
228
|
+
let u = () => {
|
|
229
|
+
j(), o && i && i.style.removeProperty("pointer-events"), r();
|
|
230
|
+
};
|
|
231
|
+
x?.cancel(), b++, P(), s && z(s), o && (i.style.removeProperty("background-color"), i.style.removeProperty("backdrop-filter"), i.style.removeProperty("opacity"), i.style.pointerEvents = "none");
|
|
232
|
+
let d = o ? y(i) : v;
|
|
233
|
+
if (c() || !s) {
|
|
234
|
+
u();
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
let p = s.getBoundingClientRect(), m = F();
|
|
238
|
+
if (!m) {
|
|
239
|
+
x = e({ onComplete: () => {
|
|
240
|
+
z(s), u();
|
|
241
|
+
} }), x.add(s, {
|
|
242
|
+
scaleX: [1, a.dismiss],
|
|
243
|
+
scaleY: [1, a.dismiss],
|
|
244
|
+
opacity: [1, 0],
|
|
245
|
+
duration: n.base,
|
|
246
|
+
ease: f
|
|
247
|
+
}, 0), o && x.add(i, {
|
|
248
|
+
backgroundColor: [_, g],
|
|
249
|
+
backdropFilter: [d, v],
|
|
250
|
+
duration: n.base,
|
|
251
|
+
ease: f
|
|
252
|
+
}, 0);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
let h = m.width / p.width, T = m.height / p.height, E = m.left - p.left, D = m.top - p.top, A = M(s), I = w?.isConnected ? M(w) : A;
|
|
256
|
+
S = L(t, s, {
|
|
257
|
+
left: p.left,
|
|
258
|
+
top: p.top,
|
|
259
|
+
width: p.width,
|
|
260
|
+
height: p.height
|
|
261
|
+
}, 2), R(s, S), S.style.borderRadius = `${A}px`, l ? (s.style.opacity = "0", x = e({ onComplete: () => {
|
|
262
|
+
P(), z(s), u();
|
|
263
|
+
} }), o && x.add(i, {
|
|
264
|
+
opacity: 0,
|
|
265
|
+
duration: n.panel,
|
|
266
|
+
ease: f
|
|
267
|
+
}, 0), x.add(S, {
|
|
268
|
+
translateX: [0, E],
|
|
269
|
+
translateY: [0, D],
|
|
270
|
+
scaleX: [1, h],
|
|
271
|
+
scaleY: [1, T],
|
|
272
|
+
borderRadius: [A, I],
|
|
273
|
+
duration: n.panel,
|
|
274
|
+
ease: f
|
|
275
|
+
}, 0).add(S, {
|
|
276
|
+
opacity: [1, 0],
|
|
277
|
+
duration: n.panel,
|
|
278
|
+
ease: f
|
|
279
|
+
}, 0)) : (C = L(t, w ?? s, m, 1), C.style.transform = `translate(${-E}px, ${-D}px) scale(${1 / h}, ${1 / T})`, C.style.opacity = "0", s.style.opacity = "0", x = e({ onComplete: () => {
|
|
280
|
+
P(), z(s), u();
|
|
281
|
+
} }), o && x.add(i, {
|
|
282
|
+
backgroundColor: [_, g],
|
|
283
|
+
backdropFilter: [d, v],
|
|
284
|
+
duration: n.panel,
|
|
285
|
+
ease: f
|
|
286
|
+
}, 0), x.add(S, {
|
|
287
|
+
translateX: [0, E],
|
|
288
|
+
translateY: [0, D],
|
|
289
|
+
scaleX: [1, h],
|
|
290
|
+
scaleY: [1, T],
|
|
291
|
+
borderRadius: [A, I],
|
|
292
|
+
duration: n.panel,
|
|
293
|
+
ease: f
|
|
294
|
+
}, 0).add(S, {
|
|
295
|
+
opacity: [1, 0],
|
|
296
|
+
duration: n.panel,
|
|
297
|
+
ease: f
|
|
298
|
+
}, 0).add(C, {
|
|
299
|
+
translateX: [-E, 0],
|
|
300
|
+
translateY: [-D, 0],
|
|
301
|
+
scaleX: [1 / h, 1],
|
|
302
|
+
scaleY: [1 / T, 1],
|
|
303
|
+
duration: n.panel,
|
|
304
|
+
ease: f
|
|
305
|
+
}, 0).add(C, {
|
|
306
|
+
opacity: [
|
|
307
|
+
0,
|
|
308
|
+
.8,
|
|
309
|
+
1
|
|
310
|
+
],
|
|
311
|
+
duration: n.panel,
|
|
312
|
+
ease: f
|
|
313
|
+
}, 0));
|
|
314
|
+
}
|
|
315
|
+
function H() {
|
|
316
|
+
x?.cancel(), x = null, b++, P(), j(), k && (z(k), k.style.removeProperty("opacity")), O && (O.style.removeProperty("background-color"), O.style.removeProperty("backdrop-filter"), O.style.removeProperty("opacity"), O.style.removeProperty("pointer-events"));
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
onEnter: B,
|
|
320
|
+
onLeave: V,
|
|
321
|
+
cancel: H
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
function x(e = ".modal-panel", t = {}) {
|
|
325
|
+
return b((t) => t instanceof HTMLElement ? t.matches(e) ? t : t.querySelector(e) : null, {
|
|
326
|
+
panelSelector: e,
|
|
327
|
+
...t
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
export { r as i, b as n, p as r, x as t };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type HeroSpringParams } from "./motion";
|
|
2
|
+
export interface HeroHandlers {
|
|
3
|
+
onEnter: (el: Element, done: () => void) => void;
|
|
4
|
+
onLeave: (el: Element, done: () => void) => void;
|
|
5
|
+
/** 取消当前转场:清理时间线、克隆层与内联样式,并立即还原真实元素 */
|
|
6
|
+
cancel: () => void;
|
|
7
|
+
}
|
|
8
|
+
export interface HeroOptions {
|
|
9
|
+
/**
|
|
10
|
+
* 是否存在独立遮罩(根 el = 遮罩,内部 getPanel() = 面板)。
|
|
11
|
+
* false:根 el 就是面板(无背景遮罩的锚定浮层)。
|
|
12
|
+
*/
|
|
13
|
+
masked?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* 无指针来源时的锚点(快捷键打开场景):用它作为展开起点与收回终点。
|
|
16
|
+
* 有指针来源(点击打开)时优先用点击的那个元素。
|
|
17
|
+
*/
|
|
18
|
+
anchor?: () => HTMLElement | null;
|
|
19
|
+
/**
|
|
20
|
+
* 只操作 ghost 模式(适合 AppSelect 下拉 / CSV 菜单这类 JS 定位的小浮层):
|
|
21
|
+
* · 打开与关闭都只动克隆体,真实浮层飞行期间 opacity 0 让位、结束同帧交回;
|
|
22
|
+
* · 不生成「图标层」,也不隐藏触发元素本体(控件本体保持可见更自然);
|
|
23
|
+
* · 不写真实浮层的内联 transform,避免干扰它自己的定位计算。
|
|
24
|
+
*/
|
|
25
|
+
ghostOnly?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* 面板选择器,默认 `.modal-panel`,用于 resolvePanel 兜底
|
|
28
|
+
* (getPanel 取不到真实元素时,按此选择器在根元素内查找面板)。
|
|
29
|
+
*/
|
|
30
|
+
panelSelector?: string;
|
|
31
|
+
/**
|
|
32
|
+
* 打开曲线(anime.js spring)参数:mass / stiffness / damping / velocity。
|
|
33
|
+
* 只需传入想覆盖的字段,其余回落到默认值。
|
|
34
|
+
*/
|
|
35
|
+
spring?: HeroSpringParams;
|
|
36
|
+
/** 关闭曲线(anime.js spring)参数;未提供的字段回落到默认值。 */
|
|
37
|
+
springAccel?: HeroSpringParams;
|
|
38
|
+
}
|
|
39
|
+
export declare function createHeroTransition(getPanel: (root: Element) => HTMLElement | null, options?: HeroOptions): HeroHandlers;
|
|
40
|
+
/**
|
|
41
|
+
* 便捷工厂:外层 <Transition :css="false"> 包住整个弹窗组件时使用。
|
|
42
|
+
* 面板通过根元素内的选择器定位(弹窗面板通常带 .modal-panel)。
|
|
43
|
+
*/
|
|
44
|
+
export declare function createDialogHero(selector?: string, options?: HeroOptions): HeroHandlers;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { createHeroTransition, createDialogHero } from "./heroTransition";
|
|
2
|
+
export type { HeroHandlers, HeroOptions } from "./heroTransition";
|
|
3
|
+
export { installModalSourceTracking } from "./modalSource";
|
|
4
|
+
export type { SourceRect, ModalSource } from "./modalSource";
|
|
5
|
+
export { SPRING_PARAMS } from "./motion";
|
|
6
|
+
export type { HeroSpringParams } from "./motion";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { i as SPRING_PARAMS, t as createDialogHero, n as createHeroTransition, r as installModalSourceTracking } from "./heroTransition-CND4UQtc.js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface SourceRect {
|
|
2
|
+
left: number;
|
|
3
|
+
top: number;
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
}
|
|
7
|
+
export interface ModalSource {
|
|
8
|
+
el: HTMLElement;
|
|
9
|
+
rect: SourceRect;
|
|
10
|
+
}
|
|
11
|
+
/** 在应用启动时调用一次 */
|
|
12
|
+
export declare function installModalSourceTracking(): void;
|
|
13
|
+
/**
|
|
14
|
+
* 取最近的触发源(含元素引用);超过 maxAge(ms)视为无效。
|
|
15
|
+
* 不清除记录:弹窗关闭时仍需以同一元素作为收回目标。
|
|
16
|
+
*/
|
|
17
|
+
export declare function consumeSource(maxAge?: number): ModalSource | null;
|
|
18
|
+
/** 矩形当前是否仍在窗口可视区域内(防止收回时飞向屏外) */
|
|
19
|
+
export declare function isRectOnScreen(r: SourceRect): boolean;
|
package/dist/motion.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export declare const DURATION: {
|
|
2
|
+
/** 极短:描边、图标状态切换 */
|
|
3
|
+
readonly micro: 100;
|
|
4
|
+
/** 按压/小型状态 */
|
|
5
|
+
readonly press: 130;
|
|
6
|
+
/** 悬停过渡 */
|
|
7
|
+
readonly hover: 150;
|
|
8
|
+
/** 短促入场(遮罩、下拉) */
|
|
9
|
+
readonly short: 180;
|
|
10
|
+
/** 标准入场 */
|
|
11
|
+
readonly base: 420;
|
|
12
|
+
/** 面板 / 选中指示 */
|
|
13
|
+
readonly panel: 420;
|
|
14
|
+
/** 整页转场 */
|
|
15
|
+
readonly page: 350;
|
|
16
|
+
/** 点击放大弹出的感知时长(与 SPRING.pop 对齐) */
|
|
17
|
+
readonly pop: 420;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* 默认弹簧参数:苹果进场/离场曲线共用同一组物理参数。
|
|
21
|
+
* 业务方可通过 HeroOptions.spring / springAccel 覆盖其中任意字段,
|
|
22
|
+
* 未提供的字段回落到这里。
|
|
23
|
+
*/
|
|
24
|
+
export declare const SPRING_PARAMS: {
|
|
25
|
+
readonly mass: 1;
|
|
26
|
+
readonly stiffness: 250;
|
|
27
|
+
readonly damping: 35;
|
|
28
|
+
readonly velocity: 20;
|
|
29
|
+
};
|
|
30
|
+
export interface HeroSpringParams {
|
|
31
|
+
mass?: number;
|
|
32
|
+
stiffness?: number;
|
|
33
|
+
damping?: number;
|
|
34
|
+
velocity?: number;
|
|
35
|
+
}
|
|
36
|
+
/** 按默认值补齐后创建 anime.js spring 缓动 */
|
|
37
|
+
export declare function springEase(overrides?: HeroSpringParams): import("animejs").Spring;
|
|
38
|
+
export declare const SCALE: {
|
|
39
|
+
/** 悬停 */
|
|
40
|
+
readonly hover: 1.04;
|
|
41
|
+
/** 按下(需要收缩感时) */
|
|
42
|
+
readonly press: 0.96;
|
|
43
|
+
/** 无锚点弹层的退场收缩(缩到中心消失) */
|
|
44
|
+
readonly dismiss: 0.9;
|
|
45
|
+
};
|
|
46
|
+
/** 系统是否要求减少动态效果 */
|
|
47
|
+
export declare function prefersReducedMotion(): boolean;
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CSSProperties, ReactNode } from "react";
|
|
2
|
+
import type { HeroSpringParams } from "./motion";
|
|
3
|
+
export interface HeroTransitionProps {
|
|
4
|
+
open: boolean;
|
|
5
|
+
masked?: boolean;
|
|
6
|
+
ghostOnly?: boolean;
|
|
7
|
+
panelSelector?: string;
|
|
8
|
+
anchor?: () => HTMLElement | null;
|
|
9
|
+
/** 打开曲线弹簧参数(未提供的字段用默认值) */
|
|
10
|
+
spring?: HeroSpringParams;
|
|
11
|
+
/** 关闭曲线弹簧参数(未提供的字段用默认值) */
|
|
12
|
+
springAccel?: HeroSpringParams;
|
|
13
|
+
onEntered?: () => void;
|
|
14
|
+
onExited?: () => void;
|
|
15
|
+
className?: string;
|
|
16
|
+
style?: CSSProperties;
|
|
17
|
+
children?: ReactNode;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* hero 转场的 React 适配:负责挂载/卸载时序,动画本身交给核心库。
|
|
21
|
+
* 离场动画期间保持挂载,done 之后才卸载。
|
|
22
|
+
*/
|
|
23
|
+
export declare function HeroTransition({ open, masked, ghostOnly, panelSelector, anchor, spring, springAccel, onEntered, onExited, className, style, children, }: HeroTransitionProps): JSX.Element | null;
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { n as e } from "./heroTransition-CND4UQtc.js";
|
|
2
|
+
import { useEffect as t, useMemo as n, useRef as r, useState as i } from "react";
|
|
3
|
+
import { jsx as a } from "react/jsx-runtime";
|
|
4
|
+
// lib/react.tsx
|
|
5
|
+
function o({ open: o, masked: s = !0, ghostOnly: c = !1, panelSelector: l = ".modal-panel", anchor: u, spring: d, springAccel: f, onEntered: p, onExited: m, className: h, style: g, children: _ }) {
|
|
6
|
+
let [v, y] = i(o), b = r(null), x = r("closed"), S = r(0), C = r(u), w = r(p), T = r(m);
|
|
7
|
+
t(() => {
|
|
8
|
+
C.current = u, w.current = p, T.current = m;
|
|
9
|
+
});
|
|
10
|
+
let E = JSON.stringify(d ?? null), D = JSON.stringify(f ?? null), O = n(() => e((e) => e instanceof HTMLElement ? e.querySelector(l) : null, {
|
|
11
|
+
masked: s,
|
|
12
|
+
ghostOnly: c,
|
|
13
|
+
panelSelector: l,
|
|
14
|
+
anchor: () => C.current?.() ?? null,
|
|
15
|
+
spring: d,
|
|
16
|
+
springAccel: f
|
|
17
|
+
}), [
|
|
18
|
+
s,
|
|
19
|
+
c,
|
|
20
|
+
l,
|
|
21
|
+
E,
|
|
22
|
+
D
|
|
23
|
+
]);
|
|
24
|
+
return t(() => {
|
|
25
|
+
if (o) {
|
|
26
|
+
if (!v) {
|
|
27
|
+
y(!0);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
let e = b.current;
|
|
31
|
+
if (!e) return;
|
|
32
|
+
let t = x.current;
|
|
33
|
+
if (t === "entering" || t === "open") return;
|
|
34
|
+
let n = ++S.current;
|
|
35
|
+
x.current = "entering", O.onEnter(e, () => {
|
|
36
|
+
n === S.current && (x.current = "open", w.current?.());
|
|
37
|
+
});
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (!v) return;
|
|
41
|
+
let e = b.current;
|
|
42
|
+
if (!e) {
|
|
43
|
+
y(!1);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (x.current === "leaving") return;
|
|
47
|
+
let t = ++S.current;
|
|
48
|
+
x.current = "leaving", O.onLeave(e, () => {
|
|
49
|
+
t === S.current && (x.current = "closed", y(!1), T.current?.());
|
|
50
|
+
});
|
|
51
|
+
}, [
|
|
52
|
+
o,
|
|
53
|
+
v,
|
|
54
|
+
O
|
|
55
|
+
]), t(() => () => {
|
|
56
|
+
S.current++, x.current = "closed", O.cancel();
|
|
57
|
+
}, [O]), v ? /* @__PURE__ */ a("div", {
|
|
58
|
+
ref: b,
|
|
59
|
+
className: h,
|
|
60
|
+
style: g,
|
|
61
|
+
children: _
|
|
62
|
+
}) : null;
|
|
63
|
+
}
|
|
64
|
+
export { o as HeroTransition };
|
package/dist/vue.d.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { type PropType } from "vue";
|
|
2
|
+
import { type HeroSpringParams } from "./index";
|
|
3
|
+
export interface HeroTransitionProps {
|
|
4
|
+
open: boolean;
|
|
5
|
+
masked?: boolean;
|
|
6
|
+
ghostOnly?: boolean;
|
|
7
|
+
panelSelector?: string;
|
|
8
|
+
anchor?: () => HTMLElement | null;
|
|
9
|
+
/** 打开曲线弹簧参数(未提供的字段用默认值) */
|
|
10
|
+
spring?: HeroSpringParams;
|
|
11
|
+
/** 关闭曲线弹簧参数(未提供的字段用默认值) */
|
|
12
|
+
springAccel?: HeroSpringParams;
|
|
13
|
+
}
|
|
14
|
+
export declare const HeroTransition: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
15
|
+
open: {
|
|
16
|
+
type: BooleanConstructor;
|
|
17
|
+
required: true;
|
|
18
|
+
};
|
|
19
|
+
masked: {
|
|
20
|
+
type: BooleanConstructor;
|
|
21
|
+
default: boolean;
|
|
22
|
+
};
|
|
23
|
+
ghostOnly: {
|
|
24
|
+
type: BooleanConstructor;
|
|
25
|
+
default: boolean;
|
|
26
|
+
};
|
|
27
|
+
panelSelector: {
|
|
28
|
+
type: StringConstructor;
|
|
29
|
+
default: string;
|
|
30
|
+
};
|
|
31
|
+
anchor: {
|
|
32
|
+
type: PropType<() => HTMLElement | null>;
|
|
33
|
+
default: undefined;
|
|
34
|
+
};
|
|
35
|
+
spring: {
|
|
36
|
+
type: PropType<HeroSpringParams>;
|
|
37
|
+
default: undefined;
|
|
38
|
+
};
|
|
39
|
+
springAccel: {
|
|
40
|
+
type: PropType<HeroSpringParams>;
|
|
41
|
+
default: undefined;
|
|
42
|
+
};
|
|
43
|
+
}>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
44
|
+
[key: string]: any;
|
|
45
|
+
}> | null, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("entered" | "exited")[], "entered" | "exited", import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
46
|
+
open: {
|
|
47
|
+
type: BooleanConstructor;
|
|
48
|
+
required: true;
|
|
49
|
+
};
|
|
50
|
+
masked: {
|
|
51
|
+
type: BooleanConstructor;
|
|
52
|
+
default: boolean;
|
|
53
|
+
};
|
|
54
|
+
ghostOnly: {
|
|
55
|
+
type: BooleanConstructor;
|
|
56
|
+
default: boolean;
|
|
57
|
+
};
|
|
58
|
+
panelSelector: {
|
|
59
|
+
type: StringConstructor;
|
|
60
|
+
default: string;
|
|
61
|
+
};
|
|
62
|
+
anchor: {
|
|
63
|
+
type: PropType<() => HTMLElement | null>;
|
|
64
|
+
default: undefined;
|
|
65
|
+
};
|
|
66
|
+
spring: {
|
|
67
|
+
type: PropType<HeroSpringParams>;
|
|
68
|
+
default: undefined;
|
|
69
|
+
};
|
|
70
|
+
springAccel: {
|
|
71
|
+
type: PropType<HeroSpringParams>;
|
|
72
|
+
default: undefined;
|
|
73
|
+
};
|
|
74
|
+
}>> & Readonly<{
|
|
75
|
+
onEntered?: ((...args: any[]) => any) | undefined;
|
|
76
|
+
onExited?: ((...args: any[]) => any) | undefined;
|
|
77
|
+
}>, {
|
|
78
|
+
panelSelector: string;
|
|
79
|
+
masked: boolean;
|
|
80
|
+
ghostOnly: boolean;
|
|
81
|
+
anchor: () => HTMLElement | null;
|
|
82
|
+
spring: HeroSpringParams;
|
|
83
|
+
springAccel: HeroSpringParams;
|
|
84
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
package/dist/vue.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { n as e } from "./heroTransition-CND4UQtc.js";
|
|
2
|
+
import "./index.js";
|
|
3
|
+
import { defineComponent as t, h as n, nextTick as r, onBeforeUnmount as i, onMounted as a, ref as o, watch as s } from "vue";
|
|
4
|
+
// lib/vue.ts
|
|
5
|
+
var c = t({
|
|
6
|
+
name: "HeroTransition",
|
|
7
|
+
inheritAttrs: !1,
|
|
8
|
+
props: {
|
|
9
|
+
open: {
|
|
10
|
+
type: Boolean,
|
|
11
|
+
required: !0
|
|
12
|
+
},
|
|
13
|
+
masked: {
|
|
14
|
+
type: Boolean,
|
|
15
|
+
default: !0
|
|
16
|
+
},
|
|
17
|
+
ghostOnly: {
|
|
18
|
+
type: Boolean,
|
|
19
|
+
default: !1
|
|
20
|
+
},
|
|
21
|
+
panelSelector: {
|
|
22
|
+
type: String,
|
|
23
|
+
default: ".modal-panel"
|
|
24
|
+
},
|
|
25
|
+
anchor: {
|
|
26
|
+
type: Function,
|
|
27
|
+
default: void 0
|
|
28
|
+
},
|
|
29
|
+
spring: {
|
|
30
|
+
type: Object,
|
|
31
|
+
default: void 0
|
|
32
|
+
},
|
|
33
|
+
springAccel: {
|
|
34
|
+
type: Object,
|
|
35
|
+
default: void 0
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
emits: ["entered", "exited"],
|
|
39
|
+
setup(t, { slots: c, attrs: l, emit: u }) {
|
|
40
|
+
let d = o(t.open), f = o(null), p = 0, m = e((e) => e instanceof HTMLElement ? e.querySelector(t.panelSelector) : null, {
|
|
41
|
+
masked: t.masked,
|
|
42
|
+
ghostOnly: t.ghostOnly,
|
|
43
|
+
panelSelector: t.panelSelector,
|
|
44
|
+
anchor: () => t.anchor?.() ?? null,
|
|
45
|
+
spring: t.spring,
|
|
46
|
+
springAccel: t.springAccel
|
|
47
|
+
});
|
|
48
|
+
async function h() {
|
|
49
|
+
let e = ++p;
|
|
50
|
+
if (d.value || (d.value = !0, await r()), e !== p) return;
|
|
51
|
+
let t = f.value;
|
|
52
|
+
t && m.onEnter(t, () => {
|
|
53
|
+
e === p && u("entered");
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function g() {
|
|
57
|
+
let e = ++p, t = f.value;
|
|
58
|
+
t ? m.onLeave(t, () => {
|
|
59
|
+
e === p && (d.value = !1, u("exited"));
|
|
60
|
+
}) : (d.value = !1, u("exited"));
|
|
61
|
+
}
|
|
62
|
+
return s(() => t.open, (e) => {
|
|
63
|
+
e ? h() : g();
|
|
64
|
+
}), a(() => {
|
|
65
|
+
t.open && h();
|
|
66
|
+
}), i(() => {
|
|
67
|
+
p++, m.cancel();
|
|
68
|
+
}), () => d.value ? n("div", {
|
|
69
|
+
...l,
|
|
70
|
+
ref: f
|
|
71
|
+
}, c.default?.()) : null;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
export { c as HeroTransition };
|
package/package.json
CHANGED
|
@@ -1,6 +1,94 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hero-animate",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "iOS 风格「图标 ⇄ 窗口」形变转场,框架无关核心 + Vue/React 组件",
|
|
6
|
+
"author": "SinJayXie",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/SinJayXie/hero-animate.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://sinjayxie.github.io/hero-animate/",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/SinJayXie/hero-animate/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"hero",
|
|
18
|
+
"hero-transition",
|
|
19
|
+
"shared-element",
|
|
20
|
+
"transition",
|
|
21
|
+
"animation",
|
|
22
|
+
"spring",
|
|
23
|
+
"animejs",
|
|
24
|
+
"vue",
|
|
25
|
+
"react",
|
|
26
|
+
"modal",
|
|
27
|
+
"dialog",
|
|
28
|
+
"popover"
|
|
29
|
+
],
|
|
30
|
+
"sideEffects": [
|
|
31
|
+
"./src/**",
|
|
32
|
+
"*.css"
|
|
33
|
+
],
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"module": "./dist/index.js",
|
|
36
|
+
"exports": {
|
|
37
|
+
".": {
|
|
38
|
+
"types": "./dist/index.d.ts",
|
|
39
|
+
"import": "./dist/index.js"
|
|
40
|
+
},
|
|
41
|
+
"./vue": {
|
|
42
|
+
"types": "./dist/vue.d.ts",
|
|
43
|
+
"import": "./dist/vue.js"
|
|
44
|
+
},
|
|
45
|
+
"./react": {
|
|
46
|
+
"types": "./dist/react.d.ts",
|
|
47
|
+
"import": "./dist/react.js"
|
|
48
|
+
},
|
|
49
|
+
"./package.json": "./package.json"
|
|
50
|
+
},
|
|
51
|
+
"files": [
|
|
52
|
+
"dist"
|
|
53
|
+
],
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=18"
|
|
56
|
+
},
|
|
57
|
+
"packageManager": "pnpm@12.15.0",
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"access": "public"
|
|
60
|
+
},
|
|
61
|
+
"scripts": {
|
|
62
|
+
"dev": "vite",
|
|
63
|
+
"build": "vite build && tsc -p tsconfig.build.json",
|
|
64
|
+
"docs:build": "vite build --config vite.docs.config.ts",
|
|
65
|
+
"docs:preview": "vite preview --config vite.docs.config.ts --port 4173",
|
|
66
|
+
"typecheck": "tsc -p tsconfig.json",
|
|
67
|
+
"prepublishOnly": "pnpm build"
|
|
68
|
+
},
|
|
69
|
+
"peerDependencies": {
|
|
70
|
+
"animejs": "^4.5.0",
|
|
71
|
+
"react": ">=18",
|
|
72
|
+
"vue": ">=3.3"
|
|
73
|
+
},
|
|
74
|
+
"peerDependenciesMeta": {
|
|
75
|
+
"react": {
|
|
76
|
+
"optional": true
|
|
77
|
+
},
|
|
78
|
+
"vue": {
|
|
79
|
+
"optional": true
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"devDependencies": {
|
|
83
|
+
"@types/prismjs": "^1.26.6",
|
|
84
|
+
"@types/react": "^18.3.31",
|
|
85
|
+
"@types/react-dom": "^18.3.7",
|
|
86
|
+
"animejs": "^4.5.0",
|
|
87
|
+
"prismjs": "^1.30.0",
|
|
88
|
+
"react": "^18.3.1",
|
|
89
|
+
"react-dom": "^18.3.1",
|
|
90
|
+
"typescript": "~6.0.3",
|
|
91
|
+
"vite": "^8.2.2",
|
|
92
|
+
"vue": "^3.5.44"
|
|
93
|
+
}
|
|
94
|
+
}
|