Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

angular-snapscroll

Package Overview
Dependencies
Maintainers
1
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

angular-snapscroll - npm Package Compare versions

Comparing version 0.3.1 to 1.0.0

.eslintignore

5

bower.json
{
"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 @@

1121

dist/angular-snapscroll.js
/**
* 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)}();
# 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()`.

@@ -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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc