@ray-js/components 1.7.56-beta.5 → 1.7.56-beta.6

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.
@@ -0,0 +1,379 @@
1
+ import _extends from "@babel/runtime/helpers/esm/extends";
2
+ import "core-js/modules/es.regexp.exec.js";
3
+ import "core-js/modules/es.string.replace.js";
4
+ import "core-js/modules/esnext.iterator.constructor.js";
5
+ import "core-js/modules/esnext.iterator.for-each.js";
6
+ import "core-js/modules/esnext.iterator.map.js";
7
+ import "core-js/modules/web.dom-collections.iterator.js";
8
+ /**
9
+ * Animated - 纯净底层动画组件
10
+ *
11
+ * 只提供三个核心方法:
12
+ * - setProperty(property, value): 设置动画属性
13
+ * - setStyle(style): 设置样式
14
+ * - setText(text): 设置文本内容
15
+ *
16
+ * 可以配合任何动画库使用(GSAP、Anime.js、Framer Motion 等)
17
+ */
18
+ import React, { forwardRef, useImperativeHandle, useRef, useMemo, useCallback } from 'react';
19
+ import TyAnimatedComponent from './native/ty-animated';
20
+
21
+ // 小程序 API 声明
22
+
23
+ /**
24
+ * 样式对象转 CSS 字符串
25
+ */
26
+ function styleToString(style) {
27
+ return Object.entries(style).map(_ref => {
28
+ let [key, value] = _ref;
29
+ const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
30
+ return "".concat(cssKey, ":").concat(value);
31
+ }).join(';');
32
+ }
33
+
34
+ /**
35
+ * 基础 Animated 组件
36
+ */
37
+ const AnimatedBase = /*#__PURE__*/forwardRef((props, ref) => {
38
+ const {
39
+ children,
40
+ className,
41
+ style,
42
+ id,
43
+ mode = 'view',
44
+ onAnimationStart,
45
+ onAnimationEnd,
46
+ // 核心触摸事件(只保留最常用的三个)
47
+ onClick,
48
+ onTouchStart,
49
+ onTouchEnd,
50
+ // 事件节流时间
51
+ throttle,
52
+ throttleClick,
53
+ throttleTouchStart,
54
+ throttleTouchEnd
55
+ } = props;
56
+ const callbackRef = useRef(onAnimationEnd);
57
+ const componentIdRef = useRef(id || "animated-".concat(Math.random().toString(36).slice(2, 11)));
58
+
59
+ // 提取文本内容(当 mode='text' 时)
60
+ const textContent = useMemo(() => {
61
+ if (mode !== 'text' || !children) return undefined;
62
+ if (typeof children === 'string' || typeof children === 'number') {
63
+ return String(children);
64
+ }
65
+ return undefined;
66
+ }, [mode, children]);
67
+
68
+ /**
69
+ * 获取 DOM 实例
70
+ */
71
+ const getDomInstance = () => {
72
+ try {
73
+ var _getCurrentPages;
74
+ const pages = (_getCurrentPages = getCurrentPages) === null || _getCurrentPages === void 0 ? void 0 : _getCurrentPages();
75
+ if (!pages || pages.length === 0) return null;
76
+ const currentPage = pages[pages.length - 1];
77
+ if (!(currentPage !== null && currentPage !== void 0 && currentPage.selectComponent)) return null;
78
+ return currentPage.selectComponent("#".concat(componentIdRef.current));
79
+ } catch (error) {
80
+ console.error('[Animated] 获取组件实例失败:', error);
81
+ return null;
82
+ }
83
+ };
84
+ useImperativeHandle(ref, () => {
85
+ return {
86
+ /**
87
+ * 设置样式(直通通道)
88
+ * 用途:设置静态样式,绕过 React/Ray 层,提升性能
89
+ * @example
90
+ * ref.current.setStyle({ color: 'red', fontSize: '16px', border: '1px solid #ccc' })
91
+ */
92
+ setStyle(styleValue, callback) {
93
+ return new Promise(resolve => {
94
+ var _inst$setCustomStyle;
95
+ const inst = getDomInstance();
96
+ if (!inst) {
97
+ console.warn('No DOM instance found');
98
+ resolve();
99
+ if (callback) callback();
100
+ return;
101
+ }
102
+
103
+ // 防止传入 undefined 或空值
104
+ if (styleValue === undefined || styleValue === null) {
105
+ console.warn('styleValue is undefined or null');
106
+ resolve();
107
+ if (callback) callback();
108
+ return;
109
+ }
110
+ const styleString = typeof styleValue === 'string' ? styleValue : styleToString(styleValue);
111
+ if (!styleString) {
112
+ console.warn('styleString is empty');
113
+ resolve();
114
+ if (callback) callback();
115
+ return;
116
+ }
117
+
118
+ // setData 完成后的回调
119
+ (_inst$setCustomStyle = inst.setCustomStyle) === null || _inst$setCustomStyle === void 0 || _inst$setCustomStyle.call(inst, styleString, () => {
120
+ // 调用用户回调
121
+ if (callback && typeof callback === 'function') {
122
+ try {
123
+ callback();
124
+ } catch (error) {
125
+ console.error('[Animated] setStyle callback error:', error);
126
+ }
127
+ }
128
+ // resolve Promise
129
+ resolve();
130
+ });
131
+ });
132
+ },
133
+ /**
134
+ * 设置文本内容(仅 mode='text' 时可用)
135
+ * 不触发 React 重渲染
136
+ */
137
+ setText(text) {
138
+ var _inst$__applyText;
139
+ // 运行时检查:只有 Text 组件才能调用 setText
140
+ if (mode !== 'text') {
141
+ console.error('[Animated] setText 只能在 Animated.Text 组件上使用');
142
+ return;
143
+ }
144
+ const inst = getDomInstance();
145
+ if (!inst) {
146
+ console.warn('[Animated] setText: 无法获取组件实例');
147
+ return;
148
+ }
149
+ (_inst$__applyText = inst.__applyText) === null || _inst$__applyText === void 0 || _inst$__applyText.call(inst, text);
150
+ },
151
+ /**
152
+ * 创建动画对象
153
+ * 使用小程序原生动画 API,这是性能最优的动画方式
154
+ *
155
+ * @param options - 动画配置(duration, timingFunction, delay, transformOrigin)
156
+ * @returns Animation 实例,包含 play() 方法
157
+ *
158
+ * @example
159
+ * // 创建并执行动画
160
+ * const animation = ref.current.animate()
161
+ * animation.translateX(100).scale(1.2).step({ duration: 300 })
162
+ * animation.translateY(50).step({ duration: 200 })
163
+ * await animation.play()
164
+ * console.log('动画完成!')
165
+ *
166
+ * // 链式调用
167
+ * const animation = ref.current
168
+ * .animate({ transformOrigin: '50% 50%' })
169
+ * .translateX(100)
170
+ * .scale(1.2)
171
+ * .step({ duration: 300 })
172
+ * await animation.play()
173
+ *
174
+ * // 复用动画
175
+ * const slideIn = ref.current.animate().translateX(100).step({ duration: 300 })
176
+ * await slideIn.play() // 第一次
177
+ * await slideIn.play() // 第二次
178
+ */
179
+ animate(options) {
180
+ var _inst$_getCreateAnima;
181
+ const inst = getDomInstance();
182
+ if (!inst) {
183
+ console.warn('[Animated] No DOM instance found');
184
+ return null;
185
+ }
186
+
187
+ // 获取 createAnimation 函数
188
+ const createAnimation = (_inst$_getCreateAnima = inst._getCreateAnimation) === null || _inst$_getCreateAnima === void 0 ? void 0 : _inst$_getCreateAnima.call(inst);
189
+ if (!createAnimation) {
190
+ console.error('[Animated] createAnimation 不可用');
191
+ return null;
192
+ }
193
+
194
+ // 创建小程序原生 Animation 实例
195
+ const animation = createAnimation(options);
196
+
197
+ // 扩展 play 方法
198
+ animation.play = () => {
199
+ return new Promise(resolve => {
200
+ var _inst$__applyAnimData;
201
+ // 导出动画数据
202
+ const animationData = animation.export();
203
+
204
+ // 包装完成回调
205
+
206
+ callbackRef.current = event => {
207
+ resolve(event);
208
+ };
209
+ (_inst$__applyAnimData = inst.__applyAnimData) === null || _inst$__applyAnimData === void 0 || _inst$__applyAnimData.call(inst, animationData);
210
+ });
211
+ };
212
+ return animation;
213
+ },
214
+ /**
215
+ * 获取元素的布局位置和尺寸信息
216
+ * 基于小程序 SelectorQuery.boundingClientRect API
217
+ */
218
+ getBoundingClientRect() {
219
+ return new Promise(resolve => {
220
+ try {
221
+ // 检查小程序 API 是否存在
222
+ if (typeof ty === 'undefined' || !ty.createSelectorQuery) {
223
+ console.error('[Animated] getBoundingClientRect: ty.createSelectorQuery 不存在');
224
+ resolve(null);
225
+ return;
226
+ }
227
+ const query = ty.createSelectorQuery();
228
+ query.select("#".concat(componentIdRef.current)).boundingClientRect(rect => {
229
+ resolve(rect || null);
230
+ }).exec(() => {
231
+ // exec callback
232
+ });
233
+ } catch (error) {
234
+ console.error('[Animated] getBoundingClientRect 执行失败:', error);
235
+ resolve(null);
236
+ }
237
+ });
238
+ },
239
+ /**
240
+ * 获取元素的详细信息(包括样式、属性等)
241
+ * 基于小程序 SelectorQuery.fields API
242
+ */
243
+ getFields(fields) {
244
+ return new Promise(resolve => {
245
+ try {
246
+ // 检查小程序 API 是否存在
247
+ if (typeof ty === 'undefined' || !ty.createSelectorQuery) {
248
+ console.error('[Animated] getFields: ty.createSelectorQuery 不存在');
249
+ resolve(null);
250
+ return;
251
+ }
252
+ const query = ty.createSelectorQuery();
253
+ query.select("#".concat(componentIdRef.current)).fields(fields, res => {
254
+ resolve(res || null);
255
+ }).exec(() => {
256
+ // exec callback
257
+ });
258
+ } catch (error) {
259
+ console.error('[Animated] getFields 执行失败:', error);
260
+ resolve(null);
261
+ }
262
+ });
263
+ },
264
+ /**
265
+ * 获取元素的计算后样式
266
+ * 便捷方法,专门用于查询样式
267
+ */
268
+ getComputedStyle(styleNames) {
269
+ return new Promise(resolve => {
270
+ try {
271
+ // 检查小程序 API 是否存在
272
+ if (typeof ty === 'undefined' || !ty.createSelectorQuery) {
273
+ console.error('[Animated] getComputedStyle: ty.createSelectorQuery 不存在');
274
+ resolve(null);
275
+ return;
276
+ }
277
+ const query = ty.createSelectorQuery();
278
+ query.select("#".concat(componentIdRef.current)).fields({
279
+ computedStyle: styleNames
280
+ }, res => {
281
+ if (!res) {
282
+ resolve(null);
283
+ return;
284
+ }
285
+
286
+ // 提取样式信息,过滤掉非样式字段
287
+ const styles = {};
288
+ styleNames.forEach(styleName => {
289
+ if (res[styleName] !== undefined) {
290
+ styles[styleName] = res[styleName];
291
+ }
292
+ });
293
+ resolve(Object.keys(styles).length > 0 ? styles : null);
294
+ }).exec(() => {
295
+ // exec callback
296
+ });
297
+ } catch (error) {
298
+ console.error('[Animated] getComputedStyle 执行失败:', error);
299
+ resolve(null);
300
+ }
301
+ });
302
+ }
303
+ };
304
+ }, []);
305
+
306
+ // 处理 customStyle,避免传入 undefined
307
+ const customStyleValue = useMemo(() => {
308
+ if (!style) return undefined;
309
+ return typeof style === 'object' ? styleToString(style) : style;
310
+ }, []);
311
+ const handleAnimationEnd = useCallback(event => {
312
+ if (callbackRef.current && typeof callbackRef.current === 'function') {
313
+ const temp = callbackRef.current;
314
+ callbackRef.current = null;
315
+ temp(event);
316
+ }
317
+ if (onAnimationEnd) {
318
+ onAnimationEnd();
319
+ }
320
+ }, [onAnimationEnd, callbackRef.current]);
321
+ const eventBindings = {};
322
+ if (onClick) eventBindings['bind:click'] = onClick;
323
+ if (onTouchStart) eventBindings['bind:touchstart'] = onTouchStart;
324
+ if (onTouchEnd) eventBindings['bind:touchend'] = onTouchEnd;
325
+ if (onAnimationStart) eventBindings['bind:animationbegin'] = onAnimationStart;
326
+ eventBindings['bind:animationfinish'] = handleAnimationEnd;
327
+ return (
328
+ /*#__PURE__*/
329
+ // @ts-ignore - 小程序自定义组件
330
+ React.createElement(TyAnimatedComponent, _extends({
331
+ id: componentIdRef.current,
332
+ __mode: mode
333
+ }, className ? {
334
+ __className: className
335
+ } : {}, customStyleValue ? {
336
+ __style: customStyleValue
337
+ } : {}, mode === 'text' && textContent !== undefined ? {
338
+ __text: textContent
339
+ } : {}, throttle !== undefined ? {
340
+ __throttle: throttle
341
+ } : {}, throttleClick !== undefined ? {
342
+ __throttleClick: throttleClick
343
+ } : {}, throttleTouchStart !== undefined ? {
344
+ __throttleTouchStart: throttleTouchStart
345
+ } : {}, throttleTouchEnd !== undefined ? {
346
+ __throttleTouchEnd: throttleTouchEnd
347
+ } : {}, eventBindings), mode === 'view' ? children : null)
348
+ );
349
+ });
350
+ AnimatedBase.displayName = 'AnimatedBase';
351
+
352
+ /**
353
+ * Animated.View 组件(不包含 setText)
354
+ */
355
+ const AnimatedView = /*#__PURE__*/forwardRef((props, ref) => /*#__PURE__*/React.createElement(AnimatedBase, _extends({}, props, {
356
+ mode: "view",
357
+ ref: ref
358
+ })));
359
+ AnimatedView.displayName = 'Animated.View';
360
+
361
+ /**
362
+ * Animated.Text 组件(包含 setText)
363
+ */
364
+ const AnimatedText = /*#__PURE__*/forwardRef((props, ref) => /*#__PURE__*/React.createElement(AnimatedBase, _extends({}, props, {
365
+ mode: "text",
366
+ ref: ref
367
+ })));
368
+ AnimatedText.displayName = 'Animated.Text';
369
+
370
+ /**
371
+ * 默认导出
372
+ */
373
+ const Animated = {
374
+ View: AnimatedView,
375
+ Text: AnimatedText
376
+ };
377
+ export default Animated;
378
+
379
+ // 导出类型
@@ -0,0 +1,274 @@
1
+ import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
2
+ import "core-js/modules/es.string.trim.js";
3
+ import "core-js/modules/esnext.iterator.constructor.js";
4
+ import "core-js/modules/esnext.iterator.filter.js";
5
+ import "core-js/modules/esnext.iterator.for-each.js";
6
+ import "core-js/modules/esnext.iterator.map.js";
7
+ import "core-js/modules/web.dom-collections.iterator.js";
8
+ // @ts-nocheck
9
+ /**
10
+ * TyAnimated - 高性能动画组件(小程序原生层)
11
+ */
12
+
13
+ // 小程序原生组件注册
14
+ Component({
15
+ properties: {
16
+ __mode: {
17
+ type: String,
18
+ value: 'view'
19
+ },
20
+ __text: {
21
+ type: String,
22
+ value: ''
23
+ },
24
+ __style: {
25
+ type: String,
26
+ value: ''
27
+ },
28
+ __className: {
29
+ type: String,
30
+ value: ''
31
+ },
32
+ onAnimationStart: {
33
+ type: null,
34
+ value: null
35
+ },
36
+ // 动画结束回调函数
37
+ onAnimationEnd: {
38
+ type: null,
39
+ value: null
40
+ },
41
+ // 统一的事件节流时间(毫秒)
42
+ __throttle: {
43
+ type: Number,
44
+ value: 0
45
+ },
46
+ // 各事件独立的节流时间(毫秒),优先级高于 __throttle
47
+ __throttleClick: {
48
+ type: Number,
49
+ value: 0
50
+ },
51
+ __throttleTouchStart: {
52
+ type: Number,
53
+ value: 0
54
+ },
55
+ __throttleTouchEnd: {
56
+ type: Number,
57
+ value: 0
58
+ }
59
+ },
60
+ // 定义外部事件
61
+ externalClasses: [],
62
+ data: {
63
+ __text: '',
64
+ __style: '',
65
+ __className: '',
66
+ __animData: null,
67
+ // 动画数据
68
+ // 记录各事件的最后触发时间
69
+ __lastClickTime: 0,
70
+ __lastTouchStartTime: 0,
71
+ __lastTouchEndTime: 0
72
+ },
73
+ lifetimes: {
74
+ attached() {
75
+ // 组件初始化
76
+ },
77
+ detached() {
78
+ // 清理工作
79
+ }
80
+ },
81
+ methods: {
82
+ /**
83
+ * 事件处理器
84
+ */
85
+ __handleTiggerEvent(eventName, e) {
86
+ // 触发自定义事件,让外部可以监听
87
+ // bubbles: false - 不在 React 层冒泡,避免与原生层冒泡重复
88
+ // 原生层的 bindtap 会自然冒泡,并经过每个组件的节流检查
89
+ this.triggerEvent(eventName, e, {
90
+ bubbles: false,
91
+ composed: true
92
+ });
93
+ },
94
+ /**
95
+ * 创建 IntersectionObserver
96
+ */
97
+ createIntersectionObserver(options) {
98
+ try {
99
+ // @ts-ignore
100
+ if (typeof ty !== 'undefined' && ty.createIntersectionObserver) {
101
+ return ty.createIntersectionObserver(this, options);
102
+ }
103
+ } catch (e) {
104
+ console.error('createIntersectionObserver error:', e);
105
+ }
106
+ return null;
107
+ },
108
+ /**
109
+ * 获取小程序的 createAnimation 方法
110
+ */
111
+ _getCreateAnimation() {
112
+ try {
113
+ // @ts-ignore
114
+ if (typeof ty !== 'undefined' && ty.createAnimation) {
115
+ return ty.createAnimation;
116
+ }
117
+ } catch (e) {
118
+ // ignore
119
+ }
120
+ return null;
121
+ },
122
+ /**
123
+ * 解析样式字符串为对象
124
+ */
125
+ _parseStyle(styleString) {
126
+ if (!styleString) return {};
127
+ const result = {};
128
+ const styles = styleString.split(';').filter(Boolean);
129
+ styles.forEach(style => {
130
+ const colonIndex = style.indexOf(':');
131
+ if (colonIndex > 0) {
132
+ const key = style.substring(0, colonIndex).trim();
133
+ const value = style.substring(colonIndex + 1).trim();
134
+ if (key && value) {
135
+ result[key] = value;
136
+ }
137
+ }
138
+ });
139
+ return result;
140
+ },
141
+ /**
142
+ * 样式对象转字符串
143
+ */
144
+ _styleToString(styleObj) {
145
+ return Object.entries(styleObj).map(_ref => {
146
+ let [key, value] = _ref;
147
+ return "".concat(key, ":").concat(value);
148
+ }).join(';');
149
+ },
150
+ setCustomStyle(value, callback) {
151
+ // 防止传入 undefined 或 null 导致小程序报错
152
+ if (value === undefined || value === null) {
153
+ console.warn('[TyAnimatedComponent] setCustomStyle: value is undefined or null, ignored');
154
+ if (callback) callback();
155
+ return;
156
+ }
157
+
158
+ // 合并样式
159
+ const currentStyle = this._parseStyle(this.data.__style || '');
160
+ const newStyle = this._parseStyle(value);
161
+ const mergedStyle = _objectSpread(_objectSpread({}, currentStyle), newStyle);
162
+ let mergedStyleString = this._styleToString(mergedStyle);
163
+ if (mergedStyleString === this.data.__style && this.data.__animData !== null) {
164
+ // 添加一个不影响渲染的注释,使样式字符串不同
165
+ mergedStyleString = "".concat(mergedStyleString, ";/* ").concat(Date.now(), " */");
166
+ }
167
+
168
+ // 一次性 setData,不嵌套
169
+ this.setData({
170
+ __style: mergedStyleString,
171
+ __animData: null // 总是清空动画数据
172
+ }, () => {
173
+ if (callback) callback();
174
+ });
175
+ },
176
+ /**
177
+ * 设置文本内容(__mode='text' 时使用)
178
+ */
179
+ __applyText(text) {
180
+ if (typeof text === 'string') {
181
+ this.setData({
182
+ __text: text
183
+ });
184
+ }
185
+ },
186
+ __applyAnimData(animData) {
187
+ if (!animData) {
188
+ console.warn('[TyAnimatedComponent] __applyAnimData: animData is null or undefined');
189
+ return;
190
+ }
191
+ this.setData({
192
+ __animData: animData
193
+ });
194
+ },
195
+ /**
196
+ * 核心方法:直接应用 Animation 对象
197
+ * @param {Function} fn - 接收 Animation 实例的回调函数
198
+ * @param {Object} options - 动画选项,包括 transformOrigin
199
+ *
200
+ * 使用示例:
201
+ * comp.__apply((anim) => {
202
+ * anim.opacity(0.5).scale(0.8).step();
203
+ * return anim.export();
204
+ * }, { transformOrigin: 'top left' });
205
+ */
206
+ __apply(fn) {
207
+ let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
208
+ const createAnimation = this._getCreateAnimation();
209
+ if (!createAnimation) {
210
+ return;
211
+ }
212
+ const anim = createAnimation(options);
213
+
214
+ // 执行用户的动画配置
215
+ const exported = fn(anim);
216
+
217
+ // 应用动画数据
218
+ const animData = exported || anim.export();
219
+ this.setData({
220
+ __animData: animData
221
+ });
222
+ },
223
+ // 核心触摸事件处理(只保留最常用的)
224
+ __onClick(e) {
225
+ // 节流判断:优先使用具体事件的节流时间,否则使用统一的节流时间
226
+ const throttleTime = this.properties.__throttleClick || this.properties.__throttle;
227
+ if (throttleTime > 0) {
228
+ const now = Date.now();
229
+ const lastTime = this.data.__lastClickTime;
230
+ if (now - lastTime < throttleTime) {
231
+ console.log("[TyAnimated][".concat(now, "] __onClick \u88AB\u8282\u6D41\u5FFD\u7565\uFF0C\u8DDD\u4E0A\u6B21\u89E6\u53D1 ").concat(now - lastTime, "ms < ").concat(throttleTime, "ms"));
232
+ return;
233
+ }
234
+ this.setData({
235
+ __lastClickTime: now
236
+ });
237
+ }
238
+ this.__handleTiggerEvent('click', e);
239
+ },
240
+ __onTouchStart(e) {
241
+ // 节流判断:优先使用具体事件的节流时间,否则使用统一的节流时间
242
+ const throttleTime = this.properties.__throttleTouchStart || this.properties.__throttle;
243
+ if (throttleTime > 0) {
244
+ const now = Date.now();
245
+ const lastTime = this.data.__lastTouchStartTime;
246
+ if (now - lastTime < throttleTime) {
247
+ console.log("[TyAnimated][".concat(now, "] __onTouchStart \u88AB\u8282\u6D41\u5FFD\u7565\uFF0C\u8DDD\u4E0A\u6B21\u89E6\u53D1 ").concat(now - lastTime, "ms < ").concat(throttleTime, "ms"));
248
+ return;
249
+ }
250
+ this.setData({
251
+ __lastTouchStartTime: now
252
+ });
253
+ }
254
+ this.__handleTiggerEvent('touchstart', e);
255
+ },
256
+ __onTouchEnd(e) {
257
+ // 节流判断:优先使用具体事件的节流时间,否则使用统一的节流时间
258
+ const throttleTime = this.properties.__throttleTouchEnd || this.properties.__throttle;
259
+ if (throttleTime > 0) {
260
+ const now = Date.now();
261
+ const lastTime = this.data.__lastTouchEndTime;
262
+ if (now - lastTime < throttleTime) {
263
+ console.log("[TyAnimated][".concat(now, "] __onTouchEnd \u88AB\u8282\u6D41\u5FFD\u7565\uFF0C\u8DDD\u4E0A\u6B21\u89E6\u53D1 ").concat(now - lastTime, "ms < ").concat(throttleTime, "ms"));
264
+ return;
265
+ }
266
+ this.setData({
267
+ __lastTouchEndTime: now
268
+ });
269
+ }
270
+ this.__handleTiggerEvent('touchend', e);
271
+ }
272
+ }
273
+ });
274
+ export default 'ty-animated-component';
@@ -14,7 +14,7 @@
14
14
  <text
15
15
  wx:else
16
16
  id="rootText"
17
- class="{{__className}}"
17
+ class="container {{__className}}"
18
18
  style="{{__style}}"
19
19
  animation="{{__animData}}"
20
20
  bindtap="__onClick"
@@ -0,0 +1,331 @@
1
+ import type React from 'react';
2
+ /**
3
+ * 触摸事件处理器(只保留核心事件)
4
+ *
5
+ * 注:只提供最常用的三个事件(onClick, onTouchStart, onTouchEnd)
6
+ * 如需其他事件(如 onLongPress, onTouchMove 等),请在 Animated 的子元素上直接绑定
7
+ */
8
+ export interface TouchEventHandler {
9
+ onClick?: (e: TouchEvent) => any;
10
+ onTouchStart?: (e: TouchEvent) => any;
11
+ onTouchEnd?: (e: TouchEvent) => any;
12
+ /**
13
+ * 统一的事件节流时间(毫秒),应用于所有事件
14
+ * 优先级:throttleClick/throttleTouchStart/throttleTouchEnd > throttle
15
+ *
16
+ * @example
17
+ * <Animated.View throttle={300} onClick={...} onTouchStart={...} />
18
+ */
19
+ throttle?: number;
20
+ /**
21
+ * click 事件节流时间(毫秒),优先级高于 throttle
22
+ *
23
+ * @example
24
+ * <Animated.View throttle={300} throttleClick={500} onClick={...} />
25
+ */
26
+ throttleClick?: number;
27
+ /** touchstart 事件节流时间(毫秒),优先级高于 throttle */
28
+ throttleTouchStart?: number;
29
+ /** touchend 事件节流时间(毫秒),优先级高于 throttle */
30
+ throttleTouchEnd?: number;
31
+ }
32
+ interface TargetType {
33
+ id: string;
34
+ offsetLeft: number;
35
+ offsetTop: number;
36
+ paths: any;
37
+ uid: any;
38
+ dataset: {
39
+ [key: string]: any;
40
+ };
41
+ }
42
+ export interface EventType {
43
+ type: string;
44
+ target: TargetType;
45
+ currentTarget: TargetType;
46
+ timestamp: number;
47
+ stopPropagation: () => void;
48
+ detail: any;
49
+ }
50
+ export type timing = 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out' | 'step-start' | 'step-end';
51
+ export type TOptions = {
52
+ duration?: number;
53
+ timingFunction?: timing;
54
+ delay?: number;
55
+ transformOrigin?: string;
56
+ };
57
+ /**
58
+ * 元素边界信息
59
+ */
60
+ export interface BoundingClientRect {
61
+ /** 元素的 ID */
62
+ id: string;
63
+ /** dataset 数据 */
64
+ dataset: Record<string, any>;
65
+ /** 左边界坐标 */
66
+ left: number;
67
+ /** 右边界坐标 */
68
+ right: number;
69
+ /** 上边界坐标 */
70
+ top: number;
71
+ /** 下边界坐标 */
72
+ bottom: number;
73
+ /** 元素宽度 */
74
+ width: number;
75
+ /** 元素高度 */
76
+ height: number;
77
+ }
78
+ /**
79
+ * 元素查询字段配置
80
+ */
81
+ export interface NodeFields {
82
+ /** 是否返回节点 id */
83
+ id?: boolean;
84
+ /** 是否返回节点 dataset */
85
+ dataset?: boolean;
86
+ /** 是否返回节点布局位置(left right top bottom) */
87
+ rect?: boolean;
88
+ /** 是否返回节点尺寸(width height) */
89
+ size?: boolean;
90
+ /** 是否返回节点的 scrollLeft scrollTop */
91
+ scrollOffset?: boolean;
92
+ /** 指定属性名列表,返回节点对应属性名的当前属性值 */
93
+ properties?: string[];
94
+ /** 指定样式名列表,返回节点对应样式名的当前值 */
95
+ computedStyle?: string[];
96
+ /** 是否返回节点对应的 Node 实例 */
97
+ node?: boolean;
98
+ }
99
+ /**
100
+ * Animated.View 组件的 Ref 类型(不包含 setText)
101
+ *
102
+ * @example
103
+ * ```tsx
104
+ * import { useRef } from 'react'
105
+ * import Animated, { type AnimatedViewRef } from '@/components/animated'
106
+ *
107
+ * const boxRef = useRef<AnimatedViewRef>(null)
108
+ * boxRef.current?.setStyle({ opacity: 0.5 })
109
+ * boxRef.current?.animate((anim) => {
110
+ * anim.translateX(100).step({ duration: 300 })
111
+ * })
112
+ * ```
113
+ */
114
+ export interface AnimatedViewRef {
115
+ /**
116
+ * 直接设置样式(无动画)
117
+ *
118
+ * 优势:不受 React 重渲染影响,可以在动画过程中随时调用 setState
119
+ * 而不用担心样式被覆盖或闪烁问题
120
+ *
121
+ * 注意:
122
+ * - 会清除动画数据并设置样式
123
+ * - 如果动画正在执行,会中断动画
124
+ * - 返回 Promise,可以等待样式应用完成
125
+ *
126
+ * @param style - 样式对象或 CSS 字符串
127
+ * @param callback - 可选的回调函数,样式应用完成后调用
128
+ * @returns Promise<void> - 样式应用完成后 resolve
129
+ *
130
+ * @example
131
+ * ```tsx
132
+ * // 基本使用
133
+ * ref.current?.setStyle({ opacity: 0.5, color: 'red' })
134
+ *
135
+ * // 等待样式应用完成
136
+ * await ref.current?.setStyle({ opacity: 0 })
137
+ *
138
+ * // 如果需要获取 rect,单独调用
139
+ * await ref.current?.setStyle({ opacity: 0 })
140
+ * const rect = await ref.current?.getBoundingClientRect()
141
+ *
142
+ * // 使用回调
143
+ * ref.current?.setStyle({ color: 'red' }, () => {
144
+ * console.log('样式已应用')
145
+ * })
146
+ * ```
147
+ */
148
+ setStyle: (style: React.CSSProperties | string, callback?: () => void) => Promise<void>;
149
+ /**
150
+ * 创建动画对象
151
+ * 使用小程序原生动画 API,这是性能最优的动画方式
152
+ *
153
+ * @param options - 动画配置(可选)
154
+ * @param options.duration - 动画持续时间(毫秒),默认 400
155
+ * @param options.timingFunction - 缓动函数,默认 'linear'
156
+ * @param options.delay - 动画延迟时间(毫秒),默认 0
157
+ * @param options.transformOrigin - 变换原点,默认 '50% 50% 0'
158
+ * @returns Animation 实例,包含所有动画方法和 play() 方法
159
+ *
160
+ * @example
161
+ * ```tsx
162
+ * // 基本使用
163
+ * const animation = ref.current.animate()
164
+ * animation.translateX(100).step({ duration: 300 })
165
+ * await animation.play()
166
+ * console.log('动画完成!')
167
+ *
168
+ * // 链式调用
169
+ * const animation = ref.current
170
+ * .animate({ transformOrigin: '50% 50%' })
171
+ * .translateX(100)
172
+ * .scale(1.2)
173
+ * .step({ duration: 300 })
174
+ * await animation.play()
175
+ *
176
+ * // 多段动画
177
+ * const animation = ref.current.animate()
178
+ * animation.translateX(100).step({ duration: 300 })
179
+ * animation.translateY(100).step({ duration: 300 })
180
+ * animation.scale(1.5).step({ duration: 300 })
181
+ * await animation.play()
182
+ *
183
+ * // 复用动画
184
+ * const slideIn = ref.current.animate().translateX(100).step({ duration: 300 })
185
+ * await slideIn.play() // 第一次
186
+ * await slideIn.play() // 第二次
187
+ * ```
188
+ */
189
+ animate: (options?: TOptions) => IAnimation & {
190
+ play(): Promise<void>;
191
+ };
192
+ /**
193
+ * 获取元素的布局位置和尺寸信息
194
+ * 基于小程序 SelectorQuery.boundingClientRect API
195
+ *
196
+ * @returns Promise<BoundingClientRect | null>
197
+ *
198
+ * @example
199
+ * ```tsx
200
+ * const rect = await boxRef.current?.getBoundingClientRect()
201
+ * console.log(rect?.left, rect?.top, rect?.width, rect?.height)
202
+ *
203
+ * // 使用位置信息实现动画
204
+ * if (rect) {
205
+ * const centerX = window.innerWidth / 2 - rect.left - rect.width / 2
206
+ * const centerY = window.innerHeight / 2 - rect.top - rect.height / 2
207
+ * boxRef.current?.animate((anim) => {
208
+ * anim.translateX(centerX).translateY(centerY).scale(2).step({ duration: 300 })
209
+ * })
210
+ * }
211
+ * ```
212
+ */
213
+ getBoundingClientRect: () => Promise<BoundingClientRect | null>;
214
+ /**
215
+ * 获取元素的详细信息(包括样式、属性等)
216
+ * 基于小程序 SelectorQuery.fields API
217
+ *
218
+ * @param fields - 查询字段配置
219
+ * @returns Promise<any>
220
+ *
221
+ * @example
222
+ * ```tsx
223
+ * // 获取元素的位置和计算后的样式
224
+ * const info = await boxRef.current?.getFields({
225
+ * rect: true,
226
+ * size: true,
227
+ * computedStyle: ['backgroundColor', 'transform']
228
+ * })
229
+ * console.log(info?.backgroundColor, info?.transform)
230
+ * ```
231
+ */
232
+ getFields: (fields: NodeFields) => Promise<any>;
233
+ /**
234
+ * 获取元素的计算后样式(computed style)
235
+ * 这是一个便捷方法,内部调用 getFields
236
+ *
237
+ * 使用注意事项:
238
+ * 1. width/height 限制:通过 CSS 自动布局的元素,width/height 通常返回 'auto'
239
+ * - 错误用法:用 getComputedStyle 获取实际像素尺寸
240
+ * - 正确用法:使用 getBoundingClientRect() 获取实际尺寸
241
+ *
242
+ * 2. 内联样式限制:通过 React style 属性设置的样式可能无法查询
243
+ * - 原因:小程序查询的是最终渲染的 CSS,不是 JS 设置的内联样式
244
+ *
245
+ * 3. 适用场景:
246
+ * - 查询 transform 动画状态(动画执行中)
247
+ * - 查询 opacity 等动画属性
248
+ * - 查询通过 CSS 类设置的样式
249
+ * - 查询元素实际尺寸(用 getBoundingClientRect)
250
+ * - 查询通过 style={{ }} 设置的背景色等
251
+ *
252
+ * @param styleNames - 样式名称数组
253
+ * @returns Promise<Record<string, any> | null> - 返回样式对象
254
+ *
255
+ * @example
256
+ * ```tsx
257
+ * // 正确示例:查询动画相关样式
258
+ * const styles = await boxRef.current?.getComputedStyle([
259
+ * 'transform',
260
+ * 'opacity'
261
+ * ])
262
+ *
263
+ * console.log(styles?.transform) // 'matrix(1.5, 0, 0, 1.5, 100, 50)'
264
+ * console.log(styles?.opacity) // '0.8'
265
+ *
266
+ * // 根据当前 transform 决定下一步动画
267
+ * if (styles?.transform !== 'none') {
268
+ * // 当前有 transform,恢复原位
269
+ * boxRef.current?.animate((anim) => {
270
+ * anim.translateX(0).translateY(0).scale(1).step({ duration: 300 })
271
+ * })
272
+ * }
273
+ *
274
+ * // 错误示例:查询尺寸
275
+ * const styles = await boxRef.current?.getComputedStyle(['width', 'height'])
276
+ * console.log(styles?.width) // 可能返回 'auto',不是实际像素值
277
+ *
278
+ * // 正确做法:使用 getBoundingClientRect
279
+ * const rect = await boxRef.current?.getBoundingClientRect()
280
+ * console.log(rect?.width) // 335 (实际像素值)
281
+ * ```
282
+ */
283
+ getComputedStyle: (styleNames: string[]) => Promise<Record<string, any> | null>;
284
+ }
285
+ /**
286
+ * Animated.Text 组件的 Ref 类型(包含 setText)
287
+ *
288
+ * @example
289
+ * ```tsx
290
+ * import { useRef } from 'react'
291
+ * import Animated, { type AnimatedTextRef } from '@/components/animated'
292
+ *
293
+ * const textRef = useRef<AnimatedTextRef>(null)
294
+ * textRef.current?.setText('新文本')
295
+ * textRef.current?.animate((anim) => {
296
+ * anim.opacity(0.5).step({ duration: 300 })
297
+ * })
298
+ * ```
299
+ */
300
+ export interface AnimatedTextRef extends AnimatedViewRef {
301
+ /**
302
+ * 直接设置文本内容(仅 Animated.Text 可用)
303
+ * 直接操作底层组件,不触发 React 重渲染
304
+ *
305
+ * @param text - 文本内容
306
+ *
307
+ * @example
308
+ * ```tsx
309
+ * <Animated.Text ref={textRef}>0</Animated.Text>
310
+ * // 更新文本
311
+ * textRef.current?.setText('100')
312
+ * ```
313
+ */
314
+ setText: (text: string) => void;
315
+ }
316
+ /**
317
+ * Animated 组件 Props
318
+ */
319
+ export interface AnimatedProps extends TouchEventHandler {
320
+ children?: React.ReactNode;
321
+ className?: string;
322
+ style?: React.CSSProperties;
323
+ id?: string;
324
+ /** 组件模式:'view' 为容器模式(默认),'text' 为文本模式 */
325
+ mode?: 'view' | 'text';
326
+ /** 动画开始回调 */
327
+ onAnimationStart?: () => void;
328
+ /** 动画结束回调 */
329
+ onAnimationEnd?: () => void;
330
+ }
331
+ export {};
@@ -0,0 +1,47 @@
1
+ interface IAnimation {
2
+ // transform
3
+ translate(x?: number, y?: number): this
4
+ translateX(x?: number): this
5
+ translateY(y?: number): this
6
+ translateZ(z?: number): this
7
+
8
+ rotate(angle: number): this
9
+ rotateX(angle: number): this
10
+ rotateY(angle: number): this
11
+ rotateZ(angle: number): this
12
+ rotate3d(x: number, y: number, z: number, angle: number): this
13
+
14
+ scale(x?: number, y?: number): this
15
+ scaleX(x?: number): this
16
+ scaleY(y?: number): this
17
+ scaleZ(z?: number): this
18
+ scale3d(x?: number, y?: number, z?: number): this
19
+
20
+ skew(x?: number, y?: number): this
21
+ skewX(x?: number): this
22
+ skewY(y?: number): this
23
+
24
+ matrix(...args: number[]): this
25
+ matrix3d(...args: number[]): this
26
+
27
+ // style
28
+ opacity(opacity: number): this
29
+ width(value: number | string): this
30
+ height(value: number | string): this
31
+ top(value: number | string): this
32
+ left(value: number | string): this
33
+ right(value: number | string): this
34
+ bottom(value: number | string): this
35
+ backgroundColor(value: string): this
36
+
37
+ // step / export
38
+ step(
39
+ options?: Partial<{
40
+ transformOrigin: string
41
+ duration: number
42
+ timingFunction: string
43
+ delay: number
44
+ }>
45
+ ): this
46
+ export(): { actions: any[] }
47
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Animated - 纯净底层动画组件
3
+ *
4
+ * 只提供三个核心方法:
5
+ * - setProperty(property, value): 设置动画属性
6
+ * - setStyle(style): 设置样式
7
+ * - setText(text): 设置文本内容
8
+ *
9
+ * 可以配合任何动画库使用(GSAP、Anime.js、Framer Motion 等)
10
+ */
11
+ import React from 'react';
12
+ import type { AnimatedViewRef, AnimatedTextRef, AnimatedProps } from './types';
13
+ /**
14
+ * 默认导出
15
+ */
16
+ declare const Animated: {
17
+ View: React.ForwardRefExoticComponent<Omit<AnimatedProps, "mode"> & React.RefAttributes<AnimatedViewRef>>;
18
+ Text: React.ForwardRefExoticComponent<Omit<AnimatedProps, "mode"> & React.RefAttributes<AnimatedTextRef>>;
19
+ };
20
+ export default Animated;
21
+ export type { AnimatedViewRef, AnimatedTextRef, AnimatedProps } from './types';
@@ -319,9 +319,9 @@ const AnimatedBase = /*#__PURE__*/forwardRef((props, ref) => {
319
319
  id: componentIdRef.current,
320
320
  __mode: mode
321
321
  }, className ? {
322
- customClass: className
322
+ __className: className
323
323
  } : {}, customStyleValue ? {
324
- customStyle: customStyleValue
324
+ __style: customStyleValue
325
325
  } : {}, mode === 'text' && textContent !== undefined ? {
326
326
  __text: textContent
327
327
  } : {}, eventBindings), mode === 'view' ? children : null)
@@ -0,0 +1,5 @@
1
+ import * as React from 'react';
2
+ declare const TyAnimated: React.ComponentType<any>;
3
+ export default TyAnimated;
4
+
5
+
@@ -28,20 +28,6 @@ Component({
28
28
  type: String,
29
29
  value: ''
30
30
  },
31
- // 兼容 FastView 接口
32
- customStyle: {
33
- type: String,
34
- value: ''
35
- },
36
- customClass: {
37
- type: String,
38
- value: '',
39
- observer: function (v) {
40
- this.setData({
41
- __className: v
42
- });
43
- }
44
- },
45
31
  onAnimationStart: {
46
32
  type: null,
47
33
  value: null
@@ -62,13 +48,7 @@ Component({
62
48
  },
63
49
  lifetimes: {
64
50
  attached() {
65
- // 组件初始化:将 customStyle 的初始值复制到 __style
66
- const initialStyle = this.properties.customStyle || '';
67
- if (initialStyle) {
68
- this.setData({
69
- __style: initialStyle
70
- });
71
- }
51
+ // 组件初始化
72
52
  },
73
53
  detached() {
74
54
  // 清理工作
@@ -0,0 +1,4 @@
1
+ {
2
+ "component": true,
3
+ "usingComponents": {}
4
+ }
@@ -0,0 +1,26 @@
1
+ <block>
2
+ <view
3
+ wx:if="{{__mode==='view'}}"
4
+ id="root"
5
+ class="{{__className}}"
6
+ style="{{__style}}"
7
+ animation="{{__animData}}"
8
+ bindtap="__onClick"
9
+ bindtouchstart="__onTouchStart"
10
+ bindtouchend="__onTouchEnd"
11
+ >
12
+ <slot></slot>
13
+ </view>
14
+ <text
15
+ wx:else
16
+ id="rootText"
17
+ class="container {{__className}}"
18
+ style="{{__style}}"
19
+ animation="{{__animData}}"
20
+ bindtap="__onClick"
21
+ bindtouchstart="__onTouchStart"
22
+ bindtouchend="__onTouchEnd"
23
+ >{{__text}}</text>
24
+ </block>
25
+
26
+
@@ -0,0 +1,3 @@
1
+ .container { display: block; }
2
+
3
+
@@ -0,0 +1 @@
1
+ export {};
package/lib/index.d.ts CHANGED
@@ -33,5 +33,5 @@ export { default as Iframe } from './Iframe';
33
33
  export { default as Modal } from './Modal';
34
34
  export { default as Video } from './Video';
35
35
  export { default as SvgRender } from './SvgRender';
36
- export { default as Animated } from './Animated';
37
- export type { AnimatedViewRef, AnimatedTextRef, AnimatedProps } from './Animated';
36
+ export { default as Animated } from './Animated ';
37
+ export type { AnimatedViewRef, AnimatedTextRef, AnimatedProps } from './Animated ';
package/lib/index.js CHANGED
@@ -33,4 +33,4 @@ export { default as Iframe } from './Iframe';
33
33
  export { default as Modal } from './Modal';
34
34
  export { default as Video } from './Video';
35
35
  export { default as SvgRender } from './SvgRender';
36
- export { default as Animated } from './Animated';
36
+ export { default as Animated } from './Animated ';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ray-js/components",
3
- "version": "1.7.56-beta.5",
3
+ "version": "1.7.56-beta.6",
4
4
  "description": "Ray basic components",
5
5
  "keywords": [
6
6
  "ray"
@@ -29,8 +29,8 @@
29
29
  "dependencies": {
30
30
  "@ray-core/macro": "^0.4.9",
31
31
  "@ray-core/wechat": "^0.4.9",
32
- "@ray-js/adapter": "1.7.56-beta.5",
33
- "@ray-js/framework-shared": "1.7.56-beta.5",
32
+ "@ray-js/adapter": "1.7.56-beta.6",
33
+ "@ray-js/framework-shared": "1.7.56-beta.6",
34
34
  "ahooks": "^3.8.5",
35
35
  "clsx": "^1.2.1",
36
36
  "core-js": "^3.43.0",
@@ -40,11 +40,11 @@
40
40
  "style-to-object": "^0.3.0"
41
41
  },
42
42
  "devDependencies": {
43
- "@ray-js/cli": "1.7.56-beta.5"
43
+ "@ray-js/cli": "1.7.56-beta.6"
44
44
  },
45
45
  "publishConfig": {
46
46
  "access": "public",
47
47
  "registry": "https://registry.npmjs.org"
48
48
  },
49
- "gitHead": "504d74785b7f6ed42a0e20d929a77e9df4971bf8"
49
+ "gitHead": "9b3e69bb2a95a48e8a8fadd533a865d97be8c8a5"
50
50
  }
File without changes
File without changes
File without changes
File without changes