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.
@@ -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/Web 走自绘 transform 路径。
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
- exports.WHEEL_ITEM_HEIGHT = react_native_1.Platform.OS === 'ios' ? IOS_NATIVE_PICKER_HEIGHT / exports.WHEEL_VISIBLE_ITEMS : BASE_WHEEL_ITEM_HEIGHT;
60
- const CENTER_OFFSET = exports.WHEEL_ITEM_HEIGHT * Math.floor(exports.WHEEL_VISIBLE_ITEMS / 2);
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 ANDROID_VELOCITY_WINDOW_MS = 90;
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 * exports.WHEEL_ITEM_HEIGHT;
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
- const currentIndexFloat = offset / exports.WHEEL_ITEM_HEIGHT;
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
- const distanceItems = Math.abs(toOffset - fromOffset) / exports.WHEEL_ITEM_HEIGHT;
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, () => ({ scrollToIndex, scrollToValue, settleToNearest, syncCurrentSelection }), [scrollToIndex, scrollToValue, settleToNearest, syncCurrentSelection]);
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 items = React.useMemo(() => options.map((item, index) => {
464
- var _a;
465
- return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.itemContainer, children: (0, jsx_runtime_1.jsx)(react_native_reanimated_1.default.Text, { accessibilityLabel: (_a = item.accessibilityLabel) !== null && _a !== void 0 ? _a : item.label, maxFontSizeMultiplier: (0, zkit_tools_1.getMaxFontSizeMultiplier)(), numberOfLines: numberOfLines, style: [
466
- styles.itemText,
467
- { color: itemTextColor },
468
- itemTextStyle,
469
- index === displayIndex && [styles.itemTextSelected, { color: selectedTextColor }, selectedItemTextStyle],
470
- item.disabled && [styles.itemTextDisabled, { color: disabledTextColor }, disabledItemTextStyle],
471
- ], testID: item.testID, children: item.label }) }, `${String(optionKey(item, index))}-${index}`));
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(() => options.map((item, index) => {
484
- var _a;
485
- return ({
486
- label: item.label,
487
- value: item.value,
488
- textColor: item.disabled ? disabledTextColor : undefined,
489
- testID: (_a = item.testID) !== null && _a !== void 0 ? _a : `wheel-item-${String(item.value)}-${index}`,
490
- });
491
- }), [disabledTextColor, options]);
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
- return ((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: (_k = selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.label) !== null && _k !== void 0 ? _k : '' }, 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", renderToHardwareTextureAndroid: react_native_1.Platform.OS === 'android', shouldRasterizeIOS: false, children: items }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.touchSurface, collapsable: false, pointerEvents: "box-only", onStartShouldSetResponder: () => canInteract, onStartShouldSetResponderCapture: () => canInteract, onMoveShouldSetResponder: () => canInteract, onMoveShouldSetResponderCapture: () => canInteract, onResponderTerminationRequest: () => false, onResponderGrant: (event) => {
503
- beginTouch(event.nativeEvent.pageY, event.nativeEvent.timestamp);
504
- }, onResponderMove: (event) => {
505
- moveTouch(event.nativeEvent.pageY, event.nativeEvent.timestamp);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zkit-ui",
3
- "version": "2.0.9",
3
+ "version": "2.0.11",
4
4
  "private": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1017,31 +1017,39 @@ export type CheckboxIndicatorProps = {
1017
1017
  };
1018
1018
 
1019
1019
  export function CheckboxIndicator({ children, style }: CheckboxIndicatorProps) {
1020
- const context = useCheckboxIndicatorContext();
1021
- const customIndicator = children ?? context.customIndicator;
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 = context.checkedSv.value * (1 - context.indeterminateSv.value);
1025
- const scale = interpolate(context.checkedSv.value, [0, 1], [0.78, 1]);
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 = context.checkedSv.value * context.indeterminateSv.value;
1031
- const scale = interpolate(context.indeterminateSv.value, [0, 1], [0.82, 1]);
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 = context.checkedSv.value;
1037
- const scale = interpolate(context.checkedSv.value, [0, 1], [0.78, 1]);
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(context.boxSize * 0.56));
1042
- const indeterminateHeight = Math.max(wp(2), Math.round(context.boxSize * 0.11));
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(context.slot)
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: context.boxSize,
1054
- height: context.boxSize,
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: context.indeterminateIndicatorColor,
1077
+ backgroundColor: indeterminateIndicatorColor,
1070
1078
  }}
1071
1079
  />
1072
1080
  </Animated.View>
1073
1081
  <Animated.View style={[styles.iconLayer, checkedIconAnimatedStyle]}>
1074
- {context.defaultIcon}
1082
+ {defaultIcon}
1075
1083
  </Animated.View>
1076
1084
  </>
1077
1085
  )}
@@ -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/Web 继续走自绘 wheel。Picker 层只处理级联状态和弹层生命周期,不把额外 JS 工作塞进滚轮关键帧。
191
+ - iOS 保留原生 `UIPickerView`,Android 使用原生虚拟化滚动,Web 使用自绘 wheel。Picker 层只处理级联状态和弹层生命周期,不把额外 JS 工作塞进滚轮关键帧。
@@ -897,7 +897,7 @@ function SwitchImpl(
897
897
  />
898
898
  <View
899
899
  style={[
900
- StyleSheet.absoluteFillObject,
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.absoluteFillObject,
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 style={[styles.stateTextSlot, checkedTextSlotStyle, checkedTextAnimatedStyle]}>
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 style={[styles.stateTextSlot, uncheckedTextSlotStyle, uncheckedTextAnimatedStyle]}>
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',