UNPKG

@shopgate/pwa-common

Version:

Common library for the Shopgate Connect PWA.

34 lines 3.69 kB
import{Observable}from'rxjs/Observable';import{Subject}from'rxjs/Subject';import{async as asyncScheduler}from'rxjs/scheduler/async';import'rxjs/add/observable/from';import'rxjs/add/observable/fromEvent';import'rxjs/add/operator/do';import'rxjs/add/operator/throttleTime';export var eventsSubject=new Subject();/** * Cold observable for View scroll events * Use this to listen for scroll-related changes in any part of the app. * @type {Observable} */export var viewScroll$=Observable.from(eventsSubject);/** * Sets up throttled scroll event stream for a given element or window. * Emits enriched scroll info such as direction and distance. * * @param {HTMLElement|Window} element - DOM node or window to observe * @param {number} throttleTime - Time in ms to throttle scroll events * @returns {Observable} - Observable emitting scroll-related data */export var emitScrollEvents=function emitScrollEvents(element){var throttleTime=arguments.length>1&&arguments[1]!==undefined?arguments[1]:250;// In rare situation during unmounting a react dom ref might // be null due to the execution order of events in fiber nodes. if(!element){return undefined;}var previousScrollTop=0;// Tracks scroll direction ('up' or 'down') var lastDirection=null;// Minimum distance to consider a real scroll var minDelta=10;// Pixels from bottom to consider "at bottom" var bottomThreshold=20;// Prevent scrollUp triggering in this zone var deadZoneThreshold=30;var scroll$=Observable.fromEvent(element,'scroll').throttleTime(throttleTime,asyncScheduler,{leading:false,trailing:true}).map(function(event){// Determine if element is the window/document or a scrollable container var isWindow=element===window||element===document.body||element===document.documentElement;// Get current scroll position var rawScrollTop=isWindow?window.scrollY||window.pageYOffset||0:element.scrollTop;// Compute max scroll value var maxScrollTop=isWindow?Math.max(document.documentElement.scrollHeight,document.body.scrollHeight)-window.innerHeight:element.scrollHeight-element.clientHeight;// Clamp scrollTop to prevent overshoot from iOS bounce var scrollTop=Math.min(rawScrollTop,maxScrollTop);var delta=scrollTop-previousScrollTop;var isScrollingDown=delta>0;var isScrollingUp=delta<0;// Determine direction var direction=lastDirection;if(isScrollingDown)direction='down';else if(isScrollingUp)direction='up';// Are we near the bottom of the scrollable area? var nearBottom=scrollTop>=maxScrollTop-bottomThreshold;var inDeadZone=scrollTop>=maxScrollTop-deadZoneThreshold;// Detect downward scroll beyond threshold and not near bottom var scrollDown=isScrollingDown&&delta>minDelta&&!nearBottom;// Detect upward scroll beyond threshold var scrollUp=isScrollingUp&&Math.abs(delta)>minDelta;// Suppress scrollUp events in dead zone to avoid iOS bounce if(scrollUp&&inDeadZone&&direction==='up'){scrollUp=false;}// Detect a bounce-back: scrollDown followed by a quick scrollUp at the bottom var bounced=lastDirection==='down'&&direction==='up'&&nearBottom;// Final scrolled flag — only emit if meaningful and not a bounce var scrolled=(scrollDown||scrollUp)&&!bounced;// Update direction and scrollTop memory lastDirection=direction;return{event:event,scrollTop:scrollTop,previousScrollTop:previousScrollTop,scrolled:scrolled,scrollUp:scrollUp,scrollDown:scrollDown&&!bounced,direction:direction,scrollOut:scrollDown&&!bounced,// legacy compatibility scrollIn:scrollUp// legacy compatibility };})["do"](function(event){// Store current scrollTop for next event comparison previousScrollTop=event.scrollTop;});// Pipe scroll data into the shared stream for global consumers scroll$.subscribe(viewScroll$);return scroll$;};