diff --git a/dist/ionic-angular.js b/dist/ionic-angular.js index e2b3c01dc5..4c931ec782 100644 --- a/dist/ionic-angular.js +++ b/dist/ionic-angular.js @@ -209,7 +209,6 @@ angular.module('ionic.ui.list', ['ionic.service', 'ngAnimate']) return { restrict: 'E', replace: true, - transclude: true, scope: { isEditing: '=', items: '=', @@ -229,8 +228,31 @@ angular.module('ionic.ui.list', ['ionic.service', 'ngAnimate']) if(attr.animation) { $element.addClass(attr.animation); } + } + } + } +}) - $element.append(transclude($scope)); +.directive('listSimple', function() { + return { + restrict: 'E', + replace: true, + transclude: true, + scope: { + isEditing: '=', + items: '=', + animation: '@', + deleteIcon: '@' + }, + template: '', + compile: function(element, attr, transclude) { + return function($scope, $element, $attr) { + var lv = new ionic.views.List({el: $element[0]}); + + if(attr.animation) { + $element.addClass(attr.animation); + } } } } diff --git a/dist/ionic-ios7.css b/dist/ionic-ios7.css index 3bf8dc2d30..1179755611 100644 --- a/dist/ionic-ios7.css +++ b/dist/ionic-ios7.css @@ -307,6 +307,13 @@ table { -moz-box-sizing: border-box; box-sizing: border-box; } +a { + -webkit-user-drag: none; + -webkit-tap-highlight-color: transparent; } + +a:focus, button:focus { + outline: 0; } + body { position: fixed; top: 0; @@ -1078,6 +1085,9 @@ a.list-item { .list-item-content > i:last-child { float: right; } +.list-item-sliding { + -webkit-transition: -webkit-transform 0.1s ease-in-out; } + .list-item-edit { position: absolute; z-index: 0; @@ -1096,8 +1106,10 @@ a.list-item { position: absolute; z-index: 0; right: 0; - top: 0; } + top: 0; + height: 100%; } .list-item-buttons .button { + height: 100%; border-radius: 0; border: none; } diff --git a/dist/ionic.css b/dist/ionic.css index e7cd72a2b5..17e218f997 100644 --- a/dist/ionic.css +++ b/dist/ionic.css @@ -1371,6 +1371,13 @@ table { -moz-box-sizing: border-box; box-sizing: border-box; } +a { + -webkit-user-drag: none; + -webkit-tap-highlight-color: transparent; } + +a:focus, button:focus { + outline: 0; } + body { position: fixed; top: 0; @@ -2165,6 +2172,9 @@ a.list-item { .list-item-content > i:last-child { float: right; } +.list-item-sliding { + -webkit-transition: -webkit-transform 0.1s ease-in-out; } + .list-item-edit { position: absolute; z-index: 0; @@ -2183,8 +2193,10 @@ a.list-item { position: absolute; z-index: 0; right: 0; - top: 0; } + top: 0; + height: 100%; } .list-item-buttons .button { + height: 100%; border-radius: 0; border: none; } diff --git a/dist/ionic.js b/dist/ionic.js index abf069fa17..9433bf4c11 100644 --- a/dist/ionic.js +++ b/dist/ionic.js @@ -1785,6 +1785,18 @@ window.ionic = { this.el = opts.el; + this.dragThresholdX = 10; + + this._initDrag(); + + window.ionic.onGesture('drag', function(e) { + _this._handleDrag(e); + }, this.el); + window.ionic.onGesture('release', function(e) { + _this._handleEndDrag(e); + }, this.el); + + /* window.ionic.onGesture('swipeleft', function(e) { _this._handleSwipeLeft(e); e.gesture.stopDetect(); @@ -1796,9 +1808,130 @@ window.ionic = { e.gesture.stopDetect(); return false; }, this.el); + */ }; ionic.views.List.prototype = { + _initDrag: function() { + this._offsetX = 0; + this._isDragging = false; + this._currentDrag = null; + }, + _startDrag: function(e) { + this._isDragging = false; + + // Grab the content item + if(e.target.classList.contains('list-item')) { + content = e.target.querySelector('.list-item-content'); + } else if(e.target.classList.contains('list-item-content')) { + content = e.target; + } + + if(!content) { + return; + } + + content.classList.remove('list-item-sliding'); + + // Grab the buttons + buttons = content.parentNode.querySelector('.list-item-buttons'); + if(!buttons) { + return; + } + + buttonsWidth = buttons.offsetWidth; + + this._currentDrag = { + buttonsWidth: buttonsWidth, + content: content + }; + }, + _handleEndDrag: function(e) { + var _this = this; + + // If we are currently dragging, we want to snap back into place + if(this._currentDrag) { + + // The final resting point X will be the width of the exposed buttons + var restingPoint = -this._currentDrag.buttonsWidth; + + // Check if the drag didn't clear the buttons and we aren't moving fast enough to swipe open + if(e.gesture.deltaX > -this._currentDrag.buttonsWidth) { + if(e.gesture.direction == "left" && Math.abs(e.gesture.velocityX) < 0.3) { + restingPoint = 0; + } else if(e.gesture.direction == "right") { + restingPoint = 0; + } + } + + var content = this._currentDrag.content; + + var onRestingAnimationEnd = function(e) { + if(e.propertyName == '-webkit-transform') { + content.classList.remove('list-item-sliding'); + } + e.target.removeEventListener('webkitTransitionEnd', onRestingAnimationEnd); + } + + window.requestAnimationFrame(function() { + var currentX = parseFloat(_this._currentDrag.content.style.webkitTransform.replace('translate3d(', '').split(',')[0]) || 0; + if(currentX !== restingPoint) { + _this._currentDrag.content.classList.add('list-item-sliding'); + _this._currentDrag.content.addEventListener('webkitTransitionEnd', onRestingAnimationEnd); + } + _this._currentDrag.content.style.webkitTransform = 'translate3d(' + restingPoint + 'px, 0, 0)'; + _this._initDrag(); + }); + + } else { + this._initDrag(); + } + }, + + /** + * Process the drag event to move the item to the left or right. + */ + _handleDrag: function(e) { + var _this = this, content, buttons; + + if(!this._currentDrag) { + this._startDrag(e); + } + + window.requestAnimationFrame(function() { + + if(!_this._currentDrag) { + return; + } + + // Calculate difference from the tap points + if(!_this._isDragging && Math.abs(e.gesture.deltaX) > _this.dragThresholdX) { + _this._isDragging = true; + + // Grab the starting X point for this item + _this._offsetX = parseFloat(_this._currentDrag.content.style.webkitTransform.replace('translate3d(', '').split(',')[0]) || 0; + } + + if(_this._isDragging) { + + // Grab the new X point, capping it at zero + + var newX = Math.min(0, _this._offsetX + e.gesture.deltaX); + if(newX < -buttonsWidth && !_this._deltaSlowX) { + _this._deltaSlowX = e.gesture.deltaX; + } + if(newX < -buttonsWidth) { + newX = Math.min(_this._deltaSlowX, _this._deltaSlowX + (((e.gesture.deltaX - _this._deltaSlowX) * 0.4))); + } + + console.log(newX); + + _this._currentDrag.content.style.webkitTransform = 'translate3d(' + newX + 'px, 0, 0)'; + } + }); + }, + + _handleSwipeLeft: function(e) { window.requestAnimationFrame(function() { @@ -1806,12 +1939,6 @@ window.ionic = { cl = item.classList, content, buttons, buttonsWidth; - // Grab the content item - if(cl.contains('list-item')) { - content = item.querySelector('.list-item-content'); - } else if(cl.contains('list-item-content')) { - content = item; - } if(!content) { return; diff --git a/example/toderp2/index.html b/example/toderp2/index.html index 708f05041d..1783be94a8 100644 --- a/example/toderp2/index.html +++ b/example/toderp2/index.html @@ -127,18 +127,24 @@ + +
- +
+
- +
  • +
    + +
    {{project.title}}
    @@ -146,7 +152,7 @@
  • -
    +
    diff --git a/example/toderp2/js/controllers.js b/example/toderp2/js/controllers.js index 3b6a1f5c8e..1453268be1 100644 --- a/example/toderp2/js/controllers.js +++ b/example/toderp2/js/controllers.js @@ -74,6 +74,8 @@ angular.module('ionic.todo.controllers', ['ionic.todo']) $scope.lastProject = null; */ + $scope.isEditingProjects = false; + // Load our settings modal Modal.fromTemplateUrl('settings.html', function(modal) { $scope.settingsModal = modal; @@ -183,6 +185,10 @@ angular.module('ionic.todo.controllers', ['ionic.todo']) }); }; + $scope.toggleProjectEditing = function() { + $scope.isEditingProjects = !$scope.isEditingProjects; + }; + var projectsRef = new Firebase(FIREBASE_URL + '/project_list'); $scope.projects = angularFireCollection(projectsRef.limit(100), function(snapshot) { if(!snapshot.val()) { diff --git a/js/ext/angular/src/directive/ionicList.js b/js/ext/angular/src/directive/ionicList.js index 3a05611be2..bbc780579d 100644 --- a/js/ext/angular/src/directive/ionicList.js +++ b/js/ext/angular/src/directive/ionicList.js @@ -27,7 +27,6 @@ angular.module('ionic.ui.list', ['ionic.service', 'ngAnimate']) return { restrict: 'E', replace: true, - transclude: true, scope: { isEditing: '=', items: '=', @@ -47,8 +46,31 @@ angular.module('ionic.ui.list', ['ionic.service', 'ngAnimate']) if(attr.animation) { $element.addClass(attr.animation); } - - $element.append(transclude($scope)); + } + } + } +}) + +.directive('listSimple', function() { + return { + restrict: 'E', + replace: true, + transclude: true, + scope: { + isEditing: '=', + items: '=', + animation: '@', + deleteIcon: '@' + }, + template: '', + compile: function(element, attr, transclude) { + return function($scope, $element, $attr) { + var lv = new ionic.views.List({el: $element[0]}); + + if(attr.animation) { + $element.addClass(attr.animation); + } } } } diff --git a/js/views/listView.js b/js/views/listView.js index fb63690afc..1e347d319a 100644 --- a/js/views/listView.js +++ b/js/views/listView.js @@ -5,6 +5,18 @@ this.el = opts.el; + this.dragThresholdX = 10; + + this._initDrag(); + + window.ionic.onGesture('drag', function(e) { + _this._handleDrag(e); + }, this.el); + window.ionic.onGesture('release', function(e) { + _this._handleEndDrag(e); + }, this.el); + + /* window.ionic.onGesture('swipeleft', function(e) { _this._handleSwipeLeft(e); e.gesture.stopDetect(); @@ -16,9 +28,130 @@ e.gesture.stopDetect(); return false; }, this.el); + */ }; ionic.views.List.prototype = { + _initDrag: function() { + this._offsetX = 0; + this._isDragging = false; + this._currentDrag = null; + }, + _startDrag: function(e) { + this._isDragging = false; + + // Grab the content item + if(e.target.classList.contains('list-item')) { + content = e.target.querySelector('.list-item-content'); + } else if(e.target.classList.contains('list-item-content')) { + content = e.target; + } + + if(!content) { + return; + } + + content.classList.remove('list-item-sliding'); + + // Grab the buttons + buttons = content.parentNode.querySelector('.list-item-buttons'); + if(!buttons) { + return; + } + + buttonsWidth = buttons.offsetWidth; + + this._currentDrag = { + buttonsWidth: buttonsWidth, + content: content + }; + }, + _handleEndDrag: function(e) { + var _this = this; + + // If we are currently dragging, we want to snap back into place + if(this._currentDrag) { + + // The final resting point X will be the width of the exposed buttons + var restingPoint = -this._currentDrag.buttonsWidth; + + // Check if the drag didn't clear the buttons and we aren't moving fast enough to swipe open + if(e.gesture.deltaX > -this._currentDrag.buttonsWidth) { + if(e.gesture.direction == "left" && Math.abs(e.gesture.velocityX) < 0.3) { + restingPoint = 0; + } else if(e.gesture.direction == "right") { + restingPoint = 0; + } + } + + var content = this._currentDrag.content; + + var onRestingAnimationEnd = function(e) { + if(e.propertyName == '-webkit-transform') { + content.classList.remove('list-item-sliding'); + } + e.target.removeEventListener('webkitTransitionEnd', onRestingAnimationEnd); + } + + window.requestAnimationFrame(function() { + var currentX = parseFloat(_this._currentDrag.content.style.webkitTransform.replace('translate3d(', '').split(',')[0]) || 0; + if(currentX !== restingPoint) { + _this._currentDrag.content.classList.add('list-item-sliding'); + _this._currentDrag.content.addEventListener('webkitTransitionEnd', onRestingAnimationEnd); + } + _this._currentDrag.content.style.webkitTransform = 'translate3d(' + restingPoint + 'px, 0, 0)'; + _this._initDrag(); + }); + + } else { + this._initDrag(); + } + }, + + /** + * Process the drag event to move the item to the left or right. + */ + _handleDrag: function(e) { + var _this = this, content, buttons; + + if(!this._currentDrag) { + this._startDrag(e); + } + + window.requestAnimationFrame(function() { + + if(!_this._currentDrag) { + return; + } + + // Calculate difference from the tap points + if(!_this._isDragging && Math.abs(e.gesture.deltaX) > _this.dragThresholdX) { + _this._isDragging = true; + + // Grab the starting X point for this item + _this._offsetX = parseFloat(_this._currentDrag.content.style.webkitTransform.replace('translate3d(', '').split(',')[0]) || 0; + } + + if(_this._isDragging) { + + // Grab the new X point, capping it at zero + + var newX = Math.min(0, _this._offsetX + e.gesture.deltaX); + if(newX < -buttonsWidth && !_this._deltaSlowX) { + _this._deltaSlowX = e.gesture.deltaX; + } + if(newX < -buttonsWidth) { + newX = Math.min(_this._deltaSlowX, _this._deltaSlowX + (((e.gesture.deltaX - _this._deltaSlowX) * 0.4))); + } + + console.log(newX); + + _this._currentDrag.content.style.webkitTransform = 'translate3d(' + newX + 'px, 0, 0)'; + } + }); + }, + + _handleSwipeLeft: function(e) { window.requestAnimationFrame(function() { @@ -26,12 +159,6 @@ cl = item.classList, content, buttons, buttonsWidth; - // Grab the content item - if(cl.contains('list-item')) { - content = item.querySelector('.list-item-content'); - } else if(cl.contains('list-item-content')) { - content = item; - } if(!content) { return; diff --git a/scss/ionic/_listview.scss b/scss/ionic/_listview.scss index 53cb1f645c..10559aabe8 100644 --- a/scss/ionic/_listview.scss +++ b/scss/ionic/_listview.scss @@ -98,6 +98,7 @@ a.list-item { } } + .list-item-content { position: relative; @@ -115,6 +116,10 @@ a.list-item { } } +.list-item-sliding { + -webkit-transition: -webkit-transform 0.1s ease-in-out; +} + .list-item-edit { position: absolute; z-index: 0; @@ -138,8 +143,10 @@ a.list-item { z-index: 0; right: 0; top: 0; + height: 100%; .button { + height: 100%; border-radius: 0; border: none; } diff --git a/scss/ionic/_scaffolding.scss b/scss/ionic/_scaffolding.scss index 4dd9d4b067..aba5445845 100644 --- a/scss/ionic/_scaffolding.scss +++ b/scss/ionic/_scaffolding.scss @@ -4,6 +4,17 @@ @include box-sizing(border-box); } +a { + -webkit-user-drag: none; + -webkit-tap-highlight-color: transparent; +} + +a, button { + &:focus { + outline: 0; + } +} + body { position: fixed; top: 0; @@ -20,8 +31,10 @@ body { text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: none; text-size-adjust: none; + -webkit-tap-highlight-color: transparent; -webkit-user-drag: none; -webkit-user-select: none;