(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vue')) : typeof define === 'function' && define.amd ? define(['exports', 'vue'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Icon = {}, global.Vue)); })(this, (function (exports, vue) { 'use strict'; const matchIconName = /^[a-z0-9]+(-[a-z0-9]+)*$/; const stringToIcon = (value, validate, allowSimpleName, provider = "") => { const colonSeparated = value.split(":"); if (value.slice(0, 1) === "@") { if (colonSeparated.length < 2 || colonSeparated.length > 3) { return null; } provider = colonSeparated.shift().slice(1); } if (colonSeparated.length > 3 || !colonSeparated.length) { return null; } if (colonSeparated.length > 1) { const name2 = colonSeparated.pop(); const prefix = colonSeparated.pop(); const result = { // Allow provider without '@': "provider:prefix:name" provider: colonSeparated.length > 0 ? colonSeparated[0] : provider, prefix, name: name2 }; return validate && !validateIconName(result) ? null : result; } const name = colonSeparated[0]; const dashSeparated = name.split("-"); if (dashSeparated.length > 1) { const result = { provider, prefix: dashSeparated.shift(), name: dashSeparated.join("-") }; return validate && !validateIconName(result) ? null : result; } if (allowSimpleName && provider === "") { const result = { provider, prefix: "", name }; return validate && !validateIconName(result, allowSimpleName) ? null : result; } return null; }; const validateIconName = (icon, allowSimpleName) => { if (!icon) { return false; } return !!((icon.provider === "" || icon.provider.match(matchIconName)) && (allowSimpleName && icon.prefix === "" || icon.prefix.match(matchIconName)) && icon.name.match(matchIconName)); }; const defaultIconDimensions = Object.freeze( { left: 0, top: 0, width: 16, height: 16 } ); const defaultIconTransformations = Object.freeze({ rotate: 0, vFlip: false, hFlip: false }); const defaultIconProps = Object.freeze({ ...defaultIconDimensions, ...defaultIconTransformations }); const defaultExtendedIconProps = Object.freeze({ ...defaultIconProps, body: "", hidden: false }); function mergeIconTransformations(obj1, obj2) { const result = {}; if (!obj1.hFlip !== !obj2.hFlip) { result.hFlip = true; } if (!obj1.vFlip !== !obj2.vFlip) { result.vFlip = true; } const rotate = ((obj1.rotate || 0) + (obj2.rotate || 0)) % 4; if (rotate) { result.rotate = rotate; } return result; } function mergeIconData(parent, child) { const result = mergeIconTransformations(parent, child); for (const key in defaultExtendedIconProps) { if (key in defaultIconTransformations) { if (key in parent && !(key in result)) { result[key] = defaultIconTransformations[key]; } } else if (key in child) { result[key] = child[key]; } else if (key in parent) { result[key] = parent[key]; } } return result; } function getIconsTree(data, names) { const icons = data.icons; const aliases = data.aliases || /* @__PURE__ */ Object.create(null); const resolved = /* @__PURE__ */ Object.create(null); function resolve(name) { if (icons[name]) { return resolved[name] = []; } if (!(name in resolved)) { resolved[name] = null; const parent = aliases[name] && aliases[name].parent; const value = parent && resolve(parent); if (value) { resolved[name] = [parent].concat(value); } } return resolved[name]; } (names || Object.keys(icons).concat(Object.keys(aliases))).forEach(resolve); return resolved; } function internalGetIconData(data, name, tree) { const icons = data.icons; const aliases = data.aliases || /* @__PURE__ */ Object.create(null); let currentProps = {}; function parse(name2) { currentProps = mergeIconData( icons[name2] || aliases[name2], currentProps ); } parse(name); tree.forEach(parse); return mergeIconData(data, currentProps); } function parseIconSet(data, callback) { const names = []; if (typeof data !== "object" || typeof data.icons !== "object") { return names; } if (data.not_found instanceof Array) { data.not_found.forEach((name) => { callback(name, null); names.push(name); }); } const tree = getIconsTree(data); for (const name in tree) { const item = tree[name]; if (item) { callback(name, internalGetIconData(data, name, item)); names.push(name); } } return names; } const optionalPropertyDefaults = { provider: "", aliases: {}, not_found: {}, ...defaultIconDimensions }; function checkOptionalProps(item, defaults) { for (const prop in defaults) { if (prop in item && typeof item[prop] !== typeof defaults[prop]) { return false; } } return true; } function quicklyValidateIconSet(obj) { if (typeof obj !== "object" || obj === null) { return null; } const data = obj; if (typeof data.prefix !== "string" || !obj.icons || typeof obj.icons !== "object") { return null; } if (!checkOptionalProps(obj, optionalPropertyDefaults)) { return null; } const icons = data.icons; for (const name in icons) { const icon = icons[name]; if (!name.match(matchIconName) || typeof icon.body !== "string" || !checkOptionalProps( icon, defaultExtendedIconProps )) { return null; } } const aliases = data.aliases || /* @__PURE__ */ Object.create(null); for (const name in aliases) { const icon = aliases[name]; const parent = icon.parent; if (!name.match(matchIconName) || typeof parent !== "string" || !icons[parent] && !aliases[parent] || !checkOptionalProps( icon, defaultExtendedIconProps )) { return null; } } return data; } const dataStorage = /* @__PURE__ */ Object.create(null); function newStorage(provider, prefix) { return { provider, prefix, icons: /* @__PURE__ */ Object.create(null), missing: /* @__PURE__ */ new Set() }; } function getStorage(provider, prefix) { const providerStorage = dataStorage[provider] || (dataStorage[provider] = /* @__PURE__ */ Object.create(null)); return providerStorage[prefix] || (providerStorage[prefix] = newStorage(provider, prefix)); } function addIconSet(storage, data) { if (!quicklyValidateIconSet(data)) { return []; } return parseIconSet(data, (name, icon) => { if (icon) { storage.icons[name] = icon; } else { storage.missing.add(name); } }); } function addIconToStorage(storage, name, icon) { try { if (typeof icon.body === "string") { storage.icons[name] = { ...icon }; return true; } } catch (err) { } return false; } function listIcons(provider, prefix) { let allIcons = []; const providers = typeof provider === "string" ? [provider] : Object.keys(dataStorage); providers.forEach((provider2) => { const prefixes = typeof provider2 === "string" && typeof prefix === "string" ? [prefix] : Object.keys(dataStorage[provider2] || {}); prefixes.forEach((prefix2) => { const storage = getStorage(provider2, prefix2); allIcons = allIcons.concat( Object.keys(storage.icons).map( (name) => (provider2 !== "" ? "@" + provider2 + ":" : "") + prefix2 + ":" + name ) ); }); }); return allIcons; } let simpleNames = false; function allowSimpleNames(allow) { if (typeof allow === "boolean") { simpleNames = allow; } return simpleNames; } function getIconData(name) { const icon = typeof name === "string" ? stringToIcon(name, true, simpleNames) : name; if (icon) { const storage = getStorage(icon.provider, icon.prefix); const iconName = icon.name; return storage.icons[iconName] || (storage.missing.has(iconName) ? null : void 0); } } function addIcon(name, data) { const icon = stringToIcon(name, true, simpleNames); if (!icon) { return false; } const storage = getStorage(icon.provider, icon.prefix); return addIconToStorage(storage, icon.name, data); } function addCollection(data, provider) { if (typeof data !== "object") { return false; } if (typeof provider !== "string") { provider = data.provider || ""; } if (simpleNames && !provider && !data.prefix) { let added = false; if (quicklyValidateIconSet(data)) { data.prefix = ""; parseIconSet(data, (name, icon) => { if (icon && addIcon(name, icon)) { added = true; } }); } return added; } const prefix = data.prefix; if (!validateIconName({ provider, prefix, name: "a" })) { return false; } const storage = getStorage(provider, prefix); return !!addIconSet(storage, data); } function iconExists(name) { return !!getIconData(name); } function getIcon(name) { const result = getIconData(name); return result ? { ...defaultIconProps, ...result } : null; } const defaultIconSizeCustomisations = Object.freeze({ width: null, height: null }); const defaultIconCustomisations = Object.freeze({ // Dimensions ...defaultIconSizeCustomisations, // Transformations ...defaultIconTransformations }); const unitsSplit = /(-?[0-9.]*[0-9]+[0-9.]*)/g; const unitsTest = /^-?[0-9.]*[0-9]+[0-9.]*$/g; function calculateSize(size, ratio, precision) { if (ratio === 1) { return size; } precision = precision || 100; if (typeof size === "number") { return Math.ceil(size * ratio * precision) / precision; } if (typeof size !== "string") { return size; } const oldParts = size.split(unitsSplit); if (oldParts === null || !oldParts.length) { return size; } const newParts = []; let code = oldParts.shift(); let isNumber = unitsTest.test(code); while (true) { if (isNumber) { const num = parseFloat(code); if (isNaN(num)) { newParts.push(code); } else { newParts.push(Math.ceil(num * ratio * precision) / precision); } } else { newParts.push(code); } code = oldParts.shift(); if (code === void 0) { return newParts.join(""); } isNumber = !isNumber; } } const isUnsetKeyword = (value) => value === "unset" || value === "undefined" || value === "none"; function iconToSVG(icon, customisations) { const fullIcon = { ...defaultIconProps, ...icon }; const fullCustomisations = { ...defaultIconCustomisations, ...customisations }; const box = { left: fullIcon.left, top: fullIcon.top, width: fullIcon.width, height: fullIcon.height }; let body = fullIcon.body; [fullIcon, fullCustomisations].forEach((props) => { const transformations = []; const hFlip = props.hFlip; const vFlip = props.vFlip; let rotation = props.rotate; if (hFlip) { if (vFlip) { rotation += 2; } else { transformations.push( "translate(" + (box.width + box.left).toString() + " " + (0 - box.top).toString() + ")" ); transformations.push("scale(-1 1)"); box.top = box.left = 0; } } else if (vFlip) { transformations.push( "translate(" + (0 - box.left).toString() + " " + (box.height + box.top).toString() + ")" ); transformations.push("scale(1 -1)"); box.top = box.left = 0; } let tempValue; if (rotation < 0) { rotation -= Math.floor(rotation / 4) * 4; } rotation = rotation % 4; switch (rotation) { case 1: tempValue = box.height / 2 + box.top; transformations.unshift( "rotate(90 " + tempValue.toString() + " " + tempValue.toString() + ")" ); break; case 2: transformations.unshift( "rotate(180 " + (box.width / 2 + box.left).toString() + " " + (box.height / 2 + box.top).toString() + ")" ); break; case 3: tempValue = box.width / 2 + box.left; transformations.unshift( "rotate(-90 " + tempValue.toString() + " " + tempValue.toString() + ")" ); break; } if (rotation % 2 === 1) { if (box.left !== box.top) { tempValue = box.left; box.left = box.top; box.top = tempValue; } if (box.width !== box.height) { tempValue = box.width; box.width = box.height; box.height = tempValue; } } if (transformations.length) { body = '' + body + ""; } }); const customisationsWidth = fullCustomisations.width; const customisationsHeight = fullCustomisations.height; const boxWidth = box.width; const boxHeight = box.height; let width; let height; if (customisationsWidth === null) { height = customisationsHeight === null ? "1em" : customisationsHeight === "auto" ? boxHeight : customisationsHeight; width = calculateSize(height, boxWidth / boxHeight); } else { width = customisationsWidth === "auto" ? boxWidth : customisationsWidth; height = customisationsHeight === null ? calculateSize(width, boxHeight / boxWidth) : customisationsHeight === "auto" ? boxHeight : customisationsHeight; } const attributes = {}; const setAttr = (prop, value) => { if (!isUnsetKeyword(value)) { attributes[prop] = value.toString(); } }; setAttr("width", width); setAttr("height", height); attributes.viewBox = box.left.toString() + " " + box.top.toString() + " " + boxWidth.toString() + " " + boxHeight.toString(); return { attributes, body }; } const regex = /\sid="(\S+)"/g; const randomPrefix = "IconifyId" + Date.now().toString(16) + (Math.random() * 16777216 | 0).toString(16); let counter = 0; function replaceIDs(body, prefix = randomPrefix) { const ids = []; let match; while (match = regex.exec(body)) { ids.push(match[1]); } if (!ids.length) { return body; } const suffix = "suffix" + (Math.random() * 16777216 | Date.now()).toString(16); ids.forEach((id) => { const newID = typeof prefix === "function" ? prefix(id) : prefix + (counter++).toString(); const escapedID = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); body = body.replace( // Allowed characters before id: [#;"] // Allowed characters after id: [)"], .[a-z] new RegExp('([#;"])(' + escapedID + ')([")]|\\.[a-z])', "g"), "$1" + newID + suffix + "$3" ); }); body = body.replace(new RegExp(suffix, "g"), ""); return body; } const storage = /* @__PURE__ */ Object.create(null); function setAPIModule(provider, item) { storage[provider] = item; } function getAPIModule(provider) { return storage[provider] || storage[""]; } function createAPIConfig(source) { let resources; if (typeof source.resources === "string") { resources = [source.resources]; } else { resources = source.resources; if (!(resources instanceof Array) || !resources.length) { return null; } } const result = { // API hosts resources, // Root path path: source.path || "/", // URL length limit maxURL: source.maxURL || 500, // Timeout before next host is used. rotate: source.rotate || 750, // Timeout before failing query. timeout: source.timeout || 5e3, // Randomise default API end point. random: source.random === true, // Start index index: source.index || 0, // Receive data after time out (used if time out kicks in first, then API module sends data anyway). dataAfterTimeout: source.dataAfterTimeout !== false }; return result; } const configStorage = /* @__PURE__ */ Object.create(null); const fallBackAPISources = [ "https://api.simplesvg.com", "https://api.unisvg.com" ]; const fallBackAPI = []; while (fallBackAPISources.length > 0) { if (fallBackAPISources.length === 1) { fallBackAPI.push(fallBackAPISources.shift()); } else { if (Math.random() > 0.5) { fallBackAPI.push(fallBackAPISources.shift()); } else { fallBackAPI.push(fallBackAPISources.pop()); } } } configStorage[""] = createAPIConfig({ resources: ["https://api.iconify.design"].concat(fallBackAPI) }); function addAPIProvider(provider, customConfig) { const config = createAPIConfig(customConfig); if (config === null) { return false; } configStorage[provider] = config; return true; } function getAPIConfig(provider) { return configStorage[provider]; } function listAPIProviders() { return Object.keys(configStorage); } const detectFetch = () => { let callback; try { callback = fetch; if (typeof callback === "function") { return callback; } } catch (err) { } }; let fetchModule = detectFetch(); function setFetch(fetch2) { fetchModule = fetch2; } function getFetch() { return fetchModule; } function calculateMaxLength(provider, prefix) { const config = getAPIConfig(provider); if (!config) { return 0; } let result; if (!config.maxURL) { result = 0; } else { let maxHostLength = 0; config.resources.forEach((item) => { const host = item; maxHostLength = Math.max(maxHostLength, host.length); }); const url = prefix + ".json?icons="; result = config.maxURL - maxHostLength - config.path.length - url.length; } return result; } function shouldAbort(status) { return status === 404; } const prepare = (provider, prefix, icons) => { const results = []; const maxLength = calculateMaxLength(provider, prefix); const type = "icons"; let item = { type, provider, prefix, icons: [] }; let length = 0; icons.forEach((name, index) => { length += name.length + 1; if (length >= maxLength && index > 0) { results.push(item); item = { type, provider, prefix, icons: [] }; length = name.length; } item.icons.push(name); }); results.push(item); return results; }; function getPath(provider) { if (typeof provider === "string") { const config = getAPIConfig(provider); if (config) { return config.path; } } return "/"; } const send = (host, params, callback) => { if (!fetchModule) { callback("abort", 424); return; } let path = getPath(params.provider); switch (params.type) { case "icons": { const prefix = params.prefix; const icons = params.icons; const iconsList = icons.join(","); const urlParams = new URLSearchParams({ icons: iconsList }); path += prefix + ".json?" + urlParams.toString(); break; } case "custom": { const uri = params.uri; path += uri.slice(0, 1) === "/" ? uri.slice(1) : uri; break; } default: callback("abort", 400); return; } let defaultError = 503; fetchModule(host + path).then((response) => { const status = response.status; if (status !== 200) { setTimeout(() => { callback(shouldAbort(status) ? "abort" : "next", status); }); return; } defaultError = 501; return response.json(); }).then((data) => { if (typeof data !== "object" || data === null) { setTimeout(() => { if (data === 404) { callback("abort", data); } else { callback("next", defaultError); } }); return; } setTimeout(() => { callback("success", data); }); }).catch(() => { callback("next", defaultError); }); }; const fetchAPIModule = { prepare, send }; function sortIcons(icons) { const result = { loaded: [], missing: [], pending: [] }; const storage = /* @__PURE__ */ Object.create(null); icons.sort((a, b) => { if (a.provider !== b.provider) { return a.provider.localeCompare(b.provider); } if (a.prefix !== b.prefix) { return a.prefix.localeCompare(b.prefix); } return a.name.localeCompare(b.name); }); let lastIcon = { provider: "", prefix: "", name: "" }; icons.forEach((icon) => { if (lastIcon.name === icon.name && lastIcon.prefix === icon.prefix && lastIcon.provider === icon.provider) { return; } lastIcon = icon; const provider = icon.provider; const prefix = icon.prefix; const name = icon.name; const providerStorage = storage[provider] || (storage[provider] = /* @__PURE__ */ Object.create(null)); const localStorage = providerStorage[prefix] || (providerStorage[prefix] = getStorage(provider, prefix)); let list; if (name in localStorage.icons) { list = result.loaded; } else if (prefix === "" || localStorage.missing.has(name)) { list = result.missing; } else { list = result.pending; } const item = { provider, prefix, name }; list.push(item); }); return result; } function removeCallback(storages, id) { storages.forEach((storage) => { const items = storage.loaderCallbacks; if (items) { storage.loaderCallbacks = items.filter((row) => row.id !== id); } }); } function updateCallbacks(storage) { if (!storage.pendingCallbacksFlag) { storage.pendingCallbacksFlag = true; setTimeout(() => { storage.pendingCallbacksFlag = false; const items = storage.loaderCallbacks ? storage.loaderCallbacks.slice(0) : []; if (!items.length) { return; } let hasPending = false; const provider = storage.provider; const prefix = storage.prefix; items.forEach((item) => { const icons = item.icons; const oldLength = icons.pending.length; icons.pending = icons.pending.filter((icon) => { if (icon.prefix !== prefix) { return true; } const name = icon.name; if (storage.icons[name]) { icons.loaded.push({ provider, prefix, name }); } else if (storage.missing.has(name)) { icons.missing.push({ provider, prefix, name }); } else { hasPending = true; return true; } return false; }); if (icons.pending.length !== oldLength) { if (!hasPending) { removeCallback([storage], item.id); } item.callback( icons.loaded.slice(0), icons.missing.slice(0), icons.pending.slice(0), item.abort ); } }); }); } } let idCounter = 0; function storeCallback(callback, icons, pendingSources) { const id = idCounter++; const abort = removeCallback.bind(null, pendingSources, id); if (!icons.pending.length) { return abort; } const item = { id, icons, callback, abort }; pendingSources.forEach((storage) => { (storage.loaderCallbacks || (storage.loaderCallbacks = [])).push(item); }); return abort; } function listToIcons(list, validate = true, simpleNames = false) { const result = []; list.forEach((item) => { const icon = typeof item === "string" ? stringToIcon(item, validate, simpleNames) : item; if (icon) { result.push(icon); } }); return result; } // src/config.ts var defaultConfig = { resources: [], index: 0, timeout: 2e3, rotate: 750, random: false, dataAfterTimeout: false }; // src/query.ts function sendQuery(config, payload, query, done) { const resourcesCount = config.resources.length; const startIndex = config.random ? Math.floor(Math.random() * resourcesCount) : config.index; let resources; if (config.random) { let list = config.resources.slice(0); resources = []; while (list.length > 1) { const nextIndex = Math.floor(Math.random() * list.length); resources.push(list[nextIndex]); list = list.slice(0, nextIndex).concat(list.slice(nextIndex + 1)); } resources = resources.concat(list); } else { resources = config.resources.slice(startIndex).concat(config.resources.slice(0, startIndex)); } const startTime = Date.now(); let status = "pending"; let queriesSent = 0; let lastError; let timer = null; let queue = []; let doneCallbacks = []; if (typeof done === "function") { doneCallbacks.push(done); } function resetTimer() { if (timer) { clearTimeout(timer); timer = null; } } function abort() { if (status === "pending") { status = "aborted"; } resetTimer(); queue.forEach((item) => { if (item.status === "pending") { item.status = "aborted"; } }); queue = []; } function subscribe(callback, overwrite) { if (overwrite) { doneCallbacks = []; } if (typeof callback === "function") { doneCallbacks.push(callback); } } function getQueryStatus() { return { startTime, payload, status, queriesSent, queriesPending: queue.length, subscribe, abort }; } function failQuery() { status = "failed"; doneCallbacks.forEach((callback) => { callback(void 0, lastError); }); } function clearQueue() { queue.forEach((item) => { if (item.status === "pending") { item.status = "aborted"; } }); queue = []; } function moduleResponse(item, response, data) { const isError = response !== "success"; queue = queue.filter((queued) => queued !== item); switch (status) { case "pending": break; case "failed": if (isError || !config.dataAfterTimeout) { return; } break; default: return; } if (response === "abort") { lastError = data; failQuery(); return; } if (isError) { lastError = data; if (!queue.length) { if (!resources.length) { failQuery(); } else { execNext(); } } return; } resetTimer(); clearQueue(); if (!config.random) { const index = config.resources.indexOf(item.resource); if (index !== -1 && index !== config.index) { config.index = index; } } status = "completed"; doneCallbacks.forEach((callback) => { callback(data); }); } function execNext() { if (status !== "pending") { return; } resetTimer(); const resource = resources.shift(); if (resource === void 0) { if (queue.length) { timer = setTimeout(() => { resetTimer(); if (status === "pending") { clearQueue(); failQuery(); } }, config.timeout); return; } failQuery(); return; } const item = { status: "pending", resource, callback: (status2, data) => { moduleResponse(item, status2, data); } }; queue.push(item); queriesSent++; timer = setTimeout(execNext, config.rotate); query(resource, payload, item.callback); } setTimeout(execNext); return getQueryStatus; } // src/index.ts function initRedundancy(cfg) { const config = { ...defaultConfig, ...cfg }; let queries = []; function cleanup() { queries = queries.filter((item) => item().status === "pending"); } function query(payload, queryCallback, doneCallback) { const query2 = sendQuery( config, payload, queryCallback, (data, error) => { cleanup(); if (doneCallback) { doneCallback(data, error); } } ); queries.push(query2); return query2; } function find(callback) { return queries.find((value) => { return callback(value); }) || null; } const instance = { query, find, setIndex: (index) => { config.index = index; }, getIndex: () => config.index, cleanup }; return instance; } function emptyCallback$1() { } const redundancyCache = /* @__PURE__ */ Object.create(null); function getRedundancyCache(provider) { if (!redundancyCache[provider]) { const config = getAPIConfig(provider); if (!config) { return; } const redundancy = initRedundancy(config); const cachedReundancy = { config, redundancy }; redundancyCache[provider] = cachedReundancy; } return redundancyCache[provider]; } function sendAPIQuery(target, query, callback) { let redundancy; let send; if (typeof target === "string") { const api = getAPIModule(target); if (!api) { callback(void 0, 424); return emptyCallback$1; } send = api.send; const cached = getRedundancyCache(target); if (cached) { redundancy = cached.redundancy; } } else { const config = createAPIConfig(target); if (config) { redundancy = initRedundancy(config); const moduleKey = target.resources ? target.resources[0] : ""; const api = getAPIModule(moduleKey); if (api) { send = api.send; } } } if (!redundancy || !send) { callback(void 0, 424); return emptyCallback$1; } return redundancy.query(query, send, callback)().abort; } const browserCacheVersion = "iconify2"; const browserCachePrefix = "iconify"; const browserCacheCountKey = browserCachePrefix + "-count"; const browserCacheVersionKey = browserCachePrefix + "-version"; const browserStorageHour = 36e5; const browserStorageCacheExpiration = 168; function getStoredItem(func, key) { try { return func.getItem(key); } catch (err) { } } function setStoredItem(func, key, value) { try { func.setItem(key, value); return true; } catch (err) { } } function removeStoredItem(func, key) { try { func.removeItem(key); } catch (err) { } } function setBrowserStorageItemsCount(storage, value) { return setStoredItem(storage, browserCacheCountKey, value.toString()); } function getBrowserStorageItemsCount(storage) { return parseInt(getStoredItem(storage, browserCacheCountKey)) || 0; } const browserStorageConfig = { local: true, session: true }; const browserStorageEmptyItems = { local: /* @__PURE__ */ new Set(), session: /* @__PURE__ */ new Set() }; let browserStorageStatus = false; function setBrowserStorageStatus(status) { browserStorageStatus = status; } let _window = typeof window === "undefined" ? {} : window; function getBrowserStorage(key) { const attr = key + "Storage"; try { if (_window && _window[attr] && typeof _window[attr].length === "number") { return _window[attr]; } } catch (err) { } browserStorageConfig[key] = false; } function iterateBrowserStorage(key, callback) { const func = getBrowserStorage(key); if (!func) { return; } const version = getStoredItem(func, browserCacheVersionKey); if (version !== browserCacheVersion) { if (version) { const total2 = getBrowserStorageItemsCount(func); for (let i = 0; i < total2; i++) { removeStoredItem(func, browserCachePrefix + i.toString()); } } setStoredItem(func, browserCacheVersionKey, browserCacheVersion); setBrowserStorageItemsCount(func, 0); return; } const minTime = Math.floor(Date.now() / browserStorageHour) - browserStorageCacheExpiration; const parseItem = (index) => { const name = browserCachePrefix + index.toString(); const item = getStoredItem(func, name); if (typeof item !== "string") { return; } try { const data = JSON.parse(item); if (typeof data === "object" && typeof data.cached === "number" && data.cached > minTime && typeof data.provider === "string" && typeof data.data === "object" && typeof data.data.prefix === "string" && // Valid item: run callback callback(data, index)) { return true; } } catch (err) { } removeStoredItem(func, name); }; let total = getBrowserStorageItemsCount(func); for (let i = total - 1; i >= 0; i--) { if (!parseItem(i)) { if (i === total - 1) { total--; setBrowserStorageItemsCount(func, total); } else { browserStorageEmptyItems[key].add(i); } } } } function initBrowserStorage() { if (browserStorageStatus) { return; } setBrowserStorageStatus(true); for (const key in browserStorageConfig) { iterateBrowserStorage(key, (item) => { const iconSet = item.data; const provider = item.provider; const prefix = iconSet.prefix; const storage = getStorage( provider, prefix ); if (!addIconSet(storage, iconSet).length) { return false; } const lastModified = iconSet.lastModified || -1; storage.lastModifiedCached = storage.lastModifiedCached ? Math.min(storage.lastModifiedCached, lastModified) : lastModified; return true; }); } } function updateLastModified(storage, lastModified) { const lastValue = storage.lastModifiedCached; if ( // Matches or newer lastValue && lastValue >= lastModified ) { return lastValue === lastModified; } storage.lastModifiedCached = lastModified; if (lastValue) { for (const key in browserStorageConfig) { iterateBrowserStorage(key, (item) => { const iconSet = item.data; return item.provider !== storage.provider || iconSet.prefix !== storage.prefix || iconSet.lastModified === lastModified; }); } } return true; } function storeInBrowserStorage(storage, data) { if (!browserStorageStatus) { initBrowserStorage(); } function store(key) { let func; if (!browserStorageConfig[key] || !(func = getBrowserStorage(key))) { return; } const set = browserStorageEmptyItems[key]; let index; if (set.size) { set.delete(index = Array.from(set).shift()); } else { index = getBrowserStorageItemsCount(func); if (!setBrowserStorageItemsCount(func, index + 1)) { return; } } const item = { cached: Math.floor(Date.now() / browserStorageHour), provider: storage.provider, data }; return setStoredItem( func, browserCachePrefix + index.toString(), JSON.stringify(item) ); } if (data.lastModified && !updateLastModified(storage, data.lastModified)) { return; } if (!Object.keys(data.icons).length) { return; } if (data.not_found) { data = Object.assign({}, data); delete data.not_found; } if (!store("local")) { store("session"); } } function emptyCallback() { } function loadedNewIcons(storage) { if (!storage.iconsLoaderFlag) { storage.iconsLoaderFlag = true; setTimeout(() => { storage.iconsLoaderFlag = false; updateCallbacks(storage); }); } } function loadNewIcons(storage, icons) { if (!storage.iconsToLoad) { storage.iconsToLoad = icons; } else { storage.iconsToLoad = storage.iconsToLoad.concat(icons).sort(); } if (!storage.iconsQueueFlag) { storage.iconsQueueFlag = true; setTimeout(() => { storage.iconsQueueFlag = false; const { provider, prefix } = storage; const icons2 = storage.iconsToLoad; delete storage.iconsToLoad; let api; if (!icons2 || !(api = getAPIModule(provider))) { return; } const params = api.prepare(provider, prefix, icons2); params.forEach((item) => { sendAPIQuery(provider, item, (data) => { if (typeof data !== "object") { item.icons.forEach((name) => { storage.missing.add(name); }); } else { try { const parsed = addIconSet( storage, data ); if (!parsed.length) { return; } const pending = storage.pendingIcons; if (pending) { parsed.forEach((name) => { pending.delete(name); }); } storeInBrowserStorage(storage, data); } catch (err) { console.error(err); } } loadedNewIcons(storage); }); }); }); } } const loadIcons = (icons, callback) => { const cleanedIcons = listToIcons(icons, true, allowSimpleNames()); const sortedIcons = sortIcons(cleanedIcons); if (!sortedIcons.pending.length) { let callCallback = true; if (callback) { setTimeout(() => { if (callCallback) { callback( sortedIcons.loaded, sortedIcons.missing, sortedIcons.pending, emptyCallback ); } }); } return () => { callCallback = false; }; } const newIcons = /* @__PURE__ */ Object.create(null); const sources = []; let lastProvider, lastPrefix; sortedIcons.pending.forEach((icon) => { const { provider, prefix } = icon; if (prefix === lastPrefix && provider === lastProvider) { return; } lastProvider = provider; lastPrefix = prefix; sources.push(getStorage(provider, prefix)); const providerNewIcons = newIcons[provider] || (newIcons[provider] = /* @__PURE__ */ Object.create(null)); if (!providerNewIcons[prefix]) { providerNewIcons[prefix] = []; } }); sortedIcons.pending.forEach((icon) => { const { provider, prefix, name } = icon; const storage = getStorage(provider, prefix); const pendingQueue = storage.pendingIcons || (storage.pendingIcons = /* @__PURE__ */ new Set()); if (!pendingQueue.has(name)) { pendingQueue.add(name); newIcons[provider][prefix].push(name); } }); sources.forEach((storage) => { const { provider, prefix } = storage; if (newIcons[provider][prefix].length) { loadNewIcons(storage, newIcons[provider][prefix]); } }); return callback ? storeCallback(callback, sortedIcons, sources) : emptyCallback; }; const loadIcon = (icon) => { return new Promise((fulfill, reject) => { const iconObj = typeof icon === "string" ? stringToIcon(icon, true) : icon; if (!iconObj) { reject(icon); return; } loadIcons([iconObj || icon], (loaded) => { if (loaded.length && iconObj) { const data = getIconData(iconObj); if (data) { fulfill({ ...defaultIconProps, ...data }); return; } } reject(icon); }); }); }; function toggleBrowserCache(storage, value) { switch (storage) { case "local": case "session": browserStorageConfig[storage] = value; break; case "all": for (const key in browserStorageConfig) { browserStorageConfig[key] = value; } break; } } function mergeCustomisations(defaults, item) { const result = { ...defaults }; for (const key in item) { const value = item[key]; const valueType = typeof value; if (key in defaultIconSizeCustomisations) { if (value === null || value && (valueType === "string" || valueType === "number")) { result[key] = value; } } else if (valueType === typeof result[key]) { result[key] = key === "rotate" ? value % 4 : value; } } return result; } const separator = /[\s,]+/; function flipFromString(custom, flip) { flip.split(separator).forEach((str) => { const value = str.trim(); switch (value) { case "horizontal": custom.hFlip = true; break; case "vertical": custom.vFlip = true; break; } }); } function rotateFromString(value, defaultValue = 0) { const units = value.replace(/^-?[0-9.]*/, ""); function cleanup(value2) { while (value2 < 0) { value2 += 4; } return value2 % 4; } if (units === "") { const num = parseInt(value); return isNaN(num) ? 0 : cleanup(num); } else if (units !== value) { let split = 0; switch (units) { case "%": split = 25; break; case "deg": split = 90; } if (split) { let num = parseFloat(value.slice(0, value.length - units.length)); if (isNaN(num)) { return 0; } num = num / split; return num % 1 === 0 ? cleanup(num) : 0; } } return defaultValue; } function iconToHTML(body, attributes) { let renderAttribsHTML = body.indexOf("xlink:") === -1 ? "" : ' xmlns:xlink="http://www.w3.org/1999/xlink"'; for (const attr in attributes) { renderAttribsHTML += " " + attr + '="' + attributes[attr] + '"'; } return '" + body + ""; } function encodeSVGforURL(svg) { return svg.replace(/"/g, "'").replace(/%/g, "%25").replace(/#/g, "%23").replace(//g, "%3E").replace(/\s+/g, " "); } function svgToData(svg) { return "data:image/svg+xml," + encodeSVGforURL(svg); } function svgToURL(svg) { return 'url("' + svgToData(svg) + '")'; } const defaultExtendedIconCustomisations = { ...defaultIconCustomisations, inline: false, }; /** * Default SVG attributes */ const svgDefaults = { 'xmlns': 'http://www.w3.org/2000/svg', 'xmlns:xlink': 'http://www.w3.org/1999/xlink', 'aria-hidden': true, 'role': 'img', }; /** * Style modes */ const commonProps = { display: 'inline-block', }; const monotoneProps = { backgroundColor: 'currentColor', }; const coloredProps = { backgroundColor: 'transparent', }; // Dynamically add common props to variables above const propsToAdd = { Image: 'var(--svg)', Repeat: 'no-repeat', Size: '100% 100%', }; const propsToAddTo = { webkitMask: monotoneProps, mask: monotoneProps, background: coloredProps, }; for (const prefix in propsToAddTo) { const list = propsToAddTo[prefix]; for (const prop in propsToAdd) { list[prefix + prop] = propsToAdd[prop]; } } /** * Aliases for customisations. * In Vue 'v-' properties are reserved, so v-flip must be renamed */ const customisationAliases = {}; ['horizontal', 'vertical'].forEach((prefix) => { const attr = prefix.slice(0, 1) + 'Flip'; // vertical-flip customisationAliases[prefix + '-flip'] = attr; // v-flip customisationAliases[prefix.slice(0, 1) + '-flip'] = attr; // verticalFlip customisationAliases[prefix + 'Flip'] = attr; }); /** * Fix size: add 'px' to numbers */ function fixSize(value) { return value + (value.match(/^[-0-9.]+$/) ? 'px' : ''); } /** * Render icon */ const render = ( // Icon must be validated before calling this function icon, // Partial properties props) => { // Split properties const customisations = mergeCustomisations(defaultExtendedIconCustomisations, props); const componentProps = { ...svgDefaults }; // Check mode const mode = props.mode || 'svg'; // Copy style const style = {}; const propsStyle = props.style; const customStyle = typeof propsStyle === 'object' && !(propsStyle instanceof Array) ? propsStyle : {}; // Get element properties for (let key in props) { const value = props[key]; if (value === void 0) { continue; } switch (key) { // Properties to ignore case 'icon': case 'style': case 'onLoad': case 'mode': break; // Boolean attributes case 'inline': case 'hFlip': case 'vFlip': customisations[key] = value === true || value === 'true' || value === 1; break; // Flip as string: 'horizontal,vertical' case 'flip': if (typeof value === 'string') { flipFromString(customisations, value); } break; // Color: override style case 'color': style.color = value; break; // Rotation as string case 'rotate': if (typeof value === 'string') { customisations[key] = rotateFromString(value); } else if (typeof value === 'number') { customisations[key] = value; } break; // Remove aria-hidden case 'ariaHidden': case 'aria-hidden': // Vue transforms 'aria-hidden' property to 'ariaHidden' if (value !== true && value !== 'true') { delete componentProps['aria-hidden']; } break; default: { const alias = customisationAliases[key]; if (alias) { // Aliases for boolean customisations if (value === true || value === 'true' || value === 1) { customisations[alias] = true; } } else if (defaultExtendedIconCustomisations[key] === void 0) { // Copy missing property if it does not exist in customisations componentProps[key] = value; } } } } // Generate icon const item = iconToSVG(icon, customisations); const renderAttribs = item.attributes; // Inline display if (customisations.inline) { style.verticalAlign = '-0.125em'; } if (mode === 'svg') { // Add style componentProps.style = { ...style, ...customStyle, }; // Add icon stuff Object.assign(componentProps, renderAttribs); // Counter for ids based on "id" property to render icons consistently on server and client let localCounter = 0; let id = props.id; if (typeof id === 'string') { // Convert '-' to '_' to avoid errors in animations id = id.replace(/-/g, '_'); } // Add innerHTML and style to props componentProps['innerHTML'] = replaceIDs(item.body, id ? () => id + 'ID' + localCounter++ : 'iconifyVue'); // Render icon return vue.h('svg', componentProps); } // Render with style const { body, width, height } = icon; const useMask = mode === 'mask' || (mode === 'bg' ? false : body.indexOf('currentColor') !== -1); // Generate SVG const html = iconToHTML(body, { ...renderAttribs, width: width + '', height: height + '', }); // Generate style componentProps.style = { ...style, '--svg': svgToURL(html), 'width': fixSize(renderAttribs.width), 'height': fixSize(renderAttribs.height), ...commonProps, ...(useMask ? monotoneProps : coloredProps), ...customStyle, }; return vue.h('span', componentProps); }; /** * Enable cache */ function enableCache(storage) { toggleBrowserCache(storage, true); } /** * Disable cache */ function disableCache(storage) { toggleBrowserCache(storage, false); } /** * Initialise stuff */ // Enable short names allowSimpleNames(true); // Set API module setAPIModule('', fetchAPIModule); /** * Browser stuff */ if (typeof document !== 'undefined' && typeof window !== 'undefined') { // Set cache and load existing cache initBrowserStorage(); const _window = window; // Load icons from global "IconifyPreload" if (_window.IconifyPreload !== void 0) { const preload = _window.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" if (_window.IconifyProviders !== void 0) { const providers = _window.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 (!addAPIProvider(key, value)) { console.error(err); } } catch (e) { console.error(err); } } } } } /** * Empty icon data, rendered when icon is not available */ const emptyIcon = { ...defaultIconProps, body: '', }; const Icon = vue.defineComponent({ // Do not inherit other attributes: it is handled by render() inheritAttrs: false, // Set initial data data() { return { // Mounted status iconMounted: false, // Callback counter to trigger re-render counter: 0, }; }, mounted() { // Current icon name this._name = ''; // Loading this._loadingIcon = null; // Mark as mounted this.iconMounted = true; }, unmounted() { this.abortLoading(); }, methods: { abortLoading() { if (this._loadingIcon) { this._loadingIcon.abort(); this._loadingIcon = null; } }, // Get data for icon to render or null getIcon(icon, onload) { // Icon is an object if (typeof icon === 'object' && icon !== null && typeof icon.body === 'string') { // Stop loading this._name = ''; this.abortLoading(); return { data: icon, }; } // Invalid icon? let iconName; if (typeof icon !== 'string' || (iconName = stringToIcon(icon, false, true)) === null) { this.abortLoading(); return null; } // Load icon const data = getIconData(iconName); if (!data) { // Icon data is not available if (!this._loadingIcon || this._loadingIcon.name !== icon) { // New icon to load this.abortLoading(); this._name = ''; if (data !== null) { // Icon was not loaded this._loadingIcon = { name: icon, abort: loadIcons([iconName], () => { this.counter++; }), }; } } return null; } // Icon data is available this.abortLoading(); if (this._name !== icon) { this._name = icon; if (onload) { onload(icon); } } // Add classes const classes = ['iconify']; if (iconName.prefix !== '') { classes.push('iconify--' + iconName.prefix); } if (iconName.provider !== '') { classes.push('iconify--' + iconName.provider); } return { data, classes }; }, }, // Render icon render() { // Re-render when counter changes this.counter; const props = this.$attrs; // Get icon data const icon = this.iconMounted ? this.getIcon(props.icon, props.onLoad) : null; // Validate icon object if (!icon) { return render(emptyIcon, props); } // Add classes let newProps = props; if (icon.classes) { newProps = { ...props, class: (typeof props['class'] === 'string' ? props['class'] + ' ' : '') + icon.classes.join(' '), }; } // Render icon return render({ ...defaultIconProps, ...icon.data, }, newProps); }, }); /** * Internal API */ const _api = { getAPIConfig, setAPIModule, sendAPIQuery, setFetch, getFetch, listAPIProviders, }; exports.Icon = Icon; exports._api = _api; exports.addAPIProvider = addAPIProvider; exports.addCollection = addCollection; exports.addIcon = addIcon; exports.buildIcon = iconToSVG; exports.calculateSize = calculateSize; exports.disableCache = disableCache; exports.enableCache = enableCache; exports.getIcon = getIcon; exports.iconExists = iconExists; exports.listIcons = listIcons; exports.loadIcon = loadIcon; exports.loadIcons = loadIcons; exports.replaceIDs = replaceIDs; }));