import Vue, { CreateElement, VNode } from 'vue'; import { ExtendedVue } from 'vue/types/vue'; import { IconifyJSON } from '@iconify/types'; // Core import { IconifyIconName } from '@iconify/core/lib/icon/name'; import { IconifyIconSize, IconifyHorizontalIconAlignment, IconifyVerticalIconAlignment, } from '@iconify/core/lib/customisations'; import { IconifyStorageFunctions, storageFunctions, getIconData, allowSimpleNames, } from '@iconify/core/lib/storage/functions'; import { IconifyBuilderFunctions, builderFunctions, } from '@iconify/core/lib/builder/functions'; import type { IconifyIconBuildResult } from '@iconify/core/lib/builder'; import { fullIcon, IconifyIcon } from '@iconify/core/lib/icon'; // Modules import { coreModules } from '@iconify/core/lib/modules'; // API import { API, IconifyAPIInternalStorage } from '@iconify/core/lib/api/'; import { IconifyAPIFunctions, IconifyAPIInternalFunctions, APIFunctions, APIInternalFunctions, } from '@iconify/core/lib/api/functions'; import { setAPIModule, IconifyAPIModule, IconifyAPISendQuery, IconifyAPIPrepareQuery, GetIconifyAPIModule, } from '@iconify/core/lib/api/modules'; import { getAPIModule as getJSONPAPIModule } from '@iconify/core/lib/api/modules/jsonp'; import { getAPIModule as getFetchAPIModule, setFetch, } from '@iconify/core/lib/api/modules/fetch'; import { setAPIConfig, PartialIconifyAPIConfig, IconifyAPIConfig, getAPIConfig, GetAPIConfig, } from '@iconify/core/lib/api/config'; import { IconifyIconLoaderCallback, IconifyIconLoaderAbort, } from '@iconify/core/lib/interfaces/loader'; // Cache import { storeCache, loadCache } from '@iconify/core/lib/browser-storage'; import { toggleBrowserCache } from '@iconify/core/lib/browser-storage/functions'; import { IconifyBrowserCacheType, IconifyBrowserCacheFunctions, } from '@iconify/core/lib/browser-storage/functions'; // Properties import { RawIconCustomisations, IconifyIconOnLoad, IconProps, IconifyIconCustomisations, IconifyIconProps, } from './props'; // Render SVG import { render } from './render'; /** * Export required types */ // Function sets export { IconifyStorageFunctions, IconifyBuilderFunctions, IconifyBrowserCacheFunctions, IconifyAPIFunctions, IconifyAPIInternalFunctions, }; // JSON stuff export { IconifyIcon, IconifyJSON, IconifyIconName }; // Customisations and icon props export { IconifyIconCustomisations, IconifyIconSize, IconifyHorizontalIconAlignment, IconifyVerticalIconAlignment, IconifyIconProps, IconProps, IconifyIconOnLoad, }; // API export { IconifyAPIConfig, IconifyIconLoaderCallback, IconifyIconLoaderAbort, IconifyAPIInternalStorage, IconifyAPIModule, GetAPIConfig, IconifyAPIPrepareQuery, IconifyAPISendQuery, PartialIconifyAPIConfig, }; // Builder functions export { RawIconCustomisations, IconifyIconBuildResult }; /* Browser cache */ export { IconifyBrowserCacheType }; /** * Enable and disable browser cache */ export const enableCache = (storage: IconifyBrowserCacheType) => toggleBrowserCache(storage, true); export const disableCache = (storage: IconifyBrowserCacheType) => toggleBrowserCache(storage, false); /* Storage functions */ /** * Check if icon exists */ export const iconExists = storageFunctions.iconExists; /** * Get icon data */ export const getIcon = storageFunctions.getIcon; /** * List available icons */ export const listIcons = storageFunctions.listIcons; /** * Add one icon */ export const addIcon = storageFunctions.addIcon; /** * Add icon set */ export const addCollection = storageFunctions.addCollection; /* Builder functions */ /** * Calculate icon size */ export const calculateSize = builderFunctions.calculateSize; /** * Replace unique ids in content */ export const replaceIDs = builderFunctions.replaceIDs; /** * Build SVG */ export const buildIcon = builderFunctions.buildIcon; /* API functions */ /** * Load icons */ export const loadIcons = APIFunctions.loadIcons; /** * Add API provider */ export const addAPIProvider = APIFunctions.addAPIProvider; /** * Export internal functions that can be used by third party implementations */ export const _api = APIInternalFunctions; /** * Initialise stuff */ // Enable short names allowSimpleNames(true); // Set API coreModules.api = API; // Use Fetch API by default let getAPIModule: GetIconifyAPIModule = getFetchAPIModule; try { if (typeof document !== 'undefined' && typeof window !== 'undefined') { // If window and document exist, attempt to load whatever module is available, otherwise use Fetch API getAPIModule = typeof fetch === 'function' && typeof Promise === 'function' ? getFetchAPIModule : getJSONPAPIModule; } } catch (err) { // } setAPIModule('', getAPIModule(getAPIConfig)); /** * Function to enable node-fetch for getting icons on server side */ _api.setFetch = (nodeFetch: typeof fetch) => { setFetch(nodeFetch); if (getAPIModule !== getFetchAPIModule) { getAPIModule = getFetchAPIModule; setAPIModule('', getAPIModule(getAPIConfig)); } }; /** * Browser stuff */ if (typeof document !== 'undefined' && typeof window !== 'undefined') { // Set cache and load existing cache coreModules.cache = storeCache; loadCache(); const _window = window; // Load icons from global "IconifyPreload" interface WindowWithIconifyPreload { IconifyPreload: IconifyJSON[] | IconifyJSON; } if ( (_window as unknown as WindowWithIconifyPreload).IconifyPreload !== void 0 ) { const preload = (_window as unknown as WindowWithIconifyPreload) .IconifyPreload; const err = 'Invalid IconifyPreload syntax.'; if (typeof preload === 'object' && preload !== null) { (preload instanceof Array ? preload : [preload]).forEach((item) => { try { if ( // Check if item is an object and not null/array typeof item !== 'object' || item === null || item instanceof Array || // Check for 'icons' and 'prefix' typeof item.icons !== 'object' || typeof item.prefix !== 'string' || // Add icon set !addCollection(item) ) { console.error(err); } } catch (e) { console.error(err); } }); } } // Set API from global "IconifyProviders" interface WindowWithIconifyProviders { IconifyProviders: Record; } if ( (_window as unknown as WindowWithIconifyProviders).IconifyProviders !== void 0 ) { const providers = (_window as unknown as WindowWithIconifyProviders) .IconifyProviders; if (typeof providers === 'object' && providers !== null) { for (let key in providers) { const err = 'IconifyProviders[' + key + '] is invalid.'; try { const value = providers[key]; if ( typeof value !== 'object' || !value || value.resources === void 0 ) { continue; } if (!setAPIConfig(key, value)) { console.error(err); } } catch (e) { console.error(err); } } } } } /** * Component */ export const Icon = Vue.extend({ // Do not inherit other attributes: it is handled by render() // In Vue 2 style is still passed! inheritAttrs: false, // Set initial data data() { return { // Mounted status mounted: false, }; }, beforeMount() { // Current icon name this._name = ''; // Loading this._loadingIcon = null; // Mark as mounted this.mounted = true; }, beforeDestroy() { this.abortLoading(); }, methods: { abortLoading() { if (this._loadingIcon) { this._loadingIcon.abort(); this._loadingIcon = null; } }, // Get data for icon to render or null getIcon(icon: IconifyIcon | string, onload?: IconifyIconOnLoad) { // Icon is an object if ( typeof icon === 'object' && icon !== null && typeof icon.body === 'string' ) { // Stop loading this._name = ''; this.abortLoading(); return fullIcon(icon); } // Invalid icon? if (typeof icon !== 'string') { this.abortLoading(); return null; } // Load icon const data = getIconData(icon); if (data === null) { // Icon needs to be loaded if (!this._loadingIcon || this._loadingIcon.name !== icon) { // New icon to load this.abortLoading(); this._name = ''; this._loadingIcon = { name: icon, abort: API.loadIcons([icon], () => { this.$forceUpdate(); }), }; } return null; } // Icon data is available this.abortLoading(); if (this._name !== icon) { this._name = icon; if (onload) { onload(icon); } } return data; }, }, // Render icon render(createElement: CreateElement): VNode { function placeholder(slots): VNode { // Render child nodes if (slots.default) { const result = slots.default; if (result instanceof Array && result.length > 0) { // If there are multiple child nodes, they must be wrapped in Vue 2 return result.length === 1 ? result[0] : createElement('span', result); } } return null as unknown as VNode; } if (!this.mounted) { return placeholder(this.$slots); } // Get icon data const props = this.$attrs; const icon = this.getIcon(props.icon, props.onLoad); // Validate icon object if (!icon) { // Render child nodes return placeholder(this.$slots); } // Valid icon: render it return render(createElement, props, this.$data, icon); }, });