@deephaven/golden-layout 0.17.1-beta.2 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/LayoutManager.js +1140 -0
- package/dist/LayoutManager.js.map +1 -0
- package/dist/base.js +16 -0
- package/dist/base.js.map +1 -0
- package/dist/config/ItemDefaultConfig.js +8 -0
- package/dist/config/ItemDefaultConfig.js.map +1 -0
- package/dist/config/defaultConfig.js +42 -0
- package/dist/config/defaultConfig.js.map +1 -0
- package/dist/config/index.js +7 -0
- package/dist/config/index.js.map +1 -0
- package/dist/container/ItemContainer.js +192 -0
- package/dist/container/ItemContainer.js.map +1 -0
- package/dist/container/index.js +5 -0
- package/dist/container/index.js.map +1 -0
- package/dist/controls/BrowserPopout.js +260 -0
- package/dist/controls/BrowserPopout.js.map +1 -0
- package/dist/controls/DragProxy.js +236 -0
- package/dist/controls/DragProxy.js.map +1 -0
- package/dist/controls/DragSource.js +60 -0
- package/dist/controls/DragSource.js.map +1 -0
- package/dist/controls/DragSourceFromEvent.js +75 -0
- package/dist/controls/DragSourceFromEvent.js.map +1 -0
- package/dist/controls/DropTargetIndicator.js +28 -0
- package/dist/controls/DropTargetIndicator.js.map +1 -0
- package/dist/controls/Header.js +698 -0
- package/dist/controls/Header.js.map +1 -0
- package/dist/controls/HeaderButton.js +23 -0
- package/dist/controls/HeaderButton.js.map +1 -0
- package/dist/controls/Splitter.js +45 -0
- package/dist/controls/Splitter.js.map +1 -0
- package/dist/controls/Tab.js +259 -0
- package/dist/controls/Tab.js.map +1 -0
- package/dist/controls/TransitionIndicator.js +64 -0
- package/dist/controls/TransitionIndicator.js.map +1 -0
- package/dist/controls/index.js +23 -0
- package/dist/controls/index.js.map +1 -0
- package/dist/errors/ConfigurationError.js +10 -0
- package/dist/errors/ConfigurationError.js.map +1 -0
- package/dist/errors/index.js +5 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/items/AbstractContentItem.js +617 -0
- package/dist/items/AbstractContentItem.js.map +1 -0
- package/dist/items/Component.js +84 -0
- package/dist/items/Component.js.map +1 -0
- package/dist/items/Root.js +93 -0
- package/dist/items/Root.js.map +1 -0
- package/dist/items/RowOrColumn.js +550 -0
- package/dist/items/RowOrColumn.js.map +1 -0
- package/dist/items/Stack.js +498 -0
- package/dist/items/Stack.js.map +1 -0
- package/dist/items/index.js +13 -0
- package/dist/items/index.js.map +1 -0
- package/dist/utils/BubblingEvent.js +12 -0
- package/dist/utils/BubblingEvent.js.map +1 -0
- package/dist/utils/ConfigMinifier.js +160 -0
- package/dist/utils/ConfigMinifier.js.map +1 -0
- package/dist/utils/DragListener.js +128 -0
- package/dist/utils/DragListener.js.map +1 -0
- package/dist/utils/EventEmitter.js +133 -0
- package/dist/utils/EventEmitter.js.map +1 -0
- package/dist/utils/EventHub.js +147 -0
- package/dist/utils/EventHub.js.map +1 -0
- package/dist/utils/ReactComponentHandler.js +135 -0
- package/dist/utils/ReactComponentHandler.js.map +1 -0
- package/dist/utils/index.js +22 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/utils.js +195 -0
- package/dist/utils/utils.js.map +1 -0
- package/package.json +20 -47
- package/dist/goldenlayout.js +0 -6314
- package/dist/goldenlayout.min.js +0 -1
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
import $ from 'jquery';
|
|
2
|
+
import utils from '../utils/index.js';
|
|
3
|
+
import HeaderButton from './HeaderButton.js';
|
|
4
|
+
import Tab from './Tab.js';
|
|
5
|
+
/**
|
|
6
|
+
* This class represents a header above a Stack ContentItem.
|
|
7
|
+
*
|
|
8
|
+
* @param {lm.LayoutManager} layoutManager
|
|
9
|
+
* @param {lm.item.AbstractContentItem} parent
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
var Header = function Header(layoutManager, parent) {
|
|
13
|
+
utils.EventEmitter.call(this);
|
|
14
|
+
this.layoutManager = layoutManager;
|
|
15
|
+
this.element = $(Header._template);
|
|
16
|
+
|
|
17
|
+
if (this.layoutManager.config.settings.selectionEnabled === true) {
|
|
18
|
+
this.element.addClass('lm_selectable');
|
|
19
|
+
this.element.on('click', utils.fnBind(this._onHeaderClick, this));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
this.tabsContainer = this.element.find('.lm_tabs');
|
|
23
|
+
this.tabDropdownContainer = this.element.find('.lm_tabdropdown_list');
|
|
24
|
+
this.tabDropdownContainer.hide();
|
|
25
|
+
this.tabDropdownSearch = this.element.find('.lm_tabdropdown_search input');
|
|
26
|
+
this.tabDropdownList = null;
|
|
27
|
+
this._handleFilterInput = this._handleFilterInput.bind(this);
|
|
28
|
+
this._handleFilterKeydown = this._handleFilterKeydown.bind(this);
|
|
29
|
+
this.tabDropdownSearch.on('input', this._handleFilterInput);
|
|
30
|
+
this.tabDropdownSearch.on('keydown', this._handleFilterKeydown);
|
|
31
|
+
this.controlsContainer = this.element.find('.lm_controls');
|
|
32
|
+
this.parent = parent;
|
|
33
|
+
this.parent.on('resize', this._updateTabSizes, this);
|
|
34
|
+
this.tabs = [];
|
|
35
|
+
this.activeContentItem = null;
|
|
36
|
+
this.closeButton = null;
|
|
37
|
+
this.tabDropdownButton = null;
|
|
38
|
+
this.tabNextButton = $(Header._nextButtonTemplate);
|
|
39
|
+
this.tabPreviousButton = $(Header._previousButtonTemplate);
|
|
40
|
+
this._handleItemPickedUp = this._handleItemPickedUp.bind(this);
|
|
41
|
+
this._handleItemDropped = this._handleItemDropped.bind(this);
|
|
42
|
+
this._handleNextMouseEnter = this._handleNextMouseEnter.bind(this);
|
|
43
|
+
this._handleNextMouseLeave = this._handleNextMouseLeave.bind(this);
|
|
44
|
+
this._handlePreviousMouseEnter = this._handlePreviousMouseEnter.bind(this);
|
|
45
|
+
this._handlePreviousMouseLeave = this._handlePreviousMouseLeave.bind(this);
|
|
46
|
+
this._handleScrollRepeat = this._handleScrollRepeat.bind(this);
|
|
47
|
+
this._handleNextButtonMouseDown = this._handleNextButtonMouseDown.bind(this);
|
|
48
|
+
this._handlePreviousButtonMouseDown = this._handlePreviousButtonMouseDown.bind(this);
|
|
49
|
+
this._handleScrollButtonMouseDown = this._handleScrollButtonMouseDown.bind(this);
|
|
50
|
+
this._handleScrollButtonMouseUp = this._handleScrollButtonMouseUp.bind(this);
|
|
51
|
+
this._handleScrollEvent = this._handleScrollEvent.bind(this);
|
|
52
|
+
this.tabNextButton.on('mousedown', this._handleNextButtonMouseDown);
|
|
53
|
+
this.tabPreviousButton.on('mousedown', this._handlePreviousButtonMouseDown);
|
|
54
|
+
this.tabsContainer.on('scroll', this._handleScrollEvent); // use for scroll repeat
|
|
55
|
+
|
|
56
|
+
this.holdTimer = null;
|
|
57
|
+
this.rAF = null; // mouse hold timeout to act as hold instead of click
|
|
58
|
+
|
|
59
|
+
this.CLICK_TIMEOUT = 500; // mouse hold acceleration
|
|
60
|
+
|
|
61
|
+
this.START_SPEED = 0.01;
|
|
62
|
+
this.ACCELERATION = 0.0005;
|
|
63
|
+
this.SCROLL_LEFT = 'left';
|
|
64
|
+
this.SCROLL_RIGHT = 'right';
|
|
65
|
+
this.layoutManager.on('itemPickedUp', this._handleItemPickedUp);
|
|
66
|
+
this.layoutManager.on('itemDropped', this._handleItemDropped);
|
|
67
|
+
this.isDraggingTab = false;
|
|
68
|
+
this.isOverflowing = false;
|
|
69
|
+
this.isDropdownShown = false;
|
|
70
|
+
this.dropdownKeyIndex = 0; // append previous button template
|
|
71
|
+
|
|
72
|
+
this.tabsContainer.before(this.tabPreviousButton); // change reference to just the li, not the wrapping ul
|
|
73
|
+
|
|
74
|
+
this.tabPreviousButton = this.tabPreviousButton.find('>:first-child');
|
|
75
|
+
this.tabPreviousButton.hide();
|
|
76
|
+
this._showAdditionalTabsDropdown = this._showAdditionalTabsDropdown.bind(this);
|
|
77
|
+
this._hideAdditionalTabsDropdown = this._hideAdditionalTabsDropdown.bind(this);
|
|
78
|
+
this._lastVisibleTabIndex = -1;
|
|
79
|
+
this._tabControlOffset = this.layoutManager.config.settings.tabControlOffset;
|
|
80
|
+
|
|
81
|
+
this._createControls();
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
Header._template = ['<div class="lm_header">', '<ul class="lm_tabs"></ul>', '<ul class="lm_controls"></ul>', '<ul class="lm_tabdropdown_list">', '<li class="lm_tabdropdown_search"><input type="text" placeholder="Find tab..."></li>', '</ul>', '</div>'].join('');
|
|
85
|
+
Header._previousButtonTemplate = ['<ul class="lm_controls">', '<li class="lm_tabpreviousbutton"></li>', '</ul>'].join('');
|
|
86
|
+
Header._nextButtonTemplate = ['<li class="lm_tabnextbutton"></li>'].join('');
|
|
87
|
+
utils.copy(Header.prototype, {
|
|
88
|
+
/**
|
|
89
|
+
* Creates a new tab and associates it with a contentItem
|
|
90
|
+
*
|
|
91
|
+
* @param {lm.item.AbstractContentItem} contentItem
|
|
92
|
+
* @param {Integer} index The position of the tab
|
|
93
|
+
*
|
|
94
|
+
* @returns {void}
|
|
95
|
+
*/
|
|
96
|
+
createTab: function createTab(contentItem, index) {
|
|
97
|
+
var tab, i; //If there's already a tab relating to the
|
|
98
|
+
//content item, don't do anything
|
|
99
|
+
|
|
100
|
+
for (i = 0; i < this.tabs.length; i++) {
|
|
101
|
+
if (this.tabs[i].contentItem === contentItem) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
tab = new Tab(this, contentItem);
|
|
107
|
+
|
|
108
|
+
if (this.tabs.length === 0) {
|
|
109
|
+
this.tabs.push(tab);
|
|
110
|
+
this.tabsContainer.append(tab.element);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (index === undefined) {
|
|
115
|
+
index = this.tabs.length;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (index > 0) {
|
|
119
|
+
this.tabs[index - 1].element.after(tab.element);
|
|
120
|
+
} else {
|
|
121
|
+
this.tabs[0].element.before(tab.element);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
this.tabs.splice(index, 0, tab);
|
|
125
|
+
|
|
126
|
+
this._updateTabSizes();
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Finds a tab based on the contentItem its associated with and removes it.
|
|
131
|
+
*
|
|
132
|
+
* @param {lm.item.AbstractContentItem} contentItem
|
|
133
|
+
*
|
|
134
|
+
* @returns {void}
|
|
135
|
+
*/
|
|
136
|
+
removeTab: function removeTab(contentItem) {
|
|
137
|
+
for (var i = 0; i < this.tabs.length; i++) {
|
|
138
|
+
if (this.tabs[i].contentItem === contentItem) {
|
|
139
|
+
this.tabs[i]._$destroy();
|
|
140
|
+
|
|
141
|
+
this.tabs.splice(i, 1);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
throw new Error('contentItem is not controlled by this header');
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* The programmatical equivalent of clicking a Tab.
|
|
151
|
+
*
|
|
152
|
+
* @param {lm.item.AbstractContentItem} contentItem
|
|
153
|
+
*/
|
|
154
|
+
setActiveContentItem: function setActiveContentItem(contentItem) {
|
|
155
|
+
var isActive;
|
|
156
|
+
|
|
157
|
+
for (var i = 0; i < this.tabs.length; i++) {
|
|
158
|
+
isActive = this.tabs[i].contentItem === contentItem;
|
|
159
|
+
this.tabs[i].setActive(isActive);
|
|
160
|
+
|
|
161
|
+
if (isActive === true) {
|
|
162
|
+
this.activeContentItem = contentItem;
|
|
163
|
+
this.parent.config.activeItemIndex = i;
|
|
164
|
+
}
|
|
165
|
+
} // makes sure dropped tabs are scrollintoview, removed any re-ordering
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
this.tabs[this.parent.config.activeItemIndex].element.get(0).scrollIntoView({
|
|
169
|
+
inline: 'nearest'
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
this._hideAdditionalTabsDropdown();
|
|
173
|
+
|
|
174
|
+
this._updateTabSizes();
|
|
175
|
+
|
|
176
|
+
this.parent.emitBubblingEvent('stateChanged');
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Programmatically operate with header position.
|
|
181
|
+
*
|
|
182
|
+
* @param {string} position one of ('top','left','right','bottom') to set or empty to get it.
|
|
183
|
+
*
|
|
184
|
+
* @returns {string} previous header position
|
|
185
|
+
*/
|
|
186
|
+
position: function position(_position) {
|
|
187
|
+
var previous = this.parent._header.show;
|
|
188
|
+
if (previous && !this.parent._side) previous = 'top';
|
|
189
|
+
|
|
190
|
+
if (_position !== undefined && this.parent._header.show !== _position) {
|
|
191
|
+
this.parent._header.show = _position;
|
|
192
|
+
|
|
193
|
+
this.parent._setupHeaderPosition();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return previous;
|
|
197
|
+
},
|
|
198
|
+
// Manually attaching so wheel can be passive instead of jquery .on
|
|
199
|
+
// _attachWheelListener is called by parent init
|
|
200
|
+
_attachWheelListener: function _attachWheelListener() {
|
|
201
|
+
this.tabsContainer.get(0).addEventListener('wheel', this._handleWheelEvent, {
|
|
202
|
+
passive: true
|
|
203
|
+
});
|
|
204
|
+
},
|
|
205
|
+
// detach called by this.destroy
|
|
206
|
+
_detachWheelListener: function _detachWheelListener() {
|
|
207
|
+
this.tabsContainer.get(0).removeEventListener('wheel', this._handleWheelEvent, {
|
|
208
|
+
passive: true
|
|
209
|
+
});
|
|
210
|
+
},
|
|
211
|
+
_handleWheelEvent: function _handleWheelEvent(event) {
|
|
212
|
+
var target = event.currentTarget; // we only care about the larger of the two deltas
|
|
213
|
+
|
|
214
|
+
var delta = Math.abs(event.deltaY) > Math.abs(event.deltaX) ? event.deltaY : event.deltaX; // jshint
|
|
215
|
+
|
|
216
|
+
/* globals WheelEvent */
|
|
217
|
+
|
|
218
|
+
if (event.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
|
|
219
|
+
// Users can set OS to be in deltaMode page
|
|
220
|
+
// scrolly by page units as pixels
|
|
221
|
+
delta *= this.tabsContainer.innerWidhth();
|
|
222
|
+
} else if (event.deltaMode === WheelEvent.DOM_DELTA_LINE) {
|
|
223
|
+
// chrome goes 100px per 3 lines, and firefox would go 102 per 3 (17 lineheight * 3 lines * 2)
|
|
224
|
+
delta *= 100 / 3;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
target.scrollLeft += Math.round(delta);
|
|
228
|
+
},
|
|
229
|
+
// on scroll we need to check if side error might need to be disabled
|
|
230
|
+
_handleScrollEvent: function _handleScrollEvent() {
|
|
231
|
+
this._checkScrollArrows();
|
|
232
|
+
},
|
|
233
|
+
// when and item is picked up, attach mouse enter listeners to next/previous buttons
|
|
234
|
+
_handleItemPickedUp: function _handleItemPickedUp() {
|
|
235
|
+
this.isDraggingTab = true;
|
|
236
|
+
this.controlsContainer.on('mouseenter', this._handleNextMouseEnter);
|
|
237
|
+
this.tabPreviousButton.on('mouseenter', this._handlePreviousMouseEnter);
|
|
238
|
+
},
|
|
239
|
+
// when an item is dropped remove listeners and cancel animation
|
|
240
|
+
_handleItemDropped: function _handleItemDropped() {
|
|
241
|
+
this.isDraggingTab = false;
|
|
242
|
+
this.rAF = window.cancelAnimationFrame(this.rAF);
|
|
243
|
+
this.controlsContainer.off('mouseenter', this._handleNextMouseEnter);
|
|
244
|
+
this.controlsContainer.off('mouseleave', this._handleNextMouseLeave);
|
|
245
|
+
this.tabPreviousButton.off('mouseenter', this._handlePreviousMouseEnter);
|
|
246
|
+
this.tabPreviousButton.off('mouseleave', this._handlePreviousMouseLeave);
|
|
247
|
+
},
|
|
248
|
+
// on next/previous enter start scroll repeat animation loop
|
|
249
|
+
// and attach a listener looking for mouseleave
|
|
250
|
+
// cancel animation on mouse leave, and remove leave listener
|
|
251
|
+
_handleNextMouseEnter: function _handleNextMouseEnter() {
|
|
252
|
+
this.controlsContainer.on('mouseleave', this._handleNextMouseLeave);
|
|
253
|
+
|
|
254
|
+
this._handleScrollRepeat(this.SCROLL_RIGHT, this.tabsContainer.scrollLeft());
|
|
255
|
+
},
|
|
256
|
+
_handlePreviousMouseEnter: function _handlePreviousMouseEnter() {
|
|
257
|
+
this.tabPreviousButton.on('mouseleave', this._handlePreviousMouseLeave);
|
|
258
|
+
|
|
259
|
+
this._handleScrollRepeat(this.SCROLL_LEFT, this.tabsContainer.scrollLeft());
|
|
260
|
+
},
|
|
261
|
+
_handleNextMouseLeave: function _handleNextMouseLeave() {
|
|
262
|
+
this.rAF = window.cancelAnimationFrame(this.rAF);
|
|
263
|
+
this.controlsContainer.off('mouseleave', this._handleNextMouseLeave);
|
|
264
|
+
},
|
|
265
|
+
_handlePreviousMouseLeave: function _handlePreviousMouseLeave() {
|
|
266
|
+
this.rAF = window.cancelAnimationFrame(this.rAF);
|
|
267
|
+
this.tabPreviousButton.off('mouseleave', this._handlePreviousMouseLeave);
|
|
268
|
+
},
|
|
269
|
+
// scroll one tab to the right on mouse down
|
|
270
|
+
// start scrollRepeat if mouse is held down
|
|
271
|
+
_handleNextButtonMouseDown: function _handleNextButtonMouseDown() {
|
|
272
|
+
var rightOffscreenChild;
|
|
273
|
+
|
|
274
|
+
for (var i = 0; i < this.tabs.length; i += 1) {
|
|
275
|
+
if (this.tabs[i].element.get(0).offsetLeft > this.tabsContainer.get(0).offsetWidth + this.tabsContainer.scrollLeft()) {
|
|
276
|
+
rightOffscreenChild = this.tabs[i].element.get(0);
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (rightOffscreenChild) {
|
|
282
|
+
rightOffscreenChild.scrollIntoView({
|
|
283
|
+
behavior: 'smooth',
|
|
284
|
+
inline: 'nearest'
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
this._handleScrollButtonMouseDown(this.SCROLL_RIGHT);
|
|
288
|
+
} else {
|
|
289
|
+
this.tabsContainer.get(0).scrollLeft = this.tabsContainer.get(0).scrollWidth;
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
// scroll left by one tab
|
|
293
|
+
// start scrollRepeat if mouse is held down
|
|
294
|
+
_handlePreviousButtonMouseDown: function _handlePreviousButtonMouseDown() {
|
|
295
|
+
var leftOffscreenChild;
|
|
296
|
+
|
|
297
|
+
for (var i = this.tabs.length - 1; i >= 0; i -= 1) {
|
|
298
|
+
if (this.tabs[i].element.get(0).offsetLeft < this.tabsContainer.scrollLeft()) {
|
|
299
|
+
leftOffscreenChild = this.tabs[i].element.get(0);
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (leftOffscreenChild) {
|
|
305
|
+
leftOffscreenChild.scrollIntoView({
|
|
306
|
+
behavior: 'smooth',
|
|
307
|
+
inline: 'start'
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
this._handleScrollButtonMouseDown(this.SCROLL_LEFT);
|
|
311
|
+
} else {
|
|
312
|
+
this.tabsContainer.get(0).scrollLeft = 0;
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
// when hold timer is reached start scroll repeat anim loop
|
|
316
|
+
// cancel it when mouse up happens anywhere
|
|
317
|
+
_handleScrollButtonMouseDown: function _handleScrollButtonMouseDown(direction) {
|
|
318
|
+
// closure so that scrollLeft is value at end of timer, and not start of timer
|
|
319
|
+
this.holdTimer = setTimeout(function () {
|
|
320
|
+
this._handleScrollRepeat(direction, this.tabsContainer.scrollLeft(), 100); // kickstart deltaX to be faster
|
|
321
|
+
|
|
322
|
+
}.bind(this), this.CLICK_TIMEOUT);
|
|
323
|
+
window.addEventListener('mouseup', this._handleScrollButtonMouseUp);
|
|
324
|
+
},
|
|
325
|
+
// cancel scroll repeat
|
|
326
|
+
_handleScrollButtonMouseUp: function _handleScrollButtonMouseUp() {
|
|
327
|
+
this.holdTimer = clearTimeout(this.holdTimer);
|
|
328
|
+
this.rAF = cancelAnimationFrame(this.rAF);
|
|
329
|
+
window.removeEventListener('mouseup', this._handleScrollButtonMouseUp);
|
|
330
|
+
},
|
|
331
|
+
// disables scroll arrow if at edge of scroll area
|
|
332
|
+
_checkScrollArrows: function _checkScrollArrows() {
|
|
333
|
+
if (this.tabsContainer.scrollLeft() === 0) {
|
|
334
|
+
this.tabPreviousButton.first().attr('disabled', true);
|
|
335
|
+
} else if (this.tabsContainer.scrollLeft() + this.tabsContainer.innerWidth() === this.tabsContainer.get(0).scrollWidth) {
|
|
336
|
+
this.tabNextButton.attr('disabled', true);
|
|
337
|
+
} else {
|
|
338
|
+
this.tabNextButton.attr('disabled', false);
|
|
339
|
+
this.tabPreviousButton.first().attr('disabled', false);
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
// scrolls the tab header container on drag tab over control buttons
|
|
343
|
+
// or on press and hold of scroll arrows at either end
|
|
344
|
+
// called recurisevly in an animation loop until cancelled
|
|
345
|
+
// or directional end is reached
|
|
346
|
+
_handleScrollRepeat: function _handleScrollRepeat(direction, startX, deltaX, prevTimestamp) {
|
|
347
|
+
if (!deltaX) deltaX = 0;
|
|
348
|
+
var tabContainerRect = this.tabsContainer.get(0).getBoundingClientRect();
|
|
349
|
+
|
|
350
|
+
if (direction === this.SCROLL_LEFT) {
|
|
351
|
+
this.tabsContainer.scrollLeft(startX - deltaX);
|
|
352
|
+
|
|
353
|
+
if (this.isDraggingTab) {
|
|
354
|
+
// update drag placeholder
|
|
355
|
+
this.parent._highlightHeaderDropZone(tabContainerRect.left - 1);
|
|
356
|
+
} // stop loop at left edge
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
if (this.tabsContainer.scrollLeft() === 0) {
|
|
360
|
+
this._checkScrollArrows();
|
|
361
|
+
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
} else if (direction === this.SCROLL_RIGHT) {
|
|
365
|
+
this.tabsContainer.scrollLeft(startX + deltaX);
|
|
366
|
+
|
|
367
|
+
if (this.isDraggingTab) {
|
|
368
|
+
// update drag placeholder
|
|
369
|
+
this.parent._highlightHeaderDropZone(tabContainerRect.right + 1);
|
|
370
|
+
} // stop loop at right edge
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
if (this.tabsContainer.scrollLeft() + this.tabsContainer.innerWidth() === this.tabsContainer.get(0).scrollWidth) {
|
|
374
|
+
this._checkScrollArrows();
|
|
375
|
+
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
} // setup animation loop, scroll with acceleration
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
window.cancelAnimationFrame(this.rAF);
|
|
382
|
+
this.rAF = window.requestAnimationFrame(function (timestamp) {
|
|
383
|
+
var startTime = prevTimestamp || timestamp;
|
|
384
|
+
var deltaTime = timestamp - startTime;
|
|
385
|
+
var newDeltaX = this.START_SPEED * deltaTime + 0.5 * this.ACCELERATION * (deltaTime * deltaTime);
|
|
386
|
+
newDeltaX = Math.min(newDeltaX, this.tabsContainer.get(0).scrollWidth);
|
|
387
|
+
|
|
388
|
+
this._handleScrollRepeat(direction, startX, newDeltaX, startTime);
|
|
389
|
+
}.bind(this));
|
|
390
|
+
},
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Programmatically set closability.
|
|
394
|
+
*
|
|
395
|
+
* @package private
|
|
396
|
+
* @param {Boolean} isClosable Whether to enable/disable closability.
|
|
397
|
+
*
|
|
398
|
+
* @returns {Boolean} Whether the action was successful
|
|
399
|
+
*/
|
|
400
|
+
_$setClosable: function _$setClosable(isClosable) {
|
|
401
|
+
if (this.closeButton && this._isClosable()) {
|
|
402
|
+
this.closeButton.element[isClosable ? 'show' : 'hide']();
|
|
403
|
+
return true;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return false;
|
|
407
|
+
},
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Destroys the entire header
|
|
411
|
+
*
|
|
412
|
+
* @package private
|
|
413
|
+
*
|
|
414
|
+
* @returns {void}
|
|
415
|
+
*/
|
|
416
|
+
_$destroy: function _$destroy() {
|
|
417
|
+
this.emit('destroy', this);
|
|
418
|
+
|
|
419
|
+
for (var i = 0; i < this.tabs.length; i++) {
|
|
420
|
+
this.tabs[i]._$destroy();
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
this._detachWheelListener();
|
|
424
|
+
|
|
425
|
+
this._handleItemDropped();
|
|
426
|
+
|
|
427
|
+
$(document).off('mouseup', this._hideAdditionalTabsDropdown);
|
|
428
|
+
this.tabDropdownSearch.off('input', this._handleFilterInput);
|
|
429
|
+
this.tabDropdownSearch.off('keydown', this._handleFilterKeydown);
|
|
430
|
+
this.element.remove();
|
|
431
|
+
},
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* get settings from header
|
|
435
|
+
*
|
|
436
|
+
* @returns {string} when exists
|
|
437
|
+
*/
|
|
438
|
+
_getHeaderSetting: function _getHeaderSetting(name) {
|
|
439
|
+
if (name in this.parent._header) return this.parent._header[name];
|
|
440
|
+
},
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Creates the popout, maximise and close buttons in the header's top right corner
|
|
444
|
+
*
|
|
445
|
+
* @returns {void}
|
|
446
|
+
*/
|
|
447
|
+
_createControls: function _createControls() {
|
|
448
|
+
var closeStack, popout, label, maximiseLabel, minimiseLabel, maximise, maximiseButton, tabDropdownLabel, tabOverflowNextLabel, tabOverflowPreviousLabel;
|
|
449
|
+
/**
|
|
450
|
+
* Dropdown to show additional tabs.
|
|
451
|
+
*/
|
|
452
|
+
|
|
453
|
+
tabDropdownLabel = this.layoutManager.config.labels.tabDropdown;
|
|
454
|
+
tabOverflowNextLabel = this.layoutManager.config.labels.tabNextLabel;
|
|
455
|
+
tabOverflowPreviousLabel = this.layoutManager.config.labels.tabPreviousLabel;
|
|
456
|
+
this.tabDropdownButton = new HeaderButton(this, tabDropdownLabel, 'lm_tabdropdown', this._showAdditionalTabsDropdown);
|
|
457
|
+
this.tabDropdownButton.element.hide();
|
|
458
|
+
this.controlsContainer.prepend(this.tabNextButton);
|
|
459
|
+
this.tabNextButton.hide();
|
|
460
|
+
/**
|
|
461
|
+
* Popout control to launch component in new window.
|
|
462
|
+
*/
|
|
463
|
+
|
|
464
|
+
if (this._getHeaderSetting('popout')) {
|
|
465
|
+
popout = utils.fnBind(this._onPopoutClick, this);
|
|
466
|
+
label = this._getHeaderSetting('popout');
|
|
467
|
+
new HeaderButton(this, label, 'lm_popout', popout);
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Maximise control - set the component to the full size of the layout
|
|
471
|
+
*/
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
if (this._getHeaderSetting('maximise')) {
|
|
475
|
+
maximise = utils.fnBind(this.parent.toggleMaximise, this.parent);
|
|
476
|
+
maximiseLabel = this._getHeaderSetting('maximise');
|
|
477
|
+
minimiseLabel = this._getHeaderSetting('minimise');
|
|
478
|
+
maximiseButton = new HeaderButton(this, maximiseLabel, 'lm_maximise', maximise);
|
|
479
|
+
this.parent.on('maximised', function () {
|
|
480
|
+
maximiseButton.element.attr('title', minimiseLabel);
|
|
481
|
+
});
|
|
482
|
+
this.parent.on('minimised', function () {
|
|
483
|
+
maximiseButton.element.attr('title', maximiseLabel);
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Close button
|
|
488
|
+
*/
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
if (this._isClosable()) {
|
|
492
|
+
closeStack = utils.fnBind(this.parent.remove, this.parent);
|
|
493
|
+
label = this._getHeaderSetting('close');
|
|
494
|
+
this.closeButton = new HeaderButton(this, label, 'lm_close', closeStack);
|
|
495
|
+
}
|
|
496
|
+
},
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Shows drop down for additional tabs when there are too many to display.
|
|
500
|
+
*
|
|
501
|
+
* @returns {void}
|
|
502
|
+
*/
|
|
503
|
+
_showAdditionalTabsDropdown: function _showAdditionalTabsDropdown() {
|
|
504
|
+
if (this.isDropdownShown) {
|
|
505
|
+
this._hideAdditionalTabsDropdown();
|
|
506
|
+
|
|
507
|
+
return;
|
|
508
|
+
} // clone tabs in the current list, with event listeners
|
|
509
|
+
// and add them to the drop down
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
this.tabDropdownList = this.tabsContainer.clone(true).appendTo(this.tabDropdownContainer).children().removeClass('lm_active'); // show the dropdown
|
|
513
|
+
|
|
514
|
+
this.tabDropdownContainer.show();
|
|
515
|
+
this.isDropdownShown = true; // dropdown is a part of the header z-index context
|
|
516
|
+
// add class to header when dropdown is open
|
|
517
|
+
// so we can bump the z-index of the lists parent
|
|
518
|
+
|
|
519
|
+
this.element.addClass('lm_dropdown_open'); // focus the dropdown filter list input
|
|
520
|
+
|
|
521
|
+
this.tabDropdownSearch.val('').focus();
|
|
522
|
+
this.dropdownKeyIndex = 0;
|
|
523
|
+
this.tabDropdownList.eq(this.dropdownKeyIndex).addClass('lm_keyboard_active');
|
|
524
|
+
$(document).on('mousedown', this._hideAdditionalTabsDropdown);
|
|
525
|
+
|
|
526
|
+
this._updateAdditionalTabsDropdown();
|
|
527
|
+
},
|
|
528
|
+
// enables synthetic keyboard navigation of the list
|
|
529
|
+
_handleFilterKeydown: function _handleFilterKeydown(e) {
|
|
530
|
+
if (this.dropdownKeyIndex === -1) return;
|
|
531
|
+
|
|
532
|
+
if (e.key === 'Escape') {
|
|
533
|
+
this._hideAdditionalTabsDropdown();
|
|
534
|
+
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
539
|
+
// simulate "click"
|
|
540
|
+
this._hideAdditionalTabsDropdown();
|
|
541
|
+
|
|
542
|
+
this.tabs[this.dropdownKeyIndex]._onTabClick();
|
|
543
|
+
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function getNextDropdownIndex(startIndex, delta, tabDropdownList) {
|
|
548
|
+
if (tabDropdownList.length < 2) {
|
|
549
|
+
return -1;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
var i = (startIndex + delta + tabDropdownList.length) % tabDropdownList.length;
|
|
553
|
+
|
|
554
|
+
while (i !== startIndex) {
|
|
555
|
+
if (tabDropdownList.eq(i).css('display') !== 'none') {
|
|
556
|
+
return i;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
i = (i + delta + tabDropdownList.length) % tabDropdownList.length;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
return startIndex;
|
|
563
|
+
} // allow tab or arrow key navigation of list, prevent tabs default behaviour
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
if (e.key === 'ArrowDown' || e.key === 'Tab' && e.shiftKey === false) {
|
|
567
|
+
e.preventDefault();
|
|
568
|
+
this.tabDropdownList.eq(this.dropdownKeyIndex).removeClass('lm_keyboard_active');
|
|
569
|
+
this.dropdownKeyIndex = getNextDropdownIndex(this.dropdownKeyIndex, 1, this.tabDropdownList);
|
|
570
|
+
this.tabDropdownList.eq(this.dropdownKeyIndex).addClass('lm_keyboard_active');
|
|
571
|
+
} else if (e.key === 'ArrowUp' || e.key === 'Tab') {
|
|
572
|
+
e.preventDefault();
|
|
573
|
+
this.tabDropdownList.eq(this.dropdownKeyIndex).removeClass('lm_keyboard_active');
|
|
574
|
+
this.dropdownKeyIndex = getNextDropdownIndex(this.dropdownKeyIndex, -1, this.tabDropdownList);
|
|
575
|
+
this.tabDropdownList.eq(this.dropdownKeyIndex).addClass('lm_keyboard_active');
|
|
576
|
+
}
|
|
577
|
+
},
|
|
578
|
+
// filters the list
|
|
579
|
+
_handleFilterInput: function _handleFilterInput(event) {
|
|
580
|
+
// reset keyboard index
|
|
581
|
+
this.tabDropdownList.eq(this.dropdownKeyIndex).removeClass('lm_keyboard_active');
|
|
582
|
+
this.dropdownKeyIndex = -1;
|
|
583
|
+
|
|
584
|
+
for (var i = 0; i < this.tabDropdownList.length; i++) {
|
|
585
|
+
if (this.tabs[i].contentItem.config.title.toLowerCase().indexOf(event.target.value.toLowerCase()) !== -1) {
|
|
586
|
+
this.tabDropdownList.eq(i).css('display', '');
|
|
587
|
+
if (this.dropdownKeyIndex === -1) this.dropdownKeyIndex = i;
|
|
588
|
+
} else {
|
|
589
|
+
this.tabDropdownList.eq(i).css('display', 'none');
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (this.dropdownKeyIndex !== -1) {
|
|
594
|
+
this.tabDropdownList.eq(this.dropdownKeyIndex).addClass('lm_keyboard_active');
|
|
595
|
+
}
|
|
596
|
+
},
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Hides drop down for additional tabs when needed. It is called via mousedown
|
|
600
|
+
* event on document when list is open, or programmatically when drag starts,
|
|
601
|
+
* or active tab changes etc.
|
|
602
|
+
*
|
|
603
|
+
* @returns {void}
|
|
604
|
+
*/
|
|
605
|
+
_hideAdditionalTabsDropdown: function _hideAdditionalTabsDropdown(event) {
|
|
606
|
+
// dropdown already closed, do nothing
|
|
607
|
+
if (!this.isDropdownShown) return;
|
|
608
|
+
|
|
609
|
+
if (event && this.tabDropdownContainer.get(0).contains(event.target)) {
|
|
610
|
+
// prevent events occuring inside the list from causing a close
|
|
611
|
+
return;
|
|
612
|
+
} else if (event && this.tabDropdownButton.element.get(0) === event.target) {
|
|
613
|
+
// do nothing on the mouse down so that the click event can close, rather then re-open
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
this.element.removeClass('lm_dropdown_open');
|
|
618
|
+
this.tabDropdownContainer.hide();
|
|
619
|
+
this.isDropdownShown = false; // remove the current tab list
|
|
620
|
+
|
|
621
|
+
this.tabDropdownContainer.find('.lm_tabs').remove();
|
|
622
|
+
$(document).off('mousedown', this._hideAdditionalTabsDropdown);
|
|
623
|
+
},
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Ensures additional tab drop down doesn't overflow screen, and instead becomes scrollable.
|
|
627
|
+
*
|
|
628
|
+
* @returns {void}
|
|
629
|
+
*/
|
|
630
|
+
_updateAdditionalTabsDropdown: function _updateAdditionalTabsDropdown() {
|
|
631
|
+
this.tabDropdownContainer.css('max-height', '');
|
|
632
|
+
var h = this.tabDropdownContainer[0].scrollHeight;
|
|
633
|
+
if (h === 0) return; // height can be zero if called on a hidden or empty list
|
|
634
|
+
|
|
635
|
+
var y = this.tabDropdownContainer.offset().top - $(window).scrollTop(); // set max height of tab dropdown to be less then the viewport height - dropdown offset
|
|
636
|
+
|
|
637
|
+
if (y + h > $(window).height()) {
|
|
638
|
+
this.tabDropdownContainer.css('max-height', $(window).height() - y - 10); // 10 being a padding value
|
|
639
|
+
}
|
|
640
|
+
},
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Checks whether the header is closable based on the parent config and
|
|
644
|
+
* the global config.
|
|
645
|
+
*
|
|
646
|
+
* @returns {Boolean} Whether the header is closable.
|
|
647
|
+
*/
|
|
648
|
+
_isClosable: function _isClosable() {
|
|
649
|
+
return this.parent.config.isClosable && this.layoutManager.config.settings.showCloseIcon;
|
|
650
|
+
},
|
|
651
|
+
_onPopoutClick: function _onPopoutClick() {
|
|
652
|
+
if (this.layoutManager.config.settings.popoutWholeStack === true) {
|
|
653
|
+
this.parent.popout();
|
|
654
|
+
} else {
|
|
655
|
+
this.activeContentItem.popout();
|
|
656
|
+
}
|
|
657
|
+
},
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Invoked when the header's background is clicked (not it's tabs or controls)
|
|
661
|
+
*
|
|
662
|
+
* @param {jQuery DOM event} event
|
|
663
|
+
*
|
|
664
|
+
* @returns {void}
|
|
665
|
+
*/
|
|
666
|
+
_onHeaderClick: function _onHeaderClick(event) {
|
|
667
|
+
if (event.target === this.element[0]) {
|
|
668
|
+
this.parent.select();
|
|
669
|
+
}
|
|
670
|
+
},
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Pushes the tabs to the tab dropdown if the available space is not sufficient
|
|
674
|
+
*
|
|
675
|
+
* @returns {void}
|
|
676
|
+
*/
|
|
677
|
+
_updateTabSizes: function _updateTabSizes() {
|
|
678
|
+
if (this.tabs.length === 0) {
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if (!this.isOverflowing && this.tabsContainer.get(0).scrollWidth > this.tabsContainer.get(0).clientWidth) {
|
|
683
|
+
this.tabDropdownButton.element.show();
|
|
684
|
+
this.tabNextButton.show();
|
|
685
|
+
this.tabPreviousButton.show();
|
|
686
|
+
this.isOverflowing = true;
|
|
687
|
+
} else if (this.isOverflowing && this.tabsContainer.get(0).scrollWidth <= this.tabsContainer.get(0).clientWidth) {
|
|
688
|
+
this.tabDropdownButton.element.hide();
|
|
689
|
+
this.tabNextButton.hide();
|
|
690
|
+
this.tabPreviousButton.hide();
|
|
691
|
+
this.isOverflowing = false;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
if (this.isOverflowing) this._checkScrollArrows();
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
export default Header;
|
|
698
|
+
//# sourceMappingURL=Header.js.map
|