mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-05 23:38:38 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
12
devtools/client/shared/components/tabs/moz.build
Normal file
12
devtools/client/shared/components/tabs/moz.build
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'tabbar.css',
|
||||
'tabbar.js',
|
||||
'tabs.css',
|
||||
'tabs.js',
|
||||
)
|
||||
53
devtools/client/shared/components/tabs/tabbar.css
Normal file
53
devtools/client/shared/components/tabs/tabbar.css
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
.tabs .tabs-navigation {
|
||||
line-height: 15px;
|
||||
}
|
||||
|
||||
.tabs .tabs-navigation {
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.tabs .tabs-menu-item:first-child {
|
||||
border-inline-start-width: 0;
|
||||
}
|
||||
|
||||
.tabs .tabs-navigation .tabs-menu-item:focus {
|
||||
outline: var(--theme-focus-outline);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.tabs .tabs-menu-item.is-active {
|
||||
height: 23px;
|
||||
}
|
||||
|
||||
/* Firebug theme is using slightly different height. */
|
||||
.theme-firebug .tabs .tabs-navigation {
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
/* The tab takes entire horizontal space and individual tabs
|
||||
should stretch accordingly. Use flexbox for the behavior.
|
||||
Use also `overflow: hidden` so, 'overflow' and 'underflow'
|
||||
events are fired (it's utilized by the all-tabs-menu). */
|
||||
.tabs .tabs-navigation .tabs-menu {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.tabs .tabs-navigation .tabs-menu-item {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.tabs .tabs-navigation .tabs-menu-item a {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Firebug theme doesn't stretch the tabs. */
|
||||
.theme-firebug .tabs .tabs-navigation .tabs-menu-item {
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
204
devtools/client/shared/components/tabs/tabbar.js
Normal file
204
devtools/client/shared/components/tabs/tabbar.js
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { DOM, createClass, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
|
||||
const Tabs = createFactory(require("devtools/client/shared/components/tabs/tabs").Tabs);
|
||||
|
||||
const Menu = require("devtools/client/framework/menu");
|
||||
const MenuItem = require("devtools/client/framework/menu-item");
|
||||
|
||||
// Shortcuts
|
||||
const { div } = DOM;
|
||||
|
||||
/**
|
||||
* Renders Tabbar component.
|
||||
*/
|
||||
let Tabbar = createClass({
|
||||
displayName: "Tabbar",
|
||||
|
||||
propTypes: {
|
||||
onSelect: PropTypes.func,
|
||||
showAllTabsMenu: PropTypes.bool,
|
||||
toolbox: PropTypes.object,
|
||||
},
|
||||
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
showAllTabsMenu: false,
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function () {
|
||||
return {
|
||||
tabs: [],
|
||||
activeTab: 0
|
||||
};
|
||||
},
|
||||
|
||||
// Public API
|
||||
|
||||
addTab: function (id, title, selected = false, panel, url) {
|
||||
let tabs = this.state.tabs.slice();
|
||||
tabs.push({id, title, panel, url});
|
||||
|
||||
let newState = Object.assign({}, this.state, {
|
||||
tabs: tabs,
|
||||
});
|
||||
|
||||
if (selected) {
|
||||
newState.activeTab = tabs.length - 1;
|
||||
}
|
||||
|
||||
this.setState(newState, () => {
|
||||
if (this.props.onSelect && selected) {
|
||||
this.props.onSelect(id);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
toggleTab: function (tabId, isVisible) {
|
||||
let index = this.getTabIndex(tabId);
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let tabs = this.state.tabs.slice();
|
||||
tabs[index] = Object.assign({}, tabs[index], {
|
||||
isVisible: isVisible
|
||||
});
|
||||
|
||||
this.setState(Object.assign({}, this.state, {
|
||||
tabs: tabs,
|
||||
}));
|
||||
},
|
||||
|
||||
removeTab: function (tabId) {
|
||||
let index = this.getTabIndex(tabId);
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let tabs = this.state.tabs.slice();
|
||||
tabs.splice(index, 1);
|
||||
|
||||
this.setState(Object.assign({}, this.state, {
|
||||
tabs: tabs,
|
||||
}));
|
||||
},
|
||||
|
||||
select: function (tabId) {
|
||||
let index = this.getTabIndex(tabId);
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newState = Object.assign({}, this.state, {
|
||||
activeTab: index,
|
||||
});
|
||||
|
||||
this.setState(newState, () => {
|
||||
if (this.props.onSelect) {
|
||||
this.props.onSelect(tabId);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Helpers
|
||||
|
||||
getTabIndex: function (tabId) {
|
||||
let tabIndex = -1;
|
||||
this.state.tabs.forEach((tab, index) => {
|
||||
if (tab.id == tabId) {
|
||||
tabIndex = index;
|
||||
}
|
||||
});
|
||||
return tabIndex;
|
||||
},
|
||||
|
||||
getTabId: function (index) {
|
||||
return this.state.tabs[index].id;
|
||||
},
|
||||
|
||||
getCurrentTabId: function () {
|
||||
return this.state.tabs[this.state.activeTab].id;
|
||||
},
|
||||
|
||||
// Event Handlers
|
||||
|
||||
onTabChanged: function (index) {
|
||||
this.setState({
|
||||
activeTab: index
|
||||
});
|
||||
|
||||
if (this.props.onSelect) {
|
||||
this.props.onSelect(this.state.tabs[index].id);
|
||||
}
|
||||
},
|
||||
|
||||
onAllTabsMenuClick: function (event) {
|
||||
let menu = new Menu();
|
||||
let target = event.target;
|
||||
|
||||
// Generate list of menu items from the list of tabs.
|
||||
this.state.tabs.forEach(tab => {
|
||||
menu.append(new MenuItem({
|
||||
label: tab.title,
|
||||
type: "checkbox",
|
||||
checked: this.getCurrentTabId() == tab.id,
|
||||
click: () => this.select(tab.id),
|
||||
}));
|
||||
});
|
||||
|
||||
// Show a drop down menu with frames.
|
||||
// XXX Missing menu API for specifying target (anchor)
|
||||
// and relative position to it. See also:
|
||||
// https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/Method/openPopup
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1274551
|
||||
let rect = target.getBoundingClientRect();
|
||||
let screenX = target.ownerDocument.defaultView.mozInnerScreenX;
|
||||
let screenY = target.ownerDocument.defaultView.mozInnerScreenY;
|
||||
menu.popup(rect.left + screenX, rect.bottom + screenY, this.props.toolbox);
|
||||
|
||||
return menu;
|
||||
},
|
||||
|
||||
// Rendering
|
||||
|
||||
renderTab: function (tab) {
|
||||
if (typeof tab.panel === "function") {
|
||||
return tab.panel({
|
||||
key: tab.id,
|
||||
title: tab.title,
|
||||
id: tab.id,
|
||||
url: tab.url,
|
||||
});
|
||||
}
|
||||
|
||||
return tab.panel;
|
||||
},
|
||||
|
||||
render: function () {
|
||||
let tabs = this.state.tabs.map(tab => {
|
||||
return this.renderTab(tab);
|
||||
});
|
||||
|
||||
return (
|
||||
div({className: "devtools-sidebar-tabs"},
|
||||
Tabs({
|
||||
onAllTabsMenuClick: this.onAllTabsMenuClick,
|
||||
showAllTabsMenu: this.props.showAllTabsMenu,
|
||||
tabActive: this.state.activeTab,
|
||||
onAfterChange: this.onTabChanged},
|
||||
tabs
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = Tabbar;
|
||||
183
devtools/client/shared/components/tabs/tabs.css
Normal file
183
devtools/client/shared/components/tabs/tabs.css
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* Tabs General Styles */
|
||||
|
||||
.tabs {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tabs .tabs-menu {
|
||||
display: table;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tabs .tabs-menu-item {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.tabs .tabs-menu-item a {
|
||||
display: block;
|
||||
color: #A9A9A9;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid transparent;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tabs .tabs-menu-item a {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Make sure panel content takes entire vertical space.
|
||||
(minus the height of the tab bar) */
|
||||
.tabs .panels {
|
||||
height: calc(100% - 24px);
|
||||
}
|
||||
|
||||
.tabs .tab-panel {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tabs .all-tabs-menu {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
offset-inline-end: 0;
|
||||
width: 15px;
|
||||
height: 100%;
|
||||
border-inline-start: 1px solid var(--theme-splitter-color);
|
||||
background: url("chrome://devtools/skin/images/dropmarker.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-color: var(--theme-tab-toolbar-background);
|
||||
}
|
||||
|
||||
/* Light Theme */
|
||||
|
||||
.theme-dark .tabs,
|
||||
.theme-light .tabs {
|
||||
background: var(--theme-body-background);
|
||||
}
|
||||
|
||||
.theme-dark .tabs .tabs-navigation,
|
||||
.theme-light .tabs .tabs-navigation {
|
||||
position: relative;
|
||||
border-bottom: 1px solid var(--theme-splitter-color);
|
||||
background: var(--theme-tab-toolbar-background);
|
||||
}
|
||||
|
||||
.theme-dark .tabs .tabs-menu-item,
|
||||
.theme-light .tabs .tabs-menu-item {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
border-inline-start-width: 1px;
|
||||
border-color: var(--theme-splitter-color);
|
||||
}
|
||||
|
||||
.theme-dark .tabs .tabs-menu-item:last-child,
|
||||
.theme-light:not(.theme-firebug) .tabs .tabs-menu-item:last-child {
|
||||
border-inline-end-width: 1px;
|
||||
}
|
||||
|
||||
.theme-dark .tabs .tabs-menu-item a,
|
||||
.theme-light .tabs .tabs-menu-item a {
|
||||
color: var(--theme-content-color1);
|
||||
padding: 3px 15px;
|
||||
}
|
||||
|
||||
.theme-dark .tabs .tabs-menu-item:hover:not(.is-active),
|
||||
.theme-light .tabs .tabs-menu-item:hover:not(.is-active) {
|
||||
background-color: var(--toolbar-tab-hover);
|
||||
}
|
||||
|
||||
.theme-dark .tabs .tabs-menu-item:hover:active:not(.is-active),
|
||||
.theme-light .tabs .tabs-menu-item:hover:active:not(.is-active) {
|
||||
background-color: var(--toolbar-tab-hover-active);
|
||||
}
|
||||
|
||||
.theme-dark .tabs .tabs-menu-item.is-active,
|
||||
.theme-light .tabs .tabs-menu-item.is-active {
|
||||
background-color: var(--theme-selection-background);
|
||||
}
|
||||
|
||||
.theme-dark .tabs .tabs-menu-item.is-active a,
|
||||
.theme-light .tabs .tabs-menu-item.is-active a {
|
||||
color: var(--theme-selection-color);
|
||||
}
|
||||
|
||||
/* Dark Theme */
|
||||
|
||||
.theme-dark .tabs .tabs-menu-item a {
|
||||
color: var(--theme-body-color-alt);
|
||||
}
|
||||
|
||||
.theme-dark .tabs .tabs-menu-item:hover:not(.is-active) a {
|
||||
color: #CED3D9;
|
||||
}
|
||||
|
||||
.theme-dark .tabs .tabs-menu-item:hover:active a {
|
||||
color: var(--theme-selection-color);
|
||||
}
|
||||
|
||||
/* Firebug Theme */
|
||||
|
||||
.theme-firebug .tabs .tabs-navigation {
|
||||
background-image: linear-gradient(rgba(253, 253, 253, 0.2), rgba(253, 253, 253, 0));
|
||||
padding-top: 3px;
|
||||
padding-left: 3px;
|
||||
border-bottom: 1px solid rgb(170, 188, 207);
|
||||
}
|
||||
|
||||
.theme-firebug .tabs .tabs-menu {
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
.theme-firebug .tabs .tabs-menu-item.is-active,
|
||||
.theme-firebug .tabs .tabs-menu-item.is-active:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.theme-firebug .tabs .tabs-menu-item {
|
||||
position: relative;
|
||||
border-inline-start-width: 0;
|
||||
}
|
||||
|
||||
.theme-firebug .tabs .tabs-menu-item a {
|
||||
font-family: var(--proportional-font-family);
|
||||
font-weight: bold;
|
||||
color: var(--theme-body-color);
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
.theme-firebug .tabs .tabs-menu-item:hover:not(.is-active) a {
|
||||
border: 1px solid #C8C8C8;
|
||||
border-bottom: 1px solid transparent;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.theme-firebug .tabs .tabs-menu-item.is-active a {
|
||||
background-color: rgb(247, 251, 254);
|
||||
border: 1px solid rgb(170, 188, 207);
|
||||
border-bottom-color: transparent;
|
||||
color: var(--theme-body-color);
|
||||
}
|
||||
|
||||
.theme-firebug .tabs .tabs-menu-item:hover:active a {
|
||||
background-color: var(--toolbar-tab-hover-active);
|
||||
}
|
||||
|
||||
.theme-firebug .tabs .tabs-menu-item.is-active:hover:active a {
|
||||
background-color: var(--theme-selection-background);
|
||||
color: var(--theme-selection-color);
|
||||
}
|
||||
|
||||
.theme-firebug .tabs .tabs-menu-item a {
|
||||
border: 1px solid transparent;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
369
devtools/client/shared/components/tabs/tabs.js
Normal file
369
devtools/client/shared/components/tabs/tabs.js
Normal file
|
|
@ -0,0 +1,369 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
define(function (require, exports, module) {
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { DOM } = React;
|
||||
const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
|
||||
|
||||
/**
|
||||
* Renders simple 'tab' widget.
|
||||
*
|
||||
* Based on ReactSimpleTabs component
|
||||
* https://github.com/pedronauck/react-simpletabs
|
||||
*
|
||||
* Component markup (+CSS) example:
|
||||
*
|
||||
* <div class='tabs'>
|
||||
* <nav class='tabs-navigation'>
|
||||
* <ul class='tabs-menu'>
|
||||
* <li class='tabs-menu-item is-active'>Tab #1</li>
|
||||
* <li class='tabs-menu-item'>Tab #2</li>
|
||||
* </ul>
|
||||
* </nav>
|
||||
* <div class='panels'>
|
||||
* The content of active panel here
|
||||
* </div>
|
||||
* <div>
|
||||
*/
|
||||
let Tabs = React.createClass({
|
||||
displayName: "Tabs",
|
||||
|
||||
propTypes: {
|
||||
className: React.PropTypes.oneOfType([
|
||||
React.PropTypes.array,
|
||||
React.PropTypes.string,
|
||||
React.PropTypes.object
|
||||
]),
|
||||
tabActive: React.PropTypes.number,
|
||||
onMount: React.PropTypes.func,
|
||||
onBeforeChange: React.PropTypes.func,
|
||||
onAfterChange: React.PropTypes.func,
|
||||
children: React.PropTypes.oneOfType([
|
||||
React.PropTypes.array,
|
||||
React.PropTypes.element
|
||||
]).isRequired,
|
||||
showAllTabsMenu: React.PropTypes.bool,
|
||||
onAllTabsMenuClick: React.PropTypes.func,
|
||||
},
|
||||
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
tabActive: 0,
|
||||
showAllTabsMenu: false,
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function () {
|
||||
return {
|
||||
tabActive: this.props.tabActive,
|
||||
|
||||
// This array is used to store an information whether a tab
|
||||
// at specific index has already been created (e.g. selected
|
||||
// at least once).
|
||||
// If yes, it's rendered even if not currently selected.
|
||||
// This is because in some cases we don't want to re-create
|
||||
// tab content when it's being unselected/selected.
|
||||
// E.g. in case of an iframe being used as a tab-content
|
||||
// we want the iframe to stay in the DOM.
|
||||
created: [],
|
||||
|
||||
// True if tabs can't fit into available horizontal space.
|
||||
overflow: false,
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function () {
|
||||
let node = findDOMNode(this);
|
||||
node.addEventListener("keydown", this.onKeyDown, false);
|
||||
|
||||
// Register overflow listeners to manage visibility
|
||||
// of all-tabs-menu. This menu is displayed when there
|
||||
// is not enough h-space to render all tabs.
|
||||
// It allows the user to select a tab even if it's hidden.
|
||||
if (this.props.showAllTabsMenu) {
|
||||
node.addEventListener("overflow", this.onOverflow, false);
|
||||
node.addEventListener("underflow", this.onUnderflow, false);
|
||||
}
|
||||
|
||||
let index = this.state.tabActive;
|
||||
if (this.props.onMount) {
|
||||
this.props.onMount(index);
|
||||
}
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function (newProps) {
|
||||
// Check type of 'tabActive' props to see if it's valid
|
||||
// (it's 0-based index).
|
||||
if (typeof newProps.tabActive == "number") {
|
||||
let created = [...this.state.created];
|
||||
created[newProps.tabActive] = true;
|
||||
|
||||
this.setState(Object.assign({}, this.state, {
|
||||
tabActive: newProps.tabActive,
|
||||
created: created,
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
componentWillUnmount: function () {
|
||||
let node = findDOMNode(this);
|
||||
node.removeEventListener("keydown", this.onKeyDown, false);
|
||||
|
||||
if (this.props.showAllTabsMenu) {
|
||||
node.removeEventListener("overflow", this.onOverflow, false);
|
||||
node.removeEventListener("underflow", this.onUnderflow, false);
|
||||
}
|
||||
},
|
||||
|
||||
// DOM Events
|
||||
|
||||
onOverflow: function (event) {
|
||||
if (event.target.classList.contains("tabs-menu")) {
|
||||
this.setState({
|
||||
overflow: true
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onUnderflow: function (event) {
|
||||
if (event.target.classList.contains("tabs-menu")) {
|
||||
this.setState({
|
||||
overflow: false
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onKeyDown: function (event) {
|
||||
// Bail out if the focus isn't on a tab.
|
||||
if (!event.target.closest(".tabs-menu-item")) {
|
||||
return;
|
||||
}
|
||||
|
||||
let tabActive = this.state.tabActive;
|
||||
let tabCount = this.props.children.length;
|
||||
|
||||
switch (event.code) {
|
||||
case "ArrowRight":
|
||||
tabActive = Math.min(tabCount - 1, tabActive + 1);
|
||||
break;
|
||||
case "ArrowLeft":
|
||||
tabActive = Math.max(0, tabActive - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.state.tabActive != tabActive) {
|
||||
this.setActive(tabActive);
|
||||
}
|
||||
},
|
||||
|
||||
onClickTab: function (index, event) {
|
||||
this.setActive(index);
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
onAllTabsMenuClick: function (event) {
|
||||
if (this.props.onAllTabsMenuClick) {
|
||||
this.props.onAllTabsMenuClick(event);
|
||||
}
|
||||
},
|
||||
|
||||
// API
|
||||
|
||||
setActive: function (index) {
|
||||
let onAfterChange = this.props.onAfterChange;
|
||||
let onBeforeChange = this.props.onBeforeChange;
|
||||
|
||||
if (onBeforeChange) {
|
||||
let cancel = onBeforeChange(index);
|
||||
if (cancel) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let created = [...this.state.created];
|
||||
created[index] = true;
|
||||
|
||||
let newState = Object.assign({}, this.state, {
|
||||
tabActive: index,
|
||||
created: created
|
||||
});
|
||||
|
||||
this.setState(newState, () => {
|
||||
// Properly set focus on selected tab.
|
||||
let node = findDOMNode(this);
|
||||
let selectedTab = node.querySelector(".is-active > a");
|
||||
if (selectedTab) {
|
||||
selectedTab.focus();
|
||||
}
|
||||
|
||||
if (onAfterChange) {
|
||||
onAfterChange(index);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Rendering
|
||||
|
||||
renderMenuItems: function () {
|
||||
if (!this.props.children) {
|
||||
throw new Error("There must be at least one Tab");
|
||||
}
|
||||
|
||||
if (!Array.isArray(this.props.children)) {
|
||||
this.props.children = [this.props.children];
|
||||
}
|
||||
|
||||
let tabs = this.props.children
|
||||
.map(tab => {
|
||||
return typeof tab === "function" ? tab() : tab;
|
||||
}).filter(tab => {
|
||||
return tab;
|
||||
}).map((tab, index) => {
|
||||
let ref = ("tab-menu-" + index);
|
||||
let title = tab.props.title;
|
||||
let tabClassName = tab.props.className;
|
||||
let isTabSelected = this.state.tabActive === index;
|
||||
|
||||
let classes = [
|
||||
"tabs-menu-item",
|
||||
tabClassName,
|
||||
isTabSelected ? "is-active" : ""
|
||||
].join(" ");
|
||||
|
||||
// Set tabindex to -1 (except the selected tab) so, it's focusable,
|
||||
// but not reachable via sequential tab-key navigation.
|
||||
// Changing selected tab (and so, moving focus) is done through
|
||||
// left and right arrow keys.
|
||||
// See also `onKeyDown()` event handler.
|
||||
return (
|
||||
DOM.li({
|
||||
ref: ref,
|
||||
key: index,
|
||||
id: "tab-" + index,
|
||||
className: classes,
|
||||
role: "presentation",
|
||||
},
|
||||
DOM.a({
|
||||
tabIndex: this.state.tabActive === index ? 0 : -1,
|
||||
"aria-controls": "panel-" + index,
|
||||
"aria-selected": isTabSelected,
|
||||
role: "tab",
|
||||
onClick: this.onClickTab.bind(this, index),
|
||||
},
|
||||
title
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
// Display the menu only if there is not enough horizontal
|
||||
// space for all tabs (and overflow happened).
|
||||
let allTabsMenu = this.state.overflow ? (
|
||||
DOM.div({
|
||||
className: "all-tabs-menu",
|
||||
onClick: this.props.onAllTabsMenuClick
|
||||
})
|
||||
) : null;
|
||||
|
||||
return (
|
||||
DOM.nav({className: "tabs-navigation"},
|
||||
DOM.ul({className: "tabs-menu", role: "tablist"},
|
||||
tabs
|
||||
),
|
||||
allTabsMenu
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
renderPanels: function () {
|
||||
if (!this.props.children) {
|
||||
throw new Error("There must be at least one Tab");
|
||||
}
|
||||
|
||||
if (!Array.isArray(this.props.children)) {
|
||||
this.props.children = [this.props.children];
|
||||
}
|
||||
|
||||
let selectedIndex = this.state.tabActive;
|
||||
|
||||
let panels = this.props.children
|
||||
.map(tab => {
|
||||
return typeof tab === "function" ? tab() : tab;
|
||||
}).filter(tab => {
|
||||
return tab;
|
||||
}).map((tab, index) => {
|
||||
let selected = selectedIndex == index;
|
||||
|
||||
// Use 'visibility:hidden' + 'width/height:0' for hiding
|
||||
// content of non-selected tab. It's faster (not sure why)
|
||||
// than display:none and visibility:collapse.
|
||||
let style = {
|
||||
visibility: selected ? "visible" : "hidden",
|
||||
height: selected ? "100%" : "0",
|
||||
width: selected ? "100%" : "0",
|
||||
};
|
||||
|
||||
return (
|
||||
DOM.div({
|
||||
key: index,
|
||||
id: "panel-" + index,
|
||||
style: style,
|
||||
className: "tab-panel-box",
|
||||
role: "tabpanel",
|
||||
"aria-labelledby": "tab-" + index,
|
||||
},
|
||||
(selected || this.state.created[index]) ? tab : null
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
DOM.div({className: "panels"},
|
||||
panels
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
let classNames = ["tabs", this.props.className].join(" ");
|
||||
|
||||
return (
|
||||
DOM.div({className: classNames},
|
||||
this.renderMenuItems(),
|
||||
this.renderPanels()
|
||||
)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Renders simple tab 'panel'.
|
||||
*/
|
||||
let Panel = React.createClass({
|
||||
displayName: "Panel",
|
||||
|
||||
propTypes: {
|
||||
title: React.PropTypes.string.isRequired,
|
||||
children: React.PropTypes.oneOfType([
|
||||
React.PropTypes.array,
|
||||
React.PropTypes.element
|
||||
]).isRequired
|
||||
},
|
||||
|
||||
render: function () {
|
||||
return DOM.div({className: "tab-panel"},
|
||||
this.props.children
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Exports from this module
|
||||
exports.TabPanel = Panel;
|
||||
exports.Tabs = Tabs;
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue