@mpxjs/webpack-plugin 2.10.24 → 2.11.0
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/index.js +4 -1
- package/lib/platform/style/wx/index.js +417 -120
- package/lib/platform/template/wx/component-config/picker.js +0 -6
- package/lib/react/style-helper.js +6 -6
- package/lib/resolver/AddEnvPlugin.js +1 -1
- package/lib/resolver/AddModePlugin.js +1 -1
- package/lib/resolver/ExtendComponentsPlugin.js +68 -0
- package/lib/runtime/components/extends/section-list.mpx +14 -0
- package/lib/runtime/components/react/animationHooks/index.ts +7 -3
- package/lib/runtime/components/react/dist/animationHooks/index.d.ts +0 -2
- package/lib/runtime/components/react/dist/animationHooks/index.js +6 -1
- package/lib/runtime/components/react/dist/event.config.d.ts +2 -6
- package/lib/runtime/components/react/dist/event.config.js +28 -24
- package/lib/runtime/components/react/dist/getInnerListeners.d.ts +1 -1
- package/lib/runtime/components/react/dist/getInnerListeners.js +74 -56
- package/lib/runtime/components/react/dist/mpx-button.d.ts +1 -2
- package/lib/runtime/components/react/dist/mpx-button.jsx +16 -9
- package/lib/runtime/components/react/dist/mpx-camera.d.ts +0 -2
- package/lib/runtime/components/react/dist/mpx-camera.jsx +36 -5
- package/lib/runtime/components/react/dist/mpx-canvas/index.d.ts +0 -2
- package/lib/runtime/components/react/dist/mpx-canvas/index.jsx +1 -3
- package/lib/runtime/components/react/dist/mpx-canvas/utils.d.ts +1 -1
- package/lib/runtime/components/react/dist/mpx-checkbox-group.d.ts +0 -2
- package/lib/runtime/components/react/dist/mpx-checkbox-group.jsx +3 -4
- package/lib/runtime/components/react/dist/mpx-checkbox.d.ts +1 -2
- package/lib/runtime/components/react/dist/mpx-checkbox.jsx +8 -9
- package/lib/runtime/components/react/dist/mpx-form.d.ts +1 -2
- package/lib/runtime/components/react/dist/mpx-form.jsx +5 -5
- package/lib/runtime/components/react/dist/mpx-icon/index.d.ts +0 -2
- package/lib/runtime/components/react/dist/mpx-icon/index.jsx +2 -2
- package/lib/runtime/components/react/dist/mpx-image.d.ts +0 -2
- package/lib/runtime/components/react/dist/mpx-image.jsx +59 -43
- package/lib/runtime/components/react/dist/mpx-inline-text.jsx +13 -4
- package/lib/runtime/components/react/dist/mpx-input.d.ts +0 -2
- package/lib/runtime/components/react/dist/mpx-input.jsx +16 -8
- package/lib/runtime/components/react/dist/mpx-label.d.ts +1 -2
- package/lib/runtime/components/react/dist/mpx-label.jsx +5 -6
- package/lib/runtime/components/react/dist/mpx-movable-area.d.ts +0 -2
- package/lib/runtime/components/react/dist/mpx-movable-area.jsx +3 -3
- package/lib/runtime/components/react/dist/mpx-movable-view.d.ts +1 -2
- package/lib/runtime/components/react/dist/mpx-movable-view.jsx +6 -6
- package/lib/runtime/components/react/dist/mpx-nav.jsx +1 -1
- package/lib/runtime/components/react/dist/mpx-picker/index.jsx +72 -23
- package/lib/runtime/components/react/dist/mpx-picker/multiSelector.jsx +2 -2
- package/lib/runtime/components/react/dist/mpx-picker/type.d.ts +4 -0
- package/lib/runtime/components/react/dist/mpx-picker-view/index.d.ts +1 -1
- package/lib/runtime/components/react/dist/mpx-picker-view/index.jsx +10 -14
- package/lib/runtime/components/react/dist/mpx-picker-view/pickerVIewContext.d.ts +0 -2
- package/lib/runtime/components/react/dist/mpx-picker-view/pickerVIewContext.js +0 -5
- package/lib/runtime/components/react/dist/mpx-picker-view-column/index.d.ts +1 -1
- package/lib/runtime/components/react/dist/mpx-picker-view-column/index.jsx +34 -14
- package/lib/runtime/components/react/dist/mpx-picker-view-column/pickerViewColumnItem.d.ts +0 -2
- package/lib/runtime/components/react/dist/mpx-picker-view-column/pickerViewColumnItem.jsx +4 -5
- package/lib/runtime/components/react/dist/mpx-picker-view-column/pickerViewColumnItemLite.d.ts +0 -2
- package/lib/runtime/components/react/dist/mpx-picker-view-column/pickerViewColumnItemLite.jsx +3 -5
- package/lib/runtime/components/react/dist/mpx-popup/index.d.ts +5 -4
- package/lib/runtime/components/react/dist/mpx-popup/index.jsx +2 -2
- package/lib/runtime/components/react/dist/mpx-popup/popupBase.jsx +3 -2
- package/lib/runtime/components/react/dist/mpx-progress.d.ts +0 -2
- package/lib/runtime/components/react/dist/mpx-progress.jsx +1 -3
- package/lib/runtime/components/react/dist/mpx-radio-group.d.ts +0 -2
- package/lib/runtime/components/react/dist/mpx-radio-group.jsx +3 -4
- package/lib/runtime/components/react/dist/mpx-radio.d.ts +1 -2
- package/lib/runtime/components/react/dist/mpx-radio.jsx +8 -9
- package/lib/runtime/components/react/dist/mpx-rich-text/index.d.ts +0 -2
- package/lib/runtime/components/react/dist/mpx-rich-text/index.jsx +1 -3
- package/lib/runtime/components/react/dist/mpx-scroll-view.d.ts +1 -2
- package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +6 -6
- package/lib/runtime/components/react/dist/mpx-section-list.d.ts +51 -0
- package/lib/runtime/components/react/dist/mpx-section-list.jsx +376 -0
- package/lib/runtime/components/react/dist/mpx-simple-text.jsx +37 -19
- package/lib/runtime/components/react/dist/mpx-simple-view.d.ts +4 -1
- package/lib/runtime/components/react/dist/mpx-simple-view.jsx +11 -4
- package/lib/runtime/components/react/dist/mpx-slider.d.ts +0 -2
- package/lib/runtime/components/react/dist/mpx-slider.jsx +6 -7
- package/lib/runtime/components/react/dist/mpx-sticky-header.d.ts +1 -2
- package/lib/runtime/components/react/dist/mpx-sticky-header.jsx +5 -5
- package/lib/runtime/components/react/dist/mpx-sticky-section.d.ts +1 -2
- package/lib/runtime/components/react/dist/mpx-sticky-section.jsx +6 -6
- package/lib/runtime/components/react/dist/mpx-swiper-item.d.ts +1 -2
- package/lib/runtime/components/react/dist/mpx-swiper-item.jsx +5 -5
- package/lib/runtime/components/react/dist/mpx-swiper.d.ts +1 -2
- package/lib/runtime/components/react/dist/mpx-swiper.jsx +4 -8
- package/lib/runtime/components/react/dist/mpx-switch.d.ts +0 -2
- package/lib/runtime/components/react/dist/mpx-switch.jsx +1 -3
- package/lib/runtime/components/react/dist/mpx-text.d.ts +0 -2
- package/lib/runtime/components/react/dist/mpx-text.jsx +43 -25
- package/lib/runtime/components/react/dist/mpx-video.d.ts +0 -2
- package/lib/runtime/components/react/dist/mpx-video.jsx +4 -5
- package/lib/runtime/components/react/dist/mpx-view.d.ts +2 -2
- package/lib/runtime/components/react/dist/mpx-view.jsx +214 -114
- package/lib/runtime/components/react/dist/mpx-web-view.d.ts +2 -1
- package/lib/runtime/components/react/dist/mpx-web-view.jsx +5 -1
- package/lib/runtime/components/react/dist/namedColorSet.d.ts +2 -0
- package/lib/runtime/components/react/dist/namedColorSet.js +149 -0
- package/lib/runtime/components/react/dist/utils.d.ts +71 -70
- package/lib/runtime/components/react/dist/utils.jsx +1295 -456
- package/lib/runtime/components/react/event.config.ts +32 -25
- package/lib/runtime/components/react/getInnerListeners.ts +84 -60
- package/lib/runtime/components/react/mpx-button.tsx +18 -18
- package/lib/runtime/components/react/mpx-camera.tsx +40 -8
- package/lib/runtime/components/react/mpx-canvas/index.tsx +5 -7
- package/lib/runtime/components/react/mpx-canvas/utils.tsx +3 -1
- package/lib/runtime/components/react/mpx-checkbox-group.tsx +2 -8
- package/lib/runtime/components/react/mpx-checkbox.tsx +9 -13
- package/lib/runtime/components/react/mpx-form.tsx +6 -8
- package/lib/runtime/components/react/mpx-icon/index.tsx +1 -5
- package/lib/runtime/components/react/mpx-image.tsx +66 -58
- package/lib/runtime/components/react/mpx-inline-text.tsx +13 -4
- package/lib/runtime/components/react/mpx-input.tsx +19 -12
- package/lib/runtime/components/react/mpx-label.tsx +6 -10
- package/lib/runtime/components/react/mpx-movable-area.tsx +3 -5
- package/lib/runtime/components/react/mpx-movable-view.tsx +7 -9
- package/lib/runtime/components/react/mpx-nav.tsx +1 -1
- package/lib/runtime/components/react/mpx-picker/index.tsx +100 -29
- package/lib/runtime/components/react/mpx-picker/multiSelector.tsx +3 -3
- package/lib/runtime/components/react/mpx-picker/selector.tsx +1 -1
- package/lib/runtime/components/react/mpx-picker/type.ts +4 -0
- package/lib/runtime/components/react/mpx-picker-view/index.tsx +17 -24
- package/lib/runtime/components/react/mpx-picker-view/pickerVIewContext.ts +0 -9
- package/lib/runtime/components/react/mpx-picker-view-column/index.tsx +44 -23
- package/lib/runtime/components/react/mpx-picker-view-column/pickerViewColumnItem.tsx +1 -9
- package/lib/runtime/components/react/mpx-picker-view-column/pickerViewColumnItemLite.tsx +0 -9
- package/lib/runtime/components/react/mpx-popup/index.tsx +7 -3
- package/lib/runtime/components/react/mpx-popup/popupBase.tsx +3 -2
- package/lib/runtime/components/react/mpx-progress.tsx +0 -6
- package/lib/runtime/components/react/mpx-radio-group.tsx +2 -8
- package/lib/runtime/components/react/mpx-radio.tsx +9 -13
- package/lib/runtime/components/react/mpx-rich-text/index.tsx +0 -6
- package/lib/runtime/components/react/mpx-scroll-view.tsx +7 -9
- package/lib/runtime/components/react/mpx-section-list.tsx +557 -0
- package/lib/runtime/components/react/mpx-simple-text.tsx +36 -25
- package/lib/runtime/components/react/mpx-simple-view.tsx +17 -5
- package/lib/runtime/components/react/mpx-slider.tsx +15 -13
- package/lib/runtime/components/react/mpx-sticky-header.tsx +6 -8
- package/lib/runtime/components/react/mpx-sticky-section.tsx +7 -9
- package/lib/runtime/components/react/mpx-swiper-item.tsx +6 -7
- package/lib/runtime/components/react/mpx-swiper.tsx +5 -11
- package/lib/runtime/components/react/mpx-switch.tsx +0 -6
- package/lib/runtime/components/react/mpx-text.tsx +45 -36
- package/lib/runtime/components/react/mpx-video.tsx +3 -8
- package/lib/runtime/components/react/mpx-view.tsx +235 -129
- package/lib/runtime/components/react/mpx-web-view.tsx +12 -6
- package/lib/runtime/components/react/namedColorSet.ts +150 -0
- package/lib/runtime/components/react/types/getInnerListeners.d.ts +21 -1
- package/lib/runtime/components/react/types/global.d.ts +1 -0
- package/lib/runtime/components/react/utils.tsx +1352 -550
- package/lib/runtime/i18n.wxs +15 -9
- package/lib/style-compiler/strip-conditional.js +19 -4
- package/lib/template-compiler/compiler.js +6 -0
- package/package.json +6 -6
|
@@ -1,52 +1,63 @@
|
|
|
1
1
|
import { useEffect, useCallback, useMemo, useRef, ReactNode, ReactElement, isValidElement, useContext, useState, Dispatch, SetStateAction, createElement, MutableRefObject } from 'react'
|
|
2
2
|
import { LayoutChangeEvent, TextStyle, ImageProps, Image } from 'react-native'
|
|
3
|
-
import { isObject, isFunction, isNumber, hasOwn, diffAndCloneA, error, warn } from '@mpxjs/utils'
|
|
3
|
+
import { isObject, isFunction, isNumber, hasOwn, diffAndCloneA, shallowEqual, error, warn } from '@mpxjs/utils'
|
|
4
4
|
import { VarContext, ScrollViewContext, RouteContext, TextPassThroughContext, TextPassThroughContextValue } from './context'
|
|
5
5
|
import { ExpressionParser, parseFunc, ReplaceSource } from './parser'
|
|
6
6
|
import { initialWindowMetrics } from 'react-native-safe-area-context'
|
|
7
7
|
import type { AnyFunc, ExtendedFunctionComponent } from './types/common'
|
|
8
8
|
import { Gesture } from 'react-native-gesture-handler'
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
// ============================================================
|
|
11
|
+
// declare + constants
|
|
12
|
+
// ============================================================
|
|
13
|
+
|
|
14
|
+
declare const __mpx_mode__: 'ios' | 'android' | 'harmony'
|
|
15
|
+
|
|
16
|
+
export const percentRegExp = /^\s*-?\d+(\.\d+)?%\s*$/
|
|
17
|
+
export const svgRegExp = /\.svg(?:[?#].*)?$/i
|
|
18
|
+
export const hiddenStyle = {
|
|
19
|
+
opacity: 0
|
|
20
|
+
}
|
|
21
|
+
export const isIOS = __mpx_mode__ === 'ios'
|
|
22
|
+
export const isAndroid = __mpx_mode__ === 'android'
|
|
23
|
+
export const isHarmony = __mpx_mode__ === 'harmony'
|
|
24
|
+
export const extendObject = Object.assign
|
|
25
|
+
|
|
26
|
+
const textStyleMap: Record<string, boolean> = {
|
|
11
27
|
color: true,
|
|
12
28
|
letterSpacing: true,
|
|
13
29
|
lineHeight: true,
|
|
14
30
|
includeFontPadding: true,
|
|
15
31
|
writingDirection: true
|
|
16
32
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
33
|
+
const urlRegExp = /^\s*url\(["']?(.*?)["']?\)\s*$/
|
|
34
|
+
const linearGradientRegExp = /^\s*linear-gradient\(.*\)\s*$/
|
|
35
|
+
const digitStartRegExp = /^\d/
|
|
36
|
+
const varDecRegExp = /^--/
|
|
37
|
+
const varUseRegExp = /var\(/
|
|
38
|
+
const unoVarDecRegExp = /^--un-/
|
|
39
|
+
const unoVarUseRegExp = /var\(--un-/
|
|
40
|
+
const lengthValueRegExp = /^(-?(?:\d+(?:\.\d+)?|\.\d+)(?:rpx|px|%|vw|vh)?|hairlineWidth)$/
|
|
41
|
+
const DEFAULT_FONT_SIZE = 16
|
|
42
|
+
// transform: 'rotateX(45deg) ...' 单段拆出 fn 名与括号内值
|
|
43
|
+
const transformFnRegExp = /([/\w]+)\((.+)\)/
|
|
44
|
+
// boxShadow 子值识别 rpx 单位(仅 rpx 需要换算为 px,其它单位保留原样)
|
|
45
|
+
const rpxSuffixRegExp = /\d+rpx$/
|
|
46
|
+
// font-family / font 简写解析时统一去掉单/双引号
|
|
47
|
+
const quoteCharRegExp = /["']/g
|
|
48
|
+
const defaultBoxSizingStyle = {
|
|
49
|
+
boxSizing: 'content-box'
|
|
50
|
+
}
|
|
51
|
+
const backgroundStyleMap: Record<string, boolean> = {
|
|
21
52
|
backgroundImage: true,
|
|
22
53
|
backgroundSize: true,
|
|
23
54
|
backgroundRepeat: true,
|
|
24
55
|
backgroundPosition: true
|
|
25
56
|
}
|
|
26
|
-
|
|
57
|
+
const textPropsMap: Record<string, boolean> = {
|
|
27
58
|
ellipsizeMode: true,
|
|
28
59
|
numberOfLines: true
|
|
29
60
|
}
|
|
30
|
-
export const DEFAULT_FONT_SIZE = 16
|
|
31
|
-
export const HIDDEN_STYLE = {
|
|
32
|
-
opacity: 0
|
|
33
|
-
}
|
|
34
|
-
export const DEFAULT_BOX_SIZING_STYLE = {
|
|
35
|
-
boxSizing: 'content-box'
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
declare const __mpx_mode__: 'ios' | 'android' | 'harmony'
|
|
39
|
-
|
|
40
|
-
export const isIOS = __mpx_mode__ === 'ios'
|
|
41
|
-
export const isAndroid = __mpx_mode__ === 'android'
|
|
42
|
-
export const isHarmony = __mpx_mode__ === 'harmony'
|
|
43
|
-
|
|
44
|
-
const varDecRegExp = /^--/
|
|
45
|
-
const varUseRegExp = /var\(/
|
|
46
|
-
const unoVarDecRegExp = /^--un-/
|
|
47
|
-
const unoVarUseRegExp = /var\(--un-/
|
|
48
|
-
const calcUseRegExp = /calc\(/
|
|
49
|
-
const envUseRegExp = /env\(/
|
|
50
61
|
const boxSizingAffectingStyleMap: Record<string, boolean> = {
|
|
51
62
|
padding: true,
|
|
52
63
|
paddingTop: true,
|
|
@@ -57,47 +68,233 @@ const boxSizingAffectingStyleMap: Record<string, boolean> = {
|
|
|
57
68
|
borderTopWidth: true,
|
|
58
69
|
borderRightWidth: true,
|
|
59
70
|
borderBottomWidth: true,
|
|
60
|
-
borderLeftWidth: true
|
|
71
|
+
borderLeftWidth: true,
|
|
72
|
+
border: true,
|
|
73
|
+
borderTop: true,
|
|
74
|
+
borderRight: true,
|
|
75
|
+
borderBottom: true,
|
|
76
|
+
borderLeft: true
|
|
77
|
+
}
|
|
78
|
+
const runtimeAbbreviationMap: Record<string, string[]> = {
|
|
79
|
+
margin: ['marginTop', 'marginRight', 'marginBottom', 'marginLeft'],
|
|
80
|
+
padding: ['paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft'],
|
|
81
|
+
borderRadius: ['borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius'],
|
|
82
|
+
borderWidth: ['borderTopWidth', 'borderRightWidth', 'borderBottomWidth', 'borderLeftWidth'],
|
|
83
|
+
borderColor: ['borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor'],
|
|
84
|
+
border: ['borderWidth', 'borderStyle', 'borderColor'],
|
|
85
|
+
// RN 不支持单边 border-*-style,统一展开到 borderStyle
|
|
86
|
+
// 实测 RN 上当 borderStyle 不为 solid 时单边 border-*-color 不生效,
|
|
87
|
+
// 这里把单边 color 也统一展开到 borderColor 规避(width 不能这样做,否则会覆盖其它三边)
|
|
88
|
+
borderTop: ['borderTopWidth', 'borderStyle', 'borderColor'],
|
|
89
|
+
borderRight: ['borderRightWidth', 'borderStyle', 'borderColor'],
|
|
90
|
+
borderBottom: ['borderBottomWidth', 'borderStyle', 'borderColor'],
|
|
91
|
+
borderLeft: ['borderLeftWidth', 'borderStyle', 'borderColor'],
|
|
92
|
+
flexFlow: ['flexDirection', 'flexWrap'],
|
|
93
|
+
textShadow: ['textShadowOffset.width', 'textShadowOffset.height', 'textShadowRadius', 'textShadowColor'],
|
|
94
|
+
textDecoration: ['textDecorationLine', 'textDecorationStyle', 'textDecorationColor'],
|
|
95
|
+
// gap:CSS 2 槽位(row-gap / column-gap),单值复制行列。RN 原生支持但要求 number。
|
|
96
|
+
// 单值串在 __getStyle 的 transformStyleObj / 编译期 formatValue 已被 __formatValue 换算为 number;
|
|
97
|
+
// 真正以字符串形态进 transformShorthand 的是多值串(如 '10px 20px')—— unitRegExp 不命中、原样透传。
|
|
98
|
+
// 因此进入 runtimeForceExpandCompositeMap,避免「单值透传」捷径放过这类多值串残留。
|
|
99
|
+
// 百分比(`gap: 50%` / `gap: 10px 50%`)由 transformShorthand 写回阶段就地 resolvePercent 落成 number
|
|
100
|
+
gap: ['rowGap', 'columnGap'],
|
|
101
|
+
// inset:4 槽位等价 margin 四值语法;RN 0.74+ 原生支持单值 DimensionValue,单值走 runtimeCompositeStyleMap 短路透传
|
|
102
|
+
inset: ['top', 'right', 'bottom', 'left'],
|
|
103
|
+
// outline: <outline-width> || <outline-style> || <outline-color>,顺序不敏感
|
|
104
|
+
outline: ['outlineWidth', 'outlineStyle', 'outlineColor']
|
|
105
|
+
}
|
|
106
|
+
const runtimeCompositeStyleMap: Record<string, boolean> = {
|
|
107
|
+
margin: true,
|
|
108
|
+
padding: true,
|
|
109
|
+
borderRadius: true,
|
|
110
|
+
borderWidth: true,
|
|
111
|
+
borderColor: true,
|
|
112
|
+
gap: true,
|
|
113
|
+
inset: true
|
|
114
|
+
}
|
|
115
|
+
const runtimeUnorderedAbbreviationMap: Record<string, boolean> = {
|
|
116
|
+
border: true,
|
|
117
|
+
borderTop: true,
|
|
118
|
+
borderRight: true,
|
|
119
|
+
borderBottom: true,
|
|
120
|
+
borderLeft: true,
|
|
121
|
+
flexFlow: true,
|
|
122
|
+
textShadow: true,
|
|
123
|
+
textDecoration: true,
|
|
124
|
+
// outline 与 border 简写共享同一套缺省值处理
|
|
125
|
+
outline: true
|
|
126
|
+
}
|
|
127
|
+
// 单值简写在 __getStyle 阶段经 __formatValue 把带单位长度换算为 number 后,
|
|
128
|
+
// 需要在 transformShorthand 入口转回字符串 token 交给展开逻辑的 key 集合。
|
|
129
|
+
// margin / padding / borderRadius / borderWidth / borderColor / inset 不在此列:
|
|
130
|
+
// RN 原生支持这些 key 直接吃单值 number,无需展开。
|
|
131
|
+
const runtimeNumericExpandShorthandMap: Record<string, boolean> = {
|
|
132
|
+
border: true,
|
|
133
|
+
borderTop: true,
|
|
134
|
+
borderRight: true,
|
|
135
|
+
borderBottom: true,
|
|
136
|
+
borderLeft: true,
|
|
137
|
+
outline: true,
|
|
138
|
+
gap: true
|
|
139
|
+
}
|
|
140
|
+
// CSS border-width: medium 的实测值(各主流浏览器一致取 3px)
|
|
141
|
+
// 与编译期 wx/index.js 同名常量保持一致;调整需两侧一起改
|
|
142
|
+
const BORDER_MEDIUM_WIDTH = 3
|
|
143
|
+
// 运行时简写槽位缺省值表(与编译期 ShorthandDefaultMap 镜像,仅 CSS quote 形式不同)
|
|
144
|
+
// 值即槽位缺省时追加的补齐值;borderColor / textShadowRadius 因 RN 有内置缺省值不补,不进此表
|
|
145
|
+
// none 清除语义统一保留到 useTransformStyle 末尾处理
|
|
146
|
+
const runtimeShorthandDefaultMap: Record<string, Record<string, any>> = {
|
|
147
|
+
border: { borderWidth: BORDER_MEDIUM_WIDTH, borderStyle: 'none' },
|
|
148
|
+
borderTop: { borderTopWidth: BORDER_MEDIUM_WIDTH, borderStyle: 'none' },
|
|
149
|
+
borderRight: { borderRightWidth: BORDER_MEDIUM_WIDTH, borderStyle: 'none' },
|
|
150
|
+
borderBottom: { borderBottomWidth: BORDER_MEDIUM_WIDTH, borderStyle: 'none' },
|
|
151
|
+
borderLeft: { borderLeftWidth: BORDER_MEDIUM_WIDTH, borderStyle: 'none' },
|
|
152
|
+
// outline 与 border 缺省值完全对齐:缺 width → BORDER_MEDIUM_WIDTH;
|
|
153
|
+
// 缺 style → outlineStyle: none,末尾统一转换为 outlineWidth: 0
|
|
154
|
+
outline: { outlineWidth: BORDER_MEDIUM_WIDTH, outlineStyle: 'none' },
|
|
155
|
+
textShadow: { textShadowColor: '#000' }
|
|
156
|
+
// textDecoration / flexFlow 暂不配置,与 RN 默认一致
|
|
61
157
|
}
|
|
62
158
|
|
|
159
|
+
const borderStyleMap: Record<string, boolean> = {
|
|
160
|
+
// RN 实测仅支持 solid/dotted/dashed;none 作为 CSS 合法值保留到末尾统一转换为 borderWidth: 0
|
|
161
|
+
solid: true,
|
|
162
|
+
dotted: true,
|
|
163
|
+
dashed: true,
|
|
164
|
+
none: true
|
|
165
|
+
}
|
|
166
|
+
const textDecorationLineMap: Record<string, boolean> = {
|
|
167
|
+
none: true,
|
|
168
|
+
underline: true,
|
|
169
|
+
'line-through': true
|
|
170
|
+
}
|
|
171
|
+
const textDecorationStyleMap: Record<string, boolean> = {
|
|
172
|
+
solid: true,
|
|
173
|
+
double: true,
|
|
174
|
+
dotted: true,
|
|
175
|
+
dashed: true
|
|
176
|
+
}
|
|
177
|
+
// font 简写 <font-weight> 槽位允许的关键字 / 数值(CSS 子集,与 RN 支持一致)
|
|
178
|
+
const fontWeightMap: Record<string, boolean> = {
|
|
179
|
+
bold: true,
|
|
180
|
+
normal: true,
|
|
181
|
+
100: true,
|
|
182
|
+
200: true,
|
|
183
|
+
300: true,
|
|
184
|
+
400: true,
|
|
185
|
+
500: true,
|
|
186
|
+
600: true,
|
|
187
|
+
700: true,
|
|
188
|
+
800: true,
|
|
189
|
+
900: true
|
|
190
|
+
}
|
|
191
|
+
const flexDirectionMap: Record<string, boolean> = {
|
|
192
|
+
row: true,
|
|
193
|
+
'row-reverse': true,
|
|
194
|
+
column: true,
|
|
195
|
+
'column-reverse': true
|
|
196
|
+
}
|
|
197
|
+
const flexWrapMap: Record<string, boolean> = {
|
|
198
|
+
wrap: true,
|
|
199
|
+
nowrap: true,
|
|
200
|
+
'wrap-reverse': true
|
|
201
|
+
}
|
|
63
202
|
const safeAreaInsetMap: Record<string, 'top' | 'right' | 'bottom' | 'left'> = {
|
|
64
203
|
'safe-area-inset-top': 'top',
|
|
65
204
|
'safe-area-inset-right': 'right',
|
|
66
205
|
'safe-area-inset-bottom': 'bottom',
|
|
67
206
|
'safe-area-inset-left': 'left'
|
|
68
207
|
}
|
|
208
|
+
const radiusPercentRule: Record<string, 'height' | 'width'> = {
|
|
209
|
+
borderTopLeftRadius: 'width',
|
|
210
|
+
borderBottomLeftRadius: 'width',
|
|
211
|
+
borderBottomRightRadius: 'width',
|
|
212
|
+
borderTopRightRadius: 'width',
|
|
213
|
+
borderRadius: 'width'
|
|
214
|
+
}
|
|
215
|
+
const selfPercentRule: Record<string, 'height' | 'width'> = extendObject({
|
|
216
|
+
translateX: 'width',
|
|
217
|
+
translateY: 'height'
|
|
218
|
+
}, radiusPercentRule)
|
|
219
|
+
const parentHeightPercentRule: Record<string, boolean> = {
|
|
220
|
+
height: true,
|
|
221
|
+
minHeight: true,
|
|
222
|
+
maxHeight: true,
|
|
223
|
+
top: true,
|
|
224
|
+
bottom: true,
|
|
225
|
+
// row-gap 百分比按 CSS 规范基于容器内容区高度;columnGap 不入此表,落默认 parentWidth 分支
|
|
226
|
+
rowGap: true
|
|
227
|
+
}
|
|
228
|
+
const bgRepeatMap: Record<string, boolean> = {
|
|
229
|
+
repeat: true,
|
|
230
|
+
'repeat-x': true,
|
|
231
|
+
'repeat-y': true,
|
|
232
|
+
'no-repeat': true
|
|
233
|
+
}
|
|
234
|
+
const bgPositionMap: Record<string, boolean> = {
|
|
235
|
+
left: true,
|
|
236
|
+
right: true,
|
|
237
|
+
top: true,
|
|
238
|
+
bottom: true,
|
|
239
|
+
center: true
|
|
240
|
+
}
|
|
69
241
|
|
|
70
|
-
|
|
242
|
+
// ============================================================
|
|
243
|
+
// interfaces / types
|
|
244
|
+
// ============================================================
|
|
71
245
|
|
|
72
|
-
|
|
73
|
-
|
|
246
|
+
interface PercentConfig {
|
|
247
|
+
fontSize?: number | string
|
|
248
|
+
width?: number
|
|
249
|
+
height?: number
|
|
250
|
+
parentWidth?: number
|
|
251
|
+
parentHeight?: number
|
|
74
252
|
}
|
|
75
253
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
254
|
+
interface TransformStyleConfig {
|
|
255
|
+
enableVar?: boolean
|
|
256
|
+
parentWidth?: number
|
|
257
|
+
parentHeight?: number
|
|
258
|
+
transformRadiusPercent?: boolean
|
|
259
|
+
defaultStyle?: Record<string, any>
|
|
81
260
|
}
|
|
82
261
|
|
|
83
|
-
export
|
|
84
|
-
|
|
262
|
+
export interface VisitorArg {
|
|
263
|
+
target: Record<string, any>
|
|
264
|
+
key: string
|
|
265
|
+
value: any
|
|
266
|
+
keyPath: Array<string>
|
|
85
267
|
}
|
|
86
268
|
|
|
87
|
-
|
|
88
|
-
|
|
269
|
+
interface LayoutConfig {
|
|
270
|
+
props: Record<string, any>
|
|
271
|
+
hasSelfPercent: boolean
|
|
272
|
+
setWidth?: Dispatch<SetStateAction<number>>
|
|
273
|
+
setHeight?: Dispatch<SetStateAction<number>>
|
|
274
|
+
onLayout?: (event: LayoutChangeEvent) => void
|
|
275
|
+
nodeRef: React.RefObject<any>
|
|
89
276
|
}
|
|
90
277
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
278
|
+
export interface WrapChildrenConfig {
|
|
279
|
+
hasVarDec: boolean
|
|
280
|
+
varContext?: Record<string, any>
|
|
281
|
+
textPassThrough?: TextPassThroughContextValue | null
|
|
94
282
|
}
|
|
95
283
|
|
|
96
|
-
export
|
|
97
|
-
|
|
98
|
-
|
|
284
|
+
export interface TextPassThroughValueOptions {
|
|
285
|
+
enableTextPassThrough?: boolean
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export interface GestureHandler {
|
|
289
|
+
nodeRefs?: Array<{ getNodeInstance: () => { nodeRef: unknown } }>
|
|
290
|
+
current?: unknown
|
|
291
|
+
handlerTag?: number
|
|
99
292
|
}
|
|
100
293
|
|
|
294
|
+
// ============================================================
|
|
295
|
+
// generic utility functions
|
|
296
|
+
// ============================================================
|
|
297
|
+
|
|
101
298
|
export function omit<T, K extends string> (obj: T, fields: K[]): Omit<T, K> {
|
|
102
299
|
const shallowCopy: any = extendObject({}, obj)
|
|
103
300
|
for (let i = 0; i < fields.length; i += 1) {
|
|
@@ -107,31 +304,9 @@ export function omit<T, K extends string> (obj: T, fields: K[]): Omit<T, K> {
|
|
|
107
304
|
return shallowCopy
|
|
108
305
|
}
|
|
109
306
|
|
|
110
|
-
/**
|
|
111
|
-
* 用法等同于 useEffect,但是会忽略首次执行,只在依赖更新时执行
|
|
112
|
-
*/
|
|
113
|
-
export const useUpdateEffect = (effect: any, deps: any) => {
|
|
114
|
-
const isMounted = useRef(false)
|
|
115
|
-
|
|
116
|
-
// for react-refresh
|
|
117
|
-
useEffect(() => {
|
|
118
|
-
return () => {
|
|
119
|
-
isMounted.current = false
|
|
120
|
-
}
|
|
121
|
-
}, [])
|
|
122
|
-
|
|
123
|
-
useEffect(() => {
|
|
124
|
-
if (!isMounted.current) {
|
|
125
|
-
isMounted.current = true
|
|
126
|
-
} else {
|
|
127
|
-
return effect()
|
|
128
|
-
}
|
|
129
|
-
}, deps)
|
|
130
|
-
}
|
|
131
|
-
|
|
132
307
|
export const parseUrl = (cssUrl = '') => {
|
|
133
308
|
if (!cssUrl) return
|
|
134
|
-
const match = cssUrl.match(
|
|
309
|
+
const match = cssUrl.match(urlRegExp)
|
|
135
310
|
return match?.[1]
|
|
136
311
|
}
|
|
137
312
|
|
|
@@ -143,6 +318,10 @@ export const getRestProps = (transferProps: any = {}, originProps: any = {}, del
|
|
|
143
318
|
)
|
|
144
319
|
}
|
|
145
320
|
|
|
321
|
+
export function getDefaultAllowFontScaling (): boolean {
|
|
322
|
+
return global.__mpx?.config?.rnConfig?.allowFontScaling ?? false
|
|
323
|
+
}
|
|
324
|
+
|
|
146
325
|
export function isText (ele: ReactNode): ele is ReactElement {
|
|
147
326
|
if (isValidElement(ele)) {
|
|
148
327
|
const displayName = (ele.type as ExtendedFunctionComponent)?.displayName
|
|
@@ -158,106 +337,168 @@ export function isStringChildren (children: ReactNode) {
|
|
|
158
337
|
return children.every((child) => typeof child === 'string')
|
|
159
338
|
}
|
|
160
339
|
|
|
161
|
-
|
|
340
|
+
// 多value解析
|
|
341
|
+
export function parseValues (str: string, char = ' ') {
|
|
342
|
+
let stack = 0
|
|
343
|
+
let temp = ''
|
|
344
|
+
const result = []
|
|
345
|
+
for (let i = 0; i < str.length; i++) {
|
|
346
|
+
if (str[i] === '(') {
|
|
347
|
+
stack++
|
|
348
|
+
} else if (str[i] === ')') {
|
|
349
|
+
stack--
|
|
350
|
+
}
|
|
351
|
+
// 非括号内 或者 非分隔字符且非空
|
|
352
|
+
if (stack !== 0 || str[i] !== char) {
|
|
353
|
+
temp += str[i]
|
|
354
|
+
}
|
|
355
|
+
if ((stack === 0 && str[i] === char) || i === str.length - 1) {
|
|
356
|
+
result.push(temp.trim())
|
|
357
|
+
temp = ''
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return result
|
|
361
|
+
}
|
|
162
362
|
|
|
163
|
-
export
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
363
|
+
export const debounce = <T extends AnyFunc> (
|
|
364
|
+
func: T,
|
|
365
|
+
delay: number
|
|
366
|
+
): ((...args: Parameters<T>) => void) & { clear: () => void } => {
|
|
367
|
+
let timer: any
|
|
368
|
+
const wrapper = (...args: ReadonlyArray<any>) => {
|
|
369
|
+
timer && clearTimeout(timer)
|
|
370
|
+
timer = setTimeout(() => {
|
|
371
|
+
func(...args)
|
|
372
|
+
}, delay)
|
|
373
|
+
}
|
|
374
|
+
wrapper.clear = () => {
|
|
375
|
+
timer && clearTimeout(timer)
|
|
376
|
+
timer = null
|
|
377
|
+
}
|
|
378
|
+
return wrapper
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// ============================================================
|
|
382
|
+
// style classification & splitting
|
|
383
|
+
// ============================================================
|
|
384
|
+
|
|
385
|
+
function isTextStyle (key: string) {
|
|
386
|
+
return hasOwn(textStyleMap, key) || key.startsWith('font') || key.startsWith('text')
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function isColorValue (token: string): boolean {
|
|
390
|
+
if (token.startsWith('#') || token.startsWith('rgb(') || token.startsWith('rgba(') || token.startsWith('hsl(') || token.startsWith('hsla(')) return true
|
|
391
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
392
|
+
return hasOwn(require('./namedColorSet').default, token.toLowerCase())
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function isLengthValue (token: string): boolean {
|
|
396
|
+
return lengthValueRegExp.test(token)
|
|
174
397
|
}
|
|
175
398
|
|
|
176
|
-
export function
|
|
399
|
+
export function isBoxSizingAffectingStyle (key: string) {
|
|
400
|
+
return hasOwn(boxSizingAffectingStyleMap, key)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// 仅在调用侧已确认存在 padding / border 等 box-sizing 影响项时调用:
|
|
404
|
+
// 给样式补默认 boxSizing(用户未显式设置时)。是否需要进入由调用侧决定,
|
|
405
|
+
// useTransformStyle / mpx-simple-view / mpx-simple-text 都按 hasBoxSizingAffectingStyle 短路。
|
|
406
|
+
export function transformBoxSizing (style: Record<string, any> = {}) {
|
|
407
|
+
if (style.boxSizing === undefined) {
|
|
408
|
+
style.boxSizing = global.__mpx?.config?.rnConfig?.defaultBoxSizing ?? defaultBoxSizingStyle.boxSizing
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export function splitStyle<T extends Record<string, any>> (styleObj: T = {} as T, sideEffect?: (key: string, val: T[keyof T]) => void): {
|
|
177
413
|
textStyle?: Partial<T>
|
|
178
414
|
backgroundStyle?: Partial<T>
|
|
179
415
|
innerStyle?: Partial<T>
|
|
180
416
|
} {
|
|
181
|
-
|
|
417
|
+
const keys = Object.keys(styleObj)
|
|
418
|
+
let textStyle: Partial<T> | undefined
|
|
419
|
+
let backgroundStyle: Partial<T> | undefined
|
|
420
|
+
let innerStyle: Partial<T> | undefined
|
|
421
|
+
let firstSpecialIdx = -1
|
|
422
|
+
|
|
423
|
+
for (let i = 0; i < keys.length; i++) {
|
|
424
|
+
const key = keys[i]
|
|
425
|
+
const val = styleObj[key]
|
|
182
426
|
sideEffect && sideEffect(key, val)
|
|
427
|
+
|
|
183
428
|
if (isTextStyle(key)) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
} else {
|
|
188
|
-
|
|
429
|
+
if (firstSpecialIdx < 0) firstSpecialIdx = i
|
|
430
|
+
textStyle = textStyle || {}
|
|
431
|
+
textStyle[key as keyof T] = val
|
|
432
|
+
} else if (hasOwn(backgroundStyleMap, key)) {
|
|
433
|
+
if (firstSpecialIdx < 0) firstSpecialIdx = i
|
|
434
|
+
backgroundStyle = backgroundStyle || {}
|
|
435
|
+
backgroundStyle[key as keyof T] = val
|
|
436
|
+
} else if (firstSpecialIdx >= 0) {
|
|
437
|
+
innerStyle = innerStyle || {}
|
|
438
|
+
innerStyle[key as keyof T] = val
|
|
189
439
|
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
const radiusPercentRule: Record<string, 'height' | 'width'> = {
|
|
193
|
-
borderTopLeftRadius: 'width',
|
|
194
|
-
borderBottomLeftRadius: 'width',
|
|
195
|
-
borderBottomRightRadius: 'width',
|
|
196
|
-
borderTopRightRadius: 'width',
|
|
197
|
-
borderRadius: 'width'
|
|
198
|
-
}
|
|
199
|
-
const selfPercentRule: Record<string, 'height' | 'width'> = extendObject({
|
|
200
|
-
translateX: 'width',
|
|
201
|
-
translateY: 'height'
|
|
202
|
-
}, radiusPercentRule)
|
|
203
|
-
|
|
204
|
-
const parentHeightPercentRule: Record<string, boolean> = {
|
|
205
|
-
height: true,
|
|
206
|
-
minHeight: true,
|
|
207
|
-
maxHeight: true,
|
|
208
|
-
top: true,
|
|
209
|
-
bottom: true
|
|
210
|
-
}
|
|
440
|
+
}
|
|
211
441
|
|
|
212
|
-
|
|
213
|
-
fontSize?: number | string
|
|
214
|
-
width?: number
|
|
215
|
-
height?: number
|
|
216
|
-
parentFontSize?: number
|
|
217
|
-
parentWidth?: number
|
|
218
|
-
parentHeight?: number
|
|
219
|
-
}
|
|
442
|
+
if (firstSpecialIdx < 0) return { innerStyle: styleObj }
|
|
220
443
|
|
|
221
|
-
|
|
222
|
-
|
|
444
|
+
if (firstSpecialIdx > 0) {
|
|
445
|
+
innerStyle = innerStyle || {}
|
|
446
|
+
for (let i = 0; i < firstSpecialIdx; i++) {
|
|
447
|
+
const key = keys[i]
|
|
448
|
+
innerStyle[key as keyof T] = styleObj[key]
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return { textStyle, backgroundStyle, innerStyle }
|
|
223
452
|
}
|
|
224
453
|
|
|
225
|
-
function
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
454
|
+
export function splitProps<T extends Record<string, any>> (props: T = {} as T): {
|
|
455
|
+
textProps?: Partial<T>
|
|
456
|
+
innerProps?: Partial<T>
|
|
457
|
+
} {
|
|
458
|
+
const keys = Object.keys(props)
|
|
459
|
+
let textProps: Partial<T> | undefined
|
|
460
|
+
let innerProps: Partial<T> | undefined
|
|
461
|
+
let firstTextIdx = -1
|
|
462
|
+
|
|
463
|
+
for (let i = 0; i < keys.length; i++) {
|
|
464
|
+
const key = keys[i]
|
|
465
|
+
if (hasOwn(textPropsMap, key)) {
|
|
466
|
+
if (firstTextIdx < 0) firstTextIdx = i
|
|
467
|
+
textProps = textProps || {}
|
|
468
|
+
textProps[key as keyof T] = props[key]
|
|
469
|
+
} else if (firstTextIdx >= 0) {
|
|
470
|
+
innerProps = innerProps || {}
|
|
471
|
+
innerProps[key as keyof T] = props[key]
|
|
472
|
+
}
|
|
244
473
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
474
|
+
|
|
475
|
+
if (firstTextIdx < 0) return { innerProps: props }
|
|
476
|
+
|
|
477
|
+
if (firstTextIdx > 0) {
|
|
478
|
+
innerProps = innerProps || {}
|
|
479
|
+
for (let i = 0; i < firstTextIdx; i++) {
|
|
480
|
+
const key = keys[i]
|
|
481
|
+
innerProps[key as keyof T] = props[key]
|
|
482
|
+
}
|
|
250
483
|
}
|
|
484
|
+
return { textProps, innerProps }
|
|
251
485
|
}
|
|
252
486
|
|
|
253
|
-
function
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
|
|
487
|
+
export function pickStyle (styleObj: Record<string, any> = {}, pickedKeys: Array<string>, callback?: (key: string, val: number | string) => number | string) {
|
|
488
|
+
return pickedKeys.reduce<Record<string, any>>((acc, key) => {
|
|
489
|
+
if (key in styleObj) {
|
|
490
|
+
acc[key] = callback ? callback(key, styleObj[key]) : styleObj[key]
|
|
491
|
+
}
|
|
492
|
+
return acc
|
|
493
|
+
}, {})
|
|
259
494
|
}
|
|
260
495
|
|
|
496
|
+
// ============================================================
|
|
497
|
+
// style transform pipeline
|
|
498
|
+
// ============================================================
|
|
499
|
+
|
|
500
|
+
// --- var ---
|
|
501
|
+
|
|
261
502
|
function resolveVar (input: string, varContext: Record<string, any>) {
|
|
262
503
|
const parsed = parseFunc(input, 'var')
|
|
263
504
|
const replaced = new ReplaceSource(input)
|
|
@@ -281,40 +522,84 @@ function resolveVar (input: string, varContext: Record<string, any>) {
|
|
|
281
522
|
if (varValue === undefined) return
|
|
282
523
|
replaced.replace(start, end - 1, varValue)
|
|
283
524
|
}
|
|
284
|
-
|
|
285
|
-
return global.__formatValue(replaced.source())
|
|
525
|
+
|
|
526
|
+
return global.__formatValue(replaced.source())
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function transformVar (styleObj: Record<string, any>, varKeyPaths: Array<Array<string>>, varContext: Record<string, any>, visitOther: (arg: VisitorArg) => void) {
|
|
530
|
+
varKeyPaths.forEach((varKeyPath) => {
|
|
531
|
+
setStyle(styleObj, varKeyPath, ({ target, key, value }) => {
|
|
532
|
+
const resolved = resolveVar(value, varContext)
|
|
533
|
+
if (resolved === undefined) {
|
|
534
|
+
delete target[key]
|
|
535
|
+
error(`Can not resolve css var at ${varKeyPath.join('.')}:${value}.`)
|
|
536
|
+
return
|
|
537
|
+
}
|
|
538
|
+
target[key] = resolved
|
|
539
|
+
visitOther({ target, key, value: target[key], keyPath: varKeyPath })
|
|
540
|
+
})
|
|
541
|
+
})
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// --- env ---
|
|
545
|
+
|
|
546
|
+
function transformEnv (styleObj: Record<string, any>, envKeyPaths: Array<Array<string>>, navigation: Record<string, any> | undefined) {
|
|
547
|
+
const insets = extendObject({}, initialWindowMetrics?.insets, navigation?.insets)
|
|
548
|
+
envKeyPaths.forEach((envKeyPath) => {
|
|
549
|
+
setStyle(styleObj, envKeyPath, ({ target, key, value }) => {
|
|
550
|
+
const parsed = parseFunc(value, 'env')
|
|
551
|
+
const replaced = new ReplaceSource(value)
|
|
552
|
+
parsed.forEach(({ start, end, args }) => {
|
|
553
|
+
const name = args[0]
|
|
554
|
+
const fallback = args[1] || ''
|
|
555
|
+
const inset = insets[safeAreaInsetMap[name]]
|
|
556
|
+
const next = '' + (inset ?? global.__formatValue(fallback))
|
|
557
|
+
replaced.replace(start, end - 1, next)
|
|
558
|
+
})
|
|
559
|
+
target[key] = global.__formatValue(replaced.source())
|
|
560
|
+
})
|
|
561
|
+
})
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// --- percent ---
|
|
565
|
+
|
|
566
|
+
function resolvePercent (value: string | number | undefined, key: string, percentConfig: PercentConfig): string | number | undefined {
|
|
567
|
+
if (!(typeof value === 'string' && percentRegExp.test(value))) return value
|
|
568
|
+
let base
|
|
569
|
+
let reason
|
|
570
|
+
if (key === 'fontSize') {
|
|
571
|
+
base = DEFAULT_FONT_SIZE
|
|
572
|
+
reason = 'default-font-size'
|
|
573
|
+
} else if (key === 'lineHeight') {
|
|
574
|
+
base = resolvePercent(percentConfig.fontSize, 'fontSize', percentConfig)
|
|
575
|
+
reason = 'font-size'
|
|
576
|
+
} else if (selfPercentRule[key]) {
|
|
577
|
+
base = percentConfig[selfPercentRule[key]]
|
|
578
|
+
reason = selfPercentRule[key]
|
|
579
|
+
} else if (parentHeightPercentRule[key]) {
|
|
580
|
+
base = percentConfig.parentHeight
|
|
581
|
+
reason = 'parent-height'
|
|
582
|
+
} else {
|
|
583
|
+
base = percentConfig.parentWidth
|
|
584
|
+
reason = 'parent-width'
|
|
585
|
+
}
|
|
586
|
+
if (typeof base !== 'number') {
|
|
587
|
+
error(`[${key}] can not contain % unit unless you set [${reason}] with a number for the percent calculation.`)
|
|
588
|
+
return value
|
|
589
|
+
} else {
|
|
590
|
+
return parseFloat(value) / 100 * base
|
|
591
|
+
}
|
|
286
592
|
}
|
|
287
593
|
|
|
288
|
-
function
|
|
289
|
-
|
|
290
|
-
setStyle(styleObj,
|
|
291
|
-
|
|
292
|
-
if (resolved === undefined) {
|
|
293
|
-
delete target[key]
|
|
294
|
-
error(`Can not resolve css var at ${varKeyPath.join('.')}:${value}.`)
|
|
295
|
-
return
|
|
296
|
-
}
|
|
297
|
-
target[key] = resolved
|
|
298
|
-
visitOther({ target, key, value: target[key], keyPath: varKeyPath })
|
|
594
|
+
function transformPercent (styleObj: Record<string, any>, percentKeyPaths: Array<Array<string>>, percentConfig: PercentConfig) {
|
|
595
|
+
percentKeyPaths.forEach((percentKeyPath) => {
|
|
596
|
+
setStyle(styleObj, percentKeyPath, ({ target, key, value }) => {
|
|
597
|
+
target[key] = resolvePercent(value, key, percentConfig)
|
|
299
598
|
})
|
|
300
599
|
})
|
|
301
600
|
}
|
|
302
601
|
|
|
303
|
-
|
|
304
|
-
envKeyPaths.forEach((envKeyPath) => {
|
|
305
|
-
setStyle(styleObj, envKeyPath, ({ target, key, value }) => {
|
|
306
|
-
const parsed = parseFunc(value, 'env')
|
|
307
|
-
const replaced = new ReplaceSource(value)
|
|
308
|
-
parsed.forEach(({ start, end, args }) => {
|
|
309
|
-
const name = args[0]
|
|
310
|
-
const fallback = args[1] || ''
|
|
311
|
-
const value = '' + (getSafeAreaInset(name, navigation) ?? global.__formatValue(fallback))
|
|
312
|
-
replaced.replace(start, end - 1, value)
|
|
313
|
-
})
|
|
314
|
-
target[key] = global.__formatValue(replaced.source())
|
|
315
|
-
})
|
|
316
|
-
})
|
|
317
|
-
}
|
|
602
|
+
// --- calc ---
|
|
318
603
|
|
|
319
604
|
function transformCalc (styleObj: Record<string, any>, calcKeyPaths: Array<Array<string>>, formatter: (value: string, key: string) => number) {
|
|
320
605
|
calcKeyPaths.forEach((calcKeyPath) => {
|
|
@@ -337,6 +622,8 @@ function transformCalc (styleObj: Record<string, any>, calcKeyPaths: Array<Array
|
|
|
337
622
|
})
|
|
338
623
|
}
|
|
339
624
|
|
|
625
|
+
// --- misc ---
|
|
626
|
+
|
|
340
627
|
function transformStringify (styleObj: Record<string, any>) {
|
|
341
628
|
if (isNumber(styleObj.fontWeight)) {
|
|
342
629
|
styleObj.fontWeight = '' + styleObj.fontWeight
|
|
@@ -347,34 +634,8 @@ function transformStringify (styleObj: Record<string, any>) {
|
|
|
347
634
|
}
|
|
348
635
|
}
|
|
349
636
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
styleObj.position = 'absolute'
|
|
353
|
-
meta.hasPositionFixed = true
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
// 多value解析
|
|
357
|
-
export function parseValues (str: string, char = ' ') {
|
|
358
|
-
let stack = 0
|
|
359
|
-
let temp = ''
|
|
360
|
-
const result = []
|
|
361
|
-
for (let i = 0; i < str.length; i++) {
|
|
362
|
-
if (str[i] === '(') {
|
|
363
|
-
stack++
|
|
364
|
-
} else if (str[i] === ')') {
|
|
365
|
-
stack--
|
|
366
|
-
}
|
|
367
|
-
// 非括号内 或者 非分隔字符且非空
|
|
368
|
-
if (stack !== 0 || str[i] !== char) {
|
|
369
|
-
temp += str[i]
|
|
370
|
-
}
|
|
371
|
-
if ((stack === 0 && str[i] === char) || i === str.length - 1) {
|
|
372
|
-
result.push(temp.trim())
|
|
373
|
-
temp = ''
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
return result
|
|
377
|
-
}
|
|
637
|
+
// --- transform / shadow ---
|
|
638
|
+
|
|
378
639
|
// parse string transform, eg: transform: 'rotateX(45deg) rotateZ(0.785398rad)'
|
|
379
640
|
function parseTransform (transformStr: string) {
|
|
380
641
|
const values = parseValues(transformStr)
|
|
@@ -382,55 +643,108 @@ function parseTransform (transformStr: string) {
|
|
|
382
643
|
// Todo 1 transform 排序不一致时,transform动画会闪烁,故这里同样的排序输出 transform
|
|
383
644
|
// values.sort()
|
|
384
645
|
const transform: { [propName: string]: string | number | number[] }[] = []
|
|
646
|
+
// 与编译期 wx/index.js 同 transform formatter 口径对齐:
|
|
647
|
+
// 「整条 transform 声明」绝不会因单个子项失败而丢弃,被丢的只有那一个子项,其余 transform 项仍输出。
|
|
648
|
+
// 因此本 forEach 内所有「丢这一项」的提示一律用 warn(编译期同名分支注释:「仅丢这一 transform 项,其它项仍输出,按规范使用 warn」)。
|
|
385
649
|
values.forEach(item => {
|
|
386
|
-
const match = item.match(
|
|
387
|
-
if (match
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
650
|
+
const match = item.match(transformFnRegExp)
|
|
651
|
+
if (!match || match.length < 3) {
|
|
652
|
+
warn(`Transform value [${item}] is not a valid fn(value) form, dropped.`)
|
|
653
|
+
return
|
|
654
|
+
}
|
|
655
|
+
let key = match[1]
|
|
656
|
+
const val = match[2]
|
|
657
|
+
switch (key) {
|
|
658
|
+
case 'rotateX':
|
|
659
|
+
case 'rotateY':
|
|
660
|
+
case 'rotateZ':
|
|
661
|
+
case 'rotate':
|
|
662
|
+
case 'skewX':
|
|
663
|
+
case 'skewY':
|
|
664
|
+
key = key === 'rotate' ? 'rotateZ' : key
|
|
665
|
+
transform.push({ [key]: val })
|
|
666
|
+
break
|
|
667
|
+
case 'translateX':
|
|
668
|
+
case 'translateY':
|
|
669
|
+
case 'scaleX':
|
|
670
|
+
case 'scaleY':
|
|
671
|
+
case 'perspective':
|
|
672
|
+
transform.push({ [key]: global.__formatValue(val) })
|
|
673
|
+
break
|
|
674
|
+
case 'matrix': {
|
|
675
|
+
// parseValues 内部已 trim
|
|
676
|
+
const matrixValues = parseValues(val, ',').map(v => +v)
|
|
677
|
+
if (matrixValues.length === 6) {
|
|
678
|
+
const [a, b, c, d, tx, ty] = matrixValues
|
|
679
|
+
transform.push({ matrix: [a, b, 0, 0, c, d, 0, 0, 0, 0, 1, 0, tx, ty, 0, 1] })
|
|
680
|
+
} else {
|
|
681
|
+
warn(`Transform matrix only supports 6 values in React Native, got ${matrixValues.length}`)
|
|
682
|
+
}
|
|
683
|
+
break
|
|
684
|
+
}
|
|
685
|
+
case 'matrix3d': {
|
|
686
|
+
// parseValues 内部已 trim
|
|
687
|
+
const matrixValues = parseValues(val, ',').map(v => +v)
|
|
688
|
+
if (matrixValues.length === 16) {
|
|
689
|
+
transform.push({ matrix: matrixValues })
|
|
690
|
+
} else {
|
|
691
|
+
warn(`Transform matrix only supports 16 values in React Native, got ${matrixValues.length}`)
|
|
692
|
+
}
|
|
693
|
+
break
|
|
694
|
+
}
|
|
695
|
+
case 'translate':
|
|
696
|
+
case 'scale':
|
|
697
|
+
case 'skew':
|
|
698
|
+
case 'translate3d': // x y 支持 z不支持
|
|
699
|
+
case 'scale3d': // x y 支持 z不支持
|
|
700
|
+
{
|
|
701
|
+
// 2 个以上的值处理
|
|
702
|
+
key = key.replace('3d', '')
|
|
703
|
+
const vals = parseValues(val, ',').splice(0, 3)
|
|
704
|
+
// scale(.5) === scaleX(.5) scaleY(.5)
|
|
705
|
+
if (vals.length === 1 && key === 'scale') {
|
|
706
|
+
vals.push(vals[0])
|
|
707
|
+
}
|
|
708
|
+
const xyz = ['X', 'Y', 'Z']
|
|
709
|
+
vals.forEach((v, index) => {
|
|
710
|
+
if (key !== 'rotate' && index > 1) {
|
|
711
|
+
warn(`Transform [${key}Z] is not supported in React Native, dropped.`)
|
|
712
|
+
return
|
|
422
713
|
}
|
|
423
|
-
|
|
424
|
-
transform.push(
|
|
425
|
-
|
|
426
|
-
|
|
714
|
+
// parseValues 内部已 trim
|
|
715
|
+
transform.push({ [`${key}${xyz[index] || ''}`]: global.__formatValue(v) })
|
|
716
|
+
})
|
|
717
|
+
break
|
|
718
|
+
}
|
|
719
|
+
case 'rotate3d': {
|
|
720
|
+
const parts = parseValues(val, ',')
|
|
721
|
+
if (parts.length !== 4) {
|
|
722
|
+
warn(`Transform rotate3d only supports 4 values, got ${parts.length}`)
|
|
427
723
|
break
|
|
428
724
|
}
|
|
725
|
+
// parseValues 内部已 trim
|
|
726
|
+
const x = +parts[0]
|
|
727
|
+
const y = +parts[1]
|
|
728
|
+
const z = +parts[2]
|
|
729
|
+
const angle = parts[3]
|
|
730
|
+
if (x && !y && !z) transform.push({ rotateX: angle })
|
|
731
|
+
else if (!x && y && !z) transform.push({ rotateY: angle })
|
|
732
|
+
else if (!x && !y && z) transform.push({ rotateZ: angle })
|
|
733
|
+
else warn(`Transform rotate3d(${val}) only supports single-axis vector (1,0,0) / (0,1,0) / (0,0,1) in React Native, dropped.`)
|
|
734
|
+
break
|
|
429
735
|
}
|
|
736
|
+
case 'translateZ':
|
|
737
|
+
case 'scaleZ':
|
|
738
|
+
// RN 不支持 Z 轴 translate/scale,丢该子项;编译期同分支用 warn(unsupportedPropError isError=false)
|
|
739
|
+
warn(`Transform [${key}] is not supported in React Native, dropped.`)
|
|
740
|
+
break
|
|
741
|
+
default:
|
|
742
|
+
warn(`Transform fn [${key}] is not supported in React Native, dropped.`)
|
|
430
743
|
}
|
|
431
744
|
})
|
|
432
745
|
return transform
|
|
433
746
|
}
|
|
747
|
+
|
|
434
748
|
// format style transform
|
|
435
749
|
function transformTransform (style: Record<string, any>) {
|
|
436
750
|
if (!style.transform || Array.isArray(style.transform)) return
|
|
@@ -440,20 +754,471 @@ function transformTransform (style: Record<string, any>) {
|
|
|
440
754
|
function transformBoxShadow (styleObj: Record<string, any>) {
|
|
441
755
|
if (!styleObj.boxShadow) return
|
|
442
756
|
styleObj.boxShadow = parseValues(styleObj.boxShadow).reduce((res, i, idx) => {
|
|
443
|
-
|
|
757
|
+
let formatted: string | number
|
|
758
|
+
// 需要保留 px 关键字,这里仅处理 rpx 转 px
|
|
759
|
+
if (rpxSuffixRegExp.test(i)) {
|
|
760
|
+
formatted = global.__formatValue(i) + 'px'
|
|
761
|
+
} else {
|
|
762
|
+
formatted = i
|
|
763
|
+
}
|
|
764
|
+
return `${res}${idx === 0 ? '' : ' '}${formatted}`
|
|
444
765
|
}, '')
|
|
445
766
|
}
|
|
446
767
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
768
|
+
// --- shorthand ---
|
|
769
|
+
|
|
770
|
+
function expandCompositeValues (values: string[]): string[] {
|
|
771
|
+
const v = values.slice(0, 4)
|
|
772
|
+
switch (v.length) {
|
|
773
|
+
case 1:
|
|
774
|
+
return [v[0], v[0], v[0], v[0]]
|
|
775
|
+
case 2:
|
|
776
|
+
return [v[0], v[1], v[0], v[1]]
|
|
777
|
+
case 3:
|
|
778
|
+
return [v[0], v[1], v[2], v[1]]
|
|
779
|
+
default:
|
|
780
|
+
return v
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function expandAbbreviation (values: string[], props: string[]): Array<[string, any]> {
|
|
785
|
+
const result: Array<[string, any]> = []
|
|
786
|
+
for (let i = 0; i < props.length && i < values.length; i++) {
|
|
787
|
+
pushExpandedPair(result, props[i], global.__formatValue(values[i]))
|
|
788
|
+
}
|
|
789
|
+
return result
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function pushExpandedPair (result: Array<[string, any]>, prop: string, value: any) {
|
|
793
|
+
if (prop.includes('.')) {
|
|
794
|
+
const [main, sub] = prop.split('.')
|
|
795
|
+
let entry = result.find(item => item[0] === main)
|
|
796
|
+
if (!entry) {
|
|
797
|
+
entry = [main, {}]
|
|
798
|
+
result.push(entry)
|
|
799
|
+
}
|
|
800
|
+
entry[1][sub] = value
|
|
801
|
+
} else {
|
|
802
|
+
result.push([prop, value])
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
function getUnorderedShorthandProp (key: string, token: string, used: Record<string, boolean>): string | undefined {
|
|
807
|
+
const props = runtimeAbbreviationMap[key]
|
|
808
|
+
for (const prop of props) {
|
|
809
|
+
if (used[prop]) continue
|
|
810
|
+
if (matchRuntimeShorthandProp(prop, token)) return prop
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
function matchRuntimeShorthandProp (prop: string, token: string): boolean {
|
|
815
|
+
if (prop === 'textShadowOffset.width' || prop === 'textShadowOffset.height' || prop === 'textShadowRadius') return isLengthValue(token)
|
|
816
|
+
if (prop === 'textShadowColor') return isColorValue(token)
|
|
817
|
+
if (prop.endsWith('Width')) return isLengthValue(token)
|
|
818
|
+
if (prop === 'textDecorationStyle') return hasOwn(textDecorationStyleMap, token)
|
|
819
|
+
if (prop.endsWith('Style')) return hasOwn(borderStyleMap, token)
|
|
820
|
+
if (prop.endsWith('Color')) return isColorValue(token)
|
|
821
|
+
if (prop === 'flexDirection') return hasOwn(flexDirectionMap, token)
|
|
822
|
+
if (prop === 'flexWrap') return hasOwn(flexWrapMap, token)
|
|
823
|
+
return false
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// 通用补齐:扫描完所有 token 后,将 runtimeShorthandDefaultMap 中未被占用(不在 used)的槽位追加到 pairs
|
|
827
|
+
// used 即主循环的占用记录,key 是完整目标 prop 名(含 textShadowOffset.width 这类 dot 路径)
|
|
828
|
+
function applyRuntimeShorthandDefaults (key: string, pairs: Array<[string, any]>, used: Record<string, boolean>): Array<[string, any]> {
|
|
829
|
+
const defaults = runtimeShorthandDefaultMap[key]
|
|
830
|
+
if (!defaults) return pairs
|
|
831
|
+
for (const target in defaults) {
|
|
832
|
+
if (!used[target]) {
|
|
833
|
+
pushExpandedPair(pairs, target, defaults[target])
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return pairs
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
function expandUnorderedAbbreviation (key: string, values: string[]): Array<[string, any]> {
|
|
840
|
+
const result: Array<[string, any]> = []
|
|
841
|
+
const used: Record<string, boolean> = {}
|
|
842
|
+
let hasTextDecorationNone = false
|
|
843
|
+
let hasUnderline = false
|
|
844
|
+
let hasLineThrough = false
|
|
845
|
+
for (const value of values) {
|
|
846
|
+
if (key === 'textDecoration' && hasOwn(textDecorationLineMap, value)) {
|
|
847
|
+
switch (value) {
|
|
848
|
+
case 'underline':
|
|
849
|
+
hasUnderline = true
|
|
850
|
+
continue
|
|
851
|
+
case 'line-through':
|
|
852
|
+
hasLineThrough = true
|
|
853
|
+
continue
|
|
854
|
+
case 'none':
|
|
855
|
+
hasTextDecorationNone = true
|
|
856
|
+
continue
|
|
857
|
+
// textDecorationLineMap 命中但分支未处理的 line token:落到通用 getUnorderedShorthandProp 流程
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
const prop = getUnorderedShorthandProp(key, value, used)
|
|
861
|
+
if (!prop) {
|
|
862
|
+
// 该 token 与 key 下任何空闲槽位都不匹配(未知类型 / 槽位已占满),静默丢弃可能让用户难以察觉
|
|
863
|
+
warn(`Token [${value}] in [${key}: ${values.join(' ')}] is not a valid value or has no available slot, dropped.`)
|
|
864
|
+
continue
|
|
865
|
+
}
|
|
866
|
+
used[prop] = true
|
|
867
|
+
pushExpandedPair(result, prop, global.__formatValue(value))
|
|
868
|
+
}
|
|
869
|
+
if (hasUnderline || hasLineThrough) {
|
|
870
|
+
result.push(['textDecorationLine', hasUnderline && hasLineThrough ? 'underline line-through' : hasUnderline ? 'underline' : 'line-through'])
|
|
871
|
+
} else if (hasTextDecorationNone) {
|
|
872
|
+
result.push(['textDecorationLine', 'none'])
|
|
873
|
+
}
|
|
874
|
+
// text-shadow 至少需要 offset-x / offset-y;缺省 height 时按 CSS 默认补 0 并发出 warn
|
|
875
|
+
if (key === 'textShadow') {
|
|
876
|
+
const offsetEntry = result.find(item => item[0] === 'textShadowOffset')?.[1]
|
|
877
|
+
if (offsetEntry && offsetEntry.width !== undefined && offsetEntry.height === undefined) {
|
|
878
|
+
warn(`Value of [textShadow:${values.join(' ')}] is missing offset-y, fallback to 0, please check again!`)
|
|
879
|
+
offsetEntry.height = 0
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
return applyRuntimeShorthandDefaults(key, result, used)
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
function expandFlex (value: string): Array<[string, any]> | null {
|
|
886
|
+
const values = parseValues(value)
|
|
887
|
+
if (values.length === 0) return null
|
|
888
|
+
if (values.length === 1) {
|
|
889
|
+
if (values[0] === 'none') {
|
|
890
|
+
return [['flexGrow', 0], ['flexShrink', 0]]
|
|
891
|
+
}
|
|
892
|
+
if (values[0] === 'initial') {
|
|
893
|
+
return [['flexGrow', 0], ['flexShrink', 1]]
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
const result: Array<[string, any]> = []
|
|
897
|
+
let i = 0
|
|
898
|
+
const isNum = (v: string) => !isNaN(+v)
|
|
899
|
+
if (isNum(values[i])) {
|
|
900
|
+
result.push(['flexGrow', +values[i++]])
|
|
901
|
+
} else {
|
|
902
|
+
result.push(['flexGrow', 1])
|
|
903
|
+
}
|
|
904
|
+
if (i < values.length && isNum(values[i])) {
|
|
905
|
+
result.push(['flexShrink', +values[i++]])
|
|
906
|
+
} else {
|
|
907
|
+
result.push(['flexShrink', 1])
|
|
908
|
+
}
|
|
909
|
+
if (i < values.length) {
|
|
910
|
+
if (values[i] !== 'auto') {
|
|
911
|
+
result.push(['flexBasis', global.__formatValue(values[i])])
|
|
912
|
+
}
|
|
913
|
+
} else {
|
|
914
|
+
result.push(['flexBasis', 0])
|
|
915
|
+
}
|
|
916
|
+
return result
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// font 简写专用 transform,仿 transformFlex 的标志位模式。
|
|
920
|
+
// RN 等效子集语法:font: [ <font-style> ] [ <font-variant-css2> ] [ <font-weight> ] <font-size> [ / <line-height> ] <font-family>
|
|
921
|
+
// - 必填项:font-size 与 font-family;缺其一整条 font 声明丢弃(error)
|
|
922
|
+
// - 非必填 token(font-stretch / 数字型 font-variant-numeric / system 关键字等):warn 提示并忽略,保留其余槽位
|
|
923
|
+
function transformFont (styleObj: Record<string, any>) {
|
|
924
|
+
const value = styleObj.font
|
|
925
|
+
if (typeof value !== 'string') return
|
|
926
|
+
const tokens = parseValues(value)
|
|
927
|
+
let sizeIdx = -1
|
|
928
|
+
let lineHeight: string | undefined
|
|
929
|
+
const result: Array<[string, any]> = []
|
|
930
|
+
// 1. 定位 font-size(第一个 length,可能带 /<line-height>)
|
|
931
|
+
// 注意:unit-less 数字也命中 length 正则,需要先排除 font-weight 数字(100..900 / bold / normal),
|
|
932
|
+
// 否则 `font: 500 16px Arial` 会把 500 误判为 fontSize。
|
|
933
|
+
// fontSize 自身可能是 %(如 `font: 50% Arial`),保留给文本透传阶段按继承 fontSize 解析;
|
|
934
|
+
// fontSize % 校验通过的是 `isLengthValue`(length 正则含 %),主流程已用 length 分支接住,无需特判
|
|
935
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
936
|
+
let t = tokens[i]
|
|
937
|
+
if (t.endsWith('/') && tokens[i + 1]) {
|
|
938
|
+
t += tokens[i + 1]
|
|
939
|
+
tokens.splice(i + 1, 1)
|
|
940
|
+
} else if (tokens[i + 1] === '/' && tokens[i + 2]) {
|
|
941
|
+
t += `/${tokens[i + 2]}`
|
|
942
|
+
tokens.splice(i + 1, 2)
|
|
943
|
+
} else if (tokens[i + 1]?.startsWith('/') && tokens[i + 1].length > 1) {
|
|
944
|
+
t += tokens[i + 1]
|
|
945
|
+
tokens.splice(i + 1, 1)
|
|
946
|
+
}
|
|
947
|
+
const [sizePart, lhPart] = parseValues(t, '/')
|
|
948
|
+
if (hasOwn(fontWeightMap, sizePart)) continue
|
|
949
|
+
if (isLengthValue(sizePart)) {
|
|
950
|
+
sizeIdx = i
|
|
951
|
+
result.push(['fontSize', global.__formatValue(sizePart)])
|
|
952
|
+
if (lhPart) lineHeight = lhPart
|
|
953
|
+
break
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
if (sizeIdx === -1) {
|
|
957
|
+
// 缺必填 font-size 整体丢弃;与 transformFlex「Flex shorthand value [...] ..., dropped.」同口径
|
|
958
|
+
error(`Font shorthand value [${value}] is missing required <font-size>, dropped.`)
|
|
959
|
+
delete styleObj.font
|
|
960
|
+
return
|
|
961
|
+
}
|
|
962
|
+
// 2. 前导段 font-style / font-variant(small-caps) / font-weight,顺序不敏感
|
|
963
|
+
for (let i = 0; i < sizeIdx; i++) {
|
|
964
|
+
const t = tokens[i]
|
|
965
|
+
if (t === 'normal') continue
|
|
966
|
+
if (t === 'italic') {
|
|
967
|
+
result.push(['fontStyle', t])
|
|
968
|
+
} else if (t === 'small-caps') {
|
|
969
|
+
// RN processFontVariant 接受字符串,内部 split 归一为数组,与 font-variant 长属性同口径
|
|
970
|
+
result.push(['fontVariant', t])
|
|
971
|
+
} else if (hasOwn(fontWeightMap, t)) {
|
|
972
|
+
result.push(['fontWeight', t])
|
|
973
|
+
} else {
|
|
974
|
+
// 其余(font-stretch / 数字型 font-variant-numeric / system 关键字等)→ 非必填:
|
|
975
|
+
// 与 transformBackground「Token [...] in [background: ...] ..., dropped.」同口径,warn + 忽略该 token、保留其余
|
|
976
|
+
warn(`Token [${t}] in [font: ${value}] is not supported (only font-style / small-caps / font-weight are valid before <font-size>), dropped.`)
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
// 3. line-height:文本透传阶段会基于当前 / 继承 fontSize 解析百分比。
|
|
980
|
+
// - unit-less 数字(1.5)→ '150%' 中间态,避免和已格式化 RN 绝对 number 混淆
|
|
981
|
+
// - 其它值(120% / 40px / 40rpx)→ __formatValue
|
|
982
|
+
if (lineHeight !== undefined) {
|
|
983
|
+
const lh = !isNaN(+lineHeight)
|
|
984
|
+
? `${+lineHeight * 100}%`
|
|
985
|
+
: global.__formatValue(lineHeight)
|
|
986
|
+
if (lh !== undefined) {
|
|
987
|
+
result.push(['lineHeight', lh])
|
|
988
|
+
} else {
|
|
989
|
+
warn(`Line-height [${lineHeight}] in [font: ${value}] could not be resolved, dropped.`)
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
// 4. font-family(font-size 之后剩余部分;多字体取首值、去引号)
|
|
993
|
+
const familyStr = tokens.slice(sizeIdx + 1).join(' ').trim()
|
|
994
|
+
if (!familyStr) {
|
|
995
|
+
error(`Font shorthand value [${value}] is missing required <font-family>, dropped.`)
|
|
996
|
+
delete styleObj.font
|
|
997
|
+
return
|
|
998
|
+
}
|
|
999
|
+
// parseValues 内部已 trim
|
|
1000
|
+
const family = parseValues(familyStr.replace(quoteCharRegExp, ''), ',')[0]
|
|
1001
|
+
if (family) result.push(['fontFamily', family])
|
|
1002
|
+
delete styleObj.font
|
|
1003
|
+
for (const [prop, val] of result) {
|
|
1004
|
+
if (!hasOwn(styleObj, prop)) styleObj[prop] = val
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
function transformFlex (styleObj: Record<string, any>) {
|
|
1009
|
+
const value = styleObj.flex
|
|
1010
|
+
if (typeof value !== 'string') return
|
|
1011
|
+
delete styleObj.flex
|
|
1012
|
+
const flexResult = expandFlex(value)
|
|
1013
|
+
if (!flexResult) {
|
|
1014
|
+
// expandFlex 仅在 parseValues 出空数组时返回 null(空字符串 / 纯空白),属于明确非法,整段丢弃
|
|
1015
|
+
error(`Flex shorthand value [${value}] is not a valid CSS flex value, dropped.`)
|
|
1016
|
+
return
|
|
1017
|
+
}
|
|
1018
|
+
for (const [prop, val] of flexResult) {
|
|
1019
|
+
if (!hasOwn(styleObj, prop)) styleObj[prop] = val
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
export function transformShorthand (styleObj: Record<string, any>, shorthandKeys: string[], percentConfig: PercentConfig) {
|
|
1024
|
+
for (const key of shorthandKeys) {
|
|
1025
|
+
const value = styleObj[key]
|
|
1026
|
+
|
|
1027
|
+
// —— 以下为公共链路(与 textDecoration / flexFlow / textShadow / 四值简写共用)——
|
|
1028
|
+
let values: string[]
|
|
1029
|
+
if (typeof value === 'string') {
|
|
1030
|
+
values = parseValues(value)
|
|
1031
|
+
} else if (typeof value === 'number' && hasOwn(runtimeNumericExpandShorthandMap, key)) {
|
|
1032
|
+
// runtimeNumericExpandShorthandMap 中的简写(border / outline / gap)在 __getStyle 的 transformStyleObj
|
|
1033
|
+
// 阶段已被 __formatValue 把带单位长度换算为 number;这里转回字符串 token 交给后续展开,
|
|
1034
|
+
// 否则会原样残留为 RN 不认识或行为不一致的简写 key。
|
|
1035
|
+
values = ['' + value]
|
|
1036
|
+
} else {
|
|
1037
|
+
continue
|
|
1038
|
+
}
|
|
1039
|
+
const props = runtimeAbbreviationMap[key]
|
|
1040
|
+
if (!props) continue
|
|
1041
|
+
// 单值短路:composite 且单值通常透传(RN 原生支持 margin / padding / inset / border-* 单值 DimensionValue);
|
|
1042
|
+
// 但 gap 必须展开 —— RN gap / rowGap / columnGap 严格要求 number,单值串虽已在 __getStyle 阶段被 __formatValue 换算,
|
|
1043
|
+
// 多值串(如 '10px 20px')仍会原样透传到这里,需展开后逐 token 再过 __formatValue 才能落成 number;
|
|
1044
|
+
// `%` 在写回阶段由 transformShorthand 调 resolvePercent 落成 number(rowGap 基 parentHeight、columnGap 基 parentWidth)
|
|
1045
|
+
if (
|
|
1046
|
+
hasOwn(runtimeCompositeStyleMap, key) &&
|
|
1047
|
+
values.length === 1 &&
|
|
1048
|
+
key !== 'gap'
|
|
1049
|
+
) continue
|
|
1050
|
+
let expandedValues = values
|
|
1051
|
+
let pairs: Array<[string, any]>
|
|
1052
|
+
if (hasOwn(runtimeCompositeStyleMap, key)) {
|
|
1053
|
+
expandedValues = expandCompositeValues(values)
|
|
1054
|
+
}
|
|
1055
|
+
if (hasOwn(runtimeUnorderedAbbreviationMap, key)) {
|
|
1056
|
+
// expandUnorderedAbbreviation 末尾已内部调用 applyRuntimeShorthandDefaults 补齐
|
|
1057
|
+
pairs = expandUnorderedAbbreviation(key, values)
|
|
1058
|
+
} else {
|
|
1059
|
+
pairs = expandAbbreviation(expandedValues, props)
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
delete styleObj[key]
|
|
1063
|
+
for (const [prop, val] of pairs) {
|
|
1064
|
+
if (hasOwn(styleObj, prop)) continue
|
|
1065
|
+
// gap 简写展开后的 rowGap / columnGap 仍可能是 `%` 字符串(如 `gap: 50%` / `gap: 10px 50%`),
|
|
1066
|
+
// RN 类型严格要求 number,这里就地用 resolvePercent 落成数字(rowGap 基 parentHeight、columnGap 基 parentWidth)
|
|
1067
|
+
if ((prop === 'rowGap' || prop === 'columnGap') && typeof val === 'string' && percentRegExp.test(val)) {
|
|
1068
|
+
styleObj[prop] = resolvePercent(val, prop, percentConfig)
|
|
1069
|
+
} else {
|
|
1070
|
+
styleObj[prop] = val
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// --- runtime alignment ---
|
|
1077
|
+
|
|
1078
|
+
function transformFontFamily (styleObj: Record<string, any>) {
|
|
1079
|
+
const value = styleObj.fontFamily
|
|
1080
|
+
if (typeof value !== 'string') return
|
|
1081
|
+
const stripped = value.replace(quoteCharRegExp, '').trim()
|
|
1082
|
+
if (!stripped) return
|
|
1083
|
+
const values = parseValues(stripped, ',')
|
|
1084
|
+
// parseValues 内部已 trim
|
|
1085
|
+
styleObj.fontFamily = values[0]
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
function transformBorderStyleNone (styleObj: Record<string, any>) {
|
|
1089
|
+
if (styleObj.borderStyle === 'none') {
|
|
1090
|
+
delete styleObj.borderStyle
|
|
1091
|
+
delete styleObj.borderTopWidth
|
|
1092
|
+
delete styleObj.borderRightWidth
|
|
1093
|
+
delete styleObj.borderBottomWidth
|
|
1094
|
+
delete styleObj.borderLeftWidth
|
|
1095
|
+
styleObj.borderWidth = 0
|
|
1096
|
+
}
|
|
1097
|
+
if (styleObj.outlineStyle === 'none') {
|
|
1098
|
+
delete styleObj.outlineStyle
|
|
1099
|
+
styleObj.outlineWidth = 0
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
function transformPosition (styleObj: Record<string, any>) {
|
|
1104
|
+
if (styleObj.position === 'fixed') {
|
|
1105
|
+
styleObj.position = 'absolute'
|
|
1106
|
+
return true
|
|
1107
|
+
}
|
|
1108
|
+
return false
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
function transOrderXY (values: string[]) {
|
|
1112
|
+
if (values.length === 2 && ['top', 'bottom'].includes(values[0])) {
|
|
1113
|
+
[values[0], values[1]] = [values[1], values[0]]
|
|
1114
|
+
}
|
|
1115
|
+
return values
|
|
1116
|
+
}
|
|
1117
|
+
function transformBackground (styleObj: Record<string, any>) {
|
|
1118
|
+
if (typeof styleObj.backgroundSize === 'string') {
|
|
1119
|
+
styleObj.backgroundSize = parseValues(styleObj.backgroundSize)
|
|
1120
|
+
}
|
|
1121
|
+
if (typeof styleObj.backgroundPosition === 'string') {
|
|
1122
|
+
const parts = parseValues(styleObj.backgroundPosition)
|
|
1123
|
+
styleObj.backgroundPosition = transOrderXY(parts.map(v => v === 'center' ? '50%' : v))
|
|
1124
|
+
}
|
|
1125
|
+
const value = styleObj.background
|
|
1126
|
+
if (typeof value !== 'string') return
|
|
1127
|
+
delete styleObj.background
|
|
1128
|
+
if (value === 'none') {
|
|
1129
|
+
styleObj.backgroundImage = 'none'
|
|
1130
|
+
styleObj.backgroundColor = 'transparent'
|
|
1131
|
+
return
|
|
1132
|
+
}
|
|
1133
|
+
const tokens = parseValues(value)
|
|
1134
|
+
const positionValues: string[] = []
|
|
1135
|
+
const sizeValues: string[] = []
|
|
1136
|
+
let isSize = false
|
|
1137
|
+
for (const token of tokens) {
|
|
1138
|
+
if (urlRegExp.test(token)) {
|
|
1139
|
+
styleObj.backgroundImage = token
|
|
1140
|
+
} else if (linearGradientRegExp.test(token)) {
|
|
1141
|
+
styleObj.backgroundImage = token
|
|
1142
|
+
} else if (hasOwn(bgRepeatMap, token)) {
|
|
1143
|
+
styleObj.backgroundRepeat = token
|
|
1144
|
+
} else if (isColorValue(token)) {
|
|
1145
|
+
styleObj.backgroundColor = token
|
|
1146
|
+
} else if (token === '/') {
|
|
1147
|
+
isSize = true
|
|
1148
|
+
} else {
|
|
1149
|
+
const slashParts = parseValues(token, '/')
|
|
1150
|
+
if (slashParts.length > 1) {
|
|
1151
|
+
const posPart = slashParts[0]
|
|
1152
|
+
if (posPart) positionValues.push(posPart === 'center' ? '50%' : posPart)
|
|
1153
|
+
isSize = true
|
|
1154
|
+
for (let i = 1; i < slashParts.length; i++) {
|
|
1155
|
+
if (slashParts[i]) sizeValues.push(slashParts[i])
|
|
1156
|
+
}
|
|
1157
|
+
} else if (isSize) {
|
|
1158
|
+
sizeValues.push(token)
|
|
1159
|
+
} else if (hasOwn(bgPositionMap, token) || percentRegExp.test(token) || digitStartRegExp.test(token)) {
|
|
1160
|
+
positionValues.push(token === 'center' ? '50%' : token)
|
|
1161
|
+
} else {
|
|
1162
|
+
// background-attachment / background-origin / background-clip 及拼写错误等:RN 均不支持,提示用户避免静默丢值
|
|
1163
|
+
warn(`Token [${token}] in [background: ${value}] is not a recognized background sub-value, dropped.`)
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
if (positionValues.length) styleObj.backgroundPosition = transOrderXY(positionValues)
|
|
1168
|
+
if (sizeValues.length) styleObj.backgroundSize = sizeValues
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// ============================================================
|
|
1172
|
+
// style traversal
|
|
1173
|
+
// ============================================================
|
|
1174
|
+
|
|
1175
|
+
export function traverseStyle (styleObj: Record<string, any>, visitor: (arg: VisitorArg) => void) {
|
|
1176
|
+
const keyPath: Array<string> = []
|
|
1177
|
+
function traverse<T extends Record<string, any>> (target: T) {
|
|
1178
|
+
if (Array.isArray(target)) {
|
|
1179
|
+
for (let i = 0; i < target.length; i++) {
|
|
1180
|
+
const key = String(i)
|
|
1181
|
+
const value = target[i]
|
|
1182
|
+
keyPath.push(key)
|
|
1183
|
+
visitor({ target, key, value, keyPath })
|
|
1184
|
+
traverse(value)
|
|
1185
|
+
keyPath.pop()
|
|
1186
|
+
}
|
|
1187
|
+
} else if (isObject(target)) {
|
|
1188
|
+
const keys = Object.keys(target)
|
|
1189
|
+
for (let i = 0; i < keys.length; i++) {
|
|
1190
|
+
const key = keys[i]
|
|
1191
|
+
const value = (target as Record<string, any>)[key]
|
|
1192
|
+
keyPath.push(key)
|
|
1193
|
+
visitor({ target, key, value, keyPath })
|
|
1194
|
+
traverse(value)
|
|
1195
|
+
keyPath.pop()
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
traverse(styleObj)
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
export function setStyle (styleObj: Record<string, any>, keyPath: Array<string>, setter: (arg: VisitorArg) => void) {
|
|
1203
|
+
let target = styleObj
|
|
1204
|
+
const lastKey = keyPath[keyPath.length - 1]
|
|
1205
|
+
for (let i = 0; i < keyPath.length - 1; i++) {
|
|
1206
|
+
target = target[keyPath[i]]
|
|
1207
|
+
if (!target) return
|
|
1208
|
+
}
|
|
1209
|
+
setter({
|
|
1210
|
+
target,
|
|
1211
|
+
key: lastKey,
|
|
1212
|
+
value: target[lastKey],
|
|
1213
|
+
keyPath
|
|
1214
|
+
})
|
|
454
1215
|
}
|
|
455
1216
|
|
|
456
|
-
|
|
1217
|
+
// ============================================================
|
|
1218
|
+
// core style hook
|
|
1219
|
+
// ============================================================
|
|
1220
|
+
|
|
1221
|
+
export function useTransformStyle (styleObj: Record<string, any> = {}, { enableVar, transformRadiusPercent, parentWidth, parentHeight, defaultStyle }: TransformStyleConfig) {
|
|
457
1222
|
const varStyle: Record<string, any> = {}
|
|
458
1223
|
const unoVarStyle: Record<string, any> = {}
|
|
459
1224
|
const normalStyle: Record<string, any> = {}
|
|
@@ -461,16 +1226,84 @@ export function useTransformStyle (styleObj: Record<string, any> = {}, { enableV
|
|
|
461
1226
|
let hasVarUse = false
|
|
462
1227
|
let hasSelfPercent = false
|
|
463
1228
|
let hasBoxSizingAffectingStyle = false
|
|
1229
|
+
// 顶层 transform* 标志位:在 styleVisitor 阶段从原始 styleObj 收集,
|
|
1230
|
+
// 调用侧据此决定是否进入对应 transform 函数。
|
|
1231
|
+
let hasTransform = false
|
|
1232
|
+
let hasBoxShadow = false
|
|
1233
|
+
let hasFontFamily = false
|
|
1234
|
+
let hasFlex = false
|
|
1235
|
+
let hasFont = false
|
|
1236
|
+
let needTransformBackground = false
|
|
1237
|
+
let needStringify = false
|
|
464
1238
|
const varKeyPaths: Array<Array<string>> = []
|
|
465
1239
|
const unoVarKeyPaths: Array<Array<string>> = []
|
|
466
1240
|
const percentKeyPaths: Array<Array<string>> = []
|
|
467
1241
|
const calcKeyPaths: Array<Array<string>> = []
|
|
468
1242
|
const envKeyPaths: Array<Array<string>> = []
|
|
1243
|
+
const shorthandKeys: string[] = []
|
|
469
1244
|
const [width, setWidth] = useState(0)
|
|
470
1245
|
const [height, setHeight] = useState(0)
|
|
471
1246
|
const navigation = useNavigation()
|
|
472
1247
|
|
|
473
|
-
function
|
|
1248
|
+
function collectTopLevelFlags (key: string, value: any) {
|
|
1249
|
+
switch (key) {
|
|
1250
|
+
case 'transform':
|
|
1251
|
+
hasTransform = true
|
|
1252
|
+
break
|
|
1253
|
+
case 'boxShadow':
|
|
1254
|
+
hasBoxShadow = true
|
|
1255
|
+
break
|
|
1256
|
+
case 'fontFamily':
|
|
1257
|
+
hasFontFamily = true
|
|
1258
|
+
break
|
|
1259
|
+
case 'flex':
|
|
1260
|
+
hasFlex = true
|
|
1261
|
+
break
|
|
1262
|
+
case 'font':
|
|
1263
|
+
hasFont = true
|
|
1264
|
+
break
|
|
1265
|
+
case 'background':
|
|
1266
|
+
case 'backgroundSize':
|
|
1267
|
+
case 'backgroundPosition':
|
|
1268
|
+
needTransformBackground = true
|
|
1269
|
+
break
|
|
1270
|
+
case 'fontWeight':
|
|
1271
|
+
case 'transformOrigin':
|
|
1272
|
+
needStringify = true
|
|
1273
|
+
break
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
function visitOther ({ key, value, keyPath }: VisitorArg) {
|
|
1278
|
+
if (typeof value !== 'string') return
|
|
1279
|
+
const hasPercent = value.includes('%')
|
|
1280
|
+
const hasCalc = value.includes('calc(')
|
|
1281
|
+
const hasEnv = value.includes('env(')
|
|
1282
|
+
if (!(hasPercent || hasCalc || hasEnv)) return
|
|
1283
|
+
let resolvedKeyPath: Array<string> | undefined
|
|
1284
|
+
if (hasEnv) {
|
|
1285
|
+
resolvedKeyPath = keyPath.slice()
|
|
1286
|
+
envKeyPaths.push(resolvedKeyPath)
|
|
1287
|
+
}
|
|
1288
|
+
if (hasPercent) {
|
|
1289
|
+
// fixme 去掉 translate & border-radius 的百分比计算
|
|
1290
|
+
// fixme Image 组件 borderRadius 仅支持 number
|
|
1291
|
+
const needRadiusPercent = transformRadiusPercent && hasOwn(radiusPercentRule, key)
|
|
1292
|
+
// RN gap / rowGap / columnGap 不支持 %,需要运行时换算为 number 喂给 RN
|
|
1293
|
+
const needGapPercent = key === 'rowGap' || key === 'columnGap'
|
|
1294
|
+
if ((needRadiusPercent || needGapPercent) && percentRegExp.test(value)) {
|
|
1295
|
+
if (needRadiusPercent) hasSelfPercent = true
|
|
1296
|
+
resolvedKeyPath = resolvedKeyPath ?? keyPath.slice()
|
|
1297
|
+
percentKeyPaths.push(resolvedKeyPath)
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
if (hasCalc) {
|
|
1301
|
+
resolvedKeyPath = resolvedKeyPath ?? keyPath.slice()
|
|
1302
|
+
calcKeyPaths.push(resolvedKeyPath)
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
function styleVisitor ({ target, key, value, keyPath }: VisitorArg) {
|
|
474
1307
|
if (keyPath.length === 1) {
|
|
475
1308
|
if (unoVarDecRegExp.test(key)) {
|
|
476
1309
|
unoVarStyle[key] = value
|
|
@@ -480,128 +1313,116 @@ export function useTransformStyle (styleObj: Record<string, any> = {}, { enableV
|
|
|
480
1313
|
} else {
|
|
481
1314
|
// clone对象避免set值时改写到props
|
|
482
1315
|
normalStyle[key] = isObject(value) ? diffAndCloneA(value).clone : value
|
|
1316
|
+
if (!hasBoxSizingAffectingStyle && isBoxSizingAffectingStyle(key)) {
|
|
1317
|
+
hasBoxSizingAffectingStyle = true
|
|
1318
|
+
}
|
|
1319
|
+
if (hasOwn(runtimeAbbreviationMap, key)) {
|
|
1320
|
+
shorthandKeys.push(key)
|
|
1321
|
+
}
|
|
1322
|
+
collectTopLevelFlags(key, value)
|
|
483
1323
|
}
|
|
484
1324
|
}
|
|
485
|
-
//
|
|
486
|
-
if (
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
function boxSizingVisitor ({ key, keyPath }: VisitorArg) {
|
|
500
|
-
if (keyPath.length === 1 && !hasBoxSizingAffectingStyle && isBoxSizingAffectingStyle(key)) {
|
|
501
|
-
hasBoxSizingAffectingStyle = true
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
function envVisitor ({ value, keyPath }: VisitorArg) {
|
|
506
|
-
if (envUseRegExp.test(value)) {
|
|
507
|
-
envKeyPaths.push(keyPath.slice())
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
function calcVisitor ({ value, keyPath }: VisitorArg) {
|
|
512
|
-
if (calcUseRegExp.test(value)) {
|
|
513
|
-
calcKeyPaths.push(keyPath.slice())
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
function percentVisitor ({ key, value, keyPath }: VisitorArg) {
|
|
518
|
-
// fixme 去掉 translate & border-radius 的百分比计算
|
|
519
|
-
// fixme Image 组件 borderRadius 仅支持 number
|
|
520
|
-
if (transformRadiusPercent && hasOwn(radiusPercentRule, key) && PERCENT_REGEX.test(value)) {
|
|
521
|
-
hasSelfPercent = true
|
|
522
|
-
percentKeyPaths.push(keyPath.slice())
|
|
523
|
-
} else if ((key === 'fontSize' || key === 'lineHeight') && PERCENT_REGEX.test(value)) {
|
|
524
|
-
percentKeyPaths.push(keyPath.slice())
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
function visitOther ({ target, key, value, keyPath }: VisitorArg) {
|
|
529
|
-
if (typeof value === 'string' && (value.includes('%') || value.includes('calc(') || value.includes('env('))) {
|
|
530
|
-
[envVisitor, percentVisitor, calcVisitor].forEach(visitor => visitor({ target, key, value, keyPath }))
|
|
1325
|
+
// var 定义中使用的 var 无需替换值,可以通过 resolveVar 递归解析出值
|
|
1326
|
+
if (varDecRegExp.test(key) || typeof value !== 'string') return
|
|
1327
|
+
// 一般情况下一个样式属性中不会混用 unocss var 和普通 css var,可分开互斥处理
|
|
1328
|
+
if (unoVarUseRegExp.test(value)) {
|
|
1329
|
+
unoVarKeyPaths.push(keyPath.slice())
|
|
1330
|
+
} else if (varUseRegExp.test(value)) {
|
|
1331
|
+
hasVarUse = true
|
|
1332
|
+
varKeyPaths.push(keyPath.slice())
|
|
1333
|
+
} else {
|
|
1334
|
+
visitOther({ target, key, value, keyPath })
|
|
531
1335
|
}
|
|
532
1336
|
}
|
|
533
1337
|
|
|
534
1338
|
// traverse var & generate normalStyle
|
|
535
|
-
traverseStyle(styleObj,
|
|
536
|
-
hasVarDec = hasVarDec || !!externalVarContext
|
|
1339
|
+
traverseStyle(styleObj, styleVisitor)
|
|
537
1340
|
enableVar = enableVar || hasVarDec || hasVarUse
|
|
538
1341
|
const enableVarRef = useRef(enableVar)
|
|
539
1342
|
if (enableVarRef.current !== enableVar) {
|
|
540
1343
|
error('css variable use/declare should be stable in the component lifecycle, or you can set [enable-var] with true.')
|
|
541
1344
|
}
|
|
542
1345
|
// apply css var
|
|
543
|
-
const varContextRef = useRef({})
|
|
1346
|
+
const varContextRef = useRef<Record<string, any>>({})
|
|
544
1347
|
if (enableVarRef.current) {
|
|
545
1348
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
546
1349
|
const varContext = useContext(VarContext)
|
|
547
|
-
|
|
548
|
-
//
|
|
549
|
-
|
|
550
|
-
|
|
1350
|
+
// 无声明节点:直接用父 varContext 解析,跳过 merge / 比对 / ref 维护。
|
|
1351
|
+
// 有声明节点:浅合并出 newVarContext,仅在内容变化时替换 ref 维持引用稳定,
|
|
1352
|
+
// 供 wrapChildren 的 <VarContext.Provider> 使用。
|
|
1353
|
+
let resolvedVarContext = varContext
|
|
1354
|
+
if (hasVarDec) {
|
|
1355
|
+
const newVarContext = extendObject({}, varContext, varStyle)
|
|
1356
|
+
if (!shallowEqual(varContextRef.current, newVarContext)) {
|
|
1357
|
+
varContextRef.current = newVarContext
|
|
1358
|
+
}
|
|
1359
|
+
resolvedVarContext = varContextRef.current
|
|
551
1360
|
}
|
|
552
|
-
transformVar(normalStyle, varKeyPaths,
|
|
1361
|
+
if (varKeyPaths.length) transformVar(normalStyle, varKeyPaths, resolvedVarContext, visitOther)
|
|
553
1362
|
}
|
|
554
1363
|
|
|
555
1364
|
// apply unocss var
|
|
556
|
-
if (unoVarKeyPaths.length)
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
const percentConfig = {
|
|
1365
|
+
if (unoVarKeyPaths.length) transformVar(normalStyle, unoVarKeyPaths, unoVarStyle, visitOther)
|
|
1366
|
+
// apply env
|
|
1367
|
+
if (envKeyPaths.length) transformEnv(normalStyle, envKeyPaths, navigation)
|
|
1368
|
+
// apply percent / calc
|
|
1369
|
+
const percentConfig: PercentConfig = {
|
|
561
1370
|
width,
|
|
562
1371
|
height,
|
|
563
1372
|
fontSize: normalStyle.fontSize,
|
|
564
1373
|
parentWidth,
|
|
565
|
-
parentHeight
|
|
566
|
-
parentFontSize
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
const positionMeta = {
|
|
570
|
-
hasPositionFixed: false
|
|
1374
|
+
parentHeight
|
|
571
1375
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
}
|
|
583
|
-
const resolved = resolvePercent(value, key, percentConfig)
|
|
584
|
-
return typeof resolved === 'number' ? resolved : 0
|
|
585
|
-
} else {
|
|
586
|
-
const formatted = global.__formatValue(value)
|
|
587
|
-
if (typeof formatted === 'number') {
|
|
588
|
-
return formatted
|
|
1376
|
+
if (hasFont) transformFont(normalStyle)
|
|
1377
|
+
if (percentKeyPaths.length) transformPercent(normalStyle, percentKeyPaths, percentConfig)
|
|
1378
|
+
if (calcKeyPaths.length) {
|
|
1379
|
+
transformCalc(normalStyle, calcKeyPaths, (value: string, key: string) => {
|
|
1380
|
+
if (percentRegExp.test(value)) {
|
|
1381
|
+
if (hasOwn(selfPercentRule, key)) {
|
|
1382
|
+
hasSelfPercent = true
|
|
1383
|
+
}
|
|
1384
|
+
const resolved = resolvePercent(value, key, percentConfig)
|
|
1385
|
+
return typeof resolved === 'number' ? resolved : 0
|
|
589
1386
|
} else {
|
|
590
|
-
|
|
591
|
-
|
|
1387
|
+
const formatted = global.__formatValue(value)
|
|
1388
|
+
if (typeof formatted === 'number') {
|
|
1389
|
+
return formatted
|
|
1390
|
+
} else {
|
|
1391
|
+
warn('calc() only support number, px, rpx, % temporarily.')
|
|
1392
|
+
return 0
|
|
1393
|
+
}
|
|
592
1394
|
}
|
|
593
|
-
}
|
|
594
|
-
}
|
|
1395
|
+
})
|
|
1396
|
+
}
|
|
595
1397
|
|
|
596
|
-
// apply position
|
|
597
|
-
transformPosition(normalStyle, positionMeta)
|
|
598
1398
|
// transform number enum stringify
|
|
599
|
-
transformStringify(normalStyle)
|
|
600
|
-
// transform
|
|
601
|
-
transformBoxShadow(normalStyle)
|
|
602
|
-
// transform 字符串格式转化数组格式
|
|
603
|
-
transformTransform(normalStyle)
|
|
604
|
-
|
|
1399
|
+
if (needStringify) transformStringify(normalStyle)
|
|
1400
|
+
// transform unit
|
|
1401
|
+
if (hasBoxShadow) transformBoxShadow(normalStyle)
|
|
1402
|
+
// transform 字符串格式转化数组格式
|
|
1403
|
+
if (hasTransform) transformTransform(normalStyle)
|
|
1404
|
+
// apply runtime style processing alignment
|
|
1405
|
+
if (hasFontFamily) transformFontFamily(normalStyle)
|
|
1406
|
+
if (hasFlex) transformFlex(normalStyle)
|
|
1407
|
+
if (shorthandKeys.length) transformShorthand(normalStyle, shorthandKeys, percentConfig)
|
|
1408
|
+
if (needTransformBackground) transformBackground(normalStyle)
|
|
1409
|
+
// borderStyle / outlineStyle: 'none' 的清除语义放到变量解析、简写展开之后统一处理。
|
|
1410
|
+
transformBorderStyleNone(normalStyle)
|
|
1411
|
+
// transform position: fixed
|
|
1412
|
+
const hasPositionFixed = transformPosition(normalStyle)
|
|
1413
|
+
// 合并组件默认样式:默认样式在 user transform 之后兜底写入,需完全符合 RN style 规范
|
|
1414
|
+
// 命中 user 已存在 key(含简写展开后的长属性)则跳过。
|
|
1415
|
+
// 复用同一次循环顺带补 hasBoxSizingAffectingStyle,避免独立扫一遍 default。
|
|
1416
|
+
if (defaultStyle) {
|
|
1417
|
+
for (const k in defaultStyle) {
|
|
1418
|
+
if (!hasOwn(defaultStyle, k)) continue
|
|
1419
|
+
if (!hasOwn(normalStyle, k)) normalStyle[k] = defaultStyle[k]
|
|
1420
|
+
if (!hasBoxSizingAffectingStyle && isBoxSizingAffectingStyle(k)) {
|
|
1421
|
+
hasBoxSizingAffectingStyle = true
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
if (hasBoxSizingAffectingStyle) transformBoxSizing(normalStyle)
|
|
605
1426
|
|
|
606
1427
|
return {
|
|
607
1428
|
hasVarDec,
|
|
@@ -610,83 +1431,82 @@ export function useTransformStyle (styleObj: Record<string, any> = {}, { enableV
|
|
|
610
1431
|
setHeight,
|
|
611
1432
|
normalStyle,
|
|
612
1433
|
hasSelfPercent,
|
|
613
|
-
hasPositionFixed
|
|
1434
|
+
hasPositionFixed
|
|
614
1435
|
}
|
|
615
1436
|
}
|
|
616
1437
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
1438
|
+
// ============================================================
|
|
1439
|
+
// other React hooks
|
|
1440
|
+
// ============================================================
|
|
1441
|
+
|
|
1442
|
+
export function useNavigation (): Record<string, any> | undefined {
|
|
1443
|
+
const { navigation } = useContext(RouteContext) || {}
|
|
1444
|
+
return navigation
|
|
622
1445
|
}
|
|
623
1446
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
})
|
|
642
|
-
}
|
|
1447
|
+
function getTextPercentBase (currentFontSize?: string | number, parentTextStyle?: TextStyle) {
|
|
1448
|
+
return typeof currentFontSize === 'number'
|
|
1449
|
+
? currentFontSize
|
|
1450
|
+
: typeof parentTextStyle?.fontSize === 'number'
|
|
1451
|
+
? parentTextStyle.fontSize
|
|
1452
|
+
: DEFAULT_FONT_SIZE
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
export function resolveTextFontSizePercentStyle<T extends TextStyle | undefined> (
|
|
1456
|
+
textStyle: T,
|
|
1457
|
+
parentTextStyle?: TextStyle
|
|
1458
|
+
): T {
|
|
1459
|
+
if (!textStyle) return textStyle
|
|
1460
|
+
|
|
1461
|
+
if (typeof textStyle.fontSize === 'string' && percentRegExp.test(textStyle.fontSize)) {
|
|
1462
|
+
const base = getTextPercentBase(undefined, parentTextStyle)
|
|
1463
|
+
textStyle.fontSize = parseFloat(textStyle.fontSize) / 100 * base
|
|
643
1464
|
}
|
|
644
|
-
|
|
1465
|
+
|
|
1466
|
+
return textStyle
|
|
645
1467
|
}
|
|
646
1468
|
|
|
647
|
-
export function
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
1469
|
+
export function resolveTextLineHeightPercentStyle<T extends TextStyle | undefined> (
|
|
1470
|
+
textStyle: T,
|
|
1471
|
+
parentTextStyle?: TextStyle
|
|
1472
|
+
): T {
|
|
1473
|
+
if (!textStyle) return textStyle
|
|
1474
|
+
|
|
1475
|
+
if (typeof textStyle.lineHeight === 'string' && percentRegExp.test(textStyle.lineHeight)) {
|
|
1476
|
+
const base = getTextPercentBase(textStyle.fontSize, parentTextStyle)
|
|
1477
|
+
textStyle.lineHeight = parseFloat(textStyle.lineHeight) / 100 * base
|
|
653
1478
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
key: lastKey,
|
|
657
|
-
value: target[lastKey],
|
|
658
|
-
keyPath
|
|
659
|
-
})
|
|
1479
|
+
|
|
1480
|
+
return textStyle
|
|
660
1481
|
}
|
|
661
1482
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
1483
|
+
/**
|
|
1484
|
+
* 用法等同于 useEffect,但是会忽略首次执行,只在依赖更新时执行
|
|
1485
|
+
*/
|
|
1486
|
+
export const useUpdateEffect = (effect: any, deps: any) => {
|
|
1487
|
+
const isMounted = useRef(false)
|
|
1488
|
+
|
|
1489
|
+
// for react-refresh
|
|
1490
|
+
useEffect(() => {
|
|
1491
|
+
return () => {
|
|
1492
|
+
isMounted.current = false
|
|
1493
|
+
}
|
|
1494
|
+
}, [])
|
|
1495
|
+
|
|
1496
|
+
useEffect(() => {
|
|
1497
|
+
if (!isMounted.current) {
|
|
1498
|
+
isMounted.current = true
|
|
669
1499
|
} else {
|
|
670
|
-
return
|
|
1500
|
+
return effect()
|
|
671
1501
|
}
|
|
672
|
-
})
|
|
673
|
-
textProps: Partial<T>
|
|
674
|
-
innerProps: Partial<T>
|
|
675
|
-
}
|
|
1502
|
+
}, deps)
|
|
676
1503
|
}
|
|
677
1504
|
|
|
678
|
-
interface LayoutConfig {
|
|
679
|
-
props: Record<string, any>
|
|
680
|
-
hasSelfPercent: boolean
|
|
681
|
-
setWidth?: Dispatch<SetStateAction<number>>
|
|
682
|
-
setHeight?: Dispatch<SetStateAction<number>>
|
|
683
|
-
onLayout?: (event?: LayoutChangeEvent) => void
|
|
684
|
-
nodeRef: React.RefObject<any>
|
|
685
|
-
}
|
|
686
1505
|
export const useLayout = ({ props, hasSelfPercent, setWidth, setHeight, onLayout, nodeRef }: LayoutConfig) => {
|
|
687
1506
|
const layoutRef = useRef({})
|
|
688
1507
|
const hasLayoutRef = useRef(false)
|
|
689
|
-
|
|
1508
|
+
// 固定首次 layout 前是否需要隐藏,避免 hasSelfPercent 后续变化时重新隐藏组件
|
|
1509
|
+
const layoutStyle = useMemo(() => { return !hasLayoutRef.current && hasSelfPercent ? hiddenStyle : undefined }, [hasLayoutRef.current])
|
|
690
1510
|
const layoutProps: Record<string, any> = {}
|
|
691
1511
|
const navigation = useNavigation()
|
|
692
1512
|
const enableOffset = props['enable-offset']
|
|
@@ -715,37 +1535,36 @@ export const useLayout = ({ props, hasSelfPercent, setWidth, setHeight, onLayout
|
|
|
715
1535
|
}
|
|
716
1536
|
}
|
|
717
1537
|
|
|
718
|
-
export
|
|
719
|
-
hasVarDec: boolean
|
|
720
|
-
varContext?: Record<string, any>
|
|
721
|
-
textPassThrough?: TextPassThroughContextValue | null
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
export interface TextPassThroughValueOptions {
|
|
725
|
-
inheritTextProps?: boolean
|
|
726
|
-
disabled?: boolean
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
export function useTextPassThroughValue (
|
|
1538
|
+
export function useTextPassThrough (
|
|
730
1539
|
textStyle?: TextStyle,
|
|
731
1540
|
textProps?: Record<string, any>,
|
|
732
|
-
{
|
|
1541
|
+
{ enableTextPassThrough = false }: TextPassThroughValueOptions = {}
|
|
733
1542
|
) {
|
|
734
|
-
const
|
|
735
|
-
|
|
1543
|
+
const shouldEnableTextPassThrough = (
|
|
1544
|
+
enableTextPassThrough ||
|
|
1545
|
+
!!textStyle ||
|
|
1546
|
+
!!textProps
|
|
1547
|
+
)
|
|
1548
|
+
const enableTextPassThroughRef = useRef(shouldEnableTextPassThrough)
|
|
1549
|
+
|
|
1550
|
+
if (enableTextPassThroughRef.current !== shouldEnableTextPassThrough) {
|
|
1551
|
+
error('[Mpx runtime error]: text style/props use should be stable in the component lifecycle, or you can set [enable-text-pass-through] with true.')
|
|
1552
|
+
}
|
|
736
1553
|
|
|
737
|
-
if (
|
|
1554
|
+
if (!enableTextPassThroughRef.current) return null
|
|
738
1555
|
|
|
739
|
-
|
|
1556
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
1557
|
+
const parent = useContext(TextPassThroughContext)
|
|
1558
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
1559
|
+
const valueRef = useRef<TextPassThroughContextValue | null>(null)
|
|
740
1560
|
|
|
741
|
-
const
|
|
742
|
-
|
|
1561
|
+
const resolvedTextStyle = resolveTextFontSizePercentStyle(textStyle, parent?.textStyle)
|
|
1562
|
+
const nextTextStyle = resolvedTextStyle
|
|
1563
|
+
? extendObject({}, parent?.textStyle, resolvedTextStyle)
|
|
743
1564
|
: parent?.textStyle
|
|
744
|
-
const nextTextProps =
|
|
745
|
-
? textProps
|
|
746
|
-
|
|
747
|
-
: parent?.pendingTextProps
|
|
748
|
-
: textProps
|
|
1565
|
+
const nextTextProps = textProps
|
|
1566
|
+
? extendObject({}, parent?.pendingTextProps, textProps)
|
|
1567
|
+
: parent?.pendingTextProps
|
|
749
1568
|
const nextValue = {
|
|
750
1569
|
textStyle: nextTextStyle,
|
|
751
1570
|
pendingTextProps: nextTextProps
|
|
@@ -758,104 +1577,6 @@ export function useTextPassThroughValue (
|
|
|
758
1577
|
return valueRef.current
|
|
759
1578
|
}
|
|
760
1579
|
|
|
761
|
-
export function wrapChildren (props: Record<string, any> = {}, { hasVarDec, varContext, textPassThrough }: WrapChildrenConfig) {
|
|
762
|
-
let { children } = props
|
|
763
|
-
if (textPassThrough) {
|
|
764
|
-
children = <TextPassThroughContext.Provider value={textPassThrough} key='textPassThroughWrap'>{children}</TextPassThroughContext.Provider>
|
|
765
|
-
}
|
|
766
|
-
if (hasVarDec && varContext) {
|
|
767
|
-
children = <VarContext.Provider value={varContext} key='varContextWrap'>{children}</VarContext.Provider>
|
|
768
|
-
}
|
|
769
|
-
return children
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
export const debounce = <T extends AnyFunc> (
|
|
773
|
-
func: T,
|
|
774
|
-
delay: number
|
|
775
|
-
): ((...args: Parameters<T>) => void) & { clear: () => void } => {
|
|
776
|
-
let timer: any
|
|
777
|
-
const wrapper = (...args: ReadonlyArray<any>) => {
|
|
778
|
-
timer && clearTimeout(timer)
|
|
779
|
-
timer = setTimeout(() => {
|
|
780
|
-
func(...args)
|
|
781
|
-
}, delay)
|
|
782
|
-
}
|
|
783
|
-
wrapper.clear = () => {
|
|
784
|
-
timer && clearTimeout(timer)
|
|
785
|
-
timer = null
|
|
786
|
-
}
|
|
787
|
-
return wrapper
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
export const useDebounceCallback = <T extends AnyFunc> (
|
|
791
|
-
func: T,
|
|
792
|
-
delay: number
|
|
793
|
-
): ((...args: Parameters<T>) => void) & { clear: () => void } => {
|
|
794
|
-
const debounced = useMemo(() => debounce(func, delay), [func])
|
|
795
|
-
return debounced
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
export const useStableCallback = <T extends AnyFunc | null | undefined> (
|
|
799
|
-
callback: T
|
|
800
|
-
): T extends AnyFunc ? T : () => void => {
|
|
801
|
-
const ref = useRef<T>(callback)
|
|
802
|
-
ref.current = callback
|
|
803
|
-
return useCallback<any>(
|
|
804
|
-
(...args: any[]) => ref.current?.(...args),
|
|
805
|
-
[]
|
|
806
|
-
)
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
export function usePrevious<T> (value: T): T | undefined {
|
|
810
|
-
const ref = useRef<T | undefined>()
|
|
811
|
-
const prev = ref.current
|
|
812
|
-
ref.current = value
|
|
813
|
-
return prev
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
export interface GestureHandler {
|
|
817
|
-
nodeRefs?: Array<{ getNodeInstance: () => { nodeRef: unknown } }>
|
|
818
|
-
current?: unknown
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
export function flatGesture (gestures: Array<GestureHandler> = []) {
|
|
822
|
-
return (gestures && gestures.flatMap((gesture: GestureHandler) => {
|
|
823
|
-
if (gesture && gesture.nodeRefs) {
|
|
824
|
-
return gesture.nodeRefs
|
|
825
|
-
.map((item: { getNodeInstance: () => any }) => item.getNodeInstance()?.instance?.gestureRef || {})
|
|
826
|
-
}
|
|
827
|
-
return gesture?.current ? [gesture] : []
|
|
828
|
-
})) || []
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
export function getCurrentPage (pageId: number | null | undefined) {
|
|
832
|
-
if (!global.getCurrentPages) return
|
|
833
|
-
const pages = global.getCurrentPages()
|
|
834
|
-
return pages.find((page: any) => isFunction(page.getPageId) && page.getPageId() === pageId)
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
export function renderImage (
|
|
838
|
-
imageProps: ImageProps,
|
|
839
|
-
enableFastImage = true
|
|
840
|
-
) {
|
|
841
|
-
let Component = Image
|
|
842
|
-
if (enableFastImage) {
|
|
843
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
844
|
-
const fastImageModule = require('@d11/react-native-fast-image')
|
|
845
|
-
Component = fastImageModule.default || fastImageModule
|
|
846
|
-
}
|
|
847
|
-
return createElement(Component, imageProps)
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
export function pickStyle (styleObj: Record<string, any> = {}, pickedKeys: Array<string>, callback?: (key: string, val: number | string) => number | string) {
|
|
851
|
-
return pickedKeys.reduce<Record<string, any>>((acc, key) => {
|
|
852
|
-
if (key in styleObj) {
|
|
853
|
-
acc[key] = callback ? callback(key, styleObj[key]) : styleObj[key]
|
|
854
|
-
}
|
|
855
|
-
return acc
|
|
856
|
-
}, {})
|
|
857
|
-
}
|
|
858
|
-
|
|
859
1580
|
export function useHover ({ enableHover, hoverStartTime, hoverStayTime, disabled }: { enableHover: boolean, hoverStartTime: number, hoverStayTime: number, disabled?: boolean }) {
|
|
860
1581
|
const enableHoverRef = useRef(enableHover)
|
|
861
1582
|
if (enableHoverRef.current !== enableHover) {
|
|
@@ -868,37 +1589,46 @@ export function useHover ({ enableHover, hoverStartTime, hoverStayTime, disabled
|
|
|
868
1589
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
869
1590
|
const [isHover, setIsHover] = useState(false)
|
|
870
1591
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
871
|
-
const
|
|
1592
|
+
const hoverRef = useRef<{
|
|
872
1593
|
startTimer?: ReturnType<typeof setTimeout>
|
|
873
1594
|
stayTimer?: ReturnType<typeof setTimeout>
|
|
874
|
-
|
|
1595
|
+
hoverStartTime: number
|
|
1596
|
+
hoverStayTime: number
|
|
1597
|
+
disabled?: boolean
|
|
1598
|
+
}>({ hoverStartTime, hoverStayTime, disabled })
|
|
1599
|
+
hoverRef.current.hoverStartTime = hoverStartTime
|
|
1600
|
+
hoverRef.current.hoverStayTime = hoverStayTime
|
|
1601
|
+
hoverRef.current.disabled = disabled
|
|
875
1602
|
|
|
876
1603
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
877
1604
|
useEffect(() => {
|
|
878
1605
|
return () => {
|
|
879
|
-
|
|
880
|
-
|
|
1606
|
+
hoverRef.current.startTimer && clearTimeout(hoverRef.current.startTimer)
|
|
1607
|
+
hoverRef.current.stayTimer && clearTimeout(hoverRef.current.stayTimer)
|
|
881
1608
|
}
|
|
882
1609
|
}, [])
|
|
883
1610
|
|
|
884
|
-
const setStartTimer = () => {
|
|
885
|
-
if (disabled) return
|
|
886
|
-
dataRef.current.startTimer && clearTimeout(dataRef.current.startTimer)
|
|
887
|
-
dataRef.current.startTimer = setTimeout(() => {
|
|
888
|
-
setIsHover(true)
|
|
889
|
-
}, +hoverStartTime)
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
const setStayTimer = () => {
|
|
893
|
-
if (disabled) return
|
|
894
|
-
dataRef.current.stayTimer && clearTimeout(dataRef.current.stayTimer)
|
|
895
|
-
dataRef.current.startTimer && clearTimeout(dataRef.current.startTimer)
|
|
896
|
-
dataRef.current.stayTimer = setTimeout(() => {
|
|
897
|
-
setIsHover(false)
|
|
898
|
-
}, +hoverStayTime)
|
|
899
|
-
}
|
|
900
1611
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
901
1612
|
const gesture = useMemo(() => {
|
|
1613
|
+
const setStartTimer = () => {
|
|
1614
|
+
const data = hoverRef.current
|
|
1615
|
+
if (data.disabled) return
|
|
1616
|
+
data.startTimer && clearTimeout(data.startTimer)
|
|
1617
|
+
data.startTimer = setTimeout(() => {
|
|
1618
|
+
setIsHover(true)
|
|
1619
|
+
}, +data.hoverStartTime)
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
const setStayTimer = () => {
|
|
1623
|
+
const data = hoverRef.current
|
|
1624
|
+
if (data.disabled) return
|
|
1625
|
+
data.stayTimer && clearTimeout(data.stayTimer)
|
|
1626
|
+
data.startTimer && clearTimeout(data.startTimer)
|
|
1627
|
+
data.stayTimer = setTimeout(() => {
|
|
1628
|
+
setIsHover(false)
|
|
1629
|
+
}, +data.hoverStayTime)
|
|
1630
|
+
}
|
|
1631
|
+
|
|
902
1632
|
return Gesture.Pan()
|
|
903
1633
|
.onTouchesDown(() => {
|
|
904
1634
|
setStartTimer()
|
|
@@ -933,3 +1663,75 @@ export function useRunOnJSCallback (callbackMapRef: MutableRefObject<Record<stri
|
|
|
933
1663
|
|
|
934
1664
|
return invokeCallback
|
|
935
1665
|
}
|
|
1666
|
+
|
|
1667
|
+
export const useDebounceCallback = <T extends AnyFunc> (
|
|
1668
|
+
func: T,
|
|
1669
|
+
delay: number
|
|
1670
|
+
): ((...args: Parameters<T>) => void) & { clear: () => void } => {
|
|
1671
|
+
const debounced = useMemo(() => debounce(func, delay), [func])
|
|
1672
|
+
return debounced
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
export const useStableCallback = <T extends AnyFunc | null | undefined> (
|
|
1676
|
+
callback: T
|
|
1677
|
+
): T extends AnyFunc ? T : () => void => {
|
|
1678
|
+
const ref = useRef<T>(callback)
|
|
1679
|
+
ref.current = callback
|
|
1680
|
+
return useCallback<any>(
|
|
1681
|
+
(...args: any[]) => ref.current?.(...args),
|
|
1682
|
+
[]
|
|
1683
|
+
)
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
export function usePrevious<T> (value: T): T | undefined {
|
|
1687
|
+
const ref = useRef<T | undefined>()
|
|
1688
|
+
const prev = ref.current
|
|
1689
|
+
ref.current = value
|
|
1690
|
+
return prev
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
// ============================================================
|
|
1694
|
+
// component helpers
|
|
1695
|
+
// ============================================================
|
|
1696
|
+
|
|
1697
|
+
export function wrapChildren (children: ReactNode, { hasVarDec, varContext, textPassThrough }: WrapChildrenConfig) {
|
|
1698
|
+
if (textPassThrough) {
|
|
1699
|
+
children = <TextPassThroughContext.Provider value={textPassThrough} key='textPassThroughWrap'>{children}</TextPassThroughContext.Provider>
|
|
1700
|
+
}
|
|
1701
|
+
if (hasVarDec && varContext) {
|
|
1702
|
+
children = <VarContext.Provider value={varContext} key='varContextWrap'>{children}</VarContext.Provider>
|
|
1703
|
+
}
|
|
1704
|
+
return children
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
export function renderImage (
|
|
1708
|
+
imageProps: ImageProps,
|
|
1709
|
+
enableFastImage = true
|
|
1710
|
+
) {
|
|
1711
|
+
let Component = Image
|
|
1712
|
+
if (enableFastImage) {
|
|
1713
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
1714
|
+
const fastImageModule = require('@d11/react-native-fast-image')
|
|
1715
|
+
Component = fastImageModule.default || fastImageModule
|
|
1716
|
+
}
|
|
1717
|
+
return createElement(Component, imageProps)
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
export function flatGesture (gestures: Array<GestureHandler> = []) {
|
|
1721
|
+
return (gestures && gestures.flatMap((gesture: GestureHandler) => {
|
|
1722
|
+
if (gesture && gesture.nodeRefs) {
|
|
1723
|
+
return gesture.nodeRefs
|
|
1724
|
+
.map((item: { getNodeInstance: () => any }) => item.getNodeInstance()?.instance?.gestureRef || {})
|
|
1725
|
+
}
|
|
1726
|
+
if (gesture && (gesture.current || gesture.handlerTag !== undefined)) {
|
|
1727
|
+
return [gesture]
|
|
1728
|
+
}
|
|
1729
|
+
return []
|
|
1730
|
+
})) || []
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
export function getCurrentPage (pageId: number | null | undefined) {
|
|
1734
|
+
if (!global.getCurrentPages) return
|
|
1735
|
+
const pages = global.getCurrentPages()
|
|
1736
|
+
return pages.find((page: any) => isFunction(page.getPageId) && page.getPageId() === pageId)
|
|
1737
|
+
}
|