diff --git a/CHANGELOG.md b/CHANGELOG.md index a7eff6a312..8cca203111 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,84 @@ + +### 0.9.26 "Rabbit" (2014-02-26) + + +#### Bug Fixes + +* **actionsheet:** Actionsheet in modal has pointer-events enabled, closes #660 ([1503cc72](http://github.com/driftyco/ionic/commit/1503cc7213c13e87804c210dea7465931e972f4a)) +* **click:** event.preventDefault() when setting focus() on an input, closes 583 ([fc8ab4b8](http://github.com/driftyco/ionic/commit/fc8ab4b8ea9b89bd3446b835476950bb70bba879)) +* **ionContent:** + * Update scss for ion-infinite-scroll element ([788df524](http://github.com/driftyco/ionic/commit/788df5243dc5300204171bcd40bf1e20fe610dbe)) + * Use new name to find ion-infinite-scroll element ([1702f5c9](http://github.com/driftyco/ionic/commit/1702f5c916dda20957761fd1e7b30d5f9bc1b6b4)) +* **ionPrefix:** disableRegisterByTagName updated w/ `ion-` prefix for correct view history ([2494b5f9](http://github.com/driftyco/ionic/commit/2494b5f988991b97c278102f0da385e9cde00aee)) +* **ionSideMenu:** use manual transclude instead of ngTransclude ([991d3cfd](http://github.com/driftyco/ionic/commit/991d3cfda1831467e38c176c69f0ecd084c86e50), closes [#666](http://github.com/driftyco/ionic/issues/666)) +* **ionTabs:** cleanup and fix many issues ([0f1b6f47](http://github.com/driftyco/ionic/commit/0f1b6f47b8ee4c72facc269da2b648bb9cc397e0), closes [#597](http://github.com/driftyco/ionic/issues/597)) +* **ionicScrollDelegate:** + * do not error if no scrollTop/Left values ([9e942f89](http://github.com/driftyco/ionic/commit/9e942f894b037284a2e36ff14d3615f076b84447), closes [#659](http://github.com/driftyco/ionic/issues/659) +* **item:** degrade .item right arrows by grade for low end devices ([3a69bb34](http://github.com/driftyco/ionic/commit/3a69bb3452944bc4d1764c7605aad0824cbb758c)) +* **loading:** make showDelay option work correctly ([7281e2ab](http://github.com/driftyco/ionic/commit/7281e2abf0f6f624f296191b3f322227089e3658), closes [#562](http://github.com/driftyco/ionic/issues/562)) +* **modal:** + * Fix modal animation for firefox, closes #671 ([0033c880](http://github.com/driftyco/ionic/commit/0033c8809a810c8b518ca68a9ada5704a7f6e9c9)) +* **navBar:** + * animations, hide back button, no flicker ([465ea769](http://github.com/driftyco/ionic/commit/465ea76969b348b3074747dcd4cebf0861a38e6d), closes [#653](http://github.com/driftyco/ionic/issues/653)) +* **platform:** Update ionic.Platform.is() to check all platforms, closes #604 ([fcd0fa73](http://github.com/driftyco/ionic/commit/fcd0fa73c4125593e6efce6b6410586f5a27fc02)) +* **sideMenu:** remove translate3d when not needed, close #636 ([07092f00](http://github.com/driftyco/ionic/commit/07092f009cfa2b52b2dc2fdb915b237bd8c37dfa)) +* **slideBox:** make `does-continue` attribute work continuously ([f6ec6a3c](http://github.com/driftyco/ionic/commit/f6ec6a3c89116174541c2fb08194a04ada92065b), closes [#575](http://github.com/driftyco/ionic/issues/575)) +* **tabs:** + * Double tapping a tab would set the wrong view history, closes #656 ([f0faae16](http://github.com/driftyco/ionic/commit/f0faae16574b1247b6f5b1fa0180cfb9248804a1)) +* **text-rendering:** Disable text-rendering:optimizeLegibility for low end devices ([10289466](http://github.com/driftyco/ionic/commit/10289466162f4efa36dc902052b78e42d0df0b2d)) +* **thumbnail:** Correctly apply thumbnail style to item directive, closes #509 ([977c1cc6](http://github.com/driftyco/ionic/commit/977c1cc6e511d752e083081adbed9f8fda1d0312)) +* **transform:** Polyfill `style.transform` to work w/ non-webkit ([52671c18](http://github.com/driftyco/ionic/commit/52671c18f97d057aca581a566cc7ea27cc055314)) + + +#### Features + +* **grade:** Set grade in body class depending on platform performance ([b69b40c8](http://github.com/driftyco/ionic/commit/b69b40c82684acf3003f19277a44caf4614c75e4)) +* **ionContent:** use child scope instead of isolate scope ([49e0dac9](http://github.com/driftyco/ionic/commit/49e0dac9992d60ca098eb1dbd1acecbc74eae85c), closes [#555](http://github.com/driftyco/ionic/issues/555)) +* **ionInfiniteScroll:** use event system ([7b0716c2](http://github.com/driftyco/ionic/commit/7b0716c23c44fe5530526c86d590111382f991d7), closes [#661](http://github.com/driftyco/ionic/issues/661)) +* **ionNavAnimation:** `` ([8354d42b](http://github.com/driftyco/ionic/commit/8354d42b4b049e97bd93ce8ebacf047284a8fad1)) +* **item:** Auto right-arrow for complex list items w/ ng-click/href, closes #472 ([327a6866](http://github.com/driftyco/ionic/commit/327a686626c81072df8e055c8be220144fade26a)) + ([110ff9f4](http://github.com/driftyco/ionic/commit/110ff9f47583c7f04bcf5b1eebea6d7bd0b25e99), closes [#243](http://github.com/driftyco/ionic/issues/243)) +* **sass:** All variables now have !default assigned, closes #631 ([53af2c7a](http://github.com/driftyco/ionic/commit/53af2c7a0177f9bcd2b43d9179832a319ecb8f7f)) +* **scrollbar:** Do not use rgba background for scrollbar on grade-b and c devices ([805c35c1](http://github.com/driftyco/ionic/commit/805c35c1f119530777498177016bb0c448e92b90)) +* **sideMenu:** + * Degrade .menu-content box-shadow w/ platform grade for animation performance ([d2a0780b](http://github.com/driftyco/ionic/commit/d2a0780bbac5b17525def8b75c5c58c168675631)) + + +#### Breaking Changes + +* The binding for ionInfiniteScroll has changed, as well +as how you finish it. + +If you had this code before: + +```html + +``` +```js +function MyCtrl($scope) { + $scope.doSomething = function(scrollDoneCallback) { + doSomething(); + scrollDoneCallback(); + }; +} +``` + +Now, your code should look like this: + +```html + +``` +```js +function MyCtrl($scope) { + $scope.doSomething = function() { + doSomething(); + $scope.$broadcast('scroll.infiniteScrollComplete'); + }; +} +``` + + + ### 0.9.25 "Quokka" (2014-02-19) diff --git a/bower.json b/bower.json index c1c7360d40..86cadd6ac0 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "ionic", - "version": "0.9.25", + "version": "0.9.26", "homepage": "https://github.com/driftyco/ionic", "authors": [ "Max Lynch ", diff --git a/component.json b/component.json index 6b28a4041b..da62fdc111 100644 --- a/component.json +++ b/component.json @@ -1,7 +1,7 @@ { "repo": "driftyco/ionic", "development": {}, - "version": "0.9.25", + "version": "0.9.26", "styles": [ "dist/css/ionic.css" ], diff --git a/package.json b/package.json index f7fdff3d74..c680731676 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "ionic", "private": false, - "version": "0.9.26-alpha", - "codename": "rabbit", + "version": "0.10.0-alpha", + "codename": "salamander", "repository": { "url": "git://github.com/driftyco/ionic.git" }, @@ -17,7 +17,7 @@ "grunt-contrib-watch": "~0.5.3", "grunt-ddescribe-iit": "0.0.4", "grunt-merge-conflict": "0.0.2", - "grunt-sass": "~0.10.0", + "grunt-sass": "~0.10.0-alpha", "grunt-string-replace": "~0.2.7", "karma": "~0.11.13", "karma-chrome-launcher": "~0.1.2", @@ -29,9 +29,7 @@ "grunt-remove-logging": "~0.2.0", "grunt-conventional-changelog": "~1.1.0", "grunt-contrib-connect": "^0.6.0", - "sauce-connect-launcher": "^0.2.2", - "dgeni": "^0.1.1", - "dgeni-packages": "^0.2.4" + "sauce-connect-launcher": "^0.2.2" }, "licenses": [ { diff --git a/release/css/ionic.css b/release/css/ionic.css index 29f1c25fba..3fd8bbb462 100644 --- a/release/css/ionic.css +++ b/release/css/ionic.css @@ -2,7 +2,7 @@ * Copyright 2014 Drifty Co. * http://drifty.com/ * - * Ionic, v0.9.25 + * Ionic, v0.9.26 * A powerful HTML5 mobile app framework. * http://ionicframework.com/ * @@ -14,7 +14,7 @@ /*! Ionicons, v#{$ionicons-version} Created by Ben Sperry for the Ionic Framework, http://ionicons.com/ - https://twitter.com/helloimben https://twitter.com/ionicframework + https://twitter.com/benjsperry https://twitter.com/ionicframework MIT License: https://github.com/driftyco/ionicons */ @font-face { @@ -1953,7 +1953,7 @@ table { a { -webkit-user-drag: none; -webkit-tap-highlight-color: transparent; } - a:hover { + a[href]:hover { cursor: pointer; } img { @@ -1963,7 +1963,6 @@ body, .ionic-body { -webkit-touch-callout: none; -webkit-font-smoothing: antialiased; font-smoothing: antialiased; - -webkit-backface-visibility: hidden; -webkit-text-size-adjust: none; -moz-text-size-adjust: none; text-size-adjust: none; @@ -1987,29 +1986,31 @@ body, .ionic-body { font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; line-height: 20px; text-rendering: optimizeLegibility; + -webkit-backface-visibility: hidden; -webkit-user-drag: none; } +body.grade-b, body.grade-c { + text-rendering: auto; } + .content { position: relative; } .scroll-content { + position: absolute; top: 0; right: 0; bottom: 0; left: 0; - position: absolute; overflow: hidden; - -webkit-backface-visibility: hidden; margin-top: -1px; width: auto; height: auto; } .scroll-view { - height: 100%; - overflow: hidden; position: relative; - -webkit-backface-visibility: hidden; - margin-top: -1px; } + overflow: hidden; + margin-top: -1px; + height: 100%; } /** * Scroll is the scroll view component available for complex and custom @@ -2040,25 +2041,25 @@ body, .ionic-body { visibility: hidden; } .scroll-bar-h { - height: 3px; - left: 2px; right: 2px; - bottom: 3px; } + bottom: 3px; + left: 2px; + height: 3px; } .scroll-bar-h .scroll-bar-indicator { height: 100%; } .scroll-bar-v { - width: 3px; - bottom: 2px; top: 2px; - right: 3px; } + right: 3px; + bottom: 2px; + width: 3px; } .scroll-bar-v .scroll-bar-indicator { width: 100%; } .scroll-bar-indicator { position: absolute; - background: rgba(0, 0, 0, 0.3); border-radius: 4px; + background: rgba(0, 0, 0, 0.3); opacity: 1; } .scroll-bar-indicator.scroll-bar-fade-out { -webkit-transition: opacity 0.3s linear; @@ -2066,37 +2067,45 @@ body, .ionic-body { transition: opacity 0.3s linear; opacity: 0; } +.grade-b .scroll-bar-indicator, .grade-c .scroll-bar-indicator { + border-radius: 0; + background: #aaa; } + .grade-b .scroll-bar-indicator.scroll-bar-fade-out, .grade-c .scroll-bar-indicator.scroll-bar-fade-out { + -webkit-transition: none; + -moz-transition: none; + transition: none; } + .scroll-refresher { - overflow: hidden; - top: -60px; - height: 60px; - left: 0; - right: 0; position: absolute; - margin: auto; } + top: -60px; + right: 0; + left: 0; + overflow: hidden; + margin: auto; + height: 60px; } .scroll-refresher .icon-refreshing { - display: none; -webkit-animation-duration: 1.5s; -moz-animation-duration: 1.5s; - animation-duration: 1.5s; } + animation-duration: 1.5s; + display: none; } .scroll-refresher-content { position: absolute; - left: 0; bottom: 15px; + left: 0; width: 100%; + color: #666666; text-align: center; - font-size: 30px; - color: #666666; } + font-size: 30px; } .ionic-refresher-content { position: absolute; - left: 0; bottom: 15px; + left: 0; width: 100%; + color: #666666; text-align: center; - font-size: 30px; - color: #666666; } + font-size: 30px; } .ionic-refresher-content .icon-pulling { -webkit-animation-duration: 200ms; -moz-animation-duration: 200ms; @@ -2135,22 +2144,22 @@ body, .ionic-body { -moz-animation-name: refresh-spin; animation-name: refresh-spin; } -infinite-scroll .scroll-infinite { +ion-infinite-scroll .scroll-infinite { + position: relative; overflow: hidden; margin-top: -70px; - height: 60px; - position: relative; } + height: 60px; } .scroll-infinite-content { position: absolute; - left: 0; bottom: 15px; + left: 0; width: 100%; + color: #666666; text-align: center; - font-size: 30px; - color: #666666; } + font-size: 30px; } -infinite-scroll.active .scroll-infinite { +ion-infinite-scroll.active .scroll-infinite { margin-top: -30px; } .overflow-scroll { @@ -2182,17 +2191,16 @@ infinite-scroll.active .scroll-infinite { bottom: 49px; } .pane { - position: absolute; - z-index: 1; -webkit-transform: translate3d(0, 0, 0); -moz-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); } + transform: translate3d(0, 0, 0); + z-index: 1; } .view { - position: absolute; z-index: 1; } .pane, .view { + position: absolute; top: 0; right: 0; bottom: 0; @@ -2350,7 +2358,7 @@ a.subdued { position: fixed; top: 0; left: 0; - z-index: 10; + z-index: 11; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0); } @@ -2438,7 +2446,7 @@ a.subdued { margin-left: 15px; margin-right: 15px; width: auto; - z-index: 10; + z-index: 11; overflow: hidden; } .action-sheet .button { display: block; @@ -2475,6 +2483,13 @@ a.subdued { .action-sheet-group .button:first-child:last-child { border-width: 0; } +.action-sheet-open { + pointer-events: none; } + .action-sheet-open.modal-open .modal { + pointer-events: none; } + .action-sheet-open .action-sheet { + pointer-events: auto; } + /** * Bar (Headers and Footers) * -------------------------------------------------- @@ -2637,6 +2652,8 @@ a.subdued { .bar .button.button-clear .icon:before, .bar .button.button-clear.icon:before, .bar .button.button-clear.icon-left:before, .bar .button.button-clear.icon-right:before { font-size: 32px; line-height: 32px; } + .bar .button.back-button:active, .bar .button.back-button.active { + opacity: 1; } .bar .button-bar > .button, .bar .buttons > .button { min-height: 31px; line-height: 32px; } @@ -3008,6 +3025,8 @@ a.subdued { font-size: 14px; font-family: "Helvetica Neue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; opacity: 0.7; } + .tab-item:hover { + cursor: pointer; } .tabs-icon-top .tab-item, .tabs-icon-bottom .tab-item { font-size: 12px; @@ -3100,8 +3119,21 @@ a.subdued { background-color: #fff; } .menu-content { + -webkit-transform: none; + -moz-transform: none; + transform: none; box-shadow: -1px 0px 2px rgba(0, 0, 0, 0.2), 1px 0px 2px rgba(0, 0, 0, 0.2); } +.grade-b .menu-content, .grade-c .menu-content { + right: -1px; + left: -1px; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + border-right: 1px solid #ccc; + border-left: 1px solid #ccc; + box-shadow: none; } + .menu-left { left: 0; } @@ -3133,6 +3165,11 @@ a.subdued { .modal.active { height: 100%; } +.modal-open { + pointer-events: none; } + .modal-open .modal { + pointer-events: auto; } + /** * Popups * -------------------------------------------------- @@ -3469,10 +3506,10 @@ a.item-content { * By default, if an .item is created out of an or \ \ -

\ +

\
\ - \
\ ', @@ -1407,16 +1488,6 @@ angular.module('ionic.ui.header', ['ngAnimate']) })(ionic); ; -angular.module('ionic.ui.bindHtml', []) -.directive('ionBindHtmlUnsafe', function () { - return function (scope, element, attr) { - element.addClass('ng-binding').data('$binding', attr.ionBindHtmlUnsafe); - scope.$watch(attr.ionBindHtmlUnsafe, function(value) { - element.html(value || ''); - }); - }; -}); -; (function() { 'use strict'; @@ -1474,35 +1545,50 @@ angular.module('ionic.ui.content', ['ionic.ui.service', 'ionic.ui.scroll']) }; }) -// The content directive is a core scrollable content area -// that is part of many View hierarchies -.directive('ionContent', ['$parse', '$timeout', '$ionicScrollDelegate', '$controller', function($parse, $timeout, $ionicScrollDelegate, $controller) { +/* + * @ngdoc directive + * @name ionContent + * + * @description + * The ionContent directive provides an easy to use content area that can be configured to use + * Ionic's custom Scroll View, or the built in overflow scorlling of the browser. + * + * While we recommend using the custom Scroll features in Ionic in most cases, sometimes (for performance reasons) only the browser's native overflow scrolling will suffice, and so we've made it easy to toggle between the Ionic scroll implementation and overflow scrolling. + * + * When using the Ionic scroll features, you'll get pull-to-refresh, customizable scroll mechanics (like bounce easing, momentum acceleration, etc.) which aligns Ionic with native SDKs that give you access to scroll behavior. You'll also get events while in a momentum scroll, which -webkit-overflow-scrolling: touch will not, making it of limited use in real applications. + * + * Also, we are working on virtual list rendering which will only work when using Ionic's scroll view. That is on the upcoming roadmap. + * + * @restrict E + * @param {boolean=} scroll Whether to allow scrolling of content. Defaults to true. + * @param {boolean=} overflow-scroll Whether to use overflow-scrolling instead of Ionic scroll. + * @param {boolean=} padding Whether to add padding to the content. + * @param {boolean=} has-header Whether to offset the content for a header bar. + * @param {boolean=} has-subheader Whether to offset the content for a subheader bar. + * @param {boolean=} has-footer Whether to offset the content for a footer bar. + * @param {boolean=} has-bouncing Whether to allow scrolling to bounce past the edges of the content. Defaults to true on iOS, false on Android. + * @param {expression=} on-refresh Expression to evaluate on refresh completion. + * @param {expression=} on-refresh-opening Expression to evaluate on refresh opening. + * @param {expression=} on-scroll Expression to evaluate when the content is scrolled. + * @param {expression=} on-scroll-complete Expression to evaluate when a scroll action completes. + */ +.directive('ionContent', [ + '$parse', + '$timeout', + '$ionicScrollDelegate', + '$controller', + '$ionicBind', +function($parse, $timeout, $ionicScrollDelegate, $controller, $ionicBind) { return { restrict: 'E', replace: true, - template: '
', transclude: true, require: '^?ionNavView', - scope: { - onRefresh: '&', - onRefreshOpening: '&', - onScroll: '&', - onScrollComplete: '&', - refreshComplete: '=', - onInfiniteScroll: '=', - infiniteScrollDistance: '@', - hasBouncing: '@', - scroll: '@', - padding: '@', - hasScrollX: '@', - hasScrollY: '@', - scrollbarX: '@', - scrollbarY: '@', - startX: '@', - startY: '@', - scrollEventInterval: '@' - }, - + scope: true, + template: + '
' + + '
' + + '
', compile: function(element, attr, transclude) { if(attr.hasHeader == "true") { element.addClass('has-header'); } if(attr.hasSubheader == "true") { element.addClass('has-subheader'); } @@ -1518,7 +1604,32 @@ angular.module('ionic.ui.content', ['ionic.ui.service', 'ionic.ui.scroll']) function prelink($scope, $element, $attr, navViewCtrl) { var clone, sc, scrollView, scrollCtrl, - c = angular.element($element.children()[0]); + scrollContent = angular.element($element[0].querySelector('.scroll')); + + transclude($scope, function(clone) { + scrollContent.append(clone); + }); + + $ionicBind($scope, $attr, { + //Use $ to stop onRefresh from recursively calling itself + $onRefresh: '&onRefresh', + $onRefreshOpening: '&onRefreshOpening', + $onScroll: '&onScroll', + $onScrollComplete: '&onScrollComplete', + $onInfiniteScroll: '&onInfiniteScroll', + refreshComplete: '=', + infiniteScrollDistance: '@', + hasBouncing: '@', + scroll: '@', + padding: '@', + hasScrollX: '@', + hasScrollY: '@', + scrollbarX: '@', + scrollbarY: '@', + startX: '@', + startY: '@', + scrollEventInterval: '@' + }); if($scope.scroll === "false") { // No scrolling @@ -1543,13 +1654,14 @@ angular.module('ionic.ui.content', ['ionic.ui.service', 'ionic.ui.scroll']) scrollingY: $scope.$eval($scope.hasScrollY) !== false, scrollEventInterval: parseInt($scope.scrollEventInterval, 10) || 20, scrollingComplete: function() { - $scope.onScrollComplete({ + $scope.$onScrollComplete({ scrollTop: this.__scrollTop, scrollLeft: this.__scrollLeft }); } } }); + //Publish scrollView to parent so children can access it scrollView = $scope.$parent.scrollView = scrollCtrl.scrollView; @@ -1577,40 +1689,6 @@ angular.module('ionic.ui.content', ['ionic.ui.service', 'ionic.ui.scroll']) } }; } - - // Check if this supports infinite scrolling and listen for scroll events - // to trigger the infinite scrolling - // TODO(ajoslin): move functionality out of this function and make testable - var infiniteScroll = $element.find('infinite-scroll'); - var infiniteStarted = false; - if(infiniteScroll) { - // Parse infinite scroll distance - var distance = attr.infiniteScrollDistance || '1%'; - var maxScroll; - if(distance.indexOf('%')) { - // It's a multiplier - maxScroll = function() { - return scrollView.getScrollMax().top * ( 1 - parseInt(distance, 10) / 100 ); - }; - } else { - // It's a pixel value - maxScroll = function() { - return scrollView.getScrollMax().top - parseInt(distance, 10); - }; - } - $element.bind('scroll', function(e) { - if( scrollView && !infiniteStarted && (scrollView.getValues().top > maxScroll() ) ) { - infiniteStarted = true; - infiniteScroll.addClass('active'); - var cb = function() { - scrollView.resize(); - infiniteStarted = false; - infiniteScroll.removeClass('active'); - }; - $scope.$apply(angular.bind($scope, $scope.onInfiniteScroll, cb)); - } - }); - } } } }; @@ -1635,13 +1713,51 @@ angular.module('ionic.ui.content', ['ionic.ui.service', 'ionic.ui.scroll']) }; }) -.directive('ionInfiniteScroll', function() { +.directive('ionInfiniteScroll', ['$ionicBind', function($ionicBind) { return { restrict: 'E', - replace: false, - template: '
' + require: '^?$ionicScroll', + template: + '
' + + '
' + + '' + + '
' + + '
', + link: function($scope, $element, $attrs, scrollCtrl) { + setTimeout(function() { + var scrollCtrl = $element.controller('$ionicScroll'); + var scrollView = scrollCtrl.scrollView; + + $ionicBind($scope, $attrs, { + distance: '@infiniteScrollDistance' + }); + function maxScroll() { + var dist = $scope.distance || '1%'; + return dist.indexOf('%') > -1 ? + scrollView.getScrollMax().top * (1 - parseInt(dist,10) / 100) : + scrollView.getScrollMax().top - parseInt(dist, 10); + } + + var infiniteScrolling = false; + $scope.$on('scroll.infiniteScrollComplete', function() { + $element[0].classList.remove('active'); + setTimeout(function() { + scrollView.resize(); + }); + infiniteScrolling = false; + }); + + scrollCtrl.$element.on('scroll', ionic.animationFrameThrottle(function() { + if (!infiniteScrolling && scrollView.getValues().top >= maxScroll()) { + $element[0].classList.add('active'); + infiniteScrolling = true; + $scope.$apply(angular.bind($scope, $scope.$onInfiniteScroll)); + } + })); + }); + } }; -}); +}]); })(); ; @@ -1918,7 +2034,7 @@ angular.module('ionic.ui.radio', []) if(!ngModel || !radioButtons) { return; } var setIt = function() { - console.log('SET'); + $element.addClass('active'); ngModel.$setViewValue($scope.$eval($attr.ngValue)); @@ -1926,7 +2042,7 @@ angular.module('ionic.ui.radio', []) }; var clickHandler = function(e) { - console.log('CLICK'); + setIt(); }; @@ -2037,7 +2153,7 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture', 'ionic.service.vie .run(['$ionicViewService', function($ionicViewService) { // set that the side-menus directive should not animate when transitioning to it - $ionicViewService.disableRegisterByTagName('side-menus'); + $ionicViewService.disableRegisterByTagName('ion-side-menus'); }]) .directive('ionSideMenus', function() { @@ -2059,7 +2175,7 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture', 'ionic.service.vie }], replace: true, transclude: true, - template: '
' + template: '
' }; }) @@ -2131,7 +2247,11 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture', 'ionic.service.vie return $scope.sideMenuContentTranslateX || 0; }, setTranslateX: ionic.animationFrameThrottle(function(amount) { - $element[0].style.webkitTransform = 'translate3d(' + amount + 'px, 0, 0)'; + if(amount === 0) { + $element[0].style[ionic.CSS.TRANSFORM] = 'none'; + } else { + $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(' + amount + 'px, 0, 0)'; + } $timeout(function() { $scope.sideMenuContentTranslateX = amount; }); @@ -2169,7 +2289,7 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture', 'ionic.service.vie replace: true, transclude: true, scope: true, - template: '', + template: '', compile: function(element, attr, transclude) { angular.isUndefined(attr.isEnabled) && attr.$set('isEnabled', 'true'); angular.isUndefined(attr.width) && attr.$set('width', '275'); @@ -2192,6 +2312,10 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture', 'ionic.service.vie $scope.$watch($attr.isEnabled, function(val) { sideMenu.setIsEnabled(!!val); }); + + transclude($scope, function(clone) { + $element.append(clone); + }); }; } }; @@ -2345,7 +2469,7 @@ angular.module('ionic.ui.slideBox', []) })(); ; -angular.module('ionic.ui.tabs', ['ionic.service.view', 'ionic.ui.bindHtml']) +angular.module('ionic.ui.tabs', ['ionic.service.view']) /** * @description @@ -2356,306 +2480,274 @@ angular.module('ionic.ui.tabs', ['ionic.service.view', 'ionic.ui.bindHtml']) .run(['$ionicViewService', function($ionicViewService) { // set that the tabs directive should not animate when transitioning - // to it. Instead, the children directives would animate - $ionicViewService.disableRegisterByTagName('tabs'); + // to it. Instead, the children directives would animate + $ionicViewService.disableRegisterByTagName('ion-tabs'); }]) -.controller('$ionicTabs', ['$scope', '$ionicViewService', function($scope, $ionicViewService) { - var _this = this; +.controller('$ionicTabs', ['$scope', '$ionicViewService', '$element', function($scope, $ionicViewService, $element) { + var self = $scope.tabsController = this; + self.tabs = []; - $scope.tabCount = 0; - $scope.selectedIndex = -1; - $scope.$enableViewRegister = false; + self.selectedTab = null; - angular.extend(this, ionic.controllers.TabBarController.prototype); - - ionic.controllers.TabBarController.call(this, { - controllerChanged: function(oldC, oldI, newC, newI) { - $scope.controllerChanged && $scope.controllerChanged({ - oldController: oldC, - oldIndex: oldI, - newController: newC, - newIndex: newI - }); - }, - tabBar: { - tryTabSelect: function() {}, - setSelectedItem: function(index) {}, - addItem: function(item) {} + self.add = function(tab) { + $ionicViewService.registerHistory(tab); + self.tabs.push(tab); + if(self.tabs.length === 1) { + self.select(tab); } - }); - - this.add = function(tabScope) { - tabScope.tabIndex = $scope.tabCount; - this.addController(tabScope); - if(tabScope.tabIndex === 0) { - this.select(0); - } - $scope.tabCount++; }; - function controllerByTabIndex(tabIndex) { - for (var x=0; x<_this.controllers.length; x++) { - if (_this.controllers[x].tabIndex === tabIndex) { - return _this.controllers[x]; + self.remove = function(tab) { + var tabIndex = self.tabs.indexOf(tab); + if (tabIndex === -1) { + return; + } + //Use a field like '$tabSelected' so developers won't accidentally set it in controllers etc + if (tab.$tabSelected) { + self.deselect(tab); + //Try to select a new tab if we're removing a tab + if (self.tabs.length === 1) { + //do nothing if there are no other tabs to select + } else { + //Select previous tab if it's the last tab, else select next tab + var newTabIndex = tabIndex === self.tabs.length - 1 ? tabIndex - 1 : tabIndex + 1; + self.select(self.tabs[newTabIndex]); } } - } + self.tabs.splice(tabIndex, 1); + }; - this.select = function(tabIndex, emitChange) { - if(tabIndex !== $scope.selectedIndex) { + self.getTabIndex = function(tab) { + return self.tabs.indexOf(tab); + }; - $scope.selectedIndex = tabIndex; - $scope.activeAnimation = $scope.animation; - _this.selectController(tabIndex); + self.deselect = function(tab) { + if (tab.$tabSelected) { + self.selectedTab = null; + tab.$tabSelected = false; + (tab.onDeselect || angular.noop)(); + } + }; - var viewData = { - type: 'tab', - typeIndex: tabIndex - }; + self.select = function(tab, shouldEmitEvent) { + var tabIndex; + if (angular.isNumber(tab)) { + tabIndex = tab; + tab = self.tabs[tabIndex]; + } else { + tabIndex = self.tabs.indexOf(tab); + } + if (!tab || tabIndex == -1) { + throw new Error('Cannot select tab "' + tabIndex + '"!'); + } - var tabController = controllerByTabIndex(tabIndex); - if (tabController) { - viewData.title = tabController.title; - viewData.historyId = tabController.$historyId; - viewData.url = tabController.url; - viewData.uiSref = tabController.viewSref; - viewData.navViewName = tabController.navViewName; - viewData.hasNavView = tabController.hasNavView; + if (self.selectedTab && self.selectedTab.$historyId == tab.$historyId) { + if (shouldEmitEvent) { + $ionicViewService.goToHistoryRoot(tab.$historyId); } + } else { + angular.forEach(self.tabs, function(tab) { + self.deselect(tab); + }); - if(emitChange) { + self.selectedTab = tab; + //Use a funny name like $tabSelected so the developer doesn't overwrite the var in a child scope + tab.$tabSelected = true; + (tab.onSelect || angular.noop)(); + + if (shouldEmitEvent) { + var viewData = { + type: 'tab', + tabIndex: tabIndex, + historyId: tab.$historyId, + navViewName: tab.navViewName, + hasNavView: !!tab.navViewName, + title: tab.title, + //Skip the first character of href if it's # + url: tab.href, + uiSref: tab.uiSref + }; $scope.$emit('viewState.changeHistory', viewData); } - } else if(emitChange) { - var currentView = $ionicViewService.getCurrentView(); - if (currentView) { - $ionicViewService.goToHistoryRoot(currentView.historyId); - } } }; - - $scope.controllers = this.controllers; - - $scope.tabsController = this; - }]) -.directive('ionTabs', ['$ionicViewService', function($ionicViewService) { +.directive('ionTabs', ['$ionicViewService', '$ionicBind', function($ionicViewService, $ionicBind) { return { restrict: 'E', replace: true, scope: true, transclude: true, controller: '$ionicTabs', - template: '
', - compile: function(element, attr, transclude, tabsCtrl) { - return function link($scope, $element, $attr) { + template: + '
' + + '
' + + '
' + + '
', + compile: function(element, attr, transclude) { + if(angular.isUndefined(attr.tabsType)) attr.$set('tabsType', 'tabs-positive'); - var tabs = $element[0].querySelector('.tabs'); + return function link($scope, $element, $attr, tabsCtrl) { - $scope.tabsType = $attr.tabsType || 'tabs-positive'; - $scope.tabsStyle = $attr.tabsStyle; - $scope.animation = $attr.animation; - - $scope.animateNav = $scope.$eval($attr.animateNav); - if($scope.animateNav !== false) { - $scope.animateNav = true; - } - - $attr.$observe('tabsStyle', function(val) { - if(tabs) { - angular.element(tabs).addClass($attr.tabsStyle); - } + $ionicBind($scope, $attr, { + $animation: '@animation', + $tabsStyle: '@tabsStyle', + $tabsType: '@tabsType' }); - $attr.$observe('tabsType', function(val) { - if(tabs) { - angular.element(tabs).addClass($attr.tabsType); - } - }); + tabsCtrl.$scope = $scope; + tabsCtrl.$element = $element; + tabsCtrl.$tabsElement = angular.element($element[0].querySelector('.tabs')); - $scope.$watch('activeAnimation', function(value) { - $element.addClass($scope.activeAnimation); + transclude($scope, function(clone) { + $element.append(clone); }); - transclude($scope, function(cloned) { - $element.prepend(cloned); - }); - }; } }; }]) +.controller('$ionicTab', ['$scope', '$ionicViewService', '$rootScope', '$element', +function($scope, $ionicViewService, $rootScope, $element) { + this.$scope = $scope; +}]) + // Generic controller directive -.directive('ionTab', ['$ionicViewService', '$rootScope', '$parse', '$interpolate', function($ionicViewService, $rootScope, $parse, $interpolate) { +.directive('ionTab', ['$rootScope', '$animate', '$ionicBind', '$compile', '$ionicViewService', function($rootScope, $animate, $ionicBind, $compile, $ionicViewService) { + + //Returns ' key="value"' if value exists + function attrStr(k,v) { + return angular.isDefined(v) ? ' ' + k + '="' + v + '"' : ''; + } return { restrict: 'E', - require: '^ionTabs', - scope: true, + require: ['^ionTabs', 'ionTab'], + replace: true, transclude: 'element', + controller: '$ionicTab', + scope: true, compile: function(element, attr, transclude) { + return function link($scope, $element, $attr, ctrls) { + var childScope, childElement, tabNavElement; + tabsCtrl = ctrls[0], + tabCtrl = ctrls[1]; - return function link($scope, $element, $attr, tabsCtrl) { - var childScope, childElement; - - $ionicViewService.registerHistory($scope); - - $scope.title = $attr.title; - $scope.icon = $attr.icon; - $scope.iconOn = $attr.iconOn; - $scope.iconOff = $attr.iconOff; - $scope.viewSref = $attr.uiSref; - $scope.url = $attr.href; - if($scope.url && $scope.url.indexOf('#') === 0) { - $scope.url = $scope.url.replace('#', ''); - } - - // Should we hide a back button when this tab is shown - $scope.hideBackButton = $scope.$eval($attr.hideBackButton); - - if($scope.hideBackButton !== true) { - $scope.hideBackButton = false; - } - - // Whether we should animate on tab change, also impacts whether we - // tell any parent nav controller to animate - $scope.animate = $scope.$eval($attr.animate); - - var badgeGet = $parse($attr.badge); - $scope.$watch(badgeGet, function(value) { - $scope.badge = value; + $ionicBind($scope, $attr, { + animate: '=', + leftButtons: '=', + rightButtons: '=', + onSelect: '&', + onDeselect: '&', + title: '@', + uiSref: '@', + href: '@', }); - $attr.$observe('badgeStyle', function(value) { - $scope.badgeStyle = value; - }); - - var leftButtonsGet = $parse($attr.leftButtons); - $scope.$watch(leftButtonsGet, function(value) { - $scope.leftButtons = value; - if($scope.doesUpdateNavRouter) { - $scope.$emit('viewState.leftButtonsChanged', $scope.rightButtons); - } - }); - - var rightButtonsGet = $parse($attr.rightButtons); - $scope.$watch(rightButtonsGet, function(value) { - $scope.rightButtons = value; - }); + tabNavElement = angular.element( + '' + ); + tabNavElement.data('$ionTabsController', tabsCtrl); + tabNavElement.data('$ionTabController', tabCtrl); + tabsCtrl.$tabsElement.append($compile(tabNavElement)($scope)); tabsCtrl.add($scope); + $scope.$on('$destroy', function() { + tabsCtrl.remove($scope); + tabNavElement.isolateScope().$destroy(); + tabNavElement.remove(); + }); - function cleanupChild() { - if(childElement) { - childElement.remove(); - childElement = null; + $scope.$watch('$tabSelected', function(value) { + if (!value) { + $scope.$broadcast('tab.hidden', $scope); } - if(childScope) { - childScope.$destroy(); - childScope = null; - } - } - - $scope.$watch('isVisible', function(value) { + childScope && childScope.$destroy(); + childScope = null; + childElement && $animate.leave(childElement); + childElement = null; if (value) { - cleanupChild(); childScope = $scope.$new(); transclude(childScope, function(clone) { + //remove title attr to stop hover annoyance! + clone[0].removeAttribute('title'); + $animate.enter(clone, tabsCtrl.$element); clone.addClass('pane'); - clone.removeAttr('title'); childElement = clone; - $element.parent().append(childElement); }); - $scope.$broadcast('tab.shown'); - } else if (childScope) { - $scope.$broadcast('tab.hidden'); - cleanupChild(); + $scope.$broadcast('tab.shown', $scope); } }); - // on link, check if it has a nav-view in it - transclude($scope.$new(), function(clone) { - var navViewEle = clone[0].getElementsByTagName("ion-nav-view"); - $scope.hasNavView = (navViewEle.length > 0); - if($scope.hasNavView) { - // this tab has a ui-view - $scope.navViewName = navViewEle[0].getAttribute('name'); - if( $ionicViewService.isCurrentStateNavView( $scope.navViewName ) ) { - // this tab's ui-view is the current one, go to it! - tabsCtrl.select($scope.tabIndex); - } + transclude($scope, function(clone) { + var navView = clone[0].querySelector('ion-nav-view'); + if (navView) { + $scope.navViewName = navView.getAttribute('name'); + selectTabIfMatchesState(); + $scope.$on('$stateChangeSuccess', selectTabIfMatchesState); } }); - var unregister = $rootScope.$on('$stateChangeSuccess', function(value){ - if( $ionicViewService.isCurrentStateNavView($scope.navViewName) && - $scope.tabIndex !== tabsCtrl.selectedIndex) { - tabsCtrl.select($scope.tabIndex); + function selectTabIfMatchesState() { + // this tab's ui-view is the current one, go to it! + if ($ionicViewService.isCurrentStateNavView($scope.navViewName)) { + tabsCtrl.select($scope); } - }); - - $scope.$on('$destroy', unregister); - + } }; } }; }]) - -.directive('ionTabControllerBar', function() { - return { - restrict: 'E', - require: '^ionTabs', - transclude: true, - replace: true, - scope: true, - template: '
' + - '' + - '
', - link: function($scope, $element, $attr, tabsCtrl) { - $element.addClass($scope.tabsType); - $element.addClass($scope.tabsStyle); - } - }; -}) - -.directive('ionTabControllerItem', ['$window', function($window) { +.directive('ionTabNav', function() { return { restrict: 'E', replace: true, - require: '^ionTabs', + require: ['^ionTabs', '^ionTab'], + template: + '' + + '{{badge}}' + + '' + + '' + + '' + + '', scope: { - iconTitle: '@', + title: '@', icon: '@', iconOn: '@', iconOff: '@', badge: '=', - badgeStyle: '=', - active: '=', - tabSelected: '@', - index: '=' + badgeStyle: '@' }, - link: function(scope, element, attrs, tabsCtrl) { - if(attrs.icon) { - scope.iconOn = scope.iconOff = attrs.icon; + compile: function(element, attr, transclude) { + if (attr.icon) { + attr.$set('iconOn', attr.icon); + attr.$set('iconOff', attr.icon); } + return function link($scope, $element, $attrs, ctrls) { + var tabsCtrl = ctrls[0], + tabCtrl = ctrls[1]; - scope.selectTab = function() { - tabsCtrl.select(scope.index, true); + $scope.isTabActive = function() { + return tabsCtrl.selectedTab === tabCtrl.$scope; + }; + $scope.selectTab = function(e) { + e.preventDefault(); + tabsCtrl.select(tabCtrl.$scope, true); + }; }; - }, - template: - '' + - '{{badge}}' + - '' + - '' + - '' + - '' + - '' + } }; -}]); - +}); ; (function(ionic) { 'use strict'; @@ -2711,7 +2803,7 @@ angular.module('ionic.ui.toggle', []) // }); // ionic.on('drag', function(e) { - // console.log('drag'); + // // $scope.toggle.drag(e); // }, handle); @@ -2806,13 +2898,13 @@ angular.module('ionic.ui.touch', []) * */ -angular.module('ionic.ui.viewState', ['ionic.service.view', 'ionic.service.gesture', 'ionic.ui.bindHtml']) +angular.module('ionic.ui.viewState', ['ionic.service.view', 'ionic.service.gesture', 'ngSanitize']) /** * Our Nav Bar directive which updates as the controller state changes. */ .directive('ionNavBar', ['$ionicViewService', '$rootScope', '$animate', '$compile', - function( $ionicViewService, $rootScope, $animate, $compile) { + function( $ionicViewService, $rootScope, $animate, $compile) { return { restrict: 'E', @@ -2827,36 +2919,41 @@ angular.module('ionic.ui.viewState', ['ionic.service.view', 'ionic.service.gestu }, controller: function() {}, template: - '