@seakoi/native-ui 1.0.0 → 1.1.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 (51) hide show
  1. package/CHANGELOG.md +559 -265
  2. package/dist/commonjs/components/base/carousel/carousel-item.js +21 -0
  3. package/dist/commonjs/components/base/carousel/carousel.js +489 -0
  4. package/dist/commonjs/components/base/carousel/index.js +36 -0
  5. package/dist/commonjs/components/base/carousel/style/index.js +47 -0
  6. package/dist/commonjs/components/base/carousel/types.js +5 -0
  7. package/dist/commonjs/components/base/carousel/utils.js +27 -0
  8. package/dist/commonjs/components/base/index.js +34 -23
  9. package/dist/module/components/base/carousel/carousel-item.js +16 -0
  10. package/dist/module/components/base/carousel/carousel.js +484 -0
  11. package/dist/module/components/base/carousel/index.js +9 -0
  12. package/dist/module/components/base/carousel/style/index.js +43 -0
  13. package/dist/module/components/base/carousel/types.js +3 -0
  14. package/dist/module/components/base/carousel/utils.js +20 -0
  15. package/dist/module/components/base/index.js +1 -0
  16. package/dist/typescript/commonjs/src/components/base/carousel/carousel-item.d.ts +4 -0
  17. package/dist/typescript/commonjs/src/components/base/carousel/carousel-item.d.ts.map +1 -0
  18. package/dist/typescript/commonjs/src/components/base/carousel/carousel.d.ts +7 -0
  19. package/dist/typescript/commonjs/src/components/base/carousel/carousel.d.ts.map +1 -0
  20. package/dist/typescript/commonjs/src/components/base/carousel/index.d.ts +7 -0
  21. package/dist/typescript/commonjs/src/components/base/carousel/index.d.ts.map +1 -0
  22. package/dist/typescript/commonjs/src/components/base/carousel/style/index.d.ts +41 -0
  23. package/dist/typescript/commonjs/src/components/base/carousel/style/index.d.ts.map +1 -0
  24. package/dist/typescript/commonjs/src/components/base/carousel/types.d.ts +113 -0
  25. package/dist/typescript/commonjs/src/components/base/carousel/types.d.ts.map +1 -0
  26. package/dist/typescript/commonjs/src/components/base/carousel/utils.d.ts +6 -0
  27. package/dist/typescript/commonjs/src/components/base/carousel/utils.d.ts.map +1 -0
  28. package/dist/typescript/commonjs/src/components/base/index.d.ts +1 -0
  29. package/dist/typescript/commonjs/src/components/base/index.d.ts.map +1 -1
  30. package/dist/typescript/module/src/components/base/carousel/carousel-item.d.ts +4 -0
  31. package/dist/typescript/module/src/components/base/carousel/carousel-item.d.ts.map +1 -0
  32. package/dist/typescript/module/src/components/base/carousel/carousel.d.ts +7 -0
  33. package/dist/typescript/module/src/components/base/carousel/carousel.d.ts.map +1 -0
  34. package/dist/typescript/module/src/components/base/carousel/index.d.ts +7 -0
  35. package/dist/typescript/module/src/components/base/carousel/index.d.ts.map +1 -0
  36. package/dist/typescript/module/src/components/base/carousel/style/index.d.ts +41 -0
  37. package/dist/typescript/module/src/components/base/carousel/style/index.d.ts.map +1 -0
  38. package/dist/typescript/module/src/components/base/carousel/types.d.ts +113 -0
  39. package/dist/typescript/module/src/components/base/carousel/types.d.ts.map +1 -0
  40. package/dist/typescript/module/src/components/base/carousel/utils.d.ts +6 -0
  41. package/dist/typescript/module/src/components/base/carousel/utils.d.ts.map +1 -0
  42. package/dist/typescript/module/src/components/base/index.d.ts +1 -0
  43. package/dist/typescript/module/src/components/base/index.d.ts.map +1 -1
  44. package/package.json +3 -2
  45. package/src/components/base/carousel/carousel-item.tsx +11 -0
  46. package/src/components/base/carousel/carousel.tsx +691 -0
  47. package/src/components/base/carousel/index.ts +8 -0
  48. package/src/components/base/carousel/style/index.ts +42 -0
  49. package/src/components/base/carousel/types.ts +134 -0
  50. package/src/components/base/carousel/utils.ts +26 -0
  51. package/src/components/base/index.ts +1 -0
@@ -0,0 +1,484 @@
1
+ "use strict";
2
+
3
+ import { clamp } from 'lodash-es';
4
+ import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
5
+ import { Animated, Easing, PanResponder, StyleSheet, View } from 'react-native';
6
+ import { getDefaultTotal, isRenderPropChildren, normalizeChildren } from "./utils.js";
7
+ import { useTheme } from "../../../native-provider/index.js";
8
+ import { useCarouselStyles } from "./style/index.js";
9
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
10
+ const getBoundaryIndexRange = ({
11
+ total,
12
+ slideSize,
13
+ trackOffset,
14
+ stuckAtBoundary
15
+ }) => {
16
+ if (total <= 0) return {
17
+ min: 0,
18
+ max: 0
19
+ };
20
+ if (!stuckAtBoundary) return {
21
+ min: 0,
22
+ max: total - 1
23
+ };
24
+ const slideRatio = slideSize / 100;
25
+ const offsetRatio = trackOffset / 100;
26
+ const min = 0 + offsetRatio / (slideRatio || 1);
27
+ const max = total - 1 - (1 - slideRatio - offsetRatio) / (slideRatio || 1);
28
+ return {
29
+ min,
30
+ max
31
+ };
32
+ };
33
+ /**
34
+ * 轮播图
35
+ */
36
+ export const Carousel = /*#__PURE__*/forwardRef((props, ref) => {
37
+ const {
38
+ defaultIndex = 0,
39
+ allowTouchMove = true,
40
+ autoplay = false,
41
+ autoplayInterval = 3000,
42
+ loop = false,
43
+ direction = 'horizontal',
44
+ onIndexChange,
45
+ indicatorProps,
46
+ indicator,
47
+ slideSize = 100,
48
+ trackOffset = 0,
49
+ stuckAtBoundary = true,
50
+ rubberband = true,
51
+ virtualOverscan = 2,
52
+ total: totalProp,
53
+ children,
54
+ style
55
+ } = props;
56
+ const styles = useCarouselStyles();
57
+ const theme = useTheme();
58
+ const [layout, setLayout] = useState({
59
+ width: 0,
60
+ height: 0
61
+ });
62
+ const [dragging, setDragging] = useState(false);
63
+ const [animating, setAnimating] = useState(false);
64
+ const [current, setCurrent] = useState(0);
65
+ const position = useRef(new Animated.Value(0));
66
+ const positionValueRef = useRef(0);
67
+ const normalizedSlideSize = useMemo(() => {
68
+ if (!Number.isFinite(slideSize)) return 100;
69
+ return clamp(slideSize, 0, 100);
70
+ }, [slideSize]);
71
+ const normalizedTrackOffset = useMemo(() => {
72
+ if (stuckAtBoundary) {
73
+ return 100 - normalizedSlideSize;
74
+ }
75
+ if (!Number.isFinite(trackOffset)) return 0;
76
+ return clamp(trackOffset, 0, 100 - normalizedSlideSize);
77
+ }, [normalizedSlideSize, trackOffset, stuckAtBoundary]);
78
+ const total = useMemo(() => {
79
+ const normalizedDefault = getDefaultTotal(children);
80
+ return totalProp ?? normalizedDefault;
81
+ }, [children, totalProp]);
82
+ const loopEnabled = loop && total > 1;
83
+ // 若是非全屏的轮播项,则在循环播放开启的情况下,则会在下一轮展示第一项时看见下一项为空白(或闪烁),这需要额外渲染更多的项(与offset、slideSize相关)
84
+ const clonesBefore = loopEnabled ? Math.max(1, normalizedSlideSize > 0 ? Math.ceil(normalizedTrackOffset / normalizedSlideSize) + 1 : 1) : 0;
85
+ const clonesAfter = loopEnabled ? Math.max(1, normalizedSlideSize > 0 ? Math.ceil((100 - normalizedTrackOffset - normalizedSlideSize) / normalizedSlideSize) + 1 : 1) : 0;
86
+ const extTotal = loopEnabled ? total + clonesBefore + clonesAfter : total;
87
+ const slidePixels = useMemo(() => {
88
+ const trackSize = direction === 'horizontal' ? layout.width : layout.height;
89
+ if (trackSize <= 0) return 0;
90
+ return trackSize * (normalizedSlideSize / 100);
91
+ }, [direction, layout.height, layout.width, normalizedSlideSize]);
92
+ const trackOffsetPixels = useMemo(() => {
93
+ const trackSize = direction === 'horizontal' ? layout.width : layout.height;
94
+ if (trackSize <= 0) return 0;
95
+ return trackSize * (normalizedTrackOffset / 100);
96
+ }, [direction, layout.height, layout.width, normalizedTrackOffset]);
97
+ const extIndexRef = useRef(0);
98
+ const reportIndexRef = useRef(null);
99
+ const staticSlides = useMemo(() => {
100
+ if (isRenderPropChildren(children)) return [];
101
+ return normalizeChildren(children, total);
102
+ }, [children, total]);
103
+ const updateCurrent = useCallback(next => {
104
+ const normalized = total > 0 ? clamp(next, 0, total - 1) : 0;
105
+ setCurrent(normalized);
106
+ }, [total]);
107
+ const maybeReportIndex = useCallback(index => {
108
+ if (!onIndexChange) return;
109
+ if (reportIndexRef.current === index) return;
110
+ reportIndexRef.current = index;
111
+ onIndexChange(index);
112
+ }, [onIndexChange]);
113
+ const getExtIndexFromIndex = useCallback(index => {
114
+ if (!loopEnabled) return index;
115
+ return clamp(index, 0, total - 1) + clonesBefore;
116
+ }, [clonesBefore, loopEnabled, total]);
117
+ const getIndexFromExtIndex = useCallback(extIndex => {
118
+ if (!loopEnabled) return clamp(extIndex, 0, total - 1);
119
+ if (extIndex < clonesBefore) {
120
+ return ((extIndex - clonesBefore) % total + total) % total;
121
+ }
122
+ if (extIndex >= clonesBefore + total) {
123
+ return (extIndex - clonesBefore - total) % total;
124
+ }
125
+ return extIndex - clonesBefore;
126
+ }, [clonesBefore, loopEnabled, total]);
127
+ const getTargetExtIndexForIndex = useCallback(targetIndex => {
128
+ if (!loopEnabled) return clamp(targetIndex, 0, total - 1);
129
+ const currentExt = extIndexRef.current;
130
+ const normalizedTarget = clamp(targetIndex, 0, total - 1);
131
+ const targetExt = normalizedTarget + clonesBefore;
132
+ if (currentExt === clonesBefore && normalizedTarget === total - 1) return clonesBefore - 1;
133
+ if (currentExt === clonesBefore + total - 1 && normalizedTarget === 0) return clonesBefore + total;
134
+ return targetExt;
135
+ }, [clonesBefore, loopEnabled, total]);
136
+ const getBoundedPosition = useCallback(nextPosition => {
137
+ if (loopEnabled) return nextPosition;
138
+ if (slidePixels <= 0) return nextPosition;
139
+ if (total <= 0) return 0;
140
+ const {
141
+ min,
142
+ max
143
+ } = getBoundaryIndexRange({
144
+ total,
145
+ slideSize: normalizedSlideSize,
146
+ trackOffset: normalizedTrackOffset,
147
+ stuckAtBoundary
148
+ });
149
+ const minPos = min * slidePixels;
150
+ const maxPos = max * slidePixels;
151
+ if (nextPosition < minPos) {
152
+ if (!rubberband) return minPos;
153
+ return minPos - (minPos - nextPosition) * 0.35;
154
+ }
155
+ if (nextPosition > maxPos) {
156
+ if (!rubberband) return maxPos;
157
+ return maxPos + (nextPosition - maxPos) * 0.35;
158
+ }
159
+ return nextPosition;
160
+ }, [loopEnabled, rubberband, slidePixels, normalizedSlideSize, stuckAtBoundary, total, normalizedTrackOffset]);
161
+ const animateTo = useCallback((toValue, onFinished) => {
162
+ const listenerId = position.current.addListener(({
163
+ value
164
+ }) => {
165
+ positionValueRef.current = value;
166
+ });
167
+ Animated.timing(position.current, {
168
+ toValue,
169
+ duration: 300,
170
+ easing: Easing.out(Easing.cubic),
171
+ useNativeDriver: true
172
+ }).start(({
173
+ finished
174
+ }) => {
175
+ position.current.removeListener(listenerId);
176
+ if (finished) {
177
+ positionValueRef.current = toValue;
178
+ onFinished?.();
179
+ }
180
+ });
181
+ }, []);
182
+ const setPositionImmediate = useCallback(toValue => {
183
+ position.current.stopAnimation();
184
+ position.current.setValue(toValue);
185
+ positionValueRef.current = toValue;
186
+ }, []);
187
+ const finalizeLoopJumpIfNeeded = useCallback(() => {
188
+ if (!loopEnabled) return;
189
+ const currentExt = extIndexRef.current;
190
+ if (currentExt < clonesBefore) {
191
+ const targetExt = currentExt + total;
192
+ extIndexRef.current = targetExt;
193
+ if (slidePixels > 0) setPositionImmediate(targetExt * slidePixels);
194
+ return;
195
+ }
196
+ if (currentExt >= clonesBefore + total) {
197
+ const targetExt = currentExt - total;
198
+ extIndexRef.current = targetExt;
199
+ if (slidePixels > 0) setPositionImmediate(targetExt * slidePixels);
200
+ }
201
+ }, [clonesBefore, loopEnabled, setPositionImmediate, slidePixels, total]);
202
+ const swipeToExtIndex = useCallback(targetExtIndex => {
203
+ if (total <= 0) return;
204
+ if (slidePixels <= 0) {
205
+ extIndexRef.current = clamp(targetExtIndex, 0, extTotal - 1);
206
+ setAnimating(false);
207
+ updateCurrent(getIndexFromExtIndex(extIndexRef.current));
208
+ return;
209
+ }
210
+ const boundedExtIndex = clamp(targetExtIndex, 0, extTotal - 1);
211
+ extIndexRef.current = boundedExtIndex;
212
+ const nextIndex = getIndexFromExtIndex(boundedExtIndex);
213
+ updateCurrent(nextIndex);
214
+ const rawPosition = boundedExtIndex * slidePixels;
215
+ const toPosition = getBoundedPosition(rawPosition);
216
+ setAnimating(true);
217
+ animateTo(toPosition, () => {
218
+ finalizeLoopJumpIfNeeded();
219
+ const normalizedIndex = getIndexFromExtIndex(extIndexRef.current);
220
+ updateCurrent(normalizedIndex);
221
+ maybeReportIndex(normalizedIndex);
222
+ setAnimating(false);
223
+ });
224
+ }, [animateTo, extTotal, finalizeLoopJumpIfNeeded, getBoundedPosition, getIndexFromExtIndex, maybeReportIndex, slidePixels, total, updateCurrent]);
225
+ const swipeTo = useCallback(index => {
226
+ if (total <= 0) return;
227
+ const targetIndex = clamp(Math.round(index), 0, total - 1);
228
+ const targetExtIndex = getTargetExtIndexForIndex(targetIndex);
229
+ swipeToExtIndex(targetExtIndex);
230
+ }, [getTargetExtIndexForIndex, swipeToExtIndex, total]);
231
+ const swipeNext = useCallback(() => {
232
+ if (total <= 1) return;
233
+ let currentExt = extIndexRef.current;
234
+ // 若当前处于 after-clone 区域(快速连击导致 finalizeLoopJumpIfNeeded 尚未执行),
235
+ // 立即将位置平移回真实 item 区域(视觉内容相同,不可感知),再继续前进
236
+ if (clonesBefore > 0 && currentExt >= clonesBefore + total) {
237
+ currentExt -= total;
238
+ extIndexRef.current = currentExt;
239
+ if (slidePixels > 0) setPositionImmediate(currentExt * slidePixels);
240
+ }
241
+ swipeToExtIndex(currentExt + 1);
242
+ }, [clonesBefore, setPositionImmediate, slidePixels, swipeToExtIndex, total]);
243
+ const swipePrev = useCallback(() => {
244
+ if (total <= 1) return;
245
+ let currentExt = extIndexRef.current;
246
+ // 若当前处于 before-clone 区域(快速连击导致 finalizeLoopJumpIfNeeded 尚未执行),
247
+ // 立即将位置平移回真实 item 区域(视觉内容相同,不可感知),再继续后退
248
+ if (clonesBefore > 0 && currentExt < clonesBefore) {
249
+ currentExt += total;
250
+ extIndexRef.current = currentExt;
251
+ if (slidePixels > 0) setPositionImmediate(currentExt * slidePixels);
252
+ }
253
+ swipeToExtIndex(currentExt - 1);
254
+ }, [clonesBefore, setPositionImmediate, slidePixels, swipeToExtIndex, total]);
255
+ useImperativeHandle(ref, () => ({
256
+ swipeTo,
257
+ swipeNext,
258
+ swipePrev
259
+ }), [swipeNext, swipePrev, swipeTo]);
260
+ useEffect(() => {
261
+ if (total <= 0) {
262
+ updateCurrent(0);
263
+ reportIndexRef.current = null;
264
+ extIndexRef.current = 0;
265
+ setPositionImmediate(0);
266
+ return;
267
+ }
268
+ const normalizedDefault = clamp(Math.round(defaultIndex), 0, total - 1);
269
+ updateCurrent(normalizedDefault);
270
+ reportIndexRef.current = normalizedDefault;
271
+ const extIndex = getExtIndexFromIndex(normalizedDefault);
272
+ extIndexRef.current = extIndex;
273
+ if (slidePixels > 0) {
274
+ setPositionImmediate(getBoundedPosition(extIndex * slidePixels));
275
+ }
276
+ }, [defaultIndex, getExtIndexFromIndex, getBoundedPosition, setPositionImmediate, slidePixels, total, updateCurrent]);
277
+ useEffect(() => {
278
+ if (slidePixels <= 0) return;
279
+ const extIndex = extIndexRef.current;
280
+ setPositionImmediate(getBoundedPosition(extIndex * slidePixels));
281
+ }, [getBoundedPosition, setPositionImmediate, slidePixels]);
282
+
283
+ // 每当 current 变化时,若允许自动轮播 且 开启了自动轮播,则执行轮播
284
+ useEffect(() => {
285
+ if (!autoplay) return;
286
+ if (total <= 1) return;
287
+ if (dragging) return;
288
+ if (autoplayInterval <= 0) return;
289
+ const timer = setTimeout(() => {
290
+ if (autoplay === 'reverse') swipePrev();else swipeNext();
291
+ }, autoplayInterval);
292
+ return () => {
293
+ clearTimeout(timer);
294
+ };
295
+ }, [autoplay, autoplayInterval, dragging, swipeNext, swipePrev, total, current]);
296
+ const onLayoutTrack = useCallback(e => {
297
+ const {
298
+ width,
299
+ height
300
+ } = e.nativeEvent.layout;
301
+ setLayout(prev => {
302
+ if (prev.width === width && prev.height === height) return prev;
303
+ return {
304
+ width,
305
+ height
306
+ };
307
+ });
308
+ }, []);
309
+ const panResponder = useMemo(() => {
310
+ if (!allowTouchMove) return null;
311
+ if (total <= 1) return null;
312
+ if (slidePixels <= 0) return null;
313
+ let startPosition = 0;
314
+ const axis = direction === 'horizontal' ? 'x' : 'y';
315
+ const shouldSet = (_, gestureState) => {
316
+ const primary = axis === 'x' ? gestureState.dx : gestureState.dy;
317
+ // const secondary = axis === 'x' ? gestureState.dy : gestureState.dx;
318
+ if (Math.abs(primary) < 5) return false;
319
+ // return Math.abs(primary) >= Math.abs(secondary);
320
+ return true;
321
+ };
322
+ return PanResponder.create({
323
+ onMoveShouldSetPanResponder: shouldSet,
324
+ onPanResponderGrant: () => {
325
+ setDragging(true);
326
+ setAnimating(false);
327
+ position.current.stopAnimation();
328
+ startPosition = positionValueRef.current;
329
+ },
330
+ onPanResponderMove: (_evt, gestureState) => {
331
+ const delta = axis === 'x' ? gestureState.dx : gestureState.dy;
332
+ let nextPosition = startPosition - delta;
333
+
334
+ // loop 开启时,拖动到最外层有效克隆中心之外则循环偏移,保证视口内始终有内容
335
+ // minPos/maxPos 是 outermost before/after clone 的中心位置,由 clonesBefore 公式保证此范围内视口始终被覆盖
336
+ if (extTotal > total && slidePixels > 0) {
337
+ const totalPixels = total * slidePixels;
338
+ const minPos = (clonesBefore - 1) * slidePixels;
339
+ const maxPos = (clonesBefore + total) * slidePixels;
340
+ while (nextPosition < minPos) {
341
+ nextPosition += totalPixels;
342
+ startPosition += totalPixels;
343
+ }
344
+ while (nextPosition > maxPos) {
345
+ nextPosition -= totalPixels;
346
+ startPosition -= totalPixels;
347
+ }
348
+ }
349
+ const bounded = getBoundedPosition(nextPosition);
350
+ position.current.setValue(bounded);
351
+ positionValueRef.current = bounded;
352
+ const nextExtIndex = clamp(Math.round(bounded / slidePixels), 0, extTotal - 1);
353
+ extIndexRef.current = nextExtIndex;
354
+ updateCurrent(getIndexFromExtIndex(nextExtIndex));
355
+ },
356
+ onPanResponderRelease: (_evt, gestureState) => {
357
+ setDragging(false);
358
+ const velocity = axis === 'x' ? gestureState.vx : gestureState.vy;
359
+ position.current.stopAnimation(value => {
360
+ const baseIndex = value / slidePixels;
361
+ const projectedIndex = (value - velocity * 2000) / slidePixels;
362
+ const minAdj = Math.floor(baseIndex);
363
+ const maxAdj = minAdj + 1;
364
+ const rounded = Math.round(projectedIndex);
365
+ const adjacent = clamp(rounded, minAdj, maxAdj);
366
+ swipeToExtIndex(adjacent);
367
+ });
368
+ },
369
+ onPanResponderTerminate: () => {
370
+ setDragging(false);
371
+ swipeToExtIndex(extIndexRef.current);
372
+ },
373
+ onPanResponderTerminationRequest: () => false,
374
+ onShouldBlockNativeResponder: () => true
375
+ });
376
+ }, [allowTouchMove, clonesBefore, direction, extTotal, getBoundedPosition, getIndexFromExtIndex, slidePixels, swipeToExtIndex, total, updateCurrent]);
377
+ const trackTransformStyle = useMemo(() => {
378
+ const translate = Animated.add(Animated.multiply(position.current, -1), trackOffsetPixels);
379
+ return direction === 'horizontal' ? {
380
+ transform: [{
381
+ translateX: translate
382
+ }]
383
+ } : {
384
+ transform: [{
385
+ translateY: translate
386
+ }]
387
+ };
388
+ }, [direction, trackOffsetPixels]);
389
+ const renderSlides = useCallback(() => {
390
+ if (total <= 0) return null;
391
+ if (slidePixels <= 0) return null;
392
+ const isVirtual = isRenderPropChildren(children) && typeof totalProp === 'number';
393
+ const effectiveOverscan = isVirtual ? Math.max(virtualOverscan, dragging || animating ? 1 : 0) : virtualOverscan;
394
+ const currentExtIndex = extIndexRef.current;
395
+ const start = isVirtual ? clamp(currentExtIndex - effectiveOverscan, 0, extTotal - 1) : 0;
396
+ const end = isVirtual ? clamp(currentExtIndex + effectiveOverscan, 0, extTotal - 1) : extTotal - 1;
397
+ const leadingSize = start * slidePixels;
398
+ const trailingSize = (extTotal - end - 1) * slidePixels;
399
+ const leadSpacerStyle = direction === 'horizontal' ? {
400
+ width: leadingSize
401
+ } : {
402
+ height: leadingSize
403
+ };
404
+ const trailSpacerStyle = direction === 'horizontal' ? {
405
+ width: trailingSize
406
+ } : {
407
+ height: trailingSize
408
+ };
409
+ const slideWrapperBaseStyle = direction === 'horizontal' ? {
410
+ width: slidePixels,
411
+ height: '100%'
412
+ } : {
413
+ height: slidePixels,
414
+ width: '100%'
415
+ };
416
+ const getElementForExtIndex = extIndex => {
417
+ const logicalIndex = loopEnabled ? getIndexFromExtIndex(extIndex) : clamp(extIndex, 0, total - 1);
418
+ if (isRenderPropChildren(children)) return children(logicalIndex);
419
+ return staticSlides[logicalIndex];
420
+ };
421
+ const slideElements = [];
422
+ for (let extIndex = start; extIndex <= end; extIndex += 1) {
423
+ const element = getElementForExtIndex(extIndex);
424
+ if (!element) continue;
425
+ slideElements.push(/*#__PURE__*/_jsx(View, {
426
+ style: [styles.slide, slideWrapperBaseStyle],
427
+ children: element
428
+ }, extIndex));
429
+ }
430
+ const leadSpacer = isVirtual && leadingSize > 0 ? /*#__PURE__*/_jsx(View, {
431
+ style: leadSpacerStyle
432
+ }) : null;
433
+ const trailSpacer = isVirtual && trailingSize > 0 ? /*#__PURE__*/_jsx(View, {
434
+ style: trailSpacerStyle
435
+ }) : null;
436
+ return /*#__PURE__*/_jsxs(_Fragment, {
437
+ children: [leadSpacer, slideElements, trailSpacer]
438
+ });
439
+ }, [children, direction, extTotal, getIndexFromExtIndex, dragging, animating, loopEnabled, slidePixels, staticSlides, styles.slide, total, totalProp, virtualOverscan]);
440
+ const renderIndicator = useCallback(() => {
441
+ if (indicator === false) return null;
442
+ if (total <= 1) return null;
443
+ if (typeof indicator === 'function') {
444
+ return indicator(total, current);
445
+ }
446
+ const activeColor = indicatorProps?.color ?? theme.palette.brand7;
447
+ const inactiveColor = theme.palette.fontGray5;
448
+ return /*#__PURE__*/_jsx(View, {
449
+ style: StyleSheet.flatten([styles.indicatorContainer, indicatorProps?.style]),
450
+ children: Array.from({
451
+ length: total
452
+ }, (_, i) => {
453
+ const isActive = i === current;
454
+ return /*#__PURE__*/_jsx(View, {
455
+ style: StyleSheet.flatten([styles.indicatorDot, {
456
+ backgroundColor: isActive ? activeColor : inactiveColor
457
+ }, isActive && styles.indicatorDotActive])
458
+ }, i);
459
+ })
460
+ });
461
+ }, [current, indicator, indicatorProps?.color, indicatorProps?.style, styles.indicatorContainer, styles.indicatorDot, styles.indicatorDotActive, theme.palette.brand7, theme.palette.fontGray5, total]);
462
+ const trackInnerStyle = useMemo(() => {
463
+ return direction === 'horizontal' ? {
464
+ flexDirection: 'row',
465
+ height: '100%'
466
+ } : {
467
+ flexDirection: 'column',
468
+ width: '100%'
469
+ };
470
+ }, [direction]);
471
+ return /*#__PURE__*/_jsx(View, {
472
+ style: StyleSheet.flatten([styles.container, style]),
473
+ children: /*#__PURE__*/_jsxs(View, {
474
+ style: styles.track,
475
+ onLayout: onLayoutTrack,
476
+ ...panResponder?.panHandlers,
477
+ children: [/*#__PURE__*/_jsx(Animated.View, {
478
+ style: StyleSheet.flatten([styles.trackInner, trackInnerStyle, trackTransformStyle]),
479
+ children: renderSlides()
480
+ }), renderIndicator()]
481
+ })
482
+ });
483
+ });
484
+ Carousel.displayName = 'Carousel';
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+
3
+ import { Carousel as _Carousel } from "./carousel.js";
4
+ import { CarouselItem } from "./carousel-item.js";
5
+ export * from "./carousel.js";
6
+ export * from "./carousel-item.js";
7
+ export const Carousel = Object.assign(_Carousel, {
8
+ Item: CarouselItem
9
+ });
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+
3
+ import { createThemedStyles } from "../../../../native-provider/index.js";
4
+ export const useCarouselStyles = createThemedStyles(theme => ({
5
+ container: {
6
+ overflow: 'hidden',
7
+ height: '100%',
8
+ width: '100%'
9
+ },
10
+ track: {
11
+ flex: 1,
12
+ overflow: 'hidden'
13
+ },
14
+ trackInner: {
15
+ flexGrow: 0,
16
+ flexShrink: 0
17
+ },
18
+ slide: {
19
+ flexGrow: 0,
20
+ flexShrink: 0,
21
+ overflow: 'hidden'
22
+ },
23
+ indicatorContainer: {
24
+ position: 'absolute',
25
+ left: 0,
26
+ right: 0,
27
+ bottom: 8,
28
+ flexDirection: 'row',
29
+ justifyContent: 'center',
30
+ alignItems: 'center',
31
+ gap: 6
32
+ },
33
+ indicatorDot: {
34
+ width: 6,
35
+ height: 6,
36
+ borderRadius: theme.roundness.full,
37
+ backgroundColor: theme.palette.fontGray5,
38
+ opacity: 0.6
39
+ },
40
+ indicatorDotActive: {
41
+ opacity: 1
42
+ }
43
+ }));
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+
3
+ export {};
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+
3
+ import { Children } from 'react';
4
+ export const isRenderPropChildren = children => {
5
+ return typeof children === 'function';
6
+ };
7
+ export const normalizeChildren = (children, total) => {
8
+ if (!children) return [];
9
+ if (isRenderPropChildren(children)) {
10
+ return Array.from({
11
+ length: total
12
+ }, (_, i) => children(i));
13
+ }
14
+ return Children.toArray(children);
15
+ };
16
+ export const getDefaultTotal = children => {
17
+ if (!children) return 0;
18
+ if (isRenderPropChildren(children)) return 0;
19
+ return Array.isArray(children) ? children.length : 1;
20
+ };
@@ -8,6 +8,7 @@ export * from "./back-to-top/index.js";
8
8
  export * from "./badge/index.js";
9
9
  export * from "./button/index.js";
10
10
  export * from "./calendar/index.js";
11
+ export * from "./carousel/index.js";
11
12
  export * from "./cell/index.js";
12
13
  export * from "./checkbox/index.js";
13
14
  export * from "./collapse/index.js";
@@ -0,0 +1,4 @@
1
+ import type { PropsWithChildren } from 'react';
2
+ import type { CarouselItemProps } from './types';
3
+ export declare const CarouselItem: React.FC<PropsWithChildren<CarouselItemProps>>;
4
+ //# sourceMappingURL=carousel-item.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"carousel-item.d.ts","sourceRoot":"","sources":["../../../../../../../src/components/base/carousel/carousel-item.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAG/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAAC,iBAAiB,CAAC,CAKvE,CAAC"}
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ import type { CarouselProps, CarouselRef } from './types';
3
+ /**
4
+ * 轮播图
5
+ */
6
+ export declare const Carousel: React.ForwardRefExoticComponent<CarouselProps & React.RefAttributes<CarouselRef>>;
7
+ //# sourceMappingURL=carousel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"carousel.d.ts","sourceRoot":"","sources":["../../../../../../../src/components/base/carousel/carousel.tsx"],"names":[],"mappings":"AACA,OAAO,KASN,MAAM,OAAO,CAAC;AAqBf,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AA6B1D;;GAEG;AACH,eAAO,MAAM,QAAQ,mFAinBnB,CAAC"}
@@ -0,0 +1,7 @@
1
+ export * from './carousel';
2
+ export * from './carousel-item';
3
+ export type * from './types';
4
+ export declare const Carousel: import("react").ForwardRefExoticComponent<import("./types").CarouselProps & import("react").RefAttributes<import("./types").CarouselRef>> & {
5
+ Item: import("react").FC<import("react").PropsWithChildren<import("./types").CarouselItemProps>>;
6
+ };
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../../src/components/base/carousel/index.ts"],"names":[],"mappings":"AAGA,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,mBAAmB,SAAS,CAAC;AAE7B,eAAO,MAAM,QAAQ;;CAAmD,CAAC"}
@@ -0,0 +1,41 @@
1
+ export declare const useCarouselStyles: (options?: import("../../../../shared/utils").AnyObject | undefined) => {
2
+ container: {
3
+ overflow: "hidden";
4
+ height: "100%";
5
+ width: "100%";
6
+ };
7
+ track: {
8
+ flex: number;
9
+ overflow: "hidden";
10
+ };
11
+ trackInner: {
12
+ flexGrow: number;
13
+ flexShrink: number;
14
+ };
15
+ slide: {
16
+ flexGrow: number;
17
+ flexShrink: number;
18
+ overflow: "hidden";
19
+ };
20
+ indicatorContainer: {
21
+ position: "absolute";
22
+ left: number;
23
+ right: number;
24
+ bottom: number;
25
+ flexDirection: "row";
26
+ justifyContent: "center";
27
+ alignItems: "center";
28
+ gap: number;
29
+ };
30
+ indicatorDot: {
31
+ width: number;
32
+ height: number;
33
+ borderRadius: 999;
34
+ backgroundColor: "rgba(0, 0, 0, 0.18)";
35
+ opacity: number;
36
+ };
37
+ indicatorDotActive: {
38
+ opacity: number;
39
+ };
40
+ };
41
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../../../src/components/base/carousel/style/index.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuC3B,CAAC"}