{"version":3,"sources":["scroll-behaviour.ts"],"names":["isTopLevelEl","el","document","body","documentElement","getElementViewportOffset","axis","offset","clientLeft","clientTop","bounds","getBoundingClientRect","left","top","getElementViewportSize","window","innerWidth","innerHeight","clientWidth","clientHeight","getSetElementScroll","scroll","prop","isTopLevel","arguments","length","determineScrollIntention","currentCoordinate","size","threshold","determineDynamicVelocity","scrollIntention","Math","abs","isScrollEndReached","scrollBounds","currentScrollOffset","scrollWidth","width","scrollHeight","height","_scrollAnimationFrameId","_currentCoordinates","_hoveredElement","_scrollableParent","_translateDragImageFn","_options","velocityFn","velocity","multiplier","_scrollIntentions","horizontal","vertical","_dynamicVelocity","x","y","scheduleScrollAnimation","requestAnimationFrame","scrollAnimation","scrollDiffX","scrollDiffY","round","updateScrollIntentions","currentCoordinates","scrollableParent","scrollIntentions","dynamicVelocity","scrollableParentBounds","scrollX","scrollY","currentCoordinatesOffset","scrollBehaviourDragImageTranslateOverride","event","hoveredElement","translateDragImageFn","cs","getComputedStyle","overflowY","overflowX","parentNode","findScrollableParent","cancelAnimationFrame"],"mappings":"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","file":"scroll-behaviour.min.js","sourcesContent":["//\n\nimport { DragImageTranslateOverrideFn } from \"./index\";\nimport { Point } from \"./internal/dom-utils\";\n\ninterface ScrollIntentions {\n horizontal:ScrollIntention;\n vertical:ScrollIntention;\n}\n\ninterface IScrollBounds {\n x:number;\n y:number;\n width:number;\n height:number;\n scrollX:number;\n scrollY:number;\n scrollHeight:number;\n scrollWidth:number;\n}\n\nconst enum ScrollIntention {\n NONE = 0,\n LEFT_OR_TOP = -1,\n RIGHT_OR_BOTTOM = 1\n}\n\nconst enum ScrollAxis {\n HORIZONTAL,\n VERTICAL\n}\n\nfunction isTopLevelEl( el:HTMLElement ):boolean {\n\n return (el === document.body || el === document.documentElement);\n}\n\nfunction getElementViewportOffset( el:HTMLElement, axis:ScrollAxis ) {\n let offset:number;\n\n if( isTopLevelEl( el ) ) {\n offset = (axis === ScrollAxis.HORIZONTAL) ? el.clientLeft : el.clientTop;\n }\n else {\n const bounds = el.getBoundingClientRect();\n offset = (axis === ScrollAxis.HORIZONTAL) ? bounds.left : bounds.top;\n }\n\n return offset;\n}\n\nfunction getElementViewportSize( el:HTMLElement, axis:ScrollAxis ) {\n let size:number;\n\n if( isTopLevelEl( el ) ) {\n size = (axis === ScrollAxis.HORIZONTAL) ? window.innerWidth : window.innerHeight;\n }\n else {\n size = (axis === ScrollAxis.HORIZONTAL) ? el.clientWidth : el.clientHeight;\n }\n\n return size;\n}\n\nfunction getSetElementScroll( el:HTMLElement, axis:ScrollAxis, scroll?:number ) {\n const prop = (axis === ScrollAxis.HORIZONTAL) ? \"scrollLeft\" : \"scrollTop\";\n\n // abstracting away compatibility issues on scroll properties of document/body\n const isTopLevel = isTopLevelEl( el );\n\n if( arguments.length === 2 ) {\n\n if( isTopLevel ) {\n return document.body[ prop ] || document.documentElement[ prop ];\n }\n\n return el[ prop ];\n }\n\n if( isTopLevel ) {\n document.documentElement[ prop ] += scroll;\n document.body[ prop ] += scroll;\n }\n else {\n el[ prop ] += scroll;\n }\n}\n\n//TODO check if scroll end is reached according to scroll intention? this is needed to implement scroll chaining\nfunction isScrollable( el:HTMLElement ):boolean {\n const cs = getComputedStyle( el );\n\n if( el.scrollHeight > el.clientHeight && (cs.overflowY === \"scroll\" || cs.overflowY === \"auto\") ) {\n return true;\n }\n\n if( el.scrollWidth > el.clientWidth && (cs.overflowX === \"scroll\" || cs.overflowX === \"auto\") ) {\n return true;\n }\n\n return false;\n}\n\nfunction findScrollableParent( el:HTMLElement ):HTMLElement {\n do {\n if( !el ) {\n return undefined;\n }\n if( isScrollable( el ) ) {\n return el;\n }\n if( el === document.documentElement ) {\n return null;\n }\n } while( el = el.parentNode );\n return null;\n}\n\nfunction determineScrollIntention( currentCoordinate:number, size:number, threshold:number ):ScrollIntention {\n\n // LEFT / TOP\n if( currentCoordinate < threshold ) {\n return ScrollIntention.LEFT_OR_TOP;\n }\n // RIGHT / BOTTOM\n else if( size - currentCoordinate < threshold ) {\n return ScrollIntention.RIGHT_OR_BOTTOM;\n }\n // NONE\n return ScrollIntention.NONE;\n}\n\nfunction determineDynamicVelocity( scrollIntention:ScrollIntention, currentCoordinate:number, size:number, threshold:number ):number {\n\n if( scrollIntention === ScrollIntention.LEFT_OR_TOP ) {\n\n return Math.abs( currentCoordinate - threshold );\n }\n else if( scrollIntention === ScrollIntention.RIGHT_OR_BOTTOM ) {\n\n return Math.abs( size - currentCoordinate - threshold );\n }\n\n return 0;\n}\n\nfunction isScrollEndReached( axis:ScrollAxis, scrollIntention:ScrollIntention, scrollBounds:IScrollBounds ) {\n\n const currentScrollOffset = (axis === ScrollAxis.HORIZONTAL) ? (scrollBounds.scrollX) : (scrollBounds.scrollY);\n\n // wants to scroll to the right/bottom\n if( scrollIntention === ScrollIntention.RIGHT_OR_BOTTOM ) {\n\n const maxScrollOffset = (axis === ScrollAxis.HORIZONTAL) ? (scrollBounds.scrollWidth - scrollBounds.width) : (scrollBounds.scrollHeight -\n scrollBounds.height);\n\n // is already at the right/bottom edge\n return currentScrollOffset >= maxScrollOffset;\n }\n // wants to scroll to the left/top\n else if( scrollIntention === ScrollIntention.LEFT_OR_TOP ) {\n\n // is already at left/top edge\n return (currentScrollOffset <= 0);\n }\n // no scroll\n return true;\n}\n\n//\n\nlet _options:ScrollOptions = {\n threshold: 75,\n // simplified cubic-ease-in function\n velocityFn: function( velocity:number, threshold:number ) {\n const multiplier = velocity / threshold;\n const easeInCubic = multiplier * multiplier * multiplier;\n return easeInCubic * threshold;\n }\n};\n\nlet _scrollIntentions:ScrollIntentions = {\n horizontal: ScrollIntention.NONE,\n vertical: ScrollIntention.NONE\n};\n\nlet _dynamicVelocity:Point = {\n x: 0,\n y: 0\n};\n\nlet _scrollAnimationFrameId:any;\nlet _currentCoordinates:Point;\nlet _hoveredElement:HTMLElement;\nlet _scrollableParent:HTMLElement;\nlet _translateDragImageFn:( offsetX:number, offsetY:number ) => void;\n\n/**\n * core handler function\n */\nfunction handleDragImageTranslateOverride( event:TouchEvent,\n currentCoordinates:Point,\n hoveredElement:HTMLElement,\n translateDragImageFn:( scrollDiffX:number, scrollDiffY:number ) => void ):void {\n\n _currentCoordinates = currentCoordinates;\n _translateDragImageFn = translateDragImageFn;\n\n // update parent if hovered element changed\n if( _hoveredElement !== hoveredElement ) {\n\n _hoveredElement = hoveredElement;\n _scrollableParent = findScrollableParent( _hoveredElement );\n }\n\n // update scroll intention and check if we should scroll at all\n //TODO implement scroll chaining? if scroll end is reached continue to look for scrollable parent\n const performScrollAnimation = updateScrollIntentions( _currentCoordinates, _scrollableParent, _options.threshold, _scrollIntentions, _dynamicVelocity );\n\n // no animation in progress but scroll is intended\n if( performScrollAnimation ) {\n\n // setup scroll animation frame\n scheduleScrollAnimation();\n }\n else if( !!_scrollAnimationFrameId ) {\n\n window.cancelAnimationFrame( _scrollAnimationFrameId );\n _scrollAnimationFrameId = null;\n }\n}\n\n//\n\nfunction scheduleScrollAnimation() {\n\n // prevent scheduling when already scheduled\n if( !!_scrollAnimationFrameId ) {\n\n return;\n }\n\n _scrollAnimationFrameId = window.requestAnimationFrame( scrollAnimation );\n}\n\nfunction scrollAnimation() {\n\n let scrollDiffX = 0,\n scrollDiffY = 0,\n isTopLevel = isTopLevelEl( _scrollableParent );\n\n if( _scrollIntentions.horizontal !== ScrollIntention.NONE ) {\n\n scrollDiffX = Math.round( _options.velocityFn( _dynamicVelocity.x, _options.threshold ) * _scrollIntentions.horizontal );\n getSetElementScroll( _scrollableParent, ScrollAxis.HORIZONTAL, scrollDiffX );\n }\n\n if( _scrollIntentions.vertical !== ScrollIntention.NONE ) {\n\n scrollDiffY = Math.round( _options.velocityFn( _dynamicVelocity.y, _options.threshold ) * _scrollIntentions.vertical );\n getSetElementScroll( _scrollableParent, ScrollAxis.VERTICAL, scrollDiffY );\n }\n\n if( isTopLevel ) {\n // on top level element scrolling we need to translate the drag image as much as we scroll\n _translateDragImageFn( scrollDiffX, scrollDiffY );\n }\n else {\n // just scroll the container and update the drag image position without offset\n _translateDragImageFn( 0, 0 );\n }\n\n // reset to make sure we can re-schedule scroll animation\n _scrollAnimationFrameId = null;\n\n // check if we should continue scrolling\n //TODO implement scroll chaining? if scroll end is reached continue to look for scrollable parent\n if( updateScrollIntentions( _currentCoordinates, _scrollableParent, _options.threshold, _scrollIntentions, _dynamicVelocity ) ) {\n\n // re-schedule animation frame callback\n scheduleScrollAnimation();\n }\n}\n\n//\n\n//\n\nfunction updateScrollIntentions( currentCoordinates:Point,\n scrollableParent:HTMLElement,\n threshold:number,\n scrollIntentions:ScrollIntentions,\n dynamicVelocity:Point ):boolean {\n\n if( !currentCoordinates || !scrollableParent ) {\n\n // when coordinates become undefined drag operation stopped. stop scrolling also.\n return false;\n }\n\n const scrollableParentBounds:IScrollBounds = {\n x: getElementViewportOffset( scrollableParent, ScrollAxis.HORIZONTAL ),\n y: getElementViewportOffset( scrollableParent, ScrollAxis.VERTICAL ),\n width: getElementViewportSize( scrollableParent, ScrollAxis.HORIZONTAL ),\n height: getElementViewportSize( scrollableParent, ScrollAxis.VERTICAL ),\n scrollX: getSetElementScroll( scrollableParent, ScrollAxis.HORIZONTAL ),\n scrollY: getSetElementScroll( scrollableParent, ScrollAxis.VERTICAL ),\n scrollWidth: scrollableParent.scrollWidth,\n scrollHeight: scrollableParent.scrollHeight\n };\n\n const currentCoordinatesOffset = {\n x: currentCoordinates.x - scrollableParentBounds.x,\n y: currentCoordinates.y - scrollableParentBounds.y\n };\n\n scrollIntentions.horizontal = determineScrollIntention( currentCoordinatesOffset.x, scrollableParentBounds.width, threshold );\n scrollIntentions.vertical = determineScrollIntention( currentCoordinatesOffset.y, scrollableParentBounds.height, threshold );\n\n if( scrollIntentions.horizontal && isScrollEndReached( ScrollAxis.HORIZONTAL, scrollIntentions.horizontal, scrollableParentBounds ) ) {\n\n // if scroll end is reached, reset to none\n scrollIntentions.horizontal = ScrollIntention.NONE;\n }\n else if( scrollIntentions.horizontal ) {\n\n dynamicVelocity.x = determineDynamicVelocity( scrollIntentions.horizontal, currentCoordinatesOffset.x, scrollableParentBounds.width, threshold );\n }\n\n if( scrollIntentions.vertical && isScrollEndReached( ScrollAxis.VERTICAL, scrollIntentions.vertical, scrollableParentBounds ) ) {\n\n // if scroll end is reached, reset to none\n scrollIntentions.vertical = ScrollIntention.NONE;\n }\n else if( scrollIntentions.vertical ) {\n\n dynamicVelocity.y = determineDynamicVelocity( scrollIntentions.vertical, currentCoordinatesOffset.y, scrollableParentBounds.height, threshold );\n }\n\n return !!(scrollIntentions.horizontal || scrollIntentions.vertical);\n}\n\n//\n\n//\n\nexport interface ScrollOptions {\n // threshold in px. when distance between scrollable element edge and touch position is smaller start programmatic scroll.\n // defaults to 75px\n threshold?:number;\n // function to customize the scroll velocity\n // velocity param: distance to scrollable element edge\n // threshold: the threshold used to determine when scrolling should start\n // defaults to cubic-ease-in.\n velocityFn:( velocity:number, threshold:number ) => number;\n}\n\nexport const scrollBehaviourDragImageTranslateOverride:DragImageTranslateOverrideFn = handleDragImageTranslateOverride;\n\n//\n"]}