@tarojs/components-react 4.1.12-beta.5 → 4.1.12-beta.51
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/dist/components/image/index.js +5 -3
- package/dist/components/image/index.js.map +1 -1
- package/dist/components/map/MapContext.js +628 -0
- package/dist/components/map/MapContext.js.map +1 -0
- package/dist/components/map/MapCustomCallout.js +91 -0
- package/dist/components/map/MapCustomCallout.js.map +1 -0
- package/dist/components/map/common.js +4 -0
- package/dist/components/map/common.js.map +1 -0
- package/dist/components/map/createMapContext.js +34 -0
- package/dist/components/map/createMapContext.js.map +1 -0
- package/dist/components/map/handler.js +50 -0
- package/dist/components/map/handler.js.map +1 -0
- package/dist/components/map/index.js +329 -0
- package/dist/components/map/index.js.map +1 -0
- package/dist/components/picker/index.js +110 -38
- package/dist/components/picker/index.js.map +1 -1
- package/dist/components/picker/picker-group.js +400 -111
- package/dist/components/picker/picker-group.js.map +1 -1
- package/dist/components/scroll-view/index.js +80 -36
- package/dist/components/scroll-view/index.js.map +1 -1
- package/dist/components/switch/index.js +125 -0
- package/dist/components/switch/index.js.map +1 -0
- package/dist/components/switch/style/index.scss.js +4 -0
- package/dist/components/switch/style/index.scss.js.map +1 -0
- package/dist/contexts/ScrollElementContext.js +6 -0
- package/dist/contexts/ScrollElementContext.js.map +1 -0
- package/dist/index.css +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/original/components/image/index.js +5 -3
- package/dist/original/components/image/index.js.map +1 -1
- package/dist/original/components/image/style/index.scss +5 -1
- package/dist/original/components/map/MapContext.js +628 -0
- package/dist/original/components/map/MapContext.js.map +1 -0
- package/dist/original/components/map/MapCustomCallout.js +91 -0
- package/dist/original/components/map/MapCustomCallout.js.map +1 -0
- package/dist/original/components/map/common.js +4 -0
- package/dist/original/components/map/common.js.map +1 -0
- package/dist/original/components/map/createMapContext.js +34 -0
- package/dist/original/components/map/createMapContext.js.map +1 -0
- package/dist/original/components/map/handler.js +50 -0
- package/dist/original/components/map/handler.js.map +1 -0
- package/dist/original/components/map/index.js +329 -0
- package/dist/original/components/map/index.js.map +1 -0
- package/dist/original/components/picker/index.js +110 -38
- package/dist/original/components/picker/index.js.map +1 -1
- package/dist/original/components/picker/picker-group.js +400 -111
- package/dist/original/components/picker/picker-group.js.map +1 -1
- package/dist/original/components/picker/style/index.scss +9 -8
- package/dist/original/components/scroll-view/index.js +80 -36
- package/dist/original/components/scroll-view/index.js.map +1 -1
- package/dist/original/components/switch/index.js +125 -0
- package/dist/original/components/switch/index.js.map +1 -0
- package/dist/original/components/switch/style/index.scss +35 -0
- package/dist/original/contexts/ScrollElementContext.js +6 -0
- package/dist/original/contexts/ScrollElementContext.js.map +1 -0
- package/dist/original/index.js +5 -1
- package/dist/original/index.js.map +1 -1
- package/dist/solid/components/image/index.js +5 -3
- package/dist/solid/components/image/index.js.map +1 -1
- package/dist/solid/components/picker/index.js +121 -45
- package/dist/solid/components/picker/index.js.map +1 -1
- package/dist/solid/components/picker/picker-group.js +423 -135
- package/dist/solid/components/picker/picker-group.js.map +1 -1
- package/dist/solid/components/scroll-view/index.js +84 -40
- package/dist/solid/components/scroll-view/index.js.map +1 -1
- package/dist/solid/contexts/ScrollElementContext.js +6 -0
- package/dist/solid/contexts/ScrollElementContext.js.map +1 -0
- package/dist/solid/index.css +1 -1
- package/package.json +8 -6
|
@@ -3,6 +3,32 @@ import Taro from '@tarojs/taro';
|
|
|
3
3
|
import * as React from 'react';
|
|
4
4
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
5
5
|
|
|
6
|
+
function requestAccessibilityFocusOnView(node) {
|
|
7
|
+
if (node == null) return;
|
|
8
|
+
const fn = Taro.setAccessibilityFocus;
|
|
9
|
+
if (typeof fn !== 'function') return;
|
|
10
|
+
fn({
|
|
11
|
+
viewRef: {
|
|
12
|
+
current: node
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
/** 部分端同 id 连续 scrollIntoView 不生效:先清空再在下一宏任务设回目标 id */
|
|
17
|
+
function usePickerItemScrollIntoView() {
|
|
18
|
+
const [scrollIntoView, setScrollIntoView] = React.useState('');
|
|
19
|
+
const pulseRef = React.useRef(0);
|
|
20
|
+
const scrollToItemId = React.useCallback(itemId => {
|
|
21
|
+
pulseRef.current += 1;
|
|
22
|
+
const token = pulseRef.current;
|
|
23
|
+
setScrollIntoView('');
|
|
24
|
+
setTimeout(() => {
|
|
25
|
+
if (pulseRef.current !== token) return;
|
|
26
|
+
setScrollIntoView(itemId);
|
|
27
|
+
}, 0);
|
|
28
|
+
}, []);
|
|
29
|
+
return [scrollIntoView, scrollToItemId];
|
|
30
|
+
}
|
|
31
|
+
// 定义常量
|
|
6
32
|
const PICKER_LINE_HEIGHT = 34; // px
|
|
7
33
|
const PICKER_VISIBLE_ITEMS = 7; // 可见行数
|
|
8
34
|
const PICKER_BLANK_ITEMS = 3; // 空白行数
|
|
@@ -12,6 +38,25 @@ const getIndicatorStyle = lineColor => {
|
|
|
12
38
|
borderBottomColor: lineColor
|
|
13
39
|
};
|
|
14
40
|
};
|
|
41
|
+
// 大屏方案版本要求
|
|
42
|
+
const MIN_DESIGN_APP_VERSION = 16;
|
|
43
|
+
// 判断是否启用测量值缩放适配(true=启用, false=使用系统侧缩放)
|
|
44
|
+
const resolveUseMeasuredScale = res => {
|
|
45
|
+
// H5/weapp 不参与大屏系数
|
|
46
|
+
if (process.env.TARO_ENV === 'h5' || process.env.TARO_ENV === 'weapp') {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
const designAppVersionRaw = res.designAppVersion;
|
|
50
|
+
const designAppVersionMajor = designAppVersionRaw != null ? parseInt(String(designAppVersionRaw).trim(), 10) : Number.NaN;
|
|
51
|
+
if (!Number.isFinite(designAppVersionMajor) || designAppVersionMajor < MIN_DESIGN_APP_VERSION) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const platform = String(res.platform || '').toLowerCase();
|
|
55
|
+
if (platform === 'harmony' || platform === 'android' || platform === 'ios') {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
};
|
|
15
60
|
// 辅助函数:计算 lengthScaleRatio
|
|
16
61
|
const calculateLengthScaleRatio = res => {
|
|
17
62
|
let lengthScaleRatio = res === null || res === void 0 ? void 0 : res.lengthScaleRatio;
|
|
@@ -50,6 +95,29 @@ const setTargetScrollTopWithScale = function (setTargetScrollTop, baseValue, ran
|
|
|
50
95
|
setTargetScrollTop(finalValue);
|
|
51
96
|
}
|
|
52
97
|
};
|
|
98
|
+
// 根据 scrollTop 计算选中索引
|
|
99
|
+
// 系统侧缩放模式:scrollHeight 已被系统缩放,直接相除即可
|
|
100
|
+
// 自行缩放模式:需要除以 ratio 换算(harmony 特殊处理,ratio 已内嵌在 itemHeight 中)
|
|
101
|
+
const getSelectedIndex = function (scrollTop, itemHeight) {
|
|
102
|
+
let lengthScaleRatio = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
|
|
103
|
+
let useMeasuredScale = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
|
|
104
|
+
if (!useMeasuredScale || process.env.TARO_PLATFORM === 'harmony') {
|
|
105
|
+
return Math.round(scrollTop / itemHeight);
|
|
106
|
+
}
|
|
107
|
+
return Math.round(scrollTop / lengthScaleRatio / itemHeight);
|
|
108
|
+
};
|
|
109
|
+
// 计算单项高度(返回设计稿值)
|
|
110
|
+
const calculateItemHeight = function (scrollView, lengthScaleRatio) {
|
|
111
|
+
let useMeasuredScale = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
112
|
+
if (process.env.TARO_PLATFORM === 'harmony') {
|
|
113
|
+
return useMeasuredScale ? PICKER_LINE_HEIGHT * lengthScaleRatio : PICKER_LINE_HEIGHT;
|
|
114
|
+
}
|
|
115
|
+
if (scrollView && (scrollView === null || scrollView === void 0 ? void 0 : scrollView.scrollHeight)) {
|
|
116
|
+
return useMeasuredScale ? scrollView.scrollHeight / lengthScaleRatio / scrollView.childNodes.length : scrollView.scrollHeight / scrollView.childNodes.length;
|
|
117
|
+
}
|
|
118
|
+
console.warn('Height measurement anomaly');
|
|
119
|
+
return PICKER_LINE_HEIGHT;
|
|
120
|
+
};
|
|
53
121
|
function PickerGroupBasic(props) {
|
|
54
122
|
const {
|
|
55
123
|
range = [],
|
|
@@ -59,69 +127,72 @@ function PickerGroupBasic(props) {
|
|
|
59
127
|
onColumnChange,
|
|
60
128
|
selectedIndex = 0,
|
|
61
129
|
// 使用selectedIndex参数,默认为0
|
|
62
|
-
colors = {}
|
|
130
|
+
colors = {},
|
|
131
|
+
enableClickItemScroll = true
|
|
63
132
|
} = props;
|
|
64
133
|
const indicatorStyle = colors.lineColor ? getIndicatorStyle(colors.lineColor) : null;
|
|
65
134
|
const [targetScrollTop, setTargetScrollTop] = React.useState(0);
|
|
135
|
+
const [scrollIntoView, scrollToItemId] = usePickerItemScrollIntoView();
|
|
66
136
|
const scrollViewRef = React.useRef(null);
|
|
67
137
|
const itemRefs = React.useRef([]);
|
|
138
|
+
const selectedIndexPropRef = React.useRef(selectedIndex);
|
|
139
|
+
selectedIndexPropRef.current = selectedIndex;
|
|
140
|
+
const syncScrollFromPropsRef = React.useRef(false);
|
|
141
|
+
const pendingScrollSettleFocusRef = React.useRef(false);
|
|
68
142
|
// 使用selectedIndex初始化当前索引
|
|
69
143
|
const [currentIndex, setCurrentIndex] = React.useState(selectedIndex);
|
|
70
144
|
// 触摸状态用于优化用户体验
|
|
71
|
-
const
|
|
145
|
+
const isTouchingRef = React.useRef(false);
|
|
72
146
|
const lengthScaleRatioRef = React.useRef(1);
|
|
147
|
+
const useMeasuredScaleRef = React.useRef(false);
|
|
73
148
|
const itemHeightRef = React.useRef(PICKER_LINE_HEIGHT);
|
|
74
|
-
// 初始化时计算 lengthScaleRatio
|
|
149
|
+
// 初始化时计算 lengthScaleRatio 并判定缩放模式
|
|
75
150
|
React.useEffect(() => {
|
|
76
151
|
Taro.getSystemInfo({
|
|
77
152
|
success: res => {
|
|
78
153
|
lengthScaleRatioRef.current = calculateLengthScaleRatio(res);
|
|
154
|
+
useMeasuredScaleRef.current = resolveUseMeasuredScale(res);
|
|
155
|
+
itemHeightRef.current = calculateItemHeight(scrollViewRef.current, lengthScaleRatioRef.current, useMeasuredScaleRef.current);
|
|
79
156
|
},
|
|
80
157
|
fail: () => {
|
|
81
|
-
// 失败时使用默认值 1
|
|
82
158
|
lengthScaleRatioRef.current = 1;
|
|
159
|
+
useMeasuredScaleRef.current = false;
|
|
83
160
|
}
|
|
84
161
|
});
|
|
85
162
|
}, []);
|
|
86
163
|
React.useEffect(() => {
|
|
87
|
-
|
|
88
|
-
if (process.env.TARO_PLATFORM === 'harmony') {
|
|
89
|
-
itemHeightRef.current = PICKER_LINE_HEIGHT * lengthScaleRatioRef.current;
|
|
90
|
-
} else {
|
|
91
|
-
if (scrollViewRef.current && ((_a = scrollViewRef.current) === null || _a === void 0 ? void 0 : _a.scrollHeight)) {
|
|
92
|
-
itemHeightRef.current = scrollViewRef.current.scrollHeight / scrollViewRef.current.childNodes.length;
|
|
93
|
-
} else {
|
|
94
|
-
console.warn('Height measurement anomaly');
|
|
95
|
-
}
|
|
96
|
-
}
|
|
164
|
+
itemHeightRef.current = calculateItemHeight(scrollViewRef.current, lengthScaleRatioRef.current, useMeasuredScaleRef.current);
|
|
97
165
|
}, [range.length]); // 只在range长度变化时重新计算
|
|
98
|
-
//
|
|
99
|
-
const getSelectedIndex = scrollTop => {
|
|
100
|
-
return Math.round(scrollTop / itemHeightRef.current);
|
|
101
|
-
};
|
|
102
|
-
// 当selectedIndex变化时,调整滚动位置
|
|
166
|
+
// props 的 selectedIndex 变化:滚至对应项并短时标记程序化滚动(syncScrollFromPropsRef)
|
|
103
167
|
React.useEffect(() => {
|
|
104
|
-
if (scrollViewRef.current && range.length > 0 && !
|
|
168
|
+
if (scrollViewRef.current && range.length > 0 && !isTouchingRef.current) {
|
|
169
|
+
syncScrollFromPropsRef.current = true;
|
|
105
170
|
const baseValue = selectedIndex * itemHeightRef.current;
|
|
106
171
|
setTargetScrollTopWithScale(setTargetScrollTop, baseValue, undefined, lengthScaleRatioRef.current);
|
|
107
172
|
setCurrentIndex(selectedIndex);
|
|
173
|
+
const tid = setTimeout(() => {
|
|
174
|
+
syncScrollFromPropsRef.current = false;
|
|
175
|
+
}, 400);
|
|
176
|
+
return () => clearTimeout(tid);
|
|
108
177
|
}
|
|
109
178
|
}, [selectedIndex, range]);
|
|
110
179
|
// 是否处于归中状态
|
|
111
180
|
const isCenterTimerId = React.useRef(null);
|
|
112
|
-
//
|
|
181
|
+
// 滚动静止后归中并同步选中
|
|
113
182
|
const handleScrollEnd = () => {
|
|
114
183
|
if (!scrollViewRef.current) return;
|
|
115
184
|
if (isCenterTimerId.current) {
|
|
116
185
|
clearTimeout(isCenterTimerId.current);
|
|
117
186
|
isCenterTimerId.current = null;
|
|
118
187
|
}
|
|
119
|
-
//
|
|
188
|
+
// 100ms 内无新滚动则归中并提交索引
|
|
120
189
|
isCenterTimerId.current = setTimeout(() => {
|
|
121
190
|
if (!scrollViewRef.current) return;
|
|
122
191
|
const scrollTop = scrollViewRef.current.scrollTop;
|
|
123
|
-
const newIndex = getSelectedIndex(scrollTop);
|
|
124
|
-
|
|
192
|
+
const newIndex = getSelectedIndex(scrollTop, itemHeightRef.current, lengthScaleRatioRef.current, useMeasuredScaleRef.current);
|
|
193
|
+
const allowA11yFocus = !syncScrollFromPropsRef.current;
|
|
194
|
+
const shouldFocusOnScrollSettle = pendingScrollSettleFocusRef.current;
|
|
195
|
+
isTouchingRef.current = false;
|
|
125
196
|
const baseValue = newIndex * itemHeightRef.current;
|
|
126
197
|
const randomOffset = Math.random() * 0.001; // 随机数为了在一个项内滚动时强制刷新
|
|
127
198
|
setTargetScrollTopWithScale(setTargetScrollTop, baseValue, randomOffset, lengthScaleRatioRef.current);
|
|
@@ -130,10 +201,15 @@ function PickerGroupBasic(props) {
|
|
|
130
201
|
columnId,
|
|
131
202
|
index: newIndex
|
|
132
203
|
});
|
|
204
|
+
pendingScrollSettleFocusRef.current = false;
|
|
205
|
+
if (allowA11yFocus && shouldFocusOnScrollSettle) {
|
|
206
|
+
requestAccessibilityFocusOnView(itemRefs.current[newIndex]);
|
|
207
|
+
}
|
|
208
|
+
syncScrollFromPropsRef.current = false;
|
|
133
209
|
isCenterTimerId.current = null;
|
|
134
210
|
}, 100);
|
|
135
211
|
};
|
|
136
|
-
//
|
|
212
|
+
// 滚动中:按 scrollTop 更新高亮索引
|
|
137
213
|
const handleScroll = () => {
|
|
138
214
|
if (!scrollViewRef.current) return;
|
|
139
215
|
if (isCenterTimerId.current) {
|
|
@@ -141,7 +217,17 @@ function PickerGroupBasic(props) {
|
|
|
141
217
|
isCenterTimerId.current = null;
|
|
142
218
|
}
|
|
143
219
|
const scrollTop = scrollViewRef.current.scrollTop;
|
|
144
|
-
const
|
|
220
|
+
const ih = itemHeightRef.current;
|
|
221
|
+
const newIndex = getSelectedIndex(scrollTop, ih, lengthScaleRatioRef.current, useMeasuredScaleRef.current);
|
|
222
|
+
const spi = selectedIndexPropRef.current;
|
|
223
|
+
if (newIndex !== spi) {
|
|
224
|
+
if (!syncScrollFromPropsRef.current || isTouchingRef.current) {
|
|
225
|
+
pendingScrollSettleFocusRef.current = true;
|
|
226
|
+
}
|
|
227
|
+
if (isTouchingRef.current) {
|
|
228
|
+
syncScrollFromPropsRef.current = false;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
145
231
|
if (newIndex !== currentIndex) {
|
|
146
232
|
setCurrentIndex(newIndex);
|
|
147
233
|
}
|
|
@@ -153,6 +239,9 @@ function PickerGroupBasic(props) {
|
|
|
153
239
|
id: `picker-item-${columnId}-${index}`,
|
|
154
240
|
ref: el => itemRefs.current[index] = el,
|
|
155
241
|
className: `taro-picker__item${index === currentIndex ? ' taro-picker__item--selected' : ''}`,
|
|
242
|
+
...(enableClickItemScroll ? {
|
|
243
|
+
onClick: () => scrollToItemId(`picker-item-${columnId}-${index}`)
|
|
244
|
+
} : {}),
|
|
156
245
|
style: {
|
|
157
246
|
height: PICKER_LINE_HEIGHT,
|
|
158
247
|
color: index === currentIndex ? colors.itemSelectedColor || undefined : colors.itemDefaultColor || undefined
|
|
@@ -171,6 +260,10 @@ function PickerGroupBasic(props) {
|
|
|
171
260
|
height: PICKER_LINE_HEIGHT
|
|
172
261
|
}
|
|
173
262
|
}, `blank-bottom-${idx}`))];
|
|
263
|
+
const clickScrollViewProps = enableClickItemScroll ? {
|
|
264
|
+
scrollIntoView,
|
|
265
|
+
scrollIntoViewAlignment: 'center'
|
|
266
|
+
} : {};
|
|
174
267
|
return /*#__PURE__*/jsxs(View, {
|
|
175
268
|
className: "taro-picker__group",
|
|
176
269
|
children: [/*#__PURE__*/jsx(View, {
|
|
@@ -189,8 +282,11 @@ function PickerGroupBasic(props) {
|
|
|
189
282
|
height: PICKER_LINE_HEIGHT * PICKER_VISIBLE_ITEMS
|
|
190
283
|
},
|
|
191
284
|
scrollTop: targetScrollTop,
|
|
285
|
+
...clickScrollViewProps,
|
|
192
286
|
onScroll: handleScroll,
|
|
193
|
-
onTouchStart: () =>
|
|
287
|
+
onTouchStart: () => {
|
|
288
|
+
isTouchingRef.current = true;
|
|
289
|
+
},
|
|
194
290
|
onScrollEnd: handleScrollEnd,
|
|
195
291
|
scrollWithAnimation: true,
|
|
196
292
|
children: realPickerItems
|
|
@@ -205,78 +301,189 @@ function PickerGroupTime(props) {
|
|
|
205
301
|
columnId,
|
|
206
302
|
updateIndex,
|
|
207
303
|
selectedIndex = 0,
|
|
208
|
-
colors = {}
|
|
304
|
+
colors = {},
|
|
305
|
+
timeA11yLimitFocus,
|
|
306
|
+
onTimeA11yLimitFocusConsumed,
|
|
307
|
+
timeA11yLimitEventNonce,
|
|
308
|
+
enableClickItemScroll = true
|
|
209
309
|
} = props;
|
|
210
310
|
const indicatorStyle = colors.lineColor ? getIndicatorStyle(colors.lineColor) : null;
|
|
211
311
|
const [targetScrollTop, setTargetScrollTop] = React.useState(0);
|
|
312
|
+
const [scrollIntoView, scrollToItemId] = usePickerItemScrollIntoView();
|
|
212
313
|
const scrollViewRef = React.useRef(null);
|
|
213
314
|
const itemRefs = React.useRef([]);
|
|
315
|
+
const selectedIndexPropRef = React.useRef(selectedIndex);
|
|
316
|
+
selectedIndexPropRef.current = selectedIndex;
|
|
317
|
+
const syncScrollFromPropsRef = React.useRef(false);
|
|
318
|
+
const prevLimitEventNonceRef = React.useRef(undefined);
|
|
319
|
+
const suppressScrollSettleFocusNonceRef = React.useRef(null);
|
|
320
|
+
const pendingScrollSettleFocusRef = React.useRef(false);
|
|
321
|
+
const pendingLimitFocusRef = React.useRef(null);
|
|
322
|
+
const limitFocusTimerRef = React.useRef(null);
|
|
214
323
|
const [currentIndex, setCurrentIndex] = React.useState(selectedIndex);
|
|
215
|
-
const
|
|
324
|
+
const isTouchingRef = React.useRef(false);
|
|
216
325
|
const lengthScaleRatioRef = React.useRef(1);
|
|
326
|
+
const useMeasuredScaleRef = React.useRef(false);
|
|
217
327
|
const itemHeightRef = React.useRef(PICKER_LINE_HEIGHT);
|
|
218
|
-
|
|
328
|
+
const clearLimitFocusTimer = React.useCallback(() => {
|
|
329
|
+
if (limitFocusTimerRef.current) {
|
|
330
|
+
clearTimeout(limitFocusTimerRef.current);
|
|
331
|
+
limitFocusTimerRef.current = null;
|
|
332
|
+
}
|
|
333
|
+
}, []);
|
|
334
|
+
const tryFocusPendingLimit = React.useCallback(() => {
|
|
335
|
+
const pending = pendingLimitFocusRef.current;
|
|
336
|
+
const scrollView = scrollViewRef.current;
|
|
337
|
+
if (!pending || !scrollView) return;
|
|
338
|
+
const visualIndex = getSelectedIndex(scrollView.scrollTop, itemHeightRef.current, lengthScaleRatioRef.current, useMeasuredScaleRef.current);
|
|
339
|
+
const isStable = visualIndex === pending.index;
|
|
340
|
+
if (!isStable) {
|
|
341
|
+
if (pending.attempts >= 20) {
|
|
342
|
+
pendingLimitFocusRef.current = null;
|
|
343
|
+
if (suppressScrollSettleFocusNonceRef.current === pending.nonce) {
|
|
344
|
+
suppressScrollSettleFocusNonceRef.current = null;
|
|
345
|
+
}
|
|
346
|
+
onTimeA11yLimitFocusConsumed === null || onTimeA11yLimitFocusConsumed === void 0 ? void 0 : onTimeA11yLimitFocusConsumed();
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
pending.attempts += 1;
|
|
350
|
+
clearLimitFocusTimer();
|
|
351
|
+
limitFocusTimerRef.current = setTimeout(() => {
|
|
352
|
+
tryFocusPendingLimit();
|
|
353
|
+
}, 50);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const node = itemRefs.current[pending.index];
|
|
357
|
+
pendingLimitFocusRef.current = null;
|
|
358
|
+
if (suppressScrollSettleFocusNonceRef.current === pending.nonce) {
|
|
359
|
+
suppressScrollSettleFocusNonceRef.current = null;
|
|
360
|
+
}
|
|
361
|
+
clearLimitFocusTimer();
|
|
362
|
+
requestAccessibilityFocusOnView(node);
|
|
363
|
+
onTimeA11yLimitFocusConsumed === null || onTimeA11yLimitFocusConsumed === void 0 ? void 0 : onTimeA11yLimitFocusConsumed();
|
|
364
|
+
}, [clearLimitFocusTimer, onTimeA11yLimitFocusConsumed]);
|
|
365
|
+
const schedulePendingLimitFocus = React.useCallback(() => {
|
|
366
|
+
if (!pendingLimitFocusRef.current) return;
|
|
367
|
+
clearLimitFocusTimer();
|
|
368
|
+
limitFocusTimerRef.current = setTimeout(() => {
|
|
369
|
+
tryFocusPendingLimit();
|
|
370
|
+
}, 100);
|
|
371
|
+
}, [clearLimitFocusTimer, tryFocusPendingLimit]);
|
|
372
|
+
React.useEffect(() => clearLimitFocusTimer, [clearLimitFocusTimer]);
|
|
373
|
+
// 初始化时计算 lengthScaleRatio 并判定缩放模式
|
|
219
374
|
React.useEffect(() => {
|
|
220
375
|
Taro.getSystemInfo({
|
|
221
376
|
success: res => {
|
|
222
377
|
lengthScaleRatioRef.current = calculateLengthScaleRatio(res);
|
|
378
|
+
useMeasuredScaleRef.current = resolveUseMeasuredScale(res);
|
|
379
|
+
itemHeightRef.current = calculateItemHeight(scrollViewRef.current, lengthScaleRatioRef.current, useMeasuredScaleRef.current);
|
|
223
380
|
},
|
|
224
381
|
fail: () => {
|
|
225
|
-
// 失败时使用默认值 1
|
|
226
382
|
lengthScaleRatioRef.current = 1;
|
|
383
|
+
useMeasuredScaleRef.current = false;
|
|
227
384
|
}
|
|
228
385
|
});
|
|
229
386
|
}, []);
|
|
230
387
|
React.useEffect(() => {
|
|
231
|
-
|
|
232
|
-
if (process.env.TARO_PLATFORM === 'harmony') {
|
|
233
|
-
itemHeightRef.current = PICKER_LINE_HEIGHT * lengthScaleRatioRef.current;
|
|
234
|
-
} else {
|
|
235
|
-
if (scrollViewRef.current && ((_a = scrollViewRef.current) === null || _a === void 0 ? void 0 : _a.scrollHeight)) {
|
|
236
|
-
itemHeightRef.current = scrollViewRef.current.scrollHeight / scrollViewRef.current.childNodes.length;
|
|
237
|
-
} else {
|
|
238
|
-
console.warn('Height measurement anomaly');
|
|
239
|
-
}
|
|
240
|
-
}
|
|
388
|
+
itemHeightRef.current = calculateItemHeight(scrollViewRef.current, lengthScaleRatioRef.current, useMeasuredScaleRef.current);
|
|
241
389
|
}, [range.length]); // 只在range长度变化时重新计算
|
|
242
|
-
|
|
243
|
-
return Math.round(scrollTop / itemHeightRef.current);
|
|
244
|
-
};
|
|
245
|
-
// 当selectedIndex变化时,调整滚动位置
|
|
390
|
+
// props 的 selectedIndex 变化:滚至对应项并短时标记程序化滚动(syncScrollFromPropsRef)
|
|
246
391
|
React.useEffect(() => {
|
|
247
|
-
if (scrollViewRef.current && range.length > 0 && !
|
|
392
|
+
if (scrollViewRef.current && range.length > 0 && !isTouchingRef.current) {
|
|
393
|
+
syncScrollFromPropsRef.current = true;
|
|
248
394
|
const baseValue = selectedIndex * itemHeightRef.current;
|
|
249
395
|
setTargetScrollTopWithScale(setTargetScrollTop, baseValue, undefined, lengthScaleRatioRef.current);
|
|
250
396
|
setCurrentIndex(selectedIndex);
|
|
397
|
+
const tid = setTimeout(() => {
|
|
398
|
+
syncScrollFromPropsRef.current = false;
|
|
399
|
+
}, 400);
|
|
400
|
+
return () => clearTimeout(tid);
|
|
251
401
|
}
|
|
252
402
|
}, [selectedIndex, range]);
|
|
403
|
+
// time 限位 nonce 变更:强制对齐 scrollTop(避免 remount 打断读屏焦点)
|
|
404
|
+
React.useEffect(() => {
|
|
405
|
+
if (!timeA11yLimitEventNonce) return;
|
|
406
|
+
if (!scrollViewRef.current || range.length === 0 || isTouchingRef.current) return;
|
|
407
|
+
syncScrollFromPropsRef.current = true;
|
|
408
|
+
const baseValue = selectedIndex * itemHeightRef.current;
|
|
409
|
+
setTargetScrollTopWithScale(setTargetScrollTop, baseValue, Math.random() * 0.001, lengthScaleRatioRef.current);
|
|
410
|
+
setCurrentIndex(selectedIndex);
|
|
411
|
+
const tid = setTimeout(() => {
|
|
412
|
+
syncScrollFromPropsRef.current = false;
|
|
413
|
+
}, 400);
|
|
414
|
+
return () => clearTimeout(tid);
|
|
415
|
+
}, [timeA11yLimitEventNonce]); // 仅依赖 nonce,其余用 ref
|
|
416
|
+
React.useLayoutEffect(() => {
|
|
417
|
+
if (timeA11yLimitEventNonce === undefined) return;
|
|
418
|
+
const prev = prevLimitEventNonceRef.current;
|
|
419
|
+
prevLimitEventNonceRef.current = timeA11yLimitEventNonce;
|
|
420
|
+
if (timeA11yLimitEventNonce > 0 && (prev === undefined || timeA11yLimitEventNonce > prev)) {
|
|
421
|
+
suppressScrollSettleFocusNonceRef.current = timeA11yLimitEventNonce;
|
|
422
|
+
}
|
|
423
|
+
}, [timeA11yLimitEventNonce]);
|
|
424
|
+
// 限位后登记本列待聚焦项,稳定后再 requestAccessibilityFocus
|
|
425
|
+
React.useEffect(() => {
|
|
426
|
+
const req = timeA11yLimitFocus;
|
|
427
|
+
if (!req || req.columnId !== columnId) return;
|
|
428
|
+
const idx = selectedIndex;
|
|
429
|
+
const nonce = req.nonce;
|
|
430
|
+
pendingScrollSettleFocusRef.current = false;
|
|
431
|
+
pendingLimitFocusRef.current = {
|
|
432
|
+
index: idx,
|
|
433
|
+
nonce,
|
|
434
|
+
attempts: 0
|
|
435
|
+
};
|
|
436
|
+
schedulePendingLimitFocus();
|
|
437
|
+
}, [timeA11yLimitFocus, columnId, selectedIndex, schedulePendingLimitFocus]);
|
|
253
438
|
// 是否处于归中状态
|
|
254
439
|
const isCenterTimerId = React.useRef(null);
|
|
255
|
-
//
|
|
440
|
+
// 滚动静止后归中并同步选中
|
|
256
441
|
const handleScrollEnd = () => {
|
|
257
442
|
if (!scrollViewRef.current) return;
|
|
258
443
|
if (isCenterTimerId.current) {
|
|
259
444
|
clearTimeout(isCenterTimerId.current);
|
|
260
445
|
isCenterTimerId.current = null;
|
|
261
446
|
}
|
|
262
|
-
//
|
|
447
|
+
// 100ms 内无新滚动则归中并提交索引
|
|
263
448
|
isCenterTimerId.current = setTimeout(() => {
|
|
264
449
|
if (!scrollViewRef.current) return;
|
|
265
450
|
const scrollTop = scrollViewRef.current.scrollTop;
|
|
266
|
-
const newIndex = getSelectedIndex(scrollTop);
|
|
267
|
-
|
|
268
|
-
|
|
451
|
+
const newIndex = getSelectedIndex(scrollTop, itemHeightRef.current, lengthScaleRatioRef.current, useMeasuredScaleRef.current);
|
|
452
|
+
const allowA11yFocus = !syncScrollFromPropsRef.current;
|
|
453
|
+
const shouldFocusOnScrollSettle = pendingScrollSettleFocusRef.current;
|
|
454
|
+
isTouchingRef.current = false;
|
|
269
455
|
const isLimited = Boolean(updateIndex(newIndex, columnId, true));
|
|
270
|
-
|
|
456
|
+
const hasPendingLimitFocus = pendingLimitFocusRef.current != null;
|
|
457
|
+
if (isLimited) {
|
|
458
|
+
pendingScrollSettleFocusRef.current = false;
|
|
459
|
+
}
|
|
271
460
|
if (!isLimited) {
|
|
272
461
|
const baseValue = newIndex * itemHeightRef.current;
|
|
273
462
|
const randomOffset = Math.random() * 0.001;
|
|
274
463
|
setTargetScrollTopWithScale(setTargetScrollTop, baseValue, randomOffset, lengthScaleRatioRef.current);
|
|
275
464
|
}
|
|
465
|
+
if (!isLimited && hasPendingLimitFocus) {
|
|
466
|
+
pendingScrollSettleFocusRef.current = false;
|
|
467
|
+
schedulePendingLimitFocus();
|
|
468
|
+
syncScrollFromPropsRef.current = false;
|
|
469
|
+
isCenterTimerId.current = null;
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
if (!isLimited && pendingLimitFocusRef.current == null && suppressScrollSettleFocusNonceRef.current != null) {
|
|
473
|
+
suppressScrollSettleFocusNonceRef.current = null;
|
|
474
|
+
}
|
|
475
|
+
const suppressByLimitNonce = suppressScrollSettleFocusNonceRef.current != null;
|
|
476
|
+
if (!isLimited && allowA11yFocus && shouldFocusOnScrollSettle) {
|
|
477
|
+
pendingScrollSettleFocusRef.current = false;
|
|
478
|
+
if (!suppressByLimitNonce) {
|
|
479
|
+
requestAccessibilityFocusOnView(itemRefs.current[newIndex]);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
syncScrollFromPropsRef.current = false;
|
|
276
483
|
isCenterTimerId.current = null;
|
|
277
484
|
}, 100);
|
|
278
485
|
};
|
|
279
|
-
//
|
|
486
|
+
// 滚动中:按 scrollTop 更新高亮索引
|
|
280
487
|
const handleScroll = () => {
|
|
281
488
|
if (!scrollViewRef.current) return;
|
|
282
489
|
if (isCenterTimerId.current) {
|
|
@@ -284,10 +491,23 @@ function PickerGroupTime(props) {
|
|
|
284
491
|
isCenterTimerId.current = null;
|
|
285
492
|
}
|
|
286
493
|
const scrollTop = scrollViewRef.current.scrollTop;
|
|
287
|
-
const
|
|
494
|
+
const ih = itemHeightRef.current;
|
|
495
|
+
const newIndex = getSelectedIndex(scrollTop, ih, lengthScaleRatioRef.current, useMeasuredScaleRef.current);
|
|
496
|
+
const spi = selectedIndexPropRef.current;
|
|
497
|
+
if (newIndex !== spi) {
|
|
498
|
+
if (!syncScrollFromPropsRef.current || isTouchingRef.current) {
|
|
499
|
+
pendingScrollSettleFocusRef.current = true;
|
|
500
|
+
}
|
|
501
|
+
if (isTouchingRef.current) {
|
|
502
|
+
syncScrollFromPropsRef.current = false;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
288
505
|
if (newIndex !== currentIndex) {
|
|
289
506
|
setCurrentIndex(newIndex);
|
|
290
507
|
}
|
|
508
|
+
if (pendingLimitFocusRef.current) {
|
|
509
|
+
schedulePendingLimitFocus();
|
|
510
|
+
}
|
|
291
511
|
};
|
|
292
512
|
// 渲染选项
|
|
293
513
|
const pickerItem = range.map((item, index) => {
|
|
@@ -296,6 +516,9 @@ function PickerGroupTime(props) {
|
|
|
296
516
|
id: `picker-item-${columnId}-${index}`,
|
|
297
517
|
ref: el => itemRefs.current[index] = el,
|
|
298
518
|
className: `taro-picker__item${index === currentIndex ? ' taro-picker__item--selected' : ''}`,
|
|
519
|
+
...(enableClickItemScroll ? {
|
|
520
|
+
onClick: () => scrollToItemId(`picker-item-${columnId}-${index}`)
|
|
521
|
+
} : {}),
|
|
299
522
|
style: {
|
|
300
523
|
height: PICKER_LINE_HEIGHT,
|
|
301
524
|
color: index === currentIndex ? colors.itemSelectedColor || undefined : colors.itemDefaultColor || undefined
|
|
@@ -314,6 +537,10 @@ function PickerGroupTime(props) {
|
|
|
314
537
|
height: PICKER_LINE_HEIGHT
|
|
315
538
|
}
|
|
316
539
|
}, `blank-bottom-${idx}`))];
|
|
540
|
+
const clickScrollViewProps = enableClickItemScroll ? {
|
|
541
|
+
scrollIntoView,
|
|
542
|
+
scrollIntoViewAlignment: 'center'
|
|
543
|
+
} : {};
|
|
317
544
|
return /*#__PURE__*/jsxs(View, {
|
|
318
545
|
className: "taro-picker__group",
|
|
319
546
|
children: [/*#__PURE__*/jsx(View, {
|
|
@@ -332,8 +559,11 @@ function PickerGroupTime(props) {
|
|
|
332
559
|
height: PICKER_LINE_HEIGHT * PICKER_VISIBLE_ITEMS
|
|
333
560
|
},
|
|
334
561
|
scrollTop: targetScrollTop,
|
|
562
|
+
...clickScrollViewProps,
|
|
335
563
|
onScroll: handleScroll,
|
|
336
|
-
onTouchStart: () =>
|
|
564
|
+
onTouchStart: () => {
|
|
565
|
+
isTouchingRef.current = true;
|
|
566
|
+
},
|
|
337
567
|
onScrollEnd: handleScrollEnd,
|
|
338
568
|
scrollWithAnimation: true,
|
|
339
569
|
children: realPickerItems
|
|
@@ -347,65 +577,70 @@ function PickerGroupDate(props) {
|
|
|
347
577
|
columnId,
|
|
348
578
|
updateDay,
|
|
349
579
|
selectedIndex = 0,
|
|
350
|
-
colors = {}
|
|
580
|
+
colors = {},
|
|
581
|
+
enableClickItemScroll = true
|
|
351
582
|
} = props;
|
|
352
583
|
const indicatorStyle = colors.lineColor ? getIndicatorStyle(colors.lineColor) : null;
|
|
353
584
|
const [targetScrollTop, setTargetScrollTop] = React.useState(0);
|
|
585
|
+
const [scrollIntoView, scrollToItemId] = usePickerItemScrollIntoView();
|
|
354
586
|
const scrollViewRef = React.useRef(null);
|
|
587
|
+
const itemRefs = React.useRef([]);
|
|
588
|
+
const selectedIndexPropRef = React.useRef(selectedIndex);
|
|
589
|
+
selectedIndexPropRef.current = selectedIndex;
|
|
590
|
+
const syncScrollFromPropsRef = React.useRef(false);
|
|
591
|
+
const pendingScrollSettleFocusRef = React.useRef(false);
|
|
355
592
|
const [currentIndex, setCurrentIndex] = React.useState(selectedIndex);
|
|
356
|
-
const
|
|
593
|
+
const isTouchingRef = React.useRef(false);
|
|
357
594
|
const lengthScaleRatioRef = React.useRef(1);
|
|
595
|
+
const useMeasuredScaleRef = React.useRef(false);
|
|
358
596
|
const itemHeightRef = React.useRef(PICKER_LINE_HEIGHT);
|
|
359
|
-
// 初始化时计算 lengthScaleRatio
|
|
597
|
+
// 初始化时计算 lengthScaleRatio 并判定缩放模式
|
|
360
598
|
React.useEffect(() => {
|
|
361
599
|
Taro.getSystemInfo({
|
|
362
600
|
success: res => {
|
|
363
601
|
lengthScaleRatioRef.current = calculateLengthScaleRatio(res);
|
|
602
|
+
useMeasuredScaleRef.current = resolveUseMeasuredScale(res);
|
|
603
|
+
itemHeightRef.current = calculateItemHeight(scrollViewRef.current, lengthScaleRatioRef.current, useMeasuredScaleRef.current);
|
|
364
604
|
},
|
|
365
605
|
fail: () => {
|
|
366
|
-
// 失败时使用默认值 1
|
|
367
606
|
lengthScaleRatioRef.current = 1;
|
|
607
|
+
useMeasuredScaleRef.current = false;
|
|
368
608
|
}
|
|
369
609
|
});
|
|
370
610
|
}, []);
|
|
371
611
|
React.useEffect(() => {
|
|
372
|
-
|
|
373
|
-
if (process.env.TARO_PLATFORM === 'harmony') {
|
|
374
|
-
itemHeightRef.current = PICKER_LINE_HEIGHT * lengthScaleRatioRef.current;
|
|
375
|
-
} else {
|
|
376
|
-
if (scrollViewRef.current && ((_a = scrollViewRef.current) === null || _a === void 0 ? void 0 : _a.scrollHeight)) {
|
|
377
|
-
itemHeightRef.current = scrollViewRef.current.scrollHeight / scrollViewRef.current.childNodes.length;
|
|
378
|
-
} else {
|
|
379
|
-
console.warn('Height measurement anomaly');
|
|
380
|
-
}
|
|
381
|
-
}
|
|
612
|
+
itemHeightRef.current = calculateItemHeight(scrollViewRef.current, lengthScaleRatioRef.current, useMeasuredScaleRef.current);
|
|
382
613
|
}, [range.length]); // 只在range长度变化时重新计算
|
|
383
|
-
|
|
384
|
-
return Math.round(scrollTop / itemHeightRef.current);
|
|
385
|
-
};
|
|
386
|
-
// 当selectedIndex变化时,调整滚动位置
|
|
614
|
+
// props 的 selectedIndex 变化:滚至对应项并短时标记程序化滚动(syncScrollFromPropsRef)
|
|
387
615
|
React.useEffect(() => {
|
|
388
|
-
if (scrollViewRef.current && range.length > 0 && !
|
|
616
|
+
if (scrollViewRef.current && range.length > 0 && !isTouchingRef.current) {
|
|
617
|
+
syncScrollFromPropsRef.current = true;
|
|
389
618
|
const baseValue = selectedIndex * itemHeightRef.current;
|
|
390
619
|
setTargetScrollTopWithScale(setTargetScrollTop, baseValue, undefined, lengthScaleRatioRef.current);
|
|
391
620
|
setCurrentIndex(selectedIndex);
|
|
621
|
+
const tid = setTimeout(() => {
|
|
622
|
+
syncScrollFromPropsRef.current = false;
|
|
623
|
+
}, 400);
|
|
624
|
+
return () => clearTimeout(tid);
|
|
392
625
|
}
|
|
393
626
|
}, [selectedIndex, range]);
|
|
394
627
|
// 是否处于归中状态
|
|
395
628
|
const isCenterTimerId = React.useRef(null);
|
|
396
|
-
//
|
|
629
|
+
// 滚动静止后归中并同步选中
|
|
397
630
|
const handleScrollEnd = () => {
|
|
398
631
|
if (!scrollViewRef.current) return;
|
|
399
632
|
if (isCenterTimerId.current) {
|
|
400
633
|
clearTimeout(isCenterTimerId.current);
|
|
401
634
|
isCenterTimerId.current = null;
|
|
402
635
|
}
|
|
403
|
-
//
|
|
636
|
+
// 100ms 内无新滚动则归中并提交索引
|
|
404
637
|
isCenterTimerId.current = setTimeout(() => {
|
|
405
638
|
if (!scrollViewRef.current) return;
|
|
406
639
|
const scrollTop = scrollViewRef.current.scrollTop;
|
|
407
|
-
const newIndex = getSelectedIndex(scrollTop);
|
|
408
|
-
|
|
640
|
+
const newIndex = getSelectedIndex(scrollTop, itemHeightRef.current, lengthScaleRatioRef.current, useMeasuredScaleRef.current);
|
|
641
|
+
const allowA11yFocus = !syncScrollFromPropsRef.current;
|
|
642
|
+
const shouldFocusOnScrollSettle = pendingScrollSettleFocusRef.current;
|
|
643
|
+
isTouchingRef.current = false;
|
|
409
644
|
const baseValue = newIndex * itemHeightRef.current;
|
|
410
645
|
const randomOffset = Math.random() * 0.001; // 随机数为了在一个项内滚动时强制刷新
|
|
411
646
|
setTargetScrollTopWithScale(setTargetScrollTop, baseValue, randomOffset, lengthScaleRatioRef.current);
|
|
@@ -416,10 +651,15 @@ function PickerGroupDate(props) {
|
|
|
416
651
|
const numericValue = parseInt(valueText.replace(/[^0-9]/g, ''));
|
|
417
652
|
updateDay(isNaN(numericValue) ? 0 : numericValue, parseInt(columnId));
|
|
418
653
|
}
|
|
654
|
+
pendingScrollSettleFocusRef.current = false;
|
|
655
|
+
if (allowA11yFocus && shouldFocusOnScrollSettle) {
|
|
656
|
+
requestAccessibilityFocusOnView(itemRefs.current[newIndex]);
|
|
657
|
+
}
|
|
658
|
+
syncScrollFromPropsRef.current = false;
|
|
419
659
|
isCenterTimerId.current = null;
|
|
420
660
|
}, 100);
|
|
421
661
|
};
|
|
422
|
-
//
|
|
662
|
+
// 滚动中:按 scrollTop 更新高亮索引
|
|
423
663
|
const handleScroll = () => {
|
|
424
664
|
if (!scrollViewRef.current) return;
|
|
425
665
|
if (isCenterTimerId.current) {
|
|
@@ -427,7 +667,17 @@ function PickerGroupDate(props) {
|
|
|
427
667
|
isCenterTimerId.current = null;
|
|
428
668
|
}
|
|
429
669
|
const scrollTop = scrollViewRef.current.scrollTop;
|
|
430
|
-
const
|
|
670
|
+
const ih = itemHeightRef.current;
|
|
671
|
+
const newIndex = getSelectedIndex(scrollTop, ih, lengthScaleRatioRef.current, useMeasuredScaleRef.current);
|
|
672
|
+
const spi = selectedIndexPropRef.current;
|
|
673
|
+
if (newIndex !== spi) {
|
|
674
|
+
if (!syncScrollFromPropsRef.current || isTouchingRef.current) {
|
|
675
|
+
pendingScrollSettleFocusRef.current = true;
|
|
676
|
+
}
|
|
677
|
+
if (isTouchingRef.current) {
|
|
678
|
+
syncScrollFromPropsRef.current = false;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
431
681
|
if (newIndex !== currentIndex) {
|
|
432
682
|
setCurrentIndex(newIndex);
|
|
433
683
|
}
|
|
@@ -436,7 +686,11 @@ function PickerGroupDate(props) {
|
|
|
436
686
|
const pickerItem = range.map((item, index) => {
|
|
437
687
|
return /*#__PURE__*/jsx(View, {
|
|
438
688
|
id: `picker-item-${columnId}-${index}`,
|
|
689
|
+
ref: el => itemRefs.current[index] = el,
|
|
439
690
|
className: `taro-picker__item${index === currentIndex ? ' taro-picker__item--selected' : ''}`,
|
|
691
|
+
...(enableClickItemScroll ? {
|
|
692
|
+
onClick: () => scrollToItemId(`picker-item-${columnId}-${index}`)
|
|
693
|
+
} : {}),
|
|
440
694
|
style: {
|
|
441
695
|
height: PICKER_LINE_HEIGHT,
|
|
442
696
|
color: index === currentIndex ? colors.itemSelectedColor || undefined : colors.itemDefaultColor || undefined
|
|
@@ -455,6 +709,10 @@ function PickerGroupDate(props) {
|
|
|
455
709
|
height: PICKER_LINE_HEIGHT
|
|
456
710
|
}
|
|
457
711
|
}, `blank-bottom-${idx}`))];
|
|
712
|
+
const clickScrollViewProps = enableClickItemScroll ? {
|
|
713
|
+
scrollIntoView,
|
|
714
|
+
scrollIntoViewAlignment: 'center'
|
|
715
|
+
} : {};
|
|
458
716
|
return /*#__PURE__*/jsxs(View, {
|
|
459
717
|
className: "taro-picker__group",
|
|
460
718
|
children: [/*#__PURE__*/jsx(View, {
|
|
@@ -473,8 +731,11 @@ function PickerGroupDate(props) {
|
|
|
473
731
|
height: PICKER_LINE_HEIGHT * PICKER_VISIBLE_ITEMS
|
|
474
732
|
},
|
|
475
733
|
scrollTop: targetScrollTop,
|
|
734
|
+
...clickScrollViewProps,
|
|
476
735
|
onScroll: handleScroll,
|
|
477
|
-
onTouchStart: () =>
|
|
736
|
+
onTouchStart: () => {
|
|
737
|
+
isTouchingRef.current = true;
|
|
738
|
+
},
|
|
478
739
|
onScrollEnd: handleScrollEnd,
|
|
479
740
|
scrollWithAnimation: true,
|
|
480
741
|
children: realPickerItems
|
|
@@ -490,52 +751,54 @@ function PickerGroupRegion(props) {
|
|
|
490
751
|
updateIndex,
|
|
491
752
|
selectedIndex = 0,
|
|
492
753
|
// 使用selectedIndex参数,默认为0
|
|
493
|
-
colors = {}
|
|
754
|
+
colors = {},
|
|
755
|
+
enableClickItemScroll = true
|
|
494
756
|
} = props;
|
|
495
757
|
const indicatorStyle = colors.lineColor ? getIndicatorStyle(colors.lineColor) : null;
|
|
496
758
|
const scrollViewRef = React.useRef(null);
|
|
497
759
|
const [targetScrollTop, setTargetScrollTop] = React.useState(0);
|
|
760
|
+
const [scrollIntoView, scrollToItemId] = usePickerItemScrollIntoView();
|
|
498
761
|
const [currentIndex, setCurrentIndex] = React.useState(selectedIndex);
|
|
499
|
-
const
|
|
762
|
+
const isTouchingRef = React.useRef(false);
|
|
500
763
|
const lengthScaleRatioRef = React.useRef(1);
|
|
764
|
+
const useMeasuredScaleRef = React.useRef(false);
|
|
501
765
|
const itemHeightRef = React.useRef(PICKER_LINE_HEIGHT);
|
|
502
|
-
const
|
|
503
|
-
|
|
766
|
+
const itemRefs = React.useRef([]);
|
|
767
|
+
const selectedIndexPropRef = React.useRef(selectedIndex);
|
|
768
|
+
selectedIndexPropRef.current = selectedIndex;
|
|
769
|
+
const syncScrollFromPropsRef = React.useRef(false);
|
|
770
|
+
const pendingScrollSettleFocusRef = React.useRef(false);
|
|
771
|
+
// 初始化时计算 lengthScaleRatio 并判定缩放模式
|
|
504
772
|
React.useEffect(() => {
|
|
505
773
|
Taro.getSystemInfo({
|
|
506
774
|
success: res => {
|
|
507
775
|
lengthScaleRatioRef.current = calculateLengthScaleRatio(res);
|
|
776
|
+
useMeasuredScaleRef.current = resolveUseMeasuredScale(res);
|
|
777
|
+
itemHeightRef.current = calculateItemHeight(scrollViewRef.current, lengthScaleRatioRef.current, useMeasuredScaleRef.current);
|
|
508
778
|
},
|
|
509
779
|
fail: () => {
|
|
510
|
-
// 失败时使用默认值 1
|
|
511
780
|
lengthScaleRatioRef.current = 1;
|
|
781
|
+
useMeasuredScaleRef.current = false;
|
|
512
782
|
}
|
|
513
783
|
});
|
|
514
784
|
}, []);
|
|
515
785
|
React.useEffect(() => {
|
|
516
|
-
|
|
517
|
-
if (process.env.TARO_PLATFORM === 'harmony') {
|
|
518
|
-
itemHeightRef.current = PICKER_LINE_HEIGHT * lengthScaleRatioRef.current;
|
|
519
|
-
} else {
|
|
520
|
-
if (scrollViewRef.current && ((_a = scrollViewRef.current) === null || _a === void 0 ? void 0 : _a.scrollHeight)) {
|
|
521
|
-
itemHeightRef.current = scrollViewRef.current.scrollHeight / scrollViewRef.current.childNodes.length;
|
|
522
|
-
} else {
|
|
523
|
-
console.warn('Height measurement anomaly');
|
|
524
|
-
}
|
|
525
|
-
}
|
|
786
|
+
itemHeightRef.current = calculateItemHeight(scrollViewRef.current, lengthScaleRatioRef.current, useMeasuredScaleRef.current);
|
|
526
787
|
}, [range.length]); // 只在range长度变化时重新计算
|
|
527
|
-
|
|
528
|
-
return Math.round(scrollTop / itemHeightRef.current);
|
|
529
|
-
};
|
|
530
|
-
// 当selectedIndex变化时,调整滚动位置
|
|
788
|
+
// props 的 selectedIndex 变化:滚至对应项并短时标记程序化滚动(syncScrollFromPropsRef)
|
|
531
789
|
React.useEffect(() => {
|
|
532
|
-
if (scrollViewRef.current && range.length > 0 && !
|
|
790
|
+
if (scrollViewRef.current && range.length > 0 && !isTouchingRef.current) {
|
|
791
|
+
syncScrollFromPropsRef.current = true;
|
|
533
792
|
const baseValue = selectedIndex * itemHeightRef.current;
|
|
534
|
-
setTargetScrollTopWithScale(setTargetScrollTop, baseValue);
|
|
793
|
+
setTargetScrollTopWithScale(setTargetScrollTop, baseValue, undefined, lengthScaleRatioRef.current);
|
|
535
794
|
setCurrentIndex(selectedIndex);
|
|
795
|
+
const tid = setTimeout(() => {
|
|
796
|
+
syncScrollFromPropsRef.current = false;
|
|
797
|
+
}, 400);
|
|
798
|
+
return () => clearTimeout(tid);
|
|
536
799
|
}
|
|
537
800
|
}, [selectedIndex, range]);
|
|
538
|
-
//
|
|
801
|
+
// 滚动静止后归中(debounce)
|
|
539
802
|
const isCenterTimerId = React.useRef(null);
|
|
540
803
|
const handleScrollEnd = () => {
|
|
541
804
|
if (!scrollViewRef.current) return;
|
|
@@ -543,19 +806,27 @@ function PickerGroupRegion(props) {
|
|
|
543
806
|
clearTimeout(isCenterTimerId.current);
|
|
544
807
|
isCenterTimerId.current = null;
|
|
545
808
|
}
|
|
546
|
-
//
|
|
809
|
+
// 100ms 内无新滚动则归中并提交索引
|
|
547
810
|
isCenterTimerId.current = setTimeout(() => {
|
|
548
811
|
if (!scrollViewRef.current) return;
|
|
549
812
|
const scrollTop = scrollViewRef.current.scrollTop;
|
|
550
|
-
const newIndex = getSelectedIndex(scrollTop);
|
|
551
|
-
|
|
813
|
+
const newIndex = getSelectedIndex(scrollTop, itemHeightRef.current, lengthScaleRatioRef.current, useMeasuredScaleRef.current);
|
|
814
|
+
const allowA11yFocus = !syncScrollFromPropsRef.current;
|
|
815
|
+
const shouldFocusOnScrollSettle = pendingScrollSettleFocusRef.current;
|
|
816
|
+
isTouchingRef.current = false;
|
|
552
817
|
const baseValue = newIndex * itemHeightRef.current;
|
|
553
818
|
const randomOffset = Math.random() * 0.001; // 随机数为了在一个项内滚动时强制刷新
|
|
554
819
|
setTargetScrollTopWithScale(setTargetScrollTop, baseValue, randomOffset, lengthScaleRatioRef.current);
|
|
555
|
-
updateIndex(newIndex, columnId, false,
|
|
820
|
+
updateIndex(newIndex, columnId, false, allowA11yFocus);
|
|
821
|
+
pendingScrollSettleFocusRef.current = false;
|
|
822
|
+
if (allowA11yFocus && shouldFocusOnScrollSettle) {
|
|
823
|
+
requestAccessibilityFocusOnView(itemRefs.current[newIndex]);
|
|
824
|
+
}
|
|
825
|
+
syncScrollFromPropsRef.current = false;
|
|
826
|
+
isCenterTimerId.current = null;
|
|
556
827
|
}, 100);
|
|
557
828
|
};
|
|
558
|
-
//
|
|
829
|
+
// 滚动中:按 scrollTop 更新高亮索引
|
|
559
830
|
const handleScroll = () => {
|
|
560
831
|
if (!scrollViewRef.current) return;
|
|
561
832
|
if (isCenterTimerId.current) {
|
|
@@ -563,7 +834,17 @@ function PickerGroupRegion(props) {
|
|
|
563
834
|
isCenterTimerId.current = null;
|
|
564
835
|
}
|
|
565
836
|
const scrollTop = scrollViewRef.current.scrollTop;
|
|
566
|
-
const
|
|
837
|
+
const ih = itemHeightRef.current;
|
|
838
|
+
const newIndex = getSelectedIndex(scrollTop, ih, lengthScaleRatioRef.current, useMeasuredScaleRef.current);
|
|
839
|
+
const spi = selectedIndexPropRef.current;
|
|
840
|
+
if (newIndex !== spi) {
|
|
841
|
+
if (!syncScrollFromPropsRef.current || isTouchingRef.current) {
|
|
842
|
+
pendingScrollSettleFocusRef.current = true;
|
|
843
|
+
}
|
|
844
|
+
if (isTouchingRef.current) {
|
|
845
|
+
syncScrollFromPropsRef.current = false;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
567
848
|
if (newIndex !== currentIndex) {
|
|
568
849
|
setCurrentIndex(newIndex);
|
|
569
850
|
}
|
|
@@ -573,7 +854,11 @@ function PickerGroupRegion(props) {
|
|
|
573
854
|
const content = rangeKey && item && typeof item === 'object' ? item[rangeKey] : item;
|
|
574
855
|
return /*#__PURE__*/jsx(View, {
|
|
575
856
|
id: `picker-item-${columnId}-${index}`,
|
|
857
|
+
ref: el => itemRefs.current[index] = el,
|
|
576
858
|
className: `taro-picker__item${index === currentIndex ? ' taro-picker__item--selected' : ''}`,
|
|
859
|
+
...(enableClickItemScroll ? {
|
|
860
|
+
onClick: () => scrollToItemId(`picker-item-${columnId}-${index}`)
|
|
861
|
+
} : {}),
|
|
577
862
|
style: {
|
|
578
863
|
height: PICKER_LINE_HEIGHT,
|
|
579
864
|
color: index === currentIndex ? colors.itemSelectedColor || undefined : colors.itemDefaultColor || undefined
|
|
@@ -592,6 +877,10 @@ function PickerGroupRegion(props) {
|
|
|
592
877
|
height: PICKER_LINE_HEIGHT
|
|
593
878
|
}
|
|
594
879
|
}, `blank-bottom-${idx}`))];
|
|
880
|
+
const clickScrollViewProps = enableClickItemScroll ? {
|
|
881
|
+
scrollIntoView,
|
|
882
|
+
scrollIntoViewAlignment: 'center'
|
|
883
|
+
} : {};
|
|
595
884
|
return /*#__PURE__*/jsxs(View, {
|
|
596
885
|
className: "taro-picker__group",
|
|
597
886
|
children: [/*#__PURE__*/jsx(View, {
|
|
@@ -610,10 +899,10 @@ function PickerGroupRegion(props) {
|
|
|
610
899
|
height: PICKER_LINE_HEIGHT * PICKER_VISIBLE_ITEMS
|
|
611
900
|
},
|
|
612
901
|
scrollTop: targetScrollTop,
|
|
902
|
+
...clickScrollViewProps,
|
|
613
903
|
onScroll: handleScroll,
|
|
614
904
|
onTouchStart: () => {
|
|
615
|
-
|
|
616
|
-
isUserBeginScrollRef.current = true;
|
|
905
|
+
isTouchingRef.current = true;
|
|
617
906
|
},
|
|
618
907
|
onScrollEnd: handleScrollEnd,
|
|
619
908
|
scrollWithAnimation: true,
|