UNPKG

45.5 kBJavaScriptView Raw
1"use strict";
2var __extends = (this && this.__extends) || (function () {
3 var extendStatics = function (d, b) {
4 extendStatics = Object.setPrototypeOf ||
5 ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
6 function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
7 return extendStatics(d, b);
8 }
9 return function (d, b) {
10 extendStatics(d, b);
11 function __() { this.constructor = d; }
12 d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
13 };
14})();
15Object.defineProperty(exports, "__esModule", { value: true });
16/*-----------------------------------------------------------------------------
17| Copyright (c) 2014-2017, PhosphorJS Contributors
18|
19| Distributed under the terms of the BSD 3-Clause License.
20|
21| The full license is in the file LICENSE, distributed with this software.
22|----------------------------------------------------------------------------*/
23var algorithm_1 = require("@phosphor/algorithm");
24var domutils_1 = require("@phosphor/domutils");
25var dragdrop_1 = require("@phosphor/dragdrop");
26var messaging_1 = require("@phosphor/messaging");
27var signaling_1 = require("@phosphor/signaling");
28var virtualdom_1 = require("@phosphor/virtualdom");
29var title_1 = require("./title");
30var widget_1 = require("./widget");
31/**
32 * A widget which displays titles as a single row or column of tabs.
33 *
34 * #### Notes
35 * If CSS transforms are used to rotate nodes for vertically oriented
36 * text, then tab dragging will not work correctly. The `tabsMovable`
37 * property should be set to `false` when rotating nodes from CSS.
38 */
39var TabBar = /** @class */ (function (_super) {
40 __extends(TabBar, _super);
41 /**
42 * Construct a new tab bar.
43 *
44 * @param options - The options for initializing the tab bar.
45 */
46 function TabBar(options) {
47 if (options === void 0) { options = {}; }
48 var _this = _super.call(this, { node: Private.createNode() }) || this;
49 _this._currentIndex = -1;
50 _this._titles = [];
51 _this._previousTitle = null;
52 _this._dragData = null;
53 _this._tabMoved = new signaling_1.Signal(_this);
54 _this._currentChanged = new signaling_1.Signal(_this);
55 _this._tabCloseRequested = new signaling_1.Signal(_this);
56 _this._tabDetachRequested = new signaling_1.Signal(_this);
57 _this._tabActivateRequested = new signaling_1.Signal(_this);
58 _this.addClass('p-TabBar');
59 _this.setFlag(widget_1.Widget.Flag.DisallowLayout);
60 _this.tabsMovable = options.tabsMovable || false;
61 _this.allowDeselect = options.allowDeselect || false;
62 _this.insertBehavior = options.insertBehavior || 'select-tab-if-needed';
63 _this.removeBehavior = options.removeBehavior || 'select-tab-after';
64 _this.renderer = options.renderer || TabBar.defaultRenderer;
65 _this._orientation = options.orientation || 'horizontal';
66 _this.dataset['orientation'] = _this._orientation;
67 return _this;
68 }
69 /**
70 * Dispose of the resources held by the widget.
71 */
72 TabBar.prototype.dispose = function () {
73 this._releaseMouse();
74 this._titles.length = 0;
75 this._previousTitle = null;
76 _super.prototype.dispose.call(this);
77 };
78 Object.defineProperty(TabBar.prototype, "currentChanged", {
79 /**
80 * A signal emitted when the current tab is changed.
81 *
82 * #### Notes
83 * This signal is emitted when the currently selected tab is changed
84 * either through user or programmatic interaction.
85 *
86 * Notably, this signal is not emitted when the index of the current
87 * tab changes due to tabs being inserted, removed, or moved. It is
88 * only emitted when the actual current tab node is changed.
89 */
90 get: function () {
91 return this._currentChanged;
92 },
93 enumerable: true,
94 configurable: true
95 });
96 Object.defineProperty(TabBar.prototype, "tabMoved", {
97 /**
98 * A signal emitted when a tab is moved by the user.
99 *
100 * #### Notes
101 * This signal is emitted when a tab is moved by user interaction.
102 *
103 * This signal is not emitted when a tab is moved programmatically.
104 */
105 get: function () {
106 return this._tabMoved;
107 },
108 enumerable: true,
109 configurable: true
110 });
111 Object.defineProperty(TabBar.prototype, "tabActivateRequested", {
112 /**
113 * A signal emitted when a tab is clicked by the user.
114 *
115 * #### Notes
116 * If the clicked tab is not the current tab, the clicked tab will be
117 * made current and the `currentChanged` signal will be emitted first.
118 *
119 * This signal is emitted even if the clicked tab is the current tab.
120 */
121 get: function () {
122 return this._tabActivateRequested;
123 },
124 enumerable: true,
125 configurable: true
126 });
127 Object.defineProperty(TabBar.prototype, "tabCloseRequested", {
128 /**
129 * A signal emitted when a tab close icon is clicked.
130 *
131 * #### Notes
132 * This signal is not emitted unless the tab title is `closable`.
133 */
134 get: function () {
135 return this._tabCloseRequested;
136 },
137 enumerable: true,
138 configurable: true
139 });
140 Object.defineProperty(TabBar.prototype, "tabDetachRequested", {
141 /**
142 * A signal emitted when a tab is dragged beyond the detach threshold.
143 *
144 * #### Notes
145 * This signal is emitted when the user drags a tab with the mouse,
146 * and mouse is dragged beyond the detach threshold.
147 *
148 * The consumer of the signal should call `releaseMouse` and remove
149 * the tab in order to complete the detach.
150 *
151 * This signal is only emitted once per drag cycle.
152 */
153 get: function () {
154 return this._tabDetachRequested;
155 },
156 enumerable: true,
157 configurable: true
158 });
159 Object.defineProperty(TabBar.prototype, "currentTitle", {
160 /**
161 * Get the currently selected title.
162 *
163 * #### Notes
164 * This will be `null` if no tab is selected.
165 */
166 get: function () {
167 return this._titles[this._currentIndex] || null;
168 },
169 /**
170 * Set the currently selected title.
171 *
172 * #### Notes
173 * If the title does not exist, the title will be set to `null`.
174 */
175 set: function (value) {
176 this.currentIndex = value ? this._titles.indexOf(value) : -1;
177 },
178 enumerable: true,
179 configurable: true
180 });
181 Object.defineProperty(TabBar.prototype, "currentIndex", {
182 /**
183 * Get the index of the currently selected tab.
184 *
185 * #### Notes
186 * This will be `-1` if no tab is selected.
187 */
188 get: function () {
189 return this._currentIndex;
190 },
191 /**
192 * Set the index of the currently selected tab.
193 *
194 * #### Notes
195 * If the value is out of range, the index will be set to `-1`.
196 */
197 set: function (value) {
198 // Adjust for an out of range index.
199 if (value < 0 || value >= this._titles.length) {
200 value = -1;
201 }
202 // Bail early if the index will not change.
203 if (this._currentIndex === value) {
204 return;
205 }
206 // Look up the previous index and title.
207 var pi = this._currentIndex;
208 var pt = this._titles[pi] || null;
209 // Look up the current index and title.
210 var ci = value;
211 var ct = this._titles[ci] || null;
212 // Update the current index and previous title.
213 this._currentIndex = ci;
214 this._previousTitle = pt;
215 // Schedule an update of the tabs.
216 this.update();
217 // Emit the current changed signal.
218 this._currentChanged.emit({
219 previousIndex: pi, previousTitle: pt,
220 currentIndex: ci, currentTitle: ct
221 });
222 },
223 enumerable: true,
224 configurable: true
225 });
226 Object.defineProperty(TabBar.prototype, "orientation", {
227 /**
228 * Get the orientation of the tab bar.
229 *
230 * #### Notes
231 * This controls whether the tabs are arranged in a row or column.
232 */
233 get: function () {
234 return this._orientation;
235 },
236 /**
237 * Set the orientation of the tab bar.
238 *
239 * #### Notes
240 * This controls whether the tabs are arranged in a row or column.
241 */
242 set: function (value) {
243 // Do nothing if the orientation does not change.
244 if (this._orientation === value) {
245 return;
246 }
247 // Release the mouse before making any changes.
248 this._releaseMouse();
249 // Toggle the orientation values.
250 this._orientation = value;
251 this.dataset['orientation'] = value;
252 },
253 enumerable: true,
254 configurable: true
255 });
256 Object.defineProperty(TabBar.prototype, "titles", {
257 /**
258 * A read-only array of the titles in the tab bar.
259 */
260 get: function () {
261 return this._titles;
262 },
263 enumerable: true,
264 configurable: true
265 });
266 Object.defineProperty(TabBar.prototype, "contentNode", {
267 /**
268 * The tab bar content node.
269 *
270 * #### Notes
271 * This is the node which holds the tab nodes.
272 *
273 * Modifying this node directly can lead to undefined behavior.
274 */
275 get: function () {
276 return this.node.getElementsByClassName('p-TabBar-content')[0];
277 },
278 enumerable: true,
279 configurable: true
280 });
281 /**
282 * Add a tab to the end of the tab bar.
283 *
284 * @param value - The title which holds the data for the tab,
285 * or an options object to convert to a title.
286 *
287 * @returns The title object added to the tab bar.
288 *
289 * #### Notes
290 * If the title is already added to the tab bar, it will be moved.
291 */
292 TabBar.prototype.addTab = function (value) {
293 return this.insertTab(this._titles.length, value);
294 };
295 /**
296 * Insert a tab into the tab bar at the specified index.
297 *
298 * @param index - The index at which to insert the tab.
299 *
300 * @param value - The title which holds the data for the tab,
301 * or an options object to convert to a title.
302 *
303 * @returns The title object added to the tab bar.
304 *
305 * #### Notes
306 * The index will be clamped to the bounds of the tabs.
307 *
308 * If the title is already added to the tab bar, it will be moved.
309 */
310 TabBar.prototype.insertTab = function (index, value) {
311 // Release the mouse before making any changes.
312 this._releaseMouse();
313 // Coerce the value to a title.
314 var title = Private.asTitle(value);
315 // Look up the index of the title.
316 var i = this._titles.indexOf(title);
317 // Clamp the insert index to the array bounds.
318 var j = Math.max(0, Math.min(index, this._titles.length));
319 // If the title is not in the array, insert it.
320 if (i === -1) {
321 // Insert the title into the array.
322 algorithm_1.ArrayExt.insert(this._titles, j, title);
323 // Connect to the title changed signal.
324 title.changed.connect(this._onTitleChanged, this);
325 // Schedule an update of the tabs.
326 this.update();
327 // Adjust the current index for the insert.
328 this._adjustCurrentForInsert(j, title);
329 // Return the title added to the tab bar.
330 return title;
331 }
332 // Otherwise, the title exists in the array and should be moved.
333 // Adjust the index if the location is at the end of the array.
334 if (j === this._titles.length) {
335 j--;
336 }
337 // Bail if there is no effective move.
338 if (i === j) {
339 return title;
340 }
341 // Move the title to the new location.
342 algorithm_1.ArrayExt.move(this._titles, i, j);
343 // Schedule an update of the tabs.
344 this.update();
345 // Adjust the current index for the move.
346 this._adjustCurrentForMove(i, j);
347 // Return the title added to the tab bar.
348 return title;
349 };
350 /**
351 * Remove a tab from the tab bar.
352 *
353 * @param title - The title for the tab to remove.
354 *
355 * #### Notes
356 * This is a no-op if the title is not in the tab bar.
357 */
358 TabBar.prototype.removeTab = function (title) {
359 this.removeTabAt(this._titles.indexOf(title));
360 };
361 /**
362 * Remove the tab at a given index from the tab bar.
363 *
364 * @param index - The index of the tab to remove.
365 *
366 * #### Notes
367 * This is a no-op if the index is out of range.
368 */
369 TabBar.prototype.removeTabAt = function (index) {
370 // Release the mouse before making any changes.
371 this._releaseMouse();
372 // Remove the title from the array.
373 var title = algorithm_1.ArrayExt.removeAt(this._titles, index);
374 // Bail if the index is out of range.
375 if (!title) {
376 return;
377 }
378 // Disconnect from the title changed signal.
379 title.changed.disconnect(this._onTitleChanged, this);
380 // Clear the previous title if it's being removed.
381 if (title === this._previousTitle) {
382 this._previousTitle = null;
383 }
384 // Schedule an update of the tabs.
385 this.update();
386 // Adjust the current index for the remove.
387 this._adjustCurrentForRemove(index, title);
388 };
389 /**
390 * Remove all tabs from the tab bar.
391 */
392 TabBar.prototype.clearTabs = function () {
393 // Bail if there is nothing to remove.
394 if (this._titles.length === 0) {
395 return;
396 }
397 // Release the mouse before making any changes.
398 this._releaseMouse();
399 // Disconnect from the title changed signals.
400 for (var _i = 0, _a = this._titles; _i < _a.length; _i++) {
401 var title = _a[_i];
402 title.changed.disconnect(this._onTitleChanged, this);
403 }
404 // Get the current index and title.
405 var pi = this.currentIndex;
406 var pt = this.currentTitle;
407 // Reset the current index and previous title.
408 this._currentIndex = -1;
409 this._previousTitle = null;
410 // Clear the title array.
411 this._titles.length = 0;
412 // Schedule an update of the tabs.
413 this.update();
414 // If no tab was selected, there's nothing else to do.
415 if (pi === -1) {
416 return;
417 }
418 // Emit the current changed signal.
419 this._currentChanged.emit({
420 previousIndex: pi, previousTitle: pt,
421 currentIndex: -1, currentTitle: null
422 });
423 };
424 /**
425 * Release the mouse and restore the non-dragged tab positions.
426 *
427 * #### Notes
428 * This will cause the tab bar to stop handling mouse events and to
429 * restore the tabs to their non-dragged positions.
430 */
431 TabBar.prototype.releaseMouse = function () {
432 this._releaseMouse();
433 };
434 /**
435 * Handle the DOM events for the tab bar.
436 *
437 * @param event - The DOM event sent to the tab bar.
438 *
439 * #### Notes
440 * This method implements the DOM `EventListener` interface and is
441 * called in response to events on the tab bar's DOM node.
442 *
443 * This should not be called directly by user code.
444 */
445 TabBar.prototype.handleEvent = function (event) {
446 switch (event.type) {
447 case 'mousedown':
448 this._evtMouseDown(event);
449 break;
450 case 'mousemove':
451 this._evtMouseMove(event);
452 break;
453 case 'mouseup':
454 this._evtMouseUp(event);
455 break;
456 case 'keydown':
457 this._evtKeyDown(event);
458 break;
459 case 'contextmenu':
460 event.preventDefault();
461 event.stopPropagation();
462 break;
463 }
464 };
465 /**
466 * A message handler invoked on a `'before-attach'` message.
467 */
468 TabBar.prototype.onBeforeAttach = function (msg) {
469 this.node.addEventListener('mousedown', this);
470 };
471 /**
472 * A message handler invoked on an `'after-detach'` message.
473 */
474 TabBar.prototype.onAfterDetach = function (msg) {
475 this.node.removeEventListener('mousedown', this);
476 this._releaseMouse();
477 };
478 /**
479 * A message handler invoked on an `'update-request'` message.
480 */
481 TabBar.prototype.onUpdateRequest = function (msg) {
482 var titles = this._titles;
483 var renderer = this.renderer;
484 var currentTitle = this.currentTitle;
485 var content = new Array(titles.length);
486 for (var i = 0, n = titles.length; i < n; ++i) {
487 var title = titles[i];
488 var current = title === currentTitle;
489 var zIndex = current ? n : n - i - 1;
490 content[i] = renderer.renderTab({ title: title, current: current, zIndex: zIndex });
491 }
492 virtualdom_1.VirtualDOM.render(content, this.contentNode);
493 };
494 /**
495 * Handle the `'keydown'` event for the tab bar.
496 */
497 TabBar.prototype._evtKeyDown = function (event) {
498 // Stop all input events during drag.
499 event.preventDefault();
500 event.stopPropagation();
501 // Release the mouse if `Escape` is pressed.
502 if (event.keyCode === 27) {
503 this._releaseMouse();
504 }
505 };
506 /**
507 * Handle the `'mousedown'` event for the tab bar.
508 */
509 TabBar.prototype._evtMouseDown = function (event) {
510 // Do nothing if it's not a left or middle mouse press.
511 if (event.button !== 0 && event.button !== 1) {
512 return;
513 }
514 // Do nothing if a drag is in progress.
515 if (this._dragData) {
516 return;
517 }
518 // Lookup the tab nodes.
519 var tabs = this.contentNode.children;
520 // Find the index of the pressed tab.
521 var index = algorithm_1.ArrayExt.findFirstIndex(tabs, function (tab) {
522 return domutils_1.ElementExt.hitTest(tab, event.clientX, event.clientY);
523 });
524 // Do nothing if the press is not on a tab.
525 if (index === -1) {
526 return;
527 }
528 // Pressing on a tab stops the event propagation.
529 event.preventDefault();
530 event.stopPropagation();
531 // Initialize the non-measured parts of the drag data.
532 this._dragData = {
533 tab: tabs[index],
534 index: index,
535 pressX: event.clientX,
536 pressY: event.clientY,
537 tabPos: -1,
538 tabSize: -1,
539 tabPressPos: -1,
540 targetIndex: -1,
541 tabLayout: null,
542 contentRect: null,
543 override: null,
544 dragActive: false,
545 dragAborted: false,
546 detachRequested: false
547 };
548 // Add the document mouse up listener.
549 document.addEventListener('mouseup', this, true);
550 // Do nothing else if the middle button is clicked.
551 if (event.button === 1) {
552 return;
553 }
554 // Do nothing else if the close icon is clicked.
555 var icon = tabs[index].querySelector(this.renderer.closeIconSelector);
556 if (icon && icon.contains(event.target)) {
557 return;
558 }
559 // Add the extra listeners if the tabs are movable.
560 if (this.tabsMovable) {
561 document.addEventListener('mousemove', this, true);
562 document.addEventListener('keydown', this, true);
563 document.addEventListener('contextmenu', this, true);
564 }
565 // Update the current index as appropriate.
566 if (this.allowDeselect && this.currentIndex === index) {
567 this.currentIndex = -1;
568 }
569 else {
570 this.currentIndex = index;
571 }
572 // Do nothing else if there is no current tab.
573 if (this.currentIndex === -1) {
574 return;
575 }
576 // Emit the tab activate request signal.
577 this._tabActivateRequested.emit({
578 index: this.currentIndex, title: this.currentTitle
579 });
580 };
581 /**
582 * Handle the `'mousemove'` event for the tab bar.
583 */
584 TabBar.prototype._evtMouseMove = function (event) {
585 // Do nothing if no drag is in progress.
586 var data = this._dragData;
587 if (!data) {
588 return;
589 }
590 // Suppress the event during a drag.
591 event.preventDefault();
592 event.stopPropagation();
593 // Lookup the tab nodes.
594 var tabs = this.contentNode.children;
595 // Bail early if the drag threshold has not been met.
596 if (!data.dragActive && !Private.dragExceeded(data, event)) {
597 return;
598 }
599 // Activate the drag if necessary.
600 if (!data.dragActive) {
601 // Fill in the rest of the drag data measurements.
602 var tabRect = data.tab.getBoundingClientRect();
603 if (this._orientation === 'horizontal') {
604 data.tabPos = data.tab.offsetLeft;
605 data.tabSize = tabRect.width;
606 data.tabPressPos = data.pressX - tabRect.left;
607 }
608 else {
609 data.tabPos = data.tab.offsetTop;
610 data.tabSize = tabRect.height;
611 data.tabPressPos = data.pressY - tabRect.top;
612 }
613 data.tabLayout = Private.snapTabLayout(tabs, this._orientation);
614 data.contentRect = this.contentNode.getBoundingClientRect();
615 data.override = dragdrop_1.Drag.overrideCursor('default');
616 // Add the dragging style classes.
617 data.tab.classList.add('p-mod-dragging');
618 this.addClass('p-mod-dragging');
619 // Mark the drag as active.
620 data.dragActive = true;
621 }
622 // Emit the detach requested signal if the threshold is exceeded.
623 if (!data.detachRequested && Private.detachExceeded(data, event)) {
624 // Only emit the signal once per drag cycle.
625 data.detachRequested = true;
626 // Setup the arguments for the signal.
627 var index = data.index;
628 var clientX = event.clientX;
629 var clientY = event.clientY;
630 var tab = tabs[index];
631 var title = this._titles[index];
632 // Emit the tab detach requested signal.
633 this._tabDetachRequested.emit({ index: index, title: title, tab: tab, clientX: clientX, clientY: clientY });
634 // Bail if the signal handler aborted the drag.
635 if (data.dragAborted) {
636 return;
637 }
638 }
639 // Update the positions of the tabs.
640 Private.layoutTabs(tabs, data, event, this._orientation);
641 };
642 /**
643 * Handle the `'mouseup'` event for the document.
644 */
645 TabBar.prototype._evtMouseUp = function (event) {
646 var _this = this;
647 // Do nothing if it's not a left or middle mouse release.
648 if (event.button !== 0 && event.button !== 1) {
649 return;
650 }
651 // Do nothing if no drag is in progress.
652 var data = this._dragData;
653 if (!data) {
654 return;
655 }
656 // Stop the event propagation.
657 event.preventDefault();
658 event.stopPropagation();
659 // Remove the extra mouse event listeners.
660 document.removeEventListener('mousemove', this, true);
661 document.removeEventListener('mouseup', this, true);
662 document.removeEventListener('keydown', this, true);
663 document.removeEventListener('contextmenu', this, true);
664 // Handle a release when the drag is not active.
665 if (!data.dragActive) {
666 // Clear the drag data.
667 this._dragData = null;
668 // Lookup the tab nodes.
669 var tabs = this.contentNode.children;
670 // Find the index of the released tab.
671 var index = algorithm_1.ArrayExt.findFirstIndex(tabs, function (tab) {
672 return domutils_1.ElementExt.hitTest(tab, event.clientX, event.clientY);
673 });
674 // Do nothing if the release is not on the original pressed tab.
675 if (index !== data.index) {
676 return;
677 }
678 // Ignore the release if the title is not closable.
679 var title = this._titles[index];
680 if (!title.closable) {
681 return;
682 }
683 // Emit the close requested signal if the middle button is released.
684 if (event.button === 1) {
685 this._tabCloseRequested.emit({ index: index, title: title });
686 return;
687 }
688 // Emit the close requested signal if the close icon was released.
689 var icon = tabs[index].querySelector(this.renderer.closeIconSelector);
690 if (icon && icon.contains(event.target)) {
691 this._tabCloseRequested.emit({ index: index, title: title });
692 return;
693 }
694 // Otherwise, there is nothing left to do.
695 return;
696 }
697 // Do nothing if the left button is not released.
698 if (event.button !== 0) {
699 return;
700 }
701 // Position the tab at its final resting position.
702 Private.finalizeTabPosition(data, this._orientation);
703 // Remove the dragging class from the tab so it can be transitioned.
704 data.tab.classList.remove('p-mod-dragging');
705 // Parse the transition duration for releasing the tab.
706 var duration = Private.parseTransitionDuration(data.tab);
707 // Complete the release on a timer to allow the tab to transition.
708 setTimeout(function () {
709 // Do nothing if the drag has been aborted.
710 if (data.dragAborted) {
711 return;
712 }
713 // Clear the drag data reference.
714 _this._dragData = null;
715 // Reset the positions of the tabs.
716 Private.resetTabPositions(_this.contentNode.children, _this._orientation);
717 // Clear the cursor grab.
718 data.override.dispose();
719 // Remove the remaining dragging style.
720 _this.removeClass('p-mod-dragging');
721 // If the tab was not moved, there is nothing else to do.
722 var i = data.index;
723 var j = data.targetIndex;
724 if (j === -1 || i === j) {
725 return;
726 }
727 // Move the title to the new locations.
728 algorithm_1.ArrayExt.move(_this._titles, i, j);
729 // Adjust the current index for the move.
730 _this._adjustCurrentForMove(i, j);
731 // Emit the tab moved signal.
732 _this._tabMoved.emit({
733 fromIndex: i, toIndex: j, title: _this._titles[j]
734 });
735 // Update the tabs immediately to prevent flicker.
736 messaging_1.MessageLoop.sendMessage(_this, widget_1.Widget.Msg.UpdateRequest);
737 }, duration);
738 };
739 /**
740 * Release the mouse and restore the non-dragged tab positions.
741 */
742 TabBar.prototype._releaseMouse = function () {
743 // Do nothing if no drag is in progress.
744 var data = this._dragData;
745 if (!data) {
746 return;
747 }
748 // Clear the drag data reference.
749 this._dragData = null;
750 // Remove the extra mouse listeners.
751 document.removeEventListener('mousemove', this, true);
752 document.removeEventListener('mouseup', this, true);
753 document.removeEventListener('keydown', this, true);
754 document.removeEventListener('contextmenu', this, true);
755 // Indicate the drag has been aborted. This allows the mouse
756 // event handlers to return early when the drag is canceled.
757 data.dragAborted = true;
758 // If the drag is not active, there's nothing more to do.
759 if (!data.dragActive) {
760 return;
761 }
762 // Reset the tabs to their non-dragged positions.
763 Private.resetTabPositions(this.contentNode.children, this._orientation);
764 // Clear the cursor override.
765 data.override.dispose();
766 // Clear the dragging style classes.
767 data.tab.classList.remove('p-mod-dragging');
768 this.removeClass('p-mod-dragging');
769 };
770 /**
771 * Adjust the current index for a tab insert operation.
772 *
773 * This method accounts for the tab bar's insertion behavior when
774 * adjusting the current index and emitting the changed signal.
775 */
776 TabBar.prototype._adjustCurrentForInsert = function (i, title) {
777 // Lookup commonly used variables.
778 var ct = this.currentTitle;
779 var ci = this._currentIndex;
780 var bh = this.insertBehavior;
781 // Handle the behavior where the new tab is always selected,
782 // or the behavior where the new tab is selected if needed.
783 if (bh === 'select-tab' || (bh === 'select-tab-if-needed' && ci === -1)) {
784 this._currentIndex = i;
785 this._previousTitle = ct;
786 this._currentChanged.emit({
787 previousIndex: ci, previousTitle: ct,
788 currentIndex: i, currentTitle: title
789 });
790 return;
791 }
792 // Otherwise, silently adjust the current index if needed.
793 if (ci >= i) {
794 this._currentIndex++;
795 }
796 };
797 /**
798 * Adjust the current index for a tab move operation.
799 *
800 * This method will not cause the actual current tab to change.
801 * It silently adjusts the index to account for the given move.
802 */
803 TabBar.prototype._adjustCurrentForMove = function (i, j) {
804 if (this._currentIndex === i) {
805 this._currentIndex = j;
806 }
807 else if (this._currentIndex < i && this._currentIndex >= j) {
808 this._currentIndex++;
809 }
810 else if (this._currentIndex > i && this._currentIndex <= j) {
811 this._currentIndex--;
812 }
813 };
814 /**
815 * Adjust the current index for a tab remove operation.
816 *
817 * This method accounts for the tab bar's remove behavior when
818 * adjusting the current index and emitting the changed signal.
819 */
820 TabBar.prototype._adjustCurrentForRemove = function (i, title) {
821 // Lookup commonly used variables.
822 var ci = this._currentIndex;
823 var bh = this.removeBehavior;
824 // Silently adjust the index if the current tab is not removed.
825 if (ci !== i) {
826 if (ci > i) {
827 this._currentIndex--;
828 }
829 return;
830 }
831 // No tab gets selected if the tab bar is empty.
832 if (this._titles.length === 0) {
833 this._currentIndex = -1;
834 this._currentChanged.emit({
835 previousIndex: i, previousTitle: title,
836 currentIndex: -1, currentTitle: null
837 });
838 return;
839 }
840 // Handle behavior where the next sibling tab is selected.
841 if (bh === 'select-tab-after') {
842 this._currentIndex = Math.min(i, this._titles.length - 1);
843 this._currentChanged.emit({
844 previousIndex: i, previousTitle: title,
845 currentIndex: this._currentIndex, currentTitle: this.currentTitle
846 });
847 return;
848 }
849 // Handle behavior where the previous sibling tab is selected.
850 if (bh === 'select-tab-before') {
851 this._currentIndex = Math.max(0, i - 1);
852 this._currentChanged.emit({
853 previousIndex: i, previousTitle: title,
854 currentIndex: this._currentIndex, currentTitle: this.currentTitle
855 });
856 return;
857 }
858 // Handle behavior where the previous history tab is selected.
859 if (bh === 'select-previous-tab') {
860 if (this._previousTitle) {
861 this._currentIndex = this._titles.indexOf(this._previousTitle);
862 this._previousTitle = null;
863 }
864 else {
865 this._currentIndex = Math.min(i, this._titles.length - 1);
866 }
867 this._currentChanged.emit({
868 previousIndex: i, previousTitle: title,
869 currentIndex: this._currentIndex, currentTitle: this.currentTitle
870 });
871 return;
872 }
873 // Otherwise, no tab gets selected.
874 this._currentIndex = -1;
875 this._currentChanged.emit({
876 previousIndex: i, previousTitle: title,
877 currentIndex: -1, currentTitle: null
878 });
879 };
880 /**
881 * Handle the `changed` signal of a title object.
882 */
883 TabBar.prototype._onTitleChanged = function (sender) {
884 this.update();
885 };
886 return TabBar;
887}(widget_1.Widget));
888exports.TabBar = TabBar;
889/**
890 * The namespace for the `TabBar` class statics.
891 */
892(function (TabBar) {
893 /**
894 * The default implementation of `IRenderer`.
895 *
896 * #### Notes
897 * Subclasses are free to reimplement rendering methods as needed.
898 */
899 var Renderer = /** @class */ (function () {
900 /**
901 * Construct a new renderer.
902 */
903 function Renderer() {
904 /**
905 * A selector which matches the close icon node in a tab.
906 */
907 this.closeIconSelector = '.p-TabBar-tabCloseIcon';
908 this._tabID = 0;
909 this._tabKeys = new WeakMap();
910 }
911 /**
912 * Render the virtual element for a tab.
913 *
914 * @param data - The data to use for rendering the tab.
915 *
916 * @returns A virtual element representing the tab.
917 */
918 Renderer.prototype.renderTab = function (data) {
919 var title = data.title.caption;
920 var key = this.createTabKey(data);
921 var style = this.createTabStyle(data);
922 var className = this.createTabClass(data);
923 var dataset = this.createTabDataset(data);
924 return (virtualdom_1.h.li({ key: key, className: className, title: title, style: style, dataset: dataset }, this.renderIcon(data), this.renderLabel(data), this.renderCloseIcon(data)));
925 };
926 /**
927 * Render the icon element for a tab.
928 *
929 * @param data - The data to use for rendering the tab.
930 *
931 * @returns A virtual element representing the tab icon.
932 */
933 Renderer.prototype.renderIcon = function (data) {
934 var className = this.createIconClass(data);
935 return virtualdom_1.h.div({ className: className }, data.title.iconLabel);
936 };
937 /**
938 * Render the label element for a tab.
939 *
940 * @param data - The data to use for rendering the tab.
941 *
942 * @returns A virtual element representing the tab label.
943 */
944 Renderer.prototype.renderLabel = function (data) {
945 return virtualdom_1.h.div({ className: 'p-TabBar-tabLabel' }, data.title.label);
946 };
947 /**
948 * Render the close icon element for a tab.
949 *
950 * @param data - The data to use for rendering the tab.
951 *
952 * @returns A virtual element representing the tab close icon.
953 */
954 Renderer.prototype.renderCloseIcon = function (data) {
955 return virtualdom_1.h.div({ className: 'p-TabBar-tabCloseIcon' });
956 };
957 /**
958 * Create a unique render key for the tab.
959 *
960 * @param data - The data to use for the tab.
961 *
962 * @returns The unique render key for the tab.
963 *
964 * #### Notes
965 * This method caches the key against the tab title the first time
966 * the key is generated. This enables efficient rendering of moved
967 * tabs and avoids subtle hover style artifacts.
968 */
969 Renderer.prototype.createTabKey = function (data) {
970 var key = this._tabKeys.get(data.title);
971 if (key === undefined) {
972 key = "tab-key-" + this._tabID++;
973 this._tabKeys.set(data.title, key);
974 }
975 return key;
976 };
977 /**
978 * Create the inline style object for a tab.
979 *
980 * @param data - The data to use for the tab.
981 *
982 * @returns The inline style data for the tab.
983 */
984 Renderer.prototype.createTabStyle = function (data) {
985 return { zIndex: "" + data.zIndex };
986 };
987 /**
988 * Create the class name for the tab.
989 *
990 * @param data - The data to use for the tab.
991 *
992 * @returns The full class name for the tab.
993 */
994 Renderer.prototype.createTabClass = function (data) {
995 var name = 'p-TabBar-tab';
996 if (data.title.className) {
997 name += " " + data.title.className;
998 }
999 if (data.title.closable) {
1000 name += ' p-mod-closable';
1001 }
1002 if (data.current) {
1003 name += ' p-mod-current';
1004 }
1005 return name;
1006 };
1007 /**
1008 * Create the dataset for a tab.
1009 *
1010 * @param data - The data to use for the tab.
1011 *
1012 * @returns The dataset for the tab.
1013 */
1014 Renderer.prototype.createTabDataset = function (data) {
1015 return data.title.dataset;
1016 };
1017 /**
1018 * Create the class name for the tab icon.
1019 *
1020 * @param data - The data to use for the tab.
1021 *
1022 * @returns The full class name for the tab icon.
1023 */
1024 Renderer.prototype.createIconClass = function (data) {
1025 var name = 'p-TabBar-tabIcon';
1026 var extra = data.title.iconClass;
1027 return extra ? name + " " + extra : name;
1028 };
1029 return Renderer;
1030 }());
1031 TabBar.Renderer = Renderer;
1032 /**
1033 * The default `Renderer` instance.
1034 */
1035 TabBar.defaultRenderer = new Renderer();
1036})(TabBar = exports.TabBar || (exports.TabBar = {}));
1037exports.TabBar = TabBar;
1038/**
1039 * The namespace for the module implementation details.
1040 */
1041var Private;
1042(function (Private) {
1043 /**
1044 * The start drag distance threshold.
1045 */
1046 Private.DRAG_THRESHOLD = 5;
1047 /**
1048 * The detach distance threshold.
1049 */
1050 Private.DETACH_THRESHOLD = 20;
1051 /**
1052 * Create the DOM node for a tab bar.
1053 */
1054 function createNode() {
1055 var node = document.createElement('div');
1056 var content = document.createElement('ul');
1057 content.className = 'p-TabBar-content';
1058 node.appendChild(content);
1059 return node;
1060 }
1061 Private.createNode = createNode;
1062 /**
1063 * Coerce a title or options into a real title.
1064 */
1065 function asTitle(value) {
1066 return value instanceof title_1.Title ? value : new title_1.Title(value);
1067 }
1068 Private.asTitle = asTitle;
1069 /**
1070 * Parse the transition duration for a tab node.
1071 */
1072 function parseTransitionDuration(tab) {
1073 var style = window.getComputedStyle(tab);
1074 return 1000 * (parseFloat(style.transitionDuration) || 0);
1075 }
1076 Private.parseTransitionDuration = parseTransitionDuration;
1077 /**
1078 * Get a snapshot of the current tab layout values.
1079 */
1080 function snapTabLayout(tabs, orientation) {
1081 var layout = new Array(tabs.length);
1082 for (var i = 0, n = tabs.length; i < n; ++i) {
1083 var node = tabs[i];
1084 var style = window.getComputedStyle(node);
1085 if (orientation === 'horizontal') {
1086 layout[i] = {
1087 pos: node.offsetLeft,
1088 size: node.offsetWidth,
1089 margin: parseFloat(style.marginLeft) || 0
1090 };
1091 }
1092 else {
1093 layout[i] = {
1094 pos: node.offsetTop,
1095 size: node.offsetHeight,
1096 margin: parseFloat(style.marginTop) || 0
1097 };
1098 }
1099 }
1100 return layout;
1101 }
1102 Private.snapTabLayout = snapTabLayout;
1103 /**
1104 * Test if the event exceeds the drag threshold.
1105 */
1106 function dragExceeded(data, event) {
1107 var dx = Math.abs(event.clientX - data.pressX);
1108 var dy = Math.abs(event.clientY - data.pressY);
1109 return dx >= Private.DRAG_THRESHOLD || dy >= Private.DRAG_THRESHOLD;
1110 }
1111 Private.dragExceeded = dragExceeded;
1112 /**
1113 * Test if the event exceeds the drag detach threshold.
1114 */
1115 function detachExceeded(data, event) {
1116 var rect = data.contentRect;
1117 return ((event.clientX < rect.left - Private.DETACH_THRESHOLD) ||
1118 (event.clientX >= rect.right + Private.DETACH_THRESHOLD) ||
1119 (event.clientY < rect.top - Private.DETACH_THRESHOLD) ||
1120 (event.clientY >= rect.bottom + Private.DETACH_THRESHOLD));
1121 }
1122 Private.detachExceeded = detachExceeded;
1123 /**
1124 * Update the relative tab positions and computed target index.
1125 */
1126 function layoutTabs(tabs, data, event, orientation) {
1127 // Compute the orientation-sensitive values.
1128 var pressPos;
1129 var localPos;
1130 var clientPos;
1131 var clientSize;
1132 if (orientation === 'horizontal') {
1133 pressPos = data.pressX;
1134 localPos = event.clientX - data.contentRect.left;
1135 clientPos = event.clientX;
1136 clientSize = data.contentRect.width;
1137 }
1138 else {
1139 pressPos = data.pressY;
1140 localPos = event.clientY - data.contentRect.top;
1141 clientPos = event.clientY;
1142 clientSize = data.contentRect.height;
1143 }
1144 // Compute the target data.
1145 var targetIndex = data.index;
1146 var targetPos = localPos - data.tabPressPos;
1147 var targetEnd = targetPos + data.tabSize;
1148 // Update the relative tab positions.
1149 for (var i = 0, n = tabs.length; i < n; ++i) {
1150 var pxPos = void 0;
1151 var layout = data.tabLayout[i];
1152 var threshold = layout.pos + (layout.size >> 1);
1153 if (i < data.index && targetPos < threshold) {
1154 pxPos = data.tabSize + data.tabLayout[i + 1].margin + "px";
1155 targetIndex = Math.min(targetIndex, i);
1156 }
1157 else if (i > data.index && targetEnd > threshold) {
1158 pxPos = -data.tabSize - layout.margin + "px";
1159 targetIndex = Math.max(targetIndex, i);
1160 }
1161 else if (i === data.index) {
1162 var ideal = clientPos - pressPos;
1163 var limit = clientSize - (data.tabPos + data.tabSize);
1164 pxPos = Math.max(-data.tabPos, Math.min(ideal, limit)) + "px";
1165 }
1166 else {
1167 pxPos = '';
1168 }
1169 if (orientation === 'horizontal') {
1170 tabs[i].style.left = pxPos;
1171 }
1172 else {
1173 tabs[i].style.top = pxPos;
1174 }
1175 }
1176 // Update the computed target index.
1177 data.targetIndex = targetIndex;
1178 }
1179 Private.layoutTabs = layoutTabs;
1180 /**
1181 * Position the drag tab at its final resting relative position.
1182 */
1183 function finalizeTabPosition(data, orientation) {
1184 // Compute the orientation-sensitive client size.
1185 var clientSize;
1186 if (orientation === 'horizontal') {
1187 clientSize = data.contentRect.width;
1188 }
1189 else {
1190 clientSize = data.contentRect.height;
1191 }
1192 // Compute the ideal final tab position.
1193 var ideal;
1194 if (data.targetIndex === data.index) {
1195 ideal = 0;
1196 }
1197 else if (data.targetIndex > data.index) {
1198 var tgt = data.tabLayout[data.targetIndex];
1199 ideal = tgt.pos + tgt.size - data.tabSize - data.tabPos;
1200 }
1201 else {
1202 var tgt = data.tabLayout[data.targetIndex];
1203 ideal = tgt.pos - data.tabPos;
1204 }
1205 // Compute the tab position limit.
1206 var limit = clientSize - (data.tabPos + data.tabSize);
1207 var final = Math.max(-data.tabPos, Math.min(ideal, limit));
1208 // Set the final orientation-sensitive position.
1209 if (orientation === 'horizontal') {
1210 data.tab.style.left = final + "px";
1211 }
1212 else {
1213 data.tab.style.top = final + "px";
1214 }
1215 }
1216 Private.finalizeTabPosition = finalizeTabPosition;
1217 /**
1218 * Reset the relative positions of the given tabs.
1219 */
1220 function resetTabPositions(tabs, orientation) {
1221 algorithm_1.each(tabs, function (tab) {
1222 if (orientation === 'horizontal') {
1223 tab.style.left = '';
1224 }
1225 else {
1226 tab.style.top = '';
1227 }
1228 });
1229 }
1230 Private.resetTabPositions = resetTabPositions;
1231})(Private || (Private = {}));