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/examples/
Example/android/ Example/android/
Example/ios/ Example/ios/
screenShoots/ screenshots/
android/ android/
ios/ 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 code
# #
experimental/ experimental/
/lib/

View File

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

View File

@@ -171,8 +171,10 @@ and run `pod install` from `ios` folder
### Troubleshooting ### Troubleshooting
#### Problems with Proguard #### Problems with Proguard
When Proguard is enabled (which it is by default for Android release builds), it causes runtine error 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`: To avoid this, add an exception to `android/app/proguard-rules.pro`:
```bash ```bash
-keep public class com.horcrux.svg.** {*;} -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: 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: Use the following code:
@@ -464,7 +466,7 @@ Colors set in the Svg element are inherited by its children:
</Svg> </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: Code explanation:
@@ -490,7 +492,7 @@ The <Rect> element is used to create a rectangle and variations of a rectangle s
</Svg> </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: Code explanation:
@@ -508,7 +510,7 @@ The <Circle> element is used to create a circle:
</Svg> </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: Code explanation:
@@ -535,7 +537,7 @@ An ellipse is closely related to a circle. The difference is that an ellipse has
</Svg> </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: Code explanation:
@@ -554,7 +556,7 @@ The <Line> element is an SVG basic shape, used to create a line connecting two p
</Svg> </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: Code explanation:
@@ -578,7 +580,7 @@ The <Polygon> element is used to create a graphic that contains at least three s
</Svg> </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: Code explanation:
@@ -599,7 +601,7 @@ The <Polyline> element is used to create any shape that consists of only straigh
</Svg> </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: Code explanation:
@@ -634,7 +636,7 @@ The following commands are available for path data:
</Svg> </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 #### Text
@@ -656,7 +658,7 @@ The <Text> element is used to define text.
</Svg> </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 #### TSpan
@@ -689,7 +691,7 @@ The <TSpan> element is used to draw multiple lines of text in SVG. Rather than h
</Svg> </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 #### TextPath
@@ -714,7 +716,7 @@ In addition to text drawn in a straight line, SVG also includes the ability to p
</Svg> </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 #### G
@@ -734,7 +736,7 @@ The <G> element is a container used to group other SVG elements. Transformations
</Svg> </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 #### 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. 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 #### Symbol
@@ -788,7 +790,7 @@ The SVG <Symbol> element is used to define reusable symbols. The shapes nested i
</Svg> </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 #### Defs
@@ -831,7 +833,7 @@ The <Image> element allows a raster image to be included in an Svg componenet.
</Svg> </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 #### ClipPath
@@ -883,7 +885,7 @@ The <ClipPath> SVG element defines a clipping path. A clipping path is used/refe
</Svg> </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 #### 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 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 - 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:_ _NOTICE:_
LinearGradient also supports percentage as prop: 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 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 - 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 #### 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). 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", "version": "9.8.5",
"name": "react-native-svg", "name": "react-native-svg",
"description": "SVG library for react-native", "description": "SVG library for react-native",
"homepage": "https://github.com/react-native-community/react-native-svg", "homepage": "https://github.com/react-native-community/react-native-svg",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/react-native-community/react-native-svg" "url": "https://github.com/react-native-community/react-native-svg"
}, },
"license": "MIT", "license": "MIT",
"main": "./index", "main": "lib/commonjs/index.js",
"keywords": [ "module": "lib/module/index.js",
"react-component", "react-native": "src/index.js",
"react-native", "types": "lib/typescript/src/index.d.ts",
"ios", "files": [
"android", "lib/",
"SVG", "src/"
"ART", ],
"VML", "@react-native-community/bob": {
"gradient" "source": "src",
], "output": "lib",
"scripts": { "targets": [
"lint": "eslint ./", "commonjs",
"format": "prettier index.js index.web.js xml.js README.md './{elements,lib}/*.js' './lib/extract/e*.js' --write", "module",
"peg": "pegjs -o ./lib/extract/transform.js ./lib/extract/transform.peg" "typescript"
}, ]
"peerDependencies": { },
"react": "*", "keywords": [
"react-native": ">=0.50.0" "react-component",
}, "react-native",
"dependencies": {}, "ios",
"devDependencies": { "android",
"@react-native-community/eslint-config": "^0.0.5", "SVG",
"babel-eslint": "^10.0.3", "ART",
"eslint": "^6.2.2", "VML",
"eslint-plugin-prettier": "^3.1.0", "gradient"
"eslint-plugin-react": "^7.14.3", ],
"pegjs": "^0.10.0", "scripts": {
"prettier": "^1.18.2", "lint": "eslint --ext .ts,.tsx src",
"react": "^16.9.0" "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",
"nativePackage": true "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 React from 'react';
import { requireNativeComponent } from 'react-native'; import { requireNativeComponent } from 'react-native';
import extractProps, { propsAndStyles } from '../lib/extract/extractProps'; import extractProps, { propsAndStyles } from '../lib/extract/extractProps';
import { NumberProp } from '../lib/extract/types';
import Shape from './Shape'; 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 displayName = 'Circle';
static defaultProps = { static defaultProps = {

View File

@@ -3,7 +3,11 @@ import { requireNativeComponent } from 'react-native';
import extractClipPath from '../lib/extract/extractClipPath'; import extractClipPath from '../lib/extract/extractClipPath';
import Shape from './Shape'; 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'; static displayName = 'ClipPath';
render() { render() {

View File

@@ -1,9 +1,15 @@
import React from 'react'; import React from 'react';
import { requireNativeComponent } from 'react-native'; import { requireNativeComponent } from 'react-native';
import extractProps, { propsAndStyles } from '../lib/extract/extractProps'; import extractProps, { propsAndStyles } from '../lib/extract/extractProps';
import { NumberProp } from '../lib/extract/types';
import Shape from './Shape'; 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 displayName = 'Ellipse';
static defaultProps = { static defaultProps = {

View File

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

View File

@@ -1,9 +1,20 @@
import React from 'react'; import React, { ReactElement } from 'react';
import { requireNativeComponent } from 'react-native'; import { requireNativeComponent } from 'react-native';
import extractGradient from '../lib/extract/extractGradient'; import extractGradient from '../lib/extract/extractGradient';
import { NumberProp, TransformProps } from '../lib/extract/types';
import Shape from './Shape'; 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 displayName = 'LinearGradient';
static defaultProps = { static defaultProps = {

View File

@@ -2,10 +2,20 @@ import React from 'react';
import { requireNativeComponent } from 'react-native'; import { requireNativeComponent } from 'react-native';
import extractTransform from '../lib/extract/extractTransform'; import extractTransform from '../lib/extract/extractTransform';
import extractProps, { propsAndStyles } from '../lib/extract/extractProps'; import extractProps, { propsAndStyles } from '../lib/extract/extractProps';
import { NumberProp, TransformProps } from '../lib/extract/types';
import units from '../lib/units'; import units from '../lib/units';
import Shape from './Shape'; 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 displayName = 'Mask';
static defaultProps = { static defaultProps = {

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,23 @@
import React from 'react'; import React, { ReactElement } from 'react';
import { requireNativeComponent } from 'react-native'; import { requireNativeComponent } from 'react-native';
import extractGradient from '../lib/extract/extractGradient'; import extractGradient from '../lib/extract/extractGradient';
import { NumberProp, TransformProps } from '../lib/extract/types';
import Shape from './Shape'; 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 displayName = 'RadialGradient';
static defaultProps = { static defaultProps = {

View File

@@ -1,9 +1,17 @@
import React from 'react'; import React from 'react';
import { requireNativeComponent } from 'react-native'; import { requireNativeComponent } from 'react-native';
import extractProps, { propsAndStyles } from '../lib/extract/extractProps'; import extractProps, { propsAndStyles } from '../lib/extract/extractProps';
import { NumberProp } from '../lib/extract/types';
import Shape from './Shape'; 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 displayName = 'Rect';
static defaultProps = { 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'; 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 displayName = 'Stop';
static defaultProps = { static defaultProps = {

View File

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

View File

@@ -3,7 +3,11 @@ import { requireNativeComponent } from 'react-native';
import extractViewBox from '../lib/extract/extractViewBox'; import extractViewBox from '../lib/extract/extractViewBox';
import Shape from './Shape'; 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'; static displayName = 'Symbol';
render() { render() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -62,7 +62,14 @@ export function toArray() {
* @param {Number} tx2 * @param {Number} tx2
* @param {Number} ty2 * @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 change = a2 !== 1 || b2 !== 0 || c2 !== 0 || d2 !== 1;
const translate = tx2 !== 0 || ty2 !== 0; const translate = tx2 !== 0 || ty2 !== 0;
if (!change && !translate) { if (!change && !translate) {
@@ -114,15 +121,15 @@ export function append(a2, b2, c2, d2, tx2, ty2) {
* @param {Number} regY Optional. * @param {Number} regY Optional.
**/ **/
export function appendTransform( export function appendTransform(
x, x: number,
y, y: number,
scaleX, scaleX: number,
scaleY, scaleY: number,
rotation, rotation: number,
skewX, skewX: number,
skewY, skewY: number,
regX, regX: number,
regY, regY: number,
) { ) {
if ( if (
x === 0 && 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 extractColor, { integerColor } from './extractColor';
import { Color } from './types';
const urlIdPattern = /^url\(#(.+)\)$/; const urlIdPattern = /^url\(#(.+)\)$/;
const currentColorBrush = [2]; const currentColorBrush = [2];
export default function extractBrush(color) { export default function extractBrush(color?: Color) {
if (typeof color === 'number') { if (typeof color === 'number') {
if (color >>> 0 === color && color >= 0 && color <= 0xffffffff) { if (color >>> 0 === color && color >= 0 && color <= 0xffffffff) {
return [0, integerColor(color)]; return [0, integerColor(color)];

View File

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

View File

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

View File

@@ -1,17 +1,21 @@
import extractBrush from './extractBrush'; import extractBrush from './extractBrush';
import extractOpacity from './extractOpacity'; import extractOpacity from './extractOpacity';
import { colorNames, integerColor } from './extractColor'; import { colorNames, integerColor } from './extractColor';
import { FillProps } from './types';
const fillRules = { const fillRules: { evenodd: number; nonzero: number } = {
evenodd: 0, evenodd: 0,
nonzero: 1, nonzero: 1,
}; };
// default fill is black // default fill is black
const black = colorNames.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; const { fill, fillRule, fillOpacity } = props;
if (fill != null) { if (fill != null) {
@@ -26,7 +30,7 @@ export default function extractFill(props, styleProperties) {
return { return {
fill: !fill && typeof fill !== 'number' ? defaultFill : extractBrush(fill), fill: !fill && typeof fill !== 'number' ? defaultFill : extractBrush(fill),
fillRule: fillRules[fillRule] === 0 ? 0 : 1, fillRule: fillRule && fillRules[fillRule] === 0 ? 0 : 1,
fillOpacity: extractOpacity(fillOpacity), 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 extractColor from './extractColor';
import extractOpacity from './extractOpacity'; import extractOpacity from './extractOpacity';
import extractTransform from './extractTransform'; import extractTransform from './extractTransform';
import { TransformProps } from './types';
import units from '../units'; import units from '../units';
const percentReg = /^([+\-]?\d+(?:\.\d+)?(?:[eE][+\-]?\d+)?)(%?)$/; const percentReg = /^([+\-]?\d+(?:\.\d+)?(?:[eE][+\-]?\d+)?)(%?)$/;
function percentToFloat(percent) { function percentToFloat(
percent:
| number
| string
| {
__getAnimatedValue: () => number;
},
): number {
if (typeof percent === 'number') { if (typeof percent === 'number') {
return percent; return percent;
} }
@@ -23,23 +31,35 @@ function percentToFloat(percent) {
return 0; 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; const { id, children, gradientTransform, transform, gradientUnits } = props;
if (!id) { if (!id) {
return null; return null;
} }
const stops = []; const stops = [];
const childArray = Children.map(children, child => const childArray = children
React.cloneElement(child, { ? Children.map(children, child =>
parent, React.cloneElement(child, {
}), parent,
); }),
)
: [];
const l = childArray.length; const l = childArray.length;
for (let i = 0; i < l; i++) { for (let i = 0; i < l; i++) {
const { const {
@@ -69,7 +89,7 @@ export default function extractGradient(props, parent) {
name: id, name: id,
gradient, gradient,
children: childArray, children: childArray,
gradientUnits: units[gradientUnits] || 0, gradientUnits: (gradientUnits && units[gradientUnits]) || 0,
gradientTransform: extractTransform( gradientTransform: extractTransform(
gradientTransform || transform || props, gradientTransform || transform || props,
), ),

View File

@@ -1,7 +1,11 @@
import { NumberProp } from './types';
const spaceReg = /\s+/; const spaceReg = /\s+/;
const commaReg = /,/g; const commaReg = /,/g;
export default function extractLengthList(lengthList) { export default function extractLengthList(
lengthList?: (NumberProp)[] | NumberProp,
): (NumberProp)[] {
if (Array.isArray(lengthList)) { if (Array.isArray(lengthList)) {
return lengthList; return lengthList;
} else if (typeof lengthList === 'number') { } 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; const polyPoints = Array.isArray(points) ? points.join(',') : points;
return polyPoints return polyPoints
.replace(/[^e]-/, ' -') .replace(/[^e]-/, ' -')

View File

@@ -5,21 +5,52 @@ import extractClipPath from './extractClipPath';
import extractResponder from './extractResponder'; import extractResponder from './extractResponder';
import extractOpacity from './extractOpacity'; import extractOpacity from './extractOpacity';
import { idPattern } from '../util'; 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; const { style } = props;
return { return {
...(style && style.length ? Object.assign({}, ...style) : style), ...(Array.isArray(style) ? Object.assign({}, ...style) : style),
...props, ...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 { opacity, onLayout, id, clipPath, mask, transform } = props;
const styleProperties = []; const styleProperties: string[] = [];
const transformProps = props2transform(props); const transformProps = props2transform(props);
const matrix = transformToMatrix(transformProps, transform); 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, matrix,
onLayout, onLayout,
...transformProps, ...transformProps,

View File

@@ -1,9 +1,14 @@
import { PanResponder } from 'react-native'; import { PanResponder } from 'react-native';
import { ResponderInstanceProps, ResponderProps } from './types';
const responderKeys = Object.keys(PanResponder.create({}).panHandlers); const responderKeys = Object.keys(PanResponder.create({}).panHandlers);
const numResponderKeys = responderKeys.length; 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 { const {
onPress, onPress,
disabled, disabled,
@@ -15,7 +20,9 @@ export default function extractResponder(props, ref) {
delayLongPress, delayLongPress,
pointerEvents, pointerEvents,
} = props; } = props;
const o = {}; const o: {
[touchableProperty: string]: unknown;
} = {};
let responsible = false; let responsible = false;
for (let i = 0; i < numResponderKeys; i++) { for (let i = 0; i < numResponderKeys; i++) {

View File

@@ -1,6 +1,7 @@
import extractBrush from './extractBrush'; import extractBrush from './extractBrush';
import extractOpacity from './extractOpacity'; import extractOpacity from './extractOpacity';
import extractLengthList from './extractLengthList'; import extractLengthList from './extractLengthList';
import { StrokeProps } from './types';
const caps = { const caps = {
butt: 0, butt: 0,
@@ -23,7 +24,10 @@ const vectorEffects = {
uri: 3, uri: 3,
}; };
export default function extractStroke(props, styleProperties) { export default function extractStroke(
props: StrokeProps,
styleProperties: string[],
) {
const { const {
stroke, stroke,
strokeOpacity, strokeOpacity,
@@ -69,15 +73,19 @@ export default function extractStroke(props, styleProperties) {
return { return {
stroke: extractBrush(stroke), stroke: extractBrush(stroke),
strokeOpacity: extractOpacity(strokeOpacity), strokeOpacity: extractOpacity(strokeOpacity),
strokeLinecap: caps[strokeLinecap] || 0, strokeLinecap: (strokeLinecap && caps[strokeLinecap]) || 0,
strokeLinejoin: joins[strokeLinejoin] || 0, strokeLinejoin: (strokeLinejoin && joins[strokeLinejoin]) || 0,
strokeDasharray: strokeDasharray:
strokeDash && strokeDash.length % 2 === 1 strokeDash && strokeDash.length % 2 === 1
? strokeDash.concat(strokeDash) ? strokeDash.concat(strokeDash)
: strokeDash, : strokeDash,
strokeWidth: strokeWidth != null ? strokeWidth : 1, strokeWidth: strokeWidth != null ? strokeWidth : 1,
strokeDashoffset: strokeDasharray ? +strokeDashoffset || 0 : null, strokeDashoffset:
strokeMiterlimit: parseFloat(strokeMiterlimit) || 4, strokeDasharray && strokeDashoffset ? +strokeDashoffset || 0 : null,
vectorEffect: vectorEffects[vectorEffect] || 0, 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 extractLengthList from './extractLengthList';
import { pickNotNil } from '../util'; 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 fontRegExp = /^\s*((?:(?:normal|bold|italic)\s+)*)(?:(\d+(?:\.\d+)?(?:%|px|em|pt|pc|mm|cm|in]))*(?:\s*\/.*?)?\s+)?\s*"?([^"]*)/i;
const fontFamilyPrefix = /^[\s"']*/; const fontFamilyPrefix = /^[\s"']*/;
const fontFamilySuffix = /[\s"']*$/; const fontFamilySuffix = /[\s"']*$/;
const commaReg = /\s*,\s*/g; 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. // SVG on the web allows for multiple font-families to be specified.
// For compatibility, we extract the first font-family, hoping // For compatibility, we extract the first font-family, hoping
// we'll get a match. // we'll get a match.
@@ -21,7 +29,7 @@ function extractSingleFontFamily(fontFamilyString) {
: null; : null;
} }
function parseFontString(font) { function parseFontString(font: string) {
if (cachedFontObjectsFromString.hasOwnProperty(font)) { if (cachedFontObjectsFromString.hasOwnProperty(font)) {
return cachedFontObjectsFromString[font]; return cachedFontObjectsFromString[font];
} }
@@ -41,7 +49,26 @@ function parseFontString(font) {
return cachedFontObjectsFromString[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 { const {
fontData, fontData,
fontStyle, fontStyle,
@@ -84,13 +111,13 @@ export function extractFont(props) {
return { ...baseFont, ...ownedFont }; return { ...baseFont, ...ownedFont };
} }
let TSpan; let TSpan: ComponentType;
export function setTSpan(TSpanImplementation) { export function setTSpan(TSpanImplementation: ComponentType) {
TSpan = TSpanImplementation; TSpan = TSpanImplementation;
} }
function getChild(child) { function getChild(child: undefined | string | number | ComponentType) {
if (typeof child === 'string' || typeof child === 'number') { if (typeof child === 'string' || typeof child === 'number') {
return <TSpan>{String(child)}</TSpan>; return <TSpan>{String(child)}</TSpan>;
} else { } 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 { const {
x, x,
y, 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 { parse } from './transform';
import { NumberProp, TransformedProps, TransformProps } from './types';
function appendTransformProps(props) { function appendTransformProps(props: TransformedProps) {
const { const {
x, x,
y, 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 x;
let y; let y;
if (typeof universal === 'number') { if (typeof universal === 'number') {
@@ -61,8 +68,9 @@ function universal2axis(universal, axisX, axisY, defaultValue) {
return [x || defaultValue || 0, y || defaultValue || 0]; return [x || defaultValue || 0, y || defaultValue || 0];
} }
export function props2transform(props) { export function props2transform(props: TransformProps): TransformedProps {
const { const {
rotation = 0,
translate, translate,
translateX, translateX,
translateY, translateY,
@@ -75,7 +83,6 @@ export function props2transform(props) {
skew, skew,
skewX, skewX,
skewY, skewY,
rotation,
x, x,
y, y,
} = props; } = 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(); reset();
appendTransformProps(props); appendTransformProps(props);
@@ -130,7 +140,9 @@ export function transformToMatrix(props, transform) {
return toArray(); return toArray();
} }
export default function extractTransform(props) { export default function extractTransform(
props: number[] | string | TransformProps,
) {
if (Array.isArray(props)) { if (Array.isArray(props)) {
return props; return props;
} }

View File

@@ -1,10 +1,14 @@
export const meetOrSliceTypes = { import { NumberProp } from './types';
export const meetOrSliceTypes: {
[meetOrSlice: string]: number;
} = {
meet: 0, meet: 0,
slice: 1, slice: 1,
none: 2, none: 2,
}; };
export const alignEnum = [ export const alignEnum: { [align: string]: string } = [
'xMinYMin', 'xMinYMin',
'xMidYMin', 'xMidYMin',
'xMaxYMin', 'xMaxYMin',
@@ -15,14 +19,17 @@ export const alignEnum = [
'xMidYMax', 'xMidYMax',
'xMaxYMax', 'xMaxYMax',
'none', 'none',
].reduce((prev, name) => { ].reduce((prev: { [align: string]: string }, name) => {
prev[name] = name; prev[name] = name;
return prev; return prev;
}, {}); }, {});
const spacesRegExp = /\s+/; const spacesRegExp = /\s+/;
export default function extractViewBox(props) { export default function extractViewBox(props: {
viewBox?: string | (NumberProp)[];
preserveAspectRatio?: string;
}) {
const { viewBox, preserveAspectRatio } = props; const { viewBox, preserveAspectRatio } = props;
if (!viewBox) { if (!viewBox) {
@@ -42,13 +49,15 @@ export default function extractViewBox(props) {
const modes = preserveAspectRatio const modes = preserveAspectRatio
? preserveAspectRatio.trim().split(spacesRegExp) ? preserveAspectRatio.trim().split(spacesRegExp)
: []; : [];
const align = modes[0];
const meetOrSlice = modes[1];
return { return {
minX: params[0], minX: params[0],
minY: params[1], minY: params[1],
vbWidth: params[2], vbWidth: params[2],
vbHeight: params[3], vbHeight: params[3],
align: alignEnum[modes[0]] || 'xMidYMid', align: alignEnum[align] || 'xMidYMid',
meetOrSlice: meetOrSliceTypes[modes[1]] || 0, 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) { export function pickNotNil(object: { [prop: string]: unknown }) {
const result = {}; const result: { [prop: string]: unknown } = {};
for (const key in object) { for (const key in object) {
if (object.hasOwnProperty(key)) { if (object.hasOwnProperty(key)) {
const value = object[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 Rect from './elements/Rect';
import Circle from './elements/Circle'; import Circle from './elements/Circle';
import Ellipse from './elements/Ellipse'; import Ellipse from './elements/Ellipse';
@@ -22,7 +29,7 @@ import ClipPath from './elements/ClipPath';
import Pattern from './elements/Pattern'; import Pattern from './elements/Pattern';
import Mask from './elements/Mask'; import Mask from './elements/Mask';
export const tags = { export const tags: { [tag: string]: ComponentType } = {
svg: Svg, svg: Svg,
circle: Circle, circle: Circle,
ellipse: Ellipse, ellipse: Ellipse,
@@ -51,7 +58,14 @@ function missingTag() {
return null; 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; const { props, children } = ast;
return ( return (
<Svg {...props} {...override}> <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 { xml, override } = props;
const ast = useMemo(() => xml && parse(xml), [xml]); const ast = useMemo(() => xml && parse(xml), [xml]);
return (ast && <SvgAst ast={ast} override={override || props} />) || null; return (ast && <SvgAst ast={ast} override={override || props} />) || null;
} }
async function fetchText(uri) { async function fetchText(uri: string) {
const response = await fetch(uri); const response = await fetch(uri);
return await response.text(); return await response.text();
} }
const err = console.error.bind(console); const err = console.error.bind(console);
export function SvgUri(props) { export function SvgUri(props: { uri: string; override?: Object }) {
const { uri } = props; const { uri } = props;
const [xml, setXml] = useState(); const [xml, setXml] = useState();
useEffect(() => { useEffect(() => {
@@ -86,18 +100,18 @@ export function SvgUri(props) {
// Extending Component is required for Animated support. // Extending Component is required for Animated support.
export class SvgFromXml extends Component { export class SvgFromXml extends Component<{ xml: string; override?: Object }> {
state = {}; state: { ast?: AST } = {};
componentDidMount() { componentDidMount() {
this.parse(this.props.xml); this.parse(this.props.xml);
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps: { xml: string }) {
const { xml } = this.props; const { xml } = this.props;
if (xml !== prevProps.xml) { if (xml !== prevProps.xml) {
this.parse(xml); this.parse(xml);
} }
} }
parse(xml) { parse(xml: string) {
try { try {
this.setState({ ast: parse(xml) }); this.setState({ ast: parse(xml) });
} catch (e) { } catch (e) {
@@ -113,18 +127,18 @@ export class SvgFromXml extends Component {
} }
} }
export class SvgFromUri extends Component { export class SvgFromUri extends Component<{ uri: string; override?: Object }> {
state = {}; state: { xml?: string } = {};
componentDidMount() { componentDidMount() {
this.fetch(this.props.uri); this.fetch(this.props.uri);
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps: { uri: string }) {
const { uri } = this.props; const { uri } = this.props;
if (uri !== prevProps.uri) { if (uri !== prevProps.uri) {
this.fetch(uri); this.fetch(uri);
} }
} }
async fetch(uri) { async fetch(uri: string) {
try { try {
this.setState({ xml: await fetchText(uri) }); this.setState({ xml: await fetchText(uri) });
} catch (e) { } 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) { type Styles = { [property: string]: string };
const style = {};
export function getStyle(string: string): Styles {
const style: Styles = {};
const declarations = string.split(';'); const declarations = string.split(';');
const { length } = declarations; const { length } = declarations;
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
@@ -160,21 +177,24 @@ export function getStyle(string) {
return style; return style;
} }
export function astToReact(child, i) { export function astToReact(
if (typeof child === 'object') { value: AST | string,
const { Tag, props, children } = child; index: number,
): ReactElement | string {
if (typeof value === 'object') {
const { Tag, props, children } = value;
return ( return (
<Tag key={i} {...props}> <Tag key={index} {...props}>
{children.map(astToReact)} {(children as (AST | string)[]).map(astToReact)}
</Tag> </Tag>
); );
} }
return child; return value;
} }
// slimmed down parser based on https://github.com/Rich-Harris/svg-parser // slimmed down parser based on https://github.com/Rich-Harris/svg-parser
function repeat(str, i) { function repeat(str: string, i: number) {
let result = ''; let result = '';
while (i--) { while (i--) {
result += str; result += str;
@@ -182,9 +202,9 @@ function repeat(str, i) {
return result; 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 lines = source.split('\n');
const nLines = lines.length; const nLines = lines.length;
let column = i; let column = i;
@@ -198,9 +218,11 @@ function locate(source, i) {
} }
} }
const before = source.slice(0, i).replace(/^\t+/, toSpaces); 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 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 pad = repeat(' ', beforeLine.length);
const snippet = `${beforeLine}${afterLine}\n${pad}^`; const snippet = `${beforeLine}${afterLine}\n${pad}^`;
return { line, column, snippet }; return { line, column, snippet };
@@ -210,15 +232,15 @@ const validNameCharacters = /[a-zA-Z0-9:_-]/;
const whitespace = /[\s\t\r\n]/; const whitespace = /[\s\t\r\n]/;
const quotemarks = /['"]/; const quotemarks = /['"]/;
export function parse(source) { export function parse(source: string) {
const length = source.length; const length = source.length;
let currentElement = null; let currentElement: AST | null = null;
let state = metadata; let state = metadata;
let children = null; let children = null;
let root = null; let root: AST | null = null;
let stack = []; let stack: AST[] = [];
function error(message) { function error(message: string) {
const { line, column, snippet } = locate(source, i); const { line, column, snippet } = locate(source, i);
throw new Error( throw new Error(
`${message} (${line}:${column}). If this is valid SVG, it's probably a bug. Please raise an issue\n\n${snippet}`, `${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 tag = getName();
const props = {}; const props: { style?: Styles | string } = {};
const element = { const element: AST = {
tag, tag,
props, props,
children: [], children: [],
@@ -298,7 +320,7 @@ export function parse(source) {
getAttributes(props); getAttributes(props);
const { style } = props; const { style } = props;
if (style) { if (typeof style === 'string') {
props.style = getStyle(style); props.style = getStyle(style);
} }
@@ -349,7 +371,7 @@ export function parse(source) {
error('Expected tag name'); error('Expected tag name');
} }
if (tag !== currentElement.tag) { if (currentElement && tag !== currentElement.tag) {
error( error(
`Expected closing tag </${tag}> to match opening tag <${currentElement.tag}>`, `Expected closing tag </${tag}> to match opening tag <${currentElement.tag}>`,
); );
@@ -379,7 +401,10 @@ export function parse(source) {
return name; return name;
} }
function getAttributes(props) { function getAttributes(props: {
[x: string]: Styles | string | number | boolean | undefined;
style?: string | Styles | undefined;
}) {
while (i < length) { while (i < length) {
if (!whitespace.test(source[i])) { if (!whitespace.test(source[i])) {
return; return;
@@ -391,7 +416,7 @@ export function parse(source) {
return; return;
} }
let value = true; let value: boolean | number | string = true;
allowSpaces(); allowSpaces();
if (source[i] === '=') { if (source[i] === '=') {
@@ -399,7 +424,7 @@ export function parse(source) {
allowSpaces(); allowSpaces();
value = getAttributeValue(); value = getAttributeValue();
if (!isNaN(value) && value.trim() !== '') { if (!isNaN(+value) && value.trim() !== '') {
value = +value; value = +value;
} }
} }
@@ -408,7 +433,7 @@ export function parse(source) {
} }
} }
function getAttributeValue() { function getAttributeValue(): string {
return quotemarks.test(source[i]) return quotemarks.test(source[i])
? getQuotedAttributeValue() ? getQuotedAttributeValue()
: getUnquotedAttributeValue(); : getUnquotedAttributeValue();
@@ -448,6 +473,8 @@ export function parse(source) {
value += escaped ? `\\${char}` : char; value += escaped ? `\\${char}` : char;
escaped = false; escaped = false;
} }
return value;
} }
function allowSpaces() { function allowSpaces() {
@@ -469,8 +496,10 @@ export function parse(source) {
error('Unexpected end of input'); error('Unexpected end of input');
} }
if (root) { if (root && typeof root === 'object') {
root.children = root.children.map(astToReact); const r: AST = root;
const ast: (AST | string)[] = r.children as (AST | string)[];
r.children = ast.map(astToReact);
} }
return root; 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"]
}