react-svg
Version:
A React component that injects SVG into the DOM.
300 lines (292 loc) • 11.1 kB
JavaScript
;
var _objectWithoutPropertiesLoose = require('@babel/runtime/helpers/objectWithoutPropertiesLoose');
var _extends = require('@babel/runtime/helpers/extends');
var _inheritsLoose = require('@babel/runtime/helpers/inheritsLoose');
var svgInjector = require('@tanem/svg-injector');
var PropTypes = require('prop-types');
var React = require('react');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var PropTypes__namespace = /*#__PURE__*/_interopNamespaceDefault(PropTypes);
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
// Hat-tip: https://github.com/mui/material-ui/tree/master/packages/mui-utils/src.
var ownerWindow = function ownerWindow(node) {
var doc = (node == null ? void 0 : node.ownerDocument) || document;
return doc.defaultView || window;
};
// Hat-tip: https://github.com/developit/preact-compat/blob/master/src/index.js#L402.
var shallowDiffers = function shallowDiffers(a, b) {
for (var i in a) {
if (!(i in b)) {
return true;
}
}
for (var _i in b) {
if (a[_i] !== b[_i]) {
return true;
}
}
return false;
};
var _excluded = ["afterInjection", "beforeInjection", "desc", "evalScripts", "fallback", "httpRequestWithCredentials", "loading", "renumerateIRIElements", "src", "title", "useRequestCache", "wrapper"];
var svgNamespace = 'http://www.w3.org/2000/svg';
var xlinkNamespace = 'http://www.w3.org/1999/xlink';
// Random prefix avoids ID collisions when multiple copies of react-svg are
// bundled (e.g. microfrontends). The counter ensures each component instance
// within the same bundle gets a unique ID.
var idPrefix = "react-svg-" + Math.random().toString(36).slice(2, 6);
var idCounter = 0;
var ReactSVG = /*#__PURE__*/function (_React$Component) {
function ReactSVG() {
var _this;
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
_this = _React$Component.call.apply(_React$Component, [this].concat(args)) || this;
_this.initialState = {
hasError: false,
isLoading: true
};
_this.state = _this.initialState;
_this._isMounted = false;
_this.reactWrapper = void 0;
_this.nonReactWrapper = void 0;
_this.refCallback = function (reactWrapper) {
_this.reactWrapper = reactWrapper;
};
return _this;
}
_inheritsLoose(ReactSVG, _React$Component);
var _proto = ReactSVG.prototype;
_proto.renderSVG = function renderSVG() {
var _this2 = this;
/* istanbul ignore else */
if (this.reactWrapper instanceof ownerWindow(this.reactWrapper).Node) {
var _this$props = this.props,
desc = _this$props.desc,
evalScripts = _this$props.evalScripts,
httpRequestWithCredentials = _this$props.httpRequestWithCredentials,
renumerateIRIElements = _this$props.renumerateIRIElements,
src = _this$props.src,
title = _this$props.title,
useRequestCache = _this$props.useRequestCache;
var onError = this.props.onError;
var beforeInjection = this.props.beforeInjection;
var afterInjection = this.props.afterInjection;
var wrapper = this.props.wrapper;
var nonReactWrapper;
var nonReactTarget;
if (wrapper === 'svg') {
nonReactWrapper = document.createElementNS(svgNamespace, wrapper);
nonReactWrapper.setAttribute('xmlns', svgNamespace);
nonReactWrapper.setAttribute('xmlns:xlink', xlinkNamespace);
nonReactTarget = document.createElementNS(svgNamespace, wrapper);
} else {
nonReactWrapper = document.createElement(wrapper);
nonReactTarget = document.createElement(wrapper);
}
nonReactWrapper.appendChild(nonReactTarget);
nonReactTarget.dataset.src = src;
this.nonReactWrapper = this.reactWrapper.appendChild(nonReactWrapper);
var handleError = function handleError(error) {
_this2.removeSVG();
if (!_this2._isMounted) {
onError(error);
return;
}
_this2.setState(function () {
return {
hasError: true,
isLoading: false
};
}, function () {
onError(error);
});
};
var afterEach = function afterEach(error, svg) {
if (error) {
handleError(error);
return;
}
// TODO (Tane): It'd be better to cleanly unsubscribe from SVGInjector
// callbacks instead of tracking a property like this.
if (_this2._isMounted) {
_this2.setState(function () {
return {
isLoading: false
};
}, function () {
try {
afterInjection(svg);
} catch (afterInjectionError) {
handleError(afterInjectionError);
}
});
}
};
// WAI best practice: SVGs need role="img" plus aria-labelledby/
// aria-describedby pointing to <title>/<desc> element IDs for screen
// readers to announce them. svg-injector copies the HTML title
// *attribute* (tooltip) but doesn't create SVG-namespace child
// elements or ARIA linkage, so we handle that here.
var beforeEach = function beforeEach(svg) {
svg.setAttribute('role', 'img');
var ariaLabelledBy = [];
var ariaDescribedBy = [];
if (title) {
var originalTitle = svg.querySelector(':scope > title');
if (originalTitle) {
svg.removeChild(originalTitle);
}
var titleId = idPrefix + "-title-" + ++idCounter;
// createElementNS is required: createElement would produce an
// HTML-namespace node that screen readers ignore inside SVG.
var newTitle = document.createElementNS(svgNamespace, 'title');
newTitle.id = titleId;
newTitle.textContent = title;
svg.prepend(newTitle);
ariaLabelledBy.push(titleId);
}
if (desc) {
var originalDesc = svg.querySelector(':scope > desc');
if (originalDesc) {
svg.removeChild(originalDesc);
}
var descId = idPrefix + "-desc-" + ++idCounter;
var newDesc = document.createElementNS(svgNamespace, 'desc');
newDesc.id = descId;
newDesc.textContent = desc;
var existingTitle = svg.querySelector(':scope > title');
if (existingTitle) {
existingTitle.after(newDesc);
} else {
svg.prepend(newDesc);
}
ariaDescribedBy.push(descId);
}
if (ariaLabelledBy.length > 0) {
svg.setAttribute('aria-labelledby', ariaLabelledBy.join(' '));
}
if (ariaDescribedBy.length > 0) {
svg.setAttribute('aria-describedby', ariaDescribedBy.join(' '));
}
try {
beforeInjection(svg);
} catch (error) {
handleError(error);
}
};
svgInjector.SVGInjector(nonReactTarget, {
afterEach: afterEach,
beforeEach: beforeEach,
cacheRequests: useRequestCache,
evalScripts: evalScripts,
httpRequestWithCredentials: httpRequestWithCredentials,
renumerateIRIElements: renumerateIRIElements
});
}
};
_proto.removeSVG = function removeSVG() {
var _this$nonReactWrapper;
if ((_this$nonReactWrapper = this.nonReactWrapper) != null && _this$nonReactWrapper.parentNode) {
this.nonReactWrapper.parentNode.removeChild(this.nonReactWrapper);
this.nonReactWrapper = null;
}
};
_proto.componentDidMount = function componentDidMount() {
this._isMounted = true;
this.renderSVG();
};
_proto.componentDidUpdate = function componentDidUpdate(prevProps) {
var _this3 = this;
if (shallowDiffers(_extends({}, prevProps), this.props)) {
this.setState(function () {
return _this3.initialState;
}, function () {
_this3.removeSVG();
_this3.renderSVG();
});
}
};
_proto.componentWillUnmount = function componentWillUnmount() {
this._isMounted = false;
this.removeSVG();
};
_proto.render = function render() {
/* eslint-disable @typescript-eslint/no-unused-vars */
var _this$props2 = this.props;
_this$props2.afterInjection;
_this$props2.beforeInjection;
_this$props2.desc;
_this$props2.evalScripts;
var Fallback = _this$props2.fallback;
_this$props2.httpRequestWithCredentials;
var Loading = _this$props2.loading;
_this$props2.renumerateIRIElements;
_this$props2.src;
_this$props2.title;
_this$props2.useRequestCache;
var wrapper = _this$props2.wrapper,
rest = _objectWithoutPropertiesLoose(_this$props2, _excluded);
/* eslint-enable @typescript-eslint/no-unused-vars */
var Wrapper = wrapper;
return /*#__PURE__*/React__namespace.createElement(Wrapper, _extends({}, rest, {
ref: this.refCallback
}, wrapper === 'svg' ? {
xmlns: svgNamespace,
xmlnsXlink: xlinkNamespace
} : {}), this.state.isLoading && Loading && /*#__PURE__*/React__namespace.createElement(Loading, null), this.state.hasError && Fallback && /*#__PURE__*/React__namespace.createElement(Fallback, null));
};
return ReactSVG;
}(React__namespace.Component);
ReactSVG.defaultProps = {
afterInjection: function afterInjection() {
return undefined;
},
beforeInjection: function beforeInjection() {
return undefined;
},
desc: '',
evalScripts: 'never',
fallback: null,
httpRequestWithCredentials: false,
loading: null,
onError: function onError() {
return undefined;
},
renumerateIRIElements: true,
title: '',
useRequestCache: true,
wrapper: 'div'
};
ReactSVG.propTypes = {
afterInjection: PropTypes__namespace.func,
beforeInjection: PropTypes__namespace.func,
desc: PropTypes__namespace.string,
evalScripts: PropTypes__namespace.oneOf(['always', 'once', 'never']),
fallback: PropTypes__namespace.oneOfType([PropTypes__namespace.func, PropTypes__namespace.object, PropTypes__namespace.string]),
httpRequestWithCredentials: PropTypes__namespace.bool,
loading: PropTypes__namespace.oneOfType([PropTypes__namespace.func, PropTypes__namespace.object, PropTypes__namespace.string]),
onError: PropTypes__namespace.func,
renumerateIRIElements: PropTypes__namespace.bool,
src: PropTypes__namespace.string.isRequired,
title: PropTypes__namespace.string,
useRequestCache: PropTypes__namespace.bool,
wrapper: PropTypes__namespace.oneOf(['div', 'span', 'svg'])
};
exports.ReactSVG = ReactSVG;
//# sourceMappingURL=react-svg.cjs.development.js.map