refactor: typescript

This commit is contained in:
Mikael Sand
2019-09-09 05:44:29 +03:00
parent 4100730d2b
commit 4f3e34e556
83 changed files with 11224 additions and 619 deletions

View File

@@ -2,7 +2,7 @@
Example/examples/
Example/android/
Example/ios/
screenShoots/
screenshots/
android/
ios/
lib/extract/transform.js
src/lib/extract/transform.js

View File

@@ -1 +1,4 @@
module.exports = { extends: "@react-native-community", "rules": { "no-bitwise": 0 } };
module.exports = {
extends: '@react-native-community',
rules: { 'no-bitwise': 0, '@typescript-eslint/no-explicit-any': 2 },
};

1
.gitignore vendored
View File

@@ -45,3 +45,4 @@ cn-doc.md
# experimental code
#
experimental/
/lib/

View File

@@ -3,7 +3,7 @@ node_modules/
Example/
experimental/
android/build/
screenShoots/
screenshots/
idl/
# OSX

View File

@@ -171,8 +171,10 @@ and run `pod install` from `ios` folder
### Troubleshooting
#### Problems with Proguard
When Proguard is enabled (which it is by default for Android release builds), it causes runtine error
To avoid this, add an exception to `android/app/proguard-rules.pro`:
```bash
-keep public class com.horcrux.svg.** {*;}
```
@@ -223,7 +225,7 @@ If you suspect that you've found a spec conformance bug, then you can test using
Here's a simple example. To render output like this:
![SVG example](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenShoots/svg.png)
![SVG example](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenshots/svg.png)
Use the following code:
@@ -464,7 +466,7 @@ Colors set in the Svg element are inherited by its children:
</Svg>
```
![Pencil](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenShoots/pencil.png)
![Pencil](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenshots/pencil.png)
Code explanation:
@@ -490,7 +492,7 @@ The <Rect> element is used to create a rectangle and variations of a rectangle s
</Svg>
```
![Rect](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenShoots/rect.png)
![Rect](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenshots/rect.png)
Code explanation:
@@ -508,7 +510,7 @@ The <Circle> element is used to create a circle:
</Svg>
```
![Rect](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenShoots/circle.png)
![Rect](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenshots/circle.png)
Code explanation:
@@ -535,7 +537,7 @@ An ellipse is closely related to a circle. The difference is that an ellipse has
</Svg>
```
![Rect](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenShoots/ellipse.png)
![Rect](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenshots/ellipse.png)
Code explanation:
@@ -554,7 +556,7 @@ The <Line> element is an SVG basic shape, used to create a line connecting two p
</Svg>
```
![Rect](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenShoots/line.png)
![Rect](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenshots/line.png)
Code explanation:
@@ -578,7 +580,7 @@ The <Polygon> element is used to create a graphic that contains at least three s
</Svg>
```
![Rect](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenShoots/polygon.png)
![Rect](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenshots/polygon.png)
Code explanation:
@@ -599,7 +601,7 @@ The <Polyline> element is used to create any shape that consists of only straigh
</Svg>
```
![Rect](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenShoots/polyline.png)
![Rect](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenshots/polyline.png)
Code explanation:
@@ -634,7 +636,7 @@ The following commands are available for path data:
</Svg>
```
![Rect](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenShoots/path.png)
![Rect](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenshots/path.png)
#### Text
@@ -656,7 +658,7 @@ The <Text> element is used to define text.
</Svg>
```
![Text](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenShoots/text.png)
![Text](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenshots/text.png)
#### TSpan
@@ -689,7 +691,7 @@ The <TSpan> element is used to draw multiple lines of text in SVG. Rather than h
</Svg>
```
![TSpan](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenShoots/tspan.png)
![TSpan](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenshots/tspan.png)
#### TextPath
@@ -714,7 +716,7 @@ In addition to text drawn in a straight line, SVG also includes the ability to p
</Svg>
```
![TextPath](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenShoots/text-path.png)
![TextPath](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenshots/text-path.png)
#### G
@@ -734,7 +736,7 @@ The <G> element is a container used to group other SVG elements. Transformations
</Svg>
```
![G](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenShoots/g.png)
![G](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenshots/g.png)
#### Use
@@ -762,7 +764,7 @@ Before the <G> element can be referenced, it must have an ID set on it via its i
The <Use> element specifies where to show the reused shapes via its x and y props. Notice that the shapes inside the <G> element are located at 0,0. That is done because their position is added to the position specified in the <Use> element.
![use](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenShoots/use.png)
![use](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenshots/use.png)
#### Symbol
@@ -788,7 +790,7 @@ The SVG <Symbol> element is used to define reusable symbols. The shapes nested i
</Svg>
```
![Symbol](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenShoots/symbol.png)
![Symbol](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenshots/symbol.png)
#### Defs
@@ -831,7 +833,7 @@ The <Image> element allows a raster image to be included in an Svg componenet.
</Svg>
```
![Image](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenShoots/image.png)
![Image](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenshots/image.png)
#### ClipPath
@@ -883,7 +885,7 @@ The <ClipPath> SVG element defines a clipping path. A clipping path is used/refe
</Svg>
```
![ClipPath](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenShoots/clip-path.png)
![ClipPath](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenshots/clip-path.png)
#### LinearGradient
@@ -915,7 +917,7 @@ Code explanation:
- The color range for a gradient can be composed of two or more colors. Each color is specified with a <Stop> tag. The offset prop is used to define where the gradient color begin and end
- The fill prop links the ellipse element to the gradient
![LinearGradient](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenShoots/lineargradient.png)
![LinearGradient](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenshots/lineargradient.png)
_NOTICE:_
LinearGradient also supports percentage as prop:
@@ -961,7 +963,7 @@ Code explanation:
- The color range for a gradient can be composed of two or more colors. Each color is specified with a <stop> tag. The offset prop is used to define where the gradient color begin and end
- The fill prop links the ellipse element to the gradient
![RadialGradient](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenShoots/radialgradient.png)
![RadialGradient](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenshots/radialgradient.png)
#### Mask
@@ -1084,7 +1086,7 @@ You can use these events to provide interactivity to your react-native-svg compo
/>
```
![TouchEvents](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenShoots/touchevents.gif)
![TouchEvents](https://raw.githubusercontent.com/react-native-community/react-native-svg/master/screenshots/touchevents.gif)
For more examples of touch in action, checkout the [TouchEvents.js examples](https://github.com/magicismight/react-native-svg-example/blob/master/examples/TouchEvents.js).

View File

@@ -1,50 +0,0 @@
import React from 'react';
import { Image, requireNativeComponent } from 'react-native';
import { meetOrSliceTypes, alignEnum } from '../lib/extract/extractViewBox';
import extractProps, { propsAndStyles } from '../lib/extract/extractProps';
import Shape from './Shape';
const spacesRegExp = /\s+/;
export default class SvgImage extends Shape {
static displayName = 'Image';
static defaultProps = {
x: 0,
y: 0,
width: 0,
height: 0,
preserveAspectRatio: 'xMidYMid meet',
};
render() {
const { props } = this;
const {
preserveAspectRatio,
x,
y,
width,
height,
xlinkHref,
href = xlinkHref,
} = props;
const modes = preserveAspectRatio.trim().split(spacesRegExp);
return (
<RNSVGImage
ref={this.refMethod}
{...extractProps({ ...propsAndStyles(props), x: null, y: null }, this)}
x={x}
y={y}
width={width}
height={height}
meetOrSlice={meetOrSliceTypes[modes[1]] || 0}
align={alignEnum[modes[0]] || 'xMidYMid'}
src={Image.resolveAssetSource(
typeof href === 'string' ? { uri: href } : href,
)}
/>
);
}
}
const RNSVGImage = requireNativeComponent('RNSVGImage');

View File

@@ -1,28 +0,0 @@
import { Component } from 'react';
import SvgTouchableMixin from '../lib/SvgTouchableMixin';
const touchKeys = Object.keys(SvgTouchableMixin);
const touchVals = touchKeys.map(key => SvgTouchableMixin[key]);
const numTouchKeys = touchKeys.length;
export default class Shape extends Component {
constructor() {
super(...arguments);
for (let i = 0; i < numTouchKeys; i++) {
const key = touchKeys[i];
const val = touchVals[i];
if (typeof val === 'function') {
this[key] = val.bind(this);
} else {
this[key] = val;
}
}
this.state = this.touchableGetInitialState();
}
refMethod = ref => {
this.root = ref;
};
setNativeProps = props => {
this.root.setNativeProps(props);
};
}

View File

@@ -1,98 +0,0 @@
import { Touchable } from 'react-native';
const PRESS_RETENTION_OFFSET = { top: 20, left: 20, right: 20, bottom: 30 };
export default {
...Touchable.Mixin,
touchableHandleStartShouldSetResponder: function(e) {
if (this.props.onStartShouldSetResponder) {
return this.props.onStartShouldSetResponder(e);
} else {
return Touchable.Mixin.touchableHandleStartShouldSetResponder.call(
this,
e,
);
}
},
touchableHandleResponderTerminationRequest: function(e) {
if (this.props.onResponderTerminationRequest) {
return this.props.onResponderTerminationRequest(e);
} else {
return Touchable.Mixin.touchableHandleResponderTerminationRequest.call(
this,
e,
);
}
},
touchableHandleResponderGrant: function(e) {
if (this.props.onResponderGrant) {
return this.props.onResponderGrant(e);
} else {
return Touchable.Mixin.touchableHandleResponderGrant.call(this, e);
}
},
touchableHandleResponderMove: function(e) {
if (this.props.onResponderMove) {
return this.props.onResponderMove(e);
} else {
return Touchable.Mixin.touchableHandleResponderMove.call(this, e);
}
},
touchableHandleResponderRelease: function(e) {
if (this.props.onResponderRelease) {
return this.props.onResponderRelease(e);
} else {
return Touchable.Mixin.touchableHandleResponderRelease.call(this, e);
}
},
touchableHandleResponderTerminate: function(e) {
if (this.props.onResponderTerminate) {
return this.props.onResponderTerminate(e);
} else {
return Touchable.Mixin.touchableHandleResponderTerminate.call(this, e);
}
},
touchableHandlePress: function(e) {
this.props.onPress && this.props.onPress(e);
},
touchableHandleActivePressIn: function(e) {
this.props.onPressIn && this.props.onPressIn(e);
},
touchableHandleActivePressOut: function(e) {
this.props.onPressOut && this.props.onPressOut(e);
},
touchableHandleLongPress: function(e) {
this.props.onLongPress && this.props.onLongPress(e);
},
touchableGetPressRectOffset: function() {
return this.props.pressRetentionOffset || PRESS_RETENTION_OFFSET;
},
touchableGetHitSlop: function() {
return this.props.hitSlop;
},
touchableGetHighlightDelayMS: function() {
return this.props.delayPressIn || 0;
},
touchableGetLongPressDelayMS: function() {
return this.props.delayLongPress === 0
? 0
: this.props.delayLongPress || 500;
},
touchableGetPressOutDelayMS: function() {
return this.props.delayPressOut || 0;
},
};

View File

@@ -1,4 +0,0 @@
export default function extractOpacity(opacity) {
const value = +opacity;
return typeof value !== 'number' || isNaN(value) ? 1 : value;
}

View File

@@ -1,4 +0,0 @@
export default {
objectBoundingBox: 0,
userSpaceOnUse: 1,
};

9999
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,43 +1,69 @@
{
"version": "9.8.5",
"name": "react-native-svg",
"description": "SVG library for react-native",
"homepage": "https://github.com/react-native-community/react-native-svg",
"repository": {
"type": "git",
"url": "https://github.com/react-native-community/react-native-svg"
},
"license": "MIT",
"main": "./index",
"keywords": [
"react-component",
"react-native",
"ios",
"android",
"SVG",
"ART",
"VML",
"gradient"
],
"scripts": {
"lint": "eslint ./",
"format": "prettier index.js index.web.js xml.js README.md './{elements,lib}/*.js' './lib/extract/e*.js' --write",
"peg": "pegjs -o ./lib/extract/transform.js ./lib/extract/transform.peg"
},
"peerDependencies": {
"react": "*",
"react-native": ">=0.50.0"
},
"dependencies": {},
"devDependencies": {
"@react-native-community/eslint-config": "^0.0.5",
"babel-eslint": "^10.0.3",
"eslint": "^6.2.2",
"eslint-plugin-prettier": "^3.1.0",
"eslint-plugin-react": "^7.14.3",
"pegjs": "^0.10.0",
"prettier": "^1.18.2",
"react": "^16.9.0"
},
"nativePackage": true
"version": "9.8.5",
"name": "react-native-svg",
"description": "SVG library for react-native",
"homepage": "https://github.com/react-native-community/react-native-svg",
"repository": {
"type": "git",
"url": "https://github.com/react-native-community/react-native-svg"
},
"license": "MIT",
"main": "lib/commonjs/index.js",
"module": "lib/module/index.js",
"react-native": "src/index.js",
"types": "lib/typescript/src/index.d.ts",
"files": [
"lib/",
"src/"
],
"@react-native-community/bob": {
"source": "src",
"output": "lib",
"targets": [
"commonjs",
"module",
"typescript"
]
},
"keywords": [
"react-component",
"react-native",
"ios",
"android",
"SVG",
"ART",
"VML",
"gradient"
],
"scripts": {
"lint": "eslint --ext .ts,.tsx src",
"format": "prettier README.md './src/**/*.{ts,tsx}' src/index.d.ts --write",
"peg": "pegjs -o src/lib/extract/transform.js ./src/lib/extract/transform.peg",
"tsc": "tsc --noEmit",
"test": "yarn lint && yarn tsc",
"prepare": "bob build",
"semantic-release": "semantic-release"
},
"peerDependencies": {
"react": "*",
"react-native": ">=0.50.0"
},
"dependencies": {},
"devDependencies": {
"@react-native-community/bob": "^0.7.0",
"@react-native-community/eslint-config": "^0.0.5",
"@types/react": "^16.9.2",
"@types/react-native": "^0.60.12",
"babel-eslint": "^10.0.3",
"eslint": "^6.2.2",
"eslint-plugin-prettier": "^3.1.0",
"eslint-plugin-react": "^7.14.3",
"pegjs": "^0.10.0",
"prettier": "^1.18.2",
"react": "^16.9.0",
"semantic-release": "^15.13.24",
"semantic-release-cli": "^5.2.1",
"typescript": "^3.6.2"
},
"nativePackage": true
}

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 851 KiB

After

Width:  |  Height:  |  Size: 851 KiB

View File

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 123 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@@ -1,9 +1,14 @@
import React from 'react';
import { requireNativeComponent } from 'react-native';
import extractProps, { propsAndStyles } from '../lib/extract/extractProps';
import { NumberProp } from '../lib/extract/types';
import Shape from './Shape';
export default class Circle extends Shape {
export default class Circle extends Shape<{
cx?: NumberProp;
cy?: NumberProp;
r?: NumberProp;
}> {
static displayName = 'Circle';
static defaultProps = {

View File

@@ -3,7 +3,11 @@ import { requireNativeComponent } from 'react-native';
import extractClipPath from '../lib/extract/extractClipPath';
import Shape from './Shape';
export default class ClipPath extends Shape {
export default class ClipPath extends Shape<{
id?: string;
clipPath?: string;
clipRule?: 'evenodd' | 'nonzero';
}> {
static displayName = 'ClipPath';
render() {

View File

@@ -1,9 +1,15 @@
import React from 'react';
import { requireNativeComponent } from 'react-native';
import extractProps, { propsAndStyles } from '../lib/extract/extractProps';
import { NumberProp } from '../lib/extract/types';
import Shape from './Shape';
export default class Ellipse extends Shape {
export default class Ellipse extends Shape<{
cx?: NumberProp;
cy?: NumberProp;
rx?: NumberProp;
ry?: NumberProp;
}> {
static displayName = 'Ellipse';
static defaultProps = {

View File

@@ -3,17 +3,22 @@ import { requireNativeComponent } from 'react-native';
import extractProps, { propsAndStyles } from '../lib/extract/extractProps';
import { extractFont } from '../lib/extract/extractText';
import extractTransform from '../lib/extract/extractTransform';
import { TransformProps } from '../lib/extract/types';
import Shape from './Shape';
export default class G extends Shape {
export default class G extends Shape<{}> {
static displayName = 'G';
setNativeProps = props => {
setNativeProps = (
props: Object & {
matrix?: number[];
} & TransformProps,
) => {
const matrix = !props.matrix && extractTransform(props);
if (matrix) {
props.matrix = matrix;
}
this.root.setNativeProps(props);
this.root && this.root.setNativeProps(props);
};
render() {

72
src/elements/Image.tsx Normal file
View File

@@ -0,0 +1,72 @@
import React from 'react';
import {
Image,
ImageSourcePropType,
requireNativeComponent,
} from 'react-native';
import { meetOrSliceTypes, alignEnum } from '../lib/extract/extractViewBox';
import extractProps, { propsAndStyles } from '../lib/extract/extractProps';
import { NumberProp } from '../lib/extract/types';
import Shape from './Shape';
const spacesRegExp = /\s+/;
export default class SvgImage extends Shape<{
preserveAspectRatio?: string;
x?: NumberProp;
y?: NumberProp;
width?: NumberProp;
height?: NumberProp;
xlinkHref?: string | number | ImageSourcePropType;
href?: string | number | ImageSourcePropType;
}> {
static displayName = 'Image';
static defaultProps = {
x: 0,
y: 0,
width: 0,
height: 0,
preserveAspectRatio: 'xMidYMid meet',
};
render() {
const { props } = this;
const {
preserveAspectRatio,
x,
y,
width,
height,
xlinkHref,
href = xlinkHref,
} = props;
const modes = preserveAspectRatio
? preserveAspectRatio.trim().split(spacesRegExp)
: [];
const align = modes[0];
const meetOrSlice: 'meet' | 'slice' | 'none' | string | undefined =
modes[1];
return (
<RNSVGImage
ref={this.refMethod}
{...extractProps({ ...propsAndStyles(props), x: null, y: null }, this)}
x={x}
y={y}
width={width}
height={height}
meetOrSlice={meetOrSliceTypes[meetOrSlice] || 0}
align={alignEnum[align] || 'xMidYMid'}
src={
!href
? null
: Image.resolveAssetSource(
typeof href === 'string' ? { uri: href } : href,
)
}
/>
);
}
}
const RNSVGImage = requireNativeComponent('RNSVGImage');

View File

@@ -1,9 +1,15 @@
import React from 'react';
import { requireNativeComponent } from 'react-native';
import extractProps, { propsAndStyles } from '../lib/extract/extractProps';
import { NumberProp } from '../lib/extract/types';
import Shape from './Shape';
export default class Line extends Shape {
export default class Line extends Shape<{
x1?: NumberProp;
y1?: NumberProp;
x2?: NumberProp;
y2?: NumberProp;
}> {
static displayName = 'Line';
static defaultProps = {

View File

@@ -1,9 +1,20 @@
import React from 'react';
import React, { ReactElement } from 'react';
import { requireNativeComponent } from 'react-native';
import extractGradient from '../lib/extract/extractGradient';
import { NumberProp, TransformProps } from '../lib/extract/types';
import Shape from './Shape';
export default class LinearGradient extends Shape {
export default class LinearGradient extends Shape<{
id?: string;
x1?: NumberProp;
y1?: NumberProp;
x2?: NumberProp;
y2?: NumberProp;
children?: ReactElement[];
transform?: number[] | string | TransformProps;
gradientTransform?: number[] | string | TransformProps;
gradientUnits?: 'objectBoundingBox' | 'userSpaceOnUse';
}> {
static displayName = 'LinearGradient';
static defaultProps = {

View File

@@ -2,10 +2,20 @@ import React from 'react';
import { requireNativeComponent } from 'react-native';
import extractTransform from '../lib/extract/extractTransform';
import extractProps, { propsAndStyles } from '../lib/extract/extractProps';
import { NumberProp, TransformProps } from '../lib/extract/types';
import units from '../lib/units';
import Shape from './Shape';
export default class Mask extends Shape {
export default class Mask extends Shape<{
x?: NumberProp;
y?: NumberProp;
width?: NumberProp;
height?: NumberProp;
transform?: number[] | string | TransformProps;
maskTransform?: number[] | string | TransformProps;
maskUnits?: 'objectBoundingBox' | 'userSpaceOnUse';
maskContentUnits?: 'objectBoundingBox' | 'userSpaceOnUse';
}> {
static displayName = 'Mask';
static defaultProps = {

View File

@@ -3,7 +3,9 @@ import { requireNativeComponent } from 'react-native';
import extractProps, { propsAndStyles } from '../lib/extract/extractProps';
import Shape from './Shape';
export default class Path extends Shape {
export default class Path extends Shape<{
d?: string;
}> {
static displayName = 'Path';
render() {

View File

@@ -2,10 +2,23 @@ import React from 'react';
import { requireNativeComponent } from 'react-native';
import extractTransform from '../lib/extract/extractTransform';
import extractViewBox from '../lib/extract/extractViewBox';
import { NumberProp, TransformProps } from '../lib/extract/types';
import units from '../lib/units';
import Shape from './Shape';
export default class Pattern extends Shape {
export default class Pattern extends Shape<{
id?: string;
x?: NumberProp;
y?: NumberProp;
width?: NumberProp;
height?: NumberProp;
viewBox?: string;
preserveAspectRatio?: string;
transform?: number[] | string | TransformProps;
patternTransform?: number[] | string | TransformProps;
patternUnits?: 'objectBoundingBox' | 'userSpaceOnUse';
patternContentUnits?: 'objectBoundingBox' | 'userSpaceOnUse';
}> {
static displayName = 'Pattern';
static defaultProps = {
@@ -42,7 +55,7 @@ export default class Pattern extends Shape {
height={height}
matrix={matrix}
patternTransform={matrix}
patternUnits={units[patternUnits] || 0}
patternUnits={(patternUnits && units[patternUnits]) || 0}
patternContentUnits={
patternContentUnits ? units[patternContentUnits] : 1
}

View File

@@ -1,21 +1,27 @@
import React from 'react';
import Path from './Path';
import Shape from './Shape';
import { NumberProp } from '../lib/extract/types';
import extractPolyPoints from '../lib/extract/extractPolyPoints';
export default class Polygon extends Shape {
export default class Polygon extends Shape<{ points?: number[] }> {
static displayName = 'Polygon';
static defaultProps = {
points: '',
};
setNativeProps = props => {
setNativeProps = (
props: Object & {
points?: string | (NumberProp)[];
d?: string;
},
) => {
const { points } = props;
if (points) {
props.d = `M${extractPolyPoints(points)}z`;
}
this.root.setNativeProps(props);
this.root && this.root.setNativeProps(props);
};
render() {
@@ -23,8 +29,8 @@ export default class Polygon extends Shape {
const { points } = props;
return (
<Path
ref={this.refMethod}
d={`M${extractPolyPoints(points)}z`}
ref={this.refMethod as ((instance: Path | null) => void)}
d={points && `M${extractPolyPoints(points)}z`}
{...props}
/>
);

View File

@@ -1,21 +1,27 @@
import React from 'react';
import Path from './Path';
import Shape from './Shape';
import { NumberProp } from '../lib/extract/types';
import extractPolyPoints from '../lib/extract/extractPolyPoints';
export default class Polyline extends Shape {
export default class Polyline extends Shape<{ points?: number[] }> {
static displayName = 'Polyline';
static defaultProps = {
points: '',
};
setNativeProps = props => {
setNativeProps = (
props: Object & {
points?: string | (NumberProp)[];
d?: string;
},
) => {
const { points } = props;
if (points) {
props.d = `M${extractPolyPoints(points)}`;
}
this.root.setNativeProps(props);
this.root && this.root.setNativeProps(props);
};
render() {
@@ -23,8 +29,8 @@ export default class Polyline extends Shape {
const { points } = props;
return (
<Path
ref={this.refMethod}
d={`M${extractPolyPoints(points)}`}
ref={this.refMethod as ((instance: Path | null) => void)}
d={points && `M${extractPolyPoints(points)}`}
{...props}
/>
);

View File

@@ -1,9 +1,23 @@
import React from 'react';
import React, { ReactElement } from 'react';
import { requireNativeComponent } from 'react-native';
import extractGradient from '../lib/extract/extractGradient';
import { NumberProp, TransformProps } from '../lib/extract/types';
import Shape from './Shape';
export default class RadialGradient extends Shape {
export default class RadialGradient extends Shape<{
fx?: NumberProp;
fy?: NumberProp;
rx?: NumberProp;
ry?: NumberProp;
r?: NumberProp;
cx?: NumberProp;
cy?: NumberProp;
id?: string;
children?: ReactElement[];
transform?: number[] | string | TransformProps;
gradientTransform?: number[] | string | TransformProps;
gradientUnits?: 'objectBoundingBox' | 'userSpaceOnUse';
}> {
static displayName = 'RadialGradient';
static defaultProps = {

View File

@@ -1,9 +1,17 @@
import React from 'react';
import { requireNativeComponent } from 'react-native';
import extractProps, { propsAndStyles } from '../lib/extract/extractProps';
import { NumberProp } from '../lib/extract/types';
import Shape from './Shape';
export default class Rect extends Shape {
export default class Rect extends Shape<{
x?: NumberProp;
y?: NumberProp;
width?: NumberProp;
height?: NumberProp;
rx?: NumberProp;
ry?: NumberProp;
}> {
static displayName = 'Rect';
static defaultProps = {

39
src/elements/Shape.tsx Normal file
View File

@@ -0,0 +1,39 @@
import { Component } from 'react';
import SvgTouchableMixin from '../lib/SvgTouchableMixin';
import { NativeMethodsMixinStatic } from 'react-native';
import { TransformProps } from '../lib/extract/types';
const { touchableGetInitialState } = SvgTouchableMixin;
const touchKeys = Object.keys(SvgTouchableMixin);
const touchVals = touchKeys.map(key => SvgTouchableMixin[key]);
const numTouchKeys = touchKeys.length;
export default class Shape<P> extends Component<P> {
[x: string]: unknown;
root: (Shape<P> & NativeMethodsMixinStatic) | null = null;
constructor(props: P, context: {}) {
super(props, context);
for (let i = 0; i < numTouchKeys; i++) {
const key = touchKeys[i];
const val = touchVals[i];
if (typeof val === 'function') {
this[key] = val.bind(this);
} else {
this[key] = val;
}
}
this.state = touchableGetInitialState();
}
refMethod: (
instance: (Shape<P> & NativeMethodsMixinStatic) | null,
) => void = (instance: (Shape<P> & NativeMethodsMixinStatic) | null) => {
this.root = instance;
};
setNativeProps = (
props: Object & {
matrix?: [number, number, number, number, number, number];
} & TransformProps,
) => {
this.root && this.root.setNativeProps(props);
};
}

View File

@@ -1,6 +1,11 @@
import { Component } from 'react';
export default class Stop extends Component {
type StopProps = {
parent?: Component;
};
export default class Stop extends Component<StopProps, {}> {
props!: StopProps;
static displayName = 'Stop';
static defaultProps = {

View File

@@ -1,10 +1,22 @@
import React from 'react';
import React, { Component } from 'react';
import {
requireNativeComponent,
StyleSheet,
findNodeHandle,
NativeModules,
MeasureOnSuccessCallback,
MeasureLayoutOnSuccessCallback,
MeasureInWindowOnSuccessCallback,
} from 'react-native';
import {
ClipProps,
FillProps,
NumberProp,
StrokeProps,
ResponderProps,
TransformProps,
ResponderInstanceProps,
} from '../lib/extract/types';
import extractResponder from '../lib/extract/extractResponder';
import extractViewBox from '../lib/extract/extractViewBox';
import Shape from './Shape';
@@ -19,26 +31,50 @@ const styles = StyleSheet.create({
},
});
export default class Svg extends Shape {
export default class Svg extends Shape<
{
style?: [] | {};
viewBox?: string;
opacity?: NumberProp;
onLayout?: () => void;
preserveAspectRatio?: string;
} & TransformProps &
ResponderProps &
StrokeProps &
FillProps &
ClipProps
> {
static displayName = 'Svg';
static defaultProps = {
preserveAspectRatio: 'xMidYMid meet',
};
measureInWindow = (...args) => {
this.root.measureInWindow(...args);
measureInWindow = (callback: MeasureInWindowOnSuccessCallback) => {
this.root && this.root.measureInWindow(callback);
};
measure = (...args) => {
this.root.measure(...args);
measure = (callback: MeasureOnSuccessCallback) => {
this.root && this.root.measure(callback);
};
measureLayout = (...args) => {
this.root.measureLayout(...args);
measureLayout = (
relativeToNativeNode: number,
onSuccess: MeasureLayoutOnSuccessCallback,
onFail: () => void /* currently unused */,
) => {
this.root &&
this.root.measureLayout(relativeToNativeNode, onSuccess, onFail);
};
setNativeProps = props => {
setNativeProps = (
props: Object & {
width?: NumberProp;
height?: NumberProp;
bbWidth?: NumberProp;
bbHeight?: NumberProp;
},
) => {
const { width, height } = props;
if (width) {
props.bbWidth = width;
@@ -46,20 +82,20 @@ export default class Svg extends Shape {
if (height) {
props.bbHeight = height;
}
this.root.setNativeProps(props);
this.root && this.root.setNativeProps(props);
};
toDataURL = (callback, options) => {
toDataURL = (callback: () => void, options: Object) => {
if (!callback) {
return;
}
const handle = findNodeHandle(this.root);
const handle = findNodeHandle(this.root as Component);
RNSVGSvgViewManager.toDataURL(handle, options, callback);
};
render() {
const {
opacity,
opacity = 1,
viewBox,
preserveAspectRatio,
style,
@@ -68,7 +104,7 @@ export default class Svg extends Shape {
...props
} = this.props;
const stylesAndProps = {
...(style && style.length ? Object.assign({}, ...style) : style),
...(Array.isArray(style) ? Object.assign({}, ...style) : style),
...props,
};
const {
@@ -121,7 +157,7 @@ export default class Svg extends Shape {
onLayout={onLayout}
ref={this.refMethod}
style={[styles.svg, style, opacityStyle, dimensions]}
{...extractResponder(props, this)}
{...extractResponder(props, this as ResponderInstanceProps)}
{...extractViewBox({ viewBox, preserveAspectRatio })}
>
<G

View File

@@ -3,7 +3,11 @@ import { requireNativeComponent } from 'react-native';
import extractViewBox from '../lib/extract/extractViewBox';
import Shape from './Shape';
export default class Symbol extends Shape {
export default class Symbol extends Shape<{
id?: string;
viewBox?: string;
preserveAspectRatio?: string;
}> {
static displayName = 'Symbol';
render() {

View File

@@ -1,22 +1,28 @@
import React from 'react';
import React, { Component } from 'react';
import { requireNativeComponent } from 'react-native';
import extractProps, { propsAndStyles } from '../lib/extract/extractProps';
import extractTransform from '../lib/extract/extractTransform';
import extractText, { setTSpan } from '../lib/extract/extractText';
import { pickNotNil } from '../lib/util';
import Shape from './Shape';
import { TransformProps } from '../lib/extract/types';
export default class TSpan extends Shape {
export default class TSpan extends Shape<{}> {
static displayName = 'TSpan';
setNativeProps = props => {
setNativeProps = (
props: Object & {
matrix?: number[];
style?: [] | {};
} & TransformProps,
) => {
const matrix = !props.matrix && extractTransform(props);
if (matrix) {
props.matrix = matrix;
}
const prop = propsAndStyles(props);
Object.assign(prop, pickNotNil(extractText(prop, false)));
this.root.setNativeProps(prop);
this.root && this.root.setNativeProps(prop);
};
render() {
@@ -30,7 +36,7 @@ export default class TSpan extends Shape {
this,
);
Object.assign(props, extractText(prop, false));
props.ref = this.refMethod;
props.ref = this.refMethod as (instance: Component | null) => void;
return <RNSVGTSpan {...props} />;
}
}

View File

@@ -1,23 +1,29 @@
import React from 'react';
import React, { Component } from 'react';
import { requireNativeComponent } from 'react-native';
import extractText from '../lib/extract/extractText';
import extractProps, { propsAndStyles } from '../lib/extract/extractProps';
import extractTransform from '../lib/extract/extractTransform';
import { TransformProps } from '../lib/extract/types';
import { pickNotNil } from '../lib/util';
import Shape from './Shape';
import './TSpan';
export default class Text extends Shape {
export default class Text extends Shape<{}> {
static displayName = 'Text';
setNativeProps = props => {
const matrix = !props.matrix && extractTransform(props);
setNativeProps = (
props: Object & {
matrix?: number[];
style?: [] | {};
} & TransformProps,
) => {
const matrix = props && !props.matrix && extractTransform(props);
if (matrix) {
props.matrix = matrix;
}
const prop = propsAndStyles(props);
Object.assign(prop, pickNotNil(extractText(prop, true)));
this.root.setNativeProps(prop);
this.root && this.root.setNativeProps(prop);
};
render() {
@@ -31,7 +37,7 @@ export default class Text extends Shape {
this,
);
Object.assign(props, extractText(prop, true));
props.ref = this.refMethod;
props.ref = this.refMethod as (instance: Component | null) => void;
return <RNSVGText {...props} />;
}
}

View File

@@ -1,22 +1,38 @@
import React from 'react';
import React, { Component } from 'react';
import { requireNativeComponent } from 'react-native';
import extractTransform from '../lib/extract/extractTransform';
import extractProps, { propsAndStyles } from '../lib/extract/extractProps';
import { NumberProp, TransformProps } from '../lib/extract/types';
import extractText from '../lib/extract/extractText';
import { idPattern, pickNotNil } from '../lib/util';
import Shape from './Shape';
import TSpan from './TSpan';
export default class TextPath extends Shape {
export default class TextPath extends Shape<{
children?: NumberProp | [NumberProp | React.ComponentType];
alignmentBaseline?: string;
startOffset?: NumberProp;
xlinkHref?: string;
midLine?: string;
spacing?: string;
method?: string;
href?: string;
side?: string;
}> {
static displayName = 'TextPath';
setNativeProps = props => {
setNativeProps = (
props: Object & {
matrix?: number[];
style?: [] | {};
} & TransformProps,
) => {
const matrix = !props.matrix && extractTransform(props);
if (matrix) {
props.matrix = matrix;
}
Object.assign(props, pickNotNil(extractText(props, true)));
this.root.setNativeProps(props);
this.root && this.root.setNativeProps(props);
};
render() {
@@ -61,7 +77,7 @@ export default class TextPath extends Shape {
midLine,
},
);
props.ref = this.refMethod;
props.ref = this.refMethod as (instance: Component | null) => void;
return <RNSVGTextPath {...props} />;
}
@@ -70,7 +86,11 @@ export default class TextPath extends Shape {
href +
'"',
);
return <TSpan ref={this.refMethod}>{children}</TSpan>;
return (
<TSpan ref={this.refMethod as (instance: Component | null) => void}>
{children}
</TSpan>
);
}
}

View File

@@ -1,10 +1,18 @@
import React from 'react';
import { requireNativeComponent } from 'react-native';
import extractProps, { propsAndStyles } from '../lib/extract/extractProps';
import { NumberProp } from '../lib/extract/types';
import { idPattern } from '../lib/util';
import Shape from './Shape';
export default class Use extends Shape {
export default class Use extends Shape<{
x?: NumberProp;
y?: NumberProp;
width?: NumberProp;
height?: NumberProp;
xlinkHref?: string;
href?: string;
}> {
static displayName = 'Use';
static defaultProps = {
@@ -26,7 +34,7 @@ export default class Use extends Shape {
href = xlinkHref,
} = props;
const matched = href.match(idPattern);
const matched = href && href.match(idPattern);
const match = matched && matched[1];
if (!match) {

View File

@@ -1,5 +1,6 @@
import * as React from 'react';
import * as ReactNative from 'react-native';
import { GestureResponderEvent } from 'react-native';
// Common props
type NumberProp = string | number;
@@ -23,8 +24,7 @@ export type FontWeight =
| '600'
| '700'
| '800'
| '900'
;
| '900';
export type FontStretch =
| 'normal'
| 'wider'
@@ -36,9 +36,13 @@ export type FontStretch =
| 'semi-expanded'
| 'expanded'
| 'extra-expanded'
| 'ultra-expanded'
;
export type TextDecoration = 'none' | 'underline' | 'overline' | 'line-through' | 'blink';
| 'ultra-expanded';
export type TextDecoration =
| 'none'
| 'underline'
| 'overline'
| 'line-through'
| 'blink';
export type FontVariantLigatures = 'normal' | 'none';
export type AlignmentBaseline =
| 'baseline'
@@ -56,9 +60,13 @@ export type AlignmentBaseline =
| 'text-after-edge'
| 'before-edge'
| 'after-edge'
| 'hanging'
;
export type BaselineShift = 'sub' | 'super' | 'baseline' | ReadonlyArray<NumberProp> | NumberProp;
| 'hanging';
export type BaselineShift =
| 'sub'
| 'super'
| 'baseline'
| ReadonlyArray<NumberProp>
| NumberProp;
export type LengthAdjust = 'spacing' | 'spacingAndGlyphs';
export type TextPathMethod = 'align' | 'stretch';
@@ -69,94 +77,100 @@ export type Linecap = 'butt' | 'square' | 'round';
export type Linejoin = 'miter' | 'bevel' | 'round';
export interface TouchableProps {
disabled?: boolean,
onPress?: (event: any) => any,
onPressIn?: (event: any) => any,
onPressOut?: (event: any) => any,
onLongPress?: (event: any) => any,
delayPressIn?: number,
delayPressOut?: number,
delayLongPress?: number
disabled?: boolean;
onPress?: (event: GestureResponderEvent) => void;
onPressIn?: (event: GestureResponderEvent) => void;
onPressOut?: (event: GestureResponderEvent) => void;
onLongPress?: (event: GestureResponderEvent) => void;
delayPressIn?: number;
delayPressOut?: number;
delayLongPress?: number;
}
export interface ResponderProps extends ReactNative.GestureResponderHandlers {
pointerEvents?: "box-none" | "none" | "box-only" | "auto",
pointerEvents?: 'box-none' | 'none' | 'box-only' | 'auto';
}
// rgba values inside range 0 to 1 inclusive
// rgbaArray = [r, g, b, a]
type rgbaArray = ReadonlyArray<number>
type rgbaArray = ReadonlyArray<number>;
// argb values inside range 0x00 to 0xff inclusive
// int32ARGBColor = 0xaarrggbb
type int32ARGBColor = number
type int32ARGBColor = number;
export interface FillProps {
fill?: int32ARGBColor | rgbaArray | string,
fillOpacity?: NumberProp,
fillRule?: FillRule,
fill?: int32ARGBColor | rgbaArray | string;
fillOpacity?: NumberProp;
fillRule?: FillRule;
}
export interface ClipProps {
clipRule?: FillRule,
clipPath?: string
clipRule?: FillRule;
clipPath?: string;
}
interface VectorEffectProps {
vectorEffect?: "none" | "non-scaling-stroke" | "nonScalingStroke" | "default" | "inherit" | "uri";
vectorEffect?:
| 'none'
| 'non-scaling-stroke'
| 'nonScalingStroke'
| 'default'
| 'inherit'
| 'uri';
}
export interface DefinitionProps {
id?: string,
id?: string;
}
export interface StrokeProps {
stroke?: int32ARGBColor | rgbaArray | string,
strokeWidth?: NumberProp,
strokeOpacity?: NumberProp,
strokeDasharray?: ReadonlyArray<NumberProp> | NumberProp,
strokeDashoffset?: NumberProp,
strokeLinecap?: Linecap,
strokeLinejoin?: Linejoin,
strokeMiterlimit?: NumberProp,
stroke?: int32ARGBColor | rgbaArray | string;
strokeWidth?: NumberProp;
strokeOpacity?: NumberProp;
strokeDasharray?: ReadonlyArray<NumberProp> | NumberProp;
strokeDashoffset?: NumberProp;
strokeLinecap?: Linecap;
strokeLinejoin?: Linejoin;
strokeMiterlimit?: NumberProp;
}
export interface FontObject {
fontStyle?: FontStyle,
fontVariant?: FontVariant,
fontWeight?: FontWeight,
fontStretch?: FontStretch,
fontSize?: NumberProp,
fontFamily?: string,
textAnchor?: TextAnchor,
textDecoration?: TextDecoration,
letterSpacing?: NumberProp,
wordSpacing?: NumberProp,
kerning?: NumberProp,
fontVariantLigatures?: FontVariantLigatures,
fontStyle?: FontStyle;
fontVariant?: FontVariant;
fontWeight?: FontWeight;
fontStretch?: FontStretch;
fontSize?: NumberProp;
fontFamily?: string;
textAnchor?: TextAnchor;
textDecoration?: TextDecoration;
letterSpacing?: NumberProp;
wordSpacing?: NumberProp;
kerning?: NumberProp;
fontVariantLigatures?: FontVariantLigatures;
}
export interface FontProps extends FontObject {
font?: FontObject,
font?: FontObject;
}
export interface TransformObject {
scale?: NumberProp,
scaleX?: NumberProp,
scaleY?: NumberProp,
rotate?: NumberProp,
rotation?: NumberProp,
translate?: NumberProp,
translateX?: NumberProp,
translateY?: NumberProp,
x?: NumberProp,
y?: NumberProp,
origin?: NumberProp,
originX?: NumberProp,
originY?: NumberProp,
skew?: NumberProp,
skewX?: NumberProp,
skewY?: NumberProp,
scale?: NumberProp;
scaleX?: NumberProp;
scaleY?: NumberProp;
rotate?: NumberProp;
rotation?: NumberProp;
translate?: NumberProp;
translateX?: NumberProp;
translateY?: NumberProp;
x?: NumberProp;
y?: NumberProp;
origin?: NumberProp;
originX?: NumberProp;
originY?: NumberProp;
skew?: NumberProp;
skewX?: NumberProp;
skewY?: NumberProp;
}
/*
@@ -177,147 +191,160 @@ export interface TransformObject {
type ColumnMajorTransformMatrix = ReadonlyArray<number>;
export interface TransformProps extends TransformObject {
transform?: ColumnMajorTransformMatrix | string | TransformObject,
transform?: ColumnMajorTransformMatrix | string | TransformObject;
}
export interface CommonMaskProps {
mask?: string;
}
export interface CommonPathProps extends FillProps, StrokeProps, ClipProps, TransformProps, VectorEffectProps, ResponderProps, TouchableProps, DefinitionProps, CommonMaskProps {}
export interface CommonPathProps
extends FillProps,
StrokeProps,
ClipProps,
TransformProps,
VectorEffectProps,
ResponderProps,
TouchableProps,
DefinitionProps,
CommonMaskProps {}
// Element props
export interface CircleProps extends CommonPathProps {
cx?: NumberProp,
cy?: NumberProp,
opacity?: NumberProp,
r?: NumberProp,
cx?: NumberProp;
cy?: NumberProp;
opacity?: NumberProp;
r?: NumberProp;
}
export const Circle: React.ComponentClass<CircleProps>;
export interface ClipPathProps {
id: string,
id: string;
}
export const ClipPath: React.ComponentClass<ClipPathProps>;
export const Defs: React.ComponentClass<{}>;
export interface EllipseProps extends CommonPathProps {
cx?: NumberProp,
cy?: NumberProp,
opacity?: NumberProp,
rx?: NumberProp,
ry?: NumberProp,
cx?: NumberProp;
cy?: NumberProp;
opacity?: NumberProp;
rx?: NumberProp;
ry?: NumberProp;
}
export const Ellipse: React.ComponentClass<EllipseProps>;
export interface GProps extends CommonPathProps {
opacity?: NumberProp,
opacity?: NumberProp;
}
export const G: React.ComponentClass<GProps>;
export interface ImageProps extends ResponderProps, CommonMaskProps, ClipProps, TouchableProps {
x?: NumberProp,
y?: NumberProp,
width?: NumberProp,
height?: NumberProp,
xlinkHref?: ReactNative.ImageProperties['source'],
href: ReactNative.ImageProperties['source'],
preserveAspectRatio?: string,
opacity?: NumberProp,
clipPath?: string
export interface ImageProps
extends ResponderProps,
CommonMaskProps,
ClipProps,
TouchableProps {
x?: NumberProp;
y?: NumberProp;
width?: NumberProp;
height?: NumberProp;
xlinkHref?: ReactNative.ImageProps['source'];
href: ReactNative.ImageProps['source'];
preserveAspectRatio?: string;
opacity?: NumberProp;
clipPath?: string;
}
export const Image: React.ComponentClass<ImageProps>;
export interface LineProps extends CommonPathProps {
opacity?: NumberProp,
x1?: NumberProp,
x2?: NumberProp,
y1?: NumberProp,
y2?: NumberProp,
opacity?: NumberProp;
x1?: NumberProp;
x2?: NumberProp;
y1?: NumberProp;
y2?: NumberProp;
}
export const Line: React.ComponentClass<LineProps>;
export interface LinearGradientProps {
x1?: NumberProp,
x2?: NumberProp,
y1?: NumberProp,
y2?: NumberProp,
gradientUnits?: Units,
id: string,
x1?: NumberProp;
x2?: NumberProp;
y1?: NumberProp;
y2?: NumberProp;
gradientUnits?: Units;
id: string;
}
export const LinearGradient: React.ComponentClass<LinearGradientProps>;
export interface PathProps extends CommonPathProps {
d: string,
opacity?: NumberProp,
d: string;
opacity?: NumberProp;
}
export const Path: React.ComponentClass<PathProps>;
export interface PatternProps {
id: string,
x?: NumberProp,
y?: NumberProp,
width?: NumberProp,
height?: NumberProp,
patternTransform?: ColumnMajorTransformMatrix | string,
patternUnits?: Units,
patternContentUnits?: Units,
viewBox?: string,
preserveAspectRatio?: string
id: string;
x?: NumberProp;
y?: NumberProp;
width?: NumberProp;
height?: NumberProp;
patternTransform?: ColumnMajorTransformMatrix | string;
patternUnits?: Units;
patternContentUnits?: Units;
viewBox?: string;
preserveAspectRatio?: string;
}
export const Pattern: React.ComponentClass<PatternProps>;
export interface PolygonProps extends CommonPathProps {
opacity?: NumberProp,
points: string | ReadonlyArray<NumberProp>,
opacity?: NumberProp;
points: string | ReadonlyArray<NumberProp>;
}
export const Polygon: React.ComponentClass<PolygonProps>;
export interface PolylineProps extends CommonPathProps {
opacity?: NumberProp,
points: string | ReadonlyArray<NumberProp>,
opacity?: NumberProp;
points: string | ReadonlyArray<NumberProp>;
}
export const Polyline: React.ComponentClass<PolylineProps>;
export interface RadialGradientProps {
fx?: NumberProp,
fy?: NumberProp,
rx?: NumberProp,
ry?: NumberProp,
cx?: NumberProp,
cy?: NumberProp,
r?: NumberProp,
gradientUnits?: Units,
id: string,
fx?: NumberProp;
fy?: NumberProp;
rx?: NumberProp;
ry?: NumberProp;
cx?: NumberProp;
cy?: NumberProp;
r?: NumberProp;
gradientUnits?: Units;
id: string;
}
export const RadialGradient: React.ComponentClass<RadialGradientProps>;
export interface RectProps extends CommonPathProps {
x?: NumberProp,
y?: NumberProp,
width?: NumberProp,
height?: NumberProp,
rx?: NumberProp,
ry?: NumberProp,
opacity?: NumberProp,
x?: NumberProp;
y?: NumberProp;
width?: NumberProp;
height?: NumberProp;
rx?: NumberProp;
ry?: NumberProp;
opacity?: NumberProp;
}
export const Rect: React.ComponentClass<RectProps>;
export interface StopProps {
stopColor?: int32ARGBColor | rgbaArray | string,
stopOpacity?: NumberProp,
offset?: NumberProp,
stopColor?: int32ARGBColor | rgbaArray | string;
stopOpacity?: NumberProp;
offset?: NumberProp;
}
export const Stop: React.ComponentClass<StopProps>;
export interface SvgProps extends GProps, ReactNative.ViewProperties {
width?: NumberProp,
height?: NumberProp,
viewBox?: string,
preserveAspectRatio?: string,
color?: int32ARGBColor | rgbaArray | string,
title?: string,
width?: NumberProp;
height?: NumberProp;
viewBox?: string;
preserveAspectRatio?: string;
color?: int32ARGBColor | rgbaArray | string;
title?: string;
}
// Svg is both regular and default exported
@@ -325,58 +352,57 @@ export const Svg: React.ComponentClass<SvgProps>;
export default Svg;
export interface SymbolProps {
id: string,
viewBox?: string,
preserveAspectRatio?: string,
opacity?: NumberProp,
id: string;
viewBox?: string;
preserveAspectRatio?: string;
opacity?: NumberProp;
}
export const Symbol: React.ComponentClass<SymbolProps>;
export interface TSpanProps extends CommonPathProps, FontProps {
dx?: NumberProp,
dy?: NumberProp,
dx?: NumberProp;
dy?: NumberProp;
}
export const TSpan: React.ComponentClass<TSpanProps>;
export interface TextSpecificProps extends CommonPathProps, FontProps {
alignmentBaseline?: AlignmentBaseline,
baselineShift?: BaselineShift,
verticalAlign?: NumberProp,
lengthAdjust?: LengthAdjust,
textLength?: NumberProp,
fontData?: null | { [name: string]: any },
fontFeatureSettings?: string,
alignmentBaseline?: AlignmentBaseline;
baselineShift?: BaselineShift;
verticalAlign?: NumberProp;
lengthAdjust?: LengthAdjust;
textLength?: NumberProp;
fontData?: null | { [name: string]: unknown };
fontFeatureSettings?: string;
}
export interface TextProps extends TextSpecificProps {
dx?: NumberProp,
dy?: NumberProp,
opacity?: NumberProp,
dx?: NumberProp;
dy?: NumberProp;
opacity?: NumberProp;
}
export const Text: React.ComponentClass<TextProps>;
export interface TextPathProps extends TextSpecificProps {
xlinkHref?: string,
href: string,
startOffset?: NumberProp,
method?: TextPathMethod,
spacing?: TextPathSpacing,
midLine: TextPathMidLine,
xlinkHref?: string;
href: string;
startOffset?: NumberProp;
method?: TextPathMethod;
spacing?: TextPathSpacing;
midLine: TextPathMidLine;
}
export const TextPath: React.ComponentClass<TextPathProps>;
export interface UseProps extends CommonPathProps {
xlinkHref?: string,
href: string,
width?: NumberProp,
height?: NumberProp,
x?: NumberProp,
y?: NumberProp,
opacity?: NumberProp,
xlinkHref?: string;
href: string;
width?: NumberProp;
height?: NumberProp;
x?: NumberProp;
y?: NumberProp;
opacity?: NumberProp;
}
export const Use: React.ComponentClass<UseProps>;
export enum EMaskUnits {
USER_SPACE_ON_USE = 'userSpaceOnUse',
OBJECT_BOUNDING_BOX = 'objectBoundingBox',
@@ -387,13 +413,13 @@ export type TMaskUnits =
| EMaskUnits.OBJECT_BOUNDING_BOX;
export interface MaskProps extends CommonPathProps {
id: string,
x?: NumberProp,
y?: NumberProp,
width?: NumberProp,
height?: NumberProp,
maskTransform?: ColumnMajorTransformMatrix | string,
maskUnits?: TMaskUnits,
maskContentUnits?: TMaskUnits,
id: string;
x?: NumberProp;
y?: NumberProp;
width?: NumberProp;
height?: NumberProp;
maskTransform?: ColumnMajorTransformMatrix | string;
maskUnits?: TMaskUnits;
maskContentUnits?: TMaskUnits;
}
export const Mask: React.ComponentClass<MaskProps>;

View File

@@ -1,6 +1,7 @@
import { createElement } from 'react-native-web';
import { resolve } from './lib/resolve';
import { Component } from 'react';
import { NumberProp } from './lib/extract/types';
/**
* `react-native-svg` supports additional props that aren't defined in the spec.
@@ -56,7 +57,12 @@ function prepare(props) {
clean.transform = transform.join(' ');
}
const styles = {};
const styles: {
fontStyle?: string;
fontFamily?: string;
fontSize?: NumberProp;
fontWeight?: NumberProp;
} = {};
if (fontFamily != null) {
styles.fontFamily = fontFamily;
@@ -100,7 +106,11 @@ export class Ellipse extends Component {
}
}
export class G extends Component {
export class G extends Component<{
x?: NumberProp;
y?: NumberProp;
translate?: string;
}> {
render() {
const { x, y, ...rest } = this.props;

View File

@@ -62,7 +62,14 @@ export function toArray() {
* @param {Number} tx2
* @param {Number} ty2
**/
export function append(a2, b2, c2, d2, tx2, ty2) {
export function append(
a2: number,
b2: number,
c2: number,
d2: number,
tx2: number,
ty2: number,
) {
const change = a2 !== 1 || b2 !== 0 || c2 !== 0 || d2 !== 1;
const translate = tx2 !== 0 || ty2 !== 0;
if (!change && !translate) {
@@ -114,15 +121,15 @@ export function append(a2, b2, c2, d2, tx2, ty2) {
* @param {Number} regY Optional.
**/
export function appendTransform(
x,
y,
scaleX,
scaleY,
rotation,
skewX,
skewY,
regX,
regY,
x: number,
y: number,
scaleX: number,
scaleY: number,
rotation: number,
skewX: number,
skewY: number,
regX: number,
regY: number,
) {
if (
x === 0 &&

View File

@@ -0,0 +1,118 @@
// @ts-ignore
import { Touchable, GestureResponderEvent } from 'react-native';
const PRESS_RETENTION_OFFSET = { top: 20, left: 20, right: 20, bottom: 30 };
// @ts-ignore
const { Mixin } = Touchable;
const {
touchableHandleStartShouldSetResponder,
touchableHandleResponderTerminationRequest,
touchableHandleResponderGrant,
touchableHandleResponderMove,
touchableHandleResponderRelease,
touchableHandleResponderTerminate,
} = Mixin;
export default {
...Mixin,
touchableHandleStartShouldSetResponder: function(e: GestureResponderEvent) {
const { onStartShouldSetResponder } = this.props;
if (onStartShouldSetResponder) {
return onStartShouldSetResponder(e);
} else {
return touchableHandleStartShouldSetResponder.call(this, e);
}
},
touchableHandleResponderTerminationRequest: function(
e: GestureResponderEvent,
) {
const { onResponderTerminationRequest } = this.props;
if (onResponderTerminationRequest) {
return onResponderTerminationRequest(e);
} else {
return touchableHandleResponderTerminationRequest.call(this, e);
}
},
touchableHandleResponderGrant: function(e: GestureResponderEvent) {
const { onResponderGrant } = this.props;
if (onResponderGrant) {
return onResponderGrant(e);
} else {
return touchableHandleResponderGrant.call(this, e);
}
},
touchableHandleResponderMove: function(e: GestureResponderEvent) {
const { onResponderMove } = this.props;
if (onResponderMove) {
return onResponderMove(e);
} else {
return touchableHandleResponderMove.call(this, e);
}
},
touchableHandleResponderRelease: function(e: GestureResponderEvent) {
const { onResponderRelease } = this.props;
if (onResponderRelease) {
return onResponderRelease(e);
} else {
return touchableHandleResponderRelease.call(this, e);
}
},
touchableHandleResponderTerminate: function(e: GestureResponderEvent) {
const { onResponderTerminate } = this.props;
if (onResponderTerminate) {
return onResponderTerminate(e);
} else {
return touchableHandleResponderTerminate.call(this, e);
}
},
touchableHandlePress: function(e: GestureResponderEvent) {
const { onPress } = this.props;
onPress && onPress(e);
},
touchableHandleActivePressIn: function(e: GestureResponderEvent) {
const { onPressIn } = this.props;
onPressIn && onPressIn(e);
},
touchableHandleActivePressOut: function(e: GestureResponderEvent) {
const { onPressOut } = this.props;
onPressOut && onPressOut(e);
},
touchableHandleLongPress: function(e: GestureResponderEvent) {
const { onLongPress } = this.props;
onLongPress && onLongPress(e);
},
touchableGetPressRectOffset: function() {
const { pressRetentionOffset } = this.props;
return pressRetentionOffset || PRESS_RETENTION_OFFSET;
},
touchableGetHitSlop: function() {
const { hitSlop } = this.props;
return hitSlop;
},
touchableGetHighlightDelayMS: function() {
const { delayPressIn } = this.props;
return delayPressIn || 0;
},
touchableGetLongPressDelayMS: function() {
const { delayLongPress } = this.props;
return delayLongPress === 0 ? 0 : delayLongPress || 500;
},
touchableGetPressOutDelayMS: function() {
const { delayPressOut } = this.props;
return delayPressOut || 0;
},
};

View File

@@ -1,10 +1,11 @@
import extractColor, { integerColor } from './extractColor';
import { Color } from './types';
const urlIdPattern = /^url\(#(.+)\)$/;
const currentColorBrush = [2];
export default function extractBrush(color) {
export default function extractBrush(color?: Color) {
if (typeof color === 'number') {
if (color >>> 0 === color && color >= 0 && color <= 0xffffffff) {
return [0, integerColor(color)];

View File

@@ -1,13 +1,17 @@
import { idPattern } from '../util';
import { ClipProps } from './types';
const clipRules = {
const clipRules: { evenodd: number; nonzero: number } = {
evenodd: 0,
nonzero: 1,
};
export default function extractClipPath(props) {
export default function extractClipPath(props: ClipProps) {
const { clipPath, clipRule } = props;
const extracted = {};
const extracted: {
clipPath?: string;
clipRule?: number;
} = {};
if (clipRule) {
extracted.clipRule = clipRules[clipRule] === 0 ? 0 : 1;

View File

@@ -1,6 +1,7 @@
import { Platform } from 'react-native';
import { Color } from './types';
export const colorNames = {
export const colors: { [colorname: string]: number[] } = {
aliceblue: [240, 248, 255],
antiquewhite: [250, 235, 215],
aqua: [0, 255, 255],
@@ -150,9 +151,10 @@ export const colorNames = {
yellow: [255, 255, 0],
yellowgreen: [154, 205, 50],
};
for (const name in colorNames) {
if (colorNames.hasOwnProperty(name)) {
const color = colorNames[name];
export const colorNames: { [colorname: string]: number | void } = {};
for (const name in colors) {
if (colors.hasOwnProperty(name)) {
const color: number[] = colors[name];
const r = color[0];
const g = color[1];
const b = color[2];
@@ -161,7 +163,7 @@ for (const name in colorNames) {
}
Object.freeze(colorNames);
function hslToRgb(_h, _s, _l, a) {
function hslToRgb(_h: number, _s: number, _l: number, a: number) {
const h = _h / 360;
const s = _s / 100;
const l = _l / 100;
@@ -210,7 +212,7 @@ function hslToRgb(_h, _s, _l, a) {
return rgb;
}
function hwbToRgb(_h, _w, _b, a) {
function hwbToRgb(_h: number, _w: number, _b: number, a: number) {
const h = _h / 360;
let wh = _w / 100;
let bl = _b / 100;
@@ -277,7 +279,7 @@ function hwbToRgb(_h, _w, _b, a) {
return [r, g, b, a];
}
function clamp(num, min, max) {
function clamp(num: number, min: number, max: number) {
return Math.min(Math.max(min, num), max);
}
@@ -287,7 +289,7 @@ const rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*(
const per = /^rgba?\(\s*([+-]?[\d.]+)%\s*,\s*([+-]?[\d.]+)%\s*,\s*([+-]?[\d.]+)%\s*(?:,\s*([+-]?[\d.]+)\s*)?\)$/;
const keyword = /(\D+)/;
function rgbFromString(string) {
function rgbFromString(string: string) {
let rgb = [0, 0, 0, 1];
let match;
let i;
@@ -339,13 +341,13 @@ function rgbFromString(string) {
return [0, 0, 0, 0];
}
rgb = colorNames[match[1]];
let color = colorNames[match[1]];
if (!(typeof rgb === 'number')) {
if (!(typeof color === 'number')) {
return null;
}
return integerColor(rgb);
return integerColor(color);
} else {
return null;
}
@@ -359,7 +361,7 @@ function rgbFromString(string) {
const hslRegEx = /^hsla?\(\s*([+-]?(?:\d*\.)?\d+)(?:deg)?\s*,\s*([+-]?[\d.]+)%\s*,\s*([+-]?[\d.]+)%\s*(?:,\s*([+-]?[\d.]+)\s*)?\)$/;
function rgbFromHslString(string) {
function rgbFromHslString(string: string) {
const match = string.match(hslRegEx);
if (!match) {
return null;
@@ -375,7 +377,7 @@ function rgbFromHslString(string) {
const hwbRegEx = /^hwb\(\s*([+-]?\d*[.]?\d+)(?:deg)?\s*,\s*([+-]?[\d.]+)%\s*,\s*([+-]?[\d.]+)%\s*(?:,\s*([+-]?[\d.]+)\s*)?\)$/;
function rgbFromHwbString(string) {
function rgbFromHwbString(string: string) {
const match = string.match(hwbRegEx);
if (!match) {
return null;
@@ -389,7 +391,7 @@ function rgbFromHwbString(string) {
return hwbToRgb(h, w, b, a);
}
function colorFromString(string) {
function colorFromString(string: string) {
const prefix = string.substring(0, 3).toLowerCase();
switch (prefix) {
@@ -402,9 +404,9 @@ function colorFromString(string) {
}
}
const identity = x => x;
const identity = (x: number) => x;
const toSignedInt32 = x => x | 0x0;
const toSignedInt32 = (x: number) => x | 0x0;
// Android use 32 bit *signed* integer to represent the color
// We utilize the fact that bitwise operations in JS also operates on
@@ -414,7 +416,7 @@ export const integerColor =
Platform.OS === 'android' ? toSignedInt32 : identity;
// Returns 0xaarrggbb or null
export default function extractColor(color) {
export default function extractColor(color: Color | void) {
if (typeof color === 'number') {
if (color >>> 0 === color && color >= 0 && color <= 0xffffffff) {
return integerColor(color);

View File

@@ -1,17 +1,21 @@
import extractBrush from './extractBrush';
import extractOpacity from './extractOpacity';
import { colorNames, integerColor } from './extractColor';
import { FillProps } from './types';
const fillRules = {
const fillRules: { evenodd: number; nonzero: number } = {
evenodd: 0,
nonzero: 1,
};
// default fill is black
const black = colorNames.black;
const defaultFill = [0, integerColor(black)];
const defaultFill = [0, integerColor(black as number)];
export default function extractFill(props, styleProperties) {
export default function extractFill(
props: FillProps,
styleProperties: string[],
) {
const { fill, fillRule, fillOpacity } = props;
if (fill != null) {
@@ -26,7 +30,7 @@ export default function extractFill(props, styleProperties) {
return {
fill: !fill && typeof fill !== 'number' ? defaultFill : extractBrush(fill),
fillRule: fillRules[fillRule] === 0 ? 0 : 1,
fillRule: fillRule && fillRules[fillRule] === 0 ? 0 : 1,
fillOpacity: extractOpacity(fillOpacity),
};
}

View File

@@ -1,13 +1,21 @@
import React, { Children } from 'react';
import React, { Children, ReactElement } from 'react';
import extractColor from './extractColor';
import extractOpacity from './extractOpacity';
import extractTransform from './extractTransform';
import { TransformProps } from './types';
import units from '../units';
const percentReg = /^([+\-]?\d+(?:\.\d+)?(?:[eE][+\-]?\d+)?)(%?)$/;
function percentToFloat(percent) {
function percentToFloat(
percent:
| number
| string
| {
__getAnimatedValue: () => number;
},
): number {
if (typeof percent === 'number') {
return percent;
}
@@ -23,23 +31,35 @@ function percentToFloat(percent) {
return 0;
}
return matched[2] ? matched[1] / 100 : +matched[1];
return matched[2] ? +matched[1] / 100 : +matched[1];
}
const offsetComparator = (object, other) => object[0] - other[0];
const offsetComparator = (object: number[], other: number[]) =>
object[0] - other[0];
export default function extractGradient(props, parent) {
export default function extractGradient(
props: {
id?: string;
children?: ReactElement[];
transform?: number[] | string | TransformProps;
gradientTransform?: number[] | string | TransformProps;
gradientUnits?: 'objectBoundingBox' | 'userSpaceOnUse';
} & TransformProps,
parent: {},
) {
const { id, children, gradientTransform, transform, gradientUnits } = props;
if (!id) {
return null;
}
const stops = [];
const childArray = Children.map(children, child =>
React.cloneElement(child, {
parent,
}),
);
const childArray = children
? Children.map(children, child =>
React.cloneElement(child, {
parent,
}),
)
: [];
const l = childArray.length;
for (let i = 0; i < l; i++) {
const {
@@ -69,7 +89,7 @@ export default function extractGradient(props, parent) {
name: id,
gradient,
children: childArray,
gradientUnits: units[gradientUnits] || 0,
gradientUnits: (gradientUnits && units[gradientUnits]) || 0,
gradientTransform: extractTransform(
gradientTransform || transform || props,
),

View File

@@ -1,7 +1,11 @@
import { NumberProp } from './types';
const spaceReg = /\s+/;
const commaReg = /,/g;
export default function extractLengthList(lengthList) {
export default function extractLengthList(
lengthList?: (NumberProp)[] | NumberProp,
): (NumberProp)[] {
if (Array.isArray(lengthList)) {
return lengthList;
} else if (typeof lengthList === 'number') {

View File

@@ -0,0 +1,6 @@
import { NumberProp } from './types';
export default function extractOpacity(opacity: NumberProp | void) {
const value = +opacity;
return isNaN(value) ? 1 : value;
}

View File

@@ -1,4 +1,6 @@
export default function extractPolyPoints(points) {
import { NumberProp } from './types';
export default function extractPolyPoints(points: string | (NumberProp)[]) {
const polyPoints = Array.isArray(points) ? points.join(',') : points;
return polyPoints
.replace(/[^e]-/, ' -')

View File

@@ -5,21 +5,52 @@ import extractClipPath from './extractClipPath';
import extractResponder from './extractResponder';
import extractOpacity from './extractOpacity';
import { idPattern } from '../util';
import {
ClipProps,
FillProps,
NumberProp,
ResponderProps,
StrokeProps,
TransformProps,
} from './types';
import { Component } from 'react';
export function propsAndStyles(props) {
export function propsAndStyles(props: Object & { style?: [] | {} }) {
const { style } = props;
return {
...(style && style.length ? Object.assign({}, ...style) : style),
...(Array.isArray(style) ? Object.assign({}, ...style) : style),
...props,
};
}
export default function extractProps(props, ref) {
export default function extractProps(
props: {
id?: string;
mask?: string;
clipPath?: string;
opacity?: NumberProp;
onLayout?: () => void;
transform?: number[] | string | TransformProps;
} & TransformProps &
ResponderProps &
StrokeProps &
FillProps &
ClipProps,
ref: Object,
) {
const { opacity, onLayout, id, clipPath, mask, transform } = props;
const styleProperties = [];
const styleProperties: string[] = [];
const transformProps = props2transform(props);
const matrix = transformToMatrix(transformProps, transform);
const extracted = {
const extracted: {
name?: string;
mask?: string;
opacity: number;
matrix: number[];
propList: string[];
onLayout?: () => void;
ref?: (instance: Component | null) => void;
} = {
matrix,
onLayout,
...transformProps,

View File

@@ -1,9 +1,14 @@
import { PanResponder } from 'react-native';
import { ResponderInstanceProps, ResponderProps } from './types';
const responderKeys = Object.keys(PanResponder.create({}).panHandlers);
const numResponderKeys = responderKeys.length;
export default function extractResponder(props, ref) {
export default function extractResponder(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
props: { [x: string]: any } & ResponderProps,
ref: ResponderInstanceProps,
) {
const {
onPress,
disabled,
@@ -15,7 +20,9 @@ export default function extractResponder(props, ref) {
delayLongPress,
pointerEvents,
} = props;
const o = {};
const o: {
[touchableProperty: string]: unknown;
} = {};
let responsible = false;
for (let i = 0; i < numResponderKeys; i++) {

View File

@@ -1,6 +1,7 @@
import extractBrush from './extractBrush';
import extractOpacity from './extractOpacity';
import extractLengthList from './extractLengthList';
import { StrokeProps } from './types';
const caps = {
butt: 0,
@@ -23,7 +24,10 @@ const vectorEffects = {
uri: 3,
};
export default function extractStroke(props, styleProperties) {
export default function extractStroke(
props: StrokeProps,
styleProperties: string[],
) {
const {
stroke,
strokeOpacity,
@@ -69,15 +73,19 @@ export default function extractStroke(props, styleProperties) {
return {
stroke: extractBrush(stroke),
strokeOpacity: extractOpacity(strokeOpacity),
strokeLinecap: caps[strokeLinecap] || 0,
strokeLinejoin: joins[strokeLinejoin] || 0,
strokeLinecap: (strokeLinecap && caps[strokeLinecap]) || 0,
strokeLinejoin: (strokeLinejoin && joins[strokeLinejoin]) || 0,
strokeDasharray:
strokeDash && strokeDash.length % 2 === 1
? strokeDash.concat(strokeDash)
: strokeDash,
strokeWidth: strokeWidth != null ? strokeWidth : 1,
strokeDashoffset: strokeDasharray ? +strokeDashoffset || 0 : null,
strokeMiterlimit: parseFloat(strokeMiterlimit) || 4,
vectorEffect: vectorEffects[vectorEffect] || 0,
strokeDashoffset:
strokeDasharray && strokeDashoffset ? +strokeDashoffset || 0 : null,
strokeMiterlimit:
(strokeMiterlimit && typeof strokeMiterlimit !== 'number'
? parseFloat(strokeMiterlimit)
: strokeMiterlimit) || 4,
vectorEffect: (vectorEffect && vectorEffects[vectorEffect]) || 0,
};
}

View File

@@ -1,15 +1,23 @@
import React, { Children } from 'react';
import React, { Children, ComponentType } from 'react';
import extractLengthList from './extractLengthList';
import { pickNotNil } from '../util';
import { NumberProp } from './types';
const fontRegExp = /^\s*((?:(?:normal|bold|italic)\s+)*)(?:(\d+(?:\.\d+)?(?:%|px|em|pt|pc|mm|cm|in]))*(?:\s*\/.*?)?\s+)?\s*"?([^"]*)/i;
const fontFamilyPrefix = /^[\s"']*/;
const fontFamilySuffix = /[\s"']*$/;
const commaReg = /\s*,\s*/g;
const cachedFontObjectsFromString = {};
const cachedFontObjectsFromString: {
[font: string]: {
fontStyle: string;
fontSize: NumberProp;
fontWeight: NumberProp;
fontFamily: string | null;
} | null;
} = {};
function extractSingleFontFamily(fontFamilyString) {
function extractSingleFontFamily(fontFamilyString?: string) {
// SVG on the web allows for multiple font-families to be specified.
// For compatibility, we extract the first font-family, hoping
// we'll get a match.
@@ -21,7 +29,7 @@ function extractSingleFontFamily(fontFamilyString) {
: null;
}
function parseFontString(font) {
function parseFontString(font: string) {
if (cachedFontObjectsFromString.hasOwnProperty(font)) {
return cachedFontObjectsFromString[font];
}
@@ -41,7 +49,26 @@ function parseFontString(font) {
return cachedFontObjectsFromString[font];
}
export function extractFont(props) {
interface fontProps {
fontData?: unknown;
fontStyle?: string;
fontVariant?: string;
fontWeight?: NumberProp;
fontStretch?: string;
fontSize?: NumberProp;
fontFamily?: string;
textAnchor?: string;
textDecoration?: string;
letterSpacing?: NumberProp;
wordSpacing?: NumberProp;
kerning?: NumberProp;
fontFeatureSettings?: string;
fontVariantLigatures?: string;
fontVariationSettings?: string;
font?: string;
}
export function extractFont(props: fontProps) {
const {
fontData,
fontStyle,
@@ -84,13 +111,13 @@ export function extractFont(props) {
return { ...baseFont, ...ownedFont };
}
let TSpan;
let TSpan: ComponentType;
export function setTSpan(TSpanImplementation) {
export function setTSpan(TSpanImplementation: ComponentType) {
TSpan = TSpanImplementation;
}
function getChild(child) {
function getChild(child: undefined | string | number | ComponentType) {
if (typeof child === 'string' || typeof child === 'number') {
return <TSpan>{String(child)}</TSpan>;
} else {
@@ -98,7 +125,20 @@ function getChild(child) {
}
}
export default function extractText(props, container) {
export type TextProps = {
x?: NumberProp;
y?: NumberProp;
dx?: NumberProp;
dy?: NumberProp;
rotate?: NumberProp;
children?: string | number | (string | number | ComponentType)[];
inlineSize?: NumberProp;
baselineShift?: NumberProp;
verticalAlign?: string;
alignmentBaseline?: string;
} & fontProps;
export default function extractText(props: TextProps, container: boolean) {
const {
x,
y,

View File

@@ -1,7 +1,9 @@
import { identity, reset, toArray, append, appendTransform } from '../Matrix2D';
import { append, appendTransform, identity, reset, toArray } from '../Matrix2D';
// @ts-ignore
import { parse } from './transform';
import { NumberProp, TransformedProps, TransformProps } from './types';
function appendTransformProps(props) {
function appendTransformProps(props: TransformedProps) {
const {
x,
y,
@@ -26,7 +28,12 @@ function appendTransformProps(props) {
);
}
function universal2axis(universal, axisX, axisY, defaultValue) {
function universal2axis(
universal: NumberProp | (NumberProp)[] | undefined,
axisX: NumberProp | void,
axisY: NumberProp | void,
defaultValue?: number,
) {
let x;
let y;
if (typeof universal === 'number') {
@@ -61,8 +68,9 @@ function universal2axis(universal, axisX, axisY, defaultValue) {
return [x || defaultValue || 0, y || defaultValue || 0];
}
export function props2transform(props) {
export function props2transform(props: TransformProps): TransformedProps {
const {
rotation = 0,
translate,
translateX,
translateY,
@@ -75,7 +83,6 @@ export function props2transform(props) {
skew,
skewX,
skewY,
rotation,
x,
y,
} = props;
@@ -98,7 +105,10 @@ export function props2transform(props) {
};
}
export function transformToMatrix(props, transform) {
export function transformToMatrix(
props: TransformedProps,
transform: number[] | string | TransformProps | void | undefined,
) {
reset();
appendTransformProps(props);
@@ -130,7 +140,9 @@ export function transformToMatrix(props, transform) {
return toArray();
}
export default function extractTransform(props) {
export default function extractTransform(
props: number[] | string | TransformProps,
) {
if (Array.isArray(props)) {
return props;
}

View File

@@ -1,10 +1,14 @@
export const meetOrSliceTypes = {
import { NumberProp } from './types';
export const meetOrSliceTypes: {
[meetOrSlice: string]: number;
} = {
meet: 0,
slice: 1,
none: 2,
};
export const alignEnum = [
export const alignEnum: { [align: string]: string } = [
'xMinYMin',
'xMidYMin',
'xMaxYMin',
@@ -15,14 +19,17 @@ export const alignEnum = [
'xMidYMax',
'xMaxYMax',
'none',
].reduce((prev, name) => {
].reduce((prev: { [align: string]: string }, name) => {
prev[name] = name;
return prev;
}, {});
const spacesRegExp = /\s+/;
export default function extractViewBox(props) {
export default function extractViewBox(props: {
viewBox?: string | (NumberProp)[];
preserveAspectRatio?: string;
}) {
const { viewBox, preserveAspectRatio } = props;
if (!viewBox) {
@@ -42,13 +49,15 @@ export default function extractViewBox(props) {
const modes = preserveAspectRatio
? preserveAspectRatio.trim().split(spacesRegExp)
: [];
const align = modes[0];
const meetOrSlice = modes[1];
return {
minX: params[0],
minY: params[1],
vbWidth: params[2],
vbHeight: params[3],
align: alignEnum[modes[0]] || 'xMidYMid',
meetOrSlice: meetOrSliceTypes[modes[1]] || 0,
align: alignEnum[align] || 'xMidYMid',
meetOrSlice: meetOrSliceTypes[meetOrSlice] || 0,
};
}

95
src/lib/extract/types.ts Normal file
View File

@@ -0,0 +1,95 @@
import { GestureResponderEvent } from 'react-native';
export type NumberProp = string | number;
export type NumberArray = (NumberProp)[] | NumberProp;
// rgbaArray = [r, g, b, a]
export type rgbaArray = number[];
// int32ARGBColor = 0xaarrggbb
export type Int32ARGBColor = number;
export type Color = Int32ARGBColor | rgbaArray | string;
export type Linecap = 'butt' | 'square' | 'round';
export type Linejoin = 'miter' | 'bevel' | 'round';
export type VectorEffect =
| 'none'
| 'non-scaling-stroke'
| 'nonScalingStroke'
| 'default'
| 'inherit'
| 'uri';
export interface TransformProps {
translate?: NumberProp;
translateX?: NumberProp;
translateY?: NumberProp;
origin?: NumberProp;
originX?: NumberProp;
originY?: NumberProp;
scale?: NumberProp;
scaleX?: NumberProp;
scaleY?: NumberProp;
skew?: NumberProp;
skewX?: NumberProp;
skewY?: NumberProp;
rotation?: NumberProp;
x?: NumberProp;
y?: NumberProp;
transform?: number[] | string | TransformProps | void | undefined;
}
export interface TransformedProps {
rotation: number;
originX: number;
originY: number;
scaleX: number;
scaleY: number;
skewX: number;
skewY: number;
x: number;
y: number;
}
export type ResponderProps = {
onPress?: () => void;
disabled?: boolean;
onPressIn?: () => void;
onPressOut?: () => void;
onLongPress?: () => void;
delayPressIn?: number;
delayPressOut?: number;
delayLongPress?: number;
pointerEvents?: string;
};
export type ResponderInstanceProps = {
touchableHandleResponderMove?: (e: GestureResponderEvent) => void;
touchableHandleResponderGrant?: (e: GestureResponderEvent) => void;
touchableHandleResponderRelease?: (e: GestureResponderEvent) => void;
touchableHandleResponderTerminate?: (e: GestureResponderEvent) => void;
touchableHandleStartShouldSetResponder?: (
e: GestureResponderEvent,
) => boolean;
touchableHandleResponderTerminationRequest?: (
e: GestureResponderEvent,
) => boolean;
};
export type FillProps = {
fill?: Color;
fillRule?: 'evenodd' | 'nonzero';
fillOpacity?: NumberProp;
};
export type StrokeProps = {
stroke?: Color;
strokeWidth?: NumberProp;
strokeOpacity?: NumberProp;
strokeDasharray?: NumberArray;
strokeDashoffset?: NumberProp;
strokeLinecap?: Linecap;
strokeLinejoin?: Linejoin;
strokeMiterlimit?: NumberProp;
vectorEffect?: VectorEffect;
};
export type ClipProps = {
clipPath?: string;
clipRule?: 'evenodd' | 'nonzero';
};

5
src/lib/units.ts Normal file
View File

@@ -0,0 +1,5 @@
export const units: { objectBoundingBox: number; userSpaceOnUse: number } = {
objectBoundingBox: 0,
userSpaceOnUse: 1,
};
export default units;

View File

@@ -1,5 +1,5 @@
export function pickNotNil(object) {
const result = {};
export function pickNotNil(object: { [prop: string]: unknown }) {
const result: { [prop: string]: unknown } = {};
for (const key in object) {
if (object.hasOwnProperty(key)) {
const value = object[key];

View File

@@ -1,4 +1,11 @@
import React, { Component, useState, useEffect, useMemo } from 'react';
import React, {
Component,
useState,
useEffect,
useMemo,
ReactElement,
ComponentType,
} from 'react';
import Rect from './elements/Rect';
import Circle from './elements/Circle';
import Ellipse from './elements/Ellipse';
@@ -22,7 +29,7 @@ import ClipPath from './elements/ClipPath';
import Pattern from './elements/Pattern';
import Mask from './elements/Mask';
export const tags = {
export const tags: { [tag: string]: ComponentType } = {
svg: Svg,
circle: Circle,
ellipse: Ellipse,
@@ -51,7 +58,14 @@ function missingTag() {
return null;
}
export function SvgAst({ ast, override }) {
interface AST {
tag: string;
children: (AST | string)[] | (ReactElement | string)[];
props: {};
Tag: ComponentType;
}
export function SvgAst({ ast, override }: { ast: AST; override?: Object }) {
const { props, children } = ast;
return (
<Svg {...props} {...override}>
@@ -60,20 +74,20 @@ export function SvgAst({ ast, override }) {
);
}
export function SvgXml(props) {
export function SvgXml(props: { xml: string; override?: Object }) {
const { xml, override } = props;
const ast = useMemo(() => xml && parse(xml), [xml]);
return (ast && <SvgAst ast={ast} override={override || props} />) || null;
}
async function fetchText(uri) {
async function fetchText(uri: string) {
const response = await fetch(uri);
return await response.text();
}
const err = console.error.bind(console);
export function SvgUri(props) {
export function SvgUri(props: { uri: string; override?: Object }) {
const { uri } = props;
const [xml, setXml] = useState();
useEffect(() => {
@@ -86,18 +100,18 @@ export function SvgUri(props) {
// Extending Component is required for Animated support.
export class SvgFromXml extends Component {
state = {};
export class SvgFromXml extends Component<{ xml: string; override?: Object }> {
state: { ast?: AST } = {};
componentDidMount() {
this.parse(this.props.xml);
}
componentDidUpdate(prevProps) {
componentDidUpdate(prevProps: { xml: string }) {
const { xml } = this.props;
if (xml !== prevProps.xml) {
this.parse(xml);
}
}
parse(xml) {
parse(xml: string) {
try {
this.setState({ ast: parse(xml) });
} catch (e) {
@@ -113,18 +127,18 @@ export class SvgFromXml extends Component {
}
}
export class SvgFromUri extends Component {
state = {};
export class SvgFromUri extends Component<{ uri: string; override?: Object }> {
state: { xml?: string } = {};
componentDidMount() {
this.fetch(this.props.uri);
}
componentDidUpdate(prevProps) {
componentDidUpdate(prevProps: { uri: string }) {
const { uri } = this.props;
if (uri !== prevProps.uri) {
this.fetch(uri);
}
}
async fetch(uri) {
async fetch(uri: string) {
try {
this.setState({ xml: await fetchText(uri) });
} catch (e) {
@@ -140,12 +154,15 @@ export class SvgFromUri extends Component {
}
}
const upperCase = (match, letter) => letter.toUpperCase();
const upperCase = (_match: string, letter: string) => letter.toUpperCase();
const camelCase = phrase => phrase.replace(/[:\-]([a-z])/g, upperCase);
const camelCase = (phrase: string) =>
phrase.replace(/[:\-]([a-z])/g, upperCase);
export function getStyle(string) {
const style = {};
type Styles = { [property: string]: string };
export function getStyle(string: string): Styles {
const style: Styles = {};
const declarations = string.split(';');
const { length } = declarations;
for (let i = 0; i < length; i++) {
@@ -160,21 +177,24 @@ export function getStyle(string) {
return style;
}
export function astToReact(child, i) {
if (typeof child === 'object') {
const { Tag, props, children } = child;
export function astToReact(
value: AST | string,
index: number,
): ReactElement | string {
if (typeof value === 'object') {
const { Tag, props, children } = value;
return (
<Tag key={i} {...props}>
{children.map(astToReact)}
<Tag key={index} {...props}>
{(children as (AST | string)[]).map(astToReact)}
</Tag>
);
}
return child;
return value;
}
// slimmed down parser based on https://github.com/Rich-Harris/svg-parser
function repeat(str, i) {
function repeat(str: string, i: number) {
let result = '';
while (i--) {
result += str;
@@ -182,9 +202,9 @@ function repeat(str, i) {
return result;
}
const toSpaces = tabs => repeat(' ', tabs.length);
const toSpaces = (tabs: string) => repeat(' ', tabs.length);
function locate(source, i) {
function locate(source: string, i: number) {
const lines = source.split('\n');
const nLines = lines.length;
let column = i;
@@ -198,9 +218,11 @@ function locate(source, i) {
}
}
const before = source.slice(0, i).replace(/^\t+/, toSpaces);
const beforeLine = /(^|\n).*$/.exec(before)[0];
const beforeExec = /(^|\n).*$/.exec(before);
const beforeLine = (beforeExec && beforeExec[0]) || '';
const after = source.slice(i);
const afterLine = /.*(\n|$)/.exec(after)[0];
const afterExec = /.*(\n|$)/.exec(after);
const afterLine = afterExec && afterExec[0];
const pad = repeat(' ', beforeLine.length);
const snippet = `${beforeLine}${afterLine}\n${pad}^`;
return { line, column, snippet };
@@ -210,15 +232,15 @@ const validNameCharacters = /[a-zA-Z0-9:_-]/;
const whitespace = /[\s\t\r\n]/;
const quotemarks = /['"]/;
export function parse(source) {
export function parse(source: string) {
const length = source.length;
let currentElement = null;
let currentElement: AST | null = null;
let state = metadata;
let children = null;
let root = null;
let stack = [];
let root: AST | null = null;
let stack: AST[] = [];
function error(message) {
function error(message: string) {
const { line, column, snippet } = locate(source, i);
throw new Error(
`${message} (${line}:${column}). If this is valid SVG, it's probably a bug. Please raise an issue\n\n${snippet}`,
@@ -281,8 +303,8 @@ export function parse(source) {
}
const tag = getName();
const props = {};
const element = {
const props: { style?: Styles | string } = {};
const element: AST = {
tag,
props,
children: [],
@@ -298,7 +320,7 @@ export function parse(source) {
getAttributes(props);
const { style } = props;
if (style) {
if (typeof style === 'string') {
props.style = getStyle(style);
}
@@ -349,7 +371,7 @@ export function parse(source) {
error('Expected tag name');
}
if (tag !== currentElement.tag) {
if (currentElement && tag !== currentElement.tag) {
error(
`Expected closing tag </${tag}> to match opening tag <${currentElement.tag}>`,
);
@@ -379,7 +401,10 @@ export function parse(source) {
return name;
}
function getAttributes(props) {
function getAttributes(props: {
[x: string]: Styles | string | number | boolean | undefined;
style?: string | Styles | undefined;
}) {
while (i < length) {
if (!whitespace.test(source[i])) {
return;
@@ -391,7 +416,7 @@ export function parse(source) {
return;
}
let value = true;
let value: boolean | number | string = true;
allowSpaces();
if (source[i] === '=') {
@@ -399,7 +424,7 @@ export function parse(source) {
allowSpaces();
value = getAttributeValue();
if (!isNaN(value) && value.trim() !== '') {
if (!isNaN(+value) && value.trim() !== '') {
value = +value;
}
}
@@ -408,7 +433,7 @@ export function parse(source) {
}
}
function getAttributeValue() {
function getAttributeValue(): string {
return quotemarks.test(source[i])
? getQuotedAttributeValue()
: getUnquotedAttributeValue();
@@ -448,6 +473,8 @@ export function parse(source) {
value += escaped ? `\\${char}` : char;
escaped = false;
}
return value;
}
function allowSpaces() {
@@ -469,8 +496,10 @@ export function parse(source) {
error('Unexpected end of input');
}
if (root) {
root.children = root.children.map(astToReact);
if (root && typeof root === 'object') {
const r: AST = root;
const ast: (AST | string)[] = r.children as (AST | string)[];
r.children = ast.map(astToReact);
}
return root;

26
tsconfig.json Normal file
View File

@@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "es2017",
"lib": ["es6"],
"jsx": "react-native",
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"types": ["react", "react-native"]
},
"files": [
"src/index.d.ts"
],
"include": ["src/index.ts"]
}