@mpxjs/webpack-plugin 2.10.17-beta.2 → 2.10.17-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/config.js +60 -0
- package/lib/file-loader.js +4 -1
- package/lib/global.d.ts +16 -0
- package/lib/index.js +22 -2
- package/lib/json-compiler/index.js +13 -4
- package/lib/platform/json/wx/index.js +6 -0
- package/lib/platform/style/wx/index.js +57 -33
- package/lib/platform/template/wx/component-config/ad.js +5 -0
- package/lib/platform/template/wx/component-config/button.js +9 -2
- package/lib/platform/template/wx/component-config/camera.js +25 -3
- package/lib/platform/template/wx/component-config/canvas.js +8 -1
- package/lib/platform/template/wx/component-config/cover-image.js +7 -2
- package/lib/platform/template/wx/component-config/cover-view.js +3 -1
- package/lib/platform/template/wx/component-config/form.js +27 -2
- package/lib/platform/template/wx/component-config/image.js +5 -0
- package/lib/platform/template/wx/component-config/input.js +10 -0
- package/lib/platform/template/wx/component-config/label.js +10 -2
- package/lib/platform/template/wx/component-config/map.js +11 -0
- package/lib/platform/template/wx/component-config/movable-area.js +4 -1
- package/lib/platform/template/wx/component-config/movable-view.js +17 -2
- package/lib/platform/template/wx/component-config/navigator.js +26 -0
- package/lib/platform/template/wx/component-config/picker-view.js +12 -0
- package/lib/platform/template/wx/component-config/picker.js +3 -1
- package/lib/platform/template/wx/component-config/progress.js +11 -1
- package/lib/platform/template/wx/component-config/rich-text.js +5 -0
- package/lib/platform/template/wx/component-config/scroll-view.js +12 -1
- package/lib/platform/template/wx/component-config/slider.js +8 -0
- package/lib/platform/template/wx/component-config/swiper-item.js +5 -2
- package/lib/platform/template/wx/component-config/swiper.js +10 -0
- package/lib/platform/template/wx/component-config/text.js +5 -0
- package/lib/platform/template/wx/component-config/textarea.js +19 -2
- package/lib/platform/template/wx/component-config/unsupported.js +10 -1
- package/lib/platform/template/wx/component-config/video.js +10 -0
- package/lib/platform/template/wx/index.js +21 -1
- package/lib/react/LoadAsyncChunkModule.js +1 -1
- package/lib/react/processStyles.js +21 -9
- package/lib/react/style-helper.js +76 -13
- package/lib/resolver/AddModePlugin.js +23 -8
- package/lib/runtime/components/react/animationHooks/index.ts +75 -0
- package/lib/runtime/components/react/animationHooks/useAnimationAPIHooks.ts +198 -0
- package/lib/runtime/components/react/animationHooks/useTransitionHooks.ts +297 -0
- package/lib/runtime/components/react/animationHooks/utils.ts +196 -0
- package/lib/runtime/components/react/context.ts +7 -1
- package/lib/runtime/components/react/dist/animationHooks/index.d.ts +16 -0
- package/lib/runtime/components/react/dist/animationHooks/index.d.ts.map +1 -0
- package/lib/runtime/components/react/dist/animationHooks/index.js +67 -0
- package/lib/runtime/components/react/dist/animationHooks/useAnimationAPIHooks.d.ts +4 -0
- package/lib/runtime/components/react/dist/animationHooks/useAnimationAPIHooks.d.ts.map +1 -0
- package/lib/runtime/components/react/dist/animationHooks/useAnimationAPIHooks.js +182 -0
- package/lib/runtime/components/react/dist/animationHooks/useTransitionHooks.d.ts +4 -0
- package/lib/runtime/components/react/dist/animationHooks/useTransitionHooks.d.ts.map +1 -0
- package/lib/runtime/components/react/dist/animationHooks/useTransitionHooks.js +274 -0
- package/lib/runtime/components/react/dist/animationHooks/utils.d.ts +110 -0
- package/lib/runtime/components/react/dist/animationHooks/utils.d.ts.map +1 -0
- package/lib/runtime/components/react/dist/animationHooks/utils.js +150 -0
- package/lib/runtime/components/react/dist/context.d.ts +6 -1
- package/lib/runtime/components/react/dist/context.d.ts.map +1 -1
- package/lib/runtime/components/react/dist/mpx-camera.d.ts +32 -0
- package/lib/runtime/components/react/dist/mpx-camera.d.ts.map +1 -0
- package/lib/runtime/components/react/dist/mpx-camera.jsx +236 -0
- package/lib/runtime/components/react/dist/mpx-input.d.ts +2 -0
- package/lib/runtime/components/react/dist/mpx-input.d.ts.map +1 -1
- package/lib/runtime/components/react/dist/mpx-input.jsx +21 -10
- package/lib/runtime/components/react/dist/mpx-keyboard-avoiding-view.d.ts.map +1 -1
- package/lib/runtime/components/react/dist/mpx-keyboard-avoiding-view.jsx +3 -0
- package/lib/runtime/components/react/dist/mpx-portal/portal-manager.jsx +2 -2
- package/lib/runtime/components/react/dist/mpx-swiper.d.ts +10 -0
- package/lib/runtime/components/react/dist/mpx-swiper.d.ts.map +1 -1
- package/lib/runtime/components/react/dist/mpx-swiper.jsx +28 -16
- package/lib/runtime/components/react/dist/mpx-view.d.ts +3 -2
- package/lib/runtime/components/react/dist/mpx-view.d.ts.map +1 -1
- package/lib/runtime/components/react/dist/mpx-view.jsx +2 -2
- package/lib/runtime/components/react/dist/mpx-web-view.jsx +1 -1
- package/lib/runtime/components/react/dist/utils.d.ts +1 -0
- package/lib/runtime/components/react/dist/utils.d.ts.map +1 -1
- package/lib/runtime/components/react/dist/utils.jsx +34 -13
- package/lib/runtime/components/react/mpx-camera.tsx +327 -0
- package/lib/runtime/components/react/mpx-input.tsx +26 -10
- package/lib/runtime/components/react/mpx-keyboard-avoiding-view.tsx +3 -0
- package/lib/runtime/components/react/mpx-portal/portal-manager.tsx +2 -2
- package/lib/runtime/components/react/mpx-swiper.tsx +43 -15
- package/lib/runtime/components/react/mpx-view.tsx +4 -5
- package/lib/runtime/components/react/mpx-web-view.tsx +1 -1
- package/lib/runtime/components/react/types/global.d.ts +1 -0
- package/lib/runtime/components/react/utils.tsx +34 -16
- package/lib/runtime/optionProcessor.js +5 -0
- package/lib/runtime/optionProcessorReact.js +7 -0
- package/lib/runtime/stringify.wxs +2 -2
- package/lib/style-compiler/strip-conditional-loader/rebaseUrl.js +225 -0
- package/lib/style-compiler/strip-conditional-loader.js +55 -180
- package/lib/template-compiler/compiler.js +1 -3
- package/lib/utils/dom-tag-config.js +1 -1
- package/lib/utils/string.js +25 -1
- package/package.json +2 -1
- package/lib/runtime/components/react/dist/useAnimationHooks.d.ts +0 -33
- package/lib/runtime/components/react/dist/useAnimationHooks.d.ts.map +0 -1
- package/lib/runtime/components/react/dist/useAnimationHooks.js +0 -289
- package/lib/runtime/components/react/useAnimationHooks.ts +0 -320
|
@@ -3,31 +3,70 @@ const selectorParser = require('postcss-selector-parser')
|
|
|
3
3
|
const { MPX_TAG_PAGE_SELECTOR } = require('../utils/const')
|
|
4
4
|
const getRulesRunner = require('../platform/index')
|
|
5
5
|
const dash2hump = require('../utils/hump-dash').dash2hump
|
|
6
|
-
const
|
|
7
|
-
const
|
|
6
|
+
const parseValues = require('../utils/string').parseValues
|
|
7
|
+
const unitRegExp = /^\s*(-?\d+(?:\.\d+)?)(rpx|vw|vh|px)?\s*$/
|
|
8
8
|
const hairlineRegExp = /^\s*hairlineWidth\s*$/
|
|
9
9
|
const varRegExp = /^--/
|
|
10
10
|
const cssPrefixExp = /^-(webkit|moz|ms|o)-/
|
|
11
|
-
function getClassMap ({ content, filename, mode, srcMode, ctorType, warn, error }) {
|
|
12
|
-
const classMap = ctorType === 'page'
|
|
11
|
+
function getClassMap ({ content, filename, mode, srcMode, ctorType, formatValueName, warn, error }) {
|
|
12
|
+
const classMap = ctorType === 'page'
|
|
13
|
+
? { [MPX_TAG_PAGE_SELECTOR]: { flex: 1, height: "'100%'" } }
|
|
14
|
+
: {}
|
|
13
15
|
|
|
14
16
|
const root = postcss.parse(content, {
|
|
15
17
|
from: filename
|
|
16
18
|
})
|
|
17
19
|
|
|
18
20
|
function formatValue (value) {
|
|
19
|
-
let matched
|
|
20
21
|
let needStringify = true
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
const matched = unitRegExp.exec(value)
|
|
23
|
+
if (matched) {
|
|
24
|
+
if (!matched[2] || matched[2] === 'px') {
|
|
25
|
+
value = matched[1]
|
|
26
|
+
needStringify = false
|
|
27
|
+
} else {
|
|
28
|
+
value = `${formatValueName}(${+matched[1]}, '${matched[2]}')`
|
|
29
|
+
needStringify = false
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (hairlineRegExp.test(value)) {
|
|
33
|
+
value = `${formatValueName}(${JSON.stringify(value)}, 'hairlineWidth')`
|
|
26
34
|
needStringify = false
|
|
27
35
|
}
|
|
28
36
|
return needStringify ? JSON.stringify(value) : value
|
|
29
37
|
}
|
|
30
38
|
|
|
39
|
+
function getMediaOptions (params) {
|
|
40
|
+
return parseValues(params).reduce((option, item) => {
|
|
41
|
+
if (['all', 'print'].includes(item)) {
|
|
42
|
+
if (item === 'media') {
|
|
43
|
+
option.type = item
|
|
44
|
+
} else {
|
|
45
|
+
error('not supported ', item)
|
|
46
|
+
return option
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (['not', 'only', 'or', ','].includes(item)) {
|
|
50
|
+
if (item === 'and') {
|
|
51
|
+
option.logical_operators = item
|
|
52
|
+
} else {
|
|
53
|
+
error('not supported ', item)
|
|
54
|
+
return option
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const bracketsExp = /\((.+?)\)/
|
|
58
|
+
if (bracketsExp.test(item)) {
|
|
59
|
+
const range = parseValues((item.match(bracketsExp)?.[1] || ''), ':')
|
|
60
|
+
if (range.length < 2) {
|
|
61
|
+
return option
|
|
62
|
+
} else {
|
|
63
|
+
option[dash2hump(range[0])] = +formatValue(range[1])
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return option
|
|
67
|
+
}, {})
|
|
68
|
+
}
|
|
69
|
+
|
|
31
70
|
const rulesRunner = getRulesRunner({
|
|
32
71
|
mode,
|
|
33
72
|
srcMode,
|
|
@@ -41,13 +80,15 @@ function getClassMap ({ content, filename, mode, srcMode, ctorType, warn, error
|
|
|
41
80
|
root.walkAtRules(rule => {
|
|
42
81
|
if (rule.name !== 'media') {
|
|
43
82
|
warn(`Only @media rule is supported in react native mode temporarily, but got @${rule.name}`)
|
|
83
|
+
// 删除不支持的 AtRule,防止其影响后续解析
|
|
84
|
+
rule.remove()
|
|
44
85
|
}
|
|
45
86
|
})
|
|
46
87
|
|
|
47
88
|
root.walkRules(rule => {
|
|
48
89
|
const classMapValue = {}
|
|
49
90
|
rule.walkDecls(({ prop, value }) => {
|
|
50
|
-
if (cssPrefixExp.test(prop) || cssPrefixExp.test(value)) return
|
|
91
|
+
if (value === 'undefined' || cssPrefixExp.test(prop) || cssPrefixExp.test(value)) return
|
|
51
92
|
let newData = rulesRunner({ prop, value, selector: rule.selector })
|
|
52
93
|
if (!newData) return
|
|
53
94
|
if (!Array.isArray(newData)) {
|
|
@@ -79,7 +120,8 @@ function getClassMap ({ content, filename, mode, srcMode, ctorType, warn, error
|
|
|
79
120
|
})
|
|
80
121
|
|
|
81
122
|
const classMapKeys = []
|
|
82
|
-
|
|
123
|
+
const options = getMediaOptions(rule.parent.params || '')
|
|
124
|
+
const isMedia = options.maxWidth || options.minWidth
|
|
83
125
|
selectorParser(selectors => {
|
|
84
126
|
selectors.each(selector => {
|
|
85
127
|
if (selector.nodes.length === 1 && selector.nodes[0].type === 'class') {
|
|
@@ -93,7 +135,28 @@ function getClassMap ({ content, filename, mode, srcMode, ctorType, warn, error
|
|
|
93
135
|
if (classMapKeys.length) {
|
|
94
136
|
classMapKeys.forEach((key) => {
|
|
95
137
|
if (Object.keys(classMapValue).length) {
|
|
96
|
-
|
|
138
|
+
let _default = classMap[key]?._default
|
|
139
|
+
let _media = classMap[key]?._media
|
|
140
|
+
if (isMedia) {
|
|
141
|
+
// 当前是媒体查询
|
|
142
|
+
_default = _default || {}
|
|
143
|
+
_media = _media || []
|
|
144
|
+
_media.push({
|
|
145
|
+
options,
|
|
146
|
+
value: classMapValue
|
|
147
|
+
})
|
|
148
|
+
classMap[key] = {
|
|
149
|
+
_media,
|
|
150
|
+
_default
|
|
151
|
+
}
|
|
152
|
+
} else if (_default) {
|
|
153
|
+
// 已有媒体查询数据,此次非媒体查询
|
|
154
|
+
Object.assign(_default, classMapValue)
|
|
155
|
+
} else {
|
|
156
|
+
// 无媒体查询
|
|
157
|
+
const val = classMap[key] || {}
|
|
158
|
+
classMap[key] = Object.assign(val, classMapValue)
|
|
159
|
+
}
|
|
97
160
|
}
|
|
98
161
|
})
|
|
99
162
|
}
|
|
@@ -24,6 +24,12 @@ module.exports = class AddModePlugin {
|
|
|
24
24
|
if (request.mode || request.env) {
|
|
25
25
|
return callback()
|
|
26
26
|
}
|
|
27
|
+
|
|
28
|
+
const queryObj = parseQuery(request.query || '?')
|
|
29
|
+
if (queryObj.mode) {
|
|
30
|
+
return callback()
|
|
31
|
+
}
|
|
32
|
+
|
|
27
33
|
const obj = {
|
|
28
34
|
mode
|
|
29
35
|
}
|
|
@@ -37,23 +43,32 @@ module.exports = class AddModePlugin {
|
|
|
37
43
|
// 当前资源没有后缀名或者路径不符合fileConditionRules规则时,直接返回
|
|
38
44
|
if (!extname || !matchCondition(resourcePath, fileConditionRules)) return callback()
|
|
39
45
|
|
|
40
|
-
const queryObj = parseQuery(request.query || '?')
|
|
41
46
|
const queryInfix = queryObj.infix
|
|
42
|
-
if (!implicitMode) queryObj.mode = mode
|
|
43
|
-
queryObj.infix = `${queryInfix || ''}.${mode}`
|
|
44
47
|
|
|
45
48
|
// 如果已经确认是mode后缀的文件,添加query与mode后直接返回
|
|
46
49
|
if (modePattern.test(path.basename(resourcePath))) {
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
// 已经被resolved到对应mode的文件,避免重复添加mode
|
|
51
|
+
const isResolved = (implicitMode || queryObj.mode === mode) && modePattern.test(queryObj.infix)
|
|
52
|
+
if (!isResolved) {
|
|
53
|
+
queryObj.infix = `${queryInfix || ''}.${mode}`
|
|
54
|
+
if (!implicitMode) queryObj.mode = mode
|
|
55
|
+
request.query = stringifyQuery(queryObj)
|
|
56
|
+
request.mode = obj.mode
|
|
57
|
+
}
|
|
49
58
|
return callback()
|
|
50
59
|
} else if (defaultMode && defaultModePattern.test(path.basename(resourcePath))) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
60
|
+
const isResolved = (implicitMode || queryObj.mode === mode) && defaultModePattern.test(queryObj.infix)
|
|
61
|
+
if (!isResolved) {
|
|
62
|
+
queryObj.infix = `${queryInfix || ''}.${defaultMode}`
|
|
63
|
+
if (!implicitMode) queryObj.mode = mode
|
|
64
|
+
request.query = stringifyQuery(queryObj)
|
|
65
|
+
request.mode = obj.mode
|
|
66
|
+
}
|
|
54
67
|
return callback()
|
|
55
68
|
}
|
|
56
69
|
|
|
70
|
+
if (!implicitMode) queryObj.mode = mode
|
|
71
|
+
queryObj.infix = `${queryInfix || ''}.${mode}`
|
|
57
72
|
obj.query = stringifyQuery(queryObj)
|
|
58
73
|
obj.path = addInfix(resourcePath, mode, extname)
|
|
59
74
|
obj.relativePath = request.relativePath && addInfix(request.relativePath, mode, extname)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { error, collectDataset, hasOwn } from '@mpxjs/utils'
|
|
2
|
+
import { useRef } from 'react'
|
|
3
|
+
import useAnimationAPIHooks from './useAnimationAPIHooks'
|
|
4
|
+
import useTransitionHooks from './useTransitionHooks'
|
|
5
|
+
import type { AnimatableValue } from 'react-native-reanimated'
|
|
6
|
+
import type { MutableRefObject } from 'react'
|
|
7
|
+
import type { NativeSyntheticEvent } from 'react-native'
|
|
8
|
+
import type { _ViewProps } from '../mpx-view'
|
|
9
|
+
|
|
10
|
+
// 动画类型
|
|
11
|
+
export type AnimationType = 'api'|'animation'|'transition'|'none'
|
|
12
|
+
|
|
13
|
+
export default function useAnimationHooks<T, P> (props: _ViewProps & { enableAnimation?: boolean | AnimationType, layoutRef: MutableRefObject<any>, transitionend?: (event: NativeSyntheticEvent<TouchEvent> | unknown) => void }) {
|
|
14
|
+
const { style: originalStyle = {}, enableAnimation, animation, transitionend, layoutRef } = props
|
|
15
|
+
// 记录动画类型
|
|
16
|
+
let animationType = ''
|
|
17
|
+
if (hasOwn(originalStyle, 'animation') || (hasOwn(originalStyle, 'animationName') && hasOwn(originalStyle, 'animationDuration'))) {
|
|
18
|
+
// css animation 只做检测提示
|
|
19
|
+
animationType = 'animation'
|
|
20
|
+
}
|
|
21
|
+
if (!!animation || enableAnimation === true) {
|
|
22
|
+
animationType = 'api'
|
|
23
|
+
}
|
|
24
|
+
// 优先级 css transition > API
|
|
25
|
+
if (hasOwn(originalStyle, 'transition') || (hasOwn(originalStyle, 'transitionProperty') && hasOwn(originalStyle, 'transitionDuration'))) {
|
|
26
|
+
animationType = 'transition'
|
|
27
|
+
}
|
|
28
|
+
// 优先以 enableAnimation 定义类型为准
|
|
29
|
+
if (enableAnimation === 'api' || enableAnimation === 'transition' || enableAnimation === 'animation') {
|
|
30
|
+
animationType = enableAnimation
|
|
31
|
+
}
|
|
32
|
+
const animationTypeRef = useRef(animationType)
|
|
33
|
+
if (animationType! && animationTypeRef.current !== animationType) {
|
|
34
|
+
// 允许 API、CssTransition 到 none,不允许 API、CssTransition 互切,不允许 none 到 API、CssTransition
|
|
35
|
+
error('[Mpx runtime error]: The animation type should be stable in the component lifecycle, or you can set animation type with [enable-animation].')
|
|
36
|
+
}
|
|
37
|
+
if (animationType === 'animation') {
|
|
38
|
+
// 暂不支持 CssAnimation 提示
|
|
39
|
+
error('[Mpx runtime error]: CSS animation is not supported yet')
|
|
40
|
+
return { enableStyleAnimation: false }
|
|
41
|
+
}
|
|
42
|
+
if (!animationTypeRef.current) return { enableStyleAnimation: false }
|
|
43
|
+
|
|
44
|
+
const hooksProps = { style: originalStyle }
|
|
45
|
+
if (transitionend && typeof transitionend === 'function') {
|
|
46
|
+
function withTimingCallback (finished?: boolean, current?: AnimatableValue, duration?: number) {
|
|
47
|
+
const target = {
|
|
48
|
+
id: animation?.id || -1,
|
|
49
|
+
dataset: collectDataset(props),
|
|
50
|
+
offsetLeft: layoutRef?.current?.offsetLeft || 0,
|
|
51
|
+
offsetTop: layoutRef?.current?.offsetTop || 0
|
|
52
|
+
}
|
|
53
|
+
transitionend!({
|
|
54
|
+
type: 'transitionend',
|
|
55
|
+
// elapsedTime 对齐wx 单位s
|
|
56
|
+
detail: { elapsedTime: duration ? duration / 1000 : 0, finished, current },
|
|
57
|
+
target,
|
|
58
|
+
currentTarget: target,
|
|
59
|
+
timeStamp: Date.now()
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
Object.assign(hooksProps, { transitionend: withTimingCallback })
|
|
63
|
+
}
|
|
64
|
+
if (animationTypeRef.current === 'api') {
|
|
65
|
+
Object.assign(hooksProps, { animation })
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
enableStyleAnimation: !!animationTypeRef.current,
|
|
69
|
+
animationStyle: animationTypeRef.current === 'api'
|
|
70
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
71
|
+
? useAnimationAPIHooks(hooksProps)
|
|
72
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
73
|
+
: useTransitionHooks(hooksProps)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { useMemo, useRef, useEffect } from 'react'
|
|
2
|
+
import { error, hasOwn } from '@mpxjs/utils'
|
|
3
|
+
import {
|
|
4
|
+
Easing,
|
|
5
|
+
withSequence,
|
|
6
|
+
makeMutable,
|
|
7
|
+
runOnJS,
|
|
8
|
+
useSharedValue,
|
|
9
|
+
useAnimatedStyle,
|
|
10
|
+
cancelAnimation
|
|
11
|
+
} from 'react-native-reanimated'
|
|
12
|
+
import {
|
|
13
|
+
easingKey,
|
|
14
|
+
animationAPIInitialValue,
|
|
15
|
+
percentExp,
|
|
16
|
+
isTransform,
|
|
17
|
+
getInitialVal,
|
|
18
|
+
getAnimation,
|
|
19
|
+
getTransformObj,
|
|
20
|
+
formatAnimatedKeys
|
|
21
|
+
} from './utils'
|
|
22
|
+
import { useRunOnJSCallback } from '../utils'
|
|
23
|
+
import type { NativeSyntheticEvent, TransformsStyle } from 'react-native'
|
|
24
|
+
import type { AnimationCallback, SharedValue, AnimatableValue } from 'react-native-reanimated'
|
|
25
|
+
import type { ExtendedViewStyle } from '../types/common'
|
|
26
|
+
import type { AnimationHooksPropsType } from './utils'
|
|
27
|
+
|
|
28
|
+
export default function useAnimationAPIHooks<T, P> (props: AnimationHooksPropsType) {
|
|
29
|
+
// console.log(`useAnimationAPIHooks, props=`, props)
|
|
30
|
+
const { style: originalStyle = {}, animation, transitionend } = props
|
|
31
|
+
// style变更标识(首次render不执行)
|
|
32
|
+
const animationDeps = useRef(-1)
|
|
33
|
+
// animation API 使用 animation.id 为依赖
|
|
34
|
+
if (animation?.id) {
|
|
35
|
+
animationDeps.current = animation.id
|
|
36
|
+
}
|
|
37
|
+
// 有动画样式的 style key(useAnimatedStyle使用)
|
|
38
|
+
const animatedStyleKeys = useSharedValue([] as (string|string[])[])
|
|
39
|
+
// 记录需要执行动画的 propName
|
|
40
|
+
const animatedKeys = useRef([] as string[])
|
|
41
|
+
// 记录上次style map
|
|
42
|
+
const lastStyleRef = useRef({} as {[propName: keyof ExtendedViewStyle]: number|string})
|
|
43
|
+
// ** 全量 style prop sharedValue
|
|
44
|
+
const shareValMap = useMemo(() => {
|
|
45
|
+
return Object.keys(animationAPIInitialValue).reduce((valMap, key) => {
|
|
46
|
+
const defaultVal = getInitialVal(originalStyle, key)
|
|
47
|
+
valMap[key] = makeMutable(defaultVal)
|
|
48
|
+
return valMap
|
|
49
|
+
}, {} as { [propName: keyof ExtendedViewStyle]: SharedValue<string|number> })
|
|
50
|
+
}, [])
|
|
51
|
+
const runOnJSCallbackRef = useRef({})
|
|
52
|
+
if (transitionend) {
|
|
53
|
+
runOnJSCallbackRef.current = {
|
|
54
|
+
transitionend
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const runOnJSCallback = useRunOnJSCallback(runOnJSCallbackRef)
|
|
58
|
+
// 设置 lastShareValRef & shareValMap
|
|
59
|
+
function updateStyleVal () {
|
|
60
|
+
Object.keys(shareValMap).forEach(key => {
|
|
61
|
+
let value = originalStyle[key]
|
|
62
|
+
if (isTransform(key)) {
|
|
63
|
+
value = originalStyle.transform
|
|
64
|
+
Object.entries(getTransformObj(value)).forEach(([key, value]) => {
|
|
65
|
+
if (value !== lastStyleRef.current[key]) {
|
|
66
|
+
lastStyleRef.current[key] = value
|
|
67
|
+
shareValMap[key].value = value
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
} else {
|
|
71
|
+
if (value !== lastStyleRef.current[key]) {
|
|
72
|
+
lastStyleRef.current[key] = value
|
|
73
|
+
shareValMap[key].value = value
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
// 根据 animation action 创建&驱动动画
|
|
79
|
+
function createAnimation (animatedKeys: string[] = []) {
|
|
80
|
+
const actions = animation?.actions || []
|
|
81
|
+
const sequence = {} as { [propName: keyof ExtendedViewStyle]: (string|number)[] }
|
|
82
|
+
const lastValueMap = {} as { [propName: keyof ExtendedViewStyle]: string|number }
|
|
83
|
+
actions.forEach(({ animatedOption, rules, transform }, index) => {
|
|
84
|
+
const { delay, duration, timingFunction, transformOrigin } = animatedOption
|
|
85
|
+
const easing = timingFunction ? easingKey[timingFunction] : Easing.inOut(Easing.quad)
|
|
86
|
+
let needSetCallback = true
|
|
87
|
+
const callback: AnimationCallback = (finished?: boolean, current?: AnimatableValue) => {
|
|
88
|
+
'worklet'
|
|
89
|
+
// 动画结束后设置下一次transformOrigin
|
|
90
|
+
if (finished) {
|
|
91
|
+
if (index < actions.length - 1) {
|
|
92
|
+
const transformOrigin = actions[index + 1].animatedOption?.transformOrigin
|
|
93
|
+
transformOrigin && (shareValMap.transformOrigin.value = transformOrigin)
|
|
94
|
+
}
|
|
95
|
+
transitionend && runOnJS(runOnJSCallback)('transitionend', finished, current, duration)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (index === 0 && transformOrigin) {
|
|
99
|
+
// 设置当次中心
|
|
100
|
+
shareValMap.transformOrigin.value = transformOrigin
|
|
101
|
+
}
|
|
102
|
+
// 添加每个key的多次step动画
|
|
103
|
+
animatedKeys.forEach(key => {
|
|
104
|
+
const shareVal = shareValMap[key].value
|
|
105
|
+
const ruleV = isTransform(key) ? transform.get(key) : rules.get(key)
|
|
106
|
+
// color 设置为 1
|
|
107
|
+
// key不存在,第一轮取shareValMap[key]value,非第一轮取上一轮的
|
|
108
|
+
let toVal = ruleV !== undefined
|
|
109
|
+
? ruleV
|
|
110
|
+
: index > 0
|
|
111
|
+
? lastValueMap[key]
|
|
112
|
+
: shareVal
|
|
113
|
+
if (percentExp.test(`${toVal}`) && !percentExp.test(shareVal as string) && !isNaN(+shareVal)) {
|
|
114
|
+
// 获取到的toVal为百分比格式化shareValMap为百分比
|
|
115
|
+
shareValMap[key].value = `${shareVal as number * 100}%`
|
|
116
|
+
} else if (percentExp.test(shareVal as string) && !percentExp.test(toVal as string) && !isNaN(+toVal)) {
|
|
117
|
+
// 初始值为百分比则格式化toVal为百分比
|
|
118
|
+
toVal = `${toVal as number * 100}%`
|
|
119
|
+
} else if (typeof toVal !== typeof shareVal) {
|
|
120
|
+
// 动画起始值和终态值类型不一致报错提示一下
|
|
121
|
+
error(`[Mpx runtime error]: Value types of property ${key} must be consistent during the animation`)
|
|
122
|
+
}
|
|
123
|
+
// Todo 对齐wx
|
|
124
|
+
const animation = getAnimation({ key, value: toVal! }, { delay, duration, easing }, needSetCallback ? callback : undefined)
|
|
125
|
+
needSetCallback = false
|
|
126
|
+
if (!sequence[key]) {
|
|
127
|
+
sequence[key] = [animation]
|
|
128
|
+
} else {
|
|
129
|
+
sequence[key].push(animation)
|
|
130
|
+
}
|
|
131
|
+
// 更新一下 lastValueMap
|
|
132
|
+
lastValueMap[key] = toVal!
|
|
133
|
+
})
|
|
134
|
+
// 赋值驱动动画
|
|
135
|
+
animatedKeys.forEach((key) => {
|
|
136
|
+
const animations = sequence[key]
|
|
137
|
+
shareValMap[key].value = animations.length > 1 ? withSequence(...animations) : animations[0]
|
|
138
|
+
})
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
// 循环 animation actions 获取所有有动画的 style prop name
|
|
142
|
+
function getAnimatedStyleKeys () {
|
|
143
|
+
return (animation?.actions || []).reduce((keyMap, action) => {
|
|
144
|
+
const { rules, transform } = action
|
|
145
|
+
const ruleArr = [...rules.keys(), ...transform.keys()]
|
|
146
|
+
ruleArr.forEach(key => {
|
|
147
|
+
keyMap.push(key)
|
|
148
|
+
})
|
|
149
|
+
// console.log('getAnimatedStyleKeys keyMap=', keyMap)
|
|
150
|
+
return keyMap
|
|
151
|
+
}, [] as string[])
|
|
152
|
+
}
|
|
153
|
+
// 获取动画样式&驱动动画
|
|
154
|
+
function startAnimation () {
|
|
155
|
+
// 更新动画样式 key map
|
|
156
|
+
animatedKeys.current = getAnimatedStyleKeys()
|
|
157
|
+
animatedStyleKeys.value = formatAnimatedKeys(['transformOrigin', ...animatedKeys.current])
|
|
158
|
+
// 驱动动画
|
|
159
|
+
createAnimation(animatedKeys.current)
|
|
160
|
+
}
|
|
161
|
+
// ** style 更新
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
// animation api style 更新同步更新 shareVal(默认)
|
|
164
|
+
updateStyleVal()
|
|
165
|
+
}, [originalStyle])
|
|
166
|
+
// ** 获取动画样式prop & 驱动动画
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
if (animationDeps.current <= 0) return
|
|
169
|
+
startAnimation()
|
|
170
|
+
}, [animationDeps.current])
|
|
171
|
+
// ** 清空动画
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
return () => {
|
|
174
|
+
Object.values(shareValMap).forEach((value) => {
|
|
175
|
+
cancelAnimation(value)
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
}, [])
|
|
179
|
+
// ** 生成动画样式
|
|
180
|
+
return useAnimatedStyle(() => {
|
|
181
|
+
// console.info(`useAnimatedStyle styles=`, originalStyle)
|
|
182
|
+
return animatedStyleKeys.value.reduce((styles, key) => {
|
|
183
|
+
if (Array.isArray(key)) {
|
|
184
|
+
const transformStyle = getTransformObj(originalStyle.transform || [])
|
|
185
|
+
key.forEach((transformKey) => {
|
|
186
|
+
transformStyle[transformKey] = shareValMap[transformKey].value
|
|
187
|
+
})
|
|
188
|
+
styles.transform = Object.entries(transformStyle).map(([key, value]) => {
|
|
189
|
+
return { [key]: value }
|
|
190
|
+
}) as Extract<'transform', TransformsStyle>
|
|
191
|
+
} else {
|
|
192
|
+
styles[key] = shareValMap[key].value
|
|
193
|
+
}
|
|
194
|
+
// console.log('animationStyle', styles)
|
|
195
|
+
return styles
|
|
196
|
+
}, {} as ExtendedViewStyle)
|
|
197
|
+
})
|
|
198
|
+
}
|