tab-title.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610
  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 { Build, Component, Element, Event, h, Host, Listen, Method, Prop, State, Watch } from "@stencil/core";
  7. import { guid } from "../../utils/guid";
  8. import { getElementDir, getElementProp, toAriaBoolean } from "../../utils/dom";
  9. import { createObserver } from "../../utils/observers";
  10. import { updateHostInteraction } from "../../utils/interactive";
  11. /**
  12. * @slot - A slot for adding text.
  13. */
  14. export class TabTitle {
  15. constructor() {
  16. //--------------------------------------------------------------------------
  17. //
  18. // Properties
  19. //
  20. //--------------------------------------------------------------------------
  21. /** Show this tab title as selected */
  22. this.active = false;
  23. /** Disable this tab title */
  24. this.disabled = false;
  25. /** @internal Parent tabs component bordered value */
  26. this.bordered = false;
  27. //--------------------------------------------------------------------------
  28. //
  29. // Private State/Props
  30. //
  31. //--------------------------------------------------------------------------
  32. /** watches for changing text content **/
  33. this.mutationObserver = createObserver("mutation", () => this.updateHasText());
  34. /** determine if there is slotted text for styling purposes */
  35. this.hasText = false;
  36. this.guid = `calcite-tab-title-${guid()}`;
  37. }
  38. activeTabChanged() {
  39. if (this.active) {
  40. this.emitActiveTab(false);
  41. }
  42. }
  43. //--------------------------------------------------------------------------
  44. //
  45. // Lifecycle
  46. //
  47. //--------------------------------------------------------------------------
  48. connectedCallback() {
  49. this.setupTextContentObserver();
  50. this.parentTabNavEl = this.el.closest("calcite-tab-nav");
  51. this.parentTabsEl = this.el.closest("calcite-tabs");
  52. }
  53. disconnectedCallback() {
  54. var _a, _b;
  55. (_a = this.mutationObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
  56. // Dispatching to body in order to be listened by other elements that are still connected to the DOM.
  57. (_b = document.body) === null || _b === void 0 ? void 0 : _b.dispatchEvent(new CustomEvent("calciteTabTitleUnregister", {
  58. detail: this.el
  59. }));
  60. }
  61. componentWillLoad() {
  62. if (Build.isBrowser) {
  63. this.updateHasText();
  64. }
  65. if (this.tab && this.active) {
  66. this.emitActiveTab(false);
  67. }
  68. }
  69. componentWillRender() {
  70. if (this.parentTabsEl) {
  71. this.layout = this.parentTabsEl.layout;
  72. this.position = this.parentTabsEl.position;
  73. this.scale = this.parentTabsEl.scale;
  74. this.bordered = this.parentTabsEl.bordered;
  75. }
  76. // handle case when tab-nav is only parent
  77. if (!this.parentTabsEl && this.parentTabNavEl) {
  78. this.position = getElementProp(this.parentTabNavEl, "position", this.position);
  79. this.scale = getElementProp(this.parentTabNavEl, "scale", this.scale);
  80. }
  81. }
  82. render() {
  83. const id = this.el.id || this.guid;
  84. const showSideBorders = this.bordered && !this.disabled && this.layout !== "center";
  85. const iconStartEl = (h("calcite-icon", { class: "calcite-tab-title--icon icon-start", flipRtl: this.iconFlipRtl === "start" || this.iconFlipRtl === "both", icon: this.iconStart, scale: "s" }));
  86. const iconEndEl = (h("calcite-icon", { class: "calcite-tab-title--icon icon-end", flipRtl: this.iconFlipRtl === "end" || this.iconFlipRtl === "both", icon: this.iconEnd, scale: "s" }));
  87. return (h(Host, { "aria-controls": this.controls, "aria-expanded": toAriaBoolean(this.active), id: id, role: "tab" },
  88. h("a", { class: {
  89. container: true,
  90. "container--has-text": this.hasText
  91. }, style: showSideBorders && { width: `${this.parentTabNavEl.indicatorWidth}px` } },
  92. this.iconStart ? iconStartEl : null,
  93. h("slot", null),
  94. this.iconEnd ? iconEndEl : null)));
  95. }
  96. async componentDidLoad() {
  97. this.calciteTabTitleRegister.emit(await this.getTabIdentifier());
  98. }
  99. componentDidRender() {
  100. updateHostInteraction(this, true);
  101. }
  102. //--------------------------------------------------------------------------
  103. //
  104. // Event Listeners
  105. //
  106. //--------------------------------------------------------------------------
  107. internalTabChangeHandler(event) {
  108. const targetTabsEl = event
  109. .composedPath()
  110. .find((el) => el.tagName === "CALCITE-TABS");
  111. if (targetTabsEl !== this.parentTabsEl) {
  112. return;
  113. }
  114. if (this.tab) {
  115. this.active = this.tab === event.detail.tab;
  116. }
  117. else {
  118. this.getTabIndex().then((index) => {
  119. this.active = index === event.detail.tab;
  120. });
  121. }
  122. event.stopPropagation();
  123. }
  124. onClick() {
  125. this.emitActiveTab();
  126. }
  127. keyDownHandler(e) {
  128. switch (e.key) {
  129. case " ":
  130. case "Enter":
  131. this.emitActiveTab();
  132. e.preventDefault();
  133. break;
  134. case "ArrowRight":
  135. if (getElementDir(this.el) === "ltr") {
  136. this.calciteTabsFocusNext.emit();
  137. }
  138. else {
  139. this.calciteTabsFocusPrevious.emit();
  140. }
  141. break;
  142. case "ArrowLeft":
  143. if (getElementDir(this.el) === "ltr") {
  144. this.calciteTabsFocusPrevious.emit();
  145. }
  146. else {
  147. this.calciteTabsFocusNext.emit();
  148. }
  149. break;
  150. }
  151. }
  152. //--------------------------------------------------------------------------
  153. //
  154. // Public Methods
  155. //
  156. //--------------------------------------------------------------------------
  157. /**
  158. * Return the index of this title within the nav
  159. */
  160. async getTabIndex() {
  161. return Array.prototype.indexOf.call(this.el.parentElement.querySelectorAll("calcite-tab-title"), this.el);
  162. }
  163. /**
  164. * @internal
  165. */
  166. async getTabIdentifier() {
  167. return this.tab ? this.tab : this.getTabIndex();
  168. }
  169. /**
  170. * @internal
  171. */
  172. async updateAriaInfo(tabIds = [], titleIds = []) {
  173. this.controls = tabIds[titleIds.indexOf(this.el.id)] || null;
  174. }
  175. updateHasText() {
  176. this.hasText = this.el.textContent.trim().length > 0;
  177. }
  178. setupTextContentObserver() {
  179. var _a;
  180. (_a = this.mutationObserver) === null || _a === void 0 ? void 0 : _a.observe(this.el, { childList: true, subtree: true });
  181. }
  182. emitActiveTab(userTriggered = true) {
  183. if (this.disabled) {
  184. return;
  185. }
  186. const payload = { tab: this.tab };
  187. this.calciteInternalTabsActivate.emit(payload);
  188. if (userTriggered) {
  189. this.calciteTabsActivate.emit(payload);
  190. }
  191. }
  192. static get is() { return "calcite-tab-title"; }
  193. static get encapsulation() { return "shadow"; }
  194. static get originalStyleUrls() { return {
  195. "$": ["tab-title.scss"]
  196. }; }
  197. static get styleUrls() { return {
  198. "$": ["tab-title.css"]
  199. }; }
  200. static get properties() { return {
  201. "active": {
  202. "type": "boolean",
  203. "mutable": true,
  204. "complexType": {
  205. "original": "boolean",
  206. "resolved": "boolean",
  207. "references": {}
  208. },
  209. "required": false,
  210. "optional": false,
  211. "docs": {
  212. "tags": [],
  213. "text": "Show this tab title as selected"
  214. },
  215. "attribute": "active",
  216. "reflect": true,
  217. "defaultValue": "false"
  218. },
  219. "disabled": {
  220. "type": "boolean",
  221. "mutable": false,
  222. "complexType": {
  223. "original": "boolean",
  224. "resolved": "boolean",
  225. "references": {}
  226. },
  227. "required": false,
  228. "optional": false,
  229. "docs": {
  230. "tags": [],
  231. "text": "Disable this tab title"
  232. },
  233. "attribute": "disabled",
  234. "reflect": true,
  235. "defaultValue": "false"
  236. },
  237. "iconEnd": {
  238. "type": "string",
  239. "mutable": false,
  240. "complexType": {
  241. "original": "string",
  242. "resolved": "string",
  243. "references": {}
  244. },
  245. "required": false,
  246. "optional": true,
  247. "docs": {
  248. "tags": [],
  249. "text": "optionally pass an icon to display at the end of a tab title - accepts calcite ui icon names"
  250. },
  251. "attribute": "icon-end",
  252. "reflect": true
  253. },
  254. "iconFlipRtl": {
  255. "type": "string",
  256. "mutable": false,
  257. "complexType": {
  258. "original": "FlipContext",
  259. "resolved": "\"both\" | \"end\" | \"start\"",
  260. "references": {
  261. "FlipContext": {
  262. "location": "import",
  263. "path": "../interfaces"
  264. }
  265. }
  266. },
  267. "required": false,
  268. "optional": true,
  269. "docs": {
  270. "tags": [],
  271. "text": "flip the icon(s) in rtl"
  272. },
  273. "attribute": "icon-flip-rtl",
  274. "reflect": true
  275. },
  276. "iconStart": {
  277. "type": "string",
  278. "mutable": false,
  279. "complexType": {
  280. "original": "string",
  281. "resolved": "string",
  282. "references": {}
  283. },
  284. "required": false,
  285. "optional": true,
  286. "docs": {
  287. "tags": [],
  288. "text": "optionally pass an icon to display at the start of a tab title - accepts calcite ui icon names"
  289. },
  290. "attribute": "icon-start",
  291. "reflect": true
  292. },
  293. "layout": {
  294. "type": "string",
  295. "mutable": true,
  296. "complexType": {
  297. "original": "TabLayout",
  298. "resolved": "\"center\" | \"inline\"",
  299. "references": {
  300. "TabLayout": {
  301. "location": "import",
  302. "path": "../tabs/interfaces"
  303. }
  304. }
  305. },
  306. "required": false,
  307. "optional": false,
  308. "docs": {
  309. "tags": [{
  310. "name": "internal",
  311. "text": "Parent tabs component layout value"
  312. }],
  313. "text": ""
  314. },
  315. "attribute": "layout",
  316. "reflect": true
  317. },
  318. "position": {
  319. "type": "string",
  320. "mutable": true,
  321. "complexType": {
  322. "original": "TabPosition",
  323. "resolved": "\"above\" | \"below\"",
  324. "references": {
  325. "TabPosition": {
  326. "location": "import",
  327. "path": "../tabs/interfaces"
  328. }
  329. }
  330. },
  331. "required": false,
  332. "optional": false,
  333. "docs": {
  334. "tags": [{
  335. "name": "internal",
  336. "text": "Parent tabs component or parent tab-nav component's position"
  337. }],
  338. "text": ""
  339. },
  340. "attribute": "position",
  341. "reflect": true
  342. },
  343. "scale": {
  344. "type": "string",
  345. "mutable": true,
  346. "complexType": {
  347. "original": "Scale",
  348. "resolved": "\"l\" | \"m\" | \"s\"",
  349. "references": {
  350. "Scale": {
  351. "location": "import",
  352. "path": "../interfaces"
  353. }
  354. }
  355. },
  356. "required": false,
  357. "optional": false,
  358. "docs": {
  359. "tags": [{
  360. "name": "internal",
  361. "text": "Parent tabs component or parent tab-nav component's scale"
  362. }],
  363. "text": ""
  364. },
  365. "attribute": "scale",
  366. "reflect": true
  367. },
  368. "bordered": {
  369. "type": "boolean",
  370. "mutable": true,
  371. "complexType": {
  372. "original": "boolean",
  373. "resolved": "boolean",
  374. "references": {}
  375. },
  376. "required": false,
  377. "optional": false,
  378. "docs": {
  379. "tags": [{
  380. "name": "internal",
  381. "text": "Parent tabs component bordered value"
  382. }],
  383. "text": ""
  384. },
  385. "attribute": "bordered",
  386. "reflect": true,
  387. "defaultValue": "false"
  388. },
  389. "tab": {
  390. "type": "string",
  391. "mutable": false,
  392. "complexType": {
  393. "original": "string",
  394. "resolved": "string",
  395. "references": {}
  396. },
  397. "required": false,
  398. "optional": true,
  399. "docs": {
  400. "tags": [],
  401. "text": "Optionally include a unique name for the tab title,\nbe sure to also set this name on the associated tab."
  402. },
  403. "attribute": "tab",
  404. "reflect": true
  405. }
  406. }; }
  407. static get states() { return {
  408. "controls": {},
  409. "hasText": {}
  410. }; }
  411. static get events() { return [{
  412. "method": "calciteTabsActivate",
  413. "name": "calciteTabsActivate",
  414. "bubbles": true,
  415. "cancelable": true,
  416. "composed": true,
  417. "docs": {
  418. "tags": [{
  419. "name": "see",
  420. "text": "[TabChangeEventDetail](https://github.com/Esri/calcite-components/blob/master/src/components/tab/interfaces.ts#L1)"
  421. }],
  422. "text": "Fires when a specific tab is activated. Emits the \"tab\" property or the index position."
  423. },
  424. "complexType": {
  425. "original": "TabChangeEventDetail",
  426. "resolved": "TabChangeEventDetail",
  427. "references": {
  428. "TabChangeEventDetail": {
  429. "location": "import",
  430. "path": "../tab/interfaces"
  431. }
  432. }
  433. }
  434. }, {
  435. "method": "calciteInternalTabsActivate",
  436. "name": "calciteInternalTabsActivate",
  437. "bubbles": true,
  438. "cancelable": true,
  439. "composed": true,
  440. "docs": {
  441. "tags": [{
  442. "name": "see",
  443. "text": "[TabChangeEventDetail](https://github.com/Esri/calcite-components/blob/master/src/components/tab/interfaces.ts#L1)"
  444. }, {
  445. "name": "internal",
  446. "text": undefined
  447. }],
  448. "text": "Fires when a specific tab is activated (`event.details`)"
  449. },
  450. "complexType": {
  451. "original": "TabChangeEventDetail",
  452. "resolved": "TabChangeEventDetail",
  453. "references": {
  454. "TabChangeEventDetail": {
  455. "location": "import",
  456. "path": "../tab/interfaces"
  457. }
  458. }
  459. }
  460. }, {
  461. "method": "calciteTabsFocusNext",
  462. "name": "calciteTabsFocusNext",
  463. "bubbles": true,
  464. "cancelable": true,
  465. "composed": true,
  466. "docs": {
  467. "tags": [{
  468. "name": "internal",
  469. "text": undefined
  470. }],
  471. "text": ""
  472. },
  473. "complexType": {
  474. "original": "any",
  475. "resolved": "any",
  476. "references": {}
  477. }
  478. }, {
  479. "method": "calciteTabsFocusPrevious",
  480. "name": "calciteTabsFocusPrevious",
  481. "bubbles": true,
  482. "cancelable": true,
  483. "composed": true,
  484. "docs": {
  485. "tags": [{
  486. "name": "internal",
  487. "text": undefined
  488. }],
  489. "text": ""
  490. },
  491. "complexType": {
  492. "original": "any",
  493. "resolved": "any",
  494. "references": {}
  495. }
  496. }, {
  497. "method": "calciteTabTitleRegister",
  498. "name": "calciteTabTitleRegister",
  499. "bubbles": true,
  500. "cancelable": true,
  501. "composed": true,
  502. "docs": {
  503. "tags": [{
  504. "name": "internal",
  505. "text": undefined
  506. }],
  507. "text": ""
  508. },
  509. "complexType": {
  510. "original": "TabID",
  511. "resolved": "number | string",
  512. "references": {
  513. "TabID": {
  514. "location": "import",
  515. "path": "../tabs/interfaces"
  516. }
  517. }
  518. }
  519. }]; }
  520. static get methods() { return {
  521. "getTabIndex": {
  522. "complexType": {
  523. "signature": "() => Promise<number>",
  524. "parameters": [],
  525. "references": {
  526. "Promise": {
  527. "location": "global"
  528. }
  529. },
  530. "return": "Promise<number>"
  531. },
  532. "docs": {
  533. "text": "Return the index of this title within the nav",
  534. "tags": []
  535. }
  536. },
  537. "getTabIdentifier": {
  538. "complexType": {
  539. "signature": "() => Promise<TabID>",
  540. "parameters": [],
  541. "references": {
  542. "Promise": {
  543. "location": "global"
  544. },
  545. "TabID": {
  546. "location": "import",
  547. "path": "../tabs/interfaces"
  548. }
  549. },
  550. "return": "Promise<TabID>"
  551. },
  552. "docs": {
  553. "text": "",
  554. "tags": [{
  555. "name": "internal",
  556. "text": undefined
  557. }]
  558. }
  559. },
  560. "updateAriaInfo": {
  561. "complexType": {
  562. "signature": "(tabIds?: string[], titleIds?: string[]) => Promise<void>",
  563. "parameters": [{
  564. "tags": [],
  565. "text": ""
  566. }, {
  567. "tags": [],
  568. "text": ""
  569. }],
  570. "references": {
  571. "Promise": {
  572. "location": "global"
  573. }
  574. },
  575. "return": "Promise<void>"
  576. },
  577. "docs": {
  578. "text": "",
  579. "tags": [{
  580. "name": "internal",
  581. "text": undefined
  582. }]
  583. }
  584. }
  585. }; }
  586. static get elementRef() { return "el"; }
  587. static get watchers() { return [{
  588. "propName": "active",
  589. "methodName": "activeTabChanged"
  590. }]; }
  591. static get listeners() { return [{
  592. "name": "calciteInternalTabChange",
  593. "method": "internalTabChangeHandler",
  594. "target": "body",
  595. "capture": false,
  596. "passive": false
  597. }, {
  598. "name": "click",
  599. "method": "onClick",
  600. "target": undefined,
  601. "capture": false,
  602. "passive": false
  603. }, {
  604. "name": "keydown",
  605. "method": "keyDownHandler",
  606. "target": undefined,
  607. "capture": false,
  608. "passive": false
  609. }]; }
  610. }