| import merge from 'deepmerge'; | 
| import Emitter from 'mitt'; | 
| import Sprite from './sprite'; | 
| import BrowserSymbol from './browser-symbol'; | 
| import defaultConfig from './browser-sprite.config'; | 
| import { | 
|   arrayFrom, | 
|   parse, | 
|   moveGradientsOutsideSymbol, | 
|   browserDetector as browser, | 
|   getUrlWithoutFragment, | 
|   updateUrls, | 
|   locationChangeAngularEmitter, | 
|   evalStylesIEWorkaround | 
| } from './utils'; | 
|   | 
| /** | 
|  * Internal emitter events | 
|  * @enum | 
|  * @private | 
|  */ | 
| const Events = { | 
|   MOUNT: 'mount', | 
|   SYMBOL_MOUNT: 'symbol_mount' | 
| }; | 
|   | 
| export default class BrowserSprite extends Sprite { | 
|   constructor(cfg = {}) { | 
|     super(merge(defaultConfig, cfg)); | 
|   | 
|     const emitter = Emitter(); | 
|     this._emitter = emitter; | 
|     this.node = null; | 
|   | 
|     const { config } = this; | 
|   | 
|     if (config.autoConfigure) { | 
|       this._autoConfigure(cfg); | 
|     } | 
|   | 
|     if (config.syncUrlsWithBaseTag) { | 
|       const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href'); | 
|       emitter.on(Events.MOUNT, () => this.updateUrls('#', baseUrl)); | 
|     } | 
|   | 
|     const handleLocationChange = this._handleLocationChange.bind(this); | 
|     this._handleLocationChange = handleLocationChange; | 
|   | 
|     // Provide way to update sprite urls externally via dispatching custom window event | 
|     if (config.listenLocationChangeEvent) { | 
|       window.addEventListener(config.locationChangeEvent, handleLocationChange); | 
|     } | 
|   | 
|     // Emit location change event in Angular automatically | 
|     if (config.locationChangeAngularEmitter) { | 
|       locationChangeAngularEmitter(config.locationChangeEvent); | 
|     } | 
|   | 
|     // After sprite mounted | 
|     emitter.on(Events.MOUNT, (spriteNode) => { | 
|       if (config.moveGradientsOutsideSymbol) { | 
|         moveGradientsOutsideSymbol(spriteNode); | 
|       } | 
|     }); | 
|   | 
|     // After symbol mounted into sprite | 
|     emitter.on(Events.SYMBOL_MOUNT, (symbolNode) => { | 
|       if (config.moveGradientsOutsideSymbol) { | 
|         moveGradientsOutsideSymbol(symbolNode.parentNode); | 
|       } | 
|   | 
|       if (browser.isIE() || browser.isEdge()) { | 
|         evalStylesIEWorkaround(symbolNode); | 
|       } | 
|     }); | 
|   } | 
|   | 
|   /** | 
|    * @return {boolean} | 
|    */ | 
|   get isMounted() { | 
|     return !!this.node; | 
|   } | 
|   | 
|   /** | 
|    * Automatically configure following options | 
|    * - `syncUrlsWithBaseTag` | 
|    * - `locationChangeAngularEmitter` | 
|    * - `moveGradientsOutsideSymbol` | 
|    * @param {Object} cfg | 
|    * @private | 
|    */ | 
|   _autoConfigure(cfg) { | 
|     const { config } = this; | 
|   | 
|     if (typeof cfg.syncUrlsWithBaseTag === 'undefined') { | 
|       config.syncUrlsWithBaseTag = typeof document.getElementsByTagName('base')[0] !== 'undefined'; | 
|     } | 
|   | 
|     if (typeof cfg.locationChangeAngularEmitter === 'undefined') { | 
|         config.locationChangeAngularEmitter = typeof window.angular !== 'undefined'; | 
|     } | 
|   | 
|     if (typeof cfg.moveGradientsOutsideSymbol === 'undefined') { | 
|       config.moveGradientsOutsideSymbol = browser.isFirefox(); | 
|     } | 
|   } | 
|   | 
|   /** | 
|    * @param {Event} event | 
|    * @param {Object} event.detail | 
|    * @param {string} event.detail.oldUrl | 
|    * @param {string} event.detail.newUrl | 
|    * @private | 
|    */ | 
|   _handleLocationChange(event) { | 
|     const { oldUrl, newUrl } = event.detail; | 
|     this.updateUrls(oldUrl, newUrl); | 
|   } | 
|   | 
|   /** | 
|    * Add new symbol. If symbol with the same id exists it will be replaced. | 
|    * If sprite already mounted - `symbol.mount(sprite.node)` will be called. | 
|    * @fires Events#SYMBOL_MOUNT | 
|    * @param {BrowserSpriteSymbol} symbol | 
|    * @return {boolean} `true` - symbol was added, `false` - replaced | 
|    */ | 
|   add(symbol) { | 
|     const sprite = this; | 
|     const isNewSymbol = super.add(symbol); | 
|   | 
|     if (this.isMounted && isNewSymbol) { | 
|       symbol.mount(sprite.node); | 
|       this._emitter.emit(Events.SYMBOL_MOUNT, symbol.node); | 
|     } | 
|   | 
|     return isNewSymbol; | 
|   } | 
|   | 
|   /** | 
|    * Attach to existing DOM node | 
|    * @param {string|Element} target | 
|    * @return {Element|null} attached DOM Element. null if node to attach not found. | 
|    */ | 
|   attach(target) { | 
|     const sprite = this; | 
|   | 
|     if (sprite.isMounted) { | 
|       return sprite.node; | 
|     } | 
|   | 
|     /** @type Element */ | 
|     const node = typeof target === 'string' ? document.querySelector(target) : target; | 
|     sprite.node = node; | 
|   | 
|     // Already added symbols needs to be mounted | 
|     this.symbols.forEach((symbol) => { | 
|       symbol.mount(sprite.node); | 
|       this._emitter.emit(Events.SYMBOL_MOUNT, symbol.node); | 
|     }); | 
|   | 
|     // Create symbols from existing DOM nodes, add and mount them | 
|     arrayFrom(node.querySelectorAll('symbol')) | 
|       .forEach((symbolNode) => { | 
|         const symbol = BrowserSymbol.createFromExistingNode(symbolNode); | 
|         symbol.node = symbolNode; // hack to prevent symbol mounting to sprite when adding | 
|         sprite.add(symbol); | 
|       }); | 
|   | 
|     this._emitter.emit(Events.MOUNT, node); | 
|   | 
|     return node; | 
|   } | 
|   | 
|   destroy() { | 
|     const { config, symbols, _emitter } = this; | 
|   | 
|     symbols.forEach(s => s.destroy()); | 
|   | 
|     _emitter.off('*'); | 
|     window.removeEventListener(config.locationChangeEvent, this._handleLocationChange); | 
|   | 
|     if (this.isMounted) { | 
|       this.unmount(); | 
|     } | 
|   } | 
|   | 
|   /** | 
|    * @fires Events#MOUNT | 
|    * @param {string|Element} [target] | 
|    * @param {boolean} [prepend=false] | 
|    * @return {Element|null} rendered sprite node. null if mount node not found. | 
|    */ | 
|   mount(target = this.config.mountTo, prepend = false) { | 
|     const sprite = this; | 
|   | 
|     if (sprite.isMounted) { | 
|       return sprite.node; | 
|     } | 
|   | 
|     const mountNode = typeof target === 'string' ? document.querySelector(target) : target; | 
|     const node = sprite.render(); | 
|     this.node = node; | 
|   | 
|     if (prepend && mountNode.childNodes[0]) { | 
|       mountNode.insertBefore(node, mountNode.childNodes[0]); | 
|     } else { | 
|       mountNode.appendChild(node); | 
|     } | 
|   | 
|     this._emitter.emit(Events.MOUNT, node); | 
|   | 
|     return node; | 
|   } | 
|   | 
|   /** | 
|    * @return {Element} | 
|    */ | 
|   render() { | 
|     return parse(this.stringify()); | 
|   } | 
|   | 
|   /** | 
|    * Detach sprite from the DOM | 
|    */ | 
|   unmount() { | 
|     this.node.parentNode.removeChild(this.node); | 
|   } | 
|   | 
|   /** | 
|    * Update URLs in sprite and usage elements | 
|    * @param {string} oldUrl | 
|    * @param {string} newUrl | 
|    * @return {boolean} `true` - URLs was updated, `false` - sprite is not mounted | 
|    */ | 
|   updateUrls(oldUrl, newUrl) { | 
|     if (!this.isMounted) { | 
|       return false; | 
|     } | 
|   | 
|     const usages = document.querySelectorAll(this.config.usagesToUpdate); | 
|   | 
|     updateUrls( | 
|       this.node, | 
|       usages, | 
|       `${getUrlWithoutFragment(oldUrl)}#`, | 
|       `${getUrlWithoutFragment(newUrl)}#` | 
|     ); | 
|   | 
|     return true; | 
|   } | 
|   | 
| } |