1
.gitignore
vendored
@@ -18,7 +18,6 @@ xcuserdata
|
||||
DerivedData
|
||||
*.hmap
|
||||
*.ipa
|
||||
gradle
|
||||
project.xcworkspace
|
||||
|
||||
# Android/IJ
|
||||
|
||||
8
.prettierrc.js
Normal file
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
arrowParens: 'avoid',
|
||||
jsxBracketSameLine: true,
|
||||
bracketSameLine: true,
|
||||
bracketSpacing: false,
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
module.exports = {
|
||||
arrowParens: 'avoid',
|
||||
jsxBracketSameLine: true,
|
||||
bracketSameLine: true,
|
||||
bracketSpacing: false,
|
||||
singleQuote: true,
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
import { Modal } from 'react-native';
|
||||
export default Modal;
|
||||
@@ -1,2 +0,0 @@
|
||||
import Modal from './modal-enhanced-react-native-web';
|
||||
export default Modal;
|
||||
@@ -1 +0,0 @@
|
||||
export * from 'react-native-svg'
|
||||
@@ -1,388 +0,0 @@
|
||||
// @ts-ignore
|
||||
import * as React from 'react';
|
||||
import { createElement, GestureResponderEvent } from 'react-native';
|
||||
import { NumberProp, TransformProps } from 'react-native-svg/src/lib/extract/types';
|
||||
import SvgTouchableMixin from 'react-native-svg/src/lib/SvgTouchableMixin';
|
||||
import { resolve } from 'react-native-svg/src/lib/resolve';
|
||||
|
||||
type BlurEvent = Object;
|
||||
type FocusEvent = Object;
|
||||
type PressEvent = Object;
|
||||
type LayoutEvent = Object;
|
||||
type EdgeInsetsProp = Object;
|
||||
|
||||
interface BaseProps {
|
||||
accessible?: boolean;
|
||||
accessibilityLabel?: string;
|
||||
accessibilityHint?: string;
|
||||
accessibilityIgnoresInvertColors?: boolean;
|
||||
accessibilityRole?: string;
|
||||
accessibilityState?: Object;
|
||||
delayLongPress?: number;
|
||||
delayPressIn?: number;
|
||||
delayPressOut?: number;
|
||||
disabled?: boolean;
|
||||
hitSlop?: EdgeInsetsProp;
|
||||
nativeID?: string;
|
||||
touchSoundDisabled?: boolean;
|
||||
onBlur?: (e: BlurEvent) => void;
|
||||
onFocus?: (e: FocusEvent) => void;
|
||||
onLayout?: (event: LayoutEvent) => object;
|
||||
onLongPress?: (event: PressEvent) => object;
|
||||
onClick?: (event: PressEvent) => object;
|
||||
onPress?: (event: PressEvent) => object;
|
||||
onPressIn?: (event: PressEvent) => object;
|
||||
onPressOut?: (event: PressEvent) => object;
|
||||
pressRetentionOffset?: EdgeInsetsProp;
|
||||
rejectResponderTermination?: boolean;
|
||||
|
||||
translate: TransformProps;
|
||||
scale: NumberProp;
|
||||
rotation: NumberProp;
|
||||
skewX: NumberProp;
|
||||
skewY: NumberProp;
|
||||
originX: NumberProp;
|
||||
originY: NumberProp;
|
||||
|
||||
fontStyle?: string;
|
||||
fontWeight?: NumberProp;
|
||||
fontSize?: NumberProp;
|
||||
fontFamily?: string;
|
||||
forwardedRef: {};
|
||||
style: Iterable<{}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* `react-native-svg` supports additional props that aren't defined in the spec.
|
||||
* This function replaces them in a spec conforming manner.
|
||||
*
|
||||
* @param {WebShape} self Instance given to us.
|
||||
* @param {Object?} props Optional overridden props given to us.
|
||||
* @returns {Object} Cleaned props object.
|
||||
* @private
|
||||
*/
|
||||
const prepare = <T extends BaseProps>(
|
||||
self: WebShape<T>,
|
||||
props = self.props
|
||||
) => {
|
||||
const {
|
||||
translate,
|
||||
scale,
|
||||
rotation,
|
||||
skewX,
|
||||
skewY,
|
||||
originX,
|
||||
originY,
|
||||
fontFamily,
|
||||
fontSize,
|
||||
fontWeight,
|
||||
fontStyle,
|
||||
style,
|
||||
forwardedRef,
|
||||
onPress,
|
||||
onPressIn,
|
||||
onPressOut,
|
||||
onLongPress,
|
||||
// @ts-ignore
|
||||
...rest
|
||||
} = props;
|
||||
const hasTouchableProperty =
|
||||
onPress || onPressIn || onPressOut || onLongPress;
|
||||
const clean: {
|
||||
accessible?: boolean;
|
||||
onStartShouldSetResponder?: (e: GestureResponderEvent) => boolean;
|
||||
onResponderMove?: (e: GestureResponderEvent) => void;
|
||||
onResponderGrant?: (e: GestureResponderEvent) => void;
|
||||
onResponderRelease?: (e: GestureResponderEvent) => void;
|
||||
onResponderTerminate?: (e: GestureResponderEvent) => void;
|
||||
onResponderTerminationRequest?: (e: GestureResponderEvent) => boolean;
|
||||
transform?: string;
|
||||
style?: {};
|
||||
ref?: {};
|
||||
} = {
|
||||
...(hasTouchableProperty
|
||||
? {
|
||||
onStartShouldSetResponder:
|
||||
self.touchableHandleStartShouldSetResponder,
|
||||
onResponderTerminationRequest:
|
||||
self.touchableHandleResponderTerminationRequest,
|
||||
onResponderGrant: self.touchableHandleResponderGrant,
|
||||
onResponderMove: self.touchableHandleResponderMove,
|
||||
onResponderRelease: self.touchableHandleResponderRelease,
|
||||
onResponderTerminate: self.touchableHandleResponderTerminate,
|
||||
}
|
||||
: null),
|
||||
...rest,
|
||||
};
|
||||
|
||||
const transform = [];
|
||||
|
||||
if (originX != null || originY != null) {
|
||||
transform.push(`translate(${originX || 0}, ${originY || 0})`);
|
||||
}
|
||||
if (translate != null) {
|
||||
transform.push(`translate(${translate})`);
|
||||
}
|
||||
if (scale != null) {
|
||||
transform.push(`scale(${scale})`);
|
||||
}
|
||||
// rotation maps to rotate, not to collide with the text rotate attribute (which acts per glyph rather than block)
|
||||
if (rotation != null) {
|
||||
transform.push(`rotate(${rotation})`);
|
||||
}
|
||||
if (skewX != null) {
|
||||
transform.push(`skewX(${skewX})`);
|
||||
}
|
||||
if (skewY != null) {
|
||||
transform.push(`skewY(${skewY})`);
|
||||
}
|
||||
if (originX != null || originY != null) {
|
||||
transform.push(`translate(${-originX || 0}, ${-originY || 0})`);
|
||||
}
|
||||
|
||||
if (transform.length) {
|
||||
clean.transform = transform.join(' ');
|
||||
}
|
||||
|
||||
if (forwardedRef) {
|
||||
clean.ref = forwardedRef;
|
||||
}
|
||||
|
||||
const styles: {
|
||||
fontStyle?: string;
|
||||
fontFamily?: string;
|
||||
fontSize?: NumberProp;
|
||||
fontWeight?: NumberProp;
|
||||
} = {};
|
||||
|
||||
if (fontFamily != null) {
|
||||
styles.fontFamily = fontFamily;
|
||||
}
|
||||
if (fontSize != null) {
|
||||
styles.fontSize = fontSize;
|
||||
}
|
||||
if (fontWeight != null) {
|
||||
styles.fontWeight = fontWeight;
|
||||
}
|
||||
if (fontStyle != null) {
|
||||
styles.fontStyle = fontStyle;
|
||||
}
|
||||
|
||||
clean.style = resolve(style, styles);
|
||||
|
||||
return clean;
|
||||
};
|
||||
|
||||
const getBoundingClientRect = (node: SVGElement) => {
|
||||
if (node) {
|
||||
// @ts-ignore
|
||||
const isElement = node.nodeType === 1; /* Node.ELEMENT_NODE */
|
||||
// @ts-ignore
|
||||
if (isElement && typeof node.getBoundingClientRect === 'function') {
|
||||
// @ts-ignore
|
||||
return node.getBoundingClientRect();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const measureLayout = (
|
||||
node: SVGElement,
|
||||
callback: (
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
left: number,
|
||||
top: number
|
||||
) => void
|
||||
) => {
|
||||
// @ts-ignore
|
||||
const relativeNode = node && node.parentNode;
|
||||
if (node && relativeNode) {
|
||||
setTimeout(() => {
|
||||
// @ts-ignore
|
||||
const relativeRect = getBoundingClientRect(relativeNode);
|
||||
// @ts-ignore
|
||||
const { height, left, top, width } = getBoundingClientRect(node);
|
||||
// @ts-ignore
|
||||
const x = left - relativeRect.left;
|
||||
// @ts-ignore
|
||||
const y = top - relativeRect.top;
|
||||
callback(x, y, width, height, left, top);
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
function remeasure() {
|
||||
// @ts-ignore
|
||||
const tag = this.state.touchable.responderID;
|
||||
if (tag == null) {
|
||||
return;
|
||||
}
|
||||
// @ts-ignore
|
||||
measureLayout(tag, this._handleQueryLayout);
|
||||
}
|
||||
|
||||
export class WebShape<
|
||||
P extends BaseProps = BaseProps,
|
||||
C = {}
|
||||
> extends React.Component<P, C> {
|
||||
[x: string]: unknown;
|
||||
constructor(props: P, context: C) {
|
||||
super(props, context);
|
||||
SvgTouchableMixin(this);
|
||||
this._remeasureMetricsOnActivation = remeasure.bind(this);
|
||||
}
|
||||
}
|
||||
|
||||
export class Circle extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('circle', prepare(this));
|
||||
}
|
||||
}
|
||||
|
||||
export class ClipPath extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('clipPath', prepare(this));
|
||||
}
|
||||
}
|
||||
|
||||
export class Defs extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('defs', prepare(this));
|
||||
}
|
||||
}
|
||||
|
||||
export class Ellipse extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('ellipse', prepare(this));
|
||||
}
|
||||
}
|
||||
|
||||
export class G extends WebShape<
|
||||
BaseProps & {
|
||||
x?: NumberProp;
|
||||
y?: NumberProp;
|
||||
translate?: string;
|
||||
}
|
||||
> {
|
||||
render(): JSX.Element {
|
||||
const { x, y, ...rest } = this.props;
|
||||
|
||||
if ((x || y) && !rest.translate) {
|
||||
rest.translate = `${x || 0}, ${y || 0}`;
|
||||
}
|
||||
|
||||
return createElement('g', prepare(this, rest));
|
||||
}
|
||||
}
|
||||
|
||||
export class Image extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('image', prepare(this));
|
||||
}
|
||||
}
|
||||
|
||||
export class Line extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('line', prepare(this));
|
||||
}
|
||||
}
|
||||
|
||||
export class LinearGradient extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('linearGradient', prepare(this));
|
||||
}
|
||||
}
|
||||
|
||||
export class Path extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('path', prepare(this));
|
||||
}
|
||||
}
|
||||
|
||||
export class Polygon extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('polygon', prepare(this));
|
||||
}
|
||||
}
|
||||
|
||||
export class Polyline extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('polyline', prepare(this));
|
||||
}
|
||||
}
|
||||
|
||||
export class RadialGradient extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('radialGradient', prepare(this));
|
||||
}
|
||||
}
|
||||
|
||||
export class Rect extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('rect', prepare(this));
|
||||
}
|
||||
}
|
||||
|
||||
export class Stop extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('stop', prepare(this));
|
||||
}
|
||||
}
|
||||
|
||||
export class Svg extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('svg', prepare(this));
|
||||
}
|
||||
}
|
||||
|
||||
export class Symbol extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('symbol', prepare(this));
|
||||
}
|
||||
}
|
||||
|
||||
export class Text extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('text', prepare(this));
|
||||
}
|
||||
}
|
||||
|
||||
export class TSpan extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('tspan', prepare(this));
|
||||
}
|
||||
}
|
||||
|
||||
export class TextPath extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('textPath', prepare(this));
|
||||
}
|
||||
}
|
||||
|
||||
export class Use extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('use', prepare(this));
|
||||
}
|
||||
}
|
||||
|
||||
export class Mask extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('mask', prepare(this));
|
||||
}
|
||||
}
|
||||
|
||||
export class Marker extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('marker', prepare(this));
|
||||
}
|
||||
}
|
||||
|
||||
export class Pattern extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('pattern', prepare(this));
|
||||
}
|
||||
}
|
||||
|
||||
export default Svg;
|
||||
@@ -1,2 +0,0 @@
|
||||
import { Image } from 'react-native-svg';
|
||||
export default Image;
|
||||
@@ -1,41 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Image as NativeImage, View } from 'react-native';
|
||||
import { Image as SvgImage } from '../Svg';
|
||||
|
||||
class WebImage extends NativeImage {
|
||||
oldRender: () => React.ReactNode;
|
||||
private _setImageRef: (ref: any) => void;
|
||||
private _imageRef: any;
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this._setImageRef = ref => {
|
||||
this._imageRef = ref;
|
||||
const attrs = ref && ref.attributes;
|
||||
const src = attrs && attrs.src;
|
||||
const uri = src && src.value;
|
||||
this.setState({ href: uri });
|
||||
};
|
||||
this.oldRender = this.render;
|
||||
this.render = () => {
|
||||
const uri = this.state && this.state.href;
|
||||
return (
|
||||
<>
|
||||
<View
|
||||
style={{
|
||||
visibility: 'hidden',
|
||||
position: 'absolute',
|
||||
width: 0,
|
||||
height: 0,
|
||||
}}>
|
||||
{this.oldRender()}
|
||||
</View>
|
||||
<SvgImage {...this.props} href={uri} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default props => (
|
||||
<WebImage {...props} source={props.source || props.href} />
|
||||
);
|
||||
27
Example/dist/index.html
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>react-native-svg example</title>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
overflow: hidden;
|
||||
}
|
||||
#root {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<script src="bundle.web.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -3,7 +3,12 @@
|
||||
*/
|
||||
|
||||
import {AppRegistry} from 'react-native';
|
||||
import App from './App';
|
||||
import App from './src/App';
|
||||
import {name as appName} from './app.json';
|
||||
|
||||
AppRegistry.registerComponent(appName, () => App);
|
||||
|
||||
if (typeof document !== 'undefined') {
|
||||
const rootTag = document.getElementById('root');
|
||||
AppRegistry.runApplication(appName, {rootTag});
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* MIT License
|
||||
* Copyright (c) 2017 React Native Community
|
||||
* https://github.com/react-native-community/react-native-modal
|
||||
*/
|
||||
|
||||
/**
|
||||
* Since react-native-animatable applies by default a margin of 100 to its sliding animation,
|
||||
* we reset them here overriding the margin to 0.
|
||||
*/
|
||||
import { Dimensions } from 'react-native';
|
||||
const { height, width } = Dimensions.get('window');
|
||||
|
||||
const makeSlideTranslation = (translationType, fromValue, toValue) => ({
|
||||
from: {
|
||||
[translationType]: fromValue,
|
||||
},
|
||||
to: {
|
||||
[translationType]: toValue,
|
||||
},
|
||||
});
|
||||
|
||||
export const slideInDown = makeSlideTranslation('translateY', -height, 0);
|
||||
|
||||
export const slideInUp = makeSlideTranslation('translateY', height, 0);
|
||||
|
||||
export const slideInLeft = makeSlideTranslation('translateX', -width, 0);
|
||||
|
||||
export const slideInRight = makeSlideTranslation('translateX', width, 0);
|
||||
|
||||
export const slideOutDown = makeSlideTranslation('translateY', 0, height);
|
||||
|
||||
export const slideOutUp = makeSlideTranslation('translateY', 0, -height);
|
||||
|
||||
export const slideOutLeft = makeSlideTranslation('translateX', 0, -width);
|
||||
|
||||
export const slideOutRight = makeSlideTranslation('translateX', 0, width);
|
||||
@@ -1,507 +0,0 @@
|
||||
/**
|
||||
* MIT License
|
||||
* Copyright (c) 2017 Ryan Florence
|
||||
* https://github.com/reactjs/react-modal/blob/master/LICENSE
|
||||
*
|
||||
* Take WAI-ARIA for React Native Web
|
||||
*
|
||||
* Modified by Ray Andrew <raydreww@gmail.com>
|
||||
* For Modal React Native Web
|
||||
*
|
||||
* MIT License
|
||||
* Copyright (c) 2018 Ray Andrew
|
||||
* https://github.com/rayandrews/react-native-web-modal
|
||||
*/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
Dimensions,
|
||||
TouchableWithoutFeedback,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
PanResponder,
|
||||
Animated,
|
||||
} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
View,
|
||||
initializeRegistryWithDefinitions,
|
||||
registerAnimation,
|
||||
createAnimation,
|
||||
} from 'react-native-animatable';
|
||||
import Modal from '../modal-react-native-web';
|
||||
|
||||
import * as ANIMATION_DEFINITIONS from './animations';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
// Override default animations
|
||||
initializeRegistryWithDefinitions(ANIMATION_DEFINITIONS);
|
||||
|
||||
// Utility for creating custom animations
|
||||
const makeAnimation = (name, obj) => {
|
||||
registerAnimation(name, createAnimation(obj));
|
||||
};
|
||||
|
||||
const isObject = obj => obj !== null && typeof obj === 'object';
|
||||
|
||||
class ReactNativeModal extends Component {
|
||||
static setAppElement = Modal.setAppElement;
|
||||
|
||||
static propTypes = {
|
||||
animationIn: PropTypes.oneOfType([PropTypes.string, PropTypes.shape]),
|
||||
animationInTiming: PropTypes.number,
|
||||
animationOut: PropTypes.oneOfType([PropTypes.string, PropTypes.shape]),
|
||||
animationOutTiming: PropTypes.number,
|
||||
avoidKeyboard: PropTypes.bool,
|
||||
backdropColor: PropTypes.string,
|
||||
backdropOpacity: PropTypes.number,
|
||||
backdropTransitionInTiming: PropTypes.number,
|
||||
backdropTransitionOutTiming: PropTypes.number,
|
||||
children: PropTypes.node.isRequired,
|
||||
isVisible: PropTypes.bool,
|
||||
hideModalContentWhileAnimating: PropTypes.bool,
|
||||
onModalShow: PropTypes.func,
|
||||
onModalHide: PropTypes.func,
|
||||
onBackButtonPress: PropTypes.func,
|
||||
onBackdropPress: PropTypes.func,
|
||||
onSwipe: PropTypes.func,
|
||||
swipeThreshold: PropTypes.number,
|
||||
swipeDirection: PropTypes.oneOf(['up', 'down', 'left', 'right']),
|
||||
useNativeDriver: PropTypes.bool,
|
||||
style: PropTypes.any,
|
||||
scrollTo: PropTypes.func,
|
||||
scrollOffset: PropTypes.number,
|
||||
scrollOffsetMax: PropTypes.number,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
animationIn: 'slideInUp',
|
||||
animationInTiming: 300,
|
||||
animationOut: 'slideOutDown',
|
||||
animationOutTiming: 300,
|
||||
avoidKeyboard: false,
|
||||
backdropColor: 'black',
|
||||
backdropOpacity: 0.7,
|
||||
backdropTransitionInTiming: 300,
|
||||
backdropTransitionOutTiming: 300,
|
||||
onModalShow: () => null,
|
||||
onModalHide: () => null,
|
||||
isVisible: false,
|
||||
hideModalContentWhileAnimating: false,
|
||||
onBackdropPress: () => null,
|
||||
onBackButtonPress: () => null,
|
||||
swipeThreshold: 100,
|
||||
useNativeDriver: false,
|
||||
scrollTo: null,
|
||||
scrollOffset: 0,
|
||||
scrollOffsetMax: 0,
|
||||
};
|
||||
|
||||
// We use an internal state for keeping track of the modal visibility: this allows us to keep
|
||||
// the modal visibile during the exit animation, even if the user has already change the
|
||||
// isVisible prop to false.
|
||||
// We store in the state the device width and height so that we can update the modal on
|
||||
// device rotation.
|
||||
state = {
|
||||
showContent: true,
|
||||
isVisible: false,
|
||||
deviceWidth: Dimensions.get('window').width,
|
||||
deviceHeight: Dimensions.get('window').height,
|
||||
isSwipeable: !!this.props.swipeDirection,
|
||||
pan: null,
|
||||
};
|
||||
|
||||
transitionLock = null;
|
||||
|
||||
inSwipeClosingState = false;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.buildAnimations(props);
|
||||
if (this.state.isSwipeable) {
|
||||
this.state = { ...this.state, pan: new Animated.ValueXY() };
|
||||
this.buildPanResponder();
|
||||
}
|
||||
if (this.props.isVisible) {
|
||||
this.state = {
|
||||
...this.state,
|
||||
isVisible: true,
|
||||
showContent: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (!this.state.isVisible && nextProps.isVisible) {
|
||||
this.setState({ isVisible: true, showContent: true });
|
||||
}
|
||||
if (
|
||||
this.props.animationIn !== nextProps.animationIn ||
|
||||
this.props.animationOut !== nextProps.animationOut
|
||||
) {
|
||||
this.buildAnimations(nextProps);
|
||||
}
|
||||
if (
|
||||
this.props.backdropOpacity !== nextProps.backdropOpacity &&
|
||||
this.backdropRef
|
||||
) {
|
||||
this.backdropRef.transitionTo(
|
||||
{ opacity: nextProps.backdropOpacity },
|
||||
this.props.backdropTransitionInTiming
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.state.isVisible) {
|
||||
this.open();
|
||||
}
|
||||
// window.addEventListener('resize', () => {
|
||||
// this.handleDimensionsUpdate();
|
||||
// });
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// window.removeEventListener('resize', () => {
|
||||
// this.handleDimensionsUpdate();
|
||||
// });
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
// On modal open request, we slide the view up and fade in the backdrop
|
||||
if (this.props.isVisible && !prevProps.isVisible) {
|
||||
this.open();
|
||||
} else if (!this.props.isVisible && prevProps.isVisible) {
|
||||
// On modal close request, we slide the view down and fade out the backdrop
|
||||
this._close();
|
||||
}
|
||||
}
|
||||
|
||||
buildPanResponder = () => {
|
||||
let animEvt = null;
|
||||
|
||||
if (
|
||||
this.props.swipeDirection === 'right' ||
|
||||
this.props.swipeDirection === 'left'
|
||||
) {
|
||||
animEvt = Animated.event([null, { dx: this.state.pan.x }]);
|
||||
} else {
|
||||
animEvt = Animated.event([null, { dy: this.state.pan.y }]);
|
||||
}
|
||||
|
||||
this.panResponder = PanResponder.create({
|
||||
onStartShouldSetPanResponder: () => {
|
||||
if (this.props.scrollTo) {
|
||||
if (this.props.scrollOffset > 0) {
|
||||
return false; // user needs to be able to scroll content back up
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
onPanResponderMove: (evt, gestureState) => {
|
||||
// Dim the background while swiping the modal
|
||||
const accDistance = this.getAccDistancePerDirection(gestureState);
|
||||
const newOpacityFactor = 1 - accDistance / this.state.deviceWidth;
|
||||
if (this.isSwipeDirectionAllowed(gestureState)) {
|
||||
this.backdropRef &&
|
||||
this.backdropRef.transitionTo({
|
||||
opacity: this.props.backdropOpacity * newOpacityFactor,
|
||||
});
|
||||
animEvt(evt, gestureState);
|
||||
} else if (this.props.scrollTo) {
|
||||
let offsetY = -gestureState.dy;
|
||||
if (offsetY > this.props.scrollOffsetMax) {
|
||||
offsetY -= (offsetY - this.props.scrollOffsetMax) / 2;
|
||||
}
|
||||
this.props.scrollTo({ y: offsetY, animated: false });
|
||||
}
|
||||
},
|
||||
onPanResponderRelease: (evt, gestureState) => {
|
||||
// Call the onSwipe prop if the threshold has been exceeded
|
||||
const accDistance = this.getAccDistancePerDirection(gestureState);
|
||||
if (accDistance > this.props.swipeThreshold) {
|
||||
if (this.props.onSwipe) {
|
||||
this.inSwipeClosingState = true;
|
||||
this.props.onSwipe();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Reset backdrop opacity and modal position
|
||||
if (this.backdropRef) {
|
||||
this.backdropRef.transitionTo(
|
||||
{ opacity: this.props.backdropOpacity },
|
||||
this.props.backdropTransitionInTiming
|
||||
);
|
||||
}
|
||||
Animated.spring(this.state.pan, {
|
||||
toValue: { x: 0, y: 0 },
|
||||
bounciness: 0,
|
||||
}).start();
|
||||
if (this.props.scrollOffset > this.props.scrollOffsetMax) {
|
||||
this.props.scrollTo({
|
||||
y: this.props.scrollOffsetMax,
|
||||
animated: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
getAccDistancePerDirection = gestureState => {
|
||||
switch (this.props.swipeDirection) {
|
||||
case 'up':
|
||||
return -gestureState.dy;
|
||||
case 'down':
|
||||
return gestureState.dy;
|
||||
case 'right':
|
||||
return gestureState.dx;
|
||||
case 'left':
|
||||
return -gestureState.dx;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
isSwipeDirectionAllowed = ({ dy, dx }) => {
|
||||
const draggedDown = dy > 0;
|
||||
const draggedUp = dy < 0;
|
||||
const draggedLeft = dx < 0;
|
||||
const draggedRight = dx > 0;
|
||||
|
||||
if (this.props.swipeDirection === 'up' && draggedUp) {
|
||||
return true;
|
||||
}
|
||||
if (this.props.swipeDirection === 'down' && draggedDown) {
|
||||
return true;
|
||||
}
|
||||
if (this.props.swipeDirection === 'right' && draggedRight) {
|
||||
return true;
|
||||
}
|
||||
if (this.props.swipeDirection === 'left' && draggedLeft) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// User can define custom react-native-animatable animations, see PR #72
|
||||
buildAnimations = props => {
|
||||
let animationIn = props.animationIn;
|
||||
let animationOut = props.animationOut;
|
||||
|
||||
if (isObject(animationIn)) {
|
||||
const animationName = JSON.stringify(animationIn);
|
||||
makeAnimation(animationName, animationIn);
|
||||
animationIn = animationName;
|
||||
}
|
||||
|
||||
if (isObject(animationOut)) {
|
||||
const animationName = JSON.stringify(animationOut);
|
||||
makeAnimation(animationName, animationOut);
|
||||
animationOut = animationName;
|
||||
}
|
||||
|
||||
this.animationIn = animationIn;
|
||||
this.animationOut = animationOut;
|
||||
};
|
||||
|
||||
handleDimensionsUpdate = () => {
|
||||
// Here we update the device dimensions in the state if the layout changed (triggering a render)
|
||||
const deviceWidth = Dimensions.get('window').width;
|
||||
const deviceHeight = Dimensions.get('window').height;
|
||||
if (
|
||||
deviceWidth !== this.state.deviceWidth ||
|
||||
deviceHeight !== this.state.deviceHeight
|
||||
) {
|
||||
this.setState({ deviceWidth, deviceHeight });
|
||||
}
|
||||
};
|
||||
|
||||
handleBackdropRef = ref => {
|
||||
this.backdropRef = ref;
|
||||
if (this.state.isVisible) {
|
||||
this.open();
|
||||
}
|
||||
};
|
||||
|
||||
handleContentRef = ref => {
|
||||
this.contentRef = ref;
|
||||
if (this.state.isVisible) {
|
||||
this.open();
|
||||
}
|
||||
};
|
||||
|
||||
open = () => {
|
||||
if (this.transitionLock) return;
|
||||
// For some reason the backdropRef and contentRef do not initialize before
|
||||
// componentDidMount is called so make sure not to try opening until the
|
||||
// refs are mounted
|
||||
// https://github.com/Dekoruma/react-native-web-modal/issues/5
|
||||
if (!(this.backdropRef && this.contentRef)) return;
|
||||
this.transitionLock = true;
|
||||
|
||||
this.backdropRef.transitionTo(
|
||||
{ opacity: this.props.backdropOpacity },
|
||||
this.props.backdropTransitionInTiming
|
||||
);
|
||||
|
||||
// This is for reset the pan position, if not modal get stuck
|
||||
// at the last release position when you try to open it.
|
||||
// Could certainly be improve - no idea for the moment.
|
||||
if (this.state.isSwipeable) {
|
||||
this.state.pan.setValue({ x: 0, y: 0 });
|
||||
}
|
||||
|
||||
this.contentRef[this.animationIn](this.props.animationInTiming).then(() => {
|
||||
this.transitionLock = false;
|
||||
if (!this.props.isVisible) {
|
||||
this._close();
|
||||
} else {
|
||||
this.props.onModalShow();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
_close = () => {
|
||||
if (this.transitionLock) return;
|
||||
this.transitionLock = true;
|
||||
if (this.backdropRef) {
|
||||
this.backdropRef.transitionTo(
|
||||
{ opacity: 0 },
|
||||
this.props.backdropTransitionOutTiming
|
||||
);
|
||||
}
|
||||
|
||||
let animationOut = this.animationOut;
|
||||
|
||||
if (this.inSwipeClosingState) {
|
||||
this.inSwipeClosingState = false;
|
||||
if (this.props.swipeDirection === 'up') {
|
||||
animationOut = 'slideOutUp';
|
||||
} else if (this.props.swipeDirection === 'down') {
|
||||
animationOut = 'slideOutDown';
|
||||
} else if (this.props.swipeDirection === 'right') {
|
||||
animationOut = 'slideOutRight';
|
||||
} else if (this.props.swipeDirection === 'left') {
|
||||
animationOut = 'slideOutLeft';
|
||||
}
|
||||
}
|
||||
|
||||
if (this.contentRef) {
|
||||
this.contentRef[animationOut](this.props.animationOutTiming).then(() => {
|
||||
this.transitionLock = false;
|
||||
if (this.props.isVisible) {
|
||||
this.open();
|
||||
} else {
|
||||
this.setState(
|
||||
{
|
||||
showContent: false,
|
||||
},
|
||||
() => {
|
||||
this.setState({
|
||||
isVisible: false,
|
||||
});
|
||||
}
|
||||
);
|
||||
this.props.onModalHide();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
animationIn,
|
||||
animationInTiming,
|
||||
animationOut,
|
||||
animationOutTiming,
|
||||
avoidKeyboard,
|
||||
backdropColor,
|
||||
backdropOpacity,
|
||||
backdropTransitionInTiming,
|
||||
backdropTransitionOutTiming,
|
||||
children,
|
||||
isVisible,
|
||||
onModalShow,
|
||||
onBackdropPress,
|
||||
onBackButtonPress,
|
||||
useNativeDriver,
|
||||
style,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
const { deviceWidth } = this.state;
|
||||
|
||||
const computedStyle = [
|
||||
{ margin: deviceWidth * 0.05, transform: [{ translateY: 0 }] },
|
||||
styles.content,
|
||||
style,
|
||||
];
|
||||
|
||||
let panHandlers = {};
|
||||
let panPosition = {};
|
||||
if (this.state.isSwipeable) {
|
||||
panHandlers = { ...this.panResponder.panHandlers };
|
||||
panPosition = this.state.pan.getLayout();
|
||||
}
|
||||
|
||||
const _children =
|
||||
this.props.hideModalContentWhileAnimating &&
|
||||
this.props.useNativeDriver &&
|
||||
!this.state.showContent ? (
|
||||
<View />
|
||||
) : (
|
||||
children
|
||||
);
|
||||
|
||||
const containerView = (
|
||||
<View
|
||||
{...panHandlers}
|
||||
ref={this.handleContentRef}
|
||||
style={[panPosition, computedStyle]}
|
||||
pointerEvents="box-none"
|
||||
useNativeDriver={useNativeDriver}
|
||||
{...otherProps}>
|
||||
{_children}
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
transparent
|
||||
animationType="none"
|
||||
visible={this.state.isVisible}
|
||||
onRequestClose={onBackButtonPress}
|
||||
{...otherProps}>
|
||||
<TouchableWithoutFeedback onPress={onBackdropPress}>
|
||||
<View
|
||||
ref={this.handleBackdropRef}
|
||||
useNativeDriver={useNativeDriver}
|
||||
style={[
|
||||
styles.backdrop,
|
||||
{
|
||||
backgroundColor: this.state.showContent
|
||||
? backdropColor
|
||||
: 'transparent',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</TouchableWithoutFeedback>
|
||||
|
||||
{avoidKeyboard && (
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : null}
|
||||
pointerEvents="box-none"
|
||||
style={computedStyle.concat([{ margin: 0 }])}>
|
||||
{containerView}
|
||||
</KeyboardAvoidingView>
|
||||
)}
|
||||
|
||||
{!avoidKeyboard && containerView}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { ReactNativeModal };
|
||||
export default ReactNativeModal;
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* MIT License
|
||||
* Copyright (c) 2017 React Native Community
|
||||
* https://github.com/react-native-community/react-native-modal
|
||||
*/
|
||||
|
||||
import { StyleSheet } from 'react-native';
|
||||
|
||||
export default StyleSheet.create({
|
||||
backdrop: {
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
opacity: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: '#000000',
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
});
|
||||
@@ -1,37 +0,0 @@
|
||||
import { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
//import ReactDOM from 'react-dom';
|
||||
|
||||
export default class Portal extends Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
el: null,
|
||||
target: null,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.setState(
|
||||
{ el: document.createElement('div'), target: document.body },
|
||||
() => {
|
||||
this.state.target.appendChild(this.state.el);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.state.target && this.state.target.removeChild(this.state.el);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
|
||||
if (this.state.el) {
|
||||
return ReactDOM.createPortal(children, this.state.el);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/**
|
||||
* MIT License
|
||||
* Copyright (c) 2017 Ryan Florence
|
||||
* https://github.com/reactjs/react-modal/blob/master/LICENSE
|
||||
*
|
||||
* Take WAI-ARIA workaround for React Native Web
|
||||
*
|
||||
* Modified by Ray Andrew <raydreww@gmail.com>
|
||||
* For Modal React Native Web
|
||||
*
|
||||
* MIT License
|
||||
* Copyright (c) 2018 Ray Andrew
|
||||
* https://github.com/rayandrews/react-native-web-modal
|
||||
*/
|
||||
|
||||
import { canUseDOM } from './utils';
|
||||
|
||||
let globalElement = null;
|
||||
|
||||
export function assertNodeList(nodeList, selector) {
|
||||
if (!nodeList || !nodeList.length) {
|
||||
throw new Error(
|
||||
`modal-react-native-web: No elements were found for selector ${selector}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function setElement(element) {
|
||||
let useElement = element;
|
||||
if (typeof useElement === 'string' && canUseDOM) {
|
||||
const el = document.querySelectorAll(useElement);
|
||||
assertNodeList(el, useElement);
|
||||
useElement = 'length' in el ? el[0] : el;
|
||||
}
|
||||
globalElement = useElement || globalElement;
|
||||
return globalElement;
|
||||
}
|
||||
|
||||
export function validateElement(appElement) {
|
||||
if (!appElement && !globalElement) {
|
||||
console.warn(
|
||||
false,
|
||||
[
|
||||
'modal-react-native-web: App element is not defined.',
|
||||
'Please use `Modal.setAppElement(el)` or set `appElement={el}`.',
|
||||
"This is needed so screen readers don't see main content",
|
||||
'when modal is opened. It is not recommended, but you can opt-out',
|
||||
'by setting `ariaHideApp={false}`.',
|
||||
].join(' ')
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function hide(appElement) {
|
||||
if (validateElement(appElement)) {
|
||||
(appElement || globalElement).setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
}
|
||||
|
||||
export function show(appElement) {
|
||||
if (validateElement(appElement)) {
|
||||
(appElement || globalElement).removeAttribute('aria-hidden');
|
||||
}
|
||||
}
|
||||
|
||||
export function documentNotReadyOrSSRTesting() {
|
||||
globalElement = null;
|
||||
}
|
||||
|
||||
export function resetForTesting() {
|
||||
globalElement = null;
|
||||
}
|
||||
@@ -1,259 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Animated, Dimensions, Easing, Platform } from 'react-native';
|
||||
|
||||
import ModalPortal from './Portal';
|
||||
import { Portal } from 'react-native-paper';
|
||||
import * as ariaAppHider from './ariaAppHider';
|
||||
import { SafeHTMLElement } from './utils';
|
||||
import styles from './styles';
|
||||
|
||||
let ariaHiddenInstances = 0;
|
||||
|
||||
export default class Modal extends Component {
|
||||
static setAppElement(element) {
|
||||
ariaAppHider.setElement(element);
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
animationType: PropTypes.oneOf(['none', 'slide', 'fade']),
|
||||
transparent: PropTypes.bool,
|
||||
visible: PropTypes.bool,
|
||||
onRequestClose:
|
||||
Platform.isTV || Platform.OS === 'android'
|
||||
? PropTypes.func.isRequired
|
||||
: PropTypes.func,
|
||||
onShow: PropTypes.func,
|
||||
onDismiss: PropTypes.func,
|
||||
children: PropTypes.node.isRequired,
|
||||
ariaHideApp: PropTypes.bool,
|
||||
appElement: PropTypes.instanceOf(SafeHTMLElement),
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
animationType: 'none',
|
||||
transparent: false,
|
||||
visible: true,
|
||||
onShow: () => {},
|
||||
onRequestClose: () => {},
|
||||
onDismiss: () => {},
|
||||
ariaHideApp: true,
|
||||
appElement: null,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
animationSlide: null,
|
||||
animationFade: null,
|
||||
styleFade: { display: props.visible ? 'flex' : 'none' },
|
||||
opacityFade: new Animated.Value(0),
|
||||
slideTranslation: new Animated.Value(0),
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.visible) this.handleShow();
|
||||
}
|
||||
|
||||
componentWillReceiveProps({ visible }) {
|
||||
if (visible && !this.props.visible) this.handleShow();
|
||||
if (!visible && this.props.visible) this.handleClose();
|
||||
}
|
||||
|
||||
handleShow() {
|
||||
const { animationType, onShow, ariaHideApp, appElement } = this.props;
|
||||
|
||||
if (ariaHideApp) {
|
||||
ariaHiddenInstances += 1;
|
||||
ariaAppHider.hide(appElement);
|
||||
}
|
||||
|
||||
if (animationType === 'slide') {
|
||||
this.animateSlideIn(onShow);
|
||||
} else if (animationType === 'fade') {
|
||||
this.animateFadeIn(onShow);
|
||||
} else {
|
||||
onShow();
|
||||
}
|
||||
}
|
||||
|
||||
handleClose() {
|
||||
const { animationType, onDismiss, ariaHideApp, appElement } = this.props;
|
||||
|
||||
if (animationType === 'slide') {
|
||||
this.animateSlideOut(onDismiss);
|
||||
} else if (animationType === 'fade') {
|
||||
this.animateFadeOut(onDismiss);
|
||||
} else {
|
||||
onDismiss();
|
||||
}
|
||||
|
||||
if (ariaHideApp && ariaHiddenInstances > 0) {
|
||||
ariaHiddenInstances -= 1;
|
||||
|
||||
if (ariaHiddenInstances === 0) {
|
||||
ariaAppHider.show(appElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fade Animation Implementation
|
||||
animateFadeIn = callback => {
|
||||
if (this.state.animationFade) {
|
||||
this.state.animationFade.stop();
|
||||
}
|
||||
|
||||
const animationFade = Animated.timing(this.state.opacityFade, {
|
||||
toValue: 1,
|
||||
duration: 300,
|
||||
});
|
||||
|
||||
this.setState(
|
||||
{
|
||||
animationFade,
|
||||
},
|
||||
() => {
|
||||
requestAnimationFrame(() => {
|
||||
this.setState({ styleFade: { display: 'flex' } }, () =>
|
||||
this.state.animationFade.start(callback)
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
animateFadeOut = callback => {
|
||||
if (this.state.animationFade) {
|
||||
this.state.animationFade.stop();
|
||||
}
|
||||
|
||||
const animationFade = Animated.timing(this.state.opacityFade, {
|
||||
toValue: 0,
|
||||
duration: 300,
|
||||
});
|
||||
|
||||
this.setState(
|
||||
{
|
||||
animationFade,
|
||||
},
|
||||
() => {
|
||||
requestAnimationFrame(() => {
|
||||
this.state.animationFade.start(() => {
|
||||
this.setState(
|
||||
{
|
||||
styleFade: { display: 'none' },
|
||||
},
|
||||
callback
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
// End of Fade Animation Implementation
|
||||
|
||||
// Slide Animation Implementation
|
||||
animateSlideIn = callback => {
|
||||
if (this.state.animationSlide) {
|
||||
this.state.animationSlide.stop();
|
||||
}
|
||||
|
||||
const animationSlide = Animated.timing(this.state.slideTranslation, {
|
||||
toValue: 1,
|
||||
easing: Easing.out(Easing.poly(4)),
|
||||
duration: 300,
|
||||
});
|
||||
|
||||
this.setState(
|
||||
{
|
||||
animationSlide,
|
||||
},
|
||||
() => {
|
||||
requestAnimationFrame(() => {
|
||||
this.setState({ styleFade: { display: 'flex' } }, () =>
|
||||
this.state.animationSlide.start(callback)
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
animateSlideOut = callback => {
|
||||
if (this.state.animationSlide) {
|
||||
this.state.animationSlide.stop();
|
||||
}
|
||||
|
||||
const animationSlide = Animated.timing(this.state.slideTranslation, {
|
||||
toValue: 0,
|
||||
easing: Easing.in(Easing.poly(4)),
|
||||
duration: 300,
|
||||
});
|
||||
|
||||
this.setState(
|
||||
{
|
||||
animationSlide,
|
||||
},
|
||||
() => {
|
||||
requestAnimationFrame(() => {
|
||||
this.state.animationSlide.start(() => {
|
||||
this.setState(
|
||||
{
|
||||
styleFade: { display: 'none' },
|
||||
},
|
||||
callback
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
// End of Slide Animation Implementation
|
||||
|
||||
getAnimationStyle() {
|
||||
const { visible, animationType } = this.props;
|
||||
const { styleFade } = this.state;
|
||||
|
||||
if (animationType === 'slide') {
|
||||
return [
|
||||
{
|
||||
transform: [
|
||||
{
|
||||
translateY: this.state.slideTranslation.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [Dimensions.get('window').height, 0],
|
||||
extrapolate: 'clamp',
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
styleFade,
|
||||
];
|
||||
}
|
||||
if (animationType === 'fade') {
|
||||
return [{ opacity: this.state.opacityFade }, styleFade];
|
||||
}
|
||||
|
||||
return [styles[visible ? 'visible' : 'hidden']];
|
||||
}
|
||||
|
||||
render() {
|
||||
const { transparent, children } = this.props;
|
||||
|
||||
const transparentStyle = transparent
|
||||
? styles.bgTransparent
|
||||
: styles.bgNotTransparent;
|
||||
const animationStyle = this.getAnimationStyle();
|
||||
|
||||
return (
|
||||
<Portal.Host>
|
||||
<Animated.View
|
||||
aria-modal="true"
|
||||
style={[styles.baseStyle, transparentStyle, animationStyle]}>
|
||||
{children}
|
||||
</Animated.View>
|
||||
</Portal.Host>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { StyleSheet } from 'react-native';
|
||||
|
||||
export default StyleSheet.create({
|
||||
baseStyle: {
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
zIndex: 9999,
|
||||
},
|
||||
bgTransparent: {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
bgNotTransparent: {
|
||||
backgroundColor: '#ffffff',
|
||||
},
|
||||
hidden: {
|
||||
display: 'none',
|
||||
},
|
||||
visible: {
|
||||
display: 'flex',
|
||||
},
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* MIT License
|
||||
* Copyright (c) 2017 Ryan Florence
|
||||
* https://github.com/reactjs/react-modal/blob/master/LICENSE
|
||||
*
|
||||
* Take WAI-ARIA workaround for React Native Web
|
||||
*
|
||||
* Modified by Ray Andrew <raydreww@gmail.com>
|
||||
* For Modal React Native Web
|
||||
*
|
||||
* MIT License
|
||||
* Copyright (c) 2018 Ray Andrew
|
||||
* https://github.com/rayandrews/react-native-web-modal
|
||||
*/
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export const canUseDOM = !!(
|
||||
typeof window !== 'undefined' &&
|
||||
window.document &&
|
||||
window.document.createElement
|
||||
);
|
||||
|
||||
export const SafeHTMLElement = canUseDOM ? HTMLElement : PropTypes.any;
|
||||
@@ -7,25 +7,33 @@
|
||||
"ios": "react-native run-ios",
|
||||
"macos": "react-native run-macos",
|
||||
"start": "react-native start",
|
||||
"start-webpack": "webpack serve",
|
||||
"test": "jest",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-native": "0.68.0-rc.1",
|
||||
"react-native-macos": "^0.66.0-0",
|
||||
"react-native-svg": "link:../"
|
||||
"react-native-svg": "link:../",
|
||||
"react-native-web": "^0.17.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.9",
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@react-native-community/eslint-config": "^2.0.0",
|
||||
"babel-jest": "^26.6.3",
|
||||
"babel-loader": "^8.2.2",
|
||||
"eslint": "^7.32.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"jest": "^26.6.3",
|
||||
"metro-react-native-babel-preset": "^0.67.0",
|
||||
"react-native-gradle-plugin": "^0.0.4",
|
||||
"react-test-renderer": "17.0.2"
|
||||
"react-test-renderer": "17.0.2",
|
||||
"webpack": "^5.69.1",
|
||||
"webpack-cli": "^4.9.2",
|
||||
"webpack-dev-server": "^4.7.4"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "react-native"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import React, {Component} from 'react';
|
||||
import {
|
||||
Dimensions,
|
||||
StyleSheet,
|
||||
@@ -14,12 +14,8 @@ import {
|
||||
TouchableHighlight,
|
||||
TouchableOpacity,
|
||||
} from 'react-native';
|
||||
import Modal from './Modal';
|
||||
import {
|
||||
Svg,
|
||||
Circle,
|
||||
Line,
|
||||
} from './Svg';
|
||||
import {Modal} from 'react-native';
|
||||
import {Svg, Circle, Line} from 'react-native-svg';
|
||||
|
||||
import * as examples from './examples';
|
||||
|
||||
@@ -124,7 +120,11 @@ const initialState = {
|
||||
};
|
||||
|
||||
export default class SvgExample extends Component {
|
||||
state = initialState;
|
||||
state: {
|
||||
content: React.ReactNode;
|
||||
modal: boolean;
|
||||
scroll?: boolean;
|
||||
} = initialState;
|
||||
|
||||
show = name => {
|
||||
if (this.state.modal) {
|
||||
@@ -202,7 +202,6 @@ export default class SvgExample extends Component {
|
||||
<Text style={styles.welcome}>SVG library for React Apps</Text>
|
||||
<View style={styles.contentContainer}>{this.getExamples()}</View>
|
||||
<Modal
|
||||
ariaHideApp={false}
|
||||
transparent={false}
|
||||
animationType="fade"
|
||||
visible={this.state.modal}
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
@@ -35,4 +35,3 @@ export {
|
||||
Reusable,
|
||||
PanResponder,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, {Component} from 'react';
|
||||
import {Svg, Circle} from '../Svg';
|
||||
import {Svg, Circle} from 'react-native-svg';
|
||||
|
||||
class CircleExample extends Component {
|
||||
static title = 'Circle';
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
RadialGradient,
|
||||
Stop,
|
||||
ClipPath,
|
||||
} from '../Svg';
|
||||
} from 'react-native-svg';
|
||||
|
||||
class ClipPathElement extends Component {
|
||||
static title = 'Clip by set clip-path with a path data';
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, {Component} from 'react';
|
||||
import {Svg, Ellipse} from '../Svg';
|
||||
import {Svg, Ellipse} from 'react-native-svg';
|
||||
|
||||
class EllipseExample extends Component {
|
||||
static title = 'Ellipse';
|
||||
@@ -1,13 +1,5 @@
|
||||
import React, {Component} from 'react';
|
||||
import {
|
||||
Svg,
|
||||
Circle,
|
||||
G,
|
||||
Text,
|
||||
Line,
|
||||
Rect,
|
||||
Use,
|
||||
} from '../Svg';
|
||||
import {Svg, Circle, G, Text, Line, Rect, Use} from 'react-native-svg';
|
||||
|
||||
class GExample extends Component {
|
||||
static title = 'G children props inherit';
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
LinearGradient,
|
||||
RadialGradient,
|
||||
Stop,
|
||||
} from '../Svg';
|
||||
} from 'react-native-svg';
|
||||
|
||||
class LinearGradientHorizontal extends Component {
|
||||
static title =
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, {Component} from 'react';
|
||||
import {Svg, Circle, Text, Rect, Defs, ClipPath} from '../Svg';
|
||||
|
||||
import Image from '../components/SnackImageCompat';
|
||||
import {Platform} from 'react-native';
|
||||
import {Svg, Circle, Text, Rect, Defs, ClipPath, Image} from 'react-native-svg';
|
||||
|
||||
class ImageExample extends Component {
|
||||
static title = 'Draw Image with preserveAspectRatio prop';
|
||||
@@ -22,7 +21,7 @@ class ImageExample extends Component {
|
||||
height="90%"
|
||||
preserveAspectRatio="xMidYMid slice"
|
||||
opacity="0.5"
|
||||
href={require('../image.jpg')}
|
||||
href={require('../assets/image.jpg')}
|
||||
clipPath="url(#image-clip)"
|
||||
/>
|
||||
<Text
|
||||
@@ -55,7 +54,7 @@ class ClipImage extends Component {
|
||||
y="5%"
|
||||
width="90%"
|
||||
height="90%"
|
||||
href={require('../image.jpg')}
|
||||
href={require('../assets/image.jpg')}
|
||||
opacity="0.6"
|
||||
clipPath="url(#clip-image)"
|
||||
/>
|
||||
@@ -83,7 +82,8 @@ class DataURI extends Component {
|
||||
y="5%"
|
||||
width="90%"
|
||||
height="90%"
|
||||
href={{uri: dataUriExample}}
|
||||
// @ts-expect-error
|
||||
href={Platform.OS === 'web' ? dataUriExample : {uri: dataUriExample}}
|
||||
opacity="0.6"
|
||||
/>
|
||||
</Svg>
|
||||
@@ -98,7 +98,7 @@ const icon = (
|
||||
y="5%"
|
||||
width="90%"
|
||||
height="90%"
|
||||
href={require('../image.jpg')}
|
||||
href={require('../assets/image.jpg')}
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, {Component} from 'react';
|
||||
import {Svg, Line} from '../Svg';
|
||||
import {Svg, Line} from 'react-native-svg';
|
||||
|
||||
class LineExample extends Component {
|
||||
static title = 'Line';
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
Animated,
|
||||
TouchableWithoutFeedback,
|
||||
} from 'react-native';
|
||||
import {Svg, G, Text, Path, Polyline, Line} from '../Svg';
|
||||
import {Svg, G, Text, Path, Polyline, Line} from 'react-native-svg';
|
||||
|
||||
const AnimatedSvg = Animated.createAnimatedComponent(Svg);
|
||||
|
||||
@@ -23,7 +23,7 @@ class PanExample extends PureComponent {
|
||||
offset = flatOffset;
|
||||
});
|
||||
const {panHandlers} = PanResponder.create({
|
||||
onStartShouldSetPanResponder: (evt, gestureState) => true,
|
||||
onStartShouldSetPanResponder: () => true,
|
||||
onMoveShouldSetPanResponderCapture: () => true,
|
||||
onPanResponderGrant: () => {
|
||||
xy.setOffset(offset);
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, {Component} from 'react';
|
||||
import {Svg, Circle, G, Text, Path} from '../Svg';
|
||||
import {Svg, Circle, G, Text, Path} from 'react-native-svg';
|
||||
|
||||
class PathExample extends Component {
|
||||
static title = 'Path';
|
||||
@@ -68,9 +68,9 @@ class BezierCurve extends Component {
|
||||
fill="none"
|
||||
/>
|
||||
<G stroke="purple" strokeWidth="3" fill="purple">
|
||||
<Circle cx="100" cy="350" r="3"/>
|
||||
<Circle cx="250" cy="50" r="3"/>
|
||||
<Circle cx="400" cy="350" r="3"/>
|
||||
<Circle cx="100" cy="350" r="3" />
|
||||
<Circle cx="250" cy="50" r="3" />
|
||||
<Circle cx="400" cy="350" r="3" />
|
||||
</G>
|
||||
<G fontSize="30" fill="black" stroke="none" textAnchor="middle">
|
||||
<Text x="100" y="350" dx="-30">
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, {Component} from 'react';
|
||||
import {Svg, G, Path, Polygon} from '../Svg';
|
||||
import {Svg, G, Path, Polygon} from 'react-native-svg';
|
||||
|
||||
class PolygonExample extends Component {
|
||||
static title = 'The following example creates a polygon with three sides';
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, {Component} from 'react';
|
||||
import {Svg, Polyline} from '../Svg';
|
||||
import {Svg, Polyline} from 'react-native-svg';
|
||||
|
||||
class PolylineExample extends Component {
|
||||
static title =
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, {Component} from 'react';
|
||||
import {Svg, Rect} from '../Svg';
|
||||
import {Svg, Rect} from 'react-native-svg';
|
||||
|
||||
class RectExample extends Component {
|
||||
static title = 'Rect';
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
RadialGradient,
|
||||
Stop,
|
||||
ClipPath,
|
||||
} from '../Svg';
|
||||
} from 'react-native-svg';
|
||||
|
||||
class UseExample extends Component {
|
||||
static title = 'Reuse svg code';
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
RadialGradient,
|
||||
Stop,
|
||||
ClipPath,
|
||||
} from '../Svg';
|
||||
} from 'react-native-svg';
|
||||
|
||||
class StrokeExample extends Component {
|
||||
static title =
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, {Component} from 'react';
|
||||
import {StyleSheet, View, Image} from 'react-native';
|
||||
import {Svg, Circle, G, Path, Line, Rect} from '../Svg';
|
||||
import {Svg, Circle, G, Path, Line, Rect} from 'react-native-svg';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
Defs,
|
||||
LinearGradient,
|
||||
Stop,
|
||||
} from '../Svg';
|
||||
} from 'react-native-svg';
|
||||
|
||||
class TextExample extends Component {
|
||||
static title = 'Text';
|
||||
@@ -1,6 +1,15 @@
|
||||
import React, {Component} from 'react';
|
||||
|
||||
import {Svg, Circle, G, Text, Path, Rect, Defs, ClipPath} from '../Svg';
|
||||
import {
|
||||
Svg,
|
||||
Circle,
|
||||
G,
|
||||
Text,
|
||||
Path,
|
||||
Rect,
|
||||
Defs,
|
||||
ClipPath,
|
||||
} from 'react-native-svg';
|
||||
|
||||
class PressExample extends Component {
|
||||
static title =
|
||||
55
Example/webpack.config.js
Normal file
@@ -0,0 +1,55 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const fromRoot = _ => path.resolve(__dirname, _);
|
||||
|
||||
module.exports = {
|
||||
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
|
||||
entry: fromRoot('index.js'),
|
||||
output: {
|
||||
path: fromRoot('dist'),
|
||||
filename: 'bundle.web.js',
|
||||
},
|
||||
devServer: {
|
||||
static: {directory: fromRoot('dist')},
|
||||
devMiddleware: {publicPath: '/'},
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(jsx?|tsx?)$/,
|
||||
use: {loader: 'babel-loader'},
|
||||
include: [
|
||||
fromRoot('index.js'),
|
||||
fromRoot('src'),
|
||||
fromRoot('node_modules/react-native-svg'),
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(gif|jpe?g|png)$/i,
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader',
|
||||
options: {esModule: false},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
symlinks: false,
|
||||
alias: {
|
||||
'react-native$': 'react-native-web',
|
||||
},
|
||||
extensions: [
|
||||
'.web.ts',
|
||||
'.ts',
|
||||
'.web.tsx',
|
||||
'.tsx',
|
||||
'.web.js',
|
||||
'.js',
|
||||
'.web.jsx',
|
||||
'.jsx',
|
||||
],
|
||||
},
|
||||
};
|
||||
1576
Example/yarn.lock
@@ -1,5 +1,6 @@
|
||||
module.exports = {
|
||||
arrowParens: 'avoid',
|
||||
jsxBracketSameLine: true,
|
||||
bracketSameLine: true,
|
||||
bracketSpacing: false,
|
||||
singleQuote: true,
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
import { NumberArray, NumberProp } from './lib/extract/types';
|
||||
import SvgTouchableMixin from './lib/SvgTouchableMixin';
|
||||
import { resolve } from './lib/resolve';
|
||||
import { getHasTouchableProperty } from './lib/util';
|
||||
|
||||
const createElement = cE || ucE;
|
||||
|
||||
@@ -61,6 +60,9 @@ interface BaseProps {
|
||||
style: Iterable<{}>;
|
||||
}
|
||||
|
||||
const hasTouchableProperty = (props: BaseProps) =>
|
||||
props.onPress || props.onPressIn || props.onPressOut || props.onLongPress;
|
||||
|
||||
/**
|
||||
* `react-native-svg` supports additional props that aren't defined in the spec.
|
||||
* This function replaces them in a spec conforming manner.
|
||||
@@ -91,7 +93,7 @@ const prepare = <T extends BaseProps>(
|
||||
// @ts-ignore
|
||||
...rest
|
||||
} = props;
|
||||
const hasTouchableProperty = getHasTouchableProperty(props);
|
||||
|
||||
const clean: {
|
||||
onStartShouldSetResponder?: (e: GestureResponderEvent) => boolean;
|
||||
onResponderMove?: (e: GestureResponderEvent) => void;
|
||||
@@ -103,7 +105,7 @@ const prepare = <T extends BaseProps>(
|
||||
style?: {};
|
||||
ref?: {};
|
||||
} = {
|
||||
...(hasTouchableProperty
|
||||
...(hasTouchableProperty(props)
|
||||
? {
|
||||
onStartShouldSetResponder:
|
||||
self.touchableHandleStartShouldSetResponder,
|
||||
@@ -241,11 +243,12 @@ export class WebShape<
|
||||
) => boolean;
|
||||
constructor(props: P, context: C) {
|
||||
super(props, context);
|
||||
const hasTouchableProperty = getHasTouchableProperty(props);
|
||||
|
||||
|
||||
// Do not attach touchable mixin handlers if SVG element doesn't have a touchable prop
|
||||
if (hasTouchableProperty) SvgTouchableMixin(this);
|
||||
|
||||
if (hasTouchableProperty(props)) {
|
||||
SvgTouchableMixin(this);
|
||||
}
|
||||
|
||||
this._remeasureMetricsOnActivation = remeasure.bind(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ function hasAttrib(elem: XmlAST, name: string): boolean {
|
||||
// finds the first node in the array that matches the test predicate, or one
|
||||
// of its children
|
||||
function findOne(
|
||||
predicate: (v: XmlAST)=> boolean,
|
||||
predicate: (v: XmlAST) => boolean,
|
||||
elems: Array<XmlAST | string>,
|
||||
): XmlAST | null {
|
||||
let elem: XmlAST | null = null;
|
||||
@@ -637,8 +637,10 @@ export const inlineStyles: Middleware = function inlineStyles(
|
||||
const selectorStr = csstree.generate(item.data);
|
||||
try {
|
||||
// apply <style/> to matched elements
|
||||
const matched = cssSelect(selectorStr, document, cssSelectOpts).map(initStyle);
|
||||
|
||||
const matched = cssSelect(selectorStr, document, cssSelectOpts).map(
|
||||
initStyle,
|
||||
);
|
||||
|
||||
if (matched.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
6
src/index.d.ts
vendored
@@ -1,6 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import * as ReactNative from 'react-native';
|
||||
import { GestureResponderEvent, TransformsStyle, OpaqueColorValue } from 'react-native';
|
||||
import {
|
||||
GestureResponderEvent,
|
||||
TransformsStyle,
|
||||
OpaqueColorValue,
|
||||
} from 'react-native';
|
||||
|
||||
// Common props
|
||||
export type NumberProp = string | number;
|
||||
|
||||
@@ -230,7 +230,8 @@ export type EllipseType = React.ComponentClass<EllipseProps>;
|
||||
export type GProps = {
|
||||
opacity?: NumberProp,
|
||||
...
|
||||
} & CommonPathProps;
|
||||
} & CommonPathProps &
|
||||
FontProps;
|
||||
declare export var G: React.ComponentClass<GProps>;
|
||||
export type GType = React.ComponentClass<GProps>;
|
||||
export interface ForeignObjectProps {
|
||||
|
||||
@@ -11,10 +11,4 @@ export function pickNotNil(object: { [prop: string]: unknown }) {
|
||||
return result;
|
||||
}
|
||||
|
||||
export const getHasTouchableProperty = (props: any) => {
|
||||
return (
|
||||
props.onPress || props.onPressIn || props.onPressOut || props.onLongPress
|
||||
);
|
||||
};
|
||||
|
||||
export const idPattern = /#([^)]+)\)?$/;
|
||||
|
||||