@rxdrag/website-lib-core 0.0.103 → 0.0.105
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/package.json +5 -5
- package/src/component-logic/index.ts +1 -8
- package/src/global.d.ts +7 -0
- package/src/index.ts +1 -3
- package/src/react/components/BackgroundHlsVideoPlayer.tsx +30 -1
- package/src/react/components/ContactForm/ContactForm.tsx +3 -2
- package/src/react/components/ContactForm/types.ts +1 -0
- package/src/react/components/ProductCard/ProductCta/index.tsx +5 -21
- package/src/react/components/Scroller.tsx +32 -4
- package/src/react/components/Share/socials.tsx +1 -2
- package/src/react/index.ts +1 -2
- package/src/component-logic/collapse.ts +0 -61
- package/src/component-logic/gsap.d.ts +0 -4
- package/src/component-logic/modal.ts +0 -45
- package/src/component-logic/motion.ts +0 -272
- package/src/component-logic/number.ts +0 -45
- package/src/component-logic/popover.ts +0 -51
- package/src/component-logic/tabs.ts +0 -10
- package/src/controller/AnimateController.ts +0 -138
- package/src/controller/AosController.ts +0 -240
- package/src/controller/CollapseController.ts +0 -130
- package/src/controller/FlipController.ts +0 -339
- package/src/controller/ModalController.ts +0 -127
- package/src/controller/NumberController.ts +0 -161
- package/src/controller/OpenableController.ts +0 -367
- package/src/controller/PageLoader.ts +0 -154
- package/src/controller/PopoverController.ts +0 -116
- package/src/controller/TabsController.ts +0 -271
- package/src/controller/applyAnimation.ts +0 -86
- package/src/controller/applyInitialState.ts +0 -79
- package/src/controller/consts.ts +0 -33
- package/src/controller/index.ts +0 -10
- package/src/controller/utils.ts +0 -48
- package/src/motion/consts.ts +0 -428
- package/src/motion/convertToGsapVars.ts +0 -102
- package/src/motion/index.ts +0 -6
- package/src/motion/normalizeAnimation.ts +0 -28
- package/src/motion/normalizeAosAnimation.ts +0 -22
- package/src/motion/normalizePopupAnimation.ts +0 -24
- package/src/motion/types.ts +0 -133
- package/src/react/hooks/index.ts +0 -1
- package/src/react/hooks/useScroll.ts +0 -30
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { DATA_OPENABLE, DATA_OPENABLE_ROLE, OpenAble } from "../controller";
|
|
2
|
-
import { AnimationConfig, OpenableAnimationConfig } from "../motion";
|
|
3
|
-
|
|
4
|
-
export const defaultPopoverPanelOpen: AnimationConfig = {
|
|
5
|
-
fromTo: {
|
|
6
|
-
from: {
|
|
7
|
-
height: 0,
|
|
8
|
-
opacity: 0,
|
|
9
|
-
},
|
|
10
|
-
to: {
|
|
11
|
-
height: "auto",
|
|
12
|
-
opacity: 1,
|
|
13
|
-
},
|
|
14
|
-
},
|
|
15
|
-
duration: 0.6,
|
|
16
|
-
ease: "spring",
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export const defaultPopoverPanelClose: AnimationConfig = {
|
|
20
|
-
fromTo: {
|
|
21
|
-
from: {
|
|
22
|
-
height: "auto",
|
|
23
|
-
opacity: 1,
|
|
24
|
-
},
|
|
25
|
-
to: {
|
|
26
|
-
height: 0,
|
|
27
|
-
opacity: 0,
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
duration: 0.25,
|
|
31
|
-
ease: "cubic-bezier(0.4, 0.0, 0.2, 1)",
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export const defaultPopoverPanelAnimation: OpenableAnimationConfig = {
|
|
35
|
-
open: defaultPopoverPanelOpen,
|
|
36
|
-
close: defaultPopoverPanelClose,
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export function getPopoverDataAttrs(openableKey?: string) {
|
|
40
|
-
return {
|
|
41
|
-
[DATA_OPENABLE]: openableKey || crypto.randomUUID(),
|
|
42
|
-
[DATA_OPENABLE_ROLE]: OpenAble.PopoverContainer,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function getPopoverPanelDataAttrs(openableKey?: string) {
|
|
47
|
-
return {
|
|
48
|
-
[DATA_OPENABLE]: openableKey || "",
|
|
49
|
-
[DATA_OPENABLE_ROLE]: OpenAble.PopoverPanel,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export const DATA_TABS = "data-tabs";
|
|
2
|
-
export const DATA_TABS_HEADER = "data-tabs-header";
|
|
3
|
-
export const DATA_TABS_PANEL = "data-tabs-panel";
|
|
4
|
-
export const DATA_TABS_TAB = "data-tabs-tab";
|
|
5
|
-
export const DATA_TABS_BODY = "data-tabs-body";
|
|
6
|
-
//当前选中的选项卡
|
|
7
|
-
export const DATA_TABS_SELECTION = "data-tabs-selection";
|
|
8
|
-
//选中动画,存动画数据
|
|
9
|
-
export const DATA_MOTION_SELECTION = "data-motion-selection";
|
|
10
|
-
export const DATA_MOTION_FLIP = "data-motion-flip";
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import { AnimationConfig, DATA_MOTION } from "../motion";
|
|
2
|
-
import { applyAnimation } from "./applyAnimation";
|
|
3
|
-
import { applyInitialState } from "./applyInitialState";
|
|
4
|
-
|
|
5
|
-
export class AnimateController {
|
|
6
|
-
private static instance: Record<string, AnimateController> = {};
|
|
7
|
-
private animations: gsap.core.Tween[] = [];
|
|
8
|
-
private unmountHandlers: Array<() => void> = [];
|
|
9
|
-
private registeredElements: WeakSet<Element> = new WeakSet();
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* 私有构造函数,防止外部直接创建实例
|
|
13
|
-
* @param clientDoc - 可选的Document对象,在SSR环境中可能为undefined
|
|
14
|
-
*/
|
|
15
|
-
private constructor(private clientDoc?: Document) {}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* 获取单例实例
|
|
19
|
-
* @param key - 实例的唯一标识,默认为"default"
|
|
20
|
-
* @param clientDoc - 可选的Document对象
|
|
21
|
-
* @returns AnimateController 实例
|
|
22
|
-
*/
|
|
23
|
-
public static getInstance(
|
|
24
|
-
key: string = "runtime",
|
|
25
|
-
clientDoc?: Document
|
|
26
|
-
): AnimateController {
|
|
27
|
-
if (!AnimateController.instance[key]) {
|
|
28
|
-
// 在服务器端渲染时,document 不存在,传入 undefined
|
|
29
|
-
const doc =
|
|
30
|
-
clientDoc || (typeof document !== "undefined" ? document : undefined);
|
|
31
|
-
AnimateController.instance[key] = new AnimateController(doc);
|
|
32
|
-
}
|
|
33
|
-
// 这个代码很有必要,需要替换掉旧doc,因为react不确定刷新的问题
|
|
34
|
-
if (clientDoc) {
|
|
35
|
-
AnimateController.instance[key].destroy();
|
|
36
|
-
AnimateController.instance[key].setClientDoc(clientDoc);
|
|
37
|
-
}
|
|
38
|
-
return AnimateController.instance[key];
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* 销毁指定的实例
|
|
43
|
-
* @param key - 实例的唯一标识
|
|
44
|
-
*/
|
|
45
|
-
public static destroyInstance(key: string = "runtime"): void {
|
|
46
|
-
if (AnimateController.instance[key]) {
|
|
47
|
-
AnimateController.instance[key].destroy();
|
|
48
|
-
delete AnimateController.instance[key];
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* 重置所有实例(主要用于测试)
|
|
54
|
-
*/
|
|
55
|
-
public static resetAll(): void {
|
|
56
|
-
Object.keys(AnimateController.instance).forEach((key) => {
|
|
57
|
-
AnimateController.instance[key].destroy();
|
|
58
|
-
});
|
|
59
|
-
AnimateController.instance = {};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* 设置客户端文档对象
|
|
64
|
-
* @param clientDoc - Document对象
|
|
65
|
-
*/
|
|
66
|
-
public setClientDoc(clientDoc: Document): void {
|
|
67
|
-
this.clientDoc = clientDoc;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* 挂载动画控制器,初始化所有动画
|
|
72
|
-
* @param clientDoc - 可选的Document对象,如果未提供则使用构造函数中的文档
|
|
73
|
-
*/
|
|
74
|
-
public mount(clientDoc?: Document): void {
|
|
75
|
-
const doc = clientDoc || this.clientDoc;
|
|
76
|
-
if (!doc) return;
|
|
77
|
-
|
|
78
|
-
// 查找所有带有 data-motion 属性的元素
|
|
79
|
-
const elements = doc.querySelectorAll(`[${DATA_MOTION}]`);
|
|
80
|
-
// 初始化新的动画
|
|
81
|
-
elements.forEach((element) => {
|
|
82
|
-
// 检查元素是否已经被注册过
|
|
83
|
-
if (this.registeredElements.has(element)) {
|
|
84
|
-
return; // 如果已经注册过,则跳过
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (
|
|
88
|
-
doc.defaultView &&
|
|
89
|
-
element instanceof doc.defaultView.HTMLElement &&
|
|
90
|
-
element.dataset.motion
|
|
91
|
-
) {
|
|
92
|
-
try {
|
|
93
|
-
const animation = JSON.parse(element.dataset.motion) as
|
|
94
|
-
| AnimationConfig
|
|
95
|
-
| undefined;
|
|
96
|
-
if (animation) {
|
|
97
|
-
if (animation.initial) {
|
|
98
|
-
applyInitialState(element, animation.initial);
|
|
99
|
-
}
|
|
100
|
-
// 执行动画
|
|
101
|
-
const tween = applyAnimation(element, animation);
|
|
102
|
-
if (tween) {
|
|
103
|
-
this.animations.push(tween);
|
|
104
|
-
// 将元素添加到已注册集合中
|
|
105
|
-
this.registeredElements.add(element);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
} catch (error) {
|
|
109
|
-
console.error("解析动画配置失败", error);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* 销毁动画控制器,清理所有动画和事件监听器
|
|
117
|
-
*/
|
|
118
|
-
public destroy(): void {
|
|
119
|
-
// 停止所有动画
|
|
120
|
-
this.animations.forEach((tween) => {
|
|
121
|
-
tween.kill();
|
|
122
|
-
});
|
|
123
|
-
this.animations = [];
|
|
124
|
-
|
|
125
|
-
// 执行所有卸载处理函数
|
|
126
|
-
this.unmountHandlers.forEach((handler) => handler());
|
|
127
|
-
this.unmountHandlers = [];
|
|
128
|
-
|
|
129
|
-
// 创建新的 WeakSet 来清理已注册元素的跟踪
|
|
130
|
-
this.registeredElements = new WeakSet();
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// 导出默认实例,方便直接使用
|
|
135
|
-
export const animate = AnimateController.getInstance(
|
|
136
|
-
"runtime",
|
|
137
|
-
typeof document !== "undefined" ? document : undefined
|
|
138
|
-
);
|
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
import { merge } from "lodash-es";
|
|
2
|
-
import {
|
|
3
|
-
AnimationConfig,
|
|
4
|
-
AosAnimationConfig,
|
|
5
|
-
DATA_MOTION_INVIEW,
|
|
6
|
-
} from "../motion";
|
|
7
|
-
import { applyAnimation } from "./applyAnimation";
|
|
8
|
-
|
|
9
|
-
export class AosController {
|
|
10
|
-
private static instances: Record<string, AosController> = {};
|
|
11
|
-
private doc?: Document;
|
|
12
|
-
private unmountHandlers: Array<() => void> = [];
|
|
13
|
-
private intersectionObserver?: IntersectionObserver;
|
|
14
|
-
|
|
15
|
-
private constructor(doc?: Document) {
|
|
16
|
-
this.doc = doc;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* 获取 AosController 实例
|
|
21
|
-
* @param key 实例的唯一标识,默认为 "runtime"
|
|
22
|
-
* @param doc 文档对象
|
|
23
|
-
* @returns AosController 实例
|
|
24
|
-
*/
|
|
25
|
-
public static getInstance(
|
|
26
|
-
key: string = "runtime",
|
|
27
|
-
doc?: Document
|
|
28
|
-
): AosController {
|
|
29
|
-
if (!AosController.instances[key]) {
|
|
30
|
-
AosController.instances[key] = new AosController(doc);
|
|
31
|
-
} else if (doc) {
|
|
32
|
-
AosController.instances[key].setDoc(doc);
|
|
33
|
-
}
|
|
34
|
-
return AosController.instances[key];
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* 销毁指定 key 的实例
|
|
39
|
-
* @param key 实例的唯一标识
|
|
40
|
-
*/
|
|
41
|
-
public static destroyInstance(key: string = "runtime"): void {
|
|
42
|
-
if (AosController.instances[key]) {
|
|
43
|
-
AosController.instances[key].destroy();
|
|
44
|
-
delete AosController.instances[key];
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* 重置所有实例
|
|
50
|
-
*/
|
|
51
|
-
public static resetAll(): void {
|
|
52
|
-
Object.keys(AosController.instances).forEach((key) => {
|
|
53
|
-
AosController.destroyInstance(key);
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* 设置文档对象
|
|
59
|
-
* @param doc 文档对象
|
|
60
|
-
*/
|
|
61
|
-
private setDoc(doc?: Document) {
|
|
62
|
-
this.doc = doc;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* 挂载 AOS 动画控制器
|
|
67
|
-
* @param clientDoc 文档对象,默认为 document
|
|
68
|
-
*/
|
|
69
|
-
public mount(): void {
|
|
70
|
-
// 如果没有可用的文档对象,则直接返回
|
|
71
|
-
if (!this.doc) return;
|
|
72
|
-
|
|
73
|
-
// 清理之前的观察器和处理函数
|
|
74
|
-
this.destroy();
|
|
75
|
-
|
|
76
|
-
// 创建 IntersectionObserver 用于检测元素进入视口
|
|
77
|
-
this.intersectionObserver = new IntersectionObserver(
|
|
78
|
-
(entries) => {
|
|
79
|
-
entries.forEach((entry) => {
|
|
80
|
-
if (entry.isIntersecting) {
|
|
81
|
-
const element = entry.target as HTMLElement;
|
|
82
|
-
this.handleElementEnterViewport(element);
|
|
83
|
-
} else {
|
|
84
|
-
const element = entry.target as HTMLElement;
|
|
85
|
-
this.handleElementLeaveViewport(element);
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
threshold: 0.1, // 元素10%可见时触发
|
|
91
|
-
root: null, // 使用视口作为根
|
|
92
|
-
}
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
// 查找所有的data-motion-inview元素
|
|
96
|
-
const inviewElements = this.doc.querySelectorAll(`[${DATA_MOTION_INVIEW}]`);
|
|
97
|
-
|
|
98
|
-
inviewElements.forEach((element) => {
|
|
99
|
-
if (
|
|
100
|
-
this.doc?.defaultView &&
|
|
101
|
-
element instanceof this.doc.defaultView.HTMLElement &&
|
|
102
|
-
element.dataset.motionInview
|
|
103
|
-
) {
|
|
104
|
-
try {
|
|
105
|
-
// 初始化元素样式
|
|
106
|
-
this.initElementStyle(element as HTMLElement);
|
|
107
|
-
|
|
108
|
-
// 添加到观察器
|
|
109
|
-
this.intersectionObserver?.observe(element);
|
|
110
|
-
|
|
111
|
-
// 添加清理函数
|
|
112
|
-
this.unmountHandlers.push(() => {
|
|
113
|
-
this.intersectionObserver?.unobserve(element);
|
|
114
|
-
});
|
|
115
|
-
} catch (error) {
|
|
116
|
-
console.error("初始化元素AOS动画时出错:", element, error);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* 初始化元素样式
|
|
124
|
-
* @param element 要初始化的元素
|
|
125
|
-
*/
|
|
126
|
-
private initElementStyle(element: HTMLElement): void {
|
|
127
|
-
try {
|
|
128
|
-
const inViewAnimation = element.dataset.motionInview
|
|
129
|
-
? (JSON.parse(element.dataset.motionInview) as
|
|
130
|
-
| AosAnimationConfig
|
|
131
|
-
| undefined)
|
|
132
|
-
: undefined;
|
|
133
|
-
|
|
134
|
-
if (!inViewAnimation) return;
|
|
135
|
-
|
|
136
|
-
// 如果设置了once属性,初始化时可能需要设置元素透明度为0
|
|
137
|
-
if (
|
|
138
|
-
inViewAnimation.once &&
|
|
139
|
-
inViewAnimation.enter &&
|
|
140
|
-
!element.dataset.motionViewportEnter
|
|
141
|
-
) {
|
|
142
|
-
// 如果是淡入动画,初始透明度可能需要设置为0
|
|
143
|
-
if (!element.style.opacity && inViewAnimation.enter.opacity === 1) {
|
|
144
|
-
element.style.opacity = "0";
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
} catch (error) {
|
|
148
|
-
console.error("初始化元素样式时出错:", element, error);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* 处理元素进入视口
|
|
154
|
-
* @param element 进入视口的元素
|
|
155
|
-
*/
|
|
156
|
-
private handleElementEnterViewport(element: HTMLElement): void {
|
|
157
|
-
try {
|
|
158
|
-
const animation = element.dataset.motion
|
|
159
|
-
? (JSON.parse(element.dataset.motion) as AnimationConfig | undefined)
|
|
160
|
-
: undefined;
|
|
161
|
-
const inViewAnimation = element.dataset.motionInview
|
|
162
|
-
? (JSON.parse(element.dataset.motionInview) as
|
|
163
|
-
| AosAnimationConfig
|
|
164
|
-
| undefined)
|
|
165
|
-
: undefined;
|
|
166
|
-
|
|
167
|
-
if (!inViewAnimation) return;
|
|
168
|
-
|
|
169
|
-
const needAction =
|
|
170
|
-
(!element.dataset.motionViewportEnter && inViewAnimation.once) ||
|
|
171
|
-
!inViewAnimation.once;
|
|
172
|
-
|
|
173
|
-
if (inViewAnimation.enter && needAction) {
|
|
174
|
-
element.dataset.motionViewportEnter = "true";
|
|
175
|
-
applyAnimation(element, merge(animation, inViewAnimation.enter), {
|
|
176
|
-
ensureVisibleOnComplete: inViewAnimation.once,
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
// 如果是一次性动画,不再观察
|
|
180
|
-
if (inViewAnimation.once) {
|
|
181
|
-
this.intersectionObserver?.unobserve(element);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
} catch (error) {
|
|
185
|
-
console.error("处理元素进入视口时出错:", element, error);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* 处理元素离开视口
|
|
191
|
-
* @param element 离开视口的元素
|
|
192
|
-
*/
|
|
193
|
-
private handleElementLeaveViewport(element: HTMLElement): void {
|
|
194
|
-
try {
|
|
195
|
-
const animation = element.dataset.motion
|
|
196
|
-
? (JSON.parse(element.dataset.motion) as AnimationConfig | undefined)
|
|
197
|
-
: undefined;
|
|
198
|
-
const inViewAnimation = element.dataset.motionInview
|
|
199
|
-
? (JSON.parse(element.dataset.motionInview) as
|
|
200
|
-
| AosAnimationConfig
|
|
201
|
-
| undefined)
|
|
202
|
-
: undefined;
|
|
203
|
-
|
|
204
|
-
if (!inViewAnimation || inViewAnimation.once) return;
|
|
205
|
-
|
|
206
|
-
if (inViewAnimation.exit) {
|
|
207
|
-
applyAnimation(element, merge(animation, inViewAnimation.exit));
|
|
208
|
-
}
|
|
209
|
-
} catch (error) {
|
|
210
|
-
console.error("处理元素离开视口时出错:", element, error);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* 销毁 AOS 动画控制器
|
|
216
|
-
*/
|
|
217
|
-
public destroy(): void {
|
|
218
|
-
// 断开所有观察
|
|
219
|
-
if (this.intersectionObserver) {
|
|
220
|
-
this.intersectionObserver.disconnect();
|
|
221
|
-
this.intersectionObserver = undefined;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// 执行所有卸载处理函数
|
|
225
|
-
this.unmountHandlers.forEach((handler) => {
|
|
226
|
-
try {
|
|
227
|
-
handler();
|
|
228
|
-
} catch (error) {
|
|
229
|
-
console.error("执行卸载处理函数失败", error);
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
this.unmountHandlers = [];
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// 导出默认实例,方便直接使用
|
|
237
|
-
export const aos = AosController.getInstance(
|
|
238
|
-
"runtime",
|
|
239
|
-
typeof document !== "undefined" ? document : undefined
|
|
240
|
-
);
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import { DATA_OPENABLE, DATA_OPENABLE_ROLE, OpenAble } from "./consts";
|
|
2
|
-
import { OpenableController } from "./OpenableController";
|
|
3
|
-
|
|
4
|
-
export class CollapseController extends OpenableController {
|
|
5
|
-
private static instances: Record<string, CollapseController> = {};
|
|
6
|
-
|
|
7
|
-
private constructor(doc?: Document) {
|
|
8
|
-
super(doc);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
public static getInstance(
|
|
12
|
-
key: string = "runtime",
|
|
13
|
-
doc?: Document
|
|
14
|
-
): CollapseController {
|
|
15
|
-
if (!CollapseController.instances[key]) {
|
|
16
|
-
CollapseController.instances[key] = new CollapseController(doc);
|
|
17
|
-
} else if (doc) {
|
|
18
|
-
CollapseController.instances[key].setDoc(doc);
|
|
19
|
-
}
|
|
20
|
-
return CollapseController.instances[key];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* 销毁指定 key 的实例
|
|
25
|
-
* @param key 实例的唯一标识
|
|
26
|
-
*/
|
|
27
|
-
public static destroyInstance(key: string = "runtime"): void {
|
|
28
|
-
if (CollapseController.instances[key]) {
|
|
29
|
-
CollapseController.instances[key].destroy();
|
|
30
|
-
delete CollapseController.instances[key];
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
private setDoc(doc?: Document) {
|
|
35
|
-
this.doc = doc;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
mount() {
|
|
39
|
-
super.mount();
|
|
40
|
-
const openables = this.doc?.querySelectorAll(`[${DATA_OPENABLE}]`);
|
|
41
|
-
openables?.forEach((openable) => {
|
|
42
|
-
const openableKey = openable.getAttribute(DATA_OPENABLE);
|
|
43
|
-
|
|
44
|
-
// 获取所有触发器
|
|
45
|
-
const allTriggers = openable?.querySelectorAll(
|
|
46
|
-
`[${DATA_OPENABLE_ROLE}="${OpenAble.CollapseTrigger}"]`
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
// 获取所有嵌套容器
|
|
50
|
-
const nestedContainers = openable?.querySelectorAll(
|
|
51
|
-
`[${DATA_OPENABLE_ROLE}="${OpenAble.PopoverContainer}"],
|
|
52
|
-
[${DATA_OPENABLE_ROLE}="${OpenAble.ModalContainer}"],
|
|
53
|
-
[${DATA_OPENABLE_ROLE}="${OpenAble.CollapseContainer}"]`
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
// 过滤出不在嵌套容器内的触发器
|
|
57
|
-
const filteredTriggers: Element[] = [];
|
|
58
|
-
allTriggers?.forEach((trigger) => {
|
|
59
|
-
let isInNestedContainer = false;
|
|
60
|
-
|
|
61
|
-
nestedContainers?.forEach((container) => {
|
|
62
|
-
if (container.contains(trigger) && container !== openable) {
|
|
63
|
-
isInNestedContainer = true;
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
if (!isInNestedContainer) {
|
|
68
|
-
filteredTriggers.push(trigger);
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
// 为过滤后的触发器添加事件处理
|
|
73
|
-
filteredTriggers.forEach((trigger) => {
|
|
74
|
-
if (openableKey) {
|
|
75
|
-
const unsub = this.initCollapseTrigger(
|
|
76
|
-
openableKey,
|
|
77
|
-
trigger as HTMLElement
|
|
78
|
-
);
|
|
79
|
-
this.unmountHandlers.push(unsub);
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* 初始化Modal的点击交互事件
|
|
86
|
-
* @param openableKey - 弹出层的唯一标识
|
|
87
|
-
* @param trigger - 触发器元素
|
|
88
|
-
* @returns 清理函数,用于移除事件监听器
|
|
89
|
-
*/
|
|
90
|
-
public initCollapseTrigger(
|
|
91
|
-
openableKey: string,
|
|
92
|
-
trigger: HTMLElement
|
|
93
|
-
): () => void {
|
|
94
|
-
if (!this.doc) return () => {};
|
|
95
|
-
|
|
96
|
-
try {
|
|
97
|
-
// 创建新的事件处理函数
|
|
98
|
-
const handleTriggerClick = () => {
|
|
99
|
-
const container = this.getOpenableContainer(openableKey);
|
|
100
|
-
if (container) {
|
|
101
|
-
if (container.classList.contains("open")) {
|
|
102
|
-
// 传递容器元素而不是触发器,确保正确移除open类
|
|
103
|
-
this.close(openableKey, container);
|
|
104
|
-
} else {
|
|
105
|
-
this.open(openableKey, trigger);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
// 添加新的事件监听器
|
|
111
|
-
trigger.addEventListener("click", handleTriggerClick);
|
|
112
|
-
|
|
113
|
-
return () => {
|
|
114
|
-
trigger.removeEventListener("click", handleTriggerClick);
|
|
115
|
-
};
|
|
116
|
-
} catch (error) {
|
|
117
|
-
console.error(`初始化Modal事件失败: ${openableKey}`, error);
|
|
118
|
-
return () => {};
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
unmount(): void {
|
|
123
|
-
super.unmount();
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export const collapse = CollapseController.getInstance(
|
|
128
|
-
"runtime",
|
|
129
|
-
typeof document !== "undefined" ? document : undefined
|
|
130
|
-
);
|