mirror of
https://github.com/danog/telegram-tt.git
synced 2024-11-27 12:55:11 +01:00
Message / Context Menu: Add scrollbars, better calculation of position and size (#1405)
This commit is contained in:
parent
4ee26fb38d
commit
fbdb13e5a2
@ -1,5 +1,5 @@
|
||||
import React, {
|
||||
FC, memo, useCallback, useEffect, useMemo, useState,
|
||||
FC, memo, useCallback, useMemo, useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { withGlobal } from '../../../lib/teact/teactn';
|
||||
|
||||
@ -7,7 +7,6 @@ import { GlobalActions, MessageListType } from '../../../global/types';
|
||||
import { ApiMessage } from '../../../api/types';
|
||||
import { IAlbum, IAnchorPosition } from '../../../types';
|
||||
import { selectAllowedMessageActions, selectCurrentMessageList } from '../../../modules/selectors';
|
||||
import { disableScrolling, enableScrolling } from '../../../util/scrollLock';
|
||||
import { pick } from '../../../util/iteratees';
|
||||
import useShowTransition from '../../../hooks/useShowTransition';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
@ -206,12 +205,6 @@ const ContextMenuContainer: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
closeMenu();
|
||||
}, [chatUsername, closeMenu, message.chatId, message.id]);
|
||||
|
||||
useEffect(() => {
|
||||
disableScrolling();
|
||||
|
||||
return enableScrolling;
|
||||
}, []);
|
||||
|
||||
const reportMessageIds = useMemo(() => (album ? album.messages : [message]).map(({ id }) => id), [album, message]);
|
||||
|
||||
if (noOptions) {
|
||||
|
@ -5,10 +5,11 @@
|
||||
.bubble {
|
||||
transform: scale(0.5);
|
||||
transition: opacity .15s cubic-bezier(0.2, 0, 0.2, 1), transform .15s cubic-bezier(0.2, 0, 0.2, 1) !important;
|
||||
overflow: auto;
|
||||
overflow: overlay;
|
||||
}
|
||||
|
||||
.backdrop {
|
||||
position: absolute;
|
||||
touch-action: none;
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
import React, { FC, useCallback } from '../../../lib/teact/teact';
|
||||
import React, {
|
||||
FC, useCallback, useEffect, useRef,
|
||||
} from '../../../lib/teact/teact';
|
||||
|
||||
import { ApiMessage } from '../../../api/types';
|
||||
import { IAnchorPosition } from '../../../types';
|
||||
|
||||
import { getMessageCopyOptions } from './helpers/copyOptions';
|
||||
import { disableScrolling, enableScrolling } from '../../../util/scrollLock';
|
||||
import useContextMenuPosition from '../../../hooks/useContextMenuPosition';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
|
||||
@ -83,6 +86,8 @@ const MessageContextMenu: FC<OwnProps> = ({
|
||||
onCloseAnimationEnd,
|
||||
onCopyLink,
|
||||
}) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
const copyOptions = getMessageCopyOptions(message, onClose, canCopyLink ? onCopyLink : undefined);
|
||||
|
||||
const getTriggerElement = useCallback(() => {
|
||||
@ -99,7 +104,9 @@ const MessageContextMenu: FC<OwnProps> = ({
|
||||
[],
|
||||
);
|
||||
|
||||
const { positionX, positionY, style } = useContextMenuPosition(
|
||||
const {
|
||||
positionX, positionY, style, menuStyle, withScroll,
|
||||
} = useContextMenuPosition(
|
||||
anchor,
|
||||
getTriggerElement,
|
||||
getRootElement,
|
||||
@ -108,14 +115,22 @@ const MessageContextMenu: FC<OwnProps> = ({
|
||||
(document.querySelector('.MiddleHeader') as HTMLElement).offsetHeight,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
disableScrolling(withScroll ? menuRef.current : undefined);
|
||||
|
||||
return enableScrolling;
|
||||
}, [withScroll]);
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
return (
|
||||
<Menu
|
||||
ref={menuRef}
|
||||
isOpen={isOpen}
|
||||
positionX={positionX}
|
||||
positionY={positionY}
|
||||
style={style}
|
||||
menuStyle={menuStyle}
|
||||
className="MessageContextMenu fluid"
|
||||
onClose={onClose}
|
||||
onCloseAnimationEnd={onCloseAnimationEnd}
|
||||
|
@ -25,6 +25,7 @@
|
||||
border-radius: var(--border-radius-default);
|
||||
min-width: 13.5rem;
|
||||
z-index: var(--z-menu-bubble);
|
||||
overscroll-behavior: contain;
|
||||
|
||||
transform: scale(0.2);
|
||||
transition: opacity .2s cubic-bezier(0.2, 0, 0.2, 1), transform .2s cubic-bezier(0.2, 0, 0.2, 1) !important;
|
||||
|
@ -19,6 +19,7 @@ type OwnProps = {
|
||||
isOpen: boolean;
|
||||
className?: string;
|
||||
style?: string;
|
||||
menuStyle?: string;
|
||||
positionX?: 'left' | 'right';
|
||||
positionY?: 'top' | 'bottom';
|
||||
autoClose?: boolean;
|
||||
@ -41,6 +42,7 @@ const Menu: FC<OwnProps> = ({
|
||||
isOpen,
|
||||
className,
|
||||
style,
|
||||
menuStyle,
|
||||
children,
|
||||
positionX = 'left',
|
||||
positionY = 'top',
|
||||
@ -116,7 +118,7 @@ const Menu: FC<OwnProps> = ({
|
||||
ref={menuRef}
|
||||
className={bubbleClassName}
|
||||
// @ts-ignore teact feature
|
||||
style={`transform-origin: ${positionY} ${positionX}`}
|
||||
style={`transform-origin: ${positionY} ${positionX};${menuStyle || ''}`}
|
||||
onClick={autoClose ? onClose : undefined}
|
||||
>
|
||||
{children}
|
||||
|
@ -2,6 +2,7 @@ import { useState, useEffect } from '../lib/teact/teact';
|
||||
import { IAnchorPosition } from '../types';
|
||||
|
||||
const MENU_POSITION_VISUAL_COMFORT_SPACE_PX = 16;
|
||||
const MENU_POSITION_BOTTOM_MARGIN = 12;
|
||||
|
||||
export default (
|
||||
anchor: IAnchorPosition | undefined,
|
||||
@ -13,7 +14,9 @@ export default (
|
||||
) => {
|
||||
const [positionX, setPositionX] = useState<'right' | 'left'>('right');
|
||||
const [positionY, setPositionY] = useState<'top' | 'bottom'>('bottom');
|
||||
const [withScroll, setWithScroll] = useState(false);
|
||||
const [style, setStyle] = useState('');
|
||||
const [menuStyle, setMenuStyle] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const triggerEl = getTriggerElement();
|
||||
@ -52,15 +55,22 @@ export default (
|
||||
setPositionY('bottom');
|
||||
|
||||
if (y - menuRect.height < rootRect.top + extraTopPadding) {
|
||||
y = rootRect.top + extraTopPadding + menuRect.height;
|
||||
y = rootRect.top + rootRect.height;
|
||||
}
|
||||
}
|
||||
|
||||
const left = horizontalPostition === 'left'
|
||||
? Math.min(x - triggerRect.left, rootRect.width - menuRect.width - MENU_POSITION_VISUAL_COMFORT_SPACE_PX)
|
||||
: Math.max((x - triggerRect.left), menuRect.width + MENU_POSITION_VISUAL_COMFORT_SPACE_PX);
|
||||
const top = Math.min(
|
||||
rootRect.height - triggerRect.top + triggerRect.height - MENU_POSITION_BOTTOM_MARGIN,
|
||||
y - triggerRect.top,
|
||||
);
|
||||
const menuMaxHeight = rootRect.height - MENU_POSITION_BOTTOM_MARGIN;
|
||||
|
||||
setStyle(`left: ${left}px; top: ${y - triggerRect.top}px;`);
|
||||
setWithScroll(menuMaxHeight < menuRect.height);
|
||||
setMenuStyle(`max-height: ${menuMaxHeight}px;`);
|
||||
setStyle(`left: ${left}px; top: ${top}px`);
|
||||
}, [
|
||||
anchor, extraPaddingX, extraTopPadding,
|
||||
getMenuElement, getRootElement, getTriggerElement,
|
||||
@ -70,5 +80,7 @@ export default (
|
||||
positionX,
|
||||
positionY,
|
||||
style,
|
||||
menuStyle,
|
||||
withScroll,
|
||||
};
|
||||
};
|
||||
|
@ -1,3 +1,5 @@
|
||||
let scrollLockEl: HTMLElement | null | undefined;
|
||||
|
||||
const IGNORED_KEYS: Record<string, boolean> = {
|
||||
Down: true,
|
||||
ArrowDown: true,
|
||||
@ -30,27 +32,42 @@ function isTextBox(target: EventTarget | null) {
|
||||
return inputTypes.indexOf(type.toLowerCase()) > -1;
|
||||
}
|
||||
|
||||
const preventDefault = (e: Event) => {
|
||||
e.preventDefault();
|
||||
const getTouchY = (e: WheelEvent | TouchEvent) => ('changedTouches' in e ? e.changedTouches[0].clientY : 0);
|
||||
|
||||
const preventDefault = (e: WheelEvent | TouchEvent) => {
|
||||
const deltaY = 'deltaY' in e ? e.deltaY : getTouchY(e);
|
||||
|
||||
if (
|
||||
!scrollLockEl
|
||||
// Allow overlay scrolling
|
||||
|| !scrollLockEl.contains(e.target as HTMLElement)
|
||||
// Prevent top overscroll
|
||||
|| (scrollLockEl.scrollTop <= 0 && deltaY <= 0)
|
||||
// Prevent bottom overscroll
|
||||
|| (scrollLockEl.scrollTop >= (scrollLockEl.scrollHeight - scrollLockEl.offsetHeight) && deltaY >= 0)
|
||||
) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
function preventDefaultForScrollKeys(e: KeyboardEvent) {
|
||||
if (IGNORED_KEYS[e.key] && !isTextBox(e.target)) {
|
||||
preventDefault(e);
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
export function disableScrolling() {
|
||||
export function disableScrolling(el?: HTMLElement | null) {
|
||||
scrollLockEl = el;
|
||||
// Disable scrolling in Chrome
|
||||
document.addEventListener('wheel', preventDefault, { passive: false });
|
||||
window.ontouchmove = preventDefault; // mobile
|
||||
document.addEventListener('touchmove', preventDefault, { passive: false });
|
||||
document.onkeydown = preventDefaultForScrollKeys;
|
||||
}
|
||||
|
||||
export function enableScrolling() {
|
||||
scrollLockEl = undefined;
|
||||
document.removeEventListener('wheel', preventDefault); // Enable scrolling in Chrome
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
window.ontouchmove = null;
|
||||
document.removeEventListener('touchmove', preventDefault);
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
document.onkeydown = null;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user