Resizer is a Module utility to inject resizing capabilities to any element inside Brackets.
On initialization, Resizer discovers all nodes tagged as "vert-resizable" and "horz-resizable" to add the resizer handler. Additionally, "top-resizer", "bottom-resizer", "left-resizer" and "right-resizer" classes control the position of the resizer on the element.
An element can be made resizable at any time using the makeResizable()
API.
Panel sizes are saved via preferences and restored when the DOM node becomes resizable
again in a subsequent launch.
The resizable elements trigger a panelResizeStart, panelResizeUpdate and panelResizeEnd event that can be used to create performance optimizations (such as hiding/showing elements while resizing), custom layout logic, etc. See makeResizable() for details on the events.
A resizable element can be collapsed/expanded using the show
, hide
and toggle
APIs or
via user action. This triggers panelCollapsed/panelExpanded events - see makeResizable().
Hides a resizable element.
function hide(element) {
var hideFunc = $(element).data("hide");
if (hideFunc) {
hideFunc.apply(element);
}
}
Returns the visibility state of a resizable element.
function isVisible(element) {
return $(element).is(":visible");
}
function _isPercentage(value) {
return !$.isNumeric(value) && value.indexOf('%') > -1;
}
function _percentageToPixels(value, total) {
return parseFloat(value.replace('%', '')) * (total / 100);
}
function _sideBarMaxSize() {
var siblingsWidth = 0;
$sideBar.siblings().not(".content").each(function (i, elem) {
var $elem = $(elem);
if ($elem.css("display") !== "none") {
siblingsWidth += $elem.outerWidth();
}
});
return $(".main-view").width() - siblingsWidth - 1;
}
Adds resizing and (optionally) expand/collapse capabilities to a given html element. The element's size & visibility are automatically saved & restored as a view-state preference.
Resizing can be configured in two directions:
Resizer handlers can be positioned on the element at:
A resizable element triggers the following events while resizing:
function makeResizable(element, direction, position, minSize, collapsible, forceLeft, createdByWorkspaceManager, usePercentages, _attachToParent) {
var $resizer = $('<div class="' + direction + '-resizer"></div>'),
$element = $(element),
$parent = $element.parent(),
$resizableElement = $($element.find(".resizable-content:first")[0]),
$body = $(window.document.body),
elementID = $element.attr("id"),
elementPrefs = PreferencesManager.getViewState(elementID) || {},
animationRequest = null,
directionProperty = direction === DIRECTION_HORIZONTAL ? "clientX" : "clientY",
directionIncrement = (position === POSITION_TOP || position === POSITION_LEFT) ? 1 : -1,
parentSizeFunction = direction === DIRECTION_HORIZONTAL ? $parent.innerWidth : $parent.innerHeight,
elementSizeFunction = function (newSize) {
if (!newSize) {
// calling the function as a getter
if (direction === DIRECTION_HORIZONTAL) {
return this.width();
} else {
return this.height();
}
} else if (!usePercentages) {
if (direction === DIRECTION_HORIZONTAL) {
return this.width(newSize);
} else {
return this.height(newSize);
}
} else {
// calling the function as a setter
var parentSize = parentSizeFunction.apply($parent),
percentage,
prop;
if (direction === DIRECTION_HORIZONTAL) {
prop = "width";
} else {
prop = "height";
}
percentage = newSize / parentSize;
this.css(prop, (percentage * 100) + "%");
return this; // chainable
}
},
resizerCSSPosition = direction === DIRECTION_HORIZONTAL ? "left" : "top",
contentSizeFunction = direction === DIRECTION_HORIZONTAL ? $resizableElement.width : $resizableElement.height;
if (PreferencesManager.get(PREFS_PURE_CODE) &&
($element.hasClass("bottom-panel") || $element.hasClass("sidebar"))) {
elementPrefs.visible = false;
}
if (!elementID) {
console.error("Resizable panels must have a DOM id to use as a preferences key:", element);
return;
}
// Detect legacy cases where panels in the editor area are created without using WorkspaceManager APIs
if ($parent[0] && $parent.is(".content") && !createdByWorkspaceManager) {
console.error("Resizable panels within the editor area should be created via WorkspaceManager.createBottomPanel(). \nElement:", element);
return;
}
if (minSize === undefined) {
minSize = DEFAULT_MIN_SIZE;
}
collapsible = collapsible || false;
if (_attachToParent) {
$parent.prepend($resizer);
} else {
$element.prepend($resizer);
}
// Important so min/max sizes behave predictably
$element.css("box-sizing", "border-box");
function adjustSibling(size) {
if (forceLeft !== undefined) {
$(forceLeft, $parent).css("left", size);
}
}
function resizeElement(elementSize, contentSize) {
elementSizeFunction.apply($element, [elementSize]);
if ($resizableElement.length) {
contentSizeFunction.apply($resizableElement, [contentSize]);
}
}
// If the resizer is positioned right or bottom of the panel, we need to listen to
// reposition it if the element size changes externally
function repositionResizer(elementSize) {
var resizerPosition = elementSize || 1;
if (position === POSITION_RIGHT || position === POSITION_BOTTOM) {
$resizer.css(resizerCSSPosition, resizerPosition);
}
}
$element.data("removeSizable", function () {
$resizer.off(".resizer");
$element.removeData("show");
$element.removeData("hide");
$element.removeData("resyncSizer");
$element.removeData("removeSizable");
$resizer.remove();
});
$element.data("resyncSizer", function () {
repositionResizer(elementSizeFunction.apply($element));
});
$element.data("show", function () {
var elementOffset = $element.offset(),
elementSize = elementSizeFunction.apply($element) || elementPrefs.size,
contentSize = contentSizeFunction.apply($resizableElement) || elementPrefs.contentSize;
// Resize the element before showing it again. If the panel was collapsed by dragging
// the resizer, the size of the element should be 0, so we restore size in preferences
resizeElement(elementSize, contentSize);
$element.show();
elementPrefs.visible = true;
if (collapsible) {
if (_attachToParent) {
$parent.prepend($resizer);
} else {
$element.prepend($resizer);
}
if (position === POSITION_TOP) {
$resizer.css(resizerCSSPosition, "");
} else if (position === POSITION_RIGHT) {
$resizer.css(resizerCSSPosition, elementOffset[resizerCSSPosition] + elementSize);
}
}
adjustSibling(elementSize);
$element.trigger("panelExpanded", [elementSize]);
PreferencesManager.setViewState(elementID, elementPrefs, null, isResizing);
});
$element.data("hide", function () {
var elementOffset = $element.offset(),
elementSize = elementSizeFunction.apply($element),
resizerSize = elementSizeFunction.apply($resizer);
$element.hide();
elementPrefs.visible = false;
if (collapsible) {
$resizer.insertBefore($element);
if (position === POSITION_RIGHT) {
$resizer.css(resizerCSSPosition, "");
} else if (position === POSITION_TOP) {
$resizer.css(resizerCSSPosition, elementOffset[resizerCSSPosition] + elementSize - resizerSize);
}
}
adjustSibling(0);
$element.trigger("panelCollapsed", [elementSize]);
PreferencesManager.setViewState(elementID, elementPrefs, null, isResizing);
});
$resizer.on("mousedown.resizer", function (e) {
var $resizeShield = $("<div class='resizing-container " + direction + "-resizing' />"),
startPosition = e[directionProperty],
startSize = $element.is(":visible") ? elementSizeFunction.apply($element) : 0,
newSize = startSize,
previousSize = startSize,
baseSize = 0,
resizeStarted = false;
isResizing = true;
$body.append($resizeShield);
if ($resizableElement.length) {
$element.children().not(".horz-resizer, .vert-resizer, .resizable-content").each(function (index, child) {
if (direction === DIRECTION_HORIZONTAL) {
baseSize += $(child).outerWidth();
} else {
baseSize += $(child).outerHeight();
}
});
}
function doRedraw() {
// only run this if the mouse is down so we don't constantly loop even
// after we're done resizing.
if (!isResizing) {
return;
}
// Check for real size changes to avoid unnecessary resizing and events
if (newSize !== previousSize) {
previousSize = newSize;
if ($element.is(":visible")) {
if (newSize < 10) {
toggle($element);
elementSizeFunction.apply($element, [0]);
} else {
// Trigger resizeStarted just before the first successful resize update
if (!resizeStarted) {
resizeStarted = true;
$element.trigger("panelResizeStart", newSize);
}
// Resize the main element to the new size. If there is a content element,
// its size is the new size minus the size of the non-resizable elements
resizeElement(newSize, (newSize - baseSize));
adjustSibling(newSize);
$element.trigger("panelResizeUpdate", [newSize]);
}
} else if (newSize > 10) {
elementSizeFunction.apply($element, [newSize]);
toggle($element);
// Trigger resizeStarted after expanding the element if it was previously collapsed
if (!resizeStarted) {
resizeStarted = true;
$element.trigger("panelResizeStart", newSize);
}
}
}
animationRequest = window.requestAnimationFrame(doRedraw);
}
function onMouseMove(e) {
// calculate newSize adding to startSize the difference
// between starting and current position, capped at minSize
newSize = Math.max(startSize + directionIncrement * (startPosition - e[directionProperty]), minSize);
// respect max size if one provided (e.g. by WorkspaceManager)
var maxSize = $element.data("maxsize");
if (maxSize !== undefined) {
// if provided as percentage size convert it to a pixel size
if (_isPercentage(maxSize)) {
maxSize = _percentageToPixels(maxSize, _sideBarMaxSize());
}
newSize = Math.min(newSize, maxSize);
}
e.preventDefault();
if (animationRequest === null) {
animationRequest = window.requestAnimationFrame(doRedraw);
}
}
$(window.document).on("mousemove", onMouseMove);
// If the element is marked as collapsible, check for double click
// to toggle the element visibility
if (collapsible) {
$resizeShield.on("mousedown", function (e) {
$(window.document).off("mousemove", onMouseMove);
$resizeShield.off("mousedown");
$resizeShield.remove();
animationRequest = null;
toggle($element);
});
}
function endResize(e) {
if (isResizing) {
var elementSize = elementSizeFunction.apply($element);
if ($element.is(":visible")) {
elementPrefs.size = elementSize;
if ($resizableElement.length) {
elementPrefs.contentSize = contentSizeFunction.apply($resizableElement);
}
PreferencesManager.setViewState(elementID, elementPrefs);
repositionResizer(elementSize);
}
isResizing = false;
if (resizeStarted) {
$element.trigger("panelResizeEnd", [elementSize]);
}
// We wait 300ms to remove the resizer container to capture a mousedown
// on the container that would account for double click
window.setTimeout(function () {
$(window.document).off("mousemove", onMouseMove);
$resizeShield.off("mousedown");
$resizeShield.remove();
animationRequest = null;
}, 300);
}
}
$(window.document).one("mouseup", endResize);
e.preventDefault();
});
// Panel preferences initialization
if (elementPrefs) {
if (elementPrefs.size !== undefined) {
elementSizeFunction.apply($element, [elementPrefs.size]);
}
if (elementPrefs.contentSize !== undefined) {
contentSizeFunction.apply($resizableElement, [elementPrefs.contentSize]);
}
if (elementPrefs.visible !== undefined && !elementPrefs.visible) {
hide($element);
} else {
adjustSibling(elementSizeFunction.apply($element));
repositionResizer(elementSizeFunction.apply($element));
}
}
}
function updateResizeLimits() {
var sideBarMaxSize = _sideBarMaxSize(),
maxSize = $sideBar.data("maxsize"),
width = false;
if (maxSize !== undefined && _isPercentage(maxSize)) {
sideBarMaxSize = _percentageToPixels(maxSize, sideBarMaxSize);
}
if ($sideBar.width() > sideBarMaxSize) {
// Adjust the sideBar's width in case it exceeds the window's width when resizing the window.
$sideBar.width(sideBarMaxSize);
resyncSizer($sideBar);
$(".content").css("left", $sideBar.width());
$sideBar.trigger("panelResizeStart", $sideBar.width());
$sideBar.trigger("panelResizeUpdate", [$sideBar.width()]);
$sideBar.trigger("panelResizeEnd", [$sideBar.width()]);
}
}
function onWindowResize(e) {
if ($sideBar.css("display") === "none") {
return;
}
if (!isWindowResizing) {
isWindowResizing = true;
// We don't need any fancy debouncing here - we just need to react before the user can start
// resizing any panels at the new window size. So just listen for first mousemove once the
// window resize releases mouse capture.
$(window.document).one("mousemove", function () {
isWindowResizing = false;
updateResizeLimits();
});
}
}
window.addEventListener("resize", onWindowResize, true);
// Scan DOM for horz-resizable and vert-resizable classes and make them resizable
AppInit.htmlReady(function () {
var minSize = DEFAULT_MIN_SIZE;
$mainView = $(".main-view");
$sideBar = $("#sidebar");
$(".vert-resizable").each(function (index, element) {
if ($(element).data().minsize !== undefined) {
minSize = $(element).data().minsize;
}
if ($(element).hasClass("top-resizer")) {
makeResizable(element, DIRECTION_VERTICAL, POSITION_TOP, minSize, $(element).hasClass("collapsible"));
}
//if ($(element).hasClass("bottom-resizer")) {
// makeResizable(element, DIRECTION_VERTICAL, POSITION_BOTTOM, DEFAULT_MIN_SIZE);
//}
});
$(".horz-resizable").each(function (index, element) {
if ($(element).data().minsize !== undefined) {
minSize = $(element).data().minsize;
}
//if ($(element).hasClass("left-resizer")) {
// makeResizable(element, DIRECTION_HORIZONTAL, POSITION_LEFT, DEFAULT_MIN_SIZE);
//}
if ($(element).hasClass("right-resizer")) {
makeResizable(element, DIRECTION_HORIZONTAL, POSITION_RIGHT, minSize, $(element).hasClass("collapsible"), $(element).data().forceleft);
}
});
// The main toolbar is only collapsible.
if ($("#main-toolbar").hasClass("collapsible") && PreferencesManager.get(PREFS_PURE_CODE)) {
ViewUtils.hideMainToolBar();
}
});
EventDispatcher.makeEventDispatcher(exports);
exports.makeResizable = makeResizable;
exports.removeSizable = removeSizable;
exports.resyncSizer = resyncSizer;
exports.toggle = toggle;
exports.show = show;
exports.hide = hide;
exports.isVisible = isVisible;
//Resizer Constants
exports.DIRECTION_VERTICAL = DIRECTION_VERTICAL;
exports.DIRECTION_HORIZONTAL = DIRECTION_HORIZONTAL;
exports.POSITION_TOP = POSITION_TOP;
exports.POSITION_RIGHT = POSITION_RIGHT;
exports.POSITION_BOTTOM = POSITION_BOTTOM;
exports.POSITION_LEFT = POSITION_LEFT;
});
Removes the resizability of an element if it's resizable
function removeSizable(element) {
var removeSizableFunc = $(element).data("removeSizable");
if (removeSizableFunc) {
removeSizableFunc.apply(element);
}
}
Updates the sizing div by resyncing to the sizing edge of the element Call this method after manually changing the size of the element
function resyncSizer(element) {
var resyncSizerFunc = $(element).data("resyncSizer");
if (resyncSizerFunc) {
resyncSizerFunc.apply(element);
}
}