@seakoi/native-ui 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/commonjs/components/base/carousel/carousel-indicator.js +56 -0
  3. package/dist/commonjs/components/base/carousel/carousel-slides.js +140 -0
  4. package/dist/commonjs/components/base/carousel/carousel.js +114 -122
  5. package/dist/commonjs/components/base/carousel/hooks/index.js +0 -14
  6. package/dist/commonjs/components/base/carousel/hooks/use-carousel-index.js +16 -13
  7. package/dist/commonjs/components/base/carousel/hooks/use-carousel-lifecycle.js +6 -2
  8. package/dist/commonjs/components/base/carousel/hooks/use-carousel-pan-responder.js +40 -12
  9. package/dist/commonjs/components/base/carousel/hooks/use-carousel-position.js +6 -2
  10. package/dist/commonjs/components/base/carousel/index.js +1 -15
  11. package/dist/commonjs/components/base/carousel/style/index.js +12 -0
  12. package/dist/commonjs/components/base/date-picker/date-picker.js +56 -44
  13. package/dist/commonjs/components/base/date-picker/date-range-picker.js +142 -50
  14. package/dist/commonjs/components/base/date-picker/style/index.js +15 -0
  15. package/dist/commonjs/components/base/date-picker-view/date-picker-view.js +19 -53
  16. package/dist/commonjs/components/base/date-picker-view/index.js +0 -22
  17. package/dist/commonjs/components/base/index.js +30 -8
  18. package/dist/commonjs/components/base/picker/index.js +26 -4
  19. package/dist/commonjs/components/base/picker/picker-content.js +60 -0
  20. package/dist/commonjs/components/base/picker/picker-context.js +9 -0
  21. package/dist/commonjs/components/base/picker/picker-field.js +37 -0
  22. package/dist/commonjs/components/base/picker/picker.js +22 -96
  23. package/dist/commonjs/components/base/picker/style/index.js +1 -3
  24. package/dist/commonjs/components/base/picker-backup/base-picker-container.js +50 -0
  25. package/dist/commonjs/components/base/picker-backup/index.js +27 -0
  26. package/dist/commonjs/components/base/picker-backup/picker-backup.js +75 -0
  27. package/dist/commonjs/components/base/picker-backup/picker-copy.js +106 -0
  28. package/dist/commonjs/components/base/{picker → picker-backup}/picker-trigger.js +5 -5
  29. package/dist/commonjs/components/base/picker-backup/style/index.js +19 -0
  30. package/dist/commonjs/components/base/picker-backup/utils.js +53 -0
  31. package/dist/commonjs/components/base/picker-view/picker-view-column.js +15 -0
  32. package/dist/commonjs/components/base/picker-view/picker-view.js +4 -4
  33. package/dist/commonjs/components/base/tag/index.js +20 -0
  34. package/dist/commonjs/components/base/tag/style/index.js +89 -0
  35. package/dist/commonjs/components/base/tag/tag-context.js +12 -0
  36. package/dist/commonjs/components/base/tag/tag-group.js +35 -0
  37. package/dist/commonjs/components/base/tag/tag.js +47 -0
  38. package/dist/commonjs/components/base/tag/types.js +5 -0
  39. package/dist/module/components/base/carousel/carousel-indicator.js +51 -0
  40. package/dist/module/components/base/carousel/carousel-slides.js +135 -0
  41. package/dist/module/components/base/carousel/carousel.js +116 -124
  42. package/dist/module/components/base/carousel/hooks/index.js +0 -2
  43. package/dist/module/components/base/carousel/hooks/use-carousel-index.js +15 -11
  44. package/dist/module/components/base/carousel/hooks/use-carousel-lifecycle.js +6 -2
  45. package/dist/module/components/base/carousel/hooks/use-carousel-pan-responder.js +40 -11
  46. package/dist/module/components/base/carousel/hooks/use-carousel-position.js +5 -1
  47. package/dist/module/components/base/carousel/index.js +1 -5
  48. package/dist/module/components/base/carousel/style/index.js +12 -0
  49. package/dist/module/components/base/date-picker/date-picker.js +60 -48
  50. package/dist/module/components/base/date-picker/date-range-picker.js +146 -54
  51. package/dist/module/components/base/date-picker/style/index.js +11 -0
  52. package/dist/module/components/base/date-picker-view/date-picker-view.js +23 -57
  53. package/dist/module/components/base/date-picker-view/index.js +1 -3
  54. package/dist/module/components/base/index.js +2 -0
  55. package/dist/module/components/base/picker/index.js +9 -1
  56. package/dist/module/components/base/picker/picker-content.js +54 -0
  57. package/dist/module/components/base/picker/picker-context.js +4 -0
  58. package/dist/module/components/base/picker/picker-field.js +32 -0
  59. package/dist/module/components/base/picker/picker.js +25 -99
  60. package/dist/module/components/base/picker/style/index.js +1 -3
  61. package/dist/module/components/base/picker-backup/base-picker-container.js +44 -0
  62. package/dist/module/components/base/picker-backup/index.js +4 -0
  63. package/dist/module/components/base/picker-backup/picker-backup.js +69 -0
  64. package/dist/module/components/base/picker-backup/picker-copy.js +101 -0
  65. package/dist/module/components/base/{picker → picker-backup}/picker-trigger.js +2 -2
  66. package/dist/module/components/base/picker-backup/style/index.js +15 -0
  67. package/dist/module/components/base/picker-backup/utils.js +48 -0
  68. package/dist/module/components/base/picker-view/picker-view-column.js +15 -0
  69. package/dist/module/components/base/picker-view/picker-view.js +4 -4
  70. package/dist/module/components/base/tag/index.js +5 -0
  71. package/dist/module/components/base/tag/style/index.js +85 -0
  72. package/dist/module/components/base/tag/tag-context.js +8 -0
  73. package/dist/module/components/base/tag/tag-group.js +29 -0
  74. package/dist/module/components/base/tag/tag.js +41 -0
  75. package/dist/module/components/base/tag/types.js +3 -0
  76. package/dist/typescript/components/base/carousel/carousel-indicator.d.ts +42 -0
  77. package/dist/typescript/components/base/carousel/carousel-indicator.d.ts.map +1 -0
  78. package/dist/typescript/components/base/carousel/carousel-slides.d.ts +49 -0
  79. package/dist/typescript/components/base/carousel/carousel-slides.d.ts.map +1 -0
  80. package/dist/typescript/components/base/carousel/carousel.d.ts +16 -11
  81. package/dist/typescript/components/base/carousel/carousel.d.ts.map +1 -1
  82. package/dist/typescript/components/base/carousel/hooks/index.d.ts +0 -2
  83. package/dist/typescript/components/base/carousel/hooks/index.d.ts.map +1 -1
  84. package/dist/typescript/components/base/carousel/hooks/use-carousel-index.d.ts.map +1 -1
  85. package/dist/typescript/components/base/carousel/hooks/use-carousel-lifecycle.d.ts.map +1 -1
  86. package/dist/typescript/components/base/carousel/hooks/use-carousel-pan-responder.d.ts.map +1 -1
  87. package/dist/typescript/components/base/carousel/hooks/use-carousel-position.d.ts.map +1 -1
  88. package/dist/typescript/components/base/carousel/index.d.ts +1 -4
  89. package/dist/typescript/components/base/carousel/index.d.ts.map +1 -1
  90. package/dist/typescript/components/base/carousel/style/index.d.ts +12 -0
  91. package/dist/typescript/components/base/carousel/style/index.d.ts.map +1 -1
  92. package/dist/typescript/components/base/carousel/types.d.ts +8 -17
  93. package/dist/typescript/components/base/carousel/types.d.ts.map +1 -1
  94. package/dist/typescript/components/base/date-picker/date-picker.d.ts +4 -2
  95. package/dist/typescript/components/base/date-picker/date-picker.d.ts.map +1 -1
  96. package/dist/typescript/components/base/date-picker/date-range-picker.d.ts +12 -3
  97. package/dist/typescript/components/base/date-picker/date-range-picker.d.ts.map +1 -1
  98. package/dist/typescript/components/base/date-picker/style/index.d.ts +9 -0
  99. package/dist/typescript/components/base/date-picker/style/index.d.ts.map +1 -0
  100. package/dist/typescript/components/base/date-picker-view/date-picker-view.d.ts +1 -6
  101. package/dist/typescript/components/base/date-picker-view/date-picker-view.d.ts.map +1 -1
  102. package/dist/typescript/components/base/date-picker-view/index.d.ts +0 -2
  103. package/dist/typescript/components/base/date-picker-view/index.d.ts.map +1 -1
  104. package/dist/typescript/components/base/date-picker-view/types.d.ts +1 -1
  105. package/dist/typescript/components/base/index.d.ts +2 -0
  106. package/dist/typescript/components/base/index.d.ts.map +1 -1
  107. package/dist/typescript/components/base/picker/index.d.ts +7 -1
  108. package/dist/typescript/components/base/picker/index.d.ts.map +1 -1
  109. package/dist/typescript/components/base/picker/picker-content.d.ts +15 -0
  110. package/dist/typescript/components/base/picker/picker-content.d.ts.map +1 -0
  111. package/dist/typescript/components/base/picker/picker-context.d.ts +18 -0
  112. package/dist/typescript/components/base/picker/picker-context.d.ts.map +1 -0
  113. package/dist/typescript/components/base/picker/picker-field.d.ts +10 -0
  114. package/dist/typescript/components/base/picker/picker-field.d.ts.map +1 -0
  115. package/dist/typescript/components/base/picker/picker.d.ts +13 -11
  116. package/dist/typescript/components/base/picker/picker.d.ts.map +1 -1
  117. package/dist/typescript/components/base/picker/style/index.d.ts +0 -2
  118. package/dist/typescript/components/base/picker/style/index.d.ts.map +1 -1
  119. package/dist/typescript/components/base/picker-backup/base-picker-container.d.ts +15 -0
  120. package/dist/typescript/components/base/picker-backup/base-picker-container.d.ts.map +1 -0
  121. package/dist/typescript/components/base/picker-backup/index.d.ts +3 -0
  122. package/dist/typescript/components/base/picker-backup/index.d.ts.map +1 -0
  123. package/dist/typescript/components/base/picker-backup/picker-backup.d.ts +26 -0
  124. package/dist/typescript/components/base/picker-backup/picker-backup.d.ts.map +1 -0
  125. package/dist/typescript/components/base/picker-backup/picker-copy.d.ts +13 -0
  126. package/dist/typescript/components/base/picker-backup/picker-copy.d.ts.map +1 -0
  127. package/dist/typescript/components/base/picker-backup/picker-trigger.d.ts.map +1 -0
  128. package/dist/typescript/components/base/picker-backup/style/index.d.ts +13 -0
  129. package/dist/typescript/components/base/picker-backup/style/index.d.ts.map +1 -0
  130. package/dist/typescript/components/base/picker-backup/utils.d.ts +8 -0
  131. package/dist/typescript/components/base/picker-backup/utils.d.ts.map +1 -0
  132. package/dist/typescript/components/base/picker-view/picker-view-column.d.ts.map +1 -1
  133. package/dist/typescript/components/base/picker-view/utils/picker.d.ts +3 -3
  134. package/dist/typescript/components/base/picker-view/utils/picker.d.ts.map +1 -1
  135. package/dist/typescript/components/base/tag/index.d.ts +5 -0
  136. package/dist/typescript/components/base/tag/index.d.ts.map +1 -0
  137. package/dist/typescript/components/base/tag/style/index.d.ts +61 -0
  138. package/dist/typescript/components/base/tag/style/index.d.ts.map +1 -0
  139. package/dist/typescript/components/base/tag/tag-context.d.ts +3 -0
  140. package/dist/typescript/components/base/tag/tag-context.d.ts.map +1 -0
  141. package/dist/typescript/components/base/tag/tag-group.d.ts +4 -0
  142. package/dist/typescript/components/base/tag/tag-group.d.ts.map +1 -0
  143. package/dist/typescript/components/base/tag/tag.d.ts +4 -0
  144. package/dist/typescript/components/base/tag/tag.d.ts.map +1 -0
  145. package/dist/typescript/components/base/tag/types.d.ts +48 -0
  146. package/dist/typescript/components/base/tag/types.d.ts.map +1 -0
  147. package/package.json +12 -4
  148. package/src/components/base/carousel/carousel-indicator.tsx +80 -0
  149. package/src/components/base/carousel/carousel-slides.tsx +177 -0
  150. package/src/components/base/carousel/carousel.tsx +108 -118
  151. package/src/components/base/carousel/hooks/index.ts +0 -2
  152. package/src/components/base/carousel/hooks/use-carousel-index.ts +13 -9
  153. package/src/components/base/carousel/hooks/use-carousel-lifecycle.ts +4 -3
  154. package/src/components/base/carousel/hooks/use-carousel-pan-responder.ts +40 -16
  155. package/src/components/base/carousel/hooks/use-carousel-position.ts +4 -1
  156. package/src/components/base/carousel/index.ts +1 -3
  157. package/src/components/base/carousel/style/index.ts +12 -0
  158. package/src/components/base/carousel/types.ts +8 -21
  159. package/src/components/base/date-picker/date-picker.tsx +64 -61
  160. package/src/components/base/date-picker/date-range-picker.tsx +178 -70
  161. package/src/components/base/date-picker/style/index.ts +10 -0
  162. package/src/components/base/date-picker-view/date-picker-view.tsx +21 -68
  163. package/src/components/base/date-picker-view/index.ts +0 -2
  164. package/src/components/base/date-picker-view/types.ts +1 -1
  165. package/src/components/base/index.ts +2 -0
  166. package/src/components/base/picker/index.ts +11 -1
  167. package/src/components/base/picker/picker-content.tsx +75 -0
  168. package/src/components/base/picker/picker-context.ts +19 -0
  169. package/src/components/base/picker/picker-field.tsx +50 -0
  170. package/src/components/base/picker/picker.tsx +38 -114
  171. package/src/components/base/picker/style/index.ts +0 -2
  172. package/src/components/base/picker-backup/base-picker-container.tsx +55 -0
  173. package/src/components/base/picker-backup/index.ts +2 -0
  174. package/src/components/base/picker-backup/picker-backup.tsx +110 -0
  175. package/src/components/base/picker-backup/picker-copy.tsx +125 -0
  176. package/src/components/base/{picker → picker-backup}/picker-trigger.tsx +2 -2
  177. package/src/components/base/picker-backup/style/index.ts +14 -0
  178. package/src/components/base/picker-backup/utils.ts +62 -0
  179. package/src/components/base/picker-view/picker-view-column.tsx +20 -0
  180. package/src/components/base/picker-view/picker-view.tsx +4 -4
  181. package/src/components/base/picker-view/utils/picker.ts +3 -5
  182. package/src/components/base/tag/index.ts +5 -0
  183. package/src/components/base/tag/style/index.tsx +84 -0
  184. package/src/components/base/tag/tag-context.ts +9 -0
  185. package/src/components/base/tag/tag-group.tsx +31 -0
  186. package/src/components/base/tag/tag.tsx +50 -0
  187. package/src/components/base/tag/types.ts +71 -0
  188. package/dist/commonjs/components/base/carousel/carousel-item.js +0 -45
  189. package/dist/commonjs/components/base/carousel/constants.js +0 -25
  190. package/dist/commonjs/components/base/carousel/hooks/use-carousel-indicator.js +0 -63
  191. package/dist/commonjs/components/base/carousel/hooks/use-carousel-slides.js +0 -95
  192. package/dist/commonjs/components/base/carousel/utils.js +0 -63
  193. package/dist/commonjs/components/base/date-picker-view/date-range-picker-view.js +0 -145
  194. package/dist/commonjs/components/base/date-picker-view/date-time-picker.js +0 -39
  195. package/dist/module/components/base/carousel/carousel-item.js +0 -40
  196. package/dist/module/components/base/carousel/constants.js +0 -21
  197. package/dist/module/components/base/carousel/hooks/use-carousel-indicator.js +0 -58
  198. package/dist/module/components/base/carousel/hooks/use-carousel-slides.js +0 -90
  199. package/dist/module/components/base/carousel/utils.js +0 -55
  200. package/dist/module/components/base/date-picker-view/date-range-picker-view.js +0 -138
  201. package/dist/module/components/base/date-picker-view/date-time-picker.js +0 -34
  202. package/dist/typescript/components/base/carousel/carousel-item.d.ts +0 -26
  203. package/dist/typescript/components/base/carousel/carousel-item.d.ts.map +0 -1
  204. package/dist/typescript/components/base/carousel/constants.d.ts +0 -17
  205. package/dist/typescript/components/base/carousel/constants.d.ts.map +0 -1
  206. package/dist/typescript/components/base/carousel/hooks/use-carousel-indicator.d.ts +0 -37
  207. package/dist/typescript/components/base/carousel/hooks/use-carousel-indicator.d.ts.map +0 -1
  208. package/dist/typescript/components/base/carousel/hooks/use-carousel-slides.d.ts +0 -51
  209. package/dist/typescript/components/base/carousel/hooks/use-carousel-slides.d.ts.map +0 -1
  210. package/dist/typescript/components/base/carousel/utils.d.ts +0 -25
  211. package/dist/typescript/components/base/carousel/utils.d.ts.map +0 -1
  212. package/dist/typescript/components/base/date-picker-view/date-range-picker-view.d.ts +0 -26
  213. package/dist/typescript/components/base/date-picker-view/date-range-picker-view.d.ts.map +0 -1
  214. package/dist/typescript/components/base/date-picker-view/date-time-picker.d.ts +0 -3
  215. package/dist/typescript/components/base/date-picker-view/date-time-picker.d.ts.map +0 -1
  216. package/dist/typescript/components/base/picker/picker-trigger.d.ts.map +0 -1
  217. package/src/components/base/carousel/carousel-item.tsx +0 -35
  218. package/src/components/base/carousel/constants.ts +0 -19
  219. package/src/components/base/carousel/hooks/use-carousel-indicator.tsx +0 -84
  220. package/src/components/base/carousel/hooks/use-carousel-slides.tsx +0 -131
  221. package/src/components/base/carousel/utils.ts +0 -55
  222. package/src/components/base/date-picker-view/date-range-picker-view.tsx +0 -191
  223. package/src/components/base/date-picker-view/date-time-picker.tsx +0 -34
  224. /package/dist/typescript/components/base/{picker → picker-backup}/picker-trigger.d.ts +0 -0
@@ -4,32 +4,36 @@ import {
4
4
  Animated,
5
5
  type LayoutChangeEvent,
6
6
  type StyleProp,
7
- StyleSheet,
8
7
  View,
9
8
  type ViewStyle,
10
9
  } from 'react-native';
11
10
 
12
11
  import { useTheme } from '#native-provider';
13
12
 
13
+ import { CarouselIndicator } from './carousel-indicator';
14
+ import { CarouselSlides } from './carousel-slides';
14
15
  import {
15
16
  useCarouselAutoplay,
16
17
  useCarouselIndex,
17
- useCarouselIndicator,
18
18
  useCarouselLifecycle,
19
19
  useCarouselPanResponder,
20
20
  useCarouselPosition,
21
- useCarouselSlides,
22
21
  useCarouselSwipe,
23
22
  } from './hooks';
24
23
  import { useCarouselStyles } from './style';
25
24
  import type { CarouselProps } from './types';
26
- import { getDefaultTotal, isRenderPropChildren } from './utils';
27
25
 
28
26
  interface LayoutState {
29
27
  width: number;
30
28
  height: number;
31
29
  }
32
30
 
31
+ /**
32
+ * 虚拟渲染触发阈值
33
+ * 当轮播项数量超过此值时启用虚拟渲染优化
34
+ */
35
+ const VIRTUAL_RENDER_THRESHOLD = 10;
36
+
33
37
  /**
34
38
  * 轮播图组件
35
39
  *
@@ -38,26 +42,31 @@ interface LayoutState {
38
42
  * @example
39
43
  * ```tsx
40
44
  * // 基础用法
41
- * <Carousel>
42
- * <Carousel.Item><View /></Carousel.Item>
43
- * <Carousel.Item><View /></Carousel.Item>
44
- * </Carousel>
45
+ * <Carousel
46
+ * data={[1, 2, 3]}
47
+ * renderItem={(item) => <View><Text>{item}</Text></View>}
48
+ * />
45
49
  *
46
50
  * // 循环播放和自动轮播
47
- * <Carousel loop autoplay autoplayInterval={3000}>
48
- * {items.map(item => <Carousel.Item key={item.id}>{item.content}</Carousel.Item>)}
49
- * </Carousel>
51
+ * <Carousel
52
+ * data={items}
53
+ * renderItem={(item) => <View>{item.content}</View>}
54
+ * loop
55
+ * autoplay
56
+ * autoplayInterval={3000}
57
+ * />
50
58
  *
51
59
  * // 虚拟渲染(大量数据)
52
- * <Carousel total={1000}>
53
- * {(index) => <Carousel.Item><Text>{index}</Text></Carousel.Item>}
54
- * </Carousel>
60
+ * <Carousel
61
+ * data={largeDataArray}
62
+ * renderItem={(item, index) => <View><Text>{index}: {item}</Text></View>}
63
+ * />
55
64
  * ```
56
65
  *
57
66
  * @param props - 组件属性
58
67
  * @returns 轮播图组件
59
68
  */
60
- export const Carousel = (props: CarouselProps) => {
69
+ export const Carousel = <Data = unknown,>(props: CarouselProps<Data>) => {
61
70
  const {
62
71
  defaultIndex = 0,
63
72
  allowTouchMove = true,
@@ -73,8 +82,8 @@ export const Carousel = (props: CarouselProps) => {
73
82
  stuckAtBoundary = true,
74
83
  rubberband = true,
75
84
  virtualOverscan = 2,
76
- total: totalProp,
77
- children,
85
+ data,
86
+ renderItem,
78
87
  style,
79
88
  blockNativeResponder = true,
80
89
  ref,
@@ -83,27 +92,31 @@ export const Carousel = (props: CarouselProps) => {
83
92
  const styles = useCarouselStyles();
84
93
  const theme = useTheme();
85
94
 
95
+ // 使用 state 存储 layout,使依赖更明确
86
96
  const [layout, setLayout] = useState<LayoutState>({ width: 0, height: 0 });
87
- const [dragging, setDragging] = useState(false);
88
- const [animating, setAnimating] = useState(false);
89
97
 
90
- const normalizedSlideSize = useMemo(() => {
91
- if (!Number.isFinite(slideSize)) return 100;
92
- return clamp(slideSize, 0, 100);
93
- }, [slideSize]);
98
+ // 分离拖动和动画状态,避免状态冲突
99
+ const [isDragging, setIsDragging] = useState(false);
100
+ const [isAnimating, setIsAnimating] = useState(false);
94
101
 
95
- const normalizedTrackOffset = useMemo(() => {
102
+ // 合并归一化计算,减少 useMemo 调用
103
+ const { normalizedSlideSize, normalizedTrackOffset } = useMemo(() => {
104
+ const slideSize_ = Number.isFinite(slideSize) ? clamp(slideSize, 0, 100) : 100;
105
+ let trackOffset_: number;
96
106
  if (stuckAtBoundary) {
97
- return 100 - normalizedSlideSize;
107
+ trackOffset_ = 100 - slideSize_;
108
+ } else {
109
+ trackOffset_ = Number.isFinite(trackOffset)
110
+ ? clamp(trackOffset, 0, 100 - slideSize_)
111
+ : 0;
98
112
  }
99
- if (!Number.isFinite(trackOffset)) return 0;
100
- return clamp(trackOffset, 0, 100 - normalizedSlideSize);
101
- }, [normalizedSlideSize, trackOffset, stuckAtBoundary]);
113
+ return {
114
+ normalizedSlideSize: slideSize_,
115
+ normalizedTrackOffset: trackOffset_,
116
+ };
117
+ }, [slideSize, trackOffset, stuckAtBoundary]);
102
118
 
103
- const total = useMemo(() => {
104
- const normalizedDefault = getDefaultTotal(children);
105
- return totalProp ?? normalizedDefault;
106
- }, [children, totalProp]);
119
+ const total = data.length;
107
120
 
108
121
  const loopEnabled = loop && total > 1;
109
122
 
@@ -129,17 +142,15 @@ export const Carousel = (props: CarouselProps) => {
129
142
 
130
143
  const extTotal = loopEnabled ? total + clonesBefore + clonesAfter : total;
131
144
 
132
- const slidePixels = useMemo(() => {
145
+ // 合并像素计算,减少重复的 trackSize 获取
146
+ const { slidePixels, trackOffsetPixels } = useMemo(() => {
133
147
  const trackSize = direction === 'horizontal' ? layout.width : layout.height;
134
- if (trackSize <= 0) return 0;
135
- return trackSize * (normalizedSlideSize / 100);
136
- }, [direction, layout.height, layout.width, normalizedSlideSize]);
137
-
138
- const trackOffsetPixels = useMemo(() => {
139
- const trackSize = direction === 'horizontal' ? layout.width : layout.height;
140
- if (trackSize <= 0) return 0;
141
- return trackSize * (normalizedTrackOffset / 100);
142
- }, [direction, layout.height, layout.width, normalizedTrackOffset]);
148
+ if (trackSize <= 0) return { slidePixels: 0, trackOffsetPixels: 0 };
149
+ return {
150
+ slidePixels: trackSize * (normalizedSlideSize / 100),
151
+ trackOffsetPixels: trackSize * (normalizedTrackOffset / 100),
152
+ };
153
+ }, [direction, layout, normalizedSlideSize, normalizedTrackOffset]);
143
154
 
144
155
  const {
145
156
  current,
@@ -167,6 +178,15 @@ export const Carousel = (props: CarouselProps) => {
167
178
  const { position, positionValueRef, animateTo, setPositionImmediate } =
168
179
  useCarouselPosition();
169
180
 
181
+ // 状态设置函数
182
+ const setDragging = useCallback((dragging: boolean) => {
183
+ setIsDragging(dragging);
184
+ }, []);
185
+
186
+ const setAnimating = useCallback((animating: boolean) => {
187
+ setIsAnimating(animating);
188
+ }, []);
189
+
170
190
  // 切换逻辑
171
191
  const { swipeNext, swipePrev, swipeToExtIndex } = useCarouselSwipe({
172
192
  total,
@@ -191,7 +211,7 @@ export const Carousel = (props: CarouselProps) => {
191
211
  autoplay,
192
212
  autoplayInterval,
193
213
  total,
194
- dragging,
214
+ dragging: isDragging,
195
215
  current,
196
216
  swipeNext,
197
217
  swipePrev,
@@ -213,8 +233,10 @@ export const Carousel = (props: CarouselProps) => {
213
233
  const onLayoutTrack = useCallback((e: LayoutChangeEvent) => {
214
234
  const { width, height } = e.nativeEvent.layout;
215
235
  setLayout(prev => {
216
- if (prev.width === width && prev.height === height) return prev;
217
- return { width, height };
236
+ if (prev.width !== width || prev.height !== height) {
237
+ return { width, height };
238
+ }
239
+ return prev;
218
240
  });
219
241
  }, []);
220
242
 
@@ -248,20 +270,20 @@ export const Carousel = (props: CarouselProps) => {
248
270
  }, [direction, position, trackOffsetPixels]);
249
271
 
250
272
  // 计算虚拟渲染的范围
251
- // 注意: extIndexRef 的变化不会触发重新计算,
252
- // 通过 dragging 和 animating 状态的变化来触发虚拟范围的更新
273
+ // 使用 current 状态而不是 extIndexRef,确保虚拟范围与当前索引同步
253
274
  const virtualRange = useMemo(() => {
254
275
  if (total <= 0 || slidePixels <= 0) {
255
276
  return null;
256
277
  }
257
278
 
258
- const isVirtual =
259
- isRenderPropChildren(children) && typeof totalProp === 'number';
279
+ const isVirtual = total > VIRTUAL_RENDER_THRESHOLD;
280
+ const isInteracting = isDragging || isAnimating;
260
281
  const effectiveOverscan = isVirtual
261
- ? Math.max(virtualOverscan, dragging || animating ? 1 : 0)
282
+ ? Math.max(virtualOverscan, isInteracting ? 1 : 0)
262
283
  : virtualOverscan;
263
284
 
264
- const currentExtIndex = extIndexRef.current;
285
+ // 使用 current 计算 extIndex,确保响应式更新
286
+ const currentExtIndex = loopEnabled ? current + clonesBefore : current;
265
287
  const start = isVirtual
266
288
  ? clamp(currentExtIndex - effectiveOverscan, 0, extTotal - 1)
267
289
  : 0;
@@ -273,88 +295,56 @@ export const Carousel = (props: CarouselProps) => {
273
295
  }, [
274
296
  total,
275
297
  slidePixels,
276
- children,
277
- totalProp,
278
298
  virtualOverscan,
279
- dragging,
280
- animating,
281
- extIndexRef, // 保留以表明依赖关系,虽然不会触发更新
282
- extTotal,
283
- ]);
284
-
285
- // 计算滑块包装器样式
286
- const slideWrapperStyle: ViewStyle = useMemo(() => {
287
- return direction === 'horizontal'
288
- ? { width: slidePixels, height: '100%' }
289
- : { height: slidePixels, width: '100%' };
290
- }, [direction, slidePixels]);
291
-
292
- // 计算虚拟渲染的占位空间样式
293
- const spacerStyles = useMemo(() => {
294
- if (!virtualRange) return null;
295
-
296
- const { start, end } = virtualRange;
297
- const leadingSize = start * slidePixels;
298
- const trailingSize = (extTotal - end - 1) * slidePixels;
299
-
300
- return {
301
- leading:
302
- direction === 'horizontal'
303
- ? { width: leadingSize }
304
- : { height: leadingSize },
305
- trailing:
306
- direction === 'horizontal'
307
- ? { width: trailingSize }
308
- : { height: trailingSize },
309
- leadingSize,
310
- trailingSize,
311
- };
312
- }, [virtualRange, slidePixels, extTotal, direction]);
313
-
314
- const { renderSlides } = useCarouselSlides({
315
- children,
316
- total,
317
- virtualRange,
299
+ isDragging,
300
+ isAnimating,
301
+ current,
318
302
  loopEnabled,
319
303
  clonesBefore,
320
- slideWrapperStyle,
321
- spacerStyles,
322
- slideStyles: styles,
323
- });
304
+ extTotal,
305
+ ]);
324
306
 
325
- const { renderIndicator } = useCarouselIndicator({
326
- indicator,
327
- indicatorProps,
328
- total,
329
- current,
330
- activeColor: theme.palette.brand7,
331
- inactiveColor: theme.palette.fontGray5,
332
- styles,
333
- });
307
+ const trackInnerStyle =
308
+ direction === 'horizontal'
309
+ ? styles.trackInnerHorizontal
310
+ : styles.trackInnerVertical;
334
311
 
335
- const trackInnerStyle: ViewStyle = useMemo(() => {
336
- return direction === 'horizontal'
337
- ? { flexDirection: 'row', height: '100%' }
338
- : { flexDirection: 'column', width: '100%' };
339
- }, [direction]);
312
+ // 边界情况处理:空数据
313
+ if (!data || data.length === 0) {
314
+ return null;
315
+ }
340
316
 
341
317
  return (
342
- <View style={StyleSheet.flatten([styles.container, style])}>
318
+ <View style={[styles.container, style]}>
343
319
  <View
344
320
  style={styles.track}
345
321
  onLayout={onLayoutTrack}
346
322
  {...panResponder?.panHandlers}
347
323
  >
348
324
  <Animated.View
349
- style={StyleSheet.flatten([
350
- styles.trackInner,
351
- trackInnerStyle,
352
- trackTransformStyle,
353
- ])}
325
+ style={[styles.trackInner, trackInnerStyle, trackTransformStyle]}
354
326
  >
355
- {renderSlides()}
327
+ <CarouselSlides<Data>
328
+ data={data}
329
+ renderItem={renderItem}
330
+ total={total}
331
+ virtualRange={virtualRange}
332
+ loopEnabled={loopEnabled}
333
+ clonesBefore={clonesBefore}
334
+ direction={direction}
335
+ slidePixels={slidePixels}
336
+ extTotal={extTotal}
337
+ />
356
338
  </Animated.View>
357
- {renderIndicator()}
339
+ <CarouselIndicator
340
+ indicator={indicator}
341
+ indicatorProps={indicatorProps}
342
+ total={total}
343
+ current={current}
344
+ activeColor={theme.palette.brand7}
345
+ inactiveColor={theme.palette.fontGray5}
346
+ styles={styles}
347
+ />
358
348
  </View>
359
349
  </View>
360
350
  );
@@ -1,8 +1,6 @@
1
1
  export { useCarouselAutoplay } from './use-carousel-autoplay';
2
2
  export { useCarouselIndex } from './use-carousel-index';
3
- export { useCarouselIndicator } from './use-carousel-indicator';
4
3
  export { useCarouselLifecycle } from './use-carousel-lifecycle';
5
4
  export { useCarouselPanResponder } from './use-carousel-pan-responder';
6
5
  export { useCarouselPosition } from './use-carousel-position';
7
- export { useCarouselSlides } from './use-carousel-slides';
8
6
  export { useCarouselSwipe } from './use-carousel-swipe';
@@ -2,9 +2,10 @@ import { useMemoizedFn } from 'ahooks';
2
2
  import { clamp } from 'lodash-es';
3
3
  import { useCallback, useRef, useState } from 'react';
4
4
 
5
- import { RUBBERBAND_DAMPING } from '#components/base/carousel/constants';
6
-
7
- import { getBoundaryIndexRange } from '../utils';
5
+ /**
6
+ * 橡皮筋效果阻尼系数
7
+ */
8
+ const RUBBERBAND_DAMPING = 0.35;
8
9
 
9
10
  interface UseCarouselIndexParams {
10
11
  total: number;
@@ -126,12 +127,15 @@ export const useCarouselIndex = (params: UseCarouselIndexParams) => {
126
127
  if (slidePixels <= 0) return nextPosition;
127
128
  if (total <= 0) return 0;
128
129
 
129
- const { min, max } = getBoundaryIndexRange({
130
- total,
131
- slideSize: normalizedSlideSize,
132
- trackOffset: normalizedTrackOffset,
133
- stuckAtBoundary,
134
- });
130
+ // 计算轮播边界索引范围
131
+ let min = 0;
132
+ let max = total - 1;
133
+ if (stuckAtBoundary) {
134
+ const slideRatio = normalizedSlideSize / 100;
135
+ const offsetRatio = normalizedTrackOffset / 100;
136
+ min = 0 + offsetRatio / (slideRatio || 1);
137
+ max = total - 1 - (1 - slideRatio - offsetRatio) / (slideRatio || 1);
138
+ }
135
139
  const minPos = min * slidePixels;
136
140
  const maxPos = max * slidePixels;
137
141
 
@@ -64,6 +64,7 @@ export const useCarouselLifecycle = (params: UseCarouselLifecycleParams) => {
64
64
  if (slidePixels > 0) {
65
65
  setPositionImmediate(getBoundedPosition(extIndex * slidePixels));
66
66
  }
67
+ // eslint-disable-next-line react-hooks/exhaustive-deps
67
68
  }, [
68
69
  defaultIndex,
69
70
  getExtIndexFromIndex,
@@ -72,8 +73,7 @@ export const useCarouselLifecycle = (params: UseCarouselLifecycleParams) => {
72
73
  slidePixels,
73
74
  total,
74
75
  updateCurrent,
75
- reportIndexRef,
76
- extIndexRef,
76
+ // refs 不需要在依赖数组中,它们的引用永远不变
77
77
  ]);
78
78
 
79
79
  // 响应 slidePixels 变化
@@ -81,5 +81,6 @@ export const useCarouselLifecycle = (params: UseCarouselLifecycleParams) => {
81
81
  if (slidePixels <= 0) return;
82
82
  const extIndex = extIndexRef.current;
83
83
  setPositionImmediate(getBoundedPosition(extIndex * slidePixels));
84
- }, [getBoundedPosition, setPositionImmediate, slidePixels, extIndexRef]);
84
+ // eslint-disable-next-line react-hooks/exhaustive-deps
85
+ }, [getBoundedPosition, setPositionImmediate, slidePixels]);
85
86
  };
@@ -1,12 +1,20 @@
1
1
  import { clamp } from 'lodash-es';
2
- import { type RefObject, useMemo } from 'react';
2
+ import { type RefObject, useMemo, useRef } from 'react';
3
3
  import {
4
4
  type Animated,
5
5
  PanResponder,
6
6
  type PanResponderInstance,
7
7
  } from 'react-native';
8
8
 
9
- import { GESTURE_MIN_DISTANCE, VELOCITY_PROJECTION_FACTOR } from '../constants';
9
+ /**
10
+ * 手势识别最小移动距离(像素)
11
+ */
12
+ const GESTURE_MIN_DISTANCE = 5;
13
+
14
+ /**
15
+ * 速度投影系数(用于计算惯性滑动距离)
16
+ */
17
+ const VELOCITY_PROJECTION_FACTOR = 2000;
10
18
 
11
19
  interface UseCarouselPanResponderParams {
12
20
  allowTouchMove: boolean;
@@ -76,6 +84,26 @@ export const useCarouselPanResponder = (
76
84
  swipeToExtIndex,
77
85
  } = params;
78
86
 
87
+ // 使用 ref 存储回调,减少 PanResponder 重建
88
+ const callbacksRef = useRef({
89
+ setDragging,
90
+ setAnimating,
91
+ updateCurrent,
92
+ getIndexFromExtIndex,
93
+ getBoundedPosition,
94
+ swipeToExtIndex,
95
+ });
96
+
97
+ // 更新回调 ref
98
+ callbacksRef.current = {
99
+ setDragging,
100
+ setAnimating,
101
+ updateCurrent,
102
+ getIndexFromExtIndex,
103
+ getBoundedPosition,
104
+ swipeToExtIndex,
105
+ };
106
+
79
107
  return useMemo(() => {
80
108
  if (!allowTouchMove) return null;
81
109
  if (total <= 1) return null;
@@ -92,8 +120,8 @@ export const useCarouselPanResponder = (
92
120
  return PanResponder.create({
93
121
  onMoveShouldSetPanResponder: shouldSet,
94
122
  onPanResponderGrant: () => {
95
- setDragging(true);
96
- setAnimating(false);
123
+ callbacksRef.current.setDragging(true);
124
+ callbacksRef.current.setAnimating(false);
97
125
  position.current.stopAnimation();
98
126
  startPosition = positionValueRef.current;
99
127
  },
@@ -117,7 +145,7 @@ export const useCarouselPanResponder = (
117
145
  }
118
146
  }
119
147
 
120
- const bounded = getBoundedPosition(nextPosition);
148
+ const bounded = callbacksRef.current.getBoundedPosition(nextPosition);
121
149
  position.current.setValue(bounded);
122
150
  positionValueRef.current = bounded;
123
151
 
@@ -127,10 +155,12 @@ export const useCarouselPanResponder = (
127
155
  extTotal - 1,
128
156
  );
129
157
  extIndexRef.current = nextExtIndex;
130
- updateCurrent(getIndexFromExtIndex(nextExtIndex));
158
+ callbacksRef.current.updateCurrent(
159
+ callbacksRef.current.getIndexFromExtIndex(nextExtIndex),
160
+ );
131
161
  },
132
162
  onPanResponderRelease: (_evt, gestureState) => {
133
- setDragging(false);
163
+ callbacksRef.current.setDragging(false);
134
164
  const velocity = axis === 'x' ? gestureState.vx : gestureState.vy;
135
165
  position.current.stopAnimation(value => {
136
166
  const baseIndex = value / slidePixels;
@@ -141,12 +171,12 @@ export const useCarouselPanResponder = (
141
171
  const maxAdj = minAdj + 1;
142
172
  const rounded = Math.round(projectedIndex);
143
173
  const adjacent = clamp(rounded, minAdj, maxAdj);
144
- swipeToExtIndex(adjacent);
174
+ callbacksRef.current.swipeToExtIndex(adjacent);
145
175
  });
146
176
  },
147
177
  onPanResponderTerminate: () => {
148
- setDragging(false);
149
- swipeToExtIndex(extIndexRef.current);
178
+ callbacksRef.current.setDragging(false);
179
+ callbacksRef.current.swipeToExtIndex(extIndexRef.current);
150
180
  },
151
181
  onPanResponderTerminationRequest: () => false,
152
182
  onShouldBlockNativeResponder: () => blockNativeResponder,
@@ -158,15 +188,9 @@ export const useCarouselPanResponder = (
158
188
  direction,
159
189
  extTotal,
160
190
  extIndexRef,
161
- getBoundedPosition,
162
- getIndexFromExtIndex,
163
191
  position,
164
192
  positionValueRef,
165
- setAnimating,
166
- setDragging,
167
193
  slidePixels,
168
- swipeToExtIndex,
169
194
  total,
170
- updateCurrent,
171
195
  ]);
172
196
  };
@@ -1,7 +1,10 @@
1
1
  import { useCallback, useRef } from 'react';
2
2
  import { Animated, Easing } from 'react-native';
3
3
 
4
- import { CAROUSEL_ANIMATION_DURATION } from '../constants';
4
+ /**
5
+ * 轮播图动画时长(毫秒)
6
+ */
7
+ const CAROUSEL_ANIMATION_DURATION = 300;
5
8
 
6
9
  /**
7
10
  * 轮播图位置和动画控制 Hook
@@ -1,8 +1,6 @@
1
1
  import { Carousel as _Carousel } from './carousel';
2
- import { CarouselItem } from './carousel-item';
3
2
 
4
3
  export * from './carousel';
5
- export * from './carousel-item';
6
4
  export type * from './types';
7
5
 
8
- export const Carousel = Object.assign(_Carousel, { Item: CarouselItem });
6
+ export const Carousel = _Carousel;
@@ -16,6 +16,18 @@ export const useCarouselStyles = createThemedStyles(theme => ({
16
16
  flexGrow: 0,
17
17
  flexShrink: 0,
18
18
  },
19
+ trackInnerHorizontal: {
20
+ flexDirection: 'row',
21
+ height: '100%',
22
+ flexGrow: 0,
23
+ flexShrink: 0,
24
+ },
25
+ trackInnerVertical: {
26
+ flexDirection: 'column',
27
+ width: '100%',
28
+ flexGrow: 0,
29
+ flexShrink: 0,
30
+ },
19
31
  slide: {
20
32
  flexGrow: 0,
21
33
  flexShrink: 0,
@@ -1,4 +1,4 @@
1
- import type { ReactElement, ReactNode, Ref } from 'react';
1
+ import type { ReactNode, Ref } from 'react';
2
2
  import type { StyleProp, ViewStyle } from 'react-native';
3
3
 
4
4
  /** Carousel 组件暴露的实例方法 */
@@ -12,7 +12,7 @@ export interface CarouselRef {
12
12
  }
13
13
 
14
14
  /** Carousel 组件属性 */
15
- export interface CarouselProps {
15
+ export interface CarouselProps<Data = unknown> {
16
16
  /**
17
17
  * 初始展示的索引
18
18
  * 该属性是响应式的,当值变化时轮播会跳转到对应索引
@@ -101,26 +101,21 @@ export interface CarouselProps {
101
101
 
102
102
  /**
103
103
  * 虚拟渲染时前后额外渲染的滑块个数
104
- * 仅在使用 render prop 且传入 total 时生效
105
104
  * @default 2
106
105
  */
107
106
  virtualOverscan?: number;
108
107
 
109
108
  /**
110
- * 总滑块数量
111
- * 使用 render prop children 时需要手动传入
109
+ * 数据源数组
112
110
  */
113
- total?: number;
111
+ data: Data[];
114
112
 
115
113
  /**
116
- * 滑块内容
117
- * - 直接传入 CarouselItem 列表
118
- * - 或者传入 render prop,根据索引返回节点
114
+ * 渲染每一项的函数
115
+ * @param item - 数据项
116
+ * @param index - 索引
119
117
  */
120
- children?:
121
- | ReactElement<CarouselItemProps>
122
- | ReactElement<CarouselItemProps>[]
123
- | ((index: number) => ReactElement<CarouselItemProps>);
118
+ renderItem: (item: Data, index: number) => ReactNode;
124
119
 
125
120
  /**
126
121
  * 是否阻断原生手势响应
@@ -134,11 +129,3 @@ export interface CarouselProps {
134
129
 
135
130
  ref?: Ref<CarouselRef>;
136
131
  }
137
-
138
- /** CarouselItem 组件属性 */
139
- export interface CarouselItemProps {
140
- /** 滑块内容 */
141
- children?: ReactNode;
142
- /** 滑块容器样式 */
143
- style?: StyleProp<ViewStyle>;
144
- }