@mpxjs/webpack-plugin 2.10.17-beta.11 → 2.10.17-beta.12
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/global.d.ts +14 -0
- package/lib/index.js +37 -3
- package/lib/platform/style/wx/index.js +48 -70
- package/lib/runtime/components/react/context.ts +3 -6
- package/lib/runtime/components/react/dist/context.d.ts +2 -5
- package/lib/runtime/components/react/dist/context.js +2 -1
- package/lib/runtime/components/react/dist/mpx-async-suspense.jsx +1 -2
- package/lib/runtime/components/react/dist/mpx-image.jsx +1 -1
- package/lib/runtime/components/react/dist/mpx-input.jsx +50 -48
- package/lib/runtime/components/react/dist/mpx-keyboard-avoiding-view.jsx +2 -15
- package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +78 -84
- package/lib/runtime/components/react/dist/mpx-sticky-header.jsx +20 -20
- package/lib/runtime/components/react/dist/mpx-textarea.jsx +0 -1
- package/lib/runtime/components/react/dist/utils.d.ts +9 -9
- package/lib/runtime/components/react/dist/utils.jsx +19 -23
- package/lib/runtime/components/react/mpx-async-suspense.tsx +1 -2
- package/lib/runtime/components/react/mpx-image.tsx +1 -1
- package/lib/runtime/components/react/mpx-input.tsx +54 -52
- package/lib/runtime/components/react/mpx-keyboard-avoiding-view.tsx +2 -15
- package/lib/runtime/components/react/mpx-scroll-view.tsx +114 -110
- package/lib/runtime/components/react/mpx-sticky-header.tsx +24 -24
- package/lib/runtime/components/react/mpx-textarea.tsx +0 -1
- package/lib/runtime/components/react/utils.tsx +20 -27
- package/lib/script-setup-compiler/index.js +2 -1
- package/lib/style-compiler/{strip-conditional.js → strip-conditional-loader.js} +15 -25
- package/package.json +1 -1
- package/lib/init.js +0 -3
package/lib/global.d.ts
CHANGED
|
@@ -11,6 +11,20 @@ declare module 'webpack' {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
declare global {
|
|
14
|
+
interface MpxWebpackPluginOptions {
|
|
15
|
+
style: {
|
|
16
|
+
cssCondition?: {
|
|
17
|
+
before?: boolean
|
|
18
|
+
after?: boolean
|
|
19
|
+
beforeExclude?: (string | RegExp)[]
|
|
20
|
+
afterExclude?: (string | RegExp)[]
|
|
21
|
+
legacy?: boolean
|
|
22
|
+
afterLegacy?: boolean
|
|
23
|
+
beforeLegacy?: boolean
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
14
28
|
type MpxLoaderContext<T> = webpack.LoaderContext<T> & {
|
|
15
29
|
getMpx(): MpxContext
|
|
16
30
|
}
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
require('./init')
|
|
4
3
|
const path = require('path')
|
|
5
4
|
const { ConcatSource, RawSource } = require('webpack').sources
|
|
6
5
|
const ResolveDependency = require('./dependencies/ResolveDependency')
|
|
@@ -59,6 +58,7 @@ const wxssLoaderPath = normalize.lib('wxss/index')
|
|
|
59
58
|
const wxmlLoaderPath = normalize.lib('wxml/loader')
|
|
60
59
|
const wxsLoaderPath = normalize.lib('wxs/loader')
|
|
61
60
|
const styleCompilerPath = normalize.lib('style-compiler/index')
|
|
61
|
+
const styleStripConditionalPath = normalize.lib('style-compiler/strip-conditional-loader')
|
|
62
62
|
const templateCompilerPath = normalize.lib('template-compiler/index')
|
|
63
63
|
const jsonCompilerPath = normalize.lib('json-compiler/index')
|
|
64
64
|
const jsonThemeCompilerPath = normalize.lib('json-compiler/theme')
|
|
@@ -79,7 +79,8 @@ const LoadAsyncChunkModule = require('./react/LoadAsyncChunkModule')
|
|
|
79
79
|
const ExternalModule = require('webpack/lib/ExternalModule')
|
|
80
80
|
const { RetryRuntimeModule, RetryRuntimeGlobal } = require('./dependencies/RetryRuntimeModule')
|
|
81
81
|
const checkVersionCompatibility = require('./utils/check-core-version-match')
|
|
82
|
-
const {
|
|
82
|
+
const { rewriteFSForCss, startFSStripForCss } = require('./style-compiler/strip-conditional-loader')
|
|
83
|
+
rewriteFSForCss()
|
|
83
84
|
checkVersionCompatibility()
|
|
84
85
|
|
|
85
86
|
const isProductionLikeMode = options => {
|
|
@@ -716,7 +717,6 @@ class MpxWebpackPlugin {
|
|
|
716
717
|
})
|
|
717
718
|
|
|
718
719
|
compiler.hooks.thisCompilation.tap('MpxWebpackPlugin', (compilation, { normalModuleFactory }) => {
|
|
719
|
-
registerStripCompilation(compilation)
|
|
720
720
|
compilation.warnings.push(...warnings)
|
|
721
721
|
compilation.errors.push(...errors)
|
|
722
722
|
const moduleGraph = compilation.moduleGraph
|
|
@@ -1934,9 +1934,42 @@ try {
|
|
|
1934
1934
|
normalModuleFactory.hooks.afterResolve.tap('MpxWebpackPlugin', ({ createData }) => {
|
|
1935
1935
|
const { queryObj } = parseRequest(createData.request)
|
|
1936
1936
|
const loaders = createData.loaders
|
|
1937
|
+
|
|
1938
|
+
// 样式 loader 类型检测和条件编译 loader 插入的工具函数
|
|
1939
|
+
const STYLE_LOADER_TYPES = ['stylus-loader', 'sass-loader', 'less-loader', 'css-loader', wxssLoaderPath]
|
|
1940
|
+
const injectStyleStripLoader = (loaders) => {
|
|
1941
|
+
// 检查是否已经存在 stripLoader
|
|
1942
|
+
const hasStripLoader = loaders.some(loader => {
|
|
1943
|
+
const loaderPath = toPosix(loader.loader)
|
|
1944
|
+
return loaderPath.includes('style-compiler/strip-conditional-loader')
|
|
1945
|
+
})
|
|
1946
|
+
if (hasStripLoader) {
|
|
1947
|
+
return
|
|
1948
|
+
}
|
|
1949
|
+
const loaderTypes = new Map(STYLE_LOADER_TYPES.map(type => [`node_modules/${type}`, -1]))
|
|
1950
|
+
loaders.forEach((loader, index) => {
|
|
1951
|
+
const currentLoader = toPosix(loader.loader)
|
|
1952
|
+
for (const [key] of loaderTypes) {
|
|
1953
|
+
if (currentLoader.includes(key)) {
|
|
1954
|
+
loaderTypes.set(key, index)
|
|
1955
|
+
break
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
})
|
|
1959
|
+
const targetIndex = STYLE_LOADER_TYPES
|
|
1960
|
+
.map(type => loaderTypes.get(`node_modules/${type}`))
|
|
1961
|
+
.find(index => index !== -1)
|
|
1962
|
+
|
|
1963
|
+
if (targetIndex !== undefined) {
|
|
1964
|
+
loaders.splice(targetIndex + 1, 0, { loader: styleStripConditionalPath })
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1937
1967
|
if (queryObj.mpx && queryObj.mpx !== MPX_PROCESSED_FLAG) {
|
|
1938
1968
|
const type = queryObj.type
|
|
1939
1969
|
const extract = queryObj.extract
|
|
1970
|
+
if (type === 'styles') {
|
|
1971
|
+
injectStyleStripLoader(loaders)
|
|
1972
|
+
}
|
|
1940
1973
|
|
|
1941
1974
|
switch (type) {
|
|
1942
1975
|
case 'styles':
|
|
@@ -1989,6 +2022,7 @@ try {
|
|
|
1989
2022
|
}
|
|
1990
2023
|
// mpxStyleOptions 为 mpx style 文件的标识,避免 Vue 文件插入 styleCompiler 后导致 vue scoped 样式隔离失效
|
|
1991
2024
|
if (isWeb(mpx.mode) && queryObj.mpxStyleOptions) {
|
|
2025
|
+
injectStyleStripLoader(loaders)
|
|
1992
2026
|
const firstLoader = loaders[0] ? toPosix(loaders[0].loader) : ''
|
|
1993
2027
|
const isPitcherRequest = firstLoader.includes('node_modules/vue-loader/lib/loaders/pitcher')
|
|
1994
2028
|
let cssLoaderIndex = -1
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const { hump2dash } = require('../../../utils/hump-dash')
|
|
2
2
|
const { parseValues } = require('../../../utils/string')
|
|
3
3
|
|
|
4
|
-
module.exports = function getSpec({ warn, error }) {
|
|
4
|
+
module.exports = function getSpec ({ warn, error }) {
|
|
5
5
|
// React Native 双端都不支持的 CSS property
|
|
6
6
|
const unsupportedPropExp = /^(white-space|text-overflow|animation|font-variant-caps|font-variant-numeric|font-variant-east-asian|font-variant-alternates|font-variant-ligatures|background-position|caret-color)$/
|
|
7
7
|
const unsupportedPropMode = {
|
|
@@ -33,8 +33,7 @@ module.exports = function getSpec({ warn, error }) {
|
|
|
33
33
|
}
|
|
34
34
|
// 值类型
|
|
35
35
|
const ValueType = {
|
|
36
|
-
|
|
37
|
-
length: 'length',
|
|
36
|
+
number: 'number',
|
|
38
37
|
color: 'color',
|
|
39
38
|
enum: 'enum'
|
|
40
39
|
}
|
|
@@ -64,27 +63,26 @@ module.exports = function getSpec({ warn, error }) {
|
|
|
64
63
|
'align-items': ['flex-start', 'flex-end', 'center', 'stretch', 'baseline'],
|
|
65
64
|
'align-self': ['auto', 'flex-start', 'flex-end', 'center', 'stretch', 'baseline'],
|
|
66
65
|
'justify-content': ['flex-start', 'flex-end', 'center', 'space-between', 'space-around', 'space-evenly'],
|
|
67
|
-
'background-size': ['contain', 'cover', 'auto', ValueType.
|
|
68
|
-
'background-position': ['left', 'right', 'top', 'bottom', 'center', ValueType.
|
|
66
|
+
'background-size': ['contain', 'cover', 'auto', ValueType.number],
|
|
67
|
+
'background-position': ['left', 'right', 'top', 'bottom', 'center', ValueType.number],
|
|
69
68
|
'background-repeat': ['no-repeat'],
|
|
70
|
-
width: ['auto', ValueType.
|
|
71
|
-
height: ['auto', ValueType.
|
|
72
|
-
'flex-basis': ['auto', ValueType.
|
|
73
|
-
margin: ['auto', ValueType.
|
|
74
|
-
'margin-top': ['auto', ValueType.
|
|
75
|
-
'margin-left': ['auto', ValueType.
|
|
76
|
-
'margin-bottom': ['auto', ValueType.
|
|
77
|
-
'margin-right': ['auto', ValueType.
|
|
78
|
-
'margin-horizontal': ['auto', ValueType.
|
|
79
|
-
'margin-vertical': ['auto', ValueType.
|
|
69
|
+
width: ['auto', ValueType.number],
|
|
70
|
+
height: ['auto', ValueType.number],
|
|
71
|
+
'flex-basis': ['auto', ValueType.number],
|
|
72
|
+
margin: ['auto', ValueType.number],
|
|
73
|
+
'margin-top': ['auto', ValueType.number],
|
|
74
|
+
'margin-left': ['auto', ValueType.number],
|
|
75
|
+
'margin-bottom': ['auto', ValueType.number],
|
|
76
|
+
'margin-right': ['auto', ValueType.number],
|
|
77
|
+
'margin-horizontal': ['auto', ValueType.number],
|
|
78
|
+
'margin-vertical': ['auto', ValueType.number]
|
|
80
79
|
}
|
|
81
80
|
// 获取值类型
|
|
82
81
|
const getValueType = (prop) => {
|
|
83
82
|
const propValueTypeRules = [
|
|
84
83
|
// 重要!!优先判断是不是枚举类型
|
|
85
84
|
[ValueType.enum, new RegExp('^(' + Object.keys(SUPPORTED_PROP_VAL_ARR).join('|') + ')$')],
|
|
86
|
-
[ValueType.
|
|
87
|
-
[ValueType.integer, /^((opacity|flex-grow|flex-shrink|z-index)|(.+-(index|opacity)))$/],
|
|
85
|
+
[ValueType.number, /^((opacity|flex-grow|flex-shrink|gap|left|right|top|bottom)|(.+-(width|height|left|right|top|bottom|radius|spacing|size|gap|index|offset|opacity)))$/],
|
|
88
86
|
[ValueType.color, /^(color|(.+-color))$/]
|
|
89
87
|
]
|
|
90
88
|
for (const rule of propValueTypeRules) {
|
|
@@ -104,86 +102,66 @@ module.exports = function getSpec({ warn, error }) {
|
|
|
104
102
|
|
|
105
103
|
const newVal = parseValues((str.match(totalVarExp)?.[1] || ''), ',')
|
|
106
104
|
if (newVal.length <= 1) return null // 没有 fallback
|
|
107
|
-
|
|
108
|
-
const fallback = newVal.slice(1).join(',').trim()
|
|
105
|
+
const fallback = newVal[1].trim()
|
|
109
106
|
// 如果 fallback 也是 var(),递归提取
|
|
110
107
|
if (totalVarExp.test(fallback)) return getDefaultValueFromVar(fallback, visited)
|
|
111
108
|
return fallback
|
|
112
109
|
}
|
|
113
110
|
|
|
114
|
-
// 属性值校验
|
|
115
|
-
// 返回值:
|
|
116
|
-
// - 通过:返回 true
|
|
117
|
-
// - 失败:返回 false
|
|
118
111
|
const verifyValues = ({ prop, value, selector }, isError = true) => {
|
|
119
112
|
prop = prop.trim()
|
|
120
|
-
|
|
113
|
+
value = value.trim()
|
|
121
114
|
const tips = isError ? error : warn
|
|
122
115
|
|
|
123
|
-
// CSS
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
// 校验阶段允许使用 fallback 作为最坏情况(避免 RN crash),但输出必须保留 rawValue
|
|
128
|
-
let valueForVerify = rawValue
|
|
129
|
-
|
|
130
|
-
if (cssVariableExp.test(valueForVerify)) {
|
|
131
|
-
const fallback = getDefaultValueFromVar(valueForVerify)
|
|
116
|
+
// 对于包含 CSS 变量的值,提取 fallback 值进行验证
|
|
117
|
+
if (cssVariableExp.test(value)) {
|
|
118
|
+
const fallback = getDefaultValueFromVar(value)
|
|
132
119
|
// undefined 表示检测到循环引用
|
|
133
120
|
if (fallback === undefined) {
|
|
134
|
-
tips(`CSS variable circular reference in fallback chain detected in ${selector} for property ${prop}, value: ${
|
|
121
|
+
tips(`CSS variable circular reference in fallback chain detected in ${selector} for property ${prop}, value: ${value}`)
|
|
135
122
|
return false
|
|
136
123
|
}
|
|
137
124
|
// null 表示没有 fallback,CSS 变量本身是合法的(运行时会解析)
|
|
138
125
|
if (fallback === null) {
|
|
139
126
|
return true
|
|
140
127
|
}
|
|
141
|
-
// 有 fallback
|
|
142
|
-
|
|
128
|
+
// 有 fallback 值,将 fallback 作为新的 value 继续后续验证流程
|
|
129
|
+
value = fallback
|
|
143
130
|
}
|
|
144
131
|
|
|
145
|
-
// calc()
|
|
146
|
-
if (calcExp.test(
|
|
132
|
+
// calc() 和 env() 跳过验证
|
|
133
|
+
if (calcExp.test(value) || envExp.test(value)) return true
|
|
147
134
|
const namedColor = ['transparent', 'aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige', 'bisque', 'black', 'blanchedalmond', 'blue', 'blueviolet', 'brown', 'burlywood', 'cadetblue', 'chartreuse', 'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson', 'cyan', 'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgreen', 'darkgrey', 'darkkhaki', 'darkmagenta', 'darkolivegreen', 'darkorange', 'darkorchid', 'darkred', 'darksalmon', 'darkseagreen', 'darkslateblue', 'darkslategrey', 'darkturquoise', 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', 'dimgrey', 'dodgerblue', 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro', 'ghostwhite', 'gold', 'goldenrod', 'gray', 'green', 'greenyellow', 'grey', 'honeydew', 'hotpink', 'indianred', 'indigo', 'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan', 'lightgoldenrodyellow', 'lightgray', 'lightgreen', 'lightgrey', 'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue', 'lightslategrey', 'lightsteelblue', 'lightyellow', 'lime', 'limegreen', 'linen', 'magenta', 'maroon', 'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple', 'mediumseagreen', 'mediumslateblue', 'mediumspringgreen', 'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream', 'mistyrose', 'moccasin', 'navajowhite', 'navy', 'oldlace', 'olive', 'olivedrab', 'orange', 'orangered', 'orchid', 'palegoldenrod', 'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip', 'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'purple', 'rebeccapurple', 'red', 'rosybrown', 'royalblue', 'saddlebrown', 'salmon', 'sandybrown', 'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue', 'slategray', 'snow', 'springgreen', 'steelblue', 'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat', 'white', 'whitesmoke', 'yellow', 'yellowgreen']
|
|
148
135
|
const valueExp = {
|
|
149
|
-
|
|
150
|
-
length: /^((-?(\d+(\.\d+)?|\.\d+))(rpx|px|%|vw|vh)?|hairlineWidth)$/,
|
|
136
|
+
number: /^((-?(\d+(\.\d+)?|\.\d+))(rpx|px|%|vw|vh)?|hairlineWidth)$/,
|
|
151
137
|
color: new RegExp(('^(' + namedColor.join('|') + ')$') + '|(^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$)|^(rgb|rgba|hsl|hsla|hwb)\\(.+\\)$')
|
|
152
138
|
}
|
|
153
139
|
const type = getValueType(prop)
|
|
154
140
|
const tipsType = (type) => {
|
|
155
141
|
const info = {
|
|
156
|
-
[ValueType.length]: '2rpx,10%,30rpx',
|
|
157
142
|
[ValueType.color]: 'rgb,rgba,hsl,hsla,hwb,named color,#000000',
|
|
158
143
|
[ValueType.enum]: `${SUPPORTED_PROP_VAL_ARR[prop]?.join(',')}`
|
|
159
144
|
}
|
|
160
|
-
tips(`Value of ${prop} in ${selector} should be ${type}${info[type] ? `, eg ${info[type]}` : ''}, received [${
|
|
145
|
+
tips(`Value of ${prop} in ${selector} should be ${type}${info[type] ? `, eg ${info[type]}` : ''}, received [${value}], please check again!`)
|
|
161
146
|
}
|
|
162
147
|
switch (type) {
|
|
163
|
-
case ValueType.
|
|
164
|
-
if (!valueExp.
|
|
165
|
-
tipsType(type)
|
|
166
|
-
return false
|
|
167
|
-
}
|
|
168
|
-
return true
|
|
169
|
-
}
|
|
170
|
-
case ValueType.integer: {
|
|
171
|
-
if (!valueExp.integer.test(valueForVerify)) {
|
|
148
|
+
case ValueType.number: {
|
|
149
|
+
if (!valueExp.number.test(value)) {
|
|
172
150
|
tipsType(type)
|
|
173
151
|
return false
|
|
174
152
|
}
|
|
175
153
|
return true
|
|
176
154
|
}
|
|
177
155
|
case ValueType.color: {
|
|
178
|
-
if (!valueExp.color.test(
|
|
156
|
+
if (!valueExp.color.test(value)) {
|
|
179
157
|
tipsType(type)
|
|
180
158
|
return false
|
|
181
159
|
}
|
|
182
160
|
return true
|
|
183
161
|
}
|
|
184
162
|
case ValueType.enum: {
|
|
185
|
-
const isIn = SUPPORTED_PROP_VAL_ARR[prop].includes(
|
|
186
|
-
const isType = Object.keys(valueExp).some(item => valueExp[item].test(
|
|
163
|
+
const isIn = SUPPORTED_PROP_VAL_ARR[prop].includes(value)
|
|
164
|
+
const isType = Object.keys(valueExp).some(item => valueExp[item].test(value) && SUPPORTED_PROP_VAL_ARR[prop].includes(ValueType[item]))
|
|
187
165
|
if (!isIn && !isType) {
|
|
188
166
|
tipsType(type)
|
|
189
167
|
return false
|
|
@@ -454,23 +432,23 @@ module.exports = function getSpec({ warn, error }) {
|
|
|
454
432
|
case 'skew':
|
|
455
433
|
case 'translate3d': // x y 支持 z不支持
|
|
456
434
|
case 'scale3d': // x y 支持 z不支持
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
}
|
|
465
|
-
const xyz = ['X', 'Y', 'Z']
|
|
466
|
-
transform.push(...vals.map((v, index) => {
|
|
467
|
-
if (key !== 'rotate' && index > 1) {
|
|
468
|
-
unsupportedPropError({ prop: `${key}Z`, value, selector }, { mode })
|
|
469
|
-
}
|
|
470
|
-
return { [`${key}${xyz[index] || ''}`]: v.trim() }
|
|
471
|
-
}))
|
|
472
|
-
break
|
|
435
|
+
{
|
|
436
|
+
// 2 个以上的值处理
|
|
437
|
+
key = key.replace('3d', '')
|
|
438
|
+
const vals = parseValues(val, ',').splice(0, 3)
|
|
439
|
+
// scale(.5) === scaleX(.5) scaleY(.5)
|
|
440
|
+
if (vals.length === 1 && key === 'scale') {
|
|
441
|
+
vals.push(vals[0])
|
|
473
442
|
}
|
|
443
|
+
const xyz = ['X', 'Y', 'Z']
|
|
444
|
+
transform.push(...vals.map((v, index) => {
|
|
445
|
+
if (key !== 'rotate' && index > 1) {
|
|
446
|
+
unsupportedPropError({ prop: `${key}Z`, value, selector }, { mode })
|
|
447
|
+
}
|
|
448
|
+
return { [`${key}${xyz[index] || ''}`]: v.trim() }
|
|
449
|
+
}))
|
|
450
|
+
break
|
|
451
|
+
}
|
|
474
452
|
case 'translateZ':
|
|
475
453
|
case 'scaleZ':
|
|
476
454
|
case 'rotate3d': // x y z angle
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createContext, Dispatch, MutableRefObject, SetStateAction } from 'react'
|
|
2
|
-
import
|
|
2
|
+
import { NativeSyntheticEvent, Animated, ScaledSize } from 'react-native'
|
|
3
3
|
import { noop } from '@mpxjs/utils'
|
|
4
4
|
|
|
5
5
|
export type LabelContextValue = MutableRefObject<{
|
|
@@ -12,10 +12,7 @@ export type KeyboardAvoidContextValue = MutableRefObject<{
|
|
|
12
12
|
adjustPosition: boolean
|
|
13
13
|
holdKeyboard?: boolean
|
|
14
14
|
keyboardHeight?: number
|
|
15
|
-
/** @internal bindfocus 异步延迟上报方法 */
|
|
16
15
|
onKeyboardShow?: () => void
|
|
17
|
-
/** @internal 多个 Input 切换聚焦场景标记位 */
|
|
18
|
-
readyToShow?: boolean
|
|
19
16
|
} | null>
|
|
20
17
|
|
|
21
18
|
export interface GroupValue {
|
|
@@ -52,7 +49,7 @@ export interface PortalContextValue {
|
|
|
52
49
|
|
|
53
50
|
export interface ScrollViewContextValue {
|
|
54
51
|
gestureRef: React.RefObject<any> | null
|
|
55
|
-
scrollOffset: Animated.Value
|
|
52
|
+
scrollOffset: Animated.Value
|
|
56
53
|
}
|
|
57
54
|
|
|
58
55
|
export interface RouteContextValue {
|
|
@@ -92,7 +89,7 @@ export const SwiperContext = createContext({})
|
|
|
92
89
|
|
|
93
90
|
export const KeyboardAvoidContext = createContext<KeyboardAvoidContextValue | null>(null)
|
|
94
91
|
|
|
95
|
-
export const ScrollViewContext = createContext<ScrollViewContextValue>({ gestureRef: null, scrollOffset:
|
|
92
|
+
export const ScrollViewContext = createContext<ScrollViewContextValue>({ gestureRef: null, scrollOffset: new Animated.Value(0) })
|
|
96
93
|
|
|
97
94
|
export const PortalContext = createContext<PortalContextValue>(null as any)
|
|
98
95
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Dispatch, MutableRefObject, SetStateAction } from 'react';
|
|
2
|
-
import
|
|
2
|
+
import { NativeSyntheticEvent, Animated, ScaledSize } from 'react-native';
|
|
3
3
|
export type LabelContextValue = MutableRefObject<{
|
|
4
4
|
triggerChange: (evt: NativeSyntheticEvent<TouchEvent>) => void;
|
|
5
5
|
}>;
|
|
@@ -9,10 +9,7 @@ export type KeyboardAvoidContextValue = MutableRefObject<{
|
|
|
9
9
|
adjustPosition: boolean;
|
|
10
10
|
holdKeyboard?: boolean;
|
|
11
11
|
keyboardHeight?: number;
|
|
12
|
-
/** @internal bindfocus 异步延迟上报方法 */
|
|
13
12
|
onKeyboardShow?: () => void;
|
|
14
|
-
/** @internal 多个 Input 切换聚焦场景标记位 */
|
|
15
|
-
readyToShow?: boolean;
|
|
16
13
|
} | null>;
|
|
17
14
|
export interface GroupValue {
|
|
18
15
|
[key: string]: {
|
|
@@ -48,7 +45,7 @@ export interface PortalContextValue {
|
|
|
48
45
|
}
|
|
49
46
|
export interface ScrollViewContextValue {
|
|
50
47
|
gestureRef: React.RefObject<any> | null;
|
|
51
|
-
scrollOffset: Animated.Value
|
|
48
|
+
scrollOffset: Animated.Value;
|
|
52
49
|
}
|
|
53
50
|
export interface RouteContextValue {
|
|
54
51
|
pageId: number;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createContext } from 'react';
|
|
2
|
+
import { Animated } from 'react-native';
|
|
2
3
|
import { noop } from '@mpxjs/utils';
|
|
3
4
|
export const MovableAreaContext = createContext({ width: 0, height: 0 });
|
|
4
5
|
export const FormContext = createContext(null);
|
|
@@ -11,7 +12,7 @@ export const IntersectionObserverContext = createContext(null);
|
|
|
11
12
|
export const RouteContext = createContext(null);
|
|
12
13
|
export const SwiperContext = createContext({});
|
|
13
14
|
export const KeyboardAvoidContext = createContext(null);
|
|
14
|
-
export const ScrollViewContext = createContext({ gestureRef: null, scrollOffset:
|
|
15
|
+
export const ScrollViewContext = createContext({ gestureRef: null, scrollOffset: new Animated.Value(0) });
|
|
15
16
|
export const PortalContext = createContext(null);
|
|
16
17
|
export const StickyContext = createContext({ registerStickyHeader: noop, unregisterStickyHeader: noop });
|
|
17
18
|
export const ProviderContext = createContext(null);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState, useEffect, useCallback, useRef, createElement } from 'react';
|
|
2
2
|
import { View, Image, StyleSheet, Text, TouchableOpacity } from 'react-native';
|
|
3
|
+
import FastImage from '@d11/react-native-fast-image';
|
|
3
4
|
const asyncChunkMap = new Map();
|
|
4
5
|
const styles = StyleSheet.create({
|
|
5
6
|
container: {
|
|
@@ -62,8 +63,6 @@ const DefaultFallback = ({ onReload }) => {
|
|
|
62
63
|
</View>);
|
|
63
64
|
};
|
|
64
65
|
const DefaultLoading = () => {
|
|
65
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
66
|
-
const FastImage = require('@d11/react-native-fast-image').default;
|
|
67
66
|
return (<View style={styles.container}>
|
|
68
67
|
<FastImage style={styles.loadingImage} source={{
|
|
69
68
|
uri: 'https://dpubstatic.udache.com/static/dpubimg/439jiCVOtNOnEv9F2LaDs_loading.gif'
|
|
@@ -104,7 +104,7 @@ const Image = forwardRef((props, ref) => {
|
|
|
104
104
|
setLoaded(true);
|
|
105
105
|
}
|
|
106
106
|
};
|
|
107
|
-
const { hasPositionFixed, hasSelfPercent, normalStyle, setWidth, setHeight } = useTransformStyle(styleObj, { enableVar,
|
|
107
|
+
const { hasPositionFixed, hasSelfPercent, normalStyle, setWidth, setHeight } = useTransformStyle(styleObj, { enableVar, isTransformBorderRadiusPercent: isAndroid && !isSvg && !isLayoutMode, externalVarContext, parentFontSize, parentWidth, parentHeight });
|
|
108
108
|
const { layoutRef, layoutStyle, layoutProps } = useLayout({
|
|
109
109
|
props,
|
|
110
110
|
hasSelfPercent,
|
|
@@ -74,14 +74,15 @@ const Input = forwardRef((props, ref) => {
|
|
|
74
74
|
};
|
|
75
75
|
const defaultValue = parseValue(value);
|
|
76
76
|
const textAlignVertical = multiline ? 'top' : 'auto';
|
|
77
|
-
const isAutoFocus = !!autoFocus || !!focus;
|
|
78
77
|
const tmpValue = useRef(defaultValue);
|
|
79
78
|
const cursorIndex = useRef(0);
|
|
80
79
|
const lineCount = useRef(0);
|
|
81
80
|
const [inputValue, setInputValue] = useState(defaultValue);
|
|
82
81
|
const [contentHeight, setContentHeight] = useState(0);
|
|
83
82
|
const [selection, setSelection] = useState({ start: -1, end: tmpValue.current.length });
|
|
84
|
-
const styleObj = extendObject({ padding: 0, backgroundColor: '#fff' }, style, multiline && autoHeight
|
|
83
|
+
const styleObj = extendObject({ padding: 0, backgroundColor: '#fff' }, style, multiline && autoHeight
|
|
84
|
+
? { height: 'auto', minHeight: Math.max(style?.minHeight || 35, contentHeight) }
|
|
85
|
+
: {});
|
|
85
86
|
const { hasPositionFixed, hasSelfPercent, normalStyle, setWidth, setHeight } = useTransformStyle(styleObj, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight });
|
|
86
87
|
const nodeRef = useRef(null);
|
|
87
88
|
useNodesRef(props, ref, nodeRef, {
|
|
@@ -143,55 +144,59 @@ const Input = forwardRef((props, ref) => {
|
|
|
143
144
|
};
|
|
144
145
|
const setKeyboardAvoidContext = () => {
|
|
145
146
|
if (keyboardAvoid) {
|
|
146
|
-
keyboardAvoid.current = {
|
|
147
|
+
keyboardAvoid.current = {
|
|
148
|
+
cursorSpacing,
|
|
149
|
+
ref: nodeRef,
|
|
150
|
+
adjustPosition,
|
|
151
|
+
holdKeyboard,
|
|
152
|
+
// fix: iOS 会在 onFocus 之前触发 keyboardWillShow 并且赋值 keyboardHeight
|
|
153
|
+
// 这里手动同步下 keyboardHeight,防止 onFocus setKeyboardAvoidContext 删掉 keyboardHeight
|
|
154
|
+
keyboardHeight: keyboardAvoid?.current?.keyboardHeight
|
|
155
|
+
};
|
|
147
156
|
}
|
|
148
157
|
};
|
|
149
158
|
const onTouchStart = () => {
|
|
150
|
-
//
|
|
151
|
-
// auto-focus/focus 不会触发而是在 useEffect 中初始化
|
|
159
|
+
// sometimes the focus event occurs later than the keyboardWillShow event
|
|
152
160
|
setKeyboardAvoidContext();
|
|
153
161
|
};
|
|
154
162
|
const onTouchEnd = (evt) => {
|
|
155
163
|
evt.nativeEvent.origin = 'input';
|
|
156
164
|
};
|
|
157
165
|
const onFocus = (evt) => {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
166
|
+
setKeyboardAvoidContext();
|
|
167
|
+
if (bindfocus) {
|
|
168
|
+
const focusAction = () => {
|
|
169
|
+
bindfocus(getCustomEvent('focus', evt, {
|
|
170
|
+
detail: {
|
|
171
|
+
value: tmpValue.current || '',
|
|
172
|
+
height: keyboardAvoid?.current?.keyboardHeight
|
|
173
|
+
},
|
|
174
|
+
layoutRef
|
|
175
|
+
}, props));
|
|
176
|
+
if (keyboardAvoid?.current?.onKeyboardShow) {
|
|
177
|
+
keyboardAvoid.current.onKeyboardShow = undefined;
|
|
178
|
+
}
|
|
179
|
+
if (keyboardAvoid?.current?.keyboardHeight) {
|
|
180
|
+
keyboardAvoid.current.keyboardHeight = undefined;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
if (keyboardAvoid?.current) {
|
|
184
|
+
// 有 keyboardAvoiding
|
|
185
|
+
if (keyboardAvoid.current.keyboardHeight) {
|
|
186
|
+
// iOS: keyboard 获取高度时机 keyboardWillShow 在 input focus 之前,可以立即执行
|
|
187
|
+
focusAction();
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
// Android,Harmony: keyboard 获取高度时机 keyboardDidShow 在 input focus 之后,需要延迟回调
|
|
191
|
+
evt.persist();
|
|
192
|
+
keyboardAvoid.current.onKeyboardShow = focusAction;
|
|
193
|
+
}
|
|
182
194
|
}
|
|
183
195
|
else {
|
|
184
|
-
//
|
|
185
|
-
|
|
186
|
-
// - Android 手动点击聚焦/自动聚焦 都一样
|
|
187
|
-
evt.persist();
|
|
188
|
-
keyboardAvoid.current.onKeyboardShow = focusAction;
|
|
196
|
+
// 无 keyboardAvoiding,直接执行 focus 回调
|
|
197
|
+
focusAction();
|
|
189
198
|
}
|
|
190
199
|
}
|
|
191
|
-
else {
|
|
192
|
-
// 兜底:无 keyboardAvoiding 直接执行 focus 回调
|
|
193
|
-
focusAction();
|
|
194
|
-
}
|
|
195
200
|
};
|
|
196
201
|
const onBlur = (evt) => {
|
|
197
202
|
bindblur && bindblur(getCustomEvent('blur', evt, {
|
|
@@ -265,21 +270,18 @@ const Input = forwardRef((props, ref) => {
|
|
|
265
270
|
};
|
|
266
271
|
}, []);
|
|
267
272
|
useEffect(() => {
|
|
268
|
-
if (
|
|
269
|
-
// auto-focus/focus=true 初始化 keyboardAvoidContext
|
|
273
|
+
if (focus) {
|
|
270
274
|
setKeyboardAvoidContext();
|
|
271
275
|
}
|
|
272
|
-
}, [
|
|
276
|
+
}, [focus]);
|
|
273
277
|
useUpdateEffect(() => {
|
|
274
278
|
if (!nodeRef?.current) {
|
|
275
279
|
return;
|
|
276
280
|
}
|
|
277
|
-
|
|
278
|
-
// 后续更新需要手动调用 focus/blur 方法,和微信小程序对齐
|
|
279
|
-
isAutoFocus
|
|
281
|
+
focus
|
|
280
282
|
? nodeRef.current?.focus()
|
|
281
283
|
: nodeRef.current?.blur();
|
|
282
|
-
}, [
|
|
284
|
+
}, [focus]);
|
|
283
285
|
const innerProps = useInnerProps(extendObject({}, props, layoutProps, {
|
|
284
286
|
ref: nodeRef,
|
|
285
287
|
style: extendObject({}, normalStyle, layoutStyle),
|
|
@@ -291,10 +293,10 @@ const Input = forwardRef((props, ref) => {
|
|
|
291
293
|
value: inputValue,
|
|
292
294
|
maxLength: maxlength === -1 ? undefined : maxlength,
|
|
293
295
|
editable: !disabled,
|
|
294
|
-
autoFocus:
|
|
296
|
+
autoFocus: !!autoFocus || !!focus,
|
|
295
297
|
selection: selectionStart > -1 || typeof cursor === 'number' ? selection : undefined,
|
|
296
298
|
selectionColor: cursorColor,
|
|
297
|
-
blurOnSubmit: multiline
|
|
299
|
+
blurOnSubmit: !multiline && !confirmHold,
|
|
298
300
|
underlineColorAndroid: 'rgba(0,0,0,0)',
|
|
299
301
|
textAlignVertical: textAlignVertical,
|
|
300
302
|
placeholderTextColor: placeholderStyle?.color,
|
|
@@ -306,7 +308,7 @@ const Input = forwardRef((props, ref) => {
|
|
|
306
308
|
onChange,
|
|
307
309
|
onSelectionChange,
|
|
308
310
|
onContentSizeChange,
|
|
309
|
-
onSubmitEditing: bindconfirm && onSubmitEditing
|
|
311
|
+
onSubmitEditing: bindconfirm && !multiline && onSubmitEditing
|
|
310
312
|
}, !!multiline && confirmType === 'return' ? {} : { enterKeyHint: confirmType }), [
|
|
311
313
|
'type',
|
|
312
314
|
'password',
|
|
@@ -28,20 +28,11 @@ const KeyboardAvoidingView = ({ children, style, contentContainerStyle }) => {
|
|
|
28
28
|
isShow.current = false;
|
|
29
29
|
if (keyboardAvoid?.current) {
|
|
30
30
|
const inputRef = keyboardAvoid.current.ref?.current;
|
|
31
|
-
if (inputRef && inputRef.isFocused()
|
|
31
|
+
if (inputRef && inputRef.isFocused()) {
|
|
32
32
|
// 修复 Android 点击键盘收起按钮时当前 input 没触发失焦的问题
|
|
33
|
-
// keyboardAvoid.current.readyToShow = true 表示聚焦到了新的输入框,不需要手动触发失焦
|
|
34
33
|
inputRef.blur();
|
|
35
34
|
}
|
|
36
|
-
|
|
37
|
-
// 修复部分 Android 机型可能时序问题:当从 input 已聚焦状态,聚焦到另一个 input 时,可能时序:
|
|
38
|
-
// - 新的 Input `onTouchStart` -> 新的 Input `onFocus` -> 旧输入框键盘 `keyboardDidHide` -> 新输入框键盘 `keyboardDidShow`
|
|
39
|
-
// - 此时 keyboardAvoid.current 如果清空 null,会导致新输入框键盘 `keyboardDidShow` 回调 keybaordAvoding 执行失败。
|
|
40
|
-
// 修复方案:
|
|
41
|
-
// 如果出现时序问题,那么新的 Input `onFocus` 会更早执行,那么 `keyboardAvoid.current.onKeyboardShow` 存在,
|
|
42
|
-
// 那么不应该重置为 null,反之,说明时正常情况,应当重置为 null。
|
|
43
|
-
keyboardAvoid.current = null;
|
|
44
|
-
}
|
|
35
|
+
keyboardAvoid.current = null;
|
|
45
36
|
}
|
|
46
37
|
cancelAnimation(offset);
|
|
47
38
|
offset.value = withTiming(0, { duration, easing });
|
|
@@ -58,10 +49,6 @@ const KeyboardAvoidingView = ({ children, style, contentContainerStyle }) => {
|
|
|
58
49
|
useEffect(() => {
|
|
59
50
|
let subscriptions = [];
|
|
60
51
|
function keybaordAvoding(evt) {
|
|
61
|
-
if (keyboardAvoid?.current?.readyToShow) {
|
|
62
|
-
// 重置标记位
|
|
63
|
-
keyboardAvoid.current.readyToShow = false;
|
|
64
|
-
}
|
|
65
52
|
if (!keyboardAvoid?.current || isShow.current) {
|
|
66
53
|
return;
|
|
67
54
|
}
|