@mpxjs/webpack-plugin 2.10.3-beta.1 → 2.10.3-beta.10

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 (24) hide show
  1. package/lib/index.js +71 -51
  2. package/lib/runtime/components/react/dist/mpx-input.jsx +14 -7
  3. package/lib/runtime/components/react/dist/mpx-keyboard-avoiding-view.jsx +23 -30
  4. package/lib/runtime/components/react/dist/mpx-swiper-item.jsx +2 -2
  5. package/lib/runtime/components/react/dist/mpx-swiper.jsx +2 -2
  6. package/lib/runtime/components/react/dist/mpx-web-view.jsx +4 -1
  7. package/lib/runtime/components/react/mpx-input.tsx +16 -7
  8. package/lib/runtime/components/react/mpx-keyboard-avoiding-view.tsx +30 -41
  9. package/lib/runtime/components/react/mpx-swiper-item.tsx +2 -2
  10. package/lib/runtime/components/react/mpx-swiper.tsx +4 -2
  11. package/lib/runtime/components/react/mpx-web-view.tsx +4 -1
  12. package/lib/runtime/components/web/mpx-titlebar.vue +243 -0
  13. package/lib/template-compiler/compiler.js +8 -1
  14. package/lib/web/processMainScript.js +3 -1
  15. package/package.json +1 -1
  16. package/lib/runtime/components/react/dist/mpx-icon/icons/cancel.png +0 -0
  17. package/lib/runtime/components/react/dist/mpx-icon/icons/clear.png +0 -0
  18. package/lib/runtime/components/react/dist/mpx-icon/icons/download.png +0 -0
  19. package/lib/runtime/components/react/dist/mpx-icon/icons/info.png +0 -0
  20. package/lib/runtime/components/react/dist/mpx-icon/icons/search.png +0 -0
  21. package/lib/runtime/components/react/dist/mpx-icon/icons/success.png +0 -0
  22. package/lib/runtime/components/react/dist/mpx-icon/icons/success_no_circle.png +0 -0
  23. package/lib/runtime/components/react/dist/mpx-icon/icons/waiting.png +0 -0
  24. package/lib/runtime/components/react/dist/mpx-icon/icons/warn.png +0 -0
package/lib/index.js CHANGED
@@ -1806,72 +1806,92 @@ try {
1806
1806
  normalModuleFactory.hooks.afterResolve.tap('MpxWebpackPlugin', ({ createData }) => {
1807
1807
  const { queryObj } = parseRequest(createData.request)
1808
1808
  const loaders = createData.loaders
1809
- if (queryObj.mpx && queryObj.mpx !== MPX_PROCESSED_FLAG) {
1810
- const type = queryObj.type
1809
+ const type = queryObj.type
1810
+ const mpxProcessFlag = queryObj.mpx && queryObj.mpx !== MPX_PROCESSED_FLAG
1811
+ const vueProcessFlag = queryObj.vue && queryObj.mpx !== MPX_PROCESSED_FLAG
1812
+ if (mpxProcessFlag || vueProcessFlag) {
1811
1813
  const extract = queryObj.extract
1812
-
1813
- if (type === 'styles') {
1814
- let insertBeforeIndex = -1
1815
- // 单次遍历收集所有索引
1814
+ if (type === 'styles' || type === 'style') {
1815
+ const loaderTypes = {
1816
+ 'node_modules/stylus-loader': -1,
1817
+ 'node_modules/sass-loader': -1,
1818
+ 'node_modules/less-loader': -1,
1819
+ 'node_modules/css-loader': -1
1820
+ }
1816
1821
  loaders.forEach((loader, index) => {
1817
1822
  const currentLoader = toPosix(loader.loader)
1818
- if (currentLoader.includes('node_modules/stylus-loader') || currentLoader.includes('node_modules/sass-loader') || currentLoader.includes('node_modules/less-loader')) {
1819
- insertBeforeIndex = index
1823
+ for (const key in loaderTypes) {
1824
+ if (currentLoader.includes(key)) {
1825
+ loaderTypes[key] = index
1826
+ break
1827
+ }
1820
1828
  }
1821
1829
  })
1830
+ const insertStripLoaders = (index) => {
1831
+ if (index !== -1) {
1832
+ loaders.splice(index, 0, { loader: styleStripConditionalPath, options: {id: 0} })
1833
+ loaders.splice(index + 2, 0, { loader: styleStripConditionalPath, options: {id: 1} })
1834
+ return true
1835
+ }
1836
+ return false
1837
+ }
1822
1838
 
1823
- if (insertBeforeIndex !== -1) {
1824
- loaders.splice(insertBeforeIndex, 0, { loader: styleStripConditionalPath })
1839
+ const priorities = ['node_modules/stylus-loader', 'node_modules/less-loader', 'node_modules/sass-loader', 'node_modules/css-loader']
1840
+ for (const loaderKey of priorities) {
1841
+ if (insertStripLoaders(loaderTypes[loaderKey])) {
1842
+ break
1843
+ }
1825
1844
  }
1826
- loaders.push({ loader: styleStripConditionalPath })
1827
1845
  }
1828
1846
 
1829
- switch (type) {
1830
- case 'styles':
1831
- case 'template': {
1832
- let insertBeforeIndex = -1
1833
- const info = typeLoaderProcessInfo[type]
1834
- loaders.forEach((loader, index) => {
1835
- const currentLoader = toPosix(loader.loader)
1836
- if (currentLoader.includes(info[0])) {
1837
- loader.loader = info[1]
1838
- insertBeforeIndex = index
1839
- } else if (currentLoader.includes(info[1])) {
1840
- insertBeforeIndex = index
1841
- }
1842
- })
1843
- if (insertBeforeIndex > -1) {
1844
- loaders.splice(insertBeforeIndex + 1, 0, {
1845
- loader: info[2]
1847
+ if (mpxProcessFlag) {
1848
+ switch (type) {
1849
+ case 'styles':
1850
+ case 'template': {
1851
+ let insertBeforeIndex = -1
1852
+ const info = typeLoaderProcessInfo[type]
1853
+ loaders.forEach((loader, index) => {
1854
+ const currentLoader = toPosix(loader.loader)
1855
+ if (currentLoader.includes(info[0])) {
1856
+ loader.loader = info[1]
1857
+ insertBeforeIndex = index
1858
+ } else if (currentLoader.includes(info[1])) {
1859
+ insertBeforeIndex = index
1860
+ }
1846
1861
  })
1862
+ if (insertBeforeIndex > -1) {
1863
+ loaders.splice(insertBeforeIndex + 1, 0, {
1864
+ loader: info[2]
1865
+ })
1866
+ }
1867
+ break
1847
1868
  }
1848
- break
1849
- }
1850
- case 'json':
1851
- if (queryObj.isTheme) {
1852
- loaders.unshift({
1853
- loader: jsonThemeCompilerPath
1854
- })
1855
- } else if (queryObj.isPlugin) {
1856
- loaders.unshift({
1857
- loader: jsonPluginCompilerPath
1858
- })
1859
- } else {
1869
+ case 'json':
1870
+ if (queryObj.isTheme) {
1871
+ loaders.unshift({
1872
+ loader: jsonThemeCompilerPath
1873
+ })
1874
+ } else if (queryObj.isPlugin) {
1875
+ loaders.unshift({
1876
+ loader: jsonPluginCompilerPath
1877
+ })
1878
+ } else {
1879
+ loaders.unshift({
1880
+ loader: jsonCompilerPath
1881
+ })
1882
+ }
1883
+ break
1884
+ case 'wxs':
1860
1885
  loaders.unshift({
1861
- loader: jsonCompilerPath
1886
+ loader: wxsLoaderPath
1862
1887
  })
1863
- }
1864
- break
1865
- case 'wxs':
1888
+ break
1889
+ }
1890
+ if (extract) {
1866
1891
  loaders.unshift({
1867
- loader: wxsLoaderPath
1892
+ loader: extractorPath
1868
1893
  })
1869
- break
1870
- }
1871
- if (extract) {
1872
- loaders.unshift({
1873
- loader: extractorPath
1874
- })
1894
+ }
1875
1895
  }
1876
1896
  createData.resource = addQuery(createData.resource, { mpx: MPX_PROCESSED_FLAG }, true)
1877
1897
  }
@@ -82,7 +82,7 @@ const Input = forwardRef((props, ref) => {
82
82
  const lineCount = useRef(0);
83
83
  const [inputValue, setInputValue] = useState(defaultValue);
84
84
  const [contentHeight, setContentHeight] = useState(0);
85
- const [selection, setSelection] = useState({ start: -1, end: -1 });
85
+ const [selection, setSelection] = useState({ start: -1, end: tmpValue.current.length });
86
86
  const styleObj = extendObject({ padding: 0, backgroundColor: '#fff' }, style, multiline && autoHeight
87
87
  ? { height: 'auto', minHeight: Math.max(style?.minHeight || 35, contentHeight) }
88
88
  : {});
@@ -94,15 +94,17 @@ const Input = forwardRef((props, ref) => {
94
94
  const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef });
95
95
  useEffect(() => {
96
96
  if (inputValue !== value) {
97
- setInputValue(parseValue(value));
97
+ const parsed = parseValue(value);
98
+ tmpValue.current = parsed;
99
+ setInputValue(parsed);
98
100
  }
99
101
  }, [value]);
100
102
  useEffect(() => {
101
- if (typeof cursor === 'number') {
102
- setSelection({ start: cursor, end: cursor });
103
+ if (selectionStart > -1) {
104
+ setSelection({ start: selectionStart, end: selectionEnd === -1 ? tmpValue.current.length : selectionEnd });
103
105
  }
104
- else if (selectionStart >= 0 && selectionEnd >= 0 && selectionStart !== selectionEnd) {
105
- setSelection({ start: selectionStart, end: selectionEnd });
106
+ else if (typeof cursor === 'number') {
107
+ setSelection({ start: cursor, end: cursor });
106
108
  }
107
109
  }, [cursor, selectionStart, selectionEnd]);
108
110
  // have not selection on the Android platformg
@@ -152,6 +154,9 @@ const Input = forwardRef((props, ref) => {
152
154
  // sometimes the focus event occurs later than the keyboardWillShow event
153
155
  setKeyboardAvoidContext();
154
156
  };
157
+ const onTouchEnd = (evt) => {
158
+ evt.nativeEvent.origin = 'input';
159
+ };
155
160
  const onFocus = (evt) => {
156
161
  setKeyboardAvoidContext();
157
162
  bindfocus && bindfocus(getCustomEvent('focus', evt, {
@@ -211,6 +216,7 @@ const Input = forwardRef((props, ref) => {
211
216
  }
212
217
  };
213
218
  const resetValue = () => {
219
+ tmpValue.current = '';
214
220
  setInputValue('');
215
221
  };
216
222
  const getValue = () => {
@@ -255,7 +261,7 @@ const Input = forwardRef((props, ref) => {
255
261
  maxLength: maxlength === -1 ? undefined : maxlength,
256
262
  editable: !disabled,
257
263
  autoFocus: !!autoFocus || !!focus,
258
- selection: selection,
264
+ selection: selectionStart > -1 || typeof cursor === 'number' ? selection : undefined,
259
265
  selectionColor: cursorColor,
260
266
  blurOnSubmit: !multiline && !confirmHold,
261
267
  underlineColorAndroid: 'rgba(0,0,0,0)',
@@ -264,6 +270,7 @@ const Input = forwardRef((props, ref) => {
264
270
  multiline: !!multiline
265
271
  }, !!multiline && confirmType === 'return' ? {} : { enterKeyHint: confirmType }, layoutProps, {
266
272
  onTouchStart,
273
+ onTouchEnd,
267
274
  onFocus,
268
275
  onBlur,
269
276
  onChange,
@@ -1,7 +1,6 @@
1
- import React, { useContext, useEffect, useMemo } from 'react';
2
- import { Keyboard, Platform, View } from 'react-native';
1
+ import React, { useContext, useEffect } from 'react';
2
+ import { Keyboard, View, Platform } from 'react-native';
3
3
  import Animated, { useSharedValue, useAnimatedStyle, withTiming, Easing } from 'react-native-reanimated';
4
- import { GestureDetector, Gesture } from 'react-native-gesture-handler';
5
4
  import { KeyboardAvoidContext } from './context';
6
5
  const KeyboardAvoidingView = ({ children, style, contentContainerStyle }) => {
7
6
  const isIOS = Platform.OS === 'ios';
@@ -10,20 +9,10 @@ const KeyboardAvoidingView = ({ children, style, contentContainerStyle }) => {
10
9
  const offset = useSharedValue(0);
11
10
  const basic = useSharedValue('auto');
12
11
  const keyboardAvoid = useContext(KeyboardAvoidContext);
13
- const dismiss = () => {
14
- Keyboard.isVisible() && Keyboard.dismiss();
15
- };
16
- const gesture = useMemo(() => {
17
- return Gesture.Tap()
18
- .onEnd(() => {
19
- dismiss();
20
- }).runOnJS(true);
21
- }, []);
22
- const animatedStyle = useAnimatedStyle(() => {
23
- return Object.assign({
24
- transform: [{ translateY: -offset.value }]
25
- }, isIOS ? {} : { flexBasis: basic.value });
26
- });
12
+ const animatedStyle = useAnimatedStyle(() => ({
13
+ transform: [{ translateY: -offset.value }],
14
+ flexBasis: basic.value
15
+ }));
27
16
  const resetKeyboard = () => {
28
17
  if (keyboardAvoid?.current) {
29
18
  keyboardAvoid.current = null;
@@ -31,6 +20,11 @@ const KeyboardAvoidingView = ({ children, style, contentContainerStyle }) => {
31
20
  offset.value = withTiming(0, { duration, easing });
32
21
  basic.value = 'auto';
33
22
  };
23
+ const onTouchEnd = ({ nativeEvent }) => {
24
+ if (nativeEvent.origin !== 'input') {
25
+ Keyboard.isVisible() && Keyboard.dismiss();
26
+ }
27
+ };
34
28
  useEffect(() => {
35
29
  let subscriptions = [];
36
30
  if (isIOS) {
@@ -46,7 +40,12 @@ const KeyboardAvoidingView = ({ children, style, contentContainerStyle }) => {
46
40
  const aboveValue = -aboveOffset >= cursorSpacing ? 0 : aboveOffset + cursorSpacing;
47
41
  const belowValue = Math.min(endCoordinates.height, aboveOffset + cursorSpacing);
48
42
  const value = aboveOffset > 0 ? belowValue : aboveValue;
49
- offset.value = withTiming(value, { duration, easing });
43
+ offset.value = withTiming(value, { duration, easing }, (finished) => {
44
+ if (finished) {
45
+ // Set flexBasic after animation to trigger re-layout and reset layout information
46
+ basic.value = '99.99%';
47
+ }
48
+ });
50
49
  });
51
50
  });
52
51
  }),
@@ -68,11 +67,7 @@ const KeyboardAvoidingView = ({ children, style, contentContainerStyle }) => {
68
67
  const value = aboveOffset > 0 ? belowValue : aboveValue;
69
68
  offset.value = withTiming(value, { duration, easing }, (finished) => {
70
69
  if (finished) {
71
- /**
72
- * In the Android environment, the layout information is not synchronized after the animation,
73
- * which results in the inability to correctly trigger element events.
74
- * Here, we utilize flexBasic to proactively trigger a re-layout
75
- */
70
+ // Set flexBasic after animation to trigger re-layout and reset layout information
76
71
  basic.value = '99.99%';
77
72
  }
78
73
  });
@@ -85,16 +80,14 @@ const KeyboardAvoidingView = ({ children, style, contentContainerStyle }) => {
85
80
  subscriptions.forEach(subscription => subscription.remove());
86
81
  };
87
82
  }, [keyboardAvoid]);
88
- return (<GestureDetector gesture={gesture}>
89
- <View style={style}>
90
- <Animated.View style={[
83
+ return (<View style={style} onTouchEnd={onTouchEnd}>
84
+ <Animated.View style={[
91
85
  contentContainerStyle,
92
86
  animatedStyle
93
87
  ]}>
94
- {children}
95
- </Animated.View>
96
- </View>
97
- </GestureDetector>);
88
+ {children}
89
+ </Animated.View>
90
+ </View>);
98
91
  };
99
92
  KeyboardAvoidingView.displayName = 'MpxKeyboardAvoidingView';
100
93
  export default KeyboardAvoidingView;
@@ -2,7 +2,7 @@ import Animated, { useAnimatedStyle, interpolate } from 'react-native-reanimated
2
2
  import { forwardRef, useRef, useContext } from 'react';
3
3
  import useInnerProps from './getInnerListeners';
4
4
  import useNodesRef from './useNodesRef'; // 引入辅助函数
5
- import { useTransformStyle, splitStyle, splitProps, wrapChildren, useLayout } from './utils';
5
+ import { useTransformStyle, splitStyle, splitProps, wrapChildren, useLayout, isHarmony } from './utils';
6
6
  import { SwiperContext } from './context';
7
7
  const _SwiperItem = forwardRef((props, ref) => {
8
8
  const { 'enable-var': enableVar, 'external-var-context': externalVarContext, style, customStyle, itemIndex } = props;
@@ -30,7 +30,7 @@ const _SwiperItem = forwardRef((props, ref) => {
30
30
  'style'
31
31
  ], { layoutRef });
32
32
  const itemAnimatedStyle = useAnimatedStyle(() => {
33
- if (!step.value)
33
+ if (!step.value && !isHarmony)
34
34
  return {};
35
35
  const inputRange = [step.value, 0];
36
36
  const outputRange = [0.7, 1];
@@ -70,7 +70,7 @@ const easeMap = {
70
70
  easeInOutCubic: Easing.inOut(Easing.cubic)
71
71
  };
72
72
  const SwiperWrapper = forwardRef((props, ref) => {
73
- const { 'indicator-dots': showsPagination, 'indicator-color': dotColor = 'rgba(0, 0, 0, .3)', 'indicator-active-color': activeDotColor = '#000000', 'enable-var': enableVar = false, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight, 'external-var-context': externalVarContext, style = {}, autoplay = false, circular = false } = props;
73
+ const { 'indicator-dots': showsPagination, 'indicator-color': dotColor = 'rgba(0, 0, 0, .3)', 'indicator-active-color': activeDotColor = '#000000', 'enable-var': enableVar = false, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight, 'external-var-context': externalVarContext, style = {}, autoplay = false, circular = false, disableGesture = false } = props;
74
74
  const easeingFunc = props['easing-function'] || 'default';
75
75
  const easeDuration = props.duration || 500;
76
76
  const horizontal = props.vertical !== undefined ? !props.vertical : true;
@@ -658,7 +658,7 @@ const SwiperWrapper = forwardRef((props, ref) => {
658
658
  {showsPagination && renderPagination()}
659
659
  </View>);
660
660
  }
661
- if (children.length === 1) {
661
+ if (children.length === 1 || disableGesture) {
662
662
  return renderSwiper();
663
663
  }
664
664
  else {
@@ -75,7 +75,10 @@ const _WebView = forwardRef((props, ref) => {
75
75
  };
76
76
  const navigation = useNavigation();
77
77
  useEffect(() => {
78
- const beforeRemoveSubscription = navigation?.addListener?.('beforeRemove', beforeRemoveHandle);
78
+ let beforeRemoveSubscription;
79
+ if (__mpx_mode__ !== 'ios') {
80
+ beforeRemoveSubscription = navigation?.addListener?.('beforeRemove', beforeRemoveHandle);
81
+ }
79
82
  return () => {
80
83
  if (isFunction(beforeRemoveSubscription)) {
81
84
  beforeRemoveSubscription();
@@ -51,7 +51,8 @@ import {
51
51
  TextInputSelectionChangeEventData,
52
52
  TextInputFocusEventData,
53
53
  TextInputChangeEventData,
54
- TextInputSubmitEditingEventData
54
+ TextInputSubmitEditingEventData,
55
+ NativeTouchEvent
55
56
  } from 'react-native'
56
57
  import { warn } from '@mpxjs/utils'
57
58
  import { useUpdateEffect, useTransformStyle, useLayout, extendObject } from './utils'
@@ -195,7 +196,7 @@ const Input = forwardRef<HandlerRef<TextInput, FinalInputProps>, FinalInputProps
195
196
 
196
197
  const [inputValue, setInputValue] = useState(defaultValue)
197
198
  const [contentHeight, setContentHeight] = useState(0)
198
- const [selection, setSelection] = useState({ start: -1, end: -1 })
199
+ const [selection, setSelection] = useState({ start: -1, end: tmpValue.current.length })
199
200
 
200
201
  const styleObj = extendObject(
201
202
  { padding: 0, backgroundColor: '#fff' },
@@ -221,15 +222,17 @@ const Input = forwardRef<HandlerRef<TextInput, FinalInputProps>, FinalInputProps
221
222
 
222
223
  useEffect(() => {
223
224
  if (inputValue !== value) {
224
- setInputValue(parseValue(value))
225
+ const parsed = parseValue(value)
226
+ tmpValue.current = parsed
227
+ setInputValue(parsed)
225
228
  }
226
229
  }, [value])
227
230
 
228
231
  useEffect(() => {
229
- if (typeof cursor === 'number') {
232
+ if (selectionStart > -1) {
233
+ setSelection({ start: selectionStart, end: selectionEnd === -1 ? tmpValue.current.length : selectionEnd })
234
+ } else if (typeof cursor === 'number') {
230
235
  setSelection({ start: cursor, end: cursor })
231
- } else if (selectionStart >= 0 && selectionEnd >= 0 && selectionStart !== selectionEnd) {
232
- setSelection({ start: selectionStart, end: selectionEnd })
233
236
  }
234
237
  }, [cursor, selectionStart, selectionEnd])
235
238
 
@@ -290,6 +293,10 @@ const Input = forwardRef<HandlerRef<TextInput, FinalInputProps>, FinalInputProps
290
293
  setKeyboardAvoidContext()
291
294
  }
292
295
 
296
+ const onTouchEnd = (evt: NativeSyntheticEvent<NativeTouchEvent & { origin?: string }>) => {
297
+ evt.nativeEvent.origin = 'input'
298
+ }
299
+
293
300
  const onFocus = (evt: NativeSyntheticEvent<TextInputFocusEventData>) => {
294
301
  setKeyboardAvoidContext()
295
302
  bindfocus && bindfocus(
@@ -388,6 +395,7 @@ const Input = forwardRef<HandlerRef<TextInput, FinalInputProps>, FinalInputProps
388
395
  }
389
396
 
390
397
  const resetValue = () => {
398
+ tmpValue.current = ''
391
399
  setInputValue('')
392
400
  }
393
401
 
@@ -440,7 +448,7 @@ const Input = forwardRef<HandlerRef<TextInput, FinalInputProps>, FinalInputProps
440
448
  maxLength: maxlength === -1 ? undefined : maxlength,
441
449
  editable: !disabled,
442
450
  autoFocus: !!autoFocus || !!focus,
443
- selection: selection,
451
+ selection: selectionStart > -1 || typeof cursor === 'number' ? selection : undefined,
444
452
  selectionColor: cursorColor,
445
453
  blurOnSubmit: !multiline && !confirmHold,
446
454
  underlineColorAndroid: 'rgba(0,0,0,0)',
@@ -452,6 +460,7 @@ const Input = forwardRef<HandlerRef<TextInput, FinalInputProps>, FinalInputProps
452
460
  layoutProps,
453
461
  {
454
462
  onTouchStart,
463
+ onTouchEnd,
455
464
  onFocus,
456
465
  onBlur,
457
466
  onChange,
@@ -1,7 +1,6 @@
1
- import React, { ReactNode, useContext, useEffect, useMemo } from 'react'
2
- import { DimensionValue, EmitterSubscription, Keyboard, Platform, View, ViewStyle } from 'react-native'
3
- import Animated, { useSharedValue, useAnimatedStyle, withTiming, Easing, runOnJS } from 'react-native-reanimated'
4
- import { GestureDetector, Gesture } from 'react-native-gesture-handler'
1
+ import React, { ReactNode, useContext, useEffect } from 'react'
2
+ import { DimensionValue, EmitterSubscription, Keyboard, View, ViewStyle, Platform, NativeSyntheticEvent, NativeTouchEvent } from 'react-native'
3
+ import Animated, { useSharedValue, useAnimatedStyle, withTiming, Easing } from 'react-native-reanimated'
5
4
  import { KeyboardAvoidContext } from './context'
6
5
 
7
6
  type KeyboardAvoidViewProps = {
@@ -19,25 +18,10 @@ const KeyboardAvoidingView = ({ children, style, contentContainerStyle }: Keyboa
19
18
  const basic = useSharedValue('auto')
20
19
  const keyboardAvoid = useContext(KeyboardAvoidContext)
21
20
 
22
- const dismiss = () => {
23
- Keyboard.isVisible() && Keyboard.dismiss()
24
- }
25
-
26
- const gesture = useMemo(() => {
27
- return Gesture.Tap()
28
- .onEnd(() => {
29
- dismiss()
30
- }).runOnJS(true)
31
- }, [])
32
-
33
- const animatedStyle = useAnimatedStyle(() => {
34
- return Object.assign(
35
- {
36
- transform: [{ translateY: -offset.value }]
37
- },
38
- isIOS ? {} : { flexBasis: basic.value as DimensionValue }
39
- )
40
- })
21
+ const animatedStyle = useAnimatedStyle(() => ({
22
+ transform: [{ translateY: -offset.value }],
23
+ flexBasis: basic.value as DimensionValue
24
+ }))
41
25
 
42
26
  const resetKeyboard = () => {
43
27
  if (keyboardAvoid?.current) {
@@ -47,6 +31,12 @@ const KeyboardAvoidingView = ({ children, style, contentContainerStyle }: Keyboa
47
31
  basic.value = 'auto'
48
32
  }
49
33
 
34
+ const onTouchEnd = ({ nativeEvent }: NativeSyntheticEvent<NativeTouchEvent & { origin?: string }>) => {
35
+ if (nativeEvent.origin !== 'input') {
36
+ Keyboard.isVisible() && Keyboard.dismiss()
37
+ }
38
+ }
39
+
50
40
  useEffect(() => {
51
41
  let subscriptions: EmitterSubscription[] = []
52
42
 
@@ -62,7 +52,12 @@ const KeyboardAvoidingView = ({ children, style, contentContainerStyle }: Keyboa
62
52
  const aboveValue = -aboveOffset >= cursorSpacing ? 0 : aboveOffset + cursorSpacing
63
53
  const belowValue = Math.min(endCoordinates.height, aboveOffset + cursorSpacing)
64
54
  const value = aboveOffset > 0 ? belowValue : aboveValue
65
- offset.value = withTiming(value, { duration, easing })
55
+ offset.value = withTiming(value, { duration, easing }, (finished) => {
56
+ if (finished) {
57
+ // Set flexBasic after animation to trigger re-layout and reset layout information
58
+ basic.value = '99.99%'
59
+ }
60
+ })
66
61
  })
67
62
  })
68
63
  }),
@@ -82,11 +77,7 @@ const KeyboardAvoidingView = ({ children, style, contentContainerStyle }: Keyboa
82
77
  const value = aboveOffset > 0 ? belowValue : aboveValue
83
78
  offset.value = withTiming(value, { duration, easing }, (finished) => {
84
79
  if (finished) {
85
- /**
86
- * In the Android environment, the layout information is not synchronized after the animation,
87
- * which results in the inability to correctly trigger element events.
88
- * Here, we utilize flexBasic to proactively trigger a re-layout
89
- */
80
+ // Set flexBasic after animation to trigger re-layout and reset layout information
90
81
  basic.value = '99.99%'
91
82
  }
92
83
  })
@@ -102,18 +93,16 @@ const KeyboardAvoidingView = ({ children, style, contentContainerStyle }: Keyboa
102
93
  }, [keyboardAvoid])
103
94
 
104
95
  return (
105
- <GestureDetector gesture={gesture}>
106
- <View style={style}>
107
- <Animated.View
108
- style={[
109
- contentContainerStyle,
110
- animatedStyle
111
- ]}
112
- >
113
- {children}
114
- </Animated.View>
115
- </View>
116
- </GestureDetector>
96
+ <View style={style} onTouchEnd={onTouchEnd}>
97
+ <Animated.View
98
+ style={[
99
+ contentContainerStyle,
100
+ animatedStyle
101
+ ]}
102
+ >
103
+ {children}
104
+ </Animated.View>
105
+ </View>
117
106
  )
118
107
  }
119
108
 
@@ -3,7 +3,7 @@ import Animated, { useAnimatedStyle, interpolate, SharedValue } from 'react-nati
3
3
  import { ReactNode, forwardRef, useRef, useContext } from 'react'
4
4
  import useInnerProps from './getInnerListeners'
5
5
  import useNodesRef, { HandlerRef } from './useNodesRef' // 引入辅助函数
6
- import { useTransformStyle, splitStyle, splitProps, wrapChildren, useLayout } from './utils'
6
+ import { useTransformStyle, splitStyle, splitProps, wrapChildren, useLayout, isHarmony } from './utils'
7
7
  import { SwiperContext } from './context'
8
8
 
9
9
  interface SwiperItemProps {
@@ -73,7 +73,7 @@ const _SwiperItem = forwardRef<HandlerRef<View, SwiperItemProps>, SwiperItemProp
73
73
  'style'
74
74
  ], { layoutRef })
75
75
  const itemAnimatedStyle = useAnimatedStyle(() => {
76
- if (!step.value) return {}
76
+ if (!step.value && !isHarmony) return {}
77
77
  const inputRange = [step.value, 0]
78
78
  const outputRange = [0.7, 1]
79
79
  // 实现元素的宽度跟随step从0到真实宽度,且不能触发重新渲染整个组件,通过AnimatedStyle的方式实现
@@ -55,6 +55,7 @@ interface SwiperProps {
55
55
  'parent-width'?: number;
56
56
  'parent-height'?: number;
57
57
  'external-var-context'?: Record<string, any>;
58
+ disableGesture?: boolean;
58
59
  bindchange?: (event: NativeSyntheticEvent<TouchEvent> | unknown) => void;
59
60
  }
60
61
 
@@ -136,7 +137,8 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
136
137
  'external-var-context': externalVarContext,
137
138
  style = {},
138
139
  autoplay = false,
139
- circular = false
140
+ circular = false,
141
+ disableGesture = false
140
142
  } = props
141
143
  const easeingFunc = props['easing-function'] || 'default'
142
144
  const easeDuration = props.duration || 500
@@ -730,7 +732,7 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
730
732
  </View>)
731
733
  }
732
734
 
733
- if (children.length === 1) {
735
+ if (children.length === 1 || disableGesture) {
734
736
  return renderSwiper()
735
737
  } else {
736
738
  return (<GestureDetector gesture={gestureHandler}>
@@ -123,7 +123,10 @@ const _WebView = forwardRef<HandlerRef<WebView, WebViewProps>, WebViewProps>((pr
123
123
  const navigation = useNavigation()
124
124
 
125
125
  useEffect(() => {
126
- const beforeRemoveSubscription = navigation?.addListener?.('beforeRemove', beforeRemoveHandle)
126
+ let beforeRemoveSubscription:any
127
+ if (__mpx_mode__ !== 'ios') {
128
+ beforeRemoveSubscription = navigation?.addListener?.('beforeRemove', beforeRemoveHandle)
129
+ }
127
130
  return () => {
128
131
  if (isFunction(beforeRemoveSubscription)) {
129
132
  beforeRemoveSubscription()
@@ -0,0 +1,243 @@
1
+ <script>
2
+ import mpx from '@mpxjs/core'
3
+ export default {
4
+ name: 'mpx-titlebar',
5
+ props: {
6
+ // 来自 app.json 中 window 的 titlebar 相关配置
7
+ windowConfig: {
8
+ type: Object,
9
+ default: () => global.__mpxPageConfig
10
+ },
11
+ // 来自 页面 json 中的 titlebar 相关配置,会覆盖 windowConfig
12
+ pageConfig: {
13
+ type: Object,
14
+ default: () => ({})
15
+ }
16
+ },
17
+ computed: {
18
+ // 合并全局 window 配置与页面配置(页面配置覆盖全局配置)
19
+ cfg () {
20
+ return Object.assign({}, this.windowConfig || {}, this.pageConfig || {})
21
+ },
22
+ // 标题文本(兼容常见字段名)
23
+ titleText () {
24
+ return this.cfg.navigationBarTitleText || this.cfg.title || ''
25
+ },
26
+ // 背景色(兼容常见字段)
27
+ backgroundColor () {
28
+ return this.cfg.navigationBarBackgroundColor || '#ffffff'
29
+ },
30
+ // 文本颜色,微信小程序中 navigationBarTextStyle 为 white 或 black
31
+ textColor () {
32
+ const style = this.cfg.navigationBarTextStyle || 'black'
33
+ return style === 'white' ? '#ffffff' : '#000000'
34
+ },
35
+ // navigationStyle: 'default' | 'custom',custom 表示需要自定义绘制
36
+ navigationStyle () {
37
+ return this.cfg.navigationStyle || 'default'
38
+ },
39
+ // 是否隐藏(navigationStyle 为 'custom' 时也应隐藏)
40
+ hidden () {
41
+ return mpx.config?.webConfig?.enableTitleBar !== true || this.navigationStyle === 'custom'
42
+ },
43
+ // 是否展示返回按钮:根据浏览器历史判断(不依赖额外 page 配置)
44
+ showBack () {
45
+ console.log('showBack', this.$router.stack.length)
46
+ try {
47
+ return this.$router.stack.length > 1
48
+ } catch (e) {
49
+ return false
50
+ }
51
+ },
52
+ // safe area 顶部 padding,使用 env(safe-area-inset-top)
53
+ safeStyle () {
54
+ // 多数浏览器支持 env(), 为兼容也使用 constant() 备选(旧 iOS Safari)
55
+ return {
56
+ paddingTop: 'env(safe-area-inset-top, 0px)'
57
+ }
58
+ },
59
+ // 内部标题栏高度(遵循小程序常见平台差异)
60
+ innerHeight () {
61
+ const isIOS = /iP(hone|od|ad)/.test(navigator.userAgent)
62
+ return (isIOS ? 44 : 48) + 'px'
63
+ },
64
+ rootStyle () {
65
+ return {
66
+ background: this.backgroundColor,
67
+ color: this.textColor
68
+ }
69
+ },
70
+ innerStyle () {
71
+ return {
72
+ height: this.innerHeight
73
+ }
74
+ }
75
+ ,
76
+ // content wrapper style: padding-top to avoid being covered by fixed titlebar
77
+ contentStyle () {
78
+ // use calc to combine innerHeight and safe-area inset
79
+ return {
80
+ paddingTop: this.hidden ? '0px' : `calc(${this.innerHeight} + env(safe-area-inset-top, 0px))`,
81
+ // create its own layer to avoid overlapping issues
82
+ transform: 'translateZ(0)',
83
+ willChange: 'transform'
84
+ }
85
+ }
86
+ },
87
+ methods: {
88
+ // 左侧点击:派发事件并在可回退时回退
89
+ onLeftClick (e) {
90
+ this.$emit('click-left', e)
91
+ if (this.showBack) {
92
+ try { window.history.back() } catch (err) {}
93
+ }
94
+ }
95
+ },
96
+ render (h) {
97
+ const leftChildren = []
98
+
99
+ // default back button (SVG) — no left slot support
100
+ if (this.showBack) {
101
+ leftChildren.push(
102
+ h('button', {
103
+ class: 'mpx-titlebar__back',
104
+ attrs: { 'aria-label': 'back', type: 'button' }
105
+ }, [
106
+ h('svg', {
107
+ attrs: {
108
+ viewBox: '0 0 24 24',
109
+ width: '20',
110
+ height: '20',
111
+ fill: 'none',
112
+ xmlns: 'http://www.w3.org/2000/svg',
113
+ focusable: 'false',
114
+ 'aria-hidden': 'true'
115
+ }
116
+ }, [
117
+ h('path', {
118
+ attrs: {
119
+ d: 'M15 18l-6-6 6-6',
120
+ stroke: 'currentColor',
121
+ 'stroke-width': '2',
122
+ 'stroke-linecap': 'round',
123
+ 'stroke-linejoin': 'round'
124
+ }
125
+ })
126
+ ])
127
+ ])
128
+ )
129
+ }
130
+
131
+ // center shows title; only default slot (page content) is supported
132
+ const centerChildren = [
133
+ h('div', { class: 'mpx-titlebar__title', style: { color: this.textColor } }, [this.titleText])
134
+ ]
135
+
136
+ // top-level wrapper: contains fixed titlebar and page content wrapper
137
+ return h('page', { class: 'mpx-titlebar-wrapper' }, [
138
+ // fixed titlebar
139
+ h('div', {
140
+ class: ['mpx-titlebar', { 'mpx-titlebar--hidden': this.hidden }],
141
+ style: this.rootStyle
142
+ }, [
143
+ h('div', { class: 'mpx-titlebar__safe', style: this.safeStyle }, [
144
+ h('div', { class: 'mpx-titlebar__inner', style: this.innerStyle }, [
145
+ h('div', { class: 'mpx-titlebar__left', on: { click: this.onLeftClick } }, leftChildren),
146
+ h('div', { class: 'mpx-titlebar__center' }, centerChildren),
147
+ h('div', { class: 'mpx-titlebar__right' }, [])
148
+ ])
149
+ ])
150
+ ]),
151
+
152
+ // page content wrapper: default slot is page content
153
+ h('div', { class: 'mpx-titlebar__content', style: this.contentStyle }, this.$slots.default || [])
154
+ ])
155
+ }
156
+ }
157
+ </script>
158
+
159
+ <style scoped>
160
+ .mpx-titlebar {
161
+ width: 100%;
162
+ box-sizing: border-box;
163
+ -webkit-font-smoothing: antialiased;
164
+ }
165
+ .mpx-titlebar--hidden {
166
+ display: none;
167
+ }
168
+ .mpx-titlebar__safe {
169
+ /* safe area handled by padding-top; include both env and constant for broader iOS support */
170
+ padding-top: env(safe-area-inset-top, 0px);
171
+ padding-top: constant(safe-area-inset-top, 0px);
172
+ }
173
+ .mpx-titlebar__inner {
174
+ display: flex;
175
+ align-items: center;
176
+ justify-content: space-between;
177
+ padding: 0 12px;
178
+ box-sizing: border-box;
179
+ }
180
+ .mpx-titlebar__left,
181
+ .mpx-titlebar__right {
182
+ flex: 0 0 auto;
183
+ min-width: 44px;
184
+ display: flex;
185
+ align-items: center;
186
+ }
187
+ .mpx-titlebar__center {
188
+ flex: 1 1 auto;
189
+ display: flex;
190
+ align-items: center;
191
+ justify-content: center;
192
+ overflow: hidden;
193
+ padding: 0 8px;
194
+ }
195
+ .mpx-titlebar__title {
196
+ font-size: 17px;
197
+ white-space: nowrap;
198
+ text-overflow: ellipsis;
199
+ overflow: hidden;
200
+ font-weight: 500;
201
+ }
202
+ .mpx-titlebar__back {
203
+ background: none;
204
+ border: none;
205
+ font-size: 20px;
206
+ color: inherit;
207
+ padding: 6px;
208
+ cursor: pointer;
209
+ }
210
+
211
+ /* wrapper and content layout */
212
+ .mpx-titlebar-wrapper {
213
+ position: relative;
214
+ width: 100%;
215
+ height: 100%;
216
+ }
217
+
218
+ .mpx-titlebar {
219
+ position: fixed;
220
+ top: 0;
221
+ left: 0;
222
+ right: 0;
223
+ z-index: 1000; /* ensure above page content */
224
+ }
225
+
226
+ .mpx-titlebar__content {
227
+ position: relative;
228
+ width: 100%;
229
+ min-height: 100%;
230
+ box-sizing: border-box;
231
+ background: transparent;
232
+ }
233
+
234
+ /* SVG icon sizing and inherit color */
235
+ .mpx-titlebar__back svg {
236
+ display: block;
237
+ width: 20px;
238
+ height: 20px;
239
+ }
240
+ .mpx-titlebar__back path {
241
+ stroke: currentColor;
242
+ }
243
+ </style>
@@ -2476,7 +2476,14 @@ function getVirtualHostRoot (options, meta) {
2476
2476
  }
2477
2477
  }
2478
2478
  if (isWeb(mode) && ctorType === 'page') {
2479
- return createASTElement('page')
2479
+ const rootView = createASTElement('mpx-titlebar', [
2480
+ {
2481
+ name: 'pageConfig',
2482
+ value: '{{ this.$options.__mpxPageConfig }}'
2483
+ }
2484
+ ])
2485
+ processElement(rootView, rootView, options, meta)
2486
+ return rootView
2480
2487
  }
2481
2488
  }
2482
2489
  return getTempNode()
@@ -60,7 +60,9 @@ Vue.use(VueRouter)\n`
60
60
  globalTabBar
61
61
  })
62
62
 
63
- output += `var App = require(${stringifyRequest(loaderContext, addQuery(loaderContext.resource, { isApp: true }))}).default\n`
63
+ output += `var App = require(${stringifyRequest(loaderContext, addQuery(loaderContext.resource, { isApp: true }))}).default;\n`
64
+
65
+ output += `Vue.component('mpx-titlebar', require(${stringifyRequest(loaderContext, normalize.lib('runtime/components/web/mpx-titlebar.vue'))}).default);\n`
64
66
 
65
67
  output += `
66
68
  export default processAppOption({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpxjs/webpack-plugin",
3
- "version": "2.10.3-beta.1",
3
+ "version": "2.10.3-beta.10",
4
4
  "description": "mpx compile core",
5
5
  "keywords": [
6
6
  "mpx"