diff --git a/sortable/sortable-tests.ts b/sortable/sortable-tests.ts
new file mode 100755
index 000000000..cc2b2cdcd
--- /dev/null
+++ b/sortable/sortable-tests.ts
@@ -0,0 +1,311 @@
+// Examples from project repo used for tests.
+
+///
+
+var simpleList = document.getElementById('list');
+var list = simpleList;
+var el = document.getElementById('el');
+var sortable = new Sortable(simpleList, {});
+var order = sortable.toArray();
+var angular: any;
+var Ply: any;
+
+sortable.sort(order.reverse());
+
+Sortable.create(list, {
+ delay: 500,
+ chosenClass: "chosen"
+});
+
+Sortable.create(el, {
+ handle: ".my-handle"
+});
+
+Sortable.create(list, {
+ filter: ".js-remove, .js-edit",
+ onFilter: function(event) {
+ var item = event.item,
+ control = event.target;
+
+ if (Sortable.utils.is(control, ".js-remove")) {
+ item.parentNode.removeChild(item);
+ }
+ else if (Sortable.utils.is(control, ".js-edit")) {
+ // ..
+ }
+ }
+});
+
+Sortable.create(el, {
+ group: "localStorage-example",
+ store: {
+ get: function(sortable) {
+ var order = localStorage.getItem(sortable.options.group);
+
+ return order ? order.split('|') : [];
+ },
+ set: function(sortable) {
+ var order = sortable.toArray();
+
+ localStorage.setItem(sortable.options.group, order.join('|'));
+ }
+ }
+});
+
+Sortable.create(simpleList, {
+ forceFallback: true
+});
+
+Sortable.create(simpleList, {
+ ghostClass: 'ghost'
+});
+
+simpleList.innerHTML = Array.apply(null, new Array(100)).map(function(value, iterator) {
+ return `
item ${iterator + 1}
`;
+}).join('');
+
+Sortable.create(simpleList, {
+ delay: 500,
+ chosenClass: 'chosen'
+});
+
+simpleList.innerHTML = Array.apply(null, new Array(10)).map(function(v, i) {
+ return 'item ' +
+ (i + 1) +
+ '
';
+}).join('');
+
+Sortable.create(simpleList, {});
+
+simpleList.innerHTML = Array.apply(null, new Array(100)).map(function(v, i) {
+ return 'item ' +
+ (i + 1) +
+ '
';
+}).join('');
+
+(function() {
+ 'use strict';
+
+ var byId = function(id) { return document.getElementById(id); },
+
+ loadScripts = function(desc, callback) {
+ var deps = [], key, idx = 0;
+
+ for (key in desc) {
+ deps.push(key);
+ }
+
+ (function _next() {
+ var pid,
+ name = deps[idx],
+ script = document.createElement('script');
+
+ script.type = 'text/javascript';
+ script.src = desc[deps[idx]];
+
+ pid = setInterval(function() {
+ if (window[name]) {
+ clearTimeout(pid);
+
+ deps[idx++] = window[name];
+
+ if (deps[idx]) {
+ _next();
+ } else {
+ callback.apply(null, deps);
+ }
+ }
+ }, 30);
+
+ document.getElementsByTagName('head')[0].appendChild(script);
+ })()
+ },
+
+ console = window.console;
+
+
+ if (!console.log) {
+ console.log = function() {
+ alert([].join.apply(arguments, ' '));
+ };
+ }
+
+
+ Sortable.create(byId('foo'), {
+ group: "words",
+ animation: 150,
+ store: {
+ get: function(sortable) {
+ var order = localStorage.getItem(sortable.options.group);
+ return order ? order.split('|') : [];
+ },
+ set: function(sortable) {
+ var order = sortable.toArray();
+ localStorage.setItem(sortable.options.group, order.join('|'));
+ }
+ },
+ onAdd: function(evt) { console.log('onAdd.foo:', [evt.item, evt.from]); },
+ onUpdate: function(evt) { console.log('onUpdate.foo:', [evt.item, evt.from]); },
+ onRemove: function(evt) { console.log('onRemove.foo:', [evt.item, evt.from]); },
+ onStart: function(evt) { console.log('onStart.foo:', [evt.item, evt.from]); },
+ onSort: function(evt) { console.log('onStart.foo:', [evt.item, evt.from]); },
+ onEnd: function(evt) { console.log('onEnd.foo:', [evt.item, evt.from]); }
+ });
+
+
+ Sortable.create(byId('bar'), {
+ group: "words",
+ animation: 150,
+ onAdd: function(evt) { console.log('onAdd.bar:', evt.item); },
+ onUpdate: function(evt) { console.log('onUpdate.bar:', evt.item); },
+ onRemove: function(evt) { console.log('onRemove.bar:', evt.item); },
+ onStart: function(evt) { console.log('onStart.foo:', evt.item); },
+ onEnd: function(evt) { console.log('onEnd.foo:', evt.item); }
+ });
+
+
+ // Multi groups
+ Sortable.create(byId('multi'), {
+ animation: 150,
+ draggable: '.tile',
+ handle: '.tile__name'
+ });
+
+ [].forEach.call(byId('multi').getElementsByClassName('tile__list'), function(el) {
+ Sortable.create(el, {
+ group: 'photo',
+ animation: 150
+ });
+ });
+
+
+ // Editable list
+ var editableList = Sortable.create(byId('editable'), {
+ animation: 150,
+ filter: '.js-remove',
+ onFilter: function(evt) {
+ evt.item.parentNode.removeChild(evt.item);
+ }
+ });
+
+
+ byId('addUser').onclick = function() {
+ Ply.dialog('prompt', {
+ title: 'Add',
+ form: { name: 'name' }
+ }).done(function(ui) {
+ var el = document.createElement('li');
+ el.innerHTML = ui.data.name + '✖';
+ editableList.el.appendChild(el);
+ });
+ };
+
+
+ // Advanced groups
+ [{
+ name: 'advanced',
+ pull: true,
+ put: true
+ },
+ {
+ name: 'advanced',
+ pull: 'clone',
+ put: false
+ }, {
+ name: 'advanced',
+ pull: false,
+ put: true
+ }].forEach(function(groupOpts, i) {
+ Sortable.create(byId('advanced-' + (i + 1)), {
+ sort: (i != 1),
+ group: groupOpts,
+ animation: 150
+ });
+ });
+
+
+ // 'handle' option
+ Sortable.create(byId('handle-1'), {
+ handle: '.drag-handle',
+ animation: 150
+ });
+
+
+ // Angular example
+ angular.module('todoApp', ['ng-sortable'])
+ .constant('ngSortableConfig', {
+ onEnd: function() {
+ console.log('default onEnd()');
+ }
+ })
+ .controller('TodoController', ['$scope', function($scope) {
+ $scope.todos = [
+ { text: 'learn angular', done: true },
+ { text: 'build an angular app', done: false }
+ ];
+
+ $scope.addTodo = function() {
+ $scope.todos.push({ text: $scope.todoText, done: false });
+ $scope.todoText = '';
+ };
+
+ $scope.remaining = function() {
+ var count = 0;
+ angular.forEach($scope.todos, function(todo) {
+ count += todo.done ? 0 : 1;
+ });
+ return count;
+ };
+
+ $scope.archive = function() {
+ var oldTodos = $scope.todos;
+ $scope.todos = [];
+ angular.forEach(oldTodos, function(todo) {
+ if (!todo.done) $scope.todos.push(todo);
+ });
+ };
+ }])
+ .controller('TodoControllerNext', ['$scope', function($scope) {
+ $scope.todos = [
+ { text: 'learn Sortable', done: true },
+ { text: 'use ng-sortable', done: false },
+ { text: 'Enjoy', done: false }
+ ];
+
+ $scope.remaining = function() {
+ var count = 0;
+ angular.forEach($scope.todos, function(todo) {
+ count += todo.done ? 0 : 1;
+ });
+ return count;
+ };
+
+ $scope.sortableConfig = { group: 'todo', animation: 150 };
+ 'Start End Add Update Remove Sort'.split(' ').forEach(function(name) {
+ $scope.sortableConfig['on' + name] = console.log.bind(console, name);
+ });
+ }]);
+})();
+
+// Background
+document.addEventListener("DOMContentLoaded", function() {
+ function setNoiseBackground(el, width, height, opacity) {
+ var canvas = document.createElement("canvas");
+ var context = canvas.getContext("2d");
+
+ canvas.width = width;
+ canvas.height = height;
+
+ for (var i = 0; i < width; i++) {
+ for (var j = 0; j < height; j++) {
+ var val = Math.floor(Math.random() * 255);
+ context.fillStyle = "rgba(" + val + "," + val + "," + val + "," + opacity + ")";
+ context.fillRect(i, j, 1, 1);
+ }
+ }
+
+ el.style.background = "url(" + canvas.toDataURL("image/png") + ")";
+ }
+
+ setNoiseBackground(document.getElementsByTagName('body')[0], 50, 50, 0.02);
+}, false);
diff --git a/sortable/sortable.d.ts b/sortable/sortable.d.ts
new file mode 100755
index 000000000..f52272659
--- /dev/null
+++ b/sortable/sortable.d.ts
@@ -0,0 +1,208 @@
+// Type definitions for Sortable.js v1.3.0-rc1
+// Project: https://github.com/RubaXa/Sortable
+// Definitions by: Maw-Fox
+// Definitions: https://github.com/borisyankov/DefinitelyTyped
+
+declare module Sortablejs {
+ interface SortableOptions {
+ group?: any;
+ sort?: boolean;
+ delay?: number;
+ disabled?: boolean;
+ store?: {
+ get: (sortable: Sortable) => any[];
+ set: (sortable: Sortable) => any;
+ };
+ animation?: number;
+ handle?: string;
+ filter?: any;
+ draggable?: string;
+ ghostClass?: string;
+ chosenClass?: string;
+ dataIdAttr?: string;
+ forceFallback?: boolean;
+ fallbackClass?: string;
+ fallbackOnBody?: boolean;
+ scroll?: boolean;
+ scrollSensitivity?: number;
+ scrollSpeed?: number;
+ setData?: (dataTransfer: any, draggedElement: any) => any;
+ onStart?: (event: any) => any;
+ onEnd?: (event: any) => any;
+ onAdd?: (event: any) => any;
+ onUpdate?: (event: any) => any;
+ onSort?: (event: any) => any;
+ onRemove?: (event: any) => any;
+ onFilter?: (event: any) => any;
+ onMove?: (event: any) => boolean;
+ }
+
+ interface SortableableUtils {
+ /**
+ * Attach an event handler function
+ * @param {HTMLElement} element an HTMLElement.
+ * @param {string} event an Event context.
+ * @param {Function} fn
+ */
+ on(element: any, event: string, fn: (event: any) => any): void;
+
+ /**
+ * Remove an event handler function
+ * @param {HTMLElement} element an HTMLElement.
+ * @param {string} event an Event context.
+ * @param {Function} fn a callback.
+ */
+ off(element: any, event: string, fn: (event: any) => any): void;
+
+ /**
+ * Get the values of all the CSS properties.
+ * @param {HTMLElement} element an HTMLElement.
+ * @returns {Object}
+ */
+ css(element: any): any;
+
+ /**
+ * Get the value of style properties.
+ * @param {HTMLElement} element an HTMLElement.
+ * @param {string} prop a property key.
+ * @returns {*}
+ */
+ css(element: any, prop: string): any;
+
+ /**
+ * Set one CSS property.
+ * @param {HTMLElement} element an HTMLElement.
+ * @param {string} prop a property key.
+ * @param {string} value a property value.
+ */
+ css(element: any, prop: string, value: string): void;
+
+ /**
+ * Set CSS properties.
+ * @param {HTMLElement} element an HTMLElement.
+ * @param {Object} props a properties object.
+ */
+ css(element: any, props: any): void;
+
+ /**
+ * Get elements by tag name.
+ * @param {HTMLElement} context an HTMLElement.
+ * @param {string} tagName A tag name.
+ * @param {function} [iterator] An iterator.
+ * @returns {HTMLElement[]}
+ */
+ find(context: any, tagName: string, iterator?: (value: any) => any): any[];
+
+ /**
+ * Takes a function and returns a new one that will always have a particular context.
+ * @param {*} context an HTMLElement.
+ * @param {function} fn a function.
+ * @returns {function}
+ */
+ bind(context: any, fn: () => any): () => any;
+
+ /**
+ * Check the current matched set of elements against a selector.
+ * @param {HTMLElement} element an HTMLElement.
+ * @param {string} selector an element selector.
+ * @returns {boolean}
+ */
+ is(element: any, selector: string): boolean;
+
+ /**
+ * For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree.
+ * @param {HTMLElement} element an HTMLElement.
+ * @param {string} selector an element seletor.
+ * @param {HTMLElement} [context] a specific element's context.
+ * @returns {HTMLElement}
+ */
+ closest(element: any, selector: string, context?: any): any;
+
+ /**
+ * Add or remove one classes from each element
+ * @param {HTMLElement} element an HTMLElement.
+ * @param {string} name a class name.
+ * @param {boolean} state a class's state.
+ */
+ toggleClass(element: any, name: string, state: boolean): void;
+ }
+
+ class DOMRect {
+ public bottom: number;
+ public height: number;
+ public left: number;
+ public right: number;
+ public top: number;
+ public width: number;
+ public x: number;
+ public y: number;
+ }
+
+ class Sortable {
+ public options: SortableOptions;
+ public el: any;
+
+ /**
+ * Sortable's main constructor.
+ * @param {HTMLElement} element Any variety of HTMLElement.
+ * @param {SortableOptions} options Sortable options object.
+ */
+ constructor(element: any, options: SortableOptions);
+
+ static active: Sortable;
+ static utils: SortableableUtils;
+
+ /**
+ * Creation of new instances.
+ * @param {HTMLElement} element Any variety of HTMLElement.
+ * @param {SortableOptions} options Sortable options object.
+ * @returns {Sortable}
+ */
+ static create(element: any, options: SortableOptions): Sortable;
+
+ /**
+ * Options getter/setter
+ * @param {string} name a SortableOptions property.
+ * @param {*} [value] a Value.
+ * @returns {*}
+ */
+ option(name: string, value: any): any;
+ option(name: string): any;
+
+ /**
+ * For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree.
+ * @param {string|HTMLElement} element an HTMLElement or selector string.
+ * @returns {HTMLElement}
+ */
+ closest(element: any): any;
+
+ /**
+ * Sorts the elements according to the array.
+ * @param {string[]} order an array of strings to sort.
+ */
+ sort(order: string[]): void;
+
+ /**
+ * Saving and restoring of the sort.
+ */
+ save(): void;
+
+ /**
+ * Removes the sortable functionality completely.
+ */
+ destroy(): void;
+
+ /**
+ * Serializes the sortable's item data-id's (dataIdAttr option) into an array of string.
+ * @returns {string[]}
+ */
+ toArray(): string[];
+ }
+}
+
+import Sortable = Sortablejs.Sortable;
+
+declare module 'Sortable' {
+ import Sortable = Sortablejs.Sortable;
+ export = Sortable;
+}