@phosphor/widgets
Version:
PhosphorJS - Widgets
677 lines (676 loc) • 24.4 kB
JavaScript
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
}
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var domutils_1 = require("@phosphor/domutils");
var dragdrop_1 = require("@phosphor/dragdrop");
var signaling_1 = require("@phosphor/signaling");
var widget_1 = require("./widget");
/**
* A widget which implements a canonical scroll bar.
*/
var ScrollBar = /** @class */ (function (_super) {
__extends(ScrollBar, _super);
/**
* Construct a new scroll bar.
*
* @param options - The options for initializing the scroll bar.
*/
function ScrollBar(options) {
if (options === void 0) { options = {}; }
var _this = _super.call(this, { node: Private.createNode() }) || this;
/**
* A timeout callback for repeating the mouse press.
*/
_this._onRepeat = function () {
// Clear the repeat timer id.
_this._repeatTimer = -1;
// Bail if the mouse has been released.
if (!_this._pressData) {
return;
}
// Look up the part that was pressed.
var part = _this._pressData.part;
// Bail if the thumb was pressed.
if (part === 'thumb') {
return;
}
// Schedule the timer for another repeat.
_this._repeatTimer = window.setTimeout(_this._onRepeat, 20);
// Get the current mouse position.
var mouseX = _this._pressData.mouseX;
var mouseY = _this._pressData.mouseY;
// Handle a decrement button repeat.
if (part === 'decrement') {
// Bail if the mouse is not over the button.
if (!domutils_1.ElementExt.hitTest(_this.decrementNode, mouseX, mouseY)) {
return;
}
// Emit the step requested signal.
_this._stepRequested.emit('decrement');
// Finished.
return;
}
// Handle an increment button repeat.
if (part === 'increment') {
// Bail if the mouse is not over the button.
if (!domutils_1.ElementExt.hitTest(_this.incrementNode, mouseX, mouseY)) {
return;
}
// Emit the step requested signal.
_this._stepRequested.emit('increment');
// Finished.
return;
}
// Handle a track repeat.
if (part === 'track') {
// Bail if the mouse is not over the track.
if (!domutils_1.ElementExt.hitTest(_this.trackNode, mouseX, mouseY)) {
return;
}
// Fetch the thumb node.
var thumbNode = _this.thumbNode;
// Bail if the mouse is over the thumb.
if (domutils_1.ElementExt.hitTest(thumbNode, mouseX, mouseY)) {
return;
}
// Fetch the client rect for the thumb.
var thumbRect = thumbNode.getBoundingClientRect();
// Determine the direction for the page request.
var dir = void 0;
if (_this._orientation === 'horizontal') {
dir = mouseX < thumbRect.left ? 'decrement' : 'increment';
}
else {
dir = mouseY < thumbRect.top ? 'decrement' : 'increment';
}
// Emit the page requested signal.
_this._pageRequested.emit(dir);
// Finished.
return;
}
};
_this._value = 0;
_this._page = 10;
_this._maximum = 100;
_this._repeatTimer = -1;
_this._pressData = null;
_this._thumbMoved = new signaling_1.Signal(_this);
_this._stepRequested = new signaling_1.Signal(_this);
_this._pageRequested = new signaling_1.Signal(_this);
_this.addClass('p-ScrollBar');
_this.setFlag(widget_1.Widget.Flag.DisallowLayout);
// Set the orientation.
_this._orientation = options.orientation || 'vertical';
_this.dataset['orientation'] = _this._orientation;
// Parse the rest of the options.
if (options.maximum !== undefined) {
_this._maximum = Math.max(0, options.maximum);
}
if (options.page !== undefined) {
_this._page = Math.max(0, options.page);
}
if (options.value !== undefined) {
_this._value = Math.max(0, Math.min(options.value, _this._maximum));
}
return _this;
}
Object.defineProperty(ScrollBar.prototype, "thumbMoved", {
/**
* A signal emitted when the user moves the scroll thumb.
*
* #### Notes
* The payload is the current value of the scroll bar.
*/
get: function () {
return this._thumbMoved;
},
enumerable: true,
configurable: true
});
Object.defineProperty(ScrollBar.prototype, "stepRequested", {
/**
* A signal emitted when the user clicks a step button.
*
* #### Notes
* The payload is whether a decrease or increase is requested.
*/
get: function () {
return this._stepRequested;
},
enumerable: true,
configurable: true
});
Object.defineProperty(ScrollBar.prototype, "pageRequested", {
/**
* A signal emitted when the user clicks the scroll track.
*
* #### Notes
* The payload is whether a decrease or increase is requested.
*/
get: function () {
return this._pageRequested;
},
enumerable: true,
configurable: true
});
Object.defineProperty(ScrollBar.prototype, "orientation", {
/**
* Get the orientation of the scroll bar.
*/
get: function () {
return this._orientation;
},
/**
* Set the orientation of the scroll bar.
*/
set: function (value) {
// Do nothing if the orientation does not change.
if (this._orientation === value) {
return;
}
// Release the mouse before making changes.
this._releaseMouse();
// Update the internal orientation.
this._orientation = value;
this.dataset['orientation'] = value;
// Schedule an update the scroll bar.
this.update();
},
enumerable: true,
configurable: true
});
Object.defineProperty(ScrollBar.prototype, "value", {
/**
* Get the current value of the scroll bar.
*/
get: function () {
return this._value;
},
/**
* Set the current value of the scroll bar.
*
* #### Notes
* The value will be clamped to the range `[0, maximum]`.
*/
set: function (value) {
// Clamp the value to the allowable range.
value = Math.max(0, Math.min(value, this._maximum));
// Do nothing if the value does not change.
if (this._value === value) {
return;
}
// Update the internal value.
this._value = value;
// Schedule an update the scroll bar.
this.update();
},
enumerable: true,
configurable: true
});
Object.defineProperty(ScrollBar.prototype, "page", {
/**
* Get the page size of the scroll bar.
*
* #### Notes
* The page size is the amount of visible content in the scrolled
* region, expressed in data units. It determines the size of the
* scroll bar thumb.
*/
get: function () {
return this._page;
},
/**
* Set the page size of the scroll bar.
*
* #### Notes
* The page size will be clamped to the range `[0, Infinity]`.
*/
set: function (value) {
// Clamp the page size to the allowable range.
value = Math.max(0, value);
// Do nothing if the value does not change.
if (this._page === value) {
return;
}
// Update the internal page size.
this._page = value;
// Schedule an update the scroll bar.
this.update();
},
enumerable: true,
configurable: true
});
Object.defineProperty(ScrollBar.prototype, "maximum", {
/**
* Get the maximum value of the scroll bar.
*/
get: function () {
return this._maximum;
},
/**
* Set the maximum value of the scroll bar.
*
* #### Notes
* The max size will be clamped to the range `[0, Infinity]`.
*/
set: function (value) {
// Clamp the value to the allowable range.
value = Math.max(0, value);
// Do nothing if the value does not change.
if (this._maximum === value) {
return;
}
// Update the internal values.
this._maximum = value;
// Clamp the current value to the new range.
this._value = Math.min(this._value, value);
// Schedule an update the scroll bar.
this.update();
},
enumerable: true,
configurable: true
});
Object.defineProperty(ScrollBar.prototype, "decrementNode", {
/**
* The scroll bar decrement button node.
*
* #### Notes
* Modifying this node directly can lead to undefined behavior.
*/
get: function () {
return this.node.getElementsByClassName('p-ScrollBar-button')[0];
},
enumerable: true,
configurable: true
});
Object.defineProperty(ScrollBar.prototype, "incrementNode", {
/**
* The scroll bar increment button node.
*
* #### Notes
* Modifying this node directly can lead to undefined behavior.
*/
get: function () {
return this.node.getElementsByClassName('p-ScrollBar-button')[1];
},
enumerable: true,
configurable: true
});
Object.defineProperty(ScrollBar.prototype, "trackNode", {
/**
* The scroll bar track node.
*
* #### Notes
* Modifying this node directly can lead to undefined behavior.
*/
get: function () {
return this.node.getElementsByClassName('p-ScrollBar-track')[0];
},
enumerable: true,
configurable: true
});
Object.defineProperty(ScrollBar.prototype, "thumbNode", {
/**
* The scroll bar thumb node.
*
* #### Notes
* Modifying this node directly can lead to undefined behavior.
*/
get: function () {
return this.node.getElementsByClassName('p-ScrollBar-thumb')[0];
},
enumerable: true,
configurable: true
});
/**
* Handle the DOM events for the scroll bar.
*
* @param event - The DOM event sent to the scroll bar.
*
* #### Notes
* This method implements the DOM `EventListener` interface and is
* called in response to events on the scroll bar's DOM node.
*
* This should not be called directly by user code.
*/
ScrollBar.prototype.handleEvent = function (event) {
switch (event.type) {
case 'mousedown':
this._evtMouseDown(event);
break;
case 'mousemove':
this._evtMouseMove(event);
break;
case 'mouseup':
this._evtMouseUp(event);
break;
case 'keydown':
this._evtKeyDown(event);
break;
case 'contextmenu':
event.preventDefault();
event.stopPropagation();
break;
}
};
/**
* A method invoked on a 'before-attach' message.
*/
ScrollBar.prototype.onBeforeAttach = function (msg) {
this.node.addEventListener('mousedown', this);
this.update();
};
/**
* A method invoked on an 'after-detach' message.
*/
ScrollBar.prototype.onAfterDetach = function (msg) {
this.node.removeEventListener('mousedown', this);
this._releaseMouse();
};
/**
* A method invoked on an 'update-request' message.
*/
ScrollBar.prototype.onUpdateRequest = function (msg) {
// Convert the value and page into percentages.
var value = this._value * 100 / this._maximum;
var page = this._page * 100 / (this._page + this._maximum);
// Clamp the value and page to the relevant range.
value = Math.max(0, Math.min(value, 100));
page = Math.max(0, Math.min(page, 100));
// Fetch the thumb style.
var thumbStyle = this.thumbNode.style;
// Update the thumb style for the current orientation.
if (this._orientation === 'horizontal') {
thumbStyle.top = '';
thumbStyle.height = '';
thumbStyle.left = value + "%";
thumbStyle.width = page + "%";
thumbStyle.transform = "translate(" + -value + "%, 0%)";
}
else {
thumbStyle.left = '';
thumbStyle.width = '';
thumbStyle.top = value + "%";
thumbStyle.height = page + "%";
thumbStyle.transform = "translate(0%, " + -value + "%)";
}
};
/**
* Handle the `'keydown'` event for the scroll bar.
*/
ScrollBar.prototype._evtKeyDown = function (event) {
// Stop all input events during drag.
event.preventDefault();
event.stopPropagation();
// Ignore anything except the `Escape` key.
if (event.keyCode !== 27) {
return;
}
// Fetch the previous scroll value.
var value = this._pressData ? this._pressData.value : -1;
// Release the mouse.
this._releaseMouse();
// Restore the old scroll value if possible.
if (value !== -1) {
this._moveThumb(value);
}
};
/**
* Handle the `'mousedown'` event for the scroll bar.
*/
ScrollBar.prototype._evtMouseDown = function (event) {
// Do nothing if it's not a left mouse press.
if (event.button !== 0) {
return;
}
// Send an activate request to the scroll bar. This can be
// used by message hooks to activate something relevant.
this.activate();
// Do nothing if the mouse is already captured.
if (this._pressData) {
return;
}
// Find the pressed scroll bar part.
var part = Private.findPart(this, event.target);
// Do nothing if the part is not of interest.
if (!part) {
return;
}
// Stop the event propagation.
event.preventDefault();
event.stopPropagation();
// Override the mouse cursor.
var override = dragdrop_1.Drag.overrideCursor('default');
// Set up the press data.
this._pressData = {
part: part, override: override,
delta: -1, value: -1,
mouseX: event.clientX,
mouseY: event.clientY
};
// Add the extra event listeners.
document.addEventListener('mousemove', this, true);
document.addEventListener('mouseup', this, true);
document.addEventListener('keydown', this, true);
document.addEventListener('contextmenu', this, true);
// Handle a thumb press.
if (part === 'thumb') {
// Fetch the thumb node.
var thumbNode = this.thumbNode;
// Fetch the client rect for the thumb.
var thumbRect = thumbNode.getBoundingClientRect();
// Update the press data delta for the current orientation.
if (this._orientation === 'horizontal') {
this._pressData.delta = event.clientX - thumbRect.left;
}
else {
this._pressData.delta = event.clientY - thumbRect.top;
}
// Add the active class to the thumb node.
thumbNode.classList.add('p-mod-active');
// Store the current value in the press data.
this._pressData.value = this._value;
// Finished.
return;
}
// Handle a track press.
if (part === 'track') {
// Fetch the client rect for the thumb.
var thumbRect = this.thumbNode.getBoundingClientRect();
// Determine the direction for the page request.
var dir = void 0;
if (this._orientation === 'horizontal') {
dir = event.clientX < thumbRect.left ? 'decrement' : 'increment';
}
else {
dir = event.clientY < thumbRect.top ? 'decrement' : 'increment';
}
// Start the repeat timer.
this._repeatTimer = window.setTimeout(this._onRepeat, 350);
// Emit the page requested signal.
this._pageRequested.emit(dir);
// Finished.
return;
}
// Handle a decrement button press.
if (part === 'decrement') {
// Add the active class to the decrement node.
this.decrementNode.classList.add('p-mod-active');
// Start the repeat timer.
this._repeatTimer = window.setTimeout(this._onRepeat, 350);
// Emit the step requested signal.
this._stepRequested.emit('decrement');
// Finished.
return;
}
// Handle an increment button press.
if (part === 'increment') {
// Add the active class to the increment node.
this.incrementNode.classList.add('p-mod-active');
// Start the repeat timer.
this._repeatTimer = window.setTimeout(this._onRepeat, 350);
// Emit the step requested signal.
this._stepRequested.emit('increment');
// Finished.
return;
}
};
/**
* Handle the `'mousemove'` event for the scroll bar.
*/
ScrollBar.prototype._evtMouseMove = function (event) {
// Do nothing if no drag is in progress.
if (!this._pressData) {
return;
}
// Stop the event propagation.
event.preventDefault();
event.stopPropagation();
// Update the mouse position.
this._pressData.mouseX = event.clientX;
this._pressData.mouseY = event.clientY;
// Bail if the thumb is not being dragged.
if (this._pressData.part !== 'thumb') {
return;
}
// Get the client rect for the thumb and track.
var thumbRect = this.thumbNode.getBoundingClientRect();
var trackRect = this.trackNode.getBoundingClientRect();
// Fetch the scroll geometry based on the orientation.
var trackPos;
var trackSpan;
if (this._orientation === 'horizontal') {
trackPos = event.clientX - trackRect.left - this._pressData.delta;
trackSpan = trackRect.width - thumbRect.width;
}
else {
trackPos = event.clientY - trackRect.top - this._pressData.delta;
trackSpan = trackRect.height - thumbRect.height;
}
// Compute the desired value from the scroll geometry.
var value = trackSpan === 0 ? 0 : trackPos * this._maximum / trackSpan;
// Move the thumb to the computed value.
this._moveThumb(value);
};
/**
* Handle the `'mouseup'` event for the scroll bar.
*/
ScrollBar.prototype._evtMouseUp = function (event) {
// Do nothing if it's not a left mouse release.
if (event.button !== 0) {
return;
}
// Stop the event propagation.
event.preventDefault();
event.stopPropagation();
// Release the mouse.
this._releaseMouse();
};
/**
* Release the mouse and restore the node states.
*/
ScrollBar.prototype._releaseMouse = function () {
// Bail if there is no press data.
if (!this._pressData) {
return;
}
// Clear the repeat timer.
clearTimeout(this._repeatTimer);
this._repeatTimer = -1;
// Clear the press data.
this._pressData.override.dispose();
this._pressData = null;
// Remove the extra event listeners.
document.removeEventListener('mousemove', this, true);
document.removeEventListener('mouseup', this, true);
document.removeEventListener('keydown', this, true);
document.removeEventListener('contextmenu', this, true);
// Remove the active classes from the nodes.
this.thumbNode.classList.remove('p-mod-active');
this.decrementNode.classList.remove('p-mod-active');
this.incrementNode.classList.remove('p-mod-active');
};
/**
* Move the thumb to the specified position.
*/
ScrollBar.prototype._moveThumb = function (value) {
// Clamp the value to the allowed range.
value = Math.max(0, Math.min(value, this._maximum));
// Bail if the value does not change.
if (this._value === value) {
return;
}
// Update the internal value.
this._value = value;
// Schedule an update of the scroll bar.
this.update();
// Emit the thumb moved signal.
this._thumbMoved.emit(value);
};
return ScrollBar;
}(widget_1.Widget));
exports.ScrollBar = ScrollBar;
/**
* The namespace for the module implementation details.
*/
var Private;
(function (Private) {
/**
* Create the DOM node for a scroll bar.
*/
function createNode() {
var node = document.createElement('div');
var decrement = document.createElement('div');
var increment = document.createElement('div');
var track = document.createElement('div');
var thumb = document.createElement('div');
decrement.className = 'p-ScrollBar-button';
increment.className = 'p-ScrollBar-button';
decrement.dataset['action'] = 'decrement';
increment.dataset['action'] = 'increment';
track.className = 'p-ScrollBar-track';
thumb.className = 'p-ScrollBar-thumb';
track.appendChild(thumb);
node.appendChild(decrement);
node.appendChild(track);
node.appendChild(increment);
return node;
}
Private.createNode = createNode;
/**
* Find the scroll bar part which contains the given target.
*/
function findPart(scrollBar, target) {
// Test the thumb.
if (scrollBar.thumbNode.contains(target)) {
return 'thumb';
}
// Test the track.
if (scrollBar.trackNode.contains(target)) {
return 'track';
}
// Test the decrement button.
if (scrollBar.decrementNode.contains(target)) {
return 'decrement';
}
// Test the increment button.
if (scrollBar.incrementNode.contains(target)) {
return 'increment';
}
// Indicate no match.
return null;
}
Private.findPart = findPart;
})(Private || (Private = {}));