phosphor-tabs
Advanced tools
Comparing version 1.0.0-beta.4 to 1.0.0-rc.0
import { Message } from 'phosphor-messaging'; | ||
import { IChangedArgs } from 'phosphor-properties'; | ||
import { ISignal } from 'phosphor-signaling'; | ||
import { Title, Widget } from 'phosphor-widget'; | ||
/** | ||
* An object which can be added to a tab bar. | ||
*/ | ||
export interface ITabItem { | ||
/** | ||
* The title object which supplies the data for a tab. | ||
*/ | ||
title: Title; | ||
} | ||
/** | ||
* The arguments object for various tab bar signals. | ||
*/ | ||
export interface ITabIndexArgs { | ||
/** | ||
* The index of the tab. | ||
*/ | ||
index: number; | ||
/** | ||
* The tab item for the tab. | ||
*/ | ||
item: ITabItem; | ||
} | ||
/** | ||
* The arguments object for a `tabMoved` signal. | ||
@@ -17,2 +38,6 @@ */ | ||
toIndex: number; | ||
/** | ||
* The tab item for the tab. | ||
*/ | ||
item: ITabItem; | ||
} | ||
@@ -22,12 +47,4 @@ /** | ||
*/ | ||
export interface ITabDetachArgs { | ||
export interface ITabDetachArgs extends ITabIndexArgs { | ||
/** | ||
* The title being dragged by the user. | ||
*/ | ||
title: Title; | ||
/** | ||
* The DOM node for the tab being dragged. | ||
*/ | ||
node: HTMLElement; | ||
/** | ||
* The current client X position of the mouse. | ||
@@ -42,3 +59,3 @@ */ | ||
/** | ||
* A widget which displays titles as a row of selectable tabs. | ||
* A widget which displays tab items as a row of tabs. | ||
*/ | ||
@@ -51,2 +68,49 @@ export declare class TabBar extends Widget { | ||
/** | ||
* Create and initialize a tab node for a tab bar. | ||
* | ||
* @param title - The title to use for the initial tab state. | ||
* | ||
* @returns A new DOM node to use as a tab in a tab bar. | ||
* | ||
* #### Notes | ||
* It is not necessary to subscribe to the `changed` signal of the | ||
* title. The tab bar subscribes to that signal and will call the | ||
* [[updateTab]] static method automatically as needed. | ||
* | ||
* This method may be reimplemented to create custom tabs. | ||
*/ | ||
static createTab(title: Title): HTMLElement; | ||
/** | ||
* Update a tab node to reflect the current state of a title. | ||
* | ||
* @param tab - A tab node created by a call to [[createTab]]. | ||
* | ||
* @param title - The title object to use for the tab state. | ||
* | ||
* #### Notes | ||
* This is called automatically when the title state changes. | ||
* | ||
* If the [[createTab]] method is reimplemented, this method should | ||
* also be reimplemented so that the tab state is properly updated. | ||
*/ | ||
static updateTab(tab: HTMLElement, title: Title): void; | ||
/** | ||
* Get the close icon node for a given tab node. | ||
* | ||
* @param tab - A tab node created by a call to [[createTab]]. | ||
* | ||
* @returns The close icon node for the tab node. | ||
* | ||
* #### Notes | ||
* The close icon node is used to correctly process click events. | ||
* | ||
* If the [[createTab]] method is reimplemented, this method should | ||
* also be reimplemented so that the correct icon node is returned. | ||
*/ | ||
static tabCloseIcon(tab: HTMLElement): HTMLElement; | ||
/** | ||
* The static type of the constructor. | ||
*/ | ||
'constructor': typeof TabBar; | ||
/** | ||
* Construct a new tab bar. | ||
@@ -60,2 +124,6 @@ */ | ||
/** | ||
* A signal emitted when the current tab is changed. | ||
*/ | ||
currentChanged: ISignal<TabBar, ITabIndexArgs>; | ||
/** | ||
* A signal emitted when a tab is moved by the user. | ||
@@ -67,3 +135,3 @@ */ | ||
*/ | ||
tabCloseRequested: ISignal<TabBar, Title>; | ||
tabCloseRequested: ISignal<TabBar, ITabIndexArgs>; | ||
/** | ||
@@ -74,13 +142,9 @@ * A signal emitted when a tab is dragged beyond the detach threshold. | ||
/** | ||
* A signal emitted when the current title is changed. | ||
* Get the currently selected tab item. | ||
*/ | ||
currentChanged: ISignal<TabBar, IChangedArgs<Title>>; | ||
/** | ||
* Get the currently selected title. | ||
* Set the currently selected tab item. | ||
*/ | ||
currentItem: ITabItem; | ||
/** | ||
* Set the currently selected title. | ||
*/ | ||
currentTitle: Title; | ||
/** | ||
* Get whether the tabs are movable by the user. | ||
@@ -93,15 +157,6 @@ */ | ||
/** | ||
* Get the tab bar header node. | ||
* | ||
* #### Notes | ||
* This can be used to add extra header content. | ||
* | ||
* This is a read-only property. | ||
*/ | ||
headerNode: HTMLElement; | ||
/** | ||
* Get the tab bar body node. | ||
* | ||
* #### Notes | ||
* This can be used to add extra body content. | ||
* This node can be used to add extra content to the tab bar. | ||
* | ||
@@ -115,4 +170,6 @@ * This is a read-only property. | ||
* #### Notes | ||
* Modifying this node can lead to undefined behavior. | ||
* This is the node which holds the tab nodes. | ||
* | ||
* Modifying this node directly can lead to undefined behavior. | ||
* | ||
* This is a read-only property. | ||
@@ -122,61 +179,60 @@ */ | ||
/** | ||
* Get the tab bar footer node. | ||
* Get the number of tab items in the tab bar. | ||
* | ||
* #### Notes | ||
* This can be used to add extra footer content. | ||
* | ||
* This is a read-only property. | ||
* @returns The number of tab items in the tab bar. | ||
*/ | ||
footerNode: HTMLElement; | ||
itemCount(): number; | ||
/** | ||
* Get the number of title objects in the tab bar. | ||
* Get the tab item at the specified index. | ||
* | ||
* @returns The number of title objects in the tab bar. | ||
* @param index - The index of the tab item of interest. | ||
* | ||
* @returns The tab item at the specified index, or `undefined`. | ||
*/ | ||
titleCount(): number; | ||
itemAt(index: number): ITabItem; | ||
/** | ||
* Get the title object at the specified index. | ||
* Get the index of the specified tab item. | ||
* | ||
* @param index - The index of the title object of interest. | ||
* @param item - The tab item of interest. | ||
* | ||
* @returns The title at the specified index, or `undefined`. | ||
* @returns The index of the specified item, or `-1`. | ||
*/ | ||
titleAt(index: number): Title; | ||
itemIndex(item: ITabItem): number; | ||
/** | ||
* Get the index of the specified title object. | ||
* Add a tab item to the end of the tab bar. | ||
* | ||
* @param title - The title object of interest. | ||
* @param item - The tab item to add to the tab bar. | ||
* | ||
* @returns The index of the specified title, or `-1`. | ||
* #### Notes | ||
* If the item is already added to the tab bar, it will be moved. | ||
*/ | ||
titleIndex(title: Title): number; | ||
addItem(item: ITabItem): void; | ||
/** | ||
* Add a title object to the end of the tab bar. | ||
* Insert a tab item at the specified index. | ||
* | ||
* @param title - The title object to add to the tab bar. | ||
* @param index - The index at which to insert the item. | ||
* | ||
* @param item - The tab item to insert into the tab bar. | ||
* | ||
* #### Notes | ||
* If the title is already added to the tab bar, it will be moved. | ||
* If the item is already added to the tab bar, it will be moved. | ||
*/ | ||
addTitle(title: Title): void; | ||
insertItem(index: number, item: ITabItem): void; | ||
/** | ||
* Insert a title object at the specified index. | ||
* Remove a tab item from the tab bar. | ||
* | ||
* @param index - The index at which to insert the title. | ||
* @param item - The tab item to remove from the tab bar. | ||
* | ||
* @param title - The title object to insert into to the tab bar. | ||
* | ||
* #### Notes | ||
* If the title is already added to the tab bar, it will be moved. | ||
* If the item is not in the tab bar, this is a no-op. | ||
*/ | ||
insertTitle(index: number, title: Title): void; | ||
removeItem(item: ITabItem): void; | ||
/** | ||
* Remove a title object from the tab bar. | ||
* Get the tab node for the item at the given index. | ||
* | ||
* @param title - The title object to remove from the tab bar. | ||
* @param index - The index of the tab item of interest. | ||
* | ||
* #### Notes | ||
* If the title is not in the tab bar, this is a no-op. | ||
* @returns The tab node for the item, or `undefined`. | ||
*/ | ||
removeTitle(title: Title): void; | ||
tabAt(index: number): HTMLElement; | ||
/** | ||
@@ -238,13 +294,11 @@ * Release the mouse and restore the non-dragged tab positions. | ||
/** | ||
* Move a tab from one index to another. | ||
*/ | ||
private _moveTab(i, j); | ||
/** | ||
* Handle the `changed` signal of a title object. | ||
*/ | ||
private _onTitleChanged(sender); | ||
private _dirty; | ||
private _tabsMovable; | ||
private _titles; | ||
private _items; | ||
private _tabs; | ||
private _dirtySet; | ||
private _currentItem; | ||
private _dragData; | ||
} |
@@ -16,6 +16,4 @@ /*----------------------------------------------------------------------------- | ||
var phosphor_domutil_1 = require('phosphor-domutil'); | ||
var phosphor_properties_1 = require('phosphor-properties'); | ||
var phosphor_signaling_1 = require('phosphor-signaling'); | ||
var phosphor_widget_1 = require('phosphor-widget'); | ||
// TODO - need better solution for storing these class names | ||
/** | ||
@@ -26,20 +24,12 @@ * The class name added to TabBar instances. | ||
/** | ||
* The class name added to the tab bar header node. | ||
* The class name added to a tab bar body node. | ||
*/ | ||
var HEADER_CLASS = 'p-TabBar-header'; | ||
/** | ||
* The class name added to the tab bar body node. | ||
*/ | ||
var BODY_CLASS = 'p-TabBar-body'; | ||
/** | ||
* The class name added to the tab bar content node. | ||
* The class name added to a tab bar content node. | ||
*/ | ||
var CONTENT_CLASS = 'p-TabBar-content'; | ||
/** | ||
* The class name added to the tab bar footer node. | ||
* The class name added to a tab bar tab. | ||
*/ | ||
var FOOTER_CLASS = 'p-TabBar-footer'; | ||
/** | ||
* The class name added to a tab. | ||
*/ | ||
var TAB_CLASS = 'p-TabBar-tab'; | ||
@@ -49,11 +39,11 @@ /** | ||
*/ | ||
var TEXT_CLASS = 'p-TabBar-tab-text'; | ||
var TEXT_CLASS = 'p-TabBar-tabText'; | ||
/** | ||
* The class name added to a tab icon node. | ||
*/ | ||
var ICON_CLASS = 'p-TabBar-tab-icon'; | ||
var ICON_CLASS = 'p-TabBar-tabIcon'; | ||
/** | ||
* The class name added to a tab close node. | ||
* The class name added to a tab close icon node. | ||
*/ | ||
var CLOSE_CLASS = 'p-TabBar-tab-close'; | ||
var CLOSE_CLASS = 'p-TabBar-tabCloseIcon'; | ||
/** | ||
@@ -84,3 +74,3 @@ * The class name added to a tab bar and tab when dragging. | ||
/** | ||
* A widget which displays titles as a row of selectable tabs. | ||
* A widget which displays tab items as a row of tabs. | ||
*/ | ||
@@ -94,5 +84,7 @@ var TabBar = (function (_super) { | ||
_super.call(this); | ||
this._dirty = false; | ||
this._tabsMovable = false; | ||
this._titles = []; | ||
this._items = []; | ||
this._tabs = []; | ||
this._dirtySet = new Set(); | ||
this._currentItem = null; | ||
this._dragData = null; | ||
@@ -106,17 +98,79 @@ this.addClass(TAB_BAR_CLASS); | ||
var node = document.createElement('div'); | ||
var header = document.createElement('div'); | ||
var body = document.createElement('div'); | ||
var content = document.createElement('ul'); | ||
var footer = document.createElement('div'); | ||
header.className = HEADER_CLASS; | ||
body.className = BODY_CLASS; | ||
content.className = CONTENT_CLASS; | ||
footer.className = FOOTER_CLASS; | ||
body.appendChild(content); | ||
node.appendChild(header); | ||
node.appendChild(body); | ||
node.appendChild(footer); | ||
return node; | ||
}; | ||
/** | ||
* Create and initialize a tab node for a tab bar. | ||
* | ||
* @param title - The title to use for the initial tab state. | ||
* | ||
* @returns A new DOM node to use as a tab in a tab bar. | ||
* | ||
* #### Notes | ||
* It is not necessary to subscribe to the `changed` signal of the | ||
* title. The tab bar subscribes to that signal and will call the | ||
* [[updateTab]] static method automatically as needed. | ||
* | ||
* This method may be reimplemented to create custom tabs. | ||
*/ | ||
TabBar.createTab = function (title) { | ||
var node = document.createElement('li'); | ||
var icon = document.createElement('span'); | ||
var text = document.createElement('span'); | ||
var close = document.createElement('span'); | ||
node.className = TAB_CLASS; | ||
icon.className = ICON_CLASS; | ||
text.className = TEXT_CLASS; | ||
close.className = CLOSE_CLASS; | ||
node.appendChild(icon); | ||
node.appendChild(text); | ||
node.appendChild(close); | ||
this.updateTab(node, title); | ||
return node; | ||
}; | ||
/** | ||
* Update a tab node to reflect the current state of a title. | ||
* | ||
* @param tab - A tab node created by a call to [[createTab]]. | ||
* | ||
* @param title - The title object to use for the tab state. | ||
* | ||
* #### Notes | ||
* This is called automatically when the title state changes. | ||
* | ||
* If the [[createTab]] method is reimplemented, this method should | ||
* also be reimplemented so that the tab state is properly updated. | ||
*/ | ||
TabBar.updateTab = function (tab, title) { | ||
var tabInfix = title.className ? ' ' + title.className : ''; | ||
var tabSuffix = title.closable ? ' ' + CLOSABLE_CLASS : ''; | ||
var iconSuffix = title.icon ? ' ' + title.icon : ''; | ||
var icon = tab.firstChild; | ||
var text = icon.nextSibling; | ||
tab.className = TAB_CLASS + tabInfix + tabSuffix; | ||
icon.className = ICON_CLASS + iconSuffix; | ||
text.textContent = title.text; | ||
}; | ||
/** | ||
* Get the close icon node for a given tab node. | ||
* | ||
* @param tab - A tab node created by a call to [[createTab]]. | ||
* | ||
* @returns The close icon node for the tab node. | ||
* | ||
* #### Notes | ||
* The close icon node is used to correctly process click events. | ||
* | ||
* If the [[createTab]] method is reimplemented, this method should | ||
* also be reimplemented so that the correct icon node is returned. | ||
*/ | ||
TabBar.tabCloseIcon = function (tab) { | ||
return tab.lastChild; | ||
}; | ||
/** | ||
* Dispose of the resources held by the widget. | ||
@@ -126,5 +180,18 @@ */ | ||
this._releaseMouse(); | ||
this._titles.length = 0; | ||
this._tabs.length = 0; | ||
this._items.length = 0; | ||
this._dirtySet.clear(); | ||
this._currentItem = null; | ||
_super.prototype.dispose.call(this); | ||
}; | ||
Object.defineProperty(TabBar.prototype, "currentChanged", { | ||
/** | ||
* A signal emitted when the current tab is changed. | ||
*/ | ||
get: function () { | ||
return TabBarPrivate.currentChangedSignal.bind(this); | ||
}, | ||
enumerable: true, | ||
configurable: true | ||
}); | ||
Object.defineProperty(TabBar.prototype, "tabMoved", { | ||
@@ -160,24 +227,25 @@ /** | ||
}); | ||
Object.defineProperty(TabBar.prototype, "currentChanged", { | ||
Object.defineProperty(TabBar.prototype, "currentItem", { | ||
/** | ||
* A signal emitted when the current title is changed. | ||
* Get the currently selected tab item. | ||
*/ | ||
get: function () { | ||
return TabBarPrivate.currentChangedSignal.bind(this); | ||
return this._currentItem; | ||
}, | ||
enumerable: true, | ||
configurable: true | ||
}); | ||
Object.defineProperty(TabBar.prototype, "currentTitle", { | ||
/** | ||
* Get the currently selected title. | ||
* Set the currently selected tab item. | ||
*/ | ||
get: function () { | ||
return TabBarPrivate.currentTitleProperty.get(this); | ||
}, | ||
/** | ||
* Set the currently selected title. | ||
*/ | ||
set: function (value) { | ||
TabBarPrivate.currentTitleProperty.set(this, value); | ||
var item = value || null; | ||
if (this._currentItem === item) { | ||
return; | ||
} | ||
var index = item ? this._items.indexOf(item) : -1; | ||
if (item && index === -1) { | ||
console.warn('Tab item not contained in tab bar.'); | ||
return; | ||
} | ||
this._currentItem = item; | ||
this.currentChanged.emit({ index: index, item: item }); | ||
this.update(); | ||
}, | ||
@@ -203,17 +271,2 @@ enumerable: true, | ||
}); | ||
Object.defineProperty(TabBar.prototype, "headerNode", { | ||
/** | ||
* Get the tab bar header node. | ||
* | ||
* #### Notes | ||
* This can be used to add extra header content. | ||
* | ||
* This is a read-only property. | ||
*/ | ||
get: function () { | ||
return this.node.getElementsByClassName(HEADER_CLASS)[0]; | ||
}, | ||
enumerable: true, | ||
configurable: true | ||
}); | ||
Object.defineProperty(TabBar.prototype, "bodyNode", { | ||
@@ -224,3 +277,3 @@ /** | ||
* #### Notes | ||
* This can be used to add extra body content. | ||
* This node can be used to add extra content to the tab bar. | ||
* | ||
@@ -240,4 +293,6 @@ * This is a read-only property. | ||
* #### Notes | ||
* Modifying this node can lead to undefined behavior. | ||
* This is the node which holds the tab nodes. | ||
* | ||
* Modifying this node directly can lead to undefined behavior. | ||
* | ||
* This is a read-only property. | ||
@@ -251,72 +306,55 @@ */ | ||
}); | ||
Object.defineProperty(TabBar.prototype, "footerNode", { | ||
/** | ||
* Get the tab bar footer node. | ||
* | ||
* #### Notes | ||
* This can be used to add extra footer content. | ||
* | ||
* This is a read-only property. | ||
*/ | ||
get: function () { | ||
return this.node.getElementsByClassName(FOOTER_CLASS)[0]; | ||
}, | ||
enumerable: true, | ||
configurable: true | ||
}); | ||
/** | ||
* Get the number of title objects in the tab bar. | ||
* Get the number of tab items in the tab bar. | ||
* | ||
* @returns The number of title objects in the tab bar. | ||
* @returns The number of tab items in the tab bar. | ||
*/ | ||
TabBar.prototype.titleCount = function () { | ||
return this._titles.length; | ||
TabBar.prototype.itemCount = function () { | ||
return this._items.length; | ||
}; | ||
/** | ||
* Get the title object at the specified index. | ||
* Get the tab item at the specified index. | ||
* | ||
* @param index - The index of the title object of interest. | ||
* @param index - The index of the tab item of interest. | ||
* | ||
* @returns The title at the specified index, or `undefined`. | ||
* @returns The tab item at the specified index, or `undefined`. | ||
*/ | ||
TabBar.prototype.titleAt = function (index) { | ||
return this._titles[index]; | ||
TabBar.prototype.itemAt = function (index) { | ||
return this._items[index]; | ||
}; | ||
/** | ||
* Get the index of the specified title object. | ||
* Get the index of the specified tab item. | ||
* | ||
* @param title - The title object of interest. | ||
* @param item - The tab item of interest. | ||
* | ||
* @returns The index of the specified title, or `-1`. | ||
* @returns The index of the specified item, or `-1`. | ||
*/ | ||
TabBar.prototype.titleIndex = function (title) { | ||
return this._titles.indexOf(title); | ||
TabBar.prototype.itemIndex = function (item) { | ||
return this._items.indexOf(item); | ||
}; | ||
/** | ||
* Add a title object to the end of the tab bar. | ||
* Add a tab item to the end of the tab bar. | ||
* | ||
* @param title - The title object to add to the tab bar. | ||
* @param item - The tab item to add to the tab bar. | ||
* | ||
* #### Notes | ||
* If the title is already added to the tab bar, it will be moved. | ||
* If the item is already added to the tab bar, it will be moved. | ||
*/ | ||
TabBar.prototype.addTitle = function (title) { | ||
this.insertTitle(this.titleCount(), title); | ||
TabBar.prototype.addItem = function (item) { | ||
this.insertItem(this.itemCount(), item); | ||
}; | ||
/** | ||
* Insert a title object at the specified index. | ||
* Insert a tab item at the specified index. | ||
* | ||
* @param index - The index at which to insert the title. | ||
* @param index - The index at which to insert the item. | ||
* | ||
* @param title - The title object to insert into to the tab bar. | ||
* @param item - The tab item to insert into the tab bar. | ||
* | ||
* #### Notes | ||
* If the title is already added to the tab bar, it will be moved. | ||
* If the item is already added to the tab bar, it will be moved. | ||
*/ | ||
TabBar.prototype.insertTitle = function (index, title) { | ||
// Release the mouse before making changes. | ||
TabBar.prototype.insertItem = function (index, item) { | ||
this._releaseMouse(); | ||
// Insert the new title or move an existing title. | ||
var n = this.titleCount(); | ||
var i = this.titleIndex(title); | ||
var n = this._items.length; | ||
var i = this._items.indexOf(item); | ||
var j = Math.max(0, Math.min(index | 0, n)); | ||
@@ -328,41 +366,52 @@ if (i !== -1) { | ||
return; | ||
arrays.move(this._titles, i, j); | ||
arrays.move(this._tabs, i, j); | ||
arrays.move(this._items, i, j); | ||
this.contentNode.insertBefore(this._tabs[j], this._tabs[j + 1]); | ||
} | ||
else { | ||
arrays.insert(this._titles, j, title); | ||
title.changed.connect(this._onTitleChanged, this); | ||
if (!this.currentTitle) | ||
this.currentTitle = title; | ||
var tab = this.constructor.createTab(item.title); | ||
arrays.insert(this._tabs, j, tab); | ||
arrays.insert(this._items, j, item); | ||
this.contentNode.insertBefore(tab, this._tabs[j + 1]); | ||
item.title.changed.connect(this._onTitleChanged, this); | ||
if (!this.currentItem) | ||
this.currentItem = item; | ||
} | ||
// Flip the dirty flag and schedule a full update. | ||
this._dirty = true; | ||
this.update(); | ||
}; | ||
/** | ||
* Remove a title object from the tab bar. | ||
* Remove a tab item from the tab bar. | ||
* | ||
* @param title - The title object to remove from the tab bar. | ||
* @param item - The tab item to remove from the tab bar. | ||
* | ||
* #### Notes | ||
* If the title is not in the tab bar, this is a no-op. | ||
* If the item is not in the tab bar, this is a no-op. | ||
*/ | ||
TabBar.prototype.removeTitle = function (title) { | ||
// Release the mouse before making changes. | ||
TabBar.prototype.removeItem = function (item) { | ||
this._releaseMouse(); | ||
// Remove the specified title, or bail if it doesn't exist. | ||
var i = arrays.remove(this._titles, title); | ||
var i = arrays.remove(this._items, item); | ||
if (i === -1) { | ||
return; | ||
} | ||
// Disconnect the title changed handler. | ||
title.changed.disconnect(this._onTitleChanged, this); | ||
// Selected the next best tab if removing the current tab. | ||
if (this.currentTitle === title) { | ||
this.currentTitle = this._titles[i] || this._titles[i - 1]; | ||
this._dirtySet.delete(item.title); | ||
item.title.changed.disconnect(this._onTitleChanged, this); | ||
this.contentNode.removeChild(arrays.removeAt(this._tabs, i)); | ||
if (this.currentItem === item) { | ||
var next = this._items[i]; | ||
var prev = this._items[i - 1]; | ||
this.currentItem = next || prev; | ||
} | ||
// Flip the dirty flag and schedule a full update. | ||
this._dirty = true; | ||
this.update(); | ||
}; | ||
/** | ||
* Get the tab node for the item at the given index. | ||
* | ||
* @param index - The index of the tab item of interest. | ||
* | ||
* @returns The tab node for the item, or `undefined`. | ||
*/ | ||
TabBar.prototype.tabAt = function (index) { | ||
return this._tabs[index]; | ||
}; | ||
/** | ||
* Release the mouse and restore the non-dragged tab positions. | ||
@@ -421,5 +470,5 @@ * | ||
TabBar.prototype.onBeforeDetach = function (msg) { | ||
this._releaseMouse(); | ||
this.node.removeEventListener('click', this); | ||
this.node.removeEventListener('mousedown', this); | ||
this._releaseMouse(); | ||
}; | ||
@@ -430,9 +479,23 @@ /** | ||
TabBar.prototype.onUpdateRequest = function (msg) { | ||
if (this._dirty) { | ||
this._dirty = false; | ||
TabBarPrivate.updateTabs(this); | ||
var tabs = this._tabs; | ||
var items = this._items; | ||
var dirty = this._dirtySet; | ||
var current = this._currentItem; | ||
var constructor = this.constructor; | ||
for (var i = 0, n = tabs.length; i < n; ++i) { | ||
var tab = tabs[i]; | ||
var item = items[i]; | ||
if (dirty.has(item.title)) { | ||
constructor.updateTab(tab, item.title); | ||
} | ||
if (item === current) { | ||
tab.classList.add(CURRENT_CLASS); | ||
tab.style.zIndex = "" + n; | ||
} | ||
else { | ||
tab.classList.remove(CURRENT_CLASS); | ||
tab.style.zIndex = "" + (n - i - 1); | ||
} | ||
} | ||
else { | ||
TabBarPrivate.updateZOrder(this); | ||
} | ||
dirty.clear(); | ||
}; | ||
@@ -463,3 +526,5 @@ /** | ||
// Do nothing if the click is not on a tab. | ||
var i = TabBarPrivate.hitTestTabs(this, event.clientX, event.clientY); | ||
var x = event.clientX; | ||
var y = event.clientY; | ||
var i = arrays.findIndex(this._tabs, function (tab) { return phosphor_domutil_1.hitTest(tab, x, y); }); | ||
if (i < 0) { | ||
@@ -472,8 +537,8 @@ return; | ||
// Ignore the click if the title is not closable. | ||
var title = this._titles[i]; | ||
if (!title.closable) { | ||
var item = this._items[i]; | ||
if (!item.title.closable) { | ||
return; | ||
} | ||
// Ignore the click if the close icon wasn't clicked. | ||
var icon = TabBarPrivate.closeIconNode(this, i); | ||
// Ignore the click if it was not on a close icon. | ||
var icon = this.constructor.tabCloseIcon(this._tabs[i]); | ||
if (!icon.contains(event.target)) { | ||
@@ -483,3 +548,3 @@ return; | ||
// Emit the tab close requested signal. | ||
this.tabCloseRequested.emit(title); | ||
this.tabCloseRequested.emit({ index: i, item: item }); | ||
}; | ||
@@ -499,3 +564,5 @@ /** | ||
// Do nothing if the press is not on a tab. | ||
var i = TabBarPrivate.hitTestTabs(this, event.clientX, event.clientY); | ||
var x = event.clientX; | ||
var y = event.clientY; | ||
var i = arrays.findIndex(this._tabs, function (tab) { return phosphor_domutil_1.hitTest(tab, x, y); }); | ||
if (i < 0) { | ||
@@ -508,3 +575,3 @@ return; | ||
// Ignore the press if it was on a close icon. | ||
var icon = TabBarPrivate.closeIconNode(this, i); | ||
var icon = this.constructor.tabCloseIcon(this._tabs[i]); | ||
if (icon.contains(event.target)) { | ||
@@ -515,3 +582,7 @@ return; | ||
if (this._tabsMovable) { | ||
this._dragData = TabBarPrivate.initDrag(i, event); | ||
this._dragData = new TabBarPrivate.DragData(); | ||
this._dragData.index = i; | ||
this._dragData.tab = this._tabs[i]; | ||
this._dragData.pressX = event.clientX; | ||
this._dragData.pressY = event.clientY; | ||
document.addEventListener('mousemove', this, true); | ||
@@ -522,4 +593,4 @@ document.addEventListener('mouseup', this, true); | ||
} | ||
// Update the current title. | ||
this.currentTitle = this._titles[i]; | ||
// Update the current item to the pressed item. | ||
this.currentItem = this._items[i]; | ||
}; | ||
@@ -537,4 +608,37 @@ /** | ||
event.stopPropagation(); | ||
// Update the tab drag positions. | ||
TabBarPrivate.moveDrag(this, this._dragData, event); | ||
// Ensure the drag threshold is exceeded before moving the tab. | ||
var data = this._dragData; | ||
if (!data.dragActive) { | ||
var dx = Math.abs(event.clientX - data.pressX); | ||
var dy = Math.abs(event.clientY - data.pressY); | ||
if (dx < DRAG_THRESHOLD && dy < DRAG_THRESHOLD) { | ||
return; | ||
} | ||
// Fill in the rest of the drag data measurements. | ||
var tabRect = data.tab.getBoundingClientRect(); | ||
data.tabLeft = data.tab.offsetLeft; | ||
data.tabWidth = tabRect.width; | ||
data.tabPressX = data.pressX - tabRect.left; | ||
data.tabLayout = TabBarPrivate.snapTabLayout(this._tabs); | ||
data.contentRect = this.contentNode.getBoundingClientRect(); | ||
data.override = phosphor_domutil_1.overrideCursor('default'); | ||
// Add the dragging classes and mark the drag as active. | ||
data.tab.classList.add(DRAGGING_CLASS); | ||
this.addClass(DRAGGING_CLASS); | ||
data.dragActive = true; | ||
} | ||
// Emit the detach request signal if the threshold is exceeded. | ||
if (!data.detachRequested && TabBarPrivate.detachExceeded(data, event)) { | ||
data.detachRequested = true; | ||
var index = data.index; | ||
var item = this._items[index]; | ||
var clientX = event.clientX; | ||
var clientY = event.clientY; | ||
this.tabDetachRequested.emit({ index: index, item: item, clientX: clientX, clientY: clientY }); | ||
if (data.dragAborted) { | ||
return; | ||
} | ||
} | ||
// Update the tab layout and computed target index. | ||
TabBarPrivate.layoutTabs(this._tabs, data, event); | ||
}; | ||
@@ -562,7 +666,39 @@ /** | ||
document.removeEventListener('contextmenu', this, true); | ||
// End the drag operation. | ||
TabBarPrivate.endDrag(this, this._dragData, event, { | ||
clear: function () { _this._dragData = null; }, | ||
move: function (i, j) { _this._moveTab(i, j); }, | ||
}); | ||
// Bail early if the drag is not active. | ||
var data = this._dragData; | ||
if (!data.dragActive) { | ||
this._dragData = null; | ||
return; | ||
} | ||
// Position the tab at its final resting position. | ||
TabBarPrivate.finalizeTabPosition(data); | ||
// Remove the dragging class from the tab so it can be transitioned. | ||
data.tab.classList.remove(DRAGGING_CLASS); | ||
// Complete the release on a timer to allow the tab to transition. | ||
setTimeout(function () { | ||
// Do nothing if the drag has been aborted. | ||
if (data.dragAborted) { | ||
return; | ||
} | ||
// Clear the drag data reference. | ||
_this._dragData = null; | ||
// Reset the positions of the tabs. | ||
TabBarPrivate.resetTabPositions(_this._tabs); | ||
// Clear the cursor grab and drag styles. | ||
data.override.dispose(); | ||
_this.removeClass(DRAGGING_CLASS); | ||
// If the tab was not moved, there is nothing else to do. | ||
var i = data.index; | ||
var j = data.targetIndex; | ||
if (j === -1 || i === j) { | ||
return; | ||
} | ||
// Move the tab and related tab item to the new location. | ||
arrays.move(_this._tabs, i, j); | ||
arrays.move(_this._items, i, j); | ||
_this.contentNode.insertBefore(_this._tabs[j], _this._tabs[j + 1]); | ||
// Emit the tab moved signal and schedule a render update. | ||
_this.tabMoved.emit({ fromIndex: i, toIndex: j, item: _this._items[j] }); | ||
_this.update(); | ||
}, TRANSITION_DURATION); | ||
}; | ||
@@ -582,23 +718,24 @@ /** | ||
document.removeEventListener('contextmenu', this, true); | ||
// Abort the drag operation and clear the drag data. | ||
TabBarPrivate.abortDrag(this, this._dragData); | ||
// Clear the drag data reference. | ||
var data = this._dragData; | ||
this._dragData = null; | ||
// Indicate the drag has been aborted. This allows the mouse | ||
// event handlers to return early when the drag is canceled. | ||
data.dragAborted = true; | ||
// If the drag is not active, there's nothing more to do. | ||
if (!data.dragActive) { | ||
return; | ||
} | ||
// Reset the tabs to their non-dragged positions. | ||
TabBarPrivate.resetTabPositions(this._tabs); | ||
// Clear the cursor override and extra styling classes. | ||
data.override.dispose(); | ||
data.tab.classList.remove(DRAGGING_CLASS); | ||
this.removeClass(DRAGGING_CLASS); | ||
}; | ||
/** | ||
* Move a tab from one index to another. | ||
*/ | ||
TabBar.prototype._moveTab = function (i, j) { | ||
var k = j < i ? j : j + 1; | ||
var content = this.contentNode; | ||
var children = content.children; | ||
arrays.move(this._titles, i, j); | ||
content.insertBefore(children[i], children[k]); | ||
this.tabMoved.emit({ fromIndex: i, toIndex: j }); | ||
this.update(); | ||
}; | ||
/** | ||
* Handle the `changed` signal of a title object. | ||
*/ | ||
TabBar.prototype._onTitleChanged = function (sender) { | ||
this._dirty = true; | ||
this._dirtySet.add(sender); | ||
this.update(); | ||
@@ -610,66 +747,2 @@ }; | ||
/** | ||
* A struct which holds the drag data for a tab bar. | ||
*/ | ||
var DragData = (function () { | ||
function DragData() { | ||
/** | ||
* The tab node being dragged. | ||
*/ | ||
this.tab = null; | ||
/** | ||
* The index of the tab being dragged. | ||
*/ | ||
this.tabIndex = -1; | ||
/** | ||
* The offset left of the tab being dragged. | ||
*/ | ||
this.tabLeft = -1; | ||
/** | ||
* The offset width of the tab being dragged. | ||
*/ | ||
this.tabWidth = -1; | ||
/** | ||
* The original mouse X position in tab coordinates. | ||
*/ | ||
this.tabPressX = -1; | ||
/** | ||
* The tab target index upon mouse release. | ||
*/ | ||
this.targetIndex = -1; | ||
/** | ||
* The array of tab layout objects snapped at drag start. | ||
*/ | ||
this.tabLayout = null; | ||
/** | ||
* The mouse press client X position. | ||
*/ | ||
this.pressX = -1; | ||
/** | ||
* The mouse press client Y position. | ||
*/ | ||
this.pressY = -1; | ||
/** | ||
* The bounding client rect of the tab bar content node. | ||
*/ | ||
this.contentRect = null; | ||
/** | ||
* The disposable to clean up the cursor override. | ||
*/ | ||
this.cursorGrab = null; | ||
/** | ||
* Whether the drag is currently active. | ||
*/ | ||
this.dragActive = false; | ||
/** | ||
* Whether the drag has been aborted. | ||
*/ | ||
this.dragAborted = false; | ||
/** | ||
* Whether a detach request as been made. | ||
*/ | ||
this.detachRequested = false; | ||
} | ||
return DragData; | ||
})(); | ||
/** | ||
* The namespace for the `TabBar` class private data. | ||
@@ -680,3 +753,3 @@ */ | ||
/** | ||
* A signal emitted when the current title is changed. | ||
* A signal emitted when the current tab item is changed. | ||
*/ | ||
@@ -697,315 +770,154 @@ TabBarPrivate.currentChangedSignal = new phosphor_signaling_1.Signal(); | ||
/** | ||
* The property descriptor for the currently selected title. | ||
* A struct which holds the drag data for a tab bar. | ||
*/ | ||
TabBarPrivate.currentTitleProperty = new phosphor_properties_1.Property({ | ||
name: 'currentTitle', | ||
value: null, | ||
coerce: coerceCurrentTitle, | ||
changed: onCurrentTitleChanged, | ||
notify: TabBarPrivate.currentChangedSignal, | ||
}); | ||
/** | ||
* Get the close icon node for the tab at the specified index. | ||
*/ | ||
function closeIconNode(owner, index) { | ||
return owner.contentNode.children[index].lastChild; | ||
} | ||
TabBarPrivate.closeIconNode = closeIconNode; | ||
/** | ||
* Get the index of the tab node at a client position, or `-1`. | ||
*/ | ||
function hitTestTabs(owner, x, y) { | ||
var nodes = owner.contentNode.children; | ||
for (var i = 0, n = nodes.length; i < n; ++i) { | ||
if (phosphor_domutil_1.hitTest(nodes[i], x, y)) | ||
return i; | ||
var DragData = (function () { | ||
function DragData() { | ||
/** | ||
* The tab node being dragged. | ||
*/ | ||
this.tab = null; | ||
/** | ||
* The index of the tab being dragged. | ||
*/ | ||
this.index = -1; | ||
/** | ||
* The offset left of the tab being dragged. | ||
*/ | ||
this.tabLeft = -1; | ||
/** | ||
* The offset width of the tab being dragged. | ||
*/ | ||
this.tabWidth = -1; | ||
/** | ||
* The original mouse X position in tab coordinates. | ||
*/ | ||
this.tabPressX = -1; | ||
/** | ||
* The tab target index upon mouse release. | ||
*/ | ||
this.targetIndex = -1; | ||
/** | ||
* The array of tab layout objects snapped at drag start. | ||
*/ | ||
this.tabLayout = null; | ||
/** | ||
* The mouse press client X position. | ||
*/ | ||
this.pressX = -1; | ||
/** | ||
* The mouse press client Y position. | ||
*/ | ||
this.pressY = -1; | ||
/** | ||
* The bounding client rect of the tab bar content node. | ||
*/ | ||
this.contentRect = null; | ||
/** | ||
* The disposable to clean up the cursor override. | ||
*/ | ||
this.override = null; | ||
/** | ||
* Whether the drag is currently active. | ||
*/ | ||
this.dragActive = false; | ||
/** | ||
* Whether the drag has been aborted. | ||
*/ | ||
this.dragAborted = false; | ||
/** | ||
* Whether a detach request as been made. | ||
*/ | ||
this.detachRequested = false; | ||
} | ||
return -1; | ||
} | ||
TabBarPrivate.hitTestTabs = hitTestTabs; | ||
return DragData; | ||
})(); | ||
TabBarPrivate.DragData = DragData; | ||
/** | ||
* Update the tab bar tabs to match the current titles. | ||
* | ||
* This is a full update which also updates the tab Z order. | ||
* Get a snapshot of the current tab layout values. | ||
*/ | ||
function updateTabs(owner) { | ||
var count = owner.titleCount(); | ||
var content = owner.contentNode; | ||
var children = content.children; | ||
var current = owner.currentTitle; | ||
while (children.length > count) { | ||
content.removeChild(content.lastChild); | ||
function snapTabLayout(tabs) { | ||
var layout = new Array(tabs.length); | ||
for (var i = 0, n = tabs.length; i < n; ++i) { | ||
var node = tabs[i]; | ||
var left = node.offsetLeft; | ||
var width = node.offsetWidth; | ||
var cstyle = window.getComputedStyle(node); | ||
var margin = parseInt(cstyle.marginLeft, 10) || 0; | ||
layout[i] = { margin: margin, left: left, width: width }; | ||
} | ||
while (children.length < count) { | ||
content.appendChild(createTabNode()); | ||
} | ||
for (var i = 0; i < count; ++i) { | ||
var node = children[i]; | ||
updateTabNode(node, owner.titleAt(i)); | ||
} | ||
updateZOrder(owner); | ||
return layout; | ||
} | ||
TabBarPrivate.updateTabs = updateTabs; | ||
TabBarPrivate.snapTabLayout = snapTabLayout; | ||
/** | ||
* Update the Z order of the tabs to match the current titles. | ||
* | ||
* This is a partial update which updates the Z order and the current | ||
* tab class. It assumes the tab count is the same as the title count. | ||
* Test if the event exceeds the drag detach threshold. | ||
*/ | ||
function updateZOrder(owner) { | ||
var count = owner.titleCount(); | ||
var content = owner.contentNode; | ||
var children = content.children; | ||
var current = owner.currentTitle; | ||
for (var i = 0; i < count; ++i) { | ||
var node = children[i]; | ||
if (owner.titleAt(i) === current) { | ||
node.classList.add(CURRENT_CLASS); | ||
node.style.zIndex = count + ''; | ||
} | ||
else { | ||
node.classList.remove(CURRENT_CLASS); | ||
node.style.zIndex = count - i - 1 + ''; | ||
} | ||
} | ||
function detachExceeded(data, event) { | ||
var rect = data.contentRect; | ||
return ((event.clientX < rect.left - DETACH_THRESHOLD) || | ||
(event.clientX >= rect.right + DETACH_THRESHOLD) || | ||
(event.clientY < rect.top - DETACH_THRESHOLD) || | ||
(event.clientY >= rect.bottom + DETACH_THRESHOLD)); | ||
} | ||
TabBarPrivate.updateZOrder = updateZOrder; | ||
TabBarPrivate.detachExceeded = detachExceeded; | ||
/** | ||
* Initialize a new drag data object for a tab bar. | ||
* | ||
* This should be called on 'mousedown' event. | ||
* Update the relative tab positions and computed target index. | ||
*/ | ||
function initDrag(tabIndex, event) { | ||
var data = new DragData(); | ||
data.tabIndex = tabIndex; | ||
data.pressX = event.clientX; | ||
data.pressY = event.clientY; | ||
return data; | ||
} | ||
TabBarPrivate.initDrag = initDrag; | ||
/** | ||
* Update the drag positions of the tabs for a tab bar. | ||
* | ||
* This should be called on a `'mousemove'` event. | ||
*/ | ||
function moveDrag(owner, data, event) { | ||
// Ensure the drag threshold is exceeded before moving the tab. | ||
if (!data.dragActive) { | ||
var dx = Math.abs(event.clientX - data.pressX); | ||
var dy = Math.abs(event.clientY - data.pressY); | ||
if (dx < DRAG_THRESHOLD && dy < DRAG_THRESHOLD) { | ||
return; | ||
} | ||
// Fill in the missing drag data measurements. | ||
var content = owner.contentNode; | ||
var tab = content.children[data.tabIndex]; | ||
var tabRect = tab.getBoundingClientRect(); | ||
data.tab = tab; | ||
data.tabLeft = tab.offsetLeft; | ||
data.tabWidth = tabRect.width; | ||
data.tabPressX = data.pressX - tabRect.left; | ||
data.contentRect = content.getBoundingClientRect(); | ||
data.tabLayout = snapTabLayout(owner); | ||
data.cursorGrab = phosphor_domutil_1.overrideCursor('default'); | ||
// Style the tab bar and tab for relative position dragging. | ||
tab.classList.add(DRAGGING_CLASS); | ||
owner.addClass(DRAGGING_CLASS); | ||
data.dragActive = true; | ||
} | ||
// Emit the detach request signal if the threshold is exceeded. | ||
if (!data.detachRequested && detachExceeded(data.contentRect, event)) { | ||
var node = data.tab; | ||
var clientX = event.clientX; | ||
var clientY = event.clientY; | ||
var title = owner.titleAt(data.tabIndex); | ||
owner.tabDetachRequested.emit({ title: title, node: node, clientX: clientX, clientY: clientY }); | ||
data.detachRequested = true; | ||
if (data.dragAborted) { | ||
return; | ||
} | ||
} | ||
// Compute the target bounds of the drag tab. | ||
var offsetLeft = event.clientX - data.contentRect.left; | ||
var targetLeft = offsetLeft - data.tabPressX; | ||
function layoutTabs(tabs, data, event) { | ||
var targetIndex = data.index; | ||
var targetLeft = event.clientX - data.contentRect.left - data.tabPressX; | ||
var targetRight = targetLeft + data.tabWidth; | ||
// Reset the target tab index. | ||
data.targetIndex = data.tabIndex; | ||
// Update the non-drag tab positions and the tab target index. | ||
var tabs = owner.contentNode.children; | ||
for (var i = 0, n = tabs.length; i < n; ++i) { | ||
var style = tabs[i].style; | ||
var layout = data.tabLayout[i]; | ||
var style = tabs[i].style; | ||
var threshold = layout.left + (layout.width >> 1); | ||
if (i < data.tabIndex && targetLeft < threshold) { | ||
if (i < data.index && targetLeft < threshold) { | ||
style.left = data.tabWidth + data.tabLayout[i + 1].margin + 'px'; | ||
data.targetIndex = Math.min(data.targetIndex, i); | ||
targetIndex = Math.min(targetIndex, i); | ||
} | ||
else if (i > data.tabIndex && targetRight > threshold) { | ||
else if (i > data.index && targetRight > threshold) { | ||
style.left = -data.tabWidth - layout.margin + 'px'; | ||
data.targetIndex = i; | ||
targetIndex = Math.max(targetIndex, i); | ||
} | ||
else if (i !== data.tabIndex) { | ||
else if (i === data.index) { | ||
var ideal = event.clientX - data.pressX; | ||
var limit = data.contentRect.width - (data.tabLeft + data.tabWidth); | ||
style.left = Math.max(-data.tabLeft, Math.min(ideal, limit)) + 'px'; | ||
} | ||
else { | ||
style.left = ''; | ||
} | ||
} | ||
// Update the drag tab position. | ||
var idealLeft = event.clientX - data.pressX; | ||
var maxLeft = data.contentRect.width - (data.tabLeft + data.tabWidth); | ||
var adjustedLeft = Math.max(-data.tabLeft, Math.min(idealLeft, maxLeft)); | ||
data.tab.style.left = adjustedLeft + 'px'; | ||
data.targetIndex = targetIndex; | ||
} | ||
TabBarPrivate.moveDrag = moveDrag; | ||
TabBarPrivate.layoutTabs = layoutTabs; | ||
/** | ||
* End the drag operation for a tab bar. | ||
* | ||
* This should be called on a `'mouseup'` event. | ||
* Position the drag tab at its final resting relative position. | ||
*/ | ||
function endDrag(owner, data, event, handler) { | ||
// Bail early if the drag is not active. | ||
if (!data.dragActive) { | ||
handler.clear(); | ||
return; | ||
function finalizeTabPosition(data) { | ||
var ideal; | ||
if (data.targetIndex === data.index) { | ||
ideal = 0; | ||
} | ||
// Compute the approximate final relative tab offset. | ||
var idealLeft; | ||
if (data.targetIndex === data.tabIndex) { | ||
idealLeft = 0; | ||
else if (data.targetIndex > data.index) { | ||
var tgt = data.tabLayout[data.targetIndex]; | ||
ideal = tgt.left + tgt.width - data.tabWidth - data.tabLeft; | ||
} | ||
else if (data.targetIndex > data.tabIndex) { | ||
var tl = data.tabLayout[data.targetIndex]; | ||
idealLeft = tl.left + tl.width - data.tabWidth - data.tabLeft; | ||
} | ||
else { | ||
var tl = data.tabLayout[data.targetIndex]; | ||
idealLeft = tl.left - data.tabLeft; | ||
var tgt = data.tabLayout[data.targetIndex]; | ||
ideal = tgt.left - data.tabLeft; | ||
} | ||
// Position the tab to its final position, subject to limits. | ||
var maxLeft = data.contentRect.width - (data.tabLeft + data.tabWidth); | ||
var adjustedLeft = Math.max(-data.tabLeft, Math.min(idealLeft, maxLeft)); | ||
data.tab.style.left = adjustedLeft + 'px'; | ||
// Remove the dragging class from the tab so it can be transitioned. | ||
data.tab.classList.remove(DRAGGING_CLASS); | ||
// Complete the release on a timer to allow the tab to transition. | ||
setTimeout(function () { | ||
// Do nothing if the drag has been aborted. | ||
if (data.dragAborted) { | ||
return; | ||
} | ||
// Clear the drag data reference. | ||
handler.clear(); | ||
// Reset the positions of the tabs. | ||
resetTabPositions(owner); | ||
// Clear the cursor grab and drag styles. | ||
data.cursorGrab.dispose(); | ||
owner.removeClass(DRAGGING_CLASS); | ||
// Finally, move the tab to its new location. | ||
if (data.targetIndex !== -1 && data.tabIndex !== data.targetIndex) { | ||
handler.move(data.tabIndex, data.targetIndex); | ||
} | ||
}, TRANSITION_DURATION); | ||
var style = data.tab.style; | ||
var limit = data.contentRect.width - (data.tabLeft + data.tabWidth); | ||
style.left = Math.max(-data.tabLeft, Math.min(ideal, limit)) + 'px'; | ||
} | ||
TabBarPrivate.endDrag = endDrag; | ||
TabBarPrivate.finalizeTabPosition = finalizeTabPosition; | ||
/** | ||
* Abort the drag operation for a tab bar. | ||
* | ||
* This should be called to cancel a drag immediately. | ||
* Reset the relative positions of the given tabs. | ||
*/ | ||
function abortDrag(owner, data) { | ||
// Indicate the drag has been aborted, which allows the drag | ||
// end handler and detach request emitter to return early. | ||
data.dragAborted = true; | ||
// If the drag is not active, there's nothing more to do. | ||
if (!data.dragActive) { | ||
return; | ||
function resetTabPositions(tabs) { | ||
for (var i = 0, n = tabs.length; i < n; ++i) { | ||
tabs[i].style.left = ''; | ||
} | ||
// Reset the tabs to their non-dragged positions. | ||
resetTabPositions(owner); | ||
// Clear the cursor override and extra styling classes. | ||
data.cursorGrab.dispose(); | ||
data.tab.classList.remove(DRAGGING_CLASS); | ||
owner.removeClass(DRAGGING_CLASS); | ||
} | ||
TabBarPrivate.abortDrag = abortDrag; | ||
/** | ||
* The coerce handler for the `currentTitle` property. | ||
*/ | ||
function coerceCurrentTitle(owner, value) { | ||
return (value && owner.titleIndex(value) !== -1) ? value : null; | ||
} | ||
/** | ||
* The change handler for the `currentTitle` property. | ||
*/ | ||
function onCurrentTitleChanged(owner) { | ||
owner.update(); | ||
} | ||
/** | ||
* Create an uninitialized DOM node for a tab. | ||
*/ | ||
function createTabNode() { | ||
var node = document.createElement('li'); | ||
var icon = document.createElement('span'); | ||
var text = document.createElement('span'); | ||
var close = document.createElement('span'); | ||
text.className = TEXT_CLASS; | ||
close.className = CLOSE_CLASS; | ||
node.appendChild(icon); | ||
node.appendChild(text); | ||
node.appendChild(close); | ||
return node; | ||
} | ||
/** | ||
* Update a tab node to reflect the state of a title. | ||
*/ | ||
function updateTabNode(node, title) { | ||
var icon = node.firstChild; | ||
var text = icon.nextSibling; | ||
var suffix = title.closable ? ' ' + CLOSABLE_CLASS : ''; | ||
if (title.className) { | ||
node.className = TAB_CLASS + ' ' + title.className + suffix; | ||
} | ||
else { | ||
node.className = TAB_CLASS + suffix; | ||
} | ||
if (title.icon) { | ||
icon.className = ICON_CLASS + ' ' + title.icon; | ||
} | ||
else { | ||
icon.className = ICON_CLASS; | ||
} | ||
text.textContent = title.text; | ||
} | ||
/** | ||
* Reset the tabs to their unadjusted positions. | ||
*/ | ||
function resetTabPositions(owner) { | ||
var children = owner.contentNode.children; | ||
for (var i = 0, n = children.length; i < n; ++i) { | ||
children[i].style.left = ''; | ||
} | ||
} | ||
/** | ||
* Get a snapshot of the current tab layout values. | ||
*/ | ||
function snapTabLayout(owner) { | ||
var layout = []; | ||
var children = owner.contentNode.children; | ||
for (var i = 0, n = children.length; i < n; ++i) { | ||
var node = children[i]; | ||
var left = node.offsetLeft; | ||
var width = node.offsetWidth; | ||
var cstyle = window.getComputedStyle(node); | ||
var margin = parseInt(cstyle.marginLeft, 10) || 0; | ||
layout.push({ margin: margin, left: left, width: width }); | ||
} | ||
return layout; | ||
} | ||
/** | ||
* Test if a mouse position exceeds the detach threshold. | ||
*/ | ||
function detachExceeded(rect, event) { | ||
return ((event.clientX < rect.left - DETACH_THRESHOLD) || | ||
(event.clientX >= rect.right + DETACH_THRESHOLD) || | ||
(event.clientY < rect.top - DETACH_THRESHOLD) || | ||
(event.clientY >= rect.bottom + DETACH_THRESHOLD)); | ||
} | ||
TabBarPrivate.resetTabPositions = resetTabPositions; | ||
})(TabBarPrivate || (TabBarPrivate = {})); |
@@ -1,5 +0,4 @@ | ||
import { IChangedArgs } from 'phosphor-properties'; | ||
import { StackedPanel } from 'phosphor-stackedpanel'; | ||
import { Title, Widget } from 'phosphor-widget'; | ||
import { ITabMovedArgs, TabBar } from './tabbar'; | ||
import { Widget } from 'phosphor-widget'; | ||
import { TabBar } from './tabbar'; | ||
/** | ||
@@ -20,6 +19,6 @@ * A widget which combines a `TabBar` and a `StackedPanel`. | ||
* | ||
* @returns The tab bar to use with a new tab panel. | ||
* @returns A new tab bar to use with a tab panel. | ||
* | ||
* #### Notes | ||
* This may be reimplemented by a subclass as needed. | ||
* This may be reimplemented by subclasses for custom tab bars. | ||
*/ | ||
@@ -30,9 +29,13 @@ static createTabBar(): TabBar; | ||
* | ||
* @returns The stacked panel to use with a new tab panel. | ||
* @returns A new stacked panel to use with a tab panel. | ||
* | ||
* #### Notes | ||
* This may be reimplemented by a subclass as needed. | ||
* This may be reimplemented by subclasses for custom stacks. | ||
*/ | ||
static createStackedPanel(): StackedPanel; | ||
/** | ||
* The static type of the constructor. | ||
*/ | ||
'constructor': typeof TabPanel; | ||
/** | ||
* Construct a new tab panel. | ||
@@ -63,3 +66,3 @@ */ | ||
* #### Notes | ||
* Modifying the tab bar titles can lead to undefined behavior. | ||
* Modifying the tab bar directly can lead to undefined behavior. | ||
* | ||
@@ -73,3 +76,3 @@ * This is a read-only property. | ||
* #### Notes | ||
* Modifying the panel children can lead to undefined behavior. | ||
* Modifying the stack directly can lead to undefined behavior. | ||
* | ||
@@ -85,3 +88,3 @@ * This is a read-only property. | ||
* #### Notes | ||
* This delegates to `childCount` of the internal stacked panel. | ||
* This delegates to the `childCount` method of the stacked panel. | ||
*/ | ||
@@ -97,3 +100,3 @@ childCount(): number; | ||
* #### Notes | ||
* This delegates to `childAt` of the internal stacked panel. | ||
* This delegates to the `childAt` method of the stacked panel. | ||
*/ | ||
@@ -109,3 +112,3 @@ childAt(index: number): Widget; | ||
* #### Notes | ||
* This delegates to `childIndex` of the internal stacked panel. | ||
* This delegates to the `childIndex` method of the stacked panel. | ||
*/ | ||
@@ -120,5 +123,2 @@ childIndex(child: Widget): number; | ||
* If the child is already contained in the panel, it will be moved. | ||
* | ||
* This adds the widget's title object to the internal tab bar, and | ||
* adds the widget itself to the internal stacked panel. | ||
*/ | ||
@@ -135,46 +135,23 @@ addChild(child: Widget): void; | ||
* If the child is already contained in the panel, it will be moved. | ||
* | ||
* This adds the widget's title object to the internal tab bar, and | ||
* adds the widget itself to the internal stacked panel. | ||
*/ | ||
insertChild(index: number, child: Widget): void; | ||
/** | ||
* Find the widget which owns the given title. | ||
* | ||
* @param title - The title object of interest. | ||
* | ||
* @returns The widget which owns the title, or `null` if no such | ||
* widget is contained within the internal stacked panel. | ||
* Handle the `currentChanged` signal from the tab bar. | ||
*/ | ||
protected findWidgetByTitle(title: Title): Widget; | ||
private _onCurrentChanged(sender, args); | ||
/** | ||
* Handle the `currentChanged` signal from the tab bar. | ||
* | ||
* #### Notes | ||
* The default implementation updates the current stack widget. | ||
* Handle the `tabCloseRequested` signal from the tab bar. | ||
*/ | ||
protected onCurrentChanged(sender: TabBar, args: IChangedArgs<Title>): void; | ||
private _onTabCloseRequested(sender, args); | ||
/** | ||
* Handle the `tabMoved` signal from the tab bar. | ||
* | ||
* #### Notes | ||
* The default implementation moves the widget in the stack. | ||
*/ | ||
protected onTabMoved(sender: TabBar, args: ITabMovedArgs): void; | ||
private _onTabMoved(sender, args); | ||
/** | ||
* Handle the `tabCloseRequested` signal from the tab bar. | ||
* | ||
* #### Notes | ||
* The default implementation closes the respective widget. | ||
*/ | ||
protected onTabCloseRequested(sender: TabBar, title: Title): void; | ||
/** | ||
* Handle the `widgetRemoved` signal from the stacked panel. | ||
* | ||
* #### Notes | ||
* The default implementation removes the title from the tab bar. | ||
*/ | ||
protected onWidgetRemoved(sender: StackedPanel, widget: Widget): void; | ||
private _onWidgetRemoved(sender, widget); | ||
private _tabBar; | ||
private _stackedPanel; | ||
private _currentWidget; | ||
} |
@@ -18,3 +18,2 @@ /*----------------------------------------------------------------------------- | ||
var tabbar_1 = require('./tabbar'); | ||
// TODO - need better solution for storing these class names | ||
/** | ||
@@ -25,2 +24,10 @@ * The class name added to TabPanel instances. | ||
/** | ||
* The class name added to a TabPanel's tab bar. | ||
*/ | ||
var TAB_BAR_CLASS = 'p-TabPanel-tabBar'; | ||
/** | ||
* The class name added to a TabPanel's stacked panel. | ||
*/ | ||
var STACKED_PANEL_CLASS = 'p-TabPanel-stackedPanel'; | ||
/** | ||
* A widget which combines a `TabBar` and a `StackedPanel`. | ||
@@ -43,15 +50,15 @@ * | ||
_super.call(this); | ||
this._currentWidget = null; | ||
this.addClass(TAB_PANEL_CLASS); | ||
var type = this.constructor; | ||
this._tabBar = type.createTabBar(); | ||
this._stackedPanel = type.createStackedPanel(); | ||
this._tabBar.tabMoved.connect(this.onTabMoved, this); | ||
this._tabBar.currentChanged.connect(this.onCurrentChanged, this); | ||
this._tabBar.tabCloseRequested.connect(this.onTabCloseRequested, this); | ||
this._stackedPanel.widgetRemoved.connect(this.onWidgetRemoved, this); | ||
phosphor_boxpanel_1.BoxLayout.setStretch(this._tabBar, 0); | ||
phosphor_boxpanel_1.BoxLayout.setStretch(this._stackedPanel, 1); | ||
this._tabBar = this.constructor.createTabBar(); | ||
this._stackedPanel = this.constructor.createStackedPanel(); | ||
this._tabBar.tabMoved.connect(this._onTabMoved, this); | ||
this._tabBar.currentChanged.connect(this._onCurrentChanged, this); | ||
this._tabBar.tabCloseRequested.connect(this._onTabCloseRequested, this); | ||
this._stackedPanel.widgetRemoved.connect(this._onWidgetRemoved, this); | ||
var layout = new phosphor_boxpanel_1.BoxLayout(); | ||
layout.direction = phosphor_boxpanel_1.BoxLayout.TopToBottom; | ||
layout.spacing = 0; | ||
phosphor_boxpanel_1.BoxLayout.setStretch(this._tabBar, 0); | ||
phosphor_boxpanel_1.BoxLayout.setStretch(this._stackedPanel, 1); | ||
layout.addChild(this._tabBar); | ||
@@ -64,9 +71,11 @@ layout.addChild(this._stackedPanel); | ||
* | ||
* @returns The tab bar to use with a new tab panel. | ||
* @returns A new tab bar to use with a tab panel. | ||
* | ||
* #### Notes | ||
* This may be reimplemented by a subclass as needed. | ||
* This may be reimplemented by subclasses for custom tab bars. | ||
*/ | ||
TabPanel.createTabBar = function () { | ||
return new tabbar_1.TabBar(); | ||
var tabBar = new tabbar_1.TabBar(); | ||
tabBar.addClass(TAB_BAR_CLASS); | ||
return tabBar; | ||
}; | ||
@@ -76,9 +85,11 @@ /** | ||
* | ||
* @returns The stacked panel to use with a new tab panel. | ||
* @returns A new stacked panel to use with a tab panel. | ||
* | ||
* #### Notes | ||
* This may be reimplemented by a subclass as needed. | ||
* This may be reimplemented by subclasses for custom stacks. | ||
*/ | ||
TabPanel.createStackedPanel = function () { | ||
return new phosphor_stackedpanel_1.StackedPanel(); | ||
var stackedPanel = new phosphor_stackedpanel_1.StackedPanel(); | ||
stackedPanel.addClass(STACKED_PANEL_CLASS); | ||
return stackedPanel; | ||
}; | ||
@@ -91,2 +102,3 @@ /** | ||
this._stackedPanel = null; | ||
this._currentWidget = null; | ||
_super.prototype.dispose.call(this); | ||
@@ -99,3 +111,3 @@ }; | ||
get: function () { | ||
return this._stackedPanel.currentWidget; | ||
return this._tabBar.currentItem; | ||
}, | ||
@@ -105,4 +117,4 @@ /** | ||
*/ | ||
set: function (widget) { | ||
this._tabBar.currentTitle = widget && widget.title; | ||
set: function (value) { | ||
this._tabBar.currentItem = value; | ||
}, | ||
@@ -122,4 +134,4 @@ enumerable: true, | ||
*/ | ||
set: function (movable) { | ||
this._tabBar.tabsMovable = movable; | ||
set: function (value) { | ||
this._tabBar.tabsMovable = value; | ||
}, | ||
@@ -134,3 +146,3 @@ enumerable: true, | ||
* #### Notes | ||
* Modifying the tab bar titles can lead to undefined behavior. | ||
* Modifying the tab bar directly can lead to undefined behavior. | ||
* | ||
@@ -150,3 +162,3 @@ * This is a read-only property. | ||
* #### Notes | ||
* Modifying the panel children can lead to undefined behavior. | ||
* Modifying the stack directly can lead to undefined behavior. | ||
* | ||
@@ -167,3 +179,3 @@ * This is a read-only property. | ||
* #### Notes | ||
* This delegates to `childCount` of the internal stacked panel. | ||
* This delegates to the `childCount` method of the stacked panel. | ||
*/ | ||
@@ -181,3 +193,3 @@ TabPanel.prototype.childCount = function () { | ||
* #### Notes | ||
* This delegates to `childAt` of the internal stacked panel. | ||
* This delegates to the `childAt` method of the stacked panel. | ||
*/ | ||
@@ -195,3 +207,3 @@ TabPanel.prototype.childAt = function (index) { | ||
* #### Notes | ||
* This delegates to `childIndex` of the internal stacked panel. | ||
* This delegates to the `childIndex` method of the stacked panel. | ||
*/ | ||
@@ -208,9 +220,5 @@ TabPanel.prototype.childIndex = function (child) { | ||
* If the child is already contained in the panel, it will be moved. | ||
* | ||
* This adds the widget's title object to the internal tab bar, and | ||
* adds the widget itself to the internal stacked panel. | ||
*/ | ||
TabPanel.prototype.addChild = function (child) { | ||
this._stackedPanel.addChild(child); | ||
this._tabBar.addTitle(child.title); | ||
this.insertChild(this.childCount(), child); | ||
}; | ||
@@ -226,65 +234,42 @@ /** | ||
* If the child is already contained in the panel, it will be moved. | ||
* | ||
* This adds the widget's title object to the internal tab bar, and | ||
* adds the widget itself to the internal stacked panel. | ||
*/ | ||
TabPanel.prototype.insertChild = function (index, child) { | ||
if (child !== this._currentWidget) | ||
child.hide(); | ||
this._stackedPanel.insertChild(index, child); | ||
this._tabBar.insertTitle(index, child.title); | ||
this._tabBar.insertItem(index, child); | ||
}; | ||
/** | ||
* Find the widget which owns the given title. | ||
* | ||
* @param title - The title object of interest. | ||
* | ||
* @returns The widget which owns the title, or `null` if no such | ||
* widget is contained within the internal stacked panel. | ||
* Handle the `currentChanged` signal from the tab bar. | ||
*/ | ||
TabPanel.prototype.findWidgetByTitle = function (title) { | ||
var panel = this._stackedPanel; | ||
for (var i = 0, n = panel.childCount(); i < n; ++i) { | ||
var child = panel.childAt(i); | ||
if (child.title === title) | ||
return child; | ||
} | ||
return null; | ||
TabPanel.prototype._onCurrentChanged = function (sender, args) { | ||
var oldWidget = this._currentWidget; | ||
var newWidget = args.item; | ||
if (oldWidget === newWidget) | ||
return; | ||
this._currentWidget = newWidget; | ||
if (oldWidget) | ||
oldWidget.hide(); | ||
if (newWidget) | ||
newWidget.show(); | ||
}; | ||
/** | ||
* Handle the `currentChanged` signal from the tab bar. | ||
* | ||
* #### Notes | ||
* The default implementation updates the current stack widget. | ||
* Handle the `tabCloseRequested` signal from the tab bar. | ||
*/ | ||
TabPanel.prototype.onCurrentChanged = function (sender, args) { | ||
this._stackedPanel.currentWidget = this.findWidgetByTitle(args.newValue); | ||
TabPanel.prototype._onTabCloseRequested = function (sender, args) { | ||
args.item.close(); | ||
}; | ||
/** | ||
* Handle the `tabMoved` signal from the tab bar. | ||
* | ||
* #### Notes | ||
* The default implementation moves the widget in the stack. | ||
*/ | ||
TabPanel.prototype.onTabMoved = function (sender, args) { | ||
var child = this._stackedPanel.childAt(args.fromIndex); | ||
this._stackedPanel.insertChild(args.toIndex, child); | ||
TabPanel.prototype._onTabMoved = function (sender, args) { | ||
this._stackedPanel.insertChild(args.toIndex, args.item); | ||
}; | ||
/** | ||
* Handle the `tabCloseRequested` signal from the tab bar. | ||
* | ||
* #### Notes | ||
* The default implementation closes the respective widget. | ||
*/ | ||
TabPanel.prototype.onTabCloseRequested = function (sender, title) { | ||
var widget = this.findWidgetByTitle(title); | ||
if (widget) | ||
widget.close(); | ||
}; | ||
/** | ||
* Handle the `widgetRemoved` signal from the stacked panel. | ||
* | ||
* #### Notes | ||
* The default implementation removes the title from the tab bar. | ||
*/ | ||
TabPanel.prototype.onWidgetRemoved = function (sender, widget) { | ||
this._tabBar.removeTitle(widget.title); | ||
TabPanel.prototype._onWidgetRemoved = function (sender, widget) { | ||
if (this._currentWidget === widget) | ||
this._currentWidget = null; | ||
this._tabBar.removeItem(widget); | ||
}; | ||
@@ -291,0 +276,0 @@ return TabPanel; |
{ | ||
"name": "phosphor-tabs", | ||
"version": "1.0.0-beta.4", | ||
"version": "1.0.0-rc.0", | ||
"description": "Phosphor widgets for creating tab bars and tab panels.", | ||
@@ -9,10 +9,9 @@ "main": "lib/index.js", | ||
"phosphor-arrays": "^1.0.6", | ||
"phosphor-boxpanel": "^1.0.0-beta.3", | ||
"phosphor-boxpanel": "^1.0.0-rc.0", | ||
"phosphor-disposable": "^1.0.5", | ||
"phosphor-domutil": "^1.2.0", | ||
"phosphor-messaging": "^1.0.6", | ||
"phosphor-properties": "^2.0.0", | ||
"phosphor-signaling": "^1.2.0", | ||
"phosphor-stackedpanel": "^1.0.0-beta.5", | ||
"phosphor-widget": "^1.0.0-beta.6" | ||
"phosphor-stackedpanel": "^1.0.0-rc.0", | ||
"phosphor-widget": "^1.0.0-rc.0" | ||
}, | ||
@@ -77,4 +76,3 @@ "devDependencies": { | ||
"ui", | ||
"widget", | ||
"widgets" | ||
"widget" | ||
], | ||
@@ -81,0 +79,0 @@ "author": "S. Chris Colbert <sccolbert@gmail.com>", |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
8
0
62295
1667
- Removedphosphor-properties@^2.0.0
Updatedphosphor-widget@^1.0.0-rc.0