OLD | NEW |
1 /* | 1 /* |
2 * Copyright (C) 2010 Google Inc. All rights reserved. | 2 * Copyright (C) 2010 Google Inc. All rights reserved. |
3 * | 3 * |
4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
5 * modification, are permitted provided that the following conditions are | 5 * modification, are permitted provided that the following conditions are |
6 * met: | 6 * met: |
7 * | 7 * |
8 * * Redistributions of source code must retain the above copyright | 8 * * Redistributions of source code must retain the above copyright |
9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
10 * * Redistributions in binary form must reproduce the above | 10 * * Redistributions in binary form must reproduce the above |
(...skipping 25 matching lines...) Expand all Loading... |
36 super(true); | 36 super(true); |
37 this.registerRequiredCSS('ui/tabbedPane.css'); | 37 this.registerRequiredCSS('ui/tabbedPane.css'); |
38 this.element.classList.add('tabbed-pane'); | 38 this.element.classList.add('tabbed-pane'); |
39 this.contentElement.classList.add('tabbed-pane-shadow'); | 39 this.contentElement.classList.add('tabbed-pane-shadow'); |
40 this.contentElement.tabIndex = -1; | 40 this.contentElement.tabIndex = -1; |
41 this._headerElement = this.contentElement.createChild('div', 'tabbed-pane-he
ader'); | 41 this._headerElement = this.contentElement.createChild('div', 'tabbed-pane-he
ader'); |
42 this._headerContentsElement = this._headerElement.createChild('div', 'tabbed
-pane-header-contents'); | 42 this._headerContentsElement = this._headerElement.createChild('div', 'tabbed
-pane-header-contents'); |
43 this._tabSlider = createElementWithClass('div', 'tabbed-pane-tab-slider'); | 43 this._tabSlider = createElementWithClass('div', 'tabbed-pane-tab-slider'); |
44 this._tabsElement = this._headerContentsElement.createChild('div', 'tabbed-p
ane-header-tabs'); | 44 this._tabsElement = this._headerContentsElement.createChild('div', 'tabbed-p
ane-header-tabs'); |
45 this._tabsElement.setAttribute('role', 'tablist'); | 45 this._tabsElement.setAttribute('role', 'tablist'); |
| 46 this._tabsElement.addEventListener('keydown', this._keyDown.bind(this), fals
e); |
46 this._contentElement = this.contentElement.createChild('div', 'tabbed-pane-c
ontent'); | 47 this._contentElement = this.contentElement.createChild('div', 'tabbed-pane-c
ontent'); |
47 this._contentElement.setAttribute('role', 'tabpanel'); | 48 this._contentElement.setAttribute('role', 'tabpanel'); |
48 this._contentElement.createChild('content'); | 49 this._contentElement.createChild('content'); |
49 /** @type {!Array.<!UI.TabbedPaneTab>} */ | 50 /** @type {!Array.<!UI.TabbedPaneTab>} */ |
50 this._tabs = []; | 51 this._tabs = []; |
51 /** @type {!Array.<!UI.TabbedPaneTab>} */ | 52 /** @type {!Array.<!UI.TabbedPaneTab>} */ |
52 this._tabsHistory = []; | 53 this._tabsHistory = []; |
53 /** @type {!Map<string, !UI.TabbedPaneTab>} */ | 54 /** @type {!Map<string, !UI.TabbedPaneTab>} */ |
54 this._tabsById = new Map(); | 55 this._tabsById = new Map(); |
55 this._currentTabLocked = false; | 56 this._currentTabLocked = false; |
(...skipping 154 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
210 } | 211 } |
211 | 212 |
212 /** | 213 /** |
213 * @param {string} id | 214 * @param {string} id |
214 * @param {boolean=} userGesture | 215 * @param {boolean=} userGesture |
215 */ | 216 */ |
216 closeTab(id, userGesture) { | 217 closeTab(id, userGesture) { |
217 this.closeTabs([id], userGesture); | 218 this.closeTabs([id], userGesture); |
218 } | 219 } |
219 | 220 |
| 221 |
220 /** | 222 /** |
221 * @param {!Array.<string>} ids | 223 * @param {!Array.<string>} ids |
222 * @param {boolean=} userGesture | 224 * @param {boolean=} userGesture |
223 */ | 225 */ |
224 closeTabs(ids, userGesture) { | 226 closeTabs(ids, userGesture) { |
225 var focused = this.hasFocus(); | 227 var focused = this.hasFocus(); |
226 for (var i = 0; i < ids.length; ++i) | 228 for (var i = 0; i < ids.length; ++i) |
227 this._innerCloseTab(ids[i], userGesture); | 229 this._innerCloseTab(ids[i], userGesture); |
228 this._updateTabElements(); | 230 this._updateTabElements(); |
229 if (this._tabsHistory.length) | 231 if (this._tabsHistory.length) |
(...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
299 break; | 301 break; |
300 } | 302 } |
301 } | 303 } |
302 if (index === -1) | 304 if (index === -1) |
303 return []; | 305 return []; |
304 return this._tabs.slice(index + 1).map(function(tab) { | 306 return this._tabs.slice(index + 1).map(function(tab) { |
305 return tab.id; | 307 return tab.id; |
306 }); | 308 }); |
307 } | 309 } |
308 | 310 |
| 311 _viewHasFocus() { |
| 312 if (this.visibleView) |
| 313 return this.visibleView.hasFocus(); |
| 314 return this.contentElement === this.contentElement.getComponentRoot().active
Element; |
| 315 } |
| 316 |
309 /** | 317 /** |
310 * @param {string} id | 318 * @param {string} id |
311 * @param {boolean=} userGesture | 319 * @param {boolean=} userGesture |
312 * @return {boolean} | 320 * @return {boolean} |
313 */ | 321 */ |
314 selectTab(id, userGesture) { | 322 selectTab(id, userGesture) { |
315 if (this._currentTabLocked) | 323 if (this._currentTabLocked) |
316 return false; | 324 return false; |
317 var focused = this.hasFocus(); | 325 var focused = this._viewHasFocus(); |
318 var tab = this._tabsById.get(id); | 326 var tab = this._tabsById.get(id); |
319 if (!tab) | 327 if (!tab) |
320 return false; | 328 return false; |
321 if (this._currentTab && this._currentTab.id === id) | 329 if (this._currentTab && this._currentTab.id === id) |
322 return true; | 330 return true; |
323 | 331 |
324 this.suspendInvalidations(); | 332 this.suspendInvalidations(); |
325 this._hideCurrentTab(); | 333 this._hideCurrentTab(); |
326 this._showTab(tab); | 334 this._showTab(tab); |
327 this.resumeInvalidations(); | 335 this.resumeInvalidations(); |
(...skipping 213 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
541 */ | 549 */ |
542 _hideTabElement(tab) { | 550 _hideTabElement(tab) { |
543 this._tabsElement.removeChild(tab.tabElement); | 551 this._tabsElement.removeChild(tab.tabElement); |
544 tab._shown = false; | 552 tab._shown = false; |
545 } | 553 } |
546 | 554 |
547 _createDropDownButton() { | 555 _createDropDownButton() { |
548 var dropDownContainer = createElementWithClass('div', 'tabbed-pane-header-ta
bs-drop-down-container'); | 556 var dropDownContainer = createElementWithClass('div', 'tabbed-pane-header-ta
bs-drop-down-container'); |
549 var chevronIcon = UI.Icon.create('largeicon-chevron', 'chevron-icon'); | 557 var chevronIcon = UI.Icon.create('largeicon-chevron', 'chevron-icon'); |
550 dropDownContainer.appendChild(chevronIcon); | 558 dropDownContainer.appendChild(chevronIcon); |
| 559 dropDownContainer.addEventListener('click', this._onDropDownMouseDown.bind(t
his)); |
551 dropDownContainer.addEventListener('mousedown', this._onDropDownMouseDown.bi
nd(this)); | 560 dropDownContainer.addEventListener('mousedown', this._onDropDownMouseDown.bi
nd(this)); |
552 return dropDownContainer; | 561 return dropDownContainer; |
553 } | 562 } |
554 | 563 |
555 /** | 564 /** |
556 * @param {!Event} event | 565 * @param {!Event} event |
557 */ | 566 */ |
558 _onDropDownMouseDown(event) { | 567 _onDropDownMouseDown(event) { |
559 if (event.which !== 1) | 568 if (event.which !== 1) |
560 return; | 569 return; |
561 var menu = new UI.ContextMenu(event); | 570 var rect = this._dropDownButton.getBoundingClientRect(); |
| 571 var menu = new UI.ContextMenu(event, false, rect.left, rect.bottom); |
562 for (var i = 0; i < this._tabs.length; ++i) { | 572 for (var i = 0; i < this._tabs.length; ++i) { |
563 var tab = this._tabs[i]; | 573 var tab = this._tabs[i]; |
564 if (tab._shown) | 574 if (tab._shown) |
565 continue; | 575 continue; |
566 menu.appendCheckboxItem(tab.title, this._dropDownMenuItemSelected.bind(thi
s, tab), this._tabsHistory[0] === tab); | 576 menu.appendCheckboxItem(tab.title, this._dropDownMenuItemSelected.bind(thi
s, tab), this._tabsHistory[0] === tab); |
567 } | 577 } |
568 menu.show(); | 578 menu.show(); |
569 } | 579 } |
570 | 580 |
571 /** | 581 /** |
(...skipping 182 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
754 return; | 764 return; |
755 | 765 |
756 this._hideTab(this._currentTab); | 766 this._hideTab(this._currentTab); |
757 delete this._currentTab; | 767 delete this._currentTab; |
758 } | 768 } |
759 | 769 |
760 /** | 770 /** |
761 * @param {!UI.TabbedPaneTab} tab | 771 * @param {!UI.TabbedPaneTab} tab |
762 */ | 772 */ |
763 _showTab(tab) { | 773 _showTab(tab) { |
| 774 tab.tabElement.tabIndex = 0; |
764 tab.tabElement.classList.add('selected'); | 775 tab.tabElement.classList.add('selected'); |
765 UI.ARIAUtils.setSelected(tab.tabElement, true); | 776 UI.ARIAUtils.setSelected(tab.tabElement, true); |
766 tab.view.show(this.element); | 777 tab.view.show(this.element); |
767 this._updateTabSlider(); | 778 this._updateTabSlider(); |
768 } | 779 } |
769 | 780 |
770 _updateTabSlider() { | 781 _updateTabSlider() { |
771 if (!this._currentTab || !this._sliderEnabled) | 782 if (!this._currentTab || !this._sliderEnabled) |
772 return; | 783 return; |
773 var left = 0; | 784 var left = 0; |
774 for (var i = 0; i < this._tabs.length && this._currentTab !== this._tabs[i]
&& this._tabs[i]._shown; i++) | 785 for (var i = 0; i < this._tabs.length && this._currentTab !== this._tabs[i]
&& this._tabs[i]._shown; i++) |
775 left += this._tabs[i]._measuredWidth; | 786 left += this._tabs[i]._measuredWidth; |
776 var sliderWidth = this._currentTab._shown ? this._currentTab._measuredWidth
: this._dropDownButton.offsetWidth; | 787 var sliderWidth = this._currentTab._shown ? this._currentTab._measuredWidth
: this._dropDownButton.offsetWidth; |
777 var scaleFactor = window.devicePixelRatio >= 1.5 ? ' scaleY(0.75)' : ''; | 788 var scaleFactor = window.devicePixelRatio >= 1.5 ? ' scaleY(0.75)' : ''; |
778 this._tabSlider.style.transform = 'translateX(' + left + 'px)' + scaleFactor
; | 789 this._tabSlider.style.transform = 'translateX(' + left + 'px)' + scaleFactor
; |
779 this._tabSlider.style.width = sliderWidth + 'px'; | 790 this._tabSlider.style.width = sliderWidth + 'px'; |
780 | 791 |
781 if (this._tabSlider.parentElement !== this._headerContentsElement) | 792 if (this._tabSlider.parentElement !== this._headerContentsElement) |
782 this._headerContentsElement.appendChild(this._tabSlider); | 793 this._headerContentsElement.appendChild(this._tabSlider); |
783 } | 794 } |
784 | 795 |
785 /** | 796 /** |
786 * @param {!UI.TabbedPaneTab} tab | 797 * @param {!UI.TabbedPaneTab} tab |
787 */ | 798 */ |
788 _hideTab(tab) { | 799 _hideTab(tab) { |
| 800 tab.tabElement.removeAttribute('tabIndex'); |
789 tab.tabElement.classList.remove('selected'); | 801 tab.tabElement.classList.remove('selected'); |
790 tab.tabElement.setAttribute('aria-selected', 'false'); | 802 tab.tabElement.setAttribute('aria-selected', 'false'); |
791 tab.view.detach(); | 803 tab.view.detach(); |
792 } | 804 } |
793 | 805 |
794 /** | 806 /** |
795 * @override | 807 * @override |
796 * @return {!Array.<!Element>} | 808 * @return {!Array.<!Element>} |
797 */ | 809 */ |
798 elementsToRestoreScrollPositionsFor() { | 810 elementsToRestoreScrollPositionsFor() { |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
840 } | 852 } |
841 | 853 |
842 /** | 854 /** |
843 * @param {boolean} allow | 855 * @param {boolean} allow |
844 * @param {boolean=} automatic | 856 * @param {boolean=} automatic |
845 */ | 857 */ |
846 setAllowTabReorder(allow, automatic) { | 858 setAllowTabReorder(allow, automatic) { |
847 this._allowTabReorder = allow; | 859 this._allowTabReorder = allow; |
848 this._automaticReorder = automatic; | 860 this._automaticReorder = automatic; |
849 } | 861 } |
| 862 |
| 863 /** |
| 864 * @param {!Event} event |
| 865 */ |
| 866 _keyDown(event) { |
| 867 if (!this._currentTab) |
| 868 return; |
| 869 var nextTabElement = null; |
| 870 switch (event.key) { |
| 871 case 'ArrowUp': |
| 872 case 'ArrowLeft': |
| 873 nextTabElement = this._currentTab.tabElement.previousElementSibling; |
| 874 if (!nextTabElement && !this._dropDownButton.parentElement) |
| 875 nextTabElement = this._currentTab.tabElement.parentElement.lastElement
Child; |
| 876 break; |
| 877 case 'ArrowDown': |
| 878 case 'ArrowRight': |
| 879 nextTabElement = this._currentTab.tabElement.nextElementSibling; |
| 880 if (!nextTabElement && !this._dropDownButton.parentElement) |
| 881 nextTabElement = this._currentTab.tabElement.parentElement.firstElemen
tChild; |
| 882 break; |
| 883 case 'Enter': |
| 884 case 'Space': |
| 885 this._currentTab.view.focus(); |
| 886 return; |
| 887 default: |
| 888 return; |
| 889 } |
| 890 if (!nextTabElement) { |
| 891 this._dropDownButton.click(); |
| 892 return; |
| 893 } |
| 894 var tab = this._tabs.find(tab => tab.tabElement === nextTabElement); |
| 895 this.selectTab(tab.id, true); |
| 896 nextTabElement.focus(); |
| 897 } |
850 }; | 898 }; |
851 | 899 |
852 /** @enum {symbol} */ | 900 /** @enum {symbol} */ |
853 UI.TabbedPane.Events = { | 901 UI.TabbedPane.Events = { |
854 TabSelected: Symbol('TabSelected'), | 902 TabSelected: Symbol('TabSelected'), |
855 TabClosed: Symbol('TabClosed'), | 903 TabClosed: Symbol('TabClosed'), |
856 TabOrderChanged: Symbol('TabOrderChanged') | 904 TabOrderChanged: Symbol('TabOrderChanged') |
857 }; | 905 }; |
858 | 906 |
859 /** | 907 /** |
(...skipping 164 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1024 tabElement.__iconElement = iconContainer; | 1072 tabElement.__iconElement = iconContainer; |
1025 } | 1073 } |
1026 | 1074 |
1027 /** | 1075 /** |
1028 * @param {boolean} measuring | 1076 * @param {boolean} measuring |
1029 * @return {!Element} | 1077 * @return {!Element} |
1030 */ | 1078 */ |
1031 _createTabElement(measuring) { | 1079 _createTabElement(measuring) { |
1032 var tabElement = createElementWithClass('div', 'tabbed-pane-header-tab'); | 1080 var tabElement = createElementWithClass('div', 'tabbed-pane-header-tab'); |
1033 tabElement.id = 'tab-' + this._id; | 1081 tabElement.id = 'tab-' + this._id; |
1034 tabElement.tabIndex = -1; | |
1035 UI.ARIAUtils.markAsTab(tabElement); | 1082 UI.ARIAUtils.markAsTab(tabElement); |
1036 UI.ARIAUtils.setSelected(tabElement, false); | 1083 UI.ARIAUtils.setSelected(tabElement, false); |
1037 tabElement.selectTabForTest = this._tabbedPane.selectTab.bind(this._tabbedPa
ne, this.id, true); | 1084 tabElement.selectTabForTest = this._tabbedPane.selectTab.bind(this._tabbedPa
ne, this.id, true); |
1038 | 1085 |
1039 var titleElement = tabElement.createChild('span', 'tabbed-pane-header-tab-ti
tle'); | 1086 var titleElement = tabElement.createChild('span', 'tabbed-pane-header-tab-ti
tle'); |
1040 titleElement.textContent = this.title; | 1087 titleElement.textContent = this.title; |
1041 titleElement.title = this.tooltip || ''; | 1088 titleElement.title = this.tooltip || ''; |
1042 this._createIconElement(tabElement, titleElement, measuring); | 1089 this._createIconElement(tabElement, titleElement, measuring); |
1043 if (!measuring) | 1090 if (!measuring) |
1044 this._titleElement = titleElement; | 1091 this._titleElement = titleElement; |
(...skipping 180 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1225 * @param {!Array.<string>} ids | 1272 * @param {!Array.<string>} ids |
1226 */ | 1273 */ |
1227 closeTabs(tabbedPane, ids) {}, | 1274 closeTabs(tabbedPane, ids) {}, |
1228 | 1275 |
1229 /** | 1276 /** |
1230 * @param {string} tabId | 1277 * @param {string} tabId |
1231 * @param {!UI.ContextMenu} contextMenu | 1278 * @param {!UI.ContextMenu} contextMenu |
1232 */ | 1279 */ |
1233 onContextMenu(tabId, contextMenu) {} | 1280 onContextMenu(tabId, contextMenu) {} |
1234 }; | 1281 }; |
OLD | NEW |