icon.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. import { Build, Host, h } from "@stencil/core";
  2. import { getSvgContent, ioniconContent } from "./request";
  3. import { getName, getUrl, inheritAttributes, isRTL } from "./utils";
  4. export class Icon {
  5. constructor() {
  6. this.iconName = null;
  7. this.inheritedAttributes = {};
  8. this.didLoadIcon = false;
  9. this.svgContent = undefined;
  10. this.isVisible = false;
  11. this.mode = getIonMode();
  12. this.color = undefined;
  13. this.ios = undefined;
  14. this.md = undefined;
  15. this.flipRtl = undefined;
  16. this.name = undefined;
  17. this.src = undefined;
  18. this.icon = undefined;
  19. this.size = undefined;
  20. this.lazy = false;
  21. this.sanitize = true;
  22. }
  23. componentWillLoad() {
  24. this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
  25. }
  26. connectedCallback() {
  27. // purposely do not return the promise here because loading
  28. // the svg file should not hold up loading the app
  29. // only load the svg if it's visible
  30. this.waitUntilVisible(this.el, '50px', () => {
  31. this.isVisible = true;
  32. this.loadIcon();
  33. });
  34. }
  35. componentDidLoad() {
  36. /**
  37. * Addresses an Angular issue where property values are assigned after the 'connectedCallback' but prior to the registration of watchers.
  38. * This enhancement ensures the loading of an icon when the component has finished rendering and the icon has yet to apply the SVG data.
  39. * This modification pertains to the usage of Angular's binding syntax:
  40. * `<ion-icon [name]="myIconName"></ion-icon>`
  41. */
  42. if (!this.didLoadIcon) {
  43. this.loadIcon();
  44. }
  45. }
  46. disconnectedCallback() {
  47. if (this.io) {
  48. this.io.disconnect();
  49. this.io = undefined;
  50. }
  51. }
  52. waitUntilVisible(el, rootMargin, cb) {
  53. if (Build.isBrowser && this.lazy && typeof window !== 'undefined' && window.IntersectionObserver) {
  54. const io = (this.io = new window.IntersectionObserver((data) => {
  55. if (data[0].isIntersecting) {
  56. io.disconnect();
  57. this.io = undefined;
  58. cb();
  59. }
  60. }, { rootMargin }));
  61. io.observe(el);
  62. }
  63. else {
  64. // browser doesn't support IntersectionObserver
  65. // so just fallback to always show it
  66. cb();
  67. }
  68. }
  69. loadIcon() {
  70. if (Build.isBrowser && this.isVisible) {
  71. const url = getUrl(this);
  72. if (url) {
  73. if (ioniconContent.has(url)) {
  74. // sync if it's already loaded
  75. this.svgContent = ioniconContent.get(url);
  76. }
  77. else {
  78. // async if it hasn't been loaded
  79. getSvgContent(url, this.sanitize).then(() => (this.svgContent = ioniconContent.get(url)));
  80. }
  81. this.didLoadIcon = true;
  82. }
  83. }
  84. this.iconName = getName(this.name, this.icon, this.mode, this.ios, this.md);
  85. }
  86. render() {
  87. const { flipRtl, iconName, inheritedAttributes, el } = this;
  88. const mode = this.mode || 'md';
  89. // we have designated that arrows & chevrons should automatically flip (unless flip-rtl is set to false) because "back" is left in ltr and right in rtl, and "forward" is the opposite
  90. const shouldAutoFlip = iconName
  91. ? (iconName.includes('arrow') || iconName.includes('chevron')) && flipRtl !== false
  92. : false;
  93. // if shouldBeFlippable is true, the icon should change direction when `dir` changes
  94. const shouldBeFlippable = flipRtl || shouldAutoFlip;
  95. return (h(Host, Object.assign({ role: "img", class: Object.assign(Object.assign({ [mode]: true }, createColorClasses(this.color)), { [`icon-${this.size}`]: !!this.size, 'flip-rtl': shouldBeFlippable, 'icon-rtl': shouldBeFlippable && isRTL(el) }) }, inheritedAttributes), Build.isBrowser && this.svgContent ? (h("div", { class: "icon-inner", innerHTML: this.svgContent })) : (h("div", { class: "icon-inner" }))));
  96. }
  97. static get is() { return "ion-icon"; }
  98. static get encapsulation() { return "shadow"; }
  99. static get originalStyleUrls() {
  100. return {
  101. "$": ["icon.css"]
  102. };
  103. }
  104. static get styleUrls() {
  105. return {
  106. "$": ["icon.css"]
  107. };
  108. }
  109. static get assetsDirs() { return ["svg"]; }
  110. static get properties() {
  111. return {
  112. "mode": {
  113. "type": "string",
  114. "mutable": true,
  115. "complexType": {
  116. "original": "string",
  117. "resolved": "string",
  118. "references": {}
  119. },
  120. "required": false,
  121. "optional": false,
  122. "docs": {
  123. "tags": [],
  124. "text": "The mode determines which platform styles to use."
  125. },
  126. "attribute": "mode",
  127. "reflect": false,
  128. "defaultValue": "getIonMode()"
  129. },
  130. "color": {
  131. "type": "string",
  132. "mutable": false,
  133. "complexType": {
  134. "original": "string",
  135. "resolved": "string | undefined",
  136. "references": {}
  137. },
  138. "required": false,
  139. "optional": true,
  140. "docs": {
  141. "tags": [],
  142. "text": "The color to use for the background of the item."
  143. },
  144. "attribute": "color",
  145. "reflect": false
  146. },
  147. "ios": {
  148. "type": "string",
  149. "mutable": false,
  150. "complexType": {
  151. "original": "string",
  152. "resolved": "string | undefined",
  153. "references": {}
  154. },
  155. "required": false,
  156. "optional": true,
  157. "docs": {
  158. "tags": [],
  159. "text": "Specifies which icon to use on `ios` mode."
  160. },
  161. "attribute": "ios",
  162. "reflect": false
  163. },
  164. "md": {
  165. "type": "string",
  166. "mutable": false,
  167. "complexType": {
  168. "original": "string",
  169. "resolved": "string | undefined",
  170. "references": {}
  171. },
  172. "required": false,
  173. "optional": true,
  174. "docs": {
  175. "tags": [],
  176. "text": "Specifies which icon to use on `md` mode."
  177. },
  178. "attribute": "md",
  179. "reflect": false
  180. },
  181. "flipRtl": {
  182. "type": "boolean",
  183. "mutable": false,
  184. "complexType": {
  185. "original": "boolean",
  186. "resolved": "boolean | undefined",
  187. "references": {}
  188. },
  189. "required": false,
  190. "optional": true,
  191. "docs": {
  192. "tags": [],
  193. "text": "Specifies whether the icon should horizontally flip when `dir` is `\"rtl\"`."
  194. },
  195. "attribute": "flip-rtl",
  196. "reflect": false
  197. },
  198. "name": {
  199. "type": "string",
  200. "mutable": false,
  201. "complexType": {
  202. "original": "string",
  203. "resolved": "string | undefined",
  204. "references": {}
  205. },
  206. "required": false,
  207. "optional": true,
  208. "docs": {
  209. "tags": [],
  210. "text": "Specifies which icon to use from the built-in set of icons."
  211. },
  212. "attribute": "name",
  213. "reflect": true
  214. },
  215. "src": {
  216. "type": "string",
  217. "mutable": false,
  218. "complexType": {
  219. "original": "string",
  220. "resolved": "string | undefined",
  221. "references": {}
  222. },
  223. "required": false,
  224. "optional": true,
  225. "docs": {
  226. "tags": [],
  227. "text": "Specifies the exact `src` of an SVG file to use."
  228. },
  229. "attribute": "src",
  230. "reflect": false
  231. },
  232. "icon": {
  233. "type": "any",
  234. "mutable": false,
  235. "complexType": {
  236. "original": "any",
  237. "resolved": "any",
  238. "references": {}
  239. },
  240. "required": false,
  241. "optional": true,
  242. "docs": {
  243. "tags": [],
  244. "text": "A combination of both `name` and `src`. If a `src` url is detected\nit will set the `src` property. Otherwise it assumes it's a built-in named\nSVG and set the `name` property."
  245. },
  246. "attribute": "icon",
  247. "reflect": false
  248. },
  249. "size": {
  250. "type": "string",
  251. "mutable": false,
  252. "complexType": {
  253. "original": "string",
  254. "resolved": "string | undefined",
  255. "references": {}
  256. },
  257. "required": false,
  258. "optional": true,
  259. "docs": {
  260. "tags": [],
  261. "text": "The size of the icon.\nAvailable options are: `\"small\"` and `\"large\"`."
  262. },
  263. "attribute": "size",
  264. "reflect": false
  265. },
  266. "lazy": {
  267. "type": "boolean",
  268. "mutable": false,
  269. "complexType": {
  270. "original": "boolean",
  271. "resolved": "boolean",
  272. "references": {}
  273. },
  274. "required": false,
  275. "optional": false,
  276. "docs": {
  277. "tags": [],
  278. "text": "If enabled, ion-icon will be loaded lazily when it's visible in the viewport.\nDefault, `false`."
  279. },
  280. "attribute": "lazy",
  281. "reflect": false,
  282. "defaultValue": "false"
  283. },
  284. "sanitize": {
  285. "type": "boolean",
  286. "mutable": false,
  287. "complexType": {
  288. "original": "boolean",
  289. "resolved": "boolean",
  290. "references": {}
  291. },
  292. "required": false,
  293. "optional": false,
  294. "docs": {
  295. "tags": [{
  296. "name": "default",
  297. "text": "true"
  298. }],
  299. "text": "When set to `false`, SVG content that is HTTP fetched will not be checked\nif the response SVG content has any `<script>` elements, or any attributes\nthat start with `on`, such as `onclick`."
  300. },
  301. "attribute": "sanitize",
  302. "reflect": false,
  303. "defaultValue": "true"
  304. }
  305. };
  306. }
  307. static get states() {
  308. return {
  309. "svgContent": {},
  310. "isVisible": {}
  311. };
  312. }
  313. static get elementRef() { return "el"; }
  314. static get watchers() {
  315. return [{
  316. "propName": "name",
  317. "methodName": "loadIcon"
  318. }, {
  319. "propName": "src",
  320. "methodName": "loadIcon"
  321. }, {
  322. "propName": "icon",
  323. "methodName": "loadIcon"
  324. }, {
  325. "propName": "ios",
  326. "methodName": "loadIcon"
  327. }, {
  328. "propName": "md",
  329. "methodName": "loadIcon"
  330. }];
  331. }
  332. }
  333. const getIonMode = () => (Build.isBrowser && typeof document !== 'undefined' && document.documentElement.getAttribute('mode')) || 'md';
  334. const createColorClasses = (color) => {
  335. return color
  336. ? {
  337. 'ion-color': true,
  338. [`ion-color-${color}`]: true,
  339. }
  340. : null;
  341. };