embla-carousel.cjs.js 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672
  1. 'use strict';
  2. function isNumber(subject) {
  3. return typeof subject === 'number';
  4. }
  5. function isString(subject) {
  6. return typeof subject === 'string';
  7. }
  8. function isBoolean(subject) {
  9. return typeof subject === 'boolean';
  10. }
  11. function isObject(subject) {
  12. return Object.prototype.toString.call(subject) === '[object Object]';
  13. }
  14. function mathAbs(n) {
  15. return Math.abs(n);
  16. }
  17. function mathSign(n) {
  18. return Math.sign(n);
  19. }
  20. function deltaAbs(valueB, valueA) {
  21. return mathAbs(valueB - valueA);
  22. }
  23. function factorAbs(valueB, valueA) {
  24. if (valueB === 0 || valueA === 0) return 0;
  25. if (mathAbs(valueB) <= mathAbs(valueA)) return 0;
  26. const diff = deltaAbs(mathAbs(valueB), mathAbs(valueA));
  27. return mathAbs(diff / valueB);
  28. }
  29. function roundToTwoDecimals(num) {
  30. return Math.round(num * 100) / 100;
  31. }
  32. function arrayKeys(array) {
  33. return objectKeys(array).map(Number);
  34. }
  35. function arrayLast(array) {
  36. return array[arrayLastIndex(array)];
  37. }
  38. function arrayLastIndex(array) {
  39. return Math.max(0, array.length - 1);
  40. }
  41. function arrayIsLastIndex(array, index) {
  42. return index === arrayLastIndex(array);
  43. }
  44. function arrayFromNumber(n, startAt = 0) {
  45. return Array.from(Array(n), (_, i) => startAt + i);
  46. }
  47. function objectKeys(object) {
  48. return Object.keys(object);
  49. }
  50. function objectsMergeDeep(objectA, objectB) {
  51. return [objectA, objectB].reduce((mergedObjects, currentObject) => {
  52. objectKeys(currentObject).forEach(key => {
  53. const valueA = mergedObjects[key];
  54. const valueB = currentObject[key];
  55. const areObjects = isObject(valueA) && isObject(valueB);
  56. mergedObjects[key] = areObjects ? objectsMergeDeep(valueA, valueB) : valueB;
  57. });
  58. return mergedObjects;
  59. }, {});
  60. }
  61. function isMouseEvent(evt, ownerWindow) {
  62. return typeof ownerWindow.MouseEvent !== 'undefined' && evt instanceof ownerWindow.MouseEvent;
  63. }
  64. function Alignment(align, viewSize) {
  65. const predefined = {
  66. start,
  67. center,
  68. end
  69. };
  70. function start() {
  71. return 0;
  72. }
  73. function center(n) {
  74. return end(n) / 2;
  75. }
  76. function end(n) {
  77. return viewSize - n;
  78. }
  79. function measure(n, index) {
  80. if (isString(align)) return predefined[align](n);
  81. return align(viewSize, n, index);
  82. }
  83. const self = {
  84. measure
  85. };
  86. return self;
  87. }
  88. function EventStore() {
  89. let listeners = [];
  90. function add(node, type, handler, options = {
  91. passive: true
  92. }) {
  93. let removeListener;
  94. if ('addEventListener' in node) {
  95. node.addEventListener(type, handler, options);
  96. removeListener = () => node.removeEventListener(type, handler, options);
  97. } else {
  98. const legacyMediaQueryList = node;
  99. legacyMediaQueryList.addListener(handler);
  100. removeListener = () => legacyMediaQueryList.removeListener(handler);
  101. }
  102. listeners.push(removeListener);
  103. return self;
  104. }
  105. function clear() {
  106. listeners = listeners.filter(remove => remove());
  107. }
  108. const self = {
  109. add,
  110. clear
  111. };
  112. return self;
  113. }
  114. function Animations(ownerDocument, ownerWindow, update, render) {
  115. const documentVisibleHandler = EventStore();
  116. const fixedTimeStep = 1000 / 60;
  117. let lastTimeStamp = null;
  118. let accumulatedTime = 0;
  119. let animationId = 0;
  120. function init() {
  121. documentVisibleHandler.add(ownerDocument, 'visibilitychange', () => {
  122. if (ownerDocument.hidden) reset();
  123. });
  124. }
  125. function destroy() {
  126. stop();
  127. documentVisibleHandler.clear();
  128. }
  129. function animate(timeStamp) {
  130. if (!animationId) return;
  131. if (!lastTimeStamp) {
  132. lastTimeStamp = timeStamp;
  133. update();
  134. update();
  135. }
  136. const timeElapsed = timeStamp - lastTimeStamp;
  137. lastTimeStamp = timeStamp;
  138. accumulatedTime += timeElapsed;
  139. while (accumulatedTime >= fixedTimeStep) {
  140. update();
  141. accumulatedTime -= fixedTimeStep;
  142. }
  143. const alpha = accumulatedTime / fixedTimeStep;
  144. render(alpha);
  145. if (animationId) {
  146. animationId = ownerWindow.requestAnimationFrame(animate);
  147. }
  148. }
  149. function start() {
  150. if (animationId) return;
  151. animationId = ownerWindow.requestAnimationFrame(animate);
  152. }
  153. function stop() {
  154. ownerWindow.cancelAnimationFrame(animationId);
  155. lastTimeStamp = null;
  156. accumulatedTime = 0;
  157. animationId = 0;
  158. }
  159. function reset() {
  160. lastTimeStamp = null;
  161. accumulatedTime = 0;
  162. }
  163. const self = {
  164. init,
  165. destroy,
  166. start,
  167. stop,
  168. update,
  169. render
  170. };
  171. return self;
  172. }
  173. function Axis(axis, contentDirection) {
  174. const isRightToLeft = contentDirection === 'rtl';
  175. const isVertical = axis === 'y';
  176. const scroll = isVertical ? 'y' : 'x';
  177. const cross = isVertical ? 'x' : 'y';
  178. const sign = !isVertical && isRightToLeft ? -1 : 1;
  179. const startEdge = getStartEdge();
  180. const endEdge = getEndEdge();
  181. function measureSize(nodeRect) {
  182. const {
  183. height,
  184. width
  185. } = nodeRect;
  186. return isVertical ? height : width;
  187. }
  188. function getStartEdge() {
  189. if (isVertical) return 'top';
  190. return isRightToLeft ? 'right' : 'left';
  191. }
  192. function getEndEdge() {
  193. if (isVertical) return 'bottom';
  194. return isRightToLeft ? 'left' : 'right';
  195. }
  196. function direction(n) {
  197. return n * sign;
  198. }
  199. const self = {
  200. scroll,
  201. cross,
  202. startEdge,
  203. endEdge,
  204. measureSize,
  205. direction
  206. };
  207. return self;
  208. }
  209. function Limit(min = 0, max = 0) {
  210. const length = mathAbs(min - max);
  211. function reachedMin(n) {
  212. return n < min;
  213. }
  214. function reachedMax(n) {
  215. return n > max;
  216. }
  217. function reachedAny(n) {
  218. return reachedMin(n) || reachedMax(n);
  219. }
  220. function constrain(n) {
  221. if (!reachedAny(n)) return n;
  222. return reachedMin(n) ? min : max;
  223. }
  224. function removeOffset(n) {
  225. if (!length) return n;
  226. return n - length * Math.ceil((n - max) / length);
  227. }
  228. const self = {
  229. length,
  230. max,
  231. min,
  232. constrain,
  233. reachedAny,
  234. reachedMax,
  235. reachedMin,
  236. removeOffset
  237. };
  238. return self;
  239. }
  240. function Counter(max, start, loop) {
  241. const {
  242. constrain
  243. } = Limit(0, max);
  244. const loopEnd = max + 1;
  245. let counter = withinLimit(start);
  246. function withinLimit(n) {
  247. return !loop ? constrain(n) : mathAbs((loopEnd + n) % loopEnd);
  248. }
  249. function get() {
  250. return counter;
  251. }
  252. function set(n) {
  253. counter = withinLimit(n);
  254. return self;
  255. }
  256. function add(n) {
  257. return clone().set(get() + n);
  258. }
  259. function clone() {
  260. return Counter(max, get(), loop);
  261. }
  262. const self = {
  263. get,
  264. set,
  265. add,
  266. clone
  267. };
  268. return self;
  269. }
  270. function DragHandler(axis, rootNode, ownerDocument, ownerWindow, target, dragTracker, location, animation, scrollTo, scrollBody, scrollTarget, index, eventHandler, percentOfView, dragFree, dragThreshold, skipSnaps, baseFriction, watchDrag) {
  271. const {
  272. cross: crossAxis,
  273. direction
  274. } = axis;
  275. const focusNodes = ['INPUT', 'SELECT', 'TEXTAREA'];
  276. const nonPassiveEvent = {
  277. passive: false
  278. };
  279. const initEvents = EventStore();
  280. const dragEvents = EventStore();
  281. const goToNextThreshold = Limit(50, 225).constrain(percentOfView.measure(20));
  282. const snapForceBoost = {
  283. mouse: 300,
  284. touch: 400
  285. };
  286. const freeForceBoost = {
  287. mouse: 500,
  288. touch: 600
  289. };
  290. const baseSpeed = dragFree ? 43 : 25;
  291. let isMoving = false;
  292. let startScroll = 0;
  293. let startCross = 0;
  294. let pointerIsDown = false;
  295. let preventScroll = false;
  296. let preventClick = false;
  297. let isMouse = false;
  298. function init(emblaApi) {
  299. if (!watchDrag) return;
  300. function downIfAllowed(evt) {
  301. if (isBoolean(watchDrag) || watchDrag(emblaApi, evt)) down(evt);
  302. }
  303. const node = rootNode;
  304. initEvents.add(node, 'dragstart', evt => evt.preventDefault(), nonPassiveEvent).add(node, 'touchmove', () => undefined, nonPassiveEvent).add(node, 'touchend', () => undefined).add(node, 'touchstart', downIfAllowed).add(node, 'mousedown', downIfAllowed).add(node, 'touchcancel', up).add(node, 'contextmenu', up).add(node, 'click', click, true);
  305. }
  306. function destroy() {
  307. initEvents.clear();
  308. dragEvents.clear();
  309. }
  310. function addDragEvents() {
  311. const node = isMouse ? ownerDocument : rootNode;
  312. dragEvents.add(node, 'touchmove', move, nonPassiveEvent).add(node, 'touchend', up).add(node, 'mousemove', move, nonPassiveEvent).add(node, 'mouseup', up);
  313. }
  314. function isFocusNode(node) {
  315. const nodeName = node.nodeName || '';
  316. return focusNodes.includes(nodeName);
  317. }
  318. function forceBoost() {
  319. const boost = dragFree ? freeForceBoost : snapForceBoost;
  320. const type = isMouse ? 'mouse' : 'touch';
  321. return boost[type];
  322. }
  323. function allowedForce(force, targetChanged) {
  324. const next = index.add(mathSign(force) * -1);
  325. const baseForce = scrollTarget.byDistance(force, !dragFree).distance;
  326. if (dragFree || mathAbs(force) < goToNextThreshold) return baseForce;
  327. if (skipSnaps && targetChanged) return baseForce * 0.5;
  328. return scrollTarget.byIndex(next.get(), 0).distance;
  329. }
  330. function down(evt) {
  331. const isMouseEvt = isMouseEvent(evt, ownerWindow);
  332. isMouse = isMouseEvt;
  333. preventClick = dragFree && isMouseEvt && !evt.buttons && isMoving;
  334. isMoving = deltaAbs(target.get(), location.get()) >= 2;
  335. if (isMouseEvt && evt.button !== 0) return;
  336. if (isFocusNode(evt.target)) return;
  337. pointerIsDown = true;
  338. dragTracker.pointerDown(evt);
  339. scrollBody.useFriction(0).useDuration(0);
  340. target.set(location);
  341. addDragEvents();
  342. startScroll = dragTracker.readPoint(evt);
  343. startCross = dragTracker.readPoint(evt, crossAxis);
  344. eventHandler.emit('pointerDown');
  345. }
  346. function move(evt) {
  347. const isTouchEvt = !isMouseEvent(evt, ownerWindow);
  348. if (isTouchEvt && evt.touches.length >= 2) return up(evt);
  349. const lastScroll = dragTracker.readPoint(evt);
  350. const lastCross = dragTracker.readPoint(evt, crossAxis);
  351. const diffScroll = deltaAbs(lastScroll, startScroll);
  352. const diffCross = deltaAbs(lastCross, startCross);
  353. if (!preventScroll && !isMouse) {
  354. if (!evt.cancelable) return up(evt);
  355. preventScroll = diffScroll > diffCross;
  356. if (!preventScroll) return up(evt);
  357. }
  358. const diff = dragTracker.pointerMove(evt);
  359. if (diffScroll > dragThreshold) preventClick = true;
  360. scrollBody.useFriction(0.3).useDuration(0.75);
  361. animation.start();
  362. target.add(direction(diff));
  363. evt.preventDefault();
  364. }
  365. function up(evt) {
  366. const currentLocation = scrollTarget.byDistance(0, false);
  367. const targetChanged = currentLocation.index !== index.get();
  368. const rawForce = dragTracker.pointerUp(evt) * forceBoost();
  369. const force = allowedForce(direction(rawForce), targetChanged);
  370. const forceFactor = factorAbs(rawForce, force);
  371. const speed = baseSpeed - 10 * forceFactor;
  372. const friction = baseFriction + forceFactor / 50;
  373. preventScroll = false;
  374. pointerIsDown = false;
  375. dragEvents.clear();
  376. scrollBody.useDuration(speed).useFriction(friction);
  377. scrollTo.distance(force, !dragFree);
  378. isMouse = false;
  379. eventHandler.emit('pointerUp');
  380. }
  381. function click(evt) {
  382. if (preventClick) {
  383. evt.stopPropagation();
  384. evt.preventDefault();
  385. preventClick = false;
  386. }
  387. }
  388. function pointerDown() {
  389. return pointerIsDown;
  390. }
  391. const self = {
  392. init,
  393. destroy,
  394. pointerDown
  395. };
  396. return self;
  397. }
  398. function DragTracker(axis, ownerWindow) {
  399. const logInterval = 170;
  400. let startEvent;
  401. let lastEvent;
  402. function readTime(evt) {
  403. return evt.timeStamp;
  404. }
  405. function readPoint(evt, evtAxis) {
  406. const property = evtAxis || axis.scroll;
  407. const coord = `client${property === 'x' ? 'X' : 'Y'}`;
  408. return (isMouseEvent(evt, ownerWindow) ? evt : evt.touches[0])[coord];
  409. }
  410. function pointerDown(evt) {
  411. startEvent = evt;
  412. lastEvent = evt;
  413. return readPoint(evt);
  414. }
  415. function pointerMove(evt) {
  416. const diff = readPoint(evt) - readPoint(lastEvent);
  417. const expired = readTime(evt) - readTime(startEvent) > logInterval;
  418. lastEvent = evt;
  419. if (expired) startEvent = evt;
  420. return diff;
  421. }
  422. function pointerUp(evt) {
  423. if (!startEvent || !lastEvent) return 0;
  424. const diffDrag = readPoint(lastEvent) - readPoint(startEvent);
  425. const diffTime = readTime(evt) - readTime(startEvent);
  426. const expired = readTime(evt) - readTime(lastEvent) > logInterval;
  427. const force = diffDrag / diffTime;
  428. const isFlick = diffTime && !expired && mathAbs(force) > 0.1;
  429. return isFlick ? force : 0;
  430. }
  431. const self = {
  432. pointerDown,
  433. pointerMove,
  434. pointerUp,
  435. readPoint
  436. };
  437. return self;
  438. }
  439. function NodeRects() {
  440. function measure(node) {
  441. const {
  442. offsetTop,
  443. offsetLeft,
  444. offsetWidth,
  445. offsetHeight
  446. } = node;
  447. const offset = {
  448. top: offsetTop,
  449. right: offsetLeft + offsetWidth,
  450. bottom: offsetTop + offsetHeight,
  451. left: offsetLeft,
  452. width: offsetWidth,
  453. height: offsetHeight
  454. };
  455. return offset;
  456. }
  457. const self = {
  458. measure
  459. };
  460. return self;
  461. }
  462. function PercentOfView(viewSize) {
  463. function measure(n) {
  464. return viewSize * (n / 100);
  465. }
  466. const self = {
  467. measure
  468. };
  469. return self;
  470. }
  471. function ResizeHandler(container, eventHandler, ownerWindow, slides, axis, watchResize, nodeRects) {
  472. const observeNodes = [container].concat(slides);
  473. let resizeObserver;
  474. let containerSize;
  475. let slideSizes = [];
  476. let destroyed = false;
  477. function readSize(node) {
  478. return axis.measureSize(nodeRects.measure(node));
  479. }
  480. function init(emblaApi) {
  481. if (!watchResize) return;
  482. containerSize = readSize(container);
  483. slideSizes = slides.map(readSize);
  484. function defaultCallback(entries) {
  485. for (const entry of entries) {
  486. if (destroyed) return;
  487. const isContainer = entry.target === container;
  488. const slideIndex = slides.indexOf(entry.target);
  489. const lastSize = isContainer ? containerSize : slideSizes[slideIndex];
  490. const newSize = readSize(isContainer ? container : slides[slideIndex]);
  491. const diffSize = mathAbs(newSize - lastSize);
  492. if (diffSize >= 0.5) {
  493. emblaApi.reInit();
  494. eventHandler.emit('resize');
  495. break;
  496. }
  497. }
  498. }
  499. resizeObserver = new ResizeObserver(entries => {
  500. if (isBoolean(watchResize) || watchResize(emblaApi, entries)) {
  501. defaultCallback(entries);
  502. }
  503. });
  504. ownerWindow.requestAnimationFrame(() => {
  505. observeNodes.forEach(node => resizeObserver.observe(node));
  506. });
  507. }
  508. function destroy() {
  509. destroyed = true;
  510. if (resizeObserver) resizeObserver.disconnect();
  511. }
  512. const self = {
  513. init,
  514. destroy
  515. };
  516. return self;
  517. }
  518. function ScrollBody(location, offsetLocation, previousLocation, target, baseDuration, baseFriction) {
  519. let scrollVelocity = 0;
  520. let scrollDirection = 0;
  521. let scrollDuration = baseDuration;
  522. let scrollFriction = baseFriction;
  523. let rawLocation = location.get();
  524. let rawLocationPrevious = 0;
  525. function seek() {
  526. const displacement = target.get() - location.get();
  527. const isInstant = !scrollDuration;
  528. let scrollDistance = 0;
  529. if (isInstant) {
  530. scrollVelocity = 0;
  531. previousLocation.set(target);
  532. location.set(target);
  533. scrollDistance = displacement;
  534. } else {
  535. previousLocation.set(location);
  536. scrollVelocity += displacement / scrollDuration;
  537. scrollVelocity *= scrollFriction;
  538. rawLocation += scrollVelocity;
  539. location.add(scrollVelocity);
  540. scrollDistance = rawLocation - rawLocationPrevious;
  541. }
  542. scrollDirection = mathSign(scrollDistance);
  543. rawLocationPrevious = rawLocation;
  544. return self;
  545. }
  546. function settled() {
  547. const diff = target.get() - offsetLocation.get();
  548. return mathAbs(diff) < 0.001;
  549. }
  550. function duration() {
  551. return scrollDuration;
  552. }
  553. function direction() {
  554. return scrollDirection;
  555. }
  556. function velocity() {
  557. return scrollVelocity;
  558. }
  559. function useBaseDuration() {
  560. return useDuration(baseDuration);
  561. }
  562. function useBaseFriction() {
  563. return useFriction(baseFriction);
  564. }
  565. function useDuration(n) {
  566. scrollDuration = n;
  567. return self;
  568. }
  569. function useFriction(n) {
  570. scrollFriction = n;
  571. return self;
  572. }
  573. const self = {
  574. direction,
  575. duration,
  576. velocity,
  577. seek,
  578. settled,
  579. useBaseFriction,
  580. useBaseDuration,
  581. useFriction,
  582. useDuration
  583. };
  584. return self;
  585. }
  586. function ScrollBounds(limit, location, target, scrollBody, percentOfView) {
  587. const pullBackThreshold = percentOfView.measure(10);
  588. const edgeOffsetTolerance = percentOfView.measure(50);
  589. const frictionLimit = Limit(0.1, 0.99);
  590. let disabled = false;
  591. function shouldConstrain() {
  592. if (disabled) return false;
  593. if (!limit.reachedAny(target.get())) return false;
  594. if (!limit.reachedAny(location.get())) return false;
  595. return true;
  596. }
  597. function constrain(pointerDown) {
  598. if (!shouldConstrain()) return;
  599. const edge = limit.reachedMin(location.get()) ? 'min' : 'max';
  600. const diffToEdge = mathAbs(limit[edge] - location.get());
  601. const diffToTarget = target.get() - location.get();
  602. const friction = frictionLimit.constrain(diffToEdge / edgeOffsetTolerance);
  603. target.subtract(diffToTarget * friction);
  604. if (!pointerDown && mathAbs(diffToTarget) < pullBackThreshold) {
  605. target.set(limit.constrain(target.get()));
  606. scrollBody.useDuration(25).useBaseFriction();
  607. }
  608. }
  609. function toggleActive(active) {
  610. disabled = !active;
  611. }
  612. const self = {
  613. shouldConstrain,
  614. constrain,
  615. toggleActive
  616. };
  617. return self;
  618. }
  619. function ScrollContain(viewSize, contentSize, snapsAligned, containScroll, pixelTolerance) {
  620. const scrollBounds = Limit(-contentSize + viewSize, 0);
  621. const snapsBounded = measureBounded();
  622. const scrollContainLimit = findScrollContainLimit();
  623. const snapsContained = measureContained();
  624. function usePixelTolerance(bound, snap) {
  625. return deltaAbs(bound, snap) <= 1;
  626. }
  627. function findScrollContainLimit() {
  628. const startSnap = snapsBounded[0];
  629. const endSnap = arrayLast(snapsBounded);
  630. const min = snapsBounded.lastIndexOf(startSnap);
  631. const max = snapsBounded.indexOf(endSnap) + 1;
  632. return Limit(min, max);
  633. }
  634. function measureBounded() {
  635. return snapsAligned.map((snapAligned, index) => {
  636. const {
  637. min,
  638. max
  639. } = scrollBounds;
  640. const snap = scrollBounds.constrain(snapAligned);
  641. const isFirst = !index;
  642. const isLast = arrayIsLastIndex(snapsAligned, index);
  643. if (isFirst) return max;
  644. if (isLast) return min;
  645. if (usePixelTolerance(min, snap)) return min;
  646. if (usePixelTolerance(max, snap)) return max;
  647. return snap;
  648. }).map(scrollBound => parseFloat(scrollBound.toFixed(3)));
  649. }
  650. function measureContained() {
  651. if (contentSize <= viewSize + pixelTolerance) return [scrollBounds.max];
  652. if (containScroll === 'keepSnaps') return snapsBounded;
  653. const {
  654. min,
  655. max
  656. } = scrollContainLimit;
  657. return snapsBounded.slice(min, max);
  658. }
  659. const self = {
  660. snapsContained,
  661. scrollContainLimit
  662. };
  663. return self;
  664. }
  665. function ScrollLimit(contentSize, scrollSnaps, loop) {
  666. const max = scrollSnaps[0];
  667. const min = loop ? max - contentSize : arrayLast(scrollSnaps);
  668. const limit = Limit(min, max);
  669. const self = {
  670. limit
  671. };
  672. return self;
  673. }
  674. function ScrollLooper(contentSize, limit, location, vectors) {
  675. const jointSafety = 0.1;
  676. const min = limit.min + jointSafety;
  677. const max = limit.max + jointSafety;
  678. const {
  679. reachedMin,
  680. reachedMax
  681. } = Limit(min, max);
  682. function shouldLoop(direction) {
  683. if (direction === 1) return reachedMax(location.get());
  684. if (direction === -1) return reachedMin(location.get());
  685. return false;
  686. }
  687. function loop(direction) {
  688. if (!shouldLoop(direction)) return;
  689. const loopDistance = contentSize * (direction * -1);
  690. vectors.forEach(v => v.add(loopDistance));
  691. }
  692. const self = {
  693. loop
  694. };
  695. return self;
  696. }
  697. function ScrollProgress(limit) {
  698. const {
  699. max,
  700. length
  701. } = limit;
  702. function get(n) {
  703. const currentLocation = n - max;
  704. return length ? currentLocation / -length : 0;
  705. }
  706. const self = {
  707. get
  708. };
  709. return self;
  710. }
  711. function ScrollSnaps(axis, alignment, containerRect, slideRects, slidesToScroll) {
  712. const {
  713. startEdge,
  714. endEdge
  715. } = axis;
  716. const {
  717. groupSlides
  718. } = slidesToScroll;
  719. const alignments = measureSizes().map(alignment.measure);
  720. const snaps = measureUnaligned();
  721. const snapsAligned = measureAligned();
  722. function measureSizes() {
  723. return groupSlides(slideRects).map(rects => arrayLast(rects)[endEdge] - rects[0][startEdge]).map(mathAbs);
  724. }
  725. function measureUnaligned() {
  726. return slideRects.map(rect => containerRect[startEdge] - rect[startEdge]).map(snap => -mathAbs(snap));
  727. }
  728. function measureAligned() {
  729. return groupSlides(snaps).map(g => g[0]).map((snap, index) => snap + alignments[index]);
  730. }
  731. const self = {
  732. snaps,
  733. snapsAligned
  734. };
  735. return self;
  736. }
  737. function SlideRegistry(containSnaps, containScroll, scrollSnaps, scrollContainLimit, slidesToScroll, slideIndexes) {
  738. const {
  739. groupSlides
  740. } = slidesToScroll;
  741. const {
  742. min,
  743. max
  744. } = scrollContainLimit;
  745. const slideRegistry = createSlideRegistry();
  746. function createSlideRegistry() {
  747. const groupedSlideIndexes = groupSlides(slideIndexes);
  748. const doNotContain = !containSnaps || containScroll === 'keepSnaps';
  749. if (scrollSnaps.length === 1) return [slideIndexes];
  750. if (doNotContain) return groupedSlideIndexes;
  751. return groupedSlideIndexes.slice(min, max).map((group, index, groups) => {
  752. const isFirst = !index;
  753. const isLast = arrayIsLastIndex(groups, index);
  754. if (isFirst) {
  755. const range = arrayLast(groups[0]) + 1;
  756. return arrayFromNumber(range);
  757. }
  758. if (isLast) {
  759. const range = arrayLastIndex(slideIndexes) - arrayLast(groups)[0] + 1;
  760. return arrayFromNumber(range, arrayLast(groups)[0]);
  761. }
  762. return group;
  763. });
  764. }
  765. const self = {
  766. slideRegistry
  767. };
  768. return self;
  769. }
  770. function ScrollTarget(loop, scrollSnaps, contentSize, limit, targetVector) {
  771. const {
  772. reachedAny,
  773. removeOffset,
  774. constrain
  775. } = limit;
  776. function minDistance(distances) {
  777. return distances.concat().sort((a, b) => mathAbs(a) - mathAbs(b))[0];
  778. }
  779. function findTargetSnap(target) {
  780. const distance = loop ? removeOffset(target) : constrain(target);
  781. const ascDiffsToSnaps = scrollSnaps.map((snap, index) => ({
  782. diff: shortcut(snap - distance, 0),
  783. index
  784. })).sort((d1, d2) => mathAbs(d1.diff) - mathAbs(d2.diff));
  785. const {
  786. index
  787. } = ascDiffsToSnaps[0];
  788. return {
  789. index,
  790. distance
  791. };
  792. }
  793. function shortcut(target, direction) {
  794. const targets = [target, target + contentSize, target - contentSize];
  795. if (!loop) return target;
  796. if (!direction) return minDistance(targets);
  797. const matchingTargets = targets.filter(t => mathSign(t) === direction);
  798. if (matchingTargets.length) return minDistance(matchingTargets);
  799. return arrayLast(targets) - contentSize;
  800. }
  801. function byIndex(index, direction) {
  802. const diffToSnap = scrollSnaps[index] - targetVector.get();
  803. const distance = shortcut(diffToSnap, direction);
  804. return {
  805. index,
  806. distance
  807. };
  808. }
  809. function byDistance(distance, snap) {
  810. const target = targetVector.get() + distance;
  811. const {
  812. index,
  813. distance: targetSnapDistance
  814. } = findTargetSnap(target);
  815. const reachedBound = !loop && reachedAny(target);
  816. if (!snap || reachedBound) return {
  817. index,
  818. distance
  819. };
  820. const diffToSnap = scrollSnaps[index] - targetSnapDistance;
  821. const snapDistance = distance + shortcut(diffToSnap, 0);
  822. return {
  823. index,
  824. distance: snapDistance
  825. };
  826. }
  827. const self = {
  828. byDistance,
  829. byIndex,
  830. shortcut
  831. };
  832. return self;
  833. }
  834. function ScrollTo(animation, indexCurrent, indexPrevious, scrollBody, scrollTarget, targetVector, eventHandler) {
  835. function scrollTo(target) {
  836. const distanceDiff = target.distance;
  837. const indexDiff = target.index !== indexCurrent.get();
  838. targetVector.add(distanceDiff);
  839. if (distanceDiff) {
  840. if (scrollBody.duration()) {
  841. animation.start();
  842. } else {
  843. animation.update();
  844. animation.render(1);
  845. animation.update();
  846. }
  847. }
  848. if (indexDiff) {
  849. indexPrevious.set(indexCurrent.get());
  850. indexCurrent.set(target.index);
  851. eventHandler.emit('select');
  852. }
  853. }
  854. function distance(n, snap) {
  855. const target = scrollTarget.byDistance(n, snap);
  856. scrollTo(target);
  857. }
  858. function index(n, direction) {
  859. const targetIndex = indexCurrent.clone().set(n);
  860. const target = scrollTarget.byIndex(targetIndex.get(), direction);
  861. scrollTo(target);
  862. }
  863. const self = {
  864. distance,
  865. index
  866. };
  867. return self;
  868. }
  869. function SlideFocus(root, slides, slideRegistry, scrollTo, scrollBody, eventStore, eventHandler, watchFocus) {
  870. const focusListenerOptions = {
  871. passive: true,
  872. capture: true
  873. };
  874. let lastTabPressTime = 0;
  875. function init(emblaApi) {
  876. if (!watchFocus) return;
  877. function defaultCallback(index) {
  878. const nowTime = new Date().getTime();
  879. const diffTime = nowTime - lastTabPressTime;
  880. if (diffTime > 10) return;
  881. eventHandler.emit('slideFocusStart');
  882. root.scrollLeft = 0;
  883. const group = slideRegistry.findIndex(group => group.includes(index));
  884. if (!isNumber(group)) return;
  885. scrollBody.useDuration(0);
  886. scrollTo.index(group, 0);
  887. eventHandler.emit('slideFocus');
  888. }
  889. eventStore.add(document, 'keydown', registerTabPress, false);
  890. slides.forEach((slide, slideIndex) => {
  891. eventStore.add(slide, 'focus', evt => {
  892. if (isBoolean(watchFocus) || watchFocus(emblaApi, evt)) {
  893. defaultCallback(slideIndex);
  894. }
  895. }, focusListenerOptions);
  896. });
  897. }
  898. function registerTabPress(event) {
  899. if (event.code === 'Tab') lastTabPressTime = new Date().getTime();
  900. }
  901. const self = {
  902. init
  903. };
  904. return self;
  905. }
  906. function Vector1D(initialValue) {
  907. let value = initialValue;
  908. function get() {
  909. return value;
  910. }
  911. function set(n) {
  912. value = normalizeInput(n);
  913. }
  914. function add(n) {
  915. value += normalizeInput(n);
  916. }
  917. function subtract(n) {
  918. value -= normalizeInput(n);
  919. }
  920. function normalizeInput(n) {
  921. return isNumber(n) ? n : n.get();
  922. }
  923. const self = {
  924. get,
  925. set,
  926. add,
  927. subtract
  928. };
  929. return self;
  930. }
  931. function Translate(axis, container) {
  932. const translate = axis.scroll === 'x' ? x : y;
  933. const containerStyle = container.style;
  934. let previousTarget = null;
  935. let disabled = false;
  936. function x(n) {
  937. return `translate3d(${n}px,0px,0px)`;
  938. }
  939. function y(n) {
  940. return `translate3d(0px,${n}px,0px)`;
  941. }
  942. function to(target) {
  943. if (disabled) return;
  944. const newTarget = roundToTwoDecimals(axis.direction(target));
  945. if (newTarget === previousTarget) return;
  946. containerStyle.transform = translate(newTarget);
  947. previousTarget = newTarget;
  948. }
  949. function toggleActive(active) {
  950. disabled = !active;
  951. }
  952. function clear() {
  953. if (disabled) return;
  954. containerStyle.transform = '';
  955. if (!container.getAttribute('style')) container.removeAttribute('style');
  956. }
  957. const self = {
  958. clear,
  959. to,
  960. toggleActive
  961. };
  962. return self;
  963. }
  964. function SlideLooper(axis, viewSize, contentSize, slideSizes, slideSizesWithGaps, snaps, scrollSnaps, location, slides) {
  965. const roundingSafety = 0.5;
  966. const ascItems = arrayKeys(slideSizesWithGaps);
  967. const descItems = arrayKeys(slideSizesWithGaps).reverse();
  968. const loopPoints = startPoints().concat(endPoints());
  969. function removeSlideSizes(indexes, from) {
  970. return indexes.reduce((a, i) => {
  971. return a - slideSizesWithGaps[i];
  972. }, from);
  973. }
  974. function slidesInGap(indexes, gap) {
  975. return indexes.reduce((a, i) => {
  976. const remainingGap = removeSlideSizes(a, gap);
  977. return remainingGap > 0 ? a.concat([i]) : a;
  978. }, []);
  979. }
  980. function findSlideBounds(offset) {
  981. return snaps.map((snap, index) => ({
  982. start: snap - slideSizes[index] + roundingSafety + offset,
  983. end: snap + viewSize - roundingSafety + offset
  984. }));
  985. }
  986. function findLoopPoints(indexes, offset, isEndEdge) {
  987. const slideBounds = findSlideBounds(offset);
  988. return indexes.map(index => {
  989. const initial = isEndEdge ? 0 : -contentSize;
  990. const altered = isEndEdge ? contentSize : 0;
  991. const boundEdge = isEndEdge ? 'end' : 'start';
  992. const loopPoint = slideBounds[index][boundEdge];
  993. return {
  994. index,
  995. loopPoint,
  996. slideLocation: Vector1D(-1),
  997. translate: Translate(axis, slides[index]),
  998. target: () => location.get() > loopPoint ? initial : altered
  999. };
  1000. });
  1001. }
  1002. function startPoints() {
  1003. const gap = scrollSnaps[0];
  1004. const indexes = slidesInGap(descItems, gap);
  1005. return findLoopPoints(indexes, contentSize, false);
  1006. }
  1007. function endPoints() {
  1008. const gap = viewSize - scrollSnaps[0] - 1;
  1009. const indexes = slidesInGap(ascItems, gap);
  1010. return findLoopPoints(indexes, -contentSize, true);
  1011. }
  1012. function canLoop() {
  1013. return loopPoints.every(({
  1014. index
  1015. }) => {
  1016. const otherIndexes = ascItems.filter(i => i !== index);
  1017. return removeSlideSizes(otherIndexes, viewSize) <= 0.1;
  1018. });
  1019. }
  1020. function loop() {
  1021. loopPoints.forEach(loopPoint => {
  1022. const {
  1023. target,
  1024. translate,
  1025. slideLocation
  1026. } = loopPoint;
  1027. const shiftLocation = target();
  1028. if (shiftLocation === slideLocation.get()) return;
  1029. translate.to(shiftLocation);
  1030. slideLocation.set(shiftLocation);
  1031. });
  1032. }
  1033. function clear() {
  1034. loopPoints.forEach(loopPoint => loopPoint.translate.clear());
  1035. }
  1036. const self = {
  1037. canLoop,
  1038. clear,
  1039. loop,
  1040. loopPoints
  1041. };
  1042. return self;
  1043. }
  1044. function SlidesHandler(container, eventHandler, watchSlides) {
  1045. let mutationObserver;
  1046. let destroyed = false;
  1047. function init(emblaApi) {
  1048. if (!watchSlides) return;
  1049. function defaultCallback(mutations) {
  1050. for (const mutation of mutations) {
  1051. if (mutation.type === 'childList') {
  1052. emblaApi.reInit();
  1053. eventHandler.emit('slidesChanged');
  1054. break;
  1055. }
  1056. }
  1057. }
  1058. mutationObserver = new MutationObserver(mutations => {
  1059. if (destroyed) return;
  1060. if (isBoolean(watchSlides) || watchSlides(emblaApi, mutations)) {
  1061. defaultCallback(mutations);
  1062. }
  1063. });
  1064. mutationObserver.observe(container, {
  1065. childList: true
  1066. });
  1067. }
  1068. function destroy() {
  1069. if (mutationObserver) mutationObserver.disconnect();
  1070. destroyed = true;
  1071. }
  1072. const self = {
  1073. init,
  1074. destroy
  1075. };
  1076. return self;
  1077. }
  1078. function SlidesInView(container, slides, eventHandler, threshold) {
  1079. const intersectionEntryMap = {};
  1080. let inViewCache = null;
  1081. let notInViewCache = null;
  1082. let intersectionObserver;
  1083. let destroyed = false;
  1084. function init() {
  1085. intersectionObserver = new IntersectionObserver(entries => {
  1086. if (destroyed) return;
  1087. entries.forEach(entry => {
  1088. const index = slides.indexOf(entry.target);
  1089. intersectionEntryMap[index] = entry;
  1090. });
  1091. inViewCache = null;
  1092. notInViewCache = null;
  1093. eventHandler.emit('slidesInView');
  1094. }, {
  1095. root: container.parentElement,
  1096. threshold
  1097. });
  1098. slides.forEach(slide => intersectionObserver.observe(slide));
  1099. }
  1100. function destroy() {
  1101. if (intersectionObserver) intersectionObserver.disconnect();
  1102. destroyed = true;
  1103. }
  1104. function createInViewList(inView) {
  1105. return objectKeys(intersectionEntryMap).reduce((list, slideIndex) => {
  1106. const index = parseInt(slideIndex);
  1107. const {
  1108. isIntersecting
  1109. } = intersectionEntryMap[index];
  1110. const inViewMatch = inView && isIntersecting;
  1111. const notInViewMatch = !inView && !isIntersecting;
  1112. if (inViewMatch || notInViewMatch) list.push(index);
  1113. return list;
  1114. }, []);
  1115. }
  1116. function get(inView = true) {
  1117. if (inView && inViewCache) return inViewCache;
  1118. if (!inView && notInViewCache) return notInViewCache;
  1119. const slideIndexes = createInViewList(inView);
  1120. if (inView) inViewCache = slideIndexes;
  1121. if (!inView) notInViewCache = slideIndexes;
  1122. return slideIndexes;
  1123. }
  1124. const self = {
  1125. init,
  1126. destroy,
  1127. get
  1128. };
  1129. return self;
  1130. }
  1131. function SlideSizes(axis, containerRect, slideRects, slides, readEdgeGap, ownerWindow) {
  1132. const {
  1133. measureSize,
  1134. startEdge,
  1135. endEdge
  1136. } = axis;
  1137. const withEdgeGap = slideRects[0] && readEdgeGap;
  1138. const startGap = measureStartGap();
  1139. const endGap = measureEndGap();
  1140. const slideSizes = slideRects.map(measureSize);
  1141. const slideSizesWithGaps = measureWithGaps();
  1142. function measureStartGap() {
  1143. if (!withEdgeGap) return 0;
  1144. const slideRect = slideRects[0];
  1145. return mathAbs(containerRect[startEdge] - slideRect[startEdge]);
  1146. }
  1147. function measureEndGap() {
  1148. if (!withEdgeGap) return 0;
  1149. const style = ownerWindow.getComputedStyle(arrayLast(slides));
  1150. return parseFloat(style.getPropertyValue(`margin-${endEdge}`));
  1151. }
  1152. function measureWithGaps() {
  1153. return slideRects.map((rect, index, rects) => {
  1154. const isFirst = !index;
  1155. const isLast = arrayIsLastIndex(rects, index);
  1156. if (isFirst) return slideSizes[index] + startGap;
  1157. if (isLast) return slideSizes[index] + endGap;
  1158. return rects[index + 1][startEdge] - rect[startEdge];
  1159. }).map(mathAbs);
  1160. }
  1161. const self = {
  1162. slideSizes,
  1163. slideSizesWithGaps,
  1164. startGap,
  1165. endGap
  1166. };
  1167. return self;
  1168. }
  1169. function SlidesToScroll(axis, viewSize, slidesToScroll, loop, containerRect, slideRects, startGap, endGap, pixelTolerance) {
  1170. const {
  1171. startEdge,
  1172. endEdge,
  1173. direction
  1174. } = axis;
  1175. const groupByNumber = isNumber(slidesToScroll);
  1176. function byNumber(array, groupSize) {
  1177. return arrayKeys(array).filter(i => i % groupSize === 0).map(i => array.slice(i, i + groupSize));
  1178. }
  1179. function bySize(array) {
  1180. if (!array.length) return [];
  1181. return arrayKeys(array).reduce((groups, rectB, index) => {
  1182. const rectA = arrayLast(groups) || 0;
  1183. const isFirst = rectA === 0;
  1184. const isLast = rectB === arrayLastIndex(array);
  1185. const edgeA = containerRect[startEdge] - slideRects[rectA][startEdge];
  1186. const edgeB = containerRect[startEdge] - slideRects[rectB][endEdge];
  1187. const gapA = !loop && isFirst ? direction(startGap) : 0;
  1188. const gapB = !loop && isLast ? direction(endGap) : 0;
  1189. const chunkSize = mathAbs(edgeB - gapB - (edgeA + gapA));
  1190. if (index && chunkSize > viewSize + pixelTolerance) groups.push(rectB);
  1191. if (isLast) groups.push(array.length);
  1192. return groups;
  1193. }, []).map((currentSize, index, groups) => {
  1194. const previousSize = Math.max(groups[index - 1] || 0);
  1195. return array.slice(previousSize, currentSize);
  1196. });
  1197. }
  1198. function groupSlides(array) {
  1199. return groupByNumber ? byNumber(array, slidesToScroll) : bySize(array);
  1200. }
  1201. const self = {
  1202. groupSlides
  1203. };
  1204. return self;
  1205. }
  1206. function Engine(root, container, slides, ownerDocument, ownerWindow, options, eventHandler) {
  1207. // Options
  1208. const {
  1209. align,
  1210. axis: scrollAxis,
  1211. direction,
  1212. startIndex,
  1213. loop,
  1214. duration,
  1215. dragFree,
  1216. dragThreshold,
  1217. inViewThreshold,
  1218. slidesToScroll: groupSlides,
  1219. skipSnaps,
  1220. containScroll,
  1221. watchResize,
  1222. watchSlides,
  1223. watchDrag,
  1224. watchFocus
  1225. } = options;
  1226. // Measurements
  1227. const pixelTolerance = 2;
  1228. const nodeRects = NodeRects();
  1229. const containerRect = nodeRects.measure(container);
  1230. const slideRects = slides.map(nodeRects.measure);
  1231. const axis = Axis(scrollAxis, direction);
  1232. const viewSize = axis.measureSize(containerRect);
  1233. const percentOfView = PercentOfView(viewSize);
  1234. const alignment = Alignment(align, viewSize);
  1235. const containSnaps = !loop && !!containScroll;
  1236. const readEdgeGap = loop || !!containScroll;
  1237. const {
  1238. slideSizes,
  1239. slideSizesWithGaps,
  1240. startGap,
  1241. endGap
  1242. } = SlideSizes(axis, containerRect, slideRects, slides, readEdgeGap, ownerWindow);
  1243. const slidesToScroll = SlidesToScroll(axis, viewSize, groupSlides, loop, containerRect, slideRects, startGap, endGap, pixelTolerance);
  1244. const {
  1245. snaps,
  1246. snapsAligned
  1247. } = ScrollSnaps(axis, alignment, containerRect, slideRects, slidesToScroll);
  1248. const contentSize = -arrayLast(snaps) + arrayLast(slideSizesWithGaps);
  1249. const {
  1250. snapsContained,
  1251. scrollContainLimit
  1252. } = ScrollContain(viewSize, contentSize, snapsAligned, containScroll, pixelTolerance);
  1253. const scrollSnaps = containSnaps ? snapsContained : snapsAligned;
  1254. const {
  1255. limit
  1256. } = ScrollLimit(contentSize, scrollSnaps, loop);
  1257. // Indexes
  1258. const index = Counter(arrayLastIndex(scrollSnaps), startIndex, loop);
  1259. const indexPrevious = index.clone();
  1260. const slideIndexes = arrayKeys(slides);
  1261. // Animation
  1262. const update = ({
  1263. dragHandler,
  1264. scrollBody,
  1265. scrollBounds,
  1266. options: {
  1267. loop
  1268. }
  1269. }) => {
  1270. if (!loop) scrollBounds.constrain(dragHandler.pointerDown());
  1271. scrollBody.seek();
  1272. };
  1273. const render = ({
  1274. scrollBody,
  1275. translate,
  1276. location,
  1277. offsetLocation,
  1278. previousLocation,
  1279. scrollLooper,
  1280. slideLooper,
  1281. dragHandler,
  1282. animation,
  1283. eventHandler,
  1284. scrollBounds,
  1285. options: {
  1286. loop
  1287. }
  1288. }, alpha) => {
  1289. const shouldSettle = scrollBody.settled();
  1290. const withinBounds = !scrollBounds.shouldConstrain();
  1291. const hasSettled = loop ? shouldSettle : shouldSettle && withinBounds;
  1292. const hasSettledAndIdle = hasSettled && !dragHandler.pointerDown();
  1293. if (hasSettledAndIdle) animation.stop();
  1294. const interpolatedLocation = location.get() * alpha + previousLocation.get() * (1 - alpha);
  1295. offsetLocation.set(interpolatedLocation);
  1296. if (loop) {
  1297. scrollLooper.loop(scrollBody.direction());
  1298. slideLooper.loop();
  1299. }
  1300. translate.to(offsetLocation.get());
  1301. if (hasSettledAndIdle) eventHandler.emit('settle');
  1302. if (!hasSettled) eventHandler.emit('scroll');
  1303. };
  1304. const animation = Animations(ownerDocument, ownerWindow, () => update(engine), alpha => render(engine, alpha));
  1305. // Shared
  1306. const friction = 0.68;
  1307. const startLocation = scrollSnaps[index.get()];
  1308. const location = Vector1D(startLocation);
  1309. const previousLocation = Vector1D(startLocation);
  1310. const offsetLocation = Vector1D(startLocation);
  1311. const target = Vector1D(startLocation);
  1312. const scrollBody = ScrollBody(location, offsetLocation, previousLocation, target, duration, friction);
  1313. const scrollTarget = ScrollTarget(loop, scrollSnaps, contentSize, limit, target);
  1314. const scrollTo = ScrollTo(animation, index, indexPrevious, scrollBody, scrollTarget, target, eventHandler);
  1315. const scrollProgress = ScrollProgress(limit);
  1316. const eventStore = EventStore();
  1317. const slidesInView = SlidesInView(container, slides, eventHandler, inViewThreshold);
  1318. const {
  1319. slideRegistry
  1320. } = SlideRegistry(containSnaps, containScroll, scrollSnaps, scrollContainLimit, slidesToScroll, slideIndexes);
  1321. const slideFocus = SlideFocus(root, slides, slideRegistry, scrollTo, scrollBody, eventStore, eventHandler, watchFocus);
  1322. // Engine
  1323. const engine = {
  1324. ownerDocument,
  1325. ownerWindow,
  1326. eventHandler,
  1327. containerRect,
  1328. slideRects,
  1329. animation,
  1330. axis,
  1331. dragHandler: DragHandler(axis, root, ownerDocument, ownerWindow, target, DragTracker(axis, ownerWindow), location, animation, scrollTo, scrollBody, scrollTarget, index, eventHandler, percentOfView, dragFree, dragThreshold, skipSnaps, friction, watchDrag),
  1332. eventStore,
  1333. percentOfView,
  1334. index,
  1335. indexPrevious,
  1336. limit,
  1337. location,
  1338. offsetLocation,
  1339. previousLocation,
  1340. options,
  1341. resizeHandler: ResizeHandler(container, eventHandler, ownerWindow, slides, axis, watchResize, nodeRects),
  1342. scrollBody,
  1343. scrollBounds: ScrollBounds(limit, offsetLocation, target, scrollBody, percentOfView),
  1344. scrollLooper: ScrollLooper(contentSize, limit, offsetLocation, [location, offsetLocation, previousLocation, target]),
  1345. scrollProgress,
  1346. scrollSnapList: scrollSnaps.map(scrollProgress.get),
  1347. scrollSnaps,
  1348. scrollTarget,
  1349. scrollTo,
  1350. slideLooper: SlideLooper(axis, viewSize, contentSize, slideSizes, slideSizesWithGaps, snaps, scrollSnaps, offsetLocation, slides),
  1351. slideFocus,
  1352. slidesHandler: SlidesHandler(container, eventHandler, watchSlides),
  1353. slidesInView,
  1354. slideIndexes,
  1355. slideRegistry,
  1356. slidesToScroll,
  1357. target,
  1358. translate: Translate(axis, container)
  1359. };
  1360. return engine;
  1361. }
  1362. function EventHandler() {
  1363. let listeners = {};
  1364. let api;
  1365. function init(emblaApi) {
  1366. api = emblaApi;
  1367. }
  1368. function getListeners(evt) {
  1369. return listeners[evt] || [];
  1370. }
  1371. function emit(evt) {
  1372. getListeners(evt).forEach(e => e(api, evt));
  1373. return self;
  1374. }
  1375. function on(evt, cb) {
  1376. listeners[evt] = getListeners(evt).concat([cb]);
  1377. return self;
  1378. }
  1379. function off(evt, cb) {
  1380. listeners[evt] = getListeners(evt).filter(e => e !== cb);
  1381. return self;
  1382. }
  1383. function clear() {
  1384. listeners = {};
  1385. }
  1386. const self = {
  1387. init,
  1388. emit,
  1389. off,
  1390. on,
  1391. clear
  1392. };
  1393. return self;
  1394. }
  1395. const defaultOptions = {
  1396. align: 'center',
  1397. axis: 'x',
  1398. container: null,
  1399. slides: null,
  1400. containScroll: 'trimSnaps',
  1401. direction: 'ltr',
  1402. slidesToScroll: 1,
  1403. inViewThreshold: 0,
  1404. breakpoints: {},
  1405. dragFree: false,
  1406. dragThreshold: 10,
  1407. loop: false,
  1408. skipSnaps: false,
  1409. duration: 25,
  1410. startIndex: 0,
  1411. active: true,
  1412. watchDrag: true,
  1413. watchResize: true,
  1414. watchSlides: true,
  1415. watchFocus: true
  1416. };
  1417. function OptionsHandler(ownerWindow) {
  1418. function mergeOptions(optionsA, optionsB) {
  1419. return objectsMergeDeep(optionsA, optionsB || {});
  1420. }
  1421. function optionsAtMedia(options) {
  1422. const optionsAtMedia = options.breakpoints || {};
  1423. const matchedMediaOptions = objectKeys(optionsAtMedia).filter(media => ownerWindow.matchMedia(media).matches).map(media => optionsAtMedia[media]).reduce((a, mediaOption) => mergeOptions(a, mediaOption), {});
  1424. return mergeOptions(options, matchedMediaOptions);
  1425. }
  1426. function optionsMediaQueries(optionsList) {
  1427. return optionsList.map(options => objectKeys(options.breakpoints || {})).reduce((acc, mediaQueries) => acc.concat(mediaQueries), []).map(ownerWindow.matchMedia);
  1428. }
  1429. const self = {
  1430. mergeOptions,
  1431. optionsAtMedia,
  1432. optionsMediaQueries
  1433. };
  1434. return self;
  1435. }
  1436. function PluginsHandler(optionsHandler) {
  1437. let activePlugins = [];
  1438. function init(emblaApi, plugins) {
  1439. activePlugins = plugins.filter(({
  1440. options
  1441. }) => optionsHandler.optionsAtMedia(options).active !== false);
  1442. activePlugins.forEach(plugin => plugin.init(emblaApi, optionsHandler));
  1443. return plugins.reduce((map, plugin) => Object.assign(map, {
  1444. [plugin.name]: plugin
  1445. }), {});
  1446. }
  1447. function destroy() {
  1448. activePlugins = activePlugins.filter(plugin => plugin.destroy());
  1449. }
  1450. const self = {
  1451. init,
  1452. destroy
  1453. };
  1454. return self;
  1455. }
  1456. function EmblaCarousel(root, userOptions, userPlugins) {
  1457. const ownerDocument = root.ownerDocument;
  1458. const ownerWindow = ownerDocument.defaultView;
  1459. const optionsHandler = OptionsHandler(ownerWindow);
  1460. const pluginsHandler = PluginsHandler(optionsHandler);
  1461. const mediaHandlers = EventStore();
  1462. const eventHandler = EventHandler();
  1463. const {
  1464. mergeOptions,
  1465. optionsAtMedia,
  1466. optionsMediaQueries
  1467. } = optionsHandler;
  1468. const {
  1469. on,
  1470. off,
  1471. emit
  1472. } = eventHandler;
  1473. const reInit = reActivate;
  1474. let destroyed = false;
  1475. let engine;
  1476. let optionsBase = mergeOptions(defaultOptions, EmblaCarousel.globalOptions);
  1477. let options = mergeOptions(optionsBase);
  1478. let pluginList = [];
  1479. let pluginApis;
  1480. let container;
  1481. let slides;
  1482. function storeElements() {
  1483. const {
  1484. container: userContainer,
  1485. slides: userSlides
  1486. } = options;
  1487. const customContainer = isString(userContainer) ? root.querySelector(userContainer) : userContainer;
  1488. container = customContainer || root.children[0];
  1489. const customSlides = isString(userSlides) ? container.querySelectorAll(userSlides) : userSlides;
  1490. slides = [].slice.call(customSlides || container.children);
  1491. }
  1492. function createEngine(options) {
  1493. const engine = Engine(root, container, slides, ownerDocument, ownerWindow, options, eventHandler);
  1494. if (options.loop && !engine.slideLooper.canLoop()) {
  1495. const optionsWithoutLoop = Object.assign({}, options, {
  1496. loop: false
  1497. });
  1498. return createEngine(optionsWithoutLoop);
  1499. }
  1500. return engine;
  1501. }
  1502. function activate(withOptions, withPlugins) {
  1503. if (destroyed) return;
  1504. optionsBase = mergeOptions(optionsBase, withOptions);
  1505. options = optionsAtMedia(optionsBase);
  1506. pluginList = withPlugins || pluginList;
  1507. storeElements();
  1508. engine = createEngine(options);
  1509. optionsMediaQueries([optionsBase, ...pluginList.map(({
  1510. options
  1511. }) => options)]).forEach(query => mediaHandlers.add(query, 'change', reActivate));
  1512. if (!options.active) return;
  1513. engine.translate.to(engine.location.get());
  1514. engine.animation.init();
  1515. engine.slidesInView.init();
  1516. engine.slideFocus.init(self);
  1517. engine.eventHandler.init(self);
  1518. engine.resizeHandler.init(self);
  1519. engine.slidesHandler.init(self);
  1520. if (engine.options.loop) engine.slideLooper.loop();
  1521. if (container.offsetParent && slides.length) engine.dragHandler.init(self);
  1522. pluginApis = pluginsHandler.init(self, pluginList);
  1523. }
  1524. function reActivate(withOptions, withPlugins) {
  1525. const startIndex = selectedScrollSnap();
  1526. deActivate();
  1527. activate(mergeOptions({
  1528. startIndex
  1529. }, withOptions), withPlugins);
  1530. eventHandler.emit('reInit');
  1531. }
  1532. function deActivate() {
  1533. engine.dragHandler.destroy();
  1534. engine.eventStore.clear();
  1535. engine.translate.clear();
  1536. engine.slideLooper.clear();
  1537. engine.resizeHandler.destroy();
  1538. engine.slidesHandler.destroy();
  1539. engine.slidesInView.destroy();
  1540. engine.animation.destroy();
  1541. pluginsHandler.destroy();
  1542. mediaHandlers.clear();
  1543. }
  1544. function destroy() {
  1545. if (destroyed) return;
  1546. destroyed = true;
  1547. mediaHandlers.clear();
  1548. deActivate();
  1549. eventHandler.emit('destroy');
  1550. eventHandler.clear();
  1551. }
  1552. function scrollTo(index, jump, direction) {
  1553. if (!options.active || destroyed) return;
  1554. engine.scrollBody.useBaseFriction().useDuration(jump === true ? 0 : options.duration);
  1555. engine.scrollTo.index(index, direction || 0);
  1556. }
  1557. function scrollNext(jump) {
  1558. const next = engine.index.add(1).get();
  1559. scrollTo(next, jump, -1);
  1560. }
  1561. function scrollPrev(jump) {
  1562. const prev = engine.index.add(-1).get();
  1563. scrollTo(prev, jump, 1);
  1564. }
  1565. function canScrollNext() {
  1566. const next = engine.index.add(1).get();
  1567. return next !== selectedScrollSnap();
  1568. }
  1569. function canScrollPrev() {
  1570. const prev = engine.index.add(-1).get();
  1571. return prev !== selectedScrollSnap();
  1572. }
  1573. function scrollSnapList() {
  1574. return engine.scrollSnapList;
  1575. }
  1576. function scrollProgress() {
  1577. return engine.scrollProgress.get(engine.offsetLocation.get());
  1578. }
  1579. function selectedScrollSnap() {
  1580. return engine.index.get();
  1581. }
  1582. function previousScrollSnap() {
  1583. return engine.indexPrevious.get();
  1584. }
  1585. function slidesInView() {
  1586. return engine.slidesInView.get();
  1587. }
  1588. function slidesNotInView() {
  1589. return engine.slidesInView.get(false);
  1590. }
  1591. function plugins() {
  1592. return pluginApis;
  1593. }
  1594. function internalEngine() {
  1595. return engine;
  1596. }
  1597. function rootNode() {
  1598. return root;
  1599. }
  1600. function containerNode() {
  1601. return container;
  1602. }
  1603. function slideNodes() {
  1604. return slides;
  1605. }
  1606. const self = {
  1607. canScrollNext,
  1608. canScrollPrev,
  1609. containerNode,
  1610. internalEngine,
  1611. destroy,
  1612. off,
  1613. on,
  1614. emit,
  1615. plugins,
  1616. previousScrollSnap,
  1617. reInit,
  1618. rootNode,
  1619. scrollNext,
  1620. scrollPrev,
  1621. scrollProgress,
  1622. scrollSnapList,
  1623. scrollTo,
  1624. selectedScrollSnap,
  1625. slideNodes,
  1626. slidesInView,
  1627. slidesNotInView
  1628. };
  1629. activate(userOptions, userPlugins);
  1630. setTimeout(() => eventHandler.emit('init'), 0);
  1631. return self;
  1632. }
  1633. EmblaCarousel.globalOptions = undefined;
  1634. module.exports = EmblaCarousel;
  1635. //# sourceMappingURL=embla-carousel.cjs.js.map