iconify.mjs 52 KB

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