{"version":3,"sources":["/Users/stefansteinhart/Development/Web/ios-html5-drag-drop-shim/src/scroll-behaviour.ts"],"names":["isTopLevelEl","el","document","body","documentElement","getElementViewportOffset","axis","offset","clientLeft","clientTop","bounds","getBoundingClientRect","left","top","getElementViewportSize","size","window","innerWidth","innerHeight","clientWidth","clientHeight","getSetElementScroll","scroll","prop","isTopLevel","arguments","length","isScrollable","cs","getComputedStyle","scrollHeight","overflowY","scrollWidth","overflowX","findScrollableParent","parentNode","determineScrollIntention","currentCoordinate","threshold","determineDynamicVelocity","scrollIntention","Math","abs","isScrollEndReached","scrollBounds","currentScrollOffset","maxScrollOffset","width","height","handleDragImageTranslateOverride","event","currentCoordinates","hoveredElement","translateDragImageFn","_currentCoordinates","_translateDragImageFn","_hoveredElement","_scrollableParent","performScrollAnimation","updateScrollIntentions","_options","_scrollIntentions","_dynamicVelocity","scheduleScrollAnimation","_scrollAnimationFrameId","cancelAnimationFrame","requestAnimationFrame","scrollAnimation","scrollDiffX","scrollDiffY","horizontal","round","velocityFn","x","vertical","y","scrollableParent","scrollIntentions","dynamicVelocity","scrollableParentBounds","scrollX","scrollY","currentCoordinatesOffset","velocity","multiplier","easeInCubic","scrollBehaviourDragImageTranslateOverride"],"mappings":"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","file":"scroll-behaviour.min.js","sourcesContent":["//\n\nimport {DragImageTranslateOverrideFn, Point} from \"./index\";\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"]}