Permalink
| <!-- | |
| Copyright (c) 2014 The Polymer Project Authors. All rights reserved. | |
| This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt | |
| The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt | |
| The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt | |
| Code distributed by Google as part of the polymer project is also | |
| subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt | |
| --> | |
| <!-- | |
| `paper-tabs` is a `core-selector` styled to look like tabs. Tabs make it easy to | |
| explore and switch between different views or functional aspects of an app, or | |
| to browse categorized data sets. | |
| Use `selected` property to get or set the selected tab. | |
| Example: | |
| <paper-tabs selected="0"> | |
| <paper-tab>TAB 1</paper-tab> | |
| <paper-tab>TAB 2</paper-tab> | |
| <paper-tab>TAB 3</paper-tab> | |
| </paper-tabs> | |
| See <a href="#paper-tab">paper-tab</a> for more information about | |
| `paper-tab`. | |
| A common usage for `paper-tabs` is to use it along with `core-pages` to switch | |
| between different views. | |
| <paper-tabs selected="{{selected}}"> | |
| <paper-tab>Tab 1</paper-tab> | |
| <paper-tab>Tab 2</paper-tab> | |
| <paper-tab>Tab 3</paper-tab> | |
| </paper-tabs> | |
| <core-pages selected="{{selected}}"> | |
| <div>Page 1</div> | |
| <div>Page 2</div> | |
| <div>Page 3</div> | |
| </core-pages> | |
| `paper-tabs` adapt to mobile/narrow layout when there is a `core-narrow` class set | |
| on itself or any of its ancestors. | |
| To use links in tabs, add `link` attribute to `paper-tabs` and put an `<a>` | |
| element in `paper-tab`. | |
| Example: | |
| <paper-tabs selected="0" link> | |
| <paper-tab> | |
| <a href="#link1" horizontal center-center layout>TAB ONE</a> | |
| </paper-tab> | |
| <paper-tab> | |
| <a href="#link2" horizontal center-center layout>TAB TWO</a> | |
| </paper-tab> | |
| <paper-tab> | |
| <a href="#link3" horizontal center-center layout>TAB THREE</a> | |
| </paper-tab> | |
| </paper-tabs> | |
| Styling tabs: | |
| To change the sliding bar color: | |
| paper-tabs.pink::shadow #selectionBar { | |
| background-color: #ff4081; | |
| } | |
| To change the ink ripple color: | |
| paper-tabs.pink paper-tab::shadow #ink { | |
| color: #ff4081; | |
| } | |
| @group Paper Elements | |
| @element paper-tabs | |
| @extends core-selector | |
| @mixins Polymer.CoreResizable https://github.com/polymer/core-resizable | |
| @homepage github.io | |
| --> | |
| <link rel="import" href="../core-selector/core-selector.html"> | |
| <link rel="import" href="../paper-icon-button/paper-icon-button.html"> | |
| <link rel="import" href="../core-resizable/core-resizable.html"> | |
| <link rel="import" href="paper-tab.html"> | |
| <polymer-element name="paper-tabs" extends="core-selector" attributes="noink nobar noslide scrollable hideScrollButton alignBottom" role="tablist" horizontal center layout> | |
| <template> | |
| <link rel="stylesheet" href="paper-tabs.css"> | |
| <div class="scroll-button" hidden?="{{!scrollable || hideScrollButton}}"> | |
| <paper-icon-button icon="chevron-left" class="{{ {hidden: leftHidden} | tokenList }}" on-down="{{holdLeft}}" on-up="{{releaseHold}}"></paper-icon-button> | |
| </div> | |
| <div id="tabsContainer" class="{{ {scrollable: scrollable} | tokenList }}" flex on-scroll="{{scroll}}" on-trackstart="{{trackStart}}"> | |
| <div id="tabsContent" horizontal layout?="{{!scrollable}}"> | |
| <shadow></shadow> | |
| <div id="selectionBar" hidden?="{{nobar}}" class="{{ {alignBottom: alignBottom} | tokenList }}" on-transitionend="{{barTransitionEnd}}"></div> | |
| </div> | |
| </div> | |
| <div class="scroll-button" hidden?="{{!scrollable || hideScrollButton}}"> | |
| <paper-icon-button icon="chevron-right" class="{{ {hidden: rightHidden} | tokenList }}" on-down="{{holdRight}}" on-up="{{releaseHold}}"></paper-icon-button> | |
| </div> | |
| </template> | |
| <script> | |
| Polymer(Polymer.mixin({ | |
| /** | |
| * If true, ink ripple effect is disabled. | |
| * | |
| * @attribute noink | |
| * @type boolean | |
| * @default false | |
| */ | |
| noink: false, | |
| /** | |
| * If true, the bottom bar to indicate the selected tab will not be shown. | |
| * | |
| * @attribute nobar | |
| * @type boolean | |
| * @default false | |
| */ | |
| nobar: false, | |
| /** | |
| * If true, the slide effect for the bottom bar is disabled. | |
| * | |
| * @attribute noslide | |
| * @type boolean | |
| * @default false | |
| */ | |
| noslide: false, | |
| /** | |
| * If true, tabs are scrollable and the tab width is based on the label width. | |
| * | |
| * @attribute scrollable | |
| * @type boolean | |
| * @default false | |
| */ | |
| scrollable: false, | |
| /** | |
| * If true, dragging on the tabs to scroll is disabled. | |
| * | |
| * @attribute disableDrag | |
| * @type boolean | |
| * @default false | |
| */ | |
| disableDrag: false, | |
| /** | |
| * If true, scroll buttons (left/right arrow) will be hidden for scrollable tabs. | |
| * | |
| * @attribute hideScrollButton | |
| * @type boolean | |
| * @default false | |
| */ | |
| hideScrollButton: false, | |
| /** | |
| * If true, the tabs are aligned to bottom (the selection bar appears at the top). | |
| * | |
| @attribute alignBottom | |
| @type boolean | |
| @default false | |
| */ | |
| alignBottom: false, | |
| eventDelegates: { | |
| 'core-resize': 'resizeHandler' | |
| }, | |
| activateEvent: 'tap', | |
| step: 10, | |
| holdDelay: 10, | |
| ready: function() { | |
| this.super(); | |
| this._trackxHandler = this.trackx.bind(this); | |
| Polymer.addEventListener(this.$.tabsContainer, 'trackx', this._trackxHandler); | |
| this._tabsObserver = new MutationObserver(this.updateBar.bind(this)); | |
| }, | |
| domReady: function() { | |
| this.async('resizeHandler'); | |
| this._tabsObserver.observe(this, {childList: true, subtree: true, characterData: true}); | |
| }, | |
| attached: function() { | |
| this.resizableAttachedHandler(); | |
| }, | |
| detached: function() { | |
| Polymer.removeEventListener(this.$.tabsContainer, 'trackx', this._trackxHandler); | |
| this._tabsObserver.disconnect(); | |
| this.resizableDetachedHandler(); | |
| }, | |
| trackStart: function(e) { | |
| if (!this.scrollable || this.disableDrag) { | |
| return; | |
| } | |
| var t = e.target; | |
| if (t && t.cancelRipple) { | |
| t.cancelRipple(); | |
| } | |
| this._startx = this.$.tabsContainer.scrollLeft; | |
| e.preventTap(); | |
| }, | |
| trackx: function(e) { | |
| if (!this.scrollable || this.disableDrag) { | |
| return; | |
| } | |
| this.$.tabsContainer.scrollLeft = this._startx - e.dx; | |
| }, | |
| resizeHandler: function() { | |
| this.scroll(); | |
| this.updateBar(); | |
| }, | |
| scroll: function() { | |
| if (!this.scrollable) { | |
| return; | |
| } | |
| var tc = this.$.tabsContainer; | |
| var l = tc.scrollLeft; | |
| this.leftHidden = l === 0; | |
| this.rightHidden = l === Math.max(0, (tc.scrollWidth - tc.clientWidth)); | |
| }, | |
| holdLeft: function() { | |
| this.holdJob = setInterval(this.scrollToLeft.bind(this), this.holdDelay); | |
| }, | |
| holdRight: function() { | |
| this.holdJob = setInterval(this.scrollToRight.bind(this), this.holdDelay); | |
| }, | |
| releaseHold: function() { | |
| clearInterval(this.holdJob); | |
| this.holdJob = null; | |
| }, | |
| scrollToLeft: function() { | |
| this.$.tabsContainer.scrollLeft -= this.step; | |
| }, | |
| scrollToRight: function() { | |
| this.$.tabsContainer.scrollLeft += this.step; | |
| }, | |
| /** | |
| * Invoke this to update the size and position of the bottom bar. Usually | |
| * you only need to call this if the `paper-tabs` is initially hidden and | |
| * later becomes visible. | |
| * | |
| * @method updateBar | |
| */ | |
| updateBar: function() { | |
| this.async('selectedItemChanged'); | |
| }, | |
| selectedItemChanged: function(old) { | |
| var oldIndex = this.selectedIndex; | |
| this.super(arguments); | |
| var s = this.$.selectionBar.style; | |
| if (!this.selectedItem) { | |
| s.width = 0; | |
| s.left = 0; | |
| return; | |
| } | |
| var r = this.$.tabsContent.getBoundingClientRect(); | |
| this._w = r.width; | |
| this._l = r.left; | |
| r = this.selectedItem.getBoundingClientRect(); | |
| this._sw = r.width; | |
| this._sl = r.left; | |
| this._sOffsetLeft = this._sl - this._l; | |
| if (this.noslide || old == null) { | |
| this.positionBarForSelected(); | |
| return; | |
| } | |
| var oldRect = old.getBoundingClientRect(); | |
| var m = 5; | |
| this.$.selectionBar.classList.add('expand'); | |
| if (oldIndex < this.selectedIndex) { | |
| s.width = this.calcPercent(this._sl + this._sw - oldRect.left) - m + '%'; | |
| this._transitionCounter = 1; | |
| } else { | |
| s.width = this.calcPercent(oldRect.left + oldRect.width - this._sl) - m + '%'; | |
| s.left = this.calcPercent(this._sOffsetLeft) + m + '%'; | |
| this._transitionCounter = 2; | |
| } | |
| if (this.scrollable) { | |
| this.scrollToSelectedIfNeeded(); | |
| } | |
| }, | |
| scrollToSelectedIfNeeded: function() { | |
| var scrollLeft = this.$.tabsContainer.scrollLeft; | |
| // scroll to selected if needed | |
| if (this._sOffsetLeft + this._sw < scrollLeft || | |
| this._sOffsetLeft - scrollLeft > this.$.tabsContainer.offsetWidth) { | |
| this.$.tabsContainer.scrollLeft = this._sOffsetLeft; | |
| } | |
| }, | |
| positionBarForSelected: function() { | |
| var s = this.$.selectionBar.style; | |
| s.width = this.calcPercent(this._sw) + '%'; | |
| s.left = this.calcPercent(this._sOffsetLeft) + '%'; | |
| }, | |
| calcPercent: function(w) { | |
| return 100 * w / this._w; | |
| }, | |
| barTransitionEnd: function(e) { | |
| this._transitionCounter--; | |
| var cl = this.$.selectionBar.classList; | |
| if (cl.contains('expand') && !this._transitionCounter) { | |
| cl.remove('expand'); | |
| cl.add('contract'); | |
| this.positionBarForSelected(); | |
| } else if (cl.contains('contract')) { | |
| cl.remove('contract'); | |
| } | |
| } | |
| }, Polymer.CoreResizable)); | |
| </script> | |
| </polymer-element> |