iconify.js 56 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980
  1. (function (global, factory) {
  2. typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vue')) :
  3. typeof define === 'function' && define.amd ? define(['exports', 'vue'], factory) :
  4. (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Icon = {}, global.Vue));
  5. })(this, (function (exports, vue) { 'use strict';
  6. const matchIconName = /^[a-z0-9]+(-[a-z0-9]+)*$/;
  7. const stringToIcon = (value, validate, allowSimpleName, provider = "") => {
  8. const colonSeparated = value.split(":");
  9. if (value.slice(0, 1) === "@") {
  10. if (colonSeparated.length < 2 || colonSeparated.length > 3) {
  11. return null;
  12. }
  13. provider = colonSeparated.shift().slice(1);
  14. }
  15. if (colonSeparated.length > 3 || !colonSeparated.length) {
  16. return null;
  17. }
  18. if (colonSeparated.length > 1) {
  19. const name2 = colonSeparated.pop();
  20. const prefix = colonSeparated.pop();
  21. const result = {
  22. // Allow provider without '@': "provider:prefix:name"
  23. provider: colonSeparated.length > 0 ? colonSeparated[0] : provider,
  24. prefix,
  25. name: name2
  26. };
  27. return validate && !validateIconName(result) ? null : result;
  28. }
  29. const name = colonSeparated[0];
  30. const dashSeparated = name.split("-");
  31. if (dashSeparated.length > 1) {
  32. const result = {
  33. provider,
  34. prefix: dashSeparated.shift(),
  35. name: dashSeparated.join("-")
  36. };
  37. return validate && !validateIconName(result) ? null : result;
  38. }
  39. if (allowSimpleName && provider === "") {
  40. const result = {
  41. provider,
  42. prefix: "",
  43. name
  44. };
  45. return validate && !validateIconName(result, allowSimpleName) ? null : result;
  46. }
  47. return null;
  48. };
  49. const validateIconName = (icon, allowSimpleName) => {
  50. if (!icon) {
  51. return false;
  52. }
  53. return !!((icon.provider === "" || icon.provider.match(matchIconName)) && (allowSimpleName && icon.prefix === "" || icon.prefix.match(matchIconName)) && icon.name.match(matchIconName));
  54. };
  55. const defaultIconDimensions = Object.freeze(
  56. {
  57. left: 0,
  58. top: 0,
  59. width: 16,
  60. height: 16
  61. }
  62. );
  63. const defaultIconTransformations = Object.freeze({
  64. rotate: 0,
  65. vFlip: false,
  66. hFlip: false
  67. });
  68. const defaultIconProps = Object.freeze({
  69. ...defaultIconDimensions,
  70. ...defaultIconTransformations
  71. });
  72. const defaultExtendedIconProps = Object.freeze({
  73. ...defaultIconProps,
  74. body: "",
  75. hidden: false
  76. });
  77. function mergeIconTransformations(obj1, obj2) {
  78. const result = {};
  79. if (!obj1.hFlip !== !obj2.hFlip) {
  80. result.hFlip = true;
  81. }
  82. if (!obj1.vFlip !== !obj2.vFlip) {
  83. result.vFlip = true;
  84. }
  85. const rotate = ((obj1.rotate || 0) + (obj2.rotate || 0)) % 4;
  86. if (rotate) {
  87. result.rotate = rotate;
  88. }
  89. return result;
  90. }
  91. function mergeIconData(parent, child) {
  92. const result = mergeIconTransformations(parent, child);
  93. for (const key in defaultExtendedIconProps) {
  94. if (key in defaultIconTransformations) {
  95. if (key in parent && !(key in result)) {
  96. result[key] = defaultIconTransformations[key];
  97. }
  98. } else if (key in child) {
  99. result[key] = child[key];
  100. } else if (key in parent) {
  101. result[key] = parent[key];
  102. }
  103. }
  104. return result;
  105. }
  106. function getIconsTree(data, names) {
  107. const icons = data.icons;
  108. const aliases = data.aliases || /* @__PURE__ */ Object.create(null);
  109. const resolved = /* @__PURE__ */ Object.create(null);
  110. function resolve(name) {
  111. if (icons[name]) {
  112. return resolved[name] = [];
  113. }
  114. if (!(name in resolved)) {
  115. resolved[name] = null;
  116. const parent = aliases[name] && aliases[name].parent;
  117. const value = parent && resolve(parent);
  118. if (value) {
  119. resolved[name] = [parent].concat(value);
  120. }
  121. }
  122. return resolved[name];
  123. }
  124. (names || Object.keys(icons).concat(Object.keys(aliases))).forEach(resolve);
  125. return resolved;
  126. }
  127. function internalGetIconData(data, name, tree) {
  128. const icons = data.icons;
  129. const aliases = data.aliases || /* @__PURE__ */ Object.create(null);
  130. let currentProps = {};
  131. function parse(name2) {
  132. currentProps = mergeIconData(
  133. icons[name2] || aliases[name2],
  134. currentProps
  135. );
  136. }
  137. parse(name);
  138. tree.forEach(parse);
  139. return mergeIconData(data, currentProps);
  140. }
  141. function parseIconSet(data, callback) {
  142. const names = [];
  143. if (typeof data !== "object" || typeof data.icons !== "object") {
  144. return names;
  145. }
  146. if (data.not_found instanceof Array) {
  147. data.not_found.forEach((name) => {
  148. callback(name, null);
  149. names.push(name);
  150. });
  151. }
  152. const tree = getIconsTree(data);
  153. for (const name in tree) {
  154. const item = tree[name];
  155. if (item) {
  156. callback(name, internalGetIconData(data, name, item));
  157. names.push(name);
  158. }
  159. }
  160. return names;
  161. }
  162. const optionalPropertyDefaults = {
  163. provider: "",
  164. aliases: {},
  165. not_found: {},
  166. ...defaultIconDimensions
  167. };
  168. function checkOptionalProps(item, defaults) {
  169. for (const prop in defaults) {
  170. if (prop in item && typeof item[prop] !== typeof defaults[prop]) {
  171. return false;
  172. }
  173. }
  174. return true;
  175. }
  176. function quicklyValidateIconSet(obj) {
  177. if (typeof obj !== "object" || obj === null) {
  178. return null;
  179. }
  180. const data = obj;
  181. if (typeof data.prefix !== "string" || !obj.icons || typeof obj.icons !== "object") {
  182. return null;
  183. }
  184. if (!checkOptionalProps(obj, optionalPropertyDefaults)) {
  185. return null;
  186. }
  187. const icons = data.icons;
  188. for (const name in icons) {
  189. const icon = icons[name];
  190. if (!name.match(matchIconName) || typeof icon.body !== "string" || !checkOptionalProps(
  191. icon,
  192. defaultExtendedIconProps
  193. )) {
  194. return null;
  195. }
  196. }
  197. const aliases = data.aliases || /* @__PURE__ */ Object.create(null);
  198. for (const name in aliases) {
  199. const icon = aliases[name];
  200. const parent = icon.parent;
  201. if (!name.match(matchIconName) || typeof parent !== "string" || !icons[parent] && !aliases[parent] || !checkOptionalProps(
  202. icon,
  203. defaultExtendedIconProps
  204. )) {
  205. return null;
  206. }
  207. }
  208. return data;
  209. }
  210. const dataStorage = /* @__PURE__ */ Object.create(null);
  211. function newStorage(provider, prefix) {
  212. return {
  213. provider,
  214. prefix,
  215. icons: /* @__PURE__ */ Object.create(null),
  216. missing: /* @__PURE__ */ new Set()
  217. };
  218. }
  219. function getStorage(provider, prefix) {
  220. const providerStorage = dataStorage[provider] || (dataStorage[provider] = /* @__PURE__ */ Object.create(null));
  221. return providerStorage[prefix] || (providerStorage[prefix] = newStorage(provider, prefix));
  222. }
  223. function addIconSet(storage, data) {
  224. if (!quicklyValidateIconSet(data)) {
  225. return [];
  226. }
  227. return parseIconSet(data, (name, icon) => {
  228. if (icon) {
  229. storage.icons[name] = icon;
  230. } else {
  231. storage.missing.add(name);
  232. }
  233. });
  234. }
  235. function addIconToStorage(storage, name, icon) {
  236. try {
  237. if (typeof icon.body === "string") {
  238. storage.icons[name] = { ...icon };
  239. return true;
  240. }
  241. } catch (err) {
  242. }
  243. return false;
  244. }
  245. function listIcons(provider, prefix) {
  246. let allIcons = [];
  247. const providers = typeof provider === "string" ? [provider] : Object.keys(dataStorage);
  248. providers.forEach((provider2) => {
  249. const prefixes = typeof provider2 === "string" && typeof prefix === "string" ? [prefix] : Object.keys(dataStorage[provider2] || {});
  250. prefixes.forEach((prefix2) => {
  251. const storage = getStorage(provider2, prefix2);
  252. allIcons = allIcons.concat(
  253. Object.keys(storage.icons).map(
  254. (name) => (provider2 !== "" ? "@" + provider2 + ":" : "") + prefix2 + ":" + name
  255. )
  256. );
  257. });
  258. });
  259. return allIcons;
  260. }
  261. let simpleNames = false;
  262. function allowSimpleNames(allow) {
  263. if (typeof allow === "boolean") {
  264. simpleNames = allow;
  265. }
  266. return simpleNames;
  267. }
  268. function getIconData(name) {
  269. const icon = typeof name === "string" ? stringToIcon(name, true, simpleNames) : name;
  270. if (icon) {
  271. const storage = getStorage(icon.provider, icon.prefix);
  272. const iconName = icon.name;
  273. return storage.icons[iconName] || (storage.missing.has(iconName) ? null : void 0);
  274. }
  275. }
  276. function addIcon(name, data) {
  277. const icon = stringToIcon(name, true, simpleNames);
  278. if (!icon) {
  279. return false;
  280. }
  281. const storage = getStorage(icon.provider, icon.prefix);
  282. return addIconToStorage(storage, icon.name, data);
  283. }
  284. function addCollection(data, provider) {
  285. if (typeof data !== "object") {
  286. return false;
  287. }
  288. if (typeof provider !== "string") {
  289. provider = data.provider || "";
  290. }
  291. if (simpleNames && !provider && !data.prefix) {
  292. let added = false;
  293. if (quicklyValidateIconSet(data)) {
  294. data.prefix = "";
  295. parseIconSet(data, (name, icon) => {
  296. if (icon && addIcon(name, icon)) {
  297. added = true;
  298. }
  299. });
  300. }
  301. return added;
  302. }
  303. const prefix = data.prefix;
  304. if (!validateIconName({
  305. provider,
  306. prefix,
  307. name: "a"
  308. })) {
  309. return false;
  310. }
  311. const storage = getStorage(provider, prefix);
  312. return !!addIconSet(storage, data);
  313. }
  314. function iconExists(name) {
  315. return !!getIconData(name);
  316. }
  317. function getIcon(name) {
  318. const result = getIconData(name);
  319. return result ? {
  320. ...defaultIconProps,
  321. ...result
  322. } : null;
  323. }
  324. const defaultIconSizeCustomisations = Object.freeze({
  325. width: null,
  326. height: null
  327. });
  328. const defaultIconCustomisations = Object.freeze({
  329. // Dimensions
  330. ...defaultIconSizeCustomisations,
  331. // Transformations
  332. ...defaultIconTransformations
  333. });
  334. const unitsSplit = /(-?[0-9.]*[0-9]+[0-9.]*)/g;
  335. const unitsTest = /^-?[0-9.]*[0-9]+[0-9.]*$/g;
  336. function calculateSize(size, ratio, precision) {
  337. if (ratio === 1) {
  338. return size;
  339. }
  340. precision = precision || 100;
  341. if (typeof size === "number") {
  342. return Math.ceil(size * ratio * precision) / precision;
  343. }
  344. if (typeof size !== "string") {
  345. return size;
  346. }
  347. const oldParts = size.split(unitsSplit);
  348. if (oldParts === null || !oldParts.length) {
  349. return size;
  350. }
  351. const newParts = [];
  352. let code = oldParts.shift();
  353. let isNumber = unitsTest.test(code);
  354. while (true) {
  355. if (isNumber) {
  356. const num = parseFloat(code);
  357. if (isNaN(num)) {
  358. newParts.push(code);
  359. } else {
  360. newParts.push(Math.ceil(num * ratio * precision) / precision);
  361. }
  362. } else {
  363. newParts.push(code);
  364. }
  365. code = oldParts.shift();
  366. if (code === void 0) {
  367. return newParts.join("");
  368. }
  369. isNumber = !isNumber;
  370. }
  371. }
  372. const isUnsetKeyword = (value) => value === "unset" || value === "undefined" || value === "none";
  373. function iconToSVG(icon, customisations) {
  374. const fullIcon = {
  375. ...defaultIconProps,
  376. ...icon
  377. };
  378. const fullCustomisations = {
  379. ...defaultIconCustomisations,
  380. ...customisations
  381. };
  382. const box = {
  383. left: fullIcon.left,
  384. top: fullIcon.top,
  385. width: fullIcon.width,
  386. height: fullIcon.height
  387. };
  388. let body = fullIcon.body;
  389. [fullIcon, fullCustomisations].forEach((props) => {
  390. const transformations = [];
  391. const hFlip = props.hFlip;
  392. const vFlip = props.vFlip;
  393. let rotation = props.rotate;
  394. if (hFlip) {
  395. if (vFlip) {
  396. rotation += 2;
  397. } else {
  398. transformations.push(
  399. "translate(" + (box.width + box.left).toString() + " " + (0 - box.top).toString() + ")"
  400. );
  401. transformations.push("scale(-1 1)");
  402. box.top = box.left = 0;
  403. }
  404. } else if (vFlip) {
  405. transformations.push(
  406. "translate(" + (0 - box.left).toString() + " " + (box.height + box.top).toString() + ")"
  407. );
  408. transformations.push("scale(1 -1)");
  409. box.top = box.left = 0;
  410. }
  411. let tempValue;
  412. if (rotation < 0) {
  413. rotation -= Math.floor(rotation / 4) * 4;
  414. }
  415. rotation = rotation % 4;
  416. switch (rotation) {
  417. case 1:
  418. tempValue = box.height / 2 + box.top;
  419. transformations.unshift(
  420. "rotate(90 " + tempValue.toString() + " " + tempValue.toString() + ")"
  421. );
  422. break;
  423. case 2:
  424. transformations.unshift(
  425. "rotate(180 " + (box.width / 2 + box.left).toString() + " " + (box.height / 2 + box.top).toString() + ")"
  426. );
  427. break;
  428. case 3:
  429. tempValue = box.width / 2 + box.left;
  430. transformations.unshift(
  431. "rotate(-90 " + tempValue.toString() + " " + tempValue.toString() + ")"
  432. );
  433. break;
  434. }
  435. if (rotation % 2 === 1) {
  436. if (box.left !== box.top) {
  437. tempValue = box.left;
  438. box.left = box.top;
  439. box.top = tempValue;
  440. }
  441. if (box.width !== box.height) {
  442. tempValue = box.width;
  443. box.width = box.height;
  444. box.height = tempValue;
  445. }
  446. }
  447. if (transformations.length) {
  448. body = '<g transform="' + transformations.join(" ") + '">' + body + "</g>";
  449. }
  450. });
  451. const customisationsWidth = fullCustomisations.width;
  452. const customisationsHeight = fullCustomisations.height;
  453. const boxWidth = box.width;
  454. const boxHeight = box.height;
  455. let width;
  456. let height;
  457. if (customisationsWidth === null) {
  458. height = customisationsHeight === null ? "1em" : customisationsHeight === "auto" ? boxHeight : customisationsHeight;
  459. width = calculateSize(height, boxWidth / boxHeight);
  460. } else {
  461. width = customisationsWidth === "auto" ? boxWidth : customisationsWidth;
  462. height = customisationsHeight === null ? calculateSize(width, boxHeight / boxWidth) : customisationsHeight === "auto" ? boxHeight : customisationsHeight;
  463. }
  464. const attributes = {};
  465. const setAttr = (prop, value) => {
  466. if (!isUnsetKeyword(value)) {
  467. attributes[prop] = value.toString();
  468. }
  469. };
  470. setAttr("width", width);
  471. setAttr("height", height);
  472. attributes.viewBox = box.left.toString() + " " + box.top.toString() + " " + boxWidth.toString() + " " + boxHeight.toString();
  473. return {
  474. attributes,
  475. body
  476. };
  477. }
  478. const regex = /\sid="(\S+)"/g;
  479. const randomPrefix = "IconifyId" + Date.now().toString(16) + (Math.random() * 16777216 | 0).toString(16);
  480. let counter = 0;
  481. function replaceIDs(body, prefix = randomPrefix) {
  482. const ids = [];
  483. let match;
  484. while (match = regex.exec(body)) {
  485. ids.push(match[1]);
  486. }
  487. if (!ids.length) {
  488. return body;
  489. }
  490. const suffix = "suffix" + (Math.random() * 16777216 | Date.now()).toString(16);
  491. ids.forEach((id) => {
  492. const newID = typeof prefix === "function" ? prefix(id) : prefix + (counter++).toString();
  493. const escapedID = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  494. body = body.replace(
  495. // Allowed characters before id: [#;"]
  496. // Allowed characters after id: [)"], .[a-z]
  497. new RegExp('([#;"])(' + escapedID + ')([")]|\\.[a-z])', "g"),
  498. "$1" + newID + suffix + "$3"
  499. );
  500. });
  501. body = body.replace(new RegExp(suffix, "g"), "");
  502. return body;
  503. }
  504. const storage = /* @__PURE__ */ Object.create(null);
  505. function setAPIModule(provider, item) {
  506. storage[provider] = item;
  507. }
  508. function getAPIModule(provider) {
  509. return storage[provider] || storage[""];
  510. }
  511. function createAPIConfig(source) {
  512. let resources;
  513. if (typeof source.resources === "string") {
  514. resources = [source.resources];
  515. } else {
  516. resources = source.resources;
  517. if (!(resources instanceof Array) || !resources.length) {
  518. return null;
  519. }
  520. }
  521. const result = {
  522. // API hosts
  523. resources,
  524. // Root path
  525. path: source.path || "/",
  526. // URL length limit
  527. maxURL: source.maxURL || 500,
  528. // Timeout before next host is used.
  529. rotate: source.rotate || 750,
  530. // Timeout before failing query.
  531. timeout: source.timeout || 5e3,
  532. // Randomise default API end point.
  533. random: source.random === true,
  534. // Start index
  535. index: source.index || 0,
  536. // Receive data after time out (used if time out kicks in first, then API module sends data anyway).
  537. dataAfterTimeout: source.dataAfterTimeout !== false
  538. };
  539. return result;
  540. }
  541. const configStorage = /* @__PURE__ */ Object.create(null);
  542. const fallBackAPISources = [
  543. "https://api.simplesvg.com",
  544. "https://api.unisvg.com"
  545. ];
  546. const fallBackAPI = [];
  547. while (fallBackAPISources.length > 0) {
  548. if (fallBackAPISources.length === 1) {
  549. fallBackAPI.push(fallBackAPISources.shift());
  550. } else {
  551. if (Math.random() > 0.5) {
  552. fallBackAPI.push(fallBackAPISources.shift());
  553. } else {
  554. fallBackAPI.push(fallBackAPISources.pop());
  555. }
  556. }
  557. }
  558. configStorage[""] = createAPIConfig({
  559. resources: ["https://api.iconify.design"].concat(fallBackAPI)
  560. });
  561. function addAPIProvider(provider, customConfig) {
  562. const config = createAPIConfig(customConfig);
  563. if (config === null) {
  564. return false;
  565. }
  566. configStorage[provider] = config;
  567. return true;
  568. }
  569. function getAPIConfig(provider) {
  570. return configStorage[provider];
  571. }
  572. function listAPIProviders() {
  573. return Object.keys(configStorage);
  574. }
  575. const detectFetch = () => {
  576. let callback;
  577. try {
  578. callback = fetch;
  579. if (typeof callback === "function") {
  580. return callback;
  581. }
  582. } catch (err) {
  583. }
  584. };
  585. let fetchModule = detectFetch();
  586. function setFetch(fetch2) {
  587. fetchModule = fetch2;
  588. }
  589. function getFetch() {
  590. return fetchModule;
  591. }
  592. function calculateMaxLength(provider, prefix) {
  593. const config = getAPIConfig(provider);
  594. if (!config) {
  595. return 0;
  596. }
  597. let result;
  598. if (!config.maxURL) {
  599. result = 0;
  600. } else {
  601. let maxHostLength = 0;
  602. config.resources.forEach((item) => {
  603. const host = item;
  604. maxHostLength = Math.max(maxHostLength, host.length);
  605. });
  606. const url = prefix + ".json?icons=";
  607. result = config.maxURL - maxHostLength - config.path.length - url.length;
  608. }
  609. return result;
  610. }
  611. function shouldAbort(status) {
  612. return status === 404;
  613. }
  614. const prepare = (provider, prefix, icons) => {
  615. const results = [];
  616. const maxLength = calculateMaxLength(provider, prefix);
  617. const type = "icons";
  618. let item = {
  619. type,
  620. provider,
  621. prefix,
  622. icons: []
  623. };
  624. let length = 0;
  625. icons.forEach((name, index) => {
  626. length += name.length + 1;
  627. if (length >= maxLength && index > 0) {
  628. results.push(item);
  629. item = {
  630. type,
  631. provider,
  632. prefix,
  633. icons: []
  634. };
  635. length = name.length;
  636. }
  637. item.icons.push(name);
  638. });
  639. results.push(item);
  640. return results;
  641. };
  642. function getPath(provider) {
  643. if (typeof provider === "string") {
  644. const config = getAPIConfig(provider);
  645. if (config) {
  646. return config.path;
  647. }
  648. }
  649. return "/";
  650. }
  651. const send = (host, params, callback) => {
  652. if (!fetchModule) {
  653. callback("abort", 424);
  654. return;
  655. }
  656. let path = getPath(params.provider);
  657. switch (params.type) {
  658. case "icons": {
  659. const prefix = params.prefix;
  660. const icons = params.icons;
  661. const iconsList = icons.join(",");
  662. const urlParams = new URLSearchParams({
  663. icons: iconsList
  664. });
  665. path += prefix + ".json?" + urlParams.toString();
  666. break;
  667. }
  668. case "custom": {
  669. const uri = params.uri;
  670. path += uri.slice(0, 1) === "/" ? uri.slice(1) : uri;
  671. break;
  672. }
  673. default:
  674. callback("abort", 400);
  675. return;
  676. }
  677. let defaultError = 503;
  678. fetchModule(host + path).then((response) => {
  679. const status = response.status;
  680. if (status !== 200) {
  681. setTimeout(() => {
  682. callback(shouldAbort(status) ? "abort" : "next", status);
  683. });
  684. return;
  685. }
  686. defaultError = 501;
  687. return response.json();
  688. }).then((data) => {
  689. if (typeof data !== "object" || data === null) {
  690. setTimeout(() => {
  691. if (data === 404) {
  692. callback("abort", data);
  693. } else {
  694. callback("next", defaultError);
  695. }
  696. });
  697. return;
  698. }
  699. setTimeout(() => {
  700. callback("success", data);
  701. });
  702. }).catch(() => {
  703. callback("next", defaultError);
  704. });
  705. };
  706. const fetchAPIModule = {
  707. prepare,
  708. send
  709. };
  710. function sortIcons(icons) {
  711. const result = {
  712. loaded: [],
  713. missing: [],
  714. pending: []
  715. };
  716. const storage = /* @__PURE__ */ Object.create(null);
  717. icons.sort((a, b) => {
  718. if (a.provider !== b.provider) {
  719. return a.provider.localeCompare(b.provider);
  720. }
  721. if (a.prefix !== b.prefix) {
  722. return a.prefix.localeCompare(b.prefix);
  723. }
  724. return a.name.localeCompare(b.name);
  725. });
  726. let lastIcon = {
  727. provider: "",
  728. prefix: "",
  729. name: ""
  730. };
  731. icons.forEach((icon) => {
  732. if (lastIcon.name === icon.name && lastIcon.prefix === icon.prefix && lastIcon.provider === icon.provider) {
  733. return;
  734. }
  735. lastIcon = icon;
  736. const provider = icon.provider;
  737. const prefix = icon.prefix;
  738. const name = icon.name;
  739. const providerStorage = storage[provider] || (storage[provider] = /* @__PURE__ */ Object.create(null));
  740. const localStorage = providerStorage[prefix] || (providerStorage[prefix] = getStorage(provider, prefix));
  741. let list;
  742. if (name in localStorage.icons) {
  743. list = result.loaded;
  744. } else if (prefix === "" || localStorage.missing.has(name)) {
  745. list = result.missing;
  746. } else {
  747. list = result.pending;
  748. }
  749. const item = {
  750. provider,
  751. prefix,
  752. name
  753. };
  754. list.push(item);
  755. });
  756. return result;
  757. }
  758. function removeCallback(storages, id) {
  759. storages.forEach((storage) => {
  760. const items = storage.loaderCallbacks;
  761. if (items) {
  762. storage.loaderCallbacks = items.filter((row) => row.id !== id);
  763. }
  764. });
  765. }
  766. function updateCallbacks(storage) {
  767. if (!storage.pendingCallbacksFlag) {
  768. storage.pendingCallbacksFlag = true;
  769. setTimeout(() => {
  770. storage.pendingCallbacksFlag = false;
  771. const items = storage.loaderCallbacks ? storage.loaderCallbacks.slice(0) : [];
  772. if (!items.length) {
  773. return;
  774. }
  775. let hasPending = false;
  776. const provider = storage.provider;
  777. const prefix = storage.prefix;
  778. items.forEach((item) => {
  779. const icons = item.icons;
  780. const oldLength = icons.pending.length;
  781. icons.pending = icons.pending.filter((icon) => {
  782. if (icon.prefix !== prefix) {
  783. return true;
  784. }
  785. const name = icon.name;
  786. if (storage.icons[name]) {
  787. icons.loaded.push({
  788. provider,
  789. prefix,
  790. name
  791. });
  792. } else if (storage.missing.has(name)) {
  793. icons.missing.push({
  794. provider,
  795. prefix,
  796. name
  797. });
  798. } else {
  799. hasPending = true;
  800. return true;
  801. }
  802. return false;
  803. });
  804. if (icons.pending.length !== oldLength) {
  805. if (!hasPending) {
  806. removeCallback([storage], item.id);
  807. }
  808. item.callback(
  809. icons.loaded.slice(0),
  810. icons.missing.slice(0),
  811. icons.pending.slice(0),
  812. item.abort
  813. );
  814. }
  815. });
  816. });
  817. }
  818. }
  819. let idCounter = 0;
  820. function storeCallback(callback, icons, pendingSources) {
  821. const id = idCounter++;
  822. const abort = removeCallback.bind(null, pendingSources, id);
  823. if (!icons.pending.length) {
  824. return abort;
  825. }
  826. const item = {
  827. id,
  828. icons,
  829. callback,
  830. abort
  831. };
  832. pendingSources.forEach((storage) => {
  833. (storage.loaderCallbacks || (storage.loaderCallbacks = [])).push(item);
  834. });
  835. return abort;
  836. }
  837. function listToIcons(list, validate = true, simpleNames = false) {
  838. const result = [];
  839. list.forEach((item) => {
  840. const icon = typeof item === "string" ? stringToIcon(item, validate, simpleNames) : item;
  841. if (icon) {
  842. result.push(icon);
  843. }
  844. });
  845. return result;
  846. }
  847. // src/config.ts
  848. var defaultConfig = {
  849. resources: [],
  850. index: 0,
  851. timeout: 2e3,
  852. rotate: 750,
  853. random: false,
  854. dataAfterTimeout: false
  855. };
  856. // src/query.ts
  857. function sendQuery(config, payload, query, done) {
  858. const resourcesCount = config.resources.length;
  859. const startIndex = config.random ? Math.floor(Math.random() * resourcesCount) : config.index;
  860. let resources;
  861. if (config.random) {
  862. let list = config.resources.slice(0);
  863. resources = [];
  864. while (list.length > 1) {
  865. const nextIndex = Math.floor(Math.random() * list.length);
  866. resources.push(list[nextIndex]);
  867. list = list.slice(0, nextIndex).concat(list.slice(nextIndex + 1));
  868. }
  869. resources = resources.concat(list);
  870. } else {
  871. resources = config.resources.slice(startIndex).concat(config.resources.slice(0, startIndex));
  872. }
  873. const startTime = Date.now();
  874. let status = "pending";
  875. let queriesSent = 0;
  876. let lastError;
  877. let timer = null;
  878. let queue = [];
  879. let doneCallbacks = [];
  880. if (typeof done === "function") {
  881. doneCallbacks.push(done);
  882. }
  883. function resetTimer() {
  884. if (timer) {
  885. clearTimeout(timer);
  886. timer = null;
  887. }
  888. }
  889. function abort() {
  890. if (status === "pending") {
  891. status = "aborted";
  892. }
  893. resetTimer();
  894. queue.forEach((item) => {
  895. if (item.status === "pending") {
  896. item.status = "aborted";
  897. }
  898. });
  899. queue = [];
  900. }
  901. function subscribe(callback, overwrite) {
  902. if (overwrite) {
  903. doneCallbacks = [];
  904. }
  905. if (typeof callback === "function") {
  906. doneCallbacks.push(callback);
  907. }
  908. }
  909. function getQueryStatus() {
  910. return {
  911. startTime,
  912. payload,
  913. status,
  914. queriesSent,
  915. queriesPending: queue.length,
  916. subscribe,
  917. abort
  918. };
  919. }
  920. function failQuery() {
  921. status = "failed";
  922. doneCallbacks.forEach((callback) => {
  923. callback(void 0, lastError);
  924. });
  925. }
  926. function clearQueue() {
  927. queue.forEach((item) => {
  928. if (item.status === "pending") {
  929. item.status = "aborted";
  930. }
  931. });
  932. queue = [];
  933. }
  934. function moduleResponse(item, response, data) {
  935. const isError = response !== "success";
  936. queue = queue.filter((queued) => queued !== item);
  937. switch (status) {
  938. case "pending":
  939. break;
  940. case "failed":
  941. if (isError || !config.dataAfterTimeout) {
  942. return;
  943. }
  944. break;
  945. default:
  946. return;
  947. }
  948. if (response === "abort") {
  949. lastError = data;
  950. failQuery();
  951. return;
  952. }
  953. if (isError) {
  954. lastError = data;
  955. if (!queue.length) {
  956. if (!resources.length) {
  957. failQuery();
  958. } else {
  959. execNext();
  960. }
  961. }
  962. return;
  963. }
  964. resetTimer();
  965. clearQueue();
  966. if (!config.random) {
  967. const index = config.resources.indexOf(item.resource);
  968. if (index !== -1 && index !== config.index) {
  969. config.index = index;
  970. }
  971. }
  972. status = "completed";
  973. doneCallbacks.forEach((callback) => {
  974. callback(data);
  975. });
  976. }
  977. function execNext() {
  978. if (status !== "pending") {
  979. return;
  980. }
  981. resetTimer();
  982. const resource = resources.shift();
  983. if (resource === void 0) {
  984. if (queue.length) {
  985. timer = setTimeout(() => {
  986. resetTimer();
  987. if (status === "pending") {
  988. clearQueue();
  989. failQuery();
  990. }
  991. }, config.timeout);
  992. return;
  993. }
  994. failQuery();
  995. return;
  996. }
  997. const item = {
  998. status: "pending",
  999. resource,
  1000. callback: (status2, data) => {
  1001. moduleResponse(item, status2, data);
  1002. }
  1003. };
  1004. queue.push(item);
  1005. queriesSent++;
  1006. timer = setTimeout(execNext, config.rotate);
  1007. query(resource, payload, item.callback);
  1008. }
  1009. setTimeout(execNext);
  1010. return getQueryStatus;
  1011. }
  1012. // src/index.ts
  1013. function initRedundancy(cfg) {
  1014. const config = {
  1015. ...defaultConfig,
  1016. ...cfg
  1017. };
  1018. let queries = [];
  1019. function cleanup() {
  1020. queries = queries.filter((item) => item().status === "pending");
  1021. }
  1022. function query(payload, queryCallback, doneCallback) {
  1023. const query2 = sendQuery(
  1024. config,
  1025. payload,
  1026. queryCallback,
  1027. (data, error) => {
  1028. cleanup();
  1029. if (doneCallback) {
  1030. doneCallback(data, error);
  1031. }
  1032. }
  1033. );
  1034. queries.push(query2);
  1035. return query2;
  1036. }
  1037. function find(callback) {
  1038. return queries.find((value) => {
  1039. return callback(value);
  1040. }) || null;
  1041. }
  1042. const instance = {
  1043. query,
  1044. find,
  1045. setIndex: (index) => {
  1046. config.index = index;
  1047. },
  1048. getIndex: () => config.index,
  1049. cleanup
  1050. };
  1051. return instance;
  1052. }
  1053. function emptyCallback$1() {
  1054. }
  1055. const redundancyCache = /* @__PURE__ */ Object.create(null);
  1056. function getRedundancyCache(provider) {
  1057. if (!redundancyCache[provider]) {
  1058. const config = getAPIConfig(provider);
  1059. if (!config) {
  1060. return;
  1061. }
  1062. const redundancy = initRedundancy(config);
  1063. const cachedReundancy = {
  1064. config,
  1065. redundancy
  1066. };
  1067. redundancyCache[provider] = cachedReundancy;
  1068. }
  1069. return redundancyCache[provider];
  1070. }
  1071. function sendAPIQuery(target, query, callback) {
  1072. let redundancy;
  1073. let send;
  1074. if (typeof target === "string") {
  1075. const api = getAPIModule(target);
  1076. if (!api) {
  1077. callback(void 0, 424);
  1078. return emptyCallback$1;
  1079. }
  1080. send = api.send;
  1081. const cached = getRedundancyCache(target);
  1082. if (cached) {
  1083. redundancy = cached.redundancy;
  1084. }
  1085. } else {
  1086. const config = createAPIConfig(target);
  1087. if (config) {
  1088. redundancy = initRedundancy(config);
  1089. const moduleKey = target.resources ? target.resources[0] : "";
  1090. const api = getAPIModule(moduleKey);
  1091. if (api) {
  1092. send = api.send;
  1093. }
  1094. }
  1095. }
  1096. if (!redundancy || !send) {
  1097. callback(void 0, 424);
  1098. return emptyCallback$1;
  1099. }
  1100. return redundancy.query(query, send, callback)().abort;
  1101. }
  1102. const browserCacheVersion = "iconify2";
  1103. const browserCachePrefix = "iconify";
  1104. const browserCacheCountKey = browserCachePrefix + "-count";
  1105. const browserCacheVersionKey = browserCachePrefix + "-version";
  1106. const browserStorageHour = 36e5;
  1107. const browserStorageCacheExpiration = 168;
  1108. function getStoredItem(func, key) {
  1109. try {
  1110. return func.getItem(key);
  1111. } catch (err) {
  1112. }
  1113. }
  1114. function setStoredItem(func, key, value) {
  1115. try {
  1116. func.setItem(key, value);
  1117. return true;
  1118. } catch (err) {
  1119. }
  1120. }
  1121. function removeStoredItem(func, key) {
  1122. try {
  1123. func.removeItem(key);
  1124. } catch (err) {
  1125. }
  1126. }
  1127. function setBrowserStorageItemsCount(storage, value) {
  1128. return setStoredItem(storage, browserCacheCountKey, value.toString());
  1129. }
  1130. function getBrowserStorageItemsCount(storage) {
  1131. return parseInt(getStoredItem(storage, browserCacheCountKey)) || 0;
  1132. }
  1133. const browserStorageConfig = {
  1134. local: true,
  1135. session: true
  1136. };
  1137. const browserStorageEmptyItems = {
  1138. local: /* @__PURE__ */ new Set(),
  1139. session: /* @__PURE__ */ new Set()
  1140. };
  1141. let browserStorageStatus = false;
  1142. function setBrowserStorageStatus(status) {
  1143. browserStorageStatus = status;
  1144. }
  1145. let _window = typeof window === "undefined" ? {} : window;
  1146. function getBrowserStorage(key) {
  1147. const attr = key + "Storage";
  1148. try {
  1149. if (_window && _window[attr] && typeof _window[attr].length === "number") {
  1150. return _window[attr];
  1151. }
  1152. } catch (err) {
  1153. }
  1154. browserStorageConfig[key] = false;
  1155. }
  1156. function iterateBrowserStorage(key, callback) {
  1157. const func = getBrowserStorage(key);
  1158. if (!func) {
  1159. return;
  1160. }
  1161. const version = getStoredItem(func, browserCacheVersionKey);
  1162. if (version !== browserCacheVersion) {
  1163. if (version) {
  1164. const total2 = getBrowserStorageItemsCount(func);
  1165. for (let i = 0; i < total2; i++) {
  1166. removeStoredItem(func, browserCachePrefix + i.toString());
  1167. }
  1168. }
  1169. setStoredItem(func, browserCacheVersionKey, browserCacheVersion);
  1170. setBrowserStorageItemsCount(func, 0);
  1171. return;
  1172. }
  1173. const minTime = Math.floor(Date.now() / browserStorageHour) - browserStorageCacheExpiration;
  1174. const parseItem = (index) => {
  1175. const name = browserCachePrefix + index.toString();
  1176. const item = getStoredItem(func, name);
  1177. if (typeof item !== "string") {
  1178. return;
  1179. }
  1180. try {
  1181. const data = JSON.parse(item);
  1182. 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
  1183. callback(data, index)) {
  1184. return true;
  1185. }
  1186. } catch (err) {
  1187. }
  1188. removeStoredItem(func, name);
  1189. };
  1190. let total = getBrowserStorageItemsCount(func);
  1191. for (let i = total - 1; i >= 0; i--) {
  1192. if (!parseItem(i)) {
  1193. if (i === total - 1) {
  1194. total--;
  1195. setBrowserStorageItemsCount(func, total);
  1196. } else {
  1197. browserStorageEmptyItems[key].add(i);
  1198. }
  1199. }
  1200. }
  1201. }
  1202. function initBrowserStorage() {
  1203. if (browserStorageStatus) {
  1204. return;
  1205. }
  1206. setBrowserStorageStatus(true);
  1207. for (const key in browserStorageConfig) {
  1208. iterateBrowserStorage(key, (item) => {
  1209. const iconSet = item.data;
  1210. const provider = item.provider;
  1211. const prefix = iconSet.prefix;
  1212. const storage = getStorage(
  1213. provider,
  1214. prefix
  1215. );
  1216. if (!addIconSet(storage, iconSet).length) {
  1217. return false;
  1218. }
  1219. const lastModified = iconSet.lastModified || -1;
  1220. storage.lastModifiedCached = storage.lastModifiedCached ? Math.min(storage.lastModifiedCached, lastModified) : lastModified;
  1221. return true;
  1222. });
  1223. }
  1224. }
  1225. function updateLastModified(storage, lastModified) {
  1226. const lastValue = storage.lastModifiedCached;
  1227. if (
  1228. // Matches or newer
  1229. lastValue && lastValue >= lastModified
  1230. ) {
  1231. return lastValue === lastModified;
  1232. }
  1233. storage.lastModifiedCached = lastModified;
  1234. if (lastValue) {
  1235. for (const key in browserStorageConfig) {
  1236. iterateBrowserStorage(key, (item) => {
  1237. const iconSet = item.data;
  1238. return item.provider !== storage.provider || iconSet.prefix !== storage.prefix || iconSet.lastModified === lastModified;
  1239. });
  1240. }
  1241. }
  1242. return true;
  1243. }
  1244. function storeInBrowserStorage(storage, data) {
  1245. if (!browserStorageStatus) {
  1246. initBrowserStorage();
  1247. }
  1248. function store(key) {
  1249. let func;
  1250. if (!browserStorageConfig[key] || !(func = getBrowserStorage(key))) {
  1251. return;
  1252. }
  1253. const set = browserStorageEmptyItems[key];
  1254. let index;
  1255. if (set.size) {
  1256. set.delete(index = Array.from(set).shift());
  1257. } else {
  1258. index = getBrowserStorageItemsCount(func);
  1259. if (!setBrowserStorageItemsCount(func, index + 1)) {
  1260. return;
  1261. }
  1262. }
  1263. const item = {
  1264. cached: Math.floor(Date.now() / browserStorageHour),
  1265. provider: storage.provider,
  1266. data
  1267. };
  1268. return setStoredItem(
  1269. func,
  1270. browserCachePrefix + index.toString(),
  1271. JSON.stringify(item)
  1272. );
  1273. }
  1274. if (data.lastModified && !updateLastModified(storage, data.lastModified)) {
  1275. return;
  1276. }
  1277. if (!Object.keys(data.icons).length) {
  1278. return;
  1279. }
  1280. if (data.not_found) {
  1281. data = Object.assign({}, data);
  1282. delete data.not_found;
  1283. }
  1284. if (!store("local")) {
  1285. store("session");
  1286. }
  1287. }
  1288. function emptyCallback() {
  1289. }
  1290. function loadedNewIcons(storage) {
  1291. if (!storage.iconsLoaderFlag) {
  1292. storage.iconsLoaderFlag = true;
  1293. setTimeout(() => {
  1294. storage.iconsLoaderFlag = false;
  1295. updateCallbacks(storage);
  1296. });
  1297. }
  1298. }
  1299. function loadNewIcons(storage, icons) {
  1300. if (!storage.iconsToLoad) {
  1301. storage.iconsToLoad = icons;
  1302. } else {
  1303. storage.iconsToLoad = storage.iconsToLoad.concat(icons).sort();
  1304. }
  1305. if (!storage.iconsQueueFlag) {
  1306. storage.iconsQueueFlag = true;
  1307. setTimeout(() => {
  1308. storage.iconsQueueFlag = false;
  1309. const { provider, prefix } = storage;
  1310. const icons2 = storage.iconsToLoad;
  1311. delete storage.iconsToLoad;
  1312. let api;
  1313. if (!icons2 || !(api = getAPIModule(provider))) {
  1314. return;
  1315. }
  1316. const params = api.prepare(provider, prefix, icons2);
  1317. params.forEach((item) => {
  1318. sendAPIQuery(provider, item, (data) => {
  1319. if (typeof data !== "object") {
  1320. item.icons.forEach((name) => {
  1321. storage.missing.add(name);
  1322. });
  1323. } else {
  1324. try {
  1325. const parsed = addIconSet(
  1326. storage,
  1327. data
  1328. );
  1329. if (!parsed.length) {
  1330. return;
  1331. }
  1332. const pending = storage.pendingIcons;
  1333. if (pending) {
  1334. parsed.forEach((name) => {
  1335. pending.delete(name);
  1336. });
  1337. }
  1338. storeInBrowserStorage(storage, data);
  1339. } catch (err) {
  1340. console.error(err);
  1341. }
  1342. }
  1343. loadedNewIcons(storage);
  1344. });
  1345. });
  1346. });
  1347. }
  1348. }
  1349. const loadIcons = (icons, callback) => {
  1350. const cleanedIcons = listToIcons(icons, true, allowSimpleNames());
  1351. const sortedIcons = sortIcons(cleanedIcons);
  1352. if (!sortedIcons.pending.length) {
  1353. let callCallback = true;
  1354. if (callback) {
  1355. setTimeout(() => {
  1356. if (callCallback) {
  1357. callback(
  1358. sortedIcons.loaded,
  1359. sortedIcons.missing,
  1360. sortedIcons.pending,
  1361. emptyCallback
  1362. );
  1363. }
  1364. });
  1365. }
  1366. return () => {
  1367. callCallback = false;
  1368. };
  1369. }
  1370. const newIcons = /* @__PURE__ */ Object.create(null);
  1371. const sources = [];
  1372. let lastProvider, lastPrefix;
  1373. sortedIcons.pending.forEach((icon) => {
  1374. const { provider, prefix } = icon;
  1375. if (prefix === lastPrefix && provider === lastProvider) {
  1376. return;
  1377. }
  1378. lastProvider = provider;
  1379. lastPrefix = prefix;
  1380. sources.push(getStorage(provider, prefix));
  1381. const providerNewIcons = newIcons[provider] || (newIcons[provider] = /* @__PURE__ */ Object.create(null));
  1382. if (!providerNewIcons[prefix]) {
  1383. providerNewIcons[prefix] = [];
  1384. }
  1385. });
  1386. sortedIcons.pending.forEach((icon) => {
  1387. const { provider, prefix, name } = icon;
  1388. const storage = getStorage(provider, prefix);
  1389. const pendingQueue = storage.pendingIcons || (storage.pendingIcons = /* @__PURE__ */ new Set());
  1390. if (!pendingQueue.has(name)) {
  1391. pendingQueue.add(name);
  1392. newIcons[provider][prefix].push(name);
  1393. }
  1394. });
  1395. sources.forEach((storage) => {
  1396. const { provider, prefix } = storage;
  1397. if (newIcons[provider][prefix].length) {
  1398. loadNewIcons(storage, newIcons[provider][prefix]);
  1399. }
  1400. });
  1401. return callback ? storeCallback(callback, sortedIcons, sources) : emptyCallback;
  1402. };
  1403. const loadIcon = (icon) => {
  1404. return new Promise((fulfill, reject) => {
  1405. const iconObj = typeof icon === "string" ? stringToIcon(icon, true) : icon;
  1406. if (!iconObj) {
  1407. reject(icon);
  1408. return;
  1409. }
  1410. loadIcons([iconObj || icon], (loaded) => {
  1411. if (loaded.length && iconObj) {
  1412. const data = getIconData(iconObj);
  1413. if (data) {
  1414. fulfill({
  1415. ...defaultIconProps,
  1416. ...data
  1417. });
  1418. return;
  1419. }
  1420. }
  1421. reject(icon);
  1422. });
  1423. });
  1424. };
  1425. function toggleBrowserCache(storage, value) {
  1426. switch (storage) {
  1427. case "local":
  1428. case "session":
  1429. browserStorageConfig[storage] = value;
  1430. break;
  1431. case "all":
  1432. for (const key in browserStorageConfig) {
  1433. browserStorageConfig[key] = value;
  1434. }
  1435. break;
  1436. }
  1437. }
  1438. function mergeCustomisations(defaults, item) {
  1439. const result = {
  1440. ...defaults
  1441. };
  1442. for (const key in item) {
  1443. const value = item[key];
  1444. const valueType = typeof value;
  1445. if (key in defaultIconSizeCustomisations) {
  1446. if (value === null || value && (valueType === "string" || valueType === "number")) {
  1447. result[key] = value;
  1448. }
  1449. } else if (valueType === typeof result[key]) {
  1450. result[key] = key === "rotate" ? value % 4 : value;
  1451. }
  1452. }
  1453. return result;
  1454. }
  1455. const separator = /[\s,]+/;
  1456. function flipFromString(custom, flip) {
  1457. flip.split(separator).forEach((str) => {
  1458. const value = str.trim();
  1459. switch (value) {
  1460. case "horizontal":
  1461. custom.hFlip = true;
  1462. break;
  1463. case "vertical":
  1464. custom.vFlip = true;
  1465. break;
  1466. }
  1467. });
  1468. }
  1469. function rotateFromString(value, defaultValue = 0) {
  1470. const units = value.replace(/^-?[0-9.]*/, "");
  1471. function cleanup(value2) {
  1472. while (value2 < 0) {
  1473. value2 += 4;
  1474. }
  1475. return value2 % 4;
  1476. }
  1477. if (units === "") {
  1478. const num = parseInt(value);
  1479. return isNaN(num) ? 0 : cleanup(num);
  1480. } else if (units !== value) {
  1481. let split = 0;
  1482. switch (units) {
  1483. case "%":
  1484. split = 25;
  1485. break;
  1486. case "deg":
  1487. split = 90;
  1488. }
  1489. if (split) {
  1490. let num = parseFloat(value.slice(0, value.length - units.length));
  1491. if (isNaN(num)) {
  1492. return 0;
  1493. }
  1494. num = num / split;
  1495. return num % 1 === 0 ? cleanup(num) : 0;
  1496. }
  1497. }
  1498. return defaultValue;
  1499. }
  1500. function iconToHTML(body, attributes) {
  1501. let renderAttribsHTML = body.indexOf("xlink:") === -1 ? "" : ' xmlns:xlink="http://www.w3.org/1999/xlink"';
  1502. for (const attr in attributes) {
  1503. renderAttribsHTML += " " + attr + '="' + attributes[attr] + '"';
  1504. }
  1505. return '<svg xmlns="http://www.w3.org/2000/svg"' + renderAttribsHTML + ">" + body + "</svg>";
  1506. }
  1507. function encodeSVGforURL(svg) {
  1508. return svg.replace(/"/g, "'").replace(/%/g, "%25").replace(/#/g, "%23").replace(/</g, "%3C").replace(/>/g, "%3E").replace(/\s+/g, " ");
  1509. }
  1510. function svgToData(svg) {
  1511. return "data:image/svg+xml," + encodeSVGforURL(svg);
  1512. }
  1513. function svgToURL(svg) {
  1514. return 'url("' + svgToData(svg) + '")';
  1515. }
  1516. const defaultExtendedIconCustomisations = {
  1517. ...defaultIconCustomisations,
  1518. inline: false,
  1519. };
  1520. /**
  1521. * Default SVG attributes
  1522. */
  1523. const svgDefaults = {
  1524. 'xmlns': 'http://www.w3.org/2000/svg',
  1525. 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
  1526. 'aria-hidden': true,
  1527. 'role': 'img',
  1528. };
  1529. /**
  1530. * Style modes
  1531. */
  1532. const commonProps = {
  1533. display: 'inline-block',
  1534. };
  1535. const monotoneProps = {
  1536. backgroundColor: 'currentColor',
  1537. };
  1538. const coloredProps = {
  1539. backgroundColor: 'transparent',
  1540. };
  1541. // Dynamically add common props to variables above
  1542. const propsToAdd = {
  1543. Image: 'var(--svg)',
  1544. Repeat: 'no-repeat',
  1545. Size: '100% 100%',
  1546. };
  1547. const propsToAddTo = {
  1548. webkitMask: monotoneProps,
  1549. mask: monotoneProps,
  1550. background: coloredProps,
  1551. };
  1552. for (const prefix in propsToAddTo) {
  1553. const list = propsToAddTo[prefix];
  1554. for (const prop in propsToAdd) {
  1555. list[prefix + prop] = propsToAdd[prop];
  1556. }
  1557. }
  1558. /**
  1559. * Aliases for customisations.
  1560. * In Vue 'v-' properties are reserved, so v-flip must be renamed
  1561. */
  1562. const customisationAliases = {};
  1563. ['horizontal', 'vertical'].forEach((prefix) => {
  1564. const attr = prefix.slice(0, 1) + 'Flip';
  1565. // vertical-flip
  1566. customisationAliases[prefix + '-flip'] = attr;
  1567. // v-flip
  1568. customisationAliases[prefix.slice(0, 1) + '-flip'] = attr;
  1569. // verticalFlip
  1570. customisationAliases[prefix + 'Flip'] = attr;
  1571. });
  1572. /**
  1573. * Fix size: add 'px' to numbers
  1574. */
  1575. function fixSize(value) {
  1576. return value + (value.match(/^[-0-9.]+$/) ? 'px' : '');
  1577. }
  1578. /**
  1579. * Render icon
  1580. */
  1581. const render = (
  1582. // Icon must be validated before calling this function
  1583. icon,
  1584. // Partial properties
  1585. props) => {
  1586. // Split properties
  1587. const customisations = mergeCustomisations(defaultExtendedIconCustomisations, props);
  1588. const componentProps = { ...svgDefaults };
  1589. // Check mode
  1590. const mode = props.mode || 'svg';
  1591. // Copy style
  1592. const style = {};
  1593. const propsStyle = props.style;
  1594. const customStyle = typeof propsStyle === 'object' && !(propsStyle instanceof Array)
  1595. ? propsStyle
  1596. : {};
  1597. // Get element properties
  1598. for (let key in props) {
  1599. const value = props[key];
  1600. if (value === void 0) {
  1601. continue;
  1602. }
  1603. switch (key) {
  1604. // Properties to ignore
  1605. case 'icon':
  1606. case 'style':
  1607. case 'onLoad':
  1608. case 'mode':
  1609. break;
  1610. // Boolean attributes
  1611. case 'inline':
  1612. case 'hFlip':
  1613. case 'vFlip':
  1614. customisations[key] =
  1615. value === true || value === 'true' || value === 1;
  1616. break;
  1617. // Flip as string: 'horizontal,vertical'
  1618. case 'flip':
  1619. if (typeof value === 'string') {
  1620. flipFromString(customisations, value);
  1621. }
  1622. break;
  1623. // Color: override style
  1624. case 'color':
  1625. style.color = value;
  1626. break;
  1627. // Rotation as string
  1628. case 'rotate':
  1629. if (typeof value === 'string') {
  1630. customisations[key] = rotateFromString(value);
  1631. }
  1632. else if (typeof value === 'number') {
  1633. customisations[key] = value;
  1634. }
  1635. break;
  1636. // Remove aria-hidden
  1637. case 'ariaHidden':
  1638. case 'aria-hidden':
  1639. // Vue transforms 'aria-hidden' property to 'ariaHidden'
  1640. if (value !== true && value !== 'true') {
  1641. delete componentProps['aria-hidden'];
  1642. }
  1643. break;
  1644. default: {
  1645. const alias = customisationAliases[key];
  1646. if (alias) {
  1647. // Aliases for boolean customisations
  1648. if (value === true || value === 'true' || value === 1) {
  1649. customisations[alias] = true;
  1650. }
  1651. }
  1652. else if (defaultExtendedIconCustomisations[key] === void 0) {
  1653. // Copy missing property if it does not exist in customisations
  1654. componentProps[key] = value;
  1655. }
  1656. }
  1657. }
  1658. }
  1659. // Generate icon
  1660. const item = iconToSVG(icon, customisations);
  1661. const renderAttribs = item.attributes;
  1662. // Inline display
  1663. if (customisations.inline) {
  1664. style.verticalAlign = '-0.125em';
  1665. }
  1666. if (mode === 'svg') {
  1667. // Add style
  1668. componentProps.style = {
  1669. ...style,
  1670. ...customStyle,
  1671. };
  1672. // Add icon stuff
  1673. Object.assign(componentProps, renderAttribs);
  1674. // Counter for ids based on "id" property to render icons consistently on server and client
  1675. let localCounter = 0;
  1676. let id = props.id;
  1677. if (typeof id === 'string') {
  1678. // Convert '-' to '_' to avoid errors in animations
  1679. id = id.replace(/-/g, '_');
  1680. }
  1681. // Add innerHTML and style to props
  1682. componentProps['innerHTML'] = replaceIDs(item.body, id ? () => id + 'ID' + localCounter++ : 'iconifyVue');
  1683. // Render icon
  1684. return vue.h('svg', componentProps);
  1685. }
  1686. // Render <span> with style
  1687. const { body, width, height } = icon;
  1688. const useMask = mode === 'mask' ||
  1689. (mode === 'bg' ? false : body.indexOf('currentColor') !== -1);
  1690. // Generate SVG
  1691. const html = iconToHTML(body, {
  1692. ...renderAttribs,
  1693. width: width + '',
  1694. height: height + '',
  1695. });
  1696. // Generate style
  1697. componentProps.style = {
  1698. ...style,
  1699. '--svg': svgToURL(html),
  1700. 'width': fixSize(renderAttribs.width),
  1701. 'height': fixSize(renderAttribs.height),
  1702. ...commonProps,
  1703. ...(useMask ? monotoneProps : coloredProps),
  1704. ...customStyle,
  1705. };
  1706. return vue.h('span', componentProps);
  1707. };
  1708. /**
  1709. * Enable cache
  1710. */
  1711. function enableCache(storage) {
  1712. toggleBrowserCache(storage, true);
  1713. }
  1714. /**
  1715. * Disable cache
  1716. */
  1717. function disableCache(storage) {
  1718. toggleBrowserCache(storage, false);
  1719. }
  1720. /**
  1721. * Initialise stuff
  1722. */
  1723. // Enable short names
  1724. allowSimpleNames(true);
  1725. // Set API module
  1726. setAPIModule('', fetchAPIModule);
  1727. /**
  1728. * Browser stuff
  1729. */
  1730. if (typeof document !== 'undefined' && typeof window !== 'undefined') {
  1731. // Set cache and load existing cache
  1732. initBrowserStorage();
  1733. const _window = window;
  1734. // Load icons from global "IconifyPreload"
  1735. if (_window.IconifyPreload !== void 0) {
  1736. const preload = _window.IconifyPreload;
  1737. const err = 'Invalid IconifyPreload syntax.';
  1738. if (typeof preload === 'object' && preload !== null) {
  1739. (preload instanceof Array ? preload : [preload]).forEach((item) => {
  1740. try {
  1741. if (
  1742. // Check if item is an object and not null/array
  1743. typeof item !== 'object' ||
  1744. item === null ||
  1745. item instanceof Array ||
  1746. // Check for 'icons' and 'prefix'
  1747. typeof item.icons !== 'object' ||
  1748. typeof item.prefix !== 'string' ||
  1749. // Add icon set
  1750. !addCollection(item)) {
  1751. console.error(err);
  1752. }
  1753. }
  1754. catch (e) {
  1755. console.error(err);
  1756. }
  1757. });
  1758. }
  1759. }
  1760. // Set API from global "IconifyProviders"
  1761. if (_window.IconifyProviders !== void 0) {
  1762. const providers = _window.IconifyProviders;
  1763. if (typeof providers === 'object' && providers !== null) {
  1764. for (let key in providers) {
  1765. const err = 'IconifyProviders[' + key + '] is invalid.';
  1766. try {
  1767. const value = providers[key];
  1768. if (typeof value !== 'object' ||
  1769. !value ||
  1770. value.resources === void 0) {
  1771. continue;
  1772. }
  1773. if (!addAPIProvider(key, value)) {
  1774. console.error(err);
  1775. }
  1776. }
  1777. catch (e) {
  1778. console.error(err);
  1779. }
  1780. }
  1781. }
  1782. }
  1783. }
  1784. /**
  1785. * Empty icon data, rendered when icon is not available
  1786. */
  1787. const emptyIcon = {
  1788. ...defaultIconProps,
  1789. body: '',
  1790. };
  1791. const Icon = vue.defineComponent({
  1792. // Do not inherit other attributes: it is handled by render()
  1793. inheritAttrs: false,
  1794. // Set initial data
  1795. data() {
  1796. return {
  1797. // Mounted status
  1798. iconMounted: false,
  1799. // Callback counter to trigger re-render
  1800. counter: 0,
  1801. };
  1802. },
  1803. mounted() {
  1804. // Current icon name
  1805. this._name = '';
  1806. // Loading
  1807. this._loadingIcon = null;
  1808. // Mark as mounted
  1809. this.iconMounted = true;
  1810. },
  1811. unmounted() {
  1812. this.abortLoading();
  1813. },
  1814. methods: {
  1815. abortLoading() {
  1816. if (this._loadingIcon) {
  1817. this._loadingIcon.abort();
  1818. this._loadingIcon = null;
  1819. }
  1820. },
  1821. // Get data for icon to render or null
  1822. getIcon(icon, onload) {
  1823. // Icon is an object
  1824. if (typeof icon === 'object' &&
  1825. icon !== null &&
  1826. typeof icon.body === 'string') {
  1827. // Stop loading
  1828. this._name = '';
  1829. this.abortLoading();
  1830. return {
  1831. data: icon,
  1832. };
  1833. }
  1834. // Invalid icon?
  1835. let iconName;
  1836. if (typeof icon !== 'string' ||
  1837. (iconName = stringToIcon(icon, false, true)) === null) {
  1838. this.abortLoading();
  1839. return null;
  1840. }
  1841. // Load icon
  1842. const data = getIconData(iconName);
  1843. if (!data) {
  1844. // Icon data is not available
  1845. if (!this._loadingIcon || this._loadingIcon.name !== icon) {
  1846. // New icon to load
  1847. this.abortLoading();
  1848. this._name = '';
  1849. if (data !== null) {
  1850. // Icon was not loaded
  1851. this._loadingIcon = {
  1852. name: icon,
  1853. abort: loadIcons([iconName], () => {
  1854. this.counter++;
  1855. }),
  1856. };
  1857. }
  1858. }
  1859. return null;
  1860. }
  1861. // Icon data is available
  1862. this.abortLoading();
  1863. if (this._name !== icon) {
  1864. this._name = icon;
  1865. if (onload) {
  1866. onload(icon);
  1867. }
  1868. }
  1869. // Add classes
  1870. const classes = ['iconify'];
  1871. if (iconName.prefix !== '') {
  1872. classes.push('iconify--' + iconName.prefix);
  1873. }
  1874. if (iconName.provider !== '') {
  1875. classes.push('iconify--' + iconName.provider);
  1876. }
  1877. return { data, classes };
  1878. },
  1879. },
  1880. // Render icon
  1881. render() {
  1882. // Re-render when counter changes
  1883. this.counter;
  1884. const props = this.$attrs;
  1885. // Get icon data
  1886. const icon = this.iconMounted
  1887. ? this.getIcon(props.icon, props.onLoad)
  1888. : null;
  1889. // Validate icon object
  1890. if (!icon) {
  1891. return render(emptyIcon, props);
  1892. }
  1893. // Add classes
  1894. let newProps = props;
  1895. if (icon.classes) {
  1896. newProps = {
  1897. ...props,
  1898. class: (typeof props['class'] === 'string'
  1899. ? props['class'] + ' '
  1900. : '') + icon.classes.join(' '),
  1901. };
  1902. }
  1903. // Render icon
  1904. return render({
  1905. ...defaultIconProps,
  1906. ...icon.data,
  1907. }, newProps);
  1908. },
  1909. });
  1910. /**
  1911. * Internal API
  1912. */
  1913. const _api = {
  1914. getAPIConfig,
  1915. setAPIModule,
  1916. sendAPIQuery,
  1917. setFetch,
  1918. getFetch,
  1919. listAPIProviders,
  1920. };
  1921. exports.Icon = Icon;
  1922. exports._api = _api;
  1923. exports.addAPIProvider = addAPIProvider;
  1924. exports.addCollection = addCollection;
  1925. exports.addIcon = addIcon;
  1926. exports.buildIcon = iconToSVG;
  1927. exports.calculateSize = calculateSize;
  1928. exports.disableCache = disableCache;
  1929. exports.enableCache = enableCache;
  1930. exports.getIcon = getIcon;
  1931. exports.iconExists = iconExists;
  1932. exports.listIcons = listIcons;
  1933. exports.loadIcon = loadIcon;
  1934. exports.loadIcons = loadIcons;
  1935. exports.replaceIDs = replaceIDs;
  1936. }));