{"version":3,"file":"svg-injector.umd.production.js","sources":["../src/clone-svg.ts","../src/types.ts","../src/is-local.ts","../src/svg-cache.ts","../src/request-queue.ts","../src/unique-id.ts","../src/inject-element.ts","../src/load-svg.ts","../src/svg-injector.ts"],"sourcesContent":["const cloneSvg = (sourceSvg: HTMLElement | SVGSVGElement) =>\n sourceSvg.cloneNode(true) as Element\n\nexport default cloneSvg\n","/* istanbul ignore file */\n\nexport type AfterAll = (elementsLoaded: number) => void\n\nexport type BeforeEach = (svg: Element) => void\n\nexport type Errback = (error: Error | null, svg?: Element) => void\n\nexport enum EvalScripts {\n Always = 'always',\n Once = 'once',\n Never = 'never'\n}\n","const isLocal = () => window.location.protocol === 'file:'\n\nexport default isLocal\n","const svgCache = new Map<\n string,\n HTMLElement | SVGSVGElement | Error | undefined\n>()\n\nexport default svgCache\n","import cloneSvg from './clone-svg'\nimport svgCache from './svg-cache'\nimport { Errback } from './types'\n\nlet requestQueue: { [key: string]: Errback[] } = {}\n\nexport const clear = () => {\n requestQueue = {}\n}\n\nexport const queueRequest = (url: string, callback: Errback) => {\n requestQueue[url] = requestQueue[url] || []\n requestQueue[url].push(callback)\n}\n\nexport const processRequestQueue = (url: string) => {\n for (let i = 0, len = requestQueue[url].length; i < len; i++) {\n // Make these calls async so we avoid blocking the page/renderer.\n setTimeout(() => {\n /* istanbul ignore else */\n if (Array.isArray(requestQueue[url])) {\n const cacheValue = svgCache.get(url)\n const callback = requestQueue[url][i]\n\n /* istanbul ignore else */\n if (\n cacheValue instanceof SVGSVGElement ||\n cacheValue instanceof HTMLElement\n ) {\n callback(null, cloneSvg(cacheValue))\n }\n\n /* istanbul ignore else */\n if (cacheValue instanceof Error) {\n callback(cacheValue)\n }\n\n /* istanbul ignore else */\n if (i === requestQueue[url].length - 1) {\n delete requestQueue[url]\n }\n }\n }, 0)\n }\n}\n","let idCounter = 0\nconst uniqueId = () => ++idCounter\nexport default uniqueId\n","import loadSvg from './load-svg'\nimport { BeforeEach, Errback, EvalScripts } from './types'\nimport uniqueId from './unique-id'\n\ntype ElementType = Element | HTMLElement | null\n\nconst injectedElements: ElementType[] = []\nconst ranScripts: { [key: string]: boolean } = {}\nconst svgNamespace = 'http://www.w3.org/2000/svg'\nconst xlinkNamespace = 'http://www.w3.org/1999/xlink'\n\nconst injectElement = (\n el: NonNullable,\n evalScripts: EvalScripts,\n renumerateIRIElements: boolean,\n beforeEach: BeforeEach,\n callback: Errback\n) => {\n const imgUrl = el.getAttribute('data-src') || el.getAttribute('src')\n\n /* istanbul ignore else */\n if (!imgUrl || !/\\.svg/i.test(imgUrl)) {\n callback(\n new Error(\n 'Attempted to inject a file with a non-svg extension: ' + imgUrl\n )\n )\n return\n }\n\n // Make sure we aren't already in the process of injecting this element to\n // avoid a race condition if multiple injections for the same element are run.\n // :NOTE: Using indexOf() only _after_ we check for SVG support and bail, so\n // no need for IE8 indexOf() polyfill.\n /* istanbul ignore else */\n if (injectedElements.indexOf(el) !== -1) {\n // TODO: Extract.\n injectedElements.splice(injectedElements.indexOf(el), 1)\n ;(el as ElementType) = null\n return\n }\n\n // Remember the request to inject this element, in case other injection calls\n // are also trying to replace this element before we finish.\n injectedElements.push(el)\n\n // Try to avoid loading the orginal image src if possible.\n el.setAttribute('src', '')\n\n loadSvg(imgUrl, (error, svg) => {\n /* istanbul ignore else */\n if (!svg) {\n // TODO: Extract.\n injectedElements.splice(injectedElements.indexOf(el), 1)\n ;(el as ElementType) = null\n callback(error)\n return\n }\n\n const imgId = el.getAttribute('id')\n /* istanbul ignore else */\n if (imgId) {\n svg.setAttribute('id', imgId)\n }\n\n const imgTitle = el.getAttribute('title')\n /* istanbul ignore else */\n if (imgTitle) {\n svg.setAttribute('title', imgTitle)\n }\n\n const imgWidth = el.getAttribute('width')\n /* istanbul ignore else */\n if (imgWidth) {\n svg.setAttribute('width', imgWidth)\n }\n\n const imgHeight = el.getAttribute('height')\n /* istanbul ignore else */\n if (imgHeight) {\n svg.setAttribute('height', imgHeight)\n }\n\n const mergedClasses = Array.from(\n new Set([\n ...(svg.getAttribute('class') || '').split(' '),\n 'injected-svg',\n ...(el.getAttribute('class') || '').split(' ')\n ])\n )\n .join(' ')\n .trim()\n svg.setAttribute('class', mergedClasses)\n\n const imgStyle = el.getAttribute('style')\n /* istanbul ignore else */\n if (imgStyle) {\n svg.setAttribute('style', imgStyle)\n }\n\n svg.setAttribute('data-src', imgUrl)\n\n // Copy all the data elements to the svg.\n const imgData = [].filter.call(el.attributes, (at: Attr) => {\n return /^data-\\w[\\w-]*$/.test(at.name)\n })\n\n Array.prototype.forEach.call(imgData, (dataAttr: Attr) => {\n /* istanbul ignore else */\n if (dataAttr.name && dataAttr.value) {\n svg.setAttribute(dataAttr.name, dataAttr.value)\n }\n })\n\n /* istanbul ignore else */\n if (renumerateIRIElements) {\n // Make sure any internally referenced clipPath ids and their clip-path\n // references are unique.\n //\n // This addresses the issue of having multiple instances of the same SVG\n // on a page and only the first clipPath id is referenced.\n //\n // Browsers often shortcut the SVG Spec and don't use clipPaths contained\n // in parent elements that are hidden, so if you hide the first SVG\n // instance on the page, then all other instances lose their clipping.\n // Reference: https://bugzilla.mozilla.org/show_bug.cgi?id=376027\n\n // Handle all defs elements that have iri capable attributes as defined by\n // w3c: http://www.w3.org/TR/SVG/linking.html#processingIRI. Mapping IRI\n // addressable elements to the properties that can reference them.\n const iriElementsAndProperties: { [key: string]: string[] } = {\n clipPath: ['clip-path'],\n 'color-profile': ['color-profile'],\n cursor: ['cursor'],\n filter: ['filter'],\n linearGradient: ['fill', 'stroke'],\n marker: ['marker', 'marker-start', 'marker-mid', 'marker-end'],\n mask: ['mask'],\n path: [],\n pattern: ['fill', 'stroke'],\n radialGradient: ['fill', 'stroke']\n }\n\n let element\n let elements\n let properties\n let currentId: string\n let newId: string\n\n Object.keys(iriElementsAndProperties).forEach(key => {\n element = key\n properties = iriElementsAndProperties[key]\n\n elements = svg.querySelectorAll(element + '[id]')\n for (let a = 0, elementsLen = elements.length; a < elementsLen; a++) {\n currentId = elements[a].id\n newId = currentId + '-' + uniqueId()\n\n // All of the properties that can reference this element type.\n let referencingElements\n Array.prototype.forEach.call(properties, (property: string) => {\n // :NOTE: using a substring match attr selector here to deal with IE\n // \"adding extra quotes in url() attrs\".\n referencingElements = svg.querySelectorAll(\n '[' + property + '*=\"' + currentId + '\"]'\n )\n for (\n let b = 0, referencingElementLen = referencingElements.length;\n b < referencingElementLen;\n b++\n ) {\n const attrValue: string | null = referencingElements[\n b\n ].getAttribute(property)\n if (\n attrValue &&\n !attrValue.match(new RegExp('url\\\\(#' + currentId + '\\\\)'))\n ) {\n continue\n }\n referencingElements[b].setAttribute(\n property,\n 'url(#' + newId + ')'\n )\n }\n })\n\n const allLinks = svg.querySelectorAll('[*|href]')\n const links = []\n for (let c = 0, allLinksLen = allLinks.length; c < allLinksLen; c++) {\n const href = allLinks[c].getAttributeNS(xlinkNamespace, 'href')\n /* istanbul ignore else */\n if (href && href.toString() === '#' + elements[a].id) {\n links.push(allLinks[c])\n }\n }\n for (let d = 0, linksLen = links.length; d < linksLen; d++) {\n links[d].setAttributeNS(xlinkNamespace, 'href', '#' + newId)\n }\n\n elements[a].id = newId\n }\n })\n }\n\n // Remove any unwanted/invalid namespaces that might have been added by SVG\n // editing tools.\n svg.removeAttribute('xmlns:a')\n\n // Post page load injected SVGs don't automatically have their script\n // elements run, so we'll need to make that happen, if requested.\n\n // Find then prune the scripts.\n const scripts = svg.querySelectorAll('script')\n const scriptsToEval: string[] = []\n let script\n let scriptType\n\n for (let i = 0, scriptsLen = scripts.length; i < scriptsLen; i++) {\n scriptType = scripts[i].getAttribute('type')\n\n // Only process javascript types. SVG defaults to 'application/ecmascript'\n // for unset types.\n /* istanbul ignore else */\n if (\n !scriptType ||\n scriptType === 'application/ecmascript' ||\n scriptType === 'application/javascript' ||\n scriptType === 'text/javascript'\n ) {\n // innerText for IE, textContent for other browsers.\n script = scripts[i].innerText || scripts[i].textContent\n\n // Stash.\n /* istanbul ignore else */\n if (script) {\n scriptsToEval.push(script)\n }\n\n // Tidy up and remove the script element since we don't need it anymore.\n svg.removeChild(scripts[i])\n }\n }\n\n // Run/Eval the scripts if needed.\n /* istanbul ignore else */\n if (\n scriptsToEval.length > 0 &&\n (evalScripts === 'always' ||\n (evalScripts === 'once' && !ranScripts[imgUrl]))\n ) {\n for (\n let l = 0, scriptsToEvalLen = scriptsToEval.length;\n l < scriptsToEvalLen;\n l++\n ) {\n // :NOTE: Yup, this is a form of eval, but it is being used to eval code\n // the caller has explictely asked to be loaded, and the code is in a\n // caller defined SVG file... not raw user input.\n //\n // Also, the code is evaluated in a closure and not in the global scope.\n // If you need to put something in global scope, use 'window'.\n new Function(scriptsToEval[l])(window)\n }\n\n // Remember we already ran scripts for this svg.\n ranScripts[imgUrl] = true\n }\n\n // :WORKAROUND: IE doesn't evaluate