kmaterialize 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/js/carousel.js ADDED
@@ -0,0 +1,717 @@
1
+ (function($) {
2
+ 'use strict';
3
+
4
+ let _defaults = {
5
+ duration: 200, // ms
6
+ dist: -100, // zoom scale TODO: make this more intuitive as an option
7
+ shift: 0, // spacing for center image
8
+ padding: 0, // Padding between non center items
9
+ numVisible: 5, // Number of visible items in carousel
10
+ fullWidth: false, // Change to full width styles
11
+ indicators: false, // Toggle indicators
12
+ noWrap: false, // Don't wrap around and cycle through items.
13
+ onCycleTo: null // Callback for when a new slide is cycled to.
14
+ };
15
+
16
+ /**
17
+ * @class
18
+ *
19
+ */
20
+ class Carousel extends Component {
21
+ /**
22
+ * Construct Carousel instance
23
+ * @constructor
24
+ * @param {Element} el
25
+ * @param {Object} options
26
+ */
27
+ constructor(el, options) {
28
+ super(Carousel, el, options);
29
+
30
+ this.el.M_Carousel = this;
31
+
32
+ /**
33
+ * Options for the carousel
34
+ * @member Carousel#options
35
+ * @prop {Number} duration
36
+ * @prop {Number} dist
37
+ * @prop {Number} shift
38
+ * @prop {Number} padding
39
+ * @prop {Number} numVisible
40
+ * @prop {Boolean} fullWidth
41
+ * @prop {Boolean} indicators
42
+ * @prop {Boolean} noWrap
43
+ * @prop {Function} onCycleTo
44
+ */
45
+ this.options = $.extend({}, Carousel.defaults, options);
46
+
47
+ // Setup
48
+ this.hasMultipleSlides = this.$el.find('.carousel-item').length > 1;
49
+ this.showIndicators = this.options.indicators && this.hasMultipleSlides;
50
+ this.noWrap = this.options.noWrap || !this.hasMultipleSlides;
51
+ this.pressed = false;
52
+ this.dragged = false;
53
+ this.offset = this.target = 0;
54
+ this.images = [];
55
+ this.itemWidth = this.$el
56
+ .find('.carousel-item')
57
+ .first()
58
+ .innerWidth();
59
+ this.itemHeight = this.$el
60
+ .find('.carousel-item')
61
+ .first()
62
+ .innerHeight();
63
+ this.dim = this.itemWidth * 2 + this.options.padding || 1; // Make sure dim is non zero for divisions.
64
+ this._autoScrollBound = this._autoScroll.bind(this);
65
+ this._trackBound = this._track.bind(this);
66
+
67
+ // Full Width carousel setup
68
+ if (this.options.fullWidth) {
69
+ this.options.dist = 0;
70
+ this._setCarouselHeight();
71
+
72
+ // Offset fixed items when indicators.
73
+ if (this.showIndicators) {
74
+ this.$el.find('.carousel-fixed-item').addClass('with-indicators');
75
+ }
76
+ }
77
+
78
+ // Iterate through slides
79
+ this.$indicators = $('<ul class="indicators"></ul>');
80
+ this.$el.find('.carousel-item').each((el, i) => {
81
+ this.images.push(el);
82
+ if (this.showIndicators) {
83
+ let $indicator = $('<li class="indicator-item"></li>');
84
+
85
+ // Add active to first by default.
86
+ if (i === 0) {
87
+ $indicator[0].classList.add('active');
88
+ }
89
+
90
+ this.$indicators.append($indicator);
91
+ }
92
+ });
93
+ if (this.showIndicators) {
94
+ this.$el.append(this.$indicators);
95
+ }
96
+ this.count = this.images.length;
97
+
98
+ // Cap numVisible at count
99
+ this.options.numVisible = Math.min(this.count, this.options.numVisible);
100
+
101
+ // Setup cross browser string
102
+ this.xform = 'transform';
103
+ ['webkit', 'Moz', 'O', 'ms'].every((prefix) => {
104
+ var e = prefix + 'Transform';
105
+ if (typeof document.body.style[e] !== 'undefined') {
106
+ this.xform = e;
107
+ return false;
108
+ }
109
+ return true;
110
+ });
111
+
112
+ this._setupEventHandlers();
113
+ this._scroll(this.offset);
114
+ }
115
+
116
+ static get defaults() {
117
+ return _defaults;
118
+ }
119
+
120
+ static init(els, options) {
121
+ return super.init(this, els, options);
122
+ }
123
+
124
+ /**
125
+ * Get Instance
126
+ */
127
+ static getInstance(el) {
128
+ let domElem = !!el.jquery ? el[0] : el;
129
+ return domElem.M_Carousel;
130
+ }
131
+
132
+ /**
133
+ * Teardown component
134
+ */
135
+ destroy() {
136
+ this._removeEventHandlers();
137
+ this.el.M_Carousel = undefined;
138
+ }
139
+
140
+ /**
141
+ * Setup Event Handlers
142
+ */
143
+ _setupEventHandlers() {
144
+ this._handleCarouselTapBound = this._handleCarouselTap.bind(this);
145
+ this._handleCarouselDragBound = this._handleCarouselDrag.bind(this);
146
+ this._handleCarouselReleaseBound = this._handleCarouselRelease.bind(this);
147
+ this._handleCarouselClickBound = this._handleCarouselClick.bind(this);
148
+
149
+ if (typeof window.ontouchstart !== 'undefined') {
150
+ this.el.addEventListener('touchstart', this._handleCarouselTapBound);
151
+ this.el.addEventListener('touchmove', this._handleCarouselDragBound);
152
+ this.el.addEventListener('touchend', this._handleCarouselReleaseBound);
153
+ }
154
+
155
+ this.el.addEventListener('mousedown', this._handleCarouselTapBound);
156
+ this.el.addEventListener('mousemove', this._handleCarouselDragBound);
157
+ this.el.addEventListener('mouseup', this._handleCarouselReleaseBound);
158
+ this.el.addEventListener('mouseleave', this._handleCarouselReleaseBound);
159
+ this.el.addEventListener('click', this._handleCarouselClickBound);
160
+
161
+ if (this.showIndicators && this.$indicators) {
162
+ this._handleIndicatorClickBound = this._handleIndicatorClick.bind(this);
163
+ this.$indicators.find('.indicator-item').each((el, i) => {
164
+ el.addEventListener('click', this._handleIndicatorClickBound);
165
+ });
166
+ }
167
+
168
+ // Resize
169
+ let throttledResize = M.throttle(this._handleResize, 200);
170
+ this._handleThrottledResizeBound = throttledResize.bind(this);
171
+
172
+ window.addEventListener('resize', this._handleThrottledResizeBound);
173
+ }
174
+
175
+ /**
176
+ * Remove Event Handlers
177
+ */
178
+ _removeEventHandlers() {
179
+ if (typeof window.ontouchstart !== 'undefined') {
180
+ this.el.removeEventListener('touchstart', this._handleCarouselTapBound);
181
+ this.el.removeEventListener('touchmove', this._handleCarouselDragBound);
182
+ this.el.removeEventListener('touchend', this._handleCarouselReleaseBound);
183
+ }
184
+ this.el.removeEventListener('mousedown', this._handleCarouselTapBound);
185
+ this.el.removeEventListener('mousemove', this._handleCarouselDragBound);
186
+ this.el.removeEventListener('mouseup', this._handleCarouselReleaseBound);
187
+ this.el.removeEventListener('mouseleave', this._handleCarouselReleaseBound);
188
+ this.el.removeEventListener('click', this._handleCarouselClickBound);
189
+
190
+ if (this.showIndicators && this.$indicators) {
191
+ this.$indicators.find('.indicator-item').each((el, i) => {
192
+ el.removeEventListener('click', this._handleIndicatorClickBound);
193
+ });
194
+ }
195
+
196
+ window.removeEventListener('resize', this._handleThrottledResizeBound);
197
+ }
198
+
199
+ /**
200
+ * Handle Carousel Tap
201
+ * @param {Event} e
202
+ */
203
+ _handleCarouselTap(e) {
204
+ // Fixes firefox draggable image bug
205
+ if (e.type === 'mousedown' && $(e.target).is('img')) {
206
+ e.preventDefault();
207
+ }
208
+ this.pressed = true;
209
+ this.dragged = false;
210
+ this.verticalDragged = false;
211
+ this.reference = this._xpos(e);
212
+ this.referenceY = this._ypos(e);
213
+
214
+ this.velocity = this.amplitude = 0;
215
+ this.frame = this.offset;
216
+ this.timestamp = Date.now();
217
+ clearInterval(this.ticker);
218
+ this.ticker = setInterval(this._trackBound, 100);
219
+ }
220
+
221
+ /**
222
+ * Handle Carousel Drag
223
+ * @param {Event} e
224
+ */
225
+ _handleCarouselDrag(e) {
226
+ let x, y, delta, deltaY;
227
+ if (this.pressed) {
228
+ x = this._xpos(e);
229
+ y = this._ypos(e);
230
+ delta = this.reference - x;
231
+ deltaY = Math.abs(this.referenceY - y);
232
+ if (deltaY < 30 && !this.verticalDragged) {
233
+ // If vertical scrolling don't allow dragging.
234
+ if (delta > 2 || delta < -2) {
235
+ this.dragged = true;
236
+ this.reference = x;
237
+ this._scroll(this.offset + delta);
238
+ }
239
+ } else if (this.dragged) {
240
+ // If dragging don't allow vertical scroll.
241
+ e.preventDefault();
242
+ e.stopPropagation();
243
+ return false;
244
+ } else {
245
+ // Vertical scrolling.
246
+ this.verticalDragged = true;
247
+ }
248
+ }
249
+
250
+ if (this.dragged) {
251
+ // If dragging don't allow vertical scroll.
252
+ e.preventDefault();
253
+ e.stopPropagation();
254
+ return false;
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Handle Carousel Release
260
+ * @param {Event} e
261
+ */
262
+ _handleCarouselRelease(e) {
263
+ if (this.pressed) {
264
+ this.pressed = false;
265
+ } else {
266
+ return;
267
+ }
268
+
269
+ clearInterval(this.ticker);
270
+ this.target = this.offset;
271
+ if (this.velocity > 10 || this.velocity < -10) {
272
+ this.amplitude = 0.9 * this.velocity;
273
+ this.target = this.offset + this.amplitude;
274
+ }
275
+ this.target = Math.round(this.target / this.dim) * this.dim;
276
+
277
+ // No wrap of items.
278
+ if (this.noWrap) {
279
+ if (this.target >= this.dim * (this.count - 1)) {
280
+ this.target = this.dim * (this.count - 1);
281
+ } else if (this.target < 0) {
282
+ this.target = 0;
283
+ }
284
+ }
285
+ this.amplitude = this.target - this.offset;
286
+ this.timestamp = Date.now();
287
+ requestAnimationFrame(this._autoScrollBound);
288
+
289
+ if (this.dragged) {
290
+ e.preventDefault();
291
+ e.stopPropagation();
292
+ }
293
+ return false;
294
+ }
295
+
296
+ /**
297
+ * Handle Carousel CLick
298
+ * @param {Event} e
299
+ */
300
+ _handleCarouselClick(e) {
301
+ // Disable clicks if carousel was dragged.
302
+ if (this.dragged) {
303
+ e.preventDefault();
304
+ e.stopPropagation();
305
+ return false;
306
+ } else if (!this.options.fullWidth) {
307
+ let clickedIndex = $(e.target)
308
+ .closest('.carousel-item')
309
+ .index();
310
+ let diff = this._wrap(this.center) - clickedIndex;
311
+
312
+ // Disable clicks if carousel was shifted by click
313
+ if (diff !== 0) {
314
+ e.preventDefault();
315
+ e.stopPropagation();
316
+ }
317
+ this._cycleTo(clickedIndex);
318
+ }
319
+ }
320
+
321
+ /**
322
+ * Handle Indicator CLick
323
+ * @param {Event} e
324
+ */
325
+ _handleIndicatorClick(e) {
326
+ e.stopPropagation();
327
+
328
+ let indicator = $(e.target).closest('.indicator-item');
329
+ if (indicator.length) {
330
+ this._cycleTo(indicator.index());
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Handle Throttle Resize
336
+ * @param {Event} e
337
+ */
338
+ _handleResize(e) {
339
+ if (this.options.fullWidth) {
340
+ this.itemWidth = this.$el
341
+ .find('.carousel-item')
342
+ .first()
343
+ .innerWidth();
344
+ this.imageHeight = this.$el.find('.carousel-item.active').height();
345
+ this.dim = this.itemWidth * 2 + this.options.padding;
346
+ this.offset = this.center * 2 * this.itemWidth;
347
+ this.target = this.offset;
348
+ this._setCarouselHeight(true);
349
+ } else {
350
+ this._scroll();
351
+ }
352
+ }
353
+
354
+ /**
355
+ * Set carousel height based on first slide
356
+ * @param {Booleam} imageOnly - true for image slides
357
+ */
358
+ _setCarouselHeight(imageOnly) {
359
+ let firstSlide = this.$el.find('.carousel-item.active').length
360
+ ? this.$el.find('.carousel-item.active').first()
361
+ : this.$el.find('.carousel-item').first();
362
+ let firstImage = firstSlide.find('img').first();
363
+ if (firstImage.length) {
364
+ if (firstImage[0].complete) {
365
+ // If image won't trigger the load event
366
+ let imageHeight = firstImage.height();
367
+ if (imageHeight > 0) {
368
+ this.$el.css('height', imageHeight + 'px');
369
+ } else {
370
+ // If image still has no height, use the natural dimensions to calculate
371
+ let naturalWidth = firstImage[0].naturalWidth;
372
+ let naturalHeight = firstImage[0].naturalHeight;
373
+ let adjustedHeight = this.$el.width() / naturalWidth * naturalHeight;
374
+ this.$el.css('height', adjustedHeight + 'px');
375
+ }
376
+ } else {
377
+ // Get height when image is loaded normally
378
+ firstImage.one('load', (el, i) => {
379
+ this.$el.css('height', el.offsetHeight + 'px');
380
+ });
381
+ }
382
+ } else if (!imageOnly) {
383
+ let slideHeight = firstSlide.height();
384
+ this.$el.css('height', slideHeight + 'px');
385
+ }
386
+ }
387
+
388
+ /**
389
+ * Get x position from event
390
+ * @param {Event} e
391
+ */
392
+ _xpos(e) {
393
+ // touch event
394
+ if (e.targetTouches && e.targetTouches.length >= 1) {
395
+ return e.targetTouches[0].clientX;
396
+ }
397
+
398
+ // mouse event
399
+ return e.clientX;
400
+ }
401
+
402
+ /**
403
+ * Get y position from event
404
+ * @param {Event} e
405
+ */
406
+ _ypos(e) {
407
+ // touch event
408
+ if (e.targetTouches && e.targetTouches.length >= 1) {
409
+ return e.targetTouches[0].clientY;
410
+ }
411
+
412
+ // mouse event
413
+ return e.clientY;
414
+ }
415
+
416
+ /**
417
+ * Wrap index
418
+ * @param {Number} x
419
+ */
420
+ _wrap(x) {
421
+ return x >= this.count ? x % this.count : x < 0 ? this._wrap(this.count + x % this.count) : x;
422
+ }
423
+
424
+ /**
425
+ * Tracks scrolling information
426
+ */
427
+ _track() {
428
+ let now, elapsed, delta, v;
429
+
430
+ now = Date.now();
431
+ elapsed = now - this.timestamp;
432
+ this.timestamp = now;
433
+ delta = this.offset - this.frame;
434
+ this.frame = this.offset;
435
+
436
+ v = 1000 * delta / (1 + elapsed);
437
+ this.velocity = 0.8 * v + 0.2 * this.velocity;
438
+ }
439
+
440
+ /**
441
+ * Auto scrolls to nearest carousel item.
442
+ */
443
+ _autoScroll() {
444
+ let elapsed, delta;
445
+
446
+ if (this.amplitude) {
447
+ elapsed = Date.now() - this.timestamp;
448
+ delta = this.amplitude * Math.exp(-elapsed / this.options.duration);
449
+ if (delta > 2 || delta < -2) {
450
+ this._scroll(this.target - delta);
451
+ requestAnimationFrame(this._autoScrollBound);
452
+ } else {
453
+ this._scroll(this.target);
454
+ }
455
+ }
456
+ }
457
+
458
+ /**
459
+ * Scroll to target
460
+ * @param {Number} x
461
+ */
462
+ _scroll(x) {
463
+ // Track scrolling state
464
+ if (!this.$el.hasClass('scrolling')) {
465
+ this.el.classList.add('scrolling');
466
+ }
467
+ if (this.scrollingTimeout != null) {
468
+ window.clearTimeout(this.scrollingTimeout);
469
+ }
470
+ this.scrollingTimeout = window.setTimeout(() => {
471
+ this.$el.removeClass('scrolling');
472
+ }, this.options.duration);
473
+
474
+ // Start actual scroll
475
+ let i,
476
+ half,
477
+ delta,
478
+ dir,
479
+ tween,
480
+ el,
481
+ alignment,
482
+ zTranslation,
483
+ tweenedOpacity,
484
+ centerTweenedOpacity;
485
+ let lastCenter = this.center;
486
+ let numVisibleOffset = 1 / this.options.numVisible;
487
+
488
+ this.offset = typeof x === 'number' ? x : this.offset;
489
+ this.center = Math.floor((this.offset + this.dim / 2) / this.dim);
490
+ delta = this.offset - this.center * this.dim;
491
+ dir = delta < 0 ? 1 : -1;
492
+ tween = -dir * delta * 2 / this.dim;
493
+ half = this.count >> 1;
494
+
495
+ if (this.options.fullWidth) {
496
+ alignment = 'translateX(0)';
497
+ centerTweenedOpacity = 1;
498
+ } else {
499
+ alignment = 'translateX(' + (this.el.clientWidth - this.itemWidth) / 2 + 'px) ';
500
+ alignment += 'translateY(' + (this.el.clientHeight - this.itemHeight) / 2 + 'px)';
501
+ centerTweenedOpacity = 1 - numVisibleOffset * tween;
502
+ }
503
+
504
+ // Set indicator active
505
+ if (this.showIndicators) {
506
+ let diff = this.center % this.count;
507
+ let activeIndicator = this.$indicators.find('.indicator-item.active');
508
+ if (activeIndicator.index() !== diff) {
509
+ activeIndicator.removeClass('active');
510
+ this.$indicators
511
+ .find('.indicator-item')
512
+ .eq(diff)[0]
513
+ .classList.add('active');
514
+ }
515
+ }
516
+
517
+ // center
518
+ // Don't show wrapped items.
519
+ if (!this.noWrap || (this.center >= 0 && this.center < this.count)) {
520
+ el = this.images[this._wrap(this.center)];
521
+
522
+ // Add active class to center item.
523
+ if (!$(el).hasClass('active')) {
524
+ this.$el.find('.carousel-item').removeClass('active');
525
+ el.classList.add('active');
526
+ }
527
+ let transformString = `${alignment} translateX(${-delta / 2}px) translateX(${dir *
528
+ this.options.shift *
529
+ tween *
530
+ i}px) translateZ(${this.options.dist * tween}px)`;
531
+ this._updateItemStyle(el, centerTweenedOpacity, 0, transformString);
532
+ }
533
+
534
+ for (i = 1; i <= half; ++i) {
535
+ // right side
536
+ if (this.options.fullWidth) {
537
+ zTranslation = this.options.dist;
538
+ tweenedOpacity = i === half && delta < 0 ? 1 - tween : 1;
539
+ } else {
540
+ zTranslation = this.options.dist * (i * 2 + tween * dir);
541
+ tweenedOpacity = 1 - numVisibleOffset * (i * 2 + tween * dir);
542
+ }
543
+ // Don't show wrapped items.
544
+ if (!this.noWrap || this.center + i < this.count) {
545
+ el = this.images[this._wrap(this.center + i)];
546
+ let transformString = `${alignment} translateX(${this.options.shift +
547
+ (this.dim * i - delta) / 2}px) translateZ(${zTranslation}px)`;
548
+ this._updateItemStyle(el, tweenedOpacity, -i, transformString);
549
+ }
550
+
551
+ // left side
552
+ if (this.options.fullWidth) {
553
+ zTranslation = this.options.dist;
554
+ tweenedOpacity = i === half && delta > 0 ? 1 - tween : 1;
555
+ } else {
556
+ zTranslation = this.options.dist * (i * 2 - tween * dir);
557
+ tweenedOpacity = 1 - numVisibleOffset * (i * 2 - tween * dir);
558
+ }
559
+ // Don't show wrapped items.
560
+ if (!this.noWrap || this.center - i >= 0) {
561
+ el = this.images[this._wrap(this.center - i)];
562
+ let transformString = `${alignment} translateX(${-this.options.shift +
563
+ (-this.dim * i - delta) / 2}px) translateZ(${zTranslation}px)`;
564
+ this._updateItemStyle(el, tweenedOpacity, -i, transformString);
565
+ }
566
+ }
567
+
568
+ // center
569
+ // Don't show wrapped items.
570
+ if (!this.noWrap || (this.center >= 0 && this.center < this.count)) {
571
+ el = this.images[this._wrap(this.center)];
572
+ let transformString = `${alignment} translateX(${-delta / 2}px) translateX(${dir *
573
+ this.options.shift *
574
+ tween}px) translateZ(${this.options.dist * tween}px)`;
575
+ this._updateItemStyle(el, centerTweenedOpacity, 0, transformString);
576
+ }
577
+
578
+ // onCycleTo callback
579
+ let $currItem = this.$el.find('.carousel-item').eq(this._wrap(this.center));
580
+ if (lastCenter !== this.center && typeof this.options.onCycleTo === 'function') {
581
+ this.options.onCycleTo.call(this, $currItem[0], this.dragged);
582
+ }
583
+
584
+ // One time callback
585
+ if (typeof this.oneTimeCallback === 'function') {
586
+ this.oneTimeCallback.call(this, $currItem[0], this.dragged);
587
+ this.oneTimeCallback = null;
588
+ }
589
+ }
590
+
591
+ /**
592
+ * Cycle to target
593
+ * @param {Element} el
594
+ * @param {Number} opacity
595
+ * @param {Number} zIndex
596
+ * @param {String} transform
597
+ */
598
+ _updateItemStyle(el, opacity, zIndex, transform) {
599
+ el.style[this.xform] = transform;
600
+ el.style.zIndex = zIndex;
601
+ el.style.opacity = opacity;
602
+ el.style.visibility = 'visible';
603
+ }
604
+
605
+ /**
606
+ * Cycle to target
607
+ * @param {Number} n
608
+ * @param {Function} callback
609
+ */
610
+ _cycleTo(n, callback) {
611
+ let diff = this.center % this.count - n;
612
+
613
+ // Account for wraparound.
614
+ if (!this.noWrap) {
615
+ if (diff < 0) {
616
+ if (Math.abs(diff + this.count) < Math.abs(diff)) {
617
+ diff += this.count;
618
+ }
619
+ } else if (diff > 0) {
620
+ if (Math.abs(diff - this.count) < diff) {
621
+ diff -= this.count;
622
+ }
623
+ }
624
+ }
625
+
626
+ this.target = this.dim * Math.round(this.offset / this.dim);
627
+ // Next
628
+ if (diff < 0) {
629
+ this.target += this.dim * Math.abs(diff);
630
+
631
+ // Prev
632
+ } else if (diff > 0) {
633
+ this.target -= this.dim * diff;
634
+ }
635
+
636
+ // Set one time callback
637
+ if (typeof callback === 'function') {
638
+ this.oneTimeCallback = callback;
639
+ }
640
+
641
+ // Scroll
642
+ if (this.offset !== this.target) {
643
+ this.amplitude = this.target - this.offset;
644
+ this.timestamp = Date.now();
645
+ requestAnimationFrame(this._autoScrollBound);
646
+ }
647
+ }
648
+
649
+ /**
650
+ * Cycle to next item
651
+ * @param {Number} [n]
652
+ */
653
+ next(n) {
654
+ if (n === undefined || isNaN(n)) {
655
+ n = 1;
656
+ }
657
+
658
+ let index = this.center + n;
659
+ if (index >= this.count || index < 0) {
660
+ if (this.noWrap) {
661
+ return;
662
+ }
663
+
664
+ index = this._wrap(index);
665
+ }
666
+ this._cycleTo(index);
667
+ }
668
+
669
+ /**
670
+ * Cycle to previous item
671
+ * @param {Number} [n]
672
+ */
673
+ prev(n) {
674
+ if (n === undefined || isNaN(n)) {
675
+ n = 1;
676
+ }
677
+
678
+ let index = this.center - n;
679
+ if (index >= this.count || index < 0) {
680
+ if (this.noWrap) {
681
+ return;
682
+ }
683
+
684
+ index = this._wrap(index);
685
+ }
686
+
687
+ this._cycleTo(index);
688
+ }
689
+
690
+ /**
691
+ * Cycle to nth item
692
+ * @param {Number} [n]
693
+ * @param {Function} callback
694
+ */
695
+ set(n, callback) {
696
+ if (n === undefined || isNaN(n)) {
697
+ n = 0;
698
+ }
699
+
700
+ if (n > this.count || n < 0) {
701
+ if (this.noWrap) {
702
+ return;
703
+ }
704
+
705
+ n = this._wrap(n);
706
+ }
707
+
708
+ this._cycleTo(n, callback);
709
+ }
710
+ }
711
+
712
+ M.Carousel = Carousel;
713
+
714
+ if (M.jQueryLoaded) {
715
+ M.initializeJqueryWrapper(Carousel, 'carousel', 'M_Carousel');
716
+ }
717
+ })(cash);