Source for "Element Resizer"

By Richard Gibson
Has 6 other scripts.


Add Syntax Highlighting (this will take a few seconds, probably freezing your browser while it works)

// ==UserScript==
// @name           Element Resizer
// @namespace      http://userscripts.org/people/336
// @description    Makes user-specifiable elements resizable by mouse and keyboard, either in or out of the page flow or "maximized" above it.
// @source         http://userscripts.org/scripts/show/2301
// @identifier     http://userscripts.org/scripts/source/2301.user.js
// @version        0.3.1
// @date           2006-10-13
// @creator        Richard Gibson <@gmail.com>
// @include        *
// ==/UserScript==
//
// **COPYRIGHT NOTICE**
// 
// I, Richard Gibson, hereby establish my original authorship of this
// work, and announce its release into the public domain.  I claim no
// exclusive copyrights to it, and will neither pursue myself (nor
// condone pursuit by others of) punishment, retribution, or forced
// compensation for its reproduction in any form.
// 
// That being said, I would like to receive credit for this work
// whenever it, or any part thereof, is reproduced or incorporated into
// another creation; and would also like compensation whenever income is
// derived from such reproduction or inclusion.  At the very least,
// please let me know if you find this work useful or enjoyable, and
// contact me with any comments or criticisms regarding it.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// 
// **END COPYRIGHT NOTICE**
//
//
// Changelog:
// 0.3.1 (2006-10-13)
// 	Fixed: issue with wrapping elements of the invalid <TABLE><FORM>...</FORM></TABLE> construct
// 		(first reported by private_lock on 2006-10-04; found on http://single.de/msg.html)
// 0.3 (2006-06-02)
// 	New: allowed differentiation of input tags by type (input:text, input:password, etc.)
// 	Improved: changed out of flow horizontal keyboard resizing to free up control+left and
// 		control+right
// 0.2.1 (2005-12-07)
// 	Updated: changes for compatibility with Gmail
// 0.2 (2005-12-07)
// 	New: added automatic updating
// 0.1 (2005-11-22)
// 	original release
//
// -----------------------------------------------------------------------------
//
// This is a Greasemonkey user script.
//
// To install, you need Greasemonkey: http://greasemonkey.mozdev.org/
// Then restart Firefox and revisit this script.
// Under Tools, there will be a new menu item to "Install User Script".
// Accept the default configuration and install.
//
// To uninstall, go to Tools/Manage User Scripts, select this script,
// and click Uninstall.
//
// -----------------------------------------------------------------------------

(function () {

// bit masks
var MODE_RESET = 0;
var MODE_RESIZED = 1;
var MODE_MAXIMIZED = 2;

// configurable constants
var SCRIPT = {
	name: "Element Resizer",
	namespace: "http://userscripts.org/people/336",
	description: 'Makes user-specifiable elements resizable by mouse and'
			+ ' keyboard, either in or out of the page flow or "maximized" above'
			+ ' it.',
	source: "http://userscripts.org"			// script homepage/description URL
			+ "/scripts/show/2301",
	identifier: "http://userscripts.org"	// script URL
			+ "/scripts/source/2301.user.js",
	version: "0.3.1",								// version
	date: (new Date(2006, 10 - 1, 13))		// update date
			.valueOf()
};
var CMD_HELP_LABEL = SCRIPT.name + ": Help";
var CMD_HELP_UNAVAILABLE = "Help is unavailable on this page";
var CMD_SETTAGS_LABEL = SCRIPT.name + ": Set Tags";
var CMD_SETTAGS_PROMPT = "Enter the comma-separated list of tags to make resizeable"
		+ "\n\nCommon tags (complete list at http://www.w3.org/TR/html4/index/elements.html):\n"
		+ "iframe\n\u00a0\u00a0\u00a0\u00a0\u00a0" + "inline subwindow\n"
		+ "img\n\u00a0\u00a0\u00a0\u00a0\u00a0" + "embedded image\n"
		+ "input:text\n\u00a0\u00a0\u00a0\u00a0\u00a0" + "single-line text field\n"
		+ "textarea\n\u00a0\u00a0\u00a0\u00a0\u00a0" + "multi-line text field";
var CMD_RESTORE_LABEL = SCRIPT.name + ": Restore All";
var MIN_SIZE_PX = 15;
var MAXIMIZED_MARGIN = "1em";
var MIN_KEY_RESIZE_INIT_DELAY = 200;	// milliseconds
var COPIED_STYLE_SUFFIX = " !important";
var RESTORE_STYLE_ATTR = "elementResizerRestoreStyles";
var RESTORE_DIM_ATTR = "elementResizerRestoreDimensions";
var REPLACEMENT_TAG = "map";	// inline content that can contain arbitrary block-level content
var REPLACEMENT_CLASS = "elementResizerReplacement";
var MAXIMIZED_CLASS = "elementResizerMaximized";
var PLACEHOLDER_TAG = "div";
var PLACEHOLDER_CLASS = "elementResizerPlaceholder";
var SHRINKWRAP_TAG = "div";
var SHRINKWRAP_CLASS = "elementResizerShrinkwrap";
var RESIZER_TAG = "span";
var RESIZER_CLASS = "elementResizerResizer";
var RESIZER_BAR_CLASS = "elementResizerBar";
var RESIZER_HANDLE_CLASS = "elementResizerHandle";
var RESIZING_CLASS = "elementResizerResizing";
var RESIZER_TITLE = ("[Shift|Ctrl|Alt]+drag:[ratio-lock|center|in-flow] resize,"
		+ "click:reset,Shift+click:maximize")
		.replace(/,/g, "\u2003")	// U+2003 Em Space (width = 1 em)
		.replace(/:/g, ":\u2006")	// U+2006 Six-Per-Em Space (width = 1/6 em)
		.replace(/ /g, "\u2008");	// U+2008 Punctuation Space (same width as ",")
var HELP_CLASS = "elementResizerHelp";
var WINDOW_CLASS = "elementResizerWindow";
var WINDOW_TITLE_CLASS = "elementResizerWindowTitle";
var WINDOW_CONTENT_CLASS = "elementResizerWindowContent";

// non-configurable constants
var REPLACEMENT_PROPERTIES = {display: "block", "float": "none", clear: "none",
		position: "static", top: 0, right: 0, bottom: 0, left: 0,
		"-moz-box-sizing": "border-box", height: "100%", width: "100%",
		"margin-top": 0, "margin-right": 0, "margin-bottom": 0, "margin-left": 0,
		"z-index": 0};
var TRIM_RE = /(^\s+)|(\s+$)/g;
var STYLE_RE = /-(.)|^(float)$/g;
var STYLE_RE_FN = function (substring, paren1, paren2) { return (paren1
		? paren1.toUpperCase()
		: "css" + paren2.charAt(0).toUpperCase() + paren2.substring(1)); };
var APPEND_PX_RE = /([0-9. ])$/;
var APPEND_PX_RE_FN = "$1px";
var REPLACEMENT_CLASS_RE = new RegExp("(^|\\s)" + REPLACEMENT_CLASS + "(\\s|$)");
var PLACEHOLDER_CLASS_RE = new RegExp("(^|\\s)" + PLACEHOLDER_CLASS + "(\\s|$)");
var SHRINKWRAP_CLASS_RE = new RegExp("(^|\\s)" + SHRINKWRAP_CLASS + "(\\s|$)");
var MAXIMIZED_CLASS_RE = new RegExp("(^|\\s+)" + MAXIMIZED_CLASS + "(\\s+|$)", "g");
var RESIZING_CLASS_RE = new RegExp("(^|\\s+)" + RESIZING_CLASS + "(\\s+|$)", "g");
	// resizer arrays (clockwise from top left; first bars and then corners:
	// 	top, right, bottom, left, top-left, top-right, bottom-right, bottom-left)
var RESIZER_DIRECTIONS = [{x:  0, y: -1}, {x: +1, y:  0}, {x:  0, y: +1},
		{x: -1, y:  0}, {x: -1, y: -1}, {x: +1, y: -1}, {x: +1, y: +1},
		{x: -1, y: +1}];
var RESIZER_SUBCLASSES = [RESIZER_BAR_CLASS, RESIZER_BAR_CLASS,
	RESIZER_BAR_CLASS, RESIZER_BAR_CLASS, RESIZER_HANDLE_CLASS,
	RESIZER_HANDLE_CLASS, RESIZER_HANDLE_CLASS, RESIZER_HANDLE_CLASS];
var RESIZER_STYLES = [
	// bars
	"top:-2px;left:0;width:100%;min-height:2px;border-top-width:2px;cursor:n-resize;",
	"right:-2px;top:0;height:100%;min-width:2px;border-right-width:2px;cursor:e-resize;",
	"bottom:-2px;left:0;width:100%;min-height:2px;border-bottom-width:2px;cursor:s-resize;",
	"left:-2px;top:0;height:100%;min-width:2px;border-left-width:2px;cursor:w-resize;",
	// corners
	"top:-4px;left:-4px;-moz-border-radius-bottomright:100%;-moz-border-radius-topleft:0;"
			+ "cursor:nw-resize;",
	"top:-4px;right:-4px;-moz-border-radius-bottomleft:100%;-moz-border-radius-topright:0;"
			+ "cursor:ne-resize;",
	"bottom:-4px;right:-4px;-moz-border-radius-topleft:100%;-moz-border-radius-bottomright:0;"
			+ "cursor:se-resize;",
	"bottom:-4px;left:-4px;-moz-border-radius-topright:100%;-moz-border-radius-bottomleft:0;"
			+ "cursor:sw-resize;"
];
var HELP_CONTENT = ['<div class="' + WINDOW_CLASS + '">',
	' <div class="' + WINDOW_TITLE_CLASS + '">',
	'  <a href="#">close</a>',
	'  <h1>Element Resizer Help</h1></div>',
	' <div class="' + WINDOW_CONTENT_CLASS + '">',
	'  <p>',
	'   Element Resizer allows you to dynamically resize page elements using',
	'   either the mouse or the keyboard.  The resizable tags can be specified',
	'   by the <kbd>Set Tags</kbd> command, available on Firefox/Greasemonkey',
	'   under <kbd>Tools &gt; User Script Commands</kbd>.  When focused (or',
	'   hovered over), resizable elements will show a resizer handle at each',
	'   corner.  A handle will grow when hovered over, and the side of a',
	'   resizable element will turn into a blue resizer bar when hovered over.',
	'  </p>',
	'  <p>',
	'   Resizes are always one of three general types, and can have up to two',
	'   special behaviors:',
	'  </p>',
	'  <dl>',
	'   <dt>In flow</dt>',
	'    <dd>The element stays in the page flow, and other elements are',
	'    moved/resized as appropriate.</dd>',
	'   <dt>Out of flow</dt><dd>The element "floats" above other content.</dd>',
	'   <dt>Maximized</dt>',
	'    <dd>The element "floats" above other content and does not scroll with',
	'    the page.</dd>',
	'   <dt>Locked aspect ratio</dt>',
	'    <dd>The aspect ratio (ratio of width to height) of the element does',
	'    not change.</dd>',
	'   <dt>Centered</dt><dd>The center of the element does not change.</dd>',
	'  </dl>',
	'',
	'  <h2>Mouse Resizing</h2>',
	'  <p>',
	'   Drag on a handle or a resizer bar to finely control the size of an',
	'   element, and end the drag to keep the resize or press <kbd>Escape</kbd>',
	'   to cancel it. This resize defaults to out of flow, and is affected by',
	'   the following modifier keys:',
	'  </p>',
	'  <dl>',
	'   <dt><kbd>Shift</kbd></dt><dd>locked aspect ratio</dd>',
	'   <dt><kbd>Control</kbd></dt><dd>centered</dd>',
	'   <dt><kbd>Alt</kbd></dt><dd>in flow</dd>',
	'  </dl>',
	'  <p>',
	'   Clicking on a handle or a resizer bar also has an effect.  If',
	'   <kbd>Shift</kbd> is held, the element is toggled to/from maximized (if',
	'   the element is already maximized, it is returned to the resized state,',
	'   or to reset if there is no non-maximized resized state).  If',
	'   <kbd>Shift</kbd> is not held, the element is toggled to/from reset (if',
	'   the element is already reset, it is returned to the resized state, or',
	'   to maximized if there is no resized state).',
	'  </p>',
	'',
	'  <h2>Keyboard Resizing</h2>',
	'  <p>(Text entry) elements can be resized with the keyboard:</p>',
	'  <dl>',
	'   <dt><kbd>Alt</kbd>+<kbd>Enter</kbd></dt>',
	'    <dd>in flow height increase</dd>',
	'   <dt><kbd>Shift</kbd>+<kbd>Alt</kbd>+<kbd>Enter</kbd></dt>',
	'    <dd>in flow height decrease</dd>',
	'   <dt><kbd>Control</kbd>+<kbd>Space</kbd></dt>',
	'    <dd>in flow width increase</dd>',
	'   <dt><kbd>Shift</kbd>+<kbd>Control</kbd>+<kbd>Space</kbd></dt>',
	'    <dd>in flow width decrease</dd>',
	'   <dt><kbd>Control</kbd>+<kbd>Up</kbd></dt>',
	'    <dd>centered out of flow height increase</dd>',
	'   <dt><kbd>Control</kbd>+<kbd>Down</kbd></dt>',
	'    <dd>centered out of flow height decrease</dd>',
	'   <dt><kbd>Shift</kbd>+<kbd>Control</kbd>+<kbd>Up</kbd></dt>',
	'    <dd>centered out of flow width increase</dd>',
	'   <dt><kbd>Shift</kbd>+<kbd>Control</kbd>+<kbd>Down</kbd></dt>',
	'    <dd>centered out of flow width decrease</dd>',
	'   <dt><kbd>Alt</kbd>+<kbd>Minus</kbd></dt>',
	'    <dd>toggle reset (defaulting to maximized)</dd>',
	'   <dt><kbd>Alt</kbd>+<kbd>Plus</kbd></dt>',
	'    <dd>toggle maximized (preferring resized over reset)</dd>',
	'  </dl>',
	'',
	'  <h2>Restoring Elements</h2>',
	'  <p>',
	'   Elements can be restored to their original position and dimensions',
	'   <sup style="font-size:smaller;font-weight:bold;">\u2020</sup>. To do',
	'   so, click on a resizer handle or bar while holding <kbd>Alt</kbd>; or',
	'   alternately press <kbd>Alt</kbd>+<kbd>Backspace</kbd> or',
	'   <kbd>Alt</kbd>+<kbd>Delete</kbd> while the element has the focus.',
	'   Restore all elements with the <kbd>Restore All</kbd> command.',
	'  </p>',
	'  <p>',
	'   <sup style="font-size:smaller;font-weight:bold;">\u2020</sup>',
	'   <span style="font-size:x-small;">Dimensions may be slightly off for',
	'   elements with intrinsic dimensions, like form elements and inline',
	'   frames.</span>',
	'  </p>',
	'',
	'  <hr />',
	'  <h2>Demo</h2>',
	'  <p>The following elements are currently resizable:</p>',
	'  <table style="margin-top:1em;"><tbody></tbody></table>',
	' </div>',
	'</div>'
].join("\n");

// global variables
var defaultPrefs = {tags: "textarea"};
var maximizedProperties = null;
var wrapTemplate;
var iframeCover;
var sizeTranslator;
var helpFrame;

// update automatically
try {
	addEventHandler(window, "load", function () { try {
		(unsafeWindow || window.wrappedJSObject || window)
				.UserScriptUpdates.requestAutomaticUpdates(SCRIPT);
	} catch (ex) {} }, false);
} catch (ex) {}

// utility functions
	// math
function round (num, intPlaces) {
	var scaler = Math.pow(10, parseInt(intPlaces) || 0);
	return Math.round(num * scaler) / scaler;
}
	// tags
function getTagAndType (strTagWithType) {
	var i = strTagWithType.indexOf(":");
	return (i == -1
		? [strTagWithType, null]
		: [strTagWithType.substring(0, i), strTagWithType.substring(i + 1)]
	);
}
	// events
function addEventHandler (objElement, strEvent, fnHandler, blnUseCapture) {
	var success = false;
	try {
		objElement.addEventListener(strEvent, fnHandler, (blnUseCapture ? true : false));
		success = true;
	}
	catch (ex) {
		try { success = objElement.attachEvent("on" + strEvent, fnHandler); } catch (ex) {}
	}
	return success;
};
function removeEventHandler (objElement, strEvent, fnHandler, blnUseCapture) {
	try {
		objElement.removeEventListener(strEvent, fnHandler, (blnUseCapture ? true : false));
	}
	catch (ex) {
		try { objElement.detachEvent("on" + strEvent, fnHandler); } catch (ex) {}
	}
};
	// style
function addGlobalStyle (strCSS, blnAtFront) {
	try {
		var document = window.document, style =
			(
				document.getElementsByTagName("head")[0]
				|| (
					document.documentElement
					|| document.getElementsByTagName("html")[0]
					|| document.appendChild(document.createElement("html"))
				) // html
						.appendChild(document.createElement("head"))
						.appendChild(document.createElement("title")).parentNode
			).appendChild(document.createElement("style"));
		if (blnAtFront) {
			style.parentNode.insertBefore(style, style.parentNode.firstChild);
		}
		style.setAttribute("type", "text/css");
		style.appendChild(document.createTextNode(strCSS));
	} catch (ex) {}
};
function applyStyleString (el, strStyle) {
	for (var i = 0, styles = strStyle.split(";"), style; i < styles.length; i++) {
		try {
			style = styles[i].split(":");
			el.style[style[0].replace(TRIM_RE, "").replace(STYLE_RE, STYLE_RE_FN)] =
					(style.slice(1).join(":")).replace(TRIM_RE, "");
		} catch (ex) {}
	}
};
function getStyle (varStyles, strProperty) {
	try {
		return varStyles.getPropertyValue(strProperty);
	}
	catch (ex) {
		return (varStyles || {})[(strProperty + '').replace(STYLE_RE, STYLE_RE_FN)];
	}
};
function bringToFront (el) {
	window.setTimeout(function () {
		try {
			var	maxZ = 1,
					doc = el.ownerDocument || window.document,
					win = doc.defaultView || window,
					all = doc.getElementsByTagName("*"),
					styles;
			for (var i = all.length - 1; i >= 0; i--) {
				try {
					styles = win.getComputedStyle(all[i], "");
				}
				catch (ex) {
					styles = all[i].currentStyle || all[i].style;
				}
				maxZ = Math.max(maxZ,
						parseInt(getStyle(styles, "z-index"), 10) || 0);
			}
			el.style.zIndex = maxZ + 1;
		} catch (ex) {}
	}, 0);
};
	// elements
function isAncestorOf (objAncestor, objDescendant, blnInclusive) {
	if (!objAncestor || !objDescendant) { return null; }
	var temp;
	
	temp = (blnInclusive ? objDescendant : objDescendant.parentNode || objDescendant.parentElement);
	while (temp && temp !== objAncestor) { temp = temp.parentNode || temp.parentElement; }
	return (temp === objAncestor);
};

// preference functions
function getPref (strPref) {
	try {
		return GM_getValue(strPref, defaultPrefs[strPref]);
	}
	catch (ex) {
		return defaultPrefs[strPref];
	}
};
function setPref (strPref, varValue) {
	try {
		GM_setValue(strPref, varValue);
	}
	catch (ex) {
		defaultPrefs[strPref] = varValue;
	}
};
function getTags () {
	var tags = getPref("tags").toLowerCase().split(",");
	for (var i = tags.length - 1; i >= 0; i--) {
		tags[i] = tags[i].replace(TRIM_RE, "");
		if (tags[i] == "*" || tags[i] == "" || tags[i] == REPLACEMENT_TAG) tags.splice(i, 1);
	}
	return tags;
};
function setTags (arrTags) {
	setPref("tags", arrTags.join(","));
};
try {
	GM_registerMenuCommand(CMD_HELP_LABEL, function () {
		var helpIsElement = false;
		closeHelp();
		bringToFront(document.body.appendChild(helpFrame));
		for (var i = 0; !helpIsElement && i < helpFrame.childNodes.length; i++) {
			helpIsElement = (helpFrame.childNodes[i].nodeType == 1);
		}
		if (helpIsElement) {
			populateHelp(getTags());
		}
		else {
			closeHelp();
			alert(CMD_HELP_UNAVAILABLE);
		}
	});
	GM_registerMenuCommand(CMD_SETTAGS_LABEL, function () {
		var tags = window.prompt(CMD_SETTAGS_PROMPT, getTags().join(","));
		if (typeof(tags) == "string") setTags(tags.split(","));
	});
	GM_registerMenuCommand(CMD_RESTORE_LABEL, function () {
		var tags = getTags(), elements, length;
		try {
			if (maximizedProperties) toggleMaximized(maximizedProperties, false);
			maximizedProperties = null;
			for (var i = tags.length - 1; i >= 0; i--) {
				elements = window.document.getElementsByTagName(tags[i]);	// no need to check type
				length = elements.length;
				for (var j = 0; j < length; j++) unwrap(elements[j]);
			}
		} catch (ex) {}
	});
} catch (ex) {}

// wrapping functions
function isWrapped (el) {
	try {
		return (
			SHRINKWRAP_CLASS_RE.test(el.parentNode.className)
			&& PLACEHOLDER_CLASS_RE.test(el.parentNode.parentNode.className)
			&& REPLACEMENT_CLASS_RE.test(
					el.parentNode.parentNode.parentNode.className)
		);
	} catch (ex) {}
	return false;
};
function wrap (el) {
	var form = el.form;
	var replacement = wrapTemplate.cloneNode(true);
	var height = el.offsetHeight;
	var width = el.offsetWidth;
	var properties = {element: el, swap: null, box: replacement,
			placeholder: replacement.lastChild,
			shrinkwrap: replacement.lastChild.lastChild,
			currentMode: MODE_RESET, previousMode: MODE_RESET, resized: false,
			replaced: {},
			dimensions: {pixelsPerWidthStep: NaN, pixelsPerHeightStep: NaN},
			textInput: {potential: /^(textarea|input)$/i.test(el.nodeName),
					actual: (el.nodeName.toLowerCase() == "textarea"),
					widthProperty: null, heightProperty: null,
					originalWidth: null, originalHeight: null},
			drag: {active: false, startX: null, startY: null, movedSinceMousedown: false,
					onmousemove: null, onmouseup: null, onkeypress: null},
			key: {keydownTimestamp: 0, onkeypress: null, onkeyup: null, tmrClear: null,
					activePresses: 0}};
	var styles = null;
	var replacementStyle = "";
	var resetStyle = "";
	var restoreStyle = "";
	var textInput = properties.textInput;
	
	// add event handlers
	addElementEventHandlers(el, properties);
	for (var resizers = properties.shrinkwrap.childNodes, i = 0;
			i < RESIZER_DIRECTIONS.length; i++) {
		addResizerEventHandlers(resizers[i], RESIZER_DIRECTIONS[i], properties);
	}
	if (form) addEventHandler(form, "submit", function () {
		unwrap(el, properties, form);
	}, true);
	
	// move styles from the element to the replacement
	try {
		styles = ((el.ownerDocument || window.document).defaultView || window)
				.getComputedStyle(el, "");
	}
	catch (ex) {
		styles = el.currentStyle || el.style;
	}
	for (var s in REPLACEMENT_PROPERTIES) {
		replacementStyle += s + ":" + getStyle(styles, s) + ";";
		resetStyle += s + ":" + REPLACEMENT_PROPERTIES[s] + COPIED_STYLE_SUFFIX
				+ ";";
		properties.replaced[s.replace(STYLE_RE, STYLE_RE_FN)] =
				getStyle(el.style, s);
		restoreStyle += s + ":" + getStyle(el.style, s) + ";";
	}
	applyStyleString(replacement, replacementStyle); 
	applyStyleString(el, resetStyle);
	el.setAttribute(RESTORE_STYLE_ATTR, restoreStyle);
	
	// convert position:static to position:relative and display:inline to display:inline-block
	if (replacement.style.position == "static") {
		for (var s in {top: 0, right: 0, bottom: 0, left: 0}) {
			replacement.style[s] = "0 !important";
		}
		replacement.style.position = "relative !important";
	}
	if (replacement.style.display == "inline") {
		// hack from http://www.spartanicus.utvinternet.ie/centered_image_gallery_with_captions.htm
		replacement.style.display = "table-cell !important";
		replacement.style.display = "inline-table !important";
		replacement.style.display = "inline-block !important";
	}
	
	// apply dimensions to the replacement, persist dimensions for each mode, and perform the wrap
	// 	note: the structure is replacement > placeholder > shrinkwrap > grippies
	replacement.style.height = properties.shrinkwrap.style.height = height + "px";
	replacement.style.width = properties.shrinkwrap.style.width = width + "px";
	for (var resizers = properties.shrinkwrap.childNodes,
			map = [["height", "top"], ["width", "right"], ["height", "bottom"], ["width", "left"]],
			i = 0; i < 4; i++) {
		applyStyleString(resizers[i],
				map[i][0] + ":" + getStyle(styles, "border-" + map[i][1] + "-width"));
	}
	for (var modes = [MODE_RESET, MODE_RESIZED, MODE_MAXIMIZED], i = 0; i < modes.length; i++) {
		properties.dimensions[modes[i]] = {top: 0, left: 0,
				width: (modes[i] == MODE_MAXIMIZED ? "100%" : width),
				height: (modes[i] == MODE_MAXIMIZED ? "100%" : height),
				boxWidth: width, boxHeight: height};
	}
	if (el.nodeName.toLowerCase() == "textarea") {
		textInput.widthProperty = "cols";
		textInput.heightProperty = "rows";
		el.setAttribute(RESTORE_DIM_ATTR,
				"cols:" + (el.getAttribute("cols") || "")
				+ ";rows:" + (el.getAttribute("rows") || ""));
	}
	else {
		textInput.widthProperty = "size";
		el.setAttribute(RESTORE_DIM_ATTR,
				"size:" + (el.getAttribute("size") || ""));
	}
	textInput.originalWidth = el.getAttribute(textInput.widthProperty);
	textInput.originalHeight = el.getAttribute(textInput.heightProperty);
	el.parentNode.insertBefore(replacement, el);
	properties.shrinkwrap.insertBefore(el, properties.shrinkwrap.firstChild);
};
function unwrap (el, objProperties, elSubmittingForm) {
	if (isWrapped(el)) {
		var box = (objProperties && objProperties.box)
				|| el.parentNode.parentNode.parentNode;
		var textInput = objProperties && objProperties.textInput;
		var restoreStyle = "";
		var submitElement;
		
		if (objProperties) {
			if (objProperties.currentMode != MODE_RESET) toggleReset(objProperties);
			if (el.hasAttribute(textInput.widthProperty)) {
				el.setAttribute(textInput.widthProperty, textInput.originalWidth);
			}
			if (el.hasAttribute(textInput.heightProperty)) {
				el.setAttribute(textInput.heightProperty, textInput.originalHeight);
			}
			for (var s in REPLACEMENT_PROPERTIES) {
				restoreStyle += s + ":" + getStyle(objProperties.replaced, s) + ";";
			}
		}
		else {
			for (var dims = (el.getAttribute(RESTORE_DIM_ATTR) || "").split(";"),
					dim, i = dims.length - 1; i >= 0; i--) {
				dim = dims[i].split(":", 2);
				if (dim[1].length > 0) el.setAttribute(dim[0], dim[1]);
			}
			restoreStyle = el.getAttribute(RESTORE_STYLE_ATTR) || "";
		}
		
		applyStyleString(el, restoreStyle);
		box.parentNode.replaceChild(el, box);
		
		// make sure form data is submitted
		if (elSubmittingForm && el.name && elSubmittingForm !== el.form) {
			try { submitElement = elSubmittingForm.elements.namedItem(el.name); } catch (ex) {}
			if (!submitElement) {
				submitElement = document.createElement("input");
				submitElement.setAttribute("type", "hidden");
				submitElement.setAttribute("name", el.name);
				elSubmittingForm.appendChild(submitElement);
			}
			if (submitElement !== el) { submitElement.setAttribute("value", el.value); }
		}
	}
};

function addElementEventHandlers (el, objProperties) {
	addEventHandler(el, "keydown", function (evt) {
		evt = evt || window.event;
		var key = objProperties.key, delay = (evt.timeStamp - key.keydownTimestamp);
		
		// update the key properties and bail out on repeats (grr... why does keydown repeat?)
		key.keydownTimestamp = evt.timeStamp || 0;
		if (key.tmrClear) clearTimeout(key.tmrClear);
		if (delay < MIN_KEY_RESIZE_INIT_DELAY) return;
		
		// measure the element and add event handlers
		if (evt.ctrlKey || evt.altKey) measureIntrinsicDimensions(el, objProperties);
		if ((evt.ctrlKey || evt.altKey) && !key.onkeypress) {
			key.onkeypress = function (evt) {
				// eliminate conflicts
				var objKey = objProperties.key;
				if (objKey.activePresses++ > 0) { objKey.activePresses--; return; }
				
				evt = evt || window.event;
				var	key = evt.keyCode || evt.which,
						ctrl = evt.ctrlKey, alt = evt.altKey, shift = evt.shiftKey,
						UP = evt.DOM_VK_UP || 38, DOWN = evt.DOM_VK_DOWN || 40,
						LEFT = evt.DOM_VK_LEFT || 37, RIGHT = evt.DOM_VK_RIGHT || 39,
						shrinkwrapStyle = objProperties.shrinkwrap.style, dimensions,
						handled = false,
						inFlow = true, centered = false,
						widthStep = objProperties.dimensions.pixelsPerWidthStep,
						heightStep = objProperties.dimensions.pixelsPerHeightStep,
						dX = 0, dY = 0, width, height;
				if (objKey.tmrClear) clearTimeout(objKey.tmrClear);
				if (alt && (key == (evt.DOM_VK_RETURN || 13) || key == (evt.DOM_VK_ENTER || 14)))
					dY = (shift ? -1 : 1) * heightStep;
				if (ctrl && key == (evt.DOM_VK_SPACE || 32)) dX = (shift ? -1 : 1) * widthStep;
				if (ctrl && (key == UP || key == DOWN)) {
					inFlow = false;
					centered = true;
					if (shift) dX = (key == UP ? 1 : -1) * widthStep;
					else dY = (key == UP ? 1 : -1) * heightStep;
				}
				if (alt && (key == (evt.DOM_VK_SUBTRACT || 109) || key == 45)) {
					handled = true;
					toggleReset(objProperties);
				}
				if (alt && (key == (evt.DOM_VK_ADD || 107) || key == 43)) {
					handled = true;
					toggleMaximized(objProperties, false);
				}
				if (alt && (key == (evt.DOM_VK_BACK_SPACE || 8)
						|| key == (evt.DOM_VK_DELETE || 46))) {
					handled = true;
					unwrap(el, objProperties);
				}
				
				if (dX != 0 || dY != 0) {
					handled = true;
					enterResizableMode(objProperties);
					dimensions = objProperties.dimensions[objProperties.currentMode];
					width = Math.max(round(dimensions.width + dX, 6), MIN_SIZE_PX);
					height = Math.max(round(dimensions.height + dY, 6), MIN_SIZE_PX);
					shrinkwrapStyle.width = width + "px";
					shrinkwrapStyle.height = height + "px";
					if (centered) {
						// hold (left + width/2) and (top + height/2) constant
						shrinkwrapStyle.left = round(dimensions.left
								+ (dimensions.width - width) / 2, 6) + "px";
						shrinkwrapStyle.top = round(dimensions.top
								+ (dimensions.height - height) / 2, 6) + "px";
					}
					if (inFlow) {
						shrinkwrapStyle.left = shrinkwrapStyle.top = "0px";
						objProperties.box.style.width = shrinkwrapStyle.width;
						objProperties.box.style.height = shrinkwrapStyle.height;
					}
					for (var s in {left: 0, top: 0, width: 0, height: 0}) {
						dimensions[s] = parseFloat(shrinkwrapStyle[s]);
					}
					dimensions.boxWidth = parseFloat(objProperties.box.style.width);
					dimensions.boxHeight = parseFloat(objProperties.box.style.height);
					writeIntrinsicDimensions(el, objProperties);
				}
				if (handled) {
					bringToFront(objProperties.shrinkwrap);
					try { evt.stopPropagation(); } catch (ex) {}
					try { evt.preventDefault(); } catch (ex) {}
				}
				objKey.activePresses--;
				return (handled ? (evt.returnValue = !(evt.cancelBubble = true)) : undefined);
			};
			addEventHandler(el, "keypress", key.onkeypress, true);
		}
		if ((evt.ctrlKey || evt.altKey) && !key.onkeyup) {
			key.onkeyup = function () {
				var key = objProperties.key;
				if (key.tmrClear) clearTimeout(key.tmrClear);
				try {
					key.tmrClear = window.setTimeout(clear,
							4 * MIN_KEY_RESIZE_INIT_DELAY);
				}
				catch (ex) { clear(); }
				
				function clear () {
					try { removeEventHandler(el, "keypress", key.onkeypress, true); } catch (ex) {}
					try { removeEventHandler(el, "keyup", key.onkeyup, true); } catch (ex) {}
					key.onkeypress = key.onkeyup = null;
				}
			};
			addEventHandler(el, "keyup", key.onkeyup, true);
		}
	}, false);
};
function addResizerEventHandlers (objResizer, objResizeDirections, objProperties) {
	addEventHandler(objResizer, "mousedown", function (evt) {
		evt = evt || window.event;
		beginDrag(evt, objResizer, objResizeDirections, objProperties);
		try { evt.stopPropagation(); } catch (ex) {}
		try { evt.preventDefault(); } catch (ex) {}
		return (evt.returnValue = !(evt.cancelBubble = true));
	}, true);
	addEventHandler(objResizer, "click", function (evt) {
		evt = evt || window.event;
		if (!objProperties.drag.movedSinceMousedown) {	// filter for real clicks
			if (evt.altKey) {
				unwrap(objProperties.element, objProperties)
			}
			else if (evt.shiftKey) {
				toggleMaximized(objProperties, false);
			}
			else {
				toggleReset(objProperties);
			}
		}
		try { evt.stopPropagation(); } catch (ex) {}
		try { evt.preventDefault(); } catch (ex) {}
		return (evt.returnValue = !(evt.cancelBubble = true));
	}, true);
};

// resize functions
function measureIntrinsicDimensions (el, objProperties) {
	var	textInput = objProperties.textInput,
			isTextInput = textInput.actual = /^textarea$/i.test(el.nodeName)
				|| (textInput.potential && /^(text|password)$/.test(el.type)),
			original = {width: el.style.width, height: el.style.height},
			textInputWidth, textInputHeight,
			ratios = {from: null, to: null},
			styles,
			extra = {width: 0, height: 0},
			extraStyles = {
				width: ["border-left-width", "padding-left", "padding-right",
					"border-right-width"],
				height: ["border-top-width", "padding-top", "padding-bottom",
					"border-bottom-width"]};
	
	// read special text input dimensioning properties
	textInputWidth = (isTextInput && textInput.widthProperty
			? parseInt(el.getAttribute(textInput.widthProperty), 10) : NaN);
	textInputHeight = (isTextInput && textInput.heightProperty
			? parseInt(el.getAttribute(textInput.heightProperty), 10) : NaN);
	
	// set to intrinsic dimensions and get current styles
	el.style.width = el.style.height = "auto !important";
	try {
		styles = ((el.ownerDocument || window.document).defaultView || window)
				.getComputedStyle(el, "");
	}
	catch (ex) {
		styles = el.currentStyle || el.style;
	}
	for (var p in extraStyles) {
		for (var i = 0; i < extraStyles[p].length; i++) {
			try {
				extra[p] += styles.getPropertyCSSValue(extraStyles[p][i])
						.getFloatValue(CSSPrimitiveValue.CSS_PX);
			}
			catch (ex) {
				extra[p] += parseFloat(getStyle(styles, extraStyles[p][i])) || 0;
			}
		}
	}
	
	// append the size translator (invisible element of 10em width and 10ex height)
	try {
		el.appendChild(sizeTranslator);
		if (sizeTranslator.parentNode !== el) throw new Error();
	}
	catch (ex) {
		el.parentNode.insertBefore(sizeTranslator, el.nextSibling);
	}
	
	// set the ratio between pixels and dimension steps
		// width
	ratios.from = objProperties.dimensions.pixelsPerWidthStep;
	ratios.to = (isNaN(textInputWidth)
		? sizeTranslator.offsetHeight/10	// ex
		: Math.max(1, el.offsetWidth - extra.width) / textInputWidth
	);
	if (!(Math.abs(ratios.from - ratios.to) <= (0.05 * ratios.from))) {
		objProperties.dimensions.pixelsPerWidthStep = ratios.to;
	}
		// height
	ratios.from = objProperties.dimensions.pixelsPerHeightStep;
	ratios.to = (isNaN(textInputHeight)
		? sizeTranslator[isTextInput ? "offsetWidth" : "offsetHeight"]/10	// em/ex
		: Math.max(1, el.offsetHeight - extra.height) / textInputHeight
	);
	if (!(Math.abs(ratios.from - ratios.to) <= (0.05 * ratios.from))) {
		objProperties.dimensions.pixelsPerHeightStep = ratios.to;
	}
	
	// remove the size translator and restore the dimensions
	try { sizeTranslator.parentNode.removeChild(sizeTranslator); } catch (ex) {}
	el.style.width = original.width;
	el.style.height = original.height;
};
function writeIntrinsicDimensions (el, objProperties) {
	var	textInput = objProperties.textInput,
			isTextInput = textInput.actual = (el.nodeName.toLowerCase() == "textarea")
				|| (textInput.potential && /^(text|password)$/.test(el.type)),
			styles, extra = {width: 0, height: 0},
			extraStyles = {width: ["border-left-width", "padding-left", "padding-right",
				"border-right-width"], height: ["border-top-width", "padding-top", "padding-bottom",
				"border-bottom-width"]};
	
	// get extra width and height (from border and padding)
	try {
		styles = ((el.ownerDocument || window.document).defaultView || window)
				.getComputedStyle(el, "");
	}
	catch (ex) {
		styles = el.currentStyle || el.style;
	}
	for (var p in extraStyles) {
		for (var i = 0; i < extraStyles[p].length; i++) {
			try {
				extra[p] += styles.getPropertyCSSValue(extraStyles[p][i])
						.getFloatValue(CSSPrimitiveValue.CSS_PX);
			}
			catch (ex) {
				extra[p] += parseFloat(getStyle(styles, extraStyles[p][i])) || 0;
			}
		}
	}
	
	// set the intrinsic dimensions
	if (isTextInput && textInput.widthProperty) {
		el.setAttribute(textInput.widthProperty, Math.max(1, Math.round(
		(el.offsetWidth - extra.width)
		/ objProperties.dimensions.pixelsPerWidthStep)));
	}
	if (isTextInput && textInput.heightProperty) {
		el.setAttribute(textInput.heightProperty, Math.max(1, Math.round(
		(el.offsetHeight - extra.height)
		/ objProperties.dimensions.pixelsPerHeightStep)));
	}
};
function beginDrag (evt, objResizer, objResizeDirections, objProperties) {
	var shrinkwrap = objProperties.shrinkwrap, drag = objProperties.drag;
	
	bringToFront(shrinkwrap);
	measureIntrinsicDimensions(shrinkwrap.firstChild, objProperties);
	shrinkwrap.className = shrinkwrap.className.replace(RESIZING_CLASS_RE, " ")
			+ " " + RESIZING_CLASS;
	objResizer.className = objResizer.className.replace(RESIZING_CLASS_RE, " ")
			+ " " + RESIZING_CLASS;
	if (drag.startX === null) drag.startX = ("pageX" in evt ? evt.pageX : evt.clientX);
	if (drag.startY === null) drag.startY = ("pageY" in evt ? evt.pageY : evt.clientY);
	drag.movedSinceMousedown = false;
	if (shrinkwrap.firstChild.nodeName.toLowerCase() == "iframe") {
		shrinkwrap.insertBefore(iframeCover, shrinkwrap.firstChild.nextSibling);
	}
	
	resizeHandlers = {
		onmousemove: function (evt) {
			evt = evt || window.event;
			continueDrag(evt, objResizeDirections, objProperties);
			try { evt.stopPropagation(); } catch (ex) {}
			try { evt.preventDefault(); } catch (ex) {}
			return (evt.returnValue = !(evt.cancelBubble = true));
		},
		onmouseup: function (evt) {
			evt = evt || window.event;
			endDrag(objResizer, objProperties, false);
			try { evt.stopPropagation(); } catch (ex) {}
			try { evt.preventDefault(); } catch (ex) {}
			return (evt.returnValue = !(evt.cancelBubble = true));
		},
		onkeypress: function (evt) {
			evt = evt || window.event;
			var key = evt.keyCode || evt.which;
			if (key == (evt.DOM_VK_ESCAPE || 27) || key == (evt.DOM_VK_CANCEL || 3)) {
				endDrag(objResizer, objProperties, true);
			}
			else if (key == (evt.DOM_VK_HELP || 6) || key == (evt.DOM_VK_F1 || 112)) {
			}
		}
	};
	for (var e in {onmousemove: 0, onmouseup: 0, onkeypress: 0}) {
		if (!drag[e]) {
			addEventHandler(window.document, e.substring(2), resizeHandlers[e],
					true);
			drag[e] = resizeHandlers[e];
		}
	}
};
function continueDrag (evt, objResizeDirections, objProperties) {
	enterResizableMode(objProperties);
	var	mode = objProperties.currentMode,	// current resizing mode
			maximized =									// whether or not the element is maximized
				(mode == MODE_MAXIMIZED),
			box = objProperties.box,				// replacement box
			shrinkwrap =								// element shrinkwrap
				objProperties.shrinkwrap,
			dimensions =								// stored (end-of-resize) dimensions
				objProperties.dimensions[mode],
			drag = objProperties.drag,				// drag properties
			aspect =										// stored (end-of-resize) aspect ratio
				dimensions.width / dimensions.height,
			xDir = objResizeDirections.x,			// horizontal resize direction (marks left/right)
			yDir = objResizeDirections.y,			// vertical resize direction (marks top/bottom)
			inFlow = !maximized && evt.altKey,	// in-flow resize
			lockedRatio = evt.shiftKey,			// locked-aspect-ratio resize
			centered =									// centered resize
				maximized || (!inFlow && evt.ctrlKey),
			centeredX = centered,					// horizontally centered resize
			centeredY = centered,					// vertically centered resize
			deltaX =										// horizontal resizer movement requested by the drag
				("pageX" in evt ? evt.pageX : evt.clientX) - drag.startX
				+ (inFlow ? dimensions.left : 0),
			deltaY =										// vertical resizer movement requested by the drag
				("pageY" in evt ? evt.pageY : evt.clientY) - drag.startY
				+ (inFlow ? dimensions.top : 0),
			minWidth =									// minimum width of the element
					MIN_SIZE_PX * (lockedRatio ? Math.max(aspect, 1) : 1),
			minHeight =									// minimum height of the element
					MIN_SIZE_PX * (lockedRatio ? Math.max(1/aspect, 1) : 1);
	
	// ensure an active drag
	drag.active = drag.movedSinceMousedown = true;
	
	// adjust deltaX and deltaY for aspect ratio locking
	if (lockedRatio) {
		// new dimensions are determined by deltaX iff vertical movement is irrelevant (e.g.,
		// 	dragging an element's vertical side) or the change would widen the aspect ratio
		if ((yDir == 0) || (xDir != 0 && (
				(((dimensions.width + xDir * deltaX)/(dimensions.height + yDir * deltaY)) > aspect)
				|| ((dimensions.height + yDir * deltaY) < 0)
				))) {
			if (yDir == 0) { yDir = 0.5 * (centered ? 2 : 1); centeredY = true; }
			deltaY = xDir * Math.ceil(yDir) * deltaX / aspect;
		}
		else {
			if (xDir == 0) { xDir = 0.5 * (centered ? 2 : 1); centeredX = true; }
			deltaX = Math.ceil(xDir) * yDir * deltaY * aspect;
		}
	}
	
	// set new left and width
	shrinkwrap.style.left = dimensions.left + (xDir < 0 || centeredX
		? Math.min(-xDir * deltaX, (dimensions.width - minWidth) / (centeredX ? 2 : 1))
		: 0
	) + "px";
	shrinkwrap.style.width =
			Math.max(dimensions.width + xDir * deltaX * (centeredX ? 2 : 1), minWidth) + "px";
	
	// set new top and height
	shrinkwrap.style.top = dimensions.top + (yDir < 0 || centeredY
		? Math.min(-yDir * deltaY, (dimensions.height - minHeight) / (centeredY ? 2 : 1))
		: 0
	) + "px";
	shrinkwrap.style.height =
			Math.max(dimensions.height + yDir * deltaY * (centeredY ? 2 : 1), minHeight) + "px";
	
	// dimension the replacement box for in-flow resize
	if (inFlow) {
		shrinkwrap.style.top = shrinkwrap.style.left = "0px";
		box.style.height = shrinkwrap.style.height;
		box.style.width = shrinkwrap.style.width;
	}
	else {
		box.style.height = dimensions.boxHeight + "px";
		box.style.width = dimensions.boxWidth + "px";
	}
};
function enterResizableMode (objProperties) {
	var	shrinkwrapStyle = objProperties.shrinkwrap.style,
			resetDimensions = objProperties.dimensions[MODE_RESET],
			resizedDimensions = objProperties.dimensions[MODE_RESIZED],
			maximizedDimensions = objProperties.dimensions[MODE_MAXIMIZED];
	if (objProperties.currentMode == MODE_RESET) {
		objProperties.previousMode = objProperties.currentMode;
		objProperties.currentMode = MODE_RESIZED;
		objProperties.resized = true;
		for (var p in resetDimensions) resizedDimensions[p] = resetDimensions[p];
	}
	else if (objProperties.currentMode == MODE_MAXIMIZED
			&& shrinkwrapStyle.width.indexOf("px") == -1) {
		shrinkwrapStyle.width = objProperties.shrinkwrap.offsetWidth + "px";
		shrinkwrapStyle.height = objProperties.shrinkwrap.offsetHeight + "px";
		for (var s in {top: 0, left: 0, height: 0, width: 0}) {
			maximizedDimensions[s] = parseFloat(shrinkwrapStyle[s]);
		}
		maximizedDimensions.boxHeight = parseFloat(objProperties.box.style.height);
		maximizedDimensions.boxWidth = parseFloat(objProperties.box.style.width);
	}
};
function endDrag (objResizer, objProperties, blnCancel) {
	var	shrinkwrap = objProperties.shrinkwrap,
			dimensions = objProperties.dimensions[objProperties.currentMode],
			drag = objProperties.drag;
	
	// remove event handlers and resize properties
	for (var e in {onmousemove: 0, onmouseup: 0, onkeypress: 0}) {
		removeEventHandler(window.document, e.substring(2), drag[e], true);
		drag[e] = null;
	}
	drag.startX = drag.startY = null;
	objResizer.className = objResizer.className.replace(RESIZING_CLASS_RE, " ");
	shrinkwrap.className = shrinkwrap.className.replace(RESIZING_CLASS_RE, " ");
	try { iframeCover.parentNode.removeChild(iframeCover); } catch (ex) {}
	
	if (drag.active) {
		drag.active = false;
		if (blnCancel) {
			for (var s in {top: 0, left: 0, height: 0, width: 0}) {
				shrinkwrap.style[s] = dimensions[s] + "px";
			}
			objProperties.box.style.height = dimensions.boxHeight + "px";
			objProperties.box.style.width = dimensions.boxWidth + "px";
		}
		else {
			for (var s in {top: 0, left: 0, height: 0, width: 0}) {
				dimensions[s] = parseFloat(shrinkwrap.style[s]);
			}
			dimensions.boxHeight = parseFloat(objProperties.box.style.height);
			dimensions.boxWidth = parseFloat(objProperties.box.style.width);
			writeIntrinsicDimensions(objProperties.element, objProperties);
		}
	}
};
function toggleMaximized (objProperties, blnRestore) {
	// if not maximized, maximize; otherwise go to resized (or reset)
	var	maximizedDimensions = objProperties.dimensions[MODE_MAXIMIZED],
			resizedDimensions = objProperties.dimensions[MODE_RESIZED],
			box = objProperties.box,
			shrinkwrapStyle = objProperties.shrinkwrap.style,
			doc = box.ownerDocument || window.document;
	if (objProperties.currentMode != MODE_MAXIMIZED) {
		try {
			if (maximizedProperties) toggleMaximized(maximizedProperties, false);
			(doc.body || doc.getElementsByTagName("body")[0]
					|| (doc.documentElement || doc.lastChild).lastChild)
					.appendChild(
						box.parentNode.replaceChild(objProperties.swap = box.cloneNode(false), box)
					);
			box.className = box.className.replace(MAXIMIZED_CLASS_RE, " ")
					+ " " + MAXIMIZED_CLASS;
			maximizedProperties = objProperties;
			if (blnRestore) {
				for (var s in {top: 0, left: 0, height: 0, width: 0}) {
					shrinkwrapStyle[s] = (maximizedDimensions[s] + "")
							.replace(APPEND_PX_RE, APPEND_PX_RE_FN);
				}
			}
			else {
				objProperties.previousMode = objProperties.currentMode;
				objProperties.currentMode = MODE_MAXIMIZED;
				// set {top,left,width,height} to {0,0,100%,100%} and maximizedDimensions to match
				var dims = {top: 0, left: 0, width: "100%", height: "100%"};
				for (var s in dims) shrinkwrapStyle[s] = maximizedDimensions[s] = dims[s];
			}
		} catch (ex) {}
	}
	else {
		if (!blnRestore) {
			objProperties.previousMode = objProperties.currentMode;
			objProperties.currentMode = (objProperties.resized ? MODE_RESIZED : MODE_RESET);
		}
		box.className = box.className.replace(MAXIMIZED_CLASS_RE, " ");
		maximizedProperties = null;
		for (var s in {top: 0, left: 0, height: 0, width: 0}) {
			shrinkwrapStyle[s] = resizedDimensions[s] + "px";
		}
		box.style.height = resizedDimensions.boxHeight + "px";
		box.style.width = resizedDimensions.boxWidth + "px";
		try {
			objProperties.swap.parentNode.replaceChild(box, objProperties.swap);
			objProperties.swap = null;
		} catch (ex) {}
	}
};
function toggleReset (objProperties) {
	// if not reset, reset; otherwise toggle with last mode (default to maximized)
	var	resetDimensions = objProperties.dimensions[MODE_RESET],
			previousDimensions = objProperties.dimensions[objProperties.previousMode],
			boxStyle = objProperties.box.style,
			shrinkwrapStyle = objProperties.shrinkwrap.style;
	if (objProperties.currentMode != MODE_RESET) {
		if (objProperties.currentMode == MODE_MAXIMIZED) {
			toggleMaximized(objProperties, true);
		}
		objProperties.previousMode = objProperties.currentMode;
		objProperties.currentMode = MODE_RESET;
		for (var s in {top: 0, left: 0, height: 0, width: 0}) {
			shrinkwrapStyle[s] = resetDimensions[s] + "px";
		}
		boxStyle.height = resetDimensions.boxHeight + "px";
		boxStyle.width = resetDimensions.boxWidth + "px";
	}
	else {
		if (objProperties.previousMode == MODE_MAXIMIZED
				|| objProperties.previousMode == MODE_RESET) {
			toggleMaximized(objProperties, true);
			objProperties.previousMode = MODE_MAXIMIZED;
		}
		else {
			for (var s in {top: 0, left: 0, height: 0, width: 0}) {
				shrinkwrapStyle[s] = previousDimensions[s] + "px";
			}
			boxStyle.height = previousDimensions.boxHeight + "px";
			boxStyle.width = previousDimensions.boxWidth + "px";
		}
		objProperties.currentMode = objProperties.previousMode;
		objProperties.previousMode = MODE_RESET;
	}
};

function populateHelp (arrTags) {
	try {
		var	document = window.document,
				table = helpFrame.getElementsByTagName("tbody")[0],
				display = table.parentNode.style.display;
		while (table.firstChild) table.removeChild(table.lastChild);
		for (var tag, newRow, el, i = 0; i < arrTags.length; i++) {
			tag = getTagAndType(arrTags[i]);
			newRow = table.appendChild(document.createElement("tr"));
			newRow.appendChild(document.createElement("th"))
					.appendChild(document.createTextNode(arrTags[i].toUpperCase()));
			el = document.createElement(tag[0]);
			if (tag[1]) el.type = tag[1];
			wrap(newRow.appendChild(document.createElement("td")).appendChild(el));
		}
		table.parentNode.style.display = "none";
		table.parentNode.style.display = display;
	} catch (ex) { alert(ex); }
};
function closeHelp (evt) {
	if (maximizedProperties) toggleMaximized(maximizedProperties, false);
	maximizedProperties = null;
	try { helpFrame.parentNode.removeChild(helpFrame); } catch (ex) {}
	try { evt.preventDefault(); } catch (ex) {}
	return false;
};

// flesh out the global objects and write class styles
(function () {
	var document = window.document, tmp;
	
	// wrap
	wrapTemplate = document.createElement(REPLACEMENT_TAG);
	wrapTemplate.className = REPLACEMENT_CLASS;
		// placeholder
	tmp = wrapTemplate.appendChild(document.createElement(PLACEHOLDER_TAG));
	tmp.className = PLACEHOLDER_CLASS;
		// shrinkwrap
	tmp = tmp.appendChild(document.createElement(SHRINKWRAP_TAG));
	tmp.className = SHRINKWRAP_CLASS;
	tmp.style.top = tmp.style.left = "0px";
		// resizers
	for (var i = 0; i < 8; i++) {
		tmp.appendChild(document.createElement(RESIZER_TAG)).className =
				RESIZER_CLASS + " " + RESIZER_SUBCLASSES[i];
		applyStyleString(tmp.lastChild, RESIZER_STYLES[i]);
		tmp.lastChild.setAttribute("title", RESIZER_TITLE);
	}
	
	// iframe cover and size translator
	iframeCover = document.createElement("img");
	iframeCover.setAttribute("src", 	// 1 pixel 100% transparent PNG
			"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAABGdBTUEAAK%2FINw"
			+ "WK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAAQSURBVHjaYvj%2F%2Fz8DQIABAA"
			+ "j8Av7bok0WAAAAAElFTkSuQmCC");
	iframeCover.setAttribute("alt", "");
	applyStyleString(iframeCover, [
		"position:absolute !important;top:0 !important;left:0 !important;",
		"width:100% !important;height:100% !important;",
		"margin:0 !important;border:0 !important;"
	].join(""));
	sizeTranslator = iframeCover.cloneNode(true);
	applyStyleString(sizeTranslator, "font-size:inherit !important;"
			+ "width:10em !important;height:10ex !important;");
	
	// help frame
	helpFrame = document.createElement("div");
	helpFrame.className = HELP_CLASS;
	helpFrame.style.background = "transparent "	// 1 pixel 20% transparent white PNG
			+ "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfF"
			+ "cSJAAAALHRFWHRDcmVhdGlvbiBUaW1lAFdlZCAxNiBOb3YgMjAwNSAxMzoxOTowMyAt"
			+ "MDUwMKTaEqIAAAAHdElNRQfVCxATHSx6OlxkAAAACXBIWXMAAB7CAAAewgFu0HU%2BA"
			+ "AAABGdBTUEAALGPC%2FxhBQAAAA1JREFUeNpj%2BP%2F%2F%2FxkACcgDyrXl4xsAAA"
			+ "AASUVORK5CYII%3D) repeat";
	helpFrame.innerHTML = HELP_CONTENT;
	addEventHandler(helpFrame.getElementsByTagName("a")[0], "click", closeHelp,
			false);
	
	// base style selectors
	var sel_REPLACEMENT = REPLACEMENT_TAG + "." + REPLACEMENT_CLASS;
	var sel_PLACEHOLDER = sel_REPLACEMENT + " > " + PLACEHOLDER_TAG + "." + PLACEHOLDER_CLASS;
	var sel_SHRINKWRAP = sel_PLACEHOLDER + " > " + SHRINKWRAP_TAG + "." + SHRINKWRAP_CLASS;
	var sel_RESIZER = sel_SHRINKWRAP + " > " + RESIZER_TAG + "." + RESIZER_CLASS;
	var sel_RESIZER_BAR = sel_RESIZER + "." + RESIZER_BAR_CLASS;
	var sel_RESIZER_HANDLE = sel_RESIZER + "." + RESIZER_HANDLE_CLASS;
	// maximized style selectors
	var sel_MAXIMIZED = sel_REPLACEMENT + "." + MAXIMIZED_CLASS;
	// dynamic style selectors
	var sel_SHRINKWRAP_HOVER_RESIZER_HANDLE = sel_SHRINKWRAP + ":hover > "
			+ RESIZER_TAG + "." + RESIZER_CLASS + "." + RESIZER_HANDLE_CLASS;
	var sel_SHRINKWRAP_RESIZING_RESIZER_BAR = sel_SHRINKWRAP + "." + RESIZING_CLASS + " > "
			+ RESIZER_TAG + "." + RESIZER_CLASS + "." + RESIZER_BAR_CLASS;
	var sel_SHRINKWRAP_RESIZING_RESIZER_HANDLE = sel_SHRINKWRAP + "." + RESIZING_CLASS + " > "
			+ RESIZER_TAG + "." + RESIZER_CLASS + "." + RESIZER_HANDLE_CLASS;
	var sel_RESIZER_BAR_HOVER = sel_RESIZER_BAR + ":hover";
	var sel_RESIZER_HANDLE_HOVER = sel_RESIZER_HANDLE + ":hover";
	var sel_RESIZER_BAR_RESIZING = sel_RESIZER_BAR + "." + RESIZING_CLASS;
	var sel_RESIZER_HANDLE_RESIZING = sel_RESIZER_HANDLE + "." + RESIZING_CLASS;
	var sel_HELP = "." + HELP_CLASS;
	var sel_WINDOW = "." + WINDOW_CLASS;
	var sel_WINDOW_TITLE = sel_WINDOW + " > ." + WINDOW_TITLE_CLASS;
	var sel_WINDOW_CONTENT = sel_WINDOW + " > ." + WINDOW_CONTENT_CLASS;
	
	// insert the base styles
	addGlobalStyle(""
		// base wrap styles
		+ sel_REPLACEMENT + " { margin: 0; border: 0 !important; padding: 0 !important;"
				+ " vertical-align: inherit !important; }\n"
		+ sel_PLACEHOLDER + " { position: relative !important;"
				+ " top: 0 !important; right: 0 !important; bottom: 0 !important; left: 0 !important;"
				+ " width: 100% !important; height: 100% !important;"
				+ " margin: 0 !important; border: 0 !important; padding: 0 !important;"
				+ " overflow: visible !important; }\n"
		+ sel_SHRINKWRAP + " { position: absolute !important;"
				+ " min-height: " + MIN_SIZE_PX + "px !important;"
				+ " min-width: " + MIN_SIZE_PX + "px !important;"
				+ " margin: 0 !important; border: 0 !important; padding: 0 !important;"
				+ " overflow: visible !important; }\n"
		+ sel_RESIZER + " { position: absolute !important;"
				+ " margin: 0 !important; padding: 0 !important; z-index: 1 !important; }\n"
		+ sel_RESIZER_BAR + " { border: 0px solid; border-color: transparent !important;"
				+ " background: transparent !important; opacity: 0.8; }\n"
		+ sel_RESIZER_HANDLE + " { visibility: hidden !important; height: 10px; width: 10px;"
				+ " border: 1px solid black; background: red; -moz-border-radius: 25%;"
				+ " z-index: 2 !important; opacity: 0.4; }\n"
		// maximized wrap styles
		+ sel_MAXIMIZED + " { position: absolute !important; position: fixed !important;"
				+ " top: 0 !important; right: 0 !important; bottom: 0 !important; left: 0 !important;"
				+ " width: auto !important; height: auto !important;"
				+ " margin: " + MAXIMIZED_MARGIN + " !important; }\n"
		// dynamic wrap styles
		+ sel_SHRINKWRAP_HOVER_RESIZER_HANDLE + ", " + sel_SHRINKWRAP_RESIZING_RESIZER_HANDLE + " {"
				+ " visibility: visible !important; }\n"
		+ sel_RESIZER_BAR_HOVER + ", " + sel_RESIZER_BAR_RESIZING + " {"
				+ " border-color: blue !important; }\n"
		+ sel_RESIZER_HANDLE_HOVER + ", " + sel_RESIZER_HANDLE_RESIZING + " {"
				+ " height: 20px; width: 20px; margin: -4px !important; z-index: 3 !important;"
				+ " opacity: 0.8; }\n"
			// reset styles on non-active resizers during a resize
		+ sel_SHRINKWRAP_RESIZING_RESIZER_BAR + ":not(." + RESIZING_CLASS + ")" + " {"
				+ " border-color: transparent !important; }\n"
		+ sel_SHRINKWRAP_RESIZING_RESIZER_HANDLE + ":not(." + RESIZING_CLASS + ")" + " {"
				+ " height: 10px; width: 10px; margin: 0 !important; z-index: 2 !important;"
				+ " opacity: 0.4; }\n"
		// help frame styles
		+ sel_HELP + " { position: absolute; position: fixed; left: 0; top: 0;"
				+ " width: 100%; height: 100%; margin: 0; border: 0; padding: 0; }"
		+ sel_WINDOW + " { position: absolute;"
				+ " top: 0; right: 0; bottom: 0; left: 0;"
				+ " width: 80%; min-width: 240px; height: 80%; min-height: 180px;"
				+ " margin: auto; border: medium solid blue; padding: 0;"
				+ " background-color: white; text-align: left; }"
		+ sel_WINDOW_TITLE + " { position: absolute; top: 0; left: 0; right: 0;"
				+ " padding: 0 0 3px 0; font: normal 14px/100% Tahoma, sans-serif;"
				+ " color: white; background-color: blue; }"
		+ sel_WINDOW_TITLE + " > h1" + " { display: inline;"
				+ " margin: 0; border: 0; padding: 0;"
				+ " font: normal 20px/120% Tahoma, sans-serif; }"
		+ sel_WINDOW_TITLE + " > a" + " { float: right; color: inherit; }"
		+ sel_WINDOW_CONTENT + " { position: absolute;"
				+ " top: 27px; bottom: 0; left: 0; right: 0; overflow: auto; }"
		+ sel_WINDOW_CONTENT + " > h2" + " {"
				+ " margin: 0.5em 0 0 0; padding: 0; font-size: 1.4em; }"
		+ sel_WINDOW_CONTENT + " > p" + " { margin: 1em 0 0 0; padding: 0; }"
		+ sel_WINDOW_CONTENT + " > dl" + " { margin: 0; padding: 0; }"
		+ sel_WINDOW_CONTENT + " > dl > dt" + " { display: compact;"
				+ " margin-right: 0; font-weight: bold; font-style: italic; }"
		+ sel_WINDOW_CONTENT + " > dl > dd" + " {"
				+ " display: inline; margin-left: 7em; }"
		+ sel_WINDOW_CONTENT + " kbd" + " { font-weight: bold; }"
		+ sel_WINDOW_CONTENT + " th" + " {"
				+ " text-align: right; vertical-align: top; }"
	, true);
})();

// pre-wrap existing elements and automatically wrap new ones
(function (evt) {
	function wrapAll () {
		var tags = getTags(), tag, elements, length;
		try {
			for (var i = tags.length - 1; i >= 0; i--) {
				tag = getTagAndType(tags[i]);
				elements = window.document.getElementsByTagName(tag[0]);
				length = elements.length;
				for (var j = 0; j < length; j++) {
					if ((!tag[1] || ("" + elements[j].type).toLowerCase() == tag[1])
							&& !isWrapped(elements[j]) && elements[j].offsetWidth > 0
							&& elements[j].offsetHeight > 0) {
						wrap(elements[j]);
					}
				}
			}
		} catch (ex) {}
	};
	function autowrap (evt) {
		evt = evt || window.event;
		var tags = getTags(), tag, target = evt.target || evt.srcElement,
				type = target.nodeName.toLowerCase(), url = null;
		if (target.nodeType == 3) {
			target = target.parentNode;
			type = target.nodeName.toLowerCase();
		}
		for (var i = 0; i < tags.length; i++) {
			tag = getTagAndType(tags[i]);
			if (type == tag[0] && (!tag[1] || ("" + target.type).toLowerCase() == tag[1])) {
				if (!isWrapped(target) && target.offsetWidth > 0
						&& target.offsetHeight > 0) {
					wrap(target);
				}
				break;
			}
		}
	};
	
	if (!addEventHandler(window, "load", wrapAll, true)) wrapAll();
	addEventHandler(window.document, "mouseover", autowrap, false);
})();

})();