@shopgate/pwa-common
Version:
Common library for the Shopgate Connect PWA.
34 lines • 3.69 kB
JavaScript
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$;};