UNPKG

@exadel/esl

Version:

Exadel Smart Library (ESL) is the lightweight custom elements library that provide a set of super-flexible components

85 lines (84 loc) 3.6 kB
import { createDeferred } from '../../esl-utils/async'; import { copyDefinedKeys } from '../../esl-utils/misc/object'; import { keepPosition } from './incremental-scroll-align-strategies'; import { ESLIncrementalScroller } from './incremental-scroll-scroller'; const animationFrames = new WeakMap(); const defaultOptions = { alignment: { x: keepPosition, y: keepPosition }, offset: 0, stabilityThreshold: 500, timeout: 4000 }; /** * ESLIncrementalScroll class provides static method to perform incremental scroll to a target element. * It continuously recalculates the target position on each animation frame to handle dynamic content and animations. */ export class ESLIncrementalScroll { /** * Returns a copy of current default options for incremental scroll. * @returns Current default options */ static get defaults() { return Object.assign({}, defaultOptions); } /** * Sets default options for incremental scroll. * Only defined values from overrides will be applied. * @param overrides - Partial options to override defaults */ static set defaults(overrides) { Object.assign(defaultOptions, copyDefinedKeys(overrides)); } /** * Performs incremental scroll to bring an element into view with smooth animation. * Continuously recalculates target position on each frame to handle dynamic content and animations. * @param $el - Target element to scroll to, or null to scroll based on alignment strategy only * @param options - Scroll configuration options * @returns Promise that resolves when scroll completes or rejects if aborted */ static to($el, options = {}) { var _a; const deferred = createDeferred(); let requestId; const opts = Object.assign(Object.assign({}, defaultOptions), options); const scrollContainer = opts.scrollContainer || window; const scroller = new ESLIncrementalScroller($el, opts); function signalCallback(e) { var _a; (_a = opts.signal) === null || _a === void 0 ? void 0 : _a.removeEventListener('abort', signalCallback); if (requestId) cancelAnimationFrame(requestId); if (e) rejectOnSignal(); // Only reject if called by abort event } function rejectOnSignal() { animationFrames.delete(scrollContainer); deferred.reject(new DOMException('Aborted', 'AbortError')); } (_a = opts.signal) === null || _a === void 0 ? void 0 : _a.addEventListener('abort', signalCallback); const existingRequestId = animationFrames.get(scrollContainer); if (existingRequestId) cancelAnimationFrame(existingRequestId); // schedule step task requestId = requestAnimationFrame(function step() { var _a, _b; if ((_a = opts.signal) === null || _a === void 0 ? void 0 : _a.aborted) return rejectOnSignal(); scroller.step(); if (scroller.shouldContinue) { requestId = requestAnimationFrame(step); animationFrames.set(scrollContainer, requestId); } else { (_b = opts.signal) === null || _b === void 0 ? void 0 : _b.removeEventListener('abort', signalCallback); animationFrames.delete(scrollContainer); deferred.resolve(); } }); animationFrames.set(scrollContainer, requestId); return deferred.promise; } }