123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- 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;
- }
- }
|