Add web example (#1717)

Added web example to `Example`
This commit is contained in:
Mathieu Acthernoene
2022-03-07 12:16:50 +01:00
committed by GitHub
parent 7afd23665f
commit 2fa9645cf6
52 changed files with 1688 additions and 1554 deletions

1
.gitignore vendored
View File

@@ -18,7 +18,6 @@ xcuserdata
DerivedData
*.hmap
*.ipa
gradle
project.xcworkspace
# Android/IJ

8
.prettierrc.js Normal file
View File

@@ -0,0 +1,8 @@
module.exports = {
arrowParens: 'avoid',
jsxBracketSameLine: true,
bracketSameLine: true,
bracketSpacing: false,
singleQuote: true,
trailingComma: 'all',
};

View File

@@ -1,5 +1,6 @@
module.exports = {
arrowParens: 'avoid',
jsxBracketSameLine: true,
bracketSameLine: true,
bracketSpacing: false,
singleQuote: true,

View File

@@ -1,2 +0,0 @@
import { Modal } from 'react-native';
export default Modal;

View File

@@ -1,2 +0,0 @@
import Modal from './modal-enhanced-react-native-web';
export default Modal;

View File

@@ -1 +0,0 @@
export * from 'react-native-svg'

View File

@@ -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;

View File

@@ -1,2 +0,0 @@
import { Image } from 'react-native-svg';
export default Image;

View File

@@ -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
View 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>

View File

@@ -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});
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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',
},
});

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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>
);
}
}

View File

@@ -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',
},
});

View File

@@ -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;

View File

@@ -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"

View File

@@ -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}

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -35,4 +35,3 @@ export {
Reusable,
PanResponder,
};

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -10,7 +10,7 @@ import {
LinearGradient,
RadialGradient,
Stop,
} from '../Svg';
} from 'react-native-svg';
class LinearGradientHorizontal extends Component {
static title =

View File

@@ -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>
);

View File

@@ -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';

View File

@@ -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);

View File

@@ -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';

View File

@@ -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';

View File

@@ -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 =

View File

@@ -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';

View File

@@ -12,7 +12,7 @@ import {
RadialGradient,
Stop,
ClipPath,
} from '../Svg';
} from 'react-native-svg';
class UseExample extends Component {
static title = 'Reuse svg code';

View File

@@ -11,7 +11,7 @@ import {
RadialGradient,
Stop,
ClipPath,
} from '../Svg';
} from 'react-native-svg';
class StrokeExample extends Component {
static title =

View File

@@ -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: {

View File

@@ -10,7 +10,7 @@ import {
Defs,
LinearGradient,
Stop,
} from '../Svg';
} from 'react-native-svg';
class TextExample extends Component {
static title = 'Text';

View File

@@ -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
View 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',
],
},
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,6 @@
module.exports = {
arrowParens: 'avoid',
jsxBracketSameLine: true,
bracketSameLine: true,
bracketSpacing: false,
singleQuote: true,

View File

@@ -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,10 +243,11 @@ 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);
}

View File

@@ -637,7 +637,9 @@ 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
View File

@@ -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;

View File

@@ -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 {

View File

@@ -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 = /#([^)]+)\)?$/;