@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
JavaScript
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;
}
}