{"version":3,"file":"svg-injector.umd.production.js","sources":["../src/clone-svg.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: SVGElement) =>\n  sourceSvg.cloneNode(true) as SVGElement\n\nexport default cloneSvg\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 (cacheValue instanceof SVGElement) {\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<ElementType>,\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 <style> tags in SVGs that are\n    // dynamically added to the page. This trick will trigger IE to read and use\n    // any existing SVG <style> tags.\n    //\n    // Reference: https://github.com/iconic/SVGInjector/issues/23.\n    const styleTags = svg.querySelectorAll('style')\n    Array.prototype.forEach.call(styleTags, (styleTag: HTMLStyleElement) => {\n      styleTag.textContent += ''\n    })\n\n    svg.setAttribute('xmlns', svgNamespace)\n    svg.setAttribute('xmlns:xlink', xlinkNamespace)\n\n    beforeEach(svg)\n\n    // Replace the image with the svg.\n    /* istanbul ignore else */\n    if (el.parentNode) {\n      el.parentNode.replaceChild(svg, el)\n    }\n\n    // Now that we no longer need it, drop references to the original element so\n    // it can be GC'd.\n    // TODO: Extract\n    injectedElements.splice(injectedElements.indexOf(el), 1)\n    ;(el as ElementType) = null\n\n    callback(null, svg)\n  })\n}\n\nexport default injectElement\n","import cloneSvg from './clone-svg'\nimport isLocal from './is-local'\nimport { processRequestQueue, queueRequest } from './request-queue'\nimport svgCache from './svg-cache'\nimport { Errback } from './types'\n\nconst loadSvg = (url: string, callback: Errback) => {\n  if (svgCache.has(url)) {\n    const cacheValue = svgCache.get(url)\n\n    if (cacheValue instanceof SVGElement) {\n      callback(null, cloneSvg(cacheValue))\n      return\n    }\n\n    if (cacheValue instanceof Error) {\n      callback(cacheValue)\n      return\n    }\n\n    queueRequest(url, callback)\n\n    return\n  }\n\n  // Seed the cache to indicate we are loading this URL.\n  svgCache.set(url, undefined)\n  queueRequest(url, callback)\n\n  const httpRequest = new XMLHttpRequest()\n\n  httpRequest.onreadystatechange = () => {\n    try {\n      if (httpRequest.readyState === 4) {\n        if (httpRequest.status === 404 || httpRequest.responseXML === null) {\n          throw new Error(\n            isLocal()\n              ? 'Note: SVG injection ajax calls do not work locally without adjusting security setting in your browser. Or consider using a local webserver.'\n              : 'Unable to load SVG file: ' + url\n          )\n        }\n\n        if (\n          httpRequest.status === 200 ||\n          (isLocal() && httpRequest.status === 0)\n        ) {\n          /* istanbul ignore else */\n          if (httpRequest.responseXML instanceof Document) {\n            /* istanbul ignore else */\n            if (httpRequest.responseXML.documentElement) {\n              svgCache.set(url, httpRequest.responseXML.documentElement)\n            }\n          }\n          processRequestQueue(url)\n        } else {\n          throw new Error(\n            'There was a problem injecting the SVG: ' +\n              httpRequest.status +\n              ' ' +\n              httpRequest.statusText\n          )\n        }\n      }\n    } catch (error) {\n      svgCache.set(url, error)\n      processRequestQueue(url)\n    }\n  }\n\n  httpRequest.open('GET', url)\n\n  // Treat and parse the response as XML, even if the server sends us a\n  // different mimetype.\n  /* istanbul ignore else */\n  if (httpRequest.overrideMimeType) {\n    httpRequest.overrideMimeType('text/xml')\n  }\n\n  httpRequest.send()\n}\n\nexport default loadSvg\n","import injectElement from './inject-element'\nimport { AfterAll, BeforeEach, Errback, EvalScripts } from './types'\n\ntype Elements = HTMLCollectionOf<Element> | NodeListOf<Element> | Element | null\n\ninterface OptionalArgs {\n  afterAll?: AfterAll\n  afterEach?: Errback\n  beforeEach?: BeforeEach\n  evalScripts?: EvalScripts\n  renumerateIRIElements?: boolean\n}\n\nconst SVGInjector = (\n  elements: Elements,\n  {\n    afterAll = () => undefined,\n    afterEach = () => undefined,\n    beforeEach = () => undefined,\n    evalScripts = 'never',\n    renumerateIRIElements = true,\n  }: OptionalArgs = {}\n) => {\n  if (elements && 'length' in elements) {\n    let elementsLoaded = 0\n    for (let i = 0, j = elements.length; i < j; i++) {\n      injectElement(\n        elements[i],\n        evalScripts,\n        renumerateIRIElements,\n        beforeEach,\n        (error, svg) => {\n          afterEach(error, svg)\n          if (\n            elements &&\n            'length' in elements &&\n            elements.length === ++elementsLoaded\n          ) {\n            afterAll(elementsLoaded)\n          }\n        }\n      )\n    }\n  } else if (elements) {\n    injectElement(\n      elements,\n      evalScripts,\n      renumerateIRIElements,\n      beforeEach,\n      (error, svg) => {\n        afterEach(error, svg)\n        afterAll(1)\n        elements = null\n      }\n    )\n  } else {\n    afterAll(0)\n  }\n}\n\nexport default SVGInjector\n"],"names":["requestQueue","url","i","length","len","idCounter","cacheValue","queueRequest","callback","Error","el","setAttribute","imgTitle","imgWidth","svg","getAttribute","mergedClasses","imgStyle","forEach","imgData","value","name","iriElementsAndProperties_1","key","elementsLen","elements_1","Array","a","_e","elementsLoaded_1"],"mappings":"mPAAA,2GCOEA,yJA4B4BC,GAAKC,mHAVDC,SAAYC,YCzB1CC,EAAY,sMCMwB,mHAgCT,sBAwBG,wICnDJJ,oDAS1BK,0BAEMA,QAORC,IAAoBC,wEAkBG,kBAGF,iBAAG,wRA4BZ,IAAIC,yVDcdC,iDAaEC,mDAOFC,qFAuBoBC,SAGVH,mFASRI,EAAIC,uEAGQ,oEAMAC,2DAeEC,0IAkBVC,aAAaC,yBAGCC,wBACJC,OAAeD,yBA+B7BE,2CAGY,wBACV,kBAGA,wMA2CkCJ,SAAQ,SAACK,KAEpCD,0BAIKE,cAEFC,kBAYMC,+cAdJF,GADhBC,uCAC+CE,0hBAtR9B,yZEgDOC,8GAMxBC"}