UNPKG

@kirz/react-native-toolkit

Version:

Toolkit to speed up React Native development

354 lines 15.3 kB
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } import { atom, createStore, useAtom, Provider } from 'jotai'; import React, { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; import { Dimensions, Image, TouchableOpacity } from 'react-native'; import Animated, { useAnimatedScrollHandler, useSharedValue } from 'react-native-reanimated'; import { Text } from './Text'; import { View } from './View'; import { ControlledPromise, scaleX } from '../index'; import { AutoplayAction } from '../utils/AutoplayAction'; export const SelectedSlideIndexAtom = atom(0); function ControlButton(_ref) { let { direction, slidesCount, ...props } = _ref; const [slideIndex] = useAtom(SelectedSlideIndexAtom); const isDisabled = direction === 'left' && slideIndex <= 0 || direction === 'right' && slideIndex >= slidesCount - 1; if (isDisabled) { return null; } return /*#__PURE__*/React.createElement(TouchableOpacity, _extends({}, props, { hitSlop: scaleX(20), disabled: isDisabled })); } const SCREEN_WIDTH = Dimensions.get('screen').width; const unwrapProps = (props, item) => typeof props === 'function' ? props(item ?? {}) : props ?? {}; export function FullscreenCarousel(_ref2) { let { controlRef, slides, spacing = 0, controls, progressValue, edgeOffset = 0, autoplay, flatListProps, slideLayout, staticLayout, width: widthProp = 'screen', onSlideChanged, ...props } = _ref2; const store = useMemo(() => createStore(), []); const [width, setWidth] = useState(widthProp === 'screen' ? SCREEN_WIDTH : typeof widthProp === 'number' ? widthProp : 0); const flatListRef = useRef(null); const activeSlideIndexRef = useRef(0); const targetSlideIndexRef = useRef(activeSlideIndexRef.current); const allowLastSlideAutoplay = useRef(true); const slideProgress = useSharedValue(0); const autoplayProgress = useSharedValue(0); const autoplayActionRef = useRef(); const scrollToIndexAwaiterRef = useRef(null); const renderItem = useCallback(_ref3 => { let { item, index } = _ref3; return /*#__PURE__*/React.createElement(View, { style: { flex: 1, width, overflow: 'hidden', paddingHorizontal: edgeOffset, marginBottom: -spacing } }, slideLayout.sections.map((section, sectionIdx) => { var _section$hidden; if (section !== null && section !== void 0 && (_section$hidden = section.hidden) !== null && _section$hidden !== void 0 && _section$hidden.call(section, item, index)) { return null; } const wrapperProps = unwrapProps(section.wrapperProps, item); return /*#__PURE__*/React.createElement(View, _extends({}, wrapperProps, { key: sectionIdx, style: [{ marginBottom: spacing }, ...(section.type === 'image' ? [{ flex: 1, justifyContent: 'center', marginHorizontal: -edgeOffset }] : section.type === 'text' ? [{ alignItems: 'center' }] : []), wrapperProps.style] }), (() => { var _section$renderItem; if (section.type === 'image') { const imageContainerProps = unwrapProps(section.imageContainerProps, item); const imageProps = unwrapProps(section.imageProps, item); return /*#__PURE__*/React.createElement(View, _extends({}, imageContainerProps, { style: [{ flex: 1, justifyContent: 'center', alignItems: 'center' }, imageContainerProps === null || imageContainerProps === void 0 ? void 0 : imageContainerProps.style] }), /*#__PURE__*/React.createElement(Image, _extends({}, imageProps, { source: section.valueGetter(item, index), resizeMode: (imageProps === null || imageProps === void 0 ? void 0 : imageProps.resizeMode) ?? 'contain', style: [{ width: '100%', height: '100%' }, imageProps === null || imageProps === void 0 ? void 0 : imageProps.style] }))); } if (section.type === 'text') { const textProps = unwrapProps(section.textProps, item); return /*#__PURE__*/React.createElement(Text, _extends({}, textProps, { style: [{ textAlign: 'center' }, textProps === null || textProps === void 0 ? void 0 : textProps.style] }), section.valueGetter(item, index)); } return (_section$renderItem = section.renderItem) === null || _section$renderItem === void 0 ? void 0 : _section$renderItem.call(section, item, index); })()); })); }, [slideLayout, width]); const renderStaticLayout = position => { return staticLayout.sections.filter(section => section.position === position).map((section, sectionIdx) => { var _section$wrapperProps; return /*#__PURE__*/React.createElement(View, _extends({}, section.wrapperProps, { key: sectionIdx, style: [position !== 'slide' ? { marginBottom: spacing } : { paddingBottom: spacing }, (_section$wrapperProps = section.wrapperProps) === null || _section$wrapperProps === void 0 ? void 0 : _section$wrapperProps.style] }), (() => { const ctx = { progress: autoplay ? autoplayProgress : slideProgress, slidesCount: slides.length, getActiveSlideIndex: () => store.get(SelectedSlideIndexAtom) }; if (section.type === 'indicator') { return /*#__PURE__*/React.createElement(section.component, ctx); } return section.renderItem(ctx); })()); }); }; const scrollTo = useCallback(async function (index) { var _autoplayActionRef$cu, _flatListRef$current; let animated = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; const targetIndex = index < 0 ? 0 : index >= slides.length - 1 ? slides.length - 1 : index; if (targetIndex === targetSlideIndexRef.current) { return true; } (_autoplayActionRef$cu = autoplayActionRef.current) === null || _autoplayActionRef$cu === void 0 ? void 0 : _autoplayActionRef$cu.pause(); targetSlideIndexRef.current = targetIndex; if (scrollToIndexAwaiterRef.current) { scrollToIndexAwaiterRef.current.awaiter.resolve(false); scrollToIndexAwaiterRef.current = null; } scrollToIndexAwaiterRef.current = { awaiter: new ControlledPromise(), target: targetIndex }; flatListRef === null || flatListRef === void 0 ? void 0 : (_flatListRef$current = flatListRef.current) === null || _flatListRef$current === void 0 ? void 0 : _flatListRef$current.scrollToIndex({ index: targetIndex, animated }); startAutoplay(targetIndex); return scrollToIndexAwaiterRef.current.awaiter.wait(); }, [slides.length]); const scrollToPrev = useCallback(async animated => { return scrollTo(targetSlideIndexRef.current - 1, animated); }, []); const scrollToNext = useCallback(async animated => { return scrollTo(targetSlideIndexRef.current + 1, animated); }, []); const startAutoplay = useCallback(async function () { var _autoplayActionRef$cu2; let slideIndex = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : activeSlideIndexRef.current; const isLastSlide = slideIndex === slides.length - 1; if (isLastSlide && !allowLastSlideAutoplay.current) { return; } if (isLastSlide) { allowLastSlideAutoplay.current = false; } (_autoplayActionRef$cu2 = autoplayActionRef.current) === null || _autoplayActionRef$cu2 === void 0 ? void 0 : _autoplayActionRef$cu2.start(slideIndex); }, []); const onScroll = useAnimatedScrollHandler(event => { var _flatListProps$onScro; slideProgress.value = event.contentOffset.x / width; flatListProps === null || flatListProps === void 0 ? void 0 : (_flatListProps$onScro = flatListProps.onScroll) === null || _flatListProps$onScro === void 0 ? void 0 : _flatListProps$onScro.call(flatListProps, event); }, [width]); const onViewableItemsChanged = useCallback(_ref4 => { let { viewableItems } = _ref4; const visibleItem = viewableItems[0]; if (!visibleItem) { return; } activeSlideIndexRef.current = visibleItem.index; targetSlideIndexRef.current = activeSlideIndexRef.current; if (scrollToIndexAwaiterRef.current && scrollToIndexAwaiterRef.current.target === visibleItem.index) { scrollToIndexAwaiterRef.current.awaiter.resolve(true); scrollToIndexAwaiterRef.current = null; } if (visibleItem.index < slides.length - 1) { allowLastSlideAutoplay.current = true; } store.set(SelectedSlideIndexAtom, visibleItem.index); onSlideChanged === null || onSlideChanged === void 0 ? void 0 : onSlideChanged(visibleItem.index); }, []); useEffect(() => { store.set(SelectedSlideIndexAtom, (flatListProps === null || flatListProps === void 0 ? void 0 : flatListProps.initialScrollIndex) ?? 0); }, [store]); useEffect(() => { if (!autoplay) { return; } autoplayActionRef.current = new AutoplayAction(autoplayProgress, autoplay.interval, { delay: autoplay.delay ?? 1000, resetDuration: autoplay.resetDuration ?? 300, async onFinish() { scrollToNext(); } }); startAutoplay(); return () => { var _autoplayActionRef$cu3; (_autoplayActionRef$cu3 = autoplayActionRef.current) === null || _autoplayActionRef$cu3 === void 0 ? void 0 : _autoplayActionRef$cu3.reset(); }; }, []); useImperativeHandle(controlRef, () => ({ scrollTo, scrollToPrev, scrollToNext }), [scrollTo, scrollToPrev, scrollToNext]); return /*#__PURE__*/React.createElement(Provider, { store: store }, /*#__PURE__*/React.createElement(View, _extends({}, props, { style: [{ flex: 1, marginBottom: -(spacing ?? 0) }, props.style] }, widthProp === 'auto' ? { onLayout: e => { setWidth(e.nativeEvent.layout.width); } } : {}), renderStaticLayout('top'), /*#__PURE__*/React.createElement(View, { style: { flex: 1, marginHorizontal: -edgeOffset } }, !!width && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Animated.FlatList, _extends({}, flatListProps, { ref: flatListRef, data: slides, renderItem: renderItem, horizontal: true, pagingEnabled: true, showsHorizontalScrollIndicator: (flatListProps === null || flatListProps === void 0 ? void 0 : flatListProps.showsHorizontalScrollIndicator) ?? false, onScroll: onScroll, scrollEventThrottle: (flatListProps === null || flatListProps === void 0 ? void 0 : flatListProps.scrollEventThrottle) ?? 16, onViewableItemsChanged: onViewableItemsChanged, viewabilityConfig: (flatListProps === null || flatListProps === void 0 ? void 0 : flatListProps.viewabilityConfig) ?? { viewAreaCoveragePercentThreshold: 60 }, onScrollBeginDrag: e => { var _autoplayActionRef$cu4, _flatListProps$onScro2; (_autoplayActionRef$cu4 = autoplayActionRef.current) === null || _autoplayActionRef$cu4 === void 0 ? void 0 : _autoplayActionRef$cu4.pause(); flatListProps === null || flatListProps === void 0 ? void 0 : (_flatListProps$onScro2 = flatListProps.onScrollBeginDrag) === null || _flatListProps$onScro2 === void 0 ? void 0 : _flatListProps$onScro2.call(flatListProps, e); }, onScrollEndDrag: e => { var _e$nativeEvent$target, _flatListProps$onScro3; const slideWidth = e.nativeEvent.layoutMeasurement.width; const targetSlide = Math.round(((_e$nativeEvent$target = e.nativeEvent.targetContentOffset) === null || _e$nativeEvent$target === void 0 ? void 0 : _e$nativeEvent$target.x) / slideWidth); startAutoplay(targetSlide); flatListProps === null || flatListProps === void 0 ? void 0 : (_flatListProps$onScro3 = flatListProps.onScrollEndDrag) === null || _flatListProps$onScro3 === void 0 ? void 0 : _flatListProps$onScro3.call(flatListProps, e); allowLastSlideAutoplay.current = true; }, style: [{ marginBottom: spacing ?? 0 }, flatListProps === null || flatListProps === void 0 ? void 0 : flatListProps.style], contentContainerStyle: flatListProps === null || flatListProps === void 0 ? void 0 : flatListProps.contentContainerStyle })), renderStaticLayout('slide')), ((controls === null || controls === void 0 ? void 0 : controls.type) === 'buttons' || (controls === null || controls === void 0 ? void 0 : controls.type) === 'fullscreen') && /*#__PURE__*/React.createElement(View, { style: { position: 'absolute', top: 0, left: 0, right: 0, bottom: spacing ?? 0 }, pointerEvents: "box-none" }, controls.type === 'fullscreen' && /*#__PURE__*/React.createElement(View, { style: { position: 'absolute', width: '100%', height: '100%' }, pointerEvents: "box-none" }, /*#__PURE__*/React.createElement(View, { style: { position: 'relative', flex: 1 } }, /*#__PURE__*/React.createElement(ControlButton, { onPress: () => { scrollToPrev(); }, direction: "left", style: { position: 'absolute', width: '50%', height: '100%', justifyContent: 'center', alignItems: 'center' }, slidesCount: slides.length }, controls === null || controls === void 0 ? void 0 : controls.leftIcon), /*#__PURE__*/React.createElement(ControlButton, { onPress: () => { scrollToNext(); }, direction: "right", style: { position: 'absolute', width: '50%', height: '100%', right: 0, justifyContent: 'center', alignItems: 'center' }, slidesCount: slides.length }, controls === null || controls === void 0 ? void 0 : controls.rightIcon))), controls.type === 'buttons' && /*#__PURE__*/React.createElement(View, { style: { position: 'absolute', width: '100%', height: '100%', paddingHorizontal: (controls === null || controls === void 0 ? void 0 : controls.buttonsOffset) ?? scaleX(10), flexDirection: 'row', alignItems: 'center' }, pointerEvents: "box-none" }, /*#__PURE__*/React.createElement(ControlButton, { onPress: () => { scrollToPrev(); }, direction: "left", slidesCount: slides.length }, controls === null || controls === void 0 ? void 0 : controls.leftIcon), /*#__PURE__*/React.createElement(View, { style: { flex: 1 } }), /*#__PURE__*/React.createElement(ControlButton, { onPress: () => { scrollToNext(); }, direction: "right", slidesCount: slides.length }, controls === null || controls === void 0 ? void 0 : controls.rightIcon)))), !!width && renderStaticLayout('bottom'))); } //# sourceMappingURL=FullscreenCarousel.js.map