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.
251 lines
7.2 KiB
251 lines
7.2 KiB
/* |
|
* https://github.com/morethanwords/tweb |
|
* Copyright (C) 2019-2021 Eduard Kuzmenko |
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE |
|
*/ |
|
|
|
import {attachClickEvent} from './dom/clickEvent'; |
|
import findUpAsChild from './dom/findUpAsChild'; |
|
import EventListenerBase from './eventListenerBase'; |
|
import ListenerSetter from './listenerSetter'; |
|
import IS_TOUCH_SUPPORTED from '../environment/touchSupport'; |
|
import safeAssign from './object/safeAssign'; |
|
import appNavigationController, {NavigationItem} from '../components/appNavigationController'; |
|
import findUpClassName from './dom/findUpClassName'; |
|
import rootScope from '../lib/rootScope'; |
|
import liteMode from './liteMode'; |
|
|
|
const KEEP_OPEN = false; |
|
const TOGGLE_TIMEOUT = 200; |
|
const ANIMATION_DURATION = 200; |
|
|
|
export type IgnoreMouseOutType = 'click' | 'menu' | 'popup'; |
|
type DropdownHoverTimeoutType = 'toggle' | 'done'; |
|
|
|
export default class DropdownHover extends EventListenerBase<{ |
|
open: () => Promise<any> | void, |
|
openAfterLayout: () => void, |
|
opened: () => any, |
|
close: () => any, |
|
closed: () => any |
|
}> { |
|
protected element: HTMLElement; |
|
protected forceClose: boolean; |
|
protected inited: boolean; |
|
protected ignoreMouseOut: Set<IgnoreMouseOutType>; |
|
protected ignoreButtons: Set<HTMLElement>; |
|
protected navigationItem: NavigationItem; |
|
protected ignoreOutClickClassName: string; |
|
protected timeouts: {[type in DropdownHoverTimeoutType]?: number}; |
|
protected detachClickEvent: () => void; |
|
|
|
constructor(options: { |
|
element: DropdownHover['element'], |
|
ignoreOutClickClassName?: string |
|
}) { |
|
super(false); |
|
safeAssign(this, options); |
|
this.forceClose = false; |
|
this.inited = false; |
|
this.ignoreMouseOut = new Set(); |
|
this.ignoreButtons = new Set(); |
|
this.timeouts = {}; |
|
} |
|
|
|
public attachButtonListener( |
|
button: HTMLElement, |
|
listenerSetter: ListenerSetter |
|
) { |
|
let firstTime = true; |
|
if(IS_TOUCH_SUPPORTED) { |
|
attachClickEvent(button, () => { |
|
if(firstTime) { |
|
firstTime = false; |
|
this.toggle(true); |
|
} else { |
|
this.toggle(); |
|
} |
|
}, {listenerSetter}); |
|
} else { |
|
listenerSetter.add(button)('mouseover', (e) => { |
|
if(firstTime) { |
|
listenerSetter.add(button)('mouseout', (e) => { |
|
this.clearTimeout('toggle'); |
|
this.onMouseOut(e); |
|
}); |
|
firstTime = false; |
|
} |
|
|
|
this.setTimeout('toggle', () => { |
|
this.toggle(true); |
|
}, TOGGLE_TIMEOUT); |
|
}); |
|
|
|
attachClickEvent(button, () => { |
|
const type: IgnoreMouseOutType = 'click'; |
|
const ignore = !this.ignoreMouseOut.has(type); |
|
|
|
if(ignore && !this.ignoreMouseOut.size) { |
|
this.ignoreButtons.add(button); |
|
setTimeout(() => { |
|
this.detachClickEvent = attachClickEvent(window, this.onClickOut, {capture: true}); |
|
}, 0); |
|
} |
|
|
|
this.setIgnoreMouseOut(type, ignore); |
|
this.toggle(ignore); |
|
}, {listenerSetter}); |
|
} |
|
} |
|
|
|
protected onClickOut = (e: MouseEvent) => { |
|
const target = e.target as HTMLElement; |
|
if( |
|
!findUpAsChild(target, this.element) && |
|
!Array.from(this.ignoreButtons).some((button) => findUpAsChild(target, button) || target === button) && |
|
this.ignoreMouseOut.size <= 1 && |
|
(!this.ignoreOutClickClassName || !findUpClassName(target, this.ignoreOutClickClassName)) |
|
) { |
|
this.toggle(false); |
|
} |
|
}; |
|
|
|
protected onMouseOut = (e: MouseEvent) => { |
|
if(KEEP_OPEN || !this.isActive()) return; |
|
this.clearTimeout('toggle'); |
|
|
|
if(this.ignoreMouseOut.size) { |
|
return; |
|
} |
|
|
|
const toElement = (e as any).toElement as HTMLElement; |
|
if(toElement && findUpAsChild(toElement, this.element)) { |
|
return; |
|
} |
|
|
|
this.setTimeout('toggle', () => { |
|
this.toggle(false); |
|
}, TOGGLE_TIMEOUT); |
|
}; |
|
|
|
protected clearTimeout(type: DropdownHoverTimeoutType) { |
|
if(this.timeouts[type] !== undefined) { |
|
clearTimeout(this.timeouts[type]); |
|
delete this.timeouts[type]; |
|
} |
|
} |
|
|
|
protected setTimeout(type: DropdownHoverTimeoutType, cb: () => void, timeout: number) { |
|
this.clearTimeout(type); |
|
this.timeouts[type] = window.setTimeout(() => { |
|
this.clearTimeout(type); |
|
cb(); |
|
}, timeout); |
|
} |
|
|
|
public init() { |
|
if(!IS_TOUCH_SUPPORTED) { |
|
this.element.onmouseout = this.onMouseOut; |
|
this.element.onmouseover = (e) => { |
|
if(this.forceClose) { |
|
return; |
|
} |
|
|
|
// console.log('onmouseover element'); |
|
this.clearTimeout('toggle'); |
|
}; |
|
} |
|
} |
|
|
|
public toggle = async(enable?: boolean) => { |
|
// if(!this.element) return; |
|
const willBeActive = (!!this.element.style.display && enable === undefined) || enable; |
|
if(this.init) { |
|
if(willBeActive) { |
|
this.init(); |
|
this.init = null; |
|
} else { |
|
return; |
|
} |
|
} |
|
|
|
if(willBeActive === this.isActive()) { |
|
return; |
|
} |
|
|
|
const delay = IS_TOUCH_SUPPORTED || !liteMode.isAvailable('animations') ? 0 : ANIMATION_DURATION; |
|
if((this.element.style.display && enable === undefined) || enable) { |
|
const res = this.dispatchResultableEvent('open'); |
|
await Promise.all(res); |
|
|
|
this.element.style.display = ''; |
|
void this.element.offsetLeft; // reflow |
|
this.element.classList.add('active'); |
|
|
|
this.dispatchEvent('openAfterLayout'); |
|
|
|
appNavigationController.pushItem(this.navigationItem = { |
|
type: 'dropdown', |
|
onPop: () => { |
|
this.toggle(false); |
|
} |
|
}); |
|
|
|
this.clearTimeout('toggle'); |
|
this.setTimeout('done', () => { |
|
this.forceClose = false; |
|
this.dispatchEvent('opened'); |
|
}, delay); |
|
|
|
// ! can't use together with resizeObserver |
|
/* if(isTouchSupported) { |
|
const height = this.element.scrollHeight + appImManager.chat.input.inputContainer.scrollHeight - 10; |
|
console.log('[ESG]: toggle: enable height', height); |
|
appImManager.chat.bubbles.scrollable.scrollTop += height; |
|
} */ |
|
|
|
/* if(touchSupport) { |
|
this.restoreScroll(); |
|
} */ |
|
} else { |
|
this.dispatchEvent('close'); |
|
this.ignoreMouseOut.clear(); |
|
this.ignoreButtons.clear(); |
|
|
|
this.element.classList.remove('active'); |
|
|
|
appNavigationController.removeItem(this.navigationItem); |
|
this.detachClickEvent?.(); |
|
this.detachClickEvent = undefined; |
|
|
|
this.clearTimeout('toggle'); |
|
this.setTimeout('done', () => { |
|
this.element.style.display = 'none'; |
|
this.forceClose = false; |
|
this.dispatchEvent('closed'); |
|
}, delay); |
|
|
|
/* if(isTouchSupported) { |
|
const scrollHeight = this.container.scrollHeight; |
|
if(scrollHeight) { |
|
const height = this.container.scrollHeight + appImManager.chat.input.inputContainer.scrollHeight - 10; |
|
appImManager.chat.bubbles.scrollable.scrollTop -= height; |
|
} |
|
} */ |
|
|
|
/* if(touchSupport) { |
|
this.restoreScroll(); |
|
} */ |
|
} |
|
|
|
// animationIntersector.checkAnimations(false, EMOTICONSSTICKERGROUP); |
|
}; |
|
|
|
public isActive() { |
|
return this.element.classList.contains('active'); |
|
} |
|
|
|
public setIgnoreMouseOut(type: IgnoreMouseOutType, ignore: boolean) { |
|
ignore ? this.ignoreMouseOut.add(type) : this.ignoreMouseOut.delete(type); |
|
} |
|
}
|
|
|