@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.
Files changed (27) hide show
  1. package/lib/global.d.ts +14 -0
  2. package/lib/index.js +37 -3
  3. package/lib/platform/style/wx/index.js +48 -70
  4. package/lib/runtime/components/react/context.ts +3 -6
  5. package/lib/runtime/components/react/dist/context.d.ts +2 -5
  6. package/lib/runtime/components/react/dist/context.js +2 -1
  7. package/lib/runtime/components/react/dist/mpx-async-suspense.jsx +1 -2
  8. package/lib/runtime/components/react/dist/mpx-image.jsx +1 -1
  9. package/lib/runtime/components/react/dist/mpx-input.jsx +50 -48
  10. package/lib/runtime/components/react/dist/mpx-keyboard-avoiding-view.jsx +2 -15
  11. package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +78 -84
  12. package/lib/runtime/components/react/dist/mpx-sticky-header.jsx +20 -20
  13. package/lib/runtime/components/react/dist/mpx-textarea.jsx +0 -1
  14. package/lib/runtime/components/react/dist/utils.d.ts +9 -9
  15. package/lib/runtime/components/react/dist/utils.jsx +19 -23
  16. package/lib/runtime/components/react/mpx-async-suspense.tsx +1 -2
  17. package/lib/runtime/components/react/mpx-image.tsx +1 -1
  18. package/lib/runtime/components/react/mpx-input.tsx +54 -52
  19. package/lib/runtime/components/react/mpx-keyboard-avoiding-view.tsx +2 -15
  20. package/lib/runtime/components/react/mpx-scroll-view.tsx +114 -110
  21. package/lib/runtime/components/react/mpx-sticky-header.tsx +24 -24
  22. package/lib/runtime/components/react/mpx-textarea.tsx +0 -1
  23. package/lib/runtime/components/react/utils.tsx +20 -27
  24. package/lib/script-setup-compiler/index.js +2 -1
  25. package/lib/style-compiler/{strip-conditional.js → strip-conditional-loader.js} +15 -25
  26. package/package.json +1 -1
  27. 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 { startFSStripForCss, registerStripCompilation } = require('./style-compiler/strip-conditional')
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
- integer: 'integer',
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.length],
68
- 'background-position': ['left', 'right', 'top', 'bottom', 'center', ValueType.length],
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.length],
71
- height: ['auto', ValueType.length],
72
- 'flex-basis': ['auto', ValueType.length],
73
- margin: ['auto', ValueType.length],
74
- 'margin-top': ['auto', ValueType.length],
75
- 'margin-left': ['auto', ValueType.length],
76
- 'margin-bottom': ['auto', ValueType.length],
77
- 'margin-right': ['auto', ValueType.length],
78
- 'margin-horizontal': ['auto', ValueType.length],
79
- 'margin-vertical': ['auto', ValueType.length]
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.length, /^((gap|left|right|top|bottom)|(.+-(width|height|left|right|top|bottom|radius|spacing|size|gap|offset)))$/],
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
- // fallback 可能本身包含逗号(如多 font-family 兜底、渐变等),这里取第2段及之后并 join 回去
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
- const rawValue = value.trim()
113
+ value = value.trim()
121
114
  const tips = isError ? error : warn
122
115
 
123
- // CSS 自定义属性(--xxx)是变量定义,不属于 RN 样式属性:
124
- // 不能按 `-height/-color` 等后缀推断类型去校验,否则会把变量定义错误过滤,导致运行时 var() 取值失败
125
- if (/^--/.test(prop)) return true
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: ${rawValue}`)
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 值:使用 fallback 继续做值校验
142
- valueForVerify = fallback.trim()
128
+ // 有 fallback 值,将 fallback 作为新的 value 继续后续验证流程
129
+ value = fallback
143
130
  }
144
131
 
145
- // calc() / env() 跳过值校验,但保留 rawValue 输出
146
- if (calcExp.test(valueForVerify) || envExp.test(valueForVerify)) return true
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
- integer: /^(-?(\d+(\.\d+)?|\.\d+))$/,
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 [${rawValue}], please check again!`)
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.length: {
164
- if (!valueExp.length.test(valueForVerify)) {
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(valueForVerify)) {
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(valueForVerify)
186
- const isType = Object.keys(valueExp).some(item => valueExp[item].test(valueForVerify) && SUPPORTED_PROP_VAL_ARR[prop].includes(ValueType[item]))
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
- // 2 个以上的值处理
459
- key = key.replace('3d', '')
460
- const vals = parseValues(val, ',').splice(0, 3)
461
- // scale(.5) === scaleX(.5) scaleY(.5)
462
- if (vals.length === 1 && key === 'scale') {
463
- vals.push(vals[0])
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 type { NativeSyntheticEvent, Animated, ScaledSize } from 'react-native'
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 | null
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: null })
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 type { NativeSyntheticEvent, Animated, ScaledSize } from 'react-native';
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 | null;
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: null });
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, transformRadiusPercent: isAndroid && !isSvg && !isLayoutMode, externalVarContext, parentFontSize, parentWidth, parentHeight });
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 ? { height: 'auto' } : {});
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 = { cursorSpacing, ref: nodeRef, adjustPosition, holdKeyboard, readyToShow: true };
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
- // 手动聚焦时初始化 keyboardAvoid 上下文
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
- if (!keyboardAvoid?.current) {
159
- // Android:从一个正聚焦状态 input,聚焦到另一个新的 input 时,正常会触发如下时序:
160
- // 新的 Input `onTouchStart` -> 旧输入框键盘 `keyboardDidHide` -> 新的 Input `onFocus`
161
- // 导致这里的 keyboardAvoid.current 为 null,所以需要判空重新初始化。
162
- setKeyboardAvoidContext();
163
- }
164
- const focusAction = () => {
165
- bindfocus?.(getCustomEvent('focus', evt, {
166
- detail: {
167
- value: tmpValue.current || '',
168
- height: keyboardAvoid?.current?.keyboardHeight
169
- },
170
- layoutRef
171
- }, props));
172
- if (keyboardAvoid?.current?.onKeyboardShow) {
173
- keyboardAvoid.current.onKeyboardShow = undefined;
174
- }
175
- };
176
- if (keyboardAvoid?.current) {
177
- // keyboardAvoiding
178
- if (keyboardAvoid.current.keyboardHeight) {
179
- // 仅以下场景触发顺序:先 keyboardWillShow 获取高度 -> 后 onFocus,可以立即执行
180
- // - iOS + 手动点击聚焦
181
- focusAction();
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
- // 其他场景触发顺序:先 onFocus -> 后 keyboardWillShow 获取高度 -> 执行回调
185
- // - iOS + auto-focus/focus=true 自动聚焦
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 (isAutoFocus) {
269
- // auto-focus/focus=true 初始化 keyboardAvoidContext
273
+ if (focus) {
270
274
  setKeyboardAvoidContext();
271
275
  }
272
- }, [isAutoFocus]);
276
+ }, [focus]);
273
277
  useUpdateEffect(() => {
274
278
  if (!nodeRef?.current) {
275
279
  return;
276
280
  }
277
- // RN autoFocus 属性仅在初次渲染时生效
278
- // 后续更新需要手动调用 focus/blur 方法,和微信小程序对齐
279
- isAutoFocus
281
+ focus
280
282
  ? nodeRef.current?.focus()
281
283
  : nodeRef.current?.blur();
282
- }, [isAutoFocus]);
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: isAutoFocus,
296
+ autoFocus: !!autoFocus || !!focus,
295
297
  selection: selectionStart > -1 || typeof cursor === 'number' ? selection : undefined,
296
298
  selectionColor: cursorColor,
297
- blurOnSubmit: multiline ? confirmType !== 'return' : !confirmHold,
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() && !keyboardAvoid.current.readyToShow) {
31
+ if (inputRef && inputRef.isFocused()) {
32
32
  // 修复 Android 点击键盘收起按钮时当前 input 没触发失焦的问题
33
- // keyboardAvoid.current.readyToShow = true 表示聚焦到了新的输入框,不需要手动触发失焦
34
33
  inputRef.blur();
35
34
  }
36
- if (!keyboardAvoid.current.onKeyboardShow) {
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
  }