mirror of
https://github.com/zoriya/react-native-svg.git
synced 2025-12-06 07:06:11 +00:00
fix: circular-dependencies (#2381)
# Summary - Require cycles are allowed, but can result in uninitialized values. Consider refactoring to remove the need for a cycle. - extract SVG web components. - extract web types. - extract utils function. ## Test Plan You can test that problem by running a test example `Test1813` ### What are the steps to reproduce (after prerequisites)? Without those changes, you can occur that problem by running a test example `Test1813` ## Compatibility | OS | Implemented | | ------- | :---------: | | iOS | ✅ | | Android | ✅ | --------- Co-authored-by: Jakub Grzywacz <jakub.grzywacz@swmansion.com>
This commit is contained in:
@@ -1,18 +1,17 @@
|
||||
import React from 'react';
|
||||
import { PlatformColor, Platform, Button, DynamicColorIOS } from 'react-native';
|
||||
import {
|
||||
Svg,
|
||||
Circle,
|
||||
Rect,
|
||||
Text,
|
||||
TSpan,
|
||||
} from 'react-native-svg';
|
||||
import {PlatformColor, Platform, Button, DynamicColorIOS} from 'react-native';
|
||||
import {Svg, Circle, Rect, Text, TSpan} from 'react-native-svg';
|
||||
|
||||
const color = PlatformColor(Platform.select({
|
||||
ios: 'systemTealColor',
|
||||
android: '@android:color/holo_blue_bright',
|
||||
default: 'black',
|
||||
}))
|
||||
const color =
|
||||
Platform.OS !== 'web'
|
||||
? PlatformColor(
|
||||
Platform.select({
|
||||
ios: 'systemTealColor',
|
||||
android: '@android:color/holo_blue_bright',
|
||||
default: 'black',
|
||||
}),
|
||||
)
|
||||
: 'black';
|
||||
|
||||
// const customContrastDynamicTextColor = DynamicColorIOS({
|
||||
// dark: 'hsla(360, 40%, 30%, 1.0)',
|
||||
@@ -27,13 +26,7 @@ export default () => {
|
||||
return (
|
||||
<>
|
||||
<Svg height="100" width="100" color={color}>
|
||||
<Circle
|
||||
cx="50"
|
||||
cy="50"
|
||||
r={test}
|
||||
strokeWidth="2.5"
|
||||
fill={color}
|
||||
/>
|
||||
<Circle cx="50" cy="50" r={test} strokeWidth="2.5" fill={color} />
|
||||
<Rect
|
||||
x="15"
|
||||
y="15"
|
||||
@@ -45,12 +38,13 @@ export default () => {
|
||||
</Svg>
|
||||
<Svg height="300" width="300" fill="red">
|
||||
<Text x={0} y={0} fontSize={20}>
|
||||
<TSpan dx={test} inlineSize={"100%"} fill="currentColor">
|
||||
Testing word-wrap... Testing word-wrap... Testing word-wrap... Testing word-wrap...
|
||||
<TSpan dx={test} inlineSize={'100%'} fill="currentColor">
|
||||
Testing word-wrap... Testing word-wrap... Testing word-wrap...
|
||||
Testing word-wrap...
|
||||
</TSpan>
|
||||
</Text>
|
||||
</Svg>
|
||||
<Button title="Click me" onPress={()=> setTest(test + 1)}/>
|
||||
<Button title="Click me" onPress={() => setTest(test + 1)} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,61 +1,65 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
SafeAreaView,
|
||||
useColorScheme,
|
||||
} from 'react-native';
|
||||
import {Svg, Ellipse} from 'react-native-svg';
|
||||
import Animated, {createAnimatedPropAdapter, processColor, useAnimatedProps, useSharedValue, withRepeat, withTiming} from 'react-native-reanimated';
|
||||
|
||||
import {Colors} from 'react-native/Libraries/NewAppScreen';
|
||||
|
||||
const AnimatedEllipse = Animated.createAnimatedComponent(Ellipse);
|
||||
|
||||
const App = () => {
|
||||
const isDarkMode = useColorScheme() === 'dark';
|
||||
const offset = useSharedValue(0);
|
||||
offset.value = withRepeat(withTiming(1.0), -1, true);
|
||||
const backgroundStyle = {
|
||||
backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
|
||||
flex: 1,
|
||||
};
|
||||
|
||||
const ellipseAnimatedProps =
|
||||
useAnimatedProps(() =>
|
||||
{
|
||||
const coordinates = {cx: 50, cy: 50, rx: 40, ry: 40};
|
||||
|
||||
return {
|
||||
cx: coordinates.cx,
|
||||
cy: coordinates.cy,
|
||||
rx: coordinates.rx,
|
||||
ry: coordinates.ry,
|
||||
stroke: 'rgb(255,0,0)',
|
||||
fill: 'yellow',
|
||||
opacity: offset.value,
|
||||
strokeWidth: 2,
|
||||
};
|
||||
}
|
||||
, [], createAnimatedPropAdapter(
|
||||
(props) => {
|
||||
if (Object.keys(props).includes('fill')) {
|
||||
props.fill = {type: 0, payload: processColor(props.fill)}
|
||||
}
|
||||
if (Object.keys(props).includes('stroke')) {
|
||||
props.stroke = {type: 0, payload: processColor(props.stroke)}
|
||||
}
|
||||
import React from 'react';
|
||||
import {SafeAreaView, useColorScheme} from 'react-native';
|
||||
import {Svg, Ellipse} from 'react-native-svg';
|
||||
import Animated, {
|
||||
createAnimatedPropAdapter,
|
||||
processColor,
|
||||
useAnimatedProps,
|
||||
useSharedValue,
|
||||
withRepeat,
|
||||
withTiming,
|
||||
} from 'react-native-reanimated';
|
||||
|
||||
const AnimatedEllipse = Animated.createAnimatedComponent(Ellipse);
|
||||
|
||||
const App = () => {
|
||||
const isDarkMode = useColorScheme() === 'dark';
|
||||
const offset = useSharedValue(0);
|
||||
offset.value = withRepeat(withTiming(1.0), -1, true);
|
||||
const backgroundStyle = {
|
||||
backgroundColor: isDarkMode ? '#333333' : '#fafafa',
|
||||
flex: 1,
|
||||
};
|
||||
|
||||
const ellipseAnimatedProps = useAnimatedProps(
|
||||
() => {
|
||||
const coordinates = {cx: 50, cy: 50, rx: 40, ry: 40};
|
||||
|
||||
return {
|
||||
cx: coordinates.cx,
|
||||
cy: coordinates.cy,
|
||||
rx: coordinates.rx,
|
||||
ry: coordinates.ry,
|
||||
stroke: 'rgb(255,0,0)',
|
||||
fill: 'yellow',
|
||||
opacity: offset.value,
|
||||
strokeWidth: 2,
|
||||
};
|
||||
},
|
||||
['fill', 'stroke']));
|
||||
|
||||
return (
|
||||
<SafeAreaView style={backgroundStyle}>
|
||||
<Svg width="100%" height="100%">
|
||||
<AnimatedEllipse
|
||||
// {...coordinates}
|
||||
animatedProps={ellipseAnimatedProps}
|
||||
[],
|
||||
createAnimatedPropAdapter(
|
||||
props => {
|
||||
if (Object.keys(props).includes('fill')) {
|
||||
props.fill = {type: 0, payload: processColor(props.fill)};
|
||||
}
|
||||
if (Object.keys(props).includes('stroke')) {
|
||||
props.stroke = {type: 0, payload: processColor(props.stroke)};
|
||||
}
|
||||
},
|
||||
['fill', 'stroke'],
|
||||
),
|
||||
);
|
||||
|
||||
return (
|
||||
<SafeAreaView style={backgroundStyle}>
|
||||
<Svg width="100%" height="100%">
|
||||
<AnimatedEllipse
|
||||
// {...coordinates}
|
||||
animatedProps={ellipseAnimatedProps}
|
||||
/>
|
||||
</Svg>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
</Svg>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -1,51 +1,23 @@
|
||||
import Shape from './elements/Shape';
|
||||
import Rect from './elements/Rect';
|
||||
import Circle from './elements/Circle';
|
||||
import Ellipse from './elements/Ellipse';
|
||||
import Polygon from './elements/Polygon';
|
||||
import Polyline from './elements/Polyline';
|
||||
import Line from './elements/Line';
|
||||
import Svg from './elements/Svg';
|
||||
import Path from './elements/Path';
|
||||
import G from './elements/G';
|
||||
import Text from './elements/Text';
|
||||
import TSpan from './elements/TSpan';
|
||||
import TextPath from './elements/TextPath';
|
||||
import Use from './elements/Use';
|
||||
import Image from './elements/Image';
|
||||
import Symbol from './elements/Symbol';
|
||||
import Defs from './elements/Defs';
|
||||
import LinearGradient from './elements/LinearGradient';
|
||||
import RadialGradient from './elements/RadialGradient';
|
||||
import Stop from './elements/Stop';
|
||||
import ClipPath from './elements/ClipPath';
|
||||
import Pattern from './elements/Pattern';
|
||||
import Mask from './elements/Mask';
|
||||
import Marker from './elements/Marker';
|
||||
import ForeignObject from './elements/ForeignObject';
|
||||
import Filter from './elements/filters/Filter';
|
||||
import FeColorMatrix from './elements/filters/FeColorMatrix';
|
||||
import FeGaussianBlur from './elements/filters/FeGaussianBlur';
|
||||
import FeOffset from './elements/filters/FeOffset';
|
||||
|
||||
import {
|
||||
AstProps,
|
||||
camelCase,
|
||||
fetchText,
|
||||
JsxAST,
|
||||
Middleware,
|
||||
parse,
|
||||
Styles,
|
||||
SvgAst,
|
||||
SvgFromUri,
|
||||
SvgFromXml,
|
||||
SvgUri,
|
||||
SvgXml,
|
||||
camelCase,
|
||||
fetchText,
|
||||
JsxAST,
|
||||
Middleware,
|
||||
Styles,
|
||||
UriProps,
|
||||
UriState,
|
||||
XmlAST,
|
||||
XmlProps,
|
||||
XmlState,
|
||||
AstProps,
|
||||
} from './xml';
|
||||
|
||||
import {
|
||||
@@ -53,6 +25,10 @@ import {
|
||||
RNSVGClipPath,
|
||||
RNSVGDefs,
|
||||
RNSVGEllipse,
|
||||
RNSVGFeColorMatrix,
|
||||
RNSVGFeGaussianBlur,
|
||||
RNSVGFeOffset,
|
||||
RNSVGFilter,
|
||||
RNSVGForeignObject,
|
||||
RNSVGGroup,
|
||||
RNSVGImage,
|
||||
@@ -71,121 +47,90 @@ import {
|
||||
RNSVGTextPath,
|
||||
RNSVGTSpan,
|
||||
RNSVGUse,
|
||||
RNSVGFilter,
|
||||
RNSVGFeColorMatrix,
|
||||
RNSVGFeGaussianBlur,
|
||||
RNSVGFeOffset,
|
||||
} from './fabric';
|
||||
|
||||
export {
|
||||
inlineStyles,
|
||||
loadLocalRawResource,
|
||||
LocalSvg,
|
||||
SvgCss,
|
||||
SvgCssUri,
|
||||
SvgWithCss,
|
||||
SvgWithCssUri,
|
||||
inlineStyles,
|
||||
LocalSvg,
|
||||
WithLocalSvg,
|
||||
loadLocalRawResource,
|
||||
} from './deprecated';
|
||||
|
||||
export type { RectProps } from './elements/Rect';
|
||||
export type { CircleProps } from './elements/Circle';
|
||||
export type { EllipseProps } from './elements/Ellipse';
|
||||
export type { PolygonProps } from './elements/Polygon';
|
||||
export type { PolylineProps } from './elements/Polyline';
|
||||
export type { LineProps } from './elements/Line';
|
||||
export type { SvgProps } from './elements/Svg';
|
||||
export type { PathProps } from './elements/Path';
|
||||
export type { GProps } from './elements/G';
|
||||
export type { TextProps } from './elements/Text';
|
||||
export type { TSpanProps } from './elements/TSpan';
|
||||
export type { TextPathProps } from './elements/TextPath';
|
||||
export type { UseProps } from './elements/Use';
|
||||
export type { ImageProps } from './elements/Image';
|
||||
export type { SymbolProps } from './elements/Symbol';
|
||||
export type { LinearGradientProps } from './elements/LinearGradient';
|
||||
export type { RadialGradientProps } from './elements/RadialGradient';
|
||||
export type { StopProps } from './elements/Stop';
|
||||
export type { ClipPathProps } from './elements/ClipPath';
|
||||
export type { PatternProps } from './elements/Pattern';
|
||||
export type { MaskProps } from './elements/Mask';
|
||||
export type { MarkerProps } from './elements/Marker';
|
||||
export type { ForeignObjectProps } from './elements/ForeignObject';
|
||||
export type { FilterProps } from './elements/filters/Filter';
|
||||
export type { EllipseProps } from './elements/Ellipse';
|
||||
export type { FeColorMatrixProps } from './elements/filters/FeColorMatrix';
|
||||
export type { FeGaussianBlurProps } from './elements/filters/FeGaussianBlur';
|
||||
export type { FeOffsetProps } from './elements/filters/FeOffset';
|
||||
export type { FilterProps } from './elements/filters/Filter';
|
||||
export type { FilterPrimitiveCommonProps } from './elements/filters/FilterPrimitive';
|
||||
export type { ForeignObjectProps } from './elements/ForeignObject';
|
||||
export type { GProps } from './elements/G';
|
||||
export type { ImageProps } from './elements/Image';
|
||||
export type { LineProps } from './elements/Line';
|
||||
export type { LinearGradientProps } from './elements/LinearGradient';
|
||||
export type { MarkerProps } from './elements/Marker';
|
||||
export type { MaskProps } from './elements/Mask';
|
||||
export type { PathProps } from './elements/Path';
|
||||
export type { PatternProps } from './elements/Pattern';
|
||||
export type { PolygonProps } from './elements/Polygon';
|
||||
export type { PolylineProps } from './elements/Polyline';
|
||||
export type { RadialGradientProps } from './elements/RadialGradient';
|
||||
export type { RectProps } from './elements/Rect';
|
||||
export type { StopProps } from './elements/Stop';
|
||||
export type { SvgProps } from './elements/Svg';
|
||||
export type { SymbolProps } from './elements/Symbol';
|
||||
export type { TextProps } from './elements/Text';
|
||||
export type { TextPathProps } from './elements/TextPath';
|
||||
export type { TSpanProps } from './elements/TSpan';
|
||||
export type { UseProps } from './elements/Use';
|
||||
|
||||
export * from './lib/extract/types';
|
||||
|
||||
export {
|
||||
Svg,
|
||||
Circle,
|
||||
Ellipse,
|
||||
G,
|
||||
Text,
|
||||
TSpan,
|
||||
TextPath,
|
||||
Path,
|
||||
Polygon,
|
||||
Polyline,
|
||||
Line,
|
||||
Rect,
|
||||
Use,
|
||||
Image,
|
||||
Symbol,
|
||||
Defs,
|
||||
LinearGradient,
|
||||
RadialGradient,
|
||||
Stop,
|
||||
ClipPath,
|
||||
Pattern,
|
||||
Mask,
|
||||
Marker,
|
||||
ForeignObject,
|
||||
camelCase,
|
||||
fetchText,
|
||||
parse,
|
||||
RNSVGCircle,
|
||||
RNSVGClipPath,
|
||||
RNSVGDefs,
|
||||
RNSVGEllipse,
|
||||
RNSVGFeColorMatrix,
|
||||
RNSVGFeGaussianBlur,
|
||||
RNSVGFeOffset,
|
||||
RNSVGFilter,
|
||||
RNSVGForeignObject,
|
||||
RNSVGGroup,
|
||||
RNSVGImage,
|
||||
RNSVGLine,
|
||||
RNSVGLinearGradient,
|
||||
RNSVGMarker,
|
||||
RNSVGMask,
|
||||
RNSVGPath,
|
||||
RNSVGPattern,
|
||||
RNSVGRadialGradient,
|
||||
RNSVGRect,
|
||||
RNSVGSvgAndroid,
|
||||
RNSVGSvgIOS,
|
||||
RNSVGSymbol,
|
||||
RNSVGText,
|
||||
RNSVGTextPath,
|
||||
RNSVGTSpan,
|
||||
RNSVGUse,
|
||||
Shape,
|
||||
SvgAst,
|
||||
SvgFromUri,
|
||||
SvgFromXml,
|
||||
SvgUri,
|
||||
SvgXml,
|
||||
camelCase,
|
||||
fetchText,
|
||||
Shape,
|
||||
Filter,
|
||||
FeColorMatrix,
|
||||
FeGaussianBlur,
|
||||
FeOffset,
|
||||
RNSVGMarker,
|
||||
RNSVGMask,
|
||||
RNSVGPattern,
|
||||
RNSVGClipPath,
|
||||
RNSVGRadialGradient,
|
||||
RNSVGLinearGradient,
|
||||
RNSVGDefs,
|
||||
RNSVGSymbol,
|
||||
RNSVGImage,
|
||||
RNSVGUse,
|
||||
RNSVGTextPath,
|
||||
RNSVGTSpan,
|
||||
RNSVGText,
|
||||
RNSVGGroup,
|
||||
RNSVGPath,
|
||||
RNSVGLine,
|
||||
RNSVGEllipse,
|
||||
RNSVGCircle,
|
||||
RNSVGRect,
|
||||
RNSVGSvgAndroid,
|
||||
RNSVGSvgIOS,
|
||||
RNSVGForeignObject,
|
||||
RNSVGFilter,
|
||||
RNSVGFeColorMatrix,
|
||||
RNSVGFeGaussianBlur,
|
||||
RNSVGFeOffset,
|
||||
};
|
||||
|
||||
export type {
|
||||
AstProps,
|
||||
JsxAST,
|
||||
Middleware,
|
||||
Styles,
|
||||
@@ -194,7 +139,7 @@ export type {
|
||||
XmlAST,
|
||||
XmlProps,
|
||||
XmlState,
|
||||
AstProps,
|
||||
};
|
||||
|
||||
export default Svg;
|
||||
export * from './elements';
|
||||
export { default } from './elements';
|
||||
|
||||
@@ -1,601 +1,57 @@
|
||||
import * as React from 'react';
|
||||
import type { CircleProps } from './elements/Circle';
|
||||
import type { ClipPathProps } from './elements/ClipPath';
|
||||
import type { EllipseProps } from './elements/Ellipse';
|
||||
import type { ForeignObjectProps } from './elements/ForeignObject';
|
||||
import type { GProps } from './elements/G';
|
||||
import type { ImageProps } from './elements/Image';
|
||||
import type { LineProps } from './elements/Line';
|
||||
import type { LinearGradientProps } from './elements/LinearGradient';
|
||||
import type { MarkerProps } from './elements/Marker';
|
||||
import type { MaskProps } from './elements/Mask';
|
||||
import type { PathProps } from './elements/Path';
|
||||
import type { PatternProps } from './elements/Pattern';
|
||||
import type { PolygonProps } from './elements/Polygon';
|
||||
import type { PolylineProps } from './elements/Polyline';
|
||||
import type { RadialGradientProps } from './elements/RadialGradient';
|
||||
import type { RectProps } from './elements/Rect';
|
||||
import type { StopProps } from './elements/Stop';
|
||||
import type { SvgProps } from './elements/Svg';
|
||||
import type { SymbolProps } from './elements/Symbol';
|
||||
import type { TextProps } from './elements/Text';
|
||||
import type { TextPathProps } from './elements/TextPath';
|
||||
import type { TSpanProps } from './elements/TSpan';
|
||||
import type { UseProps } from './elements/Use';
|
||||
import type { FilterProps } from './elements/filters/Filter';
|
||||
import type { FeColorMatrixProps } from './elements/filters/FeColorMatrix';
|
||||
import type { FeGaussianBlurProps } from './elements/filters/FeGaussianBlur';
|
||||
import type { FeOffsetProps } from './elements/filters/FeOffset';
|
||||
import type {
|
||||
GestureResponderEvent,
|
||||
ImageProps as RNImageProps,
|
||||
} from 'react-native';
|
||||
import {
|
||||
// @ts-ignore it is not seen in exports
|
||||
unstable_createElement as createElement,
|
||||
} from 'react-native';
|
||||
import type {
|
||||
NumberArray,
|
||||
NumberProp,
|
||||
TransformProps,
|
||||
} from './lib/extract/types';
|
||||
import SvgTouchableMixin from './lib/SvgTouchableMixin';
|
||||
import { resolve } from './lib/resolve';
|
||||
import {
|
||||
transformsArrayToProps,
|
||||
type TransformsStyleArray,
|
||||
} from './lib/extract/extractTransform';
|
||||
import { resolveAssetUri } from './lib/resolveAssetUri';
|
||||
AstProps,
|
||||
camelCase,
|
||||
fetchText,
|
||||
JsxAST,
|
||||
Middleware,
|
||||
parse,
|
||||
Styles,
|
||||
SvgAst,
|
||||
SvgFromUri,
|
||||
SvgFromXml,
|
||||
SvgUri,
|
||||
SvgXml,
|
||||
UriProps,
|
||||
UriState,
|
||||
XmlAST,
|
||||
XmlProps,
|
||||
XmlState,
|
||||
} from './xml';
|
||||
|
||||
type BlurEvent = object;
|
||||
type FocusEvent = object;
|
||||
type PressEvent = object;
|
||||
type LayoutEvent = object;
|
||||
type EdgeInsetsProp = object;
|
||||
export {
|
||||
inlineStyles,
|
||||
loadLocalRawResource,
|
||||
LocalSvg,
|
||||
SvgCss,
|
||||
SvgCssUri,
|
||||
SvgWithCss,
|
||||
SvgWithCssUri,
|
||||
WithLocalSvg,
|
||||
} from './deprecated';
|
||||
|
||||
interface BaseProps {
|
||||
accessible?: boolean;
|
||||
accessibilityLabel?: string;
|
||||
accessibilityHint?: string;
|
||||
accessibilityIgnoresInvertColors?: boolean;
|
||||
accessibilityRole?: string;
|
||||
accessibilityState?: object;
|
||||
delayLongPress?: number;
|
||||
delayPressIn?: number;
|
||||
delayPressOut?: number;
|
||||
disabled?: boolean;
|
||||
hitSlop?: EdgeInsetsProp;
|
||||
href?: RNImageProps['source'] | string | number;
|
||||
nativeID?: string;
|
||||
touchSoundDisabled?: boolean;
|
||||
onBlur?: (e: BlurEvent) => void;
|
||||
onFocus?: (e: FocusEvent) => void;
|
||||
onLayout?: (event: LayoutEvent) => object;
|
||||
onLongPress?: (event: PressEvent) => object;
|
||||
onClick?: (event: PressEvent) => object;
|
||||
onPress?: (event: PressEvent) => object;
|
||||
onPressIn?: (event: PressEvent) => object;
|
||||
onPressOut?: (event: PressEvent) => object;
|
||||
pressRetentionOffset?: EdgeInsetsProp;
|
||||
rejectResponderTermination?: boolean;
|
||||
|
||||
transform?: TransformProps['transform'];
|
||||
translate?: NumberArray;
|
||||
translateX?: NumberProp;
|
||||
translateY?: NumberProp;
|
||||
scale?: NumberArray;
|
||||
scaleX?: NumberProp;
|
||||
scaleY?: NumberProp;
|
||||
rotation?: NumberProp;
|
||||
skewX?: NumberProp;
|
||||
skewY?: NumberProp;
|
||||
origin?: NumberArray;
|
||||
originX?: NumberProp;
|
||||
originY?: NumberProp;
|
||||
|
||||
fontStyle?: string;
|
||||
fontWeight?: NumberProp;
|
||||
fontSize?: NumberProp;
|
||||
fontFamily?: string;
|
||||
forwardedRef?:
|
||||
| React.RefCallback<SVGElement>
|
||||
| React.MutableRefObject<SVGElement | null>;
|
||||
style?: Iterable<unknown>;
|
||||
|
||||
// different tranform props
|
||||
gradientTransform?: TransformProps['transform'];
|
||||
patternTransform?: TransformProps['transform'];
|
||||
}
|
||||
|
||||
const hasTouchableProperty = (props: BaseProps) =>
|
||||
props.onPress || props.onPressIn || props.onPressOut || props.onLongPress;
|
||||
|
||||
const camelCaseToDashed = (camelCase: string) => {
|
||||
return camelCase.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase());
|
||||
export {
|
||||
camelCase,
|
||||
fetchText,
|
||||
parse,
|
||||
SvgAst,
|
||||
SvgFromUri,
|
||||
SvgFromXml,
|
||||
SvgUri,
|
||||
SvgXml,
|
||||
};
|
||||
|
||||
function stringifyTransformProps(transformProps: TransformProps) {
|
||||
const transformArray = [];
|
||||
if (transformProps.translate != null) {
|
||||
transformArray.push(`translate(${transformProps.translate})`);
|
||||
}
|
||||
if (transformProps.translateX != null || transformProps.translateY != null) {
|
||||
transformArray.push(
|
||||
`translate(${transformProps.translateX || 0}, ${
|
||||
transformProps.translateY || 0
|
||||
})`
|
||||
);
|
||||
}
|
||||
if (transformProps.scale != null) {
|
||||
transformArray.push(`scale(${transformProps.scale})`);
|
||||
}
|
||||
if (transformProps.scaleX != null || transformProps.scaleY != null) {
|
||||
transformArray.push(
|
||||
`scale(${transformProps.scaleX || 1}, ${transformProps.scaleY || 1})`
|
||||
);
|
||||
}
|
||||
// rotation maps to rotate, not to collide with the text rotate attribute (which acts per glyph rather than block)
|
||||
if (transformProps.rotation != null) {
|
||||
transformArray.push(`rotate(${transformProps.rotation})`);
|
||||
}
|
||||
if (transformProps.skewX != null) {
|
||||
transformArray.push(`skewX(${transformProps.skewX})`);
|
||||
}
|
||||
if (transformProps.skewY != null) {
|
||||
transformArray.push(`skewY(${transformProps.skewY})`);
|
||||
}
|
||||
return transformArray;
|
||||
}
|
||||
export * from './lib/extract/types';
|
||||
|
||||
function parseTransformProp(
|
||||
transform: TransformProps['transform'],
|
||||
props?: BaseProps
|
||||
) {
|
||||
const transformArray: string[] = [];
|
||||
|
||||
props && transformArray.push(...stringifyTransformProps(props));
|
||||
|
||||
if (Array.isArray(transform)) {
|
||||
if (typeof transform[0] === 'number') {
|
||||
transformArray.push(`matrix(${transform.join(' ')})`);
|
||||
} else {
|
||||
const stringifiedProps = transformsArrayToProps(
|
||||
transform as TransformsStyleArray
|
||||
);
|
||||
transformArray.push(...stringifyTransformProps(stringifiedProps));
|
||||
}
|
||||
} else if (typeof transform === 'string') {
|
||||
transformArray.push(transform);
|
||||
}
|
||||
|
||||
return transformArray.length ? transformArray.join(' ') : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* `react-native-svg` supports additional props that aren't defined in the spec.
|
||||
* This function replaces them in a spec conforming manner.
|
||||
*
|
||||
* @param {WebShape} self Instance given to us.
|
||||
* @param {Object?} props Optional overridden props given to us.
|
||||
* @returns {Object} Cleaned props object.
|
||||
* @private
|
||||
*/
|
||||
const prepare = <T extends BaseProps>(
|
||||
self: WebShape<T>,
|
||||
props = self.props
|
||||
) => {
|
||||
const {
|
||||
transform,
|
||||
origin,
|
||||
originX,
|
||||
originY,
|
||||
fontFamily,
|
||||
fontSize,
|
||||
fontWeight,
|
||||
fontStyle,
|
||||
style,
|
||||
forwardedRef,
|
||||
gradientTransform,
|
||||
patternTransform,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const clean: {
|
||||
onStartShouldSetResponder?: (e: GestureResponderEvent) => boolean;
|
||||
onResponderMove?: (e: GestureResponderEvent) => void;
|
||||
onResponderGrant?: (e: GestureResponderEvent) => void;
|
||||
onResponderRelease?: (e: GestureResponderEvent) => void;
|
||||
onResponderTerminate?: (e: GestureResponderEvent) => void;
|
||||
onResponderTerminationRequest?: (e: GestureResponderEvent) => boolean;
|
||||
onClick?: (e: GestureResponderEvent) => void;
|
||||
transform?: string;
|
||||
gradientTransform?: string;
|
||||
patternTransform?: string;
|
||||
'transform-origin'?: string;
|
||||
href?: RNImageProps['source'] | string | null;
|
||||
style?: object;
|
||||
ref?: unknown;
|
||||
} = {
|
||||
...(hasTouchableProperty(props)
|
||||
? {
|
||||
onStartShouldSetResponder:
|
||||
self.touchableHandleStartShouldSetResponder,
|
||||
onResponderTerminationRequest:
|
||||
self.touchableHandleResponderTerminationRequest,
|
||||
onResponderGrant: self.touchableHandleResponderGrant,
|
||||
onResponderMove: self.touchableHandleResponderMove,
|
||||
onResponderRelease: self.touchableHandleResponderRelease,
|
||||
onResponderTerminate: self.touchableHandleResponderTerminate,
|
||||
}
|
||||
: null),
|
||||
...rest,
|
||||
};
|
||||
|
||||
if (origin != null) {
|
||||
clean['transform-origin'] = origin.toString().replace(',', ' ');
|
||||
} else if (originX != null || originY != null) {
|
||||
clean['transform-origin'] = `${originX || 0} ${originY || 0}`;
|
||||
}
|
||||
|
||||
// we do it like this because setting transform as undefined causes error in web
|
||||
const parsedTransform = parseTransformProp(transform, props);
|
||||
if (parsedTransform) {
|
||||
clean.transform = parsedTransform;
|
||||
}
|
||||
const parsedGradientTransform = parseTransformProp(gradientTransform);
|
||||
if (parsedGradientTransform) {
|
||||
clean.gradientTransform = parsedGradientTransform;
|
||||
}
|
||||
const parsedPatternTransform = parseTransformProp(patternTransform);
|
||||
if (parsedPatternTransform) {
|
||||
clean.patternTransform = parsedPatternTransform;
|
||||
}
|
||||
|
||||
clean.ref = (el: SVGElement | null) => {
|
||||
self.elementRef.current = el;
|
||||
if (typeof forwardedRef === 'function') {
|
||||
forwardedRef(el);
|
||||
} else if (forwardedRef) {
|
||||
forwardedRef.current = el;
|
||||
}
|
||||
};
|
||||
|
||||
const styles: {
|
||||
fontStyle?: string;
|
||||
fontFamily?: string;
|
||||
fontSize?: NumberProp;
|
||||
fontWeight?: NumberProp;
|
||||
} = {};
|
||||
|
||||
if (fontFamily != null) {
|
||||
styles.fontFamily = fontFamily;
|
||||
}
|
||||
if (fontSize != null) {
|
||||
styles.fontSize = fontSize;
|
||||
}
|
||||
if (fontWeight != null) {
|
||||
styles.fontWeight = fontWeight;
|
||||
}
|
||||
if (fontStyle != null) {
|
||||
styles.fontStyle = fontStyle;
|
||||
}
|
||||
clean.style = resolve(style, styles);
|
||||
if (props.onPress != null) {
|
||||
clean.onClick = props.onPress;
|
||||
}
|
||||
if (props.href !== null) {
|
||||
clean.href = resolveAssetUri(props.href)?.uri;
|
||||
}
|
||||
return clean;
|
||||
export * from './elements';
|
||||
export { default } from './elements';
|
||||
export type {
|
||||
AstProps,
|
||||
JsxAST,
|
||||
Middleware,
|
||||
Styles,
|
||||
UriProps,
|
||||
UriState,
|
||||
XmlAST,
|
||||
XmlProps,
|
||||
XmlState,
|
||||
};
|
||||
|
||||
const getBoundingClientRect = (node: SVGElement) => {
|
||||
if (node) {
|
||||
const isElement = node.nodeType === 1; /* Node.ELEMENT_NODE */
|
||||
if (isElement && typeof node.getBoundingClientRect === 'function') {
|
||||
return node.getBoundingClientRect();
|
||||
}
|
||||
}
|
||||
throw new Error('Can not get boundingClientRect of ' + node || 'undefined');
|
||||
};
|
||||
|
||||
const measureLayout = (
|
||||
node: SVGElement,
|
||||
callback: (
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
left: number,
|
||||
top: number
|
||||
) => void
|
||||
) => {
|
||||
const relativeNode = node?.parentNode;
|
||||
if (relativeNode) {
|
||||
setTimeout(() => {
|
||||
// @ts-expect-error TODO: handle it better
|
||||
const relativeRect = getBoundingClientRect(relativeNode);
|
||||
const { height, left, top, width } = getBoundingClientRect(node);
|
||||
const x = left - relativeRect.left;
|
||||
const y = top - relativeRect.top;
|
||||
callback(x, y, width, height, left, top);
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function remeasure(this: any) {
|
||||
const tag = this.state.touchable.responderID;
|
||||
if (tag === null) {
|
||||
return;
|
||||
}
|
||||
measureLayout(tag, this._handleQueryLayout);
|
||||
}
|
||||
|
||||
export class WebShape<
|
||||
P extends BaseProps = BaseProps,
|
||||
> extends React.Component<P> {
|
||||
[x: string]: unknown;
|
||||
protected tag?: React.ElementType;
|
||||
protected prepareProps(props: P) {
|
||||
return props;
|
||||
}
|
||||
|
||||
elementRef =
|
||||
React.createRef<SVGElement>() as React.MutableRefObject<SVGElement | null>;
|
||||
|
||||
lastMergedProps: Partial<P> = {};
|
||||
|
||||
/**
|
||||
* disclaimer: I am not sure why the props are wrapped in a `style` attribute here, but that's how reanimated calls it
|
||||
*/
|
||||
setNativeProps(props: { style: P }) {
|
||||
const merged = Object.assign(
|
||||
{},
|
||||
this.props,
|
||||
this.lastMergedProps,
|
||||
props.style
|
||||
);
|
||||
this.lastMergedProps = merged;
|
||||
const clean = prepare(this, this.prepareProps(merged));
|
||||
const current = this.elementRef.current;
|
||||
if (current) {
|
||||
for (const cleanAttribute of Object.keys(clean)) {
|
||||
const cleanValue = clean[cleanAttribute as keyof typeof clean];
|
||||
switch (cleanAttribute) {
|
||||
case 'ref':
|
||||
case 'children':
|
||||
break;
|
||||
case 'style':
|
||||
// style can be an object here or an array, so we convert it to an array and assign each element
|
||||
for (const partialStyle of ([] as unknown[]).concat(
|
||||
clean.style ?? []
|
||||
)) {
|
||||
Object.assign(current.style, partialStyle);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// apply all other incoming prop updates as attributes on the node
|
||||
// same logic as in https://github.com/software-mansion/react-native-reanimated/blob/d04720c82f5941532991b235787285d36d717247/src/reanimated2/js-reanimated/index.ts#L38-L39
|
||||
// @ts-expect-error TODO: fix this
|
||||
current.setAttribute(camelCaseToDashed(cleanAttribute), cleanValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_remeasureMetricsOnActivation: () => void;
|
||||
touchableHandleStartShouldSetResponder?: (
|
||||
e: GestureResponderEvent
|
||||
) => boolean;
|
||||
|
||||
touchableHandleResponderMove?: (e: GestureResponderEvent) => void;
|
||||
touchableHandleResponderGrant?: (e: GestureResponderEvent) => void;
|
||||
touchableHandleResponderRelease?: (e: GestureResponderEvent) => void;
|
||||
touchableHandleResponderTerminate?: (e: GestureResponderEvent) => void;
|
||||
touchableHandleResponderTerminationRequest?: (
|
||||
e: GestureResponderEvent
|
||||
) => boolean;
|
||||
|
||||
constructor(props: P) {
|
||||
super(props);
|
||||
|
||||
// Do not attach touchable mixin handlers if SVG element doesn't have a touchable prop
|
||||
if (hasTouchableProperty(props)) {
|
||||
SvgTouchableMixin(this);
|
||||
}
|
||||
|
||||
this._remeasureMetricsOnActivation = remeasure.bind(this);
|
||||
}
|
||||
|
||||
render(): JSX.Element {
|
||||
if (!this.tag) {
|
||||
throw new Error(
|
||||
'When extending `WebShape` you need to overwrite either `tag` or `render`!'
|
||||
);
|
||||
}
|
||||
this.lastMergedProps = {};
|
||||
return createElement(
|
||||
this.tag,
|
||||
prepare(this, this.prepareProps(this.props))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class Circle extends WebShape<BaseProps & CircleProps> {
|
||||
tag = 'circle' as const;
|
||||
}
|
||||
|
||||
export class ClipPath extends WebShape<BaseProps & ClipPathProps> {
|
||||
tag = 'clipPath' as const;
|
||||
}
|
||||
|
||||
export class Defs extends WebShape {
|
||||
tag = 'defs' as const;
|
||||
}
|
||||
|
||||
export class Ellipse extends WebShape<BaseProps & EllipseProps> {
|
||||
tag = 'ellipse' as const;
|
||||
}
|
||||
|
||||
export class G extends WebShape<BaseProps & GProps> {
|
||||
tag = 'g' as const;
|
||||
prepareProps(props: BaseProps & GProps) {
|
||||
const { x, y, ...rest } = props;
|
||||
|
||||
if ((x || y) && !rest.translate) {
|
||||
rest.translate = `${x || 0}, ${y || 0}`;
|
||||
}
|
||||
|
||||
return rest;
|
||||
}
|
||||
}
|
||||
|
||||
export class Image extends WebShape<BaseProps & ImageProps> {
|
||||
tag = 'image' as const;
|
||||
}
|
||||
|
||||
export class Line extends WebShape<BaseProps & LineProps> {
|
||||
tag = 'line' as const;
|
||||
}
|
||||
|
||||
export class LinearGradient extends WebShape<BaseProps & LinearGradientProps> {
|
||||
tag = 'linearGradient' as const;
|
||||
}
|
||||
|
||||
export class Path extends WebShape<BaseProps & PathProps> {
|
||||
tag = 'path' as const;
|
||||
}
|
||||
|
||||
export class Polygon extends WebShape<BaseProps & PolygonProps> {
|
||||
tag = 'polygon' as const;
|
||||
}
|
||||
|
||||
export class Polyline extends WebShape<BaseProps & PolylineProps> {
|
||||
tag = 'polyline' as const;
|
||||
}
|
||||
|
||||
export class RadialGradient extends WebShape<BaseProps & RadialGradientProps> {
|
||||
tag = 'radialGradient' as const;
|
||||
}
|
||||
|
||||
export class Rect extends WebShape<BaseProps & RectProps> {
|
||||
tag = 'rect' as const;
|
||||
}
|
||||
|
||||
export class Stop extends WebShape<BaseProps & StopProps> {
|
||||
tag = 'stop' as const;
|
||||
}
|
||||
|
||||
/* Taken from here: https://gist.github.com/jennyknuth/222825e315d45a738ed9d6e04c7a88d0 */
|
||||
function encodeSvg(svgString: string) {
|
||||
return svgString
|
||||
.replace(
|
||||
'<svg',
|
||||
~svgString.indexOf('xmlns')
|
||||
? '<svg'
|
||||
: '<svg xmlns="http://www.w3.org/2000/svg"'
|
||||
)
|
||||
.replace(/"/g, "'")
|
||||
.replace(/%/g, '%25')
|
||||
.replace(/#/g, '%23')
|
||||
.replace(/{/g, '%7B')
|
||||
.replace(/}/g, '%7D')
|
||||
.replace(/</g, '%3C')
|
||||
.replace(/>/g, '%3E')
|
||||
.replace(/\s+/g, ' ');
|
||||
}
|
||||
|
||||
export class Svg extends WebShape<BaseProps & SvgProps> {
|
||||
tag = 'svg' as const;
|
||||
toDataURL(
|
||||
callback: (data: string) => void,
|
||||
options: { width?: number; height?: number } = {}
|
||||
) {
|
||||
const ref = this.elementRef.current;
|
||||
|
||||
if (ref === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = getBoundingClientRect(ref);
|
||||
|
||||
const width = Number(options.width) || rect.width;
|
||||
const height = Number(options.height) || rect.height;
|
||||
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.setAttribute('viewBox', `0 0 ${rect.width} ${rect.height}`);
|
||||
svg.setAttribute('width', String(width));
|
||||
svg.setAttribute('height', String(height));
|
||||
svg.appendChild(ref.cloneNode(true));
|
||||
|
||||
const img = new window.Image();
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
const context = canvas.getContext('2d');
|
||||
context?.drawImage(img, 0, 0);
|
||||
callback(canvas.toDataURL().replace('data:image/png;base64,', ''));
|
||||
};
|
||||
|
||||
img.src = `data:image/svg+xml;utf8,${encodeSvg(
|
||||
new window.XMLSerializer().serializeToString(svg)
|
||||
)}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class Symbol extends WebShape<BaseProps & SymbolProps> {
|
||||
tag = 'symbol' as const;
|
||||
}
|
||||
|
||||
export class Text extends WebShape<BaseProps & TextProps> {
|
||||
tag = 'text' as const;
|
||||
}
|
||||
|
||||
export class TSpan extends WebShape<BaseProps & TSpanProps> {
|
||||
tag = 'tspan' as const;
|
||||
}
|
||||
|
||||
export class TextPath extends WebShape<BaseProps & TextPathProps> {
|
||||
tag = 'textPath' as const;
|
||||
}
|
||||
|
||||
export class Use extends WebShape<BaseProps & UseProps> {
|
||||
tag = 'use' as const;
|
||||
}
|
||||
|
||||
export class Mask extends WebShape<BaseProps & MaskProps> {
|
||||
tag = 'mask' as const;
|
||||
}
|
||||
|
||||
export class ForeignObject extends WebShape<BaseProps & ForeignObjectProps> {
|
||||
tag = 'foreignObject' as const;
|
||||
}
|
||||
|
||||
export class Marker extends WebShape<BaseProps & MarkerProps> {
|
||||
tag = 'marker' as const;
|
||||
}
|
||||
|
||||
export class Pattern extends WebShape<BaseProps & PatternProps> {
|
||||
tag = 'pattern' as const;
|
||||
}
|
||||
|
||||
export class Filter extends WebShape<BaseProps & FilterProps> {
|
||||
tag = 'filter' as const;
|
||||
}
|
||||
|
||||
export class FeColorMatrix extends WebShape<BaseProps & FeColorMatrixProps> {
|
||||
tag = 'feColorMatrix' as const;
|
||||
}
|
||||
|
||||
export class FeGaussianBlur extends WebShape<BaseProps & FeGaussianBlurProps> {
|
||||
tag = 'feGaussianBlur' as const;
|
||||
}
|
||||
|
||||
export class FeOffset extends WebShape<BaseProps & FeOffsetProps> {
|
||||
tag = 'feOffset' as const;
|
||||
}
|
||||
|
||||
export default Svg;
|
||||
|
||||
61
src/elements.ts
Normal file
61
src/elements.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import Circle from './elements/Circle';
|
||||
import ClipPath from './elements/ClipPath';
|
||||
import Defs from './elements/Defs';
|
||||
import Ellipse from './elements/Ellipse';
|
||||
import ForeignObject from './elements/ForeignObject';
|
||||
import G from './elements/G';
|
||||
import Image from './elements/Image';
|
||||
import Line from './elements/Line';
|
||||
import LinearGradient from './elements/LinearGradient';
|
||||
import Marker from './elements/Marker';
|
||||
import Mask from './elements/Mask';
|
||||
import Path from './elements/Path';
|
||||
import Pattern from './elements/Pattern';
|
||||
import Polygon from './elements/Polygon';
|
||||
import Polyline from './elements/Polyline';
|
||||
import RadialGradient from './elements/RadialGradient';
|
||||
import Rect from './elements/Rect';
|
||||
import Stop from './elements/Stop';
|
||||
import Svg from './elements/Svg';
|
||||
import Symbol from './elements/Symbol';
|
||||
import TSpan from './elements/TSpan';
|
||||
import Text from './elements/Text';
|
||||
import TextPath from './elements/TextPath';
|
||||
import Use from './elements/Use';
|
||||
import FeColorMatrix from './elements/filters/FeColorMatrix';
|
||||
import FeGaussianBlur from './elements/filters/FeGaussianBlur';
|
||||
import FeOffset from './elements/filters/FeOffset';
|
||||
import Filter from './elements/filters/Filter';
|
||||
|
||||
export {
|
||||
Circle,
|
||||
ClipPath,
|
||||
Defs,
|
||||
Ellipse,
|
||||
FeColorMatrix,
|
||||
FeGaussianBlur,
|
||||
FeOffset,
|
||||
Filter,
|
||||
ForeignObject,
|
||||
G,
|
||||
Image,
|
||||
Line,
|
||||
LinearGradient,
|
||||
Marker,
|
||||
Mask,
|
||||
Path,
|
||||
Pattern,
|
||||
Polygon,
|
||||
Polyline,
|
||||
RadialGradient,
|
||||
Rect,
|
||||
Stop,
|
||||
Svg,
|
||||
Symbol,
|
||||
TSpan,
|
||||
Text,
|
||||
TextPath,
|
||||
Use,
|
||||
};
|
||||
|
||||
export default Svg;
|
||||
188
src/elements.web.ts
Normal file
188
src/elements.web.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
import type { CircleProps } from './elements/Circle';
|
||||
import type { ClipPathProps } from './elements/ClipPath';
|
||||
import type { EllipseProps } from './elements/Ellipse';
|
||||
import type { FeColorMatrixProps } from './elements/filters/FeColorMatrix';
|
||||
import type { FeGaussianBlurProps } from './elements/filters/FeGaussianBlur';
|
||||
import type { FeOffsetProps } from './elements/filters/FeOffset';
|
||||
import type { FilterProps } from './elements/filters/Filter';
|
||||
import type { ForeignObjectProps } from './elements/ForeignObject';
|
||||
import type { GProps } from './elements/G';
|
||||
import type { ImageProps } from './elements/Image';
|
||||
import type { LineProps } from './elements/Line';
|
||||
import type { LinearGradientProps } from './elements/LinearGradient';
|
||||
import type { MarkerProps } from './elements/Marker';
|
||||
import type { MaskProps } from './elements/Mask';
|
||||
import type { PathProps } from './elements/Path';
|
||||
import type { PatternProps } from './elements/Pattern';
|
||||
import type { PolygonProps } from './elements/Polygon';
|
||||
import type { PolylineProps } from './elements/Polyline';
|
||||
import type { RadialGradientProps } from './elements/RadialGradient';
|
||||
import type { RectProps } from './elements/Rect';
|
||||
import type { StopProps } from './elements/Stop';
|
||||
import type { SvgProps } from './elements/Svg';
|
||||
import type { SymbolProps } from './elements/Symbol';
|
||||
import type { TextProps } from './elements/Text';
|
||||
import type { TextPathProps } from './elements/TextPath';
|
||||
import type { TSpanProps } from './elements/TSpan';
|
||||
import type { UseProps } from './elements/Use';
|
||||
import type { BaseProps } from './web/types';
|
||||
import { encodeSvg, getBoundingClientRect } from './web/utils';
|
||||
import { WebShape } from './web/WebShape';
|
||||
|
||||
export class Circle extends WebShape<BaseProps & CircleProps> {
|
||||
tag = 'circle' as const;
|
||||
}
|
||||
|
||||
export class ClipPath extends WebShape<BaseProps & ClipPathProps> {
|
||||
tag = 'clipPath' as const;
|
||||
}
|
||||
|
||||
export class Defs extends WebShape {
|
||||
tag = 'defs' as const;
|
||||
}
|
||||
|
||||
export class Ellipse extends WebShape<BaseProps & EllipseProps> {
|
||||
tag = 'ellipse' as const;
|
||||
}
|
||||
|
||||
export class FeColorMatrix extends WebShape<BaseProps & FeColorMatrixProps> {
|
||||
tag = 'feColorMatrix' as const;
|
||||
}
|
||||
|
||||
export class FeGaussianBlur extends WebShape<BaseProps & FeGaussianBlurProps> {
|
||||
tag = 'feGaussianBlur' as const;
|
||||
}
|
||||
|
||||
export class FeOffset extends WebShape<BaseProps & FeOffsetProps> {
|
||||
tag = 'feOffset' as const;
|
||||
}
|
||||
|
||||
export class Filter extends WebShape<BaseProps & FilterProps> {
|
||||
tag = 'filter' as const;
|
||||
}
|
||||
|
||||
export class ForeignObject extends WebShape<BaseProps & ForeignObjectProps> {
|
||||
tag = 'foreignObject' as const;
|
||||
}
|
||||
|
||||
export class G extends WebShape<BaseProps & GProps> {
|
||||
tag = 'g' as const;
|
||||
prepareProps(props: BaseProps & GProps) {
|
||||
const { x, y, ...rest } = props;
|
||||
|
||||
if ((x || y) && !rest.translate) {
|
||||
rest.translate = `${x || 0}, ${y || 0}`;
|
||||
}
|
||||
|
||||
return rest;
|
||||
}
|
||||
}
|
||||
|
||||
export class Image extends WebShape<BaseProps & ImageProps> {
|
||||
tag = 'image' as const;
|
||||
}
|
||||
|
||||
export class Line extends WebShape<BaseProps & LineProps> {
|
||||
tag = 'line' as const;
|
||||
}
|
||||
|
||||
export class LinearGradient extends WebShape<BaseProps & LinearGradientProps> {
|
||||
tag = 'linearGradient' as const;
|
||||
}
|
||||
|
||||
export class Marker extends WebShape<BaseProps & MarkerProps> {
|
||||
tag = 'marker' as const;
|
||||
}
|
||||
|
||||
export class Mask extends WebShape<BaseProps & MaskProps> {
|
||||
tag = 'mask' as const;
|
||||
}
|
||||
|
||||
export class Path extends WebShape<BaseProps & PathProps> {
|
||||
tag = 'path' as const;
|
||||
}
|
||||
|
||||
export class Pattern extends WebShape<BaseProps & PatternProps> {
|
||||
tag = 'pattern' as const;
|
||||
}
|
||||
|
||||
export class Polygon extends WebShape<BaseProps & PolygonProps> {
|
||||
tag = 'polygon' as const;
|
||||
}
|
||||
|
||||
export class Polyline extends WebShape<BaseProps & PolylineProps> {
|
||||
tag = 'polyline' as const;
|
||||
}
|
||||
|
||||
export class RadialGradient extends WebShape<BaseProps & RadialGradientProps> {
|
||||
tag = 'radialGradient' as const;
|
||||
}
|
||||
|
||||
export class Rect extends WebShape<BaseProps & RectProps> {
|
||||
tag = 'rect' as const;
|
||||
}
|
||||
|
||||
export class Stop extends WebShape<BaseProps & StopProps> {
|
||||
tag = 'stop' as const;
|
||||
}
|
||||
|
||||
export class Svg extends WebShape<BaseProps & SvgProps> {
|
||||
tag = 'svg' as const;
|
||||
toDataURL(
|
||||
callback: (data: string) => void,
|
||||
options: { width?: number; height?: number } = {}
|
||||
) {
|
||||
const ref = this.elementRef.current;
|
||||
|
||||
if (ref === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = getBoundingClientRect(ref);
|
||||
|
||||
const width = Number(options.width) || rect.width;
|
||||
const height = Number(options.height) || rect.height;
|
||||
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.setAttribute('viewBox', `0 0 ${rect.width} ${rect.height}`);
|
||||
svg.setAttribute('width', String(width));
|
||||
svg.setAttribute('height', String(height));
|
||||
svg.appendChild(ref.cloneNode(true));
|
||||
|
||||
const img = new window.Image();
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
const context = canvas.getContext('2d');
|
||||
context?.drawImage(img, 0, 0);
|
||||
callback(canvas.toDataURL().replace('data:image/png;base64,', ''));
|
||||
};
|
||||
|
||||
img.src = `data:image/svg+xml;utf8,${encodeSvg(
|
||||
new window.XMLSerializer().serializeToString(svg)
|
||||
)}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class Symbol extends WebShape<BaseProps & SymbolProps> {
|
||||
tag = 'symbol' as const;
|
||||
}
|
||||
|
||||
export class TSpan extends WebShape<BaseProps & TSpanProps> {
|
||||
tag = 'tspan' as const;
|
||||
}
|
||||
|
||||
export class Text extends WebShape<BaseProps & TextProps> {
|
||||
tag = 'text' as const;
|
||||
}
|
||||
|
||||
export class TextPath extends WebShape<BaseProps & TextPathProps> {
|
||||
tag = 'textPath' as const;
|
||||
}
|
||||
|
||||
export class Use extends WebShape<BaseProps & UseProps> {
|
||||
tag = 'use' as const;
|
||||
}
|
||||
|
||||
export default Svg;
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { tags } from '../../tags';
|
||||
import { tags } from '../../xmlTags';
|
||||
import { FilterElement, Filters } from '../types';
|
||||
import { parse } from './extractFiltersString';
|
||||
|
||||
|
||||
102
src/web/WebShape.ts
Normal file
102
src/web/WebShape.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
GestureResponderEvent,
|
||||
// @ts-ignore it is not seen in exports
|
||||
unstable_createElement as createElement,
|
||||
} from 'react-native';
|
||||
|
||||
import { BaseProps } from './types';
|
||||
import { prepare } from './utils/prepare';
|
||||
import { camelCaseToDashed, hasTouchableProperty, remeasure } from './utils';
|
||||
import SvgTouchableMixin from '../lib/SvgTouchableMixin';
|
||||
|
||||
export class WebShape<
|
||||
P extends BaseProps = BaseProps,
|
||||
> extends React.Component<P> {
|
||||
[x: string]: unknown;
|
||||
protected tag?: React.ElementType;
|
||||
protected prepareProps(props: P) {
|
||||
return props;
|
||||
}
|
||||
|
||||
elementRef =
|
||||
React.createRef<SVGElement>() as React.MutableRefObject<SVGElement | null>;
|
||||
|
||||
lastMergedProps: Partial<P> = {};
|
||||
|
||||
/**
|
||||
* disclaimer: I am not sure why the props are wrapped in a `style` attribute here, but that's how reanimated calls it
|
||||
*/
|
||||
setNativeProps(props: { style: P }) {
|
||||
const merged = Object.assign(
|
||||
{},
|
||||
this.props,
|
||||
this.lastMergedProps,
|
||||
props.style
|
||||
);
|
||||
this.lastMergedProps = merged;
|
||||
const clean = prepare(this, this.prepareProps(merged));
|
||||
const current = this.elementRef.current;
|
||||
if (current) {
|
||||
for (const cleanAttribute of Object.keys(clean)) {
|
||||
const cleanValue = clean[cleanAttribute as keyof typeof clean];
|
||||
switch (cleanAttribute) {
|
||||
case 'ref':
|
||||
case 'children':
|
||||
break;
|
||||
case 'style':
|
||||
// style can be an object here or an array, so we convert it to an array and assign each element
|
||||
for (const partialStyle of ([] as unknown[]).concat(
|
||||
clean.style ?? []
|
||||
)) {
|
||||
Object.assign(current.style, partialStyle);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// apply all other incoming prop updates as attributes on the node
|
||||
// same logic as in https://github.com/software-mansion/react-native-reanimated/blob/d04720c82f5941532991b235787285d36d717247/src/reanimated2/js-reanimated/index.ts#L38-L39
|
||||
// @ts-expect-error TODO: fix this
|
||||
current.setAttribute(camelCaseToDashed(cleanAttribute), cleanValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_remeasureMetricsOnActivation: () => void;
|
||||
touchableHandleStartShouldSetResponder?: (
|
||||
e: GestureResponderEvent
|
||||
) => boolean;
|
||||
|
||||
touchableHandleResponderMove?: (e: GestureResponderEvent) => void;
|
||||
touchableHandleResponderGrant?: (e: GestureResponderEvent) => void;
|
||||
touchableHandleResponderRelease?: (e: GestureResponderEvent) => void;
|
||||
touchableHandleResponderTerminate?: (e: GestureResponderEvent) => void;
|
||||
touchableHandleResponderTerminationRequest?: (
|
||||
e: GestureResponderEvent
|
||||
) => boolean;
|
||||
|
||||
constructor(props: P) {
|
||||
super(props);
|
||||
|
||||
// Do not attach touchable mixin handlers if SVG element doesn't have a touchable prop
|
||||
if (hasTouchableProperty(props)) {
|
||||
SvgTouchableMixin(this);
|
||||
}
|
||||
|
||||
this._remeasureMetricsOnActivation = remeasure.bind(this);
|
||||
}
|
||||
|
||||
render(): JSX.Element {
|
||||
if (!this.tag) {
|
||||
throw new Error(
|
||||
'When extending `WebShape` you need to overwrite either `tag` or `render`!'
|
||||
);
|
||||
}
|
||||
this.lastMergedProps = {};
|
||||
return createElement(
|
||||
this.tag,
|
||||
prepare(this, this.prepareProps(this.props))
|
||||
);
|
||||
}
|
||||
}
|
||||
66
src/web/types.ts
Normal file
66
src/web/types.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import type { ImageProps as RNImageProps } from 'react-native';
|
||||
import type {
|
||||
NumberArray,
|
||||
NumberProp,
|
||||
TransformProps,
|
||||
} from '../lib/extract/types';
|
||||
|
||||
type BlurEvent = object;
|
||||
type FocusEvent = object;
|
||||
type PressEvent = object;
|
||||
type LayoutEvent = object;
|
||||
type EdgeInsetsProp = object;
|
||||
|
||||
export interface BaseProps {
|
||||
accessible?: boolean;
|
||||
accessibilityLabel?: string;
|
||||
accessibilityHint?: string;
|
||||
accessibilityIgnoresInvertColors?: boolean;
|
||||
accessibilityRole?: string;
|
||||
accessibilityState?: object;
|
||||
delayLongPress?: number;
|
||||
delayPressIn?: number;
|
||||
delayPressOut?: number;
|
||||
disabled?: boolean;
|
||||
hitSlop?: EdgeInsetsProp;
|
||||
href?: RNImageProps['source'] | string | number;
|
||||
nativeID?: string;
|
||||
touchSoundDisabled?: boolean;
|
||||
onBlur?: (e: BlurEvent) => void;
|
||||
onFocus?: (e: FocusEvent) => void;
|
||||
onLayout?: (event: LayoutEvent) => object;
|
||||
onLongPress?: (event: PressEvent) => object;
|
||||
onClick?: (event: PressEvent) => object;
|
||||
onPress?: (event: PressEvent) => object;
|
||||
onPressIn?: (event: PressEvent) => object;
|
||||
onPressOut?: (event: PressEvent) => object;
|
||||
pressRetentionOffset?: EdgeInsetsProp;
|
||||
rejectResponderTermination?: boolean;
|
||||
|
||||
transform?: TransformProps['transform'];
|
||||
translate?: NumberArray;
|
||||
translateX?: NumberProp;
|
||||
translateY?: NumberProp;
|
||||
scale?: NumberArray;
|
||||
scaleX?: NumberProp;
|
||||
scaleY?: NumberProp;
|
||||
rotation?: NumberProp;
|
||||
skewX?: NumberProp;
|
||||
skewY?: NumberProp;
|
||||
origin?: NumberArray;
|
||||
originX?: NumberProp;
|
||||
originY?: NumberProp;
|
||||
|
||||
fontStyle?: string;
|
||||
fontWeight?: NumberProp;
|
||||
fontSize?: NumberProp;
|
||||
fontFamily?: string;
|
||||
forwardedRef?:
|
||||
| React.RefCallback<SVGElement>
|
||||
| React.MutableRefObject<SVGElement | null>;
|
||||
style?: Iterable<unknown>;
|
||||
|
||||
// different tranform props
|
||||
gradientTransform?: TransformProps['transform'];
|
||||
patternTransform?: TransformProps['transform'];
|
||||
}
|
||||
132
src/web/utils/index.ts
Normal file
132
src/web/utils/index.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import { BaseProps } from '../types';
|
||||
import type { TransformProps } from '../../lib/extract/types';
|
||||
import {
|
||||
transformsArrayToProps,
|
||||
TransformsStyleArray,
|
||||
} from '../../lib/extract/extractTransform';
|
||||
|
||||
export const hasTouchableProperty = (props: BaseProps) =>
|
||||
props.onPress || props.onPressIn || props.onPressOut || props.onLongPress;
|
||||
|
||||
export const camelCaseToDashed = (camelCase: string) => {
|
||||
return camelCase.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase());
|
||||
};
|
||||
|
||||
function stringifyTransformProps(transformProps: TransformProps) {
|
||||
const transformArray = [];
|
||||
if (transformProps.translate != null) {
|
||||
transformArray.push(`translate(${transformProps.translate})`);
|
||||
}
|
||||
if (transformProps.translateX != null || transformProps.translateY != null) {
|
||||
transformArray.push(
|
||||
`translate(${transformProps.translateX || 0}, ${
|
||||
transformProps.translateY || 0
|
||||
})`
|
||||
);
|
||||
}
|
||||
if (transformProps.scale != null) {
|
||||
transformArray.push(`scale(${transformProps.scale})`);
|
||||
}
|
||||
if (transformProps.scaleX != null || transformProps.scaleY != null) {
|
||||
transformArray.push(
|
||||
`scale(${transformProps.scaleX || 1}, ${transformProps.scaleY || 1})`
|
||||
);
|
||||
}
|
||||
// rotation maps to rotate, not to collide with the text rotate attribute (which acts per glyph rather than block)
|
||||
if (transformProps.rotation != null) {
|
||||
transformArray.push(`rotate(${transformProps.rotation})`);
|
||||
}
|
||||
if (transformProps.skewX != null) {
|
||||
transformArray.push(`skewX(${transformProps.skewX})`);
|
||||
}
|
||||
if (transformProps.skewY != null) {
|
||||
transformArray.push(`skewY(${transformProps.skewY})`);
|
||||
}
|
||||
return transformArray;
|
||||
}
|
||||
|
||||
export function parseTransformProp(
|
||||
transform: TransformProps['transform'],
|
||||
props?: BaseProps
|
||||
) {
|
||||
const transformArray: string[] = [];
|
||||
|
||||
props && transformArray.push(...stringifyTransformProps(props));
|
||||
|
||||
if (Array.isArray(transform)) {
|
||||
if (typeof transform[0] === 'number') {
|
||||
transformArray.push(`matrix(${transform.join(' ')})`);
|
||||
} else {
|
||||
const stringifiedProps = transformsArrayToProps(
|
||||
transform as TransformsStyleArray
|
||||
);
|
||||
transformArray.push(...stringifyTransformProps(stringifiedProps));
|
||||
}
|
||||
} else if (typeof transform === 'string') {
|
||||
transformArray.push(transform);
|
||||
}
|
||||
|
||||
return transformArray.length ? transformArray.join(' ') : undefined;
|
||||
}
|
||||
|
||||
export const getBoundingClientRect = (node: SVGElement) => {
|
||||
if (node) {
|
||||
const isElement = node.nodeType === 1; /* Node.ELEMENT_NODE */
|
||||
if (isElement && typeof node.getBoundingClientRect === 'function') {
|
||||
return node.getBoundingClientRect();
|
||||
}
|
||||
}
|
||||
throw new Error('Can not get boundingClientRect of ' + node || 'undefined');
|
||||
};
|
||||
|
||||
const measureLayout = (
|
||||
node: SVGElement,
|
||||
callback: (
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
left: number,
|
||||
top: number
|
||||
) => void
|
||||
) => {
|
||||
const relativeNode = node?.parentNode;
|
||||
if (relativeNode) {
|
||||
setTimeout(() => {
|
||||
// @ts-expect-error TODO: handle it better
|
||||
const relativeRect = getBoundingClientRect(relativeNode);
|
||||
const { height, left, top, width } = getBoundingClientRect(node);
|
||||
const x = left - relativeRect.left;
|
||||
const y = top - relativeRect.top;
|
||||
callback(x, y, width, height, left, top);
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function remeasure(this: any) {
|
||||
const tag = this.state.touchable.responderID;
|
||||
if (tag === null) {
|
||||
return;
|
||||
}
|
||||
measureLayout(tag, this._handleQueryLayout);
|
||||
}
|
||||
|
||||
/* Taken from here: https://gist.github.com/jennyknuth/222825e315d45a738ed9d6e04c7a88d0 */
|
||||
export function encodeSvg(svgString: string) {
|
||||
return svgString
|
||||
.replace(
|
||||
'<svg',
|
||||
~svgString.indexOf('xmlns')
|
||||
? '<svg'
|
||||
: '<svg xmlns="http://www.w3.org/2000/svg"'
|
||||
)
|
||||
.replace(/"/g, "'")
|
||||
.replace(/%/g, '%25')
|
||||
.replace(/#/g, '%23')
|
||||
.replace(/{/g, '%7B')
|
||||
.replace(/}/g, '%7D')
|
||||
.replace(/</g, '%3C')
|
||||
.replace(/>/g, '%3E')
|
||||
.replace(/\s+/g, ' ');
|
||||
}
|
||||
127
src/web/utils/prepare.ts
Normal file
127
src/web/utils/prepare.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import {
|
||||
GestureResponderEvent,
|
||||
type ImageProps as RNImageProps,
|
||||
} from 'react-native';
|
||||
import { BaseProps } from '../types';
|
||||
import { WebShape } from '../WebShape';
|
||||
import { hasTouchableProperty, parseTransformProp } from '.';
|
||||
import { resolve } from '../../lib/resolve';
|
||||
import { NumberProp } from '../../lib/extract/types';
|
||||
import { resolveAssetUri } from '../../lib/resolveAssetUri';
|
||||
/**
|
||||
* `react-native-svg` supports additional props that aren't defined in the spec.
|
||||
* This function replaces them in a spec conforming manner.
|
||||
*
|
||||
* @param {WebShape} self Instance given to us.
|
||||
* @param {Object?} props Optional overridden props given to us.
|
||||
* @returns {Object} Cleaned props object.
|
||||
* @private
|
||||
*/
|
||||
export const prepare = <T extends BaseProps>(
|
||||
self: WebShape<T>,
|
||||
props = self.props
|
||||
) => {
|
||||
const {
|
||||
transform,
|
||||
origin,
|
||||
originX,
|
||||
originY,
|
||||
fontFamily,
|
||||
fontSize,
|
||||
fontWeight,
|
||||
fontStyle,
|
||||
style,
|
||||
forwardedRef,
|
||||
gradientTransform,
|
||||
patternTransform,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const clean: {
|
||||
onStartShouldSetResponder?: (e: GestureResponderEvent) => boolean;
|
||||
onResponderMove?: (e: GestureResponderEvent) => void;
|
||||
onResponderGrant?: (e: GestureResponderEvent) => void;
|
||||
onResponderRelease?: (e: GestureResponderEvent) => void;
|
||||
onResponderTerminate?: (e: GestureResponderEvent) => void;
|
||||
onResponderTerminationRequest?: (e: GestureResponderEvent) => boolean;
|
||||
onClick?: (e: GestureResponderEvent) => void;
|
||||
transform?: string;
|
||||
gradientTransform?: string;
|
||||
patternTransform?: string;
|
||||
'transform-origin'?: string;
|
||||
href?: RNImageProps['source'] | string | null;
|
||||
style?: object;
|
||||
ref?: unknown;
|
||||
} = {
|
||||
...(hasTouchableProperty(props)
|
||||
? {
|
||||
onStartShouldSetResponder:
|
||||
self.touchableHandleStartShouldSetResponder,
|
||||
onResponderTerminationRequest:
|
||||
self.touchableHandleResponderTerminationRequest,
|
||||
onResponderGrant: self.touchableHandleResponderGrant,
|
||||
onResponderMove: self.touchableHandleResponderMove,
|
||||
onResponderRelease: self.touchableHandleResponderRelease,
|
||||
onResponderTerminate: self.touchableHandleResponderTerminate,
|
||||
}
|
||||
: null),
|
||||
...rest,
|
||||
};
|
||||
|
||||
if (origin != null) {
|
||||
clean['transform-origin'] = origin.toString().replace(',', ' ');
|
||||
} else if (originX != null || originY != null) {
|
||||
clean['transform-origin'] = `${originX || 0} ${originY || 0}`;
|
||||
}
|
||||
|
||||
// we do it like this because setting transform as undefined causes error in web
|
||||
const parsedTransform = parseTransformProp(transform, props);
|
||||
if (parsedTransform) {
|
||||
clean.transform = parsedTransform;
|
||||
}
|
||||
const parsedGradientTransform = parseTransformProp(gradientTransform);
|
||||
if (parsedGradientTransform) {
|
||||
clean.gradientTransform = parsedGradientTransform;
|
||||
}
|
||||
const parsedPatternTransform = parseTransformProp(patternTransform);
|
||||
if (parsedPatternTransform) {
|
||||
clean.patternTransform = parsedPatternTransform;
|
||||
}
|
||||
|
||||
clean.ref = (el: SVGElement | null) => {
|
||||
self.elementRef.current = el;
|
||||
if (typeof forwardedRef === 'function') {
|
||||
forwardedRef(el);
|
||||
} else if (forwardedRef) {
|
||||
forwardedRef.current = el;
|
||||
}
|
||||
};
|
||||
|
||||
const styles: {
|
||||
fontStyle?: string;
|
||||
fontFamily?: string;
|
||||
fontSize?: NumberProp;
|
||||
fontWeight?: NumberProp;
|
||||
} = {};
|
||||
|
||||
if (fontFamily != null) {
|
||||
styles.fontFamily = fontFamily;
|
||||
}
|
||||
if (fontSize != null) {
|
||||
styles.fontSize = fontSize;
|
||||
}
|
||||
if (fontWeight != null) {
|
||||
styles.fontWeight = fontWeight;
|
||||
}
|
||||
if (fontStyle != null) {
|
||||
styles.fontStyle = fontStyle;
|
||||
}
|
||||
clean.style = resolve(style, styles);
|
||||
if (props.onPress != null) {
|
||||
clean.onClick = props.onPress;
|
||||
}
|
||||
if (props.href !== null) {
|
||||
clean.href = resolveAssetUri(props.href)?.uri;
|
||||
}
|
||||
return clean;
|
||||
};
|
||||
@@ -2,7 +2,7 @@ import type { ComponentType, ComponentProps } from 'react';
|
||||
import * as React from 'react';
|
||||
import { Component, useEffect, useMemo, useState } from 'react';
|
||||
import type { SvgProps } from './elements/Svg';
|
||||
import { tags } from './tags';
|
||||
import { tags } from './xmlTags';
|
||||
|
||||
function missingTag() {
|
||||
return null;
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import Svg, {
|
||||
import {
|
||||
Circle,
|
||||
ClipPath,
|
||||
Defs,
|
||||
Ellipse,
|
||||
FeColorMatrix,
|
||||
FeGaussianBlur,
|
||||
FeOffset,
|
||||
Filter,
|
||||
ForeignObject,
|
||||
G,
|
||||
Image,
|
||||
Line,
|
||||
@@ -16,16 +21,13 @@ import Svg, {
|
||||
RadialGradient,
|
||||
Rect,
|
||||
Stop,
|
||||
Svg,
|
||||
Symbol,
|
||||
Text,
|
||||
TextPath,
|
||||
TSpan,
|
||||
Use,
|
||||
Symbol,
|
||||
Filter,
|
||||
FeColorMatrix,
|
||||
FeGaussianBlur,
|
||||
FeOffset,
|
||||
} from './ReactNativeSVG';
|
||||
} from './elements';
|
||||
|
||||
export const tags = {
|
||||
circle: Circle,
|
||||
@@ -36,6 +38,7 @@ export const tags = {
|
||||
feColorMatrix: FeColorMatrix,
|
||||
feGaussianBlur: FeGaussianBlur,
|
||||
feOffset: FeOffset,
|
||||
foreignObject: ForeignObject,
|
||||
g: G,
|
||||
image: Image,
|
||||
line: Line,
|
||||
Reference in New Issue
Block a user