create-expo-cljs-app
Version:
Create a react native application with Expo and Shadow-CLJS!
343 lines (304 loc) • 10.9 kB
JavaScript
/**
* 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;