@kirz/react-native-toolkit
Version:
Toolkit to speed up React Native development
354 lines • 15.3 kB
JavaScript
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