Telegram Web K with changes to work inside I2P https://web.telegram.i2p/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

462 lines
13 KiB

// credits to https://github.com/iamdustan/smoothscroll
type ScrollableElement = (Window & typeof globalThis) | Element;
export type SmoothScrollToOptions = Partial<{
top: number,
left: number,
behavior: 'smooth' | 'auto' | 'instant',
scrollTime: number
}>;
export const SCROLL_TIME = 468;
// polyfill
export default function polyfill() {
// aliases
var w = window;
var d = document;
// return if scroll behavior is supported and polyfill is not forced
if(
'scrollBehavior' in d.documentElement.style &&
(w as any).__forceSmoothScrollPolyfill__ !== true
) {
return;
}
// globals
var Element = w.HTMLElement || w.Element;
// object gathering original scroll methods
var original = {
scroll: w.scroll || w.scrollTo,
scrollBy: w.scrollBy,
elementScroll: Element.prototype.scroll || scrollElement,
scrollIntoView: Element.prototype.scrollIntoView
};
// define timing method
var now =
w.performance && w.performance.now
? w.performance.now.bind(w.performance)
: Date.now;
/**
* indicates if a the current browser is made by Microsoft
* @method isMicrosoftBrowser
* @param {String} userAgent
* @returns {Boolean}
*/
function isMicrosoftBrowser(userAgent: string) {
var userAgentPatterns = ['MSIE ', 'Trident/', 'Edge/'];
return new RegExp(userAgentPatterns.join('|')).test(userAgent);
}
/*
* IE has rounding bug rounding down clientHeight and clientWidth and
* rounding up scrollHeight and scrollWidth causing false positives
* on hasScrollableSpace
*/
var ROUNDING_TOLERANCE = isMicrosoftBrowser(w.navigator.userAgent) ? 1 : 0;
/**
* changes scroll position inside an element
* @method scrollElement
* @param {Number} x
* @param {Number} y
* @returns {undefined}
*/
function scrollElement(this: Element, x: number, y: number) {
this.scrollLeft = x;
this.scrollTop = y;
}
/**
* returns result of applying ease math function to a number
* @method ease
* @param {Number} k
* @returns {Number}
*/
function ease(k: number) {
return 0.5 * (1 - Math.cos(Math.PI * k));
}
/**
* indicates if a smooth behavior should be applied
* @method shouldBailOut
* @param {Number|Object} firstArg
* @returns {Boolean}
*/
function shouldBailOut(firstArg: SmoothScrollToOptions) {
if(
firstArg === null ||
typeof firstArg !== 'object' ||
firstArg.behavior === undefined ||
firstArg.behavior === 'auto' ||
firstArg.behavior === 'instant'
) {
// first argument is not an object/null
// or behavior is auto, instant or undefined
return true;
}
if(typeof firstArg === 'object' && firstArg.behavior === 'smooth') {
// first argument is an object and behavior is smooth
return false;
}
// throw error when behavior is not supported
throw new TypeError(
'behavior member of ScrollOptions ' +
firstArg.behavior +
' is not a valid value for enumeration ScrollBehavior.'
);
}
/**
* indicates if an element has scrollable space in the provided axis
* @method hasScrollableSpace
* @param {Node} el
* @param {String} axis
* @returns {Boolean}
*/
function hasScrollableSpace(el: Element, axis: 'X' | 'Y') {
if(axis === 'Y') {
return el.clientHeight + ROUNDING_TOLERANCE < el.scrollHeight;
}
if(axis === 'X') {
return el.clientWidth + ROUNDING_TOLERANCE < el.scrollWidth;
}
}
/**
* indicates if an element has a scrollable overflow property in the axis
* @method canOverflow
* @param {Node} el
* @param {String} axis
* @returns {Boolean}
*/
function canOverflow(el: Element, axis: string) {
// @ts-ignore
var overflowValue: string = w.getComputedStyle(el, null)['overflow' + axis];
return overflowValue === 'auto' || overflowValue === 'scroll';
}
/**
* indicates if an element can be scrolled in either axis
* @method isScrollable
* @param {Node} el
* @param {String} axis
* @returns {Boolean}
*/
function isScrollable(el: Element) {
var isScrollableY = hasScrollableSpace(el, 'Y') && canOverflow(el, 'Y');
var isScrollableX = hasScrollableSpace(el, 'X') && canOverflow(el, 'X');
return isScrollableY || isScrollableX;
}
/**
* finds scrollable parent of an element
* @method findScrollableParent
* @param {Node} el
* @returns {Node} el
*/
function findScrollableParent(el: Element) {
while(el !== d.body && isScrollable(el) === false) {
// @ts-ignore
el = el.parentNode || el.host;
}
return el;
}
/**
* self invoked function that, given a context, steps through scrolling
* @method step
* @param {Object} context
* @returns {undefined}
*/
function step(context: {
startTime: number,
scrollTime: number,
startX: number,
startY: number,
x: number,
y: number,
scrollable: ScrollableElement,
method: (this: ScrollableElement, currentX: number, currentY: number) => any
}) {
var time = now();
var value: number;
var currentX: number;
var currentY: number;
var elapsed = (time - context.startTime) / context.scrollTime;
// avoid elapsed times higher than one
elapsed = elapsed > 1 ? 1 : elapsed;
// apply easing to elapsed time
value = ease(elapsed);
currentX = context.startX + (context.x - context.startX) * value;
currentY = context.startY + (context.y - context.startY) * value;
context.method.call(context.scrollable, currentX, currentY);
// scroll more if we have not reached our destination
if(currentX !== context.x || currentY !== context.y) {
w.requestAnimationFrame(step.bind(w, context));
}
}
/**
* scrolls window or element with a smooth behavior
* @method smoothScroll
* @param {Object|Node} el
* @param {Number} x
* @param {Number} y
* @returns {undefined}
*/
function smoothScroll(el: Element, x: number, y: number, scrollTime = SCROLL_TIME) {
var scrollable: ScrollableElement;
var startX: number;
var startY: number;
var method: any;
var startTime = now();
// define scroll context
if(el === d.body) {
scrollable = w;
startX = w.scrollX || w.pageXOffset;
startY = w.scrollY || w.pageYOffset;
method = original.scroll;
} else {
scrollable = el;
startX = el.scrollLeft;
startY = el.scrollTop;
method = scrollElement;
}
// scroll looping over a frame
step({
scrollable: scrollable,
method: method,
scrollTime,
startTime: startTime,
startX: startX,
startY: startY,
x: x,
y: y
});
}
// ORIGINAL METHODS OVERRIDES
// w.scroll and w.scrollTo
w.scroll = w.scrollTo = function() {
const options = arguments[0] as SmoothScrollToOptions;
// avoid action when no arguments are passed
if(options === undefined) {
return;
}
// avoid smooth behavior if not required
if(shouldBailOut(options) === true) {
original.scroll.call(
w,
options.left !== undefined
? options.left
: typeof options !== 'object'
? options
: w.scrollX || w.pageXOffset,
// use top prop, second argument if present or fallback to scrollY
options.top !== undefined
? options.top
: arguments[1] !== undefined
? arguments[1]
: w.scrollY || w.pageYOffset
);
return;
}
// LET THE SMOOTHNESS BEGIN!
smoothScroll.call(
w,
d.body,
options.left !== undefined
? ~~options.left
: w.scrollX || w.pageXOffset,
options.top !== undefined
? ~~options.top
: w.scrollY || w.pageYOffset,
options.scrollTime
);
};
// w.scrollBy
w.scrollBy = function() {
const options = arguments[0] as SmoothScrollToOptions;
// avoid action when no arguments are passed
if(options === undefined) {
return;
}
// avoid smooth behavior if not required
if(shouldBailOut(options)) {
original.scrollBy.call(
w,
options.left !== undefined
? options.left
: typeof options !== 'object' ? options : 0,
options.top !== undefined
? options.top
: arguments[1] !== undefined ? arguments[1] : 0
);
return;
}
// LET THE SMOOTHNESS BEGIN!
smoothScroll.call(
w,
d.body,
~~options.left + (w.scrollX || w.pageXOffset),
~~options.top + (w.scrollY || w.pageYOffset),
options.scrollTime
);
};
// Element.prototype.scroll and Element.prototype.scrollTo
Element.prototype.scroll = Element.prototype.scrollTo = function() {
const options = arguments[0] as SmoothScrollToOptions;
// avoid action when no arguments are passed
if(options === undefined) {
return;
}
// avoid smooth behavior if not required
if(shouldBailOut(options) === true) {
// if one number is passed, throw error to match Firefox implementation
if(typeof options === 'number' && arguments[1] === undefined) {
throw new SyntaxError('Value could not be converted');
}
original.elementScroll.call(
this,
// use left prop, first number argument or fallback to scrollLeft
options.left !== undefined
? ~~options.left
: typeof options !== 'object' ? ~~options : this.scrollLeft,
// use top prop, second argument or fallback to scrollTop
options.top !== undefined
? ~~options.top
: arguments[1] !== undefined ? ~~arguments[1] : this.scrollTop
);
return;
}
var left = options.left;
var top = options.top;
// LET THE SMOOTHNESS BEGIN!
smoothScroll.call(
this,
this,
typeof left === 'undefined' ? this.scrollLeft : ~~left,
typeof top === 'undefined' ? this.scrollTop : ~~top,
options.scrollTime
);
};
// Element.prototype.scrollBy
Element.prototype.scrollBy = function() {
const options = arguments[0] as SmoothScrollToOptions;
// avoid action when no arguments are passed
if(options === undefined) {
return;
}
// avoid smooth behavior if not required
if(shouldBailOut(options) === true) {
original.elementScroll.call(
this,
options.left !== undefined
? ~~options.left + this.scrollLeft
: ~~options + this.scrollLeft,
options.top !== undefined
? ~~options.top + this.scrollTop
: ~~arguments[1] + this.scrollTop
);
return;
}
this.scroll({
left: ~~options.left + this.scrollLeft,
top: ~~options.top + this.scrollTop,
behavior: options.behavior as any,
scrollTime: options.scrollTime
} as any);
};
// Element.prototype.scrollIntoView
Element.prototype.scrollIntoView = function() {
const options = arguments[0] as SmoothScrollToOptions;
// avoid smooth behavior if not required
if(shouldBailOut(options) === true) {
original.scrollIntoView.call(
this,
(options === undefined ? true : options) as any
);
return;
}
// LET THE SMOOTHNESS BEGIN!
var scrollableParent = findScrollableParent(this);
var parentRects = scrollableParent.getBoundingClientRect();
var clientRects = this.getBoundingClientRect();
if(scrollableParent !== d.body) {
// reveal element inside parent
smoothScroll.call(
this,
scrollableParent,
scrollableParent.scrollLeft + clientRects.left - parentRects.left,
scrollableParent.scrollTop + clientRects.top - parentRects.top,
options.scrollTime
);
// reveal parent in viewport unless is fixed
if(w.getComputedStyle(scrollableParent).position !== 'fixed') {
w.scrollBy({
left: parentRects.left,
top: parentRects.top,
behavior: 'smooth',
scrollTime: options.scrollTime
} as any);
}
} else {
// reveal element in viewport
w.scrollBy({
left: clientRects.left,
top: clientRects.top,
behavior: 'smooth',
scrollTime: options.scrollTime
} as any);
}
};
}
/* if (typeof exports === 'object' && typeof module !== 'undefined') {
// commonjs
module.exports = { polyfill: polyfill };
} else {
// global
polyfill();
} */