// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview This implements a splitter element which can be used to resize
* elements in split panes.
*
* The parent of the splitter should be an hbox (display: -webkit-box) with at
* least one previous element sibling. The splitter controls the width of the
* element before it.
*
* <div class=split-pane>
* <div class=left>...</div>
* <div class=splitter></div>
* ...
* </div>
*
*/
cr.define('cr.ui', function() {
// TODO(arv): Currently this only supports horizontal layout.
// TODO(arv): This ignores min-width and max-width of the elements to the
// right of the splitter.
/**
* Returns the computed style width of an element.
* @param {!Element} el The element to get the width of.
* @return {number} The width in pixels.
*/
function getComputedWidth(el) {
return parseFloat(el.ownerDocument.defaultView.getComputedStyle(el).width) /
getZoomFactor(el.ownerDocument);
}
/**
* This uses a WebKit bug to work around the same bug. getComputedStyle does
* not take the page zoom into account so it returns the physical pixels
* instead of the logical pixel size.
* @param {!Document} doc The document to get the page zoom factor for.
* @param {number} The zoom factor of the document.
*/
function getZoomFactor(doc) {
var dummyElement = doc.createElement('div');
dummyElement.style.cssText =
'position:absolute;width:100px;height:100px;top:-1000px;overflow:hidden';
doc.body.appendChild(dummyElement);
var cs = doc.defaultView.getComputedStyle(dummyElement);
var rect = dummyElement.getBoundingClientRect();
var zoomFactor = parseFloat(cs.width) / 100;
doc.body.removeChild(dummyElement);
return zoomFactor;
}
/**
* Creates a new splitter element.
* @param {Object=} opt_propertyBag Optional properties.
* @constructor
* @extends {HTMLDivElement}
*/
var Splitter = cr.ui.define('div');
Splitter.prototype = {
__proto__: HTMLDivElement.prototype,
/**
* Initializes the element.
*/
decorate: function() {
this.addEventListener('mousedown', this.handleMouseDown_.bind(this),
true);
},
/**
* Starts the dragging of the splitter. Adds listeners for mouse move and
* mouse up events and calls splitter drag start handler.
* @param {!Event} e The mouse event that started the drag.
*/
startDrag: function(e) {
if (!this.boundHandleMouseMove_) {
this.boundHandleMouseMove_ = this.handleMouseMove_.bind(this);
this.boundHandleMouseUp_ = this.handleMouseUp_.bind(this);
}
var doc = this.ownerDocument;
// Use capturing events on the document to get events when the mouse
// leaves the document.
doc.addEventListener('mousemove',this.boundHandleMouseMove_, true);
doc.addEventListener('mouseup', this.boundHandleMouseUp_, true);
this.startX_ = e.clientX;
this.handleSplitterDragStart();
},
/**
* Ends the dragging of the splitter. Removes listeners set in startDrag
* and calls splitter drag end handler.
*/
endDrag: function() {
var doc = this.ownerDocument;
doc.removeEventListener('mousemove', this.boundHandleMouseMove_, true);
doc.removeEventListener('mouseup', this.boundHandleMouseUp_, true);
this.handleSplitterDragEnd();
},
/**
* Handles the mousedown event which starts the dragging of the splitter.
* @param {!Event} e The mouse event.
* @private
*/
handleMouseDown_: function(e) {
this.startDrag(e);
// Default action is to start selection and to move focus.
e.preventDefault();
},
/**
* Handles the mousemove event which moves the splitter as the user moves
* the mouse. Calls splitter drag move handler.
* @param {!Event} e The mouse event.
* @private
*/
handleMouseMove_: function(e) {
var rtl = this.ownerDocument.defaultView.getComputedStyle(this).
direction == 'rtl';
var dirMultiplier = rtl ? -1 : 1;
var deltaX = dirMultiplier * (e.clientX - this.startX_);
this.handleSplitterDragMove(deltaX);
},
/**
* Handles the mouse up event which ends the dragging of the splitter.
* @param {!Event} e The mouse event.
* @private
*/
handleMouseUp_: function(e) {
this.endDrag();
},
/**
* Handles start of the splitter dragging. Saves current width of the
* element being resized.
* @protected
*/
handleSplitterDragStart: function() {
// Use the computed width style as the base so that we can ignore what
// box sizing the element has.
var leftComponent = this.previousElementSibling;
var doc = leftComponent.ownerDocument;
this.startWidth_ = parseFloat(
doc.defaultView.getComputedStyle(leftComponent).width);
},
/**
* Handles splitter moves. Updates width of the element being resized.
* @param {number} changeX The change of splitter horizontal position.
* @protected
*/
handleSplitterDragMove: function(deltaX) {
var leftComponent = this.previousElementSibling;
leftComponent.style.width = this.startWidth_ + deltaX + 'px';
},
/**
* Handles end of the splitter dragging. This fires a 'resize' event if the
* size changed.
* @protected
*/
handleSplitterDragEnd: function() {
// Check if the size changed.
var leftComponent = this.previousElementSibling;
var doc = leftComponent.ownerDocument;
var computedWidth = parseFloat(
doc.defaultView.getComputedStyle(leftComponent).width);
if (this.startWidth_ != computedWidth)
cr.dispatchSimpleEvent(this, 'resize');
},
};
return {
Splitter: Splitter
}
});