zkit-ui 2.0.9 → 2.0.11
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/ui/Checkbox/index.d.ts.map +1 -1
- package/dist/ui/Checkbox/index.js +15 -15
- package/dist/ui/Switch/index.d.ts.map +1 -1
- package/dist/ui/Switch/index.js +14 -6
- package/dist/ui/WheelColumn/AndroidVirtualizedWheel.d.ts +26 -0
- package/dist/ui/WheelColumn/AndroidVirtualizedWheel.d.ts.map +1 -0
- package/dist/ui/WheelColumn/AndroidVirtualizedWheel.js +174 -0
- package/dist/ui/WheelColumn/index.d.ts.map +1 -1
- package/dist/ui/WheelColumn/index.js +162 -100
- package/package.json +1 -1
- package/src/ui/Checkbox/index.tsx +23 -15
- package/src/ui/Picker/README.md +1 -1
- package/src/ui/Switch/index.tsx +19 -7
- package/src/ui/WheelColumn/AndroidVirtualizedWheel.tsx +263 -0
- package/src/ui/WheelColumn/README.md +1 -1
- package/src/ui/WheelColumn/index.tsx +243 -169
|
@@ -38,14 +38,16 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
38
38
|
const React = __importStar(require("react"));
|
|
39
39
|
const react_native_1 = require("react-native");
|
|
40
40
|
const react_native_reanimated_1 = __importStar(require("react-native-reanimated"));
|
|
41
|
+
const react_native_gesture_handler_1 = require("react-native-gesture-handler");
|
|
41
42
|
const react_native_worklets_1 = require("react-native-worklets");
|
|
42
43
|
const zkit_tools_1 = require("zkit-tools");
|
|
43
44
|
const useTheme_1 = require("../../theme/useTheme");
|
|
45
|
+
const AndroidVirtualizedWheel_1 = require("./AndroidVirtualizedWheel");
|
|
44
46
|
const ZKitWheelPickerNativeComponent_1 = require("./ZKitWheelPickerNativeComponent");
|
|
45
47
|
var constants_1 = require("./constants");
|
|
46
48
|
Object.defineProperty(exports, "WHEEL_SELECTION_BACKGROUND_COLOR", { enumerable: true, get: function () { return constants_1.WHEEL_SELECTION_BACKGROUND_COLOR; } });
|
|
47
49
|
exports.WHEEL_VISIBLE_ITEMS = 5;
|
|
48
|
-
// iOS 走系统 UIPickerView,Android
|
|
50
|
+
// iOS 走系统 UIPickerView,Android 走原生虚拟列表,Web 走自绘 transform 路径。
|
|
49
51
|
// 这些尺寸是当前 app 端多轮调校后的稳定观感,保留为默认公共规格。
|
|
50
52
|
const IOS_CONFIRM_SYNC_TIMEOUT_MS = 64;
|
|
51
53
|
const IOS_NATIVE_PICKER_HEIGHT = (0, zkit_tools_1.wp)(260);
|
|
@@ -56,8 +58,9 @@ const BASE_WHEEL_AREA_HEIGHT = BASE_WHEEL_ITEM_HEIGHT * exports.WHEEL_VISIBLE_IT
|
|
|
56
58
|
exports.WHEEL_VIEWPORT_HEIGHT = react_native_1.Platform.OS === 'ios' ? IOS_NATIVE_PICKER_HEIGHT : BASE_WHEEL_AREA_HEIGHT;
|
|
57
59
|
exports.WHEEL_AREA_HEIGHT = react_native_1.Platform.OS === 'ios' ? Math.max(IOS_NATIVE_PICKER_HEIGHT, BASE_WHEEL_AREA_HEIGHT) : BASE_WHEEL_AREA_HEIGHT;
|
|
58
60
|
exports.WHEEL_AREA_VERTICAL_INSET = Math.max(0, exports.WHEEL_AREA_HEIGHT - exports.WHEEL_VIEWPORT_HEIGHT) / 2;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
const INTERNAL_WHEEL_ITEM_HEIGHT = react_native_1.Platform.OS === 'ios' ? IOS_NATIVE_PICKER_HEIGHT / exports.WHEEL_VISIBLE_ITEMS : BASE_WHEEL_ITEM_HEIGHT;
|
|
62
|
+
exports.WHEEL_ITEM_HEIGHT = INTERNAL_WHEEL_ITEM_HEIGHT;
|
|
63
|
+
const CENTER_OFFSET = INTERNAL_WHEEL_ITEM_HEIGHT * Math.floor(exports.WHEEL_VISIBLE_ITEMS / 2);
|
|
61
64
|
const IOS_SETTLE_DELAY_MS = 90;
|
|
62
65
|
const SNAP_DURATION_MIN = 140;
|
|
63
66
|
const SNAP_DURATION_MAX = 280;
|
|
@@ -66,17 +69,23 @@ const RELEASE_VELOCITY_DEADZONE = 220;
|
|
|
66
69
|
const RELEASE_LOCK_DISTANCE_ITEMS = 0.1;
|
|
67
70
|
const RELEASE_LOCK_MAX_VELOCITY = 380;
|
|
68
71
|
const MAX_FLING_ITEMS = 7;
|
|
69
|
-
const
|
|
72
|
+
const PAN_ACTIVATION_OFFSET = (0, zkit_tools_1.wp)(3);
|
|
70
73
|
const SNAP_EASING = react_native_reanimated_1.Easing.bezier(0.22, 1, 0.36, 1);
|
|
74
|
+
// tsc 输出 CommonJS 后,worklet 若直接引用导入命名空间会把整个原生模块放进闭包。
|
|
75
|
+
// 先保存具体函数引用,消费端 worklet 插件即可只序列化函数本身。
|
|
76
|
+
const cancelAnimationOnUI = react_native_reanimated_1.cancelAnimation;
|
|
77
|
+
const withTimingOnUI = react_native_reanimated_1.withTiming;
|
|
78
|
+
const scheduleOnRNFromUI = react_native_worklets_1.scheduleOnRN;
|
|
71
79
|
function clampNumber(n, min, max) {
|
|
72
80
|
'worklet';
|
|
73
81
|
return Math.max(min, Math.min(max, n));
|
|
74
82
|
}
|
|
75
83
|
function indexToOffset(index) {
|
|
76
84
|
'worklet';
|
|
77
|
-
return index *
|
|
85
|
+
return index * INTERNAL_WHEEL_ITEM_HEIGHT;
|
|
78
86
|
}
|
|
79
87
|
function getReleaseDeltaItems(velocityY) {
|
|
88
|
+
'worklet';
|
|
80
89
|
const speed = Math.abs(velocityY);
|
|
81
90
|
if (speed < RELEASE_VELOCITY_DEADZONE)
|
|
82
91
|
return 0;
|
|
@@ -84,7 +93,8 @@ function getReleaseDeltaItems(velocityY) {
|
|
|
84
93
|
return clampNumber(projected, 0, MAX_FLING_ITEMS);
|
|
85
94
|
}
|
|
86
95
|
function getTargetIndexFromRelease(offset, velocityY, maxIndex) {
|
|
87
|
-
|
|
96
|
+
'worklet';
|
|
97
|
+
const currentIndexFloat = offset / INTERNAL_WHEEL_ITEM_HEIGHT;
|
|
88
98
|
const nearestIndex = Math.round(currentIndexFloat);
|
|
89
99
|
const distanceToNearest = Math.abs(currentIndexFloat - nearestIndex);
|
|
90
100
|
const speed = Math.abs(velocityY);
|
|
@@ -106,7 +116,8 @@ function getTargetIndexFromRelease(offset, velocityY, maxIndex) {
|
|
|
106
116
|
return clampNumber(targetIndex, 0, maxIndex);
|
|
107
117
|
}
|
|
108
118
|
function getSnapDuration(fromOffset, toOffset) {
|
|
109
|
-
|
|
119
|
+
'worklet';
|
|
120
|
+
const distanceItems = Math.abs(toOffset - fromOffset) / INTERNAL_WHEEL_ITEM_HEIGHT;
|
|
110
121
|
return clampNumber(Math.round(SNAP_DURATION_MIN + distanceItems * SNAP_DURATION_PER_ITEM), SNAP_DURATION_MIN, SNAP_DURATION_MAX);
|
|
111
122
|
}
|
|
112
123
|
function findOptionIndex(options, value) {
|
|
@@ -155,6 +166,47 @@ function findNearestEnabledIndex(options, index, direction = 0) {
|
|
|
155
166
|
}
|
|
156
167
|
return -1;
|
|
157
168
|
}
|
|
169
|
+
function findNearestEnabledFlagIndex(enabledFlags, index, direction = 0) {
|
|
170
|
+
'worklet';
|
|
171
|
+
if (!enabledFlags.length)
|
|
172
|
+
return -1;
|
|
173
|
+
const clampedIndex = clampNumber(Math.round(index), 0, enabledFlags.length - 1);
|
|
174
|
+
if (enabledFlags[clampedIndex])
|
|
175
|
+
return clampedIndex;
|
|
176
|
+
const walkForward = () => {
|
|
177
|
+
'worklet';
|
|
178
|
+
for (let i = clampedIndex + 1; i < enabledFlags.length; i += 1) {
|
|
179
|
+
if (enabledFlags[i])
|
|
180
|
+
return i;
|
|
181
|
+
}
|
|
182
|
+
return -1;
|
|
183
|
+
};
|
|
184
|
+
const walkBackward = () => {
|
|
185
|
+
'worklet';
|
|
186
|
+
for (let i = clampedIndex - 1; i >= 0; i -= 1) {
|
|
187
|
+
if (enabledFlags[i])
|
|
188
|
+
return i;
|
|
189
|
+
}
|
|
190
|
+
return -1;
|
|
191
|
+
};
|
|
192
|
+
if (direction > 0) {
|
|
193
|
+
const next = walkForward();
|
|
194
|
+
return next >= 0 ? next : walkBackward();
|
|
195
|
+
}
|
|
196
|
+
if (direction < 0) {
|
|
197
|
+
const previous = walkBackward();
|
|
198
|
+
return previous >= 0 ? previous : walkForward();
|
|
199
|
+
}
|
|
200
|
+
for (let distance = 1; distance < enabledFlags.length; distance += 1) {
|
|
201
|
+
const previous = clampedIndex - distance;
|
|
202
|
+
if (previous >= 0 && enabledFlags[previous])
|
|
203
|
+
return previous;
|
|
204
|
+
const next = clampedIndex + distance;
|
|
205
|
+
if (next < enabledFlags.length && enabledFlags[next])
|
|
206
|
+
return next;
|
|
207
|
+
}
|
|
208
|
+
return -1;
|
|
209
|
+
}
|
|
158
210
|
function resolveDisplayIndex(options, value) {
|
|
159
211
|
if (!options.length)
|
|
160
212
|
return 0;
|
|
@@ -188,7 +240,7 @@ function resolveWebInteractionStyle(disabled) {
|
|
|
188
240
|
};
|
|
189
241
|
}
|
|
190
242
|
const WheelColumnBase = React.forwardRef(function WheelColumn({ options, value, defaultValue, onChange, disabled = false, width, style, itemTextStyle, selectedItemTextStyle, disabledItemTextStyle, numberOfLines = 1, accessibilityLabel, accessibilityHint, accessibilityState, testID, ...viewProps }, ref) {
|
|
191
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
243
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
192
244
|
const theme = (0, useTheme_1.useTheme)();
|
|
193
245
|
const isControlled = value !== undefined;
|
|
194
246
|
const firstDefaultValue = React.useMemo(() => {
|
|
@@ -209,6 +261,7 @@ const WheelColumnBase = React.forwardRef(function WheelColumn({ options, value,
|
|
|
209
261
|
const dragStartOffset = (0, react_native_reanimated_1.useSharedValue)(indexToOffset(displayIndex));
|
|
210
262
|
const isUserInteracting = (0, react_native_reanimated_1.useSharedValue)(false);
|
|
211
263
|
const iosPickerRef = React.useRef(null);
|
|
264
|
+
const androidWheelRef = React.useRef(null);
|
|
212
265
|
const iosSelectedIndexRef = React.useRef(displayIndex);
|
|
213
266
|
const iosSettleTimerRef = React.useRef(null);
|
|
214
267
|
const iosSyncTimerRef = React.useRef(null);
|
|
@@ -294,12 +347,17 @@ const WheelColumnBase = React.forwardRef(function WheelColumn({ options, value,
|
|
|
294
347
|
}, IOS_SETTLE_DELAY_MS);
|
|
295
348
|
}, [clearIOSSettleTimer, emitSelectedIndex, resolveSelectableIndex, syncIOSSelectedIndex]);
|
|
296
349
|
const scrollToIndex = React.useCallback((index, animated = false) => {
|
|
350
|
+
var _a;
|
|
297
351
|
const nextIndex = clampNumber(index, 0, maxIndex);
|
|
298
352
|
if (react_native_1.Platform.OS === 'ios') {
|
|
299
353
|
clearIOSSettleTimer();
|
|
300
354
|
syncIOSSelectedIndex(nextIndex);
|
|
301
355
|
return;
|
|
302
356
|
}
|
|
357
|
+
if (react_native_1.Platform.OS === 'android') {
|
|
358
|
+
(_a = androidWheelRef.current) === null || _a === void 0 ? void 0 : _a.scrollToIndex(nextIndex, animated);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
303
361
|
const nextOffset = indexToOffset(nextIndex);
|
|
304
362
|
(0, react_native_reanimated_1.cancelAnimation)(offsetY);
|
|
305
363
|
isUserInteracting.value = false;
|
|
@@ -319,6 +377,10 @@ const WheelColumnBase = React.forwardRef(function WheelColumn({ options, value,
|
|
|
319
377
|
scrollToIndex(index, animated);
|
|
320
378
|
}, [options, scrollToIndex]);
|
|
321
379
|
const settleToNearest = React.useCallback((animated = false) => {
|
|
380
|
+
var _a, _b;
|
|
381
|
+
if (react_native_1.Platform.OS === 'android') {
|
|
382
|
+
return (_b = (_a = androidWheelRef.current) === null || _a === void 0 ? void 0 : _a.settleToNearest(animated)) !== null && _b !== void 0 ? _b : resolveSelectableIndex(displayIndexRef.current);
|
|
383
|
+
}
|
|
322
384
|
const rawIndex = react_native_1.Platform.OS === 'ios'
|
|
323
385
|
? iosSelectedIndexRef.current
|
|
324
386
|
: Math.round(clampNumber(offsetY.value, 0, maxOffset) / exports.WHEEL_ITEM_HEIGHT);
|
|
@@ -343,7 +405,12 @@ const WheelColumnBase = React.forwardRef(function WheelColumn({ options, value,
|
|
|
343
405
|
resolveIOSSyncRequest(iosSelectedIndexRef.current);
|
|
344
406
|
});
|
|
345
407
|
}, [clearIOSSettleTimer, resolveIOSSyncRequest, settleToNearest]);
|
|
346
|
-
React.useImperativeHandle(ref, () => ({
|
|
408
|
+
React.useImperativeHandle(ref, () => ({
|
|
409
|
+
scrollToIndex,
|
|
410
|
+
scrollToValue,
|
|
411
|
+
settleToNearest,
|
|
412
|
+
syncCurrentSelection,
|
|
413
|
+
}), [scrollToIndex, scrollToValue, settleToNearest, syncCurrentSelection]);
|
|
347
414
|
React.useEffect(() => {
|
|
348
415
|
selectedValueRef.current = resolvedValue;
|
|
349
416
|
displayIndexRef.current = displayIndex;
|
|
@@ -381,69 +448,6 @@ const WheelColumnBase = React.forwardRef(function WheelColumn({ options, value,
|
|
|
381
448
|
resolveIOSSyncRequest(nextIndex);
|
|
382
449
|
scheduleIOSSettle(nextIndex);
|
|
383
450
|
}, [resolveIOSSyncRequest, resolveSelectableIndex, scheduleIOSSettle, syncIOSSelectedIndex]);
|
|
384
|
-
const startInteraction = React.useCallback(() => {
|
|
385
|
-
(0, react_native_reanimated_1.cancelAnimation)(offsetY);
|
|
386
|
-
isUserInteracting.value = true;
|
|
387
|
-
dragStartOffset.value = offsetY.value;
|
|
388
|
-
}, [dragStartOffset, isUserInteracting, offsetY]);
|
|
389
|
-
const updateInteraction = React.useCallback((translationY) => {
|
|
390
|
-
offsetY.value = clampNumber(dragStartOffset.value - translationY, 0, maxOffset);
|
|
391
|
-
}, [dragStartOffset, maxOffset, offsetY]);
|
|
392
|
-
const finishInteraction = React.useCallback((velocityY) => {
|
|
393
|
-
const currentOffset = clampNumber(offsetY.value, 0, maxOffset);
|
|
394
|
-
const rawIndex = getTargetIndexFromRelease(currentOffset, velocityY, maxIndex);
|
|
395
|
-
const currentIndex = Math.round(currentOffset / exports.WHEEL_ITEM_HEIGHT);
|
|
396
|
-
const nextIndex = resolveSelectableIndex(rawIndex, rawIndex - currentIndex);
|
|
397
|
-
const nextOffset = indexToOffset(nextIndex);
|
|
398
|
-
offsetY.value = (0, react_native_reanimated_1.withTiming)(nextOffset, {
|
|
399
|
-
duration: getSnapDuration(currentOffset, nextOffset),
|
|
400
|
-
easing: SNAP_EASING,
|
|
401
|
-
}, (finished) => {
|
|
402
|
-
if (!finished)
|
|
403
|
-
return;
|
|
404
|
-
isUserInteracting.value = false;
|
|
405
|
-
(0, react_native_worklets_1.scheduleOnRN)(emitSelectedIndex, nextIndex, 'user');
|
|
406
|
-
});
|
|
407
|
-
}, [emitSelectedIndex, isUserInteracting, maxIndex, maxOffset, offsetY, resolveSelectableIndex]);
|
|
408
|
-
const touchStateRef = React.useRef({
|
|
409
|
-
startPageY: 0,
|
|
410
|
-
samples: [],
|
|
411
|
-
});
|
|
412
|
-
const beginTouch = React.useCallback((pageY, timestamp) => {
|
|
413
|
-
const ts = typeof timestamp === 'number' ? timestamp : Date.now();
|
|
414
|
-
touchStateRef.current = {
|
|
415
|
-
startPageY: pageY,
|
|
416
|
-
samples: [{ pageY, timestamp: ts }],
|
|
417
|
-
};
|
|
418
|
-
startInteraction();
|
|
419
|
-
}, [startInteraction]);
|
|
420
|
-
const recordTouchSample = React.useCallback((pageY, timestamp) => {
|
|
421
|
-
var _a;
|
|
422
|
-
const ts = typeof timestamp === 'number' ? timestamp : Date.now();
|
|
423
|
-
const { samples } = touchStateRef.current;
|
|
424
|
-
samples.push({ pageY, timestamp: ts });
|
|
425
|
-
while (samples.length > 6) {
|
|
426
|
-
samples.shift();
|
|
427
|
-
}
|
|
428
|
-
const minTs = ts - ANDROID_VELOCITY_WINDOW_MS;
|
|
429
|
-
while (samples.length > 2 && ((_a = samples[0]) === null || _a === void 0 ? void 0 : _a.timestamp) < minTs) {
|
|
430
|
-
samples.shift();
|
|
431
|
-
}
|
|
432
|
-
}, []);
|
|
433
|
-
const moveTouch = React.useCallback((pageY, timestamp) => {
|
|
434
|
-
updateInteraction(pageY - touchStateRef.current.startPageY);
|
|
435
|
-
recordTouchSample(pageY, timestamp);
|
|
436
|
-
}, [recordTouchSample, updateInteraction]);
|
|
437
|
-
const endTouch = React.useCallback((pageY, timestamp) => {
|
|
438
|
-
var _a, _b, _c, _d;
|
|
439
|
-
recordTouchSample(pageY, timestamp);
|
|
440
|
-
const { samples } = touchStateRef.current;
|
|
441
|
-
const firstSample = samples[0];
|
|
442
|
-
const lastSample = samples[samples.length - 1];
|
|
443
|
-
const deltaY = ((_a = lastSample === null || lastSample === void 0 ? void 0 : lastSample.pageY) !== null && _a !== void 0 ? _a : pageY) - ((_b = firstSample === null || firstSample === void 0 ? void 0 : firstSample.pageY) !== null && _b !== void 0 ? _b : pageY);
|
|
444
|
-
const deltaT = Math.max(1, ((_c = lastSample === null || lastSample === void 0 ? void 0 : lastSample.timestamp) !== null && _c !== void 0 ? _c : Date.now()) - ((_d = firstSample === null || firstSample === void 0 ? void 0 : firstSample.timestamp) !== null && _d !== void 0 ? _d : Date.now()));
|
|
445
|
-
finishInteraction((deltaY / deltaT) * 1000);
|
|
446
|
-
}, [finishInteraction, recordTouchSample]);
|
|
447
451
|
const handleAccessibilityAction = React.useCallback((event) => {
|
|
448
452
|
if (disabled)
|
|
449
453
|
return;
|
|
@@ -457,19 +461,26 @@ const WheelColumnBase = React.forwardRef(function WheelColumn({ options, value,
|
|
|
457
461
|
scrollToIndex(nextIndex, true);
|
|
458
462
|
emitSelectedIndex(nextIndex, 'accessibility');
|
|
459
463
|
}, [disabled, emitSelectedIndex, options, scrollToIndex]);
|
|
464
|
+
const handleAndroidSelectIndex = React.useCallback((nextIndex) => emitSelectedIndex(nextIndex, 'user'), [emitSelectedIndex]);
|
|
460
465
|
const contentStyle = (0, react_native_reanimated_1.useAnimatedStyle)(() => ({
|
|
461
466
|
transform: [{ translateY: CENTER_OFFSET - offsetY.value }],
|
|
462
467
|
}));
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
468
|
+
const webItems = React.useMemo(() => react_native_1.Platform.OS === 'web'
|
|
469
|
+
? options.map((item, index) => {
|
|
470
|
+
var _a;
|
|
471
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.itemContainer, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { accessibilityLabel: (_a = item.accessibilityLabel) !== null && _a !== void 0 ? _a : item.label, maxFontSizeMultiplier: (0, zkit_tools_1.getMaxFontSizeMultiplier)(), numberOfLines: numberOfLines, style: [
|
|
472
|
+
styles.itemText,
|
|
473
|
+
{ color: itemTextColor },
|
|
474
|
+
itemTextStyle,
|
|
475
|
+
index === displayIndex && [
|
|
476
|
+
styles.itemTextSelected,
|
|
477
|
+
{ color: selectedTextColor },
|
|
478
|
+
selectedItemTextStyle,
|
|
479
|
+
],
|
|
480
|
+
item.disabled && [styles.itemTextDisabled, { color: disabledTextColor }, disabledItemTextStyle],
|
|
481
|
+
], testID: item.testID, children: item.label }) }, `${String(optionKey(item, index))}-${index}`));
|
|
482
|
+
})
|
|
483
|
+
: null, [
|
|
473
484
|
disabledItemTextStyle,
|
|
474
485
|
disabledTextColor,
|
|
475
486
|
displayIndex,
|
|
@@ -480,17 +491,73 @@ const WheelColumnBase = React.forwardRef(function WheelColumn({ options, value,
|
|
|
480
491
|
selectedItemTextStyle,
|
|
481
492
|
selectedTextColor,
|
|
482
493
|
]);
|
|
483
|
-
const nativeItems = React.useMemo(() =>
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
494
|
+
const nativeItems = React.useMemo(() => react_native_1.Platform.OS === 'ios'
|
|
495
|
+
? options.map((item, index) => {
|
|
496
|
+
var _a;
|
|
497
|
+
return ({
|
|
498
|
+
label: item.label,
|
|
499
|
+
value: item.value,
|
|
500
|
+
textColor: item.disabled ? disabledTextColor : undefined,
|
|
501
|
+
testID: (_a = item.testID) !== null && _a !== void 0 ? _a : `wheel-item-${String(item.value)}-${index}`,
|
|
502
|
+
});
|
|
503
|
+
})
|
|
504
|
+
: [], [disabledTextColor, options]);
|
|
492
505
|
const selectedOption = options[displayIndex];
|
|
493
506
|
const canInteract = !disabled && options.length > 1 && findNearestEnabledIndex(options, displayIndex) >= 0;
|
|
507
|
+
const enabledFlags = React.useMemo(() => (react_native_1.Platform.OS === 'web' ? options.map((option) => !option.disabled) : []), [options]);
|
|
508
|
+
const panGesture = React.useMemo(() => react_native_gesture_handler_1.Gesture.Pan()
|
|
509
|
+
.enabled(react_native_1.Platform.OS === 'web' && canInteract)
|
|
510
|
+
.activeOffsetY([-PAN_ACTIVATION_OFFSET, PAN_ACTIVATION_OFFSET])
|
|
511
|
+
.averageTouches(true)
|
|
512
|
+
.onStart(() => {
|
|
513
|
+
'worklet';
|
|
514
|
+
cancelAnimationOnUI(offsetY);
|
|
515
|
+
isUserInteracting.value = true;
|
|
516
|
+
dragStartOffset.value = offsetY.value;
|
|
517
|
+
})
|
|
518
|
+
.onUpdate((event) => {
|
|
519
|
+
'worklet';
|
|
520
|
+
offsetY.value = clampNumber(dragStartOffset.value - event.translationY, 0, maxOffset);
|
|
521
|
+
})
|
|
522
|
+
.onEnd((event) => {
|
|
523
|
+
'worklet';
|
|
524
|
+
const currentOffset = clampNumber(offsetY.value, 0, maxOffset);
|
|
525
|
+
const currentIndex = Math.round(currentOffset / INTERNAL_WHEEL_ITEM_HEIGHT);
|
|
526
|
+
const rawIndex = getTargetIndexFromRelease(currentOffset, event.velocityY, maxIndex);
|
|
527
|
+
const direction = rawIndex - currentIndex;
|
|
528
|
+
const enabledIndex = findNearestEnabledFlagIndex(enabledFlags, rawIndex, direction);
|
|
529
|
+
const nextIndex = enabledIndex >= 0 ? enabledIndex : clampNumber(rawIndex, 0, maxIndex);
|
|
530
|
+
const nextOffset = indexToOffset(nextIndex);
|
|
531
|
+
offsetY.value = withTimingOnUI(nextOffset, {
|
|
532
|
+
duration: getSnapDuration(currentOffset, nextOffset),
|
|
533
|
+
easing: SNAP_EASING,
|
|
534
|
+
}, (finished) => {
|
|
535
|
+
if (!finished)
|
|
536
|
+
return;
|
|
537
|
+
isUserInteracting.value = false;
|
|
538
|
+
scheduleOnRNFromUI(emitSelectedIndex, nextIndex, 'user');
|
|
539
|
+
});
|
|
540
|
+
})
|
|
541
|
+
.onFinalize((_event, success) => {
|
|
542
|
+
'worklet';
|
|
543
|
+
if (success || !isUserInteracting.value)
|
|
544
|
+
return;
|
|
545
|
+
const currentOffset = clampNumber(offsetY.value, 0, maxOffset);
|
|
546
|
+
const currentIndex = Math.round(currentOffset / INTERNAL_WHEEL_ITEM_HEIGHT);
|
|
547
|
+
const direction = currentOffset - dragStartOffset.value;
|
|
548
|
+
const enabledIndex = findNearestEnabledFlagIndex(enabledFlags, currentIndex, direction);
|
|
549
|
+
const nextIndex = enabledIndex >= 0 ? enabledIndex : clampNumber(currentIndex, 0, maxIndex);
|
|
550
|
+
const nextOffset = indexToOffset(nextIndex);
|
|
551
|
+
offsetY.value = withTimingOnUI(nextOffset, {
|
|
552
|
+
duration: getSnapDuration(currentOffset, nextOffset),
|
|
553
|
+
easing: SNAP_EASING,
|
|
554
|
+
}, (finished) => {
|
|
555
|
+
if (!finished)
|
|
556
|
+
return;
|
|
557
|
+
isUserInteracting.value = false;
|
|
558
|
+
scheduleOnRNFromUI(emitSelectedIndex, nextIndex, 'user');
|
|
559
|
+
});
|
|
560
|
+
}), [canInteract, dragStartOffset, emitSelectedIndex, enabledFlags, isUserInteracting, maxIndex, maxOffset, offsetY]);
|
|
494
561
|
const columnStyle = React.useMemo(() => [styles.column, width != null && { width }, resolveWebInteractionStyle(disabled), style], [disabled, style, width]);
|
|
495
562
|
const resolvedAccessibilityState = React.useMemo(() => ({
|
|
496
563
|
...accessibilityState,
|
|
@@ -499,15 +566,10 @@ const WheelColumnBase = React.forwardRef(function WheelColumn({ options, value,
|
|
|
499
566
|
if (react_native_1.Platform.OS === 'ios') {
|
|
500
567
|
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { ...viewProps, accessibilityHint: accessibilityHint, accessibilityLabel: accessibilityLabel, accessibilityState: resolvedAccessibilityState, accessibilityValue: { text: (_j = selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.label) !== null && _j !== void 0 ? _j : '' }, style: columnStyle, pointerEvents: canInteract ? 'auto' : 'none', testID: testID, children: (0, jsx_runtime_1.jsx)(ZKitWheelPickerNativeComponent_1.ZKitWheelPicker, { ref: iosPickerRef, items: nativeItems, selectedIndex: iosSelectedIndex, onChange: handleIOSChange, numberOfLines: numberOfLines, rowHeight: IOS_NATIVE_PICKER_ROW_HEIGHT, style: styles.iosPicker, fontFamily: nativeFontFamily, fontSize: nativeFontSize, fontStyle: nativeFontStyle, fontWeight: nativeFontWeight, color: selectedTextColor }) }));
|
|
501
568
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
}, onResponderRelease: (event) => {
|
|
507
|
-
endTouch(event.nativeEvent.pageY, event.nativeEvent.timestamp);
|
|
508
|
-
}, onResponderTerminate: (event) => {
|
|
509
|
-
endTouch(event.nativeEvent.pageY, event.nativeEvent.timestamp);
|
|
510
|
-
} })] }));
|
|
569
|
+
if (react_native_1.Platform.OS === 'android') {
|
|
570
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { ...viewProps, accessibilityActions: [{ name: 'increment' }, { name: 'decrement' }], accessibilityHint: accessibilityHint, accessibilityLabel: accessibilityLabel, accessibilityRole: "adjustable", accessibilityState: resolvedAccessibilityState, accessibilityValue: { text: (_k = selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.label) !== null && _k !== void 0 ? _k : '' }, onAccessibilityAction: handleAccessibilityAction, style: columnStyle, testID: testID, children: (0, jsx_runtime_1.jsx)(AndroidVirtualizedWheel_1.AndroidVirtualizedWheel, { ref: androidWheelRef, options: options, selectedIndex: displayIndex, canInteract: canInteract, itemHeight: exports.WHEEL_ITEM_HEIGHT, visibleItems: exports.WHEEL_VISIBLE_ITEMS, itemTextColor: itemTextColor, selectedTextColor: selectedTextColor, disabledTextColor: disabledTextColor, itemTextStyle: itemTextStyle, selectedItemTextStyle: selectedItemTextStyle, disabledItemTextStyle: disabledItemTextStyle, numberOfLines: numberOfLines, resolveSelectableIndex: resolveSelectableIndex, onSelectIndex: handleAndroidSelectIndex }) }));
|
|
571
|
+
}
|
|
572
|
+
return ((0, jsx_runtime_1.jsx)(react_native_gesture_handler_1.GestureDetector, { gesture: panGesture, touchAction: "none", userSelect: "none", children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { ...viewProps, accessibilityActions: [{ name: 'increment' }, { name: 'decrement' }], accessibilityHint: accessibilityHint, accessibilityLabel: accessibilityLabel, accessibilityRole: "adjustable", accessibilityState: resolvedAccessibilityState, accessibilityValue: { text: (_l = selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.label) !== null && _l !== void 0 ? _l : '' }, collapsable: false, onAccessibilityAction: handleAccessibilityAction, style: columnStyle, testID: testID, children: [(0, jsx_runtime_1.jsx)(react_native_reanimated_1.default.View, { style: [styles.content, contentStyle], pointerEvents: "none", children: webItems }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.touchSurface, collapsable: false, pointerEvents: "box-only" })] }) }));
|
|
511
573
|
});
|
|
512
574
|
exports.WheelColumn = React.memo(WheelColumnBase);
|
|
513
575
|
const styles = react_native_1.StyleSheet.create({
|
package/package.json
CHANGED
|
@@ -1017,31 +1017,39 @@ export type CheckboxIndicatorProps = {
|
|
|
1017
1017
|
};
|
|
1018
1018
|
|
|
1019
1019
|
export function CheckboxIndicator({ children, style }: CheckboxIndicatorProps) {
|
|
1020
|
-
const
|
|
1021
|
-
|
|
1020
|
+
const {
|
|
1021
|
+
boxSize,
|
|
1022
|
+
checkedSv,
|
|
1023
|
+
customIndicator: contextCustomIndicator,
|
|
1024
|
+
defaultIcon,
|
|
1025
|
+
indeterminateIndicatorColor,
|
|
1026
|
+
indeterminateSv,
|
|
1027
|
+
slot,
|
|
1028
|
+
} = useCheckboxIndicatorContext();
|
|
1029
|
+
const customIndicator = children ?? contextCustomIndicator;
|
|
1022
1030
|
|
|
1023
1031
|
const checkedIconAnimatedStyle = useAnimatedStyle(() => {
|
|
1024
|
-
const opacity =
|
|
1025
|
-
const scale = interpolate(
|
|
1032
|
+
const opacity = checkedSv.value * (1 - indeterminateSv.value);
|
|
1033
|
+
const scale = interpolate(checkedSv.value, [0, 1], [0.78, 1]);
|
|
1026
1034
|
return { opacity, transform: [{ scale }] };
|
|
1027
1035
|
});
|
|
1028
1036
|
|
|
1029
1037
|
const indeterminateAnimatedStyle = useAnimatedStyle(() => {
|
|
1030
|
-
const opacity =
|
|
1031
|
-
const scale = interpolate(
|
|
1038
|
+
const opacity = checkedSv.value * indeterminateSv.value;
|
|
1039
|
+
const scale = interpolate(indeterminateSv.value, [0, 1], [0.82, 1]);
|
|
1032
1040
|
return { opacity, transform: [{ scale }] };
|
|
1033
1041
|
});
|
|
1034
1042
|
|
|
1035
1043
|
const customIndicatorAnimatedStyle = useAnimatedStyle(() => {
|
|
1036
|
-
const opacity =
|
|
1037
|
-
const scale = interpolate(
|
|
1044
|
+
const opacity = checkedSv.value;
|
|
1045
|
+
const scale = interpolate(checkedSv.value, [0, 1], [0.78, 1]);
|
|
1038
1046
|
return { opacity, transform: [{ scale }] };
|
|
1039
1047
|
});
|
|
1040
1048
|
|
|
1041
|
-
const indeterminateWidth = Math.max(wp(8), Math.round(
|
|
1042
|
-
const indeterminateHeight = Math.max(wp(2), Math.round(
|
|
1049
|
+
const indeterminateWidth = Math.max(wp(8), Math.round(boxSize * 0.56));
|
|
1050
|
+
const indeterminateHeight = Math.max(wp(2), Math.round(boxSize * 0.11));
|
|
1043
1051
|
const resolvedCustomIndicator = isIndicatorRenderProp(customIndicator)
|
|
1044
|
-
? customIndicator(
|
|
1052
|
+
? customIndicator(slot)
|
|
1045
1053
|
: customIndicator;
|
|
1046
1054
|
|
|
1047
1055
|
return (
|
|
@@ -1050,8 +1058,8 @@ export function CheckboxIndicator({ children, style }: CheckboxIndicatorProps) {
|
|
|
1050
1058
|
style={[
|
|
1051
1059
|
styles.indicatorLayer,
|
|
1052
1060
|
{
|
|
1053
|
-
width:
|
|
1054
|
-
height:
|
|
1061
|
+
width: boxSize,
|
|
1062
|
+
height: boxSize,
|
|
1055
1063
|
},
|
|
1056
1064
|
style,
|
|
1057
1065
|
]}
|
|
@@ -1066,12 +1074,12 @@ export function CheckboxIndicator({ children, style }: CheckboxIndicatorProps) {
|
|
|
1066
1074
|
width: indeterminateWidth,
|
|
1067
1075
|
height: indeterminateHeight,
|
|
1068
1076
|
borderRadius: indeterminateHeight / 2,
|
|
1069
|
-
backgroundColor:
|
|
1077
|
+
backgroundColor: indeterminateIndicatorColor,
|
|
1070
1078
|
}}
|
|
1071
1079
|
/>
|
|
1072
1080
|
</Animated.View>
|
|
1073
1081
|
<Animated.View style={[styles.iconLayer, checkedIconAnimatedStyle]}>
|
|
1074
|
-
{
|
|
1082
|
+
{defaultIcon}
|
|
1075
1083
|
</Animated.View>
|
|
1076
1084
|
</>
|
|
1077
1085
|
)}
|
package/src/ui/Picker/README.md
CHANGED
|
@@ -188,4 +188,4 @@ Accessor props:
|
|
|
188
188
|
|
|
189
189
|
- `label/defaultLabel/onLabelChange` 不再作为 Picker 状态。label 是由已确认 `value + options` 推导出的展示结果,业务需要自定义显示时使用 `formatLabel` 或 trigger render context。
|
|
190
190
|
- `onChange` 表示最终表单值变化,和 TextInput、Radio、CheckboxGroup 的状态事件保持一致。
|
|
191
|
-
- iOS 保留原生 `UIPickerView`,Android
|
|
191
|
+
- iOS 保留原生 `UIPickerView`,Android 使用原生虚拟化滚动,Web 使用自绘 wheel。Picker 层只处理级联状态和弹层生命周期,不把额外 JS 工作塞进滚轮关键帧。
|
package/src/ui/Switch/index.tsx
CHANGED
|
@@ -897,7 +897,7 @@ function SwitchImpl(
|
|
|
897
897
|
/>
|
|
898
898
|
<View
|
|
899
899
|
style={[
|
|
900
|
-
StyleSheet.
|
|
900
|
+
StyleSheet.absoluteFill,
|
|
901
901
|
{
|
|
902
902
|
borderRadius: trackRadius,
|
|
903
903
|
backgroundColor: visualUncheckedTrackColor,
|
|
@@ -908,7 +908,7 @@ function SwitchImpl(
|
|
|
908
908
|
<Animated.View
|
|
909
909
|
pointerEvents="none"
|
|
910
910
|
style={[
|
|
911
|
-
StyleSheet.
|
|
911
|
+
StyleSheet.absoluteFill,
|
|
912
912
|
{
|
|
913
913
|
borderRadius: trackRadius,
|
|
914
914
|
backgroundColor: visualCheckedTrackColor,
|
|
@@ -918,8 +918,15 @@ function SwitchImpl(
|
|
|
918
918
|
/>
|
|
919
919
|
|
|
920
920
|
{hasStateText ? (
|
|
921
|
-
<View pointerEvents="none" style={styles.stateTextLayer}>
|
|
922
|
-
<Animated.View
|
|
921
|
+
<View pointerEvents="none" style={[StyleSheet.absoluteFill, styles.stateTextLayer]}>
|
|
922
|
+
<Animated.View
|
|
923
|
+
style={[
|
|
924
|
+
StyleSheet.absoluteFill,
|
|
925
|
+
styles.stateTextSlot,
|
|
926
|
+
checkedTextSlotStyle,
|
|
927
|
+
checkedTextAnimatedStyle,
|
|
928
|
+
]}
|
|
929
|
+
>
|
|
923
930
|
<Text
|
|
924
931
|
allowFontScaling={false}
|
|
925
932
|
ellipsizeMode="clip"
|
|
@@ -937,7 +944,14 @@ function SwitchImpl(
|
|
|
937
944
|
{stateText?.checked ?? ''}
|
|
938
945
|
</Text>
|
|
939
946
|
</Animated.View>
|
|
940
|
-
<Animated.View
|
|
947
|
+
<Animated.View
|
|
948
|
+
style={[
|
|
949
|
+
StyleSheet.absoluteFill,
|
|
950
|
+
styles.stateTextSlot,
|
|
951
|
+
uncheckedTextSlotStyle,
|
|
952
|
+
uncheckedTextAnimatedStyle,
|
|
953
|
+
]}
|
|
954
|
+
>
|
|
941
955
|
<Text
|
|
942
956
|
allowFontScaling={false}
|
|
943
957
|
ellipsizeMode="clip"
|
|
@@ -1069,12 +1083,10 @@ const styles = StyleSheet.create({
|
|
|
1069
1083
|
position: 'absolute',
|
|
1070
1084
|
},
|
|
1071
1085
|
stateTextLayer: {
|
|
1072
|
-
...StyleSheet.absoluteFillObject,
|
|
1073
1086
|
alignItems: 'center',
|
|
1074
1087
|
justifyContent: 'center',
|
|
1075
1088
|
},
|
|
1076
1089
|
stateTextSlot: {
|
|
1077
|
-
...StyleSheet.absoluteFillObject,
|
|
1078
1090
|
alignItems: 'center',
|
|
1079
1091
|
justifyContent: 'center',
|
|
1080
1092
|
overflow: 'hidden',
|