mirror of
https://github.com/zoriya/react-native-svg.git
synced 2026-05-27 04:32:57 +00:00
make reanimated work in web (#1886)
Up until now, trying to use reanimated with react-native-svg in react-native-web resulted in an error. This adds a setNativeProps function to the web implementation to directly modify the transform and style props on a SVGElement ref. Since there is a need to track the "last merged props" and those need to be reset on every render, the render method has been moved into the WebShape class and a tag string property has been added. As g had some extra handling for x and y, a prepareProps function was added as well.
This commit is contained in:
committed by
GitHub
parent
795bff5f37
commit
afaf500db9
+108
-76
@@ -58,13 +58,19 @@ interface BaseProps {
|
||||
fontWeight?: NumberProp;
|
||||
fontSize?: NumberProp;
|
||||
fontFamily?: string;
|
||||
forwardedRef: {};
|
||||
forwardedRef?:
|
||||
| React.RefCallback<SVGElement>
|
||||
| React.MutableRefObject<SVGElement | null>;
|
||||
style: Iterable<{}>;
|
||||
}
|
||||
|
||||
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());
|
||||
};
|
||||
|
||||
/**
|
||||
* `react-native-svg` supports additional props that aren't defined in the spec.
|
||||
* This function replaces them in a spec conforming manner.
|
||||
@@ -156,9 +162,14 @@ const prepare = <T extends BaseProps>(
|
||||
clean.transform = transform.join(' ');
|
||||
}
|
||||
|
||||
if (forwardedRef) {
|
||||
clean.ref = forwardedRef;
|
||||
}
|
||||
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;
|
||||
@@ -237,6 +248,53 @@ export class WebShape<
|
||||
C = {},
|
||||
> extends React.Component<P, C> {
|
||||
[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 {}[]).concat(clean.style ?? [])) {
|
||||
// @ts-expect-error "DOM" is not part of `compilerOptions.lib`
|
||||
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 "DOM" is not part of `compilerOptions.lib`
|
||||
current.setAttribute(camelCaseToDashed(cleanAttribute), cleanValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_remeasureMetricsOnActivation: () => void;
|
||||
touchableHandleStartShouldSetResponder?: (
|
||||
e: GestureResponderEvent,
|
||||
@@ -258,30 +316,35 @@ export class WebShape<
|
||||
|
||||
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 {
|
||||
render(): JSX.Element {
|
||||
return createElement('circle', prepare(this));
|
||||
}
|
||||
tag = 'circle' as const;
|
||||
}
|
||||
|
||||
export class ClipPath extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('clipPath', prepare(this));
|
||||
}
|
||||
tag = 'clipPath' as const;
|
||||
}
|
||||
|
||||
export class Defs extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('defs', prepare(this));
|
||||
}
|
||||
tag = 'defs' as const;
|
||||
}
|
||||
|
||||
export class Ellipse extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('ellipse', prepare(this));
|
||||
}
|
||||
tag = 'ellipse' as const;
|
||||
}
|
||||
|
||||
export class G extends WebShape<
|
||||
@@ -291,129 +354,98 @@ export class G extends WebShape<
|
||||
translate?: string;
|
||||
}
|
||||
> {
|
||||
render(): JSX.Element {
|
||||
const { x, y, ...rest } = this.props;
|
||||
tag = 'g' as const;
|
||||
prepareProps(
|
||||
props: BaseProps & {
|
||||
x?: NumberProp;
|
||||
y?: NumberProp;
|
||||
translate?: string;
|
||||
},
|
||||
) {
|
||||
const { x, y, ...rest } = props;
|
||||
|
||||
if ((x || y) && !rest.translate) {
|
||||
rest.translate = `${x || 0}, ${y || 0}`;
|
||||
}
|
||||
|
||||
return createElement('g', prepare(this, rest));
|
||||
return rest;
|
||||
}
|
||||
}
|
||||
|
||||
export class Image extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('image', prepare(this));
|
||||
}
|
||||
tag = 'image' as const;
|
||||
}
|
||||
|
||||
export class Line extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('line', prepare(this));
|
||||
}
|
||||
tag = 'line' as const;
|
||||
}
|
||||
|
||||
export class LinearGradient extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('linearGradient', prepare(this));
|
||||
}
|
||||
tag = 'linearGradient' as const;
|
||||
}
|
||||
|
||||
export class Path extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('path', prepare(this));
|
||||
}
|
||||
tag = 'path' as const;
|
||||
}
|
||||
|
||||
export class Polygon extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('polygon', prepare(this));
|
||||
}
|
||||
tag = 'polygon' as const;
|
||||
}
|
||||
|
||||
export class Polyline extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('polyline', prepare(this));
|
||||
}
|
||||
tag = 'polyline' as const;
|
||||
}
|
||||
|
||||
export class RadialGradient extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('radialGradient', prepare(this));
|
||||
}
|
||||
tag = 'radialGradient' as const;
|
||||
}
|
||||
|
||||
export class Rect extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('rect', prepare(this));
|
||||
}
|
||||
tag = 'rect' as const;
|
||||
}
|
||||
|
||||
export class Stop extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('stop', prepare(this));
|
||||
}
|
||||
tag = 'stop' as const;
|
||||
}
|
||||
|
||||
export class Svg extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('svg', prepare(this));
|
||||
}
|
||||
tag = 'svg' as const;
|
||||
}
|
||||
|
||||
export class Symbol extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('symbol', prepare(this));
|
||||
}
|
||||
tag = 'symbol' as const;
|
||||
}
|
||||
|
||||
export class Text extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('text', prepare(this));
|
||||
}
|
||||
tag = 'text' as const;
|
||||
}
|
||||
|
||||
export class TSpan extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('tspan', prepare(this));
|
||||
}
|
||||
tag = 'tspan' as const;
|
||||
}
|
||||
|
||||
export class TextPath extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('textPath', prepare(this));
|
||||
}
|
||||
tag = 'textPath' as const;
|
||||
}
|
||||
|
||||
export class Use extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('use', prepare(this));
|
||||
}
|
||||
tag = 'use' as const;
|
||||
}
|
||||
|
||||
export class Mask extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('mask', prepare(this));
|
||||
}
|
||||
tag = 'mask' as const;
|
||||
}
|
||||
|
||||
export class ForeignObject extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('foreignObject', prepare(this));
|
||||
}
|
||||
tag = 'foreignObject' as const;
|
||||
}
|
||||
|
||||
export class Marker extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('marker', prepare(this));
|
||||
}
|
||||
tag = 'marker' as const;
|
||||
}
|
||||
|
||||
export class Pattern extends WebShape {
|
||||
render(): JSX.Element {
|
||||
return createElement('pattern', prepare(this));
|
||||
}
|
||||
tag = 'pattern' as const;
|
||||
}
|
||||
|
||||
export default Svg;
|
||||
|
||||
Reference in New Issue
Block a user