ngx-owl-carousel-o
Version:
Angular powered owl-carousel
1,197 lines • 192 kB
JavaScript
import * as tslib_1 from "tslib";
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
import { OwlCarouselOConfig, OwlOptionsMockedTypes } from '../carousel/owl-carousel-o-config';
import { OwlLogger } from './logger.service';
/**
* Current state information and their tags.
*/
var States = /** @class */ (function () {
function States() {
}
return States;
}());
export { States };
/**
* Enumeration for types.
* @enum {String}
*/
export var Type;
(function (Type) {
Type["Event"] = "event";
Type["State"] = "state";
})(Type || (Type = {}));
;
/**
* Enumeration for width.
* @enum {String}
*/
export var Width;
(function (Width) {
Width["Default"] = "default";
Width["Inner"] = "inner";
Width["Outer"] = "outer";
})(Width || (Width = {}));
;
/**
* Model for coords of .owl-stage
*/
var Coords = /** @class */ (function () {
function Coords() {
}
return Coords;
}());
export { Coords };
/**
* Model for all current data of carousel
*/
var CarouselCurrentData = /** @class */ (function () {
function CarouselCurrentData() {
}
return CarouselCurrentData;
}());
export { CarouselCurrentData };
var CarouselService = /** @class */ (function () {
function CarouselService(logger) {
var _this = this;
this.logger = logger;
/**
* Subject for passing data needed for managing View
*/
this._viewSettingsShipper$ = new Subject();
/**
* Subject for notification when the carousel got initializes
*/
this._initializedCarousel$ = new Subject();
/**
* Subject for notification when the carousel's settings start changinf
*/
this._changeSettingsCarousel$ = new Subject();
/**
* Subject for notification when the carousel's settings have changed
*/
this._changedSettingsCarousel$ = new Subject();
/**
* Subject for notification when the carousel starts translating or moving
*/
this._translateCarousel$ = new Subject();
/**
* Subject for notification when the carousel stopped translating or moving
*/
this._translatedCarousel$ = new Subject();
/**
* Subject for notification when the carousel's rebuilding caused by 'resize' event starts
*/
this._resizeCarousel$ = new Subject();
/**
* Subject for notification when the carousel's rebuilding caused by 'resize' event is ended
*/
this._resizedCarousel$ = new Subject();
/**
* Subject for notification when the refresh of carousel starts
*/
this._refreshCarousel$ = new Subject();
/**
* Subject for notification when the refresh of carousel is ended
*/
this._refreshedCarousel$ = new Subject();
/**
* Subject for notification when the dragging of carousel starts
*/
this._dragCarousel$ = new Subject();
/**
* Subject for notification when the dragging of carousel is ended
*/
this._draggedCarousel$ = new Subject();
/**
* Current settings for the carousel.
*/
this.settings = {
items: 0
};
/**
* Initial data for setting classes to element .owl-carousel
*/
this.owlDOMData = {
rtl: false,
isResponsive: false,
isRefreshed: false,
isLoaded: false,
isLoading: false,
isMouseDragable: false,
isGrab: false,
isTouchDragable: false
};
/**
* Initial data of .owl-stage
*/
this.stageData = {
transform: 'translate3d(0px,0px,0px)',
transition: '0s',
width: 0,
paddingL: 0,
paddingR: 0
};
/**
* All real items.
*/
this._items = []; // is equal to this.slides
/**
* Array with width of every slide.
*/
this._widths = [];
/**
* Currently suppressed events to prevent them from beeing retriggered.
*/
this._supress = {};
/**
* References to the running plugins of this carousel.
*/
this._plugins = {};
/**
* Absolute current position.
*/
this._current = null;
/**
* All cloned items.
*/
this._clones = [];
/**
* Merge values of all items.
* @todo Maybe this could be part of a plugin.
*/
this._mergers = [];
/**
* Animation speed in milliseconds.
*/
this._speed = null;
/**
* Coordinates of all items in pixel.
* @todo The name of this member is missleading.
*/
this._coordinates = [];
/**
* Current breakpoint.
* @todo Real media queries would be nice.
*/
this._breakpoint = null;
/**
* Prefix for id of cloned slides
*/
this.clonedIdPrefix = 'cloned-';
/**
* Current options set by the caller including defaults.
*/
this._options = {};
/**
* Invalidated parts within the update process.
*/
this._invalidated = {};
/**
* Current state information and their tags.
*/
this._states = {
current: {},
tags: {
initializing: ['busy'],
animating: ['busy'],
dragging: ['interacting']
}
};
/**
* Ordered list of workers for the update process.
*/
this._pipe = [
// {
// filter: ['width', 'settings'],
// run: () => {
// this._width = this.carouselWindowWidth;
// }
// },
{
filter: ['width', 'items', 'settings'],
run: function (cache) {
cache.current = _this._items && _this._items[_this.relative(_this._current)].id;
}
},
// {
// filter: ['items', 'settings'],
// run: function() {
// // this.$stage.children('.cloned').remove();
// }
// },
{
filter: ['width', 'items', 'settings'],
run: function (cache) {
var margin = _this.settings.margin || '', grid = !_this.settings.autoWidth, rtl = _this.settings.rtl, css = {
'margin-left': rtl ? margin : '',
'margin-right': rtl ? '' : margin
};
if (!grid) {
_this.slidesData.forEach(function (slide) {
slide.marginL = css['margin-left'];
slide.marginR = css['margin-right'];
});
}
cache.css = css;
}
}, {
filter: ['width', 'items', 'settings'],
run: function (cache) {
var width = +(_this.width() / _this.settings.items).toFixed(3) - _this.settings.margin, grid = !_this.settings.autoWidth, widths = [];
var merge = null, iterator = _this._items.length;
cache.items = {
merge: false,
width: width
};
while (iterator--) {
merge = _this._mergers[iterator];
merge = _this.settings.mergeFit && Math.min(merge, _this.settings.items) || merge;
cache.items.merge = merge > 1 || cache.items.merge;
widths[iterator] = !grid ? _this._items[iterator].width ? _this._items[iterator].width : width : width * merge;
}
_this._widths = widths;
_this.slidesData.forEach(function (slide, i) {
slide.width = _this._widths[i];
slide.marginR = cache.css['margin-right'];
slide.marginL = cache.css['margin-left'];
});
}
}, {
filter: ['items', 'settings'],
run: function () {
var clones = [], items = _this._items, settings = _this.settings,
// TODO: Should be computed from number of min width items in stage
view = Math.max(settings.items * 2, 4), size = Math.ceil(items.length / 2) * 2;
var append = [], prepend = [], repeat = settings.loop && items.length ? settings.rewind ? view : Math.max(view, size) : 0;
repeat /= 2;
while (repeat--) {
// Switch to only using appended clones
clones.push(_this.normalize(clones.length / 2, true));
append.push(tslib_1.__assign({}, _this.slidesData[clones[clones.length - 1]]));
clones.push(_this.normalize(items.length - 1 - (clones.length - 1) / 2, true));
prepend.unshift(tslib_1.__assign({}, _this.slidesData[clones[clones.length - 1]]));
}
_this._clones = clones;
append = append.map(function (slide) {
slide.id = "" + _this.clonedIdPrefix + slide.id;
slide.isActive = false;
slide.isCloned = true;
return slide;
});
prepend = prepend.map(function (slide) {
slide.id = "" + _this.clonedIdPrefix + slide.id;
slide.isActive = false;
slide.isCloned = true;
return slide;
});
_this.slidesData = prepend.concat(_this.slidesData).concat(append);
}
}, {
filter: ['width', 'items', 'settings'],
run: function () {
var rtl = _this.settings.rtl ? 1 : -1, size = _this._clones.length + _this._items.length, coordinates = [];
var iterator = -1, previous = 0, current = 0;
while (++iterator < size) {
previous = coordinates[iterator - 1] || 0;
current = _this._widths[_this.relative(iterator)] + _this.settings.margin;
coordinates.push(previous + current * rtl);
}
_this._coordinates = coordinates;
}
}, {
filter: ['width', 'items', 'settings'],
run: function () {
var padding = _this.settings.stagePadding, coordinates = _this._coordinates, css = {
'width': Math.ceil(Math.abs(coordinates[coordinates.length - 1])) + padding * 2,
'padding-left': padding || '',
'padding-right': padding || ''
};
_this.stageData.width = css.width; // use this property in *ngIf directive for .owl-stage element
_this.stageData.paddingL = css['padding-left'];
_this.stageData.paddingR = css['padding-right'];
}
}, {
// filter: [ 'width', 'items', 'settings' ],
// run: cache => {
// // this method sets the width for every slide, but I set it in different way earlier
// const grid = !this.settings.autoWidth,
// items = this.$stage.children(); // use this.slidesData
// let iterator = this._coordinates.length;
// if (grid && cache.items.merge) {
// while (iterator--) {
// cache.css.width = this._widths[this.relative(iterator)];
// items.eq(iterator).css(cache.css);
// }
// } else if (grid) {
// cache.css.width = cache.items.width;
// items.css(cache.css);
// }
// }
// }, {
// filter: [ 'items' ],
// run: function() {
// this._coordinates.length < 1 && this.$stage.removeAttr('style');
// }
// }, {
filter: ['width', 'items', 'settings'],
run: function (cache) {
var current = cache.current ? _this.slidesData.findIndex(function (slide) { return slide.id === cache.current; }) : 0;
current = Math.max(_this.minimum(), Math.min(_this.maximum(), current));
_this.reset(current);
}
}, {
filter: ['position'],
run: function () {
_this.animate(_this.coordinates(_this._current));
}
}, {
filter: ['width', 'position', 'items', 'settings'],
run: function () {
var rtl = _this.settings.rtl ? 1 : -1, padding = _this.settings.stagePadding * 2, matches = [];
var begin, end, inner, outer, i, n;
begin = _this.coordinates(_this.current());
if (typeof begin === 'number') {
begin += padding;
}
else {
begin = 0;
}
end = begin + _this.width() * rtl;
if (rtl === -1 && _this.settings.center) {
var result = _this._coordinates.filter(function (element) {
return _this.settings.items % 2 === 1 ? element >= begin : element > begin;
});
begin = result.length ? result[result.length - 1] : begin;
}
for (i = 0, n = _this._coordinates.length; i < n; i++) {
inner = Math.ceil(_this._coordinates[i - 1] || 0);
outer = Math.ceil(Math.abs(_this._coordinates[i]) + padding * rtl);
if ((_this._op(inner, '<=', begin) && (_this._op(inner, '>', end)))
|| (_this._op(outer, '<', begin) && _this._op(outer, '>', end))) {
matches.push(i);
}
}
_this.slidesData.forEach(function (slide) {
slide.isActive = false;
return slide;
});
matches.forEach(function (item) {
_this.slidesData[item].isActive = true;
});
if (_this.settings.center) {
_this.slidesData.forEach(function (slide) {
slide.isCentered = false;
return slide;
});
_this.slidesData[_this.current()].isCentered = true;
}
}
}
];
}
Object.defineProperty(CarouselService.prototype, "invalidated", {
// Is needed for tests
get: function () {
return this._invalidated;
},
enumerable: true,
configurable: true
});
Object.defineProperty(CarouselService.prototype, "states", {
// is needed for tests
get: function () {
return this._states;
},
enumerable: true,
configurable: true
});
/**
* Makes _viewSettingsShipper$ Subject become Observable
* @returns Observable of _viewSettingsShipper$ Subject
*/
CarouselService.prototype.getViewCurSettings = function () {
return this._viewSettingsShipper$.asObservable();
};
/**
* Makes _initializedCarousel$ Subject become Observable
* @returns Observable of _initializedCarousel$ Subject
*/
CarouselService.prototype.getInitializedState = function () {
return this._initializedCarousel$.asObservable();
};
/**
* Makes _changeSettingsCarousel$ Subject become Observable
* @returns Observable of _changeSettingsCarousel$ Subject
*/
CarouselService.prototype.getChangeState = function () {
return this._changeSettingsCarousel$.asObservable();
};
/**
* Makes _changedSettingsCarousel$ Subject become Observable
* @returns Observable of _changedSettingsCarousel$ Subject
*/
CarouselService.prototype.getChangedState = function () {
return this._changedSettingsCarousel$.asObservable();
};
/**
* Makes _translateCarousel$ Subject become Observable
* @returns Observable of _translateCarousel$ Subject
*/
CarouselService.prototype.getTranslateState = function () {
return this._translateCarousel$.asObservable();
};
/**
* Makes _translatedCarousel$ Subject become Observable
* @returns Observable of _translatedCarousel$ Subject
*/
CarouselService.prototype.getTranslatedState = function () {
return this._translatedCarousel$.asObservable();
};
/**
* Makes _resizeCarousel$ Subject become Observable
* @returns Observable of _resizeCarousel$ Subject
*/
CarouselService.prototype.getResizeState = function () {
return this._resizeCarousel$.asObservable();
};
/**
* Makes _resizedCarousel$ Subject become Observable
* @returns Observable of _resizedCarousel$ Subject
*/
CarouselService.prototype.getResizedState = function () {
return this._resizedCarousel$.asObservable();
};
/**
* Makes _refreshCarousel$ Subject become Observable
* @returns Observable of _refreshCarousel$ Subject
*/
CarouselService.prototype.getRefreshState = function () {
return this._refreshCarousel$.asObservable();
};
/**
* Makes _refreshedCarousel$ Subject become Observable
* @returns Observable of _refreshedCarousel$ Subject
*/
CarouselService.prototype.getRefreshedState = function () {
return this._refreshedCarousel$.asObservable();
};
/**
* Makes _dragCarousel$ Subject become Observable
* @returns Observable of _dragCarousel$ Subject
*/
CarouselService.prototype.getDragState = function () {
return this._dragCarousel$.asObservable();
};
/**
* Makes _draggedCarousel$ Subject become Observable
* @returns Observable of _draggedCarousel$ Subject
*/
CarouselService.prototype.getDraggedState = function () {
return this._draggedCarousel$.asObservable();
};
/**
* Setups custom options expanding default options
* @param options custom options
*/
CarouselService.prototype.setOptions = function (options) {
var configOptions = new OwlCarouselOConfig();
var checkedOptions = this._validateOptions(options, configOptions);
this._options = tslib_1.__assign({}, configOptions, checkedOptions);
};
/**
* Checks whether user's option are set properly. Cheking is based on typings;
* @param options options set by user
* @param configOptions default options
* @returns checked and modified (if it's needed) user's options
*
* Notes:
* - if user set option with wrong type, it'll be written in console
*/
CarouselService.prototype._validateOptions = function (options, configOptions) {
var _this = this;
var checkedOptions = tslib_1.__assign({}, options);
var mockedTypes = new OwlOptionsMockedTypes();
var setRightOption = function (type, key) {
_this.logger.log("options." + key + " must be type of " + type + "; " + key + "=" + options[key] + " skipped to defaults: " + key + "=" + configOptions[key]);
return configOptions[key];
};
var _loop_1 = function (key) {
if (checkedOptions.hasOwnProperty(key)) {
// condition could be shortened but it gets harder for understanding
if (mockedTypes[key] === 'number') {
if (this_1._isNumeric(checkedOptions[key])) {
checkedOptions[key] = +checkedOptions[key];
checkedOptions[key] = key === 'items' ? this_1._validateItems(checkedOptions[key]) : checkedOptions[key];
}
else {
checkedOptions[key] = setRightOption(mockedTypes[key], key);
}
}
else if (mockedTypes[key] === 'boolean' && typeof checkedOptions[key] !== 'boolean') {
checkedOptions[key] = setRightOption(mockedTypes[key], key);
}
else if (mockedTypes[key] === 'number|boolean' && !this_1._isNumberOrBoolean(checkedOptions[key])) {
checkedOptions[key] = setRightOption(mockedTypes[key], key);
}
else if (mockedTypes[key] === 'number|string' && !this_1._isNumberOrString(checkedOptions[key])) {
checkedOptions[key] = setRightOption(mockedTypes[key], key);
}
else if (mockedTypes[key] === 'string|boolean' && !this_1._isStringOrBoolean(checkedOptions[key])) {
checkedOptions[key] = setRightOption(mockedTypes[key], key);
}
else if (mockedTypes[key] === 'string[]') {
if (Array.isArray(checkedOptions[key])) {
var isString_1 = false;
checkedOptions[key].forEach(function (element) {
isString_1 = typeof element === 'string' ? true : false;
});
if (!isString_1) {
checkedOptions[key] = setRightOption(mockedTypes[key], key);
}
;
}
else {
checkedOptions[key] = setRightOption(mockedTypes[key], key);
}
}
}
};
var this_1 = this;
for (var key in checkedOptions) {
_loop_1(key);
}
return checkedOptions;
};
/**
* Checks option items set by user and if it bigger than number of slides then returns number of slides
* @param items option items set by user
* @returns right number of items
*/
CarouselService.prototype._validateItems = function (items) {
var result;
if (items > this._items.length) {
result = this._items.length;
this.logger.log('The option \'items\' in your options is bigger than the number of slides. This option is updated to the current number of slides and the navigation got disabled');
}
else {
if (items === this._items.length && (this.settings.dots || this.settings.nav)) {
this.logger.log('Option \'items\' in your options is equal to the number of slides. So the navigation got disabled');
}
result = items;
}
return result;
};
/**
* Set current width of carousel
* @param width width of carousel Window
*/
CarouselService.prototype.setCarouselWidth = function (width) {
this._width = width;
};
/**
* Setups the current settings.
* @todo Remove responsive classes. Why should adaptive designs be brought into IE8?
* @todo Support for media queries by using `matchMedia` would be nice.
* @param carouselWidth width of carousel
* @param slides array of slides
* @param options options set by user
*/
CarouselService.prototype.setup = function (carouselWidth, slides, options) {
this.setCarouselWidth(carouselWidth);
this.setItems(slides);
this._defineSlidesData();
this.setOptions(options);
this.settings = tslib_1.__assign({}, this._options);
this.setOptionsForViewport();
this._trigger('change', { property: { name: 'settings', value: this.settings } });
this.invalidate('settings'); // must be call of this function;
this._trigger('changed', { property: { name: 'settings', value: this.settings } });
};
/**
* Set options for current viewport
*/
CarouselService.prototype.setOptionsForViewport = function () {
var _this = this;
var viewport = this._width, overwrites = this._options.responsive;
var match = -1;
if (!Object.keys(overwrites).length) {
return;
}
if (!viewport) {
this.settings.items = 1;
return;
}
for (var key in overwrites) {
if (overwrites.hasOwnProperty(key)) {
if (+key <= viewport && +key > match) {
match = Number(key);
}
}
}
this.settings = tslib_1.__assign({}, this._options, overwrites[match], { items: (overwrites[match] && overwrites[match].items) ? this._validateItems(overwrites[match].items) : this._options.items });
// if (typeof this.settings.stagePadding === 'function') {
// this.settings.stagePadding = this.settings.stagePadding();
// }
delete this.settings.responsive;
this.owlDOMData.isResponsive = true;
this.owlDOMData.isMouseDragable = this.settings.mouseDrag;
this.owlDOMData.isTouchDragable = this.settings.touchDrag;
var mergers = [];
this._items.forEach(function (item) {
var mergeN = _this.settings.merge ? item.dataMerge : 1;
mergers.push(mergeN);
});
this._mergers = mergers;
this._breakpoint = match;
this.invalidate('settings');
};
/**
* Initializes the carousel.
* @param slides array of CarouselSlideDirective
*/
CarouselService.prototype.initialize = function (slides) {
var _this = this;
this.enter('initializing');
// this.trigger('initialize');
this.owlDOMData.rtl = this.settings.rtl;
if (this._mergers.length) {
this._mergers = [];
}
slides.forEach(function (item) {
var mergeN = _this.settings.merge ? item.dataMerge : 1;
_this._mergers.push(mergeN);
});
this._clones = [];
this.reset(this._isNumeric(this.settings.startPosition) ? +this.settings.startPosition : 0);
this.invalidate('items');
this.refresh();
this.owlDOMData.isLoaded = true;
this.owlDOMData.isMouseDragable = this.settings.mouseDrag;
this.owlDOMData.isTouchDragable = this.settings.touchDrag;
this.sendChanges();
this.leave('initializing');
this._trigger('initialized');
};
;
/**
* Sends all data needed for View
*/
CarouselService.prototype.sendChanges = function () {
this._viewSettingsShipper$.next({
owlDOMData: this.owlDOMData,
stageData: this.stageData,
slidesData: this.slidesData,
navData: this.navData,
dotsData: this.dotsData
});
};
/**
* Updates option logic if necessery
*/
CarouselService.prototype._optionsLogic = function () {
if (this.settings.autoWidth) {
this.settings.stagePadding = 0;
this.settings.merge = false;
}
};
/**
* Updates the view
*/
CarouselService.prototype.update = function () {
var _this = this;
var i = 0;
var n = this._pipe.length, filter = function (item) { return _this._invalidated[item]; }, cache = {};
while (i < n) {
var filteredPipe = this._pipe[i].filter.filter(filter);
if (this._invalidated.all || filteredPipe.length > 0) {
this._pipe[i].run(cache);
}
i++;
}
this.slidesData.forEach(function (slide) { return slide.classes = _this.setCurSlideClasses(slide); });
this.sendChanges();
this._invalidated = {};
if (!this.is('valid')) {
this.enter('valid');
}
};
/**
* Gets the width of the view.
* @param [dimension=Width.Default] The dimension to return
* @returns The width of the view in pixel.
*/
CarouselService.prototype.width = function (dimension) {
dimension = dimension || Width.Default;
switch (dimension) {
case Width.Inner:
case Width.Outer:
return this._width;
default:
return this._width - this.settings.stagePadding * 2 + this.settings.margin;
}
};
/**
* Refreshes the carousel primarily for adaptive purposes.
*/
CarouselService.prototype.refresh = function () {
this.enter('refreshing');
this._trigger('refresh');
this._defineSlidesData();
this.setOptionsForViewport();
this._optionsLogic();
// this.$element.addClass(this.options.refreshClass);
this.update();
// this.$element.removeClass(this.options.refreshClass);
this.leave('refreshing');
this._trigger('refreshed');
};
/**
* Checks window `resize` event.
* @param curWidth width of .owl-carousel
*/
CarouselService.prototype.onResize = function (curWidth) {
if (!this._items.length) {
return false;
}
this.setCarouselWidth(curWidth);
this.enter('resizing');
// if (this.trigger('resize').isDefaultPrevented()) {
// this.leave('resizing');
// return false;
// }
this._trigger('resize');
this.invalidate('width');
this.refresh();
this.leave('resizing');
this._trigger('resized');
};
/**
* Prepares data for dragging carousel. It starts after firing `touchstart` and `mousedown` events.
* @todo Horizontal swipe threshold as option
* @todo #261
* @param event - The event arguments.
* @returns stage - object with 'x' and 'y' coordinates of .owl-stage
*/
CarouselService.prototype.prepareDragging = function (event) {
var stage = null, transformArr;
// could be 5 commented lines below; However there's stage transform in stageData and in updates after each move of stage
// stage = getComputedStyle(this.el.nativeElement).transform.replace(/.*\(|\)| /g, '').split(',');
// stage = {
// x: stage[stage.length === 16 ? 12 : 4],
// y: stage[stage.length === 16 ? 13 : 5]
// };
transformArr = this.stageData.transform.replace(/.*\(|\)| |[^,-\d]\w|\)/g, '').split(',');
stage = {
x: +transformArr[0],
y: +transformArr[1]
};
if (this.is('animating')) {
this.invalidate('position');
}
if (event.type === 'mousedown') {
this.owlDOMData.isGrab = true;
}
this.speed(0);
return stage;
};
/**
* Enters into a 'dragging' state
*/
CarouselService.prototype.enterDragging = function () {
this.enter('dragging');
this._trigger('drag');
};
/**
* Defines new coords for .owl-stage while dragging it
* @todo #261
* @param event the event arguments.
* @param dragData initial data got after starting dragging
* @returns coords or false
*/
CarouselService.prototype.defineNewCoordsDrag = function (event, dragData) {
var minimum = null, maximum = null, pull = null;
var delta = this.difference(dragData.pointer, this.pointer(event)), stage = this.difference(dragData.stage.start, delta);
if (!this.is('dragging')) {
return false;
}
if (this.settings.loop) {
minimum = this.coordinates(this.minimum());
maximum = +this.coordinates(this.maximum() + 1) - minimum;
stage.x = (((stage.x - minimum) % maximum + maximum) % maximum) + minimum;
}
else {
minimum = this.settings.rtl ? this.coordinates(this.maximum()) : this.coordinates(this.minimum());
maximum = this.settings.rtl ? this.coordinates(this.minimum()) : this.coordinates(this.maximum());
pull = this.settings.pullDrag ? -1 * delta.x / 5 : 0;
stage.x = Math.max(Math.min(stage.x, minimum + pull), maximum + pull);
}
return stage;
};
/**
* Finishes dragging of carousel when `touchend` and `mouseup` events fire.
* @todo #261
* @todo Threshold for click event
* @param event the event arguments.
* @param dragObj the object with dragging settings and states
* @param clickAttacher function which attaches click handler to slide or its children elements in order to prevent event bubling
*/
CarouselService.prototype.finishDragging = function (event, dragObj, clickAttacher) {
var directions = ['right', 'left'], delta = this.difference(dragObj.pointer, this.pointer(event)), stage = dragObj.stage.current, direction = directions[+(this.settings.rtl ? delta.x < +this.settings.rtl : delta.x > +this.settings.rtl)];
var currentSlideI, current, newCurrent;
if (delta.x !== 0 && this.is('dragging') || !this.is('valid')) {
this.speed(+this.settings.dragEndSpeed || this.settings.smartSpeed);
currentSlideI = this.closest(stage.x, delta.x !== 0 ? direction : dragObj.direction);
current = this.current();
newCurrent = this.current(currentSlideI === -1 ? undefined : currentSlideI);
if (current !== newCurrent) {
this.invalidate('position');
this.update();
}
dragObj.direction = direction;
if (Math.abs(delta.x) > 3 || new Date().getTime() - dragObj.time > 300) {
clickAttacher();
}
}
if (!this.is('dragging')) {
return;
}
this.leave('dragging');
this._trigger('dragged');
};
/**
* Gets absolute position of the closest item for a coordinate.
* @todo Setting `freeDrag` makes `closest` not reusable. See #165.
* @param coordinate The coordinate in pixel.
* @param direction The direction to check for the closest item. Ether `left` or `right`.
* @returns The absolute position of the closest item.
*/
CarouselService.prototype.closest = function (coordinate, direction) {
var pull = 30, width = this.width();
var coordinates = this.coordinates(), position = -1;
if (this.settings.center) {
coordinates = coordinates.map(function (item) {
if (item === 0) {
item += 0.000001;
}
return item;
});
}
// option 'freeDrag' doesn't have realization and using it here creates problem:
// variable 'position' stays unchanged (it equals -1 at the begging) and thus method returns -1
// Returning value is consumed by method current(), which taking -1 as argument calculates the index of new current slide
// In case of having 5 slides ans 'loop=false; calling 'current(-1)' sets props '_current' as 4. Just last slide remains visible instead of 3 last slides.
// if (!this.settings.freeDrag) {
// check closest item
for (var i = 0; i < coordinates.length; i++) {
if (direction === 'left' && coordinate > coordinates[i] - pull && coordinate < coordinates[i] + pull) {
position = i;
// on a right pull, check on previous index
// to do so, subtract width from value and set position = index + 1
}
else if (direction === 'right' && coordinate > coordinates[i] - width - pull && coordinate < coordinates[i] - width + pull) {
position = i + 1;
}
else if (this._op(coordinate, '<', coordinates[i])
&& this._op(coordinate, '>', coordinates[i + 1] || coordinates[i] - width)) {
position = direction === 'left' ? i + 1 : i;
}
else if (direction === null && coordinate > coordinates[i] - pull && coordinate < coordinates[i] + pull) {
position = i;
}
if (position !== -1) {
break;
}
;
}
// }
if (!this.settings.loop) {
// non loop boundries
if (this._op(coordinate, '>', coordinates[this.minimum()])) {
position = coordinate = this.minimum();
}
else if (this._op(coordinate, '<', coordinates[this.maximum()])) {
position = coordinate = this.maximum();
}
}
return position;
};
/**
* Animates the stage.
* @todo #270
* @param coordinate The coordinate in pixels.
*/
CarouselService.prototype.animate = function (coordinate) {
var animate = this.speed() > 0;
if (this.is('animating')) {
this.onTransitionEnd();
}
if (animate) {
this.enter('animating');
this._trigger('translate');
}
this.stageData.transform = 'translate3d(' + coordinate + 'px,0px,0px)';
this.stageData.transition = (this.speed() / 1000) + 's' + (this.settings.slideTransition ? ' ' + this.settings.slideTransition : '');
// also there was transition by means of JQuery.animate or css-changing property left
};
/**
* Checks whether the carousel is in a specific state or not.
* @param state The state to check.
* @returns The flag which indicates if the carousel is busy.
*/
CarouselService.prototype.is = function (state) {
return this._states.current[state] && this._states.current[state] > 0;
};
;
/**
* Sets the absolute position of the current item.
* @param position The new absolute position or nothing to leave it unchanged.
* @returns The absolute position of the current item.
*/
CarouselService.prototype.current = function (position) {
if (position === undefined) {
return this._current;
}
if (this._items.length === 0) {
return undefined;
}
position = this.normalize(position);
if (this._current !== position) {
var event_1 = this._trigger('change', { property: { name: 'position', value: position } });
// if (event.data !== undefined) {
// position = this.normalize(event.data);
// }
this._current = position;
this.invalidate('position');
this._trigger('changed', { property: { name: 'position', value: this._current } });
}
return this._current;
};
/**
* Invalidates the given part of the update routine.
* @param part The part to invalidate.
* @returns The invalidated parts.
*/
CarouselService.prototype.invalidate = function (part) {
if (typeof part === 'string') {
this._invalidated[part] = true;
if (this.is('valid')) {
this.leave('valid');
}
}
return Object.keys(this._invalidated);
};
;
/**
* Resets the absolute position of the current item.
* @param position the absolute position of the new item.
*/
CarouselService.prototype.reset = function (position) {
position = this.normalize(position);
if (position === undefined) {
return;
}
this._speed = 0;
this._current = position;
this._suppress(['translate', 'translated']);
this.animate(this.coordinates(position));
this._release(['translate', 'translated']);
};
/**
* Normalizes an absolute or a relative position of an item.
* @param position The absolute or relative position to normalize.
* @param relative Whether the given position is relative or not.
* @returns The normalized position.
*/
CarouselService.prototype.normalize = function (position, relative) {
var n = this._items.length, m = relative ? 0 : this._clones.length;
if (!this._isNumeric(position) || n < 1) {
position = undefined;
}
else if (position < 0 || position >= n + m) {
position = ((position - m / 2) % n + n) % n + m / 2;
}
return position;
};
/**
* Converts an absolute position of an item into a relative one.
* @param position The absolute position to convert.
* @returns The converted position.
*/
CarouselService.prototype.relative = function (position) {
position -= this._clones.length / 2;
return this.normalize(position, true);
};
/**
* Gets the maximum position for the current item.
* @param relative Whether to return an absolute position or a relative position.
* @returns number of maximum position
*/
CarouselService.prototype.maximum = function (relative) {
if (relative === void 0) { relative = false; }
var settings = this.settings;
var maximum = this._coordinates.length, iterator, reciprocalItemsWidth, elementWidth;
if (settings.loop) {
maximum = this._clones.length / 2 + this._items.length - 1;
}
else if (settings.autoWidth || settings.merge) {
iterator = this._items.length;
reciprocalItemsWidth = this.slidesData[--iterator].width;
elementWidth = this._width;
while (iterator--) {
// it could be use this._items instead of this.slidesData;
reciprocalItemsWidth += +this.slidesData[iterator].width + this.settings.margin;
if (reciprocalItemsWidth > elementWidth) {
break;
}
}
maximum = iterator + 1;
}
else if (settings.center) {
maximum = this._items.length - 1;
}
else {
maximum = this._items.length - settings.items;
}
if (relative) {
maximum -= this._clones.length / 2;
}
return Math.max(maximum, 0);
};
/**
* Gets the minimum position for the current item.
* @param relative Whether to return an absolute position or a relative position.
* @returns number of minimum position
*/
CarouselService.prototype.minimum = function (relative) {
if (relative === void 0) { relative = false; }
return relative ? 0 : this._clones.length / 2;
};
/**
* Gets an item at the specified relative position.
* @param position The relative position of the item.
* @returns The item at the given position or all items if no position was given.
*/
CarouselService.prototype.items = function (position) {
if (position === undefined) {
return this._items.slice();
}
position = this.normalize(position, true);
return [this._items[position]];
};
/**
* Gets an item at the specified relative position.
* @param position The relative position of the item.
* @returns The item at the given position or all items if no position was given.
*/
CarouselService.prototype.mergers = function (position) {
if (position === undefined) {
return this._mergers.slice();
}
position = this.normalize(position, true);
return this._mergers[position];
};
/**
* Gets the absolute positions of clones for an item.
* @param position The relative position of the item.
* @returns The absolute positions of clones for the item or all if no position was given.
*/
CarouselService.prototype.clones = function (position) {
var odd = this._clones.length / 2, even = odd + this._items.length, map = function (index) { return index % 2 === 0 ? even + index / 2 : odd - (index + 1) / 2; };
if (position === undefined) {
return this._clones.map(function (v, i) { return map(i); });
}
return this._clones.map(function (v, i) { return v === position ? map(i) : null; }).filter(function (item) { return item; });
};
/**
* Sets the current animation speed.
* @param speed The animation speed in milliseconds or nothing to leave it unchanged.
* @returns The current animation speed in milliseconds.
*/
CarouselService.prototype.speed = function (speed) {
if (speed !== undefined) {
this._speed = speed;
}
return this._speed;
};
/**
* Gets the coordinate of an item.
* @todo The name of this method is missleanding.
* @param position The absolute position of the item within `minimum()` and `maximum()`.
* @returns The coordinate of the item in pixel or all coordinates.
*/
CarouselService.prototype.coordinates = function (position) {
var _this = this;
var multiplier = 1, newPosition = position - 1, coordinate, result;
if (position === undefined) {
result = this._coordinates.map(function (item, index) {
return _this.coordinates(index);
});
return result;
}
if (this.settings.center) {
if (this.settings.rtl) {
multiplier = -1;
newPosition = position + 1;
}
coordinate = this._coordinates[position];
coordinate += (this.width() - coordinate + (this._coordinates[newPosition] || 0)) / 2 * multiplier;
}
else {
coordinate = this._coordinates[newPosition] || 0;
}
coordinate = Math.ceil(coordinate);
return coordinate;
};
/**
* Calculates the speed for a translation.
* @param from The absolute position of the start item.
* @param to The absolute position of the target item.
* @param factor [factor=undefined] - The time factor in milliseconds.
* @returns The time in milliseconds for the translation.
*/
CarouselService.prototype._duration = function (from, to, factor) {
if (factor === 0) {
return 0;
}
return Math.min(Math.max(Math.abs(to - from), 1), 6) * Math.abs((+factor || this.settings.smartSpeed));
};
/**
* Slides to the specified item.
* @param position The position of the item.
* @param speed The time in milliseconds for the transition.
*/
CarouselService.prototype.to = function (position, speed) {
var _this = this;
var current = this.current(), revert = null, distance = position - this.relative(current), maximum = this.maximum(), delayForLoop = 0;
var direction = +(distance > 0) - +(distance < 0), items = this._items.length, minimum = this.minimum();
if (this.settings.loop) {
if (!this.settings.rewind && Math.abs(distance) > items / 2) {
distance += direction * -1 * items;
}
position = current + distance;
revert = ((position - minimum) % items + items) % items + minimum;
if (revert !== position && revert - distance <= maximum && revert - distance > 0) {
current = revert - distance;
position = revert;
delayForLoop = 30;
this.reset(current);
this.sendChanges();
}
}
else if (this.settings.rewind) {
maximum += 1;
position = (position % maximum + maximum) % maximum;
}
else {
position = Math.max(minimum, Math.min(maximum, position));
}
setTimeout(function () {
_this.speed(_this._duration(current, position, speed));
_this.current(position);
_this.update();
}, delayForLoop);
};
/**
* Slides to the next item.
* @param speed The time in milli