coral-spectrum/coral-component-tabview/src/scripts/TabView.js
/**
* Copyright 2019 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import {BaseComponent} from '../../../coral-base-component';
import '../../../coral-component-panelstack';
import '../../../coral-component-tablist';
import {commons} from '../../../coral-utils';
import {Decorator} from '../../../coral-decorator';
/**
Enumeration for {@link TabView} orientations.
@typedef {Object} TabViewOrientationEnum
@property {String} HORIZONTAL
Tabs on top of the panels. This is the default.
@property {String} VERTICAL
Tabs are rendered on the side and match the height of the panels.
*/
const orientation = {
HORIZONTAL: 'horizontal',
VERTICAL: 'vertical'
};
// the tabview's base classname
const CLASSNAME = '_coral-TabView';
/**
@class Coral.TabView
@classdesc A TabView component is the wrapping container used to create the typical Tabbed pattern.
This is intended to be used with a {@link TabList} and {@link PanelStack}.
@htmltag coral-tabview
@extends {HTMLElement}
@extends {BaseComponent}
*/
const TabView = Decorator(class extends BaseComponent(HTMLElement) {
/** @ignore */
constructor() {
super();
// Prepare templates
this._elements = {
// Fetch or create the content zone elements
tabList: this.querySelector('coral-tablist') || document.createElement('coral-tablist'),
panelStack: this.querySelector('coral-panelstack') || document.createElement('coral-panelstack')
};
// Events
this._delegateEvents({
'coral-tablist:change > coral-tablist': '_onTabListChange',
'coral-panelstack:change > coral-panelstack': '_onPanelStackChange',
'coral-collection:add > coral-tablist': '_syncTabListAndPanelStack',
'coral-collection:remove > coral-tablist': '_syncTabListAndPanelStack',
'coral-collection:add > coral-panelstack': '_syncTabListAndPanelStack',
'coral-collection:remove > coral-panelstack': '_syncTabListAndPanelStack'
});
}
/**
The TabView's orientation. See {@link TabViewOrientationEnum}.
@type {String}
@default TabViewOrientationEnum.HORIZONTAL
@htmlattribute orientation
@htmlattributereflected
*/
get orientation() {
return this._elements.tabList.getAttribute('orientation') || orientation.HORIZONTAL;
}
set orientation(value) {
// We rely on the tablist orientation enum so don't need to double check enums
this._elements.tabList.setAttribute('orientation', value);
this._reflectAttribute('orientation', this.orientation);
this.classList[this.orientation === orientation.VERTICAL ? 'add' : 'remove'](`${CLASSNAME}--vertical`);
}
/**
The TabList which handles all the tabs.
@type {TabList}
@contentzone
*/
get tabList() {
return this._getContentZone(this._elements.tabList);
}
set tabList(value) {
// Support nested coral-tablist
if (value instanceof HTMLElement && !value.parentNode || value.parentNode === this) {
this._setContentZone('tabList', value, {
handle: 'tabList',
tagName: 'coral-tablist',
insert: function (tabs) {
tabs.setAttribute('tracking', 'off');
this.insertBefore(tabs, this._elements.panelStack || null);
}
});
}
}
/**
The PanelStack which contains all the panels.
@type {PanelStack}
@contentzone
*/
get panelStack() {
return this._getContentZone(this._elements.panelStack);
}
set panelStack(value) {
// Support nested coral-panelstack
if (value instanceof HTMLElement && !value.parentNode || value.parentNode === this) {
this._setContentZone('panelStack', value, {
handle: 'panelStack',
tagName: 'coral-panelstack',
insert: function (panels) {
this.appendChild(panels);
this._onNewPanelStack(panels);
}
});
}
}
/**
* This helps in syncing the tablist with new panelstack.
* This helpful when panelstack is changed for tabview dynamically.
* @param {PanelStack} panels new/updated panelstack
*/
_onNewPanelStack(panels) {
const tabs = this._elements.tabList;
// Bind the tablist and panel stack together, using the panel id
panels.id = panels.id || commons.getUID();
tabs.setAttribute('target', `#${panels.id}`);
if(tabs.selectedItem) {
tabs.selectedItem.selected = true;
}
}
/**
Detects a change in the TabList and triggers an event.
@private
*/
_onTabListChange(event) {
this.trigger('coral-tabview:change', {
selection: event.detail.selection,
oldSelection: event.detail.oldSelection
});
}
/** @private */
_onPanelStackChange(event) {
// everytime the panelstack changes, we verify that the tablist and panelstack are up to date
if (event.detail.selection) {
const tabSelector = event.detail.selection.getAttribute('aria-labelledby');
const tab = document.getElementById(tabSelector);
// we select the tab if this was not the case
if (tab) {
if (!tab.hasAttribute('selected')) {
tab.setAttribute('selected', '');
} else {
this._trackEvent('display', 'coral-tab', event, event.detail.selection);
}
}
}
}
/** @private */
_syncTabListAndPanelStack() {
this._elements.tabList.target = this._elements.tabList.target;
}
get _contentZones() {
return {
'coral-tablist': 'tabList',
'coral-panelstack': 'panelStack'
};
}
/**
Returns {@link TabView} orientation options.
@return {TabViewOrientationEnum}
*/
static get orientation() {
return orientation;
}
/** @ignore */
static get observedAttributes() {
return super.observedAttributes.concat(['orientation']);
}
/** @ignore */
render() {
super.render();
this.classList.add(CLASSNAME);
// Default reflected attributes
if (!this._orientation) {
this.orientation = this.orientation;
}
// Fetch or create the content zone elements
const tabs = this._elements.tabList;
const panels = this._elements.panelStack;
// Bind the tablist and panel stack together, using the panel id
panels.id = panels.id || commons.getUID();
tabs.setAttribute('target', `#${panels.id}`);
// Assign the content zones.
this.panelStack = panels;
this.tabList = tabs;
}
/**
Triggered when the {@link TabView} selected tab panel item has changed.
@typedef {CustomEvent} coral-tabview:change
@property {Tab} event.detail.selection
The new selected tab panel item.
@param {Tab} event.detail.oldSelection
The prior selected tab panel item.
*/
});
export default TabView;