tabs.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  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, Fragment } from "@stencil/core";
  7. import { SLOTS } from "./resources";
  8. /**
  9. * @slot - A slot for adding `calcite-tab`s.
  10. * @slot tab-nav - A slot for adding a `calcite-tab-nav`.
  11. */
  12. export class Tabs {
  13. constructor() {
  14. //--------------------------------------------------------------------------
  15. //
  16. // Public Properties
  17. //
  18. //--------------------------------------------------------------------------
  19. /**
  20. * Specifies the layout of the `calcite-tab-nav`, justifying the `calcite-tab-title`s to the start (`"inline"`), or across and centered (`"center"`).
  21. */
  22. this.layout = "inline";
  23. /**
  24. * Specifies the position of the component in relation to the `calcite-tab`s. The `"above"` and `"below"` values are deprecated.
  25. *
  26. */
  27. this.position = "top";
  28. /**
  29. * Specifies the size of the component.
  30. */
  31. this.scale = "m";
  32. /**
  33. * When `true`, the component will display with a folder style menu.
  34. */
  35. this.bordered = false;
  36. //--------------------------------------------------------------------------
  37. //
  38. // Events
  39. //
  40. //--------------------------------------------------------------------------
  41. //--------------------------------------------------------------------------
  42. //
  43. // Private State/Props
  44. //
  45. //--------------------------------------------------------------------------
  46. /**
  47. *
  48. * Stores an array of ids of `<calcite-tab-titles>`s to match up ARIA
  49. * attributes.
  50. */
  51. this.titles = [];
  52. /**
  53. *
  54. * Stores an array of ids of `<calcite-tab>`s to match up ARIA attributes.
  55. */
  56. this.tabs = [];
  57. }
  58. //--------------------------------------------------------------------------
  59. //
  60. // Lifecycle
  61. //
  62. //--------------------------------------------------------------------------
  63. render() {
  64. return (h(Fragment, null, h("slot", { name: SLOTS.tabNav }), h("section", null, h("slot", null))));
  65. }
  66. //--------------------------------------------------------------------------
  67. //
  68. // Event Listeners
  69. //
  70. //--------------------------------------------------------------------------
  71. /**
  72. * @param event
  73. * @internal
  74. */
  75. calciteInternalTabTitleRegister(event) {
  76. this.titles = [...this.titles, event.target];
  77. this.registryHandler();
  78. event.stopPropagation();
  79. }
  80. /**
  81. * @param event
  82. * @internal
  83. */
  84. calciteTabTitleUnregister(event) {
  85. this.titles = this.titles.filter((el) => el !== event.detail);
  86. this.registryHandler();
  87. event.stopPropagation();
  88. }
  89. /**
  90. * @param event
  91. * @internal
  92. */
  93. calciteInternalTabRegister(event) {
  94. this.tabs = [...this.tabs, event.target];
  95. this.registryHandler();
  96. event.stopPropagation();
  97. }
  98. /**
  99. * @param event
  100. * @internal
  101. */
  102. calciteTabUnregister(event) {
  103. this.tabs = this.tabs.filter((el) => el !== event.detail);
  104. this.registryHandler();
  105. event.stopPropagation();
  106. }
  107. //--------------------------------------------------------------------------
  108. //
  109. // Private Methods
  110. //
  111. //--------------------------------------------------------------------------
  112. /**
  113. *
  114. * Matches up elements from the internal `tabs` and `titles` to automatically
  115. * update the ARIA attributes and link `<calcite-tab>` and
  116. * `<calcite-tab-title>` components.
  117. */
  118. async registryHandler() {
  119. let tabIds;
  120. let titleIds;
  121. // determine if we are using `tab` based or `index` based tab identifiers.
  122. if (this.tabs.some((el) => el.tab) || this.titles.some((el) => el.tab)) {
  123. // if we are using `tab` based identifiers sort by `tab` to account for
  124. // possible out of order tabs and get the id of each tab
  125. tabIds = this.tabs.sort((a, b) => a.tab.localeCompare(b.tab)).map((el) => el.id);
  126. titleIds = this.titles.sort((a, b) => a.tab.localeCompare(b.tab)).map((el) => el.id);
  127. }
  128. else {
  129. // if we are using index based tabs then the `<calcite-tab>` and
  130. // `<calcite-tab-title>` might have been rendered out of order so the
  131. // order of `this.tabs` and `this.titles` might not reflect the DOM state,
  132. // and might not match each other so we need to get the index of all the
  133. // tabs and titles in the DOM order to match them up as a source of truth
  134. const tabDomIndexes = await Promise.all(this.tabs.map((el) => el.getTabIndex()));
  135. const titleDomIndexes = await Promise.all(this.titles.map((el) => el.getTabIndex()));
  136. // once we have the DOM order as a source of truth we can build the
  137. // matching tabIds and titleIds arrays
  138. tabIds = tabDomIndexes.reduce((ids, indexInDOM, registryIndex) => {
  139. ids[indexInDOM] = this.tabs[registryIndex].id;
  140. return ids;
  141. }, []);
  142. titleIds = titleDomIndexes.reduce((ids, indexInDOM, registryIndex) => {
  143. ids[indexInDOM] = this.titles[registryIndex].id;
  144. return ids;
  145. }, []);
  146. }
  147. // pass all our new aria information to each `<calcite-tab>` and
  148. // `<calcite-tab-title>` which will check if they can update their internal
  149. // `controlled` or `labeledBy` states and re-render if necessary
  150. this.tabs.forEach((el) => el.updateAriaInfo(tabIds, titleIds));
  151. this.titles.forEach((el) => el.updateAriaInfo(tabIds, titleIds));
  152. }
  153. static get is() { return "calcite-tabs"; }
  154. static get encapsulation() { return "shadow"; }
  155. static get originalStyleUrls() {
  156. return {
  157. "$": ["tabs.scss"]
  158. };
  159. }
  160. static get styleUrls() {
  161. return {
  162. "$": ["tabs.css"]
  163. };
  164. }
  165. static get properties() {
  166. return {
  167. "layout": {
  168. "type": "string",
  169. "mutable": false,
  170. "complexType": {
  171. "original": "TabLayout",
  172. "resolved": "\"center\" | \"inline\"",
  173. "references": {
  174. "TabLayout": {
  175. "location": "import",
  176. "path": "./interfaces"
  177. }
  178. }
  179. },
  180. "required": false,
  181. "optional": false,
  182. "docs": {
  183. "tags": [],
  184. "text": "Specifies the layout of the `calcite-tab-nav`, justifying the `calcite-tab-title`s to the start (`\"inline\"`), or across and centered (`\"center\"`)."
  185. },
  186. "attribute": "layout",
  187. "reflect": true,
  188. "defaultValue": "\"inline\""
  189. },
  190. "position": {
  191. "type": "string",
  192. "mutable": false,
  193. "complexType": {
  194. "original": "TabPosition",
  195. "resolved": "\"above\" | \"below\" | \"bottom\" | \"top\"",
  196. "references": {
  197. "TabPosition": {
  198. "location": "import",
  199. "path": "./interfaces"
  200. }
  201. }
  202. },
  203. "required": false,
  204. "optional": false,
  205. "docs": {
  206. "tags": [],
  207. "text": "Specifies the position of the component in relation to the `calcite-tab`s. The `\"above\"` and `\"below\"` values are deprecated."
  208. },
  209. "attribute": "position",
  210. "reflect": true,
  211. "defaultValue": "\"top\""
  212. },
  213. "scale": {
  214. "type": "string",
  215. "mutable": false,
  216. "complexType": {
  217. "original": "Scale",
  218. "resolved": "\"l\" | \"m\" | \"s\"",
  219. "references": {
  220. "Scale": {
  221. "location": "import",
  222. "path": "../interfaces"
  223. }
  224. }
  225. },
  226. "required": false,
  227. "optional": false,
  228. "docs": {
  229. "tags": [],
  230. "text": "Specifies the size of the component."
  231. },
  232. "attribute": "scale",
  233. "reflect": true,
  234. "defaultValue": "\"m\""
  235. },
  236. "bordered": {
  237. "type": "boolean",
  238. "mutable": true,
  239. "complexType": {
  240. "original": "boolean",
  241. "resolved": "boolean",
  242. "references": {}
  243. },
  244. "required": false,
  245. "optional": false,
  246. "docs": {
  247. "tags": [],
  248. "text": "When `true`, the component will display with a folder style menu."
  249. },
  250. "attribute": "bordered",
  251. "reflect": true,
  252. "defaultValue": "false"
  253. }
  254. };
  255. }
  256. static get states() {
  257. return {
  258. "titles": {},
  259. "tabs": {}
  260. };
  261. }
  262. static get elementRef() { return "el"; }
  263. static get listeners() {
  264. return [{
  265. "name": "calciteInternalTabTitleRegister",
  266. "method": "calciteInternalTabTitleRegister",
  267. "target": undefined,
  268. "capture": false,
  269. "passive": false
  270. }, {
  271. "name": "calciteTabTitleUnregister",
  272. "method": "calciteTabTitleUnregister",
  273. "target": "body",
  274. "capture": false,
  275. "passive": false
  276. }, {
  277. "name": "calciteInternalTabRegister",
  278. "method": "calciteInternalTabRegister",
  279. "target": undefined,
  280. "capture": false,
  281. "passive": false
  282. }, {
  283. "name": "calciteTabUnregister",
  284. "method": "calciteTabUnregister",
  285. "target": "body",
  286. "capture": false,
  287. "passive": false
  288. }];
  289. }
  290. }