embla-carousel-vue
Advanced tools
Comparing version 7.1.0 to 8.0.0-rc01
'use strict'; | ||
var vue = require('vue'); | ||
var emblaCarouselReactiveUtils = require('embla-carousel-reactive-utils'); | ||
var EmblaCarousel = require('embla-carousel'); | ||
@@ -10,35 +11,7 @@ | ||
function canUseDOM() { | ||
return !!(typeof window !== 'undefined' && window.document && window.document.createElement); | ||
} | ||
function sortAndMapPluginToOptions(plugins) { | ||
return plugins.concat().sort(function (a, b) { | ||
return a.name > b.name ? 1 : -1; | ||
}).map(function (plugin) { | ||
return plugin.options; | ||
}); | ||
} | ||
function arePluginsEqual(pluginsA, pluginsB) { | ||
if (pluginsA.length !== pluginsB.length) return false; | ||
var areEqual = EmblaCarousel__default["default"].optionsHandler().areEqual; | ||
var optionsA = sortAndMapPluginToOptions(pluginsA); | ||
var optionsB = sortAndMapPluginToOptions(pluginsB); | ||
return optionsA.every(function (optionA, index) { | ||
var optionB = optionsB[index]; | ||
return areEqual(optionA, optionB); | ||
}); | ||
} | ||
function emblaCarouselVue(options, plugins) { | ||
if (options === void 0) { | ||
options = {}; | ||
} | ||
if (plugins === void 0) { | ||
plugins = []; | ||
} | ||
var areOptionsEqual = EmblaCarousel__default["default"].optionsHandler().areEqual; | ||
var storedOptions = vue.ref(vue.isRef(options) ? options.value : options); | ||
var storedPlugins = vue.ref(vue.isRef(plugins) ? plugins.value : plugins); | ||
var emblaNode = vue.ref(); | ||
var embla = vue.ref(); | ||
function emblaCarouselVue(options = {}, plugins = []) { | ||
const storedOptions = vue.ref(vue.isRef(options) ? options.value : options); | ||
const storedPlugins = vue.ref(vue.isRef(plugins) ? plugins.value : plugins); | ||
const emblaNode = vue.ref(); | ||
const embla = vue.ref(); | ||
function reInit() { | ||
@@ -48,13 +21,13 @@ if (!embla.value) return; | ||
} | ||
vue.onMounted(function () { | ||
if (!canUseDOM() || !emblaNode.value) return; | ||
vue.onMounted(() => { | ||
if (!emblaCarouselReactiveUtils.canUseDOM() || !emblaNode.value) return; | ||
EmblaCarousel__default["default"].globalOptions = emblaCarouselVue.globalOptions; | ||
embla.value = EmblaCarousel__default["default"](emblaNode.value, storedOptions.value, storedPlugins.value); | ||
}); | ||
vue.onUnmounted(function () { | ||
vue.onUnmounted(() => { | ||
if (embla.value) embla.value.destroy(); | ||
}); | ||
if (vue.isRef(options)) { | ||
vue.watch(options, function (newOptions) { | ||
if (areOptionsEqual(storedOptions.value, newOptions)) return; | ||
vue.watch(options, newOptions => { | ||
if (emblaCarouselReactiveUtils.areOptionsEqual(storedOptions.value, newOptions)) return; | ||
storedOptions.value = newOptions; | ||
@@ -65,4 +38,4 @@ reInit(); | ||
if (vue.isRef(plugins)) { | ||
vue.watch(plugins, function (newPlugins) { | ||
if (arePluginsEqual(storedPlugins.value, newPlugins)) return; | ||
vue.watch(plugins, newPlugins => { | ||
if (emblaCarouselReactiveUtils.arePluginsEqual(storedPlugins.value, newPlugins)) return; | ||
storedPlugins.value = newPlugins; | ||
@@ -69,0 +42,0 @@ reInit(); |
import { ref, isRef, onMounted, onUnmounted, watch } from 'vue'; | ||
import { canUseDOM, areOptionsEqual, arePluginsEqual } from 'embla-carousel-reactive-utils'; | ||
import EmblaCarousel from 'embla-carousel'; | ||
function canUseDOM() { | ||
return !!(typeof window !== 'undefined' && window.document && window.document.createElement); | ||
} | ||
function sortAndMapPluginToOptions(plugins) { | ||
return plugins.concat().sort(function (a, b) { | ||
return a.name > b.name ? 1 : -1; | ||
}).map(function (plugin) { | ||
return plugin.options; | ||
}); | ||
} | ||
function arePluginsEqual(pluginsA, pluginsB) { | ||
if (pluginsA.length !== pluginsB.length) return false; | ||
var areEqual = EmblaCarousel.optionsHandler().areEqual; | ||
var optionsA = sortAndMapPluginToOptions(pluginsA); | ||
var optionsB = sortAndMapPluginToOptions(pluginsB); | ||
return optionsA.every(function (optionA, index) { | ||
var optionB = optionsB[index]; | ||
return areEqual(optionA, optionB); | ||
}); | ||
} | ||
function emblaCarouselVue(options, plugins) { | ||
if (options === void 0) { | ||
options = {}; | ||
} | ||
if (plugins === void 0) { | ||
plugins = []; | ||
} | ||
var areOptionsEqual = EmblaCarousel.optionsHandler().areEqual; | ||
var storedOptions = ref(isRef(options) ? options.value : options); | ||
var storedPlugins = ref(isRef(plugins) ? plugins.value : plugins); | ||
var emblaNode = ref(); | ||
var embla = ref(); | ||
function emblaCarouselVue(options = {}, plugins = []) { | ||
const storedOptions = ref(isRef(options) ? options.value : options); | ||
const storedPlugins = ref(isRef(plugins) ? plugins.value : plugins); | ||
const emblaNode = ref(); | ||
const embla = ref(); | ||
function reInit() { | ||
@@ -41,3 +14,3 @@ if (!embla.value) return; | ||
} | ||
onMounted(function () { | ||
onMounted(() => { | ||
if (!canUseDOM() || !emblaNode.value) return; | ||
@@ -47,7 +20,7 @@ EmblaCarousel.globalOptions = emblaCarouselVue.globalOptions; | ||
}); | ||
onUnmounted(function () { | ||
onUnmounted(() => { | ||
if (embla.value) embla.value.destroy(); | ||
}); | ||
if (isRef(options)) { | ||
watch(options, function (newOptions) { | ||
watch(options, newOptions => { | ||
if (areOptionsEqual(storedOptions.value, newOptions)) return; | ||
@@ -59,3 +32,3 @@ storedOptions.value = newOptions; | ||
if (isRef(plugins)) { | ||
watch(plugins, function (newPlugins) { | ||
watch(plugins, newPlugins => { | ||
if (arePluginsEqual(storedPlugins.value, newPlugins)) return; | ||
@@ -62,0 +35,0 @@ storedPlugins.value = newPlugins; |
@@ -1,1 +0,1505 @@ | ||
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n(require("vue"),require("embla-carousel")):"function"==typeof define&&define.amd?define(["vue","embla-carousel"],n):(e="undefined"!=typeof globalThis?globalThis:e||self).EmblaCarouselVue=n(e.Vue,e.EmblaCarousel)}(this,(function(e,n){"use strict";function u(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var t=u(n);function o(e){return e.concat().sort((function(e,n){return e.name>n.name?1:-1})).map((function(e){return e.options}))}function a(n,u){void 0===n&&(n={}),void 0===u&&(u=[]);var l=t.default.optionsHandler().areEqual,i=e.ref(e.isRef(n)?n.value:n),r=e.ref(e.isRef(u)?u.value:u),f=e.ref(),d=e.ref();function c(){d.value&&d.value.reInit(i.value,r.value)}return e.onMounted((function(){"undefined"!=typeof window&&window.document&&window.document.createElement&&f.value&&(t.default.globalOptions=a.globalOptions,d.value=t.default(f.value,i.value,r.value))})),e.onUnmounted((function(){d.value&&d.value.destroy()})),e.isRef(n)&&e.watch(n,(function(e){l(i.value,e)||(i.value=e,c())})),e.isRef(u)&&e.watch(u,(function(e){(function(e,n){if(e.length!==n.length)return!1;var u=t.default.optionsHandler().areEqual,a=o(e),l=o(n);return a.every((function(e,n){var t=l[n];return u(e,t)}))})(r.value,e)||(r.value=e,c())})),[f,d]}return a.globalOptions=void 0,a})); | ||
(function (global, factory) { | ||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('vue')) : | ||
typeof define === 'function' && define.amd ? define(['vue'], factory) : | ||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.EmblaCarouselVue = factory(global.Vue)); | ||
})(this, (function (vue) { 'use strict'; | ||
function isObject$1(subject) { | ||
return Object.prototype.toString.call(subject) === '[object Object]'; | ||
} | ||
function isRecord(subject) { | ||
return isObject$1(subject) || Array.isArray(subject); | ||
} | ||
function canUseDOM() { | ||
return !!(typeof window !== 'undefined' && window.document && window.document.createElement); | ||
} | ||
function areOptionsEqual(optionsA, optionsB) { | ||
const optionsAKeys = Object.keys(optionsA); | ||
const optionsBKeys = Object.keys(optionsB); | ||
if (optionsAKeys.length !== optionsBKeys.length) return false; | ||
const breakpointsA = JSON.stringify(Object.keys(optionsA.breakpoints || {})); | ||
const breakpointsB = JSON.stringify(Object.keys(optionsB.breakpoints || {})); | ||
if (breakpointsA !== breakpointsB) return false; | ||
return optionsAKeys.every(key => { | ||
const valueA = optionsA[key]; | ||
const valueB = optionsB[key]; | ||
if (typeof valueA === 'function') return `${valueA}` === `${valueB}`; | ||
if (!isRecord(valueA) || !isRecord(valueB)) return valueA === valueB; | ||
return areOptionsEqual(valueA, valueB); | ||
}); | ||
} | ||
function sortAndMapPluginToOptions(plugins) { | ||
return plugins.concat().sort((a, b) => a.name > b.name ? 1 : -1).map(plugin => plugin.options); | ||
} | ||
function arePluginsEqual(pluginsA, pluginsB) { | ||
if (pluginsA.length !== pluginsB.length) return false; | ||
const optionsA = sortAndMapPluginToOptions(pluginsA); | ||
const optionsB = sortAndMapPluginToOptions(pluginsB); | ||
return optionsA.every((optionA, index) => { | ||
const optionB = optionsB[index]; | ||
return areOptionsEqual(optionA, optionB); | ||
}); | ||
} | ||
function isNumber(subject) { | ||
return typeof subject === 'number'; | ||
} | ||
function isString(subject) { | ||
return typeof subject === 'string'; | ||
} | ||
function isBoolean(subject) { | ||
return typeof subject === 'boolean'; | ||
} | ||
function isObject(subject) { | ||
return Object.prototype.toString.call(subject) === '[object Object]'; | ||
} | ||
function isMouseEvent(evt) { | ||
return typeof MouseEvent !== 'undefined' && evt instanceof MouseEvent; | ||
} | ||
function mathAbs(n) { | ||
return Math.abs(n); | ||
} | ||
function mathSign(n) { | ||
return Math.sign(n); | ||
} | ||
function deltaAbs(valueB, valueA) { | ||
return mathAbs(valueB - valueA); | ||
} | ||
function factorAbs(valueB, valueA) { | ||
if (valueB === 0 || valueA === 0) return 0; | ||
if (mathAbs(valueB) <= mathAbs(valueA)) return 0; | ||
const diff = deltaAbs(mathAbs(valueB), mathAbs(valueA)); | ||
return mathAbs(diff / valueB); | ||
} | ||
function roundToDecimals(decimalPoints) { | ||
const pow = Math.pow(10, decimalPoints); | ||
return n => Math.round(n * pow) / pow; | ||
} | ||
function arrayKeys(array) { | ||
return objectKeys(array).map(Number); | ||
} | ||
function arrayLast(array) { | ||
return array[arrayLastIndex(array)]; | ||
} | ||
function arrayLastIndex(array) { | ||
return Math.max(0, array.length - 1); | ||
} | ||
function objectKeys(object) { | ||
return Object.keys(object); | ||
} | ||
function objectsMergeDeep(objectA, objectB) { | ||
return [objectA, objectB].reduce((mergedObjects, currentObject) => { | ||
objectKeys(currentObject).forEach(key => { | ||
const valueA = mergedObjects[key]; | ||
const valueB = currentObject[key]; | ||
const areObjects = isObject(valueA) && isObject(valueB); | ||
mergedObjects[key] = areObjects ? objectsMergeDeep(valueA, valueB) : valueB; | ||
}); | ||
return mergedObjects; | ||
}, {}); | ||
} | ||
function Alignment(align, viewSize) { | ||
const predefined = { | ||
start, | ||
center, | ||
end | ||
}; | ||
function start() { | ||
return 0; | ||
} | ||
function center(n) { | ||
return end(n) / 2; | ||
} | ||
function end(n) { | ||
return viewSize - n; | ||
} | ||
function percent() { | ||
return viewSize * Number(align); | ||
} | ||
function measure(n) { | ||
if (isNumber(align)) return percent(); | ||
return predefined[align](n); | ||
} | ||
const self = { | ||
measure | ||
}; | ||
return self; | ||
} | ||
function EventStore() { | ||
let listeners = []; | ||
function add(node, type, handler, options = { | ||
passive: true | ||
}) { | ||
node.addEventListener(type, handler, options); | ||
listeners.push(() => node.removeEventListener(type, handler, options)); | ||
return self; | ||
} | ||
function clear() { | ||
listeners = listeners.filter(remove => remove()); | ||
} | ||
const self = { | ||
add, | ||
clear | ||
}; | ||
return self; | ||
} | ||
function Animation(callback) { | ||
const documentVisibleHandler = EventStore(); | ||
const timeStep = 1000 / 60; | ||
let lastTimeStamp = null; | ||
let delta = 0; | ||
let animationFrame = 0; | ||
function init() { | ||
documentVisibleHandler.add(document, 'visibilitychange', () => { | ||
if (document.hidden) lastTimeStamp = null; | ||
}); | ||
} | ||
function destroy() { | ||
stop(); | ||
documentVisibleHandler.clear(); | ||
} | ||
function ifAnimating(active, cb) { | ||
return () => { | ||
if (active === !!animationFrame) cb(); | ||
}; | ||
} | ||
function animate(timeStamp) { | ||
if (!lastTimeStamp) { | ||
lastTimeStamp = timeStamp; | ||
return start(); | ||
} | ||
delta += timeStamp - lastTimeStamp; | ||
lastTimeStamp = timeStamp; | ||
while (delta >= timeStep) { | ||
callback(); | ||
delta -= timeStep; | ||
} | ||
if (animationFrame) start(); | ||
} | ||
function start() { | ||
animationFrame = window.requestAnimationFrame(animate); | ||
} | ||
function stop() { | ||
window.cancelAnimationFrame(animationFrame); | ||
lastTimeStamp = null; | ||
animationFrame = 0; | ||
} | ||
const self = { | ||
init, | ||
destroy, | ||
start: ifAnimating(false, start), | ||
stop: ifAnimating(true, stop) | ||
}; | ||
return self; | ||
} | ||
function Axis(axis, direction) { | ||
const scroll = axis === 'y' ? 'y' : 'x'; | ||
const cross = axis === 'y' ? 'x' : 'y'; | ||
const startEdge = getStartEdge(); | ||
const endEdge = getEndEdge(); | ||
function measureSize(rect) { | ||
const { | ||
width, | ||
height | ||
} = rect; | ||
return scroll === 'x' ? width : height; | ||
} | ||
function getStartEdge() { | ||
if (scroll === 'y') return 'top'; | ||
return direction === 'rtl' ? 'right' : 'left'; | ||
} | ||
function getEndEdge() { | ||
if (scroll === 'y') return 'bottom'; | ||
return direction === 'rtl' ? 'left' : 'right'; | ||
} | ||
const self = { | ||
scroll, | ||
cross, | ||
startEdge, | ||
endEdge, | ||
measureSize | ||
}; | ||
return self; | ||
} | ||
function Limit(min, max) { | ||
const length = mathAbs(min - max); | ||
function reachedMin(n) { | ||
return n < min; | ||
} | ||
function reachedMax(n) { | ||
return n > max; | ||
} | ||
function reachedAny(n) { | ||
return reachedMin(n) || reachedMax(n); | ||
} | ||
function constrain(n) { | ||
if (!reachedAny(n)) return n; | ||
return reachedMin(n) ? min : max; | ||
} | ||
function removeOffset(n) { | ||
if (!length) return n; | ||
return n - length * Math.ceil((n - max) / length); | ||
} | ||
const self = { | ||
length, | ||
max, | ||
min, | ||
constrain, | ||
reachedAny, | ||
reachedMax, | ||
reachedMin, | ||
removeOffset | ||
}; | ||
return self; | ||
} | ||
function Counter(max, start, loop) { | ||
const { | ||
min, | ||
constrain | ||
} = Limit(0, max); | ||
const loopEnd = max + 1; | ||
let counter = withinLimit(start); | ||
function withinLimit(n) { | ||
return !loop ? constrain(n) : mathAbs((loopEnd + n) % loopEnd); | ||
} | ||
function get() { | ||
return counter; | ||
} | ||
function set(n) { | ||
counter = withinLimit(n); | ||
return self; | ||
} | ||
function add(n) { | ||
return set(get() + n); | ||
} | ||
function clone() { | ||
return Counter(max, get(), loop); | ||
} | ||
const self = { | ||
add, | ||
clone, | ||
get, | ||
set, | ||
min, | ||
max | ||
}; | ||
return self; | ||
} | ||
function Direction(direction) { | ||
const sign = direction === 'rtl' ? -1 : 1; | ||
function apply(n) { | ||
return n * sign; | ||
} | ||
const self = { | ||
apply | ||
}; | ||
return self; | ||
} | ||
function Vector1D(value) { | ||
let vector = value; | ||
function get() { | ||
return vector; | ||
} | ||
function set(n) { | ||
vector = readNumber(n); | ||
return self; | ||
} | ||
function add(n) { | ||
vector += readNumber(n); | ||
return self; | ||
} | ||
function subtract(n) { | ||
vector -= readNumber(n); | ||
return self; | ||
} | ||
function multiply(n) { | ||
vector *= n; | ||
return self; | ||
} | ||
function divide(n) { | ||
vector /= n; | ||
return self; | ||
} | ||
function normalize() { | ||
if (vector !== 0) divide(vector); | ||
return self; | ||
} | ||
function readNumber(n) { | ||
return isNumber(n) ? n : n.get(); | ||
} | ||
const self = { | ||
add, | ||
divide, | ||
get, | ||
multiply, | ||
normalize, | ||
set, | ||
subtract | ||
}; | ||
return self; | ||
} | ||
function DragHandler(axis, direction, rootNode, target, dragTracker, location, animation, scrollTo, scrollBody, scrollTarget, index, eventHandler, percentOfView, dragFree, skipSnaps, baseFriction) { | ||
const { | ||
cross: crossAxis | ||
} = axis; | ||
const focusNodes = ['INPUT', 'SELECT', 'TEXTAREA']; | ||
const nonPassiveEvent = { | ||
passive: false | ||
}; | ||
const dragStartPoint = Vector1D(0); | ||
const initEvents = EventStore(); | ||
const dragEvents = EventStore(); | ||
const goToNextThreshold = Limit(50, 225).constrain(percentOfView.measure(20)); | ||
const snapForceBoost = { | ||
mouse: 300, | ||
touch: 400 | ||
}; | ||
const freeForceBoost = { | ||
mouse: 500, | ||
touch: 600 | ||
}; | ||
const baseSpeed = dragFree ? 43 : 25; | ||
let isMoving = false; | ||
let startScroll = 0; | ||
let startCross = 0; | ||
let pointerIsDown = false; | ||
let preventScroll = false; | ||
let preventClick = false; | ||
let isMouse = false; | ||
function init(emblaApi, watchDrag) { | ||
if (!watchDrag) return; | ||
function downIfAllowed(evt) { | ||
if (isBoolean(watchDrag) || watchDrag(emblaApi, evt)) down(evt); | ||
} | ||
const node = rootNode; | ||
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); | ||
} | ||
function destroy() { | ||
initEvents.clear(); | ||
dragEvents.clear(); | ||
} | ||
function addDragEvents() { | ||
const node = isMouse ? document : rootNode; | ||
dragEvents.add(node, 'touchmove', move, nonPassiveEvent).add(node, 'touchend', up).add(node, 'mousemove', move, nonPassiveEvent).add(node, 'mouseup', up); | ||
} | ||
function isFocusNode(node) { | ||
const nodeName = node.nodeName || ''; | ||
return focusNodes.includes(nodeName); | ||
} | ||
function forceBoost() { | ||
const boost = dragFree ? freeForceBoost : snapForceBoost; | ||
const type = isMouse ? 'mouse' : 'touch'; | ||
return boost[type]; | ||
} | ||
function allowedForce(force, targetChanged) { | ||
const next = index.clone().add(mathSign(force) * -1); | ||
const baseForce = scrollTarget.byDistance(force, !dragFree).distance; | ||
if (dragFree || mathAbs(force) < goToNextThreshold) return baseForce; | ||
if (skipSnaps && targetChanged) return baseForce * 0.5; | ||
return scrollTarget.byIndex(next.get(), 0).distance; | ||
} | ||
function down(evt) { | ||
const isMouseEvt = isMouseEvent(evt); | ||
isMouse = isMouseEvt; | ||
if (isMouseEvt && evt.button !== 0) return; | ||
if (isFocusNode(evt.target)) return; | ||
preventClick = dragFree && isMouseEvt && !evt.buttons && isMoving; | ||
isMoving = deltaAbs(target.get(), location.get()) >= 2; | ||
pointerIsDown = true; | ||
dragTracker.pointerDown(evt); | ||
dragStartPoint.set(target); | ||
scrollBody.useFriction(0).useDuration(0); | ||
target.set(location); | ||
addDragEvents(); | ||
startScroll = dragTracker.readPoint(evt); | ||
startCross = dragTracker.readPoint(evt, crossAxis); | ||
eventHandler.emit('pointerDown'); | ||
} | ||
function move(evt) { | ||
if (!preventScroll && !isMouse) { | ||
if (!evt.cancelable) return up(evt); | ||
const lastScroll = dragTracker.readPoint(evt); | ||
const lastCross = dragTracker.readPoint(evt, crossAxis); | ||
const diffScroll = deltaAbs(lastScroll, startScroll); | ||
const diffCross = deltaAbs(lastCross, startCross); | ||
preventScroll = diffScroll > diffCross; | ||
if (!preventScroll) return up(evt); | ||
} | ||
const diff = dragTracker.pointerMove(evt); | ||
if (diff) preventClick = true; | ||
scrollBody.useFriction(0.3).useDuration(1); | ||
animation.start(); | ||
target.add(direction.apply(diff)); | ||
evt.preventDefault(); | ||
} | ||
function up(evt) { | ||
const currentLocation = scrollTarget.byDistance(0, false); | ||
const targetChanged = currentLocation.index !== index.get(); | ||
const rawForce = dragTracker.pointerUp(evt) * forceBoost(); | ||
const force = allowedForce(direction.apply(rawForce), targetChanged); | ||
const forceFactor = factorAbs(rawForce, force); | ||
const speed = baseSpeed - 10 * forceFactor; | ||
const friction = baseFriction + forceFactor / 50; | ||
preventScroll = false; | ||
pointerIsDown = false; | ||
dragEvents.clear(); | ||
scrollBody.useDuration(speed).useFriction(friction); | ||
scrollTo.distance(force, !dragFree); | ||
isMouse = false; | ||
eventHandler.emit('pointerUp'); | ||
} | ||
function click(evt) { | ||
if (preventClick) { | ||
evt.stopPropagation(); | ||
evt.preventDefault(); | ||
} | ||
} | ||
function pointerDown() { | ||
return pointerIsDown; | ||
} | ||
const self = { | ||
init, | ||
pointerDown, | ||
destroy | ||
}; | ||
return self; | ||
} | ||
function DragTracker(axis) { | ||
const logInterval = 170; | ||
let startEvent; | ||
let lastEvent; | ||
function readTime(evt) { | ||
return evt.timeStamp; | ||
} | ||
function readPoint(evt, evtAxis) { | ||
const property = evtAxis || axis.scroll; | ||
const coord = `client${property === 'x' ? 'X' : 'Y'}`; | ||
return (isMouseEvent(evt) ? evt : evt.touches[0])[coord]; | ||
} | ||
function pointerDown(evt) { | ||
startEvent = evt; | ||
lastEvent = evt; | ||
return readPoint(evt); | ||
} | ||
function pointerMove(evt) { | ||
const diff = readPoint(evt) - readPoint(lastEvent); | ||
const expired = readTime(evt) - readTime(startEvent) > logInterval; | ||
lastEvent = evt; | ||
if (expired) startEvent = evt; | ||
return diff; | ||
} | ||
function pointerUp(evt) { | ||
if (!startEvent || !lastEvent) return 0; | ||
const diffDrag = readPoint(lastEvent) - readPoint(startEvent); | ||
const diffTime = readTime(evt) - readTime(startEvent); | ||
const expired = readTime(evt) - readTime(lastEvent) > logInterval; | ||
const force = diffDrag / diffTime; | ||
const isFlick = diffTime && !expired && mathAbs(force) > 0.1; | ||
return isFlick ? force : 0; | ||
} | ||
const self = { | ||
pointerDown, | ||
pointerMove, | ||
pointerUp, | ||
readPoint | ||
}; | ||
return self; | ||
} | ||
function PercentOfView(viewSize) { | ||
function measure(n) { | ||
return viewSize * (n / 100); | ||
} | ||
const self = { | ||
measure | ||
}; | ||
return self; | ||
} | ||
function ResizeHandler(container, slides, axis, eventHandler) { | ||
let resizeObserver; | ||
let containerSize; | ||
let slideSizes = []; | ||
let destroyed = false; | ||
function readSize(node) { | ||
return axis.measureSize(node.getBoundingClientRect()); | ||
} | ||
function init(emblaApi, watchResize) { | ||
if (!watchResize) return; | ||
containerSize = readSize(container); | ||
slideSizes = slides.map(readSize); | ||
function defaultCallback(entries) { | ||
for (const entry of entries) { | ||
const isContainer = entry.target === container; | ||
const slideIndex = slides.indexOf(entry.target); | ||
const lastSize = isContainer ? containerSize : slideSizes[slideIndex]; | ||
const newSize = readSize(isContainer ? container : slides[slideIndex]); | ||
if (lastSize !== newSize) { | ||
emblaApi.reInit(); | ||
eventHandler.emit('resize'); | ||
break; | ||
} | ||
} | ||
} | ||
resizeObserver = new ResizeObserver(entries => { | ||
if (destroyed) return; | ||
if (isBoolean(watchResize) || watchResize(emblaApi, entries)) { | ||
defaultCallback(entries); | ||
} | ||
}); | ||
const observeNodes = [container].concat(slides); | ||
observeNodes.forEach(node => resizeObserver.observe(node)); | ||
} | ||
function destroy() { | ||
if (resizeObserver) resizeObserver.disconnect(); | ||
destroyed = true; | ||
} | ||
const self = { | ||
init, | ||
destroy | ||
}; | ||
return self; | ||
} | ||
function ScrollBody(location, baseDuration, baseFriction) { | ||
const roundToTwoDecimals = roundToDecimals(2); | ||
const attraction = Vector1D(0); | ||
let attractionDirection = 0; | ||
let duration = baseDuration; | ||
let friction = baseFriction; | ||
function seek(target) { | ||
const diff = target.get() - location.get(); | ||
const isInstant = !friction || !duration; | ||
if (isInstant) { | ||
attraction.set(0); | ||
location.set(target); | ||
} else { | ||
attraction.add(diff / duration); | ||
attraction.multiply(friction); | ||
location.add(attraction); | ||
} | ||
attractionDirection = mathSign(attraction.get() || diff); | ||
} | ||
function settle(target) { | ||
const diff = target.get() - location.get(); | ||
const hasSettled = !roundToTwoDecimals(diff); | ||
if (hasSettled) location.set(target); | ||
return hasSettled; | ||
} | ||
function direction() { | ||
return attractionDirection; | ||
} | ||
function useBaseDuration() { | ||
return useDuration(baseDuration); | ||
} | ||
function useBaseFriction() { | ||
return useFriction(baseFriction); | ||
} | ||
function useDuration(n) { | ||
duration = n; | ||
return self; | ||
} | ||
function useFriction(n) { | ||
friction = n; | ||
return self; | ||
} | ||
const self = { | ||
direction, | ||
seek, | ||
settle, | ||
useBaseFriction, | ||
useBaseDuration, | ||
useFriction, | ||
useDuration | ||
}; | ||
return self; | ||
} | ||
function ScrollBounds(limit, location, target, scrollBody, percentOfView) { | ||
const pullBackThreshold = percentOfView.measure(10); | ||
const edgeOffsetTolerance = percentOfView.measure(50); | ||
const frictionLimit = Limit(0.1, 0.99); | ||
let disabled = false; | ||
function shouldConstrain() { | ||
if (disabled) return false; | ||
if (!limit.reachedAny(target.get())) return false; | ||
if (!limit.reachedAny(location.get())) return false; | ||
return true; | ||
} | ||
function constrain(pointerDown) { | ||
if (!shouldConstrain()) return; | ||
const edge = limit.reachedMin(location.get()) ? 'min' : 'max'; | ||
const diffToEdge = mathAbs(limit[edge] - location.get()); | ||
const diffToTarget = target.get() - location.get(); | ||
const friction = frictionLimit.constrain(diffToEdge / edgeOffsetTolerance); | ||
target.subtract(diffToTarget * friction); | ||
if (!pointerDown && mathAbs(diffToTarget) < pullBackThreshold) { | ||
target.set(limit.constrain(target.get())); | ||
scrollBody.useDuration(25).useBaseFriction(); | ||
} | ||
} | ||
function toggleActive(active) { | ||
disabled = !active; | ||
} | ||
const self = { | ||
constrain, | ||
toggleActive | ||
}; | ||
return self; | ||
} | ||
function ScrollContain(viewSize, contentSize, snapsAligned, containScroll) { | ||
const scrollBounds = Limit(-contentSize + viewSize, snapsAligned[0]); | ||
const snapsBounded = snapsAligned.map(scrollBounds.constrain); | ||
const snapsContained = measureContained(); | ||
function findDuplicates() { | ||
const startSnap = snapsBounded[0]; | ||
const endSnap = arrayLast(snapsBounded); | ||
const min = snapsBounded.lastIndexOf(startSnap); | ||
const max = snapsBounded.indexOf(endSnap) + 1; | ||
return Limit(min, max); | ||
} | ||
function measureContained() { | ||
if (contentSize <= viewSize) return [scrollBounds.max]; | ||
if (containScroll === 'keepSnaps') return snapsBounded; | ||
const { | ||
min, | ||
max | ||
} = findDuplicates(); | ||
return snapsBounded.slice(min, max); | ||
} | ||
const self = { | ||
snapsContained | ||
}; | ||
return self; | ||
} | ||
function ScrollLimit(contentSize, scrollSnaps, loop) { | ||
const limit = measureLimit(); | ||
function measureLimit() { | ||
const startSnap = scrollSnaps[0]; | ||
const endSnap = arrayLast(scrollSnaps); | ||
const min = loop ? startSnap - contentSize : endSnap; | ||
const max = startSnap; | ||
return Limit(min, max); | ||
} | ||
const self = { | ||
limit | ||
}; | ||
return self; | ||
} | ||
function ScrollLooper(contentSize, limit, location, vectors) { | ||
const jointSafety = 0.1; | ||
const min = limit.min + jointSafety; | ||
const max = limit.max + jointSafety; | ||
const { | ||
reachedMin, | ||
reachedMax | ||
} = Limit(min, max); | ||
function shouldLoop(direction) { | ||
if (direction === 1) return reachedMax(location.get()); | ||
if (direction === -1) return reachedMin(location.get()); | ||
return false; | ||
} | ||
function loop(direction) { | ||
if (!shouldLoop(direction)) return; | ||
const loopDistance = contentSize * (direction * -1); | ||
vectors.forEach(v => v.add(loopDistance)); | ||
} | ||
const self = { | ||
loop | ||
}; | ||
return self; | ||
} | ||
function ScrollProgress(limit) { | ||
const { | ||
max, | ||
length: scrollLength | ||
} = limit; | ||
function get(n) { | ||
const currentLocation = n - max; | ||
return currentLocation / -scrollLength; | ||
} | ||
const self = { | ||
get | ||
}; | ||
return self; | ||
} | ||
function ScrollSnaps(axis, alignment, containerRect, slideRects, slideSizesWithGaps, slidesToScroll, containScroll) { | ||
const { | ||
startEdge, | ||
endEdge | ||
} = axis; | ||
const { | ||
groupSlides | ||
} = slidesToScroll; | ||
const alignments = measureSizes().map(alignment.measure); | ||
const snaps = measureUnaligned(); | ||
const snapsAligned = measureAligned(); | ||
function measureSizes() { | ||
return groupSlides(slideRects).map(rects => arrayLast(rects)[endEdge] - rects[0][startEdge]).map(mathAbs); | ||
} | ||
function measureUnaligned() { | ||
return slideRects.map(rect => containerRect[startEdge] - rect[startEdge]).map(snap => -mathAbs(snap)); | ||
} | ||
function measureAligned() { | ||
const containedStartSnap = 0; | ||
const containedEndSnap = arrayLast(snaps) - arrayLast(slideSizesWithGaps); | ||
return groupSlides(snaps).map(g => g[0]).map((snap, index, groupedSnaps) => { | ||
const isFirst = !index; | ||
const isLast = index === arrayLastIndex(groupedSnaps); | ||
if (containScroll && isFirst) return containedStartSnap; | ||
if (containScroll && isLast) return containedEndSnap; | ||
return snap + alignments[index]; | ||
}); | ||
} | ||
const self = { | ||
snaps, | ||
snapsAligned | ||
}; | ||
return self; | ||
} | ||
function ScrollTarget(loop, scrollSnaps, contentSize, limit, targetVector) { | ||
const { | ||
reachedAny, | ||
removeOffset, | ||
constrain | ||
} = limit; | ||
function minDistance(distances) { | ||
return distances.concat().sort((a, b) => mathAbs(a) - mathAbs(b))[0]; | ||
} | ||
function findTargetSnap(target) { | ||
const distance = loop ? removeOffset(target) : constrain(target); | ||
const ascDiffsToSnaps = scrollSnaps.map(scrollSnap => scrollSnap - distance).map(diffToSnap => shortcut(diffToSnap, 0)).map((diff, i) => ({ | ||
diff, | ||
index: i | ||
})).sort((d1, d2) => mathAbs(d1.diff) - mathAbs(d2.diff)); | ||
const { | ||
index | ||
} = ascDiffsToSnaps[0]; | ||
return { | ||
index, | ||
distance | ||
}; | ||
} | ||
function shortcut(target, direction) { | ||
const targets = [target, target + contentSize, target - contentSize]; | ||
if (!loop) return targets[0]; | ||
if (!direction) return minDistance(targets); | ||
const matchingTargets = targets.filter(t => mathSign(t) === direction); | ||
return minDistance(matchingTargets); | ||
} | ||
function byIndex(index, direction) { | ||
const diffToSnap = scrollSnaps[index] - targetVector.get(); | ||
const distance = shortcut(diffToSnap, direction); | ||
return { | ||
index, | ||
distance | ||
}; | ||
} | ||
function byDistance(distance, snap) { | ||
const target = targetVector.get() + distance; | ||
const { | ||
index, | ||
distance: targetSnapDistance | ||
} = findTargetSnap(target); | ||
const reachedBound = !loop && reachedAny(target); | ||
if (!snap || reachedBound) return { | ||
index, | ||
distance | ||
}; | ||
const diffToSnap = scrollSnaps[index] - targetSnapDistance; | ||
const snapDistance = distance + shortcut(diffToSnap, 0); | ||
return { | ||
index, | ||
distance: snapDistance | ||
}; | ||
} | ||
const self = { | ||
byDistance, | ||
byIndex, | ||
shortcut | ||
}; | ||
return self; | ||
} | ||
function ScrollTo(animation, indexCurrent, indexPrevious, scrollTarget, targetVector, eventHandler) { | ||
function scrollTo(target) { | ||
const distanceDiff = target.distance; | ||
const indexDiff = target.index !== indexCurrent.get(); | ||
if (distanceDiff) { | ||
animation.start(); | ||
targetVector.add(distanceDiff); | ||
} | ||
if (indexDiff) { | ||
indexPrevious.set(indexCurrent.get()); | ||
indexCurrent.set(target.index); | ||
eventHandler.emit('select'); | ||
} | ||
} | ||
function distance(n, snap) { | ||
const target = scrollTarget.byDistance(n, snap); | ||
scrollTo(target); | ||
} | ||
function index(n, direction) { | ||
const targetIndex = indexCurrent.clone().set(n); | ||
const target = scrollTarget.byIndex(targetIndex.get(), direction); | ||
scrollTo(target); | ||
} | ||
const self = { | ||
distance, | ||
index | ||
}; | ||
return self; | ||
} | ||
function Translate(axis, direction, container) { | ||
const translate = axis.scroll === 'x' ? x : y; | ||
const containerStyle = container.style; | ||
let disabled = false; | ||
function x(n) { | ||
return `translate3d(${n}px,0px,0px)`; | ||
} | ||
function y(n) { | ||
return `translate3d(0px,${n}px,0px)`; | ||
} | ||
function to(target) { | ||
if (disabled) return; | ||
containerStyle.transform = translate(direction.apply(target.get())); | ||
} | ||
function toggleActive(active) { | ||
disabled = !active; | ||
} | ||
function clear() { | ||
if (disabled) return; | ||
containerStyle.transform = ''; | ||
if (!container.getAttribute('style')) container.removeAttribute('style'); | ||
} | ||
const self = { | ||
clear, | ||
to, | ||
toggleActive | ||
}; | ||
return self; | ||
} | ||
function SlideLooper(axis, direction, viewSize, contentSize, slideSizesWithGaps, scrollSnaps, slidesInView, scroll, slides) { | ||
const ascItems = arrayKeys(slideSizesWithGaps); | ||
const descItems = arrayKeys(slideSizesWithGaps).reverse(); | ||
const loopPoints = startPoints().concat(endPoints()); | ||
function removeSlideSizes(indexes, from) { | ||
return indexes.reduce((a, i) => { | ||
return a - slideSizesWithGaps[i]; | ||
}, from); | ||
} | ||
function slidesInGap(indexes, gap) { | ||
return indexes.reduce((a, i) => { | ||
const remainingGap = removeSlideSizes(a, gap); | ||
return remainingGap > 0 ? a.concat([i]) : a; | ||
}, []); | ||
} | ||
function findLoopPoints(indexes, edge) { | ||
const isStartEdge = edge === 'start'; | ||
const offset = isStartEdge ? -contentSize : contentSize; | ||
const slideBounds = slidesInView.findSlideBounds([offset]); | ||
return indexes.map(index => { | ||
const initial = isStartEdge ? 0 : -contentSize; | ||
const altered = isStartEdge ? contentSize : 0; | ||
const bounds = slideBounds.filter(b => b.index === index)[0]; | ||
const point = bounds[isStartEdge ? 'end' : 'start']; | ||
const shift = Vector1D(-1); | ||
const location = Vector1D(-1); | ||
const translate = Translate(axis, direction, slides[index]); | ||
const target = () => shift.set(scroll.get() > point ? initial : altered); | ||
return { | ||
index, | ||
location, | ||
translate, | ||
target | ||
}; | ||
}); | ||
} | ||
function startPoints() { | ||
const gap = scrollSnaps[0] - 1; | ||
const indexes = slidesInGap(descItems, gap); | ||
return findLoopPoints(indexes, 'end'); | ||
} | ||
function endPoints() { | ||
const gap = viewSize - scrollSnaps[0] - 1; | ||
const indexes = slidesInGap(ascItems, gap); | ||
return findLoopPoints(indexes, 'start'); | ||
} | ||
function canLoop() { | ||
return loopPoints.every(({ | ||
index | ||
}) => { | ||
const otherIndexes = ascItems.filter(i => i !== index); | ||
return removeSlideSizes(otherIndexes, viewSize) <= 0.1; | ||
}); | ||
} | ||
function loop() { | ||
loopPoints.forEach(loopPoint => { | ||
const { | ||
target, | ||
translate, | ||
location | ||
} = loopPoint; | ||
const shift = target(); | ||
if (shift.get() === location.get()) return; | ||
if (shift.get() === 0) translate.clear();else translate.to(shift); | ||
location.set(shift); | ||
}); | ||
} | ||
function clear() { | ||
loopPoints.forEach(loopPoint => loopPoint.translate.clear()); | ||
} | ||
const self = { | ||
canLoop, | ||
clear, | ||
loop, | ||
loopPoints | ||
}; | ||
return self; | ||
} | ||
function SlidesHandler(container, eventHandler) { | ||
let mutationObserver; | ||
let destroyed = false; | ||
function init(emblaApi, watchSlides) { | ||
if (!watchSlides) return; | ||
function defaultCallback(mutations) { | ||
for (const mutation of mutations) { | ||
if (mutation.type === 'childList') { | ||
emblaApi.reInit(); | ||
eventHandler.emit('slidesChanged'); | ||
break; | ||
} | ||
} | ||
} | ||
mutationObserver = new MutationObserver(mutations => { | ||
if (destroyed) return; | ||
if (isBoolean(watchSlides) || watchSlides(emblaApi, mutations)) { | ||
defaultCallback(mutations); | ||
} | ||
}); | ||
mutationObserver.observe(container, { | ||
childList: true | ||
}); | ||
} | ||
function destroy() { | ||
if (mutationObserver) mutationObserver.disconnect(); | ||
destroyed = true; | ||
} | ||
const self = { | ||
init, | ||
destroy | ||
}; | ||
return self; | ||
} | ||
function SlidesInView(viewSize, contentSize, slideSizes, snaps, limit, loop, inViewThreshold) { | ||
const { | ||
removeOffset, | ||
constrain | ||
} = limit; | ||
const roundingSafety = 0.5; | ||
const cachedOffsets = loop ? [0, contentSize, -contentSize] : [0]; | ||
const cachedBounds = findSlideBounds(cachedOffsets, inViewThreshold); | ||
function findSlideThresholds(threshold) { | ||
const slideThreshold = threshold || 0; | ||
return slideSizes.map(slideSize => { | ||
const thresholdLimit = Limit(roundingSafety, slideSize - roundingSafety); | ||
return thresholdLimit.constrain(slideSize * slideThreshold); | ||
}); | ||
} | ||
function findSlideBounds(offsets, threshold) { | ||
const slideOffsets = offsets || cachedOffsets; | ||
const slideThresholds = findSlideThresholds(threshold); | ||
return slideOffsets.reduce((list, offset) => { | ||
const bounds = snaps.map((snap, index) => ({ | ||
start: snap - slideSizes[index] + slideThresholds[index] + offset, | ||
end: snap + viewSize - slideThresholds[index] + offset, | ||
index | ||
})); | ||
return list.concat(bounds); | ||
}, []); | ||
} | ||
function check(location, bounds) { | ||
const limitedLocation = loop ? removeOffset(location) : constrain(location); | ||
const slideBounds = bounds || cachedBounds; | ||
return slideBounds.reduce((list, slideBound) => { | ||
const { | ||
index, | ||
start, | ||
end | ||
} = slideBound; | ||
const inList = list.includes(index); | ||
const inView = start < limitedLocation && end > limitedLocation; | ||
return !inList && inView ? list.concat([index]) : list; | ||
}, []); | ||
} | ||
const self = { | ||
check, | ||
findSlideBounds | ||
}; | ||
return self; | ||
} | ||
function SlideSizes(axis, containerRect, slideRects, slides, readEdgeGap) { | ||
const { | ||
measureSize, | ||
startEdge, | ||
endEdge | ||
} = axis; | ||
const withEdgeGap = slideRects[0] && readEdgeGap; | ||
const startGap = measureStartGap(); | ||
const endGap = measureEndGap(); | ||
const slideSizes = slideRects.map(measureSize); | ||
const slideSizesWithGaps = measureWithGaps(); | ||
function measureStartGap() { | ||
if (!withEdgeGap) return 0; | ||
const slideRect = slideRects[0]; | ||
return mathAbs(containerRect[startEdge] - slideRect[startEdge]); | ||
} | ||
function measureEndGap() { | ||
if (!withEdgeGap) return 0; | ||
const style = window.getComputedStyle(arrayLast(slides)); | ||
return parseFloat(style.getPropertyValue(`margin-${endEdge}`)); | ||
} | ||
function measureWithGaps() { | ||
return slideRects.map((rect, index, rects) => { | ||
const isFirst = !index; | ||
const isLast = index === arrayLastIndex(rects); | ||
if (isFirst) return slideSizes[index] + startGap; | ||
if (isLast) return slideSizes[index] + endGap; | ||
return rects[index + 1][startEdge] - rect[startEdge]; | ||
}).map(mathAbs); | ||
} | ||
const self = { | ||
slideSizes, | ||
slideSizesWithGaps | ||
}; | ||
return self; | ||
} | ||
function SlidesToScroll(viewSize, slideSizesWithGaps, slidesToScroll) { | ||
const groupByNumber = isNumber(slidesToScroll); | ||
function byNumber(array, groupSize) { | ||
return arrayKeys(array).filter(i => i % groupSize === 0).map(i => array.slice(i, i + groupSize)); | ||
} | ||
function bySize(array) { | ||
return arrayKeys(array).reduce((groupSizes, i) => { | ||
const chunk = slideSizesWithGaps.slice(arrayLast(groupSizes), i + 1); | ||
const chunkSize = chunk.reduce((a, s) => a + s, 0); | ||
return !i || chunkSize > viewSize ? groupSizes.concat(i) : groupSizes; | ||
}, []).map((start, i, groupSizes) => array.slice(start, groupSizes[i + 1])); | ||
} | ||
function groupSlides(array) { | ||
return groupByNumber ? byNumber(array, slidesToScroll) : bySize(array); | ||
} | ||
const self = { | ||
groupSlides | ||
}; | ||
return self; | ||
} | ||
function Engine(root, container, slides, options, eventHandler) { | ||
// Options | ||
const { | ||
align, | ||
axis: scrollAxis, | ||
direction: contentDirection, | ||
startIndex, | ||
inViewThreshold, | ||
loop, | ||
duration, | ||
dragFree, | ||
slidesToScroll: groupSlides, | ||
skipSnaps, | ||
containScroll | ||
} = options; | ||
// Measurements | ||
const containerRect = container.getBoundingClientRect(); | ||
const slideRects = slides.map(slide => slide.getBoundingClientRect()); | ||
const direction = Direction(contentDirection); | ||
const axis = Axis(scrollAxis, contentDirection); | ||
const viewSize = axis.measureSize(containerRect); | ||
const percentOfView = PercentOfView(viewSize); | ||
const alignment = Alignment(align, viewSize); | ||
const containSnaps = !loop && !!containScroll; | ||
const readEdgeGap = loop || !!containScroll; | ||
const { | ||
slideSizes, | ||
slideSizesWithGaps | ||
} = SlideSizes(axis, containerRect, slideRects, slides, readEdgeGap); | ||
const slidesToScroll = SlidesToScroll(viewSize, slideSizesWithGaps, groupSlides); | ||
const { | ||
snaps, | ||
snapsAligned | ||
} = ScrollSnaps(axis, alignment, containerRect, slideRects, slideSizesWithGaps, slidesToScroll, containSnaps); | ||
const contentSize = -arrayLast(snaps) + arrayLast(slideSizesWithGaps); | ||
const { | ||
snapsContained | ||
} = ScrollContain(viewSize, contentSize, snapsAligned, containScroll); | ||
const scrollSnaps = containSnaps ? snapsContained : snapsAligned; | ||
const { | ||
limit | ||
} = ScrollLimit(contentSize, scrollSnaps, loop); | ||
// Indexes | ||
const index = Counter(arrayLastIndex(scrollSnaps), startIndex, loop); | ||
const indexPrevious = index.clone(); | ||
const slideIndexes = arrayKeys(slides); | ||
// Draw | ||
const animationCallback = () => { | ||
const pointerDown = engine.dragHandler.pointerDown(); | ||
if (!loop) engine.scrollBounds.constrain(pointerDown); | ||
engine.scrollBody.seek(target); | ||
const settled = engine.scrollBody.settle(target); | ||
if (settled && !pointerDown) { | ||
engine.animation.stop(); | ||
eventHandler.emit('settle'); | ||
} | ||
if (!settled) { | ||
eventHandler.emit('scroll'); | ||
} | ||
if (loop) { | ||
engine.scrollLooper.loop(engine.scrollBody.direction()); | ||
engine.slideLooper.loop(); | ||
} | ||
engine.translate.to(location); | ||
}; | ||
// Shared | ||
const friction = 0.68; | ||
const animation = Animation(animationCallback); | ||
const startLocation = scrollSnaps[index.get()]; | ||
const location = Vector1D(startLocation); | ||
const target = Vector1D(startLocation); | ||
const scrollBody = ScrollBody(location, duration, friction); | ||
const scrollTarget = ScrollTarget(loop, scrollSnaps, contentSize, limit, target); | ||
const scrollTo = ScrollTo(animation, index, indexPrevious, scrollTarget, target, eventHandler); | ||
const slidesInView = SlidesInView(viewSize, contentSize, slideSizes, snaps, limit, loop, inViewThreshold); | ||
// Engine | ||
const engine = { | ||
containerRect, | ||
slideRects, | ||
animation, | ||
axis, | ||
direction, | ||
dragHandler: DragHandler(axis, direction, root, target, DragTracker(axis), location, animation, scrollTo, scrollBody, scrollTarget, index, eventHandler, percentOfView, dragFree, skipSnaps, friction), | ||
eventStore: EventStore(), | ||
percentOfView, | ||
index, | ||
indexPrevious, | ||
limit, | ||
location, | ||
options, | ||
resizeHandler: ResizeHandler(container, slides, axis, eventHandler), | ||
scrollBody, | ||
scrollBounds: ScrollBounds(limit, location, target, scrollBody, percentOfView), | ||
scrollLooper: ScrollLooper(contentSize, limit, location, [location, target]), | ||
scrollProgress: ScrollProgress(limit), | ||
scrollSnaps, | ||
scrollTarget, | ||
scrollTo, | ||
slideLooper: SlideLooper(axis, direction, viewSize, contentSize, slideSizesWithGaps, scrollSnaps, slidesInView, location, slides), | ||
slidesHandler: SlidesHandler(container, eventHandler), | ||
slidesInView, | ||
slideIndexes, | ||
slidesToScroll, | ||
target, | ||
translate: Translate(axis, direction, container) | ||
}; | ||
return engine; | ||
} | ||
function EventHandler() { | ||
const listeners = {}; | ||
let api; | ||
function init(emblaApi) { | ||
api = emblaApi; | ||
} | ||
function getListeners(evt) { | ||
return listeners[evt] || []; | ||
} | ||
function emit(evt) { | ||
getListeners(evt).forEach(e => e(api, evt)); | ||
return self; | ||
} | ||
function on(evt, cb) { | ||
listeners[evt] = getListeners(evt).concat([cb]); | ||
return self; | ||
} | ||
function off(evt, cb) { | ||
listeners[evt] = getListeners(evt).filter(e => e !== cb); | ||
return self; | ||
} | ||
const self = { | ||
init, | ||
emit, | ||
off, | ||
on | ||
}; | ||
return self; | ||
} | ||
const defaultOptions = { | ||
align: 'center', | ||
axis: 'x', | ||
container: null, | ||
slides: null, | ||
containScroll: null, | ||
direction: 'ltr', | ||
slidesToScroll: 1, | ||
breakpoints: {}, | ||
dragFree: false, | ||
inViewThreshold: 0, | ||
loop: false, | ||
skipSnaps: false, | ||
duration: 25, | ||
startIndex: 0, | ||
active: true, | ||
watchDrag: true, | ||
watchResize: true, | ||
watchSlides: true | ||
}; | ||
function OptionsHandler() { | ||
function mergeOptions(optionsA, optionsB) { | ||
return objectsMergeDeep(optionsA, optionsB || {}); | ||
} | ||
function optionsAtMedia(options) { | ||
const optionsAtMedia = options.breakpoints || {}; | ||
const matchedMediaOptions = objectKeys(optionsAtMedia).filter(media => window.matchMedia(media).matches).map(media => optionsAtMedia[media]).reduce((a, mediaOption) => mergeOptions(a, mediaOption), {}); | ||
return mergeOptions(options, matchedMediaOptions); | ||
} | ||
function optionsMediaQueries(optionsList) { | ||
return optionsList.map(options => objectKeys(options.breakpoints || {})).reduce((acc, mediaQueries) => acc.concat(mediaQueries), []).map(window.matchMedia); | ||
} | ||
const self = { | ||
mergeOptions, | ||
optionsAtMedia, | ||
optionsMediaQueries | ||
}; | ||
return self; | ||
} | ||
function PluginsHandler() { | ||
const optionsHandler = OptionsHandler(); | ||
let activePlugins = []; | ||
function init(plugins, emblaApi) { | ||
activePlugins = plugins.filter(({ | ||
options | ||
}) => optionsHandler.optionsAtMedia(options).active !== false); | ||
activePlugins.forEach(plugin => plugin.init(emblaApi, optionsHandler)); | ||
return plugins.reduce((map, plugin) => Object.assign(map, { | ||
[plugin.name]: plugin | ||
}), {}); | ||
} | ||
function destroy() { | ||
activePlugins = activePlugins.filter(plugin => plugin.destroy()); | ||
} | ||
const self = { | ||
init, | ||
destroy | ||
}; | ||
return self; | ||
} | ||
function EmblaCarousel(root, userOptions, userPlugins) { | ||
const mediaHandlers = EventStore(); | ||
const pluginsHandler = PluginsHandler(); | ||
const eventHandler = EventHandler(); | ||
const { | ||
mergeOptions, | ||
optionsAtMedia, | ||
optionsMediaQueries | ||
} = OptionsHandler(); | ||
const { | ||
on, | ||
off, | ||
emit | ||
} = eventHandler; | ||
const reInit = reActivate; | ||
let destroyed = false; | ||
let engine; | ||
let optionsBase = mergeOptions(defaultOptions, EmblaCarousel.globalOptions); | ||
let options = mergeOptions(optionsBase); | ||
let pluginList = []; | ||
let pluginApis; | ||
let container; | ||
let slides; | ||
function storeElements() { | ||
const { | ||
container: userContainer, | ||
slides: userSlides | ||
} = options; | ||
const customContainer = isString(userContainer) ? root.querySelector(userContainer) : userContainer; | ||
container = customContainer || root.children[0]; | ||
const customSlides = isString(userSlides) ? container.querySelectorAll(userSlides) : userSlides; | ||
slides = [].slice.call(customSlides || container.children); | ||
} | ||
function activate(withOptions, withPlugins) { | ||
if (destroyed) return; | ||
optionsBase = mergeOptions(optionsBase, withOptions); | ||
options = optionsAtMedia(optionsBase); | ||
storeElements(); | ||
engine = Engine(root, container, slides, options, eventHandler); | ||
if (!options.active) return deActivate(); | ||
engine.translate.to(engine.location); | ||
pluginList = withPlugins || pluginList; | ||
pluginApis = pluginsHandler.init(pluginList, self); | ||
optionsMediaQueries([optionsBase, ...pluginList.map(({ | ||
options | ||
}) => options)]).forEach(query => mediaHandlers.add(query, 'change', reActivate)); | ||
engine.animation.init(); | ||
eventHandler.init(self); | ||
engine.resizeHandler.init(self, options.watchResize); | ||
engine.slidesHandler.init(self, options.watchSlides); | ||
if (options.loop) { | ||
if (!engine.slideLooper.canLoop()) { | ||
deActivate(); | ||
activate({ | ||
loop: false | ||
}, withPlugins); | ||
optionsBase = mergeOptions(optionsBase, { | ||
loop: true | ||
}); | ||
return; | ||
} | ||
engine.slideLooper.loop(); | ||
} | ||
if (container.offsetParent && slides.length) { | ||
engine.dragHandler.init(self, options.watchDrag); | ||
} | ||
} | ||
function reActivate(withOptions, withPlugins) { | ||
const startIndex = selectedScrollSnap(); | ||
deActivate(); | ||
activate(mergeOptions({ | ||
startIndex | ||
}, withOptions), withPlugins); | ||
eventHandler.emit('reInit'); | ||
} | ||
function deActivate() { | ||
engine.dragHandler.destroy(); | ||
engine.animation.destroy(); | ||
engine.eventStore.clear(); | ||
engine.translate.clear(); | ||
engine.slideLooper.clear(); | ||
engine.resizeHandler.destroy(); | ||
engine.slidesHandler.destroy(); | ||
pluginsHandler.destroy(); | ||
mediaHandlers.clear(); | ||
} | ||
function destroy() { | ||
if (destroyed) return; | ||
destroyed = true; | ||
mediaHandlers.clear(); | ||
deActivate(); | ||
eventHandler.emit('destroy'); | ||
} | ||
function slidesInView(target) { | ||
const location = engine[target ? 'target' : 'location'].get(); | ||
const type = options.loop ? 'removeOffset' : 'constrain'; | ||
return engine.slidesInView.check(engine.limit[type](location)); | ||
} | ||
function slidesNotInView(target) { | ||
const inView = slidesInView(target); | ||
return engine.slideIndexes.filter(index => !inView.includes(index)); | ||
} | ||
function scrollTo(index, jump, direction) { | ||
if (!options.active || destroyed) return; | ||
engine.scrollBody.useBaseFriction().useDuration(jump ? 0 : options.duration); | ||
engine.scrollTo.index(index, direction || 0); | ||
} | ||
function scrollNext(jump) { | ||
const next = engine.index.clone().add(1); | ||
scrollTo(next.get(), jump === true, -1); | ||
} | ||
function scrollPrev(jump) { | ||
const prev = engine.index.clone().add(-1); | ||
scrollTo(prev.get(), jump === true, 1); | ||
} | ||
function canScrollNext() { | ||
const next = engine.index.clone().add(1); | ||
return next.get() !== selectedScrollSnap(); | ||
} | ||
function canScrollPrev() { | ||
const prev = engine.index.clone().add(-1); | ||
return prev.get() !== selectedScrollSnap(); | ||
} | ||
function scrollSnapList() { | ||
return engine.scrollSnaps.map(engine.scrollProgress.get); | ||
} | ||
function scrollProgress() { | ||
return engine.scrollProgress.get(engine.location.get()); | ||
} | ||
function selectedScrollSnap() { | ||
return engine.index.get(); | ||
} | ||
function previousScrollSnap() { | ||
return engine.indexPrevious.get(); | ||
} | ||
function plugins() { | ||
return pluginApis; | ||
} | ||
function internalEngine() { | ||
return engine; | ||
} | ||
function rootNode() { | ||
return root; | ||
} | ||
function containerNode() { | ||
return container; | ||
} | ||
function slideNodes() { | ||
return slides; | ||
} | ||
const self = { | ||
canScrollNext, | ||
canScrollPrev, | ||
containerNode, | ||
internalEngine, | ||
destroy, | ||
off, | ||
on, | ||
emit, | ||
plugins, | ||
previousScrollSnap, | ||
reInit, | ||
rootNode, | ||
scrollNext, | ||
scrollPrev, | ||
scrollProgress, | ||
scrollSnapList, | ||
scrollTo, | ||
selectedScrollSnap, | ||
slideNodes, | ||
slidesInView, | ||
slidesNotInView | ||
}; | ||
activate(userOptions, userPlugins); | ||
setTimeout(() => eventHandler.emit('init'), 0); | ||
return self; | ||
} | ||
EmblaCarousel.globalOptions = undefined; | ||
function emblaCarouselVue(options = {}, plugins = []) { | ||
const storedOptions = vue.ref(vue.isRef(options) ? options.value : options); | ||
const storedPlugins = vue.ref(vue.isRef(plugins) ? plugins.value : plugins); | ||
const emblaNode = vue.ref(); | ||
const embla = vue.ref(); | ||
function reInit() { | ||
if (!embla.value) return; | ||
embla.value.reInit(storedOptions.value, storedPlugins.value); | ||
} | ||
vue.onMounted(() => { | ||
if (!canUseDOM() || !emblaNode.value) return; | ||
EmblaCarousel.globalOptions = emblaCarouselVue.globalOptions; | ||
embla.value = EmblaCarousel(emblaNode.value, storedOptions.value, storedPlugins.value); | ||
}); | ||
vue.onUnmounted(() => { | ||
if (embla.value) embla.value.destroy(); | ||
}); | ||
if (vue.isRef(options)) { | ||
vue.watch(options, newOptions => { | ||
if (areOptionsEqual(storedOptions.value, newOptions)) return; | ||
storedOptions.value = newOptions; | ||
reInit(); | ||
}); | ||
} | ||
if (vue.isRef(plugins)) { | ||
vue.watch(plugins, newPlugins => { | ||
if (arePluginsEqual(storedPlugins.value, newPlugins)) return; | ||
storedPlugins.value = newPlugins; | ||
reInit(); | ||
}); | ||
} | ||
return [emblaNode, embla]; | ||
} | ||
emblaCarouselVue.globalOptions = undefined; | ||
return emblaCarouselVue; | ||
})); |
export { EmblaOptionsType } from 'embla-carousel/components/Options'; | ||
export { EmblaEventType } from 'embla-carousel/components/EventHandler'; | ||
export { EmblaPluginType } from 'embla-carousel/components/Plugins'; | ||
export { EmblaCarouselType } from 'embla-carousel/components'; | ||
export { EmblaCarouselVueType } from './components'; | ||
export { default } from './components'; | ||
export { EmblaCarouselType } from 'embla-carousel/components/EmblaCarousel'; | ||
export { EmblaCarouselVueType } from './components/emblaCarouselVue'; | ||
export { default } from './components/emblaCarouselVue'; |
{ | ||
"name": "embla-carousel-vue", | ||
"version": "7.1.0", | ||
"version": "8.0.0-rc01", | ||
"author": "David Jerleke", | ||
@@ -59,3 +59,4 @@ "description": "A lightweight carousel library with fluid motion and great swipe precision", | ||
"dependencies": { | ||
"embla-carousel": "7.1.0" | ||
"embla-carousel": "8.0.0-rc01", | ||
"embla-carousel-reactive-utils": "8.0.0-rc01" | ||
}, | ||
@@ -62,0 +63,0 @@ "peerDependencies": { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
55827
1599
3
9
1
+ Addedembla-carousel@8.0.0-rc01(transitive)
+ Addedembla-carousel-reactive-utils@8.0.0-rc01(transitive)
- Removedembla-carousel@7.1.0(transitive)
Updatedembla-carousel@8.0.0-rc01