@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
@@ -1,334 +0,0 @@
1
- import { useEffect, useState } from "react";
2
- import { Input } from "./Input";
3
- import { Submit } from "./Submit";
4
- import { Textarea } from "./Textarea";
5
- import { onPopupClose, onPopupOpen } from "../../../scripts";
6
- import { DATA_POPUP_CTA } from "../../../scripts/consts";
7
- import clsx from "clsx";
8
-
9
- /**
10
- * 简单的加密函数,用于生成防机器人的加密字段
11
- * @param value 需要加密的值
12
- * @returns 加密后的字符串
13
- */
14
- const encrypt = (value: string, formSalt: string): string => {
15
- // 获取当前时间戳,精确到分钟级别(防止频繁变化)
16
- const timestamp = Math.floor(Date.now() / (60 * 1000));
17
-
18
- // 将时间戳与盐值和输入值组合
19
- const dataToEncrypt = `${formSalt}:${timestamp}:${value}`;
20
-
21
- // 使用简单的哈希算法
22
- let hash = 0;
23
- for (let i = 0; i < dataToEncrypt.length; i++) {
24
- const char = dataToEncrypt.charCodeAt(i);
25
- hash = (hash << 5) - hash + char;
26
- hash = hash & hash; // 转换为32位整数
27
- }
28
-
29
- // 转换为16进制字符串并添加时间戳信息
30
- return `${hash.toString(16)}_${timestamp}`;
31
- };
32
-
33
- /**
34
- * 验证加密字段是否有效
35
- * @param encryptedValue 加密后的字符串
36
- * @param originalValue 原始值(如蜜罐字段的值)
37
- * @param maxAgeMinutes 最大有效时间(分钟)
38
- * @returns 是否有效
39
- */
40
- export const verifyEncryption = (
41
- formSalt: string,
42
- encryptedValue: string = "",
43
- originalValue: string = "",
44
- maxAgeMinutes: number = 30
45
- ): boolean => {
46
- // 解析加密值
47
- const parts = encryptedValue.split("_");
48
- if (parts.length !== 2) {
49
- return false; // 格式不正确
50
- }
51
-
52
- const [hashString, timestampString] = parts;
53
- const timestamp = parseInt(timestampString, 10);
54
-
55
- // 验证时间戳是否在有效期内
56
- const currentTimestamp = Math.floor(Date.now() / (60 * 1000));
57
- if (isNaN(timestamp) || currentTimestamp - timestamp > maxAgeMinutes) {
58
- return false; // 时间戳无效或已过期
59
- }
60
-
61
- // 重新计算哈希值
62
- const dataToEncrypt = `${formSalt}:${timestamp}:${originalValue}`;
63
- let hash = 0;
64
- for (let i = 0; i < dataToEncrypt.length; i++) {
65
- const char = dataToEncrypt.charCodeAt(i);
66
- hash = (hash << 5) - hash + char;
67
- hash = hash & hash;
68
- }
69
-
70
- // 比较哈希值
71
- return hashString === hash.toString(16);
72
- };
73
-
74
- export interface QuoteRequest {
75
- name?: string;
76
- email?: string;
77
- company?: string;
78
- message?: string;
79
- fromCta?: string;
80
- // 蜜罐字段,用于检测机器人
81
- phone: string;
82
- // 一个加密字段,用于防机器人,点击时附加一个加密的字符串
83
- encryptedField?: string;
84
- }
85
- interface FormErrors {
86
- name?: string;
87
- email?: string;
88
- message?: string;
89
- }
90
-
91
- export function EnquiryForm(props: {
92
- submitAlign?: "left" | "center" | "right";
93
- actionUrl?: string;
94
- formSalt: string;
95
- }) {
96
- const {
97
- submitAlign = "right",
98
- actionUrl = "/api/ask-for-quote",
99
- formSalt,
100
- } = props;
101
- const [formData, setFormData] = useState<QuoteRequest>({
102
- name: "",
103
- email: "",
104
- company: "",
105
- message: "",
106
- fromCta: "",
107
- phone: "", // 初始化蜜罐字段
108
- });
109
- // 错误状态
110
- const [errors, setErrors] = useState<FormErrors>({});
111
- const [submitting, setSubmitting] = useState(false);
112
- const [submitStatus, setSubmitStatus] = useState<{
113
- success?: boolean;
114
- message?: string;
115
- }>({});
116
- //最近被点击过的cta
117
-
118
- useEffect(() => {
119
- const unsub = onPopupOpen((event) => {
120
- setFormData((prev) => ({
121
- ...prev,
122
- fromCta: event.target?.getAttribute(DATA_POPUP_CTA) || undefined,
123
- }));
124
- });
125
- const unsub2 = onPopupClose(() => {
126
- setFormData((prev) => ({
127
- ...prev,
128
- fromCta: "",
129
- }));
130
- });
131
- return () => {
132
- unsub();
133
- unsub2();
134
- };
135
- }, []);
136
-
137
- // 处理输入变化
138
- const handleChange = (
139
- e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
140
- ) => {
141
- const { name, value } = e.target;
142
- setFormData((prev) => ({
143
- ...prev,
144
- [name]: value,
145
- }));
146
-
147
- setSubmitStatus({}); // 重置提交状态
148
-
149
- // 清除对应字段的错误
150
- if (errors[name as keyof FormErrors]) {
151
- setErrors((prev) => ({
152
- ...prev,
153
- [name]: undefined,
154
- }));
155
- }
156
- };
157
- // 验证表单
158
- const validateForm = (): boolean => {
159
- const newErrors: FormErrors = {};
160
-
161
- // if (!formData.name?.trim()) {
162
- // newErrors.name = "Please enter your name";
163
- // }
164
-
165
- if (!formData.email?.trim()) {
166
- newErrors.email = "Please enter your email";
167
- } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
168
- newErrors.email = "Please enter a valid email address";
169
- }
170
-
171
- if (!formData.message?.trim()) {
172
- newErrors.message = "Please enter your message";
173
- }
174
-
175
- // 检查蜜罐字段 - 如果填写了则表示是机器人
176
- if (formData.phone) {
177
- // 悄悄失败,不显示错误信息
178
- console.log("Honeypot triggered - likely spam submission");
179
- return false;
180
- }
181
-
182
- setErrors(newErrors);
183
- return Object.keys(newErrors).length === 0;
184
- };
185
-
186
- const handleClick = async (event: React.MouseEvent) => {
187
- event.preventDefault(); // 阻止表单默认提交行为
188
-
189
- // 验证表单
190
- if (!validateForm()) {
191
- return; // 如果验证失败,不继续提交
192
- }
193
-
194
- try {
195
- setSubmitting(true);
196
- setSubmitStatus({}); // 重置提交状态
197
-
198
- const response = await fetch(actionUrl, {
199
- method: "POST",
200
- body: JSON.stringify({
201
- ...formData,
202
- encryptedField: encrypt(formData.phone, formSalt),
203
- }),
204
- headers: {
205
- "X-Request-URL": window.location.href,
206
- "Content-Type": "application/json",
207
- },
208
- });
209
-
210
- if (!response.ok) {
211
- throw new Error(`Server responded with status: ${response.status}`);
212
- }
213
-
214
- const result = await response.json();
215
-
216
- setSubmitStatus({
217
- success: result.success,
218
- message: result.message,
219
- });
220
-
221
- if (result.success) {
222
- // 重置表单
223
- setFormData({
224
- name: "",
225
- email: "",
226
- company: "",
227
- message: "",
228
- fromCta: "产品页面",
229
- phone: "",
230
- });
231
- window.location.href = "/thanks";
232
- }
233
- } catch (error) {
234
- // 如果出现错误,打印错误信息
235
- console.error("Form submission error:", error);
236
- setSubmitStatus({
237
- success: false,
238
- message:
239
- error instanceof Error
240
- ? `Error: ${error.message}`
241
- : "Failed to submit the form. Please try again later.",
242
- });
243
- } finally {
244
- setSubmitting(false);
245
- }
246
- };
247
-
248
- return (
249
- <div className="py-4 grid max-w-2xl grid-cols-1 gap-x-6 gap-y-6 sm:grid-cols-2">
250
- <Input
251
- className="sm:col-span-1"
252
- inputClassName="mt-2 block w-full rounded-md outline-none border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-sky-600 sm:text-sm sm:leading-6"
253
- label="Your Email *"
254
- labelClassName="block text-sm font-medium leading-6 text-gray-900"
255
- name="email"
256
- required={true}
257
- autoFocus={true}
258
- type="email"
259
- value={formData.email}
260
- onChange={handleChange}
261
- error={errors.email}
262
- />
263
- <Input
264
- className="sm:col-span-1"
265
- inputClassName="mt-2 block w-full rounded-md outline-none border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-sky-600 sm:text-sm sm:leading-6"
266
- label="Your Name"
267
- labelClassName="block text-sm font-medium leading-6 text-gray-900"
268
- name="name"
269
- value={formData.name}
270
- onChange={handleChange}
271
- error={errors.name}
272
- />
273
- <Input
274
- className="col-span-full"
275
- inputClassName="mt-2 block w-full rounded-md outline-none border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-sky-600 sm:text-sm sm:leading-6"
276
- label="Company"
277
- labelClassName="block text-sm font-medium leading-6 text-gray-900"
278
- name="company"
279
- value={formData.company}
280
- onChange={handleChange}
281
- />
282
- <Textarea
283
- className="col-span-full"
284
- label="Message *"
285
- labelClassName="block text-sm font-medium leading-6 text-gray-900"
286
- name="message"
287
- required={true}
288
- textareaClassName="mt-2 block w-full rounded-md outline-none border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-sky-600 sm:text-sm sm:leading-6"
289
- value={formData.message}
290
- onChange={handleChange}
291
- error={errors.message}
292
- />
293
- {/* 蜜罐字段 - 对用户隐藏但对机器人可见 */}
294
- <div style={{ display: "none" }}>
295
- <input
296
- type="text"
297
- name="phone"
298
- value={formData.phone}
299
- onChange={handleChange}
300
- tabIndex={-1}
301
- autoComplete="off"
302
- />
303
- </div>
304
- <div
305
- className={clsx("col-span-full flex items-center gap-x-6 px-0 py-2", {
306
- "justify-start": submitAlign === "left",
307
- "justify-center": submitAlign === "center",
308
- "justify-end": submitAlign === "right",
309
- })}
310
- >
311
- {submitStatus.message && !submitStatus.success && (
312
- <div
313
- className={`text-sm ${
314
- submitStatus.success ? "text-green-600" : "text-red-600"
315
- }`}
316
- >
317
- {submitStatus.message}
318
- </div>
319
- )}
320
- <Submit
321
- className="flex gap-2 items-center relative shadow-sm btn btn-primary btn-lg nowrap"
322
- title="Send Message"
323
- spinner={
324
- <div className="left-8 flex items-center justify-center">
325
- <div className="animate-spin rounded-full h-5 w-5 border-t-2 border-b-2 border-white" />
326
- </div>
327
- }
328
- submitting={submitting}
329
- onClick={handleClick}
330
- />
331
- </div>
332
- </div>
333
- );
334
- }
@@ -1,304 +0,0 @@
1
- import {
2
- DATA_POPUP,
3
- DATA_POPUP_ROLE,
4
- EVENT_CLOSE,
5
- EVENT_OPEN,
6
- EVENT_SELECT,
7
- EVENT_UNSELECT,
8
- PopupRole,
9
- } from "./consts";
10
- import { $emit, $on, PopupEvent, SelectionEvent } from "./events";
11
- import { ANIMATIONS, DUAL_ANIMATIONS } from "../motion/consts";
12
- import { DualAnimation, SimpleAnimation } from "../motion/types";
13
-
14
- /**
15
- * 在每次页面切换后执行回调函数
16
- * 注意:此函数注册的事件监听器不会自动解注册,将在每次页面切换时执行
17
- *
18
- * @param init - 每次页面切换时要执行的回调函数
19
- * @returns 用于手动解注册事件的函数
20
- */
21
- export function onEverySwap(init: () => void) {
22
- document.addEventListener("astro:after-swap", init);
23
- // 返回一个函数,用于手动解注册事件
24
- return () => document.removeEventListener("astro:after-swap", init);
25
- }
26
-
27
- /**
28
- * 初始化动画,在 page-load 事件触发时执行
29
- *
30
- * @param init - 初始化动画的回调函数
31
- */
32
- export function onPageLoaded(init: () => void) {
33
- const doInit = () => {
34
- init();
35
- document.removeEventListener("astro:page-load", doInit);
36
- };
37
- document.addEventListener("astro:page-load", doInit);
38
- }
39
-
40
- /**
41
- * 用于在脚本中监听弹出层的打开/关闭事件,并处理相关动画效果
42
- * 注意:此函数主要用于脚本中的事件处理,组件内部已有相应实现,无需使用
43
- *
44
- * @param targetKey - 要监听的弹出层的唯一标识
45
- * @param callback - 弹出层打开时的回调函数,可返回关闭时的清理函数
46
- * @returns 取消监听的函数
47
- *
48
- * @example
49
- * ```typescript
50
- * // 监听产品菜单的打开/关闭事件并添加旋转动画
51
- * onOpen("products-popup", () => {
52
- * // 打开时顺时针旋转
53
- * animate("#products-indicator", {
54
- * rotate: [0, 180],
55
- * });
56
- *
57
- * // 返回关闭时的动画处理函数
58
- * return () => {
59
- * animate("#products-indicator", {
60
- * rotate: [180, 0],
61
- * });
62
- * };
63
- * });
64
- * ```
65
- */
66
- export function onOpen(
67
- targetKey: string,
68
- callback?: (evnet?: PopupEvent) => VoidFunction | void
69
- ) {
70
- return onPopupOpen((event) => {
71
- if (event.key === targetKey) {
72
- return callback?.(event);
73
- }
74
- });
75
- }
76
-
77
- /**
78
- * 用于在脚本中监听弹出层的打开/关闭事件,并处理相关动画效果
79
- * 注意:此函数主要用于脚本中的事件处理,组件内部已有相应实现,无需使用
80
- *
81
- * @param callback - 弹出层打开时的回调函数,可返回关闭时的清理函数
82
- * @returns 取消监听的函数
83
- *
84
- */
85
- export function onPopupOpen(
86
- callback: (event: PopupEvent) => VoidFunction | void
87
- ) {
88
- let unsubscribe: ((event: PopupEvent) => void) | void;
89
-
90
- const handleOpen = (event: PopupEvent) => {
91
- unsubscribe = callback?.(event);
92
- };
93
-
94
- const unsubscribeOpen = $on(EVENT_OPEN, handleOpen);
95
-
96
- const handelClose = (event: PopupEvent) => {
97
- unsubscribe?.(event);
98
- };
99
- const unsubscribeClose = $on(EVENT_CLOSE, handelClose);
100
-
101
- return () => {
102
- unsubscribeOpen();
103
- unsubscribeClose();
104
- };
105
- }
106
-
107
- export function onPopupClose(
108
- callback: (event: PopupEvent) => VoidFunction | void
109
- ) {
110
- return $on<PopupEvent>(EVENT_CLOSE, callback);
111
- }
112
-
113
- export function onSelected(
114
- callback: (event: SelectionEvent) => VoidFunction | void
115
- ) {
116
- return $on<SelectionEvent>(EVENT_SELECT, callback);
117
- }
118
-
119
- export function onUnSelected(
120
- callback: (event: SelectionEvent) => VoidFunction | void
121
- ) {
122
- return $on<SelectionEvent>(EVENT_UNSELECT, callback);
123
- }
124
-
125
- // 使用WeakMap存储事件处理函数引用
126
- const popoverHandlers = new WeakMap<
127
- HTMLElement,
128
- {
129
- enter: (event: Event) => void;
130
- leave: (event: Event) => void;
131
- }
132
- >();
133
-
134
- // 使用WeakMap存储Modal触发器的事件处理函数引用
135
- const modalHandlers = new WeakMap<HTMLElement, (event: Event) => void>();
136
-
137
- // 使用WeakMap存储Modal关闭按钮的事件处理函数引用
138
- const modalCloserHandlers = new WeakMap<HTMLElement, (event: Event) => void>();
139
-
140
- /**
141
- * 初始化Popover的鼠标交互事件
142
- * @param popupKey - 弹出层的唯一标识
143
- * @param element - 弹出层的 DOM 元素
144
- */
145
- export function initPopover(popupKey: string, element: HTMLElement) {
146
- // 先移除旧的事件监听器(如果存在)
147
- const oldHandlers = popoverHandlers.get(element);
148
- if (oldHandlers) {
149
- element.removeEventListener("mouseenter", oldHandlers.enter);
150
- element.removeEventListener("mouseleave", oldHandlers.leave);
151
- element.removeEventListener("touchstart", oldHandlers.enter);
152
- element.removeEventListener("touchend", oldHandlers.leave);
153
- }
154
-
155
- // 创建新的事件处理函数
156
- const handleMouseEnter = (event: Event) => {
157
- openPopup(popupKey, element);
158
- };
159
-
160
- const handleMouseLeave = (event: Event) => {
161
- closePopup(popupKey);
162
- };
163
-
164
- // 保存新的事件处理函数引用
165
- popoverHandlers.set(element, {
166
- enter: handleMouseEnter,
167
- leave: handleMouseLeave,
168
- });
169
-
170
- // 添加新的事件监听器
171
- element.addEventListener("mouseenter", handleMouseEnter);
172
- element.addEventListener("mouseleave", handleMouseLeave);
173
- element.addEventListener("touchstart", handleMouseEnter);
174
- element.addEventListener("touchend", handleMouseLeave);
175
- }
176
-
177
- export function getPopupContainer(popupKey: string) {
178
- return document.querySelector(
179
- `[${DATA_POPUP}="${popupKey}"][${DATA_POPUP_ROLE}="${PopupRole.ModalContainer}"], [${DATA_POPUP}="${popupKey}"][${DATA_POPUP_ROLE}="${PopupRole.PopoverContainer}"]`
180
- ) as HTMLElement | undefined;
181
- }
182
-
183
- export function initModal(popupKey: string, trigger: HTMLElement) {
184
- // 先移除旧的事件监听器(如果存在)
185
- const oldHandler = modalHandlers.get(trigger);
186
- if (oldHandler) {
187
- trigger.removeEventListener("click", oldHandler);
188
- }
189
-
190
- // 创建新的事件处理函数
191
- const handleTriggerClick = (event: Event) => {
192
- // 查找 Modal 容器
193
- openPopup(popupKey, trigger);
194
- };
195
-
196
- // 保存新的事件处理函数引用
197
- modalHandlers.set(trigger, handleTriggerClick);
198
-
199
- // 添加新的事件监听器
200
- trigger.addEventListener("click", handleTriggerClick);
201
- }
202
-
203
- /**
204
- * 初始化Modal关闭按钮
205
- * @param popupKey - 弹出层的唯一标识
206
- * @param closer - 关闭按钮元素
207
- */
208
- export function initModalCloser(popupKey: string, closer: HTMLElement) {
209
- // 先移除旧的事件监听器(如果存在)
210
- const oldHandler = modalCloserHandlers.get(closer);
211
- if (oldHandler) {
212
- closer.removeEventListener("click", oldHandler);
213
- }
214
-
215
- // 创建新的事件处理函数
216
- const handleCloserClick = (event: Event) => {
217
- closePopup(popupKey);
218
- };
219
-
220
- // 保存新的事件处理函数引用
221
- modalCloserHandlers.set(closer, handleCloserClick);
222
-
223
- // 添加新的事件监听器
224
- closer.addEventListener("click", handleCloserClick);
225
- }
226
-
227
- /**
228
- * 打开弹出层
229
- * @param popupKey
230
- * @param target
231
- */
232
- export function openPopup(popupKey: string, target: HTMLElement) {
233
- // 查找 Modal 容器
234
- const modalContainer = getPopupContainer(popupKey);
235
- if (modalContainer) {
236
- modalContainer.classList.add("open");
237
- $emit(EVENT_OPEN, { key: popupKey, target });
238
- } else {
239
- console.error("未找到弹出层", popupKey);
240
- }
241
- }
242
-
243
- // 选择
244
- export function select(key: string, target: HTMLElement, selection: string) {
245
- $emit(EVENT_SELECT, { key, target, selection });
246
- }
247
-
248
- // 取消选择
249
- export function unselect(key: string, target: HTMLElement, selection: string) {
250
- $emit(EVENT_UNSELECT, { key, target, selection });
251
- }
252
-
253
- /**
254
- * 关闭弹出层
255
- * @param popupKey - 弹出层的唯一标识
256
- * @param target - 可选的目标元素,如果提供则使用该元素,否则查找容器
257
- */
258
- export function closePopup(popupKey: string, target?: HTMLElement) {
259
- const container = target || getPopupContainer(popupKey);
260
- container?.classList.remove("open");
261
- $emit(EVENT_CLOSE, { key: popupKey });
262
- }
263
-
264
- export function normalizeSimpleAnimation(animation?: SimpleAnimation) {
265
- if (animation?.preset) {
266
- const preset = ANIMATIONS[animation?.preset];
267
- return {
268
- ...preset,
269
- keyframes: { ...preset.keyframes, ...animation.keyframes },
270
- transition: { ...preset.transition, ...animation.transition },
271
- };
272
- }
273
-
274
- return animation;
275
- }
276
-
277
- //处理预置动画
278
- export function normalizeDualAnimation(
279
- animation?: DualAnimation
280
- ): DualAnimation | undefined {
281
- if (!animation) {
282
- return undefined;
283
- }
284
-
285
- // 如果有预设,合并预设和自定义配置
286
- if (animation.preset) {
287
- const preset = DUAL_ANIMATIONS[animation.preset];
288
- return {
289
- ...animation,
290
- in: animation.in
291
- ? normalizeSimpleAnimation({ ...preset.in, ...animation.in })
292
- : preset.in,
293
- out: animation.out
294
- ? normalizeSimpleAnimation({ ...preset.out, ...animation.out })
295
- : preset.out,
296
- };
297
- }
298
-
299
- // 如果没有预设,但有 in/out 配置
300
- return {
301
- in: animation.in ? normalizeSimpleAnimation(animation.in) : undefined,
302
- out: animation.out ? normalizeSimpleAnimation(animation.out) : undefined,
303
- };
304
- }
@@ -1,33 +0,0 @@
1
- export type PopupEvent = {
2
- //控件组的唯一标识
3
- key: string;
4
- //发出事件的element,popover对应Container,modal对应Trigger
5
- target?: HTMLElement;
6
- };
7
-
8
- export type SelectionEvent = {
9
- //控件组的唯一标识
10
- key: string;
11
- //发出事件的element
12
- target: HTMLElement;
13
- selection: string;
14
- };
15
-
16
- export const $emit = (event: string, options?: PopupEvent | SelectionEvent) => {
17
- const customEvent = new CustomEvent(event, { detail: options });
18
- document.dispatchEvent(customEvent);
19
- };
20
-
21
- export const $on = <T = PopupEvent | SelectionEvent>(
22
- event: string,
23
- callback: (options: T) => void
24
- ) => {
25
- const eventListener = (e: CustomEvent) => {
26
- callback(e.detail);
27
- };
28
- document.addEventListener(event, eventListener as EventListener);
29
-
30
- return () => {
31
- document.removeEventListener(event, eventListener as EventListener);
32
- };
33
- };
@@ -1,3 +0,0 @@
1
- export * from "./actions";
2
- export * from "./consts";
3
- export * from "./events";