@tarojs/components-react 4.1.12-beta.60 → 4.1.12-beta.62
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/picker/index.js +10 -0
- package/dist/components/picker/index.js.map +1 -1
- package/dist/components/picker/picker-group-legacy.js +936 -0
- package/dist/components/picker/picker-group-legacy.js.map +1 -0
- package/dist/components/picker/picker-group-slider.js +1046 -0
- package/dist/components/picker/picker-group-slider.js.map +1 -0
- package/dist/components/picker/picker-group.js +37 -926
- package/dist/components/picker/picker-group.js.map +1 -1
- package/dist/original/components/picker/index.js +10 -0
- package/dist/original/components/picker/index.js.map +1 -1
- package/dist/original/components/picker/picker-group-legacy.js +936 -0
- package/dist/original/components/picker/picker-group-legacy.js.map +1 -0
- package/dist/original/components/picker/picker-group-slider.js +1046 -0
- package/dist/original/components/picker/picker-group-slider.js.map +1 -0
- package/dist/original/components/picker/picker-group.js +37 -926
- package/dist/original/components/picker/picker-group.js.map +1 -1
- package/dist/solid/components/picker/index.js +10 -0
- package/dist/solid/components/picker/index.js.map +1 -1
- package/dist/solid/components/picker/picker-group-legacy.js +952 -0
- package/dist/solid/components/picker/picker-group-legacy.js.map +1 -0
- package/dist/solid/components/picker/picker-group-slider.js +1071 -0
- package/dist/solid/components/picker/picker-group-slider.js.map +1 -0
- package/dist/solid/components/picker/picker-group.js +33 -942
- package/dist/solid/components/picker/picker-group.js.map +1 -1
- package/package.json +6 -6
|
@@ -1,936 +1,47 @@
|
|
|
1
|
-
import { View, ScrollView } from '@tarojs/components';
|
|
2
1
|
import Taro from '@tarojs/taro';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
2
|
+
import 'react';
|
|
3
|
+
import { PickerGroupLegacy } from './picker-group-legacy.js';
|
|
4
|
+
import { PickerGroupSlider } from './picker-group-slider.js';
|
|
5
|
+
export { PickerGroupBasic, PickerGroupDate, PickerGroupRegion, PickerGroupTime } from './picker-group-slider.js';
|
|
6
|
+
import { jsx } from 'react/jsx-runtime';
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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];
|
|
8
|
+
// 启用新版无障碍(slider 遮挡方案)所需的最低 app 版本;低于此版本回退旧版逐项聚焦方案
|
|
9
|
+
const MIN_A11Y_APP_VERSION = '15.9.50';
|
|
10
|
+
// 逐段比较版本号:v1 > v2 返回 1,v1 < v2 返回 -1,相等返回 0(缺失段按 0 处理)
|
|
11
|
+
function compareVersion(v1, v2) {
|
|
12
|
+
const a = String(v1).split('.');
|
|
13
|
+
const b = String(v2).split('.');
|
|
14
|
+
const len = Math.max(a.length, b.length);
|
|
15
|
+
for (let i = 0; i < len; i++) {
|
|
16
|
+
const n1 = parseInt(a[i], 10) || 0;
|
|
17
|
+
const n2 = parseInt(b[i], 10) || 0;
|
|
18
|
+
if (n1 > n2) return 1;
|
|
19
|
+
if (n1 < n2) return -1;
|
|
20
|
+
}
|
|
21
|
+
return 0;
|
|
30
22
|
}
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
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) {
|
|
23
|
+
// 读取运行环境判定是否启用新版无障碍:须同时满足 dynamic 模式 且 app 版本 >= 15.9.50;任一不满足或取不到版本时保守回退旧版(线上稳定实现)
|
|
24
|
+
function resolveUseSliderA11y() {
|
|
25
|
+
// 仅 dynamic 模式启用新版无障碍
|
|
26
|
+
if (process.env.TARO_ENV !== 'dynamic') return false;
|
|
27
|
+
try {
|
|
28
|
+
const version = Taro.getSystemInfoSync().version || '';
|
|
29
|
+
if (!version) return false;
|
|
30
|
+
return compareVersion(version, MIN_A11Y_APP_VERSION) >= 0;
|
|
31
|
+
} catch (_a) {
|
|
52
32
|
return false;
|
|
53
33
|
}
|
|
54
|
-
const platform = String(res.platform || '').toLowerCase();
|
|
55
|
-
if (platform === 'harmony' || platform === 'android' || platform === 'ios') {
|
|
56
|
-
return true;
|
|
57
|
-
}
|
|
58
|
-
return false;
|
|
59
|
-
};
|
|
60
|
-
// 辅助函数:计算 lengthScaleRatio
|
|
61
|
-
const calculateLengthScaleRatio = res => {
|
|
62
|
-
let lengthScaleRatio = res === null || res === void 0 ? void 0 : res.lengthScaleRatio;
|
|
63
|
-
if (lengthScaleRatio == null || lengthScaleRatio === 0) {
|
|
64
|
-
console.warn('Taro.getSystemInfo: lengthScaleRatio 不存在,使用计算值');
|
|
65
|
-
lengthScaleRatio = 1;
|
|
66
|
-
if (res.windowWidth < 320) {
|
|
67
|
-
lengthScaleRatio = res.windowWidth / 320;
|
|
68
|
-
} else if (res.windowWidth >= 400 && res.windowWidth < 600) {
|
|
69
|
-
lengthScaleRatio = res.windowWidth / 400;
|
|
70
|
-
}
|
|
71
|
-
const shortSide = res.windowWidth < res.windowHeight ? res.windowWidth : res.windowHeight;
|
|
72
|
-
const isBigScreen = shortSide >= 600;
|
|
73
|
-
if (isBigScreen) {
|
|
74
|
-
lengthScaleRatio = shortSide / 720;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return lengthScaleRatio;
|
|
78
|
-
};
|
|
79
|
-
// 辅助函数:获取系统信息的 lengthScaleRatio 并设置 targetScrollTop
|
|
80
|
-
const setTargetScrollTopWithScale = function (setTargetScrollTop, baseValue, randomOffset) {
|
|
81
|
-
let lengthScaleRatio = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 1;
|
|
82
|
-
// H5 和 weapp 不参与放大计算,直接使用 baseValue
|
|
83
|
-
if (process.env.TARO_ENV === 'h5' || process.env.TARO_ENV === 'weapp') {
|
|
84
|
-
const finalValue = randomOffset !== undefined ? baseValue + randomOffset : baseValue;
|
|
85
|
-
setTargetScrollTop(finalValue);
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
if (process.env.TARO_PLATFORM === 'harmony') {
|
|
89
|
-
const scaledValue = baseValue;
|
|
90
|
-
const finalValue = randomOffset !== undefined ? scaledValue + randomOffset : scaledValue;
|
|
91
|
-
setTargetScrollTop(finalValue);
|
|
92
|
-
} else {
|
|
93
|
-
const scaledValue = baseValue * lengthScaleRatio;
|
|
94
|
-
const finalValue = randomOffset !== undefined ? scaledValue + randomOffset : scaledValue;
|
|
95
|
-
setTargetScrollTop(finalValue);
|
|
96
|
-
}
|
|
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
|
-
};
|
|
121
|
-
function PickerGroupBasic(props) {
|
|
122
|
-
const {
|
|
123
|
-
range = [],
|
|
124
|
-
rangeKey,
|
|
125
|
-
columnId,
|
|
126
|
-
updateIndex,
|
|
127
|
-
onColumnChange,
|
|
128
|
-
selectedIndex = 0,
|
|
129
|
-
// 使用selectedIndex参数,默认为0
|
|
130
|
-
colors = {},
|
|
131
|
-
enableClickItemScroll = true
|
|
132
|
-
} = props;
|
|
133
|
-
const indicatorStyle = colors.lineColor ? getIndicatorStyle(colors.lineColor) : null;
|
|
134
|
-
const [targetScrollTop, setTargetScrollTop] = React.useState(0);
|
|
135
|
-
const [scrollIntoView, scrollToItemId] = usePickerItemScrollIntoView();
|
|
136
|
-
const scrollViewRef = React.useRef(null);
|
|
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);
|
|
142
|
-
// 使用selectedIndex初始化当前索引
|
|
143
|
-
const [currentIndex, setCurrentIndex] = React.useState(selectedIndex);
|
|
144
|
-
// 触摸状态用于优化用户体验
|
|
145
|
-
const isTouchingRef = React.useRef(false);
|
|
146
|
-
const lengthScaleRatioRef = React.useRef(1);
|
|
147
|
-
const useMeasuredScaleRef = React.useRef(false);
|
|
148
|
-
const itemHeightRef = React.useRef(PICKER_LINE_HEIGHT);
|
|
149
|
-
// 初始化时计算 lengthScaleRatio 并判定缩放模式
|
|
150
|
-
React.useEffect(() => {
|
|
151
|
-
Taro.getSystemInfo({
|
|
152
|
-
success: res => {
|
|
153
|
-
lengthScaleRatioRef.current = calculateLengthScaleRatio(res);
|
|
154
|
-
useMeasuredScaleRef.current = resolveUseMeasuredScale(res);
|
|
155
|
-
itemHeightRef.current = calculateItemHeight(scrollViewRef.current, lengthScaleRatioRef.current, useMeasuredScaleRef.current);
|
|
156
|
-
},
|
|
157
|
-
fail: () => {
|
|
158
|
-
lengthScaleRatioRef.current = 1;
|
|
159
|
-
useMeasuredScaleRef.current = false;
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
}, []);
|
|
163
|
-
React.useEffect(() => {
|
|
164
|
-
itemHeightRef.current = calculateItemHeight(scrollViewRef.current, lengthScaleRatioRef.current, useMeasuredScaleRef.current);
|
|
165
|
-
}, [range.length]); // 只在range长度变化时重新计算
|
|
166
|
-
// props 的 selectedIndex 变化:滚至对应项并短时标记程序化滚动(syncScrollFromPropsRef)
|
|
167
|
-
React.useEffect(() => {
|
|
168
|
-
if (scrollViewRef.current && range.length > 0 && !isTouchingRef.current) {
|
|
169
|
-
syncScrollFromPropsRef.current = true;
|
|
170
|
-
const baseValue = selectedIndex * itemHeightRef.current;
|
|
171
|
-
setTargetScrollTopWithScale(setTargetScrollTop, baseValue, undefined, lengthScaleRatioRef.current);
|
|
172
|
-
setCurrentIndex(selectedIndex);
|
|
173
|
-
const tid = setTimeout(() => {
|
|
174
|
-
syncScrollFromPropsRef.current = false;
|
|
175
|
-
}, 400);
|
|
176
|
-
return () => clearTimeout(tid);
|
|
177
|
-
}
|
|
178
|
-
}, [selectedIndex, range]);
|
|
179
|
-
// 是否处于归中状态
|
|
180
|
-
const isCenterTimerId = React.useRef(null);
|
|
181
|
-
// 滚动静止后归中并同步选中
|
|
182
|
-
const handleScrollEnd = () => {
|
|
183
|
-
if (!scrollViewRef.current) return;
|
|
184
|
-
if (isCenterTimerId.current) {
|
|
185
|
-
clearTimeout(isCenterTimerId.current);
|
|
186
|
-
isCenterTimerId.current = null;
|
|
187
|
-
}
|
|
188
|
-
// 100ms 内无新滚动则归中并提交索引
|
|
189
|
-
isCenterTimerId.current = setTimeout(() => {
|
|
190
|
-
if (!scrollViewRef.current) return;
|
|
191
|
-
const scrollTop = scrollViewRef.current.scrollTop;
|
|
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;
|
|
196
|
-
const baseValue = newIndex * itemHeightRef.current;
|
|
197
|
-
const randomOffset = Math.random() * 0.001; // 随机数为了在一个项内滚动时强制刷新
|
|
198
|
-
setTargetScrollTopWithScale(setTargetScrollTop, baseValue, randomOffset, lengthScaleRatioRef.current);
|
|
199
|
-
updateIndex(newIndex, columnId);
|
|
200
|
-
onColumnChange === null || onColumnChange === void 0 ? void 0 : onColumnChange({
|
|
201
|
-
columnId,
|
|
202
|
-
index: newIndex
|
|
203
|
-
});
|
|
204
|
-
pendingScrollSettleFocusRef.current = false;
|
|
205
|
-
if (allowA11yFocus && shouldFocusOnScrollSettle) {
|
|
206
|
-
requestAccessibilityFocusOnView(itemRefs.current[newIndex]);
|
|
207
|
-
}
|
|
208
|
-
syncScrollFromPropsRef.current = false;
|
|
209
|
-
isCenterTimerId.current = null;
|
|
210
|
-
}, 100);
|
|
211
|
-
};
|
|
212
|
-
// 滚动中:按 scrollTop 更新高亮索引
|
|
213
|
-
const handleScroll = () => {
|
|
214
|
-
if (!scrollViewRef.current) return;
|
|
215
|
-
if (isCenterTimerId.current) {
|
|
216
|
-
clearTimeout(isCenterTimerId.current);
|
|
217
|
-
isCenterTimerId.current = null;
|
|
218
|
-
}
|
|
219
|
-
const scrollTop = scrollViewRef.current.scrollTop;
|
|
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
|
-
}
|
|
231
|
-
if (newIndex !== currentIndex) {
|
|
232
|
-
setCurrentIndex(newIndex);
|
|
233
|
-
}
|
|
234
|
-
};
|
|
235
|
-
// 渲染选项
|
|
236
|
-
const pickerItem = range.map((item, index) => {
|
|
237
|
-
const content = rangeKey && item && typeof item === 'object' ? item[rangeKey] : item;
|
|
238
|
-
return /*#__PURE__*/jsx(View, {
|
|
239
|
-
id: `picker-item-${columnId}-${index}`,
|
|
240
|
-
ref: el => itemRefs.current[index] = el,
|
|
241
|
-
className: `taro-picker__item${index === currentIndex ? ' taro-picker__item--selected' : ''}`,
|
|
242
|
-
...(enableClickItemScroll ? {
|
|
243
|
-
onClick: () => scrollToItemId(`picker-item-${columnId}-${index}`)
|
|
244
|
-
} : {}),
|
|
245
|
-
style: {
|
|
246
|
-
height: PICKER_LINE_HEIGHT,
|
|
247
|
-
color: index === currentIndex ? colors.itemSelectedColor || undefined : colors.itemDefaultColor || undefined
|
|
248
|
-
},
|
|
249
|
-
children: content
|
|
250
|
-
}, index);
|
|
251
|
-
});
|
|
252
|
-
const realPickerItems = [...new Array(PICKER_BLANK_ITEMS).fill(null).map((_, idx) => /*#__PURE__*/jsx(View, {
|
|
253
|
-
className: "taro-picker__item taro-picker__item--blank",
|
|
254
|
-
style: {
|
|
255
|
-
height: PICKER_LINE_HEIGHT
|
|
256
|
-
}
|
|
257
|
-
}, `blank-top-${idx}`)), ...pickerItem, ...new Array(PICKER_BLANK_ITEMS).fill(null).map((_, idx) => /*#__PURE__*/jsx(View, {
|
|
258
|
-
className: "taro-picker__item taro-picker__item--blank",
|
|
259
|
-
style: {
|
|
260
|
-
height: PICKER_LINE_HEIGHT
|
|
261
|
-
}
|
|
262
|
-
}, `blank-bottom-${idx}`))];
|
|
263
|
-
const clickScrollViewProps = enableClickItemScroll ? {
|
|
264
|
-
scrollIntoView,
|
|
265
|
-
scrollIntoViewAlignment: 'center'
|
|
266
|
-
} : {};
|
|
267
|
-
return /*#__PURE__*/jsxs(View, {
|
|
268
|
-
className: "taro-picker__group",
|
|
269
|
-
children: [/*#__PURE__*/jsx(View, {
|
|
270
|
-
className: "taro-picker__mask"
|
|
271
|
-
}), /*#__PURE__*/jsx(View, {
|
|
272
|
-
className: "taro-picker__indicator",
|
|
273
|
-
...(indicatorStyle ? {
|
|
274
|
-
style: indicatorStyle
|
|
275
|
-
} : {})
|
|
276
|
-
}), /*#__PURE__*/jsx(ScrollView, {
|
|
277
|
-
ref: scrollViewRef,
|
|
278
|
-
scrollY: true,
|
|
279
|
-
showScrollbar: false,
|
|
280
|
-
className: "taro-picker__content",
|
|
281
|
-
style: {
|
|
282
|
-
height: PICKER_LINE_HEIGHT * PICKER_VISIBLE_ITEMS
|
|
283
|
-
},
|
|
284
|
-
scrollTop: targetScrollTop,
|
|
285
|
-
...clickScrollViewProps,
|
|
286
|
-
onScroll: handleScroll,
|
|
287
|
-
onTouchStart: () => {
|
|
288
|
-
isTouchingRef.current = true;
|
|
289
|
-
},
|
|
290
|
-
onScrollEnd: handleScrollEnd,
|
|
291
|
-
scrollWithAnimation: true,
|
|
292
|
-
children: realPickerItems
|
|
293
|
-
})]
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
// 时间选择器实现
|
|
297
|
-
function PickerGroupTime(props) {
|
|
298
|
-
const {
|
|
299
|
-
range = [],
|
|
300
|
-
rangeKey,
|
|
301
|
-
columnId,
|
|
302
|
-
updateIndex,
|
|
303
|
-
selectedIndex = 0,
|
|
304
|
-
colors = {},
|
|
305
|
-
timeA11yLimitFocus,
|
|
306
|
-
onTimeA11yLimitFocusConsumed,
|
|
307
|
-
timeA11yLimitEventNonce,
|
|
308
|
-
enableClickItemScroll = true
|
|
309
|
-
} = props;
|
|
310
|
-
const indicatorStyle = colors.lineColor ? getIndicatorStyle(colors.lineColor) : null;
|
|
311
|
-
const [targetScrollTop, setTargetScrollTop] = React.useState(0);
|
|
312
|
-
const [scrollIntoView, scrollToItemId] = usePickerItemScrollIntoView();
|
|
313
|
-
const scrollViewRef = React.useRef(null);
|
|
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);
|
|
323
|
-
const [currentIndex, setCurrentIndex] = React.useState(selectedIndex);
|
|
324
|
-
const isTouchingRef = React.useRef(false);
|
|
325
|
-
const lengthScaleRatioRef = React.useRef(1);
|
|
326
|
-
const useMeasuredScaleRef = React.useRef(false);
|
|
327
|
-
const itemHeightRef = React.useRef(PICKER_LINE_HEIGHT);
|
|
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 并判定缩放模式
|
|
374
|
-
React.useEffect(() => {
|
|
375
|
-
Taro.getSystemInfo({
|
|
376
|
-
success: res => {
|
|
377
|
-
lengthScaleRatioRef.current = calculateLengthScaleRatio(res);
|
|
378
|
-
useMeasuredScaleRef.current = resolveUseMeasuredScale(res);
|
|
379
|
-
itemHeightRef.current = calculateItemHeight(scrollViewRef.current, lengthScaleRatioRef.current, useMeasuredScaleRef.current);
|
|
380
|
-
},
|
|
381
|
-
fail: () => {
|
|
382
|
-
lengthScaleRatioRef.current = 1;
|
|
383
|
-
useMeasuredScaleRef.current = false;
|
|
384
|
-
}
|
|
385
|
-
});
|
|
386
|
-
}, []);
|
|
387
|
-
React.useEffect(() => {
|
|
388
|
-
itemHeightRef.current = calculateItemHeight(scrollViewRef.current, lengthScaleRatioRef.current, useMeasuredScaleRef.current);
|
|
389
|
-
}, [range.length]); // 只在range长度变化时重新计算
|
|
390
|
-
// props 的 selectedIndex 变化:滚至对应项并短时标记程序化滚动(syncScrollFromPropsRef)
|
|
391
|
-
React.useEffect(() => {
|
|
392
|
-
if (scrollViewRef.current && range.length > 0 && !isTouchingRef.current) {
|
|
393
|
-
syncScrollFromPropsRef.current = true;
|
|
394
|
-
const baseValue = selectedIndex * itemHeightRef.current;
|
|
395
|
-
setTargetScrollTopWithScale(setTargetScrollTop, baseValue, undefined, lengthScaleRatioRef.current);
|
|
396
|
-
setCurrentIndex(selectedIndex);
|
|
397
|
-
const tid = setTimeout(() => {
|
|
398
|
-
syncScrollFromPropsRef.current = false;
|
|
399
|
-
}, 400);
|
|
400
|
-
return () => clearTimeout(tid);
|
|
401
|
-
}
|
|
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]);
|
|
438
|
-
// 是否处于归中状态
|
|
439
|
-
const isCenterTimerId = React.useRef(null);
|
|
440
|
-
// 滚动静止后归中并同步选中
|
|
441
|
-
const handleScrollEnd = () => {
|
|
442
|
-
if (!scrollViewRef.current) return;
|
|
443
|
-
if (isCenterTimerId.current) {
|
|
444
|
-
clearTimeout(isCenterTimerId.current);
|
|
445
|
-
isCenterTimerId.current = null;
|
|
446
|
-
}
|
|
447
|
-
// 100ms 内无新滚动则归中并提交索引
|
|
448
|
-
isCenterTimerId.current = setTimeout(() => {
|
|
449
|
-
if (!scrollViewRef.current) return;
|
|
450
|
-
const scrollTop = scrollViewRef.current.scrollTop;
|
|
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;
|
|
455
|
-
const isLimited = Boolean(updateIndex(newIndex, columnId, true));
|
|
456
|
-
const hasPendingLimitFocus = pendingLimitFocusRef.current != null;
|
|
457
|
-
if (isLimited) {
|
|
458
|
-
pendingScrollSettleFocusRef.current = false;
|
|
459
|
-
}
|
|
460
|
-
if (!isLimited) {
|
|
461
|
-
const baseValue = newIndex * itemHeightRef.current;
|
|
462
|
-
const randomOffset = Math.random() * 0.001;
|
|
463
|
-
setTargetScrollTopWithScale(setTargetScrollTop, baseValue, randomOffset, lengthScaleRatioRef.current);
|
|
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;
|
|
483
|
-
isCenterTimerId.current = null;
|
|
484
|
-
}, 100);
|
|
485
|
-
};
|
|
486
|
-
// 滚动中:按 scrollTop 更新高亮索引
|
|
487
|
-
const handleScroll = () => {
|
|
488
|
-
if (!scrollViewRef.current) return;
|
|
489
|
-
if (isCenterTimerId.current) {
|
|
490
|
-
clearTimeout(isCenterTimerId.current);
|
|
491
|
-
isCenterTimerId.current = null;
|
|
492
|
-
}
|
|
493
|
-
const scrollTop = scrollViewRef.current.scrollTop;
|
|
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
|
-
}
|
|
505
|
-
if (newIndex !== currentIndex) {
|
|
506
|
-
setCurrentIndex(newIndex);
|
|
507
|
-
}
|
|
508
|
-
if (pendingLimitFocusRef.current) {
|
|
509
|
-
schedulePendingLimitFocus();
|
|
510
|
-
}
|
|
511
|
-
};
|
|
512
|
-
// 渲染选项
|
|
513
|
-
const pickerItem = range.map((item, index) => {
|
|
514
|
-
const content = rangeKey && item && typeof item === 'object' ? item[rangeKey] : item;
|
|
515
|
-
return /*#__PURE__*/jsx(View, {
|
|
516
|
-
id: `picker-item-${columnId}-${index}`,
|
|
517
|
-
ref: el => itemRefs.current[index] = el,
|
|
518
|
-
className: `taro-picker__item${index === currentIndex ? ' taro-picker__item--selected' : ''}`,
|
|
519
|
-
...(enableClickItemScroll ? {
|
|
520
|
-
onClick: () => scrollToItemId(`picker-item-${columnId}-${index}`)
|
|
521
|
-
} : {}),
|
|
522
|
-
style: {
|
|
523
|
-
height: PICKER_LINE_HEIGHT,
|
|
524
|
-
color: index === currentIndex ? colors.itemSelectedColor || undefined : colors.itemDefaultColor || undefined
|
|
525
|
-
},
|
|
526
|
-
children: content
|
|
527
|
-
}, index);
|
|
528
|
-
});
|
|
529
|
-
const realPickerItems = [...new Array(PICKER_BLANK_ITEMS).fill(null).map((_, idx) => /*#__PURE__*/jsx(View, {
|
|
530
|
-
className: "taro-picker__item taro-picker__item--blank",
|
|
531
|
-
style: {
|
|
532
|
-
height: PICKER_LINE_HEIGHT
|
|
533
|
-
}
|
|
534
|
-
}, `blank-top-${idx}`)), ...pickerItem, ...new Array(PICKER_BLANK_ITEMS).fill(null).map((_, idx) => /*#__PURE__*/jsx(View, {
|
|
535
|
-
className: "taro-picker__item taro-picker__item--blank",
|
|
536
|
-
style: {
|
|
537
|
-
height: PICKER_LINE_HEIGHT
|
|
538
|
-
}
|
|
539
|
-
}, `blank-bottom-${idx}`))];
|
|
540
|
-
const clickScrollViewProps = enableClickItemScroll ? {
|
|
541
|
-
scrollIntoView,
|
|
542
|
-
scrollIntoViewAlignment: 'center'
|
|
543
|
-
} : {};
|
|
544
|
-
return /*#__PURE__*/jsxs(View, {
|
|
545
|
-
className: "taro-picker__group",
|
|
546
|
-
children: [/*#__PURE__*/jsx(View, {
|
|
547
|
-
className: "taro-picker__mask"
|
|
548
|
-
}), /*#__PURE__*/jsx(View, {
|
|
549
|
-
className: "taro-picker__indicator",
|
|
550
|
-
...(indicatorStyle ? {
|
|
551
|
-
style: indicatorStyle
|
|
552
|
-
} : {})
|
|
553
|
-
}), /*#__PURE__*/jsx(ScrollView, {
|
|
554
|
-
ref: scrollViewRef,
|
|
555
|
-
scrollY: true,
|
|
556
|
-
showScrollbar: false,
|
|
557
|
-
className: "taro-picker__content",
|
|
558
|
-
style: {
|
|
559
|
-
height: PICKER_LINE_HEIGHT * PICKER_VISIBLE_ITEMS
|
|
560
|
-
},
|
|
561
|
-
scrollTop: targetScrollTop,
|
|
562
|
-
...clickScrollViewProps,
|
|
563
|
-
onScroll: handleScroll,
|
|
564
|
-
onTouchStart: () => {
|
|
565
|
-
isTouchingRef.current = true;
|
|
566
|
-
},
|
|
567
|
-
onScrollEnd: handleScrollEnd,
|
|
568
|
-
scrollWithAnimation: true,
|
|
569
|
-
children: realPickerItems
|
|
570
|
-
})]
|
|
571
|
-
});
|
|
572
|
-
}
|
|
573
|
-
// 日期选择器实现
|
|
574
|
-
function PickerGroupDate(props) {
|
|
575
|
-
const {
|
|
576
|
-
range = [],
|
|
577
|
-
columnId,
|
|
578
|
-
updateDay,
|
|
579
|
-
selectedIndex = 0,
|
|
580
|
-
colors = {},
|
|
581
|
-
enableClickItemScroll = true
|
|
582
|
-
} = props;
|
|
583
|
-
const indicatorStyle = colors.lineColor ? getIndicatorStyle(colors.lineColor) : null;
|
|
584
|
-
const [targetScrollTop, setTargetScrollTop] = React.useState(0);
|
|
585
|
-
const [scrollIntoView, scrollToItemId] = usePickerItemScrollIntoView();
|
|
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);
|
|
592
|
-
const [currentIndex, setCurrentIndex] = React.useState(selectedIndex);
|
|
593
|
-
const isTouchingRef = React.useRef(false);
|
|
594
|
-
const lengthScaleRatioRef = React.useRef(1);
|
|
595
|
-
const useMeasuredScaleRef = React.useRef(false);
|
|
596
|
-
const itemHeightRef = React.useRef(PICKER_LINE_HEIGHT);
|
|
597
|
-
// 初始化时计算 lengthScaleRatio 并判定缩放模式
|
|
598
|
-
React.useEffect(() => {
|
|
599
|
-
Taro.getSystemInfo({
|
|
600
|
-
success: res => {
|
|
601
|
-
lengthScaleRatioRef.current = calculateLengthScaleRatio(res);
|
|
602
|
-
useMeasuredScaleRef.current = resolveUseMeasuredScale(res);
|
|
603
|
-
itemHeightRef.current = calculateItemHeight(scrollViewRef.current, lengthScaleRatioRef.current, useMeasuredScaleRef.current);
|
|
604
|
-
},
|
|
605
|
-
fail: () => {
|
|
606
|
-
lengthScaleRatioRef.current = 1;
|
|
607
|
-
useMeasuredScaleRef.current = false;
|
|
608
|
-
}
|
|
609
|
-
});
|
|
610
|
-
}, []);
|
|
611
|
-
React.useEffect(() => {
|
|
612
|
-
itemHeightRef.current = calculateItemHeight(scrollViewRef.current, lengthScaleRatioRef.current, useMeasuredScaleRef.current);
|
|
613
|
-
}, [range.length]); // 只在range长度变化时重新计算
|
|
614
|
-
// props 的 selectedIndex 变化:滚至对应项并短时标记程序化滚动(syncScrollFromPropsRef)
|
|
615
|
-
React.useEffect(() => {
|
|
616
|
-
if (scrollViewRef.current && range.length > 0 && !isTouchingRef.current) {
|
|
617
|
-
syncScrollFromPropsRef.current = true;
|
|
618
|
-
const baseValue = selectedIndex * itemHeightRef.current;
|
|
619
|
-
setTargetScrollTopWithScale(setTargetScrollTop, baseValue, undefined, lengthScaleRatioRef.current);
|
|
620
|
-
setCurrentIndex(selectedIndex);
|
|
621
|
-
const tid = setTimeout(() => {
|
|
622
|
-
syncScrollFromPropsRef.current = false;
|
|
623
|
-
}, 400);
|
|
624
|
-
return () => clearTimeout(tid);
|
|
625
|
-
}
|
|
626
|
-
}, [selectedIndex, range]);
|
|
627
|
-
// 是否处于归中状态
|
|
628
|
-
const isCenterTimerId = React.useRef(null);
|
|
629
|
-
// 滚动静止后归中并同步选中
|
|
630
|
-
const handleScrollEnd = () => {
|
|
631
|
-
if (!scrollViewRef.current) return;
|
|
632
|
-
if (isCenterTimerId.current) {
|
|
633
|
-
clearTimeout(isCenterTimerId.current);
|
|
634
|
-
isCenterTimerId.current = null;
|
|
635
|
-
}
|
|
636
|
-
// 100ms 内无新滚动则归中并提交索引
|
|
637
|
-
isCenterTimerId.current = setTimeout(() => {
|
|
638
|
-
if (!scrollViewRef.current) return;
|
|
639
|
-
const scrollTop = scrollViewRef.current.scrollTop;
|
|
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;
|
|
644
|
-
const baseValue = newIndex * itemHeightRef.current;
|
|
645
|
-
const randomOffset = Math.random() * 0.001; // 随机数为了在一个项内滚动时强制刷新
|
|
646
|
-
setTargetScrollTopWithScale(setTargetScrollTop, baseValue, randomOffset, lengthScaleRatioRef.current);
|
|
647
|
-
// 更新日期值
|
|
648
|
-
if (updateDay) {
|
|
649
|
-
// 解析文本中的数字(移除年、月、日等后缀)
|
|
650
|
-
const valueText = range[newIndex] || '';
|
|
651
|
-
const numericValue = parseInt(valueText.replace(/[^0-9]/g, ''));
|
|
652
|
-
updateDay(isNaN(numericValue) ? 0 : numericValue, parseInt(columnId));
|
|
653
|
-
}
|
|
654
|
-
pendingScrollSettleFocusRef.current = false;
|
|
655
|
-
if (allowA11yFocus && shouldFocusOnScrollSettle) {
|
|
656
|
-
requestAccessibilityFocusOnView(itemRefs.current[newIndex]);
|
|
657
|
-
}
|
|
658
|
-
syncScrollFromPropsRef.current = false;
|
|
659
|
-
isCenterTimerId.current = null;
|
|
660
|
-
}, 100);
|
|
661
|
-
};
|
|
662
|
-
// 滚动中:按 scrollTop 更新高亮索引
|
|
663
|
-
const handleScroll = () => {
|
|
664
|
-
if (!scrollViewRef.current) return;
|
|
665
|
-
if (isCenterTimerId.current) {
|
|
666
|
-
clearTimeout(isCenterTimerId.current);
|
|
667
|
-
isCenterTimerId.current = null;
|
|
668
|
-
}
|
|
669
|
-
const scrollTop = scrollViewRef.current.scrollTop;
|
|
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
|
-
}
|
|
681
|
-
if (newIndex !== currentIndex) {
|
|
682
|
-
setCurrentIndex(newIndex);
|
|
683
|
-
}
|
|
684
|
-
};
|
|
685
|
-
// 渲染选项
|
|
686
|
-
const pickerItem = range.map((item, index) => {
|
|
687
|
-
return /*#__PURE__*/jsx(View, {
|
|
688
|
-
id: `picker-item-${columnId}-${index}`,
|
|
689
|
-
ref: el => itemRefs.current[index] = el,
|
|
690
|
-
className: `taro-picker__item${index === currentIndex ? ' taro-picker__item--selected' : ''}`,
|
|
691
|
-
...(enableClickItemScroll ? {
|
|
692
|
-
onClick: () => scrollToItemId(`picker-item-${columnId}-${index}`)
|
|
693
|
-
} : {}),
|
|
694
|
-
style: {
|
|
695
|
-
height: PICKER_LINE_HEIGHT,
|
|
696
|
-
color: index === currentIndex ? colors.itemSelectedColor || undefined : colors.itemDefaultColor || undefined
|
|
697
|
-
},
|
|
698
|
-
children: item
|
|
699
|
-
}, index);
|
|
700
|
-
});
|
|
701
|
-
const realPickerItems = [...new Array(PICKER_BLANK_ITEMS).fill(null).map((_, idx) => /*#__PURE__*/jsx(View, {
|
|
702
|
-
className: "taro-picker__item taro-picker__item--blank",
|
|
703
|
-
style: {
|
|
704
|
-
height: PICKER_LINE_HEIGHT
|
|
705
|
-
}
|
|
706
|
-
}, `blank-top-${idx}`)), ...pickerItem, ...new Array(PICKER_BLANK_ITEMS).fill(null).map((_, idx) => /*#__PURE__*/jsx(View, {
|
|
707
|
-
className: "taro-picker__item taro-picker__item--blank",
|
|
708
|
-
style: {
|
|
709
|
-
height: PICKER_LINE_HEIGHT
|
|
710
|
-
}
|
|
711
|
-
}, `blank-bottom-${idx}`))];
|
|
712
|
-
const clickScrollViewProps = enableClickItemScroll ? {
|
|
713
|
-
scrollIntoView,
|
|
714
|
-
scrollIntoViewAlignment: 'center'
|
|
715
|
-
} : {};
|
|
716
|
-
return /*#__PURE__*/jsxs(View, {
|
|
717
|
-
className: "taro-picker__group",
|
|
718
|
-
children: [/*#__PURE__*/jsx(View, {
|
|
719
|
-
className: "taro-picker__mask"
|
|
720
|
-
}), /*#__PURE__*/jsx(View, {
|
|
721
|
-
className: "taro-picker__indicator",
|
|
722
|
-
...(indicatorStyle ? {
|
|
723
|
-
style: indicatorStyle
|
|
724
|
-
} : {})
|
|
725
|
-
}), /*#__PURE__*/jsx(ScrollView, {
|
|
726
|
-
ref: scrollViewRef,
|
|
727
|
-
scrollY: true,
|
|
728
|
-
showScrollbar: false,
|
|
729
|
-
className: "taro-picker__content",
|
|
730
|
-
style: {
|
|
731
|
-
height: PICKER_LINE_HEIGHT * PICKER_VISIBLE_ITEMS
|
|
732
|
-
},
|
|
733
|
-
scrollTop: targetScrollTop,
|
|
734
|
-
...clickScrollViewProps,
|
|
735
|
-
onScroll: handleScroll,
|
|
736
|
-
onTouchStart: () => {
|
|
737
|
-
isTouchingRef.current = true;
|
|
738
|
-
},
|
|
739
|
-
onScrollEnd: handleScrollEnd,
|
|
740
|
-
scrollWithAnimation: true,
|
|
741
|
-
children: realPickerItems
|
|
742
|
-
})]
|
|
743
|
-
});
|
|
744
|
-
}
|
|
745
|
-
// 地区选择器实现
|
|
746
|
-
function PickerGroupRegion(props) {
|
|
747
|
-
const {
|
|
748
|
-
range = [],
|
|
749
|
-
rangeKey,
|
|
750
|
-
columnId,
|
|
751
|
-
updateIndex,
|
|
752
|
-
selectedIndex = 0,
|
|
753
|
-
// 使用selectedIndex参数,默认为0
|
|
754
|
-
colors = {},
|
|
755
|
-
enableClickItemScroll = true
|
|
756
|
-
} = props;
|
|
757
|
-
const indicatorStyle = colors.lineColor ? getIndicatorStyle(colors.lineColor) : null;
|
|
758
|
-
const scrollViewRef = React.useRef(null);
|
|
759
|
-
const [targetScrollTop, setTargetScrollTop] = React.useState(0);
|
|
760
|
-
const [scrollIntoView, scrollToItemId] = usePickerItemScrollIntoView();
|
|
761
|
-
const [currentIndex, setCurrentIndex] = React.useState(selectedIndex);
|
|
762
|
-
const isTouchingRef = React.useRef(false);
|
|
763
|
-
const lengthScaleRatioRef = React.useRef(1);
|
|
764
|
-
const useMeasuredScaleRef = React.useRef(false);
|
|
765
|
-
const itemHeightRef = React.useRef(PICKER_LINE_HEIGHT);
|
|
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 并判定缩放模式
|
|
772
|
-
React.useEffect(() => {
|
|
773
|
-
Taro.getSystemInfo({
|
|
774
|
-
success: res => {
|
|
775
|
-
lengthScaleRatioRef.current = calculateLengthScaleRatio(res);
|
|
776
|
-
useMeasuredScaleRef.current = resolveUseMeasuredScale(res);
|
|
777
|
-
itemHeightRef.current = calculateItemHeight(scrollViewRef.current, lengthScaleRatioRef.current, useMeasuredScaleRef.current);
|
|
778
|
-
},
|
|
779
|
-
fail: () => {
|
|
780
|
-
lengthScaleRatioRef.current = 1;
|
|
781
|
-
useMeasuredScaleRef.current = false;
|
|
782
|
-
}
|
|
783
|
-
});
|
|
784
|
-
}, []);
|
|
785
|
-
React.useEffect(() => {
|
|
786
|
-
itemHeightRef.current = calculateItemHeight(scrollViewRef.current, lengthScaleRatioRef.current, useMeasuredScaleRef.current);
|
|
787
|
-
}, [range.length]); // 只在range长度变化时重新计算
|
|
788
|
-
// props 的 selectedIndex 变化:滚至对应项并短时标记程序化滚动(syncScrollFromPropsRef)
|
|
789
|
-
React.useEffect(() => {
|
|
790
|
-
if (scrollViewRef.current && range.length > 0 && !isTouchingRef.current) {
|
|
791
|
-
syncScrollFromPropsRef.current = true;
|
|
792
|
-
const baseValue = selectedIndex * itemHeightRef.current;
|
|
793
|
-
setTargetScrollTopWithScale(setTargetScrollTop, baseValue, undefined, lengthScaleRatioRef.current);
|
|
794
|
-
setCurrentIndex(selectedIndex);
|
|
795
|
-
const tid = setTimeout(() => {
|
|
796
|
-
syncScrollFromPropsRef.current = false;
|
|
797
|
-
}, 400);
|
|
798
|
-
return () => clearTimeout(tid);
|
|
799
|
-
}
|
|
800
|
-
}, [selectedIndex, range]);
|
|
801
|
-
// 滚动静止后归中(debounce)
|
|
802
|
-
const isCenterTimerId = React.useRef(null);
|
|
803
|
-
const handleScrollEnd = () => {
|
|
804
|
-
if (!scrollViewRef.current) return;
|
|
805
|
-
if (isCenterTimerId.current) {
|
|
806
|
-
clearTimeout(isCenterTimerId.current);
|
|
807
|
-
isCenterTimerId.current = null;
|
|
808
|
-
}
|
|
809
|
-
// 100ms 内无新滚动则归中并提交索引
|
|
810
|
-
isCenterTimerId.current = setTimeout(() => {
|
|
811
|
-
if (!scrollViewRef.current) return;
|
|
812
|
-
const scrollTop = scrollViewRef.current.scrollTop;
|
|
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;
|
|
817
|
-
const baseValue = newIndex * itemHeightRef.current;
|
|
818
|
-
const randomOffset = Math.random() * 0.001; // 随机数为了在一个项内滚动时强制刷新
|
|
819
|
-
setTargetScrollTopWithScale(setTargetScrollTop, baseValue, randomOffset, lengthScaleRatioRef.current);
|
|
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;
|
|
827
|
-
}, 100);
|
|
828
|
-
};
|
|
829
|
-
// 滚动中:按 scrollTop 更新高亮索引
|
|
830
|
-
const handleScroll = () => {
|
|
831
|
-
if (!scrollViewRef.current) return;
|
|
832
|
-
if (isCenterTimerId.current) {
|
|
833
|
-
clearTimeout(isCenterTimerId.current);
|
|
834
|
-
isCenterTimerId.current = null;
|
|
835
|
-
}
|
|
836
|
-
const scrollTop = scrollViewRef.current.scrollTop;
|
|
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
|
-
}
|
|
848
|
-
if (newIndex !== currentIndex) {
|
|
849
|
-
setCurrentIndex(newIndex);
|
|
850
|
-
}
|
|
851
|
-
};
|
|
852
|
-
// 渲染选项
|
|
853
|
-
const pickerItem = range.map((item, index) => {
|
|
854
|
-
const content = rangeKey && item && typeof item === 'object' ? item[rangeKey] : item;
|
|
855
|
-
return /*#__PURE__*/jsx(View, {
|
|
856
|
-
id: `picker-item-${columnId}-${index}`,
|
|
857
|
-
ref: el => itemRefs.current[index] = el,
|
|
858
|
-
className: `taro-picker__item${index === currentIndex ? ' taro-picker__item--selected' : ''}`,
|
|
859
|
-
...(enableClickItemScroll ? {
|
|
860
|
-
onClick: () => scrollToItemId(`picker-item-${columnId}-${index}`)
|
|
861
|
-
} : {}),
|
|
862
|
-
style: {
|
|
863
|
-
height: PICKER_LINE_HEIGHT,
|
|
864
|
-
color: index === currentIndex ? colors.itemSelectedColor || undefined : colors.itemDefaultColor || undefined
|
|
865
|
-
},
|
|
866
|
-
children: content
|
|
867
|
-
}, index);
|
|
868
|
-
});
|
|
869
|
-
const realPickerItems = [...new Array(PICKER_BLANK_ITEMS).fill(null).map((_, idx) => /*#__PURE__*/jsx(View, {
|
|
870
|
-
className: "taro-picker__item taro-picker__item--blank",
|
|
871
|
-
style: {
|
|
872
|
-
height: PICKER_LINE_HEIGHT
|
|
873
|
-
}
|
|
874
|
-
}, `blank-top-${idx}`)), ...pickerItem, ...new Array(PICKER_BLANK_ITEMS).fill(null).map((_, idx) => /*#__PURE__*/jsx(View, {
|
|
875
|
-
className: "taro-picker__item taro-picker__item--blank",
|
|
876
|
-
style: {
|
|
877
|
-
height: PICKER_LINE_HEIGHT
|
|
878
|
-
}
|
|
879
|
-
}, `blank-bottom-${idx}`))];
|
|
880
|
-
const clickScrollViewProps = enableClickItemScroll ? {
|
|
881
|
-
scrollIntoView,
|
|
882
|
-
scrollIntoViewAlignment: 'center'
|
|
883
|
-
} : {};
|
|
884
|
-
return /*#__PURE__*/jsxs(View, {
|
|
885
|
-
className: "taro-picker__group",
|
|
886
|
-
children: [/*#__PURE__*/jsx(View, {
|
|
887
|
-
className: "taro-picker__mask"
|
|
888
|
-
}), /*#__PURE__*/jsx(View, {
|
|
889
|
-
className: "taro-picker__indicator",
|
|
890
|
-
...(indicatorStyle ? {
|
|
891
|
-
style: indicatorStyle
|
|
892
|
-
} : {})
|
|
893
|
-
}), /*#__PURE__*/jsx(ScrollView, {
|
|
894
|
-
ref: scrollViewRef,
|
|
895
|
-
scrollY: true,
|
|
896
|
-
showScrollbar: false,
|
|
897
|
-
className: "taro-picker__content",
|
|
898
|
-
style: {
|
|
899
|
-
height: PICKER_LINE_HEIGHT * PICKER_VISIBLE_ITEMS
|
|
900
|
-
},
|
|
901
|
-
scrollTop: targetScrollTop,
|
|
902
|
-
...clickScrollViewProps,
|
|
903
|
-
onScroll: handleScroll,
|
|
904
|
-
onTouchStart: () => {
|
|
905
|
-
isTouchingRef.current = true;
|
|
906
|
-
},
|
|
907
|
-
onScrollEnd: handleScrollEnd,
|
|
908
|
-
scrollWithAnimation: true,
|
|
909
|
-
children: realPickerItems
|
|
910
|
-
})]
|
|
911
|
-
});
|
|
912
34
|
}
|
|
913
|
-
//
|
|
35
|
+
// 默认导出:按 app 版本整体分发到新版(slider)或旧版(legacy)无障碍实现
|
|
914
36
|
function PickerGroup(props) {
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
...props
|
|
923
|
-
});
|
|
924
|
-
case 'region':
|
|
925
|
-
return /*#__PURE__*/jsx(PickerGroupRegion, {
|
|
926
|
-
...props
|
|
927
|
-
});
|
|
928
|
-
default:
|
|
929
|
-
return /*#__PURE__*/jsx(PickerGroupBasic, {
|
|
930
|
-
...props
|
|
931
|
-
});
|
|
932
|
-
}
|
|
37
|
+
// 在组件渲染时判定(此时 Taro runtime 已就绪,避免模块加载期 API 未初始化)
|
|
38
|
+
const useSliderA11y = resolveUseSliderA11y();
|
|
39
|
+
return useSliderA11y ? /*#__PURE__*/jsx(PickerGroupSlider, {
|
|
40
|
+
...props
|
|
41
|
+
}) : /*#__PURE__*/jsx(PickerGroupLegacy, {
|
|
42
|
+
...props
|
|
43
|
+
});
|
|
933
44
|
}
|
|
934
45
|
|
|
935
|
-
export { PickerGroup
|
|
46
|
+
export { PickerGroup };
|
|
936
47
|
//# sourceMappingURL=picker-group.js.map
|