From 7b750d46ac12ceb2e59018c7b45932af437f9620 Mon Sep 17 00:00:00 2001 From: bd-arc Date: Sun, 5 Apr 2020 20:29:19 +0200 Subject: [PATCH 01/34] :construction: wip(Carousel): huge revamp of carousel's logic, props and behavior --- src/carousel/Carousel.js | 804 +++++++++++++-------------------------- 1 file changed, 263 insertions(+), 541 deletions(-) diff --git a/src/carousel/Carousel.js b/src/carousel/Carousel.js index dae71a3da..bbb0a792e 100644 --- a/src/carousel/Carousel.js +++ b/src/carousel/Carousel.js @@ -1,18 +1,13 @@ import React, { Component } from 'react'; -import { Animated, Easing, FlatList, I18nManager, Platform, ScrollView, View, ViewPropTypes } from 'react-native'; +import { Animated, FlatList, I18nManager, Platform, ScrollView, View, ViewPropTypes } from 'react-native'; import PropTypes from 'prop-types'; import shallowCompare from 'react-addons-shallow-compare'; import { - defaultScrollInterpolator, - stackScrollInterpolator, - tinderScrollInterpolator, - defaultAnimatedStyles, - shiftAnimatedStyles, - stackAnimatedStyles, - tinderAnimatedStyles + defaultScrollInterpolator, stackScrollInterpolator, tinderScrollInterpolator, defaultAnimatedStyles, + shiftAnimatedStyles, stackAnimatedStyles, tinderAnimatedStyles } from '../utils/animations'; -const IS_IOS = Platform.OS === 'ios'; +const IS_ANDROID = Platform.OS === 'android'; // Native driver for scroll events // See: https://facebook.github.io/react-native/blog/2017/02/14/using-native-driver-for-animated.html @@ -34,8 +29,6 @@ export default class Carousel extends Component { itemHeight: PropTypes.number, // required for vertical carousel sliderWidth: PropTypes.number, // required for horizontal carousel sliderHeight: PropTypes.number, // required for vertical carousel - activeAnimationType: PropTypes.string, - activeAnimationOptions: PropTypes.object, activeSlideAlignment: PropTypes.oneOf(['center', 'end', 'start']), activeSlideOffset: PropTypes.number, apparitionDelay: PropTypes.number, @@ -45,7 +38,6 @@ export default class Carousel extends Component { callbackOffsetMargin: PropTypes.number, containerCustomStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, contentContainerCustomStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, - enableMomentum: PropTypes.bool, enableSnap: PropTypes.bool, firstItem: PropTypes.number, hasParallaxImages: PropTypes.bool, @@ -54,25 +46,22 @@ export default class Carousel extends Component { inactiveSlideShift: PropTypes.number, layout: PropTypes.oneOf(['default', 'stack', 'tinder']), layoutCardOffset: PropTypes.number, - lockScrollTimeoutDuration: PropTypes.number, - lockScrollWhileSnapping: PropTypes.bool, loop: PropTypes.bool, loopClonesPerSide: PropTypes.number, + preserveActiveSlideAlignment: PropTypes.bool, scrollEnabled: PropTypes.bool, scrollInterpolator: PropTypes.func, slideInterpolatedStyle: PropTypes.func, slideStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, shouldOptimizeUpdates: PropTypes.bool, - swipeThreshold: PropTypes.number, + useExperimentalSnap: PropTypes.bool, useScrollView: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]), vertical: PropTypes.bool, - onBeforeSnapToItem: PropTypes.func, + onScrollIndexChanged: PropTypes.func, onSnapToItem: PropTypes.func }; static defaultProps = { - activeAnimationType: 'timing', - activeAnimationOptions: null, activeSlideAlignment: 'center', activeSlideOffset: 20, apparitionDelay: 0, @@ -82,7 +71,6 @@ export default class Carousel extends Component { callbackOffsetMargin: 5, containerCustomStyle: {}, contentContainerCustomStyle: {}, - enableMomentum: false, enableSnap: true, firstItem: 0, hasParallaxImages: false, @@ -90,14 +78,13 @@ export default class Carousel extends Component { inactiveSlideScale: 0.9, inactiveSlideShift: 0, layout: 'default', - lockScrollTimeoutDuration: 1000, - lockScrollWhileSnapping: false, loop: false, loopClonesPerSide: 3, + preserveActiveSlideAlignment: true, scrollEnabled: true, slideStyle: {}, shouldOptimizeUpdates: true, - swipeThreshold: 20, + useExperimentalSnap: false, useScrollView: !AnimatedFlatList, vertical: false } @@ -106,7 +93,7 @@ export default class Carousel extends Component { super(props); this.state = { - hideCarousel: true, + hideCarousel: !!props.apparitionDelay, interpolators: [] }; @@ -114,42 +101,28 @@ export default class Carousel extends Component { // and this results in an absolutely crappy behavior on Android while swiping (see #156) const initialActiveItem = this._getFirstItem(props.firstItem); this._activeItem = initialActiveItem; - this._previousActiveItem = initialActiveItem; + this._onScrollActiveItem = initialActiveItem; this._previousFirstItem = initialActiveItem; this._previousItemsLength = initialActiveItem; this._mounted = false; this._positions = []; - this._currentContentOffset = 0; // store ScrollView's scroll position - this._canFireBeforeCallback = false; - this._canFireCallback = false; - this._scrollOffsetRef = null; - this._onScrollTriggered = true; // used when momentum is enabled to prevent an issue with edges items - this._lastScrollDate = 0; // used to work around a FlatList bug + this._currentScrollOffset = 0; // Store ScrollView's scroll position this._scrollEnabled = props.scrollEnabled !== false; - this._initPositionsAndInterpolators = this._initPositionsAndInterpolators.bind(this); - this._renderItem = this._renderItem.bind(this); - this._onSnap = this._onSnap.bind(this); - + this._getCellRendererComponent = this._getCellRendererComponent.bind(this); + this._getItemLayout = this._getItemLayout.bind(this); + this._getKeyExtractor = this._getKeyExtractor.bind(this); this._onLayout = this._onLayout.bind(this); this._onScroll = this._onScroll.bind(this); - this._onScrollBeginDrag = props.enableSnap ? this._onScrollBeginDrag.bind(this) : undefined; - this._onScrollEnd = props.enableSnap || props.autoplay ? this._onScrollEnd.bind(this) : undefined; - this._onScrollEndDrag = !props.enableMomentum ? this._onScrollEndDrag.bind(this) : undefined; - this._onMomentumScrollEnd = props.enableMomentum ? this._onMomentumScrollEnd.bind(this) : undefined; + this._onMomentumScrollEnd = this._onMomentumScrollEnd.bind(this); this._onTouchStart = this._onTouchStart.bind(this); this._onTouchEnd = this._onTouchEnd.bind(this); - this._onTouchRelease = this._onTouchRelease.bind(this); - - this._getKeyExtractor = this._getKeyExtractor.bind(this); + this._renderItem = this._renderItem.bind(this); + // WARNING: call this AFTER binding _onScroll this._setScrollHandler(props); - // This bool aims at fixing an iOS bug due to scrollTo that triggers onMomentumScrollEnd. - // onMomentumScrollEnd fires this._snapScroll, thus creating an infinite loop. - this._ignoreNextMomentum = false; - // Warnings if (!ViewPropTypes) { console.warn('react-native-snap-carousel: It is recommended to use at least version 0.44 of React Native with the plugin'); @@ -160,12 +133,9 @@ export default class Carousel extends Component { if (props.vertical && (!props.sliderHeight || !props.itemHeight)) { console.error('react-native-snap-carousel: You need to specify both `sliderHeight` and `itemHeight` for vertical carousels'); } - if (props.apparitionDelay && !IS_IOS && !props.useScrollView) { + if (props.apparitionDelay && IS_ANDROID && !props.useScrollView) { console.warn('react-native-snap-carousel: Using `apparitionDelay` on Android is not recommended since it can lead to rendering issues'); } - if (props.customAnimationType || props.customAnimationOptions) { - console.warn('react-native-snap-carousel: Props `customAnimationType` and `customAnimationOptions` have been renamed to `activeAnimationType` and `activeAnimationOptions`'); - } if (props.onScrollViewScroll) { console.error('react-native-snap-carousel: Prop `onScrollViewScroll` has been removed. Use `onScroll` instead'); } @@ -173,25 +143,31 @@ export default class Carousel extends Component { componentDidMount () { const { apparitionDelay, autoplay, firstItem } = this.props; - const _firstItem = this._getFirstItem(firstItem); - const apparitionCallback = () => { - this.setState({ hideCarousel: false }); - if (autoplay) { - this.startAutoplay(); - } - }; this._mounted = true; this._initPositionsAndInterpolators(); // Without 'requestAnimationFrame' or a `0` timeout, images will randomly not be rendered on Android... - requestAnimationFrame(() => { + this._initTimeout = setTimeout(() => { if (!this._mounted) { return; } - this._snapToItem(_firstItem, false, false, true, false); - this._hackActiveSlideAnimation(_firstItem, 'start', true); + const apparitionCallback = () => { + if (apparitionDelay) { + this.setState({ hideCarousel: false }); + } + if (autoplay) { + this.startAutoplay(); + } + }; + + // FlatList will use its own built-in prop `initialScrollIndex` + if (this._needsScrollView()) { + const _firstItem = this._getFirstItem(firstItem); + this._snapToItem(_firstItem, false, false); + this._hackActiveSlideAnimation(_firstItem, 'start', true); + } if (apparitionDelay) { this._apparitionTimeout = setTimeout(() => { @@ -200,7 +176,7 @@ export default class Carousel extends Component { } else { apparitionCallback(); } - }); + }, 1); } shouldComponentUpdate (nextProps, nextState) { @@ -254,29 +230,28 @@ export default class Carousel extends Component { } if (hasNewSliderWidth || hasNewSliderHeight || hasNewItemWidth || hasNewItemHeight) { - this._snapToItem(nextActiveItem, false, false, false, false); + this._snapToItem(nextActiveItem, false, false); } } else if (nextFirstItem !== this._previousFirstItem && nextFirstItem !== this._activeItem) { this._activeItem = nextFirstItem; this._previousFirstItem = nextFirstItem; - this._snapToItem(nextFirstItem, false, true, false, false); + this._snapToItem(nextFirstItem, false, true); } if (this.props.onScroll !== prevProps.onScroll) { - this._setScrollHandler(this.props); + this._setScrollHandler(this.props); } } componentWillUnmount () { this._mounted = false; this.stopAutoplay(); + clearTimeout(this._initTimeout); clearTimeout(this._apparitionTimeout); clearTimeout(this._hackSlideAnimationTimeout); clearTimeout(this._enableAutoplayTimeout); clearTimeout(this._autoplayTimeout); clearTimeout(this._snapNoMomentumTimeout); - clearTimeout(this._edgeItemTimeout); - clearTimeout(this._lockScrollTimeout); } get realIndex () { @@ -288,52 +263,51 @@ export default class Carousel extends Component { } get currentScrollPosition () { - return this._currentContentOffset; + return this._currentScrollOffset; } - _setScrollHandler(props) { - // Native driver for scroll events - const scrollEventConfig = { - listener: this._onScroll, - useNativeDriver: true, - }; - this._scrollPos = new Animated.Value(0); - const argMapping = props.vertical - ? [{ nativeEvent: { contentOffset: { y: this._scrollPos } } }] - : [{ nativeEvent: { contentOffset: { x: this._scrollPos } } }]; - - if (props.onScroll && Array.isArray(props.onScroll._argMapping)) { - // Because of a react-native issue https://github.com/facebook/react-native/issues/13294 - argMapping.pop(); - const [ argMap ] = props.onScroll._argMapping; - if (argMap && argMap.nativeEvent && argMap.nativeEvent.contentOffset) { - // Shares the same animated value passed in props - this._scrollPos = - argMap.nativeEvent.contentOffset.x || - argMap.nativeEvent.contentOffset.y || - this._scrollPos; + _setScrollHandler (props) { + // Native driver for scroll events + const scrollEventConfig = { + listener: this._onScroll, + useNativeDriver: true + }; + this._scrollPos = new Animated.Value(0); + const argMapping = props.vertical ? + [{ nativeEvent: { contentOffset: { y: this._scrollPos } } }] : + [{ nativeEvent: { contentOffset: { x: this._scrollPos } } }]; + + if (props.onScroll && Array.isArray(props.onScroll._argMapping)) { + // Because of a react-native issue https://github.com/facebook/react-native/issues/13294 + argMapping.pop(); + const [ argMap ] = props.onScroll._argMapping; + if (argMap && argMap.nativeEvent && argMap.nativeEvent.contentOffset) { + // Shares the same animated value passed in props + this._scrollPos = + argMap.nativeEvent.contentOffset.x || + argMap.nativeEvent.contentOffset.y || + this._scrollPos; + } + argMapping.push(...props.onScroll._argMapping); } - argMapping.push(...props.onScroll._argMapping); - } - this._onScrollHandler = Animated.event( - argMapping, - scrollEventConfig - ); + this._onScrollHandler = Animated.event( + argMapping, + scrollEventConfig + ); } _needsScrollView () { const { useScrollView } = this.props; - return useScrollView || !AnimatedFlatList || this._shouldUseStackLayout() || this._shouldUseTinderLayout(); + // Android's cell renderer is buggy and has a stange overflow + // TODO: a workaround might be to pass the custom animated styles directly to it + return IS_ANDROID ? + useScrollView || !AnimatedFlatList || this._shouldUseStackLayout() || this._shouldUseTinderLayout() : + useScrollView || !AnimatedFlatList; } _needsRTLAdaptations () { const { vertical } = this.props; - return IS_RTL && !IS_IOS && !vertical; - } - - _canLockScroll () { - const { scrollEnabled, enableMomentum, lockScrollWhileSnapping } = this.props; - return scrollEnabled && !enableMomentum && lockScrollWhileSnapping; + return IS_RTL && IS_ANDROID && !vertical; } _enableLoop () { @@ -352,11 +326,6 @@ export default class Carousel extends Component { this._shouldUseTinderLayout(); } - _shouldUseCustomAnimation () { - const { activeAnimationOptions } = this.props; - return !!activeAnimationOptions && !this._shouldUseStackLayout() && !this._shouldUseTinderLayout(); - } - _shouldUseShiftLayout () { const { inactiveSlideShift, layout } = this.props; return layout === 'default' && inactiveSlideShift !== 0; @@ -370,6 +339,18 @@ export default class Carousel extends Component { return this.props.layout === 'tinder'; } + _roundNumber (num, decimals = 1) { + // https://stackoverflow.com/a/41716722/ + const rounder = Math.pow(10, decimals); + return Math.round((num + Number.EPSILON) * rounder) / rounder; + } + + _isMultiple (x, y) { + // This prevents Javascript precision issues: https://stackoverflow.com/a/58440614/ + // Required because Android viewport size can return pretty complicated decimals numbers + return Math.round(Math.round(x / y) / (1 / y)) === Math.round(x); + } + _getCustomData (props = this.props) { const { data, loopClonesPerSide } = props; const dataLength = data && data.length; @@ -469,6 +450,13 @@ export default class Carousel extends Component { return loop ? index + loopClonesPerSide : index; } + _getSnapOffsets (props = this.props) { + const offset = this._getItemMainDimension(); + return [...Array(this._getCustomDataLength(props))].map((_, i) => { + return i * offset; + }); + } + _getFirstItem (index, props = this.props) { const { loopClonesPerSide } = props; const itemsLength = this._getCustomDataLength(props); @@ -496,6 +484,11 @@ export default class Carousel extends Component { return this._scrollEnabled; } + _getItemMainDimension () { + const { itemWidth, itemHeight, vertical } = this.props; + return vertical ? itemHeight : itemWidth; + } + _setScrollEnabled (scrollEnabled = true) { const wrappedRef = this._getWrappedRef(); @@ -509,7 +502,31 @@ export default class Carousel extends Component { this._scrollEnabled = scrollEnabled; } - _getKeyExtractor (item, index) { + // This will allow us to have a proper zIndex even with a FlatList + // https://github.com/facebook/react-native/issues/18616#issuecomment-389444165 + _getCellRendererComponent ({ children, index, style, ...props }) { + const cellStyle = [ + style, + { zIndex: this._getCustomDataLength() - index } + ]; + + return ( + + {children} + + ); + } + + _getItemLayout (_, index) { + const itemMainDimension = this._getItemMainDimension(); + return { + index, + length: itemMainDimension, + offset: itemMainDimension * index // + this._getContainerInnerMargin() + }; + } + + _getKeyExtractor (_, index) { return this._needsScrollView() ? `scrollview-item-${index}` : `flatlist-item-${index}`; } @@ -533,47 +550,43 @@ export default class Carousel extends Component { } } - _getViewportOffset () { - const { sliderWidth, sliderHeight, itemWidth, itemHeight, vertical, activeSlideAlignment } = this.props; - - if (activeSlideAlignment === 'start') { - return vertical ? itemHeight / 2 : itemWidth / 2; - } else if (activeSlideAlignment === 'end') { - return vertical ? - sliderHeight - (itemHeight / 2) : - sliderWidth - (itemWidth / 2); - } else { - return vertical ? sliderHeight / 2 : sliderWidth / 2; - } - } - - _getCenter (offset) { - return offset + this._getViewportOffset() - this._getContainerInnerMargin(); + _getActiveSlideOffset () { + const { activeSlideOffset } = this.props; + const itemMainDimension = this._getItemMainDimension(); + const minOffset = 10; + // Make sure activeSlideOffset never prevents the active area from being at least 10 px wide + return itemMainDimension / 2 - activeSlideOffset >= minOffset ? activeSlideOffset : minOffset; } _getActiveItem (offset) { - const { activeSlideOffset, swipeThreshold } = this.props; - const center = this._getCenter(offset); - const centerOffset = activeSlideOffset || swipeThreshold; + const itemMainDimension = this._getItemMainDimension(); + const center = offset + itemMainDimension / 2; + const activeSlideOffset = this._getActiveSlideOffset(); + const lastIndex = this._positions.length - 1; + let itemIndex; - for (let i = 0; i < this._positions.length; i++) { - const { start, end } = this._positions[i]; - if (center + centerOffset >= start && center - centerOffset <= end) { - return i; - } + if (offset <= 0) { + return 0; } - const lastIndex = this._positions.length - 1; - if (this._positions[lastIndex] && center - centerOffset > this._positions[lastIndex].end) { + if (this._positions[lastIndex] && offset >= this._positions[lastIndex].start) { return lastIndex; } - return 0; + for (let i = 0; i < this._positions.length; i++) { + const { start, end } = this._positions[i]; + if (center + activeSlideOffset >= start && center - activeSlideOffset <= end) { + itemIndex = i; + break; + } + } + + return itemIndex || 0; } _initPositionsAndInterpolators (props = this.props) { - const { data, itemWidth, itemHeight, scrollInterpolator, vertical } = props; - const sizeRef = vertical ? itemHeight : itemWidth; + const { data, scrollInterpolator } = props; + const itemMainDimension = this._getItemMainDimension(); if (!data || !data.length) { return; @@ -587,14 +600,12 @@ export default class Carousel extends Component { let animatedValue; this._positions[index] = { - start: index * sizeRef, - end: index * sizeRef + sizeRef + start: index * itemMainDimension, + end: index * itemMainDimension + itemMainDimension }; if (!this._shouldAnimateSlides(props)) { animatedValue = new Animated.Value(1); - } else if (this._shouldUseCustomAnimation()) { - animatedValue = new Animated.Value(_index === this._activeItem ? 1 : 0); } else { let interpolator; @@ -622,65 +633,6 @@ export default class Carousel extends Component { this.setState({ interpolators }); } - _getSlideAnimation (index, toValue) { - const { interpolators } = this.state; - const { activeAnimationType, activeAnimationOptions } = this.props; - - const animatedValue = interpolators && interpolators[index]; - - if (!animatedValue && animatedValue !== 0) { - return null; - } - - const animationCommonOptions = { - isInteraction: false, - useNativeDriver: true, - ...activeAnimationOptions, - toValue: toValue - }; - - return Animated.parallel([ - Animated['timing']( - animatedValue, - { ...animationCommonOptions, easing: Easing.linear } - ), - Animated[activeAnimationType]( - animatedValue, - { ...animationCommonOptions } - ) - ]); - } - - _playCustomSlideAnimation (current, next) { - const { interpolators } = this.state; - const itemsLength = this._getCustomDataLength(); - const _currentIndex = this._getCustomIndex(current); - const _currentDataIndex = this._getDataIndex(_currentIndex); - const _nextIndex = this._getCustomIndex(next); - const _nextDataIndex = this._getDataIndex(_nextIndex); - let animations = []; - - // Keep animations in sync when looping - if (this._enableLoop()) { - for (let i = 0; i < itemsLength; i++) { - if (this._getDataIndex(i) === _currentDataIndex && interpolators[i]) { - animations.push(this._getSlideAnimation(i, 0)); - } else if (this._getDataIndex(i) === _nextDataIndex && interpolators[i]) { - animations.push(this._getSlideAnimation(i, 1)); - } - } - } else { - if (interpolators[current]) { - animations.push(this._getSlideAnimation(current, 0)); - } - if (interpolators[next]) { - animations.push(this._getSlideAnimation(next, 1)); - } - } - - Animated.parallel(animations, { stopTogether: false }).start(); - } - _hackActiveSlideAnimation (index, goTo, force = false) { const { data } = this.props; @@ -705,25 +657,11 @@ export default class Carousel extends Component { }, 50); // works randomly when set to '0' } - _lockScroll () { - const { lockScrollTimeoutDuration } = this.props; - clearTimeout(this._lockScrollTimeout); - this._lockScrollTimeout = setTimeout(() => { - this._releaseScroll(); - }, lockScrollTimeoutDuration); - this._setScrollEnabled(false); - } - - _releaseScroll () { - clearTimeout(this._lockScrollTimeout); - this._setScrollEnabled(true); - } - _repositionScroll (index) { - const { data, loopClonesPerSide } = this.props; + const { data, enableSnap, loopClonesPerSide } = this.props; const dataLength = data && data.length; - if (!this._enableLoop() || !dataLength || + if (!enableSnap || !dataLength || !this._enableLoop() || (index >= loopClonesPerSide && index < dataLength + loopClonesPerSide)) { return; } @@ -736,7 +674,7 @@ export default class Carousel extends Component { repositionTo = index + dataLength; } - this._snapToItem(repositionTo, false, false, false, false); + this._snapToItem(repositionTo, false, false); } _scrollTo (offset, animated = true) { @@ -765,190 +703,72 @@ export default class Carousel extends Component { } } - _onScroll (event) { - const { callbackOffsetMargin, enableMomentum, onScroll } = this.props; - - const scrollOffset = event ? this._getScrollOffset(event) : this._currentContentOffset; - const nextActiveItem = this._getActiveItem(scrollOffset); - const itemReached = nextActiveItem === this._itemToSnapTo; - const scrollConditions = - scrollOffset >= this._scrollOffsetRef - callbackOffsetMargin && - scrollOffset <= this._scrollOffsetRef + callbackOffsetMargin; - - this._currentContentOffset = scrollOffset; - this._onScrollTriggered = true; - this._lastScrollDate = Date.now(); - - if (this._activeItem !== nextActiveItem && this._shouldUseCustomAnimation()) { - this._playCustomSlideAnimation(this._activeItem, nextActiveItem); - } - - if (enableMomentum) { - clearTimeout(this._snapNoMomentumTimeout); - - if (this._activeItem !== nextActiveItem) { - this._activeItem = nextActiveItem; - } - - if (itemReached) { - if (this._canFireBeforeCallback) { - this._onBeforeSnap(this._getDataIndex(nextActiveItem)); - } - - if (scrollConditions && this._canFireCallback) { - this._onSnap(this._getDataIndex(nextActiveItem)); - } - } - } else if (this._activeItem !== nextActiveItem && itemReached) { - if (this._canFireBeforeCallback) { - this._onBeforeSnap(this._getDataIndex(nextActiveItem)); - } - - if (scrollConditions) { - this._activeItem = nextActiveItem; - - if (this._canLockScroll()) { - this._releaseScroll(); - } - - if (this._canFireCallback) { - this._onSnap(this._getDataIndex(nextActiveItem)); - } - } - } - - if (nextActiveItem === this._itemToSnapTo && - scrollOffset === this._scrollOffsetRef) { - this._repositionScroll(nextActiveItem); - } - - if (typeof onScroll === "function" && event) { - onScroll(event); - } - } - - _onStartShouldSetResponderCapture (event) { - const { onStartShouldSetResponderCapture } = this.props; - - if (onStartShouldSetResponderCapture) { - onStartShouldSetResponderCapture(event); - } - - return this._getScrollEnabled(); - } - _onTouchStart () { - const { onTouchStart } = this.props + const { onTouchStart } = this.props; // `onTouchStart` is fired even when `scrollEnabled` is set to `false` if (this._getScrollEnabled() !== false && this._autoplaying) { this.pauseAutoPlay(); } - if (onTouchStart) { - onTouchStart() - } + onTouchStart && onTouchStart(); } _onTouchEnd () { - const { onTouchEnd } = this.props + const { onTouchEnd } = this.props; if (this._getScrollEnabled() !== false && this._autoplay && !this._autoplaying) { - // This event is buggy on Android, so a fallback is provided in _onScrollEnd() + // This event is buggy on Android, so a fallback is provided in _onMomentumScrollEnd() this.startAutoplay(); } - if (onTouchEnd) { - onTouchEnd() - } + onTouchEnd && onTouchEnd(); } - // Used when `enableSnap` is ENABLED - _onScrollBeginDrag (event) { - const { onScrollBeginDrag } = this.props; - - if (!this._getScrollEnabled()) { - return; - } - - this._scrollStartOffset = this._getScrollOffset(event); - this._scrollStartActive = this._getActiveItem(this._scrollStartOffset); - this._ignoreNextMomentum = false; - // this._canFireCallback = false; - - if (onScrollBeginDrag) { - onScrollBeginDrag(event); - } - } + _onScroll (event) { + const { onScroll, onScrollIndexChanged } = this.props; + const scrollOffset = event ? this._getScrollOffset(event) : this._currentScrollOffset; + const nextActiveItem = this._getActiveItem(scrollOffset); - // Used when `enableMomentum` is DISABLED - _onScrollEndDrag (event) { - const { onScrollEndDrag } = this.props; + this._currentScrollOffset = scrollOffset; - if (this._carouselRef) { - this._onScrollEnd && this._onScrollEnd(); + if (nextActiveItem !== this._onScrollActiveItem) { + this._onScrollActiveItem = nextActiveItem; + onScrollIndexChanged && onScrollIndexChanged(this._getDataIndex(nextActiveItem)); } - if (onScrollEndDrag) { - onScrollEndDrag(event); + if (typeof onScroll === 'function' && event) { + onScroll(event); } } - // Used when `enableMomentum` is ENABLED _onMomentumScrollEnd (event) { - const { onMomentumScrollEnd } = this.props; - - if (this._carouselRef) { - this._onScrollEnd && this._onScrollEnd(); - } - - if (onMomentumScrollEnd) { - onMomentumScrollEnd(event); - } - } + const { autoplayDelay, itemWidth, onMomentumScrollEnd, onSnapToItem } = this.props; + const scrollOffset = event ? this._getScrollOffset(event) : this._currentScrollOffset; + const nextActiveItem = this._getActiveItem(scrollOffset); + const hasSnapped = this._isMultiple(scrollOffset, itemWidth); - _onScrollEnd (event) { - const { autoplayDelay, enableSnap } = this.props; + if (nextActiveItem !== this._activeItem) { + this._activeItem = nextActiveItem; - if (this._ignoreNextMomentum) { - // iOS fix - this._ignoreNextMomentum = false; - return; - } + if (hasSnapped) { + this._repositionScroll(nextActiveItem); + } - if (this._currentContentOffset === this._scrollEndOffset) { - return; + onSnapToItem && onSnapToItem(this._getDataIndex(nextActiveItem)); } - this._scrollEndOffset = this._currentContentOffset; - this._scrollEndActive = this._getActiveItem(this._scrollEndOffset); - - if (enableSnap) { - this._snapScroll(this._scrollEndOffset - this._scrollStartOffset); - } + onMomentumScrollEnd && onMomentumScrollEnd(event); // The touchEnd event is buggy on Android, so this will serve as a fallback whenever needed // https://github.com/facebook/react-native/issues/9439 - if (this._autoplay && !this._autoplaying) { + if (IS_ANDROID && this._autoplay && !this._autoplaying) { clearTimeout(this._enableAutoplayTimeout); this._enableAutoplayTimeout = setTimeout(() => { this.startAutoplay(); - }, autoplayDelay + 50); + }, autoplayDelay); } - } - // Due to a bug, this event is only fired on iOS - // https://github.com/facebook/react-native/issues/6791 - // it's fine since we're only fixing an iOS bug in it, so ... - _onTouchRelease (event) { - const { enableMomentum } = this.props; - - if (enableMomentum && IS_IOS) { - clearTimeout(this._snapNoMomentumTimeout); - this._snapNoMomentumTimeout = setTimeout(() => { - this._snapToItem(this._activeItem); - }, 100); - } } _onLayout (event) { @@ -957,51 +777,16 @@ export default class Carousel extends Component { // Prevent unneeded actions during the first 'onLayout' (triggered on init) if (this._onLayoutInitDone) { this._initPositionsAndInterpolators(); - this._snapToItem(this._activeItem, false, false, false, false); + this._snapToItem(this._activeItem, false, false); } else { this._onLayoutInitDone = true; } - if (onLayout) { - onLayout(event); - } - } - - _snapScroll (delta) { - const { swipeThreshold } = this.props; - - // When using momentum and releasing the touch with - // no velocity, scrollEndActive will be undefined (iOS) - if (!this._scrollEndActive && this._scrollEndActive !== 0 && IS_IOS) { - this._scrollEndActive = this._scrollStartActive; - } - - if (this._scrollStartActive !== this._scrollEndActive) { - // Snap to the new active item - this._snapToItem(this._scrollEndActive); - } else { - // Snap depending on delta - if (delta > 0) { - if (delta > swipeThreshold) { - this._snapToItem(this._scrollStartActive + 1); - } else { - this._snapToItem(this._scrollEndActive); - } - } else if (delta < 0) { - if (delta < -swipeThreshold) { - this._snapToItem(this._scrollStartActive - 1); - } else { - this._snapToItem(this._scrollEndActive); - } - } else { - // Snap to current - this._snapToItem(this._scrollEndActive); - } - } + onLayout && onLayout(event); } - _snapToItem (index, animated = true, fireCallback = true, initial = false, lockScroll = true) { - const { enableMomentum, onSnapToItem, onBeforeSnapToItem } = this.props; + _snapToItem (index, animated = true, fireCallback = true) { + const { onSnapToItem } = this.props; const itemsLength = this._getCustomDataLength(); const wrappedRef = this._getWrappedRef(); @@ -1015,80 +800,21 @@ export default class Carousel extends Component { index = itemsLength - 1; } - if (index !== this._previousActiveItem) { - this._previousActiveItem = index; - - // Placed here to allow overscrolling for edges items - if (lockScroll && this._canLockScroll()) { - this._lockScroll(); - } - - if (fireCallback) { - if (onBeforeSnapToItem) { - this._canFireBeforeCallback = true; - } - - if (onSnapToItem) { - this._canFireCallback = true; - } - } - } - - this._itemToSnapTo = index; - this._scrollOffsetRef = this._positions[index] && this._positions[index].start; - this._onScrollTriggered = false; - - if (!this._scrollOffsetRef && this._scrollOffsetRef !== 0) { - return; - } - - this._scrollTo(this._scrollOffsetRef, animated); - - this._scrollEndOffset = this._currentContentOffset; - - if (enableMomentum) { - // iOS fix, check the note in the constructor - if (!initial) { - this._ignoreNextMomentum = true; - } - - // When momentum is enabled and the user is overscrolling or swiping very quickly, - // 'onScroll' is not going to be triggered for edge items. Then callback won't be - // fired and loop won't work since the scrollview is not going to be repositioned. - // As a workaround, '_onScroll()' will be called manually for these items if a given - // condition hasn't been met after a small delay. - // WARNING: this is ok only when relying on 'momentumScrollEnd', not with 'scrollEndDrag' - if (index === 0 || index === itemsLength - 1) { - clearTimeout(this._edgeItemTimeout); - this._edgeItemTimeout = setTimeout(() => { - if (!initial && index === this._activeItem && !this._onScrollTriggered) { - this._onScroll(); - } - }, 250); - } - } - } - - _onBeforeSnap (index) { - const { onBeforeSnapToItem } = this.props; - - if (!this._carouselRef) { + if (index === this._activeItem) { return; } - this._canFireBeforeCallback = false; - onBeforeSnapToItem && onBeforeSnapToItem(index); - } - - _onSnap (index) { - const { onSnapToItem } = this.props; + const toOffset = this._positions[index] && this._positions[index].start; + toOffset && this._scrollTo(toOffset, animated); - if (!this._carouselRef) { - return; + // `onMomentumScrollEnd` won't be called if the scroll isn't animated + // so we need to trigger the callback manually + // Also, it won't be called on Android when calling `scrollTo, even when the scroll is animated... + // This means we need to call it right away -> The timing will be off for manual triggering + // TODO: find a solution that doesn't rely on v3.x hacks + if (fireCallback && (!animated || IS_ANDROID)) { + onSnapToItem && onSnapToItem(this._getDataIndex(index)); } - - this._canFireCallback = false; - onSnapToItem && onSnapToItem(index); } startAutoplay () { @@ -1164,12 +890,7 @@ export default class Carousel extends Component { // https://github.com/facebook/react-native/issues/1831#issuecomment-231069668 triggerRenderingHack (offset) { - // Avoid messing with user scroll - if (Date.now() - this._lastScrollDate < 500) { - return; - } - - const scrollPosition = this._currentContentOffset; + const scrollPosition = this._currentScrollOffset; if (!scrollPosition && scrollPosition !== 0) { return; } @@ -1197,26 +918,19 @@ export default class Carousel extends Component { _renderItem ({ item, index }) { const { interpolators } = this.state; const { - hasParallaxImages, - itemWidth, - itemHeight, - keyExtractor, - renderItem, - sliderHeight, - sliderWidth, - slideStyle, - vertical + hasParallaxImages, itemWidth, itemHeight, keyExtractor, renderItem, + sliderHeight, sliderWidth, slideStyle, vertical } = this.props; - const animatedValue = interpolators && interpolators[index]; - if (!animatedValue && animatedValue !== 0) { + if (typeof animatedValue === 'undefined') { return null; } const animate = this._shouldAnimateSlides(); const Component = animate ? Animated.View : View; const animatedStyle = animate ? this._getSlideInterpolatedStyle(index, animatedValue) : {}; + const dataIndex = this._getDataIndex(index); const parallaxProps = hasParallaxImages ? { scrollPosition: this._scrollPos, @@ -1235,66 +949,56 @@ export default class Carousel extends Component { return ( - { renderItem({ item, index }, parallaxProps) } + { renderItem({ item, index, dataIndex }, parallaxProps) } ); } _getComponentOverridableProps () { const { - enableMomentum, - itemWidth, - itemHeight, - loopClonesPerSide, - sliderWidth, - sliderHeight, - vertical + itemWidth, itemHeight, loopClonesPerSide, + sliderWidth, sliderHeight, vertical } = this.props; - - const visibleItems = Math.ceil(vertical ? - sliderHeight / itemHeight : - sliderWidth / itemWidth) + 1; + const visibleItems = Math.ceil(vertical ? sliderHeight / itemHeight : sliderWidth / itemWidth) + 1; const initialNumPerSide = this._enableLoop() ? loopClonesPerSide : 2; const initialNumToRender = visibleItems + (initialNumPerSide * 2); - const maxToRenderPerBatch = 1 + (initialNumToRender * 2); - const windowSize = maxToRenderPerBatch; + const maxToRenderPerBatch = initialNumToRender + 2; + const windowSize = 1 + (maxToRenderPerBatch * 2); const specificProps = !this._needsScrollView() ? { - initialNumToRender: initialNumToRender, - maxToRenderPerBatch: maxToRenderPerBatch, - windowSize: windowSize + initialNumToRender, + maxToRenderPerBatch, + windowSize // updateCellsBatchingPeriod } : {}; return { - decelerationRate: enableMomentum ? 0.9 : 'fast', - showsHorizontalScrollIndicator: false, - showsVerticalScrollIndicator: false, - overScrollMode: 'never', + ...specificProps, automaticallyAdjustContentInsets: false, + decelerationRate: 'fast', directionalLockEnabled: true, - pinchGestureEnabled: false, - scrollsToTop: false, - removeClippedSubviews: !this._needsScrollView(), + disableScrollViewPanResponder: false, // If set to `true`, touch events will be triggered too easily inverted: this._needsRTLAdaptations(), + overScrollMode: 'never', + pinchGestureEnabled: false, + // removeClippedSubviews: !this._needsScrollView(), // renderToHardwareTextureAndroid: true, - ...specificProps + scrollsToTop: false, + showsHorizontalScrollIndicator: false, + showsVerticalScrollIndicator: false }; } _getComponentStaticProps () { const { hideCarousel } = this.state; const { - containerCustomStyle, - contentContainerCustomStyle, - keyExtractor, - sliderWidth, - sliderHeight, - style, - vertical + activeSlideAlignment, CellRendererComponent, containerCustomStyle, contentContainerCustomStyle, + firstItem, getItemLayout, keyExtractor, preserveActiveSlideAlignment, + sliderWidth, sliderHeight, style, useExperimentalSnap, vertical } = this.props; const containerStyle = [ + // { overflow: 'hidden' }, containerCustomStyle || style || {}, hideCarousel ? { opacity: 0 } : {}, vertical ? @@ -1303,41 +1007,59 @@ export default class Carousel extends Component { // and https://github.com/facebook/react-native/issues/13100#issuecomment-328986423 { width: sliderWidth, flexDirection: this._needsRTLAdaptations() ? 'row-reverse' : 'row' } ]; + + const innerMarginStyle = vertical ? { + paddingTop: this._getContainerInnerMargin(), + paddingBottom: this._getContainerInnerMargin(true) + } : { + paddingLeft: this._getContainerInnerMargin(), + paddingRight: this._getContainerInnerMargin(true) + }; + const contentContainerStyle = [ - vertical ? { - paddingTop: this._getContainerInnerMargin(), - paddingBottom: this._getContainerInnerMargin(true) - } : { - paddingLeft: this._getContainerInnerMargin(), - paddingRight: this._getContainerInnerMargin(true) - }, - contentContainerCustomStyle || {} + contentContainerCustomStyle || {}, + preserveActiveSlideAlignment && !useExperimentalSnap ? innerMarginStyle : {} ]; + // WARNING: `snapToAlignment` won't work as intended because of the following: + // https://github.com/facebook/react-native/blob/d0871d0a9a373e1d3ac35da46c85c0d0e793116d/React/Views/ScrollView/RCTScrollView.m#L751-L755 + // - Snap points will be off + // - Slide animations will be off + // - Last items won't be set as active (no `onSnapToItem` callback) + // Recommended only with large slides and `activeSlideAlignment` set to `start` for the time being + const snapProps = useExperimentalSnap ? { + disableIntervalMomentum: true, + snapToAlignment: activeSlideAlignment, + snapToInterval: this._getItemMainDimension() + } : { + snapToOffsets: this._getSnapOffsets() + }; + + // Flatlist specifics const specificProps = !this._needsScrollView() ? { - // extraData: this.state, - renderItem: this._renderItem, + CellRendererComponent: CellRendererComponent || this._getCellRendererComponent, + getItemLayout: getItemLayout || this._getItemLayout, + initialScrollIndex: this._getFirstItem(firstItem), + keyExtractor: keyExtractor || this._getKeyExtractor, numColumns: 1, - keyExtractor: keyExtractor || this._getKeyExtractor + renderItem: this._renderItem } : {}; return { - ref: c => this._carouselRef = c, - data: this._getCustomData(), - style: containerStyle, + ...specificProps, + ...snapProps, + ref: (c) => { this._carouselRef = c; }, contentContainerStyle: contentContainerStyle, + data: this._getCustomData(), horizontal: !vertical, + pointerEvents: hideCarousel ? 'none' : 'auto', scrollEventThrottle: 1, - onScroll: this._onScrollHandler, - onScrollBeginDrag: this._onScrollBeginDrag, - onScrollEndDrag: this._onScrollEndDrag, + style: containerStyle, + onLayout: this._onLayout, onMomentumScrollEnd: this._onMomentumScrollEnd, - onResponderRelease: this._onTouchRelease, - onStartShouldSetResponderCapture: this._onStartShouldSetResponderCapture, + onScroll: this._onScrollHandler, onTouchStart: this._onTouchStart, - onTouchEnd: this._onScrollEnd, - onLayout: this._onLayout, - ...specificProps + onTouchEnd: this._onTouchEnd }; } @@ -1354,7 +1076,7 @@ export default class Carousel extends Component { ...this._getComponentStaticProps() }; - const ScrollViewComponent = typeof useScrollView === 'function' ? useScrollView : AnimatedScrollView + const ScrollViewComponent = typeof useScrollView === 'function' ? useScrollView : AnimatedScrollView; return this._needsScrollView() ? ( From b6b1ed87378fefb21a50d7f0392a25d0e07a281b Mon Sep 17 00:00:00 2001 From: bd-arc Date: Sun, 5 Apr 2020 23:17:28 +0200 Subject: [PATCH 02/34] fix(Carousel): make sure that loop and autoplay props work well together --- src/carousel/Carousel.js | 72 ++++++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 24 deletions(-) diff --git a/src/carousel/Carousel.js b/src/carousel/Carousel.js index bbb0a792e..ff2306a2c 100644 --- a/src/carousel/Carousel.js +++ b/src/carousel/Carousel.js @@ -252,6 +252,7 @@ export default class Carousel extends Component { clearTimeout(this._enableAutoplayTimeout); clearTimeout(this._autoplayTimeout); clearTimeout(this._snapNoMomentumTimeout); + clearTimeout(this._androidRepositioningTimeout); } get realIndex () { @@ -339,6 +340,16 @@ export default class Carousel extends Component { return this.props.layout === 'tinder'; } + _shouldRepositionScroll (index) { + const { data, enableSnap, loopClonesPerSide } = this.props; + const dataLength = data && data.length; + if (!enableSnap || !dataLength || !this._enableLoop() || + (index >= loopClonesPerSide && index < dataLength + loopClonesPerSide)) { + return false; + } + return true; + } + _roundNumber (num, decimals = 1) { // https://stackoverflow.com/a/41716722/ const rounder = Math.pow(10, decimals); @@ -657,12 +668,11 @@ export default class Carousel extends Component { }, 50); // works randomly when set to '0' } - _repositionScroll (index) { - const { data, enableSnap, loopClonesPerSide } = this.props; + _repositionScroll (index, animated = false) { + const { data, loopClonesPerSide } = this.props; const dataLength = data && data.length; - if (!enableSnap || !dataLength || !this._enableLoop() || - (index >= loopClonesPerSide && index < dataLength + loopClonesPerSide)) { + if (typeof index === 'undefined' || !this._shouldRepositionScroll(index)) { return; } @@ -674,7 +684,7 @@ export default class Carousel extends Component { repositionTo = index + dataLength; } - this._snapToItem(repositionTo, false, false); + this._snapToItem(repositionTo, animated, false); } _scrollTo (offset, animated = true) { @@ -748,14 +758,16 @@ export default class Carousel extends Component { const nextActiveItem = this._getActiveItem(scrollOffset); const hasSnapped = this._isMultiple(scrollOffset, itemWidth); + // WARNING: everything in this condition will probably need to be called on _snapToItem as well because: + // 1. `onMomentumScrollEnd` won't be called if the scroll isn't animated + // 2. `onMomentumScrollEnd` won't be called at all on Android when scrolling programmatically if (nextActiveItem !== this._activeItem) { this._activeItem = nextActiveItem; + onSnapToItem && onSnapToItem(this._getDataIndex(nextActiveItem)); if (hasSnapped) { this._repositionScroll(nextActiveItem); } - - onSnapToItem && onSnapToItem(this._getDataIndex(nextActiveItem)); } onMomentumScrollEnd && onMomentumScrollEnd(event); @@ -804,16 +816,33 @@ export default class Carousel extends Component { return; } - const toOffset = this._positions[index] && this._positions[index].start; - toOffset && this._scrollTo(toOffset, animated); + const offset = this._positions[index] && this._positions[index].start; - // `onMomentumScrollEnd` won't be called if the scroll isn't animated + if (!offset) { + return; + } + + this._scrollTo(offset, animated); + + // On both platforms, `onMomentumScrollEnd` won't be triggered if the scroll isn't animated // so we need to trigger the callback manually - // Also, it won't be called on Android when calling `scrollTo, even when the scroll is animated... - // This means we need to call it right away -> The timing will be off for manual triggering - // TODO: find a solution that doesn't rely on v3.x hacks - if (fireCallback && (!animated || IS_ANDROID)) { - onSnapToItem && onSnapToItem(this._getDataIndex(index)); + // On Android `onMomentumScrollEnd` won't be triggered when scrolling programmatically + // Therefore everything critical needs to be manually called here as well, even though the timing might be off + const requiresManualTrigger = !animated || IS_ANDROID; + if (requiresManualTrigger) { + this._activeItem = index; + + if (fireCallback) { + onSnapToItem && onSnapToItem(this._getDataIndex(index)); + } + + // Repositioning on Android + if (IS_ANDROID && this._shouldRepositionScroll(index)) { + this._androidRepositioningTimeout = setTimeout(() => { + // Without animation, the behavior is completely buggy... + this._repositionScroll(index, true); + }, 400); // Approximate scroll duration on Android + } } } @@ -867,9 +896,6 @@ export default class Carousel extends Component { let newIndex = this._activeItem + 1; if (newIndex > itemsLength - 1) { - if (!this._enableLoop()) { - return; - } newIndex = 0; } this._snapToItem(newIndex, animated, fireCallback); @@ -880,9 +906,6 @@ export default class Carousel extends Component { let newIndex = this._activeItem - 1; if (newIndex < 0) { - if (!this._enableLoop()) { - return; - } newIndex = itemsLength - 1; } this._snapToItem(newIndex, animated, fireCallback); @@ -955,6 +978,7 @@ export default class Carousel extends Component { } _getComponentOverridableProps () { + const { hideCarousel } = this.state; const { itemWidth, itemHeight, loopClonesPerSide, sliderWidth, sliderHeight, vertical @@ -962,8 +986,8 @@ export default class Carousel extends Component { const visibleItems = Math.ceil(vertical ? sliderHeight / itemHeight : sliderWidth / itemWidth) + 1; const initialNumPerSide = this._enableLoop() ? loopClonesPerSide : 2; const initialNumToRender = visibleItems + (initialNumPerSide * 2); - const maxToRenderPerBatch = initialNumToRender + 2; - const windowSize = 1 + (maxToRenderPerBatch * 2); + const maxToRenderPerBatch = initialNumToRender + (initialNumPerSide * 2); + const windowSize = maxToRenderPerBatch; const specificProps = !this._needsScrollView() ? { initialNumToRender, @@ -981,6 +1005,7 @@ export default class Carousel extends Component { inverted: this._needsRTLAdaptations(), overScrollMode: 'never', pinchGestureEnabled: false, + pointerEvents: hideCarousel ? 'none' : 'auto', // removeClippedSubviews: !this._needsScrollView(), // renderToHardwareTextureAndroid: true, scrollsToTop: false, @@ -1052,7 +1077,6 @@ export default class Carousel extends Component { contentContainerStyle: contentContainerStyle, data: this._getCustomData(), horizontal: !vertical, - pointerEvents: hideCarousel ? 'none' : 'auto', scrollEventThrottle: 1, style: containerStyle, onLayout: this._onLayout, From f2bc3f13fc3590a9329fb5b3edd3958dd87a2644 Mon Sep 17 00:00:00 2001 From: bd-arc Date: Sun, 5 Apr 2020 23:18:38 +0200 Subject: [PATCH 03/34] chore(package): update dependencies and info --- package.json | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 99b670d0e..17f83e2b9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "react-native-snap-carousel", - "version": "3.9.1", - "description": "Swiper/carousel component for React Native with previews, multiple layouts, parallax images, performant handling of huge numbers of items, and RTL support. Compatible with Android & iOS.", + "version": "4.0.0-beta.5", + "description": "Swiper/carousel component for React Native with previews, multiple layouts, parallax images, performant handling of huge numbers of items, and more. Compatible with Android & iOS.", "main": "src/index.js", "repository": { "type": "git", @@ -31,29 +31,28 @@ "android", "ios", "snapping", - "component", - "rtl" + "component" ], - "author": "Archriss (github.com/archriss)", + "author": "Benoit Delmaire (github.com/bd-arc)", "license": "BSD-3-Clause", "dependencies": { - "prop-types": "^15.6.1", + "prop-types": "15.7.2", "react-addons-shallow-compare": "15.6.2" }, "peerDependencies": { - "react": ">=15.0.0", - "react-native": "*" + "react": ">=16.0.0", + "react-native": ">=0.58.0" }, "devDependencies": { - "babel-eslint": "^8.2.2", - "eslint": "^4.19.1", - "eslint-config-standard": "^10.2.1", - "eslint-config-standard-react": "^5.0.0", - "eslint-plugin-import": "^2.11.0", - "eslint-plugin-node": "^5.2.1", - "eslint-plugin-promise": "^3.7.0", - "eslint-plugin-react": "^7.7.0", - "eslint-plugin-standard": "^3.0.1" + "babel-eslint": "10.1.0", + "eslint": "6.8.0", + "eslint-config-standard": "14.1.1", + "eslint-config-standard-react": "9.2.0", + "eslint-plugin-import": "2.20.2", + "eslint-plugin-node": "11.1.0", + "eslint-plugin-promise": "4.2.1", + "eslint-plugin-react": "7.19.0", + "eslint-plugin-standard": "4.0.1" }, "homepage": "https://github.com/archriss/react-native-snap-carousel", "bugs": { From cdbb46a3a381c38be872c76ef43194cba0ee60b5 Mon Sep 17 00:00:00 2001 From: bd-arc Date: Sun, 5 Apr 2020 23:28:32 +0200 Subject: [PATCH 04/34] chore(package): promote alpha version to beta --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 17f83e2b9..39dae7621 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-snap-carousel", - "version": "4.0.0-beta.5", + "version": "4.0.0-beta.1", "description": "Swiper/carousel component for React Native with previews, multiple layouts, parallax images, performant handling of huge numbers of items, and more. Compatible with Android & iOS.", "main": "src/index.js", "repository": { From 46d337ab016008d8011197202333a8acaa0297fe Mon Sep 17 00:00:00 2001 From: bd-arc Date: Sun, 5 Apr 2020 23:39:30 +0200 Subject: [PATCH 05/34] refactor(Carousel): move warnings to a dedicated function --- src/carousel/Carousel.js | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/carousel/Carousel.js b/src/carousel/Carousel.js index ff2306a2c..5f0a87918 100644 --- a/src/carousel/Carousel.js +++ b/src/carousel/Carousel.js @@ -123,22 +123,8 @@ export default class Carousel extends Component { // WARNING: call this AFTER binding _onScroll this._setScrollHandler(props); - // Warnings - if (!ViewPropTypes) { - console.warn('react-native-snap-carousel: It is recommended to use at least version 0.44 of React Native with the plugin'); - } - if (!props.vertical && (!props.sliderWidth || !props.itemWidth)) { - console.error('react-native-snap-carousel: You need to specify both `sliderWidth` and `itemWidth` for horizontal carousels'); - } - if (props.vertical && (!props.sliderHeight || !props.itemHeight)) { - console.error('react-native-snap-carousel: You need to specify both `sliderHeight` and `itemHeight` for vertical carousels'); - } - if (props.apparitionDelay && IS_ANDROID && !props.useScrollView) { - console.warn('react-native-snap-carousel: Using `apparitionDelay` on Android is not recommended since it can lead to rendering issues'); - } - if (props.onScrollViewScroll) { - console.error('react-native-snap-carousel: Prop `onScrollViewScroll` has been removed. Use `onScroll` instead'); - } + // Display warnings + this._displayWarnings(props); } componentDidMount () { @@ -297,6 +283,21 @@ export default class Carousel extends Component { ); } + _displayWarnings (props = this.props) { + if (!props.vertical && (!props.sliderWidth || !props.itemWidth)) { + console.error('react-native-snap-carousel: You need to specify both `sliderWidth` and `itemWidth` for horizontal carousels'); + } + if (props.vertical && (!props.sliderHeight || !props.itemHeight)) { + console.error('react-native-snap-carousel: You need to specify both `sliderHeight` and `itemHeight` for vertical carousels'); + } + if (props.apparitionDelay && IS_ANDROID && !props.useScrollView) { + console.warn('react-native-snap-carousel: Using `apparitionDelay` on Android is not recommended since it can lead to rendering issues'); + } + if (props.onScrollViewScroll) { + console.error('react-native-snap-carousel: Prop `onScrollViewScroll` has been removed. Use `onScroll` instead'); + } + } + _needsScrollView () { const { useScrollView } = this.props; // Android's cell renderer is buggy and has a stange overflow From ec9af3140359563ddcd0132cc72f831699717b71 Mon Sep 17 00:00:00 2001 From: bd-arc Date: Sun, 5 Apr 2020 23:42:11 +0200 Subject: [PATCH 06/34] refactor(misc): code cleaning --- src/pagination/Pagination.js | 6 +++--- src/pagination/PaginationDot.js | 8 ++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/pagination/Pagination.js b/src/pagination/Pagination.js index 5c021cf36..30f817125 100644 --- a/src/pagination/Pagination.js +++ b/src/pagination/Pagination.js @@ -31,7 +31,7 @@ export default class Pagination extends PureComponent { animatedDuration: PropTypes.number, animatedFriction: PropTypes.number, animatedTension: PropTypes.number, - delayPressInDot: PropTypes.number, + delayPressInDot: PropTypes.number }; static defaultProps = { @@ -42,7 +42,7 @@ export default class Pagination extends PureComponent { animatedDuration: 250, animatedFriction: 4, animatedTension: 50, - delayPressInDot: 0, + delayPressInDot: 0 } constructor (props) { @@ -98,7 +98,7 @@ export default class Pagination extends PureComponent { animatedDuration, animatedFriction, animatedTension, - delayPressInDot, + delayPressInDot } = this.props; if (renderDots) { diff --git a/src/pagination/PaginationDot.js b/src/pagination/PaginationDot.js index e59d1969f..3b708b00c 100644 --- a/src/pagination/PaginationDot.js +++ b/src/pagination/PaginationDot.js @@ -10,9 +10,13 @@ export default class PaginationDot extends PureComponent { inactiveScale: PropTypes.number.isRequired, active: PropTypes.bool, activeOpacity: PropTypes.number, + animatedDuration: PropTypes.number, + animatedFriction: PropTypes.number, + animatedTension: PropTypes.number, carouselRef: PropTypes.object, color: PropTypes.string, containerStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, + delayPressInDot: PropTypes.number, inactiveColor: PropTypes.string, inactiveStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, index: PropTypes.number, @@ -43,11 +47,10 @@ export default class PaginationDot extends PureComponent { _animate (toValue = 0) { const { animColor, animOpacity, animTransform } = this.state; - const { animatedDuration, animatedFriction, animatedTension } = this.props + const { animatedDuration, animatedFriction, animatedTension } = this.props; const commonProperties = { toValue, - duration: animatedDuration, isInteraction: false, useNativeDriver: !this._shouldAnimateColor }; @@ -55,6 +58,7 @@ export default class PaginationDot extends PureComponent { let animations = [ Animated.timing(animOpacity, { easing: Easing.linear, + duration: animatedDuration, ...commonProperties }), Animated.spring(animTransform, { From f4a100697531539dc6f2074ec498c4e7736f712f Mon Sep 17 00:00:00 2001 From: bd-arc Date: Mon, 6 Apr 2020 11:30:38 +0200 Subject: [PATCH 07/34] refactor(Carousel): miscellaneous optimizations and code cleaning --- src/carousel/Carousel.js | 156 ++++++++++++++++++++------------------- 1 file changed, 80 insertions(+), 76 deletions(-) diff --git a/src/carousel/Carousel.js b/src/carousel/Carousel.js index 5f0a87918..4b0b95aa0 100644 --- a/src/carousel/Carousel.js +++ b/src/carousel/Carousel.js @@ -151,8 +151,8 @@ export default class Carousel extends Component { // FlatList will use its own built-in prop `initialScrollIndex` if (this._needsScrollView()) { const _firstItem = this._getFirstItem(firstItem); - this._snapToItem(_firstItem, false, false); - this._hackActiveSlideAnimation(_firstItem, 'start', true); + this._snapToItem(_firstItem, false, false, true); + // this._hackActiveSlideAnimation(_firstItem); } if (apparitionDelay) { @@ -183,7 +183,7 @@ export default class Carousel extends Component { } const nextFirstItem = this._getFirstItem(firstItem, this.props); - let nextActiveItem = this._activeItem || this._activeItem === 0 ? this._activeItem : nextFirstItem; + let nextActiveItem = typeof this._activeItem !== 'undefined' ? this._activeItem : nextFirstItem; const hasNewSliderWidth = sliderWidth && sliderWidth !== prevProps.sliderWidth; const hasNewSliderHeight = sliderHeight && sliderHeight !== prevProps.sliderHeight; @@ -212,16 +212,16 @@ export default class Carousel extends Component { // This also fixes first item's active state on Android // Because 'initialScrollIndex' apparently doesn't trigger scroll if (this._previousItemsLength > itemsLength) { - this._hackActiveSlideAnimation(nextActiveItem, null, true); + this._hackActiveSlideAnimation(nextActiveItem); } if (hasNewSliderWidth || hasNewSliderHeight || hasNewItemWidth || hasNewItemHeight) { - this._snapToItem(nextActiveItem, false, false); + this._snapToItem(nextActiveItem, false, false, true); } } else if (nextFirstItem !== this._previousFirstItem && nextFirstItem !== this._activeItem) { this._activeItem = nextFirstItem; this._previousFirstItem = nextFirstItem; - this._snapToItem(nextFirstItem, false, true); + this._snapToItem(nextFirstItem, false, true, true); } if (this.props.onScroll !== prevProps.onScroll) { @@ -411,7 +411,7 @@ export default class Carousel extends Component { _getCustomIndex (index, props = this.props) { const itemsLength = this._getCustomDataLength(props); - if (!itemsLength || (!index && index !== 0)) { + if (!itemsLength || typeof index === 'undefined') { return 0; } @@ -496,11 +496,6 @@ export default class Carousel extends Component { return this._scrollEnabled; } - _getItemMainDimension () { - const { itemWidth, itemHeight, vertical } = this.props; - return vertical ? itemHeight : itemWidth; - } - _setScrollEnabled (scrollEnabled = true) { const wrappedRef = this._getWrappedRef(); @@ -514,6 +509,24 @@ export default class Carousel extends Component { this._scrollEnabled = scrollEnabled; } + _getItemMainDimension () { + const { itemWidth, itemHeight, vertical } = this.props; + return vertical ? itemHeight : itemWidth; + } + + _getItemScrollOffset (index) { + return this._positions && this._positions[index] && this._positions[index].start; + } + + _getItemLayout (_, index) { + const itemMainDimension = this._getItemMainDimension(); + return { + index, + length: itemMainDimension, + offset: itemMainDimension * index // + this._getContainerInnerMargin() + }; + } + // This will allow us to have a proper zIndex even with a FlatList // https://github.com/facebook/react-native/issues/18616#issuecomment-389444165 _getCellRendererComponent ({ children, index, style, ...props }) { @@ -529,15 +542,6 @@ export default class Carousel extends Component { ); } - _getItemLayout (_, index) { - const itemMainDimension = this._getItemMainDimension(); - return { - index, - length: itemMainDimension, - offset: itemMainDimension * index // + this._getContainerInnerMargin() - }; - } - _getKeyExtractor (_, index) { return this._needsScrollView() ? `scrollview-item-${index}` : `flatlist-item-${index}`; } @@ -596,6 +600,22 @@ export default class Carousel extends Component { return itemIndex || 0; } + _getSlideInterpolatedStyle (index, animatedValue) { + const { layoutCardOffset, slideInterpolatedStyle } = this.props; + + if (slideInterpolatedStyle) { + return slideInterpolatedStyle(index, animatedValue, this.props); + } else if (this._shouldUseTinderLayout()) { + return tinderAnimatedStyles(index, animatedValue, this.props, layoutCardOffset); + } else if (this._shouldUseStackLayout()) { + return stackAnimatedStyles(index, animatedValue, this.props, layoutCardOffset); + } else if (this._shouldUseShiftLayout()) { + return shiftAnimatedStyles(index, animatedValue, this.props); + } else { + return defaultAnimatedStyles(index, animatedValue, this.props); + } + } + _initPositionsAndInterpolators (props = this.props) { const { data, scrollInterpolator } = props; const itemMainDimension = this._getItemMainDimension(); @@ -645,28 +665,22 @@ export default class Carousel extends Component { this.setState({ interpolators }); } - _hackActiveSlideAnimation (index, goTo, force = false) { - const { data } = this.props; + _hackActiveSlideAnimation (index, scrollValue = 1) { + const offset = this._getItemScrollOffset(index); - if (!this._mounted || !this._carouselRef || !this._positions[index] || (!force && this._enableLoop())) { + if (!this._mounted || !this._carouselRef || typeof offset === 'undefined') { return; } - const offset = this._positions[index] && this._positions[index].start; - - if (!offset && offset !== 0) { - return; - } + const multiplier = this._currentScrollOffset === 0 ? 1 : -1; + const scrollDelta = scrollValue * multiplier; - const itemsLength = data && data.length; - const direction = goTo || itemsLength === 1 ? 'start' : 'end'; - - this._scrollTo(offset + (direction === 'start' ? -1 : 1), false); + this._scrollTo({ offset: offset + scrollDelta, animated: false }); clearTimeout(this._hackSlideAnimationTimeout); this._hackSlideAnimationTimeout = setTimeout(() => { - this._scrollTo(offset, false); - }, 50); // works randomly when set to '0' + this._scrollTo({ offset, animated: false }); + }, 1); // works randomly when set to '0' } _repositionScroll (index, animated = false) { @@ -688,22 +702,30 @@ export default class Carousel extends Component { this._snapToItem(repositionTo, animated, false); } - _scrollTo (offset, animated = true) { + _scrollTo ({ offset, index, animated = true }) { const { vertical } = this.props; const wrappedRef = this._getWrappedRef(); + if (!this._mounted || !wrappedRef || (typeof offset === 'undefined' && typeof index === 'undefined')) { + return; + } - if (!this._mounted || !wrappedRef) { + let scrollToOffset; + if (typeof index !== 'undefined') { + scrollToOffset = this._getItemScrollOffset(index); + } else { + scrollToOffset = offset; + } + + if (typeof scrollToOffset === 'undefined') { return; } - const specificOptions = this._needsScrollView() ? { + const options = this._needsScrollView() ? { x: vertical ? 0 : offset, - y: vertical ? offset : 0 + y: vertical ? offset : 0, + animated } : { - offset - }; - const options = { - ...specificOptions, + offset, animated }; @@ -790,7 +812,7 @@ export default class Carousel extends Component { // Prevent unneeded actions during the first 'onLayout' (triggered on init) if (this._onLayoutInitDone) { this._initPositionsAndInterpolators(); - this._snapToItem(this._activeItem, false, false); + this._snapToItem(this._activeItem, false, false, true); } else { this._onLayoutInitDone = true; } @@ -798,7 +820,7 @@ export default class Carousel extends Component { onLayout && onLayout(event); } - _snapToItem (index, animated = true, fireCallback = true) { + _snapToItem (index, animated = true, fireCallback = true, forceScrollTo = false) { const { onSnapToItem } = this.props; const itemsLength = this._getCustomDataLength(); const wrappedRef = this._getWrappedRef(); @@ -813,17 +835,17 @@ export default class Carousel extends Component { index = itemsLength - 1; } - if (index === this._activeItem) { + if (index === this._activeItem && !forceScrollTo) { return; } - const offset = this._positions[index] && this._positions[index].start; + const offset = this._getItemScrollOffset(index); if (!offset) { return; } - this._scrollTo(offset, animated); + this._scrollTo({ offset, animated }); // On both platforms, `onMomentumScrollEnd` won't be triggered if the scroll isn't animated // so we need to trigger the callback manually @@ -839,10 +861,14 @@ export default class Carousel extends Component { // Repositioning on Android if (IS_ANDROID && this._shouldRepositionScroll(index)) { - this._androidRepositioningTimeout = setTimeout(() => { - // Without animation, the behavior is completely buggy... - this._repositionScroll(index, true); - }, 400); // Approximate scroll duration on Android + if (animated) { + this._androidRepositioningTimeout = setTimeout(() => { + // Without scroll animation, the behavior is completely buggy... + this._repositionScroll(index, true); + }, 400); // Approximate scroll duration on Android + } else { + this._repositionScroll(index); + } } } } @@ -913,30 +939,8 @@ export default class Carousel extends Component { } // https://github.com/facebook/react-native/issues/1831#issuecomment-231069668 - triggerRenderingHack (offset) { - const scrollPosition = this._currentScrollOffset; - if (!scrollPosition && scrollPosition !== 0) { - return; - } - - const scrollOffset = offset || (scrollPosition === 0 ? 1 : -1); - this._scrollTo(scrollPosition + scrollOffset, false); - } - - _getSlideInterpolatedStyle (index, animatedValue) { - const { layoutCardOffset, slideInterpolatedStyle } = this.props; - - if (slideInterpolatedStyle) { - return slideInterpolatedStyle(index, animatedValue, this.props); - } else if (this._shouldUseTinderLayout()) { - return tinderAnimatedStyles(index, animatedValue, this.props, layoutCardOffset); - } else if (this._shouldUseStackLayout()) { - return stackAnimatedStyles(index, animatedValue, this.props, layoutCardOffset); - } else if (this._shouldUseShiftLayout()) { - return shiftAnimatedStyles(index, animatedValue, this.props); - } else { - return defaultAnimatedStyles(index, animatedValue, this.props); - } + triggerRenderingHack (offset = 1) { + this._hackActiveSlideAnimation(this._activeItem, offset); } _renderItem ({ item, index }) { From ac5218954c08b2e157b4428b4f293af26c5affd0 Mon Sep 17 00:00:00 2001 From: bd-arc Date: Mon, 6 Apr 2020 12:18:17 +0200 Subject: [PATCH 08/34] feat(Carousel): retrieve RN version and update warnings --- src/carousel/Carousel.js | 53 ++++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/src/carousel/Carousel.js b/src/carousel/Carousel.js index 4b0b95aa0..2d349a95a 100644 --- a/src/carousel/Carousel.js +++ b/src/carousel/Carousel.js @@ -20,6 +20,8 @@ const AnimatedScrollView = Animated.createAnimatedComponent(ScrollView); // otherwise it is undefined at init, which messes with custom indexes const IS_RTL = I18nManager.isRTL; +const RN_PACKAGE = require('../../../react-native/package.json'); + export default class Carousel extends Component { static propTypes = { @@ -97,6 +99,8 @@ export default class Carousel extends Component { interpolators: [] }; + this._RNVersionCode = this._getRNVersionCode(); + // The following values are not stored in the state because 'setState()' is asynchronous // and this results in an absolutely crappy behavior on Android while swiping (see #156) const initialActiveItem = this._getFirstItem(props.firstItem); @@ -283,19 +287,52 @@ export default class Carousel extends Component { ); } + // This will return a future-proof version code number compatible with semantic versioning + // Examples: 0.59.3 -> 5903 / 0.61.4 -> 6104 / 0.62.12 -> 6212 / 1.0.2 -> 10002 + _getRNVersionCode () { + const version = RN_PACKAGE && RN_PACKAGE.version; + if (!version) { + return null; + } + const versionSplit = version.split('.'); + if (!versionSplit || !versionSplit.length) { + return null; + } + return versionSplit[0] * 10000 + + (typeof versionSplit[1] !== 'undefined' ? versionSplit[1] * 100 : 0) + + (typeof versionSplit[2] !== 'undefined' ? versionSplit[2] * 1 : 0); + } + _displayWarnings (props = this.props) { + const pluginName = 'react-native-snap-carousel'; + const removedProps = [ + 'activeAnimationType', + 'activeAnimationOptions', + 'enableMomentum', + 'lockScrollTimeoutDuration', + 'lockScrollWhileSnapping', + 'onBeforeSnapToItem', + 'swipeThreshold' + ]; + + if (this._RNVersionCode && this._RNVersionCode < 5800) { + console.error( + `${pluginName}: Version 4+ of the plugin is based on React Native props that were introduced in version 0.58. ` + + 'Please downgrade to version 3.x or update your version of React Native.' + ); + } if (!props.vertical && (!props.sliderWidth || !props.itemWidth)) { - console.error('react-native-snap-carousel: You need to specify both `sliderWidth` and `itemWidth` for horizontal carousels'); + console.error(`${pluginName}: You need to specify both 'sliderWidth' and 'itemWidth' for horizontal carousels`); } if (props.vertical && (!props.sliderHeight || !props.itemHeight)) { - console.error('react-native-snap-carousel: You need to specify both `sliderHeight` and `itemHeight` for vertical carousels'); - } - if (props.apparitionDelay && IS_ANDROID && !props.useScrollView) { - console.warn('react-native-snap-carousel: Using `apparitionDelay` on Android is not recommended since it can lead to rendering issues'); - } - if (props.onScrollViewScroll) { - console.error('react-native-snap-carousel: Prop `onScrollViewScroll` has been removed. Use `onScroll` instead'); + console.error(`${pluginName}: You need to specify both 'sliderHeight' and 'itemHeight' for vertical carousels`); } + + removedProps.forEach((removedProp) => { + if (props[removedProp]) { + console.warn(`${pluginName}: Prop ${removedProp} has been removed in version 4 of the plugin. `); + } + }); } _needsScrollView () { From dc187538ac0b7672b7d9c71371e67f4c042c26f5 Mon Sep 17 00:00:00 2001 From: bd-arc Date: Mon, 6 Apr 2020 13:53:39 +0200 Subject: [PATCH 09/34] refactor(Carousel): remove unneeded prop `preserveActiveSlideAlignment` --- src/carousel/Carousel.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/carousel/Carousel.js b/src/carousel/Carousel.js index 2d349a95a..8b9938537 100644 --- a/src/carousel/Carousel.js +++ b/src/carousel/Carousel.js @@ -50,7 +50,6 @@ export default class Carousel extends Component { layoutCardOffset: PropTypes.number, loop: PropTypes.bool, loopClonesPerSide: PropTypes.number, - preserveActiveSlideAlignment: PropTypes.bool, scrollEnabled: PropTypes.bool, scrollInterpolator: PropTypes.func, slideInterpolatedStyle: PropTypes.func, @@ -82,7 +81,6 @@ export default class Carousel extends Component { layout: 'default', loop: false, loopClonesPerSide: 3, - preserveActiveSlideAlignment: true, scrollEnabled: true, slideStyle: {}, shouldOptimizeUpdates: true, @@ -330,7 +328,7 @@ export default class Carousel extends Component { removedProps.forEach((removedProp) => { if (props[removedProp]) { - console.warn(`${pluginName}: Prop ${removedProp} has been removed in version 4 of the plugin. `); + console.warn(`${pluginName}: Prop ${removedProp} has been removed in version 4 of the plugin`); } }); } @@ -1059,8 +1057,8 @@ export default class Carousel extends Component { _getComponentStaticProps () { const { hideCarousel } = this.state; const { - activeSlideAlignment, CellRendererComponent, containerCustomStyle, contentContainerCustomStyle, - firstItem, getItemLayout, keyExtractor, preserveActiveSlideAlignment, + activeSlideAlignment, CellRendererComponent, containerCustomStyle, + contentContainerCustomStyle, firstItem, getItemLayout, keyExtractor, sliderWidth, sliderHeight, style, useExperimentalSnap, vertical } = this.props; @@ -1085,7 +1083,7 @@ export default class Carousel extends Component { const contentContainerStyle = [ contentContainerCustomStyle || {}, - preserveActiveSlideAlignment && !useExperimentalSnap ? innerMarginStyle : {} + !useExperimentalSnap ? innerMarginStyle : {} ]; // WARNING: `snapToAlignment` won't work as intended because of the following: From 21f36ff97588137145f1e3e77793d6981dc27f29 Mon Sep 17 00:00:00 2001 From: bd-arc Date: Mon, 6 Apr 2020 14:43:43 +0200 Subject: [PATCH 10/34] refactor(Carousel): don't activate `disableIntervalMomentum` for the experimental snap by default --- src/carousel/Carousel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/carousel/Carousel.js b/src/carousel/Carousel.js index 8b9938537..5c00e2a0e 100644 --- a/src/carousel/Carousel.js +++ b/src/carousel/Carousel.js @@ -1093,7 +1093,7 @@ export default class Carousel extends Component { // - Last items won't be set as active (no `onSnapToItem` callback) // Recommended only with large slides and `activeSlideAlignment` set to `start` for the time being const snapProps = useExperimentalSnap ? { - disableIntervalMomentum: true, + // disableIntervalMomentum: true, // Slide ± one item at a time snapToAlignment: activeSlideAlignment, snapToInterval: this._getItemMainDimension() } : { From 3cb43dc6a8c8974857d4c0e331646f7a9aef8293 Mon Sep 17 00:00:00 2001 From: bd-arc Date: Mon, 6 Apr 2020 15:01:32 +0200 Subject: [PATCH 11/34] chore(package): bump to version 4.0.0-beta.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 39dae7621..f5c4570b5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-snap-carousel", - "version": "4.0.0-beta.1", + "version": "4.0.0-beta.2", "description": "Swiper/carousel component for React Native with previews, multiple layouts, parallax images, performant handling of huge numbers of items, and more. Compatible with Android & iOS.", "main": "src/index.js", "repository": { From 01af5c4e9c68fb65566dc2538f3b4dc22c2e6042 Mon Sep 17 00:00:00 2001 From: "Leonardo E. Dominguez" Date: Mon, 18 May 2020 20:40:26 -0400 Subject: [PATCH 12/34] fix: snapping to item with index 0 not working --- src/carousel/Carousel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/carousel/Carousel.js b/src/carousel/Carousel.js index 5c00e2a0e..d27a6c8d2 100644 --- a/src/carousel/Carousel.js +++ b/src/carousel/Carousel.js @@ -876,7 +876,7 @@ export default class Carousel extends Component { const offset = this._getItemScrollOffset(index); - if (!offset) { + if (offset === undefined) { return; } From 5a54da3c3a7b7b6fee2792910260002926c7506d Mon Sep 17 00:00:00 2001 From: bd-arc Date: Tue, 26 May 2020 10:23:59 +0200 Subject: [PATCH 13/34] fix(Carousel): disable RN version check as it's breaking on snack --- src/carousel/Carousel.js | 47 +++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/src/carousel/Carousel.js b/src/carousel/Carousel.js index d27a6c8d2..196a902c9 100644 --- a/src/carousel/Carousel.js +++ b/src/carousel/Carousel.js @@ -7,6 +7,11 @@ import { shiftAnimatedStyles, stackAnimatedStyles, tinderAnimatedStyles } from '../utils/animations'; +// Metro doesn't support dynamic imports - i.e. require() done in the component itself +// But at the same time the following import will fail on Snack... +// TODO: find a way to get React Native's version without having to assume the file path +// import RN_PACKAGE from '../../../react-native/package.json'; + const IS_ANDROID = Platform.OS === 'android'; // Native driver for scroll events @@ -20,8 +25,6 @@ const AnimatedScrollView = Animated.createAnimatedComponent(ScrollView); // otherwise it is undefined at init, which messes with custom indexes const IS_RTL = I18nManager.isRTL; -const RN_PACKAGE = require('../../../react-native/package.json'); - export default class Carousel extends Component { static propTypes = { @@ -97,7 +100,7 @@ export default class Carousel extends Component { interpolators: [] }; - this._RNVersionCode = this._getRNVersionCode(); + // this._RNVersionCode = this._getRNVersionCode(); // The following values are not stored in the state because 'setState()' is asynchronous // and this results in an absolutely crappy behavior on Android while swiping (see #156) @@ -287,19 +290,19 @@ export default class Carousel extends Component { // This will return a future-proof version code number compatible with semantic versioning // Examples: 0.59.3 -> 5903 / 0.61.4 -> 6104 / 0.62.12 -> 6212 / 1.0.2 -> 10002 - _getRNVersionCode () { - const version = RN_PACKAGE && RN_PACKAGE.version; - if (!version) { - return null; - } - const versionSplit = version.split('.'); - if (!versionSplit || !versionSplit.length) { - return null; - } - return versionSplit[0] * 10000 + - (typeof versionSplit[1] !== 'undefined' ? versionSplit[1] * 100 : 0) + - (typeof versionSplit[2] !== 'undefined' ? versionSplit[2] * 1 : 0); - } + // _getRNVersionCode () { + // const version = RN_PACKAGE && RN_PACKAGE.version; + // if (!version) { + // return null; + // } + // const versionSplit = version.split('.'); + // if (!versionSplit || !versionSplit.length) { + // return null; + // } + // return versionSplit[0] * 10000 + + // (typeof versionSplit[1] !== 'undefined' ? versionSplit[1] * 100 : 0) + + // (typeof versionSplit[2] !== 'undefined' ? versionSplit[2] * 1 : 0); + // } _displayWarnings (props = this.props) { const pluginName = 'react-native-snap-carousel'; @@ -313,12 +316,12 @@ export default class Carousel extends Component { 'swipeThreshold' ]; - if (this._RNVersionCode && this._RNVersionCode < 5800) { - console.error( - `${pluginName}: Version 4+ of the plugin is based on React Native props that were introduced in version 0.58. ` + - 'Please downgrade to version 3.x or update your version of React Native.' - ); - } + // if (this._RNVersionCode && this._RNVersionCode < 5800) { + // console.error( + // `${pluginName}: Version 4+ of the plugin is based on React Native props that were introduced in version 0.58. ` + + // 'Please downgrade to version 3.x or update your version of React Native.' + // ); + // } if (!props.vertical && (!props.sliderWidth || !props.itemWidth)) { console.error(`${pluginName}: You need to specify both 'sliderWidth' and 'itemWidth' for horizontal carousels`); } From ea01a68036550d6f27e5af711e3a811ad4279581 Mon Sep 17 00:00:00 2001 From: bd-arc Date: Tue, 26 May 2020 10:24:26 +0200 Subject: [PATCH 14/34] chore(project): enable eslint locally --- .vscode/settings.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..a6fb97658 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "eslint.enable": true +} \ No newline at end of file From df5c39b2e23ee9da72097dfbb4e2d0142da2f0f4 Mon Sep 17 00:00:00 2001 From: bd-arc Date: Tue, 26 May 2020 10:34:16 +0200 Subject: [PATCH 15/34] chore(package): bump to version 4.0.0-beta.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f5c4570b5..08936f54a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-snap-carousel", - "version": "4.0.0-beta.2", + "version": "4.0.0-beta.3", "description": "Swiper/carousel component for React Native with previews, multiple layouts, parallax images, performant handling of huge numbers of items, and more. Compatible with Android & iOS.", "main": "src/index.js", "repository": { From 4c7e258b2f27fe08fa5b99c3188b8585588fb405 Mon Sep 17 00:00:00 2001 From: bd-arc Date: Wed, 27 May 2020 07:19:53 +0200 Subject: [PATCH 16/34] fix(Carousel): prevent getNode() deprecation warning with RN 0.62+ while ensuring backward-compatibility --- src/carousel/Carousel.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/carousel/Carousel.js b/src/carousel/Carousel.js index 196a902c9..07ebf058f 100644 --- a/src/carousel/Carousel.js +++ b/src/carousel/Carousel.js @@ -519,6 +519,7 @@ export default class Carousel extends Component { } _getWrappedRef () { + // Starting with RN 0.62, we should no longer call `getNode()` on the ref of an Animated component if (this._carouselRef && ( (this._needsScrollView() && this._carouselRef.scrollTo) || (!this._needsScrollView() && this._carouselRef.scrollToOffset) From a534e29c781b01e3d0a1125199b502cac6e6d074 Mon Sep 17 00:00:00 2001 From: bd-arc Date: Wed, 27 May 2020 07:20:26 +0200 Subject: [PATCH 17/34] refactor(Carousel): code cleaning --- src/carousel/Carousel.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/carousel/Carousel.js b/src/carousel/Carousel.js index 07ebf058f..e806b34c4 100644 --- a/src/carousel/Carousel.js +++ b/src/carousel/Carousel.js @@ -272,7 +272,7 @@ export default class Carousel extends Component { if (props.onScroll && Array.isArray(props.onScroll._argMapping)) { // Because of a react-native issue https://github.com/facebook/react-native/issues/13294 argMapping.pop(); - const [ argMap ] = props.onScroll._argMapping; + const [argMap] = props.onScroll._argMapping; if (argMap && argMap.nativeEvent && argMap.nativeEvent.contentOffset) { // Shares the same animated value passed in props this._scrollPos = @@ -663,7 +663,7 @@ export default class Carousel extends Component { return; } - let interpolators = []; + const interpolators = []; this._positions = []; this._getCustomData(props).forEach((itemData, index) => { From c76869dc58c0b3b88ed34ef7785baa987fcdc9e7 Mon Sep 17 00:00:00 2001 From: bd-arc Date: Wed, 27 May 2020 07:20:54 +0200 Subject: [PATCH 18/34] chore(package): bump to version 4.0.0-beta.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 08936f54a..b6a380e06 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-snap-carousel", - "version": "4.0.0-beta.3", + "version": "4.0.0-beta.4", "description": "Swiper/carousel component for React Native with previews, multiple layouts, parallax images, performant handling of huge numbers of items, and more. Compatible with Android & iOS.", "main": "src/index.js", "repository": { From ff992116c515285781950add0070fe79e48d41ae Mon Sep 17 00:00:00 2001 From: bd-arc Date: Wed, 27 May 2020 14:30:36 +0200 Subject: [PATCH 19/34] refactor(Carousel): allow prop `contentContainerCustomStyle` to override inner margin's style; warn about the potential consequences in the doc --- doc/PROPS_METHODS_AND_GETTERS.md | 2 +- src/carousel/Carousel.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/PROPS_METHODS_AND_GETTERS.md b/doc/PROPS_METHODS_AND_GETTERS.md index 8ece217d0..5167d600c 100644 --- a/doc/PROPS_METHODS_AND_GETTERS.md +++ b/doc/PROPS_METHODS_AND_GETTERS.md @@ -68,7 +68,7 @@ Prop | Description | Type | Default `activeAnimationType` | Custom [animation type](https://facebook.github.io/react-native/docs/animated.html#configuring-animations): either `'decay`, `'spring'` or `'timing'`. Note that it will only be applied to the scale animation since opacity's animation type will always be set to `timing` (no one wants the opacity to 'bounce' around). | String | `'timing'` `activeSlideAlignment` | Determine active slide's alignment relative to the carousel. Possible values are: `'start'`, `'center'` and `'end'`. **It is not recommended to use this prop in conjunction with the `layout` one.** | String | `'center'` `containerCustomStyle` | Optional styles for Scrollview's global wrapper | View Style Object | `{}` -`contentContainerCustomStyle` | Optional styles for Scrollview's items container | View Style Object | `{}` +`contentContainerCustomStyle` | Optional styles for Scrollview's items container. **:warning: Tread softly as this can mess with the carousel's inner logic!** | View Style Object | `{}` `inactiveSlideOpacity` | Value of the opacity effect applied to inactive slides | Number | `0.7` `inactiveSlideScale` | Value of the 'scale' transform applied to inactive slides | Number | `0.9` `inactiveSlideShift` | Value of the 'translate' transform applied to inactive slides (see [#204](https://github.com/archriss/react-native-snap-carousel/issues/204) or [the "custom interpolations" doc](https://github.com/archriss/react-native-snap-carousel/blob/master/doc/CUSTOM_INTERPOLATIONS.md) for an example usage). This prop will have no effect with layouts others than the default one. | Number | `0` diff --git a/src/carousel/Carousel.js b/src/carousel/Carousel.js index e806b34c4..ce1faca2a 100644 --- a/src/carousel/Carousel.js +++ b/src/carousel/Carousel.js @@ -1086,8 +1086,8 @@ export default class Carousel extends Component { }; const contentContainerStyle = [ - contentContainerCustomStyle || {}, - !useExperimentalSnap ? innerMarginStyle : {} + !useExperimentalSnap ? innerMarginStyle : {}, + contentContainerCustomStyle || {} ]; // WARNING: `snapToAlignment` won't work as intended because of the following: From 4dc54b0ff891f3608a171dedc39a1d2ea4d1a833 Mon Sep 17 00:00:00 2001 From: bd-arc Date: Thu, 23 Jul 2020 15:40:29 +0200 Subject: [PATCH 20/34] fix(Carousel): prevent a critical and random crash on android See: https://github.com/archriss/react-native-snap-carousel/issues/703 --- src/carousel/Carousel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/carousel/Carousel.js b/src/carousel/Carousel.js index ce1faca2a..5ebdf7df7 100644 --- a/src/carousel/Carousel.js +++ b/src/carousel/Carousel.js @@ -571,7 +571,7 @@ export default class Carousel extends Component { _getCellRendererComponent ({ children, index, style, ...props }) { const cellStyle = [ style, - { zIndex: this._getCustomDataLength() - index } + !IS_ANDROID ? { zIndex: this._getCustomDataLength() - index } : {} ]; return ( From 981ed7daaa4a9e3fcb5a6e62ea9b24e490b3d795 Mon Sep 17 00:00:00 2001 From: bd-arc Date: Thu, 23 Jul 2020 15:40:47 +0200 Subject: [PATCH 21/34] chore(package): bump to version 4.0.0-beta.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b6a380e06..17f83e2b9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-snap-carousel", - "version": "4.0.0-beta.4", + "version": "4.0.0-beta.5", "description": "Swiper/carousel component for React Native with previews, multiple layouts, parallax images, performant handling of huge numbers of items, and more. Compatible with Android & iOS.", "main": "src/index.js", "repository": { From 23009909e3ac55475f5d35a8d6c0ae318a025115 Mon Sep 17 00:00:00 2001 From: Thibault Malbranche Date: Sat, 1 Aug 2020 16:56:25 +0200 Subject: [PATCH 22/34] wip 2 'bugs' might have been fixed btw --- .eslintrc | 38 - .gitignore | 4 +- .vscode/settings.json | 5 +- package.json | 67 +- src/carousel/Carousel.js | 1161 ------------------ src/carousel/Carousel.tsx | 1367 ++++++++++++++++++++++ src/carousel/types.ts | 89 ++ src/{index.js => index.ts} | 8 +- src/pagination/Pagination.js | 167 --- src/pagination/Pagination.style.js | 24 - src/pagination/Pagination.style.ts | 24 + src/pagination/Pagination.tsx | 194 +++ src/pagination/PaginationDot.js | 160 --- src/pagination/PaginationDot.tsx | 188 +++ src/parallaximage/ParallaxImage.js | 222 ---- src/parallaximage/ParallaxImage.style.js | 20 - src/parallaximage/ParallaxImage.style.ts | 20 + src/parallaximage/ParallaxImage.tsx | 259 ++++ src/utils/animations.js | 325 ----- src/utils/animations.ts | 383 ++++++ tsconfig.json | 28 + 21 files changed, 2624 insertions(+), 2129 deletions(-) delete mode 100644 .eslintrc delete mode 100644 src/carousel/Carousel.js create mode 100644 src/carousel/Carousel.tsx create mode 100644 src/carousel/types.ts rename src/{index.js => index.ts} (67%) delete mode 100644 src/pagination/Pagination.js delete mode 100644 src/pagination/Pagination.style.js create mode 100644 src/pagination/Pagination.style.ts create mode 100644 src/pagination/Pagination.tsx delete mode 100644 src/pagination/PaginationDot.js create mode 100644 src/pagination/PaginationDot.tsx delete mode 100644 src/parallaximage/ParallaxImage.js delete mode 100644 src/parallaximage/ParallaxImage.style.js create mode 100644 src/parallaximage/ParallaxImage.style.ts create mode 100644 src/parallaximage/ParallaxImage.tsx delete mode 100644 src/utils/animations.js create mode 100644 src/utils/animations.ts create mode 100644 tsconfig.json diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index d4739ac63..000000000 --- a/.eslintrc +++ /dev/null @@ -1,38 +0,0 @@ -{ - "parser" : "babel-eslint", - "extends" : [ - "standard", - "standard-react" - ], - "plugins": [ - "react" - ], - "env" : { - "browser" : true, - "es6": true - }, - "globals": { - "__DEV__": false - }, - "parserOptions": { - "ecmaVersion": 6, - "ecmaFeatures": { - "jsx": true - } - }, - "rules": { - "generator-star-spacing": 0, - "indent": [2, 4, { "ignoredNodes": ["JSXAttribute", "JSXSpreadAttribute"], "SwitchCase": 1 }], - "no-warning-comments": [1, { - "terms": ["todo", "fixme", "xxx"], - "location": "start" - }], - "operator-linebreak": [2, "after"], - "padded-blocks": 0, - "semi": [2, "always"], - "react/jsx-indent-props": [2, 2], - "react/jsx-boolean-value": [0, "never"], - "react/jsx-curly-spacing": [0, "never"], - "react/jsx-indent": [2, 4] - } -} diff --git a/.gitignore b/.gitignore index c0a6a7d22..4f35f984b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ node_modules #LOCK package-lock.json -yarn.lock \ No newline at end of file +yarn.lock + +lib \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index a6fb97658..53aa4f28e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,6 @@ { - "eslint.enable": true + "eslint.enable": true, + "editor.codeActionsOnSave": { + "source.fixAll": true + } } \ No newline at end of file diff --git a/package.json b/package.json index 17f83e2b9..499aa462d 100644 --- a/package.json +++ b/package.json @@ -2,11 +2,19 @@ "name": "react-native-snap-carousel", "version": "4.0.0-beta.5", "description": "Swiper/carousel component for React Native with previews, multiple layouts, parallax images, performant handling of huge numbers of items, and more. Compatible with Android & iOS.", - "main": "src/index.js", + "main": "lib/commonjs/index", + "module": "lib/module/index", + "types": "lib/typescript/src/index.d.ts", + "react-native": "src/index", + "source": "src/index", "repository": { "type": "git", "url": "github.com/archriss/react-native-snap-carousel" }, + "scripts": { + "prepare": "bob build", + "lint": "eslint \"**/*.{js,ts,tsx}\"" + }, "keywords": [ "react", "native", @@ -36,6 +44,7 @@ "author": "Benoit Delmaire (github.com/bd-arc)", "license": "BSD-3-Clause", "dependencies": { + "@types/react-addons-shallow-compare": "^0.14.22", "prop-types": "15.7.2", "react-addons-shallow-compare": "15.6.2" }, @@ -44,15 +53,55 @@ "react-native": ">=0.58.0" }, "devDependencies": { + "@react-native-community/bob": "^0.16.2", + "@react-native-community/eslint-config": "^2.0.0", + "@types/react": "^16.9.19", + "@types/react-native": "0.62.13", "babel-eslint": "10.1.0", - "eslint": "6.8.0", - "eslint-config-standard": "14.1.1", - "eslint-config-standard-react": "9.2.0", - "eslint-plugin-import": "2.20.2", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-promise": "4.2.1", - "eslint-plugin-react": "7.19.0", - "eslint-plugin-standard": "4.0.1" + "eslint": "^7.2.0", + "eslint-config-prettier": "^6.11.0", + "eslint-plugin-prettier": "^3.1.3", + "react-native": "0.61.5", + "typescript": "^3.8.3" + }, + "eslintConfig": { + "extends": [ + "@react-native-community", + "prettier" + ], + "rules": { + "prettier/prettier": [ + "error", + { + "quoteProps": "consistent", + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "es5", + "useTabs": false + } + ] + } + }, + "eslintIgnore": [ + "node_modules/", + "lib/", + "example" + ], + "prettier": { + "quoteProps": "consistent", + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "es5", + "useTabs": false + }, + "@react-native-community/bob": { + "source": "src", + "output": "lib", + "targets": [ + "commonjs", + "module", + "typescript" + ] }, "homepage": "https://github.com/archriss/react-native-snap-carousel", "bugs": { diff --git a/src/carousel/Carousel.js b/src/carousel/Carousel.js deleted file mode 100644 index 5ebdf7df7..000000000 --- a/src/carousel/Carousel.js +++ /dev/null @@ -1,1161 +0,0 @@ -import React, { Component } from 'react'; -import { Animated, FlatList, I18nManager, Platform, ScrollView, View, ViewPropTypes } from 'react-native'; -import PropTypes from 'prop-types'; -import shallowCompare from 'react-addons-shallow-compare'; -import { - defaultScrollInterpolator, stackScrollInterpolator, tinderScrollInterpolator, defaultAnimatedStyles, - shiftAnimatedStyles, stackAnimatedStyles, tinderAnimatedStyles -} from '../utils/animations'; - -// Metro doesn't support dynamic imports - i.e. require() done in the component itself -// But at the same time the following import will fail on Snack... -// TODO: find a way to get React Native's version without having to assume the file path -// import RN_PACKAGE from '../../../react-native/package.json'; - -const IS_ANDROID = Platform.OS === 'android'; - -// Native driver for scroll events -// See: https://facebook.github.io/react-native/blog/2017/02/14/using-native-driver-for-animated.html -const AnimatedFlatList = FlatList ? Animated.createAnimatedComponent(FlatList) : null; -const AnimatedScrollView = Animated.createAnimatedComponent(ScrollView); - -// React Native automatically handles RTL layouts; unfortunately, it's buggy with horizontal ScrollView -// See https://github.com/facebook/react-native/issues/11960 -// NOTE: the following variable is not declared in the constructor -// otherwise it is undefined at init, which messes with custom indexes -const IS_RTL = I18nManager.isRTL; - -export default class Carousel extends Component { - - static propTypes = { - data: PropTypes.array.isRequired, - renderItem: PropTypes.func.isRequired, - itemWidth: PropTypes.number, // required for horizontal carousel - itemHeight: PropTypes.number, // required for vertical carousel - sliderWidth: PropTypes.number, // required for horizontal carousel - sliderHeight: PropTypes.number, // required for vertical carousel - activeSlideAlignment: PropTypes.oneOf(['center', 'end', 'start']), - activeSlideOffset: PropTypes.number, - apparitionDelay: PropTypes.number, - autoplay: PropTypes.bool, - autoplayDelay: PropTypes.number, - autoplayInterval: PropTypes.number, - callbackOffsetMargin: PropTypes.number, - containerCustomStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, - contentContainerCustomStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, - enableSnap: PropTypes.bool, - firstItem: PropTypes.number, - hasParallaxImages: PropTypes.bool, - inactiveSlideOpacity: PropTypes.number, - inactiveSlideScale: PropTypes.number, - inactiveSlideShift: PropTypes.number, - layout: PropTypes.oneOf(['default', 'stack', 'tinder']), - layoutCardOffset: PropTypes.number, - loop: PropTypes.bool, - loopClonesPerSide: PropTypes.number, - scrollEnabled: PropTypes.bool, - scrollInterpolator: PropTypes.func, - slideInterpolatedStyle: PropTypes.func, - slideStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, - shouldOptimizeUpdates: PropTypes.bool, - useExperimentalSnap: PropTypes.bool, - useScrollView: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]), - vertical: PropTypes.bool, - onScrollIndexChanged: PropTypes.func, - onSnapToItem: PropTypes.func - }; - - static defaultProps = { - activeSlideAlignment: 'center', - activeSlideOffset: 20, - apparitionDelay: 0, - autoplay: false, - autoplayDelay: 1000, - autoplayInterval: 3000, - callbackOffsetMargin: 5, - containerCustomStyle: {}, - contentContainerCustomStyle: {}, - enableSnap: true, - firstItem: 0, - hasParallaxImages: false, - inactiveSlideOpacity: 0.7, - inactiveSlideScale: 0.9, - inactiveSlideShift: 0, - layout: 'default', - loop: false, - loopClonesPerSide: 3, - scrollEnabled: true, - slideStyle: {}, - shouldOptimizeUpdates: true, - useExperimentalSnap: false, - useScrollView: !AnimatedFlatList, - vertical: false - } - - constructor (props) { - super(props); - - this.state = { - hideCarousel: !!props.apparitionDelay, - interpolators: [] - }; - - // this._RNVersionCode = this._getRNVersionCode(); - - // The following values are not stored in the state because 'setState()' is asynchronous - // and this results in an absolutely crappy behavior on Android while swiping (see #156) - const initialActiveItem = this._getFirstItem(props.firstItem); - this._activeItem = initialActiveItem; - this._onScrollActiveItem = initialActiveItem; - this._previousFirstItem = initialActiveItem; - this._previousItemsLength = initialActiveItem; - - this._mounted = false; - this._positions = []; - this._currentScrollOffset = 0; // Store ScrollView's scroll position - this._scrollEnabled = props.scrollEnabled !== false; - - this._getCellRendererComponent = this._getCellRendererComponent.bind(this); - this._getItemLayout = this._getItemLayout.bind(this); - this._getKeyExtractor = this._getKeyExtractor.bind(this); - this._onLayout = this._onLayout.bind(this); - this._onScroll = this._onScroll.bind(this); - this._onMomentumScrollEnd = this._onMomentumScrollEnd.bind(this); - this._onTouchStart = this._onTouchStart.bind(this); - this._onTouchEnd = this._onTouchEnd.bind(this); - this._renderItem = this._renderItem.bind(this); - - // WARNING: call this AFTER binding _onScroll - this._setScrollHandler(props); - - // Display warnings - this._displayWarnings(props); - } - - componentDidMount () { - const { apparitionDelay, autoplay, firstItem } = this.props; - - this._mounted = true; - this._initPositionsAndInterpolators(); - - // Without 'requestAnimationFrame' or a `0` timeout, images will randomly not be rendered on Android... - this._initTimeout = setTimeout(() => { - if (!this._mounted) { - return; - } - - const apparitionCallback = () => { - if (apparitionDelay) { - this.setState({ hideCarousel: false }); - } - if (autoplay) { - this.startAutoplay(); - } - }; - - // FlatList will use its own built-in prop `initialScrollIndex` - if (this._needsScrollView()) { - const _firstItem = this._getFirstItem(firstItem); - this._snapToItem(_firstItem, false, false, true); - // this._hackActiveSlideAnimation(_firstItem); - } - - if (apparitionDelay) { - this._apparitionTimeout = setTimeout(() => { - apparitionCallback(); - }, apparitionDelay); - } else { - apparitionCallback(); - } - }, 1); - } - - shouldComponentUpdate (nextProps, nextState) { - if (this.props.shouldOptimizeUpdates === false) { - return true; - } else { - return shallowCompare(this, nextProps, nextState); - } - } - - componentDidUpdate (prevProps) { - const { interpolators } = this.state; - const { firstItem, itemHeight, itemWidth, scrollEnabled, sliderHeight, sliderWidth } = this.props; - const itemsLength = this._getCustomDataLength(this.props); - - if (!itemsLength) { - return; - } - - const nextFirstItem = this._getFirstItem(firstItem, this.props); - let nextActiveItem = typeof this._activeItem !== 'undefined' ? this._activeItem : nextFirstItem; - - const hasNewSliderWidth = sliderWidth && sliderWidth !== prevProps.sliderWidth; - const hasNewSliderHeight = sliderHeight && sliderHeight !== prevProps.sliderHeight; - const hasNewItemWidth = itemWidth && itemWidth !== prevProps.itemWidth; - const hasNewItemHeight = itemHeight && itemHeight !== prevProps.itemHeight; - const hasNewScrollEnabled = scrollEnabled !== prevProps.scrollEnabled; - - // Prevent issues with dynamically removed items - if (nextActiveItem > itemsLength - 1) { - nextActiveItem = itemsLength - 1; - } - - // Handle changing scrollEnabled independent of user -> carousel interaction - if (hasNewScrollEnabled) { - this._setScrollEnabled(scrollEnabled); - } - - if (interpolators.length !== itemsLength || hasNewSliderWidth || - hasNewSliderHeight || hasNewItemWidth || hasNewItemHeight) { - this._activeItem = nextActiveItem; - this._previousItemsLength = itemsLength; - - this._initPositionsAndInterpolators(this.props); - - // Handle scroll issue when dynamically removing items (see #133) - // This also fixes first item's active state on Android - // Because 'initialScrollIndex' apparently doesn't trigger scroll - if (this._previousItemsLength > itemsLength) { - this._hackActiveSlideAnimation(nextActiveItem); - } - - if (hasNewSliderWidth || hasNewSliderHeight || hasNewItemWidth || hasNewItemHeight) { - this._snapToItem(nextActiveItem, false, false, true); - } - } else if (nextFirstItem !== this._previousFirstItem && nextFirstItem !== this._activeItem) { - this._activeItem = nextFirstItem; - this._previousFirstItem = nextFirstItem; - this._snapToItem(nextFirstItem, false, true, true); - } - - if (this.props.onScroll !== prevProps.onScroll) { - this._setScrollHandler(this.props); - } - } - - componentWillUnmount () { - this._mounted = false; - this.stopAutoplay(); - clearTimeout(this._initTimeout); - clearTimeout(this._apparitionTimeout); - clearTimeout(this._hackSlideAnimationTimeout); - clearTimeout(this._enableAutoplayTimeout); - clearTimeout(this._autoplayTimeout); - clearTimeout(this._snapNoMomentumTimeout); - clearTimeout(this._androidRepositioningTimeout); - } - - get realIndex () { - return this._activeItem; - } - - get currentIndex () { - return this._getDataIndex(this._activeItem); - } - - get currentScrollPosition () { - return this._currentScrollOffset; - } - - _setScrollHandler (props) { - // Native driver for scroll events - const scrollEventConfig = { - listener: this._onScroll, - useNativeDriver: true - }; - this._scrollPos = new Animated.Value(0); - const argMapping = props.vertical ? - [{ nativeEvent: { contentOffset: { y: this._scrollPos } } }] : - [{ nativeEvent: { contentOffset: { x: this._scrollPos } } }]; - - if (props.onScroll && Array.isArray(props.onScroll._argMapping)) { - // Because of a react-native issue https://github.com/facebook/react-native/issues/13294 - argMapping.pop(); - const [argMap] = props.onScroll._argMapping; - if (argMap && argMap.nativeEvent && argMap.nativeEvent.contentOffset) { - // Shares the same animated value passed in props - this._scrollPos = - argMap.nativeEvent.contentOffset.x || - argMap.nativeEvent.contentOffset.y || - this._scrollPos; - } - argMapping.push(...props.onScroll._argMapping); - } - this._onScrollHandler = Animated.event( - argMapping, - scrollEventConfig - ); - } - - // This will return a future-proof version code number compatible with semantic versioning - // Examples: 0.59.3 -> 5903 / 0.61.4 -> 6104 / 0.62.12 -> 6212 / 1.0.2 -> 10002 - // _getRNVersionCode () { - // const version = RN_PACKAGE && RN_PACKAGE.version; - // if (!version) { - // return null; - // } - // const versionSplit = version.split('.'); - // if (!versionSplit || !versionSplit.length) { - // return null; - // } - // return versionSplit[0] * 10000 + - // (typeof versionSplit[1] !== 'undefined' ? versionSplit[1] * 100 : 0) + - // (typeof versionSplit[2] !== 'undefined' ? versionSplit[2] * 1 : 0); - // } - - _displayWarnings (props = this.props) { - const pluginName = 'react-native-snap-carousel'; - const removedProps = [ - 'activeAnimationType', - 'activeAnimationOptions', - 'enableMomentum', - 'lockScrollTimeoutDuration', - 'lockScrollWhileSnapping', - 'onBeforeSnapToItem', - 'swipeThreshold' - ]; - - // if (this._RNVersionCode && this._RNVersionCode < 5800) { - // console.error( - // `${pluginName}: Version 4+ of the plugin is based on React Native props that were introduced in version 0.58. ` + - // 'Please downgrade to version 3.x or update your version of React Native.' - // ); - // } - if (!props.vertical && (!props.sliderWidth || !props.itemWidth)) { - console.error(`${pluginName}: You need to specify both 'sliderWidth' and 'itemWidth' for horizontal carousels`); - } - if (props.vertical && (!props.sliderHeight || !props.itemHeight)) { - console.error(`${pluginName}: You need to specify both 'sliderHeight' and 'itemHeight' for vertical carousels`); - } - - removedProps.forEach((removedProp) => { - if (props[removedProp]) { - console.warn(`${pluginName}: Prop ${removedProp} has been removed in version 4 of the plugin`); - } - }); - } - - _needsScrollView () { - const { useScrollView } = this.props; - // Android's cell renderer is buggy and has a stange overflow - // TODO: a workaround might be to pass the custom animated styles directly to it - return IS_ANDROID ? - useScrollView || !AnimatedFlatList || this._shouldUseStackLayout() || this._shouldUseTinderLayout() : - useScrollView || !AnimatedFlatList; - } - - _needsRTLAdaptations () { - const { vertical } = this.props; - return IS_RTL && IS_ANDROID && !vertical; - } - - _enableLoop () { - const { data, enableSnap, loop } = this.props; - return enableSnap && loop && data && data.length && data.length > 1; - } - - _shouldAnimateSlides (props = this.props) { - const { inactiveSlideOpacity, inactiveSlideScale, scrollInterpolator, slideInterpolatedStyle } = props; - return inactiveSlideOpacity < 1 || - inactiveSlideScale < 1 || - !!scrollInterpolator || - !!slideInterpolatedStyle || - this._shouldUseShiftLayout() || - this._shouldUseStackLayout() || - this._shouldUseTinderLayout(); - } - - _shouldUseShiftLayout () { - const { inactiveSlideShift, layout } = this.props; - return layout === 'default' && inactiveSlideShift !== 0; - } - - _shouldUseStackLayout () { - return this.props.layout === 'stack'; - } - - _shouldUseTinderLayout () { - return this.props.layout === 'tinder'; - } - - _shouldRepositionScroll (index) { - const { data, enableSnap, loopClonesPerSide } = this.props; - const dataLength = data && data.length; - if (!enableSnap || !dataLength || !this._enableLoop() || - (index >= loopClonesPerSide && index < dataLength + loopClonesPerSide)) { - return false; - } - return true; - } - - _roundNumber (num, decimals = 1) { - // https://stackoverflow.com/a/41716722/ - const rounder = Math.pow(10, decimals); - return Math.round((num + Number.EPSILON) * rounder) / rounder; - } - - _isMultiple (x, y) { - // This prevents Javascript precision issues: https://stackoverflow.com/a/58440614/ - // Required because Android viewport size can return pretty complicated decimals numbers - return Math.round(Math.round(x / y) / (1 / y)) === Math.round(x); - } - - _getCustomData (props = this.props) { - const { data, loopClonesPerSide } = props; - const dataLength = data && data.length; - - if (!dataLength) { - return []; - } - - if (!this._enableLoop()) { - return data; - } - - let previousItems = []; - let nextItems = []; - - if (loopClonesPerSide > dataLength) { - const dataMultiplier = Math.floor(loopClonesPerSide / dataLength); - const remainder = loopClonesPerSide % dataLength; - - for (let i = 0; i < dataMultiplier; i++) { - previousItems.push(...data); - nextItems.push(...data); - } - - previousItems.unshift(...data.slice(-remainder)); - nextItems.push(...data.slice(0, remainder)); - } else { - previousItems = data.slice(-loopClonesPerSide); - nextItems = data.slice(0, loopClonesPerSide); - } - - return previousItems.concat(data, nextItems); - } - - _getCustomDataLength (props = this.props) { - const { data, loopClonesPerSide } = props; - const dataLength = data && data.length; - - if (!dataLength) { - return 0; - } - - return this._enableLoop() ? dataLength + (2 * loopClonesPerSide) : dataLength; - } - - _getCustomIndex (index, props = this.props) { - const itemsLength = this._getCustomDataLength(props); - - if (!itemsLength || typeof index === 'undefined') { - return 0; - } - - return this._needsRTLAdaptations() ? itemsLength - index - 1 : index; - } - - _getDataIndex (index) { - const { data, loopClonesPerSide } = this.props; - const dataLength = data && data.length; - - if (!this._enableLoop() || !dataLength) { - return index; - } - - if (index >= dataLength + loopClonesPerSide) { - return loopClonesPerSide > dataLength ? - (index - loopClonesPerSide) % dataLength : - index - dataLength - loopClonesPerSide; - } else if (index < loopClonesPerSide) { - // TODO: is there a simpler way of determining the interpolated index? - if (loopClonesPerSide > dataLength) { - const baseDataIndexes = []; - const dataIndexes = []; - const dataMultiplier = Math.floor(loopClonesPerSide / dataLength); - const remainder = loopClonesPerSide % dataLength; - - for (let i = 0; i < dataLength; i++) { - baseDataIndexes.push(i); - } - - for (let j = 0; j < dataMultiplier; j++) { - dataIndexes.push(...baseDataIndexes); - } - - dataIndexes.unshift(...baseDataIndexes.slice(-remainder)); - return dataIndexes[index]; - } else { - return index + dataLength - loopClonesPerSide; - } - } else { - return index - loopClonesPerSide; - } - } - - // Used with `snapToItem()` and 'PaginationDot' - _getPositionIndex (index) { - const { loop, loopClonesPerSide } = this.props; - return loop ? index + loopClonesPerSide : index; - } - - _getSnapOffsets (props = this.props) { - const offset = this._getItemMainDimension(); - return [...Array(this._getCustomDataLength(props))].map((_, i) => { - return i * offset; - }); - } - - _getFirstItem (index, props = this.props) { - const { loopClonesPerSide } = props; - const itemsLength = this._getCustomDataLength(props); - - if (!itemsLength || index > itemsLength - 1 || index < 0) { - return 0; - } - - return this._enableLoop() ? index + loopClonesPerSide : index; - } - - _getWrappedRef () { - // Starting with RN 0.62, we should no longer call `getNode()` on the ref of an Animated component - if (this._carouselRef && ( - (this._needsScrollView() && this._carouselRef.scrollTo) || - (!this._needsScrollView() && this._carouselRef.scrollToOffset) - )) { - return this._carouselRef; - } - // https://github.com/facebook/react-native/issues/10635 - // https://stackoverflow.com/a/48786374/8412141 - return this._carouselRef && this._carouselRef.getNode && this._carouselRef.getNode(); - } - - _getScrollEnabled () { - return this._scrollEnabled; - } - - _setScrollEnabled (scrollEnabled = true) { - const wrappedRef = this._getWrappedRef(); - - if (!wrappedRef || !wrappedRef.setNativeProps) { - return; - } - - // 'setNativeProps()' is used instead of 'setState()' because the latter - // really takes a toll on Android behavior when momentum is disabled - wrappedRef.setNativeProps({ scrollEnabled }); - this._scrollEnabled = scrollEnabled; - } - - _getItemMainDimension () { - const { itemWidth, itemHeight, vertical } = this.props; - return vertical ? itemHeight : itemWidth; - } - - _getItemScrollOffset (index) { - return this._positions && this._positions[index] && this._positions[index].start; - } - - _getItemLayout (_, index) { - const itemMainDimension = this._getItemMainDimension(); - return { - index, - length: itemMainDimension, - offset: itemMainDimension * index // + this._getContainerInnerMargin() - }; - } - - // This will allow us to have a proper zIndex even with a FlatList - // https://github.com/facebook/react-native/issues/18616#issuecomment-389444165 - _getCellRendererComponent ({ children, index, style, ...props }) { - const cellStyle = [ - style, - !IS_ANDROID ? { zIndex: this._getCustomDataLength() - index } : {} - ]; - - return ( - - {children} - - ); - } - - _getKeyExtractor (_, index) { - return this._needsScrollView() ? `scrollview-item-${index}` : `flatlist-item-${index}`; - } - - _getScrollOffset (event) { - const { vertical } = this.props; - return (event && event.nativeEvent && event.nativeEvent.contentOffset && - event.nativeEvent.contentOffset[vertical ? 'y' : 'x']) || 0; - } - - _getContainerInnerMargin (opposite = false) { - const { sliderWidth, sliderHeight, itemWidth, itemHeight, vertical, activeSlideAlignment } = this.props; - - if ((activeSlideAlignment === 'start' && !opposite) || - (activeSlideAlignment === 'end' && opposite)) { - return 0; - } else if ((activeSlideAlignment === 'end' && !opposite) || - (activeSlideAlignment === 'start' && opposite)) { - return vertical ? sliderHeight - itemHeight : sliderWidth - itemWidth; - } else { - return vertical ? (sliderHeight - itemHeight) / 2 : (sliderWidth - itemWidth) / 2; - } - } - - _getActiveSlideOffset () { - const { activeSlideOffset } = this.props; - const itemMainDimension = this._getItemMainDimension(); - const minOffset = 10; - // Make sure activeSlideOffset never prevents the active area from being at least 10 px wide - return itemMainDimension / 2 - activeSlideOffset >= minOffset ? activeSlideOffset : minOffset; - } - - _getActiveItem (offset) { - const itemMainDimension = this._getItemMainDimension(); - const center = offset + itemMainDimension / 2; - const activeSlideOffset = this._getActiveSlideOffset(); - const lastIndex = this._positions.length - 1; - let itemIndex; - - if (offset <= 0) { - return 0; - } - - if (this._positions[lastIndex] && offset >= this._positions[lastIndex].start) { - return lastIndex; - } - - for (let i = 0; i < this._positions.length; i++) { - const { start, end } = this._positions[i]; - if (center + activeSlideOffset >= start && center - activeSlideOffset <= end) { - itemIndex = i; - break; - } - } - - return itemIndex || 0; - } - - _getSlideInterpolatedStyle (index, animatedValue) { - const { layoutCardOffset, slideInterpolatedStyle } = this.props; - - if (slideInterpolatedStyle) { - return slideInterpolatedStyle(index, animatedValue, this.props); - } else if (this._shouldUseTinderLayout()) { - return tinderAnimatedStyles(index, animatedValue, this.props, layoutCardOffset); - } else if (this._shouldUseStackLayout()) { - return stackAnimatedStyles(index, animatedValue, this.props, layoutCardOffset); - } else if (this._shouldUseShiftLayout()) { - return shiftAnimatedStyles(index, animatedValue, this.props); - } else { - return defaultAnimatedStyles(index, animatedValue, this.props); - } - } - - _initPositionsAndInterpolators (props = this.props) { - const { data, scrollInterpolator } = props; - const itemMainDimension = this._getItemMainDimension(); - - if (!data || !data.length) { - return; - } - - const interpolators = []; - this._positions = []; - - this._getCustomData(props).forEach((itemData, index) => { - const _index = this._getCustomIndex(index, props); - let animatedValue; - - this._positions[index] = { - start: index * itemMainDimension, - end: index * itemMainDimension + itemMainDimension - }; - - if (!this._shouldAnimateSlides(props)) { - animatedValue = new Animated.Value(1); - } else { - let interpolator; - - if (scrollInterpolator) { - interpolator = scrollInterpolator(_index, props); - } else if (this._shouldUseStackLayout()) { - interpolator = stackScrollInterpolator(_index, props); - } else if (this._shouldUseTinderLayout()) { - interpolator = tinderScrollInterpolator(_index, props); - } - - if (!interpolator || !interpolator.inputRange || !interpolator.outputRange) { - interpolator = defaultScrollInterpolator(_index, props); - } - - animatedValue = this._scrollPos.interpolate({ - ...interpolator, - extrapolate: 'clamp' - }); - } - - interpolators.push(animatedValue); - }); - - this.setState({ interpolators }); - } - - _hackActiveSlideAnimation (index, scrollValue = 1) { - const offset = this._getItemScrollOffset(index); - - if (!this._mounted || !this._carouselRef || typeof offset === 'undefined') { - return; - } - - const multiplier = this._currentScrollOffset === 0 ? 1 : -1; - const scrollDelta = scrollValue * multiplier; - - this._scrollTo({ offset: offset + scrollDelta, animated: false }); - - clearTimeout(this._hackSlideAnimationTimeout); - this._hackSlideAnimationTimeout = setTimeout(() => { - this._scrollTo({ offset, animated: false }); - }, 1); // works randomly when set to '0' - } - - _repositionScroll (index, animated = false) { - const { data, loopClonesPerSide } = this.props; - const dataLength = data && data.length; - - if (typeof index === 'undefined' || !this._shouldRepositionScroll(index)) { - return; - } - - let repositionTo = index; - - if (index >= dataLength + loopClonesPerSide) { - repositionTo = index - dataLength; - } else if (index < loopClonesPerSide) { - repositionTo = index + dataLength; - } - - this._snapToItem(repositionTo, animated, false); - } - - _scrollTo ({ offset, index, animated = true }) { - const { vertical } = this.props; - const wrappedRef = this._getWrappedRef(); - if (!this._mounted || !wrappedRef || (typeof offset === 'undefined' && typeof index === 'undefined')) { - return; - } - - let scrollToOffset; - if (typeof index !== 'undefined') { - scrollToOffset = this._getItemScrollOffset(index); - } else { - scrollToOffset = offset; - } - - if (typeof scrollToOffset === 'undefined') { - return; - } - - const options = this._needsScrollView() ? { - x: vertical ? 0 : offset, - y: vertical ? offset : 0, - animated - } : { - offset, - animated - }; - - if (this._needsScrollView()) { - wrappedRef.scrollTo(options); - } else { - wrappedRef.scrollToOffset(options); - } - } - - _onTouchStart () { - const { onTouchStart } = this.props; - - // `onTouchStart` is fired even when `scrollEnabled` is set to `false` - if (this._getScrollEnabled() !== false && this._autoplaying) { - this.pauseAutoPlay(); - } - - onTouchStart && onTouchStart(); - } - - _onTouchEnd () { - const { onTouchEnd } = this.props; - - if (this._getScrollEnabled() !== false && this._autoplay && !this._autoplaying) { - // This event is buggy on Android, so a fallback is provided in _onMomentumScrollEnd() - this.startAutoplay(); - } - - onTouchEnd && onTouchEnd(); - } - - _onScroll (event) { - const { onScroll, onScrollIndexChanged } = this.props; - const scrollOffset = event ? this._getScrollOffset(event) : this._currentScrollOffset; - const nextActiveItem = this._getActiveItem(scrollOffset); - - this._currentScrollOffset = scrollOffset; - - if (nextActiveItem !== this._onScrollActiveItem) { - this._onScrollActiveItem = nextActiveItem; - onScrollIndexChanged && onScrollIndexChanged(this._getDataIndex(nextActiveItem)); - } - - if (typeof onScroll === 'function' && event) { - onScroll(event); - } - } - - _onMomentumScrollEnd (event) { - const { autoplayDelay, itemWidth, onMomentumScrollEnd, onSnapToItem } = this.props; - const scrollOffset = event ? this._getScrollOffset(event) : this._currentScrollOffset; - const nextActiveItem = this._getActiveItem(scrollOffset); - const hasSnapped = this._isMultiple(scrollOffset, itemWidth); - - // WARNING: everything in this condition will probably need to be called on _snapToItem as well because: - // 1. `onMomentumScrollEnd` won't be called if the scroll isn't animated - // 2. `onMomentumScrollEnd` won't be called at all on Android when scrolling programmatically - if (nextActiveItem !== this._activeItem) { - this._activeItem = nextActiveItem; - onSnapToItem && onSnapToItem(this._getDataIndex(nextActiveItem)); - - if (hasSnapped) { - this._repositionScroll(nextActiveItem); - } - } - - onMomentumScrollEnd && onMomentumScrollEnd(event); - - // The touchEnd event is buggy on Android, so this will serve as a fallback whenever needed - // https://github.com/facebook/react-native/issues/9439 - if (IS_ANDROID && this._autoplay && !this._autoplaying) { - clearTimeout(this._enableAutoplayTimeout); - this._enableAutoplayTimeout = setTimeout(() => { - this.startAutoplay(); - }, autoplayDelay); - } - - } - - _onLayout (event) { - const { onLayout } = this.props; - - // Prevent unneeded actions during the first 'onLayout' (triggered on init) - if (this._onLayoutInitDone) { - this._initPositionsAndInterpolators(); - this._snapToItem(this._activeItem, false, false, true); - } else { - this._onLayoutInitDone = true; - } - - onLayout && onLayout(event); - } - - _snapToItem (index, animated = true, fireCallback = true, forceScrollTo = false) { - const { onSnapToItem } = this.props; - const itemsLength = this._getCustomDataLength(); - const wrappedRef = this._getWrappedRef(); - - if (!itemsLength || !wrappedRef) { - return; - } - - if (!index || index < 0) { - index = 0; - } else if (itemsLength > 0 && index >= itemsLength) { - index = itemsLength - 1; - } - - if (index === this._activeItem && !forceScrollTo) { - return; - } - - const offset = this._getItemScrollOffset(index); - - if (offset === undefined) { - return; - } - - this._scrollTo({ offset, animated }); - - // On both platforms, `onMomentumScrollEnd` won't be triggered if the scroll isn't animated - // so we need to trigger the callback manually - // On Android `onMomentumScrollEnd` won't be triggered when scrolling programmatically - // Therefore everything critical needs to be manually called here as well, even though the timing might be off - const requiresManualTrigger = !animated || IS_ANDROID; - if (requiresManualTrigger) { - this._activeItem = index; - - if (fireCallback) { - onSnapToItem && onSnapToItem(this._getDataIndex(index)); - } - - // Repositioning on Android - if (IS_ANDROID && this._shouldRepositionScroll(index)) { - if (animated) { - this._androidRepositioningTimeout = setTimeout(() => { - // Without scroll animation, the behavior is completely buggy... - this._repositionScroll(index, true); - }, 400); // Approximate scroll duration on Android - } else { - this._repositionScroll(index); - } - } - } - } - - startAutoplay () { - const { autoplayInterval, autoplayDelay } = this.props; - this._autoplay = true; - - if (this._autoplaying) { - return; - } - - clearTimeout(this._autoplayTimeout); - this._autoplayTimeout = setTimeout(() => { - this._autoplaying = true; - this._autoplayInterval = setInterval(() => { - if (this._autoplaying) { - this.snapToNext(); - } - }, autoplayInterval); - }, autoplayDelay); - } - - pauseAutoPlay () { - this._autoplaying = false; - clearTimeout(this._autoplayTimeout); - clearTimeout(this._enableAutoplayTimeout); - clearInterval(this._autoplayInterval); - } - - stopAutoplay () { - this._autoplay = false; - this.pauseAutoPlay(); - } - - snapToItem (index, animated = true, fireCallback = true) { - if (!index || index < 0) { - index = 0; - } - - const positionIndex = this._getPositionIndex(index); - - if (positionIndex === this._activeItem) { - return; - } - - this._snapToItem(positionIndex, animated, fireCallback); - } - - snapToNext (animated = true, fireCallback = true) { - const itemsLength = this._getCustomDataLength(); - - let newIndex = this._activeItem + 1; - if (newIndex > itemsLength - 1) { - newIndex = 0; - } - this._snapToItem(newIndex, animated, fireCallback); - } - - snapToPrev (animated = true, fireCallback = true) { - const itemsLength = this._getCustomDataLength(); - - let newIndex = this._activeItem - 1; - if (newIndex < 0) { - newIndex = itemsLength - 1; - } - this._snapToItem(newIndex, animated, fireCallback); - } - - // https://github.com/facebook/react-native/issues/1831#issuecomment-231069668 - triggerRenderingHack (offset = 1) { - this._hackActiveSlideAnimation(this._activeItem, offset); - } - - _renderItem ({ item, index }) { - const { interpolators } = this.state; - const { - hasParallaxImages, itemWidth, itemHeight, keyExtractor, renderItem, - sliderHeight, sliderWidth, slideStyle, vertical - } = this.props; - const animatedValue = interpolators && interpolators[index]; - - if (typeof animatedValue === 'undefined') { - return null; - } - - const animate = this._shouldAnimateSlides(); - const Component = animate ? Animated.View : View; - const animatedStyle = animate ? this._getSlideInterpolatedStyle(index, animatedValue) : {}; - const dataIndex = this._getDataIndex(index); - - const parallaxProps = hasParallaxImages ? { - scrollPosition: this._scrollPos, - carouselRef: this._carouselRef, - vertical, - sliderWidth, - sliderHeight, - itemWidth, - itemHeight - } : undefined; - - const mainDimension = vertical ? { height: itemHeight } : { width: itemWidth }; - const specificProps = this._needsScrollView() ? { - key: keyExtractor ? keyExtractor(item, index) : this._getKeyExtractor(item, index) - } : {}; - - return ( - - { renderItem({ item, index, dataIndex }, parallaxProps) } - - ); - } - - _getComponentOverridableProps () { - const { hideCarousel } = this.state; - const { - itemWidth, itemHeight, loopClonesPerSide, - sliderWidth, sliderHeight, vertical - } = this.props; - const visibleItems = Math.ceil(vertical ? sliderHeight / itemHeight : sliderWidth / itemWidth) + 1; - const initialNumPerSide = this._enableLoop() ? loopClonesPerSide : 2; - const initialNumToRender = visibleItems + (initialNumPerSide * 2); - const maxToRenderPerBatch = initialNumToRender + (initialNumPerSide * 2); - const windowSize = maxToRenderPerBatch; - - const specificProps = !this._needsScrollView() ? { - initialNumToRender, - maxToRenderPerBatch, - windowSize - // updateCellsBatchingPeriod - } : {}; - - return { - ...specificProps, - automaticallyAdjustContentInsets: false, - decelerationRate: 'fast', - directionalLockEnabled: true, - disableScrollViewPanResponder: false, // If set to `true`, touch events will be triggered too easily - inverted: this._needsRTLAdaptations(), - overScrollMode: 'never', - pinchGestureEnabled: false, - pointerEvents: hideCarousel ? 'none' : 'auto', - // removeClippedSubviews: !this._needsScrollView(), - // renderToHardwareTextureAndroid: true, - scrollsToTop: false, - showsHorizontalScrollIndicator: false, - showsVerticalScrollIndicator: false - }; - } - - _getComponentStaticProps () { - const { hideCarousel } = this.state; - const { - activeSlideAlignment, CellRendererComponent, containerCustomStyle, - contentContainerCustomStyle, firstItem, getItemLayout, keyExtractor, - sliderWidth, sliderHeight, style, useExperimentalSnap, vertical - } = this.props; - - const containerStyle = [ - // { overflow: 'hidden' }, - containerCustomStyle || style || {}, - hideCarousel ? { opacity: 0 } : {}, - vertical ? - { height: sliderHeight, flexDirection: 'column' } : - // LTR hack; see https://github.com/facebook/react-native/issues/11960 - // and https://github.com/facebook/react-native/issues/13100#issuecomment-328986423 - { width: sliderWidth, flexDirection: this._needsRTLAdaptations() ? 'row-reverse' : 'row' } - ]; - - const innerMarginStyle = vertical ? { - paddingTop: this._getContainerInnerMargin(), - paddingBottom: this._getContainerInnerMargin(true) - } : { - paddingLeft: this._getContainerInnerMargin(), - paddingRight: this._getContainerInnerMargin(true) - }; - - const contentContainerStyle = [ - !useExperimentalSnap ? innerMarginStyle : {}, - contentContainerCustomStyle || {} - ]; - - // WARNING: `snapToAlignment` won't work as intended because of the following: - // https://github.com/facebook/react-native/blob/d0871d0a9a373e1d3ac35da46c85c0d0e793116d/React/Views/ScrollView/RCTScrollView.m#L751-L755 - // - Snap points will be off - // - Slide animations will be off - // - Last items won't be set as active (no `onSnapToItem` callback) - // Recommended only with large slides and `activeSlideAlignment` set to `start` for the time being - const snapProps = useExperimentalSnap ? { - // disableIntervalMomentum: true, // Slide ± one item at a time - snapToAlignment: activeSlideAlignment, - snapToInterval: this._getItemMainDimension() - } : { - snapToOffsets: this._getSnapOffsets() - }; - - // Flatlist specifics - const specificProps = !this._needsScrollView() ? { - CellRendererComponent: CellRendererComponent || this._getCellRendererComponent, - getItemLayout: getItemLayout || this._getItemLayout, - initialScrollIndex: this._getFirstItem(firstItem), - keyExtractor: keyExtractor || this._getKeyExtractor, - numColumns: 1, - renderItem: this._renderItem - } : {}; - - return { - ...specificProps, - ...snapProps, - ref: (c) => { this._carouselRef = c; }, - contentContainerStyle: contentContainerStyle, - data: this._getCustomData(), - horizontal: !vertical, - scrollEventThrottle: 1, - style: containerStyle, - onLayout: this._onLayout, - onMomentumScrollEnd: this._onMomentumScrollEnd, - onScroll: this._onScrollHandler, - onTouchStart: this._onTouchStart, - onTouchEnd: this._onTouchEnd - }; - } - - render () { - const { data, renderItem, useScrollView } = this.props; - - if (!data || !renderItem) { - return null; - } - - const props = { - ...this._getComponentOverridableProps(), - ...this.props, - ...this._getComponentStaticProps() - }; - - const ScrollViewComponent = typeof useScrollView === 'function' ? useScrollView : AnimatedScrollView; - - return this._needsScrollView() ? ( - - { - this._getCustomData().map((item, index) => { - return this._renderItem({ item, index }); - }) - } - - ) : ( - - ); - } -} diff --git a/src/carousel/Carousel.tsx b/src/carousel/Carousel.tsx new file mode 100644 index 000000000..c3d9a8ee3 --- /dev/null +++ b/src/carousel/Carousel.tsx @@ -0,0 +1,1367 @@ +import React, { PropsWithChildren } from 'react'; +import { + Animated, + FlatList, + I18nManager, + Platform, + ScrollView, + View, + StyleProp, + NativeSyntheticEvent, + NativeScrollEvent, + LayoutChangeEvent, + GestureResponderEvent, + ViewStyle, +} from 'react-native'; +import shallowCompare from 'react-addons-shallow-compare'; +import { + defaultScrollInterpolator, + stackScrollInterpolator, + tinderScrollInterpolator, + defaultAnimatedStyles, + shiftAnimatedStyles, + stackAnimatedStyles, + tinderAnimatedStyles, +} from '../utils/animations'; +import type { CarouselProps, CarouselState } from './types'; + +// Metro doesn't support dynamic imports - i.e. require() done in the component itself +// But at the same time the following import will fail on Snack... +// TODO: find a way to get React Native's version without having to assume the file path +// import RN_PACKAGE from '../../../react-native/package.json'; + +const IS_ANDROID = Platform.OS === 'android'; + +// Native driver for scroll events +// See: https://facebook.github.io/react-native/blog/2017/02/14/using-native-driver-for-animated.html +const AnimatedFlatList = FlatList + ? Animated.createAnimatedComponent(FlatList) + : null; +const AnimatedScrollView = Animated.createAnimatedComponent(ScrollView); + +// React Native automatically handles RTL layouts; unfortunately, it's buggy with horizontal ScrollView +// See https://github.com/facebook/react-native/issues/11960 +// NOTE: the following variable is not declared in the constructor +// otherwise it is undefined at init, which messes with custom indexes +const IS_RTL = I18nManager.isRTL; + +export class Carousel extends React.Component< + CarouselProps, + CarouselState +> { + static defaultProps = { + activeSlideAlignment: 'center', + activeSlideOffset: 20, + apparitionDelay: 0, + autoplay: false, + autoplayDelay: 1000, + autoplayInterval: 3000, + callbackOffsetMargin: 5, + containerCustomStyle: {}, + contentContainerCustomStyle: {}, + enableSnap: true, + firstItem: 0, + hasParallaxImages: false, + inactiveSlideOpacity: 0.7, + inactiveSlideScale: 0.9, + inactiveSlideShift: 0, + layout: 'default', + loop: false, + loopClonesPerSide: 3, + scrollEnabled: true, + slideStyle: {}, + shouldOptimizeUpdates: true, + useExperimentalSnap: false, + useScrollView: !AnimatedFlatList, + vertical: false, + }; + + _activeItem: number; + _onScrollActiveItem: number; + _previousFirstItem: number; + _previousItemsLength: number; + _mounted: boolean; + _positions: { start: number; end: number }[]; + _currentScrollOffset: number; + _scrollEnabled: boolean; + + _initTimeout?: ReturnType; + _apparitionTimeout?: ReturnType; + _hackSlideAnimationTimeout?: ReturnType; + _enableAutoplayTimeout?: ReturnType; + _autoplayTimeout?: ReturnType; + _snapNoMomentumTimeout?: ReturnType; + _androidRepositioningTimeout?: ReturnType; + _autoplayInterval?: ReturnType; + + _scrollPos?: Animated.Value; + + _onScrollHandler?: (...args: any[]) => void; + + _carouselRef?: ScrollView | FlatList | null; + + _autoplaying?: boolean; + _autoplay?: boolean; + + _onLayoutInitDone?: boolean; + + constructor(props: CarouselProps) { + super(props); + + this.state = { + hideCarousel: !!props.apparitionDelay, + interpolators: [], + }; + + // this._RNVersionCode = this._getRNVersionCode(); + + // The following values are not stored in the state because 'setState()' is asynchronous + // and this results in an absolutely crappy behavior on Android while swiping (see #156) + const initialActiveItem = this._getFirstItem(props.firstItem); + this._activeItem = initialActiveItem; + this._onScrollActiveItem = initialActiveItem; + this._previousFirstItem = initialActiveItem; + this._previousItemsLength = initialActiveItem; + + this._mounted = false; + this._positions = []; + this._currentScrollOffset = 0; // Store ScrollView's scroll position + this._scrollEnabled = props.scrollEnabled !== false; + + this._getCellRendererComponent = this._getCellRendererComponent.bind(this); + this._getItemLayout = this._getItemLayout.bind(this); + this._getKeyExtractor = this._getKeyExtractor.bind(this); + this._onLayout = this._onLayout.bind(this); + this._onScroll = this._onScroll.bind(this); + this._onMomentumScrollEnd = this._onMomentumScrollEnd.bind(this); + this._onTouchStart = this._onTouchStart.bind(this); + this._onTouchEnd = this._onTouchEnd.bind(this); + this._renderItem = this._renderItem.bind(this); + + // WARNING: call this AFTER binding _onScroll + this._setScrollHandler(props); + + // Display warnings + this._displayWarnings(props); + } + + componentDidMount() { + const { apparitionDelay, autoplay, firstItem } = this.props; + + this._mounted = true; + this._initPositionsAndInterpolators(); + + // Without 'requestAnimationFrame' or a `0` timeout, images will randomly not be rendered on Android... + this._initTimeout = setTimeout(() => { + if (!this._mounted) { + return; + } + + const apparitionCallback = () => { + if (apparitionDelay) { + this.setState({ hideCarousel: false }); + } + if (autoplay) { + this.startAutoplay(); + } + }; + + // FlatList will use its own built-in prop `initialScrollIndex` + if (this._needsScrollView()) { + const _firstItem = this._getFirstItem(firstItem); + this._snapToItem(_firstItem, false, false, true); + // this._hackActiveSlideAnimation(_firstItem); + } + + if (apparitionDelay) { + this._apparitionTimeout = setTimeout(() => { + apparitionCallback(); + }, apparitionDelay); + } else { + apparitionCallback(); + } + }, 1); + } + + shouldComponentUpdate( + nextProps: CarouselProps, + nextState: any + ): boolean { + if (this.props.shouldOptimizeUpdates === false) { + return true; + } else { + return shallowCompare(this, nextProps, nextState); + } + } + + componentDidUpdate(prevProps: CarouselProps) { + const { interpolators } = this.state; + const { + firstItem, + itemHeight, + itemWidth, + scrollEnabled, + sliderHeight, + sliderWidth, + } = this.props; + const itemsLength = this._getCustomDataLength(this.props); + + if (!itemsLength) { + return; + } + + const nextFirstItem = this._getFirstItem(firstItem, this.props); + let nextActiveItem = + typeof this._activeItem !== 'undefined' + ? this._activeItem + : nextFirstItem; + + const hasNewSliderWidth = + sliderWidth && sliderWidth !== prevProps.sliderWidth; + const hasNewSliderHeight = + sliderHeight && sliderHeight !== prevProps.sliderHeight; + const hasNewItemWidth = itemWidth && itemWidth !== prevProps.itemWidth; + const hasNewItemHeight = itemHeight && itemHeight !== prevProps.itemHeight; + const hasNewScrollEnabled = scrollEnabled !== prevProps.scrollEnabled; + + // Prevent issues with dynamically removed items + if (nextActiveItem > itemsLength - 1) { + nextActiveItem = itemsLength - 1; + } + + // Handle changing scrollEnabled independent of user -> carousel interaction + if (hasNewScrollEnabled) { + this._setScrollEnabled(scrollEnabled); + } + + if ( + interpolators.length !== itemsLength || + hasNewSliderWidth || + hasNewSliderHeight || + hasNewItemWidth || + hasNewItemHeight + ) { + this._activeItem = nextActiveItem; + this._previousItemsLength = itemsLength; + + this._initPositionsAndInterpolators(this.props); + + // Handle scroll issue when dynamically removing items (see #133) + // This also fixes first item's active state on Android + // Because 'initialScrollIndex' apparently doesn't trigger scroll + if (this._previousItemsLength > itemsLength) { + this._hackActiveSlideAnimation(nextActiveItem); + } + + if ( + hasNewSliderWidth || + hasNewSliderHeight || + hasNewItemWidth || + hasNewItemHeight + ) { + this._snapToItem(nextActiveItem, false, false, true); + } + } else if ( + nextFirstItem !== this._previousFirstItem && + nextFirstItem !== this._activeItem + ) { + this._activeItem = nextFirstItem; + this._previousFirstItem = nextFirstItem; + this._snapToItem(nextFirstItem, false, true, true); + } + + if (this.props.onScroll !== prevProps.onScroll) { + this._setScrollHandler(this.props); + } + } + + componentWillUnmount() { + this._mounted = false; + this.stopAutoplay(); + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._initTimeout); + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._apparitionTimeout); + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._hackSlideAnimationTimeout); + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._enableAutoplayTimeout); + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._autoplayTimeout); + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._snapNoMomentumTimeout); + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._androidRepositioningTimeout); + } + + get realIndex() { + return this._activeItem; + } + + get currentIndex() { + return this._getDataIndex(this._activeItem); + } + + get currentScrollPosition() { + return this._currentScrollOffset; + } + + _setScrollHandler(props: CarouselProps) { + // Native driver for scroll events + const scrollEventConfig = { + listener: this._onScroll, + useNativeDriver: true, + }; + this._scrollPos = new Animated.Value(0); + const argMapping = props.vertical + ? [{ nativeEvent: { contentOffset: { y: this._scrollPos } } }] + : [{ nativeEvent: { contentOffset: { x: this._scrollPos } } }]; + + // @ts-expect-error Let's ignore for now that trick + if (props.onScroll && Array.isArray(props.onScroll._argMapping)) { + // Because of a react-native issue https://github.com/facebook/react-native/issues/13294 + argMapping.pop(); + // @ts-expect-error Let's ignore for now that trick + const [argMap] = props.onScroll._argMapping; + if (argMap && argMap.nativeEvent && argMap.nativeEvent.contentOffset) { + // Shares the same animated value passed in props + this._scrollPos = + argMap.nativeEvent.contentOffset.x || + argMap.nativeEvent.contentOffset.y || + this._scrollPos; + } + // @ts-expect-error Let's ignore for now that trick + argMapping.push(...props.onScroll._argMapping); + } + this._onScrollHandler = Animated.event( + argMapping, + scrollEventConfig + ); + } + + // This will return a future-proof version code number compatible with semantic versioning + // Examples: 0.59.3 -> 5903 / 0.61.4 -> 6104 / 0.62.12 -> 6212 / 1.0.2 -> 10002 + // _getRNVersionCode () { + // const version = RN_PACKAGE && RN_PACKAGE.version; + // if (!version) { + // return null; + // } + // const versionSplit = version.split('.'); + // if (!versionSplit || !versionSplit.length) { + // return null; + // } + // return versionSplit[0] * 10000 + + // (typeof versionSplit[1] !== 'undefined' ? versionSplit[1] * 100 : 0) + + // (typeof versionSplit[2] !== 'undefined' ? versionSplit[2] * 1 : 0); + // } + + _displayWarnings(props: CarouselProps = this.props) { + const pluginName = 'react-native-snap-carousel'; + const removedProps = [ + 'activeAnimationType', + 'activeAnimationOptions', + 'enableMomentum', + 'lockScrollTimeoutDuration', + 'lockScrollWhileSnapping', + 'onBeforeSnapToItem', + 'swipeThreshold', + ] as const; + + // if (this._RNVersionCode && this._RNVersionCode < 5800) { + // console.error( + // `${pluginName}: Version 4+ of the plugin is based on React Native props that were introduced in version 0.58. ` + + // 'Please downgrade to version 3.x or update your version of React Native.' + // ); + // } + if (!props.vertical && (!props.sliderWidth || !props.itemWidth)) { + console.error( + `${pluginName}: You need to specify both 'sliderWidth' and 'itemWidth' for horizontal carousels` + ); + } + if (props.vertical && (!props.sliderHeight || !props.itemHeight)) { + console.error( + `${pluginName}: You need to specify both 'sliderHeight' and 'itemHeight' for vertical carousels` + ); + } + + removedProps.forEach((removedProp) => { + if (removedProp in props) { + console.warn( + `${pluginName}: Prop ${removedProp} has been removed in version 4 of the plugin` + ); + } + }); + } + + _needsScrollView() { + const { useScrollView } = this.props; + // Android's cell renderer is buggy and has a stange overflow + // TODO: a workaround might be to pass the custom animated styles directly to it + return IS_ANDROID + ? useScrollView || + !AnimatedFlatList || + this._shouldUseStackLayout() || + this._shouldUseTinderLayout() + : useScrollView || !AnimatedFlatList; + } + + _needsRTLAdaptations() { + const { vertical } = this.props; + return IS_RTL && IS_ANDROID && !vertical; + } + + _enableLoop() { + const { data, enableSnap, loop } = this.props; + return enableSnap && loop && data && data.length && data.length > 1; + } + + _shouldAnimateSlides(props: CarouselProps = this.props) { + const { + inactiveSlideOpacity, + inactiveSlideScale, + scrollInterpolator, + slideInterpolatedStyle, + } = props; + return ( + inactiveSlideOpacity < 1 || + inactiveSlideScale < 1 || + !!scrollInterpolator || + !!slideInterpolatedStyle || + this._shouldUseShiftLayout() || + this._shouldUseStackLayout() || + this._shouldUseTinderLayout() + ); + } + + _shouldUseShiftLayout() { + const { inactiveSlideShift, layout } = this.props; + return layout === 'default' && inactiveSlideShift !== 0; + } + + _shouldUseStackLayout() { + return this.props.layout === 'stack'; + } + + _shouldUseTinderLayout() { + return this.props.layout === 'tinder'; + } + + _shouldRepositionScroll(index: number) { + const { data, enableSnap, loopClonesPerSide } = this.props; + const dataLength = data && data.length; + if ( + !enableSnap || + !dataLength || + !this._enableLoop() || + (index >= loopClonesPerSide && index < dataLength + loopClonesPerSide) + ) { + return false; + } + return true; + } + + _roundNumber(num: number, decimals = 1) { + // https://stackoverflow.com/a/41716722/ + const rounder = Math.pow(10, decimals); + return Math.round((num + Number.EPSILON) * rounder) / rounder; + } + + _isMultiple(x: number, y: number) { + // This prevents Javascript precision issues: https://stackoverflow.com/a/58440614/ + // Required because Android viewport size can return pretty complicated decimals numbers + return Math.round(Math.round(x / y) / (1 / y)) === Math.round(x); + } + + _getCustomData(props: CarouselProps = this.props) { + const { data, loopClonesPerSide } = props; + const dataLength = data && data.length; + + if (!dataLength) { + return []; + } + + if (!this._enableLoop()) { + return data; + } + + let previousItems = []; + let nextItems = []; + + if (loopClonesPerSide > dataLength) { + const dataMultiplier = Math.floor(loopClonesPerSide / dataLength); + const remainder = loopClonesPerSide % dataLength; + + for (let i = 0; i < dataMultiplier; i++) { + previousItems.push(...data); + nextItems.push(...data); + } + + previousItems.unshift(...data.slice(-remainder)); + nextItems.push(...data.slice(0, remainder)); + } else { + previousItems = data.slice(-loopClonesPerSide); + nextItems = data.slice(0, loopClonesPerSide); + } + + return previousItems.concat(data, nextItems); + } + + _getCustomDataLength(props: CarouselProps = this.props) { + const { data, loopClonesPerSide } = props; + const dataLength = data && data.length; + + if (!dataLength) { + return 0; + } + + return this._enableLoop() ? dataLength + 2 * loopClonesPerSide : dataLength; + } + + _getCustomIndex(index: number, props: CarouselProps = this.props) { + const itemsLength = this._getCustomDataLength(props); + + if (!itemsLength || typeof index === 'undefined') { + return 0; + } + + return this._needsRTLAdaptations() ? itemsLength - index - 1 : index; + } + + _getDataIndex(index: number) { + const { data, loopClonesPerSide } = this.props; + const dataLength = data && data.length; + + if (!this._enableLoop() || !dataLength) { + return index; + } + + if (index >= dataLength + loopClonesPerSide) { + return loopClonesPerSide > dataLength + ? (index - loopClonesPerSide) % dataLength + : index - dataLength - loopClonesPerSide; + } else if (index < loopClonesPerSide) { + // TODO: is there a simpler way of determining the interpolated index? + if (loopClonesPerSide > dataLength) { + const baseDataIndexes = []; + const dataIndexes = []; + const dataMultiplier = Math.floor(loopClonesPerSide / dataLength); + const remainder = loopClonesPerSide % dataLength; + + for (let i = 0; i < dataLength; i++) { + baseDataIndexes.push(i); + } + + for (let j = 0; j < dataMultiplier; j++) { + dataIndexes.push(...baseDataIndexes); + } + + dataIndexes.unshift(...baseDataIndexes.slice(-remainder)); + return dataIndexes[index]; + } else { + return index + dataLength - loopClonesPerSide; + } + } else { + return index - loopClonesPerSide; + } + } + + // Used with `snapToItem()` and 'PaginationDot' + _getPositionIndex(index: number) { + const { loop, loopClonesPerSide } = this.props; + return loop ? index + loopClonesPerSide : index; + } + + _getSnapOffsets(props: CarouselProps = this.props) { + const offset = this._getItemMainDimension(); + return [...Array(this._getCustomDataLength(props))].map((_, i) => { + return i * offset; + }); + } + + _getFirstItem(index: number, props: CarouselProps = this.props) { + const { loopClonesPerSide } = props; + const itemsLength = this._getCustomDataLength(props); + + if (!itemsLength || index > itemsLength - 1 || index < 0) { + return 0; + } + + return this._enableLoop() ? index + loopClonesPerSide : index; + } + + _getWrappedRef() { + // Starting with RN 0.62, we should no longer call `getNode()` on the ref of an Animated component + if ( + this._carouselRef && + ((this._needsScrollView() && + (this._carouselRef as ScrollView).scrollTo) || + (!this._needsScrollView() && + (this._carouselRef as FlatList).scrollToOffset)) + ) { + return this._carouselRef; + } + // https://github.com/facebook/react-native/issues/10635 + // https://stackoverflow.com/a/48786374/8412141 + return ( + this._carouselRef && + // @ts-expect-error This is for before 0.62 + this._carouselRef.getNode && + // @ts-expect-error This is for before 0.62 + this._carouselRef.getNode() + ); + } + + _getScrollEnabled() { + return this._scrollEnabled; + } + + _setScrollEnabled(scrollEnabled = true) { + const wrappedRef = this._getWrappedRef(); + + if (!wrappedRef || !wrappedRef.setNativeProps) { + return; + } + + // 'setNativeProps()' is used instead of 'setState()' because the latter + // really takes a toll on Android behavior when momentum is disabled + wrappedRef.setNativeProps({ scrollEnabled }); + this._scrollEnabled = scrollEnabled; + } + + _getItemMainDimension() { + return this.props.vertical ? this.props.itemHeight : this.props.itemWidth; + } + + _getItemScrollOffset(index: number) { + return ( + this._positions && this._positions[index] && this._positions[index].start + ); + } + + _getItemLayout(_: unknown, index: number) { + const itemMainDimension = this._getItemMainDimension(); + return { + index, + length: itemMainDimension, + offset: itemMainDimension * index, // + this._getContainerInnerMargin() + }; + } + + // This will allow us to have a proper zIndex even with a FlatList + // https://github.com/facebook/react-native/issues/18616#issuecomment-389444165 + _getCellRendererComponent({ + children, + index, + style, + ...props + }: PropsWithChildren<{ index: number; style: StyleProp }>) { + const cellStyle = [ + style, + !IS_ANDROID ? { zIndex: this._getCustomDataLength() - index } : {}, + ]; + + return ( + + {children} + + ); + } + + _getKeyExtractor(_: unknown, index: number) { + return this._needsScrollView() + ? `scrollview-item-${index}` + : `flatlist-item-${index}`; + } + + _getScrollOffset(event: NativeSyntheticEvent) { + const { vertical } = this.props; + return ( + (event && + event.nativeEvent && + event.nativeEvent.contentOffset && + event.nativeEvent.contentOffset[vertical ? 'y' : 'x']) || + 0 + ); + } + + _getContainerInnerMargin(opposite = false) { + const { activeSlideAlignment } = this.props; + + if ( + (activeSlideAlignment === 'start' && !opposite) || + (activeSlideAlignment === 'end' && opposite) + ) { + return 0; + } else if ( + (activeSlideAlignment === 'end' && !opposite) || + (activeSlideAlignment === 'start' && opposite) + ) { + return this.props.vertical + ? this.props.sliderHeight - this.props.itemHeight + : this.props.sliderWidth - this.props.itemWidth; + } else { + return this.props.vertical + ? (this.props.sliderHeight - this.props.itemHeight) / 2 + : (this.props.sliderWidth - this.props.itemWidth) / 2; + } + } + + _getActiveSlideOffset() { + const { activeSlideOffset } = this.props; + const itemMainDimension = this._getItemMainDimension(); + const minOffset = 10; + // Make sure activeSlideOffset never prevents the active area from being at least 10 px wide + return itemMainDimension / 2 - activeSlideOffset >= minOffset + ? activeSlideOffset + : minOffset; + } + + _getActiveItem(offset: number) { + const itemMainDimension = this._getItemMainDimension(); + const center = offset + itemMainDimension / 2; + const activeSlideOffset = this._getActiveSlideOffset(); + const lastIndex = this._positions.length - 1; + let itemIndex; + + if (offset <= 0) { + return 0; + } + + if ( + this._positions[lastIndex] && + offset >= this._positions[lastIndex].start + ) { + return lastIndex; + } + + for (let i = 0; i < this._positions.length; i++) { + const { start, end } = this._positions[i]; + if ( + center + activeSlideOffset >= start && + center - activeSlideOffset <= end + ) { + itemIndex = i; + break; + } + } + + return itemIndex || 0; + } + + _getSlideInterpolatedStyle(index: number, animatedValue: Animated.Value) { + const { layoutCardOffset, slideInterpolatedStyle } = this.props; + + if (slideInterpolatedStyle) { + return slideInterpolatedStyle(index, animatedValue, this.props); + } else if (this._shouldUseTinderLayout()) { + return tinderAnimatedStyles( + index, + animatedValue, + this.props, + layoutCardOffset + ); + } else if (this._shouldUseStackLayout()) { + return stackAnimatedStyles( + index, + animatedValue, + this.props, + layoutCardOffset + ); + } else if (this._shouldUseShiftLayout()) { + return shiftAnimatedStyles(index, animatedValue, this.props); + } else { + return defaultAnimatedStyles(index, animatedValue, this.props); + } + } + + _initPositionsAndInterpolators(props: CarouselProps = this.props) { + const { data, scrollInterpolator } = props; + const itemMainDimension = this._getItemMainDimension(); + + if (!data || !data.length) { + return; + } + + const interpolators: Animated.Value | Animated.AnimatedInterpolation[] = []; + this._positions = []; + + this._getCustomData(props).forEach((_itemData, index) => { + const _index = this._getCustomIndex(index, props); + let animatedValue; + + this._positions[index] = { + start: index * itemMainDimension, + end: index * itemMainDimension + itemMainDimension, + }; + + if (!this._shouldAnimateSlides(props)) { + animatedValue = new Animated.Value(1); + } else { + let interpolator; + + if (scrollInterpolator) { + interpolator = scrollInterpolator(_index, props); + } else if (this._shouldUseStackLayout()) { + interpolator = stackScrollInterpolator(_index, props); + } else if (this._shouldUseTinderLayout()) { + interpolator = tinderScrollInterpolator(_index, props); + } + + if ( + !interpolator || + !interpolator.inputRange || + !interpolator.outputRange + ) { + interpolator = defaultScrollInterpolator(_index, props); + } + + animatedValue = this._scrollPos!.interpolate({ + ...interpolator, + extrapolate: 'clamp', + }); + } + + interpolators.push(animatedValue); + }); + + this.setState({ interpolators }); + } + + _hackActiveSlideAnimation(index: number, scrollValue = 1) { + const offset = this._getItemScrollOffset(index); + + if (!this._mounted || !this._carouselRef || typeof offset === 'undefined') { + return; + } + + const multiplier = this._currentScrollOffset === 0 ? 1 : -1; + const scrollDelta = scrollValue * multiplier; + + this._scrollTo({ offset: offset + scrollDelta, animated: false }); + + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._hackSlideAnimationTimeout); + this._hackSlideAnimationTimeout = setTimeout(() => { + this._scrollTo({ offset, animated: false }); + }, 1); // works randomly when set to '0' + } + + _repositionScroll(index: number, animated = false) { + const { data, loopClonesPerSide } = this.props; + const dataLength = data && data.length; + + if (typeof index === 'undefined' || !this._shouldRepositionScroll(index)) { + return; + } + + let repositionTo = index; + + if (index >= dataLength + loopClonesPerSide) { + repositionTo = index - dataLength; + } else if (index < loopClonesPerSide) { + repositionTo = index + dataLength; + } + + this._snapToItem(repositionTo, animated, false); + } + + _scrollTo({ + offset, + index, + animated = true, + }: { + offset: number; + index?: number; + animated: boolean; + }) { + const { vertical } = this.props; + const wrappedRef = this._getWrappedRef(); + if ( + !this._mounted || + !wrappedRef || + (typeof offset === 'undefined' && typeof index === 'undefined') + ) { + return; + } + + let scrollToOffset; + if (typeof index !== 'undefined') { + scrollToOffset = this._getItemScrollOffset(index); + } else { + scrollToOffset = offset; + } + + if (typeof scrollToOffset === 'undefined') { + return; + } + + const options = this._needsScrollView() + ? { + x: vertical ? 0 : offset, + y: vertical ? offset : 0, + animated, + } + : { + offset, + animated, + }; + + if (this._needsScrollView()) { + wrappedRef.scrollTo(options); + } else { + wrappedRef.scrollToOffset(options); + } + } + + _onTouchStart(event: GestureResponderEvent) { + const { onTouchStart } = this.props; + + // `onTouchStart` is fired even when `scrollEnabled` is set to `false` + if (this._getScrollEnabled() !== false && this._autoplaying) { + this.pauseAutoPlay(); + } + + onTouchStart && onTouchStart(event); + } + + _onTouchEnd(event: GestureResponderEvent) { + const { onTouchEnd } = this.props; + + if ( + this._getScrollEnabled() !== false && + this._autoplay && + !this._autoplaying + ) { + // This event is buggy on Android, so a fallback is provided in _onMomentumScrollEnd() + this.startAutoplay(); + } + + onTouchEnd && onTouchEnd(event); + } + + _onScroll(event: NativeSyntheticEvent) { + const { onScroll, onScrollIndexChanged } = this.props; + const scrollOffset = event + ? this._getScrollOffset(event) + : this._currentScrollOffset; + const nextActiveItem = this._getActiveItem(scrollOffset); + + this._currentScrollOffset = scrollOffset; + + if (nextActiveItem !== this._onScrollActiveItem) { + this._onScrollActiveItem = nextActiveItem; + onScrollIndexChanged && + onScrollIndexChanged(this._getDataIndex(nextActiveItem)); + } + + if (typeof onScroll === 'function' && event) { + onScroll(event); + } + } + + _onMomentumScrollEnd(event: NativeSyntheticEvent) { + const { autoplayDelay, onMomentumScrollEnd, onSnapToItem } = this.props; + const scrollOffset = event + ? this._getScrollOffset(event) + : this._currentScrollOffset; + const nextActiveItem = this._getActiveItem(scrollOffset); + const hasSnapped = this._isMultiple( + scrollOffset, + this.props.vertical ? this.props.itemHeight : this.props.itemWidth + ); + + // WARNING: everything in this condition will probably need to be called on _snapToItem as well because: + // 1. `onMomentumScrollEnd` won't be called if the scroll isn't animated + // 2. `onMomentumScrollEnd` won't be called at all on Android when scrolling programmatically + if (nextActiveItem !== this._activeItem) { + this._activeItem = nextActiveItem; + onSnapToItem && onSnapToItem(this._getDataIndex(nextActiveItem)); + + if (hasSnapped) { + this._repositionScroll(nextActiveItem); + } + } + + onMomentumScrollEnd && onMomentumScrollEnd(event); + + // The touchEnd event is buggy on Android, so this will serve as a fallback whenever needed + // https://github.com/facebook/react-native/issues/9439 + if (IS_ANDROID && this._autoplay && !this._autoplaying) { + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._enableAutoplayTimeout); + this._enableAutoplayTimeout = setTimeout(() => { + this.startAutoplay(); + }, autoplayDelay); + } + } + + _onLayout(event: LayoutChangeEvent) { + const { onLayout } = this.props; + + // Prevent unneeded actions during the first 'onLayout' (triggered on init) + if (this._onLayoutInitDone) { + this._initPositionsAndInterpolators(); + this._snapToItem(this._activeItem, false, false, true); + } else { + this._onLayoutInitDone = true; + } + + onLayout && onLayout(event); + } + + _snapToItem( + index: number, + animated = true, + fireCallback = true, + forceScrollTo = false + ) { + const { onSnapToItem } = this.props; + const itemsLength = this._getCustomDataLength(); + const wrappedRef = this._getWrappedRef(); + + if (!itemsLength || !wrappedRef) { + return; + } + + if (!index || index < 0) { + index = 0; + } else if (itemsLength > 0 && index >= itemsLength) { + index = itemsLength - 1; + } + + if (index === this._activeItem && !forceScrollTo) { + return; + } + + const offset = this._getItemScrollOffset(index); + + if (offset === undefined) { + return; + } + + this._scrollTo({ offset, animated }); + + // On both platforms, `onMomentumScrollEnd` won't be triggered if the scroll isn't animated + // so we need to trigger the callback manually + // On Android `onMomentumScrollEnd` won't be triggered when scrolling programmatically + // Therefore everything critical needs to be manually called here as well, even though the timing might be off + const requiresManualTrigger = !animated || IS_ANDROID; + if (requiresManualTrigger) { + this._activeItem = index; + + if (fireCallback) { + onSnapToItem && onSnapToItem(this._getDataIndex(index)); + } + + // Repositioning on Android + if (IS_ANDROID && this._shouldRepositionScroll(index)) { + if (animated) { + this._androidRepositioningTimeout = setTimeout(() => { + // Without scroll animation, the behavior is completely buggy... + this._repositionScroll(index, true); + }, 400); // Approximate scroll duration on Android + } else { + this._repositionScroll(index); + } + } + } + } + + startAutoplay() { + const { autoplayInterval, autoplayDelay } = this.props; + this._autoplay = true; + + if (this._autoplaying) { + return; + } + + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._autoplayTimeout); + this._autoplayTimeout = setTimeout(() => { + this._autoplaying = true; + this._autoplayInterval = setInterval(() => { + if (this._autoplaying) { + this.snapToNext(); + } + }, autoplayInterval); + }, autoplayDelay); + } + + pauseAutoPlay() { + this._autoplaying = false; + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._autoplayTimeout); + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._enableAutoplayTimeout); + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearInterval(this._autoplayInterval); + } + + stopAutoplay() { + this._autoplay = false; + this.pauseAutoPlay(); + } + + snapToItem(index: number, animated = true, fireCallback = true) { + if (!index || index < 0) { + index = 0; + } + + const positionIndex = this._getPositionIndex(index); + + if (positionIndex === this._activeItem) { + return; + } + + this._snapToItem(positionIndex, animated, fireCallback); + } + + snapToNext(animated = true, fireCallback = true) { + const itemsLength = this._getCustomDataLength(); + + let newIndex = this._activeItem + 1; + if (newIndex > itemsLength - 1) { + newIndex = 0; + } + this._snapToItem(newIndex, animated, fireCallback); + } + + snapToPrev(animated = true, fireCallback = true) { + const itemsLength = this._getCustomDataLength(); + + let newIndex = this._activeItem - 1; + if (newIndex < 0) { + newIndex = itemsLength - 1; + } + this._snapToItem(newIndex, animated, fireCallback); + } + + // https://github.com/facebook/react-native/issues/1831#issuecomment-231069668 + triggerRenderingHack(offset = 1) { + this._hackActiveSlideAnimation(this._activeItem, offset); + } + + _renderItem({ item, index }: { item: TData; index: number }) { + const { interpolators } = this.state; + const { + hasParallaxImages, + itemWidth, + itemHeight, + keyExtractor, + renderItem, + sliderHeight, + sliderWidth, + slideStyle, + vertical, + } = this.props; + const animatedValue = interpolators && interpolators[index]; + + if (typeof animatedValue === 'undefined') { + return null; + } + + const animate = this._shouldAnimateSlides(); + const Component = animate ? Animated.View : View; + const animatedStyle = animate + ? this._getSlideInterpolatedStyle(index, animatedValue) + : {}; + const dataIndex = this._getDataIndex(index); + + const parallaxProps = hasParallaxImages + ? { + scrollPosition: this._scrollPos, + carouselRef: this._carouselRef, + vertical, + sliderWidth, + sliderHeight, + itemWidth, + itemHeight, + } + : undefined; + + const mainDimension = vertical + ? { height: itemHeight } + : { width: itemWidth }; + const specificProps = this._needsScrollView() + ? { + key: keyExtractor + ? keyExtractor(item, index) + : this._getKeyExtractor(item, index), + } + : {}; + + return ( + + {renderItem({ item, index, dataIndex }, parallaxProps)} + + ); + } + + _getComponentOverridableProps() { + const { hideCarousel } = this.state; + const { loopClonesPerSide } = this.props; + const visibleItems = + Math.ceil( + this.props.vertical + ? this.props.sliderHeight / this.props.itemHeight + : this.props.sliderWidth / this.props.itemWidth + ) + 1; + const initialNumPerSide = this._enableLoop() ? loopClonesPerSide : 2; + const initialNumToRender = visibleItems + initialNumPerSide * 2; + const maxToRenderPerBatch = initialNumToRender + initialNumPerSide * 2; + const windowSize = maxToRenderPerBatch; + + const specificProps = !this._needsScrollView() + ? { + initialNumToRender, + maxToRenderPerBatch, + windowSize, + // updateCellsBatchingPeriod + } + : {}; + + return { + ...specificProps, + automaticallyAdjustContentInsets: false, + decelerationRate: 'fast', + directionalLockEnabled: true, + disableScrollViewPanResponder: false, // If set to `true`, touch events will be triggered too easily + inverted: this._needsRTLAdaptations(), + overScrollMode: 'never', + pinchGestureEnabled: false, + pointerEvents: hideCarousel ? 'none' : 'auto', + // removeClippedSubviews: !this._needsScrollView(), + // renderToHardwareTextureAndroid: true, + scrollsToTop: false, + showsHorizontalScrollIndicator: false, + showsVerticalScrollIndicator: false, + }; + } + + _getComponentStaticProps() { + const { hideCarousel } = this.state; + const { + activeSlideAlignment, + CellRendererComponent, + containerCustomStyle, + contentContainerCustomStyle, + firstItem, + getItemLayout, + keyExtractor, + sliderWidth, + sliderHeight, + style, + useExperimentalSnap, + vertical, + } = this.props; + + const containerStyle = [ + // { overflow: 'hidden' }, + containerCustomStyle || style || {}, + hideCarousel ? { opacity: 0 } : {}, + vertical + ? { height: sliderHeight, flexDirection: 'column' } + : // LTR hack; see https://github.com/facebook/react-native/issues/11960 + // and https://github.com/facebook/react-native/issues/13100#issuecomment-328986423 + { + width: sliderWidth, + flexDirection: this._needsRTLAdaptations() ? 'row-reverse' : 'row', + }, + ]; + + const innerMarginStyle = vertical + ? { + paddingTop: this._getContainerInnerMargin(), + paddingBottom: this._getContainerInnerMargin(true), + } + : { + paddingLeft: this._getContainerInnerMargin(), + paddingRight: this._getContainerInnerMargin(true), + }; + + const contentContainerStyle = [ + !useExperimentalSnap ? innerMarginStyle : {}, + contentContainerCustomStyle || {}, + ]; + + // WARNING: `snapToAlignment` won't work as intended because of the following: + // https://github.com/facebook/react-native/blob/d0871d0a9a373e1d3ac35da46c85c0d0e793116d/React/Views/ScrollView/RCTScrollView.m#L751-L755 + // - Snap points will be off + // - Slide animations will be off + // - Last items won't be set as active (no `onSnapToItem` callback) + // Recommended only with large slides and `activeSlideAlignment` set to `start` for the time being + const snapProps = useExperimentalSnap + ? { + // disableIntervalMomentum: true, // Slide ± one item at a time + snapToAlignment: activeSlideAlignment, + snapToInterval: this._getItemMainDimension(), + } + : { + snapToOffsets: this._getSnapOffsets(), + }; + + // Flatlist specifics + const specificProps = !this._needsScrollView() + ? { + CellRendererComponent: + CellRendererComponent || this._getCellRendererComponent, + getItemLayout: getItemLayout || this._getItemLayout, + initialScrollIndex: this._getFirstItem(firstItem), + keyExtractor: keyExtractor || this._getKeyExtractor, + numColumns: 1, + renderItem: this._renderItem, + } + : {}; + + return { + ...specificProps, + ...snapProps, + ref: (c: any) => { + this._carouselRef = c; + }, + contentContainerStyle: contentContainerStyle, + data: this._getCustomData(), + horizontal: !vertical, + scrollEventThrottle: 1, + style: containerStyle, + onLayout: this._onLayout, + onMomentumScrollEnd: this._onMomentumScrollEnd, + onScroll: this._onScrollHandler, + onTouchStart: this._onTouchStart, + onTouchEnd: this._onTouchEnd, + }; + } + + render() { + const { data, renderItem, useScrollView } = this.props; + + if (!data || !renderItem) { + return null; + } + + const props = { + ...this._getComponentOverridableProps(), + ...this.props, + ...this._getComponentStaticProps(), + }; + + const ScrollViewComponent = + typeof useScrollView === 'function' ? useScrollView : AnimatedScrollView; + + return this._needsScrollView() || !AnimatedFlatList ? ( + + {this._getCustomData().map((item, index) => { + return this._renderItem({ item, index }); + })} + + ) : ( + + ); + } +} + +export default Carousel; diff --git a/src/carousel/types.ts b/src/carousel/types.ts new file mode 100644 index 000000000..a5d3e2a05 --- /dev/null +++ b/src/carousel/types.ts @@ -0,0 +1,89 @@ +import type { + StyleProp, + ViewStyle, + Animated, + NativeScrollEvent, + NativeSyntheticEvent, + FlatListProps, +} from 'react-native'; + +type CarouselBaseProps = { + data: TData[]; + renderItem: ( + baseData: { index: number; dataIndex: number; item: TData }, + parallaxData?: any + ) => any; + activeSlideAlignment: 'center' | 'end' | 'start'; + activeSlideOffset: number; + apparitionDelay: number; + autoplay: boolean; + autoplayDelay: number; + autoplayInterval: number; + callbackOffsetMargin: number; + containerCustomStyle: StyleProp; + contentContainerCustomStyle: StyleProp; + enableSnap: boolean; + firstItem: number; + hasParallaxImages: boolean; + inactiveSlideOpacity: number; + inactiveSlideScale: number; + inactiveSlideShift: number; + layout: 'default' | 'stack' | 'tinder'; + layoutCardOffset: number; + loop: boolean; + loopClonesPerSide: number; + scrollEnabled: boolean; + // TODO: check real type later + scrollInterpolator: (index: number, props: CarouselBaseProps) => any; + // TODO: check real type later + slideInterpolatedStyle: ( + index: number, + animatedValue: Animated.Value, + props: CarouselBaseProps + ) => any; + slideStyle?: StyleProp; + shouldOptimizeUpdates: boolean; + useExperimentalSnap: boolean; + // TODO: check real type later + useScrollView: boolean | React.ComponentType; + onScroll?: (event: NativeSyntheticEvent) => void; + onScrollIndexChanged?: (index: number) => void; + onSnapToItem?: (index: number) => void; +}; + +type InheritedPropsFromFlatlist = Pick< + FlatListProps, + | 'onTouchStart' + | 'onTouchEnd' + | 'onMomentumScrollEnd' + | 'onLayout' + | 'keyExtractor' + | 'CellRendererComponent' + | 'getItemLayout' + | 'style' +>; + +type VerticalCarouselProps = { + vertical: true; + itemWidth?: number; + itemHeight: number; + sliderWidth?: number; + sliderHeight: number; +}; + +type HorizontalCarouselProps = { + vertical?: false; + itemWidth: number; + itemHeight?: number; + sliderWidth: number; + sliderHeight?: number; +}; + +export type CarouselProps = CarouselBaseProps & + (VerticalCarouselProps | HorizontalCarouselProps) & + InheritedPropsFromFlatlist; + +export type CarouselState = { + hideCarousel: boolean; + interpolators: any[]; +}; diff --git a/src/index.js b/src/index.ts similarity index 67% rename from src/index.js rename to src/index.ts index d93725329..5806bbbce 100644 --- a/src/index.js +++ b/src/index.ts @@ -3,4 +3,10 @@ import Pagination from './pagination/Pagination'; import ParallaxImage from './parallaximage/ParallaxImage'; import { getInputRangeFromIndexes } from './utils/animations'; -export { Carousel as default, Pagination, ParallaxImage, getInputRangeFromIndexes }; +export { + Carousel as default, + Carousel, + Pagination, + ParallaxImage, + getInputRangeFromIndexes, +}; diff --git a/src/pagination/Pagination.js b/src/pagination/Pagination.js deleted file mode 100644 index 30f817125..000000000 --- a/src/pagination/Pagination.js +++ /dev/null @@ -1,167 +0,0 @@ -import React, { PureComponent } from 'react'; -import { I18nManager, Platform, View, ViewPropTypes } from 'react-native'; -import PropTypes from 'prop-types'; -import PaginationDot from './PaginationDot'; -import styles from './Pagination.style'; - -const IS_IOS = Platform.OS === 'ios'; -const IS_RTL = I18nManager.isRTL; - -export default class Pagination extends PureComponent { - - static propTypes = { - activeDotIndex: PropTypes.number.isRequired, - dotsLength: PropTypes.number.isRequired, - activeOpacity: PropTypes.number, - carouselRef: PropTypes.object, - containerStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, - dotColor: PropTypes.string, - dotContainerStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, - dotElement: PropTypes.element, - dotStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, - inactiveDotColor: PropTypes.string, - inactiveDotElement: PropTypes.element, - inactiveDotOpacity: PropTypes.number, - inactiveDotScale: PropTypes.number, - inactiveDotStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, - renderDots: PropTypes.func, - tappableDots: PropTypes.bool, - vertical: PropTypes.bool, - accessibilityLabel: PropTypes.string, - animatedDuration: PropTypes.number, - animatedFriction: PropTypes.number, - animatedTension: PropTypes.number, - delayPressInDot: PropTypes.number - }; - - static defaultProps = { - inactiveDotOpacity: 0.5, - inactiveDotScale: 0.5, - tappableDots: false, - vertical: false, - animatedDuration: 250, - animatedFriction: 4, - animatedTension: 50, - delayPressInDot: 0 - } - - constructor (props) { - super(props); - - // Warnings - if ((props.dotColor && !props.inactiveDotColor) || (!props.dotColor && props.inactiveDotColor)) { - console.warn( - 'react-native-snap-carousel | Pagination: ' + - 'You need to specify both `dotColor` and `inactiveDotColor`' - ); - } - if ((props.dotElement && !props.inactiveDotElement) || (!props.dotElement && props.inactiveDotElement)) { - console.warn( - 'react-native-snap-carousel | Pagination: ' + - 'You need to specify both `dotElement` and `inactiveDotElement`' - ); - } - if (props.tappableDots && props.carouselRef === undefined) { - console.warn( - 'react-native-snap-carousel | Pagination: ' + - 'You must specify prop `carouselRef` when setting `tappableDots` to `true`' - ); - } - } - - _needsRTLAdaptations () { - const { vertical } = this.props; - return IS_RTL && !IS_IOS && !vertical; - } - - get _activeDotIndex () { - const { activeDotIndex, dotsLength } = this.props; - return this._needsRTLAdaptations() ? dotsLength - activeDotIndex - 1 : activeDotIndex; - } - - get dots () { - const { - activeOpacity, - carouselRef, - dotsLength, - dotColor, - dotContainerStyle, - dotElement, - dotStyle, - inactiveDotColor, - inactiveDotElement, - inactiveDotOpacity, - inactiveDotScale, - inactiveDotStyle, - renderDots, - tappableDots, - animatedDuration, - animatedFriction, - animatedTension, - delayPressInDot - } = this.props; - - if (renderDots) { - return renderDots(this._activeDotIndex, dotsLength, this); - } - - const DefaultDot = ; - - const dots = [...Array(dotsLength).keys()].map(i => { - const isActive = i === this._activeDotIndex; - return React.cloneElement( - (isActive ? dotElement : inactiveDotElement) || DefaultDot, - { - key: `pagination-dot-${i}`, - active: isActive, - index: i - } - ); - }); - - return dots; - } - - render () { - const { dotsLength, containerStyle, vertical, accessibilityLabel } = this.props; - - if (!dotsLength || dotsLength < 2) { - return false; - } - - const style = [ - styles.sliderPagination, - { flexDirection: vertical ? - 'column' : - (this._needsRTLAdaptations() ? 'row-reverse' : 'row') - }, - containerStyle || {} - ]; - - return ( - - { this.dots } - - ); - } -} diff --git a/src/pagination/Pagination.style.js b/src/pagination/Pagination.style.js deleted file mode 100644 index 4911924f3..000000000 --- a/src/pagination/Pagination.style.js +++ /dev/null @@ -1,24 +0,0 @@ -import { StyleSheet } from 'react-native'; - -const DEFAULT_DOT_SIZE = 7; -const DEFAULT_DOT_COLOR = 'rgba(0, 0, 0, 0.75)'; - -export default StyleSheet.create({ - sliderPagination: { - alignItems: 'center', - justifyContent: 'center', - paddingHorizontal: 20, - paddingVertical: 30 - }, - sliderPaginationDotContainer: { - alignItems: 'center', - justifyContent: 'center', - marginHorizontal: 8 - }, - sliderPaginationDot: { - width: DEFAULT_DOT_SIZE, - height: DEFAULT_DOT_SIZE, - borderRadius: DEFAULT_DOT_SIZE / 2, - backgroundColor: DEFAULT_DOT_COLOR - } -}); diff --git a/src/pagination/Pagination.style.ts b/src/pagination/Pagination.style.ts new file mode 100644 index 000000000..b2c20fc62 --- /dev/null +++ b/src/pagination/Pagination.style.ts @@ -0,0 +1,24 @@ +import { StyleSheet } from 'react-native'; + +const DEFAULT_DOT_SIZE = 7; +const DEFAULT_DOT_COLOR = 'rgba(0, 0, 0, 0.75)'; + +export default StyleSheet.create({ + sliderPagination: { + alignItems: 'center', + justifyContent: 'center', + paddingHorizontal: 20, + paddingVertical: 30, + }, + sliderPaginationDotContainer: { + alignItems: 'center', + justifyContent: 'center', + marginHorizontal: 8, + }, + sliderPaginationDot: { + width: DEFAULT_DOT_SIZE, + height: DEFAULT_DOT_SIZE, + borderRadius: DEFAULT_DOT_SIZE / 2, + backgroundColor: DEFAULT_DOT_COLOR, + }, +}); diff --git a/src/pagination/Pagination.tsx b/src/pagination/Pagination.tsx new file mode 100644 index 000000000..953f5998f --- /dev/null +++ b/src/pagination/Pagination.tsx @@ -0,0 +1,194 @@ +import React, { PureComponent, ReactElement } from 'react'; +import { + I18nManager, + Platform, + View, + StyleProp, + ViewStyle, +} from 'react-native'; +import PaginationDot from './PaginationDot'; +import styles from './Pagination.style'; +import type Carousel from 'src/carousel/Carousel'; + +const IS_IOS = Platform.OS === 'ios'; +const IS_RTL = I18nManager.isRTL; + +type PaginationProps = { + activeDotIndex: number; + dotsLength: number; + activeOpacity?: number; + carouselRef?: Carousel | null; + containerStyle?: StyleProp; + dotColor?: string; + dotContainerStyle?: StyleProp; + dotElement?: ReactElement; + dotStyle?: StyleProp; + inactiveDotColor?: string; + inactiveDotElement?: ReactElement; + inactiveDotOpacity: number; + inactiveDotScale: number; + inactiveDotStyle: StyleProp; + renderDots: ( + activeIndex: number, + length: number, + context: Pagination + ) => ReactElement; + tappableDots: boolean; + vertical: boolean; + accessibilityLabel?: string; + animatedDuration: number; + animatedFriction: number; + animatedTension: number; + delayPressInDot: number; +}; + +export default class Pagination extends PureComponent { + static defaultProps = { + inactiveDotOpacity: 0.5, + inactiveDotScale: 0.5, + tappableDots: false, + vertical: false, + animatedDuration: 250, + animatedFriction: 4, + animatedTension: 50, + delayPressInDot: 0, + }; + + constructor(props: PaginationProps) { + super(props); + + // Warnings + if ( + (props.dotColor && !props.inactiveDotColor) || + (!props.dotColor && props.inactiveDotColor) + ) { + console.warn( + 'react-native-snap-carousel | Pagination: ' + + 'You need to specify both `dotColor` and `inactiveDotColor`' + ); + } + if ( + (props.dotElement && !props.inactiveDotElement) || + (!props.dotElement && props.inactiveDotElement) + ) { + console.warn( + 'react-native-snap-carousel | Pagination: ' + + 'You need to specify both `dotElement` and `inactiveDotElement`' + ); + } + if (props.tappableDots && props.carouselRef === undefined) { + console.warn( + 'react-native-snap-carousel | Pagination: ' + + 'You must specify prop `carouselRef` when setting `tappableDots` to `true`' + ); + } + } + + _needsRTLAdaptations() { + const { vertical } = this.props; + return IS_RTL && !IS_IOS && !vertical; + } + + get _activeDotIndex() { + const { activeDotIndex, dotsLength } = this.props; + return this._needsRTLAdaptations() + ? dotsLength - activeDotIndex - 1 + : activeDotIndex; + } + + get dots() { + const { + activeOpacity, + carouselRef, + dotsLength, + dotColor, + dotContainerStyle, + dotElement, + dotStyle, + inactiveDotColor, + inactiveDotElement, + inactiveDotOpacity, + inactiveDotScale, + inactiveDotStyle, + renderDots, + tappableDots, + animatedDuration, + animatedFriction, + animatedTension, + delayPressInDot, + } = this.props; + + if (renderDots) { + return renderDots(this._activeDotIndex, dotsLength, this); + } + + const DefaultDot = ( + + ); + + const dots = [...Array(dotsLength).keys()].map((i) => { + const isActive = i === this._activeDotIndex; + return React.cloneElement( + (isActive ? dotElement : inactiveDotElement) || DefaultDot, + { + key: `pagination-dot-${i}`, + active: isActive, + index: i, + } + ); + }); + + return dots; + } + + render() { + const { + dotsLength, + containerStyle, + vertical, + accessibilityLabel, + } = this.props; + + if (!dotsLength || dotsLength < 2) { + return false; + } + + const style = [ + styles.sliderPagination, + { + flexDirection: vertical + ? ('column' as const) + : this._needsRTLAdaptations() + ? ('row-reverse' as const) + : ('row' as const), + }, + containerStyle || {}, + ]; + + return ( + + {this.dots} + + ); + } +} diff --git a/src/pagination/PaginationDot.js b/src/pagination/PaginationDot.js deleted file mode 100644 index 3b708b00c..000000000 --- a/src/pagination/PaginationDot.js +++ /dev/null @@ -1,160 +0,0 @@ -import React, { PureComponent } from 'react'; -import { View, Animated, Easing, TouchableOpacity, ViewPropTypes } from 'react-native'; -import PropTypes from 'prop-types'; -import styles from './Pagination.style'; - -export default class PaginationDot extends PureComponent { - - static propTypes = { - inactiveOpacity: PropTypes.number.isRequired, - inactiveScale: PropTypes.number.isRequired, - active: PropTypes.bool, - activeOpacity: PropTypes.number, - animatedDuration: PropTypes.number, - animatedFriction: PropTypes.number, - animatedTension: PropTypes.number, - carouselRef: PropTypes.object, - color: PropTypes.string, - containerStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, - delayPressInDot: PropTypes.number, - inactiveColor: PropTypes.string, - inactiveStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, - index: PropTypes.number, - style: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, - tappable: PropTypes.bool - }; - - constructor (props) { - super(props); - this.state = { - animColor: new Animated.Value(0), - animOpacity: new Animated.Value(0), - animTransform: new Animated.Value(0) - }; - } - - componentDidMount () { - if (this.props.active) { - this._animate(1); - } - } - - componentDidUpdate (prevProps) { - if (prevProps.active !== this.props.active) { - this._animate(this.props.active ? 1 : 0); - } - } - - _animate (toValue = 0) { - const { animColor, animOpacity, animTransform } = this.state; - const { animatedDuration, animatedFriction, animatedTension } = this.props; - - const commonProperties = { - toValue, - isInteraction: false, - useNativeDriver: !this._shouldAnimateColor - }; - - let animations = [ - Animated.timing(animOpacity, { - easing: Easing.linear, - duration: animatedDuration, - ...commonProperties - }), - Animated.spring(animTransform, { - friction: animatedFriction, - tension: animatedTension, - ...commonProperties - }) - ]; - - if (this._shouldAnimateColor) { - animations.push(Animated.timing(animColor, { - easing: Easing.linear, - ...commonProperties - })); - } - - Animated.parallel(animations).start(); - } - - get _shouldAnimateColor () { - const { color, inactiveColor } = this.props; - return color && inactiveColor; - } - - render () { - const { animColor, animOpacity, animTransform } = this.state; - const { - active, - activeOpacity, - carouselRef, - color, - containerStyle, - inactiveColor, - inactiveStyle, - inactiveOpacity, - inactiveScale, - index, - style, - tappable, - delayPressInDot - } = this.props; - - const animatedStyle = { - opacity: animOpacity.interpolate({ - inputRange: [0, 1], - outputRange: [inactiveOpacity, 1] - }), - transform: [{ - scale: animTransform.interpolate({ - inputRange: [0, 1], - outputRange: [inactiveScale, 1] - }) - }] - }; - const animatedColor = this._shouldAnimateColor ? { - backgroundColor: animColor.interpolate({ - inputRange: [0, 1], - outputRange: [inactiveColor, color] - }) - } : {}; - - const dotContainerStyle = [ - styles.sliderPaginationDotContainer, - containerStyle || {} - ]; - - const dotStyle = [ - styles.sliderPaginationDot, - style || {}, - (!active && inactiveStyle) || {}, - animatedStyle, - animatedColor - ]; - - const onPress = tappable ? () => { - try { - const currentRef = carouselRef.current || carouselRef; - currentRef._snapToItem(currentRef._getPositionIndex(index)); - } catch (error) { - console.warn( - 'react-native-snap-carousel | Pagination: ' + - '`carouselRef` has to be a Carousel ref.\n' + error - ); - } - } : undefined; - - return ( - - - - ); - } -} diff --git a/src/pagination/PaginationDot.tsx b/src/pagination/PaginationDot.tsx new file mode 100644 index 000000000..caa3ddb7c --- /dev/null +++ b/src/pagination/PaginationDot.tsx @@ -0,0 +1,188 @@ +import React, { PureComponent, RefObject } from 'react'; +import { + Animated, + Easing, + TouchableOpacity, + StyleProp, + ViewStyle, +} from 'react-native'; +import styles from './Pagination.style'; +import type Carousel from 'src/carousel/Carousel'; + +type PaginationDotProps = { + inactiveOpacity: number; + inactiveScale: number; + active?: boolean; + activeOpacity?: number; + animatedDuration?: number; + animatedFriction?: number; + animatedTension?: number; + carouselRef?: Carousel | RefObject> | null; + color?: string; + containerStyle?: StyleProp; + delayPressInDot?: number; + inactiveColor?: string; + inactiveStyle?: StyleProp; + index?: number; + style?: StyleProp; + tappable?: boolean; +}; + +type PaginationDotState = { + animColor: Animated.Value; + animOpacity: Animated.Value; + animTransform: Animated.Value; +}; + +export default class PaginationDot extends PureComponent< + PaginationDotProps, + PaginationDotState +> { + constructor(props: PaginationDotProps) { + super(props); + this.state = { + animColor: new Animated.Value(0), + animOpacity: new Animated.Value(0), + animTransform: new Animated.Value(0), + }; + } + + componentDidMount() { + if (this.props.active) { + this._animate(1); + } + } + + componentDidUpdate(prevProps: PaginationDotProps) { + if (prevProps.active !== this.props.active) { + this._animate(this.props.active ? 1 : 0); + } + } + + _animate(toValue = 0) { + const { animColor, animOpacity, animTransform } = this.state; + const { animatedDuration, animatedFriction, animatedTension } = this.props; + + const commonProperties = { + toValue, + isInteraction: false, + useNativeDriver: !this._shouldAnimateColor, + }; + + let animations = [ + Animated.timing(animOpacity, { + easing: Easing.linear, + duration: animatedDuration, + ...commonProperties, + }), + Animated.spring(animTransform, { + friction: animatedFriction, + tension: animatedTension, + ...commonProperties, + }), + ]; + + if (this._shouldAnimateColor) { + animations.push( + Animated.timing(animColor, { + easing: Easing.linear, + ...commonProperties, + }) + ); + } + + Animated.parallel(animations).start(); + } + + get _shouldAnimateColor() { + const { color, inactiveColor } = this.props; + return color && inactiveColor; + } + + render() { + const { animColor, animOpacity, animTransform } = this.state; + const { + active, + activeOpacity, + carouselRef, + color, + containerStyle, + inactiveColor, + inactiveStyle, + inactiveOpacity, + inactiveScale, + index, + style, + tappable, + delayPressInDot, + } = this.props; + + const animatedStyle = { + opacity: animOpacity.interpolate({ + inputRange: [0, 1], + outputRange: [inactiveOpacity, 1], + }), + transform: [ + { + scale: animTransform.interpolate({ + inputRange: [0, 1], + outputRange: [inactiveScale, 1], + }), + }, + ], + }; + const animatedColor = + this._shouldAnimateColor && inactiveColor && color + ? { + backgroundColor: animColor.interpolate({ + inputRange: [0, 1], + outputRange: [inactiveColor, color], + }), + } + : {}; + + const dotContainerStyle = [ + styles.sliderPaginationDotContainer, + containerStyle || {}, + ]; + + const dotStyle = [ + styles.sliderPaginationDot, + style || {}, + (!active && inactiveStyle) || {}, + animatedStyle, + animatedColor, + ]; + + const onPress = + tappable && (!!index || index === 0) + ? () => { + try { + const currentRef = + carouselRef && 'current' in carouselRef + ? carouselRef.current + : carouselRef; + currentRef!._snapToItem(currentRef!._getPositionIndex(index)); + } catch (error) { + console.warn( + 'react-native-snap-carousel | Pagination: ' + + '`carouselRef` has to be a Carousel ref.\n' + + error + ); + } + } + : undefined; + + return ( + + + + ); + } +} diff --git a/src/parallaximage/ParallaxImage.js b/src/parallaximage/ParallaxImage.js deleted file mode 100644 index 8bc774a10..000000000 --- a/src/parallaximage/ParallaxImage.js +++ /dev/null @@ -1,222 +0,0 @@ -// Parallax effect inspired by https://github.com/oblador/react-native-parallax/ - -import React, { Component } from 'react'; -import { View, ViewPropTypes, Image, Animated, Easing, ActivityIndicator, findNodeHandle } from 'react-native'; -import PropTypes from 'prop-types'; -import styles from './ParallaxImage.style'; - -export default class ParallaxImage extends Component { - - static propTypes = { - ...Image.propTypes, - carouselRef: PropTypes.object, // passed from - itemHeight: PropTypes.number, // passed from - itemWidth: PropTypes.number, // passed from - scrollPosition: PropTypes.object, // passed from - sliderHeight: PropTypes.number, // passed from - sliderWidth: PropTypes.number, // passed from - vertical: PropTypes.bool, // passed from - containerStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, - dimensions: PropTypes.shape({ - width: PropTypes.number, - height: PropTypes.number - }), - fadeDuration: PropTypes.number, - parallaxFactor: PropTypes.number, - showSpinner: PropTypes.bool, - spinnerColor: PropTypes.string, - AnimatedImageComponent: PropTypes.oneOfType([ - PropTypes.func, - PropTypes.object - ]) - }; - - static defaultProps = { - containerStyle: {}, - fadeDuration: 500, - parallaxFactor: 0.3, - showSpinner: true, - spinnerColor: 'rgba(0, 0, 0, 0.4)', - AnimatedImageComponent: Animated.Image - } - - constructor (props) { - super(props); - this.state = { - offset: 0, - width: 0, - height: 0, - status: 1, // 1 -> loading; 2 -> loaded // 3 -> transition finished; 4 -> error - animOpacity: new Animated.Value(0) - }; - this._onLoad = this._onLoad.bind(this); - this._onError = this._onError.bind(this); - this._measureLayout = this._measureLayout.bind(this); - } - - setNativeProps (nativeProps) { - this._container.setNativeProps(nativeProps); - } - - componentDidMount () { - this._mounted = true; - - setTimeout(() => { - this._measureLayout(); - }, 0); - } - - componentWillUnmount () { - this._mounted = false; - } - - _measureLayout () { - if (this._container) { - const { - dimensions, - vertical, - carouselRef, - sliderWidth, - sliderHeight, - itemWidth, - itemHeight - } = this.props; - - if (carouselRef) { - this._container.measureLayout( - findNodeHandle(carouselRef), - (x, y, width, height, pageX, pageY) => { - const offset = vertical ? - y - ((sliderHeight - itemHeight) / 2) : - x - ((sliderWidth - itemWidth) / 2); - - this.setState({ - offset: offset, - width: dimensions && dimensions.width ? - dimensions.width : - Math.ceil(width), - height: dimensions && dimensions.height ? - dimensions.height : - Math.ceil(height) - }); - } - ); - } - } - } - - _onLoad (event) { - const { animOpacity } = this.state; - const { fadeDuration, onLoad } = this.props; - - if (!this._mounted) { - return; - } - - this.setState({ status: 2 }); - - if (onLoad) { - onLoad(event); - } - - Animated.timing(animOpacity, { - toValue: 1, - duration: fadeDuration, - easing: Easing.out(Easing.quad), - isInteraction: false, - useNativeDriver: true - }).start(() => { - this.setState({ status: 3 }); - }); - } - - // If arg is missing from method signature, it just won't be called - _onError (event) { - const { onError } = this.props; - - this.setState({ status: 4 }); - - if (onError) { - onError(event); - } - } - - get image () { - const { status, animOpacity, offset, width, height } = this.state; - const { - scrollPosition, - dimensions, - vertical, - sliderWidth, - sliderHeight, - parallaxFactor, - style, - AnimatedImageComponent, - ...other - } = this.props; - - const parallaxPadding = (vertical ? height : width) * parallaxFactor; - const requiredStyles = { position: 'relative' }; - const dynamicStyles = { - width: vertical ? width : width + parallaxPadding * 2, - height: vertical ? height + parallaxPadding * 2 : height, - opacity: animOpacity, - transform: scrollPosition ? [ - { - translateX: !vertical ? scrollPosition.interpolate({ - inputRange: [offset - sliderWidth, offset + sliderWidth], - outputRange: [-parallaxPadding, parallaxPadding], - extrapolate: 'clamp' - }) : 0 - }, - { - translateY: vertical ? scrollPosition.interpolate({ - inputRange: [offset - sliderHeight, offset + sliderHeight], - outputRange: [-parallaxPadding, parallaxPadding], - extrapolate: 'clamp' - }) : 0 - } - ] : [] - }; - - return ( - - ); - } - - get spinner () { - const { status } = this.state; - const { showSpinner, spinnerColor } = this.props; - - return status === 1 && showSpinner ? ( - - - - ) : false; - } - - render () { - const { containerStyle } = this.props; - - return ( - { this._container = c; }} - pointerEvents={'none'} - style={[containerStyle, styles.container]} - onLayout={this._measureLayout} - > - { this.image } - { this.spinner } - - ); - } -} diff --git a/src/parallaximage/ParallaxImage.style.js b/src/parallaximage/ParallaxImage.style.js deleted file mode 100644 index a71ea4757..000000000 --- a/src/parallaximage/ParallaxImage.style.js +++ /dev/null @@ -1,20 +0,0 @@ -import { StyleSheet } from 'react-native'; - -export default StyleSheet.create({ - container: { - overflow: 'hidden', - alignItems: 'center', - justifyContent: 'center' - }, - image: { - position: 'relative', - resizeMode: 'cover', - width: null, - height: null - }, - loaderContainer: { - ...StyleSheet.absoluteFillObject, - alignItems: 'center', - justifyContent: 'center' - } -}); diff --git a/src/parallaximage/ParallaxImage.style.ts b/src/parallaximage/ParallaxImage.style.ts new file mode 100644 index 000000000..fe03069a6 --- /dev/null +++ b/src/parallaximage/ParallaxImage.style.ts @@ -0,0 +1,20 @@ +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + container: { + overflow: 'hidden', + alignItems: 'center', + justifyContent: 'center', + }, + image: { + position: 'relative', + resizeMode: 'cover', + width: undefined, + height: undefined, + }, + loaderContainer: { + ...StyleSheet.absoluteFillObject, + alignItems: 'center', + justifyContent: 'center', + }, +}); diff --git a/src/parallaximage/ParallaxImage.tsx b/src/parallaximage/ParallaxImage.tsx new file mode 100644 index 000000000..7f4f26fdf --- /dev/null +++ b/src/parallaximage/ParallaxImage.tsx @@ -0,0 +1,259 @@ +// Parallax effect inspired by https://github.com/oblador/react-native-parallax/ + +import React, { Component } from 'react'; +import { + View, + Animated, + Easing, + ActivityIndicator, + findNodeHandle, + ImageProps, + StyleProp, + ViewStyle, + NativeSyntheticEvent, + ImageLoadEventData, + ImageErrorEventData, +} from 'react-native'; +import styles from './ParallaxImage.style'; +import type Carousel from 'src/carousel/Carousel'; + +type ParallaxImageProps = { + carouselRef: Carousel | null; // passed from + itemHeight: number; // passed from + itemWidth: number; // passed from + scrollPosition: Animated.Value; // passed from + sliderHeight: number; // passed from + sliderWidth: number; // passed from + vertical: boolean; // passed from + containerStyle: StyleProp; + dimensions: { + width: number; + height: number; + }; + fadeDuration: number; + parallaxFactor: number; + showSpinner: boolean; + spinnerColor: string; + // TODO: type it + AnimatedImageComponent: typeof Animated.Image; +} & ImageProps; + +type ParallaxImageState = { + offset: number; + width: number; + height: number; + status: 1 | 2 | 3 | 4; + animOpacity: Animated.Value; +}; + +export default class ParallaxImage extends Component< + ParallaxImageProps, + ParallaxImageState +> { + static defaultProps = { + containerStyle: {}, + fadeDuration: 500, + parallaxFactor: 0.3, + showSpinner: true, + spinnerColor: 'rgba(0, 0, 0, 0.4)', + AnimatedImageComponent: Animated.Image, + }; + + _container?: View | null; + _mounted?: boolean; + + constructor(props: ParallaxImageProps) { + super(props); + this.state = { + offset: 0, + width: 0, + height: 0, + status: 1, // 1 -> loading; 2 -> loaded // 3 -> transition finished; 4 -> error + animOpacity: new Animated.Value(0), + }; + this._onLoad = this._onLoad.bind(this); + this._onError = this._onError.bind(this); + this._measureLayout = this._measureLayout.bind(this); + } + setNativeProps(nativeProps: { [key: string]: unknown }) { + this._container?.setNativeProps(nativeProps); + } + + componentDidMount() { + this._mounted = true; + + setTimeout(() => { + this._measureLayout(); + }, 0); + } + + componentWillUnmount() { + this._mounted = false; + } + + _measureLayout() { + if (this._container) { + const { + dimensions, + vertical, + carouselRef, + sliderWidth, + sliderHeight, + itemWidth, + itemHeight, + } = this.props; + + const nodeHandle = findNodeHandle(carouselRef); + + if (carouselRef && nodeHandle) { + this._container.measureLayout( + nodeHandle, + (x, y, width, height) => { + const offset = vertical + ? y - (sliderHeight - itemHeight) / 2 + : x - (sliderWidth - itemWidth) / 2; + + this.setState({ + offset: offset, + width: + dimensions && dimensions.width + ? dimensions.width + : Math.ceil(width), + height: + dimensions && dimensions.height + ? dimensions.height + : Math.ceil(height), + }); + }, + () => {} + ); + } + } + } + + _onLoad(event: NativeSyntheticEvent) { + const { animOpacity } = this.state; + const { fadeDuration, onLoad } = this.props; + + if (!this._mounted) { + return; + } + + this.setState({ status: 2 }); + + if (onLoad) { + onLoad(event); + } + + Animated.timing(animOpacity, { + toValue: 1, + duration: fadeDuration, + easing: Easing.out(Easing.quad), + isInteraction: false, + useNativeDriver: true, + }).start(() => { + this.setState({ status: 3 }); + }); + } + + // If arg is missing from method signature, it just won't be called + _onError(event: NativeSyntheticEvent) { + const { onError } = this.props; + + this.setState({ status: 4 }); + + if (onError) { + onError(event); + } + } + + get image() { + const { status, animOpacity, offset, width, height } = this.state; + const { + scrollPosition, + // False positive :( other doesn't have the dimension key + // eslint-disable-next-line @typescript-eslint/no-unused-vars + dimensions, + vertical, + sliderWidth, + sliderHeight, + parallaxFactor, + style, + AnimatedImageComponent, + ...other + } = this.props; + const parallaxPadding = (vertical ? height : width) * parallaxFactor; + const requiredStyles = { position: 'relative' }; + const dynamicStyles = { + width: vertical ? width : width + parallaxPadding * 2, + height: vertical ? height + parallaxPadding * 2 : height, + opacity: animOpacity, + transform: scrollPosition + ? [ + { + translateX: !vertical + ? scrollPosition.interpolate({ + inputRange: [offset - sliderWidth, offset + sliderWidth], + outputRange: [-parallaxPadding, parallaxPadding], + extrapolate: 'clamp', + }) + : 0, + }, + { + translateY: vertical + ? scrollPosition.interpolate({ + inputRange: [offset - sliderHeight, offset + sliderHeight], + outputRange: [-parallaxPadding, parallaxPadding], + extrapolate: 'clamp', + }) + : 0, + }, + ] + : [], + }; + + return ( + + ); + } + + get spinner() { + const { status } = this.state; + const { showSpinner, spinnerColor } = this.props; + + return status === 1 && showSpinner ? ( + + + + ) : ( + false + ); + } + + render() { + const { containerStyle } = this.props; + + return ( + { + this._container = c; + }} + pointerEvents={'none'} + style={[containerStyle, styles.container]} + onLayout={this._measureLayout} + > + {this.image} + {this.spinner} + + ); + } +} diff --git a/src/utils/animations.js b/src/utils/animations.js deleted file mode 100644 index bdb58b62d..000000000 --- a/src/utils/animations.js +++ /dev/null @@ -1,325 +0,0 @@ -import { Platform } from 'react-native'; - -const IS_ANDROID = Platform.OS === 'android'; - -// Get scroll interpolator's input range from an array of slide indexes -// Indexes are relative to the current active slide (index 0) -// For example, using [3, 2, 1, 0, -1] will return: -// [ -// (index - 3) * sizeRef, // active + 3 -// (index - 2) * sizeRef, // active + 2 -// (index - 1) * sizeRef, // active + 1 -// index * sizeRef, // active -// (index + 1) * sizeRef // active - 1 -// ] -export function getInputRangeFromIndexes (range, index, carouselProps) { - const sizeRef = carouselProps.vertical ? carouselProps.itemHeight : carouselProps.itemWidth; - let inputRange = []; - - for (let i = 0; i < range.length; i++) { - inputRange.push((index - range[i]) * sizeRef); - } - - return inputRange; -} - -// Default behavior -// Scale and/or opacity effect -// Based on props 'inactiveSlideOpacity' and 'inactiveSlideScale' -export function defaultScrollInterpolator (index, carouselProps) { - const range = [1, 0, -1]; - const inputRange = getInputRangeFromIndexes(range, index, carouselProps); - const outputRange = [0, 1, 0]; - - return { inputRange, outputRange }; -} -export function defaultAnimatedStyles (index, animatedValue, carouselProps) { - let animatedOpacity = {}; - let animatedScale = {}; - - if (carouselProps.inactiveSlideOpacity < 1) { - animatedOpacity = { - opacity: animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [carouselProps.inactiveSlideOpacity, 1] - }) - }; - } - - if (carouselProps.inactiveSlideScale < 1) { - animatedScale = { - transform: [{ - scale: animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [carouselProps.inactiveSlideScale, 1] - }) - }] - }; - } - - return { - ...animatedOpacity, - ...animatedScale - }; -} - -// Shift animation -// Same as the default one, but the active slide is also shifted up or down -// Based on prop 'inactiveSlideShift' -export function shiftAnimatedStyles (index, animatedValue, carouselProps) { - let animatedOpacity = {}; - let animatedScale = {}; - let animatedTranslate = {}; - - if (carouselProps.inactiveSlideOpacity < 1) { - animatedOpacity = { - opacity: animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [carouselProps.inactiveSlideOpacity, 1] - }) - }; - } - - if (carouselProps.inactiveSlideScale < 1) { - animatedScale = { - scale: animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [carouselProps.inactiveSlideScale, 1] - }) - }; - } - - if (carouselProps.inactiveSlideShift !== 0) { - const translateProp = carouselProps.vertical ? 'translateX' : 'translateY'; - animatedTranslate = { - [translateProp]: animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [carouselProps.inactiveSlideShift, 0] - }) - }; - } - - return { - ...animatedOpacity, - transform: [ - { ...animatedScale }, - { ...animatedTranslate } - ] - }; -} - -// Stack animation -// Imitate a deck/stack of cards (see #195) -// WARNING: The effect had to be visually inverted on Android because this OS doesn't honor the `zIndex`property -// This means that the item with the higher zIndex (and therefore the tap receiver) remains the one AFTER the currently active item -// The `elevation` property compensates for that only visually, which is not good enough -export function stackScrollInterpolator (index, carouselProps) { - const range = IS_ANDROID ? - [1, 0, -1, -2, -3] : - [3, 2, 1, 0, -1]; - const inputRange = getInputRangeFromIndexes(range, index, carouselProps); - const outputRange = range; - - return { inputRange, outputRange }; -} -export function stackAnimatedStyles (index, animatedValue, carouselProps, cardOffset) { - const sizeRef = carouselProps.vertical ? carouselProps.itemHeight : carouselProps.itemWidth; - const translateProp = carouselProps.vertical ? 'translateY' : 'translateX'; - - const card1Scale = 0.9; - const card2Scale = 0.8; - - cardOffset = !cardOffset && cardOffset !== 0 ? 18 : cardOffset; - - const getTranslateFromScale = (cardIndex, scale) => { - const centerFactor = 1 / scale * cardIndex; - const centeredPosition = -Math.round(sizeRef * centerFactor); - const edgeAlignment = Math.round((sizeRef - (sizeRef * scale)) / 2); - const offset = Math.round(cardOffset * Math.abs(cardIndex) / scale); - - return IS_ANDROID ? - centeredPosition - edgeAlignment - offset : - centeredPosition + edgeAlignment + offset; - }; - - const opacityOutputRange = carouselProps.inactiveSlideOpacity === 1 ? [1, 1, 1, 0] : [1, 0.75, 0.5, 0]; - - return IS_ANDROID ? { - // elevation: carouselProps.data.length - index, // fix zIndex bug visually, but not from a logic point of view - opacity: animatedValue.interpolate({ - inputRange: [-3, -2, -1, 0], - outputRange: opacityOutputRange.reverse(), - extrapolate: 'clamp' - }), - transform: [{ - scale: animatedValue.interpolate({ - inputRange: [-2, -1, 0, 1], - outputRange: [card2Scale, card1Scale, 1, card1Scale], - extrapolate: 'clamp' - }) - }, { - [translateProp]: animatedValue.interpolate({ - inputRange: [-3, -2, -1, 0, 1], - outputRange: [ - getTranslateFromScale(-3, card2Scale), - getTranslateFromScale(-2, card2Scale), - getTranslateFromScale(-1, card1Scale), - 0, - sizeRef * 0.5 - ], - extrapolate: 'clamp' - }) - }] - } : { - zIndex: carouselProps.data.length - index, - opacity: animatedValue.interpolate({ - inputRange: [0, 1, 2, 3], - outputRange: opacityOutputRange, - extrapolate: 'clamp' - }), - transform: [{ - scale: animatedValue.interpolate({ - inputRange: [-1, 0, 1, 2], - outputRange: [card1Scale, 1, card1Scale, card2Scale], - extrapolate: 'clamp' - }) - }, { - [translateProp]: animatedValue.interpolate({ - inputRange: [-1, 0, 1, 2, 3], - outputRange: [ - -sizeRef * 0.5, - 0, - getTranslateFromScale(1, card1Scale), - getTranslateFromScale(2, card2Scale), - getTranslateFromScale(3, card2Scale) - ], - extrapolate: 'clamp' - }) - }] - }; -} - -// Tinder animation -// Imitate the popular Tinder layout -// WARNING: The effect had to be visually inverted on Android because this OS doesn't honor the `zIndex`property -// This means that the item with the higher zIndex (and therefore the tap receiver) remains the one AFTER the currently active item -// The `elevation` property compensates for that only visually, which is not good enough -export function tinderScrollInterpolator (index, carouselProps) { - const range = IS_ANDROID ? - [1, 0, -1, -2, -3] : - [3, 2, 1, 0, -1]; - const inputRange = getInputRangeFromIndexes(range, index, carouselProps); - const outputRange = range; - - return { inputRange, outputRange }; -} -export function tinderAnimatedStyles (index, animatedValue, carouselProps, cardOffset) { - const sizeRef = carouselProps.vertical ? carouselProps.itemHeight : carouselProps.itemWidth; - const mainTranslateProp = carouselProps.vertical ? 'translateY' : 'translateX'; - const secondaryTranslateProp = carouselProps.vertical ? 'translateX' : 'translateY'; - - const card1Scale = 0.96; - const card2Scale = 0.92; - const card3Scale = 0.88; - - const peekingCardsOpacity = IS_ANDROID ? 0.92 : 1; - - cardOffset = !cardOffset && cardOffset !== 0 ? 9 : cardOffset; - - const getMainTranslateFromScale = (cardIndex, scale) => { - const centerFactor = 1 / scale * cardIndex; - return -Math.round(sizeRef * centerFactor); - }; - - const getSecondaryTranslateFromScale = (cardIndex, scale) => { - return Math.round(cardOffset * Math.abs(cardIndex) / scale); - }; - - return IS_ANDROID ? { - // elevation: carouselProps.data.length - index, // fix zIndex bug visually, but not from a logic point of view - opacity: animatedValue.interpolate({ - inputRange: [-3, -2, -1, 0, 1], - outputRange: [0, peekingCardsOpacity, peekingCardsOpacity, 1, 0], - extrapolate: 'clamp' - }), - transform: [{ - scale: animatedValue.interpolate({ - inputRange: [-3, -2, -1, 0], - outputRange: [card3Scale, card2Scale, card1Scale, 1], - extrapolate: 'clamp' - }) - }, { - rotate: animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: ['0deg', '22deg'], - extrapolate: 'clamp' - }) - }, { - [mainTranslateProp]: animatedValue.interpolate({ - inputRange: [-3, -2, -1, 0, 1], - outputRange: [ - getMainTranslateFromScale(-3, card3Scale), - getMainTranslateFromScale(-2, card2Scale), - getMainTranslateFromScale(-1, card1Scale), - 0, - sizeRef * 1.1 - ], - extrapolate: 'clamp' - }) - }, { - [secondaryTranslateProp]: animatedValue.interpolate({ - inputRange: [-3, -2, -1, 0], - outputRange: [ - getSecondaryTranslateFromScale(-3, card3Scale), - getSecondaryTranslateFromScale(-2, card2Scale), - getSecondaryTranslateFromScale(-1, card1Scale), - 0 - ], - extrapolate: 'clamp' - }) - }] - } : { - zIndex: carouselProps.data.length - index, - opacity: animatedValue.interpolate({ - inputRange: [-1, 0, 1, 2, 3], - outputRange: [0, 1, peekingCardsOpacity, peekingCardsOpacity, 0], - extrapolate: 'clamp' - }), - transform: [{ - scale: animatedValue.interpolate({ - inputRange: [0, 1, 2, 3], - outputRange: [1, card1Scale, card2Scale, card3Scale], - extrapolate: 'clamp' - }) - }, { - rotate: animatedValue.interpolate({ - inputRange: [-1, 0], - outputRange: ['-22deg', '0deg'], - extrapolate: 'clamp' - }) - }, { - [mainTranslateProp]: animatedValue.interpolate({ - inputRange: [-1, 0, 1, 2, 3], - outputRange: [ - -sizeRef * 1.1, - 0, - getMainTranslateFromScale(1, card1Scale), - getMainTranslateFromScale(2, card2Scale), - getMainTranslateFromScale(3, card3Scale) - ], - extrapolate: 'clamp' - }) - }, { - [secondaryTranslateProp]: animatedValue.interpolate({ - inputRange: [0, 1, 2, 3], - outputRange: [ - 0, - getSecondaryTranslateFromScale(1, card1Scale), - getSecondaryTranslateFromScale(2, card2Scale), - getSecondaryTranslateFromScale(3, card3Scale) - ], - extrapolate: 'clamp' - }) - }] - }; -} diff --git a/src/utils/animations.ts b/src/utils/animations.ts new file mode 100644 index 000000000..392cd5425 --- /dev/null +++ b/src/utils/animations.ts @@ -0,0 +1,383 @@ +import { Platform, Animated } from 'react-native'; +import type { CarouselProps } from 'src/carousel/types'; + +const IS_ANDROID = Platform.OS === 'android'; + +// Get scroll interpolator's input range from an array of slide indexes +// Indexes are relative to the current active slide (index 0) +// For example, using [3, 2, 1, 0, -1] will return: +// [ +// (index - 3) * sizeRef, // active + 3 +// (index - 2) * sizeRef, // active + 2 +// (index - 1) * sizeRef, // active + 1 +// index * sizeRef, // active +// (index + 1) * sizeRef // active - 1 +// ] +export function getInputRangeFromIndexes( + range: number[], + index: number, + carouselProps: CarouselProps +) { + const sizeRef = carouselProps.vertical + ? carouselProps.itemHeight + : carouselProps.itemWidth; + let inputRange = []; + + for (let i = 0; i < range.length; i++) { + inputRange.push((index - range[i]) * sizeRef); + } + + return inputRange; +} + +// Default behavior +// Scale and/or opacity effect +// Based on props 'inactiveSlideOpacity' and 'inactiveSlideScale' +export function defaultScrollInterpolator( + index: number, + carouselProps: CarouselProps +) { + const range = [1, 0, -1]; + const inputRange = getInputRangeFromIndexes(range, index, carouselProps); + const outputRange = [0, 1, 0]; + + return { inputRange, outputRange }; +} +export function defaultAnimatedStyles( + _index: number, + animatedValue: Animated.Value, + carouselProps: CarouselProps +) { + let animatedOpacity = {}; + let animatedScale = {}; + + if (carouselProps.inactiveSlideOpacity < 1) { + animatedOpacity = { + opacity: animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [carouselProps.inactiveSlideOpacity, 1], + }), + }; + } + + if (carouselProps.inactiveSlideScale < 1) { + animatedScale = { + transform: [ + { + scale: animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [carouselProps.inactiveSlideScale, 1], + }), + }, + ], + }; + } + + return { + ...animatedOpacity, + ...animatedScale, + }; +} + +// Shift animation +// Same as the default one, but the active slide is also shifted up or down +// Based on prop 'inactiveSlideShift' +export function shiftAnimatedStyles( + _index: number, + animatedValue: Animated.Value, + carouselProps: CarouselProps +) { + let animatedOpacity = {}; + let animatedScale = {}; + let animatedTranslate = {}; + + if (carouselProps.inactiveSlideOpacity < 1) { + animatedOpacity = { + opacity: animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [carouselProps.inactiveSlideOpacity, 1], + }), + }; + } + + if (carouselProps.inactiveSlideScale < 1) { + animatedScale = { + scale: animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [carouselProps.inactiveSlideScale, 1], + }), + }; + } + + if (carouselProps.inactiveSlideShift !== 0) { + const translateProp = carouselProps.vertical ? 'translateX' : 'translateY'; + animatedTranslate = { + [translateProp]: animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [carouselProps.inactiveSlideShift, 0], + }), + }; + } + + return { + ...animatedOpacity, + transform: [{ ...animatedScale }, { ...animatedTranslate }], + }; +} + +// Stack animation +// Imitate a deck/stack of cards (see #195) +// WARNING: The effect had to be visually inverted on Android because this OS doesn't honor the `zIndex`property +// This means that the item with the higher zIndex (and therefore the tap receiver) remains the one AFTER the currently active item +// The `elevation` property compensates for that only visually, which is not good enough +export function stackScrollInterpolator( + index: number, + carouselProps: CarouselProps +) { + const range = IS_ANDROID ? [1, 0, -1, -2, -3] : [3, 2, 1, 0, -1]; + const inputRange = getInputRangeFromIndexes(range, index, carouselProps); + const outputRange = range; + + return { inputRange, outputRange }; +} +export function stackAnimatedStyles( + index: number, + animatedValue: Animated.Value, + carouselProps: CarouselProps, + cardOffset: number +) { + const sizeRef = carouselProps.vertical + ? carouselProps.itemHeight + : carouselProps.itemWidth; + const translateProp = carouselProps.vertical ? 'translateY' : 'translateX'; + + const card1Scale = 0.9; + const card2Scale = 0.8; + + cardOffset = !cardOffset && cardOffset !== 0 ? 18 : cardOffset; + + const getTranslateFromScale = (cardIndex: number, scale: number) => { + const centerFactor = (1 / scale) * cardIndex; + const centeredPosition = -Math.round(sizeRef * centerFactor); + const edgeAlignment = Math.round((sizeRef - sizeRef * scale) / 2); + const offset = Math.round((cardOffset * Math.abs(cardIndex)) / scale); + + return IS_ANDROID + ? centeredPosition - edgeAlignment - offset + : centeredPosition + edgeAlignment + offset; + }; + + const opacityOutputRange = + carouselProps.inactiveSlideOpacity === 1 ? [1, 1, 1, 0] : [1, 0.75, 0.5, 0]; + + return IS_ANDROID + ? { + // elevation: carouselProps.data.length - index, // fix zIndex bug visually, but not from a logic point of view + opacity: animatedValue.interpolate({ + inputRange: [-3, -2, -1, 0], + outputRange: opacityOutputRange.reverse(), + extrapolate: 'clamp', + }), + transform: [ + { + scale: animatedValue.interpolate({ + inputRange: [-2, -1, 0, 1], + outputRange: [card2Scale, card1Scale, 1, card1Scale], + extrapolate: 'clamp', + }), + }, + { + [translateProp]: animatedValue.interpolate({ + inputRange: [-3, -2, -1, 0, 1], + outputRange: [ + getTranslateFromScale(-3, card2Scale), + getTranslateFromScale(-2, card2Scale), + getTranslateFromScale(-1, card1Scale), + 0, + sizeRef * 0.5, + ], + extrapolate: 'clamp', + }), + }, + ], + } + : { + zIndex: carouselProps.data.length - index, + opacity: animatedValue.interpolate({ + inputRange: [0, 1, 2, 3], + outputRange: opacityOutputRange, + extrapolate: 'clamp', + }), + transform: [ + { + scale: animatedValue.interpolate({ + inputRange: [-1, 0, 1, 2], + outputRange: [card1Scale, 1, card1Scale, card2Scale], + extrapolate: 'clamp', + }), + }, + { + [translateProp]: animatedValue.interpolate({ + inputRange: [-1, 0, 1, 2, 3], + outputRange: [ + -sizeRef * 0.5, + 0, + getTranslateFromScale(1, card1Scale), + getTranslateFromScale(2, card2Scale), + getTranslateFromScale(3, card2Scale), + ], + extrapolate: 'clamp', + }), + }, + ], + }; +} + +// Tinder animation +// Imitate the popular Tinder layout +// WARNING: The effect had to be visually inverted on Android because this OS doesn't honor the `zIndex`property +// This means that the item with the higher zIndex (and therefore the tap receiver) remains the one AFTER the currently active item +// The `elevation` property compensates for that only visually, which is not good enough +export function tinderScrollInterpolator( + index: number, + carouselProps: CarouselProps +) { + const range = IS_ANDROID ? [1, 0, -1, -2, -3] : [3, 2, 1, 0, -1]; + const inputRange = getInputRangeFromIndexes(range, index, carouselProps); + const outputRange = range; + + return { inputRange, outputRange }; +} +export function tinderAnimatedStyles( + index: number, + animatedValue: Animated.Value, + carouselProps: CarouselProps, + cardOffset: number +) { + const sizeRef = carouselProps.vertical + ? carouselProps.itemHeight + : carouselProps.itemWidth; + const mainTranslateProp = carouselProps.vertical + ? 'translateY' + : 'translateX'; + const secondaryTranslateProp = carouselProps.vertical + ? 'translateX' + : 'translateY'; + + const card1Scale = 0.96; + const card2Scale = 0.92; + const card3Scale = 0.88; + + const peekingCardsOpacity = IS_ANDROID ? 0.92 : 1; + + cardOffset = !cardOffset && cardOffset !== 0 ? 9 : cardOffset; + + const getMainTranslateFromScale = (cardIndex: number, scale: number) => { + const centerFactor = (1 / scale) * cardIndex; + return -Math.round(sizeRef * centerFactor); + }; + + const getSecondaryTranslateFromScale = (cardIndex: number, scale: number) => { + return Math.round((cardOffset * Math.abs(cardIndex)) / scale); + }; + + return IS_ANDROID + ? { + // elevation: carouselProps.data.length - index, // fix zIndex bug visually, but not from a logic point of view + opacity: animatedValue.interpolate({ + inputRange: [-3, -2, -1, 0, 1], + outputRange: [0, peekingCardsOpacity, peekingCardsOpacity, 1, 0], + extrapolate: 'clamp', + }), + transform: [ + { + scale: animatedValue.interpolate({ + inputRange: [-3, -2, -1, 0], + outputRange: [card3Scale, card2Scale, card1Scale, 1], + extrapolate: 'clamp', + }), + }, + { + rotate: animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: ['0deg', '22deg'], + extrapolate: 'clamp', + }), + }, + { + [mainTranslateProp]: animatedValue.interpolate({ + inputRange: [-3, -2, -1, 0, 1], + outputRange: [ + getMainTranslateFromScale(-3, card3Scale), + getMainTranslateFromScale(-2, card2Scale), + getMainTranslateFromScale(-1, card1Scale), + 0, + sizeRef * 1.1, + ], + extrapolate: 'clamp', + }), + }, + { + [secondaryTranslateProp]: animatedValue.interpolate({ + inputRange: [-3, -2, -1, 0], + outputRange: [ + getSecondaryTranslateFromScale(-3, card3Scale), + getSecondaryTranslateFromScale(-2, card2Scale), + getSecondaryTranslateFromScale(-1, card1Scale), + 0, + ], + extrapolate: 'clamp', + }), + }, + ], + } + : { + zIndex: carouselProps.data.length - index, + opacity: animatedValue.interpolate({ + inputRange: [-1, 0, 1, 2, 3], + outputRange: [0, 1, peekingCardsOpacity, peekingCardsOpacity, 0], + extrapolate: 'clamp', + }), + transform: [ + { + scale: animatedValue.interpolate({ + inputRange: [0, 1, 2, 3], + outputRange: [1, card1Scale, card2Scale, card3Scale], + extrapolate: 'clamp', + }), + }, + { + rotate: animatedValue.interpolate({ + inputRange: [-1, 0], + outputRange: ['-22deg', '0deg'], + extrapolate: 'clamp', + }), + }, + { + [mainTranslateProp]: animatedValue.interpolate({ + inputRange: [-1, 0, 1, 2, 3], + outputRange: [ + -sizeRef * 1.1, + 0, + getMainTranslateFromScale(1, card1Scale), + getMainTranslateFromScale(2, card2Scale), + getMainTranslateFromScale(3, card3Scale), + ], + extrapolate: 'clamp', + }), + }, + { + [secondaryTranslateProp]: animatedValue.interpolate({ + inputRange: [0, 1, 2, 3], + outputRange: [ + 0, + getSecondaryTranslateFromScale(1, card1Scale), + getSecondaryTranslateFromScale(2, card2Scale), + getSecondaryTranslateFromScale(3, card3Scale), + ], + extrapolate: 'clamp', + }), + }, + ], + }; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..02d160622 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "react-native-snap-carousel": ["./src/index"] + }, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "esModuleInterop": true, + "importsNotUsedAsValues": "error", + "forceConsistentCasingInFileNames": true, + "jsx": "react", + "lib": ["esnext"], + "module": "esnext", + "moduleResolution": "node", + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "noImplicitUseStrict": false, + "noStrictGenericChecks": false, + "noUnusedLocals": true, + "noUnusedParameters": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "esnext", + }, + "include": ["src/**/*"] +} From 9f0dfe67ee5de99680e9eeff5f83aebf02370996 Mon Sep 17 00:00:00 2001 From: Thibault Malbranche Date: Thu, 6 Aug 2020 01:16:09 +0200 Subject: [PATCH 23/34] wip --- .eslintrc | 41 + .vscode/settings.json | 3 +- package.json | 51 +- src/carousel/Carousel.tsx | 2043 +++++++++++----------- src/carousel/types.ts | 45 +- src/index.ts | 10 +- src/pagination/Pagination.style.ts | 34 +- src/pagination/Pagination.tsx | 268 +-- src/pagination/PaginationDot.tsx | 289 +-- src/parallaximage/ParallaxImage.style.ts | 32 +- src/parallaximage/ParallaxImage.tsx | 359 ++-- src/utils/animations.ts | 602 +++---- src/whatthehell.js | 1 + 13 files changed, 1907 insertions(+), 1871 deletions(-) create mode 100644 .eslintrc create mode 100644 src/whatthehell.js diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 000000000..6867c22f6 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,41 @@ +{ + "parser" : "@typescript-eslint/parser", + "extends" : [ + "standard", + "standard-react", + "plugin:@typescript-eslint/recommended" + ], + "plugins": [ + "@typescript-eslint", + "react" + ], + "env" : { + "browser" : true, + "es6": true + }, + "globals": { + "__DEV__": false + }, + "settings": { + "react": { + "version": "detect" + } + }, + "rules": { + "generator-star-spacing": 0, + "indent": [2, 4, { "ignoredNodes": ["JSXAttribute", "JSXSpreadAttribute"], "SwitchCase": 1 }], + "no-warning-comments": [1, { + "terms": ["todo", "fixme", "xxx"], + "location": "start" + }], + "operator-linebreak": [2, "after"], + "padded-blocks": 0, + "semi": [2, "always"], + "react/jsx-indent-props": [2, 2], + "react/jsx-boolean-value": [0, "never"], + "react/jsx-curly-spacing": [0, "never"], + "react/jsx-indent": [2, 4], + "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/explicit-module-boundary-types": "off" + } +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 53aa4f28e..88721406e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,5 +2,6 @@ "eslint.enable": true, "editor.codeActionsOnSave": { "source.fixAll": true - } + }, + "typescript.tsdk": "node_modules/typescript/lib" } \ No newline at end of file diff --git a/package.json b/package.json index 499aa462d..f0e27182c 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,6 @@ "license": "BSD-3-Clause", "dependencies": { "@types/react-addons-shallow-compare": "^0.14.22", - "prop-types": "15.7.2", "react-addons-shallow-compare": "15.6.2" }, "peerDependencies": { @@ -53,47 +52,29 @@ "react-native": ">=0.58.0" }, "devDependencies": { - "@react-native-community/bob": "^0.16.2", - "@react-native-community/eslint-config": "^2.0.0", - "@types/react": "^16.9.19", - "@types/react-native": "0.62.13", + "@react-native-community/bob": "0.16.2", + "@types/react": "16.9.44", + "@types/react-native": "0.63.4", + "@typescript-eslint/eslint-plugin": "3.8.0", + "@typescript-eslint/parser": "3.8.0", "babel-eslint": "10.1.0", - "eslint": "^7.2.0", - "eslint-config-prettier": "^6.11.0", - "eslint-plugin-prettier": "^3.1.3", - "react-native": "0.61.5", - "typescript": "^3.8.3" + "eslint": "7.6.0", + "eslint-config-standard": "14.1.1", + "eslint-config-standard-react": "9.2.0", + "eslint-plugin-import": "2.22.0", + "eslint-plugin-node": "11.1.0", + "eslint-plugin-promise": "4.2.1", + "eslint-plugin-react": "7.20.5", + "eslint-plugin-standard": "4.0.1", + "react": "16.13.1", + "react-native": "0.63.2", + "typescript": "3.9.7" }, - "eslintConfig": { - "extends": [ - "@react-native-community", - "prettier" - ], - "rules": { - "prettier/prettier": [ - "error", - { - "quoteProps": "consistent", - "singleQuote": true, - "tabWidth": 2, - "trailingComma": "es5", - "useTabs": false - } - ] - } - }, "eslintIgnore": [ "node_modules/", "lib/", "example" ], - "prettier": { - "quoteProps": "consistent", - "singleQuote": true, - "tabWidth": 2, - "trailingComma": "es5", - "useTabs": false - }, "@react-native-community/bob": { "source": "src", "output": "lib", diff --git a/src/carousel/Carousel.tsx b/src/carousel/Carousel.tsx index c3d9a8ee3..a043f2812 100644 --- a/src/carousel/Carousel.tsx +++ b/src/carousel/Carousel.tsx @@ -1,27 +1,27 @@ import React, { PropsWithChildren } from 'react'; import { - Animated, - FlatList, - I18nManager, - Platform, - ScrollView, - View, - StyleProp, - NativeSyntheticEvent, - NativeScrollEvent, - LayoutChangeEvent, - GestureResponderEvent, - ViewStyle, + Animated, + FlatList, + I18nManager, + Platform, + ScrollView, + View, + StyleProp, + NativeSyntheticEvent, + NativeScrollEvent, + LayoutChangeEvent, + GestureResponderEvent, + ViewStyle } from 'react-native'; import shallowCompare from 'react-addons-shallow-compare'; import { - defaultScrollInterpolator, - stackScrollInterpolator, - tinderScrollInterpolator, - defaultAnimatedStyles, - shiftAnimatedStyles, - stackAnimatedStyles, - tinderAnimatedStyles, + defaultScrollInterpolator, + stackScrollInterpolator, + tinderScrollInterpolator, + defaultAnimatedStyles, + shiftAnimatedStyles, + stackAnimatedStyles, + tinderAnimatedStyles } from '../utils/animations'; import type { CarouselProps, CarouselState } from './types'; @@ -34,9 +34,9 @@ const IS_ANDROID = Platform.OS === 'android'; // Native driver for scroll events // See: https://facebook.github.io/react-native/blog/2017/02/14/using-native-driver-for-animated.html -const AnimatedFlatList = FlatList - ? Animated.createAnimatedComponent(FlatList) - : null; +const AnimatedFlatList = FlatList ? + Animated.createAnimatedComponent(FlatList) : + null; const AnimatedScrollView = Animated.createAnimatedComponent(ScrollView); // React Native automatically handles RTL layouts; unfortunately, it's buggy with horizontal ScrollView @@ -50,30 +50,30 @@ export class Carousel extends React.Component< CarouselState > { static defaultProps = { - activeSlideAlignment: 'center', - activeSlideOffset: 20, - apparitionDelay: 0, - autoplay: false, - autoplayDelay: 1000, - autoplayInterval: 3000, - callbackOffsetMargin: 5, - containerCustomStyle: {}, - contentContainerCustomStyle: {}, - enableSnap: true, - firstItem: 0, - hasParallaxImages: false, - inactiveSlideOpacity: 0.7, - inactiveSlideScale: 0.9, - inactiveSlideShift: 0, - layout: 'default', - loop: false, - loopClonesPerSide: 3, - scrollEnabled: true, - slideStyle: {}, - shouldOptimizeUpdates: true, - useExperimentalSnap: false, - useScrollView: !AnimatedFlatList, - vertical: false, + activeSlideAlignment: 'center', + activeSlideOffset: 20, + apparitionDelay: 0, + autoplay: false, + autoplayDelay: 1000, + autoplayInterval: 3000, + callbackOffsetMargin: 5, + containerCustomStyle: {}, + contentContainerCustomStyle: {}, + enableSnap: true, + firstItem: 0, + hasParallaxImages: false, + inactiveSlideOpacity: 0.7, + inactiveSlideScale: 0.9, + inactiveSlideShift: 0, + layout: 'default', + loop: false, + loopClonesPerSide: 3, + scrollEnabled: true, + slideStyle: {}, + shouldOptimizeUpdates: true, + useExperimentalSnap: false, + useScrollView: !AnimatedFlatList, + vertical: false }; _activeItem: number; @@ -96,247 +96,247 @@ export class Carousel extends React.Component< _scrollPos?: Animated.Value; - _onScrollHandler?: (...args: any[]) => void; + _onScrollHandler?: ReturnType; - _carouselRef?: ScrollView | FlatList | null; + _carouselRef: ScrollView | FlatList | null = null; _autoplaying?: boolean; _autoplay?: boolean; _onLayoutInitDone?: boolean; - constructor(props: CarouselProps) { - super(props); - - this.state = { - hideCarousel: !!props.apparitionDelay, - interpolators: [], - }; - - // this._RNVersionCode = this._getRNVersionCode(); - - // The following values are not stored in the state because 'setState()' is asynchronous - // and this results in an absolutely crappy behavior on Android while swiping (see #156) - const initialActiveItem = this._getFirstItem(props.firstItem); - this._activeItem = initialActiveItem; - this._onScrollActiveItem = initialActiveItem; - this._previousFirstItem = initialActiveItem; - this._previousItemsLength = initialActiveItem; - - this._mounted = false; - this._positions = []; - this._currentScrollOffset = 0; // Store ScrollView's scroll position - this._scrollEnabled = props.scrollEnabled !== false; - - this._getCellRendererComponent = this._getCellRendererComponent.bind(this); - this._getItemLayout = this._getItemLayout.bind(this); - this._getKeyExtractor = this._getKeyExtractor.bind(this); - this._onLayout = this._onLayout.bind(this); - this._onScroll = this._onScroll.bind(this); - this._onMomentumScrollEnd = this._onMomentumScrollEnd.bind(this); - this._onTouchStart = this._onTouchStart.bind(this); - this._onTouchEnd = this._onTouchEnd.bind(this); - this._renderItem = this._renderItem.bind(this); - - // WARNING: call this AFTER binding _onScroll - this._setScrollHandler(props); - - // Display warnings - this._displayWarnings(props); - } + constructor (props: CarouselProps) { + super(props); - componentDidMount() { - const { apparitionDelay, autoplay, firstItem } = this.props; + this.state = { + hideCarousel: !!props.apparitionDelay, + interpolators: [] + }; - this._mounted = true; - this._initPositionsAndInterpolators(); + // this._RNVersionCode = this._getRNVersionCode(); + + // The following values are not stored in the state because 'setState()' is asynchronous + // and this results in an absolutely crappy behavior on Android while swiping (see #156) + const initialActiveItem = this._getFirstItem(props.firstItem); + this._activeItem = initialActiveItem; + this._onScrollActiveItem = initialActiveItem; + this._previousFirstItem = initialActiveItem; + this._previousItemsLength = initialActiveItem; + + this._mounted = false; + this._positions = []; + this._currentScrollOffset = 0; // Store ScrollView's scroll position + this._scrollEnabled = props.scrollEnabled !== false; + + this._getCellRendererComponent = this._getCellRendererComponent.bind(this); + this._getItemLayout = this._getItemLayout.bind(this); + this._getKeyExtractor = this._getKeyExtractor.bind(this); + this._onLayout = this._onLayout.bind(this); + this._onScroll = this._onScroll.bind(this); + this._onMomentumScrollEnd = this._onMomentumScrollEnd.bind(this); + this._onTouchStart = this._onTouchStart.bind(this); + this._onTouchEnd = this._onTouchEnd.bind(this); + this._renderItem = this._renderItem.bind(this); + + // WARNING: call this AFTER binding _onScroll + this._setScrollHandler(props); + + // Display warnings + this._displayWarnings(props); + } - // Without 'requestAnimationFrame' or a `0` timeout, images will randomly not be rendered on Android... - this._initTimeout = setTimeout(() => { - if (!this._mounted) { - return; - } + componentDidMount () { + const { apparitionDelay, autoplay, firstItem } = this.props; - const apparitionCallback = () => { - if (apparitionDelay) { - this.setState({ hideCarousel: false }); - } - if (autoplay) { - this.startAutoplay(); - } - }; + this._mounted = true; + this._initPositionsAndInterpolators(); - // FlatList will use its own built-in prop `initialScrollIndex` - if (this._needsScrollView()) { - const _firstItem = this._getFirstItem(firstItem); - this._snapToItem(_firstItem, false, false, true); - // this._hackActiveSlideAnimation(_firstItem); - } + // Without 'requestAnimationFrame' or a `0` timeout, images will randomly not be rendered on Android... + this._initTimeout = setTimeout(() => { + if (!this._mounted) { + return; + } + + const apparitionCallback = () => { + if (apparitionDelay) { + this.setState({ hideCarousel: false }); + } + if (autoplay) { + this.startAutoplay(); + } + }; + + // FlatList will use its own built-in prop `initialScrollIndex` + if (this._needsScrollView()) { + const _firstItem = this._getFirstItem(firstItem); + this._snapToItem(_firstItem, false, false, true); + // this._hackActiveSlideAnimation(_firstItem); + } + + if (apparitionDelay) { + this._apparitionTimeout = setTimeout(() => { + apparitionCallback(); + }, apparitionDelay); + } else { + apparitionCallback(); + } + }, 1); + } - if (apparitionDelay) { - this._apparitionTimeout = setTimeout(() => { - apparitionCallback(); - }, apparitionDelay); + shouldComponentUpdate ( + nextProps: CarouselProps, + nextState: CarouselState + ): boolean { + if (this.props.shouldOptimizeUpdates === false) { + return true; } else { - apparitionCallback(); + return shallowCompare(this, nextProps, nextState); } - }, 1); } - shouldComponentUpdate( - nextProps: CarouselProps, - nextState: any - ): boolean { - if (this.props.shouldOptimizeUpdates === false) { - return true; - } else { - return shallowCompare(this, nextProps, nextState); - } - } + componentDidUpdate (prevProps: CarouselProps) { + const { interpolators } = this.state; + const { + firstItem, + itemHeight, + itemWidth, + scrollEnabled, + sliderHeight, + sliderWidth + } = this.props; + const itemsLength = this._getCustomDataLength(this.props); + + if (!itemsLength) { + return; + } - componentDidUpdate(prevProps: CarouselProps) { - const { interpolators } = this.state; - const { - firstItem, - itemHeight, - itemWidth, - scrollEnabled, - sliderHeight, - sliderWidth, - } = this.props; - const itemsLength = this._getCustomDataLength(this.props); - - if (!itemsLength) { - return; - } - - const nextFirstItem = this._getFirstItem(firstItem, this.props); - let nextActiveItem = - typeof this._activeItem !== 'undefined' - ? this._activeItem - : nextFirstItem; - - const hasNewSliderWidth = + const nextFirstItem = this._getFirstItem(firstItem, this.props); + let nextActiveItem = + typeof this._activeItem !== 'undefined' ? + this._activeItem : + nextFirstItem; + + const hasNewSliderWidth = sliderWidth && sliderWidth !== prevProps.sliderWidth; - const hasNewSliderHeight = + const hasNewSliderHeight = sliderHeight && sliderHeight !== prevProps.sliderHeight; - const hasNewItemWidth = itemWidth && itemWidth !== prevProps.itemWidth; - const hasNewItemHeight = itemHeight && itemHeight !== prevProps.itemHeight; - const hasNewScrollEnabled = scrollEnabled !== prevProps.scrollEnabled; - - // Prevent issues with dynamically removed items - if (nextActiveItem > itemsLength - 1) { - nextActiveItem = itemsLength - 1; - } - - // Handle changing scrollEnabled independent of user -> carousel interaction - if (hasNewScrollEnabled) { - this._setScrollEnabled(scrollEnabled); - } - - if ( - interpolators.length !== itemsLength || + const hasNewItemWidth = itemWidth && itemWidth !== prevProps.itemWidth; + const hasNewItemHeight = itemHeight && itemHeight !== prevProps.itemHeight; + const hasNewScrollEnabled = scrollEnabled !== prevProps.scrollEnabled; + + // Prevent issues with dynamically removed items + if (nextActiveItem > itemsLength - 1) { + nextActiveItem = itemsLength - 1; + } + + // Handle changing scrollEnabled independent of user -> carousel interaction + if (hasNewScrollEnabled) { + this._setScrollEnabled(scrollEnabled); + } + + if ( + interpolators.length !== itemsLength || hasNewSliderWidth || hasNewSliderHeight || hasNewItemWidth || hasNewItemHeight - ) { - this._activeItem = nextActiveItem; - this._previousItemsLength = itemsLength; + ) { + this._activeItem = nextActiveItem; + this._previousItemsLength = itemsLength; - this._initPositionsAndInterpolators(this.props); + this._initPositionsAndInterpolators(this.props); - // Handle scroll issue when dynamically removing items (see #133) - // This also fixes first item's active state on Android - // Because 'initialScrollIndex' apparently doesn't trigger scroll - if (this._previousItemsLength > itemsLength) { - this._hackActiveSlideAnimation(nextActiveItem); - } + // Handle scroll issue when dynamically removing items (see #133) + // This also fixes first item's active state on Android + // Because 'initialScrollIndex' apparently doesn't trigger scroll + if (this._previousItemsLength > itemsLength) { + this._hackActiveSlideAnimation(nextActiveItem); + } - if ( - hasNewSliderWidth || + if ( + hasNewSliderWidth || hasNewSliderHeight || hasNewItemWidth || hasNewItemHeight + ) { + this._snapToItem(nextActiveItem, false, false, true); + } + } else if ( + nextFirstItem !== this._previousFirstItem && + nextFirstItem !== this._activeItem ) { - this._snapToItem(nextActiveItem, false, false, true); + this._activeItem = nextFirstItem; + this._previousFirstItem = nextFirstItem; + this._snapToItem(nextFirstItem, false, true, true); + } + + if (this.props.onScroll !== prevProps.onScroll) { + this._setScrollHandler(this.props); } - } else if ( - nextFirstItem !== this._previousFirstItem && - nextFirstItem !== this._activeItem - ) { - this._activeItem = nextFirstItem; - this._previousFirstItem = nextFirstItem; - this._snapToItem(nextFirstItem, false, true, true); - } - - if (this.props.onScroll !== prevProps.onScroll) { - this._setScrollHandler(this.props); - } } - componentWillUnmount() { - this._mounted = false; - this.stopAutoplay(); - // @ts-expect-error setTimeout / clearTiemout is buggy :/ - clearTimeout(this._initTimeout); - // @ts-expect-error setTimeout / clearTiemout is buggy :/ - clearTimeout(this._apparitionTimeout); - // @ts-expect-error setTimeout / clearTiemout is buggy :/ - clearTimeout(this._hackSlideAnimationTimeout); - // @ts-expect-error setTimeout / clearTiemout is buggy :/ - clearTimeout(this._enableAutoplayTimeout); - // @ts-expect-error setTimeout / clearTiemout is buggy :/ - clearTimeout(this._autoplayTimeout); - // @ts-expect-error setTimeout / clearTiemout is buggy :/ - clearTimeout(this._snapNoMomentumTimeout); - // @ts-expect-error setTimeout / clearTiemout is buggy :/ - clearTimeout(this._androidRepositioningTimeout); + componentWillUnmount () { + this._mounted = false; + this.stopAutoplay(); + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._initTimeout); + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._apparitionTimeout); + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._hackSlideAnimationTimeout); + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._enableAutoplayTimeout); + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._autoplayTimeout); + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._snapNoMomentumTimeout); + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._androidRepositioningTimeout); } - get realIndex() { - return this._activeItem; + get realIndex () { + return this._activeItem; } - get currentIndex() { - return this._getDataIndex(this._activeItem); + get currentIndex () { + return this._getDataIndex(this._activeItem); } - get currentScrollPosition() { - return this._currentScrollOffset; + get currentScrollPosition () { + return this._currentScrollOffset; } - _setScrollHandler(props: CarouselProps) { - // Native driver for scroll events - const scrollEventConfig = { - listener: this._onScroll, - useNativeDriver: true, - }; - this._scrollPos = new Animated.Value(0); - const argMapping = props.vertical - ? [{ nativeEvent: { contentOffset: { y: this._scrollPos } } }] - : [{ nativeEvent: { contentOffset: { x: this._scrollPos } } }]; - - // @ts-expect-error Let's ignore for now that trick - if (props.onScroll && Array.isArray(props.onScroll._argMapping)) { - // Because of a react-native issue https://github.com/facebook/react-native/issues/13294 - argMapping.pop(); + _setScrollHandler (props: CarouselProps) { + // Native driver for scroll events + const scrollEventConfig = { + listener: this._onScroll, + useNativeDriver: true + }; + this._scrollPos = new Animated.Value(0); + const argMapping = props.vertical ? + [{ nativeEvent: { contentOffset: { y: this._scrollPos } } }] : + [{ nativeEvent: { contentOffset: { x: this._scrollPos } } }]; + // @ts-expect-error Let's ignore for now that trick - const [argMap] = props.onScroll._argMapping; - if (argMap && argMap.nativeEvent && argMap.nativeEvent.contentOffset) { - // Shares the same animated value passed in props - this._scrollPos = + if (props.onScroll && Array.isArray(props.onScroll._argMapping)) { + // Because of a react-native issue https://github.com/facebook/react-native/issues/13294 + argMapping.pop(); + // @ts-expect-error Let's ignore for now that trick + const [argMap] = props.onScroll._argMapping; + if (argMap && argMap.nativeEvent && argMap.nativeEvent.contentOffset) { + // Shares the same animated value passed in props + this._scrollPos = argMap.nativeEvent.contentOffset.x || argMap.nativeEvent.contentOffset.y || this._scrollPos; + } + // @ts-expect-error Let's ignore for now that trick + argMapping.push(...props.onScroll._argMapping); } - // @ts-expect-error Let's ignore for now that trick - argMapping.push(...props.onScroll._argMapping); - } - this._onScrollHandler = Animated.event( - argMapping, - scrollEventConfig - ); + this._onScrollHandler = Animated.event( + argMapping, + scrollEventConfig + ); } // This will return a future-proof version code number compatible with semantic versioning @@ -355,1012 +355,1011 @@ export class Carousel extends React.Component< // (typeof versionSplit[2] !== 'undefined' ? versionSplit[2] * 1 : 0); // } - _displayWarnings(props: CarouselProps = this.props) { - const pluginName = 'react-native-snap-carousel'; - const removedProps = [ - 'activeAnimationType', - 'activeAnimationOptions', - 'enableMomentum', - 'lockScrollTimeoutDuration', - 'lockScrollWhileSnapping', - 'onBeforeSnapToItem', - 'swipeThreshold', - ] as const; - - // if (this._RNVersionCode && this._RNVersionCode < 5800) { - // console.error( - // `${pluginName}: Version 4+ of the plugin is based on React Native props that were introduced in version 0.58. ` + - // 'Please downgrade to version 3.x or update your version of React Native.' - // ); - // } - if (!props.vertical && (!props.sliderWidth || !props.itemWidth)) { - console.error( - `${pluginName}: You need to specify both 'sliderWidth' and 'itemWidth' for horizontal carousels` - ); - } - if (props.vertical && (!props.sliderHeight || !props.itemHeight)) { - console.error( - `${pluginName}: You need to specify both 'sliderHeight' and 'itemHeight' for vertical carousels` - ); - } - - removedProps.forEach((removedProp) => { - if (removedProp in props) { - console.warn( - `${pluginName}: Prop ${removedProp} has been removed in version 4 of the plugin` - ); + _displayWarnings (props: CarouselProps = this.props) { + const pluginName = 'react-native-snap-carousel'; + const removedProps = [ + 'activeAnimationType', + 'activeAnimationOptions', + 'enableMomentum', + 'lockScrollTimeoutDuration', + 'lockScrollWhileSnapping', + 'onBeforeSnapToItem', + 'swipeThreshold' + ] as const; + + // if (this._RNVersionCode && this._RNVersionCode < 5800) { + // console.error( + // `${pluginName}: Version 4+ of the plugin is based on React Native props that were introduced in version 0.58. ` + + // 'Please downgrade to version 3.x or update your version of React Native.' + // ); + // } + if (!props.vertical && (!props.sliderWidth || !props.itemWidth)) { + console.error( + `${pluginName}: You need to specify both 'sliderWidth' and 'itemWidth' for horizontal carousels` + ); + } + if (props.vertical && (!props.sliderHeight || !props.itemHeight)) { + console.error( + `${pluginName}: You need to specify both 'sliderHeight' and 'itemHeight' for vertical carousels` + ); } - }); + + removedProps.forEach((removedProp) => { + if (removedProp in props) { + console.warn( + `${pluginName}: Prop ${removedProp} has been removed in version 4 of the plugin` + ); + } + }); } - _needsScrollView() { - const { useScrollView } = this.props; - // Android's cell renderer is buggy and has a stange overflow - // TODO: a workaround might be to pass the custom animated styles directly to it - return IS_ANDROID - ? useScrollView || + _needsScrollView () { + const { useScrollView } = this.props; + // Android's cell renderer is buggy and has a stange overflow + // TODO: a workaround might be to pass the custom animated styles directly to it + return IS_ANDROID ? + useScrollView || !AnimatedFlatList || this._shouldUseStackLayout() || - this._shouldUseTinderLayout() - : useScrollView || !AnimatedFlatList; + this._shouldUseTinderLayout() : + useScrollView || !AnimatedFlatList; } - _needsRTLAdaptations() { - const { vertical } = this.props; - return IS_RTL && IS_ANDROID && !vertical; + _needsRTLAdaptations () { + const { vertical } = this.props; + return IS_RTL && IS_ANDROID && !vertical; } - _enableLoop() { - const { data, enableSnap, loop } = this.props; - return enableSnap && loop && data && data.length && data.length > 1; + _enableLoop () { + const { data, enableSnap, loop } = this.props; + return enableSnap && loop && data && data.length && data.length > 1; } - _shouldAnimateSlides(props: CarouselProps = this.props) { - const { - inactiveSlideOpacity, - inactiveSlideScale, - scrollInterpolator, - slideInterpolatedStyle, - } = props; - return ( - inactiveSlideOpacity < 1 || + _shouldAnimateSlides (props: CarouselProps = this.props) { + const { + inactiveSlideOpacity, + inactiveSlideScale, + scrollInterpolator, + slideInterpolatedStyle + } = props; + return ( + inactiveSlideOpacity < 1 || inactiveSlideScale < 1 || !!scrollInterpolator || !!slideInterpolatedStyle || this._shouldUseShiftLayout() || this._shouldUseStackLayout() || this._shouldUseTinderLayout() - ); + ); } - _shouldUseShiftLayout() { - const { inactiveSlideShift, layout } = this.props; - return layout === 'default' && inactiveSlideShift !== 0; + _shouldUseShiftLayout () { + const { inactiveSlideShift, layout } = this.props; + return layout === 'default' && inactiveSlideShift !== 0; } - _shouldUseStackLayout() { - return this.props.layout === 'stack'; + _shouldUseStackLayout () { + return this.props.layout === 'stack'; } - _shouldUseTinderLayout() { - return this.props.layout === 'tinder'; + _shouldUseTinderLayout () { + return this.props.layout === 'tinder'; } - _shouldRepositionScroll(index: number) { - const { data, enableSnap, loopClonesPerSide } = this.props; - const dataLength = data && data.length; - if ( - !enableSnap || + _shouldRepositionScroll (index: number) { + const { data, enableSnap, loopClonesPerSide } = this.props; + const dataLength = data && data.length; + if ( + !enableSnap || !dataLength || !this._enableLoop() || (index >= loopClonesPerSide && index < dataLength + loopClonesPerSide) - ) { - return false; - } - return true; + ) { + return false; + } + return true; } - _roundNumber(num: number, decimals = 1) { - // https://stackoverflow.com/a/41716722/ - const rounder = Math.pow(10, decimals); - return Math.round((num + Number.EPSILON) * rounder) / rounder; + _roundNumber (num: number, decimals = 1) { + // https://stackoverflow.com/a/41716722/ + const rounder = Math.pow(10, decimals); + return Math.round((num + Number.EPSILON) * rounder) / rounder; } - _isMultiple(x: number, y: number) { - // This prevents Javascript precision issues: https://stackoverflow.com/a/58440614/ - // Required because Android viewport size can return pretty complicated decimals numbers - return Math.round(Math.round(x / y) / (1 / y)) === Math.round(x); + _isMultiple (x: number, y: number) { + // This prevents Javascript precision issues: https://stackoverflow.com/a/58440614/ + // Required because Android viewport size can return pretty complicated decimals numbers + return Math.round(Math.round(x / y) / (1 / y)) === Math.round(x); } - _getCustomData(props: CarouselProps = this.props) { - const { data, loopClonesPerSide } = props; - const dataLength = data && data.length; + _getCustomData (props: CarouselProps = this.props) { + const { data, loopClonesPerSide } = props; + const dataLength = data && data.length; - if (!dataLength) { - return []; - } + if (!dataLength) { + return []; + } - if (!this._enableLoop()) { - return data; - } + if (!this._enableLoop()) { + return data; + } - let previousItems = []; - let nextItems = []; + let previousItems = []; + let nextItems = []; - if (loopClonesPerSide > dataLength) { - const dataMultiplier = Math.floor(loopClonesPerSide / dataLength); - const remainder = loopClonesPerSide % dataLength; + if (loopClonesPerSide > dataLength) { + const dataMultiplier = Math.floor(loopClonesPerSide / dataLength); + const remainder = loopClonesPerSide % dataLength; - for (let i = 0; i < dataMultiplier; i++) { - previousItems.push(...data); - nextItems.push(...data); - } + for (let i = 0; i < dataMultiplier; i++) { + previousItems.push(...data); + nextItems.push(...data); + } - previousItems.unshift(...data.slice(-remainder)); - nextItems.push(...data.slice(0, remainder)); - } else { - previousItems = data.slice(-loopClonesPerSide); - nextItems = data.slice(0, loopClonesPerSide); - } + previousItems.unshift(...data.slice(-remainder)); + nextItems.push(...data.slice(0, remainder)); + } else { + previousItems = data.slice(-loopClonesPerSide); + nextItems = data.slice(0, loopClonesPerSide); + } - return previousItems.concat(data, nextItems); + return previousItems.concat(data, nextItems); } - _getCustomDataLength(props: CarouselProps = this.props) { - const { data, loopClonesPerSide } = props; - const dataLength = data && data.length; + _getCustomDataLength (props: CarouselProps = this.props) { + const { data, loopClonesPerSide } = props; + const dataLength = data && data.length; - if (!dataLength) { - return 0; - } + if (!dataLength) { + return 0; + } - return this._enableLoop() ? dataLength + 2 * loopClonesPerSide : dataLength; + return this._enableLoop() ? dataLength + 2 * loopClonesPerSide : dataLength; } - _getCustomIndex(index: number, props: CarouselProps = this.props) { - const itemsLength = this._getCustomDataLength(props); + _getCustomIndex (index: number, props: CarouselProps = this.props) { + const itemsLength = this._getCustomDataLength(props); - if (!itemsLength || typeof index === 'undefined') { - return 0; - } + if (!itemsLength || typeof index === 'undefined') { + return 0; + } - return this._needsRTLAdaptations() ? itemsLength - index - 1 : index; + return this._needsRTLAdaptations() ? itemsLength - index - 1 : index; } - _getDataIndex(index: number) { - const { data, loopClonesPerSide } = this.props; - const dataLength = data && data.length; + _getDataIndex (index: number) { + const { data, loopClonesPerSide } = this.props; + const dataLength = data && data.length; - if (!this._enableLoop() || !dataLength) { - return index; - } + if (!this._enableLoop() || !dataLength) { + return index; + } - if (index >= dataLength + loopClonesPerSide) { - return loopClonesPerSide > dataLength - ? (index - loopClonesPerSide) % dataLength - : index - dataLength - loopClonesPerSide; - } else if (index < loopClonesPerSide) { + if (index >= dataLength + loopClonesPerSide) { + return loopClonesPerSide > dataLength ? + (index - loopClonesPerSide) % dataLength : + index - dataLength - loopClonesPerSide; + } else if (index < loopClonesPerSide) { // TODO: is there a simpler way of determining the interpolated index? - if (loopClonesPerSide > dataLength) { - const baseDataIndexes = []; - const dataIndexes = []; - const dataMultiplier = Math.floor(loopClonesPerSide / dataLength); - const remainder = loopClonesPerSide % dataLength; - - for (let i = 0; i < dataLength; i++) { - baseDataIndexes.push(i); - } - - for (let j = 0; j < dataMultiplier; j++) { - dataIndexes.push(...baseDataIndexes); - } - - dataIndexes.unshift(...baseDataIndexes.slice(-remainder)); - return dataIndexes[index]; + if (loopClonesPerSide > dataLength) { + const baseDataIndexes = []; + const dataIndexes = []; + const dataMultiplier = Math.floor(loopClonesPerSide / dataLength); + const remainder = loopClonesPerSide % dataLength; + + for (let i = 0; i < dataLength; i++) { + baseDataIndexes.push(i); + } + + for (let j = 0; j < dataMultiplier; j++) { + dataIndexes.push(...baseDataIndexes); + } + + dataIndexes.unshift(...baseDataIndexes.slice(-remainder)); + return dataIndexes[index]; + } else { + return index + dataLength - loopClonesPerSide; + } } else { - return index + dataLength - loopClonesPerSide; + return index - loopClonesPerSide; } - } else { - return index - loopClonesPerSide; - } } // Used with `snapToItem()` and 'PaginationDot' - _getPositionIndex(index: number) { - const { loop, loopClonesPerSide } = this.props; - return loop ? index + loopClonesPerSide : index; + _getPositionIndex (index: number) { + const { loop, loopClonesPerSide } = this.props; + return loop ? index + loopClonesPerSide : index; } - _getSnapOffsets(props: CarouselProps = this.props) { - const offset = this._getItemMainDimension(); - return [...Array(this._getCustomDataLength(props))].map((_, i) => { - return i * offset; - }); + _getSnapOffsets (props: CarouselProps = this.props) { + const offset = this._getItemMainDimension(); + return [...Array(this._getCustomDataLength(props))].map((_, i) => { + return i * offset; + }); } - _getFirstItem(index: number, props: CarouselProps = this.props) { - const { loopClonesPerSide } = props; - const itemsLength = this._getCustomDataLength(props); + _getFirstItem (index: number, props: CarouselProps = this.props) { + const { loopClonesPerSide } = props; + const itemsLength = this._getCustomDataLength(props); - if (!itemsLength || index > itemsLength - 1 || index < 0) { - return 0; - } + if (!itemsLength || index > itemsLength - 1 || index < 0) { + return 0; + } - return this._enableLoop() ? index + loopClonesPerSide : index; + return this._enableLoop() ? index + loopClonesPerSide : index; } - _getWrappedRef() { - // Starting with RN 0.62, we should no longer call `getNode()` on the ref of an Animated component - if ( - this._carouselRef && + _getWrappedRef () { + // Starting with RN 0.62, we should no longer call `getNode()` on the ref of an Animated component + if ( + this._carouselRef && ((this._needsScrollView() && (this._carouselRef as ScrollView).scrollTo) || (!this._needsScrollView() && (this._carouselRef as FlatList).scrollToOffset)) - ) { - return this._carouselRef; - } - // https://github.com/facebook/react-native/issues/10635 - // https://stackoverflow.com/a/48786374/8412141 - return ( - this._carouselRef && + ) { + return this._carouselRef; + } + // https://github.com/facebook/react-native/issues/10635 + // https://stackoverflow.com/a/48786374/8412141 + return ( + this._carouselRef && // @ts-expect-error This is for before 0.62 this._carouselRef.getNode && // @ts-expect-error This is for before 0.62 this._carouselRef.getNode() - ); + ); } - _getScrollEnabled() { - return this._scrollEnabled; + _getScrollEnabled () { + return this._scrollEnabled; } - _setScrollEnabled(scrollEnabled = true) { - const wrappedRef = this._getWrappedRef(); + _setScrollEnabled (scrollEnabled = true) { + const wrappedRef = this._getWrappedRef(); - if (!wrappedRef || !wrappedRef.setNativeProps) { - return; - } + if (!wrappedRef || !wrappedRef.setNativeProps) { + return; + } - // 'setNativeProps()' is used instead of 'setState()' because the latter - // really takes a toll on Android behavior when momentum is disabled - wrappedRef.setNativeProps({ scrollEnabled }); - this._scrollEnabled = scrollEnabled; + // 'setNativeProps()' is used instead of 'setState()' because the latter + // really takes a toll on Android behavior when momentum is disabled + wrappedRef.setNativeProps({ scrollEnabled }); + this._scrollEnabled = scrollEnabled; } - _getItemMainDimension() { - return this.props.vertical ? this.props.itemHeight : this.props.itemWidth; + _getItemMainDimension () { + return this.props.vertical ? this.props.itemHeight : this.props.itemWidth; } - _getItemScrollOffset(index: number) { - return ( - this._positions && this._positions[index] && this._positions[index].start - ); + _getItemScrollOffset (index: number) { + return ( + this._positions && this._positions[index] && this._positions[index].start + ); } - _getItemLayout(_: unknown, index: number) { - const itemMainDimension = this._getItemMainDimension(); - return { - index, - length: itemMainDimension, - offset: itemMainDimension * index, // + this._getContainerInnerMargin() - }; + _getItemLayout (_: unknown, index: number) { + const itemMainDimension = this._getItemMainDimension(); + return { + index, + length: itemMainDimension, + offset: itemMainDimension * index // + this._getContainerInnerMargin() + }; } // This will allow us to have a proper zIndex even with a FlatList // https://github.com/facebook/react-native/issues/18616#issuecomment-389444165 - _getCellRendererComponent({ - children, - index, - style, - ...props - }: PropsWithChildren<{ index: number; style: StyleProp }>) { - const cellStyle = [ + _getCellRendererComponent ({ + children, + index, style, - !IS_ANDROID ? { zIndex: this._getCustomDataLength() - index } : {}, - ]; - - return ( - - {children} - - ); + ...props + }: PropsWithChildren<{ index: number; style: StyleProp }>) { + const cellStyle = [ + style, + !IS_ANDROID ? { zIndex: this._getCustomDataLength() - index } : {} + ]; + + return ( + + {children} + + ); } - _getKeyExtractor(_: unknown, index: number) { - return this._needsScrollView() - ? `scrollview-item-${index}` - : `flatlist-item-${index}`; + _getKeyExtractor (_: unknown, index: number) { + return this._needsScrollView() ? + `scrollview-item-${index}` : + `flatlist-item-${index}`; } - _getScrollOffset(event: NativeSyntheticEvent) { - const { vertical } = this.props; - return ( - (event && + _getScrollOffset (event: NativeSyntheticEvent) { + const { vertical } = this.props; + return ( + (event && event.nativeEvent && event.nativeEvent.contentOffset && event.nativeEvent.contentOffset[vertical ? 'y' : 'x']) || 0 - ); + ); } - _getContainerInnerMargin(opposite = false) { - const { activeSlideAlignment } = this.props; + _getContainerInnerMargin (opposite = false) { + const { activeSlideAlignment } = this.props; - if ( - (activeSlideAlignment === 'start' && !opposite) || + if ( + (activeSlideAlignment === 'start' && !opposite) || (activeSlideAlignment === 'end' && opposite) - ) { - return 0; - } else if ( - (activeSlideAlignment === 'end' && !opposite) || + ) { + return 0; + } else if ( + (activeSlideAlignment === 'end' && !opposite) || (activeSlideAlignment === 'start' && opposite) - ) { - return this.props.vertical - ? this.props.sliderHeight - this.props.itemHeight - : this.props.sliderWidth - this.props.itemWidth; - } else { - return this.props.vertical - ? (this.props.sliderHeight - this.props.itemHeight) / 2 - : (this.props.sliderWidth - this.props.itemWidth) / 2; - } + ) { + return this.props.vertical ? + this.props.sliderHeight - this.props.itemHeight : + this.props.sliderWidth - this.props.itemWidth; + } else { + return this.props.vertical ? + (this.props.sliderHeight - this.props.itemHeight) / 2 : + (this.props.sliderWidth - this.props.itemWidth) / 2; + } } - _getActiveSlideOffset() { - const { activeSlideOffset } = this.props; - const itemMainDimension = this._getItemMainDimension(); - const minOffset = 10; - // Make sure activeSlideOffset never prevents the active area from being at least 10 px wide - return itemMainDimension / 2 - activeSlideOffset >= minOffset - ? activeSlideOffset - : minOffset; + _getActiveSlideOffset () { + const { activeSlideOffset } = this.props; + const itemMainDimension = this._getItemMainDimension(); + const minOffset = 10; + // Make sure activeSlideOffset never prevents the active area from being at least 10 px wide + return itemMainDimension / 2 - activeSlideOffset >= minOffset ? + activeSlideOffset : + minOffset; } - _getActiveItem(offset: number) { - const itemMainDimension = this._getItemMainDimension(); - const center = offset + itemMainDimension / 2; - const activeSlideOffset = this._getActiveSlideOffset(); - const lastIndex = this._positions.length - 1; - let itemIndex; + _getActiveItem (offset: number) { + const itemMainDimension = this._getItemMainDimension(); + const center = offset + itemMainDimension / 2; + const activeSlideOffset = this._getActiveSlideOffset(); + const lastIndex = this._positions.length - 1; + let itemIndex; - if (offset <= 0) { - return 0; - } + if (offset <= 0) { + return 0; + } - if ( - this._positions[lastIndex] && + if ( + this._positions[lastIndex] && offset >= this._positions[lastIndex].start - ) { - return lastIndex; - } + ) { + return lastIndex; + } - for (let i = 0; i < this._positions.length; i++) { - const { start, end } = this._positions[i]; - if ( - center + activeSlideOffset >= start && + for (let i = 0; i < this._positions.length; i++) { + const { start, end } = this._positions[i]; + if ( + center + activeSlideOffset >= start && center - activeSlideOffset <= end - ) { - itemIndex = i; - break; + ) { + itemIndex = i; + break; + } } - } - return itemIndex || 0; + return itemIndex || 0; } - _getSlideInterpolatedStyle(index: number, animatedValue: Animated.Value) { - const { layoutCardOffset, slideInterpolatedStyle } = this.props; - - if (slideInterpolatedStyle) { - return slideInterpolatedStyle(index, animatedValue, this.props); - } else if (this._shouldUseTinderLayout()) { - return tinderAnimatedStyles( - index, - animatedValue, - this.props, - layoutCardOffset - ); - } else if (this._shouldUseStackLayout()) { - return stackAnimatedStyles( - index, - animatedValue, - this.props, - layoutCardOffset - ); - } else if (this._shouldUseShiftLayout()) { - return shiftAnimatedStyles(index, animatedValue, this.props); - } else { - return defaultAnimatedStyles(index, animatedValue, this.props); - } + _getSlideInterpolatedStyle (index: number, animatedValue: Animated.AnimatedInterpolation) { + const { layoutCardOffset, slideInterpolatedStyle } = this.props; + + if (slideInterpolatedStyle) { + return slideInterpolatedStyle(index, animatedValue, this.props); + } else if (this._shouldUseTinderLayout()) { + return tinderAnimatedStyles( + index, + animatedValue, + this.props, + layoutCardOffset + ); + } else if (this._shouldUseStackLayout()) { + return stackAnimatedStyles( + index, + animatedValue, + this.props, + layoutCardOffset + ); + } else if (this._shouldUseShiftLayout()) { + return shiftAnimatedStyles(index, animatedValue, this.props); + } else { + return defaultAnimatedStyles(index, animatedValue, this.props); + } } - _initPositionsAndInterpolators(props: CarouselProps = this.props) { - const { data, scrollInterpolator } = props; - const itemMainDimension = this._getItemMainDimension(); - - if (!data || !data.length) { - return; - } - - const interpolators: Animated.Value | Animated.AnimatedInterpolation[] = []; - this._positions = []; - - this._getCustomData(props).forEach((_itemData, index) => { - const _index = this._getCustomIndex(index, props); - let animatedValue; + _initPositionsAndInterpolators (props: CarouselProps = this.props) { + const { data, scrollInterpolator } = props; + const itemMainDimension = this._getItemMainDimension(); - this._positions[index] = { - start: index * itemMainDimension, - end: index * itemMainDimension + itemMainDimension, - }; + if (!data || !data.length) { + return; + } - if (!this._shouldAnimateSlides(props)) { - animatedValue = new Animated.Value(1); - } else { - let interpolator; - - if (scrollInterpolator) { - interpolator = scrollInterpolator(_index, props); - } else if (this._shouldUseStackLayout()) { - interpolator = stackScrollInterpolator(_index, props); - } else if (this._shouldUseTinderLayout()) { - interpolator = tinderScrollInterpolator(_index, props); - } - - if ( - !interpolator || + const interpolators: Animated.AnimatedInterpolation[] = []; + this._positions = []; + + this._getCustomData(props).forEach((_itemData, index) => { + const _index = this._getCustomIndex(index, props); + let animatedValue: Animated.AnimatedInterpolation; + + this._positions[index] = { + start: index * itemMainDimension, + end: index * itemMainDimension + itemMainDimension + }; + + if (!this._shouldAnimateSlides(props) || !this._scrollPos) { + animatedValue = new Animated.Value(1); + } else { + let interpolator; + + if (scrollInterpolator) { + interpolator = scrollInterpolator(_index, props); + } else if (this._shouldUseStackLayout()) { + interpolator = stackScrollInterpolator(_index, props); + } else if (this._shouldUseTinderLayout()) { + interpolator = tinderScrollInterpolator(_index, props); + } + + if ( + !interpolator || !interpolator.inputRange || !interpolator.outputRange - ) { - interpolator = defaultScrollInterpolator(_index, props); - } - - animatedValue = this._scrollPos!.interpolate({ - ...interpolator, - extrapolate: 'clamp', - }); - } + ) { + interpolator = defaultScrollInterpolator(_index, props); + } + + animatedValue = this._scrollPos.interpolate({ + ...interpolator, + extrapolate: 'clamp' + }); + } - interpolators.push(animatedValue); - }); + interpolators.push(animatedValue); + }); - this.setState({ interpolators }); + this.setState({ interpolators }); } - _hackActiveSlideAnimation(index: number, scrollValue = 1) { - const offset = this._getItemScrollOffset(index); + _hackActiveSlideAnimation (index: number, scrollValue = 1) { + const offset = this._getItemScrollOffset(index); - if (!this._mounted || !this._carouselRef || typeof offset === 'undefined') { - return; - } + if (!this._mounted || !this._carouselRef || typeof offset === 'undefined') { + return; + } - const multiplier = this._currentScrollOffset === 0 ? 1 : -1; - const scrollDelta = scrollValue * multiplier; + const multiplier = this._currentScrollOffset === 0 ? 1 : -1; + const scrollDelta = scrollValue * multiplier; - this._scrollTo({ offset: offset + scrollDelta, animated: false }); + this._scrollTo({ offset: offset + scrollDelta, animated: false }); - // @ts-expect-error setTimeout / clearTiemout is buggy :/ - clearTimeout(this._hackSlideAnimationTimeout); - this._hackSlideAnimationTimeout = setTimeout(() => { - this._scrollTo({ offset, animated: false }); - }, 1); // works randomly when set to '0' + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._hackSlideAnimationTimeout); + this._hackSlideAnimationTimeout = setTimeout(() => { + this._scrollTo({ offset, animated: false }); + }, 1); // works randomly when set to '0' } - _repositionScroll(index: number, animated = false) { - const { data, loopClonesPerSide } = this.props; - const dataLength = data && data.length; + _repositionScroll (index: number, animated = false) { + const { data, loopClonesPerSide } = this.props; + const dataLength = data && data.length; - if (typeof index === 'undefined' || !this._shouldRepositionScroll(index)) { - return; - } + if (typeof index === 'undefined' || !this._shouldRepositionScroll(index)) { + return; + } - let repositionTo = index; + let repositionTo = index; - if (index >= dataLength + loopClonesPerSide) { - repositionTo = index - dataLength; - } else if (index < loopClonesPerSide) { - repositionTo = index + dataLength; - } + if (index >= dataLength + loopClonesPerSide) { + repositionTo = index - dataLength; + } else if (index < loopClonesPerSide) { + repositionTo = index + dataLength; + } - this._snapToItem(repositionTo, animated, false); + this._snapToItem(repositionTo, animated, false); } - _scrollTo({ - offset, - index, - animated = true, + _scrollTo ({ + offset, + index, + animated = true }: { offset: number; index?: number; animated: boolean; }) { - const { vertical } = this.props; - const wrappedRef = this._getWrappedRef(); - if ( - !this._mounted || + const { vertical } = this.props; + const wrappedRef = this._getWrappedRef(); + if ( + !this._mounted || !wrappedRef || (typeof offset === 'undefined' && typeof index === 'undefined') - ) { - return; - } - - let scrollToOffset; - if (typeof index !== 'undefined') { - scrollToOffset = this._getItemScrollOffset(index); - } else { - scrollToOffset = offset; - } - - if (typeof scrollToOffset === 'undefined') { - return; - } - - const options = this._needsScrollView() - ? { - x: vertical ? 0 : offset, - y: vertical ? offset : 0, - animated, - } - : { - offset, - animated, - }; - - if (this._needsScrollView()) { - wrappedRef.scrollTo(options); - } else { - wrappedRef.scrollToOffset(options); - } + ) { + return; + } + + let scrollToOffset; + if (typeof index !== 'undefined') { + scrollToOffset = this._getItemScrollOffset(index); + } else { + scrollToOffset = offset; + } + + if (typeof scrollToOffset === 'undefined') { + return; + } + + const options = this._needsScrollView() ? + { + x: vertical ? 0 : offset, + y: vertical ? offset : 0, + animated + } : + { + offset, + animated + }; + + if (this._needsScrollView()) { + wrappedRef.scrollTo(options); + } else { + wrappedRef.scrollToOffset(options); + } } - _onTouchStart(event: GestureResponderEvent) { - const { onTouchStart } = this.props; + _onTouchStart (event: GestureResponderEvent) { + const { onTouchStart } = this.props; - // `onTouchStart` is fired even when `scrollEnabled` is set to `false` - if (this._getScrollEnabled() !== false && this._autoplaying) { - this.pauseAutoPlay(); - } + // `onTouchStart` is fired even when `scrollEnabled` is set to `false` + if (this._getScrollEnabled() !== false && this._autoplaying) { + this.pauseAutoPlay(); + } - onTouchStart && onTouchStart(event); + onTouchStart && onTouchStart(event); } - _onTouchEnd(event: GestureResponderEvent) { - const { onTouchEnd } = this.props; + _onTouchEnd (event: GestureResponderEvent) { + const { onTouchEnd } = this.props; - if ( - this._getScrollEnabled() !== false && + if ( + this._getScrollEnabled() !== false && this._autoplay && !this._autoplaying - ) { + ) { // This event is buggy on Android, so a fallback is provided in _onMomentumScrollEnd() - this.startAutoplay(); - } + this.startAutoplay(); + } - onTouchEnd && onTouchEnd(event); + onTouchEnd && onTouchEnd(event); } - _onScroll(event: NativeSyntheticEvent) { - const { onScroll, onScrollIndexChanged } = this.props; - const scrollOffset = event - ? this._getScrollOffset(event) - : this._currentScrollOffset; - const nextActiveItem = this._getActiveItem(scrollOffset); + _onScroll (event: NativeSyntheticEvent) { + const { onScroll, onScrollIndexChanged } = this.props; + const scrollOffset = event ? + this._getScrollOffset(event) : + this._currentScrollOffset; + const nextActiveItem = this._getActiveItem(scrollOffset); - this._currentScrollOffset = scrollOffset; + this._currentScrollOffset = scrollOffset; - if (nextActiveItem !== this._onScrollActiveItem) { - this._onScrollActiveItem = nextActiveItem; - onScrollIndexChanged && + if (nextActiveItem !== this._onScrollActiveItem) { + this._onScrollActiveItem = nextActiveItem; + onScrollIndexChanged && onScrollIndexChanged(this._getDataIndex(nextActiveItem)); - } + } - if (typeof onScroll === 'function' && event) { - onScroll(event); - } + if (typeof onScroll === 'function' && event) { + onScroll(event); + } } - _onMomentumScrollEnd(event: NativeSyntheticEvent) { - const { autoplayDelay, onMomentumScrollEnd, onSnapToItem } = this.props; - const scrollOffset = event - ? this._getScrollOffset(event) - : this._currentScrollOffset; - const nextActiveItem = this._getActiveItem(scrollOffset); - const hasSnapped = this._isMultiple( - scrollOffset, - this.props.vertical ? this.props.itemHeight : this.props.itemWidth - ); - - // WARNING: everything in this condition will probably need to be called on _snapToItem as well because: - // 1. `onMomentumScrollEnd` won't be called if the scroll isn't animated - // 2. `onMomentumScrollEnd` won't be called at all on Android when scrolling programmatically - if (nextActiveItem !== this._activeItem) { - this._activeItem = nextActiveItem; - onSnapToItem && onSnapToItem(this._getDataIndex(nextActiveItem)); - - if (hasSnapped) { - this._repositionScroll(nextActiveItem); + _onMomentumScrollEnd (event: NativeSyntheticEvent) { + const { autoplayDelay, onMomentumScrollEnd, onSnapToItem } = this.props; + const scrollOffset = event ? + this._getScrollOffset(event) : + this._currentScrollOffset; + const nextActiveItem = this._getActiveItem(scrollOffset); + const hasSnapped = this._isMultiple( + scrollOffset, + this.props.vertical ? this.props.itemHeight : this.props.itemWidth + ); + + // WARNING: everything in this condition will probably need to be called on _snapToItem as well because: + // 1. `onMomentumScrollEnd` won't be called if the scroll isn't animated + // 2. `onMomentumScrollEnd` won't be called at all on Android when scrolling programmatically + if (nextActiveItem !== this._activeItem) { + this._activeItem = nextActiveItem; + onSnapToItem && onSnapToItem(this._getDataIndex(nextActiveItem)); + + if (hasSnapped) { + this._repositionScroll(nextActiveItem); + } } - } - onMomentumScrollEnd && onMomentumScrollEnd(event); + onMomentumScrollEnd && onMomentumScrollEnd(event); - // The touchEnd event is buggy on Android, so this will serve as a fallback whenever needed - // https://github.com/facebook/react-native/issues/9439 - if (IS_ANDROID && this._autoplay && !this._autoplaying) { + // The touchEnd event is buggy on Android, so this will serve as a fallback whenever needed + // https://github.com/facebook/react-native/issues/9439 + if (IS_ANDROID && this._autoplay && !this._autoplaying) { // @ts-expect-error setTimeout / clearTiemout is buggy :/ - clearTimeout(this._enableAutoplayTimeout); - this._enableAutoplayTimeout = setTimeout(() => { - this.startAutoplay(); - }, autoplayDelay); - } + clearTimeout(this._enableAutoplayTimeout); + this._enableAutoplayTimeout = setTimeout(() => { + this.startAutoplay(); + }, autoplayDelay); + } } - _onLayout(event: LayoutChangeEvent) { - const { onLayout } = this.props; + _onLayout (event: LayoutChangeEvent) { + const { onLayout } = this.props; - // Prevent unneeded actions during the first 'onLayout' (triggered on init) - if (this._onLayoutInitDone) { - this._initPositionsAndInterpolators(); - this._snapToItem(this._activeItem, false, false, true); - } else { - this._onLayoutInitDone = true; - } + // Prevent unneeded actions during the first 'onLayout' (triggered on init) + if (this._onLayoutInitDone) { + this._initPositionsAndInterpolators(); + this._snapToItem(this._activeItem, false, false, true); + } else { + this._onLayoutInitDone = true; + } - onLayout && onLayout(event); + onLayout && onLayout(event); } - _snapToItem( - index: number, - animated = true, - fireCallback = true, - forceScrollTo = false + _snapToItem ( + index: number, + animated = true, + fireCallback = true, + forceScrollTo = false ) { - const { onSnapToItem } = this.props; - const itemsLength = this._getCustomDataLength(); - const wrappedRef = this._getWrappedRef(); - - if (!itemsLength || !wrappedRef) { - return; - } - - if (!index || index < 0) { - index = 0; - } else if (itemsLength > 0 && index >= itemsLength) { - index = itemsLength - 1; - } - - if (index === this._activeItem && !forceScrollTo) { - return; - } - - const offset = this._getItemScrollOffset(index); - - if (offset === undefined) { - return; - } - - this._scrollTo({ offset, animated }); - - // On both platforms, `onMomentumScrollEnd` won't be triggered if the scroll isn't animated - // so we need to trigger the callback manually - // On Android `onMomentumScrollEnd` won't be triggered when scrolling programmatically - // Therefore everything critical needs to be manually called here as well, even though the timing might be off - const requiresManualTrigger = !animated || IS_ANDROID; - if (requiresManualTrigger) { - this._activeItem = index; - - if (fireCallback) { - onSnapToItem && onSnapToItem(this._getDataIndex(index)); + const { onSnapToItem } = this.props; + const itemsLength = this._getCustomDataLength(); + const wrappedRef = this._getWrappedRef(); + + if (!itemsLength || !wrappedRef) { + return; + } + + if (!index || index < 0) { + index = 0; + } else if (itemsLength > 0 && index >= itemsLength) { + index = itemsLength - 1; } - // Repositioning on Android - if (IS_ANDROID && this._shouldRepositionScroll(index)) { - if (animated) { - this._androidRepositioningTimeout = setTimeout(() => { - // Without scroll animation, the behavior is completely buggy... - this._repositionScroll(index, true); - }, 400); // Approximate scroll duration on Android - } else { - this._repositionScroll(index); - } + if (index === this._activeItem && !forceScrollTo) { + return; + } + + const offset = this._getItemScrollOffset(index); + + if (offset === undefined) { + return; + } + + this._scrollTo({ offset, animated }); + + // On both platforms, `onMomentumScrollEnd` won't be triggered if the scroll isn't animated + // so we need to trigger the callback manually + // On Android `onMomentumScrollEnd` won't be triggered when scrolling programmatically + // Therefore everything critical needs to be manually called here as well, even though the timing might be off + const requiresManualTrigger = !animated || IS_ANDROID; + if (requiresManualTrigger) { + this._activeItem = index; + + if (fireCallback) { + onSnapToItem && onSnapToItem(this._getDataIndex(index)); + } + + // Repositioning on Android + if (IS_ANDROID && this._shouldRepositionScroll(index)) { + if (animated) { + this._androidRepositioningTimeout = setTimeout(() => { + // Without scroll animation, the behavior is completely buggy... + this._repositionScroll(index, true); + }, 400); // Approximate scroll duration on Android + } else { + this._repositionScroll(index); + } + } } - } } - startAutoplay() { - const { autoplayInterval, autoplayDelay } = this.props; - this._autoplay = true; - - if (this._autoplaying) { - return; - } - - // @ts-expect-error setTimeout / clearTiemout is buggy :/ - clearTimeout(this._autoplayTimeout); - this._autoplayTimeout = setTimeout(() => { - this._autoplaying = true; - this._autoplayInterval = setInterval(() => { - if (this._autoplaying) { - this.snapToNext(); - } - }, autoplayInterval); - }, autoplayDelay); + startAutoplay () { + const { autoplayInterval, autoplayDelay } = this.props; + this._autoplay = true; + + if (this._autoplaying) { + return; + } + + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._autoplayTimeout); + this._autoplayTimeout = setTimeout(() => { + this._autoplaying = true; + this._autoplayInterval = setInterval(() => { + if (this._autoplaying) { + this.snapToNext(); + } + }, autoplayInterval); + }, autoplayDelay); } - pauseAutoPlay() { - this._autoplaying = false; - // @ts-expect-error setTimeout / clearTiemout is buggy :/ - clearTimeout(this._autoplayTimeout); - // @ts-expect-error setTimeout / clearTiemout is buggy :/ - clearTimeout(this._enableAutoplayTimeout); - // @ts-expect-error setTimeout / clearTiemout is buggy :/ - clearInterval(this._autoplayInterval); + pauseAutoPlay () { + this._autoplaying = false; + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._autoplayTimeout); + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearTimeout(this._enableAutoplayTimeout); + // @ts-expect-error setTimeout / clearTiemout is buggy :/ + clearInterval(this._autoplayInterval); } - stopAutoplay() { - this._autoplay = false; - this.pauseAutoPlay(); + stopAutoplay () { + this._autoplay = false; + this.pauseAutoPlay(); } - snapToItem(index: number, animated = true, fireCallback = true) { - if (!index || index < 0) { - index = 0; - } + snapToItem (index: number, animated = true, fireCallback = true) { + if (!index || index < 0) { + index = 0; + } - const positionIndex = this._getPositionIndex(index); + const positionIndex = this._getPositionIndex(index); - if (positionIndex === this._activeItem) { - return; - } + if (positionIndex === this._activeItem) { + return; + } - this._snapToItem(positionIndex, animated, fireCallback); + this._snapToItem(positionIndex, animated, fireCallback); } - snapToNext(animated = true, fireCallback = true) { - const itemsLength = this._getCustomDataLength(); + snapToNext (animated = true, fireCallback = true) { + const itemsLength = this._getCustomDataLength(); - let newIndex = this._activeItem + 1; - if (newIndex > itemsLength - 1) { - newIndex = 0; - } - this._snapToItem(newIndex, animated, fireCallback); + let newIndex = this._activeItem + 1; + if (newIndex > itemsLength - 1) { + newIndex = 0; + } + this._snapToItem(newIndex, animated, fireCallback); } - snapToPrev(animated = true, fireCallback = true) { - const itemsLength = this._getCustomDataLength(); + snapToPrev (animated = true, fireCallback = true) { + const itemsLength = this._getCustomDataLength(); - let newIndex = this._activeItem - 1; - if (newIndex < 0) { - newIndex = itemsLength - 1; - } - this._snapToItem(newIndex, animated, fireCallback); + let newIndex = this._activeItem - 1; + if (newIndex < 0) { + newIndex = itemsLength - 1; + } + this._snapToItem(newIndex, animated, fireCallback); } // https://github.com/facebook/react-native/issues/1831#issuecomment-231069668 - triggerRenderingHack(offset = 1) { - this._hackActiveSlideAnimation(this._activeItem, offset); + triggerRenderingHack (offset = 1) { + this._hackActiveSlideAnimation(this._activeItem, offset); } - _renderItem({ item, index }: { item: TData; index: number }) { - const { interpolators } = this.state; - const { - hasParallaxImages, - itemWidth, - itemHeight, - keyExtractor, - renderItem, - sliderHeight, - sliderWidth, - slideStyle, - vertical, - } = this.props; - const animatedValue = interpolators && interpolators[index]; - - if (typeof animatedValue === 'undefined') { - return null; - } - - const animate = this._shouldAnimateSlides(); - const Component = animate ? Animated.View : View; - const animatedStyle = animate - ? this._getSlideInterpolatedStyle(index, animatedValue) - : {}; - const dataIndex = this._getDataIndex(index); - - const parallaxProps = hasParallaxImages - ? { - scrollPosition: this._scrollPos, - carouselRef: this._carouselRef, - vertical, - sliderWidth, - sliderHeight, + _renderItem ({ item, index }: { item: TData; index: number }) { + const { interpolators } = this.state; + const { + hasParallaxImages, itemWidth, itemHeight, - } - : undefined; - - const mainDimension = vertical - ? { height: itemHeight } - : { width: itemWidth }; - const specificProps = this._needsScrollView() - ? { - key: keyExtractor - ? keyExtractor(item, index) - : this._getKeyExtractor(item, index), - } - : {}; - - return ( - - {renderItem({ item, index, dataIndex }, parallaxProps)} - - ); + keyExtractor, + renderItem, + sliderHeight, + sliderWidth, + slideStyle, + vertical + } = this.props; + const animatedValue = interpolators && interpolators[index]; + + if (typeof animatedValue === 'undefined') { + return null; + } + + const animate = this._shouldAnimateSlides(); + const Component = animate ? Animated.View : View; + const animatedStyle = animate ? + this._getSlideInterpolatedStyle(index, animatedValue) : + {}; + const dataIndex = this._getDataIndex(index); + + const parallaxProps = hasParallaxImages ? + { + scrollPosition: this._scrollPos, + carouselRef: this._carouselRef, + vertical, + sliderWidth, + sliderHeight, + itemWidth, + itemHeight + } : + undefined; + + const mainDimension = vertical ? + { height: itemHeight } : + { width: itemWidth }; + const specificProps = this._needsScrollView() ? + { + key: keyExtractor ? + keyExtractor(item, index) : + this._getKeyExtractor(item, index) + } : + {}; + + return ( + + {renderItem({ item, index, dataIndex }, parallaxProps)} + + ); } - _getComponentOverridableProps() { - const { hideCarousel } = this.state; - const { loopClonesPerSide } = this.props; - const visibleItems = + _getComponentOverridableProps () { + const { hideCarousel } = this.state; + const { loopClonesPerSide } = this.props; + const visibleItems = Math.ceil( - this.props.vertical - ? this.props.sliderHeight / this.props.itemHeight - : this.props.sliderWidth / this.props.itemWidth + this.props.vertical ? + this.props.sliderHeight / this.props.itemHeight : + this.props.sliderWidth / this.props.itemWidth ) + 1; - const initialNumPerSide = this._enableLoop() ? loopClonesPerSide : 2; - const initialNumToRender = visibleItems + initialNumPerSide * 2; - const maxToRenderPerBatch = initialNumToRender + initialNumPerSide * 2; - const windowSize = maxToRenderPerBatch; - - const specificProps = !this._needsScrollView() - ? { - initialNumToRender, - maxToRenderPerBatch, - windowSize, + const initialNumPerSide = this._enableLoop() ? loopClonesPerSide : 2; + const initialNumToRender = visibleItems + initialNumPerSide * 2; + const maxToRenderPerBatch = initialNumToRender + initialNumPerSide * 2; + const windowSize = maxToRenderPerBatch; + + const specificProps = !this._needsScrollView() ? + { + initialNumToRender, + maxToRenderPerBatch, + windowSize // updateCellsBatchingPeriod - } - : {}; - - return { - ...specificProps, - automaticallyAdjustContentInsets: false, - decelerationRate: 'fast', - directionalLockEnabled: true, - disableScrollViewPanResponder: false, // If set to `true`, touch events will be triggered too easily - inverted: this._needsRTLAdaptations(), - overScrollMode: 'never', - pinchGestureEnabled: false, - pointerEvents: hideCarousel ? 'none' : 'auto', - // removeClippedSubviews: !this._needsScrollView(), - // renderToHardwareTextureAndroid: true, - scrollsToTop: false, - showsHorizontalScrollIndicator: false, - showsVerticalScrollIndicator: false, - }; + } : + {}; + + return { + ...specificProps, + automaticallyAdjustContentInsets: false, + decelerationRate: 'fast' as const, + directionalLockEnabled: true, + disableScrollViewPanResponder: false, // If set to `true`, touch events will be triggered too easily + inverted: this._needsRTLAdaptations(), + overScrollMode: 'never' as const, + pinchGestureEnabled: false, + pointerEvents: hideCarousel ? 'none' as const : 'auto' as const, + // removeClippedSubviews: !this._needsScrollView(), + // renderToHardwareTextureAndroid: true, + scrollsToTop: false, + showsHorizontalScrollIndicator: false, + showsVerticalScrollIndicator: false + }; } - _getComponentStaticProps() { - const { hideCarousel } = this.state; - const { - activeSlideAlignment, - CellRendererComponent, - containerCustomStyle, - contentContainerCustomStyle, - firstItem, - getItemLayout, - keyExtractor, - sliderWidth, - sliderHeight, - style, - useExperimentalSnap, - vertical, - } = this.props; + _getComponentStaticProps () { + const { hideCarousel } = this.state; + const { + activeSlideAlignment, + CellRendererComponent, + containerCustomStyle, + contentContainerCustomStyle, + firstItem, + getItemLayout, + keyExtractor, + sliderWidth, + sliderHeight, + style, + useExperimentalSnap, + vertical + } = this.props; - const containerStyle = [ + const containerStyle = [ // { overflow: 'hidden' }, - containerCustomStyle || style || {}, - hideCarousel ? { opacity: 0 } : {}, - vertical - ? { height: sliderHeight, flexDirection: 'column' } - : // LTR hack; see https://github.com/facebook/react-native/issues/11960 + containerCustomStyle || style || {}, + hideCarousel ? { opacity: 0 } : {}, + vertical ? + { height: sliderHeight, flexDirection: 'column' as const } : // LTR hack; see https://github.com/facebook/react-native/issues/11960 // and https://github.com/facebook/react-native/issues/13100#issuecomment-328986423 + { + width: sliderWidth, + flexDirection: this._needsRTLAdaptations() ? 'row-reverse' as const : 'row' as const + } + ]; + + const innerMarginStyle = vertical ? + { + paddingTop: this._getContainerInnerMargin(), + paddingBottom: this._getContainerInnerMargin(true) + } : + { + paddingLeft: this._getContainerInnerMargin(), + paddingRight: this._getContainerInnerMargin(true) + }; + + const contentContainerStyle = [ + !useExperimentalSnap ? innerMarginStyle : {}, + contentContainerCustomStyle || {} + ]; + + // WARNING: `snapToAlignment` won't work as intended because of the following: + // https://github.com/facebook/react-native/blob/d0871d0a9a373e1d3ac35da46c85c0d0e793116d/React/Views/ScrollView/RCTScrollView.m#L751-L755 + // - Snap points will be off + // - Slide animations will be off + // - Last items won't be set as active (no `onSnapToItem` callback) + // Recommended only with large slides and `activeSlideAlignment` set to `start` for the time being + const snapProps = useExperimentalSnap ? { - width: sliderWidth, - flexDirection: this._needsRTLAdaptations() ? 'row-reverse' : 'row', - }, - ]; - - const innerMarginStyle = vertical - ? { - paddingTop: this._getContainerInnerMargin(), - paddingBottom: this._getContainerInnerMargin(true), - } - : { - paddingLeft: this._getContainerInnerMargin(), - paddingRight: this._getContainerInnerMargin(true), - }; - - const contentContainerStyle = [ - !useExperimentalSnap ? innerMarginStyle : {}, - contentContainerCustomStyle || {}, - ]; - - // WARNING: `snapToAlignment` won't work as intended because of the following: - // https://github.com/facebook/react-native/blob/d0871d0a9a373e1d3ac35da46c85c0d0e793116d/React/Views/ScrollView/RCTScrollView.m#L751-L755 - // - Snap points will be off - // - Slide animations will be off - // - Last items won't be set as active (no `onSnapToItem` callback) - // Recommended only with large slides and `activeSlideAlignment` set to `start` for the time being - const snapProps = useExperimentalSnap - ? { // disableIntervalMomentum: true, // Slide ± one item at a time - snapToAlignment: activeSlideAlignment, - snapToInterval: this._getItemMainDimension(), - } - : { - snapToOffsets: this._getSnapOffsets(), - }; - - // Flatlist specifics - const specificProps = !this._needsScrollView() - ? { - CellRendererComponent: + snapToAlignment: activeSlideAlignment, + snapToInterval: this._getItemMainDimension() + } : + { + snapToOffsets: this._getSnapOffsets() + }; + + // Flatlist specifics + const specificProps = !this._needsScrollView() ? + { + CellRendererComponent: CellRendererComponent || this._getCellRendererComponent, - getItemLayout: getItemLayout || this._getItemLayout, - initialScrollIndex: this._getFirstItem(firstItem), - keyExtractor: keyExtractor || this._getKeyExtractor, - numColumns: 1, - renderItem: this._renderItem, - } - : {}; - - return { - ...specificProps, - ...snapProps, - ref: (c: any) => { - this._carouselRef = c; - }, - contentContainerStyle: contentContainerStyle, - data: this._getCustomData(), - horizontal: !vertical, - scrollEventThrottle: 1, - style: containerStyle, - onLayout: this._onLayout, - onMomentumScrollEnd: this._onMomentumScrollEnd, - onScroll: this._onScrollHandler, - onTouchStart: this._onTouchStart, - onTouchEnd: this._onTouchEnd, - }; + getItemLayout: getItemLayout || this._getItemLayout, + initialScrollIndex: this._getFirstItem(firstItem), + keyExtractor: keyExtractor || this._getKeyExtractor, + numColumns: 1, + renderItem: this._renderItem + } : + {}; + + return { + ...specificProps, + ...snapProps, + ref: (c: unknown) => { + this._carouselRef = c as FlatList | ScrollView; + }, + contentContainerStyle: contentContainerStyle, + data: this._getCustomData(), + horizontal: !vertical, + scrollEventThrottle: 1, + style: containerStyle, + onLayout: this._onLayout, + onMomentumScrollEnd: this._onMomentumScrollEnd, + onScroll: this._onScrollHandler, + onTouchStart: this._onTouchStart, + onTouchEnd: this._onTouchEnd + }; } - render() { - const { data, renderItem, useScrollView } = this.props; + render () { + const { data, renderItem, useScrollView } = this.props; - if (!data || !renderItem) { - return null; - } + if (!data || !renderItem) { + return null; + } - const props = { - ...this._getComponentOverridableProps(), - ...this.props, - ...this._getComponentStaticProps(), - }; + const props = { + ...this._getComponentOverridableProps(), + ...this.props, + ...this._getComponentStaticProps() + }; - const ScrollViewComponent = + const ScrollViewComponent = typeof useScrollView === 'function' ? useScrollView : AnimatedScrollView; - return this._needsScrollView() || !AnimatedFlatList ? ( - - {this._getCustomData().map((item, index) => { - return this._renderItem({ item, index }); - })} - - ) : ( - - ); + return this._needsScrollView() || !AnimatedFlatList ? ( + + {this._getCustomData().map((item, index) => { + return this._renderItem({ item, index }); + })} + + ) : ( + + ); } } diff --git a/src/carousel/types.ts b/src/carousel/types.ts index a5d3e2a05..28ac2b9ce 100644 --- a/src/carousel/types.ts +++ b/src/carousel/types.ts @@ -1,18 +1,29 @@ import type { - StyleProp, - ViewStyle, - Animated, - NativeScrollEvent, - NativeSyntheticEvent, - FlatListProps, + StyleProp, + ViewStyle, + Animated, + NativeScrollEvent, + NativeSyntheticEvent, + FlatListProps, + ScrollView, + FlatList } from 'react-native'; +import type { ReactNode } from 'react'; type CarouselBaseProps = { data: TData[]; renderItem: ( baseData: { index: number; dataIndex: number; item: TData }, - parallaxData?: any - ) => any; + parallaxData?: { + scrollPosition: Animated.Value | undefined, + carouselRef: ScrollView | FlatList | null, + vertical?: boolean, + sliderWidth?: number, + sliderHeight?: number, + itemWidth?: number, + itemHeight?: number + } + ) => ReactNode; activeSlideAlignment: 'center' | 'end' | 'start'; activeSlideOffset: number; apparitionDelay: number; @@ -33,19 +44,19 @@ type CarouselBaseProps = { loop: boolean; loopClonesPerSide: number; scrollEnabled: boolean; - // TODO: check real type later - scrollInterpolator: (index: number, props: CarouselBaseProps) => any; - // TODO: check real type later + scrollInterpolator: (index: number, props: CarouselBaseProps) => { + inputRange: number[]; + outputRange: number[]; + }; slideInterpolatedStyle: ( index: number, - animatedValue: Animated.Value, + animatedValue: Animated.AnimatedInterpolation, props: CarouselBaseProps - ) => any; - slideStyle?: StyleProp; + ) => StyleProp; + slideStyle?: Animated.WithAnimatedValue>; shouldOptimizeUpdates: boolean; useExperimentalSnap: boolean; - // TODO: check real type later - useScrollView: boolean | React.ComponentType; + useScrollView: boolean | React.ComponentType; onScroll?: (event: NativeSyntheticEvent) => void; onScrollIndexChanged?: (index: number) => void; onSnapToItem?: (index: number) => void; @@ -85,5 +96,5 @@ export type CarouselProps = CarouselBaseProps & export type CarouselState = { hideCarousel: boolean; - interpolators: any[]; + interpolators: (Animated.Value | Animated.AnimatedInterpolation)[]; }; diff --git a/src/index.ts b/src/index.ts index 5806bbbce..1535ac5bd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,9 +4,9 @@ import ParallaxImage from './parallaximage/ParallaxImage'; import { getInputRangeFromIndexes } from './utils/animations'; export { - Carousel as default, - Carousel, - Pagination, - ParallaxImage, - getInputRangeFromIndexes, + Carousel as default, + Carousel, + Pagination, + ParallaxImage, + getInputRangeFromIndexes }; diff --git a/src/pagination/Pagination.style.ts b/src/pagination/Pagination.style.ts index b2c20fc62..4911924f3 100644 --- a/src/pagination/Pagination.style.ts +++ b/src/pagination/Pagination.style.ts @@ -4,21 +4,21 @@ const DEFAULT_DOT_SIZE = 7; const DEFAULT_DOT_COLOR = 'rgba(0, 0, 0, 0.75)'; export default StyleSheet.create({ - sliderPagination: { - alignItems: 'center', - justifyContent: 'center', - paddingHorizontal: 20, - paddingVertical: 30, - }, - sliderPaginationDotContainer: { - alignItems: 'center', - justifyContent: 'center', - marginHorizontal: 8, - }, - sliderPaginationDot: { - width: DEFAULT_DOT_SIZE, - height: DEFAULT_DOT_SIZE, - borderRadius: DEFAULT_DOT_SIZE / 2, - backgroundColor: DEFAULT_DOT_COLOR, - }, + sliderPagination: { + alignItems: 'center', + justifyContent: 'center', + paddingHorizontal: 20, + paddingVertical: 30 + }, + sliderPaginationDotContainer: { + alignItems: 'center', + justifyContent: 'center', + marginHorizontal: 8 + }, + sliderPaginationDot: { + width: DEFAULT_DOT_SIZE, + height: DEFAULT_DOT_SIZE, + borderRadius: DEFAULT_DOT_SIZE / 2, + backgroundColor: DEFAULT_DOT_COLOR + } }); diff --git a/src/pagination/Pagination.tsx b/src/pagination/Pagination.tsx index 953f5998f..97184a54b 100644 --- a/src/pagination/Pagination.tsx +++ b/src/pagination/Pagination.tsx @@ -1,10 +1,10 @@ import React, { PureComponent, ReactElement } from 'react'; import { - I18nManager, - Platform, - View, - StyleProp, - ViewStyle, + I18nManager, + Platform, + View, + StyleProp, + ViewStyle } from 'react-native'; import PaginationDot from './PaginationDot'; import styles from './Pagination.style'; @@ -17,7 +17,7 @@ type PaginationProps = { activeDotIndex: number; dotsLength: number; activeOpacity?: number; - carouselRef?: Carousel | null; + carouselRef?: Carousel | null; containerStyle?: StyleProp; dotColor?: string; dotContainerStyle?: StyleProp; @@ -44,151 +44,151 @@ type PaginationProps = { export default class Pagination extends PureComponent { static defaultProps = { - inactiveDotOpacity: 0.5, - inactiveDotScale: 0.5, - tappableDots: false, - vertical: false, - animatedDuration: 250, - animatedFriction: 4, - animatedTension: 50, - delayPressInDot: 0, + inactiveDotOpacity: 0.5, + inactiveDotScale: 0.5, + tappableDots: false, + vertical: false, + animatedDuration: 250, + animatedFriction: 4, + animatedTension: 50, + delayPressInDot: 0 }; - constructor(props: PaginationProps) { - super(props); + constructor (props: PaginationProps) { + super(props); - // Warnings - if ( - (props.dotColor && !props.inactiveDotColor) || + // Warnings + if ( + (props.dotColor && !props.inactiveDotColor) || (!props.dotColor && props.inactiveDotColor) - ) { - console.warn( - 'react-native-snap-carousel | Pagination: ' + + ) { + console.warn( + 'react-native-snap-carousel | Pagination: ' + 'You need to specify both `dotColor` and `inactiveDotColor`' - ); - } - if ( - (props.dotElement && !props.inactiveDotElement) || + ); + } + if ( + (props.dotElement && !props.inactiveDotElement) || (!props.dotElement && props.inactiveDotElement) - ) { - console.warn( - 'react-native-snap-carousel | Pagination: ' + + ) { + console.warn( + 'react-native-snap-carousel | Pagination: ' + 'You need to specify both `dotElement` and `inactiveDotElement`' - ); - } - if (props.tappableDots && props.carouselRef === undefined) { - console.warn( - 'react-native-snap-carousel | Pagination: ' + + ); + } + if (props.tappableDots && props.carouselRef === undefined) { + console.warn( + 'react-native-snap-carousel | Pagination: ' + 'You must specify prop `carouselRef` when setting `tappableDots` to `true`' - ); - } + ); + } } - _needsRTLAdaptations() { - const { vertical } = this.props; - return IS_RTL && !IS_IOS && !vertical; + _needsRTLAdaptations () { + const { vertical } = this.props; + return IS_RTL && !IS_IOS && !vertical; } - get _activeDotIndex() { - const { activeDotIndex, dotsLength } = this.props; - return this._needsRTLAdaptations() - ? dotsLength - activeDotIndex - 1 - : activeDotIndex; + get _activeDotIndex () { + const { activeDotIndex, dotsLength } = this.props; + return this._needsRTLAdaptations() ? + dotsLength - activeDotIndex - 1 : + activeDotIndex; } - get dots() { - const { - activeOpacity, - carouselRef, - dotsLength, - dotColor, - dotContainerStyle, - dotElement, - dotStyle, - inactiveDotColor, - inactiveDotElement, - inactiveDotOpacity, - inactiveDotScale, - inactiveDotStyle, - renderDots, - tappableDots, - animatedDuration, - animatedFriction, - animatedTension, - delayPressInDot, - } = this.props; - - if (renderDots) { - return renderDots(this._activeDotIndex, dotsLength, this); - } - - const DefaultDot = ( - - ); - - const dots = [...Array(dotsLength).keys()].map((i) => { - const isActive = i === this._activeDotIndex; - return React.cloneElement( - (isActive ? dotElement : inactiveDotElement) || DefaultDot, - { - key: `pagination-dot-${i}`, - active: isActive, - index: i, - } + get dots () { + const { + activeOpacity, + carouselRef, + dotsLength, + dotColor, + dotContainerStyle, + dotElement, + dotStyle, + inactiveDotColor, + inactiveDotElement, + inactiveDotOpacity, + inactiveDotScale, + inactiveDotStyle, + renderDots, + tappableDots, + animatedDuration, + animatedFriction, + animatedTension, + delayPressInDot + } = this.props; + + if (renderDots) { + return renderDots(this._activeDotIndex, dotsLength, this); + } + + const DefaultDot = ( + ); - }); - return dots; + const dots = [...Array(dotsLength).keys()].map((i) => { + const isActive = i === this._activeDotIndex; + return React.cloneElement( + (isActive ? dotElement : inactiveDotElement) || DefaultDot, + { + key: `pagination-dot-${i}`, + active: isActive, + index: i + } + ); + }); + + return dots; } - render() { - const { - dotsLength, - containerStyle, - vertical, - accessibilityLabel, - } = this.props; - - if (!dotsLength || dotsLength < 2) { - return false; - } - - const style = [ - styles.sliderPagination, - { - flexDirection: vertical - ? ('column' as const) - : this._needsRTLAdaptations() - ? ('row-reverse' as const) - : ('row' as const), - }, - containerStyle || {}, - ]; - - return ( - - {this.dots} - - ); + render () { + const { + dotsLength, + containerStyle, + vertical, + accessibilityLabel + } = this.props; + + if (!dotsLength || dotsLength < 2) { + return false; + } + + const style = [ + styles.sliderPagination, + { + flexDirection: vertical ? + ('column' as const) : + this._needsRTLAdaptations() ? + ('row-reverse' as const) : + ('row' as const) + }, + containerStyle || {} + ]; + + return ( + + {this.dots} + + ); } } diff --git a/src/pagination/PaginationDot.tsx b/src/pagination/PaginationDot.tsx index caa3ddb7c..07289c497 100644 --- a/src/pagination/PaginationDot.tsx +++ b/src/pagination/PaginationDot.tsx @@ -1,10 +1,10 @@ import React, { PureComponent, RefObject } from 'react'; import { - Animated, - Easing, - TouchableOpacity, - StyleProp, - ViewStyle, + Animated, + Easing, + TouchableOpacity, + StyleProp, + ViewStyle } from 'react-native'; import styles from './Pagination.style'; import type Carousel from 'src/carousel/Carousel'; @@ -17,7 +17,7 @@ type PaginationDotProps = { animatedDuration?: number; animatedFriction?: number; animatedTension?: number; - carouselRef?: Carousel | RefObject> | null; + carouselRef?: Carousel | RefObject> | null; color?: string; containerStyle?: StyleProp; delayPressInDot?: number; @@ -38,151 +38,152 @@ export default class PaginationDot extends PureComponent< PaginationDotProps, PaginationDotState > { - constructor(props: PaginationDotProps) { - super(props); - this.state = { - animColor: new Animated.Value(0), - animOpacity: new Animated.Value(0), - animTransform: new Animated.Value(0), - }; - } - - componentDidMount() { - if (this.props.active) { - this._animate(1); + constructor (props: PaginationDotProps) { + super(props); + this.state = { + animColor: new Animated.Value(0), + animOpacity: new Animated.Value(0), + animTransform: new Animated.Value(0) + }; } - } - componentDidUpdate(prevProps: PaginationDotProps) { - if (prevProps.active !== this.props.active) { - this._animate(this.props.active ? 1 : 0); + componentDidMount () { + if (this.props.active) { + this._animate(1); + } } - } - - _animate(toValue = 0) { - const { animColor, animOpacity, animTransform } = this.state; - const { animatedDuration, animatedFriction, animatedTension } = this.props; - - const commonProperties = { - toValue, - isInteraction: false, - useNativeDriver: !this._shouldAnimateColor, - }; - - let animations = [ - Animated.timing(animOpacity, { - easing: Easing.linear, - duration: animatedDuration, - ...commonProperties, - }), - Animated.spring(animTransform, { - friction: animatedFriction, - tension: animatedTension, - ...commonProperties, - }), - ]; - - if (this._shouldAnimateColor) { - animations.push( - Animated.timing(animColor, { - easing: Easing.linear, - ...commonProperties, - }) - ); + + componentDidUpdate (prevProps: PaginationDotProps) { + if (prevProps.active !== this.props.active) { + this._animate(this.props.active ? 1 : 0); + } } - Animated.parallel(animations).start(); - } - - get _shouldAnimateColor() { - const { color, inactiveColor } = this.props; - return color && inactiveColor; - } - - render() { - const { animColor, animOpacity, animTransform } = this.state; - const { - active, - activeOpacity, - carouselRef, - color, - containerStyle, - inactiveColor, - inactiveStyle, - inactiveOpacity, - inactiveScale, - index, - style, - tappable, - delayPressInDot, - } = this.props; - - const animatedStyle = { - opacity: animOpacity.interpolate({ - inputRange: [0, 1], - outputRange: [inactiveOpacity, 1], - }), - transform: [ - { - scale: animTransform.interpolate({ - inputRange: [0, 1], - outputRange: [inactiveScale, 1], - }), - }, - ], - }; - const animatedColor = - this._shouldAnimateColor && inactiveColor && color - ? { - backgroundColor: animColor.interpolate({ - inputRange: [0, 1], - outputRange: [inactiveColor, color], + _animate (toValue = 0) { + const { animColor, animOpacity, animTransform } = this.state; + const { animatedDuration, animatedFriction, animatedTension } = this.props; + + const commonProperties = { + toValue, + isInteraction: false, + useNativeDriver: !this._shouldAnimateColor + }; + + const animations = [ + Animated.timing(animOpacity, { + easing: Easing.linear, + duration: animatedDuration, + ...commonProperties }), - } - : {}; - - const dotContainerStyle = [ - styles.sliderPaginationDotContainer, - containerStyle || {}, - ]; - - const dotStyle = [ - styles.sliderPaginationDot, - style || {}, - (!active && inactiveStyle) || {}, - animatedStyle, - animatedColor, - ]; - - const onPress = - tappable && (!!index || index === 0) - ? () => { - try { - const currentRef = - carouselRef && 'current' in carouselRef - ? carouselRef.current - : carouselRef; + Animated.spring(animTransform, { + friction: animatedFriction, + tension: animatedTension, + ...commonProperties + }) + ]; + + if (this._shouldAnimateColor) { + animations.push( + Animated.timing(animColor, { + easing: Easing.linear, + ...commonProperties + }) + ); + } + + Animated.parallel(animations).start(); + } + + get _shouldAnimateColor () { + const { color, inactiveColor } = this.props; + return color && inactiveColor; + } + + render () { + const { animColor, animOpacity, animTransform } = this.state; + const { + active, + activeOpacity, + carouselRef, + color, + containerStyle, + inactiveColor, + inactiveStyle, + inactiveOpacity, + inactiveScale, + index, + style, + tappable, + delayPressInDot + } = this.props; + + const animatedStyle = { + opacity: animOpacity.interpolate({ + inputRange: [0, 1], + outputRange: [inactiveOpacity, 1] + }), + transform: [ + { + scale: animTransform.interpolate({ + inputRange: [0, 1], + outputRange: [inactiveScale, 1] + }) + } + ] + }; + const animatedColor = + this._shouldAnimateColor && inactiveColor && color ? + { + backgroundColor: animColor.interpolate({ + inputRange: [0, 1], + outputRange: [inactiveColor, color] + }) + } : + {}; + + const dotContainerStyle = [ + styles.sliderPaginationDotContainer, + containerStyle || {} + ]; + + const dotStyle = [ + styles.sliderPaginationDot, + style || {}, + (!active && inactiveStyle) || {}, + animatedStyle, + animatedColor + ]; + + const onPress = + tappable && (!!index || index === 0) ? + () => { + try { + const currentRef = + carouselRef && 'current' in carouselRef ? + carouselRef.current : + carouselRef; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion currentRef!._snapToItem(currentRef!._getPositionIndex(index)); - } catch (error) { - console.warn( - 'react-native-snap-carousel | Pagination: ' + + } catch (error) { + console.warn( + 'react-native-snap-carousel | Pagination: ' + '`carouselRef` has to be a Carousel ref.\n' + error - ); - } - } - : undefined; - - return ( - - - - ); - } + ); + } + } : + undefined; + + return ( + + + + ); + } } diff --git a/src/parallaximage/ParallaxImage.style.ts b/src/parallaximage/ParallaxImage.style.ts index fe03069a6..6c0383617 100644 --- a/src/parallaximage/ParallaxImage.style.ts +++ b/src/parallaximage/ParallaxImage.style.ts @@ -1,20 +1,20 @@ import { StyleSheet } from 'react-native'; export default StyleSheet.create({ - container: { - overflow: 'hidden', - alignItems: 'center', - justifyContent: 'center', - }, - image: { - position: 'relative', - resizeMode: 'cover', - width: undefined, - height: undefined, - }, - loaderContainer: { - ...StyleSheet.absoluteFillObject, - alignItems: 'center', - justifyContent: 'center', - }, + container: { + overflow: 'hidden', + alignItems: 'center', + justifyContent: 'center' + }, + image: { + position: 'relative', + resizeMode: 'cover', + width: undefined, + height: undefined + }, + loaderContainer: { + ...StyleSheet.absoluteFillObject, + alignItems: 'center', + justifyContent: 'center' + } }); diff --git a/src/parallaximage/ParallaxImage.tsx b/src/parallaximage/ParallaxImage.tsx index 7f4f26fdf..6598f7088 100644 --- a/src/parallaximage/ParallaxImage.tsx +++ b/src/parallaximage/ParallaxImage.tsx @@ -2,23 +2,23 @@ import React, { Component } from 'react'; import { - View, - Animated, - Easing, - ActivityIndicator, - findNodeHandle, - ImageProps, - StyleProp, - ViewStyle, - NativeSyntheticEvent, - ImageLoadEventData, - ImageErrorEventData, + View, + Animated, + Easing, + ActivityIndicator, + findNodeHandle, + ImageProps, + StyleProp, + ViewStyle, + NativeSyntheticEvent, + ImageLoadEventData, + ImageErrorEventData } from 'react-native'; import styles from './ParallaxImage.style'; import type Carousel from 'src/carousel/Carousel'; type ParallaxImageProps = { - carouselRef: Carousel | null; // passed from + carouselRef: Carousel | null; // passed from itemHeight: number; // passed from itemWidth: number; // passed from scrollPosition: Animated.Value; // passed from @@ -34,7 +34,6 @@ type ParallaxImageProps = { parallaxFactor: number; showSpinner: boolean; spinnerColor: string; - // TODO: type it AnimatedImageComponent: typeof Animated.Image; } & ImageProps; @@ -51,209 +50,211 @@ export default class ParallaxImage extends Component< ParallaxImageState > { static defaultProps = { - containerStyle: {}, - fadeDuration: 500, - parallaxFactor: 0.3, - showSpinner: true, - spinnerColor: 'rgba(0, 0, 0, 0.4)', - AnimatedImageComponent: Animated.Image, + containerStyle: {}, + fadeDuration: 500, + parallaxFactor: 0.3, + showSpinner: true, + spinnerColor: 'rgba(0, 0, 0, 0.4)', + AnimatedImageComponent: Animated.Image }; _container?: View | null; _mounted?: boolean; - constructor(props: ParallaxImageProps) { - super(props); - this.state = { - offset: 0, - width: 0, - height: 0, - status: 1, // 1 -> loading; 2 -> loaded // 3 -> transition finished; 4 -> error - animOpacity: new Animated.Value(0), - }; - this._onLoad = this._onLoad.bind(this); - this._onError = this._onError.bind(this); - this._measureLayout = this._measureLayout.bind(this); + constructor (props: ParallaxImageProps) { + super(props); + this.state = { + offset: 0, + width: 0, + height: 0, + status: 1, // 1 -> loading; 2 -> loaded // 3 -> transition finished; 4 -> error + animOpacity: new Animated.Value(0) + }; + this._onLoad = this._onLoad.bind(this); + this._onError = this._onError.bind(this); + this._measureLayout = this._measureLayout.bind(this); } - setNativeProps(nativeProps: { [key: string]: unknown }) { + + setNativeProps (nativeProps: { [key: string]: unknown }) { this._container?.setNativeProps(nativeProps); } - componentDidMount() { - this._mounted = true; + componentDidMount () { + this._mounted = true; - setTimeout(() => { - this._measureLayout(); - }, 0); + setTimeout(() => { + this._measureLayout(); + }, 0); } - componentWillUnmount() { - this._mounted = false; + componentWillUnmount () { + this._mounted = false; } - _measureLayout() { - if (this._container) { - const { - dimensions, - vertical, - carouselRef, - sliderWidth, - sliderHeight, - itemWidth, - itemHeight, - } = this.props; + _measureLayout () { + if (this._container) { + const { + dimensions, + vertical, + carouselRef, + sliderWidth, + sliderHeight, + itemWidth, + itemHeight + } = this.props; - const nodeHandle = findNodeHandle(carouselRef); + const nodeHandle = findNodeHandle(carouselRef); - if (carouselRef && nodeHandle) { - this._container.measureLayout( - nodeHandle, - (x, y, width, height) => { - const offset = vertical - ? y - (sliderHeight - itemHeight) / 2 - : x - (sliderWidth - itemWidth) / 2; + if (carouselRef && nodeHandle) { + this._container.measureLayout( + nodeHandle, + (x, y, width, height) => { + const offset = vertical ? + y - (sliderHeight - itemHeight) / 2 : + x - (sliderWidth - itemWidth) / 2; - this.setState({ - offset: offset, - width: - dimensions && dimensions.width - ? dimensions.width - : Math.ceil(width), - height: - dimensions && dimensions.height - ? dimensions.height - : Math.ceil(height), - }); - }, - () => {} - ); + this.setState({ + offset: offset, + width: + dimensions && dimensions.width ? + dimensions.width : + Math.ceil(width), + height: + dimensions && dimensions.height ? + dimensions.height : + Math.ceil(height) + }); + }, + // eslint-disable-next-line @typescript-eslint/no-empty-function + () => {} + ); + } } - } } - _onLoad(event: NativeSyntheticEvent) { - const { animOpacity } = this.state; - const { fadeDuration, onLoad } = this.props; + _onLoad (event: NativeSyntheticEvent) { + const { animOpacity } = this.state; + const { fadeDuration, onLoad } = this.props; - if (!this._mounted) { - return; - } + if (!this._mounted) { + return; + } - this.setState({ status: 2 }); + this.setState({ status: 2 }); - if (onLoad) { - onLoad(event); - } + if (onLoad) { + onLoad(event); + } - Animated.timing(animOpacity, { - toValue: 1, - duration: fadeDuration, - easing: Easing.out(Easing.quad), - isInteraction: false, - useNativeDriver: true, - }).start(() => { - this.setState({ status: 3 }); - }); + Animated.timing(animOpacity, { + toValue: 1, + duration: fadeDuration, + easing: Easing.out(Easing.quad), + isInteraction: false, + useNativeDriver: true + }).start(() => { + this.setState({ status: 3 }); + }); } // If arg is missing from method signature, it just won't be called - _onError(event: NativeSyntheticEvent) { - const { onError } = this.props; + _onError (event: NativeSyntheticEvent) { + const { onError } = this.props; - this.setState({ status: 4 }); + this.setState({ status: 4 }); - if (onError) { - onError(event); - } + if (onError) { + onError(event); + } } - get image() { - const { status, animOpacity, offset, width, height } = this.state; - const { - scrollPosition, - // False positive :( other doesn't have the dimension key - // eslint-disable-next-line @typescript-eslint/no-unused-vars - dimensions, - vertical, - sliderWidth, - sliderHeight, - parallaxFactor, - style, - AnimatedImageComponent, - ...other - } = this.props; - const parallaxPadding = (vertical ? height : width) * parallaxFactor; - const requiredStyles = { position: 'relative' }; - const dynamicStyles = { - width: vertical ? width : width + parallaxPadding * 2, - height: vertical ? height + parallaxPadding * 2 : height, - opacity: animOpacity, - transform: scrollPosition - ? [ - { - translateX: !vertical - ? scrollPosition.interpolate({ - inputRange: [offset - sliderWidth, offset + sliderWidth], - outputRange: [-parallaxPadding, parallaxPadding], - extrapolate: 'clamp', - }) - : 0, - }, - { - translateY: vertical - ? scrollPosition.interpolate({ - inputRange: [offset - sliderHeight, offset + sliderHeight], - outputRange: [-parallaxPadding, parallaxPadding], - extrapolate: 'clamp', - }) - : 0, - }, - ] - : [], - }; + get image () { + const { status, animOpacity, offset, width, height } = this.state; + const { + scrollPosition, + // False positive :( other doesn't have the dimension key + // eslint-disable-next-line @typescript-eslint/no-unused-vars + dimensions, + vertical, + sliderWidth, + sliderHeight, + parallaxFactor, + style, + AnimatedImageComponent, + ...other + } = this.props; + const parallaxPadding = (vertical ? height : width) * parallaxFactor; + const requiredStyles = { position: 'relative' as const }; + const dynamicStyles = { + width: vertical ? width : width + parallaxPadding * 2, + height: vertical ? height + parallaxPadding * 2 : height, + opacity: animOpacity, + transform: scrollPosition ? + [ + { + translateX: !vertical ? + scrollPosition.interpolate({ + inputRange: [offset - sliderWidth, offset + sliderWidth], + outputRange: [-parallaxPadding, parallaxPadding], + extrapolate: 'clamp' + }) : + 0 + }, + { + translateY: vertical ? + scrollPosition.interpolate({ + inputRange: [offset - sliderHeight, offset + sliderHeight], + outputRange: [-parallaxPadding, parallaxPadding], + extrapolate: 'clamp' + }) : + 0 + } + ] : + [] + }; - return ( - - ); + return ( + + ); } - get spinner() { - const { status } = this.state; - const { showSpinner, spinnerColor } = this.props; + get spinner () { + const { status } = this.state; + const { showSpinner, spinnerColor } = this.props; - return status === 1 && showSpinner ? ( - - - - ) : ( - false - ); + return status === 1 && showSpinner ? ( + + + + ) : ( + false + ); } - render() { - const { containerStyle } = this.props; + render () { + const { containerStyle } = this.props; - return ( - { - this._container = c; - }} - pointerEvents={'none'} - style={[containerStyle, styles.container]} - onLayout={this._measureLayout} - > - {this.image} - {this.spinner} - - ); + return ( + { + this._container = c; + }} + pointerEvents='none' + style={[containerStyle, styles.container]} + onLayout={this._measureLayout} + > + {this.image} + {this.spinner} + + ); } } diff --git a/src/utils/animations.ts b/src/utils/animations.ts index 392cd5425..a0df4069f 100644 --- a/src/utils/animations.ts +++ b/src/utils/animations.ts @@ -13,116 +13,116 @@ const IS_ANDROID = Platform.OS === 'android'; // index * sizeRef, // active // (index + 1) * sizeRef // active - 1 // ] -export function getInputRangeFromIndexes( - range: number[], - index: number, - carouselProps: CarouselProps +export function getInputRangeFromIndexes ( + range: number[], + index: number, + carouselProps: CarouselProps ) { - const sizeRef = carouselProps.vertical - ? carouselProps.itemHeight - : carouselProps.itemWidth; - let inputRange = []; + const sizeRef = carouselProps.vertical ? + carouselProps.itemHeight : + carouselProps.itemWidth; + const inputRange = []; - for (let i = 0; i < range.length; i++) { - inputRange.push((index - range[i]) * sizeRef); - } + for (let i = 0; i < range.length; i++) { + inputRange.push((index - range[i]) * sizeRef); + } - return inputRange; + return inputRange; } // Default behavior // Scale and/or opacity effect // Based on props 'inactiveSlideOpacity' and 'inactiveSlideScale' -export function defaultScrollInterpolator( - index: number, - carouselProps: CarouselProps +export function defaultScrollInterpolator ( + index: number, + carouselProps: CarouselProps ) { - const range = [1, 0, -1]; - const inputRange = getInputRangeFromIndexes(range, index, carouselProps); - const outputRange = [0, 1, 0]; + const range = [1, 0, -1]; + const inputRange = getInputRangeFromIndexes(range, index, carouselProps); + const outputRange = [0, 1, 0]; - return { inputRange, outputRange }; + return { inputRange, outputRange }; } -export function defaultAnimatedStyles( - _index: number, - animatedValue: Animated.Value, - carouselProps: CarouselProps +export function defaultAnimatedStyles ( + _index: number, + animatedValue: Animated.AnimatedInterpolation, + carouselProps: CarouselProps ) { - let animatedOpacity = {}; - let animatedScale = {}; + let animatedOpacity = {}; + let animatedScale = {}; - if (carouselProps.inactiveSlideOpacity < 1) { - animatedOpacity = { - opacity: animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [carouselProps.inactiveSlideOpacity, 1], - }), - }; - } + if (carouselProps.inactiveSlideOpacity < 1) { + animatedOpacity = { + opacity: animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [carouselProps.inactiveSlideOpacity, 1] + }) + }; + } - if (carouselProps.inactiveSlideScale < 1) { - animatedScale = { - transform: [ - { - scale: animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [carouselProps.inactiveSlideScale, 1], - }), - }, - ], - }; - } + if (carouselProps.inactiveSlideScale < 1) { + animatedScale = { + transform: [ + { + scale: animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [carouselProps.inactiveSlideScale, 1] + }) + } + ] + }; + } - return { - ...animatedOpacity, - ...animatedScale, - }; + return { + ...animatedOpacity, + ...animatedScale + }; } // Shift animation // Same as the default one, but the active slide is also shifted up or down // Based on prop 'inactiveSlideShift' -export function shiftAnimatedStyles( - _index: number, - animatedValue: Animated.Value, - carouselProps: CarouselProps +export function shiftAnimatedStyles ( + _index: number, + animatedValue: Animated.AnimatedInterpolation, + carouselProps: CarouselProps ) { - let animatedOpacity = {}; - let animatedScale = {}; - let animatedTranslate = {}; + let animatedOpacity = {}; + let animatedScale = {}; + let animatedTranslate = {}; - if (carouselProps.inactiveSlideOpacity < 1) { - animatedOpacity = { - opacity: animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [carouselProps.inactiveSlideOpacity, 1], - }), - }; - } + if (carouselProps.inactiveSlideOpacity < 1) { + animatedOpacity = { + opacity: animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [carouselProps.inactiveSlideOpacity, 1] + }) + }; + } - if (carouselProps.inactiveSlideScale < 1) { - animatedScale = { - scale: animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [carouselProps.inactiveSlideScale, 1], - }), - }; - } + if (carouselProps.inactiveSlideScale < 1) { + animatedScale = { + scale: animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [carouselProps.inactiveSlideScale, 1] + }) + }; + } - if (carouselProps.inactiveSlideShift !== 0) { - const translateProp = carouselProps.vertical ? 'translateX' : 'translateY'; - animatedTranslate = { - [translateProp]: animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [carouselProps.inactiveSlideShift, 0], - }), - }; - } + if (carouselProps.inactiveSlideShift !== 0) { + const translateProp = carouselProps.vertical ? 'translateX' : 'translateY'; + animatedTranslate = { + [translateProp]: animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [carouselProps.inactiveSlideShift, 0] + }) + }; + } - return { - ...animatedOpacity, - transform: [{ ...animatedScale }, { ...animatedTranslate }], - }; + return { + ...animatedOpacity, + transform: [{ ...animatedScale }, { ...animatedTranslate }] + }; } // Stack animation @@ -130,107 +130,107 @@ export function shiftAnimatedStyles( // WARNING: The effect had to be visually inverted on Android because this OS doesn't honor the `zIndex`property // This means that the item with the higher zIndex (and therefore the tap receiver) remains the one AFTER the currently active item // The `elevation` property compensates for that only visually, which is not good enough -export function stackScrollInterpolator( - index: number, - carouselProps: CarouselProps +export function stackScrollInterpolator ( + index: number, + carouselProps: CarouselProps ) { - const range = IS_ANDROID ? [1, 0, -1, -2, -3] : [3, 2, 1, 0, -1]; - const inputRange = getInputRangeFromIndexes(range, index, carouselProps); - const outputRange = range; + const range = IS_ANDROID ? [1, 0, -1, -2, -3] : [3, 2, 1, 0, -1]; + const inputRange = getInputRangeFromIndexes(range, index, carouselProps); + const outputRange = range; - return { inputRange, outputRange }; + return { inputRange, outputRange }; } -export function stackAnimatedStyles( - index: number, - animatedValue: Animated.Value, - carouselProps: CarouselProps, - cardOffset: number +export function stackAnimatedStyles ( + index: number, + animatedValue: Animated.AnimatedInterpolation, + carouselProps: CarouselProps, + cardOffset: number ) { - const sizeRef = carouselProps.vertical - ? carouselProps.itemHeight - : carouselProps.itemWidth; - const translateProp = carouselProps.vertical ? 'translateY' : 'translateX'; + const sizeRef = carouselProps.vertical ? + carouselProps.itemHeight : + carouselProps.itemWidth; + const translateProp = carouselProps.vertical ? 'translateY' : 'translateX'; - const card1Scale = 0.9; - const card2Scale = 0.8; + const card1Scale = 0.9; + const card2Scale = 0.8; - cardOffset = !cardOffset && cardOffset !== 0 ? 18 : cardOffset; + cardOffset = !cardOffset && cardOffset !== 0 ? 18 : cardOffset; - const getTranslateFromScale = (cardIndex: number, scale: number) => { - const centerFactor = (1 / scale) * cardIndex; - const centeredPosition = -Math.round(sizeRef * centerFactor); - const edgeAlignment = Math.round((sizeRef - sizeRef * scale) / 2); - const offset = Math.round((cardOffset * Math.abs(cardIndex)) / scale); + const getTranslateFromScale = (cardIndex: number, scale: number) => { + const centerFactor = (1 / scale) * cardIndex; + const centeredPosition = -Math.round(sizeRef * centerFactor); + const edgeAlignment = Math.round((sizeRef - sizeRef * scale) / 2); + const offset = Math.round((cardOffset * Math.abs(cardIndex)) / scale); - return IS_ANDROID - ? centeredPosition - edgeAlignment - offset - : centeredPosition + edgeAlignment + offset; - }; + return IS_ANDROID ? + centeredPosition - edgeAlignment - offset : + centeredPosition + edgeAlignment + offset; + }; - const opacityOutputRange = + const opacityOutputRange = carouselProps.inactiveSlideOpacity === 1 ? [1, 1, 1, 0] : [1, 0.75, 0.5, 0]; - return IS_ANDROID - ? { + return IS_ANDROID ? + { // elevation: carouselProps.data.length - index, // fix zIndex bug visually, but not from a logic point of view - opacity: animatedValue.interpolate({ - inputRange: [-3, -2, -1, 0], - outputRange: opacityOutputRange.reverse(), - extrapolate: 'clamp', - }), - transform: [ - { - scale: animatedValue.interpolate({ - inputRange: [-2, -1, 0, 1], - outputRange: [card2Scale, card1Scale, 1, card1Scale], - extrapolate: 'clamp', - }), - }, - { - [translateProp]: animatedValue.interpolate({ - inputRange: [-3, -2, -1, 0, 1], - outputRange: [ - getTranslateFromScale(-3, card2Scale), - getTranslateFromScale(-2, card2Scale), - getTranslateFromScale(-1, card1Scale), - 0, - sizeRef * 0.5, - ], - extrapolate: 'clamp', + opacity: animatedValue.interpolate({ + inputRange: [-3, -2, -1, 0], + outputRange: opacityOutputRange.reverse(), + extrapolate: 'clamp' }), - }, - ], - } - : { - zIndex: carouselProps.data.length - index, - opacity: animatedValue.interpolate({ - inputRange: [0, 1, 2, 3], - outputRange: opacityOutputRange, - extrapolate: 'clamp', - }), - transform: [ - { - scale: animatedValue.interpolate({ - inputRange: [-1, 0, 1, 2], - outputRange: [card1Scale, 1, card1Scale, card2Scale], - extrapolate: 'clamp', - }), - }, - { - [translateProp]: animatedValue.interpolate({ - inputRange: [-1, 0, 1, 2, 3], - outputRange: [ - -sizeRef * 0.5, - 0, - getTranslateFromScale(1, card1Scale), - getTranslateFromScale(2, card2Scale), - getTranslateFromScale(3, card2Scale), - ], - extrapolate: 'clamp', + transform: [ + { + scale: animatedValue.interpolate({ + inputRange: [-2, -1, 0, 1], + outputRange: [card2Scale, card1Scale, 1, card1Scale], + extrapolate: 'clamp' + }) + }, + { + [translateProp]: animatedValue.interpolate({ + inputRange: [-3, -2, -1, 0, 1], + outputRange: [ + getTranslateFromScale(-3, card2Scale), + getTranslateFromScale(-2, card2Scale), + getTranslateFromScale(-1, card1Scale), + 0, + sizeRef * 0.5 + ], + extrapolate: 'clamp' + }) + } + ] + } : + { + zIndex: carouselProps.data.length - index, + opacity: animatedValue.interpolate({ + inputRange: [0, 1, 2, 3], + outputRange: opacityOutputRange, + extrapolate: 'clamp' }), - }, - ], - }; + transform: [ + { + scale: animatedValue.interpolate({ + inputRange: [-1, 0, 1, 2], + outputRange: [card1Scale, 1, card1Scale, card2Scale], + extrapolate: 'clamp' + }) + }, + { + [translateProp]: animatedValue.interpolate({ + inputRange: [-1, 0, 1, 2, 3], + outputRange: [ + -sizeRef * 0.5, + 0, + getTranslateFromScale(1, card1Scale), + getTranslateFromScale(2, card2Scale), + getTranslateFromScale(3, card2Scale) + ], + extrapolate: 'clamp' + }) + } + ] + }; } // Tinder animation @@ -238,146 +238,146 @@ export function stackAnimatedStyles( // WARNING: The effect had to be visually inverted on Android because this OS doesn't honor the `zIndex`property // This means that the item with the higher zIndex (and therefore the tap receiver) remains the one AFTER the currently active item // The `elevation` property compensates for that only visually, which is not good enough -export function tinderScrollInterpolator( - index: number, - carouselProps: CarouselProps +export function tinderScrollInterpolator ( + index: number, + carouselProps: CarouselProps ) { - const range = IS_ANDROID ? [1, 0, -1, -2, -3] : [3, 2, 1, 0, -1]; - const inputRange = getInputRangeFromIndexes(range, index, carouselProps); - const outputRange = range; + const range = IS_ANDROID ? [1, 0, -1, -2, -3] : [3, 2, 1, 0, -1]; + const inputRange = getInputRangeFromIndexes(range, index, carouselProps); + const outputRange = range; - return { inputRange, outputRange }; + return { inputRange, outputRange }; } -export function tinderAnimatedStyles( - index: number, - animatedValue: Animated.Value, - carouselProps: CarouselProps, - cardOffset: number +export function tinderAnimatedStyles ( + index: number, + animatedValue: Animated.AnimatedInterpolation, + carouselProps: CarouselProps, + cardOffset: number ) { - const sizeRef = carouselProps.vertical - ? carouselProps.itemHeight - : carouselProps.itemWidth; - const mainTranslateProp = carouselProps.vertical - ? 'translateY' - : 'translateX'; - const secondaryTranslateProp = carouselProps.vertical - ? 'translateX' - : 'translateY'; + const sizeRef = carouselProps.vertical ? + carouselProps.itemHeight : + carouselProps.itemWidth; + const mainTranslateProp = carouselProps.vertical ? + 'translateY' : + 'translateX'; + const secondaryTranslateProp = carouselProps.vertical ? + 'translateX' : + 'translateY'; - const card1Scale = 0.96; - const card2Scale = 0.92; - const card3Scale = 0.88; + const card1Scale = 0.96; + const card2Scale = 0.92; + const card3Scale = 0.88; - const peekingCardsOpacity = IS_ANDROID ? 0.92 : 1; + const peekingCardsOpacity = IS_ANDROID ? 0.92 : 1; - cardOffset = !cardOffset && cardOffset !== 0 ? 9 : cardOffset; + cardOffset = !cardOffset && cardOffset !== 0 ? 9 : cardOffset; - const getMainTranslateFromScale = (cardIndex: number, scale: number) => { - const centerFactor = (1 / scale) * cardIndex; - return -Math.round(sizeRef * centerFactor); - }; + const getMainTranslateFromScale = (cardIndex: number, scale: number) => { + const centerFactor = (1 / scale) * cardIndex; + return -Math.round(sizeRef * centerFactor); + }; - const getSecondaryTranslateFromScale = (cardIndex: number, scale: number) => { - return Math.round((cardOffset * Math.abs(cardIndex)) / scale); - }; + const getSecondaryTranslateFromScale = (cardIndex: number, scale: number) => { + return Math.round((cardOffset * Math.abs(cardIndex)) / scale); + }; - return IS_ANDROID - ? { + return IS_ANDROID ? + { // elevation: carouselProps.data.length - index, // fix zIndex bug visually, but not from a logic point of view - opacity: animatedValue.interpolate({ - inputRange: [-3, -2, -1, 0, 1], - outputRange: [0, peekingCardsOpacity, peekingCardsOpacity, 1, 0], - extrapolate: 'clamp', - }), - transform: [ - { - scale: animatedValue.interpolate({ - inputRange: [-3, -2, -1, 0], - outputRange: [card3Scale, card2Scale, card1Scale, 1], - extrapolate: 'clamp', - }), - }, - { - rotate: animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: ['0deg', '22deg'], - extrapolate: 'clamp', + opacity: animatedValue.interpolate({ + inputRange: [-3, -2, -1, 0, 1], + outputRange: [0, peekingCardsOpacity, peekingCardsOpacity, 1, 0], + extrapolate: 'clamp' }), - }, - { - [mainTranslateProp]: animatedValue.interpolate({ - inputRange: [-3, -2, -1, 0, 1], - outputRange: [ - getMainTranslateFromScale(-3, card3Scale), - getMainTranslateFromScale(-2, card2Scale), - getMainTranslateFromScale(-1, card1Scale), - 0, - sizeRef * 1.1, - ], - extrapolate: 'clamp', - }), - }, - { - [secondaryTranslateProp]: animatedValue.interpolate({ - inputRange: [-3, -2, -1, 0], - outputRange: [ - getSecondaryTranslateFromScale(-3, card3Scale), - getSecondaryTranslateFromScale(-2, card2Scale), - getSecondaryTranslateFromScale(-1, card1Scale), - 0, - ], - extrapolate: 'clamp', - }), - }, - ], - } - : { - zIndex: carouselProps.data.length - index, - opacity: animatedValue.interpolate({ - inputRange: [-1, 0, 1, 2, 3], - outputRange: [0, 1, peekingCardsOpacity, peekingCardsOpacity, 0], - extrapolate: 'clamp', - }), - transform: [ - { - scale: animatedValue.interpolate({ - inputRange: [0, 1, 2, 3], - outputRange: [1, card1Scale, card2Scale, card3Scale], - extrapolate: 'clamp', - }), - }, - { - rotate: animatedValue.interpolate({ - inputRange: [-1, 0], - outputRange: ['-22deg', '0deg'], - extrapolate: 'clamp', - }), - }, - { - [mainTranslateProp]: animatedValue.interpolate({ - inputRange: [-1, 0, 1, 2, 3], - outputRange: [ - -sizeRef * 1.1, - 0, - getMainTranslateFromScale(1, card1Scale), - getMainTranslateFromScale(2, card2Scale), - getMainTranslateFromScale(3, card3Scale), - ], - extrapolate: 'clamp', - }), - }, - { - [secondaryTranslateProp]: animatedValue.interpolate({ - inputRange: [0, 1, 2, 3], - outputRange: [ - 0, - getSecondaryTranslateFromScale(1, card1Scale), - getSecondaryTranslateFromScale(2, card2Scale), - getSecondaryTranslateFromScale(3, card3Scale), - ], - extrapolate: 'clamp', + transform: [ + { + scale: animatedValue.interpolate({ + inputRange: [-3, -2, -1, 0], + outputRange: [card3Scale, card2Scale, card1Scale, 1], + extrapolate: 'clamp' + }) + }, + { + rotate: animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: ['0deg', '22deg'], + extrapolate: 'clamp' + }) + }, + { + [mainTranslateProp]: animatedValue.interpolate({ + inputRange: [-3, -2, -1, 0, 1], + outputRange: [ + getMainTranslateFromScale(-3, card3Scale), + getMainTranslateFromScale(-2, card2Scale), + getMainTranslateFromScale(-1, card1Scale), + 0, + sizeRef * 1.1 + ], + extrapolate: 'clamp' + }) + }, + { + [secondaryTranslateProp]: animatedValue.interpolate({ + inputRange: [-3, -2, -1, 0], + outputRange: [ + getSecondaryTranslateFromScale(-3, card3Scale), + getSecondaryTranslateFromScale(-2, card2Scale), + getSecondaryTranslateFromScale(-1, card1Scale), + 0 + ], + extrapolate: 'clamp' + }) + } + ] + } : + { + zIndex: carouselProps.data.length - index, + opacity: animatedValue.interpolate({ + inputRange: [-1, 0, 1, 2, 3], + outputRange: [0, 1, peekingCardsOpacity, peekingCardsOpacity, 0], + extrapolate: 'clamp' }), - }, - ], - }; + transform: [ + { + scale: animatedValue.interpolate({ + inputRange: [0, 1, 2, 3], + outputRange: [1, card1Scale, card2Scale, card3Scale], + extrapolate: 'clamp' + }) + }, + { + rotate: animatedValue.interpolate({ + inputRange: [-1, 0], + outputRange: ['-22deg', '0deg'], + extrapolate: 'clamp' + }) + }, + { + [mainTranslateProp]: animatedValue.interpolate({ + inputRange: [-1, 0, 1, 2, 3], + outputRange: [ + -sizeRef * 1.1, + 0, + getMainTranslateFromScale(1, card1Scale), + getMainTranslateFromScale(2, card2Scale), + getMainTranslateFromScale(3, card3Scale) + ], + extrapolate: 'clamp' + }) + }, + { + [secondaryTranslateProp]: animatedValue.interpolate({ + inputRange: [0, 1, 2, 3], + outputRange: [ + 0, + getSecondaryTranslateFromScale(1, card1Scale), + getSecondaryTranslateFromScale(2, card2Scale), + getSecondaryTranslateFromScale(3, card3Scale) + ], + extrapolate: 'clamp' + }) + } + ] + }; } diff --git a/src/whatthehell.js b/src/whatthehell.js new file mode 100644 index 000000000..228c1032e --- /dev/null +++ b/src/whatthehell.js @@ -0,0 +1 @@ +[ { type: 'config', name: 'DefaultIgnorePattern', filePath: '', criteria: null, env: undefined, globals: undefined, ignorePattern: IgnorePattern { patterns: [Array], basePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork', loose: false }, noInlineConfig: undefined, parser: undefined, parserOptions: undefined, plugins: undefined, processor: undefined, reportUnusedDisableDirectives: undefined, root: undefined, rules: undefined, settings: undefined }, { type: 'config', name: '.eslintrc » eslint-config-standard', filePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/node_modules/eslint-config-standard/index.js', criteria: null, env: { es6: true, node: true }, globals: { document: 'readonly', navigator: 'readonly', window: 'readonly' }, ignorePattern: undefined, noInlineConfig: undefined, parser: undefined, parserOptions: { ecmaVersion: 2020, ecmaFeatures: [Object], sourceType: 'module' }, plugins: { import: [Object], node: [Object], promise: [Object], standard: [Object] }, processor: undefined, reportUnusedDisableDirectives: undefined, root: undefined, rules: { 'accessor-pairs': 'error', 'array-bracket-spacing': [Array], 'arrow-spacing': [Array], 'block-spacing': [Array], 'brace-style': [Array], camelcase: [Array], 'comma-dangle': [Array], 'comma-spacing': [Array], 'comma-style': [Array], 'computed-property-spacing': [Array], 'constructor-super': 'error', curly: [Array], 'dot-location': [Array], 'dot-notation': [Array], 'eol-last': 'error', eqeqeq: [Array], 'func-call-spacing': [Array], 'generator-star-spacing': [Array], 'handle-callback-err': [Array], indent: [Array], 'key-spacing': [Array], 'keyword-spacing': [Array], 'lines-between-class-members': [Array], 'new-cap': [Array], 'new-parens': 'error', 'no-array-constructor': 'error', 'no-async-promise-executor': 'error', 'no-caller': 'error', 'no-case-declarations': 'error', 'no-class-assign': 'error', 'no-compare-neg-zero': 'error', 'no-cond-assign': 'error', 'no-const-assign': 'error', 'no-constant-condition': [Array], 'no-control-regex': 'error', 'no-debugger': 'error', 'no-delete-var': 'error', 'no-dupe-args': 'error', 'no-dupe-class-members': 'error', 'no-dupe-keys': 'error', 'no-duplicate-case': 'error', 'no-empty-character-class': 'error', 'no-empty-pattern': 'error', 'no-eval': 'error', 'no-ex-assign': 'error', 'no-extend-native': 'error', 'no-extra-bind': 'error', 'no-extra-boolean-cast': 'error', 'no-extra-parens': [Array], 'no-fallthrough': 'error', 'no-floating-decimal': 'error', 'no-func-assign': 'error', 'no-global-assign': 'error', 'no-implied-eval': 'error', 'no-inner-declarations': [Array], 'no-invalid-regexp': 'error', 'no-irregular-whitespace': 'error', 'no-iterator': 'error', 'no-labels': [Array], 'no-lone-blocks': 'error', 'no-misleading-character-class': 'error', 'no-prototype-builtins': 'error', 'no-useless-catch': 'error', 'no-mixed-operators': [Array], 'no-mixed-spaces-and-tabs': 'error', 'no-multi-spaces': 'error', 'no-multi-str': 'error', 'no-multiple-empty-lines': [Array], 'no-negated-in-lhs': 'error', 'no-new': 'error', 'no-new-func': 'error', 'no-new-object': 'error', 'no-new-require': 'error', 'no-new-symbol': 'error', 'no-new-wrappers': 'error', 'no-obj-calls': 'error', 'no-octal': 'error', 'no-octal-escape': 'error', 'no-path-concat': 'error', 'no-proto': 'error', 'no-redeclare': [Array], 'no-regex-spaces': 'error', 'no-return-assign': [Array], 'no-self-assign': [Array], 'no-self-compare': 'error', 'no-sequences': 'error', 'no-shadow-restricted-names': 'error', 'no-sparse-arrays': 'error', 'no-tabs': 'error', 'no-template-curly-in-string': 'error', 'no-this-before-super': 'error', 'no-throw-literal': 'error', 'no-trailing-spaces': 'error', 'no-undef': 'error', 'no-undef-init': 'error', 'no-unexpected-multiline': 'error', 'no-unmodified-loop-condition': 'error', 'no-unneeded-ternary': [Array], 'no-unreachable': 'error', 'no-unsafe-finally': 'error', 'no-unsafe-negation': 'error', 'no-unused-expressions': [Array], 'no-unused-vars': [Array], 'no-use-before-define': [Array], 'no-useless-call': 'error', 'no-useless-computed-key': 'error', 'no-useless-constructor': 'error', 'no-useless-escape': 'error', 'no-useless-rename': 'error', 'no-useless-return': 'error', 'no-void': 'error', 'no-whitespace-before-property': 'error', 'no-with': 'error', 'object-curly-newline': [Array], 'object-curly-spacing': [Array], 'object-property-newline': [Array], 'one-var': [Array], 'operator-linebreak': [Array], 'padded-blocks': [Array], 'prefer-const': [Array], 'prefer-promise-reject-errors': 'error', 'quote-props': [Array], quotes: [Array], 'rest-spread-spacing': [Array], semi: [Array], 'semi-spacing': [Array], 'space-before-blocks': [Array], 'space-before-function-paren': [Array], 'space-in-parens': [Array], 'space-infix-ops': 'error', 'space-unary-ops': [Array], 'spaced-comment': [Array], 'symbol-description': 'error', 'template-curly-spacing': [Array], 'template-tag-spacing': [Array], 'unicode-bom': [Array], 'use-isnan': 'error', 'valid-typeof': [Array], 'wrap-iife': [Array], 'yield-star-spacing': [Array], yoda: [Array], 'import/export': 'error', 'import/first': 'error', 'import/no-absolute-path': [Array], 'import/no-duplicates': 'error', 'import/no-named-default': 'error', 'import/no-webpack-loader-syntax': 'error', 'node/no-deprecated-api': 'error', 'node/process-exit-as-throw': 'error', 'promise/param-names': 'error', 'standard/no-callback-literal': 'error' }, settings: undefined }, { type: 'config', name: '.eslintrc » eslint-config-standard-react » /Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/node_modules/eslint-config-standard-jsx/index.js', filePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/node_modules/eslint-config-standard-jsx/index.js', criteria: null, env: undefined, globals: undefined, ignorePattern: undefined, noInlineConfig: undefined, parser: undefined, parserOptions: { ecmaVersion: 2020, ecmaFeatures: [Object], sourceType: 'module' }, plugins: { react: [Object] }, processor: undefined, reportUnusedDisableDirectives: undefined, root: undefined, rules: { 'jsx-quotes': [Array], 'react/jsx-boolean-value': 'error', 'react/jsx-closing-bracket-location': [Array], 'react/jsx-closing-tag-location': 'error', 'react/jsx-curly-brace-presence': [Array], 'react/jsx-curly-newline': [Array], 'react/jsx-curly-spacing': [Array], 'react/jsx-equals-spacing': [Array], 'react/jsx-first-prop-new-line': [Array], 'react/jsx-fragments': [Array], 'react/jsx-handler-names': 'error', 'react/jsx-indent': [Array], 'react/jsx-indent-props': [Array], 'react/jsx-key': 'error', 'react/jsx-no-comment-textnodes': 'error', 'react/jsx-no-duplicate-props': 'error', 'react/jsx-no-target-blank': [Array], 'react/jsx-no-undef': 'error', 'react/jsx-pascal-case': [Array], 'react/jsx-props-no-multi-spaces': 'error', 'react/jsx-tag-spacing': [Array], 'react/jsx-uses-react': 'error', 'react/jsx-uses-vars': 'error', 'react/self-closing-comp': 'error' }, settings: undefined }, { type: 'config', name: '.eslintrc » eslint-config-standard-react', filePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/node_modules/eslint-config-standard-react/index.js', criteria: null, env: undefined, globals: undefined, ignorePattern: undefined, noInlineConfig: undefined, parser: undefined, parserOptions: { ecmaVersion: 2020, ecmaFeatures: [Object], sourceType: 'module' }, plugins: { react: [Object] }, processor: undefined, reportUnusedDisableDirectives: undefined, root: undefined, rules: { 'react/jsx-no-bind': [Array], 'react/no-did-update-set-state': 'error', 'react/no-unknown-property': 'error', 'react/no-unused-prop-types': 'error', 'react/prop-types': 'error', 'react/react-in-jsx-scope': 'error' }, settings: { react: [Object] } }, { type: 'config', name: '.eslintrc » plugin:@typescript-eslint/recommended » ./configs/base', filePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/node_modules/@typescript-eslint/eslint-plugin/dist/configs/base.js', criteria: null, env: undefined, globals: undefined, ignorePattern: undefined, noInlineConfig: undefined, parser: { error: null, filePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/node_modules/@typescript-eslint/parser/dist/index.js', id: '@typescript-eslint/parser', importerName: '.eslintrc » plugin:@typescript-eslint/recommended » ./configs/base', importerPath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/node_modules/@typescript-eslint/eslint-plugin/dist/configs/base.js' }, parserOptions: { sourceType: 'module' }, plugins: { '@typescript-eslint': [Object] }, processor: undefined, reportUnusedDisableDirectives: undefined, root: undefined, rules: undefined, settings: undefined }, { type: 'config', name: '.eslintrc » plugin:@typescript-eslint/recommended » ./configs/eslint-recommended', filePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/node_modules/@typescript-eslint/eslint-plugin/dist/configs/eslint-recommended.js', criteria: null, env: undefined, globals: undefined, ignorePattern: undefined, noInlineConfig: undefined, parser: undefined, parserOptions: undefined, plugins: undefined, processor: undefined, reportUnusedDisableDirectives: undefined, root: undefined, rules: undefined, settings: undefined }, { type: 'config', name: '.eslintrc » plugin:@typescript-eslint/recommended » ./configs/eslint-recommended#overrides[0]', filePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/node_modules/@typescript-eslint/eslint-plugin/dist/configs/eslint-recommended.js', criteria: { includes: [Array], excludes: null, basePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork' }, env: undefined, globals: undefined, ignorePattern: undefined, noInlineConfig: undefined, parser: undefined, parserOptions: undefined, plugins: undefined, processor: undefined, reportUnusedDisableDirectives: undefined, root: undefined, rules: { 'constructor-super': 'off', 'getter-return': 'off', 'no-const-assign': 'off', 'no-dupe-args': 'off', 'no-dupe-class-members': 'off', 'no-dupe-keys': 'off', 'no-func-assign': 'off', 'no-import-assign': 'off', 'no-new-symbol': 'off', 'no-obj-calls': 'off', 'no-redeclare': 'off', 'no-setter-return': 'off', 'no-this-before-super': 'off', 'no-undef': 'off', 'no-unreachable': 'off', 'no-unsafe-negation': 'off', 'no-var': 'error', 'prefer-const': 'error', 'prefer-rest-params': 'error', 'prefer-spread': 'error', 'valid-typeof': 'off' }, settings: undefined }, { type: 'config', name: '.eslintrc » plugin:@typescript-eslint/recommended', filePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/node_modules/@typescript-eslint/eslint-plugin/dist/index.js', criteria: null, env: undefined, globals: undefined, ignorePattern: undefined, noInlineConfig: undefined, parser: undefined, parserOptions: undefined, plugins: undefined, processor: undefined, reportUnusedDisableDirectives: undefined, root: undefined, rules: { '@typescript-eslint/adjacent-overload-signatures': 'error', '@typescript-eslint/ban-ts-comment': 'error', '@typescript-eslint/ban-types': 'error', '@typescript-eslint/explicit-module-boundary-types': 'warn', 'no-array-constructor': 'off', '@typescript-eslint/no-array-constructor': 'error', 'no-empty-function': 'off', '@typescript-eslint/no-empty-function': 'error', '@typescript-eslint/no-empty-interface': 'error', '@typescript-eslint/no-explicit-any': 'warn', '@typescript-eslint/no-extra-non-null-assertion': 'error', 'no-extra-semi': 'off', '@typescript-eslint/no-extra-semi': 'error', '@typescript-eslint/no-inferrable-types': 'error', '@typescript-eslint/no-misused-new': 'error', '@typescript-eslint/no-namespace': 'error', '@typescript-eslint/no-non-null-asserted-optional-chain': 'error', '@typescript-eslint/no-non-null-assertion': 'warn', '@typescript-eslint/no-this-alias': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'warn', '@typescript-eslint/no-var-requires': 'error', '@typescript-eslint/prefer-as-const': 'error', '@typescript-eslint/prefer-namespace-keyword': 'error', '@typescript-eslint/triple-slash-reference': 'error' }, settings: undefined }, { type: 'config', name: '.eslintrc', filePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/.eslintrc', criteria: null, env: { browser: true, es6: true }, globals: { __DEV__: false }, ignorePattern: undefined, noInlineConfig: undefined, parser: { error: null, filePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/node_modules/@typescript-eslint/parser/dist/index.js', id: '@typescript-eslint/parser', importerName: '.eslintrc', importerPath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/.eslintrc' }, parserOptions: undefined, plugins: { '@typescript-eslint': [Object], react: [Object] }, processor: undefined, reportUnusedDisableDirectives: undefined, root: undefined, rules: { 'generator-star-spacing': 0, indent: [Array], 'no-warning-comments': [Array], 'operator-linebreak': [Array], 'padded-blocks': 0, semi: [Array], 'react/jsx-indent-props': [Array], 'react/jsx-boolean-value': [Array], 'react/jsx-curly-spacing': [Array], 'react/jsx-indent': [Array], '@typescript-eslint/explicit-module-boundary-type': 'off' }, settings: { react: [Object] } }, { type: 'config', name: '.eslintrc#overrides[0]', filePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/.eslintrc', criteria: { includes: [Array], excludes: null, basePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork' }, env: undefined, globals: undefined, ignorePattern: undefined, noInlineConfig: undefined, parser: undefined, parserOptions: undefined, plugins: undefined, processor: undefined, reportUnusedDisableDirectives: undefined, root: undefined, rules: { '@typescript-eslint/ban-ts-comment': 0, '@typescript-eslint/explicit-module-boundary-type': 'off' }, settings: undefined }, { type: 'ignore', name: 'eslintIgnore in package.json', filePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/package.json', criteria: null, env: undefined, globals: undefined, ignorePattern: IgnorePattern { patterns: [Array], basePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork', loose: true }, noInlineConfig: undefined, parser: undefined, parserOptions: undefined, plugins: undefined, processor: undefined, reportUnusedDisableDirectives: undefined, root: undefined, rules: undefined, settings: undefined } ] \ No newline at end of file From bc86b88f83f6736c46268c7dcf6a5cfd2bc82e46 Mon Sep 17 00:00:00 2001 From: Thibault Malbranche Date: Thu, 6 Aug 2020 01:18:21 +0200 Subject: [PATCH 24/34] Delete whatthehell.js --- src/whatthehell.js | 1 - 1 file changed, 1 deletion(-) delete mode 100644 src/whatthehell.js diff --git a/src/whatthehell.js b/src/whatthehell.js deleted file mode 100644 index 228c1032e..000000000 --- a/src/whatthehell.js +++ /dev/null @@ -1 +0,0 @@ -[ { type: 'config', name: 'DefaultIgnorePattern', filePath: '', criteria: null, env: undefined, globals: undefined, ignorePattern: IgnorePattern { patterns: [Array], basePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork', loose: false }, noInlineConfig: undefined, parser: undefined, parserOptions: undefined, plugins: undefined, processor: undefined, reportUnusedDisableDirectives: undefined, root: undefined, rules: undefined, settings: undefined }, { type: 'config', name: '.eslintrc » eslint-config-standard', filePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/node_modules/eslint-config-standard/index.js', criteria: null, env: { es6: true, node: true }, globals: { document: 'readonly', navigator: 'readonly', window: 'readonly' }, ignorePattern: undefined, noInlineConfig: undefined, parser: undefined, parserOptions: { ecmaVersion: 2020, ecmaFeatures: [Object], sourceType: 'module' }, plugins: { import: [Object], node: [Object], promise: [Object], standard: [Object] }, processor: undefined, reportUnusedDisableDirectives: undefined, root: undefined, rules: { 'accessor-pairs': 'error', 'array-bracket-spacing': [Array], 'arrow-spacing': [Array], 'block-spacing': [Array], 'brace-style': [Array], camelcase: [Array], 'comma-dangle': [Array], 'comma-spacing': [Array], 'comma-style': [Array], 'computed-property-spacing': [Array], 'constructor-super': 'error', curly: [Array], 'dot-location': [Array], 'dot-notation': [Array], 'eol-last': 'error', eqeqeq: [Array], 'func-call-spacing': [Array], 'generator-star-spacing': [Array], 'handle-callback-err': [Array], indent: [Array], 'key-spacing': [Array], 'keyword-spacing': [Array], 'lines-between-class-members': [Array], 'new-cap': [Array], 'new-parens': 'error', 'no-array-constructor': 'error', 'no-async-promise-executor': 'error', 'no-caller': 'error', 'no-case-declarations': 'error', 'no-class-assign': 'error', 'no-compare-neg-zero': 'error', 'no-cond-assign': 'error', 'no-const-assign': 'error', 'no-constant-condition': [Array], 'no-control-regex': 'error', 'no-debugger': 'error', 'no-delete-var': 'error', 'no-dupe-args': 'error', 'no-dupe-class-members': 'error', 'no-dupe-keys': 'error', 'no-duplicate-case': 'error', 'no-empty-character-class': 'error', 'no-empty-pattern': 'error', 'no-eval': 'error', 'no-ex-assign': 'error', 'no-extend-native': 'error', 'no-extra-bind': 'error', 'no-extra-boolean-cast': 'error', 'no-extra-parens': [Array], 'no-fallthrough': 'error', 'no-floating-decimal': 'error', 'no-func-assign': 'error', 'no-global-assign': 'error', 'no-implied-eval': 'error', 'no-inner-declarations': [Array], 'no-invalid-regexp': 'error', 'no-irregular-whitespace': 'error', 'no-iterator': 'error', 'no-labels': [Array], 'no-lone-blocks': 'error', 'no-misleading-character-class': 'error', 'no-prototype-builtins': 'error', 'no-useless-catch': 'error', 'no-mixed-operators': [Array], 'no-mixed-spaces-and-tabs': 'error', 'no-multi-spaces': 'error', 'no-multi-str': 'error', 'no-multiple-empty-lines': [Array], 'no-negated-in-lhs': 'error', 'no-new': 'error', 'no-new-func': 'error', 'no-new-object': 'error', 'no-new-require': 'error', 'no-new-symbol': 'error', 'no-new-wrappers': 'error', 'no-obj-calls': 'error', 'no-octal': 'error', 'no-octal-escape': 'error', 'no-path-concat': 'error', 'no-proto': 'error', 'no-redeclare': [Array], 'no-regex-spaces': 'error', 'no-return-assign': [Array], 'no-self-assign': [Array], 'no-self-compare': 'error', 'no-sequences': 'error', 'no-shadow-restricted-names': 'error', 'no-sparse-arrays': 'error', 'no-tabs': 'error', 'no-template-curly-in-string': 'error', 'no-this-before-super': 'error', 'no-throw-literal': 'error', 'no-trailing-spaces': 'error', 'no-undef': 'error', 'no-undef-init': 'error', 'no-unexpected-multiline': 'error', 'no-unmodified-loop-condition': 'error', 'no-unneeded-ternary': [Array], 'no-unreachable': 'error', 'no-unsafe-finally': 'error', 'no-unsafe-negation': 'error', 'no-unused-expressions': [Array], 'no-unused-vars': [Array], 'no-use-before-define': [Array], 'no-useless-call': 'error', 'no-useless-computed-key': 'error', 'no-useless-constructor': 'error', 'no-useless-escape': 'error', 'no-useless-rename': 'error', 'no-useless-return': 'error', 'no-void': 'error', 'no-whitespace-before-property': 'error', 'no-with': 'error', 'object-curly-newline': [Array], 'object-curly-spacing': [Array], 'object-property-newline': [Array], 'one-var': [Array], 'operator-linebreak': [Array], 'padded-blocks': [Array], 'prefer-const': [Array], 'prefer-promise-reject-errors': 'error', 'quote-props': [Array], quotes: [Array], 'rest-spread-spacing': [Array], semi: [Array], 'semi-spacing': [Array], 'space-before-blocks': [Array], 'space-before-function-paren': [Array], 'space-in-parens': [Array], 'space-infix-ops': 'error', 'space-unary-ops': [Array], 'spaced-comment': [Array], 'symbol-description': 'error', 'template-curly-spacing': [Array], 'template-tag-spacing': [Array], 'unicode-bom': [Array], 'use-isnan': 'error', 'valid-typeof': [Array], 'wrap-iife': [Array], 'yield-star-spacing': [Array], yoda: [Array], 'import/export': 'error', 'import/first': 'error', 'import/no-absolute-path': [Array], 'import/no-duplicates': 'error', 'import/no-named-default': 'error', 'import/no-webpack-loader-syntax': 'error', 'node/no-deprecated-api': 'error', 'node/process-exit-as-throw': 'error', 'promise/param-names': 'error', 'standard/no-callback-literal': 'error' }, settings: undefined }, { type: 'config', name: '.eslintrc » eslint-config-standard-react » /Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/node_modules/eslint-config-standard-jsx/index.js', filePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/node_modules/eslint-config-standard-jsx/index.js', criteria: null, env: undefined, globals: undefined, ignorePattern: undefined, noInlineConfig: undefined, parser: undefined, parserOptions: { ecmaVersion: 2020, ecmaFeatures: [Object], sourceType: 'module' }, plugins: { react: [Object] }, processor: undefined, reportUnusedDisableDirectives: undefined, root: undefined, rules: { 'jsx-quotes': [Array], 'react/jsx-boolean-value': 'error', 'react/jsx-closing-bracket-location': [Array], 'react/jsx-closing-tag-location': 'error', 'react/jsx-curly-brace-presence': [Array], 'react/jsx-curly-newline': [Array], 'react/jsx-curly-spacing': [Array], 'react/jsx-equals-spacing': [Array], 'react/jsx-first-prop-new-line': [Array], 'react/jsx-fragments': [Array], 'react/jsx-handler-names': 'error', 'react/jsx-indent': [Array], 'react/jsx-indent-props': [Array], 'react/jsx-key': 'error', 'react/jsx-no-comment-textnodes': 'error', 'react/jsx-no-duplicate-props': 'error', 'react/jsx-no-target-blank': [Array], 'react/jsx-no-undef': 'error', 'react/jsx-pascal-case': [Array], 'react/jsx-props-no-multi-spaces': 'error', 'react/jsx-tag-spacing': [Array], 'react/jsx-uses-react': 'error', 'react/jsx-uses-vars': 'error', 'react/self-closing-comp': 'error' }, settings: undefined }, { type: 'config', name: '.eslintrc » eslint-config-standard-react', filePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/node_modules/eslint-config-standard-react/index.js', criteria: null, env: undefined, globals: undefined, ignorePattern: undefined, noInlineConfig: undefined, parser: undefined, parserOptions: { ecmaVersion: 2020, ecmaFeatures: [Object], sourceType: 'module' }, plugins: { react: [Object] }, processor: undefined, reportUnusedDisableDirectives: undefined, root: undefined, rules: { 'react/jsx-no-bind': [Array], 'react/no-did-update-set-state': 'error', 'react/no-unknown-property': 'error', 'react/no-unused-prop-types': 'error', 'react/prop-types': 'error', 'react/react-in-jsx-scope': 'error' }, settings: { react: [Object] } }, { type: 'config', name: '.eslintrc » plugin:@typescript-eslint/recommended » ./configs/base', filePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/node_modules/@typescript-eslint/eslint-plugin/dist/configs/base.js', criteria: null, env: undefined, globals: undefined, ignorePattern: undefined, noInlineConfig: undefined, parser: { error: null, filePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/node_modules/@typescript-eslint/parser/dist/index.js', id: '@typescript-eslint/parser', importerName: '.eslintrc » plugin:@typescript-eslint/recommended » ./configs/base', importerPath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/node_modules/@typescript-eslint/eslint-plugin/dist/configs/base.js' }, parserOptions: { sourceType: 'module' }, plugins: { '@typescript-eslint': [Object] }, processor: undefined, reportUnusedDisableDirectives: undefined, root: undefined, rules: undefined, settings: undefined }, { type: 'config', name: '.eslintrc » plugin:@typescript-eslint/recommended » ./configs/eslint-recommended', filePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/node_modules/@typescript-eslint/eslint-plugin/dist/configs/eslint-recommended.js', criteria: null, env: undefined, globals: undefined, ignorePattern: undefined, noInlineConfig: undefined, parser: undefined, parserOptions: undefined, plugins: undefined, processor: undefined, reportUnusedDisableDirectives: undefined, root: undefined, rules: undefined, settings: undefined }, { type: 'config', name: '.eslintrc » plugin:@typescript-eslint/recommended » ./configs/eslint-recommended#overrides[0]', filePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/node_modules/@typescript-eslint/eslint-plugin/dist/configs/eslint-recommended.js', criteria: { includes: [Array], excludes: null, basePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork' }, env: undefined, globals: undefined, ignorePattern: undefined, noInlineConfig: undefined, parser: undefined, parserOptions: undefined, plugins: undefined, processor: undefined, reportUnusedDisableDirectives: undefined, root: undefined, rules: { 'constructor-super': 'off', 'getter-return': 'off', 'no-const-assign': 'off', 'no-dupe-args': 'off', 'no-dupe-class-members': 'off', 'no-dupe-keys': 'off', 'no-func-assign': 'off', 'no-import-assign': 'off', 'no-new-symbol': 'off', 'no-obj-calls': 'off', 'no-redeclare': 'off', 'no-setter-return': 'off', 'no-this-before-super': 'off', 'no-undef': 'off', 'no-unreachable': 'off', 'no-unsafe-negation': 'off', 'no-var': 'error', 'prefer-const': 'error', 'prefer-rest-params': 'error', 'prefer-spread': 'error', 'valid-typeof': 'off' }, settings: undefined }, { type: 'config', name: '.eslintrc » plugin:@typescript-eslint/recommended', filePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/node_modules/@typescript-eslint/eslint-plugin/dist/index.js', criteria: null, env: undefined, globals: undefined, ignorePattern: undefined, noInlineConfig: undefined, parser: undefined, parserOptions: undefined, plugins: undefined, processor: undefined, reportUnusedDisableDirectives: undefined, root: undefined, rules: { '@typescript-eslint/adjacent-overload-signatures': 'error', '@typescript-eslint/ban-ts-comment': 'error', '@typescript-eslint/ban-types': 'error', '@typescript-eslint/explicit-module-boundary-types': 'warn', 'no-array-constructor': 'off', '@typescript-eslint/no-array-constructor': 'error', 'no-empty-function': 'off', '@typescript-eslint/no-empty-function': 'error', '@typescript-eslint/no-empty-interface': 'error', '@typescript-eslint/no-explicit-any': 'warn', '@typescript-eslint/no-extra-non-null-assertion': 'error', 'no-extra-semi': 'off', '@typescript-eslint/no-extra-semi': 'error', '@typescript-eslint/no-inferrable-types': 'error', '@typescript-eslint/no-misused-new': 'error', '@typescript-eslint/no-namespace': 'error', '@typescript-eslint/no-non-null-asserted-optional-chain': 'error', '@typescript-eslint/no-non-null-assertion': 'warn', '@typescript-eslint/no-this-alias': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'warn', '@typescript-eslint/no-var-requires': 'error', '@typescript-eslint/prefer-as-const': 'error', '@typescript-eslint/prefer-namespace-keyword': 'error', '@typescript-eslint/triple-slash-reference': 'error' }, settings: undefined }, { type: 'config', name: '.eslintrc', filePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/.eslintrc', criteria: null, env: { browser: true, es6: true }, globals: { __DEV__: false }, ignorePattern: undefined, noInlineConfig: undefined, parser: { error: null, filePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/node_modules/@typescript-eslint/parser/dist/index.js', id: '@typescript-eslint/parser', importerName: '.eslintrc', importerPath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/.eslintrc' }, parserOptions: undefined, plugins: { '@typescript-eslint': [Object], react: [Object] }, processor: undefined, reportUnusedDisableDirectives: undefined, root: undefined, rules: { 'generator-star-spacing': 0, indent: [Array], 'no-warning-comments': [Array], 'operator-linebreak': [Array], 'padded-blocks': 0, semi: [Array], 'react/jsx-indent-props': [Array], 'react/jsx-boolean-value': [Array], 'react/jsx-curly-spacing': [Array], 'react/jsx-indent': [Array], '@typescript-eslint/explicit-module-boundary-type': 'off' }, settings: { react: [Object] } }, { type: 'config', name: '.eslintrc#overrides[0]', filePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/.eslintrc', criteria: { includes: [Array], excludes: null, basePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork' }, env: undefined, globals: undefined, ignorePattern: undefined, noInlineConfig: undefined, parser: undefined, parserOptions: undefined, plugins: undefined, processor: undefined, reportUnusedDisableDirectives: undefined, root: undefined, rules: { '@typescript-eslint/ban-ts-comment': 0, '@typescript-eslint/explicit-module-boundary-type': 'off' }, settings: undefined }, { type: 'ignore', name: 'eslintIgnore in package.json', filePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork/package.json', criteria: null, env: undefined, globals: undefined, ignorePattern: IgnorePattern { patterns: [Array], basePath: '/Users/thibault/Documents/GitHub/react-native-snap-carousel-fork', loose: true }, noInlineConfig: undefined, parser: undefined, parserOptions: undefined, plugins: undefined, processor: undefined, reportUnusedDisableDirectives: undefined, root: undefined, rules: undefined, settings: undefined } ] \ No newline at end of file From b96c5f4918389bd53da7077628d48290e11bee63 Mon Sep 17 00:00:00 2001 From: Thibault Malbranche Date: Thu, 6 Aug 2020 01:30:21 +0200 Subject: [PATCH 25/34] Update animations.ts --- src/utils/animations.ts | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/utils/animations.ts b/src/utils/animations.ts index a0df4069f..20b6eacbd 100644 --- a/src/utils/animations.ts +++ b/src/utils/animations.ts @@ -13,10 +13,10 @@ const IS_ANDROID = Platform.OS === 'android'; // index * sizeRef, // active // (index + 1) * sizeRef // active - 1 // ] -export function getInputRangeFromIndexes ( +export function getInputRangeFromIndexes ( range: number[], index: number, - carouselProps: CarouselProps + carouselProps: CarouselProps ) { const sizeRef = carouselProps.vertical ? carouselProps.itemHeight : @@ -33,9 +33,9 @@ export function getInputRangeFromIndexes ( // Default behavior // Scale and/or opacity effect // Based on props 'inactiveSlideOpacity' and 'inactiveSlideScale' -export function defaultScrollInterpolator ( +export function defaultScrollInterpolator ( index: number, - carouselProps: CarouselProps + carouselProps: CarouselProps ) { const range = [1, 0, -1]; const inputRange = getInputRangeFromIndexes(range, index, carouselProps); @@ -43,10 +43,10 @@ export function defaultScrollInterpolator ( return { inputRange, outputRange }; } -export function defaultAnimatedStyles ( +export function defaultAnimatedStyles ( _index: number, animatedValue: Animated.AnimatedInterpolation, - carouselProps: CarouselProps + carouselProps: CarouselProps ) { let animatedOpacity = {}; let animatedScale = {}; @@ -82,10 +82,10 @@ export function defaultAnimatedStyles ( // Shift animation // Same as the default one, but the active slide is also shifted up or down // Based on prop 'inactiveSlideShift' -export function shiftAnimatedStyles ( +export function shiftAnimatedStyles ( _index: number, animatedValue: Animated.AnimatedInterpolation, - carouselProps: CarouselProps + carouselProps: CarouselProps ) { let animatedOpacity = {}; let animatedScale = {}; @@ -130,9 +130,9 @@ export function shiftAnimatedStyles ( // WARNING: The effect had to be visually inverted on Android because this OS doesn't honor the `zIndex`property // This means that the item with the higher zIndex (and therefore the tap receiver) remains the one AFTER the currently active item // The `elevation` property compensates for that only visually, which is not good enough -export function stackScrollInterpolator ( +export function stackScrollInterpolator ( index: number, - carouselProps: CarouselProps + carouselProps: CarouselProps ) { const range = IS_ANDROID ? [1, 0, -1, -2, -3] : [3, 2, 1, 0, -1]; const inputRange = getInputRangeFromIndexes(range, index, carouselProps); @@ -140,10 +140,10 @@ export function stackScrollInterpolator ( return { inputRange, outputRange }; } -export function stackAnimatedStyles ( +export function stackAnimatedStyles ( index: number, animatedValue: Animated.AnimatedInterpolation, - carouselProps: CarouselProps, + carouselProps: CarouselProps, cardOffset: number ) { const sizeRef = carouselProps.vertical ? @@ -238,9 +238,9 @@ export function stackAnimatedStyles ( // WARNING: The effect had to be visually inverted on Android because this OS doesn't honor the `zIndex`property // This means that the item with the higher zIndex (and therefore the tap receiver) remains the one AFTER the currently active item // The `elevation` property compensates for that only visually, which is not good enough -export function tinderScrollInterpolator ( +export function tinderScrollInterpolator ( index: number, - carouselProps: CarouselProps + carouselProps: CarouselProps ) { const range = IS_ANDROID ? [1, 0, -1, -2, -3] : [3, 2, 1, 0, -1]; const inputRange = getInputRangeFromIndexes(range, index, carouselProps); @@ -248,10 +248,10 @@ export function tinderScrollInterpolator ( return { inputRange, outputRange }; } -export function tinderAnimatedStyles ( +export function tinderAnimatedStyles ( index: number, animatedValue: Animated.AnimatedInterpolation, - carouselProps: CarouselProps, + carouselProps: CarouselProps, cardOffset: number ) { const sizeRef = carouselProps.vertical ? From e719467cbfc6c2eb44031bdfbad558bc70d98ae4 Mon Sep 17 00:00:00 2001 From: Thibault Malbranche Date: Thu, 6 Aug 2020 01:48:28 +0200 Subject: [PATCH 26/34] Update Example folder --- .npmignore | 8 +- example/.eslintrc.js | 4 + example/.flowconfig | 8 +- example/__tests__/{index.js => App-test.js} | 4 +- example/android/.project | 17 - .../org.eclipse.buildship.core.prefs | 2 - example/android/app/build.gradle | 26 +- .../java/com/example/ReactNativeFlipper.java | 72 +++ .../android/app/src/main/AndroidManifest.xml | 3 +- .../java/com/example/MainApplication.java | 16 +- example/android/build.gradle | 11 +- example/android/gradle.properties | 7 + .../android/gradle/wrapper/gradle-wrapper.jar | Bin 55616 -> 58695 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- example/android/gradlew | 35 +- example/android/gradlew.bat | 203 +++---- example/ios/Podfile | 48 +- example/ios/Podfile.lock | 497 +++++++++++------- example/ios/example-tvOSTests/Info.plist | 2 +- example/ios/example.xcodeproj/project.pbxproj | 229 ++++---- .../xcschemes/example-tvOS.xcscheme | 45 +- .../xcshareddata/xcschemes/example.xcscheme | 45 +- example/ios/example/AppDelegate.h | 7 - example/ios/example/AppDelegate.m | 30 +- .../ios/example/Base.lproj/LaunchScreen.xib | 42 -- example/ios/example/LaunchScreen.storyboard | 58 ++ example/ios/example/main.m | 7 - example/ios/exampleTests/exampleTests.m | 9 +- example/package.json | 25 +- 29 files changed, 817 insertions(+), 645 deletions(-) create mode 100644 example/.eslintrc.js rename example/__tests__/{index.js => App-test.js} (77%) delete mode 100644 example/android/.project delete mode 100644 example/android/.settings/org.eclipse.buildship.core.prefs create mode 100644 example/android/app/src/debug/java/com/example/ReactNativeFlipper.java delete mode 100644 example/ios/example/Base.lproj/LaunchScreen.xib create mode 100644 example/ios/example/LaunchScreen.storyboard diff --git a/.npmignore b/.npmignore index 00db75c8b..c4e81354c 100644 --- a/.npmignore +++ b/.npmignore @@ -1,4 +1,8 @@ -/example -/node_modules +example +node_modules +doc +ISSUE_TEMPLATE.md package-lock.json yarn.lock +.vscode +.eslintrc \ No newline at end of file diff --git a/example/.eslintrc.js b/example/.eslintrc.js new file mode 100644 index 000000000..40c6dcd05 --- /dev/null +++ b/example/.eslintrc.js @@ -0,0 +1,4 @@ +module.exports = { + root: true, + extends: '@react-native-community', +}; diff --git a/example/.flowconfig b/example/.flowconfig index 4afc766a2..b274ad1d6 100644 --- a/example/.flowconfig +++ b/example/.flowconfig @@ -21,7 +21,7 @@ node_modules/warning/.* [include] [libs] -node_modules/react-native/Libraries/react-native/react-native-interface.js +node_modules/react-native/interface.js node_modules/react-native/flow/ [options] @@ -36,9 +36,8 @@ module.file_ext=.ios.js munge_underscores=true -module.name_mapper='^react-native$' -> '/node_modules/react-native/Libraries/react-native/react-native-implementation' module.name_mapper='^react-native/\(.*\)$' -> '/node_modules/react-native/\1' -module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '/node_modules/react-native/Libraries/Image/RelativeImageStub' +module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '/node_modules/react-native/Libraries/Image/RelativeImageStub' suppress_type=$FlowIssue suppress_type=$FlowFixMe @@ -57,7 +56,6 @@ untyped-type-import=warn nonstrict-import=warn deprecated-type=warn unsafe-getters-setters=warn -inexact-spread=warn unnecessary-invariant=warn signature-verification-failure=warn deprecated-utility=error @@ -72,4 +70,4 @@ untyped-import untyped-type-import [version] -^0.105.0 +^0.122.0 diff --git a/example/__tests__/index.js b/example/__tests__/App-test.js similarity index 77% rename from example/__tests__/index.js rename to example/__tests__/App-test.js index c433b04e5..178476699 100644 --- a/example/__tests__/index.js +++ b/example/__tests__/App-test.js @@ -4,11 +4,11 @@ import 'react-native'; import React from 'react'; -import Root from '../src/index'; +import App from '../App'; // Note: test renderer must be required after react-native. import renderer from 'react-test-renderer'; it('renders correctly', () => { - renderer.create(); + renderer.create(); }); diff --git a/example/android/.project b/example/android/.project deleted file mode 100644 index 3964dd3f5..000000000 --- a/example/android/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - android - Project android created by Buildship. - - - - - org.eclipse.buildship.core.gradleprojectbuilder - - - - - - org.eclipse.buildship.core.gradleprojectnature - - diff --git a/example/android/.settings/org.eclipse.buildship.core.prefs b/example/android/.settings/org.eclipse.buildship.core.prefs deleted file mode 100644 index e8895216f..000000000 --- a/example/android/.settings/org.eclipse.buildship.core.prefs +++ /dev/null @@ -1,2 +0,0 @@ -connection.project.dir= -eclipse.preferences.version=1 diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 6ab488dc5..4005f7d38 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -15,10 +15,12 @@ import com.android.build.OutputFile * // the name of the generated asset file containing your JS bundle * bundleAssetName: "index.android.bundle", * - * // the entry file for bundle generation + * // the entry file for bundle generation. If none specified and + * // "index.android.js" exists, it will be used. Otherwise "index.js" is + * // default. Can be overridden with ENTRY_FILE environment variable. * entryFile: "index.android.js", * - * // https://facebook.github.io/react-native/docs/performance#enable-the-ram-format + * // https://reactnative.dev/docs/performance#enable-the-ram-format * bundleCommand: "ram-bundle", * * // whether to bundle JS and assets in debug mode @@ -76,7 +78,6 @@ import com.android.build.OutputFile */ project.ext.react = [ - entryFile: "index.js", enableHermes: false, // clean and rebuild if changing ] @@ -156,12 +157,13 @@ android { } release { // Caution! In production, you need to generate your own keystore file. - // see https://facebook.github.io/react-native/docs/signed-apk-android. + // see https://reactnative.dev/docs/signed-apk-android. signingConfig signingConfigs.debug minifyEnabled enableProguardInReleaseBuilds proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" } } + // applicationVariants are e.g. debug, release applicationVariants.all { variant -> variant.outputs.each { output -> @@ -180,8 +182,24 @@ android { dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) + //noinspection GradleDynamicVersion implementation "com.facebook.react:react-native:+" // From node_modules + implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" + + debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") { + exclude group:'com.facebook.fbjni' + } + + debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") { + exclude group:'com.facebook.flipper' + exclude group:'com.squareup.okhttp3', module:'okhttp' + } + + debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") { + exclude group:'com.facebook.flipper' + } + if (enableHermes) { def hermesPath = "../../node_modules/hermes-engine/android/"; debugImplementation files(hermesPath + "hermes-debug.aar") diff --git a/example/android/app/src/debug/java/com/example/ReactNativeFlipper.java b/example/android/app/src/debug/java/com/example/ReactNativeFlipper.java new file mode 100644 index 000000000..a1b70b8aa --- /dev/null +++ b/example/android/app/src/debug/java/com/example/ReactNativeFlipper.java @@ -0,0 +1,72 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + *

This source code is licensed under the MIT license found in the LICENSE file in the root + * directory of this source tree. + */ +package com.example; + +import android.content.Context; +import com.facebook.flipper.android.AndroidFlipperClient; +import com.facebook.flipper.android.utils.FlipperUtils; +import com.facebook.flipper.core.FlipperClient; +import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin; +import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin; +import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin; +import com.facebook.flipper.plugins.inspector.DescriptorMapping; +import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin; +import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor; +import com.facebook.flipper.plugins.network.NetworkFlipperPlugin; +import com.facebook.flipper.plugins.react.ReactFlipperPlugin; +import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin; +import com.facebook.react.ReactInstanceManager; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.modules.network.NetworkingModule; +import okhttp3.OkHttpClient; + +public class ReactNativeFlipper { + public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { + if (FlipperUtils.shouldEnableFlipper(context)) { + final FlipperClient client = AndroidFlipperClient.getInstance(context); + + client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults())); + client.addPlugin(new ReactFlipperPlugin()); + client.addPlugin(new DatabasesFlipperPlugin(context)); + client.addPlugin(new SharedPreferencesFlipperPlugin(context)); + client.addPlugin(CrashReporterPlugin.getInstance()); + + NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin(); + NetworkingModule.setCustomClientBuilder( + new NetworkingModule.CustomClientBuilder() { + @Override + public void apply(OkHttpClient.Builder builder) { + builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin)); + } + }); + client.addPlugin(networkFlipperPlugin); + client.start(); + + // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized + // Hence we run if after all native modules have been initialized + ReactContext reactContext = reactInstanceManager.getCurrentReactContext(); + if (reactContext == null) { + reactInstanceManager.addReactInstanceEventListener( + new ReactInstanceManager.ReactInstanceEventListener() { + @Override + public void onReactContextInitialized(ReactContext reactContext) { + reactInstanceManager.removeReactInstanceEventListener(this); + reactContext.runOnNativeModulesQueueThread( + new Runnable() { + @Override + public void run() { + client.addPlugin(new FrescoFlipperPlugin()); + } + }); + } + }); + } else { + client.addPlugin(new FrescoFlipperPlugin()); + } + } + } +} diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 24fa5b450..6e27a2a31 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -13,7 +13,8 @@ diff --git a/example/android/app/src/main/java/com/example/MainApplication.java b/example/android/app/src/main/java/com/example/MainApplication.java index 567e0a671..fd8ec883d 100644 --- a/example/android/app/src/main/java/com/example/MainApplication.java +++ b/example/android/app/src/main/java/com/example/MainApplication.java @@ -4,6 +4,7 @@ import android.content.Context; import com.facebook.react.PackageList; import com.facebook.react.ReactApplication; +import com.facebook.react.ReactInstanceManager; import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactPackage; import com.facebook.soloader.SoLoader; @@ -43,23 +44,28 @@ public ReactNativeHost getReactNativeHost() { public void onCreate() { super.onCreate(); SoLoader.init(this, /* native exopackage */ false); - initializeFlipper(this); // Remove this line if you don't want Flipper enabled + initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); } /** - * Loads Flipper in React Native templates. + * Loads Flipper in React Native templates. Call this in the onCreate method with something like + * initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); * * @param context + * @param reactInstanceManager */ - private static void initializeFlipper(Context context) { + private static void initializeFlipper( + Context context, ReactInstanceManager reactInstanceManager) { if (BuildConfig.DEBUG) { try { /* We use reflection here to pick up the class that initializes Flipper, since Flipper library is not available in release mode */ - Class aClass = Class.forName("com.facebook.flipper.ReactNativeFlipper"); - aClass.getMethod("initializeFlipper", Context.class).invoke(null, context); + Class aClass = Class.forName("com.example.ReactNativeFlipper"); + aClass + .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class) + .invoke(null, context, reactInstanceManager); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { diff --git a/example/android/build.gradle b/example/android/build.gradle index 28f7ec645..ed5a56842 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -2,18 +2,17 @@ buildscript { ext { - buildToolsVersion = "28.0.3" + buildToolsVersion = "29.0.2" minSdkVersion = 16 - compileSdkVersion = 28 - targetSdkVersion = 28 + compileSdkVersion = 29 + targetSdkVersion = 29 } repositories { google() jcenter() } dependencies { - classpath("com.android.tools.build:gradle:3.4.2") - + classpath("com.android.tools.build:gradle:3.5.3") // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } @@ -33,6 +32,6 @@ allprojects { google() jcenter() - maven { url 'https://jitpack.io' } + maven { url 'https://www.jitpack.io' } } } diff --git a/example/android/gradle.properties b/example/android/gradle.properties index 027ef9db8..04ca0ef29 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -17,5 +17,12 @@ # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX android.enableJetifier=true + +# Version of flipper SDK to use with React Native +FLIPPER_VERSION=0.37.0 diff --git a/example/android/gradle/wrapper/gradle-wrapper.jar b/example/android/gradle/wrapper/gradle-wrapper.jar index 5c2d1cf016b3885f6930543d57b744ea8c220a1a..f3d88b1c2faf2fc91d853cd5d4242b5547257070 100644 GIT binary patch delta 22808 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 delta 20005 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 nvEpR}Ffj;rE}sKLhd|1%F5dI!=ox-rLy2NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/example/ios/Podfile b/example/ios/Podfile index a52509089..f74e34522 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,45 +1,26 @@ -platform :ios, '9.0' +require_relative '../node_modules/react-native/scripts/react_native_pods' require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' -target 'example' do - # Pods for example - pod 'FBLazyVector', :path => "../node_modules/react-native/Libraries/FBLazyVector" - pod 'FBReactNativeSpec', :path => "../node_modules/react-native/Libraries/FBReactNativeSpec" - pod 'RCTRequired', :path => "../node_modules/react-native/Libraries/RCTRequired" - pod 'RCTTypeSafety', :path => "../node_modules/react-native/Libraries/TypeSafety" - pod 'React', :path => '../node_modules/react-native/' - pod 'React-Core', :path => '../node_modules/react-native/' - pod 'React-CoreModules', :path => '../node_modules/react-native/React/CoreModules' - pod 'React-Core/DevSupport', :path => '../node_modules/react-native/' - pod 'React-RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS' - pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation' - pod 'React-RCTBlob', :path => '../node_modules/react-native/Libraries/Blob' - pod 'React-RCTImage', :path => '../node_modules/react-native/Libraries/Image' - pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS' - pod 'React-RCTNetwork', :path => '../node_modules/react-native/Libraries/Network' - pod 'React-RCTSettings', :path => '../node_modules/react-native/Libraries/Settings' - pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text' - pod 'React-RCTVibration', :path => '../node_modules/react-native/Libraries/Vibration' - pod 'React-Core/RCTWebSocket', :path => '../node_modules/react-native/' +platform :ios, '10.0' - pod 'React-cxxreact', :path => '../node_modules/react-native/ReactCommon/cxxreact' - pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi' - pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor' - pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector' - pod 'ReactCommon/jscallinvoker', :path => "../node_modules/react-native/ReactCommon" - pod 'ReactCommon/turbomodule/core', :path => "../node_modules/react-native/ReactCommon" - pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga' +target 'example' do + config = use_native_modules! - pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec' - pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec' - pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec' + use_react_native!(:path => config["reactNativePath"]) target 'exampleTests' do - inherit! :search_paths + inherit! :complete # Pods for testing end - use_native_modules! + # Enables Flipper. + # + # Note that if you have use_frameworks! enabled, Flipper will not work and + # you should disable these next few lines. + use_flipper! + post_install do |installer| + flipper_post_install(installer) + end end target 'example-tvOS' do @@ -49,5 +30,4 @@ target 'example-tvOS' do inherit! :search_paths # Pods for testing end - end diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 828ecd42d..2234009c0 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2,235 +2,336 @@ PODS: - boost-for-react-native (1.63.0) - BVLinearGradient (2.5.6): - React + - CocoaAsyncSocket (7.6.4) + - CocoaLibEvent (1.0.0) - DoubleConversion (1.1.6) - - FBLazyVector (0.61.5) - - FBReactNativeSpec (0.61.5): - - Folly (= 2018.10.22.00) - - RCTRequired (= 0.61.5) - - RCTTypeSafety (= 0.61.5) - - React-Core (= 0.61.5) - - React-jsi (= 0.61.5) - - ReactCommon/turbomodule/core (= 0.61.5) - - Folly (2018.10.22.00): + - FBLazyVector (0.63.2) + - FBReactNativeSpec (0.63.2): + - Folly (= 2020.01.13.00) + - RCTRequired (= 0.63.2) + - RCTTypeSafety (= 0.63.2) + - React-Core (= 0.63.2) + - React-jsi (= 0.63.2) + - ReactCommon/turbomodule/core (= 0.63.2) + - Flipper (0.41.5): + - Flipper-Folly (~> 2.2) + - Flipper-RSocket (~> 1.1) + - Flipper-DoubleConversion (1.1.7) + - Flipper-Folly (2.2.0): + - boost-for-react-native + - CocoaLibEvent (~> 1.0) + - Flipper-DoubleConversion + - Flipper-Glog + - OpenSSL-Universal (= 1.0.2.19) + - Flipper-Glog (0.3.6) + - Flipper-PeerTalk (0.0.4) + - Flipper-RSocket (1.1.0): + - Flipper-Folly (~> 2.2) + - FlipperKit (0.41.5): + - FlipperKit/Core (= 0.41.5) + - FlipperKit/Core (0.41.5): + - Flipper (~> 0.41.5) + - FlipperKit/CppBridge + - FlipperKit/FBCxxFollyDynamicConvert + - FlipperKit/FBDefines + - FlipperKit/FKPortForwarding + - FlipperKit/CppBridge (0.41.5): + - Flipper (~> 0.41.5) + - FlipperKit/FBCxxFollyDynamicConvert (0.41.5): + - Flipper-Folly (~> 2.2) + - FlipperKit/FBDefines (0.41.5) + - FlipperKit/FKPortForwarding (0.41.5): + - CocoaAsyncSocket (~> 7.6) + - Flipper-PeerTalk (~> 0.0.4) + - FlipperKit/FlipperKitHighlightOverlay (0.41.5) + - FlipperKit/FlipperKitLayoutPlugin (0.41.5): + - FlipperKit/Core + - FlipperKit/FlipperKitHighlightOverlay + - FlipperKit/FlipperKitLayoutTextSearchable + - YogaKit (~> 1.18) + - FlipperKit/FlipperKitLayoutTextSearchable (0.41.5) + - FlipperKit/FlipperKitNetworkPlugin (0.41.5): + - FlipperKit/Core + - FlipperKit/FlipperKitReactPlugin (0.41.5): + - FlipperKit/Core + - FlipperKit/FlipperKitUserDefaultsPlugin (0.41.5): + - FlipperKit/Core + - FlipperKit/SKIOSNetworkPlugin (0.41.5): + - FlipperKit/Core + - FlipperKit/FlipperKitNetworkPlugin + - Folly (2020.01.13.00): - boost-for-react-native - DoubleConversion - - Folly/Default (= 2018.10.22.00) + - Folly/Default (= 2020.01.13.00) - glog - - Folly/Default (2018.10.22.00): + - Folly/Default (2020.01.13.00): - boost-for-react-native - DoubleConversion - glog - glog (0.3.5) - - RCTRequired (0.61.5) - - RCTTypeSafety (0.61.5): - - FBLazyVector (= 0.61.5) - - Folly (= 2018.10.22.00) - - RCTRequired (= 0.61.5) - - React-Core (= 0.61.5) - - React (0.61.5): - - React-Core (= 0.61.5) - - React-Core/DevSupport (= 0.61.5) - - React-Core/RCTWebSocket (= 0.61.5) - - React-RCTActionSheet (= 0.61.5) - - React-RCTAnimation (= 0.61.5) - - React-RCTBlob (= 0.61.5) - - React-RCTImage (= 0.61.5) - - React-RCTLinking (= 0.61.5) - - React-RCTNetwork (= 0.61.5) - - React-RCTSettings (= 0.61.5) - - React-RCTText (= 0.61.5) - - React-RCTVibration (= 0.61.5) - - React-Core (0.61.5): - - Folly (= 2018.10.22.00) + - OpenSSL-Universal (1.0.2.19): + - OpenSSL-Universal/Static (= 1.0.2.19) + - OpenSSL-Universal/Static (1.0.2.19) + - RCTRequired (0.63.2) + - RCTTypeSafety (0.63.2): + - FBLazyVector (= 0.63.2) + - Folly (= 2020.01.13.00) + - RCTRequired (= 0.63.2) + - React-Core (= 0.63.2) + - React (0.63.2): + - React-Core (= 0.63.2) + - React-Core/DevSupport (= 0.63.2) + - React-Core/RCTWebSocket (= 0.63.2) + - React-RCTActionSheet (= 0.63.2) + - React-RCTAnimation (= 0.63.2) + - React-RCTBlob (= 0.63.2) + - React-RCTImage (= 0.63.2) + - React-RCTLinking (= 0.63.2) + - React-RCTNetwork (= 0.63.2) + - React-RCTSettings (= 0.63.2) + - React-RCTText (= 0.63.2) + - React-RCTVibration (= 0.63.2) + - React-callinvoker (0.63.2) + - React-Core (0.63.2): + - Folly (= 2020.01.13.00) - glog - - React-Core/Default (= 0.61.5) - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsiexecutor (= 0.61.5) + - React-Core/Default (= 0.63.2) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsiexecutor (= 0.63.2) - Yoga - - React-Core/CoreModulesHeaders (0.61.5): - - Folly (= 2018.10.22.00) + - React-Core/CoreModulesHeaders (0.63.2): + - Folly (= 2020.01.13.00) - glog - React-Core/Default - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsiexecutor (= 0.61.5) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsiexecutor (= 0.63.2) - Yoga - - React-Core/Default (0.61.5): - - Folly (= 2018.10.22.00) + - React-Core/Default (0.63.2): + - Folly (= 2020.01.13.00) - glog - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsiexecutor (= 0.61.5) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsiexecutor (= 0.63.2) - Yoga - - React-Core/DevSupport (0.61.5): - - Folly (= 2018.10.22.00) + - React-Core/DevSupport (0.63.2): + - Folly (= 2020.01.13.00) - glog - - React-Core/Default (= 0.61.5) - - React-Core/RCTWebSocket (= 0.61.5) - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsiexecutor (= 0.61.5) - - React-jsinspector (= 0.61.5) + - React-Core/Default (= 0.63.2) + - React-Core/RCTWebSocket (= 0.63.2) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsiexecutor (= 0.63.2) + - React-jsinspector (= 0.63.2) - Yoga - - React-Core/RCTActionSheetHeaders (0.61.5): - - Folly (= 2018.10.22.00) + - React-Core/RCTActionSheetHeaders (0.63.2): + - Folly (= 2020.01.13.00) - glog - React-Core/Default - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsiexecutor (= 0.61.5) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsiexecutor (= 0.63.2) - Yoga - - React-Core/RCTAnimationHeaders (0.61.5): - - Folly (= 2018.10.22.00) + - React-Core/RCTAnimationHeaders (0.63.2): + - Folly (= 2020.01.13.00) - glog - React-Core/Default - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsiexecutor (= 0.61.5) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsiexecutor (= 0.63.2) - Yoga - - React-Core/RCTBlobHeaders (0.61.5): - - Folly (= 2018.10.22.00) + - React-Core/RCTBlobHeaders (0.63.2): + - Folly (= 2020.01.13.00) - glog - React-Core/Default - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsiexecutor (= 0.61.5) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsiexecutor (= 0.63.2) - Yoga - - React-Core/RCTImageHeaders (0.61.5): - - Folly (= 2018.10.22.00) + - React-Core/RCTImageHeaders (0.63.2): + - Folly (= 2020.01.13.00) - glog - React-Core/Default - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsiexecutor (= 0.61.5) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsiexecutor (= 0.63.2) - Yoga - - React-Core/RCTLinkingHeaders (0.61.5): - - Folly (= 2018.10.22.00) + - React-Core/RCTLinkingHeaders (0.63.2): + - Folly (= 2020.01.13.00) - glog - React-Core/Default - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsiexecutor (= 0.61.5) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsiexecutor (= 0.63.2) - Yoga - - React-Core/RCTNetworkHeaders (0.61.5): - - Folly (= 2018.10.22.00) + - React-Core/RCTNetworkHeaders (0.63.2): + - Folly (= 2020.01.13.00) - glog - React-Core/Default - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsiexecutor (= 0.61.5) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsiexecutor (= 0.63.2) - Yoga - - React-Core/RCTSettingsHeaders (0.61.5): - - Folly (= 2018.10.22.00) + - React-Core/RCTSettingsHeaders (0.63.2): + - Folly (= 2020.01.13.00) - glog - React-Core/Default - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsiexecutor (= 0.61.5) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsiexecutor (= 0.63.2) - Yoga - - React-Core/RCTTextHeaders (0.61.5): - - Folly (= 2018.10.22.00) + - React-Core/RCTTextHeaders (0.63.2): + - Folly (= 2020.01.13.00) - glog - React-Core/Default - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsiexecutor (= 0.61.5) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsiexecutor (= 0.63.2) - Yoga - - React-Core/RCTVibrationHeaders (0.61.5): - - Folly (= 2018.10.22.00) + - React-Core/RCTVibrationHeaders (0.63.2): + - Folly (= 2020.01.13.00) - glog - React-Core/Default - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsiexecutor (= 0.61.5) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsiexecutor (= 0.63.2) - Yoga - - React-Core/RCTWebSocket (0.61.5): - - Folly (= 2018.10.22.00) + - React-Core/RCTWebSocket (0.63.2): + - Folly (= 2020.01.13.00) - glog - - React-Core/Default (= 0.61.5) - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsiexecutor (= 0.61.5) + - React-Core/Default (= 0.63.2) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsiexecutor (= 0.63.2) - Yoga - - React-CoreModules (0.61.5): - - FBReactNativeSpec (= 0.61.5) - - Folly (= 2018.10.22.00) - - RCTTypeSafety (= 0.61.5) - - React-Core/CoreModulesHeaders (= 0.61.5) - - React-RCTImage (= 0.61.5) - - ReactCommon/turbomodule/core (= 0.61.5) - - React-cxxreact (0.61.5): + - React-CoreModules (0.63.2): + - FBReactNativeSpec (= 0.63.2) + - Folly (= 2020.01.13.00) + - RCTTypeSafety (= 0.63.2) + - React-Core/CoreModulesHeaders (= 0.63.2) + - React-jsi (= 0.63.2) + - React-RCTImage (= 0.63.2) + - ReactCommon/turbomodule/core (= 0.63.2) + - React-cxxreact (0.63.2): - boost-for-react-native (= 1.63.0) - DoubleConversion - - Folly (= 2018.10.22.00) + - Folly (= 2020.01.13.00) - glog - - React-jsinspector (= 0.61.5) - - React-jsi (0.61.5): + - React-callinvoker (= 0.63.2) + - React-jsinspector (= 0.63.2) + - React-jsi (0.63.2): - boost-for-react-native (= 1.63.0) - DoubleConversion - - Folly (= 2018.10.22.00) + - Folly (= 2020.01.13.00) - glog - - React-jsi/Default (= 0.61.5) - - React-jsi/Default (0.61.5): + - React-jsi/Default (= 0.63.2) + - React-jsi/Default (0.63.2): - boost-for-react-native (= 1.63.0) - DoubleConversion - - Folly (= 2018.10.22.00) - - glog - - React-jsiexecutor (0.61.5): - - DoubleConversion - - Folly (= 2018.10.22.00) + - Folly (= 2020.01.13.00) - glog - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - React-jsinspector (0.61.5) - - React-RCTActionSheet (0.61.5): - - React-Core/RCTActionSheetHeaders (= 0.61.5) - - React-RCTAnimation (0.61.5): - - React-Core/RCTAnimationHeaders (= 0.61.5) - - React-RCTBlob (0.61.5): - - React-Core/RCTBlobHeaders (= 0.61.5) - - React-Core/RCTWebSocket (= 0.61.5) - - React-jsi (= 0.61.5) - - React-RCTNetwork (= 0.61.5) - - React-RCTImage (0.61.5): - - React-Core/RCTImageHeaders (= 0.61.5) - - React-RCTNetwork (= 0.61.5) - - React-RCTLinking (0.61.5): - - React-Core/RCTLinkingHeaders (= 0.61.5) - - React-RCTNetwork (0.61.5): - - React-Core/RCTNetworkHeaders (= 0.61.5) - - React-RCTSettings (0.61.5): - - React-Core/RCTSettingsHeaders (= 0.61.5) - - React-RCTText (0.61.5): - - React-Core/RCTTextHeaders (= 0.61.5) - - React-RCTVibration (0.61.5): - - React-Core/RCTVibrationHeaders (= 0.61.5) - - ReactCommon/jscallinvoker (0.61.5): + - React-jsiexecutor (0.63.2): - DoubleConversion - - Folly (= 2018.10.22.00) + - Folly (= 2020.01.13.00) - glog - - React-cxxreact (= 0.61.5) - - ReactCommon/turbomodule/core (0.61.5): + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) + - React-jsinspector (0.63.2) + - React-RCTActionSheet (0.63.2): + - React-Core/RCTActionSheetHeaders (= 0.63.2) + - React-RCTAnimation (0.63.2): + - FBReactNativeSpec (= 0.63.2) + - Folly (= 2020.01.13.00) + - RCTTypeSafety (= 0.63.2) + - React-Core/RCTAnimationHeaders (= 0.63.2) + - React-jsi (= 0.63.2) + - ReactCommon/turbomodule/core (= 0.63.2) + - React-RCTBlob (0.63.2): + - FBReactNativeSpec (= 0.63.2) + - Folly (= 2020.01.13.00) + - React-Core/RCTBlobHeaders (= 0.63.2) + - React-Core/RCTWebSocket (= 0.63.2) + - React-jsi (= 0.63.2) + - React-RCTNetwork (= 0.63.2) + - ReactCommon/turbomodule/core (= 0.63.2) + - React-RCTImage (0.63.2): + - FBReactNativeSpec (= 0.63.2) + - Folly (= 2020.01.13.00) + - RCTTypeSafety (= 0.63.2) + - React-Core/RCTImageHeaders (= 0.63.2) + - React-jsi (= 0.63.2) + - React-RCTNetwork (= 0.63.2) + - ReactCommon/turbomodule/core (= 0.63.2) + - React-RCTLinking (0.63.2): + - FBReactNativeSpec (= 0.63.2) + - React-Core/RCTLinkingHeaders (= 0.63.2) + - React-jsi (= 0.63.2) + - ReactCommon/turbomodule/core (= 0.63.2) + - React-RCTNetwork (0.63.2): + - FBReactNativeSpec (= 0.63.2) + - Folly (= 2020.01.13.00) + - RCTTypeSafety (= 0.63.2) + - React-Core/RCTNetworkHeaders (= 0.63.2) + - React-jsi (= 0.63.2) + - ReactCommon/turbomodule/core (= 0.63.2) + - React-RCTSettings (0.63.2): + - FBReactNativeSpec (= 0.63.2) + - Folly (= 2020.01.13.00) + - RCTTypeSafety (= 0.63.2) + - React-Core/RCTSettingsHeaders (= 0.63.2) + - React-jsi (= 0.63.2) + - ReactCommon/turbomodule/core (= 0.63.2) + - React-RCTText (0.63.2): + - React-Core/RCTTextHeaders (= 0.63.2) + - React-RCTVibration (0.63.2): + - FBReactNativeSpec (= 0.63.2) + - Folly (= 2020.01.13.00) + - React-Core/RCTVibrationHeaders (= 0.63.2) + - React-jsi (= 0.63.2) + - ReactCommon/turbomodule/core (= 0.63.2) + - ReactCommon/turbomodule/core (0.63.2): - DoubleConversion - - Folly (= 2018.10.22.00) + - Folly (= 2020.01.13.00) - glog - - React-Core (= 0.61.5) - - React-cxxreact (= 0.61.5) - - React-jsi (= 0.61.5) - - ReactCommon/jscallinvoker (= 0.61.5) + - React-callinvoker (= 0.63.2) + - React-Core (= 0.63.2) + - React-cxxreact (= 0.63.2) + - React-jsi (= 0.63.2) - Yoga (1.14.0) + - YogaKit (1.18.1): + - Yoga (~> 1.14) DEPENDENCIES: - BVLinearGradient (from `../node_modules/react-native-linear-gradient`) - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - FBReactNativeSpec (from `../node_modules/react-native/Libraries/FBReactNativeSpec`) + - Flipper (~> 0.41.1) + - Flipper-DoubleConversion (= 1.1.7) + - Flipper-Folly (~> 2.2) + - Flipper-Glog (= 0.3.6) + - Flipper-PeerTalk (~> 0.0.4) + - Flipper-RSocket (~> 1.1) + - FlipperKit (~> 0.41.1) + - FlipperKit/Core (~> 0.41.1) + - FlipperKit/CppBridge (~> 0.41.1) + - FlipperKit/FBCxxFollyDynamicConvert (~> 0.41.1) + - FlipperKit/FBDefines (~> 0.41.1) + - FlipperKit/FKPortForwarding (~> 0.41.1) + - FlipperKit/FlipperKitHighlightOverlay (~> 0.41.1) + - FlipperKit/FlipperKitLayoutPlugin (~> 0.41.1) + - FlipperKit/FlipperKitLayoutTextSearchable (~> 0.41.1) + - FlipperKit/FlipperKitNetworkPlugin (~> 0.41.1) + - FlipperKit/FlipperKitReactPlugin (~> 0.41.1) + - FlipperKit/FlipperKitUserDefaultsPlugin (~> 0.41.1) + - FlipperKit/SKIOSNetworkPlugin (~> 0.41.1) - Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`) - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) - RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`) - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) - React (from `../node_modules/react-native/`) + - React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`) - React-Core (from `../node_modules/react-native/`) - React-Core/DevSupport (from `../node_modules/react-native/`) - React-Core/RCTWebSocket (from `../node_modules/react-native/`) @@ -248,13 +349,23 @@ DEPENDENCIES: - React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`) - React-RCTText (from `../node_modules/react-native/Libraries/Text`) - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) - - ReactCommon/jscallinvoker (from `../node_modules/react-native/ReactCommon`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) SPEC REPOS: trunk: - boost-for-react-native + - CocoaAsyncSocket + - CocoaLibEvent + - Flipper + - Flipper-DoubleConversion + - Flipper-Folly + - Flipper-Glog + - Flipper-PeerTalk + - Flipper-RSocket + - FlipperKit + - OpenSSL-Universal + - YogaKit EXTERNAL SOURCES: BVLinearGradient: @@ -275,6 +386,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/Libraries/TypeSafety" React: :path: "../node_modules/react-native/" + React-callinvoker: + :path: "../node_modules/react-native/ReactCommon/callinvoker" React-Core: :path: "../node_modules/react-native/" React-CoreModules: @@ -313,32 +426,44 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c BVLinearGradient: e3aad03778a456d77928f594a649e96995f1c872 - DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2 - FBLazyVector: aaeaf388755e4f29cd74acbc9e3b8da6d807c37f - FBReactNativeSpec: 118d0d177724c2d67f08a59136eb29ef5943ec75 - Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51 - glog: 1f3da668190260b06b429bb211bfbee5cd790c28 - RCTRequired: b153add4da6e7dbc44aebf93f3cf4fcae392ddf1 - RCTTypeSafety: 9aa1b91d7f9310fc6eadc3cf95126ffe818af320 - React: b6a59ef847b2b40bb6e0180a97d0ca716969ac78 - React-Core: 688b451f7d616cc1134ac95295b593d1b5158a04 - React-CoreModules: d04f8494c1a328b69ec11db9d1137d667f916dcb - React-cxxreact: d0f7bcafa196ae410e5300736b424455e7fb7ba7 - React-jsi: cb2cd74d7ccf4cffb071a46833613edc79cdf8f7 - React-jsiexecutor: d5525f9ed5f782fdbacb64b9b01a43a9323d2386 - React-jsinspector: fa0ecc501688c3c4c34f28834a76302233e29dc0 - React-RCTActionSheet: 600b4d10e3aea0913b5a92256d2719c0cdd26d76 - React-RCTAnimation: 791a87558389c80908ed06cc5dfc5e7920dfa360 - React-RCTBlob: d89293cc0236d9cb0933d85e430b0bbe81ad1d72 - React-RCTImage: 6b8e8df449eb7c814c99a92d6b52de6fe39dea4e - React-RCTLinking: 121bb231c7503cf9094f4d8461b96a130fabf4a5 - React-RCTNetwork: fb353640aafcee84ca8b78957297bd395f065c9a - React-RCTSettings: 8db258ea2a5efee381fcf7a6d5044e2f8b68b640 - React-RCTText: 9ccc88273e9a3aacff5094d2175a605efa854dbe - React-RCTVibration: a49a1f42bf8f5acf1c3e297097517c6b3af377ad - ReactCommon: 198c7c8d3591f975e5431bec1b0b3b581aa1c5dd - Yoga: f2a7cd4280bfe2cca5a7aed98ba0eb3d1310f18b + CocoaAsyncSocket: 694058e7c0ed05a9e217d1b3c7ded962f4180845 + CocoaLibEvent: 2fab71b8bd46dd33ddb959f7928ec5909f838e3f + DoubleConversion: cde416483dac037923206447da6e1454df403714 + FBLazyVector: 3ef4a7f62e7db01092f9d517d2ebc0d0677c4a37 + FBReactNativeSpec: dc7fa9088f0f2a998503a352b0554d69a4391c5a + Flipper: 33585e2d9810fe5528346be33bcf71b37bb7ae13 + Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41 + Flipper-Folly: c12092ea368353b58e992843a990a3225d4533c3 + Flipper-Glog: 1dfd6abf1e922806c52ceb8701a3599a79a200a6 + Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9 + Flipper-RSocket: 64e7431a55835eb953b0bf984ef3b90ae9fdddd7 + FlipperKit: bc68102cd4952a258a23c9c1b316c7bec1fecf83 + Folly: b73c3869541e86821df3c387eb0af5f65addfab4 + glog: 40a13f7840415b9a77023fbcae0f1e6f43192af3 + OpenSSL-Universal: 8b48cc0d10c1b2923617dfe5c178aa9ed2689355 + RCTRequired: f13f25e7b12f925f1f6a6a8c69d929a03c0129fe + RCTTypeSafety: 44982c5c8e43ff4141eb519a8ddc88059acd1f3a + React: e1c65dd41cb9db13b99f24608e47dd595f28ca9a + React-callinvoker: 552a6a6bc8b3bb794cf108ad59e5a9e2e3b4fc98 + React-Core: 9d341e725dc9cd2f49e4c49ad1fc4e8776aa2639 + React-CoreModules: 5335e168165da7f7083ce7147768d36d3e292318 + React-cxxreact: d3261ec5f7d11743fbf21e263a34ea51d1f13ebc + React-jsi: 54245e1d5f4b690dec614a73a3795964eeef13a8 + React-jsiexecutor: 8ca588cc921e70590820ce72b8789b02c67cce38 + React-jsinspector: b14e62ebe7a66e9231e9581279909f2fc3db6606 + React-RCTActionSheet: 910163b6b09685a35c4ebbc52b66d1bfbbe39fc5 + React-RCTAnimation: 9a883bbe1e9d2e158d4fb53765ed64c8dc2200c6 + React-RCTBlob: 39cf0ece1927996c4466510e25d2105f67010e13 + React-RCTImage: de355d738727b09ad3692f2a979affbd54b5f378 + React-RCTLinking: 8122f221d395a63364b2c0078ce284214bd04575 + React-RCTNetwork: 8f96c7b49ea6a0f28f98258f347b6ad218bc0830 + React-RCTSettings: 8a49622aff9c1925f5455fa340b6fe4853d64ab6 + React-RCTText: 1b6773e776e4b33f90468c20fe3b16ca3e224bb8 + React-RCTVibration: 4d2e726957f4087449739b595f107c0d4b6c2d2d + ReactCommon: a0a1edbebcac5e91338371b72ffc66aa822792ce + Yoga: 7740b94929bbacbddda59bf115b5317e9a161598 + YogaKit: f782866e155069a2cca2517aafea43200b01fd5a -PODFILE CHECKSUM: 79310af6b976c356911a8a833e9b99c3399fdda3 +PODFILE CHECKSUM: 311cf87a4a33d759b7ec994ec3735e03d4ededbf -COCOAPODS: 1.8.3 +COCOAPODS: 1.9.1 diff --git a/example/ios/example-tvOSTests/Info.plist b/example/ios/example-tvOSTests/Info.plist index 886825ccc..ba72822e8 100644 --- a/example/ios/example-tvOSTests/Info.plist +++ b/example/ios/example-tvOSTests/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/example/ios/example.xcodeproj/project.pbxproj b/example/ios/example.xcodeproj/project.pbxproj index 9c3b1afc4..87a3710b4 100644 --- a/example/ios/example.xcodeproj/project.pbxproj +++ b/example/ios/example.xcodeproj/project.pbxproj @@ -9,17 +9,17 @@ /* Begin PBXBuildFile section */ 00E356F31AD99517003FC87E /* exampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* exampleTests.m */; }; 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; - 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; - 29C871C0741732DA80D4A28B /* libPods-exampleTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E829CA22C7590836F0BDC89 /* libPods-exampleTests.a */; }; + 14E69C44E603A2DABF87E9C7 /* libPods-example-tvOSTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8AA817F492F434E8A1702ABF /* libPods-example-tvOSTests.a */; }; 2D02E4BC1E0B4A80006451C7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; 2D02E4BD1E0B4A84006451C7 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 2D02E4BF1E0B4AB3006451C7 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 2DCD954D1E0B4F2C00145EB5 /* exampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* exampleTests.m */; }; - 871938999323E775E1A601AA /* libPods-example-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D7EE427033EF6DE8A18138AF /* libPods-example-tvOS.a */; }; - B4AC6785CB63F44B1476B739 /* libPods-example-tvOSTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D322FB106C50427D6E424A6B /* libPods-example-tvOSTests.a */; }; - F272BF3BCAF6E9F45485CA12 /* libPods-example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2A1084C799FFD8DE81B1B05A /* libPods-example.a */; }; + 392E28A89C8849C7123ADF53 /* libPods-example-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 260CE5E4DEF527E7552C4F35 /* libPods-example-tvOS.a */; }; + 6A19D51D1CBD97B286B5C5A2 /* libPods-example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7BFDCC05221B3C6B417CEEE3 /* libPods-example.a */; }; + 803B24D227A21AA7146EE469 /* libPods-example-exampleTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FFEB7F51062CCC33B9B3EB4A /* libPods-example-exampleTests.a */; }; + 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -44,29 +44,29 @@ 00E356EE1AD99517003FC87E /* exampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = exampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 00E356F21AD99517003FC87E /* exampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = exampleTests.m; sourceTree = ""; }; - 0685F86383145C5CB4CDA68E /* Pods-exampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-exampleTests.release.xcconfig"; path = "Target Support Files/Pods-exampleTests/Pods-exampleTests.release.xcconfig"; sourceTree = ""; }; - 11B0F93180450FD943C626DD /* Pods-example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example.release.xcconfig"; path = "Target Support Files/Pods-example/Pods-example.release.xcconfig"; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = example/AppDelegate.h; sourceTree = ""; }; 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = example/AppDelegate.m; sourceTree = ""; }; - 13B07FB21A68108700A75B9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = example/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = example/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = example/main.m; sourceTree = ""; }; - 2848D7646158208FFC51959F /* Pods-example-tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example-tvOS.release.xcconfig"; path = "Target Support Files/Pods-example-tvOS/Pods-example-tvOS.release.xcconfig"; sourceTree = ""; }; - 2A1084C799FFD8DE81B1B05A /* libPods-example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 146009DDA787721A5A10CA2A /* Pods-example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example.debug.xcconfig"; path = "Target Support Files/Pods-example/Pods-example.debug.xcconfig"; sourceTree = ""; }; + 1A4DADB2ABBF6B72F39CB556 /* Pods-example-tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example-tvOS.release.xcconfig"; path = "Target Support Files/Pods-example-tvOS/Pods-example-tvOS.release.xcconfig"; sourceTree = ""; }; + 260CE5E4DEF527E7552C4F35 /* libPods-example-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 2D02E47B1E0B4A5D006451C7 /* example-tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example-tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 2D02E4901E0B4A5D006451C7 /* example-tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "example-tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - 4E829CA22C7590836F0BDC89 /* libPods-exampleTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-exampleTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 641D414720AFDDEC3EA337EC /* Pods-example-tvOSTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example-tvOSTests.debug.xcconfig"; path = "Target Support Files/Pods-example-tvOSTests/Pods-example-tvOSTests.debug.xcconfig"; sourceTree = ""; }; - 66247520A490D5DB33BCE5DA /* Pods-example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example.debug.xcconfig"; path = "Target Support Files/Pods-example/Pods-example.debug.xcconfig"; sourceTree = ""; }; - 81EBEEC75A4C26459B97B278 /* Pods-example-tvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example-tvOS.debug.xcconfig"; path = "Target Support Files/Pods-example-tvOS/Pods-example-tvOS.debug.xcconfig"; sourceTree = ""; }; - C67ED4B3CF798134625BA09B /* Pods-example-tvOSTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example-tvOSTests.release.xcconfig"; path = "Target Support Files/Pods-example-tvOSTests/Pods-example-tvOSTests.release.xcconfig"; sourceTree = ""; }; - C903D82C7E072C46D3D68139 /* Pods-exampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-exampleTests.debug.xcconfig"; path = "Target Support Files/Pods-exampleTests/Pods-exampleTests.debug.xcconfig"; sourceTree = ""; }; - D322FB106C50427D6E424A6B /* libPods-example-tvOSTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example-tvOSTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - D7EE427033EF6DE8A18138AF /* libPods-example-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 603B13E6FC2603CE77193B93 /* Pods-example-tvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example-tvOS.debug.xcconfig"; path = "Target Support Files/Pods-example-tvOS/Pods-example-tvOS.debug.xcconfig"; sourceTree = ""; }; + 652DBC55B51414CF52A2D6AB /* Pods-example-exampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example-exampleTests.debug.xcconfig"; path = "Target Support Files/Pods-example-exampleTests/Pods-example-exampleTests.debug.xcconfig"; sourceTree = ""; }; + 69BC6E119CCD13F6743DBE12 /* Pods-example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example.release.xcconfig"; path = "Target Support Files/Pods-example/Pods-example.release.xcconfig"; sourceTree = ""; }; + 77D6BE4E796D28131E3F4FCC /* Pods-example-tvOSTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example-tvOSTests.debug.xcconfig"; path = "Target Support Files/Pods-example-tvOSTests/Pods-example-tvOSTests.debug.xcconfig"; sourceTree = ""; }; + 7BFDCC05221B3C6B417CEEE3 /* libPods-example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = example/LaunchScreen.storyboard; sourceTree = ""; }; + 8AA817F492F434E8A1702ABF /* libPods-example-tvOSTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example-tvOSTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + AE5B9C9387CB013D7D50E938 /* Pods-example-exampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example-exampleTests.release.xcconfig"; path = "Target Support Files/Pods-example-exampleTests/Pods-example-exampleTests.release.xcconfig"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; ED2971642150620600B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.0.sdk/System/Library/Frameworks/JavaScriptCore.framework; sourceTree = DEVELOPER_DIR; }; + F9FCEFB452A0FAD1291B6DF4 /* Pods-example-tvOSTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example-tvOSTests.release.xcconfig"; path = "Target Support Files/Pods-example-tvOSTests/Pods-example-tvOSTests.release.xcconfig"; sourceTree = ""; }; + FFEB7F51062CCC33B9B3EB4A /* libPods-example-exampleTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example-exampleTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -74,7 +74,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 29C871C0741732DA80D4A28B /* libPods-exampleTests.a in Frameworks */, + 803B24D227A21AA7146EE469 /* libPods-example-exampleTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -82,7 +82,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - F272BF3BCAF6E9F45485CA12 /* libPods-example.a in Frameworks */, + 6A19D51D1CBD97B286B5C5A2 /* libPods-example.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -90,7 +90,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 871938999323E775E1A601AA /* libPods-example-tvOS.a in Frameworks */, + 392E28A89C8849C7123ADF53 /* libPods-example-tvOS.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -98,7 +98,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - B4AC6785CB63F44B1476B739 /* libPods-example-tvOSTests.a in Frameworks */, + 14E69C44E603A2DABF87E9C7 /* libPods-example-tvOSTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -130,37 +130,21 @@ 13B07FB01A68108700A75B9A /* AppDelegate.m */, 13B07FB51A68108700A75B9A /* Images.xcassets */, 13B07FB61A68108700A75B9A /* Info.plist */, - 13B07FB11A68108700A75B9A /* LaunchScreen.xib */, + 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */, 13B07FB71A68108700A75B9A /* main.m */, ); name = example; sourceTree = ""; }; - 13F9C63D4CAF75CDA2D4380F /* Pods */ = { - isa = PBXGroup; - children = ( - 66247520A490D5DB33BCE5DA /* Pods-example.debug.xcconfig */, - 11B0F93180450FD943C626DD /* Pods-example.release.xcconfig */, - 81EBEEC75A4C26459B97B278 /* Pods-example-tvOS.debug.xcconfig */, - 2848D7646158208FFC51959F /* Pods-example-tvOS.release.xcconfig */, - 641D414720AFDDEC3EA337EC /* Pods-example-tvOSTests.debug.xcconfig */, - C67ED4B3CF798134625BA09B /* Pods-example-tvOSTests.release.xcconfig */, - C903D82C7E072C46D3D68139 /* Pods-exampleTests.debug.xcconfig */, - 0685F86383145C5CB4CDA68E /* Pods-exampleTests.release.xcconfig */, - ); - name = Pods; - path = Pods; - sourceTree = ""; - }; 2D16E6871FA4F8E400B85C8A /* Frameworks */ = { isa = PBXGroup; children = ( ED297162215061F000B7C4FE /* JavaScriptCore.framework */, ED2971642150620600B7C4FE /* JavaScriptCore.framework */, - 2A1084C799FFD8DE81B1B05A /* libPods-example.a */, - D7EE427033EF6DE8A18138AF /* libPods-example-tvOS.a */, - D322FB106C50427D6E424A6B /* libPods-example-tvOSTests.a */, - 4E829CA22C7590836F0BDC89 /* libPods-exampleTests.a */, + 7BFDCC05221B3C6B417CEEE3 /* libPods-example.a */, + FFEB7F51062CCC33B9B3EB4A /* libPods-example-exampleTests.a */, + 260CE5E4DEF527E7552C4F35 /* libPods-example-tvOS.a */, + 8AA817F492F434E8A1702ABF /* libPods-example-tvOSTests.a */, ); name = Frameworks; sourceTree = ""; @@ -180,7 +164,7 @@ 00E356EF1AD99517003FC87E /* exampleTests */, 83CBBA001A601CBA00E9B192 /* Products */, 2D16E6871FA4F8E400B85C8A /* Frameworks */, - 13F9C63D4CAF75CDA2D4380F /* Pods */, + B5A56750867168C9AE8E3E33 /* Pods */, ); indentWidth = 2; sourceTree = ""; @@ -198,6 +182,22 @@ name = Products; sourceTree = ""; }; + B5A56750867168C9AE8E3E33 /* Pods */ = { + isa = PBXGroup; + children = ( + 146009DDA787721A5A10CA2A /* Pods-example.debug.xcconfig */, + 69BC6E119CCD13F6743DBE12 /* Pods-example.release.xcconfig */, + 652DBC55B51414CF52A2D6AB /* Pods-example-exampleTests.debug.xcconfig */, + AE5B9C9387CB013D7D50E938 /* Pods-example-exampleTests.release.xcconfig */, + 603B13E6FC2603CE77193B93 /* Pods-example-tvOS.debug.xcconfig */, + 1A4DADB2ABBF6B72F39CB556 /* Pods-example-tvOS.release.xcconfig */, + 77D6BE4E796D28131E3F4FCC /* Pods-example-tvOSTests.debug.xcconfig */, + F9FCEFB452A0FAD1291B6DF4 /* Pods-example-tvOSTests.release.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -205,10 +205,11 @@ isa = PBXNativeTarget; buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "exampleTests" */; buildPhases = ( - 21B1CB7811DC630A464E9976 /* [CP] Check Pods Manifest.lock */, + EC709FF54C99C02A3F981756 /* [CP] Check Pods Manifest.lock */, 00E356EA1AD99517003FC87E /* Sources */, 00E356EB1AD99517003FC87E /* Frameworks */, 00E356EC1AD99517003FC87E /* Resources */, + 8F0889A782D12B135E9B1E45 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -224,12 +225,13 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "example" */; buildPhases = ( - 7B3F63962D923B5AC2EBFF6A /* [CP] Check Pods Manifest.lock */, + 76F257DEF09210F3D6EB278E /* [CP] Check Pods Manifest.lock */, FD10A7F022414F080027D42C /* Start Packager */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, + FA858795E09158AE21118DBE /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -244,7 +246,7 @@ isa = PBXNativeTarget; buildConfigurationList = 2D02E4BA1E0B4A5E006451C7 /* Build configuration list for PBXNativeTarget "example-tvOS" */; buildPhases = ( - 71AFCCE0B3908DB29062379D /* [CP] Check Pods Manifest.lock */, + 19FCC5FA91CFB25FB6D5F933 /* [CP] Check Pods Manifest.lock */, FD10A7F122414F3F0027D42C /* Start Packager */, 2D02E4771E0B4A5D006451C7 /* Sources */, 2D02E4781E0B4A5D006451C7 /* Frameworks */, @@ -264,7 +266,7 @@ isa = PBXNativeTarget; buildConfigurationList = 2D02E4BB1E0B4A5E006451C7 /* Build configuration list for PBXNativeTarget "example-tvOSTests" */; buildPhases = ( - 6C1FB4F89E984853D9133472 /* [CP] Check Pods Manifest.lock */, + D7207153D50A8F40E8276DE5 /* [CP] Check Pods Manifest.lock */, 2D02E48C1E0B4A5D006451C7 /* Sources */, 2D02E48D1E0B4A5D006451C7 /* Frameworks */, 2D02E48E1E0B4A5D006451C7 /* Resources */, @@ -285,13 +287,15 @@ 83CBB9F71A601CBA00E9B192 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0940; - ORGANIZATIONNAME = Facebook; + LastUpgradeCheck = 1130; TargetAttributes = { 00E356ED1AD99517003FC87E = { CreatedOnToolsVersion = 6.2; TestTargetID = 13B07F861A680F5B00A75B9A; }; + 13B07F861A680F5B00A75B9A = { + LastSwiftMigration = 1120; + }; 2D02E47A1E0B4A5D006451C7 = { CreatedOnToolsVersion = 8.2.1; ProvisioningStyle = Automatic; @@ -305,7 +309,7 @@ }; buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "example" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, @@ -336,8 +340,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */, 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, - 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -373,7 +377,7 @@ shellPath = /bin/sh; shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh"; }; - 21B1CB7811DC630A464E9976 /* [CP] Check Pods Manifest.lock */ = { + 19FCC5FA91CFB25FB6D5F933 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -388,7 +392,7 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-exampleTests-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-example-tvOS-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -409,7 +413,7 @@ shellPath = /bin/sh; shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh"; }; - 6C1FB4F89E984853D9133472 /* [CP] Check Pods Manifest.lock */ = { + 76F257DEF09210F3D6EB278E /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -424,14 +428,32 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-example-tvOSTests-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-example-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 71AFCCE0B3908DB29062379D /* [CP] Check Pods Manifest.lock */ = { + 8F0889A782D12B135E9B1E45 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-example-exampleTests/Pods-example-exampleTests-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example-exampleTests/Pods-example-exampleTests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + D7207153D50A8F40E8276DE5 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -446,14 +468,14 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-example-tvOS-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-example-tvOSTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 7B3F63962D923B5AC2EBFF6A /* [CP] Check Pods Manifest.lock */ = { + EC709FF54C99C02A3F981756 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -468,13 +490,31 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-example-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-example-exampleTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + FA858795E09158AE21118DBE /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; FD10A7F022414F080027D42C /* Start Packager */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -565,22 +605,10 @@ }; /* End PBXTargetDependency section */ -/* Begin PBXVariantGroup section */ - 13B07FB11A68108700A75B9A /* LaunchScreen.xib */ = { - isa = PBXVariantGroup; - children = ( - 13B07FB21A68108700A75B9A /* Base */, - ); - name = LaunchScreen.xib; - path = example; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - /* Begin XCBuildConfiguration section */ 00E356F61AD99517003FC87E /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C903D82C7E072C46D3D68139 /* Pods-exampleTests.debug.xcconfig */; + baseConfigurationReference = 652DBC55B51414CF52A2D6AB /* Pods-example-exampleTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -588,7 +616,7 @@ "$(inherited)", ); INFOPLIST_FILE = exampleTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; OTHER_LDFLAGS = ( "-ObjC", @@ -603,12 +631,12 @@ }; 00E356F71AD99517003FC87E /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 0685F86383145C5CB4CDA68E /* Pods-exampleTests.release.xcconfig */; + baseConfigurationReference = AE5B9C9387CB013D7D50E938 /* Pods-example-exampleTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; COPY_PHASE_STRIP = NO; INFOPLIST_FILE = exampleTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; OTHER_LDFLAGS = ( "-ObjC", @@ -623,11 +651,12 @@ }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 66247520A490D5DB33BCE5DA /* Pods-example.debug.xcconfig */; + baseConfigurationReference = 146009DDA787721A5A10CA2A /* Pods-example.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; - DEAD_CODE_STRIPPING = NO; + ENABLE_BITCODE = NO; INFOPLIST_FILE = example/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_LDFLAGS = ( @@ -637,15 +666,18 @@ ); PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = example; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 11B0F93180450FD943C626DD /* Pods-example.release.xcconfig */; + baseConfigurationReference = 69BC6E119CCD13F6743DBE12 /* Pods-example.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; INFOPLIST_FILE = example/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -656,13 +688,14 @@ ); PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = example; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; 2D02E4971E0B4A5E006451C7 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 81EBEEC75A4C26459B97B278 /* Pods-example-tvOS.debug.xcconfig */; + baseConfigurationReference = 603B13E6FC2603CE77193B93 /* Pods-example-tvOS.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; @@ -680,17 +713,17 @@ "-ObjC", "-lc++", ); - PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.example-tvOS"; + PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.example-tvOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 9.2; + TVOS_DEPLOYMENT_TARGET = 10.0; }; name = Debug; }; 2D02E4981E0B4A5E006451C7 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 2848D7646158208FFC51959F /* Pods-example-tvOS.release.xcconfig */; + baseConfigurationReference = 1A4DADB2ABBF6B72F39CB556 /* Pods-example-tvOS.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; @@ -708,17 +741,17 @@ "-ObjC", "-lc++", ); - PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.example-tvOS"; + PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.example-tvOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 9.2; + TVOS_DEPLOYMENT_TARGET = 10.0; }; name = Release; }; 2D02E4991E0B4A5E006451C7 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 641D414720AFDDEC3EA337EC /* Pods-example-tvOSTests.debug.xcconfig */; + baseConfigurationReference = 77D6BE4E796D28131E3F4FCC /* Pods-example-tvOSTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NONNULL = YES; @@ -735,7 +768,7 @@ "-ObjC", "-lc++", ); - PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.example-tvOSTests"; + PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.example-tvOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example-tvOS.app/example-tvOS"; @@ -745,7 +778,7 @@ }; 2D02E49A1E0B4A5E006451C7 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C67ED4B3CF798134625BA09B /* Pods-example-tvOSTests.release.xcconfig */; + baseConfigurationReference = F9FCEFB452A0FAD1291B6DF4 /* Pods-example-tvOSTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NONNULL = YES; @@ -762,7 +795,7 @@ "-ObjC", "-lc++", ); - PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.example-tvOSTests"; + PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.example-tvOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example-tvOS.app/example-tvOS"; @@ -774,6 +807,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; @@ -816,7 +850,13 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)"; + LIBRARY_SEARCH_PATHS = ( + "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", + "\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"", + "\"$(inherited)\"", + ); MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -827,6 +867,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; @@ -862,7 +903,13 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)"; + LIBRARY_SEARCH_PATHS = ( + "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", + "\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"", + "\"$(inherited)\"", + ); MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/example/ios/example.xcodeproj/xcshareddata/xcschemes/example-tvOS.xcscheme b/example/ios/example.xcodeproj/xcshareddata/xcschemes/example-tvOS.xcscheme index dde7377e1..9570230db 100644 --- a/example/ios/example.xcodeproj/xcshareddata/xcschemes/example-tvOS.xcscheme +++ b/example/ios/example.xcodeproj/xcshareddata/xcschemes/example-tvOS.xcscheme @@ -1,25 +1,11 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #import diff --git a/example/ios/example/AppDelegate.m b/example/ios/example/AppDelegate.m index 2e1908cb1..b6e642862 100644 --- a/example/ios/example/AppDelegate.m +++ b/example/ios/example/AppDelegate.m @@ -1,20 +1,36 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - #import "AppDelegate.h" #import #import #import +#ifdef FB_SONARKIT_ENABLED +#import +#import +#import +#import +#import +#import + +static void InitializeFlipper(UIApplication *application) { + FlipperClient *client = [FlipperClient sharedClient]; + SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults]; + [client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]]; + [client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]]; + [client addPlugin:[FlipperKitReactPlugin new]]; + [client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]]; + [client start]; +} +#endif + @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { +#ifdef FB_SONARKIT_ENABLED + InitializeFlipper(application); +#endif + RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions]; RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"example" diff --git a/example/ios/example/Base.lproj/LaunchScreen.xib b/example/ios/example/Base.lproj/LaunchScreen.xib deleted file mode 100644 index 9e04807a8..000000000 --- a/example/ios/example/Base.lproj/LaunchScreen.xib +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/example/ios/example/LaunchScreen.storyboard b/example/ios/example/LaunchScreen.storyboard new file mode 100644 index 000000000..e2b4b0638 --- /dev/null +++ b/example/ios/example/LaunchScreen.storyboard @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/example/main.m b/example/ios/example/main.m index c316cf816..b1df44b95 100644 --- a/example/ios/example/main.m +++ b/example/ios/example/main.m @@ -1,10 +1,3 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - #import #import "AppDelegate.h" diff --git a/example/ios/exampleTests/exampleTests.m b/example/ios/exampleTests/exampleTests.m index 96b3481b7..9809b80c3 100644 --- a/example/ios/exampleTests/exampleTests.m +++ b/example/ios/exampleTests/exampleTests.m @@ -1,10 +1,3 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - #import #import @@ -59,7 +52,7 @@ - (void)testRendersWelcomeScreen return NO; }]; } - + #ifdef DEBUG RCTSetLogFunction(RCTDefaultLogFunction); #endif diff --git a/example/package.json b/example/package.json index 4c2c070ac..fa2ca03b5 100644 --- a/example/package.json +++ b/example/package.json @@ -1,26 +1,29 @@ { "name": "example", - "version": "0.61.5", + "version": "0.0.1", "private": true, "scripts": { "android": "react-native run-android", "ios": "react-native run-ios", "start": "react-native start", - "test": "jest" + "test": "jest", + "lint": "eslint ." }, "dependencies": { - "react": "16.9.0", - "react-native": "0.61.5", + "react": "16.13.1", + "react-native": "0.63.2", "react-native-linear-gradient": "2.5.6", - "react-native-snap-carousel": "file:../" + "react-native-snap-carousel": "../react-native-snap-carousel-v4.0.0-beta.5.tgz" }, "devDependencies": { - "@babel/core": "^7.6.2", - "@babel/runtime": "^7.6.2", - "babel-jest": "^24.9.0", - "jest": "^24.9.0", - "metro-react-native-babel-preset": "^0.56.0", - "react-test-renderer": "16.9.0" + "@babel/core": "^7.8.4", + "@babel/runtime": "^7.8.4", + "@react-native-community/eslint-config": "^1.1.0", + "babel-jest": "^25.1.0", + "eslint": "^6.5.1", + "jest": "^25.1.0", + "metro-react-native-babel-preset": "^0.59.0", + "react-test-renderer": "16.13.1" }, "jest": { "preset": "react-native" From 5f8ad4a3879a5f8e6c32fad3a5f2276671f29513 Mon Sep 17 00:00:00 2001 From: Thibault Malbranche Date: Tue, 18 Aug 2020 13:34:54 +0200 Subject: [PATCH 27/34] wip --- package.json | 4 ++-- src/carousel/Carousel.tsx | 21 +++++++-------------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index f0e27182c..17f6e2274 100644 --- a/package.json +++ b/package.json @@ -53,8 +53,8 @@ }, "devDependencies": { "@react-native-community/bob": "0.16.2", - "@types/react": "16.9.44", - "@types/react-native": "0.63.4", + "@types/react": "^16.9.46", + "@types/react-native": "^0.63.4", "@typescript-eslint/eslint-plugin": "3.8.0", "@typescript-eslint/parser": "3.8.0", "babel-eslint": "10.1.0", diff --git a/src/carousel/Carousel.tsx b/src/carousel/Carousel.tsx index a043f2812..5c7bbba2a 100644 --- a/src/carousel/Carousel.tsx +++ b/src/carousel/Carousel.tsx @@ -32,13 +32,6 @@ import type { CarouselProps, CarouselState } from './types'; const IS_ANDROID = Platform.OS === 'android'; -// Native driver for scroll events -// See: https://facebook.github.io/react-native/blog/2017/02/14/using-native-driver-for-animated.html -const AnimatedFlatList = FlatList ? - Animated.createAnimatedComponent(FlatList) : - null; -const AnimatedScrollView = Animated.createAnimatedComponent(ScrollView); - // React Native automatically handles RTL layouts; unfortunately, it's buggy with horizontal ScrollView // See https://github.com/facebook/react-native/issues/11960 // NOTE: the following variable is not declared in the constructor @@ -72,7 +65,7 @@ export class Carousel extends React.Component< slideStyle: {}, shouldOptimizeUpdates: true, useExperimentalSnap: false, - useScrollView: !AnimatedFlatList, + useScrollView: !Animated.FlatList, vertical: false }; @@ -399,10 +392,10 @@ export class Carousel extends React.Component< // TODO: a workaround might be to pass the custom animated styles directly to it return IS_ANDROID ? useScrollView || - !AnimatedFlatList || + !Animated.FlatList || this._shouldUseStackLayout() || this._shouldUseTinderLayout() : - useScrollView || !AnimatedFlatList; + useScrollView || !Animated.FlatList; } _needsRTLAdaptations () { @@ -1319,7 +1312,7 @@ export class Carousel extends React.Component< return { ...specificProps, ...snapProps, - ref: (c: unknown) => { + ref: (c: any) => { this._carouselRef = c as FlatList | ScrollView; }, contentContainerStyle: contentContainerStyle, @@ -1349,16 +1342,16 @@ export class Carousel extends React.Component< }; const ScrollViewComponent = - typeof useScrollView === 'function' ? useScrollView : AnimatedScrollView; + typeof useScrollView === 'function' ? useScrollView : Animated.ScrollView; - return this._needsScrollView() || !AnimatedFlatList ? ( + return this._needsScrollView() || !Animated.FlatList ? ( {this._getCustomData().map((item, index) => { return this._renderItem({ item, index }); })} ) : ( - + ); } } From daa8e6e1950aba5ae49061878ed26ffa855a3307 Mon Sep 17 00:00:00 2001 From: Thibault Malbranche Date: Tue, 18 Aug 2020 14:17:44 +0200 Subject: [PATCH 28/34] diverse fixes --- src/carousel/Carousel.tsx | 7 +++++-- src/index.ts | 3 ++- src/pagination/Pagination.tsx | 10 +++++----- src/pagination/PaginationDot.tsx | 12 +++++------ src/parallaximage/ParallaxImage.tsx | 31 ++++++++++++++++++----------- 5 files changed, 37 insertions(+), 26 deletions(-) diff --git a/src/carousel/Carousel.tsx b/src/carousel/Carousel.tsx index 5c7bbba2a..a40e31d99 100644 --- a/src/carousel/Carousel.tsx +++ b/src/carousel/Carousel.tsx @@ -631,7 +631,7 @@ export class Carousel extends React.Component< ); } - _getItemLayout (_: unknown, index: number) { + _getItemLayout (_: TData[], index: number) { const itemMainDimension = this._getItemMainDimension(); return { index, @@ -660,7 +660,7 @@ export class Carousel extends React.Component< ); } - _getKeyExtractor (_: unknown, index: number) { + _getKeyExtractor (_: TData, index: number) { return this._needsScrollView() ? `scrollview-item-${index}` : `flatlist-item-${index}`; @@ -1312,6 +1312,7 @@ export class Carousel extends React.Component< return { ...specificProps, ...snapProps, + // eslint-disable-next-line @typescript-eslint/no-explicit-any ref: (c: any) => { this._carouselRef = c as FlatList | ScrollView; }, @@ -1351,6 +1352,8 @@ export class Carousel extends React.Component< })} ) : ( + // @ts-expect-error Seems complicated to make TS 100% happy, while sharing that many things between + // flatlist && scrollview implementation. I'll prob try to rewrite parts of the logic to overcome that. ); } diff --git a/src/index.ts b/src/index.ts index 1535ac5bd..3e1a85498 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import Carousel from './carousel/Carousel'; import Pagination from './pagination/Pagination'; -import ParallaxImage from './parallaximage/ParallaxImage'; +import ParallaxImage, { ParallaxImageStatus } from './parallaximage/ParallaxImage'; import { getInputRangeFromIndexes } from './utils/animations'; export { @@ -8,5 +8,6 @@ export { Carousel, Pagination, ParallaxImage, + ParallaxImageStatus, getInputRangeFromIndexes }; diff --git a/src/pagination/Pagination.tsx b/src/pagination/Pagination.tsx index 97184a54b..958d86844 100644 --- a/src/pagination/Pagination.tsx +++ b/src/pagination/Pagination.tsx @@ -13,11 +13,11 @@ import type Carousel from 'src/carousel/Carousel'; const IS_IOS = Platform.OS === 'ios'; const IS_RTL = I18nManager.isRTL; -type PaginationProps = { +type PaginationProps = { activeDotIndex: number; dotsLength: number; activeOpacity?: number; - carouselRef?: Carousel | null; + carouselRef?: Carousel | null; containerStyle?: StyleProp; dotColor?: string; dotContainerStyle?: StyleProp; @@ -31,7 +31,7 @@ type PaginationProps = { renderDots: ( activeIndex: number, length: number, - context: Pagination + context: Pagination ) => ReactElement; tappableDots: boolean; vertical: boolean; @@ -42,7 +42,7 @@ type PaginationProps = { delayPressInDot: number; }; -export default class Pagination extends PureComponent { +export default class Pagination extends PureComponent> { static defaultProps = { inactiveDotOpacity: 0.5, inactiveDotScale: 0.5, @@ -54,7 +54,7 @@ export default class Pagination extends PureComponent { delayPressInDot: 0 }; - constructor (props: PaginationProps) { + constructor (props: PaginationProps) { super(props); // Warnings diff --git a/src/pagination/PaginationDot.tsx b/src/pagination/PaginationDot.tsx index 07289c497..8ea5c47bd 100644 --- a/src/pagination/PaginationDot.tsx +++ b/src/pagination/PaginationDot.tsx @@ -9,7 +9,7 @@ import { import styles from './Pagination.style'; import type Carousel from 'src/carousel/Carousel'; -type PaginationDotProps = { +type PaginationDotProps = { inactiveOpacity: number; inactiveScale: number; active?: boolean; @@ -17,7 +17,7 @@ type PaginationDotProps = { animatedDuration?: number; animatedFriction?: number; animatedTension?: number; - carouselRef?: Carousel | RefObject> | null; + carouselRef?: Carousel | RefObject> | null; color?: string; containerStyle?: StyleProp; delayPressInDot?: number; @@ -34,11 +34,11 @@ type PaginationDotState = { animTransform: Animated.Value; }; -export default class PaginationDot extends PureComponent< - PaginationDotProps, +export default class PaginationDot extends PureComponent< + PaginationDotProps, PaginationDotState > { - constructor (props: PaginationDotProps) { + constructor (props: PaginationDotProps) { super(props); this.state = { animColor: new Animated.Value(0), @@ -53,7 +53,7 @@ export default class PaginationDot extends PureComponent< } } - componentDidUpdate (prevProps: PaginationDotProps) { + componentDidUpdate (prevProps: PaginationDotProps) { if (prevProps.active !== this.props.active) { this._animate(this.props.active ? 1 : 0); } diff --git a/src/parallaximage/ParallaxImage.tsx b/src/parallaximage/ParallaxImage.tsx index 6598f7088..e7fd10ead 100644 --- a/src/parallaximage/ParallaxImage.tsx +++ b/src/parallaximage/ParallaxImage.tsx @@ -17,8 +17,8 @@ import { import styles from './ParallaxImage.style'; import type Carousel from 'src/carousel/Carousel'; -type ParallaxImageProps = { - carouselRef: Carousel | null; // passed from +type ParallaxImageProps = { + carouselRef: Carousel | null; // passed from itemHeight: number; // passed from itemWidth: number; // passed from scrollPosition: Animated.Value; // passed from @@ -37,16 +37,23 @@ type ParallaxImageProps = { AnimatedImageComponent: typeof Animated.Image; } & ImageProps; +export enum ParallaxImageStatus { + 'LOADING' = 1, + 'LOADED' = 2, + 'TRANSITION_FINISHED' = 3, + 'ERROR' = 4 +} + type ParallaxImageState = { offset: number; width: number; height: number; - status: 1 | 2 | 3 | 4; + status: ParallaxImageStatus; animOpacity: Animated.Value; }; -export default class ParallaxImage extends Component< - ParallaxImageProps, +export default class ParallaxImage extends Component< + ParallaxImageProps, ParallaxImageState > { static defaultProps = { @@ -61,13 +68,13 @@ export default class ParallaxImage extends Component< _container?: View | null; _mounted?: boolean; - constructor (props: ParallaxImageProps) { + constructor (props: ParallaxImageProps) { super(props); this.state = { offset: 0, width: 0, height: 0, - status: 1, // 1 -> loading; 2 -> loaded // 3 -> transition finished; 4 -> error + status: ParallaxImageStatus.LOADING, animOpacity: new Animated.Value(0) }; this._onLoad = this._onLoad.bind(this); @@ -140,7 +147,7 @@ export default class ParallaxImage extends Component< return; } - this.setState({ status: 2 }); + this.setState({ status: ParallaxImageStatus.LOADED }); if (onLoad) { onLoad(event); @@ -153,7 +160,7 @@ export default class ParallaxImage extends Component< isInteraction: false, useNativeDriver: true }).start(() => { - this.setState({ status: 3 }); + this.setState({ status: ParallaxImageStatus.TRANSITION_FINISHED }); }); } @@ -161,7 +168,7 @@ export default class ParallaxImage extends Component< _onError (event: NativeSyntheticEvent) { const { onError } = this.props; - this.setState({ status: 4 }); + this.setState({ status: ParallaxImageStatus.ERROR }); if (onError) { onError(event); @@ -218,7 +225,7 @@ export default class ParallaxImage extends Component< {...other} style={[styles.image, style, requiredStyles, dynamicStyles]} onLoad={this._onLoad} - onError={status !== 3 ? this._onError : undefined} // prevent infinite-loop bug + onError={status !== ParallaxImageStatus.TRANSITION_FINISHED ? this._onError : undefined} // prevent infinite-loop bug /> ); } @@ -227,7 +234,7 @@ export default class ParallaxImage extends Component< const { status } = this.state; const { showSpinner, spinnerColor } = this.props; - return status === 1 && showSpinner ? ( + return status === ParallaxImageStatus.LOADING && showSpinner ? ( Date: Tue, 18 Aug 2020 21:02:25 +0200 Subject: [PATCH 29/34] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 17f6e2274..a55a77f1c 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Swiper/carousel component for React Native with previews, multiple layouts, parallax images, performant handling of huge numbers of items, and more. Compatible with Android & iOS.", "main": "lib/commonjs/index", "module": "lib/module/index", - "types": "lib/typescript/src/index.d.ts", + "types": "lib/typescript/index.d.ts", "react-native": "src/index", "source": "src/index", "repository": { From 93fb6a00a8300e715ff708809deaf9c57f430cd5 Mon Sep 17 00:00:00 2001 From: Thibault Malbranche Date: Fri, 21 Aug 2020 16:48:31 +0200 Subject: [PATCH 30/34] wip --- src/carousel/types.ts | 6 +++--- src/utils/animations.ts | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/carousel/types.ts b/src/carousel/types.ts index 28ac2b9ce..1647eda00 100644 --- a/src/carousel/types.ts +++ b/src/carousel/types.ts @@ -40,15 +40,15 @@ type CarouselBaseProps = { inactiveSlideScale: number; inactiveSlideShift: number; layout: 'default' | 'stack' | 'tinder'; - layoutCardOffset: number; + layoutCardOffset?: number; loop: boolean; loopClonesPerSide: number; scrollEnabled: boolean; - scrollInterpolator: (index: number, props: CarouselBaseProps) => { + scrollInterpolator?: (index: number, props: CarouselBaseProps) => { inputRange: number[]; outputRange: number[]; }; - slideInterpolatedStyle: ( + slideInterpolatedStyle?: ( index: number, animatedValue: Animated.AnimatedInterpolation, props: CarouselBaseProps diff --git a/src/utils/animations.ts b/src/utils/animations.ts index 20b6eacbd..bf1c50f71 100644 --- a/src/utils/animations.ts +++ b/src/utils/animations.ts @@ -144,7 +144,7 @@ export function stackAnimatedStyles ( index: number, animatedValue: Animated.AnimatedInterpolation, carouselProps: CarouselProps, - cardOffset: number + cardOffset?: number ) { const sizeRef = carouselProps.vertical ? carouselProps.itemHeight : @@ -154,13 +154,13 @@ export function stackAnimatedStyles ( const card1Scale = 0.9; const card2Scale = 0.8; - cardOffset = !cardOffset && cardOffset !== 0 ? 18 : cardOffset; + const newCardOffset = cardOffset ?? 18; const getTranslateFromScale = (cardIndex: number, scale: number) => { const centerFactor = (1 / scale) * cardIndex; const centeredPosition = -Math.round(sizeRef * centerFactor); const edgeAlignment = Math.round((sizeRef - sizeRef * scale) / 2); - const offset = Math.round((cardOffset * Math.abs(cardIndex)) / scale); + const offset = Math.round((newCardOffset * Math.abs(cardIndex)) / scale); return IS_ANDROID ? centeredPosition - edgeAlignment - offset : @@ -252,7 +252,7 @@ export function tinderAnimatedStyles ( index: number, animatedValue: Animated.AnimatedInterpolation, carouselProps: CarouselProps, - cardOffset: number + cardOffset?: number ) { const sizeRef = carouselProps.vertical ? carouselProps.itemHeight : @@ -270,7 +270,7 @@ export function tinderAnimatedStyles ( const peekingCardsOpacity = IS_ANDROID ? 0.92 : 1; - cardOffset = !cardOffset && cardOffset !== 0 ? 9 : cardOffset; + const newCardOffset = cardOffset ?? 9; const getMainTranslateFromScale = (cardIndex: number, scale: number) => { const centerFactor = (1 / scale) * cardIndex; @@ -278,7 +278,7 @@ export function tinderAnimatedStyles ( }; const getSecondaryTranslateFromScale = (cardIndex: number, scale: number) => { - return Math.round((cardOffset * Math.abs(cardIndex)) / scale); + return Math.round((newCardOffset * Math.abs(cardIndex)) / scale); }; return IS_ANDROID ? From 0aee79efef27c2658df978a4876e3e5cdb9cefaa Mon Sep 17 00:00:00 2001 From: Thibault Malbranche Date: Fri, 21 Aug 2020 17:58:44 +0200 Subject: [PATCH 31/34] Update Pagination.tsx --- src/pagination/Pagination.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pagination/Pagination.tsx b/src/pagination/Pagination.tsx index 958d86844..2447c48bb 100644 --- a/src/pagination/Pagination.tsx +++ b/src/pagination/Pagination.tsx @@ -27,8 +27,8 @@ type PaginationProps = { inactiveDotElement?: ReactElement; inactiveDotOpacity: number; inactiveDotScale: number; - inactiveDotStyle: StyleProp; - renderDots: ( + inactiveDotStyle?: StyleProp; + renderDots?: ( activeIndex: number, length: number, context: Pagination From 12a2ea56e2462d01d0e144655521c5e7efb47c14 Mon Sep 17 00:00:00 2001 From: Thibault Malbranche Date: Mon, 24 Aug 2020 16:06:02 +0200 Subject: [PATCH 32/34] Finally it should be good --- src/carousel/Carousel.tsx | 96 ++++++++++++----------------- src/carousel/types.ts | 45 +++++++------- src/parallaximage/ParallaxImage.tsx | 63 +++++++++---------- 3 files changed, 92 insertions(+), 112 deletions(-) diff --git a/src/carousel/Carousel.tsx b/src/carousel/Carousel.tsx index a40e31d99..e9daf5b1f 100644 --- a/src/carousel/Carousel.tsx +++ b/src/carousel/Carousel.tsx @@ -65,8 +65,7 @@ export class Carousel extends React.Component< slideStyle: {}, shouldOptimizeUpdates: true, useExperimentalSnap: false, - useScrollView: !Animated.FlatList, - vertical: false + useScrollView: !Animated.FlatList }; _activeItem: number; @@ -191,11 +190,7 @@ export class Carousel extends React.Component< const { interpolators } = this.state; const { firstItem, - itemHeight, - itemWidth, - scrollEnabled, - sliderHeight, - sliderWidth + scrollEnabled } = this.props; const itemsLength = this._getCustomDataLength(this.props); @@ -209,13 +204,16 @@ export class Carousel extends React.Component< this._activeItem : nextFirstItem; - const hasNewSliderWidth = - sliderWidth && sliderWidth !== prevProps.sliderWidth; - const hasNewSliderHeight = - sliderHeight && sliderHeight !== prevProps.sliderHeight; - const hasNewItemWidth = itemWidth && itemWidth !== prevProps.itemWidth; - const hasNewItemHeight = itemHeight && itemHeight !== prevProps.itemHeight; - const hasNewScrollEnabled = scrollEnabled !== prevProps.scrollEnabled; + const hasNewSize = this.props.vertical !== prevProps.vertical || + ( + this.props.vertical && prevProps.vertical && ( + prevProps.itemHeight !== this.props.itemHeight || prevProps.sliderHeight !== this.props.sliderHeight + ) + ) || ( + !this.props.vertical && !prevProps.vertical && ( + prevProps.itemWidth !== this.props.itemWidth || prevProps.sliderWidth !== this.props.sliderWidth + ) + ); // Prevent issues with dynamically removed items if (nextActiveItem > itemsLength - 1) { @@ -223,16 +221,13 @@ export class Carousel extends React.Component< } // Handle changing scrollEnabled independent of user -> carousel interaction - if (hasNewScrollEnabled) { + if (scrollEnabled !== prevProps.scrollEnabled) { this._setScrollEnabled(scrollEnabled); } if ( interpolators.length !== itemsLength || - hasNewSliderWidth || - hasNewSliderHeight || - hasNewItemWidth || - hasNewItemHeight + hasNewSize ) { this._activeItem = nextActiveItem; this._previousItemsLength = itemsLength; @@ -246,12 +241,7 @@ export class Carousel extends React.Component< this._hackActiveSlideAnimation(nextActiveItem); } - if ( - hasNewSliderWidth || - hasNewSliderHeight || - hasNewItemWidth || - hasNewItemHeight - ) { + if (hasNewSize) { this._snapToItem(nextActiveItem, false, false, true); } } else if ( @@ -1137,15 +1127,8 @@ export class Carousel extends React.Component< _renderItem ({ item, index }: { item: TData; index: number }) { const { interpolators } = this.state; const { - hasParallaxImages, - itemWidth, - itemHeight, keyExtractor, - renderItem, - sliderHeight, - sliderWidth, - slideStyle, - vertical + slideStyle } = this.props; const animatedValue = interpolators && interpolators[index]; @@ -1160,21 +1143,9 @@ export class Carousel extends React.Component< {}; const dataIndex = this._getDataIndex(index); - const parallaxProps = hasParallaxImages ? - { - scrollPosition: this._scrollPos, - carouselRef: this._carouselRef, - vertical, - sliderWidth, - sliderHeight, - itemWidth, - itemHeight - } : - undefined; - - const mainDimension = vertical ? - { height: itemHeight } : - { width: itemWidth }; + const mainDimension = this.props.vertical ? + { height: this.props.itemHeight } : + { width: this.props.itemWidth }; const specificProps = this._needsScrollView() ? { key: keyExtractor ? @@ -1189,7 +1160,19 @@ export class Carousel extends React.Component< pointerEvents='box-none' {...specificProps} > - {renderItem({ item, index, dataIndex }, parallaxProps)} + {this.props.vertical ? this.props.renderItem({ item, index, dataIndex }, { + scrollPosition: this._scrollPos, + carouselRef: this._carouselRef, + vertical: this.props.vertical, + sliderHeight: this.props.sliderHeight, + itemHeight: this.props.itemHeight + }) : this.props.renderItem({ item, index, dataIndex }, { + scrollPosition: this._scrollPos, + carouselRef: this._carouselRef, + vertical: !!this.props.vertical, + sliderWidth: this.props.sliderWidth, + itemWidth: this.props.itemWidth + })} ); } @@ -1245,27 +1228,24 @@ export class Carousel extends React.Component< firstItem, getItemLayout, keyExtractor, - sliderWidth, - sliderHeight, style, - useExperimentalSnap, - vertical + useExperimentalSnap } = this.props; const containerStyle = [ // { overflow: 'hidden' }, containerCustomStyle || style || {}, hideCarousel ? { opacity: 0 } : {}, - vertical ? - { height: sliderHeight, flexDirection: 'column' as const } : // LTR hack; see https://github.com/facebook/react-native/issues/11960 + this.props.vertical ? + { height: this.props.sliderHeight, flexDirection: 'column' as const } : // LTR hack; see https://github.com/facebook/react-native/issues/11960 // and https://github.com/facebook/react-native/issues/13100#issuecomment-328986423 { - width: sliderWidth, + width: this.props.sliderWidth, flexDirection: this._needsRTLAdaptations() ? 'row-reverse' as const : 'row' as const } ]; - const innerMarginStyle = vertical ? + const innerMarginStyle = this.props.vertical ? { paddingTop: this._getContainerInnerMargin(), paddingBottom: this._getContainerInnerMargin(true) @@ -1318,7 +1298,7 @@ export class Carousel extends React.Component< }, contentContainerStyle: contentContainerStyle, data: this._getCustomData(), - horizontal: !vertical, + horizontal: !this.props.vertical, scrollEventThrottle: 1, style: containerStyle, onLayout: this._onLayout, diff --git a/src/carousel/types.ts b/src/carousel/types.ts index 1647eda00..46c1e5ee8 100644 --- a/src/carousel/types.ts +++ b/src/carousel/types.ts @@ -12,18 +12,6 @@ import type { ReactNode } from 'react'; type CarouselBaseProps = { data: TData[]; - renderItem: ( - baseData: { index: number; dataIndex: number; item: TData }, - parallaxData?: { - scrollPosition: Animated.Value | undefined, - carouselRef: ScrollView | FlatList | null, - vertical?: boolean, - sliderWidth?: number, - sliderHeight?: number, - itemWidth?: number, - itemHeight?: number - } - ) => ReactNode; activeSlideAlignment: 'center' | 'end' | 'start'; activeSlideOffset: number; apparitionDelay: number; @@ -35,7 +23,6 @@ type CarouselBaseProps = { contentContainerCustomStyle: StyleProp; enableSnap: boolean; firstItem: number; - hasParallaxImages: boolean; inactiveSlideOpacity: number; inactiveSlideScale: number; inactiveSlideShift: number; @@ -74,24 +61,40 @@ type InheritedPropsFromFlatlist = Pick< | 'style' >; -type VerticalCarouselProps = { +type VerticalCarouselProps = { vertical: true; - itemWidth?: number; itemHeight: number; - sliderWidth?: number; sliderHeight: number; + renderItem: ( + baseData: { index: number; dataIndex: number; item: TData }, + parallaxData: { + scrollPosition: Animated.Value | undefined, + carouselRef: ScrollView | FlatList | null, + vertical: true, + itemHeight: number, + sliderHeight: number, + } + ) => ReactNode; }; -type HorizontalCarouselProps = { - vertical?: false; +type HorizontalCarouselProps = { + vertical: false | undefined; itemWidth: number; - itemHeight?: number; sliderWidth: number; - sliderHeight?: number; + renderItem: ( + baseData: { index: number; dataIndex: number; item: TData }, + parallaxData: { + scrollPosition: Animated.Value | undefined, + carouselRef: ScrollView | FlatList | null, + vertical: false, + itemWidth: number, + sliderWidth: number, + } + ) => ReactNode; }; export type CarouselProps = CarouselBaseProps & - (VerticalCarouselProps | HorizontalCarouselProps) & + (HorizontalCarouselProps | VerticalCarouselProps) & InheritedPropsFromFlatlist; export type CarouselState = { diff --git a/src/parallaximage/ParallaxImage.tsx b/src/parallaximage/ParallaxImage.tsx index e7fd10ead..bac86b263 100644 --- a/src/parallaximage/ParallaxImage.tsx +++ b/src/parallaximage/ParallaxImage.tsx @@ -15,18 +15,23 @@ import { ImageErrorEventData } from 'react-native'; import styles from './ParallaxImage.style'; -import type Carousel from 'src/carousel/Carousel'; -type ParallaxImageProps = { - carouselRef: Carousel | null; // passed from - itemHeight: number; // passed from - itemWidth: number; // passed from - scrollPosition: Animated.Value; // passed from - sliderHeight: number; // passed from - sliderWidth: number; // passed from - vertical: boolean; // passed from +type VerticalProps = { + vertical: true; + sliderHeight: number; // passed from + itemHeight: number; // passed from +} +type HorizontalProps = { + vertical: false; + sliderWidth: number; // passed from + itemWidth: number; // passed from +} + +type ParallaxImageProps = { + carouselRef: Parameters[0]; // passed from + scrollPosition: Animated.Value | undefined; // passed from containerStyle: StyleProp; - dimensions: { + dimensions?: { width: number; height: number; }; @@ -35,7 +40,7 @@ type ParallaxImageProps = { showSpinner: boolean; spinnerColor: string; AnimatedImageComponent: typeof Animated.Image; -} & ImageProps; +} & ImageProps & (VerticalProps | HorizontalProps); export enum ParallaxImageStatus { 'LOADING' = 1, @@ -52,8 +57,8 @@ type ParallaxImageState = { animOpacity: Animated.Value; }; -export default class ParallaxImage extends Component< - ParallaxImageProps, +export default class ParallaxImage extends Component< + ParallaxImageProps, ParallaxImageState > { static defaultProps = { @@ -68,7 +73,7 @@ export default class ParallaxImage extends Component< _container?: View | null; _mounted?: boolean; - constructor (props: ParallaxImageProps) { + constructor (props: ParallaxImageProps) { super(props); this.state = { offset: 0, @@ -102,12 +107,7 @@ export default class ParallaxImage extends Component< if (this._container) { const { dimensions, - vertical, - carouselRef, - sliderWidth, - sliderHeight, - itemWidth, - itemHeight + carouselRef } = this.props; const nodeHandle = findNodeHandle(carouselRef); @@ -116,9 +116,9 @@ export default class ParallaxImage extends Component< this._container.measureLayout( nodeHandle, (x, y, width, height) => { - const offset = vertical ? - y - (sliderHeight - itemHeight) / 2 : - x - (sliderWidth - itemWidth) / 2; + const offset = this.props.vertical ? + y - (this.props.sliderHeight - this.props.itemHeight) / 2 : + x - (this.props.sliderWidth - this.props.itemWidth) / 2; this.setState({ offset: offset, @@ -182,35 +182,32 @@ export default class ParallaxImage extends Component< // False positive :( other doesn't have the dimension key // eslint-disable-next-line @typescript-eslint/no-unused-vars dimensions, - vertical, - sliderWidth, - sliderHeight, parallaxFactor, style, AnimatedImageComponent, ...other } = this.props; - const parallaxPadding = (vertical ? height : width) * parallaxFactor; + const parallaxPadding = (this.props.vertical ? height : width) * parallaxFactor; const requiredStyles = { position: 'relative' as const }; const dynamicStyles = { - width: vertical ? width : width + parallaxPadding * 2, - height: vertical ? height + parallaxPadding * 2 : height, + width: this.props.vertical ? width : width + parallaxPadding * 2, + height: this.props.vertical ? height + parallaxPadding * 2 : height, opacity: animOpacity, transform: scrollPosition ? [ { - translateX: !vertical ? + translateX: !this.props.vertical ? scrollPosition.interpolate({ - inputRange: [offset - sliderWidth, offset + sliderWidth], + inputRange: [offset - this.props.sliderWidth, offset + this.props.sliderWidth], outputRange: [-parallaxPadding, parallaxPadding], extrapolate: 'clamp' }) : 0 }, { - translateY: vertical ? + translateY: this.props.vertical ? scrollPosition.interpolate({ - inputRange: [offset - sliderHeight, offset + sliderHeight], + inputRange: [offset - this.props.sliderHeight, offset + this.props.sliderHeight], outputRange: [-parallaxPadding, parallaxPadding], extrapolate: 'clamp' }) : From 86611dc16c0d2ff5384f6d6cfff3b87b5b62ffe5 Mon Sep 17 00:00:00 2001 From: bd-arc Date: Thu, 27 Aug 2020 17:57:03 +0200 Subject: [PATCH 33/34] chore(eslint): update config --- .eslintrc | 79 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 58 insertions(+), 21 deletions(-) diff --git a/.eslintrc b/.eslintrc index 6867c22f6..d5fccb2a4 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,17 +1,26 @@ { "parser" : "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint", + "react" + ], + "env" : { + "browser" : true, + "es6": true + }, "extends" : [ "standard", "standard-react", + "eslint:recommended", + "plugin:react/recommended", + "plugin:react-hooks/recommended", + "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended" ], - "plugins": [ - "@typescript-eslint", - "react" - ], - "env" : { - "browser" : true, - "es6": true + "parserOptions": { + "ecmaFeatures": { + "jsx": true + } }, "globals": { "__DEV__": false @@ -22,20 +31,48 @@ } }, "rules": { - "generator-star-spacing": 0, - "indent": [2, 4, { "ignoredNodes": ["JSXAttribute", "JSXSpreadAttribute"], "SwitchCase": 1 }], - "no-warning-comments": [1, { - "terms": ["todo", "fixme", "xxx"], - "location": "start" + // Eslint + "comma-dangle": ["error", "never"], + "indent": ["error", 4, { "SwitchCase": 1 }], + "keyword-spacing": ["error", { "after": true, "before": true }], + "object-curly-spacing": ["error", "always"], + "quotes": ["error", "single", { "avoidEscape": true }], + "space-before-function-paren": ["error", "always"], + "space-before-blocks": ["error", "always"], + "semi": ["error", "always"], + // React + "react/jsx-curly-brace-presence": ["error", "always"], + "react/jsx-curly-newline": ["error", "consistent"], + "react/jsx-equals-spacing": ["error", "never"], + "react/jsx-indent-props": ["error", 4], + "react/jsx-max-props-per-line": ["error", { "maximum": 1, "when": "multiline" }], + "react/jsx-wrap-multilines": ["error", { + "arrow": "parens-new-line", + "assignment": "parens-new-line", + "declaration": "parens-new-line", + "logical": "ignore", + "prop": "parens", + "return": "parens-new-line" + }], + "react/jsx-tag-spacing": ["error", { + "afterOpening": "never", + "beforeClosing": "never", + "beforeSelfClosing": "always", + "closingSlash": "never" + }], + // Typescript + "@typescript-eslint/ban-ts-comment": ["error", { + "ts-expect-error": true, + "ts-ignore": "allow-with-description", + "ts-nocheck": true, + "ts-check": false, + "minimumDescriptionLength": 10 }], - "operator-linebreak": [2, "after"], - "padded-blocks": 0, - "semi": [2, "always"], - "react/jsx-indent-props": [2, 2], - "react/jsx-boolean-value": [0, "never"], - "react/jsx-curly-spacing": [0, "never"], - "react/jsx-indent": [2, 4], - "@typescript-eslint/ban-ts-comment": "off", - "@typescript-eslint/explicit-module-boundary-types": "off" + "@typescript-eslint/camelcase": 0, + "@typescript-eslint/no-explicit-any": 0, + "@typescript-eslint/no-var-requires": 0, + "@typescript-eslint/no-use-before-define": 0, + "@typescript-eslint/interface-name-prefix": 0, + "@typescript-eslint/explicit-function-return-type": ["error", { "allowExpressions": true }] } } \ No newline at end of file From 59fa8950eeb8446124454ab3e2711038066115bb Mon Sep 17 00:00:00 2001 From: bd-arc Date: Thu, 27 Aug 2020 17:58:06 +0200 Subject: [PATCH 34/34] chore(package): bump to version 4.0.0-beta.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a55a77f1c..b19e1b857 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-snap-carousel", - "version": "4.0.0-beta.5", + "version": "4.0.0-beta.6", "description": "Swiper/carousel component for React Native with previews, multiple layouts, parallax images, performant handling of huge numbers of items, and more. Compatible with Android & iOS.", "main": "lib/commonjs/index", "module": "lib/module/index",