browser-sprite.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937
  1. (function (global, factory) {
  2. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  3. typeof define === 'function' && define.amd ? define(factory) :
  4. (global.BrowserSprite = factory());
  5. }(this, (function () { 'use strict';
  6. var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
  7. function createCommonjsModule(fn, module) {
  8. return module = { exports: {} }, fn(module, module.exports), module.exports;
  9. }
  10. var deepmerge = createCommonjsModule(function (module, exports) {
  11. (function (root, factory) {
  12. if (typeof undefined === 'function' && undefined.amd) {
  13. undefined(factory);
  14. } else {
  15. module.exports = factory();
  16. }
  17. }(commonjsGlobal, function () {
  18. function isMergeableObject(val) {
  19. var nonNullObject = val && typeof val === 'object';
  20. return nonNullObject
  21. && Object.prototype.toString.call(val) !== '[object RegExp]'
  22. && Object.prototype.toString.call(val) !== '[object Date]'
  23. }
  24. function emptyTarget(val) {
  25. return Array.isArray(val) ? [] : {}
  26. }
  27. function cloneIfNecessary(value, optionsArgument) {
  28. var clone = optionsArgument && optionsArgument.clone === true;
  29. return (clone && isMergeableObject(value)) ? deepmerge(emptyTarget(value), value, optionsArgument) : value
  30. }
  31. function defaultArrayMerge(target, source, optionsArgument) {
  32. var destination = target.slice();
  33. source.forEach(function(e, i) {
  34. if (typeof destination[i] === 'undefined') {
  35. destination[i] = cloneIfNecessary(e, optionsArgument);
  36. } else if (isMergeableObject(e)) {
  37. destination[i] = deepmerge(target[i], e, optionsArgument);
  38. } else if (target.indexOf(e) === -1) {
  39. destination.push(cloneIfNecessary(e, optionsArgument));
  40. }
  41. });
  42. return destination
  43. }
  44. function mergeObject(target, source, optionsArgument) {
  45. var destination = {};
  46. if (isMergeableObject(target)) {
  47. Object.keys(target).forEach(function (key) {
  48. destination[key] = cloneIfNecessary(target[key], optionsArgument);
  49. });
  50. }
  51. Object.keys(source).forEach(function (key) {
  52. if (!isMergeableObject(source[key]) || !target[key]) {
  53. destination[key] = cloneIfNecessary(source[key], optionsArgument);
  54. } else {
  55. destination[key] = deepmerge(target[key], source[key], optionsArgument);
  56. }
  57. });
  58. return destination
  59. }
  60. function deepmerge(target, source, optionsArgument) {
  61. var array = Array.isArray(source);
  62. var options = optionsArgument || { arrayMerge: defaultArrayMerge };
  63. var arrayMerge = options.arrayMerge || defaultArrayMerge;
  64. if (array) {
  65. return Array.isArray(target) ? arrayMerge(target, source, optionsArgument) : cloneIfNecessary(source, optionsArgument)
  66. } else {
  67. return mergeObject(target, source, optionsArgument)
  68. }
  69. }
  70. deepmerge.all = function deepmergeAll(array, optionsArgument) {
  71. if (!Array.isArray(array) || array.length < 2) {
  72. throw new Error('first argument should be an array with at least two elements')
  73. }
  74. // we are sure there are at least 2 values, so it is safe to have no initial value
  75. return array.reduce(function(prev, next) {
  76. return deepmerge(prev, next, optionsArgument)
  77. })
  78. };
  79. return deepmerge
  80. }));
  81. });
  82. //
  83. // An event handler can take an optional event argument
  84. // and should not return a value
  85. // An array of all currently registered event handlers for a type
  86. // A map of event types and their corresponding event handlers.
  87. /** Mitt: Tiny (~200b) functional event emitter / pubsub.
  88. * @name mitt
  89. * @returns {Mitt}
  90. */
  91. function mitt(all ) {
  92. all = all || Object.create(null);
  93. return {
  94. /**
  95. * Register an event handler for the given type.
  96. *
  97. * @param {String} type Type of event to listen for, or `"*"` for all events
  98. * @param {Function} handler Function to call in response to given event
  99. * @memberOf mitt
  100. */
  101. on: function on(type , handler ) {
  102. (all[type] || (all[type] = [])).push(handler);
  103. },
  104. /**
  105. * Remove an event handler for the given type.
  106. *
  107. * @param {String} type Type of event to unregister `handler` from, or `"*"`
  108. * @param {Function} handler Handler function to remove
  109. * @memberOf mitt
  110. */
  111. off: function off(type , handler ) {
  112. if (all[type]) {
  113. all[type].splice(all[type].indexOf(handler) >>> 0, 1);
  114. }
  115. },
  116. /**
  117. * Invoke all handlers for the given type.
  118. * If present, `"*"` handlers are invoked after type-matched handlers.
  119. *
  120. * @param {String} type The event type to invoke
  121. * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler
  122. * @memberof mitt
  123. */
  124. emit: function emit(type , evt ) {
  125. (all[type] || []).map(function (handler) { handler(evt); });
  126. (all['*'] || []).map(function (handler) { handler(type, evt); });
  127. }
  128. };
  129. }
  130. var namespaces_1 = createCommonjsModule(function (module, exports) {
  131. var namespaces = {
  132. svg: {
  133. name: 'xmlns',
  134. uri: 'http://www.w3.org/2000/svg'
  135. },
  136. xlink: {
  137. name: 'xmlns:xlink',
  138. uri: 'http://www.w3.org/1999/xlink'
  139. }
  140. };
  141. exports.default = namespaces;
  142. module.exports = exports.default;
  143. });
  144. /**
  145. * @param {Object} attrs
  146. * @return {string}
  147. */
  148. var objectToAttrsString = function (attrs) {
  149. return Object.keys(attrs).map(function (attr) {
  150. var value = attrs[attr].toString().replace(/"/g, '&quot;');
  151. return (attr + "=\"" + value + "\"");
  152. }).join(' ');
  153. };
  154. var svg = namespaces_1.svg;
  155. var xlink = namespaces_1.xlink;
  156. var defaultAttrs = {};
  157. defaultAttrs[svg.name] = svg.uri;
  158. defaultAttrs[xlink.name] = xlink.uri;
  159. /**
  160. * @param {string} [content]
  161. * @param {Object} [attributes]
  162. * @return {string}
  163. */
  164. var wrapInSvgString = function (content, attributes) {
  165. if ( content === void 0 ) content = '';
  166. var attrs = deepmerge(defaultAttrs, attributes || {});
  167. var attrsRendered = objectToAttrsString(attrs);
  168. return ("<svg " + attrsRendered + ">" + content + "</svg>");
  169. };
  170. var svg$1 = namespaces_1.svg;
  171. var xlink$1 = namespaces_1.xlink;
  172. var defaultConfig = {
  173. attrs: ( obj = {
  174. style: ['position: absolute', 'width: 0', 'height: 0'].join('; '),
  175. 'aria-hidden': 'true'
  176. }, obj[svg$1.name] = svg$1.uri, obj[xlink$1.name] = xlink$1.uri, obj )
  177. };
  178. var obj;
  179. var Sprite = function Sprite(config) {
  180. this.config = deepmerge(defaultConfig, config || {});
  181. this.symbols = [];
  182. };
  183. /**
  184. * Add new symbol. If symbol with the same id exists it will be replaced.
  185. * @param {SpriteSymbol} symbol
  186. * @return {boolean} `true` - symbol was added, `false` - replaced
  187. */
  188. Sprite.prototype.add = function add (symbol) {
  189. var ref = this;
  190. var symbols = ref.symbols;
  191. var existing = this.find(symbol.id);
  192. if (existing) {
  193. symbols[symbols.indexOf(existing)] = symbol;
  194. return false;
  195. }
  196. symbols.push(symbol);
  197. return true;
  198. };
  199. /**
  200. * Remove symbol & destroy it
  201. * @param {string} id
  202. * @return {boolean} `true` - symbol was found & successfully destroyed, `false` - otherwise
  203. */
  204. Sprite.prototype.remove = function remove (id) {
  205. var ref = this;
  206. var symbols = ref.symbols;
  207. var symbol = this.find(id);
  208. if (symbol) {
  209. symbols.splice(symbols.indexOf(symbol), 1);
  210. symbol.destroy();
  211. return true;
  212. }
  213. return false;
  214. };
  215. /**
  216. * @param {string} id
  217. * @return {SpriteSymbol|null}
  218. */
  219. Sprite.prototype.find = function find (id) {
  220. return this.symbols.filter(function (s) { return s.id === id; })[0] || null;
  221. };
  222. /**
  223. * @param {string} id
  224. * @return {boolean}
  225. */
  226. Sprite.prototype.has = function has (id) {
  227. return this.find(id) !== null;
  228. };
  229. /**
  230. * @return {string}
  231. */
  232. Sprite.prototype.stringify = function stringify () {
  233. var ref = this.config;
  234. var attrs = ref.attrs;
  235. var stringifiedSymbols = this.symbols.map(function (s) { return s.stringify(); }).join('');
  236. return wrapInSvgString(stringifiedSymbols, attrs);
  237. };
  238. /**
  239. * @return {string}
  240. */
  241. Sprite.prototype.toString = function toString () {
  242. return this.stringify();
  243. };
  244. Sprite.prototype.destroy = function destroy () {
  245. this.symbols.forEach(function (s) { return s.destroy(); });
  246. };
  247. var SpriteSymbol = function SpriteSymbol(ref) {
  248. var id = ref.id;
  249. var viewBox = ref.viewBox;
  250. var content = ref.content;
  251. this.id = id;
  252. this.viewBox = viewBox;
  253. this.content = content;
  254. };
  255. /**
  256. * @return {string}
  257. */
  258. SpriteSymbol.prototype.stringify = function stringify () {
  259. return this.content;
  260. };
  261. /**
  262. * @return {string}
  263. */
  264. SpriteSymbol.prototype.toString = function toString () {
  265. return this.stringify();
  266. };
  267. SpriteSymbol.prototype.destroy = function destroy () {
  268. var this$1 = this;
  269. ['id', 'viewBox', 'content'].forEach(function (prop) { return delete this$1[prop]; });
  270. };
  271. /**
  272. * @param {string} content
  273. * @return {Element}
  274. */
  275. var parse = function (content) {
  276. var hasImportNode = !!document.importNode;
  277. var doc = new DOMParser().parseFromString(content, 'image/svg+xml').documentElement;
  278. /**
  279. * Fix for browser which are throwing WrongDocumentError
  280. * if you insert an element which is not part of the document
  281. * @see http://stackoverflow.com/a/7986519/4624403
  282. */
  283. if (hasImportNode) {
  284. return document.importNode(doc, true);
  285. }
  286. return doc;
  287. };
  288. var BrowserSpriteSymbol = (function (SpriteSymbol$$1) {
  289. function BrowserSpriteSymbol () {
  290. SpriteSymbol$$1.apply(this, arguments);
  291. }
  292. if ( SpriteSymbol$$1 ) BrowserSpriteSymbol.__proto__ = SpriteSymbol$$1;
  293. BrowserSpriteSymbol.prototype = Object.create( SpriteSymbol$$1 && SpriteSymbol$$1.prototype );
  294. BrowserSpriteSymbol.prototype.constructor = BrowserSpriteSymbol;
  295. var prototypeAccessors = { isMounted: {} };
  296. prototypeAccessors.isMounted.get = function () {
  297. return !!this.node;
  298. };
  299. /**
  300. * @param {Element} node
  301. * @return {BrowserSpriteSymbol}
  302. */
  303. BrowserSpriteSymbol.createFromExistingNode = function createFromExistingNode (node) {
  304. return new BrowserSpriteSymbol({
  305. id: node.getAttribute('id'),
  306. viewBox: node.getAttribute('viewBox'),
  307. content: node.outerHTML
  308. });
  309. };
  310. BrowserSpriteSymbol.prototype.destroy = function destroy () {
  311. if (this.isMounted) {
  312. this.unmount();
  313. }
  314. SpriteSymbol$$1.prototype.destroy.call(this);
  315. };
  316. /**
  317. * @param {Element|string} target
  318. * @return {Element}
  319. */
  320. BrowserSpriteSymbol.prototype.mount = function mount (target) {
  321. if (this.isMounted) {
  322. return this.node;
  323. }
  324. var mountTarget = typeof target === 'string' ? document.querySelector(target) : target;
  325. var node = this.render();
  326. this.node = node;
  327. mountTarget.appendChild(node);
  328. return node;
  329. };
  330. /**
  331. * @return {Element}
  332. */
  333. BrowserSpriteSymbol.prototype.render = function render () {
  334. var content = this.stringify();
  335. return parse(wrapInSvgString(content)).childNodes[0];
  336. };
  337. BrowserSpriteSymbol.prototype.unmount = function unmount () {
  338. this.node.parentNode.removeChild(this.node);
  339. };
  340. Object.defineProperties( BrowserSpriteSymbol.prototype, prototypeAccessors );
  341. return BrowserSpriteSymbol;
  342. }(SpriteSymbol));
  343. var defaultConfig$1 = {
  344. /**
  345. * Should following options be automatically configured:
  346. * - `syncUrlsWithBaseTag`
  347. * - `locationChangeAngularEmitter`
  348. * - `moveGradientsOutsideSymbol`
  349. * @type {boolean}
  350. */
  351. autoConfigure: true,
  352. /**
  353. * Default mounting selector
  354. * @type {string}
  355. */
  356. mountTo: 'body',
  357. /**
  358. * Fix disappearing SVG elements when <base href> exists.
  359. * Executes when sprite mounted.
  360. * @see http://stackoverflow.com/a/18265336/796152
  361. * @see https://github.com/everdimension/angular-svg-base-fix
  362. * @see https://github.com/angular/angular.js/issues/8934#issuecomment-56568466
  363. * @type {boolean}
  364. */
  365. syncUrlsWithBaseTag: false,
  366. /**
  367. * Should sprite listen custom location change event
  368. * @type {boolean}
  369. */
  370. listenLocationChangeEvent: true,
  371. /**
  372. * Custom window event name which should be emitted to update sprite urls
  373. * @type {string}
  374. */
  375. locationChangeEvent: 'locationChange',
  376. /**
  377. * Emit location change event in Angular automatically
  378. * @type {boolean}
  379. */
  380. locationChangeAngularEmitter: false,
  381. /**
  382. * Selector to find symbols usages when updating sprite urls
  383. * @type {string}
  384. */
  385. usagesToUpdate: 'use[*|href]',
  386. /**
  387. * Fix Firefox bug when gradients and patterns don't work if they are within a symbol.
  388. * Executes when sprite is rendered, but not mounted.
  389. * @see https://bugzilla.mozilla.org/show_bug.cgi?id=306674
  390. * @see https://bugzilla.mozilla.org/show_bug.cgi?id=353575
  391. * @see https://bugzilla.mozilla.org/show_bug.cgi?id=1235364
  392. * @type {boolean}
  393. */
  394. moveGradientsOutsideSymbol: false
  395. };
  396. /**
  397. * @param {*} arrayLike
  398. * @return {Array}
  399. */
  400. var arrayFrom = function (arrayLike) {
  401. return Array.prototype.slice.call(arrayLike, 0);
  402. };
  403. var browser = {
  404. isChrome: function () { return /chrome/i.test(navigator.userAgent); },
  405. isFirefox: function () { return /firefox/i.test(navigator.userAgent); },
  406. // https://msdn.microsoft.com/en-us/library/ms537503(v=vs.85).aspx
  407. isIE: function () { return /msie/i.test(navigator.userAgent) || /trident/i.test(navigator.userAgent); },
  408. isEdge: function () { return /edge/i.test(navigator.userAgent); }
  409. };
  410. /**
  411. * @param {string} name
  412. * @param {*} data
  413. */
  414. var dispatchEvent = function (name, data) {
  415. var event = document.createEvent('CustomEvent');
  416. event.initCustomEvent(name, false, false, data);
  417. window.dispatchEvent(event);
  418. };
  419. /**
  420. * IE doesn't evaluate <style> tags in SVGs that are dynamically added to the page.
  421. * This trick will trigger IE to read and use any existing SVG <style> tags.
  422. * @see https://github.com/iconic/SVGInjector/issues/23
  423. * @see https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/10898469/
  424. *
  425. * @param {Element} node DOM Element to search <style> tags in
  426. * @return {Array<HTMLStyleElement>}
  427. */
  428. var evalStylesIEWorkaround = function (node) {
  429. var updatedNodes = [];
  430. arrayFrom(node.querySelectorAll('style'))
  431. .forEach(function (style) {
  432. style.textContent += '';
  433. updatedNodes.push(style);
  434. });
  435. return updatedNodes;
  436. };
  437. /**
  438. * @param {string} [url] If not provided - current URL will be used
  439. * @return {string}
  440. */
  441. var getUrlWithoutFragment = function (url) {
  442. return (url || window.location.href).split('#')[0];
  443. };
  444. /* global angular */
  445. /**
  446. * @param {string} eventName
  447. */
  448. var locationChangeAngularEmitter = function (eventName) {
  449. angular.module('ng').run(['$rootScope', function ($rootScope) {
  450. $rootScope.$on('$locationChangeSuccess', function (e, newUrl, oldUrl) {
  451. dispatchEvent(eventName, { oldUrl: oldUrl, newUrl: newUrl });
  452. });
  453. }]);
  454. };
  455. var defaultSelector = 'linearGradient, radialGradient, pattern, mask, clipPath';
  456. /**
  457. * @param {Element} svg
  458. * @param {string} [selector]
  459. * @return {Element}
  460. */
  461. var moveGradientsOutsideSymbol = function (svg, selector) {
  462. if ( selector === void 0 ) selector = defaultSelector;
  463. arrayFrom(svg.querySelectorAll('symbol')).forEach(function (symbol) {
  464. arrayFrom(symbol.querySelectorAll(selector)).forEach(function (node) {
  465. symbol.parentNode.insertBefore(node, symbol);
  466. });
  467. });
  468. return svg;
  469. };
  470. /**
  471. * @param {NodeList} nodes
  472. * @param {Function} [matcher]
  473. * @return {Attr[]}
  474. */
  475. function selectAttributes(nodes, matcher) {
  476. var attrs = arrayFrom(nodes).reduce(function (acc, node) {
  477. if (!node.attributes) {
  478. return acc;
  479. }
  480. var arrayfied = arrayFrom(node.attributes);
  481. var matched = matcher ? arrayfied.filter(matcher) : arrayfied;
  482. return acc.concat(matched);
  483. }, []);
  484. return attrs;
  485. }
  486. /**
  487. * @param {NodeList|Node} nodes
  488. * @param {boolean} [clone=true]
  489. * @return {string}
  490. */
  491. var xLinkNS = namespaces_1.xlink.uri;
  492. var xLinkAttrName = 'xlink:href';
  493. // eslint-disable-next-line no-useless-escape
  494. var specialUrlCharsPattern = /[{}|\\\^\[\]`"<>]/g;
  495. function encoder(url) {
  496. return url.replace(specialUrlCharsPattern, function (match) {
  497. return ("%" + (match[0].charCodeAt(0).toString(16).toUpperCase()));
  498. });
  499. }
  500. function escapeRegExp(str) {
  501. return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
  502. }
  503. /**
  504. * @param {NodeList} nodes
  505. * @param {string} startsWith
  506. * @param {string} replaceWith
  507. * @return {NodeList}
  508. */
  509. function updateReferences(nodes, startsWith, replaceWith) {
  510. arrayFrom(nodes).forEach(function (node) {
  511. var href = node.getAttribute(xLinkAttrName);
  512. if (href && href.indexOf(startsWith) === 0) {
  513. var newUrl = href.replace(startsWith, replaceWith);
  514. node.setAttributeNS(xLinkNS, xLinkAttrName, newUrl);
  515. }
  516. });
  517. return nodes;
  518. }
  519. /**
  520. * List of SVG attributes to update url() target in them
  521. */
  522. var attList = [
  523. 'clipPath',
  524. 'colorProfile',
  525. 'src',
  526. 'cursor',
  527. 'fill',
  528. 'filter',
  529. 'marker',
  530. 'markerStart',
  531. 'markerMid',
  532. 'markerEnd',
  533. 'mask',
  534. 'stroke',
  535. 'style'
  536. ];
  537. var attSelector = attList.map(function (attr) { return ("[" + attr + "]"); }).join(',');
  538. /**
  539. * Update URLs in svg image (like `fill="url(...)"`) and update referencing elements
  540. * @param {Element} svg
  541. * @param {NodeList} references
  542. * @param {string|RegExp} startsWith
  543. * @param {string} replaceWith
  544. * @return {void}
  545. *
  546. * @example
  547. * const sprite = document.querySelector('svg.sprite');
  548. * const usages = document.querySelectorAll('use');
  549. * updateUrls(sprite, usages, '#', 'prefix#');
  550. */
  551. var updateUrls = function (svg, references, startsWith, replaceWith) {
  552. var startsWithEncoded = encoder(startsWith);
  553. var replaceWithEncoded = encoder(replaceWith);
  554. var nodes = svg.querySelectorAll(attSelector);
  555. var attrs = selectAttributes(nodes, function (ref) {
  556. var localName = ref.localName;
  557. var value = ref.value;
  558. return attList.indexOf(localName) !== -1 && value.indexOf(("url(" + startsWithEncoded)) !== -1;
  559. });
  560. attrs.forEach(function (attr) { return attr.value = attr.value.replace(new RegExp(escapeRegExp(startsWithEncoded), 'g'), replaceWithEncoded); });
  561. updateReferences(references, startsWithEncoded, replaceWithEncoded);
  562. };
  563. /**
  564. * Internal emitter events
  565. * @enum
  566. * @private
  567. */
  568. var Events = {
  569. MOUNT: 'mount',
  570. SYMBOL_MOUNT: 'symbol_mount'
  571. };
  572. var BrowserSprite = (function (Sprite$$1) {
  573. function BrowserSprite(cfg) {
  574. var this$1 = this;
  575. if ( cfg === void 0 ) cfg = {};
  576. Sprite$$1.call(this, deepmerge(defaultConfig$1, cfg));
  577. var emitter = mitt();
  578. this._emitter = emitter;
  579. this.node = null;
  580. var ref = this;
  581. var config = ref.config;
  582. if (config.autoConfigure) {
  583. this._autoConfigure(cfg);
  584. }
  585. if (config.syncUrlsWithBaseTag) {
  586. var baseUrl = document.getElementsByTagName('base')[0].getAttribute('href');
  587. emitter.on(Events.MOUNT, function () { return this$1.updateUrls('#', baseUrl); });
  588. }
  589. var handleLocationChange = this._handleLocationChange.bind(this);
  590. this._handleLocationChange = handleLocationChange;
  591. // Provide way to update sprite urls externally via dispatching custom window event
  592. if (config.listenLocationChangeEvent) {
  593. window.addEventListener(config.locationChangeEvent, handleLocationChange);
  594. }
  595. // Emit location change event in Angular automatically
  596. if (config.locationChangeAngularEmitter) {
  597. locationChangeAngularEmitter(config.locationChangeEvent);
  598. }
  599. // After sprite mounted
  600. emitter.on(Events.MOUNT, function (spriteNode) {
  601. if (config.moveGradientsOutsideSymbol) {
  602. moveGradientsOutsideSymbol(spriteNode);
  603. }
  604. });
  605. // After symbol mounted into sprite
  606. emitter.on(Events.SYMBOL_MOUNT, function (symbolNode) {
  607. if (config.moveGradientsOutsideSymbol) {
  608. moveGradientsOutsideSymbol(symbolNode.parentNode);
  609. }
  610. if (browser.isIE() || browser.isEdge()) {
  611. evalStylesIEWorkaround(symbolNode);
  612. }
  613. });
  614. }
  615. if ( Sprite$$1 ) BrowserSprite.__proto__ = Sprite$$1;
  616. BrowserSprite.prototype = Object.create( Sprite$$1 && Sprite$$1.prototype );
  617. BrowserSprite.prototype.constructor = BrowserSprite;
  618. var prototypeAccessors = { isMounted: {} };
  619. /**
  620. * @return {boolean}
  621. */
  622. prototypeAccessors.isMounted.get = function () {
  623. return !!this.node;
  624. };
  625. /**
  626. * Automatically configure following options
  627. * - `syncUrlsWithBaseTag`
  628. * - `locationChangeAngularEmitter`
  629. * - `moveGradientsOutsideSymbol`
  630. * @param {Object} cfg
  631. * @private
  632. */
  633. BrowserSprite.prototype._autoConfigure = function _autoConfigure (cfg) {
  634. var ref = this;
  635. var config = ref.config;
  636. if (typeof cfg.syncUrlsWithBaseTag === 'undefined') {
  637. config.syncUrlsWithBaseTag = typeof document.getElementsByTagName('base')[0] !== 'undefined';
  638. }
  639. if (typeof cfg.locationChangeAngularEmitter === 'undefined') {
  640. config.locationChangeAngularEmitter = typeof window.angular !== 'undefined';
  641. }
  642. if (typeof cfg.moveGradientsOutsideSymbol === 'undefined') {
  643. config.moveGradientsOutsideSymbol = browser.isFirefox();
  644. }
  645. };
  646. /**
  647. * @param {Event} event
  648. * @param {Object} event.detail
  649. * @param {string} event.detail.oldUrl
  650. * @param {string} event.detail.newUrl
  651. * @private
  652. */
  653. BrowserSprite.prototype._handleLocationChange = function _handleLocationChange (event) {
  654. var ref = event.detail;
  655. var oldUrl = ref.oldUrl;
  656. var newUrl = ref.newUrl;
  657. this.updateUrls(oldUrl, newUrl);
  658. };
  659. /**
  660. * Add new symbol. If symbol with the same id exists it will be replaced.
  661. * If sprite already mounted - `symbol.mount(sprite.node)` will be called.
  662. * @fires Events#SYMBOL_MOUNT
  663. * @param {BrowserSpriteSymbol} symbol
  664. * @return {boolean} `true` - symbol was added, `false` - replaced
  665. */
  666. BrowserSprite.prototype.add = function add (symbol) {
  667. var sprite = this;
  668. var isNewSymbol = Sprite$$1.prototype.add.call(this, symbol);
  669. if (this.isMounted && isNewSymbol) {
  670. symbol.mount(sprite.node);
  671. this._emitter.emit(Events.SYMBOL_MOUNT, symbol.node);
  672. }
  673. return isNewSymbol;
  674. };
  675. /**
  676. * Attach to existing DOM node
  677. * @param {string|Element} target
  678. * @return {Element|null} attached DOM Element. null if node to attach not found.
  679. */
  680. BrowserSprite.prototype.attach = function attach (target) {
  681. var this$1 = this;
  682. var sprite = this;
  683. if (sprite.isMounted) {
  684. return sprite.node;
  685. }
  686. /** @type Element */
  687. var node = typeof target === 'string' ? document.querySelector(target) : target;
  688. sprite.node = node;
  689. // Already added symbols needs to be mounted
  690. this.symbols.forEach(function (symbol) {
  691. symbol.mount(sprite.node);
  692. this$1._emitter.emit(Events.SYMBOL_MOUNT, symbol.node);
  693. });
  694. // Create symbols from existing DOM nodes, add and mount them
  695. arrayFrom(node.querySelectorAll('symbol'))
  696. .forEach(function (symbolNode) {
  697. var symbol = BrowserSpriteSymbol.createFromExistingNode(symbolNode);
  698. symbol.node = symbolNode; // hack to prevent symbol mounting to sprite when adding
  699. sprite.add(symbol);
  700. });
  701. this._emitter.emit(Events.MOUNT, node);
  702. return node;
  703. };
  704. BrowserSprite.prototype.destroy = function destroy () {
  705. var ref = this;
  706. var config = ref.config;
  707. var symbols = ref.symbols;
  708. var _emitter = ref._emitter;
  709. symbols.forEach(function (s) { return s.destroy(); });
  710. _emitter.off('*');
  711. window.removeEventListener(config.locationChangeEvent, this._handleLocationChange);
  712. if (this.isMounted) {
  713. this.unmount();
  714. }
  715. };
  716. /**
  717. * @fires Events#MOUNT
  718. * @param {string|Element} [target]
  719. * @param {boolean} [prepend=false]
  720. * @return {Element|null} rendered sprite node. null if mount node not found.
  721. */
  722. BrowserSprite.prototype.mount = function mount (target, prepend) {
  723. if ( target === void 0 ) target = this.config.mountTo;
  724. if ( prepend === void 0 ) prepend = false;
  725. var sprite = this;
  726. if (sprite.isMounted) {
  727. return sprite.node;
  728. }
  729. var mountNode = typeof target === 'string' ? document.querySelector(target) : target;
  730. var node = sprite.render();
  731. this.node = node;
  732. if (prepend && mountNode.childNodes[0]) {
  733. mountNode.insertBefore(node, mountNode.childNodes[0]);
  734. } else {
  735. mountNode.appendChild(node);
  736. }
  737. this._emitter.emit(Events.MOUNT, node);
  738. return node;
  739. };
  740. /**
  741. * @return {Element}
  742. */
  743. BrowserSprite.prototype.render = function render () {
  744. return parse(this.stringify());
  745. };
  746. /**
  747. * Detach sprite from the DOM
  748. */
  749. BrowserSprite.prototype.unmount = function unmount () {
  750. this.node.parentNode.removeChild(this.node);
  751. };
  752. /**
  753. * Update URLs in sprite and usage elements
  754. * @param {string} oldUrl
  755. * @param {string} newUrl
  756. * @return {boolean} `true` - URLs was updated, `false` - sprite is not mounted
  757. */
  758. BrowserSprite.prototype.updateUrls = function updateUrls$1 (oldUrl, newUrl) {
  759. if (!this.isMounted) {
  760. return false;
  761. }
  762. var usages = document.querySelectorAll(this.config.usagesToUpdate);
  763. updateUrls(
  764. this.node,
  765. usages,
  766. ((getUrlWithoutFragment(oldUrl)) + "#"),
  767. ((getUrlWithoutFragment(newUrl)) + "#")
  768. );
  769. return true;
  770. };
  771. Object.defineProperties( BrowserSprite.prototype, prototypeAccessors );
  772. return BrowserSprite;
  773. }(Sprite));
  774. return BrowserSprite;
  775. })));