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
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(); |
|
} */ |