@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.
Files changed (73) hide show
  1. package/dist/LayoutManager.js +1140 -0
  2. package/dist/LayoutManager.js.map +1 -0
  3. package/dist/base.js +16 -0
  4. package/dist/base.js.map +1 -0
  5. package/dist/config/ItemDefaultConfig.js +8 -0
  6. package/dist/config/ItemDefaultConfig.js.map +1 -0
  7. package/dist/config/defaultConfig.js +42 -0
  8. package/dist/config/defaultConfig.js.map +1 -0
  9. package/dist/config/index.js +7 -0
  10. package/dist/config/index.js.map +1 -0
  11. package/dist/container/ItemContainer.js +192 -0
  12. package/dist/container/ItemContainer.js.map +1 -0
  13. package/dist/container/index.js +5 -0
  14. package/dist/container/index.js.map +1 -0
  15. package/dist/controls/BrowserPopout.js +260 -0
  16. package/dist/controls/BrowserPopout.js.map +1 -0
  17. package/dist/controls/DragProxy.js +236 -0
  18. package/dist/controls/DragProxy.js.map +1 -0
  19. package/dist/controls/DragSource.js +60 -0
  20. package/dist/controls/DragSource.js.map +1 -0
  21. package/dist/controls/DragSourceFromEvent.js +75 -0
  22. package/dist/controls/DragSourceFromEvent.js.map +1 -0
  23. package/dist/controls/DropTargetIndicator.js +28 -0
  24. package/dist/controls/DropTargetIndicator.js.map +1 -0
  25. package/dist/controls/Header.js +698 -0
  26. package/dist/controls/Header.js.map +1 -0
  27. package/dist/controls/HeaderButton.js +23 -0
  28. package/dist/controls/HeaderButton.js.map +1 -0
  29. package/dist/controls/Splitter.js +45 -0
  30. package/dist/controls/Splitter.js.map +1 -0
  31. package/dist/controls/Tab.js +259 -0
  32. package/dist/controls/Tab.js.map +1 -0
  33. package/dist/controls/TransitionIndicator.js +64 -0
  34. package/dist/controls/TransitionIndicator.js.map +1 -0
  35. package/dist/controls/index.js +23 -0
  36. package/dist/controls/index.js.map +1 -0
  37. package/dist/errors/ConfigurationError.js +10 -0
  38. package/dist/errors/ConfigurationError.js.map +1 -0
  39. package/dist/errors/index.js +5 -0
  40. package/dist/errors/index.js.map +1 -0
  41. package/dist/index.js +3 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/items/AbstractContentItem.js +617 -0
  44. package/dist/items/AbstractContentItem.js.map +1 -0
  45. package/dist/items/Component.js +84 -0
  46. package/dist/items/Component.js.map +1 -0
  47. package/dist/items/Root.js +93 -0
  48. package/dist/items/Root.js.map +1 -0
  49. package/dist/items/RowOrColumn.js +550 -0
  50. package/dist/items/RowOrColumn.js.map +1 -0
  51. package/dist/items/Stack.js +498 -0
  52. package/dist/items/Stack.js.map +1 -0
  53. package/dist/items/index.js +13 -0
  54. package/dist/items/index.js.map +1 -0
  55. package/dist/utils/BubblingEvent.js +12 -0
  56. package/dist/utils/BubblingEvent.js.map +1 -0
  57. package/dist/utils/ConfigMinifier.js +160 -0
  58. package/dist/utils/ConfigMinifier.js.map +1 -0
  59. package/dist/utils/DragListener.js +128 -0
  60. package/dist/utils/DragListener.js.map +1 -0
  61. package/dist/utils/EventEmitter.js +133 -0
  62. package/dist/utils/EventEmitter.js.map +1 -0
  63. package/dist/utils/EventHub.js +147 -0
  64. package/dist/utils/EventHub.js.map +1 -0
  65. package/dist/utils/ReactComponentHandler.js +135 -0
  66. package/dist/utils/ReactComponentHandler.js.map +1 -0
  67. package/dist/utils/index.js +22 -0
  68. package/dist/utils/index.js.map +1 -0
  69. package/dist/utils/utils.js +195 -0
  70. package/dist/utils/utils.js.map +1 -0
  71. package/package.json +20 -47
  72. package/dist/goldenlayout.js +0 -6314
  73. 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