You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
303 lines
9.0 KiB
303 lines
9.0 KiB
import * as React from 'react';
|
|
import {
|
|
// @ts-ignore
|
|
unstable_createElement as ucE, createElement as cE } from 'react-native';
|
|
import SvgTouchableMixin from './lib/SvgTouchableMixin';
|
|
import { resolve } from './lib/resolve';
|
|
import { transformsArrayToProps } from './lib/extract/extractTransform';
|
|
const createElement = cE || ucE;
|
|
const hasTouchableProperty = props => props.onPress || props.onPressIn || props.onPressOut || props.onLongPress;
|
|
const camelCaseToDashed = camelCase => {
|
|
return camelCase.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
|
|
};
|
|
function stringifyTransformProps(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;
|
|
}
|
|
function parseTransformProp(transform, props) {
|
|
const transformArray = [];
|
|
props && transformArray.push(...stringifyTransformProps(props));
|
|
if (Array.isArray(transform)) {
|
|
if (typeof transform[0] === 'number') {
|
|
transformArray.push(`matrix(${transform.join(' ')})`);
|
|
} else {
|
|
const stringifiedProps = transformsArrayToProps(transform);
|
|
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 = function (self) {
|
|
let props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : self.props;
|
|
const {
|
|
transform,
|
|
origin,
|
|
originX,
|
|
originY,
|
|
fontFamily,
|
|
fontSize,
|
|
fontWeight,
|
|
fontStyle,
|
|
style,
|
|
forwardedRef,
|
|
gradientTransform,
|
|
patternTransform,
|
|
...rest
|
|
} = props;
|
|
const clean = {
|
|
...(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}`;
|
|
}
|
|
clean.transform = parseTransformProp(transform, props);
|
|
clean.gradientTransform = parseTransformProp(gradientTransform);
|
|
clean.patternTransform = parseTransformProp(patternTransform);
|
|
clean.ref = el => {
|
|
self.elementRef.current = el;
|
|
if (typeof forwardedRef === 'function') {
|
|
forwardedRef(el);
|
|
} else if (forwardedRef) {
|
|
forwardedRef.current = el;
|
|
}
|
|
};
|
|
const styles = {};
|
|
if (fontFamily != null) {
|
|
styles.fontFamily = fontFamily;
|
|
}
|
|
if (fontSize != null) {
|
|
styles.fontSize = fontSize;
|
|
}
|
|
if (fontWeight != null) {
|
|
styles.fontWeight = fontWeight;
|
|
}
|
|
if (fontStyle != null) {
|
|
styles.fontStyle = fontStyle;
|
|
}
|
|
clean.style = resolve(style, styles);
|
|
return clean;
|
|
};
|
|
const getBoundingClientRect = node => {
|
|
if (node) {
|
|
// @ts-ignore
|
|
const isElement = node.nodeType === 1; /* Node.ELEMENT_NODE */
|
|
// @ts-ignore
|
|
if (isElement && typeof node.getBoundingClientRect === 'function') {
|
|
// @ts-ignore
|
|
return node.getBoundingClientRect();
|
|
}
|
|
}
|
|
};
|
|
const measureLayout = (node, callback) => {
|
|
// @ts-ignore
|
|
const relativeNode = node && node.parentNode;
|
|
if (relativeNode) {
|
|
setTimeout(() => {
|
|
// @ts-ignore
|
|
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);
|
|
}
|
|
};
|
|
function remeasure() {
|
|
// @ts-ignore
|
|
const tag = this.state.touchable.responderID;
|
|
if (tag == null) {
|
|
return;
|
|
}
|
|
// @ts-ignore
|
|
measureLayout(tag, this._handleQueryLayout);
|
|
}
|
|
export class WebShape extends React.Component {
|
|
prepareProps(props) {
|
|
return props;
|
|
}
|
|
elementRef = /*#__PURE__*/React.createRef();
|
|
lastMergedProps = {};
|
|
|
|
/**
|
|
* disclaimer: I am not sure why the props are wrapped in a `style` attribute here, but that's how reanimated calls it
|
|
*/
|
|
setNativeProps(props) {
|
|
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];
|
|
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 [].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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
constructor(props, context) {
|
|
super(props, context);
|
|
|
|
// 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() {
|
|
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 {
|
|
tag = 'circle';
|
|
}
|
|
export class ClipPath extends WebShape {
|
|
tag = 'clipPath';
|
|
}
|
|
export class Defs extends WebShape {
|
|
tag = 'defs';
|
|
}
|
|
export class Ellipse extends WebShape {
|
|
tag = 'ellipse';
|
|
}
|
|
export class G extends WebShape {
|
|
tag = 'g';
|
|
prepareProps(props) {
|
|
const {
|
|
x,
|
|
y,
|
|
...rest
|
|
} = props;
|
|
if ((x || y) && !rest.translate) {
|
|
rest.translate = `${x || 0}, ${y || 0}`;
|
|
}
|
|
return rest;
|
|
}
|
|
}
|
|
export class Image extends WebShape {
|
|
tag = 'image';
|
|
}
|
|
export class Line extends WebShape {
|
|
tag = 'line';
|
|
}
|
|
export class LinearGradient extends WebShape {
|
|
tag = 'linearGradient';
|
|
}
|
|
export class Path extends WebShape {
|
|
tag = 'path';
|
|
}
|
|
export class Polygon extends WebShape {
|
|
tag = 'polygon';
|
|
}
|
|
export class Polyline extends WebShape {
|
|
tag = 'polyline';
|
|
}
|
|
export class RadialGradient extends WebShape {
|
|
tag = 'radialGradient';
|
|
}
|
|
export class Rect extends WebShape {
|
|
tag = 'rect';
|
|
}
|
|
export class Stop extends WebShape {
|
|
tag = 'stop';
|
|
}
|
|
export class Svg extends WebShape {
|
|
tag = 'svg';
|
|
}
|
|
export class Symbol extends WebShape {
|
|
tag = 'symbol';
|
|
}
|
|
export class Text extends WebShape {
|
|
tag = 'text';
|
|
}
|
|
export class TSpan extends WebShape {
|
|
tag = 'tspan';
|
|
}
|
|
export class TextPath extends WebShape {
|
|
tag = 'textPath';
|
|
}
|
|
export class Use extends WebShape {
|
|
tag = 'use';
|
|
}
|
|
export class Mask extends WebShape {
|
|
tag = 'mask';
|
|
}
|
|
export class ForeignObject extends WebShape {
|
|
tag = 'foreignObject';
|
|
}
|
|
export class Marker extends WebShape {
|
|
tag = 'marker';
|
|
}
|
|
export class Pattern extends WebShape {
|
|
tag = 'pattern';
|
|
}
|
|
export default Svg;
|
|
//# sourceMappingURL=ReactNativeSVG.web.js.map
|