dropdown-item.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  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.82
  5. */
  6. import { Component, Element, Event, h, Host, Listen, Method, Prop } from "@stencil/core";
  7. import { getElementProp, toAriaBoolean } from "../../utils/dom";
  8. import { CSS } from "./resources";
  9. /**
  10. * @slot - A slot for adding text.
  11. */
  12. export class DropdownItem {
  13. constructor() {
  14. //--------------------------------------------------------------------------
  15. //
  16. // Public Properties
  17. //
  18. //--------------------------------------------------------------------------
  19. /** Indicates whether the item is active. */
  20. this.active = false;
  21. }
  22. //--------------------------------------------------------------------------
  23. //
  24. // Public Methods
  25. //
  26. //--------------------------------------------------------------------------
  27. /** Sets focus on the component. */
  28. async setFocus() {
  29. this.el.focus();
  30. }
  31. //--------------------------------------------------------------------------
  32. //
  33. // Lifecycle
  34. //
  35. //--------------------------------------------------------------------------
  36. componentWillLoad() {
  37. this.initialize();
  38. }
  39. connectedCallback() {
  40. this.initialize();
  41. }
  42. render() {
  43. const scale = getElementProp(this.el, "scale", "m");
  44. const iconStartEl = (h("calcite-icon", { class: "dropdown-item-icon-start", flipRtl: this.iconFlipRtl === "start" || this.iconFlipRtl === "both", icon: this.iconStart, scale: "s" }));
  45. const contentNode = (h("span", { class: "dropdown-item-content" },
  46. h("slot", null)));
  47. const iconEndEl = (h("calcite-icon", { class: "dropdown-item-icon-end", flipRtl: this.iconFlipRtl === "end" || this.iconFlipRtl === "both", icon: this.iconEnd, scale: "s" }));
  48. const slottedContent = this.iconStart && this.iconEnd
  49. ? [iconStartEl, contentNode, iconEndEl]
  50. : this.iconStart
  51. ? [iconStartEl, h("slot", null)]
  52. : this.iconEnd
  53. ? [contentNode, iconEndEl]
  54. : contentNode;
  55. const contentEl = !this.href ? (slottedContent) : (h("a", { "aria-label": this.label, class: "dropdown-link", href: this.href, ref: (el) => (this.childLink = el), rel: this.rel, target: this.target }, slottedContent));
  56. const itemRole = this.href
  57. ? null
  58. : this.selectionMode === "single"
  59. ? "menuitemradio"
  60. : this.selectionMode === "multi"
  61. ? "menuitemcheckbox"
  62. : "menuitem";
  63. const itemAria = this.selectionMode !== "none" ? toAriaBoolean(this.active) : null;
  64. return (h(Host, { "aria-checked": itemAria, role: itemRole, tabindex: "0" },
  65. h("div", { class: {
  66. container: true,
  67. [CSS.containerLink]: !!this.href,
  68. [CSS.containerSmall]: scale === "s",
  69. [CSS.containerMedium]: scale === "m",
  70. [CSS.containerLarge]: scale === "l",
  71. [CSS.containerMulti]: this.selectionMode === "multi",
  72. [CSS.containerSingle]: this.selectionMode === "single",
  73. [CSS.containerNone]: this.selectionMode === "none"
  74. } },
  75. this.selectionMode !== "none" ? (h("calcite-icon", { class: "dropdown-item-icon", icon: this.selectionMode === "multi" ? "check" : "bullet-point", scale: "s" })) : null,
  76. contentEl)));
  77. }
  78. //--------------------------------------------------------------------------
  79. //
  80. // Event Listeners
  81. //
  82. //--------------------------------------------------------------------------
  83. onClick() {
  84. this.emitRequestedItem();
  85. }
  86. keyDownHandler(e) {
  87. switch (e.key) {
  88. case " ":
  89. this.emitRequestedItem();
  90. if (this.href) {
  91. e.preventDefault();
  92. this.childLink.click();
  93. }
  94. break;
  95. case "Enter":
  96. this.emitRequestedItem();
  97. if (this.href) {
  98. this.childLink.click();
  99. }
  100. break;
  101. case "Escape":
  102. this.calciteDropdownCloseRequest.emit();
  103. break;
  104. case "Tab":
  105. case "ArrowUp":
  106. case "ArrowDown":
  107. case "Home":
  108. case "End":
  109. this.calciteDropdownItemKeyEvent.emit({ keyboardEvent: e });
  110. break;
  111. }
  112. e.preventDefault();
  113. }
  114. updateActiveItemOnChange(event) {
  115. const parentEmittedChange = event.composedPath().includes(this.parentDropdownGroupEl);
  116. if (parentEmittedChange) {
  117. this.requestedDropdownGroup = event.detail.requestedDropdownGroup;
  118. this.requestedDropdownItem = event.detail.requestedDropdownItem;
  119. this.determineActiveItem();
  120. }
  121. }
  122. //--------------------------------------------------------------------------
  123. //
  124. // Private Methods
  125. //
  126. //--------------------------------------------------------------------------
  127. initialize() {
  128. this.selectionMode = getElementProp(this.el, "selection-mode", "single");
  129. this.parentDropdownGroupEl = this.el.closest("calcite-dropdown-group");
  130. if (this.selectionMode === "none") {
  131. this.active = false;
  132. }
  133. }
  134. determineActiveItem() {
  135. switch (this.selectionMode) {
  136. case "multi":
  137. if (this.el === this.requestedDropdownItem) {
  138. this.active = !this.active;
  139. }
  140. break;
  141. case "single":
  142. if (this.el === this.requestedDropdownItem) {
  143. this.active = true;
  144. }
  145. else if (this.requestedDropdownGroup === this.parentDropdownGroupEl) {
  146. this.active = false;
  147. }
  148. break;
  149. case "none":
  150. this.active = false;
  151. break;
  152. }
  153. }
  154. emitRequestedItem() {
  155. this.calciteDropdownItemSelect.emit({
  156. requestedDropdownItem: this.el,
  157. requestedDropdownGroup: this.parentDropdownGroupEl
  158. });
  159. }
  160. static get is() { return "calcite-dropdown-item"; }
  161. static get encapsulation() { return "shadow"; }
  162. static get originalStyleUrls() { return {
  163. "$": ["dropdown-item.scss"]
  164. }; }
  165. static get styleUrls() { return {
  166. "$": ["dropdown-item.css"]
  167. }; }
  168. static get properties() { return {
  169. "active": {
  170. "type": "boolean",
  171. "mutable": true,
  172. "complexType": {
  173. "original": "boolean",
  174. "resolved": "boolean",
  175. "references": {}
  176. },
  177. "required": false,
  178. "optional": false,
  179. "docs": {
  180. "tags": [],
  181. "text": "Indicates whether the item is active."
  182. },
  183. "attribute": "active",
  184. "reflect": true,
  185. "defaultValue": "false"
  186. },
  187. "iconFlipRtl": {
  188. "type": "string",
  189. "mutable": false,
  190. "complexType": {
  191. "original": "FlipContext",
  192. "resolved": "\"both\" | \"end\" | \"start\"",
  193. "references": {
  194. "FlipContext": {
  195. "location": "import",
  196. "path": "../interfaces"
  197. }
  198. }
  199. },
  200. "required": false,
  201. "optional": true,
  202. "docs": {
  203. "tags": [],
  204. "text": "flip the icon(s) in rtl"
  205. },
  206. "attribute": "icon-flip-rtl",
  207. "reflect": true
  208. },
  209. "iconStart": {
  210. "type": "string",
  211. "mutable": false,
  212. "complexType": {
  213. "original": "string",
  214. "resolved": "string",
  215. "references": {}
  216. },
  217. "required": false,
  218. "optional": true,
  219. "docs": {
  220. "tags": [],
  221. "text": "optionally pass an icon to display at the start of an item - accepts calcite ui icon names"
  222. },
  223. "attribute": "icon-start",
  224. "reflect": true
  225. },
  226. "iconEnd": {
  227. "type": "string",
  228. "mutable": false,
  229. "complexType": {
  230. "original": "string",
  231. "resolved": "string",
  232. "references": {}
  233. },
  234. "required": false,
  235. "optional": true,
  236. "docs": {
  237. "tags": [],
  238. "text": "optionally pass an icon to display at the end of an item - accepts calcite ui icon names"
  239. },
  240. "attribute": "icon-end",
  241. "reflect": true
  242. },
  243. "href": {
  244. "type": "string",
  245. "mutable": false,
  246. "complexType": {
  247. "original": "string",
  248. "resolved": "string",
  249. "references": {}
  250. },
  251. "required": false,
  252. "optional": true,
  253. "docs": {
  254. "tags": [],
  255. "text": "optionally pass a href - used to determine if the component should render as anchor"
  256. },
  257. "attribute": "href",
  258. "reflect": true
  259. },
  260. "label": {
  261. "type": "string",
  262. "mutable": false,
  263. "complexType": {
  264. "original": "string",
  265. "resolved": "string",
  266. "references": {}
  267. },
  268. "required": false,
  269. "optional": true,
  270. "docs": {
  271. "tags": [],
  272. "text": "Applies to the aria-label attribute on the button or hyperlink"
  273. },
  274. "attribute": "label",
  275. "reflect": false
  276. },
  277. "rel": {
  278. "type": "string",
  279. "mutable": false,
  280. "complexType": {
  281. "original": "string",
  282. "resolved": "string",
  283. "references": {}
  284. },
  285. "required": false,
  286. "optional": true,
  287. "docs": {
  288. "tags": [],
  289. "text": "The rel attribute to apply to the hyperlink"
  290. },
  291. "attribute": "rel",
  292. "reflect": false
  293. },
  294. "target": {
  295. "type": "string",
  296. "mutable": false,
  297. "complexType": {
  298. "original": "string",
  299. "resolved": "string",
  300. "references": {}
  301. },
  302. "required": false,
  303. "optional": true,
  304. "docs": {
  305. "tags": [],
  306. "text": "The target attribute to apply to the hyperlink"
  307. },
  308. "attribute": "target",
  309. "reflect": false
  310. }
  311. }; }
  312. static get events() { return [{
  313. "method": "calciteDropdownItemSelect",
  314. "name": "calciteDropdownItemSelect",
  315. "bubbles": true,
  316. "cancelable": true,
  317. "composed": true,
  318. "docs": {
  319. "tags": [{
  320. "name": "internal",
  321. "text": undefined
  322. }],
  323. "text": ""
  324. },
  325. "complexType": {
  326. "original": "any",
  327. "resolved": "any",
  328. "references": {}
  329. }
  330. }, {
  331. "method": "calciteDropdownItemKeyEvent",
  332. "name": "calciteDropdownItemKeyEvent",
  333. "bubbles": true,
  334. "cancelable": true,
  335. "composed": true,
  336. "docs": {
  337. "tags": [{
  338. "name": "internal",
  339. "text": undefined
  340. }],
  341. "text": ""
  342. },
  343. "complexType": {
  344. "original": "ItemKeyboardEvent",
  345. "resolved": "ItemKeyboardEvent",
  346. "references": {
  347. "ItemKeyboardEvent": {
  348. "location": "import",
  349. "path": "../dropdown/interfaces"
  350. }
  351. }
  352. }
  353. }, {
  354. "method": "calciteDropdownCloseRequest",
  355. "name": "calciteDropdownCloseRequest",
  356. "bubbles": true,
  357. "cancelable": true,
  358. "composed": true,
  359. "docs": {
  360. "tags": [{
  361. "name": "internal",
  362. "text": undefined
  363. }],
  364. "text": ""
  365. },
  366. "complexType": {
  367. "original": "any",
  368. "resolved": "any",
  369. "references": {}
  370. }
  371. }]; }
  372. static get methods() { return {
  373. "setFocus": {
  374. "complexType": {
  375. "signature": "() => Promise<void>",
  376. "parameters": [],
  377. "references": {
  378. "Promise": {
  379. "location": "global"
  380. }
  381. },
  382. "return": "Promise<void>"
  383. },
  384. "docs": {
  385. "text": "Sets focus on the component.",
  386. "tags": []
  387. }
  388. }
  389. }; }
  390. static get elementRef() { return "el"; }
  391. static get listeners() { return [{
  392. "name": "click",
  393. "method": "onClick",
  394. "target": undefined,
  395. "capture": false,
  396. "passive": false
  397. }, {
  398. "name": "keydown",
  399. "method": "keyDownHandler",
  400. "target": undefined,
  401. "capture": false,
  402. "passive": false
  403. }, {
  404. "name": "calciteDropdownItemChange",
  405. "method": "updateActiveItemOnChange",
  406. "target": "body",
  407. "capture": false,
  408. "passive": false
  409. }]; }
  410. }