ExamplesPlaygroundReference Source

coral-spectrum/coral-component-tabview/src/scripts/TabView.js

  1. /**
  2. * Copyright 2019 Adobe. All rights reserved.
  3. * This file is licensed to you under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License. You may obtain a copy
  5. * of the License at http://www.apache.org/licenses/LICENSE-2.0
  6. *
  7. * Unless required by applicable law or agreed to in writing, software distributed under
  8. * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
  9. * OF ANY KIND, either express or implied. See the License for the specific language
  10. * governing permissions and limitations under the License.
  11. */
  12.  
  13. import {BaseComponent} from '../../../coral-base-component';
  14. import '../../../coral-component-panelstack';
  15. import '../../../coral-component-tablist';
  16. import {commons} from '../../../coral-utils';
  17. import {Decorator} from '../../../coral-decorator';
  18.  
  19. /**
  20. Enumeration for {@link TabView} orientations.
  21.  
  22. @typedef {Object} TabViewOrientationEnum
  23.  
  24. @property {String} HORIZONTAL
  25. Tabs on top of the panels. This is the default.
  26. @property {String} VERTICAL
  27. Tabs are rendered on the side and match the height of the panels.
  28. */
  29.  
  30. const orientation = {
  31. HORIZONTAL: 'horizontal',
  32. VERTICAL: 'vertical'
  33. };
  34.  
  35. // the tabview's base classname
  36. const CLASSNAME = '_coral-TabView';
  37.  
  38. /**
  39. @class Coral.TabView
  40. @classdesc A TabView component is the wrapping container used to create the typical Tabbed pattern.
  41. This is intended to be used with a {@link TabList} and {@link PanelStack}.
  42. @htmltag coral-tabview
  43. @extends {HTMLElement}
  44. @extends {BaseComponent}
  45. */
  46. const TabView = Decorator(class extends BaseComponent(HTMLElement) {
  47. /** @ignore */
  48. constructor() {
  49. super();
  50.  
  51. // Prepare templates
  52. this._elements = {
  53. // Fetch or create the content zone elements
  54. tabList: this.querySelector('coral-tablist') || document.createElement('coral-tablist'),
  55. panelStack: this.querySelector('coral-panelstack') || document.createElement('coral-panelstack')
  56. };
  57.  
  58. // Events
  59. this._delegateEvents({
  60. 'coral-tablist:change > coral-tablist': '_onTabListChange',
  61. 'coral-panelstack:change > coral-panelstack': '_onPanelStackChange',
  62. 'coral-collection:add > coral-tablist': '_syncTabListAndPanelStack',
  63. 'coral-collection:remove > coral-tablist': '_syncTabListAndPanelStack',
  64. 'coral-collection:add > coral-panelstack': '_syncTabListAndPanelStack',
  65. 'coral-collection:remove > coral-panelstack': '_syncTabListAndPanelStack'
  66. });
  67. }
  68.  
  69. /**
  70. The TabView's orientation. See {@link TabViewOrientationEnum}.
  71.  
  72. @type {String}
  73. @default TabViewOrientationEnum.HORIZONTAL
  74. @htmlattribute orientation
  75. @htmlattributereflected
  76. */
  77. get orientation() {
  78. return this._elements.tabList.getAttribute('orientation') || orientation.HORIZONTAL;
  79. }
  80.  
  81. set orientation(value) {
  82. // We rely on the tablist orientation enum so don't need to double check enums
  83. this._elements.tabList.setAttribute('orientation', value);
  84. this._reflectAttribute('orientation', this.orientation);
  85.  
  86. this.classList[this.orientation === orientation.VERTICAL ? 'add' : 'remove'](`${CLASSNAME}--vertical`);
  87. }
  88.  
  89. /**
  90. The TabList which handles all the tabs.
  91.  
  92. @type {TabList}
  93. @contentzone
  94. */
  95. get tabList() {
  96. return this._getContentZone(this._elements.tabList);
  97. }
  98.  
  99. set tabList(value) {
  100. // Support nested coral-tablist
  101. if (value instanceof HTMLElement && !value.parentNode || value.parentNode === this) {
  102. this._setContentZone('tabList', value, {
  103. handle: 'tabList',
  104. tagName: 'coral-tablist',
  105. insert: function (tabs) {
  106. tabs.setAttribute('tracking', 'off');
  107. this.insertBefore(tabs, this._elements.panelStack || null);
  108. }
  109. });
  110. }
  111. }
  112.  
  113. /**
  114. The PanelStack which contains all the panels.
  115.  
  116. @type {PanelStack}
  117. @contentzone
  118. */
  119. get panelStack() {
  120. return this._getContentZone(this._elements.panelStack);
  121. }
  122.  
  123. set panelStack(value) {
  124. // Support nested coral-panelstack
  125. if (value instanceof HTMLElement && !value.parentNode || value.parentNode === this) {
  126. this._setContentZone('panelStack', value, {
  127. handle: 'panelStack',
  128. tagName: 'coral-panelstack',
  129. insert: function (panels) {
  130. this.appendChild(panels);
  131. this._onNewPanelStack(panels);
  132. }
  133. });
  134. }
  135. }
  136.  
  137. /**
  138. * This helps in syncing the tablist with new panelstack.
  139. * This helpful when panelstack is changed for tabview dynamically.
  140. * @param {PanelStack} panels new/updated panelstack
  141. */
  142. _onNewPanelStack(panels) {
  143. const tabs = this._elements.tabList;
  144.  
  145. // Bind the tablist and panel stack together, using the panel id
  146. panels.id = panels.id || commons.getUID();
  147. tabs.setAttribute('target', `#${panels.id}`);
  148.  
  149. if(tabs.selectedItem) {
  150. tabs.selectedItem.selected = true;
  151. }
  152. }
  153.  
  154. /**
  155. Detects a change in the TabList and triggers an event.
  156.  
  157. @private
  158. */
  159. _onTabListChange(event) {
  160. this.trigger('coral-tabview:change', {
  161. selection: event.detail.selection,
  162. oldSelection: event.detail.oldSelection
  163. });
  164. }
  165.  
  166. /** @private */
  167. _onPanelStackChange(event) {
  168. // everytime the panelstack changes, we verify that the tablist and panelstack are up to date
  169. if (event.detail.selection) {
  170. const tabSelector = event.detail.selection.getAttribute('aria-labelledby');
  171. const tab = document.getElementById(tabSelector);
  172.  
  173. // we select the tab if this was not the case
  174. if (tab) {
  175. if (!tab.hasAttribute('selected')) {
  176. tab.setAttribute('selected', '');
  177. } else {
  178. this._trackEvent('display', 'coral-tab', event, event.detail.selection);
  179. }
  180. }
  181. }
  182. }
  183.  
  184. /** @private */
  185. _syncTabListAndPanelStack() {
  186. this._elements.tabList.target = this._elements.tabList.target;
  187. }
  188.  
  189. get _contentZones() {
  190. return {
  191. 'coral-tablist': 'tabList',
  192. 'coral-panelstack': 'panelStack'
  193. };
  194. }
  195.  
  196. /**
  197. Returns {@link TabView} orientation options.
  198.  
  199. @return {TabViewOrientationEnum}
  200. */
  201. static get orientation() {
  202. return orientation;
  203. }
  204.  
  205. /** @ignore */
  206. static get observedAttributes() {
  207. return super.observedAttributes.concat(['orientation']);
  208. }
  209.  
  210. /** @ignore */
  211. render() {
  212. super.render();
  213.  
  214. this.classList.add(CLASSNAME);
  215.  
  216. // Default reflected attributes
  217. if (!this._orientation) {
  218. this.orientation = this.orientation;
  219. }
  220.  
  221. // Fetch or create the content zone elements
  222. const tabs = this._elements.tabList;
  223. const panels = this._elements.panelStack;
  224.  
  225. // Bind the tablist and panel stack together, using the panel id
  226. panels.id = panels.id || commons.getUID();
  227. tabs.setAttribute('target', `#${panels.id}`);
  228.  
  229. // Assign the content zones.
  230. this.panelStack = panels;
  231. this.tabList = tabs;
  232. }
  233.  
  234. /**
  235. Triggered when the {@link TabView} selected tab panel item has changed.
  236.  
  237. @typedef {CustomEvent} coral-tabview:change
  238.  
  239. @property {Tab} event.detail.selection
  240. The new selected tab panel item.
  241. @param {Tab} event.detail.oldSelection
  242. The prior selected tab panel item.
  243. */
  244. });
  245.  
  246. export default TabView;