{"version":3,"file":"embla-carousel-wheel-gestures.umd.js","sources":["../src/WheelGesturesPlugin.ts"],"sourcesContent":["import { CreateOptionsType, CreatePluginType, EmblaCarouselType, OptionsHandlerType } from 'embla-carousel'\nimport WheelGestures, { WheelEventState } from 'wheel-gestures'\n\nexport type WheelGesturesPluginOptions = CreateOptionsType<{\n wheelDraggingClass: string\n forceWheelAxis?: 'x' | 'y'\n target?: Element\n}>\n\ntype WheelGesturesPluginType = CreatePluginType<{}, WheelGesturesPluginOptions>\n\nconst defaultOptions: WheelGesturesPluginOptions = {\n active: true,\n breakpoints: {},\n wheelDraggingClass: 'is-wheel-dragging',\n forceWheelAxis: undefined,\n target: undefined,\n}\n\nWheelGesturesPlugin.globalOptions = undefined as WheelGesturesPluginType['options'] | undefined\n\nconst __DEV__ = process.env.NODE_ENV !== 'production'\n\nexport function WheelGesturesPlugin(userOptions: WheelGesturesPluginType['options'] = {}): WheelGesturesPluginType {\n let options: WheelGesturesPluginOptions\n let cleanup = () => {}\n\n function init(embla: EmblaCarouselType, optionsHandler: OptionsHandlerType) {\n const { mergeOptions, optionsAtMedia } = optionsHandler\n const optionsBase = mergeOptions(defaultOptions, WheelGesturesPlugin.globalOptions)\n const allOptions = mergeOptions(optionsBase, userOptions)\n options = optionsAtMedia(allOptions)\n\n const engine = embla.internalEngine()\n const targetNode = options.target ?? (embla.containerNode().parentNode as Element)\n const wheelAxis = options.forceWheelAxis ?? engine.options.axis\n const wheelGestures = WheelGestures({\n preventWheelAction: wheelAxis,\n reverseSign: [true, true, false],\n })\n\n function updateSizeRelatedVariables() {\n scrollBoundaryThreshold = (wheelAxis === 'x' ? engine.containerRect.width : engine.containerRect.height) / 2\n }\n\n const unobserveTargetNode = wheelGestures.observe(targetNode)\n const offWheel = wheelGestures.on('wheel', handleWheel)\n\n let isStarted = false\n let startEvent: MouseEvent\n let overBoundaryAccumulation = 0\n let scrollBoundaryThreshold = 0\n let blockedWaitUntilGestureEnd = false\n\n updateSizeRelatedVariables()\n embla.on('resize', updateSizeRelatedVariables)\n\n function wheelGestureStarted(state: WheelEventState) {\n try {\n startEvent = new MouseEvent('mousedown', state.event)\n dispatchEvent(startEvent)\n } catch (e) {\n // Legacy Browsers like IE 10 & 11 will throw when attempting to create the Event\n if (__DEV__) {\n console.warn(\n 'Legacy browser requires events-polyfill (https://github.com/xiel/embla-carousel-wheel-gestures#legacy-browsers)'\n )\n }\n return cleanup()\n }\n\n isStarted = true\n overBoundaryAccumulation = 0\n addNativeMouseEventListeners()\n\n if (options.wheelDraggingClass) {\n targetNode.classList.add(options.wheelDraggingClass)\n }\n }\n\n function wheelGestureEnded(state: WheelEventState) {\n isStarted = false\n dispatchEvent(createRelativeMouseEvent('mouseup', state))\n removeNativeMouseEventListeners()\n\n if (options.wheelDraggingClass) {\n targetNode.classList.remove(options.wheelDraggingClass)\n }\n }\n\n function addNativeMouseEventListeners() {\n document.documentElement.addEventListener('mousemove', preventNativeMouseHandler, true)\n document.documentElement.addEventListener('mouseup', preventNativeMouseHandler, true)\n document.documentElement.addEventListener('mousedown', preventNativeMouseHandler, true)\n }\n\n function removeNativeMouseEventListeners() {\n document.documentElement.removeEventListener('mousemove', preventNativeMouseHandler, true)\n document.documentElement.removeEventListener('mouseup', preventNativeMouseHandler, true)\n document.documentElement.removeEventListener('mousedown', preventNativeMouseHandler, true)\n }\n\n function preventNativeMouseHandler(e: MouseEvent) {\n if (isStarted && e.isTrusted) {\n e.stopImmediatePropagation()\n }\n }\n\n function createRelativeMouseEvent(type: 'mousedown' | 'mousemove' | 'mouseup', state: WheelEventState) {\n let moveX, moveY\n\n if (wheelAxis === engine.options.axis) {\n ;[moveX, moveY] = state.axisMovement\n } else {\n // if emblas axis and the wheelAxis don't match, swap the axes to match the right embla events\n ;[moveY, moveX] = state.axisMovement\n }\n\n const { isAtBoundary } = checkIfAtBoundary(state)\n\n // Apply progressive rubber band damping when at boundaries\n if (isAtBoundary) {\n // Calculate progressive damping factor based on how far over boundary we are\n const progressRatio = Math.min(overBoundaryAccumulation / scrollBoundaryThreshold, 1)\n const dampingFactor = 0.25 + progressRatio * 0.5\n const counterMoveSign = moveX > 0 ? -1 : 1\n const counterMovement = overBoundaryAccumulation * counterMoveSign\n const dampingMovement = counterMovement * dampingFactor\n\n moveX += dampingMovement\n moveY += dampingMovement\n }\n\n // prevent skipping slides\n if (!engine.options.skipSnaps && !engine.options.dragFree) {\n const maxX = engine.containerRect.width\n const maxY = engine.containerRect.height\n\n moveX = moveX < 0 ? Math.max(moveX, -maxX) : Math.min(moveX, maxX)\n moveY = moveY < 0 ? Math.max(moveY, -maxY) : Math.min(moveY, maxY)\n }\n\n return new MouseEvent(type, {\n clientX: startEvent.clientX + moveX,\n clientY: startEvent.clientY + moveY,\n screenX: startEvent.screenX + moveX,\n screenY: startEvent.screenY + moveY,\n movementX: moveX,\n movementY: moveY,\n button: 0,\n bubbles: true,\n cancelable: true,\n composed: true,\n })\n }\n\n function dispatchEvent(event: UIEvent) {\n embla.containerNode().dispatchEvent(event)\n }\n\n function checkIfAtBoundary(state: WheelEventState) {\n const {\n axisDelta: [deltaX, deltaY],\n } = state\n const scrollProgress = embla.scrollProgress()\n const canScrollNext = scrollProgress < 1\n const canScrollPrev = scrollProgress > 0\n const primaryAxisDelta = wheelAxis === 'x' ? deltaX : deltaY\n const isScrollingNext = primaryAxisDelta < 0\n const isScrollingPrev = primaryAxisDelta > 0\n const isAtBoundary = (isScrollingNext && !canScrollNext) || (isScrollingPrev && !canScrollPrev)\n\n return {\n isAtBoundary,\n primaryAxisDelta,\n }\n }\n\n function isBoundaryThresholdReached(state: WheelEventState) {\n const { isAtBoundary, primaryAxisDelta } = checkIfAtBoundary(state)\n\n if (isAtBoundary && !state.isMomentum) {\n overBoundaryAccumulation += Math.abs(primaryAxisDelta)\n\n // End gesture if we exceed the threshold\n if (overBoundaryAccumulation > scrollBoundaryThreshold) {\n blockedWaitUntilGestureEnd = true\n wheelGestureEnded(state)\n return true\n }\n } else {\n // Reset accumulation when we can scroll or when not at boundary\n overBoundaryAccumulation = 0\n }\n\n return false\n }\n\n function handleWheel(state: WheelEventState) {\n const {\n axisDelta: [deltaX, deltaY],\n } = state\n const primaryAxisDelta = wheelAxis === 'x' ? deltaX : deltaY\n const crossAxisDelta = wheelAxis === 'x' ? deltaY : deltaX\n const isRelease = state.isMomentum && state.previous && !state.previous.isMomentum\n const isEndingOrRelease = (state.isEnding && !state.isMomentum) || isRelease\n const primaryAxisDeltaIsDominant = Math.abs(primaryAxisDelta) > Math.abs(crossAxisDelta)\n\n if (primaryAxisDeltaIsDominant && !isStarted && !state.isMomentum && !blockedWaitUntilGestureEnd) {\n wheelGestureStarted(state)\n }\n\n if (blockedWaitUntilGestureEnd && state.isEnding) {\n blockedWaitUntilGestureEnd = false\n }\n\n if (!isStarted) return\n\n if (isBoundaryThresholdReached(state)) return\n\n if (isEndingOrRelease) {\n wheelGestureEnded(state)\n } else {\n dispatchEvent(createRelativeMouseEvent('mousemove', state))\n }\n }\n\n cleanup = () => {\n unobserveTargetNode()\n offWheel()\n embla.off('resize', updateSizeRelatedVariables)\n removeNativeMouseEventListeners()\n }\n }\n\n const self: WheelGesturesPluginType = {\n name: 'wheelGestures',\n options: userOptions,\n init,\n destroy: () => cleanup(),\n }\n return self\n}\n\ndeclare module 'embla-carousel' {\n interface EmblaPluginsType {\n wheelGestures?: WheelGesturesPluginType\n }\n}\n"],"names":["defaultOptions","active","breakpoints","wheelDraggingClass","forceWheelAxis","undefined","target","WheelGesturesPlugin","userOptions","options","cleanup","name","init","embla","optionsHandler","mergeOptions","optionsAtMedia","optionsBase","globalOptions","allOptions","engine","internalEngine","targetNode","containerNode","parentNode","wheelAxis","axis","wheelGestures","WheelGestures","preventWheelAction","reverseSign","updateSizeRelatedVariables","scrollBoundaryThreshold","containerRect","width","height","startEvent","unobserveTargetNode","observe","offWheel","on","state","axisDelta","deltaX","deltaY","crossAxisDelta","isEndingOrRelease","isEnding","isMomentum","previous","Math","abs","isStarted","blockedWaitUntilGestureEnd","dispatchEvent","MouseEvent","event","e","overBoundaryAccumulation","document","documentElement","addEventListener","preventNativeMouseHandler","classList","add","wheelGestureStarted","checkIfAtBoundary","isAtBoundary","primaryAxisDelta","wheelGestureEnded","isBoundaryThresholdReached","createRelativeMouseEvent","removeNativeMouseEventListeners","remove","removeEventListener","isTrusted","stopImmediatePropagation","type","moveX","moveY","axisMovement","progressRatio","min","dampingMovement","skipSnaps","dragFree","maxX","maxY","max","clientX","clientY","screenX","screenY","movementX","movementY","button","bubbles","cancelable","composed","scrollProgress","off","destroy"],"mappings":"u2BAWMA,EAA6C,CACjDC,QAAQ,EACRC,YAAa,GACbC,mBAAoB,oBACpBC,oBAAgBC,EAChBC,YAAQD,YAOME,EAAoBC,OAC9BC,WAD8BD,IAAAA,EAAkD,QAEhFE,EAAU,mBAkNwB,CACpCC,KAAM,gBACNF,QAASD,EACTI,cAnNYC,EAA0BC,WAC9BC,EAAiCD,EAAjCC,aAAcC,EAAmBF,EAAnBE,eAChBC,EAAcF,EAAaf,EAAgBO,EAAoBW,eAC/DC,EAAaJ,EAAaE,EAAaT,GAC7CC,EAAUO,EAAeG,OAEnBC,EAASP,EAAMQ,iBACfC,WAAab,EAAQH,UAAWO,EAAMU,gBAAgBC,WACtDC,WAAYhB,EAAQL,kBAAkBgB,EAAOX,QAAQiB,KACrDC,srIAAgBC,CAAc,CAClCC,mBAAoBJ,EACpBK,YAAa,EAAC,GAAM,GAAM,cAGnBC,IACPC,GAAyC,MAAdP,EAAoBL,EAAOa,cAAcC,MAAQd,EAAOa,cAAcE,QAAU,MAOzGC,EAJEC,EAAsBV,EAAcW,QAAQhB,GAC5CiB,EAAWZ,EAAca,GAAG,kBAwJbC,SAGfA,EADFC,UAAYC,OAAQC,OAGhBC,EAA+B,MAAdpB,EAAoBmB,EAASD,EAE9CG,EAAqBL,EAAMM,WAAaN,EAAMO,YADlCP,EAAMO,YAAcP,EAAMQ,WAAaR,EAAMQ,SAASD,WAErCE,KAAKC,IAJD,MAAd1B,EAAoBkB,EAASC,GAIUM,KAAKC,IAAIN,KAEtCO,IAAcX,EAAMO,aAAeK,YAvJ3CZ,OAGzBa,EADAlB,EAAa,IAAImB,WAAW,YAAad,EAAMe,QAE/C,MAAOC,UAOA/C,IAGT0C,GAAY,EACZM,EAA2B,EAmB3BC,SAASC,gBAAgBC,iBAAiB,YAAaC,GAA2B,GAClFH,SAASC,gBAAgBC,iBAAiB,UAAWC,GAA2B,GAChFH,SAASC,gBAAgBC,iBAAiB,YAAaC,GAA2B,GAlB9ErD,EAAQN,oBACVmB,EAAWyC,UAAUC,IAAIvD,EAAQN,oBAqIjC8D,CAAoBxB,GAGlBY,GAA8BZ,EAAMM,WACtCM,GAA6B,GAG1BD,aAtC6BX,SACSyB,EAAkBzB,QAArD0B,eAEa1B,EAAMO,gBACzBU,GAA4BR,KAAKC,MAHbiB,mBAMWpC,SAC7BqB,GAA6B,EAC7BgB,EAAkB5B,IACX,OAITiB,EAA2B,SAGtB,EAuBHY,CAA2B7B,KAE3BK,EACFuB,EAAkB5B,GAElBa,EAAciB,EAAyB,YAAa9B,SA/KpDW,GAAY,EAEZM,EAA2B,EAC3B1B,EAA0B,EAC1BqB,GAA6B,WA4BxBgB,EAAkB5B,GACzBW,GAAY,EACZE,EAAciB,EAAyB,UAAW9B,IAClD+B,IAEI/D,EAAQN,oBACVmB,EAAWyC,UAAUU,OAAOhE,EAAQN,6BAU/BqE,IACPb,SAASC,gBAAgBc,oBAAoB,YAAaZ,GAA2B,GACrFH,SAASC,gBAAgBc,oBAAoB,UAAWZ,GAA2B,GACnFH,SAASC,gBAAgBc,oBAAoB,YAAaZ,GAA2B,YAG9EA,EAA0BL,GAC7BL,GAAaK,EAAEkB,WACjBlB,EAAEmB,oCAIGL,EAAyBM,EAA6CpC,OACzEqC,EAAOC,KAEPtD,IAAcL,EAAOX,QAAQiB,KAAM,OACnBe,EAAMuC,aAAtBF,OAAOC,WACJ,OAEatC,EAAMuC,aAAtBD,OAAOD,UAGcZ,EAAkBzB,GAAnC0B,aAGU,KAEVc,EAAgB/B,KAAKgC,IAAIxB,EAA2B1B,EAAyB,GAI7EmD,EADkBzB,GADAoB,EAAQ,GAAK,EAAI,IADnB,IAAuB,GAAhBG,GAK7BH,GAASK,EACTJ,GAASI,MAIN/D,EAAOX,QAAQ2E,YAAchE,EAAOX,QAAQ4E,SAAU,KACnDC,EAAOlE,EAAOa,cAAcC,MAC5BqD,EAAOnE,EAAOa,cAAcE,OAElC2C,EAAQA,EAAQ,EAAI5B,KAAKsC,IAAIV,GAAQQ,GAAQpC,KAAKgC,IAAIJ,EAAOQ,GAC7DP,EAAQA,EAAQ,EAAI7B,KAAKsC,IAAIT,GAAQQ,GAAQrC,KAAKgC,IAAIH,EAAOQ,UAGxD,IAAIhC,WAAWsB,EAAM,CAC1BY,QAASrD,EAAWqD,QAAUX,EAC9BY,QAAStD,EAAWsD,QAAUX,EAC9BY,QAASvD,EAAWuD,QAAUb,EAC9Bc,QAASxD,EAAWwD,QAAUb,EAC9Bc,UAAWf,EACXgB,UAAWf,EACXgB,OAAQ,EACRC,SAAS,EACTC,YAAY,EACZC,UAAU,aAIL5C,EAAcE,GACrB3C,EAAMU,gBAAgB+B,cAAcE,YAG7BU,EAAkBzB,SAGrBA,EADFC,UAAYC,OAAQC,OAEhBuD,EAAiBtF,EAAMsF,iBAGvB/B,EAAiC,MAAd3C,EAAoBkB,EAASC,QAK/C,CACLuB,aALsBC,EAAmB,KAHrB+B,EAAiB,IAIf/B,EAAmB,KAHrB+B,EAAiB,GAQrC/B,iBAAAA,GAxHJrC,IACAlB,EAAM2B,GAAG,SAAUT,GA4KnBrB,EAAU,WACR2B,IACAE,IACA1B,EAAMuF,IAAI,SAAUrE,GACpByC,MAQF6B,QAAS,kBAAM3F,aA5NnBH,EAAoBW,mBAAgBb"}