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

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,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';
@@ -0,0 +1,359 @@
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
+ } = props;
51
+ const callbackRef = useRef(onAnimationEnd);
52
+ const componentIdRef = useRef(id || "animated-".concat(Math.random().toString(36).slice(2, 11)));
53
+
54
+ // 提取文本内容(当 mode='text' 时)
55
+ const textContent = useMemo(() => {
56
+ if (mode !== 'text' || !children) return undefined;
57
+ if (typeof children === 'string' || typeof children === 'number') {
58
+ return String(children);
59
+ }
60
+ return undefined;
61
+ }, [mode, children]);
62
+ useImperativeHandle(ref, () => {
63
+ /**
64
+ * 获取 DOM 实例
65
+ */
66
+ const getDomInstance = () => {
67
+ try {
68
+ var _getCurrentPages;
69
+ const pages = (_getCurrentPages = getCurrentPages) === null || _getCurrentPages === void 0 ? void 0 : _getCurrentPages();
70
+ if (!pages || pages.length === 0) return null;
71
+ const currentPage = pages[pages.length - 1];
72
+ if (!(currentPage !== null && currentPage !== void 0 && currentPage.selectComponent)) return null;
73
+ return currentPage.selectComponent("#".concat(componentIdRef.current));
74
+ } catch (error) {
75
+ console.error('[Animated] 获取组件实例失败:', error);
76
+ return null;
77
+ }
78
+ };
79
+ return {
80
+ /**
81
+ * 设置样式(直通通道)
82
+ * 用途:设置静态样式,绕过 React/Ray 层,提升性能
83
+ * @example
84
+ * ref.current.setStyle({ color: 'red', fontSize: '16px', border: '1px solid #ccc' })
85
+ */
86
+ setStyle(styleValue, callback) {
87
+ return new Promise(resolve => {
88
+ var _inst$setCustomStyle;
89
+ const inst = getDomInstance();
90
+ if (!inst) {
91
+ console.warn('No DOM instance found');
92
+ resolve();
93
+ if (callback) callback();
94
+ return;
95
+ }
96
+
97
+ // 防止传入 undefined 或空值
98
+ if (styleValue === undefined || styleValue === null) {
99
+ console.warn('styleValue is undefined or null');
100
+ resolve();
101
+ if (callback) callback();
102
+ return;
103
+ }
104
+ const styleString = typeof styleValue === 'string' ? styleValue : styleToString(styleValue);
105
+ if (!styleString) {
106
+ console.warn('styleString is empty');
107
+ resolve();
108
+ if (callback) callback();
109
+ return;
110
+ }
111
+
112
+ // setData 完成后的回调
113
+ (_inst$setCustomStyle = inst.setCustomStyle) === null || _inst$setCustomStyle === void 0 || _inst$setCustomStyle.call(inst, styleString, () => {
114
+ // 调用用户回调
115
+ if (callback && typeof callback === 'function') {
116
+ try {
117
+ callback();
118
+ } catch (error) {
119
+ console.error('[Animated] setStyle callback error:', error);
120
+ }
121
+ }
122
+ // resolve Promise
123
+ resolve();
124
+ });
125
+ });
126
+ },
127
+ /**
128
+ * 设置文本内容(仅 mode='text' 时可用)
129
+ * 不触发 React 重渲染
130
+ */
131
+ setText(text) {
132
+ var _inst$__applyText;
133
+ // 运行时检查:只有 Text 组件才能调用 setText
134
+ if (mode !== 'text') {
135
+ console.error('[Animated] setText 只能在 Animated.Text 组件上使用');
136
+ return;
137
+ }
138
+ const inst = getDomInstance();
139
+ if (!inst) {
140
+ console.warn('[Animated] setText: 无法获取组件实例');
141
+ return;
142
+ }
143
+ (_inst$__applyText = inst.__applyText) === null || _inst$__applyText === void 0 || _inst$__applyText.call(inst, text);
144
+ },
145
+ /**
146
+ * 创建动画对象
147
+ * 使用小程序原生动画 API,这是性能最优的动画方式
148
+ *
149
+ * @param options - 动画配置(duration, timingFunction, delay, transformOrigin)
150
+ * @returns Animation 实例,包含 play() 方法
151
+ *
152
+ * @example
153
+ * // 创建并执行动画
154
+ * const animation = ref.current.animate()
155
+ * animation.translateX(100).scale(1.2).step({ duration: 300 })
156
+ * animation.translateY(50).step({ duration: 200 })
157
+ * await animation.play()
158
+ * console.log('动画完成!')
159
+ *
160
+ * // 链式调用
161
+ * const animation = ref.current
162
+ * .animate({ transformOrigin: '50% 50%' })
163
+ * .translateX(100)
164
+ * .scale(1.2)
165
+ * .step({ duration: 300 })
166
+ * await animation.play()
167
+ *
168
+ * // 复用动画
169
+ * const slideIn = ref.current.animate().translateX(100).step({ duration: 300 })
170
+ * await slideIn.play() // 第一次
171
+ * await slideIn.play() // 第二次
172
+ */
173
+ animate(options) {
174
+ var _inst$_getCreateAnima;
175
+ const inst = getDomInstance();
176
+ if (!inst) {
177
+ console.warn('[Animated] No DOM instance found');
178
+ return null;
179
+ }
180
+
181
+ // 获取 createAnimation 函数
182
+ const createAnimation = (_inst$_getCreateAnima = inst._getCreateAnimation) === null || _inst$_getCreateAnima === void 0 ? void 0 : _inst$_getCreateAnima.call(inst);
183
+ if (!createAnimation) {
184
+ console.error('[Animated] createAnimation 不可用');
185
+ return null;
186
+ }
187
+
188
+ // 创建小程序原生 Animation 实例
189
+ const animation = createAnimation(options);
190
+
191
+ // 扩展 play 方法
192
+ animation.play = () => {
193
+ return new Promise(resolve => {
194
+ var _inst$__applyAnimData;
195
+ // 导出动画数据
196
+ const animationData = animation.export();
197
+
198
+ // 包装完成回调
199
+
200
+ callbackRef.current = event => {
201
+ resolve(event);
202
+ };
203
+ (_inst$__applyAnimData = inst.__applyAnimData) === null || _inst$__applyAnimData === void 0 || _inst$__applyAnimData.call(inst, animationData);
204
+ });
205
+ };
206
+ return animation;
207
+ },
208
+ /**
209
+ * 获取元素的布局位置和尺寸信息
210
+ * 基于小程序 SelectorQuery.boundingClientRect API
211
+ */
212
+ getBoundingClientRect() {
213
+ return new Promise(resolve => {
214
+ try {
215
+ // 检查小程序 API 是否存在
216
+ if (typeof ty === 'undefined' || !ty.createSelectorQuery) {
217
+ console.error('[Animated] getBoundingClientRect: ty.createSelectorQuery 不存在');
218
+ resolve(null);
219
+ return;
220
+ }
221
+ const query = ty.createSelectorQuery();
222
+ query.select("#".concat(componentIdRef.current)).boundingClientRect(rect => {
223
+ resolve(rect || null);
224
+ }).exec();
225
+ } catch (error) {
226
+ console.error('[Animated] getBoundingClientRect 执行失败:', error);
227
+ resolve(null);
228
+ }
229
+ });
230
+ },
231
+ /**
232
+ * 获取元素的详细信息(包括样式、属性等)
233
+ * 基于小程序 SelectorQuery.fields API
234
+ */
235
+ getFields(fields) {
236
+ return new Promise(resolve => {
237
+ try {
238
+ // 检查小程序 API 是否存在
239
+ if (typeof ty === 'undefined' || !ty.createSelectorQuery) {
240
+ console.error('[Animated] getFields: ty.createSelectorQuery 不存在');
241
+ resolve(null);
242
+ return;
243
+ }
244
+ const query = ty.createSelectorQuery();
245
+ query.select("#".concat(componentIdRef.current)).fields(fields, res => {
246
+ resolve(res || null);
247
+ }).exec();
248
+ } catch (error) {
249
+ console.error('[Animated] getFields 执行失败:', error);
250
+ resolve(null);
251
+ }
252
+ });
253
+ },
254
+ /**
255
+ * 获取元素的计算后样式
256
+ * 便捷方法,专门用于查询样式
257
+ */
258
+ getComputedStyle(styleNames) {
259
+ return new Promise(resolve => {
260
+ try {
261
+ // 检查小程序 API 是否存在
262
+ if (typeof ty === 'undefined' || !ty.createSelectorQuery) {
263
+ console.error('[Animated] getComputedStyle: ty.createSelectorQuery 不存在');
264
+ resolve(null);
265
+ return;
266
+ }
267
+ const query = ty.createSelectorQuery();
268
+ query.select("#".concat(componentIdRef.current)).fields({
269
+ computedStyle: styleNames
270
+ }, res => {
271
+ if (!res) {
272
+ resolve(null);
273
+ return;
274
+ }
275
+
276
+ // 提取样式信息,过滤掉非样式字段
277
+ const styles = {};
278
+ styleNames.forEach(styleName => {
279
+ if (res[styleName] !== undefined) {
280
+ styles[styleName] = res[styleName];
281
+ }
282
+ });
283
+ resolve(Object.keys(styles).length > 0 ? styles : null);
284
+ }).exec();
285
+ } catch (error) {
286
+ console.error('[Animated] getComputedStyle 执行失败:', error);
287
+ resolve(null);
288
+ }
289
+ });
290
+ }
291
+ };
292
+ }, []);
293
+
294
+ // 处理 customStyle,避免传入 undefined
295
+ const customStyleValue = useMemo(() => {
296
+ if (!style) return undefined;
297
+ return typeof style === 'object' ? styleToString(style) : style;
298
+ }, []);
299
+ const handleAnimationEnd = useCallback(event => {
300
+ if (callbackRef.current && typeof callbackRef.current === 'function') {
301
+ const temp = callbackRef.current;
302
+ callbackRef.current = null;
303
+ temp(event);
304
+ }
305
+ if (onAnimationEnd) {
306
+ onAnimationEnd();
307
+ }
308
+ }, [onAnimationEnd, callbackRef.current]);
309
+ const eventBindings = {};
310
+ if (onClick) eventBindings['bind:click'] = onClick;
311
+ if (onTouchStart) eventBindings['bind:touchstart'] = onTouchStart;
312
+ if (onTouchEnd) eventBindings['bind:touchend'] = onTouchEnd;
313
+ if (onAnimationStart) eventBindings['bind:animationbegin'] = onAnimationStart;
314
+ eventBindings['bind:animationfinish'] = handleAnimationEnd;
315
+ return (
316
+ /*#__PURE__*/
317
+ // @ts-ignore - 小程序自定义组件
318
+ React.createElement(TyAnimatedComponent, _extends({
319
+ id: componentIdRef.current,
320
+ __mode: mode
321
+ }, className ? {
322
+ __className: className
323
+ } : {}, customStyleValue ? {
324
+ __style: customStyleValue
325
+ } : {}, mode === 'text' && textContent !== undefined ? {
326
+ __text: textContent
327
+ } : {}, eventBindings), mode === 'view' ? children : null)
328
+ );
329
+ });
330
+ AnimatedBase.displayName = 'AnimatedBase';
331
+
332
+ /**
333
+ * Animated.View 组件(不包含 setText)
334
+ */
335
+ const AnimatedView = /*#__PURE__*/forwardRef((props, ref) => /*#__PURE__*/React.createElement(AnimatedBase, _extends({}, props, {
336
+ mode: "view",
337
+ ref: ref
338
+ })));
339
+ AnimatedView.displayName = 'Animated.View';
340
+
341
+ /**
342
+ * Animated.Text 组件(包含 setText)
343
+ */
344
+ const AnimatedText = /*#__PURE__*/forwardRef((props, ref) => /*#__PURE__*/React.createElement(AnimatedBase, _extends({}, props, {
345
+ mode: "text",
346
+ ref: ref
347
+ })));
348
+ AnimatedText.displayName = 'Animated.Text';
349
+
350
+ /**
351
+ * 默认导出
352
+ */
353
+ const Animated = {
354
+ View: AnimatedView,
355
+ Text: AnimatedText
356
+ };
357
+ export default Animated;
358
+
359
+ // 导出类型
@@ -0,0 +1,5 @@
1
+ import * as React from 'react';
2
+ declare const TyAnimated: React.ComponentType<any>;
3
+ export default TyAnimated;
4
+
5
+
@@ -0,0 +1,208 @@
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.map.js";
6
+ import "core-js/modules/web.dom-collections.iterator.js";
7
+ // @ts-nocheck
8
+ /**
9
+ * TyAnimated - 高性能动画组件(小程序原生层)
10
+ */
11
+
12
+ // 小程序原生组件注册
13
+ Component({
14
+ properties: {
15
+ __mode: {
16
+ type: String,
17
+ value: 'view'
18
+ },
19
+ __text: {
20
+ type: String,
21
+ value: ''
22
+ },
23
+ __style: {
24
+ type: String,
25
+ value: ''
26
+ },
27
+ __className: {
28
+ type: String,
29
+ value: ''
30
+ },
31
+ onAnimationStart: {
32
+ type: null,
33
+ value: null
34
+ },
35
+ // 动画结束回调函数
36
+ onAnimationEnd: {
37
+ type: null,
38
+ value: null
39
+ }
40
+ },
41
+ // 定义外部事件
42
+ externalClasses: [],
43
+ data: {
44
+ __text: '',
45
+ __style: '',
46
+ __className: '',
47
+ __animData: null // 动画数据
48
+ },
49
+ lifetimes: {
50
+ attached() {
51
+ // 组件初始化
52
+ },
53
+ detached() {
54
+ // 清理工作
55
+ }
56
+ },
57
+ methods: {
58
+ /**
59
+ * 事件处理器
60
+ */
61
+ __handleTiggerEvent(eventName, e) {
62
+ // 触发自定义事件,让外部可以监听
63
+ this.triggerEvent(eventName, e, {
64
+ // bubbles: true,
65
+ composed: true
66
+ });
67
+ },
68
+ /**
69
+ * 创建 IntersectionObserver
70
+ */
71
+ createIntersectionObserver(options) {
72
+ try {
73
+ // @ts-ignore
74
+ if (typeof ty !== 'undefined' && ty.createIntersectionObserver) {
75
+ return ty.createIntersectionObserver(this, options);
76
+ }
77
+ } catch (e) {
78
+ console.error('createIntersectionObserver error:', e);
79
+ }
80
+ return null;
81
+ },
82
+ /**
83
+ * 获取小程序的 createAnimation 方法
84
+ */
85
+ _getCreateAnimation() {
86
+ try {
87
+ // @ts-ignore
88
+ if (typeof ty !== 'undefined' && ty.createAnimation) {
89
+ return ty.createAnimation;
90
+ }
91
+ } catch (e) {
92
+ // ignore
93
+ }
94
+ return null;
95
+ },
96
+ /**
97
+ * 解析样式字符串为对象
98
+ */
99
+ _parseStyle(styleString) {
100
+ if (!styleString) return {};
101
+ const result = {};
102
+ const styles = styleString.split(';').filter(Boolean);
103
+ for (const style of styles) {
104
+ const colonIndex = style.indexOf(':');
105
+ if (colonIndex > 0) {
106
+ const key = style.substring(0, colonIndex).trim();
107
+ const value = style.substring(colonIndex + 1).trim();
108
+ if (key && value) {
109
+ result[key] = value;
110
+ }
111
+ }
112
+ }
113
+ return result;
114
+ },
115
+ /**
116
+ * 样式对象转字符串
117
+ */
118
+ _styleToString(styleObj) {
119
+ return Object.entries(styleObj).map(_ref => {
120
+ let [key, value] = _ref;
121
+ return "".concat(key, ":").concat(value);
122
+ }).join(';');
123
+ },
124
+ setCustomStyle(value, callback) {
125
+ // 防止传入 undefined 或 null 导致小程序报错
126
+ if (value === undefined || value === null) {
127
+ console.warn('[TyAnimatedComponent] setCustomStyle: value is undefined or null, ignored');
128
+ if (callback) callback();
129
+ return;
130
+ }
131
+
132
+ // 合并样式
133
+ const currentStyle = this._parseStyle(this.data.__style || '');
134
+ const newStyle = this._parseStyle(value);
135
+ const mergedStyle = _objectSpread(_objectSpread({}, currentStyle), newStyle);
136
+ let mergedStyleString = this._styleToString(mergedStyle);
137
+ if (mergedStyleString === this.data.__style && this.data.__animData !== null) {
138
+ console.log('[TyAnimated] 样式值相同但有动画数据,添加时间戳强制更新');
139
+ // 添加一个不影响渲染的注释,使样式字符串不同
140
+ mergedStyleString = "".concat(mergedStyleString, ";/* ").concat(Date.now(), " */");
141
+ }
142
+
143
+ // 一次性 setData,不嵌套
144
+ this.setData({
145
+ __style: mergedStyleString,
146
+ __animData: null // 总是清空动画数据
147
+ }, callback);
148
+ },
149
+ /**
150
+ * 设置文本内容(__mode='text' 时使用)
151
+ */
152
+ __applyText(text) {
153
+ if (typeof text === 'string') {
154
+ this.setData({
155
+ __text: text
156
+ });
157
+ }
158
+ },
159
+ __applyAnimData(animData) {
160
+ if (!animData) {
161
+ console.warn('[TyAnimatedComponent] __applyAnimData: animData is null or undefined');
162
+ return;
163
+ }
164
+ this.setData({
165
+ __animData: animData
166
+ });
167
+ },
168
+ /**
169
+ * 核心方法:直接应用 Animation 对象
170
+ * @param {Function} fn - 接收 Animation 实例的回调函数
171
+ * @param {Object} options - 动画选项,包括 transformOrigin
172
+ *
173
+ * 使用示例:
174
+ * comp.__apply((anim) => {
175
+ * anim.opacity(0.5).scale(0.8).step();
176
+ * return anim.export();
177
+ * }, { transformOrigin: 'top left' });
178
+ */
179
+ __apply(fn) {
180
+ let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
181
+ const createAnimation = this._getCreateAnimation();
182
+ if (!createAnimation) {
183
+ return;
184
+ }
185
+ const anim = createAnimation(options);
186
+
187
+ // 执行用户的动画配置
188
+ const exported = fn(anim);
189
+
190
+ // 应用动画数据
191
+ const animData = exported || anim.export();
192
+ this.setData({
193
+ __animData: animData
194
+ });
195
+ },
196
+ // 核心触摸事件处理(只保留最常用的)
197
+ __onClick(e) {
198
+ this.__handleTiggerEvent('click', e);
199
+ },
200
+ __onTouchStart(e) {
201
+ this.__handleTiggerEvent('touchstart', e);
202
+ },
203
+ __onTouchEnd(e) {
204
+ this.__handleTiggerEvent('touchend', e);
205
+ }
206
+ }
207
+ });
208
+ export default 'ty-animated-component';
@@ -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
+