@rxdrag/website-lib-core 0.0.7 → 0.0.8

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.
Files changed (71) hide show
  1. package/index.ts +1 -1
  2. package/package.json +10 -6
  3. package/src/component-logic/gsap.d.ts +4 -0
  4. package/src/component-logic/index.ts +8 -0
  5. package/src/component-logic/link-client.ts +33 -0
  6. package/src/component-logic/link.ts +50 -0
  7. package/src/component-logic/modal.ts +36 -0
  8. package/src/component-logic/motion.ts +272 -0
  9. package/src/component-logic/number.ts +45 -0
  10. package/src/component-logic/popover.ts +51 -0
  11. package/src/component-logic/tabs.ts +10 -0
  12. package/src/controller/AnimateController.ts +138 -0
  13. package/src/controller/AosController.ts +240 -0
  14. package/src/controller/FlipController.ts +339 -0
  15. package/src/controller/ModalController.ts +127 -0
  16. package/src/controller/NumberController.ts +161 -0
  17. package/src/controller/PageLoader.ts +163 -0
  18. package/src/controller/PopoverController.ts +116 -0
  19. package/src/controller/TabsController.ts +271 -0
  20. package/src/controller/applyAnimation.ts +86 -0
  21. package/src/controller/applyInitialState.ts +79 -0
  22. package/src/{scripts → controller}/consts.ts +0 -2
  23. package/src/controller/index.ts +9 -0
  24. package/src/controller/popup.ts +346 -0
  25. package/src/controller/utils.ts +48 -0
  26. package/src/entify/Entify.ts +354 -365
  27. package/src/entify/IEntify.ts +91 -0
  28. package/src/entify/index.ts +3 -2
  29. package/src/entify/lib/newQueryProductOptions.ts +2 -3
  30. package/src/entify/lib/newQueryProductsMediaOptions.ts +19 -18
  31. package/src/entify/lib/queryAllProducts.ts +11 -3
  32. package/src/entify/lib/queryFeaturedProducts.ts +3 -3
  33. package/src/entify/lib/queryLatestPosts.ts +2 -2
  34. package/src/entify/lib/queryOneTheme.ts +1 -1
  35. package/src/entify/lib/queryPostCategories.ts +3 -3
  36. package/src/entify/lib/queryPostSlugs.ts +2 -2
  37. package/src/entify/lib/queryPosts.ts +92 -92
  38. package/src/entify/lib/queryProductCategories.ts +3 -3
  39. package/src/entify/lib/queryProducts.ts +69 -69
  40. package/src/entify/lib/queryUserPosts.ts +2 -2
  41. package/src/entify/lib/searchProducts.ts +2 -2
  42. package/src/index.ts +3 -1
  43. package/src/lib/formatDate.ts +15 -0
  44. package/src/lib/index.ts +3 -0
  45. package/src/lib/pagination.ts +114 -0
  46. package/src/lib/utils.ts +119 -0
  47. package/src/motion/consts.ts +428 -598
  48. package/src/motion/convertToGsapVars.ts +102 -0
  49. package/src/motion/index.ts +5 -1
  50. package/src/motion/normalizeAnimation.ts +28 -0
  51. package/src/motion/normalizeAosAnimation.ts +22 -0
  52. package/src/motion/normalizePopupAnimation.ts +24 -0
  53. package/src/motion/types.ts +133 -46
  54. package/src/react/components/AttachmentIcon/index.tsx +53 -0
  55. package/src/react/components/ContactForm/index.tsx +341 -0
  56. package/src/react/components/Icon/index.tsx +10 -0
  57. package/src/react/components/Medias/index.tsx +347 -347
  58. package/src/react/components/ProductCard/ProductCta/index.tsx +7 -5
  59. package/src/react/components/RichTextOutline/index.tsx +76 -76
  60. package/src/react/components/Scroller.tsx +5 -1
  61. package/src/react/components/SearchInput.tsx +36 -34
  62. package/src/react/components/ToTop.tsx +63 -28
  63. package/src/react/components/index.ts +3 -1
  64. package/src/react/hooks/useScroll.ts +16 -10
  65. package/src/react/components/EnquiryForm/index.tsx +0 -334
  66. package/src/scripts/actions.ts +0 -304
  67. package/src/scripts/events.ts +0 -33
  68. package/src/scripts/index.ts +0 -3
  69. /package/src/react/components/{EnquiryForm → ContactForm}/Input.tsx +0 -0
  70. /package/src/react/components/{EnquiryForm → ContactForm}/Submit.tsx +0 -0
  71. /package/src/react/components/{EnquiryForm → ContactForm}/Textarea.tsx +0 -0
@@ -0,0 +1,161 @@
1
+ import { DATA_MOTION_NUMBER, DATA_MOTION_NUMBER_ONCE } from "../motion/consts";
2
+ import { gsap } from "gsap/dist/gsap";
3
+
4
+ export class NumberController {
5
+ private static instances: Record<string, NumberController> = {};
6
+ private doc?: Document;
7
+ private observers = new Map();
8
+
9
+ private constructor(doc?: Document) {
10
+ this.doc = doc;
11
+ }
12
+
13
+ /**
14
+ * 获取 NumberController 实例
15
+ * @param key 实例的唯一标识符
16
+ * @param doc 文档对象
17
+ * @returns NumberController 实例
18
+ */
19
+ public static getInstance(
20
+ key: string = "runtime",
21
+ doc?: Document
22
+ ): NumberController {
23
+ if (!NumberController.instances[key]) {
24
+ NumberController.instances[key] = new NumberController(doc);
25
+ } else if (doc) {
26
+ NumberController.instances[key].setDoc(doc);
27
+ }
28
+ return NumberController.instances[key];
29
+ }
30
+
31
+ /**
32
+ * 销毁指定 key 的实例
33
+ * @param key 实例的唯一标识符
34
+ */
35
+ public static destroyInstance(key: string = "runtime"): void {
36
+ if (NumberController.instances[key]) {
37
+ delete NumberController.instances[key];
38
+ }
39
+ }
40
+
41
+ /**
42
+ * 设置文档对象
43
+ * @param doc 文档对象
44
+ */
45
+ public setDoc(doc: Document): void {
46
+ this.doc = doc;
47
+ }
48
+
49
+ mount() {
50
+ // 清理之前的观察器
51
+ this.unmount();
52
+
53
+ // 检查文档对象是否存在
54
+ if (!this.doc) {
55
+ console.error("文档对象未设置,无法初始化数字动画");
56
+ return;
57
+ }
58
+
59
+ // 使用正确的属性选择器
60
+ const elements = this.doc.querySelectorAll(`[${DATA_MOTION_NUMBER}]`);
61
+
62
+ elements?.forEach((element) => {
63
+ if (
64
+ this.doc?.defaultView?.HTMLElement &&
65
+ element instanceof this.doc?.defaultView?.HTMLElement
66
+ ) {
67
+ const dataAttr = element.getAttribute(DATA_MOTION_NUMBER);
68
+ if (dataAttr) {
69
+ try {
70
+ const data = JSON.parse(dataAttr);
71
+ // 确保 start 和 end 是数字类型
72
+ const startValue = Number(data.start);
73
+ const endValue = Number(data.end);
74
+ const transition = data.transition;
75
+ const once =
76
+ element.getAttribute(DATA_MOTION_NUMBER_ONCE) === "true";
77
+
78
+ const numberElement = element.querySelector(
79
+ ".motion-safe\\:animate-number"
80
+ ) as HTMLElement;
81
+ const signElement = element.querySelector(
82
+ ".motion-safe\\:animate-sign"
83
+ ) as HTMLElement;
84
+
85
+ if (!numberElement) {
86
+ return;
87
+ }
88
+
89
+ // 重置初始状态
90
+ numberElement.innerHTML = startValue.toString();
91
+ if (signElement) {
92
+ signElement.style.display = "none";
93
+ }
94
+
95
+ // 使用 IntersectionObserver 监听元素进入视口
96
+ const observer = new IntersectionObserver(
97
+ (entries) => {
98
+ if (entries[0].isIntersecting) {
99
+ const aleadyRun =
100
+ numberElement.dataset.animationNumberRun === "true";
101
+ if (aleadyRun && once) {
102
+ return;
103
+ }
104
+
105
+ numberElement.dataset.animationNumberRun = "true";
106
+ // 使用 GSAP 动画替代 motion.animate
107
+ const obj = { value: startValue };
108
+ gsap.to(obj, {
109
+ value: endValue,
110
+ duration: transition?.duration || 1,
111
+ ease: transition?.ease || "power2.out",
112
+ onUpdate: () => {
113
+ numberElement.innerHTML = Math.round(
114
+ obj.value
115
+ )?.toString();
116
+ },
117
+ onComplete: () => {
118
+ if (signElement) {
119
+ signElement.style.display = "inline";
120
+ }
121
+ },
122
+ });
123
+
124
+ if (once) {
125
+ observer.disconnect();
126
+ this.observers.delete(element);
127
+ }
128
+ } else if (!once) {
129
+ numberElement.innerHTML = startValue.toString();
130
+ if (signElement) {
131
+ signElement.style.display = "none";
132
+ console.log("隐藏符号元素");
133
+ }
134
+ }
135
+ },
136
+ { threshold: 0.1, rootMargin: "0px 0px -10% 0px" }
137
+ );
138
+
139
+ observer.observe(element);
140
+ this.observers.set(element, observer);
141
+ } catch (error) {
142
+ console.error("处理数字动画时出错:", error);
143
+ }
144
+ }
145
+ }
146
+ });
147
+ }
148
+
149
+ public unmount() {
150
+ this.observers.forEach((observer) => {
151
+ observer.disconnect();
152
+ });
153
+ this.observers.clear();
154
+ }
155
+ }
156
+
157
+ // 导出默认实例
158
+ export const numberController = NumberController.getInstance(
159
+ "runtime",
160
+ typeof document !== "undefined" ? document : undefined
161
+ );
@@ -0,0 +1,163 @@
1
+ export interface IPageLoader {
2
+ onLoaded: (callback: () => void) => () => void;
3
+ destroy: () => void;
4
+ }
5
+
6
+ export type PageLoaderKey = "runtime" | "preview" | "design";
7
+ export class PageLoader implements IPageLoader {
8
+ private static instances: Record<string, PageLoader> = {};
9
+ private doc?: Document;
10
+ private key: string;
11
+ private callbacks: Set<() => void> = new Set();
12
+ private isSwapEventRegistered = false;
13
+
14
+ private constructor(key: PageLoaderKey, doc?: Document) {
15
+ this.key = key;
16
+ this.doc = doc;
17
+ if (doc) {
18
+ this.registerMainEventListeners();
19
+ }
20
+ }
21
+
22
+ /**
23
+ * 获取 PageLoader 实例
24
+ * @param key 实例的唯一标识,默认为 "preview"
25
+ * @param doc 文档对象
26
+ * @returns PageLoader 实例
27
+ */
28
+ public static getInstance(
29
+ key: PageLoaderKey = "preview",
30
+ doc?: Document
31
+ ): PageLoader {
32
+ if (!PageLoader.instances[key]) {
33
+ PageLoader.instances[key] = new PageLoader(key, doc);
34
+ } else if (doc) {
35
+ PageLoader.instances[key].setDoc(doc);
36
+ }
37
+ return PageLoader.instances[key];
38
+ }
39
+
40
+ /**
41
+ * 销毁指定 key 的实例
42
+ * @param key 实例的唯一标识
43
+ */
44
+ public static destroyInstance(key: PageLoaderKey = "preview"): void {
45
+ if (PageLoader.instances[key]) {
46
+ PageLoader.instances[key].destroy();
47
+ delete PageLoader.instances[key];
48
+ }
49
+ }
50
+
51
+ /**
52
+ * 设置文档对象
53
+ * @param doc 文档对象
54
+ */
55
+ private setDoc(doc?: Document) {
56
+ if (this.doc !== doc) {
57
+ this.doc = doc;
58
+ if (doc) {
59
+ this.registerMainEventListeners();
60
+ }
61
+ }
62
+ }
63
+
64
+ private registerMainEventListeners() {
65
+ if (!this.doc || this.isSwapEventRegistered) return;
66
+
67
+ try {
68
+ // 使用标志来确保回调只执行一次
69
+ let hasExecuted = false;
70
+
71
+ // 主事件处理函数,调用所有注册的回调
72
+ const handleEvent = () => {
73
+ // 如果已经执行过,则不再执行
74
+ if (hasExecuted) return;
75
+
76
+ hasExecuted = true;
77
+
78
+ // 执行所有回调
79
+ this.callbacks.forEach((callback) => {
80
+ try {
81
+ callback();
82
+ } catch (error) {
83
+ console.error("执行回调失败", error);
84
+ }
85
+ });
86
+
87
+ // 重置执行标志,允许下一次页面转场时再次执行
88
+ setTimeout(() => {
89
+ hasExecuted = false;
90
+ }, 100);
91
+ };
92
+
93
+ // 只监听一个事件,优先使用 astro:page-load
94
+ if (typeof window !== "undefined") {
95
+ // 在客户端环境中
96
+ if (
97
+ document.readyState === "complete" ||
98
+ document.readyState === "interactive"
99
+ ) {
100
+ // 如果文档已加载完成,立即执行一次
101
+ setTimeout(handleEvent, 0);
102
+ } else {
103
+ // 否则等待 DOMContentLoaded 事件
104
+ this.doc.addEventListener("DOMContentLoaded", handleEvent, {
105
+ once: true,
106
+ });
107
+ }
108
+
109
+ // 只监听页面转场事件
110
+ this.doc.addEventListener("astro:after-swap", handleEvent);
111
+ }
112
+
113
+ this.isSwapEventRegistered = true;
114
+ } catch (error) {
115
+ console.error("注册事件监听器失败", error);
116
+ }
117
+ }
118
+
119
+ public onLoaded = (callback: () => void): (() => void) => {
120
+ // 如果不是 runtime,直接执行回调
121
+ if (this.key && this.key !== "runtime") {
122
+ callback();
123
+ return () => {};
124
+ }
125
+ // 检查回调是否已经注册过
126
+ if (this.callbacks.has(callback)) {
127
+ // 如果已注册,直接返回清理函数
128
+ return () => {
129
+ this.callbacks.delete(callback);
130
+ };
131
+ }
132
+
133
+ // 添加回调到集合
134
+ this.callbacks.add(callback);
135
+
136
+ // 不再在这里立即执行回调,而是完全依赖事件系统
137
+
138
+ // 返回清理函数
139
+ return () => {
140
+ this.callbacks.delete(callback);
141
+ };
142
+ };
143
+
144
+ public destroy(): void {
145
+ if (this.doc && this.isSwapEventRegistered) {
146
+ // 移除所有事件监听器
147
+ const noop = () => {};
148
+ this.doc.removeEventListener("astro:after-swap", noop);
149
+ this.doc.removeEventListener("DOMContentLoaded", noop);
150
+
151
+ // 清空回调集合
152
+ this.callbacks.clear();
153
+ this.isSwapEventRegistered = false;
154
+ }
155
+ this.doc = undefined;
156
+ }
157
+ }
158
+
159
+ // 导出单例实例,方便直接使用
160
+ export const pageLoader = PageLoader.getInstance(
161
+ "runtime",
162
+ typeof document !== "undefined" ? document : undefined
163
+ );
@@ -0,0 +1,116 @@
1
+ import { DATA_POPUP, DATA_POPUP_ROLE, PopupRole } from "./consts";
2
+ import { PopupController } from "./popup";
3
+
4
+ export class PopoverController extends PopupController {
5
+ private static instances: Record<string, PopoverController> = {};
6
+ private constructor(doc?: Document) {
7
+ super(doc);
8
+ }
9
+ /**
10
+ * 获取 PopoverController 实例
11
+ * @param key 实例的唯一标识,默认为 "runtime"
12
+ * @param doc 文档对象
13
+ * @returns PopoverController 实例
14
+ */
15
+ public static getInstance(
16
+ key: string = "runtime",
17
+ doc?: Document
18
+ ): PopoverController {
19
+ if (!PopoverController.instances[key]) {
20
+ PopoverController.instances[key] = new PopoverController(doc);
21
+ } else if (doc) {
22
+ PopoverController.instances[key].setDoc(doc);
23
+ }
24
+ return PopoverController.instances[key];
25
+ }
26
+
27
+ /**
28
+ * 销毁指定 key 的实例
29
+ * @param key 实例的唯一标识
30
+ */
31
+ public static destroyInstance(key: string = "runtime"): void {
32
+ if (PopoverController.instances[key]) {
33
+ PopoverController.instances[key].destroy();
34
+ delete PopoverController.instances[key];
35
+ }
36
+ }
37
+
38
+ private setDoc(doc?: Document) {
39
+ this.doc = doc;
40
+ }
41
+
42
+ public getDocument(): Document | null {
43
+ return this.doc ?? null;
44
+ }
45
+
46
+ mount() {
47
+ //console.log("====> mount popover", this.doc);
48
+ super.mount();
49
+ // 获取所有的 Popover 实例
50
+ const popups = this.doc?.querySelectorAll(`[${DATA_POPUP}]`);
51
+ popups?.forEach((popup) => {
52
+ const popupKey = popup.getAttribute(DATA_POPUP);
53
+ //处理鼠标交互事件
54
+ if (popupKey && popup) {
55
+ if (
56
+ popup.getAttribute(DATA_POPUP_ROLE) === PopupRole.PopoverContainer
57
+ ) {
58
+ const unsub = this.initPopover(popupKey, popup as HTMLElement);
59
+ this.unmountHandlers.push(unsub);
60
+ }
61
+ }
62
+ });
63
+ }
64
+
65
+ unmount(): void {
66
+ super.unmount();
67
+ }
68
+
69
+ /**
70
+ * 初始化Popover的鼠标交互事件
71
+ * @param popupKey - 弹出层的唯一标识
72
+ * @param element - 弹出层的 DOM 元素
73
+ * @returns 清理函数,用于移除事件监听器
74
+ */
75
+ public initPopover = (
76
+ popupKey: string,
77
+ element: HTMLElement
78
+ ): (() => void) => {
79
+ if (!this.doc) return () => {};
80
+
81
+ try {
82
+ // 创建新的事件处理函数
83
+ const handleMouseEnter = () => {
84
+ this.open(popupKey, element);
85
+ };
86
+
87
+ const handleMouseLeave = () => {
88
+ this.close(popupKey);
89
+ };
90
+
91
+ // 添加新的事件监听器
92
+ element.addEventListener("mouseenter", handleMouseEnter);
93
+ element.addEventListener("mouseleave", handleMouseLeave);
94
+
95
+ // 使用类型断言来解决 TypeScript 类型问题
96
+ const options = { passive: true };
97
+ element.addEventListener("touchstart", handleMouseEnter, options as EventListenerOptions);
98
+ element.addEventListener("touchend", handleMouseLeave, options as EventListenerOptions);
99
+
100
+ return () => {
101
+ element.removeEventListener("mouseenter", handleMouseEnter);
102
+ element.removeEventListener("mouseleave", handleMouseLeave);
103
+ element.removeEventListener("touchstart", handleMouseEnter, options as EventListenerOptions);
104
+ element.removeEventListener("touchend", handleMouseLeave, options as EventListenerOptions);
105
+ };
106
+ } catch (error) {
107
+ console.error(`初始化Popover事件失败: ${popupKey}`, error);
108
+ return () => {};
109
+ }
110
+ };
111
+ }
112
+
113
+ export const popover = PopoverController.getInstance(
114
+ "runtime",
115
+ typeof document !== "undefined" ? document : undefined
116
+ );
@@ -0,0 +1,271 @@
1
+ import {
2
+ DATA_TABS,
3
+ DATA_TABS_SELECTION,
4
+ DATA_TABS_TAB,
5
+ DATA_TABS_PANEL,
6
+ DATA_MOTION_SELECTION,
7
+ } from "../component-logic";
8
+ import { AnimationConfig } from "../motion";
9
+ import { applyAnimation } from "./applyAnimation";
10
+ import { EVENT_SELECT, EVENT_UNSELECT } from "./consts";
11
+ import { EventBus, SelectionEvent } from "./popup";
12
+
13
+ export class TabsController {
14
+ private static instances: Record<string, TabsController> = {};
15
+ private doc?: Document;
16
+ protected eventBus = new EventBus();
17
+ protected unmountHandlers: (() => void)[] = [];
18
+
19
+ private constructor(doc?: Document) {
20
+ this.doc = doc;
21
+ }
22
+
23
+ /**
24
+ * 获取 TabsController 实例
25
+ * @param key 实例的唯一标识,默认为 "runtime"
26
+ * @param doc 文档对象
27
+ * @returns TabsController 实例
28
+ */
29
+ public static getInstance(
30
+ key: string = "runtime",
31
+ doc?: Document
32
+ ): TabsController {
33
+ if (!TabsController.instances[key]) {
34
+ TabsController.instances[key] = new TabsController(doc);
35
+ } else if (doc) {
36
+ TabsController.instances[key].setDoc(doc);
37
+ }
38
+ return TabsController.instances[key];
39
+ }
40
+
41
+ /**
42
+ * 销毁指定 key 的实例
43
+ * @param key 实例的唯一标识
44
+ */
45
+ public static destroyInstance(key: string = "runtime"): void {
46
+ if (TabsController.instances[key]) {
47
+ TabsController.instances[key].destroy();
48
+ delete TabsController.instances[key];
49
+ }
50
+ }
51
+
52
+ private setDoc(doc?: Document) {
53
+ this.doc = doc;
54
+ }
55
+
56
+ mount() {
57
+ if (!this.doc) return () => {};
58
+ this.unmount();
59
+ // 实现标签页的挂载逻辑
60
+ const tabsList = this.doc.querySelectorAll(`[${DATA_TABS}]`);
61
+ tabsList.forEach((tabs) => {
62
+ const tabsId = tabs.getAttribute(DATA_TABS);
63
+ const tabsSelection = tabs.getAttribute(DATA_TABS_SELECTION);
64
+ const tabsTabs = tabs.querySelectorAll(`[${DATA_TABS_TAB}]`);
65
+ const tabsPanels = tabs.querySelectorAll(`[${DATA_TABS_PANEL}]`);
66
+ if (!tabsId) {
67
+ return;
68
+ }
69
+ // 初始化选中标签
70
+ tabsTabs.forEach((tab, index) => {
71
+ if (tabsSelection && tabsSelection === (index + 1).toString()) {
72
+ tab.classList.add("selected");
73
+ } else {
74
+ tab.classList.remove("selected");
75
+ }
76
+
77
+ // 给标签添加点击事件
78
+ const handleTabClick = () => {
79
+ const oldSeledction = tabs.getAttribute(DATA_TABS_SELECTION);
80
+ const newSelection = (index + 1).toString();
81
+ // 如果标签已经选中,则不进行操作
82
+ if (oldSeledction === newSelection) {
83
+ return;
84
+ }
85
+ // 设置选中的标签
86
+ tabs.setAttribute(DATA_TABS_SELECTION, newSelection);
87
+ // 发出选中事件
88
+ this.select(tabsId, tab as HTMLElement, newSelection);
89
+ // 移除所有标签的选中状态
90
+ tabsTabs.forEach((tab, subIndex) => {
91
+ const tabKey = (subIndex + 1).toString();
92
+ if (oldSeledction === tabKey) {
93
+ tab.classList.remove("selected");
94
+ // 取消选中
95
+ this.unselect(tabsId, tab as HTMLElement, tabKey);
96
+ }
97
+ });
98
+ tab.classList.add("selected");
99
+
100
+ // 设置选中的面板
101
+ tabsPanels.forEach((panel, subIndex) => {
102
+ panel.classList.remove("selected");
103
+ if (index === subIndex) {
104
+ panel.classList.add("selected");
105
+ }
106
+ });
107
+ };
108
+
109
+ // 添加新的事件监听器
110
+ tab.addEventListener("click", handleTabClick);
111
+ this.unmountHandlers.push(() => {
112
+ tab.removeEventListener("click", handleTabClick);
113
+ });
114
+ });
115
+
116
+ // 初始化选中面板
117
+ tabsPanels.forEach((panel, index) => {
118
+ if (tabsSelection && tabsSelection === (index + 1).toString()) {
119
+ panel.classList.add("selected");
120
+ } else {
121
+ panel.classList.remove("selected");
122
+ }
123
+ });
124
+ });
125
+ const unsub2 = this.initTabsMotion();
126
+ this.unmountHandlers.push(unsub2);
127
+ return () => {
128
+ // 返回清理函数
129
+ this.unmountHandlers.forEach((unmountHandler) => {
130
+ unmountHandler();
131
+ });
132
+ };
133
+ }
134
+
135
+ //初始化选项卡Selection动画
136
+ initTabsMotion() {
137
+ const unsub = this.onSelected((event: SelectionEvent) => {
138
+ //查找对应的data-motion-selection元素
139
+ const tabsElement = this.doc?.querySelector(
140
+ `[${DATA_TABS}=${event.key}]`
141
+ );
142
+
143
+ if (
144
+ this.doc?.defaultView &&
145
+ tabsElement instanceof this.doc?.defaultView.HTMLElement
146
+ ) {
147
+ //该容器下,所有带有选中动画的元素
148
+ tabsElement
149
+ .querySelectorAll(`[${DATA_MOTION_SELECTION}]`)
150
+ .forEach((element) => {
151
+ const motions = (element as HTMLElement).dataset.motionSelection;
152
+ if (motions) {
153
+ const animation = JSON.parse(motions) as
154
+ | AnimationConfig
155
+ | undefined;
156
+ if (animation) {
157
+ applyAnimation(element, animation);
158
+ }
159
+ }
160
+ });
161
+ }
162
+ });
163
+
164
+ const unsub2 = this.onUnSelected((event: SelectionEvent) => {
165
+ //查找对应的data-motion-selection元素
166
+ const tabsElement = this.doc?.querySelector(
167
+ `[${DATA_TABS}=${event.key}]`
168
+ );
169
+
170
+ if (
171
+ this.doc?.defaultView &&
172
+ tabsElement instanceof this.doc?.defaultView.HTMLElement
173
+ ) {
174
+ //该容器下,所有带有选中动画的元素
175
+ // tabsElement
176
+ // .querySelectorAll(`[${DATA_MOTION_SELECTION}]`)
177
+ // .forEach((element) => {
178
+ // const motions = (element as HTMLElement).dataset.motionSelection;
179
+ // if (motions) {
180
+ // const animation = JSON.parse(motions) as
181
+ // | AnimationConfig
182
+ // | undefined;
183
+ // if (animation) {
184
+ // applyAnimation(element, animation);
185
+ // }
186
+ // }
187
+ // });
188
+ }
189
+ });
190
+
191
+ return () => {
192
+ unsub();
193
+ unsub2();
194
+ };
195
+ }
196
+
197
+ unmount() {
198
+ this.unmountHandlers.forEach((handler) => handler());
199
+ this.unmountHandlers = [];
200
+ }
201
+ /**
202
+ * 监听选项被选中事件
203
+ *
204
+ * @param callback - 选项被选中时的回调函数
205
+ * @returns 取消监听的函数
206
+ */
207
+ public onSelected = (
208
+ callback: (event: SelectionEvent) => VoidFunction | void
209
+ ): (() => void) => {
210
+ if (!this.doc) return () => {};
211
+
212
+ return this.eventBus.on(EVENT_SELECT, callback);
213
+ };
214
+
215
+ /**
216
+ * 监听选项取消选中事件
217
+ *
218
+ * @param callback - 选项取消选中时的回调函数
219
+ * @returns 取消监听的函数
220
+ */
221
+ public onUnSelected = (
222
+ callback: (event: SelectionEvent) => VoidFunction | void
223
+ ): (() => void) => {
224
+ if (!this.doc) return () => {};
225
+
226
+ return this.eventBus.on(EVENT_UNSELECT, callback);
227
+ };
228
+
229
+ /**
230
+ * 选择
231
+ * @param key - 选项的唯一标识
232
+ * @param target - 触发选择的目标元素
233
+ * @param selection - 选择的值
234
+ */
235
+ select = (key: string, target: HTMLElement, selection: string): void => {
236
+ if (this.doc) {
237
+ try {
238
+ this.eventBus.emit(EVENT_SELECT, { key, target, selection });
239
+ } catch (error) {
240
+ console.error(`触发选择事件失败: ${key}`, error);
241
+ }
242
+ }
243
+ };
244
+
245
+ /**
246
+ * 取消选择
247
+ * @param key - 选项的唯一标识
248
+ * @param target - 触发取消选择的目标元素
249
+ * @param selection - 取消选择的值
250
+ */
251
+ unselect = (key: string, target: HTMLElement, selection: string): void => {
252
+ if (this.doc) {
253
+ try {
254
+ this.eventBus.emit(EVENT_UNSELECT, { key, target, selection });
255
+ } catch (error) {
256
+ console.error(`触发取消选择事件失败: ${key}`, error);
257
+ }
258
+ }
259
+ };
260
+
261
+ destroy() {
262
+ // 实现销毁逻辑
263
+ this.eventBus.destroy();
264
+ this.unmount();
265
+ }
266
+ }
267
+
268
+ export const tabs = TabsController.getInstance(
269
+ "runtime",
270
+ typeof document !== "undefined" ? document : undefined
271
+ );