tabs.js 8.5 KB

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