]> source.dussan.org Git - jquery-ui.git/commitdiff
Merge branch 'master' into interactions interactions
authorMike Sherov <mike.sherov@gmail.com>
Mon, 10 Feb 2014 22:43:24 +0000 (14:43 -0800)
committerMike Sherov <mike.sherov@gmail.com>
Mon, 10 Feb 2014 22:43:24 +0000 (14:43 -0800)
Conflicts:
demos/draggable/constrain-movement.html
demos/draggable/cursor-style.html
demos/draggable/default.html
demos/draggable/events.html
demos/draggable/handle.html
demos/draggable/revert.html
demos/draggable/visual-feedback.html
demos/droppable/default.html
grunt.js
tests/unit/draggable/draggable.html
tests/unit/draggable/draggable_core.js
tests/unit/draggable/draggable_options.js
tests/unit/draggable/draggable_test_helpers.js
tests/unit/droppable/droppable.html
tests/unit/droppable/droppable_options.js
ui/draggable.js
ui/jquery.ui.droppable.js
ui/sortable.js

26 files changed:
1  2 
Gruntfile.js
demos/draggable/constrain-movement.html
demos/draggable/cursor-style.html
demos/draggable/default.html
demos/draggable/delay-start.html
demos/draggable/events.html
demos/draggable/handle.html
demos/draggable/map.html
demos/draggable/revert.html
demos/draggable/visual-feedback.html
demos/droppable/default.html
demos/interaction/box.html
demos/interaction/default.html
tests/unit/draggable/draggable.html
tests/unit/draggable/draggable_core.js
tests/unit/draggable/draggable_options.js
tests/unit/draggable/draggable_test_helpers.js
tests/unit/droppable/droppable.html
tests/unit/droppable/droppable_options.js
tests/unit/sortable/sortable.html
tests/unit/sortable/sortable_common.js
tests/unit/sortable/sortable_events.js
ui/draggable.js
ui/droppable.js
ui/interaction.js
ui/sortable.js

diff --cc Gruntfile.js
index 0000000000000000000000000000000000000000,545329df039ab9a7d40473acd730d9299d62bbf6..ac9fa9cb8e9cc32434e8f80490a4f2138c311959
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,230 +1,231 @@@
+ module.exports = function( grunt ) {
+ "use strict";
+ var
+       // files
+       coreFiles = [
+               "core.js",
+               "widget.js",
+               "mouse.js",
++              "interaction.js",
+               "draggable.js",
+               "droppable.js",
+               "resizable.js",
+               "selectable.js",
+               "sortable.js",
+               "effect.js"
+       ],
+       uiFiles = coreFiles.map(function( file ) {
+               return "ui/" + file;
+       }).concat( expandFiles( "ui/*.js" ).filter(function( file ) {
+               return coreFiles.indexOf( file.substring( 3 ) ) === -1;
+       }) ),
+       allI18nFiles = expandFiles( "ui/i18n/*.js" ),
+       cssFiles = [
+               "core",
+               "accordion",
+               "autocomplete",
+               "button",
+               "datepicker",
+               "dialog",
+               "menu",
+               "progressbar",
+               "resizable",
+               "selectable",
+               "selectmenu",
+               "slider",
+               "spinner",
+               "tabs",
+               "tooltip",
+               "theme"
+       ].map(function( component ) {
+               return "themes/base/" + component + ".css";
+       }),
+       // minified files
+       minify = {
+               options: {
+                       preserveComments: false
+               },
+               main: {
+                       options: {
+                               banner: createBanner( uiFiles )
+                       },
+                       files: {
+                               "dist/jquery-ui.min.js": "dist/jquery-ui.js"
+                       }
+               },
+               i18n: {
+                       options: {
+                               banner: createBanner( allI18nFiles )
+                       },
+                       files: {
+                               "dist/i18n/jquery-ui-i18n.min.js": "dist/i18n/jquery-ui-i18n.js"
+                       }
+               }
+       },
+       compareFiles = {
+               all: [
+                       "dist/jquery-ui.js",
+                       "dist/jquery-ui.min.js"
+               ]
+       };
+ function mapMinFile( file ) {
+       return "dist/" + file.replace( /\.js$/, ".min.js" ).replace( /ui\//, "minified/" );
+ }
+ function expandFiles( files ) {
+       return grunt.util._.pluck( grunt.file.expandMapping( files ), "src" ).map(function( values ) {
+               return values[ 0 ];
+       });
+ }
+ uiFiles.concat( allI18nFiles ).forEach(function( file ) {
+       minify[ file ] = {
+               options: {
+                       banner: createBanner()
+               },
+               files: {}
+       };
+       minify[ file ].files[ mapMinFile( file ) ] = file;
+ });
+ uiFiles.forEach(function( file ) {
+       // TODO this doesn't do anything until https://github.com/rwldrn/grunt-compare-size/issues/13
+       compareFiles[ file ] = [ file, mapMinFile( file ) ];
+ });
+ // grunt plugins
+ grunt.loadNpmTasks( "grunt-contrib-jshint" );
+ grunt.loadNpmTasks( "grunt-contrib-uglify" );
+ grunt.loadNpmTasks( "grunt-contrib-concat" );
+ grunt.loadNpmTasks( "grunt-contrib-qunit" );
+ grunt.loadNpmTasks( "grunt-contrib-csslint" );
+ grunt.loadNpmTasks( "grunt-jscs-checker" );
+ grunt.loadNpmTasks( "grunt-html" );
+ grunt.loadNpmTasks( "grunt-compare-size" );
+ grunt.loadNpmTasks( "grunt-git-authors" );
+ grunt.loadNpmTasks( "grunt-esformatter" );
+ // local testswarm and build tasks
+ grunt.loadTasks( "build/tasks" );
+ function stripDirectory( file ) {
+       return file.replace( /.+\/(.+?)>?$/, "$1" );
+ }
+ function createBanner( files ) {
+       // strip folders
+       var fileNames = files && files.map( stripDirectory );
+       return "/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - " +
+               "<%= grunt.template.today('isoDate') %>\n" +
+               "<%= pkg.homepage ? '* ' + pkg.homepage + '\\n' : '' %>" +
+               (files ? "* Includes: " + fileNames.join(", ") + "\n" : "") +
+               "* Copyright <%= grunt.template.today('yyyy') %> <%= pkg.author.name %>;" +
+               " Licensed <%= _.pluck(pkg.licenses, 'type').join(', ') %> */\n";
+ }
+ grunt.initConfig({
+       pkg: grunt.file.readJSON( "package.json" ),
+       files: {
+               dist: "<%= pkg.name %>-<%= pkg.version %>"
+       },
+       compare_size: compareFiles,
+       concat: {
+               ui: {
+                       options: {
+                               banner: createBanner( uiFiles ),
+                               stripBanners: {
+                                       block: true
+                               }
+                       },
+                       src: uiFiles,
+                       dest: "dist/jquery-ui.js"
+               },
+               i18n: {
+                       options: {
+                               banner: createBanner( allI18nFiles )
+                       },
+                       src: allI18nFiles,
+                       dest: "dist/i18n/jquery-ui-i18n.js"
+               },
+               css: {
+                       options: {
+                               banner: createBanner( cssFiles ),
+                               stripBanners: {
+                                       block: true
+                               }
+                       },
+                       src: cssFiles,
+                       dest: "dist/jquery-ui.css"
+               }
+       },
+       jscs: {
+               // datepicker, sortable, resizable and draggable are getting rewritten, ignore until that's done
+               ui: [ "ui/*.js", "!ui/datepicker.js", "!ui/sortable.js", "!ui/resizable.js", "!ui/draggable.js" ],
+               // TODO enable this once we have a tool that can help with fixing formatting of existing files
+               // tests: "tests/unit/**/*.js",
+               grunt: "Gruntfile.js"
+       },
+       uglify: minify,
+       htmllint: {
+               // ignore files that contain invalid html, used only for ajax content testing
+               all: grunt.file.expand( [ "demos/**/*.html", "tests/**/*.html" ] ).filter(function( file ) {
+                       return !/(?:ajax\/content\d\.html|tabs\/data\/test\.html|tests\/unit\/core\/core.*\.html)/.test( file );
+               })
+       },
+       qunit: {
+               files: expandFiles( "tests/unit/**/*.html" ).filter(function( file ) {
+                       // disabling everything that doesn't (quite) work with PhantomJS for now
+                       // TODO except for all|index|test, try to include more as we go
+                       return !( /(all|index|test|dialog|tooltip)\.html$/ ).test( file );
+               })
+       },
+       jshint: {
+               options: {
+                       jshintrc: true
+               },
+               all: [
+                       "ui/*.js",
+                       "Gruntfile.js",
+                       "build/**/*.js",
+                       "tests/unit/**/*.js"
+               ]
+       },
+       csslint: {
+               base_theme: {
+                       src: "themes/base/*.css",
+                       options: {
+                               csslintrc: ".csslintrc"
+                       }
+               }
+       },
+       esformatter: {
+               options: {
+                       preset: "jquery"
+               },
+               ui: "ui/*.js",
+               tests: "tests/unit/**/*.js",
+               build: {
+                       options: {
+                               skipHashbang: true
+                       },
+                       src: "build/**/*.js"
+               },
+               grunt: "Gruntfile.js"
+       }
+ });
+ grunt.registerTask( "default", [ "lint", "test" ]);
+ grunt.registerTask( "lint", [ "asciilint", "jshint", "jscs", "csslint", "htmllint" ]);
+ grunt.registerTask( "test", [ "qunit" ]);
+ grunt.registerTask( "sizer", [ "concat:ui", "uglify:main", "compare_size:all" ]);
+ grunt.registerTask( "sizer_all", [ "concat:ui", "uglify", "compare_size" ]);
+ };
index c29576e50225ef482222f49c481abf764fa2a694,1dcab0213fc0c8171b2315a3592941bf08fbec2c..e46fb7b27bae1ee42e3190e43836e8870bf45f9a
@@@ -3,12 -3,12 +3,12 @@@
  <head>
        <meta charset="utf-8">
        <title>jQuery UI Draggable - Constrain movement</title>
-       <link rel="stylesheet" href="../../themes/base/jquery.ui.all.css">
-       <script src="../../jquery-1.9.1.js"></script>
-       <script src="../../ui/jquery.ui.core.js"></script>
-       <script src="../../ui/jquery.ui.widget.js"></script>
-       <script src="../../ui/jquery.ui.interaction.js"></script>
-       <script src="../../ui/jquery.ui.draggable.js"></script>
+       <link rel="stylesheet" href="../../themes/base/all.css">
+       <script src="../../jquery-1.10.2.js"></script>
+       <script src="../../ui/core.js"></script>
+       <script src="../../ui/widget.js"></script>
 -      <script src="../../ui/mouse.js"></script>
++      <script src="../../ui/interaction.js"></script>
+       <script src="../../ui/draggable.js"></script>
        <link rel="stylesheet" href="../demos.css">
        <style>
        .draggable { width: 90px; height: 90px; padding: 0.5em; float: left; margin: 0 10px 10px 0; }
index 868362e476c61176eed6fef79cbe209b0e21bab0,426123644c0ae8b48bec67cec0c813c0901f3716..7a979187341318da79fe57fb08584639685a2420
@@@ -3,12 -3,12 +3,12 @@@
  <head>
        <meta charset="utf-8">
        <title>jQuery UI Draggable - Cursor style</title>
-       <link rel="stylesheet" href="../../themes/base/jquery.ui.all.css">
-       <script src="../../jquery-1.9.1.js"></script>
-       <script src="../../ui/jquery.ui.core.js"></script>
-       <script src="../../ui/jquery.ui.widget.js"></script>
-       <script src="../../ui/jquery.ui.interaction.js"></script>
-       <script src="../../ui/jquery.ui.draggable.js"></script>
+       <link rel="stylesheet" href="../../themes/base/all.css">
+       <script src="../../jquery-1.10.2.js"></script>
+       <script src="../../ui/core.js"></script>
+       <script src="../../ui/widget.js"></script>
 -      <script src="../../ui/mouse.js"></script>
++      <script src="../../ui/interaction.js"></script>
+       <script src="../../ui/draggable.js"></script>
        <link rel="stylesheet" href="../demos.css">
        <style>
        #draggable, #draggable2, #draggable3 { width: 100px; height: 100px; padding: 0.5em; float: left; margin: 0 10px 10px 0; }
index 84c35f27e599cc52ba5a66b0b3225519b3c9d1dc,283a596498582781b57d46c862be6e947110049c..1676dddc9c9d7485900a112f12ec217aa14eb160
@@@ -3,12 -3,12 +3,12 @@@
  <head>
        <meta charset="utf-8">
        <title>jQuery UI Draggable - Default functionality</title>
-       <link rel="stylesheet" href="../../themes/base/jquery.ui.all.css">
-       <script src="../../jquery-1.9.1.js"></script>
-       <script src="../../ui/jquery.ui.core.js"></script>
-       <script src="../../ui/jquery.ui.widget.js"></script>
-       <script src="../../ui/jquery.ui.interaction.js"></script>
-       <script src="../../ui/jquery.ui.draggable.js"></script>
+       <link rel="stylesheet" href="../../themes/base/all.css">
+       <script src="../../jquery-1.10.2.js"></script>
+       <script src="../../ui/core.js"></script>
+       <script src="../../ui/widget.js"></script>
 -      <script src="../../ui/mouse.js"></script>
++      <script src="../../ui/interaction.js"></script>
+       <script src="../../ui/draggable.js"></script>
        <link rel="stylesheet" href="../demos.css">
        <style>
        #draggable { width: 150px; height: 150px; padding: 0.5em; }
index 413814be8bfccfa599f7d224348a328ab082e4c2,16df485eb3017a4889e0bda58d97c1a91e9fab76..7bf341dd7c383b623e4e69c28fd32d0afe3097af
@@@ -3,12 -3,12 +3,12 @@@
  <head>
        <meta charset="utf-8">
        <title>jQuery UI Draggable - Delay start</title>
-       <link rel="stylesheet" href="../../themes/base/jquery.ui.all.css">
-       <script src="../../jquery-1.9.1.js"></script>
-       <script src="../../ui/jquery.ui.core.js"></script>
-       <script src="../../ui/jquery.ui.widget.js"></script>
-       <script src="../../ui/jquery.ui.mouse.js"></script>
-       <script src="../../ui/jquery.ui.draggable.js"></script>
+       <link rel="stylesheet" href="../../themes/base/all.css">
+       <script src="../../jquery-1.10.2.js"></script>
+       <script src="../../ui/core.js"></script>
+       <script src="../../ui/widget.js"></script>
 -      <script src="../../ui/mouse.js"></script>
++      <script src="../../ui/interaction.js"></script>
+       <script src="../../ui/draggable.js"></script>
        <link rel="stylesheet" href="../demos.css">
        <style>
        #draggable, #draggable2 { width: 120px; height: 120px; padding: 0.5em; float: left; margin: 0 10px 10px 0; }
index b480dae54342cce63ab2936d2b71e38bbcbe3327,caf392e218b3db089e1409bf1c456829a953a0d7..8c7b02607166d9f160196ba789bfc866aee03c1d
@@@ -3,12 -3,12 +3,12 @@@
  <head>
        <meta charset="utf-8">
        <title>jQuery UI Draggable - Events</title>
-       <link rel="stylesheet" href="../../themes/base/jquery.ui.all.css">
-       <script src="../../jquery-1.9.1.js"></script>
-       <script src="../../ui/jquery.ui.core.js"></script>
-       <script src="../../ui/jquery.ui.widget.js"></script>
-       <script src="../../ui/jquery.ui.interaction.js"></script>
-       <script src="../../ui/jquery.ui.draggable.js"></script>
+       <link rel="stylesheet" href="../../themes/base/all.css">
+       <script src="../../jquery-1.10.2.js"></script>
+       <script src="../../ui/core.js"></script>
+       <script src="../../ui/widget.js"></script>
 -      <script src="../../ui/mouse.js"></script>
++      <script src="../../ui/interaction.js"></script>
+       <script src="../../ui/draggable.js"></script>
        <link rel="stylesheet" href="../demos.css">
        <style>
        #draggable { width: 16em; padding: 0 1em; }
index e2584c7497e48f0c9dce5b44957dbcbd50dce325,7c3690ea6fa1a49ff2df1377d5119a705faa71cb..d8aa57b52d69f8f9dcfcbe6610d56cc7d1e8e71a
@@@ -3,12 -3,12 +3,12 @@@
  <head>
        <meta charset="utf-8">
        <title>jQuery UI Draggable - Handles</title>
-       <link rel="stylesheet" href="../../themes/base/jquery.ui.all.css">
-       <script src="../../jquery-1.9.1.js"></script>
-       <script src="../../ui/jquery.ui.core.js"></script>
-       <script src="../../ui/jquery.ui.widget.js"></script>
-       <script src="../../ui/jquery.ui.interaction.js"></script>
-       <script src="../../ui/jquery.ui.draggable.js"></script>
+       <link rel="stylesheet" href="../../themes/base/all.css">
+       <script src="../../jquery-1.10.2.js"></script>
+       <script src="../../ui/core.js"></script>
+       <script src="../../ui/widget.js"></script>
 -      <script src="../../ui/mouse.js"></script>
++      <script src="../../ui/interaction.js"></script>
+       <script src="../../ui/draggable.js"></script>
        <link rel="stylesheet" href="../demos.css">
        <style>
        #draggable, #draggable2 { width: 100px; height: 100px; padding: 0.5em; float: left; margin: 0 10px 10px 0; }
index 038b08ccb384fd3bbb84d9a2efb33e8f327a8721,0000000000000000000000000000000000000000..dfead48bf915955de448160db659b47e2ca0ad55
mode 100644,000000..100644
--- /dev/null
@@@ -1,73 -1,0 +1,73 @@@
-       <link rel="stylesheet" href="../../themes/base/jquery.ui.all.css">
-       <script src="../../jquery-1.8.2.js"></script>
-       <script src="../../ui/jquery.ui.core.js"></script>
-       <script src="../../ui/jquery.ui.widget.js"></script>
-       <script src="../../ui/jquery.ui.interaction.js"></script>
-       <script src="../../ui/jquery.ui.draggable.js"></script>
 +<!doctype html>
 +<html lang="en">
 +<head>
 +      <meta charset="utf-8">
 +      <meta name="viewport" content="width=device-width, minimum-scale=1, maximum-scale=1">
 +      <title>jQuery UI Draggable - Draggable Map</title>
++      <link rel="stylesheet" href="../../themes/base/all.css">
++      <script src="../../jquery-1.10.2.js"></script>
++      <script src="../../ui/core.js"></script>
++      <script src="../../ui/widget.js"></script>
++      <script src="../../ui/interaction.js"></script>
++      <script src="../../ui/draggable.js"></script>
 +      <link rel="stylesheet" href="../demos.css">
 +      <style>
 +      html, body, .demo { height: 90%; }
 +      #map { width: 100%; height: 100%; max-width: 320px; overflow: hidden; position: relative; }
 +      #map img { position: absolute; cursor: pointer; }
 +      #map .ui-draggable-dragging { cursor: url('closedhand.cur'), move; }
 +      </style>
 +      <script>
 +      $(window).load(function() {
 +              var viewport = $("#map");
 +              var img = viewport.find("img");
 +              var viewWidth = viewport.width();
 +        var viewHeight = viewport.height();
 +        var imgWidth = img.width();
 +        var imgHeight = img.height();
 +        var leftMax = imgWidth - viewWidth;
 +        var topMax = imgHeight - viewHeight;
 +              img.draggable({
 +                      start: function() {
 +                              $(this).addClass("ui-draggable-dragging");
 +                      },
 +                      stop: function() {
 +                              $(this).removeClass("ui-draggable-dragging");
 +                      },
 +                      drag: function(event, ui) {
 +                              if (viewWidth > imgWidth) {
 +                                      // center when there's just one image
 +                                      ui.position.left = viewWidth / 2 - imgWidth / 2;
 +                              } else {
 +                                      if (ui.position.left > 0) {
 +                                              ui.position.left = 0;
 +                                      } else if (ui.position.left + imgWidth < viewWidth) {
 +                                              ui.position.left = -leftMax;
 +                                      }
 +                              }
 +                              if (viewHeight > imgHeight) {
 +                                      // center when the view is bigger then the image
 +                                      ui.position.top = viewHeight / 2 - imgHeight / 2;
 +                              } else {
 +                                      if (ui.position.top > 0) {
 +                                              ui.position.top = 0;
 +                                      } else if (ui.position.top + imgHeight < viewHeight) {
 +                                              ui.position.top = -topMax;
 +                                      }
 +                              }
 +                      }
 +              });
 +      });
 +      </script>
 +</head>
 +<body>
 +
 +<div id="map" class="ui-widget-content">
 +      <img src="http://maps.google.com/maps/api/staticmap?zoom=11&amp;size=640x640&amp;maptype=terrain&amp;sensor=false&amp;center=Vienna" alt="Map of Vienna">
 +</div>
 +
 +<div class="demo-description">
 +<p>Drag the map around inside the viewport. Works with touch devices. The container is kept small for the demo to work.</p>
 +</div>
 +</body>
 +</html>
index 0fd5d04ce71b52d26e9fcc9d34fd8ee0f300dce9,7ccccdf55dd9fece1f8a5c89965351abdb57a221..121aeef74ac5067b8df9d6a7e6a99323bbf88d9b
@@@ -3,15 -3,15 +3,15 @@@
  <head>
        <meta charset="utf-8">
        <title>jQuery UI Draggable - Revert position</title>
-       <link rel="stylesheet" href="../../themes/base/jquery.ui.all.css">
-       <script src="../../jquery-1.9.1.js"></script>
-       <script src="../../ui/jquery.ui.core.js"></script>
-       <script src="../../ui/jquery.ui.widget.js"></script>
-       <script src="../../ui/jquery.ui.interaction.js"></script>
-       <script src="../../ui/jquery.ui.draggable.js"></script>
+       <link rel="stylesheet" href="../../themes/base/all.css">
+       <script src="../../jquery-1.10.2.js"></script>
+       <script src="../../ui/core.js"></script>
+       <script src="../../ui/widget.js"></script>
 -      <script src="../../ui/mouse.js"></script>
++      <script src="../../ui/interaction.js"></script>
+       <script src="../../ui/draggable.js"></script>
        <link rel="stylesheet" href="../demos.css">
        <style>
 -      #draggable, #draggable2 { width: 100px; height: 100px; padding: 0.5em; float: left; margin: 0 10px 10px 0; }
 +      .ui-draggable { width: 100px; height: 100px; padding: 0.5em; float: left; margin: 0 10px 10px 0; }
        </style>
        <script>
        $(function() {
index 554ae116423607a1b2fcdf41513e8a1b1ebb4b84,d90d4842c5d5f45fe22caee2f70fb40b9b062bcd..28d58b57358e1844113ab32ecf156d8db6daf742
@@@ -3,15 -3,15 +3,15 @@@
  <head>
        <meta charset="utf-8">
        <title>jQuery UI Draggable - Visual feedback</title>
-       <link rel="stylesheet" href="../../themes/base/jquery.ui.all.css">
-       <script src="../../jquery-1.9.1.js"></script>
-       <script src="../../ui/jquery.ui.core.js"></script>
-       <script src="../../ui/jquery.ui.widget.js"></script>
-       <script src="../../ui/jquery.ui.interaction.js"></script>
-       <script src="../../ui/jquery.ui.draggable.js"></script>
+       <link rel="stylesheet" href="../../themes/base/all.css">
+       <script src="../../jquery-1.10.2.js"></script>
+       <script src="../../ui/core.js"></script>
+       <script src="../../ui/widget.js"></script>
 -      <script src="../../ui/mouse.js"></script>
++      <script src="../../ui/interaction.js"></script>
+       <script src="../../ui/draggable.js"></script>
        <link rel="stylesheet" href="../demos.css">
        <style>
 -      #draggable, #draggable2, #draggable3, #set div { width: 90px; height: 90px; padding: 0.5em; float: left; margin: 0 10px 10px 0; }
 +      .ui-draggable { width: 90px; height: 90px; padding: 0.5em; float: left; margin: 0 10px 10px 0; }
        #draggable, #draggable2, #draggable3 { margin-bottom:20px; }
        #set { clear:both; float:left; width: 368px; height: 120px; }
        p { clear:both; margin:0; padding:1em 0; }
index 978e994c2877bf03d42356836975d4f0d411bff4,be3603eb8fe76b6be46622d8d6f8c92adc980b81..d341f019ab98698d2920b5b493ef6aa8cc0b3b9a
@@@ -3,13 -3,13 +3,13 @@@
  <head>
        <meta charset="utf-8">
        <title>jQuery UI Droppable - Default functionality</title>
-       <link rel="stylesheet" href="../../themes/base/jquery.ui.all.css">
-       <script src="../../jquery-1.9.1.js"></script>
-       <script src="../../ui/jquery.ui.core.js"></script>
-       <script src="../../ui/jquery.ui.widget.js"></script>
-       <script src="../../ui/jquery.ui.interaction.js"></script>
-       <script src="../../ui/jquery.ui.draggable.js"></script>
-       <script src="../../ui/jquery.ui.droppable.js"></script>
+       <link rel="stylesheet" href="../../themes/base/all.css">
+       <script src="../../jquery-1.10.2.js"></script>
+       <script src="../../ui/core.js"></script>
+       <script src="../../ui/widget.js"></script>
 -      <script src="../../ui/mouse.js"></script>
++      <script src="../../ui/interaction.js"></script>
+       <script src="../../ui/draggable.js"></script>
+       <script src="../../ui/droppable.js"></script>
        <link rel="stylesheet" href="../demos.css">
        <style>
        #draggable { width: 100px; height: 100px; padding: 0.5em; float: left; margin: 10px 10px 10px 0; }
index db80c0dcd358dd3b9a7003443234501c9daeda97,0000000000000000000000000000000000000000..ee4c98cd34dfada7dff2f0dd550af4c6c9d698bb
mode 100644,000000..100644
--- /dev/null
@@@ -1,73 -1,0 +1,73 @@@
-       <link rel="stylesheet" href="../../themes/base/jquery.ui.all.css">
-       <script src="../../jquery-1.8.2.js"></script>
-       <script src="../../ui/jquery.ui.core.js"></script>
-       <script src="../../ui/jquery.ui.widget.js"></script>
-       <script src="../../ui/jquery.ui.interaction.js"></script>
 +<!DOCTYPE html>
 +<html lang="en">
 +<head>
 +      <meta charset="utf-8">
 +      <title>jQuery UI Interaction - Box</title>
- <p>This demo shows how you can create a simple box-drawing (aka lasso) interaction built on top of the interaction utility (jquery.ui.interaction.js).</p>
++      <link rel="stylesheet" href="../../themes/base/all.css">
++      <script src="../../jquery-1.10.2.js"></script>
++      <script src="../../ui/core.js"></script>
++      <script src="../../ui/widget.js"></script>
++      <script src="../../ui/interaction.js"></script>
 +      <link rel="stylesheet" href="../demos.css">
 +      <style>
 +      #canvas { width: 300px; height: 200px; border: 1px solid black; }
 +      .demo-box { border: 1px dotted black; width: 0; height: 0; }
 +      .demo-box-complete { border-color: gray; background: lightBlue; }
 +      </style>
 +      <script>
 +      $(function() {
 +              $.widget( "demo.box", $.ui.interaction, {
 +                      _start: function( event, pointerPosition ) {
 +                              this.startPosition = pointerPosition;
 +
 +                              this.box = $( "<div>" )
 +                                      .appendTo( this.element.empty() )
 +                                      .addClass( "demo-box" )
 +                                      .offset({
 +                                              left: pointerPosition.x,
 +                                              top: pointerPosition.y
 +                                      });
 +                      },
 +
 +                      _move: function( event, pointerPosition ) {
 +                              var x1 = this.startPosition.x,
 +                                      y1 = this.startPosition.y,
 +                                      x2 = pointerPosition.x,
 +                                      y2 = pointerPosition.y;
 +
 +                              this.box
 +                                      .offset({
 +                                              left: Math.min( x1, x2 ),
 +                                              top: Math.min( y1, y2 )
 +                                      })
 +                                      .css({
 +                                              width: x1 > x2 ? x1 - x2 : x2 - x1,
 +                                              height: y1 > y2 ? y1 - y2 : y2 - y1
 +                                      });
 +                      },
 +
 +                      _stop: function() {
 +                              this.box.addClass( "demo-box-complete" );
 +                      }
 +              });
 +
 +              $( "#canvas" ).box();
 +      });
 +      </script>
 +</head>
 +<body>
 +
 +<div class="demo">
 +
 +<div id="canvas"></div>
 +
 +</div><!-- End demo -->
 +
 +
 +<div class="demo-description">
++<p>This demo shows how you can create a simple box-drawing (aka lasso) interaction built on top of the interaction utility (interaction.js).</p>
 +<p>Draw a box by starting in the box above using the mouse or a touch input device.</p>
 +</div><!-- End demo-description -->
 +
 +</body>
 +</html>
index 4f69b20e12cebe6bb6a2113d736393fcf0e58ec9,0000000000000000000000000000000000000000..2b8b92bb07b8d72dc1479406418a2b50a2e60445
mode 100644,000000..100644
--- /dev/null
@@@ -1,63 -1,0 +1,63 @@@
-       <link rel="stylesheet" href="../../themes/base/jquery.ui.all.css">
-       <script src="../../jquery-1.8.2.js"></script>
-       <script src="../../ui/jquery.ui.core.js"></script>
-       <script src="../../ui/jquery.ui.widget.js"></script>
-       <script src="../../ui/jquery.ui.interaction.js"></script>
 +<!DOCTYPE html>
 +<html lang="en">
 +<head>
 +      <meta charset="utf-8">
 +      <title>jQuery UI Interaction - Default functionality</title>
++      <link rel="stylesheet" href="../../themes/base/all.css">
++      <script src="../../jquery-1.10.2.js"></script>
++      <script src="../../ui/core.js"></script>
++      <script src="../../ui/widget.js"></script>
++      <script src="../../ui/interaction.js"></script>
 +      <link rel="stylesheet" href="../demos.css">
 +      <style>
 +      #startmovestop { width: 200px; height: 100px; background: lightBlue; }
 +      </style>
 +      <script>
 +      $(function() {
 +              $.widget( "demo.startmovestop", $.ui.interaction, {
 +                      _start: function() {
 +                              $( "#startcount" ).text(function( i, current ) {
 +                                      return parseInt( current, 10 ) + 1;
 +                              });
 +                      },
 +                      _move: function() {
 +                              $( "#movecount" ).text(function( i, current ) {
 +                                      return parseInt( current, 10 ) + 1;
 +                              });
 +                      },
 +                      _stop: function() {
 +                              $( "#stopcount" ).text(function( i, current ) {
 +                                      return parseInt( current, 10 ) + 1;
 +                              });
 +                      }
 +              });
 +
 +              $( "#startmovestop" ).startmovestop();
 +      });
 +      </script>
 +</head>
 +<body>
 +
 +<div class="demo">
 +
 +<div id="startmovestop"></div>
 +
 +<dl>
 +      <dt>Start:</dt>
 +              <dd id="startcount">0</dd>
 +      <dt>Move:</dt>
 +              <dd id="movecount">0</dd>
 +      <dt>Stop:</dt>
 +              <dd id="stopcount">0</dd>
 +</dl>
 +
 +</div><!-- End demo -->
 +
 +
 +<div class="demo-description">
 +<p>This demo shows a simple interaction built using the interaction utility (jquery.ui.interaction.js).</p>
 +<p>Make a down-move-up motion starting on the blue box above using the mouse or a touch input device to see the interaction events</p>
 +</div><!-- End demo-description -->
 +
 +</body>
 +</html>
index c6f16ee17536fa186f4dfd42cb7c36dcc376640c,02977c6cc5f9283c7cc3c3fd906b5ba1ff2c902f..1a381e314a12b67c4e171b954a407c71223c3350
        <script src="../testsuite.js"></script>
        <script>
        TestHelpers.loadResources({
-               css: [ "ui.core" ],
+               css: [ "core" ],
                js: [
-                       "ui/jquery.ui.core.js",
-                       "ui/jquery.ui.widget.js",
-                       "ui/jquery.ui.interaction.js",
-                       "ui/jquery.ui.resizable.js",
-                       "ui/jquery.ui.draggable.js",
-                       "ui/jquery.ui.droppable.js"
+                       "ui/core.js",
+                       "ui/widget.js",
+                       "ui/mouse.js",
++                      "ui/interaction.js",
+                       "ui/resizable.js",
+                       "ui/draggable.js",
+                       "ui/droppable.js"
                ]
        });
        </script>
  
++
        <script src="draggable_common.js"></script>
        <script src="draggable_core.js"></script>
        <script src="draggable_events.js"></script>
index 9875a481e1c42167d0aad48a466619106b8afbdd,7df0969b1a584d644184a0d3f0cb6bd3066878d8..9e977b52d17a85a830a97f0092b06fed1aaa024c
@@@ -59,111 -48,243 +48,323 @@@ test( "No options, relative", function(
  });
  
  test( "No options, absolute", function() {
-       expect( 1 );
-       TestHelpers.draggable.shouldMove( absoluteElement.draggable() );
+       expect( 2 );
+       TestHelpers.draggable.shouldMove( $( "#draggable2" ).draggable(), "no options, absolute" );
  });
  
--test( "resizable handle with complex markup (#8756 / #8757)", function() {
--      expect( 2 );
++//TODO: re-enable when resizable is ported to interactions
++// test( "resizable handle with complex markup (#8756 / #8757)", function() {
++//    expect( 2 );
 +
-       relativeElement
-               .append(
-                       $("<div>")
-                               .addClass("ui-resizable-handle ui-resizable-w")
-                               .append( $("<div>") )
-               );
++//    $( "#draggable1" )
++//            .append(
++//                    $("<div>")
++//                            .addClass("ui-resizable-handle ui-resizable-w")
++//                            .append( $("<div>") )
++//            );
 +
-       var handle = $(".ui-resizable-w div"),
-               target = relativeElement.draggable().resizable({ handles: "all" });
++//    var handle = $(".ui-resizable-w div"),
++//            target = $( "#draggable1" ).draggable().resizable({ handles: "all" });
 +
-       // todo: fix resizable so it doesn't require a mouseover
-       handle.simulate("mouseover").simulate( "drag", { dx: -50 } );
-       equal( target.width(), 250, "compare width" );
++//    // todo: fix resizable so it doesn't require a mouseover
++//    handle.simulate("mouseover").simulate( "drag", { dx: -50 } );
++//    equal( target.width(), 250, "compare width" );
 +
-       // todo: fix resizable so it doesn't require a mouseover
-       handle.simulate("mouseover").simulate( "drag", { dx: 50 } );
-       equal( target.width(), 200, "compare width" );
- });
++//    // todo: fix resizable so it doesn't require a mouseover
++//    handle.simulate("mouseover").simulate( "drag", { dx: 50 } );
++//    equal( target.width(), 200, "compare width" );
++// });
 +
 +test("_blockFrames, absolute parent", function() {
 +      expect( 3 );
 +      var el = $("#draggable1").draggable(),
 +              parent = $("<div style='width: 600px; height: 600px; position: absolute; top: 300px; left; 400px;'>"),
 +              iframe = $("<iframe src='about:blank' width='500' height='500' style='position: absolute; top: 25px; left: 30px;'>"),
 +              left, top;
 +
 +      parent.append( iframe );
 +
 +      $("#qunit-fixture").prepend( parent );
 +
 +      el.on( "drag", function() {
 +
 +              var block = iframe.next();
 +
 +              left = block.css("left");
 +              top = block.css("top");
 +
 +      });
 +
 +      TestHelpers.draggable.shouldMove(el);
 +
 +      equal( left, "30px" );
 +      equal( top, "25px" );
 +
 +});
 +
 +test("_blockFrames, relative parent", function() {
 +      expect( 3 );
 +      var el = $("#draggable1").draggable(),
 +              parent = $("<div style='width: 600px; height: 600px; position: relative; top: 300px; left; 400px;'>"),
 +              iframe = $("<iframe src='about:blank' width='500' height='500' style='position: absolute; top: 25px; left: 30px;'>"),
 +              left, top;
  
 -      $( "#draggable1" )
 -              .append(
 -                      $("<div>")
 -                              .addClass("ui-resizable-handle ui-resizable-w")
 -                              .append( $("<div>") )
 -              );
 +      parent.append( iframe );
  
 -      var handle = $(".ui-resizable-w div"),
 -              target = $( "#draggable1" ).draggable().resizable({ handles: "all" });
 +      $("#qunit-fixture").prepend( parent );
  
 -      // todo: fix resizable so it doesn't require a mouseover
 -      handle.simulate("mouseover").simulate( "drag", { dx: -50 } );
 -      equal( target.width(), 250, "compare width" );
 +      el.on( "drag", function() {
 +
 +              var block = iframe.next();
 +
 +              left = block.css("left");
 +              top = block.css("top");
 +
 +      });
 +
 +      TestHelpers.draggable.shouldMove(el);
 +
 +      equal( left, "30px" );
 +      equal( top, "25px" );
 +});
 +
 +test("_blockFrames, static parent", function() {
 +      expect( 3 );
 +      var el = $("#draggable1").draggable(),
 +              parent = $("<div style='width: 600px; height: 600px; margin-top: 300px; margin-left: 400px;'>"),
 +              iframe = $("<iframe src='about:blank' width='500' height='500' style='position: relative; top: 25px; left: 30px;'>"),
 +              left, top;
 +
 +      parent.append( iframe );
 +
 +      $("#qunit-fixture").prepend( parent );
 +
 +      el.on( "drag", function() {
 +
 +              var block = iframe.next();
 +
 +              left = block.css("left");
 +              top = block.css("top");
 +
 +      });
 +
 +      TestHelpers.draggable.shouldMove(el);
 +
 +      equal( left, "430px" );
 +  equal( top, "325px" );
  
 -      // todo: fix resizable so it doesn't require a mouseover
 -      handle.simulate("mouseover").simulate( "drag", { dx: 50 } );
 -      equal( target.width(), 200, "compare width" );
  });
  
 -
+ test( "#8269: Removing draggable element on drop", function() {
+       expect( 2 );
+       var element = $( "#draggable1" ).wrap( "<div id='wrapper' />" ).draggable({
+                       stop: function() {
+                               ok( true, "stop still called despite element being removed from DOM on drop" );
+                       }
+               }),
+               dropOffset = $( "#droppable" ).offset();
+       $( "#droppable" ).droppable({
+               drop: function() {
+                       $( "#wrapper" ).remove();
+                       ok( true, "element removed from DOM on drop" );
+               }
+       });
+       // Support: Opera 12.10, Safari 5.1, jQuery <1.8
+       if ( TestHelpers.draggable.unreliableContains ) {
+               ok( true, "Opera <12.14 and Safari <6.0 report wrong values for $.contains in jQuery < 1.8" );
+               ok( true, "Opera <12.14 and Safari <6.0 report wrong values for $.contains in jQuery < 1.8" );
+       } else {
+               element.simulate( "drag", {
+                       handle: "corner",
+                       x: dropOffset.left,
+                       y: dropOffset.top
+               });
+       }
+ });
+ test( "#6258: not following mouse when scrolled and using overflow-y: scroll", function() {
+       expect( 2 );
+       var element = $( "#draggable1" ).draggable({
+                       stop: function( event, ui ) {
+                               equal( ui.position.left, 1, "left position is correct despite overflow on HTML" );
+                               equal( ui.position.top, 1, "top position is correct despite overflow on HTML" );
+                               $( "html" )
+                                       .css( "overflow-y", oldOverflowY )
+                                       .css( "overflow-x", oldOverflowX )
+                                       .scrollTop( 0 )
+                                       .scrollLeft( 0 );
+                       }
+               }),
+               oldOverflowY = $( "html" ).css( "overflow-y" ),
+               oldOverflowX = $( "html" ).css( "overflow-x" );
+               TestHelpers.forceScrollableWindow();
+               $( "html" )
+                       .css( "overflow-y", "scroll" )
+                       .css( "overflow-x", "scroll" )
+                       .scrollTop( 300 )
+                       .scrollLeft( 300 );
+               element.simulate( "drag", {
+                       dx: 1,
+                       dy: 1,
+                       moves: 1
+               });
+ });
+ test( "#9315: jumps down with offset of scrollbar", function() {
+       expect( 2 );
+       var element = $( "#draggable2" ).draggable({
+                       stop: function( event, ui ) {
+                               equal( ui.position.left, 11, "left position is correct when position is absolute" );
+                               equal( ui.position.top, 11, "top position is correct when position is absolute" );
+                               $( "html" ).scrollTop( 0 ).scrollLeft( 0 );
+                       }
+               });
+               TestHelpers.forceScrollableWindow();
+               $( "html" ).scrollTop( 300 ).scrollLeft( 300 );
+               element.simulate( "drag", {
+                       dx: 1,
+                       dy: 1,
+                       moves: 1
+               });
+ });
+ test( "#5009: scroll not working with parent's position fixed", function() {
+       expect( 2 );
+       var startValue = 300,
+               element = $( "#draggable1" ).wrap( "<div id='wrapper' />" ).draggable({
+                       drag: function() {
+                               startValue += 100;
+                               $( document ).scrollTop( startValue ).scrollLeft( startValue );
+                       },
+                       stop: function( event, ui ) {
+                               equal( ui.position.left, 10, "left position is correct when parent position is fixed" );
+                               equal( ui.position.top, 10, "top position is correct when parent position is fixed" );
+                               $( document ).scrollTop( 0 ).scrollLeft( 0 );
+                       }
+               });
 -test( "#5727: draggable from iframe" , function() {
+       TestHelpers.forceScrollableWindow();
+       $( "#wrapper" ).css( "position", "fixed" );
+       element.simulate( "drag", {
+               dx: 10,
+               dy: 10,
+               moves: 3
+       });
+ });
+ test( "#9379: Draggable: position bug in scrollable div", function() {
+       expect( 2 );
+       $( "#qunit-fixture" ).html( "<div id='o_9379'><div id='i_9379'></div><div id='d_9379'>a</div></div>" );
+       $( "#i_9379" ).css({ position: "absolute", width: "500px", height: "500px" });
+       $( "#o_9379" ).css({ position: "absolute", width: "300px", height: "300px" });
+       $( "#d_9379" ).css({ width: "10px", height: "10px" });
+       var moves = 3,
+               startValue = 0,
+               dragDelta = 20,
+               delta = 100,
+               // we scroll after each drag event, so subtract 1 from number of moves for expected
+               expected = delta + ( ( moves - 1 ) * dragDelta ),
+               element = $( "#d_9379" ).draggable({
+                       drag: function() {
+                               startValue += dragDelta;
+                               $( "#o_9379" ).scrollTop( startValue ).scrollLeft( startValue );
+                       },
+                       stop: function( event, ui ) {
+                               equal( ui.position.left, expected, "left position is correct when grandparent is scrolled" );
+                               equal( ui.position.top, expected, "top position is correct when grandparent is scrolled" );
+                       }
+               });
+       $( "#o_9379" ).css( "overflow", "auto" );
+       element.simulate( "drag", {
+               dy: delta,
+               dx: delta,
+               moves: moves
+       });
+ });
++test( "#5727: draggable from iframe", function() {
+       expect( 1 );
+       var iframeBody, draggable1,
+               iframe = $( "<iframe />" ).appendTo( "#qunit-fixture" ),
+               iframeDoc = ( iframe[ 0 ].contentWindow || iframe[ 0 ].contentDocument ).document;
+       iframeDoc.write( "<!doctype html><html><body>" );
+       iframeDoc.close();
+       iframeBody = $( iframeDoc.body ).append( "<div style='width: 2px; height: 2px;' />" );
+       draggable1 = iframeBody.find( "div" );
+       draggable1.draggable();
+       equal( draggable1.closest( iframeBody ).length, 1 );
+       // TODO: fix draggable within an IFRAME to fire events on the element properly
+       // and these TestHelpers.draggable.shouldMove relies on events for testing
+       //TestHelpers.draggable.shouldMove( draggable1, "draggable from an iframe" );
+ });
+ test( "#8399: A draggable should become the active element after you are finished interacting with it, but not before.", function() {
+       expect( 2 );
+       var element = $( "<a href='#'>link</a>" ).appendTo( "#qunit-fixture" ).draggable();
+       $( document ).one( "mousemove", function() {
+               notStrictEqual( document.activeElement, element.get( 0 ), "moving a draggable anchor did not make it the active element" );
+       });
+       TestHelpers.draggable.move( element, 50, 50 );
+       strictEqual( document.activeElement, element.get( 0 ), "finishing moving a draggable anchor made it the active element" );
+ });
+ asyncTest( "#4261: active element should blur when mousing down on a draggable", function() {
+       expect( 2 );
+       var textInput = $( "<input>" ).appendTo( "#qunit-fixture" ),
+               element = $( "#draggable1" ).draggable();
+       TestHelpers.onFocus( textInput, function() {
+               strictEqual( document.activeElement, textInput.get( 0 ), "ensure that a focussed text input is the active element before mousing down on a draggable" );
+               TestHelpers.draggable.move( element, 50, 50 );
+               notStrictEqual( document.activeElement, textInput.get( 0 ), "ensure the text input is no longer the active element after mousing down on a draggable" );
+               start();
+       });
+ });
+ test( "ui-draggable-handle assigned to appropriate element", function() {
+       expect( 4 );
+       var element = $( "<div><p></p></div>" ).appendTo( "#qunit-fixture" ).draggable();
+       ok( element.hasClass( "ui-draggable-handle" ), "handle is element by default" );
+       element.draggable( "option", "handle", "p" );
+       ok( !element.hasClass( "ui-draggable-handle" ), "removed from element" );
+       ok( element.find( "p" ).hasClass( "ui-draggable-handle" ), "added to handle" );
+       element.draggable( "destroy" );
+       ok( !element.find( "p" ).hasClass( "ui-draggable-handle" ), "removed in destroy()" );
+ });
  })( jQuery );
index 0eb9ff09cef03a7d15854fd606c4681307ced4ff,0e728f8dc360f2e72c6d0c7a09e68000dbb95565..f2951885cd2ee4b950cd6d0678413c201e3188c8
@@@ -40,10 -40,10 +40,10 @@@ test( "{ appendTo: 'parent' }, default"
  
        TestHelpers.draggable.trackAppendedParent( element );
  
 -      equal( element.draggable( "option", "appendTo" ), "parent" );
 +      equal( element.draggable( "option", "appendTo" ), null );
  
        TestHelpers.draggable.move( element, 1, 1 );
-       equal( element.data( "last_dragged_parent" ), $( "#qunit-fixture" )[ 0 ] );
+       equal( element.data( "last_dragged_parent" ), $( "#main" )[ 0 ] );
  });
  
  test( "{ appendTo: Element }", function() {
@@@ -85,7 -85,7 +85,7 @@@ test( "{ appendTo: Selector }", functio
  test( "appendTo, default, switching after initialization", function() {
        expect( 2 );
  
--      var element = $( "#draggable1" ).draggable({ helper : "clone" });
++      var element = $( "#draggable1" ).draggable({ helper: "clone" });
  
        TestHelpers.draggable.trackAppendedParent( element );
  
@@@ -138,12 -138,12 +138,12 @@@ test( "{ axis: ? }, unexpected", functi
  });
  
  test( "axis, default, switching after initialization", function() {
-       expect( 3 );
+       expect( 6 );
  
--      var element = $( "#draggable1" ).draggable({ axis : false });
++      var element = $( "#draggable1" ).draggable({ axis: false });
  
        // Any Direction
-       TestHelpers.draggable.testDrag( element, element, 50, 50, 50, 50 );
+       TestHelpers.draggable.shouldMove( element, "axis: default" );
  
        // Only horizontal
        element.draggable( "option", "axis", "x" );
@@@ -229,10 -227,10 +227,10 @@@ test( "{ cancel: Selectors }, matching 
  });
  */
  
 -test( "cancelement, default, switching after initialization", function() {
 -      expect( 2 );
 +test( "cancel, default, switching after initialization", function() {
 +      expect( 3 );
  
-       $( "<div id='draggable-option-cancel-default'><input type='text'></div>" ).appendTo( "#main" );
+       $( "<div id='draggable-option-cancel-default'><input type='text'></div>" ).appendTo( "#qunit-fixture" );
  
        var input = $( "#draggable-option-cancel-default input" ),
                element = $( "#draggable-option-cancel-default" ).draggable();
@@@ -446,12 -504,12 +504,12 @@@ test( "cursorAt", function() 
        var deltaX = -3,
                deltaY = -3,
                tests = {
--                      "false": { cursorAt : false },
--                      "{ left: -5, top: -5 }": { x: -5, y: -5, cursorAt : { left: -5, top: -5 } },
--                      "[ 10, 20 ]": { x: 10, y: 20, cursorAt : [ 10, 20 ] },
--                      "'10 20'": { x: 10, y: 20, cursorAt : "10 20" },
--                      "{ left: 20, top: 40 }": { x: 20, y: 40, cursorAt : { left: 20, top: 40 } },
--                      "{ right: 10, bottom: 20 }": { x: 10, y: 20, cursorAt : { right: 10, bottom: 20 } }
++                      "false": { cursorAt: false },
++                      "{ left: -5, top: -5 }": { x: -5, y: -5, cursorAt: { left: -5, top: -5 } },
++                      "[ 10, 20 ]": { x: 10, y: 20, cursorAt: [ 10, 20 ] },
++                      "'10 20'": { x: 10, y: 20, cursorAt: "10 20" },
++                      "{ left: 20, top: 40 }": { x: 20, y: 40, cursorAt: { left: 20, top: 40 } },
++                      "{ right: 10, bottom: 20 }": { x: 10, y: 20, cursorAt: { right: 10, bottom: 20 } }
                };
  
        $.each( tests, function( testName, testData ) {
                        var element = $( "#draggable" + ( i + 1 ) ).draggable({
                                        cursorAt: testData.cursorAt,
                                        drag: function( event, ui ) {
--                                              if( !testData.cursorAt ) {
++                                              if ( !testData.cursorAt ) {
                                                        equal( ui.position.left - ui.originalPosition.left, deltaX, testName + " " + position + " left" );
                                                        equal( ui.position.top - ui.originalPosition.top, deltaY, testName + " " + position + " top" );
--                                              } else if( testData.cursorAt.right ) {
++                                              } else if ( testData.cursorAt.right ) {
                                                        equal( ui.helper.width() - ( event.clientX - ui.offset.left ), testData.x - TestHelpers.draggable.unreliableOffset, testName + " " + position + " left" );
                                                        equal( ui.helper.height() - ( event.clientY - ui.offset.top ), testData.y - TestHelpers.draggable.unreliableOffset, testName + " " +position + " top" );
                                                } else {
@@@ -487,12 -545,12 +545,12 @@@ test( "cursorAt, switching after initia
        var deltaX = -3,
                deltaY = -3,
                tests = {
--                      "false": { cursorAt : false },
--                      "{ left: -5, top: -5 }": { x: -5, y: -5, cursorAt : { left: -5, top: -5 } },
--                      "[ 10, 20 ]": { x: 10, y: 20, cursorAt : [ 10, 20 ] },
--                      "'10 20'": { x: 10, y: 20, cursorAt : "10 20" },
--                      "{ left: 20, top: 40 }": { x: 20, y: 40, cursorAt : { left: 20, top: 40 } },
--                      "{ right: 10, bottom: 20 }": { x: 10, y: 20, cursorAt : { right: 10, bottom: 20 } }
++                      "false": { cursorAt: false },
++                      "{ left: -5, top: -5 }": { x: -5, y: -5, cursorAt: { left: -5, top: -5 } },
++                      "[ 10, 20 ]": { x: 10, y: 20, cursorAt: [ 10, 20 ] },
++                      "'10 20'": { x: 10, y: 20, cursorAt: "10 20" },
++                      "{ left: 20, top: 40 }": { x: 20, y: 40, cursorAt: { left: 20, top: 40 } },
++                      "{ right: 10, bottom: 20 }": { x: 10, y: 20, cursorAt: { right: 10, bottom: 20 } }
                };
  
        $.each( tests, function( testName, testData ) {
  
                        element.draggable({
                                        drag: function( event, ui ) {
--                                              if( !testData.cursorAt ) {
++                                              if ( !testData.cursorAt ) {
                                                        equal( ui.position.left - ui.originalPosition.left, deltaX, testName + " " + position + " left" );
                                                        equal( ui.position.top - ui.originalPosition.top, deltaY, testName + " " + position + " top" );
--                                              } else if( testData.cursorAt.right ) {
++                                              } else if ( testData.cursorAt.right ) {
                                                        equal( ui.helper.width() - ( event.clientX - ui.offset.left ), testData.x - TestHelpers.draggable.unreliableOffset, testName + " " + position + " left" );
                                                        equal( ui.helper.height() - ( event.clientY - ui.offset.top ), testData.y - TestHelpers.draggable.unreliableOffset, testName + " " +position + " top" );
                                                } else {
@@@ -625,335 -659,74 +659,74 @@@ test( "handle, default, switching afte
  });
  
  test( "helper, default, switching after initialization", function() {
-       expect( 3 );
+       expect( 6 );
  
        var element = $( "#draggable1" ).draggable();
-       TestHelpers.draggable.shouldMove( element );
+       TestHelpers.draggable.shouldMove( element, "helper: default" );
  
        element.draggable( "option", "helper", "clone" );
-       TestHelpers.draggable.shouldNotMove( element );
+       TestHelpers.draggable.shouldMove( element, "helper: clone" );
  
        element.draggable( "option", "helper", "original" );
-       TestHelpers.draggable.shouldMove( element );
- });
- test( "{ helper: 'clone' }, relative", function() {
-       expect( 1 );
-       var element = $( "#draggable1" ).draggable({ helper: "clone" });
-       TestHelpers.draggable.shouldNotMove( element );
- });
- test( "{ helper: 'clone' }, absolute", function() {
-       expect( 1 );
-       var element = $( "#draggable2" ).draggable({ helper: "clone" });
-       TestHelpers.draggable.shouldNotMove( element );
- });
- test( "{ helper: 'original' }, relative, with scroll offset on parent", function() {
-       expect( 3 );
-       var element = $( "#draggable1" ).draggable({ helper: "original" });
-       TestHelpers.draggable.setScroll();
-       TestHelpers.draggable.testScroll( element, "relative" );
-       TestHelpers.draggable.setScroll();
-       TestHelpers.draggable.testScroll( element, "static" );
-       TestHelpers.draggable.setScroll();
-       TestHelpers.draggable.testScroll( element, "absolute" );
-       TestHelpers.draggable.restoreScroll();
- });
- test( "{ helper: 'original' }, relative, with scroll offset on root", function() {
-       expect( 3 );
-       var element = $( "#draggable1" ).draggable({ helper: "original" });
-       TestHelpers.draggable.setScroll( "root" );
-       TestHelpers.draggable.testScroll( element, "relative" );
-       TestHelpers.draggable.setScroll( "root" );
-       TestHelpers.draggable.testScroll( element, "static" );
-       TestHelpers.draggable.setScroll( "root" );
-       TestHelpers.draggable.testScroll( element, "absolute" );
-       TestHelpers.draggable.restoreScroll( "root" );
- });
- test( "{ helper: 'original' }, relative, with scroll offset on root and parent", function() {
-       expect( 3 );
-       var element = $( "#draggable1" ).draggable({ helper: "original" });
-       TestHelpers.draggable.setScroll();
-       TestHelpers.draggable.setScroll( "root" );
-       TestHelpers.draggable.testScroll( element, "relative" );
-       TestHelpers.draggable.setScroll();
-       TestHelpers.draggable.setScroll( "root" );
-       TestHelpers.draggable.testScroll( element, "static" );
-       TestHelpers.draggable.setScroll();
-       TestHelpers.draggable.setScroll( "root" );
-       TestHelpers.draggable.testScroll( element, "absolute" );
-       TestHelpers.draggable.restoreScroll();
-       TestHelpers.draggable.restoreScroll( "root" );
+       TestHelpers.draggable.shouldMove( element, "helper: original" );
  });
  
- test( "{ helper: 'original' }, absolute, with scroll offset on parent", function() {
-       expect( 3 );
-       var element = $( "#draggable1" ).css({ position: "absolute", top: 0, left: 0 }).draggable({ helper: "original" });
-       TestHelpers.draggable.setScroll();
-       TestHelpers.draggable.testScroll( element, "relative" );
-       TestHelpers.draggable.setScroll();
-       TestHelpers.draggable.testScroll( element, "static" );
-       TestHelpers.draggable.setScroll();
-       TestHelpers.draggable.testScroll( element, "absolute" );
-       TestHelpers.draggable.restoreScroll();
- });
- test( "{ helper: 'original' }, absolute, with scroll offset on root", function() {
-       expect( 3 );
-       var element = $( "#draggable1" ).css({ position: "absolute", top: 0, left: 0 }).draggable({ helper: "original" });
-       TestHelpers.draggable.setScroll( "root" );
-       TestHelpers.draggable.testScroll( element, "relative" );
-       TestHelpers.draggable.setScroll( "root" );
-       TestHelpers.draggable.testScroll( element, "static" );
-       TestHelpers.draggable.setScroll( "root" );
-       TestHelpers.draggable.testScroll( element, "absolute" );
-       TestHelpers.draggable.restoreScroll( "root" );
- });
- test( "{ helper: 'original' }, absolute, with scroll offset on root and parent", function() {
-       expect( 3 );
-       var element = $( "#draggable1" ).css({ position: "absolute", top: 0, left: 0 }).draggable({ helper: "original" });
-       TestHelpers.draggable.setScroll();
-       TestHelpers.draggable.setScroll( "root" );
-       TestHelpers.draggable.testScroll( element, "relative" );
-       TestHelpers.draggable.setScroll();
-       TestHelpers.draggable.setScroll( "root" );
-       TestHelpers.draggable.testScroll( element, "static" );
-       TestHelpers.draggable.setScroll();
-       TestHelpers.draggable.setScroll( "root" );
-       TestHelpers.draggable.testScroll( element, "absolute" );
-       TestHelpers.draggable.restoreScroll();
-       TestHelpers.draggable.restoreScroll( "root" );
- });
- test( "{ helper: 'original' }, fixed, with scroll offset on parent", function() {
-       expect( 3 );
-       var element = $( "#draggable1" ).css({ position: "fixed", top: 0, left: 0 }).draggable({ helper: "original" });
-       TestHelpers.draggable.setScroll();
-       TestHelpers.draggable.testScroll( element, "relative" );
-       TestHelpers.draggable.setScroll();
-       TestHelpers.draggable.testScroll( element, "static" );
-       TestHelpers.draggable.setScroll();
-       TestHelpers.draggable.testScroll( element, "absolute" );
-       TestHelpers.draggable.restoreScroll();
- });
- test( "{ helper: 'original' }, fixed, with scroll offset on root", function() {
-       expect( 3 );
-       var element = $( "#draggable1" ).css({ position: "fixed", top: 0, left: 0 }).draggable({ helper: "original" });
-       TestHelpers.draggable.setScroll( "root" );
-       TestHelpers.draggable.testScroll( element, "relative" );
-       TestHelpers.draggable.setScroll( "root" );
-       TestHelpers.draggable.testScroll( element, "static" );
-       TestHelpers.draggable.setScroll( "root" );
-       TestHelpers.draggable.testScroll( element, "absolute" );
-       TestHelpers.draggable.restoreScroll( "root" );
- });
- test( "{ helper: 'original' }, fixed, with scroll offset on root and parent", function() {
-       expect( 3 );
-       var element = $( "#draggable1" ).css({ position: "fixed", top: 0, left: 0 }).draggable({ helper: "original" });
-       TestHelpers.draggable.setScroll();
-       TestHelpers.draggable.setScroll( "root" );
-       TestHelpers.draggable.testScroll( element, "relative" );
-       TestHelpers.draggable.setScroll();
-       TestHelpers.draggable.setScroll( "root" );
-       TestHelpers.draggable.testScroll( element, "static" );
-       TestHelpers.draggable.setScroll();
-       TestHelpers.draggable.setScroll( "root" );
-       TestHelpers.draggable.testScroll( element, "absolute" );
-       TestHelpers.draggable.restoreScroll();
-       TestHelpers.draggable.restoreScroll( "root" );
- });
- test( "{ helper: 'clone' }, absolute", function() {
-       expect( 1 );
-       var helperOffset = null,
-               origOffset = $( "#draggable1" ).offset(),
-               element = $( "#draggable1" ).draggable({ helper: "clone", drag: function( event, ui) {
-                       helperOffset = ui.helper.offset();
-               } });
-       element.simulate( "drag", {
-               dx: 1,
-               dy: 1
-       });
-       deepEqual({ top: helperOffset.top - 1, left: helperOffset.left - 1 }, origOffset, "dragged[1, 1]" );
- });
- test( "{ helper: 'clone' }, absolute with scroll offset on parent", function() {
-       expect( 3 );
-       TestHelpers.draggable.setScroll();
-       var helperOffset = null,
-               origOffset = null,
-               element = $( "#draggable1" ).draggable({
-                       helper: "clone",
-                       drag: function( event, ui) {
-                               helperOffset = ui.helper.offset();
-                       }
-               });
-       $( "#main" ).css( "position", "relative" );
-       origOffset = $( "#draggable1" ).offset();
-       element.simulate( "drag", {
-               dx: 1,
-               dy: 1
-       });
-       deepEqual({ top: helperOffset.top - 1, left: helperOffset.left - 1 }, origOffset, "dragged[1, 1]" );
-       $( "#main" ).css( "position", "static" );
-       origOffset = $( "#draggable1" ).offset();
-       element.simulate( "drag", {
-               dx: 1,
-               dy: 1
-       });
-       deepEqual({ top: helperOffset.top - 1, left: helperOffset.left - 1 }, origOffset, "dragged[1, 1]" );
-       $( "#main" ).css( "position", "absolute" );
-       origOffset = $( "#draggable1" ).offset();
-       element.simulate( "drag", {
-               dx: 1,
-               dy: 1
-       });
-       deepEqual({ top: helperOffset.top - 1, left: helperOffset.left - 1 }, origOffset, "dragged[1, 1]" );
-       TestHelpers.draggable.restoreScroll();
- });
- test( "{ helper: 'clone' }, absolute with scroll offset on root", function() {
-       expect( 3 );
-       TestHelpers.draggable.setScroll( "root" );
-       var helperOffset = null,
-               origOffset = null,
-               element = $( "#draggable1" ).draggable({
-                       helper: "clone",
-                       drag: function( event, ui) {
-                               helperOffset = ui.helper.offset();
-                       }
-               });
-       $( "#main" ).css( "position", "relative" );
-       origOffset = $( "#draggable1" ).offset();
-       element.simulate( "drag", {
-               dx: 1,
-               dy: 1
-       });
-       deepEqual({ top: helperOffset.top - 1, left: helperOffset.left - 1 }, origOffset, "dragged[1, 1]" );
+ /* jshint loopfunc: true */
 -(function(){
++(function() {
+       var k, l, m,
+               scrollElements = {
+                       "no elements": [],
+                       "parent": [ "#main" ],
+                       "root": [ document ],
+                       "parent and root": [ "#main", document ],
+                       "grandparent": [ "#scrollParent" ]
+               },
+               positions = [ "absolute", "fixed", "relative", "static" ],
+               helpers = [ "original", "clone" ],
+               // static is not an option here since the fixture is in an absolute container
+               scrollPositions = [ "relative", "absolute", "fixed" ];
+       for ( m = 0 ; m < helpers.length; m++ ) {
+               for ( l = 0; l < positions.length; l++ ) {
+                       for ( k in scrollElements ) {
 -                              (function( position, helper, scrollElements, scrollElementsTitle ){
++                              (function( position, helper, scrollElements, scrollElementsTitle ) {
+                                       test( "{ helper: '" + helper + "' }, " + position + ", with scroll offset on " + scrollElementsTitle, function() {
+                                               expect( scrollPositions.length * 2 );
+                                               var i, j,
+                                                       element = $( "#draggable1" ).css({ position: position, top: 0, left: 0 }).draggable({
+                                                               helper: helper,
+                                                               scroll: false
+                                                       });
+                                               if ( scrollElements.length === 1 && scrollElements[ 0 ] === "#scrollParent" ) {
+                                                       TestHelpers.draggable.setScrollable( "#main", false );
+                                                       TestHelpers.draggable.setScrollable( "#scrollParent", true );
+                                               }
  
-       $( "#main" ).css( "position", "static" );
-       origOffset = $( "#draggable1" ).offset();
-       element.simulate( "drag", {
-               dx: 1,
-               dy: 1
-       });
-       deepEqual({ top: helperOffset.top - 1, left: helperOffset.left - 1 }, origOffset, "dragged[1, 1]" );
+                                               for ( j = 0; j < scrollPositions.length; j++ ) {
+                                                       for ( i = 0; i < scrollElements.length; i++ ) {
+                                                               TestHelpers.draggable.setScroll( scrollElements[ i ] );
+                                                       }
  
-       $( "#main" ).css( "position", "absolute" );
-       origOffset = $( "#draggable1" ).offset();
-       element.simulate( "drag", {
-               dx: 1,
-               dy: 1
-       });
-       deepEqual({ top: helperOffset.top - 1, left: helperOffset.left - 1 }, origOffset, "dragged[1, 1]" );
-       TestHelpers.draggable.restoreScroll( "root" );
- });
+                                                       TestHelpers.draggable.testScroll( element, scrollPositions[ j ] );
  
- test( "{ helper: 'clone' }, absolute with scroll offset on root and parent", function() {
-       expect( 3 );
-       TestHelpers.draggable.setScroll( "root" );
-       TestHelpers.draggable.setScroll();
+                                                       for ( i = 0; i < scrollElements.length; i++ ) {
+                                                               TestHelpers.draggable.restoreScroll( scrollElements[ i ] );
+                                                       }
+                                               }
  
-       var helperOffset = null,
-               origOffset = null,
-               element = $( "#draggable1" ).draggable({
-                       helper: "clone",
-                       drag: function( event, ui) {
-                               helperOffset = ui.helper.offset();
+                                               if ( scrollElements.length === 1 && scrollElements[ 1 ] === "#scrollParent" ) {
+                                                       TestHelpers.draggable.setScrollable( "#main", true );
+                                                       TestHelpers.draggable.setScrollable( "#scrollParent", false );
+                                               }
+                                       });
+                               })( positions[ l ], helpers[ m ], scrollElements[ k ], k );
                        }
-               });
-       $( "#main" ).css( "position", "relative" );
-       origOffset = $( "#draggable1" ).offset();
-       element.simulate( "drag", {
-               dx: 1,
-               dy: 1
-       });
-       deepEqual({ top: helperOffset.top - 1, left: helperOffset.left - 1 }, origOffset, "dragged[1, 1]" );
-       $( "#main" ).css( "position", "static" );
-       origOffset = $( "#draggable1" ).offset();
-       element.simulate( "drag", {
-               dx: 1,
-               dy: 1
-       });
-       deepEqual({ top: helperOffset.top - 1, left: helperOffset.left - 1 }, origOffset, "dragged[1, 1]" );
-       $( "#main" ).css( "position", "absolute" );
-       origOffset = $( "#draggable1" ).offset();
-       element.simulate( "drag", {
-               dx: 1,
-               dy: 1
-       });
-       deepEqual({ top: helperOffset.top - 1, left: helperOffset.left - 1 }, origOffset, "dragged[1, 1]" );
-       TestHelpers.draggable.restoreScroll( "root" );
-       TestHelpers.draggable.restoreScroll();
- });
+               }
+       }
+ })();
+ /* jshint loopfunc: false */
  
  test( "{ opacity: 0.5 }", function() {
        expect( 1 );
index 250288a5b3b1a3a8113ee55832658dcc77f4604a,0b533a4e1ba96e701357e905ef96cd11fa08d52e..dbaba4cfe05c9754afd462e8cf461258f8523670
@@@ -1,53 -1,88 +1,94 @@@
  TestHelpers.draggable = {
-       // todo: remove the unreliable offset hacks
+       // TODO: remove the unreliable offset hacks
        unreliableOffset: $.ui.ie && ( !document.documentMode || document.documentMode < 8 ) ? 2 : 0,
-       testDrag: function(el, handle, dx, dy, expectedDX, expectedDY, msg) {
-               var offsetAfter, actual, expected,
-                       offsetBefore = el.offset();
+       // Support: Opera 12.10, Safari 5.1, jQuery <1.8
 -      unreliableContains: (function(){
++      unreliableContains: (function() {
+               var element = $( "<div>" );
+               return $.contains( element[ 0 ].ownerDocument, element[ 0 ] );
+       })(),
+       testDragPosition: function( el, dx, dy, expectedDX, expectedDY, msg ) {
+               msg = msg ? msg + "." : "";
+               $( el ).one( "dragstop", function( event, ui ) {
+                       var positionExpected = { left: ui.originalPosition.left + expectedDX, top: ui.originalPosition.top + expectedDY };
+                       deepEqual( ui.position, positionExpected, "position dragged[" + dx + ", " + dy + "] " + msg );
+               } );
+       },
+       testDragOffset: function( el, dx, dy, expectedDX, expectedDY, msg ) {
+               msg = msg ? msg + "." : "";
+               var offsetBefore = el.offset(),
+                       offsetExpected = { left: offsetBefore.left + expectedDX, top: offsetBefore.top + expectedDY };
+               $( el ).one( "dragstop", function( event, ui ) {
+                       deepEqual( ui.helper.offset(), offsetExpected, "offset dragged[" + dx + ", " + dy + "] " + msg );
+               } );
+       },
+       testDrag: function( el, handle, dx, dy, expectedDX, expectedDY, msg ) {
+               TestHelpers.draggable.testDragPosition( el, dx, dy, expectedDX, expectedDY, msg );
+               TestHelpers.draggable.testDragOffset( el, dx, dy, expectedDX, expectedDY, msg );
  
                $( handle ).simulate( "drag", {
                        dx: dx,
                        dy: dy
                });
-               offsetAfter = el.offset();
-               actual = { left: offsetAfter.left, top: offsetAfter.top };
-               expected = { left: offsetBefore.left + expectedDX, top: offsetBefore.top + expectedDY };
+       },
+       shouldMovePositionButNotOffset: function( el, msg, handle ) {
+               handle = handle || el;
+               TestHelpers.draggable.testDragPosition( el, 100, 100, 100, 100, msg );
+               TestHelpers.draggable.testDragOffset( el, 100, 100, 0, 0, msg );
  
-               msg = msg ? msg + "." : "";
-               deepEqual(actual, expected, "dragged[" + dx + ", " + dy + "] " + msg);
+               $( handle ).simulate( "drag", {
+                       dx: 100,
+                       dy: 100
+               });
+       },
+       shouldMove: function( el, msg, handle ) {
+               handle = handle || el;
+               TestHelpers.draggable.testDrag( el, handle, 100, 100, 100, 100, msg );
        },
-       shouldMove: function(el, why) {
-               TestHelpers.draggable.testDrag(el, el, 50, 50, 50, 50, why);
+       shouldNotMove: function( el, msg, handle ) {
+               handle = handle || el;
+               TestHelpers.draggable.testDrag( el, handle, 100, 100, 0, 0, msg );
        },
-       shouldNotMove: function(el, why) {
-               TestHelpers.draggable.testDrag(el, el, 50, 50, 0, 0, why);
+       shouldNotDrag: function( el, msg, handle ) {
+               handle = handle || el;
+               $( el ).bind( "dragstop", function() {
+                       ok( false, "should not drag " + msg );
+               } );
+               $( handle ).simulate( "drag", {
+                       dx: 100,
+                       dy: 100
+               });
+               $( el ).unbind( "dragstop" );
        },
-       testScroll: function(el, position ) {
-               var oldPosition = $("#main").css("position");
-               $("#main").css("position", position);
-               TestHelpers.draggable.shouldMove(el, position+" parent");
-               $("#main").css("position", oldPosition);
 -      setScrollable: function ( what, isScrollable ) {
++      setScrollable: function( what, isScrollable ) {
+               var overflow = isScrollable ? "scroll" : "hidden";
+               $( what ).css({ overflow: overflow, overflowX: overflow, overflowY: overflow });
+       },
+       testScroll: function( el, position ) {
+               var oldPosition = $( "#main" ).css( "position" );
+               $( "#main" ).css({ position: position, top: "0px", left: "0px" });
+               TestHelpers.draggable.shouldMove( el, position + " parent" );
+               $( "#main" ).css( "position", oldPosition );
        },
        restoreScroll: function( what ) {
-               if( what ) {
-                       $(document).scrollTop(0); $(document).scrollLeft(0);
-               } else {
-                       $("#main").scrollTop(0); $("#main").scrollLeft(0);
-               }
+               $( what ).scrollTop( 0 ).scrollLeft( 0 );
        },
        setScroll: function( what ) {
-               if(what) {
++              if (what) {
 +                      $(document).scrollTop(100); $(document).scrollLeft(100);
 +              } else {
 +                      $("#main").scrollTop(100); $("#main").scrollLeft(100);
 +              }
++
+               $( what ).scrollTop( 100 ).scrollLeft( 100 );
        },
-       border: function(el, side) {
-               return parseInt(el.css("border-" + side + "-width"), 10) || 0;
+       border: function( el, side ) {
+               return parseInt( el.css( "border-" + side + "-width" ), 10 ) || 0;
        },
-       margin: function(el, side) {
-               return parseInt(el.css("margin-" + side), 10) || 0;
+       margin: function( el, side ) {
+               return parseInt( el.css( "margin-" + side ), 10 ) || 0;
        },
        move: function( el, x, y ) {
                $( el ).simulate( "drag", {
                        dy: y
                });
        },
--      trackMouseCss : function( el ) {
-               el.on( "drag", function() {
-                       el.data( "last_dragged_cursor", $("body").css("cursor") );
++      trackMouseCss: function( el ) {
+               el.bind( "drag", function() {
+                       el.data( "last_dragged_cursor", $( "body" ).css( "cursor" ) );
                });
        },
--      trackAppendedParent : function( el ) {
-               // appendTo ignored without being clone
++      trackAppendedParent: function( el ) {
+               // TODO: appendTo is currently ignored if helper is original (see #7044)
                el.draggable( "option", "helper", "clone" );
  
-               el.on( "drag", function(e,ui) {
-                       // Get what parent is at time of drag
-                       el.data( "last_dragged_parent", ui.helper.parent()[0] );
+               // Get what parent is at time of drag
+               el.bind( "drag", function(e,ui) {
+                       el.data( "last_dragged_parent", ui.helper.parent()[ 0 ] );
                });
        }
--};
++};
index 7d90116d6f0674344fe02c36ed0243ba01494209,9a3a4bd0796b973cabf4c356552f17b384b80517..908ca20884083d246ffa1abd0da72e527e7d9166
        <script src="../testsuite.js"></script>
        <script>
        TestHelpers.loadResources({
-               css: [ "ui.core" ],
+               css: [ "core" ],
                js: [
-                       "ui/jquery.ui.core.js",
-                       "ui/jquery.ui.widget.js",
-                       "ui/jquery.ui.interaction.js",
-                       "ui/jquery.ui.draggable.js",
-                       "ui/jquery.ui.droppable.js"
+                       "ui/core.js",
+                       "ui/widget.js",
 -                      "ui/mouse.js",
++                      "ui/interaction.js",
+                       "ui/draggable.js",
+                       "ui/droppable.js"
                ]
        });
        </script>
index ec42112b8374c7a91376a969ed5cc58a16f03ca2,1d8f95da90b348f96c14b7eb48f3fc97d9c32481..295d96d53ff03a8e8a4352de3522c42825c72665
   */
  (function($) {
  
  module("droppable: options");
  
 -/*
  test("{ accept '*' }, default ", function() {
-       
 -      ok(false, 'missing test - untested code is broken code');
++
 +      expect( 1 );
-       
++
 +      var droppable = $("#droppable1").droppable(),
 +              draggable = $("#draggable1").draggable();
-       
++
 +      TestHelpers.droppable.shouldDrop( draggable, droppable, "Default" );
-       
-       
++
  });
  
  test("{ accept: Selector }", function() {
 -      ok(false, 'missing test - untested code is broken code');
 +      expect( 3 );
-       
++
 +      var target = $("#droppable1").droppable(),
 +              source = $("#draggable1").draggable();
-       
++
 +      TestHelpers.droppable.shouldDrop( source, target, "Default" );
-       
++
 +      target.droppable( "option", "accept", "#foo" );
 +      TestHelpers.droppable.shouldNotDrop( source, target, "Not correct selector" );
-       
++
 +      target.droppable( "option", "accept", "#draggable1" );
 +      TestHelpers.droppable.shouldDrop( source, target, "Correct selector" );
-       
++
  });
  
 -test("{ accept: function(draggable) }", function() {
 -      ok(false, 'missing test - untested code is broken code');
 +test("{ accept: Function }", function() {
-       
++
 +      expect( 5 );
-       
++
 +      var target = $("#droppable1").droppable(),
 +              source = $("#draggable1").draggable(),
 +              counter = 0;
-               
-               
++
 +      function accept() {
 +              ++counter;
 +              return ( counter%2 === 0 );
 +      }
-       
++
 +      target.droppable( "option", "accept", accept );
-       
++
 +      TestHelpers.droppable.shouldNotDrop( source, target, "Function returns false" );
 +      TestHelpers.droppable.shouldDrop( source, target, "Function returns true" );
 +      TestHelpers.droppable.shouldNotDrop( source, target, "Function returns false" );
 +      TestHelpers.droppable.shouldDrop( source, target, "Function returns true" );
-       
++
 +      target.droppable( "option", "accept", "*" );
 +      TestHelpers.droppable.shouldDrop( source, target, "Reset back to *" );
-       
- });
 +
+ });
  
  test("activeClass", function() {
 -      ok(false, 'missing test - untested code is broken code');
 -});
 -*/
 -test("{ addClasses: true }, default", function() {
 -      expect( 1 );
 -      var el = $("<div></div>").droppable({ addClasses: true });
 -      ok(el.is(".ui-droppable"), "'ui-droppable' class added");
 -      el.droppable("destroy");
 -});
  
 -test("{ addClasses: false }", function() {
 -      expect( 1 );
 -      var el = $("<div></div>").droppable({ addClasses: false });
 -      ok(!el.is(".ui-droppable"), "'ui-droppable' class not added");
 -      el.droppable("destroy");
 +      expect( 6 );
 +
 +      var target = $("#droppable1").droppable(),
 +              source = $("#draggable1").draggable(),
 +              myclass = "myclass",
 +              hasclass = false;
-               
++
 +      source.on( "drag", function() {
 +              hasclass = target.hasClass( myclass );
 +      });
 +
 +      TestHelpers.droppable.shouldDrop( source, target );
 +      equal( hasclass, false, "Option not set yet" );
-       
++
 +      target.droppable( "option", "activeClass", myclass );
 +      TestHelpers.droppable.shouldDrop( source, target );
 +      equal( hasclass, true, "Option set" );
-       
++
 +      target.droppable( "option", "activeClass", false );
 +      TestHelpers.droppable.shouldDrop( source, target );
 +      equal( hasclass, false, "Option unset" );
-       
-       
-       
++
  });
  
+ test( "scope", function() {
+       expect( 4 );
+       var droppableOffset, draggableOffset, oldDraggableOffset, dx, dy,
+                       draggable1 = $("<div></div>").appendTo( "#qunit-fixture" ).draggable({ revert: "invalid" }),
+                       draggable2 = $("<div></div>").appendTo( "#qunit-fixture" ).droppable(),
+                       droppable = $("<div></div>").appendTo( "#qunit-fixture" ).droppable(),
+                       newScope = "test";
+       draggable1.draggable( "option", "scope", newScope );
+       droppable.droppable( "option", "scope", newScope );
+       // Test that droppable accepts draggable with new scope.
+       droppableOffset = droppable.offset();
+       draggableOffset = draggable1.offset();
+       dx = droppableOffset.left - draggableOffset.left;
+       dy = droppableOffset.top - draggableOffset.top;
+       draggable1.simulate( "drag", {
+               dx: dx,
+               dy: dy
+       });
+       draggableOffset = draggable1.offset();
+       equal( draggableOffset.left, droppableOffset.left );
+       equal( draggableOffset.top, droppableOffset.top );
+       // Test that droppable doesn't accept draggable with old scope.
+       draggableOffset = draggable2.offset();
+       dx = droppableOffset.left - draggableOffset.left;
+       dy = droppableOffset.top - draggableOffset.top;
+       oldDraggableOffset = draggableOffset;
+       draggable2.simulate( "drag", {
+               dx: dx,
+               dy: dy
+       });
  
+       draggableOffset = draggable2.offset();
+       equal( draggableOffset.left, oldDraggableOffset.left );
+       equal( draggableOffset.top, oldDraggableOffset.top );
+ });
  /*
 +
  test("greedy", function() {
        ok(false, 'missing test - untested code is broken code');
  });
index 6e326a8657c994f8ca20405180a606da06d39ec2,ac1c037dd16c5135611e1fc144a10d5b788ff38e..481bd2c29e32a75c0f8154f2f21b144e64338258
        <script src="../testsuite.js"></script>
        <script>
        TestHelpers.loadResources({
-               css: [ "ui.core" ],
+               css: [ "core" ],
                js: [
-                       "ui/jquery.ui.core.js",
-                       "ui/jquery.ui.widget.js",
-                       "ui/jquery.ui.mouse.js",
-                       "ui/jquery.ui.sortable.js"
+                       "ui/core.js",
+                       "ui/widget.js",
 -                      "ui/mouse.js",
++                      "ui/interaction.js",
+                       "ui/sortable.js"
                ]
        });
        </script>
index 86850a658c3c77bb100e122664e50df3d97ec645,86850a658c3c77bb100e122664e50df3d97ec645..7ba91d528c78578ae639213ea7e0532689e76a4d
@@@ -25,7 -25,7 +25,6 @@@ TestHelpers.commonWidgetTests( "sortabl
                scrollSpeed: 20,
                scope: "default",
                tolerance: "intersect",
--              zIndex: 1000,
  
                // callbacks
                activate: null,
index 1b8165acba382865bbe1bb87804a427d40c634b9,8333b8bd470b4f94ae4129412cbf38733c665ad1..5898d63767de8c2740ee73048d58623c440ddf76
@@@ -18,8 -18,8 +18,7 @@@ test("start", function() 
        });
  
        ok(hash, "start event triggered");
--      ok(hash.helper, "UI hash includes: helper");
--      ok(hash.placeholder, "UI hash includes: placeholder");
++      ok(!hash.helper, "UI hash includes: helper");
        ok(hash.item, "UI hash includes: item");
        ok(!hash.sender, "UI hash does not include: sender");
  
@@@ -27,7 -27,7 +26,6 @@@
        ok("position" in hash, "UI hash includes: position");
        ok("offset" in hash, "UI hash includes: offset");
  
--
  });
  
  test("sort", function() {
@@@ -43,8 -43,8 +41,7 @@@
        });
  
        ok(hash, "sort event triggered");
--      ok(hash.helper, "UI hash includes: helper");
--      ok(hash.placeholder, "UI hash includes: placeholder");
++      ok(!hash.helper, "UI hash does not includes: helper");
        ok(hash.position && ("top" in hash.position && "left" in hash.position), "UI hash includes: position");
        ok(hash.offset && (hash.offset.top && hash.offset.left), "UI hash includes: offset");
        ok(hash.item, "UI hash includes: item");
@@@ -177,28 -177,28 +174,28 @@@ test("#3019: Stop fires too early", fun
  
  });
  
--test("#4752: link event firing on sortable with connect list", function () {
++test("#4752: link event firing on sortable with connect list", function() {
        expect( 10 );
  
        var fired = {},
--              hasFired = function (type) { return (type in fired) && (true === fired[type]); };
++              hasFired = function(type) { return (type in fired) && (true === fired[type]); };
  
        $("#sortable").clone().attr("id", "sortable2").insertAfter("#sortable");
  
        $("#qunit-fixture ul").sortable({
                connectWith: "#qunit-fixture ul",
--              change: function () {
++              change: function() {
                        fired.change = true;
                },
--              receive: function () {
++              receive: function() {
                        fired.receive = true;
                },
--              remove: function () {
++              remove: function() {
                        fired.remove = true;
                }
        });
  
--      $("#qunit-fixture ul").bind("click.ui-sortable-test", function () {
++      $("#qunit-fixture ul").bind("click.ui-sortable-test", function() {
                fired.click = true;
        });
  
diff --cc ui/draggable.js
index 0000000000000000000000000000000000000000,13f898c8cbad58115791f95dedfe91bbf910d768..1e7990e27c93f143bfa41b1e56a0a3338d4ba95e
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1019 +1,1235 @@@
 -                      "./mouse",
 -                      "./widget"
+ /*!
+  * jQuery UI Draggable @VERSION
+  * http://jqueryui.com
+  *
+  * Copyright 2014 jQuery Foundation and other contributors
+  * Released under the MIT license.
+  * http://jquery.org/license
+  *
+  * http://api.jqueryui.com/draggable/
+  */
+ (function( factory ) {
+       if ( typeof define === "function" && define.amd ) {
+               // AMD. Register as an anonymous module.
+               define([
+                       "jquery",
+                       "./core",
 -$.widget("ui.draggable", $.ui.mouse, {
++                      "./widget",
++                      "./interaction"
+               ], factory );
+       } else {
+               // Browser globals
+               factory( jQuery );
+       }
+ }(function( $ ) {
 -              addClasses: true,
 -              appendTo: "parent",
 -              axis: false,
 -              connectToSortable: false,
 -              containment: false,
 -              cursor: "auto",
 -              cursorAt: false,
 -              grid: false,
 -              handle: false,
 -              helper: "original",
 -              iframeFix: false,
 -              opacity: false,
 -              refreshPositions: false,
 -              revert: false,
 -              revertDuration: 500,
 -              scope: "default",
 -              scroll: true,
 -              scrollSensitivity: 20,
 -              scrollSpeed: 20,
 -              snap: false,
 -              snapMode: "both",
 -              snapTolerance: 20,
 -              stack: false,
 -              zIndex: false,
++// Create a shallow copy of an object
++function copy( obj ) {
++      var prop,
++              ret = {};
++      for ( prop in obj ) {
++              ret[ prop ] = obj[ prop ];
++      }
++      return ret;
++}
++
++$.widget( "ui.draggable", $.ui.interaction, {
+       version: "@VERSION",
+       widgetEventPrefix: "drag",
++
+       options: {
 -      _create: function() {
++              appendTo: null,
++              exclude: "input,textarea,button,select",
++              handle: null,
++              helper: false,
+               // callbacks
++              beforeStart: null,
+               drag: null,
+               start: null,
+               stop: null
+       },
 -              if (this.options.helper === "original" && !(/^(?:r|a|f)/).test(this.element.css("position"))) {
 -                      this.element[0].style.position = "relative";
 -              }
 -              if (this.options.addClasses){
 -                      this.element.addClass("ui-draggable");
 -              }
 -              if (this.options.disabled){
 -                      this.element.addClass("ui-draggable-disabled");
 -              }
 -              this._setHandleClassName();
 -              this._mouseInit();
 -      },
++      // dragEl: element being dragged (original or helper)
++      // position: final CSS position of dragEl
++      // offset: offset of dragEl
++      // originalPosition: CSS position before drag start
++      // originalOffset: offset before drag start
++      // originalPointer: pageX/Y at drag start (offset of pointer)
++      // startPosition: CSS position at drag start (after beforeStart)
++      // startOffset: offset at drag start (after beforeStart)
++      // tempPosition: overridable CSS position of dragEl
++      // overflowOffset: offset of scroll parent
++      // overflow: object containing width and height keys of scroll parent
++      // domPosition: object containing original parent and index when using
++      // appendTo option without a helper
++      // dragDimensions: saved off width and height used for various options
++
++      scrollSensitivity: 20,
++      scrollSpeed: 5,
 -      _setOption: function( key, value ) {
 -              this._super( key, value );
 -              if ( key === "handle" ) {
 -                      this._setHandleClassName();
++      _create: function() {
++              this._super();
 -      },
++              // Static position elements can't be moved with top/left
++              if ( this.element.css( "position" ) === "static" ) {
++                      this.element.css( "position", "relative" );
+               }
 -      _destroy: function() {
 -              if ( ( this.helper || this.element ).is( ".ui-draggable-dragging" ) ) {
 -                      this.destroyOnClear = true;
 -                      return;
 -              }
 -              this.element.removeClass( "ui-draggable ui-draggable-dragging ui-draggable-disabled" );
 -              this._removeHandleClassName();
 -              this._mouseDestroy();
 -      _mouseCapture: function(event) {
 -
 -              var document = this.document[ 0 ],
 -                      o = this.options;
 -
 -              // support: IE9
 -              // IE9 throws an "Unspecified error" accessing document.activeElement from an <iframe>
 -              try {
 -                      // Support: IE9+
 -                      // If the <body> is blurred, IE will switch windows, see #9520
 -                      if ( document.activeElement && document.activeElement.nodeName.toLowerCase() !== "body" ) {
 -                              // Blur any element that currently has focus, see #4261
 -                              $( document.activeElement ).blur();
 -                      }
 -              } catch ( error ) {}
 -
 -              // among others, prevent a drag on a resizable-handle
 -              if (this.helper || o.disabled || $(event.target).closest(".ui-resizable-handle").length > 0) {
 -                      return false;
 -              }
 -
 -              //Quit if we're not on a valid handle
 -              this.handle = this._getHandle(event);
 -              if (!this.handle) {
 -                      return false;
 -              }
 -
 -              $(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() {
 -                      $("<div class='ui-draggable-iframeFix' style='background: #fff;'></div>")
 -                      .css({
 -                              width: this.offsetWidth+"px", height: this.offsetHeight+"px",
 -                              position: "absolute", opacity: "0.001", zIndex: 1000
 -                      })
 -                      .css($(this).offset())
 -                      .appendTo("body");
 -              });
++              this.element.addClass( "ui-draggable" );
+       },
 -              return true;
++      /** interaction interface **/
 -      _mouseStart: function(event) {
++      _isValidTarget: function( element ) {
++              var handle = this.options.handle ?
++                              element.is( this.element.find( this.options.handle ) ) :
++                              true,
++                      exclude = this.options.exclude ?
++                              element.is( this.element.find( this.options.exclude ) ) :
++                              false;
++              return handle && !exclude;
+       },
 -              var o = this.options;
++      _start: function( event, pointerPosition ) {
++              var offset, startCssLeft, startCssTop, startPosition, startOffset;
 -              //Create and append the visible helper
 -              this.helper = this._createHelper(event);
++              // Reset
++              this.dragDimensions = null;
 -              this.helper.addClass("ui-draggable-dragging");
 -
 -              //Cache the helper size
 -              this._cacheHelperProportions();
 -
 -              //If ddmanager is used for droppables, set the global draggable
 -              if($.ui.ddmanager) {
 -                      $.ui.ddmanager.current = this;
++              // The actual dragging element, should always be a jQuery object
++              this.dragEl = this.options.helper ?
++                      this._createHelper( pointerPosition ) :
++                      this.element;
 -              /*
 -               * - Position generation -
 -               * This block generates everything position related - it's the core of draggables.
 -               */
++              // _createHelper() ensures that helpers are in the correct position
++              // in the DOM, but we need to handle appendTo when there is no helper
++              if ( this.options.appendTo && this.dragEl === this.element ) {
++                      this.domPosition = {
++                              parent: this.element.parent(),
++                              index: this.element.index()
++                      };
++                      offset = this.dragEl.offset();
++                      this.dragEl
++                              .appendTo( this._appendTo() )
++                              .offset( offset );
+               }
 -              //Cache the margins of the original element
 -              this._cacheMargins();
++              this.cssPosition = this.dragEl.css( "position" );
++              this.scrollParent = this.element.scrollParent();
 -              //Store the helper's css position
 -              this.cssPosition = this.helper.css( "position" );
 -              this.scrollParent = this.helper.scrollParent();
 -              this.offsetParent = this.helper.offsetParent();
 -              this.offsetParentCssPosition = this.offsetParent.css( "position" );
 -
 -              //The element's absolute position on the page minus margins
 -              this.offset = this.positionAbs = this.element.offset();
 -              this.offset = {
 -                      top: this.offset.top - this.margins.top,
 -                      left: this.offset.left - this.margins.left
 -              };
++              // Cache starting positions
++              this.originalPosition = this.startPosition = this._getPosition();
++              this.originalOffset = this.startOffset = this.dragEl.offset();
++              this.originalPointer = pointerPosition;
 -              //Reset scroll cache
 -              this.offset.scroll = false;
++              // If not already cached within _createHelper()
++              if ( !this.dragDimensions ) {
++                      this._cacheDragDimensions( this.dragEl );
++              }
 -              $.extend(this.offset, {
 -                      click: { //Where the click happened, relative to the element
 -                              left: event.pageX - this.offset.left,
 -                              top: event.pageY - this.offset.top
 -                      },
 -                      parent: this._getParentOffset(),
 -                      relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
 -              });
++              // Cache current position and offset
++              this.position = copy( this.startPosition );
++              this.offset = copy( this.startOffset );
 -              //Generate the original position
 -              this.originalPosition = this.position = this._generatePosition( event, false );
 -              this.originalPageX = event.pageX;
 -              this.originalPageY = event.pageY;
++              // Cache the offset of scrollParent, if required for _handleScrolling()
++              if ( this.scrollParent[ 0 ] !== this.document[ 0 ] &&
++                              this.scrollParent[ 0 ].tagName.toUpperCase() !== "HTML" ) {
++                      this.overflowOffset = this.scrollParent.offset();
++              }
 -              //Adjust the mouse offset relative to the helper if "cursorAt" is supplied
 -              (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
++              this.overflow = {
++                      height: this.scrollParent[ 0 ] === this.document[ 0 ] ?
++                              this.window.height() : this.scrollParent.height(),
++                      width: this.scrollParent[ 0 ] === this.document[ 0 ] ?
++                              this.window.width() : this.scrollParent.width()
++              };
 -              //Set a containment if given in the options
 -              this._setContainment();
++              this._preparePosition( pointerPosition );
 -              //Trigger event + callbacks
 -              if(this._trigger("start", event) === false) {
 -                      this._clear();
++              // If user cancels beforeStart, don't allow dragging
++              if ( this._trigger( "beforeStart", event,
++                              this._originalHash( pointerPosition ) ) === false ) {
 -              //Recache the helper size
 -              this._cacheHelperProportions();
++                      // domPosition needs to be undone even if beforeStart is stopped,
++                      // otherwise this.dragEl will remain in the `appendTo` element
++                      this._resetDomPosition();
+                       return false;
+               }
 -              //Prepare the droppable offsets
 -              if ($.ui.ddmanager && !o.dropBehaviour) {
 -                      $.ui.ddmanager.prepareOffsets(this, event);
++              // Save off the usual properties locally, so they can be reverted
++              startCssLeft = this.dragEl.css( "left" );
++              startCssTop = this.dragEl.css( "top" );
++              startPosition = copy( this._getPosition() );
++              startOffset = copy( this.offset );
++
++              this._setCss();
++              this.startPosition = this._getPosition();
++              this.startOffset = this.dragEl.offset();
++
++              // If user cancels on start, don't allow dragging
++              if ( this._trigger( "start", event,
++                              this._fullHash( pointerPosition ) ) === false ) {
++
++                      // domPosition needs to be undone even if start is stopped,
++                      // otherwise this.dragEl will remain in the `appendTo` element
++                      this.startPosition = startPosition;
++                      this.startOffset = startOffset;
++                      this.dragEl.css({
++                              left: startCssLeft,
++                              top: startCssTop
++                      });
 -              this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position
++                      this._resetDomPosition();
++                      return false;
+               }
++              this._blockFrames();
++      },
 -              //If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003)
 -              if ( $.ui.ddmanager ) {
 -                      $.ui.ddmanager.dragStart(this, event);
++      _move: function( event, pointerPosition ) {
++              this._preparePosition( pointerPosition );
 -              return true;
 -      },
++              // If user cancels drag, don't move the element
++              if ( this._trigger( "drag", event,
++                              this._fullHash( pointerPosition ) ) === false ) {
++                      return;
+               }
 -      _mouseDrag: function(event, noPropagation) {
 -              // reset any necessary cached properties (see #5009)
 -              if ( this.offsetParentCssPosition === "fixed" ) {
 -                      this.offset.parent = this._getParentOffset();
 -              }
++              this._setCss();
 -              //Compute the helpers position
 -              this.position = this._generatePosition( event, true );
 -              this.positionAbs = this._convertPositionTo("absolute");
++              // Scroll the scrollParent, if needed
++              this._handleScrolling( pointerPosition );
++      },
 -              //Call plugins and callbacks and use the resulting position if something is returned
 -              if (!noPropagation) {
 -                      var ui = this._uiHash();
 -                      if(this._trigger("drag", event, ui) === false) {
 -                              this._mouseUp({});
 -                              return false;
++      _stop: function( event, pointerPosition ) {
++              this._preparePosition( pointerPosition );
 -                      this.position = ui.position;
 -              }
 -
 -              this.helper[ 0 ].style.left = this.position.left + "px";
 -              this.helper[ 0 ].style.top = this.position.top + "px";
 -
 -              if($.ui.ddmanager) {
 -                      $.ui.ddmanager.drag(this, event);
++              // If user cancels stop, leave helper there
++              if ( this._trigger( "stop", event,
++                              this._fullHash( pointerPosition ) ) !== false ) {
++                      if ( this.options.helper ) {
++                              delete this.helper;
++                              this.dragEl.remove();
+                       }
 -              return false;
++                      this._resetDomPosition();
+               }
 -      _mouseStop: function(event) {
++              this._unblockFrames();
+       },
 -              //If we are using droppables, inform the manager about the drop
 -              var that = this,
 -                      dropped = false;
 -              if ($.ui.ddmanager && !this.options.dropBehaviour) {
 -                      dropped = $.ui.ddmanager.drop(this, event);
++      /** internal **/
 -              //if a drop comes from outside (a sortable)
 -              if(this.dropped) {
 -                      dropped = this.dropped;
 -                      this.dropped = false;
++      _createHelper: function( pointerPosition ) {
++              var offset = this.element.offset(),
++                      xPos = (pointerPosition.x - offset.left) / this.element.outerWidth(),
++                      yPos = (pointerPosition.y - offset.top) / this.element.outerHeight();
++
++              // Clone
++              if ( this.options.helper === true ) {
++                      this.helper = this.element.clone()
++                              .removeAttr( "id" )
++                              .find( "[id]" )
++                                      .removeAttr( "id" )
++                              .end();
++              } else {
++                      // TODO: figure out the signature for this; see #4957
++                      this.helper = $( this.options.helper() );
+               }
 -              if((this.options.revert === "invalid" && !dropped) || (this.options.revert === "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) {
 -                      $(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() {
 -                              if(that._trigger("stop", event) !== false) {
 -                                      that._clear();
 -                              }
++              // Ensure the helper is in the DOM; obey the appendTo option if it exists
++              if ( this.options.appendTo || !this.helper.closest( "body" ).length ) {
++                      this.helper.appendTo( this._appendTo() || this.document[ 0 ].body );
+               }
 -              } else {
 -                      if(this._trigger("stop", event) !== false) {
 -                              this._clear();
 -                      }
 -              }
++              this._cacheDragDimensions( this.helper );
++
++              return this.helper
++                      // Helper must be absolute to function properly
++                      .css( "position", "absolute" )
++                      .offset({
++                              left: pointerPosition.x - this.dragDimensions.width * xPos,
++                              top: pointerPosition.y - this.dragDimensions.height * yPos
+                       });
 -              return false;
++      },
 -      _mouseUp: function(event) {
 -              //Remove frame helpers
 -              $("div.ui-draggable-iframeFix").each(function() {
 -                      this.parentNode.removeChild(this);
 -              });
++      _cacheDragDimensions: function( element ) {
++              this.dragDimensions = {
++                      width: element.outerWidth(),
++                      height: element.outerHeight()
++              };
+       },
 -              //If the ddmanager is used for droppables, inform the manager that dragging has stopped (see #5003)
 -              if( $.ui.ddmanager ) {
 -                      $.ui.ddmanager.dragStop(this, event);
++      _resetDomPosition: function() {
 -              // The interaction is over; whether or not the click resulted in a drag, focus the element
 -              this.element.focus();
 -
 -              return $.ui.mouse.prototype._mouseUp.call(this, event);
 -      },
 -
 -      cancel: function() {
 -
 -              if(this.helper.is(".ui-draggable-dragging")) {
 -                      this._mouseUp({});
++              // Nothing to do in this case
++              if ( !this.domPosition ) {
++                      return;
+               }
 -                      this._clear();
++              var parent = this.domPosition.parent,
++                      next = parent.children().eq( this.domPosition.index );
++              if ( next.length ) {
++                      next.before( this.element );
+               } else {
 -
 -              return this;
 -
++                      parent.append( this.element );
+               }
 -      _getHandle: function(event) {
 -              return this.options.handle ?
 -                      !!$( event.target ).closest( this.element.find( this.options.handle ) ).length :
 -                      true;
++              this.element.offset( this.offset );
++              this.domPosition = null;
+       },
 -      _setHandleClassName: function() {
 -              this._removeHandleClassName();
 -              $( this.options.handle || this.element ).addClass( "ui-draggable-handle" );
 -      },
++      // TODO: Remove after 2.0, only used for backCompat
++      _appendTo: function() {
++              return this.options.appendTo;
+       },
 -      _removeHandleClassName: function() {
 -              this.element.find( ".ui-draggable-handle" )
 -                      .addBack()
 -                      .removeClass( "ui-draggable-handle" );
 -      },
 -
 -      _createHelper: function(event) {
++      _getPosition: function() {
++              var left, top, position,
++                      scrollTop = this.scrollParent.scrollTop(),
++                      scrollLeft = this.scrollParent.scrollLeft();
 -              var o = this.options,
 -                      helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event])) : (o.helper === "clone" ? this.element.clone().removeAttr("id") : this.element);
++              // If fixed or absolute
++              if ( this.cssPosition !== "relative" ) {
++                      position = this.dragEl.position();
 -              if(!helper.parents("body").length) {
 -                      helper.appendTo((o.appendTo === "parent" ? this.element[0].parentNode : o.appendTo));
++                      // Take into account scrollbar
++                      position.top -= scrollTop;
++                      position.left -= scrollLeft;
 -              if(helper[0] !== this.element[0] && !(/(fixed|absolute)/).test(helper.css("position"))) {
 -                      helper.css("position", "absolute");
 -              }
++                      return position;
+               }
 -              return helper;
++              // When using relative, css values are checked,
++              // otherwise the position wouldn't account for padding on ancestors
++              left = this.dragEl.css( "left" );
++              top = this.dragEl.css( "top" );
 -      _adjustOffsetFromHelper: function(obj) {
 -              if (typeof obj === "string") {
 -                      obj = obj.split(" ");
 -              }
 -              if ($.isArray(obj)) {
 -                      obj = {left: +obj[0], top: +obj[1] || 0};
 -              }
 -              if ("left" in obj) {
 -                      this.offset.click.left = obj.left + this.margins.left;
 -              }
 -              if ("right" in obj) {
 -                      this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
 -              }
 -              if ("top" in obj) {
 -                      this.offset.click.top = obj.top + this.margins.top;
++              // TODO: add proper support comment
++              // Webkit will give back auto if there is no explicit value
++              left = ( left === "auto" ) ? 0: parseInt( left, 10 );
++              top = ( top === "auto" ) ? 0: parseInt( top, 10 );
++              return {
++                      left: left - scrollLeft,
++                      top: top - scrollTop
++              };
+       },
 -              if ("bottom" in obj) {
 -                      this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
++      _handleScrolling: function( pointerPosition ) {
++              var newScrollTop, newScrollLeft, change,
++                      scrollTop = this.scrollParent.scrollTop(),
++                      scrollLeft = this.scrollParent.scrollLeft(),
++                      scrollSensitivity = this.scrollSensitivity,
++
++                      // overflowOffset is only set when scrollParent is not doc/html
++                      overflowLeft = this.overflowOffset ?
++                              this.overflowOffset.left :
++                              scrollLeft,
++                      overflowTop = this.overflowOffset ?
++                              this.overflowOffset.top :
++                              scrollTop,
++                      xRight = this.overflow.width + overflowLeft - pointerPosition.x,
++                      xLeft = pointerPosition.x - overflowLeft,
++                      yBottom = this.overflow.height + overflowTop - pointerPosition.y,
++                      yTop = pointerPosition.y - overflowTop;
++
++              // Handle vertical scrolling
++              if ( yBottom < scrollSensitivity ) {
++                      change = this._speed( scrollSensitivity - yBottom );
++                      this.scrollParent.scrollTop( scrollTop + change );
++                      this.originalPointer.y = this.originalPointer.y + change;
++              } else if ( yTop < scrollSensitivity ) {
++                      change = this._speed( scrollSensitivity - yTop );
++                      newScrollTop = scrollTop - change;
++
++                      // Don't do anything unless new value is "real"
++                      if ( newScrollTop >= 0 ) {
++                              this.scrollParent.scrollTop( newScrollTop );
++                              this._speed( scrollSensitivity - yTop );
++                              this.originalPointer.y = this.originalPointer.y - change;
++                      }
+               }
 -      _isRootNode: function( element ) {
 -              return ( /(html|body)/i ).test( element.tagName ) || element === this.document[ 0 ];
++
++              // Handle horizontal scrolling
++              if ( xRight < scrollSensitivity ) {
++                      change = this._speed( scrollSensitivity - xRight );
++                      this.scrollParent.scrollLeft( scrollLeft + change);
++                      this.originalPointer.x = this.originalPointer.x + change;
++              } else if ( xLeft < scrollSensitivity ) {
++                      change = this._speed( scrollSensitivity - xLeft );
++                      newScrollLeft = scrollLeft - change;
++
++                      // Don't do anything unless new value is "real"
++                      if ( newScrollLeft >= 0 ) {
++                              this.scrollParent.scrollLeft( newScrollLeft );
++                              this.originalPointer.x = this.originalPointer.x - change;
++                      }
+               }
+       },
 -      _getParentOffset: function() {
++      _speed: function( distance ) {
++              return this.scrollSpeed + Math.round( distance / 2 );
+       },
 -              //Get the offsetParent and cache its position
 -              var po = this.offsetParent.offset(),
 -                      document = this.document[ 0 ];
++      // Uses event to determine new position of draggable, before any override
++      // from callbacks
++      // TODO: handle absolute element inside relative parent like a relative element
++      _preparePosition: function( pointerPosition ) {
++              var leftDiff = pointerPosition.x - this.originalPointer.x,
++                      topDiff = pointerPosition.y - this.originalPointer.y,
++                      newLeft = leftDiff + this.startPosition.left,
++                      newTop = topDiff + this.startPosition.top;
++
++              // Save off new values for .css() in various callbacks using this function
++              this.position = {
++                      left: newLeft,
++                      top: newTop
++              };
 -              // This is a special case where we need to modify a offset calculated on start, since the following happened:
 -              // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
 -              // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
 -              //    the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
 -              if(this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) {
 -                      po.left += this.scrollParent.scrollLeft();
 -                      po.top += this.scrollParent.scrollTop();
 -              }
++              // Save off values to compare user override against automatic coordinates
++              this.tempPosition = {
++                      left: newLeft,
++                      top: newTop
++              };
 -              if ( this._isRootNode( this.offsetParent[ 0 ] ) ) {
 -                      po = { top: 0, left: 0 };
 -              }
++              // Refresh offset cache with new positions
++              this.offset.left = this.startOffset.left + leftDiff;
++              this.offset.top = this.startOffset.top + topDiff;
++      },
 -              return {
 -                      top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
 -                      left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
 -              };
++      // Places draggable where event indicates
++      _setCss: function() {
++              var newLeft = this.position.left,
++                      newTop = this.position.top;
 -      },
++              // User overriding left/top so shortcut math is no longer valid
++              if ( this.tempPosition.left !== this.position.left ||
++                              this.tempPosition.top !== this.position.top ) {
 -      _getRelativeOffset: function() {
 -              if ( this.cssPosition !== "relative" ) {
 -                      return { top: 0, left: 0 };
++                      // Reset offset based on difference of expected and overridden values
++                      this.offset.left += newLeft - this.tempPosition.left;
++                      this.offset.top += newTop - this.tempPosition.top;
++              }
 -              var p = this.element.position(),
 -                      scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] );
++              // TODO: does this work with nested scrollable parents?
++              if ( this.cssPosition !== "fixed" ) {
++                      newLeft += this.scrollParent.scrollLeft();
++                      newTop += this.scrollParent.scrollTop();
+               }
 -              return {
 -                      top: p.top - ( parseInt(this.helper.css( "top" ), 10) || 0 ) + ( !scrollIsRootNode ? this.scrollParent.scrollTop() : 0 ),
 -                      left: p.left - ( parseInt(this.helper.css( "left" ), 10) || 0 ) + ( !scrollIsRootNode ? this.scrollParent.scrollLeft() : 0 )
++              this.dragEl.css({
++                      left: newLeft,
++                      top: newTop
++              });
++      },
 -      _cacheMargins: function() {
 -              this.margins = {
 -                      left: (parseInt(this.element.css("marginLeft"),10) || 0),
 -                      top: (parseInt(this.element.css("marginTop"),10) || 0),
 -                      right: (parseInt(this.element.css("marginRight"),10) || 0),
 -                      bottom: (parseInt(this.element.css("marginBottom"),10) || 0)
 -              };
++      _originalHash: function( pointerPosition ) {
++              var ret = {
++                      position: this.position,
++                      offset: copy( this.offset ),
++                      pointer: copy( pointerPosition )
+               };
++              if ( this.options.helper ) {
++                      ret.helper = this.dragEl;
++              }
++
++              return ret;
+       },
 -      _cacheHelperProportions: function() {
 -              this.helperProportions = {
 -                      width: this.helper.outerWidth(),
 -                      height: this.helper.outerHeight()
 -              };
++      _fullHash: function( pointerPosition ) {
++              return $.extend( this._originalHash( pointerPosition ), {
++                      originalPosition: copy( this.originalPosition ),
++                      originalOffset: copy( this.originalOffset ),
++                      originalPointer: copy( this.originalPointer )
++              });
+       },
 -      _setContainment: function() {
++      _blockFrames: function() {
++              this.iframeBlocks = this.document.find( "iframe" ).map(function() {
++                      var iframe = $( this );
++
++                      return $( "<div>" )
++                              .css( "position", "absolute" )
++                              .appendTo( iframe.parent() )
++                              .outerWidth( iframe.outerWidth() )
++                              .outerHeight( iframe.outerHeight() )
++                              .offset( iframe.offset() )[ 0 ];
++              });
+       },
 -              var over, c, ce,
 -                      o = this.options,
 -                      document = this.document[ 0 ];
++      _unblockFrames: function() {
++              if ( this.iframeBlocks ) {
++                      this.iframeBlocks.remove();
++                      delete this.iframeBlocks;
++              }
++      },
 -              this.relative_container = null;
++      _destroy: function() {
++              this.element.removeClass( "ui-draggable" );
++              this._super();
++      }
++});
 -              if ( !o.containment ) {
 -                      this.containment = null;
 -                      return;
 -              }
++// Add containment option via extension
++$.widget( "ui.draggable", $.ui.draggable, {
++      options: {
++              containment: null
++      },
 -              if ( o.containment === "window" ) {
 -                      this.containment = [
 -                              $( window ).scrollLeft() - this.offset.relative.left - this.offset.parent.left,
 -                              $( window ).scrollTop() - this.offset.relative.top - this.offset.parent.top,
 -                              $( window ).scrollLeft() + $( window ).width() - this.helperProportions.width - this.margins.left,
 -                              $( window ).scrollTop() + ( $( window ).height() || document.body.parentNode.scrollHeight ) - this.helperProportions.height - this.margins.top
 -                      ];
 -                      return;
 -              }
++      _create: function() {
++              this._super();
++              this._on({
++                      dragstart: "_setContainment",
++                      drag: "_contain"
++              });
++      },
 -              if ( o.containment === "document") {
 -                      this.containment = [
 -                              0,
 -                              0,
 -                              $( document ).width() - this.helperProportions.width - this.margins.left,
 -                              ( $( document ).height() || document.body.parentNode.scrollHeight ) - this.helperProportions.height - this.margins.top
 -                      ];
++      _setContainment: function( event, ui ) {
++              var offset, left, top, bottom, right,
++                      container = this._getContainer();
 -              if ( o.containment.constructor === Array ) {
 -                      this.containment = o.containment;
 -                      return;
++              if ( !container ) {
++                      this.containment = null;
+                       return;
+               }
 -              if ( o.containment === "parent" ) {
 -                      o.containment = this.helper[ 0 ].parentNode;
 -              }
++              if ( $.isArray( container ) ) {
++                      offset = container.offset();
++                      left = offset.left +
++                              (parseFloat( $.css( container[ 0 ], "borderLeftWidth", true ) ) || 0) +
++                              (parseFloat( $.css( container[ 0 ], "paddingLeft", true ) ) || 0);
++                      top = offset.top +
++                              (parseFloat( $.css( container[ 0 ], "borderTopWidth", true ) ) || 0) +
++                              (parseFloat( $.css( container[ 0 ], "paddingTop", true ) ) || 0);
++                      right = left + container.width();
++                      bottom = top + container.height();
++              } else {
++                      left = container[ 0 ];
++                      top = container[ 1 ];
++                      right = container[ 2 ];
++                      bottom = container[ 3 ];
+               }
 -              c = $( o.containment );
 -              ce = c[ 0 ];
++              this.containment = {
++                      left: left,
++                      top: top,
++                      right: right,
++                      bottom: bottom,
++                      leftDiff: ui.originalOffset.left - ui.originalPosition.left,
++                      topDiff: ui.originalOffset.top - ui.originalPosition.top,
++                      width: this.dragDimensions.width,
++                      height: this.dragDimensions.height
++              };
++      },
 -              if( !ce ) {
++      _contain: function( event, ui ) {
++              var containment = this.containment;
 -              over = c.css( "overflow" ) !== "hidden";
 -
 -              this.containment = [
 -                      ( parseInt( c.css( "borderLeftWidth" ), 10 ) || 0 ) + ( parseInt( c.css( "paddingLeft" ), 10 ) || 0 ),
 -                      ( parseInt( c.css( "borderTopWidth" ), 10 ) || 0 ) + ( parseInt( c.css( "paddingTop" ), 10 ) || 0 ) ,
 -                      ( over ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) - ( parseInt( c.css( "borderRightWidth" ), 10 ) || 0 ) - ( parseInt( c.css( "paddingRight" ), 10 ) || 0 ) - this.helperProportions.width - this.margins.left - this.margins.right,
 -                      ( over ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) - ( parseInt( c.css( "borderBottomWidth" ), 10 ) || 0 ) - ( parseInt( c.css( "paddingBottom" ), 10 ) || 0 ) - this.helperProportions.height - this.margins.top  - this.margins.bottom
 -              ];
 -              this.relative_container = c;
++              if ( !containment ) {
+                       return;
+               }
 -      _convertPositionTo: function(d, pos) {
++              ui.position.left = Math.max( ui.position.left,
++                      containment.left - containment.leftDiff );
++              ui.position.left = Math.min( ui.position.left,
++                      containment.right - containment.width - containment.leftDiff );
++              ui.position.top = Math.max( ui.position.top,
++                      containment.top - containment.topDiff );
++              ui.position.top = Math.min( ui.position.top,
++                      containment.bottom - containment.height - containment.topDiff );
+       },
 -              if(!pos) {
 -                      pos = this.position;
++      _getContainer: function() {
++              var container,
++                      containment = this.options.containment;
 -              var mod = d === "absolute" ? 1 : -1,
 -                      scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] );
++              if ( !containment ) {
++                      container = null;
++              } else if ( containment === "parent" ) {
++                      container = this.element.parent();
++              } else {
++                      container = $( containment );
++                      if ( !container.length ) {
++                              container = null;
++                      }
+               }
 -              return {
 -                      top: (
 -                              pos.top +                                                                                                                               // The absolute mouse position
 -                              this.offset.relative.top * mod +                                                                                // Only for relative positioned nodes: Relative offset from element to offset parent
 -                              this.offset.parent.top * mod -                                                                          // The offsetParent's offset without borders (offset + border)
 -                              ( ( this.cssPosition === "fixed" ? -this.offset.scroll.top : ( scrollIsRootNode ? 0 : this.offset.scroll.top ) ) * mod)
 -                      ),
 -                      left: (
 -                              pos.left +                                                                                                                              // The absolute mouse position
 -                              this.offset.relative.left * mod +                                                                               // Only for relative positioned nodes: Relative offset from element to offset parent
 -                              this.offset.parent.left * mod   -                                                                               // The offsetParent's offset without borders (offset + border)
 -                              ( ( this.cssPosition === "fixed" ? -this.offset.scroll.left : ( scrollIsRootNode ? 0 : this.offset.scroll.left ) ) * mod)
 -                      )
 -              };
++              return container;
++      }
++});
 -      },
++// DEPRECATED
++if ( $.uiBackCompat !== false ) {
 -      _generatePosition: function( event, constrainPosition ) {
++      // appendTo "parent" value
++      $.widget( "ui.draggable", $.ui.draggable, {
++              _appendTo: function() {
++                      var element = this.options.appendTo;
 -              var containment, co, top, left,
 -                      o = this.options,
 -                      scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] ),
 -                      pageX = event.pageX,
 -                      pageY = event.pageY;
++                      // This should only happen via _createHelper()
++                      if ( element === null ) {
++                              return this.element.parent();
++                      }
 -              // Cache the scroll
 -              if ( !scrollIsRootNode || !this.offset.scroll ) {
 -                      this.offset.scroll = {
 -                              top: this.scrollParent.scrollTop(),
 -                              left: this.scrollParent.scrollLeft()
 -                      };
++                      if ( element === "parent" ) {
++                              element = this.dragEl.parent();
++                      }
 -              /*
 -               * - Position constraining -
 -               * Constrain the position to a mix of grid, containment.
 -               */
 -
 -              // If we are not dragging yet, we won't check for options
 -              if ( constrainPosition ) {
 -                      if ( this.containment ) {
 -                              if ( this.relative_container ){
 -                                      co = this.relative_container.offset();
 -                                      containment = [
 -                                              this.containment[ 0 ] + co.left,
 -                                              this.containment[ 1 ] + co.top,
 -                                              this.containment[ 2 ] + co.left,
 -                                              this.containment[ 3 ] + co.top
 -                                      ];
 -                              }
 -                              else {
 -                                      containment = this.containment;
 -                              }
++                      return element;
+               }
++      });
 -                              if(event.pageX - this.offset.click.left < containment[0]) {
 -                                      pageX = containment[0] + this.offset.click.left;
 -                              }
 -                              if(event.pageY - this.offset.click.top < containment[1]) {
 -                                      pageY = containment[1] + this.offset.click.top;
 -                              }
 -                              if(event.pageX - this.offset.click.left > containment[2]) {
 -                                      pageX = containment[2] + this.offset.click.left;
 -                              }
 -                              if(event.pageY - this.offset.click.top > containment[3]) {
 -                                      pageY = containment[3] + this.offset.click.top;
 -                              }
++      // helper "original" or "clone" value + helper return value
++      $.widget( "ui.draggable", $.ui.draggable, {
++              _create: function() {
++                      if ( this.options.helper === "original" ) {
++                              this.options.helper = false;
++                      }
 -                      if(o.grid) {
 -                              //Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950)
 -                              top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY;
 -                              pageY = containment ? ((top - this.offset.click.top >= containment[1] || top - this.offset.click.top > containment[3]) ? top : ((top - this.offset.click.top >= containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
++                      if ( this.options.helper === "clone" ) {
++                              this.options.helper = true;
+                       }
 -                              left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX;
 -                              pageX = containment ? ((left - this.offset.click.left >= containment[0] || left - this.offset.click.left > containment[2]) ? left : ((left - this.offset.click.left >= containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
 -                      }
++                      this._super();
++              },
 -                      if ( o.axis === "y" ) {
 -                              pageX = this.originalPageX;
++              _originalHash: function() {
++                      var ret = this._superApply( arguments );
 -                      if ( o.axis === "x" ) {
 -                              pageY = this.originalPageY;
++                      if ( !ret.helper ) {
++                              ret.helper = this.element;
+                       }
 -              }
++                      return ret;
++              },
++
++              _setOption: function( key, value ) {
++                      if ( key === "helper" && value === "clone" ) {
++                              value = true;
++                      } else if ( key === "helper" && value === "original" ) {
++                              value = false;
+                       }
 -              return {
 -                      top: (
 -                              pageY -                                                                                                                                 // The absolute mouse position
 -                              this.offset.click.top   -                                                                                               // Click offset (relative to the element)
 -                              this.offset.relative.top -                                                                                              // Only for relative positioned nodes: Relative offset from element to offset parent
 -                              this.offset.parent.top +                                                                                                // The offsetParent's offset without borders (offset + border)
 -                              ( this.cssPosition === "fixed" ? -this.offset.scroll.top : ( scrollIsRootNode ? 0 : this.offset.scroll.top ) )
 -                      ),
 -                      left: (
 -                              pageX -                                                                                                                                 // The absolute mouse position
 -                              this.offset.click.left -                                                                                                // Click offset (relative to the element)
 -                              this.offset.relative.left -                                                                                             // Only for relative positioned nodes: Relative offset from element to offset parent
 -                              this.offset.parent.left +                                                                                               // The offsetParent's offset without borders (offset + border)
 -                              ( this.cssPosition === "fixed" ? -this.offset.scroll.left : ( scrollIsRootNode ? 0 : this.offset.scroll.left ) )
 -                      )
 -              };
 -      },
++                      this._super( key, value );
++              }
++      });
 -      _clear: function() {
 -              this.helper.removeClass("ui-draggable-dragging");
 -              if(this.helper[0] !== this.element[0] && !this.cancelHelperRemoval) {
 -                      this.helper.remove();
 -              }
 -              this.helper = null;
 -              this.cancelHelperRemoval = false;
 -              if ( this.destroyOnClear ) {
 -                      this.destroy();
 -              }
 -      },
++      // axis option
++      $.widget( "ui.draggable", $.ui.draggable, {
++              options: {
++                      axis: false
++              },
 -      // From now on bulk stuff - mainly helpers
++              _create: function() {
++                      this._super();
 -      _trigger: function(type, event, ui) {
 -              ui = ui || this._uiHash();
 -              $.ui.plugin.call( this, type, [ event, ui, this ], true );
 -              //The absolute position has to be recalculated after plugins
 -              if(type === "drag") {
 -                      this.positionAbs = this._convertPositionTo("absolute");
++                      this._on({
++                              drag: function( event, ui ) {
++                                      if ( this.options.axis === "x" ) {
++                                              ui.position.top = ui.originalPosition.top;
++                                      }
 -              return $.Widget.prototype._trigger.call(this, type, event, ui);
 -      },
++                                      if ( this.options.axis === "y" ) {
++                                              ui.position.left = ui.originalPosition.left;
++                                      }
++                              }
++                      });
+               }
 -      plugins: {},
++      });
 -      _uiHash: function() {
 -              return {
 -                      helper: this.helper,
 -                      position: this.position,
 -                      originalPosition: this.originalPosition,
 -                      offset: this.positionAbs
 -              };
 -      }
++      // cancel option
++      $.widget( "ui.draggable", $.ui.draggable, {
++              options: {
++                      cancel: null
++              },
 -});
++              _create: function() {
++                      if ( this.options.cancel !== null ) {
++                              this.options.exclude = this.options.cancel;
++                      }
 -$.ui.plugin.add("draggable", "connectToSortable", {
 -      start: function( event, ui, inst ) {
 -
 -              var o = inst.options,
 -                      uiSortable = $.extend({}, ui, { item: inst.element });
 -              inst.sortables = [];
 -              $(o.connectToSortable).each(function() {
 -                      var sortable = $( this ).sortable( "instance" );
 -                      if (sortable && !sortable.options.disabled) {
 -                              inst.sortables.push({
 -                                      instance: sortable,
 -                                      shouldRevert: sortable.options.revert
 -                              });
 -                              sortable.refreshPositions();    // Call the sortable's refreshPositions at drag start to refresh the containerCache since the sortable container cache is used in drag and needs to be up to date (this will ensure it's initialised as well as being kept in step with any changes that might have happened on the page).
 -                              sortable._trigger("activate", event, uiSortable);
++                      this._super();
++              },
 -              });
++              _setOption: function( key, value ) {
++                      if ( key === "cancel" ) {
++                              key = "exclude";
+                       }
 -      },
 -      stop: function( event, ui, inst ) {
 -              //If we are still over the sortable, we fake the stop event of the sortable, but also remove helper
 -              var uiSortable = $.extend( {}, ui, {
 -                      item: inst.element
 -              });
++                      this._super( key, value );
++                      this.options.cancel = this.options.exclude;
++              }
++      });
 -              $.each(inst.sortables, function() {
 -                      if(this.instance.isOver) {
++      // cursor option
++      $.widget( "ui.draggable", $.ui.draggable, {
++              options: {
++                      cursor: "auto"
++              },
 -                              this.instance.isOver = 0;
++              _create: function() {
++                      var startCursor, body;
 -                              inst.cancelHelperRemoval = true; //Don't remove the helper in the draggable instance
 -                              this.instance.cancelHelperRemoval = false; //Remove it in the sortable instance (so sortable plugins like revert still work)
++                      this._super();
 -                              //The sortable revert is supported, and we have to set a temporary dropped variable on the draggable to support revert: "valid/invalid"
 -                              if(this.shouldRevert) {
 -                                      this.instance.options.revert = this.shouldRevert;
++                      body = $( this.document[ 0 ].body );
 -                              //Trigger the stop of the sortable
 -                              this.instance._mouseStop(event);
++                      this._on({
++                              // Cache original cursor to set back
++                              dragbeforestart: function() {
++                                      if ( this.options.cursor ) {
++                                              startCursor = body[ 0 ].style.cursor;
++                                              body.css( "cursor", this.options.cursor );
++                                      }
++                              },
++
++                              // Set back cursor to whatever default was
++                              dragstop: function() {
++                                      if ( this.options.cursor ) {
++                                              body.css( "cursor", startCursor );
++                                      }
+                               }
++                      });
++              }
++      });
 -                              this.instance.options.helper = this.instance.options._helper;
++      // cursorAt option
++      $.widget( "ui.draggable", $.ui.draggable, {
++              options: {
++                      cursorAt: false
++              },
 -                              //If the helper has been the original item, restore properties in the sortable
 -                              if(inst.options.helper === "original") {
 -                                      this.instance.currentItem.css({ top: "auto", left: "auto" });
 -                              }
++              _create: function() {
++                      this._super();
 -                      } else {
 -                              this.instance.cancelHelperRemoval = false; //Remove the helper in the sortable instance
 -                              this.instance._trigger("deactivate", event, uiSortable);
 -                      }
++                      this._on({
++                              dragbeforestart: function( event, ui ) {
++                                      var cursorAt = this.options.cursorAt;
 -              });
++                                      if ( !cursorAt ) {
++                                              return;
++                                      }
 -      },
 -      drag: function( event, ui, inst ) {
 -
 -              var that = this;
 -
 -              $.each(inst.sortables, function() {
 -
 -                      var innermostIntersecting = false,
 -                              thisSortable = this;
 -
 -                      //Copy over some variables to allow calling the sortable's native _intersectsWith
 -                      this.instance.positionAbs = inst.positionAbs;
 -                      this.instance.helperProportions = inst.helperProportions;
 -                      this.instance.offset.click = inst.offset.click;
 -
 -                      if(this.instance._intersectsWith(this.instance.containerCache)) {
 -                              innermostIntersecting = true;
 -                              $.each(inst.sortables, function () {
 -                                      this.instance.positionAbs = inst.positionAbs;
 -                                      this.instance.helperProportions = inst.helperProportions;
 -                                      this.instance.offset.click = inst.offset.click;
 -                                      if (this !== thisSortable &&
 -                                              this.instance._intersectsWith(this.instance.containerCache) &&
 -                                              $.contains(thisSortable.instance.element[0], this.instance.element[0])
 -                                      ) {
 -                                              innermostIntersecting = false;
++                                      if ( typeof cursorAt === "string" ) {
++                                              cursorAt = cursorAt.split(" ");
++                                      }
++                                      if ( $.isArray( cursorAt ) ) {
++                                              cursorAt = {
++                                                      left: +cursorAt[ 0 ],
++                                                      top: +cursorAt[ 1 ] || 0
++                                              };
++                                      }
 -                                      return innermostIntersecting;
 -                              });
 -                      }
++                                      if ( "top" in cursorAt ) {
++                                              ui.position.top += ui.pointer.y -
++                                                      ui.offset.top - cursorAt.top;
+                                       }
 -                      if(innermostIntersecting) {
 -                              //If it intersects, we use a little isOver variable and set it once, so our move-in stuff gets fired only once
 -                              if(!this.instance.isOver) {
++                                      if ( "left" in cursorAt ) {
++                                              ui.position.left += ui.pointer.x -
++                                                      ui.offset.left - cursorAt.left;
++                                      }
++                                      if ( "bottom" in cursorAt ) {
++                                              ui.position.top += ui.pointer.y -
++                                                      ui.offset.top - this.dragDimensions.height +
++                                                      cursorAt.bottom;
++                                      }
++                                      if ( "right" in cursorAt ) {
++                                              ui.position.left += ui.pointer.x -
++                                                      ui.offset.left - this.dragDimensions.width +
++                                                      cursorAt.right;
++                                      }
++                              }
++                      });
++              }
++      });
++      // grid option
++      $.widget( "ui.draggable", $.ui.draggable, {
++              options: {
++                      grid: false
++              },
 -                                      this.instance.isOver = 1;
 -                                      //Now we fake the start of dragging for the sortable instance,
 -                                      //by cloning the list group item, appending it to the sortable and using it as inst.currentItem
 -                                      //We can then fire the start event of the sortable with our passed browser event, and our own helper (so it doesn't create a new one)
 -                                      this.instance.currentItem = $(that).clone().removeAttr("id").appendTo(this.instance.element).data("ui-sortable-item", true);
 -                                      this.instance.options._helper = this.instance.options.helper; //Store helper option to later restore it
 -                                      this.instance.options.helper = function() { return ui.helper[0]; };
++              _create: function() {
++                      var currentX, currentY;
 -                                      event.target = this.instance.currentItem[0];
 -                                      this.instance._mouseCapture(event, true);
 -                                      this.instance._mouseStart(event, true, true);
++                      this._super();
 -                                      //Because the browser event is way off the new appended portlet, we modify a couple of variables to reflect the changes
 -                                      this.instance.offset.click.top = inst.offset.click.top;
 -                                      this.instance.offset.click.left = inst.offset.click.left;
 -                                      this.instance.offset.parent.left -= inst.offset.parent.left - this.instance.offset.parent.left;
 -                                      this.instance.offset.parent.top -= inst.offset.parent.top - this.instance.offset.parent.top;
++                      this._on({
++                              dragbeforestart: function( event, ui ) {
++                                      if ( !this.options.grid ) {
++                                              return;
++                                      }
 -                                      inst._trigger("toSortable", event);
 -                                      inst.dropped = this.instance.element; //draggable revert needs that
 -                                      //hack so receive/update callbacks work (mostly)
 -                                      inst.currentItem = inst.element;
 -                                      this.instance.fromOutside = inst;
++                                      // Save off the start position,
++                                      // which may be overwritten during drag
++                                      currentX = ui.position.left;
++                                      currentY = ui.position.top;
++                              },
 -                              }
++                              drag: function( event, ui ) {
++                                      if ( !this.options.grid ) {
++                                              return;
++                                      }
 -                              //Provided we did all the previous steps, we can fire the drag event of the sortable on every draggable drag, when it intersects with the sortable
 -                              if(this.instance.currentItem) {
 -                                      this.instance._mouseDrag(event);
++                                      // Save off the intended intervals
++                                      var x = this.options.grid[ 0 ],
++                                              y = this.options.grid[ 1 ];
++
++                                      // Check that user is at least half way to next point on x axis
++                                      if ( x ) {
++                                              if ( ui.position.left - currentX >= x / 2 ) {
++                                                      currentX = currentX + x;
++                                              }       else if ( currentX - ui.position.left >= x / 2 ) {
++                                                      currentX = currentX - x;
++                                              }
++                                      }
++
++                                      // Check that user is at least half way to next point on y axis
++                                      if ( y ) {
++                                              if ( ui.position.top - currentY >= y / 2 ) {
++                                                      currentY = currentY + y;
++                                              } else if ( currentY - ui.position.top >= y / 2 ) {
++                                                      currentY = currentY - y;
++                                              }
++                                      }
 -                      } else {
++                                      // Force the draggable to the appropriate spot on the grid
++                                      ui.position.left = currentX;
++                                      ui.position.top = currentY;
+                               }
++                      });
++              }
++      });
 -                              //If it doesn't intersect with the sortable, and it intersected before,
 -                              //we fake the drag stop of the sortable, but make sure it doesn't remove the helper by using cancelHelperRemoval
 -                              if(this.instance.isOver) {
++      // opacity option
++      $.widget( "ui.draggable", $.ui.draggable, {
++              options: {
++                      opacity: false
++              },
 -                                      this.instance.isOver = 0;
 -                                      this.instance.cancelHelperRemoval = true;
++              _create: function() {
++                      var originalOpacity;
 -                                      //Prevent reverting on this forced stop
 -                                      this.instance.options.revert = false;
++                      this._super();
 -                                      // The out event needs to be triggered independently
 -                                      this.instance._trigger("out", event, this.instance._uiHash(this.instance));
++                      this._on({
++                              dragstart: function() {
++                                      if ( !this.options.opacity ) {
++                                              return;
++                                      }
 -                                      this.instance._mouseStop(event, true);
 -                                      this.instance.options.helper = this.instance.options._helper;
++                                      // Cache the original opacity of draggable element to reset later
++                                      originalOpacity = this.dragEl.css( "opacity" );
 -                                      //Now we remove our currentItem, the list group clone again, and the placeholder, and animate the helper back to it's original size
 -                                      this.instance.currentItem.remove();
 -                                      if(this.instance.placeholder) {
 -                                              this.instance.placeholder.remove();
++                                      // Set draggable element to new opacity
++                                      this.dragEl.css( "opacity", this.options.opacity );
++                              },
 -                                      inst._trigger("fromSortable", event);
 -                                      inst.dropped = false; //draggable revert needs that
++                              dragstop: function() {
++                                      if ( !this.options.opacity ) {
++                                              return;
+                                       }
 -                      }
++                                      // Reset opacity
++                                      this.dragEl.css( "opacity", originalOpacity );
+                               }
++                      });
++              }
++      });
 -              });
++      // TODO: handle droppables
++      // revert + revertDuration options
++      $.widget( "ui.draggable", $.ui.draggable, {
++              options: {
++                      revert: false,
++                      revertDuration: 500
++              },
 -      }
 -});
++              _create: function() {
++                      var originalLeft, originalTop, originalPosition;
 -$.ui.plugin.add("draggable", "cursor", {
 -      start: function( event, ui, instance ) {
 -              var t = $( "body" ),
 -                      o = instance.options;
++                      this._super();
 -              if (t.css("cursor")) {
 -                      o._cursor = t.css("cursor");
 -              }
 -              t.css("cursor", o.cursor);
 -      },
 -      stop: function( event, ui, instance ) {
 -              var o = instance.options;
 -              if (o._cursor) {
 -                      $("body").css("cursor", o._cursor);
 -              }
 -      }
 -});
++                      this._on({
++                              dragbeforestart: function() {
++                                      if ( !this.options.revert ) {
++                                              return;
++                                      }
 -$.ui.plugin.add("draggable", "opacity", {
 -      start: function( event, ui, instance ) {
 -              var t = $( ui.helper ),
 -                      o = instance.options;
 -              if(t.css("opacity")) {
 -                      o._opacity = t.css("opacity");
 -              }
 -              t.css("opacity", o.opacity);
 -      },
 -      stop: function( event, ui, instance ) {
 -              var o = instance.options;
 -              if(o._opacity) {
 -                      $(ui.helper).css("opacity", o._opacity);
 -              }
 -      }
 -});
++                                      // Cache the original css of draggable element to reset later
++                                      originalLeft = this.dragEl.css( "left" );
++                                      originalTop = this.dragEl.css( "top" );
++                                      originalPosition = this.dragEl.css( "position" );
++                              },
 -$.ui.plugin.add("draggable", "scroll", {
 -      start: function( event, ui, i ) {
 -              if( i.scrollParent[ 0 ] !== i.document[ 0 ] && i.scrollParent[ 0 ].tagName !== "HTML" ) {
 -                      i.overflowOffset = i.scrollParent.offset();
 -              }
 -      },
 -      drag: function( event, ui, i  ) {
 -
 -              var o = i.options,
 -                      scrolled = false,
 -                      document = i.document[ 0 ];
 -
 -              if( i.scrollParent[ 0 ] !== document && i.scrollParent[ 0 ].tagName !== "HTML" ) {
 -                      if(!o.axis || o.axis !== "x") {
 -                              if((i.overflowOffset.top + i.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) {
 -                                      i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop + o.scrollSpeed;
 -                              } else if(event.pageY - i.overflowOffset.top < o.scrollSensitivity) {
 -                                      i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop - o.scrollSpeed;
++                              dragstop: function() {
++                                      if ( !this.options.revert ) {
++                                              return;
++                                      }
 -                      }
++                                      // Reset to before drag
++                                      this.dragEl.animate({
++                                              left: originalLeft,
++                                              top: originalTop,
++                                              position: originalPosition
++                                      }, this.options.revertDuration );
+                               }
 -                      if(!o.axis || o.axis !== "y") {
 -                              if((i.overflowOffset.left + i.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) {
 -                                      i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft + o.scrollSpeed;
 -                              } else if(event.pageX - i.overflowOffset.left < o.scrollSensitivity) {
 -                                      i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft - o.scrollSpeed;
++                      });
++              }
++      });
++
++      // zIndex option
++      $.widget( "ui.draggable", $.ui.draggable, {
++              options: {
++                      zIndex: false
++              },
 -              } else {
++              _create: function() {
++                      var originalZindex;
++
++                      this._super();
++
++                      this._on({
++                              dragstart: function() {
++                                      if ( !this.options.zIndex ) {
++                                              return;
++                                      }
++
++                                      // Cache the original zIndex of draggable element to reset later
++                                      originalZindex = this.dragEl.css( "z-index" );
++
++                                      // Set draggable element to new zIndex
++                                      this.dragEl.css( "z-index", this.options.zIndex );
++                              },
++
++                              dragstop: function() {
++                                      if ( !this.options.zIndex ) {
++                                              return;
++                                      }
++
++                                      // Reset zIndex
++                                      this.dragEl.css( "z-index", originalZindex );
+                               }
++                      });
++              }
++      });
++
++      // scope option
++      $.widget( "ui.draggable", $.ui.draggable, {
++              options: {
++                      scope: "default"
++              }
++      });
++
++      // scroll + scrollSensitivity + scrollSpeedType option
++      $.widget( "ui.draggable", $.ui.draggable, {
++              options: {
++                      scroll: true,
++                      scrollSpeed: 20,
++                      scrollSensitivity: 20
++              },
++
++              _speed: function( distance ) {
++                      if ( this.options.scrollSpeed !== null ) {
++                              this.scrollSpeed = this.options.scrollSpeed;
++
++                              // Undo calculation that makes things go faster as distance increases
++                              distance = 0;
+                       }
 -                      if(!o.axis || o.axis !== "x") {
 -                              if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) {
 -                                      scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
 -                              } else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) {
 -                                      scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
 -                              }
++                      return this._super( distance );
++              },
 -                      if(!o.axis || o.axis !== "y") {
 -                              if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) {
 -                                      scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
 -                              } else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) {
 -                                      scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
 -                              }
++              _handleScrolling: function( pointerPosition ) {
++                      if ( !this.options.scroll ) {
++                              return;
+                       }
 -              if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) {
 -                      $.ui.ddmanager.prepareOffsets(i, event);
 -              }
++                      if ( this.options.scrollSensitivity !== null ) {
++                              this.scrollSensitivity = this.options.scrollSensitivity;
+                       }
++                      this._super( pointerPosition );
+               }
++      });
 -      }
 -});
++      // stack option
++      $.widget( "ui.draggable", $.ui.draggable, {
++              options: {
++                      stack: false
++              },
 -$.ui.plugin.add("draggable", "snap", {
 -      start: function( event, ui, i ) {
++              _create: function() {
++                      this._super();
 -              var o = i.options;
++                      this._on({
++                              dragbeforestart: function() {
++                                      var group, min,
++                                              stack = this.options.stack;
 -              i.snapElements = [];
++                                      if ( !stack ) {
++                                              return;
++                                      }
 -              $(o.snap.constructor !== String ? ( o.snap.items || ":data(ui-draggable)" ) : o.snap).each(function() {
 -                      var $t = $(this),
 -                              $o = $t.offset();
 -                      if(this !== i.element[0]) {
 -                              i.snapElements.push({
 -                                      item: this,
 -                                      width: $t.outerWidth(), height: $t.outerHeight(),
 -                                      top: $o.top, left: $o.left
 -                              });
 -                      }
 -              });
++                                      group = $.makeArray( $( stack ) ).sort(function( a, b ) {
++                                              var aZIndex = parseInt( $( a ).css( "zIndex" ), 10 ) || 0,
++                                                      bZIndex = parseInt( $( b ).css( "zIndex" ), 10 ) || 0;
++                                              return aZIndex - bZIndex;
++                                      });
 -      },
 -      drag: function( event, ui, inst ) {
++                                      if ( !group.length ) {
++                                              return;
++                                      }
 -              var ts, bs, ls, rs, l, r, t, b, i, first,
 -                      o = inst.options,
 -                      d = o.snapTolerance,
 -                      x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
 -                      y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;
++                                      min = parseInt( group[ 0 ].style.zIndex, 10 ) || 0;
 -              for (i = inst.snapElements.length - 1; i >= 0; i--){
++                                      $( group ).each(function( i ) {
++                                              this.style.zIndex = min + i;
++                                      });
 -                      l = inst.snapElements[i].left;
 -                      r = l + inst.snapElements[i].width;
 -                      t = inst.snapElements[i].top;
 -                      b = t + inst.snapElements[i].height;
++                                      this.element.css( "zIndex", min + group.length );
++                              }
++                      });
++              }
++      });
 -                      if ( x2 < l - d || x1 > r + d || y2 < t - d || y1 > b + d || !$.contains( inst.snapElements[ i ].item.ownerDocument, inst.snapElements[ i ].item ) ) {
 -                              if(inst.snapElements[i].snapping) {
 -                                      (inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
 -                              }
 -                              inst.snapElements[i].snapping = false;
 -                              continue;
 -                      }
++      // TODO: cleanup
++      // snap, snapMode, and snapTolerance options
++      $.widget( "ui.draggable", $.ui.draggable, {
++              options: {
++                      snap: false,
++                      snapMode: "both",
++                      snapTolerance: 20
++              },
 -                      if(o.snapMode !== "inner") {
 -                              ts = Math.abs(t - y2) <= d;
 -                              bs = Math.abs(b - y1) <= d;
 -                              ls = Math.abs(l - x2) <= d;
 -                              rs = Math.abs(r - x1) <= d;
 -                              if(ts) {
 -                                      ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
 -                              }
 -                              if(bs) {
 -                                      ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top;
++              _create: function() {
++                      var inst = this,
++                              snapElements;
 -                              if(ls) {
 -                                      ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left - inst.margins.left;
++                      this._super();
++
++                      this.element.on( "dragstart", function() {
++
++                              // Nothing to do
++                              if ( !inst.options.snap ) {
++                                      return;
+                               }
 -                              if(rs) {
 -                                      ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left;
++
++                              // Reset snapElements on every start in case there have been changes
++                              snapElements = [];
++
++                              // Select either all draggable elements, or the selector that was passed in
++                              $( inst.options.snap === true ? ":data(ui-draggable)" : inst.options.snap ).each(function() {
++
++                                      var el = $(this),
++                                              offset = el.offset();
++
++                                      // Don't add this draggable to list of elements for snapping
++                                      if ( this === inst.element[0] ) {
++                                              return;
++                                      }
++
++                                      // Save off elements dimensions for later
++                                      snapElements.push({
++                                              item: this,
++                                              width: el.outerWidth(),
++                                              height: el.outerHeight(),
++                                              top: offset.top,
++                                              left: offset.left
++                                      });
++
++                              });
++
++                              inst.margins = {
++                                      left: (parseInt(inst.element.css("marginLeft"),10) || 0),
++                                      top: (parseInt(inst.element.css("marginTop"),10) || 0),
++                                      right: (parseInt(inst.element.css("marginRight"),10) || 0),
++                                      bottom: (parseInt(inst.element.css("marginBottom"),10) || 0)
++                              };
++
++                      });
++
++                      this.element.on( "drag", function( event, ui ) {
++
++                              // Nothing to do
++                              if ( !inst.options.snap ) {
++                                      return;
+                               }
 -                      first = (ts || bs || ls || rs);
++
++                              var ts, bs, ls, rs, l, r, t, b, i, first,
++                                      o = inst.options,
++                                      d = o.snapTolerance,
++                                      x1 = ui.offset.left, x2 = x1 + inst.dragDimensions.width,
++                                      y1 = ui.offset.top, y2 = y1 + inst.dragDimensions.height;
++
++                              for (i = snapElements.length - 1; i >= 0; i--){
++                                      l = snapElements[i].left;
++                                      r = l + snapElements[i].width;
++                                      t = snapElements[i].top;
++                                      b = t + snapElements[i].height;
++
++                                      //Yes, I know, this is insane ;)
++                                      if (!((l - d < x1 && x1 < r + d && t - d < y1 && y1 < b + d) || (l - d < x1 && x1 < r + d && t - d < y2 && y2 < b + d) || (l - d < x2 && x2 < r + d && t - d < y1 && y1 < b + d) || (l - d < x2 && x2 < r + d && t - d < y2 && y2 < b + d))) {
++                                              if (snapElements[i].snapping) {
++                                                      (inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: snapElements[i].item })));
++                                              }
++                                              snapElements[i].snapping = false;
++                                              continue;
++                                      }
++
++                                      if (o.snapMode !== "inner") {
++                                              ts = Math.abs(t - y2) <= d;
++                                              bs = Math.abs(b - y1) <= d;
++                                              ls = Math.abs(l - x2) <= d;
++                                              rs = Math.abs(r - x1) <= d;
++                                              if (ts) {
++                                                      ui.position.top = inst._convertPositionTo("relative", { top: t - inst.dragDimensions.height, left: 0 }).top - inst.margins.top;
++                                              }
++                                              if (bs) {
++                                                      ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top;
++                                              }
++                                              if (ls) {
++                                                      ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.dragDimensions.width }).left - inst.margins.left;
++                                              }
++                                              if (rs) {
++                                                      ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left;
++                                              }
++                                      }
++
++                                      first = (ts || bs || ls || rs);
++
++                                      if (o.snapMode !== "outer") {
++                                              ts = Math.abs(t - y1) <= d;
++                                              bs = Math.abs(b - y2) <= d;
++                                              ls = Math.abs(l - x1) <= d;
++                                              rs = Math.abs(r - x2) <= d;
++                                              if (ts) {
++                                                      ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top;
++                                              }
++                                              if (bs) {
++                                                      ui.position.top = inst._convertPositionTo("relative", { top: b - inst.dragDimensions.height, left: 0 }).top - inst.margins.top;
++                                              }
++                                              if (ls) {
++                                                      ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left;
++                                              }
++                                              if (rs) {
++                                                      ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.dragDimensions.width }).left - inst.margins.left;
++                                              }
++                                      }
++
++                                      if (!snapElements[i].snapping && (ts || bs || ls || rs || first)) {
++                                              (inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: snapElements[i].item })));
++                                      }
++                                      snapElements[i].snapping = (ts || bs || ls || rs || first);
+                               }
++                      });
++              },
++
++              _convertPositionTo: function(d, pos) {
++                      if (!pos) {
++                              pos = this.position;
+                       }
 -                      if(o.snapMode !== "outer") {
 -                              ts = Math.abs(t - y1) <= d;
 -                              bs = Math.abs(b - y2) <= d;
 -                              ls = Math.abs(l - x1) <= d;
 -                              rs = Math.abs(r - x2) <= d;
 -                              if(ts) {
 -                                      ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top;
 -                              }
 -                              if(bs) {
 -                                      ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
 -                              }
 -                              if(ls) {
 -                                      ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left;
 -                              }
 -                              if(rs) {
 -                                      ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left - inst.margins.left;
 -                              }
++                      var mod = d === "absolute" ? 1 : -1,
++                              offset = {},
++                              scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
 -                      if(!inst.snapElements[i].snapping && (ts || bs || ls || rs || first)) {
 -                              (inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
++                      $.extend(offset, {
++                              parent: this._getParentOffset(),
++                              relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
++                      });
++
++                      return {
++                              top: (
++                                      pos.top +                                                                                                                               // The absolute mouse position
++                                      offset.relative.top * mod +                                                                             // Only for relative positioned nodes: Relative offset from element to offset parent
++                                      offset.parent.top * mod -                                                                               // The offsetParent's offset without borders (offset + border)
++                                      ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
++                              ),
++                              left: (
++                                      pos.left +                                                                                                                              // The absolute mouse position
++                                      offset.relative.left * mod +                                                                            // Only for relative positioned nodes: Relative offset from element to offset parent
++                                      offset.parent.left * mod        -                                                                               // The offsetParent's offset without borders (offset + border)
++                                      ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
++                              )
++                      };
++              },
++
++              _getParentOffset: function() {
++
++                      //Get the offsetParent and cache its position
++                      this.offsetParent = this.dragEl.offsetParent();
++                      var po = this.offsetParent.offset();
++
++                      // This is a special case where we need to modify a offset calculated on start, since the following happened:
++                      // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
++                      // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
++                      //    the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
++                      if (this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) {
++                              po.left += this.scrollParent.scrollLeft();
++                              po.top += this.scrollParent.scrollTop();
+                       }
 -                      inst.snapElements[i].snapping = (ts || bs || ls || rs || first);
++                      //This needs to be actually done for all browsers, since pageX/pageY includes this information
++                      //Ugly IE fix
++                      if ((this.offsetParent[0] === document.body) ||
++                              (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) {
++                              po = { top: 0, left: 0 };
+                       }
 -              }
 -      }
 -});
++                      return {
++                              top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
++                              left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
++                      };
++              },
++
++              _getRelativeOffset: function() {
++                      if (this.cssPosition === "relative") {
++                              var p = this.element.position();
++                              return {
++                                      top: p.top - (parseInt(this.dragEl.css("top"),10) || 0) + this.scrollParent.scrollTop(),
++                                      left: p.left - (parseInt(this.dragEl.css("left"),10) || 0) + this.scrollParent.scrollLeft()
++                              };
++                      }
 -$.ui.plugin.add("draggable", "stack", {
 -      start: function( event, ui, instance ) {
 -              var min,
 -                      o = instance.options,
 -                      group = $.makeArray($(o.stack)).sort(function(a,b) {
 -                              return (parseInt($(a).css("zIndex"),10) || 0) - (parseInt($(b).css("zIndex"),10) || 0);
 -                      });
++                      return { top: 0, left: 0 };
++              }
++      });
 -              if (!group.length) { return; }
++      // refreshPositions option
++      $.widget( "ui.draggable", $.ui.draggable, {
++              options: {
++                      refreshPositions: false
++              },
 -              min = parseInt($(group[0]).css("zIndex"), 10) || 0;
 -              $(group).each(function(i) {
 -                      $(this).css("zIndex", min + i);
 -              });
 -              this.css("zIndex", (min + group.length));
 -      }
 -});
++              _create: function() {
++                      var drops;
 -$.ui.plugin.add("draggable", "zIndex", {
 -      start: function( event, ui, instance ) {
 -              var t = $( ui.helper ),
 -                      o = instance.options;
++                      this._super();
 -              if(t.css("zIndex")) {
 -                      o._zIndex = t.css("zIndex");
 -              }
 -              t.css("zIndex", o.zIndex);
 -      },
 -      stop: function( event, ui, instance ) {
 -              var o = instance.options;
++                      this._on({
++                              dragstart: function() {
++                                      drops = $( ":data(ui-sortable)" );
++                              },
 -              if(o._zIndex) {
 -                      $(ui.helper).css("zIndex", o._zIndex);
++                              drag: function() {
++                                      if ( this.options.refreshPositions !== true ) {
++                                              return;
++                                      }
 -      }
 -});
++                                      drops.each(function() {
++                                              $( this ).sortable( "refreshPositions" );
++                                      });
++                              }
++                      });
+               }
++      });
++}
+ return $.ui.draggable;
+ }));
diff --cc ui/droppable.js
index 0000000000000000000000000000000000000000,826f46e586c56b13cfed8fa8897b1a840982d2ca..763cdc16f43d3820a395e75b3abfa4e90656dd9e
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,416 +1,345 @@@
 -                      "./mouse",
+ /*!
+  * jQuery UI Droppable @VERSION
+  * http://jqueryui.com
+  *
+  * Copyright 2014 jQuery Foundation and other contributors
+  * Released under the MIT license.
+  * http://jquery.org/license
+  *
+  * http://api.jqueryui.com/droppable/
+  */
+ (function( factory ) {
+       if ( typeof define === "function" && define.amd ) {
+               // AMD. Register as an anonymous module.
+               define([
+                       "jquery",
+                       "./core",
+                       "./widget",
 -              accept: "*",
 -              activeClass: false,
 -              addClasses: true,
++                      "./interaction",
+                       "./draggable"
+               ], factory );
+       } else {
+               // Browser globals
+               factory( jQuery );
+       }
+ }(function( $ ) {
++var guid = 0,
++      droppables = {};
++
++(function() {
++      var orig = $.ui.draggable.prototype._trigger;
++      $.ui.draggable.prototype._trigger = function( type, event, ui ) {
++              var method = "_draggable" + type.substr( 0, 1 ).toUpperCase() + type.substr( 1 ),
++                      allowed = orig.apply( this, arguments );
++
++              if ( allowed && $.ui.droppable[ method ] ) {
++                      $.ui.droppable[ method ]( event, ui );
++              }
++
++              return allowed;
++      };
++})();
++
+ $.widget( "ui.droppable", {
+       version: "@VERSION",
+       widgetEventPrefix: "drop",
++
+       options: {
 -              hoverClass: false,
 -              scope: "default",
 -              tolerance: "intersect",
 -
 -              // callbacks
 -              activate: null,
 -              deactivate: null,
 -              drop: null,
 -              out: null,
 -              over: null
++              accept: null,
+               greedy: false,
 -      _create: function() {
++              tolerance: "intersect"
+       },
 -              var proportions,
 -                      o = this.options,
 -                      accept = o.accept;
 -              this.isover = false;
 -              this.isout = true;
++      // over: whether or not a draggable is currently over droppable
++      // proportions: width and height of droppable
 -              this.accept = $.isFunction( accept ) ? accept : function( d ) {
 -                      return d.is( accept );
 -              };
++      _create: function() {
++              this.refresh();
++              this.guid = guid++;
++              droppables[ this.guid ] = this;
++      },
 -              this.proportions = function( /* valueToWrite */ ) {
 -                      if ( arguments.length ) {
 -                              // Store the droppable's proportions
 -                              proportions = arguments[ 0 ];
 -                      } else {
 -                              // Retrieve or derive the droppable's proportions
 -                              return proportions ?
 -                                      proportions :
 -                                      proportions = {
 -                                              width: this.element[ 0 ].offsetWidth,
 -                                              height: this.element[ 0 ].offsetHeight
 -                                      };
 -                      }
++      /** public **/
 -              this._addToManager( o.scope );
++      refresh: function() {
++              this.offset = this.element.offset();
++              this.proportions = {
++                      width: this.element.outerWidth(),
++                      height: this.element.outerHeight()
+               };
++      },
 -              o.addClasses && this.element.addClass( "ui-droppable" );
++      /** internal **/
 -      },
++      _start: function( event ) {
 -      _addToManager: function( scope ) {
 -              // Add the reference and positions to the manager
 -              $.ui.ddmanager.droppables[ scope ] = $.ui.ddmanager.droppables[ scope ] || [];
 -              $.ui.ddmanager.droppables[ scope ].push( this );
++              // If draggable is acceptable to this droppable
++              if ( !this._isAcceptable( event.target ) ) {
++                      return false;
++              }
 -      _splice: function( drop ) {
 -              var i = 0;
 -              for ( ; i < drop.length; i++ ) {
 -                      if ( drop[ i ] === this ) {
 -                              drop.splice( i, 1 );
++              this._trigger( "activate", event, this._uiHash() );
+       },
 -      },
++      _isAcceptable: function( draggable ) {
++
++              if ( $.isFunction( this.options.accept ) ) {
++                      if ( this.options.accept.call( this.element, draggable ) !== true ) {
++                              return false;
+                       }
++              } else if ( this.options.accept && !$( draggable ).is( this.options.accept ) ) {
++                      return false;
+               }
 -      _destroy: function() {
 -              var drop = $.ui.ddmanager.droppables[ this.options.scope ];
 -              this._splice( drop );
++              return true;
 -              this.element.removeClass( "ui-droppable ui-droppable-disabled" );
++      },
 -      _setOption: function( key, value ) {
++      _drag: function( event, ui ) {
++              var draggableProportions = $.ui.droppable.draggableProportions,
++                      edges = {
++                              right: this.offset.left + this.proportions.width,
++                              bottom: this.offset.top + this.proportions.height,
++                              draggableRight: ui.offset.left + draggableProportions.width,
++                              draggableBottom: ui.offset.top + draggableProportions.height
++                      },
++                      over = $.ui.droppable.tolerance[ this.options.tolerance ]
++                              .call( this, event, edges, ui );
++
++              // If there is sufficient overlap as deemed by tolerance
++              if ( over ) {
++                      this._trigger( "over", event, this._uiHash() );
++                      this.over = true;
++              // If there isn't enough overlap and droppable was previously flagged as over
++              } else if ( this.over ) {
++                      this.over = false;
++                      this._trigger( "out", event, this._uiHash() );
++              }
+       },
 -              if ( key === "accept" ) {
 -                      this.accept = $.isFunction( value ) ? value : function( d ) {
 -                              return d.is( value );
 -                      };
 -              } else if ( key === "scope" ) {
 -                      var drop = $.ui.ddmanager.droppables[ this.options.scope ];
++      _stop: function( event ) {
++
++              var greedy_child,
++                      self = this;
++
++              if ( this.over ) {
 -                      this._splice( drop );
 -                      this._addToManager( value );
++                      this.element.find(":data('" + this.widgetFullName + "')").each( function() {
 -              this._super( key, value );
++                              var drop = $(this).data( self.widgetFullName );
++
++                              if ( drop.options.greedy === true && drop.over === true ) {
++                                      greedy_child = true;
++                                      return false;
++                              }
++                      });
++
++                      if ( !greedy_child ) {
++                              this._trigger( "drop", event, this._uiHash() );
++                      }
+               }
 -      _activate: function( event ) {
 -              var draggable = $.ui.ddmanager.current;
 -              if ( this.options.activeClass ) {
 -                      this.element.addClass( this.options.activeClass );
 -              }
 -              if ( draggable ){
 -                      this._trigger( "activate", event, this.ui( draggable ) );
 -              }
++              this._trigger( "deactivate", event, this._uiHash() );
++
++              this.over = false;
+       },
 -      _deactivate: function( event ) {
 -              var draggable = $.ui.ddmanager.current;
 -              if ( this.options.activeClass ) {
 -                      this.element.removeClass( this.options.activeClass );
 -              }
 -              if ( draggable ){
 -                      this._trigger( "deactivate", event, this.ui( draggable ) );
++      // TODO: fill me out
++      _uiHash: function() {
++              return {};
+       },
 -      _over: function( event ) {
++      _destroy: function() {
++              delete droppables[ this.guid ];
++      }
++});
++
++$.extend( $.ui.droppable, {
++      // draggableProportions: width and height of currently dragging draggable
++      // active: array of active droppables
++
++      tolerance: {
++              // Half of the draggable overlaps the droppable, horizontally and vertically
++              intersect: function( event, edges, ui ) {
++                      var draggableProportions = $.ui.droppable.draggableProportions,
++                              xHalf = ui.offset.left + draggableProportions.width / 2,
++                              yHalf = ui.offset.top + draggableProportions.height / 2;
++
++                      return this.offset.left < xHalf && edges.right > xHalf &&
++                              this.offset.top < yHalf && edges.bottom > yHalf;
++              },
++
++              // Draggable overlaps droppable by at least one pixel
++              touch: function( event, edges, ui ) {
++                      return this.offset.left < edges.draggableRight &&
++                              edges.right > ui.offset.left &&
++                              this.offset.top < edges.draggableBottom &&
++                              edges.bottom > ui.offset.top;
++              },
++
++              // Pointer overlaps droppable
++              pointer: function( event, edges, ui ) {
++                      return ui.pointer.x >= this.offset.left && ui.pointer.x <= edges.right &&
++                              ui.pointer.y >= this.offset.top && ui.pointer.y <= edges.bottom;
++              },
++
++              // Draggable should be entirely inside droppable
++              fit: function( event, edges, ui ) {
++                      return edges.draggableRight <= edges.right &&
++                              ui.offset.left >= this.offset.left &&
++                              edges.draggableBottom <= edges.bottom &&
++                              ui.offset.top >= this.offset.top;
+               }
+       },
 -              var draggable = $.ui.ddmanager.current;
 -
 -              // Bail if draggable and droppable are same element
 -              if ( !draggable || ( draggable.currentItem || draggable.element )[ 0 ] === this.element[ 0 ] ) {
 -                      return;
 -              }
++      _draggableStart: function( event, ui ) {
++              var droppable,
++                      element = ui.helper || $( event.target );
 -              if ( this.accept.call( this.element[ 0 ], ( draggable.currentItem || draggable.element ) ) ) {
 -                      if ( this.options.hoverClass ) {
 -                              this.element.addClass( this.options.hoverClass );
++              this.draggableProportions = {
++                      width: element.outerWidth(),
++                      height: element.outerHeight()
++              };
 -                      this._trigger( "over", event, this.ui( draggable ) );
++              this.active = [];
++              for ( droppable in droppables ) {
++                      if ( droppables[ droppable ]._start( event, ui ) !== false ) {
++                              this.active.push( droppables[ droppable ] );
+                       }
 -      _out: function( event ) {
+               }
++      },
++      _draggableDrag: function( event, ui ) {
++              $.each( this.active, function() {
++                      this._drag( event, ui );
++              });
+       },
 -              var draggable = $.ui.ddmanager.current;
++      _draggableStop: function( event, ui ) {
++              $.each( this.active, function() {
++                      this._stop( event, ui );
++              });
++      }
++});
 -              // Bail if draggable and droppable are same element
 -              if ( !draggable || ( draggable.currentItem || draggable.element )[ 0 ] === this.element[ 0 ] ) {
 -                      return;
 -              }
++// DEPRECATED
++if ( $.uiBackCompat !== false ) {
 -              if ( this.accept.call( this.element[ 0 ], ( draggable.currentItem || draggable.element ) ) ) {
 -                      if ( this.options.hoverClass ) {
 -                              this.element.removeClass( this.options.hoverClass );
 -                      }
 -                      this._trigger( "out", event, this.ui( draggable ) );
++      // accept option
++      $.widget( "ui.droppable", $.ui.droppable, {
 -      },
++              options: {
++                      accept: "*"
+               }
 -      _drop: function( event, custom ) {
++      });
 -              var draggable = custom || $.ui.ddmanager.current,
 -                      childrenIntersection = false;
++      // activeClass option
++      $.widget( "ui.droppable", $.ui.droppable, {
 -              // Bail if draggable and droppable are same element
 -              if ( !draggable || ( draggable.currentItem || draggable.element )[ 0 ] === this.element[ 0 ] ) {
 -                      return false;
 -              }
++              options: {
++                      activeClass: false
++              },
 -              this.element.find( ":data(ui-droppable)" ).not( ".ui-draggable-dragging" ).each(function() {
 -                      var inst = $( this ).droppable( "instance" );
 -                      if (
 -                              inst.options.greedy &&
 -                              !inst.options.disabled &&
 -                              inst.options.scope === draggable.options.scope &&
 -                              inst.accept.call( inst.element[ 0 ], ( draggable.currentItem || draggable.element ) ) &&
 -                              $.ui.intersect( draggable, $.extend( inst, { offset: inst.element.offset() } ), inst.options.tolerance )
 -                      ) { childrenIntersection = true; return false; }
 -              });
 -              if ( childrenIntersection ) {
 -                      return false;
 -              }
++              _create: function() {
 -              if ( this.accept.call( this.element[ 0 ], ( draggable.currentItem || draggable.element ) ) ) {
 -                      if ( this.options.activeClass ) {
 -                              this.element.removeClass( this.options.activeClass );
 -                      }
 -                      if ( this.options.hoverClass ) {
 -                              this.element.removeClass( this.options.hoverClass );
 -                      }
 -                      this._trigger( "drop", event, this.ui( draggable ) );
 -                      return this.element;
 -              }
++                      var self = this,
++                                      added = false;
 -              return false;
++                      this._super();
 -      },
++                      // On drag, see if a class should be added to droppable
++                      $(this.document[0].body).on( "drag", ".ui-draggable", function( event ) {
 -      ui: function( c ) {
 -              return {
 -                      draggable: ( c.currentItem || c.element ),
 -                      helper: c.helper,
 -                      position: c.position,
 -                      offset: c.positionAbs
 -              };
 -      }
++                              if ( !added && self.options.activeClass && self._isAcceptable( event.target ) )  {
 -});
++                                      self.element.addClass( self.options.activeClass );
++                                      added = true;
 -$.ui.intersect = (function() {
 -      function isOverAxis( x, reference, size ) {
 -              return ( x >= reference ) && ( x < ( reference + size ) );
 -      }
++                              }
 -      return function( draggable, droppable, toleranceMode ) {
++                      });
 -              if ( !droppable.offset ) {
 -                      return false;
 -              }
++                      // On dragstop, remove class if one was added
++                      $(this.document[0].body).on( "dragstop", ".ui-draggable", function() {
 -              var draggableLeft, draggableTop,
 -                      x1 = ( draggable.positionAbs || draggable.position.absolute ).left,
 -                      y1 = ( draggable.positionAbs || draggable.position.absolute ).top,
 -                      x2 = x1 + draggable.helperProportions.width,
 -                      y2 = y1 + draggable.helperProportions.height,
 -                      l = droppable.offset.left,
 -                      t = droppable.offset.top,
 -                      r = l + droppable.proportions().width,
 -                      b = t + droppable.proportions().height;
 -
 -              switch ( toleranceMode ) {
 -              case "fit":
 -                      return ( l <= x1 && x2 <= r && t <= y1 && y2 <= b );
 -              case "intersect":
 -                      return ( l < x1 + ( draggable.helperProportions.width / 2 ) && // Right Half
 -                              x2 - ( draggable.helperProportions.width / 2 ) < r && // Left Half
 -                              t < y1 + ( draggable.helperProportions.height / 2 ) && // Bottom Half
 -                              y2 - ( draggable.helperProportions.height / 2 ) < b ); // Top Half
 -              case "pointer":
 -                      draggableLeft = ( ( draggable.positionAbs || draggable.position.absolute ).left + ( draggable.clickOffset || draggable.offset.click ).left );
 -                      draggableTop = ( ( draggable.positionAbs || draggable.position.absolute ).top + ( draggable.clickOffset || draggable.offset.click ).top );
 -                      return isOverAxis( draggableTop, t, droppable.proportions().height ) && isOverAxis( draggableLeft, l, droppable.proportions().width );
 -              case "touch":
 -                      return (
 -                              ( y1 >= t && y1 <= b ) || // Top edge touching
 -                              ( y2 >= t && y2 <= b ) || // Bottom edge touching
 -                              ( y1 < t && y2 > b ) // Surrounded vertically
 -                      ) && (
 -                              ( x1 >= l && x1 <= r ) || // Left edge touching
 -                              ( x2 >= l && x2 <= r ) || // Right edge touching
 -                              ( x1 < l && x2 > r ) // Surrounded horizontally
 -                      );
 -              default:
 -                      return false;
++                              if ( added ) {
++                                      self.element.removeClass( self.options.activeClass );
++                                      added = false;
++                              }
++
++                      });
 -      };
 -})();
+               }
 -/*
 -      This manager tracks offsets of draggables and droppables
 -*/
 -$.ui.ddmanager = {
 -      current: null,
 -      droppables: { "default": [] },
 -      prepareOffsets: function( t, event ) {
 -              var i, j,
 -                      m = $.ui.ddmanager.droppables[ t.options.scope ] || [],
 -                      type = event ? event.type : null, // workaround for #2317
 -                      list = ( t.currentItem || t.element ).find( ":data(ui-droppable)" ).addBack();
++      });
 -              droppablesLoop: for ( i = 0; i < m.length; i++ ) {
++      // hoverClass option
++      $.widget( "ui.droppable", $.ui.droppable, {
 -                      // No disabled and non-accepted
 -                      if ( m[ i ].options.disabled || ( t && !m[ i ].accept.call( m[ i ].element[ 0 ], ( t.currentItem || t.element ) ) ) ) {
 -                              continue;
 -                      }
++              options: {
++                      hoverClass: false
++              },
 -                      // Filter out elements in the current dragged item
 -                      for ( j = 0; j < list.length; j++ ) {
 -                              if ( list[ j ] === m[ i ].element[ 0 ] ) {
 -                                      m[ i ].proportions().height = 0;
 -                                      continue droppablesLoop;
 -                              }
 -                      }
++              _create: function() {
 -                      m[ i ].visible = m[ i ].element.css( "display" ) !== "none";
 -                      if ( !m[ i ].visible ) {
 -                              continue;
 -                      }
++                      var self = this,
++                                      added = false;
 -                      // Activate the droppable if used directly from draggables
 -                      if ( type === "mousedown" ) {
 -                              m[ i ]._activate.call( m[ i ], event );
 -                      }
++                      this._super();
 -                      m[ i ].offset = m[ i ].element.offset();
 -                      m[ i ].proportions({ width: m[ i ].element[ 0 ].offsetWidth, height: m[ i ].element[ 0 ].offsetHeight });
++                      // On over, add class if needed
++                      $(this.element).on( "dropover", function() {
 -              }
 -
 -      },
 -      drop: function( draggable, event ) {
++                              if ( !added && self.options.hoverClass )  {
 -              var dropped = false;
 -              // Create a copy of the droppables in case the list changes during the drop (#9116)
 -              $.each( ( $.ui.ddmanager.droppables[ draggable.options.scope ] || [] ).slice(), function() {
++                                      self.element.addClass( self.options.hoverClass );
++                                      added = true;
 -                      if ( !this.options ) {
 -                              return;
 -                      }
 -                      if ( !this.options.disabled && this.visible && $.ui.intersect( draggable, this, this.options.tolerance ) ) {
 -                              dropped = this._drop.call( this, event ) || dropped;
 -                      }
++                              }
 -                      if ( !this.options.disabled && this.visible && this.accept.call( this.element[ 0 ], ( draggable.currentItem || draggable.element ) ) ) {
 -                              this.isout = true;
 -                              this.isover = false;
 -                              this._deactivate.call( this, event );
 -                      }
++                      });
 -              });
 -              return dropped;
++                      // On out, remove class if one was added
++                      $(this.element).on( "dropout", function() {
 -      },
 -      dragStart: function( draggable, event ) {
 -              // Listen for scrolling so that if the dragging causes scrolling the position of the droppables can be recalculated (see #5003)
 -              draggable.element.parentsUntil( "body" ).bind( "scroll.droppable", function() {
 -                      if ( !draggable.options.refreshPositions ) {
 -                              $.ui.ddmanager.prepareOffsets( draggable, event );
 -                      }
 -              });
 -      },
 -      drag: function( draggable, event ) {
++                              if ( added ) {
++                                      self.element.removeClass( self.options.hoverClass );
++                                      added = false;
++                              }
 -              // If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse.
 -              if ( draggable.options.refreshPositions ) {
 -                      $.ui.ddmanager.prepareOffsets( draggable, event );
++                      });
 -              // Run through all droppables and check their positions based on specific tolerance options
 -              $.each( $.ui.ddmanager.droppables[ draggable.options.scope ] || [], function() {
+               }
 -                      if ( this.options.disabled || this.greedyChild || !this.visible ) {
 -                              return;
 -                      }
++      });
 -                      var parentInstance, scope, parent,
 -                              intersects = $.ui.intersect( draggable, this, this.options.tolerance ),
 -                              c = !intersects && this.isover ? "isout" : ( intersects && !this.isover ? "isover" : null );
 -                      if ( !c ) {
 -                              return;
 -                      }
++      // scope option
++      $.widget( "ui.droppable", $.ui.droppable, {
 -                      if ( this.options.greedy ) {
 -                              // find droppable parents with same scope
 -                              scope = this.options.scope;
 -                              parent = this.element.parents( ":data(ui-droppable)" ).filter(function() {
 -                                      return $( this ).droppable( "instance" ).options.scope === scope;
 -                              });
++              options: {
++                      scope: "default"
++              },
 -                              if ( parent.length ) {
 -                                      parentInstance = $( parent[ 0 ] ).droppable( "instance" );
 -                                      parentInstance.greedyChild = ( c === "isover" );
 -                              }
 -                      }
++              _isAcceptable: function( element ) {
 -                      // we just moved into a greedy child
 -                      if ( parentInstance && c === "isover" ) {
 -                              parentInstance.isover = false;
 -                              parentInstance.isout = true;
 -                              parentInstance._out.call( parentInstance, event );
++                      var draggable = $(element).data( "ui-draggable" );
 -                      this[ c ] = true;
 -                      this[c === "isout" ? "isover" : "isout"] = false;
 -                      this[c === "isover" ? "_over" : "_out"].call( this, event );
 -
 -                      // we just moved out of a greedy child
 -                      if ( parentInstance && c === "isout" ) {
 -                              parentInstance.isout = false;
 -                              parentInstance.isover = true;
 -                              parentInstance._over.call( parentInstance, event );
 -                      }
 -              });
++                      if ( this.options.scope !== "default" && draggable && draggable.options.scope === this.options.scope ) {
++                              return true;
+                       }
 -      },
 -      dragStop: function( draggable, event ) {
 -              draggable.element.parentsUntil( "body" ).unbind( "scroll.droppable" );
 -              // Call prepareOffsets one final time since IE does not fire return scroll events when overflow was caused by drag (see #5003)
 -              if ( !draggable.options.refreshPositions ) {
 -                      $.ui.ddmanager.prepareOffsets( draggable, event );
++                      return this._super( element );
 -      }
 -};
+               }
++
++      });
++
++}
+ return $.ui.droppable;
+ }));
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..816919ac646a618ac4dfa2566d7c01dd7fd1cdc5
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,316 @@@
++/*
++ * jQuery UI Interaction @VERSION
++ *
++ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
++ * Dual licensed under the MIT or GPL Version 2 licenses.
++ * http://jquery.org/license
++ *
++ * http://api.jqueryui.com/interaction/
++ */
++(function( factory ) {
++      if ( typeof define === "function" && define.amd ) {
++
++              // AMD. Register as an anonymous module.
++              define([
++                      "./widget"
++              ], factory );
++      } else {
++
++              // Browser globals
++              factory( jQuery );
++      }
++}(function( $ ) {
++
++var interaction, touchHook, pointerHook;
++
++$.widget( "ui.interaction", {
++      version: "@VERSION",
++      _create: function() {
++              // force the context so we can pass these methods to the hooks
++              this._interactionMove = $.proxy( this, "_interactionMove" );
++              this._interactionStop = $.proxy( this, "_interactionStop" );
++
++              // initialize all hooks for this widget
++              for ( var hook in interaction.hooks ) {
++                      interaction.hooks[ hook ].setup( this, this._startProxy( hook ) );
++              }
++      },
++
++      /** abstract methods **/
++
++      // _start: function( event, pointerPosition )
++      // _move: function( event, pointerPosition )
++      // _stop: function( event, pointerPosition )
++
++      /** protected **/
++
++      _isValidTarget: function() {
++              return true;
++      },
++
++      /** internal **/
++
++      // a pass through to _interactionStart() which tracks the hook that was used
++      _startProxy: function( hook ) {
++              var that = this;
++              return function( event, target, pointerPosition ) {
++                      return that._interactionStart( event, target, pointerPosition, hook );
++              };
++      },
++
++      _interactionStart: function( event, target, pointerPosition, hook ) {
++              var started;
++
++              // only one interaction can happen at a time
++              if ( interaction.started ) {
++                      return false;
++              }
++
++              // check if the event occurred on a valid target
++              if ( false === this._isValidTarget( $( target ) ) ) {
++                      return false;
++              }
++
++              // check if the widget wants the event to start an interaction
++              started = ( this._start( event, pointerPosition ) !== false );
++              if ( started ) {
++                      interaction.started = true;
++                      interaction.hooks[ hook ].handle( this,
++                              this._interactionMove, this._interactionStop );
++              }
++
++              // let the hook know if the interaction was started
++              return started;
++      },
++
++      _interactionMove: function( event, pointerPosition ) {
++              this._move( event, pointerPosition );
++      },
++
++      _interactionStop: function( event, pointerPosition ) {
++              this._stop( event, pointerPosition );
++              interaction.started = false;
++      }
++});
++
++interaction = $.ui.interaction;
++$.extend( interaction, {
++      started: false,
++      hooks: {}
++});
++
++interaction.hooks.mouse = {
++      setup: function( widget, start ) {
++              widget._on( widget.widget(), {
++                      "mousedown": function( event ) {
++                              // only react to the primary button
++                              if ( event.which === 1 ) {
++                                      var started = start( event, event.target, {
++                                              x: event.pageX,
++                                              y: event.pageY
++                                      });
++
++                                      if ( started ) {
++                                              // prevent selection
++                                              event.preventDefault();
++                                      }
++                              }
++                      }
++              });
++      },
++
++      handle: function( widget, move, stop ) {
++              function mousemove( event ) {
++                      event.preventDefault();
++                      move( event, {
++                              x: event.pageX,
++                              y: event.pageY
++                      });
++              }
++
++              function mouseup( event ) {
++                      stop( event, {
++                              x: event.pageX,
++                              y: event.pageY
++                      });
++                      widget.document
++                              .unbind( "mousemove", mousemove )
++                              .unbind( "mouseup", mouseup );
++              }
++
++              widget._on( widget.document, {
++                      "mousemove": mousemove,
++                      "mouseup": mouseup
++              });
++      }
++};
++
++// WebKit doesn't support TouchList.identifiedTouch()
++function getTouch( event ) {
++      var touches = event.originalEvent.changedTouches,
++              i = 0, length = touches.length;
++
++      for ( ; i < length; i++ ) {
++              if ( touches[ i ].identifier === touchHook.id ) {
++                      return touches[ i ];
++              }
++      }
++}
++
++touchHook = interaction.hooks.touch = {
++      setup: function( widget, start ) {
++              widget._on( widget.widget(), {
++                      "touchstart": function( event ) {
++                              var touch, started;
++
++                              if ( touchHook.id ) {
++                                      return;
++                              }
++
++                              touch = event.originalEvent.changedTouches.item( 0 );
++                              started = start( event, touch.target, {
++                                      x: touch.pageX,
++                                      y: touch.pageY
++                              });
++
++                              if ( started ) {
++                                      // track which finger is performing the interaction
++                                      touchHook.id = touch.identifier;
++                                      // prevent panning/zooming
++                                      event.preventDefault();
++                              }
++                      }
++              });
++      },
++
++      handle: function( widget, move, stop ) {
++              function moveHandler( event ) {
++                      // TODO: test non-Apple WebKits to see if they allow
++                      // zooming/scrolling if we don't preventDefault()
++                      var touch = getTouch( event );
++                      if ( !touch ) {
++                              return;
++                      }
++
++                      event.preventDefault();
++                      move( event, {
++                              x: touch.pageX,
++                              y: touch.pageY
++                      });
++              }
++
++              function stopHandler( event ) {
++                      var touch = getTouch( event );
++                      if ( !touch ) {
++                              return;
++                      }
++
++                      stop( event, {
++                              x: touch.pageX,
++                              y: touch.pageY
++                      });
++                      touchHook.id = null;
++                      widget.document
++                              .unbind( "touchmove", moveHandler )
++                              .unbind( "touchend", stopHandler );
++              }
++
++              widget._on( widget.document, {
++                      "touchmove": moveHandler,
++                      "touchend": stopHandler
++              });
++      }
++};
++
++pointerHook = interaction.hooks.msPointer = {
++      setup: function( widget, start ) {
++              widget._on( widget.widget(), {
++                      "MSPointerDown": function( _event ) {
++                              var started,
++                                      event = _event.originalEvent;
++
++                              if ( pointerHook.id ) {
++                                      return;
++                              }
++
++                              // TODO: how can we detect a "right click" with a pen?
++                              // TODO: get full details about which and button from MS
++                              // touch and pen = 1
++                              // primary mouse button = 2
++                              if ( event.which > 2 ) {
++                                      return;
++                              }
++
++                              started = start( event, event.target, {
++                                      x: event.pageX,
++                                      y: event.pageY
++                              });
++
++                              if ( started ) {
++                                      // track which pointer is performing the interaction
++                                      pointerHook.id = event.pointerId;
++                                      // prevent panning/zooming
++                                      event.preventManipulation();
++                                      // prevent promoting pointer events to mouse events
++                                      event.preventMouseEvent();
++                              }
++                      }
++              });
++      },
++
++      handle: function( widget, move, stop ) {
++              function moveHandler( _event ) {
++                      var event = _event.originalEvent,
++                              pageX = event.pageX,
++                              pageY = event.pageY;
++
++                      // always prevent manipulation to avoid panning/zooming
++                      event.preventManipulation();
++
++                      if ( event.pointerId !== pointerHook.id ) {
++                              return;
++                      }
++
++                      // MS streams events constantly, even if there is no movement
++                      // so we optimize by ignoring repeat events
++                      if ( pointerHook.x === pageX && pointerHook.y === pageY ) {
++                              return;
++                      }
++
++                      pointerHook.x = pageX;
++                      pointerHook.y = pageY;
++                      move( event, {
++                              x: pageX,
++                              y: pageY
++                      });
++              }
++
++              function stopHandler( _event ) {
++                      var event = _event.originalEvent;
++
++                      if ( event.pointerId !== pointerHook.id ) {
++                              return;
++                      }
++
++                      stop( event, {
++                              x: event.pageX,
++                              y: event.pageY
++                      });
++                      pointerHook.id = pointerHook.x = pointerHook.y = undefined;
++                      widget.document
++                              .unbind( "MSPointerMove", moveHandler )
++                              .unbind( "MSPointerUp", stopHandler )
++                              .unbind( "MSPointerCancel", stopHandler );
++              }
++
++              widget._on( widget.document, {
++                      "MSPointerMove": moveHandler,
++                      "MSPointerUp": stopHandler,
++                      "MSPointerCancel": stopHandler
++              });
++      }
++};
++
++return interaction;
++
++}));
diff --cc ui/sortable.js
index 0000000000000000000000000000000000000000,a518e75cf448cbcdd8bf186f2be8a137e92bb42a..d1e021737bc3c33881ae6d4d0c71b0327eb5ce5b
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1310 +1,475 @@@
 -                      "./mouse",
 -                      "./widget"
+ /*!
+  * jQuery UI Sortable @VERSION
+  * http://jqueryui.com
+  *
+  * Copyright 2014 jQuery Foundation and other contributors
+  * Released under the MIT license.
+  * http://jquery.org/license
+  *
+  * http://api.jqueryui.com/sortable/
+  */
++
+ (function( factory ) {
+       if ( typeof define === "function" && define.amd ) {
+               // AMD. Register as an anonymous module.
+               define([
+                       "jquery",
+                       "./core",
 -return $.widget("ui.sortable", $.ui.mouse, {
++                      "./widget",
++                      "./interaction"
+               ], factory );
+       } else {
+               // Browser globals
+               factory( jQuery );
+       }
+ }(function( $ ) {
 -      ready: false,
 -      options: {
 -              appendTo: "parent",
 -              axis: false,
 -              connectWith: false,
 -              containment: false,
 -              cursor: "auto",
 -              cursorAt: false,
 -              dropOnEmpty: true,
 -              forcePlaceholderSize: false,
 -              forceHelperSize: false,
 -              grid: false,
 -              handle: false,
 -              helper: "original",
 -              items: "> *",
 -              opacity: false,
 -              placeholder: false,
 -              revert: false,
 -              scroll: true,
 -              scrollSensitivity: 20,
 -              scrollSpeed: 20,
 -              scope: "default",
 -              tolerance: "intersect",
 -              zIndex: 1000,
 -
 -              // callbacks
 -              activate: null,
 -              beforeStop: null,
 -              change: null,
 -              deactivate: null,
 -              out: null,
 -              over: null,
 -              receive: null,
 -              remove: null,
 -              sort: null,
 -              start: null,
 -              stop: null,
 -              update: null
 -      },
 -
 -      _isOverAxis: function( x, reference, size ) {
 -              return ( x >= reference ) && ( x < ( reference + size ) );
 -      },
++// create a shallow copy of an object
++function copy( obj ) {
++      var prop,
++              ret = {};
++      for ( prop in obj ) {
++              ret[ prop ] = obj[ prop ];
++      }
++      return ret;
++}
++
++$.widget( "ui.sortable", $.ui.interaction, {
+       version: "@VERSION",
+       widgetEventPrefix: "sort",
 -      _isFloating: function( item ) {
 -              return (/left|right/).test(item.css("float")) || (/inline|table-cell/).test(item.css("display"));
++      items: "li", // TODO: move to options when API is ready
++
++      // dragEl: element being dragged (original or helper)
++      // position: final CSS position of dragEl
++      // offset: offset of dragEl
++      // originalPosition: CSS position before drag start
++      // originalOffset: offset before drag start
++      // originalPointer: pageX/Y at drag start (offset of pointer)
++      // startPosition: CSS position at drag start (after beforeStart)
++      // startOffset: offset at drag start (after beforeStart)
++      // tempPosition: overridable CSS position of dragEl
++      // overflowOffset: offset of scroll parent
++      // overflow: object containing width and height keys of scroll parent
++      // sortablePositions: cache of positions of all sortable items
++      // originalCssPosition: CSS position of element before being made absolute on start
++      // placeholder: reference to jquery object of cloned element that is being dragged
 -              var o = this.options;
 -              this.containerCache = {};
 -              this.element.addClass("ui-sortable");
 -
 -              //Get the items
 -              this.refresh();
 -
 -              //Let's determine if the items are being displayed horizontally
 -              this.floating = this.items.length ? o.axis === "x" || this._isFloating(this.items[0].item) : false;
 -
 -              //Let's determine the parent's offset
 -              this.offset = this.element.offset();
 -
 -              //Initialize mouse events for interaction
 -              this._mouseInit();
++      options: {
+       },
+       _create: function() {
 -              this._setHandleClassName();
++              this._super();
 -              //We're ready to go
 -              this.ready = true;
++              this.element.addClass( "ui-sortable" );
 -      _setOption: function( key, value ) {
 -              this._super( key, value );
++              this._setSortablePositions();
+       },
 -              if ( key === "handle" ) {
 -                      this._setHandleClassName();
 -              }
 -      },
 -
 -      _setHandleClassName: function() {
 -              this.element.find( ".ui-sortable-handle" ).removeClass( "ui-sortable-handle" );
 -              $.each( this.items, function() {
 -                      ( this.instance.options.handle ?
 -                              this.item.find( this.instance.options.handle ) : this.item )
 -                              .addClass( "ui-sortable-handle" );
 -              });
 -      },
 -
 -      _destroy: function() {
 -              this.element
 -                      .removeClass( "ui-sortable ui-sortable-disabled" )
 -                      .find( ".ui-sortable-handle" )
 -                              .removeClass( "ui-sortable-handle" );
 -              this._mouseDestroy();
 -
 -              for ( var i = this.items.length - 1; i >= 0; i-- ) {
 -                      this.items[i].item.removeData(this.widgetName + "-item");
 -              }
 -
 -              return this;
 -      },
++      _setSortablePositions: function() {
 -      _mouseCapture: function(event, overrideHandle) {
 -              var currentItem = null,
 -                      validHandle = false,
 -                      that = this;
++              var sortablePositions = this.sortablePositions = [];
 -              if (this.reverting) {
 -                      return false;
 -              }
++              this.element.find( this.items ).each( function() {
 -              if(this.options.disabled || this.options.type === "static") {
 -                      return false;
 -              }
 -
 -              //We have to refresh the items data once first
 -              this._refreshItems(event);
 -
 -              //Find out if the clicked node (or one of its parents) is a actual item in this.items
 -              $(event.target).parents().each(function() {
 -                      if($.data(this, that.widgetName + "-item") === that) {
 -                              currentItem = $(this);
 -                              return false;
 -                      }
++                      var el = $(this);
 -              if($.data(event.target, that.widgetName + "-item") === that) {
 -                      currentItem = $(event.target);
 -              }
 -
 -              if(!currentItem) {
 -                      return false;
 -              }
 -              if(this.options.handle && !overrideHandle) {
 -                      $(this.options.handle, currentItem).find("*").addBack().each(function() {
 -                              if(this === event.target) {
 -                                      validHandle = true;
 -                              }
 -                      });
 -                      if(!validHandle) {
 -                              return false;
 -                      }
 -              }
 -
 -              this.currentItem = currentItem;
 -              this._removeCurrentsFromItems();
 -              return true;
 -
 -      },
 -
 -      _mouseStart: function(event, overrideHandle, noActivation) {
 -
 -              var i, body,
 -                      o = this.options;
 -
 -              this.currentContainer = this;
 -
 -              //We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture
 -              this.refreshPositions();
 -
 -              //Create and append the visible helper
 -              this.helper = this._createHelper(event);
 -
 -              //Cache the helper size
 -              this._cacheHelperProportions();
 -
 -              /*
 -               * - Position generation -
 -               * This block generates everything position related - it's the core of draggables.
 -               */
 -
 -              //Cache the margins of the original element
 -              this._cacheMargins();
 -
 -              //Get the next scrolling parent
 -              this.scrollParent = this.helper.scrollParent();
 -
 -              //The element's absolute position on the page minus margins
 -              this.offset = this.currentItem.offset();
 -              this.offset = {
 -                      top: this.offset.top - this.margins.top,
 -                      left: this.offset.left - this.margins.left
 -              };
 -
 -              $.extend(this.offset, {
 -                      click: { //Where the click happened, relative to the element
 -                              left: event.pageX - this.offset.left,
 -                              top: event.pageY - this.offset.top
 -                      },
 -                      parent: this._getParentOffset(),
 -                      relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
 -              });
 -
 -              // Only after we got the offset, we can change the helper's position to absolute
 -              // TODO: Still need to figure out a way to make relative sorting possible
 -              this.helper.css("position", "absolute");
 -              this.cssPosition = this.helper.css("position");
 -
 -              //Generate the original position
 -              this.originalPosition = this._generatePosition(event);
 -              this.originalPageX = event.pageX;
 -              this.originalPageY = event.pageY;
 -
 -              //Adjust the mouse offset relative to the helper if "cursorAt" is supplied
 -              (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
 -
 -              //Cache the former DOM position
 -              this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] };
 -
 -              //If the helper is not the original, hide the original so it's not playing any role during the drag, won't cause anything bad this way
 -              if(this.helper[0] !== this.currentItem[0]) {
 -                      this.currentItem.hide();
 -              }
 -
 -              //Create the placeholder
 -              this._createPlaceholder();
 -
 -              //Set a containment if given in the options
 -              if(o.containment) {
 -                      this._setContainment();
 -              }
 -
 -              if( o.cursor && o.cursor !== "auto" ) { // cursor option
 -                      body = this.document.find( "body" );
 -
 -                      // support: IE
 -                      this.storedCursor = body.css( "cursor" );
 -                      body.css( "cursor", o.cursor );
 -
 -                      this.storedStylesheet = $( "<style>*{ cursor: "+o.cursor+" !important; }</style>" ).appendTo( body );
 -              }
 -
 -              if(o.opacity) { // opacity option
 -                      if (this.helper.css("opacity")) {
 -                              this._storedOpacity = this.helper.css("opacity");
 -                      }
 -                      this.helper.css("opacity", o.opacity);
 -              }
 -
 -              if(o.zIndex) { // zIndex option
 -                      if (this.helper.css("zIndex")) {
 -                              this._storedZIndex = this.helper.css("zIndex");
 -                      }
 -                      this.helper.css("zIndex", o.zIndex);
 -              }
 -
 -              //Prepare scrolling
 -              if(this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") {
 -                      this.overflowOffset = this.scrollParent.offset();
 -              }
 -
 -              //Call callbacks
 -              this._trigger("start", event, this._uiHash());
 -
 -              //Recache the helper size
 -              if(!this._preserveHelperProportions) {
 -                      this._cacheHelperProportions();
 -              }
 -
 -
 -              //Post "activate" events to possible containers
 -              if( !noActivation ) {
 -                      for ( i = this.containers.length - 1; i >= 0; i-- ) {
 -                              this.containers[ i ]._trigger( "activate", event, this._uiHash( this ) );
 -                      }
 -              }
 -
 -              //Prepare possible droppables
 -              if($.ui.ddmanager) {
 -                      $.ui.ddmanager.current = this;
 -              }
 -
 -              if ($.ui.ddmanager && !o.dropBehaviour) {
 -                      $.ui.ddmanager.prepareOffsets(this, event);
 -              }
 -
 -              this.dragging = true;
 -
 -              this.helper.addClass("ui-sortable-helper");
 -              this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position
 -              return true;
 -
 -      },
 -
 -      _mouseDrag: function(event) {
 -              var i, item, itemElement, intersection,
 -                      o = this.options,
 -                      scrolled = false;
 -
 -              //Compute the helpers position
 -              this.position = this._generatePosition(event);
 -              this.positionAbs = this._convertPositionTo("absolute");
 -
 -              if (!this.lastPositionAbs) {
 -                      this.lastPositionAbs = this.positionAbs;
 -              }
 -
 -              //Do scrolling
 -              if(this.options.scroll) {
 -                      if(this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") {
 -
 -                              if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) {
 -                                      this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed;
 -                              } else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity) {
 -                                      this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed;
 -                              }
 -
 -                              if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) {
 -                                      this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed;
 -                              } else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity) {
 -                                      this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed;
 -                              }
 -
 -                      } else {
 -
 -                              if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) {
 -                                      scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
 -                              } else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) {
 -                                      scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
 -                              }
 -
 -                              if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) {
 -                                      scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
 -                              } else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) {
 -                                      scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
 -                              }
 -
 -                      }
 -
 -                      if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) {
 -                              $.ui.ddmanager.prepareOffsets(this, event);
 -                      }
 -              }
 -
 -              //Regenerate the absolute position used for position checks
 -              this.positionAbs = this._convertPositionTo("absolute");
 -
 -              //Set the helper position
 -              if(!this.options.axis || this.options.axis !== "y") {
 -                      this.helper[0].style.left = this.position.left+"px";
 -              }
 -              if(!this.options.axis || this.options.axis !== "x") {
 -                      this.helper[0].style.top = this.position.top+"px";
 -              }
 -
 -              //Rearrange
 -              for (i = this.items.length - 1; i >= 0; i--) {
 -
 -                      //Cache variables and intersection, continue if no intersection
 -                      item = this.items[i];
 -                      itemElement = item.item[0];
 -                      intersection = this._intersectsWithPointer(item);
 -                      if (!intersection) {
 -                              continue;
 -                      }
 -
 -                      // Only put the placeholder inside the current Container, skip all
 -                      // items from other containers. This works because when moving
 -                      // an item from one container to another the
 -                      // currentContainer is switched before the placeholder is moved.
 -                      //
 -                      // Without this, moving items in "sub-sortables" can cause
 -                      // the placeholder to jitter beetween the outer and inner container.
 -                      if (item.instance !== this.currentContainer) {
 -                              continue;
 -                      }
 -
 -                      // cannot intersect with itself
 -                      // no useless actions that have been done before
 -                      // no action if the item moved is the parent of the item checked
 -                      if (itemElement !== this.currentItem[0] &&
 -                              this.placeholder[intersection === 1 ? "next" : "prev"]()[0] !== itemElement &&
 -                              !$.contains(this.placeholder[0], itemElement) &&
 -                              (this.options.type === "semi-dynamic" ? !$.contains(this.element[0], itemElement) : true)
 -                      ) {
 -
 -                              this.direction = intersection === 1 ? "down" : "up";
 -
 -                              if (this.options.tolerance === "pointer" || this._intersectsWithSides(item)) {
 -                                      this._rearrange(event, item);
 -                              } else {
 -                                      break;
 -                              }
 -
 -                              this._trigger("change", event, this._uiHash());
 -                              break;
 -                      }
 -              }
 -
 -              //Post events to containers
 -              this._contactContainers(event);
 -
 -              //Interconnect with droppables
 -              if($.ui.ddmanager) {
 -                      $.ui.ddmanager.drag(this, event);
 -              }
 -
 -              //Call callbacks
 -              this._trigger("sort", event, this._uiHash());
 -
 -              this.lastPositionAbs = this.positionAbs;
 -              return false;
++                      sortablePositions.push([{
++                              el: el,
++                              offset: el.offset()
++                      }]);
+               });
 -      _mouseStop: function(event, noPropagation) {
+       },
 -              if(!event) {
 -                      return;
 -              }
 -
 -              //If we are using droppables, inform the manager about the drop
 -              if ($.ui.ddmanager && !this.options.dropBehaviour) {
 -                      $.ui.ddmanager.drop(this, event);
 -              }
 -
 -              if(this.options.revert) {
 -                      var that = this,
 -                              cur = this.placeholder.offset(),
 -                              axis = this.options.axis,
 -                              animation = {};
 -
 -                      if ( !axis || axis === "x" ) {
 -                              animation.left = cur.left - this.offset.parent.left - this.margins.left + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollLeft);
 -                      }
 -                      if ( !axis || axis === "y" ) {
 -                              animation.top = cur.top - this.offset.parent.top - this.margins.top + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollTop);
 -                      }
 -                      this.reverting = true;
 -                      $(this.helper).animate( animation, parseInt(this.options.revert, 10) || 500, function() {
 -                              that._clear(event);
 -                      });
 -              } else {
 -                      this._clear(event, noPropagation);
 -              }
 -
 -              return false;
++      /** interaction interface **/
 -      cancel: function() {
++      _isValidTarget: function( element ) {
++              // TODO: options for what is actually valid
++              return element.is( this.items );
+       },
 -              if(this.dragging) {
++      _start: function( event, pointerPosition ) {
 -                      this._mouseUp({ target: null });
 -
 -                      if(this.options.helper === "original") {
 -                              this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
 -                      } else {
 -                              this.currentItem.show();
 -                      }
++              // The actual dragging element, should always be a jQuery object
++              // this.dragEl = this.options.helper ?
++                      // this._createHelper( pointerPosition ) :
++                      // this.element;
 -                      //Post deactivating events to containers
 -                      for (var i = this.containers.length - 1; i >= 0; i--){
 -                              this.containers[i]._trigger("deactivate", null, this._uiHash(this));
 -                              if(this.containers[i].containerCache.over) {
 -                                      this.containers[i]._trigger("out", null, this._uiHash(this));
 -                                      this.containers[i].containerCache.over = 0;
 -                              }
 -                      }
 -
 -              }
 -
 -              if (this.placeholder) {
 -                      //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
 -                      if(this.placeholder[0].parentNode) {
 -                              this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
 -                      }
 -                      if(this.options.helper !== "original" && this.helper && this.helper[0].parentNode) {
 -                              this.helper.remove();
 -                      }
 -
 -                      $.extend(this, {
 -                              helper: null,
 -                              dragging: false,
 -                              reverting: false,
 -                              _noFinalSort: null
 -                      });
 -
 -                      if(this.domPosition.prev) {
 -                              $(this.domPosition.prev).after(this.currentItem);
 -                      } else {
 -                              $(this.domPosition.parent).prepend(this.currentItem);
 -                      }
++              this.dragEl = $(event.target);
 -              return this;
 -
 -      },
 -
 -      serialize: function(o) {
 -
 -              var items = this._getItemsAsjQuery(o && o.connected),
 -                      str = [];
 -              o = o || {};
 -
 -              $(items).each(function() {
 -                      var res = ($(o.item || this).attr(o.attribute || "id") || "").match(o.expression || (/(.+)[\-=_](.+)/));
 -                      if (res) {
 -                              str.push((o.key || res[1]+"[]")+"="+(o.key && o.expression ? res[1] : res[2]));
 -                      }
++              // Save original css position if there are currently styles
++              // Otherwise the original css will be set back by removing attribute
++              if ( this.dragEl[0].style.position ) {
++                      this.originalCssPosition = this.dragEl[0].style.position;
+               }
 -              if(!str.length && o.key) {
 -                      str.push(o.key + "=");
 -              }
 -
 -              return str.join("&");
 -
 -      },
 -
 -      toArray: function(o) {
 -
 -              var items = this._getItemsAsjQuery(o && o.connected),
 -                      ret = [];
 -
 -              o = o || {};
 -
 -              items.each(function() { ret.push($(o.item || this).attr(o.attribute || "id") || ""); });
 -              return ret;
 -
 -      },
 -
 -      /* Be careful with the following core functions */
 -      _intersectsWith: function(item) {
 -
 -              var x1 = this.positionAbs.left,
 -                      x2 = x1 + this.helperProportions.width,
 -                      y1 = this.positionAbs.top,
 -                      y2 = y1 + this.helperProportions.height,
 -                      l = item.left,
 -                      r = l + item.width,
 -                      t = item.top,
 -                      b = t + item.height,
 -                      dyClick = this.offset.click.top,
 -                      dxClick = this.offset.click.left,
 -                      isOverElementHeight = ( this.options.axis === "x" ) || ( ( y1 + dyClick ) > t && ( y1 + dyClick ) < b ),
 -                      isOverElementWidth = ( this.options.axis === "y" ) || ( ( x1 + dxClick ) > l && ( x1 + dxClick ) < r ),
 -                      isOverElement = isOverElementHeight && isOverElementWidth;
 -
 -              if ( this.options.tolerance === "pointer" ||
 -                      this.options.forcePointerForContainers ||
 -                      (this.options.tolerance !== "pointer" && this.helperProportions[this.floating ? "width" : "height"] > item[this.floating ? "width" : "height"])
 -              ) {
 -                      return isOverElement;
 -              } else {
 -
 -                      return (l < x1 + (this.helperProportions.width / 2) && // Right Half
 -                              x2 - (this.helperProportions.width / 2) < r && // Left Half
 -                              t < y1 + (this.helperProportions.height / 2) && // Bottom Half
 -                              y2 - (this.helperProportions.height / 2) < b ); // Top Half
 -
++              // Create placeholder for while element is dragging
++              // TODO: what do we do about IDs?
++              // TODO: possibly use CSS for visibility portion
++              this.placeholder = this.dragEl.clone().removeAttr("id").css({
++                      visibility: "hidden",
++                      position: this.originalCssPosition || ""
+               });
 -      },
++              this.dragEl.after( this.placeholder );
++
++              this.dragEl.css( "position", "absolute" );
++
++              // // _createHelper() ensures that helpers are in the correct position
++              // // in the DOM, but we need to handle appendTo when there is no helper
++              // if ( this.options.appendTo && this.dragEl === this.element ) {
++                      // this.domPosition = {
++                              // parent: this.element.parent(),
++                              // index: this.element.index()
++                      // };
++                      // offset = this.dragEl.offset();
++                      // this.dragEl
++                              // .appendTo( this.options.appendTo )
++                              // .offset( offset );
++              // }
++
++              this.cssPosition = this.dragEl.css( "position" );
++              this.scrollParent = this.element.scrollParent();
++
++              // Cache starting positions
++              this.originalPosition = this.startPosition = this._getPosition();
++              this.originalOffset = this.startOffset = this.dragEl.offset();
++              this.originalPointer = pointerPosition;
++
++              // Cache current position and offset
++              this.position = copy( this.startPosition );
++              this.offset = copy( this.startOffset );
++
++              // Cache the offset of scrollParent, if required for _handleScrolling
++              if ( this.scrollParent[0] !== this.document[0] && this.scrollParent[0].tagName !== "HTML" ) {
++                      this.overflowOffset = this.scrollParent.offset();
+               }
 -      _intersectsWithPointer: function(item) {
 -              var isOverElementHeight = (this.options.axis === "x") || this._isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height),
 -                      isOverElementWidth = (this.options.axis === "y") || this._isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width),
 -                      isOverElement = isOverElementHeight && isOverElementWidth,
 -                      verticalDirection = this._getDragVerticalDirection(),
 -                      horizontalDirection = this._getDragHorizontalDirection();
++              this.overflow = {
++                      height: this.scrollParent[0] === this.document[0] ?
++                              this.window.height() : this.scrollParent.height(),
++                      width: this.scrollParent[0] === this.document[0] ?
++                              this.window.width() : this.scrollParent.width()
++              };
 -              if (!isOverElement) {
++              this._preparePosition( pointerPosition );
 -              return this.floating ?
 -                      ( ((horizontalDirection && horizontalDirection === "right") || verticalDirection === "down") ? 2 : 1 )
 -                      : ( verticalDirection && (verticalDirection === "down" ? 2 : 1) );
 -
 -      },
 -
 -      _intersectsWithSides: function(item) {
 -
 -              var isOverBottomHalf = this._isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height),
 -                      isOverRightHalf = this._isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width),
 -                      verticalDirection = this._getDragVerticalDirection(),
 -                      horizontalDirection = this._getDragHorizontalDirection();
 -
 -              if (this.floating && horizontalDirection) {
 -                      return ((horizontalDirection === "right" && isOverRightHalf) || (horizontalDirection === "left" && !isOverRightHalf));
 -              } else {
 -                      return verticalDirection && ((verticalDirection === "down" && isOverBottomHalf) || (verticalDirection === "up" && !isOverBottomHalf));
 -              }
 -
 -      },
 -
 -      _getDragVerticalDirection: function() {
 -              var delta = this.positionAbs.top - this.lastPositionAbs.top;
 -              return delta !== 0 && (delta > 0 ? "down" : "up");
 -      },
 -
 -      _getDragHorizontalDirection: function() {
 -              var delta = this.positionAbs.left - this.lastPositionAbs.left;
 -              return delta !== 0 && (delta > 0 ? "right" : "left");
 -      },
 -
 -      refresh: function(event) {
 -              this._refreshItems(event);
 -              this._setHandleClassName();
 -              this.refreshPositions();
 -              return this;
 -      },
++              // If user cancels beforeStart, don't allow dragging
++              if ( this._trigger( "beforeStart", event,
++                              this._originalHash( pointerPosition ) ) === false ) {
+                       return false;
+               }
 -      _connectWith: function() {
 -              var options = this.options;
 -              return options.connectWith.constructor === String ? [options.connectWith] : options.connectWith;
++              this._setCss();
++              this.startPosition = this._getPosition();
++              this.startOffset = this.dragEl.offset();
 -      _getItemsAsjQuery: function(connected) {
 -
 -              var i, j, cur, inst,
 -                      items = [],
 -                      queries = [],
 -                      connectWith = this._connectWith();
 -
 -              if(connectWith && connected) {
 -                      for (i = connectWith.length - 1; i >= 0; i--){
 -                              cur = $(connectWith[i]);
 -                              for ( j = cur.length - 1; j >= 0; j--){
 -                                      inst = $.data(cur[j], this.widgetFullName);
 -                                      if(inst && inst !== this && !inst.options.disabled) {
 -                                              queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), inst]);
 -                                      }
 -                              }
 -                      }
 -              }
++              this._trigger( "start", event, this._fullHash( pointerPosition ) );
++              this._blockFrames();
+       },
 -              queries.push([$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), this]);
++      _move: function( event, pointerPosition ) {
 -              function addItems() {
 -                      items.push( this );
 -              }
 -              for (i = queries.length - 1; i >= 0; i--){
 -                      queries[i][0].each( addItems );
 -              }
++              var sort, sortItem, sortIndex,
++                      len = this.sortablePositions.length;
 -              return $(items);
++              this._preparePosition( pointerPosition );
 -      },
++              // // If user cancels drag, don't move the element
++              // if ( this._trigger( "drag", event, this._fullHash( pointerPosition ) ) === false ) {
++                      // return;
++              // }
 -      _removeCurrentsFromItems: function() {
++              this._setCss();
 -              var list = this.currentItem.find(":data(" + this.widgetName + "-item)");
++              // Scroll the scrollParent, if needed
++              this._handleScrolling( pointerPosition );
 -              this.items = $.grep(this.items, function (item) {
 -                      for (var j=0; j < list.length; j++) {
 -                              if(list[j] === item.item[0]) {
 -                                      return false;
++              for ( sortIndex = 0; sortIndex < len; ++sortIndex ) {
++                      for ( sort in this.sortablePositions[sortIndex] ) {
++                              sortItem = this.sortablePositions[sortIndex][sort];
 -                      }
 -                      return true;
 -              });
 -
 -      },
 -
 -      _refreshItems: function(event) {
 -
 -              this.items = [];
 -              this.containers = [this];
++                              // Don't bother checking against self
++                              if ( sortItem.el[0] === this.dragEl[0] ) {
++                                      continue;
+                               }
 -              var i, j, cur, inst, targetData, _queries, item, queriesLength,
 -                      items = this.items,
 -                      queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]],
 -                      connectWith = this._connectWith();
 -
 -              if(connectWith && this.ready) { //Shouldn't be run the first time through due to massive slow-down
 -                      for (i = connectWith.length - 1; i >= 0; i--){
 -                              cur = $(connectWith[i]);
 -                              for (j = cur.length - 1; j >= 0; j--){
 -                                      inst = $.data(cur[j], this.widgetFullName);
 -                                      if(inst && inst !== this && !inst.options.disabled) {
 -                                              queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]);
 -                                              this.containers.push(inst);
 -
 -              for (i = queries.length - 1; i >= 0; i--) {
 -                      targetData = queries[i][1];
 -                      _queries = queries[i][0];
 -
 -                      for (j=0, queriesLength = _queries.length; j < queriesLength; j++) {
 -                              item = $(_queries[j]);
 -
 -                              item.data(this.widgetName + "-item", targetData); // Data for target checking (mouse manager)
 -
 -                              items.push({
 -                                      item: item,
 -                                      instance: targetData,
 -                                      width: 0, height: 0,
 -                                      left: 0, top: 0
 -                              });
 -                      }
 -              }
 -
++                              if ( this._over( sortItem ) )  {
++                                      // TODO: cache height of element
++                                      if ( ( this.offset.top + this.dragEl.height() ) > ( sortItem.offset.top + sortItem.el.height() / 2 ) ) {
++                                              sortItem.el.after( this.dragEl );
++                                              this.dragEl.after( this.placeholder );
++                                              this._setSortablePositions();
++                                      } else if ( this.offset.top     < ( sortItem.offset.top + sortItem.el.height() / 2 ) ) {
++                                              sortItem.el.before( this.dragEl );
++                                              this.dragEl.before( this.placeholder );
++                                              this._setSortablePositions();
+                                       }
+                               }
+                       }
+               }
 -      refreshPositions: function(fast) {
 -
 -              //This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change
 -              if(this.offsetParent && this.helper) {
 -                      this.offset.parent = this._getParentOffset();
 -              }
 -
 -              var i, item, t, p;
 -
 -              for (i = this.items.length - 1; i >= 0; i--){
 -                      item = this.items[i];
+       },
 -                      //We ignore calculating positions of all connected containers when we're not over them
 -                      if(item.instance !== this.currentContainer && this.currentContainer && item.item[0] !== this.currentItem[0]) {
 -                              continue;
 -                      }
 -
 -                      t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item;
 -
 -                      if (!fast) {
 -                              item.width = t.outerWidth();
 -                              item.height = t.outerHeight();
 -                      }
 -
 -                      p = t.offset();
 -                      item.left = p.left;
 -                      item.top = p.top;
 -              }
++      // TODO: swap out for real tolerance options
++      _over: function( sortItem ) {
 -              if(this.options.custom && this.options.custom.refreshContainers) {
 -                      this.options.custom.refreshContainers.call(this);
 -              } else {
 -                      for (i = this.containers.length - 1; i >= 0; i--){
 -                              p = this.containers[i].element.offset();
 -                              this.containers[i].containerCache.left = p.left;
 -                              this.containers[i].containerCache.top = p.top;
 -                              this.containers[i].containerCache.width = this.containers[i].element.outerWidth();
 -                              this.containers[i].containerCache.height = this.containers[i].element.outerHeight();
 -                      }
 -              }
++              // TODO: use same cache from _move for height and width of element
++              var edges = {
++                      droppableRight: sortItem.offset.left + sortItem.el.width(),
++                      droppableBottom: sortItem.offset.top + sortItem.el.height(),
++                      draggableRight: this.offset.left + this.dragEl.width(),
++                      draggableBottom: this.offset.top + this.dragEl.height()
++              };
 -              return this;
++              return sortItem.offset.left < edges.draggableRight &&
++                              edges.droppableRight > this.offset.left &&
++                              sortItem.offset.top < edges.draggableBottom &&
++                              edges.droppableBottom > sortItem.offset.top;
 -      _createPlaceholder: function(that) {
 -              that = that || this;
 -              var className,
 -                      o = that.options;
 -
 -              if(!o.placeholder || o.placeholder.constructor === String) {
 -                      className = o.placeholder;
 -                      o.placeholder = {
 -                              element: function() {
 -
 -                                      var nodeName = that.currentItem[0].nodeName.toLowerCase(),
 -                                              element = $( "<" + nodeName + ">", that.document[0] )
 -                                                      .addClass(className || that.currentItem[0].className+" ui-sortable-placeholder")
 -                                                      .removeClass("ui-sortable-helper");
 -
 -                                      if ( nodeName === "tr" ) {
 -                                              that.currentItem.children().each(function() {
 -                                                      $( "<td>&#160;</td>", that.document[0] )
 -                                                              .attr( "colspan", $( this ).attr( "colspan" ) || 1 )
 -                                                              .appendTo( element );
 -                                              });
 -                                      } else if ( nodeName === "img" ) {
 -                                              element.attr( "src", that.currentItem.attr( "src" ) );
 -                                      }
+       },
 -                                      if ( !className ) {
 -                                              element.css( "visibility", "hidden" );
 -                                      }
++      _stop: function( event, pointerPosition ) {
 -                                      return element;
 -                              },
 -                              update: function(container, p) {
++              this._preparePosition( pointerPosition );
 -                                      // 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that
 -                                      // 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified
 -                                      if(className && !o.forcePlaceholderSize) {
 -                                              return;
 -                                      }
 -
 -                                      //If the element doesn't have a actual height by itself (without styles coming from a stylesheet), it receives the inline height from the dragged item
 -                                      if(!p.height()) { p.height(that.currentItem.innerHeight() - parseInt(that.currentItem.css("paddingTop")||0, 10) - parseInt(that.currentItem.css("paddingBottom")||0, 10)); }
 -                                      if(!p.width()) { p.width(that.currentItem.innerWidth() - parseInt(that.currentItem.css("paddingLeft")||0, 10) - parseInt(that.currentItem.css("paddingRight")||0, 10)); }
 -                              }
 -                      };
++              // // If user cancels stop, leave helper there
++              // if ( this._trigger( "stop", event, this._fullHash( pointerPosition ) ) !== false ) {
++                      // if ( this.options.helper ) {
++                              // this.dragEl.remove();
++                      // }
++                      // this._resetDomPosition();
++              // }
 -              //Create the placeholder
 -              that.placeholder = $(o.placeholder.element.call(that.element, that.currentItem));
++              if ( this.originalCssPosition ) {
++                      this.dragEl.css( "position", this.originalCssPosition );
++              } else {
++                      this.dragEl.css( "position", "" );
+               }
 -              //Append it after the actual current item
 -              that.currentItem.after(that.placeholder);
++              // TODO: should same thing be done here as is done for position or is there better way altogether
++              this.dragEl.css( "left", "" );
++              this.dragEl.css( "top", "" );
 -              //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317)
 -              o.placeholder.update(that, that.placeholder);
++              this.placeholder.remove();
 -      _contactContainers: function(event) {
 -              var i, j, dist, itemWithLeastDistance, posProperty, sizeProperty, cur, nearBottom, floating, axis,
 -                      innermostContainer = null,
 -                      innermostIndex = null;
++              // Unset properties only needed during draggin/sorting
++              this.dragEl = null;
++              this.originalCssPosition = null;
++              this.placeholder = null;
++              this._unblockFrames();
+       },
 -              // get innermost container that intersects with item
 -              for (i = this.containers.length - 1; i >= 0; i--) {
++      // /** internal **/
 -                      // never consider a container that's located within the item itself
 -                      if($.contains(this.currentItem[0], this.containers[i].element[0])) {
 -                              continue;
 -                      }
++      // _createHelper: function( pointerPosition ) {
++              // var helper,
++                      // offset = this.element.offset(),
++                      // xPos = (pointerPosition.x - offset.left) / this.element.outerWidth(),
++                      // yPos = (pointerPosition.y - offset.top) / this.element.outerHeight();
 -                      if(this._intersectsWith(this.containers[i].containerCache)) {
++              // // clone
++              // if ( this.options.helper === true ) {
++                      // helper = this.element.clone()
++                              // .removeAttr( "id" )
++                              // .find( "[id]" )
++                                      // .removeAttr( "id" )
++                              // .end();
++              // } else {
++                      // // TODO: figure out the signature for this; see #4957
++                      // helper = $( this.options.helper() );
++              // }
 -                              // if we've already found a container and it's more "inner" than this, then continue
 -                              if(innermostContainer && $.contains(this.containers[i].element[0], innermostContainer.element[0])) {
 -                                      continue;
 -                              }
 -
 -                              innermostContainer = this.containers[i];
 -                              innermostIndex = i;
 -
 -                      } else {
 -                              // container doesn't intersect. trigger "out" event if necessary
 -                              if(this.containers[i].containerCache.over) {
 -                                      this.containers[i]._trigger("out", event, this._uiHash(this));
 -                                      this.containers[i].containerCache.over = 0;
 -                              }
 -                      }
 -
 -              }
 -
 -              // if no intersecting containers found, return
 -              if(!innermostContainer) {
 -                      return;
 -              }
 -
 -              // move the item into the container if it's not there already
 -              if(this.containers.length === 1) {
 -                      if (!this.containers[innermostIndex].containerCache.over) {
 -                              this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
 -                              this.containers[innermostIndex].containerCache.over = 1;
 -                      }
 -              } else {
 -
 -                      //When entering a new container, we will find the item with the least distance and append our item near it
 -                      dist = 10000;
 -                      itemWithLeastDistance = null;
 -                      floating = innermostContainer.floating || this._isFloating(this.currentItem);
 -                      posProperty = floating ? "left" : "top";
 -                      sizeProperty = floating ? "width" : "height";
 -                      axis = floating ? "clientX" : "clientY";
 -
 -                      for (j = this.items.length - 1; j >= 0; j--) {
 -                              if(!$.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) {
 -                                      continue;
 -                              }
 -                              if(this.items[j].item[0] === this.currentItem[0]) {
 -                                      continue;
 -                              }
 -
 -                              cur = this.items[j].item.offset()[posProperty];
 -                              nearBottom = false;
 -                              if ( event[ axis ] - cur > this.items[ j ][ sizeProperty ] / 2 ) {
 -                                      nearBottom = true;
 -                              }
 -
 -                              if ( Math.abs( event[ axis ] - cur ) < dist ) {
 -                                      dist = Math.abs( event[ axis ] - cur );
 -                                      itemWithLeastDistance = this.items[ j ];
 -                                      this.direction = nearBottom ? "up": "down";
 -                              }
 -                      }
 -
 -                      //Check if dropOnEmpty is enabled
 -                      if(!itemWithLeastDistance && !this.options.dropOnEmpty) {
 -                              return;
 -                      }
 -
 -                      if(this.currentContainer === this.containers[innermostIndex]) {
 -                              return;
 -                      }
 -
 -                      itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true);
 -                      this._trigger("change", event, this._uiHash());
 -                      this.containers[innermostIndex]._trigger("change", event, this._uiHash(this));
 -                      this.currentContainer = this.containers[innermostIndex];
++              // // Ensure the helper is in the DOM; obey the appendTo option if it exists
++              // if ( this.options.appendTo || !helper.closest( "body" ).length ) {
++                      // helper.appendTo( this.options.appendTo || this.document[0].body );
++              // }
 -                      //Update the placeholder
 -                      this.options.placeholder.update(this.currentContainer, this.placeholder);
++              // return helper
++                      // // Helper must be absolute to function properly
++                      // .css( "position", "absolute" )
++                      // .offset({
++                              // left: pointerPosition.x - helper.outerWidth() * xPos,
++                              // top: pointerPosition.y - helper.outerHeight() * yPos
++                      // });
++      // },
 -                      this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
 -                      this.containers[innermostIndex].containerCache.over = 1;
 -              }
 -
 -
 -      },
 -
 -      _createHelper: function(event) {
++      _getPosition: function() {
++              var left, top, position,
++                      scrollTop = this.scrollParent.scrollTop(),
++                      scrollLeft = this.scrollParent.scrollLeft();
 -              var o = this.options,
 -                      helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper === "clone" ? this.currentItem.clone() : this.currentItem);
++              // If fixed or absolute
++              if ( this.cssPosition !== "relative" ) {
++                      position = this.dragEl.position();
 -              //Add the helper to the DOM if that didn't happen already
 -              if(!helper.parents("body").length) {
 -                      $(o.appendTo !== "parent" ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]);
 -              }
 -
 -              if(helper[0] === this.currentItem[0]) {
 -                      this._storedCSS = { width: this.currentItem[0].style.width, height: this.currentItem[0].style.height, position: this.currentItem.css("position"), top: this.currentItem.css("top"), left: this.currentItem.css("left") };
 -              }
 -
 -              if(!helper[0].style.width || o.forceHelperSize) {
 -                      helper.width(this.currentItem.width());
 -              }
 -              if(!helper[0].style.height || o.forceHelperSize) {
 -                      helper.height(this.currentItem.height());
 -              }
 -
 -              return helper;
 -
 -      },
 -
 -      _adjustOffsetFromHelper: function(obj) {
 -              if (typeof obj === "string") {
 -                      obj = obj.split(" ");
 -              }
 -              if ($.isArray(obj)) {
 -                      obj = {left: +obj[0], top: +obj[1] || 0};
 -              }
 -              if ("left" in obj) {
 -                      this.offset.click.left = obj.left + this.margins.left;
++                      // Take into account scrollbar
++                      position.top -= scrollTop;
++                      position.left -= scrollLeft;
 -              if ("right" in obj) {
 -                      this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
 -              }
 -              if ("top" in obj) {
 -                      this.offset.click.top = obj.top + this.margins.top;
 -              }
 -              if ("bottom" in obj) {
 -                      this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
 -              }
 -      },
 -
 -      _getParentOffset: function() {
 -
++                      return position;
+               }
 -              //Get the offsetParent and cache its position
 -              this.offsetParent = this.helper.offsetParent();
 -              var po = this.offsetParent.offset();
 -              // This is a special case where we need to modify a offset calculated on start, since the following happened:
 -              // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
 -              // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
 -              //    the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
 -              if(this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) {
 -                      po.left += this.scrollParent.scrollLeft();
 -                      po.top += this.scrollParent.scrollTop();
 -              }
 -
 -              // This needs to be actually done for all browsers, since pageX/pageY includes this information
 -              // with an ugly IE fix
 -              if( this.offsetParent[0] === document.body || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) {
 -                      po = { top: 0, left: 0 };
 -              }
++              // When using relative, css values are checked
++              // Otherwise the position wouldn't account for padding on ancestors
++              left = this.dragEl.css( "left" );
++              top = this.dragEl.css( "top" );
 -                      top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
 -                      left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
++              // Webkit will give back auto if there is no explicit value
++              left = ( left === "auto" ) ? 0: parseInt( left, 10 );
++              top = ( top === "auto" ) ? 0: parseInt( top, 10 );
+               return {
 -
 -      },
 -
 -      _getRelativeOffset: function() {
 -
 -              if(this.cssPosition === "relative") {
 -                      var p = this.currentItem.position();
 -                      return {
 -                              top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
 -                              left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
 -                      };
 -              } else {
 -                      return { top: 0, left: 0 };
 -              }
 -
++                      left: left - scrollLeft,
++                      top: top - scrollTop
+               };
 -      _cacheMargins: function() {
 -              this.margins = {
 -                      left: (parseInt(this.currentItem.css("marginLeft"),10) || 0),
 -                      top: (parseInt(this.currentItem.css("marginTop"),10) || 0)
+       },
 -      },
++      _handleScrolling: function( pointerPosition ) {
++              var scrollTop = this.scrollParent.scrollTop(),
++                      scrollLeft = this.scrollParent.scrollLeft(),
++                      scrollSensitivity = 20,
++                      baseSpeed = 5,
++                      speed = function( distance ) {
++                              return baseSpeed + Math.round( distance / 2 );
++                      },
++                      // overflowOffset is only set when scrollParent is not doc/html
++                      overflowLeft = this.overflowOffset ?
++                              this.overflowOffset.left :
++                              scrollLeft,
++                      overflowTop = this.overflowOffset ?
++                              this.overflowOffset.top :
++                              scrollTop,
++                      xRight = this.overflow.width + overflowLeft - pointerPosition.x,
++                      xLeft = pointerPosition.x - overflowLeft,
++                      yBottom = this.overflow.height + overflowTop - pointerPosition.y,
++                      yTop = pointerPosition.y - overflowTop;
++
++              // Handle vertical scrolling
++              if ( yBottom < scrollSensitivity ) {
++                      this.scrollParent.scrollTop( scrollTop +
++                              speed( scrollSensitivity - yBottom ) );
++              } else if ( yTop < scrollSensitivity ) {
++                      this.scrollParent.scrollTop( scrollTop -
++                              speed( scrollSensitivity - yTop ) );
++              }
++
++              // Handle horizontal scrolling
++              if ( xRight < scrollSensitivity ) {
++                      this.scrollParent.scrollLeft( scrollLeft +
++                              speed( scrollSensitivity - xRight ) );
++              } else if ( xLeft < scrollSensitivity ) {
++                      this.scrollParent.scrollLeft( scrollLeft -
++                              speed( scrollSensitivity - xLeft ) );
++              }
++      },
++
++      // Uses event to determine new position of draggable, before any override from callbacks
++      // TODO: handle absolute element inside relative parent like a relative element
++      _preparePosition: function( pointerPosition ) {
++              var leftDiff = pointerPosition.x - this.originalPointer.x,
++                      topDiff = pointerPosition.y - this.originalPointer.y,
++                      newLeft = leftDiff + this.startPosition.left,
++                      newTop = topDiff + this.startPosition.top;
++
++              // Save off new values for .css() in various callbacks using this function
++              this.position = {
++                      left: newLeft,
++                      top: newTop
+               };
 -      _cacheHelperProportions: function() {
 -              this.helperProportions = {
 -                      width: this.helper.outerWidth(),
 -                      height: this.helper.outerHeight()
 -      _setContainment: function() {
++              // Save off values to compare user override against automatic coordinates
++              this.tempPosition = {
++                      left: newLeft,
++                      top: newTop
+               };
++
++              // Refresh offset cache with new positions
++              this.offset.left = this.startOffset.left + leftDiff;
++              this.offset.top = this.startOffset.top + topDiff;
+       },
 -              var ce, co, over,
 -                      o = this.options;
 -              if(o.containment === "parent") {
 -                      o.containment = this.helper[0].parentNode;
 -              }
 -              if(o.containment === "document" || o.containment === "window") {
 -                      this.containment = [
 -                              0 - this.offset.relative.left - this.offset.parent.left,
 -                              0 - this.offset.relative.top - this.offset.parent.top,
 -                              $(o.containment === "document" ? document : window).width() - this.helperProportions.width - this.margins.left,
 -                              ($(o.containment === "document" ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
 -                      ];
++      // Places draggable where event, or user via event/callback, indicates
++      _setCss: function() {
++              var newLeft = this.position.left,
++                      newTop = this.position.top;
 -              if(!(/^(document|window|parent)$/).test(o.containment)) {
 -                      ce = $(o.containment)[0];
 -                      co = $(o.containment).offset();
 -                      over = ($(ce).css("overflow") !== "hidden");
 -
 -                      this.containment = [
 -                              co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left,
 -                              co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top,
 -                              co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left,
 -                              co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top
 -                      ];
++              // User overriding left/top so shortcut math is no longer valid
++              if ( this.tempPosition.left !== this.position.left ||
++                              this.tempPosition.top !== this.position.top ) {
++                      // Reset offset based on difference of expected and overridden values
++                      this.offset.left += newLeft - this.tempPosition.left;
++                      this.offset.top += newTop - this.tempPosition.top;
+               }
 -      _convertPositionTo: function(d, pos) {
 -
 -              if(!pos) {
 -                      pos = this.position;
 -              }
 -              var mod = d === "absolute" ? 1 : -1,
 -                      scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent,
 -                      scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
 -
 -              return {
 -                      top: (
 -                              pos.top +                                                                                                                               // The absolute mouse position
 -                              this.offset.relative.top * mod +                                                                                // Only for relative positioned nodes: Relative offset from element to offset parent
 -                              this.offset.parent.top * mod -                                                                                  // The offsetParent's offset without borders (offset + border)
 -                              ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
 -                      ),
 -                      left: (
 -                              pos.left +                                                                                                                              // The absolute mouse position
 -                              this.offset.relative.left * mod +                                                                               // Only for relative positioned nodes: Relative offset from element to offset parent
 -                              this.offset.parent.left * mod   -                                                                               // The offsetParent's offset without borders (offset + border)
 -                              ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
 -                      )
++              // TODO: does this work with nested scrollable parents?
++              if ( this.cssPosition !== "fixed" ) {
++                      newLeft += this.scrollParent.scrollLeft();
++                      newTop += this.scrollParent.scrollTop();
+               }
++              this.dragEl.css({
++                      left: newLeft,
++                      top: newTop
++              });
+       },
 -      },
 -
 -      _generatePosition: function(event) {
 -
 -              var top, left,
 -                      o = this.options,
 -                      pageX = event.pageX,
 -                      pageY = event.pageY,
 -                      scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
 -
 -              // This is another very weird special case that only happens for relative elements:
 -              // 1. If the css position is relative
 -              // 2. and the scroll parent is the document or similar to the offset parent
 -              // we have to refresh the relative offset during the scroll so there are no jumps
 -              if(this.cssPosition === "relative" && !(this.scrollParent[0] !== document && this.scrollParent[0] !== this.offsetParent[0])) {
 -                      this.offset.relative = this._getRelativeOffset();
++      _originalHash: function( pointerPosition ) {
++              var ret = {
++                      position: this.position,
++                      offset: copy( this.offset ),
++                      pointer: copy( pointerPosition )
+               };
 -              /*
 -               * - Position constraining -
 -               * Constrain the position to a mix of grid, containment.
 -               */
 -
 -              if(this.originalPosition) { //If we are not dragging yet, we won't check for options
 -
 -                      if(this.containment) {
 -                              if(event.pageX - this.offset.click.left < this.containment[0]) {
 -                                      pageX = this.containment[0] + this.offset.click.left;
 -                              }
 -                              if(event.pageY - this.offset.click.top < this.containment[1]) {
 -                                      pageY = this.containment[1] + this.offset.click.top;
 -                              }
 -                              if(event.pageX - this.offset.click.left > this.containment[2]) {
 -                                      pageX = this.containment[2] + this.offset.click.left;
 -                              }
 -                              if(event.pageY - this.offset.click.top > this.containment[3]) {
 -                                      pageY = this.containment[3] + this.offset.click.top;
 -                              }
 -                      }
 -
 -                      if(o.grid) {
 -                              top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1];
 -                              pageY = this.containment ? ( (top - this.offset.click.top >= this.containment[1] && top - this.offset.click.top <= this.containment[3]) ? top : ((top - this.offset.click.top >= this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
 -
 -                              left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0];
 -                              pageX = this.containment ? ( (left - this.offset.click.left >= this.containment[0] && left - this.offset.click.left <= this.containment[2]) ? left : ((left - this.offset.click.left >= this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
 -                      }
 -
 -              }
 -
 -              return {
 -                      top: (
 -                              pageY -                                                                                                                         // The absolute mouse position
 -                              this.offset.click.top -                                                                                                 // Click offset (relative to the element)
 -                              this.offset.relative.top        -                                                                                       // Only for relative positioned nodes: Relative offset from element to offset parent
 -                              this.offset.parent.top +                                                                                                // The offsetParent's offset without borders (offset + border)
 -                              ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
 -                      ),
 -                      left: (
 -                              pageX -                                                                                                                         // The absolute mouse position
 -                              this.offset.click.left -                                                                                                // Click offset (relative to the element)
 -                              this.offset.relative.left       -                                                                                       // Only for relative positioned nodes: Relative offset from element to offset parent
 -                              this.offset.parent.left +                                                                                               // The offsetParent's offset without borders (offset + border)
 -                              ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
 -                      )
 -              };
 -
++              if ( this.options.helper ) {
++                      ret.helper = this.dragEl;
+               }
 -      _rearrange: function(event, i, a, hardRefresh) {
 -
 -              a ? a[0].appendChild(this.placeholder[0]) : i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction === "down" ? i.item[0] : i.item[0].nextSibling));
 -
 -              //Various things done here to improve the performance:
 -              // 1. we create a setTimeout, that calls refreshPositions
 -              // 2. on the instance, we have a counter variable, that get's higher after every append
 -              // 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same
 -              // 4. this lets only the last addition to the timeout stack through
 -              this.counter = this.counter ? ++this.counter : 1;
 -              var counter = this.counter;
 -
 -              this._delay(function() {
 -                      if(counter === this.counter) {
 -                              this.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove
 -                      }
++              return ret;
+       },
 -
++      _fullHash: function( pointerPosition ) {
++              return $.extend( this._originalHash( pointerPosition ), {
++                      originalPosition: copy( this.originalPosition ),
++                      originalOffset: copy( this.originalOffset ),
++                      originalPointer: copy( this.originalPointer )
+               });
 -      _clear: function(event, noPropagation) {
 -
 -              this.reverting = false;
 -              // We delay all events that have to be triggered to after the point where the placeholder has been removed and
 -              // everything else normalized again
 -              var i,
 -                      delayedTriggers = [];
 -
 -              // We first have to update the dom position of the actual currentItem
 -              // Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088)
 -              if(!this._noFinalSort && this.currentItem.parent().length) {
 -                      this.placeholder.before(this.currentItem);
 -              }
 -              this._noFinalSort = null;
 -
 -              if(this.helper[0] === this.currentItem[0]) {
 -                      for(i in this._storedCSS) {
 -                              if(this._storedCSS[i] === "auto" || this._storedCSS[i] === "static") {
 -                                      this._storedCSS[i] = "";
 -                              }
 -                      }
 -                      this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
 -              } else {
 -                      this.currentItem.show();
 -              }
 -
 -              if(this.fromOutside && !noPropagation) {
 -                      delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); });
 -              }
 -              if((this.fromOutside || this.domPosition.prev !== this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent !== this.currentItem.parent()[0]) && !noPropagation) {
 -                      delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed
 -              }
 -
 -              // Check if the items Container has Changed and trigger appropriate
 -              // events.
 -              if (this !== this.currentContainer) {
 -                      if(!noPropagation) {
 -                              delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); });
 -                              delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); };  }).call(this, this.currentContainer));
 -                              delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this));  }; }).call(this, this.currentContainer));
 -                      }
 -              }
 -
 -
 -              //Post events to containers
 -              function delayEvent( type, instance, container ) {
 -                      return function( event ) {
 -                              container._trigger( type, event, instance._uiHash( instance ) );
 -                      };
 -              }
 -              for (i = this.containers.length - 1; i >= 0; i--){
 -                      if (!noPropagation) {
 -                              delayedTriggers.push( delayEvent( "deactivate", this, this.containers[ i ] ) );
 -                      }
 -                      if(this.containers[i].containerCache.over) {
 -                              delayedTriggers.push( delayEvent( "out", this, this.containers[ i ] ) );
 -                              this.containers[i].containerCache.over = 0;
 -                      }
 -              }
 -
 -              //Do what was originally in plugins
 -              if ( this.storedCursor ) {
 -                      this.document.find( "body" ).css( "cursor", this.storedCursor );
 -                      this.storedStylesheet.remove();
 -              }
 -              if(this._storedOpacity) {
 -                      this.helper.css("opacity", this._storedOpacity);
 -              }
 -              if(this._storedZIndex) {
 -                      this.helper.css("zIndex", this._storedZIndex === "auto" ? "" : this._storedZIndex);
 -              }
 -
 -              this.dragging = false;
 -              if(this.cancelHelperRemoval) {
 -                      if(!noPropagation) {
 -                              this._trigger("beforeStop", event, this._uiHash());
 -                              for (i=0; i < delayedTriggers.length; i++) {
 -                                      delayedTriggers[i].call(this, event);
 -                              } //Trigger all delayed events
 -                              this._trigger("stop", event, this._uiHash());
 -                      }
 -
 -                      this.fromOutside = false;
 -                      return false;
 -              }
 -
 -              if(!noPropagation) {
 -                      this._trigger("beforeStop", event, this._uiHash());
 -              }
+       },
 -              //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
 -              this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
 -
 -              if(this.helper[0] !== this.currentItem[0]) {
 -                      this.helper.remove();
 -              }
 -              this.helper = null;
 -
 -              if(!noPropagation) {
 -                      for (i=0; i < delayedTriggers.length; i++) {
 -                              delayedTriggers[i].call(this, event);
 -                      } //Trigger all delayed events
 -                      this._trigger("stop", event, this._uiHash());
 -              }
 -
 -              this.fromOutside = false;
 -              return true;
++      _blockFrames: function() {
 -      _trigger: function() {
 -              if ($.Widget.prototype._trigger.apply(this, arguments) === false) {
 -                      this.cancel();
++              this.iframeBlocks = this.document.find( "iframe" ).map(function() {
++                      var iframe = $( this );
++                      return $( "<div>" )
++                              .css({
++                                      position: "absolute",
++                                      width: iframe.outerWidth(),
++                                      height: iframe.outerHeight()
++                              })
++                              .appendTo( iframe.parent() )
++                              .offset( iframe.offset() )[0];
++              });
+       },
 -      _uiHash: function(_inst) {
 -              var inst = _inst || this;
 -              return {
 -                      helper: inst.helper,
 -                      placeholder: inst.placeholder || $([]),
 -                      position: inst.position,
 -                      originalPosition: inst.originalPosition,
 -                      offset: inst.positionAbs,
 -                      item: inst.currentItem,
 -                      sender: _inst ? _inst.element : null
 -              };
++      _unblockFrames: function() {
++              if ( this.iframeBlocks ) {
++                      this.iframeBlocks.remove();
++                      delete this.iframeBlocks;
+               }
+       },
 -
++      _destroy: function() {
++              this.element.removeClass( "ui-sortable" );
++              this._super();
+       }
+ });
++return $.ui.sortable;
++
+ }));