UNPKG

create-expo-cljs-app

Version:

Create a react native application with Expo and Shadow-CLJS!

343 lines (304 loc) 10.9 kB
/** * Copyright (c) Nicolas Gallagher. * 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. * * @noflow */ import type { ViewProps, ViewStyle } from '../View/types'; import createReactClass from 'create-react-class'; import dismissKeyboard from '../../modules/dismissKeyboard'; import invariant from 'fbjs/lib/invariant'; import mergeRefs from '../../modules/mergeRefs'; import ScrollResponder from '../../modules/ScrollResponder'; import ScrollViewBase from './ScrollViewBase'; import StyleSheet from '../StyleSheet'; import View from '../View'; import React from 'react'; type ScrollViewProps = { ...ViewProps, contentContainerStyle?: ViewStyle, horizontal?: boolean, keyboardDismissMode?: 'none' | 'interactive' | 'on-drag', onContentSizeChange?: (e: any) => void, onScroll?: (e: any) => void, pagingEnabled?: boolean, refreshControl?: any, scrollEnabled?: boolean, scrollEventThrottle?: number, stickyHeaderIndices?: Array<number> }; const emptyObject = {}; /* eslint-disable react/prefer-es6-class */ const ScrollView = ((createReactClass({ mixins: [ScrollResponder.Mixin], getInitialState() { return this.scrollResponderMixinGetInitialState(); }, flashScrollIndicators() { this.scrollResponderFlashScrollIndicators(); }, /** * Returns a reference to the underlying scroll responder, which supports * operations like `scrollTo`. All ScrollView-like components should * implement this method so that they can be composed while providing access * to the underlying scroll responder's methods. */ getScrollResponder(): ScrollView { return this; }, getScrollableNode(): any { return this._scrollNodeRef; }, getInnerViewRef(): any { return this._innerViewRef; }, getInnerViewNode(): any { return this._innerViewRef; }, getNativeScrollRef(): any { return this._scrollNodeRef; }, /** * Scrolls to a given x, y offset, either immediately or with a smooth animation. * Syntax: * * scrollTo(options: {x: number = 0; y: number = 0; animated: boolean = true}) * * Note: The weird argument signature is due to the fact that, for historical reasons, * the function also accepts separate arguments as as alternative to the options object. * This is deprecated due to ambiguity (y before x), and SHOULD NOT BE USED. */ scrollTo( y?: number | { x?: number, y?: number, animated?: boolean }, x?: number, animated?: boolean ) { if (typeof y === 'number') { console.warn( '`scrollTo(y, x, animated)` is deprecated. Use `scrollTo({x: 5, y: 5, animated: true})` instead.' ); } else { ({ x, y, animated } = y || emptyObject); } this.getScrollResponder().scrollResponderScrollTo({ x: x || 0, y: y || 0, animated: animated !== false }); }, /** * If this is a vertical ScrollView scrolls to the bottom. * If this is a horizontal ScrollView scrolls to the right. * * Use `scrollToEnd({ animated: true })` for smooth animated scrolling, * `scrollToEnd({ animated: false })` for immediate scrolling. * If no options are passed, `animated` defaults to true. */ scrollToEnd(options?: { animated?: boolean }) { // Default to true const animated = (options && options.animated) !== false; const { horizontal } = this.props; const scrollResponder = this.getScrollResponder(); const scrollResponderNode = scrollResponder.scrollResponderGetScrollableNode(); const x = horizontal ? scrollResponderNode.scrollWidth : 0; const y = horizontal ? 0 : scrollResponderNode.scrollHeight; scrollResponder.scrollResponderScrollTo({ x, y, animated }); }, render() { const { contentContainerStyle, horizontal, onContentSizeChange, refreshControl, stickyHeaderIndices, pagingEnabled, /* eslint-disable */ forwardedRef, keyboardDismissMode, onScroll, /* eslint-enable */ ...other } = this.props; if (process.env.NODE_ENV !== 'production' && this.props.style) { const style = StyleSheet.flatten(this.props.style); const childLayoutProps = ['alignItems', 'justifyContent'].filter( (prop) => style && style[prop] !== undefined ); invariant( childLayoutProps.length === 0, `ScrollView child layout (${JSON.stringify(childLayoutProps)}) ` + 'must be applied through the contentContainerStyle prop.' ); } let contentSizeChangeProps = {}; if (onContentSizeChange) { contentSizeChangeProps = { onLayout: this._handleContentOnLayout }; } const hasStickyHeaderIndices = !horizontal && Array.isArray(stickyHeaderIndices); const children = hasStickyHeaderIndices || pagingEnabled ? React.Children.map(this.props.children, (child, i) => { const isSticky = hasStickyHeaderIndices && stickyHeaderIndices.indexOf(i) > -1; if (child != null && (isSticky || pagingEnabled)) { return ( <View style={StyleSheet.compose( isSticky && styles.stickyHeader, pagingEnabled && styles.pagingEnabledChild )} > {child} </View> ); } else { return child; } }) : this.props.children; const contentContainer = ( <View {...contentSizeChangeProps} children={children} collapsable={false} ref={this._setInnerViewRef} style={StyleSheet.compose( horizontal && styles.contentContainerHorizontal, contentContainerStyle )} /> ); const baseStyle = horizontal ? styles.baseHorizontal : styles.baseVertical; const pagingEnabledStyle = horizontal ? styles.pagingEnabledHorizontal : styles.pagingEnabledVertical; const props = { ...other, style: [baseStyle, pagingEnabled && pagingEnabledStyle, this.props.style], onTouchStart: this.scrollResponderHandleTouchStart, onTouchMove: this.scrollResponderHandleTouchMove, onTouchEnd: this.scrollResponderHandleTouchEnd, onScrollBeginDrag: this.scrollResponderHandleScrollBeginDrag, onScrollEndDrag: this.scrollResponderHandleScrollEndDrag, onMomentumScrollBegin: this.scrollResponderHandleMomentumScrollBegin, onMomentumScrollEnd: this.scrollResponderHandleMomentumScrollEnd, onStartShouldSetResponder: this.scrollResponderHandleStartShouldSetResponder, onStartShouldSetResponderCapture: this.scrollResponderHandleStartShouldSetResponderCapture, onScrollShouldSetResponder: this.scrollResponderHandleScrollShouldSetResponder, onScroll: this._handleScroll, onResponderGrant: this.scrollResponderHandleResponderGrant, onResponderTerminationRequest: this.scrollResponderHandleTerminationRequest, onResponderTerminate: this.scrollResponderHandleTerminate, onResponderRelease: this.scrollResponderHandleResponderRelease, onResponderReject: this.scrollResponderHandleResponderReject }; const ScrollViewClass = ScrollViewBase; invariant(ScrollViewClass !== undefined, 'ScrollViewClass must not be undefined'); const scrollView = ( <ScrollViewClass {...props} ref={this._setScrollNodeRef}> {contentContainer} </ScrollViewClass> ); if (refreshControl) { return React.cloneElement(refreshControl, { style: props.style }, scrollView); } return scrollView; }, _handleContentOnLayout(e: Object) { const { width, height } = e.nativeEvent.layout; this.props.onContentSizeChange(width, height); }, _handleScroll(e: Object) { if (process.env.NODE_ENV !== 'production') { if (this.props.onScroll && this.props.scrollEventThrottle == null) { console.log( 'You specified `onScroll` on a <ScrollView> but not ' + '`scrollEventThrottle`. You will only receive one event. ' + 'Using `16` you get all the events but be aware that it may ' + "cause frame drops, use a bigger number if you don't need as " + 'much precision.' ); } } if (this.props.keyboardDismissMode === 'on-drag') { dismissKeyboard(); } this.scrollResponderHandleScroll(e); }, _setInnerViewRef(node) { this._innerViewRef = node; }, _setScrollNodeRef(node) { this._scrollNodeRef = node; // ScrollView needs to add more methods to the hostNode in addition to those // added by `usePlatformMethods`. This is temporarily until an API like // `ScrollView.scrollTo(hostNode, { x, y })` is added to React Native. if (node != null) { node.getScrollResponder = this.getScrollResponder; node.getInnerViewNode = this.getInnerViewNode; node.getInnerViewRef = this.getInnerViewRef; node.getNativeScrollRef = this.getNativeScrollRef; node.getScrollableNode = this.getScrollableNode; node.scrollTo = this.scrollTo; node.scrollToEnd = this.scrollToEnd; node.flashScrollIndicators = this.flashScrollIndicators; node.scrollResponderZoomTo = this.scrollResponderZoomTo; node.scrollResponderScrollNativeHandleToKeyboard = this.scrollResponderScrollNativeHandleToKeyboard; } const ref = mergeRefs(this.props.forwardedRef); ref(node); } }): any): React.ComponentType<ScrollViewProps>); const commonStyle = { flexGrow: 1, flexShrink: 1, // Enable hardware compositing in modern browsers. // Creates a new layer with its own backing surface that can significantly // improve scroll performance. transform: [{ translateZ: 0 }], // iOS native scrolling WebkitOverflowScrolling: 'touch' }; const styles = StyleSheet.create({ baseVertical: { ...commonStyle, flexDirection: 'column', overflowX: 'hidden', overflowY: 'auto' }, baseHorizontal: { ...commonStyle, flexDirection: 'row', overflowX: 'auto', overflowY: 'hidden' }, contentContainerHorizontal: { flexDirection: 'row' }, stickyHeader: { position: 'sticky', top: 0, zIndex: 10 }, pagingEnabledHorizontal: { scrollSnapType: 'x mandatory' }, pagingEnabledVertical: { scrollSnapType: 'y mandatory' }, pagingEnabledChild: { scrollSnapAlign: 'start' } }); const ForwardedScrollView: React.AbstractComponent< React.ElementConfig<typeof ScrollView>, React.ElementRef<typeof ScrollView> > = React.forwardRef((props, forwardedRef) => { return <ScrollView {...props} forwardedRef={forwardedRef} />; }); ForwardedScrollView.displayName = 'ScrollView'; export default ForwardedScrollView;