@cantoo/rn-svg
Version:
SVG library for react-native
319 lines (307 loc) • 10.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _react = _interopRequireDefault(require("react"));
var _reactNative = require("react-native");
var _reactNativeWeb = require("react-native-web");
var _useElementLayout = _interopRequireDefault(require("react-native-web/dist/modules/useElementLayout"));
var _usePlatformMethods = _interopRequireDefault(require("react-native-web/dist/modules/usePlatformMethods"));
var _useResponderEvents = _interopRequireDefault(require("react-native-web/dist/modules/useResponderEvents"));
var _useMergeRefs = _interopRequireDefault(require("react-native-web/dist/modules/useMergeRefs"));
var forwardedProps = _interopRequireWildcard(require("react-native-web/dist/modules/forwardedProps"));
var _pick = _interopRequireDefault(require("react-native-web/dist/modules/pick"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const forwardPropsList = {
...forwardedProps.defaultProps,
...forwardedProps.accessibilityProps,
...forwardedProps.clickProps,
...forwardedProps.focusProps,
...forwardedProps.keyboardProps,
...forwardedProps.mouseProps,
...forwardedProps.touchProps,
...forwardedProps.styleProps
};
const pickProps = props => (0, _pick.default)(props, forwardPropsList);
// rgba values inside range 0 to 1 inclusive
// rgbaArray = [r, g, b, a]
// argb values inside range 0x00 to 0xff inclusive
// int32ARGBColor = 0xaarrggbb
/*
ColumnMajorTransformMatrix
[a, b, c, d, tx, ty]
This matrix can be visualized as:
╔═ ═╗
║ a c tx ║
║ b d ty ║
║ 0 0 1 ║
╚═ ═╝
*/
const SvgXml = /*#__PURE__*/_react.default.forwardRef(({
xml,
...props
}, forwardedRef) => {
const {
innerSVG,
svgAttributes
} = _react.default.useMemo(() => {
const {
attributes,
innerHTML
} = parseSVG(xml || '');
return {
innerSVG: innerHTML,
svgAttributes: kebabToCamel(attributes)
};
}, [xml]);
const svgRef = _react.default.useRef(null);
_react.default.useLayoutEffect(() => {
if (!svgRef.current) {
return;
}
svgRef.current.innerHTML = innerSVG;
}, [innerSVG]);
const {
height,
width,
viewBox,
preserveAspectRatio,
title,
opacity,
fill,
fillOpacity,
fillRule,
transform,
stroke,
strokeWidth,
strokeOpacity,
strokeDasharray,
strokeDashoffset,
strokeLinecap,
strokeLinejoin,
strokeMiterlimit,
clipRule,
clipPath,
vectorEffect,
id,
markerStart,
markerMid,
markerEnd,
mask,
originX,
originY,
translate,
scale,
rotation,
skewX,
skewY,
pointerEvents = 'none',
style,
// props that should be applyed to the View container
...containerProps
} = props;
const svgTransform = _react.default.useMemo(() => {
const transformArray = [];
if (originX != null || originY != null) {
transformArray.push(`translate(${originX || 0}, ${originY || 0})`);
}
if (translate != null) {
transformArray.push(`translate(${translate})`);
}
if (scale != null) {
transformArray.push(`scale(${scale})`);
}
// rotation maps to rotate, not to collide with the text rotate attribute (which acts per glyph rather than block)
if (rotation != null) {
transformArray.push(`rotate(${rotation})`);
}
if (skewX != null) {
transformArray.push(`skewX(${skewX})`);
}
if (skewY != null) {
transformArray.push(`skewY(${skewY})`);
}
if (originX != null || originY != null) {
transformArray.push(`translate(${-(originX || 0)}, ${-(originY || 0)})`);
}
if (transform) {
transformArray.push(transform);
}
return transformArray.length ? transformArray.join(' ') : undefined;
}, [originX, originY, rotation, scale, skewX, skewY, transform, translate]);
const containerStyle = _react.default.useMemo(() => {
const [,, widthBox, heightBox] = (viewBox || '').split(' ');
const {
width: styleWidth,
height: styleHeight,
...otherStyle
} = _reactNative.StyleSheet.flatten(style) || {};
return [styleSheet.view$raw, removeUndefined({
...otherStyle,
display: 'inline-flex',
width: parseDimension(width ?? styleWidth ?? svgAttributes.width ?? widthBox),
height: parseDimension(height ?? styleHeight ?? svgAttributes.height ?? heightBox)
})];
}, [svgAttributes.height, svgAttributes.width, height, style, viewBox, width]);
// these props should override the xml props
const overrideProps = _react.default.useMemo(() => ({
...removeUndefined(svgAttributes),
...removeUndefined({
style: styleSheet.svg,
transform: svgTransform,
viewBox,
preserveAspectRatio,
title,
opacity,
fill,
fillOpacity,
fillRule,
stroke,
strokeWidth,
strokeOpacity,
strokeDasharray,
strokeDashoffset,
strokeLinecap,
strokeLinejoin,
strokeMiterlimit,
clipRule,
clipPath,
vectorEffect,
pointerEvents,
id,
markerStart,
markerMid,
markerEnd,
mask
}),
width: undefined,
height: undefined
}), [svgAttributes, svgTransform, viewBox, preserveAspectRatio, title, opacity, fill, fillOpacity, fillRule, stroke, strokeWidth, strokeOpacity, strokeDasharray, strokeDashoffset, strokeLinecap, strokeLinejoin, strokeMiterlimit, clipRule, clipPath, vectorEffect, pointerEvents, id, markerStart, markerMid, markerEnd, mask]);
const Svg = (0, _reactNativeWeb.unstable_createElement)('svg', {
ref: svgRef,
...overrideProps
});
const {
onLayout,
onMoveShouldSetResponder,
onMoveShouldSetResponderCapture,
onResponderEnd,
onResponderGrant,
onResponderMove,
onResponderReject,
onResponderRelease,
onResponderStart,
onResponderTerminate,
onResponderTerminationRequest,
onStartShouldSetResponder,
onStartShouldSetResponderCapture,
...finalProps
} = containerProps;
const finalContainerProps = pickProps({
...finalProps,
children: Svg,
style: containerStyle
});
const hostRef = _react.default.useRef(null);
(0, _useElementLayout.default)(hostRef, onLayout);
const responderConfig = _react.default.useMemo(() => ({
onMoveShouldSetResponder,
onMoveShouldSetResponderCapture,
onResponderEnd,
onResponderGrant,
onResponderMove,
onResponderReject,
onResponderRelease,
onResponderStart,
onResponderTerminate,
onResponderTerminationRequest,
onStartShouldSetResponder,
onStartShouldSetResponderCapture
}), [onMoveShouldSetResponder, onMoveShouldSetResponderCapture, onResponderEnd, onResponderGrant, onResponderMove, onResponderReject, onResponderRelease, onResponderStart, onResponderTerminate, onResponderTerminationRequest, onStartShouldSetResponder, onStartShouldSetResponderCapture]);
(0, _useResponderEvents.default)(hostRef, responderConfig);
const platformMethodsRef = (0, _usePlatformMethods.default)(finalContainerProps);
const setRef = (0, _useMergeRefs.default)(hostRef, platformMethodsRef, forwardedRef);
finalContainerProps.ref = setRef;
return (0, _reactNativeWeb.unstable_createElement)('span', finalContainerProps);
});
SvgXml.displayName = 'Svg';
var _default = exports.default = SvgXml;
/** polyfill for Node < 12 */
function matchAll(str) {
return re => {
const matches = [];
let groups;
while (groups = re.exec(str)) {
matches.push(groups);
}
return matches;
};
}
function parseSVG(svg) {
const contentMatch = svg.match(/<svg(.*)<\/svg>/ims);
const content = contentMatch ? contentMatch[1] : '';
const [, attrs, innerHTML] = content.match(/(.*?)>(.*)/ims) || ['', '', ''];
const attributes = [...matchAll(attrs)(/([a-z0-9-]+)(=['"](.*?)['"])?/gims)].map(([, key,, value]) => ({
[key]: value
}));
return {
attributes,
innerHTML
};
}
const styleSheet = _reactNative.StyleSheet.create({
svg: {
flexGrow: 1,
flexShrink: 1
},
view$raw: {
alignItems: 'stretch',
backgroundColor: 'transparent',
// Hack because those are web props unknown from react-native
['border']: '0 solid black',
['boxSizing']: 'border-box',
display: 'inline-flex',
flexBasis: 'auto',
flexDirection: 'column',
color: 'inherit',
flexShrink: 0,
margin: 0,
minHeight: 0,
minWidth: 0,
padding: 0,
position: 'relative',
zIndex: 0
}
});
function kebabToCamel(attrs) {
const camelObj = {};
attrs.forEach(attr => {
const key = Object.keys(attr)[0];
camelObj[key.replace(/-./g, x => x.toUpperCase()[1])] = attr[key];
});
return camelObj;
}
function removeUndefined(obj) {
const finalObj = {};
Object.keys(obj).forEach(key => {
const value = obj[key];
if (value !== undefined) {
finalObj[key] = value;
}
});
return finalObj;
}
/** Ensure that the dimension is valid for React Native */
function parseDimension(dimension) {
if (typeof dimension === 'number') {
return dimension;
} else if (!dimension) {
return dimension;
} else {
return dimension.trim().match(/^\d+(\.\d+)?$/) ? Number.parseFloat(dimension) : dimension;
}
}
//# sourceMappingURL=SvgXml.web.js.map