docs: function(evt) {
  		if (evt.button === 2) {return;}
  		var tempJustSelected = justSelected;
  		justSelected = null;
  		if (!started) {return;}
  		var pt = svgedit.math.transformPoint(evt.pageX, evt.pageY, root_sctm),
  			mouse_x = pt.x * current_zoom,
  			mouse_y = pt.y * current_zoom,
  			x = mouse_x / current_zoom,
  			y = mouse_y / current_zoom,
  			element = svgedit.utilities.getElem(getId()),
  			keep = false;
  
  		var real_x = x;
  		var real_y = y;
  
  		// TODO: Make true when in multi-unit mode
  		var useUnit = false; // (curConfig.baseUnit !== 'px');
  		started = false;
  		var attrs, t;
  		switch (current_mode) {
  			// intentionally fall-through to select here
  			case "resize":
  			case "multiselect":
  				if (rubberBox != null) {
  					rubberBox.setAttribute("display", "none");
  					curBBoxes = [];
  				}
  				current_mode = "select";
  			case "select":
  				if (selectedElements[0] != null) {
  					// if we only have one selected element
  					if (selectedElements[1] == null) {
  						// set our current stroke/fill properties to the element's
  						var selected = selectedElements[0];
  						switch ( selected.tagName ) {
  							case "g":
  							case "use":
  							case "image":
  							case "foreignObject":
  								break;
  							default:
  								cur_properties.fill = selected.getAttribute("fill");
  								cur_properties.fill_opacity = selected.getAttribute("fill-opacity");
  								cur_properties.stroke = selected.getAttribute("stroke");
  								cur_properties.stroke_opacity = selected.getAttribute("stroke-opacity");
  								cur_properties.stroke_width = selected.getAttribute("stroke-width");
  								cur_properties.stroke_dasharray = selected.getAttribute("stroke-dasharray");
  								cur_properties.stroke_linejoin = selected.getAttribute("stroke-linejoin");
  								cur_properties.stroke_linecap = selected.getAttribute("stroke-linecap");
  						}
  
  						if (selected.tagName == "text") {
  							cur_text.font_size = selected.getAttribute("font-size");
  							cur_text.font_family = selected.getAttribute("font-family");
  						}
  						selectorManager.requestSelector(selected).showGrips(true);
  						
  						// This shouldn't be necessary as it was done on mouseDown...
  //							call("selected", [selected]);
  					}
  					// always recalculate dimensions to strip off stray identity transforms
  					recalculateAllSelectedDimensions();
  					// if it was being dragged/resized
  					if (real_x != r_start_x || real_y != r_start_y) {
  						var i, len = selectedElements.length;
  						for (i = 0; i < len; ++i) {
  							if (selectedElements[i] == null) {break;}
  							if (!selectedElements[i].firstChild) {
  								// Not needed for groups (incorrectly resizes elems), possibly not needed at all?
  								selectorManager.requestSelector(selectedElements[i]).resize();
  							}
  						}
  					}
  					// no change in position/size, so maybe we should move to pathedit
  					else {
  						t = evt.target;
  						if (selectedElements[0].nodeName === "path" && selectedElements[1] == null) {
  							pathActions.select(selectedElements[0]);
  						} // if it was a path
  						// else, if it was selected and this is a shift-click, remove it from selection
  						else if (evt.shiftKey) {
  							if (tempJustSelected != t) {
  								canvas.removeFromSelection([t]);
  							}
  						}
  					} // no change in mouse position
  					
  					// Remove non-scaling stroke
  					if (svgedit.browser.supportsNonScalingStroke()) {
  						var elem = selectedElements[0];
  						if (elem) {
  							elem.removeAttribute('style');
  							svgedit.utilities.walkTree(elem, function(elem) {
  								elem.removeAttribute('style');
  							});
  						}
  					}
  
  				}
  				return;
  			case "zoom":
  				if (rubberBox != null) {
  					rubberBox.setAttribute("display", "none");
  				}
  				var factor = evt.shiftKey ? 0.5 : 2;
  				call("zoomed", {
  					'x': Math.min(r_start_x, real_x),
  					'y': Math.min(r_start_y, real_y),
  					'width': Math.abs(real_x - r_start_x),
  					'height': Math.abs(real_y - r_start_y),
  					'factor': factor
  				});
  				return;
  			case "fhpath":
  				// Check that the path contains at least 2 points; a degenerate one-point path
  				// causes problems.
  				// Webkit ignores how we set the points attribute with commas and uses space
  				// to separate all coordinates, see https://bugs.webkit.org/show_bug.cgi?id=29870
  				sumDistance = 0;
  				controllPoint2 = {x:0, y:0};
  				controllPoint1 = {x:0, y:0};
  				start = {x:0, y:0};
  				end = {x:0, y:0};
  				var coords = element.getAttribute('points');
  				var commaIndex = coords.indexOf(',');
  				if (commaIndex >= 0) {
  					keep = coords.indexOf(',', commaIndex+1) >= 0;
  				} else {
  					keep = coords.indexOf(' ', coords.indexOf(' ')+1) >= 0;
  				}
  				if (keep) {
  					element = pathActions.smoothPolylineIntoPath(element);
  				}
  				break;
  			case "line":
  				attrs = $(element).attr(["x1", "x2", "y1", "y2"]);
  				keep = (attrs.x1 != attrs.x2 || attrs.y1 != attrs.y2);
  				break;
  			case "foreignObject":
  			case "square":
  			case "rect":
  			case "image":
  				attrs = $(element).attr(["width", "height"]);
  				// Image should be kept regardless of size (use inherit dimensions later)
  				keep = (attrs.width != 0 || attrs.height != 0) || current_mode === "image";
  				break;
  			case "circle":
  				keep = (element.getAttribute('r') != 0);
  				break;
  			case "ellipse":
  				attrs = $(element).attr(["rx", "ry"]);
  				keep = (attrs.rx != null || attrs.ry != null);
  				break;
  			case "fhellipse":
  				if ((freehand.maxx - freehand.minx) > 0 &&
  					(freehand.maxy - freehand.miny) > 0) {
  					element = addSvgElementFromJson({
  						"element": "ellipse",
  						"curStyles": true,
  						"attr": {
  							"cx": (freehand.minx + freehand.maxx) / 2,
  							"cy": (freehand.miny + freehand.maxy) / 2,
  							"rx": (freehand.maxx - freehand.minx) / 2,
  							"ry": (freehand.maxy - freehand.miny) / 2,
  							"id": getId()
  						}
  					});
  					call("changed",[element]);
  					keep = true;
  				}
  				break;
  			case "fhrect":
  				if ((freehand.maxx - freehand.minx) > 0 &&
  					(freehand.maxy - freehand.miny) > 0) {
  					element = addSvgElementFromJson({
  						"element": "rect",
  						"curStyles": true,
  						"attr": {
  							"x": freehand.minx,
  							"y": freehand.miny,
  							"width": (freehand.maxx - freehand.minx),
  							"height": (freehand.maxy - freehand.miny),
  							"id": getId()
  						}
  					});
  					call("changed",[element]);
  					keep = true;
  				}
  				break;
  			case "text":
  				keep = true;
  				selectOnly([element]);
  				textActions.start(element);
  				break;
  			case "path":
  				// set element to null here so that it is not removed nor finalized
  				element = null;
  				// continue to be set to true so that mouseMove happens
  				started = true;
  				
  				var res = pathActions.mouseUp(evt, element, mouse_x, mouse_y);
  				element = res.element;
  				keep = res.keep;
  				break;
  			case "pathedit":
  				keep = true;
  				element = null;
  				pathActions.mouseUp(evt);
  				break;
  			case "textedit":
  				keep = false;
  				element = null;
  				textActions.mouseUp(evt, mouse_x, mouse_y);
  				break;
  			case "rotate":
  				keep = true;
  				element = null;
  				current_mode = "select";
  				var batchCmd = canvas.undoMgr.finishUndoableChange();
  				if (!batchCmd.isEmpty()) { 
  					addCommandToHistory(batchCmd);
  				}
  				// perform recalculation to weed out any stray identity transforms that might get stuck
  				recalculateAllSelectedDimensions();
  				call("changed", selectedElements);
  				break;
  			default:
  				// This could occur in an extension
  				break;
  		}
  		
  		var ext_result = runExtensions("mouseUp", {
  			event: evt,
  			mouse_x: mouse_x,
  			mouse_y: mouse_y
  		}, true);
  		
  		$.each(ext_result, function(i, r) {
  			if (r) {
  				keep = r.keep || keep;
  				element = r.element;
  				started = r.started || started;
  			}
  		});
  		
  		if (!keep && element != null) {
  			getCurrentDrawing().releaseId(getId());
  			element.parentNode.removeChild(element);
  			element = null;
  			
  			t = evt.target;
  			
  			// if this element is in a group, go up until we reach the top-level group 
  			// just below the layer groups
  			// TODO: once we implement links, we also would have to check for <a> elements
  			while (t.parentNode.parentNode.tagName == "g") {
  				t = t.parentNode;
  			}
  			// if we are not in the middle of creating a path, and we've clicked on some shape, 
  			// then go to Select mode.
  			// WebKit returns <div> when the canvas is clicked, Firefox/Opera return <svg>
  			if ( (current_mode != "path" || !drawn_path) &&
  				t.parentNode.id != "selectorParentGroup" &&
  				t.id != "svgcanvas" && t.id != "svgroot") 
  			{
  				// switch into "select" mode if we've clicked on an element
  				canvas.setMode("select");
  				selectOnly([t], true);
  			}
  			
  		} else if (element != null) {
  			canvas.addedNew = true;
  			
  			if (useUnit) {svgedit.units.convertAttrs(element);}
  			
  			var ani_dur = 0.2, c_ani;
  			if (opac_ani.beginElement && element.getAttribute('opacity') != cur_shape.opacity) {
  				c_ani = $(opac_ani).clone().attr({
  					to: cur_shape.opacity,
  					dur: ani_dur
  				}).appendTo(element);
  				try {
  					// Fails in FF4 on foreignObject
  					c_ani[0].beginElement();
  				} catch(e){}
  			} else {
  				ani_dur = 0;
  			}
  			
  			// Ideally this would be done on the endEvent of the animation,
  			// but that doesn't seem to be supported in Webkit
  			setTimeout(function() {
  				if (c_ani) {c_ani.remove();}
  				element.setAttribute("opacity", cur_shape.opacity);
  				element.setAttribute("style", "pointer-events:inherit");
  				cleanupElement(element);
  				if (current_mode === "path") {
  					pathActions.toEditMode(element);
  				} else if (curConfig.selectNew) {
  					selectOnly([element], true);
  				}
  				// we create the insert command that is stored on the stack
  				// undo means to call cmd.unapply(), redo means to call cmd.apply()
  				addCommandToHistory(new svgedit.history.InsertElementCommand(element));
  				
  				call("changed",[element]);
  			}, ani_dur * 1000);
  		}
  		
  		startTransform = null;
  	}