angular-snapscroll
Advanced tools
Comparing version 0.3.1 to 1.0.0
{ | ||
"name": "angular-snapscroll", | ||
"version": "0.3.1", | ||
"version": "1.0.0", | ||
"authors": [ | ||
@@ -28,5 +28,6 @@ "Joel Mukuthu <joelmukuthu@gmail.com>" | ||
"dependencies": { | ||
"angular": "^1.2.24" | ||
"angular": "^1.2.24", | ||
"angular-scrollie": "^1.0.0" | ||
}, | ||
"homepage": "https://github.com/joelmukuthu/angular-snapscroll.git" | ||
} |
@@ -0,1 +1,21 @@ | ||
## 1.0.0 | ||
### Breaking changes | ||
- Dependency on [angular-scrollie](https://github.com/joelmukuthu/angular-scrollie) | ||
- [`before-snap`](DOCS.md#before-snap) and [`after-snap`](DOCS.md#after-snap) are | ||
now only called if snapIndex changes | ||
### Features | ||
- Support overriding the next snapIndex by returning a number from | ||
[`before-snap`](DOCS.md#before-snap) | ||
- Support [disabling/enabling](DOCS.md#snapscroll-directive) snapscroll | ||
programmatically | ||
- Support child elements whose height is greater than the snapscroll element | ||
- Support for arrow keys using [`enable-arrow-keys`](DOCS.md#enable-arrow-keys) | ||
### Fixes | ||
- [`snap-index`](DOCS.md#snap-index) is not initialized if the element is not | ||
scrollable | ||
- Ensure snapscroll never tries to set scrollTop to a value that is out of bounds | ||
## 0.3.0 | ||
@@ -2,0 +22,0 @@ |
/** | ||
* angular-snapscroll | ||
* Version: 0.3.1 | ||
* Version: 1.0.0 | ||
* (c) 2014-2016 Joel Mukuthu | ||
* MIT License | ||
* Built on: 16-07-2016 00:59:03 GMT+0200 | ||
* Built on: 23-09-2016 17:15:38 GMT+0200 | ||
**/ | ||
(function () { | ||
'use strict'; | ||
function easeInOutQuad(t, b, c, d) { | ||
t /= d/2; | ||
if (t < 1) { | ||
return c/2*t*t + b; | ||
} | ||
t--; | ||
return -c/2 * (t*(t-2) - 1) + b; | ||
} | ||
angular | ||
.module('snapscroll', ['wheelie']) | ||
.value('defaultSnapscrollScrollEasing', easeInOutQuad) | ||
.value('defaultSnapscrollScrollDelay', 250) | ||
.value('defaultSnapscrollSnapDuration', 800) | ||
.value('defaultSnapscrollResizeDelay', 400) | ||
.value('defaultSnapscrollBindScrollTimeout', 400); | ||
angular | ||
.module('snapscroll', ['wheelie', 'scrollie']) | ||
.value('defaultSnapscrollScrollEasing', undefined) | ||
.value('defaultSnapscrollScrollDelay', 250) | ||
.value('defaultSnapscrollSnapDuration', 800) | ||
.value('defaultSnapscrollResizeDelay', 400) | ||
.value('defaultSnapscrollBindScrollTimeout', 400); | ||
})(); | ||
(function () { | ||
'use strict'; | ||
angular.module('snapscroll') | ||
.directive('fitWindowHeight', [ | ||
'$window', | ||
'$timeout', | ||
'defaultSnapscrollResizeDelay', | ||
function ( | ||
$window, | ||
$timeout, | ||
defaultSnapscrollResizeDelay | ||
) { | ||
return { | ||
restrict: 'A', | ||
require: 'snapscroll', | ||
link: function (scope, element, attributes, snapscroll) { | ||
var windowElement, | ||
resizePromise, | ||
resizeDelay = attributes.resizeDelay; | ||
angular.module('snapscroll') | ||
.directive('fitWindowHeight', ['$window', '$timeout', 'defaultSnapscrollResizeDelay', | ||
function ($window, $timeout, defaultSnapscrollResizeDelay) { | ||
return { | ||
restrict: 'A', | ||
require: 'snapscroll', | ||
link: function (scope, element, attributes, snapscroll) { | ||
var windowElement, | ||
resizePromise, | ||
resizeDelay = attributes.resizeDelay; | ||
function onWindowResize() { | ||
if (resizeDelay === false) { | ||
snapscroll.setSnapHeight($window.innerHeight); | ||
} else { | ||
$timeout.cancel(resizePromise); | ||
resizePromise = $timeout(function () { | ||
snapscroll.setSnapHeight($window.innerHeight); | ||
}, resizeDelay); | ||
} | ||
} | ||
function onWindowResize() { | ||
if (resizeDelay === false) { | ||
snapscroll.setSnapHeight($window.innerHeight); | ||
} else { | ||
$timeout.cancel(resizePromise); | ||
resizePromise = $timeout(function () { | ||
snapscroll.setSnapHeight($window.innerHeight); | ||
}, resizeDelay); | ||
} | ||
} | ||
function init() { | ||
if (resizeDelay === 'false') { | ||
resizeDelay = false; | ||
} else { | ||
resizeDelay = parseInt(resizeDelay, 10); | ||
if (isNaN(resizeDelay)) { | ||
resizeDelay = defaultSnapscrollResizeDelay; | ||
} | ||
} | ||
function init() { | ||
if (resizeDelay === 'false') { | ||
resizeDelay = false; | ||
} else { | ||
resizeDelay = parseInt(resizeDelay, 10); | ||
if (isNaN(resizeDelay)) { | ||
resizeDelay = defaultSnapscrollResizeDelay; | ||
} | ||
} | ||
// set initial snapHeight | ||
snapscroll.setSnapHeight($window.innerHeight); | ||
// set initial snapHeight | ||
snapscroll.setSnapHeight($window.innerHeight); | ||
// update snapHeight on window resize | ||
windowElement = angular.element($window); | ||
windowElement.on('resize', onWindowResize); | ||
scope.$on('$destroy', function () { | ||
windowElement.off('resize'); | ||
}); | ||
} | ||
// update snapHeight on window resize | ||
windowElement = angular.element($window); | ||
windowElement.on('resize', onWindowResize); | ||
scope.$on('$destroy', function () { | ||
windowElement.off('resize'); | ||
}); | ||
} | ||
init(); | ||
} | ||
}; | ||
}]); | ||
init(); | ||
} | ||
}; | ||
}]); | ||
})(); | ||
(function () { | ||
'use strict'; | ||
function isNumber(value) { | ||
return angular.isNumber(value) && !isNaN(value); | ||
} | ||
var scopeObject = { | ||
snapIndex: '=?', | ||
snapHeight: '=?', | ||
beforeSnap: '&', | ||
afterSnap: '&', | ||
snapAnimation: '=?' | ||
}; | ||
var isDefined = angular.isDefined; | ||
var isUndefined = angular.isUndefined; | ||
var isFunction = angular.isFunction; | ||
var forEach = angular.forEach; | ||
var controller = ['$scope', function ($scope) { | ||
this.setSnapHeight = function (height) { | ||
$scope.snapHeight = height; | ||
var scopeObject = { | ||
enabled: '=snapscroll', | ||
snapIndex: '=?', | ||
snapHeight: '=?', | ||
beforeSnap: '&', | ||
afterSnap: '&', | ||
snapAnimation: '=?' | ||
}; | ||
}]; | ||
var isNumber = function(value) { | ||
return angular.isNumber(value) && !isNaN(value); | ||
}; | ||
var controller = ['$scope', function ($scope) { | ||
this.setSnapHeight = function (height) { | ||
$scope.snapHeight = height; | ||
}; | ||
}]; | ||
var watchSnapHeight = function (scope, callback) { | ||
scope.$watch('snapHeight', function (snapHeight, previousSnapHeight) { | ||
if (angular.isUndefined(snapHeight)) { | ||
return; | ||
} | ||
if (!isNumber(snapHeight)) { | ||
if (isNumber(previousSnapHeight)) { | ||
scope.snapHeight = previousSnapHeight; | ||
} | ||
return; | ||
} | ||
if (angular.isFunction(callback)) { | ||
callback(snapHeight); | ||
} | ||
}); | ||
}; | ||
var snapscrollAsAnAttribute = [ | ||
'$timeout', | ||
'$document', | ||
'wheelie', | ||
'scrollie', | ||
'defaultSnapscrollScrollEasing', | ||
'defaultSnapscrollScrollDelay', | ||
'defaultSnapscrollSnapDuration', | ||
'defaultSnapscrollBindScrollTimeout', | ||
function ( | ||
$timeout, | ||
$document, | ||
wheelie, | ||
scrollie, | ||
defaultSnapscrollScrollEasing, | ||
defaultSnapscrollScrollDelay, | ||
defaultSnapscrollSnapDuration, | ||
defaultSnapscrollBindScrollTimeout | ||
) { | ||
return { | ||
restrict: 'A', | ||
scope: scopeObject, | ||
controller: controller, | ||
link: function (scope, element, attributes) { | ||
function getChildren() { | ||
return element.children(); | ||
} | ||
var watchSnapIndex = function (scope, callback) { | ||
scope.$watch('snapIndex', function (snapIndex, previousSnapIndex) { | ||
if (angular.isUndefined(snapIndex)) { | ||
scope.snapIndex = 0; | ||
return; | ||
} | ||
if (!isNumber(snapIndex)) { | ||
if (isNumber(previousSnapIndex)) { | ||
scope.snapIndex = previousSnapIndex; | ||
} else { | ||
scope.snapIndex = 0; | ||
} | ||
return; | ||
} | ||
if (snapIndex % 1 !== 0) { | ||
scope.snapIndex = Math.round(snapIndex); | ||
return; | ||
} | ||
if (scope.ignoreThisSnapIndexChange) { | ||
scope.ignoreThisSnapIndexChange = undefined; | ||
return; | ||
} | ||
if (!scope.isValid(snapIndex)) { | ||
scope.ignoreThisSnapIndexChange = true; | ||
scope.snapIndex = previousSnapIndex; | ||
scope.snapDirection = 'none'; | ||
return; | ||
} | ||
if (scope.beforeSnap({snapIndex: snapIndex}) === false) { | ||
scope.ignoreThisSnapIndexChange = true; | ||
scope.snapIndex = previousSnapIndex; | ||
return; | ||
} | ||
if (angular.isFunction(callback)) { | ||
if (snapIndex > previousSnapIndex) { | ||
scope.snapDirection = 'up'; | ||
} else if (snapIndex < previousSnapIndex) { | ||
scope.snapDirection = 'down'; | ||
} | ||
callback(snapIndex, function () { | ||
scope.snapDirection = 'none'; | ||
scope.afterSnap({snapIndex: snapIndex}); | ||
}); | ||
} | ||
}); | ||
}; | ||
function getHeight(domElement) { | ||
return domElement.offsetHeight; | ||
} | ||
var initWheelEvents = function (wheelie, scope, element) { | ||
function maybePreventBubbling(e, bubbleUp) { | ||
if (!bubbleUp) { | ||
e.stopPropagation(); | ||
} | ||
} | ||
function getChildHeight(snapIndex) { | ||
return getHeight(getChildren()[snapIndex]); | ||
} | ||
wheelie.bind(element, { | ||
up: function (e) { | ||
e.preventDefault(); | ||
function getSnapHeight() { | ||
return getHeight(element[0]); | ||
} | ||
var bubbleUp; | ||
if (scope.snapDirection !== 'down') { | ||
if (scope.snapIndex - 1 < scope.snapIndexMin()) { | ||
bubbleUp = true; | ||
} else { | ||
bubbleUp = false; | ||
scope.$apply(function () { | ||
scope.snapIndex -= 1; | ||
}); | ||
} | ||
} | ||
function getScrollHeight() { | ||
return element[0].scrollHeight; | ||
} | ||
maybePreventBubbling(e, bubbleUp); | ||
}, | ||
down: function (e) { | ||
e.preventDefault(); | ||
function rectifyScrollTop(scrollTop) { | ||
var maxScrollTop = getScrollHeight() - getSnapHeight(); | ||
if (scrollTop > maxScrollTop) { | ||
return maxScrollTop; | ||
} | ||
return scrollTop; | ||
} | ||
var bubbleUp; | ||
if (scope.snapDirection !== 'up') { | ||
if (scope.snapIndex + 1 > scope.scopeIndexMax()) { | ||
bubbleUp = true; | ||
} else { | ||
bubbleUp = false; | ||
scope.$apply(function () { | ||
scope.snapIndex += 1; | ||
}); | ||
} | ||
} | ||
function getScrollTop(compositeIndex, previousCompositeIndex) { | ||
var snapIndex = compositeIndex[0]; | ||
var innerSnapIndex = compositeIndex[1]; | ||
maybePreventBubbling(e, bubbleUp); | ||
} | ||
}); | ||
var scrollTop = 0; | ||
var children = getChildren(); | ||
for (var i = 0; i < snapIndex; i++) { | ||
scrollTop += getHeight(children[i]); | ||
} | ||
scope.$on('$destroy', function () { | ||
wheelie.unbind(element); | ||
}); | ||
}; | ||
if (innerSnapIndex === 0) { | ||
return rectifyScrollTop(scrollTop); | ||
} | ||
var snapscrollAsAnAttribute = ['$timeout', 'scroll', 'wheelie', 'defaultSnapscrollScrollDelay', 'defaultSnapscrollSnapDuration', 'defaultSnapscrollBindScrollTimeout', | ||
function ($timeout, scroll, wheelie, defaultSnapscrollScrollDelay, defaultSnapscrollSnapDuration, defaultSnapscrollBindScrollTimeout) { | ||
return { | ||
restrict: 'A', | ||
scope: scopeObject, | ||
controller: controller, | ||
link: function (scope, element, attributes) { | ||
var init, | ||
snapTo, | ||
onScroll, | ||
bindScroll, | ||
scrollBound, | ||
unbindScroll, | ||
scrollPromise, | ||
bindScrollPromise, | ||
snapEasing = attributes.snapEasing, | ||
scrollDelay = attributes.scrollDelay, | ||
snapDuration = attributes.snapDuration, | ||
preventSnappingAfterManualScroll = angular.isDefined(attributes.preventSnappingAfterManualScroll); | ||
var snapHeight = getSnapHeight(); | ||
var childHeight = getHeight(children[snapIndex]); | ||
var innerScrollTop; | ||
if (isDefined(previousCompositeIndex) && | ||
innerSnapIndex < previousCompositeIndex[1]) { | ||
innerScrollTop = childHeight; | ||
for (var j = innerSnapIndex; j >= 0; j--) { | ||
innerScrollTop -= snapHeight; | ||
} | ||
} else { | ||
innerScrollTop = 0; | ||
for (var k = 0; k < innerSnapIndex; k++) { | ||
innerScrollTop += snapHeight; | ||
} | ||
var overflow = innerScrollTop + snapHeight - childHeight; | ||
if (overflow > 0) { | ||
innerScrollTop -= overflow; | ||
} | ||
} | ||
function getScrollTop(index) { | ||
var snaps = element.children(); | ||
var combinedHeight = 0; | ||
for (var i = 0; i < index; i++) { | ||
combinedHeight += parseInt(snaps[i].offsetHeight, 10); | ||
} | ||
return combinedHeight; | ||
} | ||
return rectifyScrollTop(scrollTop + innerScrollTop); | ||
} | ||
snapTo = function (index, afterSnap) { | ||
var args, | ||
top = getScrollTop(index); | ||
if (scope.snapAnimation) { | ||
if (angular.isDefined(snapEasing)) { | ||
args = [element, top, snapDuration, snapEasing]; | ||
} else { | ||
args = [element, top, snapDuration]; | ||
} | ||
} else { | ||
args = [element, top]; | ||
} | ||
if (!preventSnappingAfterManualScroll && scrollBound) { | ||
unbindScroll(); | ||
} | ||
scroll.to.apply(scroll, args).then(function () { | ||
if (angular.isFunction(afterSnap)) { | ||
afterSnap(); | ||
} | ||
if (!preventSnappingAfterManualScroll) { | ||
// bind scroll after a timeout | ||
$timeout.cancel(bindScrollPromise); | ||
bindScrollPromise = $timeout(bindScroll, defaultSnapscrollBindScrollTimeout); | ||
} | ||
}); | ||
}; | ||
function snapTo(compositeIndex, previousCompositeIndex) { | ||
var snapIndex = compositeIndex[0]; | ||
var isSnapIndexChanged = isUndefined(previousCompositeIndex) || | ||
snapIndex !== previousCompositeIndex[0]; | ||
if (isSnapIndexChanged) { | ||
var returnValue = scope.beforeSnap({ | ||
snapIndex: snapIndex | ||
}); | ||
if (returnValue === false) { | ||
if (isDefined(previousCompositeIndex)) { | ||
scope.ignoreCompositeIndexChange = true; | ||
scope.compositeIndex = previousCompositeIndex; | ||
} | ||
return; | ||
} | ||
if (isNumber(returnValue)) { | ||
scope.snapIndex = returnValue; | ||
return; | ||
} | ||
} | ||
function getSnapIndex(scrollTop) { | ||
var snapIndex = -1, | ||
snaps = element.children(), | ||
lastSnapHeight; | ||
while (scrollTop > 0) { | ||
scrollTop -= lastSnapHeight = snaps[++snapIndex].offsetHeight; | ||
} | ||
if ((lastSnapHeight / 2) >= -scrollTop) { | ||
snapIndex += 1; | ||
} | ||
return snapIndex; | ||
} | ||
return scrollTo(getScrollTop( | ||
compositeIndex, | ||
previousCompositeIndex | ||
)).then(function () { | ||
if (isSnapIndexChanged) { | ||
scope.afterSnap({ | ||
snapIndex: snapIndex | ||
}); | ||
} | ||
}); | ||
} | ||
onScroll = function () { | ||
var snap = function () { | ||
var newSnapIndex = getSnapIndex(element[0].scrollTop); | ||
if (scope.snapIndex === newSnapIndex) { | ||
snapTo(newSnapIndex); | ||
} else { | ||
scope.$apply(function () { | ||
scope.snapIndex = newSnapIndex; | ||
}); | ||
} | ||
}; | ||
scroll.stop(element); | ||
if (scrollDelay === false) { | ||
snap(); | ||
} else { | ||
$timeout.cancel(scrollPromise); | ||
scrollPromise = $timeout(snap, scrollDelay); | ||
} | ||
}; | ||
function getCurrentScrollTop() { | ||
return element[0].scrollTop; | ||
} | ||
bindScroll = function () { | ||
// if the bindScroll timeout expires while snapping is ongoing, restart the timer | ||
if (scope.snapDirection !== 'none') { | ||
bindScrollPromise = $timeout(bindScroll, defaultSnapscrollBindScrollTimeout); | ||
return; | ||
} | ||
element.on('scroll', onScroll); | ||
scrollBound = true; | ||
}; | ||
function scrollTo(scrollTop) { | ||
var args; | ||
if (!scope.snapAnimation) { | ||
args = [ | ||
element, | ||
scrollTop | ||
]; | ||
} else if (isUndefined(scope.snapEasing)) { | ||
// TODO: add tests for this. Will require refactoring | ||
// the default values into an object, which is a good | ||
// change anyway | ||
args = [ | ||
element, | ||
scrollTop, | ||
scope.snapDuration | ||
]; | ||
} else { | ||
args = [ | ||
element, | ||
scrollTop, | ||
scope.snapDuration, | ||
scope.snapEasing | ||
]; | ||
} | ||
unbindScroll = function () { | ||
element.off('scroll', onScroll); | ||
scrollBound = false; | ||
}; | ||
var currentScrollTop = getCurrentScrollTop(); | ||
if (scrollTop > currentScrollTop) { | ||
scope.snapDirection = 'down'; | ||
} else if (scrollTop < currentScrollTop) { | ||
scope.snapDirection = 'up'; | ||
} else { | ||
scope.snapDirection = 'same'; | ||
} | ||
init = function () { | ||
if (scrollDelay === 'false') { | ||
scrollDelay = false; | ||
} else { | ||
scrollDelay = parseInt(scrollDelay, 10); | ||
if (isNaN(scrollDelay)) { | ||
scrollDelay = defaultSnapscrollScrollDelay; | ||
} | ||
} | ||
unbindScroll(); | ||
return scrollie.to.apply(scrollie, args).then(function () { | ||
scope.snapDirection = undefined; | ||
bindScrollAfterDelay(); | ||
}); | ||
} | ||
if (angular.isDefined(snapEasing)) { | ||
snapEasing = scope.$parent.$eval(snapEasing); | ||
} | ||
function isScrollable() { | ||
var snapHeight = getSnapHeight(); | ||
if (!snapHeight) { | ||
return false; | ||
} | ||
var children = getChildren(); | ||
if (!children.length) { | ||
return false; | ||
} | ||
var totalHeight = 0; | ||
forEach(children, function (child) { | ||
totalHeight += getHeight(child); | ||
}); | ||
if (totalHeight < snapHeight) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
snapDuration = parseInt(snapDuration, 10); | ||
if (isNaN(snapDuration)) { | ||
snapDuration = defaultSnapscrollSnapDuration; | ||
} | ||
function isSnapIndexValid(snapIndex) { | ||
return snapIndex >= 0 && | ||
snapIndex <= getChildren().length - 1; | ||
} | ||
scope.$watch('snapAnimation', function (animation) { | ||
if (animation === undefined) { | ||
scope.snapAnimation = true; | ||
} | ||
}); | ||
function snapIndexChanged(current, previous) { | ||
if (!isScrollable()) { | ||
return; | ||
} | ||
if (isUndefined(current)) { | ||
scope.snapIndex = 0; | ||
return; | ||
} | ||
if (!isNumber(current)) { | ||
if (!isNumber(previous)) { | ||
previous = 0; | ||
} | ||
scope.snapIndex = previous; | ||
return; | ||
} | ||
if (current % 1 !== 0) { | ||
scope.snapIndex = Math.round(current); | ||
return; | ||
} | ||
if (scope.ignoreSnapIndexChange === true) { | ||
scope.ignoreSnapIndexChange = undefined; | ||
return; | ||
} | ||
if (!isSnapIndexValid(current)) { | ||
if (!isSnapIndexValid(previous)) { | ||
previous = 0; | ||
} | ||
scope.ignoreSnapIndexChange = true; | ||
scope.snapIndex = previous; | ||
return; | ||
} | ||
scope.compositeIndex = [current, 0]; | ||
} | ||
scope.snapIndexMin = function () { | ||
return 0; | ||
}; | ||
function watchSnapIndex() { | ||
scope.unwatchSnapIndex = scope.$watch( | ||
'snapIndex', | ||
snapIndexChanged | ||
); | ||
} | ||
scope.scopeIndexMax = function () { | ||
return element.children().length - 1; | ||
}; | ||
function unwatchSnapIndex() { | ||
if (!isFunction(scope.unwatchSnapIndex)) { | ||
return; | ||
} | ||
scope.unwatchSnapIndex(); | ||
scope.unwatchSnapIndex = undefined; | ||
} | ||
scope.isValid = function (snapIndex) { | ||
return snapIndex >= scope.snapIndexMin() && snapIndex <= scope.scopeIndexMax(); | ||
}; | ||
function compositeIndexChanged(current, previous) { | ||
if (isUndefined(current)) { | ||
return; | ||
} | ||
var snapIndex = current[0]; | ||
if (scope.snapIndex !== snapIndex) { | ||
scope.ignoreSnapIndexChange = true; | ||
scope.snapIndex = snapIndex; | ||
} | ||
if (scope.ignoreCompositeIndexChange === true) { | ||
scope.ignoreCompositeIndexChange = undefined; | ||
return; | ||
} | ||
snapTo(current, previous); | ||
} | ||
if (element.css('overflowY') !== 'scroll') { | ||
element.css('overflowY', 'auto'); | ||
} | ||
function watchCompositeIndex() { | ||
scope.unwatchCompositeIndex = scope.$watchCollection( | ||
'compositeIndex', | ||
compositeIndexChanged | ||
); | ||
} | ||
watchSnapHeight(scope, function (snapHeight) { | ||
element.css('height', snapHeight + 'px'); | ||
var snaps = element.children(); | ||
if (snaps.length) { | ||
angular.forEach(snaps, function (snap) { | ||
angular.element(snap).css('height', snapHeight + 'px'); | ||
}); | ||
} | ||
snapTo(scope.snapIndex); | ||
}); | ||
function unwatchCompositeIndex() { | ||
if (!isFunction(scope.unwatchCompositeIndex)) { | ||
return; | ||
} | ||
scope.unwatchCompositeIndex(); | ||
scope.unwatchCompositeIndex = undefined; | ||
} | ||
watchSnapIndex(scope, snapTo); | ||
function getMaxInnerSnapIndex(snapIndex) { | ||
var snapHeight = getSnapHeight(); | ||
var childHeight = getChildHeight(snapIndex); | ||
if (childHeight <= snapHeight) { | ||
return 0; | ||
} | ||
var max = parseInt((childHeight / snapHeight), 10); | ||
if (childHeight % snapHeight === 0) { | ||
max -= 1; | ||
} | ||
return max; | ||
} | ||
if (!preventSnappingAfterManualScroll) { | ||
bindScroll(); | ||
scope.$on('$destroy', unbindScroll); | ||
} | ||
function isCompositeIndexValid(compositeIndex) { | ||
var snapIndex = compositeIndex[0]; | ||
var innerSnapIndex = compositeIndex[1]; | ||
if (innerSnapIndex < 0) { | ||
return isSnapIndexValid(snapIndex - 1); | ||
} | ||
if (innerSnapIndex > getMaxInnerSnapIndex(snapIndex)) { | ||
return isSnapIndexValid(snapIndex + 1); | ||
} | ||
return true; | ||
} | ||
initWheelEvents(wheelie, scope, element); | ||
}; | ||
function rectifyCompositeIndex(compositeIndex) { | ||
var snapIndex = compositeIndex[0]; | ||
var innerSnapIndex = compositeIndex[1]; | ||
if (innerSnapIndex < 0) { | ||
return [ | ||
snapIndex - 1, | ||
getMaxInnerSnapIndex(snapIndex - 1) | ||
]; | ||
} | ||
if (innerSnapIndex > getMaxInnerSnapIndex(snapIndex)) { | ||
return [snapIndex + 1, 0]; | ||
} | ||
return compositeIndex; | ||
} | ||
init(); | ||
} | ||
}; | ||
} | ||
]; | ||
function snap(direction) { | ||
if (!isScrollable()) { | ||
return; | ||
} | ||
angular.module('snapscroll') | ||
.directive('snapscroll', snapscrollAsAnAttribute); | ||
})(); | ||
if (scope.snapDirection === direction) { | ||
return true; | ||
} | ||
(function () { | ||
'use strict'; | ||
// all this is adapted from https://github.com/darius/requestAnimationFrame/blob/master/requestAnimationFrame.js | ||
// ngAnimate does have $$animateReflow, but that was not built to be a wrapper around requestAnimationFrame, hence this. | ||
var snapIndex = scope.compositeIndex[0]; | ||
var innerSnapIndex = scope.compositeIndex[1]; | ||
var newInnerSnapIndex; | ||
if (direction === 'up') { | ||
newInnerSnapIndex = innerSnapIndex - 1; | ||
} | ||
if (direction === 'down') { | ||
newInnerSnapIndex = innerSnapIndex + 1; | ||
} | ||
var getWithVendorPrefix = function (funcName, $window) { | ||
var vendors = ['webkit', 'moz'], | ||
func; | ||
for (var i = 0; i < vendors.length && !func; ++i) { | ||
var vp = vendors[i]; | ||
func = $window[vp + funcName]; | ||
} | ||
return func; | ||
}; | ||
var newCompositeIndex = [snapIndex, newInnerSnapIndex]; | ||
if (!isCompositeIndexValid(newCompositeIndex)) { | ||
return; | ||
} | ||
var iOS6 = function ($window) { | ||
return /iP(ad|hone|od).*OS 6/.test($window.navigator.userAgent); | ||
}; | ||
scope.$apply(function () { | ||
scope.compositeIndex = rectifyCompositeIndex( | ||
newCompositeIndex | ||
); | ||
}); | ||
return true; | ||
} | ||
if (!Date.now) { | ||
Date.now = function() { | ||
return new Date().getTime(); | ||
}; | ||
} | ||
function snapUp() { | ||
return snap('up'); | ||
} | ||
var snapscroll = angular.module('snapscroll'); | ||
function snapDown() { | ||
return snap('down'); | ||
} | ||
snapscroll.factory('requestAnimation', ['$timeout', '$window', | ||
function ($timeout, $window) { | ||
var lastTime, | ||
requestAnimation = $window.requestAnimationFrame || getWithVendorPrefix('RequestAnimationFrame', $window); | ||
function bindWheel() { | ||
wheelie.bind(element, { | ||
up: function (e) { | ||
e.preventDefault(); | ||
if (snapUp()) { | ||
e.stopPropagation(); | ||
} | ||
}, | ||
down: function (e) { | ||
e.preventDefault(); | ||
if (snapDown()) { | ||
e.stopPropagation(); | ||
} | ||
} | ||
}); | ||
} | ||
if (!requestAnimation || iOS6($window)) { // iOS6 is buggy | ||
requestAnimation = function(callback) { | ||
var now = Date.now(); | ||
var nextTime = Math.max(lastTime + 16, now); | ||
return $timeout(function() { | ||
callback(lastTime = nextTime); | ||
}, nextTime - now); | ||
}; | ||
} | ||
function unbindWheel() { | ||
wheelie.unbind(element); | ||
} | ||
return requestAnimation; | ||
}]); | ||
function setHeight(angularElement, height) { | ||
angularElement.css('height', height + 'px'); | ||
} | ||
snapscroll.factory('cancelAnimation', ['$timeout', '$window', | ||
function ($timeout, $window) { | ||
var cancelAnimation = $window.cancelAnimationFrame || getWithVendorPrefix('CancelAnimationFrame', $window) || getWithVendorPrefix('CancelRequestAnimationFrame', $window); | ||
function snapHeightChanged(current, previous) { | ||
if (isUndefined(current)) { | ||
return; | ||
} | ||
if (!isNumber(current)) { | ||
if (isNumber(previous)) { | ||
scope.snapHeight = previous; | ||
} | ||
return; | ||
} | ||
if (!cancelAnimation || iOS6($window)) { // iOS6 is buggy | ||
cancelAnimation = $timeout.cancel; | ||
} | ||
setHeight(element, current); | ||
forEach(getChildren(), function (child) { | ||
setHeight(angular.element(child), current); | ||
}); | ||
return cancelAnimation; | ||
}]); | ||
})(); | ||
(function () { | ||
'use strict'; | ||
// this is built upon http://stackoverflow.com/a/16136789/1004406 | ||
if (isDefined(scope.snapIndex)) { | ||
if (isUndefined(scope.compositeIndex)) { | ||
scope.compositeIndex = [scope.snapIndex, 0]; | ||
} | ||
snapTo(scope.compositeIndex); | ||
} | ||
} | ||
var snapscroll = angular.module('snapscroll'); | ||
function watchSnapHeight() { | ||
scope.unwatchSnapHeight = scope.$watch( | ||
'snapHeight', | ||
snapHeightChanged | ||
); | ||
} | ||
snapscroll.factory('scroll', ['$q', 'requestAnimation', 'cancelAnimation', 'defaultSnapscrollScrollEasing', | ||
function ($q, requestAnimation, cancelAnimation, defaultSnapscrollScrollEasing) { | ||
function unwatchSnapHeight() { | ||
if (!isFunction(scope.unwatchSnapHeight)) { | ||
return; | ||
} | ||
scope.unwatchSnapHeight(); | ||
scope.unwatchSnapHeight = undefined; | ||
} | ||
function cleanUp(element, animation) { | ||
animation = null; | ||
element.data('snapscroll-animation', null); | ||
element.data('snapscroll-animation-deferred', null); | ||
} | ||
function getCompositeIndex(scrollTop) { | ||
var snapIndex = 0; | ||
var innerSnapIndex = 0; | ||
return { | ||
to: function (element, top, duration, easing) { | ||
var start, | ||
change, | ||
animate, | ||
deferred, | ||
animation, | ||
increment, | ||
currentTime; | ||
if (scrollTop > 0) { | ||
snapIndex = -1; | ||
var children = getChildren(); | ||
var childHeight; | ||
while (scrollTop > 0) { | ||
childHeight = getHeight(children[++snapIndex]); | ||
scrollTop -= childHeight; | ||
} | ||
var snapHeight = getSnapHeight(); | ||
if (childHeight > snapHeight) { | ||
scrollTop += childHeight - snapHeight; | ||
if (scrollTop >= snapHeight) { | ||
innerSnapIndex++; | ||
} | ||
while (scrollTop > 0) { | ||
innerSnapIndex++; | ||
scrollTop -= snapHeight; | ||
} | ||
if ((snapHeight / 2) >= -scrollTop) { | ||
innerSnapIndex += 1; | ||
} | ||
} else if ((childHeight / 2) >= -scrollTop) { | ||
snapIndex += 1; | ||
} | ||
} | ||
animate = function () { | ||
currentTime += increment; | ||
element[0].scrollTop = easing(currentTime, start, change, duration); | ||
if(currentTime < duration) { | ||
animation = requestAnimation(animate, increment); | ||
element.data('snapscroll-animation', animation); | ||
} else { | ||
cleanUp(element, animation); | ||
deferred.resolve(); | ||
} | ||
}; | ||
return rectifyCompositeIndex([snapIndex, innerSnapIndex]); | ||
} | ||
if (!angular.isElement(element) || !angular.isNumber(top)) { | ||
return; | ||
} | ||
function onScroll() { | ||
function snapFromSrollTop() { | ||
var compositeIndex = getCompositeIndex( | ||
getCurrentScrollTop() | ||
); | ||
if (scope.compositeIndex[0] === compositeIndex[0] && | ||
scope.compositeIndex[1] === compositeIndex[1]) { | ||
snapTo(scope.compositeIndex); | ||
} else { | ||
scope.$apply(function () { | ||
scope.compositeIndex = compositeIndex; | ||
}); | ||
} | ||
} | ||
deferred = $q.defer(); | ||
duration = parseInt(duration); | ||
animation = element.data('snapscroll-animation'); | ||
scrollie.stop(element); | ||
if (scope.scrollDelay === false) { | ||
snapFromSrollTop(); | ||
} else { | ||
$timeout.cancel(scope.scrollPromise); | ||
scope.scrollPromise = $timeout( | ||
function () { | ||
snapFromSrollTop(); | ||
scope.scrollPromise = undefined; | ||
}, | ||
scope.scrollDelay | ||
); | ||
} | ||
} | ||
if (animation) { | ||
cancelAnimation(animation); | ||
// TODO: should the promise be rejected at this point since this is just cleaning up? | ||
// element.data('snapscroll-animation-deferred').reject(); | ||
cleanUp(element, animation); | ||
} | ||
function bindScroll() { | ||
if (scope.preventSnappingAfterManualScroll || | ||
scope.scrollBound) { | ||
return; | ||
} | ||
if (isDefined(scope.snapDirection)) { // still snapping | ||
// TODO: add tests for this | ||
bindScrollAfterDelay(); | ||
return; | ||
} | ||
element.on('scroll', onScroll); | ||
scope.scrollBound = true; | ||
} | ||
if (duration === 0 || isNaN(duration)) { | ||
element[0].scrollTop = top; | ||
deferred.resolve(); | ||
} else { | ||
if (typeof easing !== 'function') { | ||
easing = defaultSnapscrollScrollEasing; | ||
} | ||
start = element[0].scrollTop; | ||
change = top - start; | ||
currentTime = 0; | ||
increment = 20; | ||
animate(); | ||
} | ||
function unbindScroll() { | ||
if (!scope.scrollBound) { | ||
return; | ||
} | ||
element.off('scroll', onScroll); | ||
scope.scrollBound = false; | ||
} | ||
element.data('snapscroll-animation-deferred', deferred); | ||
return deferred.promise; | ||
}, | ||
function bindScrollAfterDelay() { | ||
if (scope.preventSnappingAfterManualScroll) { | ||
return; | ||
} | ||
if (scope.bindScrollPromise) { | ||
$timeout.cancel(scope.bindScrollPromise); | ||
} | ||
scope.bindScrollPromise = $timeout( | ||
function () { | ||
bindScroll(); | ||
scope.bindScrollPromise = undefined; | ||
}, | ||
defaultSnapscrollBindScrollTimeout | ||
); | ||
} | ||
stop: function (element) { | ||
var animation = element.data('snapscroll-animation'); | ||
if (animation) { | ||
cancelAnimation(animation); | ||
element.data('snapscroll-animation-deferred').reject(); | ||
cleanUp(element, animation); | ||
} | ||
function onKeyDown(e) { | ||
if (e.originalEvent) { | ||
e = e.originalEvent; | ||
} | ||
var handler; | ||
var keyCode = e.keyCode; | ||
if (keyCode === 38) { | ||
handler = snapUp; | ||
} | ||
if (keyCode === 40) { | ||
handler = snapDown; | ||
} | ||
if (handler) { | ||
e.preventDefault(); | ||
handler(); | ||
} | ||
} | ||
function bindArrowKeys() { | ||
if (!scope.enableArrowKeys || scope.arrowKeysBound) { | ||
return; | ||
} | ||
$document.on('keydown', onKeyDown); | ||
scope.arrowKeysBound = true; | ||
} | ||
function unbindArrowKeys() { | ||
if (!scope.arrowKeysBound) { | ||
return; | ||
} | ||
$document.off('keydown', onKeyDown); | ||
scope.arrowKeysBound = false; | ||
} | ||
function init() { | ||
var scrollDelay = attributes.scrollDelay; | ||
if (scrollDelay === 'false') { | ||
scope.scrollDelay = false; | ||
} else { | ||
scrollDelay = parseInt(scrollDelay, 10); | ||
if (isNaN(scrollDelay)) { | ||
scrollDelay = defaultSnapscrollScrollDelay; | ||
} | ||
scope.scrollDelay = scrollDelay; | ||
} | ||
var snapEasing = attributes.snapEasing; | ||
if (isDefined(snapEasing)) { | ||
scope.snapEasing = scope.$parent.$eval(snapEasing); | ||
} else if (isFunction(defaultSnapscrollScrollEasing)) { | ||
scope.snapEasing = defaultSnapscrollScrollEasing; | ||
} | ||
var snapDuration = parseInt(attributes.snapDuration, 10); | ||
if (isNaN(snapDuration)) { | ||
snapDuration = defaultSnapscrollSnapDuration; | ||
} | ||
scope.snapDuration = snapDuration; | ||
// TODO: perform initial snap without animation | ||
if (isUndefined(scope.snapAnimation)) { | ||
scope.snapAnimation = true; | ||
} | ||
scope.enableArrowKeys = isDefined( | ||
attributes.enableArrowKeys | ||
); | ||
scope.preventSnappingAfterManualScroll = isDefined( | ||
attributes.preventSnappingAfterManualScroll | ||
); | ||
if (element.css('overflowY') !== 'scroll') { | ||
element.css('overflowY', 'auto'); | ||
} | ||
scope.$watch('enabled', function (current, previous) { | ||
function updateCompositeIndexFromScrollTop() { | ||
if (scope.preventSnappingAfterManualScroll) { | ||
return; | ||
} | ||
scope.compositeIndex = getCompositeIndex( | ||
getCurrentScrollTop() | ||
); | ||
} | ||
if (current !== false) { | ||
if (previous === false) { | ||
updateCompositeIndexFromScrollTop(); | ||
} | ||
watchCompositeIndex(); | ||
watchSnapIndex(); | ||
watchSnapHeight(); | ||
bindScroll(); | ||
bindWheel(); | ||
bindArrowKeys(); | ||
} else { | ||
unwatchCompositeIndex(); | ||
unwatchSnapIndex(); | ||
unwatchSnapHeight(); | ||
unbindScroll(); | ||
unbindWheel(); | ||
unbindArrowKeys(); | ||
} | ||
}); | ||
scope.$on('$destroy', function () { | ||
if (scope.enabled !== false) { | ||
unbindScroll(); | ||
unbindWheel(); | ||
unbindArrowKeys(); | ||
} | ||
}); | ||
} | ||
init(); | ||
} | ||
}; | ||
} | ||
}; | ||
}]); | ||
})(); | ||
]; | ||
angular.module('snapscroll') | ||
.directive('snapscroll', snapscrollAsAnAttribute); | ||
})(); |
@@ -1,2 +0,2 @@ | ||
/* angular-snapscroll v0.3.1, (c) 2014-2016 Joel Mukuthu, MIT License, built: 16-07-2016 00:59:03 GMT+0200 */ | ||
!function(){"use strict";function a(a,b,c,d){return a/=d/2,a<1?c/2*a*a+b:(a--,-c/2*(a*(a-2)-1)+b)}angular.module("snapscroll",["wheelie"]).value("defaultSnapscrollScrollEasing",a).value("defaultSnapscrollScrollDelay",250).value("defaultSnapscrollSnapDuration",800).value("defaultSnapscrollResizeDelay",400).value("defaultSnapscrollBindScrollTimeout",400)}(),function(){"use strict";angular.module("snapscroll").directive("fitWindowHeight",["$window","$timeout","defaultSnapscrollResizeDelay",function(a,b,c){return{restrict:"A",require:"snapscroll",link:function(d,e,f,g){function h(){l===!1?g.setSnapHeight(a.innerHeight):(b.cancel(k),k=b(function(){g.setSnapHeight(a.innerHeight)},l))}function i(){"false"===l?l=!1:(l=parseInt(l,10),isNaN(l)&&(l=c)),g.setSnapHeight(a.innerHeight),j=angular.element(a),j.on("resize",h),d.$on("$destroy",function(){j.off("resize")})}var j,k,l=f.resizeDelay;i()}}}])}(),function(){"use strict";var a={snapIndex:"=?",snapHeight:"=?",beforeSnap:"&",afterSnap:"&",snapAnimation:"=?"},b=["$scope",function(a){this.setSnapHeight=function(b){a.snapHeight=b}}],c=function(a){return angular.isNumber(a)&&!isNaN(a)},d=function(a,b){a.$watch("snapHeight",function(d,e){if(!angular.isUndefined(d))return c(d)?void(angular.isFunction(b)&&b(d)):void(c(e)&&(a.snapHeight=e))})},e=function(a,b){a.$watch("snapIndex",function(d,e){return angular.isUndefined(d)?void(a.snapIndex=0):c(d)?d%1!==0?void(a.snapIndex=Math.round(d)):a.ignoreThisSnapIndexChange?void(a.ignoreThisSnapIndexChange=void 0):a.isValid(d)?a.beforeSnap({snapIndex:d})===!1?(a.ignoreThisSnapIndexChange=!0,void(a.snapIndex=e)):void(angular.isFunction(b)&&(d>e?a.snapDirection="up":d<e&&(a.snapDirection="down"),b(d,function(){a.snapDirection="none",a.afterSnap({snapIndex:d})}))):(a.ignoreThisSnapIndexChange=!0,a.snapIndex=e,void(a.snapDirection="none")):void(c(e)?a.snapIndex=e:a.snapIndex=0)})},f=function(a,b,c){function d(a,b){b||a.stopPropagation()}a.bind(c,{up:function(a){a.preventDefault();var c;"down"!==b.snapDirection&&(b.snapIndex-1<b.snapIndexMin()?c=!0:(c=!1,b.$apply(function(){b.snapIndex-=1}))),d(a,c)},down:function(a){a.preventDefault();var c;"up"!==b.snapDirection&&(b.snapIndex+1>b.scopeIndexMax()?c=!0:(c=!1,b.$apply(function(){b.snapIndex+=1}))),d(a,c)}}),b.$on("$destroy",function(){a.unbind(c)})},g=["$timeout","scroll","wheelie","defaultSnapscrollScrollDelay","defaultSnapscrollSnapDuration","defaultSnapscrollBindScrollTimeout",function(c,g,h,i,j,k){return{restrict:"A",scope:a,controller:b,link:function(a,b,l){function m(a){for(var c=b.children(),d=0,e=0;e<a;e++)d+=parseInt(c[e].offsetHeight,10);return d}function n(a){for(var c,d=-1,e=b.children();a>0;)a-=c=e[++d].offsetHeight;return c/2>=-a&&(d+=1),d}var o,p,q,r,s,t,u,v,w=l.snapEasing,x=l.scrollDelay,y=l.snapDuration,z=angular.isDefined(l.preventSnappingAfterManualScroll);p=function(d,e){var f,h=m(d);f=a.snapAnimation?angular.isDefined(w)?[b,h,y,w]:[b,h,y]:[b,h],!z&&s&&t(),g.to.apply(g,f).then(function(){angular.isFunction(e)&&e(),z||(c.cancel(v),v=c(r,k))})},q=function(){var d=function(){var c=n(b[0].scrollTop);a.snapIndex===c?p(c):a.$apply(function(){a.snapIndex=c})};g.stop(b),x===!1?d():(c.cancel(u),u=c(d,x))},r=function(){return"none"!==a.snapDirection?void(v=c(r,k)):(b.on("scroll",q),void(s=!0))},t=function(){b.off("scroll",q),s=!1},(o=function(){"false"===x?x=!1:(x=parseInt(x,10),isNaN(x)&&(x=i)),angular.isDefined(w)&&(w=a.$parent.$eval(w)),y=parseInt(y,10),isNaN(y)&&(y=j),a.$watch("snapAnimation",function(b){void 0===b&&(a.snapAnimation=!0)}),a.snapIndexMin=function(){return 0},a.scopeIndexMax=function(){return b.children().length-1},a.isValid=function(b){return b>=a.snapIndexMin()&&b<=a.scopeIndexMax()},"scroll"!==b.css("overflowY")&&b.css("overflowY","auto"),d(a,function(c){b.css("height",c+"px");var d=b.children();d.length&&angular.forEach(d,function(a){angular.element(a).css("height",c+"px")}),p(a.snapIndex)}),e(a,p),z||(r(),a.$on("$destroy",t)),f(h,a,b)})()}}}];angular.module("snapscroll").directive("snapscroll",g)}(),function(){"use strict";var a=function(a,b){for(var c,d=["webkit","moz"],e=0;e<d.length&&!c;++e){var f=d[e];c=b[f+a]}return c},b=function(a){return/iP(ad|hone|od).*OS 6/.test(a.navigator.userAgent)};Date.now||(Date.now=function(){return(new Date).getTime()});var c=angular.module("snapscroll");c.factory("requestAnimation",["$timeout","$window",function(c,d){var e,f=d.requestAnimationFrame||a("RequestAnimationFrame",d);return f&&!b(d)||(f=function(a){var b=Date.now(),d=Math.max(e+16,b);return c(function(){a(e=d)},d-b)}),f}]),c.factory("cancelAnimation",["$timeout","$window",function(c,d){var e=d.cancelAnimationFrame||a("CancelAnimationFrame",d)||a("CancelRequestAnimationFrame",d);return e&&!b(d)||(e=c.cancel),e}])}(),function(){"use strict";var a=angular.module("snapscroll");a.factory("scroll",["$q","requestAnimation","cancelAnimation","defaultSnapscrollScrollEasing",function(a,b,c,d){function e(a,b){b=null,a.data("snapscroll-animation",null),a.data("snapscroll-animation-deferred",null)}return{to:function(f,g,h,i){var j,k,l,m,n,o,p;if(l=function(){p+=o,f[0].scrollTop=i(p,j,k,h),p<h?(n=b(l,o),f.data("snapscroll-animation",n)):(e(f,n),m.resolve())},angular.isElement(f)&&angular.isNumber(g))return m=a.defer(),h=parseInt(h),n=f.data("snapscroll-animation"),n&&(c(n),e(f,n)),0===h||isNaN(h)?(f[0].scrollTop=g,m.resolve()):("function"!=typeof i&&(i=d),j=f[0].scrollTop,k=g-j,p=0,o=20,l()),f.data("snapscroll-animation-deferred",m),m.promise},stop:function(a){var b=a.data("snapscroll-animation");b&&(c(b),a.data("snapscroll-animation-deferred").reject(),e(a,b))}}}])}(); | ||
/* angular-snapscroll v1.0.0, (c) 2014-2016 Joel Mukuthu, MIT License, built: 23-09-2016 17:15:38 GMT+0200 */ | ||
!function(){angular.module("snapscroll",["wheelie","scrollie"]).value("defaultSnapscrollScrollEasing",void 0).value("defaultSnapscrollScrollDelay",250).value("defaultSnapscrollSnapDuration",800).value("defaultSnapscrollResizeDelay",400).value("defaultSnapscrollBindScrollTimeout",400)}(),function(){angular.module("snapscroll").directive("fitWindowHeight",["$window","$timeout","defaultSnapscrollResizeDelay",function(a,b,c){return{restrict:"A",require:"snapscroll",link:function(d,e,f,g){function h(){l===!1?g.setSnapHeight(a.innerHeight):(b.cancel(k),k=b(function(){g.setSnapHeight(a.innerHeight)},l))}function i(){"false"===l?l=!1:(l=parseInt(l,10),isNaN(l)&&(l=c)),g.setSnapHeight(a.innerHeight),j=angular.element(a),j.on("resize",h),d.$on("$destroy",function(){j.off("resize")})}var j,k,l=f.resizeDelay;i()}}}])}(),function(){function a(a){return angular.isNumber(a)&&!isNaN(a)}var b=angular.isDefined,c=angular.isUndefined,d=angular.isFunction,e=angular.forEach,f={enabled:"=snapscroll",snapIndex:"=?",snapHeight:"=?",beforeSnap:"&",afterSnap:"&",snapAnimation:"=?"},g=["$scope",function(a){this.setSnapHeight=function(b){a.snapHeight=b}}],h=["$timeout","$document","wheelie","scrollie","defaultSnapscrollScrollEasing","defaultSnapscrollScrollDelay","defaultSnapscrollSnapDuration","defaultSnapscrollBindScrollTimeout",function(h,i,j,k,l,m,n,o){return{restrict:"A",scope:f,controller:g,link:function(f,g,p){function q(){return g.children()}function r(a){return a.offsetHeight}function s(a){return r(q()[a])}function t(){return r(g[0])}function u(){return g[0].scrollHeight}function v(a){var b=u()-t();return a>b?b:a}function w(a,c){for(var d=a[0],e=a[1],f=0,g=q(),h=0;h<d;h++)f+=r(g[h]);if(0===e)return v(f);var i,j=t(),k=r(g[d]);if(b(c)&&e<c[1]){i=k;for(var l=e;l>=0;l--)i-=j}else{i=0;for(var m=0;m<e;m++)i+=j;var n=i+j-k;n>0&&(i-=n)}return v(f+i)}function x(d,e){var g=d[0],h=c(e)||g!==e[0];if(h){var i=f.beforeSnap({snapIndex:g});if(i===!1)return void(b(e)&&(f.ignoreCompositeIndexChange=!0,f.compositeIndex=e));if(a(i))return void(f.snapIndex=i)}return z(w(d,e)).then(function(){h&&f.afterSnap({snapIndex:g})})}function y(){return g[0].scrollTop}function z(a){var b;b=f.snapAnimation?c(f.snapEasing)?[g,a,f.snapDuration]:[g,a,f.snapDuration,f.snapEasing]:[g,a];var d=y();return a>d?f.snapDirection="down":a<d?f.snapDirection="up":f.snapDirection="same",X(),k.to.apply(k,b).then(function(){f.snapDirection=void 0,Y()})}function A(){var a=t();if(!a)return!1;var b=q();if(!b.length)return!1;var c=0;return e(b,function(a){c+=r(a)}),!(c<a)}function B(a){return a>=0&&a<=q().length-1}function C(b,d){if(A())return c(b)?void(f.snapIndex=0):a(b)?b%1!==0?void(f.snapIndex=Math.round(b)):f.ignoreSnapIndexChange===!0?void(f.ignoreSnapIndexChange=void 0):B(b)?void(f.compositeIndex=[b,0]):(B(d)||(d=0),f.ignoreSnapIndexChange=!0,void(f.snapIndex=d)):(a(d)||(d=0),void(f.snapIndex=d))}function D(){f.unwatchSnapIndex=f.$watch("snapIndex",C)}function E(){d(f.unwatchSnapIndex)&&(f.unwatchSnapIndex(),f.unwatchSnapIndex=void 0)}function F(a,b){if(!c(a)){var d=a[0];return f.snapIndex!==d&&(f.ignoreSnapIndexChange=!0,f.snapIndex=d),f.ignoreCompositeIndexChange===!0?void(f.ignoreCompositeIndexChange=void 0):void x(a,b)}}function G(){f.unwatchCompositeIndex=f.$watchCollection("compositeIndex",F)}function H(){d(f.unwatchCompositeIndex)&&(f.unwatchCompositeIndex(),f.unwatchCompositeIndex=void 0)}function I(a){var b=t(),c=s(a);if(c<=b)return 0;var d=parseInt(c/b,10);return c%b===0&&(d-=1),d}function J(a){var b=a[0],c=a[1];return c<0?B(b-1):!(c>I(b))||B(b+1)}function K(a){var b=a[0],c=a[1];return c<0?[b-1,I(b-1)]:c>I(b)?[b+1,0]:a}function L(a){if(A()){if(f.snapDirection===a)return!0;var b,c=f.compositeIndex[0],d=f.compositeIndex[1];"up"===a&&(b=d-1),"down"===a&&(b=d+1);var e=[c,b];if(J(e))return f.$apply(function(){f.compositeIndex=K(e)}),!0}}function M(){return L("up")}function N(){return L("down")}function O(){j.bind(g,{up:function(a){a.preventDefault(),M()&&a.stopPropagation()},down:function(a){a.preventDefault(),N()&&a.stopPropagation()}})}function P(){j.unbind(g)}function Q(a,b){a.css("height",b+"px")}function R(d,h){if(!c(d)){if(!a(d))return void(a(h)&&(f.snapHeight=h));Q(g,d),e(q(),function(a){Q(angular.element(a),d)}),b(f.snapIndex)&&(c(f.compositeIndex)&&(f.compositeIndex=[f.snapIndex,0]),x(f.compositeIndex))}}function S(){f.unwatchSnapHeight=f.$watch("snapHeight",R)}function T(){d(f.unwatchSnapHeight)&&(f.unwatchSnapHeight(),f.unwatchSnapHeight=void 0)}function U(a){var b=0,c=0;if(a>0){b=-1;for(var d,e=q();a>0;)d=r(e[++b]),a-=d;var f=t();if(d>f){for(a+=d-f,a>=f&&c++;a>0;)c++,a-=f;f/2>=-a&&(c+=1)}else d/2>=-a&&(b+=1)}return K([b,c])}function V(){function a(){var a=U(y());f.compositeIndex[0]===a[0]&&f.compositeIndex[1]===a[1]?x(f.compositeIndex):f.$apply(function(){f.compositeIndex=a})}k.stop(g),f.scrollDelay===!1?a():(h.cancel(f.scrollPromise),f.scrollPromise=h(function(){a(),f.scrollPromise=void 0},f.scrollDelay))}function W(){if(!f.preventSnappingAfterManualScroll&&!f.scrollBound){if(b(f.snapDirection))return void Y();g.on("scroll",V),f.scrollBound=!0}}function X(){f.scrollBound&&(g.off("scroll",V),f.scrollBound=!1)}function Y(){f.preventSnappingAfterManualScroll||(f.bindScrollPromise&&h.cancel(f.bindScrollPromise),f.bindScrollPromise=h(function(){W(),f.bindScrollPromise=void 0},o))}function Z(a){a.originalEvent&&(a=a.originalEvent);var b,c=a.keyCode;38===c&&(b=M),40===c&&(b=N),b&&(a.preventDefault(),b())}function $(){f.enableArrowKeys&&!f.arrowKeysBound&&(i.on("keydown",Z),f.arrowKeysBound=!0)}function _(){f.arrowKeysBound&&(i.off("keydown",Z),f.arrowKeysBound=!1)}function aa(){var a=p.scrollDelay;"false"===a?f.scrollDelay=!1:(a=parseInt(a,10),isNaN(a)&&(a=m),f.scrollDelay=a);var e=p.snapEasing;b(e)?f.snapEasing=f.$parent.$eval(e):d(l)&&(f.snapEasing=l);var h=parseInt(p.snapDuration,10);isNaN(h)&&(h=n),f.snapDuration=h,c(f.snapAnimation)&&(f.snapAnimation=!0),f.enableArrowKeys=b(p.enableArrowKeys),f.preventSnappingAfterManualScroll=b(p.preventSnappingAfterManualScroll),"scroll"!==g.css("overflowY")&&g.css("overflowY","auto"),f.$watch("enabled",function(a,b){function c(){f.preventSnappingAfterManualScroll||(f.compositeIndex=U(y()))}a!==!1?(b===!1&&c(),G(),D(),S(),W(),O(),$()):(H(),E(),T(),X(),P(),_())}),f.$on("$destroy",function(){f.enabled!==!1&&(X(),P(),_())})}aa()}}}];angular.module("snapscroll").directive("snapscroll",h)}(); |
114
DOCS.md
# angular-snapscroll docs | ||
## snapscroll directive | ||
Adds scroll-and-snap behaviour to any element that has a vertical scrollbar (fixed height and child elements): | ||
Adds scroll-and-snap behaviour to any element that has a vertical scrollbar: | ||
```html | ||
<div style="height: 200px;" snapscroll=""> | ||
<div></div> | ||
<div></div> | ||
<div></div> | ||
<div style="height: 200px;"></div> | ||
<div style="height: 200px;"></div> | ||
<div style="height: 200px;"></div> | ||
</div> | ||
``` | ||
You can disable snapscroll programmatically by passing `false` or a binding that | ||
evaluates to `false`: | ||
```javascript | ||
angular.controller('MainCtrl', function ($scope, $window) { | ||
$scope.snapscrollEnabled = $window.innerWidth > 320; | ||
}); | ||
``` | ||
```html | ||
<!-- always disabled --> | ||
<div style="height: 200px;" snapscroll="false"> ... </div> | ||
<!-- disabled programmatically --> | ||
<div ng-controller="MainCtrl"> | ||
<div snapscroll="" snapscroll="snapscrollEnabled"> ... </div> | ||
</div> | ||
``` | ||
@@ -16,3 +31,4 @@ Other attributes that can be added are: | ||
##### snap-index | ||
provides a two-way bind to the current index of the visible child element. indeces are zero-based. | ||
provides a two-way bind to the current index of the visible child element. | ||
indeces are zero-based. | ||
```html | ||
@@ -30,3 +46,4 @@ <!-- setting an initial snapIndex to automatically seek to on page load --> | ||
##### snap-height | ||
allows you to provide the height of the element (and children elements) instead of doing it in CSS. this is a two-way bind. | ||
allows you to provide the height of the element (and children elements) instead | ||
of doing it in CSS. this is a two-way bind. | ||
```html | ||
@@ -42,3 +59,5 @@ <div snapscroll="" snap-height="200"> ... </div> | ||
#### fit-window-height | ||
instead of `snap-height`, you can use this attribute (it's actually a directive) to make the snapHeight equal the window height. snapHeight will be resized automatically if the window is resized. | ||
instead of `snap-height`, you can use this attribute (it's actually a directive) | ||
to make the snapHeight equal the window height. snapHeight will be updated | ||
automatically if the window is resized. | ||
```html | ||
@@ -48,11 +67,24 @@ <div snapscroll="" fit-window-height=""> ... </div> | ||
#### enable-arrow-keys | ||
enable support for snapping up and down when the up and down keyboard keys are | ||
pressed, respectively. | ||
```html | ||
<div snapscroll="" enable-arrow-keys=""> ... </div> | ||
``` | ||
#### before-snap | ||
is a callback executed before snapping occurs. the callback is passed a `snapIndex` parameter, which is the index being snapped to. returning `false` from this callback will prevent snapping. | ||
is a callback executed before snapping occurs. the callback is passed a | ||
`snapIndex` parameter, which is the index being snapped to. returning `false` | ||
from this callback will prevent snapping. you can also override the next | ||
`snapIndex` by returning a number. | ||
```javascript | ||
angular.controller('MainCtrl', function ($scope) { | ||
$scope.log = function (snapIndex) { | ||
$scope.beforeSnap = function (snapIndex) { | ||
console.log('snapping to', snapIndex); | ||
if (snapIndex > 1) { | ||
if (snapIndex > 4) { | ||
return false; // prevent snapping | ||
} | ||
if (snapIndex === 2) { | ||
return 3; // snap to snapIndex 3 instead | ||
} | ||
}; | ||
@@ -63,3 +95,3 @@ }); | ||
<div ng-controller="MainCtrl"> | ||
<div snapscroll="" before-snap="log(snapIndex)"> ... </div> | ||
<div snapscroll="" before-snap="beforeSnap(snapIndex)"> ... </div> | ||
</div> | ||
@@ -69,3 +101,5 @@ ``` | ||
#### after-snap | ||
is a callback executed after snapping occurs. the callback is passed a `snapIndex` parameter, which is the index just snapped to. any return value from this callback is ignored. | ||
is a callback executed after snapping occurs. the callback is passed a | ||
`snapIndex` parameter, which is the index just snapped to. any return value from | ||
this callback is ignored. | ||
```javascript | ||
@@ -100,3 +134,6 @@ angular.controller('MainCtrl', function ($scope) { | ||
<div ng-controller="MainCtrl"> | ||
<div snapscroll="" snap-index="index" snap-animation="animation" after-snap="enableAnimation()"> ... </div> | ||
<div snapscroll="" snap-index="index" snap-animation="animation" | ||
after-snap="enableAnimation()"> | ||
... | ||
</div> | ||
</div> | ||
@@ -106,7 +143,9 @@ ``` | ||
#### snap-duration | ||
integer value indicating the length of the snap animation in milliseconds. a value of 0 disables the snap-animation as well. default is 800ms. | ||
integer value indicating the length of the snap animation in milliseconds. a | ||
value of 0 disables the snap-animation as well. default is 800ms. | ||
```html | ||
<div snapscroll="" snap-duration="1200"> ... </div> | ||
``` | ||
the snap-duration can also be changed for all snapscroll instances by changing the default value: | ||
the snap-duration can also be changed for all snapscroll instances by changing | ||
the default value: | ||
```javascript | ||
@@ -118,3 +157,6 @@ angular.module('myapp', ['snapscroll']) | ||
#### snap-easing | ||
function reference that allows overriding the default easing of the snap animation. note that this is not a regular angular callback but rather a function reference. the default easing is easeInOutQuad. any of the javascript easing functions can be provided. | ||
function reference that allows overriding the default easing of the snap | ||
animation. note that this is not a regular angular callback but rather a | ||
function reference. the default easing is easeInOutQuad. any of the javascript | ||
easing functions can be provided. | ||
```javascript | ||
@@ -132,3 +174,4 @@ angular.controller('MainCtrl', function ($scope) { | ||
``` | ||
the snap-easing can also be changed for all snapscroll instances by changing the default value: | ||
the snap-easing can also be changed for all snapscroll instances by changing the | ||
default value: | ||
```javascript | ||
@@ -142,10 +185,15 @@ angular.module('myapp', ['snapscroll']) | ||
#### prevent-snapping-after-manual-scroll | ||
snapscroll listens to the `scroll` event on the element that it's bound to and automatically resets the current snap after a manual scroll so that it's always fully visible. this behaviour can be prevented by adding this attribute. | ||
snapscroll listens to the `scroll` event on the element that it's bound to and | ||
automatically resets the current snap after a manual scroll so that it's always | ||
fully visible. this behaviour can be prevented by adding this attribute. | ||
#### scroll-delay | ||
the `scroll` listener described above is throttled using a `scroll-delay`. this delay can be changed by providig a value in milliseconds. it can also be turned off by providin `false`. | ||
the `scroll` listener described above is throttled using a `scroll-delay`. this | ||
delay can be changed by providing a value in milliseconds. it can also be turned | ||
off by providing `false`. | ||
```html | ||
<div snapscroll="" scroll-delay="400"> ... </div> | ||
``` | ||
the scroll-delay can also be changed for all snapscroll instances by changing the default value: | ||
the scroll-delay can also be changed for all snapscroll instances by changing | ||
the default value: | ||
```javascript | ||
@@ -157,7 +205,10 @@ angular.module('myapp', ['snapscroll']) | ||
#### resize-delay | ||
the `resize` listener used by `fit-window-height` is throttled using a `resize-delay`. this delay can be changed by providig a value in milliseconds. it can also be turned off by providin `false`. | ||
the `resize` listener used by `fit-window-height` is throttled using a | ||
`resize-delay`. this delay can be changed by providing a value in milliseconds. | ||
it can also be turned off by providing `false`. | ||
```html | ||
<div snapscroll="" fit-window-height="" resize-delay="400"> ... </div> | ||
``` | ||
the scroll-delay can also be changed for all snapscroll instances by changing the default value: | ||
the scroll-delay can also be changed for all snapscroll instances by changing | ||
the default value: | ||
```javascript | ||
@@ -167,20 +218,1 @@ angular.module('myapp', ['snapscroll']) | ||
``` | ||
## scroll service | ||
Snapscroll also provides a `scroll` service that can be used to animate `scrollTop`, or simply to set it. It exposes two functions, `scroll.to()` and `scroll.stop()` | ||
#### scroll.to(element, top, [duration], [easing]) | ||
animates the scrollTop of `element` from it's current scrollTop to the value of `top` in a time-frame of `duration` and using the provided `easing` function. if duration is not provided or is not valid, then it sets the scrollTop without animating. | ||
it returns a `$q` promise object which is resolved when the animation is complete and is rejected if the animation is stopped. | ||
the default easing can be changed during module instantiation: | ||
```javascript | ||
angular.module('myapp', ['snapscroll']) | ||
.value('defaultSnapscrollScrollEasing', function () { | ||
// easing function | ||
}); | ||
``` | ||
#### scroll.stop(element) | ||
stops any currently-running animation on scrollTop of `element`. stopping the animation results in rejecting the promise returned by `scroll.to()`. |
185
Gruntfile.js
@@ -5,124 +5,93 @@ 'use strict'; | ||
require('load-grunt-tasks')(grunt); | ||
require('load-grunt-tasks')(grunt); | ||
grunt.initConfig({ | ||
pkg: grunt.file.readJSON('package.json'), | ||
grunt.initConfig({ | ||
pkg: grunt.file.readJSON('package.json'), | ||
info: { | ||
banner: { | ||
short: '/* <%= pkg.name %> v<%= pkg.version %>, (c) 2014-<%= grunt.template.today("yyyy") %> Joel Mukuthu, MIT License, built: <%= grunt.template.date("dd-mm-yyyy HH:MM:ss Z") %> */\n', | ||
long: '/**\n * <%= pkg.name %>\n * Version: <%= pkg.version %>\n * (c) 2014-<%= grunt.template.today("yyyy") %> Joel Mukuthu\n * MIT License\n * Built on: <%= grunt.template.date("dd-mm-yyyy HH:MM:ss Z") %>\n **/\n\n' | ||
} | ||
}, | ||
info: { | ||
banner: { | ||
short: '/* <%= pkg.name %> v<%= pkg.version %>, (c) 2014-<%= grunt.template.today("yyyy") %> Joel Mukuthu, MIT License, built: <%= grunt.template.date("dd-mm-yyyy HH:MM:ss Z") %> */\n', | ||
long: '/**\n * <%= pkg.name %>\n * Version: <%= pkg.version %>\n * (c) 2014-<%= grunt.template.today("yyyy") %> Joel Mukuthu\n * MIT License\n * Built on: <%= grunt.template.date("dd-mm-yyyy HH:MM:ss Z") %>\n **/\n\n' | ||
} | ||
}, | ||
clean: { | ||
// dev: '.tmp', | ||
dist: 'dist' | ||
}, | ||
clean: { | ||
dist: 'dist' | ||
}, | ||
concat: { | ||
options: { | ||
separator: '\n', | ||
banner: '<%= info.banner.long %>' | ||
}, | ||
// dev: { | ||
// src: ['src/*.js', 'src/**/*.js'], | ||
// dest: '.tmp/<%= pkg.name %>.js' | ||
// }, | ||
dist: { | ||
src: ['src/*.js', 'src/**/*.js'], | ||
dest: 'dist/<%= pkg.name %>.js' | ||
} | ||
}, | ||
concat: { | ||
options: { | ||
separator: '\n', | ||
banner: '<%= info.banner.long %>' | ||
}, | ||
dist: { | ||
src: ['src/*.js', 'src/**/*.js'], | ||
dest: 'dist/<%= pkg.name %>.js' | ||
} | ||
}, | ||
uglify: { | ||
options: { | ||
banner: '<%= info.banner.short %>' | ||
}, | ||
dist: { | ||
src: ['<%= concat.dist.dest %>'], | ||
dest: 'dist/<%= pkg.name %>.min.js' | ||
} | ||
}, | ||
uglify: { | ||
options: { | ||
banner: '<%= info.banner.short %>' | ||
}, | ||
dist: { | ||
src: ['<%= concat.dist.dest %>'], | ||
dest: 'dist/<%= pkg.name %>.min.js' | ||
} | ||
}, | ||
jshint: { | ||
options: { | ||
jshintrc: '.jshintrc', | ||
reporter: require('jshint-stylish') | ||
}, | ||
js: { | ||
src: [ | ||
'Gruntfile.js', | ||
'src/**/*.js' | ||
] | ||
}, | ||
test: { | ||
options: { | ||
jshintrc: 'test/.jshintrc' | ||
eslint: { | ||
target: [ | ||
'src/**/*.js', | ||
'test/spec/**/*.js' | ||
] | ||
}, | ||
src: ['test/spec/**/*.js'] | ||
} | ||
}, | ||
karma: { | ||
options: { | ||
configFile: 'test/karma.conf.js' | ||
}, | ||
single: { | ||
singleRun: true | ||
}, | ||
continuous: { | ||
singleRun: false | ||
} | ||
}, | ||
karma: { | ||
options: { | ||
configFile: 'test/karma.conf.js' | ||
}, | ||
single: { | ||
singleRun: true | ||
} | ||
}, | ||
watch: { | ||
js: { | ||
files: ['src/**/*.js'], | ||
tasks: [ | ||
'newer:jshint:js', | ||
'karma:single' | ||
] | ||
}, | ||
test: { | ||
files: ['test/spec/**/*.js'], | ||
tasks: [ | ||
'newer:jshint:test', | ||
'karma:single' | ||
] | ||
} | ||
}, | ||
watch: { | ||
files: [ | ||
'src/**/*.js', | ||
'test/spec/**/*.js' | ||
], | ||
tasks: [ | ||
'newer:eslint', | ||
'karma:single' | ||
] | ||
}, | ||
'release-it': { | ||
options: { | ||
pkgFiles: ['package.json', 'bower.json'], | ||
commitMessage: 'Release %s', | ||
tagName: 'v%s', | ||
tagAnnotation: 'Release %s', | ||
buildCommand: 'grunt build' | ||
'release-it': { | ||
options: { | ||
pkgFiles: ['package.json', 'bower.json'], | ||
commitMessage: 'Release %s', | ||
tagName: 'v%s', | ||
tagAnnotation: 'Release %s', | ||
buildCommand: 'grunt build' | ||
} | ||
} | ||
} | ||
}); | ||
}); | ||
grunt.registerTask('default', [ | ||
'jshint', | ||
'watch', | ||
]); | ||
grunt.registerTask('default', [ | ||
'eslint', | ||
'watch' | ||
]); | ||
grunt.registerTask('dev-test', [ | ||
'jshint', | ||
'karma:continuous' | ||
]); | ||
grunt.registerTask('test', [ | ||
'eslint', | ||
'karma:single' | ||
]); | ||
grunt.registerTask('test', [ | ||
'jshint', | ||
'karma:single' | ||
]); | ||
grunt.registerTask('build', [ | ||
'test', | ||
'clean:dist', | ||
'concat', | ||
'uglify' | ||
]); | ||
grunt.registerTask('build', [ | ||
'test', | ||
'clean:dist', | ||
'concat', | ||
'uglify' | ||
]); | ||
}; |
{ | ||
"name": "angular-snapscroll", | ||
"version": "0.3.1", | ||
"version": "1.0.0", | ||
"description": "Vertical scroll-and-snap functionality in angular", | ||
@@ -19,5 +19,5 @@ "main": "dist/angular-snapscroll.js", | ||
"grunt-contrib-copy": "^1.0.0", | ||
"grunt-contrib-jshint": "^1.0.0", | ||
"grunt-contrib-uglify": "^1.0.1", | ||
"grunt-contrib-watch": "^1.0.0", | ||
"grunt-eslint": "^19.0.0", | ||
"grunt-karma": "^2.0.0", | ||
@@ -27,8 +27,5 @@ "grunt-newer": "^1.2.0", | ||
"jasmine-core": "^2.4.1", | ||
"jshint-stylish": "^2.2.0", | ||
"karma": "^1.1.1", | ||
"karma-chrome-launcher": "^1.0.1", | ||
"karma-coverage": "^1.1.0", | ||
"karma-coveralls": "^1.1.2", | ||
"karma-firefox-launcher": "^1.0.0", | ||
"karma-jasmine": "^1.0.2", | ||
@@ -44,3 +41,4 @@ "karma-phantomjs-launcher": "^1.0.0", | ||
"angular": "^1.2.24", | ||
"angular-wheelie": "^1.1.0" | ||
"angular-wheelie": "^1.1.0", | ||
"angular-scrollie": "^1.0.0" | ||
}, | ||
@@ -47,0 +45,0 @@ "repository": { |
@@ -8,3 +8,3 @@ # angular-snapscroll | ||
- Only requires angular core | ||
- 5.7kB when minified, 2.1kB when gzipped | ||
- 6.2kB when minified, 2.3kB when gzipped | ||
@@ -16,3 +16,3 @@ ### [Demo](http://joelmukuthu.github.io/angular-snapscroll/) | ||
```sh | ||
bower install angular-snapscroll angular-wheelie | ||
bower install angular-snapscroll angular-wheelie angular-scrollie | ||
``` | ||
@@ -25,3 +25,5 @@ Or with npm: | ||
Note that in this case you also need to download the | ||
[latest angular-wheelie release](https://github.com/joelmukuthu/angular-wheelie/releases/latest). | ||
[latest angular-wheelie release](https://github.com/joelmukuthu/angular-wheelie/releases/latest) | ||
and the | ||
[latest angular-scrollie release](https://github.com/joelmukuthu/angular-scrollie/releases/latest). | ||
@@ -33,2 +35,3 @@ ### Usage | ||
<script src="angular-wheelie/dist/angular-wheelie.min.js"></script> | ||
<script src="angular-scrollie/dist/angular-scrollie.min.js"></script> | ||
<script src="angular-snapscroll/dist/angular-snapscroll.min.js"></script> | ||
@@ -35,0 +38,0 @@ ``` |
(function () { | ||
'use strict'; | ||
angular.module('snapscroll') | ||
.directive('fitWindowHeight', [ | ||
'$window', | ||
'$timeout', | ||
'defaultSnapscrollResizeDelay', | ||
function ( | ||
$window, | ||
$timeout, | ||
defaultSnapscrollResizeDelay | ||
) { | ||
return { | ||
restrict: 'A', | ||
require: 'snapscroll', | ||
link: function (scope, element, attributes, snapscroll) { | ||
var windowElement, | ||
resizePromise, | ||
resizeDelay = attributes.resizeDelay; | ||
angular.module('snapscroll') | ||
.directive('fitWindowHeight', ['$window', '$timeout', 'defaultSnapscrollResizeDelay', | ||
function ($window, $timeout, defaultSnapscrollResizeDelay) { | ||
return { | ||
restrict: 'A', | ||
require: 'snapscroll', | ||
link: function (scope, element, attributes, snapscroll) { | ||
var windowElement, | ||
resizePromise, | ||
resizeDelay = attributes.resizeDelay; | ||
function onWindowResize() { | ||
if (resizeDelay === false) { | ||
snapscroll.setSnapHeight($window.innerHeight); | ||
} else { | ||
$timeout.cancel(resizePromise); | ||
resizePromise = $timeout(function () { | ||
snapscroll.setSnapHeight($window.innerHeight); | ||
}, resizeDelay); | ||
} | ||
} | ||
function onWindowResize() { | ||
if (resizeDelay === false) { | ||
snapscroll.setSnapHeight($window.innerHeight); | ||
} else { | ||
$timeout.cancel(resizePromise); | ||
resizePromise = $timeout(function () { | ||
snapscroll.setSnapHeight($window.innerHeight); | ||
}, resizeDelay); | ||
} | ||
} | ||
function init() { | ||
if (resizeDelay === 'false') { | ||
resizeDelay = false; | ||
} else { | ||
resizeDelay = parseInt(resizeDelay, 10); | ||
if (isNaN(resizeDelay)) { | ||
resizeDelay = defaultSnapscrollResizeDelay; | ||
} | ||
} | ||
function init() { | ||
if (resizeDelay === 'false') { | ||
resizeDelay = false; | ||
} else { | ||
resizeDelay = parseInt(resizeDelay, 10); | ||
if (isNaN(resizeDelay)) { | ||
resizeDelay = defaultSnapscrollResizeDelay; | ||
} | ||
} | ||
// set initial snapHeight | ||
snapscroll.setSnapHeight($window.innerHeight); | ||
// set initial snapHeight | ||
snapscroll.setSnapHeight($window.innerHeight); | ||
// update snapHeight on window resize | ||
windowElement = angular.element($window); | ||
windowElement.on('resize', onWindowResize); | ||
scope.$on('$destroy', function () { | ||
windowElement.off('resize'); | ||
}); | ||
} | ||
// update snapHeight on window resize | ||
windowElement = angular.element($window); | ||
windowElement.on('resize', onWindowResize); | ||
scope.$on('$destroy', function () { | ||
windowElement.off('resize'); | ||
}); | ||
} | ||
init(); | ||
} | ||
}; | ||
}]); | ||
init(); | ||
} | ||
}; | ||
}]); | ||
})(); |
(function () { | ||
'use strict'; | ||
function isNumber(value) { | ||
return angular.isNumber(value) && !isNaN(value); | ||
} | ||
var scopeObject = { | ||
snapIndex: '=?', | ||
snapHeight: '=?', | ||
beforeSnap: '&', | ||
afterSnap: '&', | ||
snapAnimation: '=?' | ||
}; | ||
var isDefined = angular.isDefined; | ||
var isUndefined = angular.isUndefined; | ||
var isFunction = angular.isFunction; | ||
var forEach = angular.forEach; | ||
var controller = ['$scope', function ($scope) { | ||
this.setSnapHeight = function (height) { | ||
$scope.snapHeight = height; | ||
var scopeObject = { | ||
enabled: '=snapscroll', | ||
snapIndex: '=?', | ||
snapHeight: '=?', | ||
beforeSnap: '&', | ||
afterSnap: '&', | ||
snapAnimation: '=?' | ||
}; | ||
}]; | ||
var isNumber = function(value) { | ||
return angular.isNumber(value) && !isNaN(value); | ||
}; | ||
var controller = ['$scope', function ($scope) { | ||
this.setSnapHeight = function (height) { | ||
$scope.snapHeight = height; | ||
}; | ||
}]; | ||
var watchSnapHeight = function (scope, callback) { | ||
scope.$watch('snapHeight', function (snapHeight, previousSnapHeight) { | ||
if (angular.isUndefined(snapHeight)) { | ||
return; | ||
} | ||
if (!isNumber(snapHeight)) { | ||
if (isNumber(previousSnapHeight)) { | ||
scope.snapHeight = previousSnapHeight; | ||
} | ||
return; | ||
} | ||
if (angular.isFunction(callback)) { | ||
callback(snapHeight); | ||
} | ||
}); | ||
}; | ||
var snapscrollAsAnAttribute = [ | ||
'$timeout', | ||
'$document', | ||
'wheelie', | ||
'scrollie', | ||
'defaultSnapscrollScrollEasing', | ||
'defaultSnapscrollScrollDelay', | ||
'defaultSnapscrollSnapDuration', | ||
'defaultSnapscrollBindScrollTimeout', | ||
function ( | ||
$timeout, | ||
$document, | ||
wheelie, | ||
scrollie, | ||
defaultSnapscrollScrollEasing, | ||
defaultSnapscrollScrollDelay, | ||
defaultSnapscrollSnapDuration, | ||
defaultSnapscrollBindScrollTimeout | ||
) { | ||
return { | ||
restrict: 'A', | ||
scope: scopeObject, | ||
controller: controller, | ||
link: function (scope, element, attributes) { | ||
function getChildren() { | ||
return element.children(); | ||
} | ||
var watchSnapIndex = function (scope, callback) { | ||
scope.$watch('snapIndex', function (snapIndex, previousSnapIndex) { | ||
if (angular.isUndefined(snapIndex)) { | ||
scope.snapIndex = 0; | ||
return; | ||
} | ||
if (!isNumber(snapIndex)) { | ||
if (isNumber(previousSnapIndex)) { | ||
scope.snapIndex = previousSnapIndex; | ||
} else { | ||
scope.snapIndex = 0; | ||
} | ||
return; | ||
} | ||
if (snapIndex % 1 !== 0) { | ||
scope.snapIndex = Math.round(snapIndex); | ||
return; | ||
} | ||
if (scope.ignoreThisSnapIndexChange) { | ||
scope.ignoreThisSnapIndexChange = undefined; | ||
return; | ||
} | ||
if (!scope.isValid(snapIndex)) { | ||
scope.ignoreThisSnapIndexChange = true; | ||
scope.snapIndex = previousSnapIndex; | ||
scope.snapDirection = 'none'; | ||
return; | ||
} | ||
if (scope.beforeSnap({snapIndex: snapIndex}) === false) { | ||
scope.ignoreThisSnapIndexChange = true; | ||
scope.snapIndex = previousSnapIndex; | ||
return; | ||
} | ||
if (angular.isFunction(callback)) { | ||
if (snapIndex > previousSnapIndex) { | ||
scope.snapDirection = 'up'; | ||
} else if (snapIndex < previousSnapIndex) { | ||
scope.snapDirection = 'down'; | ||
} | ||
callback(snapIndex, function () { | ||
scope.snapDirection = 'none'; | ||
scope.afterSnap({snapIndex: snapIndex}); | ||
}); | ||
} | ||
}); | ||
}; | ||
function getHeight(domElement) { | ||
return domElement.offsetHeight; | ||
} | ||
var initWheelEvents = function (wheelie, scope, element) { | ||
function maybePreventBubbling(e, bubbleUp) { | ||
if (!bubbleUp) { | ||
e.stopPropagation(); | ||
} | ||
} | ||
function getChildHeight(snapIndex) { | ||
return getHeight(getChildren()[snapIndex]); | ||
} | ||
wheelie.bind(element, { | ||
up: function (e) { | ||
e.preventDefault(); | ||
function getSnapHeight() { | ||
return getHeight(element[0]); | ||
} | ||
var bubbleUp; | ||
if (scope.snapDirection !== 'down') { | ||
if (scope.snapIndex - 1 < scope.snapIndexMin()) { | ||
bubbleUp = true; | ||
} else { | ||
bubbleUp = false; | ||
scope.$apply(function () { | ||
scope.snapIndex -= 1; | ||
}); | ||
} | ||
} | ||
function getScrollHeight() { | ||
return element[0].scrollHeight; | ||
} | ||
maybePreventBubbling(e, bubbleUp); | ||
}, | ||
down: function (e) { | ||
e.preventDefault(); | ||
function rectifyScrollTop(scrollTop) { | ||
var maxScrollTop = getScrollHeight() - getSnapHeight(); | ||
if (scrollTop > maxScrollTop) { | ||
return maxScrollTop; | ||
} | ||
return scrollTop; | ||
} | ||
var bubbleUp; | ||
if (scope.snapDirection !== 'up') { | ||
if (scope.snapIndex + 1 > scope.scopeIndexMax()) { | ||
bubbleUp = true; | ||
} else { | ||
bubbleUp = false; | ||
scope.$apply(function () { | ||
scope.snapIndex += 1; | ||
}); | ||
} | ||
} | ||
function getScrollTop(compositeIndex, previousCompositeIndex) { | ||
var snapIndex = compositeIndex[0]; | ||
var innerSnapIndex = compositeIndex[1]; | ||
maybePreventBubbling(e, bubbleUp); | ||
} | ||
}); | ||
var scrollTop = 0; | ||
var children = getChildren(); | ||
for (var i = 0; i < snapIndex; i++) { | ||
scrollTop += getHeight(children[i]); | ||
} | ||
scope.$on('$destroy', function () { | ||
wheelie.unbind(element); | ||
}); | ||
}; | ||
if (innerSnapIndex === 0) { | ||
return rectifyScrollTop(scrollTop); | ||
} | ||
var snapscrollAsAnAttribute = ['$timeout', 'scroll', 'wheelie', 'defaultSnapscrollScrollDelay', 'defaultSnapscrollSnapDuration', 'defaultSnapscrollBindScrollTimeout', | ||
function ($timeout, scroll, wheelie, defaultSnapscrollScrollDelay, defaultSnapscrollSnapDuration, defaultSnapscrollBindScrollTimeout) { | ||
return { | ||
restrict: 'A', | ||
scope: scopeObject, | ||
controller: controller, | ||
link: function (scope, element, attributes) { | ||
var init, | ||
snapTo, | ||
onScroll, | ||
bindScroll, | ||
scrollBound, | ||
unbindScroll, | ||
scrollPromise, | ||
bindScrollPromise, | ||
snapEasing = attributes.snapEasing, | ||
scrollDelay = attributes.scrollDelay, | ||
snapDuration = attributes.snapDuration, | ||
preventSnappingAfterManualScroll = angular.isDefined(attributes.preventSnappingAfterManualScroll); | ||
var snapHeight = getSnapHeight(); | ||
var childHeight = getHeight(children[snapIndex]); | ||
var innerScrollTop; | ||
if (isDefined(previousCompositeIndex) && | ||
innerSnapIndex < previousCompositeIndex[1]) { | ||
innerScrollTop = childHeight; | ||
for (var j = innerSnapIndex; j >= 0; j--) { | ||
innerScrollTop -= snapHeight; | ||
} | ||
} else { | ||
innerScrollTop = 0; | ||
for (var k = 0; k < innerSnapIndex; k++) { | ||
innerScrollTop += snapHeight; | ||
} | ||
var overflow = innerScrollTop + snapHeight - childHeight; | ||
if (overflow > 0) { | ||
innerScrollTop -= overflow; | ||
} | ||
} | ||
function getScrollTop(index) { | ||
var snaps = element.children(); | ||
var combinedHeight = 0; | ||
for (var i = 0; i < index; i++) { | ||
combinedHeight += parseInt(snaps[i].offsetHeight, 10); | ||
} | ||
return combinedHeight; | ||
} | ||
return rectifyScrollTop(scrollTop + innerScrollTop); | ||
} | ||
snapTo = function (index, afterSnap) { | ||
var args, | ||
top = getScrollTop(index); | ||
if (scope.snapAnimation) { | ||
if (angular.isDefined(snapEasing)) { | ||
args = [element, top, snapDuration, snapEasing]; | ||
} else { | ||
args = [element, top, snapDuration]; | ||
} | ||
} else { | ||
args = [element, top]; | ||
} | ||
if (!preventSnappingAfterManualScroll && scrollBound) { | ||
unbindScroll(); | ||
} | ||
scroll.to.apply(scroll, args).then(function () { | ||
if (angular.isFunction(afterSnap)) { | ||
afterSnap(); | ||
} | ||
if (!preventSnappingAfterManualScroll) { | ||
// bind scroll after a timeout | ||
$timeout.cancel(bindScrollPromise); | ||
bindScrollPromise = $timeout(bindScroll, defaultSnapscrollBindScrollTimeout); | ||
} | ||
}); | ||
}; | ||
function snapTo(compositeIndex, previousCompositeIndex) { | ||
var snapIndex = compositeIndex[0]; | ||
var isSnapIndexChanged = isUndefined(previousCompositeIndex) || | ||
snapIndex !== previousCompositeIndex[0]; | ||
if (isSnapIndexChanged) { | ||
var returnValue = scope.beforeSnap({ | ||
snapIndex: snapIndex | ||
}); | ||
if (returnValue === false) { | ||
if (isDefined(previousCompositeIndex)) { | ||
scope.ignoreCompositeIndexChange = true; | ||
scope.compositeIndex = previousCompositeIndex; | ||
} | ||
return; | ||
} | ||
if (isNumber(returnValue)) { | ||
scope.snapIndex = returnValue; | ||
return; | ||
} | ||
} | ||
function getSnapIndex(scrollTop) { | ||
var snapIndex = -1, | ||
snaps = element.children(), | ||
lastSnapHeight; | ||
while (scrollTop > 0) { | ||
scrollTop -= lastSnapHeight = snaps[++snapIndex].offsetHeight; | ||
} | ||
if ((lastSnapHeight / 2) >= -scrollTop) { | ||
snapIndex += 1; | ||
} | ||
return snapIndex; | ||
} | ||
return scrollTo(getScrollTop( | ||
compositeIndex, | ||
previousCompositeIndex | ||
)).then(function () { | ||
if (isSnapIndexChanged) { | ||
scope.afterSnap({ | ||
snapIndex: snapIndex | ||
}); | ||
} | ||
}); | ||
} | ||
onScroll = function () { | ||
var snap = function () { | ||
var newSnapIndex = getSnapIndex(element[0].scrollTop); | ||
if (scope.snapIndex === newSnapIndex) { | ||
snapTo(newSnapIndex); | ||
} else { | ||
scope.$apply(function () { | ||
scope.snapIndex = newSnapIndex; | ||
}); | ||
} | ||
}; | ||
scroll.stop(element); | ||
if (scrollDelay === false) { | ||
snap(); | ||
} else { | ||
$timeout.cancel(scrollPromise); | ||
scrollPromise = $timeout(snap, scrollDelay); | ||
} | ||
}; | ||
function getCurrentScrollTop() { | ||
return element[0].scrollTop; | ||
} | ||
bindScroll = function () { | ||
// if the bindScroll timeout expires while snapping is ongoing, restart the timer | ||
if (scope.snapDirection !== 'none') { | ||
bindScrollPromise = $timeout(bindScroll, defaultSnapscrollBindScrollTimeout); | ||
return; | ||
} | ||
element.on('scroll', onScroll); | ||
scrollBound = true; | ||
}; | ||
function scrollTo(scrollTop) { | ||
var args; | ||
if (!scope.snapAnimation) { | ||
args = [ | ||
element, | ||
scrollTop | ||
]; | ||
} else if (isUndefined(scope.snapEasing)) { | ||
// TODO: add tests for this. Will require refactoring | ||
// the default values into an object, which is a good | ||
// change anyway | ||
args = [ | ||
element, | ||
scrollTop, | ||
scope.snapDuration | ||
]; | ||
} else { | ||
args = [ | ||
element, | ||
scrollTop, | ||
scope.snapDuration, | ||
scope.snapEasing | ||
]; | ||
} | ||
unbindScroll = function () { | ||
element.off('scroll', onScroll); | ||
scrollBound = false; | ||
}; | ||
var currentScrollTop = getCurrentScrollTop(); | ||
if (scrollTop > currentScrollTop) { | ||
scope.snapDirection = 'down'; | ||
} else if (scrollTop < currentScrollTop) { | ||
scope.snapDirection = 'up'; | ||
} else { | ||
scope.snapDirection = 'same'; | ||
} | ||
init = function () { | ||
if (scrollDelay === 'false') { | ||
scrollDelay = false; | ||
} else { | ||
scrollDelay = parseInt(scrollDelay, 10); | ||
if (isNaN(scrollDelay)) { | ||
scrollDelay = defaultSnapscrollScrollDelay; | ||
} | ||
} | ||
unbindScroll(); | ||
return scrollie.to.apply(scrollie, args).then(function () { | ||
scope.snapDirection = undefined; | ||
bindScrollAfterDelay(); | ||
}); | ||
} | ||
if (angular.isDefined(snapEasing)) { | ||
snapEasing = scope.$parent.$eval(snapEasing); | ||
} | ||
function isScrollable() { | ||
var snapHeight = getSnapHeight(); | ||
if (!snapHeight) { | ||
return false; | ||
} | ||
var children = getChildren(); | ||
if (!children.length) { | ||
return false; | ||
} | ||
var totalHeight = 0; | ||
forEach(children, function (child) { | ||
totalHeight += getHeight(child); | ||
}); | ||
if (totalHeight < snapHeight) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
snapDuration = parseInt(snapDuration, 10); | ||
if (isNaN(snapDuration)) { | ||
snapDuration = defaultSnapscrollSnapDuration; | ||
} | ||
function isSnapIndexValid(snapIndex) { | ||
return snapIndex >= 0 && | ||
snapIndex <= getChildren().length - 1; | ||
} | ||
scope.$watch('snapAnimation', function (animation) { | ||
if (animation === undefined) { | ||
scope.snapAnimation = true; | ||
} | ||
}); | ||
function snapIndexChanged(current, previous) { | ||
if (!isScrollable()) { | ||
return; | ||
} | ||
if (isUndefined(current)) { | ||
scope.snapIndex = 0; | ||
return; | ||
} | ||
if (!isNumber(current)) { | ||
if (!isNumber(previous)) { | ||
previous = 0; | ||
} | ||
scope.snapIndex = previous; | ||
return; | ||
} | ||
if (current % 1 !== 0) { | ||
scope.snapIndex = Math.round(current); | ||
return; | ||
} | ||
if (scope.ignoreSnapIndexChange === true) { | ||
scope.ignoreSnapIndexChange = undefined; | ||
return; | ||
} | ||
if (!isSnapIndexValid(current)) { | ||
if (!isSnapIndexValid(previous)) { | ||
previous = 0; | ||
} | ||
scope.ignoreSnapIndexChange = true; | ||
scope.snapIndex = previous; | ||
return; | ||
} | ||
scope.compositeIndex = [current, 0]; | ||
} | ||
scope.snapIndexMin = function () { | ||
return 0; | ||
}; | ||
function watchSnapIndex() { | ||
scope.unwatchSnapIndex = scope.$watch( | ||
'snapIndex', | ||
snapIndexChanged | ||
); | ||
} | ||
scope.scopeIndexMax = function () { | ||
return element.children().length - 1; | ||
}; | ||
function unwatchSnapIndex() { | ||
if (!isFunction(scope.unwatchSnapIndex)) { | ||
return; | ||
} | ||
scope.unwatchSnapIndex(); | ||
scope.unwatchSnapIndex = undefined; | ||
} | ||
scope.isValid = function (snapIndex) { | ||
return snapIndex >= scope.snapIndexMin() && snapIndex <= scope.scopeIndexMax(); | ||
}; | ||
function compositeIndexChanged(current, previous) { | ||
if (isUndefined(current)) { | ||
return; | ||
} | ||
var snapIndex = current[0]; | ||
if (scope.snapIndex !== snapIndex) { | ||
scope.ignoreSnapIndexChange = true; | ||
scope.snapIndex = snapIndex; | ||
} | ||
if (scope.ignoreCompositeIndexChange === true) { | ||
scope.ignoreCompositeIndexChange = undefined; | ||
return; | ||
} | ||
snapTo(current, previous); | ||
} | ||
if (element.css('overflowY') !== 'scroll') { | ||
element.css('overflowY', 'auto'); | ||
} | ||
function watchCompositeIndex() { | ||
scope.unwatchCompositeIndex = scope.$watchCollection( | ||
'compositeIndex', | ||
compositeIndexChanged | ||
); | ||
} | ||
watchSnapHeight(scope, function (snapHeight) { | ||
element.css('height', snapHeight + 'px'); | ||
var snaps = element.children(); | ||
if (snaps.length) { | ||
angular.forEach(snaps, function (snap) { | ||
angular.element(snap).css('height', snapHeight + 'px'); | ||
}); | ||
} | ||
snapTo(scope.snapIndex); | ||
}); | ||
function unwatchCompositeIndex() { | ||
if (!isFunction(scope.unwatchCompositeIndex)) { | ||
return; | ||
} | ||
scope.unwatchCompositeIndex(); | ||
scope.unwatchCompositeIndex = undefined; | ||
} | ||
watchSnapIndex(scope, snapTo); | ||
function getMaxInnerSnapIndex(snapIndex) { | ||
var snapHeight = getSnapHeight(); | ||
var childHeight = getChildHeight(snapIndex); | ||
if (childHeight <= snapHeight) { | ||
return 0; | ||
} | ||
var max = parseInt((childHeight / snapHeight), 10); | ||
if (childHeight % snapHeight === 0) { | ||
max -= 1; | ||
} | ||
return max; | ||
} | ||
if (!preventSnappingAfterManualScroll) { | ||
bindScroll(); | ||
scope.$on('$destroy', unbindScroll); | ||
} | ||
function isCompositeIndexValid(compositeIndex) { | ||
var snapIndex = compositeIndex[0]; | ||
var innerSnapIndex = compositeIndex[1]; | ||
if (innerSnapIndex < 0) { | ||
return isSnapIndexValid(snapIndex - 1); | ||
} | ||
if (innerSnapIndex > getMaxInnerSnapIndex(snapIndex)) { | ||
return isSnapIndexValid(snapIndex + 1); | ||
} | ||
return true; | ||
} | ||
initWheelEvents(wheelie, scope, element); | ||
}; | ||
function rectifyCompositeIndex(compositeIndex) { | ||
var snapIndex = compositeIndex[0]; | ||
var innerSnapIndex = compositeIndex[1]; | ||
if (innerSnapIndex < 0) { | ||
return [ | ||
snapIndex - 1, | ||
getMaxInnerSnapIndex(snapIndex - 1) | ||
]; | ||
} | ||
if (innerSnapIndex > getMaxInnerSnapIndex(snapIndex)) { | ||
return [snapIndex + 1, 0]; | ||
} | ||
return compositeIndex; | ||
} | ||
init(); | ||
function snap(direction) { | ||
if (!isScrollable()) { | ||
return; | ||
} | ||
if (scope.snapDirection === direction) { | ||
return true; | ||
} | ||
var snapIndex = scope.compositeIndex[0]; | ||
var innerSnapIndex = scope.compositeIndex[1]; | ||
var newInnerSnapIndex; | ||
if (direction === 'up') { | ||
newInnerSnapIndex = innerSnapIndex - 1; | ||
} | ||
if (direction === 'down') { | ||
newInnerSnapIndex = innerSnapIndex + 1; | ||
} | ||
var newCompositeIndex = [snapIndex, newInnerSnapIndex]; | ||
if (!isCompositeIndexValid(newCompositeIndex)) { | ||
return; | ||
} | ||
scope.$apply(function () { | ||
scope.compositeIndex = rectifyCompositeIndex( | ||
newCompositeIndex | ||
); | ||
}); | ||
return true; | ||
} | ||
function snapUp() { | ||
return snap('up'); | ||
} | ||
function snapDown() { | ||
return snap('down'); | ||
} | ||
function bindWheel() { | ||
wheelie.bind(element, { | ||
up: function (e) { | ||
e.preventDefault(); | ||
if (snapUp()) { | ||
e.stopPropagation(); | ||
} | ||
}, | ||
down: function (e) { | ||
e.preventDefault(); | ||
if (snapDown()) { | ||
e.stopPropagation(); | ||
} | ||
} | ||
}); | ||
} | ||
function unbindWheel() { | ||
wheelie.unbind(element); | ||
} | ||
function setHeight(angularElement, height) { | ||
angularElement.css('height', height + 'px'); | ||
} | ||
function snapHeightChanged(current, previous) { | ||
if (isUndefined(current)) { | ||
return; | ||
} | ||
if (!isNumber(current)) { | ||
if (isNumber(previous)) { | ||
scope.snapHeight = previous; | ||
} | ||
return; | ||
} | ||
setHeight(element, current); | ||
forEach(getChildren(), function (child) { | ||
setHeight(angular.element(child), current); | ||
}); | ||
if (isDefined(scope.snapIndex)) { | ||
if (isUndefined(scope.compositeIndex)) { | ||
scope.compositeIndex = [scope.snapIndex, 0]; | ||
} | ||
snapTo(scope.compositeIndex); | ||
} | ||
} | ||
function watchSnapHeight() { | ||
scope.unwatchSnapHeight = scope.$watch( | ||
'snapHeight', | ||
snapHeightChanged | ||
); | ||
} | ||
function unwatchSnapHeight() { | ||
if (!isFunction(scope.unwatchSnapHeight)) { | ||
return; | ||
} | ||
scope.unwatchSnapHeight(); | ||
scope.unwatchSnapHeight = undefined; | ||
} | ||
function getCompositeIndex(scrollTop) { | ||
var snapIndex = 0; | ||
var innerSnapIndex = 0; | ||
if (scrollTop > 0) { | ||
snapIndex = -1; | ||
var children = getChildren(); | ||
var childHeight; | ||
while (scrollTop > 0) { | ||
childHeight = getHeight(children[++snapIndex]); | ||
scrollTop -= childHeight; | ||
} | ||
var snapHeight = getSnapHeight(); | ||
if (childHeight > snapHeight) { | ||
scrollTop += childHeight - snapHeight; | ||
if (scrollTop >= snapHeight) { | ||
innerSnapIndex++; | ||
} | ||
while (scrollTop > 0) { | ||
innerSnapIndex++; | ||
scrollTop -= snapHeight; | ||
} | ||
if ((snapHeight / 2) >= -scrollTop) { | ||
innerSnapIndex += 1; | ||
} | ||
} else if ((childHeight / 2) >= -scrollTop) { | ||
snapIndex += 1; | ||
} | ||
} | ||
return rectifyCompositeIndex([snapIndex, innerSnapIndex]); | ||
} | ||
function onScroll() { | ||
function snapFromSrollTop() { | ||
var compositeIndex = getCompositeIndex( | ||
getCurrentScrollTop() | ||
); | ||
if (scope.compositeIndex[0] === compositeIndex[0] && | ||
scope.compositeIndex[1] === compositeIndex[1]) { | ||
snapTo(scope.compositeIndex); | ||
} else { | ||
scope.$apply(function () { | ||
scope.compositeIndex = compositeIndex; | ||
}); | ||
} | ||
} | ||
scrollie.stop(element); | ||
if (scope.scrollDelay === false) { | ||
snapFromSrollTop(); | ||
} else { | ||
$timeout.cancel(scope.scrollPromise); | ||
scope.scrollPromise = $timeout( | ||
function () { | ||
snapFromSrollTop(); | ||
scope.scrollPromise = undefined; | ||
}, | ||
scope.scrollDelay | ||
); | ||
} | ||
} | ||
function bindScroll() { | ||
if (scope.preventSnappingAfterManualScroll || | ||
scope.scrollBound) { | ||
return; | ||
} | ||
if (isDefined(scope.snapDirection)) { // still snapping | ||
// TODO: add tests for this | ||
bindScrollAfterDelay(); | ||
return; | ||
} | ||
element.on('scroll', onScroll); | ||
scope.scrollBound = true; | ||
} | ||
function unbindScroll() { | ||
if (!scope.scrollBound) { | ||
return; | ||
} | ||
element.off('scroll', onScroll); | ||
scope.scrollBound = false; | ||
} | ||
function bindScrollAfterDelay() { | ||
if (scope.preventSnappingAfterManualScroll) { | ||
return; | ||
} | ||
if (scope.bindScrollPromise) { | ||
$timeout.cancel(scope.bindScrollPromise); | ||
} | ||
scope.bindScrollPromise = $timeout( | ||
function () { | ||
bindScroll(); | ||
scope.bindScrollPromise = undefined; | ||
}, | ||
defaultSnapscrollBindScrollTimeout | ||
); | ||
} | ||
function onKeyDown(e) { | ||
if (e.originalEvent) { | ||
e = e.originalEvent; | ||
} | ||
var handler; | ||
var keyCode = e.keyCode; | ||
if (keyCode === 38) { | ||
handler = snapUp; | ||
} | ||
if (keyCode === 40) { | ||
handler = snapDown; | ||
} | ||
if (handler) { | ||
e.preventDefault(); | ||
handler(); | ||
} | ||
} | ||
function bindArrowKeys() { | ||
if (!scope.enableArrowKeys || scope.arrowKeysBound) { | ||
return; | ||
} | ||
$document.on('keydown', onKeyDown); | ||
scope.arrowKeysBound = true; | ||
} | ||
function unbindArrowKeys() { | ||
if (!scope.arrowKeysBound) { | ||
return; | ||
} | ||
$document.off('keydown', onKeyDown); | ||
scope.arrowKeysBound = false; | ||
} | ||
function init() { | ||
var scrollDelay = attributes.scrollDelay; | ||
if (scrollDelay === 'false') { | ||
scope.scrollDelay = false; | ||
} else { | ||
scrollDelay = parseInt(scrollDelay, 10); | ||
if (isNaN(scrollDelay)) { | ||
scrollDelay = defaultSnapscrollScrollDelay; | ||
} | ||
scope.scrollDelay = scrollDelay; | ||
} | ||
var snapEasing = attributes.snapEasing; | ||
if (isDefined(snapEasing)) { | ||
scope.snapEasing = scope.$parent.$eval(snapEasing); | ||
} else if (isFunction(defaultSnapscrollScrollEasing)) { | ||
scope.snapEasing = defaultSnapscrollScrollEasing; | ||
} | ||
var snapDuration = parseInt(attributes.snapDuration, 10); | ||
if (isNaN(snapDuration)) { | ||
snapDuration = defaultSnapscrollSnapDuration; | ||
} | ||
scope.snapDuration = snapDuration; | ||
// TODO: perform initial snap without animation | ||
if (isUndefined(scope.snapAnimation)) { | ||
scope.snapAnimation = true; | ||
} | ||
scope.enableArrowKeys = isDefined( | ||
attributes.enableArrowKeys | ||
); | ||
scope.preventSnappingAfterManualScroll = isDefined( | ||
attributes.preventSnappingAfterManualScroll | ||
); | ||
if (element.css('overflowY') !== 'scroll') { | ||
element.css('overflowY', 'auto'); | ||
} | ||
scope.$watch('enabled', function (current, previous) { | ||
function updateCompositeIndexFromScrollTop() { | ||
if (scope.preventSnappingAfterManualScroll) { | ||
return; | ||
} | ||
scope.compositeIndex = getCompositeIndex( | ||
getCurrentScrollTop() | ||
); | ||
} | ||
if (current !== false) { | ||
if (previous === false) { | ||
updateCompositeIndexFromScrollTop(); | ||
} | ||
watchCompositeIndex(); | ||
watchSnapIndex(); | ||
watchSnapHeight(); | ||
bindScroll(); | ||
bindWheel(); | ||
bindArrowKeys(); | ||
} else { | ||
unwatchCompositeIndex(); | ||
unwatchSnapIndex(); | ||
unwatchSnapHeight(); | ||
unbindScroll(); | ||
unbindWheel(); | ||
unbindArrowKeys(); | ||
} | ||
}); | ||
scope.$on('$destroy', function () { | ||
if (scope.enabled !== false) { | ||
unbindScroll(); | ||
unbindWheel(); | ||
unbindArrowKeys(); | ||
} | ||
}); | ||
} | ||
init(); | ||
} | ||
}; | ||
} | ||
}; | ||
} | ||
]; | ||
]; | ||
angular.module('snapscroll') | ||
.directive('snapscroll', snapscrollAsAnAttribute); | ||
angular.module('snapscroll') | ||
.directive('snapscroll', snapscrollAsAnAttribute); | ||
})(); |
(function () { | ||
'use strict'; | ||
function easeInOutQuad(t, b, c, d) { | ||
t /= d/2; | ||
if (t < 1) { | ||
return c/2*t*t + b; | ||
} | ||
t--; | ||
return -c/2 * (t*(t-2) - 1) + b; | ||
} | ||
angular | ||
.module('snapscroll', ['wheelie']) | ||
.value('defaultSnapscrollScrollEasing', easeInOutQuad) | ||
.value('defaultSnapscrollScrollDelay', 250) | ||
.value('defaultSnapscrollSnapDuration', 800) | ||
.value('defaultSnapscrollResizeDelay', 400) | ||
.value('defaultSnapscrollBindScrollTimeout', 400); | ||
angular | ||
.module('snapscroll', ['wheelie', 'scrollie']) | ||
.value('defaultSnapscrollScrollEasing', undefined) | ||
.value('defaultSnapscrollScrollDelay', 250) | ||
.value('defaultSnapscrollSnapDuration', 800) | ||
.value('defaultSnapscrollResizeDelay', 400) | ||
.value('defaultSnapscrollBindScrollTimeout', 400); | ||
})(); |
// Karma configuration | ||
// Generated on Sat Sep 13 2014 18:48:29 GMT+0300 (EEST) | ||
module.exports = function(config) { | ||
config.set({ | ||
var reporters = [ | ||
'progress', | ||
'coverage' | ||
]; | ||
// base path that will be used to resolve all patterns (eg. files, exclude) | ||
basePath: '../', | ||
var coverageType = 'html'; | ||
if (process.env.TRAVIS) { | ||
coverageType = 'lcov'; | ||
reporters.push('coveralls'); | ||
} | ||
// frameworks to use | ||
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter | ||
frameworks: ['jasmine'], | ||
module.exports = function (config) { | ||
config.set({ | ||
// base path that will be used to resolve all patterns (eg. files, exclude) | ||
basePath: '../', | ||
// list of files / patterns to load in the browser | ||
files: [ | ||
'node_modules/angular/angular.js', | ||
'node_modules/angular-mocks/angular-mocks.js', | ||
'node_modules/angular-wheelie/dist/angular-wheelie.js', | ||
'src/*.js', | ||
'src/**/*.js', | ||
'test/spec/**/*.js' | ||
], | ||
// frameworks to use | ||
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter | ||
frameworks: ['jasmine'], | ||
// list of files to exclude | ||
exclude: [ | ||
], | ||
// list of files / patterns to load in the browser | ||
files: [ | ||
'node_modules/angular/angular.js', | ||
'node_modules/angular-mocks/angular-mocks.js', | ||
'node_modules/angular-wheelie/dist/angular-wheelie.js', | ||
'node_modules/angular-scrollie/dist/angular-scrollie.js', | ||
'src/*.js', | ||
'src/**/*.js', | ||
'test/spec/**/*.js' | ||
], | ||
// preprocess matching files before serving them to the browser | ||
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor | ||
preprocessors: { | ||
// generage coverage for these files | ||
'src/**/*.js': ['coverage'] | ||
}, | ||
// list of files to exclude | ||
exclude: [ | ||
], | ||
// test results reporter to use | ||
// possible values: 'dots', 'progress' | ||
// available reporters: https://npmjs.org/browse/keyword/karma-reporter | ||
// coverage reporter generates tests' coverage | ||
reporters: ['progress', 'coverage','coveralls'], | ||
// preprocess matching files before serving them to the browser | ||
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor | ||
preprocessors: { | ||
// generage coverage for these files | ||
'src/**/*.js': ['coverage'] | ||
}, | ||
// configure coverage reporter | ||
coverageReporter: { | ||
type : 'lcov', | ||
dir : 'coverage/' | ||
}, | ||
// test results reporter to use | ||
// possible values: 'dots', 'progress' | ||
// available reporters: https://npmjs.org/browse/keyword/karma-reporter | ||
// coverage reporter generates tests' coverage | ||
reporters: reporters, | ||
// web server port | ||
port: 9876, | ||
// configure coverage reporter | ||
coverageReporter: { | ||
dir: 'coverage/', | ||
type: coverageType | ||
}, | ||
// enable / disable colors in the output (reporters and logs) | ||
colors: true, | ||
// web server port | ||
port: 9876, | ||
// level of logging | ||
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG | ||
logLevel: config.LOG_INFO, | ||
// enable / disable colors in the output (reporters and logs) | ||
colors: true, | ||
// enable / disable watching file and executing tests whenever any file changes | ||
autoWatch: true, | ||
// level of logging | ||
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG | ||
logLevel: config.LOG_INFO, | ||
plugins: [ | ||
'karma-jasmine', | ||
'karma-coverage', | ||
'karma-coveralls', | ||
// 'karma-chrome-launcher', | ||
// 'karma-firefox-launcher', | ||
'karma-phantomjs-launcher' | ||
], | ||
// enable / disable watching file and executing tests whenever any file changes | ||
autoWatch: true, | ||
// start these browsers | ||
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher | ||
browsers: [ | ||
// 'Chrome', | ||
// 'Firefox', | ||
'PhantomJS' | ||
], | ||
plugins: [ | ||
'karma-jasmine', | ||
'karma-coverage', | ||
'karma-coveralls', | ||
'karma-phantomjs-launcher' | ||
], | ||
// Continuous Integration mode | ||
// if true, Karma captures browsers, runs the tests and exits | ||
singleRun: true | ||
}); | ||
// start these browsers | ||
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher | ||
browsers: [ | ||
'PhantomJS' | ||
], | ||
// Continuous Integration mode | ||
// if true, Karma captures browsers, runs the tests and exits | ||
singleRun: true | ||
}); | ||
}; |
@@ -1,173 +0,165 @@ | ||
'use strict'; | ||
describe('Directive: fitWindowHeight', function () { | ||
var $compile, | ||
$scope, | ||
snapHeightMock; | ||
var $compile, | ||
$scope, | ||
snapHeightMock; | ||
beforeEach(module('snapscroll')); | ||
beforeEach(module('snapscroll')); | ||
beforeEach(module(function ($provide) { | ||
// use $provide.factory() for mocking directives, not $provide.value() since directives are factories | ||
$provide.factory('snapscrollDirective', function () { | ||
// very important to return an array of directive definitions!! that's how angular works | ||
return [{ | ||
scope: {}, | ||
restrict: 'A', | ||
name: 'snapscroll', | ||
controller: function () { | ||
this.setSnapHeight = function (height) { | ||
snapHeightMock = height; | ||
}; | ||
} | ||
}]; | ||
}); | ||
})); | ||
beforeEach(module(function ($provide) { | ||
// use $provide.factory() for mocking directives, not $provide.value() | ||
// since directives are factories | ||
$provide.factory('snapscrollDirective', function () { | ||
// very important to return an array of directive definitions!! | ||
// that's how angular works | ||
return [{ | ||
scope: {}, | ||
restrict: 'A', | ||
name: 'snapscroll', | ||
controller: function () { | ||
this.setSnapHeight = function (height) { | ||
snapHeightMock = height; | ||
}; | ||
} | ||
}]; | ||
}); | ||
})); | ||
beforeEach(inject(function (_$compile_, _$rootScope_) { | ||
$compile = _$compile_; | ||
$scope = _$rootScope_.$new(); | ||
})); | ||
beforeEach(inject(function (_$compile_, _$rootScope_) { | ||
$compile = _$compile_; | ||
$scope = _$rootScope_.$new(); | ||
})); | ||
function compileElement(html) { | ||
var element = angular.element(html); | ||
element = $compile(element)($scope); | ||
$scope.$digest(); | ||
return element; | ||
} | ||
function compileElement(html) { | ||
var element = angular.element(html); | ||
element = $compile(element)($scope); | ||
$scope.$digest(); | ||
return element; | ||
} | ||
function testSetsSnapHeight(html, $window) { | ||
var element; | ||
$window.innerHeight = 400; | ||
element = compileElement(html); | ||
expect(snapHeightMock).toBe(400); | ||
} | ||
function testSetsSnapHeight(html, $window) { | ||
$window.innerHeight = 400; | ||
compileElement(html); | ||
expect(snapHeightMock).toBe(400); | ||
} | ||
function testUpdatesSnapHeightOnWindowResize(html, $window, $timeout) { | ||
var element; | ||
$window.innerHeight = 400; | ||
element = compileElement(html); | ||
$window.innerHeight = 200; | ||
angular.element($window).triggerHandler('resize'); | ||
expect(snapHeightMock).toBe(400); | ||
$timeout.flush(); | ||
expect(snapHeightMock).toBe(200); | ||
} | ||
function testUpdatesSnapHeightOnWindowResize(html, $window, $timeout) { | ||
$window.innerHeight = 400; | ||
compileElement(html); | ||
$window.innerHeight = 200; | ||
angular.element($window).triggerHandler('resize'); | ||
expect(snapHeightMock).toBe(400); | ||
$timeout.flush(); | ||
expect(snapHeightMock).toBe(200); | ||
} | ||
function testDefaultsResizeDelayToTheValueOfDefaulSnapscrollSnapToWindowHeightResizeDelay(html, $window, $timeout, defaultSnapscrollResizeDelay) { | ||
var element; | ||
$window.innerHeight = 400; | ||
element = compileElement(html); | ||
$window.innerHeight = 200; | ||
angular.element($window).triggerHandler('resize'); | ||
$timeout.flush(defaultSnapscrollResizeDelay - 1); | ||
expect(snapHeightMock).toBe(400); | ||
$timeout.flush(1); | ||
expect(snapHeightMock).toBe(200); | ||
} | ||
function testDefaultsResizeDelayToTheValueOfDefaulSnapscrollSnapToWindowHeightResizeDelay(html, $window, $timeout, defaultSnapscrollResizeDelay) { | ||
$window.innerHeight = 400; | ||
compileElement(html); | ||
$window.innerHeight = 200; | ||
angular.element($window).triggerHandler('resize'); | ||
$timeout.flush(defaultSnapscrollResizeDelay - 1); | ||
expect(snapHeightMock).toBe(400); | ||
$timeout.flush(1); | ||
expect(snapHeightMock).toBe(200); | ||
} | ||
function testAllowsSettingResizeDelay(html, $window, $timeout) { | ||
var element; | ||
$window.innerHeight = 400; | ||
element = compileElement(html); | ||
$window.innerHeight = 200; | ||
angular.element($window).triggerHandler('resize'); | ||
$timeout.flush(499); | ||
expect(snapHeightMock).toBe(400); | ||
$timeout.flush(1); | ||
expect(snapHeightMock).toBe(200); | ||
} | ||
function testAllowsSettingResizeDelay(html, $window, $timeout) { | ||
$window.innerHeight = 400; | ||
compileElement(html); | ||
$window.innerHeight = 200; | ||
angular.element($window).triggerHandler('resize'); | ||
$timeout.flush(499); | ||
expect(snapHeightMock).toBe(400); | ||
$timeout.flush(1); | ||
expect(snapHeightMock).toBe(200); | ||
} | ||
function testDoesNotAllowSettingResizeDelayWithAnExpression(html, $window, $timeout) { | ||
var element; | ||
$window.innerHeight = 400; | ||
element = compileElement(html); | ||
$window.innerHeight = 200; | ||
angular.element($window).triggerHandler('resize'); | ||
$timeout.flush(499); | ||
expect(snapHeightMock).toBe(200); | ||
$timeout.flush(1); | ||
expect(snapHeightMock).toBe(200); | ||
} | ||
function testDoesNotAllowSettingResizeDelayWithAnExpression(html, $window, $timeout) { | ||
$window.innerHeight = 400; | ||
compileElement(html); | ||
$window.innerHeight = 200; | ||
angular.element($window).triggerHandler('resize'); | ||
$timeout.flush(499); | ||
expect(snapHeightMock).toBe(200); | ||
$timeout.flush(1); | ||
expect(snapHeightMock).toBe(200); | ||
} | ||
function testDefaultsResizeDelayToTheValueOfDefaulSnapscrollSnapToWindowHeightResizeDelayIfBadTimeoutIsProvided(html, $window, $timeout, defaultSnapscrollResizeDelay) { | ||
var element; | ||
$window.innerHeight = 400; | ||
element = compileElement(html); | ||
$window.innerHeight = 200; | ||
angular.element($window).triggerHandler('resize'); | ||
$timeout.flush(defaultSnapscrollResizeDelay - 1); | ||
expect(snapHeightMock).toBe(400); | ||
$timeout.flush(1); | ||
expect(snapHeightMock).toBe(200); | ||
} | ||
function testDefaultsResizeDelayToTheValueOfDefaulSnapscrollSnapToWindowHeightResizeDelayIfBadTimeoutIsProvided(html, $window, $timeout, defaultSnapscrollResizeDelay) { | ||
$window.innerHeight = 400; | ||
compileElement(html); | ||
$window.innerHeight = 200; | ||
angular.element($window).triggerHandler('resize'); | ||
$timeout.flush(defaultSnapscrollResizeDelay - 1); | ||
expect(snapHeightMock).toBe(400); | ||
$timeout.flush(1); | ||
expect(snapHeightMock).toBe(200); | ||
} | ||
function testAllowsTurningOffResizeDelay(html, $window, $timeout) { | ||
var element; | ||
$window.innerHeight = 400; | ||
element = compileElement(html); | ||
$window.innerHeight = 200; | ||
angular.element($window).triggerHandler('resize'); | ||
expect(function () { | ||
$timeout.flush(); | ||
}).toThrow(); | ||
expect(snapHeightMock).toBe(200); | ||
} | ||
function testAllowsTurningOffResizeDelay(html, $window, $timeout) { | ||
$window.innerHeight = 400; | ||
compileElement(html); | ||
$window.innerHeight = 200; | ||
angular.element($window).triggerHandler('resize'); | ||
expect(function () { | ||
$timeout.flush(); | ||
}).toThrow(); | ||
expect(snapHeightMock).toBe(200); | ||
} | ||
function testStopsListeningToResizeWhenScopeDestroyed(html, $window, $timeout) { | ||
var element; | ||
$window.innerHeight = 400; | ||
element = compileElement(html); | ||
$scope.$destroy(); | ||
$window.innerHeight = 200; | ||
angular.element($window).triggerHandler('resize'); | ||
expect(function () { | ||
$timeout.flush(); | ||
}).toThrow(); | ||
expect(snapHeightMock).toBe(400); | ||
} | ||
function testStopsListeningToResizeWhenScopeDestroyed(html, $window, $timeout) { | ||
$window.innerHeight = 400; | ||
compileElement(html); | ||
$scope.$destroy(); | ||
$window.innerHeight = 200; | ||
angular.element($window).triggerHandler('resize'); | ||
expect(function () { | ||
$timeout.flush(); | ||
}).toThrow(); | ||
expect(snapHeightMock).toBe(400); | ||
} | ||
it('requires snapscroll', function () { | ||
var html = '<div fit-window-height=""></div>'; | ||
expect(function () { | ||
compileElement(html); | ||
}).toThrow(); | ||
}); | ||
it('requires snapscroll', function () { | ||
var html = '<div fit-window-height=""></div>'; | ||
expect(function () { | ||
compileElement(html); | ||
}).toThrow(); | ||
}); | ||
describe('when applied to snapscroll as an attribute', function () { | ||
describe('when applied to snapscroll as an attribute', function () { | ||
it('sets the snapHeight to equal the window height', inject(function ($window) { | ||
testSetsSnapHeight('<div snapscroll="" fit-window-height=""></div>', $window); | ||
})); | ||
it('sets the snapHeight to equal the window height', inject(function ($window) { | ||
testSetsSnapHeight('<div snapscroll="" fit-window-height=""></div>', $window); | ||
})); | ||
it('updates the snapHeight on window resize after a timeout', inject(function ($window, $timeout) { | ||
testUpdatesSnapHeightOnWindowResize('<div snapscroll="" fit-window-height=""></div>', $window, $timeout); | ||
})); | ||
it('updates the snapHeight on window resize after a timeout', inject(function ($window, $timeout) { | ||
testUpdatesSnapHeightOnWindowResize('<div snapscroll="" fit-window-height=""></div>', $window, $timeout); | ||
})); | ||
it('defaults the resizeDelay to the value of defaultSnapscrollSnapToWindowHeightResizeDelay', inject(function ($window, $timeout, defaultSnapscrollResizeDelay) { | ||
testDefaultsResizeDelayToTheValueOfDefaulSnapscrollSnapToWindowHeightResizeDelay('<div snapscroll="" fit-window-height=""></div>', $window, $timeout, defaultSnapscrollResizeDelay); | ||
})); | ||
it('defaults the resizeDelay to the value of defaultSnapscrollSnapToWindowHeightResizeDelay', inject(function ($window, $timeout, defaultSnapscrollResizeDelay) { | ||
testDefaultsResizeDelayToTheValueOfDefaulSnapscrollSnapToWindowHeightResizeDelay('<div snapscroll="" fit-window-height=""></div>', $window, $timeout, defaultSnapscrollResizeDelay); | ||
})); | ||
it('allows setting the resizeDelay', inject(function ($window, $timeout) { | ||
testAllowsSettingResizeDelay('<div snapscroll="" fit-window-height="" resize-delay="500"></div>', $window, $timeout); | ||
})); | ||
it('allows setting the resizeDelay', inject(function ($window, $timeout) { | ||
testAllowsSettingResizeDelay('<div snapscroll="" fit-window-height="" resize-delay="500"></div>', $window, $timeout); | ||
})); | ||
it('deos not allow setting the resizeDelay using an expression', inject(function ($window, $timeout) { | ||
testDoesNotAllowSettingResizeDelayWithAnExpression('<div snapscroll="" fit-window-height="" resize-delay="300 + 200"></div>', $window, $timeout); | ||
it('deos not allow setting the resizeDelay using an expression', inject(function ($window, $timeout) { | ||
testDoesNotAllowSettingResizeDelayWithAnExpression('<div snapscroll="" fit-window-height="" resize-delay="300 + 200"></div>', $window, $timeout); | ||
})); | ||
})); | ||
it('defaults the resizeDelay to the value of defaultSnapscrollSnapToWindowHeightResizeDelay if a bad timeout is provided', inject(function ($window, $timeout, defaultSnapscrollResizeDelay) { | ||
testDefaultsResizeDelayToTheValueOfDefaulSnapscrollSnapToWindowHeightResizeDelayIfBadTimeoutIsProvided('<div snapscroll="" fit-window-height="" resize-delay="bad"></div>', $window, $timeout, defaultSnapscrollResizeDelay); | ||
})); | ||
it('defaults the resizeDelay to the value of defaultSnapscrollSnapToWindowHeightResizeDelay if a bad timeout is provided', inject(function ($window, $timeout, defaultSnapscrollResizeDelay) { | ||
testDefaultsResizeDelayToTheValueOfDefaulSnapscrollSnapToWindowHeightResizeDelayIfBadTimeoutIsProvided('<div snapscroll="" fit-window-height="" resize-delay="bad"></div>', $window, $timeout, defaultSnapscrollResizeDelay); | ||
})); | ||
it('allows turning off the resizeDelay if passed \'false\'', inject(function ($window, $timeout) { | ||
testAllowsTurningOffResizeDelay('<div snapscroll="" fit-window-height="" resize-delay="false"></div>', $window, $timeout); | ||
})); | ||
it('allows turning off the resizeDelay if passed \'false\'', inject(function ($window, $timeout) { | ||
testAllowsTurningOffResizeDelay('<div snapscroll="" fit-window-height="" resize-delay="false"></div>', $window, $timeout); | ||
})); | ||
it('stops listening to window resize when scope is destroyed', inject(function ($window, $timeout) { | ||
testStopsListeningToResizeWhenScopeDestroyed('<div snapscroll="" fit-window-height=""></div>', $window, $timeout); | ||
})); | ||
}); | ||
it('stops listening to window resize when scope is destroyed', inject(function ($window, $timeout) { | ||
testStopsListeningToResizeWhenScopeDestroyed('<div snapscroll="" fit-window-height=""></div>', $window, $timeout); | ||
})); | ||
}); | ||
}); |
@@ -1,41 +0,39 @@ | ||
'use strict'; | ||
describe('Module: snapscroll', function () { | ||
it('is created', function () { | ||
var app; | ||
it('is created', function () { | ||
var app; | ||
expect(function () { | ||
app = angular.module('snapscroll'); | ||
}).not.toThrow(); | ||
expect(function () { | ||
app = angular.module('snapscroll'); | ||
}).not.toThrow(); | ||
expect(app).toBeDefined(); | ||
}); | ||
expect(app).toBeDefined(); | ||
}); | ||
describe('registers the', function () { | ||
describe('registers the', function () { | ||
beforeEach(module('snapscroll')); | ||
beforeEach(module('snapscroll')); | ||
it('defaultSnapscrollScrollEasing', inject(function (defaultSnapscrollScrollEasing) { | ||
expect(angular.isFunction(defaultSnapscrollScrollEasing)).toBe(true); | ||
})); | ||
it('defaultSnapscrollScrollEasing as undefined', inject(function (defaultSnapscrollScrollEasing) { | ||
expect(angular.isUndefined(defaultSnapscrollScrollEasing)).toBe(true); | ||
})); | ||
it('defaultSnapscrollScrollDelay', inject(function (defaultSnapscrollScrollDelay) { | ||
expect(angular.isNumber(defaultSnapscrollScrollDelay)).toBe(true); | ||
})); | ||
it('defaultSnapscrollScrollDelay', inject(function (defaultSnapscrollScrollDelay) { | ||
expect(angular.isNumber(defaultSnapscrollScrollDelay)).toBe(true); | ||
})); | ||
it('defaultSnapscrollSnapDuration', inject(function (defaultSnapscrollSnapDuration) { | ||
expect(angular.isNumber(defaultSnapscrollSnapDuration)).toBe(true); | ||
})); | ||
it('defaultSnapscrollSnapDuration', inject(function (defaultSnapscrollSnapDuration) { | ||
expect(angular.isNumber(defaultSnapscrollSnapDuration)).toBe(true); | ||
})); | ||
it('defaultSnapscrollResizeDelay', inject(function (defaultSnapscrollResizeDelay) { | ||
expect(angular.isNumber(defaultSnapscrollResizeDelay)).toBe(true); | ||
})); | ||
it('defaultSnapscrollResizeDelay', inject(function (defaultSnapscrollResizeDelay) { | ||
expect(angular.isNumber(defaultSnapscrollResizeDelay)).toBe(true); | ||
})); | ||
it('defaultSnapscrollBindScrollTimeout', inject(function (defaultSnapscrollBindScrollTimeout) { | ||
expect(angular.isNumber(defaultSnapscrollBindScrollTimeout)).toBe(true); | ||
})); | ||
it('defaultSnapscrollBindScrollTimeout', inject(function (defaultSnapscrollBindScrollTimeout) { | ||
expect(angular.isNumber(defaultSnapscrollBindScrollTimeout)).toBe(true); | ||
})); | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
228044
20
4696
0
112
4
24
+ Addedangular-scrollie@^1.0.0
+ Addedangular-scrollie@1.1.1(transitive)