ngx-owl-carousel-o
Version:
Angular powered owl-carousel
1,253 lines (1,248 loc) • 134 kB
JavaScript
import { __decorate, __metadata, __param } from 'tslib';
import { Injectable, isDevMode, ErrorHandler, InjectionToken, PLATFORM_ID, Inject, Optional, Input, Directive, TemplateRef, EventEmitter, ContentChildren, QueryList, Output, HostListener, Component, ElementRef, NgZone, Renderer2, Attribute, HostBinding, NgModule } from '@angular/core';
import { isPlatformBrowser, LocationStrategy, CommonModule } from '@angular/common';
import { Subject, merge, of, from } from 'rxjs';
import { EventManager } from '@angular/platform-browser';
import { tap, filter, switchMap, first, take, skip, map, toArray, delay } from 'rxjs/operators';
import { ActivatedRoute, Router, NavigationEnd } from '@angular/router';
import { trigger, state, style, transition, animate } from '@angular/animations';
let ResizeService = class ResizeService {
constructor(eventManager) {
this.eventManager = eventManager;
this.resizeSubject = new Subject();
this.eventManager.addGlobalEventListener('window', 'resize', this.onResize.bind(this));
this.eventManager.addGlobalEventListener('window', 'onload', this.onLoaded.bind(this));
}
/**
* Makes resizeSubject become Observable
* @returns Observable of resizeSubject
*/
get onResize$() {
return this.resizeSubject.asObservable();
}
/**
* Handler of 'resize' event. Passes data throw resizeSubject
* @param event Event Object of 'resize' event
*/
onResize(event) {
this.resizeSubject.next(event.target);
}
/**
* Handler of 'onload' event. Defines the width of window
* @param event Event Object of 'onload' event
*/
onLoaded(event) {
this.windowWidth = event.target;
}
};
ResizeService = __decorate([
Injectable(),
__metadata("design:paramtypes", [EventManager])
], ResizeService);
/**
* Defaults value of options
*/
class OwlCarouselOConfig {
constructor() {
this.items = 3;
this.loop = false;
this.center = false;
this.rewind = false;
this.mouseDrag = true;
this.touchDrag = true;
this.pullDrag = true;
this.freeDrag = false;
this.margin = 0;
this.stagePadding = 0;
this.merge = false;
this.mergeFit = true;
this.autoWidth = false;
this.startPosition = 0;
this.rtl = false;
this.smartSpeed = 250;
this.fluidSpeed = false;
this.dragEndSpeed = false;
this.responsive = {};
this.responsiveRefreshRate = 200;
// defaults to Navigation
this.nav = false;
this.navText = ['prev', 'next'];
this.navSpeed = false;
this.slideBy = 1; // stage moves on 1 width of slide; if slideBy = 2, stage moves on 2 widths of slide
this.dots = true;
this.dotsEach = false;
this.dotsData = false;
this.dotsSpeed = false;
// defaults to Autoplay
this.autoplay = false;
this.autoplayTimeout = 5000;
this.autoplayHoverPause = false;
this.autoplaySpeed = false;
// defaults to LazyLoading
this.lazyLoad = false;
this.lazyLoadEager = 0;
// defaults to Animate
this.slideTransition = '';
this.animateOut = false;
this.animateIn = false;
// defaults to AutoHeight
this.autoHeight = false;
// defaults to Hash
this.URLhashListener = false;
}
}
/**
* we can't read types from OwlOptions in javascript because of props have undefined value and types of those props are used for validating inputs
* class below is copy of OwlOptions but its all props have string value showing certain type;
* this is class is being used just in method _validateOptions() of CarouselService;
*/
class OwlOptionsMockedTypes {
constructor() {
this.items = 'number';
this.loop = 'boolean';
this.center = 'boolean';
this.rewind = 'boolean';
this.mouseDrag = 'boolean';
this.touchDrag = 'boolean';
this.pullDrag = 'boolean';
this.freeDrag = 'boolean';
this.margin = 'number';
this.stagePadding = 'number';
this.merge = 'boolean';
this.mergeFit = 'boolean';
this.autoWidth = 'boolean';
this.startPosition = 'number|string';
this.rtl = 'boolean';
this.smartSpeed = 'number';
this.fluidSpeed = 'boolean';
this.dragEndSpeed = 'number|boolean';
this.responsive = {};
this.responsiveRefreshRate = 'number';
// defaults to Navigation
this.nav = 'boolean';
this.navText = 'string[]';
this.navSpeed = 'number|boolean';
this.slideBy = 'number|string'; // stage moves on 1 width of slide; if slideBy = 2, stage moves on 2 widths of slide
this.dots = 'boolean';
this.dotsEach = 'number|boolean';
this.dotsData = 'boolean';
this.dotsSpeed = 'number|boolean';
// defaults to Autoplay
this.autoplay = 'boolean';
this.autoplayTimeout = 'number';
this.autoplayHoverPause = 'boolean';
this.autoplaySpeed = 'number|boolean';
// defaults to LazyLoading
this.lazyLoad = 'boolean';
this.lazyLoadEager = 'number';
// defaults to Animate
this.slideTransition = 'string';
this.animateOut = 'string|boolean';
this.animateIn = 'string|boolean';
// defaults to AutoHeight
this.autoHeight = 'boolean';
// defaults to Hash
this.URLhashListener = "boolean";
}
}
let OwlLogger = class OwlLogger {
constructor(errorHandler) {
this.errorHandler = errorHandler;
}
log(value, ...rest) {
if (isDevMode()) {
console.log(value, ...rest);
}
}
error(error) {
this.errorHandler.handleError(error);
}
warn(value, ...rest) {
console.warn(value, ...rest);
}
};
OwlLogger = __decorate([
Injectable(),
__metadata("design:paramtypes", [ErrorHandler])
], OwlLogger);
/**
* Enumeration for types.
* @enum {String}
*/
var Type;
(function (Type) {
Type["Event"] = "event";
Type["State"] = "state";
})(Type || (Type = {}));
/**
* Enumeration for width.
* @enum {String}
*/
var Width;
(function (Width) {
Width["Default"] = "default";
Width["Inner"] = "inner";
Width["Outer"] = "outer";
})(Width || (Width = {}));
let CarouselService = class CarouselService {
constructor(logger) {
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: 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: (cache) => {
const 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(slide => {
slide.marginL = css['margin-left'];
slide.marginR = css['margin-right'];
});
}
cache.css = css;
}
}, {
filter: ['width', 'items', 'settings'],
run: (cache) => {
const width = +(this.width() / this.settings.items).toFixed(3) - this.settings.margin, grid = !this.settings.autoWidth, widths = [];
let 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((slide, i) => {
slide.width = this._widths[i];
slide.marginR = cache.css['margin-right'];
slide.marginL = cache.css['margin-left'];
});
}
}, {
filter: ['items', 'settings'],
run: () => {
const 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;
let 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(Object.assign({}, this.slidesData[clones[clones.length - 1]]));
clones.push(this.normalize(items.length - 1 - (clones.length - 1) / 2, true));
prepend.unshift(Object.assign({}, this.slidesData[clones[clones.length - 1]]));
}
this._clones = clones;
append = append.map(slide => {
slide.id = `${this.clonedIdPrefix}${slide.id}`;
slide.isActive = false;
slide.isCloned = true;
return slide;
});
prepend = prepend.map(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: () => {
const rtl = this.settings.rtl ? 1 : -1, size = this._clones.length + this._items.length, coordinates = [];
let 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: () => {
const 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: cache => {
let current = cache.current ? this.slidesData.findIndex(slide => slide.id === cache.current) : 0;
current = Math.max(this.minimum(), Math.min(this.maximum(), current));
this.reset(current);
}
}, {
filter: ['position'],
run: () => {
this.animate(this.coordinates(this._current));
}
}, {
filter: ['width', 'position', 'items', 'settings'],
run: () => {
const rtl = this.settings.rtl ? 1 : -1, padding = this.settings.stagePadding * 2, matches = [];
let 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) {
const result = this._coordinates.filter(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(slide => {
slide.isActive = false;
return slide;
});
matches.forEach(item => {
this.slidesData[item].isActive = true;
});
if (this.settings.center) {
this.slidesData.forEach(slide => {
slide.isCentered = false;
return slide;
});
this.slidesData[this.current()].isCentered = true;
}
}
}
];
}
// Is needed for tests
get invalidated() {
return this._invalidated;
}
// is needed for tests
get states() {
return this._states;
}
/**
* Makes _viewSettingsShipper$ Subject become Observable
* @returns Observable of _viewSettingsShipper$ Subject
*/
getViewCurSettings() {
return this._viewSettingsShipper$.asObservable();
}
/**
* Makes _initializedCarousel$ Subject become Observable
* @returns Observable of _initializedCarousel$ Subject
*/
getInitializedState() {
return this._initializedCarousel$.asObservable();
}
/**
* Makes _changeSettingsCarousel$ Subject become Observable
* @returns Observable of _changeSettingsCarousel$ Subject
*/
getChangeState() {
return this._changeSettingsCarousel$.asObservable();
}
/**
* Makes _changedSettingsCarousel$ Subject become Observable
* @returns Observable of _changedSettingsCarousel$ Subject
*/
getChangedState() {
return this._changedSettingsCarousel$.asObservable();
}
/**
* Makes _translateCarousel$ Subject become Observable
* @returns Observable of _translateCarousel$ Subject
*/
getTranslateState() {
return this._translateCarousel$.asObservable();
}
/**
* Makes _translatedCarousel$ Subject become Observable
* @returns Observable of _translatedCarousel$ Subject
*/
getTranslatedState() {
return this._translatedCarousel$.asObservable();
}
/**
* Makes _resizeCarousel$ Subject become Observable
* @returns Observable of _resizeCarousel$ Subject
*/
getResizeState() {
return this._resizeCarousel$.asObservable();
}
/**
* Makes _resizedCarousel$ Subject become Observable
* @returns Observable of _resizedCarousel$ Subject
*/
getResizedState() {
return this._resizedCarousel$.asObservable();
}
/**
* Makes _refreshCarousel$ Subject become Observable
* @returns Observable of _refreshCarousel$ Subject
*/
getRefreshState() {
return this._refreshCarousel$.asObservable();
}
/**
* Makes _refreshedCarousel$ Subject become Observable
* @returns Observable of _refreshedCarousel$ Subject
*/
getRefreshedState() {
return this._refreshedCarousel$.asObservable();
}
/**
* Makes _dragCarousel$ Subject become Observable
* @returns Observable of _dragCarousel$ Subject
*/
getDragState() {
return this._dragCarousel$.asObservable();
}
/**
* Makes _draggedCarousel$ Subject become Observable
* @returns Observable of _draggedCarousel$ Subject
*/
getDraggedState() {
return this._draggedCarousel$.asObservable();
}
/**
* Setups custom options expanding default options
* @param options custom options
*/
setOptions(options) {
const configOptions = new OwlCarouselOConfig();
const checkedOptions = this._validateOptions(options, configOptions);
this._options = Object.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
*/
_validateOptions(options, configOptions) {
const checkedOptions = Object.assign({}, options);
const mockedTypes = new OwlOptionsMockedTypes();
const setRightOption = (type, key) => {
this.logger.log(`options.${key} must be type of ${type}; ${key}=${options[key]} skipped to defaults: ${key}=${configOptions[key]}`);
return configOptions[key];
};
for (const key in checkedOptions) {
if (checkedOptions.hasOwnProperty(key)) {
// condition could be shortened but it gets harder for understanding
if (mockedTypes[key] === 'number') {
if (this._isNumeric(checkedOptions[key])) {
checkedOptions[key] = +checkedOptions[key];
checkedOptions[key] = key === 'items' ? this._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._isNumberOrBoolean(checkedOptions[key])) {
checkedOptions[key] = setRightOption(mockedTypes[key], key);
}
else if (mockedTypes[key] === 'number|string' && !this._isNumberOrString(checkedOptions[key])) {
checkedOptions[key] = setRightOption(mockedTypes[key], key);
}
else if (mockedTypes[key] === 'string|boolean' && !this._isStringOrBoolean(checkedOptions[key])) {
checkedOptions[key] = setRightOption(mockedTypes[key], key);
}
else if (mockedTypes[key] === 'string[]') {
if (Array.isArray(checkedOptions[key])) {
let isString = false;
checkedOptions[key].forEach(element => {
isString = typeof element === 'string' ? true : false;
});
if (!isString) {
checkedOptions[key] = setRightOption(mockedTypes[key], key);
}
}
else {
checkedOptions[key] = setRightOption(mockedTypes[key], 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
*/
_validateItems(items) {
let 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
*/
setCarouselWidth(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
*/
setup(carouselWidth, slides, options) {
this.setCarouselWidth(carouselWidth);
this.setItems(slides);
this._defineSlidesData();
this.setOptions(options);
this.settings = Object.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
*/
setOptionsForViewport() {
const viewport = this._width, overwrites = this._options.responsive;
let match = -1;
if (!Object.keys(overwrites).length) {
return;
}
if (!viewport) {
this.settings.items = 1;
return;
}
for (const key in overwrites) {
if (overwrites.hasOwnProperty(key)) {
if (+key <= viewport && +key > match) {
match = Number(key);
}
}
}
this.settings = Object.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;
const mergers = [];
this._items.forEach(item => {
const 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
*/
initialize(slides) {
this.enter('initializing');
// this.trigger('initialize');
this.owlDOMData.rtl = this.settings.rtl;
if (this._mergers.length) {
this._mergers = [];
}
slides.forEach(item => {
const 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
*/
sendChanges() {
this._viewSettingsShipper$.next({
owlDOMData: this.owlDOMData,
stageData: this.stageData,
slidesData: this.slidesData,
navData: this.navData,
dotsData: this.dotsData
});
}
/**
* Updates option logic if necessery
*/
_optionsLogic() {
if (this.settings.autoWidth) {
this.settings.stagePadding = 0;
this.settings.merge = false;
}
}
/**
* Updates the view
*/
update() {
let i = 0;
const n = this._pipe.length, filter = item => this._invalidated[item], cache = {};
while (i < n) {
const filteredPipe = this._pipe[i].filter.filter(filter);
if (this._invalidated.all || filteredPipe.length > 0) {
this._pipe[i].run(cache);
}
i++;
}
this.slidesData.forEach(slide => 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.
*/
width(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.
*/
refresh() {
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
*/
onResize(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
*/
prepareDragging(event) {
let 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
*/
enterDragging() {
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
*/
defineNewCoordsDrag(event, dragData) {
let minimum = null, maximum = null, pull = null;
const 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
*/
finishDragging(event, dragObj, clickAttacher) {
const 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)];
let 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.
*/
closest(coordinate, direction) {
const pull = 30, width = this.width();
let coordinates = this.coordinates(), position = -1;
if (this.settings.center) {
coordinates = coordinates.map(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 (let 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.
*/
animate(coordinate) {
const 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.
*/
is(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.
*/
current(position) {
if (position === undefined) {
return this._current;
}
if (this._items.length === 0) {
return undefined;
}
position = this.normalize(position);
if (this._current !== position) {
const event = 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.
*/
invalidate(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.
*/
reset(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.
*/
normalize(position, relative) {
const 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.
*/
relative(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
*/
maximum(relative = false) {
const settings = this.settings;
let 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
*/
minimum(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.
*/
items(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.
*/
mergers(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.
*/
clones(position) {
const odd = this._clones.length / 2, even = odd + this._items.length, map = index => index % 2 === 0 ? even + index / 2 : odd - (index + 1) / 2;
if (position === undefined) {
return this._clones.map((v, i) => map(i));
}
return this._clones.map((v, i) => v === position ? map(i) : null).filter(item => 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.
*/
speed(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.
*/
coordinates(position) {
let multiplier = 1, newPosition = position - 1, coordinate, result;
if (position === undefined) {
result = this._coordinates.map((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];