link.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. /*!
  2. * All material copyright ESRI, All Rights Reserved, unless otherwise specified.
  3. * See https://github.com/Esri/calcite-components/blob/master/LICENSE.md for details.
  4. * v1.0.0-beta.97
  5. */
  6. import { h, Host } from "@stencil/core";
  7. import { focusElement, getElementDir } from "../../utils/dom";
  8. import { CSS_UTILITY } from "../../utils/resources";
  9. import { updateHostInteraction } from "../../utils/interactive";
  10. /** Any attributes placed on <calcite-link> component will propagate to the rendered child */
  11. /** Passing a 'href' will render an anchor link, instead of a span. Role will be set to link, or link, depending on this. */
  12. /** It is the consumers responsibility to add aria information, rel, target, for links, and any link attributes for form submission */
  13. /** @slot - A slot for adding text. */
  14. export class Link {
  15. constructor() {
  16. //--------------------------------------------------------------------------
  17. //
  18. // Properties
  19. //
  20. //--------------------------------------------------------------------------
  21. /** When `true`, interaction is prevented and the component is displayed with lower opacity. */
  22. this.disabled = false;
  23. /**
  24. * Prompts the user to save the linked URL instead of navigating to it. Can be used with or without a value:
  25. * Without a value, the browser will suggest a filename/extension
  26. * See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-download.
  27. */
  28. this.download = false;
  29. this.childElClickHandler = (event) => {
  30. if (!event.isTrusted) {
  31. // click was invoked internally, we stop it here
  32. event.stopPropagation();
  33. }
  34. };
  35. //--------------------------------------------------------------------------
  36. //
  37. // Private Methods
  38. //
  39. //--------------------------------------------------------------------------
  40. this.storeTagRef = (el) => {
  41. this.childEl = el;
  42. };
  43. }
  44. //--------------------------------------------------------------------------
  45. //
  46. // Lifecycle
  47. //
  48. //--------------------------------------------------------------------------
  49. componentDidRender() {
  50. updateHostInteraction(this);
  51. }
  52. render() {
  53. const { download, el } = this;
  54. const dir = getElementDir(el);
  55. const childElType = this.href ? "a" : "span";
  56. const iconStartEl = (h("calcite-icon", { class: "calcite-link--icon icon-start", flipRtl: this.iconFlipRtl === "start" || this.iconFlipRtl === "both", icon: this.iconStart, scale: "s" }));
  57. const iconEndEl = (h("calcite-icon", { class: "calcite-link--icon icon-end", flipRtl: this.iconFlipRtl === "end" || this.iconFlipRtl === "both", icon: this.iconEnd, scale: "s" }));
  58. const Tag = childElType;
  59. const role = childElType === "span" ? "link" : null;
  60. const tabIndex = childElType === "span" ? 0 : null;
  61. return (h(Host, { role: "presentation" }, h(Tag, { class: { [CSS_UTILITY.rtl]: dir === "rtl" },
  62. /*
  63. When the 'download' property of type 'boolean | string' is set to true, the value is "".
  64. This works around that issue for now.
  65. */
  66. download: Tag === "a" && (download === "" || download) ? download : null, href: Tag === "a" && this.href, onClick: this.childElClickHandler, ref: this.storeTagRef, rel: Tag === "a" && this.rel, role: role, tabIndex: tabIndex, target: Tag === "a" && this.target }, this.iconStart ? iconStartEl : null, h("slot", null), this.iconEnd ? iconEndEl : null)));
  67. }
  68. //--------------------------------------------------------------------------
  69. //
  70. // Events
  71. //
  72. //--------------------------------------------------------------------------
  73. clickHandler(event) {
  74. // forwards the click() to the internal link for non user-initiated events
  75. if (!event.isTrusted) {
  76. this.childEl.click();
  77. }
  78. }
  79. //--------------------------------------------------------------------------
  80. //
  81. // Public Methods
  82. //
  83. //--------------------------------------------------------------------------
  84. /** Sets focus on the component. */
  85. async setFocus() {
  86. focusElement(this.childEl);
  87. }
  88. static get is() { return "calcite-link"; }
  89. static get encapsulation() { return "shadow"; }
  90. static get originalStyleUrls() {
  91. return {
  92. "$": ["link.scss"]
  93. };
  94. }
  95. static get styleUrls() {
  96. return {
  97. "$": ["link.css"]
  98. };
  99. }
  100. static get properties() {
  101. return {
  102. "disabled": {
  103. "type": "boolean",
  104. "mutable": false,
  105. "complexType": {
  106. "original": "boolean",
  107. "resolved": "boolean",
  108. "references": {}
  109. },
  110. "required": false,
  111. "optional": false,
  112. "docs": {
  113. "tags": [],
  114. "text": "When `true`, interaction is prevented and the component is displayed with lower opacity."
  115. },
  116. "attribute": "disabled",
  117. "reflect": true,
  118. "defaultValue": "false"
  119. },
  120. "download": {
  121. "type": "any",
  122. "mutable": false,
  123. "complexType": {
  124. "original": "string | boolean",
  125. "resolved": "boolean | string",
  126. "references": {}
  127. },
  128. "required": false,
  129. "optional": false,
  130. "docs": {
  131. "tags": [],
  132. "text": "Prompts the user to save the linked URL instead of navigating to it. Can be used with or without a value:\nWithout a value, the browser will suggest a filename/extension\nSee https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-download."
  133. },
  134. "attribute": "download",
  135. "reflect": true,
  136. "defaultValue": "false"
  137. },
  138. "href": {
  139. "type": "string",
  140. "mutable": false,
  141. "complexType": {
  142. "original": "string",
  143. "resolved": "string",
  144. "references": {}
  145. },
  146. "required": false,
  147. "optional": true,
  148. "docs": {
  149. "tags": [],
  150. "text": "Specifies the URL of the linked resource, which can be set as an absolute or relative path."
  151. },
  152. "attribute": "href",
  153. "reflect": true
  154. },
  155. "iconEnd": {
  156. "type": "string",
  157. "mutable": false,
  158. "complexType": {
  159. "original": "string",
  160. "resolved": "string",
  161. "references": {}
  162. },
  163. "required": false,
  164. "optional": true,
  165. "docs": {
  166. "tags": [],
  167. "text": "Specifies an icon to display at the end of the component."
  168. },
  169. "attribute": "icon-end",
  170. "reflect": true
  171. },
  172. "iconFlipRtl": {
  173. "type": "string",
  174. "mutable": false,
  175. "complexType": {
  176. "original": "FlipContext",
  177. "resolved": "\"both\" | \"end\" | \"start\"",
  178. "references": {
  179. "FlipContext": {
  180. "location": "import",
  181. "path": "../interfaces"
  182. }
  183. }
  184. },
  185. "required": false,
  186. "optional": true,
  187. "docs": {
  188. "tags": [],
  189. "text": "When `true`, the icon will be flipped when the element direction is right-to-left (`\"rtl\"`)."
  190. },
  191. "attribute": "icon-flip-rtl",
  192. "reflect": true
  193. },
  194. "iconStart": {
  195. "type": "string",
  196. "mutable": false,
  197. "complexType": {
  198. "original": "string",
  199. "resolved": "string",
  200. "references": {}
  201. },
  202. "required": false,
  203. "optional": true,
  204. "docs": {
  205. "tags": [],
  206. "text": "Specifies an icon to display at the start of the component."
  207. },
  208. "attribute": "icon-start",
  209. "reflect": true
  210. },
  211. "rel": {
  212. "type": "string",
  213. "mutable": false,
  214. "complexType": {
  215. "original": "string",
  216. "resolved": "string",
  217. "references": {}
  218. },
  219. "required": false,
  220. "optional": true,
  221. "docs": {
  222. "tags": [],
  223. "text": "Specifies the relationship to the linked document defined in `href`."
  224. },
  225. "attribute": "rel",
  226. "reflect": false
  227. },
  228. "target": {
  229. "type": "string",
  230. "mutable": false,
  231. "complexType": {
  232. "original": "string",
  233. "resolved": "string",
  234. "references": {}
  235. },
  236. "required": false,
  237. "optional": true,
  238. "docs": {
  239. "tags": [],
  240. "text": "Specifies the frame or window to open the linked document."
  241. },
  242. "attribute": "target",
  243. "reflect": false
  244. }
  245. };
  246. }
  247. static get methods() {
  248. return {
  249. "setFocus": {
  250. "complexType": {
  251. "signature": "() => Promise<void>",
  252. "parameters": [],
  253. "references": {
  254. "Promise": {
  255. "location": "global"
  256. }
  257. },
  258. "return": "Promise<void>"
  259. },
  260. "docs": {
  261. "text": "Sets focus on the component.",
  262. "tags": []
  263. }
  264. }
  265. };
  266. }
  267. static get elementRef() { return "el"; }
  268. static get listeners() {
  269. return [{
  270. "name": "click",
  271. "method": "clickHandler",
  272. "target": undefined,
  273. "capture": false,
  274. "passive": false
  275. }];
  276. }
  277. }