offline.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698
  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 defaultIconDimensions = Object.freeze(
  7. {
  8. left: 0,
  9. top: 0,
  10. width: 16,
  11. height: 16
  12. }
  13. );
  14. const defaultIconTransformations = Object.freeze({
  15. rotate: 0,
  16. vFlip: false,
  17. hFlip: false
  18. });
  19. const defaultIconProps = Object.freeze({
  20. ...defaultIconDimensions,
  21. ...defaultIconTransformations
  22. });
  23. const defaultExtendedIconProps = Object.freeze({
  24. ...defaultIconProps,
  25. body: "",
  26. hidden: false
  27. });
  28. function mergeIconTransformations(obj1, obj2) {
  29. const result = {};
  30. if (!obj1.hFlip !== !obj2.hFlip) {
  31. result.hFlip = true;
  32. }
  33. if (!obj1.vFlip !== !obj2.vFlip) {
  34. result.vFlip = true;
  35. }
  36. const rotate = ((obj1.rotate || 0) + (obj2.rotate || 0)) % 4;
  37. if (rotate) {
  38. result.rotate = rotate;
  39. }
  40. return result;
  41. }
  42. function mergeIconData(parent, child) {
  43. const result = mergeIconTransformations(parent, child);
  44. for (const key in defaultExtendedIconProps) {
  45. if (key in defaultIconTransformations) {
  46. if (key in parent && !(key in result)) {
  47. result[key] = defaultIconTransformations[key];
  48. }
  49. } else if (key in child) {
  50. result[key] = child[key];
  51. } else if (key in parent) {
  52. result[key] = parent[key];
  53. }
  54. }
  55. return result;
  56. }
  57. function getIconsTree(data, names) {
  58. const icons = data.icons;
  59. const aliases = data.aliases || /* @__PURE__ */ Object.create(null);
  60. const resolved = /* @__PURE__ */ Object.create(null);
  61. function resolve(name) {
  62. if (icons[name]) {
  63. return resolved[name] = [];
  64. }
  65. if (!(name in resolved)) {
  66. resolved[name] = null;
  67. const parent = aliases[name] && aliases[name].parent;
  68. const value = parent && resolve(parent);
  69. if (value) {
  70. resolved[name] = [parent].concat(value);
  71. }
  72. }
  73. return resolved[name];
  74. }
  75. (names || Object.keys(icons).concat(Object.keys(aliases))).forEach(resolve);
  76. return resolved;
  77. }
  78. function internalGetIconData(data, name, tree) {
  79. const icons = data.icons;
  80. const aliases = data.aliases || /* @__PURE__ */ Object.create(null);
  81. let currentProps = {};
  82. function parse(name2) {
  83. currentProps = mergeIconData(
  84. icons[name2] || aliases[name2],
  85. currentProps
  86. );
  87. }
  88. parse(name);
  89. tree.forEach(parse);
  90. return mergeIconData(data, currentProps);
  91. }
  92. function parseIconSet(data, callback) {
  93. const names = [];
  94. if (typeof data !== "object" || typeof data.icons !== "object") {
  95. return names;
  96. }
  97. if (data.not_found instanceof Array) {
  98. data.not_found.forEach((name) => {
  99. callback(name, null);
  100. names.push(name);
  101. });
  102. }
  103. const tree = getIconsTree(data);
  104. for (const name in tree) {
  105. const item = tree[name];
  106. if (item) {
  107. callback(name, internalGetIconData(data, name, item));
  108. names.push(name);
  109. }
  110. }
  111. return names;
  112. }
  113. const matchIconName = /^[a-z0-9]+(-[a-z0-9]+)*$/;
  114. const optionalPropertyDefaults = {
  115. provider: "",
  116. aliases: {},
  117. not_found: {},
  118. ...defaultIconDimensions
  119. };
  120. function checkOptionalProps(item, defaults) {
  121. for (const prop in defaults) {
  122. if (prop in item && typeof item[prop] !== typeof defaults[prop]) {
  123. return false;
  124. }
  125. }
  126. return true;
  127. }
  128. function quicklyValidateIconSet(obj) {
  129. if (typeof obj !== "object" || obj === null) {
  130. return null;
  131. }
  132. const data = obj;
  133. if (typeof data.prefix !== "string" || !obj.icons || typeof obj.icons !== "object") {
  134. return null;
  135. }
  136. if (!checkOptionalProps(obj, optionalPropertyDefaults)) {
  137. return null;
  138. }
  139. const icons = data.icons;
  140. for (const name in icons) {
  141. const icon = icons[name];
  142. if (!name.match(matchIconName) || typeof icon.body !== "string" || !checkOptionalProps(
  143. icon,
  144. defaultExtendedIconProps
  145. )) {
  146. return null;
  147. }
  148. }
  149. const aliases = data.aliases || /* @__PURE__ */ Object.create(null);
  150. for (const name in aliases) {
  151. const icon = aliases[name];
  152. const parent = icon.parent;
  153. if (!name.match(matchIconName) || typeof parent !== "string" || !icons[parent] && !aliases[parent] || !checkOptionalProps(
  154. icon,
  155. defaultExtendedIconProps
  156. )) {
  157. return null;
  158. }
  159. }
  160. return data;
  161. }
  162. const defaultIconSizeCustomisations = Object.freeze({
  163. width: null,
  164. height: null
  165. });
  166. const defaultIconCustomisations = Object.freeze({
  167. // Dimensions
  168. ...defaultIconSizeCustomisations,
  169. // Transformations
  170. ...defaultIconTransformations
  171. });
  172. function mergeCustomisations(defaults, item) {
  173. const result = {
  174. ...defaults
  175. };
  176. for (const key in item) {
  177. const value = item[key];
  178. const valueType = typeof value;
  179. if (key in defaultIconSizeCustomisations) {
  180. if (value === null || value && (valueType === "string" || valueType === "number")) {
  181. result[key] = value;
  182. }
  183. } else if (valueType === typeof result[key]) {
  184. result[key] = key === "rotate" ? value % 4 : value;
  185. }
  186. }
  187. return result;
  188. }
  189. const separator = /[\s,]+/;
  190. function flipFromString(custom, flip) {
  191. flip.split(separator).forEach((str) => {
  192. const value = str.trim();
  193. switch (value) {
  194. case "horizontal":
  195. custom.hFlip = true;
  196. break;
  197. case "vertical":
  198. custom.vFlip = true;
  199. break;
  200. }
  201. });
  202. }
  203. function rotateFromString(value, defaultValue = 0) {
  204. const units = value.replace(/^-?[0-9.]*/, "");
  205. function cleanup(value2) {
  206. while (value2 < 0) {
  207. value2 += 4;
  208. }
  209. return value2 % 4;
  210. }
  211. if (units === "") {
  212. const num = parseInt(value);
  213. return isNaN(num) ? 0 : cleanup(num);
  214. } else if (units !== value) {
  215. let split = 0;
  216. switch (units) {
  217. case "%":
  218. split = 25;
  219. break;
  220. case "deg":
  221. split = 90;
  222. }
  223. if (split) {
  224. let num = parseFloat(value.slice(0, value.length - units.length));
  225. if (isNaN(num)) {
  226. return 0;
  227. }
  228. num = num / split;
  229. return num % 1 === 0 ? cleanup(num) : 0;
  230. }
  231. }
  232. return defaultValue;
  233. }
  234. const unitsSplit = /(-?[0-9.]*[0-9]+[0-9.]*)/g;
  235. const unitsTest = /^-?[0-9.]*[0-9]+[0-9.]*$/g;
  236. function calculateSize(size, ratio, precision) {
  237. if (ratio === 1) {
  238. return size;
  239. }
  240. precision = precision || 100;
  241. if (typeof size === "number") {
  242. return Math.ceil(size * ratio * precision) / precision;
  243. }
  244. if (typeof size !== "string") {
  245. return size;
  246. }
  247. const oldParts = size.split(unitsSplit);
  248. if (oldParts === null || !oldParts.length) {
  249. return size;
  250. }
  251. const newParts = [];
  252. let code = oldParts.shift();
  253. let isNumber = unitsTest.test(code);
  254. while (true) {
  255. if (isNumber) {
  256. const num = parseFloat(code);
  257. if (isNaN(num)) {
  258. newParts.push(code);
  259. } else {
  260. newParts.push(Math.ceil(num * ratio * precision) / precision);
  261. }
  262. } else {
  263. newParts.push(code);
  264. }
  265. code = oldParts.shift();
  266. if (code === void 0) {
  267. return newParts.join("");
  268. }
  269. isNumber = !isNumber;
  270. }
  271. }
  272. const isUnsetKeyword = (value) => value === "unset" || value === "undefined" || value === "none";
  273. function iconToSVG(icon, customisations) {
  274. const fullIcon = {
  275. ...defaultIconProps,
  276. ...icon
  277. };
  278. const fullCustomisations = {
  279. ...defaultIconCustomisations,
  280. ...customisations
  281. };
  282. const box = {
  283. left: fullIcon.left,
  284. top: fullIcon.top,
  285. width: fullIcon.width,
  286. height: fullIcon.height
  287. };
  288. let body = fullIcon.body;
  289. [fullIcon, fullCustomisations].forEach((props) => {
  290. const transformations = [];
  291. const hFlip = props.hFlip;
  292. const vFlip = props.vFlip;
  293. let rotation = props.rotate;
  294. if (hFlip) {
  295. if (vFlip) {
  296. rotation += 2;
  297. } else {
  298. transformations.push(
  299. "translate(" + (box.width + box.left).toString() + " " + (0 - box.top).toString() + ")"
  300. );
  301. transformations.push("scale(-1 1)");
  302. box.top = box.left = 0;
  303. }
  304. } else if (vFlip) {
  305. transformations.push(
  306. "translate(" + (0 - box.left).toString() + " " + (box.height + box.top).toString() + ")"
  307. );
  308. transformations.push("scale(1 -1)");
  309. box.top = box.left = 0;
  310. }
  311. let tempValue;
  312. if (rotation < 0) {
  313. rotation -= Math.floor(rotation / 4) * 4;
  314. }
  315. rotation = rotation % 4;
  316. switch (rotation) {
  317. case 1:
  318. tempValue = box.height / 2 + box.top;
  319. transformations.unshift(
  320. "rotate(90 " + tempValue.toString() + " " + tempValue.toString() + ")"
  321. );
  322. break;
  323. case 2:
  324. transformations.unshift(
  325. "rotate(180 " + (box.width / 2 + box.left).toString() + " " + (box.height / 2 + box.top).toString() + ")"
  326. );
  327. break;
  328. case 3:
  329. tempValue = box.width / 2 + box.left;
  330. transformations.unshift(
  331. "rotate(-90 " + tempValue.toString() + " " + tempValue.toString() + ")"
  332. );
  333. break;
  334. }
  335. if (rotation % 2 === 1) {
  336. if (box.left !== box.top) {
  337. tempValue = box.left;
  338. box.left = box.top;
  339. box.top = tempValue;
  340. }
  341. if (box.width !== box.height) {
  342. tempValue = box.width;
  343. box.width = box.height;
  344. box.height = tempValue;
  345. }
  346. }
  347. if (transformations.length) {
  348. body = '<g transform="' + transformations.join(" ") + '">' + body + "</g>";
  349. }
  350. });
  351. const customisationsWidth = fullCustomisations.width;
  352. const customisationsHeight = fullCustomisations.height;
  353. const boxWidth = box.width;
  354. const boxHeight = box.height;
  355. let width;
  356. let height;
  357. if (customisationsWidth === null) {
  358. height = customisationsHeight === null ? "1em" : customisationsHeight === "auto" ? boxHeight : customisationsHeight;
  359. width = calculateSize(height, boxWidth / boxHeight);
  360. } else {
  361. width = customisationsWidth === "auto" ? boxWidth : customisationsWidth;
  362. height = customisationsHeight === null ? calculateSize(width, boxHeight / boxWidth) : customisationsHeight === "auto" ? boxHeight : customisationsHeight;
  363. }
  364. const attributes = {};
  365. const setAttr = (prop, value) => {
  366. if (!isUnsetKeyword(value)) {
  367. attributes[prop] = value.toString();
  368. }
  369. };
  370. setAttr("width", width);
  371. setAttr("height", height);
  372. attributes.viewBox = box.left.toString() + " " + box.top.toString() + " " + boxWidth.toString() + " " + boxHeight.toString();
  373. return {
  374. attributes,
  375. body
  376. };
  377. }
  378. const regex = /\sid="(\S+)"/g;
  379. const randomPrefix = "IconifyId" + Date.now().toString(16) + (Math.random() * 16777216 | 0).toString(16);
  380. let counter = 0;
  381. function replaceIDs(body, prefix = randomPrefix) {
  382. const ids = [];
  383. let match;
  384. while (match = regex.exec(body)) {
  385. ids.push(match[1]);
  386. }
  387. if (!ids.length) {
  388. return body;
  389. }
  390. const suffix = "suffix" + (Math.random() * 16777216 | Date.now()).toString(16);
  391. ids.forEach((id) => {
  392. const newID = typeof prefix === "function" ? prefix(id) : prefix + (counter++).toString();
  393. const escapedID = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  394. body = body.replace(
  395. // Allowed characters before id: [#;"]
  396. // Allowed characters after id: [)"], .[a-z]
  397. new RegExp('([#;"])(' + escapedID + ')([")]|\\.[a-z])', "g"),
  398. "$1" + newID + suffix + "$3"
  399. );
  400. });
  401. body = body.replace(new RegExp(suffix, "g"), "");
  402. return body;
  403. }
  404. function iconToHTML(body, attributes) {
  405. let renderAttribsHTML = body.indexOf("xlink:") === -1 ? "" : ' xmlns:xlink="http://www.w3.org/1999/xlink"';
  406. for (const attr in attributes) {
  407. renderAttribsHTML += " " + attr + '="' + attributes[attr] + '"';
  408. }
  409. return '<svg xmlns="http://www.w3.org/2000/svg"' + renderAttribsHTML + ">" + body + "</svg>";
  410. }
  411. function encodeSVGforURL(svg) {
  412. return svg.replace(/"/g, "'").replace(/%/g, "%25").replace(/#/g, "%23").replace(/</g, "%3C").replace(/>/g, "%3E").replace(/\s+/g, " ");
  413. }
  414. function svgToData(svg) {
  415. return "data:image/svg+xml," + encodeSVGforURL(svg);
  416. }
  417. function svgToURL(svg) {
  418. return 'url("' + svgToData(svg) + '")';
  419. }
  420. const defaultExtendedIconCustomisations = {
  421. ...defaultIconCustomisations,
  422. inline: false,
  423. };
  424. /**
  425. * Default SVG attributes
  426. */
  427. const svgDefaults = {
  428. 'xmlns': 'http://www.w3.org/2000/svg',
  429. 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
  430. 'aria-hidden': true,
  431. 'role': 'img',
  432. };
  433. /**
  434. * Style modes
  435. */
  436. const commonProps = {
  437. display: 'inline-block',
  438. };
  439. const monotoneProps = {
  440. backgroundColor: 'currentColor',
  441. };
  442. const coloredProps = {
  443. backgroundColor: 'transparent',
  444. };
  445. // Dynamically add common props to variables above
  446. const propsToAdd = {
  447. Image: 'var(--svg)',
  448. Repeat: 'no-repeat',
  449. Size: '100% 100%',
  450. };
  451. const propsToAddTo = {
  452. webkitMask: monotoneProps,
  453. mask: monotoneProps,
  454. background: coloredProps,
  455. };
  456. for (const prefix in propsToAddTo) {
  457. const list = propsToAddTo[prefix];
  458. for (const prop in propsToAdd) {
  459. list[prefix + prop] = propsToAdd[prop];
  460. }
  461. }
  462. /**
  463. * Aliases for customisations.
  464. * In Vue 'v-' properties are reserved, so v-flip must be renamed
  465. */
  466. const customisationAliases = {};
  467. ['horizontal', 'vertical'].forEach((prefix) => {
  468. const attr = prefix.slice(0, 1) + 'Flip';
  469. // vertical-flip
  470. customisationAliases[prefix + '-flip'] = attr;
  471. // v-flip
  472. customisationAliases[prefix.slice(0, 1) + '-flip'] = attr;
  473. // verticalFlip
  474. customisationAliases[prefix + 'Flip'] = attr;
  475. });
  476. /**
  477. * Fix size: add 'px' to numbers
  478. */
  479. function fixSize(value) {
  480. return value + (value.match(/^[-0-9.]+$/) ? 'px' : '');
  481. }
  482. /**
  483. * Render icon
  484. */
  485. const render = (
  486. // Icon must be validated before calling this function
  487. icon,
  488. // Partial properties
  489. props) => {
  490. // Split properties
  491. const customisations = mergeCustomisations(defaultExtendedIconCustomisations, props);
  492. const componentProps = { ...svgDefaults };
  493. // Check mode
  494. const mode = props.mode || 'svg';
  495. // Copy style
  496. const style = {};
  497. const propsStyle = props.style;
  498. const customStyle = typeof propsStyle === 'object' && !(propsStyle instanceof Array)
  499. ? propsStyle
  500. : {};
  501. // Get element properties
  502. for (let key in props) {
  503. const value = props[key];
  504. if (value === void 0) {
  505. continue;
  506. }
  507. switch (key) {
  508. // Properties to ignore
  509. case 'icon':
  510. case 'style':
  511. case 'onLoad':
  512. case 'mode':
  513. break;
  514. // Boolean attributes
  515. case 'inline':
  516. case 'hFlip':
  517. case 'vFlip':
  518. customisations[key] =
  519. value === true || value === 'true' || value === 1;
  520. break;
  521. // Flip as string: 'horizontal,vertical'
  522. case 'flip':
  523. if (typeof value === 'string') {
  524. flipFromString(customisations, value);
  525. }
  526. break;
  527. // Color: override style
  528. case 'color':
  529. style.color = value;
  530. break;
  531. // Rotation as string
  532. case 'rotate':
  533. if (typeof value === 'string') {
  534. customisations[key] = rotateFromString(value);
  535. }
  536. else if (typeof value === 'number') {
  537. customisations[key] = value;
  538. }
  539. break;
  540. // Remove aria-hidden
  541. case 'ariaHidden':
  542. case 'aria-hidden':
  543. // Vue transforms 'aria-hidden' property to 'ariaHidden'
  544. if (value !== true && value !== 'true') {
  545. delete componentProps['aria-hidden'];
  546. }
  547. break;
  548. default: {
  549. const alias = customisationAliases[key];
  550. if (alias) {
  551. // Aliases for boolean customisations
  552. if (value === true || value === 'true' || value === 1) {
  553. customisations[alias] = true;
  554. }
  555. }
  556. else if (defaultExtendedIconCustomisations[key] === void 0) {
  557. // Copy missing property if it does not exist in customisations
  558. componentProps[key] = value;
  559. }
  560. }
  561. }
  562. }
  563. // Generate icon
  564. const item = iconToSVG(icon, customisations);
  565. const renderAttribs = item.attributes;
  566. // Inline display
  567. if (customisations.inline) {
  568. style.verticalAlign = '-0.125em';
  569. }
  570. if (mode === 'svg') {
  571. // Add style
  572. componentProps.style = {
  573. ...style,
  574. ...customStyle,
  575. };
  576. // Add icon stuff
  577. Object.assign(componentProps, renderAttribs);
  578. // Counter for ids based on "id" property to render icons consistently on server and client
  579. let localCounter = 0;
  580. let id = props.id;
  581. if (typeof id === 'string') {
  582. // Convert '-' to '_' to avoid errors in animations
  583. id = id.replace(/-/g, '_');
  584. }
  585. // Add innerHTML and style to props
  586. componentProps['innerHTML'] = replaceIDs(item.body, id ? () => id + 'ID' + localCounter++ : 'iconifyVue');
  587. // Render icon
  588. return vue.h('svg', componentProps);
  589. }
  590. // Render <span> with style
  591. const { body, width, height } = icon;
  592. const useMask = mode === 'mask' ||
  593. (mode === 'bg' ? false : body.indexOf('currentColor') !== -1);
  594. // Generate SVG
  595. const html = iconToHTML(body, {
  596. ...renderAttribs,
  597. width: width + '',
  598. height: height + '',
  599. });
  600. // Generate style
  601. componentProps.style = {
  602. ...style,
  603. '--svg': svgToURL(html),
  604. 'width': fixSize(renderAttribs.width),
  605. 'height': fixSize(renderAttribs.height),
  606. ...commonProps,
  607. ...(useMask ? monotoneProps : coloredProps),
  608. ...customStyle,
  609. };
  610. return vue.h('span', componentProps);
  611. };
  612. /**
  613. * Storage for icons referred by name
  614. */
  615. const storage = Object.create(null);
  616. /**
  617. * Add icon to storage, allowing to call it by name
  618. *
  619. * @param name
  620. * @param data
  621. */
  622. function addIcon(name, data) {
  623. storage[name] = data;
  624. }
  625. /**
  626. * Add collection to storage, allowing to call icons by name
  627. *
  628. * @param data Icon set
  629. * @param prefix Optional prefix to add to icon names, true (default) if prefix from icon set should be used.
  630. */
  631. function addCollection(data, prefix) {
  632. const iconPrefix = typeof prefix === 'string'
  633. ? prefix
  634. : prefix !== false && typeof data.prefix === 'string'
  635. ? data.prefix + ':'
  636. : '';
  637. quicklyValidateIconSet(data) &&
  638. parseIconSet(data, (name, icon) => {
  639. if (icon) {
  640. storage[iconPrefix + name] = icon;
  641. }
  642. });
  643. }
  644. /**
  645. * Component
  646. */
  647. const Icon = vue.defineComponent({
  648. // Do not inherit other attributes: it is handled by render()
  649. inheritAttrs: false,
  650. // Render icon
  651. render() {
  652. const props = this.$attrs;
  653. // Check icon
  654. const propsIcon = props.icon;
  655. const icon = typeof propsIcon === 'string'
  656. ? storage[propsIcon]
  657. : typeof propsIcon === 'object'
  658. ? propsIcon
  659. : null;
  660. // Validate icon object
  661. if (icon === null ||
  662. typeof icon !== 'object' ||
  663. typeof icon.body !== 'string') {
  664. return this.$slots.default ? this.$slots.default() : null;
  665. }
  666. // Valid icon: render it
  667. return render({
  668. ...defaultIconProps,
  669. ...icon,
  670. }, props);
  671. },
  672. });
  673. exports.Icon = Icon;
  674. exports.addCollection = addCollection;
  675. exports.addIcon = addIcon;
  676. }));