@mpxjs/webpack-plugin 2.10.17-beta.6 → 2.10.17-beta.8

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 (35) hide show
  1. package/lib/index.js +1 -0
  2. package/lib/json-compiler/index.js +18 -1
  3. package/lib/platform/style/wx/index.js +1 -17
  4. package/lib/react/processJSON.js +20 -1
  5. package/lib/react/processScript.js +1 -0
  6. package/lib/react/script-helper.js +0 -1
  7. package/lib/runtime/components/ali/mpx-recycle-view.mpx +518 -0
  8. package/lib/runtime/components/ali/mpx-sticky-header.mpx +212 -0
  9. package/lib/runtime/components/ali/mpx-sticky-section.mpx +17 -0
  10. package/lib/runtime/components/react/animationHooks/useTransitionHooks.ts +34 -30
  11. package/lib/runtime/components/react/animationHooks/utils.ts +3 -2
  12. package/lib/runtime/components/react/dist/animationHooks/useTransitionHooks.js +38 -33
  13. package/lib/runtime/components/react/dist/animationHooks/utils.js +3 -2
  14. package/lib/runtime/components/react/dist/mpx-image.jsx +2 -2
  15. package/lib/runtime/components/react/dist/mpx-recycle-view.d.ts +45 -0
  16. package/lib/runtime/components/react/dist/mpx-recycle-view.jsx +272 -0
  17. package/lib/runtime/components/react/dist/mpx-swiper.jsx +6 -5
  18. package/lib/runtime/components/react/dist/utils.d.ts +2 -1
  19. package/lib/runtime/components/react/dist/utils.jsx +15 -21
  20. package/lib/runtime/components/react/mpx-image.tsx +2 -2
  21. package/lib/runtime/components/react/mpx-recycle-view.tsx +398 -0
  22. package/lib/runtime/components/react/mpx-scroll-view.tsx +1 -0
  23. package/lib/runtime/components/react/mpx-sticky-section.tsx +1 -1
  24. package/lib/runtime/components/react/mpx-swiper.tsx +6 -5
  25. package/lib/runtime/components/react/utils.tsx +18 -23
  26. package/lib/runtime/components/web/mpx-recycle-view.vue +508 -0
  27. package/lib/runtime/components/wx/mpx-list-header-default.mpx +21 -0
  28. package/lib/runtime/components/wx/mpx-recycle-item-default.mpx +21 -0
  29. package/lib/runtime/components/wx/mpx-recycle-view.mpx +193 -0
  30. package/lib/runtime/components/wx/mpx-section-header-default.mpx +21 -0
  31. package/lib/template-compiler/compiler.js +8 -3
  32. package/lib/utils/const.js +17 -0
  33. package/lib/utils/process-extend-components.js +43 -0
  34. package/lib/web/processJSON.js +20 -2
  35. package/package.json +1 -1
@@ -0,0 +1,272 @@
1
+ import React, { forwardRef, useRef, useState, useEffect, useMemo, createElement, useImperativeHandle } from 'react';
2
+ import { SectionList, RefreshControl } from 'react-native';
3
+ import useInnerProps, { getCustomEvent } from './getInnerListeners';
4
+ import { extendObject, useLayout, useTransformStyle } from './utils';
5
+ const getGeneric = (generichash, generickey) => {
6
+ if (!generichash || !generickey)
7
+ return null;
8
+ const GenericComponent = global.__mpxGenericsMap?.[generichash]?.[generickey]?.();
9
+ if (!GenericComponent)
10
+ return null;
11
+ return forwardRef((props, ref) => {
12
+ return createElement(GenericComponent, extendObject({}, {
13
+ ref: ref
14
+ }, props));
15
+ });
16
+ };
17
+ const getListHeaderComponent = (generichash, generickey, data) => {
18
+ if (!generichash || !generickey)
19
+ return undefined;
20
+ const ListHeaderComponent = getGeneric(generichash, generickey);
21
+ return ListHeaderComponent ? createElement(ListHeaderComponent, { listHeaderData: data }) : null;
22
+ };
23
+ const getSectionHeaderRenderer = (generichash, generickey) => {
24
+ if (!generichash || !generickey)
25
+ return undefined;
26
+ return (sectionData) => {
27
+ if (!sectionData.section.hasSectionHeader)
28
+ return null;
29
+ const SectionHeaderComponent = getGeneric(generichash, generickey);
30
+ return SectionHeaderComponent ? createElement(SectionHeaderComponent, { itemData: sectionData.section.headerData }) : null;
31
+ };
32
+ };
33
+ const getItemRenderer = (generichash, generickey) => {
34
+ if (!generichash || !generickey)
35
+ return undefined;
36
+ return ({ item }) => {
37
+ const ItemComponent = getGeneric(generichash, generickey);
38
+ return ItemComponent ? createElement(ItemComponent, { itemData: item }) : null;
39
+ };
40
+ };
41
+ const RecycleView = forwardRef((props = {}, ref) => {
42
+ const { enhanced = false, bounces = true, scrollEventThrottle = 0, height, width, listData, generichash, style = {}, itemHeight = {}, sectionHeaderHeight = {}, listHeaderHeight = {}, listHeaderData = null, useListHeader = false, 'genericrecycle-item': genericrecycleItem, 'genericsection-header': genericsectionHeader, 'genericlist-header': genericListHeader, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight, 'enable-sticky': enableSticky = false, 'enable-back-to-top': enableBackToTop = false, 'end-reached-threshold': onEndReachedThreshold = 0.1, 'refresher-enabled': refresherEnabled, 'show-scrollbar': showScrollbar = true, 'refresher-triggered': refresherTriggered } = props;
43
+ const [refreshing, setRefreshing] = useState(!!refresherTriggered);
44
+ const scrollViewRef = useRef(null);
45
+ const indexMap = useRef({});
46
+ const reverseIndexMap = useRef({});
47
+ const { hasSelfPercent, setWidth, setHeight } = useTransformStyle(style, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight });
48
+ const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef: scrollViewRef });
49
+ useEffect(() => {
50
+ if (refreshing !== refresherTriggered) {
51
+ setRefreshing(!!refresherTriggered);
52
+ }
53
+ }, [refresherTriggered]);
54
+ const onRefresh = () => {
55
+ const { bindrefresherrefresh } = props;
56
+ bindrefresherrefresh &&
57
+ bindrefresherrefresh(getCustomEvent('refresherrefresh', {}, { layoutRef }, props));
58
+ };
59
+ const onEndReached = () => {
60
+ const { bindscrolltolower } = props;
61
+ bindscrolltolower &&
62
+ bindscrolltolower(getCustomEvent('scrolltolower', {}, { layoutRef }, props));
63
+ };
64
+ const onScroll = (event) => {
65
+ const { bindscroll } = props;
66
+ bindscroll &&
67
+ bindscroll(getCustomEvent('scroll', event.nativeEvent, { layoutRef }, props));
68
+ };
69
+ // 通过sectionIndex和rowIndex获取原始索引
70
+ const getOriginalIndex = (sectionIndex, rowIndex) => {
71
+ const key = `${sectionIndex}_${rowIndex}`;
72
+ return reverseIndexMap.current[key] ?? -1; // 如果找不到,返回-1
73
+ };
74
+ const scrollToIndex = ({ index, animated, viewOffset = 0, viewPosition = 0 }) => {
75
+ if (scrollViewRef.current) {
76
+ // 通过索引映射表快速定位位置
77
+ const position = indexMap.current[index];
78
+ const [sectionIndex, itemIndex] = position.split('_');
79
+ scrollViewRef.current.scrollToLocation?.({
80
+ itemIndex: itemIndex === 'header' ? 0 : Number(itemIndex) + 1,
81
+ sectionIndex: Number(sectionIndex) || 0,
82
+ animated,
83
+ viewOffset,
84
+ viewPosition
85
+ });
86
+ }
87
+ };
88
+ const getItemHeight = ({ sectionIndex, rowIndex }) => {
89
+ if (!itemHeight) {
90
+ return 0;
91
+ }
92
+ if (itemHeight.getter) {
93
+ const item = convertedListData[sectionIndex].data[rowIndex];
94
+ // 使用getOriginalIndex获取原始索引
95
+ const originalIndex = getOriginalIndex(sectionIndex, rowIndex);
96
+ return itemHeight.getter?.(item, originalIndex) || 0;
97
+ }
98
+ else {
99
+ return itemHeight.value || 0;
100
+ }
101
+ };
102
+ const getSectionHeaderHeight = ({ sectionIndex }) => {
103
+ const item = convertedListData[sectionIndex];
104
+ const { hasSectionHeader } = item;
105
+ // 使用getOriginalIndex获取原始索引
106
+ const originalIndex = getOriginalIndex(sectionIndex, 'header');
107
+ if (!hasSectionHeader)
108
+ return 0;
109
+ if (sectionHeaderHeight.getter) {
110
+ return sectionHeaderHeight.getter?.(item, originalIndex) || 0;
111
+ }
112
+ else {
113
+ return sectionHeaderHeight.value || 0;
114
+ }
115
+ };
116
+ const convertedListData = useMemo(() => {
117
+ const sections = [];
118
+ let currentSection = null;
119
+ // 清空之前的索引映射
120
+ indexMap.current = {};
121
+ // 清空反向索引映射
122
+ reverseIndexMap.current = {};
123
+ listData.forEach((item, index) => {
124
+ if (item.isSectionHeader) {
125
+ // 如果已经存在一个 section,先把它添加到 sections 中
126
+ if (currentSection) {
127
+ sections.push(currentSection);
128
+ }
129
+ // 创建新的 section
130
+ currentSection = {
131
+ headerData: item,
132
+ data: [],
133
+ hasSectionHeader: true,
134
+ _originalItemIndex: index
135
+ };
136
+ // 为 section header 添加索引映射
137
+ const sectionIndex = sections.length;
138
+ indexMap.current[index] = `${sectionIndex}_header`;
139
+ // 添加反向索引映射
140
+ reverseIndexMap.current[`${sectionIndex}_header`] = index;
141
+ }
142
+ else {
143
+ // 如果没有当前 section,创建一个默认的
144
+ if (!currentSection) {
145
+ // 创建默认section (无header的section)
146
+ currentSection = {
147
+ headerData: null,
148
+ data: [],
149
+ hasSectionHeader: false,
150
+ _originalItemIndex: -1
151
+ };
152
+ }
153
+ // 将 item 添加到当前 section 的 data 中
154
+ const itemIndex = currentSection.data.length;
155
+ currentSection.data.push(extendObject({}, item, {
156
+ _originalItemIndex: index
157
+ }));
158
+ let sectionIndex;
159
+ // 为 item 添加索引映射 - 存储格式为: "sectionIndex_itemIndex"
160
+ if (!currentSection.hasSectionHeader && sections.length === 0) {
161
+ // 在默认section中(第一个且无header)
162
+ sectionIndex = 0;
163
+ indexMap.current[index] = `${sectionIndex}_${itemIndex}`;
164
+ }
165
+ else {
166
+ // 在普通section中
167
+ sectionIndex = sections.length;
168
+ indexMap.current[index] = `${sectionIndex}_${itemIndex}`;
169
+ }
170
+ // 添加反向索引映射
171
+ reverseIndexMap.current[`${sectionIndex}_${itemIndex}`] = index;
172
+ }
173
+ });
174
+ // 添加最后一个 section
175
+ if (currentSection) {
176
+ sections.push(currentSection);
177
+ }
178
+ return sections;
179
+ }, [listData]);
180
+ const { getItemLayout } = useMemo(() => {
181
+ const layouts = [];
182
+ let offset = 0;
183
+ if (useListHeader) {
184
+ // 计算列表头部的高度
185
+ offset += listHeaderHeight.getter?.() || listHeaderHeight.value || 0;
186
+ }
187
+ // 遍历所有 sections
188
+ convertedListData.forEach((section, sectionIndex) => {
189
+ // 添加 section header 的位置信息
190
+ const headerHeight = getSectionHeaderHeight({ sectionIndex });
191
+ layouts.push({
192
+ length: headerHeight,
193
+ offset,
194
+ index: layouts.length
195
+ });
196
+ offset += headerHeight;
197
+ // 添加该 section 中所有 items 的位置信息
198
+ section.data.forEach((item, itemIndex) => {
199
+ const contenteight = getItemHeight({ sectionIndex, rowIndex: itemIndex });
200
+ layouts.push({
201
+ length: contenteight,
202
+ offset,
203
+ index: layouts.length
204
+ });
205
+ offset += contenteight;
206
+ });
207
+ // 添加该 section 尾部位置信息
208
+ // 因为即使 sectionList 没传 renderSectionFooter,getItemLayout 中的 index 的计算也会包含尾部节点
209
+ layouts.push({
210
+ length: 0,
211
+ offset,
212
+ index: layouts.length
213
+ });
214
+ });
215
+ return {
216
+ itemLayouts: layouts,
217
+ getItemLayout: (data, index) => layouts[index]
218
+ };
219
+ }, [convertedListData, useListHeader]);
220
+ const scrollAdditionalProps = extendObject({
221
+ alwaysBounceVertical: false,
222
+ alwaysBounceHorizontal: false,
223
+ scrollEventThrottle: scrollEventThrottle,
224
+ scrollsToTop: enableBackToTop,
225
+ showsHorizontalScrollIndicator: showScrollbar,
226
+ onEndReachedThreshold,
227
+ ref: scrollViewRef,
228
+ bounces: false,
229
+ stickySectionHeadersEnabled: enableSticky,
230
+ onScroll: onScroll,
231
+ onEndReached: onEndReached
232
+ }, layoutProps);
233
+ if (enhanced) {
234
+ Object.assign(scrollAdditionalProps, {
235
+ bounces
236
+ });
237
+ }
238
+ if (refresherEnabled) {
239
+ Object.assign(scrollAdditionalProps, {
240
+ refreshing: refreshing
241
+ });
242
+ }
243
+ useImperativeHandle(ref, () => {
244
+ return {
245
+ ...props,
246
+ scrollToIndex
247
+ };
248
+ });
249
+ const innerProps = useInnerProps(extendObject({}, props, scrollAdditionalProps), [
250
+ 'id',
251
+ 'show-scrollbar',
252
+ 'lower-threshold',
253
+ 'refresher-triggered',
254
+ 'refresher-enabled',
255
+ 'bindrefresherrefresh'
256
+ ], { layoutRef });
257
+ return createElement(SectionList, extendObject({
258
+ style: [{ height, width }, style, layoutStyle],
259
+ sections: convertedListData,
260
+ renderItem: getItemRenderer(generichash, genericrecycleItem),
261
+ getItemLayout: getItemLayout,
262
+ ListHeaderComponent: useListHeader ? getListHeaderComponent(generichash, genericListHeader, listHeaderData) : null,
263
+ renderSectionHeader: getSectionHeaderRenderer(generichash, genericsectionHeader),
264
+ refreshControl: refresherEnabled
265
+ ? React.createElement(RefreshControl, {
266
+ onRefresh: onRefresh,
267
+ refreshing: refreshing
268
+ })
269
+ : undefined
270
+ }, innerProps));
271
+ });
272
+ export default RecycleView;
@@ -128,6 +128,8 @@ const SwiperWrapper = forwardRef((props, ref) => {
128
128
  const moveTranstion = useSharedValue(0);
129
129
  const timerId = useRef(0);
130
130
  const intervalTimer = props.interval || 500;
131
+ // 记录是否首次
132
+ const isFirstRef = useRef(true);
131
133
  const simultaneousHandlers = flatGesture(originSimultaneousHandlers);
132
134
  const waitForHandlers = flatGesture(waitFor);
133
135
  // 判断gesture手势是否需要协同处理、等待手势失败响应
@@ -355,10 +357,8 @@ const SwiperWrapper = forwardRef((props, ref) => {
355
357
  };
356
358
  }, []);
357
359
  function handleSwiperChange(current, pCurrent) {
358
- if (pCurrent !== currentIndex.value) {
359
- const eventData = getCustomEvent('change', {}, { detail: { current, source: 'touch' }, layoutRef: layoutRef });
360
- bindchange && bindchange(eventData);
361
- }
360
+ const eventData = getCustomEvent('change', {}, { detail: { current, source: 'touch' }, layoutRef: layoutRef });
361
+ bindchange && bindchange(eventData);
362
362
  }
363
363
  const runOnJSCallbackRef = useRef({
364
364
  loop,
@@ -408,9 +408,10 @@ const SwiperWrapper = forwardRef((props, ref) => {
408
408
  // 1. 用户在当前页切换选中项,动画;用户携带选中index打开到swiper页直接选中不走动画
409
409
  useAnimatedReaction(() => currentIndex.value, (newIndex, preIndex) => {
410
410
  // 这里必须传递函数名, 直接写()=> {}形式会报 访问了未sharedValue信息
411
- if (newIndex !== preIndex && bindchange) {
411
+ if (newIndex !== preIndex && bindchange && !isFirstRef.current) {
412
412
  runOnJS(runOnJSCallback)('handleSwiperChange', newIndex, propCurrent);
413
413
  }
414
+ isFirstRef.current = false;
414
415
  });
415
416
  useEffect(() => {
416
417
  let patchStep = 0;
@@ -39,8 +39,9 @@ interface TransformStyleConfig {
39
39
  parentFontSize?: number;
40
40
  parentWidth?: number;
41
41
  parentHeight?: number;
42
+ isTransformBorderRadiusPercent?: boolean;
42
43
  }
43
- export declare function useTransformStyle(styleObj: Record<string, any> | undefined, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight }: TransformStyleConfig): {
44
+ export declare function useTransformStyle(styleObj: Record<string, any> | undefined, { enableVar, isTransformBorderRadiusPercent, externalVarContext, parentFontSize, parentWidth, parentHeight }: TransformStyleConfig): {
44
45
  hasVarDec: boolean;
45
46
  varContextRef: MutableRefObject<{}>;
46
47
  setWidth: Dispatch<SetStateAction<number>>;
@@ -1,6 +1,6 @@
1
1
  import { useEffect, useCallback, useMemo, useRef, isValidElement, useContext, useState, Children, cloneElement, createElement } from 'react';
2
2
  import { Image } from 'react-native';
3
- import { isObject, isFunction, isNumber, hasOwn, diffAndCloneA, error, warn, isEmptyObject } from '@mpxjs/utils';
3
+ import { isObject, isFunction, isNumber, hasOwn, diffAndCloneA, error, warn } from '@mpxjs/utils';
4
4
  import { VarContext, ScrollViewContext, RouteContext } from './context';
5
5
  import { ExpressionParser, parseFunc, ReplaceSource } from './parser';
6
6
  import { initialWindowMetrics } from 'react-native-safe-area-context';
@@ -110,15 +110,17 @@ export function splitStyle(styleObj) {
110
110
  }
111
111
  });
112
112
  }
113
- const selfPercentRule = {
114
- translateX: 'width',
115
- translateY: 'height',
113
+ const radiusPercentRule = {
116
114
  borderTopLeftRadius: 'width',
117
115
  borderBottomLeftRadius: 'width',
118
116
  borderBottomRightRadius: 'width',
119
117
  borderTopRightRadius: 'width',
120
118
  borderRadius: 'width'
121
119
  };
120
+ const selfPercentRule = Object.assign({
121
+ translateX: 'width',
122
+ translateY: 'height'
123
+ }, radiusPercentRule);
122
124
  const parentHeightPercentRule = {
123
125
  height: true,
124
126
  minHeight: true,
@@ -193,7 +195,7 @@ function transformVar(styleObj, varKeyPaths, varContext, visitOther) {
193
195
  const resolved = resolveVar(value, varContext);
194
196
  if (resolved === undefined) {
195
197
  delete target[key];
196
- // error(`Can not resolve css var at ${varKeyPath.join('.')}:${value}.`)
198
+ error(`Can not resolve css var at ${varKeyPath.join('.')}:${value}.`);
197
199
  return;
198
200
  }
199
201
  target[key] = resolved;
@@ -343,15 +345,7 @@ function transformBoxShadow(styleObj) {
343
345
  return `${res}${idx === 0 ? '' : ' '}${global.__formatValue(i)}`;
344
346
  }, '');
345
347
  }
346
- function transformZIndex(styleObj) {
347
- if (!styleObj.zIndex || typeof styleObj.zIndex === 'number')
348
- return;
349
- if (styleObj.zIndex === 'auto') {
350
- error('Property [z-index] does not supported [auto], please check again!');
351
- styleObj.zIndex = 0;
352
- }
353
- }
354
- export function useTransformStyle(styleObj = {}, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight }) {
348
+ export function useTransformStyle(styleObj = {}, { enableVar, isTransformBorderRadiusPercent, externalVarContext, parentFontSize, parentWidth, parentHeight }) {
355
349
  const varStyle = {};
356
350
  const unoVarStyle = {};
357
351
  const normalStyle = {};
@@ -403,7 +397,7 @@ export function useTransformStyle(styleObj = {}, { enableVar, externalVarContext
403
397
  function calcVisitor({ key, value, keyPath }) {
404
398
  if (calcUseRegExp.test(value)) {
405
399
  // calc translate & border-radius 的百分比计算
406
- if (hasOwn(selfPercentRule, key) && /%/.test(value)) {
400
+ if (hasOwn(selfPercentRule, key) && /calc\(\d+%/.test(value)) {
407
401
  hasSelfPercent = true;
408
402
  percentKeyPaths.push(keyPath.slice());
409
403
  }
@@ -412,7 +406,12 @@ export function useTransformStyle(styleObj = {}, { enableVar, externalVarContext
412
406
  }
413
407
  function percentVisitor({ key, value, keyPath }) {
414
408
  // fixme 去掉 translate & border-radius 的百分比计算
415
- if ((key === 'fontSize' || key === 'lineHeight') && PERCENT_REGEX.test(value)) {
409
+ // fixme Image 组件 borderRadius 仅支持 number
410
+ if (isTransformBorderRadiusPercent && hasOwn(radiusPercentRule, key) && PERCENT_REGEX.test(value)) {
411
+ hasSelfPercent = true;
412
+ percentKeyPaths.push(keyPath.slice());
413
+ }
414
+ else if ((key === 'fontSize' || key === 'lineHeight') && PERCENT_REGEX.test(value)) {
416
415
  percentKeyPaths.push(keyPath.slice());
417
416
  }
418
417
  }
@@ -485,11 +484,6 @@ export function useTransformStyle(styleObj = {}, { enableVar, externalVarContext
485
484
  transformStringify(normalStyle);
486
485
  // transform rpx to px
487
486
  transformBoxShadow(normalStyle);
488
- // transform z-index auto to 0
489
- transformZIndex(normalStyle);
490
- if (Array.isArray(normalStyle.transform)) {
491
- normalStyle.transform = normalStyle.transform.filter(item => !isEmptyObject(item));
492
- }
493
487
  return {
494
488
  hasVarDec,
495
489
  varContextRef,
@@ -26,7 +26,7 @@ import { noop } from '@mpxjs/utils'
26
26
  import { SvgCssUri } from 'react-native-svg/css'
27
27
  import useInnerProps, { getCustomEvent } from './getInnerListeners'
28
28
  import useNodesRef, { HandlerRef } from './useNodesRef'
29
- import { SVG_REGEXP, useLayout, useTransformStyle, renderImage, extendObject } from './utils'
29
+ import { SVG_REGEXP, useLayout, useTransformStyle, renderImage, extendObject, isAndroid } from './utils'
30
30
  import Portal from './mpx-portal'
31
31
 
32
32
  export type Mode =
@@ -190,7 +190,7 @@ const Image = forwardRef<HandlerRef<RNImage, ImageProps>, ImageProps>((props, re
190
190
  normalStyle,
191
191
  setWidth,
192
192
  setHeight
193
- } = useTransformStyle(styleObj, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight })
193
+ } = useTransformStyle(styleObj, { enableVar, isTransformBorderRadiusPercent: isAndroid && !isSvg && !isLayoutMode, externalVarContext, parentFontSize, parentWidth, parentHeight })
194
194
 
195
195
  const { layoutRef, layoutStyle, layoutProps } = useLayout({
196
196
  props,