');return r.append(n.contents()),n.append(r).addClass("tabs-"+t.tabs.position()+" tabs-"+t.tabs.style()),{pre:i,post:o}}}}]),l.directive("ionToggle",["$timeout","$ionicConfig",function(e,t){return{restrict:"E",replace:!0,require:"?ngModel",transclude:!0,template:'
',compile:function(e,n){var i=e.find("input");return d({name:n.name,"ng-value":n.ngValue,"ng-model":n.ngModel,"ng-checked":n.ngChecked,"ng-disabled":n.ngDisabled,"ng-true-value":n.ngTrueValue,"ng-false-value":n.ngFalseValue,"ng-change":n.ngChange},function(e,t){f(e)&&i.attr(t,e)}),n.toggleClass&&e[0].getElementsByTagName("label")[0].classList.add(n.toggleClass),e.addClass("toggle-"+t.form.toggle()),function(e,t){var n=t[0].getElementsByTagName("label")[0],i=n.children[0],o=n.children[1],r=o.children[0],a=v(i).controller("ngModel");e.toggle=new ionic.views.Toggle({el:n,track:o,checkbox:i,handle:r,onChange:function(){a&&(a.$setViewValue(i.checked),e.$apply())}}),e.$on("$destroy",function(){e.toggle.destroy()})}}}}]),l.directive("ionView",function(){return{restrict:"EA",priority:1e3,controller:"$ionicView",compile:function(e){return e.addClass("pane"),e[0].removeAttribute("title"),function(e,t,n,i){i.init()}}}})}();
\ No newline at end of file
diff --git a/release/js/ionic.bundle.js b/release/js/ionic.bundle.js
index 8282192c1e2..0defd37239e 100644
--- a/release/js/ionic.bundle.js
+++ b/release/js/ionic.bundle.js
@@ -9,7 +9,7 @@
* Copyright 2014 Drifty Co.
* http://drifty.com/
*
- * Ionic, v1.0.0-rc.2
+ * Ionic, v1.0.0-rc.3
* A powerful HTML5 mobile app framework.
* http://ionicframework.com/
*
@@ -25,7 +25,7 @@
// build processes may have already created an ionic obj
window.ionic = window.ionic || {};
window.ionic.views = {};
-window.ionic.version = '1.0.0-rc.2';
+window.ionic.version = '1.0.0-rc.3';
(function (ionic) {
@@ -2770,7 +2770,7 @@ ionic.tap = {
for (x = 0; x < previousInputFocus.length; x++) {
previousInputFocus[x].classList.remove('previous-input-focus');
previousInputFocus[x].style.top = '';
- previousInputFocus[x].focus();
+ if ( ionic.keyboard.isOpen && !ionic.keyboard.isClosing ) previousInputFocus[x].focus();
}
});
},
@@ -2864,6 +2864,7 @@ function triggerMouseEvent(type, ele, x, y) {
}
function tapClickGateKeeper(e) {
+ //console.log('click ' + Date.now() + ' isIonicTap: ' + (e.isIonicTap ? true : false));
if (e.target.type == 'submit' && e.detail === 0) {
// do not prevent click if it came from an "Enter" or "Go" keypress submit
return;
@@ -2885,6 +2886,7 @@ function tapClickGateKeeper(e) {
// MOUSE
function tapMouseDown(e) {
+ //console.log('mousedown ' + Date.now());
if (e.isIonicTap || tapIgnoreEvent(e)) return;
if (tapEnabledTouchEvents) {
@@ -2910,6 +2912,7 @@ function tapMouseDown(e) {
}
function tapMouseUp(e) {
+ //console.log("mouseup " + Date.now());
if (tapEnabledTouchEvents) {
e.stopPropagation();
e.preventDefault();
@@ -2938,6 +2941,7 @@ function tapMouseMove(e) {
// TOUCH
function tapTouchStart(e) {
+ //console.log("touchstart " + Date.now());
if (tapIgnoreEvent(e)) return;
tapPointerMoved = false;
@@ -2963,6 +2967,7 @@ function tapTouchStart(e) {
}
function tapTouchEnd(e) {
+ //console.log('touchend ' + Date.now());
if (tapIgnoreEvent(e)) return;
tapEnableTouchEvents();
@@ -3056,6 +3061,7 @@ function tapFocusOutActive() {
}
function tapFocusIn(e) {
+ //console.log('focusin ' + Date.now());
// Because a text input doesn't preventDefault (so the caret still works) there's a chance
// that its mousedown event 300ms later will change the focus to another element after
// the keyboard shows up.
@@ -3076,7 +3082,8 @@ function tapFocusIn(e) {
ionic.scroll.isScrolling = false;
}
-function tapFocusOut() {
+function tapFocusOut(e) {
+ //console.log("focusout");
tapActiveElement(null);
}
@@ -3541,74 +3548,140 @@ ionic.DomUtil.ready(function() {
*
*/
-var keyboardViewportHeight = getViewportHeight();
-var keyboardIsOpen;
+/**
+ * The current viewport height.
+ */
+var keyboardCurrentViewportHeight;
+
+/**
+ * The viewport height when in portrait orientation.
+ */
+var keyboardPortraitViewportHeight = 0;
+
+/**
+ * The viewport height when in landscape orientation.
+ */
+var keyboardLandscapeViewportHeight = 0;
+
+/**
+ * The currently focused input.
+ */
var keyboardActiveElement;
+
+/**
+ * The scroll view containing the currently focused input.
+ */
+var scrollView;
+
+/**
+ * Timer for the setInterval that polls window.innerHeight to determine whether
+ * the layout has updated for the keyboard showing/hiding.
+ */
+var waitForResizeTimer;
+
+/**
+ * Sometimes when switching inputs or orientations, focusout will fire before
+ * focusin, so this timer is for the small setTimeout to determine if we should
+ * really focusout/hide the keyboard.
+ */
var keyboardFocusOutTimer;
-var keyboardFocusInTimer;
-var keyboardPollHeightTimer;
-var keyboardLastShow = 0;
+/**
+ * on Android, orientationchange will fire before the keyboard plugin notifies
+ * the browser that the keyboard will show/is showing, so this flag indicates
+ * to nativeShow that there was an orientationChange and we should update
+ * the viewport height with an accurate keyboard height value
+ */
+var wasOrientationChange = false;
+
+/**
+ * CSS class added to the body indicating the keyboard is open.
+ */
var KEYBOARD_OPEN_CSS = 'keyboard-open';
-var SCROLL_CONTAINER_CSS = 'scroll';
+/**
+ * CSS class that indicates a scroll container.
+ */
+var SCROLL_CONTAINER_CSS = 'scroll-content';
+
+/**
+ * Ionic keyboard namespace.
+ * @namespace keyboard
+ */
ionic.keyboard = {
- isOpen: false,
- height: null,
- landscape: false,
- hide: function() {
- clearTimeout(keyboardFocusInTimer);
- clearTimeout(keyboardFocusOutTimer);
- clearTimeout(keyboardPollHeightTimer);
+ /**
+ * Whether the keyboard is open or not.
+ */
+ isOpen: false,
- ionic.keyboard.isOpen = false;
+ /**
+ * Whether the keyboard is closing or not.
+ */
+ isClosing: false,
- ionic.trigger('resetScrollView', {
- target: keyboardActiveElement
- }, true);
+ /**
+ * Whether the keyboard is opening or not.
+ */
+ isOpening: false,
- ionic.requestAnimationFrame(function(){
- document.body.classList.remove(KEYBOARD_OPEN_CSS);
- });
+ /**
+ * The height of the keyboard in pixels, as reported by the keyboard plugin.
+ * If the plugin is not available, calculated as the difference in
+ * window.innerHeight after the keyboard has shown.
+ */
+ height: 0,
- // the keyboard is gone now, remove the touchmove that disables native scroll
- if (window.navigator.msPointerEnabled) {
- document.removeEventListener("MSPointerMove", keyboardPreventDefault);
- } else {
- document.removeEventListener('touchmove', keyboardPreventDefault);
- }
- document.removeEventListener('keydown', keyboardOnKeyDown);
+ /**
+ * Whether the device is in landscape orientation or not.
+ */
+ isLandscape: false,
- if( keyboardHasPlugin() ) {
+ /**
+ * Hide the keyboard, if it is open.
+ */
+ hide: function() {
+ if (keyboardHasPlugin()) {
cordova.plugins.Keyboard.close();
}
+ keyboardActiveElement && keyboardActiveElement.blur();
},
+ /**
+ * An alias for cordova.plugins.Keyboard.show(). If the keyboard plugin
+ * is installed, show the keyboard.
+ */
show: function() {
- if( keyboardHasPlugin() ) {
+ if (keyboardHasPlugin()) {
cordova.plugins.Keyboard.show();
}
}
};
-function keyboardInit() {
- if( keyboardHasPlugin() ) {
- window.addEventListener('native.keyboardshow', keyboardNativeShow);
- window.addEventListener('native.keyboardhide', keyboardFocusOut);
+// Initialize the viewport height (after ionic.keyboard.height has been
+// defined).
+keyboardCurrentViewportHeight = getViewportHeight();
- //deprecated
- window.addEventListener('native.showkeyboard', keyboardNativeShow);
- window.addEventListener('native.hidekeyboard', keyboardFocusOut);
+ /* Event handlers */
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Event handler for first touch event, initializes all event listeners
+ * for keyboard related events.
+ */
+function keyboardInit() {
+ var debouncedKeyboardFocusIn = ionic.debounce(keyboardFocusIn, 200, true);
+
+ if (keyboardHasPlugin()) {
+ window.addEventListener('native.keyboardshow', ionic.debounce(keyboardNativeShow, 100, true));
+ window.addEventListener('native.keyboardhide', keyboardFocusOut);
} else {
document.body.addEventListener('focusout', keyboardFocusOut);
}
- document.body.addEventListener('ionic.focusin', keyboardBrowserFocusIn);
- document.body.addEventListener('focusin', keyboardBrowserFocusIn);
-
- document.body.addEventListener('orientationchange', keyboardOrientationChange);
+ document.body.addEventListener('ionic.focusin', debouncedKeyboardFocusIn);
+ document.body.addEventListener('focusin', debouncedKeyboardFocusIn);
if (window.navigator.msPointerEnabled) {
document.removeEventListener("MSPointerDown", keyboardInit);
@@ -3617,83 +3690,74 @@ function keyboardInit() {
}
}
+/**
+ * Event handler for 'native.keyboardshow' event, sets keyboard.height to the
+ * reported height and keyboard.isOpening to true. Then calls
+ * keyboardWaitForResize with keyboardShow or keyboardUpdateViewportHeight as
+ * the callback depending on whether the event was triggered by a focusin or
+ * an orientationchange.
+ */
function keyboardNativeShow(e) {
clearTimeout(keyboardFocusOutTimer);
- ionic.keyboard.height = e.keyboardHeight;
-}
+ //console.log("keyboardNativeShow fired at: " + Date.now());
+ //console.log("keyboardNativeshow window.innerHeight: " + window.innerHeight);
-function keyboardBrowserFocusIn(e) {
- if( !e.target || e.target.readOnly || !ionic.tap.isKeyboardElement(e.target) || !keyboardIsWithinScroll(e.target) ) return;
-
- //console.log("keyboardBrowserFocusIn");
- document.addEventListener('keydown', keyboardOnKeyDown, false);
-
- document.body.scrollTop = 0;
- document.body.querySelector('.scroll-content').scrollTop = 0;
+ if (!ionic.keyboard.isOpen || ionic.keyboard.isClosing) {
+ ionic.keyboard.isOpening = true;
+ ionic.keyboard.isClosing = false;
+ }
- keyboardActiveElement = e.target;
+ ionic.keyboard.height = e.keyboardHeight;
+ //console.log('nativeshow keyboard height:' + e.keyboardHeight);
- keyboardSetShow(e);
+ if (wasOrientationChange) {
+ keyboardWaitForResize(keyboardUpdateViewportHeight, true);
+ } else {
+ keyboardWaitForResize(keyboardShow, true);
+ }
}
-function keyboardSetShow(e) {
- clearTimeout(keyboardFocusInTimer);
+/**
+ * Event handler for 'focusin' and 'ionic.focusin' events. Initializes
+ * keyboard state (keyboardActiveElement and keyboard.isOpening) for the
+ * appropriate adjustments once the window has resized. If not using the
+ * keyboard plugin, calls keyboardWaitForResize with keyboardShow as the
+ * callback or keyboardShow right away if the keyboard is already open. If
+ * using the keyboard plugin does nothing and lets keyboardNativeShow handle
+ * adjustments with a more accurate keyboard height.
+ */
+function keyboardFocusIn(e) {
clearTimeout(keyboardFocusOutTimer);
+ //console.log("keyboardFocusIn from: " + e.type + " at: " + Date.now());
- keyboardFocusInTimer = setTimeout(function(){
- if ( keyboardLastShow + 350 > Date.now() ) return;
- //console.log('keyboardSetShow');
- keyboardLastShow = Date.now();
- var keyboardHeight;
- var elementBounds = keyboardActiveElement.getBoundingClientRect();
- var count = 0;
-
- keyboardPollHeightTimer = setInterval(function(){
-
- keyboardHeight = keyboardGetHeight();
- if (count > 10){
- clearInterval(keyboardPollHeightTimer);
- //waited long enough, just guess
- keyboardHeight = 275;
- }
- if (keyboardHeight){
- clearInterval(keyboardPollHeightTimer);
- keyboardShow(e.target, elementBounds.top, elementBounds.bottom, keyboardViewportHeight, keyboardHeight);
- }
- count++;
-
- }, 100);
- }, 32);
-}
-
-function keyboardShow(element, elementTop, elementBottom, viewportHeight, keyboardHeight) {
- var details = {
- target: element,
- elementTop: Math.round(elementTop),
- elementBottom: Math.round(elementBottom),
- keyboardHeight: keyboardHeight,
- viewportHeight: viewportHeight
- };
-
- details.hasPlugin = keyboardHasPlugin();
-
- details.contentHeight = viewportHeight - keyboardHeight;
-
- //console.log('keyboardShow', keyboardHeight, details.contentHeight);
-
- // figure out if the element is under the keyboard
- details.isElementUnderKeyboard = (details.elementBottom > details.contentHeight);
-
- ionic.keyboard.isOpen = true;
+ if (!e.target ||
+ e.target.readOnly ||
+ !ionic.tap.isKeyboardElement(e.target) ||
+ !(scrollView = inputScrollView(e.target))) {
+ return;
+ }
- // send event so the scroll view adjusts
- keyboardActiveElement = element;
- ionic.trigger('scrollChildIntoView', details, true);
+ keyboardActiveElement = e.target;
+ // if using JS scrolling, undo the effects of native overflow scroll so the
+ // scroll view is positioned correctly
+ document.body.scrollTop = 0;
+ scrollView.scrollTop = 0;
ionic.requestAnimationFrame(function(){
- document.body.classList.add(KEYBOARD_OPEN_CSS);
+ document.body.scrollTop = 0;
+ scrollView.scrollTop = 0;
});
+ if (!ionic.keyboard.isOpen || ionic.keyboard.isClosing) {
+ ionic.keyboard.isOpening = true;
+ ionic.keyboard.isClosing = false;
+ }
+
+ // attempt to prevent browser from natively scrolling input into view while
+ // we are trying to do the same (while we are scrolling) if the user taps the
+ // keyboard
+ document.addEventListener('keydown', keyboardOnKeyDown, false);
+
// any showing part of the document that isn't within the scroll the user
// could touchmove and cause some ugly changes to the app, so disable
// any touchmove events while the keyboard is open using e.preventDefault()
@@ -3703,91 +3767,292 @@ function keyboardShow(element, elementTop, elementBottom, viewportHeight, keyboa
document.addEventListener('touchmove', keyboardPreventDefault, false);
}
- return details;
+ // if we aren't using the plugin and the keyboard isn't open yet, wait for the
+ // window to resize so we can get an accurate estimate of the keyboard size,
+ // otherwise we do nothing and let nativeShow call keyboardShow once we have
+ // an exact keyboard height
+ // if the keyboard is already open, go ahead and scroll the input into view
+ // if necessary
+ if (!ionic.keyboard.isOpen && !keyboardHasPlugin()) {
+ keyboardWaitForResize(keyboardShow, true);
+
+ } else if (ionic.keyboard.isOpen) {
+ keyboardShow();
+ }
}
+/**
+ * Event handler for 'focusout' events. Sets keyboard.isClosing to true and
+ * calls keyboardWaitForResize with keyboardHide as the callback after a small
+ * timeout.
+ */
function keyboardFocusOut(e) {
clearTimeout(keyboardFocusOutTimer);
+ //console.log("keyboardFocusOut fired at: " + Date.now());
+ //console.log("keyboardFocusOut event type: " + e.type);
+
+ if (ionic.keyboard.isOpen || ionic.keyboard.isOpening) {
+ ionic.keyboard.isClosing = true;
+ ionic.keyboard.isOpening = false;
+ }
- keyboardFocusOutTimer = setTimeout(ionic.keyboard.hide, 350);
+ // Call keyboardHide with a slight delay because sometimes on focus or
+ // orientation change focusin is called immediately after, so we give it time
+ // to cancel keyboardHide
+ keyboardFocusOutTimer = setTimeout(function() {
+ ionic.requestAnimationFrame(function() {
+ // focusOut during or right after an orientationchange, so we didn't get
+ // a chance to update the viewport height yet, do it and keyboardHide
+ //console.log("focusOut, wasOrientationChange: " + wasOrientationChange);
+ if (wasOrientationChange) {
+ keyboardWaitForResize(function(){
+ keyboardUpdateViewportHeight();
+ keyboardHide();
+ }, false);
+ } else {
+ keyboardWaitForResize(keyboardHide, false);
+ }
+ });
+ }, 50);
}
-function keyboardUpdateViewportHeight() {
- if( getViewportHeight() > keyboardViewportHeight ) {
- keyboardViewportHeight = getViewportHeight();
+/**
+ * Event handler for 'orientationchange' events. If using the keyboard plugin
+ * and the keyboard is open on Android, sets wasOrientationChange to true so
+ * nativeShow can update the viewport height with an accurate keyboard height.
+ * If the keyboard isn't open or keyboard plugin isn't being used,
+ * waits for the window to resize before updating the viewport height.
+ *
+ * On iOS, where orientationchange fires after the keyboard has already shown,
+ * updates the viewport immediately, regardless of if the keyboard is already
+ * open.
+ */
+function keyboardOrientationChange() {
+ //console.log("orientationchange fired at: " + Date.now());
+ //console.log("orientation was: " + (ionic.keyboard.isLandscape ? "landscape" : "portrait"));
+
+ // toggle orientation
+ ionic.keyboard.isLandscape = !ionic.keyboard.isLandscape;
+ // //console.log("now orientation is: " + (ionic.keyboard.isLandscape ? "landscape" : "portrait"));
+
+ // no need to wait for resizing on iOS, and orientationchange always fires
+ // after the keyboard has opened, so it doesn't matter if it's open or not
+ if (ionic.Platform.isIOS()) {
+ keyboardUpdateViewportHeight();
+ }
+
+ // On Android, if the keyboard isn't open or we aren't using the keyboard
+ // plugin, update the viewport height once everything has resized. If the
+ // keyboard is open and we are using the keyboard plugin do nothing and let
+ // nativeShow handle it using an accurate keyboard height.
+ if ( ionic.Platform.isAndroid()) {
+ if (!ionic.keyboard.isOpen || !keyboardHasPlugin()) {
+ keyboardWaitForResize(keyboardUpdateViewportHeight, false);
+ } else {
+ wasOrientationChange = true;
+ }
}
}
+/**
+ * Event handler for 'keydown' event. Tries to prevent browser from natively
+ * scrolling an input into view when a user taps the keyboard while we are
+ * scrolling the input into view ourselves with JS.
+ */
function keyboardOnKeyDown(e) {
- if( ionic.scroll.isScrolling ) {
+ if (ionic.scroll.isScrolling) {
keyboardPreventDefault(e);
}
}
+/**
+ * Event for 'touchmove' or 'MSPointerMove'. Prevents native scrolling on
+ * elements outside the scroll view while the keyboard is open.
+ */
function keyboardPreventDefault(e) {
- if( e.target.tagName !== 'TEXTAREA' ) {
+ if (e.target.tagName !== 'TEXTAREA') {
e.preventDefault();
}
}
-function keyboardOrientationChange() {
- var updatedViewportHeight = getViewportHeight();
+ /* Private API */
+/* -------------------------------------------------------------------------- */
- //too slow, have to wait for updated height
- if (updatedViewportHeight === keyboardViewportHeight){
- var count = 0;
- var pollViewportHeight = setInterval(function(){
- //give up
- if (count > 10){
- clearInterval(pollViewportHeight);
- }
+/**
+ * Polls window.innerHeight until it has updated to an expected value (or
+ * sufficient time has passed) before calling the specified callback function.
+ * Only necessary for non-fullscreen Android which sometimes reports multiple
+ * window.innerHeight values during interim layouts while it is resizing.
+ *
+ * On iOS, the window.innerHeight will already be updated, but we use the 50ms
+ * delay as essentially a timeout so that scroll view adjustments happen after
+ * the keyboard has shown so there isn't a white flash from us resizing too
+ * quickly.
+ *
+ * @param {Function} callback the function to call once the window has resized
+ * @param {boolean} isOpening whether the resize is from the keyboard opening
+ * or not
+ */
+function keyboardWaitForResize(callback, isOpening) {
+ clearInterval(waitForResizeTimer);
+ var count = 0;
+ var maxCount;
+ var initialHeight = getViewportHeight();
+ var viewportHeight = initialHeight;
- updatedViewportHeight = getViewportHeight();
+ //console.log("waitForResize initial viewport height: " + viewportHeight);
+ //var start = Date.now();
+ //console.log("start: " + start);
- if (updatedViewportHeight !== keyboardViewportHeight){
- if (updatedViewportHeight < keyboardViewportHeight){
- ionic.keyboard.landscape = true;
- } else {
- ionic.keyboard.landscape = false;
- }
- keyboardViewportHeight = updatedViewportHeight;
- clearInterval(pollViewportHeight);
- }
- count++;
+ // want to fail relatively quickly on modern android devices, since it's much
+ // more likely we just have a bad keyboard height
+ if (ionic.Platform.isAndroid() && ionic.Platform.version() < 4.4) {
+ maxCount = 30;
+ } else if (ionic.Platform.isAndroid()) {
+ maxCount = 10;
+ } else {
+ maxCount = 1;
+ }
+
+ // poll timer
+ waitForResizeTimer = setInterval(function(){
+ viewportHeight = getViewportHeight();
+
+ // height hasn't updated yet, try again in 50ms
+ // if not using plugin, wait for maxCount to ensure we have waited long enough
+ // to get an accurate keyboard height
+ if (++count < maxCount &&
+ ((!isPortraitViewportHeight(viewportHeight) &&
+ !isLandscapeViewportHeight(viewportHeight)) ||
+ !ionic.keyboard.height)) {
+ return;
+ }
+
+ // infer the keyboard height from the resize if not using the keyboard plugin
+ if ( !keyboardHasPlugin() ) {
+ ionic.keyboard.height = Math.abs(initialHeight - window.innerHeight);
+ }
+
+ // set to true if we were waiting for the keyboard to open
+ ionic.keyboard.isOpen = isOpening;
+
+ clearInterval(waitForResizeTimer);
+ //var end = Date.now();
+ //console.log("waitForResize count: " + count);
+ //console.log("end: " + end);
+ //console.log("difference: " + ( end - start ) + "ms");
+
+ //console.log("callback: " + callback.name);
+ callback();
+
+ }, 50);
+}
+
+/**
+ * On keyboard close sets keyboard state to closed, resets the scroll view,
+ * removes CSS from body indicating keyboard was open, removes any event
+ * listeners for when the keyboard is open and on Android blurs the active
+ * element (which in some cases will still have focus even if the keyboard
+ * is closed and can cause it to reappear on subsequent taps).
+ */
+function keyboardHide() {
+ clearTimeout(keyboardFocusOutTimer);
+ //console.log("keyboardHide");
+
+ ionic.keyboard.isOpen = false;
+ ionic.keyboard.isClosing = false;
+
+ ionic.trigger('resetScrollView', {
+ target: keyboardActiveElement
+ }, true);
- }, 50);
+ ionic.requestAnimationFrame(function(){
+ document.body.classList.remove(KEYBOARD_OPEN_CSS);
+ });
+
+ // the keyboard is gone now, remove the touchmove that disables native scroll
+ if (window.navigator.msPointerEnabled) {
+ document.removeEventListener("MSPointerMove", keyboardPreventDefault);
} else {
- keyboardViewportHeight = updatedViewportHeight;
+ document.removeEventListener('touchmove', keyboardPreventDefault);
}
+ document.removeEventListener('keydown', keyboardOnKeyDown);
+
+ if (ionic.Platform.isAndroid()) {
+ // on android closing the keyboard with the back/dismiss button won't remove
+ // focus and keyboard can re-appear on subsequent taps (like scrolling)
+ if (keyboardHasPlugin()) cordova.plugins.Keyboard.close();
+ keyboardActiveElement && keyboardActiveElement.blur();
+ }
+}
+
+/**
+ * On keyboard open sets keyboard state to open, adds CSS to the body
+ * indicating the keyboard is open and tells the scroll view to resize and
+ * the currently focused input into view if necessary.
+ */
+function keyboardShow() {
+ var elementBounds = keyboardActiveElement.getBoundingClientRect();
+ var details = {
+ target: keyboardActiveElement,
+ elementTop: Math.round(elementBounds.top),
+ elementBottom: Math.round(elementBounds.bottom),
+ keyboardHeight: ionic.keyboard.height,
+ viewportHeight: keyboardCurrentViewportHeight
+ };
+
+ details.windowHeight = details.viewportHeight - details.keyboardHeight;
+ //console.log("keyboardShow viewportHeight: " + details.viewportHeight +
+ //", windowHeight: " + details.windowHeight +
+ //", keyboardHeight: " + details.keyboardHeight);
+
+ // figure out if the element is under the keyboard
+ details.isElementUnderKeyboard = (details.elementBottom > details.windowHeight);
+ //console.log("isUnderKeyboard: " + details.isElementUnderKeyboard);
+ //console.log("elementBottom: " + details.elementBottom);
+
+ ionic.keyboard.isOpen = true;
+ ionic.keyboard.isOpening = false;
+
+ // send event so the scroll view adjusts
+ ionic.trigger('scrollChildIntoView', details, true);
+
+ setTimeout(function(){
+ document.body.classList.add(KEYBOARD_OPEN_CSS);
+ }, 400);
+
+ return details;
}
function keyboardGetHeight() {
- // check if we are already have a keyboard height from the plugin
+ // check if we already have a keyboard height from the plugin or resize calculations
if ( ionic.keyboard.height ) {
return ionic.keyboard.height;
}
- if ( ionic.Platform.isAndroid() ){
- //should be using the plugin, no way to know how big the keyboard is, so guess
- if ( ionic.Platform.isFullScreen ){
+ if ( ionic.Platform.isAndroid() ) {
+ // should be using the plugin, no way to know how big the keyboard is, so guess
+ if ( ionic.Platform.isFullScreen ) {
return 275;
}
- //otherwise, wait for the screen to resize
- if ( getViewportHeight() < keyboardViewportHeight ){
- return keyboardViewportHeight - getViewportHeight();
+ // otherwise just calculate it
+ var contentHeight = window.innerHeight;
+ if ( contentHeight < keyboardCurrentViewportHeight ) {
+ return keyboardCurrentViewportHeight - contentHeight;
} else {
return 0;
}
}
- // fallback for when its the webview without the plugin
+ // fallback for when it's the webview without the plugin
// or for just the standard web browser
+ // TODO: have these be based on device
if( ionic.Platform.isIOS() ) {
- if ( ionic.keyboard.landscape ){
+ if ( ionic.keyboard.isLandscape ) {
return 206;
}
- if (!ionic.Platform.isWebView()){
+ if ( !ionic.Platform.isWebView() ) {
return 216;
}
@@ -3798,18 +4063,86 @@ function keyboardGetHeight() {
return 275;
}
+function isPortraitViewportHeight(viewportHeight) {
+ return !ionic.keyboard.isLandscape &&
+ keyboardPortraitViewportHeight &&
+ ( Math.abs(keyboardPortraitViewportHeight - viewportHeight) < 2 );
+}
+
+function isLandscapeViewportHeight(viewportHeight) {
+ return ionic.keyboard.isLandscape &&
+ keyboardLandscapeViewportHeight &&
+ ( Math.abs(keyboardLandscapeViewportHeight - viewportHeight) < 2 );
+}
+
+function keyboardUpdateViewportHeight() {
+ wasOrientationChange = false;
+ keyboardCurrentViewportHeight = getViewportHeight();
+
+ if (ionic.keyboard.isLandscape && !keyboardLandscapeViewportHeight) {
+ //console.log("saved landscape: " + keyboardCurrentViewportHeight);
+ keyboardLandscapeViewportHeight = keyboardCurrentViewportHeight;
+
+ } else if (!ionic.keyboard.isLandscape && !keyboardPortraitViewportHeight) {
+ //console.log("saved portrait: " + keyboardCurrentViewportHeight);
+ keyboardPortraitViewportHeight = keyboardCurrentViewportHeight;
+ }
+
+ ionic.trigger('resetScrollView', {
+ target: keyboardActiveElement
+ }, true);
+
+ if (ionic.keyboard.isOpen && ionic.tap.isTextInput(keyboardActiveElement)) {
+ keyboardShow();
+ }
+}
+
+function keyboardInitViewportHeight(e) {
+ var viewportHeight = getViewportHeight();
+ //console.log("Keyboard init VP: " + viewportHeight + " " + window.innerWidth);
+ // can't just use window.innerHeight in case the keyboard is opened immediately
+ if ((viewportHeight / window.innerWidth) < 1) {
+ ionic.keyboard.isLandscape = true;
+ }
+ //console.log("ionic.keyboard.isLandscape is: " + ionic.keyboard.isLandscape);
+
+ // initialize or update the current viewport height values if coming from a
+ // resume event
+ if ((e && viewportHeight != keyboardCurrentViewportHeight) || !e) {
+ keyboardCurrentViewportHeight = viewportHeight;
+ if (ionic.keyboard.isLandscape && !keyboardLandscapeViewportHeight) {
+ keyboardLandscapeViewportHeight = keyboardCurrentViewportHeight;
+ } else if (!ionic.keyboard.isLandscape && !keyboardPortraitViewportHeight) {
+ keyboardPortraitViewportHeight = keyboardCurrentViewportHeight;
+ }
+ }
+}
+
function getViewportHeight() {
- return window.innerHeight || screen.height;
+ var windowHeight = window.innerHeight;
+ //console.log('window.innerHeight is: ' + windowHeight);
+ //console.log('kb height is: ' + ionic.keyboard.height);
+ //console.log('kb isOpen: ' + ionic.keyboard.isOpen);
+
+ //TODO: add iPad undocked/split kb once kb plugin supports it
+ // the keyboard overlays the window on Android fullscreen
+ if (!(ionic.Platform.isAndroid() && ionic.Platform.isFullScreen) &&
+ (ionic.keyboard.isOpen || ionic.keyboard.isOpening) &&
+ !ionic.keyboard.isClosing) {
+
+ return windowHeight + ionic.keyboard.height;
+ }
+ return windowHeight;
}
-function keyboardIsWithinScroll(ele) {
+function inputScrollView(ele) {
while(ele) {
- if(ele.classList.contains(SCROLL_CONTAINER_CSS)) {
- return true;
+ if (ele.classList.contains(SCROLL_CONTAINER_CSS)) {
+ return ele;
}
ele = ele.parentElement;
}
- return false;
+ return null;
}
function keyboardHasPlugin() {
@@ -3817,11 +4150,35 @@ function keyboardHasPlugin() {
}
ionic.Platform.ready(function() {
- keyboardUpdateViewportHeight();
+ keyboardInitViewportHeight();
+
+ window.addEventListener('orientationchange', keyboardOrientationChange);
+
+ // if orientation changes while app is in background, update on resuming
+ /*
+ if ( ionic.Platform.isWebView() ) {
+ document.addEventListener('resume', keyboardInitViewportHeight);
+
+ if (ionic.Platform.isAndroid()) {
+ //TODO: onbackpressed to detect keyboard close without focusout or plugin
+ }
+ }
+ */
+
+ // if orientation changes while app is in background, update on resuming
+/* if ( ionic.Platform.isWebView() ) {
+ document.addEventListener('pause', function() {
+ window.removeEventListener('orientationchange', keyboardOrientationChange);
+ })
+ document.addEventListener('resume', function() {
+ keyboardInitViewportHeight();
+ window.addEventListener('orientationchange', keyboardOrientationChange)
+ });
+ }*/
// Android sometimes reports bad innerHeight on window.load
// try it again in a lil bit to play it safe
- setTimeout(keyboardUpdateViewportHeight, 999);
+ setTimeout(keyboardInitViewportHeight, 999);
// only initialize the adjustments for the virtual keyboard
// if a touchstart event happens
@@ -4627,86 +4984,136 @@ ionic.views.Scroll = ionic.views.View.inherit({
// Event Handler
var container = self.__container;
- self.scrollChildIntoView = function(e) {
+ // save height when scroll view is shrunk so we don't need to reflow
+ var scrollViewOffsetHeight;
- //distance from bottom of scrollview to top of viewport
- var scrollBottomOffsetToTop;
+ /**
+ * Shrink the scroll view when the keyboard is up if necessary and if the
+ * focused input is below the bottom of the shrunk scroll view, scroll it
+ * into view.
+ */
+ self.scrollChildIntoView = function(e) {
+ void 0;
- if ( !self.isScrolledIntoView ) {
+ // D
+ var scrollBottomOffsetToTop = container.getBoundingClientRect().bottom;
+ // D - A
+ scrollViewOffsetHeight = container.offsetHeight;
+ var alreadyShrunk = self.isShrunkForKeyboard;
+
+ var isModal = container.parentNode.classList.contains('modal');
+ // 680px is when the media query for 60% modal width kicks in
+ var isInsetModal = isModal && window.innerWidth >= 680;
+
+ /*
+ * _______
+ * |---A---| <- top of scroll view
+ * | |
+ * |---B---| <- keyboard
+ * | C | <- input
+ * |---D---| <- initial bottom of scroll view
+ * |___E___| <- bottom of viewport
+ *
+ * All commented calculations relative to the top of the viewport (ie E
+ * is the viewport height, not 0)
+ */
+ if (!alreadyShrunk) {
// shrink scrollview so we can actually scroll if the input is hidden
// if it isn't shrink so we can scroll to inputs under the keyboard
- if ((ionic.Platform.isIOS() || ionic.Platform.isFullScreen)){
-
+ // inset modals won't shrink on Android on their own when the keyboard appears
+ if ( ionic.Platform.isIOS() || ionic.Platform.isFullScreen || isInsetModal ) {
// if there are things below the scroll view account for them and
// subtract them from the keyboard height when resizing
- scrollBottomOffsetToTop = container.getBoundingClientRect().bottom;
+ // E - D E D
var scrollBottomOffsetToBottom = e.detail.viewportHeight - scrollBottomOffsetToTop;
+
+ // 0 or D - B if D > B E - B E - D
var keyboardOffset = Math.max(0, e.detail.keyboardHeight - scrollBottomOffsetToBottom);
- container.style.height = (container.clientHeight - keyboardOffset) + "px";
- container.style.overflow = "visible";
- //update scroll view
- self.resize();
+
+ ionic.requestAnimationFrame(function(){
+ // D - A or B - A if D > B D - A max(0, D - B)
+ scrollViewOffsetHeight = scrollViewOffsetHeight - keyboardOffset;
+ container.style.height = scrollViewOffsetHeight + "px";
+
+ //update scroll view
+ self.resize();
+ });
}
- self.isScrolledIntoView = true;
+
+ self.isShrunkForKeyboard = true;
+
}
- //If the element is positioned under the keyboard...
- if ( e.detail.isElementUnderKeyboard ) {
- var delay;
- // Wait on android for web view to resize
- if ( ionic.Platform.isAndroid() && !ionic.Platform.isFullScreen ) {
- // android y u resize so slow
- if ( ionic.Platform.version() < 4.4) {
- delay = 500;
- } else {
- // probably overkill for chrome
- delay = 350;
+ /*
+ * _______
+ * |---A---| <- top of scroll view
+ * | * | <- where we want to scroll to
+ * |--B-D--| <- keyboard, bottom of scroll view
+ * | C | <- input
+ * | |
+ * |___E___| <- bottom of viewport
+ *
+ * All commented calculations relative to the top of the viewport (ie E
+ * is the viewport height, not 0)
+ */
+ // if the element is positioned under the keyboard scroll it into view
+ if (e.detail.isElementUnderKeyboard) {
+
+ ionic.requestAnimationFrame(function(){
+ container.scrollTop = 0;
+ // update D if we shrunk
+ if (self.isShrunkForKeyboard && !alreadyShrunk) {
+ scrollBottomOffsetToTop = container.getBoundingClientRect().bottom;
}
- } else {
- delay = 80;
- }
- //Put element in middle of visible screen
- //Wait for android to update view height and resize() to reset scroll position
- ionic.scroll.isScrolling = true;
- setTimeout(function(){
- //middle of the scrollview, where we want to scroll to
- var scrollMidpointOffset = container.clientHeight * 0.5;
+ // middle of the scrollview, this is where we want to scroll to
+ // (D - A) / 2
+ var scrollMidpointOffset = scrollViewOffsetHeight * 0.5;
+ //console.log("container.offsetHeight: " + scrollViewOffsetHeight);
- scrollBottomOffsetToTop = container.getBoundingClientRect().bottom;
- //distance from top of focused element to the bottom of the scroll view
- var elementTopOffsetToScrollBottom = e.detail.elementTop - scrollBottomOffsetToTop;
+ // middle of the input we want to scroll into view
+ // C
+ var inputMidpoint = ((e.detail.elementBottom + e.detail.elementTop) / 2);
- var scrollTop = elementTopOffsetToScrollBottom + scrollMidpointOffset;
+ // distance from middle of input to the bottom of the scroll view
+ // C - D C D
+ var inputMidpointOffsetToScrollBottom = inputMidpoint - scrollBottomOffsetToTop;
- if (scrollTop > 0){
- ionic.tap.cloneFocusedInput(container, self);
+ //C - D + (D - A)/2 C - D (D - A)/ 2
+ var scrollTop = inputMidpointOffsetToScrollBottom + scrollMidpointOffset;
+
+ if ( scrollTop > 0) {
+ if (ionic.Platform.isIOS()) ionic.tap.cloneFocusedInput(container, self);
self.scrollBy(0, scrollTop, true);
self.onScroll();
}
- }, delay);
+ });
}
- //Only the first scrollView parent of the element that broadcasted this event
- //(the active element that needs to be shown) should receive this event
+ // Only the first scrollView parent of the element that broadcasted this event
+ // (the active element that needs to be shown) should receive this event
e.stopPropagation();
};
self.resetScrollView = function(e) {
//return scrollview to original height once keyboard has hidden
- if (self.isScrolledIntoView) {
- self.isScrolledIntoView = false;
+ if ( self.isShrunkForKeyboard ) {
+ self.isShrunkForKeyboard = false;
container.style.height = "";
- container.style.overflow = "";
- self.resize();
- ionic.scroll.isScrolling = false;
}
+ self.resize();
};
//Broadcasted when keyboard is shown on some platforms.
//See js/utils/keyboard.js
container.addEventListener('scrollChildIntoView', self.scrollChildIntoView);
- container.addEventListener('resetScrollView', self.resetScrollView);
+
+ // Listen on document because container may not have had the last
+ // keyboardActiveElement, for example after closing a modal with a focused
+ // input and returning to a previously resized scroll view in an ion-content.
+ // Since we can only resize scroll views that are currently visible, just resize
+ // the current scroll view when the keyboard is closed.
+ document.addEventListener('resetScrollView', self.resetScrollView);
function getEventTouches(e) {
return e.touches && e.touches.length ? e.touches : [{
@@ -4924,7 +5331,7 @@ ionic.views.Scroll = ionic.views.View.inherit({
document.removeEventListener('wheel', self.mouseWheel);
container.removeEventListener('scrollChildIntoView', self.scrollChildIntoView);
- container.removeEventListener('resetScrollView', self.resetScrollView);
+ document.removeEventListener('resetScrollView', self.resetScrollView);
ionic.tap.removeClonedInputs(container, self);
@@ -7872,6 +8279,7 @@ ionic.views.Slider = ionic.views.View.inherit({
translate(index+1, delta.x + slidePos[index+1], 0);
}
+ options.onDrag && options.onDrag();
}
},
@@ -7962,6 +8370,7 @@ ionic.views.Slider = ionic.views.View.inherit({
document.removeEventListener('mouseup', events, false);
}
+ options.onDragEnd && options.onDragEnd();
},
transitionEnd: function(event) {
@@ -41496,7 +41905,7 @@ angular.module('ui.router.state')
* Copyright 2014 Drifty Co.
* http://drifty.com/
*
- * Ionic, v1.0.0-rc.2
+ * Ionic, v1.0.0-rc.3
* A powerful HTML5 mobile app framework.
* http://ionicframework.com/
*
@@ -43848,7 +44257,8 @@ IonicModule
'$ionicTemplateLoader',
'$q',
'$log',
-function($rootScope, $ionicBody, $compile, $timeout, $ionicPlatform, $ionicTemplateLoader, $q, $log) {
+ '$ionicClickBlock',
+function($rootScope, $ionicBody, $compile, $timeout, $ionicPlatform, $ionicTemplateLoader, $q, $log, $ionicClickBlock) {
/**
* @ngdoc controller
@@ -43912,6 +44322,11 @@ function($rootScope, $ionicBody, $compile, $timeout, $ionicPlatform, $ionicTempl
$ionicBody.append(self.el);
}
+ // if modal was closed while the keyboard was up, reset scroll view on
+ // next show since we can only resize it once it's visible
+ var scrollCtrl = modalEl.data('$$ionicScrollController');
+ scrollCtrl && scrollCtrl.resize();
+
if (target && self.positionView) {
self.positionView(target, modalEl);
// set up a listener for in case the window size changes
@@ -43960,6 +44375,10 @@ function($rootScope, $ionicBody, $compile, $timeout, $ionicPlatform, $ionicTempl
var self = this;
var modalEl = jqLite(self.modalEl);
+ // on iOS, clicks will sometimes bleed through/ghost click on underlying
+ // elements
+ $ionicClickBlock.show(600);
+
self.el.classList.remove('active');
modalEl.addClass('ng-leave');
@@ -46450,10 +46869,31 @@ function($scope, $element, $attrs, $q, $ionicConfig, $ionicHistory) {
self.updateBackButton = function() {
+ var ele;
if ((isBackShown && isNavBackShown && isBackEnabled) !== isBackElementShown) {
isBackElementShown = isBackShown && isNavBackShown && isBackEnabled;
- var backBtnEle = getEle(BACK_BUTTON);
- backBtnEle && backBtnEle.classList[ isBackElementShown ? 'remove' : 'add' ](HIDE);
+ ele = getEle(BACK_BUTTON);
+ ele && ele.classList[ isBackElementShown ? 'remove' : 'add' ](HIDE);
+ }
+
+ if (isBackEnabled) {
+ ele = ele || getEle(BACK_BUTTON);
+ if (ele) {
+ if (self.backButtonIcon !== $ionicConfig.backButton.icon()) {
+ ele = getEle(BACK_BUTTON + ' .icon');
+ if (ele) {
+ self.backButtonIcon = $ionicConfig.backButton.icon();
+ ele.className = 'icon ' + self.backButtonIcon;
+ }
+ }
+
+ if (self.backButtonText !== $ionicConfig.backButton.text()) {
+ ele = getEle(BACK_BUTTON + ' .back-text');
+ if (ele) {
+ ele.textContent = self.backButtonText = $ionicConfig.backButton.text();
+ }
+ }
+ }
}
};
@@ -47024,7 +47464,6 @@ function($scope, $element, $attrs, $compile, $timeout, $ionicNavBarDelegate, $io
var lastViewItemEle = {};
var leftButtonsEle, rightButtonsEle;
- //navEle[BACK_BUTTON] = self.createBackButtonElement(headerBarEle);
navEle[BACK_BUTTON] = createNavElement(BACK_BUTTON);
navEle[BACK_BUTTON] && headerBarEle.append(navEle[BACK_BUTTON]);
@@ -47048,6 +47487,8 @@ function($scope, $element, $attrs, $compile, $timeout, $ionicNavBarDelegate, $io
$element.append($compile(containerEle)($scope.$new()));
var headerBarCtrl = headerBarEle.data('$ionHeaderBarController');
+ headerBarCtrl.backButtonIcon = $ionicConfig.backButton.icon();
+ headerBarCtrl.backButtonText = $ionicConfig.backButton.text();
var headerBarInstance = {
isActive: isActive,
@@ -47241,13 +47682,24 @@ function($scope, $element, $attrs, $compile, $timeout, $ionicNavBarDelegate, $io
navBarTransition.direction = 'back';
navBarTransition.run(step);
},
- cancel: function(shouldAnimate, speed) {
+ cancel: function(shouldAnimate, speed, cancelData) {
navSwipeAttr(speed);
navBarAttr(leavingHeaderBar, 'active');
navBarAttr(enteringHeaderBar, 'cached');
navBarTransition.shouldAnimate = shouldAnimate;
navBarTransition.run(0);
self.activeTransition = navBarTransition = null;
+
+ var runApply;
+ if (cancelData.showBar !== self.showBar()) {
+ self.showBar(cancelData.showBar);
+ }
+ if (cancelData.showBackButton !== self.showBackButton()) {
+ self.showBackButton(cancelData.showBackButton);
+ }
+ if (runApply) {
+ $scope.$apply();
+ }
},
complete: function(shouldAnimate, speed) {
navSwipeAttr(speed);
@@ -47315,6 +47767,7 @@ function($scope, $element, $attrs, $compile, $timeout, $ionicNavBarDelegate, $io
self.visibleBar = function(shouldShow) {
if (shouldShow && !isVisible) {
$element.removeClass(CSS_HIDE);
+ self.align();
} else if (!shouldShow && isVisible) {
$element.addClass(CSS_HIDE);
}
@@ -47342,10 +47795,12 @@ function($scope, $element, $attrs, $compile, $timeout, $ionicNavBarDelegate, $io
* to show.
*/
self.showBackButton = function(shouldShow) {
- for (var x = 0; x < headerBars.length; x++) {
- headerBars[x].controller().showNavBack(!!shouldShow);
+ if (arguments.length) {
+ for (var x = 0; x < headerBars.length; x++) {
+ headerBars[x].controller().showNavBack(!!shouldShow);
+ }
+ $scope.$isBackButtonShown = !!shouldShow;
}
- $scope.$isBackButtonShown = !!shouldShow;
return $scope.$isBackButtonShown;
};
@@ -47357,7 +47812,12 @@ function($scope, $element, $attrs, $compile, $timeout, $ionicNavBarDelegate, $io
*/
self.showActiveBackButton = function(shouldShow) {
var headerBar = getOnScreenHeaderBar();
- headerBar && headerBar.controller().showBack(shouldShow);
+ if (headerBar) {
+ if (arguments.length) {
+ return headerBar.controller().showBack(shouldShow);
+ }
+ return headerBar.controller().showBack();
+ }
};
@@ -47751,13 +48211,25 @@ function($scope, $element, $attrs, $compile, $controller, $ionicNavBarDelegate,
*/
self.showBackButton = function(shouldShow) {
var associatedNavBarCtrl = getAssociatedNavBarCtrl();
- associatedNavBarCtrl && associatedNavBarCtrl.showActiveBackButton(shouldShow);
+ if (associatedNavBarCtrl) {
+ if (arguments.length) {
+ return associatedNavBarCtrl.showActiveBackButton(shouldShow);
+ }
+ return associatedNavBarCtrl.showActiveBackButton();
+ }
+ return true;
};
self.showBar = function(val) {
var associatedNavBarCtrl = getAssociatedNavBarCtrl();
- associatedNavBarCtrl && associatedNavBarCtrl.showBar(val);
+ if (associatedNavBarCtrl) {
+ if (arguments.length) {
+ return associatedNavBarCtrl.showBar(val);
+ }
+ return associatedNavBarCtrl.showBar();
+ }
+ return true;
};
@@ -47782,6 +48254,7 @@ function($scope, $element, $attrs, $compile, $controller, $ionicNavBarDelegate,
var viewTransition, associatedNavBarCtrl, backView;
var deregDragStart, deregDrag, deregRelease;
var windowWidth, startDragX, dragPoints;
+ var cancelData = {};
function onDragStart(ev) {
if (!isPrimary) return;
@@ -47803,6 +48276,11 @@ function($scope, $element, $attrs, $compile, $controller, $ionicNavBarDelegate,
dragPoints = [];
+ cancelData = {
+ showBar: self.showBar(),
+ showBackButton: self.showBackButton()
+ };
+
var switcher = $ionicViewSwitcher.create(self, registerData, backView, $ionicHistory.currentView(), true, false);
switcher.loadViewElements(registerData);
switcher.render(registerData);
@@ -47859,16 +48337,18 @@ function($scope, $element, $attrs, $compile, $controller, $ionicNavBarDelegate,
disableAnimation = (releaseSwipeCompletion < 0.03 || releaseSwipeCompletion > 0.97);
if (isSwipingRight && (releaseSwipeCompletion > 0.5 || velocity > 0.1)) {
+ // complete view transition on release
var speed = (velocity > 0.5 || velocity < 0.05 || releaseX > windowWidth - 45) ? 'fast' : 'slow';
navSwipeAttr(disableAnimation ? '' : speed);
backView.go();
associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.complete(!disableAnimation, speed);
} else {
+ // cancel view transition on release
navSwipeAttr(disableAnimation ? '' : 'fast');
disableRenderStartViewId = null;
viewTransition.cancel(!disableAnimation);
- associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.cancel(!disableAnimation, 'fast');
+ associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.cancel(!disableAnimation, 'fast', cancelData);
disableAnimation = null;
}
@@ -49836,7 +50316,10 @@ function CollectionRepeatDirective($ionicCollectionManager, $parse, $window, $$r
scrollCtrl.$element.on('scroll.resize', refreshDimensions);
angular.element($window).on('resize', onResize);
- var unlistenToExposeAside = $rootScope.$on('$ionicExposeAside', onResize);
+ var unlistenToExposeAside = $rootScope.$on('$ionicExposeAside', ionic.animationFrameThrottle(function() {
+ scrollCtrl.scrollView.resize();
+ onResize();
+ }));
$timeout(refreshDimensions, 0, false);
function onResize() {
@@ -49912,7 +50395,7 @@ function CollectionRepeatDirective($ionicCollectionManager, $parse, $window, $$r
renderBuffer: renderBuffer,
scope: scope,
scrollView: scrollCtrl.scrollView,
- transclude: transclude,
+ transclude: transclude
}));
}
@@ -49956,16 +50439,15 @@ function CollectionRepeatDirective($ionicCollectionManager, $parse, $window, $$r
//4) Dynamic Mode
// - The user provides a dynamic expression for the width or height. This is re-evaluated
// for every item, stored on the `.getValue()` field.
- if (!heightExpr && !widthExpr) {
- heightData.computed = widthData.computed = true;
+ if (heightExpr) {
+ parseDimensionAttr(heightExpr, heightData);
} else {
- if (heightExpr) {
- parseDimensionAttr(heightExpr, heightData);
- } else {
- heightData.computed = true;
- }
- if (!widthExpr) widthExpr = '"100%"';
+ heightData.computed = true;
+ }
+ if (widthExpr) {
parseDimensionAttr(widthExpr, widthData);
+ } else {
+ widthData.computed = true;
}
}
@@ -50085,7 +50567,7 @@ function CollectionRepeatDirective($ionicCollectionManager, $parse, $window, $$r
RepeatManagerFactory.$inject = ['$rootScope', '$window', '$$rAF'];
function RepeatManagerFactory($rootScope, $window, $$rAF) {
- var EMPTY_DIMENSION = { primaryPos: 0, secondaryPos: 0, primarySize: 0, secondarySize: 0 };
+ var EMPTY_DIMENSION = { primaryPos: 0, secondaryPos: 0, primarySize: 0, secondarySize: 0, rowPrimarySize: 0 };
return function RepeatController(options) {
var afterItemsNode = options.afterItemsNode;
@@ -50173,6 +50655,7 @@ function RepeatManagerFactory($rootScope, $window, $$rAF) {
scrollView.__$callback = scrollView.__callback;
scrollView.__callback = function(transformLeft, transformTop, zoom, wasResize) {
var scrollValue = view.getScrollValue();
+ if(window.d)dump('_-callback render', scrollValue, view.scrollPrimarySize + renderAfterBoundary);
if (renderStartIndex === -1 ||
scrollValue + view.scrollPrimarySize > renderAfterBoundary ||
scrollValue < renderBeforeBoundary) {
@@ -50332,6 +50815,7 @@ function RepeatManagerFactory($rootScope, $window, $$rAF) {
if (item.secondarySize !== dim.secondarySize || item.primarySize !== dim.primarySize) {
item.node.style.cssText = item.node.style.cssText
.replace(WIDTH_HEIGHT_REGEX, WIDTH_HEIGHT_TEMPLATE_STR
+ //TODO fix item.primarySize + 1 hack
.replace(PRIMARY, (item.primarySize = dim.primarySize) + 1)
.replace(SECONDARY, (item.secondarySize = dim.secondarySize))
);
@@ -50549,14 +51033,22 @@ function RepeatManagerFactory($rootScope, $window, $$rAF) {
dim.secondaryPos = prevDimension.secondaryPos + prevDimension.secondarySize;
if (i === 0 || dim.secondaryPos + dim.secondarySize > self.scrollSecondarySize) {
- dim.rowStartIndex = i;
dim.secondaryPos = 0;
dim.primarySize = self.getItemPrimarySize(i, data[i]);
- dim.primaryPos = prevDimension.primaryPos + prevDimension.primarySize;
+ dim.primaryPos = prevDimension.primaryPos + prevDimension.rowPrimarySize;
+
+ dim.rowStartIndex = i;
+ dim.rowPrimarySize = dim.primarySize;
} else {
- dim.rowStartIndex = prevDimension.rowStartIndex;
- dim.primarySize = prevDimension.primarySize;
+ dim.primarySize = self.getItemPrimarySize(i, data[i]);
dim.primaryPos = prevDimension.primaryPos;
+ dim.rowStartIndex = prevDimension.rowStartIndex;
+
+ dimensions[dim.rowStartIndex].rowPrimarySize = dim.rowPrimarySize = Math.max(
+ dimensions[dim.rowStartIndex].rowPrimarySize,
+ dim.primarySize
+ );
+ dim.rowPrimarySize = Math.max(dim.primarySize, dim.rowPrimarySize);
}
}
}
@@ -50620,7 +51112,7 @@ function RepeatManagerFactory($rootScope, $window, $$rAF) {
// scrolling down
} else if (scrollValue >= oldScrollValue) {
for (i = oldRenderStartIndex, len = data.length; i < len; i++) {
- if ((dim = this.getDimensions(i)) && dim.primaryPos + dim.primarySize >= scrollValue) {
+ if ((dim = this.getDimensions(i)) && dim.primaryPos + dim.rowPrimarySize >= scrollValue) {
break;
}
}
@@ -50641,7 +51133,7 @@ function RepeatManagerFactory($rootScope, $window, $$rAF) {
// -- Calculate renderEndIndex
var lastRowDim;
for (i = renderStartIndex + 1, len = data.length; i < len; i++) {
- if ((dim = this.getDimensions(i)) && dim.primaryPos + dim.primarySize > scrollValueEnd) {
+ if ((dim = this.getDimensions(i)) && dim.primaryPos + dim.rowPrimarySize > scrollValueEnd) {
// Go all the way to the end of the row if we're in a grid
if (isGridView) {
@@ -50657,7 +51149,7 @@ function RepeatManagerFactory($rootScope, $window, $$rAF) {
renderEndIndex = Math.min(i, data.length - 1);
renderAfterBoundary = renderEndIndex !== -1 ?
- ((dim = this.getDimensions(renderEndIndex)).primaryPos + dim.primarySize) :
+ ((dim = this.getDimensions(renderEndIndex)).primaryPos + (dim.rowPrimarySize || dim.primarySize)) :
-1;
oldScrollValue = scrollValue;
@@ -50670,8 +51162,6 @@ function RepeatManagerFactory($rootScope, $window, $$rAF) {
}
-
-
/**
* @ngdoc directive
* @name ionContent
@@ -53640,7 +54130,7 @@ IonicModule
* @param {boolean=} does-continue Whether the slide box should loop.
* @param {boolean=} auto-play Whether the slide box should automatically slide. Default true if does-continue is true.
* @param {number=} slide-interval How many milliseconds to wait to change slides (if does-continue is true). Defaults to 4000.
- * @param {boolean=} show-pager Whether a pager should be shown for this slide box. Accepts expressions via `show-pager="{{shouldShow()}}"`.
+ * @param {boolean=} show-pager Whether a pager should be shown for this slide box. Accepts expressions via `show-pager="{{shouldShow()}}"`. Defaults to true.
* @param {expression=} pager-click Expression to call when a pager is clicked (if show-pager is true). Is passed the 'index' variable.
* @param {expression=} on-slide-changed Expression called whenever the slide is changed. Is passed an '$index' variable.
* @param {expression=} active-slide Model to bind the current slide to.
@@ -53651,7 +54141,8 @@ IonicModule
'$compile',
'$ionicSlideBoxDelegate',
'$ionicHistory',
-function($timeout, $compile, $ionicSlideBoxDelegate, $ionicHistory) {
+ '$ionicScrollDelegate',
+function($timeout, $compile, $ionicSlideBoxDelegate, $ionicHistory, $ionicScrollDelegate) {
return {
restrict: 'E',
replace: true,
@@ -53691,9 +54182,25 @@ function($timeout, $compile, $ionicSlideBoxDelegate, $ionicHistory) {
$scope.activeSlide = slideIndex;
// Try to trigger a digest
$timeout(function() {});
+ },
+ onDrag: function() {
+ freezeAllScrolls(true);
+ },
+ onDragEnd: function() {
+ freezeAllScrolls(false);
}
});
+ function freezeAllScrolls(shouldFreeze) {
+ if (shouldFreeze && !_this.isScrollFreeze) {
+ $ionicScrollDelegate.freezeAllScrolls(shouldFreeze);
+
+ } else if (!shouldFreeze && _this.isScrollFreeze) {
+ $ionicScrollDelegate.freezeAllScrolls(false);
+ }
+ _this.isScrollFreeze = shouldFreeze;
+ }
+
slider.enableSlide($scope.$eval($attrs.disableScroll) !== true);
$scope.$watch('activeSlide', function(nv) {
@@ -53743,6 +54250,12 @@ function($timeout, $compile, $ionicSlideBoxDelegate, $ionicHistory) {
'
',
link: function($scope, $element, $attr, slideBoxCtrl) {
+ // if showPager is undefined, show the pager
+ if (!isDefined($attr.showPager)) {
+ $scope.showPager = true;
+ getPager().toggleClass('hide', !true);
+ }
+
$attr.$observe('showPager', function(show) {
show = $scope.$eval(show);
getPager().toggleClass('hide', !show);
diff --git a/release/js/ionic.bundle.min.js b/release/js/ionic.bundle.min.js
index 58b923bda21..995c86aa3be 100644
--- a/release/js/ionic.bundle.min.js
+++ b/release/js/ionic.bundle.min.js
@@ -9,7 +9,7 @@
* Copyright 2014 Drifty Co.
* http://drifty.com/
*
- * Ionic, v1.0.0-rc.2
+ * Ionic, v1.0.0-rc.3
* A powerful HTML5 mobile app framework.
* http://ionicframework.com/
*
@@ -19,9 +19,9 @@
*
*/
-!function(){function e(e,t,n){t!==!1?z.addEventListener(e,Z[e],n):z.removeEventListener(e,Z[e])}function t(e){var t=T(e.target),i=E(t);if(ionic.tap.requiresNativeClick(i)||F)return!1;var o=ionic.tap.pointerCoord(e);n("click",i,o.x,o.y),h(i)}function n(e,t,n,i){var o=document.createEvent("MouseEvents");o.initMouseEvent(e,!0,!0,window,1,0,0,n,i,!1,!1,!1,!1,0,null),o.isIonicTap=!0,t.dispatchEvent(o)}function i(e){return("submit"!=e.target.type||0!==e.detail)&&(ionic.scroll.isScrolling&&ionic.tap.containsOrIsTextInput(e.target)||!e.isIonicTap&&!ionic.tap.requiresNativeClick(e.target))?(e.stopPropagation(),ionic.tap.isLabelWithTextInput(e.target)||e.preventDefault(),!1):void 0}function o(t){if(!t.isIonicTap&&!_(t)){if(k)return t.stopPropagation(),ionic.tap.isTextInput(t.target)&&$===t.target||/^(select|option)$/i.test(t.target.tagName)||t.preventDefault(),!1;F=!1,X=ionic.tap.pointerCoord(t),e("mousemove"),ionic.activator.start(t)}}function r(n){return k?(n.stopPropagation(),n.preventDefault(),!1):_(n)||/^(select|option)$/i.test(n.target.tagName)?!1:(v(n)||t(n),e("mousemove",!1),ionic.activator.end(),void(F=!1))}function s(t){return v(t)?(e("mousemove",!1),ionic.activator.end(),F=!0,!1):void 0}function a(t){if(!_(t)&&(F=!1,d(),X=ionic.tap.pointerCoord(t),e(U),ionic.activator.start(t),ionic.Platform.isIOS()&&ionic.tap.isLabelWithTextInput(t.target))){var n=E(T(t.target));n!==H&&t.preventDefault()}}function l(e){_(e)||(d(),v(e)||(t(e),/^(select|option)$/i.test(e.target.tagName)&&e.preventDefault()),$=e.target,u())}function c(t){return v(t)?(F=!0,e(U,!1),ionic.activator.end(),!1):void 0}function u(){e(U,!1),ionic.activator.end(),F=!1}function d(){k=!0,clearTimeout(Y),Y=setTimeout(function(){k=!1},600)}function _(e){return e.isTapHandled?!0:(e.isTapHandled=!0,ionic.scroll.isScrolling&&ionic.tap.containsOrIsTextInput(e.target)?(e.preventDefault(),!0):void 0)}function h(e){W=null;var t=!1;"SELECT"==e.tagName?(n("mousedown",e,0,0),e.focus&&e.focus(),t=!0):g()===e?t=!0:/^(input|textarea)$/i.test(e.tagName)||e.isContentEditable?(t=!0,e.focus&&e.focus(),e.value=e.value,k&&(W=e)):f(),t&&(g(e),ionic.trigger("ionic.focusin",{target:e},!0))}function f(){var e=g();e&&(/^(input|textarea|select)$/i.test(e.tagName)||e.isContentEditable)&&e.blur(),g(null)}function p(e){k&&ionic.tap.isTextInput(g())&&ionic.tap.isTextInput(W)&&W!==e.target&&(W.focus(),W=null),ionic.scroll.isScrolling=!1}function m(){g(null)}function g(e){return arguments.length&&(H=e),H||document.activeElement}function v(e){if(!e||1!==e.target.nodeType||!X||0===X.x&&0===X.y)return!1;var t=ionic.tap.pointerCoord(e),n=!(!e.target.classList||!e.target.classList.contains||"function"!=typeof e.target.classList.contains),i=n&&e.target.classList.contains("button")?B:q;return Math.abs(X.x-t.x)>i||Math.abs(X.y-t.y)>i}function T(e,t){for(var n=e,i=0;6>i&&n;i++){if("LABEL"===n.tagName)return n;n=n.parentElement}return t!==!1?e:void 0}function E(e){if(e&&"LABEL"===e.tagName){if(e.control)return e.control;if(e.querySelector){var t=e.querySelector("input,textarea,select");if(t)return t}}return e}function S(){G()?(window.addEventListener("native.keyboardshow",w),window.addEventListener("native.keyboardhide",L),window.addEventListener("native.showkeyboard",w),window.addEventListener("native.hidekeyboard",L)):document.body.addEventListener("focusout",L),document.body.addEventListener("ionic.focusin",b),document.body.addEventListener("focusin",b),document.body.addEventListener("orientationchange",I),window.navigator.msPointerEnabled?document.removeEventListener("MSPointerDown",S):document.removeEventListener("touchstart",S)}function w(e){clearTimeout(j),ionic.keyboard.height=e.keyboardHeight}function b(e){e.target&&!e.target.readOnly&&ionic.tap.isKeyboardElement(e.target)&&P(e.target)&&(document.addEventListener("keydown",M,!1),document.body.scrollTop=0,document.body.querySelector(".scroll-content").scrollTop=0,K=e.target,y(e))}function y(e){clearTimeout(J),clearTimeout(j),J=setTimeout(function(){if(!(tt+350>Date.now())){tt=Date.now();var t,n=K.getBoundingClientRect(),i=0;Q=setInterval(function(){t=C(),i>10&&(clearInterval(Q),t=275),t&&(clearInterval(Q),D(e.target,n.top,n.bottom,et,t)),i++},100)}},32)}function D(e,t,n,i,o){var r={target:e,elementTop:Math.round(t),elementBottom:Math.round(n),keyboardHeight:o,viewportHeight:i};return r.hasPlugin=G(),r.contentHeight=i-o,r.isElementUnderKeyboard=r.elementBottom>r.contentHeight,ionic.keyboard.isOpen=!0,K=e,ionic.trigger("scrollChildIntoView",r,!0),ionic.requestAnimationFrame(function(){document.body.classList.add(nt)}),window.navigator.msPointerEnabled?document.addEventListener("MSPointerMove",N,!1):document.addEventListener("touchmove",N,!1),r}function L(){clearTimeout(j),j=setTimeout(ionic.keyboard.hide,350)}function x(){A()>et&&(et=A())}function M(e){ionic.scroll.isScrolling&&N(e)}function N(e){"TEXTAREA"!==e.target.tagName&&e.preventDefault()}function I(){var e=A();if(e===et)var t=0,n=setInterval(function(){t>10&&clearInterval(n),e=A(),e!==et&&(ionic.keyboard.landscape=et>e?!0:!1,et=e,clearInterval(n)),t++},50);else et=e}function C(){return ionic.keyboard.height?ionic.keyboard.height:ionic.Platform.isAndroid()?ionic.Platform.isFullScreen?275:A()